summaryrefslogtreecommitdiffstats
path: root/usr.sbin
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin')
-rw-r--r--usr.sbin/Makefile216
-rw-r--r--usr.sbin/Makefile.amd6434
-rw-r--r--usr.sbin/Makefile.arm4
-rw-r--r--usr.sbin/Makefile.arm646
-rw-r--r--usr.sbin/Makefile.i38638
-rw-r--r--usr.sbin/Makefile.inc6
-rw-r--r--usr.sbin/Makefile.mips3
-rw-r--r--usr.sbin/Makefile.powerpc4
-rw-r--r--usr.sbin/Makefile.sparc644
-rw-r--r--usr.sbin/ac/Makefile18
-rw-r--r--usr.sbin/ac/Makefile.depend18
-rw-r--r--usr.sbin/ac/ac.8148
-rw-r--r--usr.sbin/ac/ac.c521
-rw-r--r--usr.sbin/accton/Makefile7
-rw-r--r--usr.sbin/accton/Makefile.depend18
-rw-r--r--usr.sbin/accton/accton.842
-rw-r--r--usr.sbin/accton/accton.c87
-rw-r--r--usr.sbin/acpi/Makefile7
-rw-r--r--usr.sbin/acpi/Makefile.inc25
-rw-r--r--usr.sbin/acpi/acpiconf/Makefile8
-rw-r--r--usr.sbin/acpi/acpiconf/Makefile.depend18
-rw-r--r--usr.sbin/acpi/acpiconf/acpiconf.894
-rw-r--r--usr.sbin/acpi/acpiconf/acpiconf.c247
-rw-r--r--usr.sbin/acpi/acpidb/Makefile79
-rw-r--r--usr.sbin/acpi/acpidb/Makefile.depend19
-rw-r--r--usr.sbin/acpi/acpidb/acpidb.8167
-rw-r--r--usr.sbin/acpi/acpidb/acpidb.c527
-rw-r--r--usr.sbin/acpi/acpidump/Makefile8
-rw-r--r--usr.sbin/acpi/acpidump/Makefile.depend18
-rw-r--r--usr.sbin/acpi/acpidump/acpi.c1577
-rw-r--r--usr.sbin/acpi/acpidump/acpi_user.c222
-rw-r--r--usr.sbin/acpi/acpidump/acpidump.8200
-rw-r--r--usr.sbin/acpi/acpidump/acpidump.c144
-rw-r--r--usr.sbin/acpi/acpidump/acpidump.h158
-rw-r--r--usr.sbin/acpi/iasl/Makefile121
-rw-r--r--usr.sbin/acpi/iasl/Makefile.depend107
-rw-r--r--usr.sbin/acpi/iasl/iasl.8179
-rw-r--r--usr.sbin/adduser/Makefile6
-rw-r--r--usr.sbin/adduser/Makefile.depend11
-rw-r--r--usr.sbin/adduser/adduser.8479
-rw-r--r--usr.sbin/adduser/adduser.conf.5221
-rw-r--r--usr.sbin/adduser/adduser.sh1051
-rw-r--r--usr.sbin/adduser/rmuser.8210
-rw-r--r--usr.sbin/adduser/rmuser.sh361
-rw-r--r--usr.sbin/amd/Makefile13
-rw-r--r--usr.sbin/amd/Makefile.inc40
-rw-r--r--usr.sbin/amd/NOTES3
-rw-r--r--usr.sbin/amd/amd/Makefile52
-rw-r--r--usr.sbin/amd/amd/Makefile.depend33
-rw-r--r--usr.sbin/amd/amq/Makefile19
-rw-r--r--usr.sbin/amd/amq/Makefile.depend24
-rw-r--r--usr.sbin/amd/fixmount/Makefile20
-rw-r--r--usr.sbin/amd/fixmount/Makefile.depend25
-rw-r--r--usr.sbin/amd/fsinfo/Makefile24
-rw-r--r--usr.sbin/amd/fsinfo/Makefile.depend33
-rw-r--r--usr.sbin/amd/hlfsd/Makefile18
-rw-r--r--usr.sbin/amd/hlfsd/Makefile.depend24
-rw-r--r--usr.sbin/amd/include/Makefile29
-rw-r--r--usr.sbin/amd/include/Makefile.depend11
-rw-r--r--usr.sbin/amd/include/amu_autofs_prot.h8
-rw-r--r--usr.sbin/amd/include/amu_nfs_prot.h1
-rw-r--r--usr.sbin/amd/include/aux_conf.h72
-rw-r--r--usr.sbin/amd/include/build_version.h8
-rw-r--r--usr.sbin/amd/include/config.h2202
-rw-r--r--usr.sbin/amd/include/newvers.sh34
-rw-r--r--usr.sbin/amd/libamu/Makefile36
-rw-r--r--usr.sbin/amd/libamu/Makefile.depend22
-rw-r--r--usr.sbin/amd/mk-amd-map/Makefile15
-rw-r--r--usr.sbin/amd/mk-amd-map/Makefile.depend24
-rw-r--r--usr.sbin/amd/pawd/Makefile19
-rw-r--r--usr.sbin/amd/pawd/Makefile.depend24
-rw-r--r--usr.sbin/amd/scripts/Makefile7
-rw-r--r--usr.sbin/amd/scripts/Makefile.depend11
-rw-r--r--usr.sbin/amd/wire-test/Makefile15
-rw-r--r--usr.sbin/amd/wire-test/Makefile.depend24
-rw-r--r--usr.sbin/ancontrol/Makefile11
-rw-r--r--usr.sbin/ancontrol/Makefile.depend20
-rw-r--r--usr.sbin/ancontrol/ancontrol.8553
-rw-r--r--usr.sbin/ancontrol/ancontrol.c1779
-rw-r--r--usr.sbin/apm/Makefile8
-rw-r--r--usr.sbin/apm/Makefile.depend18
-rw-r--r--usr.sbin/apm/apm.8153
-rw-r--r--usr.sbin/apm/apm.c490
-rw-r--r--usr.sbin/apmd/Makefile17
-rw-r--r--usr.sbin/apmd/Makefile.depend22
-rw-r--r--usr.sbin/apmd/README213
-rw-r--r--usr.sbin/apmd/apmd.8322
-rw-r--r--usr.sbin/apmd/apmd.c705
-rw-r--r--usr.sbin/apmd/apmd.h133
-rw-r--r--usr.sbin/apmd/apmdlex.l116
-rw-r--r--usr.sbin/apmd/apmdparse.y205
-rw-r--r--usr.sbin/apmd/contrib/pccardq.c286
-rw-r--r--usr.sbin/arp/Makefile9
-rw-r--r--usr.sbin/arp/Makefile.depend19
-rw-r--r--usr.sbin/arp/arp.4235
-rw-r--r--usr.sbin/arp/arp.8192
-rw-r--r--usr.sbin/arp/arp.c882
-rw-r--r--usr.sbin/asf/Makefile9
-rw-r--r--usr.sbin/asf/Makefile.depend20
-rw-r--r--usr.sbin/asf/asf.8179
-rw-r--r--usr.sbin/asf/asf.c427
-rw-r--r--usr.sbin/asf/asf.h40
-rw-r--r--usr.sbin/asf/asf_kld.c59
-rw-r--r--usr.sbin/asf/asf_kvm.c128
-rw-r--r--usr.sbin/asf/asf_prog.c73
-rw-r--r--usr.sbin/audit/Makefile17
-rw-r--r--usr.sbin/audit/Makefile.depend19
-rw-r--r--usr.sbin/auditd/Makefile18
-rw-r--r--usr.sbin/auditd/Makefile.depend20
-rw-r--r--usr.sbin/auditdistd/Makefile34
-rw-r--r--usr.sbin/auditdistd/Makefile.depend31
-rw-r--r--usr.sbin/auditreduce/Makefile17
-rw-r--r--usr.sbin/auditreduce/Makefile.depend19
-rw-r--r--usr.sbin/authpf/Makefile23
-rw-r--r--usr.sbin/authpf/Makefile.depend21
-rw-r--r--usr.sbin/autofs/Makefile32
-rw-r--r--usr.sbin/autofs/Makefile.depend21
-rw-r--r--usr.sbin/autofs/auto_master.5374
-rw-r--r--usr.sbin/autofs/automount.8111
-rw-r--r--usr.sbin/autofs/automount.c396
-rw-r--r--usr.sbin/autofs/automountd.8103
-rw-r--r--usr.sbin/autofs/automountd.c569
-rw-r--r--usr.sbin/autofs/autounmountd.888
-rw-r--r--usr.sbin/autofs/autounmountd.c351
-rw-r--r--usr.sbin/autofs/common.c1225
-rw-r--r--usr.sbin/autofs/common.h114
-rw-r--r--usr.sbin/autofs/defined.c272
-rw-r--r--usr.sbin/autofs/log.c198
-rw-r--r--usr.sbin/autofs/popen.c193
-rw-r--r--usr.sbin/autofs/token.l58
-rw-r--r--usr.sbin/bhyve/Makefile52
-rw-r--r--usr.sbin/bhyve/Makefile.depend22
-rw-r--r--usr.sbin/bhyve/acpi.c1012
-rw-r--r--usr.sbin/bhyve/acpi.h54
-rw-r--r--usr.sbin/bhyve/ahci.h322
-rw-r--r--usr.sbin/bhyve/atkbdc.c90
-rw-r--r--usr.sbin/bhyve/bhyve.8352
-rw-r--r--usr.sbin/bhyve/bhyverun.c971
-rw-r--r--usr.sbin/bhyve/bhyverun.h55
-rw-r--r--usr.sbin/bhyve/block_if.c822
-rw-r--r--usr.sbin/bhyve/block_if.h70
-rw-r--r--usr.sbin/bhyve/bootrom.c111
-rw-r--r--usr.sbin/bhyve/bootrom.h38
-rw-r--r--usr.sbin/bhyve/consport.c153
-rw-r--r--usr.sbin/bhyve/dbgport.c151
-rw-r--r--usr.sbin/bhyve/dbgport.h34
-rw-r--r--usr.sbin/bhyve/fwctl.c549
-rw-r--r--usr.sbin/bhyve/fwctl.h54
-rw-r--r--usr.sbin/bhyve/inout.c297
-rw-r--r--usr.sbin/bhyve/inout.h79
-rw-r--r--usr.sbin/bhyve/ioapic.c74
-rw-r--r--usr.sbin/bhyve/ioapic.h39
-rw-r--r--usr.sbin/bhyve/mem.c291
-rw-r--r--usr.sbin/bhyve/mem.h61
-rw-r--r--usr.sbin/bhyve/mevent.c456
-rw-r--r--usr.sbin/bhyve/mevent.h51
-rw-r--r--usr.sbin/bhyve/mevent_test.c256
-rw-r--r--usr.sbin/bhyve/mptbl.c377
-rw-r--r--usr.sbin/bhyve/mptbl.h35
-rw-r--r--usr.sbin/bhyve/pci_ahci.c2354
-rw-r--r--usr.sbin/bhyve/pci_emul.c2107
-rw-r--r--usr.sbin/bhyve/pci_emul.h283
-rw-r--r--usr.sbin/bhyve/pci_hostbridge.c70
-rw-r--r--usr.sbin/bhyve/pci_irq.c346
-rw-r--r--usr.sbin/bhyve/pci_irq.h45
-rw-r--r--usr.sbin/bhyve/pci_lpc.c450
-rw-r--r--usr.sbin/bhyve/pci_lpc.h73
-rw-r--r--usr.sbin/bhyve/pci_passthru.c796
-rw-r--r--usr.sbin/bhyve/pci_uart.c119
-rw-r--r--usr.sbin/bhyve/pci_virtio_block.c410
-rw-r--r--usr.sbin/bhyve/pci_virtio_net.c730
-rw-r--r--usr.sbin/bhyve/pci_virtio_rnd.c189
-rw-r--r--usr.sbin/bhyve/pm.c312
-rw-r--r--usr.sbin/bhyve/post.c53
-rw-r--r--usr.sbin/bhyve/rtc.c129
-rw-r--r--usr.sbin/bhyve/rtc.h34
-rw-r--r--usr.sbin/bhyve/smbiostbl.c827
-rw-r--r--usr.sbin/bhyve/smbiostbl.h36
-rw-r--r--usr.sbin/bhyve/spinup_ap.c104
-rw-r--r--usr.sbin/bhyve/spinup_ap.h34
-rw-r--r--usr.sbin/bhyve/task_switch.c939
-rw-r--r--usr.sbin/bhyve/uart_emul.c674
-rw-r--r--usr.sbin/bhyve/uart_emul.h45
-rw-r--r--usr.sbin/bhyve/virtio.c777
-rw-r--r--usr.sbin/bhyve/virtio.h464
-rw-r--r--usr.sbin/bhyve/xmsr.c230
-rw-r--r--usr.sbin/bhyve/xmsr.h36
-rw-r--r--usr.sbin/bhyvectl/Makefile16
-rw-r--r--usr.sbin/bhyvectl/Makefile.depend20
-rw-r--r--usr.sbin/bhyvectl/bhyvectl.c2219
-rw-r--r--usr.sbin/bhyveload/Makefile13
-rw-r--r--usr.sbin/bhyveload/Makefile.depend20
-rw-r--r--usr.sbin/bhyveload/bhyveload.8173
-rw-r--r--usr.sbin/bhyveload/bhyveload.c772
-rw-r--r--usr.sbin/binmiscctl/Makefile10
-rw-r--r--usr.sbin/binmiscctl/Makefile.depend18
-rw-r--r--usr.sbin/binmiscctl/binmiscctl.8309
-rw-r--r--usr.sbin/binmiscctl/binmiscctl.c510
-rw-r--r--usr.sbin/bluetooth/Makefile26
-rw-r--r--usr.sbin/bluetooth/Makefile.inc4
-rw-r--r--usr.sbin/bluetooth/ath3kfw/Makefile7
-rw-r--r--usr.sbin/bluetooth/ath3kfw/Makefile.depend20
-rw-r--r--usr.sbin/bluetooth/ath3kfw/ath3kfw.878
-rw-r--r--usr.sbin/bluetooth/ath3kfw/ath3kfw.c297
-rw-r--r--usr.sbin/bluetooth/bcmfw/BCM-LEGAL.txt8
-rw-r--r--usr.sbin/bluetooth/bcmfw/Makefile8
-rw-r--r--usr.sbin/bluetooth/bcmfw/Makefile.depend19
-rw-r--r--usr.sbin/bluetooth/bcmfw/README22
-rw-r--r--usr.sbin/bluetooth/bcmfw/bcmfw.8105
-rw-r--r--usr.sbin/bluetooth/bcmfw/bcmfw.c308
-rw-r--r--usr.sbin/bluetooth/bt3cfw/Makefile10
-rw-r--r--usr.sbin/bluetooth/bt3cfw/Makefile.depend19
-rw-r--r--usr.sbin/bluetooth/bt3cfw/bt3cfw.873
-rw-r--r--usr.sbin/bluetooth/bt3cfw/bt3cfw.c228
-rw-r--r--usr.sbin/bluetooth/bthidcontrol/Makefile14
-rw-r--r--usr.sbin/bluetooth/bthidcontrol/Makefile.depend28
-rw-r--r--usr.sbin/bluetooth/bthidcontrol/bthidcontrol.8102
-rw-r--r--usr.sbin/bluetooth/bthidcontrol/bthidcontrol.c216
-rw-r--r--usr.sbin/bluetooth/bthidcontrol/bthidcontrol.h50
-rw-r--r--usr.sbin/bluetooth/bthidcontrol/hid.c215
-rw-r--r--usr.sbin/bluetooth/bthidcontrol/sdp.c435
-rw-r--r--usr.sbin/bluetooth/bthidd/Makefile16
-rw-r--r--usr.sbin/bluetooth/bthidd/Makefile.depend27
-rw-r--r--usr.sbin/bluetooth/bthidd/bthid_config.h70
-rw-r--r--usr.sbin/bluetooth/bthidd/bthidd.8127
-rw-r--r--usr.sbin/bluetooth/bthidd/bthidd.c267
-rw-r--r--usr.sbin/bluetooth/bthidd/bthidd.conf.sample72
-rw-r--r--usr.sbin/bluetooth/bthidd/bthidd.h93
-rw-r--r--usr.sbin/bluetooth/bthidd/client.c259
-rw-r--r--usr.sbin/bluetooth/bthidd/hid.c415
-rw-r--r--usr.sbin/bluetooth/bthidd/kbd.c581
-rw-r--r--usr.sbin/bluetooth/bthidd/kbd.h41
-rw-r--r--usr.sbin/bluetooth/bthidd/lexer.l106
-rw-r--r--usr.sbin/bluetooth/bthidd/parser.y476
-rw-r--r--usr.sbin/bluetooth/bthidd/server.c352
-rw-r--r--usr.sbin/bluetooth/bthidd/session.c185
-rw-r--r--usr.sbin/bluetooth/btpand/Makefile12
-rw-r--r--usr.sbin/bluetooth/btpand/Makefile.depend21
-rw-r--r--usr.sbin/bluetooth/btpand/bnep.c756
-rw-r--r--usr.sbin/bluetooth/btpand/bnep.h72
-rw-r--r--usr.sbin/bluetooth/btpand/btpand.8240
-rw-r--r--usr.sbin/bluetooth/btpand/btpand.c294
-rw-r--r--usr.sbin/bluetooth/btpand/btpand.h212
-rw-r--r--usr.sbin/bluetooth/btpand/channel.c335
-rw-r--r--usr.sbin/bluetooth/btpand/client.c228
-rw-r--r--usr.sbin/bluetooth/btpand/event.c310
-rw-r--r--usr.sbin/bluetooth/btpand/event.h147
-rw-r--r--usr.sbin/bluetooth/btpand/packet.c111
-rw-r--r--usr.sbin/bluetooth/btpand/sdp.c210
-rw-r--r--usr.sbin/bluetooth/btpand/sdp.h38
-rw-r--r--usr.sbin/bluetooth/btpand/server.c293
-rw-r--r--usr.sbin/bluetooth/btpand/tap.c168
-rw-r--r--usr.sbin/bluetooth/hccontrol/Makefile13
-rw-r--r--usr.sbin/bluetooth/hccontrol/Makefile.depend19
-rw-r--r--usr.sbin/bluetooth/hccontrol/hccontrol.8184
-rw-r--r--usr.sbin/bluetooth/hccontrol/hccontrol.c330
-rw-r--r--usr.sbin/bluetooth/hccontrol/hccontrol.h80
-rw-r--r--usr.sbin/bluetooth/hccontrol/host_controller_baseband.c1961
-rw-r--r--usr.sbin/bluetooth/hccontrol/info.c217
-rw-r--r--usr.sbin/bluetooth/hccontrol/le.c356
-rw-r--r--usr.sbin/bluetooth/hccontrol/link_control.c961
-rw-r--r--usr.sbin/bluetooth/hccontrol/link_policy.c306
-rw-r--r--usr.sbin/bluetooth/hccontrol/node.c608
-rw-r--r--usr.sbin/bluetooth/hccontrol/send_recv.c184
-rw-r--r--usr.sbin/bluetooth/hccontrol/status.c245
-rw-r--r--usr.sbin/bluetooth/hccontrol/util.c421
-rw-r--r--usr.sbin/bluetooth/hcsecd/Makefile12
-rw-r--r--usr.sbin/bluetooth/hcsecd/Makefile.depend26
-rw-r--r--usr.sbin/bluetooth/hcsecd/hcsecd.8128
-rw-r--r--usr.sbin/bluetooth/hcsecd/hcsecd.c447
-rw-r--r--usr.sbin/bluetooth/hcsecd/hcsecd.conf64
-rw-r--r--usr.sbin/bluetooth/hcsecd/hcsecd.conf.5131
-rw-r--r--usr.sbin/bluetooth/hcsecd/hcsecd.h65
-rw-r--r--usr.sbin/bluetooth/hcsecd/lexer.l95
-rw-r--r--usr.sbin/bluetooth/hcsecd/parser.y435
-rw-r--r--usr.sbin/bluetooth/hcseriald/Makefile10
-rw-r--r--usr.sbin/bluetooth/hcseriald/Makefile.depend19
-rw-r--r--usr.sbin/bluetooth/hcseriald/hcseriald.886
-rw-r--r--usr.sbin/bluetooth/hcseriald/hcseriald.c268
-rw-r--r--usr.sbin/bluetooth/l2control/Makefile11
-rw-r--r--usr.sbin/bluetooth/l2control/Makefile.depend19
-rw-r--r--usr.sbin/bluetooth/l2control/l2cap.c314
-rw-r--r--usr.sbin/bluetooth/l2control/l2control.897
-rw-r--r--usr.sbin/bluetooth/l2control/l2control.c221
-rw-r--r--usr.sbin/bluetooth/l2control/l2control.h49
-rw-r--r--usr.sbin/bluetooth/l2ping/Makefile10
-rw-r--r--usr.sbin/bluetooth/l2ping/Makefile.depend20
-rw-r--r--usr.sbin/bluetooth/l2ping/l2ping.8114
-rw-r--r--usr.sbin/bluetooth/l2ping/l2ping.c293
-rw-r--r--usr.sbin/bluetooth/rfcomm_pppd/Makefile13
-rw-r--r--usr.sbin/bluetooth/rfcomm_pppd/Makefile.depend20
-rw-r--r--usr.sbin/bluetooth/rfcomm_pppd/rfcomm_pppd.8354
-rw-r--r--usr.sbin/bluetooth/rfcomm_pppd/rfcomm_pppd.c471
-rw-r--r--usr.sbin/bluetooth/sdpcontrol/Makefile11
-rw-r--r--usr.sbin/bluetooth/sdpcontrol/Makefile.depend20
-rw-r--r--usr.sbin/bluetooth/sdpcontrol/sdpcontrol.8118
-rw-r--r--usr.sbin/bluetooth/sdpcontrol/sdpcontrol.c220
-rw-r--r--usr.sbin/bluetooth/sdpcontrol/sdpcontrol.h49
-rw-r--r--usr.sbin/bluetooth/sdpcontrol/search.c748
-rw-r--r--usr.sbin/bluetooth/sdpd/Makefile13
-rw-r--r--usr.sbin/bluetooth/sdpd/Makefile.depend21
-rw-r--r--usr.sbin/bluetooth/sdpd/bgd.c102
-rw-r--r--usr.sbin/bluetooth/sdpd/dun.c137
-rw-r--r--usr.sbin/bluetooth/sdpd/ftrn.c118
-rw-r--r--usr.sbin/bluetooth/sdpd/gn.c173
-rw-r--r--usr.sbin/bluetooth/sdpd/irmc.c134
-rw-r--r--usr.sbin/bluetooth/sdpd/irmc_command.c118
-rw-r--r--usr.sbin/bluetooth/sdpd/lan.c173
-rw-r--r--usr.sbin/bluetooth/sdpd/log.c127
-rw-r--r--usr.sbin/bluetooth/sdpd/log.h47
-rw-r--r--usr.sbin/bluetooth/sdpd/main.c236
-rw-r--r--usr.sbin/bluetooth/sdpd/nap.c210
-rw-r--r--usr.sbin/bluetooth/sdpd/opush.c134
-rw-r--r--usr.sbin/bluetooth/sdpd/panu.c173
-rw-r--r--usr.sbin/bluetooth/sdpd/profile.c498
-rw-r--r--usr.sbin/bluetooth/sdpd/profile.h96
-rw-r--r--usr.sbin/bluetooth/sdpd/provider.c197
-rw-r--r--usr.sbin/bluetooth/sdpd/provider.h75
-rw-r--r--usr.sbin/bluetooth/sdpd/sar.c318
-rw-r--r--usr.sbin/bluetooth/sdpd/scr.c93
-rw-r--r--usr.sbin/bluetooth/sdpd/sd.c229
-rw-r--r--usr.sbin/bluetooth/sdpd/sdpd.8140
-rw-r--r--usr.sbin/bluetooth/sdpd/server.c590
-rw-r--r--usr.sbin/bluetooth/sdpd/server.h102
-rw-r--r--usr.sbin/bluetooth/sdpd/sp.c118
-rw-r--r--usr.sbin/bluetooth/sdpd/srr.c140
-rw-r--r--usr.sbin/bluetooth/sdpd/ssar.c253
-rw-r--r--usr.sbin/bluetooth/sdpd/ssr.c283
-rw-r--r--usr.sbin/bluetooth/sdpd/sur.c84
-rw-r--r--usr.sbin/bluetooth/sdpd/uuid-private.h39
-rw-r--r--usr.sbin/bluetooth/sdpd/uuid.c56
-rw-r--r--usr.sbin/boot0cfg/Makefile10
-rw-r--r--usr.sbin/boot0cfg/Makefile.depend21
-rw-r--r--usr.sbin/boot0cfg/boot0cfg.8204
-rw-r--r--usr.sbin/boot0cfg/boot0cfg.c607
-rw-r--r--usr.sbin/boot98cfg/Makefile10
-rw-r--r--usr.sbin/boot98cfg/Makefile.depend17
-rw-r--r--usr.sbin/boot98cfg/boot98cfg.8104
-rw-r--r--usr.sbin/boot98cfg/boot98cfg.c319
-rw-r--r--usr.sbin/bootparamd/Makefile5
-rw-r--r--usr.sbin/bootparamd/Makefile.inc6
-rw-r--r--usr.sbin/bootparamd/bootparamd/Makefile29
-rw-r--r--usr.sbin/bootparamd/bootparamd/Makefile.depend33
-rw-r--r--usr.sbin/bootparamd/bootparamd/README61
-rw-r--r--usr.sbin/bootparamd/bootparamd/bootparamd.876
-rw-r--r--usr.sbin/bootparamd/bootparamd/bootparamd.c358
-rw-r--r--usr.sbin/bootparamd/bootparamd/bootparams.586
-rw-r--r--usr.sbin/bootparamd/bootparamd/main.c115
-rw-r--r--usr.sbin/bootparamd/callbootd/Makefile24
-rw-r--r--usr.sbin/bootparamd/callbootd/Makefile.depend31
-rw-r--r--usr.sbin/bootparamd/callbootd/callbootd.c204
-rw-r--r--usr.sbin/bsdconfig/Makefile28
-rw-r--r--usr.sbin/bsdconfig/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/USAGE47
-rwxr-xr-xusr.sbin/bsdconfig/bsdconfig428
-rw-r--r--usr.sbin/bsdconfig/bsdconfig.8254
-rw-r--r--usr.sbin/bsdconfig/console/INDEX70
-rw-r--r--usr.sbin/bsdconfig/console/Makefile11
-rw-r--r--usr.sbin/bsdconfig/console/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/console/USAGE37
-rwxr-xr-xusr.sbin/bsdconfig/console/console146
-rwxr-xr-xusr.sbin/bsdconfig/console/font193
-rw-r--r--usr.sbin/bsdconfig/console/include/Makefile6
-rw-r--r--usr.sbin/bsdconfig/console/include/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/console/include/messages.subr270
-rwxr-xr-xusr.sbin/bsdconfig/console/keymap332
-rwxr-xr-xusr.sbin/bsdconfig/console/repeat143
-rwxr-xr-xusr.sbin/bsdconfig/console/saver195
-rwxr-xr-xusr.sbin/bsdconfig/console/screenmap155
-rwxr-xr-xusr.sbin/bsdconfig/console/ttys207
-rw-r--r--usr.sbin/bsdconfig/diskmgmt/INDEX57
-rw-r--r--usr.sbin/bsdconfig/diskmgmt/Makefile11
-rw-r--r--usr.sbin/bsdconfig/diskmgmt/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/diskmgmt/USAGE37
-rwxr-xr-xusr.sbin/bsdconfig/diskmgmt/diskmgmt85
-rw-r--r--usr.sbin/bsdconfig/diskmgmt/include/Makefile6
-rw-r--r--usr.sbin/bsdconfig/diskmgmt/include/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/diskmgmt/include/messages.subr27
-rw-r--r--usr.sbin/bsdconfig/docsinstall/INDEX57
-rw-r--r--usr.sbin/bsdconfig/docsinstall/Makefile11
-rw-r--r--usr.sbin/bsdconfig/docsinstall/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/docsinstall/USAGE37
-rwxr-xr-xusr.sbin/bsdconfig/docsinstall/docsinstall97
-rw-r--r--usr.sbin/bsdconfig/docsinstall/include/Makefile6
-rw-r--r--usr.sbin/bsdconfig/docsinstall/include/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/docsinstall/include/messages.subr28
-rw-r--r--usr.sbin/bsdconfig/dot/INDEX57
-rw-r--r--usr.sbin/bsdconfig/dot/Makefile11
-rw-r--r--usr.sbin/bsdconfig/dot/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/dot/USAGE143
-rwxr-xr-xusr.sbin/bsdconfig/dot/dot678
-rw-r--r--usr.sbin/bsdconfig/dot/include/Makefile6
-rw-r--r--usr.sbin/bsdconfig/dot/include/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/dot/include/messages.subr31
-rw-r--r--usr.sbin/bsdconfig/examples/Makefile6
-rw-r--r--usr.sbin/bsdconfig/examples/Makefile.depend11
-rwxr-xr-xusr.sbin/bsdconfig/examples/add_some_packages.sh13
-rwxr-xr-xusr.sbin/bsdconfig/examples/browse_packages_http.sh32
-rw-r--r--usr.sbin/bsdconfig/examples/bsdconfigrc42
-rw-r--r--usr.sbin/bsdconfig/include/Makefile7
-rw-r--r--usr.sbin/bsdconfig/include/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/include/bsdconfig.hlp12
-rw-r--r--usr.sbin/bsdconfig/include/media.hlp54
-rw-r--r--usr.sbin/bsdconfig/include/messages.subr427
-rw-r--r--usr.sbin/bsdconfig/include/network_device.hlp58
-rw-r--r--usr.sbin/bsdconfig/include/options.hlp115
-rw-r--r--usr.sbin/bsdconfig/include/tcp.hlp33
-rw-r--r--usr.sbin/bsdconfig/include/usage.hlp64
-rw-r--r--usr.sbin/bsdconfig/includes/INDEX57
-rw-r--r--usr.sbin/bsdconfig/includes/Makefile11
-rw-r--r--usr.sbin/bsdconfig/includes/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/includes/USAGE71
-rw-r--r--usr.sbin/bsdconfig/includes/include/Makefile6
-rw-r--r--usr.sbin/bsdconfig/includes/include/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/includes/include/messages.subr28
-rwxr-xr-xusr.sbin/bsdconfig/includes/includes.sh205
-rw-r--r--usr.sbin/bsdconfig/mouse/INDEX62
-rw-r--r--usr.sbin/bsdconfig/mouse/Makefile11
-rw-r--r--usr.sbin/bsdconfig/mouse/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/mouse/USAGE37
-rwxr-xr-xusr.sbin/bsdconfig/mouse/disable97
-rwxr-xr-xusr.sbin/bsdconfig/mouse/enable128
-rwxr-xr-xusr.sbin/bsdconfig/mouse/flags95
-rw-r--r--usr.sbin/bsdconfig/mouse/include/Makefile6
-rw-r--r--usr.sbin/bsdconfig/mouse/include/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/mouse/include/messages.subr93
-rwxr-xr-xusr.sbin/bsdconfig/mouse/mouse144
-rwxr-xr-xusr.sbin/bsdconfig/mouse/port154
-rwxr-xr-xusr.sbin/bsdconfig/mouse/type170
-rw-r--r--usr.sbin/bsdconfig/networking/INDEX61
-rw-r--r--usr.sbin/bsdconfig/networking/Makefile11
-rw-r--r--usr.sbin/bsdconfig/networking/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/networking/USAGE37
-rwxr-xr-xusr.sbin/bsdconfig/networking/defaultrouter76
-rwxr-xr-xusr.sbin/bsdconfig/networking/devices164
-rwxr-xr-xusr.sbin/bsdconfig/networking/hostname76
-rw-r--r--usr.sbin/bsdconfig/networking/include/Makefile6
-rw-r--r--usr.sbin/bsdconfig/networking/include/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/networking/include/messages.subr105
-rwxr-xr-xusr.sbin/bsdconfig/networking/nameservers76
-rwxr-xr-xusr.sbin/bsdconfig/networking/networking151
-rw-r--r--usr.sbin/bsdconfig/networking/share/Makefile7
-rw-r--r--usr.sbin/bsdconfig/networking/share/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/networking/share/common.subr58
-rw-r--r--usr.sbin/bsdconfig/networking/share/device.subr385
-rw-r--r--usr.sbin/bsdconfig/networking/share/hostname.subr162
-rw-r--r--usr.sbin/bsdconfig/networking/share/ipaddr.subr219
-rw-r--r--usr.sbin/bsdconfig/networking/share/media.subr247
-rw-r--r--usr.sbin/bsdconfig/networking/share/netmask.subr137
-rw-r--r--usr.sbin/bsdconfig/networking/share/resolv.subr502
-rw-r--r--usr.sbin/bsdconfig/networking/share/routing.subr133
-rw-r--r--usr.sbin/bsdconfig/networking/share/services.subr55
-rw-r--r--usr.sbin/bsdconfig/packages/INDEX56
-rw-r--r--usr.sbin/bsdconfig/packages/Makefile11
-rw-r--r--usr.sbin/bsdconfig/packages/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/packages/USAGE37
-rw-r--r--usr.sbin/bsdconfig/packages/include/Makefile6
-rw-r--r--usr.sbin/bsdconfig/packages/include/Makefile.depend11
-rwxr-xr-xusr.sbin/bsdconfig/packages/include/messages.subr27
-rwxr-xr-xusr.sbin/bsdconfig/packages/packages82
-rw-r--r--usr.sbin/bsdconfig/password/INDEX57
-rw-r--r--usr.sbin/bsdconfig/password/Makefile11
-rw-r--r--usr.sbin/bsdconfig/password/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/password/USAGE37
-rw-r--r--usr.sbin/bsdconfig/password/include/Makefile6
-rw-r--r--usr.sbin/bsdconfig/password/include/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/password/include/messages.subr35
-rwxr-xr-xusr.sbin/bsdconfig/password/password85
-rw-r--r--usr.sbin/bsdconfig/password/share/Makefile6
-rw-r--r--usr.sbin/bsdconfig/password/share/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/password/share/password.subr124
-rw-r--r--usr.sbin/bsdconfig/security/INDEX58
-rw-r--r--usr.sbin/bsdconfig/security/Makefile11
-rw-r--r--usr.sbin/bsdconfig/security/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/security/USAGE37
-rw-r--r--usr.sbin/bsdconfig/security/include/Makefile6
-rw-r--r--usr.sbin/bsdconfig/security/include/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/security/include/messages.subr50
-rw-r--r--usr.sbin/bsdconfig/security/include/securelevel.hlp40
-rwxr-xr-xusr.sbin/bsdconfig/security/kern_securelevel175
-rwxr-xr-xusr.sbin/bsdconfig/security/security179
-rw-r--r--usr.sbin/bsdconfig/share/Makefile10
-rw-r--r--usr.sbin/bsdconfig/share/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/share/common.subr1047
-rw-r--r--usr.sbin/bsdconfig/share/device.subr1396
-rw-r--r--usr.sbin/bsdconfig/share/dialog.subr2341
-rw-r--r--usr.sbin/bsdconfig/share/geom.subr430
-rw-r--r--usr.sbin/bsdconfig/share/keymap.subr263
-rw-r--r--usr.sbin/bsdconfig/share/media/Makefile8
-rw-r--r--usr.sbin/bsdconfig/share/media/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/share/media/any.subr149
-rw-r--r--usr.sbin/bsdconfig/share/media/cdrom.subr217
-rw-r--r--usr.sbin/bsdconfig/share/media/common.subr155
-rw-r--r--usr.sbin/bsdconfig/share/media/directory.subr151
-rw-r--r--usr.sbin/bsdconfig/share/media/dos.subr165
-rw-r--r--usr.sbin/bsdconfig/share/media/floppy.subr229
-rw-r--r--usr.sbin/bsdconfig/share/media/ftp.subr911
-rw-r--r--usr.sbin/bsdconfig/share/media/http.subr688
-rw-r--r--usr.sbin/bsdconfig/share/media/httpproxy.subr463
-rw-r--r--usr.sbin/bsdconfig/share/media/network.subr182
-rw-r--r--usr.sbin/bsdconfig/share/media/nfs.subr258
-rw-r--r--usr.sbin/bsdconfig/share/media/options.subr327
-rw-r--r--usr.sbin/bsdconfig/share/media/tcpip.subr1713
-rw-r--r--usr.sbin/bsdconfig/share/media/ufs.subr198
-rw-r--r--usr.sbin/bsdconfig/share/media/usb.subr176
-rw-r--r--usr.sbin/bsdconfig/share/mustberoot.subr424
-rw-r--r--usr.sbin/bsdconfig/share/packages/Makefile6
-rw-r--r--usr.sbin/bsdconfig/share/packages/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/share/packages/categories.subr209
-rw-r--r--usr.sbin/bsdconfig/share/packages/index.subr414
-rw-r--r--usr.sbin/bsdconfig/share/packages/musthavepkg.subr87
-rw-r--r--usr.sbin/bsdconfig/share/packages/packages.subr1192
-rw-r--r--usr.sbin/bsdconfig/share/script.subr219
-rw-r--r--usr.sbin/bsdconfig/share/strings.subr454
-rw-r--r--usr.sbin/bsdconfig/share/struct.subr206
-rw-r--r--usr.sbin/bsdconfig/share/sysrc.subr746
-rw-r--r--usr.sbin/bsdconfig/share/variable.subr315
-rw-r--r--usr.sbin/bsdconfig/startup/INDEX62
-rw-r--r--usr.sbin/bsdconfig/startup/Makefile11
-rw-r--r--usr.sbin/bsdconfig/startup/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/startup/USAGE37
-rw-r--r--usr.sbin/bsdconfig/startup/include/Makefile6
-rw-r--r--usr.sbin/bsdconfig/startup/include/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/startup/include/messages.subr112
-rwxr-xr-xusr.sbin/bsdconfig/startup/misc406
-rwxr-xr-xusr.sbin/bsdconfig/startup/rcadd149
-rwxr-xr-xusr.sbin/bsdconfig/startup/rcconf264
-rwxr-xr-xusr.sbin/bsdconfig/startup/rcdelete414
-rwxr-xr-xusr.sbin/bsdconfig/startup/rcedit72
-rwxr-xr-xusr.sbin/bsdconfig/startup/rcvar220
-rw-r--r--usr.sbin/bsdconfig/startup/share/Makefile6
-rw-r--r--usr.sbin/bsdconfig/startup/share/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/startup/share/rcconf.subr500
-rw-r--r--usr.sbin/bsdconfig/startup/share/rcedit.subr90
-rw-r--r--usr.sbin/bsdconfig/startup/share/rcvar.subr236
-rwxr-xr-xusr.sbin/bsdconfig/startup/startup140
-rw-r--r--usr.sbin/bsdconfig/timezone/INDEX57
-rw-r--r--usr.sbin/bsdconfig/timezone/Makefile11
-rw-r--r--usr.sbin/bsdconfig/timezone/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/timezone/USAGE46
-rw-r--r--usr.sbin/bsdconfig/timezone/include/Makefile6
-rw-r--r--usr.sbin/bsdconfig/timezone/include/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/timezone/include/messages.subr78
-rw-r--r--usr.sbin/bsdconfig/timezone/share/Makefile7
-rw-r--r--usr.sbin/bsdconfig/timezone/share/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/timezone/share/continents.subr166
-rw-r--r--usr.sbin/bsdconfig/timezone/share/countries.subr105
-rw-r--r--usr.sbin/bsdconfig/timezone/share/iso3166.subr202
-rw-r--r--usr.sbin/bsdconfig/timezone/share/menus.subr225
-rw-r--r--usr.sbin/bsdconfig/timezone/share/zones.subr523
-rwxr-xr-xusr.sbin/bsdconfig/timezone/timezone457
-rw-r--r--usr.sbin/bsdconfig/ttys/INDEX57
-rw-r--r--usr.sbin/bsdconfig/ttys/Makefile11
-rw-r--r--usr.sbin/bsdconfig/ttys/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/ttys/USAGE37
-rw-r--r--usr.sbin/bsdconfig/ttys/include/Makefile6
-rw-r--r--usr.sbin/bsdconfig/ttys/include/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/ttys/include/messages.subr31
-rwxr-xr-xusr.sbin/bsdconfig/ttys/ttys128
-rw-r--r--usr.sbin/bsdconfig/usermgmt/INDEX64
-rw-r--r--usr.sbin/bsdconfig/usermgmt/Makefile11
-rw-r--r--usr.sbin/bsdconfig/usermgmt/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/usermgmt/USAGE37
-rwxr-xr-xusr.sbin/bsdconfig/usermgmt/groupadd77
-rwxr-xr-xusr.sbin/bsdconfig/usermgmt/groupdel100
-rwxr-xr-xusr.sbin/bsdconfig/usermgmt/groupedit100
-rw-r--r--usr.sbin/bsdconfig/usermgmt/include/Makefile6
-rw-r--r--usr.sbin/bsdconfig/usermgmt/include/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/usermgmt/include/messages.subr119
-rw-r--r--usr.sbin/bsdconfig/usermgmt/include/usermgmt.hlp76
-rw-r--r--usr.sbin/bsdconfig/usermgmt/share/Makefile6
-rw-r--r--usr.sbin/bsdconfig/usermgmt/share/Makefile.depend11
-rw-r--r--usr.sbin/bsdconfig/usermgmt/share/group.subr518
-rw-r--r--usr.sbin/bsdconfig/usermgmt/share/group_input.subr596
-rw-r--r--usr.sbin/bsdconfig/usermgmt/share/user.subr1183
-rw-r--r--usr.sbin/bsdconfig/usermgmt/share/user_input.subr1338
-rwxr-xr-xusr.sbin/bsdconfig/usermgmt/useradd77
-rwxr-xr-xusr.sbin/bsdconfig/usermgmt/userdel100
-rwxr-xr-xusr.sbin/bsdconfig/usermgmt/useredit100
-rwxr-xr-xusr.sbin/bsdconfig/usermgmt/usermgmt168
-rw-r--r--usr.sbin/bsdinstall/Makefile8
-rw-r--r--usr.sbin/bsdinstall/Makefile.depend11
-rwxr-xr-xusr.sbin/bsdinstall/bsdinstall88
-rw-r--r--usr.sbin/bsdinstall/bsdinstall.8374
-rw-r--r--usr.sbin/bsdinstall/distextract/Makefile10
-rw-r--r--usr.sbin/bsdinstall/distextract/Makefile.depend31
-rw-r--r--usr.sbin/bsdinstall/distextract/distextract.c329
-rw-r--r--usr.sbin/bsdinstall/distfetch/Makefile10
-rw-r--r--usr.sbin/bsdinstall/distfetch/Makefile.depend24
-rw-r--r--usr.sbin/bsdinstall/distfetch/distfetch.c223
-rw-r--r--usr.sbin/bsdinstall/partedit/Makefile24
-rw-r--r--usr.sbin/bsdinstall/partedit/Makefile.depend25
-rw-r--r--usr.sbin/bsdinstall/partedit/diskeditor.c290
-rw-r--r--usr.sbin/bsdinstall/partedit/diskeditor.h47
-rw-r--r--usr.sbin/bsdinstall/partedit/gpart_ops.c1386
-rw-r--r--usr.sbin/bsdinstall/partedit/part_wizard.c365
-rw-r--r--usr.sbin/bsdinstall/partedit/partedit.c562
-rw-r--r--usr.sbin/bsdinstall/partedit/partedit.h87
-rw-r--r--usr.sbin/bsdinstall/partedit/partedit_generic.c79
-rw-r--r--usr.sbin/bsdinstall/partedit/partedit_pc98.c83
-rw-r--r--usr.sbin/bsdinstall/partedit/partedit_powerpc.c124
-rw-r--r--usr.sbin/bsdinstall/partedit/partedit_sparc64.c82
-rw-r--r--usr.sbin/bsdinstall/partedit/partedit_x86.c150
-rw-r--r--usr.sbin/bsdinstall/partedit/sade.872
-rw-r--r--usr.sbin/bsdinstall/partedit/scripted.c213
-rw-r--r--usr.sbin/bsdinstall/scripts/Makefile10
-rw-r--r--usr.sbin/bsdinstall/scripts/Makefile.depend11
-rwxr-xr-xusr.sbin/bsdinstall/scripts/adduser34
-rwxr-xr-xusr.sbin/bsdinstall/scripts/auto470
-rwxr-xr-xusr.sbin/bsdinstall/scripts/checksum71
-rwxr-xr-xusr.sbin/bsdinstall/scripts/config52
-rwxr-xr-xusr.sbin/bsdinstall/scripts/docsinstall166
-rwxr-xr-xusr.sbin/bsdinstall/scripts/entropy34
-rwxr-xr-xusr.sbin/bsdinstall/scripts/hostname52
-rwxr-xr-xusr.sbin/bsdinstall/scripts/jail132
-rwxr-xr-xusr.sbin/bsdinstall/scripts/keymap238
-rwxr-xr-xusr.sbin/bsdinstall/scripts/mirrorselect193
-rwxr-xr-xusr.sbin/bsdinstall/scripts/mount55
-rwxr-xr-xusr.sbin/bsdinstall/scripts/netconfig217
-rwxr-xr-xusr.sbin/bsdinstall/scripts/netconfig_ipv499
-rwxr-xr-xusr.sbin/bsdinstall/scripts/netconfig_ipv6160
-rwxr-xr-xusr.sbin/bsdinstall/scripts/rootpass36
-rwxr-xr-xusr.sbin/bsdinstall/scripts/script137
-rwxr-xr-xusr.sbin/bsdinstall/scripts/services68
-rwxr-xr-xusr.sbin/bsdinstall/scripts/time29
-rwxr-xr-xusr.sbin/bsdinstall/scripts/umount42
-rwxr-xr-xusr.sbin/bsdinstall/scripts/wlanconfig175
-rwxr-xr-xusr.sbin/bsdinstall/scripts/zfsboot1654
-rw-r--r--usr.sbin/bsnmpd/Makefile8
-rw-r--r--usr.sbin/bsnmpd/Makefile.inc3
-rw-r--r--usr.sbin/bsnmpd/bsnmpd/Makefile51
-rw-r--r--usr.sbin/bsnmpd/bsnmpd/Makefile.depend51
-rw-r--r--usr.sbin/bsnmpd/gensnmptree/Makefile15
-rw-r--r--usr.sbin/bsnmpd/gensnmptree/Makefile.depend18
-rw-r--r--usr.sbin/bsnmpd/modules/Makefile33
-rw-r--r--usr.sbin/bsnmpd/modules/Makefile.depend11
-rw-r--r--usr.sbin/bsnmpd/modules/Makefile.inc11
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_atm/BEGEMOT-ATM-FREEBSD-MIB.txt99
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_atm/Makefile21
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_atm/Makefile.depend34
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_atm/atm_freebsd.def56
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_atm/atm_sys.c301
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_bridge/BEGEMOT-BRIDGE-MIB.txt1166
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_bridge/BRIDGE-MIB.txt1483
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_bridge/Makefile19
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_bridge/Makefile.depend41
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_bridge/RSTP-MIB.txt325
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_bridge/bridge_addrs.c589
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_bridge/bridge_if.c1479
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_bridge/bridge_pf.c116
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_bridge/bridge_port.c1513
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_bridge/bridge_snmp.c338
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_bridge/bridge_snmp.h357
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_bridge/bridge_sys.c1503
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_bridge/bridge_tree.def283
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_bridge/snmp_bridge.3119
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hast/BEGEMOT-HAST-MIB.txt362
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hast/Makefile42
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hast/Makefile.depend37
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hast/hast_snmp.c541
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hast/hast_tree.def76
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hast/snmp_hast.370
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hostres/BEGEMOT-HOSTRES-MIB.txt125
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hostres/Makefile81
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hostres/Makefile.depend82
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hostres/hostres_begemot.c171
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hostres/hostres_device_tbl.c684
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hostres/hostres_diskstorage_tbl.c653
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hostres/hostres_fs_tbl.c473
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hostres/hostres_network_tbl.c302
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hostres/hostres_partition_tbl.c630
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hostres/hostres_printer_tbl.c398
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hostres/hostres_processor_tbl.c431
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hostres/hostres_scalars.c492
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hostres/hostres_snmp.c208
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hostres/hostres_snmp.h324
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hostres/hostres_storage_tbl.c662
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hostres/hostres_swinstalled_tbl.c555
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hostres/hostres_swrun_tbl.c792
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hostres/hostres_tree.def292
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_hostres/snmp_hostres.388
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_lm75/BEGEMOT-LM75-MIB.txt160
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_lm75/Makefile13
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_lm75/Makefile.depend28
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_lm75/lm75_tree.def56
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_lm75/snmp_lm75.360
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_lm75/snmp_lm75.c435
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_mibII/Makefile29
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_mibII/Makefile.depend70
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_netgraph/BEGEMOT-NETGRAPH.txt398
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_netgraph/Makefile16
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_netgraph/Makefile.depend29
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_netgraph/netgraph_tree.def78
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_netgraph/snmp_netgraph.3436
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_netgraph/snmp_netgraph.c1690
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_netgraph/snmp_netgraph.h91
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_pf/BEGEMOT-PF-MIB.txt1347
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_pf/Makefile13
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_pf/Makefile.depend28
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_pf/pf_snmp.c1797
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_pf/pf_tree.def210
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_target/Makefile20
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_target/Makefile.depend27
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_usm/Makefile22
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_usm/Makefile.depend27
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_vacm/Makefile20
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_vacm/Makefile.depend27
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_wlan/BEGEMOT-WIRELESS-MIB.txt3898
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_wlan/Makefile15
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_wlan/Makefile.depend31
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_wlan/snmp_wlan.3160
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_wlan/wlan_snmp.c4513
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_wlan/wlan_snmp.h286
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_wlan/wlan_sys.c3145
-rw-r--r--usr.sbin/bsnmpd/modules/snmp_wlan/wlan_tree.def677
-rw-r--r--usr.sbin/bsnmpd/tools/Makefile7
-rw-r--r--usr.sbin/bsnmpd/tools/Makefile.inc13
-rw-r--r--usr.sbin/bsnmpd/tools/bsnmptools/Makefile21
-rw-r--r--usr.sbin/bsnmpd/tools/bsnmptools/Makefile.depend21
-rw-r--r--usr.sbin/bsnmpd/tools/bsnmptools/bsnmpget.1433
-rw-r--r--usr.sbin/bsnmpd/tools/bsnmptools/bsnmpget.c1289
-rw-r--r--usr.sbin/bsnmpd/tools/libbsnmptools/Makefile13
-rw-r--r--usr.sbin/bsnmpd/tools/libbsnmptools/Makefile.depend20
-rw-r--r--usr.sbin/bsnmpd/tools/libbsnmptools/bsnmpimport.c971
-rw-r--r--usr.sbin/bsnmpd/tools/libbsnmptools/bsnmpmap.c1018
-rw-r--r--usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptc.c1287
-rw-r--r--usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptc.h95
-rw-r--r--usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptools.c2132
-rw-r--r--usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptools.h333
-rw-r--r--usr.sbin/btxld/Makefile7
-rw-r--r--usr.sbin/btxld/Makefile.depend18
-rw-r--r--usr.sbin/btxld/btx.h68
-rw-r--r--usr.sbin/btxld/btxld.898
-rw-r--r--usr.sbin/btxld/btxld.c575
-rw-r--r--usr.sbin/btxld/elfh.c125
-rw-r--r--usr.sbin/btxld/elfh.h38
-rw-r--r--usr.sbin/camdd/Makefile10
-rw-r--r--usr.sbin/camdd/Makefile.depend25
-rw-r--r--usr.sbin/camdd/camdd.8283
-rw-r--r--usr.sbin/camdd/camdd.c3423
-rw-r--r--usr.sbin/cdcontrol/Makefile7
-rw-r--r--usr.sbin/cdcontrol/Makefile.depend21
-rw-r--r--usr.sbin/cdcontrol/cdcontrol.1222
-rw-r--r--usr.sbin/cdcontrol/cdcontrol.c1310
-rw-r--r--usr.sbin/chkgrp/Makefile6
-rw-r--r--usr.sbin/chkgrp/Makefile.depend18
-rw-r--r--usr.sbin/chkgrp/chkgrp.893
-rw-r--r--usr.sbin/chkgrp/chkgrp.c193
-rw-r--r--usr.sbin/chown/Makefile14
-rw-r--r--usr.sbin/chown/Makefile.depend18
-rw-r--r--usr.sbin/chown/chgrp.1150
-rw-r--r--usr.sbin/chown/chown.8171
-rw-r--r--usr.sbin/chown/chown.c317
-rw-r--r--usr.sbin/chown/tests/Makefile9
-rwxr-xr-xusr.sbin/chown/tests/chown-f_test.sh21
-rw-r--r--usr.sbin/chroot/Makefile7
-rw-r--r--usr.sbin/chroot/Makefile.depend18
-rw-r--r--usr.sbin/chroot/chroot.894
-rw-r--r--usr.sbin/chroot/chroot.c182
-rw-r--r--usr.sbin/ckdist/Makefile10
-rw-r--r--usr.sbin/ckdist/Makefile.depend19
-rw-r--r--usr.sbin/ckdist/ckdist.1132
-rw-r--r--usr.sbin/ckdist/ckdist.c445
-rw-r--r--usr.sbin/clear_locks/Makefile7
-rw-r--r--usr.sbin/clear_locks/Makefile.depend21
-rw-r--r--usr.sbin/clear_locks/clear_locks.851
-rw-r--r--usr.sbin/clear_locks/clear_locks.c70
-rw-r--r--usr.sbin/config/Makefile22
-rw-r--r--usr.sbin/config/Makefile.depend37
-rw-r--r--usr.sbin/config/config.5405
-rw-r--r--usr.sbin/config/config.8283
-rw-r--r--usr.sbin/config/config.h211
-rw-r--r--usr.sbin/config/config.y469
-rw-r--r--usr.sbin/config/configvers.h53
-rw-r--r--usr.sbin/config/kernconf.tmpl17
-rw-r--r--usr.sbin/config/lang.l325
-rw-r--r--usr.sbin/config/main.c778
-rw-r--r--usr.sbin/config/mkheaders.c66
-rw-r--r--usr.sbin/config/mkmakefile.c779
-rw-r--r--usr.sbin/config/mkoptions.c435
-rw-r--r--usr.sbin/cpucontrol/Makefile9
-rw-r--r--usr.sbin/cpucontrol/Makefile.depend18
-rw-r--r--usr.sbin/cpucontrol/amd.c181
-rw-r--r--usr.sbin/cpucontrol/amd.h49
-rw-r--r--usr.sbin/cpucontrol/cpucontrol.8182
-rw-r--r--usr.sbin/cpucontrol/cpucontrol.c485
-rw-r--r--usr.sbin/cpucontrol/cpucontrol.h56
-rw-r--r--usr.sbin/cpucontrol/intel.c287
-rw-r--r--usr.sbin/cpucontrol/intel.h70
-rw-r--r--usr.sbin/cpucontrol/via.c224
-rw-r--r--usr.sbin/cpucontrol/via.h63
-rw-r--r--usr.sbin/crashinfo/Makefile6
-rw-r--r--usr.sbin/crashinfo/Makefile.depend11
-rw-r--r--usr.sbin/crashinfo/crashinfo.8109
-rwxr-xr-xusr.sbin/crashinfo/crashinfo.sh304
-rw-r--r--usr.sbin/cron/Makefile5
-rw-r--r--usr.sbin/cron/Makefile.inc3
-rw-r--r--usr.sbin/cron/cron/Makefile13
-rw-r--r--usr.sbin/cron/cron/Makefile.depend21
-rw-r--r--usr.sbin/cron/cron/compat.h140
-rw-r--r--usr.sbin/cron/cron/config.h87
-rw-r--r--usr.sbin/cron/cron/cron.8221
-rw-r--r--usr.sbin/cron/cron/cron.c561
-rw-r--r--usr.sbin/cron/cron/cron.h307
-rw-r--r--usr.sbin/cron/cron/database.c263
-rw-r--r--usr.sbin/cron/cron/do_command.c622
-rw-r--r--usr.sbin/cron/cron/externs.h147
-rw-r--r--usr.sbin/cron/cron/job.c76
-rw-r--r--usr.sbin/cron/cron/pathnames.h81
-rw-r--r--usr.sbin/cron/cron/popen.c246
-rw-r--r--usr.sbin/cron/cron/user.c128
-rw-r--r--usr.sbin/cron/crontab/Makefile17
-rw-r--r--usr.sbin/cron/crontab/Makefile.depend21
-rw-r--r--usr.sbin/cron/crontab/crontab.1145
-rw-r--r--usr.sbin/cron/crontab/crontab.5323
-rw-r--r--usr.sbin/cron/crontab/crontab.c643
-rw-r--r--usr.sbin/cron/doc/CHANGES158
-rw-r--r--usr.sbin/cron/doc/CONVERSION85
-rw-r--r--usr.sbin/cron/doc/FEATURES84
-rw-r--r--usr.sbin/cron/doc/INSTALL87
-rw-r--r--usr.sbin/cron/doc/MAIL475
-rw-r--r--usr.sbin/cron/doc/Makefile.vixie128
-rw-r--r--usr.sbin/cron/doc/README72
-rw-r--r--usr.sbin/cron/doc/README.1ST4
-rw-r--r--usr.sbin/cron/doc/THANKS29
-rw-r--r--usr.sbin/cron/lib/Makefile12
-rw-r--r--usr.sbin/cron/lib/Makefile.depend14
-rw-r--r--usr.sbin/cron/lib/compat.c237
-rw-r--r--usr.sbin/cron/lib/entry.c667
-rw-r--r--usr.sbin/cron/lib/env.c269
-rw-r--r--usr.sbin/cron/lib/misc.c600
-rw-r--r--usr.sbin/crunch/COPYRIGHT25
-rw-r--r--usr.sbin/crunch/Makefile5
-rw-r--r--usr.sbin/crunch/Makefile.inc6
-rw-r--r--usr.sbin/crunch/README88
-rw-r--r--usr.sbin/crunch/crunchgen/Makefile10
-rw-r--r--usr.sbin/crunch/crunchgen/Makefile.depend20
-rw-r--r--usr.sbin/crunch/crunchgen/crunched_main.c130
-rw-r--r--usr.sbin/crunch/crunchgen/crunchgen.1475
-rw-r--r--usr.sbin/crunch/crunchgen/crunchgen.c1236
-rw-r--r--usr.sbin/crunch/crunchgen/mkskel.sh15
-rw-r--r--usr.sbin/crunch/crunchide/Makefile9
-rw-r--r--usr.sbin/crunch/crunchide/Makefile.depend18
-rw-r--r--usr.sbin/crunch/crunchide/crunchide.192
-rw-r--r--usr.sbin/crunch/crunchide/crunchide.c267
-rw-r--r--usr.sbin/crunch/crunchide/exec_elf32.c491
-rw-r--r--usr.sbin/crunch/crunchide/exec_elf64.c40
-rw-r--r--usr.sbin/crunch/crunchide/extern.h43
-rw-r--r--usr.sbin/crunch/examples/Makefile33
-rw-r--r--usr.sbin/crunch/examples/filesystem.conf31
-rw-r--r--usr.sbin/crunch/examples/fixit.conf45
-rw-r--r--usr.sbin/crunch/examples/kcopy.conf21
-rw-r--r--usr.sbin/crunch/examples/really-big.conf158
-rw-r--r--usr.sbin/ctladm/Makefile20
-rw-r--r--usr.sbin/ctladm/Makefile.depend22
-rw-r--r--usr.sbin/ctladm/ctladm.81026
-rw-r--r--usr.sbin/ctladm/ctladm.c4262
-rw-r--r--usr.sbin/ctladm/ctladm.h50
-rw-r--r--usr.sbin/ctladm/util.c156
-rw-r--r--usr.sbin/ctld/Makefile21
-rw-r--r--usr.sbin/ctld/Makefile.depend31
-rw-r--r--usr.sbin/ctld/chap.c422
-rw-r--r--usr.sbin/ctld/ctl.conf.5494
-rw-r--r--usr.sbin/ctld/ctld.8119
-rw-r--r--usr.sbin/ctld/ctld.c2619
-rw-r--r--usr.sbin/ctld/ctld.h455
-rw-r--r--usr.sbin/ctld/discovery.c336
-rw-r--r--usr.sbin/ctld/isns.c252
-rw-r--r--usr.sbin/ctld/isns.h92
-rw-r--r--usr.sbin/ctld/kernel.c1276
-rw-r--r--usr.sbin/ctld/keys.c198
-rw-r--r--usr.sbin/ctld/log.c198
-rw-r--r--usr.sbin/ctld/login.c1004
-rw-r--r--usr.sbin/ctld/parse.y1150
-rw-r--r--usr.sbin/ctld/pdu.c264
-rw-r--r--usr.sbin/ctld/token.l96
-rw-r--r--usr.sbin/ctm/Makefile5
-rw-r--r--usr.sbin/ctm/Makefile.inc5
-rw-r--r--usr.sbin/ctm/README85
-rw-r--r--usr.sbin/ctm/ctm/Makefile24
-rw-r--r--usr.sbin/ctm/ctm/Makefile.depend19
-rw-r--r--usr.sbin/ctm/ctm/ctm.1335
-rw-r--r--usr.sbin/ctm/ctm/ctm.5182
-rw-r--r--usr.sbin/ctm/ctm/ctm.c331
-rw-r--r--usr.sbin/ctm/ctm/ctm.h163
-rw-r--r--usr.sbin/ctm/ctm/ctm_ed.c114
-rw-r--r--usr.sbin/ctm/ctm/ctm_input.c134
-rw-r--r--usr.sbin/ctm/ctm/ctm_pass1.c250
-rw-r--r--usr.sbin/ctm/ctm/ctm_pass2.c301
-rw-r--r--usr.sbin/ctm/ctm/ctm_pass3.c295
-rw-r--r--usr.sbin/ctm/ctm/ctm_passb.c142
-rw-r--r--usr.sbin/ctm/ctm/ctm_syntax.c67
-rw-r--r--usr.sbin/ctm/ctm_dequeue/Makefile13
-rw-r--r--usr.sbin/ctm/ctm_dequeue/Makefile.depend18
-rw-r--r--usr.sbin/ctm/ctm_dequeue/ctm_dequeue.c209
-rw-r--r--usr.sbin/ctm/ctm_rmail/Makefile10
-rw-r--r--usr.sbin/ctm/ctm_rmail/Makefile.depend18
-rw-r--r--usr.sbin/ctm/ctm_rmail/ctm_rmail.1510
-rw-r--r--usr.sbin/ctm/ctm_rmail/ctm_rmail.c672
-rw-r--r--usr.sbin/ctm/ctm_rmail/error.c102
-rw-r--r--usr.sbin/ctm/ctm_rmail/error.h5
-rw-r--r--usr.sbin/ctm/ctm_rmail/options.h139
-rw-r--r--usr.sbin/ctm/ctm_smail/Makefile13
-rw-r--r--usr.sbin/ctm/ctm_smail/Makefile.depend18
-rw-r--r--usr.sbin/ctm/ctm_smail/ctm_smail.c497
-rw-r--r--usr.sbin/ctm/mkCTM/Makefile25
-rw-r--r--usr.sbin/ctm/mkCTM/ctm_conf.cvs-cur9
-rw-r--r--usr.sbin/ctm/mkCTM/ctm_conf.ports-cur7
-rw-r--r--usr.sbin/ctm/mkCTM/ctm_conf.smp-cur7
-rw-r--r--usr.sbin/ctm/mkCTM/ctm_conf.src-cur9
-rw-r--r--usr.sbin/ctm/mkCTM/ctm_conf.src-special9
-rwxr-xr-xusr.sbin/ctm/mkCTM/dequeue6
-rw-r--r--usr.sbin/ctm/mkCTM/mkCTM188
-rw-r--r--usr.sbin/ctm/mkCTM/mkctm.c597
-rw-r--r--usr.sbin/daemon/Makefile8
-rw-r--r--usr.sbin/daemon/Makefile.depend18
-rw-r--r--usr.sbin/daemon/daemon.8163
-rw-r--r--usr.sbin/daemon/daemon.c276
-rw-r--r--usr.sbin/dconschat/Makefile12
-rw-r--r--usr.sbin/dconschat/Makefile.depend21
-rw-r--r--usr.sbin/dconschat/dconschat.8329
-rw-r--r--usr.sbin/dconschat/dconschat.c1157
-rw-r--r--usr.sbin/devctl/Makefile8
-rw-r--r--usr.sbin/devctl/Makefile.depend19
-rw-r--r--usr.sbin/devctl/devctl.8137
-rw-r--r--usr.sbin/devctl/devctl.c282
-rw-r--r--usr.sbin/devinfo/Makefile8
-rw-r--r--usr.sbin/devinfo/Makefile.depend18
-rw-r--r--usr.sbin/devinfo/devinfo.876
-rw-r--r--usr.sbin/devinfo/devinfo.c237
-rw-r--r--usr.sbin/digictl/Makefile6
-rw-r--r--usr.sbin/digictl/Makefile.depend18
-rw-r--r--usr.sbin/digictl/digictl.8118
-rw-r--r--usr.sbin/digictl/digictl.c171
-rw-r--r--usr.sbin/diskinfo/Makefile13
-rw-r--r--usr.sbin/diskinfo/Makefile.depend19
-rw-r--r--usr.sbin/diskinfo/diskinfo.874
-rw-r--r--usr.sbin/diskinfo/diskinfo.c388
-rw-r--r--usr.sbin/dumpcis/Makefile9
-rw-r--r--usr.sbin/dumpcis/Makefile.depend18
-rw-r--r--usr.sbin/dumpcis/cardinfo.h205
-rw-r--r--usr.sbin/dumpcis/cis.h279
-rw-r--r--usr.sbin/dumpcis/dumpcis.848
-rw-r--r--usr.sbin/dumpcis/main.c58
-rw-r--r--usr.sbin/dumpcis/printcis.c1105
-rw-r--r--usr.sbin/dumpcis/readcis.c377
-rw-r--r--usr.sbin/dumpcis/readcis.h61
-rw-r--r--usr.sbin/editmap/Makefile29
-rw-r--r--usr.sbin/editmap/Makefile.depend23
-rw-r--r--usr.sbin/edquota/Makefile12
-rw-r--r--usr.sbin/edquota/Makefile.depend19
-rw-r--r--usr.sbin/edquota/edquota.8264
-rw-r--r--usr.sbin/edquota/edquota.c959
-rw-r--r--usr.sbin/edquota/pathnames.h36
-rw-r--r--usr.sbin/eeprom/Makefile11
-rw-r--r--usr.sbin/eeprom/Makefile.depend16
-rw-r--r--usr.sbin/eeprom/eeprom.8700
-rw-r--r--usr.sbin/eeprom/eeprom.c146
-rw-r--r--usr.sbin/eeprom/ofw_options.c310
-rw-r--r--usr.sbin/eeprom/ofw_options.h34
-rw-r--r--usr.sbin/etcupdate/Makefile12
-rw-r--r--usr.sbin/etcupdate/Makefile.depend11
-rw-r--r--usr.sbin/etcupdate/etcupdate.8900
-rwxr-xr-xusr.sbin/etcupdate/etcupdate.sh1795
-rw-r--r--usr.sbin/etcupdate/tests/Makefile15
-rw-r--r--usr.sbin/etcupdate/tests/always_test.sh630
-rw-r--r--usr.sbin/etcupdate/tests/conflicts_test.sh294
-rw-r--r--usr.sbin/etcupdate/tests/fbsdid_test.sh394
-rw-r--r--usr.sbin/etcupdate/tests/ignore_test.sh276
-rw-r--r--usr.sbin/etcupdate/tests/preworld_test.sh252
-rw-r--r--usr.sbin/etcupdate/tests/tests_test.sh1021
-rw-r--r--usr.sbin/etcupdate/tests/tzsetup_test.sh239
-rw-r--r--usr.sbin/extattr/Makefile14
-rw-r--r--usr.sbin/extattr/Makefile.depend19
-rw-r--r--usr.sbin/extattr/rmextattr.8135
-rw-r--r--usr.sbin/extattr/rmextattr.c296
-rw-r--r--usr.sbin/extattrctl/Makefile6
-rw-r--r--usr.sbin/extattrctl/Makefile.depend19
-rw-r--r--usr.sbin/extattrctl/extattrctl.8181
-rw-r--r--usr.sbin/extattrctl/extattrctl.c265
-rw-r--r--usr.sbin/fdcontrol/Makefile14
-rw-r--r--usr.sbin/fdcontrol/Makefile.depend18
-rw-r--r--usr.sbin/fdcontrol/fdcontrol.8334
-rw-r--r--usr.sbin/fdcontrol/fdcontrol.c209
-rw-r--r--usr.sbin/fdformat/Makefile14
-rw-r--r--usr.sbin/fdformat/Makefile.depend18
-rw-r--r--usr.sbin/fdformat/fdformat.1180
-rw-r--r--usr.sbin/fdformat/fdformat.c360
-rw-r--r--usr.sbin/fdread/Makefile10
-rw-r--r--usr.sbin/fdread/Makefile.depend18
-rw-r--r--usr.sbin/fdread/fdread.1233
-rw-r--r--usr.sbin/fdread/fdread.c330
-rw-r--r--usr.sbin/fdread/fdutil.c509
-rw-r--r--usr.sbin/fdread/fdutil.h37
-rw-r--r--usr.sbin/fdwrite/Makefile12
-rw-r--r--usr.sbin/fdwrite/Makefile.depend18
-rw-r--r--usr.sbin/fdwrite/fdwrite.1129
-rw-r--r--usr.sbin/fdwrite/fdwrite.c196
-rw-r--r--usr.sbin/fifolog/Makefile7
-rw-r--r--usr.sbin/fifolog/Makefile.inc3
-rw-r--r--usr.sbin/fifolog/fifolog_create/Makefile21
-rw-r--r--usr.sbin/fifolog/fifolog_create/Makefile.depend20
-rw-r--r--usr.sbin/fifolog/fifolog_create/fifolog.1218
-rw-r--r--usr.sbin/fifolog/fifolog_create/fifolog_create.c115
-rw-r--r--usr.sbin/fifolog/fifolog_reader/Makefile19
-rw-r--r--usr.sbin/fifolog/fifolog_reader/Makefile.depend20
-rw-r--r--usr.sbin/fifolog/fifolog_reader/fifolog_reader.c171
-rw-r--r--usr.sbin/fifolog/fifolog_writer/Makefile15
-rw-r--r--usr.sbin/fifolog/fifolog_writer/Makefile.depend20
-rw-r--r--usr.sbin/fifolog/fifolog_writer/fifolog_writer.c115
-rw-r--r--usr.sbin/fifolog/flint.lnt55
-rw-r--r--usr.sbin/fifolog/lib/Makefile14
-rw-r--r--usr.sbin/fifolog/lib/Makefile.depend17
-rw-r--r--usr.sbin/fifolog/lib/fifolog.h138
-rw-r--r--usr.sbin/fifolog/lib/fifolog_create.c122
-rw-r--r--usr.sbin/fifolog/lib/fifolog_int.c256
-rw-r--r--usr.sbin/fifolog/lib/fifolog_reader.c322
-rw-r--r--usr.sbin/fifolog/lib/fifolog_write.h75
-rw-r--r--usr.sbin/fifolog/lib/fifolog_write_poll.c412
-rw-r--r--usr.sbin/fifolog/lib/getdate.y887
-rw-r--r--usr.sbin/fifolog/lib/libfifolog.h47
-rw-r--r--usr.sbin/fifolog/lib/libfifolog_int.h45
-rw-r--r--usr.sbin/fifolog/lib/miniobj.h74
-rw-r--r--usr.sbin/flowctl/Makefile20
-rw-r--r--usr.sbin/flowctl/Makefile.depend20
-rw-r--r--usr.sbin/flowctl/flowctl.890
-rw-r--r--usr.sbin/flowctl/flowctl.c426
-rw-r--r--usr.sbin/fmtree/Makefile21
-rw-r--r--usr.sbin/fmtree/Makefile.depend19
-rw-r--r--usr.sbin/fmtree/compare.c388
-rw-r--r--usr.sbin/fmtree/create.c428
-rw-r--r--usr.sbin/fmtree/excludes.c111
-rw-r--r--usr.sbin/fmtree/extern.h59
-rw-r--r--usr.sbin/fmtree/misc.c124
-rw-r--r--usr.sbin/fmtree/mtree.8401
-rw-r--r--usr.sbin/fmtree/mtree.c191
-rw-r--r--usr.sbin/fmtree/mtree.h98
-rw-r--r--usr.sbin/fmtree/spec.c323
-rw-r--r--usr.sbin/fmtree/specspec.c256
-rw-r--r--usr.sbin/fmtree/test/test00.sh67
-rw-r--r--usr.sbin/fmtree/test/test01.sh40
-rw-r--r--usr.sbin/fmtree/test/test02.sh36
-rw-r--r--usr.sbin/fmtree/test/test03.sh60
-rw-r--r--usr.sbin/fmtree/test/test04.sh51
-rw-r--r--usr.sbin/fmtree/test/test05.sh25
-rw-r--r--usr.sbin/fmtree/verify.c259
-rw-r--r--usr.sbin/freebsd-update/Makefile6
-rw-r--r--usr.sbin/freebsd-update/Makefile.depend11
-rw-r--r--usr.sbin/freebsd-update/freebsd-update.8197
-rw-r--r--usr.sbin/freebsd-update/freebsd-update.sh3304
-rw-r--r--usr.sbin/fstyp/Makefile45
-rw-r--r--usr.sbin/fstyp/Makefile.depend32
-rw-r--r--usr.sbin/fstyp/cd9660.c62
-rw-r--r--usr.sbin/fstyp/ext2fs.c85
-rw-r--r--usr.sbin/fstyp/fstyp.8127
-rw-r--r--usr.sbin/fstyp/fstyp.c235
-rw-r--r--usr.sbin/fstyp/fstyp.h51
-rw-r--r--usr.sbin/fstyp/geli.c71
-rw-r--r--usr.sbin/fstyp/msdosfs.c175
-rw-r--r--usr.sbin/fstyp/msdosfs.h140
-rw-r--r--usr.sbin/fstyp/ntfs.c163
-rw-r--r--usr.sbin/fstyp/tests/Makefile13
-rw-r--r--usr.sbin/fstyp/tests/ext2.img.bz2bin0 -> 365 bytes
-rw-r--r--usr.sbin/fstyp/tests/ext3.img.bz2bin0 -> 362 bytes
-rw-r--r--usr.sbin/fstyp/tests/ext4.img.bz2bin0 -> 373 bytes
-rw-r--r--usr.sbin/fstyp/tests/ext4_with_label.img.bz2bin0 -> 369 bytes
-rwxr-xr-xusr.sbin/fstyp/tests/fstyp_test.sh234
-rw-r--r--usr.sbin/fstyp/tests/ntfs.img.bz2bin0 -> 35821 bytes
-rw-r--r--usr.sbin/fstyp/tests/ntfs_with_label.img.bz2bin0 -> 35809 bytes
-rw-r--r--usr.sbin/fstyp/ufs.c109
-rw-r--r--usr.sbin/fstyp/zfs.c78
-rw-r--r--usr.sbin/ftp-proxy/Makefile16
-rw-r--r--usr.sbin/ftp-proxy/Makefile.depend20
-rw-r--r--usr.sbin/fwcontrol/Makefile13
-rw-r--r--usr.sbin/fwcontrol/Makefile.depend19
-rw-r--r--usr.sbin/fwcontrol/fwcontrol.8220
-rw-r--r--usr.sbin/fwcontrol/fwcontrol.c1089
-rw-r--r--usr.sbin/fwcontrol/fwdv.c418
-rw-r--r--usr.sbin/fwcontrol/fwmethods.h10
-rw-r--r--usr.sbin/fwcontrol/fwmpegts.c274
-rw-r--r--usr.sbin/getfmac/Makefile6
-rw-r--r--usr.sbin/getfmac/Makefile.depend18
-rw-r--r--usr.sbin/getfmac/getfmac.857
-rw-r--r--usr.sbin/getfmac/getfmac.c116
-rw-r--r--usr.sbin/getpmac/Makefile6
-rw-r--r--usr.sbin/getpmac/Makefile.depend18
-rw-r--r--usr.sbin/getpmac/getpmac.859
-rw-r--r--usr.sbin/getpmac/getpmac.c127
-rw-r--r--usr.sbin/gpioctl/Makefile10
-rw-r--r--usr.sbin/gpioctl/Makefile.depend19
-rw-r--r--usr.sbin/gpioctl/gpioctl.8132
-rw-r--r--usr.sbin/gpioctl/gpioctl.c327
-rw-r--r--usr.sbin/gssd/Makefile35
-rw-r--r--usr.sbin/gssd/Makefile.depend44
-rw-r--r--usr.sbin/gssd/gssd.8119
-rw-r--r--usr.sbin/gssd/gssd.c1292
-rw-r--r--usr.sbin/gstat/Makefile7
-rw-r--r--usr.sbin/gstat/Makefile.depend26
-rw-r--r--usr.sbin/gstat/gstat.8101
-rw-r--r--usr.sbin/gstat/gstat.c455
-rw-r--r--usr.sbin/hyperv/Makefile7
-rw-r--r--usr.sbin/hyperv/Makefile.inc4
-rw-r--r--usr.sbin/hyperv/tools/Makefile13
-rw-r--r--usr.sbin/hyperv/tools/Makefile.depend19
-rw-r--r--usr.sbin/i2c/Makefile8
-rw-r--r--usr.sbin/i2c/Makefile.depend18
-rw-r--r--usr.sbin/i2c/i2c.8164
-rw-r--r--usr.sbin/i2c/i2c.c689
-rw-r--r--usr.sbin/ifmcstat/Makefile18
-rw-r--r--usr.sbin/ifmcstat/Makefile.depend19
-rw-r--r--usr.sbin/ifmcstat/ifmcstat.8129
-rw-r--r--usr.sbin/ifmcstat/ifmcstat.c1246
-rw-r--r--usr.sbin/ifmcstat/printb.c64
-rw-r--r--usr.sbin/inetd/Makefile27
-rw-r--r--usr.sbin/inetd/Makefile.depend23
-rw-r--r--usr.sbin/inetd/builtins.c814
-rw-r--r--usr.sbin/inetd/inetd.8955
-rw-r--r--usr.sbin/inetd/inetd.c2565
-rw-r--r--usr.sbin/inetd/inetd.h145
-rw-r--r--usr.sbin/inetd/pathnames.h36
-rw-r--r--usr.sbin/iostat/Makefile11
-rw-r--r--usr.sbin/iostat/Makefile.depend22
-rw-r--r--usr.sbin/iostat/iostat.8516
-rw-r--r--usr.sbin/iostat/iostat.c1021
-rw-r--r--usr.sbin/iovctl/Makefile17
-rw-r--r--usr.sbin/iovctl/Makefile.depend21
-rw-r--r--usr.sbin/iovctl/iovctl.8123
-rw-r--r--usr.sbin/iovctl/iovctl.c403
-rw-r--r--usr.sbin/iovctl/iovctl.conf.5171
-rw-r--r--usr.sbin/iovctl/iovctl.h37
-rw-r--r--usr.sbin/iovctl/parse.c416
-rw-r--r--usr.sbin/iovctl/validate.c274
-rw-r--r--usr.sbin/ip6addrctl/Makefile6
-rw-r--r--usr.sbin/ip6addrctl/Makefile.depend18
-rw-r--r--usr.sbin/ip6addrctl/ip6addrctl.8126
-rw-r--r--usr.sbin/ip6addrctl/ip6addrctl.c456
-rw-r--r--usr.sbin/ip6addrctl/ip6addrctl.conf.sample12
-rw-r--r--usr.sbin/ipfwpcap/Makefile18
-rw-r--r--usr.sbin/ipfwpcap/Makefile.depend19
-rw-r--r--usr.sbin/ipfwpcap/ipfwpcap.8132
-rw-r--r--usr.sbin/ipfwpcap/ipfwpcap.c303
-rw-r--r--usr.sbin/iscsid/Makefile15
-rw-r--r--usr.sbin/iscsid/Makefile.depend21
-rw-r--r--usr.sbin/iscsid/chap.c422
-rw-r--r--usr.sbin/iscsid/discovery.c220
-rw-r--r--usr.sbin/iscsid/iscsid.8115
-rw-r--r--usr.sbin/iscsid/iscsid.c603
-rw-r--r--usr.sbin/iscsid/iscsid.h149
-rw-r--r--usr.sbin/iscsid/keys.c198
-rw-r--r--usr.sbin/iscsid/log.c198
-rw-r--r--usr.sbin/iscsid/login.c831
-rw-r--r--usr.sbin/iscsid/pdu.c297
-rw-r--r--usr.sbin/jail/Makefile25
-rw-r--r--usr.sbin/jail/Makefile.depend31
-rw-r--r--usr.sbin/jail/command.c979
-rw-r--r--usr.sbin/jail/config.c850
-rw-r--r--usr.sbin/jail/jail.81305
-rw-r--r--usr.sbin/jail/jail.c1018
-rw-r--r--usr.sbin/jail/jail.conf.5232
-rw-r--r--usr.sbin/jail/jaillex.l235
-rw-r--r--usr.sbin/jail/jailp.h237
-rw-r--r--usr.sbin/jail/jailparse.y216
-rw-r--r--usr.sbin/jail/state.c468
-rw-r--r--usr.sbin/jexec/Makefile7
-rw-r--r--usr.sbin/jexec/Makefile.depend21
-rw-r--r--usr.sbin/jexec/jexec.885
-rw-r--r--usr.sbin/jexec/jexec.c191
-rw-r--r--usr.sbin/jls/Makefile16
-rw-r--r--usr.sbin/jls/Makefile.depend22
-rw-r--r--usr.sbin/jls/jls.8128
-rw-r--r--usr.sbin/jls/jls.c555
-rw-r--r--usr.sbin/kbdcontrol/Makefile13
-rw-r--r--usr.sbin/kbdcontrol/Makefile.depend21
-rw-r--r--usr.sbin/kbdcontrol/kbdcontrol.1291
-rw-r--r--usr.sbin/kbdcontrol/kbdcontrol.c1229
-rw-r--r--usr.sbin/kbdcontrol/kbdmap.5335
-rw-r--r--usr.sbin/kbdcontrol/lex.h72
-rw-r--r--usr.sbin/kbdcontrol/lex.l151
-rw-r--r--usr.sbin/kbdcontrol/path.h8
-rw-r--r--usr.sbin/kbdmap/Languages.phrases37
-rw-r--r--usr.sbin/kbdmap/Makefile7
-rw-r--r--usr.sbin/kbdmap/Makefile.depend18
-rw-r--r--usr.sbin/kbdmap/TODO6
-rw-r--r--usr.sbin/kbdmap/kbdmap.1155
-rw-r--r--usr.sbin/kbdmap/kbdmap.c871
-rw-r--r--usr.sbin/kbdmap/kbdmap.h39
-rw-r--r--usr.sbin/keyserv/Makefile25
-rw-r--r--usr.sbin/keyserv/Makefile.depend29
-rw-r--r--usr.sbin/keyserv/crypt_server.c276
-rw-r--r--usr.sbin/keyserv/keyserv.880
-rw-r--r--usr.sbin/keyserv/keyserv.c821
-rw-r--r--usr.sbin/keyserv/keyserv.h17
-rw-r--r--usr.sbin/keyserv/setkey.c550
-rw-r--r--usr.sbin/kgmon/Makefile16
-rw-r--r--usr.sbin/kgmon/Makefile.depend20
-rw-r--r--usr.sbin/kgmon/kgmon.8129
-rw-r--r--usr.sbin/kgmon/kgmon.c528
-rw-r--r--usr.sbin/kgzip/Makefile9
-rw-r--r--usr.sbin/kgzip/Makefile.depend16
-rw-r--r--usr.sbin/kgzip/aouthdr.c79
-rw-r--r--usr.sbin/kgzip/aouthdr.h59
-rw-r--r--usr.sbin/kgzip/elfhdr.c164
-rw-r--r--usr.sbin/kgzip/elfhdr.h84
-rw-r--r--usr.sbin/kgzip/kgz.h57
-rw-r--r--usr.sbin/kgzip/kgzcmp.c237
-rw-r--r--usr.sbin/kgzip/kgzip.8143
-rw-r--r--usr.sbin/kgzip/kgzip.c176
-rw-r--r--usr.sbin/kgzip/kgzip.h51
-rw-r--r--usr.sbin/kgzip/kgzld.c101
-rw-r--r--usr.sbin/kgzip/xio.c121
-rw-r--r--usr.sbin/kldxref/Makefile15
-rw-r--r--usr.sbin/kldxref/Makefile.depend18
-rw-r--r--usr.sbin/kldxref/ef.c647
-rw-r--r--usr.sbin/kldxref/ef.h69
-rw-r--r--usr.sbin/kldxref/ef_amd64.c116
-rw-r--r--usr.sbin/kldxref/ef_i386.c96
-rw-r--r--usr.sbin/kldxref/ef_nop.c40
-rw-r--r--usr.sbin/kldxref/ef_obj.c606
-rw-r--r--usr.sbin/kldxref/ef_powerpc.c74
-rw-r--r--usr.sbin/kldxref/ef_sparc64.c69
-rw-r--r--usr.sbin/kldxref/fileformat45
-rw-r--r--usr.sbin/kldxref/kldxref.895
-rw-r--r--usr.sbin/kldxref/kldxref.c717
-rw-r--r--usr.sbin/lastlogin/Makefile6
-rw-r--r--usr.sbin/lastlogin/Makefile.depend18
-rw-r--r--usr.sbin/lastlogin/lastlogin.894
-rw-r--r--usr.sbin/lastlogin/lastlogin.c152
-rw-r--r--usr.sbin/lmcconfig/Makefile10
-rw-r--r--usr.sbin/lmcconfig/Makefile.depend19
-rw-r--r--usr.sbin/lmcconfig/lmcconfig.8723
-rw-r--r--usr.sbin/lmcconfig/lmcconfig.c2553
-rw-r--r--usr.sbin/lpr/Makefile11
-rw-r--r--usr.sbin/lpr/Makefile.inc9
-rw-r--r--usr.sbin/lpr/chkprintcap/Makefile13
-rw-r--r--usr.sbin/lpr/chkprintcap/Makefile.depend19
-rw-r--r--usr.sbin/lpr/chkprintcap/chkprintcap.899
-rw-r--r--usr.sbin/lpr/chkprintcap/chkprintcap.c318
-rw-r--r--usr.sbin/lpr/chkprintcap/skimprintcap.c260
-rw-r--r--usr.sbin/lpr/chkprintcap/skimprintcap.h44
-rw-r--r--usr.sbin/lpr/common_source/Makefile15
-rw-r--r--usr.sbin/lpr/common_source/Makefile.depend14
-rw-r--r--usr.sbin/lpr/common_source/common.c778
-rw-r--r--usr.sbin/lpr/common_source/ctlinfo.c914
-rw-r--r--usr.sbin/lpr/common_source/ctlinfo.h73
-rw-r--r--usr.sbin/lpr/common_source/displayq.c636
-rw-r--r--usr.sbin/lpr/common_source/lp.cdefs.h122
-rw-r--r--usr.sbin/lpr/common_source/lp.h317
-rw-r--r--usr.sbin/lpr/common_source/lp.local.h78
-rw-r--r--usr.sbin/lpr/common_source/matchjobs.c568
-rw-r--r--usr.sbin/lpr/common_source/matchjobs.h102
-rw-r--r--usr.sbin/lpr/common_source/net.c298
-rw-r--r--usr.sbin/lpr/common_source/pathnames.h49
-rw-r--r--usr.sbin/lpr/common_source/printcap.c451
-rw-r--r--usr.sbin/lpr/common_source/request.c81
-rw-r--r--usr.sbin/lpr/common_source/rmjob.c392
-rw-r--r--usr.sbin/lpr/common_source/startdaemon.c105
-rw-r--r--usr.sbin/lpr/filters.ru/Makefile8
-rw-r--r--usr.sbin/lpr/filters.ru/Makefile.depend11
-rw-r--r--usr.sbin/lpr/filters.ru/Makefile.inc5
-rw-r--r--usr.sbin/lpr/filters.ru/bjc-240.sh.sample64
-rw-r--r--usr.sbin/lpr/filters.ru/koi2855/Makefile8
-rw-r--r--usr.sbin/lpr/filters.ru/koi2855/Makefile.depend17
-rw-r--r--usr.sbin/lpr/filters.ru/koi2855/koi2855.c104
-rw-r--r--usr.sbin/lpr/filters.ru/koi2alt/Makefile8
-rw-r--r--usr.sbin/lpr/filters.ru/koi2alt/Makefile.depend17
-rw-r--r--usr.sbin/lpr/filters.ru/koi2alt/koi2alt.c105
-rw-r--r--usr.sbin/lpr/filters/Makefile11
-rw-r--r--usr.sbin/lpr/filters/Makefile.depend18
-rw-r--r--usr.sbin/lpr/filters/lpf.c218
-rw-r--r--usr.sbin/lpr/lp/Makefile8
-rw-r--r--usr.sbin/lpr/lp/Makefile.depend11
-rw-r--r--usr.sbin/lpr/lp/lp.1125
-rw-r--r--usr.sbin/lpr/lp/lp.sh81
-rw-r--r--usr.sbin/lpr/lpc/Makefile18
-rw-r--r--usr.sbin/lpr/lpc/Makefile.depend21
-rw-r--r--usr.sbin/lpr/lpc/cmds.c1330
-rw-r--r--usr.sbin/lpr/lpc/cmdtab.c90
-rw-r--r--usr.sbin/lpr/lpc/extern.h77
-rw-r--r--usr.sbin/lpr/lpc/lpc.8309
-rw-r--r--usr.sbin/lpr/lpc/lpc.c419
-rw-r--r--usr.sbin/lpr/lpc/lpc.h51
-rw-r--r--usr.sbin/lpr/lpc/movejobs.c271
-rw-r--r--usr.sbin/lpr/lpd/Makefile14
-rw-r--r--usr.sbin/lpr/lpd/Makefile.depend20
-rw-r--r--usr.sbin/lpr/lpd/extern.h46
-rw-r--r--usr.sbin/lpr/lpd/lpd.8341
-rw-r--r--usr.sbin/lpr/lpd/lpd.c942
-rw-r--r--usr.sbin/lpr/lpd/lpdchar.c1068
-rw-r--r--usr.sbin/lpr/lpd/modes.c231
-rw-r--r--usr.sbin/lpr/lpd/printjob.c2020
-rw-r--r--usr.sbin/lpr/lpd/recvjob.c405
-rw-r--r--usr.sbin/lpr/lpq/Makefile15
-rw-r--r--usr.sbin/lpr/lpq/Makefile.depend19
-rw-r--r--usr.sbin/lpr/lpq/lpq.1138
-rw-r--r--usr.sbin/lpr/lpq/lpq.c195
-rw-r--r--usr.sbin/lpr/lpr/Makefile20
-rw-r--r--usr.sbin/lpr/lpr/Makefile.depend19
-rw-r--r--usr.sbin/lpr/lpr/lpr.1321
-rw-r--r--usr.sbin/lpr/lpr/lpr.c892
-rw-r--r--usr.sbin/lpr/lpr/printcap.5434
-rw-r--r--usr.sbin/lpr/lprm/Makefile17
-rw-r--r--usr.sbin/lpr/lprm/Makefile.depend19
-rw-r--r--usr.sbin/lpr/lprm/lprm.1147
-rw-r--r--usr.sbin/lpr/lprm/lprm.c162
-rw-r--r--usr.sbin/lpr/lptest/Makefile8
-rw-r--r--usr.sbin/lpr/lptest/Makefile.depend17
-rw-r--r--usr.sbin/lpr/lptest/lptest.173
-rw-r--r--usr.sbin/lpr/lptest/lptest.c85
-rw-r--r--usr.sbin/lpr/pac/Makefile13
-rw-r--r--usr.sbin/lpr/pac/Makefile.depend19
-rw-r--r--usr.sbin/lpr/pac/pac.8105
-rw-r--r--usr.sbin/lpr/pac/pac.c450
-rw-r--r--usr.sbin/lptcontrol/Makefile6
-rw-r--r--usr.sbin/lptcontrol/Makefile.depend17
-rw-r--r--usr.sbin/lptcontrol/lptcontrol.890
-rw-r--r--usr.sbin/lptcontrol/lptcontrol.c106
-rw-r--r--usr.sbin/mailstats/Makefile30
-rw-r--r--usr.sbin/mailstats/Makefile.depend22
-rw-r--r--usr.sbin/mailwrapper/Makefile36
-rw-r--r--usr.sbin/mailwrapper/Makefile.depend19
-rw-r--r--usr.sbin/mailwrapper/mailwrapper.8167
-rw-r--r--usr.sbin/mailwrapper/mailwrapper.c173
-rw-r--r--usr.sbin/mailwrapper/pathnames.h35
-rw-r--r--usr.sbin/makefs/Makefile41
-rw-r--r--usr.sbin/makefs/Makefile.depend21
-rw-r--r--usr.sbin/makefs/cd9660.c2129
-rw-r--r--usr.sbin/makefs/cd9660.h364
-rw-r--r--usr.sbin/makefs/cd9660/Makefile.inc9
-rw-r--r--usr.sbin/makefs/cd9660/cd9660_archimedes.c126
-rw-r--r--usr.sbin/makefs/cd9660/cd9660_archimedes.h50
-rw-r--r--usr.sbin/makefs/cd9660/cd9660_conversion.c200
-rw-r--r--usr.sbin/makefs/cd9660/cd9660_debug.c488
-rw-r--r--usr.sbin/makefs/cd9660/cd9660_eltorito.c711
-rw-r--r--usr.sbin/makefs/cd9660/cd9660_eltorito.h164
-rw-r--r--usr.sbin/makefs/cd9660/cd9660_strings.c120
-rw-r--r--usr.sbin/makefs/cd9660/cd9660_write.c524
-rw-r--r--usr.sbin/makefs/cd9660/iso9660_rrip.c838
-rw-r--r--usr.sbin/makefs/cd9660/iso9660_rrip.h290
-rw-r--r--usr.sbin/makefs/ffs.c1173
-rw-r--r--usr.sbin/makefs/ffs.h70
-rw-r--r--usr.sbin/makefs/ffs/Makefile.inc9
-rw-r--r--usr.sbin/makefs/ffs/buf.c222
-rw-r--r--usr.sbin/makefs/ffs/buf.h67
-rw-r--r--usr.sbin/makefs/ffs/ffs_alloc.c681
-rw-r--r--usr.sbin/makefs/ffs/ffs_balloc.c578
-rw-r--r--usr.sbin/makefs/ffs/ffs_bswap.c260
-rw-r--r--usr.sbin/makefs/ffs/ffs_extern.h77
-rw-r--r--usr.sbin/makefs/ffs/ffs_subr.c193
-rw-r--r--usr.sbin/makefs/ffs/mkfs.c839
-rw-r--r--usr.sbin/makefs/ffs/newfs_extern.h36
-rw-r--r--usr.sbin/makefs/ffs/ufs_bmap.c140
-rw-r--r--usr.sbin/makefs/ffs/ufs_bswap.h90
-rw-r--r--usr.sbin/makefs/ffs/ufs_inode.h97
-rw-r--r--usr.sbin/makefs/makefs.8398
-rw-r--r--usr.sbin/makefs/makefs.c376
-rw-r--r--usr.sbin/makefs/makefs.h286
-rw-r--r--usr.sbin/makefs/mtree.c1132
-rw-r--r--usr.sbin/makefs/tests/Makefile15
-rwxr-xr-xusr.sbin/makefs/tests/makefs_cd9660_tests.sh373
-rwxr-xr-xusr.sbin/makefs/tests/makefs_ffs_tests.sh237
-rwxr-xr-xusr.sbin/makefs/tests/makefs_tests_common.sh152
-rw-r--r--usr.sbin/makefs/walk.c687
-rw-r--r--usr.sbin/makemap/Makefile30
-rw-r--r--usr.sbin/makemap/Makefile.depend23
-rw-r--r--usr.sbin/manctl/Makefile6
-rw-r--r--usr.sbin/manctl/Makefile.depend11
-rw-r--r--usr.sbin/manctl/manctl.858
-rw-r--r--usr.sbin/manctl/manctl.sh380
-rw-r--r--usr.sbin/memcontrol/Makefile6
-rw-r--r--usr.sbin/memcontrol/Makefile.depend18
-rw-r--r--usr.sbin/memcontrol/memcontrol.8111
-rw-r--r--usr.sbin/memcontrol/memcontrol.c342
-rw-r--r--usr.sbin/mergemaster/Makefile7
-rw-r--r--usr.sbin/mergemaster/Makefile.depend11
-rw-r--r--usr.sbin/mergemaster/mergemaster.8475
-rwxr-xr-xusr.sbin/mergemaster/mergemaster.sh1414
-rw-r--r--usr.sbin/mfiutil/Makefile18
-rw-r--r--usr.sbin/mfiutil/Makefile.depend19
-rw-r--r--usr.sbin/mfiutil/mfi_bbu.c249
-rw-r--r--usr.sbin/mfiutil/mfi_cmd.c351
-rw-r--r--usr.sbin/mfiutil/mfi_config.c1287
-rw-r--r--usr.sbin/mfiutil/mfi_drive.c772
-rw-r--r--usr.sbin/mfiutil/mfi_evt.c701
-rw-r--r--usr.sbin/mfiutil/mfi_flash.c192
-rw-r--r--usr.sbin/mfiutil/mfi_foreign.c364
-rw-r--r--usr.sbin/mfiutil/mfi_patrol.c336
-rw-r--r--usr.sbin/mfiutil/mfi_properties.c171
-rw-r--r--usr.sbin/mfiutil/mfi_show.c788
-rw-r--r--usr.sbin/mfiutil/mfi_volume.c498
-rw-r--r--usr.sbin/mfiutil/mfiutil.8723
-rw-r--r--usr.sbin/mfiutil/mfiutil.c185
-rw-r--r--usr.sbin/mfiutil/mfiutil.h183
-rw-r--r--usr.sbin/mixer/Makefile6
-rw-r--r--usr.sbin/mixer/Makefile.depend18
-rw-r--r--usr.sbin/mixer/mixer.8181
-rw-r--r--usr.sbin/mixer/mixer.c336
-rw-r--r--usr.sbin/mld6query/Makefile25
-rw-r--r--usr.sbin/mld6query/Makefile.depend19
-rw-r--r--usr.sbin/mld6query/mld6.c352
-rw-r--r--usr.sbin/mld6query/mld6query.891
-rw-r--r--usr.sbin/mlxcontrol/Makefile11
-rw-r--r--usr.sbin/mlxcontrol/Makefile.depend18
-rw-r--r--usr.sbin/mlxcontrol/command.c712
-rw-r--r--usr.sbin/mlxcontrol/config.c157
-rw-r--r--usr.sbin/mlxcontrol/interface.c289
-rw-r--r--usr.sbin/mlxcontrol/mlxcontrol.8148
-rw-r--r--usr.sbin/mlxcontrol/mlxcontrol.h89
-rw-r--r--usr.sbin/mlxcontrol/util.c172
-rw-r--r--usr.sbin/mount_smbfs/Makefile16
-rw-r--r--usr.sbin/mount_smbfs/Makefile.depend20
-rw-r--r--usr.sbin/mountd/Makefile16
-rw-r--r--usr.sbin/mountd/Makefile.depend22
-rw-r--r--usr.sbin/mountd/exports.5515
-rw-r--r--usr.sbin/mountd/mountd.8198
-rw-r--r--usr.sbin/mountd/mountd.c3290
-rw-r--r--usr.sbin/mountd/netgroup.5190
-rw-r--r--usr.sbin/mountd/pathnames.h36
-rw-r--r--usr.sbin/moused/Makefile11
-rw-r--r--usr.sbin/moused/Makefile.depend20
-rw-r--r--usr.sbin/moused/moused.8854
-rw-r--r--usr.sbin/moused/moused.c3427
-rw-r--r--usr.sbin/mpsutil/Makefile22
-rw-r--r--usr.sbin/mpsutil/Makefile.depend18
-rw-r--r--usr.sbin/mpsutil/mpr_ioctl.h388
-rw-r--r--usr.sbin/mpsutil/mps_cmd.c731
-rw-r--r--usr.sbin/mpsutil/mps_flash.c237
-rw-r--r--usr.sbin/mpsutil/mps_ioctl.h387
-rw-r--r--usr.sbin/mpsutil/mps_show.c772
-rw-r--r--usr.sbin/mpsutil/mpsutil.8165
-rw-r--r--usr.sbin/mpsutil/mpsutil.c207
-rw-r--r--usr.sbin/mpsutil/mpsutil.h147
-rw-r--r--usr.sbin/mptable/Makefile5
-rw-r--r--usr.sbin/mptable/Makefile.depend18
-rw-r--r--usr.sbin/mptable/mptable.169
-rw-r--r--usr.sbin/mptable/mptable.c934
-rw-r--r--usr.sbin/mptutil/Makefile18
-rw-r--r--usr.sbin/mptutil/Makefile.depend21
-rw-r--r--usr.sbin/mptutil/mpt_cam.c569
-rw-r--r--usr.sbin/mptutil/mpt_cmd.c629
-rw-r--r--usr.sbin/mptutil/mpt_config.c1199
-rw-r--r--usr.sbin/mptutil/mpt_drive.c405
-rw-r--r--usr.sbin/mptutil/mpt_evt.c159
-rw-r--r--usr.sbin/mptutil/mpt_show.c575
-rw-r--r--usr.sbin/mptutil/mpt_volume.c258
-rw-r--r--usr.sbin/mptutil/mptutil.8397
-rw-r--r--usr.sbin/mptutil/mptutil.c125
-rw-r--r--usr.sbin/mptutil/mptutil.h178
-rw-r--r--usr.sbin/mtest/Makefile18
-rw-r--r--usr.sbin/mtest/Makefile.depend19
-rw-r--r--usr.sbin/mtest/mtest.8174
-rw-r--r--usr.sbin/mtest/mtest.c851
-rw-r--r--usr.sbin/nandsim/Makefile8
-rw-r--r--usr.sbin/nandsim/Makefile.depend18
-rw-r--r--usr.sbin/nandsim/nandsim.8229
-rw-r--r--usr.sbin/nandsim/nandsim.c1397
-rw-r--r--usr.sbin/nandsim/nandsim_cfgparse.c959
-rw-r--r--usr.sbin/nandsim/nandsim_cfgparse.h86
-rw-r--r--usr.sbin/nandsim/nandsim_rcfile.c440
-rw-r--r--usr.sbin/nandsim/nandsim_rcfile.h70
-rw-r--r--usr.sbin/nandsim/sample.conf174
-rw-r--r--usr.sbin/nandtool/Makefile10
-rw-r--r--usr.sbin/nandtool/Makefile.depend21
-rw-r--r--usr.sbin/nandtool/nand_erase.c114
-rw-r--r--usr.sbin/nandtool/nand_info.c86
-rw-r--r--usr.sbin/nandtool/nand_read.c139
-rw-r--r--usr.sbin/nandtool/nand_readoob.c111
-rw-r--r--usr.sbin/nandtool/nand_write.c143
-rw-r--r--usr.sbin/nandtool/nand_writeoob.c113
-rw-r--r--usr.sbin/nandtool/nandtool.8184
-rw-r--r--usr.sbin/nandtool/nandtool.c283
-rw-r--r--usr.sbin/nandtool/nandtool.h57
-rw-r--r--usr.sbin/nandtool/usage.h112
-rw-r--r--usr.sbin/ndiscvt/Makefile29
-rw-r--r--usr.sbin/ndiscvt/Makefile.depend26
-rw-r--r--usr.sbin/ndiscvt/inf-parse.y110
-rw-r--r--usr.sbin/ndiscvt/inf-token.l131
-rw-r--r--usr.sbin/ndiscvt/inf.c916
-rw-r--r--usr.sbin/ndiscvt/inf.h61
-rw-r--r--usr.sbin/ndiscvt/ndiscvt.8283
-rw-r--r--usr.sbin/ndiscvt/ndiscvt.c432
-rw-r--r--usr.sbin/ndiscvt/ndisgen.886
-rw-r--r--usr.sbin/ndiscvt/ndisgen.sh559
-rw-r--r--usr.sbin/ndiscvt/windrv_stub.c266
-rw-r--r--usr.sbin/ndp/Makefile27
-rw-r--r--usr.sbin/ndp/Makefile.depend19
-rw-r--r--usr.sbin/ndp/ndp.8313
-rw-r--r--usr.sbin/ndp/ndp.c1375
-rw-r--r--usr.sbin/newsyslog/Makefile13
-rw-r--r--usr.sbin/newsyslog/Makefile.depend18
-rw-r--r--usr.sbin/newsyslog/extern.h68
-rw-r--r--usr.sbin/newsyslog/newsyslog.8308
-rw-r--r--usr.sbin/newsyslog/newsyslog.c2673
-rw-r--r--usr.sbin/newsyslog/newsyslog.conf.5394
-rw-r--r--usr.sbin/newsyslog/pathnames.h29
-rw-r--r--usr.sbin/newsyslog/ptimes.c618
-rw-r--r--usr.sbin/newsyslog/tests/Makefile5
-rw-r--r--usr.sbin/newsyslog/tests/legacy_test.sh444
-rw-r--r--usr.sbin/nfscbd/Makefile6
-rw-r--r--usr.sbin/nfscbd/Makefile.depend19
-rw-r--r--usr.sbin/nfscbd/nfscbd.886
-rw-r--r--usr.sbin/nfscbd/nfscbd.c373
-rw-r--r--usr.sbin/nfsd/Makefile7
-rw-r--r--usr.sbin/nfsd/Makefile.depend21
-rw-r--r--usr.sbin/nfsd/nfsd.8240
-rw-r--r--usr.sbin/nfsd/nfsd.c1086
-rw-r--r--usr.sbin/nfsd/nfsv4.4331
-rw-r--r--usr.sbin/nfsd/stablerestart.597
-rw-r--r--usr.sbin/nfsdumpstate/Makefile6
-rw-r--r--usr.sbin/nfsdumpstate/Makefile.depend19
-rw-r--r--usr.sbin/nfsdumpstate/nfsdumpstate.874
-rw-r--r--usr.sbin/nfsdumpstate/nfsdumpstate.c280
-rw-r--r--usr.sbin/nfsrevoke/Makefile6
-rw-r--r--usr.sbin/nfsrevoke/Makefile.depend18
-rw-r--r--usr.sbin/nfsrevoke/nfsrevoke.864
-rw-r--r--usr.sbin/nfsrevoke/nfsrevoke.c123
-rw-r--r--usr.sbin/nfsuserd/Makefile7
-rw-r--r--usr.sbin/nfsuserd/Makefile.depend19
-rw-r--r--usr.sbin/nfsuserd/nfsuserd.8128
-rw-r--r--usr.sbin/nfsuserd/nfsuserd.c725
-rw-r--r--usr.sbin/ngctl/Makefile27
-rw-r--r--usr.sbin/ngctl/Makefile.depend22
-rw-r--r--usr.sbin/ngctl/config.c111
-rw-r--r--usr.sbin/ngctl/connect.c90
-rw-r--r--usr.sbin/ngctl/debug.c84
-rw-r--r--usr.sbin/ngctl/dot.c200
-rw-r--r--usr.sbin/ngctl/list.c146
-rw-r--r--usr.sbin/ngctl/main.c665
-rw-r--r--usr.sbin/ngctl/mkpeer.c90
-rw-r--r--usr.sbin/ngctl/msg.c165
-rw-r--r--usr.sbin/ngctl/name.c81
-rw-r--r--usr.sbin/ngctl/ngctl.8141
-rw-r--r--usr.sbin/ngctl/ngctl.h82
-rw-r--r--usr.sbin/ngctl/rmhook.c86
-rw-r--r--usr.sbin/ngctl/show.c139
-rw-r--r--usr.sbin/ngctl/shutdown.c79
-rw-r--r--usr.sbin/ngctl/status.c101
-rw-r--r--usr.sbin/ngctl/types.c101
-rw-r--r--usr.sbin/ngctl/write.c121
-rw-r--r--usr.sbin/nghook/Makefile10
-rw-r--r--usr.sbin/nghook/Makefile.depend19
-rw-r--r--usr.sbin/nghook/main.c309
-rw-r--r--usr.sbin/nghook/nghook.8145
-rw-r--r--usr.sbin/nmtree/Makefile26
-rw-r--r--usr.sbin/nmtree/Makefile.depend21
-rw-r--r--usr.sbin/nmtree/mtree.5264
-rw-r--r--usr.sbin/nmtree/tests/Makefile30
-rw-r--r--usr.sbin/nologin/Makefile16
-rw-r--r--usr.sbin/nologin/Makefile.depend17
-rw-r--r--usr.sbin/nologin/nologin.596
-rw-r--r--usr.sbin/nologin/nologin.857
-rw-r--r--usr.sbin/nologin/nologin.c51
-rw-r--r--usr.sbin/nscd/Makefile16
-rw-r--r--usr.sbin/nscd/Makefile.depend21
-rw-r--r--usr.sbin/nscd/agent.c127
-rw-r--r--usr.sbin/nscd/agent.h71
-rw-r--r--usr.sbin/nscd/agents/Makefile.inc3
-rw-r--r--usr.sbin/nscd/agents/group.c260
-rw-r--r--usr.sbin/nscd/agents/group.h32
-rw-r--r--usr.sbin/nscd/agents/passwd.c268
-rw-r--r--usr.sbin/nscd/agents/passwd.h32
-rw-r--r--usr.sbin/nscd/agents/services.c281
-rw-r--r--usr.sbin/nscd/agents/services.h32
-rw-r--r--usr.sbin/nscd/cachelib.c1244
-rw-r--r--usr.sbin/nscd/cachelib.h263
-rw-r--r--usr.sbin/nscd/cacheplcs.c590
-rw-r--r--usr.sbin/nscd/cacheplcs.h129
-rw-r--r--usr.sbin/nscd/config.c588
-rw-r--r--usr.sbin/nscd/config.h150
-rw-r--r--usr.sbin/nscd/debug.c150
-rw-r--r--usr.sbin/nscd/debug.h67
-rw-r--r--usr.sbin/nscd/hashtable.h221
-rw-r--r--usr.sbin/nscd/log.c79
-rw-r--r--usr.sbin/nscd/log.h43
-rw-r--r--usr.sbin/nscd/mp_rs_query.c537
-rw-r--r--usr.sbin/nscd/mp_rs_query.h34
-rw-r--r--usr.sbin/nscd/mp_ws_query.c546
-rw-r--r--usr.sbin/nscd/mp_ws_query.h35
-rw-r--r--usr.sbin/nscd/nscd.8165
-rw-r--r--usr.sbin/nscd/nscd.c870
-rw-r--r--usr.sbin/nscd/nscd.conf.5158
-rw-r--r--usr.sbin/nscd/nscdcli.c287
-rw-r--r--usr.sbin/nscd/nscdcli.h55
-rw-r--r--usr.sbin/nscd/parser.c522
-rw-r--r--usr.sbin/nscd/parser.h35
-rw-r--r--usr.sbin/nscd/protocol.c551
-rw-r--r--usr.sbin/nscd/protocol.h249
-rw-r--r--usr.sbin/nscd/query.c1277
-rw-r--r--usr.sbin/nscd/query.h104
-rw-r--r--usr.sbin/nscd/singletons.c38
-rw-r--r--usr.sbin/nscd/singletons.h47
-rw-r--r--usr.sbin/ntp/Makefile18
-rw-r--r--usr.sbin/ntp/Makefile.inc19
-rw-r--r--usr.sbin/ntp/config.h1808
-rw-r--r--usr.sbin/ntp/doc/Makefile34
-rw-r--r--usr.sbin/ntp/doc/Makefile.depend11
-rw-r--r--usr.sbin/ntp/doc/drivers/Makefile21
-rw-r--r--usr.sbin/ntp/doc/drivers/Makefile.depend11
-rw-r--r--usr.sbin/ntp/doc/drivers/icons/Makefile13
-rw-r--r--usr.sbin/ntp/doc/drivers/icons/Makefile.depend11
-rw-r--r--usr.sbin/ntp/doc/drivers/scripts/Makefile13
-rw-r--r--usr.sbin/ntp/doc/drivers/scripts/Makefile.depend11
-rw-r--r--usr.sbin/ntp/doc/hints/Makefile17
-rw-r--r--usr.sbin/ntp/doc/hints/Makefile.depend11
-rw-r--r--usr.sbin/ntp/doc/icons/Makefile13
-rw-r--r--usr.sbin/ntp/doc/icons/Makefile.depend11
-rw-r--r--usr.sbin/ntp/doc/ntp-keygen.81073
-rw-r--r--usr.sbin/ntp/doc/ntp.conf.52858
-rw-r--r--usr.sbin/ntp/doc/ntp.keys.5160
-rw-r--r--usr.sbin/ntp/doc/ntpd.8910
-rw-r--r--usr.sbin/ntp/doc/ntpdate.8279
-rw-r--r--usr.sbin/ntp/doc/ntpdc.8811
-rw-r--r--usr.sbin/ntp/doc/ntpq.8966
-rw-r--r--usr.sbin/ntp/doc/ntptime.867
-rw-r--r--usr.sbin/ntp/doc/ntptrace.893
-rw-r--r--usr.sbin/ntp/doc/pic/Makefile27
-rw-r--r--usr.sbin/ntp/doc/pic/Makefile.depend11
-rw-r--r--usr.sbin/ntp/doc/scripts/Makefile15
-rw-r--r--usr.sbin/ntp/doc/scripts/Makefile.depend11
-rw-r--r--usr.sbin/ntp/doc/sntp.8314
-rw-r--r--usr.sbin/ntp/libntp/Makefile89
-rw-r--r--usr.sbin/ntp/libntp/Makefile.depend19
-rw-r--r--usr.sbin/ntp/libntpevent/Makefile34
-rw-r--r--usr.sbin/ntp/libntpevent/Makefile.depend16
-rw-r--r--usr.sbin/ntp/libntpevent/event2/event-config.h648
-rw-r--r--usr.sbin/ntp/libopts/Makefile14
-rw-r--r--usr.sbin/ntp/libopts/Makefile.depend13
-rw-r--r--usr.sbin/ntp/libparse/Makefile19
-rw-r--r--usr.sbin/ntp/libparse/Makefile.depend16
-rw-r--r--usr.sbin/ntp/ntp-keygen/Makefile29
-rw-r--r--usr.sbin/ntp/ntp-keygen/Makefile.depend24
-rw-r--r--usr.sbin/ntp/ntpd/Makefile52
-rw-r--r--usr.sbin/ntp/ntpd/Makefile.depend27
-rw-r--r--usr.sbin/ntp/ntpdate/Makefile30
-rw-r--r--usr.sbin/ntp/ntpdate/Makefile.depend25
-rw-r--r--usr.sbin/ntp/ntpdc/Makefile36
-rw-r--r--usr.sbin/ntp/ntpdc/Makefile.depend28
-rw-r--r--usr.sbin/ntp/ntpdc/nl.c895
-rw-r--r--usr.sbin/ntp/ntpq/Makefile40
-rw-r--r--usr.sbin/ntp/ntpq/Makefile.depend28
-rw-r--r--usr.sbin/ntp/ntptime/Makefile16
-rw-r--r--usr.sbin/ntp/ntptime/Makefile.depend23
-rwxr-xr-xusr.sbin/ntp/scripts/mkver44
-rw-r--r--usr.sbin/ntp/scripts/ntptrace62
-rwxr-xr-xusr.sbin/ntp/scripts/ntpver8
-rw-r--r--usr.sbin/ntp/sntp/Makefile31
-rw-r--r--usr.sbin/ntp/sntp/Makefile.depend25
-rw-r--r--usr.sbin/nvram/Makefile7
-rw-r--r--usr.sbin/nvram/nvram.8118
-rw-r--r--usr.sbin/nvram/nvram.c226
-rw-r--r--usr.sbin/ofwdump/Makefile7
-rw-r--r--usr.sbin/ofwdump/Makefile.depend19
-rw-r--r--usr.sbin/ofwdump/ofw_util.c230
-rw-r--r--usr.sbin/ofwdump/ofw_util.h50
-rw-r--r--usr.sbin/ofwdump/ofwdump.8107
-rw-r--r--usr.sbin/ofwdump/ofwdump.c250
-rw-r--r--usr.sbin/ofwdump/pathnames.h30
-rw-r--r--usr.sbin/pc-sysinstall/Makefile6
-rw-r--r--usr.sbin/pc-sysinstall/Makefile.inc3
-rw-r--r--usr.sbin/pc-sysinstall/backend-partmanager/Makefile7
-rw-r--r--usr.sbin/pc-sysinstall/backend-partmanager/Makefile.depend11
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-partmanager/create-part.sh103
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-partmanager/delete-part.sh89
-rw-r--r--usr.sbin/pc-sysinstall/backend-query/Makefile12
-rw-r--r--usr.sbin/pc-sysinstall/backend-query/Makefile.depend11
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/detect-emulation.sh41
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/detect-laptop.sh32
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/detect-nics.sh36
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/disk-info.sh60
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/disk-list.sh112
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/disk-part.sh111
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/enable-net.sh114
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/get-packages.sh52
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/list-components.sh55
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/list-config.sh30
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/list-mirrors.sh37
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/list-packages.sh86
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/list-rsync-backups.sh70
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/list-tzones.sh34
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/query-langs.sh30
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/send-logs.sh83
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/set-mirror.sh40
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/setup-ssh-keys.sh64
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/sys-mem.sh39
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/test-live.sh33
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/test-netup.sh69
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/update-part-list.sh99
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/xkeyboard-layouts.sh67
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/xkeyboard-models.sh58
-rwxr-xr-xusr.sbin/pc-sysinstall/backend-query/xkeyboard-variants.sh56
-rw-r--r--usr.sbin/pc-sysinstall/backend/Makefile14
-rw-r--r--usr.sbin/pc-sysinstall/backend/Makefile.depend11
-rwxr-xr-xusr.sbin/pc-sysinstall/backend/functions-bsdlabel.sh782
-rwxr-xr-xusr.sbin/pc-sysinstall/backend/functions-cleanup.sh411
-rwxr-xr-xusr.sbin/pc-sysinstall/backend/functions-disk.sh908
-rwxr-xr-xusr.sbin/pc-sysinstall/backend/functions-extractimage.sh552
-rwxr-xr-xusr.sbin/pc-sysinstall/backend/functions-ftp.sh414
-rwxr-xr-xusr.sbin/pc-sysinstall/backend/functions-installcomponents.sh177
-rwxr-xr-xusr.sbin/pc-sysinstall/backend/functions-installpackages.sh188
-rwxr-xr-xusr.sbin/pc-sysinstall/backend/functions-localize.sh541
-rwxr-xr-xusr.sbin/pc-sysinstall/backend/functions-mountdisk.sh240
-rwxr-xr-xusr.sbin/pc-sysinstall/backend/functions-mountoptical.sh152
-rwxr-xr-xusr.sbin/pc-sysinstall/backend/functions-networking.sh500
-rwxr-xr-xusr.sbin/pc-sysinstall/backend/functions-newfs.sh262
-rwxr-xr-xusr.sbin/pc-sysinstall/backend/functions-packages.sh411
-rwxr-xr-xusr.sbin/pc-sysinstall/backend/functions-parse.sh229
-rwxr-xr-xusr.sbin/pc-sysinstall/backend/functions-runcommands.sh110
-rwxr-xr-xusr.sbin/pc-sysinstall/backend/functions-unmount.sh210
-rwxr-xr-xusr.sbin/pc-sysinstall/backend/functions-upgrade.sh247
-rwxr-xr-xusr.sbin/pc-sysinstall/backend/functions-users.sh186
-rwxr-xr-xusr.sbin/pc-sysinstall/backend/functions.sh540
-rwxr-xr-xusr.sbin/pc-sysinstall/backend/installimage.sh34
-rwxr-xr-xusr.sbin/pc-sysinstall/backend/parseconfig.sh124
-rwxr-xr-xusr.sbin/pc-sysinstall/backend/startautoinstall.sh136
-rw-r--r--usr.sbin/pc-sysinstall/conf/Makefile9
-rw-r--r--usr.sbin/pc-sysinstall/conf/Makefile.depend11
-rw-r--r--usr.sbin/pc-sysinstall/conf/avail-langs20
-rw-r--r--usr.sbin/pc-sysinstall/conf/exclude-from-upgrade15
-rw-r--r--usr.sbin/pc-sysinstall/conf/licenses/bsd-en.txt24
-rw-r--r--usr.sbin/pc-sysinstall/conf/licenses/intel-en.txt207
-rw-r--r--usr.sbin/pc-sysinstall/conf/licenses/nvidia-en.txt53
-rw-r--r--usr.sbin/pc-sysinstall/conf/pc-sysinstall.conf84
-rw-r--r--usr.sbin/pc-sysinstall/doc/Makefile7
-rw-r--r--usr.sbin/pc-sysinstall/doc/Makefile.depend11
-rw-r--r--usr.sbin/pc-sysinstall/doc/help-disk-list1
-rw-r--r--usr.sbin/pc-sysinstall/doc/help-disk-size1
-rw-r--r--usr.sbin/pc-sysinstall/doc/help-index100
-rw-r--r--usr.sbin/pc-sysinstall/doc/help-start-autoinstall39
-rw-r--r--usr.sbin/pc-sysinstall/examples/Makefile10
-rw-r--r--usr.sbin/pc-sysinstall/examples/Makefile.depend11
-rw-r--r--usr.sbin/pc-sysinstall/examples/README403
-rw-r--r--usr.sbin/pc-sysinstall/examples/pc-autoinstall.conf52
-rw-r--r--usr.sbin/pc-sysinstall/examples/pcinstall.cfg.fbsd-netinstall71
-rw-r--r--usr.sbin/pc-sysinstall/examples/pcinstall.cfg.geli50
-rw-r--r--usr.sbin/pc-sysinstall/examples/pcinstall.cfg.gmirror45
-rw-r--r--usr.sbin/pc-sysinstall/examples/pcinstall.cfg.netinstall68
-rw-r--r--usr.sbin/pc-sysinstall/examples/pcinstall.cfg.restore57
-rw-r--r--usr.sbin/pc-sysinstall/examples/pcinstall.cfg.rsync45
-rw-r--r--usr.sbin/pc-sysinstall/examples/pcinstall.cfg.upgrade24
-rw-r--r--usr.sbin/pc-sysinstall/examples/pcinstall.cfg.zfs59
-rw-r--r--usr.sbin/pc-sysinstall/pc-sysinstall/Makefile6
-rw-r--r--usr.sbin/pc-sysinstall/pc-sysinstall/Makefile.depend11
-rw-r--r--usr.sbin/pc-sysinstall/pc-sysinstall/pc-sysinstall.8120
-rwxr-xr-xusr.sbin/pc-sysinstall/pc-sysinstall/pc-sysinstall.sh240
-rw-r--r--usr.sbin/pciconf/Makefile10
-rw-r--r--usr.sbin/pciconf/Makefile.depend18
-rw-r--r--usr.sbin/pciconf/cap.c887
-rw-r--r--usr.sbin/pciconf/err.c173
-rw-r--r--usr.sbin/pciconf/pathnames.h4
-rw-r--r--usr.sbin/pciconf/pciconf.8370
-rw-r--r--usr.sbin/pciconf/pciconf.c1024
-rw-r--r--usr.sbin/pciconf/pciconf.h43
-rw-r--r--usr.sbin/periodic/Makefile6
-rw-r--r--usr.sbin/periodic/Makefile.depend11
-rw-r--r--usr.sbin/periodic/periodic.8258
-rw-r--r--usr.sbin/periodic/periodic.sh143
-rw-r--r--usr.sbin/pkg/Makefile11
-rw-r--r--usr.sbin/pkg/Makefile.depend31
-rw-r--r--usr.sbin/pkg/config.c541
-rw-r--r--usr.sbin/pkg/config.h63
-rw-r--r--usr.sbin/pkg/dns_utils.c222
-rw-r--r--usr.sbin/pkg/dns_utils.h46
-rw-r--r--usr.sbin/pkg/pkg.7281
-rw-r--r--usr.sbin/pkg/pkg.c1109
-rw-r--r--usr.sbin/pmcannotate/Makefile10
-rw-r--r--usr.sbin/pmcannotate/Makefile.depend18
-rw-r--r--usr.sbin/pmcannotate/pmcannotate.8109
-rw-r--r--usr.sbin/pmcannotate/pmcannotate.c803
-rw-r--r--usr.sbin/pmccontrol/Makefile12
-rw-r--r--usr.sbin/pmccontrol/Makefile.depend19
-rw-r--r--usr.sbin/pmccontrol/pmccontrol.8127
-rw-r--r--usr.sbin/pmccontrol/pmccontrol.c487
-rw-r--r--usr.sbin/pmcstat/Makefile14
-rw-r--r--usr.sbin/pmcstat/Makefile.depend23
-rw-r--r--usr.sbin/pmcstat/pmcpl_annotate.c111
-rw-r--r--usr.sbin/pmcstat/pmcpl_annotate.h41
-rw-r--r--usr.sbin/pmcstat/pmcpl_annotate_cg.c127
-rw-r--r--usr.sbin/pmcstat/pmcpl_annotate_cg.h42
-rw-r--r--usr.sbin/pmcstat/pmcpl_callgraph.c687
-rw-r--r--usr.sbin/pmcstat/pmcpl_callgraph.h67
-rw-r--r--usr.sbin/pmcstat/pmcpl_calltree.c1197
-rw-r--r--usr.sbin/pmcstat/pmcpl_calltree.h42
-rw-r--r--usr.sbin/pmcstat/pmcpl_gprof.c566
-rw-r--r--usr.sbin/pmcstat/pmcpl_gprof.h47
-rw-r--r--usr.sbin/pmcstat/pmcstat.8500
-rw-r--r--usr.sbin/pmcstat/pmcstat.c1537
-rw-r--r--usr.sbin/pmcstat/pmcstat.h187
-rw-r--r--usr.sbin/pmcstat/pmcstat_log.c2238
-rw-r--r--usr.sbin/pmcstat/pmcstat_log.h199
-rw-r--r--usr.sbin/pmcstat/pmcstat_top.h75
-rw-r--r--usr.sbin/pmcstudy/Makefile12
-rw-r--r--usr.sbin/pmcstudy/eval_expr.c717
-rw-r--r--usr.sbin/pmcstudy/eval_expr.h58
-rw-r--r--usr.sbin/pmcstudy/pmcstudy.8145
-rw-r--r--usr.sbin/pmcstudy/pmcstudy.c2954
-rw-r--r--usr.sbin/pnpinfo/Makefile16
-rw-r--r--usr.sbin/pnpinfo/Makefile.depend16
-rw-r--r--usr.sbin/portsnap/Makefile5
-rw-r--r--usr.sbin/portsnap/Makefile.inc5
-rw-r--r--usr.sbin/portsnap/make_index/Makefile8
-rw-r--r--usr.sbin/portsnap/make_index/Makefile.depend18
-rw-r--r--usr.sbin/portsnap/make_index/make_index.c513
-rw-r--r--usr.sbin/portsnap/phttpget/Makefile8
-rw-r--r--usr.sbin/portsnap/phttpget/Makefile.depend18
-rw-r--r--usr.sbin/portsnap/phttpget/phttpget.888
-rw-r--r--usr.sbin/portsnap/phttpget/phttpget.c730
-rw-r--r--usr.sbin/portsnap/portsnap/Makefile6
-rw-r--r--usr.sbin/portsnap/portsnap/Makefile.depend11
-rw-r--r--usr.sbin/portsnap/portsnap/portsnap.8277
-rw-r--r--usr.sbin/portsnap/portsnap/portsnap.sh1139
-rw-r--r--usr.sbin/powerd/Makefile8
-rw-r--r--usr.sbin/powerd/Makefile.depend19
-rw-r--r--usr.sbin/powerd/powerd.8163
-rw-r--r--usr.sbin/powerd/powerd.c800
-rw-r--r--usr.sbin/ppp/Makefile114
-rw-r--r--usr.sbin/ppp/Makefile.depend27
-rw-r--r--usr.sbin/ppp/README.changes140
-rw-r--r--usr.sbin/ppp/README.nat378
-rw-r--r--usr.sbin/ppp/acf.c116
-rw-r--r--usr.sbin/ppp/acf.h33
-rw-r--r--usr.sbin/ppp/arp.c316
-rw-r--r--usr.sbin/ppp/arp.h36
-rw-r--r--usr.sbin/ppp/async.c220
-rw-r--r--usr.sbin/ppp/async.h53
-rw-r--r--usr.sbin/ppp/atm.c237
-rw-r--r--usr.sbin/ppp/atm.h35
-rw-r--r--usr.sbin/ppp/auth.c481
-rw-r--r--usr.sbin/ppp/auth.h68
-rw-r--r--usr.sbin/ppp/bundle.c2019
-rw-r--r--usr.sbin/ppp/bundle.h216
-rw-r--r--usr.sbin/ppp/cbcp.c763
-rw-r--r--usr.sbin/ppp/cbcp.h65
-rw-r--r--usr.sbin/ppp/ccp.c826
-rw-r--r--usr.sbin/ppp/ccp.h165
-rw-r--r--usr.sbin/ppp/chap.c972
-rw-r--r--usr.sbin/ppp/chap.h75
-rw-r--r--usr.sbin/ppp/chap_ms.c415
-rw-r--r--usr.sbin/ppp/chap_ms.h52
-rw-r--r--usr.sbin/ppp/chat.c802
-rw-r--r--usr.sbin/ppp/chat.h82
-rw-r--r--usr.sbin/ppp/command.c3332
-rw-r--r--usr.sbin/ppp/command.h75
-rw-r--r--usr.sbin/ppp/datalink.c1478
-rw-r--r--usr.sbin/ppp/datalink.h156
-rw-r--r--usr.sbin/ppp/deflate.c601
-rw-r--r--usr.sbin/ppp/deflate.h30
-rw-r--r--usr.sbin/ppp/defs.c441
-rw-r--r--usr.sbin/ppp/defs.h142
-rw-r--r--usr.sbin/ppp/descriptor.h53
-rw-r--r--usr.sbin/ppp/ether.c737
-rw-r--r--usr.sbin/ppp/ether.h37
-rw-r--r--usr.sbin/ppp/exec.c410
-rw-r--r--usr.sbin/ppp/exec.h35
-rw-r--r--usr.sbin/ppp/filter.c604
-rw-r--r--usr.sbin/ppp/filter.h101
-rw-r--r--usr.sbin/ppp/fsm.c1213
-rw-r--r--usr.sbin/ppp/fsm.h201
-rw-r--r--usr.sbin/ppp/hdlc.c438
-rw-r--r--usr.sbin/ppp/hdlc.h116
-rw-r--r--usr.sbin/ppp/i4b.h37
-rw-r--r--usr.sbin/ppp/id.c292
-rw-r--r--usr.sbin/ppp/id.h81
-rw-r--r--usr.sbin/ppp/iface.c836
-rw-r--r--usr.sbin/ppp/iface.h69
-rw-r--r--usr.sbin/ppp/ip.c993
-rw-r--r--usr.sbin/ppp/ip.h44
-rw-r--r--usr.sbin/ppp/ipcp.c1482
-rw-r--r--usr.sbin/ppp/ipcp.h132
-rw-r--r--usr.sbin/ppp/iplist.c225
-rw-r--r--usr.sbin/ppp/iplist.h51
-rw-r--r--usr.sbin/ppp/ipv6cp.c786
-rw-r--r--usr.sbin/ppp/ipv6cp.h83
-rw-r--r--usr.sbin/ppp/layer.h52
-rw-r--r--usr.sbin/ppp/lcp.c1305
-rw-r--r--usr.sbin/ppp/lcp.h143
-rw-r--r--usr.sbin/ppp/link.c412
-rw-r--r--usr.sbin/ppp/link.h81
-rw-r--r--usr.sbin/ppp/log.c532
-rw-r--r--usr.sbin/ppp/log.h105
-rw-r--r--usr.sbin/ppp/lqr.c532
-rw-r--r--usr.sbin/ppp/lqr.h82
-rw-r--r--usr.sbin/ppp/main.c680
-rw-r--r--usr.sbin/ppp/main.h32
-rw-r--r--usr.sbin/ppp/mbuf.c440
-rw-r--r--usr.sbin/ppp/mbuf.h119
-rw-r--r--usr.sbin/ppp/mp.c1209
-rw-r--r--usr.sbin/ppp/mp.h146
-rw-r--r--usr.sbin/ppp/mppe.c817
-rw-r--r--usr.sbin/ppp/mppe.h33
-rw-r--r--usr.sbin/ppp/nat_cmd.c601
-rw-r--r--usr.sbin/ppp/nat_cmd.h42
-rw-r--r--usr.sbin/ppp/ncp.c547
-rw-r--r--usr.sbin/ppp/ncp.h101
-rw-r--r--usr.sbin/ppp/ncpaddr.c1010
-rw-r--r--usr.sbin/ppp/ncpaddr.h109
-rw-r--r--usr.sbin/ppp/netgraph.c743
-rw-r--r--usr.sbin/ppp/netgraph.h37
-rw-r--r--usr.sbin/ppp/pap.c303
-rw-r--r--usr.sbin/ppp/pap.h40
-rw-r--r--usr.sbin/ppp/physical.c1140
-rw-r--r--usr.sbin/ppp/physical.h176
-rw-r--r--usr.sbin/ppp/ppp.86079
-rw-r--r--usr.sbin/ppp/ppp.conf37
-rw-r--r--usr.sbin/ppp/pred.c345
-rw-r--r--usr.sbin/ppp/pred.h31
-rw-r--r--usr.sbin/ppp/probe.c78
-rw-r--r--usr.sbin/ppp/probe.h38
-rw-r--r--usr.sbin/ppp/prompt.c574
-rw-r--r--usr.sbin/ppp/prompt.h96
-rw-r--r--usr.sbin/ppp/proto.c115
-rw-r--r--usr.sbin/ppp/proto.h64
-rw-r--r--usr.sbin/ppp/radius.c1361
-rw-r--r--usr.sbin/ppp/radius.h133
-rw-r--r--usr.sbin/ppp/route.c934
-rw-r--r--usr.sbin/ppp/route.h74
-rw-r--r--usr.sbin/ppp/server.c422
-rw-r--r--usr.sbin/ppp/server.h61
-rw-r--r--usr.sbin/ppp/sig.c119
-rw-r--r--usr.sbin/ppp/sig.h35
-rw-r--r--usr.sbin/ppp/slcompress.c605
-rw-r--r--usr.sbin/ppp/slcompress.h161
-rw-r--r--usr.sbin/ppp/sync.c84
-rw-r--r--usr.sbin/ppp/sync.h29
-rw-r--r--usr.sbin/ppp/systems.c483
-rw-r--r--usr.sbin/ppp/systems.h43
-rw-r--r--usr.sbin/ppp/tcp.c212
-rw-r--r--usr.sbin/ppp/tcp.h34
-rw-r--r--usr.sbin/ppp/tcpmss.c185
-rw-r--r--usr.sbin/ppp/tcpmss.h29
-rw-r--r--usr.sbin/ppp/throughput.c302
-rw-r--r--usr.sbin/ppp/throughput.h70
-rw-r--r--usr.sbin/ppp/timer.c302
-rw-r--r--usr.sbin/ppp/timer.h55
-rw-r--r--usr.sbin/ppp/tty.c770
-rw-r--r--usr.sbin/ppp/tty.h37
-rw-r--r--usr.sbin/ppp/tun.c119
-rw-r--r--usr.sbin/ppp/tun.h39
-rw-r--r--usr.sbin/ppp/ua.h74
-rw-r--r--usr.sbin/ppp/udp.c335
-rw-r--r--usr.sbin/ppp/udp.h35
-rw-r--r--usr.sbin/ppp/vjcomp.c200
-rw-r--r--usr.sbin/ppp/vjcomp.h36
-rw-r--r--usr.sbin/pppctl/Makefile10
-rw-r--r--usr.sbin/pppctl/Makefile.depend22
-rw-r--r--usr.sbin/pppctl/pppctl.8232
-rw-r--r--usr.sbin/pppctl/pppctl.c680
-rw-r--r--usr.sbin/praliases/Makefile30
-rw-r--r--usr.sbin/praliases/Makefile.depend23
-rw-r--r--usr.sbin/praudit/Makefile15
-rw-r--r--usr.sbin/praudit/Makefile.depend19
-rw-r--r--usr.sbin/procctl/Makefile6
-rw-r--r--usr.sbin/procctl/Makefile.depend18
-rw-r--r--usr.sbin/procctl/procctl.834
-rw-r--r--usr.sbin/procctl/procctl.c79
-rw-r--r--usr.sbin/pstat/Makefile11
-rw-r--r--usr.sbin/pstat/Makefile.depend21
-rw-r--r--usr.sbin/pstat/pstat.8251
-rw-r--r--usr.sbin/pstat/pstat.c597
-rw-r--r--usr.sbin/pw/Makefile19
-rw-r--r--usr.sbin/pw/Makefile.depend21
-rw-r--r--usr.sbin/pw/README22
-rw-r--r--usr.sbin/pw/bitmap.c131
-rw-r--r--usr.sbin/pw/bitmap.h50
-rw-r--r--usr.sbin/pw/cpdir.c124
-rw-r--r--usr.sbin/pw/grupd.c105
-rw-r--r--usr.sbin/pw/psdate.c261
-rw-r--r--usr.sbin/pw/psdate.h40
-rw-r--r--usr.sbin/pw/pw.81049
-rw-r--r--usr.sbin/pw/pw.c381
-rw-r--r--usr.sbin/pw/pw.conf.5318
-rw-r--r--usr.sbin/pw/pw.h106
-rw-r--r--usr.sbin/pw/pw_conf.c524
-rw-r--r--usr.sbin/pw/pw_group.c694
-rw-r--r--usr.sbin/pw/pw_log.c68
-rw-r--r--usr.sbin/pw/pw_nis.c95
-rw-r--r--usr.sbin/pw/pw_user.c1815
-rw-r--r--usr.sbin/pw/pw_utils.c99
-rw-r--r--usr.sbin/pw/pw_vpw.c205
-rw-r--r--usr.sbin/pw/pwupd.c149
-rw-r--r--usr.sbin/pw/pwupd.h152
-rw-r--r--usr.sbin/pw/rm_r.c70
-rw-r--r--usr.sbin/pw/strtounum.c72
-rw-r--r--usr.sbin/pw/tests/Makefile22
-rw-r--r--usr.sbin/pw/tests/group3
-rwxr-xr-xusr.sbin/pw/tests/helper_functions.shin32
-rw-r--r--usr.sbin/pw/tests/master.passwd4
-rw-r--r--usr.sbin/pw/tests/pw-modified.conf62
-rw-r--r--usr.sbin/pw/tests/pw.conf62
-rwxr-xr-xusr.sbin/pw/tests/pw_config.sh26
-rwxr-xr-xusr.sbin/pw/tests/pw_etcdir.sh18
-rwxr-xr-xusr.sbin/pw/tests/pw_groupadd.sh26
-rwxr-xr-xusr.sbin/pw/tests/pw_groupdel.sh24
-rwxr-xr-xusr.sbin/pw/tests/pw_groupmod.sh118
-rwxr-xr-xusr.sbin/pw/tests/pw_lock.sh42
-rwxr-xr-xusr.sbin/pw/tests/pw_useradd.sh385
-rwxr-xr-xusr.sbin/pw/tests/pw_userdel.sh67
-rwxr-xr-xusr.sbin/pw/tests/pw_usermod.sh222
-rwxr-xr-xusr.sbin/pw/tests/pw_usernext.sh45
-rw-r--r--usr.sbin/pwd_mkdb/Makefile12
-rw-r--r--usr.sbin/pwd_mkdb/Makefile.depend19
-rw-r--r--usr.sbin/pwd_mkdb/pwd_mkdb.8212
-rw-r--r--usr.sbin/pwd_mkdb/pwd_mkdb.c784
-rw-r--r--usr.sbin/quot/Makefile8
-rw-r--r--usr.sbin/quot/Makefile.depend18
-rw-r--r--usr.sbin/quot/quot.8110
-rw-r--r--usr.sbin/quot/quot.c644
-rw-r--r--usr.sbin/quotaon/Makefile11
-rw-r--r--usr.sbin/quotaon/Makefile.depend19
-rw-r--r--usr.sbin/quotaon/quotaon.8137
-rw-r--r--usr.sbin/quotaon/quotaon.c192
-rw-r--r--usr.sbin/rarpd/Makefile13
-rw-r--r--usr.sbin/rarpd/Makefile.depend20
-rw-r--r--usr.sbin/rarpd/rarpd.8160
-rw-r--r--usr.sbin/rarpd/rarpd.c1003
-rw-r--r--usr.sbin/repquota/Makefile8
-rw-r--r--usr.sbin/repquota/Makefile.depend19
-rw-r--r--usr.sbin/repquota/repquota.8111
-rw-r--r--usr.sbin/repquota/repquota.c371
-rw-r--r--usr.sbin/rip6query/Makefile9
-rw-r--r--usr.sbin/rip6query/Makefile.depend19
-rw-r--r--usr.sbin/rip6query/rip6query.864
-rw-r--r--usr.sbin/rip6query/rip6query.c197
-rw-r--r--usr.sbin/rmt/Makefile12
-rw-r--r--usr.sbin/rmt/Makefile.depend18
-rw-r--r--usr.sbin/rmt/rmt.8221
-rw-r--r--usr.sbin/rmt/rmt.c250
-rw-r--r--usr.sbin/route6d/Makefile11
-rw-r--r--usr.sbin/route6d/Makefile.depend19
-rwxr-xr-xusr.sbin/route6d/misc/chkrt64
-rw-r--r--usr.sbin/route6d/misc/cksum.c52
-rw-r--r--usr.sbin/route6d/route6d.8297
-rw-r--r--usr.sbin/route6d/route6d.c3623
-rw-r--r--usr.sbin/route6d/route6d.h81
-rw-r--r--usr.sbin/rpc.lockd/Makefile28
-rw-r--r--usr.sbin/rpc.lockd/Makefile.depend25
-rw-r--r--usr.sbin/rpc.lockd/kern.c602
-rw-r--r--usr.sbin/rpc.lockd/lock_proc.c1321
-rw-r--r--usr.sbin/rpc.lockd/lockd.c1025
-rw-r--r--usr.sbin/rpc.lockd/lockd.h41
-rw-r--r--usr.sbin/rpc.lockd/lockd_lock.c2302
-rw-r--r--usr.sbin/rpc.lockd/lockd_lock.h25
-rw-r--r--usr.sbin/rpc.lockd/rpc.lockd.8146
-rw-r--r--usr.sbin/rpc.lockd/test.c365
-rw-r--r--usr.sbin/rpc.statd/Makefile26
-rw-r--r--usr.sbin/rpc.statd/Makefile.depend32
-rw-r--r--usr.sbin/rpc.statd/file.c361
-rw-r--r--usr.sbin/rpc.statd/procs.c436
-rw-r--r--usr.sbin/rpc.statd/rpc.statd.8137
-rw-r--r--usr.sbin/rpc.statd/statd.c655
-rw-r--r--usr.sbin/rpc.statd/statd.h111
-rw-r--r--usr.sbin/rpc.statd/test.c144
-rw-r--r--usr.sbin/rpc.umntall/Makefile10
-rw-r--r--usr.sbin/rpc.umntall/Makefile.depend20
-rw-r--r--usr.sbin/rpc.umntall/mounttab.c230
-rw-r--r--usr.sbin/rpc.umntall/mounttab.h46
-rw-r--r--usr.sbin/rpc.umntall/rpc.umntall.8126
-rw-r--r--usr.sbin/rpc.umntall/rpc.umntall.c266
-rw-r--r--usr.sbin/rpc.yppasswdd/Makefile61
-rw-r--r--usr.sbin/rpc.yppasswdd/Makefile.depend48
-rw-r--r--usr.sbin/rpc.yppasswdd/rpc.yppasswdd.8359
-rw-r--r--usr.sbin/rpc.yppasswdd/yppasswd_private.x70
-rw-r--r--usr.sbin/rpc.yppasswdd/yppasswdd_extern.h69
-rw-r--r--usr.sbin/rpc.yppasswdd/yppasswdd_main.c352
-rw-r--r--usr.sbin/rpc.yppasswdd/yppasswdd_server.c919
-rw-r--r--usr.sbin/rpc.yppasswdd/yppwupdate34
-rw-r--r--usr.sbin/rpc.ypupdated/Makefile33
-rw-r--r--usr.sbin/rpc.ypupdated/Makefile.depend29
-rw-r--r--usr.sbin/rpc.ypupdated/update.c329
-rw-r--r--usr.sbin/rpc.ypupdated/yp_dbdelete.c68
-rw-r--r--usr.sbin/rpc.ypupdated/yp_dbupdate.c147
-rwxr-xr-xusr.sbin/rpc.ypupdated/ypupdate33
-rw-r--r--usr.sbin/rpc.ypupdated/ypupdated_extern.h33
-rw-r--r--usr.sbin/rpc.ypupdated/ypupdated_main.c285
-rw-r--r--usr.sbin/rpc.ypupdated/ypupdated_server.c227
-rw-r--r--usr.sbin/rpc.ypxfrd/Makefile36
-rw-r--r--usr.sbin/rpc.ypxfrd/Makefile.depend30
-rw-r--r--usr.sbin/rpc.ypxfrd/rpc.ypxfrd.8153
-rw-r--r--usr.sbin/rpc.ypxfrd/ypxfrd_extern.h50
-rw-r--r--usr.sbin/rpc.ypxfrd/ypxfrd_main.c302
-rw-r--r--usr.sbin/rpc.ypxfrd/ypxfrd_server.c145
-rw-r--r--usr.sbin/rpcbind/Makefile25
-rw-r--r--usr.sbin/rpcbind/Makefile.depend23
-rw-r--r--usr.sbin/rpcbind/check_bound.c241
-rw-r--r--usr.sbin/rpcbind/pmap_svc.c367
-rw-r--r--usr.sbin/rpcbind/rpcb_stat.c205
-rw-r--r--usr.sbin/rpcbind/rpcb_svc.c232
-rw-r--r--usr.sbin/rpcbind/rpcb_svc_4.c454
-rw-r--r--usr.sbin/rpcbind/rpcb_svc_com.c1486
-rw-r--r--usr.sbin/rpcbind/rpcbind.8150
-rw-r--r--usr.sbin/rpcbind/rpcbind.c868
-rw-r--r--usr.sbin/rpcbind/rpcbind.h155
-rw-r--r--usr.sbin/rpcbind/security.c290
-rw-r--r--usr.sbin/rpcbind/tests/Makefile17
-rw-r--r--usr.sbin/rpcbind/tests/addrmerge_test.c849
-rw-r--r--usr.sbin/rpcbind/util.c401
-rw-r--r--usr.sbin/rpcbind/warmstart.c178
-rw-r--r--usr.sbin/rrenumd/Makefile37
-rw-r--r--usr.sbin/rrenumd/Makefile.depend29
-rw-r--r--usr.sbin/rrenumd/lexer.l269
-rw-r--r--usr.sbin/rrenumd/parser.y673
-rw-r--r--usr.sbin/rrenumd/rrenumd.8103
-rw-r--r--usr.sbin/rrenumd/rrenumd.c663
-rw-r--r--usr.sbin/rrenumd/rrenumd.conf.5369
-rw-r--r--usr.sbin/rrenumd/rrenumd.h60
-rw-r--r--usr.sbin/rtadvctl/Makefile13
-rw-r--r--usr.sbin/rtadvctl/Makefile.depend19
-rw-r--r--usr.sbin/rtadvctl/rtadvctl.8105
-rw-r--r--usr.sbin/rtadvctl/rtadvctl.c938
-rw-r--r--usr.sbin/rtadvd/Makefile26
-rw-r--r--usr.sbin/rtadvd/Makefile.depend20
-rw-r--r--usr.sbin/rtadvd/advcap.c436
-rw-r--r--usr.sbin/rtadvd/advcap.h46
-rw-r--r--usr.sbin/rtadvd/config.c1540
-rw-r--r--usr.sbin/rtadvd/config.h53
-rw-r--r--usr.sbin/rtadvd/control.c492
-rw-r--r--usr.sbin/rtadvd/control.h74
-rw-r--r--usr.sbin/rtadvd/control_client.c131
-rw-r--r--usr.sbin/rtadvd/control_client.h30
-rw-r--r--usr.sbin/rtadvd/control_server.c752
-rw-r--r--usr.sbin/rtadvd/control_server.h42
-rw-r--r--usr.sbin/rtadvd/if.c748
-rw-r--r--usr.sbin/rtadvd/if.h61
-rw-r--r--usr.sbin/rtadvd/pathnames.h6
-rw-r--r--usr.sbin/rtadvd/rrenum.c502
-rw-r--r--usr.sbin/rtadvd/rrenum.h34
-rw-r--r--usr.sbin/rtadvd/rtadvd.8242
-rw-r--r--usr.sbin/rtadvd/rtadvd.c1914
-rw-r--r--usr.sbin/rtadvd/rtadvd.conf22
-rw-r--r--usr.sbin/rtadvd/rtadvd.conf.5530
-rw-r--r--usr.sbin/rtadvd/rtadvd.h298
-rw-r--r--usr.sbin/rtadvd/timer.c199
-rw-r--r--usr.sbin/rtadvd/timer.h52
-rw-r--r--usr.sbin/rtadvd/timer_subr.c91
-rw-r--r--usr.sbin/rtadvd/timer_subr.h59
-rw-r--r--usr.sbin/rtprio/Makefile10
-rw-r--r--usr.sbin/rtprio/Makefile.depend18
-rw-r--r--usr.sbin/rtprio/rtprio.1200
-rw-r--r--usr.sbin/rtprio/rtprio.c153
-rw-r--r--usr.sbin/rtsold/Makefile24
-rw-r--r--usr.sbin/rtsold/Makefile.depend19
-rw-r--r--usr.sbin/rtsold/dump.c183
-rw-r--r--usr.sbin/rtsold/if.c430
-rw-r--r--usr.sbin/rtsold/probe.c189
-rw-r--r--usr.sbin/rtsold/rtsock.c175
-rw-r--r--usr.sbin/rtsold/rtsol.c955
-rw-r--r--usr.sbin/rtsold/rtsold.8304
-rw-r--r--usr.sbin/rtsold/rtsold.c924
-rw-r--r--usr.sbin/rtsold/rtsold.h194
-rw-r--r--usr.sbin/rwhod/Makefile9
-rw-r--r--usr.sbin/rwhod/Makefile.depend20
-rw-r--r--usr.sbin/rwhod/rwhod.8239
-rw-r--r--usr.sbin/rwhod/rwhod.c781
-rw-r--r--usr.sbin/sa/Makefile15
-rw-r--r--usr.sbin/sa/Makefile.depend18
-rw-r--r--usr.sbin/sa/db.c207
-rw-r--r--usr.sbin/sa/extern.h110
-rw-r--r--usr.sbin/sa/main.c533
-rw-r--r--usr.sbin/sa/pathnames.h35
-rw-r--r--usr.sbin/sa/pdb.c374
-rw-r--r--usr.sbin/sa/sa.8262
-rw-r--r--usr.sbin/sa/tests/Makefile31
-rw-r--r--usr.sbin/sa/tests/legacy_test.sh78
-rwxr-xr-xusr.sbin/sa/tests/prime.sh37
-rw-r--r--usr.sbin/sa/tests/v1-amd64-sav.inbin0 -> 8192 bytes
-rw-r--r--usr.sbin/sa/tests/v1-amd64-sav.out5
-rw-r--r--usr.sbin/sa/tests/v1-amd64-u.out28
-rw-r--r--usr.sbin/sa/tests/v1-amd64-usr.inbin0 -> 8192 bytes
-rw-r--r--usr.sbin/sa/tests/v1-amd64-usr.out1
-rw-r--r--usr.sbin/sa/tests/v1-i386-sav.inbin0 -> 8192 bytes
-rw-r--r--usr.sbin/sa/tests/v1-i386-sav.out5
-rw-r--r--usr.sbin/sa/tests/v1-i386-u.out28
-rw-r--r--usr.sbin/sa/tests/v1-i386-usr.inbin0 -> 8192 bytes
-rw-r--r--usr.sbin/sa/tests/v1-i386-usr.out1
-rw-r--r--usr.sbin/sa/tests/v1-sparc64-sav.inbin0 -> 16384 bytes
-rw-r--r--usr.sbin/sa/tests/v1-sparc64-sav.out5
-rw-r--r--usr.sbin/sa/tests/v1-sparc64-u.out28
-rw-r--r--usr.sbin/sa/tests/v1-sparc64-usr.inbin0 -> 16384 bytes
-rw-r--r--usr.sbin/sa/tests/v1-sparc64-usr.out1
-rw-r--r--usr.sbin/sa/tests/v2-amd64-sav.inbin0 -> 8192 bytes
-rw-r--r--usr.sbin/sa/tests/v2-amd64-u.out28
-rw-r--r--usr.sbin/sa/tests/v2-amd64-usr.inbin0 -> 8192 bytes
-rw-r--r--usr.sbin/sa/tests/v2-i386-sav.inbin0 -> 8192 bytes
-rw-r--r--usr.sbin/sa/tests/v2-i386-u.out28
-rw-r--r--usr.sbin/sa/tests/v2-i386-usr.inbin0 -> 8192 bytes
-rw-r--r--usr.sbin/sa/tests/v2-sparc64-sav.inbin0 -> 16384 bytes
-rw-r--r--usr.sbin/sa/tests/v2-sparc64-u.out36
-rw-r--r--usr.sbin/sa/tests/v2-sparc64-usr.inbin0 -> 16384 bytes
-rw-r--r--usr.sbin/sa/usrdb.c239
-rw-r--r--usr.sbin/sendmail/Makefile68
-rw-r--r--usr.sbin/sendmail/Makefile.depend106
-rw-r--r--usr.sbin/service/Makefile7
-rw-r--r--usr.sbin/service/Makefile.depend11
-rw-r--r--usr.sbin/service/service.8135
-rwxr-xr-xusr.sbin/service/service.sh155
-rw-r--r--usr.sbin/services_mkdb/Makefile9
-rw-r--r--usr.sbin/services_mkdb/Makefile.depend19
-rw-r--r--usr.sbin/services_mkdb/extern.h34
-rw-r--r--usr.sbin/services_mkdb/services_mkdb.8111
-rw-r--r--usr.sbin/services_mkdb/services_mkdb.c456
-rw-r--r--usr.sbin/services_mkdb/uniq.c158
-rw-r--r--usr.sbin/sesutil/Makefile9
-rw-r--r--usr.sbin/sesutil/Makefile.depend19
-rw-r--r--usr.sbin/sesutil/eltsub.c235
-rw-r--r--usr.sbin/sesutil/eltsub.h37
-rw-r--r--usr.sbin/sesutil/sesutil.8130
-rw-r--r--usr.sbin/sesutil/sesutil.c560
-rw-r--r--usr.sbin/setfib/Makefile6
-rw-r--r--usr.sbin/setfib/Makefile.depend18
-rw-r--r--usr.sbin/setfib/setfib.198
-rw-r--r--usr.sbin/setfib/setfib.c105
-rw-r--r--usr.sbin/setfmac/Makefile7
-rw-r--r--usr.sbin/setfmac/Makefile.depend18
-rw-r--r--usr.sbin/setfmac/setfmac.866
-rw-r--r--usr.sbin/setfmac/setfmac.c498
-rw-r--r--usr.sbin/setfmac/setfsmac.8129
-rw-r--r--usr.sbin/setpmac/Makefile6
-rw-r--r--usr.sbin/setpmac/Makefile.depend18
-rw-r--r--usr.sbin/setpmac/setpmac.865
-rw-r--r--usr.sbin/setpmac/setpmac.c92
-rw-r--r--usr.sbin/sicontrol/Makefile9
-rw-r--r--usr.sbin/sicontrol/Makefile.depend18
-rw-r--r--usr.sbin/sicontrol/sicontrol.8109
-rw-r--r--usr.sbin/sicontrol/sicontrol.c726
-rw-r--r--usr.sbin/smbmsg/Makefile7
-rw-r--r--usr.sbin/smbmsg/Makefile.depend18
-rw-r--r--usr.sbin/smbmsg/pathnames.h29
-rw-r--r--usr.sbin/smbmsg/smbmsg.8286
-rw-r--r--usr.sbin/smbmsg/smbmsg.c347
-rw-r--r--usr.sbin/snapinfo/Makefile9
-rw-r--r--usr.sbin/snapinfo/Makefile.depend19
-rw-r--r--usr.sbin/snapinfo/snapinfo.866
-rw-r--r--usr.sbin/snapinfo/snapinfo.c181
-rw-r--r--usr.sbin/spkrtest/Makefile6
-rw-r--r--usr.sbin/spkrtest/Makefile.depend11
-rw-r--r--usr.sbin/spkrtest/spkrtest.848
-rw-r--r--usr.sbin/spkrtest/spkrtest.sh113
-rw-r--r--usr.sbin/spray/Makefile8
-rw-r--r--usr.sbin/spray/Makefile.depend21
-rw-r--r--usr.sbin/spray/spray.876
-rw-r--r--usr.sbin/spray/spray.c220
-rw-r--r--usr.sbin/syslogd/Makefile22
-rw-r--r--usr.sbin/syslogd/Makefile.depend20
-rw-r--r--usr.sbin/syslogd/pathnames.h35
-rw-r--r--usr.sbin/syslogd/syslog.conf.5525
-rw-r--r--usr.sbin/syslogd/syslogd.8427
-rw-r--r--usr.sbin/syslogd/syslogd.c2851
-rw-r--r--usr.sbin/sysrc/Makefile7
-rw-r--r--usr.sbin/sysrc/Makefile.depend11
-rw-r--r--usr.sbin/sysrc/sysrc929
-rw-r--r--usr.sbin/sysrc/sysrc.8477
-rw-r--r--usr.sbin/tcpdchk/Makefile22
-rw-r--r--usr.sbin/tcpdchk/Makefile.depend20
-rw-r--r--usr.sbin/tcpdmatch/Makefile21
-rw-r--r--usr.sbin/tcpdmatch/Makefile.depend20
-rw-r--r--usr.sbin/tcpdrop/Makefile7
-rw-r--r--usr.sbin/tcpdrop/Makefile.depend18
-rw-r--r--usr.sbin/tcpdrop/tcpdrop.897
-rw-r--r--usr.sbin/tcpdrop/tcpdrop.c355
-rw-r--r--usr.sbin/tcpdump/Makefile6
-rw-r--r--usr.sbin/tcpdump/Makefile.inc6
-rw-r--r--usr.sbin/tcpdump/tcpdump/Makefile200
-rw-r--r--usr.sbin/tcpdump/tcpdump/Makefile.depend27
-rw-r--r--usr.sbin/tcpdump/tcpdump/config.h406
-rw-r--r--usr.sbin/tcpdump/tcpdump/tcpdump.11977
-rw-r--r--usr.sbin/tests/Makefile8
-rw-r--r--usr.sbin/timed/Makefile6
-rw-r--r--usr.sbin/timed/timed/CHANGES145
-rw-r--r--usr.sbin/timed/timed/Makefile16
-rw-r--r--usr.sbin/timed/timed/Makefile.depend22
-rw-r--r--usr.sbin/timed/timed/acksend.c125
-rw-r--r--usr.sbin/timed/timed/byteorder.c80
-rw-r--r--usr.sbin/timed/timed/candidate.c162
-rw-r--r--usr.sbin/timed/timed/cksum.c81
-rw-r--r--usr.sbin/timed/timed/correct.c190
-rw-r--r--usr.sbin/timed/timed/extern.h87
-rw-r--r--usr.sbin/timed/timed/globals.h169
-rw-r--r--usr.sbin/timed/timed/master.c839
-rw-r--r--usr.sbin/timed/timed/measure.c336
-rw-r--r--usr.sbin/timed/timed/networkdelta.c260
-rw-r--r--usr.sbin/timed/timed/pathnames.h37
-rw-r--r--usr.sbin/timed/timed/readmsg.c502
-rw-r--r--usr.sbin/timed/timed/slave.c690
-rw-r--r--usr.sbin/timed/timed/timed.8288
-rw-r--r--usr.sbin/timed/timed/timed.c833
-rw-r--r--usr.sbin/timed/timedc/Makefile15
-rw-r--r--usr.sbin/timed/timedc/Makefile.depend20
-rw-r--r--usr.sbin/timed/timedc/cmds.c542
-rw-r--r--usr.sbin/timed/timedc/cmdtab.c57
-rw-r--r--usr.sbin/timed/timedc/extern.h50
-rw-r--r--usr.sbin/timed/timedc/timedc.8141
-rw-r--r--usr.sbin/timed/timedc/timedc.c252
-rw-r--r--usr.sbin/timed/timedc/timedc.h59
-rw-r--r--usr.sbin/traceroute/Makefile41
-rw-r--r--usr.sbin/traceroute/Makefile.depend22
-rw-r--r--usr.sbin/traceroute/findsaddr-udp.c94
-rw-r--r--usr.sbin/traceroute6/Makefile32
-rw-r--r--usr.sbin/traceroute6/Makefile.depend20
-rw-r--r--usr.sbin/traceroute6/traceroute6.8185
-rw-r--r--usr.sbin/traceroute6/traceroute6.c1455
-rw-r--r--usr.sbin/trpt/Makefile17
-rw-r--r--usr.sbin/trpt/Makefile.depend19
-rw-r--r--usr.sbin/trpt/trpt.8149
-rw-r--r--usr.sbin/trpt/trpt.c465
-rw-r--r--usr.sbin/tzsetup/Makefile12
-rw-r--r--usr.sbin/tzsetup/Makefile.depend21
-rw-r--r--usr.sbin/tzsetup/tzsetup.8167
-rw-r--r--usr.sbin/tzsetup/tzsetup.c1065
-rw-r--r--usr.sbin/uathload/Makefile30
-rw-r--r--usr.sbin/uathload/Makefile.depend18
-rw-r--r--usr.sbin/uathload/uathload.869
-rw-r--r--usr.sbin/uathload/uathload.c233
-rw-r--r--usr.sbin/uefisign/Makefile11
-rw-r--r--usr.sbin/uefisign/Makefile.depend19
-rw-r--r--usr.sbin/uefisign/child.c277
-rw-r--r--usr.sbin/uefisign/magic.h66
-rw-r--r--usr.sbin/uefisign/pe.c564
-rw-r--r--usr.sbin/uefisign/uefisign.893
-rw-r--r--usr.sbin/uefisign/uefisign.c425
-rw-r--r--usr.sbin/uefisign/uefisign.h91
-rw-r--r--usr.sbin/ugidfw/Makefile8
-rw-r--r--usr.sbin/ugidfw/Makefile.depend19
-rw-r--r--usr.sbin/ugidfw/ugidfw.8360
-rw-r--r--usr.sbin/ugidfw/ugidfw.c214
-rw-r--r--usr.sbin/uhsoctl/Makefile9
-rw-r--r--usr.sbin/uhsoctl/Makefile.depend20
-rw-r--r--usr.sbin/uhsoctl/uhsoctl.1107
-rw-r--r--usr.sbin/uhsoctl/uhsoctl.c1561
-rw-r--r--usr.sbin/unbound/Makefile7
-rw-r--r--usr.sbin/unbound/Makefile.inc5
-rw-r--r--usr.sbin/unbound/anchor/Makefile16
-rw-r--r--usr.sbin/unbound/anchor/Makefile.depend24
-rw-r--r--usr.sbin/unbound/checkconf/Makefile15
-rw-r--r--usr.sbin/unbound/checkconf/Makefile.depend23
-rw-r--r--usr.sbin/unbound/control/Makefile16
-rw-r--r--usr.sbin/unbound/control/Makefile.depend23
-rw-r--r--usr.sbin/unbound/daemon/Makefile15
-rw-r--r--usr.sbin/unbound/daemon/Makefile.depend24
-rw-r--r--usr.sbin/unbound/local-setup/Makefile6
-rw-r--r--usr.sbin/unbound/local-setup/Makefile.depend11
-rwxr-xr-xusr.sbin/unbound/local-setup/local-unbound-setup.sh462
-rw-r--r--usr.sbin/usbconfig/Makefile9
-rw-r--r--usr.sbin/usbconfig/Makefile.depend20
-rw-r--r--usr.sbin/usbconfig/dump.c483
-rw-r--r--usr.sbin/usbconfig/dump.h40
-rw-r--r--usr.sbin/usbconfig/usbconfig.8100
-rw-r--r--usr.sbin/usbconfig/usbconfig.c823
-rw-r--r--usr.sbin/usbdump/Makefile8
-rw-r--r--usr.sbin/usbdump/Makefile.depend18
-rw-r--r--usr.sbin/usbdump/usbdump.8157
-rw-r--r--usr.sbin/usbdump/usbdump.c986
-rw-r--r--usr.sbin/utx/Makefile6
-rw-r--r--usr.sbin/utx/Makefile.depend18
-rw-r--r--usr.sbin/utx/utx.897
-rw-r--r--usr.sbin/utx/utx.c110
-rw-r--r--usr.sbin/vidcontrol/Makefile6
-rw-r--r--usr.sbin/vidcontrol/Makefile.depend18
-rw-r--r--usr.sbin/vidcontrol/decode.c99
-rw-r--r--usr.sbin/vidcontrol/decode.h3
-rw-r--r--usr.sbin/vidcontrol/path.h8
-rw-r--r--usr.sbin/vidcontrol/vidcontrol.1580
-rw-r--r--usr.sbin/vidcontrol/vidcontrol.c1484
-rw-r--r--usr.sbin/vigr/Makefile7
-rw-r--r--usr.sbin/vigr/Makefile.depend11
-rw-r--r--usr.sbin/vigr/vigr.871
-rw-r--r--usr.sbin/vigr/vigr.sh95
-rw-r--r--usr.sbin/vipw/Makefile9
-rw-r--r--usr.sbin/vipw/Makefile.depend19
-rw-r--r--usr.sbin/vipw/vipw.8122
-rw-r--r--usr.sbin/vipw/vipw.c137
-rw-r--r--usr.sbin/wake/Makefile8
-rw-r--r--usr.sbin/wake/Makefile.depend18
-rw-r--r--usr.sbin/wake/wake.868
-rw-r--r--usr.sbin/wake/wake.c216
-rw-r--r--usr.sbin/watch/Makefile10
-rw-r--r--usr.sbin/watch/Makefile.depend19
-rw-r--r--usr.sbin/watch/watch.8120
-rw-r--r--usr.sbin/watch/watch.c441
-rw-r--r--usr.sbin/watchdogd/Makefile12
-rw-r--r--usr.sbin/watchdogd/Makefile.depend20
-rw-r--r--usr.sbin/watchdogd/watchdog.873
-rw-r--r--usr.sbin/watchdogd/watchdogd.8325
-rw-r--r--usr.sbin/watchdogd/watchdogd.c792
-rw-r--r--usr.sbin/wlandebug/Makefile8
-rw-r--r--usr.sbin/wlandebug/Makefile.depend18
-rw-r--r--usr.sbin/wlandebug/wlandebug.8177
-rw-r--r--usr.sbin/wlandebug/wlandebug.c243
-rw-r--r--usr.sbin/wlconfig/Makefile9
-rw-r--r--usr.sbin/wlconfig/Makefile.depend16
-rw-r--r--usr.sbin/wlconfig/wlconfig.8143
-rw-r--r--usr.sbin/wlconfig/wlconfig.c417
-rw-r--r--usr.sbin/wpa/Makefile8
-rw-r--r--usr.sbin/wpa/Makefile.crypto134
-rw-r--r--usr.sbin/wpa/Makefile.inc38
-rw-r--r--usr.sbin/wpa/hostapd/Makefile122
-rw-r--r--usr.sbin/wpa/hostapd/Makefile.depend23
-rw-r--r--usr.sbin/wpa/hostapd/hostapd.8137
-rw-r--r--usr.sbin/wpa/hostapd/hostapd.conf.5211
-rw-r--r--usr.sbin/wpa/hostapd_cli/Makefile17
-rw-r--r--usr.sbin/wpa/hostapd_cli/Makefile.depend20
-rw-r--r--usr.sbin/wpa/hostapd_cli/hostapd_cli.8112
-rw-r--r--usr.sbin/wpa/ndis_events/Makefile8
-rw-r--r--usr.sbin/wpa/ndis_events/Makefile.depend19
-rw-r--r--usr.sbin/wpa/ndis_events/ndis_events.8135
-rw-r--r--usr.sbin/wpa/ndis_events/ndis_events.c351
-rw-r--r--usr.sbin/wpa/wpa_cli/Makefile20
-rw-r--r--usr.sbin/wpa/wpa_cli/Makefile.depend20
-rw-r--r--usr.sbin/wpa/wpa_cli/wpa_cli.8222
-rw-r--r--usr.sbin/wpa/wpa_passphrase/Makefile17
-rw-r--r--usr.sbin/wpa/wpa_passphrase/Makefile.depend20
-rw-r--r--usr.sbin/wpa/wpa_passphrase/wpa_passphrase.865
-rw-r--r--usr.sbin/wpa/wpa_priv/Makefile16
-rw-r--r--usr.sbin/wpa/wpa_supplicant/Makefile147
-rw-r--r--usr.sbin/wpa/wpa_supplicant/Makefile.depend23
-rw-r--r--usr.sbin/wpa/wpa_supplicant/Packet32.c413
-rw-r--r--usr.sbin/wpa/wpa_supplicant/Packet32.h67
-rw-r--r--usr.sbin/wpa/wpa_supplicant/ntddndis.h31
-rw-r--r--usr.sbin/wpa/wpa_supplicant/wpa_supplicant.8184
-rw-r--r--usr.sbin/wpa/wpa_supplicant/wpa_supplicant.conf.5578
-rw-r--r--usr.sbin/yp_mkdb/Makefile14
-rw-r--r--usr.sbin/yp_mkdb/Makefile.depend20
-rw-r--r--usr.sbin/yp_mkdb/yp_mkdb.8209
-rw-r--r--usr.sbin/yp_mkdb/yp_mkdb.c340
-rw-r--r--usr.sbin/ypbind/Makefile12
-rw-r--r--usr.sbin/ypbind/Makefile.depend21
-rw-r--r--usr.sbin/ypbind/yp_ping.c308
-rw-r--r--usr.sbin/ypbind/yp_ping.h5
-rw-r--r--usr.sbin/ypbind/ypbind.8202
-rw-r--r--usr.sbin/ypbind/ypbind.c1010
-rw-r--r--usr.sbin/ypldap/Makefile20
-rw-r--r--usr.sbin/ypldap/aldap.c1272
-rw-r--r--usr.sbin/ypldap/aldap.h221
-rw-r--r--usr.sbin/ypldap/ber.c1268
-rw-r--r--usr.sbin/ypldap/ber.h129
-rw-r--r--usr.sbin/ypldap/entries.c149
-rw-r--r--usr.sbin/ypldap/ldapclient.c705
-rw-r--r--usr.sbin/ypldap/log.c162
-rw-r--r--usr.sbin/ypldap/parse.y838
-rw-r--r--usr.sbin/ypldap/yp.c652
-rw-r--r--usr.sbin/ypldap/ypldap.882
-rw-r--r--usr.sbin/ypldap/ypldap.c651
-rw-r--r--usr.sbin/ypldap/ypldap.conf.5167
-rw-r--r--usr.sbin/ypldap/ypldap.h222
-rw-r--r--usr.sbin/ypldap/ypldap_dns.c254
-rw-r--r--usr.sbin/yppoll/Makefile9
-rw-r--r--usr.sbin/yppoll/Makefile.depend21
-rw-r--r--usr.sbin/yppoll/yppoll.878
-rw-r--r--usr.sbin/yppoll/yppoll.c173
-rw-r--r--usr.sbin/yppush/Makefile30
-rw-r--r--usr.sbin/yppush/Makefile.depend28
-rw-r--r--usr.sbin/yppush/yppush.8180
-rw-r--r--usr.sbin/yppush/yppush_extern.h44
-rw-r--r--usr.sbin/yppush/yppush_main.c620
-rw-r--r--usr.sbin/ypserv/Makefile45
-rw-r--r--usr.sbin/ypserv/Makefile.depend34
-rw-r--r--usr.sbin/ypserv/Makefile.yp695
-rw-r--r--usr.sbin/ypserv/common/yplib_host.c356
-rw-r--r--usr.sbin/ypserv/common/yplib_host.h53
-rw-r--r--usr.sbin/ypserv/yp_access.c334
-rw-r--r--usr.sbin/ypserv/yp_dblookup.c733
-rw-r--r--usr.sbin/ypserv/yp_dnslookup.c551
-rw-r--r--usr.sbin/ypserv/yp_error.c73
-rw-r--r--usr.sbin/ypserv/yp_extern.h115
-rw-r--r--usr.sbin/ypserv/yp_main.c579
-rw-r--r--usr.sbin/ypserv/yp_server.c985
-rw-r--r--usr.sbin/ypserv/yp_svc_udp.c71
-rw-r--r--usr.sbin/ypserv/ypinit.8200
-rw-r--r--usr.sbin/ypserv/ypinit.sh387
-rw-r--r--usr.sbin/ypserv/ypserv.8463
-rw-r--r--usr.sbin/ypset/Makefile9
-rw-r--r--usr.sbin/ypset/Makefile.depend21
-rw-r--r--usr.sbin/ypset/ypset.888
-rw-r--r--usr.sbin/ypset/ypset.c149
-rw-r--r--usr.sbin/zic/Makefile7
-rw-r--r--usr.sbin/zic/Makefile.inc3
-rw-r--r--usr.sbin/zic/README88
-rw-r--r--usr.sbin/zic/zdump/Makefile15
-rw-r--r--usr.sbin/zic/zdump/Makefile.depend18
-rw-r--r--usr.sbin/zic/zic/Makefile16
-rw-r--r--usr.sbin/zic/zic/Makefile.depend18
-rw-r--r--usr.sbin/zzz/Makefile6
-rw-r--r--usr.sbin/zzz/Makefile.depend11
-rw-r--r--usr.sbin/zzz/zzz.865
-rw-r--r--usr.sbin/zzz/zzz.sh43
2482 files changed, 580302 insertions, 0 deletions
diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile
new file mode 100644
index 0000000..16feb68
--- /dev/null
+++ b/usr.sbin/Makefile
@@ -0,0 +1,216 @@
+# From: @(#)Makefile 5.20 (Berkeley) 6/12/93
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+SUBDIR= adduser \
+ arp \
+ binmiscctl \
+ bsdconfig \
+ camdd \
+ cdcontrol \
+ chkgrp \
+ chown \
+ chroot \
+ ckdist \
+ clear_locks \
+ crashinfo \
+ cron \
+ ctladm \
+ ctld \
+ daemon \
+ dconschat \
+ devctl \
+ devinfo \
+ digictl \
+ diskinfo \
+ dumpcis \
+ extattr \
+ extattrctl \
+ fifolog \
+ fstyp \
+ fwcontrol \
+ getfmac \
+ getpmac \
+ gstat \
+ i2c \
+ ifmcstat \
+ iostat \
+ iovctl \
+ kldxref \
+ mailwrapper \
+ makefs \
+ memcontrol \
+ mergemaster \
+ mfiutil \
+ mixer \
+ mlxcontrol \
+ mountd \
+ mount_smbfs \
+ mpsutil \
+ mptutil \
+ mtest \
+ newsyslog \
+ nfscbd \
+ nfsd \
+ nfsdumpstate \
+ nfsrevoke \
+ nfsuserd \
+ nmtree \
+ nologin \
+ pciconf \
+ periodic \
+ powerd \
+ procctl \
+ pstat \
+ pw \
+ pwd_mkdb \
+ quot \
+ rarpd \
+ rmt \
+ rpcbind \
+ rpc.lockd \
+ rpc.statd \
+ rpc.umntall \
+ rtprio \
+ service \
+ services_mkdb \
+ sesutil \
+ setfib \
+ setfmac \
+ setpmac \
+ smbmsg \
+ snapinfo \
+ spray \
+ syslogd \
+ sysrc \
+ tcpdrop \
+ tcpdump \
+ traceroute \
+ trpt \
+ tzsetup \
+ uefisign \
+ ugidfw \
+ vigr \
+ vipw \
+ wake \
+ watch \
+ watchdogd \
+ zic
+
+# NB: keep these sorted by MK_* knobs
+
+SUBDIR.${MK_ACCT}+= accton
+SUBDIR.${MK_ACCT}+= sa
+SUBDIR.${MK_AMD}+= amd
+SUBDIR.${MK_AUDIT}+= audit
+SUBDIR.${MK_AUDIT}+= auditd
+.if ${MK_OPENSSL} != "no"
+SUBDIR.${MK_AUDIT}+= auditdistd
+.endif
+SUBDIR.${MK_AUDIT}+= auditreduce
+SUBDIR.${MK_AUDIT}+= praudit
+SUBDIR.${MK_AUTHPF}+= authpf
+SUBDIR.${MK_AUTOFS}+= autofs
+SUBDIR.${MK_BLUETOOTH}+= bluetooth
+SUBDIR.${MK_BOOTPARAMD}+= bootparamd
+SUBDIR.${MK_BSDINSTALL}+= bsdinstall
+SUBDIR.${MK_BSNMP}+= bsnmpd
+SUBDIR.${MK_CTM}+= ctm
+SUBDIR.${MK_FLOPPY}+= fdcontrol
+SUBDIR.${MK_FLOPPY}+= fdformat
+SUBDIR.${MK_FLOPPY}+= fdread
+SUBDIR.${MK_FLOPPY}+= fdwrite
+SUBDIR.${MK_FMTREE}+= fmtree
+SUBDIR.${MK_FREEBSD_UPDATE}+= freebsd-update
+SUBDIR.${MK_GSSAPI}+= gssd
+SUBDIR.${MK_GPIO}+= gpioctl
+SUBDIR.${MK_INET6}+= ip6addrctl
+SUBDIR.${MK_INET6}+= mld6query
+SUBDIR.${MK_INET6}+= ndp
+SUBDIR.${MK_INET6}+= rip6query
+SUBDIR.${MK_INET6}+= route6d
+SUBDIR.${MK_INET6}+= rrenumd
+SUBDIR.${MK_INET6}+= rtadvctl
+SUBDIR.${MK_INET6}+= rtadvd
+SUBDIR.${MK_INET6}+= rtsold
+SUBDIR.${MK_INET6}+= traceroute6
+SUBDIR.${MK_INETD}+= inetd
+SUBDIR.${MK_IPFW}+= ipfwpcap
+SUBDIR.${MK_ISCSI}+= iscsid
+SUBDIR.${MK_JAIL}+= jail
+SUBDIR.${MK_JAIL}+= jexec
+SUBDIR.${MK_JAIL}+= jls
+# XXX MK_SYSCONS
+SUBDIR.${MK_LEGACY_CONSOLE}+= kbdcontrol
+SUBDIR.${MK_LEGACY_CONSOLE}+= kbdmap
+SUBDIR.${MK_LEGACY_CONSOLE}+= moused
+SUBDIR.${MK_LEGACY_CONSOLE}+= vidcontrol
+.if ${MK_LIBTHR} != "no" || ${MK_LIBPTHREAD} != "no"
+SUBDIR.${MK_PPP}+= pppctl
+SUBDIR.${MK_NS_CACHING}+= nscd
+.endif
+SUBDIR.${MK_LPR}+= lpr
+SUBDIR.${MK_MAN_UTILS}+= manctl
+SUBDIR.${MK_NAND}+= nandsim
+SUBDIR.${MK_NAND}+= nandtool
+SUBDIR.${MK_NETGRAPH}+= flowctl
+SUBDIR.${MK_NETGRAPH}+= lmcconfig
+SUBDIR.${MK_NETGRAPH}+= ngctl
+SUBDIR.${MK_NETGRAPH}+= nghook
+SUBDIR.${MK_NIS}+= rpc.yppasswdd
+SUBDIR.${MK_NIS}+= rpc.ypupdated
+SUBDIR.${MK_NIS}+= rpc.ypxfrd
+SUBDIR.${MK_NIS}+= ypbind
+SUBDIR.${MK_NIS}+= ypldap
+SUBDIR.${MK_NIS}+= yp_mkdb
+SUBDIR.${MK_NIS}+= yppoll
+SUBDIR.${MK_NIS}+= yppush
+SUBDIR.${MK_NIS}+= ypserv
+SUBDIR.${MK_NIS}+= ypset
+SUBDIR.${MK_NTP}+= ntp
+SUBDIR.${MK_OPENSSL}+= keyserv
+SUBDIR.${MK_PC_SYSINSTALL}+= pc-sysinstall
+SUBDIR.${MK_PF}+= ftp-proxy
+SUBDIR.${MK_PKGBOOTSTRAP}+= pkg
+SUBDIR.${MK_PMC}+= pmcannotate
+SUBDIR.${MK_PMC}+= pmccontrol
+SUBDIR.${MK_PMC}+= pmcstat
+SUBDIR.${MK_PORTSNAP}+= portsnap
+SUBDIR.${MK_PPP}+= ppp
+SUBDIR.${MK_QUOTAS}+= edquota
+SUBDIR.${MK_QUOTAS}+= quotaon
+SUBDIR.${MK_QUOTAS}+= repquota
+SUBDIR.${MK_RCMDS}+= rwhod
+SUBDIR.${MK_RCS}+= etcupdate
+SUBDIR.${MK_SENDMAIL}+= editmap
+SUBDIR.${MK_SENDMAIL}+= mailstats
+SUBDIR.${MK_SENDMAIL}+= makemap
+SUBDIR.${MK_SENDMAIL}+= praliases
+SUBDIR.${MK_SENDMAIL}+= sendmail
+SUBDIR.${MK_TCP_WRAPPERS}+= tcpdchk
+SUBDIR.${MK_TCP_WRAPPERS}+= tcpdmatch
+SUBDIR.${MK_TIMED}+= timed
+SUBDIR.${MK_TOOLCHAIN}+= config
+SUBDIR.${MK_TOOLCHAIN}+= crunch
+SUBDIR.${MK_UNBOUND}+= unbound
+SUBDIR.${MK_USB}+= uathload
+SUBDIR.${MK_USB}+= uhsoctl
+SUBDIR.${MK_USB}+= usbconfig
+SUBDIR.${MK_USB}+= usbdump
+SUBDIR.${MK_UTMPX}+= ac
+SUBDIR.${MK_UTMPX}+= lastlogin
+SUBDIR.${MK_UTMPX}+= utx
+SUBDIR.${MK_WIRELESS}+= ancontrol
+SUBDIR.${MK_WIRELESS}+= wlandebug
+SUBDIR.${MK_WIRELESS}+= wpa
+
+SUBDIR.${MK_TESTS}+= tests
+
+.include <bsd.arch.inc.mk>
+
+SUBDIR:= ${SUBDIR:O}
+
+SUBDIR_PARALLEL=
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/Makefile.amd64 b/usr.sbin/Makefile.amd64
new file mode 100644
index 0000000..0fdccaf
--- /dev/null
+++ b/usr.sbin/Makefile.amd64
@@ -0,0 +1,34 @@
+# $FreeBSD$
+
+# kgzip: builds, but missing support files
+# mptable: broken (not 64 bit clean)
+# pnpinfo: crashes (not really useful anyway)
+.if ${MK_ACPI} != "no"
+SUBDIR+= acpi
+.endif
+.if ${MK_APM} != "no"
+SUBDIR+= apm
+.endif
+SUBDIR+= asf
+.if ${MK_BHYVE} != "no"
+SUBDIR+= bhyve
+SUBDIR+= bhyvectl
+SUBDIR+= bhyveload
+.endif
+SUBDIR+= boot0cfg
+.if ${MK_TOOLCHAIN} != "no"
+SUBDIR+= btxld
+.endif
+SUBDIR+= cpucontrol
+.if ${MK_HYPERV} != "no"
+SUBDIR+= hyperv
+.endif
+SUBDIR+= kgmon
+SUBDIR+= lptcontrol
+SUBDIR+= mptable
+.if ${MK_NDIS} != "no"
+SUBDIR+= ndiscvt
+.endif
+SUBDIR+= sicontrol
+SUBDIR+= spkrtest
+SUBDIR+= zzz
diff --git a/usr.sbin/Makefile.arm b/usr.sbin/Makefile.arm
new file mode 100644
index 0000000..84cff4a
--- /dev/null
+++ b/usr.sbin/Makefile.arm
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+SUBDIR+= kgmon
+SUBDIR+= ofwdump
diff --git a/usr.sbin/Makefile.arm64 b/usr.sbin/Makefile.arm64
new file mode 100644
index 0000000..c2d5f36
--- /dev/null
+++ b/usr.sbin/Makefile.arm64
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+.if ${MK_ACPI} != "no"
+SUBDIR+= acpi
+.endif
+SUBDIR+= ofwdump
diff --git a/usr.sbin/Makefile.i386 b/usr.sbin/Makefile.i386
new file mode 100644
index 0000000..6c99daf
--- /dev/null
+++ b/usr.sbin/Makefile.i386
@@ -0,0 +1,38 @@
+# $FreeBSD$
+
+.if ${MK_APM} != "no"
+SUBDIR+= apm
+SUBDIR+= apmd
+.endif
+SUBDIR+= asf
+.if ${MK_TOOLCHAIN} != "no"
+SUBDIR+= btxld
+.endif
+SUBDIR+= cpucontrol
+SUBDIR+= kgmon
+SUBDIR+= kgzip
+SUBDIR+= lptcontrol
+SUBDIR+= mptable
+.if ${MK_NDIS} != "no"
+SUBDIR+= ndiscvt
+.endif
+SUBDIR+= pnpinfo
+SUBDIR+= sicontrol
+SUBDIR+= spkrtest
+SUBDIR+= zzz
+
+# Differentiate between FreeBSD/i386 and FreeBSD/pc98
+.if ${MACHINE} == "i386"
+.if ${MK_ACPI} != "no"
+SUBDIR+= acpi
+.endif
+SUBDIR+= boot0cfg
+.if ${MK_HYPERV} != "no"
+SUBDIR+= hyperv
+.endif
+.if ${MK_WIRELESS} != "no"
+SUBDIR+= wlconfig
+.endif
+.elif ${MACHINE} == "pc98"
+SUBDIR+= boot98cfg
+.endif
diff --git a/usr.sbin/Makefile.inc b/usr.sbin/Makefile.inc
new file mode 100644
index 0000000..bd13613
--- /dev/null
+++ b/usr.sbin/Makefile.inc
@@ -0,0 +1,6 @@
+# @(#)Makefile.inc 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+BINDIR?= /usr/sbin
+
+WARNS?= 6
diff --git a/usr.sbin/Makefile.mips b/usr.sbin/Makefile.mips
new file mode 100644
index 0000000..8987110
--- /dev/null
+++ b/usr.sbin/Makefile.mips
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+SUBDIR+= ofwdump
diff --git a/usr.sbin/Makefile.powerpc b/usr.sbin/Makefile.powerpc
new file mode 100644
index 0000000..131eb57
--- /dev/null
+++ b/usr.sbin/Makefile.powerpc
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+SUBDIR+= nvram
+SUBDIR+= ofwdump
diff --git a/usr.sbin/Makefile.sparc64 b/usr.sbin/Makefile.sparc64
new file mode 100644
index 0000000..81f7a9b
--- /dev/null
+++ b/usr.sbin/Makefile.sparc64
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+SUBDIR+= eeprom
+SUBDIR+= ofwdump
diff --git a/usr.sbin/ac/Makefile b/usr.sbin/ac/Makefile
new file mode 100644
index 0000000..0fac2fb
--- /dev/null
+++ b/usr.sbin/ac/Makefile
@@ -0,0 +1,18 @@
+# $FreeBSD$
+
+PROG= ac
+MAN= ac.8
+
+# If "CONSOLE_TTY" is not defined, this program is compatible with the
+# traditional implementation (using SunOS 4.x as the sample traditional
+# implementation). This is the default.
+#
+# If "CONSOLE_TTY" is defined, it must be defined to the appropriate
+# console name, e.g. "vga". Additionally, the various commented-out
+# sections of the man page should be uncommented. This is not the
+# default because of the inability to detect the proper console name
+# easily, especially on m68k systems, which can share binaries.
+#
+#CFLAGS+=-DCONSOLE_TTY=\"vga\"
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ac/Makefile.depend b/usr.sbin/ac/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/ac/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ac/ac.8 b/usr.sbin/ac/ac.8
new file mode 100644
index 0000000..954d9f7
--- /dev/null
+++ b/usr.sbin/ac/ac.8
@@ -0,0 +1,148 @@
+.\"
+.\" Copyright (c) 1994 Simon J. Gerraty
+.\" 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.
+.\"
+.\" 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 January 21, 2010
+.Dt AC 8
+.Os
+.Sh NAME
+.Nm ac
+.Nd connect time accounting
+.Sh SYNOPSIS
+.Nm
+.Op Fl dp
+.\".Op Fl c Ar console
+.Op Fl t Ar tty
+.Op Fl w Ar wtmp
+.Op Ar users ...
+.Sh DESCRIPTION
+If the file
+.Pa /var/log/utx.log
+exists, a record of individual login and logout
+times are written to it by
+.Xr login 1
+and
+.Xr init 8 ,
+respectively.
+The
+.Nm
+utility
+examines these records and writes the accumulated connect time (in hours)
+for all logins to the standard output.
+.Pp
+The options are as follows:
+.Bl -tag -width indentXXX
+.It Fl d
+Display the connect times in 24 hour chunks.
+.\" .It Fl c Ar console
+.\" Use
+.\" .Ar console
+.\" as the name of the device that local X sessions (ut_host of ":0.0")
+.\" originate from. If any login has been recorded on
+.\" .Ar console
+.\" then these X sessions are ignored unless COMPAT_SUNOS was defined at
+.\" compile time.
+.It Fl p
+Print individual users' totals.
+.It Fl t Ar tty
+Only do accounting logins on certain ttys.
+The
+.Ar tty
+specification can start with '!' to indicate not this
+.Ar tty
+and end with '*' to indicate all similarly named ttys.
+Multiple
+.Fl t
+flags may be specified.
+.It Fl w Ar wtmp
+Read connect time data from
+.Ar wtmp
+instead of the default file,
+.Pa /var/log/utx.log .
+.It Ar users ...
+Display totals for the given individuals only.
+.El
+.Pp
+If no arguments are given,
+.Nm
+displays the total connect time for all
+accounts with login sessions recorded in
+.Pa utx.log .
+.Pp
+The default
+.Pa utx.log
+file will increase without bound unless it is truncated.
+It is normally truncated by the daily scripts run
+by
+.Xr cron 8 ,
+which rename and rotate the
+.Pa utx.log
+files, keeping a week's worth of data on
+hand.
+No login or connect time accounting is performed if
+.Pa /var/log/utx.log
+does not exist.
+.Sh FILES
+.Bl -tag -width /var/log/utx.log -compact
+.It Pa /var/log/utx.log
+connect time accounting file
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+Allow times recorded in
+.Pa modems
+to be charged out at a different rate than
+.Pa other :
+.Bd -literal -offset indent
+ac -p -t "ttyd*" > modems
+ac -p -t "!ttyd*" > other
+.Ed
+.Sh SEE ALSO
+.Xr login 1 ,
+.Xr getutxent 3 ,
+.Xr init 8 ,
+.Xr sa 8
+.\" .Sh NOTES
+.\" If COMPAT_SUNOS is defined
+.\" .Nm ac
+.\" ignores the fact that entries with ut_host of ":0.0" are not real
+.\" login sessions. Normally such entries are ignored except in the case
+.\" of a user being logged in when the
+.\" .Pa wtmp
+.\" file was rotated, in which case a login with ut_host of ":0.0" may
+.\" appear without any preceding console logins.
+.\" If no one is logged in on the console, the user is deemed to have
+.\" logged in on at the earliest time stamp found in
+.\" .Pa wtmp .
+.\" Use of
+.\" .Pa console
+.\" allows
+.\" .Nm ac
+.\" to identify and correctly process a logout for the user. The default
+.\" value for
+.\" .Pa console
+.\" is usually correct at compile time.
diff --git a/usr.sbin/ac/ac.c b/usr.sbin/ac/ac.c
new file mode 100644
index 0000000..f5d4bda
--- /dev/null
+++ b/usr.sbin/ac/ac.c
@@ -0,0 +1,521 @@
+/*-
+ * Copyright (c) 1994 Christopher G. Demetriou
+ * Copyright (c) 1994 Simon J. Gerraty
+ * Copyright (c) 2012 Ed Schouten <ed@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/queue.h>
+#include <sys/time.h>
+
+#include <err.h>
+#include <errno.h>
+#include <langinfo.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <timeconv.h>
+#include <unistd.h>
+#include <utmpx.h>
+
+/*
+ * this is for our list of currently logged in sessions
+ */
+struct utmpx_entry {
+ SLIST_ENTRY(utmpx_entry) next;
+ char user[sizeof(((struct utmpx *)0)->ut_user)];
+ char id[sizeof(((struct utmpx *)0)->ut_id)];
+#ifdef CONSOLE_TTY
+ char line[sizeof(((struct utmpx *)0)->ut_line)];
+#endif
+ struct timeval time;
+};
+
+/*
+ * this is for our list of users that are accumulating time.
+ */
+struct user_entry {
+ SLIST_ENTRY(user_entry) next;
+ char user[sizeof(((struct utmpx *)0)->ut_user)];
+ struct timeval time;
+};
+
+/*
+ * this is for chosing whether to ignore a login
+ */
+struct tty_entry {
+ SLIST_ENTRY(tty_entry) next;
+ char line[sizeof(((struct utmpx *)0)->ut_line) + 2];
+ size_t len;
+ int ret;
+};
+
+/*
+ * globals - yes yuk
+ */
+#ifdef CONSOLE_TTY
+static const char *Console = CONSOLE_TTY;
+#endif
+static struct timeval Total = { 0, 0 };
+static struct timeval FirstTime = { 0, 0 };
+static int Flags = 0;
+static SLIST_HEAD(, utmpx_entry) CurUtmpx = SLIST_HEAD_INITIALIZER(CurUtmpx);
+static SLIST_HEAD(, user_entry) Users = SLIST_HEAD_INITIALIZER(Users);
+static SLIST_HEAD(, tty_entry) Ttys = SLIST_HEAD_INITIALIZER(Ttys);
+
+#define AC_W 1 /* not _PATH_WTMP */
+#define AC_D 2 /* daily totals (ignore -p) */
+#define AC_P 4 /* per-user totals */
+#define AC_U 8 /* specified users only */
+#define AC_T 16 /* specified ttys only */
+
+static void ac(const char *);
+static void usage(void);
+
+static void
+add_tty(const char *line)
+{
+ struct tty_entry *tp;
+ char *rcp;
+
+ Flags |= AC_T;
+
+ if ((tp = malloc(sizeof(*tp))) == NULL)
+ errx(1, "malloc failed");
+ tp->len = 0; /* full match */
+ tp->ret = 1; /* do if match */
+ if (*line == '!') { /* don't do if match */
+ tp->ret = 0;
+ line++;
+ }
+ strlcpy(tp->line, line, sizeof(tp->line));
+ /* Wildcard. */
+ if ((rcp = strchr(tp->line, '*')) != NULL) {
+ *rcp = '\0';
+ /* Match len bytes only. */
+ tp->len = strlen(tp->line);
+ }
+ SLIST_INSERT_HEAD(&Ttys, tp, next);
+}
+
+/*
+ * should we process the named tty?
+ */
+static int
+do_tty(const char *line)
+{
+ struct tty_entry *tp;
+ int def_ret = 0;
+
+ SLIST_FOREACH(tp, &Ttys, next) {
+ if (tp->ret == 0) /* specific don't */
+ def_ret = 1; /* default do */
+ if (tp->len != 0) {
+ if (strncmp(line, tp->line, tp->len) == 0)
+ return tp->ret;
+ } else {
+ if (strncmp(line, tp->line, sizeof(tp->line)) == 0)
+ return tp->ret;
+ }
+ }
+ return (def_ret);
+}
+
+#ifdef CONSOLE_TTY
+/*
+ * is someone logged in on Console?
+ */
+static int
+on_console(void)
+{
+ struct utmpx_entry *up;
+
+ SLIST_FOREACH(up, &CurUtmpx, next)
+ if (strcmp(up->line, Console) == 0)
+ return (1);
+ return (0);
+}
+#endif
+
+/*
+ * Update user's login time.
+ * If no entry for this user is found, a new entry is inserted into the
+ * list alphabetically.
+ */
+static void
+update_user(const char *user, struct timeval secs)
+{
+ struct user_entry *up, *aup;
+ int c;
+
+ aup = NULL;
+ SLIST_FOREACH(up, &Users, next) {
+ c = strcmp(up->user, user);
+ if (c == 0) {
+ timeradd(&up->time, &secs, &up->time);
+ timeradd(&Total, &secs, &Total);
+ return;
+ } else if (c > 0)
+ break;
+ aup = up;
+ }
+ /*
+ * not found so add new user unless specified users only
+ */
+ if (Flags & AC_U)
+ return;
+
+ if ((up = malloc(sizeof(*up))) == NULL)
+ errx(1, "malloc failed");
+ if (aup == NULL)
+ SLIST_INSERT_HEAD(&Users, up, next);
+ else
+ SLIST_INSERT_AFTER(aup, up, next);
+ strlcpy(up->user, user, sizeof(up->user));
+ up->time = secs;
+ timeradd(&Total, &secs, &Total);
+}
+
+int
+main(int argc, char *argv[])
+{
+ const char *wtmpf = NULL;
+ int c;
+
+ (void) setlocale(LC_TIME, "");
+
+ while ((c = getopt(argc, argv, "c:dpt:w:")) != -1) {
+ switch (c) {
+ case 'c':
+#ifdef CONSOLE_TTY
+ Console = optarg;
+#else
+ usage(); /* XXX */
+#endif
+ break;
+ case 'd':
+ Flags |= AC_D;
+ break;
+ case 'p':
+ Flags |= AC_P;
+ break;
+ case 't': /* only do specified ttys */
+ add_tty(optarg);
+ break;
+ case 'w':
+ Flags |= AC_W;
+ wtmpf = optarg;
+ break;
+ case '?':
+ default:
+ usage();
+ break;
+ }
+ }
+ if (optind < argc) {
+ /*
+ * initialize user list
+ */
+ for (; optind < argc; optind++) {
+ update_user(argv[optind], (struct timeval){ 0, 0 });
+ }
+ Flags |= AC_U; /* freeze user list */
+ }
+ if (Flags & AC_D)
+ Flags &= ~AC_P;
+ ac(wtmpf);
+
+ return (0);
+}
+
+/*
+ * print login time in decimal hours
+ */
+static void
+show(const char *user, struct timeval secs)
+{
+ (void)printf("\t%-*s %8.2f\n",
+ (int)sizeof(((struct user_entry *)0)->user), user,
+ (double)secs.tv_sec / 3600);
+}
+
+static void
+show_users(void)
+{
+ struct user_entry *lp;
+
+ SLIST_FOREACH(lp, &Users, next)
+ show(lp->user, lp->time);
+}
+
+/*
+ * print total login time for 24hr period in decimal hours
+ */
+static void
+show_today(struct timeval today)
+{
+ struct user_entry *up;
+ struct utmpx_entry *lp;
+ char date[64];
+ struct timeval diff, total = { 0, 0 }, usec = { 0, 1 }, yesterday;
+ static int d_first = -1;
+
+ if (d_first < 0)
+ d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
+ timersub(&today, &usec, &yesterday);
+ (void)strftime(date, sizeof(date),
+ d_first ? "%e %b total" : "%b %e total",
+ localtime(&yesterday.tv_sec));
+
+ SLIST_FOREACH(lp, &CurUtmpx, next) {
+ timersub(&today, &lp->time, &diff);
+ update_user(lp->user, diff);
+ /* As if they just logged in. */
+ lp->time = today;
+ }
+ SLIST_FOREACH(up, &Users, next) {
+ timeradd(&total, &up->time, &total);
+ /* For next day. */
+ timerclear(&up->time);
+ }
+ if (timerisset(&total))
+ (void)printf("%s %11.2f\n", date, (double)total.tv_sec / 3600);
+}
+
+/*
+ * Log a user out and update their times.
+ * If ut_type is BOOT_TIME or SHUTDOWN_TIME, we log all users out as the
+ * system has been shut down.
+ */
+static void
+log_out(const struct utmpx *up)
+{
+ struct utmpx_entry *lp, *lp2, *tlp;
+ struct timeval secs;
+
+ for (lp = SLIST_FIRST(&CurUtmpx), lp2 = NULL; lp != NULL;)
+ if (up->ut_type == BOOT_TIME || up->ut_type == SHUTDOWN_TIME ||
+ (up->ut_type == DEAD_PROCESS &&
+ memcmp(lp->id, up->ut_id, sizeof(up->ut_id)) == 0)) {
+ timersub(&up->ut_tv, &lp->time, &secs);
+ update_user(lp->user, secs);
+ /*
+ * now lose it
+ */
+ tlp = lp;
+ lp = SLIST_NEXT(lp, next);
+ if (lp2 == NULL)
+ SLIST_REMOVE_HEAD(&CurUtmpx, next);
+ else
+ SLIST_REMOVE_AFTER(lp2, next);
+ free(tlp);
+ } else {
+ lp2 = lp;
+ lp = SLIST_NEXT(lp, next);
+ }
+}
+
+/*
+ * if do_tty says ok, login a user
+ */
+static void
+log_in(struct utmpx *up)
+{
+ struct utmpx_entry *lp;
+
+ /*
+ * this could be a login. if we're not dealing with
+ * the console name, say it is.
+ *
+ * If we are, and if ut_host==":0.0" we know that it
+ * isn't a real login. _But_ if we have not yet recorded
+ * someone being logged in on Console - due to the wtmp
+ * file starting after they logged in, we'll pretend they
+ * logged in, at the start of the wtmp file.
+ */
+
+#ifdef CONSOLE_TTY
+ if (up->ut_host[0] == ':') {
+ /*
+ * SunOS 4.0.2 does not treat ":0.0" as special but we
+ * do.
+ */
+ if (on_console())
+ return;
+ /*
+ * ok, no recorded login, so they were here when wtmp
+ * started! Adjust ut_time!
+ */
+ up->ut_tv = FirstTime;
+ /*
+ * this allows us to pick the right logout
+ */
+ strlcpy(up->ut_line, Console, sizeof(up->ut_line));
+ }
+#endif
+ /*
+ * If we are doing specified ttys only, we ignore
+ * anything else.
+ */
+ if (Flags & AC_T && !do_tty(up->ut_line))
+ return;
+
+ /*
+ * go ahead and log them in
+ */
+ if ((lp = malloc(sizeof(*lp))) == NULL)
+ errx(1, "malloc failed");
+ SLIST_INSERT_HEAD(&CurUtmpx, lp, next);
+ strlcpy(lp->user, up->ut_user, sizeof(lp->user));
+ memcpy(lp->id, up->ut_id, sizeof(lp->id));
+#ifdef CONSOLE_TTY
+ memcpy(lp->line, up->ut_line, sizeof(lp->line));
+#endif
+ lp->time = up->ut_tv;
+}
+
+static void
+ac(const char *file)
+{
+ struct utmpx_entry *lp;
+ struct utmpx *usr, usht;
+ struct tm *ltm;
+ struct timeval prev_secs, ut_timecopy, secs, clock_shift, now;
+ int day, rfound;
+
+ day = -1;
+ timerclear(&prev_secs); /* Minimum acceptable date == 1970. */
+ timerclear(&secs);
+ timerclear(&clock_shift);
+ rfound = 0;
+ if (setutxdb(UTXDB_LOG, file) != 0)
+ err(1, "%s", file);
+ while ((usr = getutxent()) != NULL) {
+ rfound++;
+ ut_timecopy = usr->ut_tv;
+ /* Don't let the time run backwards. */
+ if (timercmp(&ut_timecopy, &prev_secs, <))
+ ut_timecopy = prev_secs;
+ prev_secs = ut_timecopy;
+
+ if (!timerisset(&FirstTime))
+ FirstTime = ut_timecopy;
+ if (Flags & AC_D) {
+ ltm = localtime(&ut_timecopy.tv_sec);
+ if (day >= 0 && day != ltm->tm_yday) {
+ day = ltm->tm_yday;
+ /*
+ * print yesterday's total
+ */
+ secs = ut_timecopy;
+ secs.tv_sec -= ltm->tm_sec;
+ secs.tv_sec -= 60 * ltm->tm_min;
+ secs.tv_sec -= 3600 * ltm->tm_hour;
+ secs.tv_usec = 0;
+ show_today(secs);
+ } else
+ day = ltm->tm_yday;
+ }
+ switch(usr->ut_type) {
+ case OLD_TIME:
+ clock_shift = ut_timecopy;
+ break;
+ case NEW_TIME:
+ timersub(&clock_shift, &ut_timecopy, &clock_shift);
+ /*
+ * adjust time for those logged in
+ */
+ SLIST_FOREACH(lp, &CurUtmpx, next)
+ timersub(&lp->time, &clock_shift, &lp->time);
+ break;
+ case BOOT_TIME:
+ case SHUTDOWN_TIME:
+ log_out(usr);
+ FirstTime = ut_timecopy; /* shouldn't be needed */
+ break;
+ case USER_PROCESS:
+ /*
+ * If they came in on pts/..., then it is only
+ * a login session if the ut_host field is non-empty.
+ */
+ if (strncmp(usr->ut_line, "pts/", 4) != 0 ||
+ *usr->ut_host != '\0')
+ log_in(usr);
+ break;
+ case DEAD_PROCESS:
+ log_out(usr);
+ break;
+ }
+ }
+ endutxent();
+ (void)gettimeofday(&now, NULL);
+ if (Flags & AC_W)
+ usht.ut_tv = ut_timecopy;
+ else
+ usht.ut_tv = now;
+ usht.ut_type = SHUTDOWN_TIME;
+
+ if (Flags & AC_D) {
+ ltm = localtime(&ut_timecopy.tv_sec);
+ if (day >= 0 && day != ltm->tm_yday) {
+ /*
+ * print yesterday's total
+ */
+ secs = ut_timecopy;
+ secs.tv_sec -= ltm->tm_sec;
+ secs.tv_sec -= 60 * ltm->tm_min;
+ secs.tv_sec -= 3600 * ltm->tm_hour;
+ secs.tv_usec = 0;
+ show_today(secs);
+ }
+ }
+ /*
+ * anyone still logged in gets time up to now
+ */
+ log_out(&usht);
+
+ if (Flags & AC_D)
+ show_today(now);
+ else {
+ if (Flags & AC_P)
+ show_users();
+ show("total", Total);
+ }
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+#ifdef CONSOLE_TTY
+ "ac [-dp] [-c console] [-t tty] [-w wtmp] [users ...]\n");
+#else
+ "ac [-dp] [-t tty] [-w wtmp] [users ...]\n");
+#endif
+ exit(1);
+}
diff --git a/usr.sbin/accton/Makefile b/usr.sbin/accton/Makefile
new file mode 100644
index 0000000..ea9b4f4
--- /dev/null
+++ b/usr.sbin/accton/Makefile
@@ -0,0 +1,7 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+PROG= accton
+MAN= accton.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/accton/Makefile.depend b/usr.sbin/accton/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/accton/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/accton/accton.8 b/usr.sbin/accton/accton.8
new file mode 100644
index 0000000..73c7d29
--- /dev/null
+++ b/usr.sbin/accton/accton.8
@@ -0,0 +1,42 @@
+.\" $FreeBSD$
+.\"
+.Dd May 21, 1993
+.Dt ACCTON 8
+.Os
+.Sh NAME
+.Nm accton
+.Nd enable/disable system accounting
+.Sh SYNOPSIS
+.Nm
+.Op Ar acctfile
+.Sh DESCRIPTION
+The
+.Nm
+utility is used
+for switching system accounting on or off.
+If called with the argument
+.Ar acctfile ,
+system accounting is enabled.
+The
+.Ar acctfile
+specified must exist prior to starting system accounting, or
+.Nm
+will return an error.
+A record of every process that is started by the
+.Xr execve 2
+system call and then later exits the system is stored in
+.Ar acctfile .
+The
+.Xr sa 8
+command may be used to examine the accounting records.
+If no arguments are given, system accounting is disabled.
+.Sh FILES
+.Bl -tag -width /var/account/acct
+.It Pa /var/account/acct
+default accounting file
+.El
+.Sh SEE ALSO
+.Xr lastcomm 1 ,
+.Xr acct 2 ,
+.Xr acct 5 ,
+.Xr sa 8
diff --git a/usr.sbin/accton/accton.c b/usr.sbin/accton/accton.c
new file mode 100644
index 0000000..33e6604
--- /dev/null
+++ b/usr.sbin/accton/accton.c
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1988, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)accton.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+
+ while ((ch = getopt(argc, argv, "")) != -1)
+ switch(ch) {
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ switch(argc) {
+ case 0:
+ if (acct(NULL))
+ err(1, NULL);
+ break;
+ case 1:
+ if (acct(*argv))
+ err(1, "%s", *argv);
+ break;
+ default:
+ usage();
+ }
+ exit(0);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: accton [file]\n");
+ exit(1);
+}
diff --git a/usr.sbin/acpi/Makefile b/usr.sbin/acpi/Makefile
new file mode 100644
index 0000000..8190bc7
--- /dev/null
+++ b/usr.sbin/acpi/Makefile
@@ -0,0 +1,7 @@
+# Makefile for acpi tools
+# $Id: Makefile,v 1.1 2000/07/14 18:16:22 iwasaki Exp $
+# $FreeBSD$
+
+SUBDIR= acpiconf acpidb acpidump iasl
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/acpi/Makefile.inc b/usr.sbin/acpi/Makefile.inc
new file mode 100644
index 0000000..d6ce93b
--- /dev/null
+++ b/usr.sbin/acpi/Makefile.inc
@@ -0,0 +1,25 @@
+# $Id: Makefile.inc,v 1.1 2000/07/14 18:16:22 iwasaki Exp $
+# $FreeBSD$
+
+ACPICA_DIR= ${.CURDIR}/../../../sys/contrib/dev/acpica
+CFLAGS+= -I${.CURDIR}/../../../sys
+
+.if exists(${.CURDIR}/../../Makefile.inc)
+.include "${.CURDIR}/../../Makefile.inc"
+.endif
+
+.PATH: ${ACPICA_DIR} \
+ ${ACPICA_DIR}/common \
+ ${ACPICA_DIR}/compiler \
+ ${ACPICA_DIR}/components/debugger \
+ ${ACPICA_DIR}/components/disassembler \
+ ${ACPICA_DIR}/components/dispatcher \
+ ${ACPICA_DIR}/components/events \
+ ${ACPICA_DIR}/components/executer \
+ ${ACPICA_DIR}/components/hardware \
+ ${ACPICA_DIR}/components/namespace \
+ ${ACPICA_DIR}/components/parser \
+ ${ACPICA_DIR}/components/resources \
+ ${ACPICA_DIR}/components/tables \
+ ${ACPICA_DIR}/components/utilities \
+ ${ACPICA_DIR}/os_specific/service_layers
diff --git a/usr.sbin/acpi/acpiconf/Makefile b/usr.sbin/acpi/acpiconf/Makefile
new file mode 100644
index 0000000..0bbadc1
--- /dev/null
+++ b/usr.sbin/acpi/acpiconf/Makefile
@@ -0,0 +1,8 @@
+# $Id: Makefile,v 1.2 2000/07/14 18:16:25 iwasaki Exp $
+# $FreeBSD$
+
+PROG= acpiconf
+MAN= acpiconf.8
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/acpi/acpiconf/Makefile.depend b/usr.sbin/acpi/acpiconf/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/acpi/acpiconf/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/acpi/acpiconf/acpiconf.8 b/usr.sbin/acpi/acpiconf/acpiconf.8
new file mode 100644
index 0000000..07862d7
--- /dev/null
+++ b/usr.sbin/acpi/acpiconf/acpiconf.8
@@ -0,0 +1,94 @@
+.\"-
+.\" Copyright (c) 2000 Dag-Erling Coïdan Smørgrav
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer
+.\" in this position and unchanged.
+.\" 2. Redistributions in binary form must 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 September 22, 2015
+.Dt ACPICONF 8
+.Os
+.Sh NAME
+.Nm acpiconf
+.Nd control ACPI power management
+.Sh SYNOPSIS
+.Nm
+.Op Fl h
+.Op Fl i Ar batt
+.Op Fl k Ar ack
+.Op Fl s Ar type
+.Sh DESCRIPTION
+The
+.Nm
+utility allows the user control of the ACPI power management
+functions.
+The following command-line options are recognized:
+.Bl -tag -width ".Fl s Ar type"
+.It Fl h
+Displays a summary of available options.
+.It Fl i Ar batt
+Get design information about the specified battery.
+.It Fl k Ar ack
+Ack or abort a pending suspend request using the argument provided.
+.Sy Most users should not use this option directly.
+.It Fl s Ar type
+Enters the specified sleep mode.
+Recognized types are
+.Cm 1
+(only the CPU clock is stopped),
+.Cm 2
+(not implemented on most systems but similar to S1),
+.Cm 3
+(the CPU context is lost and memory context is preserved),
+and
+.Cm 4
+(the CPU context is lost and memory context is stored to disk).
+Sleep states may also be given as S1, S2, etc.
+The supported states depend on BIOS implementation, including ACPI
+byte code (AML).
+If the
+.Pa /etc/rc.suspend
+and
+.Pa /etc/rc.resume
+scripts are executable, they will be run before and after entering
+the given sleep state.
+.El
+.Sh SEE ALSO
+.Xr acpi 4 ,
+.Xr acpidump 8 ,
+.Xr apm 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 5.0 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility was written by
+.An Mitsuru Iwasaki Aq Mt iwasaki@FreeBSD.org .
+This manual page was written by
+.An Dag-Erling Sm\(/orgrav Aq Mt des@FreeBSD.org .
diff --git a/usr.sbin/acpi/acpiconf/acpiconf.c b/usr.sbin/acpi/acpiconf/acpiconf.c
new file mode 100644
index 0000000..1fab4b6
--- /dev/null
+++ b/usr.sbin/acpi/acpiconf/acpiconf.c
@@ -0,0 +1,247 @@
+/*-
+ * Copyright (c) 1999 Mitsuru IWASAKI <iwasaki@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.
+ *
+ * $Id: acpiconf.c,v 1.5 2000/08/08 14:12:19 iwasaki Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include <dev/acpica/acpiio.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+
+#define ACPIDEV "/dev/acpi"
+
+static int acpifd;
+
+static void
+acpi_init(void)
+{
+ acpifd = open(ACPIDEV, O_RDWR);
+ if (acpifd == -1)
+ acpifd = open(ACPIDEV, O_RDONLY);
+ if (acpifd == -1)
+ err(EX_OSFILE, ACPIDEV);
+}
+
+/* Prepare to sleep and then wait for the signal that sleeping can occur. */
+static void
+acpi_sleep(int sleep_type)
+{
+ int ret;
+
+ /* Notify OS that we want to sleep. devd(8) gets this notify. */
+ ret = ioctl(acpifd, ACPIIO_REQSLPSTATE, &sleep_type);
+ if (ret != 0)
+ err(EX_IOERR, "request sleep type (%d) failed", sleep_type);
+}
+
+/* Ack or abort a pending suspend request. */
+static void
+acpi_sleep_ack(int err_val)
+{
+ int ret;
+
+ ret = ioctl(acpifd, ACPIIO_ACKSLPSTATE, &err_val);
+ if (ret != 0)
+ err(EX_IOERR, "ack sleep type failed");
+}
+
+/* should be a acpi define, but doesn't appear to be */
+#define UNKNOWN_CAP 0xffffffff
+#define UNKNOWN_VOLTAGE 0xffffffff
+
+static int
+acpi_battinfo(int num)
+{
+ union acpi_battery_ioctl_arg battio;
+ const char *pwr_units;
+ int hours, min, amp;
+ uint32_t volt;
+
+ if (num < 0 || num > 64)
+ err(EX_USAGE, "invalid battery %d", num);
+
+ /* Print battery design information. */
+ battio.unit = num;
+ if (ioctl(acpifd, ACPIIO_BATT_GET_BIF, &battio) == -1)
+ err(EX_IOERR, "get battery info (%d) failed", num);
+ amp = battio.bif.units;
+ pwr_units = amp ? "mA" : "mW";
+ if (battio.bif.dcap == UNKNOWN_CAP)
+ printf("Design capacity:\tunknown\n");
+ else
+ printf("Design capacity:\t%d %sh\n", battio.bif.dcap,
+ pwr_units);
+ if (battio.bif.lfcap == UNKNOWN_CAP)
+ printf("Last full capacity:\tunknown\n");
+ else
+ printf("Last full capacity:\t%d %sh\n", battio.bif.lfcap,
+ pwr_units);
+ printf("Technology:\t\t%s\n", battio.bif.btech == 0 ?
+ "primary (non-rechargeable)" : "secondary (rechargeable)");
+ if (battio.bif.dvol == UNKNOWN_CAP)
+ printf("Design voltage:\t\tunknown\n");
+ else
+ printf("Design voltage:\t\t%d mV\n", battio.bif.dvol);
+ printf("Capacity (warn):\t%d %sh\n", battio.bif.wcap, pwr_units);
+ printf("Capacity (low):\t\t%d %sh\n", battio.bif.lcap, pwr_units);
+ printf("Low/warn granularity:\t%d %sh\n", battio.bif.gra1, pwr_units);
+ printf("Warn/full granularity:\t%d %sh\n", battio.bif.gra2, pwr_units);
+ printf("Model number:\t\t%s\n", battio.bif.model);
+ printf("Serial number:\t\t%s\n", battio.bif.serial);
+ printf("Type:\t\t\t%s\n", battio.bif.type);
+ printf("OEM info:\t\t%s\n", battio.bif.oeminfo);
+
+ /* Fetch battery voltage information. */
+ volt = UNKNOWN_VOLTAGE;
+ battio.unit = num;
+ if (ioctl(acpifd, ACPIIO_BATT_GET_BST, &battio) == -1)
+ err(EX_IOERR, "get battery status (%d) failed", num);
+ if (battio.bst.state != ACPI_BATT_STAT_NOT_PRESENT)
+ volt = battio.bst.volt;
+
+ /* Print current battery state information. */
+ battio.unit = num;
+ if (ioctl(acpifd, ACPIIO_BATT_GET_BATTINFO, &battio) == -1)
+ err(EX_IOERR, "get battery user info (%d) failed", num);
+ if (battio.battinfo.state != ACPI_BATT_STAT_NOT_PRESENT) {
+ const char *state;
+ switch (battio.battinfo.state & ACPI_BATT_STAT_BST_MASK) {
+ case 0:
+ state = "high";
+ break;
+ case ACPI_BATT_STAT_DISCHARG:
+ state = "discharging";
+ break;
+ case ACPI_BATT_STAT_CHARGING:
+ state = "charging";
+ break;
+ case ACPI_BATT_STAT_CRITICAL:
+ state = "critical";
+ break;
+ case ACPI_BATT_STAT_DISCHARG | ACPI_BATT_STAT_CRITICAL:
+ state = "critical discharging";
+ break;
+ case ACPI_BATT_STAT_CHARGING | ACPI_BATT_STAT_CRITICAL:
+ state = "critical charging";
+ break;
+ default:
+ state = "invalid";
+ }
+ printf("State:\t\t\t%s\n", state);
+ if (battio.battinfo.cap == -1)
+ printf("Remaining capacity:\tunknown\n");
+ else
+ printf("Remaining capacity:\t%d%%\n",
+ battio.battinfo.cap);
+ if (battio.battinfo.min == -1)
+ printf("Remaining time:\t\tunknown\n");
+ else {
+ hours = battio.battinfo.min / 60;
+ min = battio.battinfo.min % 60;
+ printf("Remaining time:\t\t%d:%02d\n", hours, min);
+ }
+ if (battio.battinfo.rate == -1)
+ printf("Present rate:\t\tunknown\n");
+ else if (amp && volt != UNKNOWN_VOLTAGE) {
+ printf("Present rate:\t\t%d mA (%d mW)\n",
+ battio.battinfo.rate,
+ battio.battinfo.rate * volt / 1000);
+ } else
+ printf("Present rate:\t\t%d %s\n",
+ battio.battinfo.rate, pwr_units);
+ } else
+ printf("State:\t\t\tnot present\n");
+
+ /* Print battery voltage information. */
+ if (volt == UNKNOWN_VOLTAGE)
+ printf("Present voltage:\tunknown\n");
+ else
+ printf("Present voltage:\t%d mV\n", volt);
+
+ return (0);
+}
+
+static void
+usage(const char* prog)
+{
+ printf("usage: %s [-h] [-i batt] [-k ack] [-s 1-4]\n", prog);
+ exit(0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char c, *prog;
+ int sleep_type;
+
+ prog = argv[0];
+ if (argc < 2)
+ usage(prog);
+ /* NOTREACHED */
+
+ sleep_type = -1;
+ acpi_init();
+ while ((c = getopt(argc, argv, "hi:k:s:")) != -1) {
+ switch (c) {
+ case 'i':
+ acpi_battinfo(atoi(optarg));
+ break;
+ case 'k':
+ acpi_sleep_ack(atoi(optarg));
+ break;
+ case 's':
+ if (optarg[0] == 'S')
+ sleep_type = optarg[1] - '0';
+ else
+ sleep_type = optarg[0] - '0';
+ if (sleep_type < 1 || sleep_type > 4)
+ errx(EX_USAGE, "invalid sleep type (%d)",
+ sleep_type);
+ break;
+ case 'h':
+ default:
+ usage(prog);
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (sleep_type != -1)
+ acpi_sleep(sleep_type);
+
+ close(acpifd);
+ exit (0);
+}
diff --git a/usr.sbin/acpi/acpidb/Makefile b/usr.sbin/acpi/acpidb/Makefile
new file mode 100644
index 0000000..8d13e77
--- /dev/null
+++ b/usr.sbin/acpi/acpidb/Makefile
@@ -0,0 +1,79 @@
+# $FreeBSD$
+
+PROG= acpidb
+SRCS= acpidb.c
+
+# common
+SRCS+= acgetline.c ahids.c ahuuids.c cmfsize.c
+
+# components/debugger
+SRCS+= dbcmds.c dbconvert.c dbdisply.c dbexec.c dbfileio.c \
+ dbhistry.c dbinput.c dbmethod.c dbnames.c dbobject.c \
+ dbstats.c dbtest.c dbutils.c dbxface.c
+
+# components/disassembler
+SRCS+= dmbuffer.c dmcstyle.c dmdeferred.c dmnames.c dmopcode.c \
+ dmresrc.c dmresrcl.c dmresrcl2.c dmresrcs.c dmutils.c \
+ dmwalk.c
+
+# components/dispatcher
+SRCS+= dsargs.c dscontrol.c dsdebug.c dsfield.c dsinit.c \
+ dsmethod.c dsmthdat.c dsobject.c dsopcode.c dsutils.c \
+ dswexec.c dswload.c dswload2.c dswscope.c dswstate.c
+
+# components/events
+SRCS+= evevent.c evglock.c evgpe.c evgpeblk.c evgpeinit.c \
+ evgpeutil.c evhandler.c evmisc.c evregion.c evrgnini.c \
+ evsci.c evxface.c evxfevnt.c evxfregn.c
+
+# components/executer
+SRCS+= exconfig.c exconvrt.c excreate.c exdebug.c exdump.c \
+ exfield.c exfldio.c exmisc.c exmutex.c exnames.c \
+ exoparg1.c exoparg2.c exoparg3.c exoparg6.c exprep.c \
+ exregion.c exresnte.c exresolv.c exresop.c exstore.c \
+ exstoren.c exstorob.c exsystem.c exutils.c
+
+# components/hardware
+SRCS+= hwacpi.c hwesleep.c hwgpe.c hwpci.c hwregs.c hwsleep.c \
+ hwvalid.c hwxface.c hwxfsleep.c
+
+# components/namespace
+SRCS+= nsaccess.c nsalloc.c nsarguments.c nsconvert.c nsdump.c \
+ nseval.c nsinit.c nsload.c nsnames.c nsobject.c \
+ nsparse.c nspredef.c nsprepkg.c nsrepair.c nsrepair2.c \
+ nssearch.c nsutils.c nswalk.c nsxfeval.c nsxfname.c \
+ nsxfobj.c
+
+# components/parser
+SRCS+= psargs.c psloop.c psobject.c psopcode.c psopinfo.c \
+ psparse.c psscope.c pstree.c psutils.c pswalk.c \
+ psxface.c
+
+# components/resources
+SRCS+= rsaddr.c rscalc.c rscreate.c rsdump.c rsdumpinfo.c \
+ rsinfo.c rsio.c rsirq.c rslist.c rsmemory.c rsmisc.c \
+ rsserial.c rsutils.c rsxface.c
+
+# components/tables
+SRCS+= tbdata.c tbfadt.c tbfind.c tbinstal.c tbprint.c \
+ tbutils.c tbxface.c tbxfload.c
+
+# components/utilities
+SRCS+= utaddress.c utalloc.c utbuffer.c utcache.c utcopy.c \
+ utdebug.c utdecode.c utdelete.c uterror.c uteval.c \
+ utexcep.c utfileio.c utglobal.c uthex.c utids.c \
+ utinit.c utlock.c utmath.c utmisc.c utmutex.c \
+ utnonansi.c utobject.c utosi.c utownerid.c utpredef.c \
+ utprint.c utresrc.c utstate.c utstring.c uttrack.c \
+ utuuid.c utxface.c utxferror.c utxfinit.c
+
+# os_specific/service_layers
+SRCS+= oslibcfs.c osunixxf.c
+
+MAN= acpidb.8
+WARNS?= 3
+
+CFLAGS+= -DACPI_EXEC_APP -fno-strict-aliasing
+LIBADD= pthread
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/acpi/acpidb/Makefile.depend b/usr.sbin/acpi/acpidb/Makefile.depend
new file mode 100644
index 0000000..7b92dbd
--- /dev/null
+++ b/usr.sbin/acpi/acpidb/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libthr \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/acpi/acpidb/acpidb.8 b/usr.sbin/acpi/acpidb/acpidb.8
new file mode 100644
index 0000000..67fab31
--- /dev/null
+++ b/usr.sbin/acpi/acpidb/acpidb.8
@@ -0,0 +1,167 @@
+.\"-
+.\" Copyright (c) 2003 Nate Lawson
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer
+.\" in this position and unchanged.
+.\" 2. Redistributions in binary form must 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 7, 2003
+.Dt ACPIDB 8
+.Os
+.Sh NAME
+.Nm acpidb
+.Nd ACPI DSDT debugger
+.Sh SYNOPSIS
+.Nm
+.Ar input-file
+.Sh DESCRIPTION
+The
+.Nm
+utility is a debugger for the ACPI DSDT.
+It can parse and execute various
+AML methods and display the result.
+.Sh COMMANDS
+.Ss General-Purpose Commands
+.Bl -tag -width indent
+.It Ic Allocations
+Display list of current memory allocations
+.It Ic Dump Ar Address | Namepath Op Cm Byte | Word | Dword | Qword
+Display ACPI objects or memory
+.It Ic EnableAcpi
+Enable ACPI (hardware) mode
+.It Ic Help
+Show various help screens
+.It Ic History
+Display command history buffer
+.It Ic Level Ar DebugLevel Op Cm console
+Get/Set debug level for file or console
+.It Ic Locks
+Current status of internal mutexes
+.It Ic Quit No or Ic Exit
+Exit the debugger
+.It Ic Stats Op Cm Allocations | Memory | Misc | Objects | Tables
+Display namespace and memory statistics
+.It Ic Tables
+Display info about loaded ACPI tables
+.It Ic Unload Ar TableSig Op Ar Instance
+Unload an ACPI table
+.It Ic !\& Ar CommandNumber
+Execute command from history buffer
+.It Ic !!
+Execute last command again
+.El
+.Ss Namespace Access Commands
+.Bl -tag -width indent
+.It Ic Event Cm F | G Ar Value
+Generate AcpiEvent (Fixed/GPE)
+.It Ic Find Ar Name
+Find ACPI name(s) with wildcards
+.Ql ( ?\&
+is wildcard)
+.It Ic Method
+Display list of loaded control methods
+.It Ic Namespace Oo Ar Addr | Path Oc Op Ar Depth
+Display loaded namespace tree/subtree
+.It Ic Notify Ar NamePath Value
+Send a notification
+.It Ic Objects Ar ObjectType
+Display all objects of the given type
+.It Ic Owner Ar OwnerId Op Ar Depth
+Display loaded namespace by object owner
+.It Ic Prefix Op Ar NamePath
+Set or Get current execution prefix
+.It Ic References Ar Addr
+Find all references to object at addr
+.It Ic Resources
+Get and display resources
+.It Ic Terminate
+Delete namespace and all internal objects
+.It Ic Thread Ar Threads Loops NamePath
+Spawn threads to execute method(s)
+.El
+.Ss Control Method Execution Commands
+.Bl -tag -width indent
+.It Ic Arguments
+.Pq Ic Args
+Display method arguments
+.It Ic Breakpoint Ar AmlOffset
+Set an AML execution breakpoint
+.It Ic Call
+Run to next control method invocation
+.It Ic Debug Ar Namepath Op Ar Arguments
+Single Step a control method
+.It Ic Execute Ar Namepath Op Arguments
+Execute control method
+.It Ic Go
+Allow method to run to completion
+.It Ic Information
+Display info about the current method
+.It Ic Into
+Step into (not over) a method call
+.It Ic List Op OpcodeCount
+Display method ASL statements
+.It Ic Locals
+Display method local variables
+.It Ic Results
+Display method result stack
+.It Ic Set Cm A | L Ar # Value
+Set method data (Arguments/Locals)
+.It Ic Stop
+Terminate control method
+.It Ic Tree
+Display control method calling tree
+.It Ic <Enter>
+Single step next AML opcode (over calls)
+.El
+.Ss File I/O Commands
+.Bl -tag -width indent
+.It Ic Close
+Close debug output file
+.It Ic Open Ar Filename
+Open a file for debug output
+.It Ic Load Ar Filename
+Load ACPI table from a file
+.El
+.Sh SEE ALSO
+.Xr acpi 4 ,
+.Xr acpidump 8 ,
+.Xr iasl 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in the
+.Nm acpicatools
+port.
+It was imported for
+.Fx 5.2 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility was written by
+.An Mitsuru Iwasaki Aq Mt iwasaki@FreeBSD.org
+and uses Intel ACPI-CA for the backend.
+This manual page was written by
+.An Nate Lawson .
diff --git a/usr.sbin/acpi/acpidb/acpidb.c b/usr.sbin/acpi/acpidb/acpidb.c
new file mode 100644
index 0000000..6b71961
--- /dev/null
+++ b/usr.sbin/acpi/acpidb/acpidb.c
@@ -0,0 +1,527 @@
+/*-
+ * Copyright (c) 2000-2002 Mitsuru IWASAKI <iwasaki@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/param.h>
+#include <sys/queue.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/stdint.h>
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+#include <contrib/dev/acpica/include/accommon.h>
+#include <contrib/dev/acpica/include/acdebug.h>
+#include <contrib/dev/acpica/include/amlresrc.h>
+
+/*
+ * Dummy DSDT Table Header
+ */
+
+static ACPI_TABLE_HEADER dummy_dsdt_table = {
+ "DSDT", 123, 1, 123, "OEMID", "OEMTBLID", 1, "CRID", 1
+};
+
+/*
+ * Region space I/O routines on virtual machine
+ */
+
+static int aml_debug_prompt = 1;
+
+struct ACPIRegionContent {
+ TAILQ_ENTRY(ACPIRegionContent) links;
+ int regtype;
+ ACPI_PHYSICAL_ADDRESS addr;
+ UINT8 value;
+};
+
+TAILQ_HEAD(ACPIRegionContentList, ACPIRegionContent);
+static struct ACPIRegionContentList RegionContentList;
+
+static int aml_simulation_initialized = 0;
+
+ACPI_PHYSICAL_ADDRESS AeLocalGetRootPointer(void);
+void AeDoObjectOverrides(void);
+void AeTableOverride(ACPI_TABLE_HEADER *, ACPI_TABLE_HEADER **);
+
+static void aml_simulation_init(void);
+static int aml_simulate_regcontent_add(int regtype,
+ ACPI_PHYSICAL_ADDRESS addr,
+ UINT8 value);
+static int aml_simulate_regcontent_read(int regtype,
+ ACPI_PHYSICAL_ADDRESS addr,
+ UINT8 *valuep);
+static int aml_simulate_regcontent_write(int regtype,
+ ACPI_PHYSICAL_ADDRESS addr,
+ UINT8 *valuep);
+static UINT64 aml_simulate_prompt(char *msg, UINT64 def_val);
+static void aml_simulation_regload(const char *dumpfile);
+static void aml_simulation_regdump(const char *dumpfile);
+
+/* Stubs to simplify linkage to the ACPICA core subsystem. */
+ACPI_PHYSICAL_ADDRESS
+AcpiOsGetRootPointer(void)
+{
+
+ return (0);
+}
+
+void
+AeDoObjectOverrides(void)
+{
+}
+
+void
+AeTableOverride(ACPI_TABLE_HEADER *ExistingTable, ACPI_TABLE_HEADER **NewTable)
+{
+}
+
+void
+MpSaveGpioInfo(ACPI_PARSE_OBJECT *Op, AML_RESOURCE *Resource,
+ UINT32 PinCount, UINT16 *PinList, char *DeviceName)
+{
+}
+
+void
+MpSaveSerialInfo(ACPI_PARSE_OBJECT *Op, AML_RESOURCE *Resource,
+ char *DeviceName)
+{
+}
+
+static void
+aml_simulation_init(void)
+{
+
+ aml_simulation_initialized = 1;
+ TAILQ_INIT(&RegionContentList);
+ aml_simulation_regload("region.ini");
+}
+
+static int
+aml_simulate_regcontent_add(int regtype, ACPI_PHYSICAL_ADDRESS addr, UINT8 value)
+{
+ struct ACPIRegionContent *rc;
+
+ rc = malloc(sizeof(struct ACPIRegionContent));
+ if (rc == NULL) {
+ return (-1); /* malloc fail */
+ }
+ rc->regtype = regtype;
+ rc->addr = addr;
+ rc->value = value;
+
+ TAILQ_INSERT_TAIL(&RegionContentList, rc, links);
+ return (0);
+}
+
+static int
+aml_simulate_regcontent_read(int regtype, ACPI_PHYSICAL_ADDRESS addr, UINT8 *valuep)
+{
+ struct ACPIRegionContent *rc;
+
+ if (!aml_simulation_initialized) {
+ aml_simulation_init();
+ }
+ TAILQ_FOREACH(rc, &RegionContentList, links) {
+ if (rc->regtype == regtype && rc->addr == addr) {
+ *valuep = rc->value;
+ return (1); /* found */
+ }
+ }
+
+ *valuep = 0;
+ return (aml_simulate_regcontent_add(regtype, addr, *valuep));
+}
+
+static int
+aml_simulate_regcontent_write(int regtype, ACPI_PHYSICAL_ADDRESS addr, UINT8 *valuep)
+{
+ struct ACPIRegionContent *rc;
+
+ if (!aml_simulation_initialized) {
+ aml_simulation_init();
+ }
+ TAILQ_FOREACH(rc, &RegionContentList, links) {
+ if (rc->regtype == regtype && rc->addr == addr) {
+ rc->value = *valuep;
+ return (1); /* exists */
+ }
+ }
+
+ return (aml_simulate_regcontent_add(regtype, addr, *valuep));
+}
+
+static UINT64
+aml_simulate_prompt(char *msg, UINT64 def_val)
+{
+ char buf[16], *ep;
+ UINT64 val;
+
+ val = def_val;
+ printf("DEBUG");
+ if (msg != NULL) {
+ printf("%s", msg);
+ }
+ printf("(default: 0x%jx ", (uintmax_t)val);
+ printf(" / %ju) >>", (uintmax_t)val);
+ fflush(stdout);
+
+ bzero(buf, sizeof buf);
+ while (1) {
+ if (read(0, buf, sizeof buf) == 0) {
+ continue;
+ }
+ if (buf[0] == '\n') {
+ break; /* use default value */
+ }
+ if (buf[0] == '0' && buf[1] == 'x') {
+ val = strtoq(buf, &ep, 16);
+ } else {
+ val = strtoq(buf, &ep, 10);
+ }
+ break;
+ }
+ return (val);
+}
+
+static void
+aml_simulation_regload(const char *dumpfile)
+{
+ char buf[256], *np, *ep;
+ struct ACPIRegionContent rc;
+ FILE *fp;
+
+ if (!aml_simulation_initialized) {
+ return;
+ }
+
+ if ((fp = fopen(dumpfile, "r")) == NULL) {
+ return;
+ }
+
+ while (fgets(buf, sizeof buf, fp) != NULL) {
+ np = buf;
+ /* reading region type */
+ rc.regtype = strtoq(np, &ep, 10);
+ if (np == ep) {
+ continue;
+ }
+ np = ep;
+
+ /* reading address */
+ rc.addr = strtoq(np, &ep, 16);
+ if (np == ep) {
+ continue;
+ }
+ np = ep;
+
+ /* reading value */
+ rc.value = strtoq(np, &ep, 16);
+ if (np == ep) {
+ continue;
+ }
+ aml_simulate_regcontent_write(rc.regtype, rc.addr, &rc.value);
+ }
+
+ fclose(fp);
+}
+
+static void
+aml_simulation_regdump(const char *dumpfile)
+{
+ struct ACPIRegionContent *rc;
+ FILE *fp;
+
+ if (!aml_simulation_initialized) {
+ return;
+ }
+ if ((fp = fopen(dumpfile, "w")) == NULL) {
+ warn("%s", dumpfile);
+ return;
+ }
+ while (!TAILQ_EMPTY(&RegionContentList)) {
+ rc = TAILQ_FIRST(&RegionContentList);
+ fprintf(fp, "%d 0x%jx 0x%x\n",
+ rc->regtype, (uintmax_t)rc->addr, rc->value);
+ TAILQ_REMOVE(&RegionContentList, rc, links);
+ free(rc);
+ }
+
+ fclose(fp);
+ TAILQ_INIT(&RegionContentList);
+}
+
+/*
+ * Space handlers on virtual machine
+ */
+
+static ACPI_STATUS
+aml_vm_space_handler(
+ UINT32 SpaceID,
+ UINT32 Function,
+ ACPI_PHYSICAL_ADDRESS Address,
+ UINT32 BitWidth,
+ UINT64 *Value,
+ int Prompt)
+{
+ int state;
+ UINT8 val;
+ UINT64 value, i;
+ char msg[256];
+ static const char *space_names[] = {
+ "SYSTEM_MEMORY", "SYSTEM_IO", "PCI_CONFIG",
+ "EC", "SMBUS", "CMOS", "PCI_BAR_TARGET"};
+
+ switch (Function) {
+ case ACPI_READ:
+ value = 0;
+ for (i = 0; (i * 8) < BitWidth; i++) {
+ state = aml_simulate_regcontent_read(SpaceID,
+ Address + i, &val);
+ if (state == -1) {
+ return (AE_NO_MEMORY);
+ }
+ value |= val << (i * 8);
+ }
+ *Value = value;
+ if (Prompt) {
+ sprintf(msg, "[read (%s, %2d, 0x%jx)]",
+ space_names[SpaceID], BitWidth,
+ (uintmax_t)Address);
+ *Value = aml_simulate_prompt(msg, value);
+ if (*Value != value) {
+ return(aml_vm_space_handler(SpaceID,
+ ACPI_WRITE,
+ Address, BitWidth, Value, 0));
+ }
+ }
+ break;
+
+ case ACPI_WRITE:
+ value = *Value;
+ if (Prompt) {
+ sprintf(msg, "[write(%s, %2d, 0x%jx)]",
+ space_names[SpaceID], BitWidth,
+ (uintmax_t)Address);
+ value = aml_simulate_prompt(msg, *Value);
+ }
+ *Value = value;
+ for (i = 0; (i * 8) < BitWidth; i++) {
+ val = value & 0xff;
+ state = aml_simulate_regcontent_write(SpaceID,
+ Address + i, &val);
+ if (state == -1) {
+ return (AE_NO_MEMORY);
+ }
+ value = value >> 8;
+ }
+ }
+
+ return (AE_OK);
+}
+
+#define DECLARE_VM_SPACE_HANDLER(name, id); \
+static ACPI_STATUS \
+aml_vm_space_handler_##name ( \
+ UINT32 Function, \
+ ACPI_PHYSICAL_ADDRESS Address, \
+ UINT32 BitWidth, \
+ UINT64 *Value) \
+{ \
+ return (aml_vm_space_handler(id, Function, Address, \
+ BitWidth, Value, aml_debug_prompt)); \
+}
+
+DECLARE_VM_SPACE_HANDLER(system_memory, ACPI_ADR_SPACE_SYSTEM_MEMORY);
+DECLARE_VM_SPACE_HANDLER(system_io, ACPI_ADR_SPACE_SYSTEM_IO);
+DECLARE_VM_SPACE_HANDLER(pci_config, ACPI_ADR_SPACE_PCI_CONFIG);
+DECLARE_VM_SPACE_HANDLER(ec, ACPI_ADR_SPACE_EC);
+DECLARE_VM_SPACE_HANDLER(smbus, ACPI_ADR_SPACE_SMBUS);
+DECLARE_VM_SPACE_HANDLER(cmos, ACPI_ADR_SPACE_CMOS);
+DECLARE_VM_SPACE_HANDLER(pci_bar_target,ACPI_ADR_SPACE_PCI_BAR_TARGET);
+
+/*
+ * Load DSDT data file and invoke debugger
+ */
+
+static int
+load_dsdt(const char *dsdtfile)
+{
+ char filetmp[PATH_MAX];
+ u_int8_t *code;
+ struct stat sb;
+ int fd, fd2;
+ int error;
+
+ fd = open(dsdtfile, O_RDONLY, 0);
+ if (fd == -1) {
+ perror("open");
+ return (-1);
+ }
+ if (fstat(fd, &sb) == -1) {
+ perror("fstat");
+ close(fd);
+ return (-1);
+ }
+ code = mmap(NULL, (size_t)sb.st_size, PROT_READ, MAP_PRIVATE, fd, (off_t)0);
+ if (code == NULL) {
+ perror("mmap");
+ return (-1);
+ }
+ if ((error = AcpiInitializeSubsystem()) != AE_OK) {
+ return (-1);
+ }
+
+ /*
+ * make sure DSDT data contains table header or not.
+ */
+ if (strncmp((char *)code, "DSDT", 4) == 0) {
+ strncpy(filetmp, dsdtfile, sizeof(filetmp));
+ } else {
+ mode_t mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ dummy_dsdt_table.Length = sizeof(ACPI_TABLE_HEADER) + sb.st_size;
+ snprintf(filetmp, sizeof(filetmp), "%s.tmp", dsdtfile);
+ fd2 = open(filetmp, O_WRONLY | O_CREAT | O_TRUNC, mode);
+ if (fd2 == -1) {
+ perror("open");
+ return (-1);
+ }
+ write(fd2, &dummy_dsdt_table, sizeof(ACPI_TABLE_HEADER));
+
+ write(fd2, code, sb.st_size);
+ close(fd2);
+ }
+
+ /*
+ * Install the virtual machine version of address space handlers.
+ */
+ if ((error = AcpiInstallAddressSpaceHandler(ACPI_ROOT_OBJECT,
+ ACPI_ADR_SPACE_SYSTEM_MEMORY,
+ (ACPI_ADR_SPACE_HANDLER)aml_vm_space_handler_system_memory,
+ NULL, NULL)) != AE_OK) {
+ fprintf(stderr, "could not initialise SystemMemory handler: %d\n", error);
+ return (-1);
+ }
+ if ((error = AcpiInstallAddressSpaceHandler(ACPI_ROOT_OBJECT,
+ ACPI_ADR_SPACE_SYSTEM_IO,
+ (ACPI_ADR_SPACE_HANDLER)aml_vm_space_handler_system_io,
+ NULL, NULL)) != AE_OK) {
+ fprintf(stderr, "could not initialise SystemIO handler: %d\n", error);
+ return (-1);
+ }
+ if ((error = AcpiInstallAddressSpaceHandler(ACPI_ROOT_OBJECT,
+ ACPI_ADR_SPACE_PCI_CONFIG,
+ (ACPI_ADR_SPACE_HANDLER)aml_vm_space_handler_pci_config,
+ NULL, NULL)) != AE_OK) {
+ fprintf(stderr, "could not initialise PciConfig handler: %d\n", error);
+ return (-1);
+ }
+ if ((error = AcpiInstallAddressSpaceHandler(ACPI_ROOT_OBJECT,
+ ACPI_ADR_SPACE_EC,
+ (ACPI_ADR_SPACE_HANDLER)aml_vm_space_handler_ec,
+ NULL, NULL)) != AE_OK) {
+ fprintf(stderr, "could not initialise EC handler: %d\n", error);
+ return (-1);
+ }
+ if ((error = AcpiInstallAddressSpaceHandler(ACPI_ROOT_OBJECT,
+ ACPI_ADR_SPACE_SMBUS,
+ (ACPI_ADR_SPACE_HANDLER)aml_vm_space_handler_smbus,
+ NULL, NULL)) != AE_OK) {
+ fprintf(stderr, "could not initialise SMBUS handler: %d\n", error);
+ return (-1);
+ }
+ if ((error = AcpiInstallAddressSpaceHandler(ACPI_ROOT_OBJECT,
+ ACPI_ADR_SPACE_CMOS,
+ (ACPI_ADR_SPACE_HANDLER)aml_vm_space_handler_cmos,
+ NULL, NULL)) != AE_OK) {
+ fprintf(stderr, "could not initialise CMOS handler: %d\n", error);
+ return (-1);
+ }
+ if ((error = AcpiInstallAddressSpaceHandler(ACPI_ROOT_OBJECT,
+ ACPI_ADR_SPACE_PCI_BAR_TARGET,
+ (ACPI_ADR_SPACE_HANDLER)aml_vm_space_handler_pci_bar_target,
+ NULL, NULL)) != AE_OK) {
+ fprintf(stderr, "could not initialise PCI BAR TARGET handler: %d\n", error);
+ return (-1);
+ }
+
+ AcpiDbGetTableFromFile(filetmp, NULL, TRUE);
+
+ AcpiInitializeDebugger();
+ AcpiGbl_DebuggerConfiguration = 0;
+ AcpiDbUserCommands(':', NULL);
+
+ if (strcmp(dsdtfile, filetmp) != 0) {
+ unlink(filetmp);
+ }
+
+ return (0);
+}
+
+static void
+usage(const char *progname)
+{
+
+ printf("usage: %s dsdt_file\n", progname);
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *progname;
+
+ progname = argv[0];
+
+ if (argc == 1) {
+ usage(progname);
+ }
+
+ AcpiDbgLevel = ACPI_DEBUG_DEFAULT;
+
+ /*
+ * Match kernel options for the interpreter. Global variable names
+ * can be found in acglobal.h.
+ */
+ AcpiGbl_EnableInterpreterSlack = TRUE;
+
+ aml_simulation_regload("region.ini");
+ if (load_dsdt(argv[1]) == 0) {
+ aml_simulation_regdump("region.dmp");
+ }
+
+ return (0);
+}
diff --git a/usr.sbin/acpi/acpidump/Makefile b/usr.sbin/acpi/acpidump/Makefile
new file mode 100644
index 0000000..e258f8e
--- /dev/null
+++ b/usr.sbin/acpi/acpidump/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= acpidump
+MAN= acpidump.8
+SRCS= acpi.c acpi_user.c acpidump.c
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/acpi/acpidump/Makefile.depend b/usr.sbin/acpi/acpidump/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/acpi/acpidump/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/acpi/acpidump/acpi.c b/usr.sbin/acpi/acpidump/acpi.c
new file mode 100644
index 0000000..52a9e8a
--- /dev/null
+++ b/usr.sbin/acpi/acpidump/acpi.c
@@ -0,0 +1,1577 @@
+/*-
+ * Copyright (c) 1998 Doug Rabson
+ * Copyright (c) 2000 Mitsuru IWASAKI <iwasaki@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/param.h>
+#include <sys/endian.h>
+#include <sys/stat.h>
+#include <sys/wait.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 "acpidump.h"
+
+#define BEGIN_COMMENT "/*\n"
+#define END_COMMENT " */\n"
+
+static void acpi_print_string(char *s, size_t length);
+static void acpi_print_gas(ACPI_GENERIC_ADDRESS *gas);
+static int acpi_get_fadt_revision(ACPI_TABLE_FADT *fadt);
+static void acpi_handle_fadt(ACPI_TABLE_HEADER *fadt);
+static void acpi_print_cpu(u_char cpu_id);
+static void acpi_print_cpu_uid(uint32_t uid, char *uid_string);
+static void acpi_print_local_apic(uint32_t apic_id, uint32_t flags);
+static void acpi_print_io_apic(uint32_t apic_id, uint32_t int_base,
+ uint64_t apic_addr);
+static void acpi_print_mps_flags(uint16_t flags);
+static void acpi_print_intr(uint32_t intr, uint16_t mps_flags);
+static void acpi_print_local_nmi(u_int lint, uint16_t mps_flags);
+static void acpi_print_madt(ACPI_SUBTABLE_HEADER *mp);
+static void acpi_handle_madt(ACPI_TABLE_HEADER *sdp);
+static void acpi_handle_ecdt(ACPI_TABLE_HEADER *sdp);
+static void acpi_handle_hpet(ACPI_TABLE_HEADER *sdp);
+static void acpi_handle_mcfg(ACPI_TABLE_HEADER *sdp);
+static void acpi_handle_slit(ACPI_TABLE_HEADER *sdp);
+static void acpi_print_srat_cpu(uint32_t apic_id, uint32_t proximity_domain,
+ uint32_t flags);
+static void acpi_print_srat_memory(ACPI_SRAT_MEM_AFFINITY *mp);
+static void acpi_print_srat(ACPI_SUBTABLE_HEADER *srat);
+static void acpi_handle_srat(ACPI_TABLE_HEADER *sdp);
+static void acpi_handle_tcpa(ACPI_TABLE_HEADER *sdp);
+static void acpi_print_sdt(ACPI_TABLE_HEADER *sdp);
+static void acpi_print_fadt(ACPI_TABLE_HEADER *sdp);
+static void acpi_print_facs(ACPI_TABLE_FACS *facs);
+static void acpi_print_dsdt(ACPI_TABLE_HEADER *dsdp);
+static ACPI_TABLE_HEADER *acpi_map_sdt(vm_offset_t pa);
+static void acpi_print_rsd_ptr(ACPI_TABLE_RSDP *rp);
+static void acpi_handle_rsdt(ACPI_TABLE_HEADER *rsdp);
+static void acpi_walk_subtables(ACPI_TABLE_HEADER *table, void *first,
+ void (*action)(ACPI_SUBTABLE_HEADER *));
+
+/* Size of an address. 32-bit for ACPI 1.0, 64-bit for ACPI 2.0 and up. */
+static int addr_size;
+
+/* Strings used in the TCPA table */
+static const char *tcpa_event_type_strings[] = {
+ "PREBOOT Certificate",
+ "POST Code",
+ "Unused",
+ "No Action",
+ "Separator",
+ "Action",
+ "Event Tag",
+ "S-CRTM Contents",
+ "S-CRTM Version",
+ "CPU Microcode",
+ "Platform Config Flags",
+ "Table of Devices",
+ "Compact Hash",
+ "IPL",
+ "IPL Partition Data",
+ "Non-Host Code",
+ "Non-Host Config",
+ "Non-Host Info"
+};
+
+static const char *TCPA_pcclient_strings[] = {
+ "<undefined>",
+ "SMBIOS",
+ "BIS Certificate",
+ "POST BIOS ROM Strings",
+ "ESCD",
+ "CMOS",
+ "NVRAM",
+ "Option ROM Execute",
+ "Option ROM Configurateion",
+ "<undefined>",
+ "Option ROM Microcode Update ",
+ "S-CRTM Version String",
+ "S-CRTM Contents",
+ "POST Contents",
+ "Table of Devices",
+};
+
+#define PRINTFLAG_END() printflag_end()
+
+static char pf_sep = '{';
+
+static void
+printflag_end(void)
+{
+
+ if (pf_sep != '{') {
+ printf("}");
+ pf_sep = '{';
+ }
+ printf("\n");
+}
+
+static void
+printflag(uint64_t var, uint64_t mask, const char *name)
+{
+
+ if (var & mask) {
+ printf("%c%s", pf_sep, name);
+ pf_sep = ',';
+ }
+}
+
+static void
+acpi_print_string(char *s, size_t length)
+{
+ int c;
+
+ /* Trim trailing spaces and NULLs */
+ while (length > 0 && (s[length - 1] == ' ' || s[length - 1] == '\0'))
+ length--;
+
+ while (length--) {
+ c = *s++;
+ putchar(c);
+ }
+}
+
+static void
+acpi_print_gas(ACPI_GENERIC_ADDRESS *gas)
+{
+ switch(gas->SpaceId) {
+ case ACPI_GAS_MEMORY:
+ if (gas->BitWidth <= 32)
+ printf("0x%08x:%u[%u] (Memory)",
+ (u_int)gas->Address, gas->BitOffset,
+ gas->BitWidth);
+ else
+ printf("0x%016jx:%u[%u] (Memory)",
+ (uintmax_t)gas->Address, gas->BitOffset,
+ gas->BitWidth);
+ break;
+ case ACPI_GAS_IO:
+ printf("0x%02x:%u[%u] (IO)", (u_int)gas->Address,
+ gas->BitOffset, gas->BitWidth);
+ break;
+ case ACPI_GAS_PCI:
+ printf("%x:%x+0x%x (PCI)", (uint16_t)(gas->Address >> 32),
+ (uint16_t)((gas->Address >> 16) & 0xffff),
+ (uint16_t)gas->Address);
+ break;
+ /* XXX How to handle these below? */
+ case ACPI_GAS_EMBEDDED:
+ printf("0x%x:%u[%u] (EC)", (uint16_t)gas->Address,
+ gas->BitOffset, gas->BitWidth);
+ break;
+ case ACPI_GAS_SMBUS:
+ printf("0x%x:%u[%u] (SMBus)", (uint16_t)gas->Address,
+ gas->BitOffset, gas->BitWidth);
+ break;
+ case ACPI_GAS_CMOS:
+ case ACPI_GAS_PCIBAR:
+ case ACPI_GAS_DATATABLE:
+ case ACPI_GAS_FIXED:
+ default:
+ printf("0x%016jx (?)", (uintmax_t)gas->Address);
+ break;
+ }
+}
+
+/* The FADT revision indicates whether we use the DSDT or X_DSDT addresses. */
+static int
+acpi_get_fadt_revision(ACPI_TABLE_FADT *fadt)
+{
+ int fadt_revision;
+
+ /* Set the FADT revision separately from the RSDP version. */
+ if (addr_size == 8) {
+ fadt_revision = 2;
+
+ /*
+ * A few systems (e.g., IBM T23) have an RSDP that claims
+ * revision 2 but the 64 bit addresses are invalid. If
+ * revision 2 and the 32 bit address is non-zero but the
+ * 32 and 64 bit versions don't match, prefer the 32 bit
+ * version for all subsequent tables.
+ */
+ if (fadt->Facs != 0 &&
+ (fadt->XFacs & 0xffffffff) != fadt->Facs)
+ fadt_revision = 1;
+ } else
+ fadt_revision = 1;
+ return (fadt_revision);
+}
+
+static void
+acpi_handle_fadt(ACPI_TABLE_HEADER *sdp)
+{
+ ACPI_TABLE_HEADER *dsdp;
+ ACPI_TABLE_FACS *facs;
+ ACPI_TABLE_FADT *fadt;
+ int fadt_revision;
+
+ fadt = (ACPI_TABLE_FADT *)sdp;
+ acpi_print_fadt(sdp);
+
+ fadt_revision = acpi_get_fadt_revision(fadt);
+ if (fadt_revision == 1)
+ facs = (ACPI_TABLE_FACS *)acpi_map_sdt(fadt->Facs);
+ else
+ facs = (ACPI_TABLE_FACS *)acpi_map_sdt(fadt->XFacs);
+ if (memcmp(facs->Signature, ACPI_SIG_FACS, 4) != 0 || facs->Length < 64)
+ errx(1, "FACS is corrupt");
+ acpi_print_facs(facs);
+
+ if (fadt_revision == 1)
+ dsdp = (ACPI_TABLE_HEADER *)acpi_map_sdt(fadt->Dsdt);
+ else
+ dsdp = (ACPI_TABLE_HEADER *)acpi_map_sdt(fadt->XDsdt);
+ if (acpi_checksum(dsdp, dsdp->Length))
+ errx(1, "DSDT is corrupt");
+ acpi_print_dsdt(dsdp);
+}
+
+static void
+acpi_walk_subtables(ACPI_TABLE_HEADER *table, void *first,
+ void (*action)(ACPI_SUBTABLE_HEADER *))
+{
+ ACPI_SUBTABLE_HEADER *subtable;
+ char *end;
+
+ subtable = first;
+ end = (char *)table + table->Length;
+ while ((char *)subtable < end) {
+ printf("\n");
+ action(subtable);
+ subtable = (ACPI_SUBTABLE_HEADER *)((char *)subtable +
+ subtable->Length);
+ }
+}
+
+static void
+acpi_print_cpu(u_char cpu_id)
+{
+
+ printf("\tACPI CPU=");
+ if (cpu_id == 0xff)
+ printf("ALL\n");
+ else
+ printf("%d\n", (u_int)cpu_id);
+}
+
+static void
+acpi_print_cpu_uid(uint32_t uid, char *uid_string)
+{
+
+ printf("\tUID=%d", uid);
+ if (uid_string != NULL)
+ printf(" (%s)", uid_string);
+ printf("\n");
+}
+
+static void
+acpi_print_local_apic(uint32_t apic_id, uint32_t flags)
+{
+
+ printf("\tFlags={");
+ if (flags & ACPI_MADT_ENABLED)
+ printf("ENABLED");
+ else
+ printf("DISABLED");
+ printf("}\n");
+ printf("\tAPIC ID=%d\n", apic_id);
+}
+
+static void
+acpi_print_io_apic(uint32_t apic_id, uint32_t int_base, uint64_t apic_addr)
+{
+
+ printf("\tAPIC ID=%d\n", apic_id);
+ printf("\tINT BASE=%d\n", int_base);
+ printf("\tADDR=0x%016jx\n", (uintmax_t)apic_addr);
+}
+
+static void
+acpi_print_mps_flags(uint16_t flags)
+{
+
+ printf("\tFlags={Polarity=");
+ switch (flags & ACPI_MADT_POLARITY_MASK) {
+ case ACPI_MADT_POLARITY_CONFORMS:
+ printf("conforming");
+ break;
+ case ACPI_MADT_POLARITY_ACTIVE_HIGH:
+ printf("active-hi");
+ break;
+ case ACPI_MADT_POLARITY_ACTIVE_LOW:
+ printf("active-lo");
+ break;
+ default:
+ printf("0x%x", flags & ACPI_MADT_POLARITY_MASK);
+ break;
+ }
+ printf(", Trigger=");
+ switch (flags & ACPI_MADT_TRIGGER_MASK) {
+ case ACPI_MADT_TRIGGER_CONFORMS:
+ printf("conforming");
+ break;
+ case ACPI_MADT_TRIGGER_EDGE:
+ printf("edge");
+ break;
+ case ACPI_MADT_TRIGGER_LEVEL:
+ printf("level");
+ break;
+ default:
+ printf("0x%x", (flags & ACPI_MADT_TRIGGER_MASK) >> 2);
+ }
+ printf("}\n");
+}
+
+static void
+acpi_print_intr(uint32_t intr, uint16_t mps_flags)
+{
+
+ printf("\tINTR=%d\n", intr);
+ acpi_print_mps_flags(mps_flags);
+}
+
+static void
+acpi_print_local_nmi(u_int lint, uint16_t mps_flags)
+{
+
+ printf("\tLINT Pin=%d\n", lint);
+ acpi_print_mps_flags(mps_flags);
+}
+
+static const char *apic_types[] = { "Local APIC", "IO APIC", "INT Override",
+ "NMI", "Local APIC NMI",
+ "Local APIC Override", "IO SAPIC",
+ "Local SAPIC", "Platform Interrupt",
+ "Local X2APIC", "Local X2APIC NMI" };
+static const char *platform_int_types[] = { "0 (unknown)", "PMI", "INIT",
+ "Corrected Platform Error" };
+
+static void
+acpi_print_madt(ACPI_SUBTABLE_HEADER *mp)
+{
+ ACPI_MADT_LOCAL_APIC *lapic;
+ ACPI_MADT_IO_APIC *ioapic;
+ ACPI_MADT_INTERRUPT_OVERRIDE *over;
+ ACPI_MADT_NMI_SOURCE *nmi;
+ ACPI_MADT_LOCAL_APIC_NMI *lapic_nmi;
+ ACPI_MADT_LOCAL_APIC_OVERRIDE *lapic_over;
+ ACPI_MADT_IO_SAPIC *iosapic;
+ ACPI_MADT_LOCAL_SAPIC *lsapic;
+ ACPI_MADT_INTERRUPT_SOURCE *isrc;
+ ACPI_MADT_LOCAL_X2APIC *x2apic;
+ ACPI_MADT_LOCAL_X2APIC_NMI *x2apic_nmi;
+
+ if (mp->Type < sizeof(apic_types) / sizeof(apic_types[0]))
+ printf("\tType=%s\n", apic_types[mp->Type]);
+ else
+ printf("\tType=%d (unknown)\n", mp->Type);
+ switch (mp->Type) {
+ case ACPI_MADT_TYPE_LOCAL_APIC:
+ lapic = (ACPI_MADT_LOCAL_APIC *)mp;
+ acpi_print_cpu(lapic->ProcessorId);
+ acpi_print_local_apic(lapic->Id, lapic->LapicFlags);
+ break;
+ case ACPI_MADT_TYPE_IO_APIC:
+ ioapic = (ACPI_MADT_IO_APIC *)mp;
+ acpi_print_io_apic(ioapic->Id, ioapic->GlobalIrqBase,
+ ioapic->Address);
+ break;
+ case ACPI_MADT_TYPE_INTERRUPT_OVERRIDE:
+ over = (ACPI_MADT_INTERRUPT_OVERRIDE *)mp;
+ printf("\tBUS=%d\n", (u_int)over->Bus);
+ printf("\tIRQ=%d\n", (u_int)over->SourceIrq);
+ acpi_print_intr(over->GlobalIrq, over->IntiFlags);
+ break;
+ case ACPI_MADT_TYPE_NMI_SOURCE:
+ nmi = (ACPI_MADT_NMI_SOURCE *)mp;
+ acpi_print_intr(nmi->GlobalIrq, nmi->IntiFlags);
+ break;
+ case ACPI_MADT_TYPE_LOCAL_APIC_NMI:
+ lapic_nmi = (ACPI_MADT_LOCAL_APIC_NMI *)mp;
+ acpi_print_cpu(lapic_nmi->ProcessorId);
+ acpi_print_local_nmi(lapic_nmi->Lint, lapic_nmi->IntiFlags);
+ break;
+ case ACPI_MADT_TYPE_LOCAL_APIC_OVERRIDE:
+ lapic_over = (ACPI_MADT_LOCAL_APIC_OVERRIDE *)mp;
+ printf("\tLocal APIC ADDR=0x%016jx\n",
+ (uintmax_t)lapic_over->Address);
+ break;
+ case ACPI_MADT_TYPE_IO_SAPIC:
+ iosapic = (ACPI_MADT_IO_SAPIC *)mp;
+ acpi_print_io_apic(iosapic->Id, iosapic->GlobalIrqBase,
+ iosapic->Address);
+ break;
+ case ACPI_MADT_TYPE_LOCAL_SAPIC:
+ lsapic = (ACPI_MADT_LOCAL_SAPIC *)mp;
+ acpi_print_cpu(lsapic->ProcessorId);
+ acpi_print_local_apic(lsapic->Id, lsapic->LapicFlags);
+ printf("\tAPIC EID=%d\n", (u_int)lsapic->Eid);
+ if (mp->Length > __offsetof(ACPI_MADT_LOCAL_SAPIC, Uid))
+ acpi_print_cpu_uid(lsapic->Uid, lsapic->UidString);
+ break;
+ case ACPI_MADT_TYPE_INTERRUPT_SOURCE:
+ isrc = (ACPI_MADT_INTERRUPT_SOURCE *)mp;
+ if (isrc->Type < sizeof(platform_int_types) /
+ sizeof(platform_int_types[0]))
+ printf("\tType=%s\n", platform_int_types[isrc->Type]);
+ else
+ printf("\tType=%d (unknown)\n", isrc->Type);
+ printf("\tAPIC ID=%d\n", (u_int)isrc->Id);
+ printf("\tAPIC EID=%d\n", (u_int)isrc->Eid);
+ printf("\tSAPIC Vector=%d\n", (u_int)isrc->IoSapicVector);
+ acpi_print_intr(isrc->GlobalIrq, isrc->IntiFlags);
+ break;
+ case ACPI_MADT_TYPE_LOCAL_X2APIC:
+ x2apic = (ACPI_MADT_LOCAL_X2APIC *)mp;
+ acpi_print_cpu_uid(x2apic->Uid, NULL);
+ acpi_print_local_apic(x2apic->LocalApicId, x2apic->LapicFlags);
+ break;
+ case ACPI_MADT_TYPE_LOCAL_X2APIC_NMI:
+ x2apic_nmi = (ACPI_MADT_LOCAL_X2APIC_NMI *)mp;
+ acpi_print_cpu_uid(x2apic_nmi->Uid, NULL);
+ acpi_print_local_nmi(x2apic_nmi->Lint, x2apic_nmi->IntiFlags);
+ break;
+ }
+}
+
+static void
+acpi_handle_madt(ACPI_TABLE_HEADER *sdp)
+{
+ ACPI_TABLE_MADT *madt;
+
+ printf(BEGIN_COMMENT);
+ acpi_print_sdt(sdp);
+ madt = (ACPI_TABLE_MADT *)sdp;
+ printf("\tLocal APIC ADDR=0x%08x\n", madt->Address);
+ printf("\tFlags={");
+ if (madt->Flags & ACPI_MADT_PCAT_COMPAT)
+ printf("PC-AT");
+ printf("}\n");
+ acpi_walk_subtables(sdp, (madt + 1), acpi_print_madt);
+ printf(END_COMMENT);
+}
+
+static void
+acpi_handle_hpet(ACPI_TABLE_HEADER *sdp)
+{
+ ACPI_TABLE_HPET *hpet;
+
+ printf(BEGIN_COMMENT);
+ acpi_print_sdt(sdp);
+ hpet = (ACPI_TABLE_HPET *)sdp;
+ printf("\tHPET Number=%d\n", hpet->Sequence);
+ printf("\tADDR=");
+ acpi_print_gas(&hpet->Address);
+ printf("\tHW Rev=0x%x\n", hpet->Id & ACPI_HPET_ID_HARDWARE_REV_ID);
+ printf("\tComparators=%d\n", (hpet->Id & ACPI_HPET_ID_COMPARATORS) >>
+ 8);
+ printf("\tCounter Size=%d\n", hpet->Id & ACPI_HPET_ID_COUNT_SIZE_CAP ?
+ 1 : 0);
+ printf("\tLegacy IRQ routing capable={");
+ if (hpet->Id & ACPI_HPET_ID_LEGACY_CAPABLE)
+ printf("TRUE}\n");
+ else
+ printf("FALSE}\n");
+ printf("\tPCI Vendor ID=0x%04x\n", hpet->Id >> 16);
+ printf("\tMinimal Tick=%d\n", hpet->MinimumTick);
+ printf("\tFlags=0x%02x\n", hpet->Flags);
+ printf(END_COMMENT);
+}
+
+static void
+acpi_handle_ecdt(ACPI_TABLE_HEADER *sdp)
+{
+ ACPI_TABLE_ECDT *ecdt;
+
+ printf(BEGIN_COMMENT);
+ acpi_print_sdt(sdp);
+ ecdt = (ACPI_TABLE_ECDT *)sdp;
+ printf("\tEC_CONTROL=");
+ acpi_print_gas(&ecdt->Control);
+ printf("\n\tEC_DATA=");
+ acpi_print_gas(&ecdt->Data);
+ printf("\n\tUID=%#x, ", ecdt->Uid);
+ printf("GPE_BIT=%#x\n", ecdt->Gpe);
+ printf("\tEC_ID=%s\n", ecdt->Id);
+ printf(END_COMMENT);
+}
+
+static void
+acpi_handle_mcfg(ACPI_TABLE_HEADER *sdp)
+{
+ ACPI_TABLE_MCFG *mcfg;
+ ACPI_MCFG_ALLOCATION *alloc;
+ u_int i, entries;
+
+ printf(BEGIN_COMMENT);
+ acpi_print_sdt(sdp);
+ mcfg = (ACPI_TABLE_MCFG *)sdp;
+ entries = (sdp->Length - sizeof(ACPI_TABLE_MCFG)) /
+ sizeof(ACPI_MCFG_ALLOCATION);
+ alloc = (ACPI_MCFG_ALLOCATION *)(mcfg + 1);
+ for (i = 0; i < entries; i++, alloc++) {
+ printf("\n");
+ printf("\tBase Address=0x%016jx\n", (uintmax_t)alloc->Address);
+ printf("\tSegment Group=0x%04x\n", alloc->PciSegment);
+ printf("\tStart Bus=%d\n", alloc->StartBusNumber);
+ printf("\tEnd Bus=%d\n", alloc->EndBusNumber);
+ }
+ printf(END_COMMENT);
+}
+
+static void
+acpi_handle_slit(ACPI_TABLE_HEADER *sdp)
+{
+ ACPI_TABLE_SLIT *slit;
+ UINT64 i, j;
+
+ printf(BEGIN_COMMENT);
+ acpi_print_sdt(sdp);
+ slit = (ACPI_TABLE_SLIT *)sdp;
+ printf("\tLocality Count=%ju\n", (uintmax_t)slit->LocalityCount);
+ printf("\n\t ");
+ for (i = 0; i < slit->LocalityCount; i++)
+ printf(" %3ju", (uintmax_t)i);
+ printf("\n\t +");
+ for (i = 0; i < slit->LocalityCount; i++)
+ printf("----");
+ printf("\n");
+ for (i = 0; i < slit->LocalityCount; i++) {
+ printf("\t %3ju |", (uintmax_t)i);
+ for (j = 0; j < slit->LocalityCount; j++)
+ printf(" %3d",
+ slit->Entry[i * slit->LocalityCount + j]);
+ printf("\n");
+ }
+ printf(END_COMMENT);
+}
+
+static void
+acpi_print_srat_cpu(uint32_t apic_id, uint32_t proximity_domain,
+ uint32_t flags)
+{
+
+ printf("\tFlags={");
+ if (flags & ACPI_SRAT_CPU_ENABLED)
+ printf("ENABLED");
+ else
+ printf("DISABLED");
+ printf("}\n");
+ printf("\tAPIC ID=%d\n", apic_id);
+ printf("\tProximity Domain=%d\n", proximity_domain);
+}
+
+static char *
+acpi_tcpa_evname(struct TCPAevent *event)
+{
+ struct TCPApc_event *pc_event;
+ char *eventname = NULL;
+
+ pc_event = (struct TCPApc_event *)(event + 1);
+
+ switch(event->event_type) {
+ case PREBOOT:
+ case POST_CODE:
+ case UNUSED:
+ case NO_ACTION:
+ case SEPARATOR:
+ case SCRTM_CONTENTS:
+ case SCRTM_VERSION:
+ case CPU_MICROCODE:
+ case PLATFORM_CONFIG_FLAGS:
+ case TABLE_OF_DEVICES:
+ case COMPACT_HASH:
+ case IPL:
+ case IPL_PARTITION_DATA:
+ case NONHOST_CODE:
+ case NONHOST_CONFIG:
+ case NONHOST_INFO:
+ asprintf(&eventname, "%s",
+ tcpa_event_type_strings[event->event_type]);
+ break;
+
+ case ACTION:
+ eventname = calloc(event->event_size + 1, sizeof(char));
+ memcpy(eventname, pc_event, event->event_size);
+ break;
+
+ case EVENT_TAG:
+ switch (pc_event->event_id) {
+ case SMBIOS:
+ case BIS_CERT:
+ case CMOS:
+ case NVRAM:
+ case OPTION_ROM_EXEC:
+ case OPTION_ROM_CONFIG:
+ case S_CRTM_VERSION:
+ case POST_BIOS_ROM:
+ case ESCD:
+ case OPTION_ROM_MICROCODE:
+ case S_CRTM_CONTENTS:
+ case POST_CONTENTS:
+ asprintf(&eventname, "%s",
+ TCPA_pcclient_strings[pc_event->event_id]);
+ break;
+
+ default:
+ asprintf(&eventname, "<unknown tag 0x%02x>",
+ pc_event->event_id);
+ break;
+ }
+ break;
+
+ default:
+ asprintf(&eventname, "<unknown 0x%02x>", event->event_type);
+ break;
+ }
+
+ return eventname;
+}
+
+static void
+acpi_print_tcpa(struct TCPAevent *event)
+{
+ int i;
+ char *eventname;
+
+ eventname = acpi_tcpa_evname(event);
+
+ printf("\t%d", event->pcr_index);
+ printf(" 0x");
+ for (i = 0; i < 20; i++)
+ printf("%02x", event->pcr_value[i]);
+ printf(" [%s]\n", eventname ? eventname : "<unknown>");
+
+ free(eventname);
+}
+
+static void
+acpi_handle_tcpa(ACPI_TABLE_HEADER *sdp)
+{
+ struct TCPAbody *tcpa;
+ struct TCPAevent *event;
+ uintmax_t len, paddr;
+ unsigned char *vaddr = NULL;
+ unsigned char *vend = NULL;
+
+ printf(BEGIN_COMMENT);
+ acpi_print_sdt(sdp);
+ tcpa = (struct TCPAbody *) sdp;
+
+ switch (tcpa->platform_class) {
+ case ACPI_TCPA_BIOS_CLIENT:
+ len = tcpa->client.log_max_len;
+ paddr = tcpa->client.log_start_addr;
+ break;
+
+ case ACPI_TCPA_BIOS_SERVER:
+ len = tcpa->server.log_max_len;
+ paddr = tcpa->server.log_start_addr;
+ break;
+
+ default:
+ printf("XXX");
+ printf(END_COMMENT);
+ return;
+ }
+ printf("\tClass %u Base Address 0x%jx Length %ju\n\n",
+ tcpa->platform_class, paddr, len);
+
+ if (len == 0) {
+ printf("\tEmpty TCPA table\n");
+ printf(END_COMMENT);
+ return;
+ }
+ if(sdp->Revision == 1){
+ printf("\tOLD TCPA spec log found. Dumping not supported.\n");
+ printf(END_COMMENT);
+ return;
+ }
+
+ vaddr = (unsigned char *)acpi_map_physical(paddr, len);
+ vend = vaddr + len;
+
+ while (vaddr != NULL) {
+ if ((vaddr + sizeof(struct TCPAevent) >= vend)||
+ (vaddr + sizeof(struct TCPAevent) < vaddr))
+ break;
+ event = (struct TCPAevent *)(void *)vaddr;
+ if (vaddr + event->event_size >= vend)
+ break;
+ if (vaddr + event->event_size < vaddr)
+ break;
+ if (event->event_type == 0 && event->event_size == 0)
+ break;
+#if 0
+ {
+ unsigned int i, j, k;
+
+ printf("\n\tsize %d\n\t\t%p ", event->event_size, vaddr);
+ for (j = 0, i = 0; i <
+ sizeof(struct TCPAevent) + event->event_size; i++) {
+ printf("%02x ", vaddr[i]);
+ if ((i+1) % 8 == 0) {
+ for (k = 0; k < 8; k++)
+ printf("%c", isprint(vaddr[j+k]) ?
+ vaddr[j+k] : '.');
+ printf("\n\t\t%p ", &vaddr[i + 1]);
+ j = i + 1;
+ }
+ }
+ printf("\n"); }
+#endif
+ acpi_print_tcpa(event);
+
+ vaddr += sizeof(struct TCPAevent) + event->event_size;
+ }
+
+ printf(END_COMMENT);
+}
+
+static const char *
+devscope_type2str(int type)
+{
+ static char typebuf[16];
+
+ switch (type) {
+ case 1:
+ return ("PCI Endpoint Device");
+ case 2:
+ return ("PCI Sub-Hierarchy");
+ case 3:
+ return ("IOAPIC");
+ case 4:
+ return ("HPET");
+ default:
+ snprintf(typebuf, sizeof(typebuf), "%d", type);
+ return (typebuf);
+ }
+}
+
+static int
+acpi_handle_dmar_devscope(void *addr, int remaining)
+{
+ char sep;
+ int pathlen;
+ ACPI_DMAR_PCI_PATH *path, *pathend;
+ ACPI_DMAR_DEVICE_SCOPE *devscope = addr;
+
+ if (remaining < (int)sizeof(ACPI_DMAR_DEVICE_SCOPE))
+ return (-1);
+
+ if (remaining < devscope->Length)
+ return (-1);
+
+ printf("\n");
+ printf("\t\tType=%s\n", devscope_type2str(devscope->EntryType));
+ printf("\t\tLength=%d\n", devscope->Length);
+ printf("\t\tEnumerationId=%d\n", devscope->EnumerationId);
+ printf("\t\tStartBusNumber=%d\n", devscope->Bus);
+
+ path = (ACPI_DMAR_PCI_PATH *)(devscope + 1);
+ pathlen = devscope->Length - sizeof(ACPI_DMAR_DEVICE_SCOPE);
+ pathend = path + pathlen / sizeof(ACPI_DMAR_PCI_PATH);
+ if (path < pathend) {
+ sep = '{';
+ printf("\t\tPath=");
+ do {
+ printf("%c%d:%d", sep, path->Device, path->Function);
+ sep=',';
+ path++;
+ } while (path < pathend);
+ printf("}\n");
+ }
+
+ return (devscope->Length);
+}
+
+static void
+acpi_handle_dmar_drhd(ACPI_DMAR_HARDWARE_UNIT *drhd)
+{
+ char *cp;
+ int remaining, consumed;
+
+ printf("\n");
+ printf("\tType=DRHD\n");
+ printf("\tLength=%d\n", drhd->Header.Length);
+
+#define PRINTFLAG(var, flag) printflag((var), ACPI_DMAR_## flag, #flag)
+
+ printf("\tFlags=");
+ PRINTFLAG(drhd->Flags, INCLUDE_ALL);
+ PRINTFLAG_END();
+
+#undef PRINTFLAG
+
+ printf("\tSegment=%d\n", drhd->Segment);
+ printf("\tAddress=0x%016jx\n", (uintmax_t)drhd->Address);
+
+ remaining = drhd->Header.Length - sizeof(ACPI_DMAR_HARDWARE_UNIT);
+ if (remaining > 0)
+ printf("\tDevice Scope:");
+ while (remaining > 0) {
+ cp = (char *)drhd + drhd->Header.Length - remaining;
+ consumed = acpi_handle_dmar_devscope(cp, remaining);
+ if (consumed <= 0)
+ break;
+ else
+ remaining -= consumed;
+ }
+}
+
+static void
+acpi_handle_dmar_rmrr(ACPI_DMAR_RESERVED_MEMORY *rmrr)
+{
+ char *cp;
+ int remaining, consumed;
+
+ printf("\n");
+ printf("\tType=RMRR\n");
+ printf("\tLength=%d\n", rmrr->Header.Length);
+ printf("\tSegment=%d\n", rmrr->Segment);
+ printf("\tBaseAddress=0x%016jx\n", (uintmax_t)rmrr->BaseAddress);
+ printf("\tLimitAddress=0x%016jx\n", (uintmax_t)rmrr->EndAddress);
+
+ remaining = rmrr->Header.Length - sizeof(ACPI_DMAR_RESERVED_MEMORY);
+ if (remaining > 0)
+ printf("\tDevice Scope:");
+ while (remaining > 0) {
+ cp = (char *)rmrr + rmrr->Header.Length - remaining;
+ consumed = acpi_handle_dmar_devscope(cp, remaining);
+ if (consumed <= 0)
+ break;
+ else
+ remaining -= consumed;
+ }
+}
+
+static void
+acpi_handle_dmar_atsr(ACPI_DMAR_ATSR *atsr)
+{
+ char *cp;
+ int remaining, consumed;
+
+ printf("\n");
+ printf("\tType=ATSR\n");
+ printf("\tLength=%d\n", atsr->Header.Length);
+
+#define PRINTFLAG(var, flag) printflag((var), ACPI_DMAR_## flag, #flag)
+
+ printf("\tFlags=");
+ PRINTFLAG(atsr->Flags, ALL_PORTS);
+ PRINTFLAG_END();
+
+#undef PRINTFLAG
+
+ printf("\tSegment=%d\n", atsr->Segment);
+
+ remaining = atsr->Header.Length - sizeof(ACPI_DMAR_ATSR);
+ if (remaining > 0)
+ printf("\tDevice Scope:");
+ while (remaining > 0) {
+ cp = (char *)atsr + atsr->Header.Length - remaining;
+ consumed = acpi_handle_dmar_devscope(cp, remaining);
+ if (consumed <= 0)
+ break;
+ else
+ remaining -= consumed;
+ }
+}
+
+static void
+acpi_handle_dmar_rhsa(ACPI_DMAR_RHSA *rhsa)
+{
+
+ printf("\n");
+ printf("\tType=RHSA\n");
+ printf("\tLength=%d\n", rhsa->Header.Length);
+ printf("\tBaseAddress=0x%016jx\n", (uintmax_t)rhsa->BaseAddress);
+ printf("\tProximityDomain=0x%08x\n", rhsa->ProximityDomain);
+}
+
+static int
+acpi_handle_dmar_remapping_structure(void *addr, int remaining)
+{
+ ACPI_DMAR_HEADER *hdr = addr;
+
+ if (remaining < (int)sizeof(ACPI_DMAR_HEADER))
+ return (-1);
+
+ if (remaining < hdr->Length)
+ return (-1);
+
+ switch (hdr->Type) {
+ case ACPI_DMAR_TYPE_HARDWARE_UNIT:
+ acpi_handle_dmar_drhd(addr);
+ break;
+ case ACPI_DMAR_TYPE_RESERVED_MEMORY:
+ acpi_handle_dmar_rmrr(addr);
+ break;
+ case ACPI_DMAR_TYPE_ROOT_ATS:
+ acpi_handle_dmar_atsr(addr);
+ break;
+ case ACPI_DMAR_TYPE_HARDWARE_AFFINITY:
+ acpi_handle_dmar_rhsa(addr);
+ break;
+ default:
+ printf("\n");
+ printf("\tType=%d\n", hdr->Type);
+ printf("\tLength=%d\n", hdr->Length);
+ break;
+ }
+ return (hdr->Length);
+}
+
+#ifndef ACPI_DMAR_X2APIC_OPT_OUT
+#define ACPI_DMAR_X2APIC_OPT_OUT (0x2)
+#endif
+
+static void
+acpi_handle_dmar(ACPI_TABLE_HEADER *sdp)
+{
+ char *cp;
+ int remaining, consumed;
+ ACPI_TABLE_DMAR *dmar;
+
+ printf(BEGIN_COMMENT);
+ acpi_print_sdt(sdp);
+ dmar = (ACPI_TABLE_DMAR *)sdp;
+ printf("\tHost Address Width=%d\n", dmar->Width + 1);
+
+#define PRINTFLAG(var, flag) printflag((var), ACPI_DMAR_## flag, #flag)
+
+ printf("\tFlags=");
+ PRINTFLAG(dmar->Flags, INTR_REMAP);
+ PRINTFLAG(dmar->Flags, X2APIC_OPT_OUT);
+ PRINTFLAG_END();
+
+#undef PRINTFLAG
+
+ remaining = sdp->Length - sizeof(ACPI_TABLE_DMAR);
+ while (remaining > 0) {
+ cp = (char *)sdp + sdp->Length - remaining;
+ consumed = acpi_handle_dmar_remapping_structure(cp, remaining);
+ if (consumed <= 0)
+ break;
+ else
+ remaining -= consumed;
+ }
+
+ printf(END_COMMENT);
+}
+
+static void
+acpi_print_srat_memory(ACPI_SRAT_MEM_AFFINITY *mp)
+{
+
+ printf("\tFlags={");
+ if (mp->Flags & ACPI_SRAT_MEM_ENABLED)
+ printf("ENABLED");
+ else
+ printf("DISABLED");
+ if (mp->Flags & ACPI_SRAT_MEM_HOT_PLUGGABLE)
+ printf(",HOT_PLUGGABLE");
+ if (mp->Flags & ACPI_SRAT_MEM_NON_VOLATILE)
+ printf(",NON_VOLATILE");
+ printf("}\n");
+ printf("\tBase Address=0x%016jx\n", (uintmax_t)mp->BaseAddress);
+ printf("\tLength=0x%016jx\n", (uintmax_t)mp->Length);
+ printf("\tProximity Domain=%d\n", mp->ProximityDomain);
+}
+
+static const char *srat_types[] = { "CPU", "Memory", "X2APIC" };
+
+static void
+acpi_print_srat(ACPI_SUBTABLE_HEADER *srat)
+{
+ ACPI_SRAT_CPU_AFFINITY *cpu;
+ ACPI_SRAT_X2APIC_CPU_AFFINITY *x2apic;
+
+ if (srat->Type < sizeof(srat_types) / sizeof(srat_types[0]))
+ printf("\tType=%s\n", srat_types[srat->Type]);
+ else
+ printf("\tType=%d (unknown)\n", srat->Type);
+ switch (srat->Type) {
+ case ACPI_SRAT_TYPE_CPU_AFFINITY:
+ cpu = (ACPI_SRAT_CPU_AFFINITY *)srat;
+ acpi_print_srat_cpu(cpu->ApicId,
+ cpu->ProximityDomainHi[2] << 24 |
+ cpu->ProximityDomainHi[1] << 16 |
+ cpu->ProximityDomainHi[0] << 0 |
+ cpu->ProximityDomainLo, cpu->Flags);
+ break;
+ case ACPI_SRAT_TYPE_MEMORY_AFFINITY:
+ acpi_print_srat_memory((ACPI_SRAT_MEM_AFFINITY *)srat);
+ break;
+ case ACPI_SRAT_TYPE_X2APIC_CPU_AFFINITY:
+ x2apic = (ACPI_SRAT_X2APIC_CPU_AFFINITY *)srat;
+ acpi_print_srat_cpu(x2apic->ApicId, x2apic->ProximityDomain,
+ x2apic->Flags);
+ break;
+ }
+}
+
+static void
+acpi_handle_srat(ACPI_TABLE_HEADER *sdp)
+{
+ ACPI_TABLE_SRAT *srat;
+
+ printf(BEGIN_COMMENT);
+ acpi_print_sdt(sdp);
+ srat = (ACPI_TABLE_SRAT *)sdp;
+ printf("\tTable Revision=%d\n", srat->TableRevision);
+ acpi_walk_subtables(sdp, (srat + 1), acpi_print_srat);
+ printf(END_COMMENT);
+}
+
+static void
+acpi_print_sdt(ACPI_TABLE_HEADER *sdp)
+{
+ printf(" ");
+ acpi_print_string(sdp->Signature, ACPI_NAME_SIZE);
+ printf(": Length=%d, Revision=%d, Checksum=%d,\n",
+ sdp->Length, sdp->Revision, sdp->Checksum);
+ printf("\tOEMID=");
+ acpi_print_string(sdp->OemId, ACPI_OEM_ID_SIZE);
+ printf(", OEM Table ID=");
+ acpi_print_string(sdp->OemTableId, ACPI_OEM_TABLE_ID_SIZE);
+ printf(", OEM Revision=0x%x,\n", sdp->OemRevision);
+ printf("\tCreator ID=");
+ acpi_print_string(sdp->AslCompilerId, ACPI_NAME_SIZE);
+ printf(", Creator Revision=0x%x\n", sdp->AslCompilerRevision);
+}
+
+static void
+acpi_print_rsdt(ACPI_TABLE_HEADER *rsdp)
+{
+ ACPI_TABLE_RSDT *rsdt;
+ ACPI_TABLE_XSDT *xsdt;
+ int i, entries;
+
+ rsdt = (ACPI_TABLE_RSDT *)rsdp;
+ xsdt = (ACPI_TABLE_XSDT *)rsdp;
+ printf(BEGIN_COMMENT);
+ acpi_print_sdt(rsdp);
+ entries = (rsdp->Length - sizeof(ACPI_TABLE_HEADER)) / addr_size;
+ printf("\tEntries={ ");
+ for (i = 0; i < entries; i++) {
+ if (i > 0)
+ printf(", ");
+ if (addr_size == 4)
+ printf("0x%08x", le32toh(rsdt->TableOffsetEntry[i]));
+ else
+ printf("0x%016jx",
+ (uintmax_t)le64toh(xsdt->TableOffsetEntry[i]));
+ }
+ printf(" }\n");
+ printf(END_COMMENT);
+}
+
+static const char *acpi_pm_profiles[] = {
+ "Unspecified", "Desktop", "Mobile", "Workstation",
+ "Enterprise Server", "SOHO Server", "Appliance PC"
+};
+
+static void
+acpi_print_fadt(ACPI_TABLE_HEADER *sdp)
+{
+ ACPI_TABLE_FADT *fadt;
+ const char *pm;
+
+ fadt = (ACPI_TABLE_FADT *)sdp;
+ printf(BEGIN_COMMENT);
+ acpi_print_sdt(sdp);
+ printf(" \tFACS=0x%x, DSDT=0x%x\n", fadt->Facs,
+ fadt->Dsdt);
+ printf("\tINT_MODEL=%s\n", fadt->Model ? "APIC" : "PIC");
+ if (fadt->PreferredProfile >= sizeof(acpi_pm_profiles) / sizeof(char *))
+ pm = "Reserved";
+ else
+ pm = acpi_pm_profiles[fadt->PreferredProfile];
+ printf("\tPreferred_PM_Profile=%s (%d)\n", pm, fadt->PreferredProfile);
+ printf("\tSCI_INT=%d\n", fadt->SciInterrupt);
+ printf("\tSMI_CMD=0x%x, ", fadt->SmiCommand);
+ printf("ACPI_ENABLE=0x%x, ", fadt->AcpiEnable);
+ printf("ACPI_DISABLE=0x%x, ", fadt->AcpiDisable);
+ printf("S4BIOS_REQ=0x%x\n", fadt->S4BiosRequest);
+ printf("\tPSTATE_CNT=0x%x\n", fadt->PstateControl);
+ printf("\tPM1a_EVT_BLK=0x%x-0x%x\n",
+ fadt->Pm1aEventBlock,
+ fadt->Pm1aEventBlock + fadt->Pm1EventLength - 1);
+ if (fadt->Pm1bEventBlock != 0)
+ printf("\tPM1b_EVT_BLK=0x%x-0x%x\n",
+ fadt->Pm1bEventBlock,
+ fadt->Pm1bEventBlock + fadt->Pm1EventLength - 1);
+ printf("\tPM1a_CNT_BLK=0x%x-0x%x\n",
+ fadt->Pm1aControlBlock,
+ fadt->Pm1aControlBlock + fadt->Pm1ControlLength - 1);
+ if (fadt->Pm1bControlBlock != 0)
+ printf("\tPM1b_CNT_BLK=0x%x-0x%x\n",
+ fadt->Pm1bControlBlock,
+ fadt->Pm1bControlBlock + fadt->Pm1ControlLength - 1);
+ if (fadt->Pm2ControlBlock != 0)
+ printf("\tPM2_CNT_BLK=0x%x-0x%x\n",
+ fadt->Pm2ControlBlock,
+ fadt->Pm2ControlBlock + fadt->Pm2ControlLength - 1);
+ printf("\tPM_TMR_BLK=0x%x-0x%x\n",
+ fadt->PmTimerBlock,
+ fadt->PmTimerBlock + fadt->PmTimerLength - 1);
+ if (fadt->Gpe0Block != 0)
+ printf("\tGPE0_BLK=0x%x-0x%x\n",
+ fadt->Gpe0Block,
+ fadt->Gpe0Block + fadt->Gpe0BlockLength - 1);
+ if (fadt->Gpe1Block != 0)
+ printf("\tGPE1_BLK=0x%x-0x%x, GPE1_BASE=%d\n",
+ fadt->Gpe1Block,
+ fadt->Gpe1Block + fadt->Gpe1BlockLength - 1,
+ fadt->Gpe1Base);
+ if (fadt->CstControl != 0)
+ printf("\tCST_CNT=0x%x\n", fadt->CstControl);
+ printf("\tP_LVL2_LAT=%d us, P_LVL3_LAT=%d us\n",
+ fadt->C2Latency, fadt->C3Latency);
+ printf("\tFLUSH_SIZE=%d, FLUSH_STRIDE=%d\n",
+ fadt->FlushSize, fadt->FlushStride);
+ printf("\tDUTY_OFFSET=%d, DUTY_WIDTH=%d\n",
+ fadt->DutyOffset, fadt->DutyWidth);
+ printf("\tDAY_ALRM=%d, MON_ALRM=%d, CENTURY=%d\n",
+ fadt->DayAlarm, fadt->MonthAlarm, fadt->Century);
+
+#define PRINTFLAG(var, flag) printflag((var), ACPI_FADT_## flag, #flag)
+
+ printf("\tIAPC_BOOT_ARCH=");
+ PRINTFLAG(fadt->BootFlags, LEGACY_DEVICES);
+ PRINTFLAG(fadt->BootFlags, 8042);
+ PRINTFLAG(fadt->BootFlags, NO_VGA);
+ PRINTFLAG(fadt->BootFlags, NO_MSI);
+ PRINTFLAG(fadt->BootFlags, NO_ASPM);
+ PRINTFLAG_END();
+
+ printf("\tFlags=");
+ PRINTFLAG(fadt->Flags, WBINVD);
+ PRINTFLAG(fadt->Flags, WBINVD_FLUSH);
+ PRINTFLAG(fadt->Flags, C1_SUPPORTED);
+ PRINTFLAG(fadt->Flags, C2_MP_SUPPORTED);
+ PRINTFLAG(fadt->Flags, POWER_BUTTON);
+ PRINTFLAG(fadt->Flags, SLEEP_BUTTON);
+ PRINTFLAG(fadt->Flags, FIXED_RTC);
+ PRINTFLAG(fadt->Flags, S4_RTC_WAKE);
+ PRINTFLAG(fadt->Flags, 32BIT_TIMER);
+ PRINTFLAG(fadt->Flags, DOCKING_SUPPORTED);
+ PRINTFLAG(fadt->Flags, RESET_REGISTER);
+ PRINTFLAG(fadt->Flags, SEALED_CASE);
+ PRINTFLAG(fadt->Flags, HEADLESS);
+ PRINTFLAG(fadt->Flags, SLEEP_TYPE);
+ PRINTFLAG(fadt->Flags, PCI_EXPRESS_WAKE);
+ PRINTFLAG(fadt->Flags, PLATFORM_CLOCK);
+ PRINTFLAG(fadt->Flags, S4_RTC_VALID);
+ PRINTFLAG(fadt->Flags, REMOTE_POWER_ON);
+ PRINTFLAG(fadt->Flags, APIC_CLUSTER);
+ PRINTFLAG(fadt->Flags, APIC_PHYSICAL);
+ PRINTFLAG_END();
+
+#undef PRINTFLAG
+
+ if (fadt->Flags & ACPI_FADT_RESET_REGISTER) {
+ printf("\tRESET_REG=");
+ acpi_print_gas(&fadt->ResetRegister);
+ printf(", RESET_VALUE=%#x\n", fadt->ResetValue);
+ }
+ if (acpi_get_fadt_revision(fadt) > 1) {
+ printf("\tX_FACS=0x%016jx, ", (uintmax_t)fadt->XFacs);
+ printf("X_DSDT=0x%016jx\n", (uintmax_t)fadt->XDsdt);
+ printf("\tX_PM1a_EVT_BLK=");
+ acpi_print_gas(&fadt->XPm1aEventBlock);
+ if (fadt->XPm1bEventBlock.Address != 0) {
+ printf("\n\tX_PM1b_EVT_BLK=");
+ acpi_print_gas(&fadt->XPm1bEventBlock);
+ }
+ printf("\n\tX_PM1a_CNT_BLK=");
+ acpi_print_gas(&fadt->XPm1aControlBlock);
+ if (fadt->XPm1bControlBlock.Address != 0) {
+ printf("\n\tX_PM1b_CNT_BLK=");
+ acpi_print_gas(&fadt->XPm1bControlBlock);
+ }
+ if (fadt->XPm2ControlBlock.Address != 0) {
+ printf("\n\tX_PM2_CNT_BLK=");
+ acpi_print_gas(&fadt->XPm2ControlBlock);
+ }
+ printf("\n\tX_PM_TMR_BLK=");
+ acpi_print_gas(&fadt->XPmTimerBlock);
+ if (fadt->XGpe0Block.Address != 0) {
+ printf("\n\tX_GPE0_BLK=");
+ acpi_print_gas(&fadt->XGpe0Block);
+ }
+ if (fadt->XGpe1Block.Address != 0) {
+ printf("\n\tX_GPE1_BLK=");
+ acpi_print_gas(&fadt->XGpe1Block);
+ }
+ printf("\n");
+ }
+
+ printf(END_COMMENT);
+}
+
+static void
+acpi_print_facs(ACPI_TABLE_FACS *facs)
+{
+ printf(BEGIN_COMMENT);
+ printf(" FACS:\tLength=%u, ", facs->Length);
+ printf("HwSig=0x%08x, ", facs->HardwareSignature);
+ printf("Firm_Wake_Vec=0x%08x\n", facs->FirmwareWakingVector);
+
+ printf("\tGlobal_Lock=");
+ if (facs->GlobalLock != 0) {
+ if (facs->GlobalLock & ACPI_GLOCK_PENDING)
+ printf("PENDING,");
+ if (facs->GlobalLock & ACPI_GLOCK_OWNED)
+ printf("OWNED");
+ }
+ printf("\n");
+
+ printf("\tFlags=");
+ if (facs->Flags & ACPI_FACS_S4_BIOS_PRESENT)
+ printf("S4BIOS");
+ printf("\n");
+
+ if (facs->XFirmwareWakingVector != 0)
+ printf("\tX_Firm_Wake_Vec=%016jx\n",
+ (uintmax_t)facs->XFirmwareWakingVector);
+ printf("\tVersion=%u\n", facs->Version);
+
+ printf(END_COMMENT);
+}
+
+static void
+acpi_print_dsdt(ACPI_TABLE_HEADER *dsdp)
+{
+ printf(BEGIN_COMMENT);
+ acpi_print_sdt(dsdp);
+ printf(END_COMMENT);
+}
+
+int
+acpi_checksum(void *p, size_t length)
+{
+ uint8_t *bp;
+ uint8_t sum;
+
+ bp = p;
+ sum = 0;
+ while (length--)
+ sum += *bp++;
+
+ return (sum);
+}
+
+static ACPI_TABLE_HEADER *
+acpi_map_sdt(vm_offset_t pa)
+{
+ ACPI_TABLE_HEADER *sp;
+
+ sp = acpi_map_physical(pa, sizeof(ACPI_TABLE_HEADER));
+ sp = acpi_map_physical(pa, sp->Length);
+ return (sp);
+}
+
+static void
+acpi_print_rsd_ptr(ACPI_TABLE_RSDP *rp)
+{
+ printf(BEGIN_COMMENT);
+ printf(" RSD PTR: OEM=");
+ acpi_print_string(rp->OemId, ACPI_OEM_ID_SIZE);
+ printf(", ACPI_Rev=%s (%d)\n", rp->Revision < 2 ? "1.0x" : "2.0x",
+ rp->Revision);
+ if (rp->Revision < 2) {
+ printf("\tRSDT=0x%08x, cksum=%u\n", rp->RsdtPhysicalAddress,
+ rp->Checksum);
+ } else {
+ printf("\tXSDT=0x%016jx, length=%u, cksum=%u\n",
+ (uintmax_t)rp->XsdtPhysicalAddress, rp->Length,
+ rp->ExtendedChecksum);
+ }
+ printf(END_COMMENT);
+}
+
+static void
+acpi_handle_rsdt(ACPI_TABLE_HEADER *rsdp)
+{
+ ACPI_TABLE_HEADER *sdp;
+ ACPI_TABLE_RSDT *rsdt;
+ ACPI_TABLE_XSDT *xsdt;
+ vm_offset_t addr;
+ int entries, i;
+
+ acpi_print_rsdt(rsdp);
+ rsdt = (ACPI_TABLE_RSDT *)rsdp;
+ xsdt = (ACPI_TABLE_XSDT *)rsdp;
+ entries = (rsdp->Length - sizeof(ACPI_TABLE_HEADER)) / addr_size;
+ for (i = 0; i < entries; i++) {
+ if (addr_size == 4)
+ addr = le32toh(rsdt->TableOffsetEntry[i]);
+ else
+ addr = le64toh(xsdt->TableOffsetEntry[i]);
+ if (addr == 0)
+ continue;
+ sdp = (ACPI_TABLE_HEADER *)acpi_map_sdt(addr);
+ if (acpi_checksum(sdp, sdp->Length)) {
+ warnx("RSDT entry %d (sig %.4s) is corrupt", i,
+ sdp->Signature);
+ continue;
+ }
+ if (!memcmp(sdp->Signature, ACPI_SIG_FADT, 4))
+ acpi_handle_fadt(sdp);
+ else if (!memcmp(sdp->Signature, ACPI_SIG_MADT, 4))
+ acpi_handle_madt(sdp);
+ else if (!memcmp(sdp->Signature, ACPI_SIG_HPET, 4))
+ acpi_handle_hpet(sdp);
+ else if (!memcmp(sdp->Signature, ACPI_SIG_ECDT, 4))
+ acpi_handle_ecdt(sdp);
+ else if (!memcmp(sdp->Signature, ACPI_SIG_MCFG, 4))
+ acpi_handle_mcfg(sdp);
+ else if (!memcmp(sdp->Signature, ACPI_SIG_SLIT, 4))
+ acpi_handle_slit(sdp);
+ else if (!memcmp(sdp->Signature, ACPI_SIG_SRAT, 4))
+ acpi_handle_srat(sdp);
+ else if (!memcmp(sdp->Signature, ACPI_SIG_TCPA, 4))
+ acpi_handle_tcpa(sdp);
+ else if (!memcmp(sdp->Signature, ACPI_SIG_DMAR, 4))
+ acpi_handle_dmar(sdp);
+ else {
+ printf(BEGIN_COMMENT);
+ acpi_print_sdt(sdp);
+ printf(END_COMMENT);
+ }
+ }
+}
+
+ACPI_TABLE_HEADER *
+sdt_load_devmem(void)
+{
+ ACPI_TABLE_RSDP *rp;
+ ACPI_TABLE_HEADER *rsdp;
+
+ rp = acpi_find_rsd_ptr();
+ if (!rp)
+ errx(1, "Can't find ACPI information");
+
+ if (tflag)
+ acpi_print_rsd_ptr(rp);
+ if (rp->Revision < 2) {
+ rsdp = (ACPI_TABLE_HEADER *)acpi_map_sdt(rp->RsdtPhysicalAddress);
+ if (memcmp(rsdp->Signature, "RSDT", 4) != 0 ||
+ acpi_checksum(rsdp, rsdp->Length) != 0)
+ errx(1, "RSDT is corrupted");
+ addr_size = sizeof(uint32_t);
+ } else {
+ rsdp = (ACPI_TABLE_HEADER *)acpi_map_sdt(rp->XsdtPhysicalAddress);
+ if (memcmp(rsdp->Signature, "XSDT", 4) != 0 ||
+ acpi_checksum(rsdp, rsdp->Length) != 0)
+ errx(1, "XSDT is corrupted");
+ addr_size = sizeof(uint64_t);
+ }
+ return (rsdp);
+}
+
+/* Write the DSDT to a file, concatenating any SSDTs (if present). */
+static int
+write_dsdt(int fd, ACPI_TABLE_HEADER *rsdt, ACPI_TABLE_HEADER *dsdt)
+{
+ ACPI_TABLE_HEADER sdt;
+ ACPI_TABLE_HEADER *ssdt;
+ uint8_t sum;
+
+ /* Create a new checksum to account for the DSDT and any SSDTs. */
+ sdt = *dsdt;
+ if (rsdt != NULL) {
+ sdt.Checksum = 0;
+ sum = acpi_checksum(dsdt + 1, dsdt->Length -
+ sizeof(ACPI_TABLE_HEADER));
+ ssdt = sdt_from_rsdt(rsdt, ACPI_SIG_SSDT, NULL);
+ while (ssdt != NULL) {
+ sdt.Length += ssdt->Length - sizeof(ACPI_TABLE_HEADER);
+ sum += acpi_checksum(ssdt + 1,
+ ssdt->Length - sizeof(ACPI_TABLE_HEADER));
+ ssdt = sdt_from_rsdt(rsdt, ACPI_SIG_SSDT, ssdt);
+ }
+ sum += acpi_checksum(&sdt, sizeof(ACPI_TABLE_HEADER));
+ sdt.Checksum -= sum;
+ }
+
+ /* Write out the DSDT header and body. */
+ write(fd, &sdt, sizeof(ACPI_TABLE_HEADER));
+ write(fd, dsdt + 1, dsdt->Length - sizeof(ACPI_TABLE_HEADER));
+
+ /* Write out any SSDTs (if present.) */
+ if (rsdt != NULL) {
+ ssdt = sdt_from_rsdt(rsdt, "SSDT", NULL);
+ while (ssdt != NULL) {
+ write(fd, ssdt + 1, ssdt->Length -
+ sizeof(ACPI_TABLE_HEADER));
+ ssdt = sdt_from_rsdt(rsdt, "SSDT", ssdt);
+ }
+ }
+ return (0);
+}
+
+void
+dsdt_save_file(char *outfile, ACPI_TABLE_HEADER *rsdt, ACPI_TABLE_HEADER *dsdp)
+{
+ int fd;
+ mode_t mode;
+
+ assert(outfile != NULL);
+ mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+ fd = open(outfile, O_WRONLY | O_CREAT | O_TRUNC, mode);
+ if (fd == -1) {
+ perror("dsdt_save_file");
+ return;
+ }
+ write_dsdt(fd, rsdt, dsdp);
+ close(fd);
+}
+
+void
+aml_disassemble(ACPI_TABLE_HEADER *rsdt, ACPI_TABLE_HEADER *dsdp)
+{
+ char buf[PATH_MAX], tmpstr[PATH_MAX];
+ const char *tmpdir;
+ char *tmpext;
+ FILE *fp;
+ size_t len;
+ int fd;
+
+ tmpdir = getenv("TMPDIR");
+ if (tmpdir == NULL)
+ tmpdir = _PATH_TMP;
+ strncpy(tmpstr, tmpdir, sizeof(tmpstr));
+ if (realpath(tmpstr, buf) == NULL) {
+ perror("realpath tmp dir");
+ return;
+ }
+ strncpy(tmpstr, buf, sizeof(tmpstr));
+ strncat(tmpstr, "/acpidump.", sizeof(tmpstr) - strlen(buf));
+ len = strlen(tmpstr);
+ tmpext = tmpstr + len;
+ strncpy(tmpext, "XXXXXX", sizeof(tmpstr) - len);
+ fd = mkstemp(tmpstr);
+ if (fd < 0) {
+ perror("iasl tmp file");
+ return;
+ }
+ write_dsdt(fd, rsdt, dsdp);
+ close(fd);
+
+ /* Run iasl -d on the temp file */
+ if (fork() == 0) {
+ close(STDOUT_FILENO);
+ if (vflag == 0)
+ close(STDERR_FILENO);
+ execl("/usr/sbin/iasl", "iasl", "-d", tmpstr, NULL);
+ err(1, "exec");
+ }
+
+ wait(NULL);
+ unlink(tmpstr);
+
+ /* Dump iasl's output to stdout */
+ strncpy(tmpext, "dsl", sizeof(tmpstr) - len);
+ fp = fopen(tmpstr, "r");
+ unlink(tmpstr);
+ if (fp == NULL) {
+ perror("iasl tmp file (read)");
+ return;
+ }
+ while ((len = fread(buf, 1, sizeof(buf), fp)) > 0)
+ fwrite(buf, 1, len, stdout);
+ fclose(fp);
+}
+
+void
+sdt_print_all(ACPI_TABLE_HEADER *rsdp)
+{
+ acpi_handle_rsdt(rsdp);
+}
+
+/* Fetch a table matching the given signature via the RSDT. */
+ACPI_TABLE_HEADER *
+sdt_from_rsdt(ACPI_TABLE_HEADER *rsdp, const char *sig, ACPI_TABLE_HEADER *last)
+{
+ ACPI_TABLE_HEADER *sdt;
+ ACPI_TABLE_RSDT *rsdt;
+ ACPI_TABLE_XSDT *xsdt;
+ vm_offset_t addr;
+ int entries, i;
+
+ rsdt = (ACPI_TABLE_RSDT *)rsdp;
+ xsdt = (ACPI_TABLE_XSDT *)rsdp;
+ entries = (rsdp->Length - sizeof(ACPI_TABLE_HEADER)) / addr_size;
+ for (i = 0; i < entries; i++) {
+ if (addr_size == 4)
+ addr = le32toh(rsdt->TableOffsetEntry[i]);
+ else
+ addr = le64toh(xsdt->TableOffsetEntry[i]);
+ if (addr == 0)
+ continue;
+ sdt = (ACPI_TABLE_HEADER *)acpi_map_sdt(addr);
+ if (last != NULL) {
+ if (sdt == last)
+ last = NULL;
+ continue;
+ }
+ if (memcmp(sdt->Signature, sig, strlen(sig)))
+ continue;
+ if (acpi_checksum(sdt, sdt->Length))
+ errx(1, "RSDT entry %d is corrupt", i);
+ return (sdt);
+ }
+
+ return (NULL);
+}
+
+ACPI_TABLE_HEADER *
+dsdt_from_fadt(ACPI_TABLE_FADT *fadt)
+{
+ ACPI_TABLE_HEADER *sdt;
+
+ /* Use the DSDT address if it is version 1, otherwise use XDSDT. */
+ if (acpi_get_fadt_revision(fadt) == 1)
+ sdt = (ACPI_TABLE_HEADER *)acpi_map_sdt(fadt->Dsdt);
+ else
+ sdt = (ACPI_TABLE_HEADER *)acpi_map_sdt(fadt->XDsdt);
+ if (acpi_checksum(sdt, sdt->Length))
+ errx(1, "DSDT is corrupt\n");
+ return (sdt);
+}
diff --git a/usr.sbin/acpi/acpidump/acpi_user.c b/usr.sbin/acpi/acpidump/acpi_user.c
new file mode 100644
index 0000000..d759ea7
--- /dev/null
+++ b/usr.sbin/acpi/acpidump/acpi_user.c
@@ -0,0 +1,222 @@
+/*-
+ * Copyright (c) 1999 Doug Rabson
+ * Copyright (c) 2000 Mitsuru IWASAKI <iwasaki@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/param.h>
+#include <sys/mman.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <kenv.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "acpidump.h"
+
+static char hint_acpi_0_rsdp[] = "hint.acpi.0.rsdp";
+static char machdep_acpi_root[] = "machdep.acpi_root";
+static int acpi_mem_fd = -1;
+
+struct acpi_user_mapping {
+ LIST_ENTRY(acpi_user_mapping) link;
+ vm_offset_t pa;
+ caddr_t va;
+ size_t size;
+};
+
+static LIST_HEAD(acpi_user_mapping_list, acpi_user_mapping) maplist;
+
+static void
+acpi_user_init(void)
+{
+
+ if (acpi_mem_fd == -1) {
+ acpi_mem_fd = open("/dev/mem", O_RDONLY);
+ if (acpi_mem_fd == -1)
+ err(1, "opening /dev/mem");
+ LIST_INIT(&maplist);
+ }
+}
+
+static struct acpi_user_mapping *
+acpi_user_find_mapping(vm_offset_t pa, size_t size)
+{
+ struct acpi_user_mapping *map;
+
+ /* First search for an existing mapping */
+ for (map = LIST_FIRST(&maplist); map; map = LIST_NEXT(map, link)) {
+ if (map->pa <= pa && map->size >= pa + size - map->pa)
+ return (map);
+ }
+
+ /* Then create a new one */
+ size = round_page(pa + size) - trunc_page(pa);
+ pa = trunc_page(pa);
+ map = malloc(sizeof(struct acpi_user_mapping));
+ if (!map)
+ errx(1, "out of memory");
+ map->pa = pa;
+ map->va = mmap(0, size, PROT_READ, MAP_SHARED, acpi_mem_fd, pa);
+ map->size = size;
+ if ((intptr_t) map->va == -1)
+ err(1, "can't map address");
+ LIST_INSERT_HEAD(&maplist, map, link);
+
+ return (map);
+}
+
+static ACPI_TABLE_RSDP *
+acpi_get_rsdp(u_long addr)
+{
+ ACPI_TABLE_RSDP rsdp;
+ size_t len;
+
+ /* Read in the table signature and check it. */
+ pread(acpi_mem_fd, &rsdp, 8, addr);
+ if (memcmp(rsdp.Signature, "RSD PTR ", 8))
+ return (NULL);
+
+ /* Read the entire table. */
+ pread(acpi_mem_fd, &rsdp, sizeof(rsdp), addr);
+
+ /* Check the standard checksum. */
+ if (acpi_checksum(&rsdp, ACPI_RSDP_CHECKSUM_LENGTH) != 0)
+ return (NULL);
+
+ /* Check extended checksum if table version >= 2. */
+ if (rsdp.Revision >= 2 &&
+ acpi_checksum(&rsdp, ACPI_RSDP_XCHECKSUM_LENGTH) != 0)
+ return (NULL);
+
+ /* If the revision is 0, assume a version 1 length. */
+ if (rsdp.Revision == 0)
+ len = sizeof(ACPI_RSDP_COMMON);
+ else
+ len = rsdp.Length;
+
+ return (acpi_map_physical(addr, len));
+}
+
+static ACPI_TABLE_RSDP *
+acpi_scan_rsd_ptr(void)
+{
+#if defined(__amd64__) || defined(__i386__)
+ ACPI_TABLE_RSDP *rsdp;
+ u_long addr, end;
+
+ /*
+ * On ia32, scan physical memory for the RSD PTR if above failed.
+ * According to section 5.2.2 of the ACPI spec, we only consider
+ * two regions for the base address:
+ * 1. EBDA (1 KB area addressed by the 16 bit pointer at 0x40E
+ * 2. High memory (0xE0000 - 0xFFFFF)
+ */
+ addr = ACPI_EBDA_PTR_LOCATION;
+ pread(acpi_mem_fd, &addr, sizeof(uint16_t), addr);
+ addr <<= 4;
+ end = addr + ACPI_EBDA_WINDOW_SIZE;
+ for (; addr < end; addr += 16)
+ if ((rsdp = acpi_get_rsdp(addr)) != NULL)
+ return (rsdp);
+ addr = ACPI_HI_RSDP_WINDOW_BASE;
+ end = addr + ACPI_HI_RSDP_WINDOW_SIZE;
+ for (; addr < end; addr += 16)
+ if ((rsdp = acpi_get_rsdp(addr)) != NULL)
+ return (rsdp);
+#endif /* __amd64__ || __i386__ */
+ return (NULL);
+}
+
+/*
+ * Public interfaces
+ */
+ACPI_TABLE_RSDP *
+acpi_find_rsd_ptr(void)
+{
+ ACPI_TABLE_RSDP *rsdp;
+ char buf[20];
+ u_long addr;
+ size_t len;
+
+ acpi_user_init();
+
+ addr = 0;
+
+ /* Attempt to use kenv or sysctl to find RSD PTR record. */
+ if (kenv(KENV_GET, hint_acpi_0_rsdp, buf, 20) > 0)
+ addr = strtoul(buf, NULL, 0);
+ if (addr == 0) {
+ len = sizeof(addr);
+ if (sysctlbyname(machdep_acpi_root, &addr, &len, NULL, 0) != 0)
+ addr = 0;
+ }
+ if (addr != 0 && (rsdp = acpi_get_rsdp(addr)) != NULL)
+ return (rsdp);
+
+ return (acpi_scan_rsd_ptr());
+}
+
+void *
+acpi_map_physical(vm_offset_t pa, size_t size)
+{
+ struct acpi_user_mapping *map;
+
+ map = acpi_user_find_mapping(pa, size);
+ return (map->va + (pa - map->pa));
+}
+
+ACPI_TABLE_HEADER *
+dsdt_load_file(char *infile)
+{
+ ACPI_TABLE_HEADER *sdt;
+ uint8_t *dp;
+ struct stat sb;
+
+ if ((acpi_mem_fd = open(infile, O_RDONLY)) == -1)
+ errx(1, "opening %s", infile);
+
+ LIST_INIT(&maplist);
+
+ if (fstat(acpi_mem_fd, &sb) == -1)
+ errx(1, "fstat %s", infile);
+
+ dp = mmap(0, sb.st_size, PROT_READ, MAP_PRIVATE, acpi_mem_fd, 0);
+ if (dp == NULL)
+ errx(1, "mmap %s", infile);
+
+ sdt = (ACPI_TABLE_HEADER *)dp;
+ if (strncmp(dp, ACPI_SIG_DSDT, 4) != 0 ||
+ acpi_checksum(sdt, sdt->Length) != 0)
+ return (NULL);
+
+ return (sdt);
+}
diff --git a/usr.sbin/acpi/acpidump/acpidump.8 b/usr.sbin/acpi/acpidump/acpidump.8
new file mode 100644
index 0000000..a03917a
--- /dev/null
+++ b/usr.sbin/acpi/acpidump/acpidump.8
@@ -0,0 +1,200 @@
+.\" ACPI (ACPI Package)
+.\"
+.\" Copyright (c) 1999 Doug Rabson <dfr@FreeBSD.org>
+.\" Copyright (c) 2000 Mitsuru IWASAKI <iwasaki@FreeBSD.org>
+.\" Copyright (c) 2000 Yasuo YOKOYAMA <yokoyama@jp.FreeBSD.org>
+.\" Copyright (c) 2000 Hiroki Sato <hrs@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 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 February 14, 2005
+.Dt ACPIDUMP 8
+.Os
+.Sh NAME
+.Nm acpidump
+.Nd dump ACPI tables and ASL
+.Sh SYNOPSIS
+.Nm
+.Op Fl d
+.Op Fl t
+.Op Fl h
+.Op Fl v
+.Op Fl f Ar dsdt_input
+.Op Fl o Ar dsdt_output
+.Sh DESCRIPTION
+The
+.Nm
+utility analyzes ACPI tables in physical memory and can dump them to a file.
+In addition,
+.Nm
+can call
+.Xr iasl 8
+to disassemble AML
+(ACPI Machine Language)
+found in these tables and dump them as ASL
+(ACPI Source Language)
+to stdout.
+.Pp
+ACPI tables have an essential data block (the DSDT,
+Differentiated System Description Table)
+that includes information used on the kernel side such as
+detailed information about PnP hardware, procedures for controlling
+power management support, and so on.
+The
+.Nm
+utility can extract the DSDT data block from physical memory and store it into
+an output file and optionally also disassemble it.
+If any Secondary System Description Table
+(SSDT)
+entries exist, they will also be included in the output file and disassembly.
+.Pp
+When
+.Nm
+is invoked without the
+.Fl f
+option, it will read ACPI tables from physical memory via
+.Pa /dev/mem .
+First it searches for the RSDP
+(Root System Description Pointer),
+which has the signature
+.Qq RSD PTR\ \& ,
+and then gets the RSDT
+(Root System Description Table),
+which includes a list of pointers to physical memory addresses
+for other tables.
+The RSDT itself and all other tables linked from RSDT are generically
+called SDTs
+(System Description Tables)
+and their header has a common format which consists of items
+such as Signature, Length, Revision, Checksum, OEMID, OEM Table ID,
+OEM Revision, Creator ID and Creator Revision.
+When invoked with the
+.Fl t
+flag, the
+.Nm
+utility dumps contents of the following tables:
+.Pp
+.Bl -tag -offset indent -width 12345 -compact
+.It DMAR
+.It DSDT
+.It ECDT
+.It FACS
+.It FADT
+.It HPET
+.It MADT
+.It MCFG
+.It RSD PTR
+.It RSDT
+.El
+.Pp
+The RSDT contains a pointer to the physical memory address of the FACP
+(Fixed ACPI Description Table).
+The FACP defines static system information about power management support
+(ACPI Hardware Register Implementation)
+such as interrupt mode (INT_MODEL),
+SCI interrupt number, SMI command port (SMI_CMD)
+and the location of ACPI registers.
+The FACP also has a pointer to a physical memory address for the DSDT.
+While the other tables are fixed format,
+the DSDT consists of free-formatted AML data.
+.Sh OPTIONS
+The following options are supported by
+.Nm :
+.Bl -tag -width indent
+.It Fl d
+Disassemble the DSDT into ASL using
+.Xr iasl 8
+and print the results to stdout.
+.It Fl t
+Dump the contents of the various fixed tables listed above.
+.It Fl h
+Displays usage and exit.
+.It Fl v
+Enable verbose messages.
+.It Fl f Ar dsdt_input
+Load the DSDT from the specified file instead of physical memory.
+Since only the DSDT is stored in the file, the
+.Fl t
+flag may not be used with this option.
+.It Fl o Ar dsdt_output
+Store the DSDT data block from physical memory into the specified file.
+.El
+.Sh FILES
+.Bl -tag -width /dev/mem
+.It Pa /dev/mem
+.El
+.Sh EXAMPLES
+If a developer requests a copy of your ASL, please use the following
+command to dump all tables and compress the result.
+.Bd -literal -offset indent
+# acpidump -dt | gzip -c9 > my_computer.asl.gz
+.Ed
+.Pp
+This example dumps the DSDT from physical memory to foo.dsdt.
+It also prints the contents of various system tables and disassembles
+the AML contained in the DSDT to stdout, redirecting the output
+to foo.asl.
+.Bd -literal -offset indent
+# acpidump -t -d -o foo.dsdt > foo.asl
+.Ed
+.Pp
+This example reads a DSDT file and disassembles it to stdout.
+Verbose messages are enabled.
+.Bd -literal -offset indent
+# acpidump -v -d -f foo.dsdt
+.Ed
+.Sh SEE ALSO
+.Xr acpi 4 ,
+.Xr mem 4 ,
+.Xr acpiconf 8 ,
+.Xr acpidb 8 ,
+.Xr iasl 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 5.0
+and was rewritten to use
+.Xr iasl 8
+for
+.Fx 5.2 .
+.Sh AUTHORS
+.An Doug Rabson Aq Mt dfr@FreeBSD.org
+.An Mitsuru IWASAKI Aq Mt iwasaki@FreeBSD.org
+.An Yasuo YOKOYAMA Aq Mt yokoyama@jp.FreeBSD.org
+.An Nate Lawson Aq Mt njl@FreeBSD.org
+.Pp
+.An -nosplit
+Some contributions made by
+.An Chitoshi Ohsawa Aq Mt ohsawa@catv1.ccn-net.ne.jp ,
+.An Takayasu IWANASHI Aq Mt takayasu@wendy.a.perfect-liberty.or.jp ,
+.An Yoshihiko SARUMARU Aq Mt mistral@imasy.or.jp ,
+.An Hiroki Sato Aq Mt hrs@FreeBSD.org ,
+.An Michael Lucas Aq Mt mwlucas@blackhelicopters.org
+and
+.An Michael Smith Aq Mt msmith@FreeBSD.org .
+.Sh BUGS
+The current implementation does not dump the BOOT structure or
+other miscellaneous tables.
diff --git a/usr.sbin/acpi/acpidump/acpidump.c b/usr.sbin/acpi/acpidump/acpidump.c
new file mode 100644
index 0000000..38844b6
--- /dev/null
+++ b/usr.sbin/acpi/acpidump/acpidump.c
@@ -0,0 +1,144 @@
+/*-
+ * Copyright (c) 2000 Mitsuru IWASAKI <iwasaki@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/param.h>
+#include <assert.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "acpidump.h"
+
+int dflag; /* Disassemble AML using iasl(8) */
+int tflag; /* Dump contents of SDT tables */
+int vflag; /* Use verbose messages */
+
+static void
+usage(const char *progname)
+{
+
+ fprintf(stderr, "usage: %s [-d] [-t] [-h] [-v] [-f dsdt_input] "
+ "[-o dsdt_output]\n", progname);
+ fprintf(stderr, "To send ASL:\n\t%s -dt | gzip -c9 > foo.asl.gz\n",
+ progname);
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ ACPI_TABLE_HEADER *rsdt, *sdt;
+ char c, *progname;
+ char *dsdt_input_file, *dsdt_output_file;
+
+ dsdt_input_file = dsdt_output_file = NULL;
+ progname = argv[0];
+
+ if (argc < 2)
+ usage(progname);
+
+ while ((c = getopt(argc, argv, "dhtvf:o:")) != -1) {
+ switch (c) {
+ case 'd':
+ dflag = 1;
+ break;
+ case 't':
+ tflag = 1;
+ break;
+ case 'v':
+ vflag = 1;
+ break;
+ case 'f':
+ dsdt_input_file = optarg;
+ break;
+ case 'o':
+ dsdt_output_file = optarg;
+ break;
+ case 'h':
+ default:
+ usage(progname);
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Get input either from file or /dev/mem */
+ if (dsdt_input_file != NULL) {
+ if (dflag == 0 && tflag == 0) {
+ warnx("Need to specify -d or -t with DSDT input file");
+ usage(progname);
+ } else if (tflag != 0) {
+ warnx("Can't use -t with DSDT input file");
+ usage(progname);
+ }
+ if (vflag)
+ warnx("loading DSDT file: %s", dsdt_input_file);
+ rsdt = dsdt_load_file(dsdt_input_file);
+ } else {
+ if (vflag)
+ warnx("loading RSD PTR from /dev/mem");
+ rsdt = sdt_load_devmem();
+ }
+
+ /* Display misc. SDT tables (only available when using /dev/mem) */
+ if (tflag) {
+ if (vflag)
+ warnx("printing various SDT tables");
+ sdt_print_all(rsdt);
+ }
+
+ /* Translate RSDT to DSDT pointer */
+ if (dsdt_input_file == NULL) {
+ sdt = sdt_from_rsdt(rsdt, ACPI_SIG_FADT, NULL);
+ sdt = dsdt_from_fadt((ACPI_TABLE_FADT *)sdt);
+ } else {
+ sdt = rsdt;
+ rsdt = NULL;
+ }
+
+ /* Dump the DSDT and SSDTs to a file */
+ if (dsdt_output_file != NULL) {
+ if (vflag)
+ warnx("saving DSDT file: %s", dsdt_output_file);
+ dsdt_save_file(dsdt_output_file, rsdt, sdt);
+ }
+
+ /* Disassemble the DSDT into ASL */
+ if (dflag) {
+ if (vflag)
+ warnx("disassembling DSDT, iasl messages follow");
+ aml_disassemble(rsdt, sdt);
+ if (vflag)
+ warnx("iasl processing complete");
+ }
+
+ exit(0);
+}
diff --git a/usr.sbin/acpi/acpidump/acpidump.h b/usr.sbin/acpi/acpidump/acpidump.h
new file mode 100644
index 0000000..3421519
--- /dev/null
+++ b/usr.sbin/acpi/acpidump/acpidump.h
@@ -0,0 +1,158 @@
+/*-
+ * Copyright (c) 1999 Doug Rabson
+ * Copyright (c) 2000 Mitsuru IWASAKI <iwasaki@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 _ACPIDUMP_H_
+#define _ACPIDUMP_H_
+
+#include <contrib/dev/acpica/include/acpi.h>
+#include <contrib/dev/acpica/include/acconfig.h>
+#include <contrib/dev/acpica/include/actbl1.h>
+
+/* GAS address space ID constants. */
+#define ACPI_GAS_MEMORY 0
+#define ACPI_GAS_IO 1
+#define ACPI_GAS_PCI 2
+#define ACPI_GAS_EMBEDDED 3
+#define ACPI_GAS_SMBUS 4
+#define ACPI_GAS_CMOS 5
+#define ACPI_GAS_PCIBAR 6
+#define ACPI_GAS_DATATABLE 7
+#define ACPI_GAS_FIXED 0x7f
+
+/* Subfields in the HPET Id member. */
+#define ACPI_HPET_ID_HARDWARE_REV_ID 0x000000ff
+#define ACPI_HPET_ID_COMPARATORS 0x00001f00
+#define ACPI_HPET_ID_COUNT_SIZE_CAP 0x00002000
+#define ACPI_HPET_ID_LEGACY_CAPABLE 0x00008000
+#define ACPI_HPET_ID_PCI_VENDOR_ID 0xffff0000
+
+/* Find and map the RSD PTR structure and return it for parsing */
+ACPI_TABLE_HEADER *sdt_load_devmem(void);
+
+/* TCPA */
+struct TCPAbody {
+ ACPI_TABLE_HEADER header;
+ uint16_t platform_class;
+#define ACPI_TCPA_BIOS_CLIENT 0x00
+#define ACPI_TCPA_BIOS_SERVER 0x01
+ union {
+ struct client_hdr {
+ uint32_t log_max_len __packed;
+ uint64_t log_start_addr __packed;
+ } client;
+ struct server_hdr {
+ uint16_t reserved;
+ uint64_t log_max_len __packed;
+ uint64_t log_start_addr __packed;
+ } server;
+ };
+} __packed;
+
+struct TCPAevent {
+ u_int32_t pcr_index;
+ u_int32_t event_type;
+ u_int8_t pcr_value[20];
+ u_int32_t event_size;
+ u_int8_t event_data[0];
+};
+
+struct TCPApc_event {
+ u_int32_t event_id;
+ u_int32_t event_size;
+ u_int8_t event_data[0];
+};
+
+enum TCPAevent_types {
+ PREBOOT = 0,
+ POST_CODE,
+ UNUSED,
+ NO_ACTION,
+ SEPARATOR,
+ ACTION,
+ EVENT_TAG,
+ SCRTM_CONTENTS,
+ SCRTM_VERSION,
+ CPU_MICROCODE,
+ PLATFORM_CONFIG_FLAGS,
+ TABLE_OF_DEVICES,
+ COMPACT_HASH,
+ IPL,
+ IPL_PARTITION_DATA,
+ NONHOST_CODE,
+ NONHOST_CONFIG,
+ NONHOST_INFO,
+ EVENT_TYPE_MAX,
+};
+
+enum TCPApcclient_ids {
+ SMBIOS = 1,
+ BIS_CERT,
+ POST_BIOS_ROM,
+ ESCD,
+ CMOS,
+ NVRAM,
+ OPTION_ROM_EXEC,
+ OPTION_ROM_CONFIG,
+ OPTION_ROM_MICROCODE = 10,
+ S_CRTM_VERSION,
+ S_CRTM_CONTENTS,
+ POST_CONTENTS,
+ HOST_TABLE_OF_DEVICES,
+ PCCLIENT_ID_MAX,
+};
+
+/*
+ * Load the DSDT from a previous save file. Note that other tables are
+ * not saved (i.e. FADT)
+ */
+ACPI_TABLE_HEADER *dsdt_load_file(char *);
+
+/* Save the DSDT to a file */
+void dsdt_save_file(char *, ACPI_TABLE_HEADER *, ACPI_TABLE_HEADER *);
+
+/* Print out as many fixed tables as possible, given the RSD PTR */
+void sdt_print_all(ACPI_TABLE_HEADER *);
+
+/* Disassemble the AML in the DSDT */
+void aml_disassemble(ACPI_TABLE_HEADER *, ACPI_TABLE_HEADER *);
+
+/* Routines for accessing tables in physical memory */
+ACPI_TABLE_RSDP *acpi_find_rsd_ptr(void);
+void *acpi_map_physical(vm_offset_t, size_t);
+ACPI_TABLE_HEADER *sdt_from_rsdt(ACPI_TABLE_HEADER *, const char *,
+ ACPI_TABLE_HEADER *);
+ACPI_TABLE_HEADER *dsdt_from_fadt(ACPI_TABLE_FADT *);
+int acpi_checksum(void *, size_t);
+
+/* Command line flags */
+extern int dflag;
+extern int tflag;
+extern int vflag;
+
+#endif /* !_ACPIDUMP_H_ */
diff --git a/usr.sbin/acpi/iasl/Makefile b/usr.sbin/acpi/iasl/Makefile
new file mode 100644
index 0000000..b4385c7
--- /dev/null
+++ b/usr.sbin/acpi/iasl/Makefile
@@ -0,0 +1,121 @@
+# $FreeBSD$
+
+PROG= iasl
+
+# common
+SRCS= adfile.c adisasm.c adwalk.c ahids.c ahpredef.c \
+ ahtable.c ahuuids.c cmfsize.c dmextern.c dmrestag.c \
+ dmtable.c dmtbdump.c dmtbinfo.c getopt.c
+
+# compiler
+SRCS+= aslanalyze.c aslascii.c aslbtypes.c aslcodegen.c \
+ aslcompile.c aslcompiler.y.h aslcompilerlex.c \
+ aslcompilerparse.c aslerror.c aslfileio.c aslfiles.c \
+ aslfold.c aslhex.c asllength.c asllisting.c \
+ asllistsup.c aslload.c asllookup.c aslmain.c aslmap.c \
+ aslmapenter.c aslmapoutput.c aslmaputils.c \
+ aslmessages.c aslmethod.c aslnamesp.c asloffset.c \
+ aslopcodes.c asloperands.c aslopt.c asloptions.c \
+ aslpredef.c aslprepkg.c aslprintf.c aslprune.c \
+ aslresource.c aslrestype1.c aslrestype1i.c \
+ aslrestype2.c aslrestype2d.c aslrestype2e.c \
+ aslrestype2q.c aslrestype2s.c aslrestype2w.c \
+ aslstartup.c aslstubs.c asltransform.c asltree.c \
+ aslutils.c asluuid.c aslwalks.c aslxref.c dtcompile.c \
+ dtexpress.c dtfield.c dtio.c dtparser.y.h dtparserlex.c \
+ dtparserparse.c dtsubtable.c dttable.c dttemplate.c \
+ dtutils.c prexpress.c prmacros.c prparser.y.h \
+ prparserlex.c prparserparse.c prscan.c prutils.c
+
+# components/debugger
+SRCS+= dbfileio.c
+
+# components/disassembler
+SRCS+= dmbuffer.c dmcstyle.c dmdeferred.c dmnames.c dmopcode.c \
+ dmresrc.c dmresrcl.c dmresrcl2.c dmresrcs.c dmutils.c \
+ dmwalk.c
+
+# components/dispatcher
+SRCS+= dsargs.c dscontrol.c dsfield.c dsobject.c dsopcode.c \
+ dsutils.c dswexec.c dswload.c dswload2.c dswscope.c \
+ dswstate.c
+
+# components/executer
+SRCS+= exconvrt.c excreate.c exdump.c exmisc.c exmutex.c \
+ exnames.c exoparg1.c exoparg2.c exoparg3.c exoparg6.c \
+ exprep.c exresnte.c exresolv.c exresop.c exstore.c \
+ exstoren.c exstorob.c exsystem.c exutils.c
+
+# components/parser
+SRCS+= psargs.c psloop.c psobject.c psopcode.c psopinfo.c \
+ psparse.c psscope.c pstree.c psutils.c pswalk.c
+
+# components/namespace
+SRCS+= nsaccess.c nsalloc.c nsdump.c nsnames.c nsobject.c \
+ nsparse.c nssearch.c nsutils.c nswalk.c
+
+# components/tables
+SRCS+= tbdata.c tbfadt.c tbinstal.c tbprint.c tbutils.c \
+ tbxface.c
+
+# components/utilities
+SRCS+= utaddress.c utalloc.c utbuffer.c utcache.c utcopy.c \
+ utdebug.c utdecode.c utdelete.c uterror.c utexcep.c \
+ utfileio.c utglobal.c uthex.c utinit.c utlock.c \
+ utmath.c utmisc.c utmutex.c utnonansi.c utobject.c \
+ utownerid.c utpredef.c utprint.c utresrc.c utstate.c \
+ utstring.c utuuid.c utxface.c utxferror.c
+
+# os_specific/service_layers
+SRCS+= oslibcfs.c osunixxf.c
+
+WARNS?= 2
+
+MAN= iasl.8
+
+CFLAGS+= -DACPI_ASL_COMPILER -I.
+LFLAGS= -i -s
+YFLAGS= -d
+
+CLEANFILES= aslcompiler.y aslcompiler.y.h aslcompilerlex.c \
+ aslcompilerparse.c aslcompilerparse.h dtparser.y.h \
+ dtparserlex.c dtparserparse.c dtparserparse.h \
+ prparser.y.h prparserlex.c prparserparse.c \
+ prparserparse.h
+
+aslcompilerlex.c: aslcompiler.l aslsupport.l
+ ${LEX} ${LFLAGS} -PAslCompiler -o${.TARGET} \
+ ${ACPICA_DIR}/compiler/aslcompiler.l
+
+aslcompiler.y: aslparser.y aslrules.y aslsupport.y asltokens.y asltypes.y
+ m4 -P -I${ACPICA_DIR}/compiler \
+ ${ACPICA_DIR}/compiler/aslparser.y > ${.TARGET}
+
+.ORDER: aslcompilerparse.c aslcompilerparse.h
+aslcompilerparse.c aslcompilerparse.h: aslcompiler.y
+ ${YACC} ${YFLAGS} -pAslCompiler -oaslcompilerparse.c ${.ALLSRC}
+
+aslcompiler.y.h: aslcompilerparse.h .NOMETA
+ ln -f ${.ALLSRC} ${.TARGET}
+
+dtparserlex.c: dtparser.l
+ ${LEX} ${LFLAGS} -PDtParser -o${.TARGET} ${.ALLSRC}
+
+.ORDER: dtparserparse.c dtparserparse.h
+dtparserparse.c dtparserparse.h: dtparser.y
+ ${YACC} ${YFLAGS} -pDtParser -odtparserparse.c ${.ALLSRC}
+
+dtparser.y.h: dtparserparse.h .NOMETA
+ ln -f ${.ALLSRC} ${.TARGET}
+
+prparserlex.c: prparser.l
+ ${LEX} ${LFLAGS} -PPrParser -o${.TARGET} ${.ALLSRC}
+
+.ORDER: prparserparse.c prparserparse.h
+prparserparse.c prparserparse.h: prparser.y
+ ${YACC} ${YFLAGS} -pPrParser -oprparserparse.c ${.ALLSRC}
+
+prparser.y.h: prparserparse.h .NOMETA
+ ln -f ${.ALLSRC} ${.TARGET}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/acpi/iasl/Makefile.depend b/usr.sbin/acpi/iasl/Makefile.depend
new file mode 100644
index 0000000..ef1c922
--- /dev/null
+++ b/usr.sbin/acpi/iasl/Makefile.depend
@@ -0,0 +1,107 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ usr.bin/yacc.host \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+aslanalyze.o: aslcompiler.y.h
+aslanalyze.po: aslcompiler.y.h
+aslbtypes.o: aslcompiler.y.h
+aslbtypes.po: aslcompiler.y.h
+aslcodegen.o: aslcompiler.y.h
+aslcodegen.po: aslcompiler.y.h
+aslcompilerlex.o: aslcompiler.y.h
+aslcompilerlex.o: aslcompilerlex.c
+aslcompilerlex.po: aslcompiler.y.h
+aslcompilerlex.po: aslcompilerlex.c
+aslcompilerparse.o: aslcompilerparse.c
+aslcompilerparse.po: aslcompilerparse.c
+aslfold.o: aslcompiler.y.h
+aslfold.po: aslcompiler.y.h
+asllength.o: aslcompiler.y.h
+asllength.po: aslcompiler.y.h
+asllisting.o: aslcompiler.y.h
+asllisting.po: aslcompiler.y.h
+asllistsup.o: aslcompiler.y.h
+asllistsup.po: aslcompiler.y.h
+aslload.o: aslcompiler.y.h
+aslload.po: aslcompiler.y.h
+asllookup.o: aslcompiler.y.h
+asllookup.po: aslcompiler.y.h
+aslmapoutput.o: aslcompiler.y.h
+aslmapoutput.po: aslcompiler.y.h
+aslmaputils.o: aslcompiler.y.h
+aslmaputils.po: aslcompiler.y.h
+aslmethod.o: aslcompiler.y.h
+aslmethod.po: aslcompiler.y.h
+aslnamesp.o: aslcompiler.y.h
+aslnamesp.po: aslcompiler.y.h
+asloffset.o: aslcompiler.y.h
+asloffset.po: aslcompiler.y.h
+aslopcodes.o: aslcompiler.y.h
+aslopcodes.po: aslcompiler.y.h
+asloperands.o: aslcompiler.y.h
+asloperands.po: aslcompiler.y.h
+aslopt.o: aslcompiler.y.h
+aslopt.po: aslcompiler.y.h
+aslpredef.o: aslcompiler.y.h
+aslpredef.po: aslcompiler.y.h
+aslprepkg.o: aslcompiler.y.h
+aslprepkg.po: aslcompiler.y.h
+aslprintf.o: aslcompiler.y.h
+aslprintf.po: aslcompiler.y.h
+aslprune.o: aslcompiler.y.h
+aslprune.po: aslcompiler.y.h
+aslresource.o: aslcompiler.y.h
+aslresource.po: aslcompiler.y.h
+aslrestype1.o: aslcompiler.y.h
+aslrestype1.po: aslcompiler.y.h
+aslrestype1i.o: aslcompiler.y.h
+aslrestype1i.po: aslcompiler.y.h
+aslrestype2.o: aslcompiler.y.h
+aslrestype2.po: aslcompiler.y.h
+aslrestype2d.o: aslcompiler.y.h
+aslrestype2d.po: aslcompiler.y.h
+aslrestype2q.o: aslcompiler.y.h
+aslrestype2q.po: aslcompiler.y.h
+aslrestype2s.o: aslcompiler.y.h
+aslrestype2s.po: aslcompiler.y.h
+aslrestype2w.o: aslcompiler.y.h
+aslrestype2w.po: aslcompiler.y.h
+asltransform.o: aslcompiler.y.h
+asltransform.po: aslcompiler.y.h
+asltree.o: aslcompiler.y.h
+asltree.po: aslcompiler.y.h
+aslutils.o: aslcompiler.y.h
+aslutils.po: aslcompiler.y.h
+aslwalks.o: aslcompiler.y.h
+aslwalks.po: aslcompiler.y.h
+aslxref.o: aslcompiler.y.h
+aslxref.po: aslcompiler.y.h
+dtexpress.o: dtparser.y.h
+dtexpress.po: dtparser.y.h
+dtparserlex.o: dtparser.y.h
+dtparserlex.o: dtparserlex.c
+dtparserlex.po: dtparser.y.h
+dtparserlex.po: dtparserlex.c
+dtparserparse.o: dtparserparse.c
+dtparserparse.po: dtparserparse.c
+prparserlex.o: prparser.y.h
+prparserlex.o: prparserlex.c
+prparserlex.po: prparser.y.h
+prparserlex.po: prparserlex.c
+prparserparse.o: prparserparse.c
+prparserparse.po: prparserparse.c
+.endif
diff --git a/usr.sbin/acpi/iasl/iasl.8 b/usr.sbin/acpi/iasl/iasl.8
new file mode 100644
index 0000000..bf16c8c
--- /dev/null
+++ b/usr.sbin/acpi/iasl/iasl.8
@@ -0,0 +1,179 @@
+.\"-
+.\" Copyright (c) 2003 Nate Lawson
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer
+.\" in this position and unchanged.
+.\" 2. Redistributions in binary form must 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 May 20, 2008
+.Dt IASL 8
+.Os
+.Sh NAME
+.Nm iasl
+.Nd Intel ACPI compiler/decompiler
+.Sh SYNOPSIS
+.Nm
+.Op Fl 2cefghl
+.Op Fl b Ar type
+.Op Fl d Ar file
+.Op Fl dc Ar file
+.Op Fl hc
+.Op Fl hr
+.Op Fl i Ar type
+.Op Fl ln
+.Op Fl ls
+.Op Fl oa
+.Op Fl of
+.Op Fl oi
+.Op Fl on
+.Op Fl ot
+.Op Fl p Ar prefix
+.Op Fl s Ar type
+.Op Fl t Ar type
+.Op Fl vi
+.Op Fl vo
+.Op Fl vr
+.Op Fl vs
+.Op Fl x Ar level
+.Op Fl w Ar level
+.Ar input-file
+.Sh DESCRIPTION
+The
+.Nm
+utility is a compiler/decompiler for ACPI Source Language (ASL)
+and ACPI Machine Language (AML).
+Major features of
+.Nm
+include:
+.Bl -bullet -offset indent
+.It
+Full support for the ACPI 3.0b Specification including ASL grammar
+elements and operators.
+.It
+Extensive compiler syntax and semantic error checking, especially in
+the area of control methods.
+This reduces the number of errors that are
+not discovered until the AML code is actually interpreted (i.e., the
+compile-time error checking reduces the number of run-time errors).
+.It
+Multiple types of output files, including formatted listing files with
+intermixed source, several types of AML files, and error messages.
+.El
+.Sh OPTIONS
+.Bl -tag -width indent
+.It Fl 2
+Emit ACPI 2.0 compatible ASL code.
+.It Fl b Sm Cm p | t | b Sm
+Create compiler debug/trace file
+.Pq Pa *.txt .
+Types: Parse/Tree/Both.
+.It Fl c
+Parse only, no output generation.
+.It Fl d Ar file
+Disassemble AML to ASL source code file
+.Pq Pa *.dsl .
+.It Fl dc Ar file
+Disassemble AML and immediately compile it.
+(Obtain DSDT from current system if no input file.)
+.It Fl e
+Generate
+.Fn External
+statements for unresolved symbols.
+.It Fl f
+Ignore errors, force creation of AML output file(s).
+.It Fl g
+Get ACPI tables and write to files
+.Pq Pa *.dat .
+.It Fl h
+Additional help and compiler debug options.
+.It Fl hc
+Display operators allowed in constant expressions.
+.It Fl hr
+Display ACPI reserved method names.
+.It Fl i Sm Cm a | c Sm
+Create assembler or C include file
+.Pa ( *.inc
+or
+.Pa *.h ) .
+.It Fl l
+Create mixed listing file (ASL source and AML)
+.Pq Pa *.lst .
+.It Fl ln
+Create namespace file
+.Pq Pa *.nsp .
+.It Fl ls
+Create combined source file (expanded includes)
+.Pq Pa *.src .
+.It Fl oa
+Disable all optimizations (compatibility mode).
+.It Fl of
+Disable constant folding.
+.It Fl oi
+Disable integer optimization to Zero/One/Ones.
+.It Fl on
+Disable named reference string optimization.
+.It Fl ot
+Display compile times.
+.It Fl p Ar prefix
+Specify filename prefix for all output files (including
+.Pa .aml ) .
+.It Fl s Sm Cm a | c Sm
+Create AML in assembler or C source file
+.Pa ( *.asm
+or
+.Pa *.c ) .
+.It Fl t Ar a|c
+Create AML in assembler or C hex table
+.Pq Pa *.hex .
+.It Fl vi
+Less verbose errors and warnings for use with IDEs.
+.It Fl vo
+Enable optimization comments.
+.It Fl vr
+Disable remarks.
+.It Fl vs
+Disable signon.
+.It Fl x Ar level
+Set debug level for trace output.
+.It Fl w Ar level
+Set warning level.
+.El
+.Sh SEE ALSO
+.Xr acpi 4 ,
+.Xr acpidump 8
+.Sh HISTORY
+The
+.Nm
+utility is provided with Intel ACPI-CA.
+It first appeared in
+.Fx 5.2 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility was written by
+.An Intel .
+This manual page was written by
+.An Nate Lawson .
diff --git a/usr.sbin/adduser/Makefile b/usr.sbin/adduser/Makefile
new file mode 100644
index 0000000..0ca2dae
--- /dev/null
+++ b/usr.sbin/adduser/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+SCRIPTS=adduser.sh rmuser.sh
+MAN= adduser.conf.5 adduser.8 rmuser.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/adduser/Makefile.depend b/usr.sbin/adduser/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/adduser/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/adduser/adduser.8 b/usr.sbin/adduser/adduser.8
new file mode 100644
index 0000000..2e6a5b5
--- /dev/null
+++ b/usr.sbin/adduser/adduser.8
@@ -0,0 +1,479 @@
+.\" Copyright (c) 1995-1996 Wolfram Schneider <wosch@FreeBSD.org>. Berlin.
+.\" All rights reserved.
+.\" Copyright (c) 2002-2004 Michael Telahun Makonnen <mtm@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 September 15, 2012
+.Dt ADDUSER 8
+.Os
+.Sh NAME
+.Nm adduser
+.Nd command for adding new users
+.Sh SYNOPSIS
+.Nm
+.Op Fl CDENShq
+.Op Fl G Ar groups
+.Op Fl L Ar login_class
+.Op Fl M Ar mode
+.Op Fl d Ar partition
+.Op Fl f Ar file
+.Op Fl g Ar login_group
+.Op Fl k Ar dotdir
+.Op Fl m Ar message_file
+.Op Fl s Ar shell
+.Op Fl u Ar uid_start
+.Op Fl w Ar type
+.Sh DESCRIPTION
+The
+.Nm
+utility is a shell script, implemented around the
+.Xr pw 8
+command, for adding new users.
+It creates passwd/group entries, a home directory,
+copies dotfiles and sends the new user a welcome message.
+It supports two modes of operation.
+It may be used interactively
+at the command line to add one user at a time, or it may be directed
+to get the list of new users from a file and operate in batch mode
+without requiring any user interaction.
+.Sh RESTRICTIONS
+.Bl -tag -width indent
+.It username
+Login name.
+The user name is restricted to whatever
+.Xr pw 8
+will accept.
+Generally this means it
+may contain only lowercase characters or digits but cannot begin with the
+.Ql -
+character.
+Maximum length
+is 16 characters.
+The reasons for this limit are historical.
+Given that people have traditionally wanted to break this
+limit for aesthetic reasons, it has never been of great importance to break
+such a basic fundamental parameter in
+.Ux .
+You can change
+.Dv UT_NAMESIZE
+in
+.In utmp.h
+and recompile the
+world; people have done this and it works, but you will have problems
+with any precompiled programs, or source that assumes the 8-character
+name limit, such as NIS.
+The NIS protocol mandates an 8-character username.
+If you need a longer login name for e-mail addresses,
+you can define an alias in
+.Pa /etc/mail/aliases .
+.It "full name"
+This is typically known as the gecos field and usually contains
+the user's full name.
+Additionally, it may contain a comma separated
+list of values such as office number and work and home phones.
+If the
+name contains an ampersand it will be replaced by the capitalized
+login name when displayed by other programs.
+The
+.Ql \&:
+character is not allowed.
+.It shell
+Unless the
+.Fl S
+argument is supplied only valid shells from the shell database
+.Pq Pa /etc/shells
+are allowed.
+In addition,
+either the base name or the full path of the shell may be supplied.
+.It UID
+Automatically generated or your choice.
+It must be less than 32000.
+.It "GID/login group"
+Automatically generated or your choice.
+It must be less than 32000.
+.It password
+You may choose an empty password, disable the password, use a
+randomly generated password or specify your own plaintext password,
+which will be encrypted before being stored in the user database.
+.El
+.Sh UNIQUE GROUPS
+Perhaps you are missing what
+.Em can
+be done with this scheme that falls apart
+with most other schemes.
+With each user in their own group,
+they can safely run with a umask of 002 instead of the usual 022
+and create files in their home directory
+without worrying about others being able to change them.
+.Pp
+For a shared area you create a separate UID/GID, you place each person
+that should be able to access this area into that new group.
+.Pp
+This model of UID/GID administration allows far greater flexibility than lumping
+users into groups and having to muck with the umask when working in a shared
+area.
+.Pp
+I have been using this model for almost 10 years and found that it works
+for most situations, and has never gotten in the way.
+(Rod Grimes)
+.Sh CONFIGURATION
+The
+.Nm
+utility reads its configuration information from
+.Pa /etc/adduser.conf .
+If this file does not exist, it will use predefined defaults.
+While this file may be edited by hand,
+the safer option is to use the
+.Fl C
+command line argument.
+With this argument,
+.Nm
+will start interactive input, save the answers to its prompts in
+.Pa /etc/adduser.conf ,
+and promptly exit without modifying the user
+database.
+Options specified on the command line will take precedence over
+any values saved in this file.
+.Sh OPTIONS
+.Bl -tag -width indent
+.It Fl C
+Create new configuration file and exit.
+This option is mutually exclusive with the
+.Fl f
+option.
+.It Fl d Ar partition
+Home partition.
+Default partition, under which all user directories
+will be located.
+The
+.Pa /nonexistent
+partition is considered special.
+The
+.Nm
+script will not create and populate a home directory by that name.
+Otherwise,
+by default it attempts to create a home directory.
+.It Fl D
+Do not attempt to create the home directory.
+.It Fl E
+Disable the account.
+This option will lock the account by prepending the string
+.Dq Li *LOCKED*
+to the password field.
+The account may be unlocked
+by the super-user with the
+.Xr pw 8
+command:
+.Pp
+.D1 Nm pw Cm unlock Op Ar name | uid
+.It Fl f Ar file
+Get the list of accounts to create from
+.Ar file .
+If
+.Ar file
+is
+.Dq Fl ,
+then get the list from standard input.
+If this option is specified,
+.Nm
+will operate in batch mode and will not seek any user input.
+If an error is encountered while processing an account, it will write a
+message to standard error and move to the next account.
+The format
+of the input file is described below.
+.It Fl g Ar login_group
+Normally,
+if no login group is specified,
+it is assumed to be the same as the username.
+This option makes
+.Ar login_group
+the default.
+.It Fl G Ar groups
+Space-separated list of additional groups.
+This option allows the user to specify additional groups to add users to.
+The user is a member of these groups in addition to their login group.
+.It Fl h
+Print a summary of options and exit.
+.It Fl k Ar directory
+Copy files from
+.Ar directory
+into the home
+directory of new users;
+.Pa dot.foo
+will be renamed to
+.Pa .foo .
+.It Fl L Ar login_class
+Set default login class.
+.It Fl m Ar file
+Send new users a welcome message from
+.Ar file .
+Specifying a value of
+.Cm no
+for
+.Ar file
+causes no message to be sent to new users.
+Please note that the message
+file can reference the internal variables of the
+.Nm
+script.
+.It Fl M Ar mode
+Create the home directory with permissions set to
+.Ar mode .
+.It Fl N
+Do not read the default configuration file.
+.It Fl q
+Minimal user feedback.
+In particular, the random password will not be echoed to
+standard output.
+.It Fl s Ar shell
+Default shell for new users.
+The
+.Ar shell
+argument may be the base name of the shell or the full path.
+Unless the
+.Fl S
+argument is supplied the shell must exist in
+.Pa /etc/shells
+or be the special shell
+.Em nologin
+to be considered a valid shell.
+.It Fl S
+The existence or validity of the specified shell will not be checked.
+.It Fl u Ar uid
+Use UIDs from
+.Ar uid
+on up.
+.It Fl w Ar type
+Password type.
+The
+.Nm
+utility allows the user to specify what type of password to create.
+The
+.Ar type
+argument may have one of the following values:
+.Bl -tag -width ".Cm random"
+.It Cm no
+Disable the password.
+Instead of an encrypted string, the password field will contain a single
+.Ql *
+character.
+The user may not log in until the super-user
+manually enables the password.
+.It Cm none
+Use an empty string as the password.
+.It Cm yes
+Use a user-supplied string as the password.
+In interactive mode,
+the user will be prompted for the password.
+In batch mode, the
+last (10th) field in the line is assumed to be the password.
+.It Cm random
+Generate a random string and use it as a password.
+The password will be echoed to standard output.
+In addition, it will be available for inclusion in the message file in the
+.Va randompass
+variable.
+.El
+.El
+.Sh FORMAT
+When the
+.Fl f
+option is used, the account information must be stored in a specific
+format.
+All empty lines or lines beginning with a
+.Ql #
+will be ignored.
+All other lines must contain ten colon
+.Pq Ql \&:
+separated fields as described below.
+Command line options do not take precedence
+over values in the fields.
+Only the password field may contain a
+.Ql \&:
+character as part of the string.
+.Pp
+.Sm off
+.D1 Ar name : uid : gid : class : change : expire : gecos : home_dir : shell : password
+.Sm on
+.Bl -tag -width ".Ar password"
+.It Ar name
+Login name.
+This field may not be empty.
+.It Ar uid
+Numeric login user ID.
+If this field is left empty, it will be automatically generated.
+.It Ar gid
+Numeric primary group ID.
+If this field is left empty, a group with the
+same name as the user name will be created and its GID will be used
+instead.
+.It Ar class
+Login class.
+This field may be left empty.
+.It Ar change
+Password ageing.
+This field denotes the password change date for the account.
+The format of this field is the same as the format of the
+.Fl p
+argument to
+.Xr pw 8 .
+It may be
+.Ar dd Ns - Ns Ar mmm Ns - Ns Ar yy Ns Op Ar yy ,
+where
+.Ar dd
+is for the day,
+.Ar mmm
+is for the month in numeric or alphabetical format:
+.Dq Li 10
+or
+.Dq Li Oct ,
+and
+.Ar yy Ns Op Ar yy
+is the four or two digit year.
+To denote a time relative to the current date the format is:
+.No + Ns Ar n Ns Op Ar mhdwoy ,
+where
+.Ar n
+denotes a number, followed by the minutes, hours, days, weeks,
+months or years after which the password must be changed.
+This field may be left empty to turn it off.
+.It Ar expire
+Account expiration.
+This field denotes the expiry date of the account.
+The account may not be used after the specified date.
+The format of this field is the same as that for password ageing.
+This field may be left empty to turn it off.
+.It Ar gecos
+Full name and other extra information about the user.
+.It Ar home_dir
+Home directory.
+If this field is left empty, it will be automatically
+created by appending the username to the home partition.
+The
+.Pa /nonexistent
+home directory is considered special and
+is understood to mean that no home directory is to be
+created for the user.
+.It Ar shell
+Login shell.
+This field should contain either the base name or
+the full path to a valid login shell.
+.It Ar password
+User password.
+This field should contain a plaintext string, which will
+be encrypted before being placed in the user database.
+If the password type is
+.Cm yes
+and this field is empty, it is assumed the account will have an empty password.
+If the password type is
+.Cm random
+and this field is
+.Em not
+empty, its contents will be used
+as a password.
+This field will be ignored if the
+.Fl w
+option is used with a
+.Cm no
+or
+.Cm none
+argument.
+Be careful not to terminate this field with a closing
+.Ql \&:
+because it will be treated as part of the password.
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /etc/adduser.message" -compact
+.It Pa /etc/master.passwd
+user database
+.It Pa /etc/group
+group database
+.It Pa /etc/shells
+shell database
+.It Pa /etc/login.conf
+login classes database
+.It Pa /etc/adduser.conf
+configuration file for
+.Nm
+.It Pa /etc/adduser.message
+message file for
+.Nm
+.It Pa /usr/share/skel
+skeletal login directory
+.It Pa /var/log/adduser
+logfile for
+.Nm
+.El
+.Sh SEE ALSO
+.Xr chpass 1 ,
+.Xr passwd 1 ,
+.Xr adduser.conf 5 ,
+.Xr aliases 5 ,
+.Xr group 5 ,
+.Xr login.conf 5 ,
+.Xr passwd 5 ,
+.Xr shells 5 ,
+.Xr adding_user 8 ,
+.Xr pw 8 ,
+.Xr pwd_mkdb 8 ,
+.Xr rmuser 8 ,
+.Xr vipw 8 ,
+.Xr yp 8
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Fx 2.1 .
+.Sh AUTHORS
+.An -nosplit
+This manual page and the original script, in Perl, was written by
+.An Wolfram Schneider Aq Mt wosch@FreeBSD.org .
+The replacement script, written as a Bourne
+shell script with some enhancements, and the man page modification that
+came with it were done by
+.An Mike Makonnen Aq Mt mtm@identd.net .
+.Sh BUGS
+In order for
+.Nm
+to correctly expand variables such as
+.Va $username
+and
+.Va $randompass
+in the message sent to new users, it must let the shell evaluate
+each line of the message file.
+This means that shell commands can also be embedded in the message file.
+The
+.Nm
+utility attempts to mitigate the possibility of an attacker using this
+feature by refusing to evaluate the file if it is not owned and writable
+only by the root user.
+In addition, shell special characters and operators will have to be
+escaped when used in the message file.
+.Pp
+Also, password ageing and account expiry times are currently settable
+only in batch mode or when specified in
+.Pa /etc/adduser.conf .
+The user should be able to set them in interactive mode as well.
diff --git a/usr.sbin/adduser/adduser.conf.5 b/usr.sbin/adduser/adduser.conf.5
new file mode 100644
index 0000000..af9fe22
--- /dev/null
+++ b/usr.sbin/adduser/adduser.conf.5
@@ -0,0 +1,221 @@
+.\"
+.\" Copyright (c) 2004 Tom Rhodes
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 12, 2007
+.Dt ADDUSER.CONF 5
+.Os
+.Sh NAME
+.Nm adduser.conf
+.Nd
+.Xr adduser 8
+configuration file
+.Sh DESCRIPTION
+The
+.Pa /etc/adduser.conf
+file is automatically generated by the
+.Xr adduser 8
+utility when invoked with the
+.Fl C
+command-line option.
+It is not meant to be edited by hand.
+.Pp
+The
+.Pa /etc/adduser.conf
+file is used to pre-set certain configuration options for
+the
+.Xr adduser 8
+utility.
+When
+.Xr adduser 8
+is invoked, it will check to see if this file exists, and
+if so, the configuration will be used or offered as the
+default settings.
+The
+.Nm
+file offers three types of configuration:
+.Bl -bullet
+.It
+Default settings offered by
+.Xr adduser 8 .
+These options are specified in the configuration file and offered
+as the default during every invocation of the
+.Xr adduser 8
+utility.
+.It
+Configuration options which can be set in
+.Nm ,
+but overridden by passing a flag to
+.Xr adduser 8 .
+.It
+Configuration supported by
+.Xr adduser 8
+but not offered by a flag or during initial invocation.
+.El
+.Pp
+In the first case, these options can be set in
+.Nm
+but will still be offered when
+.Xr adduser 8
+is invoked.
+In the second case,
+.Xr adduser 8
+will read the configuration data unless a flag
+has been passed to override it.
+For example, the
+.Va defaultshell
+option.
+In the third case, the configuration will be utilized, but the
+user will never be prompted to modify the default setting by
+either a flag or an
+.Xr adduser 8
+prompt.
+For example, the
+.Va upwexpire
+setting.
+.Pp
+The following configuration options can be set in
+.Nm :
+.Bl -tag -width ".Va defaultgroups" -offset indent
+.It Va defaultLgroup
+The default group new users will be added to.
+.It Va defaultclass
+The default class to place users in as described in
+.Xr login.conf 5 .
+.It Va defaultgroups
+This option is used to specify what other groups the new account
+should be added to.
+.It Va passwdtype
+May be one of
+.Cm no , none , random ,
+or
+.Cm yes ,
+as described in
+.Xr adduser 8 .
+As such, the text is not duplicated here and may be
+read in
+.Xr adduser 8 .
+.It Va homeprefix
+The default home directory prefix, usually
+.Pa /home .
+.It Va defaultshell
+The user's default shell which may be any of the shells listed in
+.Xr shells 5 .
+.It Va udotdir
+Defines the location of the default shell and environment
+configuration files.
+.It Va msgfile
+Location of the default new user message file.
+This message will be sent to all new users if specified
+here or at the
+.Xr adduser 8
+prompt.
+.It Va disableflag
+The default message enclosed in brackets for the
+lock account prompt.
+.It Va upwexpire
+The default password expiration time.
+Format of the date is either a
+.Ux
+time in decimal, or a date in
+.Sm off
+.Ar dd No - Ar mmm No - Ar yy Op Ar yy
+.Sm on
+format, where
+.Ar dd
+is the day,
+.Ar mmm
+is the month in either numeric or
+alphabetic format, and
+.Ar yy Ns Op Ar yy
+is either a two or four digit year.
+This option also accepts a relative date in the form of
+.Sm off
+.Ar n Op Ar m h d w o y
+.Sm on
+where
+.Ar n
+is a decimal, octal (leading 0) or hexadecimal (leading 0x) digit
+followed by the number of Minutes, Hours, Days, Weeks, Months or
+Years from the current date at
+which the expiration time is to be set.
+.It Va uexpire
+The default account expire time.
+The format is similar to the
+.Va upwexpire
+option.
+.It Va ugecos
+The default information to be held in the GECOS field of
+.Pa /etc/master.passwd .
+.It Va uidstart
+The default user ID setting.
+This must be a number above 1000 and fewer than 65534.
+.El
+.Sh EXAMPLES
+The following is an example
+.Nm
+file created with the
+.Fl C
+.Xr adduser 8
+flag and modified.
+.Bd -literal -offset indent
+# Configuration file for adduser(8).
+# NOTE: only *some* variables are saved.
+# Last Modified on Fri Mar 30 14:04:05 EST 2004.
+
+defaultLgroup=
+defaultclass=
+defaultgroups=
+passwdtype=yes
+homeprefix=/home
+defaultshell=/bin/csh
+udotdir=/usr/share/skel
+msgfile=/etc/adduser.msg
+disableflag=
+upwexpire=91d # Expire passwords 91 days after creation.
+.Ed
+.Sh SEE ALSO
+.Xr group 5 ,
+.Xr passwd 5 ,
+.Xr adduser 8 ,
+.Xr pw 8 ,
+.Xr rmuser 8
+.Sh HISTORY
+The
+.Nm
+manual page first appeared in
+.Fx 5.3 .
+.Sh AUTHORS
+This manual page was written by
+.An Tom Rhodes Aq Mt trhodes@FreeBSD.org .
+.Sh BUGS
+The internal variables documented here may change without notice.
+Do not rely on them.
+To modify this file invoke
+.Xr adduser 8
+with the
+.Fl C
+option instead.
diff --git a/usr.sbin/adduser/adduser.sh b/usr.sbin/adduser/adduser.sh
new file mode 100644
index 0000000..4b0a6f6
--- /dev/null
+++ b/usr.sbin/adduser/adduser.sh
@@ -0,0 +1,1051 @@
+#!/bin/sh
+#
+# Copyright (c) 2002-2004 Michael Telahun Makonnen. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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.
+#
+# Email: Mike Makonnen <mtm@FreeBSD.Org>
+#
+# $FreeBSD$
+#
+
+# err msg
+# Display $msg on stderr, unless we're being quiet.
+#
+err() {
+ if [ -z "$quietflag" ]; then
+ echo 1>&2 ${THISCMD}: ERROR: $*
+ fi
+}
+
+# info msg
+# Display $msg on stdout, unless we're being quiet.
+#
+info() {
+ if [ -z "$quietflag" ]; then
+ echo ${THISCMD}: INFO: $*
+ fi
+}
+
+# get_nextuid
+# Output the value of $_uid if it is available for use. If it
+# is not, output the value of the next higher uid that is available.
+# If a uid is not specified, output the first available uid, as indicated
+# by pw(8).
+#
+get_nextuid () {
+ _uid=$1
+ _nextuid=
+
+ if [ -z "$_uid" ]; then
+ _nextuid="`${PWCMD} usernext | cut -f1 -d:`"
+ else
+ while : ; do
+ ${PWCMD} usershow $_uid > /dev/null 2>&1
+ if [ ! "$?" -eq 0 ]; then
+ _nextuid=$_uid
+ break
+ fi
+ _uid=$(($_uid + 1))
+ done
+ fi
+ echo $_nextuid
+}
+
+# show_usage
+# Display usage information for this utility.
+#
+show_usage() {
+ echo "usage: ${THISCMD} [options]"
+ echo " options may include:"
+ echo " -C save to the configuration file only"
+ echo " -D do not attempt to create the home directory"
+ echo " -E disable this account after creation"
+ echo " -G additional groups to add accounts to"
+ echo " -L login class of the user"
+ echo " -M file permission for home directory"
+ echo " -N do not read configuration file"
+ echo " -S a nonexistent shell is not an error"
+ echo " -d home directory"
+ echo " -f file from which input will be received"
+ echo " -g default login group"
+ echo " -h display this usage message"
+ echo " -k path to skeleton home directory"
+ echo " -m user welcome message file"
+ echo " -q absolute minimal user feedback"
+ echo " -s shell"
+ echo " -u uid to start at"
+ echo " -w password type: no, none, yes or random"
+}
+
+# valid_shells
+# Outputs a list of valid shells from /etc/shells. Only the
+# basename of the shell is output.
+#
+valid_shells() {
+ _prefix=
+ cat ${ETCSHELLS} |
+ while read _path _junk ; do
+ case $_path in
+ \#*|'')
+ ;;
+ *)
+ echo -n "${_prefix}`basename $_path`"
+ _prefix=' '
+ ;;
+ esac
+ done
+
+ # /usr/sbin/nologin is a special case
+ [ -x "${NOLOGIN_PATH}" ] && echo -n " ${NOLOGIN}"
+}
+
+# fullpath_from_shell shell
+# Given $shell, which is either the full path to a shell or
+# the basename component of a valid shell, get the
+# full path to the shell from the /etc/shells file.
+#
+fullpath_from_shell() {
+ _shell=$1
+ [ -z "$_shell" ] && return 1
+
+ # /usr/sbin/nologin is a special case; it needs to be handled
+ # before the cat | while loop, since a 'return' from within
+ # a subshell will not terminate the function's execution, and
+ # the path to the nologin shell might be printed out twice.
+ #
+ if [ "$_shell" = "${NOLOGIN}" -o \
+ "$_shell" = "${NOLOGIN_PATH}" ]; then
+ echo ${NOLOGIN_PATH}
+ return 0;
+ fi
+
+ cat ${ETCSHELLS} |
+ while read _path _junk ; do
+ case "$_path" in
+ \#*|'')
+ ;;
+ *)
+ if [ "$_path" = "$_shell" -o \
+ "`basename $_path`" = "$_shell" ]; then
+ echo $_path
+ return 0
+ fi
+ ;;
+ esac
+ done
+
+ return 1
+}
+
+# shell_exists shell
+# If the given shell is listed in ${ETCSHELLS} or it is
+# the nologin shell this function will return 0.
+# Otherwise, it will return 1. If shell is valid but
+# the path is invalid or it is not executable it
+# will emit an informational message saying so.
+#
+shell_exists() {
+ _sh="$1"
+ _shellchk="${GREPCMD} '^$_sh$' ${ETCSHELLS} > /dev/null 2>&1"
+
+ if ! eval $_shellchk; then
+ # The nologin shell is not listed in /etc/shells.
+ if [ "$_sh" != "${NOLOGIN_PATH}" ]; then
+ err "Invalid shell ($_sh) for user $username."
+ return 1
+ fi
+ fi
+ ! [ -x "$_sh" ] &&
+ info "The shell ($_sh) does not exist or is not executable."
+
+ return 0
+}
+
+# save_config
+# Save some variables to a configuration file.
+# Note: not all script variables are saved, only those that
+# it makes sense to save.
+#
+save_config() {
+ echo "# Configuration file for adduser(8)." > ${ADDUSERCONF}
+ echo "# NOTE: only *some* variables are saved." >> ${ADDUSERCONF}
+ echo "# Last Modified on `${DATECMD}`." >> ${ADDUSERCONF}
+ echo '' >> ${ADDUSERCONF}
+ echo "defaultHomePerm=$uhomeperm" >> ${ADDUSERCONF}
+ echo "defaultLgroup=$ulogingroup" >> ${ADDUSERCONF}
+ echo "defaultclass=$uclass" >> ${ADDUSERCONF}
+ echo "defaultgroups=$ugroups" >> ${ADDUSERCONF}
+ echo "passwdtype=$passwdtype" >> ${ADDUSERCONF}
+ echo "homeprefix=$homeprefix" >> ${ADDUSERCONF}
+ echo "defaultshell=$ushell" >> ${ADDUSERCONF}
+ echo "udotdir=$udotdir" >> ${ADDUSERCONF}
+ echo "msgfile=$msgfile" >> ${ADDUSERCONF}
+ echo "disableflag=$disableflag" >> ${ADDUSERCONF}
+ echo "uidstart=$uidstart" >> ${ADDUSERCONF}
+}
+
+# add_user
+# Add a user to the user database. If the user chose to send a welcome
+# message or lock the account, do so.
+#
+add_user() {
+
+ # Is this a configuration run? If so, don't modify user database.
+ #
+ if [ -n "$configflag" ]; then
+ save_config
+ return
+ fi
+
+ _uid=
+ _name=
+ _comment=
+ _gecos=
+ _home=
+ _group=
+ _grouplist=
+ _shell=
+ _class=
+ _dotdir=
+ _expire=
+ _pwexpire=
+ _passwd=
+ _upasswd=
+ _passwdmethod=
+
+ _name="-n '$username'"
+ [ -n "$uuid" ] && _uid='-u "$uuid"'
+ [ -n "$ulogingroup" ] && _group='-g "$ulogingroup"'
+ [ -n "$ugroups" ] && _grouplist='-G "$ugroups"'
+ [ -n "$ushell" ] && _shell='-s "$ushell"'
+ [ -n "$uclass" ] && _class='-L "$uclass"'
+ [ -n "$ugecos" ] && _comment='-c "$ugecos"'
+ [ -n "$udotdir" ] && _dotdir='-k "$udotdir"'
+ [ -n "$uexpire" ] && _expire='-e "$uexpire"'
+ [ -n "$upwexpire" ] && _pwexpire='-p "$upwexpire"'
+ if [ -z "$Dflag" -a -n "$uhome" ]; then
+ # The /nonexistent home directory is special. It
+ # means the user has no home directory.
+ if [ "$uhome" = "$NOHOME" ]; then
+ _home='-d "$uhome"'
+ else
+ # Use home directory permissions if specified
+ if [ -n "$uhomeperm" ]; then
+ _home='-m -d "$uhome" -M "$uhomeperm"'
+ else
+ _home='-m -d "$uhome"'
+ fi
+ fi
+ elif [ -n "$Dflag" -a -n "$uhome" ]; then
+ _home='-d "$uhome"'
+ fi
+ case $passwdtype in
+ no)
+ _passwdmethod="-w no"
+ _passwd="-h -"
+ ;;
+ yes)
+ # Note on processing the password: The outer double quotes
+ # make literal everything except ` and \ and $.
+ # The outer single quotes make literal ` and $.
+ # We can ensure the \ isn't treated specially by specifying
+ # the -r switch to the read command used to obtain the input.
+ #
+ _passwdmethod="-w yes"
+ _passwd="-h 0"
+ _upasswd='echo "$upass" |'
+ ;;
+ none)
+ _passwdmethod="-w none"
+ ;;
+ random)
+ _passwdmethod="-w random"
+ ;;
+ esac
+
+ _pwcmd="$_upasswd ${PWCMD} useradd $_uid $_name $_group $_grouplist $_comment"
+ _pwcmd="$_pwcmd $_shell $_class $_home $_dotdir $_passwdmethod $_passwd"
+ _pwcmd="$_pwcmd $_expire $_pwexpire"
+
+ if ! _output=`eval $_pwcmd` ; then
+ err "There was an error adding user ($username)."
+ return 1
+ else
+ info "Successfully added ($username) to the user database."
+ if [ "random" = "$passwdtype" ]; then
+ randompass="$_output"
+ info "Password for ($username) is: $randompass"
+ fi
+ fi
+
+ if [ -n "$disableflag" ]; then
+ if ${PWCMD} lock $username ; then
+ info "Account ($username) is locked."
+ else
+ info "Account ($username) could NOT be locked."
+ fi
+ fi
+
+ _line=
+ _owner=
+ _perms=
+ if [ -n "$msgflag" ]; then
+ [ -r "$msgfile" ] && {
+ # We're evaluating the contents of an external file.
+ # Let's not open ourselves up for attack. _perms will
+ # be empty if it's writeable only by the owner. _owner
+ # will *NOT* be empty if the file is owned by root.
+ #
+ _dir="`dirname $msgfile`"
+ _file="`basename $msgfile`"
+ _perms=`/usr/bin/find $_dir -name $_file -perm +07022 -prune`
+ _owner=`/usr/bin/find $_dir -name $_file -user 0 -prune`
+ if [ -z "$_owner" -o -n "$_perms" ]; then
+ err "The message file ($msgfile) may be writeable only by root."
+ return 1
+ fi
+ cat "$msgfile" |
+ while read _line ; do
+ eval echo "$_line"
+ done | ${MAILCMD} -s"Welcome" ${username}
+ info "Sent welcome message to ($username)."
+ }
+ fi
+}
+
+# get_user
+# Reads username of the account from standard input or from a global
+# variable containing an account line from a file. The username is
+# required. If this is an interactive session it will prompt in
+# a loop until a username is entered. If it is batch processing from
+# a file it will output an error message and return to the caller.
+#
+get_user() {
+ _input=
+
+ # No need to take down user names if this is a configuration saving run.
+ [ -n "$configflag" ] && return
+
+ while : ; do
+ if [ -z "$fflag" ]; then
+ echo -n "Username: "
+ read _input
+ else
+ _input="`echo "$fileline" | cut -f1 -d:`"
+ fi
+
+ # There *must* be a username, and it must not exist. If
+ # this is an interactive session give the user an
+ # opportunity to retry.
+ #
+ if [ -z "$_input" ]; then
+ err "You must enter a username!"
+ [ -z "$fflag" ] && continue
+ fi
+ ${PWCMD} usershow $_input > /dev/null 2>&1
+ if [ "$?" -eq 0 ]; then
+ err "User exists!"
+ [ -z "$fflag" ] && continue
+ fi
+ break
+ done
+ username="$_input"
+}
+
+# get_gecos
+# Reads extra information about the user. Can be used both in interactive
+# and batch (from file) mode.
+#
+get_gecos() {
+ _input=
+
+ # No need to take down additional user information for a configuration run.
+ [ -n "$configflag" ] && return
+
+ if [ -z "$fflag" ]; then
+ echo -n "Full name: "
+ read _input
+ else
+ _input="`echo "$fileline" | cut -f7 -d:`"
+ fi
+ ugecos="$_input"
+}
+
+# get_shell
+# Get the account's shell. Works in interactive and batch mode. It
+# accepts either the base name of the shell or the full path.
+# If an invalid shell is entered it will simply use the default shell.
+#
+get_shell() {
+ _input=
+ _fullpath=
+ ushell="$defaultshell"
+
+ # Make sure the current value of the shell is a valid one
+ if [ -z "$Sflag" ]; then
+ if ! shell_exists $ushell ; then
+ info "Using default shell ${defaultshell}."
+ ushell="$defaultshell"
+ fi
+ fi
+
+ if [ -z "$fflag" ]; then
+ echo -n "Shell ($shells) [`basename $ushell`]: "
+ read _input
+ else
+ _input="`echo "$fileline" | cut -f9 -d:`"
+ fi
+ if [ -n "$_input" ]; then
+ if [ -n "$Sflag" ]; then
+ ushell="$_input"
+ else
+ _fullpath=`fullpath_from_shell $_input`
+ if [ -n "$_fullpath" ]; then
+ ushell="$_fullpath"
+ else
+ err "Invalid shell ($_input) for user $username."
+ info "Using default shell ${defaultshell}."
+ ushell="$defaultshell"
+ fi
+ fi
+ fi
+}
+
+# get_homedir
+# Reads the account's home directory. Used both with interactive input
+# and batch input.
+#
+get_homedir() {
+ _input=
+ if [ -z "$fflag" ]; then
+ echo -n "Home directory [${homeprefix}/${username}]: "
+ read _input
+ else
+ _input="`echo "$fileline" | cut -f8 -d:`"
+ fi
+
+ if [ -n "$_input" ]; then
+ uhome="$_input"
+ # if this is a configuration run, then user input is the home
+ # directory prefix. Otherwise it is understood to
+ # be $prefix/$user
+ #
+ [ -z "$configflag" ] && homeprefix="`dirname $uhome`" || homeprefix="$uhome"
+ else
+ uhome="${homeprefix}/${username}"
+ fi
+}
+
+# get_homeperm
+# Reads the account's home directory permissions.
+#
+get_homeperm() {
+ uhomeperm=$defaultHomePerm
+ _input=
+ _prompt=
+
+ if [ -n "$uhomeperm" ]; then
+ _prompt="Home directory permissions [${uhomeperm}]: "
+ else
+ _prompt="Home directory permissions (Leave empty for default): "
+ fi
+ if [ -z "$fflag" ]; then
+ echo -n "$_prompt"
+ read _input
+ fi
+
+ if [ -n "$_input" ]; then
+ uhomeperm="$_input"
+ fi
+}
+
+# get_uid
+# Reads a numeric userid in an interactive or batch session. Automatically
+# allocates one if it is not specified.
+#
+get_uid() {
+ uuid=${uidstart}
+ _input=
+ _prompt=
+
+ if [ -n "$uuid" ]; then
+ uuid=`get_nextuid $uuid`
+ _prompt="Uid [$uuid]: "
+ else
+ _prompt="Uid (Leave empty for default): "
+ fi
+ if [ -z "$fflag" ]; then
+ echo -n "$_prompt"
+ read _input
+ else
+ _input="`echo "$fileline" | cut -f2 -d:`"
+ fi
+
+ [ -n "$_input" ] && uuid=$_input
+ uuid=`get_nextuid $uuid`
+ uidstart=$uuid
+}
+
+# get_class
+# Reads login class of account. Can be used in interactive or batch mode.
+#
+get_class() {
+ uclass="$defaultclass"
+ _input=
+ _class=${uclass:-"default"}
+
+ if [ -z "$fflag" ]; then
+ echo -n "Login class [$_class]: "
+ read _input
+ else
+ _input="`echo "$fileline" | cut -f4 -d:`"
+ fi
+
+ [ -n "$_input" ] && uclass="$_input"
+}
+
+# get_logingroup
+# Reads user's login group. Can be used in both interactive and batch
+# modes. The specified value can be a group name or its numeric id.
+# This routine leaves the field blank if nothing is provided and
+# a default login group has not been set. The pw(8) command
+# will then provide a login group with the same name as the username.
+#
+get_logingroup() {
+ ulogingroup="$defaultLgroup"
+ _input=
+
+ if [ -z "$fflag" ]; then
+ echo -n "Login group [${ulogingroup:-$username}]: "
+ read _input
+ else
+ _input="`echo "$fileline" | cut -f3 -d:`"
+ fi
+
+ # Pw(8) will use the username as login group if it's left empty
+ [ -n "$_input" ] && ulogingroup="$_input"
+}
+
+# get_groups
+# Read additional groups for the user. It can be used in both interactive
+# and batch modes.
+#
+get_groups() {
+ ugroups="$defaultgroups"
+ _input=
+ _group=${ulogingroup:-"${username}"}
+
+ if [ -z "$configflag" ]; then
+ [ -z "$fflag" ] && echo -n "Login group is $_group. Invite $username"
+ [ -z "$fflag" ] && echo -n " into other groups? [$ugroups]: "
+ else
+ [ -z "$fflag" ] && echo -n "Enter additional groups [$ugroups]: "
+ fi
+ read _input
+
+ [ -n "$_input" ] && ugroups="$_input"
+}
+
+# get_expire_dates
+# Read expiry information for the account and also for the password. This
+# routine is used only from batch processing mode.
+#
+get_expire_dates() {
+ upwexpire="`echo "$fileline" | cut -f5 -d:`"
+ uexpire="`echo "$fileline" | cut -f6 -d:`"
+}
+
+# get_password
+# Read the password in batch processing mode. The password field matters
+# only when the password type is "yes" or "random". If the field is empty and the
+# password type is "yes", then it assumes the account has an empty passsword
+# and changes the password type accordingly. If the password type is "random"
+# and the password field is NOT empty, then it assumes the account will NOT
+# have a random password and set passwdtype to "yes."
+#
+get_password() {
+ # We may temporarily change a password type. Make sure it's changed
+ # back to whatever it was before we process the next account.
+ #
+ [ -n "$savedpwtype" ] && {
+ passwdtype=$savedpwtype
+ savedpwtype=
+ }
+
+ # There may be a ':' in the password
+ upass=${fileline#*:*:*:*:*:*:*:*:*:}
+
+ if [ -z "$upass" ]; then
+ case $passwdtype in
+ yes)
+ # if it's empty, assume an empty password
+ passwdtype=none
+ savedpwtype=yes
+ ;;
+ esac
+ else
+ case $passwdtype in
+ random)
+ passwdtype=yes
+ savedpwtype=random
+ ;;
+ esac
+ fi
+}
+
+# input_from_file
+# Reads a line of account information from standard input and
+# adds it to the user database.
+#
+input_from_file() {
+ _field=
+
+ while read -r fileline ; do
+ case "$fileline" in
+ \#*|'')
+ ;;
+ *)
+ get_user || continue
+ get_gecos
+ get_uid
+ get_logingroup
+ get_class
+ get_shell
+ get_homedir
+ get_homeperm
+ get_password
+ get_expire_dates
+ ugroups="$defaultgroups"
+
+ add_user
+ ;;
+ esac
+ done
+}
+
+# input_interactive
+# Prompts for user information interactively, and commits to
+# the user database.
+#
+input_interactive() {
+ _disable=
+ _pass=
+ _passconfirm=
+ _random="no"
+ _emptypass="no"
+ _usepass="yes"
+ _logingroup_ok="no"
+ _groups_ok="no"
+ case $passwdtype in
+ none)
+ _emptypass="yes"
+ _usepass="yes"
+ ;;
+ no)
+ _usepass="no"
+ ;;
+ random)
+ _random="yes"
+ ;;
+ esac
+
+ get_user
+ get_gecos
+ get_uid
+
+ # The case where group = user is handled elsewhere, so
+ # validate any other groups the user is invited to.
+ until [ "$_logingroup_ok" = yes ]; do
+ get_logingroup
+ _logingroup_ok=yes
+ if [ -n "$ulogingroup" -a "$username" != "$ulogingroup" ]; then
+ if ! ${PWCMD} show group $ulogingroup > /dev/null 2>&1; then
+ echo "Group $ulogingroup does not exist!"
+ _logingroup_ok=no
+ fi
+ fi
+ done
+ until [ "$_groups_ok" = yes ]; do
+ get_groups
+ _groups_ok=yes
+ for i in $ugroups; do
+ if [ "$username" != "$i" ]; then
+ if ! ${PWCMD} show group $i > /dev/null 2>&1; then
+ echo "Group $i does not exist!"
+ _groups_ok=no
+ fi
+ fi
+ done
+ done
+
+ get_class
+ get_shell
+ get_homedir
+ get_homeperm
+
+ while : ; do
+ echo -n "Use password-based authentication? [$_usepass]: "
+ read _input
+ [ -z "$_input" ] && _input=$_usepass
+ case $_input in
+ [Nn][Oo]|[Nn])
+ passwdtype="no"
+ ;;
+ [Yy][Ee][Ss]|[Yy][Ee]|[Yy])
+ while : ; do
+ echo -n "Use an empty password? (yes/no) [$_emptypass]: "
+ read _input
+ [ -n "$_input" ] && _emptypass=$_input
+ case $_emptypass in
+ [Nn][Oo]|[Nn])
+ echo -n "Use a random password? (yes/no) [$_random]: "
+ read _input
+ [ -n "$_input" ] && _random="$_input"
+ case $_random in
+ [Yy][Ee][Ss]|[Yy][Ee]|[Yy])
+ passwdtype="random"
+ break
+ ;;
+ esac
+ passwdtype="yes"
+ [ -n "$configflag" ] && break
+ trap 'stty echo; exit' 0 1 2 3 15
+ stty -echo
+ echo -n "Enter password: "
+ read -r upass
+ echo''
+ echo -n "Enter password again: "
+ read -r _passconfirm
+ echo ''
+ stty echo
+ # if user entered a blank password
+ # explicitly ask again.
+ [ -z "$upass" -a -z "$_passconfirm" ] \
+ && continue
+ ;;
+ [Yy][Ee][Ss]|[Yy][Ee]|[Yy])
+ passwdtype="none"
+ break;
+ ;;
+ *)
+ # invalid answer; repeat the loop
+ continue
+ ;;
+ esac
+ if [ "$upass" != "$_passconfirm" ]; then
+ echo "Passwords did not match!"
+ continue
+ fi
+ break
+ done
+ ;;
+ *)
+ # invalid answer; repeat loop
+ continue
+ ;;
+ esac
+ break;
+ done
+ _disable=${disableflag:-"no"}
+ while : ; do
+ echo -n "Lock out the account after creation? [$_disable]: "
+ read _input
+ [ -z "$_input" ] && _input=$_disable
+ case $_input in
+ [Nn][Oo]|[Nn])
+ disableflag=
+ ;;
+ [Yy][Ee][Ss]|[Yy][Ee]|[Yy])
+ disableflag=yes
+ ;;
+ *)
+ # invalid answer; repeat loop
+ continue
+ ;;
+ esac
+ break
+ done
+
+ # Display the information we have so far and prompt to
+ # commit it.
+ #
+ _disable=${disableflag:-"no"}
+ [ -z "$configflag" ] && printf "%-10s : %s\n" Username $username
+ case $passwdtype in
+ yes)
+ _pass='*****'
+ ;;
+ no)
+ _pass='<disabled>'
+ ;;
+ none)
+ _pass='<blank>'
+ ;;
+ random)
+ _pass='<random>'
+ ;;
+ esac
+ [ -z "$configflag" ] && printf "%-10s : %s\n" "Password" "$_pass"
+ [ -n "$configflag" ] && printf "%-10s : %s\n" "Pass Type" "$passwdtype"
+ [ -z "$configflag" ] && printf "%-10s : %s\n" "Full Name" "$ugecos"
+ [ -z "$configflag" ] && printf "%-10s : %s\n" "Uid" "$uuid"
+ printf "%-10s : %s\n" "Class" "$uclass"
+ printf "%-10s : %s %s\n" "Groups" "${ulogingroup:-$username}" "$ugroups"
+ printf "%-10s : %s\n" "Home" "$uhome"
+ printf "%-10s : %s\n" "Home Mode" "$uhomeperm"
+ printf "%-10s : %s\n" "Shell" "$ushell"
+ printf "%-10s : %s\n" "Locked" "$_disable"
+ while : ; do
+ echo -n "OK? (yes/no): "
+ read _input
+ case $_input in
+ [Nn][Oo]|[Nn])
+ return 1
+ ;;
+ [Yy][Ee][Ss]|[Yy][Ee]|[Yy])
+ add_user
+ ;;
+ *)
+ continue
+ ;;
+ esac
+ break
+ done
+ return 0
+}
+
+#### END SUBROUTINE DEFINITION ####
+
+THISCMD=`/usr/bin/basename $0`
+DEFAULTSHELL=/bin/sh
+ADDUSERCONF="${ADDUSERCONF:-/etc/adduser.conf}"
+PWCMD="${PWCMD:-/usr/sbin/pw}"
+MAILCMD="${MAILCMD:-mail}"
+ETCSHELLS="${ETCSHELLS:-/etc/shells}"
+NOHOME="/nonexistent"
+NOLOGIN="nologin"
+NOLOGIN_PATH="/usr/sbin/nologin"
+GREPCMD="/usr/bin/grep"
+DATECMD="/bin/date"
+
+# Set default values
+#
+username=
+uuid=
+uidstart=
+ugecos=
+ulogingroup=
+uclass=
+uhome=
+uhomeperm=
+upass=
+ushell=
+udotdir=/usr/share/skel
+ugroups=
+uexpire=
+upwexpire=
+shells="`valid_shells`"
+passwdtype="yes"
+msgfile=/etc/adduser.msg
+msgflag=
+quietflag=
+configflag=
+fflag=
+infile=
+disableflag=
+Dflag=
+Sflag=
+readconfig="yes"
+homeprefix="/home"
+randompass=
+fileline=
+savedpwtype=
+defaultclass=
+defaultLgroup=
+defaultgroups=
+defaultshell="${DEFAULTSHELL}"
+defaultHomePerm=
+
+# Make sure the user running this program is root. This isn't a security
+# measure as much as it is a useful method of reminding the user to
+# 'su -' before he/she wastes time entering data that won't be saved.
+#
+procowner=${procowner:-`/usr/bin/id -u`}
+if [ "$procowner" != "0" ]; then
+ err 'you must be the super-user (uid 0) to use this utility.'
+ exit 1
+fi
+
+# Override from our conf file
+# Quickly go through the commandline line to see if we should read
+# from our configuration file. The actual parsing of the commandline
+# arguments happens after we read in our configuration file (commandline
+# should override configuration file).
+#
+for _i in $* ; do
+ if [ "$_i" = "-N" ]; then
+ readconfig=
+ break;
+ fi
+done
+if [ -n "$readconfig" ]; then
+ # On a long-lived system, the first time this script is run it
+ # will barf upon reading the configuration file for its perl predecessor.
+ if ( . ${ADDUSERCONF} > /dev/null 2>&1 ); then
+ [ -r ${ADDUSERCONF} ] && . ${ADDUSERCONF} > /dev/null 2>&1
+ fi
+fi
+
+# Process command-line options
+#
+for _switch ; do
+ case $_switch in
+ -L)
+ defaultclass="$2"
+ shift; shift
+ ;;
+ -C)
+ configflag=yes
+ shift
+ ;;
+ -D)
+ Dflag=yes
+ shift
+ ;;
+ -E)
+ disableflag=yes
+ shift
+ ;;
+ -k)
+ udotdir="$2"
+ shift; shift
+ ;;
+ -f)
+ [ "$2" != "-" ] && infile="$2"
+ fflag=yes
+ shift; shift
+ ;;
+ -g)
+ defaultLgroup="$2"
+ shift; shift
+ ;;
+ -G)
+ defaultgroups="$2"
+ shift; shift
+ ;;
+ -h)
+ show_usage
+ exit 0
+ ;;
+ -d)
+ homeprefix="$2"
+ shift; shift
+ ;;
+ -m)
+ case "$2" in
+ [Nn][Oo])
+ msgflag=
+ ;;
+ *)
+ msgflag=yes
+ msgfile="$2"
+ ;;
+ esac
+ shift; shift
+ ;;
+ -M)
+ defaultHomePerm=$2
+ shift; shift
+ ;;
+ -N)
+ readconfig=
+ shift
+ ;;
+ -w)
+ case "$2" in
+ no|none|random|yes)
+ passwdtype=$2
+ ;;
+ *)
+ show_usage
+ exit 1
+ ;;
+ esac
+ shift; shift
+ ;;
+ -q)
+ quietflag=yes
+ shift
+ ;;
+ -s)
+ defaultshell="`fullpath_from_shell $2`"
+ shift; shift
+ ;;
+ -S)
+ Sflag=yes
+ shift
+ ;;
+ -u)
+ uidstart=$2
+ shift; shift
+ ;;
+ esac
+done
+
+# If the -f switch was used, get input from a file. Otherwise,
+# this is an interactive session.
+#
+if [ -n "$fflag" ]; then
+ if [ -z "$infile" ]; then
+ input_from_file
+ elif [ -n "$infile" ]; then
+ if [ -r "$infile" ]; then
+ input_from_file < $infile
+ else
+ err "File ($infile) is unreadable or does not exist."
+ fi
+ fi
+else
+ input_interactive
+ while : ; do
+ if [ -z "$configflag" ]; then
+ echo -n "Add another user? (yes/no): "
+ else
+ echo -n "Re-edit the default configuration? (yes/no): "
+ fi
+ read _input
+ case $_input in
+ [Yy][Ee][Ss]|[Yy][Ee]|[Yy])
+ uidstart=`get_nextuid $uidstart`
+ input_interactive
+ continue
+ ;;
+ [Nn][Oo]|[Nn])
+ echo "Goodbye!"
+ ;;
+ *)
+ continue
+ ;;
+ esac
+ break
+ done
+fi
diff --git a/usr.sbin/adduser/rmuser.8 b/usr.sbin/adduser/rmuser.8
new file mode 100644
index 0000000..e24d5ee
--- /dev/null
+++ b/usr.sbin/adduser/rmuser.8
@@ -0,0 +1,210 @@
+.\" Copyright 1995, 1996, 1997
+.\" Guy Helmer, Ames, Iowa 50014. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer as
+.\" the first lines of this file unmodified.
+.\" 2. Redistributions in binary form must 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 GUY HELMER ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL GUY HELMER BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (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 10, 2002
+.Dt RMUSER 8
+.Os
+.Sh NAME
+.Nm rmuser
+.Nd remove users from the system
+.Sh SYNOPSIS
+.Nm
+.Op Fl yv
+.Op Fl f Ar file
+.Op Ar username ...
+.Sh DESCRIPTION
+The
+.Nm
+utility removes one or more users submitted on the command line
+or from a file.
+In removing a user from the system, this utility:
+.Bl -enum
+.It
+Removes the user's
+.Xr crontab 1
+entry (if any).
+.It
+Removes any
+.Xr at 1
+jobs belonging to the user.
+.It
+Sends a
+.Dv SIGKILL
+signal to all processes owned by the user.
+.It
+Removes the user from the system's local password file.
+.It
+Removes the user's home directory (if it is owned by the user),
+including handling of symbolic links in the path to the actual home
+directory.
+.It
+Removes the incoming mail and POP daemon mail files belonging to the
+user from
+.Pa /var/mail .
+.It
+Removes all files owned by the user from
+.Pa /tmp , /var/tmp ,
+and
+.Pa /var/tmp/vi.recover .
+.It
+Removes the username from all groups to which it belongs in
+.Pa /etc/group .
+(If a group becomes empty and the group name is the same as the username,
+the group is removed; this complements
+.Xr adduser 8 Ns 's
+per-user unique groups.)
+.It
+Removes all message queues, shared memory segments and
+semaphores owned by the user.
+.El
+.Pp
+The
+.Nm
+utility refuses to remove users whose UID is 0 (typically root), since
+certain actions (namely, killing all the user's processes, and perhaps
+removing the user's home directory) would cause damage to a running system.
+If it is necessary to remove a user whose UID is 0, see
+.Xr vipw 8
+for information on directly editing the password file.
+.Pp
+If
+.Nm
+was not invoked with the
+.Fl y
+option, it will
+show the selected user's password file entry and ask for confirmation
+that the user be removed.
+It will then ask for confirmation to delete
+the user's home directory.
+If the answer is in the affirmative, the home
+directory and any files and subdirectories under it will be deleted only if
+they are owned by the user.
+See
+.Xr pw 8
+for more details.
+.Pp
+As
+.Nm
+operates, it informs the user regarding the current activity.
+If any
+errors occur, they are posted to standard error and, if it is possible for
+.Nm
+to continue, it will.
+.Pp
+The options are as follows:
+.Bl -tag -width ".Ar username"
+.It Fl f Ar file
+The
+.Nm
+utility will get a list of users to be removed from
+.Ar file ,
+which will contain one user per line.
+Anything following a hash mark
+.Pq Ql # ,
+including the hash mark itself, is considered a comment and will not
+be processed.
+If the file is owned by anyone other than a user with
+UID 0, or is writable by anyone other than the owner,
+.Nm
+will refuse to continue.
+.It Fl y
+Implicitly answer
+.Dq Li yes
+to any and all prompts.
+Currently, this includes
+prompts on whether to remove the specified user and whether to remove
+the home directory.
+This option requires that either the
+.Fl f
+option be used, or one or more user names be given as command line
+arguments.
+.It Fl v
+Enable verbose mode.
+Normally,
+the output includes one line per removed user;
+however,
+with this option
+.Nm
+will be much more chatty about the steps taken.
+.It Ar username
+Identifies one or more users to be removed; if not present,
+.Nm
+interactively asks for one or more users to be removed.
+.El
+.Sh FILES
+.Bl -tag -width "Pa /etc/master.passwd" -compact
+.It Pa /etc/master.passwd
+.It Pa /etc/passwd
+.It Pa /etc/group
+.It Pa /etc/spwd.db
+.It Pa /etc/pwd.db
+.El
+.Sh SEE ALSO
+.Xr at 1 ,
+.Xr chpass 1 ,
+.Xr crontab 1 ,
+.Xr finger 1 ,
+.Xr passwd 1 ,
+.Xr group 5 ,
+.Xr passwd 5 ,
+.Xr adduser 8 ,
+.Xr pw 8 ,
+.Xr pwd_mkdb 8 ,
+.Xr vipw 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 2.2 .
+.Sh BUGS
+The
+.Nm
+utility does not comprehensively search the file system for all files
+owned by the removed user and remove them; to do so on a system
+of any size is prohibitively slow and I/O intensive.
+It is also unable to remove symbolic links that were created by the
+user in
+.Pa /tmp
+or
+.Pa /var/tmp ,
+as symbolic links on
+.Bx 4.4
+file systems do not contain information
+as to who created them.
+Also, there may be other files created in
+.Pa /var/mail
+other than
+.Pa /var/mail/ Ns Ar username
+and
+.Pa /var/mail/.pop. Ns Ar username
+that are not owned by the removed user but should be removed.
+.Pp
+The
+.Nm
+utility has no knowledge of YP/NIS, and it operates only on the
+local password file.
diff --git a/usr.sbin/adduser/rmuser.sh b/usr.sbin/adduser/rmuser.sh
new file mode 100644
index 0000000..6b09225
--- /dev/null
+++ b/usr.sbin/adduser/rmuser.sh
@@ -0,0 +1,361 @@
+#!/bin/sh
+#
+# Copyright (c) 2002, 2003 Michael Telahun Makonnen. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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.
+#
+# Email: Mike Makonnen <mtm@FreeBSD.Org>
+#
+# $FreeBSD$
+#
+
+ATJOBDIR="/var/at/jobs"
+CRONJOBDIR="/var/cron/tabs"
+MAILSPOOL="/var/mail"
+SIGKILL="-KILL"
+TEMPDIRS="/tmp /var/tmp"
+THISCMD=`/usr/bin/basename $0`
+PWCMD="${PWCMD:-/usr/sbin/pw}"
+
+# err msg
+# Display $msg on stderr.
+#
+err() {
+ echo 1>&2 ${THISCMD}: $*
+}
+
+# verbose
+# Returns 0 if verbose mode is set, 1 if it is not.
+#
+verbose() {
+ [ -n "$vflag" ] && return 0 || return 1
+}
+
+# rm_files login
+# Removes files or empty directories belonging to $login from various
+# temporary directories.
+#
+rm_files() {
+ # The argument is required
+ [ -n $1 ] && login=$1 || return
+
+ totalcount=0
+ for _dir in ${TEMPDIRS} ; do
+ filecount=0
+ if [ ! -d $_dir ]; then
+ err "$_dir is not a valid directory."
+ continue
+ fi
+ verbose && echo -n "Removing files owned by ($login) in $_dir:"
+ filecount=`find 2>/dev/null "$_dir" -user "$login" -delete -print |
+ wc -l | sed 's/ *//'`
+ verbose && echo " $filecount removed."
+ totalcount=$(($totalcount + $filecount))
+ done
+ ! verbose && [ $totalcount -ne 0 ] && echo -n " files($totalcount)"
+}
+
+# rm_mail login
+# Removes unix mail and pop daemon files belonging to the user
+# specified in the $login argument.
+#
+rm_mail() {
+ # The argument is required
+ [ -n $1 ] && login=$1 || return
+
+ verbose && echo -n "Removing mail spool(s) for ($login):"
+ if [ -f ${MAILSPOOL}/$login ]; then
+ verbose && echo -n " ${MAILSPOOL}/$login" ||
+ echo -n " mailspool"
+ rm ${MAILSPOOL}/$login
+ fi
+ if [ -f ${MAILSPOOL}/.${login}.pop ]; then
+ verbose && echo -n " ${MAILSPOOL}/.${login}.pop" ||
+ echo -n " pop3"
+ rm ${MAILSPOOL}/.${login}.pop
+ fi
+ verbose && echo '.'
+}
+
+# kill_procs login
+# Send a SIGKILL to all processes owned by $login.
+#
+kill_procs() {
+ # The argument is required
+ [ -n $1 ] && login=$1 || return
+
+ verbose && echo -n "Terminating all processes owned by ($login):"
+ killcount=0
+ proclist=`ps 2>/dev/null -U $login | grep -v '^\ *PID' | awk '{print $1}'`
+ for _pid in $proclist ; do
+ kill 2>/dev/null ${SIGKILL} $_pid
+ killcount=$(($killcount + 1))
+ done
+ verbose && echo " ${SIGKILL} signal sent to $killcount processes."
+ ! verbose && [ $killcount -ne 0 ] && echo -n " processes(${killcount})"
+}
+
+# rm_at_jobs login
+# Remove at (1) jobs belonging to $login.
+#
+rm_at_jobs() {
+ # The argument is required
+ [ -n $1 ] && login=$1 || return
+
+ atjoblist=`find 2>/dev/null ${ATJOBDIR} -maxdepth 1 -user $login -print`
+ jobcount=0
+ verbose && echo -n "Removing at(1) jobs owned by ($login):"
+ for _atjob in $atjoblist ; do
+ rm -f $_atjob
+ jobcount=$(($jobcount + 1))
+ done
+ verbose && echo " $jobcount removed."
+ ! verbose && [ $jobcount -ne 0 ] && echo -n " at($jobcount)"
+}
+
+# rm_crontab login
+# Removes crontab file belonging to user $login.
+#
+rm_crontab() {
+ # The argument is required
+ [ -n $1 ] && login=$1 || return
+
+ verbose && echo -n "Removing crontab for ($login):"
+ if [ -f ${CRONJOBDIR}/$login ]; then
+ verbose && echo -n " ${CRONJOBDIR}/$login" || echo -n " crontab"
+ rm -f ${CRONJOBDIR}/$login
+ fi
+ verbose && echo '.'
+}
+
+# rm_ipc login
+# Remove all IPC mechanisms which are owned by $login.
+#
+rm_ipc() {
+ verbose && echo -n "Removing IPC mechanisms"
+ for i in s m q; do
+ ipcs -$i |
+ awk -v i=$i -v login=$1 '$1 == i && $5 == login { print $2 }' |
+ xargs -n 1 ipcrm -$i
+ done
+ verbose && echo '.'
+}
+
+# rm_user login
+# Remove user $login from the system. This subroutine makes use
+# of the pw(8) command to remove a user from the system. The pw(8)
+# command will remove the specified user from the user database
+# and group file and remove any crontabs. His home
+# directory will be removed if it is owned by him and contains no
+# files or subdirectories owned by other users. Mail spool files will
+# also be removed.
+#
+rm_user() {
+ # The argument is required
+ [ -n $1 ] && login=$1 || return
+
+ verbose && echo -n "Removing user ($login)"
+ [ -n "$pw_rswitch" ] && {
+ verbose && echo -n " (including home directory)"
+ ! verbose && echo -n " home"
+ }
+ ! verbose && echo -n " passwd"
+ verbose && echo -n " from the system:"
+ ${PWCMD} userdel -n $login $pw_rswitch
+ verbose && echo ' Done.'
+}
+
+# prompt_yesno msg
+# Prompts the user with a $msg. The answer is expected to be
+# yes, no, or some variation thereof. This subroutine returns 0
+# if the answer was yes, 1 if it was not.
+#
+prompt_yesno() {
+ # The argument is required
+ [ -n "$1" ] && msg="$1" || return
+
+ while : ; do
+ echo -n "$msg"
+ read _ans
+ case $_ans in
+ [Nn][Oo]|[Nn])
+ return 1
+ ;;
+ [Yy][Ee][Ss]|[Yy][Ee]|[Yy])
+ return 0
+ ;;
+ *)
+ ;;
+ esac
+ done
+}
+
+# show_usage
+# (no arguments)
+# Display usage message.
+#
+show_usage() {
+ echo "usage: ${THISCMD} [-yv] [-f file] [user ...]"
+ echo " if the -y switch is used, either the -f switch or"
+ echo " one or more user names must be given"
+}
+
+#### END SUBROUTINE DEFENITION ####
+
+ffile=
+fflag=
+procowner=
+pw_rswitch=
+userlist=
+yflag=
+vflag=
+
+procowner=`/usr/bin/id -u`
+if [ "$procowner" != "0" ]; then
+ err 'you must be root (0) to use this utility.'
+ exit 1
+fi
+
+args=`getopt 2>/dev/null yvf: $*`
+if [ "$?" != "0" ]; then
+ show_usage
+ exit 1
+fi
+set -- $args
+for _switch ; do
+ case $_switch in
+ -y)
+ yflag=1
+ shift
+ ;;
+ -v)
+ vflag=1
+ shift
+ ;;
+ -f)
+ fflag=1
+ ffile="$2"
+ shift; shift
+ ;;
+ --)
+ shift
+ break
+ ;;
+ esac
+done
+
+# Get user names from a file if the -f switch was used. Otherwise,
+# get them from the commandline arguments. If we're getting it
+# from a file, the file must be owned by and writable only by root.
+#
+if [ $fflag ]; then
+ _insecure=`find $ffile ! -user 0 -or -perm +0022`
+ if [ -n "$_insecure" ]; then
+ err "file ($ffile) must be owned by and writeable only by root."
+ exit 1
+ fi
+ if [ -r "$ffile" ]; then
+ userlist=`cat $ffile | while read _user _junk ; do
+ case $_user in
+ \#*|'')
+ ;;
+ *)
+ echo -n "$userlist $_user"
+ ;;
+ esac
+ done`
+ fi
+else
+ while [ $1 ] ; do
+ userlist="$userlist $1"
+ shift
+ done
+fi
+
+# If the -y or -f switch has been used and the list of users to remove
+# is empty it is a fatal error. Otherwise, prompt the user for a list
+# of one or more user names.
+#
+if [ ! "$userlist" ]; then
+ if [ $fflag ]; then
+ err "($ffile) does not exist or does not contain any user names."
+ exit 1
+ elif [ $yflag ]; then
+ show_usage
+ exit 1
+ else
+ echo -n "Please enter one or more usernames: "
+ read userlist
+ fi
+fi
+
+_user=
+_uid=
+for _user in $userlist ; do
+ # Make sure the name exists in the passwd database and that it
+ # does not have a uid of 0
+ #
+ userrec=`pw 2>/dev/null usershow -n $_user`
+ if [ "$?" != "0" ]; then
+ err "user ($_user) does not exist in the password database."
+ continue
+ fi
+ _uid=`echo $userrec | awk -F: '{print $3}'`
+ if [ "$_uid" = "0" ]; then
+ err "user ($_user) has uid 0. You may not remove this user."
+ continue
+ fi
+
+ # If the -y switch was not used ask for confirmation to remove the
+ # user and home directory.
+ #
+ if [ -z "$yflag" ]; then
+ echo "Matching password entry:"
+ echo
+ echo $userrec
+ echo
+ if ! prompt_yesno "Is this the entry you wish to remove? " ; then
+ continue
+ fi
+ _homedir=`echo $userrec | awk -F: '{print $9}'`
+ if prompt_yesno "Remove user's home directory ($_homedir)? "; then
+ pw_rswitch="-r"
+ fi
+ else
+ pw_rswitch="-r"
+ fi
+
+ # Disable any further attempts to log into this account
+ ${PWCMD} 2>/dev/null lock $_user
+
+ # Remove crontab, mail spool, etc. Then obliterate the user from
+ # the passwd and group database.
+ #
+ ! verbose && echo -n "Removing user ($_user):"
+ rm_crontab $_user
+ rm_at_jobs $_user
+ rm_ipc $_user
+ kill_procs $_user
+ rm_files $_user
+ rm_mail $_user
+ rm_user $_user
+ ! verbose && echo "."
+done
diff --git a/usr.sbin/amd/Makefile b/usr.sbin/amd/Makefile
new file mode 100644
index 0000000..77c5a4c
--- /dev/null
+++ b/usr.sbin/amd/Makefile
@@ -0,0 +1,13 @@
+# ex:ts=8
+#
+# Makefile for amd
+# This file is under a "BSD" copyright (c) by David O'Brien 1998
+#
+# $FreeBSD$
+
+SUBDIR= include libamu .WAIT \
+ amd amq fixmount fsinfo hlfsd mk-amd-map pawd \
+ scripts wire-test
+SUBDIR_PARALLEL=
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/amd/Makefile.inc b/usr.sbin/amd/Makefile.inc
new file mode 100644
index 0000000..8c397cf
--- /dev/null
+++ b/usr.sbin/amd/Makefile.inc
@@ -0,0 +1,40 @@
+# ex:ts=8
+#
+# $FreeBSD$
+#
+# Makefile for amd
+# This file is under a "BSD" copyright (c) by David O'Brien 1998.
+# Portions derived from
+# $NetBSD: Makefile.inc,v 1.10 1998/08/08 22:33:27 christos Exp $
+# Portions derived from amd/libamu/Makefile
+# $NetBSD: Makefile,v 1.8 1998/08/08 22:33:37 christos Exp $
+
+.include <src.opts.mk>
+
+CFLAGS+= -I. -I${.CURDIR}
+CFLAGS+= -I${.CURDIR}/../include
+.if exists(${.OBJDIR}/../include)
+CFLAGS+= -I${.OBJDIR}/../include
+.endif
+CFLAGS+= -I${.CURDIR}/../../../contrib/amd/include
+CFLAGS+= -I${.CURDIR}/../../../contrib/amd
+CFLAGS+= -DHAVE_CONFIG_H
+.if ${MK_NIS} == "no"
+CFLAGS+= -DHAVE_LOCALCONFIG_H
+.endif
+
+.if ${MK_HESIOD} != "no"
+CFLAGS+= -DYES_HESIOD
+.endif
+
+CFLAGS+= -DHOST_CPU=\"${MACHINE_CPUARCH}\" -DHOST_ARCH=\"${MACHINE_ARCH}\"
+
+RPCCOM= RPCGEN_CPP=${CPP:Q} rpcgen
+MOUNT_X= ${DESTDIR}/usr/include/rpcsvc/mount.x
+NFS_PROT_X= ${DESTDIR}/usr/include/rpcsvc/nfs_prot.x
+
+WARNS?= 1
+
+.if exists(${.CURDIR}/../../Makefile.inc)
+.include "${.CURDIR}/../../Makefile.inc"
+.endif
diff --git a/usr.sbin/amd/NOTES b/usr.sbin/amd/NOTES
new file mode 100644
index 0000000..b04244b
--- /dev/null
+++ b/usr.sbin/amd/NOTES
@@ -0,0 +1,3 @@
+amd/amd supports HESIOD, LDAP, and NIS+ which we don't presently.
+If they are added to FreeBSD, add info_hesiod.c, info_ldap.c, info_nisplus.c
+to amd/amd's Makefile.
diff --git a/usr.sbin/amd/amd/Makefile b/usr.sbin/amd/amd/Makefile
new file mode 100644
index 0000000..602c941
--- /dev/null
+++ b/usr.sbin/amd/amd/Makefile
@@ -0,0 +1,52 @@
+# ex:ts=8
+#
+# Makefile for amd
+# This file is under a "BSD" copyright (c) by David O'Brien 1998
+#
+# $FreeBSD$
+#
+
+.include <src.opts.mk>
+
+.PATH: ${.CURDIR}/../../../contrib/amd/amd
+
+PROG= amd
+MAN= amd.8
+SRCS= conf_parse.y conf_tok.l
+SRCS+= am_ops.c amd.c amfs_auto.c amfs_direct.c amfs_error.c amfs_generic.c
+SRCS+= amfs_host.c amfs_link.c amfs_linkx.c amfs_nfsl.c
+SRCS+= amfs_nfsx.c amfs_program.c amfs_root.c amfs_toplvl.c
+SRCS+= amfs_union.c amq_subr.c amq_svc.c autil.c clock.c conf.c
+SRCS+= get_args.c info_exec.c info_file.c info_ndbm.c info_passwd.c
+SRCS+= info_union.c map.c mapc.c mntfs.c nfs_prot_svc.c nfs_start.c
+SRCS+= nfs_subr.c ops_cdfs.c ops_mfs.c ops_nfs.c ops_nfs3.c
+SRCS+= ops_nullfs.c ops_pcfs.c ops_tfs.c ops_ufs.c ops_umapfs.c
+SRCS+= ops_unionfs.c opts.c readdir.c restart.c rpc_fwd.c sched.c
+SRCS+= srvr_amfs_auto.c srvr_nfs.c
+
+CFLAGS+= -I${.CURDIR}/../../../contrib/amd/amd \
+ -I${DESTDIR}/usr/include/rpcsvc
+
+LIBADD= amu wrap
+
+CLEANFILES+= conf_parse.c conf_parse.h conf_tok.c
+
+conf_tok.o: conf_parse.h
+
+# These are generated at compile time
+SRCS+= mount_xdr.c
+CLEANFILES+= mount_xdr.c
+
+mount_xdr.c: ${MOUNT_X}
+ ${RPCCOM} -c -DWANT_NFS3 ${MOUNT_X} -o ${.TARGET}
+
+.if ${MK_HESIOD} != "no"
+SRCS+= info_hesiod.c
+CFLAGS+= -DHAVE_MAP_HESIOD
+.endif
+
+.if ${MK_NIS} != "no"
+SRCS+= info_nis.c
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/amd/amd/Makefile.depend b/usr.sbin/amd/amd/Makefile.depend
new file mode 100644
index 0000000..c0afb66
--- /dev/null
+++ b/usr.sbin/amd/amd/Makefile.depend
@@ -0,0 +1,33 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libwrap \
+ usr.bin/yacc.host \
+ usr.sbin/amd/include \
+ usr.sbin/amd/libamu \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+conf_parse.o: conf_parse.c
+conf_parse.po: conf_parse.c
+conf_tok.o: conf_parse.h
+conf_tok.o: conf_tok.c
+conf_tok.po: conf_parse.h
+conf_tok.po: conf_tok.c
+mount_xdr.o: mount_xdr.c
+mount_xdr.po: mount_xdr.c
+.endif
diff --git a/usr.sbin/amd/amq/Makefile b/usr.sbin/amd/amq/Makefile
new file mode 100644
index 0000000..968ae4c
--- /dev/null
+++ b/usr.sbin/amd/amq/Makefile
@@ -0,0 +1,19 @@
+# ex:ts=8
+#
+# Makefile for amd
+# This file is under a "BSD" copyright (c) by David O'Brien 1998
+#
+# $FreeBSD$
+#
+
+.PATH: ${.CURDIR}/../../../contrib/amd/amq
+
+PROG= amq
+MAN= amq.8
+SRCS= amq.c amq_clnt.c amq_xdr.c
+
+CFLAGS+= -I${.CURDIR}/../../../contrib/amd/amq
+
+LIBADD= amu
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/amd/amq/Makefile.depend b/usr.sbin/amd/amq/Makefile.depend
new file mode 100644
index 0000000..30890ab
--- /dev/null
+++ b/usr.sbin/amd/amq/Makefile.depend
@@ -0,0 +1,24 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libwrap \
+ usr.sbin/amd/include \
+ usr.sbin/amd/libamu \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/amd/fixmount/Makefile b/usr.sbin/amd/fixmount/Makefile
new file mode 100644
index 0000000..8137ffb
--- /dev/null
+++ b/usr.sbin/amd/fixmount/Makefile
@@ -0,0 +1,20 @@
+# ex:ts=8
+#
+# Makefile for amd
+# This file is under a "BSD" copyright (c) by David O'Brien 1998
+#
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../../contrib/amd/fixmount \
+ ${.CURDIR}/../../../contrib/amd/conf/checkmount
+
+PROG= fixmount
+MAN= fixmount.8
+SRCS= fixmount.c
+
+# These would be links created by the GNU-style configure
+SRCS+= checkmount_bsd44.c
+
+LIBADD+= amu rpcsvc
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/amd/fixmount/Makefile.depend b/usr.sbin/amd/fixmount/Makefile.depend
new file mode 100644
index 0000000..4ed4920
--- /dev/null
+++ b/usr.sbin/amd/fixmount/Makefile.depend
@@ -0,0 +1,25 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/librpcsvc \
+ lib/libwrap \
+ usr.sbin/amd/include \
+ usr.sbin/amd/libamu \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/amd/fsinfo/Makefile b/usr.sbin/amd/fsinfo/Makefile
new file mode 100644
index 0000000..a059da6
--- /dev/null
+++ b/usr.sbin/amd/fsinfo/Makefile
@@ -0,0 +1,24 @@
+# ex:ts=8
+#
+# Makefile for amd
+# This file is under a "BSD" copyright (c) by David O'Brien 1998
+#
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../../contrib/amd/fsinfo
+
+PROG= fsinfo
+MAN= fsinfo.8
+SRCS= fsi_gram.y fsi_lex.l
+SRCS+= fsi_analyze.c fsi_dict.c fsi_util.c fsinfo.c wr_atab.c
+SRCS+= wr_bparam.c wr_dumpset.c wr_exportfs.c wr_fstab.c
+
+CFLAGS+= -I${.CURDIR}/../../../contrib/amd/fsinfo
+
+LIBADD= amu
+
+CLEANFILES+= fsi_gram.c fsi_gram.h fsi_lex.c
+
+fsi_lex.o: fsi_gram.h
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/amd/fsinfo/Makefile.depend b/usr.sbin/amd/fsinfo/Makefile.depend
new file mode 100644
index 0000000..5d30d8b
--- /dev/null
+++ b/usr.sbin/amd/fsinfo/Makefile.depend
@@ -0,0 +1,33 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libwrap \
+ usr.bin/yacc.host \
+ usr.sbin/amd/include \
+ usr.sbin/amd/libamu \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+fsi_gram.o: fsi_gram.c
+fsi_gram.po: fsi_gram.c
+fsi_lex.o: fsi_gram.h
+fsi_lex.o: fsi_lex.c
+fsi_lex.po: fsi_gram.h
+fsi_lex.po: fsi_lex.c
+fsinfo.o: fsi_gram.h
+fsinfo.po: fsi_gram.h
+.endif
diff --git a/usr.sbin/amd/hlfsd/Makefile b/usr.sbin/amd/hlfsd/Makefile
new file mode 100644
index 0000000..96d05c4
--- /dev/null
+++ b/usr.sbin/amd/hlfsd/Makefile
@@ -0,0 +1,18 @@
+# ex:ts=8
+#
+# Makefile for amd
+# This file is under a "BSD" copyright (c) by David O'Brien 1998
+#
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../../contrib/amd/hlfsd
+
+PROG= hlfsd
+MAN= hlfsd.8
+SRCS= hlfsd.c homedir.c nfs_prot_svc.c stubs.c
+
+CFLAGS+= -I${.CURDIR}/../../../contrib/amd/hlfsd
+
+LIBADD= amu
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/amd/hlfsd/Makefile.depend b/usr.sbin/amd/hlfsd/Makefile.depend
new file mode 100644
index 0000000..30890ab
--- /dev/null
+++ b/usr.sbin/amd/hlfsd/Makefile.depend
@@ -0,0 +1,24 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libwrap \
+ usr.sbin/amd/include \
+ usr.sbin/amd/libamu \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/amd/include/Makefile b/usr.sbin/amd/include/Makefile
new file mode 100644
index 0000000..e594d71
--- /dev/null
+++ b/usr.sbin/amd/include/Makefile
@@ -0,0 +1,29 @@
+# ex:ts=8
+#
+# $FreeBSD$
+#
+# Makefile for amd
+# This file is under a "BSD" copyright (c) by David O'Brien 1998.
+# Portions derived from amd/libamu/Makefile
+# $NetBSD: Makefile,v 1.8 1998/08/08 22:33:37 christos Exp $
+
+.include <src.opts.mk>
+
+SRCS= config_local.h
+.if ${MK_NIS} == "no"
+SRCS+= localconfig.h
+.endif
+CLEANFILES= ${SRCS}
+
+all depend: ${SRCS}
+
+config_local.h: newvers.sh
+ @rm -f ${.TARGET}
+ sh ${.ALLSRC} ${.CURDIR}/../../../sys/conf/newvers.sh > ${.TARGET}
+
+localconfig.h:
+ @rm -f ${.TARGET}
+ @echo "/* NIS disabled by WITHOUT_NIS src.conf option */" >> ${.TARGET}
+ @echo "#undef HAVE_MAP_NIS" >> ${.TARGET}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/amd/include/Makefile.depend b/usr.sbin/amd/include/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/amd/include/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/amd/include/amu_autofs_prot.h b/usr.sbin/amd/include/amu_autofs_prot.h
new file mode 100644
index 0000000..12f5b95
--- /dev/null
+++ b/usr.sbin/amd/include/amu_autofs_prot.h
@@ -0,0 +1,8 @@
+// $FreeBSD$
+
+/* Adjust once we have some form of Autofs support. */
+
+#if 0
+#include "conf/autofs/autofs_default.h"
+#endif
+
diff --git a/usr.sbin/amd/include/amu_nfs_prot.h b/usr.sbin/amd/include/amu_nfs_prot.h
new file mode 100644
index 0000000..8e8856f
--- /dev/null
+++ b/usr.sbin/amd/include/amu_nfs_prot.h
@@ -0,0 +1 @@
+#include "conf/nfs_prot/nfs_prot_freebsd3.h"
diff --git a/usr.sbin/amd/include/aux_conf.h b/usr.sbin/amd/include/aux_conf.h
new file mode 100644
index 0000000..1529ba3
--- /dev/null
+++ b/usr.sbin/amd/include/aux_conf.h
@@ -0,0 +1,72 @@
+/* $FreeBSD$ */
+
+/*
+ * aux_conf.h:
+ * This file gets "filled in" for each architecture.
+ * aux_conf.h. Generated from aux_conf.h.in by configure.
+ */
+
+#ifndef _AUX_CONF_H
+#define _AUX_CONF_H
+
+/*
+ * The next line is a literal inclusion of a file which includes a
+ * definition for the MOUNT_TRAP macro for a particular architecture.
+ * If it defines the wrong entry, check the AC_CHECK_MOUNT_TRAP m4 macro
+ * in $srcdir/m4/macros.
+ */
+
+/* $srcdir/conf/trap/trap_default.h */
+#define MOUNT_TRAP(type, mnt, flags, mnt_data) mount(type, mnt->mnt_dir, flags, mnt_data)
+/* End of included MOUNT_TRAP macro definition file */
+
+/*
+ * The next line is a literal replacement of a variable which defines the
+ * the UNMOUNT_TRAP macro for a particular architecture.
+ * If it defines the wrong entry, check the AC_CHECK_UNMOUNT_CALL m4 macro
+ * in $srcdir/aclocal.m4. If the arguments are being defined wrong, check
+ * the macro AC_CHECK_UNMOUNT_ARGS in $srcdir/m4/macros.
+ */
+#define UNMOUNT_TRAP(mnt) unmount(mnt->mnt_dir)
+/* End of replaced UNMOUNT_TRAP macro definition */
+/* umount(8) executable path, for type:=program */
+#define UNMOUNT_PROGRAM "/sbin/umount"
+
+/*
+ * The next line is a literal inclusion of a file which includes a
+ * definition for the NFS_FH_DREF macro for a particular architecture.
+ * If it defines the wrong entry, check the AC_CHECK_NFS_FH_DREF m4 macro
+ * in $srcdir/m4/macros.
+ */
+
+/* $srcdir/conf/fh_dref/fh_dref_freebsd22.h */
+#define NFS_FH_DREF(dst, src) (dst) = (u_char *) (src)
+/* End of included NFS_FH_DREF macro definition file */
+
+/*
+ * The next line is a literal inclusion of a file which includes a
+ * definition for the NFS_SA_DREF macro for a particular architecture.
+ * If it defines the wrong entry, check the AC_CHECK_NFS_SA_DREF m4 macro
+ * in $srcdir/m4/macros.
+ */
+
+/* $srcdir/conf/sa_dref/sa_dref_bsd44.h */
+#define NFS_SA_DREF(dst, src) { \
+ (dst)->addr = (struct sockaddr *) (src); \
+ (dst)->addrlen = sizeof(*src); \
+ }
+#define NFS_ARGS_T_ADDR_IS_POINTER 1
+/* End of included NFS_SA_DREF macro definition file */
+
+/*
+ * The next line is a literal inclusion of a file which includes a
+ * definition for the NFS_HN_DREF macro for a particular architecture.
+ * If it defines the wrong entry, check the AC_CHECK_NFS_HN_DREF m4 macro
+ * in $srcdir/m4/macros.
+ */
+
+/* $srcdir/conf/hn_dref/hn_dref_default.h */
+#define NFS_HN_DREF(dst, src) (dst) = (src)
+/* End of included NFS_HN_DREF macro definition file */
+
+#endif /* not _AUX_CONF_H */
diff --git a/usr.sbin/amd/include/build_version.h b/usr.sbin/amd/include/build_version.h
new file mode 100644
index 0000000..09eb1fc
--- /dev/null
+++ b/usr.sbin/amd/include/build_version.h
@@ -0,0 +1,8 @@
+/* $FreeBSD$ */
+
+#include <sys/param.h>
+/*#define AMU_BUILD_VERSION 1 */
+#define AMU_BUILD_VERSION __FreeBSD_version
+#define USER_NAME "David O'Brien <obrien"
+#define HOST_NAME "FreeBSD.org>"
+#define CONFIG_DATE "4-December-2007 PST"
diff --git a/usr.sbin/amd/include/config.h b/usr.sbin/amd/include/config.h
new file mode 100644
index 0000000..cffb82a
--- /dev/null
+++ b/usr.sbin/amd/include/config.h
@@ -0,0 +1,2202 @@
+/*
+ * $FreeBSD$
+ *
+ * portions derived from
+ * $NetBSD: config.h,v 1.11 1998/08/08 22:33:37 christos Exp $
+ */
+
+#ifndef _CONFIG_H
+#define _CONFIG_H
+
+/* We [FREEBSD-NATIVE] pick some parameters from our local config file */
+#include "config_local.h"
+
+/* define name of am-utils' NFS protocol header */
+#define AMU_NFS_PROTOCOL_HEADER "./conf/nfs_prot/nfs_prot_freebsd3.h"
+
+/* Type of the 5rd argument to authunix_create() */
+#define AUTH_CREATE_GIDLIST_TYPE gid_t
+
+/* Define configuration date */
+/* #define CONFIG_DATE "Tue Dec 4 21:39:00 PST 2007" */
+
+/* Turn off general debugging by default */
+/* #undef DEBUG */
+
+/* Turn off memory debugging by default */
+/* #undef DEBUG_MEM */
+
+/* Define name of host OS's distribution name (eg. debian, redhat, suse, etc.)
+ */
+#define DISTRO_NAME "The FreeBSD Project"
+
+/* Define to the type of elements in the array set by `getgroups'. Usually
+ this is either `int' or `gid_t'. */
+#define GETGROUPS_T gid_t
+
+/* Define to 1 if the `getpgrp' function requires zero arguments. */
+#define GETPGRP_VOID 1
+
+/* Define if have automount filesystem */
+#define HAVE_AMU_FS_AUTO 1
+
+/* Define if have direct automount filesystem */
+#define HAVE_AMU_FS_DIRECT 1
+
+/* Define if have error filesystem */
+#define HAVE_AMU_FS_ERROR 1
+
+/* Define if have NFS host-tree filesystem */
+#define HAVE_AMU_FS_HOST 1
+
+/* Define if have symbolic-link filesystem */
+#define HAVE_AMU_FS_LINK 1
+
+/* Define if have symlink with existence check filesystem */
+#define HAVE_AMU_FS_LINKX 1
+
+/* Define if have nfsl (NFS with local link check) filesystem */
+#define HAVE_AMU_FS_NFSL 1
+
+/* Define if have multi-NFS filesystem */
+#define HAVE_AMU_FS_NFSX 1
+
+/* Define if have program filesystem */
+#define HAVE_AMU_FS_PROGRAM 1
+
+/* Define if have "top-level" filesystem */
+#define HAVE_AMU_FS_TOPLVL 1
+
+/* Define if have union filesystem */
+#define HAVE_AMU_FS_UNION 1
+
+/* Define to 1 if you have the <arpa/inet.h> header file. */
+#define HAVE_ARPA_INET_H 1
+
+/* Define to 1 if you have the <arpa/nameser.h> header file. */
+#define HAVE_ARPA_NAMESER_H 1
+
+/* Define to 1 if you have the <assert.h> header file. */
+#define HAVE_ASSERT_H 1
+
+/* Define to 1 if `addr' is member of `autofs_args_t'. */
+/* #undef HAVE_AUTOFS_ARGS_T_ADDR */
+
+/* define if have a bad version of hasmntopt() */
+/* #undef HAVE_BAD_HASMNTOPT */
+
+/* define if have a bad version of memcmp() */
+/* #undef HAVE_BAD_MEMCMP */
+
+/* define if have a bad version of yp_all() */
+/* #undef HAVE_BAD_YP_ALL */
+
+/* Define to 1 if you have the `bcmp' function. */
+#define HAVE_BCMP 1
+
+/* Define to 1 if you have the `bcopy' function. */
+#define HAVE_BCOPY 1
+
+/* Define to 1 if you have the <bsd/rpc/rpc.h> header file. */
+/* #undef HAVE_BSD_RPC_RPC_H */
+
+/* Define to 1 if you have the `bzero' function. */
+#define HAVE_BZERO 1
+
+/* System supports C99-style variable-length argument macros */
+#define HAVE_C99_VARARGS_MACROS 1
+
+/* Define to 1 if `flags' is member of `cdfs_args_t'. */
+#define HAVE_CDFS_ARGS_T_FLAGS 1
+
+/* Define to 1 if `fspec' is member of `cdfs_args_t'. */
+#define HAVE_CDFS_ARGS_T_FSPEC 1
+
+/* Define to 1 if `iso_flags' is member of `cdfs_args_t'. */
+/* #undef HAVE_CDFS_ARGS_T_ISO_FLAGS */
+
+/* Define to 1 if `iso_pgthresh' is member of `cdfs_args_t'. */
+/* #undef HAVE_CDFS_ARGS_T_ISO_PGTHRESH */
+
+/* Define to 1 if `norrip' is member of `cdfs_args_t'. */
+/* #undef HAVE_CDFS_ARGS_T_NORRIP */
+
+/* Define to 1 if `ssector' is member of `cdfs_args_t'. */
+#define HAVE_CDFS_ARGS_T_SSECTOR 1
+
+/* Define to 1 if you have the <cdfs/cdfsmount.h> header file. */
+/* #undef HAVE_CDFS_CDFSMOUNT_H */
+
+/* Define to 1 if you have the <cdfs/cdfs_mount.h> header file. */
+/* #undef HAVE_CDFS_CDFS_MOUNT_H */
+
+/* Define to 1 if you have the `clnt_create' function. */
+#define HAVE_CLNT_CREATE 1
+
+/* Define to 1 if you have the `clnt_create_vers' function. */
+#define HAVE_CLNT_CREATE_VERS 1
+
+/* Define to 1 if you have the `clnt_create_vers_timed' function. */
+#define HAVE_CLNT_CREATE_VERS_TIMED 1
+
+/* Define to 1 if you have the `clnt_spcreateerror' function. */
+#define HAVE_CLNT_SPCREATEERROR 1
+
+/* Define to 1 if you have the `clnt_sperrno' function. */
+#define HAVE_CLNT_SPERRNO 1
+
+/* Define to 1 if you have the `clock_gettime' function. */
+#define HAVE_CLOCK_GETTIME 1
+
+/* Define to 1 if you have the <cluster.h> header file. */
+/* #undef HAVE_CLUSTER_H */
+
+/* Define to 1 if you have the `cnodeid' function. */
+/* #undef HAVE_CNODEID */
+
+/* Define to 1 if you have the <ctype.h> header file. */
+#define HAVE_CTYPE_H 1
+
+/* Define to 1 if you have the <db1/ndbm.h> header file. */
+/* #undef HAVE_DB1_NDBM_H */
+
+/* Define to 1 if you have the `dbm_open' function. */
+#define HAVE_DBM_OPEN 1
+
+/* Define to 1 if you have the `dg_mount' function. */
+/* #undef HAVE_DG_MOUNT */
+
+/* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'.
+ */
+#define HAVE_DIRENT_H 1
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Define to 1 if `flags' is member of `efs_args_t'. */
+/* #undef HAVE_EFS_ARGS_T_FLAGS */
+
+/* Define to 1 if `fspec' is member of `efs_args_t'. */
+/* #undef HAVE_EFS_ARGS_T_FSPEC */
+
+/* Define to 1 if you have the <errno.h> header file. */
+#define HAVE_ERRNO_H 1
+
+/* does extern definition for clnt_spcreateerror() exist? */
+#define HAVE_EXTERN_CLNT_SPCREATEERROR 1
+
+/* does extern definition for clnt_sperrno() exist? */
+#define HAVE_EXTERN_CLNT_SPERRNO 1
+
+/* does extern definition for free() exist? */
+#define HAVE_EXTERN_FREE 1
+
+/* does extern definition for getccent() (hpux) exist? */
+/* #undef HAVE_EXTERN_GETCCENT */
+
+/* does extern definition for getdomainname() exist? */
+#define HAVE_EXTERN_GETDOMAINNAME 1
+
+/* does extern definition for getdtablesize() exist? */
+#define HAVE_EXTERN_GETDTABLESIZE 1
+
+/* does extern definition for gethostname() exist? */
+#define HAVE_EXTERN_GETHOSTNAME 1
+
+/* does extern definition for getlogin() exist? */
+#define HAVE_EXTERN_GETLOGIN 1
+
+/* does extern definition for getpagesize() exist? */
+#define HAVE_EXTERN_GETPAGESIZE 1
+
+/* does extern definition for gettablesize() exist? */
+/* #undef HAVE_EXTERN_GETTABLESIZE */
+
+/* does extern definition for getwd() exist? */
+#define HAVE_EXTERN_GETWD 1
+
+/* does extern definition for get_myaddress() exist? */
+#define HAVE_EXTERN_GET_MYADDRESS 1
+
+/* does extern definition for hosts_ctl() exist? */
+/* #undef HAVE_EXTERN_HOSTS_CTL */
+
+/* does extern definition for innetgr() exist? */
+#define HAVE_EXTERN_INNETGR 1
+
+/* does extern definition for ldap_enable_cache() exist? */
+/* #undef HAVE_EXTERN_LDAP_ENABLE_CACHE */
+
+/* does extern definition for mkstemp() exist? */
+#define HAVE_EXTERN_MKSTEMP 1
+
+/* does extern definition for mntctl() exist? */
+/* #undef HAVE_EXTERN_MNTCTL */
+
+/* does extern definition for optarg exist? */
+#define HAVE_EXTERN_OPTARG 1
+
+/* does extern definition for sbrk() exist? */
+#define HAVE_EXTERN_SBRK 1
+
+/* does extern definition for seteuid() exist? */
+#define HAVE_EXTERN_SETEUID 1
+
+/* does extern definition for setitimer() exist? */
+#define HAVE_EXTERN_SETITIMER 1
+
+/* does extern definition for sleep() exist? */
+#define HAVE_EXTERN_SLEEP 1
+
+/* does extern definition for strcasecmp() exist? */
+#define HAVE_EXTERN_STRCASECMP 1
+
+/* does extern definition for strdup() exist? */
+#define HAVE_EXTERN_STRDUP 1
+
+/* does extern definition for strlcat() exist? */
+#define HAVE_EXTERN_STRLCAT 1
+
+/* does extern definition for strlcpy() exist? */
+#define HAVE_EXTERN_STRLCPY 1
+
+/* does extern definition for strstr() exist? */
+#define HAVE_EXTERN_STRSTR 1
+
+/* does extern definition for sys_errlist[] exist? */
+#define HAVE_EXTERN_SYS_ERRLIST 1
+
+/* does extern definition for ualarm() exist? */
+#define HAVE_EXTERN_UALARM 1
+
+/* does extern definition for usleep() exist? */
+#define HAVE_EXTERN_USLEEP 1
+
+/* does extern definition for vsnprintf() exist? */
+#define HAVE_EXTERN_VSNPRINTF 1
+
+/* does extern definition for wait3() exist? */
+#define HAVE_EXTERN_WAIT3 1
+
+/* does extern definition for xdr_callmsg() exist? */
+#define HAVE_EXTERN_XDR_CALLMSG 1
+
+/* does extern definition for xdr_opaque_auth() exist? */
+#define HAVE_EXTERN_XDR_OPAQUE_AUTH 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if `fds_bits' is member of `fd_set'. */
+#define HAVE_FD_SET_FDS_BITS 1
+
+/* Define to 1 if you have the `fgets' function. */
+#define HAVE_FGETS 1
+
+/* Define if plain fhandle type exists */
+#define HAVE_FHANDLE 1
+
+/* Define to 1 if you have the `flock' function. */
+#define HAVE_FLOCK 1
+
+/* Define to 1 if you have the `fork' function. */
+#define HAVE_FORK 1
+
+/* Define to 1 if you have the `fsmount' function. */
+/* #undef HAVE_FSMOUNT */
+
+/* Define if have AUTOFS filesystem */
+/* #undef HAVE_FS_AUTOFS */
+
+/* Define if have CACHEFS filesystem */
+/* #undef HAVE_FS_CACHEFS */
+
+/* Define if have CDFS filesystem */
+#define HAVE_FS_CDFS 1
+
+/* Define if have CFS (crypto) filesystem */
+/* #undef HAVE_FS_CFS */
+
+/* Define if have EFS filesystem (irix) */
+/* #undef HAVE_FS_EFS */
+
+/* Define if have FFS filesystem */
+/* #undef HAVE_FS_FFS */
+
+/* Define if have HSFS filesystem */
+/* #undef HAVE_FS_HSFS */
+
+/* Define if have LOFS filesystem */
+/* #undef HAVE_FS_LOFS */
+
+/* Define if have MFS filesystem */
+/* #undef HAVE_FS_MFS */
+
+/* Define if have NFS filesystem */
+#define HAVE_FS_NFS 1
+
+/* Define if have NFS3 filesystem */
+#define HAVE_FS_NFS3 1
+
+/* Define if have NULLFS (loopback on bsd44) filesystem */
+#define HAVE_FS_NULLFS 1
+
+/* Define if have PCFS filesystem */
+#define HAVE_FS_PCFS 1
+
+/* Define if have TFS filesystem */
+/* #undef HAVE_FS_TFS */
+
+/* Define if have TMPFS filesystem */
+/* #undef HAVE_FS_TMPFS */
+
+/* Define if have UFS filesystem */
+#define HAVE_FS_UFS 1
+
+/* Define if have UMAPFS (uid/gid mapping) filesystem */
+/* #undef HAVE_FS_UMAPFS */
+
+/* Define if have UNIONFS filesystem */
+#define HAVE_FS_UNIONFS 1
+
+/* Define if have XFS filesystem (irix) */
+/* #undef HAVE_FS_XFS */
+
+/* System supports GCC-style variable-length argument macros */
+/* #undef HAVE_GCC_VARARGS_MACROS */
+
+/* Define to 1 if you have the <gdbm-ndbm.h> header file. */
+/* #undef HAVE_GDBM_NDBM_H */
+
+/* Define to 1 if you have the `getccent' function. */
+/* #undef HAVE_GETCCENT */
+
+/* Define to 1 if you have the `getcwd' function. */
+#define HAVE_GETCWD 1
+
+/* Define to 1 if you have the `getdomainname' function. */
+#define HAVE_GETDOMAINNAME 1
+
+/* Define to 1 if you have the `getdtablesize' function. */
+#define HAVE_GETDTABLESIZE 1
+
+/* Define to 1 if you have the `gethostname' function. */
+#define HAVE_GETHOSTNAME 1
+
+/* Define to 1 if you have the `getifaddrs' function. */
+#define HAVE_GETIFADDRS 1
+
+/* Define to 1 if you have the `getmntinfo' function. */
+#define HAVE_GETMNTINFO 1
+
+/* Define to 1 if you have the `getmountent' function. */
+/* #undef HAVE_GETMOUNTENT */
+
+/* Define to 1 if you have the `getpagesize' function. */
+#define HAVE_GETPAGESIZE 1
+
+/* Define to 1 if you have the `getpwnam' function. */
+#define HAVE_GETPWNAM 1
+
+/* Define to 1 if you have the `gettimeofday' function. */
+#define HAVE_GETTIMEOFDAY 1
+
+/* Define to 1 if you have the `get_myaddress' function. */
+#define HAVE_GET_MYADDRESS 1
+
+/* define if your system's getopt() is GNU getopt() (are you using glibc) */
+/* #undef HAVE_GNU_GETOPT */
+
+/* Define to 1 if you have the <grp.h> header file. */
+#define HAVE_GRP_H 1
+
+/* Define to 1 if you have the `hasmntopt' function. */
+/* #undef HAVE_HASMNTOPT */
+
+#ifdef YES_HESIOD
+/* Define to 1 if you have the <hesiod.h> header file. */
+#define HAVE_HESIOD_H 1
+
+/* Define to 1 if you have the `hesiod_init' function. */
+#define HAVE_HESIOD_INIT 1
+
+/* Define to 1 if you have the `hesiod_reload' function. */
+/* #undef HAVE_HESIOD_RELOAD */
+
+/* Define to 1 if you have the `hesiod_to_bind' function. */
+#define HAVE_HESIOD_TO_BIND 1
+
+/* Define to 1 if you have the `hes_init' function. */
+#define HAVE_HES_INIT 1
+#else
+#undef HAVE_HESIOD_H
+#undef HAVE_HESIOD_INIT
+#undef HAVE_HESIOD_RELOAD
+#undef HAVE_HESIOD_TO_BIND
+#undef HAVE_HES_INIT
+#endif
+
+/* Define to 1 if you have the <hsfs/hsfs.h> header file. */
+/* #undef HAVE_HSFS_HSFS_H */
+
+/* Define to 1 if you have the `hstrerror' function. */
+#define HAVE_HSTRERROR 1
+
+/* Define to 1 if you have the <ifaddrs.h> header file. */
+#define HAVE_IFADDRS_H 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the <irs.h> header file. */
+/* #undef HAVE_IRS_H */
+
+/* Define to 1 if you have the <isofs/cd9660/cd9660_mount.h> header file. */
+#define HAVE_ISOFS_CD9660_CD9660_MOUNT_H 1
+
+/* Define to 1 if you have the <lber.h> header file. */
+/* #undef HAVE_LBER_H */
+
+/* Define to 1 if you have the `ldap_enable_cache' function. */
+/* #undef HAVE_LDAP_ENABLE_CACHE */
+
+/* Define to 1 if you have the <ldap.h> header file. */
+/* #undef HAVE_LDAP_H */
+
+/* Define to 1 if you have the `ldap_open' function. */
+/* #undef HAVE_LDAP_OPEN */
+
+/* Define to 1 if you have the <libgen.h> header file. */
+#define HAVE_LIBGEN_H 1
+
+/* Define to 1 if you have the `malloc' library (-lmalloc). */
+/* #undef HAVE_LIBMALLOC */
+
+/* Define to 1 if you have the `mapmalloc' library (-lmapmalloc). */
+/* #undef HAVE_LIBMAPMALLOC */
+
+/* Define to 1 if you have the `nsl' library (-lnsl). */
+/* #undef HAVE_LIBNSL */
+
+/* Define to 1 if you have the `posix4' library (-lposix4). */
+/* #undef HAVE_LIBPOSIX4 */
+
+/* Define to 1 if you have the `resolv' library (-lresolv). */
+/* #undef HAVE_LIBRESOLV */
+
+/* Define to 1 if you have the `rpc' library (-lrpc). */
+/* #undef HAVE_LIBRPC */
+
+/* Define to 1 if you have the `rpcsvc' library (-lrpcsvc). */
+#define HAVE_LIBRPCSVC 1
+
+/* Define to 1 if you have the `rt' library (-lrt). */
+/* #undef HAVE_LIBRT */
+
+/* does libwrap exist? */
+#define HAVE_LIBWRAP 1
+
+/* Define to 1 if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define to 1 if you have the <linux/auto_fs4.h> header file. */
+/* #undef HAVE_LINUX_AUTO_FS4_H */
+
+/* Define to 1 if you have the <linux/auto_fs.h> header file. */
+/* #undef HAVE_LINUX_AUTO_FS_H */
+
+/* Define to 1 if you have the <linux/fs.h> header file. */
+/* #undef HAVE_LINUX_FS_H */
+
+/* Define to 1 if you have the <linux/kdev_t.h> header file. */
+/* #undef HAVE_LINUX_KDEV_T_H */
+
+/* Define to 1 if you have the <linux/list.h> header file. */
+/* #undef HAVE_LINUX_LIST_H */
+
+/* Define to 1 if you have the <linux/loop.h> header file. */
+/* #undef HAVE_LINUX_LOOP_H */
+
+/* Define to 1 if you have the <linux/nfs2.h> header file. */
+/* #undef HAVE_LINUX_NFS2_H */
+
+/* Define to 1 if you have the <linux/nfs.h> header file. */
+/* #undef HAVE_LINUX_NFS_H */
+
+/* Define to 1 if you have the <linux/nfs_mount.h> header file. */
+/* #undef HAVE_LINUX_NFS_MOUNT_H */
+
+/* Define to 1 if you have the <linux/posix_types.h> header file. */
+/* #undef HAVE_LINUX_POSIX_TYPES_H */
+
+/* Define to 1 if you have the <linux/socket.h> header file. */
+/* #undef HAVE_LINUX_SOCKET_H */
+
+/* Define to 1 if you support file names longer than 14 characters. */
+#define HAVE_LONG_FILE_NAMES 1
+
+/* Define to 1 if you have the <machine/endian.h> header file. */
+#define HAVE_MACHINE_ENDIAN_H 1
+
+/* Define to 1 if you have the `madvise' function. */
+#define HAVE_MADVISE 1
+
+/* Define to 1 if you have the <malloc.h> header file. */
+/* #undef HAVE_MALLOC_H */
+
+/* Define if have DBM maps */
+/* #undef HAVE_MAP_DBM */
+
+/* Define if have executable maps */
+#define HAVE_MAP_EXEC 1
+
+/* Define if have file maps (everyone should have it!) */
+#define HAVE_MAP_FILE 1
+
+#ifdef YES_HESIOD
+/* Define if have HESIOD maps */
+#define HAVE_MAP_HESIOD 1
+#else
+#undef HAVE_MAP_HESIOD
+#endif
+
+/* Define if have LDAP maps */
+/* #undef HAVE_MAP_LDAP */
+
+/* Define if have NDBM maps */
+#define HAVE_MAP_NDBM 1
+
+/* Define if have NIS maps */
+#define HAVE_MAP_NIS 1
+
+/* Define if have NIS+ maps */
+/* #undef HAVE_MAP_NISPLUS */
+
+/* Define if have PASSWD maps */
+#define HAVE_MAP_PASSWD 1
+
+/* Define if have UNION maps */
+#define HAVE_MAP_UNION 1
+
+/* Define to 1 if you have the `memcmp' function. */
+#define HAVE_MEMCMP 1
+
+/* Define to 1 if you have the `memcpy' function. */
+#define HAVE_MEMCPY 1
+
+/* Define to 1 if you have the `memmove' function. */
+#define HAVE_MEMMOVE 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `memset' function. */
+#define HAVE_MEMSET 1
+
+/* Define to 1 if you have the `mkdir' function. */
+#define HAVE_MKDIR 1
+
+/* Define to 1 if you have the `mkstemp' function. */
+#define HAVE_MKSTEMP 1
+
+/* Define to 1 if you have the `mlockall' function. */
+#define HAVE_MLOCKALL 1
+
+/* Define to 1 if you have the `mntctl' function. */
+/* #undef HAVE_MNTCTL */
+
+/* Define to 1 if you have the <mntent.h> header file. */
+/* #undef HAVE_MNTENT_H */
+
+/* Define to 1 if `mnt_cnode' is member of `mntent_t'. */
+/* #undef HAVE_MNTENT_T_MNT_CNODE */
+
+/* Define to 1 if `mnt_ro' is member of `mntent_t'. */
+/* #undef HAVE_MNTENT_T_MNT_RO */
+
+/* Define to 1 if `mnt_time' is member of `mntent_t'. */
+/* #undef HAVE_MNTENT_T_MNT_TIME */
+
+/* does mntent_t have mnt_time field and is of type "char *" ? */
+/* #undef HAVE_MNTENT_T_MNT_TIME_STRING */
+
+/* Define to 1 if you have the <mnttab.h> header file. */
+/* #undef HAVE_MNTTAB_H */
+
+/* Define to 1 if you have the `mount' function. */
+#define HAVE_MOUNT 1
+
+/* Define to 1 if `optptr' is member of `mounta'. */
+/* #undef HAVE_MOUNTA_OPTPTR */
+
+/* Define to 1 if you have the `mountsyscall' function. */
+/* #undef HAVE_MOUNTSYSCALL */
+
+/* Define to 1 if you have the <mount.h> header file. */
+/* #undef HAVE_MOUNT_H */
+
+/* Define to 1 if you have the <msdosfs/msdosfsmount.h> header file. */
+/* #undef HAVE_MSDOSFS_MSDOSFSMOUNT_H */
+
+/* Define to 1 if you have the <fs/msdosfs/msdosfsmount.h> header file. */
+#define HAVE_FS_MSDOSFS_MSDOSFSMOUNT_H 1
+
+/* Define to 1 if you have the <ndbm.h> header file. */
+#define HAVE_NDBM_H 1
+
+/* Define to 1 if you have the <ndir.h> header file. */
+/* #undef HAVE_NDIR_H */
+
+/* Define to 1 if you have the <netconfig.h> header file. */
+#define HAVE_NETCONFIG_H 1
+
+/* Define to 1 if you have the <netdb.h> header file. */
+#define HAVE_NETDB_H 1
+
+/* Define to 1 if you have the <netdir.h> header file. */
+/* #undef HAVE_NETDIR_H */
+
+/* Define to 1 if you have the <netinet/if_ether.h> header file. */
+#define HAVE_NETINET_IF_ETHER_H 1
+
+/* Define to 1 if you have the <netinet/in.h> header file. */
+#define HAVE_NETINET_IN_H 1
+
+/* Define to 1 if you have the <net/errno.h> header file. */
+/* #undef HAVE_NET_ERRNO_H */
+
+/* Define to 1 if you have the <net/if.h> header file. */
+#define HAVE_NET_IF_H 1
+
+/* Define to 1 if you have the <net/route.h> header file. */
+#define HAVE_NET_ROUTE_H 1
+
+/* Define to 1 if you have the <nfsclient/nfsargs.h> header file. */
+#define HAVE_NFSCLIENT_NFSARGS_H 1
+
+/* Define to 1 if `acdirmax' is member of `nfs_args_t'. */
+#define HAVE_NFS_ARGS_T_ACDIRMAX 1
+
+/* Define to 1 if `acdirmin' is member of `nfs_args_t'. */
+#define HAVE_NFS_ARGS_T_ACDIRMIN 1
+
+/* Define to 1 if `acregmax' is member of `nfs_args_t'. */
+#define HAVE_NFS_ARGS_T_ACREGMAX 1
+
+/* Define to 1 if `acregmin' is member of `nfs_args_t'. */
+#define HAVE_NFS_ARGS_T_ACREGMIN 1
+
+/* Define to 1 if `addrlen' is member of `nfs_args_t'. */
+#define HAVE_NFS_ARGS_T_ADDRLEN 1
+
+/* Define to 1 if `bsize' is member of `nfs_args_t'. */
+/* #undef HAVE_NFS_ARGS_T_BSIZE */
+
+/* Define to 1 if `fhsize' is member of `nfs_args_t'. */
+#define HAVE_NFS_ARGS_T_FHSIZE 1
+
+/* Define to 1 if `fh_len' is member of `nfs_args_t'. */
+/* #undef HAVE_NFS_ARGS_T_FH_LEN */
+
+/* Define to 1 if `gfs_flags' is member of `nfs_args_t'. */
+/* #undef HAVE_NFS_ARGS_T_GFS_FLAGS */
+
+/* Define to 1 if `namlen' is member of `nfs_args_t'. */
+/* #undef HAVE_NFS_ARGS_T_NAMLEN */
+
+/* Define to 1 if `optstr' is member of `nfs_args_t'. */
+/* #undef HAVE_NFS_ARGS_T_OPTSTR */
+
+/* Define to 1 if `proto' is member of `nfs_args_t'. */
+#define HAVE_NFS_ARGS_T_PROTO 1
+
+/* Define to 1 if `sotype' is member of `nfs_args_t'. */
+#define HAVE_NFS_ARGS_T_SOTYPE 1
+
+/* Define to 1 if `version' is member of `nfs_args_t'. */
+#define HAVE_NFS_ARGS_T_VERSION 1
+
+/* Define to 1 if you have the <nfs/export.h> header file. */
+/* #undef HAVE_NFS_EXPORT_H */
+
+/* Define to 1 if you have the <nfs/mount.h> header file. */
+/* #undef HAVE_NFS_MOUNT_H */
+
+/* Define to 1 if you have the <nfs/nfsmount.h> header file. */
+/* #undef HAVE_NFS_NFSMOUNT_H */
+
+/* Define to 1 if you have the <nfs/nfsproto.h> header file. */
+#define HAVE_NFS_NFSPROTO_H 1
+
+/* Define to 1 if you have the <nfs/nfsv2.h> header file. */
+/* #undef HAVE_NFS_NFSV2_H */
+
+/* Define to 1 if you have the <nfs/nfs_clnt.h> header file. */
+/* #undef HAVE_NFS_NFS_CLNT_H */
+
+/* Define to 1 if you have the <nfs/nfs_gfs.h> header file. */
+/* #undef HAVE_NFS_NFS_GFS_H */
+
+/* Define to 1 if you have the <nfs/nfs.h> header file. */
+/* #undef HAVE_NFS_NFS_H */
+
+/* Define to 1 if you have the <nfs/nfs_mount.h> header file. */
+/* #undef HAVE_NFS_NFS_MOUNT_H */
+
+/* Define to 1 if you have the <nfs/pathconf.h> header file. */
+/* #undef HAVE_NFS_PATHCONF_H */
+
+/* define if the host has NFS protocol headers in system headers */
+/* #undef HAVE_NFS_PROT_HEADERS */
+
+/* Define to 1 if you have the <nfs/rpcv2.h> header file. */
+/* #define HAVE_NFS_RPCV2_H 1 */
+
+/* Define to 1 if you have the `nis_domain_of' function. */
+/* #undef HAVE_NIS_DOMAIN_OF */
+
+/* Define to 1 if you have the <nsswitch.h> header file. */
+#define HAVE_NSSWITCH_H 1
+
+/* Define to 1 if you have the `opendir' function. */
+#define HAVE_OPENDIR 1
+/* Define to 1 if `dsttime' is member of `pcfs_args_t'. */
+/* #undef HAVE_PCFS_ARGS_T_DSTTIME */
+
+/* Define to 1 if `fspec' is member of `pcfs_args_t'. */
+#define HAVE_PCFS_ARGS_T_FSPEC 1
+
+/* Define to 1 if `gid' is member of `pcfs_args_t'. */
+#define HAVE_PCFS_ARGS_T_GID 1
+
+/* Define to 1 if `mask' is member of `pcfs_args_t'. */
+#define HAVE_PCFS_ARGS_T_MASK 1
+
+/* Define to 1 if `dirmask' is member of `pcfs_args_t'. */
+#define HAVE_PCFS_ARGS_T_DIRMASK 1
+
+/* Define to 1 if `secondswest' is member of `pcfs_args_t'. */
+/* #undef HAVE_PCFS_ARGS_T_SECONDSWEST */
+
+/* Define to 1 if `uid' is member of `pcfs_args_t'. */
+#define HAVE_PCFS_ARGS_T_UID 1
+
+/* Define to 1 if you have the `plock' function. */
+/* #undef HAVE_PLOCK */
+
+/* Define to 1 if you have the <pwd.h> header file. */
+#define HAVE_PWD_H 1
+
+/* Define to 1 if you have the `regcomp' function. */
+#define HAVE_REGCOMP 1
+
+/* Define to 1 if you have the `regexec' function. */
+#define HAVE_REGEXEC 1
+
+/* Define to 1 if you have the <regex.h> header file. */
+#define HAVE_REGEX_H 1
+
+/* Define to 1 if you have the <resolv.h> header file. */
+#define HAVE_RESOLV_H 1
+
+/* Define to 1 if system calls automatically restart after interruption by a
+ signal. */
+#define HAVE_RESTARTABLE_SYSCALLS 1
+
+/* Define to 1 if you have the `rmdir' function. */
+#define HAVE_RMDIR 1
+
+/* Define to 1 if you have the <rpcsvc/autofs_prot.h> header file. */
+/* #undef HAVE_RPCSVC_AUTOFS_PROT_H */
+
+/* Define to 1 if you have the <rpcsvc/mountv3.h> header file. */
+/* #undef HAVE_RPCSVC_MOUNTV3_H */
+
+/* Define to 1 if you have the <rpcsvc/mount.h> header file. */
+#define HAVE_RPCSVC_MOUNT_H 1
+
+/* Define to 1 if you have the <rpcsvc/nfs_prot.h> header file. */
+#define HAVE_RPCSVC_NFS_PROT_H 1
+
+/* Define to 1 if you have the <rpcsvc/nis.h> header file. */
+#define HAVE_RPCSVC_NIS_H 1
+
+/* Define to 1 if you have the <rpcsvc/ypclnt.h> header file. */
+#define HAVE_RPCSVC_YPCLNT_H 1
+
+/* Define to 1 if you have the <rpcsvc/yp_prot.h> header file. */
+#define HAVE_RPCSVC_YP_PROT_H 1
+
+/* Define to 1 if you have the <rpc/auth_des.h> header file. */
+#define HAVE_RPC_AUTH_DES_H 1
+
+/* Define to 1 if you have the <rpc/pmap_clnt.h> header file. */
+#define HAVE_RPC_PMAP_CLNT_H 1
+
+/* Define to 1 if you have the <rpc/pmap_prot.h> header file. */
+#define HAVE_RPC_PMAP_PROT_H 1
+
+/* Define to 1 if you have the <rpc/rpc.h> header file. */
+#define HAVE_RPC_RPC_H 1
+
+/* Define to 1 if you have the <rpc/types.h> header file. */
+#define HAVE_RPC_TYPES_H 1
+
+/* Define to 1 if you have the <rpc/xdr.h> header file. */
+#define HAVE_RPC_XDR_H 1
+
+/* Define to 1 if you have the `select' function. */
+#define HAVE_SELECT 1
+
+/* Define to 1 if you have the `seteuid' function. */
+#define HAVE_SETEUID 1
+
+/* Define to 1 if you have the `setitimer' function. */
+#define HAVE_SETITIMER 1
+
+/* Define to 1 if you have the <setjmp.h> header file. */
+#define HAVE_SETJMP_H 1
+
+/* Define to 1 if you have the `setresuid' function. */
+#define HAVE_SETRESUID 1
+
+/* Define to 1 if you have the `setsid' function. */
+#define HAVE_SETSID 1
+
+/* Define to 1 if you have the `sigaction' function. */
+#define HAVE_SIGACTION 1
+
+/* Define to 1 if you have the `signal' function. */
+#define HAVE_SIGNAL 1
+
+/* Define to 1 if you have the <signal.h> header file. */
+#define HAVE_SIGNAL_H 1
+
+/* Define to 1 if you have the `sigsuspend' function. */
+#define HAVE_SIGSUSPEND 1
+
+/* Define to 1 if you have the `socket' function. */
+#define HAVE_SOCKET 1
+
+/* Define to 1 if you have the <socketbits.h> header file. */
+/* #undef HAVE_SOCKETBITS_H */
+
+/* Define to 1 if you have the <statbuf.h> header file. */
+/* #undef HAVE_STATBUF_H */
+
+/* Define to 1 if you have the `statfs' function. */
+#define HAVE_STATFS 1
+
+/* Define to 1 if you have the `statvfs' function. */
+#define HAVE_STATVFS 1
+
+/* Define to 1 if you have the <stdarg.h> header file. */
+#define HAVE_STDARG_H 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdio.h> header file. */
+#define HAVE_STDIO_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the `strcasecmp' function. */
+#define HAVE_STRCASECMP 1
+
+/* Define to 1 if you have the `strchr' function. */
+#define HAVE_STRCHR 1
+
+/* Define to 1 if you have the `strcspn' function. */
+#define HAVE_STRCSPN 1
+
+/* Define to 1 if you have the `strdup' function. */
+#define HAVE_STRDUP 1
+
+/* Define to 1 if you have the `strerror' function. */
+#define HAVE_STRERROR 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strlcat' function. */
+#define HAVE_STRLCAT 1
+
+/* Define to 1 if you have the `strlcpy' function. */
+#define HAVE_STRLCPY 1
+
+/* Define to 1 if you have the `strspn' function. */
+#define HAVE_STRSPN 1
+
+/* Define to 1 if you have the `strstr' function. */
+#define HAVE_STRSTR 1
+
+/* Define to 1 if `fhs_fh' is member of `struct fhstatus'. */
+/* #undef HAVE_STRUCT_FHSTATUS_FHS_FH */
+
+/* Define to 1 if `ifa_next' is member of `struct ifaddrs'. */
+#define HAVE_STRUCT_IFADDRS_IFA_NEXT 1
+
+/* Define to 1 if `ifr_addr' is member of `struct ifreq'. */
+#define HAVE_STRUCT_IFREQ_IFR_ADDR 1
+
+/* Define if have struct mntent in one of the standard headers */
+/* #undef HAVE_STRUCT_MNTENT */
+
+/* Define if have struct mnttab in one of the standard headers */
+/* #undef HAVE_STRUCT_MNTTAB */
+
+/* Define if have struct nfs_args in one of the standard nfs headers */
+#define HAVE_STRUCT_NFS_ARGS 1
+
+/* Define if have struct nfs_gfs_mount in one of the standard nfs headers */
+/* #undef HAVE_STRUCT_NFS_GFS_MOUNT */
+
+/* Define to 1 if `sa_len' is member of `struct sockaddr'. */
+#define HAVE_STRUCT_SOCKADDR_SA_LEN 1
+
+/* Define to 1 if `f_fstypename' is member of `struct statfs'. */
+#define HAVE_STRUCT_STATFS_F_FSTYPENAME 1
+
+/* Define to 1 if `devid' is member of `struct umntrequest'. */
+/* #undef HAVE_STRUCT_UMNTREQUEST_DEVID */
+
+/* Define to 1 if you have the `svc_getreq' function. */
+#define HAVE_SVC_GETREQ 1
+
+/* Define to 1 if you have the `svc_getreqset' function. */
+#define HAVE_SVC_GETREQSET 1
+
+/* Define to 1 if you have the `sysfs' function. */
+/* #undef HAVE_SYSFS */
+
+/* Define to 1 if you have the `syslog' function. */
+#define HAVE_SYSLOG 1
+
+/* Define to 1 if you have the <syslog.h> header file. */
+#define HAVE_SYSLOG_H 1
+
+/* Define to 1 if you have the <sys/config.h> header file. */
+/* #undef HAVE_SYS_CONFIG_H */
+
+/* Define to 1 if you have the <sys/dg_mount.h> header file. */
+/* #undef HAVE_SYS_DG_MOUNT_H */
+
+/* Define to 1 if you have the <sys/dir.h> header file. */
+#define HAVE_SYS_DIR_H 1
+
+/* Define to 1 if you have the <sys/errno.h> header file. */
+#define HAVE_SYS_ERRNO_H 1
+
+/* Define to 1 if you have the <sys/file.h> header file. */
+#define HAVE_SYS_FILE_H 1
+
+/* Define to 1 if you have the <sys/fsid.h> header file. */
+/* #undef HAVE_SYS_FSID_H */
+
+/* Define to 1 if you have the <sys/fstyp.h> header file. */
+/* #undef HAVE_SYS_FSTYP_H */
+
+/* Define to 1 if you have the <sys/fs/autofs.h> header file. */
+/* #undef HAVE_SYS_FS_AUTOFS_H */
+
+/* Define to 1 if you have the <sys/fs/autofs_prot.h> header file. */
+/* #undef HAVE_SYS_FS_AUTOFS_PROT_H */
+
+/* Define to 1 if you have the <sys/fs/cachefs_fs.h> header file. */
+/* #undef HAVE_SYS_FS_CACHEFS_FS_H */
+
+/* Define to 1 if you have the <sys/fs/efs_clnt.h> header file. */
+/* #undef HAVE_SYS_FS_EFS_CLNT_H */
+
+/* Define to 1 if you have the <sys/fs/nfs_clnt.h> header file. */
+/* #undef HAVE_SYS_FS_NFS_CLNT_H */
+
+/* Define to 1 if you have the <sys/fs/nfs.h> header file. */
+/* #undef HAVE_SYS_FS_NFS_H */
+
+/* Define to 1 if you have the <sys/fs/nfs/mount.h> header file. */
+/* #undef HAVE_SYS_FS_NFS_MOUNT_H */
+
+/* Define to 1 if you have the <sys/fs/nfs/nfs_clnt.h> header file. */
+/* #undef HAVE_SYS_FS_NFS_NFS_CLNT_H */
+
+/* Define to 1 if you have the <sys/fs/pc_fs.h> header file. */
+/* #undef HAVE_SYS_FS_PC_FS_H */
+
+/* Define to 1 if you have the <sys/fs/tmp.h> header file. */
+/* #undef HAVE_SYS_FS_TMP_H */
+
+/* Define to 1 if you have the <sys/fs_types.h> header file. */
+/* #undef HAVE_SYS_FS_TYPES_H */
+
+/* Define to 1 if you have the <sys/fs/ufs_mount.h> header file. */
+/* #undef HAVE_SYS_FS_UFS_MOUNT_H */
+
+/* Define to 1 if you have the <sys/fs/xfs_clnt.h> header file. */
+/* #undef HAVE_SYS_FS_XFS_CLNT_H */
+
+/* Define to 1 if you have the <sys/immu.h> header file. */
+/* #undef HAVE_SYS_IMMU_H */
+
+/* Define to 1 if you have the <sys/ioctl.h> header file. */
+#define HAVE_SYS_IOCTL_H 1
+
+/* Define to 1 if you have the <sys/lock.h> header file. */
+#define HAVE_SYS_LOCK_H 1
+
+/* Define to 1 if you have the <sys/machine.h> header file. */
+/* #undef HAVE_SYS_MACHINE_H */
+
+/* Define to 1 if you have the <sys/mbuf.h> header file. */
+#define HAVE_SYS_MBUF_H 1
+
+/* Define to 1 if you have the <sys/mman.h> header file. */
+#define HAVE_SYS_MMAN_H 1
+
+/* Define to 1 if you have the <sys/mntctl.h> header file. */
+/* #undef HAVE_SYS_MNTCTL_H */
+
+/* Define to 1 if you have the <sys/mntent.h> header file. */
+/* #undef HAVE_SYS_MNTENT_H */
+
+/* Define to 1 if you have the <sys/mnttab.h> header file. */
+/* #undef HAVE_SYS_MNTTAB_H */
+
+/* Define to 1 if you have the <sys/mount.h> header file. */
+#define HAVE_SYS_MOUNT_H 1
+
+/* Define to 1 if you have the <sys/ndir.h> header file. */
+/* #undef HAVE_SYS_NDIR_H */
+
+/* Define to 1 if you have the <sys/netconfig.h> header file. */
+/* #undef HAVE_SYS_NETCONFIG_H */
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#define HAVE_SYS_PARAM_H 1
+
+/* Define to 1 if you have the <sys/pathconf.h> header file. */
+/* #undef HAVE_SYS_PATHCONF_H */
+
+/* Define to 1 if you have the <sys/proc.h> header file. */
+#define HAVE_SYS_PROC_H 1
+
+/* Define to 1 if you have the <sys/resource.h> header file. */
+#define HAVE_SYS_RESOURCE_H 1
+
+/* Define to 1 if you have the <sys/sema.h> header file. */
+#define HAVE_SYS_SEMA_H 1
+
+/* Define to 1 if you have the <sys/signal.h> header file. */
+#define HAVE_SYS_SIGNAL_H 1
+
+/* Define to 1 if you have the <sys/socket.h> header file. */
+#define HAVE_SYS_SOCKET_H 1
+
+/* Define to 1 if you have the <sys/sockio.h> header file. */
+#define HAVE_SYS_SOCKIO_H 1
+
+/* Define to 1 if you have the <sys/statfs.h> header file. */
+/* #undef HAVE_SYS_STATFS_H */
+
+/* Define to 1 if you have the <sys/statvfs.h> header file. */
+#define HAVE_SYS_STATVFS_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/syscall.h> header file. */
+#define HAVE_SYS_SYSCALL_H 1
+
+/* Define to 1 if you have the <sys/syslimits.h> header file. */
+#define HAVE_SYS_SYSLIMITS_H 1
+
+/* Define to 1 if you have the <sys/syslog.h> header file. */
+#define HAVE_SYS_SYSLOG_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/tiuser.h> header file. */
+/* #undef HAVE_SYS_TIUSER_H */
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <sys/ucred.h> header file. */
+#define HAVE_SYS_UCRED_H 1
+
+/* Define to 1 if you have the <sys/uio.h> header file. */
+#define HAVE_SYS_UIO_H 1
+
+/* Define to 1 if you have the <sys/utsname.h> header file. */
+#define HAVE_SYS_UTSNAME_H 1
+
+/* Define to 1 if you have the <sys/vfs.h> header file. */
+/* #undef HAVE_SYS_VFS_H */
+
+/* Define to 1 if you have the <sys/vmount.h> header file. */
+/* #undef HAVE_SYS_VMOUNT_H */
+
+/* Define to 1 if you have the <sys/vnode.h> header file. */
+#define HAVE_SYS_VNODE_H 1
+
+/* Define to 1 if you have <sys/wait.h> that is POSIX.1 compatible. */
+#define HAVE_SYS_WAIT_H 1
+
+/* Define to 1 if you have the <tcpd.h> header file. */
+#define HAVE_TCPD_H 1
+
+/* Define to 1 if you have the <time.h> header file. */
+#define HAVE_TIME_H 1
+
+/* Define to 1 if you have the <tiuser.h> header file. */
+/* #undef HAVE_TIUSER_H */
+
+/* Define to 1 if you have the <tmpfs/tmp.h> header file. */
+/* #undef HAVE_TMPFS_TMP_H */
+
+/* what type of network transport type is in use? TLI or sockets? */
+/* #undef HAVE_TRANSPORT_TYPE_TLI */
+
+/* Define to 1 if you have the `ualarm' function. */
+#define HAVE_UALARM 1
+
+/* Define to 1 if `flags' is member of `ufs_args_t'. */
+/* #undef HAVE_UFS_ARGS_T_FLAGS */
+
+/* Define to 1 if `fspec' is member of `ufs_args_t'. */
+#define HAVE_UFS_ARGS_T_FSPEC 1
+
+/* Define to 1 if `ufs_flags' is member of `ufs_args_t'. */
+/* #undef HAVE_UFS_ARGS_T_UFS_FLAGS */
+
+/* Define to 1 if `ufs_pgthresh' is member of `ufs_args_t'. */
+/* #undef HAVE_UFS_ARGS_T_UFS_PGTHRESH */
+
+/* Define to 1 if you have the <ufs/ufs/extattr.h> header file. */
+#define HAVE_UFS_UFS_EXTATTR_H 1
+
+/* Define to 1 if you have the <ufs/ufs_mount.h> header file. */
+/* #undef HAVE_UFS_UFS_MOUNT_H */
+
+/* Define to 1 if you have the <ufs/ufs/ufsmount.h> header file. */
+#define HAVE_UFS_UFS_UFSMOUNT_H 1
+
+/* Define to 1 if you have the `umount' function. */
+/* #undef HAVE_UMOUNT */
+
+/* Define to 1 if you have the `umount2' function. */
+/* #undef HAVE_UMOUNT2 */
+
+/* Define to 1 if you have the `uname' function. */
+#define HAVE_UNAME 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the `unmount' function. */
+#define HAVE_UNMOUNT 1
+
+/* Define to 1 if you have the `uvmount' function. */
+/* #undef HAVE_UVMOUNT */
+
+/* Define to 1 if you have the <varargs.h> header file. */
+/* #undef HAVE_VARARGS_H */
+
+/* Define to 1 if you have the `vfork' function. */
+#define HAVE_VFORK 1
+
+/* Define to 1 if you have the <vfork.h> header file. */
+/* #undef HAVE_VFORK_H */
+
+/* Define to 1 if you have the `vfsmount' function. */
+/* #undef HAVE_VFSMOUNT */
+
+/* Define to 1 if you have the `vmount' function. */
+/* #undef HAVE_VMOUNT */
+
+/* Define to 1 if you have the `vsnprintf' function. */
+#define HAVE_VSNPRINTF 1
+
+/* Define to 1 if you have the `wait3' function. */
+#define HAVE_WAIT3 1
+
+/* Define to 1 if you have the `waitpid' function. */
+#define HAVE_WAITPID 1
+
+/* Define to 1 if `fork' works. */
+#define HAVE_WORKING_FORK 1
+
+/* Define to 1 if `vfork' works. */
+#define HAVE_WORKING_VFORK 1
+
+/* Define to 1 if you have the `xdr_attrstat' function. */
+#define HAVE_XDR_ATTRSTAT 1
+
+/* Define to 1 if you have the `xdr_createargs' function. */
+#define HAVE_XDR_CREATEARGS 1
+
+/* Define to 1 if you have the `xdr_dirlist' function. */
+#define HAVE_XDR_DIRLIST 1
+
+/* Define to 1 if you have the `xdr_diropargs' function. */
+#define HAVE_XDR_DIROPARGS 1
+
+/* Define to 1 if you have the `xdr_diropokres' function. */
+#define HAVE_XDR_DIROPOKRES 1
+
+/* Define to 1 if you have the `xdr_diropres' function. */
+#define HAVE_XDR_DIROPRES 1
+
+/* Define to 1 if you have the `xdr_dirpath' function. */
+#define HAVE_XDR_DIRPATH 1
+
+/* Define to 1 if you have the `xdr_entry' function. */
+#define HAVE_XDR_ENTRY 1
+
+/* Define to 1 if you have the `xdr_exportnode' function. */
+#define HAVE_XDR_EXPORTNODE 1
+
+/* Define to 1 if you have the `xdr_exports' function. */
+#define HAVE_XDR_EXPORTS 1
+
+/* Define to 1 if you have the `xdr_fattr' function. */
+#define HAVE_XDR_FATTR 1
+
+/* Define to 1 if you have the `xdr_fhandle' function. */
+#define HAVE_XDR_FHANDLE 1
+
+/* Define to 1 if you have the `xdr_fhstatus' function. */
+#define HAVE_XDR_FHSTATUS 1
+
+/* Define to 1 if you have the `xdr_filename' function. */
+#define HAVE_XDR_FILENAME 1
+
+/* Define to 1 if you have the `xdr_ftype' function. */
+#define HAVE_XDR_FTYPE 1
+
+/* Define to 1 if you have the `xdr_groupnode' function. */
+#define HAVE_XDR_GROUPNODE 1
+
+/* Define to 1 if you have the `xdr_groups' function. */
+#define HAVE_XDR_GROUPS 1
+
+/* Define to 1 if you have the `xdr_linkargs' function. */
+#define HAVE_XDR_LINKARGS 1
+
+/* Define to 1 if you have the `xdr_mountbody' function. */
+#define HAVE_XDR_MOUNTBODY 1
+
+/* Define to 1 if you have the `xdr_mountlist' function. */
+#define HAVE_XDR_MOUNTLIST 1
+
+/* Define to 1 if you have the `xdr_name' function. */
+#define HAVE_XDR_NAME 1
+
+/* Define to 1 if you have the `xdr_nfscookie' function. */
+#define HAVE_XDR_NFSCOOKIE 1
+
+/* Define to 1 if you have the `xdr_nfspath' function. */
+#define HAVE_XDR_NFSPATH 1
+
+/* Define to 1 if you have the `xdr_nfsstat' function. */
+#define HAVE_XDR_NFSSTAT 1
+
+/* Define to 1 if you have the `xdr_nfstime' function. */
+#define HAVE_XDR_NFSTIME 1
+
+/* Define to 1 if you have the `xdr_nfs_fh' function. */
+#define HAVE_XDR_NFS_FH 1
+
+/* Define to 1 if you have the `xdr_pointer' function. */
+#define HAVE_XDR_POINTER 1
+
+/* Define to 1 if you have the `xdr_readargs' function. */
+#define HAVE_XDR_READARGS 1
+
+/* Define to 1 if you have the `xdr_readdirargs' function. */
+#define HAVE_XDR_READDIRARGS 1
+
+/* Define to 1 if you have the `xdr_readdirres' function. */
+#define HAVE_XDR_READDIRRES 1
+
+/* Define to 1 if you have the `xdr_readlinkres' function. */
+#define HAVE_XDR_READLINKRES 1
+
+/* Define to 1 if you have the `xdr_readokres' function. */
+#define HAVE_XDR_READOKRES 1
+
+/* Define to 1 if you have the `xdr_readres' function. */
+#define HAVE_XDR_READRES 1
+
+/* Define to 1 if you have the `xdr_renameargs' function. */
+#define HAVE_XDR_RENAMEARGS 1
+
+/* Define to 1 if you have the `xdr_sattr' function. */
+#define HAVE_XDR_SATTR 1
+
+/* Define to 1 if you have the `xdr_sattrargs' function. */
+#define HAVE_XDR_SATTRARGS 1
+
+/* Define to 1 if you have the `xdr_statfsokres' function. */
+#define HAVE_XDR_STATFSOKRES 1
+
+/* Define to 1 if you have the `xdr_statfsres' function. */
+#define HAVE_XDR_STATFSRES 1
+
+/* Define to 1 if you have the `xdr_symlinkargs' function. */
+#define HAVE_XDR_SYMLINKARGS 1
+
+/* Define to 1 if you have the `xdr_writeargs' function. */
+#define HAVE_XDR_WRITEARGS 1
+
+/* Define to 1 if `flags' is member of `xfs_args_t'. */
+/* #undef HAVE_XFS_ARGS_T_FLAGS */
+
+/* Define to 1 if `fspec' is member of `xfs_args_t'. */
+/* #undef HAVE_XFS_ARGS_T_FSPEC */
+
+/* Define to 1 if you have the `yp_all' function. */
+/* #undef HAVE_YP_ALL */
+
+/* Define to 1 if you have the `yp_get_default_domain' function. */
+#define HAVE_YP_GET_DEFAULT_DOMAIN 1
+
+/* Define to 1 if you have the `_seterr_reply' function. */
+#define HAVE__SETERR_REPLY 1
+
+/* Define to 1 if you have the `__rpc_get_local_uid' function. */
+#define HAVE___RPC_GET_LOCAL_UID 1
+
+/* Define to 1 if you have the `__seterr_reply' function. */
+/* #undef HAVE___SETERR_REPLY */
+
+/* Name of mount type to hide amd mount from df(1) */
+#define HIDE_MOUNT_TYPE "nfs"
+
+/* Define name of host machine's architecture (eg. sun4) */
+/* #define HOST_ARCH "i386" */
+
+/* Define name of host machine's cpu (eg. sparc) */
+/* #define HOST_CPU "i386" */
+
+/* Define the header version of (linux) hosts (eg. 2.2.10) */
+// #undef define HOST_HEADER_VERSION */
+
+/* Define name of host */
+/* #define HOST_NAME "trang.nuxi.org" */
+
+/* Define name and version of host machine (eg. solaris2.5.1) */
+/* #define HOST_OS "freebsd8.0" */
+
+/* Define only name of host machine OS (eg. solaris2) */
+/* #define HOST_OS_NAME "freebsd8" */
+
+/* Define only version of host machine (eg. 2.5.1) */
+/* #define HOST_OS_VERSION "8.0" */
+
+/* Define name of host machine's vendor (eg. sun) */
+#define HOST_VENDOR "undermydesk"
+
+/* Ignore permission bits */
+/* #undef MNT2_CDFS_OPT_DEFPERM */
+
+/* Use on-disk permission bits */
+/* #undef MNT2_CDFS_OPT_NODEFPERM */
+
+/* Strip off extension from version string */
+/* #undef MNT2_CDFS_OPT_NOVERSION */
+
+/* Use Rock Ridge Interchange Protocol (RRIP) extensions */
+/* #undef MNT2_CDFS_OPT_RRIP */
+
+/* asynchronous filesystem access */
+#define MNT2_GEN_OPT_ASYNC 0x40
+
+/* automounter filesystem (ignore) flag, used in bsdi-4.1 */
+/* #undef MNT2_GEN_OPT_AUTOMNTFS */
+
+/* automounter filesystem flag, used in Mac OS X / Darwin */
+/* #undef MNT2_GEN_OPT_AUTOMOUNTED */
+
+/* directory hardlink */
+/* #undef MNT2_GEN_OPT_BIND */
+
+/* cache (what?) */
+/* #undef MNT2_GEN_OPT_CACHE */
+
+/* 6-argument mount */
+/* #undef MNT2_GEN_OPT_DATA */
+
+/* Use a lazy unmount (detach) */
+/* #undef MNT2_GEN_OPT_DETACH */
+
+/* Use a forced unmount */
+#define MNT2_GEN_OPT_FORCE 0x80000
+
+/* old (4-argument) mount (compatibility) */
+/* #undef MNT2_GEN_OPT_FSS */
+
+/* old BSD group-id on create */
+/* #undef MNT2_GEN_OPT_GRPID */
+
+/* ignore mount entry in df output */
+#define MNT2_GEN_OPT_IGNORE 0x800000
+
+/* journaling filesystem (AIX's UFS/FFS) */
+/* #undef MNT2_GEN_OPT_JFS */
+
+/* do multi-component lookup on files */
+/* #undef MNT2_GEN_OPT_MULTI */
+
+/* use type string instead of int */
+/* #undef MNT2_GEN_OPT_NEWTYPE */
+
+/* NFS mount */
+/* #undef MNT2_GEN_OPT_NFS */
+
+/* nocache (what?) */
+/* #undef MNT2_GEN_OPT_NOCACHE */
+
+/* do not interpret special device files */
+#define MNT2_GEN_OPT_NODEV 0x0
+
+/* no exec calls allowed */
+#define MNT2_GEN_OPT_NOEXEC 0x4
+
+/* do not interpret special device files */
+/* #undef MNT2_GEN_OPT_NONDEV */
+
+/* Disallow mounts beneath this mount */
+/* #undef MNT2_GEN_OPT_NOSUB */
+
+/* Setuid programs disallowed */
+#define MNT2_GEN_OPT_NOSUID 0x8
+
+/* Return ENAMETOOLONG for long filenames */
+/* #undef MNT2_GEN_OPT_NOTRUNC */
+
+/* Pass mount option string to kernel */
+/* #undef MNT2_GEN_OPT_OPTIONSTR */
+
+/* allow overlay mounts */
+/* #undef MNT2_GEN_OPT_OVERLAY */
+
+/* check quotas */
+#define MNT2_GEN_OPT_QUOTA 0x2000
+
+/* Read-only */
+#define MNT2_GEN_OPT_RDONLY 0x1
+
+/* change options on an existing mount */
+/* #undef MNT2_GEN_OPT_REMOUNT */
+
+/* read only */
+/* #undef MNT2_GEN_OPT_RONLY */
+
+/* synchronize data immediately to filesystem */
+/* #undef MNT2_GEN_OPT_SYNC */
+
+/* synchronous filesystem access (same as SYNC) */
+#define MNT2_GEN_OPT_SYNCHRONOUS 0x2
+
+/* Mount with Sys 5-specific semantics */
+/* #undef MNT2_GEN_OPT_SYS5 */
+
+/* Union mount */
+/* #undef MNT2_GEN_OPT_UNION */
+
+/* set max secs for dir attr cache */
+#define MNT2_NFS_OPT_ACDIRMAX 0x200000
+
+/* set min secs for dir attr cache */
+#define MNT2_NFS_OPT_ACDIRMIN 0x100000
+
+/* set max secs for file attr cache */
+#define MNT2_NFS_OPT_ACREGMAX 0x80000
+
+/* set min secs for file attr cache */
+#define MNT2_NFS_OPT_ACREGMIN 0x40000
+
+/* Authentication error */
+/* #undef MNT2_NFS_OPT_AUTHERR */
+
+/* hide mount type from df(1) */
+/* #undef MNT2_NFS_OPT_AUTO */
+
+/* set dead server retry thresh */
+#define MNT2_NFS_OPT_DEADTHRESH 0x4000
+
+/* Dismount in progress */
+/* #undef MNT2_NFS_OPT_DISMINPROG */
+
+/* Dismounted */
+/* #undef MNT2_NFS_OPT_DISMNT */
+
+/* Don't estimate rtt dynamically */
+#define MNT2_NFS_OPT_DUMBTIMR 0x800
+
+/* provide name of server's fs to system */
+/* #undef MNT2_NFS_OPT_FSNAME */
+
+/* System V-style gid inheritance */
+/* #undef MNT2_NFS_OPT_GRPID */
+
+/* Has authenticator */
+/* #undef MNT2_NFS_OPT_HASAUTH */
+
+/* set hostname for error printf */
+/* #undef MNT2_NFS_OPT_HOSTNAME */
+
+/* ignore mount point */
+/* #undef MNT2_NFS_OPT_IGNORE */
+
+/* allow interrupts on hard mount */
+#define MNT2_NFS_OPT_INT 0x40
+
+/* Bits set internally */
+/* #undef MNT2_NFS_OPT_INTERNAL */
+
+/* allow interrupts on hard mount */
+/* #undef MNT2_NFS_OPT_INTR */
+
+/* Use Kerberos authentication */
+/* #undef MNT2_NFS_OPT_KERB */
+
+/* use kerberos credentials */
+/* #undef MNT2_NFS_OPT_KERBEROS */
+
+/* transport's knetconfig structure */
+/* #undef MNT2_NFS_OPT_KNCONF */
+
+/* set lease term (nqnfs) */
+/* #undef MNT2_NFS_OPT_LEASETERM */
+
+/* Local locking (no lock manager) */
+/* #undef MNT2_NFS_OPT_LLOCK */
+
+/* set maximum grouplist size */
+#define MNT2_NFS_OPT_MAXGRPS 0x20
+
+/* Mnt server for mnt point */
+/* #undef MNT2_NFS_OPT_MNTD */
+
+/* Assume writes were mine */
+/* #undef MNT2_NFS_OPT_MYWRITE */
+
+/* mount NFS Version 3 */
+#define MNT2_NFS_OPT_NFSV3 0x200
+
+/* don't cache attributes */
+/* #undef MNT2_NFS_OPT_NOAC */
+
+/* Don't Connect the socket */
+#define MNT2_NFS_OPT_NOCONN 0x80
+
+/* no close-to-open consistency */
+/* #undef MNT2_NFS_OPT_NOCTO */
+
+/* disallow interrupts on hard mounts */
+/* #undef MNT2_NFS_OPT_NOINT */
+
+/* Don't use locking */
+/* #undef MNT2_NFS_OPT_NONLM */
+
+/* Get lease for lookup */
+/* #undef MNT2_NFS_OPT_NQLOOKLEASE */
+
+/* Use Nqnfs protocol */
+/* #undef MNT2_NFS_OPT_NQNFS */
+
+/* paging threshold */
+/* #undef MNT2_NFS_OPT_PGTHRESH */
+
+/* static pathconf kludge info */
+/* #undef MNT2_NFS_OPT_POSIX */
+
+/* Use local locking */
+/* #undef MNT2_NFS_OPT_PRIVATE */
+
+/* allow property list operations (ACLs over NFS) */
+/* #undef MNT2_NFS_OPT_PROPLIST */
+
+/* Rcv socket lock */
+/* #undef MNT2_NFS_OPT_RCVLOCK */
+
+/* Do lookup with readdir (nqnfs) */
+/* #undef MNT2_NFS_OPT_RDIRALOOK */
+
+/* Use Readdirplus for NFSv3 */
+#define MNT2_NFS_OPT_RDIRPLUS 0x10000
+
+/* set read ahead */
+#define MNT2_NFS_OPT_READAHEAD 0x2000
+
+/* Set readdir size */
+#define MNT2_NFS_OPT_READDIRSIZE 0x20000
+
+/* Allocate a reserved port */
+#define MNT2_NFS_OPT_RESVPORT 0x8000
+
+/* set number of request retries */
+#define MNT2_NFS_OPT_RETRANS 0x10
+
+/* read only */
+/* #undef MNT2_NFS_OPT_RONLY */
+
+/* use RPC to do secure NFS time sync */
+/* #undef MNT2_NFS_OPT_RPCTIMESYNC */
+
+/* set read size */
+#define MNT2_NFS_OPT_RSIZE 0x4
+
+/* secure mount */
+/* #undef MNT2_NFS_OPT_SECURE */
+
+/* Send socket lock */
+/* #undef MNT2_NFS_OPT_SNDLOCK */
+
+/* soft mount (hard is default) */
+#define MNT2_NFS_OPT_SOFT 0x1
+
+/* spongy mount */
+/* #undef MNT2_NFS_OPT_SPONGY */
+
+/* set symlink cache time-to-live */
+/* #undef MNT2_NFS_OPT_SYMTTL */
+
+/* use TCP for mounts */
+/* #undef MNT2_NFS_OPT_TCP */
+
+/* set initial timeout */
+#define MNT2_NFS_OPT_TIMEO 0x8
+
+/* linux NFSv3 */
+/* #undef MNT2_NFS_OPT_VER3 */
+
+/* Wait for authentication */
+/* #undef MNT2_NFS_OPT_WAITAUTH */
+
+/* Wants an authenticator */
+/* #undef MNT2_NFS_OPT_WANTAUTH */
+
+/* Want receive socket lock */
+/* #undef MNT2_NFS_OPT_WANTRCV */
+
+/* Want send socket lock */
+/* #undef MNT2_NFS_OPT_WANTSND */
+
+/* set write size */
+#define MNT2_NFS_OPT_WSIZE 0x2
+
+/* 32<->64 dir cookie translation */
+/* #undef MNT2_NFS_OPT_XLATECOOKIE */
+
+/* Force Win95 long names */
+#define MNT2_PCFS_OPT_LONGNAME 0x2
+
+/* Completely ignore Win95 entries */
+#define MNT2_PCFS_OPT_NOWIN95 0x4
+
+/* Force old DOS short names only */
+#define MNT2_PCFS_OPT_SHORTNAME 0x1
+
+/* Name of mount table file name */
+/* #undef MNTTAB_FILE_NAME */
+
+/* Mount Table option string: Max attr cache timeout (dirs) */
+/* #undef MNTTAB_OPT_ACDIRMAX */
+
+/* Mount Table option string: Min attr cache timeout (dirs) */
+/* #undef MNTTAB_OPT_ACDIRMIN */
+
+/* Mount Table option string: Max attr cache timeout (files) */
+/* #undef MNTTAB_OPT_ACREGMAX */
+
+/* Mount Table option string: Min attr cache timeout (files) */
+/* #undef MNTTAB_OPT_ACREGMIN */
+
+/* Mount Table option string: Attr cache timeout (sec) */
+/* #undef MNTTAB_OPT_ACTIMEO */
+
+/* Mount Table option string: Do mount retries in background */
+/* #undef MNTTAB_OPT_BG */
+
+/* Mount Table option string: compress */
+/* #undef MNTTAB_OPT_COMPRESS */
+
+/* Mount Table option string: Device id of mounted fs */
+/* #undef MNTTAB_OPT_DEV */
+
+/* Mount Table option string: Automount direct map mount */
+/* #undef MNTTAB_OPT_DIRECT */
+
+/* Mount Table option string: Do mount retries in foreground */
+/* #undef MNTTAB_OPT_FG */
+
+/* Mount Table option string: Filesystem id of mounted fs */
+/* #undef MNTTAB_OPT_FSID */
+
+/* Mount Table option string: SysV-compatible gid on create */
+/* #undef MNTTAB_OPT_GRPID */
+
+/* Mount Table option string: Hard mount */
+/* #undef MNTTAB_OPT_HARD */
+
+/* Mount Table option string: Ignore this entry */
+/* #undef MNTTAB_OPT_IGNORE */
+
+/* Mount Table option string: Automount indirect map mount */
+/* #undef MNTTAB_OPT_INDIRECT */
+
+/* Mount Table option string: Allow NFS ops to be interrupted */
+/* #undef MNTTAB_OPT_INTR */
+
+/* Mount Table option string: Secure (AUTH_Kerb) mounting */
+/* #undef MNTTAB_OPT_KERB */
+
+/* Mount Table option string: Local locking (no lock manager) */
+/* #undef MNTTAB_OPT_LLOCK */
+
+/* Force Win95 long names */
+/* #undef MNTTAB_OPT_LONGNAME */
+
+/* Mount Table option string: Automount map */
+/* #undef MNTTAB_OPT_MAP */
+
+/* Mount Table option string: max groups */
+/* #undef MNTTAB_OPT_MAXGROUPS */
+
+/* Mount Table option string: Do multi-component lookup */
+/* #undef MNTTAB_OPT_MULTI */
+
+/* Mount Table option string: Don't cache attributes at all */
+/* #undef MNTTAB_OPT_NOAC */
+
+/* Mount Table option string: No auto (what?) */
+/* #undef MNTTAB_OPT_NOAUTO */
+
+/* Mount Table option string: No connection */
+/* #undef MNTTAB_OPT_NOCONN */
+
+/* Mount Table option string: No close-to-open consistency */
+/* #undef MNTTAB_OPT_NOCTO */
+
+/* Mount Table option string: Don't allow interrupted ops */
+/* #undef MNTTAB_OPT_NOINTR */
+
+/* Mount Table option string: Don't check quotas */
+/* #undef MNTTAB_OPT_NOQUOTA */
+
+/* Mount Table option string: Do no allow setting sec attrs */
+/* #undef MNTTAB_OPT_NOSETSEC */
+
+/* Mount Table option string: Disallow mounts on subdirs */
+/* #undef MNTTAB_OPT_NOSUB */
+
+/* Mount Table option string: Set uid not allowed */
+/* #undef MNTTAB_OPT_NOSUID */
+
+/* Completely ignore Win95 entries */
+/* #undef MNTTAB_OPT_NOWIN95 */
+
+/* Mount Table option string: action to taken on error */
+/* #undef MNTTAB_OPT_ONERROR */
+
+/* Mount Table option string: paging threshold */
+/* #undef MNTTAB_OPT_PGTHRESH */
+
+/* Mount Table option string: NFS server IP port number */
+/* #undef MNTTAB_OPT_PORT */
+
+/* Mount Table option string: Get static pathconf for mount */
+/* #undef MNTTAB_OPT_POSIX */
+
+/* Mount Table option string: Use local locking */
+/* #undef MNTTAB_OPT_PRIVATE */
+
+/* Mount Table option string: support property lists (ACLs) */
+/* #undef MNTTAB_OPT_PROPLIST */
+
+/* Mount Table option string: protocol network_id indicator */
+/* #undef MNTTAB_OPT_PROTO */
+
+/* Mount Table option string: Check quotas */
+/* #undef MNTTAB_OPT_QUOTA */
+
+/* Mount Table option string: Change mount options */
+/* #undef MNTTAB_OPT_REMOUNT */
+
+/* Mount Table option string: Max retransmissions (soft mnts) */
+/* #undef MNTTAB_OPT_RETRANS */
+
+/* Mount Table option string: Number of mount retries */
+/* #undef MNTTAB_OPT_RETRY */
+
+/* Mount Table option string: Read only */
+/* #undef MNTTAB_OPT_RO */
+
+/* Mount Table option string: Read/write with quotas */
+/* #undef MNTTAB_OPT_RQ */
+
+/* Mount Table option string: Max NFS read size (bytes) */
+/* #undef MNTTAB_OPT_RSIZE */
+
+/* Mount Table option string: Read/write */
+/* #undef MNTTAB_OPT_RW */
+
+/* Mount Table option string: Secure (AUTH_DES) mounting */
+/* #undef MNTTAB_OPT_SECURE */
+
+/* Force old DOS short names only */
+/* #undef MNTTAB_OPT_SHORTNAME */
+
+/* Mount Table option string: Soft mount */
+/* #undef MNTTAB_OPT_SOFT */
+
+/* Mount Table option string: spongy mount */
+/* #undef MNTTAB_OPT_SPONGY */
+
+/* Mount Table option string: Set uid allowed */
+/* #undef MNTTAB_OPT_SUID */
+
+/* Mount Table option string: set symlink cache time-to-live */
+/* #undef MNTTAB_OPT_SYMTTL */
+
+/* Mount Table option string: Synchronous local directory ops */
+/* #undef MNTTAB_OPT_SYNCDIR */
+
+/* Mount Table option string: NFS timeout (1/10 sec) */
+/* #undef MNTTAB_OPT_TIMEO */
+
+/* Mount Table option string: min. time between inconsistencies */
+/* #undef MNTTAB_OPT_TOOSOON */
+
+/* Mount Table option string: protocol version number indicator */
+/* #undef MNTTAB_OPT_VERS */
+
+/* Mount Table option string: Max NFS write size (bytes) */
+/* #undef MNTTAB_OPT_WSIZE */
+
+/* Mount-table entry name for AUTOFS filesystem */
+/* #undef MNTTAB_TYPE_AUTOFS */
+
+/* Mount-table entry name for CACHEFS filesystem */
+/* #undef MNTTAB_TYPE_CACHEFS */
+
+/* Mount-table entry name for CDFS filesystem */
+#define MNTTAB_TYPE_CDFS "cd9660"
+
+/* Mount-table entry name for CFS (crypto) filesystem */
+/* #undef MNTTAB_TYPE_CFS */
+
+/* Mount-table entry name for EFS filesystem (irix) */
+/* #undef MNTTAB_TYPE_EFS */
+
+/* Mount-table entry name for FFS filesystem */
+/* #undef MNTTAB_TYPE_FFS */
+
+/* Mount-table entry name for LOFS filesystem */
+/* #undef MNTTAB_TYPE_LOFS */
+
+/* Mount-table entry name for MFS filesystem */
+/* #undef MNTTAB_TYPE_MFS */
+
+/* Mount-table entry name for NFS filesystem */
+#define MNTTAB_TYPE_NFS "nfs"
+
+/* Mount-table entry name for NFS3 filesystem */
+#define MNTTAB_TYPE_NFS3 "nfs3"
+
+/* Mount-table entry name for NULLFS (loopback on bsd44) filesystem */
+#define MNTTAB_TYPE_NULLFS "nullfs"
+
+/* Mount-table entry name for PCFS filesystem */
+#define MNTTAB_TYPE_PCFS "msdosfs"
+
+/* Mount-table entry name for TFS filesystem */
+/* #undef MNTTAB_TYPE_TFS */
+
+/* Mount-table entry name for TMPFS filesystem */
+/* #undef MNTTAB_TYPE_TMPFS */
+
+/* Mount-table entry name for UFS filesystem */
+#define MNTTAB_TYPE_UFS "ufs"
+
+/* Mount-table entry name for UMAPFS (uid/gid mapping) filesystem */
+/* #undef MNTTAB_TYPE_UMAPFS */
+
+/* Mount-table entry name for UNIONFS filesystem */
+#define MNTTAB_TYPE_UNIONFS "unionfs"
+
+/* Mount-table entry name for XFS filesystem (irix) */
+/* #undef MNTTAB_TYPE_XFS */
+
+/* Define if mount table is on file, undefine if in kernel */
+/* #undef MOUNT_TABLE_ON_FILE */
+
+/* Mount(2) type/name for AUTOFS filesystem */
+/* #undef MOUNT_TYPE_AUTOFS */
+
+/* Mount(2) type/name for CACHEFS filesystem */
+/* #undef MOUNT_TYPE_CACHEFS */
+
+/* Mount(2) type/name for CDFS filesystem */
+#define MOUNT_TYPE_CDFS "cd9660"
+
+/* Mount(2) type/name for CFS (crypto) filesystem */
+/* #undef MOUNT_TYPE_CFS */
+
+/* Mount(2) type/name for EFS filesystem (irix) */
+/* #undef MOUNT_TYPE_EFS */
+
+/* Mount(2) type/name for FFS filesystem */
+/* #undef MOUNT_TYPE_FFS */
+
+/* Mount(2) type/name for IGNORE filesystem (not real just ignore for df) */
+#define MOUNT_TYPE_IGNORE MNT_IGNORE
+
+/* Mount(2) type/name for LOFS filesystem */
+/* #undef MOUNT_TYPE_LOFS */
+
+/* Mount(2) type/name for MFS filesystem */
+/* #undef MOUNT_TYPE_MFS */
+
+/* Mount(2) type/name for NFS filesystem */
+#define MOUNT_TYPE_NFS "nfs"
+
+/* Mount(2) type/name for NFS3 filesystem */
+#define MOUNT_TYPE_NFS3 MOUNT_NFS3
+
+/* Mount(2) type/name for NULLFS (loopback on bsd44) filesystem */
+#define MOUNT_TYPE_NULLFS "nullfs"
+
+/* Mount(2) type/name for PCFS filesystem. XXX: conf/trap/trap_hpux.h may
+ override this definition for HPUX 9.0 */
+#define MOUNT_TYPE_PCFS "msdosfs"
+
+/* Mount(2) type/name for TFS filesystem */
+/* #undef MOUNT_TYPE_TFS */
+
+/* Mount(2) type/name for TMPFS filesystem */
+/* #undef MOUNT_TYPE_TMPFS */
+
+/* Mount(2) type/name for UFS filesystem */
+#define MOUNT_TYPE_UFS "ufs"
+
+/* Mount(2) type/name for UMAPFS (uid/gid mapping) filesystem */
+/* #undef MOUNT_TYPE_UMAPFS */
+
+/* Mount(2) type/name for UNIONFS filesystem */
+#define MOUNT_TYPE_UNIONFS MNT_UNION
+
+/* Mount(2) type/name for XFS filesystem (irix) */
+/* #undef MOUNT_TYPE_XFS */
+
+/* The string used in printf to print the mount-type field of mount(2) */
+#define MTYPE_PRINTF_TYPE "%s"
+
+/* Type of the mount-type field in the mount() system call */
+#define MTYPE_TYPE char *
+
+/* does libwrap expect caller to define the variables allow_severity and
+ deny_severity */
+/* #undef NEED_LIBWRAP_SEVERITY_VARIABLES */
+
+/* Defined to the header file containing ndbm-compatible definitions */
+#define NEW_DBM_H <ndbm.h>
+
+/* Define the field name for the filehandle within nfs_args_t */
+#define NFS_FH_FIELD fh
+
+/* Define to 1 if your C compiler doesn't accept -c and -o together. */
+/* #undef NO_MINUS_C_MINUS_O */
+
+/* Name of package */
+#define PACKAGE "am-utils"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "https://bugzilla.am-utils.org/ or am-utils@am-utils.org"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "am-utils"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "am-utils 6.1.5"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "am-utils"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "6.1.5"
+
+/* Type of the 6th argument to recvfrom() */
+#define RECVFROM_FROMLEN_TYPE int
+
+/* should signal handlers be reinstalled? */
+/* #undef REINSTALL_SIGNAL_HANDLER */
+
+/* Define as the return type of signal handlers (`int' or `void'). */
+#define RETSIGTYPE void
+
+/* Define to 1 if the `setpgrp' function takes no argument. */
+/* #undef SETPGRP_VOID */
+
+/* Define to 1 if the `S_IS*' macros in <sys/stat.h> do not work properly. */
+/* #undef STAT_MACROS_BROKEN */
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define the type of the 3rd argument ('in') to svc_getargs() */
+#define SVC_IN_ARG_TYPE caddr_t
+
+/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
+#define TIME_WITH_SYS_TIME 1
+
+/* Define to 1 if your <sys/time.h> declares `struct tm'. */
+/* #undef TM_IN_SYS_TIME */
+
+/* Define user name */
+/* #define USER_NAME "obrien" */
+
+/* define if must NOT use NFS "noconn" option */
+#define USE_CONNECTED_NFS_SOCKETS 1
+
+/* define if must use NFS "noconn" option */
+/* #undef USE_UNCONNECTED_NFS_SOCKETS */
+
+/* Version number of package */
+#define VERSION "6.1.5"
+
+/* Define to 1 if your processor stores words with the most significant byte
+ first (like Motorola and SPARC, unlike Intel and VAX). */
+/* #undef WORDS_BIGENDIAN */
+
+/* Define to the type of xdr procedure type */
+#define XDRPROC_T_TYPE xdrproc_t
+
+/* Type of the 3rd argument to yp_order() */
+#define YP_ORDER_OUTORDER_TYPE int
+
+/* Define to 1 if `lex' declares `yytext' as a `char *' by default, not a
+ `char[]'. */
+#define YYTEXT_POINTER 1
+
+/* Define to 1 if on AIX 3.
+ System headers sometimes define this.
+ We just want to avoid a redefinition error message. */
+#ifndef _ALL_SOURCE
+/* # undef _ALL_SOURCE */
+#endif
+
+/* Define a type/structure for an NFS V2 filehandle */
+#define am_nfs_fh nfs_fh
+
+/* Define a type/structure for an NFS V3 filehandle */
+#define am_nfs_fh3 nfs_fh3_freebsd3
+
+/* Define a type for the autofs_args structure */
+/* #undef autofs_args_t */
+
+/* Define a type for the cachefs_args structure */
+/* #undef cachefs_args_t */
+
+/* Define a type for the cdfs_args structure */
+#define cdfs_args_t struct iso_args
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define a type for the efs_args structure */
+/* #undef efs_args_t */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef gid_t */
+
+/* Define to `__inline__' or `__inline' if that's what the C compiler
+ calls it, or to nothing if 'inline' is not supported under any name. */
+#ifndef __cplusplus
+/* #undef inline */
+#endif
+
+/* Define a type for the lofs_args structure */
+/* #undef lofs_args_t */
+
+/* Define a type for the mfs_args structure */
+/* #undef mfs_args_t */
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef mode_t */
+
+/* Define a type for the nfs_args structure */
+#define nfs_args_t struct nfs_args
+
+/* Define a type for the pcfs_args structure */
+#define pcfs_args_t struct msdosfs_args
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef pid_t */
+
+/* Check if pte_t is defined in <sys/immu.h> */
+/* #undef pte_t */
+
+/* Define a type for the rfs_args structure */
+/* #undef rfs_args_t */
+
+/* Check if rpcvers_t is defined in <rpc/types.h> */
+/* #undef rpcvers_t */
+
+/* Define to `unsigned' if <sys/types.h> does not define. */
+/* #undef size_t */
+
+/* Define to `long' if <sys/types.h> does not define. */
+/* #undef time_t */
+
+/* Define a type for the tmpfs_args structure */
+/* #undef tmpfs_args_t */
+
+/* Define a type for the ufs_args structure */
+#define ufs_args_t struct ufs_args
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef uid_t */
+
+/* Define as `fork' if `vfork' does not work. */
+/* #undef vfork */
+
+/* Define to "void *" if compiler can handle, otherwise "char *" */
+#define voidp void *
+
+/* Define to empty if the keyword `volatile' does not work. Warning: valid
+ code using `volatile' can become incorrect without. Disable with care. */
+/* #undef volatile */
+
+/* Define a type for the xfs_args structure */
+/* #undef xfs_args_t */
+
+
+/****************************************************************************/
+/*** INCLUDE localconfig.h if it exists, to allow users to make some ***/
+/*** compile time configuration changes. ***/
+/****************************************************************************/
+/* does a local configuration file exist? */
+/* #undef HAVE_LOCALCONFIG_H */
+#ifdef HAVE_LOCALCONFIG_H
+# include <localconfig.h>
+#endif /* HAVE_LOCALCONFIG_H */
+
+#endif /* not _CONFIG_H */
+
+/*
+ * Local Variables:
+ * mode: c
+ * End:
+ */
+
+/* End of am-utils-6.x config.h file */
diff --git a/usr.sbin/amd/include/newvers.sh b/usr.sbin/amd/include/newvers.sh
new file mode 100644
index 0000000..0ca390e
--- /dev/null
+++ b/usr.sbin/amd/include/newvers.sh
@@ -0,0 +1,34 @@
+# $NetBSD: mkconf,v 1.1.1.1 1997/07/24 21:20:12 christos Exp $
+# $FreeBSD$
+# mkconf
+# Generate local configuration parameters for amd
+#
+
+if [ -e $1 ]; then
+ eval `LC_ALL=C egrep '^[A-Z]+=' $1 | grep -v COPYRIGHT`
+ OS=`echo ${TYPE} | LC_ALL=C tr 'A-Z' 'a-z'`
+ echo '/* Define name and version of host machine (eg. solaris2.5.1) */'
+ echo "#define HOST_OS \"${OS}${REVISION}\""
+ echo '/* Define only name of host machine OS (eg. solaris2) */'
+ echo "#define HOST_OS_NAME \"${OS}${REVISION}\"" \
+ | sed -e 's/\.[-._0-9]*//'
+ echo '/* Define only version of host machine (eg. 2.5.1) */'
+ echo "#define HOST_OS_VERSION \"${REVISION}\""
+else
+cat << __NO_newvers_sh
+
+/* Define name and version of host machine (eg. solaris2.5.1) */
+#define HOST_OS "`uname -s | LC_ALL=C tr 'A-Z' 'a-z'``uname -r`"
+
+/* Define only name of host machine OS (eg. solaris2) */
+#define HOST_OS_NAME "`uname -s | LC_ALL=C tr 'A-Z' 'a-z'``uname -r | sed -e 's/\..*$//'`"
+
+/* Define only version of host machine (eg. 2.5.1) */
+#define HOST_OS_VERSION "`uname -r | sed -e 's/[-([:alpha:]].*//'`"
+
+__NO_newvers_sh
+fi
+
+cat << __EOF
+
+__EOF
diff --git a/usr.sbin/amd/libamu/Makefile b/usr.sbin/amd/libamu/Makefile
new file mode 100644
index 0000000..fa8e3ab
--- /dev/null
+++ b/usr.sbin/amd/libamu/Makefile
@@ -0,0 +1,36 @@
+# ex:ts=8
+#
+# Makefile for amd
+# This file is under a "BSD" copyright (c) by David O'Brien 1998
+#
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../../contrib/amd/libamu \
+ ${.CURDIR}/../../../contrib/amd/conf/transp \
+ ${.CURDIR}/../../../contrib/amd/conf/mtab \
+ ${.CURDIR}/../../../contrib/amd/conf/umount
+
+LIB= amu
+INTERNALLIB=
+SRCS= hasmntopt.c misc_rpc.c mount_fs.c mtab.c nfs_prot_xdr.c \
+ strutil.c wire.c xutil.c
+
+# These would be links created by the GNU-style configure
+SRCS+= transp_sockets.c mtab_bsd.c umount_bsd44.c
+
+# Generated at compile time (replaces supplied xdr_func.c)
+SRCS+= nfs_prot_x.c xdr_func_%undef.c
+CLEANFILES+= nfs_prot_x.c xdr_func_%undef.c
+
+CFLAGS+= -I${.CURDIR}/../../../contrib/amd/libamu \
+ -I${DESTDIR}/usr/include/rpcsvc
+
+nfs_prot_x.c: ${NFS_PROT_X}
+ ${RPCCOM} -c -C -DWANT_NFS3 ${NFS_PROT_X} -o ${.TARGET}
+
+XDRDEFS!= grep 'ifndef.*HAVE_XDR' ${.CURDIR}/../../../contrib/amd/libamu/xdr_func.c | awk '{print "-D"$$2}'
+
+xdr_func_%undef.c: xdr_func.c
+ -unifdef ${XDRDEFS} < ${.ALLSRC} > ${.TARGET}
+
+.include <bsd.lib.mk>
diff --git a/usr.sbin/amd/libamu/Makefile.depend b/usr.sbin/amd/libamu/Makefile.depend
new file mode 100644
index 0000000..ee19de4
--- /dev/null
+++ b/usr.sbin/amd/libamu/Makefile.depend
@@ -0,0 +1,22 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/arpa \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/libwrap \
+ usr.sbin/amd/include \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+nfs_prot_x.o: nfs_prot_x.c
+nfs_prot_x.po: nfs_prot_x.c
+xdr_func_%undef.o: xdr_func_%undef.c
+xdr_func_%undef.po: xdr_func_%undef.c
+.endif
diff --git a/usr.sbin/amd/mk-amd-map/Makefile b/usr.sbin/amd/mk-amd-map/Makefile
new file mode 100644
index 0000000..417ea2a
--- /dev/null
+++ b/usr.sbin/amd/mk-amd-map/Makefile
@@ -0,0 +1,15 @@
+# ex:ts=8
+#
+# Makefile for amd
+# This file is under a "BSD" copyright (c) by David O'Brien 1998
+#
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../../contrib/amd/mk-amd-map
+
+PROG= mk-amd-map
+MAN= mk-amd-map.8
+
+LIBADD= amu
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/amd/mk-amd-map/Makefile.depend b/usr.sbin/amd/mk-amd-map/Makefile.depend
new file mode 100644
index 0000000..30890ab
--- /dev/null
+++ b/usr.sbin/amd/mk-amd-map/Makefile.depend
@@ -0,0 +1,24 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libwrap \
+ usr.sbin/amd/include \
+ usr.sbin/amd/libamu \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/amd/pawd/Makefile b/usr.sbin/amd/pawd/Makefile
new file mode 100644
index 0000000..2870ab4
--- /dev/null
+++ b/usr.sbin/amd/pawd/Makefile
@@ -0,0 +1,19 @@
+# ex:ts=8
+#
+# Makefile for amd
+# This file is under a "BSD" copyright (c) by David O'Brien 1998
+#
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../../contrib/amd/amq
+
+BINDIR= /usr/bin
+
+PROG= pawd
+SRCS= pawd.c amq_clnt.c amq_xdr.c
+
+CFLAGS+= -I${.CURDIR}/../../../contrib/amd/amq
+
+LIBADD= amu
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/amd/pawd/Makefile.depend b/usr.sbin/amd/pawd/Makefile.depend
new file mode 100644
index 0000000..30890ab
--- /dev/null
+++ b/usr.sbin/amd/pawd/Makefile.depend
@@ -0,0 +1,24 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libwrap \
+ usr.sbin/amd/include \
+ usr.sbin/amd/libamu \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/amd/scripts/Makefile b/usr.sbin/amd/scripts/Makefile
new file mode 100644
index 0000000..822b951
--- /dev/null
+++ b/usr.sbin/amd/scripts/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../../contrib/amd/scripts
+
+MAN= amd.conf.5
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/amd/scripts/Makefile.depend b/usr.sbin/amd/scripts/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/amd/scripts/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/amd/wire-test/Makefile b/usr.sbin/amd/wire-test/Makefile
new file mode 100644
index 0000000..edde2eb
--- /dev/null
+++ b/usr.sbin/amd/wire-test/Makefile
@@ -0,0 +1,15 @@
+# ex:ts=8
+#
+# Makefile for amd
+# This file is under a "BSD" copyright (c) by David O'Brien 1998
+#
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../../contrib/amd/wire-test
+
+PROG= wire-test
+MAN= wire-test.8
+
+LIBADD= amu
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/amd/wire-test/Makefile.depend b/usr.sbin/amd/wire-test/Makefile.depend
new file mode 100644
index 0000000..30890ab
--- /dev/null
+++ b/usr.sbin/amd/wire-test/Makefile.depend
@@ -0,0 +1,24 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libwrap \
+ usr.sbin/amd/include \
+ usr.sbin/amd/libamu \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ancontrol/Makefile b/usr.sbin/ancontrol/Makefile
new file mode 100644
index 0000000..f06b943
--- /dev/null
+++ b/usr.sbin/ancontrol/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+PROG= ancontrol
+MAN= ancontrol.8
+
+WARNS?= 3
+CFLAGS+= -DANCACHE -I${.CURDIR}/../../sys
+
+LIBADD= md
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ancontrol/Makefile.depend b/usr.sbin/ancontrol/Makefile.depend
new file mode 100644
index 0000000..6a2d406
--- /dev/null
+++ b/usr.sbin/ancontrol/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libmd \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ancontrol/ancontrol.8 b/usr.sbin/ancontrol/ancontrol.8
new file mode 100644
index 0000000..25fa5ae
--- /dev/null
+++ b/usr.sbin/ancontrol/ancontrol.8
@@ -0,0 +1,553 @@
+.\" Copyright (c) 1997, 1998, 1999
+.\" Bill Paul <wpaul@ee.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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd September 10, 1999
+.Dt ANCONTROL 8
+.Os
+.Sh NAME
+.Nm ancontrol
+.Nd configure Aironet 4500/4800 devices
+.Sh SYNOPSIS
+.Nm
+.Fl i Ar iface Fl A
+.Nm
+.Fl i Ar iface Fl N
+.Nm
+.Fl i Ar iface Fl S
+.Nm
+.Fl i Ar iface Fl I
+.Nm
+.Fl i Ar iface Fl T
+.Nm
+.Fl i Ar iface Fl C
+.Nm
+.Fl i Ar iface Fl Q
+.Nm
+.Fl i Ar iface Fl Z
+.Nm
+.Fl i Ar iface Fl R
+.Nm
+.Fl i Ar iface Fl t Cm 0 Ns - Ns Cm 4
+.Nm
+.Fl i Ar iface Fl s Cm 0 Ns - Ns Cm 3
+.Nm
+.Fl i Ar iface
+.Op Fl v Cm 1 Ns - Ns Cm 4
+.Fl a Ar AP
+.Nm
+.Fl i Ar iface Fl b Ar beacon_period
+.Nm
+.Fl i Ar iface
+.Op Fl v Cm 0 | 1
+.Fl d Cm 0 Ns - Ns Cm 3
+.Nm
+.Fl i Ar iface Fl e Cm 0 Ns - Ns Cm 4
+.Nm
+.Fl i Ar iface
+.Op Fl v Cm 0 Ns - Ns Cm 8
+.Fl k Ar key
+.Nm
+.Fl i Ar iface
+.Fl K Cm 0 Ns - Ns Cm 2
+.Nm
+.Fl i Ar iface
+.Fl W Cm 0 Ns - Ns Cm 2
+.Nm
+.Fl i Ar iface
+.Fl L Ar user_name
+.Nm
+.Fl i Ar iface Fl j Ar netjoin_timeout
+.Nm
+.Fl i Ar iface Fl l Ar station_name
+.Nm
+.Fl i Ar iface Fl m Ar mac_address
+.Nm
+.Fl i Ar iface
+.Op Fl v Cm 1 Ns - Ns Cm 3
+.Fl n Ar SSID
+.Nm
+.Fl i Ar iface Fl o Cm 0 | 1
+.Nm
+.Fl i Ar iface Fl p Ar tx_power
+.Nm
+.Fl i Ar iface Fl c Ar frequency
+.Nm
+.Fl i Ar iface Fl f Ar fragmentation_threshold
+.Nm
+.Fl i Ar iface Fl r Ar RTS_threshold
+.Nm
+.Fl i Ar iface Fl M Cm 0 Ns - Ns Cm 15
+.Nm
+.Fl h
+.Sh DESCRIPTION
+The
+.Nm
+utility controls the operation of Aironet wireless networking
+devices via the
+.Xr an 4
+driver.
+Most of the parameters that can be changed relate to the
+IEEE 802.11 protocol which the Aironet cards implement.
+This includes such things as
+the station name, whether the station is operating in ad-hoc (point
+to point) or infrastructure mode, and the network name of a service
+set to join.
+The
+.Nm
+utility can also be used to view the current NIC status, configuration
+and to dump out the values of the card's statistics counters.
+.Pp
+The
+.Ar iface
+argument given to
+.Nm
+should be the logical interface name associated with the Aironet
+device
+.Li ( an0 , an1 ,
+etc.).
+If one is not specified the device
+.Dq Li an0
+will be assumed.
+.Pp
+The
+.Nm
+utility is not designed to support the combination of arguments from different
+.Sx SYNOPSIS
+lines in a single
+.Nm
+invocation, and such combinations are not recommended.
+.Sh OPTIONS
+The options are as follows:
+.Bl -tag -width indent
+.It Fl i Ar iface Fl A
+Display the preferred access point list.
+The AP list can be used by
+stations to specify the MAC address of access points with which it
+wishes to associate.
+If no AP list is specified (the default) then
+the station will associate with the first access point that it finds
+which serves the SSID(s) specified in the SSID list.
+The AP list can
+be modified with the
+.Fl a
+option.
+.It Fl i Ar iface Fl N
+Display the SSID list.
+This is a list of service set IDs (i.e., network names)
+with which the station wishes to associate.
+There may be up to three SSIDs
+in the list: the station will go through the list in ascending order and
+associate with the first matching SSID that it finds.
+.It Fl i Ar iface Fl S
+Display NIC status information.
+This includes the current operating
+status, current BSSID, SSID, channel, beacon period and currently
+associated access point.
+The operating mode indicates the state of
+the NIC, MAC status and receiver status.
+When the
+.Qq Li synced
+keyword
+appears, it means the NIC has successfully associated with an access
+point, associated with an ad-hoc
+.Dq master
+station, or become a
+.Dq master
+itself.
+The beacon period can be anything between 20 and 976 milliseconds.
+The default is 100.
+.It Fl i Ar iface Fl I
+Display NIC capability information.
+This shows the device type,
+frequency, speed and power level capabilities and firmware revision levels.
+.It Fl i Ar iface Fl T
+Display the NIC's internal statistics counters.
+.It Fl i Ar iface Fl C
+Display current NIC configuration.
+This shows the current operation mode,
+receive mode, MAC address, power save settings, various timing settings,
+channel selection, diversity, transmit power and transmit speed.
+.It Fl i Ar iface Fl Q
+Display the cached signal strength information maintained by the
+.Xr an 4
+driver.
+The driver retains information about signal strength and
+noise level for packets received from different hosts.
+The signal strength and noise level values are displayed in units of dBms by
+default.
+The
+.Va hw.an.an_cache_mode
+.Xr sysctl 8
+variable can be set to
+.Cm raw , dbm
+or
+.Cm per .
+.It Fl i Ar iface Fl Z
+Clear the signal strength cache maintained internally by the
+.Xr an 4
+driver.
+.It Fl i Ar iface Fl R
+Display RSSI map that converts from the RSSI index to percent and dBm.
+.It Fl i Ar iface Fl t Cm 0 Ns - Ns Cm 4
+Select transmit speed.
+The available settings are as follows:
+.Bl -column ".Em TX rate" -offset indent
+.Em "TX rate NIC speed"
+.It Cm 0 Ta "Auto -- NIC selects optimal speed"
+.It Cm 1 Ta "1Mbps fixed"
+.It Cm 2 Ta "2Mbps fixed"
+.It Cm 3 Ta "5.5Mbps fixed"
+.It Cm 4 Ta "11Mbps fixed"
+.El
+.Pp
+Note that the 5.5 and 11Mbps settings are only supported on the 4800
+series adapters: the 4500 series adapters have a maximum speed of 2Mbps.
+.It Fl i Ar iface Fl s Cm 0 Ns - Ns Cm 3
+Set power save mode.
+Valid selections are as follows:
+.Bl -column ".Em Selection" -offset indent
+.Em "Selection Power save mode"
+.It Cm 0 Ta "None - power save disabled"
+.It Cm 1 Ta "Constantly awake mode (CAM)"
+.It Cm 2 Ta "Power Save Polling (PSP)"
+.It Cm 3 Ta "Fast Power Save Polling (PSP-CAM)"
+.El
+.Pp
+Note that for IBSS (ad-hoc) mode, only PSP mode is supported, and only
+if the ATIM window is non-zero.
+.It Fl i Ar iface Oo Fl v Cm 1 Ns - Ns Cm 4 Oc Fl a Ar AP
+Set preferred access point.
+The
+.Ar AP
+is specified as a MAC address consisting of 6 hexadecimal values
+separated by colons.
+By default, the
+.Fl a
+option only sets the first entry in the AP list.
+The
+.Fl v
+modifier can be used to specify exactly which AP list entry is to be
+modified.
+If the
+.Fl v
+flag is not used, the first AP list entry will be changed.
+.It Fl i Ar iface Fl b Ar beacon_period
+Set the ad-hoc mode beacon period.
+The
+.Ar beacon_period
+is specified in milliseconds.
+The default is 100ms.
+.It Fl i Ar iface Oo Fl v Cm 0 | 1 Oc Fl d Cm 0 Ns - Ns Cm 3
+Select the antenna diversity.
+Aironet devices can be configured with up
+to two antennas, and transmit and receive diversity can be configured
+accordingly.
+Valid selections are as follows:
+.Bl -column ".Em Selection" -offset indent
+.Em "Selection Diversity"
+.It Cm 0 Ta "Select factory default diversity"
+.It Cm 1 Ta "Antenna 1 only"
+.It Cm 2 Ta "Antenna 2 only"
+.It Cm 3 Ta "Antenna 1 and 2"
+.El
+.Pp
+The receive and transmit diversity can be set independently.
+The user
+must specify which diversity setting is to be modified by using the
+.Fl v
+option: selection
+.Cm 0
+sets the receive diversity and
+.Cm 1
+sets the transmit diversity.
+.It Fl i Ar iface Fl e Cm 0 Ns - Ns Cm 4
+Set the transmit WEP key to use.
+Note that until this command is issued, the device will use the
+last key programmed.
+The transmit key is stored in NVRAM.
+Currently
+set transmit key can be checked via
+.Fl C
+option.
+Selection
+.Cm 4
+sets the card in
+.Dq "Home Network Mode"
+and uses the home key.
+.It Fl i Ar iface Oo Fl v Cm 0 Ns - Ns Cm 8 Oc Fl k Ar key
+Set a WEP key.
+For 40 bit prefix 10 hex character with 0x.
+For 128 bit prefix 26 hex character with 0x.
+Use
+.Qq
+as the key to erase the key.
+Supports 4 keys; even numbers are for permanent keys
+and odd number are for temporary keys.
+For example,
+.Fl v Cm 1
+sets the first temporary key.
+(A
+.Dq permanent
+key is stored in NVRAM; a
+.Dq temporary
+key is not.)
+Note that the device will use the most recently-programmed key by default.
+Currently set keys can be checked via
+.Fl C
+option, only the sizes of the
+keys are returned.
+The value of
+.Cm 8
+is for the home key.
+Note that the value for the home key can be read back from firmware.
+.It Fl i Ar iface Fl K Cm 0 Ns - Ns Cm 2
+Set authorization type.
+Use
+.Cm 0
+for none,
+.Cm 1
+for
+.Dq Open ,
+.Cm 2
+for
+.Dq "Shared Key" .
+.It Fl i Ar iface Fl W Cm 0 Ns - Ns Cm 2
+Enable WEP.
+Use
+.Cm 0
+for no WEP,
+.Cm 1
+to enable full WEP,
+.Cm 2
+for mixed cell.
+.It Fl i Ar iface Fl L Ar user_name
+Enable LEAP and query for password.
+It will check to see if it has authenticated for up to 60s.
+To disable LEAP, set WEP mode.
+.It Fl i Ar iface Fl j Ar netjoin_timeout
+Set the ad-hoc network join timeout.
+When a station is first activated
+in ad-hoc mode, it will search out a
+.Dq master
+station with the desired
+SSID and associate with it.
+If the station is unable to locate another
+station with the same SSID after a suitable timeout, it sets itself up
+as the
+.Dq master
+so that other stations may associate with it.
+This
+timeout defaults to 10000 milliseconds (10 seconds) but may be changed
+with this option.
+The timeout should be specified in milliseconds.
+.It Fl i Ar iface Fl l Ar station_name
+Set the station name used internally by the NIC.
+The
+.Ar station_name
+can be any text string up to 16 characters in length.
+The default name
+is set by the driver to
+.Dq Li FreeBSD .
+.It Fl i Ar iface Fl m Ar mac_address
+Set the station address for the specified interface.
+The
+.Ar mac_address
+is specified as a series of six hexadecimal values separated by colons,
+e.g.:
+.Li 00:60:1d:12:34:56 .
+This programs the new address into the card
+and updates the interface as well.
+.It Fl i Ar iface Oo Fl v Cm 1 Ns - Ns Cm 3 Oc Fl n Ar SSID
+Set the desired SSID (network name).
+There are three SSIDs which allows
+the NIC to work with access points at several locations without needing
+to be reconfigured.
+The NIC checks each SSID in sequence when searching
+for a match.
+The SSID to be changed can be specified with the
+.Fl v
+modifier option.
+If the
+.Fl v
+flag is not used, the first SSID in the list is set.
+.It Fl i Ar iface Fl o Cm 0 | 1
+Set the operating mode of the Aironet interface.
+Valid selections are
+.Cm 0
+for ad-hoc mode and
+.Cm 1
+for infrastructure mode.
+The default driver setting is for infrastructure
+mode.
+.It Fl i Ar iface Fl p Ar tx_power
+Set the transmit power level in milliwatts.
+Valid power settings
+vary depending on the actual NIC and can be viewed by dumping the
+device capabilities with the
+.Fl I
+flag.
+Typical values are 1, 5, 20, 50 and 100mW.
+Selecting 0 sets
+the factory default.
+.It Fl i Ar iface Fl c Ar frequency
+Set the radio frequency of a given interface.
+The
+.Ar frequency
+should be specified as a channel ID as shown in the table below.
+The
+list of available frequencies is dependent on radio regulations specified
+by regional authorities.
+Recognized regulatory authorities include
+the FCC (United States), ETSI (Europe), France and Japan.
+Frequencies
+in the table are specified in MHz.
+.Bl -column ".Em Channel ID" ".Em FCC" ".Em ETSI" ".Em France" ".Em Japan" -offset indent
+.Em "Channel ID FCC ETSI France Japan"
+.It Cm 1 Ta 2412 Ta 2412 Ta - Ta -
+.It Cm 2 Ta 2417 Ta 2417 Ta - Ta -
+.It Cm 3 Ta 2422 Ta 2422 Ta - Ta -
+.It Cm 4 Ta 2427 Ta 2427 Ta - Ta -
+.It Cm 5 Ta 2432 Ta 2432 Ta - Ta -
+.It Cm 6 Ta 2437 Ta 2437 Ta - Ta -
+.It Cm 7 Ta 2442 Ta 2442 Ta - Ta -
+.It Cm 8 Ta 2447 Ta 2447 Ta - Ta -
+.It Cm 9 Ta 2452 Ta 2452 Ta - Ta -
+.It Cm 10 Ta 2457 Ta 2457 Ta 2457 Ta -
+.It Cm 11 Ta 2462 Ta 2462 Ta 2462 Ta -
+.It Cm 12 Ta - Ta 2467 Ta 2467 Ta -
+.It Cm 13 Ta - Ta 2472 Ta 2472 Ta -
+.It Cm 14 Ta - Ta - Ta - Ta 2484
+.El
+.Pp
+If an illegal channel is specified, the
+NIC will revert to its default channel.
+For NICs sold in the United States
+and Europe, the default channel is 3.
+For NICs sold in France, the default
+channel is 11.
+For NICs sold in Japan, the only available channel is 14.
+Note that two stations must be set to the same channel in order to
+communicate.
+.It Fl i Ar iface Fl f Ar fragmentation_threshold
+Set the fragmentation threshold in bytes.
+This threshold controls the
+point at which outgoing packets will be split into multiple fragments.
+If a single fragment is not sent successfully, only that fragment will
+need to be retransmitted instead of the whole packet.
+The fragmentation
+threshold can be anything from 64 to 2312 bytes.
+The default is 2312.
+.It Fl i Ar iface Fl r Ar RTS_threshold
+Set the RTS/CTS threshold for a given interface.
+This controls the
+number of bytes used for the RTS/CTS handshake boundary.
+The
+.Ar RTS_threshold
+can be any value between 0 and 2312.
+The default is 2312.
+.It Fl i Ar iface Fl M Cm 0 Ns - Ns Cm 15
+Set monitor mode via bit mask, meaning:
+.Pp
+.Bl -tag -width indent -offset indent -compact
+.It Em Bit
+.Em Meaning
+.It 0
+to not dump 802.11 packet.
+.It 1
+to enable 802.11 monitor.
+.It 2
+to monitor any SSID.
+.It 4
+to not skip beacons, monitor beacons produces a high system load.
+.It 8
+to enable full Aironet header returned via BPF.
+Note it appears that a SSID must be set.
+.El
+.It Fl h
+Print a list of available options and sample usage.
+.El
+.Sh SECURITY NOTES
+WEP
+.Pq Dq "wired equivalent privacy"
+is based on the RC4 algorithm,
+using a 24 bit initialization vector.
+.Pp
+RC4 is supposedly vulnerable to certain known plaintext attacks,
+especially with 40 bit keys.
+So the security of WEP in part depends on how much known plaintext
+is transmitted.
+.Pp
+Because of this, although counter-intuitive, using
+.Dq "shared key"
+authentication (which involves sending known plaintext) is less
+secure than using
+.Dq open
+authentication when WEP is enabled.
+.Pp
+Devices may alternate among all of the configured WEP keys when
+transmitting packets.
+Therefore, all configured keys (up to four) must agree.
+.Sh EXAMPLES
+.Bd -literal -offset indent
+ancontrol -i an0 -v 0 -k 0x12345678901234567890123456
+ancontrol -i an0 -K 2
+ancontrol -i an0 -W 1
+ancontrol -i an0 -e 0
+.Ed
+.Pp
+Sets a WEP key 0, enables
+.Dq "Shared Key"
+authentication, enables full WEP
+and uses transmit key 0.
+.Sh SEE ALSO
+.Xr an 4 ,
+.Xr ifconfig 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 4.0 .
+.Sh AUTHORS
+The
+.Nm
+utility was written by
+.An Bill Paul Aq Mt wpaul@ee.columbia.edu .
+.Sh BUGS
+The statistics counters do not seem to show the amount of transmit
+and received frames as increasing.
+This is likely due to the fact that
+the
+.Xr an 4
+driver uses unmodified packet mode instead of letting the NIC perform
+802.11/ethernet encapsulation itself.
+.Pp
+Setting the channel does not seem to have any effect.
diff --git a/usr.sbin/ancontrol/ancontrol.c b/usr.sbin/ancontrol/ancontrol.c
new file mode 100644
index 0000000..4ff32ff
--- /dev/null
+++ b/usr.sbin/ancontrol/ancontrol.c
@@ -0,0 +1,1779 @@
+/*
+ * Copyright 1997, 1998, 1999
+ * Bill Paul <wpaul@ee.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.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] = "@(#) Copyright (c) 1997, 1998, 1999\
+ Bill Paul. All rights reserved.";
+#endif
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+
+#include <arpa/inet.h>
+
+#include <net/if.h>
+#include <net/ethernet.h>
+
+#include <dev/an/if_aironet_ieee.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <err.h>
+#include <md4.h>
+#include <ctype.h>
+
+static int an_getval(const char *, struct an_req *);
+static void an_setval(const char *, struct an_req *);
+static void an_printwords(const u_int16_t *, int);
+static void an_printspeeds(const u_int8_t *, int);
+static void an_printbool(int);
+static void an_printhex(const char *, int);
+static void an_printstr(char *, int);
+static void an_dumpstatus(const char *);
+static void an_dumpstats(const char *);
+static void an_dumpconfig(const char *);
+static void an_dumpcaps(const char *);
+static void an_dumpssid(const char *);
+static void an_dumpap(const char *);
+static void an_setconfig(const char *, int, void *);
+static void an_setssid(const char *, int, void *);
+static void an_setap(const char *, int, void *);
+static void an_setspeed(const char *, int, void *);
+static void an_readkeyinfo(const char *);
+#ifdef ANCACHE
+static void an_zerocache(const char *);
+static void an_readcache(const char *);
+#endif
+static int an_hex2int(char);
+static void an_str2key(const char *, struct an_ltv_key *);
+static void an_setkeys(const char *, const char *, int);
+static void an_enable_tx_key(const char *, const char *);
+static void an_enable_leap_mode(const char *, const char *);
+static void an_dumprssimap(const char *);
+static void usage(const char *);
+
+#define ACT_DUMPSTATS 1
+#define ACT_DUMPCONFIG 2
+#define ACT_DUMPSTATUS 3
+#define ACT_DUMPCAPS 4
+#define ACT_DUMPSSID 5
+#define ACT_DUMPAP 6
+
+#define ACT_SET_OPMODE 7
+#define ACT_SET_SSID 8
+#define ACT_SET_FREQ 11
+#define ACT_SET_AP1 12
+#define ACT_SET_AP2 13
+#define ACT_SET_AP3 14
+#define ACT_SET_AP4 15
+#define ACT_SET_DRIVERNAME 16
+#define ACT_SET_SCANMODE 17
+#define ACT_SET_TXRATE 18
+#define ACT_SET_RTS_THRESH 19
+#define ACT_SET_PWRSAVE 20
+#define ACT_SET_DIVERSITY_RX 21
+#define ACT_SET_DIVERSITY_TX 22
+#define ACT_SET_RTS_RETRYLIM 23
+#define ACT_SET_WAKE_DURATION 24
+#define ACT_SET_BEACON_PERIOD 25
+#define ACT_SET_TXPWR 26
+#define ACT_SET_FRAG_THRESH 27
+#define ACT_SET_NETJOIN 28
+#define ACT_SET_MYNAME 29
+#define ACT_SET_MAC 30
+
+#define ACT_DUMPCACHE 31
+#define ACT_ZEROCACHE 32
+
+#define ACT_ENABLE_WEP 33
+#define ACT_SET_KEY_TYPE 34
+#define ACT_SET_KEYS 35
+#define ACT_ENABLE_TX_KEY 36
+#define ACT_SET_MONITOR_MODE 37
+#define ACT_SET_LEAP_MODE 38
+
+#define ACT_DUMPRSSIMAP 39
+
+static int
+an_getval(const char *iface, struct an_req *areq)
+{
+ struct ifreq ifr;
+ int s, okay = 1;
+
+ bzero(&ifr, sizeof(ifr));
+
+ strlcpy(ifr.ifr_name, iface, sizeof(ifr.ifr_name));
+ ifr.ifr_data = (caddr_t)areq;
+
+ s = socket(AF_INET, SOCK_DGRAM, 0);
+
+ if (s == -1)
+ err(1, "socket");
+
+ if (ioctl(s, SIOCGAIRONET, &ifr) == -1) {
+ okay = 0;
+ err(1, "SIOCGAIRONET");
+ }
+
+ close(s);
+
+ return (okay);
+}
+
+static void
+an_setval(const char *iface, struct an_req *areq)
+{
+ struct ifreq ifr;
+ int s;
+
+ bzero(&ifr, sizeof(ifr));
+
+ strlcpy(ifr.ifr_name, iface, sizeof(ifr.ifr_name));
+ ifr.ifr_data = (caddr_t)areq;
+
+ s = socket(AF_INET, SOCK_DGRAM, 0);
+
+ if (s == -1)
+ err(1, "socket");
+
+ if (ioctl(s, SIOCSAIRONET, &ifr) == -1)
+ err(1, "SIOCSAIRONET");
+
+ close(s);
+
+ return;
+}
+
+static void
+an_printstr(char *str, int len)
+{
+ int i;
+
+ for (i = 0; i < len - 1; i++) {
+ if (str[i] == '\0')
+ str[i] = ' ';
+ }
+
+ printf("[ %.*s ]", len, str);
+}
+
+static void
+an_printwords(const u_int16_t *w, int len)
+{
+ int i;
+
+ printf("[ ");
+ for (i = 0; i < len; i++)
+ printf("%u ", w[i]);
+ printf("]");
+}
+
+static void
+an_printspeeds(const u_int8_t *w, int len)
+{
+ int i;
+
+ printf("[ ");
+ for (i = 0; i < len && w[i]; i++)
+ printf("%2.1fMbps ", w[i] * 0.500);
+ printf("]");
+}
+
+static void
+an_printbool(int val)
+{
+ if (val)
+ printf("[ On ]");
+ else
+ printf("[ Off ]");
+}
+
+static void
+an_printhex(const char *ptr, int len)
+{
+ int i;
+
+ printf("[ ");
+ for (i = 0; i < len; i++) {
+ printf("%02x", ptr[i] & 0xFF);
+ if (i < (len - 1))
+ printf(":");
+ }
+
+ printf(" ]");
+}
+
+
+
+static void
+an_dumpstatus(const char *iface)
+{
+ struct an_ltv_status *sts;
+ struct an_req areq;
+ struct an_ltv_rssi_map an_rssimap;
+ int rssimap_valid = 0;
+
+ /*
+ * Try to get RSSI to percent and dBM table
+ */
+
+ an_rssimap.an_len = sizeof(an_rssimap);
+ an_rssimap.an_type = AN_RID_RSSI_MAP;
+ rssimap_valid = an_getval(iface, (struct an_req*)&an_rssimap);
+
+ if (rssimap_valid)
+ printf("RSSI table:\t\t[ present ]\n");
+ else
+ printf("RSSI table:\t\t[ not available ]\n");
+
+ areq.an_len = sizeof(areq);
+ areq.an_type = AN_RID_STATUS;
+
+ an_getval(iface, &areq);
+
+ sts = (struct an_ltv_status *)&areq;
+
+ printf("MAC address:\t\t");
+ an_printhex((char *)&sts->an_macaddr, ETHER_ADDR_LEN);
+ printf("\nOperating mode:\t\t[ ");
+ if (sts->an_opmode & AN_STATUS_OPMODE_CONFIGURED)
+ printf("configured ");
+ if (sts->an_opmode & AN_STATUS_OPMODE_MAC_ENABLED)
+ printf("MAC ON ");
+ if (sts->an_opmode & AN_STATUS_OPMODE_RX_ENABLED)
+ printf("RX ON ");
+ if (sts->an_opmode & AN_STATUS_OPMODE_IN_SYNC)
+ printf("synced ");
+ if (sts->an_opmode & AN_STATUS_OPMODE_ASSOCIATED)
+ printf("associated ");
+ if (sts->an_opmode & AN_STATUS_OPMODE_LEAP)
+ printf("LEAP ");
+ if (sts->an_opmode & AN_STATUS_OPMODE_ERROR)
+ printf("error ");
+ printf("]\n");
+ printf("Error code:\t\t");
+ an_printhex((char *)&sts->an_errcode, 1);
+ if (rssimap_valid)
+ printf("\nSignal strength:\t[ %u%% ]",
+ an_rssimap.an_entries[
+ sts->an_normalized_strength].an_rss_pct);
+ else
+ printf("\nSignal strength:\t[ %u%% ]",
+ sts->an_normalized_strength);
+ printf("\nAverage Noise:\t\t[ %u%% ]", sts->an_avg_noise_prev_min_pc);
+ if (rssimap_valid)
+ printf("\nSignal quality:\t\t[ %u%% ]",
+ an_rssimap.an_entries[
+ sts->an_cur_signal_quality].an_rss_pct);
+ else
+ printf("\nSignal quality:\t\t[ %u ]",
+ sts->an_cur_signal_quality);
+ printf("\nMax Noise:\t\t[ %u%% ]", sts->an_max_noise_prev_min_pc);
+ /*
+ * XXX: This uses the old definition of the rate field (units of
+ * 500kbps). Technically the new definition is that this field
+ * contains arbitrary values, but no devices which need this
+ * support exist and the IEEE seems to intend to use the old
+ * definition until they get something big so we'll keep using
+ * it as well because this will work with new cards with
+ * rate <= 63.5Mbps.
+ */
+ printf("\nCurrent TX rate:\t[ %u%s ]", sts->an_current_tx_rate / 2,
+ (sts->an_current_tx_rate % 2) ? ".5" : "");
+ printf("\nCurrent SSID:\t\t");
+ an_printstr((char *)&sts->an_ssid, sts->an_ssidlen);
+ printf("\nCurrent AP name:\t");
+ an_printstr((char *)&sts->an_ap_name, 16);
+ printf("\nCurrent BSSID:\t\t");
+ an_printhex((char *)&sts->an_cur_bssid, ETHER_ADDR_LEN);
+ printf("\nBeacon period:\t\t");
+ an_printwords(&sts->an_beacon_period, 1);
+ printf("\nDTIM period:\t\t");
+ an_printwords(&sts->an_dtim_period, 1);
+ printf("\nATIM duration:\t\t");
+ an_printwords(&sts->an_atim_duration, 1);
+ printf("\nHOP period:\t\t");
+ an_printwords(&sts->an_hop_period, 1);
+ printf("\nChannel set:\t\t");
+ an_printwords(&sts->an_channel_set, 1);
+ printf("\nCurrent channel:\t");
+ an_printwords(&sts->an_cur_channel, 1);
+ printf("\nHops to backbone:\t");
+ an_printwords(&sts->an_hops_to_backbone, 1);
+ printf("\nTotal AP load:\t\t");
+ an_printwords(&sts->an_ap_total_load, 1);
+ printf("\nOur generated load:\t");
+ an_printwords(&sts->an_our_generated_load, 1);
+ printf("\nAccumulated ARL:\t");
+ an_printwords(&sts->an_accumulated_arl, 1);
+ printf("\n");
+ return;
+}
+
+static void
+an_dumpcaps(const char *iface)
+{
+ struct an_ltv_caps *caps;
+ struct an_req areq;
+ u_int16_t tmp;
+
+ areq.an_len = sizeof(areq);
+ areq.an_type = AN_RID_CAPABILITIES;
+
+ an_getval(iface, &areq);
+
+ caps = (struct an_ltv_caps *)&areq;
+
+ printf("OUI:\t\t\t");
+ an_printhex((char *)&caps->an_oui, 3);
+ printf("\nProduct number:\t\t");
+ an_printwords(&caps->an_prodnum, 1);
+ printf("\nManufacturer name:\t");
+ an_printstr((char *)&caps->an_manufname, 32);
+ printf("\nProduce name:\t\t");
+ an_printstr((char *)&caps->an_prodname, 16);
+ printf("\nFirmware version:\t");
+ an_printstr((char *)&caps->an_prodvers, 1);
+ printf("\nOEM MAC address:\t");
+ an_printhex((char *)&caps->an_oemaddr, ETHER_ADDR_LEN);
+ printf("\nAironet MAC address:\t");
+ an_printhex((char *)&caps->an_aironetaddr, ETHER_ADDR_LEN);
+ printf("\nRadio type:\t\t[ ");
+ if (caps->an_radiotype & AN_RADIOTYPE_80211_FH)
+ printf("802.11 FH");
+ else if (caps->an_radiotype & AN_RADIOTYPE_80211_DS)
+ printf("802.11 DS");
+ else if (caps->an_radiotype & AN_RADIOTYPE_LM2000_DS)
+ printf("LM2000 DS");
+ else
+ printf("unknown (%x)", caps->an_radiotype);
+ printf(" ]");
+ printf("\nRegulatory domain:\t");
+ an_printwords(&caps->an_regdomain, 1);
+ printf("\nAssigned CallID:\t");
+ an_printhex((char *)&caps->an_callid, 6);
+ printf("\nSupported speeds:\t");
+ an_printspeeds(caps->an_rates, 8);
+ printf("\nRX Diversity:\t\t[ ");
+ if (caps->an_rx_diversity == AN_DIVERSITY_FACTORY_DEFAULT)
+ printf("factory default");
+ else if (caps->an_rx_diversity == AN_DIVERSITY_ANTENNA_1_ONLY)
+ printf("antenna 1 only");
+ else if (caps->an_rx_diversity == AN_DIVERSITY_ANTENNA_2_ONLY)
+ printf("antenna 2 only");
+ else if (caps->an_rx_diversity == AN_DIVERSITY_ANTENNA_1_AND_2)
+ printf("antenna 1 and 2");
+ printf(" ]");
+ printf("\nTX Diversity:\t\t[ ");
+ if (caps->an_tx_diversity == AN_DIVERSITY_FACTORY_DEFAULT)
+ printf("factory default");
+ else if (caps->an_tx_diversity == AN_DIVERSITY_ANTENNA_1_ONLY)
+ printf("antenna 1 only");
+ else if (caps->an_tx_diversity == AN_DIVERSITY_ANTENNA_2_ONLY)
+ printf("antenna 2 only");
+ else if (caps->an_tx_diversity == AN_DIVERSITY_ANTENNA_1_AND_2)
+ printf("antenna 1 and 2");
+ printf(" ]");
+ printf("\nSupported power levels:\t");
+ an_printwords(caps->an_tx_powerlevels, 8);
+ printf("\nHardware revision:\t");
+ tmp = ntohs(caps->an_hwrev);
+ an_printhex((char *)&tmp, 2);
+ printf("\nSoftware revision:\t");
+ tmp = ntohs(caps->an_fwrev);
+ an_printhex((char *)&tmp, 2);
+ printf("\nSoftware subrevision:\t");
+ tmp = ntohs(caps->an_fwsubrev);
+ an_printhex((char *)&tmp, 2);
+ printf("\nInterface revision:\t");
+ tmp = ntohs(caps->an_ifacerev);
+ an_printhex((char *)&tmp, 2);
+ printf("\nBootblock revision:\t");
+ tmp = ntohs(caps->an_bootblockrev);
+ an_printhex((char *)&tmp, 2);
+ printf("\n");
+ return;
+}
+
+static void
+an_dumpstats(const char *iface)
+{
+ struct an_ltv_stats *stats;
+ struct an_req areq;
+
+ areq.an_len = sizeof(areq);
+ areq.an_type = AN_RID_32BITS_CUM;
+
+ an_getval(iface, &areq);
+
+ stats = (struct an_ltv_stats *)((uint16_t *)&areq - 1);
+
+ printf("RX overruns:\t\t\t\t\t[ %u ]\n", stats->an_rx_overruns);
+ printf("RX PLCP CSUM errors:\t\t\t\t[ %u ]\n",
+ stats->an_rx_plcp_csum_errs);
+ printf("RX PLCP format errors:\t\t\t\t[ %u ]\n",
+ stats->an_rx_plcp_format_errs);
+ printf("RX PLCP length errors:\t\t\t\t[ %u ]\n",
+ stats->an_rx_plcp_len_errs);
+ printf("RX MAC CRC errors:\t\t\t\t[ %u ]\n",
+ stats->an_rx_mac_crc_errs);
+ printf("RX MAC CRC OK:\t\t\t\t\t[ %u ]\n",
+ stats->an_rx_mac_crc_ok);
+ printf("RX WEP errors:\t\t\t\t\t[ %u ]\n",
+ stats->an_rx_wep_errs);
+ printf("RX WEP OK:\t\t\t\t\t[ %u ]\n",
+ stats->an_rx_wep_ok);
+ printf("Long retries:\t\t\t\t\t[ %u ]\n",
+ stats->an_retry_long);
+ printf("Short retries:\t\t\t\t\t[ %u ]\n",
+ stats->an_retry_short);
+ printf("Retries exhausted:\t\t\t\t[ %u ]\n",
+ stats->an_retry_max);
+ printf("Bad ACK:\t\t\t\t\t[ %u ]\n",
+ stats->an_no_ack);
+ printf("Bad CTS:\t\t\t\t\t[ %u ]\n",
+ stats->an_no_cts);
+ printf("RX good ACKs:\t\t\t\t\t[ %u ]\n",
+ stats->an_rx_ack_ok);
+ printf("RX good CTSs:\t\t\t\t\t[ %u ]\n",
+ stats->an_rx_cts_ok);
+ printf("TX good ACKs:\t\t\t\t\t[ %u ]\n",
+ stats->an_tx_ack_ok);
+ printf("TX good RTSs:\t\t\t\t\t[ %u ]\n",
+ stats->an_tx_rts_ok);
+ printf("TX good CTSs:\t\t\t\t\t[ %u ]\n",
+ stats->an_tx_cts_ok);
+ printf("LMAC multicasts transmitted:\t\t\t[ %u ]\n",
+ stats->an_tx_lmac_mcasts);
+ printf("LMAC broadcasts transmitted:\t\t\t[ %u ]\n",
+ stats->an_tx_lmac_bcasts);
+ printf("LMAC unicast frags transmitted:\t\t\t[ %u ]\n",
+ stats->an_tx_lmac_ucast_frags);
+ printf("LMAC unicasts transmitted:\t\t\t[ %u ]\n",
+ stats->an_tx_lmac_ucasts);
+ printf("Beacons transmitted:\t\t\t\t[ %u ]\n",
+ stats->an_tx_beacons);
+ printf("Beacons received:\t\t\t\t[ %u ]\n",
+ stats->an_rx_beacons);
+ printf("Single transmit collisions:\t\t\t[ %u ]\n",
+ stats->an_tx_single_cols);
+ printf("Multiple transmit collisions:\t\t\t[ %u ]\n",
+ stats->an_tx_multi_cols);
+ printf("Transmits without deferrals:\t\t\t[ %u ]\n",
+ stats->an_tx_defers_no);
+ printf("Transmits deferred due to protocol:\t\t[ %u ]\n",
+ stats->an_tx_defers_prot);
+ printf("Transmits deferred due to energy detect:\t\t[ %u ]\n",
+ stats->an_tx_defers_energy);
+ printf("RX duplicate frames/frags:\t\t\t[ %u ]\n",
+ stats->an_rx_dups);
+ printf("RX partial frames:\t\t\t\t[ %u ]\n",
+ stats->an_rx_partial);
+ printf("TX max lifetime exceeded:\t\t\t[ %u ]\n",
+ stats->an_tx_too_old);
+ printf("RX max lifetime exceeded:\t\t\t[ %u ]\n",
+ stats->an_tx_too_old);
+ printf("Sync lost due to too many missed beacons:\t[ %u ]\n",
+ stats->an_lostsync_missed_beacons);
+ printf("Sync lost due to ARL exceeded:\t\t\t[ %u ]\n",
+ stats->an_lostsync_arl_exceeded);
+ printf("Sync lost due to deauthentication:\t\t[ %u ]\n",
+ stats->an_lostsync_deauthed);
+ printf("Sync lost due to disassociation:\t\t[ %u ]\n",
+ stats->an_lostsync_disassociated);
+ printf("Sync lost due to excess change in TSF timing:\t[ %u ]\n",
+ stats->an_lostsync_tsf_timing);
+ printf("Host transmitted multicasts:\t\t\t[ %u ]\n",
+ stats->an_tx_host_mcasts);
+ printf("Host transmitted broadcasts:\t\t\t[ %u ]\n",
+ stats->an_tx_host_bcasts);
+ printf("Host transmitted unicasts:\t\t\t[ %u ]\n",
+ stats->an_tx_host_ucasts);
+ printf("Host transmission failures:\t\t\t[ %u ]\n",
+ stats->an_tx_host_failed);
+ printf("Host received multicasts:\t\t\t[ %u ]\n",
+ stats->an_rx_host_mcasts);
+ printf("Host received broadcasts:\t\t\t[ %u ]\n",
+ stats->an_rx_host_bcasts);
+ printf("Host received unicasts:\t\t\t\t[ %u ]\n",
+ stats->an_rx_host_ucasts);
+ printf("Host receive discards:\t\t\t\t[ %u ]\n",
+ stats->an_rx_host_discarded);
+ printf("HMAC transmitted multicasts:\t\t\t[ %u ]\n",
+ stats->an_tx_hmac_mcasts);
+ printf("HMAC transmitted broadcasts:\t\t\t[ %u ]\n",
+ stats->an_tx_hmac_bcasts);
+ printf("HMAC transmitted unicasts:\t\t\t[ %u ]\n",
+ stats->an_tx_hmac_ucasts);
+ printf("HMAC transmissions failed:\t\t\t[ %u ]\n",
+ stats->an_tx_hmac_failed);
+ printf("HMAC received multicasts:\t\t\t[ %u ]\n",
+ stats->an_rx_hmac_mcasts);
+ printf("HMAC received broadcasts:\t\t\t[ %u ]\n",
+ stats->an_rx_hmac_bcasts);
+ printf("HMAC received unicasts:\t\t\t\t[ %u ]\n",
+ stats->an_rx_hmac_ucasts);
+ printf("HMAC receive discards:\t\t\t\t[ %u ]\n",
+ stats->an_rx_hmac_discarded);
+ printf("HMAC transmits accepted:\t\t\t[ %u ]\n",
+ stats->an_tx_hmac_accepted);
+ printf("SSID mismatches:\t\t\t\t[ %u ]\n",
+ stats->an_ssid_mismatches);
+ printf("Access point mismatches:\t\t\t[ %u ]\n",
+ stats->an_ap_mismatches);
+ printf("Speed mismatches:\t\t\t\t[ %u ]\n",
+ stats->an_rates_mismatches);
+ printf("Authentication rejects:\t\t\t\t[ %u ]\n",
+ stats->an_auth_rejects);
+ printf("Authentication timeouts:\t\t\t[ %u ]\n",
+ stats->an_auth_timeouts);
+ printf("Association rejects:\t\t\t\t[ %u ]\n",
+ stats->an_assoc_rejects);
+ printf("Association timeouts:\t\t\t\t[ %u ]\n",
+ stats->an_assoc_timeouts);
+ printf("Management frames received:\t\t\t[ %u ]\n",
+ stats->an_rx_mgmt_pkts);
+ printf("Management frames transmitted:\t\t\t[ %u ]\n",
+ stats->an_tx_mgmt_pkts);
+ printf("Refresh frames received:\t\t\t[ %u ]\n",
+ stats->an_rx_refresh_pkts),
+ printf("Refresh frames transmitted:\t\t\t[ %u ]\n",
+ stats->an_tx_refresh_pkts),
+ printf("Poll frames received:\t\t\t\t[ %u ]\n",
+ stats->an_rx_poll_pkts);
+ printf("Poll frames transmitted:\t\t\t[ %u ]\n",
+ stats->an_tx_poll_pkts);
+ printf("Host requested sync losses:\t\t\t[ %u ]\n",
+ stats->an_lostsync_hostreq);
+ printf("Host transmitted bytes:\t\t\t\t[ %u ]\n",
+ stats->an_host_tx_bytes);
+ printf("Host received bytes:\t\t\t\t[ %u ]\n",
+ stats->an_host_rx_bytes);
+ printf("Uptime in microseconds:\t\t\t\t[ %u ]\n",
+ stats->an_uptime_usecs);
+ printf("Uptime in seconds:\t\t\t\t[ %u ]\n",
+ stats->an_uptime_secs);
+ printf("Sync lost due to better AP:\t\t\t[ %u ]\n",
+ stats->an_lostsync_better_ap);
+}
+
+static void
+an_dumpap(const char *iface)
+{
+ struct an_ltv_aplist *ap;
+ struct an_req areq;
+
+ areq.an_len = sizeof(areq);
+ areq.an_type = AN_RID_APLIST;
+
+ an_getval(iface, &areq);
+
+ ap = (struct an_ltv_aplist *)&areq;
+ printf("Access point 1:\t\t\t");
+ an_printhex((char *)&ap->an_ap1, ETHER_ADDR_LEN);
+ printf("\nAccess point 2:\t\t\t");
+ an_printhex((char *)&ap->an_ap2, ETHER_ADDR_LEN);
+ printf("\nAccess point 3:\t\t\t");
+ an_printhex((char *)&ap->an_ap3, ETHER_ADDR_LEN);
+ printf("\nAccess point 4:\t\t\t");
+ an_printhex((char *)&ap->an_ap4, ETHER_ADDR_LEN);
+ printf("\n");
+
+ return;
+}
+
+static void
+an_dumpssid(const char *iface)
+{
+ struct an_ltv_ssidlist_new *ssid;
+ struct an_req areq;
+ int i, max;
+
+ areq.an_len = sizeof(areq);
+ areq.an_type = AN_RID_SSIDLIST;
+
+ an_getval(iface, &areq);
+
+ max = (areq.an_len - 4) / sizeof(struct an_ltv_ssid_entry);
+ if ( max > MAX_SSIDS ) {
+ printf("Too many SSIDs only printing %d of %d\n",
+ MAX_SSIDS, max);
+ max = MAX_SSIDS;
+ }
+ ssid = (struct an_ltv_ssidlist_new *)&areq;
+ for (i = 0; i < max; i++)
+ printf("SSID %2d:\t\t\t[ %.*s ]\n", i + 1,
+ ssid->an_entry[i].an_len,
+ ssid->an_entry[i].an_ssid);
+
+ return;
+}
+
+static void
+an_dumpconfig(const char *iface)
+{
+ struct an_ltv_genconfig *cfg;
+ struct an_req areq;
+ unsigned char diversity;
+
+ areq.an_len = sizeof(areq);
+ areq.an_type = AN_RID_ACTUALCFG;
+
+ an_getval(iface, &areq);
+
+ cfg = (struct an_ltv_genconfig *)&areq;
+
+ printf("Operating mode:\t\t\t\t[ ");
+ if ((cfg->an_opmode & 0x7) == AN_OPMODE_IBSS_ADHOC)
+ printf("ad-hoc");
+ if ((cfg->an_opmode & 0x7) == AN_OPMODE_INFRASTRUCTURE_STATION)
+ printf("infrastructure");
+ if ((cfg->an_opmode & 0x7) == AN_OPMODE_AP)
+ printf("access point");
+ if ((cfg->an_opmode & 0x7) == AN_OPMODE_AP_REPEATER)
+ printf("access point repeater");
+ printf(" ]");
+ printf("\nReceive mode:\t\t\t\t[ ");
+ if ((cfg->an_rxmode & 0x7) == AN_RXMODE_BC_MC_ADDR)
+ printf("broadcast/multicast/unicast");
+ if ((cfg->an_rxmode & 0x7) == AN_RXMODE_BC_ADDR)
+ printf("broadcast/unicast");
+ if ((cfg->an_rxmode & 0x7) == AN_RXMODE_ADDR)
+ printf("unicast");
+ if ((cfg->an_rxmode & 0x7) == AN_RXMODE_80211_MONITOR_CURBSS)
+ printf("802.11 monitor, current BSSID");
+ if ((cfg->an_rxmode & 0x7) == AN_RXMODE_80211_MONITOR_ANYBSS)
+ printf("802.11 monitor, any BSSID");
+ if ((cfg->an_rxmode & 0x7) == AN_RXMODE_LAN_MONITOR_CURBSS)
+ printf("LAN monitor, current BSSID");
+ printf(" ]");
+ printf("\nFragment threshold:\t\t\t");
+ an_printwords(&cfg->an_fragthresh, 1);
+ printf("\nRTS threshold:\t\t\t\t");
+ an_printwords(&cfg->an_rtsthresh, 1);
+ printf("\nMAC address:\t\t\t\t");
+ an_printhex((char *)&cfg->an_macaddr, ETHER_ADDR_LEN);
+ printf("\nSupported rates:\t\t\t");
+ an_printspeeds(cfg->an_rates, 8);
+ printf("\nShort retry limit:\t\t\t");
+ an_printwords(&cfg->an_shortretry_limit, 1);
+ printf("\nLong retry limit:\t\t\t");
+ an_printwords(&cfg->an_longretry_limit, 1);
+ printf("\nTX MSDU lifetime:\t\t\t");
+ an_printwords(&cfg->an_tx_msdu_lifetime, 1);
+ printf("\nRX MSDU lifetime:\t\t\t");
+ an_printwords(&cfg->an_rx_msdu_lifetime, 1);
+ printf("\nStationary:\t\t\t\t");
+ an_printbool(cfg->an_stationary);
+ printf("\nOrdering:\t\t\t\t");
+ an_printbool(cfg->an_ordering);
+ printf("\nDevice type:\t\t\t\t[ ");
+ if (cfg->an_devtype == AN_DEVTYPE_PC4500)
+ printf("PC4500");
+ else if (cfg->an_devtype == AN_DEVTYPE_PC4800)
+ printf("PC4800");
+ else
+ printf("unknown (%x)", cfg->an_devtype);
+ printf(" ]");
+ printf("\nScanning mode:\t\t\t\t[ ");
+ if (cfg->an_scanmode == AN_SCANMODE_ACTIVE)
+ printf("active");
+ if (cfg->an_scanmode == AN_SCANMODE_PASSIVE)
+ printf("passive");
+ if (cfg->an_scanmode == AN_SCANMODE_AIRONET_ACTIVE)
+ printf("Aironet active");
+ printf(" ]");
+ printf("\nProbe delay:\t\t\t\t");
+ an_printwords(&cfg->an_probedelay, 1);
+ printf("\nProbe energy timeout:\t\t\t");
+ an_printwords(&cfg->an_probe_energy_timeout, 1);
+ printf("\nProbe response timeout:\t\t\t");
+ an_printwords(&cfg->an_probe_response_timeout, 1);
+ printf("\nBeacon listen timeout:\t\t\t");
+ an_printwords(&cfg->an_beacon_listen_timeout, 1);
+ printf("\nIBSS join network timeout:\t\t");
+ an_printwords(&cfg->an_ibss_join_net_timeout, 1);
+ printf("\nAuthentication timeout:\t\t\t");
+ an_printwords(&cfg->an_auth_timeout, 1);
+ printf("\nWEP enabled:\t\t\t\t[ ");
+ if (cfg->an_authtype & AN_AUTHTYPE_PRIVACY_IN_USE)
+ {
+ if (cfg->an_authtype & AN_AUTHTYPE_LEAP)
+ printf("LEAP");
+ else if (cfg->an_authtype & AN_AUTHTYPE_ALLOW_UNENCRYPTED)
+ printf("mixed cell");
+ else
+ printf("full");
+ }
+ else
+ printf("no");
+ printf(" ]");
+ printf("\nAuthentication type:\t\t\t[ ");
+ if ((cfg->an_authtype & AN_AUTHTYPE_MASK) == AN_AUTHTYPE_NONE)
+ printf("none");
+ if ((cfg->an_authtype & AN_AUTHTYPE_MASK) == AN_AUTHTYPE_OPEN)
+ printf("open");
+ if ((cfg->an_authtype & AN_AUTHTYPE_MASK) == AN_AUTHTYPE_SHAREDKEY)
+ printf("shared key");
+ printf(" ]");
+ printf("\nAssociation timeout:\t\t\t");
+ an_printwords(&cfg->an_assoc_timeout, 1);
+ printf("\nSpecified AP association timeout:\t");
+ an_printwords(&cfg->an_specified_ap_timeout, 1);
+ printf("\nOffline scan interval:\t\t\t");
+ an_printwords(&cfg->an_offline_scan_interval, 1);
+ printf("\nOffline scan duration:\t\t\t");
+ an_printwords(&cfg->an_offline_scan_duration, 1);
+ printf("\nLink loss delay:\t\t\t");
+ an_printwords(&cfg->an_link_loss_delay, 1);
+ printf("\nMax beacon loss time:\t\t\t");
+ an_printwords(&cfg->an_max_beacon_lost_time, 1);
+ printf("\nRefresh interval:\t\t\t");
+ an_printwords(&cfg->an_refresh_interval, 1);
+ printf("\nPower save mode:\t\t\t[ ");
+ if (cfg->an_psave_mode == AN_PSAVE_NONE)
+ printf("none");
+ if (cfg->an_psave_mode == AN_PSAVE_CAM)
+ printf("constantly awake mode");
+ if (cfg->an_psave_mode == AN_PSAVE_PSP)
+ printf("PSP");
+ if (cfg->an_psave_mode == AN_PSAVE_PSP_CAM)
+ printf("PSP-CAM (fast PSP)");
+ printf(" ]");
+ printf("\nSleep through DTIMs:\t\t\t");
+ an_printbool(cfg->an_sleep_for_dtims);
+ printf("\nPower save listen interval:\t\t");
+ an_printwords(&cfg->an_listen_interval, 1);
+ printf("\nPower save fast listen interval:\t");
+ an_printwords(&cfg->an_fast_listen_interval, 1);
+ printf("\nPower save listen decay:\t\t");
+ an_printwords(&cfg->an_listen_decay, 1);
+ printf("\nPower save fast listen decay:\t\t");
+ an_printwords(&cfg->an_fast_listen_decay, 1);
+ printf("\nAP/ad-hoc Beacon period:\t\t");
+ an_printwords(&cfg->an_beacon_period, 1);
+ printf("\nAP/ad-hoc ATIM duration:\t\t");
+ an_printwords(&cfg->an_atim_duration, 1);
+ printf("\nAP/ad-hoc current channel:\t\t");
+ an_printwords(&cfg->an_ds_channel, 1);
+ printf("\nAP/ad-hoc DTIM period:\t\t\t");
+ an_printwords(&cfg->an_dtim_period, 1);
+ printf("\nRadio type:\t\t\t\t[ ");
+ if (cfg->an_radiotype & AN_RADIOTYPE_80211_FH)
+ printf("802.11 FH");
+ else if (cfg->an_radiotype & AN_RADIOTYPE_80211_DS)
+ printf("802.11 DS");
+ else if (cfg->an_radiotype & AN_RADIOTYPE_LM2000_DS)
+ printf("LM2000 DS");
+ else
+ printf("unknown (%x)", cfg->an_radiotype);
+ printf(" ]");
+ printf("\nRX Diversity:\t\t\t\t[ ");
+ diversity = cfg->an_diversity & 0xFF;
+ if (diversity == AN_DIVERSITY_FACTORY_DEFAULT)
+ printf("factory default");
+ else if (diversity == AN_DIVERSITY_ANTENNA_1_ONLY)
+ printf("antenna 1 only");
+ else if (diversity == AN_DIVERSITY_ANTENNA_2_ONLY)
+ printf("antenna 2 only");
+ else if (diversity == AN_DIVERSITY_ANTENNA_1_AND_2)
+ printf("antenna 1 and 2");
+ printf(" ]");
+ printf("\nTX Diversity:\t\t\t\t[ ");
+ diversity = (cfg->an_diversity >> 8) & 0xFF;
+ if (diversity == AN_DIVERSITY_FACTORY_DEFAULT)
+ printf("factory default");
+ else if (diversity == AN_DIVERSITY_ANTENNA_1_ONLY)
+ printf("antenna 1 only");
+ else if (diversity == AN_DIVERSITY_ANTENNA_2_ONLY)
+ printf("antenna 2 only");
+ else if (diversity == AN_DIVERSITY_ANTENNA_1_AND_2)
+ printf("antenna 1 and 2");
+ printf(" ]");
+ printf("\nTransmit power level:\t\t\t");
+ an_printwords(&cfg->an_tx_power, 1);
+ printf("\nRSS threshold:\t\t\t\t");
+ an_printwords(&cfg->an_rss_thresh, 1);
+ printf("\nNode name:\t\t\t\t");
+ an_printstr((char *)&cfg->an_nodename, 16);
+ printf("\nARL threshold:\t\t\t\t");
+ an_printwords(&cfg->an_arl_thresh, 1);
+ printf("\nARL decay:\t\t\t\t");
+ an_printwords(&cfg->an_arl_decay, 1);
+ printf("\nARL delay:\t\t\t\t");
+ an_printwords(&cfg->an_arl_delay, 1);
+ printf("\nConfiguration:\t\t\t\t[ ");
+ if (cfg->an_home_product & AN_HOME_NETWORK)
+ printf("Home Configuration");
+ else
+ printf("Enterprise Configuration");
+ printf(" ]");
+
+ printf("\n");
+ printf("\n");
+ an_readkeyinfo(iface);
+}
+
+static void
+an_dumprssimap(const char *iface)
+{
+ struct an_ltv_rssi_map *rssi;
+ struct an_req areq;
+ int i;
+
+ areq.an_len = sizeof(areq);
+ areq.an_type = AN_RID_RSSI_MAP;
+
+ an_getval(iface, &areq);
+
+ rssi = (struct an_ltv_rssi_map *)&areq;
+
+ printf("idx\tpct\t dBm\n");
+
+ for (i = 0; i < 0xFF; i++) {
+ /*
+ * negate the dBm value: it's the only way the power
+ * level makes sense
+ */
+ printf("%3d\t%3d\t%4d\n", i,
+ rssi->an_entries[i].an_rss_pct,
+ - rssi->an_entries[i].an_rss_dbm);
+ }
+}
+
+static void
+usage(const char *p)
+{
+ fprintf(stderr, "usage: %s -i iface -A (show specified APs)\n", p);
+ fprintf(stderr, "\t%s -i iface -N (show specified SSIDss)\n", p);
+ fprintf(stderr, "\t%s -i iface -S (show NIC status)\n", p);
+ fprintf(stderr, "\t%s -i iface -I (show NIC capabilities)\n", p);
+ fprintf(stderr, "\t%s -i iface -T (show stats counters)\n", p);
+ fprintf(stderr, "\t%s -i iface -C (show current config)\n", p);
+ fprintf(stderr, "\t%s -i iface -R (show RSSI map)\n", p);
+ fprintf(stderr, "\t%s -i iface -t 0-4 (set TX speed)\n", p);
+ fprintf(stderr, "\t%s -i iface -s 0-3 (set power save mode)\n", p);
+ fprintf(stderr, "\t%s -i iface [-v 1-4] -a AP (specify AP)\n", p);
+ fprintf(stderr, "\t%s -i iface -b val (set beacon period)\n", p);
+ fprintf(stderr, "\t%s -i iface [-v 0|1] -d val (set diversity)\n", p);
+ fprintf(stderr, "\t%s -i iface -j val (set netjoin timeout)\n", p);
+ fprintf(stderr, "\t%s -i iface -e 0-4 (enable transmit key)\n", p);
+ fprintf(stderr, "\t%s -i iface [-v 0-8] -k key (set key)\n", p);
+ fprintf(stderr, "\t%s -i iface -K 0-2 (no auth/open/shared secret)\n", p);
+ fprintf(stderr, "\t%s -i iface -W 0-2 (no WEP/full WEP/mixed cell)\n", p);
+ fprintf(stderr, "\t%s -i iface -l val (set station name)\n", p);
+ fprintf(stderr, "\t%s -i iface -m val (set MAC address)\n", p);
+ fprintf(stderr, "\t%s -i iface [-v 1-3] -n SSID "
+ "(specify SSID)\n", p);
+ fprintf(stderr, "\t%s -i iface -o 0|1 (set operating mode)\n", p);
+ fprintf(stderr, "\t%s -i iface -c val (set ad-hoc channel)\n", p);
+ fprintf(stderr, "\t%s -i iface -f val (set frag threshold)\n", p);
+ fprintf(stderr, "\t%s -i iface -r val (set RTS threshold)\n", p);
+ fprintf(stderr, "\t%s -i iface -M 0-15 (set monitor mode)\n", p);
+ fprintf(stderr, "\t%s -i iface -L user (enter LEAP authentication mode)\n", p);
+#ifdef ANCACHE
+ fprintf(stderr, "\t%s -i iface -Q print signal quality cache\n", p);
+ fprintf(stderr, "\t%s -i iface -Z zero out signal cache\n", p);
+#endif
+
+ fprintf(stderr, "\t%s -h (display this message)\n", p);
+
+ exit(1);
+}
+
+static void
+an_setconfig(const char *iface, int act, void *arg)
+{
+ struct an_ltv_genconfig *cfg;
+ struct an_ltv_caps *caps;
+ struct an_req areq;
+ struct an_req areq_caps;
+ u_int16_t diversity = 0;
+ struct ether_addr *addr;
+ int i;
+
+ areq.an_len = sizeof(areq);
+ areq.an_type = AN_RID_GENCONFIG;
+ an_getval(iface, &areq);
+ cfg = (struct an_ltv_genconfig *)&areq;
+
+ areq_caps.an_len = sizeof(areq);
+ areq_caps.an_type = AN_RID_CAPABILITIES;
+ an_getval(iface, &areq_caps);
+ caps = (struct an_ltv_caps *)&areq_caps;
+
+ switch(act) {
+ case ACT_SET_OPMODE:
+ cfg->an_opmode = atoi(arg);
+ break;
+ case ACT_SET_FREQ:
+ cfg->an_ds_channel = atoi(arg);
+ break;
+ case ACT_SET_PWRSAVE:
+ cfg->an_psave_mode = atoi(arg);
+ break;
+ case ACT_SET_SCANMODE:
+ cfg->an_scanmode = atoi(arg);
+ break;
+ case ACT_SET_DIVERSITY_RX:
+ case ACT_SET_DIVERSITY_TX:
+ switch(atoi(arg)) {
+ case 0:
+ diversity = AN_DIVERSITY_FACTORY_DEFAULT;
+ break;
+ case 1:
+ diversity = AN_DIVERSITY_ANTENNA_1_ONLY;
+ break;
+ case 2:
+ diversity = AN_DIVERSITY_ANTENNA_2_ONLY;
+ break;
+ case 3:
+ diversity = AN_DIVERSITY_ANTENNA_1_AND_2;
+ break;
+ default:
+ errx(1, "bad diversity setting: %u", diversity);
+ break;
+ }
+ if (act == ACT_SET_DIVERSITY_RX) {
+ cfg->an_diversity &= 0xFF00;
+ cfg->an_diversity |= diversity;
+ } else {
+ cfg->an_diversity &= 0x00FF;
+ cfg->an_diversity |= (diversity << 8);
+ }
+ break;
+ case ACT_SET_TXPWR:
+ for (i = 0; i < 8; i++) {
+ if (caps->an_tx_powerlevels[i] == atoi(arg))
+ break;
+ }
+ if (i == 8)
+ errx(1, "unsupported power level: %dmW", atoi(arg));
+
+ cfg->an_tx_power = atoi(arg);
+ break;
+ case ACT_SET_RTS_THRESH:
+ cfg->an_rtsthresh = atoi(arg);
+ break;
+ case ACT_SET_RTS_RETRYLIM:
+ cfg->an_shortretry_limit =
+ cfg->an_longretry_limit = atoi(arg);
+ break;
+ case ACT_SET_BEACON_PERIOD:
+ cfg->an_beacon_period = atoi(arg);
+ break;
+ case ACT_SET_WAKE_DURATION:
+ cfg->an_atim_duration = atoi(arg);
+ break;
+ case ACT_SET_FRAG_THRESH:
+ cfg->an_fragthresh = atoi(arg);
+ break;
+ case ACT_SET_NETJOIN:
+ cfg->an_ibss_join_net_timeout = atoi(arg);
+ break;
+ case ACT_SET_MYNAME:
+ bzero(cfg->an_nodename, 16);
+ strncpy((char *)&cfg->an_nodename, optarg, 16);
+ break;
+ case ACT_SET_MAC:
+ addr = ether_aton((char *)arg);
+
+ if (addr == NULL)
+ errx(1, "badly formatted address");
+ bzero(cfg->an_macaddr, ETHER_ADDR_LEN);
+ bcopy(addr, &cfg->an_macaddr, ETHER_ADDR_LEN);
+ break;
+ case ACT_ENABLE_WEP:
+ switch (atoi (arg)) {
+ case 0:
+ /* no WEP */
+ cfg->an_authtype &= ~(AN_AUTHTYPE_PRIVACY_IN_USE
+ | AN_AUTHTYPE_ALLOW_UNENCRYPTED
+ | AN_AUTHTYPE_LEAP);
+ break;
+ case 1:
+ /* full WEP */
+ cfg->an_authtype |= AN_AUTHTYPE_PRIVACY_IN_USE;
+ cfg->an_authtype &= ~AN_AUTHTYPE_ALLOW_UNENCRYPTED;
+ cfg->an_authtype &= ~AN_AUTHTYPE_LEAP;
+ break;
+ case 2:
+ /* mixed cell */
+ cfg->an_authtype = AN_AUTHTYPE_PRIVACY_IN_USE
+ | AN_AUTHTYPE_ALLOW_UNENCRYPTED;
+ break;
+ }
+ break;
+ case ACT_SET_KEY_TYPE:
+ cfg->an_authtype = (cfg->an_authtype & ~AN_AUTHTYPE_MASK)
+ | atoi(arg);
+ break;
+ case ACT_SET_MONITOR_MODE:
+ areq.an_type = AN_RID_MONITOR_MODE;
+ cfg->an_len = atoi(arg); /* mode is put in length */
+ break;
+ default:
+ errx(1, "unknown action");
+ break;
+ }
+
+ an_setval(iface, &areq);
+ exit(0);
+}
+
+static void
+an_setspeed(const char *iface, int act __unused, void *arg)
+{
+ struct an_req areq;
+ struct an_ltv_caps *caps;
+ u_int16_t speed;
+
+ areq.an_len = sizeof(areq);
+ areq.an_type = AN_RID_CAPABILITIES;
+
+ an_getval(iface, &areq);
+ caps = (struct an_ltv_caps *)&areq;
+
+ switch(atoi(arg)) {
+ case 0:
+ speed = 0;
+ break;
+ case 1:
+ speed = AN_RATE_1MBPS;
+ break;
+ case 2:
+ speed = AN_RATE_2MBPS;
+ break;
+ case 3:
+ if (caps->an_rates[2] != AN_RATE_5_5MBPS)
+ errx(1, "5.5Mbps not supported on this card");
+ speed = AN_RATE_5_5MBPS;
+ break;
+ case 4:
+ if (caps->an_rates[3] != AN_RATE_11MBPS)
+ errx(1, "11Mbps not supported on this card");
+ speed = AN_RATE_11MBPS;
+ break;
+ default:
+ errx(1, "unsupported speed");
+ break;
+ }
+
+ areq.an_len = 6;
+ areq.an_type = AN_RID_TX_SPEED;
+ areq.an_val[0] = speed;
+
+ an_setval(iface, &areq);
+ exit(0);
+}
+
+static void
+an_setap(const char *iface, int act, void *arg)
+{
+ struct an_ltv_aplist *ap;
+ struct an_req areq;
+ struct ether_addr *addr;
+
+ areq.an_len = sizeof(areq);
+ areq.an_type = AN_RID_APLIST;
+
+ an_getval(iface, &areq);
+ ap = (struct an_ltv_aplist *)&areq;
+
+ addr = ether_aton((char *)arg);
+
+ if (addr == NULL)
+ errx(1, "badly formatted address");
+
+ switch(act) {
+ case ACT_SET_AP1:
+ bzero(ap->an_ap1, ETHER_ADDR_LEN);
+ bcopy(addr, &ap->an_ap1, ETHER_ADDR_LEN);
+ break;
+ case ACT_SET_AP2:
+ bzero(ap->an_ap2, ETHER_ADDR_LEN);
+ bcopy(addr, &ap->an_ap2, ETHER_ADDR_LEN);
+ break;
+ case ACT_SET_AP3:
+ bzero(ap->an_ap3, ETHER_ADDR_LEN);
+ bcopy(addr, &ap->an_ap3, ETHER_ADDR_LEN);
+ break;
+ case ACT_SET_AP4:
+ bzero(ap->an_ap4, ETHER_ADDR_LEN);
+ bcopy(addr, &ap->an_ap4, ETHER_ADDR_LEN);
+ break;
+ default:
+ errx(1, "unknown action");
+ break;
+ }
+
+ an_setval(iface, &areq);
+ exit(0);
+}
+
+static void
+an_setssid(const char *iface, int act, void *arg)
+{
+ struct an_ltv_ssidlist_new *ssid;
+ struct an_req areq;
+ int max;
+
+ areq.an_len = sizeof(areq);
+ areq.an_type = AN_RID_SSIDLIST;
+
+ an_getval(iface, &areq);
+ ssid = (struct an_ltv_ssidlist_new *)&areq;
+
+ max = (areq.an_len - 4) / sizeof(struct an_ltv_ssid_entry);
+ if ( max > MAX_SSIDS ) {
+ printf("Too many SSIDs only printing %d of %d\n",
+ MAX_SSIDS, max);
+ max = MAX_SSIDS;
+ }
+
+ if ( act > max ) {
+ errx(1, "bad modifier %d: there "
+ "are only %d SSID settings", act, max);
+ exit(1);
+ }
+
+ bzero(ssid->an_entry[act-1].an_ssid,
+ sizeof(ssid->an_entry[act-1].an_ssid));
+ strlcpy(ssid->an_entry[act-1].an_ssid, (char *)arg,
+ sizeof(ssid->an_entry[act-1].an_ssid));
+ ssid->an_entry[act-1].an_len
+ = strlen(ssid->an_entry[act-1].an_ssid);
+
+ an_setval(iface, &areq);
+
+ exit(0);
+}
+
+#ifdef ANCACHE
+static void
+an_zerocache(const char *iface)
+{
+ struct an_req areq;
+
+ bzero(&areq, sizeof(areq));
+ areq.an_len = 0;
+ areq.an_type = AN_RID_ZERO_CACHE;
+
+ an_getval(iface, &areq);
+}
+
+static void
+an_readcache(const char *iface)
+{
+ struct an_req areq;
+ uint16_t *an_sigitems;
+ struct an_sigcache *sc;
+ int i;
+
+ if (iface == NULL)
+ errx(1, "must specify interface name");
+
+ bzero(&areq, sizeof(areq));
+ areq.an_len = AN_MAX_DATALEN;
+ areq.an_type = AN_RID_READ_CACHE;
+
+ an_getval(iface, &areq);
+
+ an_sigitems = areq.an_val;
+ sc = (struct an_sigcache *)((int32_t *)areq.an_val + 1);
+
+ for (i = 0; i < *an_sigitems; i++) {
+ printf("[%d/%d]:", i+1, *an_sigitems);
+ printf(" %02x:%02x:%02x:%02x:%02x:%02x,",
+ sc->macsrc[0]&0xff,
+ sc->macsrc[1]&0xff,
+ sc->macsrc[2]&0xff,
+ sc->macsrc[3]&0xff,
+ sc->macsrc[4]&0xff,
+ sc->macsrc[5]&0xff);
+ printf(" %d.%d.%d.%d,",((sc->ipsrc >> 0) & 0xff),
+ ((sc->ipsrc >> 8) & 0xff),
+ ((sc->ipsrc >> 16) & 0xff),
+ ((sc->ipsrc >> 24) & 0xff));
+ printf(" sig: %d, noise: %d, qual: %d\n",
+ sc->signal,
+ sc->noise,
+ sc->quality);
+ sc++;
+ }
+}
+#endif
+
+static int
+an_hex2int(char c)
+{
+ if (c >= '0' && c <= '9')
+ return (c - '0');
+ if (c >= 'A' && c <= 'F')
+ return (c - 'A' + 10);
+ if (c >= 'a' && c <= 'f')
+ return (c - 'a' + 10);
+
+ return (0);
+}
+
+static void
+an_str2key(const char *s, struct an_ltv_key *k)
+{
+ int n, i;
+ char *p;
+
+ /* Is this a hex string? */
+ if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
+ /* Yes, convert to int. */
+ n = 0;
+ p = (char *)&k->key[0];
+ for (i = 2; s[i] != '\0' && s[i + 1] != '\0'; i+= 2) {
+ *p++ = (an_hex2int(s[i]) << 4) + an_hex2int(s[i + 1]);
+ n++;
+ }
+ if (s[i] != '\0')
+ errx(1, "hex strings must be of even length");
+ k->klen = n;
+ } else {
+ /* No, just copy it in. */
+ bcopy(s, k->key, strlen(s));
+ k->klen = strlen(s);
+ }
+
+ return;
+}
+
+static void
+an_setkeys(const char *iface, const char *key, int keytype)
+{
+ struct an_req areq;
+ struct an_ltv_key *k;
+
+ bzero(&areq, sizeof(areq));
+ k = (struct an_ltv_key *)&areq;
+
+ if (strlen(key) > 28) {
+ err(1, "encryption key must be no "
+ "more than 18 characters long");
+ }
+
+ an_str2key(key, k);
+
+ k->kindex=keytype/2;
+
+ if (!(k->klen==0 || k->klen==5 || k->klen==13)) {
+ err(1, "encryption key must be 0, 5 or 13 bytes long");
+ }
+
+ /* default mac and only valid one (from manual) 1.0.0.0.0.0 */
+ k->mac[0]=1;
+ k->mac[1]=0;
+ k->mac[2]=0;
+ k->mac[3]=0;
+ k->mac[4]=0;
+ k->mac[5]=0;
+
+ switch(keytype & 1) {
+ case 0:
+ areq.an_len = sizeof(struct an_ltv_key);
+ areq.an_type = AN_RID_WEP_PERM;
+ an_setval(iface, &areq);
+ break;
+ case 1:
+ areq.an_len = sizeof(struct an_ltv_key);
+ areq.an_type = AN_RID_WEP_TEMP;
+ an_setval(iface, &areq);
+ break;
+ }
+}
+
+static void
+an_readkeyinfo(const char *iface)
+{
+ struct an_req areq;
+ struct an_ltv_genconfig *cfg;
+ struct an_ltv_key *k;
+ int i;
+ int home;
+
+ areq.an_len = sizeof(areq);
+ areq.an_type = AN_RID_ACTUALCFG;
+ an_getval(iface, &areq);
+ cfg = (struct an_ltv_genconfig *)&areq;
+ if (cfg->an_home_product & AN_HOME_NETWORK)
+ home = 1;
+ else
+ home = 0;
+
+ bzero(&areq, sizeof(areq));
+ k = (struct an_ltv_key *)&areq;
+
+ printf("WEP Key status:\n");
+ areq.an_type = AN_RID_WEP_TEMP; /* read first key */
+ for(i=0; i<5; i++) {
+ areq.an_len = sizeof(struct an_ltv_key);
+ an_getval(iface, &areq);
+ if (k->kindex == 0xffff)
+ break;
+ switch (k->klen) {
+ case 0:
+ printf("\tKey %u is unset\n", k->kindex);
+ break;
+ case 5:
+ printf("\tKey %u is set 40 bits\n", k->kindex);
+ break;
+ case 13:
+ printf("\tKey %u is set 128 bits\n", k->kindex);
+ break;
+ default:
+ printf("\tWEP Key %d has an unknown size %u\n",
+ i, k->klen);
+ }
+
+ areq.an_type = AN_RID_WEP_PERM; /* read next key */
+ }
+ k->kindex = 0xffff;
+ areq.an_len = sizeof(struct an_ltv_key);
+ an_getval(iface, &areq);
+ printf("\tThe active transmit key is %d\n", 4 * home + k->mac[0]);
+
+ return;
+}
+
+static void
+an_enable_tx_key(const char *iface, const char *arg)
+{
+ struct an_req areq;
+ struct an_ltv_key *k;
+ struct an_ltv_genconfig *config;
+
+ bzero(&areq, sizeof(areq));
+
+ /* set home or not home mode */
+ areq.an_len = sizeof(struct an_ltv_genconfig);
+ areq.an_type = AN_RID_GENCONFIG;
+ an_getval(iface, &areq);
+ config = (struct an_ltv_genconfig *)&areq;
+ if (atoi(arg) == 4) {
+ config->an_home_product |= AN_HOME_NETWORK;
+ }else{
+ config->an_home_product &= ~AN_HOME_NETWORK;
+ }
+ an_setval(iface, &areq);
+
+ bzero(&areq, sizeof(areq));
+
+ k = (struct an_ltv_key *)&areq;
+
+ /* From a Cisco engineer write the transmit key to use in the
+ first MAC, index is FFFF*/
+ k->kindex=0xffff;
+ k->klen=0;
+
+ k->mac[0]=atoi(arg);
+ k->mac[1]=0;
+ k->mac[2]=0;
+ k->mac[3]=0;
+ k->mac[4]=0;
+ k->mac[5]=0;
+
+ areq.an_len = sizeof(struct an_ltv_key);
+ areq.an_type = AN_RID_WEP_PERM;
+ an_setval(iface, &areq);
+}
+
+static void
+an_enable_leap_mode(const char *iface, const char *username)
+{
+ struct an_req areq;
+ struct an_ltv_status *sts;
+ struct an_ltv_genconfig *cfg;
+ struct an_ltv_caps *caps;
+ struct an_ltv_leap_username an_username;
+ struct an_ltv_leap_password an_password;
+ char *password;
+ MD4_CTX context;
+ int len;
+ int i;
+ char unicode_password[LEAP_PASSWORD_MAX * 2];
+
+ areq.an_len = sizeof(areq);
+ areq.an_type = AN_RID_CAPABILITIES;
+
+ an_getval(iface, &areq);
+
+ caps = (struct an_ltv_caps *)&areq;
+
+ if (!(caps->an_softcaps & AN_AUTHTYPE_LEAP)) {
+ fprintf(stderr, "Firmware does not support LEAP\n");
+ exit(1);
+ }
+
+ bzero(&an_username, sizeof(an_username));
+ bzero(&an_password, sizeof(an_password));
+
+ len = strlen(username);
+ if (len > LEAP_USERNAME_MAX) {
+ printf("Username too long (max %d)\n", LEAP_USERNAME_MAX);
+ exit(1);
+ }
+ strncpy(an_username.an_username, username, len);
+ an_username.an_username_len = len;
+ an_username.an_len = sizeof(an_username);
+ an_username.an_type = AN_RID_LEAPUSERNAME;
+
+ password = getpass("Enter LEAP password:");
+
+ len = strlen(password);
+ if (len > LEAP_PASSWORD_MAX) {
+ printf("Password too long (max %d)\n", LEAP_PASSWORD_MAX);
+ exit(1);
+ }
+
+ bzero(&unicode_password, sizeof(unicode_password));
+ for(i = 0; i < len; i++) {
+ unicode_password[i * 2] = *password++;
+ }
+
+ /* First half */
+ MD4Init(&context);
+ MD4Update(&context, unicode_password, len * 2);
+ MD4Final(&an_password.an_password[0], &context);
+
+ /* Second half */
+ MD4Init (&context);
+ MD4Update (&context, &an_password.an_password[0], 16);
+ MD4Final (&an_password.an_password[16], &context);
+
+ an_password.an_password_len = 32;
+ an_password.an_len = sizeof(an_password);
+ an_password.an_type = AN_RID_LEAPPASSWORD;
+
+ an_setval(iface, (struct an_req *)&an_username);
+ an_setval(iface, (struct an_req *)&an_password);
+
+ areq.an_len = sizeof(areq);
+ areq.an_type = AN_RID_GENCONFIG;
+ an_getval(iface, &areq);
+ cfg = (struct an_ltv_genconfig *)&areq;
+ cfg->an_authtype = (AN_AUTHTYPE_PRIVACY_IN_USE | AN_AUTHTYPE_LEAP);
+ an_setval(iface, &areq);
+
+ sts = (struct an_ltv_status *)&areq;
+ areq.an_type = AN_RID_STATUS;
+
+ for (i = 60; i > 0; i--) {
+ an_getval(iface, &areq);
+ if (sts->an_opmode & AN_STATUS_OPMODE_LEAP) {
+ printf("Authenticated\n");
+ break;
+ }
+ sleep(1);
+ }
+
+ if (i == 0) {
+ fprintf(stderr, "Failed LEAP authentication\n");
+ exit(1);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ int act = 0;
+ const char *iface = NULL;
+ int modifier = 0;
+ char *key = NULL;
+ void *arg = NULL;
+ char *p = argv[0];
+
+ /* Get the interface name */
+ opterr = 0;
+ ch = getopt(argc, argv, "i:");
+ if (ch == 'i') {
+ iface = optarg;
+ } else {
+ if (argc > 1 && *argv[1] != '-') {
+ iface = argv[1];
+ optind = 2;
+ } else {
+ iface = "an0";
+ optind = 1;
+ }
+ optreset = 1;
+ }
+ opterr = 1;
+
+ while ((ch = getopt(argc, argv,
+ "ANISCTRht:a:e:o:s:n:v:d:j:b:c:f:r:p:w:m:l:k:K:W:QZM:L:")) != -1) {
+ switch(ch) {
+ case 'Z':
+#ifdef ANCACHE
+ act = ACT_ZEROCACHE;
+#else
+ errx(1, "ANCACHE not available");
+#endif
+ break;
+ case 'Q':
+#ifdef ANCACHE
+ act = ACT_DUMPCACHE;
+#else
+ errx(1, "ANCACHE not available");
+#endif
+ break;
+ case 'A':
+ act = ACT_DUMPAP;
+ break;
+ case 'N':
+ act = ACT_DUMPSSID;
+ break;
+ case 'S':
+ act = ACT_DUMPSTATUS;
+ break;
+ case 'I':
+ act = ACT_DUMPCAPS;
+ break;
+ case 'T':
+ act = ACT_DUMPSTATS;
+ break;
+ case 'C':
+ act = ACT_DUMPCONFIG;
+ break;
+ case 'R':
+ act = ACT_DUMPRSSIMAP;
+ break;
+ case 't':
+ act = ACT_SET_TXRATE;
+ arg = optarg;
+ break;
+ case 's':
+ act = ACT_SET_PWRSAVE;
+ arg = optarg;
+ break;
+ case 'p':
+ act = ACT_SET_TXPWR;
+ arg = optarg;
+ break;
+ case 'v':
+ modifier = atoi(optarg);
+ break;
+ case 'a':
+ switch(modifier) {
+ case 0:
+ case 1:
+ act = ACT_SET_AP1;
+ break;
+ case 2:
+ act = ACT_SET_AP2;
+ break;
+ case 3:
+ act = ACT_SET_AP3;
+ break;
+ case 4:
+ act = ACT_SET_AP4;
+ break;
+ default:
+ errx(1, "bad modifier %d: there "
+ "are only 4 access point settings",
+ modifier);
+ usage(p);
+ break;
+ }
+ arg = optarg;
+ break;
+ case 'b':
+ act = ACT_SET_BEACON_PERIOD;
+ arg = optarg;
+ break;
+ case 'd':
+ switch(modifier) {
+ case 0:
+ act = ACT_SET_DIVERSITY_RX;
+ break;
+ case 1:
+ act = ACT_SET_DIVERSITY_TX;
+ break;
+ default:
+ errx(1, "must specify RX or TX diversity");
+ break;
+ }
+ if (!isdigit(*optarg)) {
+ errx(1, "%s is not numeric", optarg);
+ exit(1);
+ }
+ arg = optarg;
+ break;
+ case 'j':
+ act = ACT_SET_NETJOIN;
+ arg = optarg;
+ break;
+ case 'l':
+ act = ACT_SET_MYNAME;
+ arg = optarg;
+ break;
+ case 'm':
+ act = ACT_SET_MAC;
+ arg = optarg;
+ break;
+ case 'n':
+ if (modifier == 0)
+ modifier = 1;
+ act = ACT_SET_SSID;
+ arg = optarg;
+ break;
+ case 'o':
+ act = ACT_SET_OPMODE;
+ arg = optarg;
+ break;
+ case 'c':
+ act = ACT_SET_FREQ;
+ arg = optarg;
+ break;
+ case 'f':
+ act = ACT_SET_FRAG_THRESH;
+ arg = optarg;
+ break;
+ case 'W':
+ act = ACT_ENABLE_WEP;
+ arg = optarg;
+ break;
+ case 'K':
+ act = ACT_SET_KEY_TYPE;
+ arg = optarg;
+ break;
+ case 'k':
+ act = ACT_SET_KEYS;
+ key = optarg;
+ break;
+ case 'e':
+ act = ACT_ENABLE_TX_KEY;
+ arg = optarg;
+ break;
+ case 'q':
+ act = ACT_SET_RTS_RETRYLIM;
+ arg = optarg;
+ break;
+ case 'r':
+ act = ACT_SET_RTS_THRESH;
+ arg = optarg;
+ break;
+ case 'w':
+ act = ACT_SET_WAKE_DURATION;
+ arg = optarg;
+ break;
+ case 'M':
+ act = ACT_SET_MONITOR_MODE;
+ arg = optarg;
+ break;
+ case 'L':
+ act = ACT_SET_LEAP_MODE;
+ arg = optarg;
+ break;
+ case 'h':
+ default:
+ usage(p);
+ }
+ }
+
+ if (iface == NULL || (!act && !key))
+ usage(p);
+
+ switch(act) {
+ case ACT_DUMPSTATUS:
+ an_dumpstatus(iface);
+ break;
+ case ACT_DUMPCAPS:
+ an_dumpcaps(iface);
+ break;
+ case ACT_DUMPSTATS:
+ an_dumpstats(iface);
+ break;
+ case ACT_DUMPCONFIG:
+ an_dumpconfig(iface);
+ break;
+ case ACT_DUMPSSID:
+ an_dumpssid(iface);
+ break;
+ case ACT_DUMPAP:
+ an_dumpap(iface);
+ break;
+ case ACT_DUMPRSSIMAP:
+ an_dumprssimap(iface);
+ break;
+ case ACT_SET_SSID:
+ an_setssid(iface, modifier, arg);
+ break;
+ case ACT_SET_AP1:
+ case ACT_SET_AP2:
+ case ACT_SET_AP3:
+ case ACT_SET_AP4:
+ an_setap(iface, act, arg);
+ break;
+ case ACT_SET_TXRATE:
+ an_setspeed(iface, act, arg);
+ break;
+#ifdef ANCACHE
+ case ACT_ZEROCACHE:
+ an_zerocache(iface);
+ break;
+ case ACT_DUMPCACHE:
+ an_readcache(iface);
+ break;
+
+#endif
+ case ACT_SET_KEYS:
+ an_setkeys(iface, key, modifier);
+ break;
+ case ACT_ENABLE_TX_KEY:
+ an_enable_tx_key(iface, arg);
+ break;
+ case ACT_SET_LEAP_MODE:
+ an_enable_leap_mode(iface, arg);
+ break;
+ default:
+ an_setconfig(iface, act, arg);
+ break;
+ }
+
+ exit(0);
+}
+
diff --git a/usr.sbin/apm/Makefile b/usr.sbin/apm/Makefile
new file mode 100644
index 0000000..3cc6f50
--- /dev/null
+++ b/usr.sbin/apm/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= apm
+MAN= apm.8
+MLINKS= apm.8 apmconf.8
+MANSUBDIR= /${MACHINE_CPUARCH}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/apm/Makefile.depend b/usr.sbin/apm/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/apm/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/apm/apm.8 b/usr.sbin/apm/apm.8
new file mode 100644
index 0000000..96eb973
--- /dev/null
+++ b/usr.sbin/apm/apm.8
@@ -0,0 +1,153 @@
+.\" LP (Laptop Package)
+.\"
+.\" Copyright (c) 1994 by Tatsumi Hosokawa <hosokawa@jp.FreeBSD.org>
+.\"
+.\" This software may be used, modified, copied, and distributed, in
+.\" both source and binary form provided that the above copyright and
+.\" these terms are retained. Under no circumstances is the author
+.\" responsible for the proper functioning of this software, nor does
+.\" the author assume any responsibility for damages incurred with its
+.\" use.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd December 22, 2009
+.Dt APM 8 i386
+.Os
+.Sh NAME
+.Nm apm
+.Nd control the APM BIOS and display its information
+.Sh SYNOPSIS
+.Nm
+.Op Fl ablstzZ
+.Op Fl d Ar enable
+.Op Fl e Ar enable
+.Op Fl h Ar enable
+.Op Fl r Ar delta
+.Sh DESCRIPTION
+The
+.Nm
+utility
+controls the Intel / Microsoft APM (Advanced Power Management) BIOS and
+displays the current status of APM on laptop PCs.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl a
+Display the current AC-line status as an integer value.
+The values
+0, 1 and 2 correspond to the
+.Dq off-line
+state,
+.Dq on-line
+state or
+.Dq backup power
+state, respectively.
+.It Fl b
+Display an integer value reflecting the current battery status.
+The values 0, 1, 2, 3, correspond to the
+.Dq high
+status,
+.Dq low
+status,
+.Dq critical
+status,
+.Dq charging
+status respectively.
+.It Fl d Ar enable
+Disable/enable suspending of the display separately from a normal suspend
+using the boolean value for
+.Ar enable .
+This feature seems to not work on many different laptops,
+including the Libretto 30CT and 50CT.
+.It Fl e Ar enable
+Enable or disable APM functions of the computer,
+depending on the boolean
+.Ar enable
+argument.
+.It Fl h Ar enable
+Depending on the boolean value of
+.Ar enable ,
+enable or disable the HLT instruction in the kernel context switch routine.
+These options are not necessary for almost all APM implementations,
+but for some implementations whose
+.Dq Pa Idle CPU
+call executes both CPU clock slowdown and HLT instruction,
+.Fl h Cm false
+is necessary to prevent the system from reducing its peak performance.
+See
+.Xr apm 4
+for details.
+.It Fl l
+Display the remaining battery percentage.
+If your laptop does not
+support this function, 255 is displayed.
+.It Fl r Ar delta
+Enable the resume wakeup timer, if the laptop supports it.
+This
+does not actually suspend the laptop, but if the laptop is suspended,
+and it supports resume from suspend, then it will be resumed after
+.Ar delta
+seconds (from when you run this command, not from when you suspend).
+.It Fl s
+Display the status of the APM support as an integer value.
+The values
+0 and 1 correspond to the
+.Dq disabled
+state or
+.Dq enabled
+state respectively.
+.It Fl t
+Display the estimated remaining battery lifetime in seconds.
+If
+it is unknown, -1 is displayed.
+.It Fl Z
+Transition the system into standby mode.
+This mode uses less power than
+full power mode, but more than suspend mode.
+Some laptops support
+resuming from this state on timer or Ring Indicator events.
+The
+output of
+.Nm
+tells what your laptop claims to support.
+.It Fl z
+Suspend the system.
+It is used by
+.Xr zzz 8 .
+.El
+.Pp
+If no options are specified,
+.Nm
+displays information and current status of APM in verbose mode.
+If multiple display options are given, the values are displayed one
+per line in the order given here.
+.Sh NOTES
+.Xr apmconf 8
+has been merged in
+.Nm
+and thus
+.Nm
+replaces all of its functionality.
+.Sh SEE ALSO
+.Xr apm 4 ,
+.Xr zzz 8
+.Sh AUTHORS
+.An Tatsumi Hosokawa Aq Mt hosokawa@jp.FreeBSD.org
+.Sh BUGS
+Some APM implementations do not support parameters needed by
+.Nm .
+On such systems,
+.Nm
+displays them as unknown.
+.Pp
+Some APM implementations cannot handle events such as pushing the
+power button or closing the cover.
+On such implementations, the system
+.Ar must
+be suspended
+.Ar only
+by using
+.Nm
+or
+.Nm zzz .
diff --git a/usr.sbin/apm/apm.c b/usr.sbin/apm/apm.c
new file mode 100644
index 0000000..05652da
--- /dev/null
+++ b/usr.sbin/apm/apm.c
@@ -0,0 +1,490 @@
+/*
+ * APM BIOS utility for FreeBSD
+ *
+ * Copyright (C) 1994-1996 by Tatsumi Hosokawa <hosokawa@jp.FreeBSD.org>
+ *
+ * This software may be used, modified, copied, distributed, and sold,
+ * in both source and binary form provided that the above copyright and
+ * these terms are retained. Under no circumstances is the author
+ * responsible for the proper functioning of this software, nor does
+ * the author assume any responsibility for damages incurred with its
+ * use.
+ *
+ * Sep., 1994 Implemented on FreeBSD 1.1.5.1R (Toshiba AVS001WD)
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/file.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/sysctl.h>
+
+#include <machine/apm_bios.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define APMDEV "/dev/apm"
+
+#define APM_UNKNOWN 255
+
+#define xh(a) (((a) & 0xff00) >> 8)
+#define xl(a) ((a) & 0xff)
+#define APMERR(a) xh(a)
+
+static int cmos_wall = 0; /* True when wall time is in cmos clock, else UTC */
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: apm [-ablstzZ] [-d enable ] [ -e enable ] "
+ "[ -h enable ] [-r delta]\n");
+ exit(1);
+}
+
+/*
+ * Return 1 for boolean true, and 0 for false, according to the
+ * interpretation of the string argument given.
+ */
+static int
+is_true(const char *boolean)
+{
+ char *endp;
+ long val;
+
+ val = strtoul(boolean, &endp, 0);
+ if (*endp == '\0')
+ return (val != 0 ? 1 : 0);
+ if (strcasecmp(boolean, "true") == 0 ||
+ strcasecmp(boolean, "yes") == 0 ||
+ strcasecmp(boolean, "enable") == 0)
+ return (1);
+ if (strcasecmp(boolean, "false") == 0 ||
+ strcasecmp(boolean, "no") == 0 ||
+ strcasecmp(boolean, "disable") == 0)
+ return (0);
+ /* Well, I have no idea what the user wants, so... */
+ warnx("invalid boolean argument \"%s\"", boolean);
+ usage();
+ /* NOTREACHED */
+
+ return (0);
+}
+
+static int
+int2bcd(int i)
+{
+ int retval = 0;
+ int base = 0;
+
+ if (i >= 10000)
+ return -1;
+
+ while (i) {
+ retval |= (i % 10) << base;
+ i /= 10;
+ base += 4;
+ }
+ return retval;
+}
+
+static int
+bcd2int(int bcd)
+{
+ int retval = 0;
+ int place = 1;
+
+ if (bcd > 0x9999)
+ return -1;
+
+ while (bcd) {
+ retval += (bcd & 0xf) * place;
+ bcd >>= 4;
+ place *= 10;
+ }
+ return retval;
+}
+
+static void
+apm_suspend(int fd)
+{
+ if (ioctl(fd, APMIO_SUSPEND, NULL) == -1)
+ err(1, "ioctl(APMIO_SUSPEND)");
+}
+
+static void
+apm_standby(int fd)
+{
+ if (ioctl(fd, APMIO_STANDBY, NULL) == -1)
+ err(1, "ioctl(APMIO_STANDBY)");
+}
+
+static void
+apm_getinfo(int fd, apm_info_t aip)
+{
+ if (ioctl(fd, APMIO_GETINFO, aip) == -1)
+ err(1, "ioctl(APMIO_GETINFO)");
+}
+
+static void
+apm_enable(int fd, int enable)
+{
+ if (enable) {
+ if (ioctl(fd, APMIO_ENABLE) == -1)
+ err(1, "ioctl(APMIO_ENABLE)");
+ } else {
+ if (ioctl(fd, APMIO_DISABLE) == -1)
+ err(1, "ioctl(APMIO_DISABLE)");
+ }
+}
+
+static void
+print_batt_time(int batt_time)
+{
+ printf("Remaining battery time: ");
+ if (batt_time == -1)
+ printf("unknown\n");
+ else {
+ int h, m, s;
+
+ h = batt_time;
+ s = h % 60;
+ h /= 60;
+ m = h % 60;
+ h /= 60;
+ printf("%2d:%02d:%02d\n", h, m, s);
+ }
+}
+
+static void
+print_batt_life(u_int batt_life)
+{
+ printf("Remaining battery life: ");
+ if (batt_life == APM_UNKNOWN)
+ printf("unknown\n");
+ else if (batt_life <= 100)
+ printf("%d%%\n", batt_life);
+ else
+ printf("invalid value (0x%x)\n", batt_life);
+}
+
+static void
+print_batt_stat(u_int batt_stat)
+{
+ const char *batt_msg[] = { "high", "low", "critical", "charging" };
+
+ printf("Battery Status: ");
+ if (batt_stat == APM_UNKNOWN)
+ printf("unknown\n");
+ else if (batt_stat > 3)
+ printf("invalid value (0x%x)\n", batt_stat);
+ else
+ printf("%s\n", batt_msg[batt_stat]);
+}
+
+static void
+print_all_info(int fd, apm_info_t aip, int bioscall_available)
+{
+ struct apm_bios_arg args;
+ int apmerr;
+ const char *line_msg[] = { "off-line", "on-line" , "backup power"};
+
+ printf("APM version: %d.%d\n", aip->ai_major, aip->ai_minor);
+ printf("APM Management: %s\n", aip->ai_status ? "Enabled" : "Disabled");
+ printf("AC Line status: ");
+ if (aip->ai_acline == APM_UNKNOWN)
+ printf("unknown\n");
+ else if (aip->ai_acline > 2)
+ printf("invalid value (0x%x)\n", aip->ai_acline);
+ else
+ printf("%s\n", line_msg[aip->ai_acline]);
+
+ print_batt_stat(aip->ai_batt_stat);
+ print_batt_life(aip->ai_batt_life);
+ print_batt_time(aip->ai_batt_time);
+
+ if (aip->ai_infoversion >= 1) {
+ printf("Number of batteries: ");
+ if (aip->ai_batteries == ~0U)
+ printf("unknown\n");
+ else {
+ u_int i;
+ struct apm_pwstatus aps;
+
+ printf("%d\n", aip->ai_batteries);
+ for (i = 0; i < aip->ai_batteries; ++i) {
+ bzero(&aps, sizeof(aps));
+ aps.ap_device = PMDV_BATT0 + i;
+ if (ioctl(fd, APMIO_GETPWSTATUS, &aps) == -1)
+ continue;
+ printf("Battery %d:\n", i);
+ if (aps.ap_batt_flag & APM_BATT_NOT_PRESENT) {
+ printf("not present\n");
+ continue;
+ }
+ printf("\t");
+ print_batt_stat(aps.ap_batt_stat);
+ printf("\t");
+ print_batt_life(aps.ap_batt_life);
+ printf("\t");
+ print_batt_time(aps.ap_batt_time);
+ }
+ }
+ }
+
+ if (bioscall_available) {
+ /*
+ * try to get the suspend timer
+ */
+ bzero(&args, sizeof(args));
+ args.eax = (APM_BIOS) << 8 | APM_RESUMETIMER;
+ args.ebx = PMDV_APMBIOS;
+ args.ecx = 0x0001;
+ if (ioctl(fd, APMIO_BIOS, &args)) {
+ printf("Resume timer: unknown\n");
+ } else {
+ apmerr = APMERR(args.eax);
+ if (apmerr == 0x0d || apmerr == 0x86)
+ printf("Resume timer: disabled\n");
+ else if (apmerr)
+ warnx(
+ "failed to get the resume timer: APM error0x%x", apmerr);
+ else {
+ /*
+ * OK. We have the time (all bcd).
+ * CH - seconds
+ * DH - hours
+ * DL - minutes
+ * xh(SI) - month (1-12)
+ * xl(SI) - day of month (1-31)
+ * DI - year
+ */
+ struct tm tm;
+ char buf[1024];
+ time_t t;
+
+ tm.tm_sec = bcd2int(xh(args.ecx));
+ tm.tm_min = bcd2int(xl(args.edx));
+ tm.tm_hour = bcd2int(xh(args.edx));
+ tm.tm_mday = bcd2int(xl(args.esi));
+ tm.tm_mon = bcd2int(xh(args.esi)) - 1;
+ tm.tm_year = bcd2int(args.edi) - 1900;
+ if (cmos_wall)
+ t = mktime(&tm);
+ else
+ t = timegm(&tm);
+ if (t != -1) {
+ tm = *localtime(&t);
+ strftime(buf, sizeof(buf), "%c", &tm);
+ printf("Resume timer: %s\n", buf);
+ } else
+ printf("Resume timer: unknown\n");
+ }
+ }
+
+ /*
+ * Get the ring indicator resume state
+ */
+ bzero(&args, sizeof(args));
+ args.eax = (APM_BIOS) << 8 | APM_RESUMEONRING;
+ args.ebx = PMDV_APMBIOS;
+ args.ecx = 0x0002;
+ if (ioctl(fd, APMIO_BIOS, &args) == 0) {
+ printf("Resume on ring indicator: %sabled\n",
+ args.ecx ? "en" : "dis");
+ }
+ }
+
+ if (aip->ai_infoversion >= 1) {
+ if (aip->ai_capabilities == 0xff00)
+ return;
+ printf("APM Capabilities:\n");
+ if (aip->ai_capabilities & 0x01)
+ printf("\tglobal standby state\n");
+ if (aip->ai_capabilities & 0x02)
+ printf("\tglobal suspend state\n");
+ if (aip->ai_capabilities & 0x04)
+ printf("\tresume timer from standby\n");
+ if (aip->ai_capabilities & 0x08)
+ printf("\tresume timer from suspend\n");
+ if (aip->ai_capabilities & 0x10)
+ printf("\tRI resume from standby\n");
+ if (aip->ai_capabilities & 0x20)
+ printf("\tRI resume from suspend\n");
+ if (aip->ai_capabilities & 0x40)
+ printf("\tPCMCIA RI resume from standby\n");
+ if (aip->ai_capabilities & 0x80)
+ printf("\tPCMCIA RI resume from suspend\n");
+ }
+
+}
+
+/*
+ * currently, it can turn off the display, but the display never comes
+ * back until the machine suspend/resumes :-).
+ */
+static void
+apm_display(int fd, int newstate)
+{
+ if (ioctl(fd, APMIO_DISPLAY, &newstate) == -1)
+ err(1, "ioctl(APMIO_DISPLAY)");
+}
+
+static void
+apm_haltcpu(int fd, int enable)
+{
+ if (enable) {
+ if (ioctl(fd, APMIO_HALTCPU, NULL) == -1)
+ err(1, "ioctl(APMIO_HALTCPU)");
+ } else {
+ if (ioctl(fd, APMIO_NOTHALTCPU, NULL) == -1)
+ err(1, "ioctl(APMIO_NOTHALTCPU)");
+ }
+}
+
+static void
+apm_set_timer(int fd, int delta)
+{
+ time_t tmr;
+ struct tm *tm;
+ struct apm_bios_arg args;
+
+ tmr = time(NULL) + delta;
+ if (cmos_wall)
+ tm = localtime(&tmr);
+ else
+ tm = gmtime(&tmr);
+ bzero(&args, sizeof(args));
+ args.eax = (APM_BIOS) << 8 | APM_RESUMETIMER;
+ args.ebx = PMDV_APMBIOS;
+ if (delta > 0) {
+ args.ecx = (int2bcd(tm->tm_sec) << 8) | 0x02;
+ args.edx = (int2bcd(tm->tm_hour) << 8) | int2bcd(tm->tm_min);
+ args.esi = (int2bcd(tm->tm_mon + 1) << 8) | int2bcd(tm->tm_mday);
+ args.edi = int2bcd(tm->tm_year + 1900);
+ } else {
+ args.ecx = 0x0000;
+ }
+ if (ioctl(fd, APMIO_BIOS, &args)) {
+ err(1,"set resume timer");
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ int c, fd;
+ int dosleep = 0, all_info = 1, apm_status = 0, batt_status = 0;
+ int display = -1, batt_life = 0, ac_status = 0, standby = 0;
+ int batt_time = 0, delta = 0, enable = -1, haltcpu = -1;
+ int bioscall_available = 0;
+ size_t cmos_wall_len = sizeof(cmos_wall);
+
+ if (sysctlbyname("machdep.wall_cmos_clock", &cmos_wall, &cmos_wall_len,
+ NULL, 0) == -1)
+ err(1, "sysctlbyname(machdep.wall_cmos_clock)");
+
+ while ((c = getopt(argc, argv, "abe:h:lRr:stzd:Z")) != -1) {
+ switch (c) {
+ case 'a':
+ ac_status = 1;
+ all_info = 0;
+ break;
+ case 'b':
+ batt_status = 1;
+ all_info = 0;
+ break;
+ case 'd':
+ display = is_true(optarg);
+ all_info = 0;
+ break;
+ case 'l':
+ batt_life = 1;
+ all_info = 0;
+ break;
+ case 'R':
+ delta = -1;
+ break;
+ case 'r':
+ delta = atoi(optarg);
+ break;
+ case 's':
+ apm_status = 1;
+ all_info = 0;
+ break;
+ case 'e':
+ enable = is_true(optarg);
+ all_info = 0;
+ break;
+ case 'h':
+ haltcpu = is_true(optarg);
+ all_info = 0;
+ break;
+ case 't':
+ batt_time = 1;
+ all_info = 0;
+ break;
+ case 'z':
+ dosleep = 1;
+ all_info = 0;
+ break;
+ case 'Z':
+ standby = 1;
+ all_info = 0;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+ }
+ if (haltcpu != -1 || enable != -1 || display != -1 || delta || dosleep
+ || standby) {
+ fd = open(APMDEV, O_RDWR);
+ bioscall_available = 1;
+ } else if ((fd = open(APMDEV, O_RDWR)) >= 0)
+ bioscall_available = 1;
+ else
+ fd = open(APMDEV, O_RDONLY);
+ if (fd == -1)
+ err(1, "can't open %s", APMDEV);
+ if (enable != -1)
+ apm_enable(fd, enable);
+ if (haltcpu != -1)
+ apm_haltcpu(fd, haltcpu);
+ if (delta)
+ apm_set_timer(fd, delta);
+ if (dosleep)
+ apm_suspend(fd);
+ else if (standby)
+ apm_standby(fd);
+ else if (delta == 0) {
+ struct apm_info info;
+
+ apm_getinfo(fd, &info);
+ if (all_info)
+ print_all_info(fd, &info, bioscall_available);
+ if (ac_status)
+ printf("%d\n", info.ai_acline);
+ if (batt_status)
+ printf("%d\n", info.ai_batt_stat);
+ if (batt_life)
+ printf("%d\n", info.ai_batt_life);
+ if (apm_status)
+ printf("%d\n", info.ai_status);
+ if (batt_time)
+ printf("%d\n", info.ai_batt_time);
+ if (display != -1)
+ apm_display(fd, display);
+ }
+ close(fd);
+ exit(0);
+}
diff --git a/usr.sbin/apmd/Makefile b/usr.sbin/apmd/Makefile
new file mode 100644
index 0000000..b2afdfaf
--- /dev/null
+++ b/usr.sbin/apmd/Makefile
@@ -0,0 +1,17 @@
+# $FreeBSD$
+
+PROG= apmd
+MAN= apmd.8
+MANSUBDIR= /i386
+SRCS= apmd.c apmdlex.l apmdparse.y y.tab.h
+
+WARNS?= 3
+
+LIBADD= l
+
+CFLAGS+= -I${.CURDIR}
+
+test:
+ ./apmd -d -f etc/apmd.conf -n
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/apmd/Makefile.depend b/usr.sbin/apmd/Makefile.depend
new file mode 100644
index 0000000..bae339a
--- /dev/null
+++ b/usr.sbin/apmd/Makefile.depend
@@ -0,0 +1,22 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+apmdlex.o: apmdlex.c
+apmdlex.o: y.tab.h
+apmdlex.po: apmdlex.c
+apmdlex.po: y.tab.h
+apmdparse.o: apmdparse.c
+apmdparse.po: apmdparse.c
+.endif
diff --git a/usr.sbin/apmd/README b/usr.sbin/apmd/README
new file mode 100644
index 0000000..dd0a03a
--- /dev/null
+++ b/usr.sbin/apmd/README
@@ -0,0 +1,213 @@
+FreeBSD apmd Package Release Notes (19990711 version)
+
+1. What is "apmd"?
+==================
+
+The apmd package provides a means of handling various APM events from
+userland code. Using apmd.conf, the apmd(8) configuration file, you
+can select the APM events to be handled from userland and specify the
+commands for a given event, allowing APM behaviour to be configured
+flexibly.
+
+
+2. How to install the apmd package
+==================================
+
+2.1 Making the apmd control device file
+---------------------------------------
+
+apmd(8) uses the new special device file /dev/apmctl. This should be
+created as follows:
+
+# cd /dev
+# mknod apmctl c 39 8
+
+2.2 Applying the kernel patch and building a new kernel
+-------------------------------------------------------
+
+The next step is to apply the patch against the sys source tree.
+Go to the source directory (eg. /usr/src/ or /usr/PAO3/src/) and run
+the patch command as follows:
+
+# gzip -cd [somewhere]/apmd-sys-R320.diff | patch
+
+For PAO3 users, the patch file name would be apmd-sys-PAO3.diff
+instead of apmd-sys-R320.diff. After this step has completed
+successfully, build and install a new kernel and reboot your system.
+
+2.3 Making the apmd program
+---------------------------
+
+Go to src/usr.sbin/ and extract the apmd tarball as follows:
+
+# tar xzpvf [somewhere]/apmd-usr.sbin.tar.gz
+
+Before doing a make all, you need to copy apm_bios.h in the sys source
+tree to /usr/include/machine/ first:
+
+# cp /sys/i386/include/apm_bios.h /usr/include/machine/
+
+Then do the build and install steps in the apmd directory:
+
+# cd src/usr.sbin/apmd
+# make depend all install
+
+2.4 Setting up the configuration file and userland script
+---------------------------------------------------------
+
+In src/usr.sbin/apm/etc/ there are example configuration and userland
+script files which are invoked automatically when the APM BIOS informs
+apmd of an event, such as suspend request. Copy these files to
+/etc/ as follows:
+
+# cp src/usr.sbin/apm/etc/* /etc/
+
+
+3. Running the apmd daemon program
+==================================
+
+To run apmd(8) in background mode, simply type ``apmd''.
+
+# apmd
+
+To make a running apmd reload /etc/apmd.conf, send a SIGHUP signal to
+the apmd(8) process.
+
+# kill -HUP [apmd pid]
+or
+# killall -HUP apmd
+
+apmd has some command line options. For the details, please
+refer to the manpage of apmd.
+
+4. Configuration file
+=====================
+
+The structure of the apmd configuration file is quite simple. For
+example:
+
+apm_event SUSPENDREQ {
+ exec "sync && sync && sync";
+ exec "sleep 1";
+ exec "zzz";
+}
+
+Will cause apmd to receive the APM event SUSPENDREQ (which may be
+posted by an LCD close), run the sync command 3 times and wait for a
+while, then execute zzz (apm -z) to put the system in the suspend
+state.
+
+4.1 The apm_event keyword
+-------------------------
+`apm_event' is the keyword which indicates the start of configuration for
+each events.
+
+4.2 APM events
+--------------
+
+If you wish to execute the same commands for different events, the
+event names should be delimited by a comma. The following are valid
+event names:
+
+o Events ignored by the kernel if apmd is running:
+
+STANDBYREQ
+SUSPENDREQ
+USERSUSPENDREQ
+BATTERYLOW
+
+o Events passed to apmd after kernel handling:
+
+NORMRESUME
+CRITRESUME
+STANDBYRESUME
+POWERSTATECHANGE
+UPDATETIME
+
+
+Other events will not be sent to apmd.
+
+4.3 command line syntax
+-----------------------
+
+In the example above, the three lines beginning with `exec' are commands
+for the event. Each line should be terminated with a semicolon. The
+command list for the event should be enclosed by `{' and `}'. apmd(8)
+uses /bin/sh for double-quotation enclosed command execution, just as
+with system(3). Each command is executed in order until the end of
+the list is reached or a command finishes with a non-zero status code.
+apmd(8) will report any failed command's status code via syslog(3)
+and will then reject the request event posted by APM BIOS.
+
+4.4 Built-in functions
+----------------------
+
+You can also specify apmd built-in functions instead of command lines.
+A built-in function name should be terminated with a semicolon, just as
+with a command line.
+The following built-in functions are currently supported:
+
+o reject;
+
+ Reject last request posted by the APM BIOS. This can be used to reject a
+ SUSPEND request when the LCD is closed and put the system in a STANDBY
+ state instead.
+
+
+
+5. EXAMPLES
+===========
+
+Sample configuration commands include:
+
+apm_event SUSPENDREQ {
+ exec "/etc/rc.suspend";
+}
+
+apm_event USERSUSPENDREQ {
+ exec "sync && sync && sync";
+ exec "sleep 1";
+ exec "apm -z";
+}
+
+apm_event NORMRESUME, STANDBYRESUME {
+ exec "/etc/rc.resume";
+}
+
+# resume event configuration for serial mouse users by
+# reinitializing a moused(8) connected to a serial port.
+#
+#apm_event NORMRESUME {
+# exec "kill -HUP `cat /var/run/moused.pid`";
+#}
+
+# suspend request event configuration for ATA HDD users:
+# execute standby instead of suspend.
+#
+#apm_event SUSPENDREQ {
+# reject;
+# exec "sync && sync && sync";
+# exec "sleep 1";
+# exec "apm -Z";
+#}
+
+
+6. Call for developers
+======================
+
+The initial version of apmd(8) was implemented primarily to test the
+kernel support code and was ALPHA quality. Based on that code, the
+current version was developed by KOIE Hidetaka <hide@koie.org>.
+However, we're still looking around for interesting new features and
+ideas, so if you have any thoughts, please let us know.
+Documentation is also sparse, and the manpage have just written.
+If you wish to collaborate on this work, please e-mail me:
+iwasaki@freebsd.org.
+
+
+June 1, 1999
+Created by: iwasaki@FreeBSD.org
+Edited by: jkh@FreeBSD.org
+ nick@foobar.org
+
+$FreeBSD$
diff --git a/usr.sbin/apmd/apmd.8 b/usr.sbin/apmd/apmd.8
new file mode 100644
index 0000000..afecd47
--- /dev/null
+++ b/usr.sbin/apmd/apmd.8
@@ -0,0 +1,322 @@
+.\" Copyright (c) 1999 Mitsuru IWASAKI <iwasaki@FreeBSD.org>
+.\" Copyright (c) 1999 KOIE Hidetaka <koie@suri.co.jp>
+.\" Copyright (c) 1999 Yoshihiko SARUMARU <mistral@imasy.or.jp>
+.\" Copyright (c) 1999 Norihiro Kumagai <kuma@nk.rim.or.jp>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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.
+.\"
+.\" @(#)apmd.8 1.1 (FreeBSD) 6/28/99
+.\" $FreeBSD$
+.\"
+.Dd June 28, 1999
+.Dt APMD 8 i386
+.Os
+.Sh NAME
+.Nm apmd
+.Nd Advanced Power Management monitor daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl d
+.Op Fl f file
+.Op Fl s
+.Op Fl v
+.Sh DESCRIPTION
+The
+.Nm
+utility
+monitors the occurrence of the specified Advanced Power Management
+.Pq Tn APM
+events and, if one of the events occurs, it executes the sequence of
+commands corresponding to the event.
+Only the events specified in the
+configuration file are notified to
+.Nm ;
+all other events are ignored.
+For each event posted by the APM BIOS,
+.Nm
+invokes the sequence of commands specified in the configuration file.
+When
+.Nm
+is running with monitoring suspend/standby requests,
+the kernel will not process those requests.
+Therefore, if you wish action to be taken when these events
+occur, you need to explicitly configure the appropriate commands or
+built-in functions in the configuration file.
+.Pp
+The
+.Nm
+utility recognizes the following runtime options:
+.Bl -tag -width f_file
+.It Fl d
+Starts in debug mode.
+This causes
+.Nm
+to execute in the foreground instead of in daemon mode.
+.It Fl f Ar file
+Specifies a different configuration file
+.Ar file
+to be used in place of the default
+.Pa /etc/apmd.conf .
+.It Fl s
+Causes
+.Nm
+to simulate a POWERSTATECHANGE event when a power state change is detected
+(AC_POWER_STATE) but the bios of the laptop does not report it.
+This enables you to do things like dimming the LCD backlight when you unplug
+the power cord.
+.It Fl v
+Verbose mode.
+.El
+.Pp
+When
+.Nm
+starts, it reads the configuration file
+.Pa ( /etc/apmd.conf
+as default)
+and notifies the set of events to be monitored to the APM device driver.
+When it terminates, the APM device driver automatically cancels
+monitored events.
+.Pp
+If the
+.Nm
+process receives a
+.Dv SIGHUP ,
+it will reread its configuration file and
+notify the APM device driver of any changes to its configuration.
+.Pp
+The
+.Nm
+utility uses the device
+.Pa /dev/apmctl
+to issue
+.Xr ioctl 2
+requests for monitoring events and for controlling the APM system.
+This device file is opened exclusively, so only a single
+.Nm
+process can be running at any time.
+.Pp
+When
+.Nm
+receives an APM event, it forks a child process to execute the
+commands specified in the configuration file and then continues
+listening for more events.
+The child process executes the commands
+specified, one at a time and in the order that they are listed.
+.Pp
+While
+.Nm
+is processing the command list for SUSPEND/STANDBY requests, the APM kernel
+device driver issues notifications to APM BIOS once per second so that the
+BIOS knows that there are still some commands pending, and that it should not
+complete the request just yet.
+.Pp
+The
+.Nm
+utility creates the file
+.Pa /var/run/apmd.pid ,
+and stores its process
+id there.
+This can be used to kill or reconfigure
+.Nm .
+.Sh CONFIGURATION FILE
+The structure of the
+.Nm
+configuration file is quite simple.
+For example:
+.Bd -literal
+apm_event SUSPENDREQ {
+ exec "sync && sync && sync";
+ exec "sleep 1";
+ exec "zzz";
+}
+.Ed
+.Pp
+will cause
+.Nm
+to receive the APM event
+.Ql SUSPENDREQ
+(which may be posted by an LCD close), run the
+.Ql sync
+command 3 times and wait for a while, then execute
+.Nm zzz ( Ns Nm apm Fl z )
+to put the system in the suspend state.
+.Bl -bullet
+.It
+The apm_event keyword
+.Bd -ragged -offset indent
+.Ql apm_event
+is the keyword which indicates the start of configuration for
+each event.
+.Ed
+.It
+APM events
+.Bd -ragged -offset indent
+If you wish to execute the same commands for different events, the
+event names should be delimited by a comma.
+The following are
+valid event names:
+.Bl -item
+.It
+- Events ignored by the kernel if
+.Nm
+is running:
+.Pp
+.Bl -tag -width USERSUSPENDREQ -compact -offset indent
+.It STANDBYREQ
+.It USERSTANDBYREQ
+.It SUSPENDREQ
+should include sync in the command list,
+.It USERSUSPENDREQ
+should include sync in the command list,
+.It BATTERYLOW
+only zzz should be specified in the command list.
+.El
+.It
+- Events passed to
+.Nm
+after kernel handling:
+.Pp
+.Bl -tag -width USERSUSPENDREQ -compact -offset indent
+.It NORMRESUME
+.It CRITRESUME
+.It STANDBYRESUME
+.It POWERSTATECHANGE
+.It UPDATETIME
+.It CAPABILITIESCHANGE
+.El
+.Pp
+Other events will not be sent to
+.Nm .
+.El
+.Ed
+.It
+command line syntax
+.Bd -ragged -offset indent
+In the example above, the three lines beginning with
+.Ql exec
+are commands for the event.
+Each line should be terminated with a semicolon.
+The command list for the event should be enclosed by
+.Ql {
+and
+.Ql } .
+The
+.Nm
+utility uses
+.Pa /bin/sh
+for double-quotation enclosed command execution, just as with
+.Xr system 3 .
+Each command is executed in order until the end of
+the list is reached or a command finishes with a non-zero status code.
+The
+.Nm
+utility will report any failed command's status code via
+.Xr syslog 3
+and will then reject the request event posted by the APM BIOS.
+.Ed
+.It
+Built-in functions
+.Bd -ragged -offset indent
+You can also specify
+.Nm
+built-in functions instead of command lines.
+A built-in function name should be terminated with a semicolon,
+just as with a command line.
+The following built-in functions are currently supported:
+.Bl -item
+.It
+.Bl -tag -width ".It - reject"
+.It - reject
+Reject last request posted by APM BIOS.
+This can be used to reject
+a SUSPEND request when the LCD is closed and put the system in a
+STANDBY state instead.
+.El
+.El
+.Ed
+.El
+.Sh FILES
+.Bl -tag -width /etc/apmd.conf -compact
+.It Pa /etc/apmd.conf
+.It Pa /dev/apmctl
+.It Pa /var/run/apmd.pid
+.El
+.Sh EXAMPLES
+Sample configuration commands include:
+.Bd -literal
+apm_event SUSPENDREQ {
+ exec "/etc/rc.suspend apm suspend";
+}
+
+apm_event USERSUSPENDREQ {
+ exec "sync && sync && sync";
+ exec "sleep 1";
+ exec "apm -z";
+}
+
+apm_event NORMRESUME {
+ exec "/etc/rc.resume apm suspend";
+}
+
+apm_event STANDBYRESUME {
+ exec "/etc/rc.resume apm standby";
+}
+
+# resume event configuration for serial mouse users by
+# reinitializing a moused(8) connected to a serial port.
+#
+#apm_event NORMRESUME {
+# exec "kill -HUP `cat /var/run/moused.pid`";
+#}
+#
+# suspend request event configuration for ATA HDD users:
+# execute standby instead of suspend.
+#
+#apm_event SUSPENDREQ {
+# reject;
+# exec "sync && sync && sync";
+# exec "sleep 1";
+# exec "apm -Z";
+#}
+.Ed
+.Sh SEE ALSO
+.Xr apm 4 ,
+.Xr apm 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 3.3 .
+.Sh AUTHORS
+.An Mitsuru IWASAKI Aq Mt iwasaki@FreeBSD.org
+.An KOIE Hidetaka Aq Mt koie@suri.co.jp
+.Pp
+.An -nosplit
+Some contributions made by
+.An Warner Losh Aq Mt imp@FreeBSD.org ,
+.An Hiroshi Yamashita Aq Mt bluemoon@msj.biglobe.ne.jp ,
+.An Yoshihiko SARUMARU Aq Mt mistral@imasy.or.jp ,
+.An Norihiro Kumagai Aq Mt kuma@nk.rim.or.jp ,
+.An NAKAGAWA Yoshihisa Aq Mt nakagawa@jp.FreeBSD.org ,
+and
+.An Nick Hilliard Aq Mt nick@foobar.org .
diff --git a/usr.sbin/apmd/apmd.c b/usr.sbin/apmd/apmd.c
new file mode 100644
index 0000000..585383c
--- /dev/null
+++ b/usr.sbin/apmd/apmd.c
@@ -0,0 +1,705 @@
+/*-
+ * APM (Advanced Power Management) Event Dispatcher
+ *
+ * Copyright (c) 1999 Mitsuru IWASAKI <iwasaki@FreeBSD.org>
+ * Copyright (c) 1999 KOIE Hidetaka <koie@suri.co.jp>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <assert.h>
+#include <bitstring.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <machine/apm_bios.h>
+
+#include "apmd.h"
+
+int debug_level = 0;
+int verbose = 0;
+int soft_power_state_change = 0;
+const char *apmd_configfile = APMD_CONFIGFILE;
+const char *apmd_pidfile = APMD_PIDFILE;
+int apmctl_fd = -1, apmnorm_fd = -1;
+
+/*
+ * table of event handlers
+ */
+#define EVENT_CONFIG_INITIALIZER(EV,R) { #EV, NULL, R },
+struct event_config events[EVENT_MAX] = {
+ EVENT_CONFIG_INITIALIZER(NOEVENT, 0)
+ EVENT_CONFIG_INITIALIZER(STANDBYREQ, 1)
+ EVENT_CONFIG_INITIALIZER(SUSPENDREQ, 1)
+ EVENT_CONFIG_INITIALIZER(NORMRESUME, 0)
+ EVENT_CONFIG_INITIALIZER(CRITRESUME, 0)
+ EVENT_CONFIG_INITIALIZER(BATTERYLOW, 0)
+ EVENT_CONFIG_INITIALIZER(POWERSTATECHANGE, 0)
+ EVENT_CONFIG_INITIALIZER(UPDATETIME, 0)
+ EVENT_CONFIG_INITIALIZER(CRITSUSPEND, 1)
+ EVENT_CONFIG_INITIALIZER(USERSTANDBYREQ, 1)
+ EVENT_CONFIG_INITIALIZER(USERSUSPENDREQ, 1)
+ EVENT_CONFIG_INITIALIZER(STANDBYRESUME, 0)
+ EVENT_CONFIG_INITIALIZER(CAPABILITIESCHANGE, 0)
+};
+
+/*
+ * List of battery events
+ */
+struct battery_watch_event *battery_watch_list = NULL;
+
+#define BATT_CHK_INTV 10 /* how many seconds between battery state checks? */
+
+/*
+ * default procedure
+ */
+struct event_cmd *
+event_cmd_default_clone(void *this)
+{
+ struct event_cmd * oldone = this;
+ struct event_cmd * newone = malloc(oldone->len);
+
+ newone->next = NULL;
+ newone->len = oldone->len;
+ newone->name = oldone->name;
+ newone->op = oldone->op;
+ return newone;
+}
+
+/*
+ * exec command
+ */
+int
+event_cmd_exec_act(void *this)
+{
+ struct event_cmd_exec * p = this;
+ int status = -1;
+ pid_t pid;
+
+ switch ((pid = fork())) {
+ case -1:
+ warn("cannot fork");
+ break;
+ case 0:
+ /* child process */
+ signal(SIGHUP, SIG_DFL);
+ signal(SIGCHLD, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ execl(_PATH_BSHELL, "sh", "-c", p->line, (char *)NULL);
+ _exit(127);
+ default:
+ /* parent process */
+ do {
+ pid = waitpid(pid, &status, 0);
+ } while (pid == -1 && errno == EINTR);
+ break;
+ }
+ return status;
+}
+void
+event_cmd_exec_dump(void *this, FILE *fp)
+{
+ fprintf(fp, " \"%s\"", ((struct event_cmd_exec *)this)->line);
+}
+struct event_cmd *
+event_cmd_exec_clone(void *this)
+{
+ struct event_cmd_exec * newone = (struct event_cmd_exec *) event_cmd_default_clone(this);
+ struct event_cmd_exec * oldone = this;
+
+ newone->evcmd.next = NULL;
+ newone->evcmd.len = oldone->evcmd.len;
+ newone->evcmd.name = oldone->evcmd.name;
+ newone->evcmd.op = oldone->evcmd.op;
+ if ((newone->line = strdup(oldone->line)) == NULL)
+ err(1, "out of memory");
+ return (struct event_cmd *) newone;
+}
+void
+event_cmd_exec_free(void *this)
+{
+ free(((struct event_cmd_exec *)this)->line);
+}
+struct event_cmd_op event_cmd_exec_ops = {
+ event_cmd_exec_act,
+ event_cmd_exec_dump,
+ event_cmd_exec_clone,
+ event_cmd_exec_free
+};
+
+/*
+ * reject command
+ */
+int
+event_cmd_reject_act(void *this __unused)
+{
+ int rc = 0;
+
+ if (ioctl(apmctl_fd, APMIO_REJECTLASTREQ, NULL)) {
+ syslog(LOG_NOTICE, "fail to reject\n");
+ rc = -1;
+ }
+ return rc;
+}
+struct event_cmd_op event_cmd_reject_ops = {
+ event_cmd_reject_act,
+ NULL,
+ event_cmd_default_clone,
+ NULL
+};
+
+/*
+ * manipulate event_config
+ */
+struct event_cmd *
+clone_event_cmd_list(struct event_cmd *p)
+{
+ struct event_cmd dummy;
+ struct event_cmd *q = &dummy;
+ for ( ;p; p = p->next) {
+ assert(p->op->clone);
+ if ((q->next = p->op->clone(p)) == NULL)
+ err(1, "out of memory");
+ q = q->next;
+ }
+ q->next = NULL;
+ return dummy.next;
+}
+void
+free_event_cmd_list(struct event_cmd *p)
+{
+ struct event_cmd * q;
+ for ( ; p ; p = q) {
+ q = p->next;
+ if (p->op->free)
+ p->op->free(p);
+ free(p);
+ }
+}
+int
+register_battery_handlers(
+ int level, int direction,
+ struct event_cmd *cmdlist)
+{
+ /*
+ * level is negative if it's in "minutes", non-negative if
+ * percentage.
+ *
+ * direction =1 means we care about this level when charging,
+ * direction =-1 means we care about it when discharging.
+ */
+ if (level>100) /* percentage > 100 */
+ return -1;
+ if (abs(direction) != 1) /* nonsense direction value */
+ return -1;
+
+ if (cmdlist) {
+ struct battery_watch_event *we;
+
+ if ((we = malloc(sizeof(struct battery_watch_event))) == NULL)
+ err(1, "out of memory");
+
+ we->next = battery_watch_list; /* starts at NULL */
+ battery_watch_list = we;
+ we->level = abs(level);
+ we->type = (level<0)?BATTERY_MINUTES:BATTERY_PERCENT;
+ we->direction = (direction<0)?BATTERY_DISCHARGING:
+ BATTERY_CHARGING;
+ we->done = 0;
+ we->cmdlist = clone_event_cmd_list(cmdlist);
+ }
+ return 0;
+}
+int
+register_apm_event_handlers(
+ bitstr_t bit_decl(evlist, EVENT_MAX),
+ struct event_cmd *cmdlist)
+{
+ if (cmdlist) {
+ bitstr_t bit_decl(tmp, EVENT_MAX);
+ memcpy(&tmp, evlist, bitstr_size(EVENT_MAX));
+
+ for (;;) {
+ int n;
+ struct event_cmd *p;
+ struct event_cmd *q;
+ bit_ffs(tmp, EVENT_MAX, &n);
+ if (n < 0)
+ break;
+ p = events[n].cmdlist;
+ if ((q = clone_event_cmd_list(cmdlist)) == NULL)
+ err(1, "out of memory");
+ if (p) {
+ while (p->next != NULL)
+ p = p->next;
+ p->next = q;
+ } else {
+ events[n].cmdlist = q;
+ }
+ bit_clear(tmp, n);
+ }
+ }
+ return 0;
+}
+
+/*
+ * execute command
+ */
+int
+exec_run_cmd(struct event_cmd *p)
+{
+ int status = 0;
+
+ for (; p; p = p->next) {
+ assert(p->op->act);
+ if (verbose)
+ syslog(LOG_INFO, "action: %s", p->name);
+ status = p->op->act(p);
+ if (status) {
+ syslog(LOG_NOTICE, "command finished with %d\n", status);
+ break;
+ }
+ }
+ return status;
+}
+
+/*
+ * execute command -- the event version
+ */
+int
+exec_event_cmd(struct event_config *ev)
+{
+ int status = 0;
+
+ status = exec_run_cmd(ev->cmdlist);
+ if (status && ev->rejectable) {
+ syslog(LOG_ERR, "canceled");
+ event_cmd_reject_act(NULL);
+ }
+ return status;
+}
+
+/*
+ * read config file
+ */
+extern FILE * yyin;
+extern int yydebug;
+
+void
+read_config(void)
+{
+ int i;
+
+ if ((yyin = fopen(apmd_configfile, "r")) == NULL) {
+ err(1, "cannot open config file");
+ }
+
+#ifdef DEBUG
+ yydebug = debug_level;
+#endif
+
+ if (yyparse() != 0)
+ err(1, "cannot parse config file");
+
+ fclose(yyin);
+
+ /* enable events */
+ for (i = 0; i < EVENT_MAX; i++) {
+ if (events[i].cmdlist) {
+ u_int event_type = i;
+ if (write(apmctl_fd, &event_type, sizeof(u_int)) == -1) {
+ err(1, "cannot enable event 0x%x", event_type);
+ }
+ }
+ }
+}
+
+void
+dump_config(void)
+{
+ int i;
+ struct battery_watch_event *q;
+
+ for (i = 0; i < EVENT_MAX; i++) {
+ struct event_cmd * p;
+ if ((p = events[i].cmdlist)) {
+ fprintf(stderr, "apm_event %s {\n", events[i].name);
+ for ( ; p ; p = p->next) {
+ fprintf(stderr, "\t%s", p->name);
+ if (p->op->dump)
+ p->op->dump(p, stderr);
+ fprintf(stderr, ";\n");
+ }
+ fprintf(stderr, "}\n");
+ }
+ }
+ for (q = battery_watch_list ; q != NULL ; q = q -> next) {
+ struct event_cmd * p;
+ fprintf(stderr, "apm_battery %d%s %s {\n",
+ q -> level,
+ (q -> type == BATTERY_PERCENT)?"%":"m",
+ (q -> direction == BATTERY_CHARGING)?"charging":
+ "discharging");
+ for ( p = q -> cmdlist; p ; p = p->next) {
+ fprintf(stderr, "\t%s", p->name);
+ if (p->op->dump)
+ p->op->dump(p, stderr);
+ fprintf(stderr, ";\n");
+ }
+ fprintf(stderr, "}\n");
+ }
+}
+
+void
+destroy_config(void)
+{
+ int i;
+ struct battery_watch_event *q;
+
+ /* disable events */
+ for (i = 0; i < EVENT_MAX; i++) {
+ if (events[i].cmdlist) {
+ u_int event_type = i;
+ if (write(apmctl_fd, &event_type, sizeof(u_int)) == -1) {
+ err(1, "cannot disable event 0x%x", event_type);
+ }
+ }
+ }
+
+ for (i = 0; i < EVENT_MAX; i++) {
+ struct event_cmd * p;
+ if ((p = events[i].cmdlist))
+ free_event_cmd_list(p);
+ events[i].cmdlist = NULL;
+ }
+
+ for( ; battery_watch_list; battery_watch_list = battery_watch_list -> next) {
+ free_event_cmd_list(battery_watch_list->cmdlist);
+ q = battery_watch_list->next;
+ free(battery_watch_list);
+ battery_watch_list = q;
+ }
+}
+
+void
+restart(void)
+{
+ destroy_config();
+ read_config();
+ if (verbose)
+ dump_config();
+}
+
+/*
+ * write pid file
+ */
+static void
+write_pid(void)
+{
+ FILE *fp = fopen(apmd_pidfile, "w");
+
+ if (fp) {
+ fprintf(fp, "%ld\n", (long)getpid());
+ fclose(fp);
+ }
+}
+
+/*
+ * handle signals
+ */
+static int signal_fd[2];
+
+void
+enque_signal(int sig)
+{
+ if (write(signal_fd[1], &sig, sizeof sig) != sizeof sig)
+ err(1, "cannot process signal.");
+}
+
+void
+wait_child(void)
+{
+ int status;
+ while (waitpid(-1, &status, WNOHANG) > 0)
+ ;
+}
+
+int
+proc_signal(int fd)
+{
+ int rc = 0;
+ int sig;
+
+ while (read(fd, &sig, sizeof sig) == sizeof sig) {
+ syslog(LOG_INFO, "caught signal: %d", sig);
+ switch (sig) {
+ case SIGHUP:
+ syslog(LOG_NOTICE, "restart by SIG");
+ restart();
+ break;
+ case SIGTERM:
+ syslog(LOG_NOTICE, "going down on signal %d", sig);
+ rc = -1;
+ return rc;
+ case SIGCHLD:
+ wait_child();
+ break;
+ default:
+ warn("unexpected signal(%d) received.", sig);
+ break;
+ }
+ }
+ return rc;
+}
+void
+proc_apmevent(int fd)
+{
+ struct apm_event_info apmevent;
+
+ while (ioctl(fd, APMIO_NEXTEVENT, &apmevent) == 0) {
+ int status;
+ syslog(LOG_NOTICE, "apmevent %04x index %d\n",
+ apmevent.type, apmevent.index);
+ syslog(LOG_INFO, "apm event: %s", events[apmevent.type].name);
+ if (fork() == 0) {
+ status = exec_event_cmd(&events[apmevent.type]);
+ exit(status);
+ }
+ }
+}
+
+#define AC_POWER_STATE ((pw_info.ai_acline == 1) ? BATTERY_CHARGING :\
+ BATTERY_DISCHARGING)
+
+void
+check_battery(void)
+{
+
+ static int first_time=1, last_state;
+ int status;
+
+ struct apm_info pw_info;
+ struct battery_watch_event *p;
+
+ /* If we don't care, don't bother */
+ if (battery_watch_list == NULL)
+ return;
+
+ if (first_time) {
+ if ( ioctl(apmnorm_fd, APMIO_GETINFO, &pw_info) < 0)
+ err(1, "cannot check battery state.");
+/*
+ * This next statement isn't entirely true. The spec does not tie AC
+ * line state to battery charging or not, but this is a bit lazier to do.
+ */
+ last_state = AC_POWER_STATE;
+ first_time = 0;
+ return; /* We can't process events, we have no baseline */
+ }
+
+ /*
+ * XXX - should we do this a bunch of times and perform some sort
+ * of smoothing or correction?
+ */
+ if ( ioctl(apmnorm_fd, APMIO_GETINFO, &pw_info) < 0)
+ err(1, "cannot check battery state.");
+
+ /*
+ * If we're not in the state now that we were in last time,
+ * then it's a transition, which means we must clean out
+ * the event-caught state.
+ */
+ if (last_state != AC_POWER_STATE) {
+ if (soft_power_state_change && fork() == 0) {
+ status = exec_event_cmd(&events[PMEV_POWERSTATECHANGE]);
+ exit(status);
+ }
+ last_state = AC_POWER_STATE;
+ for (p = battery_watch_list ; p!=NULL ; p = p -> next)
+ p->done = 0;
+ }
+ for (p = battery_watch_list ; p != NULL ; p = p -> next)
+ if (p -> direction == AC_POWER_STATE &&
+ !(p -> done) &&
+ ((p -> type == BATTERY_PERCENT &&
+ p -> level == (int)pw_info.ai_batt_life) ||
+ (p -> type == BATTERY_MINUTES &&
+ p -> level == (pw_info.ai_batt_time / 60)))) {
+ p -> done++;
+ if (verbose)
+ syslog(LOG_NOTICE, "Caught battery event: %s, %d%s",
+ (p -> direction == BATTERY_CHARGING)?"charging":"discharging",
+ p -> level,
+ (p -> type == BATTERY_PERCENT)?"%":" minutes");
+ if (fork() == 0) {
+ status = exec_run_cmd(p -> cmdlist);
+ exit(status);
+ }
+ }
+}
+void
+event_loop(void)
+{
+ int fdmax = 0;
+ struct sigaction nsa;
+ fd_set master_rfds;
+ sigset_t sigmask, osigmask;
+
+ FD_ZERO(&master_rfds);
+ FD_SET(apmctl_fd, &master_rfds);
+ fdmax = apmctl_fd > fdmax ? apmctl_fd : fdmax;
+
+ FD_SET(signal_fd[0], &master_rfds);
+ fdmax = signal_fd[0] > fdmax ? signal_fd[0] : fdmax;
+
+ memset(&nsa, 0, sizeof nsa);
+ nsa.sa_handler = enque_signal;
+ sigfillset(&nsa.sa_mask);
+ nsa.sa_flags = SA_RESTART;
+ sigaction(SIGHUP, &nsa, NULL);
+ sigaction(SIGCHLD, &nsa, NULL);
+ sigaction(SIGTERM, &nsa, NULL);
+
+ sigemptyset(&sigmask);
+ sigaddset(&sigmask, SIGHUP);
+ sigaddset(&sigmask, SIGCHLD);
+ sigaddset(&sigmask, SIGTERM);
+ sigprocmask(SIG_SETMASK, &sigmask, &osigmask);
+
+ while (1) {
+ fd_set rfds;
+ int res;
+ struct timeval to;
+
+ to.tv_sec = BATT_CHK_INTV;
+ to.tv_usec = 0;
+
+ memcpy(&rfds, &master_rfds, sizeof rfds);
+ sigprocmask(SIG_SETMASK, &osigmask, NULL);
+ if ((res=select(fdmax + 1, &rfds, 0, 0, &to)) < 0) {
+ if (errno != EINTR)
+ err(1, "select");
+ }
+ sigprocmask(SIG_SETMASK, &sigmask, NULL);
+
+ if (res == 0) { /* time to check the battery */
+ check_battery();
+ continue;
+ }
+
+ if (FD_ISSET(signal_fd[0], &rfds)) {
+ if (proc_signal(signal_fd[0]) < 0)
+ return;
+ }
+
+ if (FD_ISSET(apmctl_fd, &rfds))
+ proc_apmevent(apmctl_fd);
+ }
+}
+
+int
+main(int ac, char* av[])
+{
+ int ch;
+ int daemonize = 1;
+ char *prog;
+ int logopt = LOG_NDELAY | LOG_PID;
+
+ while ((ch = getopt(ac, av, "df:sv")) != -1) {
+ switch (ch) {
+ case 'd':
+ daemonize = 0;
+ debug_level++;
+ break;
+ case 'f':
+ apmd_configfile = optarg;
+ break;
+ case 's':
+ soft_power_state_change = 1;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ default:
+ err(1, "unknown option `%c'", ch);
+ }
+ }
+
+ if (daemonize)
+ daemon(0, 0);
+
+#ifdef NICE_INCR
+ nice(NICE_INCR);
+#endif
+
+ if (!daemonize)
+ logopt |= LOG_PERROR;
+
+ prog = strrchr(av[0], '/');
+ openlog(prog ? prog+1 : av[0], logopt, LOG_DAEMON);
+
+ syslog(LOG_NOTICE, "start");
+
+ if (pipe(signal_fd) < 0)
+ err(1, "pipe");
+ if (fcntl(signal_fd[0], F_SETFL, O_NONBLOCK) < 0)
+ err(1, "fcntl");
+
+ if ((apmnorm_fd = open(APM_NORM_DEVICEFILE, O_RDWR)) == -1) {
+ err(1, "cannot open device file `%s'", APM_NORM_DEVICEFILE);
+ }
+
+ if (fcntl(apmnorm_fd, F_SETFD, 1) == -1) {
+ err(1, "cannot set close-on-exec flag for device file '%s'", APM_NORM_DEVICEFILE);
+ }
+
+ if ((apmctl_fd = open(APM_CTL_DEVICEFILE, O_RDWR)) == -1) {
+ err(1, "cannot open device file `%s'", APM_CTL_DEVICEFILE);
+ }
+
+ if (fcntl(apmctl_fd, F_SETFD, 1) == -1) {
+ err(1, "cannot set close-on-exec flag for device file '%s'", APM_CTL_DEVICEFILE);
+ }
+
+ restart();
+ write_pid();
+ event_loop();
+ exit(EXIT_SUCCESS);
+}
+
diff --git a/usr.sbin/apmd/apmd.h b/usr.sbin/apmd/apmd.h
new file mode 100644
index 0000000..d379feb
--- /dev/null
+++ b/usr.sbin/apmd/apmd.h
@@ -0,0 +1,133 @@
+/*-
+ * APM (Advanced Power Management) Event Dispatcher
+ *
+ * Copyright (c) 1999 Mitsuru IWASAKI <iwasaki@FreeBSD.org>
+ * Copyright (c) 1999 KOIE Hidetaka <koie@suri.co.jp>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+#define APMD_CONFIGFILE "/etc/apmd.conf"
+#define APM_CTL_DEVICEFILE "/dev/apmctl"
+#define APM_NORM_DEVICEFILE "/dev/apm"
+#define APMD_PIDFILE "/var/run/apmd.pid"
+#define NICE_INCR -20
+
+enum {
+ EVENT_NOEVENT,
+ EVENT_STANDBYREQ,
+ EVENT_SUSPENDREQ,
+ EVENT_NORMRESUME,
+ EVENT_CRITRESUME,
+ EVENT_BATTERYLOW,
+ EVENT_POWERSTATECHANGE,
+ EVENT_UPDATETIME,
+ EVENT_CRITSUSPEND,
+ EVENT_USERSTANDBYREQ,
+ EVENT_USERSUSPENDREQ,
+ EVENT_STANDBYRESUME,
+ EVENT_CAPABILITIESCHANGE,
+ EVENT_MAX
+};
+
+struct event_cmd_op {
+ int (* act)(void *this);
+ void (* dump)(void *this, FILE * fp);
+ struct event_cmd * (* clone)(void *this);
+ void (* free)(void *this);
+};
+struct event_cmd {
+ struct event_cmd * next;
+ size_t len;
+ char * name;
+ struct event_cmd_op * op;
+};
+struct event_cmd_exec {
+ struct event_cmd evcmd;
+ char * line; /* Command line */
+};
+struct event_cmd_reject {
+ struct event_cmd evcmd;
+};
+
+struct event_config {
+ const char *name;
+ struct event_cmd * cmdlist;
+ int rejectable;
+};
+
+struct battery_watch_event {
+ struct battery_watch_event *next;
+ int level;
+ enum {
+ BATTERY_CHARGING,
+ BATTERY_DISCHARGING
+ } direction;
+ enum {
+ BATTERY_MINUTES,
+ BATTERY_PERCENT
+ } type;
+ int done;
+ struct event_cmd *cmdlist;
+};
+
+
+extern struct event_cmd_op event_cmd_exec_ops;
+extern struct event_cmd_op event_cmd_reject_ops;
+extern struct event_config events[EVENT_MAX];
+extern struct battery_watch_event *battery_watch_list;
+
+extern int register_battery_handlers(
+ int level, int direction,
+ struct event_cmd *cmdlist);
+extern int register_apm_event_handlers(
+ bitstr_t bit_decl(evlist, EVENT_MAX),
+ struct event_cmd *cmdlist);
+extern void free_event_cmd_list(struct event_cmd *p);
+
+extern int yyparse(void);
+
+void yyerror(const char *);
+int yylex(void);
+
+struct event_cmd *event_cmd_default_clone(void *);
+int event_cmd_exec_act(void *);
+void event_cmd_exec_dump(void *, FILE *);
+struct event_cmd *event_cmd_exec_clone(void *);
+void event_cmd_exec_free(void *);
+int event_cmd_reject_act(void *);
+struct event_cmd *clone_event_cmd_list(struct event_cmd *);
+int exec_run_cmd(struct event_cmd *);
+int exec_event_cmd(struct event_config *);
+void read_config(void);
+void dump_config(void);
+void destroy_config(void);
+void restart(void);
+void enque_signal(int);
+void wait_child(void);
+int proc_signal(int);
+void proc_apmevent(int);
+void check_battery(void);
+void event_loop(void);
diff --git a/usr.sbin/apmd/apmdlex.l b/usr.sbin/apmd/apmdlex.l
new file mode 100644
index 0000000..b002feb
--- /dev/null
+++ b/usr.sbin/apmd/apmdlex.l
@@ -0,0 +1,116 @@
+%{
+/*-
+ * APM (Advanced Power Management) Event Dispatcher
+ *
+ * Copyright (c) 1999 Mitsuru IWASAKI <iwasaki@FreeBSD.org>
+ * Copyright (c) 1999 KOIE Hidetaka <koie@suri.co.jp>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <string.h>
+#include <syslog.h>
+#include <bitstring.h>
+#include "apmd.h"
+#include "y.tab.h"
+
+int lineno;
+int first_time;
+%}
+
+/* We don't need it, avoid the warning. */
+%option nounput
+%option noinput
+
+%s TOP
+
+%%
+
+%{
+ if (first_time) {
+ BEGIN TOP;
+ lineno = 1;
+ first_time = 0;
+ }
+%}
+
+<TOP>[ \t]+ ;
+<TOP>\n lineno++;
+<TOP>, { return COMMA; }
+<TOP>; { return SEMICOLON; }
+<TOP>#.*$ ;
+
+<TOP>apm_event { return APMEVENT; }
+
+<TOP>NOEVENT { yylval.ev = EVENT_NOEVENT; return EVENT; }
+<TOP>STANDBYREQ { yylval.ev = EVENT_STANDBYREQ; return EVENT; }
+<TOP>SUSPENDREQ { yylval.ev = EVENT_SUSPENDREQ; return EVENT; }
+<TOP>NORMRESUME { yylval.ev = EVENT_NORMRESUME; return EVENT; }
+<TOP>CRITRESUME { yylval.ev = EVENT_CRITRESUME; return EVENT; }
+<TOP>BATTERYLOW { yylval.ev = EVENT_BATTERYLOW; return EVENT; }
+<TOP>POWERSTATECHANGE { yylval.ev = EVENT_POWERSTATECHANGE; return EVENT; }
+<TOP>UPDATETIME { yylval.ev = EVENT_UPDATETIME; return EVENT; }
+<TOP>CRITSUSPEND { yylval.ev = EVENT_CRITSUSPEND; return EVENT; }
+<TOP>USERSTANDBYREQ { yylval.ev = EVENT_USERSTANDBYREQ; return EVENT; }
+<TOP>USERSUSPENDREQ { yylval.ev = EVENT_USERSUSPENDREQ; return EVENT; }
+<TOP>STANDBYRESUME { yylval.ev = EVENT_STANDBYRESUME; return EVENT; }
+<TOP>CAPABILITIESCHANGE { yylval.ev = EVENT_CAPABILITIESCHANGE; return EVENT; }
+
+<TOP>apm_battery { return APMBATT; }
+
+<TOP>charging { return BATTCHARGE; }
+<TOP>discharging { return BATTDISCHARGE; }
+<TOP>[0-9]+% {
+ yylval.i = atoi(yytext);
+ return BATTPERCENT;
+ }
+<TOP>[0-9]+[Mm] {
+ yylval.i = -atoi(yytext);
+ return BATTTIME;
+ }
+
+<TOP>exec { return EXECCMD; }
+<TOP>reject { return REJECTCMD; }
+
+<TOP>\{ { return BEGINBLOCK; }
+<TOP>\} { return ENDBLOCK; }
+<TOP>\"[^"]+\" {
+ int len = strlen(yytext) - 2;
+ if ((yylval.str = (char *) malloc(len + 1)) == NULL)
+ goto out;
+ memcpy(yylval.str, yytext + 1, len);
+ yylval.str[len] = '\0';
+ out:
+ return STRING;
+ }
+
+<TOP>[^"{},;#\n\t ]+ { yylval.str = strdup(yytext); return UNKNOWN; }
+%%
+
+void
+yyerror(const char *s)
+{
+ syslog(LOG_ERR, "line %d: %s%s %s.\n", lineno, yytext, yytext?":":"", s);
+}
diff --git a/usr.sbin/apmd/apmdparse.y b/usr.sbin/apmd/apmdparse.y
new file mode 100644
index 0000000..597982b
--- /dev/null
+++ b/usr.sbin/apmd/apmdparse.y
@@ -0,0 +1,205 @@
+%{
+/*-
+ * APM (Advanced Power Management) Event Dispatcher
+ *
+ * Copyright (c) 1999 Mitsuru IWASAKI <iwasaki@FreeBSD.org>
+ * Copyright (c) 1999 KOIE Hidetaka <koie@suri.co.jp>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <stdio.h>
+#include <bitstring.h>
+#include <stdlib.h>
+#include <string.h>
+#include "apmd.h"
+
+#ifdef DEBUG
+#define YYDEBUG 1
+#endif
+
+extern int first_time;
+
+%}
+
+%union {
+ char *str;
+ bitstr_t bit_decl(evlist, EVENT_MAX);
+ int ev;
+ struct event_cmd * evcmd;
+ int i;
+}
+
+%token BEGINBLOCK ENDBLOCK
+%token COMMA SEMICOLON
+%token APMEVENT
+%token APMBATT
+%token BATTCHARGE BATTDISCHARGE
+%token <i> BATTTIME BATTPERCENT
+%token EXECCMD REJECTCMD
+%token <ev> EVENT
+%token <str> STRING UNKNOWN
+
+%type <i> apm_battery_level
+%type <i> apm_battery_direction
+%type <str> string
+%type <str> unknown
+%type <evlist> event_list
+%type <evcmd> cmd_list
+%type <evcmd> cmd
+%type <evcmd> exec_cmd reject_cmd
+
+%%
+
+config_file
+ : { first_time = 1; } config_list
+ ;
+
+config_list
+ : config
+ | config_list config
+ ;
+
+config
+ : apm_event_statement
+ | apm_battery_statement
+ ;
+
+apm_event_statement
+ : APMEVENT event_list BEGINBLOCK cmd_list ENDBLOCK
+ {
+ if (register_apm_event_handlers($2, $4) < 0)
+ abort(); /* XXX */
+ free_event_cmd_list($4);
+ }
+ ;
+
+apm_battery_level
+ : BATTPERCENT
+ {
+ $$ = $1;
+ }
+ | BATTTIME
+ {
+ $$ = $1;
+ }
+ ;
+
+apm_battery_direction
+ : BATTCHARGE
+ {
+ $$ = 1;
+ }
+ | BATTDISCHARGE
+ {
+ $$ = -1;
+ }
+ ;
+apm_battery_statement
+ : APMBATT apm_battery_level apm_battery_direction
+ BEGINBLOCK cmd_list ENDBLOCK
+ {
+ if (register_battery_handlers($2, $3, $5) < 0)
+ abort(); /* XXX */
+ free_event_cmd_list($5);
+ }
+ ;
+
+event_list
+ : EVENT
+ {
+ bit_nclear($$, 0, EVENT_MAX - 1);
+ bit_set($$, $1);
+ }
+ | event_list COMMA EVENT
+ {
+ memcpy(&($$), &($1), bitstr_size(EVENT_MAX));
+ bit_set($$, $3);
+ }
+ ;
+
+cmd_list
+ : /* empty */
+ {
+ $$ = NULL;
+ }
+ | cmd_list cmd
+ {
+ struct event_cmd * p = $1;
+ if (p) {
+ while (p->next != NULL)
+ p = p->next;
+ p->next = $2;
+ $$ = $1;
+ } else {
+ $$ = $2;
+ }
+ }
+ ;
+
+cmd
+ : exec_cmd SEMICOLON { $$ = $1; }
+ | reject_cmd SEMICOLON { $$ = $1; }
+ ;
+
+exec_cmd
+ : EXECCMD string
+ {
+ size_t len = sizeof (struct event_cmd_exec);
+ struct event_cmd_exec *cmd = malloc(len);
+ cmd->evcmd.next = NULL;
+ cmd->evcmd.len = len;
+ cmd->evcmd.name = "exec";
+ cmd->evcmd.op = &event_cmd_exec_ops;
+ cmd->line = $2;
+ $$ = (struct event_cmd *) cmd;
+ }
+ ;
+
+reject_cmd
+ : REJECTCMD
+ {
+ size_t len = sizeof (struct event_cmd_reject);
+ struct event_cmd_reject *cmd = malloc(len);
+ cmd->evcmd.next = NULL;
+ cmd->evcmd.len = len;
+ cmd->evcmd.name = "reject";
+ cmd->evcmd.op = &event_cmd_reject_ops;
+ $$ = (struct event_cmd *) cmd;
+ }
+ ;
+
+string
+ : STRING { $$ = $1; }
+ ;
+
+unknown
+ : UNKNOWN
+ {
+ $$ = $1;
+ }
+ ;
+%%
+
diff --git a/usr.sbin/apmd/contrib/pccardq.c b/usr.sbin/apmd/contrib/pccardq.c
new file mode 100644
index 0000000..aebd1d5
--- /dev/null
+++ b/usr.sbin/apmd/contrib/pccardq.c
@@ -0,0 +1,286 @@
+/* $FreeBSD$ */
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+const char *const pccardd_file = "/var/tmp/.pccardd";
+const char *prog = "pccardq";
+const char *tmp_dir = "/tmp";
+unsigned slot_map = ~0;
+
+void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-a] [-n] [-s slot]\n", prog);
+}
+
+int
+proc_arg(int ac, char **av)
+{
+ int rc = -1;
+ int ch;
+
+ char *p = strrchr(av[0], '/');
+ prog = p ? p + 1 : av[0];
+
+ tmp_dir = getenv("TMPDIR") ? getenv("TMPDIR") : tmp_dir;
+
+ while ((ch = getopt(ac, av, "ans:")) != -1) {
+ switch (ch) {
+ case 'a':
+ slot_map = ~0;
+ break;
+ case 'n':
+ slot_map = 0;
+ break;
+ case 's':
+ {
+ int n = atoi(optarg);
+ if (n < 0 || n >= CHAR_BIT * sizeof slot_map) {
+ warnc(0, "Invalid slot number.");
+ usage();
+ goto out;
+ }
+ if (slot_map == ~0)
+ slot_map = 0;
+ slot_map |= 1 << n;
+ }
+ break;
+ default:
+ usage();
+ goto out;
+ }
+ }
+
+ rc = 0;
+ out:
+ return rc;
+}
+
+int
+connect_to_pccardd(char **path)
+{
+ int so = -1;
+ int pccardd_len;
+ struct sockaddr_un pccardq;
+ struct sockaddr_un pccardd;
+
+ if ((so = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0) {
+ warn("socket");
+ goto err;
+ }
+
+ snprintf(pccardq.sun_path, sizeof pccardq.sun_path,
+ "%s/%s%ld%ld", tmp_dir, prog, (long) getpid(), (long) time(0));
+ pccardq.sun_family = AF_UNIX;
+ pccardq.sun_len = offsetof(struct sockaddr_un, sun_path) + strlen(pccardq.sun_path);
+ if (bind(so, (struct sockaddr *) &pccardq, pccardq.sun_len) < 0) {
+ warn("bind: %s", pccardq.sun_path);
+ goto err;
+ }
+ if ((*path = strdup(pccardq.sun_path)) == NULL) {
+ warn("strdup");
+ goto err;
+ }
+
+ pccardd_len = strlen(pccardd_file) + 1;
+ if (pccardd_len > sizeof pccardd.sun_path) {
+ warnc(0, "%s: too long", pccardd_file);
+ goto err;
+ }
+ pccardd.sun_len = offsetof(struct sockaddr_un, sun_path) + pccardd_len;
+ pccardd.sun_family = AF_UNIX;
+ strcpy(pccardd.sun_path, pccardd_file);
+ if (connect(so, (struct sockaddr *) &pccardd, pccardd.sun_len) < 0) {
+ warn("connect: %s", pccardd_file);
+ goto err;
+ }
+ return so;
+ err:
+ if (so >= 0)
+ close(so);
+ return -1;
+}
+
+int
+get_slot_number(int so)
+{
+ char buf[8];
+ int rv;
+ int nslot;
+
+ if ((rv = write(so, "S", 1)) < 1) {
+ warn("write");
+ goto err;
+ } else if (rv != 1) {
+ warnc(0, "write: fail.");
+ goto err;
+ }
+
+ if ((rv = read(so, buf, sizeof buf)) < 0) {
+ warn("read");
+ goto err;
+ }
+ buf[sizeof buf - 1] = 0;
+ if (sscanf(buf, "%d", &nslot) != 1) {
+ warnc(0, "Invalid response.");
+ goto err;
+ }
+ return nslot;
+ err:
+ return -1;
+}
+
+enum {
+ SLOT_EMPTY = 0,
+ SLOT_FILLED = 1,
+ SLOT_INACTIVE = 2,
+ SLOT_UNDEFINED = 9
+};
+
+int
+get_slot_info(int so, int slot, char **manuf, char **version, char
+ **device, int *state)
+{
+ int rc = -1;
+ int rv;
+ static char buf[1024];
+ int slen;
+ char *s;
+ char *sl;
+
+ char *_manuf;
+ char *_version;
+ char *_device;
+
+ if ((slen = snprintf(buf, sizeof buf, "N%d", slot)) < 0) {
+ warnc(0, "write");
+ goto err;
+ }
+
+ if ((rv = write(so, buf, slen)) < 0) {
+ warn("write");
+ goto err;
+ } else if (rv != slen) {
+ warnc(0, "write");
+ goto err;
+ }
+
+ if ((rv = read(so, buf, sizeof buf)) < 0) {
+ warn("read");
+ goto err;
+ }
+
+ s = buf;
+ if ((sl = strsep(&s, "~")) == NULL)
+ goto parse_err;
+ if (atoi(sl) != slot)
+ goto parse_err;
+ if ((_manuf = strsep(&s, "~")) == NULL)
+ goto parse_err;
+ if ((_version = strsep(&s, "~")) == NULL)
+ goto parse_err;
+ if ((_device = strsep(&s, "~")) == NULL)
+ goto parse_err;
+ if (sscanf(s, "%1d", state) != 1)
+ goto parse_err;
+ if (s != NULL && strchr(s, '~') != NULL)
+ goto parse_err;
+
+ if ((*manuf = strdup(_manuf)) == NULL) {
+ warn("strdup");
+ goto err;
+ }
+ if ((*version = strdup(_version)) == NULL) {
+ warn("strdup");
+ goto err;
+ }
+ if ((*device = strdup(_device)) == NULL) {
+ warn("strdup");
+ goto err;
+ }
+ if (*manuf == NULL || *version == NULL || *device == NULL) {
+ warn("strdup");
+ goto err;
+ }
+
+ rc = 0;
+ err:
+ return rc;
+ parse_err:
+ warnc(0, "Invalid response: %*s", rv, buf);
+ return rc;
+}
+
+const char *
+strstate(int state)
+{
+ switch (state) {
+ case 0:
+ return "empty";
+ case 1:
+ return "filled";
+ case 2:
+ return "inactive";
+ default:
+ return "unknown";
+ }
+}
+
+int
+main(int ac, char **av)
+{
+ char *path = NULL;
+ int so = -1;
+ int nslot;
+ int i;
+
+ if (proc_arg(ac, av) < 0)
+ goto out;
+ if ((so = connect_to_pccardd(&path)) < 0)
+ goto out;
+ if ((nslot = get_slot_number(so)) < 0)
+ goto out;
+ if (slot_map == 0) {
+ printf("%d\n", nslot);
+ } else {
+ for (i = 0; i < nslot; i++) {
+ if ((slot_map & (1 << i))) {
+ char *manuf;
+ char *version;
+ char *device;
+ int state;
+
+ if (get_slot_info(so, i, &manuf, &version, &device,
+ &state) < 0)
+ goto out;
+ if (manuf == NULL || version == NULL || device == NULL)
+ goto out;
+ printf("%d~%s~%s~%s~%s\n",
+ i, manuf, version, device, strstate(state));
+ free(manuf);
+ free(version);
+ free(device);
+ }
+ }
+ }
+ out:
+ if (path) {
+ unlink(path);
+ free(path);
+ }
+ if (so >= 0)
+ close(so);
+ exit(0);
+}
diff --git a/usr.sbin/arp/Makefile b/usr.sbin/arp/Makefile
new file mode 100644
index 0000000..6ca9dd2
--- /dev/null
+++ b/usr.sbin/arp/Makefile
@@ -0,0 +1,9 @@
+# @(#)Makefile 8.2 (Berkeley) 4/18/94
+# $FreeBSD$
+
+PROG= arp
+MAN= arp.4 arp.8
+
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/arp/Makefile.depend b/usr.sbin/arp/Makefile.depend
new file mode 100644
index 0000000..54c1f6f
--- /dev/null
+++ b/usr.sbin/arp/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/arp/arp.4 b/usr.sbin/arp/arp.4
new file mode 100644
index 0000000..7bfa8ec
--- /dev/null
+++ b/usr.sbin/arp/arp.4
@@ -0,0 +1,235 @@
+.\" Copyright (c) 1985, 1986, 1988, 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.
+.\"
+.\" @(#)arp4.4 6.5 (Berkeley) 4/18/94
+.\" $FreeBSD$
+.\"
+.Dd November 5, 2013
+.Dt ARP 4
+.Os
+.Sh NAME
+.Nm arp
+.Nd Address Resolution Protocol
+.Sh SYNOPSIS
+.Cd "device ether"
+.Sh DESCRIPTION
+The Address Resolution Protocol (ARP) is used to dynamically
+map between Protocol Addresses (such as IP addresses) and
+Local Network Addresses (such as Ethernet addresses).
+This implementation maps IP addresses to Ethernet,
+ARCnet,
+or Token Ring addresses.
+It is used by all the Ethernet interface drivers.
+.Pp
+ARP caches Internet-Ethernet address mappings.
+When an interface requests a mapping for an address not in the cache,
+ARP queues the message which requires the mapping and broadcasts
+a message on the associated network requesting the address mapping.
+If a response is provided, the new mapping is cached and any pending
+message is transmitted.
+ARP will queue at most one packet while waiting for a response to a
+mapping request;
+only the most recently ``transmitted'' packet is kept.
+If the target host does not respond after several requests,
+the host is considered to be down allowing an error to be returned to
+transmission attempts.
+Further demand for this mapping causes ARP request retransmissions, that
+are ratelimited to one packet per second.
+The error is
+.Er EHOSTDOWN
+for a non-responding destination host, and
+.Er EHOSTUNREACH
+for a non-responding router.
+.Pp
+The ARP cache is stored in the system routing table as
+dynamically-created host routes.
+The route to a directly-attached Ethernet network is installed as a
+.Dq cloning
+route (one with the
+.Li RTF_CLONING
+flag set),
+causing routes to individual hosts on that network to be created on
+demand.
+These routes time out periodically (normally 20 minutes after validated;
+entries are not validated when not in use).
+.Pp
+ARP entries may be added, deleted or changed with the
+.Xr arp 8
+utility.
+Manually-added entries may be temporary or permanent,
+and may be
+.Dq published ,
+in which case the system will respond to ARP requests for that host
+as if it were the target of the request.
+.Pp
+In the past,
+ARP was used to negotiate the use of a trailer encapsulation.
+This is no longer supported.
+.Pp
+ARP watches passively for hosts impersonating the local host (i.e., a host
+which responds to an ARP mapping request for the local host's address).
+.Pp
+Proxy ARP is a feature whereby the local host will respond to requests
+for addresses other than itself, with its own address.
+Normally, proxy ARP in
+.Fx
+is set up on a host-by-host basis using the
+.Xr arp 8
+utility, by adding an entry for each host inside a given subnet for
+which proxying of ARP requests is desired.
+However, the
+.Dq "proxy all"
+feature causes the local host to act as a proxy for
+.Em all
+hosts reachable through some other network interface,
+different from the one the request came in from.
+It may be enabled by setting the
+.Xr sysctl 8
+MIB variable
+.Va net.link.ether.inet.proxyall
+to 1.
+.Sh MIB Variables
+The ARP protocol implements a number of configurable variables in
+.Va net.link.ether.inet
+branch
+of the
+.Xr sysctl 3
+MIB.
+.Bl -tag -width "log_arp_permanent_modify"
+.It Va allow_multicast
+Should the kernel install ARP entries with multicast bit set in
+the hardware address.
+Installing such entries is RFC 1812 violation, but some prorietary
+load balancing techniques require routers on network to do so.
+Turned off by default.
+.It Va log_arp_movements
+Should the kernel log movements of IP addresses from one hardware
+address to an other.
+See
+.Sx DIAGNOSTICS
+below.
+Turned on by default.
+.It Va log_arp_permanent_modify
+Should the kernel log attempts of remote host on network to modify a
+permanent ARP entry.
+See
+.Sx DIAGNOSTICS
+below.
+Turned on by default.
+.It Va log_arp_wrong_iface
+Should the kernel log attempts to insert an ARP entry on an interface
+when the IP network the address belongs to is connected to an other
+interface.
+See
+.Sx DIAGNOSTICS
+below.
+Turned on by default.
+.It Va max_log_per_second
+Limit number of remotely triggered logging events to a configured value
+per second.
+Default is 1 log message per second.
+.It Va max_age
+How long an ARP entry is held in the cache until it needs to be refreshed.
+Default is 1200 seconds.
+.It Va maxhold
+How many packets hold in the per-entry output queue while the entry
+is being resolved.
+Default is one packet.
+.It Va maxtries
+Number of retransmits before host is considered down and error is returned.
+Default is 5 tries.
+.It Va proxyall
+Enables ARP proxying for all hosts on net.
+Turned off by default.
+.It Va wait
+Lifetime of an incomplete ARP entry.
+Default is 20 seconds.
+.El
+.Sh DIAGNOSTICS
+.Bl -diag
+.It "arp: %x:%x:%x:%x:%x:%x is using my IP address %d.%d.%d.%d on %s!"
+ARP has discovered another host on the local network which responds to
+mapping requests for its own Internet address with a different Ethernet
+address, generally indicating that two hosts are attempting to use the
+same Internet address.
+.It "arp: link address is broadcast for IP address %d.%d.%d.%d!"
+ARP requested information for a host, and received an answer indicating
+that the host's ethernet address is the ethernet broadcast address.
+This indicates a misconfigured or broken device.
+.It "arp: %d.%d.%d.%d moved from %x:%x:%x:%x:%x:%x to %x:%x:%x:%x:%x:%x on %s"
+ARP had a cached value for the ethernet address of the referenced host,
+but received a reply indicating that the host is at a new address.
+This can happen normally when host hardware addresses change,
+or when a mobile node arrives or leaves the local subnet.
+It can also indicate a problem with proxy ARP.
+This message can only be issued if the sysctl
+.Va net.link.ether.inet.log_arp_movements
+is set to 1, which is the system's default behaviour.
+.It "arpresolve: can't allocate llinfo for %d.%d.%d.%d"
+The route for the referenced host points to a device upon which ARP is
+required, but ARP was unable to allocate a routing table entry in which
+to store the host's MAC address.
+This usually points to a misconfigured routing table.
+It can also occur if the kernel cannot allocate memory.
+.It "arp: %d.%d.%d.%d is on if0 but got reply from %x:%x:%x:%x:%x:%x on if1"
+Physical connections exist to the same logical IP network on both if0 and if1.
+It can also occur if an entry already exists in the ARP cache for the IP
+address above, and the cable has been disconnected from if0, then reconnected
+to if1.
+This message can only be issued if the sysctl
+.Va net.link.ether.inet.log_arp_wrong_iface
+is set to 1, which is the system's default behaviour.
+.It "arp: %x:%x:%x:%x:%x:%x attempts to modify permanent entry for %d.%d.%d.%d on %s"
+ARP has received an ARP reply that attempts to overwrite a permanent
+entry in the local ARP table.
+This error will only be logged if the sysctl
+.Va net.link.ether.inet.log_arp_permanent_modify
+is set to 1, which is the system's default behaviour.
+.It "arp: %x:%x:%x:%x:%x:%x is multicast"
+Kernel refused to install an entry with multicast hardware address.
+If you really want such addresses being installed, set the sysctl
+.Va net.link.ether.inet.allow_multicast
+to a positive value.
+.El
+.Sh SEE ALSO
+.Xr inet 4 ,
+.Xr route 4 ,
+.Xr arp 8 ,
+.Xr ifconfig 8 ,
+.Xr route 8 ,
+.Xr sysctl 8
+.Rs
+.%A Plummer, D.
+.%B "An Ethernet Address Resolution Protocol"
+.%T RFC826
+.Re
+.Rs
+.%A Leffler, S.J.
+.%A Karels, M.J.
+.%B "Trailer Encapsulations"
+.%T RFC893
+.Re
diff --git a/usr.sbin/arp/arp.8 b/usr.sbin/arp/arp.8
new file mode 100644
index 0000000..87c5c5f
--- /dev/null
+++ b/usr.sbin/arp/arp.8
@@ -0,0 +1,192 @@
+.\" 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.
+.\"
+.\" @(#)arp.8 8.1 (Berkeley) 6/6/93
+.\" $FreeBSD$
+.\"
+.Dd January 31, 2013
+.Dt ARP 8
+.Os
+.Sh NAME
+.Nm arp
+.Nd address resolution display and control
+.Sh SYNOPSIS
+.Nm
+.Op Fl n
+.Op Fl i Ar interface
+.Ar hostname
+.Nm
+.Op Fl n
+.Op Fl i Ar interface
+.Fl a
+.Nm
+.Fl d Ar hostname
+.Op Cm pub
+.Nm
+.Fl d
+.Op Fl i Ar interface
+.Fl a
+.Nm
+.Fl s Ar hostname ether_addr
+.Op Cm temp
+.Op Cm blackhole No \&| Cm reject
+.Op Cm pub
+.Nm
+.Fl S Ar hostname ether_addr
+.Op Cm temp
+.Op Cm blackhole No \&| Cm reject
+.Op Cm pub
+.Nm
+.Fl f Ar filename
+.Sh DESCRIPTION
+The
+.Nm
+utility displays and modifies the Internet-to-Ethernet address translation
+tables used by the address resolution protocol
+.Pq Xr arp 4 .
+With no flags, the program displays the current
+.Tn ARP
+entry for
+.Ar hostname .
+The host may be specified by name or by number,
+using Internet dot notation.
+.Pp
+Available options:
+.Bl -tag -width indent
+.It Fl a
+The program displays or deletes all of the current
+.Tn ARP
+entries.
+.It Fl d
+A super-user may delete an entry for the host called
+.Ar hostname
+with the
+.Fl d
+flag.
+If the
+.Cm pub
+keyword is specified, only the
+.Dq published
+.Tn ARP
+entry
+for this host will be deleted.
+.Pp
+Alternatively, the
+.Fl d
+flag may be combined with the
+.Fl a
+flag to delete all entries.
+.It Fl i Ar interface
+Limit the operation scope to the
+.Tn ARP
+entries on
+.Ar interface .
+Applicable only to the following operations:
+display one, display all, delete all.
+.It Fl n
+Show network addresses as numbers (normally
+.Nm
+attempts to display addresses symbolically).
+.It Fl s Ar hostname ether_addr
+Create an
+.Tn ARP
+entry for the host called
+.Ar hostname
+with the Ethernet address
+.Ar ether_addr .
+The Ethernet address is given as six hex bytes separated by colons.
+The entry will be permanent unless the word
+.Cm temp
+is given in the command.
+If the word
+.Cm pub
+is given, the entry will be
+.Dq published ;
+i.e., this system will
+act as an
+.Tn ARP
+server,
+responding to requests for
+.Ar hostname
+even though the host address is not its own.
+In this case the
+.Ar ether_addr
+can be given as
+.Cm auto
+in which case the interfaces on this host will be examined,
+and if one of them is found to occupy the same subnet, its
+Ethernet address will be used.
+.Pp
+If the
+.Cm reject
+keyword is specified the entry will be marked so that traffic to
+the host will be discarded and the sender will be notified the
+host is unreachable.
+The
+.Cm blackhole
+keyword is similar in that traffic is discarded but the sender is
+not notified.
+These can be used to block external traffic to a host without
+using a firewall.
+.It Fl S Ar hostname ether_addr
+Is just like
+.Fl s
+except any existing
+.Tn ARP
+entry for this host will be deleted first.
+.It Fl f Ar filename
+Cause the file
+.Ar filename
+to be read and multiple entries to be set in the
+.Tn ARP
+tables.
+Entries
+in the file should be of the form
+.Pp
+.Bd -ragged -offset indent -compact
+.Ar hostname ether_addr
+.Op Cm temp
+.Op Cm blackhole No \&| Cm reject
+.Op Cm pub
+.Ed
+.Pp
+with argument meanings as given above.
+Leading whitespace and empty lines are ignored.
+A
+.Ql #
+character will mark the rest of the line as a comment.
+.El
+.Sh SEE ALSO
+.Xr inet 3 ,
+.Xr arp 4 ,
+.Xr ifconfig 8 ,
+.Xr ndp 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.3 .
diff --git a/usr.sbin/arp/arp.c b/usr.sbin/arp/arp.c
new file mode 100644
index 0000000..eefde75
--- /dev/null
+++ b/usr.sbin/arp/arp.c
@@ -0,0 +1,882 @@
+/*
+ * Copyright (c) 1984, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Sun Microsystems, 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 char const copyright[] =
+"@(#) Copyright (c) 1984, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char const sccsid[] = "@(#)from: arp.c 8.2 (Berkeley) 1/2/94";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * arp - display, set, and delete arp table entries
+ */
+
+
+#include <sys/param.h>
+#include <sys/file.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/route.h>
+#include <net/iso88025.h>
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <netdb.h>
+#include <nlist.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+typedef void (action_fn)(struct sockaddr_dl *sdl,
+ struct sockaddr_in *s_in, struct rt_msghdr *rtm);
+
+static int search(u_long addr, action_fn *action);
+static action_fn print_entry;
+static action_fn nuke_entry;
+
+static int delete(char *host);
+static void usage(void);
+static int set(int argc, char **argv);
+static int get(char *host);
+static int file(char *name);
+static struct rt_msghdr *rtmsg(int cmd,
+ struct sockaddr_in *dst, struct sockaddr_dl *sdl);
+static int get_ether_addr(in_addr_t ipaddr, struct ether_addr *hwaddr);
+static struct sockaddr_in *getaddr(char *host);
+static int valid_type(int type);
+
+static int nflag; /* no reverse dns lookups */
+static char *rifname;
+
+static time_t expire_time;
+static int flags, doing_proxy;
+
+struct if_nameindex *ifnameindex;
+
+/* which function we're supposed to do */
+#define F_GET 1
+#define F_SET 2
+#define F_FILESET 3
+#define F_REPLACE 4
+#define F_DELETE 5
+
+#define SETFUNC(f) { if (func) usage(); func = (f); }
+
+int
+main(int argc, char *argv[])
+{
+ int ch, func = 0;
+ int rtn = 0;
+ int aflag = 0; /* do it for all entries */
+
+ while ((ch = getopt(argc, argv, "andfsSi:")) != -1)
+ switch(ch) {
+ case 'a':
+ aflag = 1;
+ break;
+ case 'd':
+ SETFUNC(F_DELETE);
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'S':
+ SETFUNC(F_REPLACE);
+ break;
+ case 's':
+ SETFUNC(F_SET);
+ break;
+ case 'f' :
+ SETFUNC(F_FILESET);
+ break;
+ case 'i':
+ rifname = optarg;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!func)
+ func = F_GET;
+ if (rifname) {
+ if (func != F_GET && !(func == F_DELETE && aflag))
+ errx(1, "-i not applicable to this operation");
+ if (if_nametoindex(rifname) == 0) {
+ if (errno == ENXIO)
+ errx(1, "interface %s does not exist", rifname);
+ else
+ err(1, "if_nametoindex(%s)", rifname);
+ }
+ }
+ switch (func) {
+ case F_GET:
+ if (aflag) {
+ if (argc != 0)
+ usage();
+ search(0, print_entry);
+ } else {
+ if (argc != 1)
+ usage();
+ rtn = get(argv[0]);
+ }
+ break;
+ case F_SET:
+ case F_REPLACE:
+ if (argc < 2 || argc > 6)
+ usage();
+ if (func == F_REPLACE)
+ (void)delete(argv[0]);
+ rtn = set(argc, argv) ? 1 : 0;
+ break;
+ case F_DELETE:
+ if (aflag) {
+ if (argc != 0)
+ usage();
+ search(0, nuke_entry);
+ } else {
+ if (argc != 1)
+ usage();
+ rtn = delete(argv[0]);
+ }
+ break;
+ case F_FILESET:
+ if (argc != 1)
+ usage();
+ rtn = file(argv[0]);
+ break;
+ }
+
+ if (ifnameindex != NULL)
+ if_freenameindex(ifnameindex);
+
+ return (rtn);
+}
+
+/*
+ * Process a file to set standard arp entries
+ */
+static int
+file(char *name)
+{
+ FILE *fp;
+ int i, retval;
+ char line[100], arg[5][50], *args[5], *p;
+
+ if ((fp = fopen(name, "r")) == NULL)
+ err(1, "cannot open %s", name);
+ args[0] = &arg[0][0];
+ args[1] = &arg[1][0];
+ args[2] = &arg[2][0];
+ args[3] = &arg[3][0];
+ args[4] = &arg[4][0];
+ retval = 0;
+ while(fgets(line, sizeof(line), fp) != NULL) {
+ if ((p = strchr(line, '#')) != NULL)
+ *p = '\0';
+ for (p = line; isblank(*p); p++);
+ if (*p == '\n' || *p == '\0')
+ continue;
+ i = sscanf(p, "%49s %49s %49s %49s %49s", arg[0], arg[1],
+ arg[2], arg[3], arg[4]);
+ if (i < 2) {
+ warnx("bad line: %s", line);
+ retval = 1;
+ continue;
+ }
+ if (set(i, args))
+ retval = 1;
+ }
+ fclose(fp);
+ return (retval);
+}
+
+/*
+ * Given a hostname, fills up a (static) struct sockaddr_in with
+ * the address of the host and returns a pointer to the
+ * structure.
+ */
+static struct sockaddr_in *
+getaddr(char *host)
+{
+ struct hostent *hp;
+ static struct sockaddr_in reply;
+
+ bzero(&reply, sizeof(reply));
+ reply.sin_len = sizeof(reply);
+ reply.sin_family = AF_INET;
+ reply.sin_addr.s_addr = inet_addr(host);
+ if (reply.sin_addr.s_addr == INADDR_NONE) {
+ if (!(hp = gethostbyname(host))) {
+ warnx("%s: %s", host, hstrerror(h_errno));
+ return (NULL);
+ }
+ bcopy((char *)hp->h_addr, (char *)&reply.sin_addr,
+ sizeof reply.sin_addr);
+ }
+ return (&reply);
+}
+
+/*
+ * Returns true if the type is a valid one for ARP.
+ */
+static int
+valid_type(int type)
+{
+
+ switch (type) {
+ case IFT_ETHER:
+ case IFT_FDDI:
+ case IFT_INFINIBAND:
+ case IFT_ISO88023:
+ case IFT_ISO88024:
+ case IFT_ISO88025:
+ case IFT_L2VLAN:
+ case IFT_BRIDGE:
+ return (1);
+ default:
+ return (0);
+ }
+}
+
+/*
+ * Set an individual arp entry
+ */
+static int
+set(int argc, char **argv)
+{
+ struct sockaddr_in *addr;
+ struct sockaddr_in *dst; /* what are we looking for */
+ struct sockaddr_dl *sdl;
+ struct rt_msghdr *rtm;
+ struct ether_addr *ea;
+ char *host = argv[0], *eaddr = argv[1];
+ struct sockaddr_dl sdl_m;
+
+ argc -= 2;
+ argv += 2;
+
+ bzero(&sdl_m, sizeof(sdl_m));
+ sdl_m.sdl_len = sizeof(sdl_m);
+ sdl_m.sdl_family = AF_LINK;
+
+ dst = getaddr(host);
+ if (dst == NULL)
+ return (1);
+ doing_proxy = flags = expire_time = 0;
+ while (argc-- > 0) {
+ if (strncmp(argv[0], "temp", 4) == 0) {
+ struct timespec tp;
+ int max_age;
+ size_t len = sizeof(max_age);
+
+ clock_gettime(CLOCK_MONOTONIC, &tp);
+ if (sysctlbyname("net.link.ether.inet.max_age",
+ &max_age, &len, NULL, 0) != 0)
+ err(1, "sysctlbyname");
+ expire_time = tp.tv_sec + max_age;
+ } else if (strncmp(argv[0], "pub", 3) == 0) {
+ flags |= RTF_ANNOUNCE;
+ doing_proxy = 1;
+ if (argc && strncmp(argv[1], "only", 3) == 0) {
+ /*
+ * Compatibility: in pre FreeBSD 8 times
+ * the "only" keyword used to mean that
+ * an ARP entry should be announced, but
+ * not installed into routing table.
+ */
+ argc--; argv++;
+ }
+ } else if (strncmp(argv[0], "blackhole", 9) == 0) {
+ if (flags & RTF_REJECT) {
+ printf("Choose one of blackhole or reject, not both.\n");
+ }
+ flags |= RTF_BLACKHOLE;
+ } else if (strncmp(argv[0], "reject", 6) == 0) {
+ if (flags & RTF_BLACKHOLE) {
+ printf("Choose one of blackhole or reject, not both.\n");
+ }
+ flags |= RTF_REJECT;
+ } else if (strncmp(argv[0], "trail", 5) == 0) {
+ /* XXX deprecated and undocumented feature */
+ printf("%s: Sending trailers is no longer supported\n",
+ host);
+ }
+ argv++;
+ }
+ ea = (struct ether_addr *)LLADDR(&sdl_m);
+ if (doing_proxy && !strcmp(eaddr, "auto")) {
+ if (!get_ether_addr(dst->sin_addr.s_addr, ea)) {
+ printf("no interface found for %s\n",
+ inet_ntoa(dst->sin_addr));
+ return (1);
+ }
+ sdl_m.sdl_alen = ETHER_ADDR_LEN;
+ } else {
+ struct ether_addr *ea1 = ether_aton(eaddr);
+
+ if (ea1 == NULL) {
+ warnx("invalid Ethernet address '%s'", eaddr);
+ return (1);
+ } else {
+ *ea = *ea1;
+ sdl_m.sdl_alen = ETHER_ADDR_LEN;
+ }
+ }
+
+ /*
+ * In the case a proxy-arp entry is being added for
+ * a remote end point, the RTF_ANNOUNCE flag in the
+ * RTM_GET command is an indication to the kernel
+ * routing code that the interface associated with
+ * the prefix route covering the local end of the
+ * PPP link should be returned, on which ARP applies.
+ */
+ rtm = rtmsg(RTM_GET, dst, &sdl_m);
+ if (rtm == NULL) {
+ warn("%s", host);
+ return (1);
+ }
+ addr = (struct sockaddr_in *)(rtm + 1);
+ sdl = (struct sockaddr_dl *)(SA_SIZE(addr) + (char *)addr);
+
+ if ((sdl->sdl_family != AF_LINK) ||
+ (rtm->rtm_flags & RTF_GATEWAY) ||
+ !valid_type(sdl->sdl_type)) {
+ printf("cannot intuit interface index and type for %s\n", host);
+ return (1);
+ }
+ sdl_m.sdl_type = sdl->sdl_type;
+ sdl_m.sdl_index = sdl->sdl_index;
+ return (rtmsg(RTM_ADD, dst, &sdl_m) == NULL);
+}
+
+/*
+ * Display an individual arp entry
+ */
+static int
+get(char *host)
+{
+ struct sockaddr_in *addr;
+
+ addr = getaddr(host);
+ if (addr == NULL)
+ return (1);
+ if (0 == search(addr->sin_addr.s_addr, print_entry)) {
+ printf("%s (%s) -- no entry",
+ host, inet_ntoa(addr->sin_addr));
+ if (rifname)
+ printf(" on %s", rifname);
+ printf("\n");
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * Delete an arp entry
+ */
+static int
+delete(char *host)
+{
+ struct sockaddr_in *addr, *dst;
+ struct rt_msghdr *rtm;
+ struct sockaddr_dl *sdl;
+ struct sockaddr_dl sdl_m;
+
+ dst = getaddr(host);
+ if (dst == NULL)
+ return (1);
+
+ /*
+ * Perform a regular entry delete first.
+ */
+ flags &= ~RTF_ANNOUNCE;
+
+ /*
+ * setup the data structure to notify the kernel
+ * it is the ARP entry the RTM_GET is interested
+ * in
+ */
+ bzero(&sdl_m, sizeof(sdl_m));
+ sdl_m.sdl_len = sizeof(sdl_m);
+ sdl_m.sdl_family = AF_LINK;
+
+ for (;;) { /* try twice */
+ rtm = rtmsg(RTM_GET, dst, &sdl_m);
+ if (rtm == NULL) {
+ warn("%s", host);
+ return (1);
+ }
+ addr = (struct sockaddr_in *)(rtm + 1);
+ sdl = (struct sockaddr_dl *)(SA_SIZE(addr) + (char *)addr);
+
+ /*
+ * With the new L2/L3 restructure, the route
+ * returned is a prefix route. The important
+ * piece of information from the previous
+ * RTM_GET is the interface index. In the
+ * case of ECMP, the kernel will traverse
+ * the route group for the given entry.
+ */
+ if (sdl->sdl_family == AF_LINK &&
+ !(rtm->rtm_flags & RTF_GATEWAY) &&
+ valid_type(sdl->sdl_type) ) {
+ addr->sin_addr.s_addr = dst->sin_addr.s_addr;
+ break;
+ }
+
+ /*
+ * Regualar entry delete failed, now check if there
+ * is a proxy-arp entry to remove.
+ */
+ if (flags & RTF_ANNOUNCE) {
+ fprintf(stderr, "delete: cannot locate %s\n",host);
+ return (1);
+ }
+
+ flags |= RTF_ANNOUNCE;
+ }
+ rtm->rtm_flags |= RTF_LLDATA;
+ if (rtmsg(RTM_DELETE, dst, NULL) != NULL) {
+ printf("%s (%s) deleted\n", host, inet_ntoa(addr->sin_addr));
+ return (0);
+ }
+ return (1);
+}
+
+
+/*
+ * Search the arp table and do some action on matching entries
+ */
+static int
+search(u_long addr, action_fn *action)
+{
+ int mib[6];
+ size_t needed;
+ char *lim, *buf, *next;
+ struct rt_msghdr *rtm;
+ struct sockaddr_in *sin2;
+ struct sockaddr_dl *sdl;
+ char ifname[IF_NAMESIZE];
+ int st, found_entry = 0;
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = AF_INET;
+ mib[4] = NET_RT_FLAGS;
+#ifdef RTF_LLINFO
+ mib[5] = RTF_LLINFO;
+#else
+ mib[5] = 0;
+#endif
+ if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0)
+ err(1, "route-sysctl-estimate");
+ if (needed == 0) /* empty table */
+ return 0;
+ buf = NULL;
+ for (;;) {
+ buf = reallocf(buf, needed);
+ if (buf == NULL)
+ errx(1, "could not reallocate memory");
+ st = sysctl(mib, 6, buf, &needed, NULL, 0);
+ if (st == 0 || errno != ENOMEM)
+ break;
+ needed += needed / 8;
+ }
+ if (st == -1)
+ err(1, "actual retrieval of routing table");
+ lim = buf + needed;
+ for (next = buf; next < lim; next += rtm->rtm_msglen) {
+ rtm = (struct rt_msghdr *)next;
+ sin2 = (struct sockaddr_in *)(rtm + 1);
+ sdl = (struct sockaddr_dl *)((char *)sin2 + SA_SIZE(sin2));
+ if (rifname && if_indextoname(sdl->sdl_index, ifname) &&
+ strcmp(ifname, rifname))
+ continue;
+ if (addr) {
+ if (addr != sin2->sin_addr.s_addr)
+ continue;
+ found_entry = 1;
+ }
+ (*action)(sdl, sin2, rtm);
+ }
+ free(buf);
+ return (found_entry);
+}
+
+/*
+ * Display an arp entry
+ */
+
+static void
+print_entry(struct sockaddr_dl *sdl,
+ struct sockaddr_in *addr, struct rt_msghdr *rtm)
+{
+ const char *host;
+ struct hostent *hp;
+ struct iso88025_sockaddr_dl_data *trld;
+ struct if_nameindex *p;
+ int seg;
+
+ if (ifnameindex == NULL)
+ if ((ifnameindex = if_nameindex()) == NULL)
+ err(1, "cannot retrieve interface names");
+
+ if (nflag == 0)
+ hp = gethostbyaddr((caddr_t)&(addr->sin_addr),
+ sizeof addr->sin_addr, AF_INET);
+ else
+ hp = 0;
+ if (hp)
+ host = hp->h_name;
+ else {
+ host = "?";
+ if (h_errno == TRY_AGAIN)
+ nflag = 1;
+ }
+ printf("%s (%s) at ", host, inet_ntoa(addr->sin_addr));
+ if (sdl->sdl_alen) {
+ if ((sdl->sdl_type == IFT_ETHER ||
+ sdl->sdl_type == IFT_L2VLAN ||
+ sdl->sdl_type == IFT_BRIDGE) &&
+ sdl->sdl_alen == ETHER_ADDR_LEN)
+ printf("%s", ether_ntoa((struct ether_addr *)LLADDR(sdl)));
+ else {
+ int n = sdl->sdl_nlen > 0 ? sdl->sdl_nlen + 1 : 0;
+
+ printf("%s", link_ntoa(sdl) + n);
+ }
+ } else
+ printf("(incomplete)");
+
+ for (p = ifnameindex; p && ifnameindex->if_index &&
+ ifnameindex->if_name; p++) {
+ if (p->if_index == sdl->sdl_index) {
+ printf(" on %s", p->if_name);
+ break;
+ }
+ }
+
+ if (rtm->rtm_rmx.rmx_expire == 0)
+ printf(" permanent");
+ else {
+ static struct timespec tp;
+ if (tp.tv_sec == 0)
+ clock_gettime(CLOCK_MONOTONIC, &tp);
+ if ((expire_time = rtm->rtm_rmx.rmx_expire - tp.tv_sec) > 0)
+ printf(" expires in %d seconds", (int)expire_time);
+ else
+ printf(" expired");
+ }
+ if (rtm->rtm_flags & RTF_ANNOUNCE)
+ printf(" published");
+ switch(sdl->sdl_type) {
+ case IFT_ETHER:
+ printf(" [ethernet]");
+ break;
+ case IFT_ISO88025:
+ printf(" [token-ring]");
+ trld = SDL_ISO88025(sdl);
+ if (trld->trld_rcf != 0) {
+ printf(" rt=%x", ntohs(trld->trld_rcf));
+ for (seg = 0;
+ seg < ((TR_RCF_RIFLEN(trld->trld_rcf) - 2 ) / 2);
+ seg++)
+ printf(":%x", ntohs(*(trld->trld_route[seg])));
+ }
+ break;
+ case IFT_FDDI:
+ printf(" [fddi]");
+ break;
+ case IFT_ATM:
+ printf(" [atm]");
+ break;
+ case IFT_L2VLAN:
+ printf(" [vlan]");
+ break;
+ case IFT_IEEE1394:
+ printf(" [firewire]");
+ break;
+ case IFT_BRIDGE:
+ printf(" [bridge]");
+ break;
+ case IFT_INFINIBAND:
+ printf(" [infiniband]");
+ break;
+ default:
+ break;
+ }
+
+ printf("\n");
+
+}
+
+/*
+ * Nuke an arp entry
+ */
+static void
+nuke_entry(struct sockaddr_dl *sdl __unused,
+ struct sockaddr_in *addr, struct rt_msghdr *rtm)
+{
+ char ip[20];
+
+ if (rtm->rtm_flags & RTF_PINNED)
+ return;
+
+ snprintf(ip, sizeof(ip), "%s", inet_ntoa(addr->sin_addr));
+ delete(ip);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
+ "usage: arp [-n] [-i interface] hostname",
+ " arp [-n] [-i interface] -a",
+ " arp -d hostname [pub]",
+ " arp -d [-i interface] -a",
+ " arp -s hostname ether_addr [temp] [reject | blackhole] [pub [only]]",
+ " arp -S hostname ether_addr [temp] [reject | blackhole] [pub [only]]",
+ " arp -f filename");
+ exit(1);
+}
+
+static struct rt_msghdr *
+rtmsg(int cmd, struct sockaddr_in *dst, struct sockaddr_dl *sdl)
+{
+ static int seq;
+ int rlen;
+ int l;
+ struct sockaddr_in so_mask, *som = &so_mask;
+ static int s = -1;
+ static pid_t pid;
+
+ static struct {
+ struct rt_msghdr m_rtm;
+ char m_space[512];
+ } m_rtmsg;
+
+ struct rt_msghdr *rtm = &m_rtmsg.m_rtm;
+ char *cp = m_rtmsg.m_space;
+
+ if (s < 0) { /* first time: open socket, get pid */
+ s = socket(PF_ROUTE, SOCK_RAW, 0);
+ if (s < 0)
+ err(1, "socket");
+ pid = getpid();
+ }
+ bzero(&so_mask, sizeof(so_mask));
+ so_mask.sin_len = 8;
+ so_mask.sin_addr.s_addr = 0xffffffff;
+
+ errno = 0;
+ /*
+ * XXX RTM_DELETE relies on a previous RTM_GET to fill the buffer
+ * appropriately.
+ */
+ if (cmd == RTM_DELETE)
+ goto doit;
+ bzero((char *)&m_rtmsg, sizeof(m_rtmsg));
+ rtm->rtm_flags = flags;
+ rtm->rtm_version = RTM_VERSION;
+
+ switch (cmd) {
+ default:
+ errx(1, "internal wrong cmd");
+ case RTM_ADD:
+ rtm->rtm_addrs |= RTA_GATEWAY;
+ rtm->rtm_rmx.rmx_expire = expire_time;
+ rtm->rtm_inits = RTV_EXPIRE;
+ rtm->rtm_flags |= (RTF_HOST | RTF_STATIC | RTF_LLDATA);
+ if (doing_proxy) {
+ rtm->rtm_addrs |= RTA_NETMASK;
+ rtm->rtm_flags &= ~RTF_HOST;
+ }
+ /* FALLTHROUGH */
+ case RTM_GET:
+ rtm->rtm_addrs |= RTA_DST;
+ }
+#define NEXTADDR(w, s) \
+ do { \
+ if ((s) != NULL && rtm->rtm_addrs & (w)) { \
+ bcopy((s), cp, sizeof(*(s))); \
+ cp += SA_SIZE(s); \
+ } \
+ } while (0)
+
+ NEXTADDR(RTA_DST, dst);
+ NEXTADDR(RTA_GATEWAY, sdl);
+ NEXTADDR(RTA_NETMASK, som);
+
+ rtm->rtm_msglen = cp - (char *)&m_rtmsg;
+doit:
+ l = rtm->rtm_msglen;
+ rtm->rtm_seq = ++seq;
+ rtm->rtm_type = cmd;
+ if ((rlen = write(s, (char *)&m_rtmsg, l)) < 0) {
+ if (errno != ESRCH || cmd != RTM_DELETE) {
+ warn("writing to routing socket");
+ return (NULL);
+ }
+ }
+ do {
+ l = read(s, (char *)&m_rtmsg, sizeof(m_rtmsg));
+ } while (l > 0 && (rtm->rtm_seq != seq || rtm->rtm_pid != pid));
+ if (l < 0)
+ warn("read from routing socket");
+ return (rtm);
+}
+
+/*
+ * get_ether_addr - get the hardware address of an interface on the
+ * the same subnet as ipaddr.
+ */
+#define MAX_IFS 32
+
+static int
+get_ether_addr(in_addr_t ipaddr, struct ether_addr *hwaddr)
+{
+ struct ifreq *ifr, *ifend, *ifp;
+ in_addr_t ina, mask;
+ struct sockaddr_dl *dla;
+ struct ifreq ifreq;
+ struct ifconf ifc;
+ struct ifreq ifs[MAX_IFS];
+ int sock;
+ int retval = 0;
+
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0)
+ err(1, "socket");
+
+ ifc.ifc_len = sizeof(ifs);
+ ifc.ifc_req = ifs;
+ if (ioctl(sock, SIOCGIFCONF, &ifc) < 0) {
+ warnx("ioctl(SIOCGIFCONF)");
+ goto done;
+ }
+
+#define NEXTIFR(i) \
+ ((struct ifreq *)((char *)&(i)->ifr_addr \
+ + MAX((i)->ifr_addr.sa_len, sizeof((i)->ifr_addr))) )
+
+ /*
+ * Scan through looking for an interface with an Internet
+ * address on the same subnet as `ipaddr'.
+ */
+ ifend = (struct ifreq *)(ifc.ifc_buf + ifc.ifc_len);
+ for (ifr = ifc.ifc_req; ifr < ifend; ifr = NEXTIFR(ifr) ) {
+ if (ifr->ifr_addr.sa_family != AF_INET)
+ continue;
+ strncpy(ifreq.ifr_name, ifr->ifr_name,
+ sizeof(ifreq.ifr_name));
+ ifreq.ifr_addr = ifr->ifr_addr;
+ /*
+ * Check that the interface is up,
+ * and not point-to-point or loopback.
+ */
+ if (ioctl(sock, SIOCGIFFLAGS, &ifreq) < 0)
+ continue;
+ if ((ifreq.ifr_flags &
+ (IFF_UP|IFF_BROADCAST|IFF_POINTOPOINT|
+ IFF_LOOPBACK|IFF_NOARP))
+ != (IFF_UP|IFF_BROADCAST))
+ continue;
+ /*
+ * Get its netmask and check that it's on
+ * the right subnet.
+ */
+ if (ioctl(sock, SIOCGIFNETMASK, &ifreq) < 0)
+ continue;
+ mask = ((struct sockaddr_in *)
+ &ifreq.ifr_addr)->sin_addr.s_addr;
+ ina = ((struct sockaddr_in *)
+ &ifr->ifr_addr)->sin_addr.s_addr;
+ if ((ipaddr & mask) == (ina & mask))
+ break; /* ok, we got it! */
+ }
+
+ if (ifr >= ifend)
+ goto done;
+
+ /*
+ * Now scan through again looking for a link-level address
+ * for this interface.
+ */
+ ifp = ifr;
+ for (ifr = ifc.ifc_req; ifr < ifend; ifr = NEXTIFR(ifr))
+ if (strcmp(ifp->ifr_name, ifr->ifr_name) == 0 &&
+ ifr->ifr_addr.sa_family == AF_LINK)
+ break;
+ if (ifr >= ifend)
+ goto done;
+ /*
+ * Found the link-level address - copy it out
+ */
+ dla = (struct sockaddr_dl *) &ifr->ifr_addr;
+ memcpy(hwaddr, LLADDR(dla), dla->sdl_alen);
+ printf("using interface %s for proxy with address ",
+ ifp->ifr_name);
+ printf("%s\n", ether_ntoa(hwaddr));
+ retval = dla->sdl_alen;
+done:
+ close(sock);
+ return (retval);
+}
diff --git a/usr.sbin/asf/Makefile b/usr.sbin/asf/Makefile
new file mode 100644
index 0000000..49f9305
--- /dev/null
+++ b/usr.sbin/asf/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PROG= asf
+SRCS= asf.c asf_kld.c asf_kvm.c asf_prog.c
+MAN= asf.8
+
+LIBADD= kvm
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/asf/Makefile.depend b/usr.sbin/asf/Makefile.depend
new file mode 100644
index 0000000..34582cd
--- /dev/null
+++ b/usr.sbin/asf/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libelf \
+ lib/libkvm \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/asf/asf.8 b/usr.sbin/asf/asf.8
new file mode 100644
index 0000000..3928137
--- /dev/null
+++ b/usr.sbin/asf/asf.8
@@ -0,0 +1,179 @@
+.\" Copyright (c) 2003 Greg Lehey. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 Greg Lehey ``as is'' and
+.\" any express or implied warranties, including, but not limited to, the
+.\" implied warranties of merchantability and fitness for a particular purpose
+.\" are disclaimed. in no event shall Greg Lehey be liable
+.\" for any direct, indirect, incidental, special, exemplary, or consequential
+.\" damages (including, but not limited to, procurement of substitute goods
+.\" or services; loss of use, data, or profits; or business interruption)
+.\" however caused and on any theory of liability, whether in contract, strict
+.\" liability, or tort (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 20, 2006
+.Dt ASF 8
+.Os
+.Sh NAME
+.Nm asf
+.Nd add symbol files
+.Sh SYNOPSIS
+.Nm
+.Op Fl afKksVx
+.Op Fl M Ar core
+.Op Fl N Ar system
+.Op Fl o Ar outfile
+.Op Fl X Ar suffix
+.Op Ar modules-path Op Ar outfile
+.Sh DESCRIPTION
+By default,
+.Nm
+reads
+.Xr kldstat 8
+output from standard input and writes to the
+.Pa .asf
+file a list of
+.Xr gdb 1
+commands to add symbol files from KLDs in subdirectories of the subdirectory
+.Pa modules
+of the current directory, which is intended to be a kernel build directory.
+This allows
+.Xr gdb 1
+to load the symbols into the debugging environment.
+.Pp
+An optional
+.Ar modules-path
+argument can specify a semicolon-separated list of directory pathnames
+similar to the
+.Va kern.module_path
+sysctl.
+Each directory in the list will be searched in turn for modules.
+The default list consists of just one element,
+.Pa modules ,
+which is suitable if the current directory is a kernel build directory.
+.Pp
+If
+.Ar outfile
+is specified,
+.Nm
+writes to it instead of
+.Pa .asf .
+If
+.Ar outfile
+is a single dash
+.Pq Sq Fl ,
+standard output is used.
+.Sh OPTIONS
+The following options modify the function of
+.Nm :
+.Bl -tag -width indent
+.It Fl a
+When writing to an explicit
+.Ar outfile ,
+append to the file rather than overwriting it.
+.It Fl f
+Instead of trying to simplistically guess the path for each module, perform
+a traversal in the same way that
+.Xr find 1
+does to locate an exact path for each module, no matter where in
+.Ar modules-path
+it is located.
+.It Fl K
+Instead of reading from standard input, use the conventional
+system interface to get the list of modules currently loaded.
+.It Fl k
+Instead of reading from standard input, start a
+.Xr kldstat 8
+and read the information from it.
+.It Fl M
+Specify the core file for
+.Xr kvm 3 .
+Implies
+.Fl V .
+.It Fl N
+Specify the system file for
+.Xr kvm 3 .
+Implies
+.Fl V .
+.It Fl o
+Specify the file for
+.Nm
+to write or append its output to.
+If
+.Ar outfile
+is a single dash
+.Pq Sq Fl ,
+standard output is used.
+.It Fl s
+Do not prepend a (guessed) subdirectory of the module path.
+.It Fl V
+Instead of reading from standard input, use the
+.Xr kvm 3
+interface to get the list of modules.
+This interface allows for inspecting system crash dumps,
+as well as the live system.
+The
+.Fl M
+and
+.Fl N
+options will be of use if inspecting a crash dump.
+Elevated privileges, e.g., those of a superuser,
+may be needed to use this option.
+.It Fl X
+Add
+.Ar suffix
+to the list of suffixes
+.Nm
+tries to append to KLD file names.
+The default list consists of
+.Pa .debug ,
+.Pa .symbols ,
+and the null suffix.
+The null suffix always stays at the list tail, after the suffix added.
+Should it be needed in the middle of the list,
+a blank suffix can be specified to
+.Fl X
+instead.
+.It Fl x
+Clear the list of suffixes
+.Nm
+tries to append to KLD file names.
+Only the null suffix is left in the list.
+.El
+.Sh EXAMPLES
+To add symbol files from the system search path specified by the
+.Va kern.module_path
+sysctl, the following command can be used:
+.Pp
+.Dl asf -s `sysctl -n kern.module_path`
+.Sh SEE ALSO
+.Xr gdb 1 ,
+.Xr kvm 3 ,
+.Xr kld 4 ,
+.Xr kldstat 8 ,
+.Xr sysctl 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 5.2 .
+.Sh AUTHORS
+.An Greg Lehey Aq Mt grog@FreeBSD.org
+.Sh BUGS
+Module paths are guessed in a rather naive way by default.
+It is likely to lag behind the changes to the build tree layout.
+Using
+.Fl f
+is recommended.
diff --git a/usr.sbin/asf/asf.c b/usr.sbin/asf/asf.c
new file mode 100644
index 0000000..c913e3d
--- /dev/null
+++ b/usr.sbin/asf/asf.c
@@ -0,0 +1,427 @@
+/*-
+ * Copyright (c) 2002, 2003 Greg Lehey
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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.
+ */
+/* $Id: asf.c,v 1.4 2003/05/04 02:55:20 grog Exp grog $ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fts.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "asf.h"
+
+struct kfile {
+ char *name;
+ caddr_t addr;
+ int seen;
+ STAILQ_ENTRY(kfile) link;
+};
+
+static STAILQ_HEAD(,kfile) kfile_head = STAILQ_HEAD_INITIALIZER(kfile_head);
+
+void
+kfile_add(const char *name, caddr_t addr)
+{
+ struct kfile *kfp;
+
+ if ((kfp = malloc(sizeof(*kfp))) == NULL ||
+ (kfp->name = strdup(name)) == NULL)
+ errx(2, "out of memory");
+ kfp->addr = addr;
+ kfp->seen = 0;
+ STAILQ_INSERT_TAIL(&kfile_head, kfp, link);
+}
+
+static struct kfile *
+kfile_find(const char *name)
+{
+ struct kfile *kfp;
+
+ STAILQ_FOREACH(kfp, &kfile_head, link)
+ if (strcmp(kfp->name, name) == 0)
+ return (kfp); /* found */
+
+ return (NULL); /* not found */
+}
+
+static int
+kfile_allseen(void)
+{
+ struct kfile *kfp;
+
+ STAILQ_FOREACH(kfp, &kfile_head, link)
+ if (!kfp->seen)
+ return (0); /* at least one unseen */
+
+ return (1); /* all seen */
+}
+
+static int
+kfile_empty(void)
+{
+ return (STAILQ_EMPTY(&kfile_head));
+}
+
+/*
+ * Take a blank separated list of tokens and turn it into a list of
+ * individual nul-delimited strings. Build a list of pointers at
+ * token, which must have enough space for the tokens. Return the
+ * number of tokens, or -1 on error (typically a missing string
+ * delimiter).
+ */
+int
+tokenize(char *cptr, char *token[], int maxtoken)
+{
+ char delim; /* delimiter to search for */
+ int tokennr; /* index of this token */
+
+ for (tokennr = 0; tokennr < maxtoken;) {
+ while (isspace(*cptr))
+ cptr++; /* skip initial white space */
+ if ((*cptr == '\0') || (*cptr == '\n')
+ || (*cptr == '#')) /* end of line */
+ return tokennr; /* return number of tokens found */
+ delim = *cptr;
+ token[tokennr] = cptr; /* point to it */
+ tokennr++; /* one more */
+ if (tokennr == maxtoken) /* run off the end? */
+ return tokennr;
+ if ((delim == '\'') || (delim == '"')) { /* delimitered */
+ for (;;) {
+ cptr++;
+ if ((*cptr == delim)
+ && (cptr[-1] != '\\')) { /* found the partner */
+ cptr++; /* move on past */
+ if (!isspace(*cptr)) /* no space after closing quote */
+ return -1;
+ *cptr++ = '\0'; /* delimit */
+ } else if ((*cptr == '\0')
+ || (*cptr == '\n')) /* end of line */
+ return -1;
+ }
+ } else { /* not quoted */
+ while ((*cptr != '\0') && (!isspace(*cptr)) && (*cptr != '\n'))
+ cptr++;
+ if (*cptr != '\0') /* not end of the line, */
+ *cptr++ = '\0'; /* delimit and move to the next */
+ }
+ }
+ return maxtoken; /* can't get here */
+}
+
+static void
+doobj(const char *path, caddr_t addr, FILE *out)
+{
+ uintmax_t base = (uintptr_t)addr;
+ uintmax_t textaddr = 0;
+ uintmax_t dataaddr = 0;
+ uintmax_t bssaddr = 0;
+ uintmax_t *up;
+ int octokens;
+ char *octoken[MAXTOKEN];
+ char ocbuf[LINE_MAX + PATH_MAX];
+ FILE *objcopy;
+
+ snprintf(ocbuf, sizeof(ocbuf),
+ "/usr/bin/objdump --section-headers %s", path);
+ if ((objcopy = popen(ocbuf, "r")) == NULL)
+ err(2, "can't start %s", ocbuf);
+ while (fgets(ocbuf, sizeof(ocbuf), objcopy)) {
+ octokens = tokenize(ocbuf, octoken, MAXTOKEN);
+ if (octokens <= 1)
+ continue;
+ up = NULL;
+ if (strcmp(octoken[1], ".text") == 0)
+ up = &textaddr;
+ else if (strcmp(octoken[1], ".data") == 0)
+ up = &dataaddr;
+ else if (strcmp(octoken[1], ".bss") == 0)
+ up = &bssaddr;
+ if (up == NULL)
+ continue;
+ *up = strtoumax(octoken[3], NULL, 16) + base;
+ }
+ if (textaddr) { /* we must have a text address */
+ fprintf(out, "add-symbol-file %s 0x%jx", path, textaddr);
+ if (dataaddr)
+ fprintf(out, " -s .data 0x%jx", dataaddr);
+ if (bssaddr)
+ fprintf(out, " -s .bss 0x%jx", bssaddr);
+ fprintf(out, "\n");
+ }
+}
+
+static void
+findmodules(char *path_argv[], const char *sfx[], FILE *out)
+{
+ char *p;
+ FTS *fts;
+ FTSENT *ftsent;
+ struct kfile *kfp;
+ int i;
+ int sl;
+
+ /* Have to fts once per suffix to find preferred suffixes first */
+ do {
+ sl = *sfx ? strlen(*sfx) : 0; /* current suffix length */
+ fts = fts_open(path_argv, FTS_PHYSICAL | FTS_NOCHDIR, NULL);
+ if (fts == NULL)
+ err(2, "can't begin traversing module path");
+ while ((ftsent = fts_read(fts)) != NULL) {
+ if (ftsent->fts_info == FTS_DNR ||
+ ftsent->fts_info == FTS_ERR ||
+ ftsent->fts_info == FTS_NS) {
+ errno = ftsent->fts_errno;
+ err(2, "error while traversing path %s", ftsent->fts_path);
+ }
+ if (ftsent->fts_info != FTS_F)
+ continue; /* not a plain file */
+
+ if (sl > 0) {
+ /* non-blank suffix; see if file name has it */
+ i = ftsent->fts_namelen - sl;
+ if (i <= 0 || strcmp(ftsent->fts_name + i, *sfx) != 0)
+ continue; /* no such suffix */
+ if ((p = strdup(ftsent->fts_name)) == NULL)
+ errx(2, "out of memory");
+ p[i] = '\0'; /* remove suffix in the copy */
+ kfp = kfile_find(p);
+ free(p);
+ } else
+ kfp = kfile_find(ftsent->fts_name);
+
+ if (kfp && !kfp->seen) {
+ doobj(ftsent->fts_path, kfp->addr, out);
+ kfp->seen = 1;
+ /* Optimization: stop fts as soon as seen all loaded modules */
+ if (kfile_allseen()) {
+ fts_close(fts);
+ return;
+ }
+ }
+ }
+ if (ftsent == NULL && errno != 0)
+ err(2, "couldn't complete traversing module path");
+ fts_close(fts);
+ } while (*sfx++);
+}
+
+static void
+usage(const char *myname)
+{
+ fprintf(stderr,
+ "Usage:\n"
+ "%s [-afKksVx] [-M core] [-N system] [-o outfile] [-X suffix]\n"
+ "%*s [modules-path [outfile]]\n\n"
+ "\t-a\tappend to outfile\n"
+ "\t-f\tfind the module in any subdirectory of modules-path\n"
+ "\t-K\tuse kld(2) to get the list of modules\n"
+ "\t-k\ttake input from kldstat(8)\n"
+ "\t-M\tspecify core name for kvm(3)\n"
+ "\t-N\tspecify system name for kvm(3)\n"
+ "\t-o\tuse outfile instead of \".asf\"\n"
+ "\t-s\tdon't prepend subdir for module path\n"
+ "\t-V\tuse kvm(3) to get the list of modules\n"
+ "\t-X\tappend suffix to list of possible module file name suffixes\n"
+ "\t-x\tclear list of possible module file name suffixes\n",
+ myname, (int)strlen(myname), "");
+ exit(2);
+}
+
+#define MAXPATHS 15
+#define MAXSUFFIXES 15
+
+/* KLD file names end in this */
+static int nsuffixes = 2;
+static const char *suffixes[MAXSUFFIXES + 1] = {
+ ".debug",
+ ".symbols",
+ NULL
+};
+
+int
+main(int argc, char *argv[])
+{
+ char basename[PATH_MAX];
+ char path[PATH_MAX];
+ char *modules_argv[MAXPATHS + 1];
+ char *copy, *p;
+ char **ap;
+ const char *filemode = "w"; /* mode for outfile */
+ const char *modules_path = "modules"; /* path to kernel build directory */
+ const char *outfile = ".asf"; /* and where to write the output */
+ const char *corefile = NULL; /* for kvm(3) */
+ const char *sysfile = NULL; /* for kvm(3) */
+ const char **sfx;
+ struct kfile *kfp;
+ struct stat st;
+ FILE *out; /* output file */
+ int dofind = 0;
+ int dokld = 0;
+ int dokvm = 0;
+ int nosubdir = 0;
+ int runprog = 0;
+ int i;
+ const int sl = strlen(KLDSUFFIX);
+
+ while ((i = getopt(argc, argv, "afKkM:N:o:sVX:x")) != -1)
+ switch (i) {
+ case 'a':
+ filemode = "a"; /* append to outfile */
+ break;
+ case 'f':
+ dofind = 1; /* find .ko (recursively) */
+ break;
+ case 'K':
+ dokld = 1; /* use kld(2) interface */
+ break;
+ case 'k':
+ runprog = 1; /* get input from kldstat(8) */
+ break;
+ case 'M':
+ corefile = optarg; /* core file for kvm(3) */
+ break;
+ case 'N':
+ sysfile = optarg; /* system file (kernel) for kvm(3) */
+ break;
+ case 'o':
+ outfile = optarg; /* output file name */
+ break;
+ case 's':
+ nosubdir = 1; /* don't descend into subdirs */
+ break;
+ case 'V':
+ dokvm = 1; /* use kvm(3) interface */
+ break;
+ case 'X':
+ if (nsuffixes >= MAXSUFFIXES)
+ errx(2, "only %d suffixes can be specified", MAXSUFFIXES);
+ suffixes[nsuffixes++] = optarg;
+ suffixes[nsuffixes] = NULL;
+ break;
+ case 'x':
+ nsuffixes = 0;
+ suffixes[0] = NULL;
+ break;
+ default:
+ usage(argv[0]);
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0) {
+ modules_path = argv[0];
+ argc--, argv++;
+ }
+ if (argc > 0) {
+ outfile = argv[0];
+ argc--, argv++;
+ }
+ if (argc > 0)
+ usage(argv[0]);
+
+ if (strcmp(outfile, "-") == 0)
+ out = stdout;
+ else
+ if ((out = fopen(outfile, filemode)) == NULL)
+ err(2, "can't open output file %s", outfile);
+
+ if (dokvm || corefile || sysfile) {
+ if (dokld || runprog)
+ warnx("using kvm(3) instead");
+ asf_kvm(sysfile, corefile);
+ } else if (dokld) {
+ if (runprog)
+ warnx("using kld(2) instead");
+ asf_kld();
+ } else
+ asf_prog(runprog);
+
+ /* Avoid long operations like module tree traversal when nothing to do */
+ if (kfile_empty()) {
+ warnx("no kernel modules loaded");
+ return (0);
+ }
+
+ if ((copy = strdup(modules_path)) == NULL)
+ errx(2, "out of memory");
+ for (
+ ap = modules_argv, p = copy;
+ (*ap = strsep(&p, ";")) != NULL && ap < &modules_argv[MAXPATHS];
+ ap++
+ );
+ if (*ap)
+ errx(2, "only %d module path elements can be specified", MAXPATHS);
+
+ if (!dofind)
+ STAILQ_FOREACH(kfp, &kfile_head, link) {
+ for (ap = modules_argv; *ap; ap++) {
+ if (!nosubdir) {
+ /* prepare basename of KLD, w/o suffix */
+ strlcpy(basename, kfp->name, sizeof(basename) - 1);
+ i = strlen(basename);
+ if (i > sl && strcmp(basename + i - sl, KLDSUFFIX) == 0)
+ i -= sl;
+ basename[i] = '/';
+ basename[i + 1] = '\0';
+ }
+ for (sfx = suffixes;; sfx++) {
+ snprintf(path, sizeof(path),
+ "%s/%s%s%s",
+ *ap,
+ nosubdir ? "" : basename,
+ kfp->name,
+ *sfx ? *sfx : "");
+ if (stat(path, &st) == 0) {
+ doobj(path, kfp->addr, out);
+ goto found;
+ }
+ if (*sfx == NULL)
+ break;
+ }
+ }
+ warnx("module %s not found in search path", kfp->name);
+found:
+ ;
+ }
+ else
+ findmodules(modules_argv, suffixes, out);
+
+ free(copy);
+ return (0);
+}
diff --git a/usr.sbin/asf/asf.h b/usr.sbin/asf/asf.h
new file mode 100644
index 0000000..d034936
--- /dev/null
+++ b/usr.sbin/asf/asf.h
@@ -0,0 +1,40 @@
+/*-
+ * Copyright (c) 2006 The FreeBSD 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.
+ *
+ * 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$
+ */
+
+#define KERNFILE "kernel"
+#define KLDSUFFIX ".ko"
+
+#define MAXTOKEN 10
+
+int tokenize(char *cptr, char *token[], int maxtoken);
+
+void kfile_add(const char *name, caddr_t addr);
+
+void asf_kld(void);
+void asf_kvm(const char *kernfile, const char *corefile);
+void asf_prog(int run);
diff --git a/usr.sbin/asf/asf_kld.c b/usr.sbin/asf/asf_kld.c
new file mode 100644
index 0000000..f0bd699
--- /dev/null
+++ b/usr.sbin/asf/asf_kld.c
@@ -0,0 +1,59 @@
+/*-
+ * Copyright (c) 2006 The FreeBSD 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.
+ *
+ * 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 <string.h>
+
+#include "asf.h"
+
+/*
+ * Get the linker file list using the kld interface.
+ * Works with a live kernel only.
+ */
+void
+asf_kld(void)
+{
+ struct kld_file_stat kfs;
+ int fid = 0; /* indicates the beginning of the linker file list */
+
+ while ((fid = kldnext(fid)) != 0) {
+ if (fid == -1)
+ err(2, "kldnext");
+ kfs.version = sizeof(kfs); /* must be set for kldstat(2) */
+ /* Get info on this linker file */
+ if (kldstat(fid, &kfs) == -1)
+ err(2, "kldstat");
+ if (strcmp(kfs.name, KERNFILE) == 0)
+ continue;
+ /* Add to our list of linker files */
+ kfile_add(kfs.name, kfs.address);
+ }
+}
diff --git a/usr.sbin/asf/asf_kvm.c b/usr.sbin/asf/asf_kvm.c
new file mode 100644
index 0000000..aa4effe
--- /dev/null
+++ b/usr.sbin/asf/asf_kvm.c
@@ -0,0 +1,128 @@
+/*-
+ * Copyright (c) 2006 The FreeBSD 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.
+ *
+ * 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/queue.h> /* for <sys/linker.h> with _KERNEL defined */
+#include <err.h>
+#include <fcntl.h>
+#include <kvm.h>
+#include <limits.h>
+#include <nlist.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define _KERNEL
+#include <sys/linker.h>
+#undef _KERNEL
+
+#include "asf.h"
+
+/* Name of the head of the linker file list in /sys/kern/kern_linker.c */
+#define LINKER_HEAD "linker_files"
+
+/*
+ * Get the list of linker files using kvm(3).
+ * Can work with a live kernel as well as with a crash dump.
+ */
+void
+asf_kvm(const char *kernfile, const char *corefile)
+{
+ char errbuf[LINE_MAX];
+ char name[PATH_MAX];
+ kvm_t *kd;
+ struct nlist nl[2];
+ struct linker_file lf;
+ linker_file_list_t linker_files;
+ ssize_t n;
+ void *kp;
+
+ kd = kvm_openfiles(kernfile, corefile, NULL, O_RDONLY, errbuf);
+ if (kd == NULL)
+ errx(2, "open kernel memory: %s", errbuf);
+
+ /*
+ * Locate the head of the linker file list using kernel symbols.
+ */
+ strcpy(name, LINKER_HEAD);
+ nl[0].n_name = name; /* can't use LINKER_HEAD here because it's const */
+ nl[1].n_name = NULL; /* terminate the array for kvm_nlist() */
+ switch (kvm_nlist(kd, nl)) {
+ case 0:
+ break;
+ case -1:
+ warnx("%s: %s", LINKER_HEAD, kvm_geterr(kd));
+ kvm_close(kd);
+ exit(2);
+ default:
+ kvm_close(kd);
+ errx(2, "%s: symbol not found", LINKER_HEAD);
+ }
+
+ /*
+ * Read the head of the linker file list from kernel memory.
+ */
+ n = kvm_read(kd, nl[0].n_value, &linker_files, sizeof(linker_files));
+ if (n == -1)
+ goto read_err;
+ if (n != sizeof(linker_files)) {
+ kvm_close(kd);
+ errx(2, "%s: short read", LINKER_HEAD);
+ }
+
+ /*
+ * Traverse the linker file list starting at its head.
+ */
+ for (kp = linker_files.tqh_first; kp; kp = lf.link.tqe_next) {
+ /* Read a linker file structure */
+ n = kvm_read(kd, (u_long)kp, &lf, sizeof(lf));
+ if (n == -1)
+ goto read_err;
+ if (n != sizeof(lf)) {
+ kvm_close(kd);
+ errx(2, "kvm: short read");
+ }
+ /* Read the name of the file stored separately */
+ bzero(name, sizeof(name));
+ n = kvm_read(kd, (u_long)lf.filename, name, sizeof(name) - 1);
+ if (n == -1)
+ goto read_err;
+ if (strcmp(name, KERNFILE) == 0)
+ continue;
+ /* Add this file to our list of linker files */
+ kfile_add(name, lf.address);
+ }
+ kvm_close(kd);
+ return;
+
+read_err: /* A common error case */
+ warnx("read kernel memory: %s", kvm_geterr(kd));
+ kvm_close(kd);
+ exit(2);
+}
diff --git a/usr.sbin/asf/asf_prog.c b/usr.sbin/asf/asf_prog.c
new file mode 100644
index 0000000..9a13cd6
--- /dev/null
+++ b/usr.sbin/asf/asf_prog.c
@@ -0,0 +1,73 @@
+/*-
+ * Copyright (c) 2002, 2003 Greg Lehey
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <err.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "asf.h"
+
+/*
+ * Get the linker file list from kldstat(8) output.
+ * The "run" flag tells if kldstat(8) should run now.
+ * Of course, kldstat(1) can run in a live system only, but its output
+ * can be saved beforehand and fed to this function later via stdin.
+ */
+void
+asf_prog(int run)
+{
+ char buf[LINE_MAX];
+ char *token[MAXTOKEN];
+ char *endp;
+ FILE *kldstat;
+ caddr_t base;
+ int tokens;
+
+ if (run) {
+ if ((kldstat = popen("kldstat", "r")) == NULL)
+ err(2, "can't start kldstat");
+ } else
+ kldstat = stdin;
+
+ while (fgets(buf, sizeof(buf), kldstat)) {
+ /* Skip header line and main kernel file */
+ if (buf[0] == 'I' || strstr(buf, KERNFILE))
+ continue;
+ tokens = tokenize(buf, token, MAXTOKEN);
+ if (tokens < 4)
+ continue;
+ base = (caddr_t)(uintptr_t)strtoumax(token[2], &endp, 16);
+ if (endp == NULL || *endp != '\0')
+ continue;
+ kfile_add(token[4], base);
+ }
+}
diff --git a/usr.sbin/audit/Makefile b/usr.sbin/audit/Makefile
new file mode 100644
index 0000000..14fc893
--- /dev/null
+++ b/usr.sbin/audit/Makefile
@@ -0,0 +1,17 @@
+#
+# $FreeBSD$
+#
+
+OPENBSMDIR=${.CURDIR}/../../contrib/openbsm
+.PATH: ${OPENBSMDIR}/bin/audit
+
+CFLAGS+= -I${OPENBSMDIR}
+
+PROG= audit
+MAN= audit.8
+
+LIBADD= bsm
+
+WARNS?= 5
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/audit/Makefile.depend b/usr.sbin/audit/Makefile.depend
new file mode 100644
index 0000000..97143a8
--- /dev/null
+++ b/usr.sbin/audit/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbsm \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/auditd/Makefile b/usr.sbin/auditd/Makefile
new file mode 100644
index 0000000..3fb6a40
--- /dev/null
+++ b/usr.sbin/auditd/Makefile
@@ -0,0 +1,18 @@
+#
+# $FreeBSD$
+#
+
+OPENBSMDIR=${.CURDIR}/../../contrib/openbsm
+.PATH: ${OPENBSMDIR}/bin/auditd
+
+CFLAGS+= -I${OPENBSMDIR}
+
+PROG= auditd
+SRCS= auditd.c audit_warn.c auditd_fbsd.c
+MAN= auditd.8
+
+LIBADD= auditd bsm
+
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/auditd/Makefile.depend b/usr.sbin/auditd/Makefile.depend
new file mode 100644
index 0000000..ae040b6
--- /dev/null
+++ b/usr.sbin/auditd/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libauditd \
+ lib/libbsm \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/auditdistd/Makefile b/usr.sbin/auditdistd/Makefile
new file mode 100644
index 0000000..8d32884
--- /dev/null
+++ b/usr.sbin/auditdistd/Makefile
@@ -0,0 +1,34 @@
+#
+# $FreeBSD$
+#
+
+OPENBSMDIR=${.CURDIR}/../../contrib/openbsm
+.PATH: ${OPENBSMDIR}/bin/auditdistd
+
+# Addition of auditdistd because otherwise generated parse.c can't find
+# auditdistd.h. This seems like a makefile non-feature.
+CFLAGS+=-I${OPENBSMDIR} -I${OPENBSMDIR}/bin/auditdistd
+
+NO_WFORMAT=
+NO_WMISSING_VARIABLE_DECLARATIONS=
+
+PROG= auditdistd
+SRCS= auditdistd.c
+SRCS+= parse.y pjdlog.c
+SRCS+= proto.c proto_common.c proto_socketpair.c proto_tcp.c proto_tls.c
+SRCS+= receiver.c
+SRCS+= sandbox.c sender.c subr.c
+SRCS+= token.l trail.c
+MAN= auditdistd.8 auditdistd.conf.5
+
+LIBADD+= l pthread util crypto ssl
+
+YFLAGS+=-v
+
+CLEANFILES=parse.c parse.h parse.output
+
+# auditdistd cannot use FreeBSD specific lock annotation macros. Disable
+# thread safety analysis completely.
+NO_WTHREAD_SAFETY=
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/auditdistd/Makefile.depend b/usr.sbin/auditdistd/Makefile.depend
new file mode 100644
index 0000000..b939d66
--- /dev/null
+++ b/usr.sbin/auditdistd/Makefile.depend
@@ -0,0 +1,31 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libthr \
+ lib/libutil \
+ secure/lib/libcrypto \
+ secure/lib/libssl \
+ usr.bin/lex/lib \
+ usr.bin/yacc.host \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+parse.o: parse.c
+parse.po: parse.c
+token.o: parse.h
+token.o: token.c
+token.po: parse.h
+token.po: token.c
+.endif
diff --git a/usr.sbin/auditreduce/Makefile b/usr.sbin/auditreduce/Makefile
new file mode 100644
index 0000000..a462e1b
--- /dev/null
+++ b/usr.sbin/auditreduce/Makefile
@@ -0,0 +1,17 @@
+#
+# $FreeBSD$
+#
+
+OPENBSMDIR=${.CURDIR}/../../contrib/openbsm
+.PATH: ${OPENBSMDIR}/bin/auditreduce
+
+CFLAGS+= -I${OPENBSMDIR}
+
+PROG= auditreduce
+MAN= auditreduce.1
+
+LIBADD= bsm
+
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/auditreduce/Makefile.depend b/usr.sbin/auditreduce/Makefile.depend
new file mode 100644
index 0000000..97143a8
--- /dev/null
+++ b/usr.sbin/auditreduce/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbsm \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/authpf/Makefile b/usr.sbin/authpf/Makefile
new file mode 100644
index 0000000..be95a4b
--- /dev/null
+++ b/usr.sbin/authpf/Makefile
@@ -0,0 +1,23 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../contrib/pf/authpf
+
+PROG= authpf
+MAN= authpf.8
+BINOWN= root
+BINGRP= authpf
+BINMODE= 6555
+
+SRCS= authpf.c
+
+# XXX ALTQ:
+CFLAGS+= -DENABLE_ALTQ
+
+LIBADD= m util
+
+WARNS?= 3
+
+LINKS= ${BINDIR}/authpf ${BINDIR}/authpf-noip
+MLINKS= authpf.8 authpf-noip.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/authpf/Makefile.depend b/usr.sbin/authpf/Makefile.depend
new file mode 100644
index 0000000..fb0a041
--- /dev/null
+++ b/usr.sbin/authpf/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+ lib/msun \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/autofs/Makefile b/usr.sbin/autofs/Makefile
new file mode 100644
index 0000000..00c79dd
--- /dev/null
+++ b/usr.sbin/autofs/Makefile
@@ -0,0 +1,32 @@
+# $FreeBSD$
+
+PROG= automountd
+SRCS= automount.c
+SRCS+= automountd.c
+SRCS+= autounmountd.c
+SRCS+= common.c
+SRCS+= defined.c
+SRCS+= getmntopts.c
+SRCS+= log.c
+SRCS+= popen.c
+SRCS+= token.l
+
+CFLAGS+=-I${.CURDIR}
+CFLAGS+=-I${.CURDIR}/../../sys/fs/autofs
+
+MAN= automount.8 automountd.8 autounmountd.8 auto_master.5
+
+LIBADD= util
+
+# Needed for getmntopts.c
+MOUNT= ${.CURDIR}/../../sbin/mount
+CFLAGS+=-I${MOUNT}
+
+WARNS= 6
+
+LINKS= ${BINDIR}/automountd ${BINDIR}/automount
+LINKS+= ${BINDIR}/automountd ${BINDIR}/autounmountd
+
+.PATH: ${MOUNT}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/autofs/Makefile.depend b/usr.sbin/autofs/Makefile.depend
new file mode 100644
index 0000000..61f3b2b
--- /dev/null
+++ b/usr.sbin/autofs/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+token.o: token.c
+token.po: token.c
+.endif
diff --git a/usr.sbin/autofs/auto_master.5 b/usr.sbin/autofs/auto_master.5
new file mode 100644
index 0000000..89100f7
--- /dev/null
+++ b/usr.sbin/autofs/auto_master.5
@@ -0,0 +1,374 @@
+.\" Copyright (c) 2014 The FreeBSD Foundation
+.\" All rights reserved.
+.\"
+.\" This software was 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 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 13, 2015
+.Dt AUTO_MASTER 5
+.Os
+.Sh NAME
+.Nm auto_master
+.Nd auto_master and map file format
+.Sh DESCRIPTION
+The automounter configuration consists of the
+.Nm
+configuration file, which assigns filesystem paths to map names,
+and maps, which contain actual mount information.
+The
+.Nm
+configuration file is used by the
+.Xr automount 8
+command.
+Map files are read by the
+.Xr automountd 8
+daemon.
+.Sh AUTO_MASTER SYNTAX
+The
+.Nm
+file consists of lines with two or three entries separated by whitespace
+and terminated by newline character:
+.Bd -literal -offset indent
+.Pa mountpoint Pa map_name Op Ar -options
+.Ed
+.Pp
+.Pa mountpoint
+is either a fully specified path, or
+.Li /- .
+When
+.Pa mountpoint
+is a full path,
+.Pa map_name
+must reference an indirect map.
+Otherwise,
+.Pa map_name
+must reference a direct map.
+See
+.Sx "MAP SYNTAX" below.
+.Pp
+.Pa map_name
+specifies map to use.
+If
+.Pa map_name
+begins with
+.Li - ,
+it specifies a special map.
+See
+.Sx "MAP SYNTAX"
+below.
+If
+.Pa map_name
+is not a fully specified path
+.Pq it does not start with Li / ,
+.Xr automountd 8
+will search for that name in
+.Li /etc .
+Otherwise it will use the path as given.
+If the file indicated by
+.Pa map_name
+is executable,
+.Xr automountd 8
+will assume it is an executable map.
+See
+.Sx "MAP SYNTAX"
+below.
+Otherwise, the file is opened and the contents parsed.
+.Pp
+.Pa -options
+is an optional field that starts with
+.Li -
+and can contain generic filesystem mount options.
+.Pp
+The following example specifies that the /etc/auto_example indirect map
+will be mounted on /example.
+.Bd -literal -offset indent
+/example auto_example
+.Ed
+.Sh MAP SYNTAX
+Map files consist of lines with a number of entries separated by whitespace
+and terminated by newline character:
+.Bd -literal -offset indent
+.Pa key Oo Ar -options Oc Oo Ar mountpoint Oo -options Oc Oc Ar location Op ...
+.Ed
+.Pp
+In most cases, it can be simplified to:
+.Bd -literal -offset indent
+.Pa key Oo Ar -options Oc Ar location
+.Ed
+.Pp
+.Pa key
+is the path component used by
+.Xr automountd 8
+to find the right map entry to use.
+It is also used to form the final mountpoint.
+A wildcard
+.Pq Ql *
+can be used for the key.
+It matches every directory that does not match other keys.
+Those directories will not be visible to the user
+until accessed.
+.Pp
+The
+.Ar options
+field, if present, must begin with
+.Li - .
+When mounting the filesystem, options supplied to
+.Nm
+and options specified in the map entry are concatenated together.
+The special option
+.Li fstype
+is used to specify filesystem type.
+It is not passed to the mount program as an option.
+Instead, it is passed as an argument to
+.Cm "mount -t".
+The default
+.Li fstype
+is
+.Ql nfs .
+The special option
+.Li nobrowse
+is used to disable creation of top-level directories for special
+and executable maps.
+.Pp
+The optional
+.Pa mountpoint
+field is used to specify multiple mount points
+for a single key.
+.Pp
+The
+.Ar location
+field specifies the filesystem to be mounted.
+Ampersands
+.Pq Ql &
+in the
+.Ar location
+field are replaced with the value of
+.Ar key .
+This is typically used with wildcards, like:
+.Bd -literal -offset indent
+.Li * 192.168.1.1:/share/&
+.Ed
+.Pp
+The
+.Ar location
+field may contain references to variables, like:
+.Bd -literal -offset indent
+.Li sys 192.168.1.1:/sys/${OSNAME}
+.Ed
+.Pp
+Defined variables are:
+.Pp
+.Bl -tag -width "-OSNAME" -compact
+.It Li ARCH
+Expands to the output of
+.Li "uname -p" .
+.It Li CPU
+Same as ARCH.
+.It Li HOST
+Expands to the output of
+.Li "uname -n" .
+.It Li OSNAME
+Expands to the output of
+.Li "uname -s" .
+.It Li OSREL
+Expands to the output of
+.Li "uname -r" .
+.It Li OSVERS
+Expands to the output of
+.Li "uname -v" .
+.El
+.Pp
+Additional variables can be defined with the
+.Fl D
+option of
+.Xr automount 8
+and
+.Xr automountd 8 .
+.Pp
+To pass a location that begins with
+.Li / ,
+prefix it with a colon.
+For example,
+.Li :/dev/cd0 .
+.Pp
+This example, when put into
+.Pa /etc/auto_example ,
+and with
+.Nm
+referring to the map as described above, specifies that the NFS share
+.Li 192.168.1.1:/share/example/x
+will be mounted on
+.Pa /example/x/
+when any process attempts to access that mountpoint, with
+.Li intr
+and
+.Li nfsv4
+mount options, described in
+.Xr mount_nfs 8 :
+.Bd -literal -offset indent
+.Li x -intr,nfsv4 192.168.1.1:/share/example/x
+.Ed
+.Pp
+Automatically mount an SMB share on access, as a guest user,
+without prompting for a password:
+.Bd -literal -offset indent
+.Li share -fstype=smbfs,-N ://@server/share
+.Ed
+.Pp
+Automatically mount the CD drive on access:
+.Bd -literal -offset indent
+.Li cd -fstype=cd9660 :/dev/cd0
+.Ed
+.Sh SPECIAL MAPS
+Special maps have names beginning with
+.Li - .
+Supported special maps are:
+.Pp
+.Bl -tag -width "-hosts" -compact
+.It Li -hosts
+Query the remote NFS server and map exported shares.
+This map is traditionally mounted on
+.Pa /net .
+Access to files on a remote NFS server is provided through the
+.Pf /net/ Ar nfs-server-ip Ns / Ns Ar share-name Ns/
+directory without any additional configuration.
+Directories for individual NFS servers are not present until the first access,
+when they are automatically created.
+.It Li -media
+Query devices that are not yet mounted, but contain valid filesystems.
+Generally used to access files on removable media.
+.It Li -noauto
+Mount filesystems configured in
+.Xr fstab 5
+as "noauto".
+This needs to be set up as a direct map.
+.It Li -null
+Prevent
+.Xr automountd 8
+from mounting anything on the mountpoint.
+.El
+.Pp
+It is possible to add custom special maps by adding them, as executable
+maps named
+.Pa special_foo ,
+to the
+.Pa /etc/autofs/
+directory.
+.Sh EXECUTABLE MAPS
+If the map file specified in
+.Nm
+has the execute bit set,
+.Xr automountd 8
+will execute it and parse the standard output instead of parsing
+the file contents.
+When called without command line arguments, the executable is
+expected to output a list of available map keys separated by
+newline characters.
+Otherwise, the executable will be called with a key name as
+a command line argument.
+Output from the executable is expected to be the entry for that key,
+not including the key itself.
+.Sh INDIRECT VERSUS DIRECT MAPS
+Indirect maps are referred to in
+.Nm
+by entries with a fully qualified path as a mount point, and must contain only
+relative paths as keys.
+Direct maps are referred to in
+.Nm
+by entries with
+.Li /-
+as the mountpoint, and must contain only fully qualified paths as keys.
+For indirect maps, the final mount point is determined by concatenating the
+.Nm
+mountpoint with the map entry key and optional map entry mountpoint.
+For direct maps, the final mount point is determined by concatenating
+the map entry key with the optional map entry mountpoint.
+.Pp
+The example above could be rewritten using direct map, by placing this in
+.Nm :
+.Bd -literal -offset indent
+.Li /- auto_example
+.Ed
+.Pp
+and this in
+.Li /etc/auto_example
+map file:
+.Bd -literal -offset indent
+.Li /example/x -intr,nfsv4 192.168.1.1:/share/example/x
+.Li /example/share -fstype=smbfs,-N ://@server/share
+.Li /example/cd -fstype=cd9660 :/dev/cd0
+.Ed
+.Sh DIRECTORY SERVICES
+Both
+.Nm
+and maps may contain entries consisting of a plus sign and map name:
+.Bd -literal -offset indent
+.Li +auto_master
+.Ed
+.Pp
+Those entries cause
+.Xr automountd 8
+daemon to retrieve the named map from directory services (like LDAP)
+and include it where the entry was.
+.Pp
+If the file containing the map referenced in
+.Nm
+is not found, the map will be retrieved from directory services instead.
+.Pp
+To retrieve entries from directory services,
+.Xr automountd 8
+daemon runs
+.Pa /etc/autofs/include ,
+which is usually a shell script, with map name as the only command line
+parameter.
+The script should output entries formatted according to
+.Nm
+or automounter map syntax to standard output.
+An example script to use LDAP is included in
+.Pa /etc/autofs/include_ldap .
+It can be symlinked to
+.Pa /etc/autofs/include .
+.Sh FILES
+.Bl -tag -width ".Pa /etc/auto_master" -compact
+.It Pa /etc/auto_master
+The default location of the
+.Pa auto_master
+file.
+.It Pa /etc/autofs/
+Directory containing shell scripts to implement special maps and directory
+services.
+.El
+.Sh SEE ALSO
+.Xr autofs 5 ,
+.Xr automount 8 ,
+.Xr automountd 8 ,
+.Xr autounmountd 8
+.Sh AUTHORS
+The
+.Nm
+configuration file functionality was developed by
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+under sponsorship from the FreeBSD Foundation.
diff --git a/usr.sbin/autofs/automount.8 b/usr.sbin/autofs/automount.8
new file mode 100644
index 0000000..c111a99
--- /dev/null
+++ b/usr.sbin/autofs/automount.8
@@ -0,0 +1,111 @@
+.\" Copyright (c) 2014 The FreeBSD Foundation
+.\" All rights reserved.
+.\"
+.\" This software was 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 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 November 22, 2014
+.Dt AUTOMOUNT 8
+.Os
+.Sh NAME
+.Nm automount
+.Nd update autofs mounts
+.Sh SYNOPSIS
+.Nm
+.Op Fl D Ar name=value
+.Op Fl L
+.Op Fl c
+.Op Fl f
+.Op Fl o Ar options
+.Op Fl v
+.Op Fl u
+.Sh DESCRIPTION
+When called without options, the
+.Nm
+command parses the
+.Xr auto_master 5
+configuration file and any direct maps that it references, and mounts
+or unmounts
+.Xr autofs 4
+filesystems to match.
+These options are available:
+.Bl -tag -width ".Fl v"
+.It Fl D
+Define a variable.
+It is only useful with
+.Fl L .
+.It Fl L
+Do not mount or unmount anything.
+Instead parse
+.Xr auto_master 5
+and any direct maps, then print them to standard output.
+When specified more than once, all the maps, including indirect ones,
+will be parsed and shown.
+This is useful when debugging configuration problems.
+.It Fl c
+Flush caches, discarding possibly stale information obtained from maps
+and directory services.
+.It Fl f
+Force unmount, to be used with
+.Fl u .
+.It Fl o
+Specify mount options to be used along with the ones specified in the maps.
+It is only useful with
+.Fl L .
+.It Fl u
+Try to unmount filesystems mounted by
+.Xr automountd 8 .
+.Xr autofs 5
+mounts are not unmounted.
+To unmount all
+.Xr autofs
+mounts, use
+.Cm "umount -At autofs".
+.It Fl v
+Increase verbosity.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+Unmount all filesystems mounted by
+.Xr automountd 8 :
+.Dl Nm Fl u
+.Sh SEE ALSO
+.Xr auto_master 5 ,
+.Xr autofs 5 ,
+.Xr automountd 8 ,
+.Xr autounmountd 8
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Fx 10.1 .
+.Sh AUTHORS
+The
+.Nm
+was developed by
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+under sponsorship from the FreeBSD Foundation.
diff --git a/usr.sbin/autofs/automount.c b/usr.sbin/autofs/automount.c
new file mode 100644
index 0000000..ce5d861
--- /dev/null
+++ b/usr.sbin/autofs/automount.c
@@ -0,0 +1,396 @@
+/*-
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/utsname.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libutil.h>
+
+#include "common.h"
+#include "mntopts.h"
+
+static int
+unmount_by_statfs(const struct statfs *sb, bool force)
+{
+ char *fsid_str;
+ int error, ret, flags;
+
+ ret = asprintf(&fsid_str, "FSID:%d:%d",
+ sb->f_fsid.val[0], sb->f_fsid.val[1]);
+ if (ret < 0)
+ log_err(1, "asprintf");
+
+ log_debugx("unmounting %s using %s", sb->f_mntonname, fsid_str);
+
+ flags = MNT_BYFSID;
+ if (force)
+ flags |= MNT_FORCE;
+ error = unmount(fsid_str, flags);
+ free(fsid_str);
+ if (error != 0)
+ log_warn("cannot unmount %s", sb->f_mntonname);
+
+ return (error);
+}
+
+static const struct statfs *
+find_statfs(const struct statfs *mntbuf, int nitems, const char *mountpoint)
+{
+ int i;
+
+ for (i = 0; i < nitems; i++) {
+ if (strcmp(mntbuf[i].f_mntonname, mountpoint) == 0)
+ return (mntbuf + i);
+ }
+
+ return (NULL);
+}
+
+static void
+mount_autofs(const char *from, const char *fspath, const char *options,
+ const char *prefix)
+{
+ struct iovec *iov = NULL;
+ char errmsg[255];
+ int error, iovlen = 0;
+
+ create_directory(fspath);
+
+ log_debugx("mounting %s on %s, prefix \"%s\", options \"%s\"",
+ from, fspath, prefix, options);
+ memset(errmsg, 0, sizeof(errmsg));
+
+ build_iovec(&iov, &iovlen, "fstype",
+ __DECONST(void *, "autofs"), (size_t)-1);
+ build_iovec(&iov, &iovlen, "fspath",
+ __DECONST(void *, fspath), (size_t)-1);
+ build_iovec(&iov, &iovlen, "from",
+ __DECONST(void *, from), (size_t)-1);
+ build_iovec(&iov, &iovlen, "errmsg",
+ errmsg, sizeof(errmsg));
+
+ /*
+ * Append the options and mountpoint defined in auto_master(5);
+ * this way automountd(8) does not need to parse it.
+ */
+ build_iovec(&iov, &iovlen, "master_options",
+ __DECONST(void *, options), (size_t)-1);
+ build_iovec(&iov, &iovlen, "master_prefix",
+ __DECONST(void *, prefix), (size_t)-1);
+
+ error = nmount(iov, iovlen, 0);
+ if (error != 0) {
+ if (*errmsg != '\0') {
+ log_err(1, "cannot mount %s on %s: %s",
+ from, fspath, errmsg);
+ } else {
+ log_err(1, "cannot mount %s on %s", from, fspath);
+ }
+ }
+}
+
+static void
+mount_if_not_already(const struct node *n, const char *map, const char *options,
+ const char *prefix, const struct statfs *mntbuf, int nitems)
+{
+ const struct statfs *sb;
+ char *mountpoint;
+ char *from;
+ int ret;
+
+ ret = asprintf(&from, "map %s", map);
+ if (ret < 0)
+ log_err(1, "asprintf");
+
+ mountpoint = node_path(n);
+ sb = find_statfs(mntbuf, nitems, mountpoint);
+ if (sb != NULL) {
+ if (strcmp(sb->f_fstypename, "autofs") != 0) {
+ log_debugx("unknown filesystem mounted "
+ "on %s; mounting", mountpoint);
+ /*
+ * XXX: Compare options and 'from',
+ * and update the mount if necessary.
+ */
+ } else {
+ log_debugx("autofs already mounted "
+ "on %s", mountpoint);
+ free(from);
+ free(mountpoint);
+ return;
+ }
+ } else {
+ log_debugx("nothing mounted on %s; mounting",
+ mountpoint);
+ }
+
+ mount_autofs(from, mountpoint, options, prefix);
+ free(from);
+ free(mountpoint);
+}
+
+static void
+mount_unmount(struct node *root)
+{
+ struct statfs *mntbuf;
+ struct node *n, *n2;
+ int i, nitems;
+
+ nitems = getmntinfo(&mntbuf, MNT_WAIT);
+ if (nitems <= 0)
+ log_err(1, "getmntinfo");
+
+ log_debugx("unmounting stale autofs mounts");
+
+ for (i = 0; i < nitems; i++) {
+ if (strcmp(mntbuf[i].f_fstypename, "autofs") != 0) {
+ log_debugx("skipping %s, filesystem type is not autofs",
+ mntbuf[i].f_mntonname);
+ continue;
+ }
+
+ n = node_find(root, mntbuf[i].f_mntonname);
+ if (n != NULL) {
+ log_debugx("leaving autofs mounted on %s",
+ mntbuf[i].f_mntonname);
+ continue;
+ }
+
+ log_debugx("autofs mounted on %s not found "
+ "in new configuration; unmounting", mntbuf[i].f_mntonname);
+ unmount_by_statfs(&(mntbuf[i]), false);
+ }
+
+ log_debugx("mounting new autofs mounts");
+
+ TAILQ_FOREACH(n, &root->n_children, n_next) {
+ if (!node_is_direct_map(n)) {
+ mount_if_not_already(n, n->n_map, n->n_options,
+ n->n_key, mntbuf, nitems);
+ continue;
+ }
+
+ TAILQ_FOREACH(n2, &n->n_children, n_next) {
+ mount_if_not_already(n2, n->n_map, n->n_options,
+ "/", mntbuf, nitems);
+ }
+ }
+}
+
+static void
+flush_autofs(const char *fspath)
+{
+ struct iovec *iov = NULL;
+ char errmsg[255];
+ int error, iovlen = 0;
+
+ log_debugx("flushing %s", fspath);
+ memset(errmsg, 0, sizeof(errmsg));
+
+ build_iovec(&iov, &iovlen, "fstype",
+ __DECONST(void *, "autofs"), (size_t)-1);
+ build_iovec(&iov, &iovlen, "fspath",
+ __DECONST(void *, fspath), (size_t)-1);
+ build_iovec(&iov, &iovlen, "errmsg",
+ errmsg, sizeof(errmsg));
+
+ error = nmount(iov, iovlen, MNT_UPDATE);
+ if (error != 0) {
+ if (*errmsg != '\0') {
+ log_err(1, "cannot flush %s: %s",
+ fspath, errmsg);
+ } else {
+ log_err(1, "cannot flush %s", fspath);
+ }
+ }
+}
+
+static void
+flush_caches(void)
+{
+ struct statfs *mntbuf;
+ int i, nitems;
+
+ nitems = getmntinfo(&mntbuf, MNT_WAIT);
+ if (nitems <= 0)
+ log_err(1, "getmntinfo");
+
+ log_debugx("flushing autofs caches");
+
+ for (i = 0; i < nitems; i++) {
+ if (strcmp(mntbuf[i].f_fstypename, "autofs") != 0) {
+ log_debugx("skipping %s, filesystem type is not autofs",
+ mntbuf[i].f_mntonname);
+ continue;
+ }
+
+ flush_autofs(mntbuf[i].f_mntonname);
+ }
+}
+
+static void
+unmount_automounted(bool force)
+{
+ struct statfs *mntbuf;
+ int i, nitems;
+
+ nitems = getmntinfo(&mntbuf, MNT_WAIT);
+ if (nitems <= 0)
+ log_err(1, "getmntinfo");
+
+ log_debugx("unmounting automounted filesystems");
+
+ for (i = 0; i < nitems; i++) {
+ if (strcmp(mntbuf[i].f_fstypename, "autofs") == 0) {
+ log_debugx("skipping %s, filesystem type is autofs",
+ mntbuf[i].f_mntonname);
+ continue;
+ }
+
+ if ((mntbuf[i].f_flags & MNT_AUTOMOUNTED) == 0) {
+ log_debugx("skipping %s, not automounted",
+ mntbuf[i].f_mntonname);
+ continue;
+ }
+
+ unmount_by_statfs(&(mntbuf[i]), force);
+ }
+}
+
+static void
+usage_automount(void)
+{
+
+ fprintf(stderr, "usage: automount [-D name=value][-o opts][-Lcfuv]\n");
+ exit(1);
+}
+
+int
+main_automount(int argc, char **argv)
+{
+ struct node *root;
+ int ch, debug = 0, show_maps = 0;
+ char *options = NULL;
+ bool do_unmount = false, force_unmount = false, flush = false;
+
+ /*
+ * Note that in automount(8), the only purpose of variable
+ * handling is to aid in debugging maps (automount -L).
+ */
+ defined_init();
+
+ while ((ch = getopt(argc, argv, "D:Lfco:uv")) != -1) {
+ switch (ch) {
+ case 'D':
+ defined_parse_and_add(optarg);
+ break;
+ case 'L':
+ show_maps++;
+ break;
+ case 'c':
+ flush = true;
+ break;
+ case 'f':
+ force_unmount = true;
+ break;
+ case 'o':
+ options = concat(options, ',', optarg);
+ break;
+ case 'u':
+ do_unmount = true;
+ break;
+ case 'v':
+ debug++;
+ break;
+ case '?':
+ default:
+ usage_automount();
+ }
+ }
+ argc -= optind;
+ if (argc != 0)
+ usage_automount();
+
+ if (force_unmount && !do_unmount)
+ usage_automount();
+
+ log_init(debug);
+
+ if (flush) {
+ flush_caches();
+ return (0);
+ }
+
+ if (do_unmount) {
+ unmount_automounted(force_unmount);
+ return (0);
+ }
+
+ root = node_new_root();
+ parse_master(root, AUTO_MASTER_PATH);
+
+ if (show_maps) {
+ if (show_maps > 1) {
+ node_expand_indirect_maps(root);
+ node_expand_ampersand(root, NULL);
+ }
+ node_expand_defined(root);
+ node_print(root, options);
+ return (0);
+ }
+
+ mount_unmount(root);
+
+ return (0);
+}
diff --git a/usr.sbin/autofs/automountd.8 b/usr.sbin/autofs/automountd.8
new file mode 100644
index 0000000..175633b
--- /dev/null
+++ b/usr.sbin/autofs/automountd.8
@@ -0,0 +1,103 @@
+.\" Copyright (c) 2014 The FreeBSD Foundation
+.\" All rights reserved.
+.\"
+.\" This software was 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 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 10, 2015
+.Dt AUTOMOUNTD 8
+.Os
+.Sh NAME
+.Nm automountd
+.Nd daemon handling autofs mount requests
+.Sh SYNOPSIS
+.Nm
+.Op Fl D Ar name=value
+.Op Fl i
+.Op Fl m Ar maxproc
+.Op Fl o Ar options
+.Op Fl d
+.Op Fl v
+.Sh DESCRIPTION
+The
+.Nm
+daemon is responsible for handling
+.Xr autofs 5
+mount requests, parsing maps,
+and mounting filesystems they specify.
+On startup,
+.Nm
+forks into background and waits for kernel requests.
+When a request is received,
+.Nm
+forks a child process.
+The child process parses the appropriate map and mounts filesystems accordingly.
+Then it signals the kernel to release blocked processes that were waiting
+for the mount.
+.Bl -tag -width ".Fl v"
+.It Fl D
+Define a variable.
+.It Fl i
+For indirect mounts, only create subdirectories if there are no wildcard
+entries.
+Without
+.Fl i ,
+.Nm
+creates all the subdirectories it can.
+Users may not realize that the wildcard map entry makes it possible to access
+directories that have not yet been created.
+.It Fl m Ar maxproc
+Limit the number of forked
+.Nm
+processes, and thus the number of mount requests being handled in parallel.
+The default is 30.
+.It Fl d
+Debug mode: increase verbosity and do not daemonize.
+.It Fl o Ar options
+Specify mount options.
+Options specified here will be overridden by options entered in maps or
+.Xr auto_master 5 .
+.It Fl v
+Increase verbosity.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr auto_master 5 ,
+.Xr autofs 5 ,
+.Xr automount 8 ,
+.Xr autounmountd 8
+.Sh HISTORY
+The
+.Nm
+daemon appeared in
+.Fx 10.1 .
+.Sh AUTHORS
+The
+.Nm
+was developed by
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+under sponsorship from the FreeBSD Foundation.
diff --git a/usr.sbin/autofs/automountd.c b/usr.sbin/autofs/automountd.c
new file mode 100644
index 0000000..2c9b1a9
--- /dev/null
+++ b/usr.sbin/autofs/automountd.c
@@ -0,0 +1,569 @@
+/*-
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/utsname.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libutil.h>
+
+#include "autofs_ioctl.h"
+
+#include "common.h"
+
+#define AUTOMOUNTD_PIDFILE "/var/run/automountd.pid"
+
+static int nchildren = 0;
+static int autofs_fd;
+static int request_id;
+
+static void
+done(int request_error, bool wildcards)
+{
+ struct autofs_daemon_done add;
+ int error;
+
+ memset(&add, 0, sizeof(add));
+ add.add_id = request_id;
+ add.add_wildcards = wildcards;
+ add.add_error = request_error;
+
+ log_debugx("completing request %d with error %d",
+ request_id, request_error);
+
+ error = ioctl(autofs_fd, AUTOFSDONE, &add);
+ if (error != 0)
+ log_warn("AUTOFSDONE");
+}
+
+/*
+ * Remove "fstype=whatever" from optionsp and return the "whatever" part.
+ */
+static char *
+pick_option(const char *option, char **optionsp)
+{
+ char *tofree, *pair, *newoptions;
+ char *picked = NULL;
+ bool first = true;
+
+ tofree = *optionsp;
+
+ newoptions = calloc(strlen(*optionsp) + 1, 1);
+ if (newoptions == NULL)
+ log_err(1, "calloc");
+
+ while ((pair = strsep(optionsp, ",")) != NULL) {
+ /*
+ * XXX: strncasecmp(3) perhaps?
+ */
+ if (strncmp(pair, option, strlen(option)) == 0) {
+ picked = checked_strdup(pair + strlen(option));
+ } else {
+ if (first == false)
+ strcat(newoptions, ",");
+ else
+ first = false;
+ strcat(newoptions, pair);
+ }
+ }
+
+ free(tofree);
+ *optionsp = newoptions;
+
+ return (picked);
+}
+
+static void
+create_subtree(const struct node *node, bool incomplete)
+{
+ const struct node *child;
+ char *path;
+ bool wildcard_found = false;
+
+ /*
+ * Skip wildcard nodes.
+ */
+ if (strcmp(node->n_key, "*") == 0)
+ return;
+
+ path = node_path(node);
+ log_debugx("creating subtree at %s", path);
+ create_directory(path);
+
+ if (incomplete) {
+ TAILQ_FOREACH(child, &node->n_children, n_next) {
+ if (strcmp(child->n_key, "*") == 0) {
+ wildcard_found = true;
+ break;
+ }
+ }
+
+ if (wildcard_found) {
+ log_debugx("node %s contains wildcard entry; "
+ "not creating its subdirectories due to -d flag",
+ path);
+ free(path);
+ return;
+ }
+ }
+
+ free(path);
+
+ TAILQ_FOREACH(child, &node->n_children, n_next)
+ create_subtree(child, incomplete);
+}
+
+static void
+exit_callback(void)
+{
+
+ done(EIO, true);
+}
+
+static void
+handle_request(const struct autofs_daemon_request *adr, char *cmdline_options,
+ bool incomplete_hierarchy)
+{
+ const char *map;
+ struct node *root, *parent, *node;
+ FILE *f;
+ char *key, *options, *fstype, *nobrowse, *retrycnt, *tmp;
+ int error;
+ bool wildcards;
+
+ log_debugx("got request %d: from %s, path %s, prefix \"%s\", "
+ "key \"%s\", options \"%s\"", adr->adr_id, adr->adr_from,
+ adr->adr_path, adr->adr_prefix, adr->adr_key, adr->adr_options);
+
+ /*
+ * Try to notify the kernel about any problems.
+ */
+ request_id = adr->adr_id;
+ atexit(exit_callback);
+
+ if (strncmp(adr->adr_from, "map ", 4) != 0) {
+ log_errx(1, "invalid mountfrom \"%s\"; failing request",
+ adr->adr_from);
+ }
+
+ map = adr->adr_from + 4; /* 4 for strlen("map "); */
+ root = node_new_root();
+ if (adr->adr_prefix[0] == '\0' || strcmp(adr->adr_prefix, "/") == 0) {
+ /*
+ * Direct map. autofs(4) doesn't have a way to determine
+ * correct map key, but since it's a direct map, we can just
+ * use adr_path instead.
+ */
+ parent = root;
+ key = checked_strdup(adr->adr_path);
+ } else {
+ /*
+ * Indirect map.
+ */
+ parent = node_new_map(root, checked_strdup(adr->adr_prefix),
+ NULL, checked_strdup(map),
+ checked_strdup("[kernel request]"), lineno);
+
+ if (adr->adr_key[0] == '\0')
+ key = NULL;
+ else
+ key = checked_strdup(adr->adr_key);
+ }
+
+ /*
+ * "Wildcards" here actually means "make autofs(4) request
+ * automountd(8) action if the node being looked up does not
+ * exist, even though the parent is marked as cached". This
+ * needs to be done for maps with wildcard entries, but also
+ * for special and executable maps.
+ */
+ parse_map(parent, map, key, &wildcards);
+ if (!wildcards)
+ wildcards = node_has_wildcards(parent);
+ if (wildcards)
+ log_debugx("map may contain wildcard entries");
+ else
+ log_debugx("map does not contain wildcard entries");
+
+ if (key != NULL)
+ node_expand_wildcard(root, key);
+
+ node = node_find(root, adr->adr_path);
+ if (node == NULL) {
+ log_errx(1, "map %s does not contain key for \"%s\"; "
+ "failing mount", map, adr->adr_path);
+ }
+
+ options = node_options(node);
+
+ /*
+ * Append options from auto_master.
+ */
+ options = concat(options, ',', adr->adr_options);
+
+ /*
+ * Prepend options passed via automountd(8) command line.
+ */
+ options = concat(cmdline_options, ',', options);
+
+ if (node->n_location == NULL) {
+ log_debugx("found node defined at %s:%d; not a mountpoint",
+ node->n_config_file, node->n_config_line);
+
+ nobrowse = pick_option("nobrowse", &options);
+ if (nobrowse != NULL && key == NULL) {
+ log_debugx("skipping map %s due to \"nobrowse\" "
+ "option; exiting", map);
+ done(0, true);
+
+ /*
+ * Exit without calling exit_callback().
+ */
+ quick_exit(0);
+ }
+
+ /*
+ * Not a mountpoint; create directories in the autofs mount
+ * and complete the request.
+ */
+ create_subtree(node, incomplete_hierarchy);
+
+ if (incomplete_hierarchy && key != NULL) {
+ /*
+ * We still need to create the single subdirectory
+ * user is trying to access.
+ */
+ tmp = concat(adr->adr_path, '/', key);
+ node = node_find(root, tmp);
+ if (node != NULL)
+ create_subtree(node, false);
+ }
+
+ log_debugx("nothing to mount; exiting");
+ done(0, wildcards);
+
+ /*
+ * Exit without calling exit_callback().
+ */
+ quick_exit(0);
+ }
+
+ log_debugx("found node defined at %s:%d; it is a mountpoint",
+ node->n_config_file, node->n_config_line);
+
+ if (key != NULL)
+ node_expand_ampersand(node, key);
+ error = node_expand_defined(node);
+ if (error != 0) {
+ log_errx(1, "variable expansion failed for %s; "
+ "failing mount", adr->adr_path);
+ }
+
+ /*
+ * Append "automounted".
+ */
+ options = concat(options, ',', "automounted");
+
+ /*
+ * Remove "nobrowse", mount(8) doesn't understand it.
+ */
+ pick_option("nobrowse", &options);
+
+ /*
+ * Figure out fstype.
+ */
+ fstype = pick_option("fstype=", &options);
+ if (fstype == NULL) {
+ log_debugx("fstype not specified in options; "
+ "defaulting to \"nfs\"");
+ fstype = checked_strdup("nfs");
+ }
+
+ if (strcmp(fstype, "nfs") == 0) {
+ /*
+ * The mount_nfs(8) command defaults to retry undefinitely.
+ * We do not want that behaviour, because it leaves mount_nfs(8)
+ * instances and automountd(8) children hanging forever.
+ * Disable retries unless the option was passed explicitly.
+ */
+ retrycnt = pick_option("retrycnt=", &options);
+ if (retrycnt == NULL) {
+ log_debugx("retrycnt not specified in options; "
+ "defaulting to 1");
+ options = concat(options, ',', "retrycnt=1");
+ } else {
+ options = concat(options, ',',
+ concat("retrycnt", '=', retrycnt));
+ }
+ }
+
+ f = auto_popen("mount", "-t", fstype, "-o", options,
+ node->n_location, adr->adr_path, NULL);
+ assert(f != NULL);
+ error = auto_pclose(f);
+ if (error != 0)
+ log_errx(1, "mount failed");
+
+ log_debugx("mount done; exiting");
+ done(0, wildcards);
+
+ /*
+ * Exit without calling exit_callback().
+ */
+ quick_exit(0);
+}
+
+static void
+sigchld_handler(int dummy __unused)
+{
+
+ /*
+ * The only purpose of this handler is to make SIGCHLD
+ * interrupt the AUTOFSREQUEST ioctl(2), so we can call
+ * wait_for_children().
+ */
+}
+
+static void
+register_sigchld(void)
+{
+ struct sigaction sa;
+ int error;
+
+ bzero(&sa, sizeof(sa));
+ sa.sa_handler = sigchld_handler;
+ sigfillset(&sa.sa_mask);
+ error = sigaction(SIGCHLD, &sa, NULL);
+ if (error != 0)
+ log_err(1, "sigaction");
+
+}
+
+
+static int
+wait_for_children(bool block)
+{
+ pid_t pid;
+ int status;
+ int num = 0;
+
+ for (;;) {
+ /*
+ * If "block" is true, wait for at least one process.
+ */
+ if (block && num == 0)
+ pid = wait4(-1, &status, 0, NULL);
+ else
+ pid = wait4(-1, &status, WNOHANG, NULL);
+ if (pid <= 0)
+ break;
+ if (WIFSIGNALED(status)) {
+ log_warnx("child process %d terminated with signal %d",
+ pid, WTERMSIG(status));
+ } else if (WEXITSTATUS(status) != 0) {
+ log_debugx("child process %d terminated with exit status %d",
+ pid, WEXITSTATUS(status));
+ } else {
+ log_debugx("child process %d terminated gracefully", pid);
+ }
+ num++;
+ }
+
+ return (num);
+}
+
+static void
+usage_automountd(void)
+{
+
+ fprintf(stderr, "usage: automountd [-D name=value][-m maxproc]"
+ "[-o opts][-Tidv]\n");
+ exit(1);
+}
+
+int
+main_automountd(int argc, char **argv)
+{
+ struct pidfh *pidfh;
+ pid_t pid, otherpid;
+ const char *pidfile_path = AUTOMOUNTD_PIDFILE;
+ char *options = NULL;
+ struct autofs_daemon_request request;
+ int ch, debug = 0, error, maxproc = 30, retval, saved_errno;
+ bool dont_daemonize = false, incomplete_hierarchy = false;
+
+ defined_init();
+
+ while ((ch = getopt(argc, argv, "D:Tdim:o:v")) != -1) {
+ switch (ch) {
+ case 'D':
+ defined_parse_and_add(optarg);
+ break;
+ case 'T':
+ /*
+ * For compatibility with other implementations,
+ * such as OS X.
+ */
+ debug++;
+ break;
+ case 'd':
+ dont_daemonize = true;
+ debug++;
+ break;
+ case 'i':
+ incomplete_hierarchy = true;
+ break;
+ case 'm':
+ maxproc = atoi(optarg);
+ break;
+ case 'o':
+ options = concat(options, ',', optarg);
+ break;
+ case 'v':
+ debug++;
+ break;
+ case '?':
+ default:
+ usage_automountd();
+ }
+ }
+ argc -= optind;
+ if (argc != 0)
+ usage_automountd();
+
+ log_init(debug);
+
+ pidfh = pidfile_open(pidfile_path, 0600, &otherpid);
+ if (pidfh == NULL) {
+ if (errno == EEXIST) {
+ log_errx(1, "daemon already running, pid: %jd.",
+ (intmax_t)otherpid);
+ }
+ log_err(1, "cannot open or create pidfile \"%s\"",
+ pidfile_path);
+ }
+
+ autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
+ if (autofs_fd < 0 && errno == ENOENT) {
+ saved_errno = errno;
+ retval = kldload("autofs");
+ if (retval != -1)
+ autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
+ else
+ errno = saved_errno;
+ }
+ if (autofs_fd < 0)
+ log_err(1, "failed to open %s", AUTOFS_PATH);
+
+ if (dont_daemonize == false) {
+ if (daemon(0, 0) == -1) {
+ log_warn("cannot daemonize");
+ pidfile_remove(pidfh);
+ exit(1);
+ }
+ } else {
+ lesser_daemon();
+ }
+
+ pidfile_write(pidfh);
+
+ register_sigchld();
+
+ for (;;) {
+ log_debugx("waiting for request from the kernel");
+
+ memset(&request, 0, sizeof(request));
+ error = ioctl(autofs_fd, AUTOFSREQUEST, &request);
+ if (error != 0) {
+ if (errno == EINTR) {
+ nchildren -= wait_for_children(false);
+ assert(nchildren >= 0);
+ continue;
+ }
+
+ log_err(1, "AUTOFSREQUEST");
+ }
+
+ if (dont_daemonize) {
+ log_debugx("not forking due to -d flag; "
+ "will exit after servicing a single request");
+ } else {
+ nchildren -= wait_for_children(false);
+ assert(nchildren >= 0);
+
+ while (maxproc > 0 && nchildren >= maxproc) {
+ log_debugx("maxproc limit of %d child processes hit; "
+ "waiting for child process to exit", maxproc);
+ nchildren -= wait_for_children(true);
+ assert(nchildren >= 0);
+ }
+ log_debugx("got request; forking child process #%d",
+ nchildren);
+ nchildren++;
+
+ pid = fork();
+ if (pid < 0)
+ log_err(1, "fork");
+ if (pid > 0)
+ continue;
+ }
+
+ pidfile_close(pidfh);
+ handle_request(&request, options, incomplete_hierarchy);
+ }
+
+ pidfile_close(pidfh);
+
+ return (0);
+}
+
diff --git a/usr.sbin/autofs/autounmountd.8 b/usr.sbin/autofs/autounmountd.8
new file mode 100644
index 0000000..bf7afa4
--- /dev/null
+++ b/usr.sbin/autofs/autounmountd.8
@@ -0,0 +1,88 @@
+.\" Copyright (c) 2014 The FreeBSD Foundation
+.\" All rights reserved.
+.\"
+.\" This software was 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 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 13, 2014
+.Dt AUTOUNMOUNTD 8
+.Os
+.Sh NAME
+.Nm autounmountd
+.Nd daemon unmounting automounted filesystems
+.Sh SYNOPSIS
+.Nm
+.Op Fl d
+.Op Fl r Ar time
+.Op Fl t Ar time
+.Op Fl v
+.Sh DESCRIPTION
+The
+.Nm
+daemon is responsible for unmounting filesystems mounted by
+.Xr automountd 8 .
+On startup,
+.Nm
+retrieves a list of filesystems that have the
+.Li automounted
+mount option set.
+The list is updated every time a filesystem is mounted or unmounted.
+After a specified time passes,
+.Nm
+attempts to unmount a filesystem, retrying after some time if necessary.
+.Pp
+These options are available:
+.Bl -tag -width ".Fl v"
+.It Fl d
+Debug mode: increase verbosity and do not daemonize.
+.It Fl r
+Number of seconds to wait before trying to unmount an expired filesystem
+after a previous attempt failed, possibly due to filesystem being busy.
+The default value is 600, or ten minutes.
+.It Fl t
+Number of seconds to wait before trying to unmount a filesystem.
+The default value is 600, or ten minutes.
+.It Fl v
+Increase verbosity.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr auto_master 5 ,
+.Xr autofs 5 ,
+.Xr automount 8 ,
+.Xr automountd 8
+.Sh HISTORY
+The
+.Nm
+daemon appeared in
+.Fx 10.1 .
+.Sh AUTHORS
+The
+.Nm
+was developed by
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+under sponsorship from the FreeBSD Foundation.
diff --git a/usr.sbin/autofs/autounmountd.c b/usr.sbin/autofs/autounmountd.c
new file mode 100644
index 0000000..b85f3ca
--- /dev/null
+++ b/usr.sbin/autofs/autounmountd.c
@@ -0,0 +1,351 @@
+/*-
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/mount.h>
+#include <sys/time.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <libutil.h>
+
+#include "common.h"
+
+#define AUTOUNMOUNTD_PIDFILE "/var/run/autounmountd.pid"
+
+struct automounted_fs {
+ TAILQ_ENTRY(automounted_fs) af_next;
+ time_t af_mount_time;
+ bool af_mark;
+ fsid_t af_fsid;
+ char af_mountpoint[MNAMELEN];
+};
+
+static TAILQ_HEAD(, automounted_fs) automounted;
+
+static struct automounted_fs *
+automounted_find(fsid_t fsid)
+{
+ struct automounted_fs *af;
+
+ TAILQ_FOREACH(af, &automounted, af_next) {
+ if (af->af_fsid.val[0] == fsid.val[0] &&
+ af->af_fsid.val[1] == fsid.val[1])
+ return (af);
+ }
+
+ return (NULL);
+}
+
+static struct automounted_fs *
+automounted_add(fsid_t fsid, const char *mountpoint)
+{
+ struct automounted_fs *af;
+
+ af = calloc(sizeof(*af), 1);
+ if (af == NULL)
+ log_err(1, "calloc");
+ af->af_mount_time = time(NULL);
+ af->af_fsid = fsid;
+ strlcpy(af->af_mountpoint, mountpoint, sizeof(af->af_mountpoint));
+
+ TAILQ_INSERT_TAIL(&automounted, af, af_next);
+
+ return (af);
+}
+
+static void
+automounted_remove(struct automounted_fs *af)
+{
+
+ TAILQ_REMOVE(&automounted, af, af_next);
+ free(af);
+}
+
+static void
+refresh_automounted(void)
+{
+ struct automounted_fs *af, *tmpaf;
+ struct statfs *mntbuf;
+ int i, nitems;
+
+ nitems = getmntinfo(&mntbuf, MNT_WAIT);
+ if (nitems <= 0)
+ log_err(1, "getmntinfo");
+
+ log_debugx("refreshing list of automounted filesystems");
+
+ TAILQ_FOREACH(af, &automounted, af_next)
+ af->af_mark = false;
+
+ for (i = 0; i < nitems; i++) {
+ if (strcmp(mntbuf[i].f_fstypename, "autofs") == 0) {
+ log_debugx("skipping %s, filesystem type is autofs",
+ mntbuf[i].f_mntonname);
+ continue;
+ }
+
+ if ((mntbuf[i].f_flags & MNT_AUTOMOUNTED) == 0) {
+ log_debugx("skipping %s, not automounted",
+ mntbuf[i].f_mntonname);
+ continue;
+ }
+
+ af = automounted_find(mntbuf[i].f_fsid);
+ if (af == NULL) {
+ log_debugx("new automounted filesystem found on %s "
+ "(FSID:%d:%d)", mntbuf[i].f_mntonname,
+ mntbuf[i].f_fsid.val[0], mntbuf[i].f_fsid.val[1]);
+ af = automounted_add(mntbuf[i].f_fsid,
+ mntbuf[i].f_mntonname);
+ } else {
+ log_debugx("already known automounted filesystem "
+ "found on %s (FSID:%d:%d)", mntbuf[i].f_mntonname,
+ mntbuf[i].f_fsid.val[0], mntbuf[i].f_fsid.val[1]);
+ }
+ af->af_mark = true;
+ }
+
+ TAILQ_FOREACH_SAFE(af, &automounted, af_next, tmpaf) {
+ if (af->af_mark)
+ continue;
+ log_debugx("lost filesystem mounted on %s (FSID:%d:%d)",
+ af->af_mountpoint, af->af_fsid.val[0], af->af_fsid.val[1]);
+ automounted_remove(af);
+ }
+}
+
+static int
+unmount_by_fsid(const fsid_t fsid, const char *mountpoint)
+{
+ char *fsid_str;
+ int error, ret;
+
+ ret = asprintf(&fsid_str, "FSID:%d:%d", fsid.val[0], fsid.val[1]);
+ if (ret < 0)
+ log_err(1, "asprintf");
+
+ error = unmount(fsid_str, MNT_BYFSID);
+ if (error != 0) {
+ if (errno == EBUSY) {
+ log_debugx("cannot unmount %s (%s): %s",
+ mountpoint, fsid_str, strerror(errno));
+ } else {
+ log_warn("cannot unmount %s (%s)",
+ mountpoint, fsid_str);
+ }
+ }
+
+ free(fsid_str);
+
+ return (error);
+}
+
+static double
+expire_automounted(double expiration_time)
+{
+ struct automounted_fs *af, *tmpaf;
+ time_t now;
+ double mounted_for, mounted_max = -1.0;
+ int error;
+
+ now = time(NULL);
+
+ log_debugx("expiring automounted filesystems");
+
+ TAILQ_FOREACH_SAFE(af, &automounted, af_next, tmpaf) {
+ mounted_for = difftime(now, af->af_mount_time);
+
+ if (mounted_for < expiration_time) {
+ log_debugx("skipping %s (FSID:%d:%d), mounted "
+ "for %.0f seconds", af->af_mountpoint,
+ af->af_fsid.val[0], af->af_fsid.val[1],
+ mounted_for);
+
+ if (mounted_for > mounted_max)
+ mounted_max = mounted_for;
+
+ continue;
+ }
+
+ log_debugx("filesystem mounted on %s (FSID:%d:%d), "
+ "was mounted for %.0f seconds; unmounting",
+ af->af_mountpoint, af->af_fsid.val[0], af->af_fsid.val[1],
+ mounted_for);
+ error = unmount_by_fsid(af->af_fsid, af->af_mountpoint);
+ if (error != 0) {
+ if (mounted_for > mounted_max)
+ mounted_max = mounted_for;
+ }
+ }
+
+ return (mounted_max);
+}
+
+static void
+usage_autounmountd(void)
+{
+
+ fprintf(stderr, "usage: autounmountd [-r time][-t time][-dv]\n");
+ exit(1);
+}
+
+static void
+do_wait(int kq, double sleep_time)
+{
+ struct timespec timeout;
+ struct kevent unused;
+ int nevents;
+
+ if (sleep_time != -1.0) {
+ assert(sleep_time > 0.0);
+ timeout.tv_sec = sleep_time;
+ timeout.tv_nsec = 0;
+
+ log_debugx("waiting for filesystem event for %.0f seconds", sleep_time);
+ nevents = kevent(kq, NULL, 0, &unused, 1, &timeout);
+ } else {
+ log_debugx("waiting for filesystem event");
+ nevents = kevent(kq, NULL, 0, &unused, 1, NULL);
+ }
+ if (nevents < 0)
+ log_err(1, "kevent");
+
+ if (nevents == 0) {
+ log_debugx("timeout reached");
+ assert(sleep_time > 0.0);
+ } else {
+ log_debugx("got filesystem event");
+ }
+}
+
+int
+main_autounmountd(int argc, char **argv)
+{
+ struct kevent event;
+ struct pidfh *pidfh;
+ pid_t otherpid;
+ const char *pidfile_path = AUTOUNMOUNTD_PIDFILE;
+ int ch, debug = 0, error, kq;
+ double expiration_time = 600, retry_time = 600, mounted_max, sleep_time;
+ bool dont_daemonize = false;
+
+ while ((ch = getopt(argc, argv, "dr:t:v")) != -1) {
+ switch (ch) {
+ case 'd':
+ dont_daemonize = true;
+ debug++;
+ break;
+ case 'r':
+ retry_time = atoi(optarg);
+ break;
+ case 't':
+ expiration_time = atoi(optarg);
+ break;
+ case 'v':
+ debug++;
+ break;
+ case '?':
+ default:
+ usage_autounmountd();
+ }
+ }
+ argc -= optind;
+ if (argc != 0)
+ usage_autounmountd();
+
+ if (retry_time <= 0)
+ log_errx(1, "retry time must be greater than zero");
+ if (expiration_time <= 0)
+ log_errx(1, "expiration time must be greater than zero");
+
+ log_init(debug);
+
+ pidfh = pidfile_open(pidfile_path, 0600, &otherpid);
+ if (pidfh == NULL) {
+ if (errno == EEXIST) {
+ log_errx(1, "daemon already running, pid: %jd.",
+ (intmax_t)otherpid);
+ }
+ log_err(1, "cannot open or create pidfile \"%s\"",
+ pidfile_path);
+ }
+
+ if (dont_daemonize == false) {
+ if (daemon(0, 0) == -1) {
+ log_warn("cannot daemonize");
+ pidfile_remove(pidfh);
+ exit(1);
+ }
+ }
+
+ pidfile_write(pidfh);
+
+ TAILQ_INIT(&automounted);
+
+ kq = kqueue();
+ if (kq < 0)
+ log_err(1, "kqueue");
+
+ EV_SET(&event, 0, EVFILT_FS, EV_ADD | EV_CLEAR, 0, 0, NULL);
+ error = kevent(kq, &event, 1, NULL, 0, NULL);
+ if (error < 0)
+ log_err(1, "kevent");
+
+ for (;;) {
+ refresh_automounted();
+ mounted_max = expire_automounted(expiration_time);
+ if (mounted_max == -1.0) {
+ sleep_time = mounted_max;
+ log_debugx("no filesystems to expire");
+ } else if (mounted_max < expiration_time) {
+ sleep_time = difftime(expiration_time, mounted_max);
+ log_debugx("some filesystems expire in %.0f seconds",
+ sleep_time);
+ } else {
+ sleep_time = retry_time;
+ log_debugx("some expired filesystems remain mounted, "
+ "will retry in %.0f seconds", sleep_time);
+ }
+
+ do_wait(kq, sleep_time);
+ }
+
+ return (0);
+}
diff --git a/usr.sbin/autofs/common.c b/usr.sbin/autofs/common.c
new file mode 100644
index 0000000..eae118f
--- /dev/null
+++ b/usr.sbin/autofs/common.c
@@ -0,0 +1,1225 @@
+/*-
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/utsname.h>
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <netdb.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#define _WITH_GETLINE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libutil.h>
+
+#include "autofs_ioctl.h"
+
+#include "common.h"
+
+extern FILE *yyin;
+extern char *yytext;
+extern int yylex(void);
+
+static void parse_master_yyin(struct node *root, const char *master);
+static void parse_map_yyin(struct node *parent, const char *map,
+ const char *executable_key);
+
+char *
+checked_strdup(const char *s)
+{
+ char *c;
+
+ assert(s != NULL);
+
+ c = strdup(s);
+ if (c == NULL)
+ log_err(1, "strdup");
+ return (c);
+}
+
+/*
+ * Concatenate two strings, inserting separator between them, unless not needed.
+ */
+char *
+concat(const char *s1, char separator, const char *s2)
+{
+ char *result;
+ char s1last, s2first;
+ int ret;
+
+ if (s1 == NULL)
+ s1 = "";
+ if (s2 == NULL)
+ s2 = "";
+
+ if (s1[0] == '\0')
+ s1last = '\0';
+ else
+ s1last = s1[strlen(s1) - 1];
+
+ s2first = s2[0];
+
+ if (s1last == separator && s2first == separator) {
+ /*
+ * If s1 ends with the separator and s2 begins with
+ * it - skip the latter; otherwise concatenating "/"
+ * and "/foo" would end up returning "//foo".
+ */
+ ret = asprintf(&result, "%s%s", s1, s2 + 1);
+ } else if (s1last == separator || s2first == separator ||
+ s1[0] == '\0' || s2[0] == '\0') {
+ ret = asprintf(&result, "%s%s", s1, s2);
+ } else {
+ ret = asprintf(&result, "%s%c%s", s1, separator, s2);
+ }
+ if (ret < 0)
+ log_err(1, "asprintf");
+
+ //log_debugx("%s: got %s and %s, returning %s", __func__, s1, s2, result);
+
+ return (result);
+}
+
+void
+create_directory(const char *path)
+{
+ char *component, *copy, *tofree, *partial, *tmp;
+ int error;
+
+ assert(path[0] == '/');
+
+ /*
+ * +1 to skip the leading slash.
+ */
+ copy = tofree = checked_strdup(path + 1);
+
+ partial = checked_strdup("");
+ for (;;) {
+ component = strsep(&copy, "/");
+ if (component == NULL)
+ break;
+ tmp = concat(partial, '/', component);
+ free(partial);
+ partial = tmp;
+ //log_debugx("creating \"%s\"", partial);
+ error = mkdir(partial, 0755);
+ if (error != 0 && errno != EEXIST) {
+ log_warn("cannot create %s", partial);
+ return;
+ }
+ }
+
+ free(tofree);
+}
+
+struct node *
+node_new_root(void)
+{
+ struct node *n;
+
+ n = calloc(1, sizeof(*n));
+ if (n == NULL)
+ log_err(1, "calloc");
+ // XXX
+ n->n_key = checked_strdup("/");
+ n->n_options = checked_strdup("");
+
+ TAILQ_INIT(&n->n_children);
+
+ return (n);
+}
+
+struct node *
+node_new(struct node *parent, char *key, char *options, char *location,
+ const char *config_file, int config_line)
+{
+ struct node *n;
+
+ n = calloc(1, sizeof(*n));
+ if (n == NULL)
+ log_err(1, "calloc");
+
+ TAILQ_INIT(&n->n_children);
+ assert(key != NULL);
+ assert(key[0] != '\0');
+ n->n_key = key;
+ if (options != NULL)
+ n->n_options = options;
+ else
+ n->n_options = strdup("");
+ n->n_location = location;
+ assert(config_file != NULL);
+ n->n_config_file = config_file;
+ assert(config_line >= 0);
+ n->n_config_line = config_line;
+
+ assert(parent != NULL);
+ n->n_parent = parent;
+ TAILQ_INSERT_TAIL(&parent->n_children, n, n_next);
+
+ return (n);
+}
+
+struct node *
+node_new_map(struct node *parent, char *key, char *options, char *map,
+ const char *config_file, int config_line)
+{
+ struct node *n;
+
+ n = calloc(1, sizeof(*n));
+ if (n == NULL)
+ log_err(1, "calloc");
+
+ TAILQ_INIT(&n->n_children);
+ assert(key != NULL);
+ assert(key[0] != '\0');
+ n->n_key = key;
+ if (options != NULL)
+ n->n_options = options;
+ else
+ n->n_options = strdup("");
+ n->n_map = map;
+ assert(config_file != NULL);
+ n->n_config_file = config_file;
+ assert(config_line >= 0);
+ n->n_config_line = config_line;
+
+ assert(parent != NULL);
+ n->n_parent = parent;
+ TAILQ_INSERT_TAIL(&parent->n_children, n, n_next);
+
+ return (n);
+}
+
+static struct node *
+node_duplicate(const struct node *o, struct node *parent)
+{
+ const struct node *child;
+ struct node *n;
+
+ if (parent == NULL)
+ parent = o->n_parent;
+
+ n = node_new(parent, o->n_key, o->n_options, o->n_location,
+ o->n_config_file, o->n_config_line);
+
+ TAILQ_FOREACH(child, &o->n_children, n_next)
+ node_duplicate(child, n);
+
+ return (n);
+}
+
+static void
+node_delete(struct node *n)
+{
+ struct node *child, *tmp;
+
+ assert (n != NULL);
+
+ TAILQ_FOREACH_SAFE(child, &n->n_children, n_next, tmp)
+ node_delete(child);
+
+ if (n->n_parent != NULL)
+ TAILQ_REMOVE(&n->n_parent->n_children, n, n_next);
+
+ free(n);
+}
+
+/*
+ * Move (reparent) node 'n' to make it sibling of 'previous', placed
+ * just after it.
+ */
+static void
+node_move_after(struct node *n, struct node *previous)
+{
+
+ TAILQ_REMOVE(&n->n_parent->n_children, n, n_next);
+ n->n_parent = previous->n_parent;
+ TAILQ_INSERT_AFTER(&previous->n_parent->n_children, previous, n, n_next);
+}
+
+static void
+node_expand_includes(struct node *root, bool is_master)
+{
+ struct node *n, *n2, *tmp, *tmp2, *tmproot;
+ int error;
+
+ TAILQ_FOREACH_SAFE(n, &root->n_children, n_next, tmp) {
+ if (n->n_key[0] != '+')
+ continue;
+
+ error = access(AUTO_INCLUDE_PATH, F_OK);
+ if (error != 0) {
+ log_errx(1, "directory services not configured; "
+ "%s does not exist", AUTO_INCLUDE_PATH);
+ }
+
+ /*
+ * "+1" to skip leading "+".
+ */
+ yyin = auto_popen(AUTO_INCLUDE_PATH, n->n_key + 1, NULL);
+ assert(yyin != NULL);
+
+ tmproot = node_new_root();
+ if (is_master)
+ parse_master_yyin(tmproot, n->n_key);
+ else
+ parse_map_yyin(tmproot, n->n_key, NULL);
+
+ error = auto_pclose(yyin);
+ yyin = NULL;
+ if (error != 0) {
+ log_errx(1, "failed to handle include \"%s\"",
+ n->n_key);
+ }
+
+ /*
+ * Entries to be included are now in tmproot. We need to merge
+ * them with the rest, preserving their place and ordering.
+ */
+ TAILQ_FOREACH_REVERSE_SAFE(n2,
+ &tmproot->n_children, nodehead, n_next, tmp2) {
+ node_move_after(n2, n);
+ }
+
+ node_delete(n);
+ node_delete(tmproot);
+ }
+}
+
+static char *
+expand_ampersand(char *string, const char *key)
+{
+ char c, *expanded;
+ int i, ret, before_len = 0;
+ bool backslashed = false;
+
+ assert(key[0] != '\0');
+
+ expanded = checked_strdup(string);
+
+ for (i = 0; string[i] != '\0'; i++) {
+ c = string[i];
+ if (c == '\\' && backslashed == false) {
+ backslashed = true;
+ continue;
+ }
+ if (backslashed) {
+ backslashed = false;
+ continue;
+ }
+ backslashed = false;
+ if (c != '&')
+ continue;
+
+ /*
+ * The 'before_len' variable contains the number
+ * of characters before the '&'.
+ */
+ before_len = i;
+ //assert(i + 1 < (int)strlen(string));
+
+ ret = asprintf(&expanded, "%.*s%s%s",
+ before_len, string, key, string + before_len + 1);
+ if (ret < 0)
+ log_err(1, "asprintf");
+
+ //log_debugx("\"%s\" expanded with key \"%s\" to \"%s\"",
+ // string, key, expanded);
+
+ /*
+ * Figure out where to start searching for next variable.
+ */
+ string = expanded;
+ i = before_len + strlen(key);
+ backslashed = false;
+ //assert(i < (int)strlen(string));
+ }
+
+ return (expanded);
+}
+
+/*
+ * Expand "&" in n_location. If the key is NULL, try to use
+ * key from map entries themselves. Keep in mind that maps
+ * consist of tho levels of node structures, the key is one
+ * level up.
+ *
+ * Variant with NULL key is for "automount -LL".
+ */
+void
+node_expand_ampersand(struct node *n, const char *key)
+{
+ struct node *child;
+
+ if (n->n_location != NULL) {
+ if (key == NULL) {
+ if (n->n_parent != NULL &&
+ strcmp(n->n_parent->n_key, "*") != 0) {
+ n->n_location = expand_ampersand(n->n_location,
+ n->n_parent->n_key);
+ }
+ } else {
+ n->n_location = expand_ampersand(n->n_location, key);
+ }
+ }
+
+ TAILQ_FOREACH(child, &n->n_children, n_next)
+ node_expand_ampersand(child, key);
+}
+
+/*
+ * Expand "*" in n_key.
+ */
+void
+node_expand_wildcard(struct node *n, const char *key)
+{
+ struct node *child, *expanded;
+
+ assert(key != NULL);
+
+ if (strcmp(n->n_key, "*") == 0) {
+ expanded = node_duplicate(n, NULL);
+ expanded->n_key = checked_strdup(key);
+ node_move_after(expanded, n);
+ }
+
+ TAILQ_FOREACH(child, &n->n_children, n_next)
+ node_expand_wildcard(child, key);
+}
+
+int
+node_expand_defined(struct node *n)
+{
+ struct node *child;
+ int error, cumulated_error = 0;
+
+ if (n->n_location != NULL) {
+ n->n_location = defined_expand(n->n_location);
+ if (n->n_location == NULL) {
+ log_warnx("failed to expand location for %s",
+ node_path(n));
+ return (EINVAL);
+ }
+ }
+
+ TAILQ_FOREACH(child, &n->n_children, n_next) {
+ error = node_expand_defined(child);
+ if (error != 0 && cumulated_error == 0)
+ cumulated_error = error;
+ }
+
+ return (cumulated_error);
+}
+
+static bool
+node_is_direct_key(const struct node *n)
+{
+
+ if (n->n_parent != NULL && n->n_parent->n_parent == NULL &&
+ strcmp(n->n_key, "/-") == 0) {
+ return (true);
+ }
+
+ return (false);
+}
+
+bool
+node_is_direct_map(const struct node *n)
+{
+
+ for (;;) {
+ assert(n->n_parent != NULL);
+ if (n->n_parent->n_parent == NULL)
+ break;
+ n = n->n_parent;
+ }
+
+ return (node_is_direct_key(n));
+}
+
+bool
+node_has_wildcards(const struct node *n)
+{
+ const struct node *child;
+
+ TAILQ_FOREACH(child, &n->n_children, n_next) {
+ if (strcmp(child->n_key, "*") == 0)
+ return (true);
+ }
+
+ return (false);
+}
+
+static void
+node_expand_maps(struct node *n, bool indirect)
+{
+ struct node *child, *tmp;
+
+ TAILQ_FOREACH_SAFE(child, &n->n_children, n_next, tmp) {
+ if (node_is_direct_map(child)) {
+ if (indirect)
+ continue;
+ } else {
+ if (indirect == false)
+ continue;
+ }
+
+ /*
+ * This is the first-level map node; the one that contains
+ * the key and subnodes with mountpoints and actual map names.
+ */
+ if (child->n_map == NULL)
+ continue;
+
+ if (indirect) {
+ log_debugx("map \"%s\" is an indirect map, parsing",
+ child->n_map);
+ } else {
+ log_debugx("map \"%s\" is a direct map, parsing",
+ child->n_map);
+ }
+ parse_map(child, child->n_map, NULL, NULL);
+ }
+}
+
+static void
+node_expand_direct_maps(struct node *n)
+{
+
+ node_expand_maps(n, false);
+}
+
+void
+node_expand_indirect_maps(struct node *n)
+{
+
+ node_expand_maps(n, true);
+}
+
+static char *
+node_path_x(const struct node *n, char *x)
+{
+ char *path;
+
+ if (n->n_parent == NULL)
+ return (x);
+
+ /*
+ * Return "/-" for direct maps only if we were asked for path
+ * to the "/-" node itself, not to any of its subnodes.
+ */
+ if (node_is_direct_key(n) && x[0] != '\0')
+ return (x);
+
+ assert(n->n_key[0] != '\0');
+ path = concat(n->n_key, '/', x);
+ free(x);
+
+ return (node_path_x(n->n_parent, path));
+}
+
+/*
+ * Return full path for node, consisting of concatenated
+ * paths of node itself and all its parents, up to the root.
+ */
+char *
+node_path(const struct node *n)
+{
+ char *path;
+ size_t len;
+
+ path = node_path_x(n, checked_strdup(""));
+
+ /*
+ * Strip trailing slash, unless the whole path is "/".
+ */
+ len = strlen(path);
+ if (len > 1 && path[len - 1] == '/')
+ path[len - 1] = '\0';
+
+ return (path);
+}
+
+static char *
+node_options_x(const struct node *n, char *x)
+{
+ char *options;
+
+ if (n == NULL)
+ return (x);
+
+ options = concat(x, ',', n->n_options);
+ free(x);
+
+ return (node_options_x(n->n_parent, options));
+}
+
+/*
+ * Return options for node, consisting of concatenated
+ * options from the node itself and all its parents,
+ * up to the root.
+ */
+char *
+node_options(const struct node *n)
+{
+
+ return (node_options_x(n, checked_strdup("")));
+}
+
+static void
+node_print_indent(const struct node *n, const char *cmdline_options,
+ int indent)
+{
+ const struct node *child, *first_child;
+ char *path, *options, *tmp;
+
+ path = node_path(n);
+ tmp = node_options(n);
+ options = concat(cmdline_options, ',', tmp);
+ free(tmp);
+
+ /*
+ * Do not show both parent and child node if they have the same
+ * mountpoint; only show the child node. This means the typical,
+ * "key location", map entries are shown in a single line;
+ * the "key mountpoint1 location2 mountpoint2 location2" entries
+ * take multiple lines.
+ */
+ first_child = TAILQ_FIRST(&n->n_children);
+ if (first_child == NULL || TAILQ_NEXT(first_child, n_next) != NULL ||
+ strcmp(path, node_path(first_child)) != 0) {
+ assert(n->n_location == NULL || n->n_map == NULL);
+ printf("%*.s%-*s %s%-*s %-*s # %s map %s at %s:%d\n",
+ indent, "",
+ 25 - indent,
+ path,
+ options[0] != '\0' ? "-" : " ",
+ 20,
+ options[0] != '\0' ? options : "",
+ 20,
+ n->n_location != NULL ? n->n_location : n->n_map != NULL ? n->n_map : "",
+ node_is_direct_map(n) ? "direct" : "indirect",
+ indent == 0 ? "referenced" : "defined",
+ n->n_config_file, n->n_config_line);
+ }
+
+ free(path);
+ free(options);
+
+ TAILQ_FOREACH(child, &n->n_children, n_next)
+ node_print_indent(child, cmdline_options, indent + 2);
+}
+
+/*
+ * Recursively print node with all its children. The cmdline_options
+ * argument is used for additional options to be prepended to all the
+ * others - usually those are the options passed by command line.
+ */
+void
+node_print(const struct node *n, const char *cmdline_options)
+{
+ const struct node *child;
+
+ TAILQ_FOREACH(child, &n->n_children, n_next)
+ node_print_indent(child, cmdline_options, 0);
+}
+
+static struct node *
+node_find_x(struct node *node, const char *path)
+{
+ struct node *child, *found;
+ char *tmp;
+ size_t tmplen;
+
+ //log_debugx("looking up %s in %s", path, node_path(node));
+
+ if (!node_is_direct_key(node)) {
+ tmp = node_path(node);
+ tmplen = strlen(tmp);
+ if (strncmp(tmp, path, tmplen) != 0) {
+ free(tmp);
+ return (NULL);
+ }
+ if (path[tmplen] != '/' && path[tmplen] != '\0') {
+ /*
+ * If we have two map entries like 'foo' and 'foobar', make
+ * sure the search for 'foobar' won't match 'foo' instead.
+ */
+ free(tmp);
+ return (NULL);
+ }
+ free(tmp);
+ }
+
+ TAILQ_FOREACH(child, &node->n_children, n_next) {
+ found = node_find_x(child, path);
+ if (found != NULL)
+ return (found);
+ }
+
+ if (node->n_parent == NULL || node_is_direct_key(node))
+ return (NULL);
+
+ return (node);
+}
+
+struct node *
+node_find(struct node *root, const char *path)
+{
+ struct node *node;
+
+ assert(root->n_parent == NULL);
+
+ node = node_find_x(root, path);
+ if (node != NULL)
+ assert(node != root);
+
+ return (node);
+}
+
+/*
+ * Canonical form of a map entry looks like this:
+ *
+ * key [-options] [ [/mountpoint] [-options2] location ... ]
+ *
+ * Entries for executable maps are slightly different, as they
+ * lack the 'key' field and are always single-line; the key field
+ * for those maps is taken from 'executable_key' argument.
+ *
+ * We parse it in such a way that a map always has two levels - first
+ * for key, and the second, for the mountpoint.
+ */
+static void
+parse_map_yyin(struct node *parent, const char *map, const char *executable_key)
+{
+ char *key = NULL, *options = NULL, *mountpoint = NULL,
+ *options2 = NULL, *location = NULL;
+ int ret;
+ struct node *node;
+
+ lineno = 1;
+
+ if (executable_key != NULL)
+ key = checked_strdup(executable_key);
+
+ for (;;) {
+ ret = yylex();
+ if (ret == 0 || ret == NEWLINE) {
+ /*
+ * In case of executable map, the key is always
+ * non-NULL, even if the map is empty. So, make sure
+ * we don't fail empty maps here.
+ */
+ if ((key != NULL && executable_key == NULL) ||
+ options != NULL) {
+ log_errx(1, "truncated entry at %s, line %d",
+ map, lineno);
+ }
+ if (ret == 0 || executable_key != NULL) {
+ /*
+ * End of file.
+ */
+ break;
+ } else {
+ key = options = NULL;
+ continue;
+ }
+ }
+ if (key == NULL) {
+ key = checked_strdup(yytext);
+ if (key[0] == '+') {
+ node_new(parent, key, NULL, NULL, map, lineno);
+ key = options = NULL;
+ continue;
+ }
+ continue;
+ } else if (yytext[0] == '-') {
+ if (options != NULL) {
+ log_errx(1, "duplicated options at %s, line %d",
+ map, lineno);
+ }
+ /*
+ * +1 to skip leading "-".
+ */
+ options = checked_strdup(yytext + 1);
+ continue;
+ }
+
+ /*
+ * We cannot properly handle a situation where the map key
+ * is "/". Ignore such entries.
+ *
+ * XXX: According to Piete Brooks, Linux automounter uses
+ * "/" as a wildcard character in LDAP maps. Perhaps
+ * we should work around this braindamage by substituting
+ * "*" for "/"?
+ */
+ if (strcmp(key, "/") == 0) {
+ log_warnx("nonsensical map key \"/\" at %s, line %d; "
+ "ignoring map entry ", map, lineno);
+
+ /*
+ * Skip the rest of the entry.
+ */
+ do {
+ ret = yylex();
+ } while (ret != 0 && ret != NEWLINE);
+
+ key = options = NULL;
+ continue;
+ }
+
+ //log_debugx("adding map node, %s", key);
+ node = node_new(parent, key, options, NULL, map, lineno);
+ key = options = NULL;
+
+ for (;;) {
+ if (yytext[0] == '/') {
+ if (mountpoint != NULL) {
+ log_errx(1, "duplicated mountpoint "
+ "in %s, line %d", map, lineno);
+ }
+ if (options2 != NULL || location != NULL) {
+ log_errx(1, "mountpoint out of order "
+ "in %s, line %d", map, lineno);
+ }
+ mountpoint = checked_strdup(yytext);
+ goto again;
+ }
+
+ if (yytext[0] == '-') {
+ if (options2 != NULL) {
+ log_errx(1, "duplicated options "
+ "in %s, line %d", map, lineno);
+ }
+ if (location != NULL) {
+ log_errx(1, "options out of order "
+ "in %s, line %d", map, lineno);
+ }
+ options2 = checked_strdup(yytext + 1);
+ goto again;
+ }
+
+ if (location != NULL) {
+ log_errx(1, "too many arguments "
+ "in %s, line %d", map, lineno);
+ }
+
+ /*
+ * If location field starts with colon, e.g. ":/dev/cd0",
+ * then strip it.
+ */
+ if (yytext[0] == ':') {
+ location = checked_strdup(yytext + 1);
+ if (location[0] == '\0') {
+ log_errx(1, "empty location in %s, "
+ "line %d", map, lineno);
+ }
+ } else {
+ location = checked_strdup(yytext);
+ }
+
+ if (mountpoint == NULL)
+ mountpoint = checked_strdup("/");
+ if (options2 == NULL)
+ options2 = checked_strdup("");
+
+#if 0
+ log_debugx("adding map node, %s %s %s",
+ mountpoint, options2, location);
+#endif
+ node_new(node, mountpoint, options2, location,
+ map, lineno);
+ mountpoint = options2 = location = NULL;
+again:
+ ret = yylex();
+ if (ret == 0 || ret == NEWLINE) {
+ if (mountpoint != NULL || options2 != NULL ||
+ location != NULL) {
+ log_errx(1, "truncated entry "
+ "in %s, line %d", map, lineno);
+ }
+ break;
+ }
+ }
+ }
+}
+
+/*
+ * Parse output of a special map called without argument. It is a list
+ * of keys, separated by newlines. They can contain whitespace, so use
+ * getline(3) instead of lexer used for maps.
+ */
+static void
+parse_map_keys_yyin(struct node *parent, const char *map)
+{
+ char *line = NULL, *key;
+ size_t linecap = 0;
+ ssize_t linelen;
+
+ lineno = 1;
+
+ for (;;) {
+ linelen = getline(&line, &linecap, yyin);
+ if (linelen < 0) {
+ /*
+ * End of file.
+ */
+ break;
+ }
+ if (linelen <= 1) {
+ /*
+ * Empty line, consisting of just the newline.
+ */
+ continue;
+ }
+
+ /*
+ * "-1" to strip the trailing newline.
+ */
+ key = strndup(line, linelen - 1);
+
+ log_debugx("adding key \"%s\"", key);
+ node_new(parent, key, NULL, NULL, map, lineno);
+ lineno++;
+ }
+ free(line);
+}
+
+static bool
+file_is_executable(const char *path)
+{
+ struct stat sb;
+ int error;
+
+ error = stat(path, &sb);
+ if (error != 0)
+ log_err(1, "cannot stat %s", path);
+ if ((sb.st_mode & S_IXUSR) || (sb.st_mode & S_IXGRP) ||
+ (sb.st_mode & S_IXOTH))
+ return (true);
+ return (false);
+}
+
+/*
+ * Parse a special map, e.g. "-hosts".
+ */
+static void
+parse_special_map(struct node *parent, const char *map, const char *key)
+{
+ char *path;
+ int error, ret;
+
+ assert(map[0] == '-');
+
+ /*
+ * +1 to skip leading "-" in map name.
+ */
+ ret = asprintf(&path, "%s/special_%s", AUTO_SPECIAL_PREFIX, map + 1);
+ if (ret < 0)
+ log_err(1, "asprintf");
+
+ yyin = auto_popen(path, key, NULL);
+ assert(yyin != NULL);
+
+ if (key == NULL) {
+ parse_map_keys_yyin(parent, map);
+ } else {
+ parse_map_yyin(parent, map, key);
+ }
+
+ error = auto_pclose(yyin);
+ yyin = NULL;
+ if (error != 0)
+ log_errx(1, "failed to handle special map \"%s\"", map);
+
+ node_expand_includes(parent, false);
+ node_expand_direct_maps(parent);
+
+ free(path);
+}
+
+/*
+ * Retrieve and parse map from directory services, e.g. LDAP.
+ * Note that it is different from executable maps, in that
+ * the include script outputs the whole map to standard output
+ * (as opposed to executable maps that only output a single
+ * entry, without the key), and it takes the map name as an
+ * argument, instead of key.
+ */
+static void
+parse_included_map(struct node *parent, const char *map)
+{
+ int error;
+
+ assert(map[0] != '-');
+ assert(map[0] != '/');
+
+ error = access(AUTO_INCLUDE_PATH, F_OK);
+ if (error != 0) {
+ log_errx(1, "directory services not configured;"
+ " %s does not exist", AUTO_INCLUDE_PATH);
+ }
+
+ yyin = auto_popen(AUTO_INCLUDE_PATH, map, NULL);
+ assert(yyin != NULL);
+
+ parse_map_yyin(parent, map, NULL);
+
+ error = auto_pclose(yyin);
+ yyin = NULL;
+ if (error != 0)
+ log_errx(1, "failed to handle remote map \"%s\"", map);
+
+ node_expand_includes(parent, false);
+ node_expand_direct_maps(parent);
+}
+
+void
+parse_map(struct node *parent, const char *map, const char *key,
+ bool *wildcards)
+{
+ char *path = NULL;
+ int error, ret;
+ bool executable;
+
+ assert(map != NULL);
+ assert(map[0] != '\0');
+
+ log_debugx("parsing map \"%s\"", map);
+
+ if (wildcards != NULL)
+ *wildcards = false;
+
+ if (map[0] == '-') {
+ if (wildcards != NULL)
+ *wildcards = true;
+ return (parse_special_map(parent, map, key));
+ }
+
+ if (map[0] == '/') {
+ path = checked_strdup(map);
+ } else {
+ ret = asprintf(&path, "%s/%s", AUTO_MAP_PREFIX, map);
+ if (ret < 0)
+ log_err(1, "asprintf");
+ log_debugx("map \"%s\" maps to \"%s\"", map, path);
+
+ /*
+ * See if the file exists. If not, try to obtain the map
+ * from directory services.
+ */
+ error = access(path, F_OK);
+ if (error != 0) {
+ log_debugx("map file \"%s\" does not exist; falling "
+ "back to directory services", path);
+ return (parse_included_map(parent, map));
+ }
+ }
+
+ executable = file_is_executable(path);
+
+ if (executable) {
+ log_debugx("map \"%s\" is executable", map);
+
+ if (wildcards != NULL)
+ *wildcards = true;
+
+ if (key != NULL) {
+ yyin = auto_popen(path, key, NULL);
+ } else {
+ yyin = auto_popen(path, NULL);
+ }
+ assert(yyin != NULL);
+ } else {
+ yyin = fopen(path, "r");
+ if (yyin == NULL)
+ log_err(1, "unable to open \"%s\"", path);
+ }
+
+ free(path);
+ path = NULL;
+
+ parse_map_yyin(parent, map, executable ? key : NULL);
+
+ if (executable) {
+ error = auto_pclose(yyin);
+ yyin = NULL;
+ if (error != 0) {
+ log_errx(1, "failed to handle executable map \"%s\"",
+ map);
+ }
+ } else {
+ fclose(yyin);
+ }
+ yyin = NULL;
+
+ log_debugx("done parsing map \"%s\"", map);
+
+ node_expand_includes(parent, false);
+ node_expand_direct_maps(parent);
+}
+
+static void
+parse_master_yyin(struct node *root, const char *master)
+{
+ char *mountpoint = NULL, *map = NULL, *options = NULL;
+ int ret;
+
+ /*
+ * XXX: 1 gives incorrect values; wtf?
+ */
+ lineno = 0;
+
+ for (;;) {
+ ret = yylex();
+ if (ret == 0 || ret == NEWLINE) {
+ if (mountpoint != NULL) {
+ //log_debugx("adding map for %s", mountpoint);
+ node_new_map(root, mountpoint, options, map,
+ master, lineno);
+ }
+ if (ret == 0) {
+ break;
+ } else {
+ mountpoint = map = options = NULL;
+ continue;
+ }
+ }
+ if (mountpoint == NULL) {
+ mountpoint = checked_strdup(yytext);
+ } else if (map == NULL) {
+ map = checked_strdup(yytext);
+ } else if (options == NULL) {
+ /*
+ * +1 to skip leading "-".
+ */
+ options = checked_strdup(yytext + 1);
+ } else {
+ log_errx(1, "too many arguments at %s, line %d",
+ master, lineno);
+ }
+ }
+}
+
+void
+parse_master(struct node *root, const char *master)
+{
+
+ log_debugx("parsing auto_master file at \"%s\"", master);
+
+ yyin = fopen(master, "r");
+ if (yyin == NULL)
+ err(1, "unable to open %s", master);
+
+ parse_master_yyin(root, master);
+
+ fclose(yyin);
+ yyin = NULL;
+
+ log_debugx("done parsing \"%s\"", master);
+
+ node_expand_includes(root, true);
+ node_expand_direct_maps(root);
+}
+
+/*
+ * Two things daemon(3) does, that we actually also want to do
+ * when running in foreground, is closing the stdin and chdiring
+ * to "/". This is what we do here.
+ */
+void
+lesser_daemon(void)
+{
+ int error, fd;
+
+ error = chdir("/");
+ if (error != 0)
+ log_warn("chdir");
+
+ fd = open(_PATH_DEVNULL, O_RDWR, 0);
+ if (fd < 0) {
+ log_warn("cannot open %s", _PATH_DEVNULL);
+ return;
+ }
+
+ error = dup2(fd, STDIN_FILENO);
+ if (error != 0)
+ log_warn("dup2");
+
+ error = close(fd);
+ if (error != 0) {
+ /* Bloody hell. */
+ log_warn("close");
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ char *cmdname;
+
+ if (argv[0] == NULL)
+ log_errx(1, "NULL command name");
+
+ cmdname = basename(argv[0]);
+
+ if (strcmp(cmdname, "automount") == 0)
+ return (main_automount(argc, argv));
+ else if (strcmp(cmdname, "automountd") == 0)
+ return (main_automountd(argc, argv));
+ else if (strcmp(cmdname, "autounmountd") == 0)
+ return (main_autounmountd(argc, argv));
+ else
+ log_errx(1, "binary name should be either \"automount\", "
+ "\"automountd\", or \"autounmountd\"");
+}
diff --git a/usr.sbin/autofs/common.h b/usr.sbin/autofs/common.h
new file mode 100644
index 0000000..dc84415
--- /dev/null
+++ b/usr.sbin/autofs/common.h
@@ -0,0 +1,114 @@
+/*-
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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$
+ */
+
+#ifndef AUTOMOUNTD_H
+#define AUTOMOUNTD_H
+
+#include <sys/queue.h>
+#include <stdbool.h>
+
+#define AUTO_MASTER_PATH "/etc/auto_master"
+#define AUTO_MAP_PREFIX "/etc"
+#define AUTO_SPECIAL_PREFIX "/etc/autofs"
+#define AUTO_INCLUDE_PATH AUTO_SPECIAL_PREFIX "/include"
+
+struct node {
+ TAILQ_ENTRY(node) n_next;
+ TAILQ_HEAD(nodehead, node) n_children;
+ struct node *n_parent;
+ char *n_key;
+ char *n_options;
+ char *n_location;
+ char *n_map;
+ const char *n_config_file;
+ int n_config_line;
+};
+
+struct defined_value {
+ TAILQ_ENTRY(defined_value) d_next;
+ char *d_name;
+ char *d_value;
+};
+
+void log_init(int level);
+void log_set_peer_name(const char *name);
+void log_set_peer_addr(const char *addr);
+void log_err(int, const char *, ...)
+ __dead2 __printf0like(2, 3);
+void log_errx(int, const char *, ...)
+ __dead2 __printf0like(2, 3);
+void log_warn(const char *, ...) __printf0like(1, 2);
+void log_warnx(const char *, ...) __printflike(1, 2);
+void log_debugx(const char *, ...) __printf0like(1, 2);
+
+char *checked_strdup(const char *);
+char *concat(const char *s1, char separator, const char *s2);
+void create_directory(const char *path);
+
+struct node *node_new_root(void);
+struct node *node_new(struct node *parent, char *key, char *options,
+ char *location, const char *config_file, int config_line);
+struct node *node_new_map(struct node *parent, char *key, char *options,
+ char *map, const char *config_file, int config_line);
+struct node *node_find(struct node *root, const char *mountpoint);
+bool node_is_direct_map(const struct node *n);
+bool node_has_wildcards(const struct node *n);
+char *node_path(const struct node *n);
+char *node_options(const struct node *n);
+void node_expand_ampersand(struct node *root, const char *key);
+void node_expand_wildcard(struct node *root, const char *key);
+int node_expand_defined(struct node *root);
+void node_expand_indirect_maps(struct node *n);
+void node_print(const struct node *n, const char *cmdline_options);
+void parse_master(struct node *root, const char *path);
+void parse_map(struct node *parent, const char *map, const char *args,
+ bool *wildcards);
+char *defined_expand(const char *string);
+void defined_init(void);
+void defined_parse_and_add(char *def);
+void lesser_daemon(void);
+
+int main_automount(int argc, char **argv);
+int main_automountd(int argc, char **argv);
+int main_autounmountd(int argc, char **argv);
+
+FILE *auto_popen(const char *argv0, ...);
+int auto_pclose(FILE *iop);
+
+/*
+ * lex(1) stuff.
+ */
+extern int lineno;
+
+#define STR 1
+#define NEWLINE 2
+
+#endif /* !AUTOMOUNTD_H */
diff --git a/usr.sbin/autofs/defined.c b/usr.sbin/autofs/defined.c
new file mode 100644
index 0000000..eaaea28
--- /dev/null
+++ b/usr.sbin/autofs/defined.c
@@ -0,0 +1,272 @@
+/*-
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+/*
+ * All the "defined" stuff is for handling variables,
+ * such as ${OSNAME}, in maps.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/utsname.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libutil.h>
+
+#include "common.h"
+
+static TAILQ_HEAD(, defined_value) defined_values;
+
+static const char *
+defined_find(const char *name)
+{
+ struct defined_value *d;
+
+ TAILQ_FOREACH(d, &defined_values, d_next) {
+ if (strcmp(d->d_name, name) == 0)
+ return (d->d_value);
+ }
+
+ return (NULL);
+}
+
+char *
+defined_expand(const char *string)
+{
+ const char *value;
+ char c, *expanded, *name;
+ int i, ret, before_len = 0, name_off = 0, name_len = 0, after_off = 0;
+ bool backslashed = false, bracketed = false;
+
+ expanded = checked_strdup(string);
+
+ for (i = 0; string[i] != '\0'; i++) {
+ c = string[i];
+ if (c == '\\' && backslashed == false) {
+ backslashed = true;
+ continue;
+ }
+ if (backslashed) {
+ backslashed = false;
+ continue;
+ }
+ backslashed = false;
+ if (c != '$')
+ continue;
+
+ /*
+ * The 'before_len' variable contains the number
+ * of characters before the '$'.
+ */
+ before_len = i;
+ assert(i + 1 < (int)strlen(string));
+ if (string[i + 1] == '{')
+ bracketed = true;
+
+ if (string[i + 1] == '\0') {
+ log_warnx("truncated variable");
+ return (NULL);
+ }
+
+ /*
+ * Skip '$'.
+ */
+ i++;
+
+ if (bracketed) {
+ if (string[i + 1] == '\0') {
+ log_warnx("truncated variable");
+ return (NULL);
+ }
+
+ /*
+ * Skip '{'.
+ */
+ i++;
+ }
+
+ /*
+ * The 'name_off' variable contains the number
+ * of characters before the variable name,
+ * including the "$" or "${".
+ */
+ name_off = i;
+
+ for (; string[i] != '\0'; i++) {
+ c = string[i];
+ /*
+ * XXX: Decide on the set of characters that can be
+ * used in a variable name.
+ */
+ if (isalnum(c) || c == '_')
+ continue;
+
+ /*
+ * End of variable name.
+ */
+ if (bracketed) {
+ if (c != '}')
+ continue;
+
+ /*
+ * The 'after_off' variable contains the number
+ * of characters before the rest of the string,
+ * i.e. after the variable name.
+ */
+ after_off = i + 1;
+ assert(i > 1);
+ assert(i - 1 > name_off);
+ name_len = i - name_off;
+ break;
+ }
+
+ after_off = i;
+ assert(i > 1);
+ assert(i > name_off);
+ name_len = i - name_off;
+ break;
+ }
+
+ name = strndup(string + name_off, name_len);
+ if (name == NULL)
+ log_err(1, "strndup");
+ value = defined_find(name);
+ if (value == NULL) {
+ log_warnx("undefined variable ${%s}", name);
+ return (NULL);
+ }
+
+ /*
+ * Concatenate it back.
+ */
+ ret = asprintf(&expanded, "%.*s%s%s",
+ before_len, string, value, string + after_off);
+ if (ret < 0)
+ log_err(1, "asprintf");
+
+ //log_debugx("\"%s\" expanded to \"%s\"", string, expanded);
+ free(name);
+
+ /*
+ * Figure out where to start searching for next variable.
+ */
+ string = expanded;
+ i = before_len + strlen(value);
+ backslashed = bracketed = false;
+ before_len = name_off = name_len = after_off = 0;
+ assert(i <= (int)strlen(string));
+ }
+
+ if (before_len != 0 || name_off != 0 || name_len != 0 || after_off != 0) {
+ log_warnx("truncated variable");
+ return (NULL);
+ }
+
+ return (expanded);
+}
+
+static void
+defined_add(const char *name, const char *value)
+{
+ struct defined_value *d;
+ const char *found;
+
+ found = defined_find(name);
+ if (found != NULL)
+ log_errx(1, "variable %s already defined", name);
+
+ log_debugx("defining variable %s=%s", name, value);
+
+ d = calloc(sizeof(*d), 1);
+ if (d == NULL)
+ log_err(1, "calloc");
+ d->d_name = checked_strdup(name);
+ d->d_value = checked_strdup(value);
+
+ TAILQ_INSERT_TAIL(&defined_values, d, d_next);
+}
+
+void
+defined_parse_and_add(char *def)
+{
+ char *name, *value;
+
+ value = def;
+ name = strsep(&value, "=");
+
+ if (value == NULL || value[0] == '\0')
+ log_errx(1, "missing variable value");
+ if (name == NULL || name[0] == '\0')
+ log_errx(1, "missing variable name");
+
+ defined_add(name, value);
+}
+
+void
+defined_init(void)
+{
+ struct utsname name;
+ int error;
+
+ TAILQ_INIT(&defined_values);
+
+ error = uname(&name);
+ if (error != 0)
+ log_err(1, "uname");
+
+ defined_add("ARCH", name.machine);
+ defined_add("CPU", name.machine);
+ defined_add("HOST", name.nodename);
+ defined_add("OSNAME", name.sysname);
+ defined_add("OSREL", name.release);
+ defined_add("OSVERS", name.version);
+}
diff --git a/usr.sbin/autofs/log.c b/usr.sbin/autofs/log.c
new file mode 100644
index 0000000..d5682cc
--- /dev/null
+++ b/usr.sbin/autofs/log.c
@@ -0,0 +1,198 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <vis.h>
+
+#include "common.h"
+
+static int log_level = 0;
+static char *peer_name = NULL;
+static char *peer_addr = NULL;
+
+#define MSGBUF_LEN 1024
+
+void
+log_init(int level)
+{
+
+ log_level = level;
+ openlog(getprogname(), LOG_NDELAY | LOG_PID, LOG_DAEMON);
+}
+
+void
+log_set_peer_name(const char *name)
+{
+
+ /*
+ * XXX: Turn it into assertion?
+ */
+ if (peer_name != NULL)
+ log_errx(1, "%s called twice", __func__);
+ if (peer_addr == NULL)
+ log_errx(1, "%s called before log_set_peer_addr", __func__);
+
+ peer_name = checked_strdup(name);
+}
+
+void
+log_set_peer_addr(const char *addr)
+{
+
+ /*
+ * XXX: Turn it into assertion?
+ */
+ if (peer_addr != NULL)
+ log_errx(1, "%s called twice", __func__);
+
+ peer_addr = checked_strdup(addr);
+}
+
+static void
+log_common(int priority, int log_errno, const char *fmt, va_list ap)
+{
+ static char msgbuf[MSGBUF_LEN];
+ static char msgbuf_strvised[MSGBUF_LEN * 4 + 1];
+ int ret;
+
+ ret = vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
+ if (ret < 0) {
+ fprintf(stderr, "%s: snprintf failed", getprogname());
+ syslog(LOG_CRIT, "snprintf failed");
+ exit(1);
+ }
+
+ ret = strnvis(msgbuf_strvised, sizeof(msgbuf_strvised), msgbuf, VIS_NL);
+ if (ret < 0) {
+ fprintf(stderr, "%s: strnvis failed", getprogname());
+ syslog(LOG_CRIT, "strnvis failed");
+ exit(1);
+ }
+
+ if (log_errno == -1) {
+ if (peer_name != NULL) {
+ fprintf(stderr, "%s: %s (%s): %s\n", getprogname(),
+ peer_addr, peer_name, msgbuf_strvised);
+ syslog(priority, "%s (%s): %s",
+ peer_addr, peer_name, msgbuf_strvised);
+ } else if (peer_addr != NULL) {
+ fprintf(stderr, "%s: %s: %s\n", getprogname(),
+ peer_addr, msgbuf_strvised);
+ syslog(priority, "%s: %s",
+ peer_addr, msgbuf_strvised);
+ } else {
+ fprintf(stderr, "%s: %s\n", getprogname(), msgbuf_strvised);
+ syslog(priority, "%s", msgbuf_strvised);
+ }
+
+ } else {
+ if (peer_name != NULL) {
+ fprintf(stderr, "%s: %s (%s): %s: %s\n", getprogname(),
+ peer_addr, peer_name, msgbuf_strvised, strerror(errno));
+ syslog(priority, "%s (%s): %s: %s",
+ peer_addr, peer_name, msgbuf_strvised, strerror(errno));
+ } else if (peer_addr != NULL) {
+ fprintf(stderr, "%s: %s: %s: %s\n", getprogname(),
+ peer_addr, msgbuf_strvised, strerror(errno));
+ syslog(priority, "%s: %s: %s",
+ peer_addr, msgbuf_strvised, strerror(errno));
+ } else {
+ fprintf(stderr, "%s: %s: %s\n", getprogname(),
+ msgbuf_strvised, strerror(errno));
+ syslog(priority, "%s: %s",
+ msgbuf_strvised, strerror(errno));
+ }
+ }
+}
+
+void
+log_err(int eval, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ log_common(LOG_CRIT, errno, fmt, ap);
+ va_end(ap);
+
+ exit(eval);
+}
+
+void
+log_errx(int eval, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ log_common(LOG_CRIT, -1, fmt, ap);
+ va_end(ap);
+
+ exit(eval);
+}
+
+void
+log_warn(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ log_common(LOG_WARNING, errno, fmt, ap);
+ va_end(ap);
+}
+
+void
+log_warnx(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ log_common(LOG_WARNING, -1, fmt, ap);
+ va_end(ap);
+}
+
+void
+log_debugx(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (log_level == 0)
+ return;
+
+ va_start(ap, fmt);
+ log_common(LOG_DEBUG, -1, fmt, ap);
+ va_end(ap);
+}
diff --git a/usr.sbin/autofs/popen.c b/usr.sbin/autofs/popen.c
new file mode 100644
index 0000000..e114880
--- /dev/null
+++ b/usr.sbin/autofs/popen.c
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This code is derived from software written by Ken Arnold and
+ * published in UNIX Review, Vol. 6, No. 8.
+ *
+ * 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.
+ * 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/queue.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <paths.h>
+
+#include "common.h"
+
+extern char **environ;
+
+struct pid {
+ SLIST_ENTRY(pid) next;
+ FILE *outfp;
+ pid_t pid;
+ char *command;
+};
+static SLIST_HEAD(, pid) pidlist = SLIST_HEAD_INITIALIZER(pidlist);
+
+#define ARGV_LEN 42
+
+/*
+ * Replacement for popen(3), without stdin (which we do not use), but with
+ * stderr, proper logging, and improved command line arguments passing.
+ * Error handling is built in - if it returns, then it succeeded.
+ */
+FILE *
+auto_popen(const char *argv0, ...)
+{
+ va_list ap;
+ struct pid *cur, *p;
+ pid_t pid;
+ int error, i, nullfd, outfds[2];
+ char *arg, *argv[ARGV_LEN], *command;
+
+ nullfd = open(_PATH_DEVNULL, O_RDWR, 0);
+ if (nullfd < 0)
+ log_err(1, "cannot open %s", _PATH_DEVNULL);
+
+ error = pipe(outfds);
+ if (error != 0)
+ log_err(1, "pipe");
+
+ cur = malloc(sizeof(struct pid));
+ if (cur == NULL)
+ log_err(1, "malloc");
+
+ argv[0] = checked_strdup(argv0);
+ command = argv[0];
+
+ va_start(ap, argv0);
+ for (i = 1;; i++) {
+ if (i >= ARGV_LEN)
+ log_errx(1, "too many arguments to auto_popen");
+ arg = va_arg(ap, char *);
+ argv[i] = arg;
+ if (arg == NULL)
+ break;
+
+ command = concat(command, ' ', arg);
+ }
+ va_end(ap);
+
+ cur->command = checked_strdup(command);
+
+ switch (pid = fork()) {
+ case -1: /* Error. */
+ log_err(1, "fork");
+ /* NOTREACHED */
+ case 0: /* Child. */
+ dup2(nullfd, STDIN_FILENO);
+ dup2(outfds[1], STDOUT_FILENO);
+
+ close(nullfd);
+ close(outfds[0]);
+ close(outfds[1]);
+
+ SLIST_FOREACH(p, &pidlist, next)
+ close(fileno(p->outfp));
+ execvp(argv[0], argv);
+ log_err(1, "failed to execute %s", argv[0]);
+ /* NOTREACHED */
+ }
+
+ log_debugx("executing \"%s\" as pid %d", command, pid);
+
+ /* Parent; assume fdopen cannot fail. */
+ cur->outfp = fdopen(outfds[0], "r");
+ close(nullfd);
+ close(outfds[1]);
+
+ /* Link into list of file descriptors. */
+ cur->pid = pid;
+ SLIST_INSERT_HEAD(&pidlist, cur, next);
+
+ return (cur->outfp);
+}
+
+int
+auto_pclose(FILE *iop)
+{
+ struct pid *cur, *last = NULL;
+ int status;
+ pid_t pid;
+
+ /*
+ * Find the appropriate file pointer and remove it from the list.
+ */
+ SLIST_FOREACH(cur, &pidlist, next) {
+ if (cur->outfp == iop)
+ break;
+ last = cur;
+ }
+ if (cur == NULL) {
+ return (-1);
+ }
+ if (last == NULL)
+ SLIST_REMOVE_HEAD(&pidlist, next);
+ else
+ SLIST_REMOVE_AFTER(last, next);
+
+ fclose(cur->outfp);
+
+ do {
+ pid = wait4(cur->pid, &status, 0, NULL);
+ } while (pid == -1 && errno == EINTR);
+
+ if (WIFSIGNALED(status)) {
+ log_warnx("\"%s\", pid %d, terminated with signal %d",
+ cur->command, pid, WTERMSIG(status));
+ return (status);
+ }
+
+ if (WEXITSTATUS(status) != 0) {
+ log_warnx("\"%s\", pid %d, terminated with exit status %d",
+ cur->command, pid, WEXITSTATUS(status));
+ return (status);
+ }
+
+ log_debugx("\"%s\", pid %d, terminated gracefully", cur->command, pid);
+
+ free(cur->command);
+ free(cur);
+
+ return (pid == -1 ? -1 : status);
+}
diff --git a/usr.sbin/autofs/token.l b/usr.sbin/autofs/token.l
new file mode 100644
index 0000000..6a92b7f
--- /dev/null
+++ b/usr.sbin/autofs/token.l
@@ -0,0 +1,58 @@
+%{
+/*-
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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 <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "common.h"
+
+int lineno;
+
+#define YY_DECL int yylex(void)
+extern int yylex(void);
+
+%}
+
+%option noinput
+%option nounput
+%option noyywrap
+
+%%
+\"[^"]+\" { yytext++; yytext[strlen(yytext) - 1] = '\0'; return STR; };
+[a-zA-Z0-9\.\+-_/\:\[\]$&%{}]+ { return STR; }
+#.*\n { lineno++; return NEWLINE; };
+\\\n { lineno++; };
+\n { lineno++; return NEWLINE; }
+[ \t]+ /* ignore whitespace */;
+. { return STR; }
+%%
diff --git a/usr.sbin/bhyve/Makefile b/usr.sbin/bhyve/Makefile
new file mode 100644
index 0000000..0e1a85e
--- /dev/null
+++ b/usr.sbin/bhyve/Makefile
@@ -0,0 +1,52 @@
+#
+# $FreeBSD$
+#
+
+PROG= bhyve
+
+DEBUG_FLAGS= -g -O0
+
+MAN= bhyve.8
+
+SRCS= \
+ atkbdc.c \
+ acpi.c \
+ bhyverun.c \
+ block_if.c \
+ bootrom.c \
+ consport.c \
+ dbgport.c \
+ fwctl.c \
+ inout.c \
+ ioapic.c \
+ mem.c \
+ mevent.c \
+ mptbl.c \
+ pci_ahci.c \
+ pci_emul.c \
+ pci_hostbridge.c \
+ pci_irq.c \
+ pci_lpc.c \
+ pci_passthru.c \
+ pci_virtio_block.c \
+ pci_virtio_net.c \
+ pci_virtio_rnd.c \
+ pci_uart.c \
+ pm.c \
+ post.c \
+ rtc.c \
+ smbiostbl.c \
+ task_switch.c \
+ uart_emul.c \
+ virtio.c \
+ xmsr.c \
+ spinup_ap.c
+
+.PATH: ${.CURDIR}/../../sys/amd64/vmm
+SRCS+= vmm_instruction_emul.c
+
+LIBADD= vmmapi md pthread
+
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bhyve/Makefile.depend b/usr.sbin/bhyve/Makefile.depend
new file mode 100644
index 0000000..e12ecb9
--- /dev/null
+++ b/usr.sbin/bhyve/Makefile.depend
@@ -0,0 +1,22 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libmd \
+ lib/libthr \
+ lib/libutil \
+ lib/libvmmapi \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bhyve/acpi.c b/usr.sbin/bhyve/acpi.c
new file mode 100644
index 0000000..57fe783
--- /dev/null
+++ b/usr.sbin/bhyve/acpi.c
@@ -0,0 +1,1012 @@
+/*-
+ * Copyright (c) 2012 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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$
+ */
+
+/*
+ * bhyve ACPI table generator.
+ *
+ * Create the minimal set of ACPI tables required to boot FreeBSD (and
+ * hopefully other o/s's) by writing out ASL template files for each of
+ * the tables and the compiling them to AML with the Intel iasl compiler.
+ * The AML files are then read into guest memory.
+ *
+ * The tables are placed in the guest's ROM area just below 1MB physical,
+ * above the MPTable.
+ *
+ * Layout
+ * ------
+ * RSDP -> 0xf2400 (36 bytes fixed)
+ * RSDT -> 0xf2440 (36 bytes + 4*7 table addrs, 4 used)
+ * XSDT -> 0xf2480 (36 bytes + 8*7 table addrs, 4 used)
+ * MADT -> 0xf2500 (depends on #CPUs)
+ * FADT -> 0xf2600 (268 bytes)
+ * HPET -> 0xf2740 (56 bytes)
+ * MCFG -> 0xf2780 (60 bytes)
+ * FACS -> 0xf27C0 (64 bytes)
+ * DSDT -> 0xf2800 (variable - can go up to 0x100000)
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <sys/stat.h>
+
+#include <paths.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <machine/vmm.h>
+#include <vmmapi.h>
+
+#include "bhyverun.h"
+#include "acpi.h"
+#include "pci_emul.h"
+
+/*
+ * Define the base address of the ACPI tables, and the offsets to
+ * the individual tables
+ */
+#define BHYVE_ACPI_BASE 0xf2400
+#define RSDT_OFFSET 0x040
+#define XSDT_OFFSET 0x080
+#define MADT_OFFSET 0x100
+#define FADT_OFFSET 0x200
+#define HPET_OFFSET 0x340
+#define MCFG_OFFSET 0x380
+#define FACS_OFFSET 0x3C0
+#define DSDT_OFFSET 0x400
+
+#define BHYVE_ASL_TEMPLATE "bhyve.XXXXXXX"
+#define BHYVE_ASL_SUFFIX ".aml"
+#define BHYVE_ASL_COMPILER "/usr/sbin/iasl"
+
+static int basl_keep_temps;
+static int basl_verbose_iasl;
+static int basl_ncpu;
+static uint32_t basl_acpi_base = BHYVE_ACPI_BASE;
+static uint32_t hpet_capabilities;
+
+/*
+ * Contains the full pathname of the template to be passed
+ * to mkstemp/mktemps(3)
+ */
+static char basl_template[MAXPATHLEN];
+static char basl_stemplate[MAXPATHLEN];
+
+/*
+ * State for dsdt_line(), dsdt_indent(), and dsdt_unindent().
+ */
+static FILE *dsdt_fp;
+static int dsdt_indent_level;
+static int dsdt_error;
+
+struct basl_fio {
+ int fd;
+ FILE *fp;
+ char f_name[MAXPATHLEN];
+};
+
+#define EFPRINTF(...) \
+ err = fprintf(__VA_ARGS__); if (err < 0) goto err_exit;
+
+#define EFFLUSH(x) \
+ err = fflush(x); if (err != 0) goto err_exit;
+
+static int
+basl_fwrite_rsdp(FILE *fp)
+{
+ int err;
+
+ err = 0;
+
+ EFPRINTF(fp, "/*\n");
+ EFPRINTF(fp, " * bhyve RSDP template\n");
+ EFPRINTF(fp, " */\n");
+ EFPRINTF(fp, "[0008]\t\tSignature : \"RSD PTR \"\n");
+ EFPRINTF(fp, "[0001]\t\tChecksum : 43\n");
+ EFPRINTF(fp, "[0006]\t\tOem ID : \"BHYVE \"\n");
+ EFPRINTF(fp, "[0001]\t\tRevision : 02\n");
+ EFPRINTF(fp, "[0004]\t\tRSDT Address : %08X\n",
+ basl_acpi_base + RSDT_OFFSET);
+ EFPRINTF(fp, "[0004]\t\tLength : 00000024\n");
+ EFPRINTF(fp, "[0008]\t\tXSDT Address : 00000000%08X\n",
+ basl_acpi_base + XSDT_OFFSET);
+ EFPRINTF(fp, "[0001]\t\tExtended Checksum : 00\n");
+ EFPRINTF(fp, "[0003]\t\tReserved : 000000\n");
+
+ EFFLUSH(fp);
+
+ return (0);
+
+err_exit:
+ return (errno);
+}
+
+static int
+basl_fwrite_rsdt(FILE *fp)
+{
+ int err;
+
+ err = 0;
+
+ EFPRINTF(fp, "/*\n");
+ EFPRINTF(fp, " * bhyve RSDT template\n");
+ EFPRINTF(fp, " */\n");
+ EFPRINTF(fp, "[0004]\t\tSignature : \"RSDT\"\n");
+ EFPRINTF(fp, "[0004]\t\tTable Length : 00000000\n");
+ EFPRINTF(fp, "[0001]\t\tRevision : 01\n");
+ EFPRINTF(fp, "[0001]\t\tChecksum : 00\n");
+ EFPRINTF(fp, "[0006]\t\tOem ID : \"BHYVE \"\n");
+ EFPRINTF(fp, "[0008]\t\tOem Table ID : \"BVRSDT \"\n");
+ EFPRINTF(fp, "[0004]\t\tOem Revision : 00000001\n");
+ /* iasl will fill in the compiler ID/revision fields */
+ EFPRINTF(fp, "[0004]\t\tAsl Compiler ID : \"xxxx\"\n");
+ EFPRINTF(fp, "[0004]\t\tAsl Compiler Revision : 00000000\n");
+ EFPRINTF(fp, "\n");
+
+ /* Add in pointers to the MADT, FADT and HPET */
+ EFPRINTF(fp, "[0004]\t\tACPI Table Address 0 : %08X\n",
+ basl_acpi_base + MADT_OFFSET);
+ EFPRINTF(fp, "[0004]\t\tACPI Table Address 1 : %08X\n",
+ basl_acpi_base + FADT_OFFSET);
+ EFPRINTF(fp, "[0004]\t\tACPI Table Address 2 : %08X\n",
+ basl_acpi_base + HPET_OFFSET);
+ EFPRINTF(fp, "[0004]\t\tACPI Table Address 3 : %08X\n",
+ basl_acpi_base + MCFG_OFFSET);
+
+ EFFLUSH(fp);
+
+ return (0);
+
+err_exit:
+ return (errno);
+}
+
+static int
+basl_fwrite_xsdt(FILE *fp)
+{
+ int err;
+
+ err = 0;
+
+ EFPRINTF(fp, "/*\n");
+ EFPRINTF(fp, " * bhyve XSDT template\n");
+ EFPRINTF(fp, " */\n");
+ EFPRINTF(fp, "[0004]\t\tSignature : \"XSDT\"\n");
+ EFPRINTF(fp, "[0004]\t\tTable Length : 00000000\n");
+ EFPRINTF(fp, "[0001]\t\tRevision : 01\n");
+ EFPRINTF(fp, "[0001]\t\tChecksum : 00\n");
+ EFPRINTF(fp, "[0006]\t\tOem ID : \"BHYVE \"\n");
+ EFPRINTF(fp, "[0008]\t\tOem Table ID : \"BVXSDT \"\n");
+ EFPRINTF(fp, "[0004]\t\tOem Revision : 00000001\n");
+ /* iasl will fill in the compiler ID/revision fields */
+ EFPRINTF(fp, "[0004]\t\tAsl Compiler ID : \"xxxx\"\n");
+ EFPRINTF(fp, "[0004]\t\tAsl Compiler Revision : 00000000\n");
+ EFPRINTF(fp, "\n");
+
+ /* Add in pointers to the MADT, FADT and HPET */
+ EFPRINTF(fp, "[0004]\t\tACPI Table Address 0 : 00000000%08X\n",
+ basl_acpi_base + MADT_OFFSET);
+ EFPRINTF(fp, "[0004]\t\tACPI Table Address 1 : 00000000%08X\n",
+ basl_acpi_base + FADT_OFFSET);
+ EFPRINTF(fp, "[0004]\t\tACPI Table Address 2 : 00000000%08X\n",
+ basl_acpi_base + HPET_OFFSET);
+ EFPRINTF(fp, "[0004]\t\tACPI Table Address 3 : 00000000%08X\n",
+ basl_acpi_base + MCFG_OFFSET);
+
+ EFFLUSH(fp);
+
+ return (0);
+
+err_exit:
+ return (errno);
+}
+
+static int
+basl_fwrite_madt(FILE *fp)
+{
+ int err;
+ int i;
+
+ err = 0;
+
+ EFPRINTF(fp, "/*\n");
+ EFPRINTF(fp, " * bhyve MADT template\n");
+ EFPRINTF(fp, " */\n");
+ EFPRINTF(fp, "[0004]\t\tSignature : \"APIC\"\n");
+ EFPRINTF(fp, "[0004]\t\tTable Length : 00000000\n");
+ EFPRINTF(fp, "[0001]\t\tRevision : 01\n");
+ EFPRINTF(fp, "[0001]\t\tChecksum : 00\n");
+ EFPRINTF(fp, "[0006]\t\tOem ID : \"BHYVE \"\n");
+ EFPRINTF(fp, "[0008]\t\tOem Table ID : \"BVMADT \"\n");
+ EFPRINTF(fp, "[0004]\t\tOem Revision : 00000001\n");
+
+ /* iasl will fill in the compiler ID/revision fields */
+ EFPRINTF(fp, "[0004]\t\tAsl Compiler ID : \"xxxx\"\n");
+ EFPRINTF(fp, "[0004]\t\tAsl Compiler Revision : 00000000\n");
+ EFPRINTF(fp, "\n");
+
+ EFPRINTF(fp, "[0004]\t\tLocal Apic Address : FEE00000\n");
+ EFPRINTF(fp, "[0004]\t\tFlags (decoded below) : 00000001\n");
+ EFPRINTF(fp, "\t\t\tPC-AT Compatibility : 1\n");
+ EFPRINTF(fp, "\n");
+
+ /* Add a Processor Local APIC entry for each CPU */
+ for (i = 0; i < basl_ncpu; i++) {
+ EFPRINTF(fp, "[0001]\t\tSubtable Type : 00\n");
+ EFPRINTF(fp, "[0001]\t\tLength : 08\n");
+ /* iasl expects hex values for the proc and apic id's */
+ EFPRINTF(fp, "[0001]\t\tProcessor ID : %02x\n", i);
+ EFPRINTF(fp, "[0001]\t\tLocal Apic ID : %02x\n", i);
+ EFPRINTF(fp, "[0004]\t\tFlags (decoded below) : 00000001\n");
+ EFPRINTF(fp, "\t\t\tProcessor Enabled : 1\n");
+ EFPRINTF(fp, "\n");
+ }
+
+ /* Always a single IOAPIC entry, with ID 0 */
+ EFPRINTF(fp, "[0001]\t\tSubtable Type : 01\n");
+ EFPRINTF(fp, "[0001]\t\tLength : 0C\n");
+ /* iasl expects a hex value for the i/o apic id */
+ EFPRINTF(fp, "[0001]\t\tI/O Apic ID : %02x\n", 0);
+ EFPRINTF(fp, "[0001]\t\tReserved : 00\n");
+ EFPRINTF(fp, "[0004]\t\tAddress : fec00000\n");
+ EFPRINTF(fp, "[0004]\t\tInterrupt : 00000000\n");
+ EFPRINTF(fp, "\n");
+
+ /* Legacy IRQ0 is connected to pin 2 of the IOAPIC */
+ EFPRINTF(fp, "[0001]\t\tSubtable Type : 02\n");
+ EFPRINTF(fp, "[0001]\t\tLength : 0A\n");
+ EFPRINTF(fp, "[0001]\t\tBus : 00\n");
+ EFPRINTF(fp, "[0001]\t\tSource : 00\n");
+ EFPRINTF(fp, "[0004]\t\tInterrupt : 00000002\n");
+ EFPRINTF(fp, "[0002]\t\tFlags (decoded below) : 0005\n");
+ EFPRINTF(fp, "\t\t\tPolarity : 1\n");
+ EFPRINTF(fp, "\t\t\tTrigger Mode : 1\n");
+ EFPRINTF(fp, "\n");
+
+ EFPRINTF(fp, "[0001]\t\tSubtable Type : 02\n");
+ EFPRINTF(fp, "[0001]\t\tLength : 0A\n");
+ EFPRINTF(fp, "[0001]\t\tBus : 00\n");
+ EFPRINTF(fp, "[0001]\t\tSource : %02X\n", SCI_INT);
+ EFPRINTF(fp, "[0004]\t\tInterrupt : %08X\n", SCI_INT);
+ EFPRINTF(fp, "[0002]\t\tFlags (decoded below) : 0000\n");
+ EFPRINTF(fp, "\t\t\tPolarity : 3\n");
+ EFPRINTF(fp, "\t\t\tTrigger Mode : 3\n");
+ EFPRINTF(fp, "\n");
+
+ /* Local APIC NMI is connected to LINT 1 on all CPUs */
+ EFPRINTF(fp, "[0001]\t\tSubtable Type : 04\n");
+ EFPRINTF(fp, "[0001]\t\tLength : 06\n");
+ EFPRINTF(fp, "[0001]\t\tProcessorId : FF\n");
+ EFPRINTF(fp, "[0002]\t\tFlags (decoded below) : 0005\n");
+ EFPRINTF(fp, "\t\t\tPolarity : 1\n");
+ EFPRINTF(fp, "\t\t\tTrigger Mode : 1\n");
+ EFPRINTF(fp, "[0001]\t\tInterrupt : 01\n");
+ EFPRINTF(fp, "\n");
+
+ EFFLUSH(fp);
+
+ return (0);
+
+err_exit:
+ return (errno);
+}
+
+static int
+basl_fwrite_fadt(FILE *fp)
+{
+ int err;
+
+ err = 0;
+
+ EFPRINTF(fp, "/*\n");
+ EFPRINTF(fp, " * bhyve FADT template\n");
+ EFPRINTF(fp, " */\n");
+ EFPRINTF(fp, "[0004]\t\tSignature : \"FACP\"\n");
+ EFPRINTF(fp, "[0004]\t\tTable Length : 0000010C\n");
+ EFPRINTF(fp, "[0001]\t\tRevision : 05\n");
+ EFPRINTF(fp, "[0001]\t\tChecksum : 00\n");
+ EFPRINTF(fp, "[0006]\t\tOem ID : \"BHYVE \"\n");
+ EFPRINTF(fp, "[0008]\t\tOem Table ID : \"BVFACP \"\n");
+ EFPRINTF(fp, "[0004]\t\tOem Revision : 00000001\n");
+ /* iasl will fill in the compiler ID/revision fields */
+ EFPRINTF(fp, "[0004]\t\tAsl Compiler ID : \"xxxx\"\n");
+ EFPRINTF(fp, "[0004]\t\tAsl Compiler Revision : 00000000\n");
+ EFPRINTF(fp, "\n");
+
+ EFPRINTF(fp, "[0004]\t\tFACS Address : %08X\n",
+ basl_acpi_base + FACS_OFFSET);
+ EFPRINTF(fp, "[0004]\t\tDSDT Address : %08X\n",
+ basl_acpi_base + DSDT_OFFSET);
+ EFPRINTF(fp, "[0001]\t\tModel : 01\n");
+ EFPRINTF(fp, "[0001]\t\tPM Profile : 00 [Unspecified]\n");
+ EFPRINTF(fp, "[0002]\t\tSCI Interrupt : %04X\n",
+ SCI_INT);
+ EFPRINTF(fp, "[0004]\t\tSMI Command Port : %08X\n",
+ SMI_CMD);
+ EFPRINTF(fp, "[0001]\t\tACPI Enable Value : %02X\n",
+ BHYVE_ACPI_ENABLE);
+ EFPRINTF(fp, "[0001]\t\tACPI Disable Value : %02X\n",
+ BHYVE_ACPI_DISABLE);
+ EFPRINTF(fp, "[0001]\t\tS4BIOS Command : 00\n");
+ EFPRINTF(fp, "[0001]\t\tP-State Control : 00\n");
+ EFPRINTF(fp, "[0004]\t\tPM1A Event Block Address : %08X\n",
+ PM1A_EVT_ADDR);
+ EFPRINTF(fp, "[0004]\t\tPM1B Event Block Address : 00000000\n");
+ EFPRINTF(fp, "[0004]\t\tPM1A Control Block Address : %08X\n",
+ PM1A_CNT_ADDR);
+ EFPRINTF(fp, "[0004]\t\tPM1B Control Block Address : 00000000\n");
+ EFPRINTF(fp, "[0004]\t\tPM2 Control Block Address : 00000000\n");
+ EFPRINTF(fp, "[0004]\t\tPM Timer Block Address : %08X\n",
+ IO_PMTMR);
+ EFPRINTF(fp, "[0004]\t\tGPE0 Block Address : 00000000\n");
+ EFPRINTF(fp, "[0004]\t\tGPE1 Block Address : 00000000\n");
+ EFPRINTF(fp, "[0001]\t\tPM1 Event Block Length : 04\n");
+ EFPRINTF(fp, "[0001]\t\tPM1 Control Block Length : 02\n");
+ EFPRINTF(fp, "[0001]\t\tPM2 Control Block Length : 00\n");
+ EFPRINTF(fp, "[0001]\t\tPM Timer Block Length : 04\n");
+ EFPRINTF(fp, "[0001]\t\tGPE0 Block Length : 00\n");
+ EFPRINTF(fp, "[0001]\t\tGPE1 Block Length : 00\n");
+ EFPRINTF(fp, "[0001]\t\tGPE1 Base Offset : 00\n");
+ EFPRINTF(fp, "[0001]\t\t_CST Support : 00\n");
+ EFPRINTF(fp, "[0002]\t\tC2 Latency : 0000\n");
+ EFPRINTF(fp, "[0002]\t\tC3 Latency : 0000\n");
+ EFPRINTF(fp, "[0002]\t\tCPU Cache Size : 0000\n");
+ EFPRINTF(fp, "[0002]\t\tCache Flush Stride : 0000\n");
+ EFPRINTF(fp, "[0001]\t\tDuty Cycle Offset : 00\n");
+ EFPRINTF(fp, "[0001]\t\tDuty Cycle Width : 00\n");
+ EFPRINTF(fp, "[0001]\t\tRTC Day Alarm Index : 00\n");
+ EFPRINTF(fp, "[0001]\t\tRTC Month Alarm Index : 00\n");
+ EFPRINTF(fp, "[0001]\t\tRTC Century Index : 32\n");
+ EFPRINTF(fp, "[0002]\t\tBoot Flags (decoded below) : 0000\n");
+ EFPRINTF(fp, "\t\t\tLegacy Devices Supported (V2) : 0\n");
+ EFPRINTF(fp, "\t\t\t8042 Present on ports 60/64 (V2) : 0\n");
+ EFPRINTF(fp, "\t\t\tVGA Not Present (V4) : 1\n");
+ EFPRINTF(fp, "\t\t\tMSI Not Supported (V4) : 0\n");
+ EFPRINTF(fp, "\t\t\tPCIe ASPM Not Supported (V4) : 1\n");
+ EFPRINTF(fp, "\t\t\tCMOS RTC Not Present (V5) : 0\n");
+ EFPRINTF(fp, "[0001]\t\tReserved : 00\n");
+ EFPRINTF(fp, "[0004]\t\tFlags (decoded below) : 00000000\n");
+ EFPRINTF(fp, "\t\t\tWBINVD instruction is operational (V1) : 1\n");
+ EFPRINTF(fp, "\t\t\tWBINVD flushes all caches (V1) : 0\n");
+ EFPRINTF(fp, "\t\t\tAll CPUs support C1 (V1) : 1\n");
+ EFPRINTF(fp, "\t\t\tC2 works on MP system (V1) : 0\n");
+ EFPRINTF(fp, "\t\t\tControl Method Power Button (V1) : 0\n");
+ EFPRINTF(fp, "\t\t\tControl Method Sleep Button (V1) : 1\n");
+ EFPRINTF(fp, "\t\t\tRTC wake not in fixed reg space (V1) : 0\n");
+ EFPRINTF(fp, "\t\t\tRTC can wake system from S4 (V1) : 0\n");
+ EFPRINTF(fp, "\t\t\t32-bit PM Timer (V1) : 1\n");
+ EFPRINTF(fp, "\t\t\tDocking Supported (V1) : 0\n");
+ EFPRINTF(fp, "\t\t\tReset Register Supported (V2) : 1\n");
+ EFPRINTF(fp, "\t\t\tSealed Case (V3) : 0\n");
+ EFPRINTF(fp, "\t\t\tHeadless - No Video (V3) : 1\n");
+ EFPRINTF(fp, "\t\t\tUse native instr after SLP_TYPx (V3) : 0\n");
+ EFPRINTF(fp, "\t\t\tPCIEXP_WAK Bits Supported (V4) : 0\n");
+ EFPRINTF(fp, "\t\t\tUse Platform Timer (V4) : 0\n");
+ EFPRINTF(fp, "\t\t\tRTC_STS valid on S4 wake (V4) : 0\n");
+ EFPRINTF(fp, "\t\t\tRemote Power-on capable (V4) : 0\n");
+ EFPRINTF(fp, "\t\t\tUse APIC Cluster Model (V4) : 0\n");
+ EFPRINTF(fp, "\t\t\tUse APIC Physical Destination Mode (V4) : 1\n");
+ EFPRINTF(fp, "\t\t\tHardware Reduced (V5) : 0\n");
+ EFPRINTF(fp, "\t\t\tLow Power S0 Idle (V5) : 0\n");
+ EFPRINTF(fp, "\n");
+
+ EFPRINTF(fp,
+ "[0012]\t\tReset Register : [Generic Address Structure]\n");
+ EFPRINTF(fp, "[0001]\t\tSpace ID : 01 [SystemIO]\n");
+ EFPRINTF(fp, "[0001]\t\tBit Width : 08\n");
+ EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n");
+ EFPRINTF(fp, "[0001]\t\tEncoded Access Width : 01 [Byte Access:8]\n");
+ EFPRINTF(fp, "[0008]\t\tAddress : 0000000000000CF9\n");
+ EFPRINTF(fp, "\n");
+
+ EFPRINTF(fp, "[0001]\t\tValue to cause reset : 06\n");
+ EFPRINTF(fp, "[0002]\t\tARM Flags (decoded below): 0000\n");
+ EFPRINTF(fp, "\t\t\tPSCI Compliant : 0\n");
+ EFPRINTF(fp, "\t\t\tMust use HVC for PSCI : 0\n");
+ EFPRINTF(fp, "[0001]\t\tFADT Minor Revision : 01\n");
+ EFPRINTF(fp, "[0008]\t\tFACS Address : 00000000%08X\n",
+ basl_acpi_base + FACS_OFFSET);
+ EFPRINTF(fp, "[0008]\t\tDSDT Address : 00000000%08X\n",
+ basl_acpi_base + DSDT_OFFSET);
+ EFPRINTF(fp,
+ "[0012]\t\tPM1A Event Block : [Generic Address Structure]\n");
+ EFPRINTF(fp, "[0001]\t\tSpace ID : 01 [SystemIO]\n");
+ EFPRINTF(fp, "[0001]\t\tBit Width : 20\n");
+ EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n");
+ EFPRINTF(fp, "[0001]\t\tEncoded Access Width : 02 [Word Access:16]\n");
+ EFPRINTF(fp, "[0008]\t\tAddress : 00000000%08X\n",
+ PM1A_EVT_ADDR);
+ EFPRINTF(fp, "\n");
+
+ EFPRINTF(fp,
+ "[0012]\t\tPM1B Event Block : [Generic Address Structure]\n");
+ EFPRINTF(fp, "[0001]\t\tSpace ID : 01 [SystemIO]\n");
+ EFPRINTF(fp, "[0001]\t\tBit Width : 00\n");
+ EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n");
+ EFPRINTF(fp,
+ "[0001]\t\tEncoded Access Width : 00 [Undefined/Legacy]\n");
+ EFPRINTF(fp, "[0008]\t\tAddress : 0000000000000000\n");
+ EFPRINTF(fp, "\n");
+
+ EFPRINTF(fp,
+ "[0012]\t\tPM1A Control Block : [Generic Address Structure]\n");
+ EFPRINTF(fp, "[0001]\t\tSpace ID : 01 [SystemIO]\n");
+ EFPRINTF(fp, "[0001]\t\tBit Width : 10\n");
+ EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n");
+ EFPRINTF(fp, "[0001]\t\tEncoded Access Width : 02 [Word Access:16]\n");
+ EFPRINTF(fp, "[0008]\t\tAddress : 00000000%08X\n",
+ PM1A_CNT_ADDR);
+ EFPRINTF(fp, "\n");
+
+ EFPRINTF(fp,
+ "[0012]\t\tPM1B Control Block : [Generic Address Structure]\n");
+ EFPRINTF(fp, "[0001]\t\tSpace ID : 01 [SystemIO]\n");
+ EFPRINTF(fp, "[0001]\t\tBit Width : 00\n");
+ EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n");
+ EFPRINTF(fp,
+ "[0001]\t\tEncoded Access Width : 00 [Undefined/Legacy]\n");
+ EFPRINTF(fp, "[0008]\t\tAddress : 0000000000000000\n");
+ EFPRINTF(fp, "\n");
+
+ EFPRINTF(fp,
+ "[0012]\t\tPM2 Control Block : [Generic Address Structure]\n");
+ EFPRINTF(fp, "[0001]\t\tSpace ID : 01 [SystemIO]\n");
+ EFPRINTF(fp, "[0001]\t\tBit Width : 08\n");
+ EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n");
+ EFPRINTF(fp,
+ "[0001]\t\tEncoded Access Width : 00 [Undefined/Legacy]\n");
+ EFPRINTF(fp, "[0008]\t\tAddress : 0000000000000000\n");
+ EFPRINTF(fp, "\n");
+
+ /* Valid for bhyve */
+ EFPRINTF(fp,
+ "[0012]\t\tPM Timer Block : [Generic Address Structure]\n");
+ EFPRINTF(fp, "[0001]\t\tSpace ID : 01 [SystemIO]\n");
+ EFPRINTF(fp, "[0001]\t\tBit Width : 20\n");
+ EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n");
+ EFPRINTF(fp,
+ "[0001]\t\tEncoded Access Width : 03 [DWord Access:32]\n");
+ EFPRINTF(fp, "[0008]\t\tAddress : 00000000%08X\n",
+ IO_PMTMR);
+ EFPRINTF(fp, "\n");
+
+ EFPRINTF(fp, "[0012]\t\tGPE0 Block : [Generic Address Structure]\n");
+ EFPRINTF(fp, "[0001]\t\tSpace ID : 01 [SystemIO]\n");
+ EFPRINTF(fp, "[0001]\t\tBit Width : 00\n");
+ EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n");
+ EFPRINTF(fp, "[0001]\t\tEncoded Access Width : 01 [Byte Access:8]\n");
+ EFPRINTF(fp, "[0008]\t\tAddress : 0000000000000000\n");
+ EFPRINTF(fp, "\n");
+
+ EFPRINTF(fp, "[0012]\t\tGPE1 Block : [Generic Address Structure]\n");
+ EFPRINTF(fp, "[0001]\t\tSpace ID : 01 [SystemIO]\n");
+ EFPRINTF(fp, "[0001]\t\tBit Width : 00\n");
+ EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n");
+ EFPRINTF(fp,
+ "[0001]\t\tEncoded Access Width : 00 [Undefined/Legacy]\n");
+ EFPRINTF(fp, "[0008]\t\tAddress : 0000000000000000\n");
+ EFPRINTF(fp, "\n");
+
+ EFPRINTF(fp,
+ "[0012]\t\tSleep Control Register : [Generic Address Structure]\n");
+ EFPRINTF(fp, "[0001]\t\tSpace ID : 01 [SystemIO]\n");
+ EFPRINTF(fp, "[0001]\t\tBit Width : 08\n");
+ EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n");
+ EFPRINTF(fp, "[0001]\t\tEncoded Access Width : 01 [Byte Access:8]\n");
+ EFPRINTF(fp, "[0008]\t\tAddress : 0000000000000000\n");
+ EFPRINTF(fp, "\n");
+
+ EFPRINTF(fp,
+ "[0012]\t\tSleep Status Register : [Generic Address Structure]\n");
+ EFPRINTF(fp, "[0001]\t\tSpace ID : 01 [SystemIO]\n");
+ EFPRINTF(fp, "[0001]\t\tBit Width : 08\n");
+ EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n");
+ EFPRINTF(fp, "[0001]\t\tEncoded Access Width : 01 [Byte Access:8]\n");
+ EFPRINTF(fp, "[0008]\t\tAddress : 0000000000000000\n");
+
+ EFFLUSH(fp);
+
+ return (0);
+
+err_exit:
+ return (errno);
+}
+
+static int
+basl_fwrite_hpet(FILE *fp)
+{
+ int err;
+
+ err = 0;
+
+ EFPRINTF(fp, "/*\n");
+ EFPRINTF(fp, " * bhyve HPET template\n");
+ EFPRINTF(fp, " */\n");
+ EFPRINTF(fp, "[0004]\t\tSignature : \"HPET\"\n");
+ EFPRINTF(fp, "[0004]\t\tTable Length : 00000000\n");
+ EFPRINTF(fp, "[0001]\t\tRevision : 01\n");
+ EFPRINTF(fp, "[0001]\t\tChecksum : 00\n");
+ EFPRINTF(fp, "[0006]\t\tOem ID : \"BHYVE \"\n");
+ EFPRINTF(fp, "[0008]\t\tOem Table ID : \"BVHPET \"\n");
+ EFPRINTF(fp, "[0004]\t\tOem Revision : 00000001\n");
+
+ /* iasl will fill in the compiler ID/revision fields */
+ EFPRINTF(fp, "[0004]\t\tAsl Compiler ID : \"xxxx\"\n");
+ EFPRINTF(fp, "[0004]\t\tAsl Compiler Revision : 00000000\n");
+ EFPRINTF(fp, "\n");
+
+ EFPRINTF(fp, "[0004]\t\tTimer Block ID : %08X\n", hpet_capabilities);
+ EFPRINTF(fp,
+ "[0012]\t\tTimer Block Register : [Generic Address Structure]\n");
+ EFPRINTF(fp, "[0001]\t\tSpace ID : 00 [SystemMemory]\n");
+ EFPRINTF(fp, "[0001]\t\tBit Width : 00\n");
+ EFPRINTF(fp, "[0001]\t\tBit Offset : 00\n");
+ EFPRINTF(fp,
+ "[0001]\t\tEncoded Access Width : 00 [Undefined/Legacy]\n");
+ EFPRINTF(fp, "[0008]\t\tAddress : 00000000FED00000\n");
+ EFPRINTF(fp, "\n");
+
+ EFPRINTF(fp, "[0001]\t\tHPET Number : 00\n");
+ EFPRINTF(fp, "[0002]\t\tMinimum Clock Ticks : 0000\n");
+ EFPRINTF(fp, "[0004]\t\tFlags (decoded below) : 00000001\n");
+ EFPRINTF(fp, "\t\t\t4K Page Protect : 1\n");
+ EFPRINTF(fp, "\t\t\t64K Page Protect : 0\n");
+ EFPRINTF(fp, "\n");
+
+ EFFLUSH(fp);
+
+ return (0);
+
+err_exit:
+ return (errno);
+}
+
+static int
+basl_fwrite_mcfg(FILE *fp)
+{
+ int err = 0;
+
+ EFPRINTF(fp, "/*\n");
+ EFPRINTF(fp, " * bhyve MCFG template\n");
+ EFPRINTF(fp, " */\n");
+ EFPRINTF(fp, "[0004]\t\tSignature : \"MCFG\"\n");
+ EFPRINTF(fp, "[0004]\t\tTable Length : 00000000\n");
+ EFPRINTF(fp, "[0001]\t\tRevision : 01\n");
+ EFPRINTF(fp, "[0001]\t\tChecksum : 00\n");
+ EFPRINTF(fp, "[0006]\t\tOem ID : \"BHYVE \"\n");
+ EFPRINTF(fp, "[0008]\t\tOem Table ID : \"BVMCFG \"\n");
+ EFPRINTF(fp, "[0004]\t\tOem Revision : 00000001\n");
+
+ /* iasl will fill in the compiler ID/revision fields */
+ EFPRINTF(fp, "[0004]\t\tAsl Compiler ID : \"xxxx\"\n");
+ EFPRINTF(fp, "[0004]\t\tAsl Compiler Revision : 00000000\n");
+ EFPRINTF(fp, "[0008]\t\tReserved : 0\n");
+ EFPRINTF(fp, "\n");
+
+ EFPRINTF(fp, "[0008]\t\tBase Address : %016lX\n", pci_ecfg_base());
+ EFPRINTF(fp, "[0002]\t\tSegment Group: 0000\n");
+ EFPRINTF(fp, "[0001]\t\tStart Bus: 00\n");
+ EFPRINTF(fp, "[0001]\t\tEnd Bus: FF\n");
+ EFPRINTF(fp, "[0004]\t\tReserved : 0\n");
+ EFFLUSH(fp);
+ return (0);
+err_exit:
+ return (errno);
+}
+
+static int
+basl_fwrite_facs(FILE *fp)
+{
+ int err;
+
+ err = 0;
+
+ EFPRINTF(fp, "/*\n");
+ EFPRINTF(fp, " * bhyve FACS template\n");
+ EFPRINTF(fp, " */\n");
+ EFPRINTF(fp, "[0004]\t\tSignature : \"FACS\"\n");
+ EFPRINTF(fp, "[0004]\t\tLength : 00000040\n");
+ EFPRINTF(fp, "[0004]\t\tHardware Signature : 00000000\n");
+ EFPRINTF(fp, "[0004]\t\t32 Firmware Waking Vector : 00000000\n");
+ EFPRINTF(fp, "[0004]\t\tGlobal Lock : 00000000\n");
+ EFPRINTF(fp, "[0004]\t\tFlags (decoded below) : 00000000\n");
+ EFPRINTF(fp, "\t\t\tS4BIOS Support Present : 0\n");
+ EFPRINTF(fp, "\t\t\t64-bit Wake Supported (V2) : 0\n");
+ EFPRINTF(fp,
+ "[0008]\t\t64 Firmware Waking Vector : 0000000000000000\n");
+ EFPRINTF(fp, "[0001]\t\tVersion : 02\n");
+ EFPRINTF(fp, "[0003]\t\tReserved : 000000\n");
+ EFPRINTF(fp, "[0004]\t\tOspmFlags (decoded below) : 00000000\n");
+ EFPRINTF(fp, "\t\t\t64-bit Wake Env Required (V2) : 0\n");
+
+ EFFLUSH(fp);
+
+ return (0);
+
+err_exit:
+ return (errno);
+}
+
+/*
+ * Helper routines for writing to the DSDT from other modules.
+ */
+void
+dsdt_line(const char *fmt, ...)
+{
+ va_list ap;
+ int err;
+
+ if (dsdt_error != 0)
+ return;
+
+ if (strcmp(fmt, "") != 0) {
+ if (dsdt_indent_level != 0)
+ EFPRINTF(dsdt_fp, "%*c", dsdt_indent_level * 2, ' ');
+ va_start(ap, fmt);
+ if (vfprintf(dsdt_fp, fmt, ap) < 0)
+ goto err_exit;
+ va_end(ap);
+ }
+ EFPRINTF(dsdt_fp, "\n");
+ return;
+
+err_exit:
+ dsdt_error = errno;
+}
+
+void
+dsdt_indent(int levels)
+{
+
+ dsdt_indent_level += levels;
+ assert(dsdt_indent_level >= 0);
+}
+
+void
+dsdt_unindent(int levels)
+{
+
+ assert(dsdt_indent_level >= levels);
+ dsdt_indent_level -= levels;
+}
+
+void
+dsdt_fixed_ioport(uint16_t iobase, uint16_t length)
+{
+
+ dsdt_line("IO (Decode16,");
+ dsdt_line(" 0x%04X, // Range Minimum", iobase);
+ dsdt_line(" 0x%04X, // Range Maximum", iobase);
+ dsdt_line(" 0x01, // Alignment");
+ dsdt_line(" 0x%02X, // Length", length);
+ dsdt_line(" )");
+}
+
+void
+dsdt_fixed_irq(uint8_t irq)
+{
+
+ dsdt_line("IRQNoFlags ()");
+ dsdt_line(" {%d}", irq);
+}
+
+void
+dsdt_fixed_mem32(uint32_t base, uint32_t length)
+{
+
+ dsdt_line("Memory32Fixed (ReadWrite,");
+ dsdt_line(" 0x%08X, // Address Base", base);
+ dsdt_line(" 0x%08X, // Address Length", length);
+ dsdt_line(" )");
+}
+
+static int
+basl_fwrite_dsdt(FILE *fp)
+{
+ int err;
+
+ err = 0;
+ dsdt_fp = fp;
+ dsdt_error = 0;
+ dsdt_indent_level = 0;
+
+ dsdt_line("/*");
+ dsdt_line(" * bhyve DSDT template");
+ dsdt_line(" */");
+ dsdt_line("DefinitionBlock (\"bhyve_dsdt.aml\", \"DSDT\", 2,"
+ "\"BHYVE \", \"BVDSDT \", 0x00000001)");
+ dsdt_line("{");
+ dsdt_line(" Name (_S5, Package ()");
+ dsdt_line(" {");
+ dsdt_line(" 0x05,");
+ dsdt_line(" Zero,");
+ dsdt_line(" })");
+
+ pci_write_dsdt();
+
+ dsdt_line("");
+ dsdt_line(" Scope (_SB.PC00)");
+ dsdt_line(" {");
+ dsdt_line(" Device (HPET)");
+ dsdt_line(" {");
+ dsdt_line(" Name (_HID, EISAID(\"PNP0103\"))");
+ dsdt_line(" Name (_UID, 0)");
+ dsdt_line(" Name (_CRS, ResourceTemplate ()");
+ dsdt_line(" {");
+ dsdt_indent(4);
+ dsdt_fixed_mem32(0xFED00000, 0x400);
+ dsdt_unindent(4);
+ dsdt_line(" })");
+ dsdt_line(" }");
+ dsdt_line(" }");
+ dsdt_line("}");
+
+ if (dsdt_error != 0)
+ return (dsdt_error);
+
+ EFFLUSH(fp);
+
+ return (0);
+
+err_exit:
+ return (errno);
+}
+
+static int
+basl_open(struct basl_fio *bf, int suffix)
+{
+ int err;
+
+ err = 0;
+
+ if (suffix) {
+ strlcpy(bf->f_name, basl_stemplate, MAXPATHLEN);
+ bf->fd = mkstemps(bf->f_name, strlen(BHYVE_ASL_SUFFIX));
+ } else {
+ strlcpy(bf->f_name, basl_template, MAXPATHLEN);
+ bf->fd = mkstemp(bf->f_name);
+ }
+
+ if (bf->fd > 0) {
+ bf->fp = fdopen(bf->fd, "w+");
+ if (bf->fp == NULL) {
+ unlink(bf->f_name);
+ close(bf->fd);
+ }
+ } else {
+ err = 1;
+ }
+
+ return (err);
+}
+
+static void
+basl_close(struct basl_fio *bf)
+{
+
+ if (!basl_keep_temps)
+ unlink(bf->f_name);
+ fclose(bf->fp);
+}
+
+static int
+basl_start(struct basl_fio *in, struct basl_fio *out)
+{
+ int err;
+
+ err = basl_open(in, 0);
+ if (!err) {
+ err = basl_open(out, 1);
+ if (err) {
+ basl_close(in);
+ }
+ }
+
+ return (err);
+}
+
+static void
+basl_end(struct basl_fio *in, struct basl_fio *out)
+{
+
+ basl_close(in);
+ basl_close(out);
+}
+
+static int
+basl_load(struct vmctx *ctx, int fd, uint64_t off)
+{
+ struct stat sb;
+ void *gaddr;
+
+ if (fstat(fd, &sb) < 0)
+ return (errno);
+
+ gaddr = paddr_guest2host(ctx, basl_acpi_base + off, sb.st_size);
+ if (gaddr == NULL)
+ return (EFAULT);
+
+ if (read(fd, gaddr, sb.st_size) < 0)
+ return (errno);
+
+ return (0);
+}
+
+static int
+basl_compile(struct vmctx *ctx, int (*fwrite_section)(FILE *), uint64_t offset)
+{
+ struct basl_fio io[2];
+ static char iaslbuf[3*MAXPATHLEN + 10];
+ char *fmt;
+ int err;
+
+ err = basl_start(&io[0], &io[1]);
+ if (!err) {
+ err = (*fwrite_section)(io[0].fp);
+
+ if (!err) {
+ /*
+ * iasl sends the results of the compilation to
+ * stdout. Shut this down by using the shell to
+ * redirect stdout to /dev/null, unless the user
+ * has requested verbose output for debugging
+ * purposes
+ */
+ fmt = basl_verbose_iasl ?
+ "%s -p %s %s" :
+ "/bin/sh -c \"%s -p %s %s\" 1> /dev/null";
+
+ snprintf(iaslbuf, sizeof(iaslbuf),
+ fmt,
+ BHYVE_ASL_COMPILER,
+ io[1].f_name, io[0].f_name);
+ err = system(iaslbuf);
+
+ if (!err) {
+ /*
+ * Copy the aml output file into guest
+ * memory at the specified location
+ */
+ err = basl_load(ctx, io[1].fd, offset);
+ }
+ }
+ basl_end(&io[0], &io[1]);
+ }
+
+ return (err);
+}
+
+static int
+basl_make_templates(void)
+{
+ const char *tmpdir;
+ int err;
+ int len;
+
+ err = 0;
+
+ /*
+ *
+ */
+ if ((tmpdir = getenv("BHYVE_TMPDIR")) == NULL || *tmpdir == '\0' ||
+ (tmpdir = getenv("TMPDIR")) == NULL || *tmpdir == '\0') {
+ tmpdir = _PATH_TMP;
+ }
+
+ len = strlen(tmpdir);
+
+ if ((len + sizeof(BHYVE_ASL_TEMPLATE) + 1) < MAXPATHLEN) {
+ strcpy(basl_template, tmpdir);
+ while (len > 0 && basl_template[len - 1] == '/')
+ len--;
+ basl_template[len] = '/';
+ strcpy(&basl_template[len + 1], BHYVE_ASL_TEMPLATE);
+ } else
+ err = E2BIG;
+
+ if (!err) {
+ /*
+ * len has been intialized (and maybe adjusted) above
+ */
+ if ((len + sizeof(BHYVE_ASL_TEMPLATE) + 1 +
+ sizeof(BHYVE_ASL_SUFFIX)) < MAXPATHLEN) {
+ strcpy(basl_stemplate, tmpdir);
+ basl_stemplate[len] = '/';
+ strcpy(&basl_stemplate[len + 1], BHYVE_ASL_TEMPLATE);
+ len = strlen(basl_stemplate);
+ strcpy(&basl_stemplate[len], BHYVE_ASL_SUFFIX);
+ } else
+ err = E2BIG;
+ }
+
+ return (err);
+}
+
+static struct {
+ int (*wsect)(FILE *fp);
+ uint64_t offset;
+} basl_ftables[] =
+{
+ { basl_fwrite_rsdp, 0},
+ { basl_fwrite_rsdt, RSDT_OFFSET },
+ { basl_fwrite_xsdt, XSDT_OFFSET },
+ { basl_fwrite_madt, MADT_OFFSET },
+ { basl_fwrite_fadt, FADT_OFFSET },
+ { basl_fwrite_hpet, HPET_OFFSET },
+ { basl_fwrite_mcfg, MCFG_OFFSET },
+ { basl_fwrite_facs, FACS_OFFSET },
+ { basl_fwrite_dsdt, DSDT_OFFSET },
+ { NULL }
+};
+
+int
+acpi_build(struct vmctx *ctx, int ncpu)
+{
+ int err;
+ int i;
+
+ basl_ncpu = ncpu;
+
+ err = vm_get_hpet_capabilities(ctx, &hpet_capabilities);
+ if (err != 0)
+ return (err);
+
+ /*
+ * For debug, allow the user to have iasl compiler output sent
+ * to stdout rather than /dev/null
+ */
+ if (getenv("BHYVE_ACPI_VERBOSE_IASL"))
+ basl_verbose_iasl = 1;
+
+ /*
+ * Allow the user to keep the generated ASL files for debugging
+ * instead of deleting them following use
+ */
+ if (getenv("BHYVE_ACPI_KEEPTMPS"))
+ basl_keep_temps = 1;
+
+ i = 0;
+ err = basl_make_templates();
+
+ /*
+ * Run through all the ASL files, compiling them and
+ * copying them into guest memory
+ */
+ while (!err && basl_ftables[i].wsect != NULL) {
+ err = basl_compile(ctx, basl_ftables[i].wsect,
+ basl_ftables[i].offset);
+ i++;
+ }
+
+ return (err);
+}
diff --git a/usr.sbin/bhyve/acpi.h b/usr.sbin/bhyve/acpi.h
new file mode 100644
index 0000000..652164a
--- /dev/null
+++ b/usr.sbin/bhyve/acpi.h
@@ -0,0 +1,54 @@
+/*-
+ * Copyright (c) 2012 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 _ACPI_H_
+#define _ACPI_H_
+
+#define SCI_INT 9
+
+#define SMI_CMD 0xb2
+#define BHYVE_ACPI_ENABLE 0xa0
+#define BHYVE_ACPI_DISABLE 0xa1
+
+#define PM1A_EVT_ADDR 0x400
+#define PM1A_CNT_ADDR 0x404
+
+#define IO_PMTMR 0x408 /* 4-byte i/o port for the timer */
+
+struct vmctx;
+
+int acpi_build(struct vmctx *ctx, int ncpu);
+void dsdt_line(const char *fmt, ...);
+void dsdt_fixed_ioport(uint16_t iobase, uint16_t length);
+void dsdt_fixed_irq(uint8_t irq);
+void dsdt_fixed_mem32(uint32_t base, uint32_t length);
+void dsdt_indent(int levels);
+void dsdt_unindent(int levels);
+void sci_init(struct vmctx *ctx);
+
+#endif /* _ACPI_H_ */
diff --git a/usr.sbin/bhyve/ahci.h b/usr.sbin/bhyve/ahci.h
new file mode 100644
index 0000000..1fd9f20
--- /dev/null
+++ b/usr.sbin/bhyve/ahci.h
@@ -0,0 +1,322 @@
+/*-
+ * Copyright (c) 1998 - 2008 Søren Schmidt <sos@FreeBSD.org>
+ * Copyright (c) 2009-2012 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,
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _AHCI_H_
+#define _AHCI_H_
+
+/* ATA register defines */
+#define ATA_DATA 0 /* (RW) data */
+
+#define ATA_FEATURE 1 /* (W) feature */
+#define ATA_F_DMA 0x01 /* enable DMA */
+#define ATA_F_OVL 0x02 /* enable overlap */
+
+#define ATA_COUNT 2 /* (W) sector count */
+
+#define ATA_SECTOR 3 /* (RW) sector # */
+#define ATA_CYL_LSB 4 /* (RW) cylinder# LSB */
+#define ATA_CYL_MSB 5 /* (RW) cylinder# MSB */
+#define ATA_DRIVE 6 /* (W) Sector/Drive/Head */
+#define ATA_D_LBA 0x40 /* use LBA addressing */
+#define ATA_D_IBM 0xa0 /* 512 byte sectors, ECC */
+
+#define ATA_COMMAND 7 /* (W) command */
+
+#define ATA_ERROR 8 /* (R) error */
+#define ATA_E_ILI 0x01 /* illegal length */
+#define ATA_E_NM 0x02 /* no media */
+#define ATA_E_ABORT 0x04 /* command aborted */
+#define ATA_E_MCR 0x08 /* media change request */
+#define ATA_E_IDNF 0x10 /* ID not found */
+#define ATA_E_MC 0x20 /* media changed */
+#define ATA_E_UNC 0x40 /* uncorrectable data */
+#define ATA_E_ICRC 0x80 /* UDMA crc error */
+#define ATA_E_ATAPI_SENSE_MASK 0xf0 /* ATAPI sense key mask */
+
+#define ATA_IREASON 9 /* (R) interrupt reason */
+#define ATA_I_CMD 0x01 /* cmd (1) | data (0) */
+#define ATA_I_IN 0x02 /* read (1) | write (0) */
+#define ATA_I_RELEASE 0x04 /* released bus (1) */
+#define ATA_I_TAGMASK 0xf8 /* tag mask */
+
+#define ATA_STATUS 10 /* (R) status */
+#define ATA_ALTSTAT 11 /* (R) alternate status */
+#define ATA_S_ERROR 0x01 /* error */
+#define ATA_S_INDEX 0x02 /* index */
+#define ATA_S_CORR 0x04 /* data corrected */
+#define ATA_S_DRQ 0x08 /* data request */
+#define ATA_S_DSC 0x10 /* drive seek completed */
+#define ATA_S_SERVICE 0x10 /* drive needs service */
+#define ATA_S_DWF 0x20 /* drive write fault */
+#define ATA_S_DMA 0x20 /* DMA ready */
+#define ATA_S_READY 0x40 /* drive ready */
+#define ATA_S_BUSY 0x80 /* busy */
+
+#define ATA_CONTROL 12 /* (W) control */
+#define ATA_A_IDS 0x02 /* disable interrupts */
+#define ATA_A_RESET 0x04 /* RESET controller */
+#define ATA_A_4BIT 0x08 /* 4 head bits */
+#define ATA_A_HOB 0x80 /* High Order Byte enable */
+
+/* SATA register defines */
+#define ATA_SSTATUS 13
+#define ATA_SS_DET_MASK 0x0000000f
+#define ATA_SS_DET_NO_DEVICE 0x00000000
+#define ATA_SS_DET_DEV_PRESENT 0x00000001
+#define ATA_SS_DET_PHY_ONLINE 0x00000003
+#define ATA_SS_DET_PHY_OFFLINE 0x00000004
+
+#define ATA_SS_SPD_MASK 0x000000f0
+#define ATA_SS_SPD_NO_SPEED 0x00000000
+#define ATA_SS_SPD_GEN1 0x00000010
+#define ATA_SS_SPD_GEN2 0x00000020
+#define ATA_SS_SPD_GEN3 0x00000030
+
+#define ATA_SS_IPM_MASK 0x00000f00
+#define ATA_SS_IPM_NO_DEVICE 0x00000000
+#define ATA_SS_IPM_ACTIVE 0x00000100
+#define ATA_SS_IPM_PARTIAL 0x00000200
+#define ATA_SS_IPM_SLUMBER 0x00000600
+#define ATA_SS_IPM_DEVSLEEP 0x00000800
+
+#define ATA_SERROR 14
+#define ATA_SE_DATA_CORRECTED 0x00000001
+#define ATA_SE_COMM_CORRECTED 0x00000002
+#define ATA_SE_DATA_ERR 0x00000100
+#define ATA_SE_COMM_ERR 0x00000200
+#define ATA_SE_PROT_ERR 0x00000400
+#define ATA_SE_HOST_ERR 0x00000800
+#define ATA_SE_PHY_CHANGED 0x00010000
+#define ATA_SE_PHY_IERROR 0x00020000
+#define ATA_SE_COMM_WAKE 0x00040000
+#define ATA_SE_DECODE_ERR 0x00080000
+#define ATA_SE_PARITY_ERR 0x00100000
+#define ATA_SE_CRC_ERR 0x00200000
+#define ATA_SE_HANDSHAKE_ERR 0x00400000
+#define ATA_SE_LINKSEQ_ERR 0x00800000
+#define ATA_SE_TRANSPORT_ERR 0x01000000
+#define ATA_SE_UNKNOWN_FIS 0x02000000
+#define ATA_SE_EXCHANGED 0x04000000
+
+#define ATA_SCONTROL 15
+#define ATA_SC_DET_MASK 0x0000000f
+#define ATA_SC_DET_IDLE 0x00000000
+#define ATA_SC_DET_RESET 0x00000001
+#define ATA_SC_DET_DISABLE 0x00000004
+
+#define ATA_SC_SPD_MASK 0x000000f0
+#define ATA_SC_SPD_NO_SPEED 0x00000000
+#define ATA_SC_SPD_SPEED_GEN1 0x00000010
+#define ATA_SC_SPD_SPEED_GEN2 0x00000020
+#define ATA_SC_SPD_SPEED_GEN3 0x00000030
+
+#define ATA_SC_IPM_MASK 0x00000f00
+#define ATA_SC_IPM_NONE 0x00000000
+#define ATA_SC_IPM_DIS_PARTIAL 0x00000100
+#define ATA_SC_IPM_DIS_SLUMBER 0x00000200
+#define ATA_SC_IPM_DIS_DEVSLEEP 0x00000400
+
+#define ATA_SACTIVE 16
+
+#define AHCI_MAX_PORTS 32
+#define AHCI_MAX_SLOTS 32
+#define AHCI_MAX_IRQS 16
+
+/* SATA AHCI v1.0 register defines */
+#define AHCI_CAP 0x00
+#define AHCI_CAP_NPMASK 0x0000001f
+#define AHCI_CAP_SXS 0x00000020
+#define AHCI_CAP_EMS 0x00000040
+#define AHCI_CAP_CCCS 0x00000080
+#define AHCI_CAP_NCS 0x00001F00
+#define AHCI_CAP_NCS_SHIFT 8
+#define AHCI_CAP_PSC 0x00002000
+#define AHCI_CAP_SSC 0x00004000
+#define AHCI_CAP_PMD 0x00008000
+#define AHCI_CAP_FBSS 0x00010000
+#define AHCI_CAP_SPM 0x00020000
+#define AHCI_CAP_SAM 0x00080000
+#define AHCI_CAP_ISS 0x00F00000
+#define AHCI_CAP_ISS_SHIFT 20
+#define AHCI_CAP_SCLO 0x01000000
+#define AHCI_CAP_SAL 0x02000000
+#define AHCI_CAP_SALP 0x04000000
+#define AHCI_CAP_SSS 0x08000000
+#define AHCI_CAP_SMPS 0x10000000
+#define AHCI_CAP_SSNTF 0x20000000
+#define AHCI_CAP_SNCQ 0x40000000
+#define AHCI_CAP_64BIT 0x80000000
+
+#define AHCI_GHC 0x04
+#define AHCI_GHC_AE 0x80000000
+#define AHCI_GHC_MRSM 0x00000004
+#define AHCI_GHC_IE 0x00000002
+#define AHCI_GHC_HR 0x00000001
+
+#define AHCI_IS 0x08
+#define AHCI_PI 0x0c
+#define AHCI_VS 0x10
+
+#define AHCI_CCCC 0x14
+#define AHCI_CCCC_TV_MASK 0xffff0000
+#define AHCI_CCCC_TV_SHIFT 16
+#define AHCI_CCCC_CC_MASK 0x0000ff00
+#define AHCI_CCCC_CC_SHIFT 8
+#define AHCI_CCCC_INT_MASK 0x000000f8
+#define AHCI_CCCC_INT_SHIFT 3
+#define AHCI_CCCC_EN 0x00000001
+#define AHCI_CCCP 0x18
+
+#define AHCI_EM_LOC 0x1C
+#define AHCI_EM_CTL 0x20
+#define AHCI_EM_MR 0x00000001
+#define AHCI_EM_TM 0x00000100
+#define AHCI_EM_RST 0x00000200
+#define AHCI_EM_LED 0x00010000
+#define AHCI_EM_SAFTE 0x00020000
+#define AHCI_EM_SES2 0x00040000
+#define AHCI_EM_SGPIO 0x00080000
+#define AHCI_EM_SMB 0x01000000
+#define AHCI_EM_XMT 0x02000000
+#define AHCI_EM_ALHD 0x04000000
+#define AHCI_EM_PM 0x08000000
+
+#define AHCI_CAP2 0x24
+#define AHCI_CAP2_BOH 0x00000001
+#define AHCI_CAP2_NVMP 0x00000002
+#define AHCI_CAP2_APST 0x00000004
+#define AHCI_CAP2_SDS 0x00000008
+#define AHCI_CAP2_SADM 0x00000010
+#define AHCI_CAP2_DESO 0x00000020
+
+#define AHCI_OFFSET 0x100
+#define AHCI_STEP 0x80
+
+#define AHCI_P_CLB 0x00
+#define AHCI_P_CLBU 0x04
+#define AHCI_P_FB 0x08
+#define AHCI_P_FBU 0x0c
+#define AHCI_P_IS 0x10
+#define AHCI_P_IE 0x14
+#define AHCI_P_IX_DHR 0x00000001
+#define AHCI_P_IX_PS 0x00000002
+#define AHCI_P_IX_DS 0x00000004
+#define AHCI_P_IX_SDB 0x00000008
+#define AHCI_P_IX_UF 0x00000010
+#define AHCI_P_IX_DP 0x00000020
+#define AHCI_P_IX_PC 0x00000040
+#define AHCI_P_IX_MP 0x00000080
+
+#define AHCI_P_IX_PRC 0x00400000
+#define AHCI_P_IX_IPM 0x00800000
+#define AHCI_P_IX_OF 0x01000000
+#define AHCI_P_IX_INF 0x04000000
+#define AHCI_P_IX_IF 0x08000000
+#define AHCI_P_IX_HBD 0x10000000
+#define AHCI_P_IX_HBF 0x20000000
+#define AHCI_P_IX_TFE 0x40000000
+#define AHCI_P_IX_CPD 0x80000000
+
+#define AHCI_P_CMD 0x18
+#define AHCI_P_CMD_ST 0x00000001
+#define AHCI_P_CMD_SUD 0x00000002
+#define AHCI_P_CMD_POD 0x00000004
+#define AHCI_P_CMD_CLO 0x00000008
+#define AHCI_P_CMD_FRE 0x00000010
+#define AHCI_P_CMD_CCS_MASK 0x00001f00
+#define AHCI_P_CMD_CCS_SHIFT 8
+#define AHCI_P_CMD_ISS 0x00002000
+#define AHCI_P_CMD_FR 0x00004000
+#define AHCI_P_CMD_CR 0x00008000
+#define AHCI_P_CMD_CPS 0x00010000
+#define AHCI_P_CMD_PMA 0x00020000
+#define AHCI_P_CMD_HPCP 0x00040000
+#define AHCI_P_CMD_MPSP 0x00080000
+#define AHCI_P_CMD_CPD 0x00100000
+#define AHCI_P_CMD_ESP 0x00200000
+#define AHCI_P_CMD_FBSCP 0x00400000
+#define AHCI_P_CMD_APSTE 0x00800000
+#define AHCI_P_CMD_ATAPI 0x01000000
+#define AHCI_P_CMD_DLAE 0x02000000
+#define AHCI_P_CMD_ALPE 0x04000000
+#define AHCI_P_CMD_ASP 0x08000000
+#define AHCI_P_CMD_ICC_MASK 0xf0000000
+#define AHCI_P_CMD_NOOP 0x00000000
+#define AHCI_P_CMD_ACTIVE 0x10000000
+#define AHCI_P_CMD_PARTIAL 0x20000000
+#define AHCI_P_CMD_SLUMBER 0x60000000
+#define AHCI_P_CMD_DEVSLEEP 0x80000000
+
+#define AHCI_P_TFD 0x20
+#define AHCI_P_SIG 0x24
+#define AHCI_P_SSTS 0x28
+#define AHCI_P_SCTL 0x2c
+#define AHCI_P_SERR 0x30
+#define AHCI_P_SACT 0x34
+#define AHCI_P_CI 0x38
+#define AHCI_P_SNTF 0x3C
+#define AHCI_P_FBS 0x40
+#define AHCI_P_FBS_EN 0x00000001
+#define AHCI_P_FBS_DEC 0x00000002
+#define AHCI_P_FBS_SDE 0x00000004
+#define AHCI_P_FBS_DEV 0x00000f00
+#define AHCI_P_FBS_DEV_SHIFT 8
+#define AHCI_P_FBS_ADO 0x0000f000
+#define AHCI_P_FBS_ADO_SHIFT 12
+#define AHCI_P_FBS_DWE 0x000f0000
+#define AHCI_P_FBS_DWE_SHIFT 16
+#define AHCI_P_DEVSLP 0x44
+#define AHCI_P_DEVSLP_ADSE 0x00000001
+#define AHCI_P_DEVSLP_DSP 0x00000002
+#define AHCI_P_DEVSLP_DETO 0x000003fc
+#define AHCI_P_DEVSLP_DETO_SHIFT 2
+#define AHCI_P_DEVSLP_MDAT 0x00007c00
+#define AHCI_P_DEVSLP_MDAT_SHIFT 10
+#define AHCI_P_DEVSLP_DITO 0x01ff8000
+#define AHCI_P_DEVSLP_DITO_SHIFT 15
+#define AHCI_P_DEVSLP_DM 0x0e000000
+#define AHCI_P_DEVSLP_DM_SHIFT 25
+
+/* Just to be sure, if building as module. */
+#if MAXPHYS < 512 * 1024
+#undef MAXPHYS
+#define MAXPHYS 512 * 1024
+#endif
+/* Pessimistic prognosis on number of required S/G entries */
+#define AHCI_SG_ENTRIES (roundup(btoc(MAXPHYS) + 1, 8))
+/* Command list. 32 commands. First, 1Kbyte aligned. */
+#define AHCI_CL_OFFSET 0
+#define AHCI_CL_SIZE 32
+/* Command tables. Up to 32 commands, Each, 128byte aligned. */
+#define AHCI_CT_OFFSET (AHCI_CL_OFFSET + AHCI_CL_SIZE * AHCI_MAX_SLOTS)
+#define AHCI_CT_SIZE (128 + AHCI_SG_ENTRIES * 16)
+/* Total main work area. */
+#define AHCI_WORK_SIZE (AHCI_CT_OFFSET + AHCI_CT_SIZE * ch->numslots)
+
+#endif /* _AHCI_H_ */
diff --git a/usr.sbin/bhyve/atkbdc.c b/usr.sbin/bhyve/atkbdc.c
new file mode 100644
index 0000000..930b7af
--- /dev/null
+++ b/usr.sbin/bhyve/atkbdc.c
@@ -0,0 +1,90 @@
+/*-
+ * Copyright (c) 2014 Tycho Nightingale <tycho.nightingale@pluribusnetworks.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 ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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 <machine/vmm.h>
+
+#include <vmmapi.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+
+#include "inout.h"
+#include "pci_lpc.h"
+
+#define KBD_DATA_PORT 0x60
+
+#define KBD_STS_CTL_PORT 0x64
+#define KBD_SYS_FLAG 0x4
+
+#define KBDC_RESET 0xfe
+
+static int
+atkbdc_data_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
+ uint32_t *eax, void *arg)
+{
+ if (bytes != 1)
+ return (-1);
+
+ *eax = 0;
+
+ return (0);
+}
+
+static int
+atkbdc_sts_ctl_handler(struct vmctx *ctx, int vcpu, int in, int port,
+ int bytes, uint32_t *eax, void *arg)
+{
+ int error, retval;
+
+ if (bytes != 1)
+ return (-1);
+
+ retval = 0;
+ if (in) {
+ *eax = KBD_SYS_FLAG; /* system passed POST */
+ } else {
+ switch (*eax) {
+ case KBDC_RESET: /* Pulse "reset" line. */
+ error = vm_suspend(ctx, VM_SUSPEND_RESET);
+ assert(error == 0 || errno == EALREADY);
+ break;
+ }
+ }
+
+ return (retval);
+}
+
+INOUT_PORT(atkdbc, KBD_DATA_PORT, IOPORT_F_INOUT, atkbdc_data_handler);
+SYSRES_IO(KBD_DATA_PORT, 1);
+INOUT_PORT(atkbdc, KBD_STS_CTL_PORT, IOPORT_F_INOUT,
+ atkbdc_sts_ctl_handler);
+SYSRES_IO(KBD_STS_CTL_PORT, 1);
diff --git a/usr.sbin/bhyve/bhyve.8 b/usr.sbin/bhyve/bhyve.8
new file mode 100644
index 0000000..f101700
--- /dev/null
+++ b/usr.sbin/bhyve/bhyve.8
@@ -0,0 +1,352 @@
+.\" Copyright (c) 2013 Peter Grehan
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 August 7, 2015
+.Dt BHYVE 8
+.Os
+.Sh NAME
+.Nm bhyve
+.Nd "run a guest operating system inside a virtual machine"
+.Sh SYNOPSIS
+.Nm
+.Op Fl abehuwxACHPSWY
+.Op Fl c Ar numcpus
+.Op Fl g Ar gdbport
+.Op Fl l Ar lpcdev Ns Op , Ns Ar conf
+.Op Fl m Ar size Ns Op Ar K|k|M|m|G|g|T|t
+.Op Fl p Ar vcpu:hostcpu
+.Op Fl s Ar slot,emulation Ns Op , Ns Ar conf
+.Op Fl U Ar uuid
+.Ar vmname
+.Sh DESCRIPTION
+.Nm
+is a hypervisor that runs guest operating systems inside a
+virtual machine.
+.Pp
+Parameters such as the number of virtual CPUs, amount of guest memory, and
+I/O connectivity can be specified with command-line parameters.
+.Pp
+The guest operating system must be loaded with
+.Xr bhyveload 8
+or a similar boot loader before running
+.Nm .
+.Pp
+.Nm
+runs until the guest operating system reboots or an unhandled hypervisor
+exit is detected.
+.Sh OPTIONS
+.Bl -tag -width 10n
+.It Fl a
+The guest's local APIC is configured in xAPIC mode.
+The xAPIC mode is the default setting so this option is redundant.
+It will be deprecated in a future version.
+.It Fl A
+Generate ACPI tables.
+Required for
+.Fx Ns /amd64
+guests.
+.It Fl b
+Enable a low-level console device supported by
+.Fx
+kernels compiled with
+.Cd "device bvmconsole" .
+This option will be deprecated in a future version.
+.It Fl c Ar numcpus
+Number of guest virtual CPUs.
+The default is 1 and the maximum is 16.
+.It Fl C
+Include guest memory in core file.
+.It Fl e
+Force
+.Nm
+to exit when a guest issues an access to an I/O port that is not emulated.
+This is intended for debug purposes.
+.It Fl g Ar gdbport
+For
+.Fx
+kernels compiled with
+.Cd "device bvmdebug" ,
+allow a remote kernel kgdb to be relayed to the guest kernel gdb stub
+via a local IPv4 address and this port.
+This option will be deprecated in a future version.
+.It Fl h
+Print help message and exit.
+.It Fl H
+Yield the virtual CPU thread when a HLT instruction is detected.
+If this option is not specified, virtual CPUs will use 100% of a host CPU.
+.It Fl l Ar lpcdev Ns Op , Ns Ar conf
+Allow devices behind the LPC PCI-ISA bridge to be configured.
+The only supported devices are the TTY-class devices
+.Ar com1
+and
+.Ar com2
+and the boot ROM device
+.Ar bootrom .
+.It Fl m Ar size Ns Op Ar K|k|M|m|G|g|T|t
+Guest physical memory size in bytes.
+This must be the same size that was given to
+.Xr bhyveload 8 .
+.Pp
+The size argument may be suffixed with one of K, M, G or T (either upper
+or lower case) to indicate a multiple of kilobytes, megabytes, gigabytes,
+or terabytes.
+If no suffix is given, the value is assumed to be in megabytes.
+.It Fl p Ar vcpu:hostcpu
+Pin guest's virtual CPU
+.Em vcpu
+to
+.Em hostcpu .
+.It Fl P
+Force the guest virtual CPU to exit when a PAUSE instruction is detected.
+.It Fl s Ar slot,emulation Ns Op , Ns Ar conf
+Configure a virtual PCI slot and function.
+.Pp
+.Nm
+provides PCI bus emulation and virtual devices that can be attached to
+slots on the bus.
+There are 32 available slots, with the option of providing up to 8 functions
+per slot.
+.Bl -tag -width 10n
+.It Ar slot
+.Ar pcislot[:function]
+.Ar bus:pcislot:function
+.Pp
+The
+.Ar pcislot
+value is 0 to 31.
+The optional
+.Ar function
+value is 0 to 7.
+The optional
+.Ar bus
+value is 0 to 255.
+If not specified, the
+.Ar function
+value defaults to 0.
+If not specified, the
+.Ar bus
+value defaults to 0.
+.It Ar emulation
+.Bl -tag -width 10n
+.It Li hostbridge | Li amd_hostbridge
+.Pp
+Provide a simple host bridge.
+This is usually configured at slot 0, and is required by most guest
+operating systems.
+The
+.Li amd_hostbridge
+emulation is identical but uses a PCI vendor ID of
+.Li AMD .
+.It Li passthru
+PCI pass-through device.
+.It Li virtio-net
+Virtio network interface.
+.It Li virtio-blk
+Virtio block storage interface.
+.It Li virtio-rnd
+Virtio RNG interface.
+.It Li ahci-cd
+AHCI controller attached to an ATAPI CD/DVD.
+.It Li ahci-hd
+AHCI controller attached to a SATA hard-drive.
+.It Li uart
+PCI 16550 serial device.
+.It Li lpc
+LPC PCI-ISA bridge with COM1 and COM2 16550 serial ports and a boot ROM.
+The LPC bridge emulation can only be configured on bus 0.
+.El
+.It Op Ar conf
+This optional parameter describes the backend for device emulations.
+If
+.Ar conf
+is not specified, the device emulation has no backend and can be
+considered unconnected.
+.Pp
+Network devices:
+.Bl -tag -width 10n
+.It Ar tapN Ns Op , Ns Ar mac=xx:xx:xx:xx:xx:xx
+.It Ar vmnetN Ns Op , Ns Ar mac=xx:xx:xx:xx:xx:xx
+.Pp
+If
+.Ar mac
+is not specified, the MAC address is derived from a fixed OUI and the
+remaining bytes from an MD5 hash of the slot and function numbers and
+the device name.
+.Pp
+The MAC address is an ASCII string in
+.Xr ethers 5
+format.
+.El
+.Pp
+Block storage devices:
+.Bl -tag -width 10n
+.It Pa /filename Ns Oo , Ns Ar block-device-options Oc
+.It Pa /dev/xxx Ns Oo , Ns Ar block-device-options Oc
+.El
+.Pp
+The
+.Ar block-device-options
+are:
+.Bl -tag -width 8n
+.It Li nocache
+Open the file with
+.Dv O_DIRECT .
+.It Li direct
+Open the file using
+.Dv O_SYNC .
+.It Li ro
+Force the file to be opened read-only.
+.It Li sectorsize= Ns Ar logical Ns Oo / Ns Ar physical Oc
+Specify the logical and physical sector sizes of the emulated disk.
+The physical sector size is optional and is equal to the logical sector size
+if not explicitly specified.
+.El
+.Pp
+TTY devices:
+.Bl -tag -width 10n
+.It Li stdio
+Connect the serial port to the standard input and output of
+the
+.Nm
+process.
+.It Pa /dev/xxx
+Use the host TTY device for serial port I/O.
+.El
+.Pp
+Boot ROM device:
+.Bl -tag -width 10n
+.It Pa romfile
+Map
+.Ar romfile
+in the guest address space reserved for boot firmware.
+.El
+.Pp
+Pass-through devices:
+.Bl -tag -width 10n
+.It Ns Ar slot Ns / Ns Ar bus Ns / Ns Ar function
+Connect to a PCI device on the host at the selector described by
+.Ar slot ,
+.Ar bus ,
+and
+.Ar function
+numbers.
+.El
+.Pp
+Guest memory must be wired using the
+.Fl S
+option when a pass-through device is configured.
+.Pp
+The host device must have been reserved at boot-time using the
+.Va pptdev
+loader variable as described in
+.Xr vmm 4 .
+.El
+.It Fl S
+Wire guest memory.
+.It Fl u
+RTC keeps UTC time.
+.It Fl U Ar uuid
+Set the universally unique identifier
+.Pq UUID
+in the guest's System Management BIOS System Information structure.
+By default a UUID is generated from the host's hostname and
+.Ar vmname .
+.It Fl w
+Ignore accesses to unimplemented Model Specific Registers (MSRs).
+This is intended for debug purposes.
+.It Fl W
+Force virtio PCI device emulations to use MSI interrupts instead of MSI-X
+interrupts.
+.It Fl x
+The guest's local APIC is configured in x2APIC mode.
+.It Fl Y
+Disable MPtable generation.
+.It Ar vmname
+Alphanumeric name of the guest.
+This should be the same as that created by
+.Xr bhyveload 8 .
+.El
+.Sh EXAMPLES
+The guest operating system must have been loaded with
+.Xr bhyveload 8
+or a similar boot loader before
+.Xr bhyve 4
+can be run.
+.Pp
+To run a virtual machine with 1GB of memory, two virtual CPUs, a virtio
+block device backed by the
+.Pa /my/image
+filesystem image, and a serial port for the console:
+.Bd -literal -offset indent
+bhyve -c 2 -s 0,hostbridge -s 1,lpc -s 2,virtio-blk,/my/image \\
+ -l com1,stdio -A -H -P -m 1G vm1
+.Ed
+.Pp
+Run a 24GB single-CPU virtual machine with three network ports, one of which
+has a MAC address specified:
+.Bd -literal -offset indent
+bhyve -s 0,hostbridge -s 1,lpc -s 2:0,virtio-net,tap0 \\
+ -s 2:1,virtio-net,tap1 \\
+ -s 2:2,virtio-net,tap2,mac=00:be:fa:76:45:00 \\
+ -s 3,virtio-blk,/my/image -l com1,stdio \\
+ -A -H -P -m 24G bigvm
+.Ed
+.Pp
+Run an 8GB quad-CPU virtual machine with 8 AHCI SATA disks, an AHCI ATAPI
+CD-ROM, a single virtio network port, an AMD hostbridge, and the console
+port connected to an
+.Xr nmdm 4
+null-modem device.
+.Bd -literal -offset indent
+bhyve -c 4 \\
+ -s 0,amd_hostbridge -s 1,lpc \\
+ -s 1:0,ahci-hd,/images/disk.1 \\
+ -s 1:1,ahci-hd,/images/disk.2 \\
+ -s 1:2,ahci-hd,/images/disk.3 \\
+ -s 1:3,ahci-hd,/images/disk.4 \\
+ -s 1:4,ahci-hd,/images/disk.5 \\
+ -s 1:5,ahci-hd,/images/disk.6 \\
+ -s 1:6,ahci-hd,/images/disk.7 \\
+ -s 1:7,ahci-hd,/images/disk.8 \\
+ -s 2,ahci-cd,/images/install.iso \\
+ -s 3,virtio-net,tap0 \\
+ -l com1,/dev/nmdm0A \\
+ -A -H -P -m 8G
+.Ed
+.Sh SEE ALSO
+.Xr bhyve 4 ,
+.Xr nmdm 4 ,
+.Xr vmm 4 ,
+.Xr ethers 5 ,
+.Xr bhyvectl 8 ,
+.Xr bhyveload 8
+.Sh HISTORY
+.Nm
+first appeared in
+.Fx 10.0 .
+.Sh AUTHORS
+.An Neel Natu Aq Mt neel@freebsd.org
+.An Peter Grehan Aq Mt grehan@freebsd.org
diff --git a/usr.sbin/bhyve/bhyverun.c b/usr.sbin/bhyve/bhyverun.c
new file mode 100644
index 0000000..bfa135b
--- /dev/null
+++ b/usr.sbin/bhyve/bhyverun.c
@@ -0,0 +1,971 @@
+/*-
+ * Copyright (c) 2011 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <sys/types.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+
+#include <machine/atomic.h>
+#include <machine/segments.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <err.h>
+#include <libgen.h>
+#include <unistd.h>
+#include <assert.h>
+#include <errno.h>
+#include <pthread.h>
+#include <pthread_np.h>
+#include <sysexits.h>
+#include <stdbool.h>
+
+#include <machine/vmm.h>
+#include <vmmapi.h>
+
+#include "bhyverun.h"
+#include "acpi.h"
+#include "inout.h"
+#include "dbgport.h"
+#include "fwctl.h"
+#include "ioapic.h"
+#include "mem.h"
+#include "mevent.h"
+#include "mptbl.h"
+#include "pci_emul.h"
+#include "pci_irq.h"
+#include "pci_lpc.h"
+#include "smbiostbl.h"
+#include "xmsr.h"
+#include "spinup_ap.h"
+#include "rtc.h"
+
+#define GUEST_NIO_PORT 0x488 /* guest upcalls via i/o port */
+
+#define MB (1024UL * 1024)
+#define GB (1024UL * MB)
+
+typedef int (*vmexit_handler_t)(struct vmctx *, struct vm_exit *, int *vcpu);
+extern int vmexit_task_switch(struct vmctx *, struct vm_exit *, int *vcpu);
+
+char *vmname;
+
+int guest_ncpus;
+char *guest_uuid_str;
+
+static int guest_vmexit_on_hlt, guest_vmexit_on_pause;
+static int virtio_msix = 1;
+static int x2apic_mode = 0; /* default is xAPIC */
+
+static int strictio;
+static int strictmsr = 1;
+
+static int acpi;
+
+static char *progname;
+static const int BSP = 0;
+
+static cpuset_t cpumask;
+
+static void vm_loop(struct vmctx *ctx, int vcpu, uint64_t rip);
+
+static struct vm_exit vmexit[VM_MAXCPU];
+
+struct bhyvestats {
+ uint64_t vmexit_bogus;
+ uint64_t vmexit_reqidle;
+ uint64_t vmexit_hlt;
+ uint64_t vmexit_pause;
+ uint64_t vmexit_mtrap;
+ uint64_t vmexit_inst_emul;
+ uint64_t cpu_switch_rotate;
+ uint64_t cpu_switch_direct;
+} stats;
+
+struct mt_vmm_info {
+ pthread_t mt_thr;
+ struct vmctx *mt_ctx;
+ int mt_vcpu;
+} mt_vmm_info[VM_MAXCPU];
+
+static cpuset_t *vcpumap[VM_MAXCPU] = { NULL };
+
+static void
+usage(int code)
+{
+
+ fprintf(stderr,
+ "Usage: %s [-abehuwxACHPSWY] [-c vcpus] [-g <gdb port>] [-l <lpc>]\n"
+ " %*s [-m mem] [-p vcpu:hostcpu] [-s <pci>] [-U uuid] <vm>\n"
+ " -a: local apic is in xAPIC mode (deprecated)\n"
+ " -A: create ACPI tables\n"
+ " -c: # cpus (default 1)\n"
+ " -C: include guest memory in core file\n"
+ " -e: exit on unhandled I/O access\n"
+ " -g: gdb port\n"
+ " -h: help\n"
+ " -H: vmexit from the guest on hlt\n"
+ " -l: LPC device configuration\n"
+ " -m: memory size in MB\n"
+ " -p: pin 'vcpu' to 'hostcpu'\n"
+ " -P: vmexit from the guest on pause\n"
+ " -s: <slot,driver,configinfo> PCI slot config\n"
+ " -S: guest memory cannot be swapped\n"
+ " -u: RTC keeps UTC time\n"
+ " -U: uuid\n"
+ " -w: ignore unimplemented MSRs\n"
+ " -W: force virtio to use single-vector MSI\n"
+ " -x: local apic is in x2APIC mode\n"
+ " -Y: disable MPtable generation\n",
+ progname, (int)strlen(progname), "");
+
+ exit(code);
+}
+
+static int
+pincpu_parse(const char *opt)
+{
+ int vcpu, pcpu;
+
+ if (sscanf(opt, "%d:%d", &vcpu, &pcpu) != 2) {
+ fprintf(stderr, "invalid format: %s\n", opt);
+ return (-1);
+ }
+
+ if (vcpu < 0 || vcpu >= VM_MAXCPU) {
+ fprintf(stderr, "vcpu '%d' outside valid range from 0 to %d\n",
+ vcpu, VM_MAXCPU - 1);
+ return (-1);
+ }
+
+ if (pcpu < 0 || pcpu >= CPU_SETSIZE) {
+ fprintf(stderr, "hostcpu '%d' outside valid range from "
+ "0 to %d\n", pcpu, CPU_SETSIZE - 1);
+ return (-1);
+ }
+
+ if (vcpumap[vcpu] == NULL) {
+ if ((vcpumap[vcpu] = malloc(sizeof(cpuset_t))) == NULL) {
+ perror("malloc");
+ return (-1);
+ }
+ CPU_ZERO(vcpumap[vcpu]);
+ }
+ CPU_SET(pcpu, vcpumap[vcpu]);
+ return (0);
+}
+
+void
+vm_inject_fault(void *arg, int vcpu, int vector, int errcode_valid,
+ int errcode)
+{
+ struct vmctx *ctx;
+ int error, restart_instruction;
+
+ ctx = arg;
+ restart_instruction = 1;
+
+ error = vm_inject_exception(ctx, vcpu, vector, errcode_valid, errcode,
+ restart_instruction);
+ assert(error == 0);
+}
+
+void *
+paddr_guest2host(struct vmctx *ctx, uintptr_t gaddr, size_t len)
+{
+
+ return (vm_map_gpa(ctx, gaddr, len));
+}
+
+int
+fbsdrun_vmexit_on_pause(void)
+{
+
+ return (guest_vmexit_on_pause);
+}
+
+int
+fbsdrun_vmexit_on_hlt(void)
+{
+
+ return (guest_vmexit_on_hlt);
+}
+
+int
+fbsdrun_virtio_msix(void)
+{
+
+ return (virtio_msix);
+}
+
+static void *
+fbsdrun_start_thread(void *param)
+{
+ char tname[MAXCOMLEN + 1];
+ struct mt_vmm_info *mtp;
+ int vcpu;
+
+ mtp = param;
+ vcpu = mtp->mt_vcpu;
+
+ snprintf(tname, sizeof(tname), "vcpu %d", vcpu);
+ pthread_set_name_np(mtp->mt_thr, tname);
+
+ vm_loop(mtp->mt_ctx, vcpu, vmexit[vcpu].rip);
+
+ /* not reached */
+ exit(1);
+ return (NULL);
+}
+
+void
+fbsdrun_addcpu(struct vmctx *ctx, int fromcpu, int newcpu, uint64_t rip)
+{
+ int error;
+
+ assert(fromcpu == BSP);
+
+ /*
+ * The 'newcpu' must be activated in the context of 'fromcpu'. If
+ * vm_activate_cpu() is delayed until newcpu's pthread starts running
+ * then vmm.ko is out-of-sync with bhyve and this can create a race
+ * with vm_suspend().
+ */
+ error = vm_activate_cpu(ctx, newcpu);
+ if (error != 0)
+ err(EX_OSERR, "could not activate CPU %d", newcpu);
+
+ CPU_SET_ATOMIC(newcpu, &cpumask);
+
+ /*
+ * Set up the vmexit struct to allow execution to start
+ * at the given RIP
+ */
+ vmexit[newcpu].rip = rip;
+ vmexit[newcpu].inst_length = 0;
+
+ mt_vmm_info[newcpu].mt_ctx = ctx;
+ mt_vmm_info[newcpu].mt_vcpu = newcpu;
+
+ error = pthread_create(&mt_vmm_info[newcpu].mt_thr, NULL,
+ fbsdrun_start_thread, &mt_vmm_info[newcpu]);
+ assert(error == 0);
+}
+
+static int
+fbsdrun_deletecpu(struct vmctx *ctx, int vcpu)
+{
+
+ if (!CPU_ISSET(vcpu, &cpumask)) {
+ fprintf(stderr, "Attempting to delete unknown cpu %d\n", vcpu);
+ exit(1);
+ }
+
+ CPU_CLR_ATOMIC(vcpu, &cpumask);
+ return (CPU_EMPTY(&cpumask));
+}
+
+static int
+vmexit_handle_notify(struct vmctx *ctx, struct vm_exit *vme, int *pvcpu,
+ uint32_t eax)
+{
+#if BHYVE_DEBUG
+ /*
+ * put guest-driven debug here
+ */
+#endif
+ return (VMEXIT_CONTINUE);
+}
+
+static int
+vmexit_inout(struct vmctx *ctx, struct vm_exit *vme, int *pvcpu)
+{
+ int error;
+ int bytes, port, in, out;
+ int vcpu;
+
+ vcpu = *pvcpu;
+
+ port = vme->u.inout.port;
+ bytes = vme->u.inout.bytes;
+ in = vme->u.inout.in;
+ out = !in;
+
+ /* Extra-special case of host notifications */
+ if (out && port == GUEST_NIO_PORT) {
+ error = vmexit_handle_notify(ctx, vme, pvcpu, vme->u.inout.eax);
+ return (error);
+ }
+
+ error = emulate_inout(ctx, vcpu, vme, strictio);
+ if (error) {
+ fprintf(stderr, "Unhandled %s%c 0x%04x at 0x%lx\n",
+ in ? "in" : "out",
+ bytes == 1 ? 'b' : (bytes == 2 ? 'w' : 'l'),
+ port, vmexit->rip);
+ return (VMEXIT_ABORT);
+ } else {
+ return (VMEXIT_CONTINUE);
+ }
+}
+
+static int
+vmexit_rdmsr(struct vmctx *ctx, struct vm_exit *vme, int *pvcpu)
+{
+ uint64_t val;
+ uint32_t eax, edx;
+ int error;
+
+ val = 0;
+ error = emulate_rdmsr(ctx, *pvcpu, vme->u.msr.code, &val);
+ if (error != 0) {
+ fprintf(stderr, "rdmsr to register %#x on vcpu %d\n",
+ vme->u.msr.code, *pvcpu);
+ if (strictmsr) {
+ vm_inject_gp(ctx, *pvcpu);
+ return (VMEXIT_CONTINUE);
+ }
+ }
+
+ eax = val;
+ error = vm_set_register(ctx, *pvcpu, VM_REG_GUEST_RAX, eax);
+ assert(error == 0);
+
+ edx = val >> 32;
+ error = vm_set_register(ctx, *pvcpu, VM_REG_GUEST_RDX, edx);
+ assert(error == 0);
+
+ return (VMEXIT_CONTINUE);
+}
+
+static int
+vmexit_wrmsr(struct vmctx *ctx, struct vm_exit *vme, int *pvcpu)
+{
+ int error;
+
+ error = emulate_wrmsr(ctx, *pvcpu, vme->u.msr.code, vme->u.msr.wval);
+ if (error != 0) {
+ fprintf(stderr, "wrmsr to register %#x(%#lx) on vcpu %d\n",
+ vme->u.msr.code, vme->u.msr.wval, *pvcpu);
+ if (strictmsr) {
+ vm_inject_gp(ctx, *pvcpu);
+ return (VMEXIT_CONTINUE);
+ }
+ }
+ return (VMEXIT_CONTINUE);
+}
+
+static int
+vmexit_spinup_ap(struct vmctx *ctx, struct vm_exit *vme, int *pvcpu)
+{
+ int newcpu;
+ int retval = VMEXIT_CONTINUE;
+
+ newcpu = spinup_ap(ctx, *pvcpu,
+ vme->u.spinup_ap.vcpu, vme->u.spinup_ap.rip);
+
+ return (retval);
+}
+
+#define DEBUG_EPT_MISCONFIG
+#ifdef DEBUG_EPT_MISCONFIG
+#define EXIT_REASON_EPT_MISCONFIG 49
+#define VMCS_GUEST_PHYSICAL_ADDRESS 0x00002400
+#define VMCS_IDENT(x) ((x) | 0x80000000)
+
+static uint64_t ept_misconfig_gpa, ept_misconfig_pte[4];
+static int ept_misconfig_ptenum;
+#endif
+
+static int
+vmexit_vmx(struct vmctx *ctx, struct vm_exit *vmexit, int *pvcpu)
+{
+
+ fprintf(stderr, "vm exit[%d]\n", *pvcpu);
+ fprintf(stderr, "\treason\t\tVMX\n");
+ fprintf(stderr, "\trip\t\t0x%016lx\n", vmexit->rip);
+ fprintf(stderr, "\tinst_length\t%d\n", vmexit->inst_length);
+ fprintf(stderr, "\tstatus\t\t%d\n", vmexit->u.vmx.status);
+ fprintf(stderr, "\texit_reason\t%u\n", vmexit->u.vmx.exit_reason);
+ fprintf(stderr, "\tqualification\t0x%016lx\n",
+ vmexit->u.vmx.exit_qualification);
+ fprintf(stderr, "\tinst_type\t\t%d\n", vmexit->u.vmx.inst_type);
+ fprintf(stderr, "\tinst_error\t\t%d\n", vmexit->u.vmx.inst_error);
+#ifdef DEBUG_EPT_MISCONFIG
+ if (vmexit->u.vmx.exit_reason == EXIT_REASON_EPT_MISCONFIG) {
+ vm_get_register(ctx, *pvcpu,
+ VMCS_IDENT(VMCS_GUEST_PHYSICAL_ADDRESS),
+ &ept_misconfig_gpa);
+ vm_get_gpa_pmap(ctx, ept_misconfig_gpa, ept_misconfig_pte,
+ &ept_misconfig_ptenum);
+ fprintf(stderr, "\tEPT misconfiguration:\n");
+ fprintf(stderr, "\t\tGPA: %#lx\n", ept_misconfig_gpa);
+ fprintf(stderr, "\t\tPTE(%d): %#lx %#lx %#lx %#lx\n",
+ ept_misconfig_ptenum, ept_misconfig_pte[0],
+ ept_misconfig_pte[1], ept_misconfig_pte[2],
+ ept_misconfig_pte[3]);
+ }
+#endif /* DEBUG_EPT_MISCONFIG */
+ return (VMEXIT_ABORT);
+}
+
+static int
+vmexit_svm(struct vmctx *ctx, struct vm_exit *vmexit, int *pvcpu)
+{
+
+ fprintf(stderr, "vm exit[%d]\n", *pvcpu);
+ fprintf(stderr, "\treason\t\tSVM\n");
+ fprintf(stderr, "\trip\t\t0x%016lx\n", vmexit->rip);
+ fprintf(stderr, "\tinst_length\t%d\n", vmexit->inst_length);
+ fprintf(stderr, "\texitcode\t%#lx\n", vmexit->u.svm.exitcode);
+ fprintf(stderr, "\texitinfo1\t%#lx\n", vmexit->u.svm.exitinfo1);
+ fprintf(stderr, "\texitinfo2\t%#lx\n", vmexit->u.svm.exitinfo2);
+ return (VMEXIT_ABORT);
+}
+
+static int
+vmexit_bogus(struct vmctx *ctx, struct vm_exit *vmexit, int *pvcpu)
+{
+
+ assert(vmexit->inst_length == 0);
+
+ stats.vmexit_bogus++;
+
+ return (VMEXIT_CONTINUE);
+}
+
+static int
+vmexit_reqidle(struct vmctx *ctx, struct vm_exit *vmexit, int *pvcpu)
+{
+
+ assert(vmexit->inst_length == 0);
+
+ stats.vmexit_reqidle++;
+
+ return (VMEXIT_CONTINUE);
+}
+
+static int
+vmexit_hlt(struct vmctx *ctx, struct vm_exit *vmexit, int *pvcpu)
+{
+
+ stats.vmexit_hlt++;
+
+ /*
+ * Just continue execution with the next instruction. We use
+ * the HLT VM exit as a way to be friendly with the host
+ * scheduler.
+ */
+ return (VMEXIT_CONTINUE);
+}
+
+static int
+vmexit_pause(struct vmctx *ctx, struct vm_exit *vmexit, int *pvcpu)
+{
+
+ stats.vmexit_pause++;
+
+ return (VMEXIT_CONTINUE);
+}
+
+static int
+vmexit_mtrap(struct vmctx *ctx, struct vm_exit *vmexit, int *pvcpu)
+{
+
+ assert(vmexit->inst_length == 0);
+
+ stats.vmexit_mtrap++;
+
+ return (VMEXIT_CONTINUE);
+}
+
+static int
+vmexit_inst_emul(struct vmctx *ctx, struct vm_exit *vmexit, int *pvcpu)
+{
+ int err, i;
+ struct vie *vie;
+
+ stats.vmexit_inst_emul++;
+
+ vie = &vmexit->u.inst_emul.vie;
+ err = emulate_mem(ctx, *pvcpu, vmexit->u.inst_emul.gpa,
+ vie, &vmexit->u.inst_emul.paging);
+
+ if (err) {
+ if (err == ESRCH) {
+ fprintf(stderr, "Unhandled memory access to 0x%lx\n",
+ vmexit->u.inst_emul.gpa);
+ }
+
+ fprintf(stderr, "Failed to emulate instruction [");
+ for (i = 0; i < vie->num_valid; i++) {
+ fprintf(stderr, "0x%02x%s", vie->inst[i],
+ i != (vie->num_valid - 1) ? " " : "");
+ }
+ fprintf(stderr, "] at 0x%lx\n", vmexit->rip);
+ return (VMEXIT_ABORT);
+ }
+
+ return (VMEXIT_CONTINUE);
+}
+
+static pthread_mutex_t resetcpu_mtx = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t resetcpu_cond = PTHREAD_COND_INITIALIZER;
+
+static int
+vmexit_suspend(struct vmctx *ctx, struct vm_exit *vmexit, int *pvcpu)
+{
+ enum vm_suspend_how how;
+
+ how = vmexit->u.suspended.how;
+
+ fbsdrun_deletecpu(ctx, *pvcpu);
+
+ if (*pvcpu != BSP) {
+ pthread_mutex_lock(&resetcpu_mtx);
+ pthread_cond_signal(&resetcpu_cond);
+ pthread_mutex_unlock(&resetcpu_mtx);
+ pthread_exit(NULL);
+ }
+
+ pthread_mutex_lock(&resetcpu_mtx);
+ while (!CPU_EMPTY(&cpumask)) {
+ pthread_cond_wait(&resetcpu_cond, &resetcpu_mtx);
+ }
+ pthread_mutex_unlock(&resetcpu_mtx);
+
+ switch (how) {
+ case VM_SUSPEND_RESET:
+ exit(0);
+ case VM_SUSPEND_POWEROFF:
+ exit(1);
+ case VM_SUSPEND_HALT:
+ exit(2);
+ case VM_SUSPEND_TRIPLEFAULT:
+ exit(3);
+ default:
+ fprintf(stderr, "vmexit_suspend: invalid reason %d\n", how);
+ exit(100);
+ }
+ return (0); /* NOTREACHED */
+}
+
+static vmexit_handler_t handler[VM_EXITCODE_MAX] = {
+ [VM_EXITCODE_INOUT] = vmexit_inout,
+ [VM_EXITCODE_INOUT_STR] = vmexit_inout,
+ [VM_EXITCODE_VMX] = vmexit_vmx,
+ [VM_EXITCODE_SVM] = vmexit_svm,
+ [VM_EXITCODE_BOGUS] = vmexit_bogus,
+ [VM_EXITCODE_REQIDLE] = vmexit_reqidle,
+ [VM_EXITCODE_RDMSR] = vmexit_rdmsr,
+ [VM_EXITCODE_WRMSR] = vmexit_wrmsr,
+ [VM_EXITCODE_MTRAP] = vmexit_mtrap,
+ [VM_EXITCODE_INST_EMUL] = vmexit_inst_emul,
+ [VM_EXITCODE_SPINUP_AP] = vmexit_spinup_ap,
+ [VM_EXITCODE_SUSPENDED] = vmexit_suspend,
+ [VM_EXITCODE_TASK_SWITCH] = vmexit_task_switch,
+};
+
+static void
+vm_loop(struct vmctx *ctx, int vcpu, uint64_t startrip)
+{
+ int error, rc;
+ enum vm_exitcode exitcode;
+ cpuset_t active_cpus;
+
+ if (vcpumap[vcpu] != NULL) {
+ error = pthread_setaffinity_np(pthread_self(),
+ sizeof(cpuset_t), vcpumap[vcpu]);
+ assert(error == 0);
+ }
+
+ error = vm_active_cpus(ctx, &active_cpus);
+ assert(CPU_ISSET(vcpu, &active_cpus));
+
+ error = vm_set_register(ctx, vcpu, VM_REG_GUEST_RIP, startrip);
+ assert(error == 0);
+
+ while (1) {
+ error = vm_run(ctx, vcpu, &vmexit[vcpu]);
+ if (error != 0)
+ break;
+
+ exitcode = vmexit[vcpu].exitcode;
+ if (exitcode >= VM_EXITCODE_MAX || handler[exitcode] == NULL) {
+ fprintf(stderr, "vm_loop: unexpected exitcode 0x%x\n",
+ exitcode);
+ exit(1);
+ }
+
+ rc = (*handler[exitcode])(ctx, &vmexit[vcpu], &vcpu);
+
+ switch (rc) {
+ case VMEXIT_CONTINUE:
+ break;
+ case VMEXIT_ABORT:
+ abort();
+ default:
+ exit(1);
+ }
+ }
+ fprintf(stderr, "vm_run error %d, errno %d\n", error, errno);
+}
+
+static int
+num_vcpus_allowed(struct vmctx *ctx)
+{
+ int tmp, error;
+
+ error = vm_get_capability(ctx, BSP, VM_CAP_UNRESTRICTED_GUEST, &tmp);
+
+ /*
+ * The guest is allowed to spinup more than one processor only if the
+ * UNRESTRICTED_GUEST capability is available.
+ */
+ if (error == 0)
+ return (VM_MAXCPU);
+ else
+ return (1);
+}
+
+void
+fbsdrun_set_capabilities(struct vmctx *ctx, int cpu)
+{
+ int err, tmp;
+
+ if (fbsdrun_vmexit_on_hlt()) {
+ err = vm_get_capability(ctx, cpu, VM_CAP_HALT_EXIT, &tmp);
+ if (err < 0) {
+ fprintf(stderr, "VM exit on HLT not supported\n");
+ exit(1);
+ }
+ vm_set_capability(ctx, cpu, VM_CAP_HALT_EXIT, 1);
+ if (cpu == BSP)
+ handler[VM_EXITCODE_HLT] = vmexit_hlt;
+ }
+
+ if (fbsdrun_vmexit_on_pause()) {
+ /*
+ * pause exit support required for this mode
+ */
+ err = vm_get_capability(ctx, cpu, VM_CAP_PAUSE_EXIT, &tmp);
+ if (err < 0) {
+ fprintf(stderr,
+ "SMP mux requested, no pause support\n");
+ exit(1);
+ }
+ vm_set_capability(ctx, cpu, VM_CAP_PAUSE_EXIT, 1);
+ if (cpu == BSP)
+ handler[VM_EXITCODE_PAUSE] = vmexit_pause;
+ }
+
+ if (x2apic_mode)
+ err = vm_set_x2apic_state(ctx, cpu, X2APIC_ENABLED);
+ else
+ err = vm_set_x2apic_state(ctx, cpu, X2APIC_DISABLED);
+
+ if (err) {
+ fprintf(stderr, "Unable to set x2apic state (%d)\n", err);
+ exit(1);
+ }
+
+ vm_set_capability(ctx, cpu, VM_CAP_ENABLE_INVPCID, 1);
+}
+
+static struct vmctx *
+do_open(const char *vmname)
+{
+ struct vmctx *ctx;
+ int error;
+ bool reinit, romboot;
+
+ reinit = romboot = false;
+
+ if (lpc_bootrom())
+ romboot = true;
+
+ error = vm_create(vmname);
+ if (error) {
+ if (errno == EEXIST) {
+ if (romboot) {
+ reinit = true;
+ } else {
+ /*
+ * The virtual machine has been setup by the
+ * userspace bootloader.
+ */
+ }
+ } else {
+ perror("vm_create");
+ exit(1);
+ }
+ } else {
+ if (!romboot) {
+ /*
+ * If the virtual machine was just created then a
+ * bootrom must be configured to boot it.
+ */
+ fprintf(stderr, "virtual machine cannot be booted\n");
+ exit(1);
+ }
+ }
+
+ ctx = vm_open(vmname);
+ if (ctx == NULL) {
+ perror("vm_open");
+ exit(1);
+ }
+
+ if (reinit) {
+ error = vm_reinit(ctx);
+ if (error) {
+ perror("vm_reinit");
+ exit(1);
+ }
+ }
+ return (ctx);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int c, error, gdb_port, err, bvmcons;
+ int max_vcpus, mptgen, memflags;
+ int rtc_localtime;
+ struct vmctx *ctx;
+ uint64_t rip;
+ size_t memsize;
+ char *optstr;
+
+ bvmcons = 0;
+ progname = basename(argv[0]);
+ gdb_port = 0;
+ guest_ncpus = 1;
+ memsize = 256 * MB;
+ mptgen = 1;
+ rtc_localtime = 1;
+ memflags = 0;
+
+ optstr = "abehuwxACHIPSWYp:g:c:s:m:l:U:";
+ while ((c = getopt(argc, argv, optstr)) != -1) {
+ switch (c) {
+ case 'a':
+ x2apic_mode = 0;
+ break;
+ case 'A':
+ acpi = 1;
+ break;
+ case 'b':
+ bvmcons = 1;
+ break;
+ case 'p':
+ if (pincpu_parse(optarg) != 0) {
+ errx(EX_USAGE, "invalid vcpu pinning "
+ "configuration '%s'", optarg);
+ }
+ break;
+ case 'c':
+ guest_ncpus = atoi(optarg);
+ break;
+ case 'C':
+ memflags |= VM_MEM_F_INCORE;
+ break;
+ case 'g':
+ gdb_port = atoi(optarg);
+ break;
+ case 'l':
+ if (lpc_device_parse(optarg) != 0) {
+ errx(EX_USAGE, "invalid lpc device "
+ "configuration '%s'", optarg);
+ }
+ break;
+ case 's':
+ if (pci_parse_slot(optarg) != 0)
+ exit(1);
+ else
+ break;
+ case 'S':
+ memflags |= VM_MEM_F_WIRED;
+ break;
+ case 'm':
+ error = vm_parse_memsize(optarg, &memsize);
+ if (error)
+ errx(EX_USAGE, "invalid memsize '%s'", optarg);
+ break;
+ case 'H':
+ guest_vmexit_on_hlt = 1;
+ break;
+ case 'I':
+ /*
+ * The "-I" option was used to add an ioapic to the
+ * virtual machine.
+ *
+ * An ioapic is now provided unconditionally for each
+ * virtual machine and this option is now deprecated.
+ */
+ break;
+ case 'P':
+ guest_vmexit_on_pause = 1;
+ break;
+ case 'e':
+ strictio = 1;
+ break;
+ case 'u':
+ rtc_localtime = 0;
+ break;
+ case 'U':
+ guest_uuid_str = optarg;
+ break;
+ case 'w':
+ strictmsr = 0;
+ break;
+ case 'W':
+ virtio_msix = 0;
+ break;
+ case 'x':
+ x2apic_mode = 1;
+ break;
+ case 'Y':
+ mptgen = 0;
+ break;
+ case 'h':
+ usage(0);
+ default:
+ usage(1);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage(1);
+
+ vmname = argv[0];
+ ctx = do_open(vmname);
+
+ if (guest_ncpus < 1) {
+ fprintf(stderr, "Invalid guest vCPUs (%d)\n", guest_ncpus);
+ exit(1);
+ }
+
+ max_vcpus = num_vcpus_allowed(ctx);
+ if (guest_ncpus > max_vcpus) {
+ fprintf(stderr, "%d vCPUs requested but only %d available\n",
+ guest_ncpus, max_vcpus);
+ exit(1);
+ }
+
+ fbsdrun_set_capabilities(ctx, BSP);
+
+ vm_set_memflags(ctx, memflags);
+ err = vm_setup_memory(ctx, memsize, VM_MMAP_ALL);
+ if (err) {
+ fprintf(stderr, "Unable to setup memory (%d)\n", errno);
+ exit(1);
+ }
+
+ error = init_msr();
+ if (error) {
+ fprintf(stderr, "init_msr error %d", error);
+ exit(1);
+ }
+
+ init_mem();
+ init_inout();
+ pci_irq_init(ctx);
+ ioapic_init(ctx);
+
+ rtc_init(ctx, rtc_localtime);
+ sci_init(ctx);
+
+ /*
+ * Exit if a device emulation finds an error in it's initilization
+ */
+ if (init_pci(ctx) != 0)
+ exit(1);
+
+ if (gdb_port != 0)
+ init_dbgport(gdb_port);
+
+ if (bvmcons)
+ init_bvmcons();
+
+ if (lpc_bootrom()) {
+ if (vm_set_capability(ctx, BSP, VM_CAP_UNRESTRICTED_GUEST, 1)) {
+ fprintf(stderr, "ROM boot failed: unrestricted guest "
+ "capability not available\n");
+ exit(1);
+ }
+ error = vcpu_reset(ctx, BSP);
+ assert(error == 0);
+ }
+
+ error = vm_get_register(ctx, BSP, VM_REG_GUEST_RIP, &rip);
+ assert(error == 0);
+
+ /*
+ * build the guest tables, MP etc.
+ */
+ if (mptgen) {
+ error = mptable_build(ctx, guest_ncpus);
+ if (error)
+ exit(1);
+ }
+
+ error = smbios_build(ctx);
+ assert(error == 0);
+
+ if (acpi) {
+ error = acpi_build(ctx, guest_ncpus);
+ assert(error == 0);
+ }
+
+ if (lpc_bootrom())
+ fwctl_init();
+
+ /*
+ * Change the proc title to include the VM name.
+ */
+ setproctitle("%s", vmname);
+
+ /*
+ * Add CPU 0
+ */
+ fbsdrun_addcpu(ctx, BSP, BSP, rip);
+
+ /*
+ * Head off to the main event dispatch loop
+ */
+ mevent_dispatch();
+
+ exit(1);
+}
diff --git a/usr.sbin/bhyve/bhyverun.h b/usr.sbin/bhyve/bhyverun.h
new file mode 100644
index 0000000..c51bf48
--- /dev/null
+++ b/usr.sbin/bhyve/bhyverun.h
@@ -0,0 +1,55 @@
+/*-
+ * Copyright (c) 2011 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 _FBSDRUN_H_
+#define _FBSDRUN_H_
+
+#ifndef CTASSERT /* Allow lint to override */
+#define CTASSERT(x) _CTASSERT(x, __LINE__)
+#define _CTASSERT(x, y) __CTASSERT(x, y)
+#define __CTASSERT(x, y) typedef char __assert ## y[(x) ? 1 : -1]
+#endif
+
+#define VMEXIT_CONTINUE (0)
+#define VMEXIT_ABORT (-1)
+
+struct vmctx;
+extern int guest_ncpus;
+extern char *guest_uuid_str;
+extern char *vmname;
+
+void *paddr_guest2host(struct vmctx *ctx, uintptr_t addr, size_t len);
+
+void fbsdrun_set_capabilities(struct vmctx *ctx, int cpu);
+void fbsdrun_addcpu(struct vmctx *ctx, int fromcpu, int newcpu, uint64_t rip);
+int fbsdrun_muxed(void);
+int fbsdrun_vmexit_on_hlt(void);
+int fbsdrun_vmexit_on_pause(void);
+int fbsdrun_disable_x2apic(void);
+int fbsdrun_virtio_msix(void);
+#endif
diff --git a/usr.sbin/bhyve/block_if.c b/usr.sbin/bhyve/block_if.c
new file mode 100644
index 0000000..ef8e11e
--- /dev/null
+++ b/usr.sbin/bhyve/block_if.c
@@ -0,0 +1,822 @@
+/*-
+ * Copyright (c) 2013 Peter Grehan <grehan@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 ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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 <sys/param.h>
+#include <sys/queue.h>
+#include <sys/errno.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/disk.h>
+
+#include <assert.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+#include <pthread_np.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include <machine/atomic.h>
+
+#include "bhyverun.h"
+#include "mevent.h"
+#include "block_if.h"
+
+#define BLOCKIF_SIG 0xb109b109
+
+#define BLOCKIF_NUMTHR 8
+#define BLOCKIF_MAXREQ (64 + BLOCKIF_NUMTHR)
+
+enum blockop {
+ BOP_READ,
+ BOP_WRITE,
+ BOP_FLUSH,
+ BOP_DELETE
+};
+
+enum blockstat {
+ BST_FREE,
+ BST_BLOCK,
+ BST_PEND,
+ BST_BUSY,
+ BST_DONE
+};
+
+struct blockif_elem {
+ TAILQ_ENTRY(blockif_elem) be_link;
+ struct blockif_req *be_req;
+ enum blockop be_op;
+ enum blockstat be_status;
+ pthread_t be_tid;
+ off_t be_block;
+};
+
+struct blockif_ctxt {
+ int bc_magic;
+ int bc_fd;
+ int bc_ischr;
+ int bc_isgeom;
+ int bc_candelete;
+ int bc_rdonly;
+ off_t bc_size;
+ int bc_sectsz;
+ int bc_psectsz;
+ int bc_psectoff;
+ int bc_closing;
+ pthread_t bc_btid[BLOCKIF_NUMTHR];
+ pthread_mutex_t bc_mtx;
+ pthread_cond_t bc_cond;
+
+ /* Request elements and free/pending/busy queues */
+ TAILQ_HEAD(, blockif_elem) bc_freeq;
+ TAILQ_HEAD(, blockif_elem) bc_pendq;
+ TAILQ_HEAD(, blockif_elem) bc_busyq;
+ struct blockif_elem bc_reqs[BLOCKIF_MAXREQ];
+};
+
+static pthread_once_t blockif_once = PTHREAD_ONCE_INIT;
+
+struct blockif_sig_elem {
+ pthread_mutex_t bse_mtx;
+ pthread_cond_t bse_cond;
+ int bse_pending;
+ struct blockif_sig_elem *bse_next;
+};
+
+static struct blockif_sig_elem *blockif_bse_head;
+
+static int
+blockif_enqueue(struct blockif_ctxt *bc, struct blockif_req *breq,
+ enum blockop op)
+{
+ struct blockif_elem *be, *tbe;
+ off_t off;
+ int i;
+
+ be = TAILQ_FIRST(&bc->bc_freeq);
+ assert(be != NULL);
+ assert(be->be_status == BST_FREE);
+ TAILQ_REMOVE(&bc->bc_freeq, be, be_link);
+ be->be_req = breq;
+ be->be_op = op;
+ switch (op) {
+ case BOP_READ:
+ case BOP_WRITE:
+ case BOP_DELETE:
+ off = breq->br_offset;
+ for (i = 0; i < breq->br_iovcnt; i++)
+ off += breq->br_iov[i].iov_len;
+ break;
+ default:
+ off = OFF_MAX;
+ }
+ be->be_block = off;
+ TAILQ_FOREACH(tbe, &bc->bc_pendq, be_link) {
+ if (tbe->be_block == breq->br_offset)
+ break;
+ }
+ if (tbe == NULL) {
+ TAILQ_FOREACH(tbe, &bc->bc_busyq, be_link) {
+ if (tbe->be_block == breq->br_offset)
+ break;
+ }
+ }
+ if (tbe == NULL)
+ be->be_status = BST_PEND;
+ else
+ be->be_status = BST_BLOCK;
+ TAILQ_INSERT_TAIL(&bc->bc_pendq, be, be_link);
+ return (be->be_status == BST_PEND);
+}
+
+static int
+blockif_dequeue(struct blockif_ctxt *bc, pthread_t t, struct blockif_elem **bep)
+{
+ struct blockif_elem *be;
+
+ TAILQ_FOREACH(be, &bc->bc_pendq, be_link) {
+ if (be->be_status == BST_PEND)
+ break;
+ assert(be->be_status == BST_BLOCK);
+ }
+ if (be == NULL)
+ return (0);
+ TAILQ_REMOVE(&bc->bc_pendq, be, be_link);
+ be->be_status = BST_BUSY;
+ be->be_tid = t;
+ TAILQ_INSERT_TAIL(&bc->bc_busyq, be, be_link);
+ *bep = be;
+ return (1);
+}
+
+static void
+blockif_complete(struct blockif_ctxt *bc, struct blockif_elem *be)
+{
+ struct blockif_elem *tbe;
+
+ if (be->be_status == BST_DONE || be->be_status == BST_BUSY)
+ TAILQ_REMOVE(&bc->bc_busyq, be, be_link);
+ else
+ TAILQ_REMOVE(&bc->bc_pendq, be, be_link);
+ TAILQ_FOREACH(tbe, &bc->bc_pendq, be_link) {
+ if (tbe->be_req->br_offset == be->be_block)
+ tbe->be_status = BST_PEND;
+ }
+ be->be_tid = 0;
+ be->be_status = BST_FREE;
+ be->be_req = NULL;
+ TAILQ_INSERT_TAIL(&bc->bc_freeq, be, be_link);
+}
+
+static void
+blockif_proc(struct blockif_ctxt *bc, struct blockif_elem *be, uint8_t *buf)
+{
+ struct blockif_req *br;
+ off_t arg[2];
+ ssize_t clen, len, off, boff, voff;
+ int i, err;
+
+ br = be->be_req;
+ if (br->br_iovcnt <= 1)
+ buf = NULL;
+ err = 0;
+ switch (be->be_op) {
+ case BOP_READ:
+ if (buf == NULL) {
+ if ((len = preadv(bc->bc_fd, br->br_iov, br->br_iovcnt,
+ br->br_offset)) < 0)
+ err = errno;
+ else
+ br->br_resid -= len;
+ break;
+ }
+ i = 0;
+ off = voff = 0;
+ while (br->br_resid > 0) {
+ len = MIN(br->br_resid, MAXPHYS);
+ if (pread(bc->bc_fd, buf, len, br->br_offset +
+ off) < 0) {
+ err = errno;
+ break;
+ }
+ boff = 0;
+ do {
+ clen = MIN(len - boff, br->br_iov[i].iov_len -
+ voff);
+ memcpy(br->br_iov[i].iov_base + voff,
+ buf + boff, clen);
+ if (clen < br->br_iov[i].iov_len - voff)
+ voff += clen;
+ else {
+ i++;
+ voff = 0;
+ }
+ boff += clen;
+ } while (boff < len);
+ off += len;
+ br->br_resid -= len;
+ }
+ break;
+ case BOP_WRITE:
+ if (bc->bc_rdonly) {
+ err = EROFS;
+ break;
+ }
+ if (buf == NULL) {
+ if ((len = pwritev(bc->bc_fd, br->br_iov, br->br_iovcnt,
+ br->br_offset)) < 0)
+ err = errno;
+ else
+ br->br_resid -= len;
+ break;
+ }
+ i = 0;
+ off = voff = 0;
+ while (br->br_resid > 0) {
+ len = MIN(br->br_resid, MAXPHYS);
+ boff = 0;
+ do {
+ clen = MIN(len - boff, br->br_iov[i].iov_len -
+ voff);
+ memcpy(buf + boff,
+ br->br_iov[i].iov_base + voff, clen);
+ if (clen < br->br_iov[i].iov_len - voff)
+ voff += clen;
+ else {
+ i++;
+ voff = 0;
+ }
+ boff += clen;
+ } while (boff < len);
+ if (pwrite(bc->bc_fd, buf, len, br->br_offset +
+ off) < 0) {
+ err = errno;
+ break;
+ }
+ off += len;
+ br->br_resid -= len;
+ }
+ break;
+ case BOP_FLUSH:
+ if (bc->bc_ischr) {
+ if (ioctl(bc->bc_fd, DIOCGFLUSH))
+ err = errno;
+ } else if (fsync(bc->bc_fd))
+ err = errno;
+ break;
+ case BOP_DELETE:
+ if (!bc->bc_candelete)
+ err = EOPNOTSUPP;
+ else if (bc->bc_rdonly)
+ err = EROFS;
+ else if (bc->bc_ischr) {
+ arg[0] = br->br_offset;
+ arg[1] = br->br_resid;
+ if (ioctl(bc->bc_fd, DIOCGDELETE, arg))
+ err = errno;
+ else
+ br->br_resid = 0;
+ } else
+ err = EOPNOTSUPP;
+ break;
+ default:
+ err = EINVAL;
+ break;
+ }
+
+ be->be_status = BST_DONE;
+
+ (*br->br_callback)(br, err);
+}
+
+static void *
+blockif_thr(void *arg)
+{
+ struct blockif_ctxt *bc;
+ struct blockif_elem *be;
+ pthread_t t;
+ uint8_t *buf;
+
+ bc = arg;
+ if (bc->bc_isgeom)
+ buf = malloc(MAXPHYS);
+ else
+ buf = NULL;
+ t = pthread_self();
+
+ pthread_mutex_lock(&bc->bc_mtx);
+ for (;;) {
+ while (blockif_dequeue(bc, t, &be)) {
+ pthread_mutex_unlock(&bc->bc_mtx);
+ blockif_proc(bc, be, buf);
+ pthread_mutex_lock(&bc->bc_mtx);
+ blockif_complete(bc, be);
+ }
+ /* Check ctxt status here to see if exit requested */
+ if (bc->bc_closing)
+ break;
+ pthread_cond_wait(&bc->bc_cond, &bc->bc_mtx);
+ }
+ pthread_mutex_unlock(&bc->bc_mtx);
+
+ if (buf)
+ free(buf);
+ pthread_exit(NULL);
+ return (NULL);
+}
+
+static void
+blockif_sigcont_handler(int signal, enum ev_type type, void *arg)
+{
+ struct blockif_sig_elem *bse;
+
+ for (;;) {
+ /*
+ * Process the entire list even if not intended for
+ * this thread.
+ */
+ do {
+ bse = blockif_bse_head;
+ if (bse == NULL)
+ return;
+ } while (!atomic_cmpset_ptr((uintptr_t *)&blockif_bse_head,
+ (uintptr_t)bse,
+ (uintptr_t)bse->bse_next));
+
+ pthread_mutex_lock(&bse->bse_mtx);
+ bse->bse_pending = 0;
+ pthread_cond_signal(&bse->bse_cond);
+ pthread_mutex_unlock(&bse->bse_mtx);
+ }
+}
+
+static void
+blockif_init(void)
+{
+ mevent_add(SIGCONT, EVF_SIGNAL, blockif_sigcont_handler, NULL);
+ (void) signal(SIGCONT, SIG_IGN);
+}
+
+struct blockif_ctxt *
+blockif_open(const char *optstr, const char *ident)
+{
+ char tname[MAXCOMLEN + 1];
+ char name[MAXPATHLEN];
+ char *nopt, *xopts, *cp;
+ struct blockif_ctxt *bc;
+ struct stat sbuf;
+ struct diocgattr_arg arg;
+ off_t size, psectsz, psectoff;
+ int extra, fd, i, sectsz;
+ int nocache, sync, ro, candelete, geom, ssopt, pssopt;
+
+ pthread_once(&blockif_once, blockif_init);
+
+ fd = -1;
+ ssopt = 0;
+ nocache = 0;
+ sync = 0;
+ ro = 0;
+
+ /*
+ * The first element in the optstring is always a pathname.
+ * Optional elements follow
+ */
+ nopt = xopts = strdup(optstr);
+ while (xopts != NULL) {
+ cp = strsep(&xopts, ",");
+ if (cp == nopt) /* file or device pathname */
+ continue;
+ else if (!strcmp(cp, "nocache"))
+ nocache = 1;
+ else if (!strcmp(cp, "sync") || !strcmp(cp, "direct"))
+ sync = 1;
+ else if (!strcmp(cp, "ro"))
+ ro = 1;
+ else if (sscanf(cp, "sectorsize=%d/%d", &ssopt, &pssopt) == 2)
+ ;
+ else if (sscanf(cp, "sectorsize=%d", &ssopt) == 1)
+ pssopt = ssopt;
+ else {
+ fprintf(stderr, "Invalid device option \"%s\"\n", cp);
+ goto err;
+ }
+ }
+
+ extra = 0;
+ if (nocache)
+ extra |= O_DIRECT;
+ if (sync)
+ extra |= O_SYNC;
+
+ fd = open(nopt, (ro ? O_RDONLY : O_RDWR) | extra);
+ if (fd < 0 && !ro) {
+ /* Attempt a r/w fail with a r/o open */
+ fd = open(nopt, O_RDONLY | extra);
+ ro = 1;
+ }
+
+ if (fd < 0) {
+ perror("Could not open backing file");
+ goto err;
+ }
+
+ if (fstat(fd, &sbuf) < 0) {
+ perror("Could not stat backing file");
+ goto err;
+ }
+
+ /*
+ * Deal with raw devices
+ */
+ size = sbuf.st_size;
+ sectsz = DEV_BSIZE;
+ psectsz = psectoff = 0;
+ candelete = geom = 0;
+ if (S_ISCHR(sbuf.st_mode)) {
+ if (ioctl(fd, DIOCGMEDIASIZE, &size) < 0 ||
+ ioctl(fd, DIOCGSECTORSIZE, &sectsz)) {
+ perror("Could not fetch dev blk/sector size");
+ goto err;
+ }
+ assert(size != 0);
+ assert(sectsz != 0);
+ if (ioctl(fd, DIOCGSTRIPESIZE, &psectsz) == 0 && psectsz > 0)
+ ioctl(fd, DIOCGSTRIPEOFFSET, &psectoff);
+ strlcpy(arg.name, "GEOM::candelete", sizeof(arg.name));
+ arg.len = sizeof(arg.value.i);
+ if (ioctl(fd, DIOCGATTR, &arg) == 0)
+ candelete = arg.value.i;
+ if (ioctl(fd, DIOCGPROVIDERNAME, name) == 0)
+ geom = 1;
+ } else
+ psectsz = sbuf.st_blksize;
+
+ if (ssopt != 0) {
+ if (!powerof2(ssopt) || !powerof2(pssopt) || ssopt < 512 ||
+ ssopt > pssopt) {
+ fprintf(stderr, "Invalid sector size %d/%d\n",
+ ssopt, pssopt);
+ goto err;
+ }
+
+ /*
+ * Some backend drivers (e.g. cd0, ada0) require that the I/O
+ * size be a multiple of the device's sector size.
+ *
+ * Validate that the emulated sector size complies with this
+ * requirement.
+ */
+ if (S_ISCHR(sbuf.st_mode)) {
+ if (ssopt < sectsz || (ssopt % sectsz) != 0) {
+ fprintf(stderr, "Sector size %d incompatible "
+ "with underlying device sector size %d\n",
+ ssopt, sectsz);
+ goto err;
+ }
+ }
+
+ sectsz = ssopt;
+ psectsz = pssopt;
+ psectoff = 0;
+ }
+
+ bc = calloc(1, sizeof(struct blockif_ctxt));
+ if (bc == NULL) {
+ perror("calloc");
+ goto err;
+ }
+
+ bc->bc_magic = BLOCKIF_SIG;
+ bc->bc_fd = fd;
+ bc->bc_ischr = S_ISCHR(sbuf.st_mode);
+ bc->bc_isgeom = geom;
+ bc->bc_candelete = candelete;
+ bc->bc_rdonly = ro;
+ bc->bc_size = size;
+ bc->bc_sectsz = sectsz;
+ bc->bc_psectsz = psectsz;
+ bc->bc_psectoff = psectoff;
+ pthread_mutex_init(&bc->bc_mtx, NULL);
+ pthread_cond_init(&bc->bc_cond, NULL);
+ TAILQ_INIT(&bc->bc_freeq);
+ TAILQ_INIT(&bc->bc_pendq);
+ TAILQ_INIT(&bc->bc_busyq);
+ for (i = 0; i < BLOCKIF_MAXREQ; i++) {
+ bc->bc_reqs[i].be_status = BST_FREE;
+ TAILQ_INSERT_HEAD(&bc->bc_freeq, &bc->bc_reqs[i], be_link);
+ }
+
+ for (i = 0; i < BLOCKIF_NUMTHR; i++) {
+ pthread_create(&bc->bc_btid[i], NULL, blockif_thr, bc);
+ snprintf(tname, sizeof(tname), "blk-%s-%d", ident, i);
+ pthread_set_name_np(bc->bc_btid[i], tname);
+ }
+
+ return (bc);
+err:
+ if (fd >= 0)
+ close(fd);
+ return (NULL);
+}
+
+static int
+blockif_request(struct blockif_ctxt *bc, struct blockif_req *breq,
+ enum blockop op)
+{
+ int err;
+
+ err = 0;
+
+ pthread_mutex_lock(&bc->bc_mtx);
+ if (!TAILQ_EMPTY(&bc->bc_freeq)) {
+ /*
+ * Enqueue and inform the block i/o thread
+ * that there is work available
+ */
+ if (blockif_enqueue(bc, breq, op))
+ pthread_cond_signal(&bc->bc_cond);
+ } else {
+ /*
+ * Callers are not allowed to enqueue more than
+ * the specified blockif queue limit. Return an
+ * error to indicate that the queue length has been
+ * exceeded.
+ */
+ err = E2BIG;
+ }
+ pthread_mutex_unlock(&bc->bc_mtx);
+
+ return (err);
+}
+
+int
+blockif_read(struct blockif_ctxt *bc, struct blockif_req *breq)
+{
+
+ assert(bc->bc_magic == BLOCKIF_SIG);
+ return (blockif_request(bc, breq, BOP_READ));
+}
+
+int
+blockif_write(struct blockif_ctxt *bc, struct blockif_req *breq)
+{
+
+ assert(bc->bc_magic == BLOCKIF_SIG);
+ return (blockif_request(bc, breq, BOP_WRITE));
+}
+
+int
+blockif_flush(struct blockif_ctxt *bc, struct blockif_req *breq)
+{
+
+ assert(bc->bc_magic == BLOCKIF_SIG);
+ return (blockif_request(bc, breq, BOP_FLUSH));
+}
+
+int
+blockif_delete(struct blockif_ctxt *bc, struct blockif_req *breq)
+{
+
+ assert(bc->bc_magic == BLOCKIF_SIG);
+ return (blockif_request(bc, breq, BOP_DELETE));
+}
+
+int
+blockif_cancel(struct blockif_ctxt *bc, struct blockif_req *breq)
+{
+ struct blockif_elem *be;
+
+ assert(bc->bc_magic == BLOCKIF_SIG);
+
+ pthread_mutex_lock(&bc->bc_mtx);
+ /*
+ * Check pending requests.
+ */
+ TAILQ_FOREACH(be, &bc->bc_pendq, be_link) {
+ if (be->be_req == breq)
+ break;
+ }
+ if (be != NULL) {
+ /*
+ * Found it.
+ */
+ blockif_complete(bc, be);
+ pthread_mutex_unlock(&bc->bc_mtx);
+
+ return (0);
+ }
+
+ /*
+ * Check in-flight requests.
+ */
+ TAILQ_FOREACH(be, &bc->bc_busyq, be_link) {
+ if (be->be_req == breq)
+ break;
+ }
+ if (be == NULL) {
+ /*
+ * Didn't find it.
+ */
+ pthread_mutex_unlock(&bc->bc_mtx);
+ return (EINVAL);
+ }
+
+ /*
+ * Interrupt the processing thread to force it return
+ * prematurely via it's normal callback path.
+ */
+ while (be->be_status == BST_BUSY) {
+ struct blockif_sig_elem bse, *old_head;
+
+ pthread_mutex_init(&bse.bse_mtx, NULL);
+ pthread_cond_init(&bse.bse_cond, NULL);
+
+ bse.bse_pending = 1;
+
+ do {
+ old_head = blockif_bse_head;
+ bse.bse_next = old_head;
+ } while (!atomic_cmpset_ptr((uintptr_t *)&blockif_bse_head,
+ (uintptr_t)old_head,
+ (uintptr_t)&bse));
+
+ pthread_kill(be->be_tid, SIGCONT);
+
+ pthread_mutex_lock(&bse.bse_mtx);
+ while (bse.bse_pending)
+ pthread_cond_wait(&bse.bse_cond, &bse.bse_mtx);
+ pthread_mutex_unlock(&bse.bse_mtx);
+ }
+
+ pthread_mutex_unlock(&bc->bc_mtx);
+
+ /*
+ * The processing thread has been interrupted. Since it's not
+ * clear if the callback has been invoked yet, return EBUSY.
+ */
+ return (EBUSY);
+}
+
+int
+blockif_close(struct blockif_ctxt *bc)
+{
+ void *jval;
+ int err, i;
+
+ err = 0;
+
+ assert(bc->bc_magic == BLOCKIF_SIG);
+
+ /*
+ * Stop the block i/o thread
+ */
+ pthread_mutex_lock(&bc->bc_mtx);
+ bc->bc_closing = 1;
+ pthread_mutex_unlock(&bc->bc_mtx);
+ pthread_cond_broadcast(&bc->bc_cond);
+ for (i = 0; i < BLOCKIF_NUMTHR; i++)
+ pthread_join(bc->bc_btid[i], &jval);
+
+ /* XXX Cancel queued i/o's ??? */
+
+ /*
+ * Release resources
+ */
+ bc->bc_magic = 0;
+ close(bc->bc_fd);
+ free(bc);
+
+ return (0);
+}
+
+/*
+ * Return virtual C/H/S values for a given block. Use the algorithm
+ * outlined in the VHD specification to calculate values.
+ */
+void
+blockif_chs(struct blockif_ctxt *bc, uint16_t *c, uint8_t *h, uint8_t *s)
+{
+ off_t sectors; /* total sectors of the block dev */
+ off_t hcyl; /* cylinders times heads */
+ uint16_t secpt; /* sectors per track */
+ uint8_t heads;
+
+ assert(bc->bc_magic == BLOCKIF_SIG);
+
+ sectors = bc->bc_size / bc->bc_sectsz;
+
+ /* Clamp the size to the largest possible with CHS */
+ if (sectors > 65535UL*16*255)
+ sectors = 65535UL*16*255;
+
+ if (sectors >= 65536UL*16*63) {
+ secpt = 255;
+ heads = 16;
+ hcyl = sectors / secpt;
+ } else {
+ secpt = 17;
+ hcyl = sectors / secpt;
+ heads = (hcyl + 1023) / 1024;
+
+ if (heads < 4)
+ heads = 4;
+
+ if (hcyl >= (heads * 1024) || heads > 16) {
+ secpt = 31;
+ heads = 16;
+ hcyl = sectors / secpt;
+ }
+ if (hcyl >= (heads * 1024)) {
+ secpt = 63;
+ heads = 16;
+ hcyl = sectors / secpt;
+ }
+ }
+
+ *c = hcyl / heads;
+ *h = heads;
+ *s = secpt;
+}
+
+/*
+ * Accessors
+ */
+off_t
+blockif_size(struct blockif_ctxt *bc)
+{
+
+ assert(bc->bc_magic == BLOCKIF_SIG);
+ return (bc->bc_size);
+}
+
+int
+blockif_sectsz(struct blockif_ctxt *bc)
+{
+
+ assert(bc->bc_magic == BLOCKIF_SIG);
+ return (bc->bc_sectsz);
+}
+
+void
+blockif_psectsz(struct blockif_ctxt *bc, int *size, int *off)
+{
+
+ assert(bc->bc_magic == BLOCKIF_SIG);
+ *size = bc->bc_psectsz;
+ *off = bc->bc_psectoff;
+}
+
+int
+blockif_queuesz(struct blockif_ctxt *bc)
+{
+
+ assert(bc->bc_magic == BLOCKIF_SIG);
+ return (BLOCKIF_MAXREQ - 1);
+}
+
+int
+blockif_is_ro(struct blockif_ctxt *bc)
+{
+
+ assert(bc->bc_magic == BLOCKIF_SIG);
+ return (bc->bc_rdonly);
+}
+
+int
+blockif_candelete(struct blockif_ctxt *bc)
+{
+
+ assert(bc->bc_magic == BLOCKIF_SIG);
+ return (bc->bc_candelete);
+}
diff --git a/usr.sbin/bhyve/block_if.h b/usr.sbin/bhyve/block_if.h
new file mode 100644
index 0000000..8e63407
--- /dev/null
+++ b/usr.sbin/bhyve/block_if.h
@@ -0,0 +1,70 @@
+/*-
+ * Copyright (c) 2013 Peter Grehan <grehan@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 ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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 block API to be used by bhyve block-device emulations. The routines
+ * are thread safe, with no assumptions about the context of the completion
+ * callback - it may occur in the caller's context, or asynchronously in
+ * another thread.
+ */
+
+#ifndef _BLOCK_IF_H_
+#define _BLOCK_IF_H_
+
+#include <sys/uio.h>
+#include <sys/unistd.h>
+
+#define BLOCKIF_IOV_MAX 33 /* not practical to be IOV_MAX */
+
+struct blockif_req {
+ struct iovec br_iov[BLOCKIF_IOV_MAX];
+ int br_iovcnt;
+ off_t br_offset;
+ ssize_t br_resid;
+ void (*br_callback)(struct blockif_req *req, int err);
+ void *br_param;
+};
+
+struct blockif_ctxt;
+struct blockif_ctxt *blockif_open(const char *optstr, const char *ident);
+off_t blockif_size(struct blockif_ctxt *bc);
+void blockif_chs(struct blockif_ctxt *bc, uint16_t *c, uint8_t *h,
+ uint8_t *s);
+int blockif_sectsz(struct blockif_ctxt *bc);
+void blockif_psectsz(struct blockif_ctxt *bc, int *size, int *off);
+int blockif_queuesz(struct blockif_ctxt *bc);
+int blockif_is_ro(struct blockif_ctxt *bc);
+int blockif_candelete(struct blockif_ctxt *bc);
+int blockif_read(struct blockif_ctxt *bc, struct blockif_req *breq);
+int blockif_write(struct blockif_ctxt *bc, struct blockif_req *breq);
+int blockif_flush(struct blockif_ctxt *bc, struct blockif_req *breq);
+int blockif_delete(struct blockif_ctxt *bc, struct blockif_req *breq);
+int blockif_cancel(struct blockif_ctxt *bc, struct blockif_req *breq);
+int blockif_close(struct blockif_ctxt *bc);
+
+#endif /* _BLOCK_IF_H_ */
diff --git a/usr.sbin/bhyve/bootrom.c b/usr.sbin/bhyve/bootrom.c
new file mode 100644
index 0000000..5e4e0e9
--- /dev/null
+++ b/usr.sbin/bhyve/bootrom.c
@@ -0,0 +1,111 @@
+/*-
+ * Copyright (c) 2015 Neel Natu <neel@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 ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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/param.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <machine/vmm.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdbool.h>
+
+#include <vmmapi.h>
+#include "bhyverun.h"
+#include "bootrom.h"
+
+#define MAX_BOOTROM_SIZE (16 * 1024 * 1024) /* 16 MB */
+
+int
+bootrom_init(struct vmctx *ctx, const char *romfile)
+{
+ struct stat sbuf;
+ vm_paddr_t gpa;
+ ssize_t rlen;
+ char *ptr;
+ int fd, i, rv, prot;
+
+ rv = -1;
+ fd = open(romfile, O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "Error opening bootrom \"%s\": %s\n",
+ romfile, strerror(errno));
+ goto done;
+ }
+
+ if (fstat(fd, &sbuf) < 0) {
+ fprintf(stderr, "Could not fstat bootrom file \"%s\": %s\n",
+ romfile, strerror(errno));
+ goto done;
+ }
+
+ /*
+ * Limit bootrom size to 16MB so it doesn't encroach into reserved
+ * MMIO space (e.g. APIC, HPET, MSI).
+ */
+ if (sbuf.st_size > MAX_BOOTROM_SIZE || sbuf.st_size < PAGE_SIZE) {
+ fprintf(stderr, "Invalid bootrom size %ld\n", sbuf.st_size);
+ goto done;
+ }
+
+ if (sbuf.st_size & PAGE_MASK) {
+ fprintf(stderr, "Bootrom size %ld is not a multiple of the "
+ "page size\n", sbuf.st_size);
+ goto done;
+ }
+
+ ptr = vm_create_devmem(ctx, VM_BOOTROM, "bootrom", sbuf.st_size);
+ if (ptr == MAP_FAILED)
+ goto done;
+
+ /* Map the bootrom into the guest address space */
+ prot = PROT_READ | PROT_EXEC;
+ gpa = (1ULL << 32) - sbuf.st_size;
+ if (vm_mmap_memseg(ctx, gpa, VM_BOOTROM, 0, sbuf.st_size, prot) != 0)
+ goto done;
+
+ /* Read 'romfile' into the guest address space */
+ for (i = 0; i < sbuf.st_size / PAGE_SIZE; i++) {
+ rlen = read(fd, ptr + i * PAGE_SIZE, PAGE_SIZE);
+ if (rlen != PAGE_SIZE) {
+ fprintf(stderr, "Incomplete read of page %d of bootrom "
+ "file %s: %ld bytes\n", i, romfile, rlen);
+ goto done;
+ }
+ }
+ rv = 0;
+done:
+ if (fd >= 0)
+ close(fd);
+ return (rv);
+}
diff --git a/usr.sbin/bhyve/bootrom.h b/usr.sbin/bhyve/bootrom.h
new file mode 100644
index 0000000..af150d3
--- /dev/null
+++ b/usr.sbin/bhyve/bootrom.h
@@ -0,0 +1,38 @@
+/*-
+ * Copyright (c) 2015 Neel Natu <neel@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 ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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 _BOOTROM_H_
+#define _BOOTROM_H_
+
+#include <stdbool.h>
+
+struct vmctx;
+
+int bootrom_init(struct vmctx *ctx, const char *romfile);
+
+#endif
diff --git a/usr.sbin/bhyve/consport.c b/usr.sbin/bhyve/consport.c
new file mode 100644
index 0000000..4074e95
--- /dev/null
+++ b/usr.sbin/bhyve/consport.c
@@ -0,0 +1,153 @@
+/*-
+ * Copyright (c) 2011 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <sys/types.h>
+#include <sys/select.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <termios.h>
+#include <unistd.h>
+#include <stdbool.h>
+
+#include "inout.h"
+#include "pci_lpc.h"
+
+#define BVM_CONSOLE_PORT 0x220
+#define BVM_CONS_SIG ('b' << 8 | 'v')
+
+static struct termios tio_orig, tio_new;
+
+static void
+ttyclose(void)
+{
+ tcsetattr(STDIN_FILENO, TCSANOW, &tio_orig);
+}
+
+static void
+ttyopen(void)
+{
+ tcgetattr(STDIN_FILENO, &tio_orig);
+
+ cfmakeraw(&tio_new);
+ tcsetattr(STDIN_FILENO, TCSANOW, &tio_new);
+
+ atexit(ttyclose);
+}
+
+static bool
+tty_char_available(void)
+{
+ fd_set rfds;
+ struct timeval tv;
+
+ FD_ZERO(&rfds);
+ FD_SET(STDIN_FILENO, &rfds);
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ if (select(STDIN_FILENO + 1, &rfds, NULL, NULL, &tv) > 0) {
+ return (true);
+ } else {
+ return (false);
+ }
+}
+
+static int
+ttyread(void)
+{
+ char rb;
+
+ if (tty_char_available()) {
+ read(STDIN_FILENO, &rb, 1);
+ return (rb & 0xff);
+ } else {
+ return (-1);
+ }
+}
+
+static void
+ttywrite(unsigned char wb)
+{
+ (void) write(STDOUT_FILENO, &wb, 1);
+}
+
+static int
+console_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
+ uint32_t *eax, void *arg)
+{
+ static int opened;
+
+ if (bytes == 2 && in) {
+ *eax = BVM_CONS_SIG;
+ return (0);
+ }
+
+ /*
+ * Guests might probe this port to look for old ISA devices
+ * using single-byte reads. Return 0xff for those.
+ */
+ if (bytes == 1 && in) {
+ *eax = 0xff;
+ return (0);
+ }
+
+ if (bytes != 4)
+ return (-1);
+
+ if (!opened) {
+ ttyopen();
+ opened = 1;
+ }
+
+ if (in)
+ *eax = ttyread();
+ else
+ ttywrite(*eax);
+
+ return (0);
+}
+
+SYSRES_IO(BVM_CONSOLE_PORT, 4);
+
+static struct inout_port consport = {
+ "bvmcons",
+ BVM_CONSOLE_PORT,
+ 1,
+ IOPORT_F_INOUT,
+ console_handler
+};
+
+void
+init_bvmcons(void)
+{
+
+ register_inout(&consport);
+}
diff --git a/usr.sbin/bhyve/dbgport.c b/usr.sbin/bhyve/dbgport.c
new file mode 100644
index 0000000..5be0ceb
--- /dev/null
+++ b/usr.sbin/bhyve/dbgport.c
@@ -0,0 +1,151 @@
+/*-
+ * Copyright (c) 2011 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/uio.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "inout.h"
+#include "dbgport.h"
+#include "pci_lpc.h"
+
+#define BVM_DBG_PORT 0x224
+#define BVM_DBG_SIG ('B' << 8 | 'V')
+
+static int listen_fd, conn_fd;
+
+static struct sockaddr_in sin;
+
+static int
+dbg_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
+ uint32_t *eax, void *arg)
+{
+ char ch;
+ int nwritten, nread, printonce;
+
+ if (bytes == 2 && in) {
+ *eax = BVM_DBG_SIG;
+ return (0);
+ }
+
+ if (bytes != 4)
+ return (-1);
+
+again:
+ printonce = 0;
+ while (conn_fd < 0) {
+ if (!printonce) {
+ printf("Waiting for connection from gdb\r\n");
+ printonce = 1;
+ }
+ conn_fd = accept(listen_fd, NULL, NULL);
+ if (conn_fd >= 0)
+ fcntl(conn_fd, F_SETFL, O_NONBLOCK);
+ else if (errno != EINTR)
+ perror("accept");
+ }
+
+ if (in) {
+ nread = read(conn_fd, &ch, 1);
+ if (nread == -1 && errno == EAGAIN)
+ *eax = -1;
+ else if (nread == 1)
+ *eax = ch;
+ else {
+ close(conn_fd);
+ conn_fd = -1;
+ goto again;
+ }
+ } else {
+ ch = *eax;
+ nwritten = write(conn_fd, &ch, 1);
+ if (nwritten != 1) {
+ close(conn_fd);
+ conn_fd = -1;
+ goto again;
+ }
+ }
+ return (0);
+}
+
+static struct inout_port dbgport = {
+ "bvmdbg",
+ BVM_DBG_PORT,
+ 1,
+ IOPORT_F_INOUT,
+ dbg_handler
+};
+
+SYSRES_IO(BVM_DBG_PORT, 4);
+
+void
+init_dbgport(int sport)
+{
+ int reuse;
+
+ conn_fd = -1;
+
+ if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+ perror("socket");
+ exit(1);
+ }
+
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_ANY);
+ sin.sin_port = htons(sport);
+
+ reuse = 1;
+ if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse,
+ sizeof(reuse)) < 0) {
+ perror("setsockopt");
+ exit(1);
+ }
+
+ if (bind(listen_fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
+ perror("bind");
+ exit(1);
+ }
+
+ if (listen(listen_fd, 1) < 0) {
+ perror("listen");
+ exit(1);
+ }
+
+ register_inout(&dbgport);
+}
diff --git a/usr.sbin/bhyve/dbgport.h b/usr.sbin/bhyve/dbgport.h
new file mode 100644
index 0000000..2ddcbf8
--- /dev/null
+++ b/usr.sbin/bhyve/dbgport.h
@@ -0,0 +1,34 @@
+/*-
+ * Copyright (c) 2011 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 _DBGPORT_H_
+#define _DBGPORT_H_
+
+void init_dbgport(int port);
+
+#endif
diff --git a/usr.sbin/bhyve/fwctl.c b/usr.sbin/bhyve/fwctl.c
new file mode 100644
index 0000000..4b6164b
--- /dev/null
+++ b/usr.sbin/bhyve/fwctl.c
@@ -0,0 +1,549 @@
+/*-
+ * Copyright (c) 2015 Peter Grehan <grehan@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 ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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$
+ */
+
+/*
+ * Guest firmware interface. Uses i/o ports x510/x511 as Qemu does,
+ * but with a request/response messaging protocol.
+ */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/errno.h>
+#include <sys/uio.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "bhyverun.h"
+#include "inout.h"
+#include "fwctl.h"
+
+/*
+ * Messaging protocol base operations
+ */
+#define OP_NULL 1
+#define OP_ECHO 2
+#define OP_GET 3
+#define OP_GET_LEN 4
+#define OP_SET 5
+#define OP_MAX OP_SET
+
+/* I/O ports */
+#define FWCTL_OUT 0x510
+#define FWCTL_IN 0x511
+
+/*
+ * Back-end state-machine
+ */
+enum state {
+ DORMANT,
+ IDENT_WAIT,
+ IDENT_SEND,
+ REQ,
+ RESP
+} be_state = DORMANT;
+
+static uint8_t sig[] = { 'B', 'H', 'Y', 'V' };
+static u_int ident_idx;
+
+struct op_info {
+ int op;
+ int (*op_start)(int len);
+ void (*op_data)(uint32_t data, int len);
+ int (*op_result)(struct iovec **data);
+ void (*op_done)(struct iovec *data);
+};
+static struct op_info *ops[OP_MAX+1];
+
+/* Return 0-padded uint32_t */
+static uint32_t
+fwctl_send_rest(uint32_t *data, size_t len)
+{
+ union {
+ uint8_t c[4];
+ uint32_t w;
+ } u;
+ uint8_t *cdata;
+ int i;
+
+ cdata = (uint8_t *) data;
+ u.w = 0;
+
+ for (i = 0, u.w = 0; i < len; i++)
+ u.c[i] = *cdata++;
+
+ return (u.w);
+}
+
+/*
+ * error op dummy proto - drop all data sent and return an error
+*/
+static int errop_code;
+
+static void
+errop_set(int err)
+{
+
+ errop_code = err;
+}
+
+static int
+errop_start(int len)
+{
+ errop_code = ENOENT;
+
+ /* accept any length */
+ return (errop_code);
+}
+
+static void
+errop_data(uint32_t data, int len)
+{
+
+ /* ignore */
+}
+
+static int
+errop_result(struct iovec **data)
+{
+
+ /* no data to send back; always successful */
+ *data = NULL;
+ return (errop_code);
+}
+
+static void
+errop_done(struct iovec *data)
+{
+
+ /* assert data is NULL */
+}
+
+static struct op_info errop_info = {
+ .op_start = errop_start,
+ .op_data = errop_data,
+ .op_result = errop_result,
+ .op_done = errop_done
+};
+
+/* OID search */
+SET_DECLARE(ctl_set, struct ctl);
+
+CTL_NODE("hw.ncpu", &guest_ncpus, sizeof(guest_ncpus));
+
+static struct ctl *
+ctl_locate(const char *str, int maxlen)
+{
+ struct ctl *cp, **cpp;
+
+ SET_FOREACH(cpp, ctl_set) {
+ cp = *cpp;
+ if (!strncmp(str, cp->c_oid, maxlen))
+ return (cp);
+ }
+
+ return (NULL);
+}
+
+/* uefi-sysctl get-len */
+#define FGET_STRSZ 80
+static struct iovec fget_biov[2];
+static char fget_str[FGET_STRSZ];
+static struct {
+ size_t f_sz;
+ uint32_t f_data[1024];
+} fget_buf;
+static int fget_cnt;
+static size_t fget_size;
+
+static int
+fget_start(int len)
+{
+
+ if (len > FGET_STRSZ)
+ return(E2BIG);
+
+ fget_cnt = 0;
+
+ return (0);
+}
+
+static void
+fget_data(uint32_t data, int len)
+{
+
+ *((uint32_t *) &fget_str[fget_cnt]) = data;
+ fget_cnt += sizeof(uint32_t);
+}
+
+static int
+fget_result(struct iovec **data, int val)
+{
+ struct ctl *cp;
+ int err;
+
+ err = 0;
+
+ /* Locate the OID */
+ cp = ctl_locate(fget_str, fget_cnt);
+ if (cp == NULL) {
+ *data = NULL;
+ err = ENOENT;
+ } else {
+ if (val) {
+ /* For now, copy the len/data into a buffer */
+ memset(&fget_buf, 0, sizeof(fget_buf));
+ fget_buf.f_sz = cp->c_len;
+ memcpy(fget_buf.f_data, cp->c_data, cp->c_len);
+ fget_biov[0].iov_base = (char *)&fget_buf;
+ fget_biov[0].iov_len = sizeof(fget_buf.f_sz) +
+ cp->c_len;
+ } else {
+ fget_size = cp->c_len;
+ fget_biov[0].iov_base = (char *)&fget_size;
+ fget_biov[0].iov_len = sizeof(fget_size);
+ }
+
+ fget_biov[1].iov_base = NULL;
+ fget_biov[1].iov_len = 0;
+ *data = fget_biov;
+ }
+
+ return (err);
+}
+
+static void
+fget_done(struct iovec *data)
+{
+
+ /* nothing needs to be freed */
+}
+
+static int
+fget_len_result(struct iovec **data)
+{
+ return (fget_result(data, 0));
+}
+
+static int
+fget_val_result(struct iovec **data)
+{
+ return (fget_result(data, 1));
+}
+
+static struct op_info fgetlen_info = {
+ .op_start = fget_start,
+ .op_data = fget_data,
+ .op_result = fget_len_result,
+ .op_done = fget_done
+};
+
+static struct op_info fgetval_info = {
+ .op_start = fget_start,
+ .op_data = fget_data,
+ .op_result = fget_val_result,
+ .op_done = fget_done
+};
+
+static struct req_info {
+ int req_error;
+ u_int req_count;
+ uint32_t req_size;
+ uint32_t req_type;
+ uint32_t req_txid;
+ struct op_info *req_op;
+ int resp_error;
+ int resp_count;
+ int resp_size;
+ int resp_off;
+ struct iovec *resp_biov;
+} rinfo;
+
+static void
+fwctl_response_done(void)
+{
+
+ (*rinfo.req_op->op_done)(rinfo.resp_biov);
+
+ /* reinit the req data struct */
+ memset(&rinfo, 0, sizeof(rinfo));
+}
+
+static void
+fwctl_request_done(void)
+{
+
+ rinfo.resp_error = (*rinfo.req_op->op_result)(&rinfo.resp_biov);
+
+ /* XXX only a single vector supported at the moment */
+ rinfo.resp_off = 0;
+ if (rinfo.resp_biov == NULL) {
+ rinfo.resp_size = 0;
+ } else {
+ rinfo.resp_size = rinfo.resp_biov[0].iov_len;
+ }
+}
+
+static int
+fwctl_request_start(void)
+{
+ int err;
+
+ /* Data size doesn't include header */
+ rinfo.req_size -= 12;
+
+ rinfo.req_op = &errop_info;
+ if (rinfo.req_type <= OP_MAX && ops[rinfo.req_type] != NULL)
+ rinfo.req_op = ops[rinfo.req_type];
+
+ err = (*rinfo.req_op->op_start)(rinfo.req_size);
+
+ if (err) {
+ errop_set(err);
+ rinfo.req_op = &errop_info;
+ }
+
+ /* Catch case of zero-length message here */
+ if (rinfo.req_size == 0) {
+ fwctl_request_done();
+ return (1);
+ }
+
+ return (0);
+}
+
+static int
+fwctl_request_data(uint32_t value)
+{
+ int remlen;
+
+ /* Make sure remaining size is >= 0 */
+ rinfo.req_size -= sizeof(uint32_t);
+ remlen = (rinfo.req_size > 0) ? rinfo.req_size: 0;
+
+ (*rinfo.req_op->op_data)(value, remlen);
+
+ if (rinfo.req_size < sizeof(uint32_t)) {
+ fwctl_request_done();
+ return (1);
+ }
+
+ return (0);
+}
+
+static int
+fwctl_request(uint32_t value)
+{
+
+ int ret;
+
+ ret = 0;
+
+ switch (rinfo.req_count) {
+ case 0:
+ /* Verify size */
+ if (value < 12) {
+ printf("msg size error");
+ exit(1);
+ }
+ rinfo.req_size = value;
+ rinfo.req_count = 1;
+ break;
+ case 1:
+ rinfo.req_type = value;
+ rinfo.req_count++;
+ break;
+ case 2:
+ rinfo.req_txid = value;
+ rinfo.req_count++;
+ ret = fwctl_request_start();
+ break;
+ default:
+ ret = fwctl_request_data(value);
+ break;
+ }
+
+ return (ret);
+}
+
+static int
+fwctl_response(uint32_t *retval)
+{
+ uint32_t *dp;
+ int remlen;
+
+ switch(rinfo.resp_count) {
+ case 0:
+ /* 4 x u32 header len + data */
+ *retval = 4*sizeof(uint32_t) +
+ roundup(rinfo.resp_size, sizeof(uint32_t));
+ rinfo.resp_count++;
+ break;
+ case 1:
+ *retval = rinfo.req_type;
+ rinfo.resp_count++;
+ break;
+ case 2:
+ *retval = rinfo.req_txid;
+ rinfo.resp_count++;
+ break;
+ case 3:
+ *retval = rinfo.resp_error;
+ rinfo.resp_count++;
+ break;
+ default:
+ remlen = rinfo.resp_size - rinfo.resp_off;
+ dp = (uint32_t *)
+ ((uint8_t *)rinfo.resp_biov->iov_base + rinfo.resp_off);
+ if (remlen >= sizeof(uint32_t)) {
+ *retval = *dp;
+ } else if (remlen > 0) {
+ *retval = fwctl_send_rest(dp, remlen);
+ }
+ rinfo.resp_off += sizeof(uint32_t);
+ break;
+ }
+
+ if (rinfo.resp_count > 3 &&
+ rinfo.resp_size - rinfo.resp_off <= 0) {
+ fwctl_response_done();
+ return (1);
+ }
+
+ return (0);
+}
+
+
+/*
+ * i/o port handling.
+ */
+static uint8_t
+fwctl_inb(void)
+{
+ uint8_t retval;
+
+ retval = 0xff;
+
+ switch (be_state) {
+ case IDENT_SEND:
+ retval = sig[ident_idx++];
+ if (ident_idx >= sizeof(sig))
+ be_state = REQ;
+ break;
+ default:
+ break;
+ }
+
+ return (retval);
+}
+
+static void
+fwctl_outw(uint16_t val)
+{
+ switch (be_state) {
+ case IDENT_WAIT:
+ if (val == 0) {
+ be_state = IDENT_SEND;
+ ident_idx = 0;
+ }
+ break;
+ default:
+ /* ignore */
+ break;
+ }
+}
+
+static uint32_t
+fwctl_inl(void)
+{
+ uint32_t retval;
+
+ switch (be_state) {
+ case RESP:
+ if (fwctl_response(&retval))
+ be_state = REQ;
+ break;
+ default:
+ retval = 0xffffffff;
+ break;
+ }
+
+ return (retval);
+}
+
+static void
+fwctl_outl(uint32_t val)
+{
+
+ switch (be_state) {
+ case REQ:
+ if (fwctl_request(val))
+ be_state = RESP;
+ default:
+ break;
+ }
+
+}
+
+static int
+fwctl_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
+ uint32_t *eax, void *arg)
+{
+
+ if (in) {
+ if (bytes == 1)
+ *eax = fwctl_inb();
+ else if (bytes == 4)
+ *eax = fwctl_inl();
+ else
+ *eax = 0xffff;
+ } else {
+ if (bytes == 2)
+ fwctl_outw(*eax);
+ else if (bytes == 4)
+ fwctl_outl(*eax);
+ }
+
+ return (0);
+}
+INOUT_PORT(fwctl_wreg, FWCTL_OUT, IOPORT_F_INOUT, fwctl_handler);
+INOUT_PORT(fwctl_rreg, FWCTL_IN, IOPORT_F_IN, fwctl_handler);
+
+void
+fwctl_init(void)
+{
+
+ ops[OP_GET_LEN] = &fgetlen_info;
+ ops[OP_GET] = &fgetval_info;
+
+ be_state = IDENT_WAIT;
+}
diff --git a/usr.sbin/bhyve/fwctl.h b/usr.sbin/bhyve/fwctl.h
new file mode 100644
index 0000000..f5f8d13
--- /dev/null
+++ b/usr.sbin/bhyve/fwctl.h
@@ -0,0 +1,54 @@
+/*-
+ * Copyright (c) 2015 Peter Grehan <grehan@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 ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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 _FWCTL_H_
+#define _FWCTL_H_
+
+#include <sys/linker_set.h>
+
+/*
+ * Linker set api for export of information to guest firmware via
+ * a sysctl-like OID interface
+ */
+struct ctl {
+ const char *c_oid;
+ const void *c_data;
+ const int c_len;
+};
+
+#define CTL_NODE(oid, data, len) \
+ static struct ctl __CONCAT(__ctl, __LINE__) = { \
+ oid, \
+ (data), \
+ (len), \
+ }; \
+ DATA_SET(ctl_set, __CONCAT(__ctl, __LINE__))
+
+void fwctl_init(void);
+
+#endif /* _FWCTL_H_ */
diff --git a/usr.sbin/bhyve/inout.c b/usr.sbin/bhyve/inout.c
new file mode 100644
index 0000000..929bb3c
--- /dev/null
+++ b/usr.sbin/bhyve/inout.c
@@ -0,0 +1,297 @@
+/*-
+ * Copyright (c) 2011 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <sys/param.h>
+#include <sys/linker_set.h>
+#include <sys/_iovec.h>
+#include <sys/mman.h>
+
+#include <x86/psl.h>
+#include <x86/segments.h>
+
+#include <machine/vmm.h>
+#include <machine/vmm_instruction_emul.h>
+#include <vmmapi.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include "bhyverun.h"
+#include "inout.h"
+
+SET_DECLARE(inout_port_set, struct inout_port);
+
+#define MAX_IOPORTS (1 << 16)
+
+#define VERIFY_IOPORT(port, size) \
+ assert((port) >= 0 && (size) > 0 && ((port) + (size)) <= MAX_IOPORTS)
+
+static struct {
+ const char *name;
+ int flags;
+ inout_func_t handler;
+ void *arg;
+} inout_handlers[MAX_IOPORTS];
+
+static int
+default_inout(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
+ uint32_t *eax, void *arg)
+{
+ if (in) {
+ switch (bytes) {
+ case 4:
+ *eax = 0xffffffff;
+ break;
+ case 2:
+ *eax = 0xffff;
+ break;
+ case 1:
+ *eax = 0xff;
+ break;
+ }
+ }
+
+ return (0);
+}
+
+static void
+register_default_iohandler(int start, int size)
+{
+ struct inout_port iop;
+
+ VERIFY_IOPORT(start, size);
+
+ bzero(&iop, sizeof(iop));
+ iop.name = "default";
+ iop.port = start;
+ iop.size = size;
+ iop.flags = IOPORT_F_INOUT | IOPORT_F_DEFAULT;
+ iop.handler = default_inout;
+
+ register_inout(&iop);
+}
+
+int
+emulate_inout(struct vmctx *ctx, int vcpu, struct vm_exit *vmexit, int strict)
+{
+ int addrsize, bytes, flags, in, port, prot, rep;
+ uint32_t eax, val;
+ inout_func_t handler;
+ void *arg;
+ int error, fault, retval;
+ enum vm_reg_name idxreg;
+ uint64_t gla, index, iterations, count;
+ struct vm_inout_str *vis;
+ struct iovec iov[2];
+
+ bytes = vmexit->u.inout.bytes;
+ in = vmexit->u.inout.in;
+ port = vmexit->u.inout.port;
+
+ assert(port < MAX_IOPORTS);
+ assert(bytes == 1 || bytes == 2 || bytes == 4);
+
+ handler = inout_handlers[port].handler;
+
+ if (strict && handler == default_inout)
+ return (-1);
+
+ flags = inout_handlers[port].flags;
+ arg = inout_handlers[port].arg;
+
+ if (in) {
+ if (!(flags & IOPORT_F_IN))
+ return (-1);
+ } else {
+ if (!(flags & IOPORT_F_OUT))
+ return (-1);
+ }
+
+ retval = 0;
+ if (vmexit->u.inout.string) {
+ vis = &vmexit->u.inout_str;
+ rep = vis->inout.rep;
+ addrsize = vis->addrsize;
+ prot = in ? PROT_WRITE : PROT_READ;
+ assert(addrsize == 2 || addrsize == 4 || addrsize == 8);
+
+ /* Index register */
+ idxreg = in ? VM_REG_GUEST_RDI : VM_REG_GUEST_RSI;
+ index = vis->index & vie_size2mask(addrsize);
+
+ /* Count register */
+ count = vis->count & vie_size2mask(addrsize);
+
+ /* Limit number of back-to-back in/out emulations to 16 */
+ iterations = MIN(count, 16);
+ while (iterations > 0) {
+ assert(retval == 0);
+ if (vie_calculate_gla(vis->paging.cpu_mode,
+ vis->seg_name, &vis->seg_desc, index, bytes,
+ addrsize, prot, &gla)) {
+ vm_inject_gp(ctx, vcpu);
+ break;
+ }
+
+ error = vm_copy_setup(ctx, vcpu, &vis->paging, gla,
+ bytes, prot, iov, nitems(iov), &fault);
+ if (error) {
+ retval = -1; /* Unrecoverable error */
+ break;
+ } else if (fault) {
+ retval = 0; /* Resume guest to handle fault */
+ break;
+ }
+
+ if (vie_alignment_check(vis->paging.cpl, bytes,
+ vis->cr0, vis->rflags, gla)) {
+ vm_inject_ac(ctx, vcpu, 0);
+ break;
+ }
+
+ val = 0;
+ if (!in)
+ vm_copyin(ctx, vcpu, iov, &val, bytes);
+
+ retval = handler(ctx, vcpu, in, port, bytes, &val, arg);
+ if (retval != 0)
+ break;
+
+ if (in)
+ vm_copyout(ctx, vcpu, &val, iov, bytes);
+
+ /* Update index */
+ if (vis->rflags & PSL_D)
+ index -= bytes;
+ else
+ index += bytes;
+
+ count--;
+ iterations--;
+ }
+
+ /* Update index register */
+ error = vie_update_register(ctx, vcpu, idxreg, index, addrsize);
+ assert(error == 0);
+
+ /*
+ * Update count register only if the instruction had a repeat
+ * prefix.
+ */
+ if (rep) {
+ error = vie_update_register(ctx, vcpu, VM_REG_GUEST_RCX,
+ count, addrsize);
+ assert(error == 0);
+ }
+
+ /* Restart the instruction if more iterations remain */
+ if (retval == 0 && count != 0) {
+ error = vm_restart_instruction(ctx, vcpu);
+ assert(error == 0);
+ }
+ } else {
+ eax = vmexit->u.inout.eax;
+ val = eax & vie_size2mask(bytes);
+ retval = handler(ctx, vcpu, in, port, bytes, &val, arg);
+ if (retval == 0 && in) {
+ eax &= ~vie_size2mask(bytes);
+ eax |= val & vie_size2mask(bytes);
+ error = vm_set_register(ctx, vcpu, VM_REG_GUEST_RAX,
+ eax);
+ assert(error == 0);
+ }
+ }
+ return (retval);
+}
+
+void
+init_inout(void)
+{
+ struct inout_port **iopp, *iop;
+
+ /*
+ * Set up the default handler for all ports
+ */
+ register_default_iohandler(0, MAX_IOPORTS);
+
+ /*
+ * Overwrite with specified handlers
+ */
+ SET_FOREACH(iopp, inout_port_set) {
+ iop = *iopp;
+ assert(iop->port < MAX_IOPORTS);
+ inout_handlers[iop->port].name = iop->name;
+ inout_handlers[iop->port].flags = iop->flags;
+ inout_handlers[iop->port].handler = iop->handler;
+ inout_handlers[iop->port].arg = NULL;
+ }
+}
+
+int
+register_inout(struct inout_port *iop)
+{
+ int i;
+
+ VERIFY_IOPORT(iop->port, iop->size);
+
+ /*
+ * Verify that the new registration is not overwriting an already
+ * allocated i/o range.
+ */
+ if ((iop->flags & IOPORT_F_DEFAULT) == 0) {
+ for (i = iop->port; i < iop->port + iop->size; i++) {
+ if ((inout_handlers[i].flags & IOPORT_F_DEFAULT) == 0)
+ return (-1);
+ }
+ }
+
+ for (i = iop->port; i < iop->port + iop->size; i++) {
+ inout_handlers[i].name = iop->name;
+ inout_handlers[i].flags = iop->flags;
+ inout_handlers[i].handler = iop->handler;
+ inout_handlers[i].arg = iop->arg;
+ }
+
+ return (0);
+}
+
+int
+unregister_inout(struct inout_port *iop)
+{
+
+ VERIFY_IOPORT(iop->port, iop->size);
+ assert(inout_handlers[iop->port].name == iop->name);
+
+ register_default_iohandler(iop->port, iop->size);
+
+ return (0);
+}
diff --git a/usr.sbin/bhyve/inout.h b/usr.sbin/bhyve/inout.h
new file mode 100644
index 0000000..7f39095
--- /dev/null
+++ b/usr.sbin/bhyve/inout.h
@@ -0,0 +1,79 @@
+/*-
+ * Copyright (c) 2011 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 _INOUT_H_
+#define _INOUT_H_
+
+#include <sys/linker_set.h>
+
+struct vmctx;
+struct vm_exit;
+
+/*
+ * inout emulation handlers return 0 on success and -1 on failure.
+ */
+typedef int (*inout_func_t)(struct vmctx *ctx, int vcpu, int in, int port,
+ int bytes, uint32_t *eax, void *arg);
+
+struct inout_port {
+ const char *name;
+ int port;
+ int size;
+ int flags;
+ inout_func_t handler;
+ void *arg;
+};
+#define IOPORT_F_IN 0x1
+#define IOPORT_F_OUT 0x2
+#define IOPORT_F_INOUT (IOPORT_F_IN | IOPORT_F_OUT)
+
+/*
+ * The following flags are used internally and must not be used by
+ * device models.
+ */
+#define IOPORT_F_DEFAULT 0x80000000 /* claimed by default handler */
+
+#define INOUT_PORT(name, port, flags, handler) \
+ static struct inout_port __CONCAT(__inout_port, __LINE__) = { \
+ #name, \
+ (port), \
+ 1, \
+ (flags), \
+ (handler), \
+ 0 \
+ }; \
+ DATA_SET(inout_port_set, __CONCAT(__inout_port, __LINE__))
+
+void init_inout(void);
+int emulate_inout(struct vmctx *, int vcpu, struct vm_exit *vmexit,
+ int strict);
+int register_inout(struct inout_port *iop);
+int unregister_inout(struct inout_port *iop);
+void init_bvmcons(void);
+
+#endif /* _INOUT_H_ */
diff --git a/usr.sbin/bhyve/ioapic.c b/usr.sbin/bhyve/ioapic.c
new file mode 100644
index 0000000..0ad69d9
--- /dev/null
+++ b/usr.sbin/bhyve/ioapic.c
@@ -0,0 +1,74 @@
+/*-
+ * Copyright (c) 2014 Hudson River Trading LLC
+ * Written by: John H. 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+
+#include <machine/vmm.h>
+#include <vmmapi.h>
+
+#include "ioapic.h"
+
+/*
+ * Assign PCI INTx interrupts to I/O APIC pins in a round-robin
+ * fashion. Note that we have no idea what the HPET is using, but the
+ * HPET is also programmable whereas this is intended for hardwired
+ * PCI interrupts.
+ *
+ * This assumes a single I/O APIC where pins >= 16 are permitted for
+ * PCI devices.
+ */
+static int pci_pins;
+
+void
+ioapic_init(struct vmctx *ctx)
+{
+
+ if (vm_ioapic_pincount(ctx, &pci_pins) < 0) {
+ pci_pins = 0;
+ return;
+ }
+
+ /* Ignore the first 16 pins. */
+ if (pci_pins <= 16) {
+ pci_pins = 0;
+ return;
+ }
+ pci_pins -= 16;
+}
+
+int
+ioapic_pci_alloc_irq(void)
+{
+ static int last_pin;
+
+ if (pci_pins == 0)
+ return (-1);
+ return (16 + (last_pin++ % pci_pins));
+}
diff --git a/usr.sbin/bhyve/ioapic.h b/usr.sbin/bhyve/ioapic.h
new file mode 100644
index 0000000..efdd3c6
--- /dev/null
+++ b/usr.sbin/bhyve/ioapic.h
@@ -0,0 +1,39 @@
+/*-
+ * Copyright (c) 2014 Hudson River Trading LLC
+ * Written by: John H. 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 _IOAPIC_H_
+#define _IOAPIC_H_
+
+/*
+ * Allocate a PCI IRQ from the I/O APIC.
+ */
+void ioapic_init(struct vmctx *ctx);
+int ioapic_pci_alloc_irq(void);
+
+#endif
diff --git a/usr.sbin/bhyve/mem.c b/usr.sbin/bhyve/mem.c
new file mode 100644
index 0000000..2a9f430
--- /dev/null
+++ b/usr.sbin/bhyve/mem.c
@@ -0,0 +1,291 @@
+/*-
+ * Copyright (c) 2012 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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$
+ */
+
+/*
+ * Memory ranges are represented with an RB tree. On insertion, the range
+ * is checked for overlaps. On lookup, the key has the same base and limit
+ * so it can be searched within the range.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/tree.h>
+#include <sys/errno.h>
+#include <machine/vmm.h>
+#include <machine/vmm_instruction_emul.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <pthread.h>
+
+#include "mem.h"
+
+struct mmio_rb_range {
+ RB_ENTRY(mmio_rb_range) mr_link; /* RB tree links */
+ struct mem_range mr_param;
+ uint64_t mr_base;
+ uint64_t mr_end;
+};
+
+struct mmio_rb_tree;
+RB_PROTOTYPE(mmio_rb_tree, mmio_rb_range, mr_link, mmio_rb_range_compare);
+
+RB_HEAD(mmio_rb_tree, mmio_rb_range) mmio_rb_root, mmio_rb_fallback;
+
+/*
+ * Per-vCPU cache. Since most accesses from a vCPU will be to
+ * consecutive addresses in a range, it makes sense to cache the
+ * result of a lookup.
+ */
+static struct mmio_rb_range *mmio_hint[VM_MAXCPU];
+
+static pthread_rwlock_t mmio_rwlock;
+
+static int
+mmio_rb_range_compare(struct mmio_rb_range *a, struct mmio_rb_range *b)
+{
+ if (a->mr_end < b->mr_base)
+ return (-1);
+ else if (a->mr_base > b->mr_end)
+ return (1);
+ return (0);
+}
+
+static int
+mmio_rb_lookup(struct mmio_rb_tree *rbt, uint64_t addr,
+ struct mmio_rb_range **entry)
+{
+ struct mmio_rb_range find, *res;
+
+ find.mr_base = find.mr_end = addr;
+
+ res = RB_FIND(mmio_rb_tree, rbt, &find);
+
+ if (res != NULL) {
+ *entry = res;
+ return (0);
+ }
+
+ return (ENOENT);
+}
+
+static int
+mmio_rb_add(struct mmio_rb_tree *rbt, struct mmio_rb_range *new)
+{
+ struct mmio_rb_range *overlap;
+
+ overlap = RB_INSERT(mmio_rb_tree, rbt, new);
+
+ if (overlap != NULL) {
+#ifdef RB_DEBUG
+ printf("overlap detected: new %lx:%lx, tree %lx:%lx\n",
+ new->mr_base, new->mr_end,
+ overlap->mr_base, overlap->mr_end);
+#endif
+
+ return (EEXIST);
+ }
+
+ return (0);
+}
+
+#if 0
+static void
+mmio_rb_dump(struct mmio_rb_tree *rbt)
+{
+ struct mmio_rb_range *np;
+
+ pthread_rwlock_rdlock(&mmio_rwlock);
+ RB_FOREACH(np, mmio_rb_tree, rbt) {
+ printf(" %lx:%lx, %s\n", np->mr_base, np->mr_end,
+ np->mr_param.name);
+ }
+ pthread_rwlock_unlock(&mmio_rwlock);
+}
+#endif
+
+RB_GENERATE(mmio_rb_tree, mmio_rb_range, mr_link, mmio_rb_range_compare);
+
+static int
+mem_read(void *ctx, int vcpu, uint64_t gpa, uint64_t *rval, int size, void *arg)
+{
+ int error;
+ struct mem_range *mr = arg;
+
+ error = (*mr->handler)(ctx, vcpu, MEM_F_READ, gpa, size,
+ rval, mr->arg1, mr->arg2);
+ return (error);
+}
+
+static int
+mem_write(void *ctx, int vcpu, uint64_t gpa, uint64_t wval, int size, void *arg)
+{
+ int error;
+ struct mem_range *mr = arg;
+
+ error = (*mr->handler)(ctx, vcpu, MEM_F_WRITE, gpa, size,
+ &wval, mr->arg1, mr->arg2);
+ return (error);
+}
+
+int
+emulate_mem(struct vmctx *ctx, int vcpu, uint64_t paddr, struct vie *vie,
+ struct vm_guest_paging *paging)
+
+{
+ struct mmio_rb_range *entry;
+ int err, immutable;
+
+ pthread_rwlock_rdlock(&mmio_rwlock);
+ /*
+ * First check the per-vCPU cache
+ */
+ if (mmio_hint[vcpu] &&
+ paddr >= mmio_hint[vcpu]->mr_base &&
+ paddr <= mmio_hint[vcpu]->mr_end) {
+ entry = mmio_hint[vcpu];
+ } else
+ entry = NULL;
+
+ if (entry == NULL) {
+ if (mmio_rb_lookup(&mmio_rb_root, paddr, &entry) == 0) {
+ /* Update the per-vCPU cache */
+ mmio_hint[vcpu] = entry;
+ } else if (mmio_rb_lookup(&mmio_rb_fallback, paddr, &entry)) {
+ pthread_rwlock_unlock(&mmio_rwlock);
+ return (ESRCH);
+ }
+ }
+
+ assert(entry != NULL);
+
+ /*
+ * An 'immutable' memory range is guaranteed to be never removed
+ * so there is no need to hold 'mmio_rwlock' while calling the
+ * handler.
+ *
+ * XXX writes to the PCIR_COMMAND register can cause register_mem()
+ * to be called. If the guest is using PCI extended config space
+ * to modify the PCIR_COMMAND register then register_mem() can
+ * deadlock on 'mmio_rwlock'. However by registering the extended
+ * config space window as 'immutable' the deadlock can be avoided.
+ */
+ immutable = (entry->mr_param.flags & MEM_F_IMMUTABLE);
+ if (immutable)
+ pthread_rwlock_unlock(&mmio_rwlock);
+
+ err = vmm_emulate_instruction(ctx, vcpu, paddr, vie, paging,
+ mem_read, mem_write, &entry->mr_param);
+
+ if (!immutable)
+ pthread_rwlock_unlock(&mmio_rwlock);
+
+ return (err);
+}
+
+static int
+register_mem_int(struct mmio_rb_tree *rbt, struct mem_range *memp)
+{
+ struct mmio_rb_range *entry, *mrp;
+ int err;
+
+ err = 0;
+
+ mrp = malloc(sizeof(struct mmio_rb_range));
+
+ if (mrp != NULL) {
+ mrp->mr_param = *memp;
+ mrp->mr_base = memp->base;
+ mrp->mr_end = memp->base + memp->size - 1;
+ pthread_rwlock_wrlock(&mmio_rwlock);
+ if (mmio_rb_lookup(rbt, memp->base, &entry) != 0)
+ err = mmio_rb_add(rbt, mrp);
+ pthread_rwlock_unlock(&mmio_rwlock);
+ if (err)
+ free(mrp);
+ } else
+ err = ENOMEM;
+
+ return (err);
+}
+
+int
+register_mem(struct mem_range *memp)
+{
+
+ return (register_mem_int(&mmio_rb_root, memp));
+}
+
+int
+register_mem_fallback(struct mem_range *memp)
+{
+
+ return (register_mem_int(&mmio_rb_fallback, memp));
+}
+
+int
+unregister_mem(struct mem_range *memp)
+{
+ struct mem_range *mr;
+ struct mmio_rb_range *entry = NULL;
+ int err, i;
+
+ pthread_rwlock_wrlock(&mmio_rwlock);
+ err = mmio_rb_lookup(&mmio_rb_root, memp->base, &entry);
+ if (err == 0) {
+ mr = &entry->mr_param;
+ assert(mr->name == memp->name);
+ assert(mr->base == memp->base && mr->size == memp->size);
+ assert((mr->flags & MEM_F_IMMUTABLE) == 0);
+ RB_REMOVE(mmio_rb_tree, &mmio_rb_root, entry);
+
+ /* flush Per-vCPU cache */
+ for (i=0; i < VM_MAXCPU; i++) {
+ if (mmio_hint[i] == entry)
+ mmio_hint[i] = NULL;
+ }
+ }
+ pthread_rwlock_unlock(&mmio_rwlock);
+
+ if (entry)
+ free(entry);
+
+ return (err);
+}
+
+void
+init_mem(void)
+{
+
+ RB_INIT(&mmio_rb_root);
+ RB_INIT(&mmio_rb_fallback);
+ pthread_rwlock_init(&mmio_rwlock, NULL);
+}
diff --git a/usr.sbin/bhyve/mem.h b/usr.sbin/bhyve/mem.h
new file mode 100644
index 0000000..f671eae
--- /dev/null
+++ b/usr.sbin/bhyve/mem.h
@@ -0,0 +1,61 @@
+/*-
+ * Copyright (c) 2012 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 _MEM_H_
+#define _MEM_H_
+
+#include <sys/linker_set.h>
+
+struct vmctx;
+
+typedef int (*mem_func_t)(struct vmctx *ctx, int vcpu, int dir, uint64_t addr,
+ int size, uint64_t *val, void *arg1, long arg2);
+
+struct mem_range {
+ const char *name;
+ int flags;
+ mem_func_t handler;
+ void *arg1;
+ long arg2;
+ uint64_t base;
+ uint64_t size;
+};
+#define MEM_F_READ 0x1
+#define MEM_F_WRITE 0x2
+#define MEM_F_RW 0x3
+#define MEM_F_IMMUTABLE 0x4 /* mem_range cannot be unregistered */
+
+void init_mem(void);
+int emulate_mem(struct vmctx *, int vcpu, uint64_t paddr, struct vie *vie,
+ struct vm_guest_paging *paging);
+
+int register_mem(struct mem_range *memp);
+int register_mem_fallback(struct mem_range *memp);
+int unregister_mem(struct mem_range *memp);
+
+#endif /* _MEM_H_ */
diff --git a/usr.sbin/bhyve/mevent.c b/usr.sbin/bhyve/mevent.c
new file mode 100644
index 0000000..07d3baf
--- /dev/null
+++ b/usr.sbin/bhyve/mevent.c
@@ -0,0 +1,456 @@
+/*-
+ * Copyright (c) 2011 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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$
+ */
+
+/*
+ * Micro event library for FreeBSD, designed for a single i/o thread
+ * using kqueue, and having events be persistent by default.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/time.h>
+
+#include <pthread.h>
+#include <pthread_np.h>
+
+#include "mevent.h"
+
+#define MEVENT_MAX 64
+
+#define MEV_ADD 1
+#define MEV_ENABLE 2
+#define MEV_DISABLE 3
+#define MEV_DEL_PENDING 4
+
+extern char *vmname;
+
+static pthread_t mevent_tid;
+static int mevent_timid = 43;
+static int mevent_pipefd[2];
+static pthread_mutex_t mevent_lmutex = PTHREAD_MUTEX_INITIALIZER;
+
+struct mevent {
+ void (*me_func)(int, enum ev_type, void *);
+#define me_msecs me_fd
+ int me_fd;
+ int me_timid;
+ enum ev_type me_type;
+ void *me_param;
+ int me_cq;
+ int me_state;
+ int me_closefd;
+ LIST_ENTRY(mevent) me_list;
+};
+
+static LIST_HEAD(listhead, mevent) global_head, change_head;
+
+static void
+mevent_qlock(void)
+{
+ pthread_mutex_lock(&mevent_lmutex);
+}
+
+static void
+mevent_qunlock(void)
+{
+ pthread_mutex_unlock(&mevent_lmutex);
+}
+
+static void
+mevent_pipe_read(int fd, enum ev_type type, void *param)
+{
+ char buf[MEVENT_MAX];
+ int status;
+
+ /*
+ * Drain the pipe read side. The fd is non-blocking so this is
+ * safe to do.
+ */
+ do {
+ status = read(fd, buf, sizeof(buf));
+ } while (status == MEVENT_MAX);
+}
+
+static void
+mevent_notify(void)
+{
+ char c;
+
+ /*
+ * If calling from outside the i/o thread, write a byte on the
+ * pipe to force the i/o thread to exit the blocking kevent call.
+ */
+ if (mevent_pipefd[1] != 0 && pthread_self() != mevent_tid) {
+ write(mevent_pipefd[1], &c, 1);
+ }
+}
+
+static int
+mevent_kq_filter(struct mevent *mevp)
+{
+ int retval;
+
+ retval = 0;
+
+ if (mevp->me_type == EVF_READ)
+ retval = EVFILT_READ;
+
+ if (mevp->me_type == EVF_WRITE)
+ retval = EVFILT_WRITE;
+
+ if (mevp->me_type == EVF_TIMER)
+ retval = EVFILT_TIMER;
+
+ if (mevp->me_type == EVF_SIGNAL)
+ retval = EVFILT_SIGNAL;
+
+ return (retval);
+}
+
+static int
+mevent_kq_flags(struct mevent *mevp)
+{
+ int ret;
+
+ switch (mevp->me_state) {
+ case MEV_ADD:
+ ret = EV_ADD; /* implicitly enabled */
+ break;
+ case MEV_ENABLE:
+ ret = EV_ENABLE;
+ break;
+ case MEV_DISABLE:
+ ret = EV_DISABLE;
+ break;
+ case MEV_DEL_PENDING:
+ ret = EV_DELETE;
+ break;
+ default:
+ assert(0);
+ break;
+ }
+
+ return (ret);
+}
+
+static int
+mevent_kq_fflags(struct mevent *mevp)
+{
+ /* XXX nothing yet, perhaps EV_EOF for reads ? */
+ return (0);
+}
+
+static int
+mevent_build(int mfd, struct kevent *kev)
+{
+ struct mevent *mevp, *tmpp;
+ int i;
+
+ i = 0;
+
+ mevent_qlock();
+
+ LIST_FOREACH_SAFE(mevp, &change_head, me_list, tmpp) {
+ if (mevp->me_closefd) {
+ /*
+ * A close of the file descriptor will remove the
+ * event
+ */
+ close(mevp->me_fd);
+ } else {
+ if (mevp->me_type == EVF_TIMER) {
+ kev[i].ident = mevp->me_timid;
+ kev[i].data = mevp->me_msecs;
+ } else {
+ kev[i].ident = mevp->me_fd;
+ kev[i].data = 0;
+ }
+ kev[i].filter = mevent_kq_filter(mevp);
+ kev[i].flags = mevent_kq_flags(mevp);
+ kev[i].fflags = mevent_kq_fflags(mevp);
+ kev[i].udata = mevp;
+ i++;
+ }
+
+ mevp->me_cq = 0;
+ LIST_REMOVE(mevp, me_list);
+
+ if (mevp->me_state == MEV_DEL_PENDING) {
+ free(mevp);
+ } else {
+ LIST_INSERT_HEAD(&global_head, mevp, me_list);
+ }
+
+ assert(i < MEVENT_MAX);
+ }
+
+ mevent_qunlock();
+
+ return (i);
+}
+
+static void
+mevent_handle(struct kevent *kev, int numev)
+{
+ struct mevent *mevp;
+ int i;
+
+ for (i = 0; i < numev; i++) {
+ mevp = kev[i].udata;
+
+ /* XXX check for EV_ERROR ? */
+
+ (*mevp->me_func)(mevp->me_fd, mevp->me_type, mevp->me_param);
+ }
+}
+
+struct mevent *
+mevent_add(int tfd, enum ev_type type,
+ void (*func)(int, enum ev_type, void *), void *param)
+{
+ struct mevent *lp, *mevp;
+
+ if (tfd < 0 || func == NULL) {
+ return (NULL);
+ }
+
+ mevp = NULL;
+
+ mevent_qlock();
+
+ /*
+ * Verify that the fd/type tuple is not present in any list
+ */
+ LIST_FOREACH(lp, &global_head, me_list) {
+ if (type != EVF_TIMER && lp->me_fd == tfd &&
+ lp->me_type == type) {
+ goto exit;
+ }
+ }
+
+ LIST_FOREACH(lp, &change_head, me_list) {
+ if (type != EVF_TIMER && lp->me_fd == tfd &&
+ lp->me_type == type) {
+ goto exit;
+ }
+ }
+
+ /*
+ * Allocate an entry, populate it, and add it to the change list.
+ */
+ mevp = calloc(1, sizeof(struct mevent));
+ if (mevp == NULL) {
+ goto exit;
+ }
+
+ if (type == EVF_TIMER) {
+ mevp->me_msecs = tfd;
+ mevp->me_timid = mevent_timid++;
+ } else
+ mevp->me_fd = tfd;
+ mevp->me_type = type;
+ mevp->me_func = func;
+ mevp->me_param = param;
+
+ LIST_INSERT_HEAD(&change_head, mevp, me_list);
+ mevp->me_cq = 1;
+ mevp->me_state = MEV_ADD;
+ mevent_notify();
+
+exit:
+ mevent_qunlock();
+
+ return (mevp);
+}
+
+static int
+mevent_update(struct mevent *evp, int newstate)
+{
+ /*
+ * It's not possible to enable/disable a deleted event
+ */
+ if (evp->me_state == MEV_DEL_PENDING)
+ return (EINVAL);
+
+ /*
+ * No update needed if state isn't changing
+ */
+ if (evp->me_state == newstate)
+ return (0);
+
+ mevent_qlock();
+
+ evp->me_state = newstate;
+
+ /*
+ * Place the entry onto the changed list if not already there.
+ */
+ if (evp->me_cq == 0) {
+ evp->me_cq = 1;
+ LIST_REMOVE(evp, me_list);
+ LIST_INSERT_HEAD(&change_head, evp, me_list);
+ mevent_notify();
+ }
+
+ mevent_qunlock();
+
+ return (0);
+}
+
+int
+mevent_enable(struct mevent *evp)
+{
+
+ return (mevent_update(evp, MEV_ENABLE));
+}
+
+int
+mevent_disable(struct mevent *evp)
+{
+
+ return (mevent_update(evp, MEV_DISABLE));
+}
+
+static int
+mevent_delete_event(struct mevent *evp, int closefd)
+{
+ mevent_qlock();
+
+ /*
+ * Place the entry onto the changed list if not already there, and
+ * mark as to be deleted.
+ */
+ if (evp->me_cq == 0) {
+ evp->me_cq = 1;
+ LIST_REMOVE(evp, me_list);
+ LIST_INSERT_HEAD(&change_head, evp, me_list);
+ mevent_notify();
+ }
+ evp->me_state = MEV_DEL_PENDING;
+
+ if (closefd)
+ evp->me_closefd = 1;
+
+ mevent_qunlock();
+
+ return (0);
+}
+
+int
+mevent_delete(struct mevent *evp)
+{
+
+ return (mevent_delete_event(evp, 0));
+}
+
+int
+mevent_delete_close(struct mevent *evp)
+{
+
+ return (mevent_delete_event(evp, 1));
+}
+
+static void
+mevent_set_name(void)
+{
+
+ pthread_set_name_np(mevent_tid, "mevent");
+}
+
+void
+mevent_dispatch(void)
+{
+ struct kevent changelist[MEVENT_MAX];
+ struct kevent eventlist[MEVENT_MAX];
+ struct mevent *pipev;
+ int mfd;
+ int numev;
+ int ret;
+
+ mevent_tid = pthread_self();
+ mevent_set_name();
+
+ mfd = kqueue();
+ assert(mfd > 0);
+
+ /*
+ * Open the pipe that will be used for other threads to force
+ * the blocking kqueue call to exit by writing to it. Set the
+ * descriptor to non-blocking.
+ */
+ ret = pipe(mevent_pipefd);
+ if (ret < 0) {
+ perror("pipe");
+ exit(0);
+ }
+
+ /*
+ * Add internal event handler for the pipe write fd
+ */
+ pipev = mevent_add(mevent_pipefd[0], EVF_READ, mevent_pipe_read, NULL);
+ assert(pipev != NULL);
+
+ for (;;) {
+ /*
+ * Build changelist if required.
+ * XXX the changelist can be put into the blocking call
+ * to eliminate the extra syscall. Currently better for
+ * debug.
+ */
+ numev = mevent_build(mfd, changelist);
+ if (numev) {
+ ret = kevent(mfd, changelist, numev, NULL, 0, NULL);
+ if (ret == -1) {
+ perror("Error return from kevent change");
+ }
+ }
+
+ /*
+ * Block awaiting events
+ */
+ ret = kevent(mfd, NULL, 0, eventlist, MEVENT_MAX, NULL);
+ if (ret == -1 && errno != EINTR) {
+ perror("Error return from kevent monitor");
+ }
+
+ /*
+ * Handle reported events
+ */
+ mevent_handle(eventlist, ret);
+ }
+}
diff --git a/usr.sbin/bhyve/mevent.h b/usr.sbin/bhyve/mevent.h
new file mode 100644
index 0000000..d6a59c6
--- /dev/null
+++ b/usr.sbin/bhyve/mevent.h
@@ -0,0 +1,51 @@
+/*-
+ * Copyright (c) 2011 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 _MEVENT_H_
+#define _MEVENT_H_
+
+enum ev_type {
+ EVF_READ,
+ EVF_WRITE,
+ EVF_TIMER,
+ EVF_SIGNAL
+};
+
+struct mevent;
+
+struct mevent *mevent_add(int fd, enum ev_type type,
+ void (*func)(int, enum ev_type, void *),
+ void *param);
+int mevent_enable(struct mevent *evp);
+int mevent_disable(struct mevent *evp);
+int mevent_delete(struct mevent *evp);
+int mevent_delete_close(struct mevent *evp);
+
+void mevent_dispatch(void);
+
+#endif /* _MEVENT_H_ */
diff --git a/usr.sbin/bhyve/mevent_test.c b/usr.sbin/bhyve/mevent_test.c
new file mode 100644
index 0000000..9c68ff7
--- /dev/null
+++ b/usr.sbin/bhyve/mevent_test.c
@@ -0,0 +1,256 @@
+/*-
+ * Copyright (c) 2011 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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$
+ */
+
+/*
+ * Test program for the micro event library. Set up a simple TCP echo
+ * service.
+ *
+ * cc mevent_test.c mevent.c -lpthread
+ */
+
+#include <sys/types.h>
+#include <sys/stdint.h>
+#include <sys/sysctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <machine/cpufunc.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <unistd.h>
+
+#include "mevent.h"
+
+#define TEST_PORT 4321
+
+static pthread_mutex_t accept_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t accept_condvar = PTHREAD_COND_INITIALIZER;
+
+static struct mevent *tevp;
+
+char *vmname = "test vm";
+
+
+#define MEVENT_ECHO
+
+/* Number of timer events to capture */
+#define TEVSZ 4096
+uint64_t tevbuf[TEVSZ];
+
+static void
+timer_print(void)
+{
+ uint64_t min, max, diff, sum, tsc_freq;
+ size_t len;
+ int j;
+
+ min = UINT64_MAX;
+ max = 0;
+ sum = 0;
+
+ len = sizeof(tsc_freq);
+ sysctlbyname("machdep.tsc_freq", &tsc_freq, &len, NULL, 0);
+
+ for (j = 1; j < TEVSZ; j++) {
+ /* Convert a tsc diff into microseconds */
+ diff = (tevbuf[j] - tevbuf[j-1]) * 1000000 / tsc_freq;
+ sum += diff;
+ if (min > diff)
+ min = diff;
+ if (max < diff)
+ max = diff;
+ }
+
+ printf("timers done: usecs, min %ld, max %ld, mean %ld\n", min, max,
+ sum/(TEVSZ - 1));
+}
+
+static void
+timer_callback(int fd, enum ev_type type, void *param)
+{
+ static int i;
+
+ if (i >= TEVSZ)
+ abort();
+
+ tevbuf[i++] = rdtsc();
+
+ if (i == TEVSZ) {
+ mevent_delete(tevp);
+ timer_print();
+ }
+}
+
+
+#ifdef MEVENT_ECHO
+struct esync {
+ pthread_mutex_t e_mt;
+ pthread_cond_t e_cond;
+};
+
+static void
+echoer_callback(int fd, enum ev_type type, void *param)
+{
+ struct esync *sync = param;
+
+ pthread_mutex_lock(&sync->e_mt);
+ pthread_cond_signal(&sync->e_cond);
+ pthread_mutex_unlock(&sync->e_mt);
+}
+
+static void *
+echoer(void *param)
+{
+ struct esync sync;
+ struct mevent *mev;
+ char buf[128];
+ int fd = (int)(uintptr_t) param;
+ int len;
+
+ pthread_mutex_init(&sync.e_mt, NULL);
+ pthread_cond_init(&sync.e_cond, NULL);
+
+ pthread_mutex_lock(&sync.e_mt);
+
+ mev = mevent_add(fd, EVF_READ, echoer_callback, &sync);
+ if (mev == NULL) {
+ printf("Could not allocate echoer event\n");
+ exit(1);
+ }
+
+ while (!pthread_cond_wait(&sync.e_cond, &sync.e_mt)) {
+ len = read(fd, buf, sizeof(buf));
+ if (len > 0) {
+ write(fd, buf, len);
+ write(0, buf, len);
+ } else {
+ break;
+ }
+ }
+
+ mevent_delete_close(mev);
+
+ pthread_mutex_unlock(&sync.e_mt);
+ pthread_mutex_destroy(&sync.e_mt);
+ pthread_cond_destroy(&sync.e_cond);
+
+ return (NULL);
+}
+
+#else
+
+static void *
+echoer(void *param)
+{
+ char buf[128];
+ int fd = (int)(uintptr_t) param;
+ int len;
+
+ while ((len = read(fd, buf, sizeof(buf))) > 0) {
+ write(1, buf, len);
+ }
+
+ return (NULL);
+}
+#endif /* MEVENT_ECHO */
+
+static void
+acceptor_callback(int fd, enum ev_type type, void *param)
+{
+ pthread_mutex_lock(&accept_mutex);
+ pthread_cond_signal(&accept_condvar);
+ pthread_mutex_unlock(&accept_mutex);
+}
+
+static void *
+acceptor(void *param)
+{
+ struct sockaddr_in sin;
+ pthread_t tid;
+ int news;
+ int s;
+ static int first;
+
+ if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+ perror("socket");
+ exit(1);
+ }
+
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_ANY);
+ sin.sin_port = htons(TEST_PORT);
+
+ if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
+ perror("bind");
+ exit(1);
+ }
+
+ if (listen(s, 1) < 0) {
+ perror("listen");
+ exit(1);
+ }
+
+ (void) mevent_add(s, EVF_READ, acceptor_callback, NULL);
+
+ pthread_mutex_lock(&accept_mutex);
+
+ while (!pthread_cond_wait(&accept_condvar, &accept_mutex)) {
+ news = accept(s, NULL, NULL);
+ if (news < 0) {
+ perror("accept error");
+ } else {
+ static int first = 1;
+
+ if (first) {
+ /*
+ * Start a timer
+ */
+ first = 0;
+ tevp = mevent_add(1, EVF_TIMER, timer_callback,
+ NULL);
+ }
+
+ printf("incoming connection, spawning thread\n");
+ pthread_create(&tid, NULL, echoer,
+ (void *)(uintptr_t)news);
+ }
+ }
+
+ return (NULL);
+}
+
+main()
+{
+ pthread_t tid;
+
+ pthread_create(&tid, NULL, acceptor, NULL);
+
+ mevent_dispatch();
+}
diff --git a/usr.sbin/bhyve/mptbl.c b/usr.sbin/bhyve/mptbl.c
new file mode 100644
index 0000000..904d103
--- /dev/null
+++ b/usr.sbin/bhyve/mptbl.c
@@ -0,0 +1,377 @@
+/*-
+ * Copyright (c) 2012 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <sys/types.h>
+#include <sys/errno.h>
+#include <x86/mptable.h>
+
+#include <stdio.h>
+#include <string.h>
+
+#include "acpi.h"
+#include "bhyverun.h"
+#include "mptbl.h"
+#include "pci_emul.h"
+
+#define MPTABLE_BASE 0xF0000
+
+/* floating pointer length + maximum length of configuration table */
+#define MPTABLE_MAX_LENGTH (65536 + 16)
+
+#define LAPIC_PADDR 0xFEE00000
+#define LAPIC_VERSION 16
+
+#define IOAPIC_PADDR 0xFEC00000
+#define IOAPIC_VERSION 0x11
+
+#define MP_SPECREV 4
+#define MPFP_SIG "_MP_"
+
+/* Configuration header defines */
+#define MPCH_SIG "PCMP"
+#define MPCH_OEMID "BHyVe "
+#define MPCH_OEMID_LEN 8
+#define MPCH_PRODID "Hypervisor "
+#define MPCH_PRODID_LEN 12
+
+/* Processor entry defines */
+#define MPEP_SIG_FAMILY 6 /* XXX bhyve should supply this */
+#define MPEP_SIG_MODEL 26
+#define MPEP_SIG_STEPPING 5
+#define MPEP_SIG \
+ ((MPEP_SIG_FAMILY << 8) | \
+ (MPEP_SIG_MODEL << 4) | \
+ (MPEP_SIG_STEPPING))
+
+#define MPEP_FEATURES (0xBFEBFBFF) /* XXX Intel i7 */
+
+/* Number of local intr entries */
+#define MPEII_NUM_LOCAL_IRQ 2
+
+/* Bus entry defines */
+#define MPE_NUM_BUSES 2
+#define MPE_BUSNAME_LEN 6
+#define MPE_BUSNAME_ISA "ISA "
+#define MPE_BUSNAME_PCI "PCI "
+
+static void *oem_tbl_start;
+static int oem_tbl_size;
+
+static uint8_t
+mpt_compute_checksum(void *base, size_t len)
+{
+ uint8_t *bytes;
+ uint8_t sum;
+
+ for(bytes = base, sum = 0; len > 0; len--) {
+ sum += *bytes++;
+ }
+
+ return (256 - sum);
+}
+
+static void
+mpt_build_mpfp(mpfps_t mpfp, vm_paddr_t gpa)
+{
+
+ memset(mpfp, 0, sizeof(*mpfp));
+ memcpy(mpfp->signature, MPFP_SIG, 4);
+ mpfp->pap = gpa + sizeof(*mpfp);
+ mpfp->length = 1;
+ mpfp->spec_rev = MP_SPECREV;
+ mpfp->checksum = mpt_compute_checksum(mpfp, sizeof(*mpfp));
+}
+
+static void
+mpt_build_mpch(mpcth_t mpch)
+{
+
+ memset(mpch, 0, sizeof(*mpch));
+ memcpy(mpch->signature, MPCH_SIG, 4);
+ mpch->spec_rev = MP_SPECREV;
+ memcpy(mpch->oem_id, MPCH_OEMID, MPCH_OEMID_LEN);
+ memcpy(mpch->product_id, MPCH_PRODID, MPCH_PRODID_LEN);
+ mpch->apic_address = LAPIC_PADDR;
+}
+
+static void
+mpt_build_proc_entries(proc_entry_ptr mpep, int ncpu)
+{
+ int i;
+
+ for (i = 0; i < ncpu; i++) {
+ memset(mpep, 0, sizeof(*mpep));
+ mpep->type = MPCT_ENTRY_PROCESSOR;
+ mpep->apic_id = i; // XXX
+ mpep->apic_version = LAPIC_VERSION;
+ mpep->cpu_flags = PROCENTRY_FLAG_EN;
+ if (i == 0)
+ mpep->cpu_flags |= PROCENTRY_FLAG_BP;
+ mpep->cpu_signature = MPEP_SIG;
+ mpep->feature_flags = MPEP_FEATURES;
+ mpep++;
+ }
+}
+
+static void
+mpt_build_localint_entries(int_entry_ptr mpie)
+{
+
+ /* Hardcode LINT0 as ExtINT on all CPUs. */
+ memset(mpie, 0, sizeof(*mpie));
+ mpie->type = MPCT_ENTRY_LOCAL_INT;
+ mpie->int_type = INTENTRY_TYPE_EXTINT;
+ mpie->int_flags = INTENTRY_FLAGS_POLARITY_CONFORM |
+ INTENTRY_FLAGS_TRIGGER_CONFORM;
+ mpie->dst_apic_id = 0xff;
+ mpie->dst_apic_int = 0;
+ mpie++;
+
+ /* Hardcode LINT1 as NMI on all CPUs. */
+ memset(mpie, 0, sizeof(*mpie));
+ mpie->type = MPCT_ENTRY_LOCAL_INT;
+ mpie->int_type = INTENTRY_TYPE_NMI;
+ mpie->int_flags = INTENTRY_FLAGS_POLARITY_CONFORM |
+ INTENTRY_FLAGS_TRIGGER_CONFORM;
+ mpie->dst_apic_id = 0xff;
+ mpie->dst_apic_int = 1;
+}
+
+static void
+mpt_build_bus_entries(bus_entry_ptr mpeb)
+{
+
+ memset(mpeb, 0, sizeof(*mpeb));
+ mpeb->type = MPCT_ENTRY_BUS;
+ mpeb->bus_id = 0;
+ memcpy(mpeb->bus_type, MPE_BUSNAME_PCI, MPE_BUSNAME_LEN);
+ mpeb++;
+
+ memset(mpeb, 0, sizeof(*mpeb));
+ mpeb->type = MPCT_ENTRY_BUS;
+ mpeb->bus_id = 1;
+ memcpy(mpeb->bus_type, MPE_BUSNAME_ISA, MPE_BUSNAME_LEN);
+}
+
+static void
+mpt_build_ioapic_entries(io_apic_entry_ptr mpei, int id)
+{
+
+ memset(mpei, 0, sizeof(*mpei));
+ mpei->type = MPCT_ENTRY_IOAPIC;
+ mpei->apic_id = id;
+ mpei->apic_version = IOAPIC_VERSION;
+ mpei->apic_flags = IOAPICENTRY_FLAG_EN;
+ mpei->apic_address = IOAPIC_PADDR;
+}
+
+static int
+mpt_count_ioint_entries(void)
+{
+ int bus, count;
+
+ count = 0;
+ for (bus = 0; bus <= PCI_BUSMAX; bus++)
+ count += pci_count_lintr(bus);
+
+ /*
+ * Always include entries for the first 16 pins along with a entry
+ * for each active PCI INTx pin.
+ */
+ return (16 + count);
+}
+
+static void
+mpt_generate_pci_int(int bus, int slot, int pin, int pirq_pin, int ioapic_irq,
+ void *arg)
+{
+ int_entry_ptr *mpiep, mpie;
+
+ mpiep = arg;
+ mpie = *mpiep;
+ memset(mpie, 0, sizeof(*mpie));
+
+ /*
+ * This is always after another I/O interrupt entry, so cheat
+ * and fetch the I/O APIC ID from the prior entry.
+ */
+ mpie->type = MPCT_ENTRY_INT;
+ mpie->int_type = INTENTRY_TYPE_INT;
+ mpie->src_bus_id = bus;
+ mpie->src_bus_irq = slot << 2 | (pin - 1);
+ mpie->dst_apic_id = mpie[-1].dst_apic_id;
+ mpie->dst_apic_int = ioapic_irq;
+
+ *mpiep = mpie + 1;
+}
+
+static void
+mpt_build_ioint_entries(int_entry_ptr mpie, int id)
+{
+ int pin, bus;
+
+ /*
+ * The following config is taken from kernel mptable.c
+ * mptable_parse_default_config_ints(...), for now
+ * just use the default config, tweek later if needed.
+ */
+
+ /* First, generate the first 16 pins. */
+ for (pin = 0; pin < 16; pin++) {
+ memset(mpie, 0, sizeof(*mpie));
+ mpie->type = MPCT_ENTRY_INT;
+ mpie->src_bus_id = 1;
+ mpie->dst_apic_id = id;
+
+ /*
+ * All default configs route IRQs from bus 0 to the first 16
+ * pins of the first I/O APIC with an APIC ID of 2.
+ */
+ mpie->dst_apic_int = pin;
+ switch (pin) {
+ case 0:
+ /* Pin 0 is an ExtINT pin. */
+ mpie->int_type = INTENTRY_TYPE_EXTINT;
+ break;
+ case 2:
+ /* IRQ 0 is routed to pin 2. */
+ mpie->int_type = INTENTRY_TYPE_INT;
+ mpie->src_bus_irq = 0;
+ break;
+ case SCI_INT:
+ /* ACPI SCI is level triggered and active-lo. */
+ mpie->int_flags = INTENTRY_FLAGS_POLARITY_ACTIVELO |
+ INTENTRY_FLAGS_TRIGGER_LEVEL;
+ mpie->int_type = INTENTRY_TYPE_INT;
+ mpie->src_bus_irq = SCI_INT;
+ break;
+ default:
+ /* All other pins are identity mapped. */
+ mpie->int_type = INTENTRY_TYPE_INT;
+ mpie->src_bus_irq = pin;
+ break;
+ }
+ mpie++;
+ }
+
+ /* Next, generate entries for any PCI INTx interrupts. */
+ for (bus = 0; bus <= PCI_BUSMAX; bus++)
+ pci_walk_lintr(bus, mpt_generate_pci_int, &mpie);
+}
+
+void
+mptable_add_oemtbl(void *tbl, int tblsz)
+{
+
+ oem_tbl_start = tbl;
+ oem_tbl_size = tblsz;
+}
+
+int
+mptable_build(struct vmctx *ctx, int ncpu)
+{
+ mpcth_t mpch;
+ bus_entry_ptr mpeb;
+ io_apic_entry_ptr mpei;
+ proc_entry_ptr mpep;
+ mpfps_t mpfp;
+ int_entry_ptr mpie;
+ int ioints, bus;
+ char *curraddr;
+ char *startaddr;
+
+ startaddr = paddr_guest2host(ctx, MPTABLE_BASE, MPTABLE_MAX_LENGTH);
+ if (startaddr == NULL) {
+ fprintf(stderr, "mptable requires mapped mem\n");
+ return (ENOMEM);
+ }
+
+ /*
+ * There is no way to advertise multiple PCI hierarchies via MPtable
+ * so require that there is no PCI hierarchy with a non-zero bus
+ * number.
+ */
+ for (bus = 1; bus <= PCI_BUSMAX; bus++) {
+ if (pci_bus_configured(bus)) {
+ fprintf(stderr, "MPtable is incompatible with "
+ "multiple PCI hierarchies.\r\n");
+ fprintf(stderr, "MPtable generation can be disabled "
+ "by passing the -Y option to bhyve(8).\r\n");
+ return (EINVAL);
+ }
+ }
+
+ curraddr = startaddr;
+ mpfp = (mpfps_t)curraddr;
+ mpt_build_mpfp(mpfp, MPTABLE_BASE);
+ curraddr += sizeof(*mpfp);
+
+ mpch = (mpcth_t)curraddr;
+ mpt_build_mpch(mpch);
+ curraddr += sizeof(*mpch);
+
+ mpep = (proc_entry_ptr)curraddr;
+ mpt_build_proc_entries(mpep, ncpu);
+ curraddr += sizeof(*mpep) * ncpu;
+ mpch->entry_count += ncpu;
+
+ mpeb = (bus_entry_ptr) curraddr;
+ mpt_build_bus_entries(mpeb);
+ curraddr += sizeof(*mpeb) * MPE_NUM_BUSES;
+ mpch->entry_count += MPE_NUM_BUSES;
+
+ mpei = (io_apic_entry_ptr)curraddr;
+ mpt_build_ioapic_entries(mpei, 0);
+ curraddr += sizeof(*mpei);
+ mpch->entry_count++;
+
+ mpie = (int_entry_ptr) curraddr;
+ ioints = mpt_count_ioint_entries();
+ mpt_build_ioint_entries(mpie, 0);
+ curraddr += sizeof(*mpie) * ioints;
+ mpch->entry_count += ioints;
+
+ mpie = (int_entry_ptr)curraddr;
+ mpt_build_localint_entries(mpie);
+ curraddr += sizeof(*mpie) * MPEII_NUM_LOCAL_IRQ;
+ mpch->entry_count += MPEII_NUM_LOCAL_IRQ;
+
+ if (oem_tbl_start) {
+ mpch->oem_table_pointer = curraddr - startaddr + MPTABLE_BASE;
+ mpch->oem_table_size = oem_tbl_size;
+ memcpy(curraddr, oem_tbl_start, oem_tbl_size);
+ }
+
+ mpch->base_table_length = curraddr - (char *)mpch;
+ mpch->checksum = mpt_compute_checksum(mpch, mpch->base_table_length);
+
+ return (0);
+}
diff --git a/usr.sbin/bhyve/mptbl.h b/usr.sbin/bhyve/mptbl.h
new file mode 100644
index 0000000..e9e1c42
--- /dev/null
+++ b/usr.sbin/bhyve/mptbl.h
@@ -0,0 +1,35 @@
+/*-
+ * Copyright (c) 2012 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 _MPTBL_H_
+#define _MPTBL_H_
+
+int mptable_build(struct vmctx *ctx, int ncpu);
+void mptable_add_oemtbl(void *tbl, int tblsz);
+
+#endif /* _MPTBL_H_ */
diff --git a/usr.sbin/bhyve/pci_ahci.c b/usr.sbin/bhyve/pci_ahci.c
new file mode 100644
index 0000000..5c4743c
--- /dev/null
+++ b/usr.sbin/bhyve/pci_ahci.c
@@ -0,0 +1,2354 @@
+/*-
+ * Copyright (c) 2013 Zhixiang Yu <zcore@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 ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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 <sys/param.h>
+#include <sys/linker_set.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <sys/ioctl.h>
+#include <sys/disk.h>
+#include <sys/ata.h>
+#include <sys/endian.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <assert.h>
+#include <pthread.h>
+#include <pthread_np.h>
+#include <inttypes.h>
+#include <md5.h>
+
+#include "bhyverun.h"
+#include "pci_emul.h"
+#include "ahci.h"
+#include "block_if.h"
+
+#define MAX_PORTS 6 /* Intel ICH8 AHCI supports 6 ports */
+
+#define PxSIG_ATA 0x00000101 /* ATA drive */
+#define PxSIG_ATAPI 0xeb140101 /* ATAPI drive */
+
+enum sata_fis_type {
+ FIS_TYPE_REGH2D = 0x27, /* Register FIS - host to device */
+ FIS_TYPE_REGD2H = 0x34, /* Register FIS - device to host */
+ FIS_TYPE_DMAACT = 0x39, /* DMA activate FIS - device to host */
+ FIS_TYPE_DMASETUP = 0x41, /* DMA setup FIS - bidirectional */
+ FIS_TYPE_DATA = 0x46, /* Data FIS - bidirectional */
+ FIS_TYPE_BIST = 0x58, /* BIST activate FIS - bidirectional */
+ FIS_TYPE_PIOSETUP = 0x5F, /* PIO setup FIS - device to host */
+ FIS_TYPE_SETDEVBITS = 0xA1, /* Set dev bits FIS - device to host */
+};
+
+/*
+ * SCSI opcodes
+ */
+#define TEST_UNIT_READY 0x00
+#define REQUEST_SENSE 0x03
+#define INQUIRY 0x12
+#define START_STOP_UNIT 0x1B
+#define PREVENT_ALLOW 0x1E
+#define READ_CAPACITY 0x25
+#define READ_10 0x28
+#define POSITION_TO_ELEMENT 0x2B
+#define READ_TOC 0x43
+#define GET_EVENT_STATUS_NOTIFICATION 0x4A
+#define MODE_SENSE_10 0x5A
+#define REPORT_LUNS 0xA0
+#define READ_12 0xA8
+#define READ_CD 0xBE
+
+/*
+ * SCSI mode page codes
+ */
+#define MODEPAGE_RW_ERROR_RECOVERY 0x01
+#define MODEPAGE_CD_CAPABILITIES 0x2A
+
+/*
+ * ATA commands
+ */
+#define ATA_SF_ENAB_SATA_SF 0x10
+#define ATA_SATA_SF_AN 0x05
+#define ATA_SF_DIS_SATA_SF 0x90
+
+/*
+ * Debug printf
+ */
+#ifdef AHCI_DEBUG
+static FILE *dbg;
+#define DPRINTF(format, arg...) do{fprintf(dbg, format, ##arg);fflush(dbg);}while(0)
+#else
+#define DPRINTF(format, arg...)
+#endif
+#define WPRINTF(format, arg...) printf(format, ##arg)
+
+struct ahci_ioreq {
+ struct blockif_req io_req;
+ struct ahci_port *io_pr;
+ STAILQ_ENTRY(ahci_ioreq) io_flist;
+ TAILQ_ENTRY(ahci_ioreq) io_blist;
+ uint8_t *cfis;
+ uint32_t len;
+ uint32_t done;
+ int slot;
+ int more;
+};
+
+struct ahci_port {
+ struct blockif_ctxt *bctx;
+ struct pci_ahci_softc *pr_sc;
+ uint8_t *cmd_lst;
+ uint8_t *rfis;
+ char ident[20 + 1];
+ int atapi;
+ int reset;
+ int waitforclear;
+ int mult_sectors;
+ uint8_t xfermode;
+ uint8_t err_cfis[20];
+ uint8_t sense_key;
+ uint8_t asc;
+ u_int ccs;
+ uint32_t pending;
+
+ uint32_t clb;
+ uint32_t clbu;
+ uint32_t fb;
+ uint32_t fbu;
+ uint32_t is;
+ uint32_t ie;
+ uint32_t cmd;
+ uint32_t unused0;
+ uint32_t tfd;
+ uint32_t sig;
+ uint32_t ssts;
+ uint32_t sctl;
+ uint32_t serr;
+ uint32_t sact;
+ uint32_t ci;
+ uint32_t sntf;
+ uint32_t fbs;
+
+ /*
+ * i/o request info
+ */
+ struct ahci_ioreq *ioreq;
+ int ioqsz;
+ STAILQ_HEAD(ahci_fhead, ahci_ioreq) iofhd;
+ TAILQ_HEAD(ahci_bhead, ahci_ioreq) iobhd;
+};
+
+struct ahci_cmd_hdr {
+ uint16_t flags;
+ uint16_t prdtl;
+ uint32_t prdbc;
+ uint64_t ctba;
+ uint32_t reserved[4];
+};
+
+struct ahci_prdt_entry {
+ uint64_t dba;
+ uint32_t reserved;
+#define DBCMASK 0x3fffff
+ uint32_t dbc;
+};
+
+struct pci_ahci_softc {
+ struct pci_devinst *asc_pi;
+ pthread_mutex_t mtx;
+ int ports;
+ uint32_t cap;
+ uint32_t ghc;
+ uint32_t is;
+ uint32_t pi;
+ uint32_t vs;
+ uint32_t ccc_ctl;
+ uint32_t ccc_pts;
+ uint32_t em_loc;
+ uint32_t em_ctl;
+ uint32_t cap2;
+ uint32_t bohc;
+ uint32_t lintr;
+ struct ahci_port port[MAX_PORTS];
+};
+#define ahci_ctx(sc) ((sc)->asc_pi->pi_vmctx)
+
+static void ahci_handle_port(struct ahci_port *p);
+
+static inline void lba_to_msf(uint8_t *buf, int lba)
+{
+ lba += 150;
+ buf[0] = (lba / 75) / 60;
+ buf[1] = (lba / 75) % 60;
+ buf[2] = lba % 75;
+}
+
+/*
+ * generate HBA intr depending on whether or not ports within
+ * the controller have an interrupt pending.
+ */
+static void
+ahci_generate_intr(struct pci_ahci_softc *sc)
+{
+ struct pci_devinst *pi;
+ int i;
+
+ pi = sc->asc_pi;
+
+ for (i = 0; i < sc->ports; i++) {
+ struct ahci_port *pr;
+ pr = &sc->port[i];
+ if (pr->is & pr->ie)
+ sc->is |= (1 << i);
+ }
+
+ DPRINTF("%s %x\n", __func__, sc->is);
+
+ if (sc->is && (sc->ghc & AHCI_GHC_IE)) {
+ if (pci_msi_enabled(pi)) {
+ /*
+ * Generate an MSI interrupt on every edge
+ */
+ pci_generate_msi(pi, 0);
+ } else if (!sc->lintr) {
+ /*
+ * Only generate a pin-based interrupt if one wasn't
+ * in progress
+ */
+ sc->lintr = 1;
+ pci_lintr_assert(pi);
+ }
+ } else if (sc->lintr) {
+ /*
+ * No interrupts: deassert pin-based signal if it had
+ * been asserted
+ */
+ pci_lintr_deassert(pi);
+ sc->lintr = 0;
+ }
+}
+
+static void
+ahci_write_fis(struct ahci_port *p, enum sata_fis_type ft, uint8_t *fis)
+{
+ int offset, len, irq;
+
+ if (p->rfis == NULL || !(p->cmd & AHCI_P_CMD_FRE))
+ return;
+
+ switch (ft) {
+ case FIS_TYPE_REGD2H:
+ offset = 0x40;
+ len = 20;
+ irq = (fis[1] & (1 << 6)) ? AHCI_P_IX_DHR : 0;
+ break;
+ case FIS_TYPE_SETDEVBITS:
+ offset = 0x58;
+ len = 8;
+ irq = (fis[1] & (1 << 6)) ? AHCI_P_IX_SDB : 0;
+ break;
+ case FIS_TYPE_PIOSETUP:
+ offset = 0x20;
+ len = 20;
+ irq = (fis[1] & (1 << 6)) ? AHCI_P_IX_PS : 0;
+ break;
+ default:
+ WPRINTF("unsupported fis type %d\n", ft);
+ return;
+ }
+ if (fis[2] & ATA_S_ERROR) {
+ p->waitforclear = 1;
+ irq |= AHCI_P_IX_TFE;
+ }
+ memcpy(p->rfis + offset, fis, len);
+ if (irq) {
+ p->is |= irq;
+ ahci_generate_intr(p->pr_sc);
+ }
+}
+
+static void
+ahci_write_fis_piosetup(struct ahci_port *p)
+{
+ uint8_t fis[20];
+
+ memset(fis, 0, sizeof(fis));
+ fis[0] = FIS_TYPE_PIOSETUP;
+ ahci_write_fis(p, FIS_TYPE_PIOSETUP, fis);
+}
+
+static void
+ahci_write_fis_sdb(struct ahci_port *p, int slot, uint8_t *cfis, uint32_t tfd)
+{
+ uint8_t fis[8];
+ uint8_t error;
+
+ error = (tfd >> 8) & 0xff;
+ tfd &= 0x77;
+ memset(fis, 0, sizeof(fis));
+ fis[0] = FIS_TYPE_SETDEVBITS;
+ fis[1] = (1 << 6);
+ fis[2] = tfd;
+ fis[3] = error;
+ if (fis[2] & ATA_S_ERROR) {
+ p->err_cfis[0] = slot;
+ p->err_cfis[2] = tfd;
+ p->err_cfis[3] = error;
+ memcpy(&p->err_cfis[4], cfis + 4, 16);
+ } else {
+ *(uint32_t *)(fis + 4) = (1 << slot);
+ p->sact &= ~(1 << slot);
+ }
+ p->tfd &= ~0x77;
+ p->tfd |= tfd;
+ ahci_write_fis(p, FIS_TYPE_SETDEVBITS, fis);
+}
+
+static void
+ahci_write_fis_d2h(struct ahci_port *p, int slot, uint8_t *cfis, uint32_t tfd)
+{
+ uint8_t fis[20];
+ uint8_t error;
+
+ error = (tfd >> 8) & 0xff;
+ memset(fis, 0, sizeof(fis));
+ fis[0] = FIS_TYPE_REGD2H;
+ fis[1] = (1 << 6);
+ fis[2] = tfd & 0xff;
+ fis[3] = error;
+ fis[4] = cfis[4];
+ fis[5] = cfis[5];
+ fis[6] = cfis[6];
+ fis[7] = cfis[7];
+ fis[8] = cfis[8];
+ fis[9] = cfis[9];
+ fis[10] = cfis[10];
+ fis[11] = cfis[11];
+ fis[12] = cfis[12];
+ fis[13] = cfis[13];
+ if (fis[2] & ATA_S_ERROR) {
+ p->err_cfis[0] = 0x80;
+ p->err_cfis[2] = tfd & 0xff;
+ p->err_cfis[3] = error;
+ memcpy(&p->err_cfis[4], cfis + 4, 16);
+ } else
+ p->ci &= ~(1 << slot);
+ p->tfd = tfd;
+ ahci_write_fis(p, FIS_TYPE_REGD2H, fis);
+}
+
+static void
+ahci_write_fis_d2h_ncq(struct ahci_port *p, int slot)
+{
+ uint8_t fis[20];
+
+ p->tfd = ATA_S_READY | ATA_S_DSC;
+ memset(fis, 0, sizeof(fis));
+ fis[0] = FIS_TYPE_REGD2H;
+ fis[1] = 0; /* No interrupt */
+ fis[2] = p->tfd; /* Status */
+ fis[3] = 0; /* No error */
+ p->ci &= ~(1 << slot);
+ ahci_write_fis(p, FIS_TYPE_REGD2H, fis);
+}
+
+static void
+ahci_write_reset_fis_d2h(struct ahci_port *p)
+{
+ uint8_t fis[20];
+
+ memset(fis, 0, sizeof(fis));
+ fis[0] = FIS_TYPE_REGD2H;
+ fis[3] = 1;
+ fis[4] = 1;
+ if (p->atapi) {
+ fis[5] = 0x14;
+ fis[6] = 0xeb;
+ }
+ fis[12] = 1;
+ ahci_write_fis(p, FIS_TYPE_REGD2H, fis);
+}
+
+static void
+ahci_check_stopped(struct ahci_port *p)
+{
+ /*
+ * If we are no longer processing the command list and nothing
+ * is in-flight, clear the running bit, the current command
+ * slot, the command issue and active bits.
+ */
+ if (!(p->cmd & AHCI_P_CMD_ST)) {
+ if (p->pending == 0) {
+ p->ccs = 0;
+ p->cmd &= ~(AHCI_P_CMD_CR | AHCI_P_CMD_CCS_MASK);
+ p->ci = 0;
+ p->sact = 0;
+ p->waitforclear = 0;
+ }
+ }
+}
+
+static void
+ahci_port_stop(struct ahci_port *p)
+{
+ struct ahci_ioreq *aior;
+ uint8_t *cfis;
+ int slot;
+ int ncq;
+ int error;
+
+ assert(pthread_mutex_isowned_np(&p->pr_sc->mtx));
+
+ TAILQ_FOREACH(aior, &p->iobhd, io_blist) {
+ /*
+ * Try to cancel the outstanding blockif request.
+ */
+ error = blockif_cancel(p->bctx, &aior->io_req);
+ if (error != 0)
+ continue;
+
+ slot = aior->slot;
+ cfis = aior->cfis;
+ if (cfis[2] == ATA_WRITE_FPDMA_QUEUED ||
+ cfis[2] == ATA_READ_FPDMA_QUEUED ||
+ cfis[2] == ATA_SEND_FPDMA_QUEUED)
+ ncq = 1;
+
+ if (ncq)
+ p->sact &= ~(1 << slot);
+ else
+ p->ci &= ~(1 << slot);
+
+ /*
+ * This command is now done.
+ */
+ p->pending &= ~(1 << slot);
+
+ /*
+ * Delete the blockif request from the busy list
+ */
+ TAILQ_REMOVE(&p->iobhd, aior, io_blist);
+
+ /*
+ * Move the blockif request back to the free list
+ */
+ STAILQ_INSERT_TAIL(&p->iofhd, aior, io_flist);
+ }
+
+ ahci_check_stopped(p);
+}
+
+static void
+ahci_port_reset(struct ahci_port *pr)
+{
+ pr->serr = 0;
+ pr->sact = 0;
+ pr->xfermode = ATA_UDMA6;
+ pr->mult_sectors = 128;
+
+ if (!pr->bctx) {
+ pr->ssts = ATA_SS_DET_NO_DEVICE;
+ pr->sig = 0xFFFFFFFF;
+ pr->tfd = 0x7F;
+ return;
+ }
+ pr->ssts = ATA_SS_DET_PHY_ONLINE | ATA_SS_IPM_ACTIVE;
+ if (pr->sctl & ATA_SC_SPD_MASK)
+ pr->ssts |= (pr->sctl & ATA_SC_SPD_MASK);
+ else
+ pr->ssts |= ATA_SS_SPD_GEN3;
+ pr->tfd = (1 << 8) | ATA_S_DSC | ATA_S_DMA;
+ if (!pr->atapi) {
+ pr->sig = PxSIG_ATA;
+ pr->tfd |= ATA_S_READY;
+ } else
+ pr->sig = PxSIG_ATAPI;
+ ahci_write_reset_fis_d2h(pr);
+}
+
+static void
+ahci_reset(struct pci_ahci_softc *sc)
+{
+ int i;
+
+ sc->ghc = AHCI_GHC_AE;
+ sc->is = 0;
+
+ if (sc->lintr) {
+ pci_lintr_deassert(sc->asc_pi);
+ sc->lintr = 0;
+ }
+
+ for (i = 0; i < sc->ports; i++) {
+ sc->port[i].ie = 0;
+ sc->port[i].is = 0;
+ sc->port[i].cmd = (AHCI_P_CMD_SUD | AHCI_P_CMD_POD);
+ if (sc->port[i].bctx)
+ sc->port[i].cmd |= AHCI_P_CMD_CPS;
+ sc->port[i].sctl = 0;
+ ahci_port_reset(&sc->port[i]);
+ }
+}
+
+static void
+ata_string(uint8_t *dest, const char *src, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++) {
+ if (*src)
+ dest[i ^ 1] = *src++;
+ else
+ dest[i ^ 1] = ' ';
+ }
+}
+
+static void
+atapi_string(uint8_t *dest, const char *src, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++) {
+ if (*src)
+ dest[i] = *src++;
+ else
+ dest[i] = ' ';
+ }
+}
+
+/*
+ * Build up the iovec based on the PRDT, 'done' and 'len'.
+ */
+static void
+ahci_build_iov(struct ahci_port *p, struct ahci_ioreq *aior,
+ struct ahci_prdt_entry *prdt, uint16_t prdtl)
+{
+ struct blockif_req *breq = &aior->io_req;
+ int i, j, skip, todo, left, extra;
+ uint32_t dbcsz;
+
+ /* Copy part of PRDT between 'done' and 'len' bytes into the iov. */
+ skip = aior->done;
+ left = aior->len - aior->done;
+ todo = 0;
+ for (i = 0, j = 0; i < prdtl && j < BLOCKIF_IOV_MAX && left > 0;
+ i++, prdt++) {
+ dbcsz = (prdt->dbc & DBCMASK) + 1;
+ /* Skip already done part of the PRDT */
+ if (dbcsz <= skip) {
+ skip -= dbcsz;
+ continue;
+ }
+ dbcsz -= skip;
+ if (dbcsz > left)
+ dbcsz = left;
+ breq->br_iov[j].iov_base = paddr_guest2host(ahci_ctx(p->pr_sc),
+ prdt->dba + skip, dbcsz);
+ breq->br_iov[j].iov_len = dbcsz;
+ todo += dbcsz;
+ left -= dbcsz;
+ skip = 0;
+ j++;
+ }
+
+ /* If we got limited by IOV length, round I/O down to sector size. */
+ if (j == BLOCKIF_IOV_MAX) {
+ extra = todo % blockif_sectsz(p->bctx);
+ todo -= extra;
+ assert(todo > 0);
+ while (extra > 0) {
+ if (breq->br_iov[j - 1].iov_len > extra) {
+ breq->br_iov[j - 1].iov_len -= extra;
+ break;
+ }
+ extra -= breq->br_iov[j - 1].iov_len;
+ j--;
+ }
+ }
+
+ breq->br_iovcnt = j;
+ breq->br_resid = todo;
+ aior->done += todo;
+ aior->more = (aior->done < aior->len && i < prdtl);
+}
+
+static void
+ahci_handle_rw(struct ahci_port *p, int slot, uint8_t *cfis, uint32_t done)
+{
+ struct ahci_ioreq *aior;
+ struct blockif_req *breq;
+ struct ahci_prdt_entry *prdt;
+ struct ahci_cmd_hdr *hdr;
+ uint64_t lba;
+ uint32_t len;
+ int err, first, ncq, readop;
+
+ prdt = (struct ahci_prdt_entry *)(cfis + 0x80);
+ hdr = (struct ahci_cmd_hdr *)(p->cmd_lst + slot * AHCI_CL_SIZE);
+ ncq = 0;
+ readop = 1;
+ first = (done == 0);
+
+ if (cfis[2] == ATA_WRITE || cfis[2] == ATA_WRITE48 ||
+ cfis[2] == ATA_WRITE_MUL || cfis[2] == ATA_WRITE_MUL48 ||
+ cfis[2] == ATA_WRITE_DMA || cfis[2] == ATA_WRITE_DMA48 ||
+ cfis[2] == ATA_WRITE_FPDMA_QUEUED)
+ readop = 0;
+
+ if (cfis[2] == ATA_WRITE_FPDMA_QUEUED ||
+ cfis[2] == ATA_READ_FPDMA_QUEUED) {
+ lba = ((uint64_t)cfis[10] << 40) |
+ ((uint64_t)cfis[9] << 32) |
+ ((uint64_t)cfis[8] << 24) |
+ ((uint64_t)cfis[6] << 16) |
+ ((uint64_t)cfis[5] << 8) |
+ cfis[4];
+ len = cfis[11] << 8 | cfis[3];
+ if (!len)
+ len = 65536;
+ ncq = 1;
+ } else if (cfis[2] == ATA_READ48 || cfis[2] == ATA_WRITE48 ||
+ cfis[2] == ATA_READ_MUL48 || cfis[2] == ATA_WRITE_MUL48 ||
+ cfis[2] == ATA_READ_DMA48 || cfis[2] == ATA_WRITE_DMA48) {
+ lba = ((uint64_t)cfis[10] << 40) |
+ ((uint64_t)cfis[9] << 32) |
+ ((uint64_t)cfis[8] << 24) |
+ ((uint64_t)cfis[6] << 16) |
+ ((uint64_t)cfis[5] << 8) |
+ cfis[4];
+ len = cfis[13] << 8 | cfis[12];
+ if (!len)
+ len = 65536;
+ } else {
+ lba = ((cfis[7] & 0xf) << 24) | (cfis[6] << 16) |
+ (cfis[5] << 8) | cfis[4];
+ len = cfis[12];
+ if (!len)
+ len = 256;
+ }
+ lba *= blockif_sectsz(p->bctx);
+ len *= blockif_sectsz(p->bctx);
+
+ /* Pull request off free list */
+ aior = STAILQ_FIRST(&p->iofhd);
+ assert(aior != NULL);
+ STAILQ_REMOVE_HEAD(&p->iofhd, io_flist);
+
+ aior->cfis = cfis;
+ aior->slot = slot;
+ aior->len = len;
+ aior->done = done;
+ breq = &aior->io_req;
+ breq->br_offset = lba + done;
+ ahci_build_iov(p, aior, prdt, hdr->prdtl);
+
+ /* Mark this command in-flight. */
+ p->pending |= 1 << slot;
+
+ /* Stuff request onto busy list. */
+ TAILQ_INSERT_HEAD(&p->iobhd, aior, io_blist);
+
+ if (ncq && first)
+ ahci_write_fis_d2h_ncq(p, slot);
+
+ if (readop)
+ err = blockif_read(p->bctx, breq);
+ else
+ err = blockif_write(p->bctx, breq);
+ assert(err == 0);
+}
+
+static void
+ahci_handle_flush(struct ahci_port *p, int slot, uint8_t *cfis)
+{
+ struct ahci_ioreq *aior;
+ struct blockif_req *breq;
+ int err;
+
+ /*
+ * Pull request off free list
+ */
+ aior = STAILQ_FIRST(&p->iofhd);
+ assert(aior != NULL);
+ STAILQ_REMOVE_HEAD(&p->iofhd, io_flist);
+ aior->cfis = cfis;
+ aior->slot = slot;
+ aior->len = 0;
+ aior->done = 0;
+ aior->more = 0;
+ breq = &aior->io_req;
+
+ /*
+ * Mark this command in-flight.
+ */
+ p->pending |= 1 << slot;
+
+ /*
+ * Stuff request onto busy list
+ */
+ TAILQ_INSERT_HEAD(&p->iobhd, aior, io_blist);
+
+ err = blockif_flush(p->bctx, breq);
+ assert(err == 0);
+}
+
+static inline void
+read_prdt(struct ahci_port *p, int slot, uint8_t *cfis,
+ void *buf, int size)
+{
+ struct ahci_cmd_hdr *hdr;
+ struct ahci_prdt_entry *prdt;
+ void *to;
+ int i, len;
+
+ hdr = (struct ahci_cmd_hdr *)(p->cmd_lst + slot * AHCI_CL_SIZE);
+ len = size;
+ to = buf;
+ prdt = (struct ahci_prdt_entry *)(cfis + 0x80);
+ for (i = 0; i < hdr->prdtl && len; i++) {
+ uint8_t *ptr;
+ uint32_t dbcsz;
+ int sublen;
+
+ dbcsz = (prdt->dbc & DBCMASK) + 1;
+ ptr = paddr_guest2host(ahci_ctx(p->pr_sc), prdt->dba, dbcsz);
+ sublen = len < dbcsz ? len : dbcsz;
+ memcpy(to, ptr, sublen);
+ len -= sublen;
+ to += sublen;
+ prdt++;
+ }
+}
+
+static void
+ahci_handle_dsm_trim(struct ahci_port *p, int slot, uint8_t *cfis, uint32_t done)
+{
+ struct ahci_ioreq *aior;
+ struct blockif_req *breq;
+ uint8_t *entry;
+ uint64_t elba;
+ uint32_t len, elen;
+ int err, first, ncq;
+ uint8_t buf[512];
+
+ first = (done == 0);
+ if (cfis[2] == ATA_DATA_SET_MANAGEMENT) {
+ len = (uint16_t)cfis[13] << 8 | cfis[12];
+ len *= 512;
+ ncq = 0;
+ } else { /* ATA_SEND_FPDMA_QUEUED */
+ len = (uint16_t)cfis[11] << 8 | cfis[3];
+ len *= 512;
+ ncq = 1;
+ }
+ read_prdt(p, slot, cfis, buf, sizeof(buf));
+
+next:
+ entry = &buf[done];
+ elba = ((uint64_t)entry[5] << 40) |
+ ((uint64_t)entry[4] << 32) |
+ ((uint64_t)entry[3] << 24) |
+ ((uint64_t)entry[2] << 16) |
+ ((uint64_t)entry[1] << 8) |
+ entry[0];
+ elen = (uint16_t)entry[7] << 8 | entry[6];
+ done += 8;
+ if (elen == 0) {
+ if (done >= len) {
+ ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC);
+ p->pending &= ~(1 << slot);
+ ahci_check_stopped(p);
+ if (!first)
+ ahci_handle_port(p);
+ return;
+ }
+ goto next;
+ }
+
+ /*
+ * Pull request off free list
+ */
+ aior = STAILQ_FIRST(&p->iofhd);
+ assert(aior != NULL);
+ STAILQ_REMOVE_HEAD(&p->iofhd, io_flist);
+ aior->cfis = cfis;
+ aior->slot = slot;
+ aior->len = len;
+ aior->done = done;
+ aior->more = (len != done);
+
+ breq = &aior->io_req;
+ breq->br_offset = elba * blockif_sectsz(p->bctx);
+ breq->br_resid = elen * blockif_sectsz(p->bctx);
+
+ /*
+ * Mark this command in-flight.
+ */
+ p->pending |= 1 << slot;
+
+ /*
+ * Stuff request onto busy list
+ */
+ TAILQ_INSERT_HEAD(&p->iobhd, aior, io_blist);
+
+ if (ncq && first)
+ ahci_write_fis_d2h_ncq(p, slot);
+
+ err = blockif_delete(p->bctx, breq);
+ assert(err == 0);
+}
+
+static inline void
+write_prdt(struct ahci_port *p, int slot, uint8_t *cfis,
+ void *buf, int size)
+{
+ struct ahci_cmd_hdr *hdr;
+ struct ahci_prdt_entry *prdt;
+ void *from;
+ int i, len;
+
+ hdr = (struct ahci_cmd_hdr *)(p->cmd_lst + slot * AHCI_CL_SIZE);
+ len = size;
+ from = buf;
+ prdt = (struct ahci_prdt_entry *)(cfis + 0x80);
+ for (i = 0; i < hdr->prdtl && len; i++) {
+ uint8_t *ptr;
+ uint32_t dbcsz;
+ int sublen;
+
+ dbcsz = (prdt->dbc & DBCMASK) + 1;
+ ptr = paddr_guest2host(ahci_ctx(p->pr_sc), prdt->dba, dbcsz);
+ sublen = len < dbcsz ? len : dbcsz;
+ memcpy(ptr, from, sublen);
+ len -= sublen;
+ from += sublen;
+ prdt++;
+ }
+ hdr->prdbc = size - len;
+}
+
+static void
+ahci_checksum(uint8_t *buf, int size)
+{
+ int i;
+ uint8_t sum = 0;
+
+ for (i = 0; i < size - 1; i++)
+ sum += buf[i];
+ buf[size - 1] = 0x100 - sum;
+}
+
+static void
+ahci_handle_read_log(struct ahci_port *p, int slot, uint8_t *cfis)
+{
+ struct ahci_cmd_hdr *hdr;
+ uint8_t buf[512];
+
+ hdr = (struct ahci_cmd_hdr *)(p->cmd_lst + slot * AHCI_CL_SIZE);
+ if (p->atapi || hdr->prdtl == 0 || cfis[4] != 0x10 ||
+ cfis[5] != 0 || cfis[9] != 0 || cfis[12] != 1 || cfis[13] != 0) {
+ ahci_write_fis_d2h(p, slot, cfis,
+ (ATA_E_ABORT << 8) | ATA_S_READY | ATA_S_ERROR);
+ return;
+ }
+
+ memset(buf, 0, sizeof(buf));
+ memcpy(buf, p->err_cfis, sizeof(p->err_cfis));
+ ahci_checksum(buf, sizeof(buf));
+
+ if (cfis[2] == ATA_READ_LOG_EXT)
+ ahci_write_fis_piosetup(p);
+ write_prdt(p, slot, cfis, (void *)buf, sizeof(buf));
+ ahci_write_fis_d2h(p, slot, cfis, ATA_S_DSC | ATA_S_READY);
+}
+
+static void
+handle_identify(struct ahci_port *p, int slot, uint8_t *cfis)
+{
+ struct ahci_cmd_hdr *hdr;
+
+ hdr = (struct ahci_cmd_hdr *)(p->cmd_lst + slot * AHCI_CL_SIZE);
+ if (p->atapi || hdr->prdtl == 0) {
+ ahci_write_fis_d2h(p, slot, cfis,
+ (ATA_E_ABORT << 8) | ATA_S_READY | ATA_S_ERROR);
+ } else {
+ uint16_t buf[256];
+ uint64_t sectors;
+ int sectsz, psectsz, psectoff, candelete, ro;
+ uint16_t cyl;
+ uint8_t sech, heads;
+
+ ro = blockif_is_ro(p->bctx);
+ candelete = blockif_candelete(p->bctx);
+ sectsz = blockif_sectsz(p->bctx);
+ sectors = blockif_size(p->bctx) / sectsz;
+ blockif_chs(p->bctx, &cyl, &heads, &sech);
+ blockif_psectsz(p->bctx, &psectsz, &psectoff);
+ memset(buf, 0, sizeof(buf));
+ buf[0] = 0x0040;
+ buf[1] = cyl;
+ buf[3] = heads;
+ buf[6] = sech;
+ ata_string((uint8_t *)(buf+10), p->ident, 20);
+ ata_string((uint8_t *)(buf+23), "001", 8);
+ ata_string((uint8_t *)(buf+27), "BHYVE SATA DISK", 40);
+ buf[47] = (0x8000 | 128);
+ buf[48] = 0;
+ buf[49] = (1 << 8 | 1 << 9 | 1 << 11);
+ buf[50] = (1 << 14);
+ buf[53] = (1 << 1 | 1 << 2);
+ if (p->mult_sectors)
+ buf[59] = (0x100 | p->mult_sectors);
+ if (sectors <= 0x0fffffff) {
+ buf[60] = sectors;
+ buf[61] = (sectors >> 16);
+ } else {
+ buf[60] = 0xffff;
+ buf[61] = 0x0fff;
+ }
+ buf[63] = 0x7;
+ if (p->xfermode & ATA_WDMA0)
+ buf[63] |= (1 << ((p->xfermode & 7) + 8));
+ buf[64] = 0x3;
+ buf[65] = 120;
+ buf[66] = 120;
+ buf[67] = 120;
+ buf[68] = 120;
+ buf[69] = 0;
+ buf[75] = 31;
+ buf[76] = (ATA_SATA_GEN1 | ATA_SATA_GEN2 | ATA_SATA_GEN3 |
+ ATA_SUPPORT_NCQ);
+ buf[77] = (ATA_SUPPORT_RCVSND_FPDMA_QUEUED |
+ (p->ssts & ATA_SS_SPD_MASK) >> 3);
+ buf[80] = 0x3f0;
+ buf[81] = 0x28;
+ buf[82] = (ATA_SUPPORT_POWERMGT | ATA_SUPPORT_WRITECACHE|
+ ATA_SUPPORT_LOOKAHEAD | ATA_SUPPORT_NOP);
+ buf[83] = (ATA_SUPPORT_ADDRESS48 | ATA_SUPPORT_FLUSHCACHE |
+ ATA_SUPPORT_FLUSHCACHE48 | 1 << 14);
+ buf[84] = (1 << 14);
+ buf[85] = (ATA_SUPPORT_POWERMGT | ATA_SUPPORT_WRITECACHE|
+ ATA_SUPPORT_LOOKAHEAD | ATA_SUPPORT_NOP);
+ buf[86] = (ATA_SUPPORT_ADDRESS48 | ATA_SUPPORT_FLUSHCACHE |
+ ATA_SUPPORT_FLUSHCACHE48 | 1 << 15);
+ buf[87] = (1 << 14);
+ buf[88] = 0x7f;
+ if (p->xfermode & ATA_UDMA0)
+ buf[88] |= (1 << ((p->xfermode & 7) + 8));
+ buf[100] = sectors;
+ buf[101] = (sectors >> 16);
+ buf[102] = (sectors >> 32);
+ buf[103] = (sectors >> 48);
+ if (candelete && !ro) {
+ buf[69] |= ATA_SUPPORT_RZAT | ATA_SUPPORT_DRAT;
+ buf[105] = 1;
+ buf[169] = ATA_SUPPORT_DSM_TRIM;
+ }
+ buf[106] = 0x4000;
+ buf[209] = 0x4000;
+ if (psectsz > sectsz) {
+ buf[106] |= 0x2000;
+ buf[106] |= ffsl(psectsz / sectsz) - 1;
+ buf[209] |= (psectoff / sectsz);
+ }
+ if (sectsz > 512) {
+ buf[106] |= 0x1000;
+ buf[117] = sectsz / 2;
+ buf[118] = ((sectsz / 2) >> 16);
+ }
+ buf[119] = (ATA_SUPPORT_RWLOGDMAEXT | 1 << 14);
+ buf[120] = (ATA_SUPPORT_RWLOGDMAEXT | 1 << 14);
+ buf[222] = 0x1020;
+ buf[255] = 0x00a5;
+ ahci_checksum((uint8_t *)buf, sizeof(buf));
+ ahci_write_fis_piosetup(p);
+ write_prdt(p, slot, cfis, (void *)buf, sizeof(buf));
+ ahci_write_fis_d2h(p, slot, cfis, ATA_S_DSC | ATA_S_READY);
+ }
+}
+
+static void
+handle_atapi_identify(struct ahci_port *p, int slot, uint8_t *cfis)
+{
+ if (!p->atapi) {
+ ahci_write_fis_d2h(p, slot, cfis,
+ (ATA_E_ABORT << 8) | ATA_S_READY | ATA_S_ERROR);
+ } else {
+ uint16_t buf[256];
+
+ memset(buf, 0, sizeof(buf));
+ buf[0] = (2 << 14 | 5 << 8 | 1 << 7 | 2 << 5);
+ ata_string((uint8_t *)(buf+10), p->ident, 20);
+ ata_string((uint8_t *)(buf+23), "001", 8);
+ ata_string((uint8_t *)(buf+27), "BHYVE SATA DVD ROM", 40);
+ buf[49] = (1 << 9 | 1 << 8);
+ buf[50] = (1 << 14 | 1);
+ buf[53] = (1 << 2 | 1 << 1);
+ buf[62] = 0x3f;
+ buf[63] = 7;
+ if (p->xfermode & ATA_WDMA0)
+ buf[63] |= (1 << ((p->xfermode & 7) + 8));
+ buf[64] = 3;
+ buf[65] = 120;
+ buf[66] = 120;
+ buf[67] = 120;
+ buf[68] = 120;
+ buf[76] = (ATA_SATA_GEN1 | ATA_SATA_GEN2 | ATA_SATA_GEN3);
+ buf[77] = ((p->ssts & ATA_SS_SPD_MASK) >> 3);
+ buf[78] = (1 << 5);
+ buf[80] = 0x3f0;
+ buf[82] = (ATA_SUPPORT_POWERMGT | ATA_SUPPORT_PACKET |
+ ATA_SUPPORT_RESET | ATA_SUPPORT_NOP);
+ buf[83] = (1 << 14);
+ buf[84] = (1 << 14);
+ buf[85] = (ATA_SUPPORT_POWERMGT | ATA_SUPPORT_PACKET |
+ ATA_SUPPORT_RESET | ATA_SUPPORT_NOP);
+ buf[87] = (1 << 14);
+ buf[88] = 0x7f;
+ if (p->xfermode & ATA_UDMA0)
+ buf[88] |= (1 << ((p->xfermode & 7) + 8));
+ buf[222] = 0x1020;
+ buf[255] = 0x00a5;
+ ahci_checksum((uint8_t *)buf, sizeof(buf));
+ ahci_write_fis_piosetup(p);
+ write_prdt(p, slot, cfis, (void *)buf, sizeof(buf));
+ ahci_write_fis_d2h(p, slot, cfis, ATA_S_DSC | ATA_S_READY);
+ }
+}
+
+static void
+atapi_inquiry(struct ahci_port *p, int slot, uint8_t *cfis)
+{
+ uint8_t buf[36];
+ uint8_t *acmd;
+ int len;
+ uint32_t tfd;
+
+ acmd = cfis + 0x40;
+
+ if (acmd[1] & 1) { /* VPD */
+ if (acmd[2] == 0) { /* Supported VPD pages */
+ buf[0] = 0x05;
+ buf[1] = 0;
+ buf[2] = 0;
+ buf[3] = 1;
+ buf[4] = 0;
+ len = 4 + buf[3];
+ } else {
+ p->sense_key = ATA_SENSE_ILLEGAL_REQUEST;
+ p->asc = 0x24;
+ tfd = (p->sense_key << 12) | ATA_S_READY | ATA_S_ERROR;
+ cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN;
+ ahci_write_fis_d2h(p, slot, cfis, tfd);
+ return;
+ }
+ } else {
+ buf[0] = 0x05;
+ buf[1] = 0x80;
+ buf[2] = 0x00;
+ buf[3] = 0x21;
+ buf[4] = 31;
+ buf[5] = 0;
+ buf[6] = 0;
+ buf[7] = 0;
+ atapi_string(buf + 8, "BHYVE", 8);
+ atapi_string(buf + 16, "BHYVE DVD-ROM", 16);
+ atapi_string(buf + 32, "001", 4);
+ len = sizeof(buf);
+ }
+
+ if (len > acmd[4])
+ len = acmd[4];
+ cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN;
+ write_prdt(p, slot, cfis, buf, len);
+ ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC);
+}
+
+static void
+atapi_read_capacity(struct ahci_port *p, int slot, uint8_t *cfis)
+{
+ uint8_t buf[8];
+ uint64_t sectors;
+
+ sectors = blockif_size(p->bctx) / 2048;
+ be32enc(buf, sectors - 1);
+ be32enc(buf + 4, 2048);
+ cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN;
+ write_prdt(p, slot, cfis, buf, sizeof(buf));
+ ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC);
+}
+
+static void
+atapi_read_toc(struct ahci_port *p, int slot, uint8_t *cfis)
+{
+ uint8_t *acmd;
+ uint8_t format;
+ int len;
+
+ acmd = cfis + 0x40;
+
+ len = be16dec(acmd + 7);
+ format = acmd[9] >> 6;
+ switch (format) {
+ case 0:
+ {
+ int msf, size;
+ uint64_t sectors;
+ uint8_t start_track, buf[20], *bp;
+
+ msf = (acmd[1] >> 1) & 1;
+ start_track = acmd[6];
+ if (start_track > 1 && start_track != 0xaa) {
+ uint32_t tfd;
+ p->sense_key = ATA_SENSE_ILLEGAL_REQUEST;
+ p->asc = 0x24;
+ tfd = (p->sense_key << 12) | ATA_S_READY | ATA_S_ERROR;
+ cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN;
+ ahci_write_fis_d2h(p, slot, cfis, tfd);
+ return;
+ }
+ bp = buf + 2;
+ *bp++ = 1;
+ *bp++ = 1;
+ if (start_track <= 1) {
+ *bp++ = 0;
+ *bp++ = 0x14;
+ *bp++ = 1;
+ *bp++ = 0;
+ if (msf) {
+ *bp++ = 0;
+ lba_to_msf(bp, 0);
+ bp += 3;
+ } else {
+ *bp++ = 0;
+ *bp++ = 0;
+ *bp++ = 0;
+ *bp++ = 0;
+ }
+ }
+ *bp++ = 0;
+ *bp++ = 0x14;
+ *bp++ = 0xaa;
+ *bp++ = 0;
+ sectors = blockif_size(p->bctx) / blockif_sectsz(p->bctx);
+ sectors >>= 2;
+ if (msf) {
+ *bp++ = 0;
+ lba_to_msf(bp, sectors);
+ bp += 3;
+ } else {
+ be32enc(bp, sectors);
+ bp += 4;
+ }
+ size = bp - buf;
+ be16enc(buf, size - 2);
+ if (len > size)
+ len = size;
+ write_prdt(p, slot, cfis, buf, len);
+ cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN;
+ ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC);
+ break;
+ }
+ case 1:
+ {
+ uint8_t buf[12];
+
+ memset(buf, 0, sizeof(buf));
+ buf[1] = 0xa;
+ buf[2] = 0x1;
+ buf[3] = 0x1;
+ if (len > sizeof(buf))
+ len = sizeof(buf);
+ write_prdt(p, slot, cfis, buf, len);
+ cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN;
+ ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC);
+ break;
+ }
+ case 2:
+ {
+ int msf, size;
+ uint64_t sectors;
+ uint8_t start_track, *bp, buf[50];
+
+ msf = (acmd[1] >> 1) & 1;
+ start_track = acmd[6];
+ bp = buf + 2;
+ *bp++ = 1;
+ *bp++ = 1;
+
+ *bp++ = 1;
+ *bp++ = 0x14;
+ *bp++ = 0;
+ *bp++ = 0xa0;
+ *bp++ = 0;
+ *bp++ = 0;
+ *bp++ = 0;
+ *bp++ = 0;
+ *bp++ = 1;
+ *bp++ = 0;
+ *bp++ = 0;
+
+ *bp++ = 1;
+ *bp++ = 0x14;
+ *bp++ = 0;
+ *bp++ = 0xa1;
+ *bp++ = 0;
+ *bp++ = 0;
+ *bp++ = 0;
+ *bp++ = 0;
+ *bp++ = 1;
+ *bp++ = 0;
+ *bp++ = 0;
+
+ *bp++ = 1;
+ *bp++ = 0x14;
+ *bp++ = 0;
+ *bp++ = 0xa2;
+ *bp++ = 0;
+ *bp++ = 0;
+ *bp++ = 0;
+ sectors = blockif_size(p->bctx) / blockif_sectsz(p->bctx);
+ sectors >>= 2;
+ if (msf) {
+ *bp++ = 0;
+ lba_to_msf(bp, sectors);
+ bp += 3;
+ } else {
+ be32enc(bp, sectors);
+ bp += 4;
+ }
+
+ *bp++ = 1;
+ *bp++ = 0x14;
+ *bp++ = 0;
+ *bp++ = 1;
+ *bp++ = 0;
+ *bp++ = 0;
+ *bp++ = 0;
+ if (msf) {
+ *bp++ = 0;
+ lba_to_msf(bp, 0);
+ bp += 3;
+ } else {
+ *bp++ = 0;
+ *bp++ = 0;
+ *bp++ = 0;
+ *bp++ = 0;
+ }
+
+ size = bp - buf;
+ be16enc(buf, size - 2);
+ if (len > size)
+ len = size;
+ write_prdt(p, slot, cfis, buf, len);
+ cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN;
+ ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC);
+ break;
+ }
+ default:
+ {
+ uint32_t tfd;
+
+ p->sense_key = ATA_SENSE_ILLEGAL_REQUEST;
+ p->asc = 0x24;
+ tfd = (p->sense_key << 12) | ATA_S_READY | ATA_S_ERROR;
+ cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN;
+ ahci_write_fis_d2h(p, slot, cfis, tfd);
+ break;
+ }
+ }
+}
+
+static void
+atapi_report_luns(struct ahci_port *p, int slot, uint8_t *cfis)
+{
+ uint8_t buf[16];
+
+ memset(buf, 0, sizeof(buf));
+ buf[3] = 8;
+
+ cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN;
+ write_prdt(p, slot, cfis, buf, sizeof(buf));
+ ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC);
+}
+
+static void
+atapi_read(struct ahci_port *p, int slot, uint8_t *cfis, uint32_t done)
+{
+ struct ahci_ioreq *aior;
+ struct ahci_cmd_hdr *hdr;
+ struct ahci_prdt_entry *prdt;
+ struct blockif_req *breq;
+ struct pci_ahci_softc *sc;
+ uint8_t *acmd;
+ uint64_t lba;
+ uint32_t len;
+ int err;
+
+ sc = p->pr_sc;
+ acmd = cfis + 0x40;
+ hdr = (struct ahci_cmd_hdr *)(p->cmd_lst + slot * AHCI_CL_SIZE);
+ prdt = (struct ahci_prdt_entry *)(cfis + 0x80);
+
+ lba = be32dec(acmd + 2);
+ if (acmd[0] == READ_10)
+ len = be16dec(acmd + 7);
+ else
+ len = be32dec(acmd + 6);
+ if (len == 0) {
+ cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN;
+ ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC);
+ }
+ lba *= 2048;
+ len *= 2048;
+
+ /*
+ * Pull request off free list
+ */
+ aior = STAILQ_FIRST(&p->iofhd);
+ assert(aior != NULL);
+ STAILQ_REMOVE_HEAD(&p->iofhd, io_flist);
+ aior->cfis = cfis;
+ aior->slot = slot;
+ aior->len = len;
+ aior->done = done;
+ breq = &aior->io_req;
+ breq->br_offset = lba + done;
+ ahci_build_iov(p, aior, prdt, hdr->prdtl);
+
+ /* Mark this command in-flight. */
+ p->pending |= 1 << slot;
+
+ /* Stuff request onto busy list. */
+ TAILQ_INSERT_HEAD(&p->iobhd, aior, io_blist);
+
+ err = blockif_read(p->bctx, breq);
+ assert(err == 0);
+}
+
+static void
+atapi_request_sense(struct ahci_port *p, int slot, uint8_t *cfis)
+{
+ uint8_t buf[64];
+ uint8_t *acmd;
+ int len;
+
+ acmd = cfis + 0x40;
+ len = acmd[4];
+ if (len > sizeof(buf))
+ len = sizeof(buf);
+ memset(buf, 0, len);
+ buf[0] = 0x70 | (1 << 7);
+ buf[2] = p->sense_key;
+ buf[7] = 10;
+ buf[12] = p->asc;
+ write_prdt(p, slot, cfis, buf, len);
+ cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN;
+ ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC);
+}
+
+static void
+atapi_start_stop_unit(struct ahci_port *p, int slot, uint8_t *cfis)
+{
+ uint8_t *acmd = cfis + 0x40;
+ uint32_t tfd;
+
+ switch (acmd[4] & 3) {
+ case 0:
+ case 1:
+ case 3:
+ cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN;
+ tfd = ATA_S_READY | ATA_S_DSC;
+ break;
+ case 2:
+ /* TODO eject media */
+ cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN;
+ p->sense_key = ATA_SENSE_ILLEGAL_REQUEST;
+ p->asc = 0x53;
+ tfd = (p->sense_key << 12) | ATA_S_READY | ATA_S_ERROR;
+ break;
+ }
+ ahci_write_fis_d2h(p, slot, cfis, tfd);
+}
+
+static void
+atapi_mode_sense(struct ahci_port *p, int slot, uint8_t *cfis)
+{
+ uint8_t *acmd;
+ uint32_t tfd;
+ uint8_t pc, code;
+ int len;
+
+ acmd = cfis + 0x40;
+ len = be16dec(acmd + 7);
+ pc = acmd[2] >> 6;
+ code = acmd[2] & 0x3f;
+
+ switch (pc) {
+ case 0:
+ switch (code) {
+ case MODEPAGE_RW_ERROR_RECOVERY:
+ {
+ uint8_t buf[16];
+
+ if (len > sizeof(buf))
+ len = sizeof(buf);
+
+ memset(buf, 0, sizeof(buf));
+ be16enc(buf, 16 - 2);
+ buf[2] = 0x70;
+ buf[8] = 0x01;
+ buf[9] = 16 - 10;
+ buf[11] = 0x05;
+ write_prdt(p, slot, cfis, buf, len);
+ tfd = ATA_S_READY | ATA_S_DSC;
+ break;
+ }
+ case MODEPAGE_CD_CAPABILITIES:
+ {
+ uint8_t buf[30];
+
+ if (len > sizeof(buf))
+ len = sizeof(buf);
+
+ memset(buf, 0, sizeof(buf));
+ be16enc(buf, 30 - 2);
+ buf[2] = 0x70;
+ buf[8] = 0x2A;
+ buf[9] = 30 - 10;
+ buf[10] = 0x08;
+ buf[12] = 0x71;
+ be16enc(&buf[18], 2);
+ be16enc(&buf[20], 512);
+ write_prdt(p, slot, cfis, buf, len);
+ tfd = ATA_S_READY | ATA_S_DSC;
+ break;
+ }
+ default:
+ goto error;
+ break;
+ }
+ break;
+ case 3:
+ p->sense_key = ATA_SENSE_ILLEGAL_REQUEST;
+ p->asc = 0x39;
+ tfd = (p->sense_key << 12) | ATA_S_READY | ATA_S_ERROR;
+ break;
+error:
+ case 1:
+ case 2:
+ p->sense_key = ATA_SENSE_ILLEGAL_REQUEST;
+ p->asc = 0x24;
+ tfd = (p->sense_key << 12) | ATA_S_READY | ATA_S_ERROR;
+ break;
+ }
+ cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN;
+ ahci_write_fis_d2h(p, slot, cfis, tfd);
+}
+
+static void
+atapi_get_event_status_notification(struct ahci_port *p, int slot,
+ uint8_t *cfis)
+{
+ uint8_t *acmd;
+ uint32_t tfd;
+
+ acmd = cfis + 0x40;
+
+ /* we don't support asynchronous operation */
+ if (!(acmd[1] & 1)) {
+ p->sense_key = ATA_SENSE_ILLEGAL_REQUEST;
+ p->asc = 0x24;
+ tfd = (p->sense_key << 12) | ATA_S_READY | ATA_S_ERROR;
+ } else {
+ uint8_t buf[8];
+ int len;
+
+ len = be16dec(acmd + 7);
+ if (len > sizeof(buf))
+ len = sizeof(buf);
+
+ memset(buf, 0, sizeof(buf));
+ be16enc(buf, 8 - 2);
+ buf[2] = 0x04;
+ buf[3] = 0x10;
+ buf[5] = 0x02;
+ write_prdt(p, slot, cfis, buf, len);
+ tfd = ATA_S_READY | ATA_S_DSC;
+ }
+ cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN;
+ ahci_write_fis_d2h(p, slot, cfis, tfd);
+}
+
+static void
+handle_packet_cmd(struct ahci_port *p, int slot, uint8_t *cfis)
+{
+ uint8_t *acmd;
+
+ acmd = cfis + 0x40;
+
+#ifdef AHCI_DEBUG
+ {
+ int i;
+ DPRINTF("ACMD:");
+ for (i = 0; i < 16; i++)
+ DPRINTF("%02x ", acmd[i]);
+ DPRINTF("\n");
+ }
+#endif
+
+ switch (acmd[0]) {
+ case TEST_UNIT_READY:
+ cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN;
+ ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC);
+ break;
+ case INQUIRY:
+ atapi_inquiry(p, slot, cfis);
+ break;
+ case READ_CAPACITY:
+ atapi_read_capacity(p, slot, cfis);
+ break;
+ case PREVENT_ALLOW:
+ /* TODO */
+ cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN;
+ ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC);
+ break;
+ case READ_TOC:
+ atapi_read_toc(p, slot, cfis);
+ break;
+ case REPORT_LUNS:
+ atapi_report_luns(p, slot, cfis);
+ break;
+ case READ_10:
+ case READ_12:
+ atapi_read(p, slot, cfis, 0);
+ break;
+ case REQUEST_SENSE:
+ atapi_request_sense(p, slot, cfis);
+ break;
+ case START_STOP_UNIT:
+ atapi_start_stop_unit(p, slot, cfis);
+ break;
+ case MODE_SENSE_10:
+ atapi_mode_sense(p, slot, cfis);
+ break;
+ case GET_EVENT_STATUS_NOTIFICATION:
+ atapi_get_event_status_notification(p, slot, cfis);
+ break;
+ default:
+ cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN;
+ p->sense_key = ATA_SENSE_ILLEGAL_REQUEST;
+ p->asc = 0x20;
+ ahci_write_fis_d2h(p, slot, cfis, (p->sense_key << 12) |
+ ATA_S_READY | ATA_S_ERROR);
+ break;
+ }
+}
+
+static void
+ahci_handle_cmd(struct ahci_port *p, int slot, uint8_t *cfis)
+{
+
+ p->tfd |= ATA_S_BUSY;
+ switch (cfis[2]) {
+ case ATA_ATA_IDENTIFY:
+ handle_identify(p, slot, cfis);
+ break;
+ case ATA_SETFEATURES:
+ {
+ switch (cfis[3]) {
+ case ATA_SF_ENAB_SATA_SF:
+ switch (cfis[12]) {
+ case ATA_SATA_SF_AN:
+ p->tfd = ATA_S_DSC | ATA_S_READY;
+ break;
+ default:
+ p->tfd = ATA_S_ERROR | ATA_S_READY;
+ p->tfd |= (ATA_ERROR_ABORT << 8);
+ break;
+ }
+ break;
+ case ATA_SF_ENAB_WCACHE:
+ case ATA_SF_DIS_WCACHE:
+ case ATA_SF_ENAB_RCACHE:
+ case ATA_SF_DIS_RCACHE:
+ p->tfd = ATA_S_DSC | ATA_S_READY;
+ break;
+ case ATA_SF_SETXFER:
+ {
+ switch (cfis[12] & 0xf8) {
+ case ATA_PIO:
+ case ATA_PIO0:
+ break;
+ case ATA_WDMA0:
+ case ATA_UDMA0:
+ p->xfermode = (cfis[12] & 0x7);
+ break;
+ }
+ p->tfd = ATA_S_DSC | ATA_S_READY;
+ break;
+ }
+ default:
+ p->tfd = ATA_S_ERROR | ATA_S_READY;
+ p->tfd |= (ATA_ERROR_ABORT << 8);
+ break;
+ }
+ ahci_write_fis_d2h(p, slot, cfis, p->tfd);
+ break;
+ }
+ case ATA_SET_MULTI:
+ if (cfis[12] != 0 &&
+ (cfis[12] > 128 || (cfis[12] & (cfis[12] - 1)))) {
+ p->tfd = ATA_S_ERROR | ATA_S_READY;
+ p->tfd |= (ATA_ERROR_ABORT << 8);
+ } else {
+ p->mult_sectors = cfis[12];
+ p->tfd = ATA_S_DSC | ATA_S_READY;
+ }
+ ahci_write_fis_d2h(p, slot, cfis, p->tfd);
+ break;
+ case ATA_READ:
+ case ATA_WRITE:
+ case ATA_READ48:
+ case ATA_WRITE48:
+ case ATA_READ_MUL:
+ case ATA_WRITE_MUL:
+ case ATA_READ_MUL48:
+ case ATA_WRITE_MUL48:
+ case ATA_READ_DMA:
+ case ATA_WRITE_DMA:
+ case ATA_READ_DMA48:
+ case ATA_WRITE_DMA48:
+ case ATA_READ_FPDMA_QUEUED:
+ case ATA_WRITE_FPDMA_QUEUED:
+ ahci_handle_rw(p, slot, cfis, 0);
+ break;
+ case ATA_FLUSHCACHE:
+ case ATA_FLUSHCACHE48:
+ ahci_handle_flush(p, slot, cfis);
+ break;
+ case ATA_DATA_SET_MANAGEMENT:
+ if (cfis[11] == 0 && cfis[3] == ATA_DSM_TRIM &&
+ cfis[13] == 0 && cfis[12] == 1) {
+ ahci_handle_dsm_trim(p, slot, cfis, 0);
+ break;
+ }
+ ahci_write_fis_d2h(p, slot, cfis,
+ (ATA_E_ABORT << 8) | ATA_S_READY | ATA_S_ERROR);
+ break;
+ case ATA_SEND_FPDMA_QUEUED:
+ if ((cfis[13] & 0x1f) == ATA_SFPDMA_DSM &&
+ cfis[17] == 0 && cfis[16] == ATA_DSM_TRIM &&
+ cfis[11] == 0 && cfis[13] == 1) {
+ ahci_handle_dsm_trim(p, slot, cfis, 0);
+ break;
+ }
+ ahci_write_fis_d2h(p, slot, cfis,
+ (ATA_E_ABORT << 8) | ATA_S_READY | ATA_S_ERROR);
+ break;
+ case ATA_READ_LOG_EXT:
+ case ATA_READ_LOG_DMA_EXT:
+ ahci_handle_read_log(p, slot, cfis);
+ break;
+ case ATA_SECURITY_FREEZE_LOCK:
+ case ATA_SMART_CMD:
+ case ATA_NOP:
+ ahci_write_fis_d2h(p, slot, cfis,
+ (ATA_E_ABORT << 8) | ATA_S_READY | ATA_S_ERROR);
+ break;
+ case ATA_CHECK_POWER_MODE:
+ cfis[12] = 0xff; /* always on */
+ ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC);
+ break;
+ case ATA_STANDBY_CMD:
+ case ATA_STANDBY_IMMEDIATE:
+ case ATA_IDLE_CMD:
+ case ATA_IDLE_IMMEDIATE:
+ case ATA_SLEEP:
+ case ATA_READ_VERIFY:
+ case ATA_READ_VERIFY48:
+ ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC);
+ break;
+ case ATA_ATAPI_IDENTIFY:
+ handle_atapi_identify(p, slot, cfis);
+ break;
+ case ATA_PACKET_CMD:
+ if (!p->atapi) {
+ ahci_write_fis_d2h(p, slot, cfis,
+ (ATA_E_ABORT << 8) | ATA_S_READY | ATA_S_ERROR);
+ } else
+ handle_packet_cmd(p, slot, cfis);
+ break;
+ default:
+ WPRINTF("Unsupported cmd:%02x\n", cfis[2]);
+ ahci_write_fis_d2h(p, slot, cfis,
+ (ATA_E_ABORT << 8) | ATA_S_READY | ATA_S_ERROR);
+ break;
+ }
+}
+
+static void
+ahci_handle_slot(struct ahci_port *p, int slot)
+{
+ struct ahci_cmd_hdr *hdr;
+ struct ahci_prdt_entry *prdt;
+ struct pci_ahci_softc *sc;
+ uint8_t *cfis;
+ int cfl;
+
+ sc = p->pr_sc;
+ hdr = (struct ahci_cmd_hdr *)(p->cmd_lst + slot * AHCI_CL_SIZE);
+ cfl = (hdr->flags & 0x1f) * 4;
+ cfis = paddr_guest2host(ahci_ctx(sc), hdr->ctba,
+ 0x80 + hdr->prdtl * sizeof(struct ahci_prdt_entry));
+ prdt = (struct ahci_prdt_entry *)(cfis + 0x80);
+
+#ifdef AHCI_DEBUG
+ DPRINTF("\ncfis:");
+ for (i = 0; i < cfl; i++) {
+ if (i % 10 == 0)
+ DPRINTF("\n");
+ DPRINTF("%02x ", cfis[i]);
+ }
+ DPRINTF("\n");
+
+ for (i = 0; i < hdr->prdtl; i++) {
+ DPRINTF("%d@%08"PRIx64"\n", prdt->dbc & 0x3fffff, prdt->dba);
+ prdt++;
+ }
+#endif
+
+ if (cfis[0] != FIS_TYPE_REGH2D) {
+ WPRINTF("Not a H2D FIS:%02x\n", cfis[0]);
+ return;
+ }
+
+ if (cfis[1] & 0x80) {
+ ahci_handle_cmd(p, slot, cfis);
+ } else {
+ if (cfis[15] & (1 << 2))
+ p->reset = 1;
+ else if (p->reset) {
+ p->reset = 0;
+ ahci_port_reset(p);
+ }
+ p->ci &= ~(1 << slot);
+ }
+}
+
+static void
+ahci_handle_port(struct ahci_port *p)
+{
+
+ if (!(p->cmd & AHCI_P_CMD_ST))
+ return;
+
+ /*
+ * Search for any new commands to issue ignoring those that
+ * are already in-flight. Stop if device is busy or in error.
+ */
+ for (; (p->ci & ~p->pending) != 0; p->ccs = ((p->ccs + 1) & 31)) {
+ if ((p->tfd & (ATA_S_BUSY | ATA_S_DRQ)) != 0)
+ break;
+ if (p->waitforclear)
+ break;
+ if ((p->ci & ~p->pending & (1 << p->ccs)) != 0) {
+ p->cmd &= ~AHCI_P_CMD_CCS_MASK;
+ p->cmd |= p->ccs << AHCI_P_CMD_CCS_SHIFT;
+ ahci_handle_slot(p, p->ccs);
+ }
+ }
+}
+
+/*
+ * blockif callback routine - this runs in the context of the blockif
+ * i/o thread, so the mutex needs to be acquired.
+ */
+static void
+ata_ioreq_cb(struct blockif_req *br, int err)
+{
+ struct ahci_cmd_hdr *hdr;
+ struct ahci_ioreq *aior;
+ struct ahci_port *p;
+ struct pci_ahci_softc *sc;
+ uint32_t tfd;
+ uint8_t *cfis;
+ int slot, ncq, dsm;
+
+ DPRINTF("%s %d\n", __func__, err);
+
+ ncq = dsm = 0;
+ aior = br->br_param;
+ p = aior->io_pr;
+ cfis = aior->cfis;
+ slot = aior->slot;
+ sc = p->pr_sc;
+ hdr = (struct ahci_cmd_hdr *)(p->cmd_lst + slot * AHCI_CL_SIZE);
+
+ if (cfis[2] == ATA_WRITE_FPDMA_QUEUED ||
+ cfis[2] == ATA_READ_FPDMA_QUEUED ||
+ cfis[2] == ATA_SEND_FPDMA_QUEUED)
+ ncq = 1;
+ if (cfis[2] == ATA_DATA_SET_MANAGEMENT ||
+ (cfis[2] == ATA_SEND_FPDMA_QUEUED &&
+ (cfis[13] & 0x1f) == ATA_SFPDMA_DSM))
+ dsm = 1;
+
+ pthread_mutex_lock(&sc->mtx);
+
+ /*
+ * Delete the blockif request from the busy list
+ */
+ TAILQ_REMOVE(&p->iobhd, aior, io_blist);
+
+ /*
+ * Move the blockif request back to the free list
+ */
+ STAILQ_INSERT_TAIL(&p->iofhd, aior, io_flist);
+
+ if (!err)
+ hdr->prdbc = aior->done;
+
+ if (!err && aior->more) {
+ if (dsm)
+ ahci_handle_dsm_trim(p, slot, cfis, aior->done);
+ else
+ ahci_handle_rw(p, slot, cfis, aior->done);
+ goto out;
+ }
+
+ if (!err)
+ tfd = ATA_S_READY | ATA_S_DSC;
+ else
+ tfd = (ATA_E_ABORT << 8) | ATA_S_READY | ATA_S_ERROR;
+ if (ncq)
+ ahci_write_fis_sdb(p, slot, cfis, tfd);
+ else
+ ahci_write_fis_d2h(p, slot, cfis, tfd);
+
+ /*
+ * This command is now complete.
+ */
+ p->pending &= ~(1 << slot);
+
+ ahci_check_stopped(p);
+ ahci_handle_port(p);
+out:
+ pthread_mutex_unlock(&sc->mtx);
+ DPRINTF("%s exit\n", __func__);
+}
+
+static void
+atapi_ioreq_cb(struct blockif_req *br, int err)
+{
+ struct ahci_cmd_hdr *hdr;
+ struct ahci_ioreq *aior;
+ struct ahci_port *p;
+ struct pci_ahci_softc *sc;
+ uint8_t *cfis;
+ uint32_t tfd;
+ int slot;
+
+ DPRINTF("%s %d\n", __func__, err);
+
+ aior = br->br_param;
+ p = aior->io_pr;
+ cfis = aior->cfis;
+ slot = aior->slot;
+ sc = p->pr_sc;
+ hdr = (struct ahci_cmd_hdr *)(p->cmd_lst + aior->slot * AHCI_CL_SIZE);
+
+ pthread_mutex_lock(&sc->mtx);
+
+ /*
+ * Delete the blockif request from the busy list
+ */
+ TAILQ_REMOVE(&p->iobhd, aior, io_blist);
+
+ /*
+ * Move the blockif request back to the free list
+ */
+ STAILQ_INSERT_TAIL(&p->iofhd, aior, io_flist);
+
+ if (!err)
+ hdr->prdbc = aior->done;
+
+ if (!err && aior->more) {
+ atapi_read(p, slot, cfis, aior->done);
+ goto out;
+ }
+
+ if (!err) {
+ tfd = ATA_S_READY | ATA_S_DSC;
+ } else {
+ p->sense_key = ATA_SENSE_ILLEGAL_REQUEST;
+ p->asc = 0x21;
+ tfd = (p->sense_key << 12) | ATA_S_READY | ATA_S_ERROR;
+ }
+ cfis[4] = (cfis[4] & ~7) | ATA_I_CMD | ATA_I_IN;
+ ahci_write_fis_d2h(p, slot, cfis, tfd);
+
+ /*
+ * This command is now complete.
+ */
+ p->pending &= ~(1 << slot);
+
+ ahci_check_stopped(p);
+ ahci_handle_port(p);
+out:
+ pthread_mutex_unlock(&sc->mtx);
+ DPRINTF("%s exit\n", __func__);
+}
+
+static void
+pci_ahci_ioreq_init(struct ahci_port *pr)
+{
+ struct ahci_ioreq *vr;
+ int i;
+
+ pr->ioqsz = blockif_queuesz(pr->bctx);
+ pr->ioreq = calloc(pr->ioqsz, sizeof(struct ahci_ioreq));
+ STAILQ_INIT(&pr->iofhd);
+
+ /*
+ * Add all i/o request entries to the free queue
+ */
+ for (i = 0; i < pr->ioqsz; i++) {
+ vr = &pr->ioreq[i];
+ vr->io_pr = pr;
+ if (!pr->atapi)
+ vr->io_req.br_callback = ata_ioreq_cb;
+ else
+ vr->io_req.br_callback = atapi_ioreq_cb;
+ vr->io_req.br_param = vr;
+ STAILQ_INSERT_TAIL(&pr->iofhd, vr, io_flist);
+ }
+
+ TAILQ_INIT(&pr->iobhd);
+}
+
+static void
+pci_ahci_port_write(struct pci_ahci_softc *sc, uint64_t offset, uint64_t value)
+{
+ int port = (offset - AHCI_OFFSET) / AHCI_STEP;
+ offset = (offset - AHCI_OFFSET) % AHCI_STEP;
+ struct ahci_port *p = &sc->port[port];
+
+ DPRINTF("pci_ahci_port %d: write offset 0x%"PRIx64" value 0x%"PRIx64"\n",
+ port, offset, value);
+
+ switch (offset) {
+ case AHCI_P_CLB:
+ p->clb = value;
+ break;
+ case AHCI_P_CLBU:
+ p->clbu = value;
+ break;
+ case AHCI_P_FB:
+ p->fb = value;
+ break;
+ case AHCI_P_FBU:
+ p->fbu = value;
+ break;
+ case AHCI_P_IS:
+ p->is &= ~value;
+ break;
+ case AHCI_P_IE:
+ p->ie = value & 0xFDC000FF;
+ ahci_generate_intr(sc);
+ break;
+ case AHCI_P_CMD:
+ {
+ p->cmd &= ~(AHCI_P_CMD_ST | AHCI_P_CMD_SUD | AHCI_P_CMD_POD |
+ AHCI_P_CMD_CLO | AHCI_P_CMD_FRE | AHCI_P_CMD_APSTE |
+ AHCI_P_CMD_ATAPI | AHCI_P_CMD_DLAE | AHCI_P_CMD_ALPE |
+ AHCI_P_CMD_ASP | AHCI_P_CMD_ICC_MASK);
+ p->cmd |= (AHCI_P_CMD_ST | AHCI_P_CMD_SUD | AHCI_P_CMD_POD |
+ AHCI_P_CMD_CLO | AHCI_P_CMD_FRE | AHCI_P_CMD_APSTE |
+ AHCI_P_CMD_ATAPI | AHCI_P_CMD_DLAE | AHCI_P_CMD_ALPE |
+ AHCI_P_CMD_ASP | AHCI_P_CMD_ICC_MASK) & value;
+
+ if (!(value & AHCI_P_CMD_ST)) {
+ ahci_port_stop(p);
+ } else {
+ uint64_t clb;
+
+ p->cmd |= AHCI_P_CMD_CR;
+ clb = (uint64_t)p->clbu << 32 | p->clb;
+ p->cmd_lst = paddr_guest2host(ahci_ctx(sc), clb,
+ AHCI_CL_SIZE * AHCI_MAX_SLOTS);
+ }
+
+ if (value & AHCI_P_CMD_FRE) {
+ uint64_t fb;
+
+ p->cmd |= AHCI_P_CMD_FR;
+ fb = (uint64_t)p->fbu << 32 | p->fb;
+ /* we don't support FBSCP, so rfis size is 256Bytes */
+ p->rfis = paddr_guest2host(ahci_ctx(sc), fb, 256);
+ } else {
+ p->cmd &= ~AHCI_P_CMD_FR;
+ }
+
+ if (value & AHCI_P_CMD_CLO) {
+ p->tfd &= ~(ATA_S_BUSY | ATA_S_DRQ);
+ p->cmd &= ~AHCI_P_CMD_CLO;
+ }
+
+ if (value & AHCI_P_CMD_ICC_MASK) {
+ p->cmd &= ~AHCI_P_CMD_ICC_MASK;
+ }
+
+ ahci_handle_port(p);
+ break;
+ }
+ case AHCI_P_TFD:
+ case AHCI_P_SIG:
+ case AHCI_P_SSTS:
+ WPRINTF("pci_ahci_port: read only registers 0x%"PRIx64"\n", offset);
+ break;
+ case AHCI_P_SCTL:
+ p->sctl = value;
+ if (!(p->cmd & AHCI_P_CMD_ST)) {
+ if (value & ATA_SC_DET_RESET)
+ ahci_port_reset(p);
+ }
+ break;
+ case AHCI_P_SERR:
+ p->serr &= ~value;
+ break;
+ case AHCI_P_SACT:
+ p->sact |= value;
+ break;
+ case AHCI_P_CI:
+ p->ci |= value;
+ ahci_handle_port(p);
+ break;
+ case AHCI_P_SNTF:
+ case AHCI_P_FBS:
+ default:
+ break;
+ }
+}
+
+static void
+pci_ahci_host_write(struct pci_ahci_softc *sc, uint64_t offset, uint64_t value)
+{
+ DPRINTF("pci_ahci_host: write offset 0x%"PRIx64" value 0x%"PRIx64"\n",
+ offset, value);
+
+ switch (offset) {
+ case AHCI_CAP:
+ case AHCI_PI:
+ case AHCI_VS:
+ case AHCI_CAP2:
+ DPRINTF("pci_ahci_host: read only registers 0x%"PRIx64"\n", offset);
+ break;
+ case AHCI_GHC:
+ if (value & AHCI_GHC_HR)
+ ahci_reset(sc);
+ else if (value & AHCI_GHC_IE) {
+ sc->ghc |= AHCI_GHC_IE;
+ ahci_generate_intr(sc);
+ }
+ break;
+ case AHCI_IS:
+ sc->is &= ~value;
+ ahci_generate_intr(sc);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+pci_ahci_write(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
+ int baridx, uint64_t offset, int size, uint64_t value)
+{
+ struct pci_ahci_softc *sc = pi->pi_arg;
+
+ assert(baridx == 5);
+ assert((offset % 4) == 0 && size == 4);
+
+ pthread_mutex_lock(&sc->mtx);
+
+ if (offset < AHCI_OFFSET)
+ pci_ahci_host_write(sc, offset, value);
+ else if (offset < AHCI_OFFSET + sc->ports * AHCI_STEP)
+ pci_ahci_port_write(sc, offset, value);
+ else
+ WPRINTF("pci_ahci: unknown i/o write offset 0x%"PRIx64"\n", offset);
+
+ pthread_mutex_unlock(&sc->mtx);
+}
+
+static uint64_t
+pci_ahci_host_read(struct pci_ahci_softc *sc, uint64_t offset)
+{
+ uint32_t value;
+
+ switch (offset) {
+ case AHCI_CAP:
+ case AHCI_GHC:
+ case AHCI_IS:
+ case AHCI_PI:
+ case AHCI_VS:
+ case AHCI_CCCC:
+ case AHCI_CCCP:
+ case AHCI_EM_LOC:
+ case AHCI_EM_CTL:
+ case AHCI_CAP2:
+ {
+ uint32_t *p = &sc->cap;
+ p += (offset - AHCI_CAP) / sizeof(uint32_t);
+ value = *p;
+ break;
+ }
+ default:
+ value = 0;
+ break;
+ }
+ DPRINTF("pci_ahci_host: read offset 0x%"PRIx64" value 0x%x\n",
+ offset, value);
+
+ return (value);
+}
+
+static uint64_t
+pci_ahci_port_read(struct pci_ahci_softc *sc, uint64_t offset)
+{
+ uint32_t value;
+ int port = (offset - AHCI_OFFSET) / AHCI_STEP;
+ offset = (offset - AHCI_OFFSET) % AHCI_STEP;
+
+ switch (offset) {
+ case AHCI_P_CLB:
+ case AHCI_P_CLBU:
+ case AHCI_P_FB:
+ case AHCI_P_FBU:
+ case AHCI_P_IS:
+ case AHCI_P_IE:
+ case AHCI_P_CMD:
+ case AHCI_P_TFD:
+ case AHCI_P_SIG:
+ case AHCI_P_SSTS:
+ case AHCI_P_SCTL:
+ case AHCI_P_SERR:
+ case AHCI_P_SACT:
+ case AHCI_P_CI:
+ case AHCI_P_SNTF:
+ case AHCI_P_FBS:
+ {
+ uint32_t *p= &sc->port[port].clb;
+ p += (offset - AHCI_P_CLB) / sizeof(uint32_t);
+ value = *p;
+ break;
+ }
+ default:
+ value = 0;
+ break;
+ }
+
+ DPRINTF("pci_ahci_port %d: read offset 0x%"PRIx64" value 0x%x\n",
+ port, offset, value);
+
+ return value;
+}
+
+static uint64_t
+pci_ahci_read(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int baridx,
+ uint64_t regoff, int size)
+{
+ struct pci_ahci_softc *sc = pi->pi_arg;
+ uint64_t offset;
+ uint32_t value;
+
+ assert(baridx == 5);
+ assert(size == 1 || size == 2 || size == 4);
+ assert((regoff & (size - 1)) == 0);
+
+ pthread_mutex_lock(&sc->mtx);
+
+ offset = regoff & ~0x3; /* round down to a multiple of 4 bytes */
+ if (offset < AHCI_OFFSET)
+ value = pci_ahci_host_read(sc, offset);
+ else if (offset < AHCI_OFFSET + sc->ports * AHCI_STEP)
+ value = pci_ahci_port_read(sc, offset);
+ else {
+ value = 0;
+ WPRINTF("pci_ahci: unknown i/o read offset 0x%"PRIx64"\n",
+ regoff);
+ }
+ value >>= 8 * (regoff & 0x3);
+
+ pthread_mutex_unlock(&sc->mtx);
+
+ return (value);
+}
+
+static int
+pci_ahci_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts, int atapi)
+{
+ char bident[sizeof("XX:X:X")];
+ struct blockif_ctxt *bctxt;
+ struct pci_ahci_softc *sc;
+ int ret, slots;
+ MD5_CTX mdctx;
+ u_char digest[16];
+
+ ret = 0;
+
+ if (opts == NULL) {
+ fprintf(stderr, "pci_ahci: backing device required\n");
+ return (1);
+ }
+
+#ifdef AHCI_DEBUG
+ dbg = fopen("/tmp/log", "w+");
+#endif
+
+ sc = calloc(1, sizeof(struct pci_ahci_softc));
+ pi->pi_arg = sc;
+ sc->asc_pi = pi;
+ sc->ports = MAX_PORTS;
+
+ /*
+ * Only use port 0 for a backing device. All other ports will be
+ * marked as unused
+ */
+ sc->port[0].atapi = atapi;
+
+ /*
+ * Attempt to open the backing image. Use the PCI
+ * slot/func for the identifier string.
+ */
+ snprintf(bident, sizeof(bident), "%d:%d", pi->pi_slot, pi->pi_func);
+ bctxt = blockif_open(opts, bident);
+ if (bctxt == NULL) {
+ ret = 1;
+ goto open_fail;
+ }
+ sc->port[0].bctx = bctxt;
+ sc->port[0].pr_sc = sc;
+
+ /*
+ * Create an identifier for the backing file. Use parts of the
+ * md5 sum of the filename
+ */
+ MD5Init(&mdctx);
+ MD5Update(&mdctx, opts, strlen(opts));
+ MD5Final(digest, &mdctx);
+ sprintf(sc->port[0].ident, "BHYVE-%02X%02X-%02X%02X-%02X%02X",
+ digest[0], digest[1], digest[2], digest[3], digest[4], digest[5]);
+
+ /*
+ * Allocate blockif request structures and add them
+ * to the free list
+ */
+ pci_ahci_ioreq_init(&sc->port[0]);
+
+ pthread_mutex_init(&sc->mtx, NULL);
+
+ /* Intel ICH8 AHCI */
+ slots = sc->port[0].ioqsz;
+ if (slots > 32)
+ slots = 32;
+ --slots;
+ sc->cap = AHCI_CAP_64BIT | AHCI_CAP_SNCQ | AHCI_CAP_SSNTF |
+ AHCI_CAP_SMPS | AHCI_CAP_SSS | AHCI_CAP_SALP |
+ AHCI_CAP_SAL | AHCI_CAP_SCLO | (0x3 << AHCI_CAP_ISS_SHIFT)|
+ AHCI_CAP_PMD | AHCI_CAP_SSC | AHCI_CAP_PSC |
+ (slots << AHCI_CAP_NCS_SHIFT) | AHCI_CAP_SXS | (sc->ports - 1);
+
+ /* Only port 0 implemented */
+ sc->pi = 1;
+ sc->vs = 0x10300;
+ sc->cap2 = AHCI_CAP2_APST;
+ ahci_reset(sc);
+
+ pci_set_cfgdata16(pi, PCIR_DEVICE, 0x2821);
+ pci_set_cfgdata16(pi, PCIR_VENDOR, 0x8086);
+ pci_set_cfgdata8(pi, PCIR_CLASS, PCIC_STORAGE);
+ pci_set_cfgdata8(pi, PCIR_SUBCLASS, PCIS_STORAGE_SATA);
+ pci_set_cfgdata8(pi, PCIR_PROGIF, PCIP_STORAGE_SATA_AHCI_1_0);
+ pci_emul_add_msicap(pi, 1);
+ pci_emul_alloc_bar(pi, 5, PCIBAR_MEM32,
+ AHCI_OFFSET + sc->ports * AHCI_STEP);
+
+ pci_lintr_request(pi);
+
+open_fail:
+ if (ret) {
+ if (sc->port[0].bctx != NULL)
+ blockif_close(sc->port[0].bctx);
+ free(sc);
+ }
+
+ return (ret);
+}
+
+static int
+pci_ahci_hd_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts)
+{
+
+ return (pci_ahci_init(ctx, pi, opts, 0));
+}
+
+static int
+pci_ahci_atapi_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts)
+{
+
+ return (pci_ahci_init(ctx, pi, opts, 1));
+}
+
+/*
+ * Use separate emulation names to distinguish drive and atapi devices
+ */
+struct pci_devemu pci_de_ahci_hd = {
+ .pe_emu = "ahci-hd",
+ .pe_init = pci_ahci_hd_init,
+ .pe_barwrite = pci_ahci_write,
+ .pe_barread = pci_ahci_read
+};
+PCI_EMUL_SET(pci_de_ahci_hd);
+
+struct pci_devemu pci_de_ahci_cd = {
+ .pe_emu = "ahci-cd",
+ .pe_init = pci_ahci_atapi_init,
+ .pe_barwrite = pci_ahci_write,
+ .pe_barread = pci_ahci_read
+};
+PCI_EMUL_SET(pci_de_ahci_cd);
diff --git a/usr.sbin/bhyve/pci_emul.c b/usr.sbin/bhyve/pci_emul.c
new file mode 100644
index 0000000..c6763c9
--- /dev/null
+++ b/usr.sbin/bhyve/pci_emul.c
@@ -0,0 +1,2107 @@
+/*-
+ * Copyright (c) 2011 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <sys/param.h>
+#include <sys/linker_set.h>
+#include <sys/errno.h>
+
+#include <ctype.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <assert.h>
+#include <stdbool.h>
+
+#include <machine/vmm.h>
+#include <vmmapi.h>
+
+#include "acpi.h"
+#include "bhyverun.h"
+#include "inout.h"
+#include "ioapic.h"
+#include "mem.h"
+#include "pci_emul.h"
+#include "pci_irq.h"
+#include "pci_lpc.h"
+
+#define CONF1_ADDR_PORT 0x0cf8
+#define CONF1_DATA_PORT 0x0cfc
+
+#define CONF1_ENABLE 0x80000000ul
+
+#define MAXBUSES (PCI_BUSMAX + 1)
+#define MAXSLOTS (PCI_SLOTMAX + 1)
+#define MAXFUNCS (PCI_FUNCMAX + 1)
+
+struct funcinfo {
+ char *fi_name;
+ char *fi_param;
+ struct pci_devinst *fi_devi;
+};
+
+struct intxinfo {
+ int ii_count;
+ int ii_pirq_pin;
+ int ii_ioapic_irq;
+};
+
+struct slotinfo {
+ struct intxinfo si_intpins[4];
+ struct funcinfo si_funcs[MAXFUNCS];
+};
+
+struct businfo {
+ uint16_t iobase, iolimit; /* I/O window */
+ uint32_t membase32, memlimit32; /* mmio window below 4GB */
+ uint64_t membase64, memlimit64; /* mmio window above 4GB */
+ struct slotinfo slotinfo[MAXSLOTS];
+};
+
+static struct businfo *pci_businfo[MAXBUSES];
+
+SET_DECLARE(pci_devemu_set, struct pci_devemu);
+
+static uint64_t pci_emul_iobase;
+static uint64_t pci_emul_membase32;
+static uint64_t pci_emul_membase64;
+
+#define PCI_EMUL_IOBASE 0x2000
+#define PCI_EMUL_IOLIMIT 0x10000
+
+#define PCI_EMUL_ECFG_BASE 0xE0000000 /* 3.5GB */
+#define PCI_EMUL_ECFG_SIZE (MAXBUSES * 1024 * 1024) /* 1MB per bus */
+SYSRES_MEM(PCI_EMUL_ECFG_BASE, PCI_EMUL_ECFG_SIZE);
+
+#define PCI_EMUL_MEMLIMIT32 PCI_EMUL_ECFG_BASE
+
+#define PCI_EMUL_MEMBASE64 0xD000000000UL
+#define PCI_EMUL_MEMLIMIT64 0xFD00000000UL
+
+static struct pci_devemu *pci_emul_finddev(char *name);
+static void pci_lintr_route(struct pci_devinst *pi);
+static void pci_lintr_update(struct pci_devinst *pi);
+static void pci_cfgrw(struct vmctx *ctx, int vcpu, int in, int bus, int slot,
+ int func, int coff, int bytes, uint32_t *val);
+
+static __inline void
+CFGWRITE(struct pci_devinst *pi, int coff, uint32_t val, int bytes)
+{
+
+ if (bytes == 1)
+ pci_set_cfgdata8(pi, coff, val);
+ else if (bytes == 2)
+ pci_set_cfgdata16(pi, coff, val);
+ else
+ pci_set_cfgdata32(pi, coff, val);
+}
+
+static __inline uint32_t
+CFGREAD(struct pci_devinst *pi, int coff, int bytes)
+{
+
+ if (bytes == 1)
+ return (pci_get_cfgdata8(pi, coff));
+ else if (bytes == 2)
+ return (pci_get_cfgdata16(pi, coff));
+ else
+ return (pci_get_cfgdata32(pi, coff));
+}
+
+/*
+ * I/O access
+ */
+
+/*
+ * Slot options are in the form:
+ *
+ * <bus>:<slot>:<func>,<emul>[,<config>]
+ * <slot>[:<func>],<emul>[,<config>]
+ *
+ * slot is 0..31
+ * func is 0..7
+ * emul is a string describing the type of PCI device e.g. virtio-net
+ * config is an optional string, depending on the device, that can be
+ * used for configuration.
+ * Examples are:
+ * 1,virtio-net,tap0
+ * 3:0,dummy
+ */
+static void
+pci_parse_slot_usage(char *aopt)
+{
+
+ fprintf(stderr, "Invalid PCI slot info field \"%s\"\n", aopt);
+}
+
+int
+pci_parse_slot(char *opt)
+{
+ struct businfo *bi;
+ struct slotinfo *si;
+ char *emul, *config, *str, *cp;
+ int error, bnum, snum, fnum;
+
+ error = -1;
+ str = strdup(opt);
+
+ emul = config = NULL;
+ if ((cp = strchr(str, ',')) != NULL) {
+ *cp = '\0';
+ emul = cp + 1;
+ if ((cp = strchr(emul, ',')) != NULL) {
+ *cp = '\0';
+ config = cp + 1;
+ }
+ } else {
+ pci_parse_slot_usage(opt);
+ goto done;
+ }
+
+ /* <bus>:<slot>:<func> */
+ if (sscanf(str, "%d:%d:%d", &bnum, &snum, &fnum) != 3) {
+ bnum = 0;
+ /* <slot>:<func> */
+ if (sscanf(str, "%d:%d", &snum, &fnum) != 2) {
+ fnum = 0;
+ /* <slot> */
+ if (sscanf(str, "%d", &snum) != 1) {
+ snum = -1;
+ }
+ }
+ }
+
+ if (bnum < 0 || bnum >= MAXBUSES || snum < 0 || snum >= MAXSLOTS ||
+ fnum < 0 || fnum >= MAXFUNCS) {
+ pci_parse_slot_usage(opt);
+ goto done;
+ }
+
+ if (pci_businfo[bnum] == NULL)
+ pci_businfo[bnum] = calloc(1, sizeof(struct businfo));
+
+ bi = pci_businfo[bnum];
+ si = &bi->slotinfo[snum];
+
+ if (si->si_funcs[fnum].fi_name != NULL) {
+ fprintf(stderr, "pci slot %d:%d already occupied!\n",
+ snum, fnum);
+ goto done;
+ }
+
+ if (pci_emul_finddev(emul) == NULL) {
+ fprintf(stderr, "pci slot %d:%d: unknown device \"%s\"\n",
+ snum, fnum, emul);
+ goto done;
+ }
+
+ error = 0;
+ si->si_funcs[fnum].fi_name = emul;
+ si->si_funcs[fnum].fi_param = config;
+
+done:
+ if (error)
+ free(str);
+
+ return (error);
+}
+
+static int
+pci_valid_pba_offset(struct pci_devinst *pi, uint64_t offset)
+{
+
+ if (offset < pi->pi_msix.pba_offset)
+ return (0);
+
+ if (offset >= pi->pi_msix.pba_offset + pi->pi_msix.pba_size) {
+ return (0);
+ }
+
+ return (1);
+}
+
+int
+pci_emul_msix_twrite(struct pci_devinst *pi, uint64_t offset, int size,
+ uint64_t value)
+{
+ int msix_entry_offset;
+ int tab_index;
+ char *dest;
+
+ /* support only 4 or 8 byte writes */
+ if (size != 4 && size != 8)
+ return (-1);
+
+ /*
+ * Return if table index is beyond what device supports
+ */
+ tab_index = offset / MSIX_TABLE_ENTRY_SIZE;
+ if (tab_index >= pi->pi_msix.table_count)
+ return (-1);
+
+ msix_entry_offset = offset % MSIX_TABLE_ENTRY_SIZE;
+
+ /* support only aligned writes */
+ if ((msix_entry_offset % size) != 0)
+ return (-1);
+
+ dest = (char *)(pi->pi_msix.table + tab_index);
+ dest += msix_entry_offset;
+
+ if (size == 4)
+ *((uint32_t *)dest) = value;
+ else
+ *((uint64_t *)dest) = value;
+
+ return (0);
+}
+
+uint64_t
+pci_emul_msix_tread(struct pci_devinst *pi, uint64_t offset, int size)
+{
+ char *dest;
+ int msix_entry_offset;
+ int tab_index;
+ uint64_t retval = ~0;
+
+ /*
+ * The PCI standard only allows 4 and 8 byte accesses to the MSI-X
+ * table but we also allow 1 byte access to accommodate reads from
+ * ddb.
+ */
+ if (size != 1 && size != 4 && size != 8)
+ return (retval);
+
+ msix_entry_offset = offset % MSIX_TABLE_ENTRY_SIZE;
+
+ /* support only aligned reads */
+ if ((msix_entry_offset % size) != 0) {
+ return (retval);
+ }
+
+ tab_index = offset / MSIX_TABLE_ENTRY_SIZE;
+
+ if (tab_index < pi->pi_msix.table_count) {
+ /* valid MSI-X Table access */
+ dest = (char *)(pi->pi_msix.table + tab_index);
+ dest += msix_entry_offset;
+
+ if (size == 1)
+ retval = *((uint8_t *)dest);
+ else if (size == 4)
+ retval = *((uint32_t *)dest);
+ else
+ retval = *((uint64_t *)dest);
+ } else if (pci_valid_pba_offset(pi, offset)) {
+ /* return 0 for PBA access */
+ retval = 0;
+ }
+
+ return (retval);
+}
+
+int
+pci_msix_table_bar(struct pci_devinst *pi)
+{
+
+ if (pi->pi_msix.table != NULL)
+ return (pi->pi_msix.table_bar);
+ else
+ return (-1);
+}
+
+int
+pci_msix_pba_bar(struct pci_devinst *pi)
+{
+
+ if (pi->pi_msix.table != NULL)
+ return (pi->pi_msix.pba_bar);
+ else
+ return (-1);
+}
+
+static int
+pci_emul_io_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
+ uint32_t *eax, void *arg)
+{
+ struct pci_devinst *pdi = arg;
+ struct pci_devemu *pe = pdi->pi_d;
+ uint64_t offset;
+ int i;
+
+ for (i = 0; i <= PCI_BARMAX; i++) {
+ if (pdi->pi_bar[i].type == PCIBAR_IO &&
+ port >= pdi->pi_bar[i].addr &&
+ port + bytes <= pdi->pi_bar[i].addr + pdi->pi_bar[i].size) {
+ offset = port - pdi->pi_bar[i].addr;
+ if (in)
+ *eax = (*pe->pe_barread)(ctx, vcpu, pdi, i,
+ offset, bytes);
+ else
+ (*pe->pe_barwrite)(ctx, vcpu, pdi, i, offset,
+ bytes, *eax);
+ return (0);
+ }
+ }
+ return (-1);
+}
+
+static int
+pci_emul_mem_handler(struct vmctx *ctx, int vcpu, int dir, uint64_t addr,
+ int size, uint64_t *val, void *arg1, long arg2)
+{
+ struct pci_devinst *pdi = arg1;
+ struct pci_devemu *pe = pdi->pi_d;
+ uint64_t offset;
+ int bidx = (int) arg2;
+
+ assert(bidx <= PCI_BARMAX);
+ assert(pdi->pi_bar[bidx].type == PCIBAR_MEM32 ||
+ pdi->pi_bar[bidx].type == PCIBAR_MEM64);
+ assert(addr >= pdi->pi_bar[bidx].addr &&
+ addr + size <= pdi->pi_bar[bidx].addr + pdi->pi_bar[bidx].size);
+
+ offset = addr - pdi->pi_bar[bidx].addr;
+
+ if (dir == MEM_F_WRITE) {
+ if (size == 8) {
+ (*pe->pe_barwrite)(ctx, vcpu, pdi, bidx, offset,
+ 4, *val & 0xffffffff);
+ (*pe->pe_barwrite)(ctx, vcpu, pdi, bidx, offset + 4,
+ 4, *val >> 32);
+ } else {
+ (*pe->pe_barwrite)(ctx, vcpu, pdi, bidx, offset,
+ size, *val);
+ }
+ } else {
+ if (size == 8) {
+ *val = (*pe->pe_barread)(ctx, vcpu, pdi, bidx,
+ offset, 4);
+ *val |= (*pe->pe_barread)(ctx, vcpu, pdi, bidx,
+ offset + 4, 4) << 32;
+ } else {
+ *val = (*pe->pe_barread)(ctx, vcpu, pdi, bidx,
+ offset, size);
+ }
+ }
+
+ return (0);
+}
+
+
+static int
+pci_emul_alloc_resource(uint64_t *baseptr, uint64_t limit, uint64_t size,
+ uint64_t *addr)
+{
+ uint64_t base;
+
+ assert((size & (size - 1)) == 0); /* must be a power of 2 */
+
+ base = roundup2(*baseptr, size);
+
+ if (base + size <= limit) {
+ *addr = base;
+ *baseptr = base + size;
+ return (0);
+ } else
+ return (-1);
+}
+
+int
+pci_emul_alloc_bar(struct pci_devinst *pdi, int idx, enum pcibar_type type,
+ uint64_t size)
+{
+
+ return (pci_emul_alloc_pbar(pdi, idx, 0, type, size));
+}
+
+/*
+ * Register (or unregister) the MMIO or I/O region associated with the BAR
+ * register 'idx' of an emulated pci device.
+ */
+static void
+modify_bar_registration(struct pci_devinst *pi, int idx, int registration)
+{
+ int error;
+ struct inout_port iop;
+ struct mem_range mr;
+
+ switch (pi->pi_bar[idx].type) {
+ case PCIBAR_IO:
+ bzero(&iop, sizeof(struct inout_port));
+ iop.name = pi->pi_name;
+ iop.port = pi->pi_bar[idx].addr;
+ iop.size = pi->pi_bar[idx].size;
+ if (registration) {
+ iop.flags = IOPORT_F_INOUT;
+ iop.handler = pci_emul_io_handler;
+ iop.arg = pi;
+ error = register_inout(&iop);
+ } else
+ error = unregister_inout(&iop);
+ break;
+ case PCIBAR_MEM32:
+ case PCIBAR_MEM64:
+ bzero(&mr, sizeof(struct mem_range));
+ mr.name = pi->pi_name;
+ mr.base = pi->pi_bar[idx].addr;
+ mr.size = pi->pi_bar[idx].size;
+ if (registration) {
+ mr.flags = MEM_F_RW;
+ mr.handler = pci_emul_mem_handler;
+ mr.arg1 = pi;
+ mr.arg2 = idx;
+ error = register_mem(&mr);
+ } else
+ error = unregister_mem(&mr);
+ break;
+ default:
+ error = EINVAL;
+ break;
+ }
+ assert(error == 0);
+}
+
+static void
+unregister_bar(struct pci_devinst *pi, int idx)
+{
+
+ modify_bar_registration(pi, idx, 0);
+}
+
+static void
+register_bar(struct pci_devinst *pi, int idx)
+{
+
+ modify_bar_registration(pi, idx, 1);
+}
+
+/* Are we decoding i/o port accesses for the emulated pci device? */
+static int
+porten(struct pci_devinst *pi)
+{
+ uint16_t cmd;
+
+ cmd = pci_get_cfgdata16(pi, PCIR_COMMAND);
+
+ return (cmd & PCIM_CMD_PORTEN);
+}
+
+/* Are we decoding memory accesses for the emulated pci device? */
+static int
+memen(struct pci_devinst *pi)
+{
+ uint16_t cmd;
+
+ cmd = pci_get_cfgdata16(pi, PCIR_COMMAND);
+
+ return (cmd & PCIM_CMD_MEMEN);
+}
+
+/*
+ * Update the MMIO or I/O address that is decoded by the BAR register.
+ *
+ * If the pci device has enabled the address space decoding then intercept
+ * the address range decoded by the BAR register.
+ */
+static void
+update_bar_address(struct pci_devinst *pi, uint64_t addr, int idx, int type)
+{
+ int decode;
+
+ if (pi->pi_bar[idx].type == PCIBAR_IO)
+ decode = porten(pi);
+ else
+ decode = memen(pi);
+
+ if (decode)
+ unregister_bar(pi, idx);
+
+ switch (type) {
+ case PCIBAR_IO:
+ case PCIBAR_MEM32:
+ pi->pi_bar[idx].addr = addr;
+ break;
+ case PCIBAR_MEM64:
+ pi->pi_bar[idx].addr &= ~0xffffffffUL;
+ pi->pi_bar[idx].addr |= addr;
+ break;
+ case PCIBAR_MEMHI64:
+ pi->pi_bar[idx].addr &= 0xffffffff;
+ pi->pi_bar[idx].addr |= addr;
+ break;
+ default:
+ assert(0);
+ }
+
+ if (decode)
+ register_bar(pi, idx);
+}
+
+int
+pci_emul_alloc_pbar(struct pci_devinst *pdi, int idx, uint64_t hostbase,
+ enum pcibar_type type, uint64_t size)
+{
+ int error;
+ uint64_t *baseptr, limit, addr, mask, lobits, bar;
+
+ assert(idx >= 0 && idx <= PCI_BARMAX);
+
+ if ((size & (size - 1)) != 0)
+ size = 1UL << flsl(size); /* round up to a power of 2 */
+
+ /* Enforce minimum BAR sizes required by the PCI standard */
+ if (type == PCIBAR_IO) {
+ if (size < 4)
+ size = 4;
+ } else {
+ if (size < 16)
+ size = 16;
+ }
+
+ switch (type) {
+ case PCIBAR_NONE:
+ baseptr = NULL;
+ addr = mask = lobits = 0;
+ break;
+ case PCIBAR_IO:
+ baseptr = &pci_emul_iobase;
+ limit = PCI_EMUL_IOLIMIT;
+ mask = PCIM_BAR_IO_BASE;
+ lobits = PCIM_BAR_IO_SPACE;
+ break;
+ case PCIBAR_MEM64:
+ /*
+ * XXX
+ * Some drivers do not work well if the 64-bit BAR is allocated
+ * above 4GB. Allow for this by allocating small requests under
+ * 4GB unless then allocation size is larger than some arbitrary
+ * number (32MB currently).
+ */
+ if (size > 32 * 1024 * 1024) {
+ /*
+ * XXX special case for device requiring peer-peer DMA
+ */
+ if (size == 0x100000000UL)
+ baseptr = &hostbase;
+ else
+ baseptr = &pci_emul_membase64;
+ limit = PCI_EMUL_MEMLIMIT64;
+ mask = PCIM_BAR_MEM_BASE;
+ lobits = PCIM_BAR_MEM_SPACE | PCIM_BAR_MEM_64 |
+ PCIM_BAR_MEM_PREFETCH;
+ break;
+ } else {
+ baseptr = &pci_emul_membase32;
+ limit = PCI_EMUL_MEMLIMIT32;
+ mask = PCIM_BAR_MEM_BASE;
+ lobits = PCIM_BAR_MEM_SPACE | PCIM_BAR_MEM_64;
+ }
+ break;
+ case PCIBAR_MEM32:
+ baseptr = &pci_emul_membase32;
+ limit = PCI_EMUL_MEMLIMIT32;
+ mask = PCIM_BAR_MEM_BASE;
+ lobits = PCIM_BAR_MEM_SPACE | PCIM_BAR_MEM_32;
+ break;
+ default:
+ printf("pci_emul_alloc_base: invalid bar type %d\n", type);
+ assert(0);
+ }
+
+ if (baseptr != NULL) {
+ error = pci_emul_alloc_resource(baseptr, limit, size, &addr);
+ if (error != 0)
+ return (error);
+ }
+
+ pdi->pi_bar[idx].type = type;
+ pdi->pi_bar[idx].addr = addr;
+ pdi->pi_bar[idx].size = size;
+
+ /* Initialize the BAR register in config space */
+ bar = (addr & mask) | lobits;
+ pci_set_cfgdata32(pdi, PCIR_BAR(idx), bar);
+
+ if (type == PCIBAR_MEM64) {
+ assert(idx + 1 <= PCI_BARMAX);
+ pdi->pi_bar[idx + 1].type = PCIBAR_MEMHI64;
+ pci_set_cfgdata32(pdi, PCIR_BAR(idx + 1), bar >> 32);
+ }
+
+ register_bar(pdi, idx);
+
+ return (0);
+}
+
+#define CAP_START_OFFSET 0x40
+static int
+pci_emul_add_capability(struct pci_devinst *pi, u_char *capdata, int caplen)
+{
+ int i, capoff, reallen;
+ uint16_t sts;
+
+ assert(caplen > 0);
+
+ reallen = roundup2(caplen, 4); /* dword aligned */
+
+ sts = pci_get_cfgdata16(pi, PCIR_STATUS);
+ if ((sts & PCIM_STATUS_CAPPRESENT) == 0)
+ capoff = CAP_START_OFFSET;
+ else
+ capoff = pi->pi_capend + 1;
+
+ /* Check if we have enough space */
+ if (capoff + reallen > PCI_REGMAX + 1)
+ return (-1);
+
+ /* Set the previous capability pointer */
+ if ((sts & PCIM_STATUS_CAPPRESENT) == 0) {
+ pci_set_cfgdata8(pi, PCIR_CAP_PTR, capoff);
+ pci_set_cfgdata16(pi, PCIR_STATUS, sts|PCIM_STATUS_CAPPRESENT);
+ } else
+ pci_set_cfgdata8(pi, pi->pi_prevcap + 1, capoff);
+
+ /* Copy the capability */
+ for (i = 0; i < caplen; i++)
+ pci_set_cfgdata8(pi, capoff + i, capdata[i]);
+
+ /* Set the next capability pointer */
+ pci_set_cfgdata8(pi, capoff + 1, 0);
+
+ pi->pi_prevcap = capoff;
+ pi->pi_capend = capoff + reallen - 1;
+ return (0);
+}
+
+static struct pci_devemu *
+pci_emul_finddev(char *name)
+{
+ struct pci_devemu **pdpp, *pdp;
+
+ SET_FOREACH(pdpp, pci_devemu_set) {
+ pdp = *pdpp;
+ if (!strcmp(pdp->pe_emu, name)) {
+ return (pdp);
+ }
+ }
+
+ return (NULL);
+}
+
+static int
+pci_emul_init(struct vmctx *ctx, struct pci_devemu *pde, int bus, int slot,
+ int func, struct funcinfo *fi)
+{
+ struct pci_devinst *pdi;
+ int err;
+
+ pdi = calloc(1, sizeof(struct pci_devinst));
+
+ pdi->pi_vmctx = ctx;
+ pdi->pi_bus = bus;
+ pdi->pi_slot = slot;
+ pdi->pi_func = func;
+ pthread_mutex_init(&pdi->pi_lintr.lock, NULL);
+ pdi->pi_lintr.pin = 0;
+ pdi->pi_lintr.state = IDLE;
+ pdi->pi_lintr.pirq_pin = 0;
+ pdi->pi_lintr.ioapic_irq = 0;
+ pdi->pi_d = pde;
+ snprintf(pdi->pi_name, PI_NAMESZ, "%s-pci-%d", pde->pe_emu, slot);
+
+ /* Disable legacy interrupts */
+ pci_set_cfgdata8(pdi, PCIR_INTLINE, 255);
+ pci_set_cfgdata8(pdi, PCIR_INTPIN, 0);
+
+ pci_set_cfgdata8(pdi, PCIR_COMMAND,
+ PCIM_CMD_PORTEN | PCIM_CMD_MEMEN | PCIM_CMD_BUSMASTEREN);
+
+ err = (*pde->pe_init)(ctx, pdi, fi->fi_param);
+ if (err == 0)
+ fi->fi_devi = pdi;
+ else
+ free(pdi);
+
+ return (err);
+}
+
+void
+pci_populate_msicap(struct msicap *msicap, int msgnum, int nextptr)
+{
+ int mmc;
+
+ CTASSERT(sizeof(struct msicap) == 14);
+
+ /* Number of msi messages must be a power of 2 between 1 and 32 */
+ assert((msgnum & (msgnum - 1)) == 0 && msgnum >= 1 && msgnum <= 32);
+ mmc = ffs(msgnum) - 1;
+
+ bzero(msicap, sizeof(struct msicap));
+ msicap->capid = PCIY_MSI;
+ msicap->nextptr = nextptr;
+ msicap->msgctrl = PCIM_MSICTRL_64BIT | (mmc << 1);
+}
+
+int
+pci_emul_add_msicap(struct pci_devinst *pi, int msgnum)
+{
+ struct msicap msicap;
+
+ pci_populate_msicap(&msicap, msgnum, 0);
+
+ return (pci_emul_add_capability(pi, (u_char *)&msicap, sizeof(msicap)));
+}
+
+static void
+pci_populate_msixcap(struct msixcap *msixcap, int msgnum, int barnum,
+ uint32_t msix_tab_size)
+{
+ CTASSERT(sizeof(struct msixcap) == 12);
+
+ assert(msix_tab_size % 4096 == 0);
+
+ bzero(msixcap, sizeof(struct msixcap));
+ msixcap->capid = PCIY_MSIX;
+
+ /*
+ * Message Control Register, all fields set to
+ * zero except for the Table Size.
+ * Note: Table size N is encoded as N-1
+ */
+ msixcap->msgctrl = msgnum - 1;
+
+ /*
+ * MSI-X BAR setup:
+ * - MSI-X table start at offset 0
+ * - PBA table starts at a 4K aligned offset after the MSI-X table
+ */
+ msixcap->table_info = barnum & PCIM_MSIX_BIR_MASK;
+ msixcap->pba_info = msix_tab_size | (barnum & PCIM_MSIX_BIR_MASK);
+}
+
+static void
+pci_msix_table_init(struct pci_devinst *pi, int table_entries)
+{
+ int i, table_size;
+
+ assert(table_entries > 0);
+ assert(table_entries <= MAX_MSIX_TABLE_ENTRIES);
+
+ table_size = table_entries * MSIX_TABLE_ENTRY_SIZE;
+ pi->pi_msix.table = calloc(1, table_size);
+
+ /* set mask bit of vector control register */
+ for (i = 0; i < table_entries; i++)
+ pi->pi_msix.table[i].vector_control |= PCIM_MSIX_VCTRL_MASK;
+}
+
+int
+pci_emul_add_msixcap(struct pci_devinst *pi, int msgnum, int barnum)
+{
+ uint32_t tab_size;
+ struct msixcap msixcap;
+
+ assert(msgnum >= 1 && msgnum <= MAX_MSIX_TABLE_ENTRIES);
+ assert(barnum >= 0 && barnum <= PCIR_MAX_BAR_0);
+
+ tab_size = msgnum * MSIX_TABLE_ENTRY_SIZE;
+
+ /* Align table size to nearest 4K */
+ tab_size = roundup2(tab_size, 4096);
+
+ pi->pi_msix.table_bar = barnum;
+ pi->pi_msix.pba_bar = barnum;
+ pi->pi_msix.table_offset = 0;
+ pi->pi_msix.table_count = msgnum;
+ pi->pi_msix.pba_offset = tab_size;
+ pi->pi_msix.pba_size = PBA_SIZE(msgnum);
+
+ pci_msix_table_init(pi, msgnum);
+
+ pci_populate_msixcap(&msixcap, msgnum, barnum, tab_size);
+
+ /* allocate memory for MSI-X Table and PBA */
+ pci_emul_alloc_bar(pi, barnum, PCIBAR_MEM32,
+ tab_size + pi->pi_msix.pba_size);
+
+ return (pci_emul_add_capability(pi, (u_char *)&msixcap,
+ sizeof(msixcap)));
+}
+
+void
+msixcap_cfgwrite(struct pci_devinst *pi, int capoff, int offset,
+ int bytes, uint32_t val)
+{
+ uint16_t msgctrl, rwmask;
+ int off;
+
+ off = offset - capoff;
+ /* Message Control Register */
+ if (off == 2 && bytes == 2) {
+ rwmask = PCIM_MSIXCTRL_MSIX_ENABLE | PCIM_MSIXCTRL_FUNCTION_MASK;
+ msgctrl = pci_get_cfgdata16(pi, offset);
+ msgctrl &= ~rwmask;
+ msgctrl |= val & rwmask;
+ val = msgctrl;
+
+ pi->pi_msix.enabled = val & PCIM_MSIXCTRL_MSIX_ENABLE;
+ pi->pi_msix.function_mask = val & PCIM_MSIXCTRL_FUNCTION_MASK;
+ pci_lintr_update(pi);
+ }
+
+ CFGWRITE(pi, offset, val, bytes);
+}
+
+void
+msicap_cfgwrite(struct pci_devinst *pi, int capoff, int offset,
+ int bytes, uint32_t val)
+{
+ uint16_t msgctrl, rwmask, msgdata, mme;
+ uint32_t addrlo;
+
+ /*
+ * If guest is writing to the message control register make sure
+ * we do not overwrite read-only fields.
+ */
+ if ((offset - capoff) == 2 && bytes == 2) {
+ rwmask = PCIM_MSICTRL_MME_MASK | PCIM_MSICTRL_MSI_ENABLE;
+ msgctrl = pci_get_cfgdata16(pi, offset);
+ msgctrl &= ~rwmask;
+ msgctrl |= val & rwmask;
+ val = msgctrl;
+
+ addrlo = pci_get_cfgdata32(pi, capoff + 4);
+ if (msgctrl & PCIM_MSICTRL_64BIT)
+ msgdata = pci_get_cfgdata16(pi, capoff + 12);
+ else
+ msgdata = pci_get_cfgdata16(pi, capoff + 8);
+
+ mme = msgctrl & PCIM_MSICTRL_MME_MASK;
+ pi->pi_msi.enabled = msgctrl & PCIM_MSICTRL_MSI_ENABLE ? 1 : 0;
+ if (pi->pi_msi.enabled) {
+ pi->pi_msi.addr = addrlo;
+ pi->pi_msi.msg_data = msgdata;
+ pi->pi_msi.maxmsgnum = 1 << (mme >> 4);
+ } else {
+ pi->pi_msi.maxmsgnum = 0;
+ }
+ pci_lintr_update(pi);
+ }
+
+ CFGWRITE(pi, offset, val, bytes);
+}
+
+void
+pciecap_cfgwrite(struct pci_devinst *pi, int capoff, int offset,
+ int bytes, uint32_t val)
+{
+
+ /* XXX don't write to the readonly parts */
+ CFGWRITE(pi, offset, val, bytes);
+}
+
+#define PCIECAP_VERSION 0x2
+int
+pci_emul_add_pciecap(struct pci_devinst *pi, int type)
+{
+ int err;
+ struct pciecap pciecap;
+
+ CTASSERT(sizeof(struct pciecap) == 60);
+
+ if (type != PCIEM_TYPE_ROOT_PORT)
+ return (-1);
+
+ bzero(&pciecap, sizeof(pciecap));
+
+ pciecap.capid = PCIY_EXPRESS;
+ pciecap.pcie_capabilities = PCIECAP_VERSION | PCIEM_TYPE_ROOT_PORT;
+ pciecap.link_capabilities = 0x411; /* gen1, x1 */
+ pciecap.link_status = 0x11; /* gen1, x1 */
+
+ err = pci_emul_add_capability(pi, (u_char *)&pciecap, sizeof(pciecap));
+ return (err);
+}
+
+/*
+ * This function assumes that 'coff' is in the capabilities region of the
+ * config space.
+ */
+static void
+pci_emul_capwrite(struct pci_devinst *pi, int offset, int bytes, uint32_t val)
+{
+ int capid;
+ uint8_t capoff, nextoff;
+
+ /* Do not allow un-aligned writes */
+ if ((offset & (bytes - 1)) != 0)
+ return;
+
+ /* Find the capability that we want to update */
+ capoff = CAP_START_OFFSET;
+ while (1) {
+ nextoff = pci_get_cfgdata8(pi, capoff + 1);
+ if (nextoff == 0)
+ break;
+ if (offset >= capoff && offset < nextoff)
+ break;
+
+ capoff = nextoff;
+ }
+ assert(offset >= capoff);
+
+ /*
+ * Capability ID and Next Capability Pointer are readonly.
+ * However, some o/s's do 4-byte writes that include these.
+ * For this case, trim the write back to 2 bytes and adjust
+ * the data.
+ */
+ if (offset == capoff || offset == capoff + 1) {
+ if (offset == capoff && bytes == 4) {
+ bytes = 2;
+ offset += 2;
+ val >>= 16;
+ } else
+ return;
+ }
+
+ capid = pci_get_cfgdata8(pi, capoff);
+ switch (capid) {
+ case PCIY_MSI:
+ msicap_cfgwrite(pi, capoff, offset, bytes, val);
+ break;
+ case PCIY_MSIX:
+ msixcap_cfgwrite(pi, capoff, offset, bytes, val);
+ break;
+ case PCIY_EXPRESS:
+ pciecap_cfgwrite(pi, capoff, offset, bytes, val);
+ break;
+ default:
+ break;
+ }
+}
+
+static int
+pci_emul_iscap(struct pci_devinst *pi, int offset)
+{
+ uint16_t sts;
+
+ sts = pci_get_cfgdata16(pi, PCIR_STATUS);
+ if ((sts & PCIM_STATUS_CAPPRESENT) != 0) {
+ if (offset >= CAP_START_OFFSET && offset <= pi->pi_capend)
+ return (1);
+ }
+ return (0);
+}
+
+static int
+pci_emul_fallback_handler(struct vmctx *ctx, int vcpu, int dir, uint64_t addr,
+ int size, uint64_t *val, void *arg1, long arg2)
+{
+ /*
+ * Ignore writes; return 0xff's for reads. The mem read code
+ * will take care of truncating to the correct size.
+ */
+ if (dir == MEM_F_READ) {
+ *val = 0xffffffffffffffff;
+ }
+
+ return (0);
+}
+
+static int
+pci_emul_ecfg_handler(struct vmctx *ctx, int vcpu, int dir, uint64_t addr,
+ int bytes, uint64_t *val, void *arg1, long arg2)
+{
+ int bus, slot, func, coff, in;
+
+ coff = addr & 0xfff;
+ func = (addr >> 12) & 0x7;
+ slot = (addr >> 15) & 0x1f;
+ bus = (addr >> 20) & 0xff;
+ in = (dir == MEM_F_READ);
+ if (in)
+ *val = ~0UL;
+ pci_cfgrw(ctx, vcpu, in, bus, slot, func, coff, bytes, (uint32_t *)val);
+ return (0);
+}
+
+uint64_t
+pci_ecfg_base(void)
+{
+
+ return (PCI_EMUL_ECFG_BASE);
+}
+
+#define BUSIO_ROUNDUP 32
+#define BUSMEM_ROUNDUP (1024 * 1024)
+
+int
+init_pci(struct vmctx *ctx)
+{
+ struct mem_range mr;
+ struct pci_devemu *pde;
+ struct businfo *bi;
+ struct slotinfo *si;
+ struct funcinfo *fi;
+ size_t lowmem;
+ int bus, slot, func;
+ int error;
+
+ pci_emul_iobase = PCI_EMUL_IOBASE;
+ pci_emul_membase32 = vm_get_lowmem_limit(ctx);
+ pci_emul_membase64 = PCI_EMUL_MEMBASE64;
+
+ for (bus = 0; bus < MAXBUSES; bus++) {
+ if ((bi = pci_businfo[bus]) == NULL)
+ continue;
+ /*
+ * Keep track of the i/o and memory resources allocated to
+ * this bus.
+ */
+ bi->iobase = pci_emul_iobase;
+ bi->membase32 = pci_emul_membase32;
+ bi->membase64 = pci_emul_membase64;
+
+ for (slot = 0; slot < MAXSLOTS; slot++) {
+ si = &bi->slotinfo[slot];
+ for (func = 0; func < MAXFUNCS; func++) {
+ fi = &si->si_funcs[func];
+ if (fi->fi_name == NULL)
+ continue;
+ pde = pci_emul_finddev(fi->fi_name);
+ assert(pde != NULL);
+ error = pci_emul_init(ctx, pde, bus, slot,
+ func, fi);
+ if (error)
+ return (error);
+ }
+ }
+
+ /*
+ * Add some slop to the I/O and memory resources decoded by
+ * this bus to give a guest some flexibility if it wants to
+ * reprogram the BARs.
+ */
+ pci_emul_iobase += BUSIO_ROUNDUP;
+ pci_emul_iobase = roundup2(pci_emul_iobase, BUSIO_ROUNDUP);
+ bi->iolimit = pci_emul_iobase;
+
+ pci_emul_membase32 += BUSMEM_ROUNDUP;
+ pci_emul_membase32 = roundup2(pci_emul_membase32,
+ BUSMEM_ROUNDUP);
+ bi->memlimit32 = pci_emul_membase32;
+
+ pci_emul_membase64 += BUSMEM_ROUNDUP;
+ pci_emul_membase64 = roundup2(pci_emul_membase64,
+ BUSMEM_ROUNDUP);
+ bi->memlimit64 = pci_emul_membase64;
+ }
+
+ /*
+ * PCI backends are initialized before routing INTx interrupts
+ * so that LPC devices are able to reserve ISA IRQs before
+ * routing PIRQ pins.
+ */
+ for (bus = 0; bus < MAXBUSES; bus++) {
+ if ((bi = pci_businfo[bus]) == NULL)
+ continue;
+
+ for (slot = 0; slot < MAXSLOTS; slot++) {
+ si = &bi->slotinfo[slot];
+ for (func = 0; func < MAXFUNCS; func++) {
+ fi = &si->si_funcs[func];
+ if (fi->fi_devi == NULL)
+ continue;
+ pci_lintr_route(fi->fi_devi);
+ }
+ }
+ }
+ lpc_pirq_routed();
+
+ /*
+ * The guest physical memory map looks like the following:
+ * [0, lowmem) guest system memory
+ * [lowmem, lowmem_limit) memory hole (may be absent)
+ * [lowmem_limit, 0xE0000000) PCI hole (32-bit BAR allocation)
+ * [0xE0000000, 0xF0000000) PCI extended config window
+ * [0xF0000000, 4GB) LAPIC, IOAPIC, HPET, firmware
+ * [4GB, 4GB + highmem)
+ */
+
+ /*
+ * Accesses to memory addresses that are not allocated to system
+ * memory or PCI devices return 0xff's.
+ */
+ lowmem = vm_get_lowmem_size(ctx);
+ bzero(&mr, sizeof(struct mem_range));
+ mr.name = "PCI hole";
+ mr.flags = MEM_F_RW | MEM_F_IMMUTABLE;
+ mr.base = lowmem;
+ mr.size = (4ULL * 1024 * 1024 * 1024) - lowmem;
+ mr.handler = pci_emul_fallback_handler;
+ error = register_mem_fallback(&mr);
+ assert(error == 0);
+
+ /* PCI extended config space */
+ bzero(&mr, sizeof(struct mem_range));
+ mr.name = "PCI ECFG";
+ mr.flags = MEM_F_RW | MEM_F_IMMUTABLE;
+ mr.base = PCI_EMUL_ECFG_BASE;
+ mr.size = PCI_EMUL_ECFG_SIZE;
+ mr.handler = pci_emul_ecfg_handler;
+ error = register_mem(&mr);
+ assert(error == 0);
+
+ return (0);
+}
+
+static void
+pci_apic_prt_entry(int bus, int slot, int pin, int pirq_pin, int ioapic_irq,
+ void *arg)
+{
+
+ dsdt_line(" Package ()");
+ dsdt_line(" {");
+ dsdt_line(" 0x%X,", slot << 16 | 0xffff);
+ dsdt_line(" 0x%02X,", pin - 1);
+ dsdt_line(" Zero,");
+ dsdt_line(" 0x%X", ioapic_irq);
+ dsdt_line(" },");
+}
+
+static void
+pci_pirq_prt_entry(int bus, int slot, int pin, int pirq_pin, int ioapic_irq,
+ void *arg)
+{
+ char *name;
+
+ name = lpc_pirq_name(pirq_pin);
+ if (name == NULL)
+ return;
+ dsdt_line(" Package ()");
+ dsdt_line(" {");
+ dsdt_line(" 0x%X,", slot << 16 | 0xffff);
+ dsdt_line(" 0x%02X,", pin - 1);
+ dsdt_line(" %s,", name);
+ dsdt_line(" 0x00");
+ dsdt_line(" },");
+ free(name);
+}
+
+/*
+ * A bhyve virtual machine has a flat PCI hierarchy with a root port
+ * corresponding to each PCI bus.
+ */
+static void
+pci_bus_write_dsdt(int bus)
+{
+ struct businfo *bi;
+ struct slotinfo *si;
+ struct pci_devinst *pi;
+ int count, func, slot;
+
+ /*
+ * If there are no devices on this 'bus' then just return.
+ */
+ if ((bi = pci_businfo[bus]) == NULL) {
+ /*
+ * Bus 0 is special because it decodes the I/O ports used
+ * for PCI config space access even if there are no devices
+ * on it.
+ */
+ if (bus != 0)
+ return;
+ }
+
+ dsdt_line(" Device (PC%02X)", bus);
+ dsdt_line(" {");
+ dsdt_line(" Name (_HID, EisaId (\"PNP0A03\"))");
+ dsdt_line(" Name (_ADR, Zero)");
+
+ dsdt_line(" Method (_BBN, 0, NotSerialized)");
+ dsdt_line(" {");
+ dsdt_line(" Return (0x%08X)", bus);
+ dsdt_line(" }");
+ dsdt_line(" Name (_CRS, ResourceTemplate ()");
+ dsdt_line(" {");
+ dsdt_line(" WordBusNumber (ResourceProducer, MinFixed, "
+ "MaxFixed, PosDecode,");
+ dsdt_line(" 0x0000, // Granularity");
+ dsdt_line(" 0x%04X, // Range Minimum", bus);
+ dsdt_line(" 0x%04X, // Range Maximum", bus);
+ dsdt_line(" 0x0000, // Translation Offset");
+ dsdt_line(" 0x0001, // Length");
+ dsdt_line(" ,, )");
+
+ if (bus == 0) {
+ dsdt_indent(3);
+ dsdt_fixed_ioport(0xCF8, 8);
+ dsdt_unindent(3);
+
+ dsdt_line(" WordIO (ResourceProducer, MinFixed, MaxFixed, "
+ "PosDecode, EntireRange,");
+ dsdt_line(" 0x0000, // Granularity");
+ dsdt_line(" 0x0000, // Range Minimum");
+ dsdt_line(" 0x0CF7, // Range Maximum");
+ dsdt_line(" 0x0000, // Translation Offset");
+ dsdt_line(" 0x0CF8, // Length");
+ dsdt_line(" ,, , TypeStatic)");
+
+ dsdt_line(" WordIO (ResourceProducer, MinFixed, MaxFixed, "
+ "PosDecode, EntireRange,");
+ dsdt_line(" 0x0000, // Granularity");
+ dsdt_line(" 0x0D00, // Range Minimum");
+ dsdt_line(" 0x%04X, // Range Maximum",
+ PCI_EMUL_IOBASE - 1);
+ dsdt_line(" 0x0000, // Translation Offset");
+ dsdt_line(" 0x%04X, // Length",
+ PCI_EMUL_IOBASE - 0x0D00);
+ dsdt_line(" ,, , TypeStatic)");
+
+ if (bi == NULL) {
+ dsdt_line(" })");
+ goto done;
+ }
+ }
+ assert(bi != NULL);
+
+ /* i/o window */
+ dsdt_line(" WordIO (ResourceProducer, MinFixed, MaxFixed, "
+ "PosDecode, EntireRange,");
+ dsdt_line(" 0x0000, // Granularity");
+ dsdt_line(" 0x%04X, // Range Minimum", bi->iobase);
+ dsdt_line(" 0x%04X, // Range Maximum",
+ bi->iolimit - 1);
+ dsdt_line(" 0x0000, // Translation Offset");
+ dsdt_line(" 0x%04X, // Length",
+ bi->iolimit - bi->iobase);
+ dsdt_line(" ,, , TypeStatic)");
+
+ /* mmio window (32-bit) */
+ dsdt_line(" DWordMemory (ResourceProducer, PosDecode, "
+ "MinFixed, MaxFixed, NonCacheable, ReadWrite,");
+ dsdt_line(" 0x00000000, // Granularity");
+ dsdt_line(" 0x%08X, // Range Minimum\n", bi->membase32);
+ dsdt_line(" 0x%08X, // Range Maximum\n",
+ bi->memlimit32 - 1);
+ dsdt_line(" 0x00000000, // Translation Offset");
+ dsdt_line(" 0x%08X, // Length\n",
+ bi->memlimit32 - bi->membase32);
+ dsdt_line(" ,, , AddressRangeMemory, TypeStatic)");
+
+ /* mmio window (64-bit) */
+ dsdt_line(" QWordMemory (ResourceProducer, PosDecode, "
+ "MinFixed, MaxFixed, NonCacheable, ReadWrite,");
+ dsdt_line(" 0x0000000000000000, // Granularity");
+ dsdt_line(" 0x%016lX, // Range Minimum\n", bi->membase64);
+ dsdt_line(" 0x%016lX, // Range Maximum\n",
+ bi->memlimit64 - 1);
+ dsdt_line(" 0x0000000000000000, // Translation Offset");
+ dsdt_line(" 0x%016lX, // Length\n",
+ bi->memlimit64 - bi->membase64);
+ dsdt_line(" ,, , AddressRangeMemory, TypeStatic)");
+ dsdt_line(" })");
+
+ count = pci_count_lintr(bus);
+ if (count != 0) {
+ dsdt_indent(2);
+ dsdt_line("Name (PPRT, Package ()");
+ dsdt_line("{");
+ pci_walk_lintr(bus, pci_pirq_prt_entry, NULL);
+ dsdt_line("})");
+ dsdt_line("Name (APRT, Package ()");
+ dsdt_line("{");
+ pci_walk_lintr(bus, pci_apic_prt_entry, NULL);
+ dsdt_line("})");
+ dsdt_line("Method (_PRT, 0, NotSerialized)");
+ dsdt_line("{");
+ dsdt_line(" If (PICM)");
+ dsdt_line(" {");
+ dsdt_line(" Return (APRT)");
+ dsdt_line(" }");
+ dsdt_line(" Else");
+ dsdt_line(" {");
+ dsdt_line(" Return (PPRT)");
+ dsdt_line(" }");
+ dsdt_line("}");
+ dsdt_unindent(2);
+ }
+
+ dsdt_indent(2);
+ for (slot = 0; slot < MAXSLOTS; slot++) {
+ si = &bi->slotinfo[slot];
+ for (func = 0; func < MAXFUNCS; func++) {
+ pi = si->si_funcs[func].fi_devi;
+ if (pi != NULL && pi->pi_d->pe_write_dsdt != NULL)
+ pi->pi_d->pe_write_dsdt(pi);
+ }
+ }
+ dsdt_unindent(2);
+done:
+ dsdt_line(" }");
+}
+
+void
+pci_write_dsdt(void)
+{
+ int bus;
+
+ dsdt_indent(1);
+ dsdt_line("Name (PICM, 0x00)");
+ dsdt_line("Method (_PIC, 1, NotSerialized)");
+ dsdt_line("{");
+ dsdt_line(" Store (Arg0, PICM)");
+ dsdt_line("}");
+ dsdt_line("");
+ dsdt_line("Scope (_SB)");
+ dsdt_line("{");
+ for (bus = 0; bus < MAXBUSES; bus++)
+ pci_bus_write_dsdt(bus);
+ dsdt_line("}");
+ dsdt_unindent(1);
+}
+
+int
+pci_bus_configured(int bus)
+{
+ assert(bus >= 0 && bus < MAXBUSES);
+ return (pci_businfo[bus] != NULL);
+}
+
+int
+pci_msi_enabled(struct pci_devinst *pi)
+{
+ return (pi->pi_msi.enabled);
+}
+
+int
+pci_msi_maxmsgnum(struct pci_devinst *pi)
+{
+ if (pi->pi_msi.enabled)
+ return (pi->pi_msi.maxmsgnum);
+ else
+ return (0);
+}
+
+int
+pci_msix_enabled(struct pci_devinst *pi)
+{
+
+ return (pi->pi_msix.enabled && !pi->pi_msi.enabled);
+}
+
+void
+pci_generate_msix(struct pci_devinst *pi, int index)
+{
+ struct msix_table_entry *mte;
+
+ if (!pci_msix_enabled(pi))
+ return;
+
+ if (pi->pi_msix.function_mask)
+ return;
+
+ if (index >= pi->pi_msix.table_count)
+ return;
+
+ mte = &pi->pi_msix.table[index];
+ if ((mte->vector_control & PCIM_MSIX_VCTRL_MASK) == 0) {
+ /* XXX Set PBA bit if interrupt is disabled */
+ vm_lapic_msi(pi->pi_vmctx, mte->addr, mte->msg_data);
+ }
+}
+
+void
+pci_generate_msi(struct pci_devinst *pi, int index)
+{
+
+ if (pci_msi_enabled(pi) && index < pci_msi_maxmsgnum(pi)) {
+ vm_lapic_msi(pi->pi_vmctx, pi->pi_msi.addr,
+ pi->pi_msi.msg_data + index);
+ }
+}
+
+static bool
+pci_lintr_permitted(struct pci_devinst *pi)
+{
+ uint16_t cmd;
+
+ cmd = pci_get_cfgdata16(pi, PCIR_COMMAND);
+ return (!(pi->pi_msi.enabled || pi->pi_msix.enabled ||
+ (cmd & PCIM_CMD_INTxDIS)));
+}
+
+void
+pci_lintr_request(struct pci_devinst *pi)
+{
+ struct businfo *bi;
+ struct slotinfo *si;
+ int bestpin, bestcount, pin;
+
+ bi = pci_businfo[pi->pi_bus];
+ assert(bi != NULL);
+
+ /*
+ * Just allocate a pin from our slot. The pin will be
+ * assigned IRQs later when interrupts are routed.
+ */
+ si = &bi->slotinfo[pi->pi_slot];
+ bestpin = 0;
+ bestcount = si->si_intpins[0].ii_count;
+ for (pin = 1; pin < 4; pin++) {
+ if (si->si_intpins[pin].ii_count < bestcount) {
+ bestpin = pin;
+ bestcount = si->si_intpins[pin].ii_count;
+ }
+ }
+
+ si->si_intpins[bestpin].ii_count++;
+ pi->pi_lintr.pin = bestpin + 1;
+ pci_set_cfgdata8(pi, PCIR_INTPIN, bestpin + 1);
+}
+
+static void
+pci_lintr_route(struct pci_devinst *pi)
+{
+ struct businfo *bi;
+ struct intxinfo *ii;
+
+ if (pi->pi_lintr.pin == 0)
+ return;
+
+ bi = pci_businfo[pi->pi_bus];
+ assert(bi != NULL);
+ ii = &bi->slotinfo[pi->pi_slot].si_intpins[pi->pi_lintr.pin - 1];
+
+ /*
+ * Attempt to allocate an I/O APIC pin for this intpin if one
+ * is not yet assigned.
+ */
+ if (ii->ii_ioapic_irq == 0)
+ ii->ii_ioapic_irq = ioapic_pci_alloc_irq();
+ assert(ii->ii_ioapic_irq > 0);
+
+ /*
+ * Attempt to allocate a PIRQ pin for this intpin if one is
+ * not yet assigned.
+ */
+ if (ii->ii_pirq_pin == 0)
+ ii->ii_pirq_pin = pirq_alloc_pin(pi->pi_vmctx);
+ assert(ii->ii_pirq_pin > 0);
+
+ pi->pi_lintr.ioapic_irq = ii->ii_ioapic_irq;
+ pi->pi_lintr.pirq_pin = ii->ii_pirq_pin;
+ pci_set_cfgdata8(pi, PCIR_INTLINE, pirq_irq(ii->ii_pirq_pin));
+}
+
+void
+pci_lintr_assert(struct pci_devinst *pi)
+{
+
+ assert(pi->pi_lintr.pin > 0);
+
+ pthread_mutex_lock(&pi->pi_lintr.lock);
+ if (pi->pi_lintr.state == IDLE) {
+ if (pci_lintr_permitted(pi)) {
+ pi->pi_lintr.state = ASSERTED;
+ pci_irq_assert(pi);
+ } else
+ pi->pi_lintr.state = PENDING;
+ }
+ pthread_mutex_unlock(&pi->pi_lintr.lock);
+}
+
+void
+pci_lintr_deassert(struct pci_devinst *pi)
+{
+
+ assert(pi->pi_lintr.pin > 0);
+
+ pthread_mutex_lock(&pi->pi_lintr.lock);
+ if (pi->pi_lintr.state == ASSERTED) {
+ pi->pi_lintr.state = IDLE;
+ pci_irq_deassert(pi);
+ } else if (pi->pi_lintr.state == PENDING)
+ pi->pi_lintr.state = IDLE;
+ pthread_mutex_unlock(&pi->pi_lintr.lock);
+}
+
+static void
+pci_lintr_update(struct pci_devinst *pi)
+{
+
+ pthread_mutex_lock(&pi->pi_lintr.lock);
+ if (pi->pi_lintr.state == ASSERTED && !pci_lintr_permitted(pi)) {
+ pci_irq_deassert(pi);
+ pi->pi_lintr.state = PENDING;
+ } else if (pi->pi_lintr.state == PENDING && pci_lintr_permitted(pi)) {
+ pi->pi_lintr.state = ASSERTED;
+ pci_irq_assert(pi);
+ }
+ pthread_mutex_unlock(&pi->pi_lintr.lock);
+}
+
+int
+pci_count_lintr(int bus)
+{
+ int count, slot, pin;
+ struct slotinfo *slotinfo;
+
+ count = 0;
+ if (pci_businfo[bus] != NULL) {
+ for (slot = 0; slot < MAXSLOTS; slot++) {
+ slotinfo = &pci_businfo[bus]->slotinfo[slot];
+ for (pin = 0; pin < 4; pin++) {
+ if (slotinfo->si_intpins[pin].ii_count != 0)
+ count++;
+ }
+ }
+ }
+ return (count);
+}
+
+void
+pci_walk_lintr(int bus, pci_lintr_cb cb, void *arg)
+{
+ struct businfo *bi;
+ struct slotinfo *si;
+ struct intxinfo *ii;
+ int slot, pin;
+
+ if ((bi = pci_businfo[bus]) == NULL)
+ return;
+
+ for (slot = 0; slot < MAXSLOTS; slot++) {
+ si = &bi->slotinfo[slot];
+ for (pin = 0; pin < 4; pin++) {
+ ii = &si->si_intpins[pin];
+ if (ii->ii_count != 0)
+ cb(bus, slot, pin + 1, ii->ii_pirq_pin,
+ ii->ii_ioapic_irq, arg);
+ }
+ }
+}
+
+/*
+ * Return 1 if the emulated device in 'slot' is a multi-function device.
+ * Return 0 otherwise.
+ */
+static int
+pci_emul_is_mfdev(int bus, int slot)
+{
+ struct businfo *bi;
+ struct slotinfo *si;
+ int f, numfuncs;
+
+ numfuncs = 0;
+ if ((bi = pci_businfo[bus]) != NULL) {
+ si = &bi->slotinfo[slot];
+ for (f = 0; f < MAXFUNCS; f++) {
+ if (si->si_funcs[f].fi_devi != NULL) {
+ numfuncs++;
+ }
+ }
+ }
+ return (numfuncs > 1);
+}
+
+/*
+ * Ensure that the PCIM_MFDEV bit is properly set (or unset) depending on
+ * whether or not is a multi-function being emulated in the pci 'slot'.
+ */
+static void
+pci_emul_hdrtype_fixup(int bus, int slot, int off, int bytes, uint32_t *rv)
+{
+ int mfdev;
+
+ if (off <= PCIR_HDRTYPE && off + bytes > PCIR_HDRTYPE) {
+ mfdev = pci_emul_is_mfdev(bus, slot);
+ switch (bytes) {
+ case 1:
+ case 2:
+ *rv &= ~PCIM_MFDEV;
+ if (mfdev) {
+ *rv |= PCIM_MFDEV;
+ }
+ break;
+ case 4:
+ *rv &= ~(PCIM_MFDEV << 16);
+ if (mfdev) {
+ *rv |= (PCIM_MFDEV << 16);
+ }
+ break;
+ }
+ }
+}
+
+static void
+pci_emul_cmdsts_write(struct pci_devinst *pi, int coff, uint32_t new, int bytes)
+{
+ int i, rshift;
+ uint32_t cmd, cmd2, changed, old, readonly;
+
+ cmd = pci_get_cfgdata16(pi, PCIR_COMMAND); /* stash old value */
+
+ /*
+ * From PCI Local Bus Specification 3.0 sections 6.2.2 and 6.2.3.
+ *
+ * XXX Bits 8, 11, 12, 13, 14 and 15 in the status register are
+ * 'write 1 to clear'. However these bits are not set to '1' by
+ * any device emulation so it is simpler to treat them as readonly.
+ */
+ rshift = (coff & 0x3) * 8;
+ readonly = 0xFFFFF880 >> rshift;
+
+ old = CFGREAD(pi, coff, bytes);
+ new &= ~readonly;
+ new |= (old & readonly);
+ CFGWRITE(pi, coff, new, bytes); /* update config */
+
+ cmd2 = pci_get_cfgdata16(pi, PCIR_COMMAND); /* get updated value */
+ changed = cmd ^ cmd2;
+
+ /*
+ * If the MMIO or I/O address space decoding has changed then
+ * register/unregister all BARs that decode that address space.
+ */
+ for (i = 0; i <= PCI_BARMAX; i++) {
+ switch (pi->pi_bar[i].type) {
+ case PCIBAR_NONE:
+ case PCIBAR_MEMHI64:
+ break;
+ case PCIBAR_IO:
+ /* I/O address space decoding changed? */
+ if (changed & PCIM_CMD_PORTEN) {
+ if (porten(pi))
+ register_bar(pi, i);
+ else
+ unregister_bar(pi, i);
+ }
+ break;
+ case PCIBAR_MEM32:
+ case PCIBAR_MEM64:
+ /* MMIO address space decoding changed? */
+ if (changed & PCIM_CMD_MEMEN) {
+ if (memen(pi))
+ register_bar(pi, i);
+ else
+ unregister_bar(pi, i);
+ }
+ break;
+ default:
+ assert(0);
+ }
+ }
+
+ /*
+ * If INTx has been unmasked and is pending, assert the
+ * interrupt.
+ */
+ pci_lintr_update(pi);
+}
+
+static void
+pci_cfgrw(struct vmctx *ctx, int vcpu, int in, int bus, int slot, int func,
+ int coff, int bytes, uint32_t *eax)
+{
+ struct businfo *bi;
+ struct slotinfo *si;
+ struct pci_devinst *pi;
+ struct pci_devemu *pe;
+ int idx, needcfg;
+ uint64_t addr, bar, mask;
+
+ if ((bi = pci_businfo[bus]) != NULL) {
+ si = &bi->slotinfo[slot];
+ pi = si->si_funcs[func].fi_devi;
+ } else
+ pi = NULL;
+
+ /*
+ * Just return if there is no device at this slot:func or if the
+ * the guest is doing an un-aligned access.
+ */
+ if (pi == NULL || (bytes != 1 && bytes != 2 && bytes != 4) ||
+ (coff & (bytes - 1)) != 0) {
+ if (in)
+ *eax = 0xffffffff;
+ return;
+ }
+
+ /*
+ * Ignore all writes beyond the standard config space and return all
+ * ones on reads.
+ */
+ if (coff >= PCI_REGMAX + 1) {
+ if (in) {
+ *eax = 0xffffffff;
+ /*
+ * Extended capabilities begin at offset 256 in config
+ * space. Absence of extended capabilities is signaled
+ * with all 0s in the extended capability header at
+ * offset 256.
+ */
+ if (coff <= PCI_REGMAX + 4)
+ *eax = 0x00000000;
+ }
+ return;
+ }
+
+ pe = pi->pi_d;
+
+ /*
+ * Config read
+ */
+ if (in) {
+ /* Let the device emulation override the default handler */
+ if (pe->pe_cfgread != NULL) {
+ needcfg = pe->pe_cfgread(ctx, vcpu, pi, coff, bytes,
+ eax);
+ } else {
+ needcfg = 1;
+ }
+
+ if (needcfg)
+ *eax = CFGREAD(pi, coff, bytes);
+
+ pci_emul_hdrtype_fixup(bus, slot, coff, bytes, eax);
+ } else {
+ /* Let the device emulation override the default handler */
+ if (pe->pe_cfgwrite != NULL &&
+ (*pe->pe_cfgwrite)(ctx, vcpu, pi, coff, bytes, *eax) == 0)
+ return;
+
+ /*
+ * Special handling for write to BAR registers
+ */
+ if (coff >= PCIR_BAR(0) && coff < PCIR_BAR(PCI_BARMAX + 1)) {
+ /*
+ * Ignore writes to BAR registers that are not
+ * 4-byte aligned.
+ */
+ if (bytes != 4 || (coff & 0x3) != 0)
+ return;
+ idx = (coff - PCIR_BAR(0)) / 4;
+ mask = ~(pi->pi_bar[idx].size - 1);
+ switch (pi->pi_bar[idx].type) {
+ case PCIBAR_NONE:
+ pi->pi_bar[idx].addr = bar = 0;
+ break;
+ case PCIBAR_IO:
+ addr = *eax & mask;
+ addr &= 0xffff;
+ bar = addr | PCIM_BAR_IO_SPACE;
+ /*
+ * Register the new BAR value for interception
+ */
+ if (addr != pi->pi_bar[idx].addr) {
+ update_bar_address(pi, addr, idx,
+ PCIBAR_IO);
+ }
+ break;
+ case PCIBAR_MEM32:
+ addr = bar = *eax & mask;
+ bar |= PCIM_BAR_MEM_SPACE | PCIM_BAR_MEM_32;
+ if (addr != pi->pi_bar[idx].addr) {
+ update_bar_address(pi, addr, idx,
+ PCIBAR_MEM32);
+ }
+ break;
+ case PCIBAR_MEM64:
+ addr = bar = *eax & mask;
+ bar |= PCIM_BAR_MEM_SPACE | PCIM_BAR_MEM_64 |
+ PCIM_BAR_MEM_PREFETCH;
+ if (addr != (uint32_t)pi->pi_bar[idx].addr) {
+ update_bar_address(pi, addr, idx,
+ PCIBAR_MEM64);
+ }
+ break;
+ case PCIBAR_MEMHI64:
+ mask = ~(pi->pi_bar[idx - 1].size - 1);
+ addr = ((uint64_t)*eax << 32) & mask;
+ bar = addr >> 32;
+ if (bar != pi->pi_bar[idx - 1].addr >> 32) {
+ update_bar_address(pi, addr, idx - 1,
+ PCIBAR_MEMHI64);
+ }
+ break;
+ default:
+ assert(0);
+ }
+ pci_set_cfgdata32(pi, coff, bar);
+
+ } else if (pci_emul_iscap(pi, coff)) {
+ pci_emul_capwrite(pi, coff, bytes, *eax);
+ } else if (coff >= PCIR_COMMAND && coff < PCIR_REVID) {
+ pci_emul_cmdsts_write(pi, coff, *eax, bytes);
+ } else {
+ CFGWRITE(pi, coff, *eax, bytes);
+ }
+ }
+}
+
+static int cfgenable, cfgbus, cfgslot, cfgfunc, cfgoff;
+
+static int
+pci_emul_cfgaddr(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
+ uint32_t *eax, void *arg)
+{
+ uint32_t x;
+
+ if (bytes != 4) {
+ if (in)
+ *eax = (bytes == 2) ? 0xffff : 0xff;
+ return (0);
+ }
+
+ if (in) {
+ x = (cfgbus << 16) | (cfgslot << 11) | (cfgfunc << 8) | cfgoff;
+ if (cfgenable)
+ x |= CONF1_ENABLE;
+ *eax = x;
+ } else {
+ x = *eax;
+ cfgenable = (x & CONF1_ENABLE) == CONF1_ENABLE;
+ cfgoff = x & PCI_REGMAX;
+ cfgfunc = (x >> 8) & PCI_FUNCMAX;
+ cfgslot = (x >> 11) & PCI_SLOTMAX;
+ cfgbus = (x >> 16) & PCI_BUSMAX;
+ }
+
+ return (0);
+}
+INOUT_PORT(pci_cfgaddr, CONF1_ADDR_PORT, IOPORT_F_INOUT, pci_emul_cfgaddr);
+
+static int
+pci_emul_cfgdata(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
+ uint32_t *eax, void *arg)
+{
+ int coff;
+
+ assert(bytes == 1 || bytes == 2 || bytes == 4);
+
+ coff = cfgoff + (port - CONF1_DATA_PORT);
+ if (cfgenable) {
+ pci_cfgrw(ctx, vcpu, in, cfgbus, cfgslot, cfgfunc, coff, bytes,
+ eax);
+ } else {
+ /* Ignore accesses to cfgdata if not enabled by cfgaddr */
+ if (in)
+ *eax = 0xffffffff;
+ }
+ return (0);
+}
+
+INOUT_PORT(pci_cfgdata, CONF1_DATA_PORT+0, IOPORT_F_INOUT, pci_emul_cfgdata);
+INOUT_PORT(pci_cfgdata, CONF1_DATA_PORT+1, IOPORT_F_INOUT, pci_emul_cfgdata);
+INOUT_PORT(pci_cfgdata, CONF1_DATA_PORT+2, IOPORT_F_INOUT, pci_emul_cfgdata);
+INOUT_PORT(pci_cfgdata, CONF1_DATA_PORT+3, IOPORT_F_INOUT, pci_emul_cfgdata);
+
+#define PCI_EMUL_TEST
+#ifdef PCI_EMUL_TEST
+/*
+ * Define a dummy test device
+ */
+#define DIOSZ 8
+#define DMEMSZ 4096
+struct pci_emul_dsoftc {
+ uint8_t ioregs[DIOSZ];
+ uint8_t memregs[2][DMEMSZ];
+};
+
+#define PCI_EMUL_MSI_MSGS 4
+#define PCI_EMUL_MSIX_MSGS 16
+
+static int
+pci_emul_dinit(struct vmctx *ctx, struct pci_devinst *pi, char *opts)
+{
+ int error;
+ struct pci_emul_dsoftc *sc;
+
+ sc = calloc(1, sizeof(struct pci_emul_dsoftc));
+
+ pi->pi_arg = sc;
+
+ pci_set_cfgdata16(pi, PCIR_DEVICE, 0x0001);
+ pci_set_cfgdata16(pi, PCIR_VENDOR, 0x10DD);
+ pci_set_cfgdata8(pi, PCIR_CLASS, 0x02);
+
+ error = pci_emul_add_msicap(pi, PCI_EMUL_MSI_MSGS);
+ assert(error == 0);
+
+ error = pci_emul_alloc_bar(pi, 0, PCIBAR_IO, DIOSZ);
+ assert(error == 0);
+
+ error = pci_emul_alloc_bar(pi, 1, PCIBAR_MEM32, DMEMSZ);
+ assert(error == 0);
+
+ error = pci_emul_alloc_bar(pi, 2, PCIBAR_MEM32, DMEMSZ);
+ assert(error == 0);
+
+ return (0);
+}
+
+static void
+pci_emul_diow(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int baridx,
+ uint64_t offset, int size, uint64_t value)
+{
+ int i;
+ struct pci_emul_dsoftc *sc = pi->pi_arg;
+
+ if (baridx == 0) {
+ if (offset + size > DIOSZ) {
+ printf("diow: iow too large, offset %ld size %d\n",
+ offset, size);
+ return;
+ }
+
+ if (size == 1) {
+ sc->ioregs[offset] = value & 0xff;
+ } else if (size == 2) {
+ *(uint16_t *)&sc->ioregs[offset] = value & 0xffff;
+ } else if (size == 4) {
+ *(uint32_t *)&sc->ioregs[offset] = value;
+ } else {
+ printf("diow: iow unknown size %d\n", size);
+ }
+
+ /*
+ * Special magic value to generate an interrupt
+ */
+ if (offset == 4 && size == 4 && pci_msi_enabled(pi))
+ pci_generate_msi(pi, value % pci_msi_maxmsgnum(pi));
+
+ if (value == 0xabcdef) {
+ for (i = 0; i < pci_msi_maxmsgnum(pi); i++)
+ pci_generate_msi(pi, i);
+ }
+ }
+
+ if (baridx == 1 || baridx == 2) {
+ if (offset + size > DMEMSZ) {
+ printf("diow: memw too large, offset %ld size %d\n",
+ offset, size);
+ return;
+ }
+
+ i = baridx - 1; /* 'memregs' index */
+
+ if (size == 1) {
+ sc->memregs[i][offset] = value;
+ } else if (size == 2) {
+ *(uint16_t *)&sc->memregs[i][offset] = value;
+ } else if (size == 4) {
+ *(uint32_t *)&sc->memregs[i][offset] = value;
+ } else if (size == 8) {
+ *(uint64_t *)&sc->memregs[i][offset] = value;
+ } else {
+ printf("diow: memw unknown size %d\n", size);
+ }
+
+ /*
+ * magic interrupt ??
+ */
+ }
+
+ if (baridx > 2) {
+ printf("diow: unknown bar idx %d\n", baridx);
+ }
+}
+
+static uint64_t
+pci_emul_dior(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int baridx,
+ uint64_t offset, int size)
+{
+ struct pci_emul_dsoftc *sc = pi->pi_arg;
+ uint32_t value;
+ int i;
+
+ if (baridx == 0) {
+ if (offset + size > DIOSZ) {
+ printf("dior: ior too large, offset %ld size %d\n",
+ offset, size);
+ return (0);
+ }
+
+ if (size == 1) {
+ value = sc->ioregs[offset];
+ } else if (size == 2) {
+ value = *(uint16_t *) &sc->ioregs[offset];
+ } else if (size == 4) {
+ value = *(uint32_t *) &sc->ioregs[offset];
+ } else {
+ printf("dior: ior unknown size %d\n", size);
+ }
+ }
+
+ if (baridx == 1 || baridx == 2) {
+ if (offset + size > DMEMSZ) {
+ printf("dior: memr too large, offset %ld size %d\n",
+ offset, size);
+ return (0);
+ }
+
+ i = baridx - 1; /* 'memregs' index */
+
+ if (size == 1) {
+ value = sc->memregs[i][offset];
+ } else if (size == 2) {
+ value = *(uint16_t *) &sc->memregs[i][offset];
+ } else if (size == 4) {
+ value = *(uint32_t *) &sc->memregs[i][offset];
+ } else if (size == 8) {
+ value = *(uint64_t *) &sc->memregs[i][offset];
+ } else {
+ printf("dior: ior unknown size %d\n", size);
+ }
+ }
+
+
+ if (baridx > 2) {
+ printf("dior: unknown bar idx %d\n", baridx);
+ return (0);
+ }
+
+ return (value);
+}
+
+struct pci_devemu pci_dummy = {
+ .pe_emu = "dummy",
+ .pe_init = pci_emul_dinit,
+ .pe_barwrite = pci_emul_diow,
+ .pe_barread = pci_emul_dior
+};
+PCI_EMUL_SET(pci_dummy);
+
+#endif /* PCI_EMUL_TEST */
diff --git a/usr.sbin/bhyve/pci_emul.h b/usr.sbin/bhyve/pci_emul.h
new file mode 100644
index 0000000..6b8c4e0
--- /dev/null
+++ b/usr.sbin/bhyve/pci_emul.h
@@ -0,0 +1,283 @@
+/*-
+ * Copyright (c) 2011 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 _PCI_EMUL_H_
+#define _PCI_EMUL_H_
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/kernel.h>
+#include <sys/_pthreadtypes.h>
+
+#include <dev/pci/pcireg.h>
+
+#include <assert.h>
+
+#define PCI_BARMAX PCIR_MAX_BAR_0 /* BAR registers in a Type 0 header */
+
+struct vmctx;
+struct pci_devinst;
+struct memory_region;
+
+struct pci_devemu {
+ char *pe_emu; /* Name of device emulation */
+
+ /* instance creation */
+ int (*pe_init)(struct vmctx *, struct pci_devinst *,
+ char *opts);
+
+ /* ACPI DSDT enumeration */
+ void (*pe_write_dsdt)(struct pci_devinst *);
+
+ /* config space read/write callbacks */
+ int (*pe_cfgwrite)(struct vmctx *ctx, int vcpu,
+ struct pci_devinst *pi, int offset,
+ int bytes, uint32_t val);
+ int (*pe_cfgread)(struct vmctx *ctx, int vcpu,
+ struct pci_devinst *pi, int offset,
+ int bytes, uint32_t *retval);
+
+ /* BAR read/write callbacks */
+ void (*pe_barwrite)(struct vmctx *ctx, int vcpu,
+ struct pci_devinst *pi, int baridx,
+ uint64_t offset, int size, uint64_t value);
+ uint64_t (*pe_barread)(struct vmctx *ctx, int vcpu,
+ struct pci_devinst *pi, int baridx,
+ uint64_t offset, int size);
+};
+#define PCI_EMUL_SET(x) DATA_SET(pci_devemu_set, x);
+
+enum pcibar_type {
+ PCIBAR_NONE,
+ PCIBAR_IO,
+ PCIBAR_MEM32,
+ PCIBAR_MEM64,
+ PCIBAR_MEMHI64
+};
+
+struct pcibar {
+ enum pcibar_type type; /* io or memory */
+ uint64_t size;
+ uint64_t addr;
+};
+
+#define PI_NAMESZ 40
+
+struct msix_table_entry {
+ uint64_t addr;
+ uint32_t msg_data;
+ uint32_t vector_control;
+} __packed;
+
+/*
+ * In case the structure is modified to hold extra information, use a define
+ * for the size that should be emulated.
+ */
+#define MSIX_TABLE_ENTRY_SIZE 16
+#define MAX_MSIX_TABLE_ENTRIES 2048
+#define PBA_SIZE(msgnum) (roundup2((msgnum), 64) / 8)
+
+enum lintr_stat {
+ IDLE,
+ ASSERTED,
+ PENDING
+};
+
+struct pci_devinst {
+ struct pci_devemu *pi_d;
+ struct vmctx *pi_vmctx;
+ uint8_t pi_bus, pi_slot, pi_func;
+ char pi_name[PI_NAMESZ];
+ int pi_bar_getsize;
+ int pi_prevcap;
+ int pi_capend;
+
+ struct {
+ int8_t pin;
+ enum lintr_stat state;
+ int pirq_pin;
+ int ioapic_irq;
+ pthread_mutex_t lock;
+ } pi_lintr;
+
+ struct {
+ int enabled;
+ uint64_t addr;
+ uint64_t msg_data;
+ int maxmsgnum;
+ } pi_msi;
+
+ struct {
+ int enabled;
+ int table_bar;
+ int pba_bar;
+ uint32_t table_offset;
+ int table_count;
+ uint32_t pba_offset;
+ int pba_size;
+ int function_mask;
+ struct msix_table_entry *table; /* allocated at runtime */
+ } pi_msix;
+
+ void *pi_arg; /* devemu-private data */
+
+ u_char pi_cfgdata[PCI_REGMAX + 1];
+ struct pcibar pi_bar[PCI_BARMAX + 1];
+};
+
+struct msicap {
+ uint8_t capid;
+ uint8_t nextptr;
+ uint16_t msgctrl;
+ uint32_t addrlo;
+ uint32_t addrhi;
+ uint16_t msgdata;
+} __packed;
+
+struct msixcap {
+ uint8_t capid;
+ uint8_t nextptr;
+ uint16_t msgctrl;
+ uint32_t table_info; /* bar index and offset within it */
+ uint32_t pba_info; /* bar index and offset within it */
+} __packed;
+
+struct pciecap {
+ uint8_t capid;
+ uint8_t nextptr;
+ uint16_t pcie_capabilities;
+
+ uint32_t dev_capabilities; /* all devices */
+ uint16_t dev_control;
+ uint16_t dev_status;
+
+ uint32_t link_capabilities; /* devices with links */
+ uint16_t link_control;
+ uint16_t link_status;
+
+ uint32_t slot_capabilities; /* ports with slots */
+ uint16_t slot_control;
+ uint16_t slot_status;
+
+ uint16_t root_control; /* root ports */
+ uint16_t root_capabilities;
+ uint32_t root_status;
+
+ uint32_t dev_capabilities2; /* all devices */
+ uint16_t dev_control2;
+ uint16_t dev_status2;
+
+ uint32_t link_capabilities2; /* devices with links */
+ uint16_t link_control2;
+ uint16_t link_status2;
+
+ uint32_t slot_capabilities2; /* ports with slots */
+ uint16_t slot_control2;
+ uint16_t slot_status2;
+} __packed;
+
+typedef void (*pci_lintr_cb)(int b, int s, int pin, int pirq_pin,
+ int ioapic_irq, void *arg);
+
+int init_pci(struct vmctx *ctx);
+void msicap_cfgwrite(struct pci_devinst *pi, int capoff, int offset,
+ int bytes, uint32_t val);
+void msixcap_cfgwrite(struct pci_devinst *pi, int capoff, int offset,
+ int bytes, uint32_t val);
+void pci_callback(void);
+int pci_emul_alloc_bar(struct pci_devinst *pdi, int idx,
+ enum pcibar_type type, uint64_t size);
+int pci_emul_alloc_pbar(struct pci_devinst *pdi, int idx,
+ uint64_t hostbase, enum pcibar_type type, uint64_t size);
+int pci_emul_add_msicap(struct pci_devinst *pi, int msgnum);
+int pci_emul_add_pciecap(struct pci_devinst *pi, int pcie_device_type);
+void pci_generate_msi(struct pci_devinst *pi, int msgnum);
+void pci_generate_msix(struct pci_devinst *pi, int msgnum);
+void pci_lintr_assert(struct pci_devinst *pi);
+void pci_lintr_deassert(struct pci_devinst *pi);
+void pci_lintr_request(struct pci_devinst *pi);
+int pci_msi_enabled(struct pci_devinst *pi);
+int pci_msix_enabled(struct pci_devinst *pi);
+int pci_msix_table_bar(struct pci_devinst *pi);
+int pci_msix_pba_bar(struct pci_devinst *pi);
+int pci_msi_msgnum(struct pci_devinst *pi);
+int pci_parse_slot(char *opt);
+void pci_populate_msicap(struct msicap *cap, int msgs, int nextptr);
+int pci_emul_add_msixcap(struct pci_devinst *pi, int msgnum, int barnum);
+int pci_emul_msix_twrite(struct pci_devinst *pi, uint64_t offset, int size,
+ uint64_t value);
+uint64_t pci_emul_msix_tread(struct pci_devinst *pi, uint64_t offset, int size);
+int pci_count_lintr(int bus);
+void pci_walk_lintr(int bus, pci_lintr_cb cb, void *arg);
+void pci_write_dsdt(void);
+uint64_t pci_ecfg_base(void);
+int pci_bus_configured(int bus);
+
+static __inline void
+pci_set_cfgdata8(struct pci_devinst *pi, int offset, uint8_t val)
+{
+ assert(offset <= PCI_REGMAX);
+ *(uint8_t *)(pi->pi_cfgdata + offset) = val;
+}
+
+static __inline void
+pci_set_cfgdata16(struct pci_devinst *pi, int offset, uint16_t val)
+{
+ assert(offset <= (PCI_REGMAX - 1) && (offset & 1) == 0);
+ *(uint16_t *)(pi->pi_cfgdata + offset) = val;
+}
+
+static __inline void
+pci_set_cfgdata32(struct pci_devinst *pi, int offset, uint32_t val)
+{
+ assert(offset <= (PCI_REGMAX - 3) && (offset & 3) == 0);
+ *(uint32_t *)(pi->pi_cfgdata + offset) = val;
+}
+
+static __inline uint8_t
+pci_get_cfgdata8(struct pci_devinst *pi, int offset)
+{
+ assert(offset <= PCI_REGMAX);
+ return (*(uint8_t *)(pi->pi_cfgdata + offset));
+}
+
+static __inline uint16_t
+pci_get_cfgdata16(struct pci_devinst *pi, int offset)
+{
+ assert(offset <= (PCI_REGMAX - 1) && (offset & 1) == 0);
+ return (*(uint16_t *)(pi->pi_cfgdata + offset));
+}
+
+static __inline uint32_t
+pci_get_cfgdata32(struct pci_devinst *pi, int offset)
+{
+ assert(offset <= (PCI_REGMAX - 3) && (offset & 3) == 0);
+ return (*(uint32_t *)(pi->pi_cfgdata + offset));
+}
+
+#endif /* _PCI_EMUL_H_ */
diff --git a/usr.sbin/bhyve/pci_hostbridge.c b/usr.sbin/bhyve/pci_hostbridge.c
new file mode 100644
index 0000000..5c9ea28
--- /dev/null
+++ b/usr.sbin/bhyve/pci_hostbridge.c
@@ -0,0 +1,70 @@
+/*-
+ * Copyright (c) 2011 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 "pci_emul.h"
+
+static int
+pci_hostbridge_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts)
+{
+
+ /* config space */
+ pci_set_cfgdata16(pi, PCIR_VENDOR, 0x1275); /* NetApp */
+ pci_set_cfgdata16(pi, PCIR_DEVICE, 0x1275); /* NetApp */
+ pci_set_cfgdata8(pi, PCIR_HDRTYPE, PCIM_HDRTYPE_NORMAL);
+ pci_set_cfgdata8(pi, PCIR_CLASS, PCIC_BRIDGE);
+ pci_set_cfgdata8(pi, PCIR_SUBCLASS, PCIS_BRIDGE_HOST);
+
+ pci_emul_add_pciecap(pi, PCIEM_TYPE_ROOT_PORT);
+
+ return (0);
+}
+
+static int
+pci_amd_hostbridge_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts)
+{
+ (void) pci_hostbridge_init(ctx, pi, opts);
+ pci_set_cfgdata16(pi, PCIR_VENDOR, 0x1022); /* AMD */
+ pci_set_cfgdata16(pi, PCIR_DEVICE, 0x7432); /* made up */
+
+ return (0);
+}
+
+struct pci_devemu pci_de_amd_hostbridge = {
+ .pe_emu = "amd_hostbridge",
+ .pe_init = pci_amd_hostbridge_init,
+};
+PCI_EMUL_SET(pci_de_amd_hostbridge);
+
+struct pci_devemu pci_de_hostbridge = {
+ .pe_emu = "hostbridge",
+ .pe_init = pci_hostbridge_init,
+};
+PCI_EMUL_SET(pci_de_hostbridge);
diff --git a/usr.sbin/bhyve/pci_irq.c b/usr.sbin/bhyve/pci_irq.c
new file mode 100644
index 0000000..f22b15c
--- /dev/null
+++ b/usr.sbin/bhyve/pci_irq.c
@@ -0,0 +1,346 @@
+/*-
+ * Copyright (c) 2014 Hudson River Trading LLC
+ * Written by: John H. 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.
+ */
+
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <machine/vmm.h>
+
+#include <assert.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <vmmapi.h>
+
+#include "acpi.h"
+#include "inout.h"
+#include "pci_emul.h"
+#include "pci_irq.h"
+#include "pci_lpc.h"
+
+/*
+ * Implement an 8 pin PCI interrupt router compatible with the router
+ * present on Intel's ICH10 chip.
+ */
+
+/* Fields in each PIRQ register. */
+#define PIRQ_DIS 0x80
+#define PIRQ_IRQ 0x0f
+
+/* Only IRQs 3-7, 9-12, and 14-15 are permitted. */
+#define PERMITTED_IRQS 0xdef8
+#define IRQ_PERMITTED(irq) (((1U << (irq)) & PERMITTED_IRQS) != 0)
+
+/* IRQ count to disable an IRQ. */
+#define IRQ_DISABLED 0xff
+
+static struct pirq {
+ uint8_t reg;
+ int use_count;
+ int active_count;
+ pthread_mutex_t lock;
+} pirqs[8];
+
+static u_char irq_counts[16];
+static int pirq_cold = 1;
+
+/*
+ * Returns true if this pin is enabled with a valid IRQ. Setting the
+ * register to a reserved IRQ causes interrupts to not be asserted as
+ * if the pin was disabled.
+ */
+static bool
+pirq_valid_irq(int reg)
+{
+
+ if (reg & PIRQ_DIS)
+ return (false);
+ return (IRQ_PERMITTED(reg & PIRQ_IRQ));
+}
+
+uint8_t
+pirq_read(int pin)
+{
+
+ assert(pin > 0 && pin <= nitems(pirqs));
+ return (pirqs[pin - 1].reg);
+}
+
+void
+pirq_write(struct vmctx *ctx, int pin, uint8_t val)
+{
+ struct pirq *pirq;
+
+ assert(pin > 0 && pin <= nitems(pirqs));
+ pirq = &pirqs[pin - 1];
+ pthread_mutex_lock(&pirq->lock);
+ if (pirq->reg != (val & (PIRQ_DIS | PIRQ_IRQ))) {
+ if (pirq->active_count != 0 && pirq_valid_irq(pirq->reg))
+ vm_isa_deassert_irq(ctx, pirq->reg & PIRQ_IRQ, -1);
+ pirq->reg = val & (PIRQ_DIS | PIRQ_IRQ);
+ if (pirq->active_count != 0 && pirq_valid_irq(pirq->reg))
+ vm_isa_assert_irq(ctx, pirq->reg & PIRQ_IRQ, -1);
+ }
+ pthread_mutex_unlock(&pirq->lock);
+}
+
+void
+pci_irq_reserve(int irq)
+{
+
+ assert(irq >= 0 && irq < nitems(irq_counts));
+ assert(pirq_cold);
+ assert(irq_counts[irq] == 0 || irq_counts[irq] == IRQ_DISABLED);
+ irq_counts[irq] = IRQ_DISABLED;
+}
+
+void
+pci_irq_use(int irq)
+{
+
+ assert(irq >= 0 && irq < nitems(irq_counts));
+ assert(pirq_cold);
+ assert(irq_counts[irq] != IRQ_DISABLED);
+ irq_counts[irq]++;
+}
+
+void
+pci_irq_init(struct vmctx *ctx)
+{
+ int i;
+
+ for (i = 0; i < nitems(pirqs); i++) {
+ pirqs[i].reg = PIRQ_DIS;
+ pirqs[i].use_count = 0;
+ pirqs[i].active_count = 0;
+ pthread_mutex_init(&pirqs[i].lock, NULL);
+ }
+ for (i = 0; i < nitems(irq_counts); i++) {
+ if (IRQ_PERMITTED(i))
+ irq_counts[i] = 0;
+ else
+ irq_counts[i] = IRQ_DISABLED;
+ }
+}
+
+void
+pci_irq_assert(struct pci_devinst *pi)
+{
+ struct pirq *pirq;
+
+ if (pi->pi_lintr.pirq_pin > 0) {
+ assert(pi->pi_lintr.pirq_pin <= nitems(pirqs));
+ pirq = &pirqs[pi->pi_lintr.pirq_pin - 1];
+ pthread_mutex_lock(&pirq->lock);
+ pirq->active_count++;
+ if (pirq->active_count == 1 && pirq_valid_irq(pirq->reg)) {
+ vm_isa_assert_irq(pi->pi_vmctx, pirq->reg & PIRQ_IRQ,
+ pi->pi_lintr.ioapic_irq);
+ pthread_mutex_unlock(&pirq->lock);
+ return;
+ }
+ pthread_mutex_unlock(&pirq->lock);
+ }
+ vm_ioapic_assert_irq(pi->pi_vmctx, pi->pi_lintr.ioapic_irq);
+}
+
+void
+pci_irq_deassert(struct pci_devinst *pi)
+{
+ struct pirq *pirq;
+
+ if (pi->pi_lintr.pirq_pin > 0) {
+ assert(pi->pi_lintr.pirq_pin <= nitems(pirqs));
+ pirq = &pirqs[pi->pi_lintr.pirq_pin - 1];
+ pthread_mutex_lock(&pirq->lock);
+ pirq->active_count--;
+ if (pirq->active_count == 0 && pirq_valid_irq(pirq->reg)) {
+ vm_isa_deassert_irq(pi->pi_vmctx, pirq->reg & PIRQ_IRQ,
+ pi->pi_lintr.ioapic_irq);
+ pthread_mutex_unlock(&pirq->lock);
+ return;
+ }
+ pthread_mutex_unlock(&pirq->lock);
+ }
+ vm_ioapic_deassert_irq(pi->pi_vmctx, pi->pi_lintr.ioapic_irq);
+}
+
+int
+pirq_alloc_pin(struct vmctx *ctx)
+{
+ int best_count, best_irq, best_pin, irq, pin;
+
+ pirq_cold = 0;
+
+ /* First, find the least-used PIRQ pin. */
+ best_pin = 0;
+ best_count = pirqs[0].use_count;
+ for (pin = 1; pin < nitems(pirqs); pin++) {
+ if (pirqs[pin].use_count < best_count) {
+ best_pin = pin;
+ best_count = pirqs[pin].use_count;
+ }
+ }
+ pirqs[best_pin].use_count++;
+
+ /* Second, route this pin to an IRQ. */
+ if (pirqs[best_pin].reg == PIRQ_DIS) {
+ best_irq = -1;
+ best_count = 0;
+ for (irq = 0; irq < nitems(irq_counts); irq++) {
+ if (irq_counts[irq] == IRQ_DISABLED)
+ continue;
+ if (best_irq == -1 || irq_counts[irq] < best_count) {
+ best_irq = irq;
+ best_count = irq_counts[irq];
+ }
+ }
+ assert(best_irq >= 0);
+ irq_counts[best_irq]++;
+ pirqs[best_pin].reg = best_irq;
+ vm_isa_set_irq_trigger(ctx, best_irq, LEVEL_TRIGGER);
+ }
+
+ return (best_pin + 1);
+}
+
+int
+pirq_irq(int pin)
+{
+ assert(pin > 0 && pin <= nitems(pirqs));
+ return (pirqs[pin - 1].reg & PIRQ_IRQ);
+}
+
+/* XXX: Generate $PIR table. */
+
+static void
+pirq_dsdt(void)
+{
+ char *irq_prs, *old;
+ int irq, pin;
+
+ irq_prs = NULL;
+ for (irq = 0; irq < nitems(irq_counts); irq++) {
+ if (!IRQ_PERMITTED(irq))
+ continue;
+ if (irq_prs == NULL)
+ asprintf(&irq_prs, "%d", irq);
+ else {
+ old = irq_prs;
+ asprintf(&irq_prs, "%s,%d", old, irq);
+ free(old);
+ }
+ }
+
+ /*
+ * A helper method to validate a link register's value. This
+ * duplicates pirq_valid_irq().
+ */
+ dsdt_line("");
+ dsdt_line("Method (PIRV, 1, NotSerialized)");
+ dsdt_line("{");
+ dsdt_line(" If (And (Arg0, 0x%02X))", PIRQ_DIS);
+ dsdt_line(" {");
+ dsdt_line(" Return (0x00)");
+ dsdt_line(" }");
+ dsdt_line(" And (Arg0, 0x%02X, Local0)", PIRQ_IRQ);
+ dsdt_line(" If (LLess (Local0, 0x03))");
+ dsdt_line(" {");
+ dsdt_line(" Return (0x00)");
+ dsdt_line(" }");
+ dsdt_line(" If (LEqual (Local0, 0x08))");
+ dsdt_line(" {");
+ dsdt_line(" Return (0x00)");
+ dsdt_line(" }");
+ dsdt_line(" If (LEqual (Local0, 0x0D))");
+ dsdt_line(" {");
+ dsdt_line(" Return (0x00)");
+ dsdt_line(" }");
+ dsdt_line(" Return (0x01)");
+ dsdt_line("}");
+
+ for (pin = 0; pin < nitems(pirqs); pin++) {
+ dsdt_line("");
+ dsdt_line("Device (LNK%c)", 'A' + pin);
+ dsdt_line("{");
+ dsdt_line(" Name (_HID, EisaId (\"PNP0C0F\"))");
+ dsdt_line(" Name (_UID, 0x%02X)", pin + 1);
+ dsdt_line(" Method (_STA, 0, NotSerialized)");
+ dsdt_line(" {");
+ dsdt_line(" If (PIRV (PIR%c))", 'A' + pin);
+ dsdt_line(" {");
+ dsdt_line(" Return (0x0B)");
+ dsdt_line(" }");
+ dsdt_line(" Else");
+ dsdt_line(" {");
+ dsdt_line(" Return (0x09)");
+ dsdt_line(" }");
+ dsdt_line(" }");
+ dsdt_line(" Name (_PRS, ResourceTemplate ()");
+ dsdt_line(" {");
+ dsdt_line(" IRQ (Level, ActiveLow, Shared, )");
+ dsdt_line(" {%s}", irq_prs);
+ dsdt_line(" })");
+ dsdt_line(" Name (CB%02X, ResourceTemplate ()", pin + 1);
+ dsdt_line(" {");
+ dsdt_line(" IRQ (Level, ActiveLow, Shared, )");
+ dsdt_line(" {}");
+ dsdt_line(" })");
+ dsdt_line(" CreateWordField (CB%02X, 0x01, CIR%c)",
+ pin + 1, 'A' + pin);
+ dsdt_line(" Method (_CRS, 0, NotSerialized)");
+ dsdt_line(" {");
+ dsdt_line(" And (PIR%c, 0x%02X, Local0)", 'A' + pin,
+ PIRQ_DIS | PIRQ_IRQ);
+ dsdt_line(" If (PIRV (Local0))");
+ dsdt_line(" {");
+ dsdt_line(" ShiftLeft (0x01, Local0, CIR%c)", 'A' + pin);
+ dsdt_line(" }");
+ dsdt_line(" Else");
+ dsdt_line(" {");
+ dsdt_line(" Store (0x00, CIR%c)", 'A' + pin);
+ dsdt_line(" }");
+ dsdt_line(" Return (CB%02X)", pin + 1);
+ dsdt_line(" }");
+ dsdt_line(" Method (_DIS, 0, NotSerialized)");
+ dsdt_line(" {");
+ dsdt_line(" Store (0x80, PIR%c)", 'A' + pin);
+ dsdt_line(" }");
+ dsdt_line(" Method (_SRS, 1, NotSerialized)");
+ dsdt_line(" {");
+ dsdt_line(" CreateWordField (Arg0, 0x01, SIR%c)", 'A' + pin);
+ dsdt_line(" FindSetRightBit (SIR%c, Local0)", 'A' + pin);
+ dsdt_line(" Store (Decrement (Local0), PIR%c)", 'A' + pin);
+ dsdt_line(" }");
+ dsdt_line("}");
+ }
+ free(irq_prs);
+}
+LPC_DSDT(pirq_dsdt);
diff --git a/usr.sbin/bhyve/pci_irq.h b/usr.sbin/bhyve/pci_irq.h
new file mode 100644
index 0000000..24f9c99
--- /dev/null
+++ b/usr.sbin/bhyve/pci_irq.h
@@ -0,0 +1,45 @@
+/*-
+ * Copyright (c) 2014 Hudson River Trading LLC
+ * Written by: John H. 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 __PCI_IRQ_H__
+#define __PCI_IRQ_H__
+
+struct pci_devinst;
+
+void pci_irq_assert(struct pci_devinst *pi);
+void pci_irq_deassert(struct pci_devinst *pi);
+void pci_irq_init(struct vmctx *ctx);
+void pci_irq_reserve(int irq);
+void pci_irq_use(int irq);
+int pirq_alloc_pin(struct vmctx *ctx);
+int pirq_irq(int pin);
+uint8_t pirq_read(int pin);
+void pirq_write(struct vmctx *ctx, int pin, uint8_t val);
+
+#endif
diff --git a/usr.sbin/bhyve/pci_lpc.c b/usr.sbin/bhyve/pci_lpc.c
new file mode 100644
index 0000000..2203a00
--- /dev/null
+++ b/usr.sbin/bhyve/pci_lpc.c
@@ -0,0 +1,450 @@
+/*-
+ * Copyright (c) 2013 Neel Natu <neel@freebsd.org>
+ * Copyright (c) 2013 Tycho Nightingale <tycho.nightingale@pluribusnetworks.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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <sys/types.h>
+#include <machine/vmm.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <vmmapi.h>
+
+#include "acpi.h"
+#include "bootrom.h"
+#include "inout.h"
+#include "pci_emul.h"
+#include "pci_irq.h"
+#include "pci_lpc.h"
+#include "uart_emul.h"
+
+#define IO_ICU1 0x20
+#define IO_ICU2 0xA0
+
+SET_DECLARE(lpc_dsdt_set, struct lpc_dsdt);
+SET_DECLARE(lpc_sysres_set, struct lpc_sysres);
+
+#define ELCR_PORT 0x4d0
+SYSRES_IO(ELCR_PORT, 2);
+
+#define IO_TIMER1_PORT 0x40
+
+#define NMISC_PORT 0x61
+SYSRES_IO(NMISC_PORT, 1);
+
+static struct pci_devinst *lpc_bridge;
+
+static const char *romfile;
+
+#define LPC_UART_NUM 2
+static struct lpc_uart_softc {
+ struct uart_softc *uart_softc;
+ const char *opts;
+ int iobase;
+ int irq;
+ int enabled;
+} lpc_uart_softc[LPC_UART_NUM];
+
+static const char *lpc_uart_names[LPC_UART_NUM] = { "COM1", "COM2" };
+
+/*
+ * LPC device configuration is in the following form:
+ * <lpc_device_name>[,<options>]
+ * For e.g. "com1,stdio" or "bootrom,/var/romfile"
+ */
+int
+lpc_device_parse(const char *opts)
+{
+ int unit, error;
+ char *str, *cpy, *lpcdev;
+
+ error = -1;
+ str = cpy = strdup(opts);
+ lpcdev = strsep(&str, ",");
+ if (lpcdev != NULL) {
+ if (strcasecmp(lpcdev, "bootrom") == 0) {
+ romfile = str;
+ error = 0;
+ goto done;
+ }
+ for (unit = 0; unit < LPC_UART_NUM; unit++) {
+ if (strcasecmp(lpcdev, lpc_uart_names[unit]) == 0) {
+ lpc_uart_softc[unit].opts = str;
+ error = 0;
+ goto done;
+ }
+ }
+ }
+
+done:
+ if (error)
+ free(cpy);
+
+ return (error);
+}
+
+const char *
+lpc_bootrom(void)
+{
+
+ return (romfile);
+}
+
+static void
+lpc_uart_intr_assert(void *arg)
+{
+ struct lpc_uart_softc *sc = arg;
+
+ assert(sc->irq >= 0);
+
+ vm_isa_pulse_irq(lpc_bridge->pi_vmctx, sc->irq, sc->irq);
+}
+
+static void
+lpc_uart_intr_deassert(void *arg)
+{
+ /*
+ * The COM devices on the LPC bus generate edge triggered interrupts,
+ * so nothing more to do here.
+ */
+}
+
+static int
+lpc_uart_io_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
+ uint32_t *eax, void *arg)
+{
+ int offset;
+ struct lpc_uart_softc *sc = arg;
+
+ offset = port - sc->iobase;
+
+ switch (bytes) {
+ case 1:
+ if (in)
+ *eax = uart_read(sc->uart_softc, offset);
+ else
+ uart_write(sc->uart_softc, offset, *eax);
+ break;
+ case 2:
+ if (in) {
+ *eax = uart_read(sc->uart_softc, offset);
+ *eax |= uart_read(sc->uart_softc, offset + 1) << 8;
+ } else {
+ uart_write(sc->uart_softc, offset, *eax);
+ uart_write(sc->uart_softc, offset + 1, *eax >> 8);
+ }
+ break;
+ default:
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+lpc_init(struct vmctx *ctx)
+{
+ struct lpc_uart_softc *sc;
+ struct inout_port iop;
+ const char *name;
+ int unit, error;
+
+ if (romfile != NULL) {
+ error = bootrom_init(ctx, romfile);
+ if (error)
+ return (error);
+ }
+
+ /* COM1 and COM2 */
+ for (unit = 0; unit < LPC_UART_NUM; unit++) {
+ sc = &lpc_uart_softc[unit];
+ name = lpc_uart_names[unit];
+
+ if (uart_legacy_alloc(unit, &sc->iobase, &sc->irq) != 0) {
+ fprintf(stderr, "Unable to allocate resources for "
+ "LPC device %s\n", name);
+ return (-1);
+ }
+ pci_irq_reserve(sc->irq);
+
+ sc->uart_softc = uart_init(lpc_uart_intr_assert,
+ lpc_uart_intr_deassert, sc);
+
+ if (uart_set_backend(sc->uart_softc, sc->opts) != 0) {
+ fprintf(stderr, "Unable to initialize backend '%s' "
+ "for LPC device %s\n", sc->opts, name);
+ return (-1);
+ }
+
+ bzero(&iop, sizeof(struct inout_port));
+ iop.name = name;
+ iop.port = sc->iobase;
+ iop.size = UART_IO_BAR_SIZE;
+ iop.flags = IOPORT_F_INOUT;
+ iop.handler = lpc_uart_io_handler;
+ iop.arg = sc;
+
+ error = register_inout(&iop);
+ assert(error == 0);
+ sc->enabled = 1;
+ }
+
+ return (0);
+}
+
+static void
+pci_lpc_write_dsdt(struct pci_devinst *pi)
+{
+ struct lpc_dsdt **ldpp, *ldp;
+
+ dsdt_line("");
+ dsdt_line("Device (ISA)");
+ dsdt_line("{");
+ dsdt_line(" Name (_ADR, 0x%04X%04X)", pi->pi_slot, pi->pi_func);
+ dsdt_line(" OperationRegion (LPCR, PCI_Config, 0x00, 0x100)");
+ dsdt_line(" Field (LPCR, AnyAcc, NoLock, Preserve)");
+ dsdt_line(" {");
+ dsdt_line(" Offset (0x60),");
+ dsdt_line(" PIRA, 8,");
+ dsdt_line(" PIRB, 8,");
+ dsdt_line(" PIRC, 8,");
+ dsdt_line(" PIRD, 8,");
+ dsdt_line(" Offset (0x68),");
+ dsdt_line(" PIRE, 8,");
+ dsdt_line(" PIRF, 8,");
+ dsdt_line(" PIRG, 8,");
+ dsdt_line(" PIRH, 8");
+ dsdt_line(" }");
+ dsdt_line("");
+
+ dsdt_indent(1);
+ SET_FOREACH(ldpp, lpc_dsdt_set) {
+ ldp = *ldpp;
+ ldp->handler();
+ }
+
+ dsdt_line("");
+ dsdt_line("Device (PIC)");
+ dsdt_line("{");
+ dsdt_line(" Name (_HID, EisaId (\"PNP0000\"))");
+ dsdt_line(" Name (_CRS, ResourceTemplate ()");
+ dsdt_line(" {");
+ dsdt_indent(2);
+ dsdt_fixed_ioport(IO_ICU1, 2);
+ dsdt_fixed_ioport(IO_ICU2, 2);
+ dsdt_fixed_irq(2);
+ dsdt_unindent(2);
+ dsdt_line(" })");
+ dsdt_line("}");
+
+ dsdt_line("");
+ dsdt_line("Device (TIMR)");
+ dsdt_line("{");
+ dsdt_line(" Name (_HID, EisaId (\"PNP0100\"))");
+ dsdt_line(" Name (_CRS, ResourceTemplate ()");
+ dsdt_line(" {");
+ dsdt_indent(2);
+ dsdt_fixed_ioport(IO_TIMER1_PORT, 4);
+ dsdt_fixed_irq(0);
+ dsdt_unindent(2);
+ dsdt_line(" })");
+ dsdt_line("}");
+ dsdt_unindent(1);
+
+ dsdt_line("}");
+}
+
+static void
+pci_lpc_sysres_dsdt(void)
+{
+ struct lpc_sysres **lspp, *lsp;
+
+ dsdt_line("");
+ dsdt_line("Device (SIO)");
+ dsdt_line("{");
+ dsdt_line(" Name (_HID, EisaId (\"PNP0C02\"))");
+ dsdt_line(" Name (_CRS, ResourceTemplate ()");
+ dsdt_line(" {");
+
+ dsdt_indent(2);
+ SET_FOREACH(lspp, lpc_sysres_set) {
+ lsp = *lspp;
+ switch (lsp->type) {
+ case LPC_SYSRES_IO:
+ dsdt_fixed_ioport(lsp->base, lsp->length);
+ break;
+ case LPC_SYSRES_MEM:
+ dsdt_fixed_mem32(lsp->base, lsp->length);
+ break;
+ }
+ }
+ dsdt_unindent(2);
+
+ dsdt_line(" })");
+ dsdt_line("}");
+}
+LPC_DSDT(pci_lpc_sysres_dsdt);
+
+static void
+pci_lpc_uart_dsdt(void)
+{
+ struct lpc_uart_softc *sc;
+ int unit;
+
+ for (unit = 0; unit < LPC_UART_NUM; unit++) {
+ sc = &lpc_uart_softc[unit];
+ if (!sc->enabled)
+ continue;
+ dsdt_line("");
+ dsdt_line("Device (%s)", lpc_uart_names[unit]);
+ dsdt_line("{");
+ dsdt_line(" Name (_HID, EisaId (\"PNP0501\"))");
+ dsdt_line(" Name (_UID, %d)", unit + 1);
+ dsdt_line(" Name (_CRS, ResourceTemplate ()");
+ dsdt_line(" {");
+ dsdt_indent(2);
+ dsdt_fixed_ioport(sc->iobase, UART_IO_BAR_SIZE);
+ dsdt_fixed_irq(sc->irq);
+ dsdt_unindent(2);
+ dsdt_line(" })");
+ dsdt_line("}");
+ }
+}
+LPC_DSDT(pci_lpc_uart_dsdt);
+
+static int
+pci_lpc_cfgwrite(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
+ int coff, int bytes, uint32_t val)
+{
+ int pirq_pin;
+
+ if (bytes == 1) {
+ pirq_pin = 0;
+ if (coff >= 0x60 && coff <= 0x63)
+ pirq_pin = coff - 0x60 + 1;
+ if (coff >= 0x68 && coff <= 0x6b)
+ pirq_pin = coff - 0x68 + 5;
+ if (pirq_pin != 0) {
+ pirq_write(ctx, pirq_pin, val);
+ pci_set_cfgdata8(pi, coff, pirq_read(pirq_pin));
+ return (0);
+ }
+ }
+ return (-1);
+}
+
+static void
+pci_lpc_write(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
+ int baridx, uint64_t offset, int size, uint64_t value)
+{
+}
+
+static uint64_t
+pci_lpc_read(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
+ int baridx, uint64_t offset, int size)
+{
+ return (0);
+}
+
+#define LPC_DEV 0x7000
+#define LPC_VENDOR 0x8086
+
+static int
+pci_lpc_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts)
+{
+
+ /*
+ * Do not allow more than one LPC bridge to be configured.
+ */
+ if (lpc_bridge != NULL) {
+ fprintf(stderr, "Only one LPC bridge is allowed.\n");
+ return (-1);
+ }
+
+ /*
+ * Enforce that the LPC can only be configured on bus 0. This
+ * simplifies the ACPI DSDT because it can provide a decode for
+ * all legacy i/o ports behind bus 0.
+ */
+ if (pi->pi_bus != 0) {
+ fprintf(stderr, "LPC bridge can be present only on bus 0.\n");
+ return (-1);
+ }
+
+ if (lpc_init(ctx) != 0)
+ return (-1);
+
+ /* initialize config space */
+ pci_set_cfgdata16(pi, PCIR_DEVICE, LPC_DEV);
+ pci_set_cfgdata16(pi, PCIR_VENDOR, LPC_VENDOR);
+ pci_set_cfgdata8(pi, PCIR_CLASS, PCIC_BRIDGE);
+ pci_set_cfgdata8(pi, PCIR_SUBCLASS, PCIS_BRIDGE_ISA);
+
+ lpc_bridge = pi;
+
+ return (0);
+}
+
+char *
+lpc_pirq_name(int pin)
+{
+ char *name;
+
+ if (lpc_bridge == NULL)
+ return (NULL);
+ asprintf(&name, "\\_SB.PC00.ISA.LNK%c,", 'A' + pin - 1);
+ return (name);
+}
+
+void
+lpc_pirq_routed(void)
+{
+ int pin;
+
+ if (lpc_bridge == NULL)
+ return;
+
+ for (pin = 0; pin < 4; pin++)
+ pci_set_cfgdata8(lpc_bridge, 0x60 + pin, pirq_read(pin + 1));
+ for (pin = 0; pin < 4; pin++)
+ pci_set_cfgdata8(lpc_bridge, 0x68 + pin, pirq_read(pin + 5));
+}
+
+struct pci_devemu pci_de_lpc = {
+ .pe_emu = "lpc",
+ .pe_init = pci_lpc_init,
+ .pe_write_dsdt = pci_lpc_write_dsdt,
+ .pe_cfgwrite = pci_lpc_cfgwrite,
+ .pe_barwrite = pci_lpc_write,
+ .pe_barread = pci_lpc_read
+};
+PCI_EMUL_SET(pci_de_lpc);
diff --git a/usr.sbin/bhyve/pci_lpc.h b/usr.sbin/bhyve/pci_lpc.h
new file mode 100644
index 0000000..431f5cf
--- /dev/null
+++ b/usr.sbin/bhyve/pci_lpc.h
@@ -0,0 +1,73 @@
+/*-
+ * Copyright (c) 2013 Neel Natu <neel@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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 _LPC_H_
+#define _LPC_H_
+
+#include <sys/linker_set.h>
+
+typedef void (*lpc_write_dsdt_t)(void);
+
+struct lpc_dsdt {
+ lpc_write_dsdt_t handler;
+};
+
+#define LPC_DSDT(handler) \
+ static struct lpc_dsdt __CONCAT(__lpc_dsdt, __LINE__) = { \
+ (handler), \
+ }; \
+ DATA_SET(lpc_dsdt_set, __CONCAT(__lpc_dsdt, __LINE__))
+
+enum lpc_sysres_type {
+ LPC_SYSRES_IO,
+ LPC_SYSRES_MEM
+};
+
+struct lpc_sysres {
+ enum lpc_sysres_type type;
+ uint32_t base;
+ uint32_t length;
+};
+
+#define LPC_SYSRES(type, base, length) \
+ static struct lpc_sysres __CONCAT(__lpc_sysres, __LINE__) = { \
+ (type), \
+ (base), \
+ (length) \
+ }; \
+ DATA_SET(lpc_sysres_set, __CONCAT(__lpc_sysres, __LINE__))
+
+#define SYSRES_IO(base, length) LPC_SYSRES(LPC_SYSRES_IO, base, length)
+#define SYSRES_MEM(base, length) LPC_SYSRES(LPC_SYSRES_MEM, base, length)
+
+int lpc_device_parse(const char *opt);
+char *lpc_pirq_name(int pin);
+void lpc_pirq_routed(void);
+const char *lpc_bootrom(void);
+
+#endif
diff --git a/usr.sbin/bhyve/pci_passthru.c b/usr.sbin/bhyve/pci_passthru.c
new file mode 100644
index 0000000..5b52b05
--- /dev/null
+++ b/usr.sbin/bhyve/pci_passthru.c
@@ -0,0 +1,796 @@
+/*-
+ * Copyright (c) 2011 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <sys/param.h>
+#include <sys/types.h>
+#include <sys/pciio.h>
+#include <sys/ioctl.h>
+
+#include <dev/io/iodev.h>
+#include <dev/pci/pcireg.h>
+
+#include <machine/iodev.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <machine/vmm.h>
+#include <vmmapi.h>
+#include "pci_emul.h"
+#include "mem.h"
+
+#ifndef _PATH_DEVPCI
+#define _PATH_DEVPCI "/dev/pci"
+#endif
+
+#ifndef _PATH_DEVIO
+#define _PATH_DEVIO "/dev/io"
+#endif
+
+#define LEGACY_SUPPORT 1
+
+#define MSIX_TABLE_COUNT(ctrl) (((ctrl) & PCIM_MSIXCTRL_TABLE_SIZE) + 1)
+#define MSIX_CAPLEN 12
+
+static int pcifd = -1;
+static int iofd = -1;
+
+struct passthru_softc {
+ struct pci_devinst *psc_pi;
+ struct pcibar psc_bar[PCI_BARMAX + 1];
+ struct {
+ int capoff;
+ int msgctrl;
+ int emulated;
+ } psc_msi;
+ struct {
+ int capoff;
+ } psc_msix;
+ struct pcisel psc_sel;
+};
+
+static int
+msi_caplen(int msgctrl)
+{
+ int len;
+
+ len = 10; /* minimum length of msi capability */
+
+ if (msgctrl & PCIM_MSICTRL_64BIT)
+ len += 4;
+
+#if 0
+ /*
+ * Ignore the 'mask' and 'pending' bits in the MSI capability.
+ * We'll let the guest manipulate them directly.
+ */
+ if (msgctrl & PCIM_MSICTRL_VECTOR)
+ len += 10;
+#endif
+
+ return (len);
+}
+
+static uint32_t
+read_config(const struct pcisel *sel, long reg, int width)
+{
+ struct pci_io pi;
+
+ bzero(&pi, sizeof(pi));
+ pi.pi_sel = *sel;
+ pi.pi_reg = reg;
+ pi.pi_width = width;
+
+ if (ioctl(pcifd, PCIOCREAD, &pi) < 0)
+ return (0); /* XXX */
+ else
+ return (pi.pi_data);
+}
+
+static void
+write_config(const struct pcisel *sel, long reg, int width, uint32_t data)
+{
+ struct pci_io pi;
+
+ bzero(&pi, sizeof(pi));
+ pi.pi_sel = *sel;
+ pi.pi_reg = reg;
+ pi.pi_width = width;
+ pi.pi_data = data;
+
+ (void)ioctl(pcifd, PCIOCWRITE, &pi); /* XXX */
+}
+
+#ifdef LEGACY_SUPPORT
+static int
+passthru_add_msicap(struct pci_devinst *pi, int msgnum, int nextptr)
+{
+ int capoff, i;
+ struct msicap msicap;
+ u_char *capdata;
+
+ pci_populate_msicap(&msicap, msgnum, nextptr);
+
+ /*
+ * XXX
+ * Copy the msi capability structure in the last 16 bytes of the
+ * config space. This is wrong because it could shadow something
+ * useful to the device.
+ */
+ capoff = 256 - roundup(sizeof(msicap), 4);
+ capdata = (u_char *)&msicap;
+ for (i = 0; i < sizeof(msicap); i++)
+ pci_set_cfgdata8(pi, capoff + i, capdata[i]);
+
+ return (capoff);
+}
+#endif /* LEGACY_SUPPORT */
+
+static int
+cfginitmsi(struct passthru_softc *sc)
+{
+ int i, ptr, capptr, cap, sts, caplen, table_size;
+ uint32_t u32;
+ struct pcisel sel;
+ struct pci_devinst *pi;
+ struct msixcap msixcap;
+ uint32_t *msixcap_ptr;
+
+ pi = sc->psc_pi;
+ sel = sc->psc_sel;
+
+ /*
+ * Parse the capabilities and cache the location of the MSI
+ * and MSI-X capabilities.
+ */
+ sts = read_config(&sel, PCIR_STATUS, 2);
+ if (sts & PCIM_STATUS_CAPPRESENT) {
+ ptr = read_config(&sel, PCIR_CAP_PTR, 1);
+ while (ptr != 0 && ptr != 0xff) {
+ cap = read_config(&sel, ptr + PCICAP_ID, 1);
+ if (cap == PCIY_MSI) {
+ /*
+ * Copy the MSI capability into the config
+ * space of the emulated pci device
+ */
+ sc->psc_msi.capoff = ptr;
+ sc->psc_msi.msgctrl = read_config(&sel,
+ ptr + 2, 2);
+ sc->psc_msi.emulated = 0;
+ caplen = msi_caplen(sc->psc_msi.msgctrl);
+ capptr = ptr;
+ while (caplen > 0) {
+ u32 = read_config(&sel, capptr, 4);
+ pci_set_cfgdata32(pi, capptr, u32);
+ caplen -= 4;
+ capptr += 4;
+ }
+ } else if (cap == PCIY_MSIX) {
+ /*
+ * Copy the MSI-X capability
+ */
+ sc->psc_msix.capoff = ptr;
+ caplen = 12;
+ msixcap_ptr = (uint32_t*) &msixcap;
+ capptr = ptr;
+ while (caplen > 0) {
+ u32 = read_config(&sel, capptr, 4);
+ *msixcap_ptr = u32;
+ pci_set_cfgdata32(pi, capptr, u32);
+ caplen -= 4;
+ capptr += 4;
+ msixcap_ptr++;
+ }
+ }
+ ptr = read_config(&sel, ptr + PCICAP_NEXTPTR, 1);
+ }
+ }
+
+ if (sc->psc_msix.capoff != 0) {
+ pi->pi_msix.pba_bar =
+ msixcap.pba_info & PCIM_MSIX_BIR_MASK;
+ pi->pi_msix.pba_offset =
+ msixcap.pba_info & ~PCIM_MSIX_BIR_MASK;
+ pi->pi_msix.table_bar =
+ msixcap.table_info & PCIM_MSIX_BIR_MASK;
+ pi->pi_msix.table_offset =
+ msixcap.table_info & ~PCIM_MSIX_BIR_MASK;
+ pi->pi_msix.table_count = MSIX_TABLE_COUNT(msixcap.msgctrl);
+ pi->pi_msix.pba_size = PBA_SIZE(pi->pi_msix.table_count);
+
+ /* Allocate the emulated MSI-X table array */
+ table_size = pi->pi_msix.table_count * MSIX_TABLE_ENTRY_SIZE;
+ pi->pi_msix.table = calloc(1, table_size);
+
+ /* Mask all table entries */
+ for (i = 0; i < pi->pi_msix.table_count; i++) {
+ pi->pi_msix.table[i].vector_control |=
+ PCIM_MSIX_VCTRL_MASK;
+ }
+ }
+
+#ifdef LEGACY_SUPPORT
+ /*
+ * If the passthrough device does not support MSI then craft a
+ * MSI capability for it. We link the new MSI capability at the
+ * head of the list of capabilities.
+ */
+ if ((sts & PCIM_STATUS_CAPPRESENT) != 0 && sc->psc_msi.capoff == 0) {
+ int origptr, msiptr;
+ origptr = read_config(&sel, PCIR_CAP_PTR, 1);
+ msiptr = passthru_add_msicap(pi, 1, origptr);
+ sc->psc_msi.capoff = msiptr;
+ sc->psc_msi.msgctrl = pci_get_cfgdata16(pi, msiptr + 2);
+ sc->psc_msi.emulated = 1;
+ pci_set_cfgdata8(pi, PCIR_CAP_PTR, msiptr);
+ }
+#endif
+
+ /* Make sure one of the capabilities is present */
+ if (sc->psc_msi.capoff == 0 && sc->psc_msix.capoff == 0)
+ return (-1);
+ else
+ return (0);
+}
+
+static uint64_t
+msix_table_read(struct passthru_softc *sc, uint64_t offset, int size)
+{
+ struct pci_devinst *pi;
+ struct msix_table_entry *entry;
+ uint8_t *src8;
+ uint16_t *src16;
+ uint32_t *src32;
+ uint64_t *src64;
+ uint64_t data;
+ size_t entry_offset;
+ int index;
+
+ pi = sc->psc_pi;
+ if (offset < pi->pi_msix.table_offset)
+ return (-1);
+
+ offset -= pi->pi_msix.table_offset;
+ index = offset / MSIX_TABLE_ENTRY_SIZE;
+ if (index >= pi->pi_msix.table_count)
+ return (-1);
+
+ entry = &pi->pi_msix.table[index];
+ entry_offset = offset % MSIX_TABLE_ENTRY_SIZE;
+
+ switch(size) {
+ case 1:
+ src8 = (uint8_t *)((void *)entry + entry_offset);
+ data = *src8;
+ break;
+ case 2:
+ src16 = (uint16_t *)((void *)entry + entry_offset);
+ data = *src16;
+ break;
+ case 4:
+ src32 = (uint32_t *)((void *)entry + entry_offset);
+ data = *src32;
+ break;
+ case 8:
+ src64 = (uint64_t *)((void *)entry + entry_offset);
+ data = *src64;
+ break;
+ default:
+ return (-1);
+ }
+
+ return (data);
+}
+
+static void
+msix_table_write(struct vmctx *ctx, int vcpu, struct passthru_softc *sc,
+ uint64_t offset, int size, uint64_t data)
+{
+ struct pci_devinst *pi;
+ struct msix_table_entry *entry;
+ uint32_t *dest;
+ size_t entry_offset;
+ uint32_t vector_control;
+ int error, index;
+
+ pi = sc->psc_pi;
+ if (offset < pi->pi_msix.table_offset)
+ return;
+
+ offset -= pi->pi_msix.table_offset;
+ index = offset / MSIX_TABLE_ENTRY_SIZE;
+ if (index >= pi->pi_msix.table_count)
+ return;
+
+ entry = &pi->pi_msix.table[index];
+ entry_offset = offset % MSIX_TABLE_ENTRY_SIZE;
+
+ /* Only 4 byte naturally-aligned writes are supported */
+ assert(size == 4);
+ assert(entry_offset % 4 == 0);
+
+ vector_control = entry->vector_control;
+ dest = (uint32_t *)((void *)entry + entry_offset);
+ *dest = data;
+ /* If MSI-X hasn't been enabled, do nothing */
+ if (pi->pi_msix.enabled) {
+ /* If the entry is masked, don't set it up */
+ if ((entry->vector_control & PCIM_MSIX_VCTRL_MASK) == 0 ||
+ (vector_control & PCIM_MSIX_VCTRL_MASK) == 0) {
+ error = vm_setup_pptdev_msix(ctx, vcpu,
+ sc->psc_sel.pc_bus, sc->psc_sel.pc_dev,
+ sc->psc_sel.pc_func, index, entry->addr,
+ entry->msg_data, entry->vector_control);
+ }
+ }
+}
+
+static int
+init_msix_table(struct vmctx *ctx, struct passthru_softc *sc, uint64_t base)
+{
+ int b, s, f;
+ int error, idx;
+ size_t len, remaining;
+ uint32_t table_size, table_offset;
+ uint32_t pba_size, pba_offset;
+ vm_paddr_t start;
+ struct pci_devinst *pi = sc->psc_pi;
+
+ assert(pci_msix_table_bar(pi) >= 0 && pci_msix_pba_bar(pi) >= 0);
+
+ b = sc->psc_sel.pc_bus;
+ s = sc->psc_sel.pc_dev;
+ f = sc->psc_sel.pc_func;
+
+ /*
+ * If the MSI-X table BAR maps memory intended for
+ * other uses, it is at least assured that the table
+ * either resides in its own page within the region,
+ * or it resides in a page shared with only the PBA.
+ */
+ table_offset = rounddown2(pi->pi_msix.table_offset, 4096);
+
+ table_size = pi->pi_msix.table_offset - table_offset;
+ table_size += pi->pi_msix.table_count * MSIX_TABLE_ENTRY_SIZE;
+ table_size = roundup2(table_size, 4096);
+
+ if (pi->pi_msix.pba_bar == pi->pi_msix.table_bar) {
+ pba_offset = pi->pi_msix.pba_offset;
+ pba_size = pi->pi_msix.pba_size;
+ if (pba_offset >= table_offset + table_size ||
+ table_offset >= pba_offset + pba_size) {
+ /*
+ * The PBA can reside in the same BAR as the MSI-x
+ * tables as long as it does not overlap with any
+ * naturally aligned page occupied by the tables.
+ */
+ } else {
+ /* Need to also emulate the PBA, not supported yet */
+ printf("Unsupported MSI-X configuration: %d/%d/%d\n",
+ b, s, f);
+ return (-1);
+ }
+ }
+
+ idx = pi->pi_msix.table_bar;
+ start = pi->pi_bar[idx].addr;
+ remaining = pi->pi_bar[idx].size;
+
+ /* Map everything before the MSI-X table */
+ if (table_offset > 0) {
+ len = table_offset;
+ error = vm_map_pptdev_mmio(ctx, b, s, f, start, len, base);
+ if (error)
+ return (error);
+
+ base += len;
+ start += len;
+ remaining -= len;
+ }
+
+ /* Skip the MSI-X table */
+ base += table_size;
+ start += table_size;
+ remaining -= table_size;
+
+ /* Map everything beyond the end of the MSI-X table */
+ if (remaining > 0) {
+ len = remaining;
+ error = vm_map_pptdev_mmio(ctx, b, s, f, start, len, base);
+ if (error)
+ return (error);
+ }
+
+ return (0);
+}
+
+static int
+cfginitbar(struct vmctx *ctx, struct passthru_softc *sc)
+{
+ int i, error;
+ struct pci_devinst *pi;
+ struct pci_bar_io bar;
+ enum pcibar_type bartype;
+ uint64_t base, size;
+
+ pi = sc->psc_pi;
+
+ /*
+ * Initialize BAR registers
+ */
+ for (i = 0; i <= PCI_BARMAX; i++) {
+ bzero(&bar, sizeof(bar));
+ bar.pbi_sel = sc->psc_sel;
+ bar.pbi_reg = PCIR_BAR(i);
+
+ if (ioctl(pcifd, PCIOCGETBAR, &bar) < 0)
+ continue;
+
+ if (PCI_BAR_IO(bar.pbi_base)) {
+ bartype = PCIBAR_IO;
+ base = bar.pbi_base & PCIM_BAR_IO_BASE;
+ } else {
+ switch (bar.pbi_base & PCIM_BAR_MEM_TYPE) {
+ case PCIM_BAR_MEM_64:
+ bartype = PCIBAR_MEM64;
+ break;
+ default:
+ bartype = PCIBAR_MEM32;
+ break;
+ }
+ base = bar.pbi_base & PCIM_BAR_MEM_BASE;
+ }
+ size = bar.pbi_length;
+
+ if (bartype != PCIBAR_IO) {
+ if (((base | size) & PAGE_MASK) != 0) {
+ printf("passthru device %d/%d/%d BAR %d: "
+ "base %#lx or size %#lx not page aligned\n",
+ sc->psc_sel.pc_bus, sc->psc_sel.pc_dev,
+ sc->psc_sel.pc_func, i, base, size);
+ return (-1);
+ }
+ }
+
+ /* Cache information about the "real" BAR */
+ sc->psc_bar[i].type = bartype;
+ sc->psc_bar[i].size = size;
+ sc->psc_bar[i].addr = base;
+
+ /* Allocate the BAR in the guest I/O or MMIO space */
+ error = pci_emul_alloc_pbar(pi, i, base, bartype, size);
+ if (error)
+ return (-1);
+
+ /* The MSI-X table needs special handling */
+ if (i == pci_msix_table_bar(pi)) {
+ error = init_msix_table(ctx, sc, base);
+ if (error)
+ return (-1);
+ } else if (bartype != PCIBAR_IO) {
+ /* Map the physical BAR in the guest MMIO space */
+ error = vm_map_pptdev_mmio(ctx, sc->psc_sel.pc_bus,
+ sc->psc_sel.pc_dev, sc->psc_sel.pc_func,
+ pi->pi_bar[i].addr, pi->pi_bar[i].size, base);
+ if (error)
+ return (-1);
+ }
+
+ /*
+ * 64-bit BAR takes up two slots so skip the next one.
+ */
+ if (bartype == PCIBAR_MEM64) {
+ i++;
+ assert(i <= PCI_BARMAX);
+ sc->psc_bar[i].type = PCIBAR_MEMHI64;
+ }
+ }
+ return (0);
+}
+
+static int
+cfginit(struct vmctx *ctx, struct pci_devinst *pi, int bus, int slot, int func)
+{
+ int error;
+ struct passthru_softc *sc;
+
+ error = 1;
+ sc = pi->pi_arg;
+
+ bzero(&sc->psc_sel, sizeof(struct pcisel));
+ sc->psc_sel.pc_bus = bus;
+ sc->psc_sel.pc_dev = slot;
+ sc->psc_sel.pc_func = func;
+
+ if (cfginitmsi(sc) != 0)
+ goto done;
+
+ if (cfginitbar(ctx, sc) != 0)
+ goto done;
+
+ error = 0; /* success */
+done:
+ return (error);
+}
+
+static int
+passthru_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts)
+{
+ int bus, slot, func, error, memflags;
+ struct passthru_softc *sc;
+
+ sc = NULL;
+ error = 1;
+
+ memflags = vm_get_memflags(ctx);
+ if (!(memflags & VM_MEM_F_WIRED)) {
+ fprintf(stderr, "passthru requires guest memory to be wired\n");
+ goto done;
+ }
+
+ if (pcifd < 0) {
+ pcifd = open(_PATH_DEVPCI, O_RDWR, 0);
+ if (pcifd < 0)
+ goto done;
+ }
+
+ if (iofd < 0) {
+ iofd = open(_PATH_DEVIO, O_RDWR, 0);
+ if (iofd < 0)
+ goto done;
+ }
+
+ if (opts == NULL ||
+ sscanf(opts, "%d/%d/%d", &bus, &slot, &func) != 3)
+ goto done;
+
+ if (vm_assign_pptdev(ctx, bus, slot, func) != 0)
+ goto done;
+
+ sc = calloc(1, sizeof(struct passthru_softc));
+
+ pi->pi_arg = sc;
+ sc->psc_pi = pi;
+
+ /* initialize config space */
+ if ((error = cfginit(ctx, pi, bus, slot, func)) != 0)
+ goto done;
+
+ error = 0; /* success */
+done:
+ if (error) {
+ free(sc);
+ vm_unassign_pptdev(ctx, bus, slot, func);
+ }
+ return (error);
+}
+
+static int
+bar_access(int coff)
+{
+ if (coff >= PCIR_BAR(0) && coff < PCIR_BAR(PCI_BARMAX + 1))
+ return (1);
+ else
+ return (0);
+}
+
+static int
+msicap_access(struct passthru_softc *sc, int coff)
+{
+ int caplen;
+
+ if (sc->psc_msi.capoff == 0)
+ return (0);
+
+ caplen = msi_caplen(sc->psc_msi.msgctrl);
+
+ if (coff >= sc->psc_msi.capoff && coff < sc->psc_msi.capoff + caplen)
+ return (1);
+ else
+ return (0);
+}
+
+static int
+msixcap_access(struct passthru_softc *sc, int coff)
+{
+ if (sc->psc_msix.capoff == 0)
+ return (0);
+
+ return (coff >= sc->psc_msix.capoff &&
+ coff < sc->psc_msix.capoff + MSIX_CAPLEN);
+}
+
+static int
+passthru_cfgread(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
+ int coff, int bytes, uint32_t *rv)
+{
+ struct passthru_softc *sc;
+
+ sc = pi->pi_arg;
+
+ /*
+ * PCI BARs and MSI capability is emulated.
+ */
+ if (bar_access(coff) || msicap_access(sc, coff))
+ return (-1);
+
+#ifdef LEGACY_SUPPORT
+ /*
+ * Emulate PCIR_CAP_PTR if this device does not support MSI capability
+ * natively.
+ */
+ if (sc->psc_msi.emulated) {
+ if (coff >= PCIR_CAP_PTR && coff < PCIR_CAP_PTR + 4)
+ return (-1);
+ }
+#endif
+
+ /* Everything else just read from the device's config space */
+ *rv = read_config(&sc->psc_sel, coff, bytes);
+
+ return (0);
+}
+
+static int
+passthru_cfgwrite(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
+ int coff, int bytes, uint32_t val)
+{
+ int error, msix_table_entries, i;
+ struct passthru_softc *sc;
+
+ sc = pi->pi_arg;
+
+ /*
+ * PCI BARs are emulated
+ */
+ if (bar_access(coff))
+ return (-1);
+
+ /*
+ * MSI capability is emulated
+ */
+ if (msicap_access(sc, coff)) {
+ msicap_cfgwrite(pi, sc->psc_msi.capoff, coff, bytes, val);
+
+ error = vm_setup_pptdev_msi(ctx, vcpu, sc->psc_sel.pc_bus,
+ sc->psc_sel.pc_dev, sc->psc_sel.pc_func,
+ pi->pi_msi.addr, pi->pi_msi.msg_data,
+ pi->pi_msi.maxmsgnum);
+ if (error != 0) {
+ printf("vm_setup_pptdev_msi error %d\r\n", errno);
+ exit(1);
+ }
+ return (0);
+ }
+
+ if (msixcap_access(sc, coff)) {
+ msixcap_cfgwrite(pi, sc->psc_msix.capoff, coff, bytes, val);
+ if (pi->pi_msix.enabled) {
+ msix_table_entries = pi->pi_msix.table_count;
+ for (i = 0; i < msix_table_entries; i++) {
+ error = vm_setup_pptdev_msix(ctx, vcpu,
+ sc->psc_sel.pc_bus, sc->psc_sel.pc_dev,
+ sc->psc_sel.pc_func, i,
+ pi->pi_msix.table[i].addr,
+ pi->pi_msix.table[i].msg_data,
+ pi->pi_msix.table[i].vector_control);
+
+ if (error) {
+ printf("vm_setup_pptdev_msix error "
+ "%d\r\n", errno);
+ exit(1);
+ }
+ }
+ }
+ return (0);
+ }
+
+#ifdef LEGACY_SUPPORT
+ /*
+ * If this device does not support MSI natively then we cannot let
+ * the guest disable legacy interrupts from the device. It is the
+ * legacy interrupt that is triggering the virtual MSI to the guest.
+ */
+ if (sc->psc_msi.emulated && pci_msi_enabled(pi)) {
+ if (coff == PCIR_COMMAND && bytes == 2)
+ val &= ~PCIM_CMD_INTxDIS;
+ }
+#endif
+
+ write_config(&sc->psc_sel, coff, bytes, val);
+
+ return (0);
+}
+
+static void
+passthru_write(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int baridx,
+ uint64_t offset, int size, uint64_t value)
+{
+ struct passthru_softc *sc;
+ struct iodev_pio_req pio;
+
+ sc = pi->pi_arg;
+
+ if (baridx == pci_msix_table_bar(pi)) {
+ msix_table_write(ctx, vcpu, sc, offset, size, value);
+ } else {
+ assert(pi->pi_bar[baridx].type == PCIBAR_IO);
+ bzero(&pio, sizeof(struct iodev_pio_req));
+ pio.access = IODEV_PIO_WRITE;
+ pio.port = sc->psc_bar[baridx].addr + offset;
+ pio.width = size;
+ pio.val = value;
+
+ (void)ioctl(iofd, IODEV_PIO, &pio);
+ }
+}
+
+static uint64_t
+passthru_read(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int baridx,
+ uint64_t offset, int size)
+{
+ struct passthru_softc *sc;
+ struct iodev_pio_req pio;
+ uint64_t val;
+
+ sc = pi->pi_arg;
+
+ if (baridx == pci_msix_table_bar(pi)) {
+ val = msix_table_read(sc, offset, size);
+ } else {
+ assert(pi->pi_bar[baridx].type == PCIBAR_IO);
+ bzero(&pio, sizeof(struct iodev_pio_req));
+ pio.access = IODEV_PIO_READ;
+ pio.port = sc->psc_bar[baridx].addr + offset;
+ pio.width = size;
+ pio.val = 0;
+
+ (void)ioctl(iofd, IODEV_PIO, &pio);
+
+ val = pio.val;
+ }
+
+ return (val);
+}
+
+struct pci_devemu passthru = {
+ .pe_emu = "passthru",
+ .pe_init = passthru_init,
+ .pe_cfgwrite = passthru_cfgwrite,
+ .pe_cfgread = passthru_cfgread,
+ .pe_barwrite = passthru_write,
+ .pe_barread = passthru_read,
+};
+PCI_EMUL_SET(passthru);
diff --git a/usr.sbin/bhyve/pci_uart.c b/usr.sbin/bhyve/pci_uart.c
new file mode 100644
index 0000000..21b93bf
--- /dev/null
+++ b/usr.sbin/bhyve/pci_uart.c
@@ -0,0 +1,119 @@
+/*-
+ * Copyright (c) 2012 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <sys/types.h>
+
+#include <stdio.h>
+
+#include "bhyverun.h"
+#include "pci_emul.h"
+#include "uart_emul.h"
+
+/*
+ * Pick a PCI vid/did of a chip with a single uart at
+ * BAR0, that most versions of FreeBSD can understand:
+ * Siig CyberSerial 1-port.
+ */
+#define COM_VENDOR 0x131f
+#define COM_DEV 0x2000
+
+static void
+pci_uart_intr_assert(void *arg)
+{
+ struct pci_devinst *pi = arg;
+
+ pci_lintr_assert(pi);
+}
+
+static void
+pci_uart_intr_deassert(void *arg)
+{
+ struct pci_devinst *pi = arg;
+
+ pci_lintr_deassert(pi);
+}
+
+static void
+pci_uart_write(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
+ int baridx, uint64_t offset, int size, uint64_t value)
+{
+
+ assert(baridx == 0);
+ assert(size == 1);
+
+ uart_write(pi->pi_arg, offset, value);
+}
+
+uint64_t
+pci_uart_read(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
+ int baridx, uint64_t offset, int size)
+{
+ uint8_t val;
+
+ assert(baridx == 0);
+ assert(size == 1);
+
+ val = uart_read(pi->pi_arg, offset);
+ return (val);
+}
+
+static int
+pci_uart_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts)
+{
+ struct uart_softc *sc;
+
+ pci_emul_alloc_bar(pi, 0, PCIBAR_IO, UART_IO_BAR_SIZE);
+ pci_lintr_request(pi);
+
+ /* initialize config space */
+ pci_set_cfgdata16(pi, PCIR_DEVICE, COM_DEV);
+ pci_set_cfgdata16(pi, PCIR_VENDOR, COM_VENDOR);
+ pci_set_cfgdata8(pi, PCIR_CLASS, PCIC_SIMPLECOMM);
+
+ sc = uart_init(pci_uart_intr_assert, pci_uart_intr_deassert, pi);
+ pi->pi_arg = sc;
+
+ if (uart_set_backend(sc, opts) != 0) {
+ fprintf(stderr, "Unable to initialize backend '%s' for "
+ "pci uart at %d:%d\n", opts, pi->pi_slot, pi->pi_func);
+ return (-1);
+ }
+
+ return (0);
+}
+
+struct pci_devemu pci_de_com = {
+ .pe_emu = "uart",
+ .pe_init = pci_uart_init,
+ .pe_barwrite = pci_uart_write,
+ .pe_barread = pci_uart_read
+};
+PCI_EMUL_SET(pci_de_com);
diff --git a/usr.sbin/bhyve/pci_virtio_block.c b/usr.sbin/bhyve/pci_virtio_block.c
new file mode 100644
index 0000000..8500be6
--- /dev/null
+++ b/usr.sbin/bhyve/pci_virtio_block.c
@@ -0,0 +1,410 @@
+/*-
+ * Copyright (c) 2011 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <sys/param.h>
+#include <sys/linker_set.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <sys/ioctl.h>
+#include <sys/disk.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <assert.h>
+#include <pthread.h>
+#include <md5.h>
+
+#include "bhyverun.h"
+#include "pci_emul.h"
+#include "virtio.h"
+#include "block_if.h"
+
+#define VTBLK_RINGSZ 64
+
+#define VTBLK_S_OK 0
+#define VTBLK_S_IOERR 1
+#define VTBLK_S_UNSUPP 2
+
+#define VTBLK_BLK_ID_BYTES 20
+
+/* Capability bits */
+#define VTBLK_F_SEG_MAX (1 << 2) /* Maximum request segments */
+#define VTBLK_F_BLK_SIZE (1 << 6) /* cfg block size valid */
+#define VTBLK_F_FLUSH (1 << 9) /* Cache flush support */
+#define VTBLK_F_TOPOLOGY (1 << 10) /* Optimal I/O alignment */
+
+/*
+ * Host capabilities
+ */
+#define VTBLK_S_HOSTCAPS \
+ ( VTBLK_F_SEG_MAX | \
+ VTBLK_F_BLK_SIZE | \
+ VTBLK_F_FLUSH | \
+ VTBLK_F_TOPOLOGY | \
+ VIRTIO_RING_F_INDIRECT_DESC ) /* indirect descriptors */
+
+/*
+ * Config space "registers"
+ */
+struct vtblk_config {
+ uint64_t vbc_capacity;
+ uint32_t vbc_size_max;
+ uint32_t vbc_seg_max;
+ struct {
+ uint16_t cylinders;
+ uint8_t heads;
+ uint8_t sectors;
+ } vbc_geometry;
+ uint32_t vbc_blk_size;
+ struct {
+ uint8_t physical_block_exp;
+ uint8_t alignment_offset;
+ uint16_t min_io_size;
+ uint32_t opt_io_size;
+ } vbc_topology;
+ uint8_t vbc_writeback;
+} __packed;
+
+/*
+ * Fixed-size block header
+ */
+struct virtio_blk_hdr {
+#define VBH_OP_READ 0
+#define VBH_OP_WRITE 1
+#define VBH_OP_FLUSH 4
+#define VBH_OP_FLUSH_OUT 5
+#define VBH_OP_IDENT 8
+#define VBH_FLAG_BARRIER 0x80000000 /* OR'ed into vbh_type */
+ uint32_t vbh_type;
+ uint32_t vbh_ioprio;
+ uint64_t vbh_sector;
+} __packed;
+
+/*
+ * Debug printf
+ */
+static int pci_vtblk_debug;
+#define DPRINTF(params) if (pci_vtblk_debug) printf params
+#define WPRINTF(params) printf params
+
+struct pci_vtblk_ioreq {
+ struct blockif_req io_req;
+ struct pci_vtblk_softc *io_sc;
+ uint8_t *io_status;
+ uint16_t io_idx;
+};
+
+/*
+ * Per-device softc
+ */
+struct pci_vtblk_softc {
+ struct virtio_softc vbsc_vs;
+ pthread_mutex_t vsc_mtx;
+ struct vqueue_info vbsc_vq;
+ struct vtblk_config vbsc_cfg;
+ struct blockif_ctxt *bc;
+ char vbsc_ident[VTBLK_BLK_ID_BYTES];
+ struct pci_vtblk_ioreq vbsc_ios[VTBLK_RINGSZ];
+};
+
+static void pci_vtblk_reset(void *);
+static void pci_vtblk_notify(void *, struct vqueue_info *);
+static int pci_vtblk_cfgread(void *, int, int, uint32_t *);
+static int pci_vtblk_cfgwrite(void *, int, int, uint32_t);
+
+static struct virtio_consts vtblk_vi_consts = {
+ "vtblk", /* our name */
+ 1, /* we support 1 virtqueue */
+ sizeof(struct vtblk_config), /* config reg size */
+ pci_vtblk_reset, /* reset */
+ pci_vtblk_notify, /* device-wide qnotify */
+ pci_vtblk_cfgread, /* read PCI config */
+ pci_vtblk_cfgwrite, /* write PCI config */
+ NULL, /* apply negotiated features */
+ VTBLK_S_HOSTCAPS, /* our capabilities */
+};
+
+static void
+pci_vtblk_reset(void *vsc)
+{
+ struct pci_vtblk_softc *sc = vsc;
+
+ DPRINTF(("vtblk: device reset requested !\n"));
+ vi_reset_dev(&sc->vbsc_vs);
+}
+
+static void
+pci_vtblk_done(struct blockif_req *br, int err)
+{
+ struct pci_vtblk_ioreq *io = br->br_param;
+ struct pci_vtblk_softc *sc = io->io_sc;
+
+ /* convert errno into a virtio block error return */
+ if (err == EOPNOTSUPP || err == ENOSYS)
+ *io->io_status = VTBLK_S_UNSUPP;
+ else if (err != 0)
+ *io->io_status = VTBLK_S_IOERR;
+ else
+ *io->io_status = VTBLK_S_OK;
+
+ /*
+ * Return the descriptor back to the host.
+ * We wrote 1 byte (our status) to host.
+ */
+ pthread_mutex_lock(&sc->vsc_mtx);
+ vq_relchain(&sc->vbsc_vq, io->io_idx, 1);
+ vq_endchains(&sc->vbsc_vq, 0);
+ pthread_mutex_unlock(&sc->vsc_mtx);
+}
+
+static void
+pci_vtblk_proc(struct pci_vtblk_softc *sc, struct vqueue_info *vq)
+{
+ struct virtio_blk_hdr *vbh;
+ struct pci_vtblk_ioreq *io;
+ int i, n;
+ int err;
+ ssize_t iolen;
+ int writeop, type;
+ off_t offset;
+ struct iovec iov[BLOCKIF_IOV_MAX + 2];
+ uint16_t idx, flags[BLOCKIF_IOV_MAX + 2];
+
+ n = vq_getchain(vq, &idx, iov, BLOCKIF_IOV_MAX + 2, flags);
+
+ /*
+ * The first descriptor will be the read-only fixed header,
+ * and the last is for status (hence +2 above and below).
+ * The remaining iov's are the actual data I/O vectors.
+ *
+ * XXX - note - this fails on crash dump, which does a
+ * VIRTIO_BLK_T_FLUSH with a zero transfer length
+ */
+ assert(n >= 2 && n <= BLOCKIF_IOV_MAX + 2);
+
+ io = &sc->vbsc_ios[idx];
+ assert((flags[0] & VRING_DESC_F_WRITE) == 0);
+ assert(iov[0].iov_len == sizeof(struct virtio_blk_hdr));
+ vbh = iov[0].iov_base;
+ memcpy(&io->io_req.br_iov, &iov[1], sizeof(struct iovec) * (n - 2));
+ io->io_req.br_iovcnt = n - 2;
+ io->io_req.br_offset = vbh->vbh_sector * DEV_BSIZE;
+ io->io_status = iov[--n].iov_base;
+ assert(iov[n].iov_len == 1);
+ assert(flags[n] & VRING_DESC_F_WRITE);
+
+ /*
+ * XXX
+ * The guest should not be setting the BARRIER flag because
+ * we don't advertise the capability.
+ */
+ type = vbh->vbh_type & ~VBH_FLAG_BARRIER;
+ writeop = (type == VBH_OP_WRITE);
+
+ iolen = 0;
+ for (i = 1; i < n; i++) {
+ /*
+ * - write op implies read-only descriptor,
+ * - read/ident op implies write-only descriptor,
+ * therefore test the inverse of the descriptor bit
+ * to the op.
+ */
+ assert(((flags[i] & VRING_DESC_F_WRITE) == 0) == writeop);
+ iolen += iov[i].iov_len;
+ }
+ io->io_req.br_resid = iolen;
+
+ DPRINTF(("virtio-block: %s op, %zd bytes, %d segs, offset %ld\n\r",
+ writeop ? "write" : "read/ident", iolen, i - 1, offset));
+
+ switch (type) {
+ case VBH_OP_READ:
+ err = blockif_read(sc->bc, &io->io_req);
+ break;
+ case VBH_OP_WRITE:
+ err = blockif_write(sc->bc, &io->io_req);
+ break;
+ case VBH_OP_FLUSH:
+ case VBH_OP_FLUSH_OUT:
+ err = blockif_flush(sc->bc, &io->io_req);
+ break;
+ case VBH_OP_IDENT:
+ /* Assume a single buffer */
+ /* S/n equal to buffer is not zero-terminated. */
+ memset(iov[1].iov_base, 0, iov[1].iov_len);
+ strncpy(iov[1].iov_base, sc->vbsc_ident,
+ MIN(iov[1].iov_len, sizeof(sc->vbsc_ident)));
+ pci_vtblk_done(&io->io_req, 0);
+ return;
+ default:
+ pci_vtblk_done(&io->io_req, EOPNOTSUPP);
+ return;
+ }
+ assert(err == 0);
+}
+
+static void
+pci_vtblk_notify(void *vsc, struct vqueue_info *vq)
+{
+ struct pci_vtblk_softc *sc = vsc;
+
+ while (vq_has_descs(vq))
+ pci_vtblk_proc(sc, vq);
+}
+
+static int
+pci_vtblk_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts)
+{
+ char bident[sizeof("XX:X:X")];
+ struct blockif_ctxt *bctxt;
+ MD5_CTX mdctx;
+ u_char digest[16];
+ struct pci_vtblk_softc *sc;
+ off_t size;
+ int i, sectsz, sts, sto;
+
+ if (opts == NULL) {
+ printf("virtio-block: backing device required\n");
+ return (1);
+ }
+
+ /*
+ * The supplied backing file has to exist
+ */
+ snprintf(bident, sizeof(bident), "%d:%d", pi->pi_slot, pi->pi_func);
+ bctxt = blockif_open(opts, bident);
+ if (bctxt == NULL) {
+ perror("Could not open backing file");
+ return (1);
+ }
+
+ size = blockif_size(bctxt);
+ sectsz = blockif_sectsz(bctxt);
+ blockif_psectsz(bctxt, &sts, &sto);
+
+ sc = calloc(1, sizeof(struct pci_vtblk_softc));
+ sc->bc = bctxt;
+ for (i = 0; i < VTBLK_RINGSZ; i++) {
+ struct pci_vtblk_ioreq *io = &sc->vbsc_ios[i];
+ io->io_req.br_callback = pci_vtblk_done;
+ io->io_req.br_param = io;
+ io->io_sc = sc;
+ io->io_idx = i;
+ }
+
+ pthread_mutex_init(&sc->vsc_mtx, NULL);
+
+ /* init virtio softc and virtqueues */
+ vi_softc_linkup(&sc->vbsc_vs, &vtblk_vi_consts, sc, pi, &sc->vbsc_vq);
+ sc->vbsc_vs.vs_mtx = &sc->vsc_mtx;
+
+ sc->vbsc_vq.vq_qsize = VTBLK_RINGSZ;
+ /* sc->vbsc_vq.vq_notify = we have no per-queue notify */
+
+ /*
+ * Create an identifier for the backing file. Use parts of the
+ * md5 sum of the filename
+ */
+ MD5Init(&mdctx);
+ MD5Update(&mdctx, opts, strlen(opts));
+ MD5Final(digest, &mdctx);
+ sprintf(sc->vbsc_ident, "BHYVE-%02X%02X-%02X%02X-%02X%02X",
+ digest[0], digest[1], digest[2], digest[3], digest[4], digest[5]);
+
+ /* setup virtio block config space */
+ sc->vbsc_cfg.vbc_capacity = size / DEV_BSIZE; /* 512-byte units */
+ sc->vbsc_cfg.vbc_size_max = 0; /* not negotiated */
+ sc->vbsc_cfg.vbc_seg_max = BLOCKIF_IOV_MAX;
+ sc->vbsc_cfg.vbc_geometry.cylinders = 0; /* no geometry */
+ sc->vbsc_cfg.vbc_geometry.heads = 0;
+ sc->vbsc_cfg.vbc_geometry.sectors = 0;
+ sc->vbsc_cfg.vbc_blk_size = sectsz;
+ sc->vbsc_cfg.vbc_topology.physical_block_exp =
+ (sts > sectsz) ? (ffsll(sts / sectsz) - 1) : 0;
+ sc->vbsc_cfg.vbc_topology.alignment_offset =
+ (sto != 0) ? ((sts - sto) / sectsz) : 0;
+ sc->vbsc_cfg.vbc_topology.min_io_size = 0;
+ sc->vbsc_cfg.vbc_topology.opt_io_size = 0;
+ sc->vbsc_cfg.vbc_writeback = 0;
+
+ /*
+ * Should we move some of this into virtio.c? Could
+ * have the device, class, and subdev_0 as fields in
+ * the virtio constants structure.
+ */
+ pci_set_cfgdata16(pi, PCIR_DEVICE, VIRTIO_DEV_BLOCK);
+ pci_set_cfgdata16(pi, PCIR_VENDOR, VIRTIO_VENDOR);
+ pci_set_cfgdata8(pi, PCIR_CLASS, PCIC_STORAGE);
+ pci_set_cfgdata16(pi, PCIR_SUBDEV_0, VIRTIO_TYPE_BLOCK);
+ pci_set_cfgdata16(pi, PCIR_SUBVEND_0, VIRTIO_VENDOR);
+
+ if (vi_intr_init(&sc->vbsc_vs, 1, fbsdrun_virtio_msix())) {
+ blockif_close(sc->bc);
+ free(sc);
+ return (1);
+ }
+ vi_set_io_bar(&sc->vbsc_vs, 0);
+ return (0);
+}
+
+static int
+pci_vtblk_cfgwrite(void *vsc, int offset, int size, uint32_t value)
+{
+
+ DPRINTF(("vtblk: write to readonly reg %d\n\r", offset));
+ return (1);
+}
+
+static int
+pci_vtblk_cfgread(void *vsc, int offset, int size, uint32_t *retval)
+{
+ struct pci_vtblk_softc *sc = vsc;
+ void *ptr;
+
+ /* our caller has already verified offset and size */
+ ptr = (uint8_t *)&sc->vbsc_cfg + offset;
+ memcpy(retval, ptr, size);
+ return (0);
+}
+
+struct pci_devemu pci_de_vblk = {
+ .pe_emu = "virtio-blk",
+ .pe_init = pci_vtblk_init,
+ .pe_barwrite = vi_pci_write,
+ .pe_barread = vi_pci_read
+};
+PCI_EMUL_SET(pci_de_vblk);
diff --git a/usr.sbin/bhyve/pci_virtio_net.c b/usr.sbin/bhyve/pci_virtio_net.c
new file mode 100644
index 0000000..aa9b581
--- /dev/null
+++ b/usr.sbin/bhyve/pci_virtio_net.c
@@ -0,0 +1,730 @@
+/*-
+ * Copyright (c) 2011 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <sys/param.h>
+#include <sys/linker_set.h>
+#include <sys/select.h>
+#include <sys/uio.h>
+#include <sys/ioctl.h>
+#include <machine/atomic.h>
+#include <net/ethernet.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <assert.h>
+#include <md5.h>
+#include <pthread.h>
+#include <pthread_np.h>
+
+#include "bhyverun.h"
+#include "pci_emul.h"
+#include "mevent.h"
+#include "virtio.h"
+
+#define VTNET_RINGSZ 1024
+
+#define VTNET_MAXSEGS 256
+
+/*
+ * Host capabilities. Note that we only offer a few of these.
+ */
+#define VIRTIO_NET_F_CSUM (1 << 0) /* host handles partial cksum */
+#define VIRTIO_NET_F_GUEST_CSUM (1 << 1) /* guest handles partial cksum */
+#define VIRTIO_NET_F_MAC (1 << 5) /* host supplies MAC */
+#define VIRTIO_NET_F_GSO_DEPREC (1 << 6) /* deprecated: host handles GSO */
+#define VIRTIO_NET_F_GUEST_TSO4 (1 << 7) /* guest can rcv TSOv4 */
+#define VIRTIO_NET_F_GUEST_TSO6 (1 << 8) /* guest can rcv TSOv6 */
+#define VIRTIO_NET_F_GUEST_ECN (1 << 9) /* guest can rcv TSO with ECN */
+#define VIRTIO_NET_F_GUEST_UFO (1 << 10) /* guest can rcv UFO */
+#define VIRTIO_NET_F_HOST_TSO4 (1 << 11) /* host can rcv TSOv4 */
+#define VIRTIO_NET_F_HOST_TSO6 (1 << 12) /* host can rcv TSOv6 */
+#define VIRTIO_NET_F_HOST_ECN (1 << 13) /* host can rcv TSO with ECN */
+#define VIRTIO_NET_F_HOST_UFO (1 << 14) /* host can rcv UFO */
+#define VIRTIO_NET_F_MRG_RXBUF (1 << 15) /* host can merge RX buffers */
+#define VIRTIO_NET_F_STATUS (1 << 16) /* config status field available */
+#define VIRTIO_NET_F_CTRL_VQ (1 << 17) /* control channel available */
+#define VIRTIO_NET_F_CTRL_RX (1 << 18) /* control channel RX mode support */
+#define VIRTIO_NET_F_CTRL_VLAN (1 << 19) /* control channel VLAN filtering */
+#define VIRTIO_NET_F_GUEST_ANNOUNCE \
+ (1 << 21) /* guest can send gratuitous pkts */
+
+#define VTNET_S_HOSTCAPS \
+ ( VIRTIO_NET_F_MAC | VIRTIO_NET_F_MRG_RXBUF | VIRTIO_NET_F_STATUS | \
+ VIRTIO_F_NOTIFY_ON_EMPTY | VIRTIO_RING_F_INDIRECT_DESC)
+
+/*
+ * PCI config-space "registers"
+ */
+struct virtio_net_config {
+ uint8_t mac[6];
+ uint16_t status;
+} __packed;
+
+/*
+ * Queue definitions.
+ */
+#define VTNET_RXQ 0
+#define VTNET_TXQ 1
+#define VTNET_CTLQ 2 /* NB: not yet supported */
+
+#define VTNET_MAXQ 3
+
+/*
+ * Fixed network header size
+ */
+struct virtio_net_rxhdr {
+ uint8_t vrh_flags;
+ uint8_t vrh_gso_type;
+ uint16_t vrh_hdr_len;
+ uint16_t vrh_gso_size;
+ uint16_t vrh_csum_start;
+ uint16_t vrh_csum_offset;
+ uint16_t vrh_bufs;
+} __packed;
+
+/*
+ * Debug printf
+ */
+static int pci_vtnet_debug;
+#define DPRINTF(params) if (pci_vtnet_debug) printf params
+#define WPRINTF(params) printf params
+
+/*
+ * Per-device softc
+ */
+struct pci_vtnet_softc {
+ struct virtio_softc vsc_vs;
+ struct vqueue_info vsc_queues[VTNET_MAXQ - 1];
+ pthread_mutex_t vsc_mtx;
+ struct mevent *vsc_mevp;
+
+ int vsc_tapfd;
+ int vsc_rx_ready;
+ volatile int resetting; /* set and checked outside lock */
+
+ uint64_t vsc_features; /* negotiated features */
+
+ struct virtio_net_config vsc_config;
+
+ pthread_mutex_t rx_mtx;
+ int rx_in_progress;
+ int rx_vhdrlen;
+ int rx_merge; /* merged rx bufs in use */
+
+ pthread_t tx_tid;
+ pthread_mutex_t tx_mtx;
+ pthread_cond_t tx_cond;
+ int tx_in_progress;
+};
+
+static void pci_vtnet_reset(void *);
+/* static void pci_vtnet_notify(void *, struct vqueue_info *); */
+static int pci_vtnet_cfgread(void *, int, int, uint32_t *);
+static int pci_vtnet_cfgwrite(void *, int, int, uint32_t);
+static void pci_vtnet_neg_features(void *, uint64_t);
+
+static struct virtio_consts vtnet_vi_consts = {
+ "vtnet", /* our name */
+ VTNET_MAXQ - 1, /* we currently support 2 virtqueues */
+ sizeof(struct virtio_net_config), /* config reg size */
+ pci_vtnet_reset, /* reset */
+ NULL, /* device-wide qnotify -- not used */
+ pci_vtnet_cfgread, /* read PCI config */
+ pci_vtnet_cfgwrite, /* write PCI config */
+ pci_vtnet_neg_features, /* apply negotiated features */
+ VTNET_S_HOSTCAPS, /* our capabilities */
+};
+
+/*
+ * If the transmit thread is active then stall until it is done.
+ */
+static void
+pci_vtnet_txwait(struct pci_vtnet_softc *sc)
+{
+
+ pthread_mutex_lock(&sc->tx_mtx);
+ while (sc->tx_in_progress) {
+ pthread_mutex_unlock(&sc->tx_mtx);
+ usleep(10000);
+ pthread_mutex_lock(&sc->tx_mtx);
+ }
+ pthread_mutex_unlock(&sc->tx_mtx);
+}
+
+/*
+ * If the receive thread is active then stall until it is done.
+ */
+static void
+pci_vtnet_rxwait(struct pci_vtnet_softc *sc)
+{
+
+ pthread_mutex_lock(&sc->rx_mtx);
+ while (sc->rx_in_progress) {
+ pthread_mutex_unlock(&sc->rx_mtx);
+ usleep(10000);
+ pthread_mutex_lock(&sc->rx_mtx);
+ }
+ pthread_mutex_unlock(&sc->rx_mtx);
+}
+
+static void
+pci_vtnet_reset(void *vsc)
+{
+ struct pci_vtnet_softc *sc = vsc;
+
+ DPRINTF(("vtnet: device reset requested !\n"));
+
+ sc->resetting = 1;
+
+ /*
+ * Wait for the transmit and receive threads to finish their
+ * processing.
+ */
+ pci_vtnet_txwait(sc);
+ pci_vtnet_rxwait(sc);
+
+ sc->vsc_rx_ready = 0;
+ sc->rx_merge = 1;
+ sc->rx_vhdrlen = sizeof(struct virtio_net_rxhdr);
+
+ /* now reset rings, MSI-X vectors, and negotiated capabilities */
+ vi_reset_dev(&sc->vsc_vs);
+
+ sc->resetting = 0;
+}
+
+/*
+ * Called to send a buffer chain out to the tap device
+ */
+static void
+pci_vtnet_tap_tx(struct pci_vtnet_softc *sc, struct iovec *iov, int iovcnt,
+ int len)
+{
+ static char pad[60]; /* all zero bytes */
+
+ if (sc->vsc_tapfd == -1)
+ return;
+
+ /*
+ * If the length is < 60, pad out to that and add the
+ * extra zero'd segment to the iov. It is guaranteed that
+ * there is always an extra iov available by the caller.
+ */
+ if (len < 60) {
+ iov[iovcnt].iov_base = pad;
+ iov[iovcnt].iov_len = 60 - len;
+ iovcnt++;
+ }
+ (void) writev(sc->vsc_tapfd, iov, iovcnt);
+}
+
+/*
+ * Called when there is read activity on the tap file descriptor.
+ * Each buffer posted by the guest is assumed to be able to contain
+ * an entire ethernet frame + rx header.
+ * MP note: the dummybuf is only used for discarding frames, so there
+ * is no need for it to be per-vtnet or locked.
+ */
+static uint8_t dummybuf[2048];
+
+static __inline struct iovec *
+rx_iov_trim(struct iovec *iov, int *niov, int tlen)
+{
+ struct iovec *riov;
+
+ /* XXX short-cut: assume first segment is >= tlen */
+ assert(iov[0].iov_len >= tlen);
+
+ iov[0].iov_len -= tlen;
+ if (iov[0].iov_len == 0) {
+ assert(*niov > 1);
+ *niov -= 1;
+ riov = &iov[1];
+ } else {
+ iov[0].iov_base = (void *)((uintptr_t)iov[0].iov_base + tlen);
+ riov = &iov[0];
+ }
+
+ return (riov);
+}
+
+static void
+pci_vtnet_tap_rx(struct pci_vtnet_softc *sc)
+{
+ struct iovec iov[VTNET_MAXSEGS], *riov;
+ struct vqueue_info *vq;
+ void *vrx;
+ int len, n;
+ uint16_t idx;
+
+ /*
+ * Should never be called without a valid tap fd
+ */
+ assert(sc->vsc_tapfd != -1);
+
+ /*
+ * But, will be called when the rx ring hasn't yet
+ * been set up or the guest is resetting the device.
+ */
+ if (!sc->vsc_rx_ready || sc->resetting) {
+ /*
+ * Drop the packet and try later.
+ */
+ (void) read(sc->vsc_tapfd, dummybuf, sizeof(dummybuf));
+ return;
+ }
+
+ /*
+ * Check for available rx buffers
+ */
+ vq = &sc->vsc_queues[VTNET_RXQ];
+ if (!vq_has_descs(vq)) {
+ /*
+ * Drop the packet and try later. Interrupt on
+ * empty, if that's negotiated.
+ */
+ (void) read(sc->vsc_tapfd, dummybuf, sizeof(dummybuf));
+ vq_endchains(vq, 1);
+ return;
+ }
+
+ do {
+ /*
+ * Get descriptor chain.
+ */
+ n = vq_getchain(vq, &idx, iov, VTNET_MAXSEGS, NULL);
+ assert(n >= 1 && n <= VTNET_MAXSEGS);
+
+ /*
+ * Get a pointer to the rx header, and use the
+ * data immediately following it for the packet buffer.
+ */
+ vrx = iov[0].iov_base;
+ riov = rx_iov_trim(iov, &n, sc->rx_vhdrlen);
+
+ len = readv(sc->vsc_tapfd, riov, n);
+
+ if (len < 0 && errno == EWOULDBLOCK) {
+ /*
+ * No more packets, but still some avail ring
+ * entries. Interrupt if needed/appropriate.
+ */
+ vq_retchain(vq);
+ vq_endchains(vq, 0);
+ return;
+ }
+
+ /*
+ * The only valid field in the rx packet header is the
+ * number of buffers if merged rx bufs were negotiated.
+ */
+ memset(vrx, 0, sc->rx_vhdrlen);
+
+ if (sc->rx_merge) {
+ struct virtio_net_rxhdr *vrxh;
+
+ vrxh = vrx;
+ vrxh->vrh_bufs = 1;
+ }
+
+ /*
+ * Release this chain and handle more chains.
+ */
+ vq_relchain(vq, idx, len + sc->rx_vhdrlen);
+ } while (vq_has_descs(vq));
+
+ /* Interrupt if needed, including for NOTIFY_ON_EMPTY. */
+ vq_endchains(vq, 1);
+}
+
+static void
+pci_vtnet_tap_callback(int fd, enum ev_type type, void *param)
+{
+ struct pci_vtnet_softc *sc = param;
+
+ pthread_mutex_lock(&sc->rx_mtx);
+ sc->rx_in_progress = 1;
+ pci_vtnet_tap_rx(sc);
+ sc->rx_in_progress = 0;
+ pthread_mutex_unlock(&sc->rx_mtx);
+
+}
+
+static void
+pci_vtnet_ping_rxq(void *vsc, struct vqueue_info *vq)
+{
+ struct pci_vtnet_softc *sc = vsc;
+
+ /*
+ * A qnotify means that the rx process can now begin
+ */
+ if (sc->vsc_rx_ready == 0) {
+ sc->vsc_rx_ready = 1;
+ vq->vq_used->vu_flags |= VRING_USED_F_NO_NOTIFY;
+ }
+}
+
+static void
+pci_vtnet_proctx(struct pci_vtnet_softc *sc, struct vqueue_info *vq)
+{
+ struct iovec iov[VTNET_MAXSEGS + 1];
+ int i, n;
+ int plen, tlen;
+ uint16_t idx;
+
+ /*
+ * Obtain chain of descriptors. The first one is
+ * really the header descriptor, so we need to sum
+ * up two lengths: packet length and transfer length.
+ */
+ n = vq_getchain(vq, &idx, iov, VTNET_MAXSEGS, NULL);
+ assert(n >= 1 && n <= VTNET_MAXSEGS);
+ plen = 0;
+ tlen = iov[0].iov_len;
+ for (i = 1; i < n; i++) {
+ plen += iov[i].iov_len;
+ tlen += iov[i].iov_len;
+ }
+
+ DPRINTF(("virtio: packet send, %d bytes, %d segs\n\r", plen, n));
+ pci_vtnet_tap_tx(sc, &iov[1], n - 1, plen);
+
+ /* chain is processed, release it and set tlen */
+ vq_relchain(vq, idx, tlen);
+}
+
+static void
+pci_vtnet_ping_txq(void *vsc, struct vqueue_info *vq)
+{
+ struct pci_vtnet_softc *sc = vsc;
+
+ /*
+ * Any ring entries to process?
+ */
+ if (!vq_has_descs(vq))
+ return;
+
+ /* Signal the tx thread for processing */
+ pthread_mutex_lock(&sc->tx_mtx);
+ vq->vq_used->vu_flags |= VRING_USED_F_NO_NOTIFY;
+ if (sc->tx_in_progress == 0)
+ pthread_cond_signal(&sc->tx_cond);
+ pthread_mutex_unlock(&sc->tx_mtx);
+}
+
+/*
+ * Thread which will handle processing of TX desc
+ */
+static void *
+pci_vtnet_tx_thread(void *param)
+{
+ struct pci_vtnet_softc *sc = param;
+ struct vqueue_info *vq;
+ int error;
+
+ vq = &sc->vsc_queues[VTNET_TXQ];
+
+ /*
+ * Let us wait till the tx queue pointers get initialised &
+ * first tx signaled
+ */
+ pthread_mutex_lock(&sc->tx_mtx);
+ error = pthread_cond_wait(&sc->tx_cond, &sc->tx_mtx);
+ assert(error == 0);
+
+ for (;;) {
+ /* note - tx mutex is locked here */
+ while (sc->resetting || !vq_has_descs(vq)) {
+ vq->vq_used->vu_flags &= ~VRING_USED_F_NO_NOTIFY;
+ mb();
+ if (!sc->resetting && vq_has_descs(vq))
+ break;
+
+ sc->tx_in_progress = 0;
+ error = pthread_cond_wait(&sc->tx_cond, &sc->tx_mtx);
+ assert(error == 0);
+ }
+ vq->vq_used->vu_flags |= VRING_USED_F_NO_NOTIFY;
+ sc->tx_in_progress = 1;
+ pthread_mutex_unlock(&sc->tx_mtx);
+
+ do {
+ /*
+ * Run through entries, placing them into
+ * iovecs and sending when an end-of-packet
+ * is found
+ */
+ pci_vtnet_proctx(sc, vq);
+ } while (vq_has_descs(vq));
+
+ /*
+ * Generate an interrupt if needed.
+ */
+ vq_endchains(vq, 1);
+
+ pthread_mutex_lock(&sc->tx_mtx);
+ }
+}
+
+#ifdef notyet
+static void
+pci_vtnet_ping_ctlq(void *vsc, struct vqueue_info *vq)
+{
+
+ DPRINTF(("vtnet: control qnotify!\n\r"));
+}
+#endif
+
+static int
+pci_vtnet_parsemac(char *mac_str, uint8_t *mac_addr)
+{
+ struct ether_addr *ea;
+ char *tmpstr;
+ char zero_addr[ETHER_ADDR_LEN] = { 0, 0, 0, 0, 0, 0 };
+
+ tmpstr = strsep(&mac_str,"=");
+
+ if ((mac_str != NULL) && (!strcmp(tmpstr,"mac"))) {
+ ea = ether_aton(mac_str);
+
+ if (ea == NULL || ETHER_IS_MULTICAST(ea->octet) ||
+ memcmp(ea->octet, zero_addr, ETHER_ADDR_LEN) == 0) {
+ fprintf(stderr, "Invalid MAC %s\n", mac_str);
+ return (EINVAL);
+ } else
+ memcpy(mac_addr, ea->octet, ETHER_ADDR_LEN);
+ }
+
+ return (0);
+}
+
+
+static int
+pci_vtnet_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts)
+{
+ MD5_CTX mdctx;
+ unsigned char digest[16];
+ char nstr[80];
+ char tname[MAXCOMLEN + 1];
+ struct pci_vtnet_softc *sc;
+ char *devname;
+ char *vtopts;
+ int mac_provided;
+
+ sc = calloc(1, sizeof(struct pci_vtnet_softc));
+
+ pthread_mutex_init(&sc->vsc_mtx, NULL);
+
+ vi_softc_linkup(&sc->vsc_vs, &vtnet_vi_consts, sc, pi, sc->vsc_queues);
+ sc->vsc_vs.vs_mtx = &sc->vsc_mtx;
+
+ sc->vsc_queues[VTNET_RXQ].vq_qsize = VTNET_RINGSZ;
+ sc->vsc_queues[VTNET_RXQ].vq_notify = pci_vtnet_ping_rxq;
+ sc->vsc_queues[VTNET_TXQ].vq_qsize = VTNET_RINGSZ;
+ sc->vsc_queues[VTNET_TXQ].vq_notify = pci_vtnet_ping_txq;
+#ifdef notyet
+ sc->vsc_queues[VTNET_CTLQ].vq_qsize = VTNET_RINGSZ;
+ sc->vsc_queues[VTNET_CTLQ].vq_notify = pci_vtnet_ping_ctlq;
+#endif
+
+ /*
+ * Attempt to open the tap device and read the MAC address
+ * if specified
+ */
+ mac_provided = 0;
+ sc->vsc_tapfd = -1;
+ if (opts != NULL) {
+ char tbuf[80];
+ int err;
+
+ devname = vtopts = strdup(opts);
+ (void) strsep(&vtopts, ",");
+
+ if (vtopts != NULL) {
+ err = pci_vtnet_parsemac(vtopts, sc->vsc_config.mac);
+ if (err != 0) {
+ free(devname);
+ return (err);
+ }
+ mac_provided = 1;
+ }
+
+ strcpy(tbuf, "/dev/");
+ strlcat(tbuf, devname, sizeof(tbuf));
+
+ free(devname);
+
+ sc->vsc_tapfd = open(tbuf, O_RDWR);
+ if (sc->vsc_tapfd == -1) {
+ WPRINTF(("open of tap device %s failed\n", tbuf));
+ } else {
+ /*
+ * Set non-blocking and register for read
+ * notifications with the event loop
+ */
+ int opt = 1;
+ if (ioctl(sc->vsc_tapfd, FIONBIO, &opt) < 0) {
+ WPRINTF(("tap device O_NONBLOCK failed\n"));
+ close(sc->vsc_tapfd);
+ sc->vsc_tapfd = -1;
+ }
+
+ sc->vsc_mevp = mevent_add(sc->vsc_tapfd,
+ EVF_READ,
+ pci_vtnet_tap_callback,
+ sc);
+ if (sc->vsc_mevp == NULL) {
+ WPRINTF(("Could not register event\n"));
+ close(sc->vsc_tapfd);
+ sc->vsc_tapfd = -1;
+ }
+ }
+ }
+
+ /*
+ * The default MAC address is the standard NetApp OUI of 00-a0-98,
+ * followed by an MD5 of the PCI slot/func number and dev name
+ */
+ if (!mac_provided) {
+ snprintf(nstr, sizeof(nstr), "%d-%d-%s", pi->pi_slot,
+ pi->pi_func, vmname);
+
+ MD5Init(&mdctx);
+ MD5Update(&mdctx, nstr, strlen(nstr));
+ MD5Final(digest, &mdctx);
+
+ sc->vsc_config.mac[0] = 0x00;
+ sc->vsc_config.mac[1] = 0xa0;
+ sc->vsc_config.mac[2] = 0x98;
+ sc->vsc_config.mac[3] = digest[0];
+ sc->vsc_config.mac[4] = digest[1];
+ sc->vsc_config.mac[5] = digest[2];
+ }
+
+ /* initialize config space */
+ pci_set_cfgdata16(pi, PCIR_DEVICE, VIRTIO_DEV_NET);
+ pci_set_cfgdata16(pi, PCIR_VENDOR, VIRTIO_VENDOR);
+ pci_set_cfgdata8(pi, PCIR_CLASS, PCIC_NETWORK);
+ pci_set_cfgdata16(pi, PCIR_SUBDEV_0, VIRTIO_TYPE_NET);
+ pci_set_cfgdata16(pi, PCIR_SUBVEND_0, VIRTIO_VENDOR);
+
+ /* Link is up if we managed to open tap device. */
+ sc->vsc_config.status = (opts == NULL || sc->vsc_tapfd >= 0);
+
+ /* use BAR 1 to map MSI-X table and PBA, if we're using MSI-X */
+ if (vi_intr_init(&sc->vsc_vs, 1, fbsdrun_virtio_msix()))
+ return (1);
+
+ /* use BAR 0 to map config regs in IO space */
+ vi_set_io_bar(&sc->vsc_vs, 0);
+
+ sc->resetting = 0;
+
+ sc->rx_merge = 1;
+ sc->rx_vhdrlen = sizeof(struct virtio_net_rxhdr);
+ sc->rx_in_progress = 0;
+ pthread_mutex_init(&sc->rx_mtx, NULL);
+
+ /*
+ * Initialize tx semaphore & spawn TX processing thread.
+ * As of now, only one thread for TX desc processing is
+ * spawned.
+ */
+ sc->tx_in_progress = 0;
+ pthread_mutex_init(&sc->tx_mtx, NULL);
+ pthread_cond_init(&sc->tx_cond, NULL);
+ pthread_create(&sc->tx_tid, NULL, pci_vtnet_tx_thread, (void *)sc);
+ snprintf(tname, sizeof(tname), "vtnet-%d:%d tx", pi->pi_slot,
+ pi->pi_func);
+ pthread_set_name_np(sc->tx_tid, tname);
+
+ return (0);
+}
+
+static int
+pci_vtnet_cfgwrite(void *vsc, int offset, int size, uint32_t value)
+{
+ struct pci_vtnet_softc *sc = vsc;
+ void *ptr;
+
+ if (offset < 6) {
+ assert(offset + size <= 6);
+ /*
+ * The driver is allowed to change the MAC address
+ */
+ ptr = &sc->vsc_config.mac[offset];
+ memcpy(ptr, &value, size);
+ } else {
+ /* silently ignore other writes */
+ DPRINTF(("vtnet: write to readonly reg %d\n\r", offset));
+ }
+
+ return (0);
+}
+
+static int
+pci_vtnet_cfgread(void *vsc, int offset, int size, uint32_t *retval)
+{
+ struct pci_vtnet_softc *sc = vsc;
+ void *ptr;
+
+ ptr = (uint8_t *)&sc->vsc_config + offset;
+ memcpy(retval, ptr, size);
+ return (0);
+}
+
+static void
+pci_vtnet_neg_features(void *vsc, uint64_t negotiated_features)
+{
+ struct pci_vtnet_softc *sc = vsc;
+
+ sc->vsc_features = negotiated_features;
+
+ if (!(sc->vsc_features & VIRTIO_NET_F_MRG_RXBUF)) {
+ sc->rx_merge = 0;
+ /* non-merge rx header is 2 bytes shorter */
+ sc->rx_vhdrlen -= 2;
+ }
+}
+
+struct pci_devemu pci_de_vnet = {
+ .pe_emu = "virtio-net",
+ .pe_init = pci_vtnet_init,
+ .pe_barwrite = vi_pci_write,
+ .pe_barread = vi_pci_read
+};
+PCI_EMUL_SET(pci_de_vnet);
diff --git a/usr.sbin/bhyve/pci_virtio_rnd.c b/usr.sbin/bhyve/pci_virtio_rnd.c
new file mode 100644
index 0000000..78448f5
--- /dev/null
+++ b/usr.sbin/bhyve/pci_virtio_rnd.c
@@ -0,0 +1,189 @@
+/*-
+ * Copyright (c) 2014 Nahanni Systems 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
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must 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.
+ */
+
+/*
+ * virtio entropy device emulation.
+ * Randomness is sourced from /dev/random which does not block
+ * once it has been seeded at bootup.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/linker_set.h>
+#include <sys/uio.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <assert.h>
+#include <pthread.h>
+
+#include "bhyverun.h"
+#include "pci_emul.h"
+#include "virtio.h"
+
+#define VTRND_RINGSZ 64
+
+
+static int pci_vtrnd_debug;
+#define DPRINTF(params) if (pci_vtrnd_debug) printf params
+#define WPRINTF(params) printf params
+
+/*
+ * Per-device softc
+ */
+struct pci_vtrnd_softc {
+ struct virtio_softc vrsc_vs;
+ struct vqueue_info vrsc_vq;
+ pthread_mutex_t vrsc_mtx;
+ uint64_t vrsc_cfg;
+ int vrsc_fd;
+};
+
+static void pci_vtrnd_reset(void *);
+static void pci_vtrnd_notify(void *, struct vqueue_info *);
+
+static struct virtio_consts vtrnd_vi_consts = {
+ "vtrnd", /* our name */
+ 1, /* we support 1 virtqueue */
+ 0, /* config reg size */
+ pci_vtrnd_reset, /* reset */
+ pci_vtrnd_notify, /* device-wide qnotify */
+ NULL, /* read virtio config */
+ NULL, /* write virtio config */
+ NULL, /* apply negotiated features */
+ 0, /* our capabilities */
+};
+
+
+static void
+pci_vtrnd_reset(void *vsc)
+{
+ struct pci_vtrnd_softc *sc;
+
+ sc = vsc;
+
+ DPRINTF(("vtrnd: device reset requested !\n"));
+ vi_reset_dev(&sc->vrsc_vs);
+}
+
+
+static void
+pci_vtrnd_notify(void *vsc, struct vqueue_info *vq)
+{
+ struct iovec iov;
+ struct pci_vtrnd_softc *sc;
+ int len;
+ uint16_t idx;
+
+ sc = vsc;
+
+ if (sc->vrsc_fd < 0) {
+ vq_endchains(vq, 0);
+ return;
+ }
+
+ while (vq_has_descs(vq)) {
+ vq_getchain(vq, &idx, &iov, 1, NULL);
+
+ len = read(sc->vrsc_fd, iov.iov_base, iov.iov_len);
+
+ DPRINTF(("vtrnd: vtrnd_notify(): %d\r\n", len));
+
+ /* Catastrophe if unable to read from /dev/random */
+ assert(len > 0);
+
+ /*
+ * Release this chain and handle more
+ */
+ vq_relchain(vq, idx, len);
+ }
+ vq_endchains(vq, 1); /* Generate interrupt if appropriate. */
+}
+
+
+static int
+pci_vtrnd_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts)
+{
+ struct pci_vtrnd_softc *sc;
+ int fd;
+ int len;
+ uint8_t v;
+
+ /*
+ * Should always be able to open /dev/random.
+ */
+ fd = open("/dev/random", O_RDONLY | O_NONBLOCK);
+
+ assert(fd >= 0);
+
+ /*
+ * Check that device is seeded and non-blocking.
+ */
+ len = read(fd, &v, sizeof(v));
+ if (len <= 0) {
+ WPRINTF(("vtrnd: /dev/random not ready, read(): %d", len));
+ return (1);
+ }
+
+ sc = calloc(1, sizeof(struct pci_vtrnd_softc));
+
+ vi_softc_linkup(&sc->vrsc_vs, &vtrnd_vi_consts, sc, pi, &sc->vrsc_vq);
+ sc->vrsc_vs.vs_mtx = &sc->vrsc_mtx;
+
+ sc->vrsc_vq.vq_qsize = VTRND_RINGSZ;
+
+ /* keep /dev/random opened while emulating */
+ sc->vrsc_fd = fd;
+
+ /* initialize config space */
+ pci_set_cfgdata16(pi, PCIR_DEVICE, VIRTIO_DEV_RANDOM);
+ pci_set_cfgdata16(pi, PCIR_VENDOR, VIRTIO_VENDOR);
+ pci_set_cfgdata8(pi, PCIR_CLASS, PCIC_CRYPTO);
+ pci_set_cfgdata16(pi, PCIR_SUBDEV_0, VIRTIO_TYPE_ENTROPY);
+ pci_set_cfgdata16(pi, PCIR_SUBVEND_0, VIRTIO_VENDOR);
+
+ if (vi_intr_init(&sc->vrsc_vs, 1, fbsdrun_virtio_msix()))
+ return (1);
+ vi_set_io_bar(&sc->vrsc_vs, 0);
+
+ return (0);
+}
+
+
+struct pci_devemu pci_de_vrnd = {
+ .pe_emu = "virtio-rnd",
+ .pe_init = pci_vtrnd_init,
+ .pe_barwrite = vi_pci_write,
+ .pe_barread = vi_pci_read
+};
+PCI_EMUL_SET(pci_de_vrnd);
diff --git a/usr.sbin/bhyve/pm.c b/usr.sbin/bhyve/pm.c
new file mode 100644
index 0000000..f7c1c23
--- /dev/null
+++ b/usr.sbin/bhyve/pm.c
@@ -0,0 +1,312 @@
+/*-
+ * Copyright (c) 2013 Hudson River Trading LLC
+ * Written by: John H. 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <machine/vmm.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <pthread.h>
+#include <signal.h>
+#include <vmmapi.h>
+
+#include "acpi.h"
+#include "inout.h"
+#include "mevent.h"
+#include "pci_irq.h"
+#include "pci_lpc.h"
+
+static pthread_mutex_t pm_lock = PTHREAD_MUTEX_INITIALIZER;
+static struct mevent *power_button;
+static sig_t old_power_handler;
+
+/*
+ * Reset Control register at I/O port 0xcf9. Bit 2 forces a system
+ * reset when it transitions from 0 to 1. Bit 1 selects the type of
+ * reset to attempt: 0 selects a "soft" reset, and 1 selects a "hard"
+ * reset.
+ */
+static int
+reset_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
+ uint32_t *eax, void *arg)
+{
+ int error;
+
+ static uint8_t reset_control;
+
+ if (bytes != 1)
+ return (-1);
+ if (in)
+ *eax = reset_control;
+ else {
+ reset_control = *eax;
+
+ /* Treat hard and soft resets the same. */
+ if (reset_control & 0x4) {
+ error = vm_suspend(ctx, VM_SUSPEND_RESET);
+ assert(error == 0 || errno == EALREADY);
+ }
+ }
+ return (0);
+}
+INOUT_PORT(reset_reg, 0xCF9, IOPORT_F_INOUT, reset_handler);
+
+/*
+ * ACPI's SCI is a level-triggered interrupt.
+ */
+static int sci_active;
+
+static void
+sci_assert(struct vmctx *ctx)
+{
+
+ if (sci_active)
+ return;
+ vm_isa_assert_irq(ctx, SCI_INT, SCI_INT);
+ sci_active = 1;
+}
+
+static void
+sci_deassert(struct vmctx *ctx)
+{
+
+ if (!sci_active)
+ return;
+ vm_isa_deassert_irq(ctx, SCI_INT, SCI_INT);
+ sci_active = 0;
+}
+
+/*
+ * Power Management 1 Event Registers
+ *
+ * The only power management event supported is a power button upon
+ * receiving SIGTERM.
+ */
+static uint16_t pm1_enable, pm1_status;
+
+#define PM1_TMR_STS 0x0001
+#define PM1_BM_STS 0x0010
+#define PM1_GBL_STS 0x0020
+#define PM1_PWRBTN_STS 0x0100
+#define PM1_SLPBTN_STS 0x0200
+#define PM1_RTC_STS 0x0400
+#define PM1_WAK_STS 0x8000
+
+#define PM1_TMR_EN 0x0001
+#define PM1_GBL_EN 0x0020
+#define PM1_PWRBTN_EN 0x0100
+#define PM1_SLPBTN_EN 0x0200
+#define PM1_RTC_EN 0x0400
+
+static void
+sci_update(struct vmctx *ctx)
+{
+ int need_sci;
+
+ /* See if the SCI should be active or not. */
+ need_sci = 0;
+ if ((pm1_enable & PM1_TMR_EN) && (pm1_status & PM1_TMR_STS))
+ need_sci = 1;
+ if ((pm1_enable & PM1_GBL_EN) && (pm1_status & PM1_GBL_STS))
+ need_sci = 1;
+ if ((pm1_enable & PM1_PWRBTN_EN) && (pm1_status & PM1_PWRBTN_STS))
+ need_sci = 1;
+ if ((pm1_enable & PM1_SLPBTN_EN) && (pm1_status & PM1_SLPBTN_STS))
+ need_sci = 1;
+ if ((pm1_enable & PM1_RTC_EN) && (pm1_status & PM1_RTC_STS))
+ need_sci = 1;
+ if (need_sci)
+ sci_assert(ctx);
+ else
+ sci_deassert(ctx);
+}
+
+static int
+pm1_status_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
+ uint32_t *eax, void *arg)
+{
+
+ if (bytes != 2)
+ return (-1);
+
+ pthread_mutex_lock(&pm_lock);
+ if (in)
+ *eax = pm1_status;
+ else {
+ /*
+ * Writes are only permitted to clear certain bits by
+ * writing 1 to those flags.
+ */
+ pm1_status &= ~(*eax & (PM1_WAK_STS | PM1_RTC_STS |
+ PM1_SLPBTN_STS | PM1_PWRBTN_STS | PM1_BM_STS));
+ sci_update(ctx);
+ }
+ pthread_mutex_unlock(&pm_lock);
+ return (0);
+}
+
+static int
+pm1_enable_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
+ uint32_t *eax, void *arg)
+{
+
+ if (bytes != 2)
+ return (-1);
+
+ pthread_mutex_lock(&pm_lock);
+ if (in)
+ *eax = pm1_enable;
+ else {
+ /*
+ * Only permit certain bits to be set. We never use
+ * the global lock, but ACPI-CA whines profusely if it
+ * can't set GBL_EN.
+ */
+ pm1_enable = *eax & (PM1_PWRBTN_EN | PM1_GBL_EN);
+ sci_update(ctx);
+ }
+ pthread_mutex_unlock(&pm_lock);
+ return (0);
+}
+INOUT_PORT(pm1_status, PM1A_EVT_ADDR, IOPORT_F_INOUT, pm1_status_handler);
+INOUT_PORT(pm1_enable, PM1A_EVT_ADDR + 2, IOPORT_F_INOUT, pm1_enable_handler);
+
+static void
+power_button_handler(int signal, enum ev_type type, void *arg)
+{
+ struct vmctx *ctx;
+
+ ctx = arg;
+ pthread_mutex_lock(&pm_lock);
+ if (!(pm1_status & PM1_PWRBTN_STS)) {
+ pm1_status |= PM1_PWRBTN_STS;
+ sci_update(ctx);
+ }
+ pthread_mutex_unlock(&pm_lock);
+}
+
+/*
+ * Power Management 1 Control Register
+ *
+ * This is mostly unimplemented except that we wish to handle writes that
+ * set SPL_EN to handle S5 (soft power off).
+ */
+static uint16_t pm1_control;
+
+#define PM1_SCI_EN 0x0001
+#define PM1_SLP_TYP 0x1c00
+#define PM1_SLP_EN 0x2000
+#define PM1_ALWAYS_ZERO 0xc003
+
+static int
+pm1_control_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
+ uint32_t *eax, void *arg)
+{
+ int error;
+
+ if (bytes != 2)
+ return (-1);
+ if (in)
+ *eax = pm1_control;
+ else {
+ /*
+ * Various bits are write-only or reserved, so force them
+ * to zero in pm1_control. Always preserve SCI_EN as OSPM
+ * can never change it.
+ */
+ pm1_control = (pm1_control & PM1_SCI_EN) |
+ (*eax & ~(PM1_SLP_EN | PM1_ALWAYS_ZERO));
+
+ /*
+ * If SLP_EN is set, check for S5. Bhyve's _S5_ method
+ * says that '5' should be stored in SLP_TYP for S5.
+ */
+ if (*eax & PM1_SLP_EN) {
+ if ((pm1_control & PM1_SLP_TYP) >> 10 == 5) {
+ error = vm_suspend(ctx, VM_SUSPEND_POWEROFF);
+ assert(error == 0 || errno == EALREADY);
+ }
+ }
+ }
+ return (0);
+}
+INOUT_PORT(pm1_control, PM1A_CNT_ADDR, IOPORT_F_INOUT, pm1_control_handler);
+SYSRES_IO(PM1A_EVT_ADDR, 8);
+
+/*
+ * ACPI SMI Command Register
+ *
+ * This write-only register is used to enable and disable ACPI.
+ */
+static int
+smi_cmd_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
+ uint32_t *eax, void *arg)
+{
+
+ assert(!in);
+ if (bytes != 1)
+ return (-1);
+
+ pthread_mutex_lock(&pm_lock);
+ switch (*eax) {
+ case BHYVE_ACPI_ENABLE:
+ pm1_control |= PM1_SCI_EN;
+ if (power_button == NULL) {
+ power_button = mevent_add(SIGTERM, EVF_SIGNAL,
+ power_button_handler, ctx);
+ old_power_handler = signal(SIGTERM, SIG_IGN);
+ }
+ break;
+ case BHYVE_ACPI_DISABLE:
+ pm1_control &= ~PM1_SCI_EN;
+ if (power_button != NULL) {
+ mevent_delete(power_button);
+ power_button = NULL;
+ signal(SIGTERM, old_power_handler);
+ }
+ break;
+ }
+ pthread_mutex_unlock(&pm_lock);
+ return (0);
+}
+INOUT_PORT(smi_cmd, SMI_CMD, IOPORT_F_OUT, smi_cmd_handler);
+SYSRES_IO(SMI_CMD, 1);
+
+void
+sci_init(struct vmctx *ctx)
+{
+
+ /*
+ * Mark ACPI's SCI as level trigger and bump its use count
+ * in the PIRQ router.
+ */
+ pci_irq_use(SCI_INT);
+ vm_isa_set_irq_trigger(ctx, SCI_INT, LEVEL_TRIGGER);
+}
diff --git a/usr.sbin/bhyve/post.c b/usr.sbin/bhyve/post.c
new file mode 100644
index 0000000..5215a0c
--- /dev/null
+++ b/usr.sbin/bhyve/post.c
@@ -0,0 +1,53 @@
+/*-
+ * Copyright (c) 2011 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <sys/types.h>
+
+#include <assert.h>
+
+#include "inout.h"
+#include "pci_lpc.h"
+
+static int
+post_data_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
+ uint32_t *eax, void *arg)
+{
+ assert(in == 1);
+
+ if (bytes != 1)
+ return (-1);
+
+ *eax = 0xff; /* return some garbage */
+ return (0);
+}
+
+INOUT_PORT(post, 0x84, IOPORT_F_IN, post_data_handler);
+SYSRES_IO(0x84, 1);
diff --git a/usr.sbin/bhyve/rtc.c b/usr.sbin/bhyve/rtc.c
new file mode 100644
index 0000000..5c70154
--- /dev/null
+++ b/usr.sbin/bhyve/rtc.c
@@ -0,0 +1,129 @@
+/*-
+ * Copyright (c) 2011 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <sys/types.h>
+
+#include <time.h>
+#include <assert.h>
+
+#include <machine/vmm.h>
+#include <vmmapi.h>
+
+#include "acpi.h"
+#include "pci_lpc.h"
+#include "rtc.h"
+
+#define IO_RTC 0x70
+
+#define RTC_LMEM_LSB 0x34
+#define RTC_LMEM_MSB 0x35
+#define RTC_HMEM_LSB 0x5b
+#define RTC_HMEM_SB 0x5c
+#define RTC_HMEM_MSB 0x5d
+
+#define m_64KB (64*1024)
+#define m_16MB (16*1024*1024)
+#define m_4GB (4ULL*1024*1024*1024)
+
+/*
+ * Returns the current RTC time as number of seconds since 00:00:00 Jan 1, 1970
+ */
+static time_t
+rtc_time(struct vmctx *ctx, int use_localtime)
+{
+ struct tm tm;
+ time_t t;
+
+ time(&t);
+ if (use_localtime) {
+ localtime_r(&t, &tm);
+ t = timegm(&tm);
+ }
+ return (t);
+}
+
+void
+rtc_init(struct vmctx *ctx, int use_localtime)
+{
+ size_t himem;
+ size_t lomem;
+ int err;
+
+ /* XXX init diag/reset code/equipment/checksum ? */
+
+ /*
+ * Report guest memory size in nvram cells as required by UEFI.
+ * Little-endian encoding.
+ * 0x34/0x35 - 64KB chunks above 16MB, below 4GB
+ * 0x5b/0x5c/0x5d - 64KB chunks above 4GB
+ */
+ lomem = (vm_get_lowmem_size(ctx) - m_16MB) / m_64KB;
+ err = vm_rtc_write(ctx, RTC_LMEM_LSB, lomem);
+ assert(err == 0);
+ err = vm_rtc_write(ctx, RTC_LMEM_MSB, lomem >> 8);
+ assert(err == 0);
+
+ himem = vm_get_highmem_size(ctx) / m_64KB;
+ err = vm_rtc_write(ctx, RTC_HMEM_LSB, himem);
+ assert(err == 0);
+ err = vm_rtc_write(ctx, RTC_HMEM_SB, himem >> 8);
+ assert(err == 0);
+ err = vm_rtc_write(ctx, RTC_HMEM_MSB, himem >> 16);
+ assert(err == 0);
+
+ err = vm_rtc_settime(ctx, rtc_time(ctx, use_localtime));
+ assert(err == 0);
+}
+
+static void
+rtc_dsdt(void)
+{
+
+ dsdt_line("");
+ dsdt_line("Device (RTC)");
+ dsdt_line("{");
+ dsdt_line(" Name (_HID, EisaId (\"PNP0B00\"))");
+ dsdt_line(" Name (_CRS, ResourceTemplate ()");
+ dsdt_line(" {");
+ dsdt_indent(2);
+ dsdt_fixed_ioport(IO_RTC, 2);
+ dsdt_fixed_irq(8);
+ dsdt_unindent(2);
+ dsdt_line(" })");
+ dsdt_line("}");
+}
+LPC_DSDT(rtc_dsdt);
+
+/*
+ * Reserve the extended RTC I/O ports although they are not emulated at this
+ * time.
+ */
+SYSRES_IO(0x72, 6);
diff --git a/usr.sbin/bhyve/rtc.h b/usr.sbin/bhyve/rtc.h
new file mode 100644
index 0000000..5b08ca3
--- /dev/null
+++ b/usr.sbin/bhyve/rtc.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2013 Peter Grehan <grehan@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 _RTC_H_
+#define _RTC_H_
+
+void rtc_init(struct vmctx *ctx, int use_localtime);
+
+#endif /* _RTC_H_ */
diff --git a/usr.sbin/bhyve/smbiostbl.c b/usr.sbin/bhyve/smbiostbl.c
new file mode 100644
index 0000000..59a1358
--- /dev/null
+++ b/usr.sbin/bhyve/smbiostbl.c
@@ -0,0 +1,827 @@
+/*-
+ * Copyright (c) 2014 Tycho Nightingale <tycho.nightingale@pluribusnetworks.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 ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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 <assert.h>
+#include <errno.h>
+#include <md5.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <uuid.h>
+
+#include <machine/vmm.h>
+#include <vmmapi.h>
+
+#include "bhyverun.h"
+#include "smbiostbl.h"
+
+#define MB (1024*1024)
+#define GB (1024ULL*1024*1024)
+
+#define SMBIOS_BASE 0xF1000
+
+/* BHYVE_ACPI_BASE - SMBIOS_BASE) */
+#define SMBIOS_MAX_LENGTH (0xF2400 - 0xF1000)
+
+#define SMBIOS_TYPE_BIOS 0
+#define SMBIOS_TYPE_SYSTEM 1
+#define SMBIOS_TYPE_CHASSIS 3
+#define SMBIOS_TYPE_PROCESSOR 4
+#define SMBIOS_TYPE_MEMARRAY 16
+#define SMBIOS_TYPE_MEMDEVICE 17
+#define SMBIOS_TYPE_MEMARRAYMAP 19
+#define SMBIOS_TYPE_BOOT 32
+#define SMBIOS_TYPE_EOT 127
+
+struct smbios_structure {
+ uint8_t type;
+ uint8_t length;
+ uint16_t handle;
+} __packed;
+
+typedef int (*initializer_func_t)(struct smbios_structure *template_entry,
+ const char **template_strings, char *curaddr, char **endaddr,
+ uint16_t *n, uint16_t *size);
+
+struct smbios_template_entry {
+ struct smbios_structure *entry;
+ const char **strings;
+ initializer_func_t initializer;
+};
+
+/*
+ * SMBIOS Structure Table Entry Point
+ */
+#define SMBIOS_ENTRY_EANCHOR "_SM_"
+#define SMBIOS_ENTRY_EANCHORLEN 4
+#define SMBIOS_ENTRY_IANCHOR "_DMI_"
+#define SMBIOS_ENTRY_IANCHORLEN 5
+
+struct smbios_entry_point {
+ char eanchor[4]; /* anchor tag */
+ uint8_t echecksum; /* checksum of entry point structure */
+ uint8_t eplen; /* length in bytes of entry point */
+ uint8_t major; /* major version of the SMBIOS spec */
+ uint8_t minor; /* minor version of the SMBIOS spec */
+ uint16_t maxssize; /* maximum size in bytes of a struct */
+ uint8_t revision; /* entry point structure revision */
+ uint8_t format[5]; /* entry point rev-specific data */
+ char ianchor[5]; /* intermediate anchor tag */
+ uint8_t ichecksum; /* intermediate checksum */
+ uint16_t stlen; /* len in bytes of structure table */
+ uint32_t staddr; /* physical addr of structure table */
+ uint16_t stnum; /* number of structure table entries */
+ uint8_t bcdrev; /* BCD value representing DMI ver */
+} __packed;
+
+/*
+ * BIOS Information
+ */
+#define SMBIOS_FL_ISA 0x00000010 /* ISA is supported */
+#define SMBIOS_FL_PCI 0x00000080 /* PCI is supported */
+#define SMBIOS_FL_SHADOW 0x00001000 /* BIOS shadowing is allowed */
+#define SMBIOS_FL_CDBOOT 0x00008000 /* Boot from CD is supported */
+#define SMBIOS_FL_SELBOOT 0x00010000 /* Selectable Boot supported */
+#define SMBIOS_FL_EDD 0x00080000 /* EDD Spec is supported */
+
+#define SMBIOS_XB1_FL_ACPI 0x00000001 /* ACPI is supported */
+
+#define SMBIOS_XB2_FL_BBS 0x00000001 /* BIOS Boot Specification */
+#define SMBIOS_XB2_FL_VM 0x00000010 /* Virtual Machine */
+
+struct smbios_table_type0 {
+ struct smbios_structure header;
+ uint8_t vendor; /* vendor string */
+ uint8_t version; /* version string */
+ uint16_t segment; /* address segment location */
+ uint8_t rel_date; /* release date */
+ uint8_t size; /* rom size */
+ uint64_t cflags; /* characteristics */
+ uint8_t xc_bytes[2]; /* characteristics ext bytes */
+ uint8_t sb_major_rel; /* system bios version */
+ uint8_t sb_minor_rele;
+ uint8_t ecfw_major_rel; /* embedded ctrl fw version */
+ uint8_t ecfw_minor_rel;
+} __packed;
+
+/*
+ * System Information
+ */
+#define SMBIOS_WAKEUP_SWITCH 0x06 /* power switch */
+
+struct smbios_table_type1 {
+ struct smbios_structure header;
+ uint8_t manufacturer; /* manufacturer string */
+ uint8_t product; /* product name string */
+ uint8_t version; /* version string */
+ uint8_t serial; /* serial number string */
+ uint8_t uuid[16]; /* uuid byte array */
+ uint8_t wakeup; /* wake-up event */
+ uint8_t sku; /* sku number string */
+ uint8_t family; /* family name string */
+} __packed;
+
+/*
+ * System Enclosure or Chassis
+ */
+#define SMBIOS_CHT_UNKNOWN 0x02 /* unknown */
+
+#define SMBIOS_CHST_SAFE 0x03 /* safe */
+
+#define SMBIOS_CHSC_NONE 0x03 /* none */
+
+struct smbios_table_type3 {
+ struct smbios_structure header;
+ uint8_t manufacturer; /* manufacturer string */
+ uint8_t type; /* type */
+ uint8_t version; /* version string */
+ uint8_t serial; /* serial number string */
+ uint8_t asset; /* asset tag string */
+ uint8_t bustate; /* boot-up state */
+ uint8_t psstate; /* power supply state */
+ uint8_t tstate; /* thermal state */
+ uint8_t security; /* security status */
+ uint8_t uheight; /* height in 'u's */
+ uint8_t cords; /* number of power cords */
+ uint8_t elems; /* number of element records */
+ uint8_t elemlen; /* length of records */
+ uint8_t sku; /* sku number string */
+} __packed;
+
+/*
+ * Processor Information
+ */
+#define SMBIOS_PRT_CENTRAL 0x03 /* central processor */
+
+#define SMBIOS_PRF_OTHER 0x01 /* other */
+
+#define SMBIOS_PRS_PRESENT 0x40 /* socket is populated */
+#define SMBIOS_PRS_ENABLED 0x1 /* enabled */
+
+#define SMBIOS_PRU_NONE 0x06 /* none */
+
+#define SMBIOS_PFL_64B 0x04 /* 64-bit capable */
+
+struct smbios_table_type4 {
+ struct smbios_structure header;
+ uint8_t socket; /* socket designation string */
+ uint8_t type; /* processor type */
+ uint8_t family; /* processor family */
+ uint8_t manufacturer; /* manufacturer string */
+ uint64_t cpuid; /* processor cpuid */
+ uint8_t version; /* version string */
+ uint8_t voltage; /* voltage */
+ uint16_t clkspeed; /* ext clock speed in mhz */
+ uint16_t maxspeed; /* maximum speed in mhz */
+ uint16_t curspeed; /* current speed in mhz */
+ uint8_t status; /* status */
+ uint8_t upgrade; /* upgrade */
+ uint16_t l1handle; /* l1 cache handle */
+ uint16_t l2handle; /* l2 cache handle */
+ uint16_t l3handle; /* l3 cache handle */
+ uint8_t serial; /* serial number string */
+ uint8_t asset; /* asset tag string */
+ uint8_t part; /* part number string */
+ uint8_t cores; /* cores per socket */
+ uint8_t ecores; /* enabled cores */
+ uint8_t threads; /* threads per socket */
+ uint16_t cflags; /* processor characteristics */
+ uint16_t family2; /* processor family 2 */
+} __packed;
+
+/*
+ * Physical Memory Array
+ */
+#define SMBIOS_MAL_SYSMB 0x03 /* system board or motherboard */
+
+#define SMBIOS_MAU_SYSTEM 0x03 /* system memory */
+
+#define SMBIOS_MAE_NONE 0x03 /* none */
+
+struct smbios_table_type16 {
+ struct smbios_structure header;
+ uint8_t location; /* physical device location */
+ uint8_t use; /* device functional purpose */
+ uint8_t ecc; /* err detect/correct method */
+ uint32_t size; /* max mem capacity in kb */
+ uint16_t errhand; /* handle of error (if any) */
+ uint16_t ndevs; /* num of slots or sockets */
+ uint64_t xsize; /* max mem capacity in bytes */
+} __packed;
+
+/*
+ * Memory Device
+ */
+#define SMBIOS_MDFF_UNKNOWN 0x02 /* unknown */
+
+#define SMBIOS_MDT_UNKNOWN 0x02 /* unknown */
+
+#define SMBIOS_MDF_UNKNOWN 0x0004 /* unknown */
+
+struct smbios_table_type17 {
+ struct smbios_structure header;
+ uint16_t arrayhand; /* handle of physl mem array */
+ uint16_t errhand; /* handle of mem error data */
+ uint16_t twidth; /* total width in bits */
+ uint16_t dwidth; /* data width in bits */
+ uint16_t size; /* size in bytes */
+ uint8_t form; /* form factor */
+ uint8_t set; /* set */
+ uint8_t dloc; /* device locator string */
+ uint8_t bloc; /* phys bank locator string */
+ uint8_t type; /* memory type */
+ uint16_t flags; /* memory characteristics */
+ uint16_t maxspeed; /* maximum speed in mhz */
+ uint8_t manufacturer; /* manufacturer string */
+ uint8_t serial; /* serial number string */
+ uint8_t asset; /* asset tag string */
+ uint8_t part; /* part number string */
+ uint8_t attributes; /* attributes */
+ uint32_t xsize; /* extended size in mbs */
+ uint16_t curspeed; /* current speed in mhz */
+ uint16_t minvoltage; /* minimum voltage */
+ uint16_t maxvoltage; /* maximum voltage */
+ uint16_t curvoltage; /* configured voltage */
+} __packed;
+
+/*
+ * Memory Array Mapped Address
+ */
+struct smbios_table_type19 {
+ struct smbios_structure header;
+ uint32_t saddr; /* start phys addr in kb */
+ uint32_t eaddr; /* end phys addr in kb */
+ uint16_t arrayhand; /* physical mem array handle */
+ uint8_t width; /* num of dev in row */
+ uint64_t xsaddr; /* start phys addr in bytes */
+ uint64_t xeaddr; /* end phys addr in bytes */
+} __packed;
+
+/*
+ * System Boot Information
+ */
+#define SMBIOS_BOOT_NORMAL 0 /* no errors detected */
+
+struct smbios_table_type32 {
+ struct smbios_structure header;
+ uint8_t reserved[6];
+ uint8_t status; /* boot status */
+} __packed;
+
+/*
+ * End-of-Table
+ */
+struct smbios_table_type127 {
+ struct smbios_structure header;
+} __packed;
+
+struct smbios_table_type0 smbios_type0_template = {
+ { SMBIOS_TYPE_BIOS, sizeof (struct smbios_table_type0), 0 },
+ 1, /* bios vendor string */
+ 2, /* bios version string */
+ 0xF000, /* bios address segment location */
+ 3, /* bios release date */
+ 0x0, /* bios size (64k * (n + 1) is the size in bytes) */
+ SMBIOS_FL_ISA | SMBIOS_FL_PCI | SMBIOS_FL_SHADOW |
+ SMBIOS_FL_CDBOOT | SMBIOS_FL_EDD,
+ { SMBIOS_XB1_FL_ACPI, SMBIOS_XB2_FL_BBS | SMBIOS_XB2_FL_VM },
+ 0x0, /* bios major release */
+ 0x0, /* bios minor release */
+ 0xff, /* embedded controller firmware major release */
+ 0xff /* embedded controller firmware minor release */
+};
+
+const char *smbios_type0_strings[] = {
+ "BHYVE", /* vendor string */
+ "1.00", /* bios version string */
+ "03/14/2014", /* bios release date string */
+ NULL
+};
+
+struct smbios_table_type1 smbios_type1_template = {
+ { SMBIOS_TYPE_SYSTEM, sizeof (struct smbios_table_type1), 0 },
+ 1, /* manufacturer string */
+ 2, /* product string */
+ 3, /* version string */
+ 4, /* serial number string */
+ { 0 },
+ SMBIOS_WAKEUP_SWITCH,
+ 5, /* sku string */
+ 6 /* family string */
+};
+
+static int smbios_type1_initializer(struct smbios_structure *template_entry,
+ const char **template_strings, char *curaddr, char **endaddr,
+ uint16_t *n, uint16_t *size);
+
+const char *smbios_type1_strings[] = {
+ " ", /* manufacturer string */
+ "BHYVE", /* product name string */
+ "1.0", /* version string */
+ "None", /* serial number string */
+ "None", /* sku string */
+ " ", /* family name string */
+ NULL
+};
+
+struct smbios_table_type3 smbios_type3_template = {
+ { SMBIOS_TYPE_CHASSIS, sizeof (struct smbios_table_type3), 0 },
+ 1, /* manufacturer string */
+ SMBIOS_CHT_UNKNOWN,
+ 2, /* version string */
+ 3, /* serial number string */
+ 4, /* asset tag string */
+ SMBIOS_CHST_SAFE,
+ SMBIOS_CHST_SAFE,
+ SMBIOS_CHST_SAFE,
+ SMBIOS_CHSC_NONE,
+ 0, /* height in 'u's (0=enclosure height unspecified) */
+ 0, /* number of power cords (0=number unspecified) */
+ 0, /* number of contained element records */
+ 0, /* length of records */
+ 5 /* sku number string */
+};
+
+const char *smbios_type3_strings[] = {
+ " ", /* manufacturer string */
+ "1.0", /* version string */
+ "None", /* serial number string */
+ "None", /* asset tag string */
+ "None", /* sku number string */
+ NULL
+};
+
+struct smbios_table_type4 smbios_type4_template = {
+ { SMBIOS_TYPE_PROCESSOR, sizeof (struct smbios_table_type4), 0 },
+ 1, /* socket designation string */
+ SMBIOS_PRT_CENTRAL,
+ SMBIOS_PRF_OTHER,
+ 2, /* manufacturer string */
+ 0, /* cpuid */
+ 3, /* version string */
+ 0, /* voltage */
+ 0, /* external clock frequency in mhz (0=unknown) */
+ 0, /* maximum frequency in mhz (0=unknown) */
+ 0, /* current frequency in mhz (0=unknown) */
+ SMBIOS_PRS_PRESENT | SMBIOS_PRS_ENABLED,
+ SMBIOS_PRU_NONE,
+ -1, /* l1 cache handle */
+ -1, /* l2 cache handle */
+ -1, /* l3 cache handle */
+ 4, /* serial number string */
+ 5, /* asset tag string */
+ 6, /* part number string */
+ 0, /* cores per socket (0=unknown) */
+ 0, /* enabled cores per socket (0=unknown) */
+ 0, /* threads per socket (0=unknown) */
+ SMBIOS_PFL_64B,
+ SMBIOS_PRF_OTHER
+};
+
+const char *smbios_type4_strings[] = {
+ " ", /* socket designation string */
+ " ", /* manufacturer string */
+ " ", /* version string */
+ "None", /* serial number string */
+ "None", /* asset tag string */
+ "None", /* part number string */
+ NULL
+};
+
+static int smbios_type4_initializer(struct smbios_structure *template_entry,
+ const char **template_strings, char *curaddr, char **endaddr,
+ uint16_t *n, uint16_t *size);
+
+struct smbios_table_type16 smbios_type16_template = {
+ { SMBIOS_TYPE_MEMARRAY, sizeof (struct smbios_table_type16), 0 },
+ SMBIOS_MAL_SYSMB,
+ SMBIOS_MAU_SYSTEM,
+ SMBIOS_MAE_NONE,
+ 0x80000000, /* max mem capacity in kb (0x80000000=use extended) */
+ -1, /* handle of error (if any) */
+ 0, /* number of slots or sockets (TBD) */
+ 0 /* extended maximum memory capacity in bytes (TBD) */
+};
+
+static int smbios_type16_initializer(struct smbios_structure *template_entry,
+ const char **template_strings, char *curaddr, char **endaddr,
+ uint16_t *n, uint16_t *size);
+
+struct smbios_table_type17 smbios_type17_template = {
+ { SMBIOS_TYPE_MEMDEVICE, sizeof (struct smbios_table_type17), 0 },
+ -1, /* handle of physical memory array */
+ -1, /* handle of memory error data */
+ 64, /* total width in bits including ecc */
+ 64, /* data width in bits */
+ 0x7fff, /* size in bytes (0x7fff=use extended)*/
+ SMBIOS_MDFF_UNKNOWN,
+ 0, /* set (0x00=none, 0xff=unknown) */
+ 1, /* device locator string */
+ 2, /* physical bank locator string */
+ SMBIOS_MDT_UNKNOWN,
+ SMBIOS_MDF_UNKNOWN,
+ 0, /* maximum memory speed in mhz (0=unknown) */
+ 3, /* manufacturer string */
+ 4, /* serial number string */
+ 5, /* asset tag string */
+ 6, /* part number string */
+ 0, /* attributes (0=unknown rank information) */
+ 0, /* extended size in mb (TBD) */
+ 0, /* current speed in mhz (0=unknown) */
+ 0, /* minimum voltage in mv (0=unknown) */
+ 0, /* maximum voltage in mv (0=unknown) */
+ 0 /* configured voltage in mv (0=unknown) */
+};
+
+const char *smbios_type17_strings[] = {
+ " ", /* device locator string */
+ " ", /* physical bank locator string */
+ " ", /* manufacturer string */
+ "None", /* serial number string */
+ "None", /* asset tag string */
+ "None", /* part number string */
+ NULL
+};
+
+static int smbios_type17_initializer(struct smbios_structure *template_entry,
+ const char **template_strings, char *curaddr, char **endaddr,
+ uint16_t *n, uint16_t *size);
+
+struct smbios_table_type19 smbios_type19_template = {
+ { SMBIOS_TYPE_MEMARRAYMAP, sizeof (struct smbios_table_type19), 0 },
+ 0xffffffff, /* starting phys addr in kb (0xffffffff=use ext) */
+ 0xffffffff, /* ending phys addr in kb (0xffffffff=use ext) */
+ -1, /* physical memory array handle */
+ 1, /* number of devices that form a row */
+ 0, /* extended starting phys addr in bytes (TDB) */
+ 0 /* extended ending phys addr in bytes (TDB) */
+};
+
+static int smbios_type19_initializer(struct smbios_structure *template_entry,
+ const char **template_strings, char *curaddr, char **endaddr,
+ uint16_t *n, uint16_t *size);
+
+struct smbios_table_type32 smbios_type32_template = {
+ { SMBIOS_TYPE_BOOT, sizeof (struct smbios_table_type32), 0 },
+ { 0, 0, 0, 0, 0, 0 },
+ SMBIOS_BOOT_NORMAL
+};
+
+struct smbios_table_type127 smbios_type127_template = {
+ { SMBIOS_TYPE_EOT, sizeof (struct smbios_table_type127), 0 }
+};
+
+static int smbios_generic_initializer(struct smbios_structure *template_entry,
+ const char **template_strings, char *curaddr, char **endaddr,
+ uint16_t *n, uint16_t *size);
+
+static struct smbios_template_entry smbios_template[] = {
+ { (struct smbios_structure *)&smbios_type0_template,
+ smbios_type0_strings,
+ smbios_generic_initializer },
+ { (struct smbios_structure *)&smbios_type1_template,
+ smbios_type1_strings,
+ smbios_type1_initializer },
+ { (struct smbios_structure *)&smbios_type3_template,
+ smbios_type3_strings,
+ smbios_generic_initializer },
+ { (struct smbios_structure *)&smbios_type4_template,
+ smbios_type4_strings,
+ smbios_type4_initializer },
+ { (struct smbios_structure *)&smbios_type16_template,
+ NULL,
+ smbios_type16_initializer },
+ { (struct smbios_structure *)&smbios_type17_template,
+ smbios_type17_strings,
+ smbios_type17_initializer },
+ { (struct smbios_structure *)&smbios_type19_template,
+ NULL,
+ smbios_type19_initializer },
+ { (struct smbios_structure *)&smbios_type32_template,
+ NULL,
+ smbios_generic_initializer },
+ { (struct smbios_structure *)&smbios_type127_template,
+ NULL,
+ smbios_generic_initializer },
+ { NULL,NULL, NULL }
+};
+
+static uint64_t guest_lomem, guest_himem;
+static uint16_t type16_handle;
+
+static int
+smbios_generic_initializer(struct smbios_structure *template_entry,
+ const char **template_strings, char *curaddr, char **endaddr,
+ uint16_t *n, uint16_t *size)
+{
+ struct smbios_structure *entry;
+
+ memcpy(curaddr, template_entry, template_entry->length);
+ entry = (struct smbios_structure *)curaddr;
+ entry->handle = *n + 1;
+ curaddr += entry->length;
+ if (template_strings != NULL) {
+ int i;
+
+ for (i = 0; template_strings[i] != NULL; i++) {
+ const char *string;
+ int len;
+
+ string = template_strings[i];
+ len = strlen(string) + 1;
+ memcpy(curaddr, string, len);
+ curaddr += len;
+ }
+ *curaddr = '\0';
+ curaddr++;
+ } else {
+ /* Minimum string section is double nul */
+ *curaddr = '\0';
+ curaddr++;
+ *curaddr = '\0';
+ curaddr++;
+ }
+ (*n)++;
+ *endaddr = curaddr;
+
+ return (0);
+}
+
+static int
+smbios_type1_initializer(struct smbios_structure *template_entry,
+ const char **template_strings, char *curaddr, char **endaddr,
+ uint16_t *n, uint16_t *size)
+{
+ struct smbios_table_type1 *type1;
+
+ smbios_generic_initializer(template_entry, template_strings,
+ curaddr, endaddr, n, size);
+ type1 = (struct smbios_table_type1 *)curaddr;
+
+ if (guest_uuid_str != NULL) {
+ uuid_t uuid;
+ uint32_t status;
+
+ uuid_from_string(guest_uuid_str, &uuid, &status);
+ if (status != uuid_s_ok)
+ return (-1);
+
+ uuid_enc_le(&type1->uuid, &uuid);
+ } else {
+ MD5_CTX mdctx;
+ u_char digest[16];
+ char hostname[MAXHOSTNAMELEN];
+
+ /*
+ * Universally unique and yet reproducible are an
+ * oxymoron, however reproducible is desirable in
+ * this case.
+ */
+ if (gethostname(hostname, sizeof(hostname)))
+ return (-1);
+
+ MD5Init(&mdctx);
+ MD5Update(&mdctx, vmname, strlen(vmname));
+ MD5Update(&mdctx, hostname, sizeof(hostname));
+ MD5Final(digest, &mdctx);
+
+ /*
+ * Set the variant and version number.
+ */
+ digest[6] &= 0x0F;
+ digest[6] |= 0x30; /* version 3 */
+ digest[8] &= 0x3F;
+ digest[8] |= 0x80;
+
+ memcpy(&type1->uuid, digest, sizeof (digest));
+ }
+
+ return (0);
+}
+
+static int
+smbios_type4_initializer(struct smbios_structure *template_entry,
+ const char **template_strings, char *curaddr, char **endaddr,
+ uint16_t *n, uint16_t *size)
+{
+ int i;
+
+ for (i = 0; i < guest_ncpus; i++) {
+ struct smbios_table_type4 *type4;
+ char *p;
+ int nstrings, len;
+
+ smbios_generic_initializer(template_entry, template_strings,
+ curaddr, endaddr, n, size);
+ type4 = (struct smbios_table_type4 *)curaddr;
+ p = curaddr + sizeof (struct smbios_table_type4);
+ nstrings = 0;
+ while (p < *endaddr - 1) {
+ if (*p++ == '\0')
+ nstrings++;
+ }
+ len = sprintf(*endaddr - 1, "CPU #%d", i) + 1;
+ *endaddr += len - 1;
+ *(*endaddr) = '\0';
+ (*endaddr)++;
+ type4->socket = nstrings + 1;
+ curaddr = *endaddr;
+ }
+
+ return (0);
+}
+
+static int
+smbios_type16_initializer(struct smbios_structure *template_entry,
+ const char **template_strings, char *curaddr, char **endaddr,
+ uint16_t *n, uint16_t *size)
+{
+ struct smbios_table_type16 *type16;
+
+ type16_handle = *n;
+ smbios_generic_initializer(template_entry, template_strings,
+ curaddr, endaddr, n, size);
+ type16 = (struct smbios_table_type16 *)curaddr;
+ type16->xsize = guest_lomem + guest_himem;
+ type16->ndevs = guest_himem > 0 ? 2 : 1;
+
+ return (0);
+}
+
+static int
+smbios_type17_initializer(struct smbios_structure *template_entry,
+ const char **template_strings, char *curaddr, char **endaddr,
+ uint16_t *n, uint16_t *size)
+{
+ struct smbios_table_type17 *type17;
+
+ smbios_generic_initializer(template_entry, template_strings,
+ curaddr, endaddr, n, size);
+ type17 = (struct smbios_table_type17 *)curaddr;
+ type17->arrayhand = type16_handle;
+ type17->xsize = guest_lomem;
+
+ if (guest_himem > 0) {
+ curaddr = *endaddr;
+ smbios_generic_initializer(template_entry, template_strings,
+ curaddr, endaddr, n, size);
+ type17 = (struct smbios_table_type17 *)curaddr;
+ type17->arrayhand = type16_handle;
+ type17->xsize = guest_himem;
+ }
+
+ return (0);
+}
+
+static int
+smbios_type19_initializer(struct smbios_structure *template_entry,
+ const char **template_strings, char *curaddr, char **endaddr,
+ uint16_t *n, uint16_t *size)
+{
+ struct smbios_table_type19 *type19;
+
+ smbios_generic_initializer(template_entry, template_strings,
+ curaddr, endaddr, n, size);
+ type19 = (struct smbios_table_type19 *)curaddr;
+ type19->arrayhand = type16_handle;
+ type19->xsaddr = 0;
+ type19->xeaddr = guest_lomem;
+
+ if (guest_himem > 0) {
+ curaddr = *endaddr;
+ smbios_generic_initializer(template_entry, template_strings,
+ curaddr, endaddr, n, size);
+ type19 = (struct smbios_table_type19 *)curaddr;
+ type19->arrayhand = type16_handle;
+ type19->xsaddr = 4*GB;
+ type19->xeaddr = guest_himem;
+ }
+
+ return (0);
+}
+
+static void
+smbios_ep_initializer(struct smbios_entry_point *smbios_ep, uint32_t staddr)
+{
+ memset(smbios_ep, 0, sizeof(*smbios_ep));
+ memcpy(smbios_ep->eanchor, SMBIOS_ENTRY_EANCHOR,
+ SMBIOS_ENTRY_EANCHORLEN);
+ smbios_ep->eplen = 0x1F;
+ assert(sizeof (struct smbios_entry_point) == smbios_ep->eplen);
+ smbios_ep->major = 2;
+ smbios_ep->minor = 6;
+ smbios_ep->revision = 0;
+ memcpy(smbios_ep->ianchor, SMBIOS_ENTRY_IANCHOR,
+ SMBIOS_ENTRY_IANCHORLEN);
+ smbios_ep->staddr = staddr;
+ smbios_ep->bcdrev = 0x24;
+}
+
+static void
+smbios_ep_finalizer(struct smbios_entry_point *smbios_ep, uint16_t len,
+ uint16_t num, uint16_t maxssize)
+{
+ uint8_t checksum;
+ int i;
+
+ smbios_ep->maxssize = maxssize;
+ smbios_ep->stlen = len;
+ smbios_ep->stnum = num;
+
+ checksum = 0;
+ for (i = 0x10; i < 0x1f; i++) {
+ checksum -= ((uint8_t *)smbios_ep)[i];
+ }
+ smbios_ep->ichecksum = checksum;
+
+ checksum = 0;
+ for (i = 0; i < 0x1f; i++) {
+ checksum -= ((uint8_t *)smbios_ep)[i];
+ }
+ smbios_ep->echecksum = checksum;
+}
+
+int
+smbios_build(struct vmctx *ctx)
+{
+ struct smbios_entry_point *smbios_ep;
+ uint16_t n;
+ uint16_t maxssize;
+ char *curaddr, *startaddr, *ststartaddr;
+ int i;
+ int err;
+
+ guest_lomem = vm_get_lowmem_size(ctx);
+ guest_himem = vm_get_highmem_size(ctx);
+
+ startaddr = paddr_guest2host(ctx, SMBIOS_BASE, SMBIOS_MAX_LENGTH);
+ if (startaddr == NULL) {
+ fprintf(stderr, "smbios table requires mapped mem\n");
+ return (ENOMEM);
+ }
+
+ curaddr = startaddr;
+
+ smbios_ep = (struct smbios_entry_point *)curaddr;
+ smbios_ep_initializer(smbios_ep, SMBIOS_BASE +
+ sizeof(struct smbios_entry_point));
+ curaddr += sizeof(struct smbios_entry_point);
+ ststartaddr = curaddr;
+
+ n = 0;
+ maxssize = 0;
+ for (i = 0; smbios_template[i].entry != NULL; i++) {
+ struct smbios_structure *entry;
+ const char **strings;
+ initializer_func_t initializer;
+ char *endaddr;
+ uint16_t size;
+
+ entry = smbios_template[i].entry;
+ strings = smbios_template[i].strings;
+ initializer = smbios_template[i].initializer;
+
+ err = (*initializer)(entry, strings, curaddr, &endaddr,
+ &n, &size);
+ if (err != 0)
+ return (err);
+
+ if (size > maxssize)
+ maxssize = size;
+
+ curaddr = endaddr;
+ }
+
+ assert(curaddr - startaddr < SMBIOS_MAX_LENGTH);
+ smbios_ep_finalizer(smbios_ep, curaddr - ststartaddr, n, maxssize);
+
+ return (0);
+}
diff --git a/usr.sbin/bhyve/smbiostbl.h b/usr.sbin/bhyve/smbiostbl.h
new file mode 100644
index 0000000..e8b3a4f
--- /dev/null
+++ b/usr.sbin/bhyve/smbiostbl.h
@@ -0,0 +1,36 @@
+/*-
+ * Copyright (c) 2014 Tycho Nightingale <tycho.nightingale@pluribusnetworks.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 ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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 _SMBIOSTBL_H_
+#define _SMBIOSTBL_H_
+
+struct vmctx;
+
+int smbios_build(struct vmctx *ctx);
+
+#endif /* _SMBIOSTBL_H_ */
diff --git a/usr.sbin/bhyve/spinup_ap.c b/usr.sbin/bhyve/spinup_ap.c
new file mode 100644
index 0000000..c597023
--- /dev/null
+++ b/usr.sbin/bhyve/spinup_ap.c
@@ -0,0 +1,104 @@
+/*-
+ * Copyright (c) 2012 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <sys/param.h>
+#include <sys/types.h>
+
+#include <machine/vmm.h>
+#include <vmmapi.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "bhyverun.h"
+#include "spinup_ap.h"
+
+static void
+spinup_ap_realmode(struct vmctx *ctx, int newcpu, uint64_t *rip)
+{
+ int vector, error;
+ uint16_t cs;
+ uint64_t desc_base;
+ uint32_t desc_limit, desc_access;
+
+ vector = *rip >> PAGE_SHIFT;
+ *rip = 0;
+
+ /*
+ * Update the %cs and %rip of the guest so that it starts
+ * executing real mode code at at 'vector << 12'.
+ */
+ error = vm_set_register(ctx, newcpu, VM_REG_GUEST_RIP, *rip);
+ assert(error == 0);
+
+ error = vm_get_desc(ctx, newcpu, VM_REG_GUEST_CS, &desc_base,
+ &desc_limit, &desc_access);
+ assert(error == 0);
+
+ desc_base = vector << PAGE_SHIFT;
+ error = vm_set_desc(ctx, newcpu, VM_REG_GUEST_CS,
+ desc_base, desc_limit, desc_access);
+ assert(error == 0);
+
+ cs = (vector << PAGE_SHIFT) >> 4;
+ error = vm_set_register(ctx, newcpu, VM_REG_GUEST_CS, cs);
+ assert(error == 0);
+}
+
+int
+spinup_ap(struct vmctx *ctx, int vcpu, int newcpu, uint64_t rip)
+{
+ int error;
+
+ assert(newcpu != 0);
+ assert(newcpu < guest_ncpus);
+
+ error = vcpu_reset(ctx, newcpu);
+ assert(error == 0);
+
+ fbsdrun_set_capabilities(ctx, newcpu);
+
+ /*
+ * Enable the 'unrestricted guest' mode for 'newcpu'.
+ *
+ * Set up the processor state in power-on 16-bit mode, with the CS:IP
+ * init'd to the specified low-mem 4K page.
+ */
+ error = vm_set_capability(ctx, newcpu, VM_CAP_UNRESTRICTED_GUEST, 1);
+ assert(error == 0);
+
+ spinup_ap_realmode(ctx, newcpu, &rip);
+
+ fbsdrun_addcpu(ctx, vcpu, newcpu, rip);
+
+ return (newcpu);
+}
diff --git a/usr.sbin/bhyve/spinup_ap.h b/usr.sbin/bhyve/spinup_ap.h
new file mode 100644
index 0000000..2749ee9
--- /dev/null
+++ b/usr.sbin/bhyve/spinup_ap.h
@@ -0,0 +1,34 @@
+/*-
+ * Copyright (c) 2012 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 _SPINUP_AP_H_
+#define _SPINUP_AP_H_
+
+int spinup_ap(struct vmctx *ctx, int vcpu, int newcpu, uint64_t rip);
+
+#endif
diff --git a/usr.sbin/bhyve/task_switch.c b/usr.sbin/bhyve/task_switch.c
new file mode 100644
index 0000000..69dfaae
--- /dev/null
+++ b/usr.sbin/bhyve/task_switch.c
@@ -0,0 +1,939 @@
+/*-
+ * Copyright (c) 2014 Neel Natu <neel@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 ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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/_iovec.h>
+#include <sys/mman.h>
+
+#include <x86/psl.h>
+#include <x86/segments.h>
+#include <x86/specialreg.h>
+#include <machine/vmm.h>
+#include <machine/vmm_instruction_emul.h>
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <errno.h>
+
+#include <vmmapi.h>
+
+#include "bhyverun.h"
+
+/*
+ * Using 'struct i386tss' is tempting but causes myriad sign extension
+ * issues because all of its fields are defined as signed integers.
+ */
+struct tss32 {
+ uint16_t tss_link;
+ uint16_t rsvd1;
+ uint32_t tss_esp0;
+ uint16_t tss_ss0;
+ uint16_t rsvd2;
+ uint32_t tss_esp1;
+ uint16_t tss_ss1;
+ uint16_t rsvd3;
+ uint32_t tss_esp2;
+ uint16_t tss_ss2;
+ uint16_t rsvd4;
+ uint32_t tss_cr3;
+ uint32_t tss_eip;
+ uint32_t tss_eflags;
+ uint32_t tss_eax;
+ uint32_t tss_ecx;
+ uint32_t tss_edx;
+ uint32_t tss_ebx;
+ uint32_t tss_esp;
+ uint32_t tss_ebp;
+ uint32_t tss_esi;
+ uint32_t tss_edi;
+ uint16_t tss_es;
+ uint16_t rsvd5;
+ uint16_t tss_cs;
+ uint16_t rsvd6;
+ uint16_t tss_ss;
+ uint16_t rsvd7;
+ uint16_t tss_ds;
+ uint16_t rsvd8;
+ uint16_t tss_fs;
+ uint16_t rsvd9;
+ uint16_t tss_gs;
+ uint16_t rsvd10;
+ uint16_t tss_ldt;
+ uint16_t rsvd11;
+ uint16_t tss_trap;
+ uint16_t tss_iomap;
+};
+CTASSERT(sizeof(struct tss32) == 104);
+
+#define SEL_START(sel) (((sel) & ~0x7))
+#define SEL_LIMIT(sel) (((sel) | 0x7))
+#define TSS_BUSY(type) (((type) & 0x2) != 0)
+
+static uint64_t
+GETREG(struct vmctx *ctx, int vcpu, int reg)
+{
+ uint64_t val;
+ int error;
+
+ error = vm_get_register(ctx, vcpu, reg, &val);
+ assert(error == 0);
+ return (val);
+}
+
+static void
+SETREG(struct vmctx *ctx, int vcpu, int reg, uint64_t val)
+{
+ int error;
+
+ error = vm_set_register(ctx, vcpu, reg, val);
+ assert(error == 0);
+}
+
+static struct seg_desc
+usd_to_seg_desc(struct user_segment_descriptor *usd)
+{
+ struct seg_desc seg_desc;
+
+ seg_desc.base = (u_int)USD_GETBASE(usd);
+ if (usd->sd_gran)
+ seg_desc.limit = (u_int)(USD_GETLIMIT(usd) << 12) | 0xfff;
+ else
+ seg_desc.limit = (u_int)USD_GETLIMIT(usd);
+ seg_desc.access = usd->sd_type | usd->sd_dpl << 5 | usd->sd_p << 7;
+ seg_desc.access |= usd->sd_xx << 12;
+ seg_desc.access |= usd->sd_def32 << 14;
+ seg_desc.access |= usd->sd_gran << 15;
+
+ return (seg_desc);
+}
+
+/*
+ * Inject an exception with an error code that is a segment selector.
+ * The format of the error code is described in section 6.13, "Error Code",
+ * Intel SDM volume 3.
+ *
+ * Bit 0 (EXT) denotes whether the exception occurred during delivery
+ * of an external event like an interrupt.
+ *
+ * Bit 1 (IDT) indicates whether the selector points to a gate descriptor
+ * in the IDT.
+ *
+ * Bit 2(GDT/LDT) has the usual interpretation of Table Indicator (TI).
+ */
+static void
+sel_exception(struct vmctx *ctx, int vcpu, int vector, uint16_t sel, int ext)
+{
+ /*
+ * Bit 2 from the selector is retained as-is in the error code.
+ *
+ * Bit 1 can be safely cleared because none of the selectors
+ * encountered during task switch emulation refer to a task
+ * gate in the IDT.
+ *
+ * Bit 0 is set depending on the value of 'ext'.
+ */
+ sel &= ~0x3;
+ if (ext)
+ sel |= 0x1;
+ vm_inject_fault(ctx, vcpu, vector, 1, sel);
+}
+
+/*
+ * Return 0 if the selector 'sel' in within the limits of the GDT/LDT
+ * and non-zero otherwise.
+ */
+static int
+desc_table_limit_check(struct vmctx *ctx, int vcpu, uint16_t sel)
+{
+ uint64_t base;
+ uint32_t limit, access;
+ int error, reg;
+
+ reg = ISLDT(sel) ? VM_REG_GUEST_LDTR : VM_REG_GUEST_GDTR;
+ error = vm_get_desc(ctx, vcpu, reg, &base, &limit, &access);
+ assert(error == 0);
+
+ if (reg == VM_REG_GUEST_LDTR) {
+ if (SEG_DESC_UNUSABLE(access) || !SEG_DESC_PRESENT(access))
+ return (-1);
+ }
+
+ if (limit < SEL_LIMIT(sel))
+ return (-1);
+ else
+ return (0);
+}
+
+/*
+ * Read/write the segment descriptor 'desc' into the GDT/LDT slot referenced
+ * by the selector 'sel'.
+ *
+ * Returns 0 on success.
+ * Returns 1 if an exception was injected into the guest.
+ * Returns -1 otherwise.
+ */
+static int
+desc_table_rw(struct vmctx *ctx, int vcpu, struct vm_guest_paging *paging,
+ uint16_t sel, struct user_segment_descriptor *desc, bool doread,
+ int *faultptr)
+{
+ struct iovec iov[2];
+ uint64_t base;
+ uint32_t limit, access;
+ int error, reg;
+
+ reg = ISLDT(sel) ? VM_REG_GUEST_LDTR : VM_REG_GUEST_GDTR;
+ error = vm_get_desc(ctx, vcpu, reg, &base, &limit, &access);
+ assert(error == 0);
+ assert(limit >= SEL_LIMIT(sel));
+
+ error = vm_copy_setup(ctx, vcpu, paging, base + SEL_START(sel),
+ sizeof(*desc), doread ? PROT_READ : PROT_WRITE, iov, nitems(iov),
+ faultptr);
+ if (error || *faultptr)
+ return (error);
+
+ if (doread)
+ vm_copyin(ctx, vcpu, iov, desc, sizeof(*desc));
+ else
+ vm_copyout(ctx, vcpu, desc, iov, sizeof(*desc));
+ return (0);
+}
+
+static int
+desc_table_read(struct vmctx *ctx, int vcpu, struct vm_guest_paging *paging,
+ uint16_t sel, struct user_segment_descriptor *desc, int *faultptr)
+{
+ return (desc_table_rw(ctx, vcpu, paging, sel, desc, true, faultptr));
+}
+
+static int
+desc_table_write(struct vmctx *ctx, int vcpu, struct vm_guest_paging *paging,
+ uint16_t sel, struct user_segment_descriptor *desc, int *faultptr)
+{
+ return (desc_table_rw(ctx, vcpu, paging, sel, desc, false, faultptr));
+}
+
+/*
+ * Read the TSS descriptor referenced by 'sel' into 'desc'.
+ *
+ * Returns 0 on success.
+ * Returns 1 if an exception was injected into the guest.
+ * Returns -1 otherwise.
+ */
+static int
+read_tss_descriptor(struct vmctx *ctx, int vcpu, struct vm_task_switch *ts,
+ uint16_t sel, struct user_segment_descriptor *desc, int *faultptr)
+{
+ struct vm_guest_paging sup_paging;
+ int error;
+
+ assert(!ISLDT(sel));
+ assert(IDXSEL(sel) != 0);
+
+ /* Fetch the new TSS descriptor */
+ if (desc_table_limit_check(ctx, vcpu, sel)) {
+ if (ts->reason == TSR_IRET)
+ sel_exception(ctx, vcpu, IDT_TS, sel, ts->ext);
+ else
+ sel_exception(ctx, vcpu, IDT_GP, sel, ts->ext);
+ return (1);
+ }
+
+ sup_paging = ts->paging;
+ sup_paging.cpl = 0; /* implicit supervisor mode */
+ error = desc_table_read(ctx, vcpu, &sup_paging, sel, desc, faultptr);
+ return (error);
+}
+
+static bool
+code_desc(int sd_type)
+{
+ /* code descriptor */
+ return ((sd_type & 0x18) == 0x18);
+}
+
+static bool
+stack_desc(int sd_type)
+{
+ /* writable data descriptor */
+ return ((sd_type & 0x1A) == 0x12);
+}
+
+static bool
+data_desc(int sd_type)
+{
+ /* data descriptor or a readable code descriptor */
+ return ((sd_type & 0x18) == 0x10 || (sd_type & 0x1A) == 0x1A);
+}
+
+static bool
+ldt_desc(int sd_type)
+{
+
+ return (sd_type == SDT_SYSLDT);
+}
+
+/*
+ * Validate the descriptor 'seg_desc' associated with 'segment'.
+ */
+static int
+validate_seg_desc(struct vmctx *ctx, int vcpu, struct vm_task_switch *ts,
+ int segment, struct seg_desc *seg_desc, int *faultptr)
+{
+ struct vm_guest_paging sup_paging;
+ struct user_segment_descriptor usd;
+ int error, idtvec;
+ int cpl, dpl, rpl;
+ uint16_t sel, cs;
+ bool ldtseg, codeseg, stackseg, dataseg, conforming;
+
+ ldtseg = codeseg = stackseg = dataseg = false;
+ switch (segment) {
+ case VM_REG_GUEST_LDTR:
+ ldtseg = true;
+ break;
+ case VM_REG_GUEST_CS:
+ codeseg = true;
+ break;
+ case VM_REG_GUEST_SS:
+ stackseg = true;
+ break;
+ case VM_REG_GUEST_DS:
+ case VM_REG_GUEST_ES:
+ case VM_REG_GUEST_FS:
+ case VM_REG_GUEST_GS:
+ dataseg = true;
+ break;
+ default:
+ assert(0);
+ }
+
+ /* Get the segment selector */
+ sel = GETREG(ctx, vcpu, segment);
+
+ /* LDT selector must point into the GDT */
+ if (ldtseg && ISLDT(sel)) {
+ sel_exception(ctx, vcpu, IDT_TS, sel, ts->ext);
+ return (1);
+ }
+
+ /* Descriptor table limit check */
+ if (desc_table_limit_check(ctx, vcpu, sel)) {
+ sel_exception(ctx, vcpu, IDT_TS, sel, ts->ext);
+ return (1);
+ }
+
+ /* NULL selector */
+ if (IDXSEL(sel) == 0) {
+ /* Code and stack segment selectors cannot be NULL */
+ if (codeseg || stackseg) {
+ sel_exception(ctx, vcpu, IDT_TS, sel, ts->ext);
+ return (1);
+ }
+ seg_desc->base = 0;
+ seg_desc->limit = 0;
+ seg_desc->access = 0x10000; /* unusable */
+ return (0);
+ }
+
+ /* Read the descriptor from the GDT/LDT */
+ sup_paging = ts->paging;
+ sup_paging.cpl = 0; /* implicit supervisor mode */
+ error = desc_table_read(ctx, vcpu, &sup_paging, sel, &usd, faultptr);
+ if (error || *faultptr)
+ return (error);
+
+ /* Verify that the descriptor type is compatible with the segment */
+ if ((ldtseg && !ldt_desc(usd.sd_type)) ||
+ (codeseg && !code_desc(usd.sd_type)) ||
+ (dataseg && !data_desc(usd.sd_type)) ||
+ (stackseg && !stack_desc(usd.sd_type))) {
+ sel_exception(ctx, vcpu, IDT_TS, sel, ts->ext);
+ return (1);
+ }
+
+ /* Segment must be marked present */
+ if (!usd.sd_p) {
+ if (ldtseg)
+ idtvec = IDT_TS;
+ else if (stackseg)
+ idtvec = IDT_SS;
+ else
+ idtvec = IDT_NP;
+ sel_exception(ctx, vcpu, idtvec, sel, ts->ext);
+ return (1);
+ }
+
+ cs = GETREG(ctx, vcpu, VM_REG_GUEST_CS);
+ cpl = cs & SEL_RPL_MASK;
+ rpl = sel & SEL_RPL_MASK;
+ dpl = usd.sd_dpl;
+
+ if (stackseg && (rpl != cpl || dpl != cpl)) {
+ sel_exception(ctx, vcpu, IDT_TS, sel, ts->ext);
+ return (1);
+ }
+
+ if (codeseg) {
+ conforming = (usd.sd_type & 0x4) ? true : false;
+ if ((conforming && (cpl < dpl)) ||
+ (!conforming && (cpl != dpl))) {
+ sel_exception(ctx, vcpu, IDT_TS, sel, ts->ext);
+ return (1);
+ }
+ }
+
+ if (dataseg) {
+ /*
+ * A data segment is always non-conforming except when it's
+ * descriptor is a readable, conforming code segment.
+ */
+ if (code_desc(usd.sd_type) && (usd.sd_type & 0x4) != 0)
+ conforming = true;
+ else
+ conforming = false;
+
+ if (!conforming && (rpl > dpl || cpl > dpl)) {
+ sel_exception(ctx, vcpu, IDT_TS, sel, ts->ext);
+ return (1);
+ }
+ }
+ *seg_desc = usd_to_seg_desc(&usd);
+ return (0);
+}
+
+static void
+tss32_save(struct vmctx *ctx, int vcpu, struct vm_task_switch *task_switch,
+ uint32_t eip, struct tss32 *tss, struct iovec *iov)
+{
+
+ /* General purpose registers */
+ tss->tss_eax = GETREG(ctx, vcpu, VM_REG_GUEST_RAX);
+ tss->tss_ecx = GETREG(ctx, vcpu, VM_REG_GUEST_RCX);
+ tss->tss_edx = GETREG(ctx, vcpu, VM_REG_GUEST_RDX);
+ tss->tss_ebx = GETREG(ctx, vcpu, VM_REG_GUEST_RBX);
+ tss->tss_esp = GETREG(ctx, vcpu, VM_REG_GUEST_RSP);
+ tss->tss_ebp = GETREG(ctx, vcpu, VM_REG_GUEST_RBP);
+ tss->tss_esi = GETREG(ctx, vcpu, VM_REG_GUEST_RSI);
+ tss->tss_edi = GETREG(ctx, vcpu, VM_REG_GUEST_RDI);
+
+ /* Segment selectors */
+ tss->tss_es = GETREG(ctx, vcpu, VM_REG_GUEST_ES);
+ tss->tss_cs = GETREG(ctx, vcpu, VM_REG_GUEST_CS);
+ tss->tss_ss = GETREG(ctx, vcpu, VM_REG_GUEST_SS);
+ tss->tss_ds = GETREG(ctx, vcpu, VM_REG_GUEST_DS);
+ tss->tss_fs = GETREG(ctx, vcpu, VM_REG_GUEST_FS);
+ tss->tss_gs = GETREG(ctx, vcpu, VM_REG_GUEST_GS);
+
+ /* eflags and eip */
+ tss->tss_eflags = GETREG(ctx, vcpu, VM_REG_GUEST_RFLAGS);
+ if (task_switch->reason == TSR_IRET)
+ tss->tss_eflags &= ~PSL_NT;
+ tss->tss_eip = eip;
+
+ /* Copy updated old TSS into guest memory */
+ vm_copyout(ctx, vcpu, tss, iov, sizeof(struct tss32));
+}
+
+static void
+update_seg_desc(struct vmctx *ctx, int vcpu, int reg, struct seg_desc *sd)
+{
+ int error;
+
+ error = vm_set_desc(ctx, vcpu, reg, sd->base, sd->limit, sd->access);
+ assert(error == 0);
+}
+
+/*
+ * Update the vcpu registers to reflect the state of the new task.
+ */
+static int
+tss32_restore(struct vmctx *ctx, int vcpu, struct vm_task_switch *ts,
+ uint16_t ot_sel, struct tss32 *tss, struct iovec *iov, int *faultptr)
+{
+ struct seg_desc seg_desc, seg_desc2;
+ uint64_t *pdpte, maxphyaddr, reserved;
+ uint32_t eflags;
+ int error, i;
+ bool nested;
+
+ nested = false;
+ if (ts->reason != TSR_IRET && ts->reason != TSR_JMP) {
+ tss->tss_link = ot_sel;
+ nested = true;
+ }
+
+ eflags = tss->tss_eflags;
+ if (nested)
+ eflags |= PSL_NT;
+
+ /* LDTR */
+ SETREG(ctx, vcpu, VM_REG_GUEST_LDTR, tss->tss_ldt);
+
+ /* PBDR */
+ if (ts->paging.paging_mode != PAGING_MODE_FLAT) {
+ if (ts->paging.paging_mode == PAGING_MODE_PAE) {
+ /*
+ * XXX Assuming 36-bit MAXPHYADDR.
+ */
+ maxphyaddr = (1UL << 36) - 1;
+ pdpte = paddr_guest2host(ctx, tss->tss_cr3 & ~0x1f, 32);
+ for (i = 0; i < 4; i++) {
+ /* Check reserved bits if the PDPTE is valid */
+ if (!(pdpte[i] & 0x1))
+ continue;
+ /*
+ * Bits 2:1, 8:5 and bits above the processor's
+ * maximum physical address are reserved.
+ */
+ reserved = ~maxphyaddr | 0x1E6;
+ if (pdpte[i] & reserved) {
+ vm_inject_gp(ctx, vcpu);
+ return (1);
+ }
+ }
+ SETREG(ctx, vcpu, VM_REG_GUEST_PDPTE0, pdpte[0]);
+ SETREG(ctx, vcpu, VM_REG_GUEST_PDPTE1, pdpte[1]);
+ SETREG(ctx, vcpu, VM_REG_GUEST_PDPTE2, pdpte[2]);
+ SETREG(ctx, vcpu, VM_REG_GUEST_PDPTE3, pdpte[3]);
+ }
+ SETREG(ctx, vcpu, VM_REG_GUEST_CR3, tss->tss_cr3);
+ ts->paging.cr3 = tss->tss_cr3;
+ }
+
+ /* eflags and eip */
+ SETREG(ctx, vcpu, VM_REG_GUEST_RFLAGS, eflags);
+ SETREG(ctx, vcpu, VM_REG_GUEST_RIP, tss->tss_eip);
+
+ /* General purpose registers */
+ SETREG(ctx, vcpu, VM_REG_GUEST_RAX, tss->tss_eax);
+ SETREG(ctx, vcpu, VM_REG_GUEST_RCX, tss->tss_ecx);
+ SETREG(ctx, vcpu, VM_REG_GUEST_RDX, tss->tss_edx);
+ SETREG(ctx, vcpu, VM_REG_GUEST_RBX, tss->tss_ebx);
+ SETREG(ctx, vcpu, VM_REG_GUEST_RSP, tss->tss_esp);
+ SETREG(ctx, vcpu, VM_REG_GUEST_RBP, tss->tss_ebp);
+ SETREG(ctx, vcpu, VM_REG_GUEST_RSI, tss->tss_esi);
+ SETREG(ctx, vcpu, VM_REG_GUEST_RDI, tss->tss_edi);
+
+ /* Segment selectors */
+ SETREG(ctx, vcpu, VM_REG_GUEST_ES, tss->tss_es);
+ SETREG(ctx, vcpu, VM_REG_GUEST_CS, tss->tss_cs);
+ SETREG(ctx, vcpu, VM_REG_GUEST_SS, tss->tss_ss);
+ SETREG(ctx, vcpu, VM_REG_GUEST_DS, tss->tss_ds);
+ SETREG(ctx, vcpu, VM_REG_GUEST_FS, tss->tss_fs);
+ SETREG(ctx, vcpu, VM_REG_GUEST_GS, tss->tss_gs);
+
+ /*
+ * If this is a nested task then write out the new TSS to update
+ * the previous link field.
+ */
+ if (nested)
+ vm_copyout(ctx, vcpu, tss, iov, sizeof(*tss));
+
+ /* Validate segment descriptors */
+ error = validate_seg_desc(ctx, vcpu, ts, VM_REG_GUEST_LDTR, &seg_desc,
+ faultptr);
+ if (error || *faultptr)
+ return (error);
+ update_seg_desc(ctx, vcpu, VM_REG_GUEST_LDTR, &seg_desc);
+
+ /*
+ * Section "Checks on Guest Segment Registers", Intel SDM, Vol 3.
+ *
+ * The SS and CS attribute checks on VM-entry are inter-dependent so
+ * we need to make sure that both segments are valid before updating
+ * either of them. This ensures that the VMCS state can pass the
+ * VM-entry checks so the guest can handle any exception injected
+ * during task switch emulation.
+ */
+ error = validate_seg_desc(ctx, vcpu, ts, VM_REG_GUEST_CS, &seg_desc,
+ faultptr);
+ if (error || *faultptr)
+ return (error);
+
+ error = validate_seg_desc(ctx, vcpu, ts, VM_REG_GUEST_SS, &seg_desc2,
+ faultptr);
+ if (error || *faultptr)
+ return (error);
+ update_seg_desc(ctx, vcpu, VM_REG_GUEST_CS, &seg_desc);
+ update_seg_desc(ctx, vcpu, VM_REG_GUEST_SS, &seg_desc2);
+ ts->paging.cpl = tss->tss_cs & SEL_RPL_MASK;
+
+ error = validate_seg_desc(ctx, vcpu, ts, VM_REG_GUEST_DS, &seg_desc,
+ faultptr);
+ if (error || *faultptr)
+ return (error);
+ update_seg_desc(ctx, vcpu, VM_REG_GUEST_DS, &seg_desc);
+
+ error = validate_seg_desc(ctx, vcpu, ts, VM_REG_GUEST_ES, &seg_desc,
+ faultptr);
+ if (error || *faultptr)
+ return (error);
+ update_seg_desc(ctx, vcpu, VM_REG_GUEST_ES, &seg_desc);
+
+ error = validate_seg_desc(ctx, vcpu, ts, VM_REG_GUEST_FS, &seg_desc,
+ faultptr);
+ if (error || *faultptr)
+ return (error);
+ update_seg_desc(ctx, vcpu, VM_REG_GUEST_FS, &seg_desc);
+
+ error = validate_seg_desc(ctx, vcpu, ts, VM_REG_GUEST_GS, &seg_desc,
+ faultptr);
+ if (error || *faultptr)
+ return (error);
+ update_seg_desc(ctx, vcpu, VM_REG_GUEST_GS, &seg_desc);
+
+ return (0);
+}
+
+/*
+ * Push an error code on the stack of the new task. This is needed if the
+ * task switch was triggered by a hardware exception that causes an error
+ * code to be saved (e.g. #PF).
+ */
+static int
+push_errcode(struct vmctx *ctx, int vcpu, struct vm_guest_paging *paging,
+ int task_type, uint32_t errcode, int *faultptr)
+{
+ struct iovec iov[2];
+ struct seg_desc seg_desc;
+ int stacksize, bytes, error;
+ uint64_t gla, cr0, rflags;
+ uint32_t esp;
+ uint16_t stacksel;
+
+ *faultptr = 0;
+
+ cr0 = GETREG(ctx, vcpu, VM_REG_GUEST_CR0);
+ rflags = GETREG(ctx, vcpu, VM_REG_GUEST_RFLAGS);
+ stacksel = GETREG(ctx, vcpu, VM_REG_GUEST_SS);
+
+ error = vm_get_desc(ctx, vcpu, VM_REG_GUEST_SS, &seg_desc.base,
+ &seg_desc.limit, &seg_desc.access);
+ assert(error == 0);
+
+ /*
+ * Section "Error Code" in the Intel SDM vol 3: the error code is
+ * pushed on the stack as a doubleword or word (depending on the
+ * default interrupt, trap or task gate size).
+ */
+ if (task_type == SDT_SYS386BSY || task_type == SDT_SYS386TSS)
+ bytes = 4;
+ else
+ bytes = 2;
+
+ /*
+ * PUSH instruction from Intel SDM vol 2: the 'B' flag in the
+ * stack-segment descriptor determines the size of the stack
+ * pointer outside of 64-bit mode.
+ */
+ if (SEG_DESC_DEF32(seg_desc.access))
+ stacksize = 4;
+ else
+ stacksize = 2;
+
+ esp = GETREG(ctx, vcpu, VM_REG_GUEST_RSP);
+ esp -= bytes;
+
+ if (vie_calculate_gla(paging->cpu_mode, VM_REG_GUEST_SS,
+ &seg_desc, esp, bytes, stacksize, PROT_WRITE, &gla)) {
+ sel_exception(ctx, vcpu, IDT_SS, stacksel, 1);
+ *faultptr = 1;
+ return (0);
+ }
+
+ if (vie_alignment_check(paging->cpl, bytes, cr0, rflags, gla)) {
+ vm_inject_ac(ctx, vcpu, 1);
+ *faultptr = 1;
+ return (0);
+ }
+
+ error = vm_copy_setup(ctx, vcpu, paging, gla, bytes, PROT_WRITE,
+ iov, nitems(iov), faultptr);
+ if (error || *faultptr)
+ return (error);
+
+ vm_copyout(ctx, vcpu, &errcode, iov, bytes);
+ SETREG(ctx, vcpu, VM_REG_GUEST_RSP, esp);
+ return (0);
+}
+
+/*
+ * Evaluate return value from helper functions and potentially return to
+ * the VM run loop.
+ */
+#define CHKERR(error,fault) \
+ do { \
+ assert((error == 0) || (error == EFAULT)); \
+ if (error) \
+ return (VMEXIT_ABORT); \
+ else if (fault) \
+ return (VMEXIT_CONTINUE); \
+ } while (0)
+
+int
+vmexit_task_switch(struct vmctx *ctx, struct vm_exit *vmexit, int *pvcpu)
+{
+ struct seg_desc nt;
+ struct tss32 oldtss, newtss;
+ struct vm_task_switch *task_switch;
+ struct vm_guest_paging *paging, sup_paging;
+ struct user_segment_descriptor nt_desc, ot_desc;
+ struct iovec nt_iov[2], ot_iov[2];
+ uint64_t cr0, ot_base;
+ uint32_t eip, ot_lim, access;
+ int error, ext, fault, minlimit, nt_type, ot_type, vcpu;
+ enum task_switch_reason reason;
+ uint16_t nt_sel, ot_sel;
+
+ task_switch = &vmexit->u.task_switch;
+ nt_sel = task_switch->tsssel;
+ ext = vmexit->u.task_switch.ext;
+ reason = vmexit->u.task_switch.reason;
+ paging = &vmexit->u.task_switch.paging;
+ vcpu = *pvcpu;
+
+ assert(paging->cpu_mode == CPU_MODE_PROTECTED);
+
+ /*
+ * Calculate the instruction pointer to store in the old TSS.
+ */
+ eip = vmexit->rip + vmexit->inst_length;
+
+ /*
+ * Section 4.6, "Access Rights" in Intel SDM Vol 3.
+ * The following page table accesses are implicitly supervisor mode:
+ * - accesses to GDT or LDT to load segment descriptors
+ * - accesses to the task state segment during task switch
+ */
+ sup_paging = *paging;
+ sup_paging.cpl = 0; /* implicit supervisor mode */
+
+ /* Fetch the new TSS descriptor */
+ error = read_tss_descriptor(ctx, vcpu, task_switch, nt_sel, &nt_desc,
+ &fault);
+ CHKERR(error, fault);
+
+ nt = usd_to_seg_desc(&nt_desc);
+
+ /* Verify the type of the new TSS */
+ nt_type = SEG_DESC_TYPE(nt.access);
+ if (nt_type != SDT_SYS386BSY && nt_type != SDT_SYS386TSS &&
+ nt_type != SDT_SYS286BSY && nt_type != SDT_SYS286TSS) {
+ sel_exception(ctx, vcpu, IDT_TS, nt_sel, ext);
+ goto done;
+ }
+
+ /* TSS descriptor must have present bit set */
+ if (!SEG_DESC_PRESENT(nt.access)) {
+ sel_exception(ctx, vcpu, IDT_NP, nt_sel, ext);
+ goto done;
+ }
+
+ /*
+ * TSS must have a minimum length of 104 bytes for a 32-bit TSS and
+ * 44 bytes for a 16-bit TSS.
+ */
+ if (nt_type == SDT_SYS386BSY || nt_type == SDT_SYS386TSS)
+ minlimit = 104 - 1;
+ else if (nt_type == SDT_SYS286BSY || nt_type == SDT_SYS286TSS)
+ minlimit = 44 - 1;
+ else
+ minlimit = 0;
+
+ assert(minlimit > 0);
+ if (nt.limit < minlimit) {
+ sel_exception(ctx, vcpu, IDT_TS, nt_sel, ext);
+ goto done;
+ }
+
+ /* TSS must be busy if task switch is due to IRET */
+ if (reason == TSR_IRET && !TSS_BUSY(nt_type)) {
+ sel_exception(ctx, vcpu, IDT_TS, nt_sel, ext);
+ goto done;
+ }
+
+ /*
+ * TSS must be available (not busy) if task switch reason is
+ * CALL, JMP, exception or interrupt.
+ */
+ if (reason != TSR_IRET && TSS_BUSY(nt_type)) {
+ sel_exception(ctx, vcpu, IDT_GP, nt_sel, ext);
+ goto done;
+ }
+
+ /* Fetch the new TSS */
+ error = vm_copy_setup(ctx, vcpu, &sup_paging, nt.base, minlimit + 1,
+ PROT_READ | PROT_WRITE, nt_iov, nitems(nt_iov), &fault);
+ CHKERR(error, fault);
+ vm_copyin(ctx, vcpu, nt_iov, &newtss, minlimit + 1);
+
+ /* Get the old TSS selector from the guest's task register */
+ ot_sel = GETREG(ctx, vcpu, VM_REG_GUEST_TR);
+ if (ISLDT(ot_sel) || IDXSEL(ot_sel) == 0) {
+ /*
+ * This might happen if a task switch was attempted without
+ * ever loading the task register with LTR. In this case the
+ * TR would contain the values from power-on:
+ * (sel = 0, base = 0, limit = 0xffff).
+ */
+ sel_exception(ctx, vcpu, IDT_TS, ot_sel, task_switch->ext);
+ goto done;
+ }
+
+ /* Get the old TSS base and limit from the guest's task register */
+ error = vm_get_desc(ctx, vcpu, VM_REG_GUEST_TR, &ot_base, &ot_lim,
+ &access);
+ assert(error == 0);
+ assert(!SEG_DESC_UNUSABLE(access) && SEG_DESC_PRESENT(access));
+ ot_type = SEG_DESC_TYPE(access);
+ assert(ot_type == SDT_SYS386BSY || ot_type == SDT_SYS286BSY);
+
+ /* Fetch the old TSS descriptor */
+ error = read_tss_descriptor(ctx, vcpu, task_switch, ot_sel, &ot_desc,
+ &fault);
+ CHKERR(error, fault);
+
+ /* Get the old TSS */
+ error = vm_copy_setup(ctx, vcpu, &sup_paging, ot_base, minlimit + 1,
+ PROT_READ | PROT_WRITE, ot_iov, nitems(ot_iov), &fault);
+ CHKERR(error, fault);
+ vm_copyin(ctx, vcpu, ot_iov, &oldtss, minlimit + 1);
+
+ /*
+ * Clear the busy bit in the old TSS descriptor if the task switch
+ * due to an IRET or JMP instruction.
+ */
+ if (reason == TSR_IRET || reason == TSR_JMP) {
+ ot_desc.sd_type &= ~0x2;
+ error = desc_table_write(ctx, vcpu, &sup_paging, ot_sel,
+ &ot_desc, &fault);
+ CHKERR(error, fault);
+ }
+
+ if (nt_type == SDT_SYS286BSY || nt_type == SDT_SYS286TSS) {
+ fprintf(stderr, "Task switch to 16-bit TSS not supported\n");
+ return (VMEXIT_ABORT);
+ }
+
+ /* Save processor state in old TSS */
+ tss32_save(ctx, vcpu, task_switch, eip, &oldtss, ot_iov);
+
+ /*
+ * If the task switch was triggered for any reason other than IRET
+ * then set the busy bit in the new TSS descriptor.
+ */
+ if (reason != TSR_IRET) {
+ nt_desc.sd_type |= 0x2;
+ error = desc_table_write(ctx, vcpu, &sup_paging, nt_sel,
+ &nt_desc, &fault);
+ CHKERR(error, fault);
+ }
+
+ /* Update task register to point at the new TSS */
+ SETREG(ctx, vcpu, VM_REG_GUEST_TR, nt_sel);
+
+ /* Update the hidden descriptor state of the task register */
+ nt = usd_to_seg_desc(&nt_desc);
+ update_seg_desc(ctx, vcpu, VM_REG_GUEST_TR, &nt);
+
+ /* Set CR0.TS */
+ cr0 = GETREG(ctx, vcpu, VM_REG_GUEST_CR0);
+ SETREG(ctx, vcpu, VM_REG_GUEST_CR0, cr0 | CR0_TS);
+
+ /*
+ * We are now committed to the task switch. Any exceptions encountered
+ * after this point will be handled in the context of the new task and
+ * the saved instruction pointer will belong to the new task.
+ */
+ error = vm_set_register(ctx, vcpu, VM_REG_GUEST_RIP, newtss.tss_eip);
+ assert(error == 0);
+
+ /* Load processor state from new TSS */
+ error = tss32_restore(ctx, vcpu, task_switch, ot_sel, &newtss, nt_iov,
+ &fault);
+ CHKERR(error, fault);
+
+ /*
+ * Section "Interrupt Tasks" in Intel SDM, Vol 3: if an exception
+ * caused an error code to be generated, this error code is copied
+ * to the stack of the new task.
+ */
+ if (task_switch->errcode_valid) {
+ assert(task_switch->ext);
+ assert(task_switch->reason == TSR_IDT_GATE);
+ error = push_errcode(ctx, vcpu, &task_switch->paging, nt_type,
+ task_switch->errcode, &fault);
+ CHKERR(error, fault);
+ }
+
+ /*
+ * Treatment of virtual-NMI blocking if NMI is delivered through
+ * a task gate.
+ *
+ * Section "Architectural State Before A VM Exit", Intel SDM, Vol3:
+ * If the virtual NMIs VM-execution control is 1, VM entry injects
+ * an NMI, and delivery of the NMI causes a task switch that causes
+ * a VM exit, virtual-NMI blocking is in effect before the VM exit
+ * commences.
+ *
+ * Thus, virtual-NMI blocking is in effect at the time of the task
+ * switch VM exit.
+ */
+
+ /*
+ * Treatment of virtual-NMI unblocking on IRET from NMI handler task.
+ *
+ * Section "Changes to Instruction Behavior in VMX Non-Root Operation"
+ * If "virtual NMIs" control is 1 IRET removes any virtual-NMI blocking.
+ * This unblocking of virtual-NMI occurs even if IRET causes a fault.
+ *
+ * Thus, virtual-NMI blocking is cleared at the time of the task switch
+ * VM exit.
+ */
+
+ /*
+ * If the task switch was triggered by an event delivered through
+ * the IDT then extinguish the pending event from the vcpu's
+ * exitintinfo.
+ */
+ if (task_switch->reason == TSR_IDT_GATE) {
+ error = vm_set_intinfo(ctx, vcpu, 0);
+ assert(error == 0);
+ }
+
+ /*
+ * XXX should inject debug exception if 'T' bit is 1
+ */
+done:
+ return (VMEXIT_CONTINUE);
+}
diff --git a/usr.sbin/bhyve/uart_emul.c b/usr.sbin/bhyve/uart_emul.c
new file mode 100644
index 0000000..538bf58
--- /dev/null
+++ b/usr.sbin/bhyve/uart_emul.c
@@ -0,0 +1,674 @@
+/*-
+ * Copyright (c) 2012 NetApp, Inc.
+ * Copyright (c) 2013 Neel Natu <neel@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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <sys/types.h>
+#include <dev/ic/ns16550.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <string.h>
+#include <pthread.h>
+
+#include "mevent.h"
+#include "uart_emul.h"
+
+#define COM1_BASE 0x3F8
+#define COM1_IRQ 4
+#define COM2_BASE 0x2F8
+#define COM2_IRQ 3
+
+#define DEFAULT_RCLK 1843200
+#define DEFAULT_BAUD 9600
+
+#define FCR_RX_MASK 0xC0
+
+#define MCR_OUT1 0x04
+#define MCR_OUT2 0x08
+
+#define MSR_DELTA_MASK 0x0f
+
+#ifndef REG_SCR
+#define REG_SCR com_scr
+#endif
+
+#define FIFOSZ 16
+
+static bool uart_stdio; /* stdio in use for i/o */
+static struct termios tio_stdio_orig;
+
+static struct {
+ int baseaddr;
+ int irq;
+ bool inuse;
+} uart_lres[] = {
+ { COM1_BASE, COM1_IRQ, false},
+ { COM2_BASE, COM2_IRQ, false},
+};
+
+#define UART_NLDEVS (sizeof(uart_lres) / sizeof(uart_lres[0]))
+
+struct fifo {
+ uint8_t buf[FIFOSZ];
+ int rindex; /* index to read from */
+ int windex; /* index to write to */
+ int num; /* number of characters in the fifo */
+ int size; /* size of the fifo */
+};
+
+struct ttyfd {
+ bool opened;
+ int fd; /* tty device file descriptor */
+ struct termios tio_orig, tio_new; /* I/O Terminals */
+};
+
+struct uart_softc {
+ pthread_mutex_t mtx; /* protects all softc elements */
+ uint8_t data; /* Data register (R/W) */
+ uint8_t ier; /* Interrupt enable register (R/W) */
+ uint8_t lcr; /* Line control register (R/W) */
+ uint8_t mcr; /* Modem control register (R/W) */
+ uint8_t lsr; /* Line status register (R/W) */
+ uint8_t msr; /* Modem status register (R/W) */
+ uint8_t fcr; /* FIFO control register (W) */
+ uint8_t scr; /* Scratch register (R/W) */
+
+ uint8_t dll; /* Baudrate divisor latch LSB */
+ uint8_t dlh; /* Baudrate divisor latch MSB */
+
+ struct fifo rxfifo;
+ struct mevent *mev;
+
+ struct ttyfd tty;
+ bool thre_int_pending; /* THRE interrupt pending */
+
+ void *arg;
+ uart_intr_func_t intr_assert;
+ uart_intr_func_t intr_deassert;
+};
+
+static void uart_drain(int fd, enum ev_type ev, void *arg);
+
+static void
+ttyclose(void)
+{
+
+ tcsetattr(STDIN_FILENO, TCSANOW, &tio_stdio_orig);
+}
+
+static void
+ttyopen(struct ttyfd *tf)
+{
+
+ tcgetattr(tf->fd, &tf->tio_orig);
+
+ tf->tio_new = tf->tio_orig;
+ cfmakeraw(&tf->tio_new);
+ tf->tio_new.c_cflag |= CLOCAL;
+ tcsetattr(tf->fd, TCSANOW, &tf->tio_new);
+
+ if (tf->fd == STDIN_FILENO) {
+ tio_stdio_orig = tf->tio_orig;
+ atexit(ttyclose);
+ }
+}
+
+static int
+ttyread(struct ttyfd *tf)
+{
+ unsigned char rb;
+
+ if (read(tf->fd, &rb, 1) == 1)
+ return (rb);
+ else
+ return (-1);
+}
+
+static void
+ttywrite(struct ttyfd *tf, unsigned char wb)
+{
+
+ (void)write(tf->fd, &wb, 1);
+}
+
+static void
+rxfifo_reset(struct uart_softc *sc, int size)
+{
+ char flushbuf[32];
+ struct fifo *fifo;
+ ssize_t nread;
+ int error;
+
+ fifo = &sc->rxfifo;
+ bzero(fifo, sizeof(struct fifo));
+ fifo->size = size;
+
+ if (sc->tty.opened) {
+ /*
+ * Flush any unread input from the tty buffer.
+ */
+ while (1) {
+ nread = read(sc->tty.fd, flushbuf, sizeof(flushbuf));
+ if (nread != sizeof(flushbuf))
+ break;
+ }
+
+ /*
+ * Enable mevent to trigger when new characters are available
+ * on the tty fd.
+ */
+ error = mevent_enable(sc->mev);
+ assert(error == 0);
+ }
+}
+
+static int
+rxfifo_available(struct uart_softc *sc)
+{
+ struct fifo *fifo;
+
+ fifo = &sc->rxfifo;
+ return (fifo->num < fifo->size);
+}
+
+static int
+rxfifo_putchar(struct uart_softc *sc, uint8_t ch)
+{
+ struct fifo *fifo;
+ int error;
+
+ fifo = &sc->rxfifo;
+
+ if (fifo->num < fifo->size) {
+ fifo->buf[fifo->windex] = ch;
+ fifo->windex = (fifo->windex + 1) % fifo->size;
+ fifo->num++;
+ if (!rxfifo_available(sc)) {
+ if (sc->tty.opened) {
+ /*
+ * Disable mevent callback if the FIFO is full.
+ */
+ error = mevent_disable(sc->mev);
+ assert(error == 0);
+ }
+ }
+ return (0);
+ } else
+ return (-1);
+}
+
+static int
+rxfifo_getchar(struct uart_softc *sc)
+{
+ struct fifo *fifo;
+ int c, error, wasfull;
+
+ wasfull = 0;
+ fifo = &sc->rxfifo;
+ if (fifo->num > 0) {
+ if (!rxfifo_available(sc))
+ wasfull = 1;
+ c = fifo->buf[fifo->rindex];
+ fifo->rindex = (fifo->rindex + 1) % fifo->size;
+ fifo->num--;
+ if (wasfull) {
+ if (sc->tty.opened) {
+ error = mevent_enable(sc->mev);
+ assert(error == 0);
+ }
+ }
+ return (c);
+ } else
+ return (-1);
+}
+
+static int
+rxfifo_numchars(struct uart_softc *sc)
+{
+ struct fifo *fifo = &sc->rxfifo;
+
+ return (fifo->num);
+}
+
+static void
+uart_opentty(struct uart_softc *sc)
+{
+
+ ttyopen(&sc->tty);
+ sc->mev = mevent_add(sc->tty.fd, EVF_READ, uart_drain, sc);
+ assert(sc->mev != NULL);
+}
+
+static uint8_t
+modem_status(uint8_t mcr)
+{
+ uint8_t msr;
+
+ if (mcr & MCR_LOOPBACK) {
+ /*
+ * In the loopback mode certain bits from the MCR are
+ * reflected back into MSR.
+ */
+ msr = 0;
+ if (mcr & MCR_RTS)
+ msr |= MSR_CTS;
+ if (mcr & MCR_DTR)
+ msr |= MSR_DSR;
+ if (mcr & MCR_OUT1)
+ msr |= MSR_RI;
+ if (mcr & MCR_OUT2)
+ msr |= MSR_DCD;
+ } else {
+ /*
+ * Always assert DCD and DSR so tty open doesn't block
+ * even if CLOCAL is turned off.
+ */
+ msr = MSR_DCD | MSR_DSR;
+ }
+ assert((msr & MSR_DELTA_MASK) == 0);
+
+ return (msr);
+}
+
+/*
+ * The IIR returns a prioritized interrupt reason:
+ * - receive data available
+ * - transmit holding register empty
+ * - modem status change
+ *
+ * Return an interrupt reason if one is available.
+ */
+static int
+uart_intr_reason(struct uart_softc *sc)
+{
+
+ if ((sc->lsr & LSR_OE) != 0 && (sc->ier & IER_ERLS) != 0)
+ return (IIR_RLS);
+ else if (rxfifo_numchars(sc) > 0 && (sc->ier & IER_ERXRDY) != 0)
+ return (IIR_RXTOUT);
+ else if (sc->thre_int_pending && (sc->ier & IER_ETXRDY) != 0)
+ return (IIR_TXRDY);
+ else if ((sc->msr & MSR_DELTA_MASK) != 0 && (sc->ier & IER_EMSC) != 0)
+ return (IIR_MLSC);
+ else
+ return (IIR_NOPEND);
+}
+
+static void
+uart_reset(struct uart_softc *sc)
+{
+ uint16_t divisor;
+
+ divisor = DEFAULT_RCLK / DEFAULT_BAUD / 16;
+ sc->dll = divisor;
+ sc->dlh = divisor >> 16;
+ sc->msr = modem_status(sc->mcr);
+
+ rxfifo_reset(sc, 1); /* no fifo until enabled by software */
+}
+
+/*
+ * Toggle the COM port's intr pin depending on whether or not we have an
+ * interrupt condition to report to the processor.
+ */
+static void
+uart_toggle_intr(struct uart_softc *sc)
+{
+ uint8_t intr_reason;
+
+ intr_reason = uart_intr_reason(sc);
+
+ if (intr_reason == IIR_NOPEND)
+ (*sc->intr_deassert)(sc->arg);
+ else
+ (*sc->intr_assert)(sc->arg);
+}
+
+static void
+uart_drain(int fd, enum ev_type ev, void *arg)
+{
+ struct uart_softc *sc;
+ int ch;
+
+ sc = arg;
+
+ assert(fd == sc->tty.fd);
+ assert(ev == EVF_READ);
+
+ /*
+ * This routine is called in the context of the mevent thread
+ * to take out the softc lock to protect against concurrent
+ * access from a vCPU i/o exit
+ */
+ pthread_mutex_lock(&sc->mtx);
+
+ if ((sc->mcr & MCR_LOOPBACK) != 0) {
+ (void) ttyread(&sc->tty);
+ } else {
+ while (rxfifo_available(sc) &&
+ ((ch = ttyread(&sc->tty)) != -1)) {
+ rxfifo_putchar(sc, ch);
+ }
+ uart_toggle_intr(sc);
+ }
+
+ pthread_mutex_unlock(&sc->mtx);
+}
+
+void
+uart_write(struct uart_softc *sc, int offset, uint8_t value)
+{
+ int fifosz;
+ uint8_t msr;
+
+ pthread_mutex_lock(&sc->mtx);
+
+ /*
+ * Take care of the special case DLAB accesses first
+ */
+ if ((sc->lcr & LCR_DLAB) != 0) {
+ if (offset == REG_DLL) {
+ sc->dll = value;
+ goto done;
+ }
+
+ if (offset == REG_DLH) {
+ sc->dlh = value;
+ goto done;
+ }
+ }
+
+ switch (offset) {
+ case REG_DATA:
+ if (sc->mcr & MCR_LOOPBACK) {
+ if (rxfifo_putchar(sc, value) != 0)
+ sc->lsr |= LSR_OE;
+ } else if (sc->tty.opened) {
+ ttywrite(&sc->tty, value);
+ } /* else drop on floor */
+ sc->thre_int_pending = true;
+ break;
+ case REG_IER:
+ /*
+ * Apply mask so that bits 4-7 are 0
+ * Also enables bits 0-3 only if they're 1
+ */
+ sc->ier = value & 0x0F;
+ break;
+ case REG_FCR:
+ /*
+ * When moving from FIFO and 16450 mode and vice versa,
+ * the FIFO contents are reset.
+ */
+ if ((sc->fcr & FCR_ENABLE) ^ (value & FCR_ENABLE)) {
+ fifosz = (value & FCR_ENABLE) ? FIFOSZ : 1;
+ rxfifo_reset(sc, fifosz);
+ }
+
+ /*
+ * The FCR_ENABLE bit must be '1' for the programming
+ * of other FCR bits to be effective.
+ */
+ if ((value & FCR_ENABLE) == 0) {
+ sc->fcr = 0;
+ } else {
+ if ((value & FCR_RCV_RST) != 0)
+ rxfifo_reset(sc, FIFOSZ);
+
+ sc->fcr = value &
+ (FCR_ENABLE | FCR_DMA | FCR_RX_MASK);
+ }
+ break;
+ case REG_LCR:
+ sc->lcr = value;
+ break;
+ case REG_MCR:
+ /* Apply mask so that bits 5-7 are 0 */
+ sc->mcr = value & 0x1F;
+ msr = modem_status(sc->mcr);
+
+ /*
+ * Detect if there has been any change between the
+ * previous and the new value of MSR. If there is
+ * then assert the appropriate MSR delta bit.
+ */
+ if ((msr & MSR_CTS) ^ (sc->msr & MSR_CTS))
+ sc->msr |= MSR_DCTS;
+ if ((msr & MSR_DSR) ^ (sc->msr & MSR_DSR))
+ sc->msr |= MSR_DDSR;
+ if ((msr & MSR_DCD) ^ (sc->msr & MSR_DCD))
+ sc->msr |= MSR_DDCD;
+ if ((sc->msr & MSR_RI) != 0 && (msr & MSR_RI) == 0)
+ sc->msr |= MSR_TERI;
+
+ /*
+ * Update the value of MSR while retaining the delta
+ * bits.
+ */
+ sc->msr &= MSR_DELTA_MASK;
+ sc->msr |= msr;
+ break;
+ case REG_LSR:
+ /*
+ * Line status register is not meant to be written to
+ * during normal operation.
+ */
+ break;
+ case REG_MSR:
+ /*
+ * As far as I can tell MSR is a read-only register.
+ */
+ break;
+ case REG_SCR:
+ sc->scr = value;
+ break;
+ default:
+ break;
+ }
+
+done:
+ uart_toggle_intr(sc);
+ pthread_mutex_unlock(&sc->mtx);
+}
+
+uint8_t
+uart_read(struct uart_softc *sc, int offset)
+{
+ uint8_t iir, intr_reason, reg;
+
+ pthread_mutex_lock(&sc->mtx);
+
+ /*
+ * Take care of the special case DLAB accesses first
+ */
+ if ((sc->lcr & LCR_DLAB) != 0) {
+ if (offset == REG_DLL) {
+ reg = sc->dll;
+ goto done;
+ }
+
+ if (offset == REG_DLH) {
+ reg = sc->dlh;
+ goto done;
+ }
+ }
+
+ switch (offset) {
+ case REG_DATA:
+ reg = rxfifo_getchar(sc);
+ break;
+ case REG_IER:
+ reg = sc->ier;
+ break;
+ case REG_IIR:
+ iir = (sc->fcr & FCR_ENABLE) ? IIR_FIFO_MASK : 0;
+
+ intr_reason = uart_intr_reason(sc);
+
+ /*
+ * Deal with side effects of reading the IIR register
+ */
+ if (intr_reason == IIR_TXRDY)
+ sc->thre_int_pending = false;
+
+ iir |= intr_reason;
+
+ reg = iir;
+ break;
+ case REG_LCR:
+ reg = sc->lcr;
+ break;
+ case REG_MCR:
+ reg = sc->mcr;
+ break;
+ case REG_LSR:
+ /* Transmitter is always ready for more data */
+ sc->lsr |= LSR_TEMT | LSR_THRE;
+
+ /* Check for new receive data */
+ if (rxfifo_numchars(sc) > 0)
+ sc->lsr |= LSR_RXRDY;
+ else
+ sc->lsr &= ~LSR_RXRDY;
+
+ reg = sc->lsr;
+
+ /* The LSR_OE bit is cleared on LSR read */
+ sc->lsr &= ~LSR_OE;
+ break;
+ case REG_MSR:
+ /*
+ * MSR delta bits are cleared on read
+ */
+ reg = sc->msr;
+ sc->msr &= ~MSR_DELTA_MASK;
+ break;
+ case REG_SCR:
+ reg = sc->scr;
+ break;
+ default:
+ reg = 0xFF;
+ break;
+ }
+
+done:
+ uart_toggle_intr(sc);
+ pthread_mutex_unlock(&sc->mtx);
+
+ return (reg);
+}
+
+int
+uart_legacy_alloc(int which, int *baseaddr, int *irq)
+{
+
+ if (which < 0 || which >= UART_NLDEVS || uart_lres[which].inuse)
+ return (-1);
+
+ uart_lres[which].inuse = true;
+ *baseaddr = uart_lres[which].baseaddr;
+ *irq = uart_lres[which].irq;
+
+ return (0);
+}
+
+struct uart_softc *
+uart_init(uart_intr_func_t intr_assert, uart_intr_func_t intr_deassert,
+ void *arg)
+{
+ struct uart_softc *sc;
+
+ sc = calloc(1, sizeof(struct uart_softc));
+
+ sc->arg = arg;
+ sc->intr_assert = intr_assert;
+ sc->intr_deassert = intr_deassert;
+
+ pthread_mutex_init(&sc->mtx, NULL);
+
+ uart_reset(sc);
+
+ return (sc);
+}
+
+static int
+uart_tty_backend(struct uart_softc *sc, const char *opts)
+{
+ int fd;
+ int retval;
+
+ retval = -1;
+
+ fd = open(opts, O_RDWR | O_NONBLOCK);
+ if (fd > 0 && isatty(fd)) {
+ sc->tty.fd = fd;
+ sc->tty.opened = true;
+ retval = 0;
+ }
+
+ return (retval);
+}
+
+int
+uart_set_backend(struct uart_softc *sc, const char *opts)
+{
+ int retval;
+
+ retval = -1;
+
+ if (opts == NULL)
+ return (0);
+
+ if (strcmp("stdio", opts) == 0) {
+ if (!uart_stdio) {
+ sc->tty.fd = STDIN_FILENO;
+ sc->tty.opened = true;
+ uart_stdio = true;
+ retval = 0;
+ }
+ } else if (uart_tty_backend(sc, opts) == 0) {
+ retval = 0;
+ }
+
+ /* Make the backend file descriptor non-blocking */
+ if (retval == 0)
+ retval = fcntl(sc->tty.fd, F_SETFL, O_NONBLOCK);
+
+ if (retval == 0)
+ uart_opentty(sc);
+
+ return (retval);
+}
diff --git a/usr.sbin/bhyve/uart_emul.h b/usr.sbin/bhyve/uart_emul.h
new file mode 100644
index 0000000..993b92e
--- /dev/null
+++ b/usr.sbin/bhyve/uart_emul.h
@@ -0,0 +1,45 @@
+/*-
+ * Copyright (c) 2013 Neel Natu <neel@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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 _UART_EMUL_H_
+#define _UART_EMUL_H_
+
+
+#define UART_IO_BAR_SIZE 8
+
+struct uart_softc;
+
+typedef void (*uart_intr_func_t)(void *arg);
+struct uart_softc *uart_init(uart_intr_func_t intr_assert,
+ uart_intr_func_t intr_deassert, void *arg);
+
+int uart_legacy_alloc(int unit, int *ioaddr, int *irq);
+uint8_t uart_read(struct uart_softc *sc, int offset);
+void uart_write(struct uart_softc *sc, int offset, uint8_t value);
+int uart_set_backend(struct uart_softc *sc, const char *opt);
+#endif
diff --git a/usr.sbin/bhyve/virtio.c b/usr.sbin/bhyve/virtio.c
new file mode 100644
index 0000000..11b1e62
--- /dev/null
+++ b/usr.sbin/bhyve/virtio.c
@@ -0,0 +1,777 @@
+/*-
+ * Copyright (c) 2013 Chris Torek <torek @ torek 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 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/uio.h>
+
+#include <stdio.h>
+#include <stdint.h>
+#include <pthread.h>
+#include <pthread_np.h>
+
+#include "bhyverun.h"
+#include "pci_emul.h"
+#include "virtio.h"
+
+/*
+ * Functions for dealing with generalized "virtual devices" as
+ * defined by <https://www.google.com/#output=search&q=virtio+spec>
+ */
+
+/*
+ * In case we decide to relax the "virtio softc comes at the
+ * front of virtio-based device softc" constraint, let's use
+ * this to convert.
+ */
+#define DEV_SOFTC(vs) ((void *)(vs))
+
+/*
+ * Link a virtio_softc to its constants, the device softc, and
+ * the PCI emulation.
+ */
+void
+vi_softc_linkup(struct virtio_softc *vs, struct virtio_consts *vc,
+ void *dev_softc, struct pci_devinst *pi,
+ struct vqueue_info *queues)
+{
+ int i;
+
+ /* vs and dev_softc addresses must match */
+ assert((void *)vs == dev_softc);
+ vs->vs_vc = vc;
+ vs->vs_pi = pi;
+ pi->pi_arg = vs;
+
+ vs->vs_queues = queues;
+ for (i = 0; i < vc->vc_nvq; i++) {
+ queues[i].vq_vs = vs;
+ queues[i].vq_num = i;
+ }
+}
+
+/*
+ * Reset device (device-wide). This erases all queues, i.e.,
+ * all the queues become invalid (though we don't wipe out the
+ * internal pointers, we just clear the VQ_ALLOC flag).
+ *
+ * It resets negotiated features to "none".
+ *
+ * If MSI-X is enabled, this also resets all the vectors to NO_VECTOR.
+ */
+void
+vi_reset_dev(struct virtio_softc *vs)
+{
+ struct vqueue_info *vq;
+ int i, nvq;
+
+ if (vs->vs_mtx)
+ assert(pthread_mutex_isowned_np(vs->vs_mtx));
+
+ nvq = vs->vs_vc->vc_nvq;
+ for (vq = vs->vs_queues, i = 0; i < nvq; vq++, i++) {
+ vq->vq_flags = 0;
+ vq->vq_last_avail = 0;
+ vq->vq_save_used = 0;
+ vq->vq_pfn = 0;
+ vq->vq_msix_idx = VIRTIO_MSI_NO_VECTOR;
+ }
+ vs->vs_negotiated_caps = 0;
+ vs->vs_curq = 0;
+ /* vs->vs_status = 0; -- redundant */
+ if (vs->vs_isr)
+ pci_lintr_deassert(vs->vs_pi);
+ vs->vs_isr = 0;
+ vs->vs_msix_cfg_idx = VIRTIO_MSI_NO_VECTOR;
+}
+
+/*
+ * Set I/O BAR (usually 0) to map PCI config registers.
+ */
+void
+vi_set_io_bar(struct virtio_softc *vs, int barnum)
+{
+ size_t size;
+
+ /*
+ * ??? should we use CFG0 if MSI-X is disabled?
+ * Existing code did not...
+ */
+ size = VTCFG_R_CFG1 + vs->vs_vc->vc_cfgsize;
+ pci_emul_alloc_bar(vs->vs_pi, barnum, PCIBAR_IO, size);
+}
+
+/*
+ * Initialize MSI-X vector capabilities if we're to use MSI-X,
+ * or MSI capabilities if not.
+ *
+ * We assume we want one MSI-X vector per queue, here, plus one
+ * for the config vec.
+ */
+int
+vi_intr_init(struct virtio_softc *vs, int barnum, int use_msix)
+{
+ int nvec;
+
+ if (use_msix) {
+ vs->vs_flags |= VIRTIO_USE_MSIX;
+ VS_LOCK(vs);
+ vi_reset_dev(vs); /* set all vectors to NO_VECTOR */
+ VS_UNLOCK(vs);
+ nvec = vs->vs_vc->vc_nvq + 1;
+ if (pci_emul_add_msixcap(vs->vs_pi, nvec, barnum))
+ return (1);
+ } else
+ vs->vs_flags &= ~VIRTIO_USE_MSIX;
+
+ /* Only 1 MSI vector for bhyve */
+ pci_emul_add_msicap(vs->vs_pi, 1);
+
+ /* Legacy interrupts are mandatory for virtio devices */
+ pci_lintr_request(vs->vs_pi);
+
+ return (0);
+}
+
+/*
+ * Initialize the currently-selected virtio queue (vs->vs_curq).
+ * The guest just gave us a page frame number, from which we can
+ * calculate the addresses of the queue.
+ */
+void
+vi_vq_init(struct virtio_softc *vs, uint32_t pfn)
+{
+ struct vqueue_info *vq;
+ uint64_t phys;
+ size_t size;
+ char *base;
+
+ vq = &vs->vs_queues[vs->vs_curq];
+ vq->vq_pfn = pfn;
+ phys = (uint64_t)pfn << VRING_PFN;
+ size = vring_size(vq->vq_qsize);
+ base = paddr_guest2host(vs->vs_pi->pi_vmctx, phys, size);
+
+ /* First page(s) are descriptors... */
+ vq->vq_desc = (struct virtio_desc *)base;
+ base += vq->vq_qsize * sizeof(struct virtio_desc);
+
+ /* ... immediately followed by "avail" ring (entirely uint16_t's) */
+ vq->vq_avail = (struct vring_avail *)base;
+ base += (2 + vq->vq_qsize + 1) * sizeof(uint16_t);
+
+ /* Then it's rounded up to the next page... */
+ base = (char *)roundup2((uintptr_t)base, VRING_ALIGN);
+
+ /* ... and the last page(s) are the used ring. */
+ vq->vq_used = (struct vring_used *)base;
+
+ /* Mark queue as allocated, and start at 0 when we use it. */
+ vq->vq_flags = VQ_ALLOC;
+ vq->vq_last_avail = 0;
+ vq->vq_save_used = 0;
+}
+
+/*
+ * Helper inline for vq_getchain(): record the i'th "real"
+ * descriptor.
+ */
+static inline void
+_vq_record(int i, volatile struct virtio_desc *vd, struct vmctx *ctx,
+ struct iovec *iov, int n_iov, uint16_t *flags) {
+
+ if (i >= n_iov)
+ return;
+ iov[i].iov_base = paddr_guest2host(ctx, vd->vd_addr, vd->vd_len);
+ iov[i].iov_len = vd->vd_len;
+ if (flags != NULL)
+ flags[i] = vd->vd_flags;
+}
+#define VQ_MAX_DESCRIPTORS 512 /* see below */
+
+/*
+ * Examine the chain of descriptors starting at the "next one" to
+ * make sure that they describe a sensible request. If so, return
+ * the number of "real" descriptors that would be needed/used in
+ * acting on this request. This may be smaller than the number of
+ * available descriptors, e.g., if there are two available but
+ * they are two separate requests, this just returns 1. Or, it
+ * may be larger: if there are indirect descriptors involved,
+ * there may only be one descriptor available but it may be an
+ * indirect pointing to eight more. We return 8 in this case,
+ * i.e., we do not count the indirect descriptors, only the "real"
+ * ones.
+ *
+ * Basically, this vets the vd_flags and vd_next field of each
+ * descriptor and tells you how many are involved. Since some may
+ * be indirect, this also needs the vmctx (in the pci_devinst
+ * at vs->vs_pi) so that it can find indirect descriptors.
+ *
+ * As we process each descriptor, we copy and adjust it (guest to
+ * host address wise, also using the vmtctx) into the given iov[]
+ * array (of the given size). If the array overflows, we stop
+ * placing values into the array but keep processing descriptors,
+ * up to VQ_MAX_DESCRIPTORS, before giving up and returning -1.
+ * So you, the caller, must not assume that iov[] is as big as the
+ * return value (you can process the same thing twice to allocate
+ * a larger iov array if needed, or supply a zero length to find
+ * out how much space is needed).
+ *
+ * If you want to verify the WRITE flag on each descriptor, pass a
+ * non-NULL "flags" pointer to an array of "uint16_t" of the same size
+ * as n_iov and we'll copy each vd_flags field after unwinding any
+ * indirects.
+ *
+ * If some descriptor(s) are invalid, this prints a diagnostic message
+ * and returns -1. If no descriptors are ready now it simply returns 0.
+ *
+ * You are assumed to have done a vq_ring_ready() if needed (note
+ * that vq_has_descs() does one).
+ */
+int
+vq_getchain(struct vqueue_info *vq, uint16_t *pidx,
+ struct iovec *iov, int n_iov, uint16_t *flags)
+{
+ int i;
+ u_int ndesc, n_indir;
+ u_int idx, next;
+ volatile struct virtio_desc *vdir, *vindir, *vp;
+ struct vmctx *ctx;
+ struct virtio_softc *vs;
+ const char *name;
+
+ vs = vq->vq_vs;
+ name = vs->vs_vc->vc_name;
+
+ /*
+ * Note: it's the responsibility of the guest not to
+ * update vq->vq_avail->va_idx until all of the descriptors
+ * the guest has written are valid (including all their
+ * vd_next fields and vd_flags).
+ *
+ * Compute (last_avail - va_idx) in integers mod 2**16. This is
+ * the number of descriptors the device has made available
+ * since the last time we updated vq->vq_last_avail.
+ *
+ * We just need to do the subtraction as an unsigned int,
+ * then trim off excess bits.
+ */
+ idx = vq->vq_last_avail;
+ ndesc = (uint16_t)((u_int)vq->vq_avail->va_idx - idx);
+ if (ndesc == 0)
+ return (0);
+ if (ndesc > vq->vq_qsize) {
+ /* XXX need better way to diagnose issues */
+ fprintf(stderr,
+ "%s: ndesc (%u) out of range, driver confused?\r\n",
+ name, (u_int)ndesc);
+ return (-1);
+ }
+
+ /*
+ * Now count/parse "involved" descriptors starting from
+ * the head of the chain.
+ *
+ * To prevent loops, we could be more complicated and
+ * check whether we're re-visiting a previously visited
+ * index, but we just abort if the count gets excessive.
+ */
+ ctx = vs->vs_pi->pi_vmctx;
+ *pidx = next = vq->vq_avail->va_ring[idx & (vq->vq_qsize - 1)];
+ vq->vq_last_avail++;
+ for (i = 0; i < VQ_MAX_DESCRIPTORS; next = vdir->vd_next) {
+ if (next >= vq->vq_qsize) {
+ fprintf(stderr,
+ "%s: descriptor index %u out of range, "
+ "driver confused?\r\n",
+ name, next);
+ return (-1);
+ }
+ vdir = &vq->vq_desc[next];
+ if ((vdir->vd_flags & VRING_DESC_F_INDIRECT) == 0) {
+ _vq_record(i, vdir, ctx, iov, n_iov, flags);
+ i++;
+ } else if ((vs->vs_vc->vc_hv_caps &
+ VIRTIO_RING_F_INDIRECT_DESC) == 0) {
+ fprintf(stderr,
+ "%s: descriptor has forbidden INDIRECT flag, "
+ "driver confused?\r\n",
+ name);
+ return (-1);
+ } else {
+ n_indir = vdir->vd_len / 16;
+ if ((vdir->vd_len & 0xf) || n_indir == 0) {
+ fprintf(stderr,
+ "%s: invalid indir len 0x%x, "
+ "driver confused?\r\n",
+ name, (u_int)vdir->vd_len);
+ return (-1);
+ }
+ vindir = paddr_guest2host(ctx,
+ vdir->vd_addr, vdir->vd_len);
+ /*
+ * Indirects start at the 0th, then follow
+ * their own embedded "next"s until those run
+ * out. Each one's indirect flag must be off
+ * (we don't really have to check, could just
+ * ignore errors...).
+ */
+ next = 0;
+ for (;;) {
+ vp = &vindir[next];
+ if (vp->vd_flags & VRING_DESC_F_INDIRECT) {
+ fprintf(stderr,
+ "%s: indirect desc has INDIR flag,"
+ " driver confused?\r\n",
+ name);
+ return (-1);
+ }
+ _vq_record(i, vp, ctx, iov, n_iov, flags);
+ if (++i > VQ_MAX_DESCRIPTORS)
+ goto loopy;
+ if ((vp->vd_flags & VRING_DESC_F_NEXT) == 0)
+ break;
+ next = vp->vd_next;
+ if (next >= n_indir) {
+ fprintf(stderr,
+ "%s: invalid next %u > %u, "
+ "driver confused?\r\n",
+ name, (u_int)next, n_indir);
+ return (-1);
+ }
+ }
+ }
+ if ((vdir->vd_flags & VRING_DESC_F_NEXT) == 0)
+ return (i);
+ }
+loopy:
+ fprintf(stderr,
+ "%s: descriptor loop? count > %d - driver confused?\r\n",
+ name, i);
+ return (-1);
+}
+
+/*
+ * Return the currently-first request chain back to the available queue.
+ *
+ * (This chain is the one you handled when you called vq_getchain()
+ * and used its positive return value.)
+ */
+void
+vq_retchain(struct vqueue_info *vq)
+{
+
+ vq->vq_last_avail--;
+}
+
+/*
+ * Return specified request chain to the guest, setting its I/O length
+ * to the provided value.
+ *
+ * (This chain is the one you handled when you called vq_getchain()
+ * and used its positive return value.)
+ */
+void
+vq_relchain(struct vqueue_info *vq, uint16_t idx, uint32_t iolen)
+{
+ uint16_t uidx, mask;
+ volatile struct vring_used *vuh;
+ volatile struct virtio_used *vue;
+
+ /*
+ * Notes:
+ * - mask is N-1 where N is a power of 2 so computes x % N
+ * - vuh points to the "used" data shared with guest
+ * - vue points to the "used" ring entry we want to update
+ * - head is the same value we compute in vq_iovecs().
+ *
+ * (I apologize for the two fields named vu_idx; the
+ * virtio spec calls the one that vue points to, "id"...)
+ */
+ mask = vq->vq_qsize - 1;
+ vuh = vq->vq_used;
+
+ uidx = vuh->vu_idx;
+ vue = &vuh->vu_ring[uidx++ & mask];
+ vue->vu_idx = idx;
+ vue->vu_tlen = iolen;
+ vuh->vu_idx = uidx;
+}
+
+/*
+ * Driver has finished processing "available" chains and calling
+ * vq_relchain on each one. If driver used all the available
+ * chains, used_all should be set.
+ *
+ * If the "used" index moved we may need to inform the guest, i.e.,
+ * deliver an interrupt. Even if the used index did NOT move we
+ * may need to deliver an interrupt, if the avail ring is empty and
+ * we are supposed to interrupt on empty.
+ *
+ * Note that used_all_avail is provided by the caller because it's
+ * a snapshot of the ring state when he decided to finish interrupt
+ * processing -- it's possible that descriptors became available after
+ * that point. (It's also typically a constant 1/True as well.)
+ */
+void
+vq_endchains(struct vqueue_info *vq, int used_all_avail)
+{
+ struct virtio_softc *vs;
+ uint16_t event_idx, new_idx, old_idx;
+ int intr;
+
+ /*
+ * Interrupt generation: if we're using EVENT_IDX,
+ * interrupt if we've crossed the event threshold.
+ * Otherwise interrupt is generated if we added "used" entries,
+ * but suppressed by VRING_AVAIL_F_NO_INTERRUPT.
+ *
+ * In any case, though, if NOTIFY_ON_EMPTY is set and the
+ * entire avail was processed, we need to interrupt always.
+ */
+ vs = vq->vq_vs;
+ old_idx = vq->vq_save_used;
+ vq->vq_save_used = new_idx = vq->vq_used->vu_idx;
+ if (used_all_avail &&
+ (vs->vs_negotiated_caps & VIRTIO_F_NOTIFY_ON_EMPTY))
+ intr = 1;
+ else if (vs->vs_negotiated_caps & VIRTIO_RING_F_EVENT_IDX) {
+ event_idx = VQ_USED_EVENT_IDX(vq);
+ /*
+ * This calculation is per docs and the kernel
+ * (see src/sys/dev/virtio/virtio_ring.h).
+ */
+ intr = (uint16_t)(new_idx - event_idx - 1) <
+ (uint16_t)(new_idx - old_idx);
+ } else {
+ intr = new_idx != old_idx &&
+ !(vq->vq_avail->va_flags & VRING_AVAIL_F_NO_INTERRUPT);
+ }
+ if (intr)
+ vq_interrupt(vs, vq);
+}
+
+/* Note: these are in sorted order to make for a fast search */
+static struct config_reg {
+ uint16_t cr_offset; /* register offset */
+ uint8_t cr_size; /* size (bytes) */
+ uint8_t cr_ro; /* true => reg is read only */
+ const char *cr_name; /* name of reg */
+} config_regs[] = {
+ { VTCFG_R_HOSTCAP, 4, 1, "HOSTCAP" },
+ { VTCFG_R_GUESTCAP, 4, 0, "GUESTCAP" },
+ { VTCFG_R_PFN, 4, 0, "PFN" },
+ { VTCFG_R_QNUM, 2, 1, "QNUM" },
+ { VTCFG_R_QSEL, 2, 0, "QSEL" },
+ { VTCFG_R_QNOTIFY, 2, 0, "QNOTIFY" },
+ { VTCFG_R_STATUS, 1, 0, "STATUS" },
+ { VTCFG_R_ISR, 1, 0, "ISR" },
+ { VTCFG_R_CFGVEC, 2, 0, "CFGVEC" },
+ { VTCFG_R_QVEC, 2, 0, "QVEC" },
+};
+
+static inline struct config_reg *
+vi_find_cr(int offset) {
+ u_int hi, lo, mid;
+ struct config_reg *cr;
+
+ lo = 0;
+ hi = sizeof(config_regs) / sizeof(*config_regs) - 1;
+ while (hi >= lo) {
+ mid = (hi + lo) >> 1;
+ cr = &config_regs[mid];
+ if (cr->cr_offset == offset)
+ return (cr);
+ if (cr->cr_offset < offset)
+ lo = mid + 1;
+ else
+ hi = mid - 1;
+ }
+ return (NULL);
+}
+
+/*
+ * Handle pci config space reads.
+ * If it's to the MSI-X info, do that.
+ * If it's part of the virtio standard stuff, do that.
+ * Otherwise dispatch to the actual driver.
+ */
+uint64_t
+vi_pci_read(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
+ int baridx, uint64_t offset, int size)
+{
+ struct virtio_softc *vs = pi->pi_arg;
+ struct virtio_consts *vc;
+ struct config_reg *cr;
+ uint64_t virtio_config_size, max;
+ const char *name;
+ uint32_t newoff;
+ uint32_t value;
+ int error;
+
+ if (vs->vs_flags & VIRTIO_USE_MSIX) {
+ if (baridx == pci_msix_table_bar(pi) ||
+ baridx == pci_msix_pba_bar(pi)) {
+ return (pci_emul_msix_tread(pi, offset, size));
+ }
+ }
+
+ /* XXX probably should do something better than just assert() */
+ assert(baridx == 0);
+
+ if (vs->vs_mtx)
+ pthread_mutex_lock(vs->vs_mtx);
+
+ vc = vs->vs_vc;
+ name = vc->vc_name;
+ value = size == 1 ? 0xff : size == 2 ? 0xffff : 0xffffffff;
+
+ if (size != 1 && size != 2 && size != 4)
+ goto bad;
+
+ if (pci_msix_enabled(pi))
+ virtio_config_size = VTCFG_R_CFG1;
+ else
+ virtio_config_size = VTCFG_R_CFG0;
+
+ if (offset >= virtio_config_size) {
+ /*
+ * Subtract off the standard size (including MSI-X
+ * registers if enabled) and dispatch to underlying driver.
+ * If that fails, fall into general code.
+ */
+ newoff = offset - virtio_config_size;
+ max = vc->vc_cfgsize ? vc->vc_cfgsize : 0x100000000;
+ if (newoff + size > max)
+ goto bad;
+ error = (*vc->vc_cfgread)(DEV_SOFTC(vs), newoff, size, &value);
+ if (!error)
+ goto done;
+ }
+
+bad:
+ cr = vi_find_cr(offset);
+ if (cr == NULL || cr->cr_size != size) {
+ if (cr != NULL) {
+ /* offset must be OK, so size must be bad */
+ fprintf(stderr,
+ "%s: read from %s: bad size %d\r\n",
+ name, cr->cr_name, size);
+ } else {
+ fprintf(stderr,
+ "%s: read from bad offset/size %jd/%d\r\n",
+ name, (uintmax_t)offset, size);
+ }
+ goto done;
+ }
+
+ switch (offset) {
+ case VTCFG_R_HOSTCAP:
+ value = vc->vc_hv_caps;
+ break;
+ case VTCFG_R_GUESTCAP:
+ value = vs->vs_negotiated_caps;
+ break;
+ case VTCFG_R_PFN:
+ if (vs->vs_curq < vc->vc_nvq)
+ value = vs->vs_queues[vs->vs_curq].vq_pfn;
+ break;
+ case VTCFG_R_QNUM:
+ value = vs->vs_curq < vc->vc_nvq ?
+ vs->vs_queues[vs->vs_curq].vq_qsize : 0;
+ break;
+ case VTCFG_R_QSEL:
+ value = vs->vs_curq;
+ break;
+ case VTCFG_R_QNOTIFY:
+ value = 0; /* XXX */
+ break;
+ case VTCFG_R_STATUS:
+ value = vs->vs_status;
+ break;
+ case VTCFG_R_ISR:
+ value = vs->vs_isr;
+ vs->vs_isr = 0; /* a read clears this flag */
+ if (value)
+ pci_lintr_deassert(pi);
+ break;
+ case VTCFG_R_CFGVEC:
+ value = vs->vs_msix_cfg_idx;
+ break;
+ case VTCFG_R_QVEC:
+ value = vs->vs_curq < vc->vc_nvq ?
+ vs->vs_queues[vs->vs_curq].vq_msix_idx :
+ VIRTIO_MSI_NO_VECTOR;
+ break;
+ }
+done:
+ if (vs->vs_mtx)
+ pthread_mutex_unlock(vs->vs_mtx);
+ return (value);
+}
+
+/*
+ * Handle pci config space writes.
+ * If it's to the MSI-X info, do that.
+ * If it's part of the virtio standard stuff, do that.
+ * Otherwise dispatch to the actual driver.
+ */
+void
+vi_pci_write(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
+ int baridx, uint64_t offset, int size, uint64_t value)
+{
+ struct virtio_softc *vs = pi->pi_arg;
+ struct vqueue_info *vq;
+ struct virtio_consts *vc;
+ struct config_reg *cr;
+ uint64_t virtio_config_size, max;
+ const char *name;
+ uint32_t newoff;
+ int error;
+
+ if (vs->vs_flags & VIRTIO_USE_MSIX) {
+ if (baridx == pci_msix_table_bar(pi) ||
+ baridx == pci_msix_pba_bar(pi)) {
+ pci_emul_msix_twrite(pi, offset, size, value);
+ return;
+ }
+ }
+
+ /* XXX probably should do something better than just assert() */
+ assert(baridx == 0);
+
+ if (vs->vs_mtx)
+ pthread_mutex_lock(vs->vs_mtx);
+
+ vc = vs->vs_vc;
+ name = vc->vc_name;
+
+ if (size != 1 && size != 2 && size != 4)
+ goto bad;
+
+ if (pci_msix_enabled(pi))
+ virtio_config_size = VTCFG_R_CFG1;
+ else
+ virtio_config_size = VTCFG_R_CFG0;
+
+ if (offset >= virtio_config_size) {
+ /*
+ * Subtract off the standard size (including MSI-X
+ * registers if enabled) and dispatch to underlying driver.
+ */
+ newoff = offset - virtio_config_size;
+ max = vc->vc_cfgsize ? vc->vc_cfgsize : 0x100000000;
+ if (newoff + size > max)
+ goto bad;
+ error = (*vc->vc_cfgwrite)(DEV_SOFTC(vs), newoff, size, value);
+ if (!error)
+ goto done;
+ }
+
+bad:
+ cr = vi_find_cr(offset);
+ if (cr == NULL || cr->cr_size != size || cr->cr_ro) {
+ if (cr != NULL) {
+ /* offset must be OK, wrong size and/or reg is R/O */
+ if (cr->cr_size != size)
+ fprintf(stderr,
+ "%s: write to %s: bad size %d\r\n",
+ name, cr->cr_name, size);
+ if (cr->cr_ro)
+ fprintf(stderr,
+ "%s: write to read-only reg %s\r\n",
+ name, cr->cr_name);
+ } else {
+ fprintf(stderr,
+ "%s: write to bad offset/size %jd/%d\r\n",
+ name, (uintmax_t)offset, size);
+ }
+ goto done;
+ }
+
+ switch (offset) {
+ case VTCFG_R_GUESTCAP:
+ vs->vs_negotiated_caps = value & vc->vc_hv_caps;
+ if (vc->vc_apply_features)
+ (*vc->vc_apply_features)(DEV_SOFTC(vs),
+ vs->vs_negotiated_caps);
+ break;
+ case VTCFG_R_PFN:
+ if (vs->vs_curq >= vc->vc_nvq)
+ goto bad_qindex;
+ vi_vq_init(vs, value);
+ break;
+ case VTCFG_R_QSEL:
+ /*
+ * Note that the guest is allowed to select an
+ * invalid queue; we just need to return a QNUM
+ * of 0 while the bad queue is selected.
+ */
+ vs->vs_curq = value;
+ break;
+ case VTCFG_R_QNOTIFY:
+ if (value >= vc->vc_nvq) {
+ fprintf(stderr, "%s: queue %d notify out of range\r\n",
+ name, (int)value);
+ goto done;
+ }
+ vq = &vs->vs_queues[value];
+ if (vq->vq_notify)
+ (*vq->vq_notify)(DEV_SOFTC(vs), vq);
+ else if (vc->vc_qnotify)
+ (*vc->vc_qnotify)(DEV_SOFTC(vs), vq);
+ else
+ fprintf(stderr,
+ "%s: qnotify queue %d: missing vq/vc notify\r\n",
+ name, (int)value);
+ break;
+ case VTCFG_R_STATUS:
+ vs->vs_status = value;
+ if (value == 0)
+ (*vc->vc_reset)(DEV_SOFTC(vs));
+ break;
+ case VTCFG_R_CFGVEC:
+ vs->vs_msix_cfg_idx = value;
+ break;
+ case VTCFG_R_QVEC:
+ if (vs->vs_curq >= vc->vc_nvq)
+ goto bad_qindex;
+ vq = &vs->vs_queues[vs->vs_curq];
+ vq->vq_msix_idx = value;
+ break;
+ }
+ goto done;
+
+bad_qindex:
+ fprintf(stderr,
+ "%s: write config reg %s: curq %d >= max %d\r\n",
+ name, cr->cr_name, vs->vs_curq, vc->vc_nvq);
+done:
+ if (vs->vs_mtx)
+ pthread_mutex_unlock(vs->vs_mtx);
+}
diff --git a/usr.sbin/bhyve/virtio.h b/usr.sbin/bhyve/virtio.h
new file mode 100644
index 0000000..0e96a1d
--- /dev/null
+++ b/usr.sbin/bhyve/virtio.h
@@ -0,0 +1,464 @@
+/*-
+ * Copyright (c) 2013 Chris Torek <torek @ torek 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 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 _VIRTIO_H_
+#define _VIRTIO_H_
+
+/*
+ * These are derived from several virtio specifications.
+ *
+ * Some useful links:
+ * https://github.com/rustyrussell/virtio-spec
+ * http://people.redhat.com/pbonzini/virtio-spec.pdf
+ */
+
+/*
+ * A virtual device has zero or more "virtual queues" (virtqueue).
+ * Each virtqueue uses at least two 4096-byte pages, laid out thus:
+ *
+ * +-----------------------------------------------+
+ * | "desc": <N> descriptors, 16 bytes each |
+ * | ----------------------------------------- |
+ * | "avail": 2 uint16; <N> uint16; 1 uint16 |
+ * | ----------------------------------------- |
+ * | pad to 4k boundary |
+ * +-----------------------------------------------+
+ * | "used": 2 x uint16; <N> elems; 1 uint16 |
+ * | ----------------------------------------- |
+ * | pad to 4k boundary |
+ * +-----------------------------------------------+
+ *
+ * The number <N> that appears here is always a power of two and is
+ * limited to no more than 32768 (as it must fit in a 16-bit field).
+ * If <N> is sufficiently large, the above will occupy more than
+ * two pages. In any case, all pages must be physically contiguous
+ * within the guest's physical address space.
+ *
+ * The <N> 16-byte "desc" descriptors consist of a 64-bit guest
+ * physical address <addr>, a 32-bit length <len>, a 16-bit
+ * <flags>, and a 16-bit <next> field (all in guest byte order).
+ *
+ * There are three flags that may be set :
+ * NEXT descriptor is chained, so use its "next" field
+ * WRITE descriptor is for host to write into guest RAM
+ * (else host is to read from guest RAM)
+ * INDIRECT descriptor address field is (guest physical)
+ * address of a linear array of descriptors
+ *
+ * Unless INDIRECT is set, <len> is the number of bytes that may
+ * be read/written from guest physical address <addr>. If
+ * INDIRECT is set, WRITE is ignored and <len> provides the length
+ * of the indirect descriptors (and <len> must be a multiple of
+ * 16). Note that NEXT may still be set in the main descriptor
+ * pointing to the indirect, and should be set in each indirect
+ * descriptor that uses the next descriptor (these should generally
+ * be numbered sequentially). However, INDIRECT must not be set
+ * in the indirect descriptors. Upon reaching an indirect descriptor
+ * without a NEXT bit, control returns to the direct descriptors.
+ *
+ * Except inside an indirect, each <next> value must be in the
+ * range [0 .. N) (i.e., the half-open interval). (Inside an
+ * indirect, each <next> must be in the range [0 .. <len>/16).)
+ *
+ * The "avail" data structures reside in the same pages as the
+ * "desc" structures since both together are used by the device to
+ * pass information to the hypervisor's virtual driver. These
+ * begin with a 16-bit <flags> field and 16-bit index <idx>, then
+ * have <N> 16-bit <ring> values, followed by one final 16-bit
+ * field <used_event>. The <N> <ring> entries are simply indices
+ * indices into the descriptor ring (and thus must meet the same
+ * constraints as each <next> value). However, <idx> is counted
+ * up from 0 (initially) and simply wraps around after 65535; it
+ * is taken mod <N> to find the next available entry.
+ *
+ * The "used" ring occupies a separate page or pages, and contains
+ * values written from the virtual driver back to the guest OS.
+ * This begins with a 16-bit <flags> and 16-bit <idx>, then there
+ * are <N> "vring_used" elements, followed by a 16-bit <avail_event>.
+ * The <N> "vring_used" elements consist of a 32-bit <id> and a
+ * 32-bit <len> (vu_tlen below). The <id> is simply the index of
+ * the head of a descriptor chain the guest made available
+ * earlier, and the <len> is the number of bytes actually written,
+ * e.g., in the case of a network driver that provided a large
+ * receive buffer but received only a small amount of data.
+ *
+ * The two event fields, <used_event> and <avail_event>, in the
+ * avail and used rings (respectively -- note the reversal!), are
+ * always provided, but are used only if the virtual device
+ * negotiates the VIRTIO_RING_F_EVENT_IDX feature during feature
+ * negotiation. Similarly, both rings provide a flag --
+ * VRING_AVAIL_F_NO_INTERRUPT and VRING_USED_F_NO_NOTIFY -- in
+ * their <flags> field, indicating that the guest does not need an
+ * interrupt, or that the hypervisor driver does not need a
+ * notify, when descriptors are added to the corresponding ring.
+ * (These are provided only for interrupt optimization and need
+ * not be implemented.)
+ */
+#define VRING_ALIGN 4096
+
+#define VRING_DESC_F_NEXT (1 << 0)
+#define VRING_DESC_F_WRITE (1 << 1)
+#define VRING_DESC_F_INDIRECT (1 << 2)
+
+struct virtio_desc { /* AKA vring_desc */
+ uint64_t vd_addr; /* guest physical address */
+ uint32_t vd_len; /* length of scatter/gather seg */
+ uint16_t vd_flags; /* VRING_F_DESC_* */
+ uint16_t vd_next; /* next desc if F_NEXT */
+} __packed;
+
+struct virtio_used { /* AKA vring_used_elem */
+ uint32_t vu_idx; /* head of used descriptor chain */
+ uint32_t vu_tlen; /* length written-to */
+} __packed;
+
+#define VRING_AVAIL_F_NO_INTERRUPT 1
+
+struct vring_avail {
+ uint16_t va_flags; /* VRING_AVAIL_F_* */
+ uint16_t va_idx; /* counts to 65535, then cycles */
+ uint16_t va_ring[]; /* size N, reported in QNUM value */
+/* uint16_t va_used_event; -- after N ring entries */
+} __packed;
+
+#define VRING_USED_F_NO_NOTIFY 1
+struct vring_used {
+ uint16_t vu_flags; /* VRING_USED_F_* */
+ uint16_t vu_idx; /* counts to 65535, then cycles */
+ struct virtio_used vu_ring[]; /* size N */
+/* uint16_t vu_avail_event; -- after N ring entries */
+} __packed;
+
+/*
+ * The address of any given virtual queue is determined by a single
+ * Page Frame Number register. The guest writes the PFN into the
+ * PCI config space. However, a device that has two or more
+ * virtqueues can have a different PFN, and size, for each queue.
+ * The number of queues is determinable via the PCI config space
+ * VTCFG_R_QSEL register. Writes to QSEL select the queue: 0 means
+ * queue #0, 1 means queue#1, etc. Once a queue is selected, the
+ * remaining PFN and QNUM registers refer to that queue.
+ *
+ * QNUM is a read-only register containing a nonzero power of two
+ * that indicates the (hypervisor's) queue size. Or, if reading it
+ * produces zero, the hypervisor does not have a corresponding
+ * queue. (The number of possible queues depends on the virtual
+ * device. The block device has just one; the network device
+ * provides either two -- 0 = receive, 1 = transmit -- or three,
+ * with 2 = control.)
+ *
+ * PFN is a read/write register giving the physical page address of
+ * the virtqueue in guest memory (the guest must allocate enough space
+ * based on the hypervisor's provided QNUM).
+ *
+ * QNOTIFY is effectively write-only: when the guest writes a queue
+ * number to the register, the hypervisor should scan the specified
+ * virtqueue. (Reading QNOTIFY currently always gets 0).
+ */
+
+/*
+ * PFN register shift amount
+ */
+#define VRING_PFN 12
+
+/*
+ * Virtio device types
+ *
+ * XXX Should really be merged with <dev/virtio/virtio.h> defines
+ */
+#define VIRTIO_TYPE_NET 1
+#define VIRTIO_TYPE_BLOCK 2
+#define VIRTIO_TYPE_CONSOLE 3
+#define VIRTIO_TYPE_ENTROPY 4
+#define VIRTIO_TYPE_BALLOON 5
+#define VIRTIO_TYPE_IOMEMORY 6
+#define VIRTIO_TYPE_RPMSG 7
+#define VIRTIO_TYPE_SCSI 8
+#define VIRTIO_TYPE_9P 9
+
+/* experimental IDs start at 65535 and work down */
+
+/*
+ * PCI vendor/device IDs
+ */
+#define VIRTIO_VENDOR 0x1AF4
+#define VIRTIO_DEV_NET 0x1000
+#define VIRTIO_DEV_BLOCK 0x1001
+#define VIRTIO_DEV_RANDOM 0x1002
+
+/*
+ * PCI config space constants.
+ *
+ * If MSI-X is enabled, the ISR register is generally not used,
+ * and the configuration vector and queue vector appear at offsets
+ * 20 and 22 with the remaining configuration registers at 24.
+ * If MSI-X is not enabled, those two registers disappear and
+ * the remaining configuration registers start at offset 20.
+ */
+#define VTCFG_R_HOSTCAP 0
+#define VTCFG_R_GUESTCAP 4
+#define VTCFG_R_PFN 8
+#define VTCFG_R_QNUM 12
+#define VTCFG_R_QSEL 14
+#define VTCFG_R_QNOTIFY 16
+#define VTCFG_R_STATUS 18
+#define VTCFG_R_ISR 19
+#define VTCFG_R_CFGVEC 20
+#define VTCFG_R_QVEC 22
+#define VTCFG_R_CFG0 20 /* No MSI-X */
+#define VTCFG_R_CFG1 24 /* With MSI-X */
+#define VTCFG_R_MSIX 20
+
+/*
+ * Bits in VTCFG_R_STATUS. Guests need not actually set any of these,
+ * but a guest writing 0 to this register means "please reset".
+ */
+#define VTCFG_STATUS_ACK 0x01 /* guest OS has acknowledged dev */
+#define VTCFG_STATUS_DRIVER 0x02 /* guest OS driver is loaded */
+#define VTCFG_STATUS_DRIVER_OK 0x04 /* guest OS driver ready */
+#define VTCFG_STATUS_FAILED 0x80 /* guest has given up on this dev */
+
+/*
+ * Bits in VTCFG_R_ISR. These apply only if not using MSI-X.
+ *
+ * (We don't [yet?] ever use CONF_CHANGED.)
+ */
+#define VTCFG_ISR_QUEUES 0x01 /* re-scan queues */
+#define VTCFG_ISR_CONF_CHANGED 0x80 /* configuration changed */
+
+#define VIRTIO_MSI_NO_VECTOR 0xFFFF
+
+/*
+ * Feature flags.
+ * Note: bits 0 through 23 are reserved to each device type.
+ */
+#define VIRTIO_F_NOTIFY_ON_EMPTY (1 << 24)
+#define VIRTIO_RING_F_INDIRECT_DESC (1 << 28)
+#define VIRTIO_RING_F_EVENT_IDX (1 << 29)
+
+/* From section 2.3, "Virtqueue Configuration", of the virtio specification */
+static inline size_t
+vring_size(u_int qsz)
+{
+ size_t size;
+
+ /* constant 3 below = va_flags, va_idx, va_used_event */
+ size = sizeof(struct virtio_desc) * qsz + sizeof(uint16_t) * (3 + qsz);
+ size = roundup2(size, VRING_ALIGN);
+
+ /* constant 3 below = vu_flags, vu_idx, vu_avail_event */
+ size += sizeof(uint16_t) * 3 + sizeof(struct virtio_used) * qsz;
+ size = roundup2(size, VRING_ALIGN);
+
+ return (size);
+}
+
+struct vmctx;
+struct pci_devinst;
+struct vqueue_info;
+
+/*
+ * A virtual device, with some number (possibly 0) of virtual
+ * queues and some size (possibly 0) of configuration-space
+ * registers private to the device. The virtio_softc should come
+ * at the front of each "derived class", so that a pointer to the
+ * virtio_softc is also a pointer to the more specific, derived-
+ * from-virtio driver's softc.
+ *
+ * Note: inside each hypervisor virtio driver, changes to these
+ * data structures must be locked against other threads, if any.
+ * Except for PCI config space register read/write, we assume each
+ * driver does the required locking, but we need a pointer to the
+ * lock (if there is one) for PCI config space read/write ops.
+ *
+ * When the guest reads or writes the device's config space, the
+ * generic layer checks for operations on the special registers
+ * described above. If the offset of the register(s) being read
+ * or written is past the CFG area (CFG0 or CFG1), the request is
+ * passed on to the virtual device, after subtracting off the
+ * generic-layer size. (So, drivers can just use the offset as
+ * an offset into "struct config", for instance.)
+ *
+ * (The virtio layer also makes sure that the read or write is to/
+ * from a "good" config offset, hence vc_cfgsize, and on BAR #0.
+ * However, the driver must verify the read or write size and offset
+ * and that no one is writing a readonly register.)
+ *
+ * The BROKED flag ("this thing done gone and broked") is for future
+ * use.
+ */
+#define VIRTIO_USE_MSIX 0x01
+#define VIRTIO_EVENT_IDX 0x02 /* use the event-index values */
+#define VIRTIO_BROKED 0x08 /* ??? */
+
+struct virtio_softc {
+ struct virtio_consts *vs_vc; /* constants (see below) */
+ int vs_flags; /* VIRTIO_* flags from above */
+ pthread_mutex_t *vs_mtx; /* POSIX mutex, if any */
+ struct pci_devinst *vs_pi; /* PCI device instance */
+ uint32_t vs_negotiated_caps; /* negotiated capabilities */
+ struct vqueue_info *vs_queues; /* one per vc_nvq */
+ int vs_curq; /* current queue */
+ uint8_t vs_status; /* value from last status write */
+ uint8_t vs_isr; /* ISR flags, if not MSI-X */
+ uint16_t vs_msix_cfg_idx; /* MSI-X vector for config event */
+};
+
+#define VS_LOCK(vs) \
+do { \
+ if (vs->vs_mtx) \
+ pthread_mutex_lock(vs->vs_mtx); \
+} while (0)
+
+#define VS_UNLOCK(vs) \
+do { \
+ if (vs->vs_mtx) \
+ pthread_mutex_unlock(vs->vs_mtx); \
+} while (0)
+
+struct virtio_consts {
+ const char *vc_name; /* name of driver (for diagnostics) */
+ int vc_nvq; /* number of virtual queues */
+ size_t vc_cfgsize; /* size of dev-specific config regs */
+ void (*vc_reset)(void *); /* called on virtual device reset */
+ void (*vc_qnotify)(void *, struct vqueue_info *);
+ /* called on QNOTIFY if no VQ notify */
+ int (*vc_cfgread)(void *, int, int, uint32_t *);
+ /* called to read config regs */
+ int (*vc_cfgwrite)(void *, int, int, uint32_t);
+ /* called to write config regs */
+ void (*vc_apply_features)(void *, uint64_t);
+ /* called to apply negotiated features */
+ uint64_t vc_hv_caps; /* hypervisor-provided capabilities */
+};
+
+/*
+ * Data structure allocated (statically) per virtual queue.
+ *
+ * Drivers may change vq_qsize after a reset. When the guest OS
+ * requests a device reset, the hypervisor first calls
+ * vs->vs_vc->vc_reset(); then the data structure below is
+ * reinitialized (for each virtqueue: vs->vs_vc->vc_nvq).
+ *
+ * The remaining fields should only be fussed-with by the generic
+ * code.
+ *
+ * Note: the addresses of vq_desc, vq_avail, and vq_used are all
+ * computable from each other, but it's a lot simpler if we just
+ * keep a pointer to each one. The event indices are similarly
+ * (but more easily) computable, and this time we'll compute them:
+ * they're just XX_ring[N].
+ */
+#define VQ_ALLOC 0x01 /* set once we have a pfn */
+#define VQ_BROKED 0x02 /* ??? */
+struct vqueue_info {
+ uint16_t vq_qsize; /* size of this queue (a power of 2) */
+ void (*vq_notify)(void *, struct vqueue_info *);
+ /* called instead of vc_notify, if not NULL */
+
+ struct virtio_softc *vq_vs; /* backpointer to softc */
+ uint16_t vq_num; /* we're the num'th queue in the softc */
+
+ uint16_t vq_flags; /* flags (see above) */
+ uint16_t vq_last_avail; /* a recent value of vq_avail->va_idx */
+ uint16_t vq_save_used; /* saved vq_used->vu_idx; see vq_endchains */
+ uint16_t vq_msix_idx; /* MSI-X index, or VIRTIO_MSI_NO_VECTOR */
+
+ uint32_t vq_pfn; /* PFN of virt queue (not shifted!) */
+
+ volatile struct virtio_desc *vq_desc; /* descriptor array */
+ volatile struct vring_avail *vq_avail; /* the "avail" ring */
+ volatile struct vring_used *vq_used; /* the "used" ring */
+
+};
+/* as noted above, these are sort of backwards, name-wise */
+#define VQ_AVAIL_EVENT_IDX(vq) \
+ (*(volatile uint16_t *)&(vq)->vq_used->vu_ring[(vq)->vq_qsize])
+#define VQ_USED_EVENT_IDX(vq) \
+ ((vq)->vq_avail->va_ring[(vq)->vq_qsize])
+
+/*
+ * Is this ring ready for I/O?
+ */
+static inline int
+vq_ring_ready(struct vqueue_info *vq)
+{
+
+ return (vq->vq_flags & VQ_ALLOC);
+}
+
+/*
+ * Are there "available" descriptors? (This does not count
+ * how many, just returns True if there are some.)
+ */
+static inline int
+vq_has_descs(struct vqueue_info *vq)
+{
+
+ return (vq_ring_ready(vq) && vq->vq_last_avail !=
+ vq->vq_avail->va_idx);
+}
+
+/*
+ * Deliver an interrupt to guest on the given virtual queue
+ * (if possible, or a generic MSI interrupt if not using MSI-X).
+ */
+static inline void
+vq_interrupt(struct virtio_softc *vs, struct vqueue_info *vq)
+{
+
+ if (pci_msix_enabled(vs->vs_pi))
+ pci_generate_msix(vs->vs_pi, vq->vq_msix_idx);
+ else {
+ VS_LOCK(vs);
+ vs->vs_isr |= VTCFG_ISR_QUEUES;
+ pci_generate_msi(vs->vs_pi, 0);
+ pci_lintr_assert(vs->vs_pi);
+ VS_UNLOCK(vs);
+ }
+}
+
+struct iovec;
+void vi_softc_linkup(struct virtio_softc *vs, struct virtio_consts *vc,
+ void *dev_softc, struct pci_devinst *pi,
+ struct vqueue_info *queues);
+int vi_intr_init(struct virtio_softc *vs, int barnum, int use_msix);
+void vi_reset_dev(struct virtio_softc *);
+void vi_set_io_bar(struct virtio_softc *, int);
+
+int vq_getchain(struct vqueue_info *vq, uint16_t *pidx,
+ struct iovec *iov, int n_iov, uint16_t *flags);
+void vq_retchain(struct vqueue_info *vq);
+void vq_relchain(struct vqueue_info *vq, uint16_t idx, uint32_t iolen);
+void vq_endchains(struct vqueue_info *vq, int used_all_avail);
+
+uint64_t vi_pci_read(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
+ int baridx, uint64_t offset, int size);
+void vi_pci_write(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
+ int baridx, uint64_t offset, int size, uint64_t value);
+#endif /* _VIRTIO_H_ */
diff --git a/usr.sbin/bhyve/xmsr.c b/usr.sbin/bhyve/xmsr.c
new file mode 100644
index 0000000..5b7bfbb
--- /dev/null
+++ b/usr.sbin/bhyve/xmsr.c
@@ -0,0 +1,230 @@
+/*-
+ * Copyright (c) 2011 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <sys/types.h>
+
+#include <machine/cpufunc.h>
+#include <machine/vmm.h>
+#include <machine/specialreg.h>
+
+#include <vmmapi.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "xmsr.h"
+
+static int cpu_vendor_intel, cpu_vendor_amd;
+
+int
+emulate_wrmsr(struct vmctx *ctx, int vcpu, uint32_t num, uint64_t val)
+{
+
+ if (cpu_vendor_intel) {
+ switch (num) {
+ case 0xd04: /* Sandy Bridge uncore PMCs */
+ case 0xc24:
+ return (0);
+ case MSR_BIOS_UPDT_TRIG:
+ return (0);
+ case MSR_BIOS_SIGN:
+ return (0);
+ default:
+ break;
+ }
+ } else if (cpu_vendor_amd) {
+ switch (num) {
+ case MSR_HWCR:
+ /*
+ * Ignore writes to hardware configuration MSR.
+ */
+ return (0);
+
+ case MSR_NB_CFG1:
+ case MSR_IC_CFG:
+ return (0); /* Ignore writes */
+
+ case MSR_PERFEVSEL0:
+ case MSR_PERFEVSEL1:
+ case MSR_PERFEVSEL2:
+ case MSR_PERFEVSEL3:
+ /* Ignore writes to the PerfEvtSel MSRs */
+ return (0);
+
+ case MSR_K7_PERFCTR0:
+ case MSR_K7_PERFCTR1:
+ case MSR_K7_PERFCTR2:
+ case MSR_K7_PERFCTR3:
+ /* Ignore writes to the PerfCtr MSRs */
+ return (0);
+
+ case MSR_P_STATE_CONTROL:
+ /* Ignore write to change the P-state */
+ return (0);
+
+ default:
+ break;
+ }
+ }
+ return (-1);
+}
+
+int
+emulate_rdmsr(struct vmctx *ctx, int vcpu, uint32_t num, uint64_t *val)
+{
+ int error = 0;
+
+ if (cpu_vendor_intel) {
+ switch (num) {
+ case MSR_BIOS_SIGN:
+ case MSR_IA32_PLATFORM_ID:
+ case MSR_PKG_ENERGY_STATUS:
+ case MSR_PP0_ENERGY_STATUS:
+ case MSR_PP1_ENERGY_STATUS:
+ case MSR_DRAM_ENERGY_STATUS:
+ *val = 0;
+ break;
+ case MSR_RAPL_POWER_UNIT:
+ /*
+ * Use the default value documented in section
+ * "RAPL Interfaces" in Intel SDM vol3.
+ */
+ *val = 0x000a1003;
+ break;
+ default:
+ error = -1;
+ break;
+ }
+ } else if (cpu_vendor_amd) {
+ switch (num) {
+ case MSR_BIOS_SIGN:
+ *val = 0;
+ break;
+ case MSR_HWCR:
+ /*
+ * Bios and Kernel Developer's Guides for AMD Families
+ * 12H, 14H, 15H and 16H.
+ */
+ *val = 0x01000010; /* Reset value */
+ *val |= 1 << 9; /* MONITOR/MWAIT disable */
+ break;
+
+ case MSR_NB_CFG1:
+ case MSR_IC_CFG:
+ /*
+ * The reset value is processor family dependent so
+ * just return 0.
+ */
+ *val = 0;
+ break;
+
+ case MSR_PERFEVSEL0:
+ case MSR_PERFEVSEL1:
+ case MSR_PERFEVSEL2:
+ case MSR_PERFEVSEL3:
+ /*
+ * PerfEvtSel MSRs are not properly virtualized so just
+ * return zero.
+ */
+ *val = 0;
+ break;
+
+ case MSR_K7_PERFCTR0:
+ case MSR_K7_PERFCTR1:
+ case MSR_K7_PERFCTR2:
+ case MSR_K7_PERFCTR3:
+ /*
+ * PerfCtr MSRs are not properly virtualized so just
+ * return zero.
+ */
+ *val = 0;
+ break;
+
+ case MSR_SMM_ADDR:
+ case MSR_SMM_MASK:
+ /*
+ * Return the reset value defined in the AMD Bios and
+ * Kernel Developer's Guide.
+ */
+ *val = 0;
+ break;
+
+ case MSR_P_STATE_LIMIT:
+ case MSR_P_STATE_CONTROL:
+ case MSR_P_STATE_STATUS:
+ case MSR_P_STATE_CONFIG(0): /* P0 configuration */
+ *val = 0;
+ break;
+
+ /*
+ * OpenBSD guests test bit 0 of this MSR to detect if the
+ * workaround for erratum 721 is already applied.
+ * http://support.amd.com/TechDocs/41322_10h_Rev_Gd.pdf
+ */
+ case 0xC0011029:
+ *val = 1;
+ break;
+
+ default:
+ error = -1;
+ break;
+ }
+ } else {
+ error = -1;
+ }
+ return (error);
+}
+
+int
+init_msr(void)
+{
+ int error;
+ u_int regs[4];
+ char cpu_vendor[13];
+
+ do_cpuid(0, regs);
+ ((u_int *)&cpu_vendor)[0] = regs[1];
+ ((u_int *)&cpu_vendor)[1] = regs[3];
+ ((u_int *)&cpu_vendor)[2] = regs[2];
+ cpu_vendor[12] = '\0';
+
+ error = 0;
+ if (strcmp(cpu_vendor, "AuthenticAMD") == 0) {
+ cpu_vendor_amd = 1;
+ } else if (strcmp(cpu_vendor, "GenuineIntel") == 0) {
+ cpu_vendor_intel = 1;
+ } else {
+ fprintf(stderr, "Unknown cpu vendor \"%s\"\n", cpu_vendor);
+ error = -1;
+ }
+ return (error);
+}
diff --git a/usr.sbin/bhyve/xmsr.h b/usr.sbin/bhyve/xmsr.h
new file mode 100644
index 0000000..bcf65b7
--- /dev/null
+++ b/usr.sbin/bhyve/xmsr.h
@@ -0,0 +1,36 @@
+/*-
+ * Copyright (c) 2011 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 _XMSR_H_
+#define _XMSR_H_
+
+int init_msr(void);
+int emulate_wrmsr(struct vmctx *ctx, int vcpu, uint32_t code, uint64_t val);
+int emulate_rdmsr(struct vmctx *ctx, int vcpu, uint32_t code, uint64_t *val);
+
+#endif
diff --git a/usr.sbin/bhyvectl/Makefile b/usr.sbin/bhyvectl/Makefile
new file mode 100644
index 0000000..4a33dee
--- /dev/null
+++ b/usr.sbin/bhyvectl/Makefile
@@ -0,0 +1,16 @@
+#
+# $FreeBSD$
+#
+
+PROG= bhyvectl
+SRCS= bhyvectl.c
+
+MAN=
+
+LIBADD= vmmapi util
+
+WARNS?= 3
+
+CFLAGS+= -I${.CURDIR}/../../sys/amd64/vmm
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bhyvectl/Makefile.depend b/usr.sbin/bhyvectl/Makefile.depend
new file mode 100644
index 0000000..2d0f324
--- /dev/null
+++ b/usr.sbin/bhyvectl/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+ lib/libvmmapi \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bhyvectl/bhyvectl.c b/usr.sbin/bhyvectl/bhyvectl.c
new file mode 100644
index 0000000..40e5170
--- /dev/null
+++ b/usr.sbin/bhyvectl/bhyvectl.c
@@ -0,0 +1,2219 @@
+/*-
+ * Copyright (c) 2011 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <sys/param.h>
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <sys/errno.h>
+#include <sys/mman.h>
+#include <sys/cpuset.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <libgen.h>
+#include <libutil.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <time.h>
+#include <assert.h>
+#include <libutil.h>
+
+#include <machine/cpufunc.h>
+#include <machine/specialreg.h>
+#include <machine/vmm.h>
+#include <machine/vmm_dev.h>
+#include <vmmapi.h>
+
+#include "amd/vmcb.h"
+#include "intel/vmcs.h"
+
+#define MB (1UL << 20)
+#define GB (1UL << 30)
+
+#define REQ_ARG required_argument
+#define NO_ARG no_argument
+#define OPT_ARG optional_argument
+
+static const char *progname;
+
+static void
+usage(bool cpu_intel)
+{
+
+ (void)fprintf(stderr,
+ "Usage: %s --vm=<vmname>\n"
+ " [--cpu=<vcpu_number>]\n"
+ " [--create]\n"
+ " [--destroy]\n"
+ " [--get-all]\n"
+ " [--get-stats]\n"
+ " [--set-desc-ds]\n"
+ " [--get-desc-ds]\n"
+ " [--set-desc-es]\n"
+ " [--get-desc-es]\n"
+ " [--set-desc-gs]\n"
+ " [--get-desc-gs]\n"
+ " [--set-desc-fs]\n"
+ " [--get-desc-fs]\n"
+ " [--set-desc-cs]\n"
+ " [--get-desc-cs]\n"
+ " [--set-desc-ss]\n"
+ " [--get-desc-ss]\n"
+ " [--set-desc-tr]\n"
+ " [--get-desc-tr]\n"
+ " [--set-desc-ldtr]\n"
+ " [--get-desc-ldtr]\n"
+ " [--set-desc-gdtr]\n"
+ " [--get-desc-gdtr]\n"
+ " [--set-desc-idtr]\n"
+ " [--get-desc-idtr]\n"
+ " [--run]\n"
+ " [--capname=<capname>]\n"
+ " [--getcap]\n"
+ " [--setcap=<0|1>]\n"
+ " [--desc-base=<BASE>]\n"
+ " [--desc-limit=<LIMIT>]\n"
+ " [--desc-access=<ACCESS>]\n"
+ " [--set-cr0=<CR0>]\n"
+ " [--get-cr0]\n"
+ " [--set-cr3=<CR3>]\n"
+ " [--get-cr3]\n"
+ " [--set-cr4=<CR4>]\n"
+ " [--get-cr4]\n"
+ " [--set-dr7=<DR7>]\n"
+ " [--get-dr7]\n"
+ " [--set-rsp=<RSP>]\n"
+ " [--get-rsp]\n"
+ " [--set-rip=<RIP>]\n"
+ " [--get-rip]\n"
+ " [--get-rax]\n"
+ " [--set-rax=<RAX>]\n"
+ " [--get-rbx]\n"
+ " [--get-rcx]\n"
+ " [--get-rdx]\n"
+ " [--get-rsi]\n"
+ " [--get-rdi]\n"
+ " [--get-rbp]\n"
+ " [--get-r8]\n"
+ " [--get-r9]\n"
+ " [--get-r10]\n"
+ " [--get-r11]\n"
+ " [--get-r12]\n"
+ " [--get-r13]\n"
+ " [--get-r14]\n"
+ " [--get-r15]\n"
+ " [--set-rflags=<RFLAGS>]\n"
+ " [--get-rflags]\n"
+ " [--set-cs]\n"
+ " [--get-cs]\n"
+ " [--set-ds]\n"
+ " [--get-ds]\n"
+ " [--set-es]\n"
+ " [--get-es]\n"
+ " [--set-fs]\n"
+ " [--get-fs]\n"
+ " [--set-gs]\n"
+ " [--get-gs]\n"
+ " [--set-ss]\n"
+ " [--get-ss]\n"
+ " [--get-tr]\n"
+ " [--get-ldtr]\n"
+ " [--set-x2apic-state=<state>]\n"
+ " [--get-x2apic-state]\n"
+ " [--unassign-pptdev=<bus/slot/func>]\n"
+ " [--set-mem=<memory in units of MB>]\n"
+ " [--get-lowmem]\n"
+ " [--get-highmem]\n"
+ " [--get-gpa-pmap]\n"
+ " [--assert-lapic-lvt=<pin>]\n"
+ " [--inject-nmi]\n"
+ " [--force-reset]\n"
+ " [--force-poweroff]\n"
+ " [--get-rtc-time]\n"
+ " [--set-rtc-time=<secs>]\n"
+ " [--get-rtc-nvram]\n"
+ " [--set-rtc-nvram=<val>]\n"
+ " [--rtc-nvram-offset=<offset>]\n"
+ " [--get-active-cpus]\n"
+ " [--get-suspended-cpus]\n"
+ " [--get-intinfo]\n"
+ " [--get-eptp]\n"
+ " [--set-exception-bitmap]\n"
+ " [--get-exception-bitmap]\n"
+ " [--get-tsc-offset]\n"
+ " [--get-guest-pat]\n"
+ " [--get-io-bitmap-address]\n"
+ " [--get-msr-bitmap]\n"
+ " [--get-msr-bitmap-address]\n"
+ " [--get-guest-sysenter]\n"
+ " [--get-exit-reason]\n",
+ progname);
+
+ if (cpu_intel) {
+ (void)fprintf(stderr,
+ " [--get-vmcs-pinbased-ctls]\n"
+ " [--get-vmcs-procbased-ctls]\n"
+ " [--get-vmcs-procbased-ctls2]\n"
+ " [--get-vmcs-entry-interruption-info]\n"
+ " [--set-vmcs-entry-interruption-info=<info>]\n"
+ " [--get-vmcs-guest-physical-address\n"
+ " [--get-vmcs-guest-linear-address\n"
+ " [--get-vmcs-host-pat]\n"
+ " [--get-vmcs-host-cr0]\n"
+ " [--get-vmcs-host-cr3]\n"
+ " [--get-vmcs-host-cr4]\n"
+ " [--get-vmcs-host-rip]\n"
+ " [--get-vmcs-host-rsp]\n"
+ " [--get-vmcs-cr0-mask]\n"
+ " [--get-vmcs-cr0-shadow]\n"
+ " [--get-vmcs-cr4-mask]\n"
+ " [--get-vmcs-cr4-shadow]\n"
+ " [--get-vmcs-cr3-targets]\n"
+ " [--get-vmcs-apic-access-address]\n"
+ " [--get-vmcs-virtual-apic-address]\n"
+ " [--get-vmcs-tpr-threshold]\n"
+ " [--get-vmcs-vpid]\n"
+ " [--get-vmcs-instruction-error]\n"
+ " [--get-vmcs-exit-ctls]\n"
+ " [--get-vmcs-entry-ctls]\n"
+ " [--get-vmcs-link]\n"
+ " [--get-vmcs-exit-qualification]\n"
+ " [--get-vmcs-exit-interruption-info]\n"
+ " [--get-vmcs-exit-interruption-error]\n"
+ " [--get-vmcs-interruptibility]\n"
+ );
+ } else {
+ (void)fprintf(stderr,
+ " [--get-vmcb-intercepts]\n"
+ " [--get-vmcb-asid]\n"
+ " [--get-vmcb-exit-details]\n"
+ " [--get-vmcb-tlb-ctrl]\n"
+ " [--get-vmcb-virq]\n"
+ " [--get-avic-apic-bar]\n"
+ " [--get-avic-backing-page]\n"
+ " [--get-avic-table]\n"
+ );
+ }
+ exit(1);
+}
+
+static int get_rtc_time, set_rtc_time;
+static int get_rtc_nvram, set_rtc_nvram;
+static int rtc_nvram_offset;
+static uint8_t rtc_nvram_value;
+static time_t rtc_secs;
+
+static int get_stats, getcap, setcap, capval, get_gpa_pmap;
+static int inject_nmi, assert_lapic_lvt;
+static int force_reset, force_poweroff;
+static const char *capname;
+static int create, destroy, get_memmap, get_memseg;
+static int get_intinfo;
+static int get_active_cpus, get_suspended_cpus;
+static uint64_t memsize;
+static int set_cr0, get_cr0, set_cr3, get_cr3, set_cr4, get_cr4;
+static int set_efer, get_efer;
+static int set_dr7, get_dr7;
+static int set_rsp, get_rsp, set_rip, get_rip, set_rflags, get_rflags;
+static int set_rax, get_rax;
+static int get_rbx, get_rcx, get_rdx, get_rsi, get_rdi, get_rbp;
+static int get_r8, get_r9, get_r10, get_r11, get_r12, get_r13, get_r14, get_r15;
+static int set_desc_ds, get_desc_ds;
+static int set_desc_es, get_desc_es;
+static int set_desc_fs, get_desc_fs;
+static int set_desc_gs, get_desc_gs;
+static int set_desc_cs, get_desc_cs;
+static int set_desc_ss, get_desc_ss;
+static int set_desc_gdtr, get_desc_gdtr;
+static int set_desc_idtr, get_desc_idtr;
+static int set_desc_tr, get_desc_tr;
+static int set_desc_ldtr, get_desc_ldtr;
+static int set_cs, set_ds, set_es, set_fs, set_gs, set_ss, set_tr, set_ldtr;
+static int get_cs, get_ds, get_es, get_fs, get_gs, get_ss, get_tr, get_ldtr;
+static int set_x2apic_state, get_x2apic_state;
+enum x2apic_state x2apic_state;
+static int unassign_pptdev, bus, slot, func;
+static int run;
+
+/*
+ * VMCB specific.
+ */
+static int get_vmcb_intercept, get_vmcb_exit_details, get_vmcb_tlb_ctrl;
+static int get_vmcb_virq, get_avic_table;
+
+/*
+ * VMCS-specific fields
+ */
+static int get_pinbased_ctls, get_procbased_ctls, get_procbased_ctls2;
+static int get_eptp, get_io_bitmap, get_tsc_offset;
+static int get_vmcs_entry_interruption_info, set_vmcs_entry_interruption_info;
+static int get_vmcs_interruptibility;
+uint32_t vmcs_entry_interruption_info;
+static int get_vmcs_gpa, get_vmcs_gla;
+static int get_exception_bitmap, set_exception_bitmap, exception_bitmap;
+static int get_cr0_mask, get_cr0_shadow;
+static int get_cr4_mask, get_cr4_shadow;
+static int get_cr3_targets;
+static int get_apic_access_addr, get_virtual_apic_addr, get_tpr_threshold;
+static int get_msr_bitmap, get_msr_bitmap_address;
+static int get_vpid_asid;
+static int get_inst_err, get_exit_ctls, get_entry_ctls;
+static int get_host_cr0, get_host_cr3, get_host_cr4;
+static int get_host_rip, get_host_rsp;
+static int get_guest_pat, get_host_pat;
+static int get_guest_sysenter, get_vmcs_link;
+static int get_exit_reason, get_vmcs_exit_qualification;
+static int get_vmcs_exit_interruption_info, get_vmcs_exit_interruption_error;
+static int get_vmcs_exit_inst_length;
+
+static uint64_t desc_base;
+static uint32_t desc_limit, desc_access;
+
+static int get_all;
+
+static void
+dump_vm_run_exitcode(struct vm_exit *vmexit, int vcpu)
+{
+ printf("vm exit[%d]\n", vcpu);
+ printf("\trip\t\t0x%016lx\n", vmexit->rip);
+ printf("\tinst_length\t%d\n", vmexit->inst_length);
+ switch (vmexit->exitcode) {
+ case VM_EXITCODE_INOUT:
+ printf("\treason\t\tINOUT\n");
+ printf("\tdirection\t%s\n", vmexit->u.inout.in ? "IN" : "OUT");
+ printf("\tbytes\t\t%d\n", vmexit->u.inout.bytes);
+ printf("\tflags\t\t%s%s\n",
+ vmexit->u.inout.string ? "STRING " : "",
+ vmexit->u.inout.rep ? "REP " : "");
+ printf("\tport\t\t0x%04x\n", vmexit->u.inout.port);
+ printf("\teax\t\t0x%08x\n", vmexit->u.inout.eax);
+ break;
+ case VM_EXITCODE_VMX:
+ printf("\treason\t\tVMX\n");
+ printf("\tstatus\t\t%d\n", vmexit->u.vmx.status);
+ printf("\texit_reason\t0x%08x (%u)\n",
+ vmexit->u.vmx.exit_reason, vmexit->u.vmx.exit_reason);
+ printf("\tqualification\t0x%016lx\n",
+ vmexit->u.vmx.exit_qualification);
+ printf("\tinst_type\t\t%d\n", vmexit->u.vmx.inst_type);
+ printf("\tinst_error\t\t%d\n", vmexit->u.vmx.inst_error);
+ break;
+ case VM_EXITCODE_SVM:
+ printf("\treason\t\tSVM\n");
+ printf("\texit_reason\t\t%#lx\n", vmexit->u.svm.exitcode);
+ printf("\texitinfo1\t\t%#lx\n", vmexit->u.svm.exitinfo1);
+ printf("\texitinfo2\t\t%#lx\n", vmexit->u.svm.exitinfo2);
+ break;
+ default:
+ printf("*** unknown vm run exitcode %d\n", vmexit->exitcode);
+ break;
+ }
+}
+
+/* AMD 6th generation and Intel compatible MSRs */
+#define MSR_AMD6TH_START 0xC0000000
+#define MSR_AMD6TH_END 0xC0001FFF
+/* AMD 7th and 8th generation compatible MSRs */
+#define MSR_AMD7TH_START 0xC0010000
+#define MSR_AMD7TH_END 0xC0011FFF
+
+static const char *
+msr_name(uint32_t msr)
+{
+ static char buf[32];
+
+ switch(msr) {
+ case MSR_TSC:
+ return ("MSR_TSC");
+ case MSR_EFER:
+ return ("MSR_EFER");
+ case MSR_STAR:
+ return ("MSR_STAR");
+ case MSR_LSTAR:
+ return ("MSR_LSTAR");
+ case MSR_CSTAR:
+ return ("MSR_CSTAR");
+ case MSR_SF_MASK:
+ return ("MSR_SF_MASK");
+ case MSR_FSBASE:
+ return ("MSR_FSBASE");
+ case MSR_GSBASE:
+ return ("MSR_GSBASE");
+ case MSR_KGSBASE:
+ return ("MSR_KGSBASE");
+ case MSR_SYSENTER_CS_MSR:
+ return ("MSR_SYSENTER_CS_MSR");
+ case MSR_SYSENTER_ESP_MSR:
+ return ("MSR_SYSENTER_ESP_MSR");
+ case MSR_SYSENTER_EIP_MSR:
+ return ("MSR_SYSENTER_EIP_MSR");
+ case MSR_PAT:
+ return ("MSR_PAT");
+ }
+ snprintf(buf, sizeof(buf), "MSR %#08x", msr);
+
+ return (buf);
+}
+
+static inline void
+print_msr_pm(uint64_t msr, int vcpu, int readable, int writeable)
+{
+
+ if (readable || writeable) {
+ printf("%-20s[%d]\t\t%c%c\n", msr_name(msr), vcpu,
+ readable ? 'R' : '-', writeable ? 'W' : '-');
+ }
+}
+
+/*
+ * Reference APM vol2, section 15.11 MSR Intercepts.
+ */
+static void
+dump_amd_msr_pm(const char *bitmap, int vcpu)
+{
+ int byte, bit, readable, writeable;
+ uint32_t msr;
+
+ for (msr = 0; msr < 0x2000; msr++) {
+ byte = msr / 4;
+ bit = (msr % 4) * 2;
+
+ /* Look at MSRs in the range 0x00000000 to 0x00001FFF */
+ readable = (bitmap[byte] & (1 << bit)) ? 0 : 1;
+ writeable = (bitmap[byte] & (2 << bit)) ? 0 : 1;
+ print_msr_pm(msr, vcpu, readable, writeable);
+
+ /* Look at MSRs in the range 0xC0000000 to 0xC0001FFF */
+ byte += 2048;
+ readable = (bitmap[byte] & (1 << bit)) ? 0 : 1;
+ writeable = (bitmap[byte] & (2 << bit)) ? 0 : 1;
+ print_msr_pm(msr + MSR_AMD6TH_START, vcpu, readable,
+ writeable);
+
+ /* MSR 0xC0010000 to 0xC0011FF is only for AMD */
+ byte += 4096;
+ readable = (bitmap[byte] & (1 << bit)) ? 0 : 1;
+ writeable = (bitmap[byte] & (2 << bit)) ? 0 : 1;
+ print_msr_pm(msr + MSR_AMD7TH_START, vcpu, readable,
+ writeable);
+ }
+}
+
+/*
+ * Reference Intel SDM Vol3 Section 24.6.9 MSR-Bitmap Address
+ */
+static void
+dump_intel_msr_pm(const char *bitmap, int vcpu)
+{
+ int byte, bit, readable, writeable;
+ uint32_t msr;
+
+ for (msr = 0; msr < 0x2000; msr++) {
+ byte = msr / 8;
+ bit = msr & 0x7;
+
+ /* Look at MSRs in the range 0x00000000 to 0x00001FFF */
+ readable = (bitmap[byte] & (1 << bit)) ? 0 : 1;
+ writeable = (bitmap[2048 + byte] & (1 << bit)) ? 0 : 1;
+ print_msr_pm(msr, vcpu, readable, writeable);
+
+ /* Look at MSRs in the range 0xC0000000 to 0xC0001FFF */
+ byte += 1024;
+ readable = (bitmap[byte] & (1 << bit)) ? 0 : 1;
+ writeable = (bitmap[2048 + byte] & (1 << bit)) ? 0 : 1;
+ print_msr_pm(msr + MSR_AMD6TH_START, vcpu, readable,
+ writeable);
+ }
+}
+
+static int
+dump_msr_bitmap(int vcpu, uint64_t addr, bool cpu_intel)
+{
+ int error, fd, map_size;
+ const char *bitmap;
+
+ error = -1;
+ bitmap = MAP_FAILED;
+
+ fd = open("/dev/mem", O_RDONLY, 0);
+ if (fd < 0) {
+ perror("Couldn't open /dev/mem");
+ goto done;
+ }
+
+ if (cpu_intel)
+ map_size = PAGE_SIZE;
+ else
+ map_size = 2 * PAGE_SIZE;
+
+ bitmap = mmap(NULL, map_size, PROT_READ, MAP_SHARED, fd, addr);
+ if (bitmap == MAP_FAILED) {
+ perror("mmap failed");
+ goto done;
+ }
+
+ if (cpu_intel)
+ dump_intel_msr_pm(bitmap, vcpu);
+ else
+ dump_amd_msr_pm(bitmap, vcpu);
+
+ error = 0;
+done:
+ if (bitmap != MAP_FAILED)
+ munmap((void *)bitmap, map_size);
+ if (fd >= 0)
+ close(fd);
+
+ return (error);
+}
+
+static int
+vm_get_vmcs_field(struct vmctx *ctx, int vcpu, int field, uint64_t *ret_val)
+{
+
+ return (vm_get_register(ctx, vcpu, VMCS_IDENT(field), ret_val));
+}
+
+static int
+vm_set_vmcs_field(struct vmctx *ctx, int vcpu, int field, uint64_t val)
+{
+
+ return (vm_set_register(ctx, vcpu, VMCS_IDENT(field), val));
+}
+
+static int
+vm_get_vmcb_field(struct vmctx *ctx, int vcpu, int off, int bytes,
+ uint64_t *ret_val)
+{
+
+ return (vm_get_register(ctx, vcpu, VMCB_ACCESS(off, bytes), ret_val));
+}
+
+static int
+vm_set_vmcb_field(struct vmctx *ctx, int vcpu, int off, int bytes,
+ uint64_t val)
+{
+
+ return (vm_set_register(ctx, vcpu, VMCB_ACCESS(off, bytes), val));
+}
+
+enum {
+ VMNAME = 1000, /* avoid collision with return values from getopt */
+ VCPU,
+ SET_MEM,
+ SET_EFER,
+ SET_CR0,
+ SET_CR3,
+ SET_CR4,
+ SET_DR7,
+ SET_RSP,
+ SET_RIP,
+ SET_RAX,
+ SET_RFLAGS,
+ DESC_BASE,
+ DESC_LIMIT,
+ DESC_ACCESS,
+ SET_CS,
+ SET_DS,
+ SET_ES,
+ SET_FS,
+ SET_GS,
+ SET_SS,
+ SET_TR,
+ SET_LDTR,
+ SET_X2APIC_STATE,
+ SET_EXCEPTION_BITMAP,
+ SET_VMCS_ENTRY_INTERRUPTION_INFO,
+ SET_CAP,
+ CAPNAME,
+ UNASSIGN_PPTDEV,
+ GET_GPA_PMAP,
+ ASSERT_LAPIC_LVT,
+ SET_RTC_TIME,
+ SET_RTC_NVRAM,
+ RTC_NVRAM_OFFSET,
+};
+
+static void
+print_cpus(const char *banner, const cpuset_t *cpus)
+{
+ int i, first;
+
+ first = 1;
+ printf("%s:\t", banner);
+ if (!CPU_EMPTY(cpus)) {
+ for (i = 0; i < CPU_SETSIZE; i++) {
+ if (CPU_ISSET(i, cpus)) {
+ printf("%s%d", first ? " " : ", ", i);
+ first = 0;
+ }
+ }
+ } else
+ printf(" (none)");
+ printf("\n");
+}
+
+static void
+print_intinfo(const char *banner, uint64_t info)
+{
+ int type;
+
+ printf("%s:\t", banner);
+ if (info & VM_INTINFO_VALID) {
+ type = info & VM_INTINFO_TYPE;
+ switch (type) {
+ case VM_INTINFO_HWINTR:
+ printf("extint");
+ break;
+ case VM_INTINFO_NMI:
+ printf("nmi");
+ break;
+ case VM_INTINFO_SWINTR:
+ printf("swint");
+ break;
+ default:
+ printf("exception");
+ break;
+ }
+ printf(" vector %d", (int)VM_INTINFO_VECTOR(info));
+ if (info & VM_INTINFO_DEL_ERRCODE)
+ printf(" errcode %#x", (u_int)(info >> 32));
+ } else {
+ printf("n/a");
+ }
+ printf("\n");
+}
+
+static bool
+cpu_vendor_intel(void)
+{
+ u_int regs[4];
+ char cpu_vendor[13];
+
+ do_cpuid(0, regs);
+ ((u_int *)&cpu_vendor)[0] = regs[1];
+ ((u_int *)&cpu_vendor)[1] = regs[3];
+ ((u_int *)&cpu_vendor)[2] = regs[2];
+ cpu_vendor[12] = '\0';
+
+ if (strcmp(cpu_vendor, "AuthenticAMD") == 0) {
+ return (false);
+ } else if (strcmp(cpu_vendor, "GenuineIntel") == 0) {
+ return (true);
+ } else {
+ fprintf(stderr, "Unknown cpu vendor \"%s\"\n", cpu_vendor);
+ exit(1);
+ }
+}
+
+static int
+get_all_registers(struct vmctx *ctx, int vcpu)
+{
+ uint64_t cr0, cr3, cr4, dr7, rsp, rip, rflags, efer;
+ uint64_t rax, rbx, rcx, rdx, rsi, rdi, rbp;
+ uint64_t r8, r9, r10, r11, r12, r13, r14, r15;
+ int error = 0;
+
+ if (!error && (get_efer || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_EFER, &efer);
+ if (error == 0)
+ printf("efer[%d]\t\t0x%016lx\n", vcpu, efer);
+ }
+
+ if (!error && (get_cr0 || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_CR0, &cr0);
+ if (error == 0)
+ printf("cr0[%d]\t\t0x%016lx\n", vcpu, cr0);
+ }
+
+ if (!error && (get_cr3 || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_CR3, &cr3);
+ if (error == 0)
+ printf("cr3[%d]\t\t0x%016lx\n", vcpu, cr3);
+ }
+
+ if (!error && (get_cr4 || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_CR4, &cr4);
+ if (error == 0)
+ printf("cr4[%d]\t\t0x%016lx\n", vcpu, cr4);
+ }
+
+ if (!error && (get_dr7 || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_DR7, &dr7);
+ if (error == 0)
+ printf("dr7[%d]\t\t0x%016lx\n", vcpu, dr7);
+ }
+
+ if (!error && (get_rsp || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_RSP, &rsp);
+ if (error == 0)
+ printf("rsp[%d]\t\t0x%016lx\n", vcpu, rsp);
+ }
+
+ if (!error && (get_rip || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_RIP, &rip);
+ if (error == 0)
+ printf("rip[%d]\t\t0x%016lx\n", vcpu, rip);
+ }
+
+ if (!error && (get_rax || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_RAX, &rax);
+ if (error == 0)
+ printf("rax[%d]\t\t0x%016lx\n", vcpu, rax);
+ }
+
+ if (!error && (get_rbx || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_RBX, &rbx);
+ if (error == 0)
+ printf("rbx[%d]\t\t0x%016lx\n", vcpu, rbx);
+ }
+
+ if (!error && (get_rcx || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_RCX, &rcx);
+ if (error == 0)
+ printf("rcx[%d]\t\t0x%016lx\n", vcpu, rcx);
+ }
+
+ if (!error && (get_rdx || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_RDX, &rdx);
+ if (error == 0)
+ printf("rdx[%d]\t\t0x%016lx\n", vcpu, rdx);
+ }
+
+ if (!error && (get_rsi || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_RSI, &rsi);
+ if (error == 0)
+ printf("rsi[%d]\t\t0x%016lx\n", vcpu, rsi);
+ }
+
+ if (!error && (get_rdi || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_RDI, &rdi);
+ if (error == 0)
+ printf("rdi[%d]\t\t0x%016lx\n", vcpu, rdi);
+ }
+
+ if (!error && (get_rbp || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_RBP, &rbp);
+ if (error == 0)
+ printf("rbp[%d]\t\t0x%016lx\n", vcpu, rbp);
+ }
+
+ if (!error && (get_r8 || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_R8, &r8);
+ if (error == 0)
+ printf("r8[%d]\t\t0x%016lx\n", vcpu, r8);
+ }
+
+ if (!error && (get_r9 || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_R9, &r9);
+ if (error == 0)
+ printf("r9[%d]\t\t0x%016lx\n", vcpu, r9);
+ }
+
+ if (!error && (get_r10 || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_R10, &r10);
+ if (error == 0)
+ printf("r10[%d]\t\t0x%016lx\n", vcpu, r10);
+ }
+
+ if (!error && (get_r11 || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_R11, &r11);
+ if (error == 0)
+ printf("r11[%d]\t\t0x%016lx\n", vcpu, r11);
+ }
+
+ if (!error && (get_r12 || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_R12, &r12);
+ if (error == 0)
+ printf("r12[%d]\t\t0x%016lx\n", vcpu, r12);
+ }
+
+ if (!error && (get_r13 || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_R13, &r13);
+ if (error == 0)
+ printf("r13[%d]\t\t0x%016lx\n", vcpu, r13);
+ }
+
+ if (!error && (get_r14 || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_R14, &r14);
+ if (error == 0)
+ printf("r14[%d]\t\t0x%016lx\n", vcpu, r14);
+ }
+
+ if (!error && (get_r15 || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_R15, &r15);
+ if (error == 0)
+ printf("r15[%d]\t\t0x%016lx\n", vcpu, r15);
+ }
+
+ if (!error && (get_rflags || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_RFLAGS,
+ &rflags);
+ if (error == 0)
+ printf("rflags[%d]\t0x%016lx\n", vcpu, rflags);
+ }
+
+ return (error);
+}
+
+static int
+get_all_segments(struct vmctx *ctx, int vcpu)
+{
+ uint64_t cs, ds, es, fs, gs, ss, tr, ldtr;
+ int error = 0;
+
+ if (!error && (get_desc_ds || get_all)) {
+ error = vm_get_desc(ctx, vcpu, VM_REG_GUEST_DS,
+ &desc_base, &desc_limit, &desc_access);
+ if (error == 0) {
+ printf("ds desc[%d]\t0x%016lx/0x%08x/0x%08x\n",
+ vcpu, desc_base, desc_limit, desc_access);
+ }
+ }
+
+ if (!error && (get_desc_es || get_all)) {
+ error = vm_get_desc(ctx, vcpu, VM_REG_GUEST_ES,
+ &desc_base, &desc_limit, &desc_access);
+ if (error == 0) {
+ printf("es desc[%d]\t0x%016lx/0x%08x/0x%08x\n",
+ vcpu, desc_base, desc_limit, desc_access);
+ }
+ }
+
+ if (!error && (get_desc_fs || get_all)) {
+ error = vm_get_desc(ctx, vcpu, VM_REG_GUEST_FS,
+ &desc_base, &desc_limit, &desc_access);
+ if (error == 0) {
+ printf("fs desc[%d]\t0x%016lx/0x%08x/0x%08x\n",
+ vcpu, desc_base, desc_limit, desc_access);
+ }
+ }
+
+ if (!error && (get_desc_gs || get_all)) {
+ error = vm_get_desc(ctx, vcpu, VM_REG_GUEST_GS,
+ &desc_base, &desc_limit, &desc_access);
+ if (error == 0) {
+ printf("gs desc[%d]\t0x%016lx/0x%08x/0x%08x\n",
+ vcpu, desc_base, desc_limit, desc_access);
+ }
+ }
+
+ if (!error && (get_desc_ss || get_all)) {
+ error = vm_get_desc(ctx, vcpu, VM_REG_GUEST_SS,
+ &desc_base, &desc_limit, &desc_access);
+ if (error == 0) {
+ printf("ss desc[%d]\t0x%016lx/0x%08x/0x%08x\n",
+ vcpu, desc_base, desc_limit, desc_access);
+ }
+ }
+
+ if (!error && (get_desc_cs || get_all)) {
+ error = vm_get_desc(ctx, vcpu, VM_REG_GUEST_CS,
+ &desc_base, &desc_limit, &desc_access);
+ if (error == 0) {
+ printf("cs desc[%d]\t0x%016lx/0x%08x/0x%08x\n",
+ vcpu, desc_base, desc_limit, desc_access);
+ }
+ }
+
+ if (!error && (get_desc_tr || get_all)) {
+ error = vm_get_desc(ctx, vcpu, VM_REG_GUEST_TR,
+ &desc_base, &desc_limit, &desc_access);
+ if (error == 0) {
+ printf("tr desc[%d]\t0x%016lx/0x%08x/0x%08x\n",
+ vcpu, desc_base, desc_limit, desc_access);
+ }
+ }
+
+ if (!error && (get_desc_ldtr || get_all)) {
+ error = vm_get_desc(ctx, vcpu, VM_REG_GUEST_LDTR,
+ &desc_base, &desc_limit, &desc_access);
+ if (error == 0) {
+ printf("ldtr desc[%d]\t0x%016lx/0x%08x/0x%08x\n",
+ vcpu, desc_base, desc_limit, desc_access);
+ }
+ }
+
+ if (!error && (get_desc_gdtr || get_all)) {
+ error = vm_get_desc(ctx, vcpu, VM_REG_GUEST_GDTR,
+ &desc_base, &desc_limit, &desc_access);
+ if (error == 0) {
+ printf("gdtr[%d]\t\t0x%016lx/0x%08x\n",
+ vcpu, desc_base, desc_limit);
+ }
+ }
+
+ if (!error && (get_desc_idtr || get_all)) {
+ error = vm_get_desc(ctx, vcpu, VM_REG_GUEST_IDTR,
+ &desc_base, &desc_limit, &desc_access);
+ if (error == 0) {
+ printf("idtr[%d]\t\t0x%016lx/0x%08x\n",
+ vcpu, desc_base, desc_limit);
+ }
+ }
+
+ if (!error && (get_cs || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_CS, &cs);
+ if (error == 0)
+ printf("cs[%d]\t\t0x%04lx\n", vcpu, cs);
+ }
+
+ if (!error && (get_ds || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_DS, &ds);
+ if (error == 0)
+ printf("ds[%d]\t\t0x%04lx\n", vcpu, ds);
+ }
+
+ if (!error && (get_es || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_ES, &es);
+ if (error == 0)
+ printf("es[%d]\t\t0x%04lx\n", vcpu, es);
+ }
+
+ if (!error && (get_fs || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_FS, &fs);
+ if (error == 0)
+ printf("fs[%d]\t\t0x%04lx\n", vcpu, fs);
+ }
+
+ if (!error && (get_gs || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_GS, &gs);
+ if (error == 0)
+ printf("gs[%d]\t\t0x%04lx\n", vcpu, gs);
+ }
+
+ if (!error && (get_ss || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_SS, &ss);
+ if (error == 0)
+ printf("ss[%d]\t\t0x%04lx\n", vcpu, ss);
+ }
+
+ if (!error && (get_tr || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_TR, &tr);
+ if (error == 0)
+ printf("tr[%d]\t\t0x%04lx\n", vcpu, tr);
+ }
+
+ if (!error && (get_ldtr || get_all)) {
+ error = vm_get_register(ctx, vcpu, VM_REG_GUEST_LDTR, &ldtr);
+ if (error == 0)
+ printf("ldtr[%d]\t\t0x%04lx\n", vcpu, ldtr);
+ }
+
+ return (error);
+}
+
+static int
+get_misc_vmcs(struct vmctx *ctx, int vcpu)
+{
+ uint64_t ctl, cr0, cr3, cr4, rsp, rip, pat, addr, u64;
+ int error = 0;
+
+ if (!error && (get_cr0_mask || get_all)) {
+ uint64_t cr0mask;
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_CR0_MASK, &cr0mask);
+ if (error == 0)
+ printf("cr0_mask[%d]\t\t0x%016lx\n", vcpu, cr0mask);
+ }
+
+ if (!error && (get_cr0_shadow || get_all)) {
+ uint64_t cr0shadow;
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_CR0_SHADOW,
+ &cr0shadow);
+ if (error == 0)
+ printf("cr0_shadow[%d]\t\t0x%016lx\n", vcpu, cr0shadow);
+ }
+
+ if (!error && (get_cr4_mask || get_all)) {
+ uint64_t cr4mask;
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_CR4_MASK, &cr4mask);
+ if (error == 0)
+ printf("cr4_mask[%d]\t\t0x%016lx\n", vcpu, cr4mask);
+ }
+
+ if (!error && (get_cr4_shadow || get_all)) {
+ uint64_t cr4shadow;
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_CR4_SHADOW,
+ &cr4shadow);
+ if (error == 0)
+ printf("cr4_shadow[%d]\t\t0x%016lx\n", vcpu, cr4shadow);
+ }
+
+ if (!error && (get_cr3_targets || get_all)) {
+ uint64_t target_count, target_addr;
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_CR3_TARGET_COUNT,
+ &target_count);
+ if (error == 0) {
+ printf("cr3_target_count[%d]\t0x%016lx\n",
+ vcpu, target_count);
+ }
+
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_CR3_TARGET0,
+ &target_addr);
+ if (error == 0) {
+ printf("cr3_target0[%d]\t\t0x%016lx\n",
+ vcpu, target_addr);
+ }
+
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_CR3_TARGET1,
+ &target_addr);
+ if (error == 0) {
+ printf("cr3_target1[%d]\t\t0x%016lx\n",
+ vcpu, target_addr);
+ }
+
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_CR3_TARGET2,
+ &target_addr);
+ if (error == 0) {
+ printf("cr3_target2[%d]\t\t0x%016lx\n",
+ vcpu, target_addr);
+ }
+
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_CR3_TARGET3,
+ &target_addr);
+ if (error == 0) {
+ printf("cr3_target3[%d]\t\t0x%016lx\n",
+ vcpu, target_addr);
+ }
+ }
+
+ if (!error && (get_pinbased_ctls || get_all)) {
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_PIN_BASED_CTLS, &ctl);
+ if (error == 0)
+ printf("pinbased_ctls[%d]\t0x%016lx\n", vcpu, ctl);
+ }
+
+ if (!error && (get_procbased_ctls || get_all)) {
+ error = vm_get_vmcs_field(ctx, vcpu,
+ VMCS_PRI_PROC_BASED_CTLS, &ctl);
+ if (error == 0)
+ printf("procbased_ctls[%d]\t0x%016lx\n", vcpu, ctl);
+ }
+
+ if (!error && (get_procbased_ctls2 || get_all)) {
+ error = vm_get_vmcs_field(ctx, vcpu,
+ VMCS_SEC_PROC_BASED_CTLS, &ctl);
+ if (error == 0)
+ printf("procbased_ctls2[%d]\t0x%016lx\n", vcpu, ctl);
+ }
+
+ if (!error && (get_vmcs_gla || get_all)) {
+ error = vm_get_vmcs_field(ctx, vcpu,
+ VMCS_GUEST_LINEAR_ADDRESS, &u64);
+ if (error == 0)
+ printf("gla[%d]\t\t0x%016lx\n", vcpu, u64);
+ }
+
+ if (!error && (get_vmcs_gpa || get_all)) {
+ error = vm_get_vmcs_field(ctx, vcpu,
+ VMCS_GUEST_PHYSICAL_ADDRESS, &u64);
+ if (error == 0)
+ printf("gpa[%d]\t\t0x%016lx\n", vcpu, u64);
+ }
+
+ if (!error && (get_vmcs_entry_interruption_info ||
+ get_all)) {
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_ENTRY_INTR_INFO,&u64);
+ if (error == 0) {
+ printf("entry_interruption_info[%d]\t0x%016lx\n",
+ vcpu, u64);
+ }
+ }
+
+ if (!error && (get_tpr_threshold || get_all)) {
+ uint64_t threshold;
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_TPR_THRESHOLD,
+ &threshold);
+ if (error == 0)
+ printf("tpr_threshold[%d]\t0x%016lx\n", vcpu, threshold);
+ }
+
+ if (!error && (get_inst_err || get_all)) {
+ uint64_t insterr;
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_INSTRUCTION_ERROR,
+ &insterr);
+ if (error == 0) {
+ printf("instruction_error[%d]\t0x%016lx\n",
+ vcpu, insterr);
+ }
+ }
+
+ if (!error && (get_exit_ctls || get_all)) {
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_EXIT_CTLS, &ctl);
+ if (error == 0)
+ printf("exit_ctls[%d]\t\t0x%016lx\n", vcpu, ctl);
+ }
+
+ if (!error && (get_entry_ctls || get_all)) {
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_ENTRY_CTLS, &ctl);
+ if (error == 0)
+ printf("entry_ctls[%d]\t\t0x%016lx\n", vcpu, ctl);
+ }
+
+ if (!error && (get_host_pat || get_all)) {
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_HOST_IA32_PAT, &pat);
+ if (error == 0)
+ printf("host_pat[%d]\t\t0x%016lx\n", vcpu, pat);
+ }
+
+ if (!error && (get_host_cr0 || get_all)) {
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_HOST_CR0, &cr0);
+ if (error == 0)
+ printf("host_cr0[%d]\t\t0x%016lx\n", vcpu, cr0);
+ }
+
+ if (!error && (get_host_cr3 || get_all)) {
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_HOST_CR3, &cr3);
+ if (error == 0)
+ printf("host_cr3[%d]\t\t0x%016lx\n", vcpu, cr3);
+ }
+
+ if (!error && (get_host_cr4 || get_all)) {
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_HOST_CR4, &cr4);
+ if (error == 0)
+ printf("host_cr4[%d]\t\t0x%016lx\n", vcpu, cr4);
+ }
+
+ if (!error && (get_host_rip || get_all)) {
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_HOST_RIP, &rip);
+ if (error == 0)
+ printf("host_rip[%d]\t\t0x%016lx\n", vcpu, rip);
+ }
+
+ if (!error && (get_host_rsp || get_all)) {
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_HOST_RSP, &rsp);
+ if (error == 0)
+ printf("host_rsp[%d]\t\t0x%016lx\n", vcpu, rsp);
+ }
+
+ if (!error && (get_vmcs_link || get_all)) {
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_LINK_POINTER, &addr);
+ if (error == 0)
+ printf("vmcs_pointer[%d]\t0x%016lx\n", vcpu, addr);
+ }
+
+ if (!error && (get_vmcs_exit_interruption_info || get_all)) {
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_EXIT_INTR_INFO, &u64);
+ if (error == 0) {
+ printf("vmcs_exit_interruption_info[%d]\t0x%016lx\n",
+ vcpu, u64);
+ }
+ }
+
+ if (!error && (get_vmcs_exit_interruption_error || get_all)) {
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_EXIT_INTR_ERRCODE,
+ &u64);
+ if (error == 0) {
+ printf("vmcs_exit_interruption_error[%d]\t0x%016lx\n",
+ vcpu, u64);
+ }
+ }
+
+ if (!error && (get_vmcs_interruptibility || get_all)) {
+ error = vm_get_vmcs_field(ctx, vcpu,
+ VMCS_GUEST_INTERRUPTIBILITY, &u64);
+ if (error == 0) {
+ printf("vmcs_guest_interruptibility[%d]\t0x%016lx\n",
+ vcpu, u64);
+ }
+ }
+
+ if (!error && (get_vmcs_exit_inst_length || get_all)) {
+ error = vm_get_vmcs_field(ctx, vcpu,
+ VMCS_EXIT_INSTRUCTION_LENGTH, &u64);
+ if (error == 0)
+ printf("vmcs_exit_inst_length[%d]\t0x%08x\n", vcpu,
+ (uint32_t)u64);
+ }
+
+ if (!error && (get_vmcs_exit_qualification || get_all)) {
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_EXIT_QUALIFICATION,
+ &u64);
+ if (error == 0)
+ printf("vmcs_exit_qualification[%d]\t0x%016lx\n",
+ vcpu, u64);
+ }
+
+ return (error);
+}
+
+static int
+get_misc_vmcb(struct vmctx *ctx, int vcpu)
+{
+ uint64_t ctl, addr;
+ int error = 0;
+
+ if (!error && (get_vmcb_intercept || get_all)) {
+ error = vm_get_vmcb_field(ctx, vcpu, VMCB_OFF_CR_INTERCEPT, 4,
+ &ctl);
+ if (error == 0)
+ printf("cr_intercept[%d]\t0x%08x\n", vcpu, (int)ctl);
+
+ error = vm_get_vmcb_field(ctx, vcpu, VMCB_OFF_DR_INTERCEPT, 4,
+ &ctl);
+ if (error == 0)
+ printf("dr_intercept[%d]\t0x%08x\n", vcpu, (int)ctl);
+
+ error = vm_get_vmcb_field(ctx, vcpu, VMCB_OFF_EXC_INTERCEPT, 4,
+ &ctl);
+ if (error == 0)
+ printf("exc_intercept[%d]\t0x%08x\n", vcpu, (int)ctl);
+
+ error = vm_get_vmcb_field(ctx, vcpu, VMCB_OFF_INST1_INTERCEPT,
+ 4, &ctl);
+ if (error == 0)
+ printf("inst1_intercept[%d]\t0x%08x\n", vcpu, (int)ctl);
+
+ error = vm_get_vmcb_field(ctx, vcpu, VMCB_OFF_INST2_INTERCEPT,
+ 4, &ctl);
+ if (error == 0)
+ printf("inst2_intercept[%d]\t0x%08x\n", vcpu, (int)ctl);
+ }
+
+ if (!error && (get_vmcb_tlb_ctrl || get_all)) {
+ error = vm_get_vmcb_field(ctx, vcpu, VMCB_OFF_TLB_CTRL,
+ 4, &ctl);
+ if (error == 0)
+ printf("TLB ctrl[%d]\t0x%016lx\n", vcpu, ctl);
+ }
+
+ if (!error && (get_vmcb_exit_details || get_all)) {
+ error = vm_get_vmcb_field(ctx, vcpu, VMCB_OFF_EXITINFO1,
+ 8, &ctl);
+ if (error == 0)
+ printf("exitinfo1[%d]\t0x%016lx\n", vcpu, ctl);
+ error = vm_get_vmcb_field(ctx, vcpu, VMCB_OFF_EXITINFO2,
+ 8, &ctl);
+ if (error == 0)
+ printf("exitinfo2[%d]\t0x%016lx\n", vcpu, ctl);
+ error = vm_get_vmcb_field(ctx, vcpu, VMCB_OFF_EXITINTINFO,
+ 8, &ctl);
+ if (error == 0)
+ printf("exitintinfo[%d]\t0x%016lx\n", vcpu, ctl);
+ }
+
+ if (!error && (get_vmcb_virq || get_all)) {
+ error = vm_get_vmcb_field(ctx, vcpu, VMCB_OFF_VIRQ,
+ 8, &ctl);
+ if (error == 0)
+ printf("v_irq/tpr[%d]\t0x%016lx\n", vcpu, ctl);
+ }
+
+ if (!error && (get_apic_access_addr || get_all)) {
+ error = vm_get_vmcb_field(ctx, vcpu, VMCB_OFF_AVIC_BAR, 8,
+ &addr);
+ if (error == 0)
+ printf("AVIC apic_bar[%d]\t0x%016lx\n", vcpu, addr);
+ }
+
+ if (!error && (get_virtual_apic_addr || get_all)) {
+ error = vm_get_vmcb_field(ctx, vcpu, VMCB_OFF_AVIC_PAGE, 8,
+ &addr);
+ if (error == 0)
+ printf("AVIC backing page[%d]\t0x%016lx\n", vcpu, addr);
+ }
+
+ if (!error && (get_avic_table || get_all)) {
+ error = vm_get_vmcb_field(ctx, vcpu, VMCB_OFF_AVIC_LT, 8,
+ &addr);
+ if (error == 0)
+ printf("AVIC logical table[%d]\t0x%016lx\n",
+ vcpu, addr);
+ error = vm_get_vmcb_field(ctx, vcpu, VMCB_OFF_AVIC_PT, 8,
+ &addr);
+ if (error == 0)
+ printf("AVIC physical table[%d]\t0x%016lx\n",
+ vcpu, addr);
+ }
+
+ return (error);
+}
+
+static struct option *
+setup_options(bool cpu_intel)
+{
+ const struct option common_opts[] = {
+ { "vm", REQ_ARG, 0, VMNAME },
+ { "cpu", REQ_ARG, 0, VCPU },
+ { "set-mem", REQ_ARG, 0, SET_MEM },
+ { "set-efer", REQ_ARG, 0, SET_EFER },
+ { "set-cr0", REQ_ARG, 0, SET_CR0 },
+ { "set-cr3", REQ_ARG, 0, SET_CR3 },
+ { "set-cr4", REQ_ARG, 0, SET_CR4 },
+ { "set-dr7", REQ_ARG, 0, SET_DR7 },
+ { "set-rsp", REQ_ARG, 0, SET_RSP },
+ { "set-rip", REQ_ARG, 0, SET_RIP },
+ { "set-rax", REQ_ARG, 0, SET_RAX },
+ { "set-rflags", REQ_ARG, 0, SET_RFLAGS },
+ { "desc-base", REQ_ARG, 0, DESC_BASE },
+ { "desc-limit", REQ_ARG, 0, DESC_LIMIT },
+ { "desc-access",REQ_ARG, 0, DESC_ACCESS },
+ { "set-cs", REQ_ARG, 0, SET_CS },
+ { "set-ds", REQ_ARG, 0, SET_DS },
+ { "set-es", REQ_ARG, 0, SET_ES },
+ { "set-fs", REQ_ARG, 0, SET_FS },
+ { "set-gs", REQ_ARG, 0, SET_GS },
+ { "set-ss", REQ_ARG, 0, SET_SS },
+ { "set-tr", REQ_ARG, 0, SET_TR },
+ { "set-ldtr", REQ_ARG, 0, SET_LDTR },
+ { "set-x2apic-state",REQ_ARG, 0, SET_X2APIC_STATE },
+ { "set-exception-bitmap",
+ REQ_ARG, 0, SET_EXCEPTION_BITMAP },
+ { "capname", REQ_ARG, 0, CAPNAME },
+ { "unassign-pptdev", REQ_ARG, 0, UNASSIGN_PPTDEV },
+ { "setcap", REQ_ARG, 0, SET_CAP },
+ { "get-gpa-pmap", REQ_ARG, 0, GET_GPA_PMAP },
+ { "assert-lapic-lvt", REQ_ARG, 0, ASSERT_LAPIC_LVT },
+ { "get-rtc-time", NO_ARG, &get_rtc_time, 1 },
+ { "set-rtc-time", REQ_ARG, 0, SET_RTC_TIME },
+ { "rtc-nvram-offset", REQ_ARG, 0, RTC_NVRAM_OFFSET },
+ { "get-rtc-nvram", NO_ARG, &get_rtc_nvram, 1 },
+ { "set-rtc-nvram", REQ_ARG, 0, SET_RTC_NVRAM },
+ { "getcap", NO_ARG, &getcap, 1 },
+ { "get-stats", NO_ARG, &get_stats, 1 },
+ { "get-desc-ds",NO_ARG, &get_desc_ds, 1 },
+ { "set-desc-ds",NO_ARG, &set_desc_ds, 1 },
+ { "get-desc-es",NO_ARG, &get_desc_es, 1 },
+ { "set-desc-es",NO_ARG, &set_desc_es, 1 },
+ { "get-desc-ss",NO_ARG, &get_desc_ss, 1 },
+ { "set-desc-ss",NO_ARG, &set_desc_ss, 1 },
+ { "get-desc-cs",NO_ARG, &get_desc_cs, 1 },
+ { "set-desc-cs",NO_ARG, &set_desc_cs, 1 },
+ { "get-desc-fs",NO_ARG, &get_desc_fs, 1 },
+ { "set-desc-fs",NO_ARG, &set_desc_fs, 1 },
+ { "get-desc-gs",NO_ARG, &get_desc_gs, 1 },
+ { "set-desc-gs",NO_ARG, &set_desc_gs, 1 },
+ { "get-desc-tr",NO_ARG, &get_desc_tr, 1 },
+ { "set-desc-tr",NO_ARG, &set_desc_tr, 1 },
+ { "set-desc-ldtr", NO_ARG, &set_desc_ldtr, 1 },
+ { "get-desc-ldtr", NO_ARG, &get_desc_ldtr, 1 },
+ { "set-desc-gdtr", NO_ARG, &set_desc_gdtr, 1 },
+ { "get-desc-gdtr", NO_ARG, &get_desc_gdtr, 1 },
+ { "set-desc-idtr", NO_ARG, &set_desc_idtr, 1 },
+ { "get-desc-idtr", NO_ARG, &get_desc_idtr, 1 },
+ { "get-memmap", NO_ARG, &get_memmap, 1 },
+ { "get-memseg", NO_ARG, &get_memseg, 1 },
+ { "get-efer", NO_ARG, &get_efer, 1 },
+ { "get-cr0", NO_ARG, &get_cr0, 1 },
+ { "get-cr3", NO_ARG, &get_cr3, 1 },
+ { "get-cr4", NO_ARG, &get_cr4, 1 },
+ { "get-dr7", NO_ARG, &get_dr7, 1 },
+ { "get-rsp", NO_ARG, &get_rsp, 1 },
+ { "get-rip", NO_ARG, &get_rip, 1 },
+ { "get-rax", NO_ARG, &get_rax, 1 },
+ { "get-rbx", NO_ARG, &get_rbx, 1 },
+ { "get-rcx", NO_ARG, &get_rcx, 1 },
+ { "get-rdx", NO_ARG, &get_rdx, 1 },
+ { "get-rsi", NO_ARG, &get_rsi, 1 },
+ { "get-rdi", NO_ARG, &get_rdi, 1 },
+ { "get-rbp", NO_ARG, &get_rbp, 1 },
+ { "get-r8", NO_ARG, &get_r8, 1 },
+ { "get-r9", NO_ARG, &get_r9, 1 },
+ { "get-r10", NO_ARG, &get_r10, 1 },
+ { "get-r11", NO_ARG, &get_r11, 1 },
+ { "get-r12", NO_ARG, &get_r12, 1 },
+ { "get-r13", NO_ARG, &get_r13, 1 },
+ { "get-r14", NO_ARG, &get_r14, 1 },
+ { "get-r15", NO_ARG, &get_r15, 1 },
+ { "get-rflags", NO_ARG, &get_rflags, 1 },
+ { "get-cs", NO_ARG, &get_cs, 1 },
+ { "get-ds", NO_ARG, &get_ds, 1 },
+ { "get-es", NO_ARG, &get_es, 1 },
+ { "get-fs", NO_ARG, &get_fs, 1 },
+ { "get-gs", NO_ARG, &get_gs, 1 },
+ { "get-ss", NO_ARG, &get_ss, 1 },
+ { "get-tr", NO_ARG, &get_tr, 1 },
+ { "get-ldtr", NO_ARG, &get_ldtr, 1 },
+ { "get-eptp", NO_ARG, &get_eptp, 1 },
+ { "get-exception-bitmap",
+ NO_ARG, &get_exception_bitmap, 1 },
+ { "get-io-bitmap-address",
+ NO_ARG, &get_io_bitmap, 1 },
+ { "get-tsc-offset", NO_ARG, &get_tsc_offset, 1 },
+ { "get-msr-bitmap",
+ NO_ARG, &get_msr_bitmap, 1 },
+ { "get-msr-bitmap-address",
+ NO_ARG, &get_msr_bitmap_address, 1 },
+ { "get-guest-pat", NO_ARG, &get_guest_pat, 1 },
+ { "get-guest-sysenter",
+ NO_ARG, &get_guest_sysenter, 1 },
+ { "get-exit-reason",
+ NO_ARG, &get_exit_reason, 1 },
+ { "get-x2apic-state", NO_ARG, &get_x2apic_state, 1 },
+ { "get-all", NO_ARG, &get_all, 1 },
+ { "run", NO_ARG, &run, 1 },
+ { "create", NO_ARG, &create, 1 },
+ { "destroy", NO_ARG, &destroy, 1 },
+ { "inject-nmi", NO_ARG, &inject_nmi, 1 },
+ { "force-reset", NO_ARG, &force_reset, 1 },
+ { "force-poweroff", NO_ARG, &force_poweroff, 1 },
+ { "get-active-cpus", NO_ARG, &get_active_cpus, 1 },
+ { "get-suspended-cpus", NO_ARG, &get_suspended_cpus, 1 },
+ { "get-intinfo", NO_ARG, &get_intinfo, 1 },
+ };
+
+ const struct option intel_opts[] = {
+ { "get-vmcs-pinbased-ctls",
+ NO_ARG, &get_pinbased_ctls, 1 },
+ { "get-vmcs-procbased-ctls",
+ NO_ARG, &get_procbased_ctls, 1 },
+ { "get-vmcs-procbased-ctls2",
+ NO_ARG, &get_procbased_ctls2, 1 },
+ { "get-vmcs-guest-linear-address",
+ NO_ARG, &get_vmcs_gla, 1 },
+ { "get-vmcs-guest-physical-address",
+ NO_ARG, &get_vmcs_gpa, 1 },
+ { "get-vmcs-entry-interruption-info",
+ NO_ARG, &get_vmcs_entry_interruption_info, 1},
+ { "get-vmcs-cr0-mask", NO_ARG, &get_cr0_mask, 1 },
+ { "get-vmcs-cr0-shadow", NO_ARG,&get_cr0_shadow, 1 },
+ { "get-vmcs-cr4-mask", NO_ARG, &get_cr4_mask, 1 },
+ { "get-vmcs-cr4-shadow", NO_ARG, &get_cr4_shadow, 1 },
+ { "get-vmcs-cr3-targets", NO_ARG, &get_cr3_targets, 1 },
+ { "get-vmcs-tpr-threshold",
+ NO_ARG, &get_tpr_threshold, 1 },
+ { "get-vmcs-vpid", NO_ARG, &get_vpid_asid, 1 },
+ { "get-vmcs-exit-ctls", NO_ARG, &get_exit_ctls, 1 },
+ { "get-vmcs-entry-ctls",
+ NO_ARG, &get_entry_ctls, 1 },
+ { "get-vmcs-instruction-error",
+ NO_ARG, &get_inst_err, 1 },
+ { "get-vmcs-host-pat", NO_ARG, &get_host_pat, 1 },
+ { "get-vmcs-host-cr0",
+ NO_ARG, &get_host_cr0, 1 },
+ { "set-vmcs-entry-interruption-info",
+ REQ_ARG, 0, SET_VMCS_ENTRY_INTERRUPTION_INFO },
+ { "get-vmcs-exit-qualification",
+ NO_ARG, &get_vmcs_exit_qualification, 1 },
+ { "get-vmcs-exit-inst-length",
+ NO_ARG, &get_vmcs_exit_inst_length, 1 },
+ { "get-vmcs-interruptibility",
+ NO_ARG, &get_vmcs_interruptibility, 1 },
+ { "get-vmcs-exit-interruption-error",
+ NO_ARG, &get_vmcs_exit_interruption_error, 1 },
+ { "get-vmcs-exit-interruption-info",
+ NO_ARG, &get_vmcs_exit_interruption_info, 1 },
+ { "get-vmcs-link", NO_ARG, &get_vmcs_link, 1 },
+ { "get-vmcs-host-cr3",
+ NO_ARG, &get_host_cr3, 1 },
+ { "get-vmcs-host-cr4",
+ NO_ARG, &get_host_cr4, 1 },
+ { "get-vmcs-host-rip",
+ NO_ARG, &get_host_rip, 1 },
+ { "get-vmcs-host-rsp",
+ NO_ARG, &get_host_rsp, 1 },
+ { "get-apic-access-address",
+ NO_ARG, &get_apic_access_addr, 1},
+ { "get-virtual-apic-address",
+ NO_ARG, &get_virtual_apic_addr, 1}
+ };
+
+ const struct option amd_opts[] = {
+ { "get-vmcb-intercepts",
+ NO_ARG, &get_vmcb_intercept, 1 },
+ { "get-vmcb-asid",
+ NO_ARG, &get_vpid_asid, 1 },
+ { "get-vmcb-exit-details",
+ NO_ARG, &get_vmcb_exit_details, 1 },
+ { "get-vmcb-tlb-ctrl",
+ NO_ARG, &get_vmcb_tlb_ctrl, 1 },
+ { "get-vmcb-virq",
+ NO_ARG, &get_vmcb_virq, 1 },
+ { "get-avic-apic-bar",
+ NO_ARG, &get_apic_access_addr, 1 },
+ { "get-avic-backing-page",
+ NO_ARG, &get_virtual_apic_addr, 1 },
+ { "get-avic-table",
+ NO_ARG, &get_avic_table, 1 }
+ };
+
+ const struct option null_opt = {
+ NULL, 0, NULL, 0
+ };
+
+ struct option *all_opts;
+ char *cp;
+ int optlen;
+
+ optlen = sizeof(common_opts);
+
+ if (cpu_intel)
+ optlen += sizeof(intel_opts);
+ else
+ optlen += sizeof(amd_opts);
+
+ optlen += sizeof(null_opt);
+
+ all_opts = malloc(optlen);
+
+ cp = (char *)all_opts;
+ memcpy(cp, common_opts, sizeof(common_opts));
+ cp += sizeof(common_opts);
+
+ if (cpu_intel) {
+ memcpy(cp, intel_opts, sizeof(intel_opts));
+ cp += sizeof(intel_opts);
+ } else {
+ memcpy(cp, amd_opts, sizeof(amd_opts));
+ cp += sizeof(amd_opts);
+ }
+
+ memcpy(cp, &null_opt, sizeof(null_opt));
+ cp += sizeof(null_opt);
+
+ return (all_opts);
+}
+
+static const char *
+wday_str(int idx)
+{
+ static const char *weekdays[] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+ };
+
+ if (idx >= 0 && idx < 7)
+ return (weekdays[idx]);
+ else
+ return ("UNK");
+}
+
+static const char *
+mon_str(int idx)
+{
+ static const char *months[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ };
+
+ if (idx >= 0 && idx < 12)
+ return (months[idx]);
+ else
+ return ("UNK");
+}
+
+static int
+show_memmap(struct vmctx *ctx)
+{
+ char name[SPECNAMELEN + 1], numbuf[8];
+ vm_ooffset_t segoff;
+ vm_paddr_t gpa;
+ size_t maplen, seglen;
+ int error, flags, prot, segid, delim;
+
+ printf("Address Length Segment Offset ");
+ printf("Prot Flags\n");
+
+ gpa = 0;
+ while (1) {
+ error = vm_mmap_getnext(ctx, &gpa, &segid, &segoff, &maplen,
+ &prot, &flags);
+ if (error)
+ return (errno == ENOENT ? 0 : error);
+
+ error = vm_get_memseg(ctx, segid, &seglen, name, sizeof(name));
+ if (error)
+ return (error);
+
+ printf("%-12lX", gpa);
+ humanize_number(numbuf, sizeof(numbuf), maplen, "B",
+ HN_AUTOSCALE, HN_NOSPACE);
+ printf("%-12s", numbuf);
+
+ printf("%-12s", name[0] ? name : "sysmem");
+ printf("%-12lX", segoff);
+ printf("%c%c%c ", prot & PROT_READ ? 'R' : '-',
+ prot & PROT_WRITE ? 'W' : '-',
+ prot & PROT_EXEC ? 'X' : '-');
+
+ delim = '\0';
+ if (flags & VM_MEMMAP_F_WIRED) {
+ printf("%cwired", delim);
+ delim = '/';
+ }
+ if (flags & VM_MEMMAP_F_IOMMU) {
+ printf("%ciommu", delim);
+ delim = '/';
+ }
+ printf("\n");
+
+ gpa += maplen;
+ }
+}
+
+static int
+show_memseg(struct vmctx *ctx)
+{
+ char name[SPECNAMELEN + 1], numbuf[8];
+ size_t seglen;
+ int error, segid;
+
+ printf("ID Length Name\n");
+
+ segid = 0;
+ while (1) {
+ error = vm_get_memseg(ctx, segid, &seglen, name, sizeof(name));
+ if (error)
+ return (errno == EINVAL ? 0 : error);
+
+ if (seglen) {
+ printf("%-4d", segid);
+ humanize_number(numbuf, sizeof(numbuf), seglen, "B",
+ HN_AUTOSCALE, HN_NOSPACE);
+ printf("%-12s", numbuf);
+ printf("%s", name[0] ? name : "sysmem");
+ printf("\n");
+ }
+ segid++;
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *vmname;
+ int error, ch, vcpu, ptenum;
+ vm_paddr_t gpa_pmap;
+ struct vm_exit vmexit;
+ uint64_t rax, cr0, cr3, cr4, dr7, rsp, rip, rflags, efer, pat;
+ uint64_t eptp, bm, addr, u64, pteval[4], *pte, info[2];
+ struct vmctx *ctx;
+ cpuset_t cpus;
+ bool cpu_intel;
+ uint64_t cs, ds, es, fs, gs, ss, tr, ldtr;
+ struct tm tm;
+ struct option *opts;
+
+ cpu_intel = cpu_vendor_intel();
+ opts = setup_options(cpu_intel);
+
+ vcpu = 0;
+ vmname = NULL;
+ assert_lapic_lvt = -1;
+ progname = basename(argv[0]);
+
+ while ((ch = getopt_long(argc, argv, "", opts, NULL)) != -1) {
+ switch (ch) {
+ case 0:
+ break;
+ case VMNAME:
+ vmname = optarg;
+ break;
+ case VCPU:
+ vcpu = atoi(optarg);
+ break;
+ case SET_MEM:
+ memsize = atoi(optarg) * MB;
+ memsize = roundup(memsize, 2 * MB);
+ break;
+ case SET_EFER:
+ efer = strtoul(optarg, NULL, 0);
+ set_efer = 1;
+ break;
+ case SET_CR0:
+ cr0 = strtoul(optarg, NULL, 0);
+ set_cr0 = 1;
+ break;
+ case SET_CR3:
+ cr3 = strtoul(optarg, NULL, 0);
+ set_cr3 = 1;
+ break;
+ case SET_CR4:
+ cr4 = strtoul(optarg, NULL, 0);
+ set_cr4 = 1;
+ break;
+ case SET_DR7:
+ dr7 = strtoul(optarg, NULL, 0);
+ set_dr7 = 1;
+ break;
+ case SET_RSP:
+ rsp = strtoul(optarg, NULL, 0);
+ set_rsp = 1;
+ break;
+ case SET_RIP:
+ rip = strtoul(optarg, NULL, 0);
+ set_rip = 1;
+ break;
+ case SET_RAX:
+ rax = strtoul(optarg, NULL, 0);
+ set_rax = 1;
+ break;
+ case SET_RFLAGS:
+ rflags = strtoul(optarg, NULL, 0);
+ set_rflags = 1;
+ break;
+ case DESC_BASE:
+ desc_base = strtoul(optarg, NULL, 0);
+ break;
+ case DESC_LIMIT:
+ desc_limit = strtoul(optarg, NULL, 0);
+ break;
+ case DESC_ACCESS:
+ desc_access = strtoul(optarg, NULL, 0);
+ break;
+ case SET_CS:
+ cs = strtoul(optarg, NULL, 0);
+ set_cs = 1;
+ break;
+ case SET_DS:
+ ds = strtoul(optarg, NULL, 0);
+ set_ds = 1;
+ break;
+ case SET_ES:
+ es = strtoul(optarg, NULL, 0);
+ set_es = 1;
+ break;
+ case SET_FS:
+ fs = strtoul(optarg, NULL, 0);
+ set_fs = 1;
+ break;
+ case SET_GS:
+ gs = strtoul(optarg, NULL, 0);
+ set_gs = 1;
+ break;
+ case SET_SS:
+ ss = strtoul(optarg, NULL, 0);
+ set_ss = 1;
+ break;
+ case SET_TR:
+ tr = strtoul(optarg, NULL, 0);
+ set_tr = 1;
+ break;
+ case SET_LDTR:
+ ldtr = strtoul(optarg, NULL, 0);
+ set_ldtr = 1;
+ break;
+ case SET_X2APIC_STATE:
+ x2apic_state = strtol(optarg, NULL, 0);
+ set_x2apic_state = 1;
+ break;
+ case SET_EXCEPTION_BITMAP:
+ exception_bitmap = strtoul(optarg, NULL, 0);
+ set_exception_bitmap = 1;
+ break;
+ case SET_VMCS_ENTRY_INTERRUPTION_INFO:
+ vmcs_entry_interruption_info = strtoul(optarg, NULL, 0);
+ set_vmcs_entry_interruption_info = 1;
+ break;
+ case SET_CAP:
+ capval = strtoul(optarg, NULL, 0);
+ setcap = 1;
+ break;
+ case SET_RTC_TIME:
+ rtc_secs = strtoul(optarg, NULL, 0);
+ set_rtc_time = 1;
+ break;
+ case SET_RTC_NVRAM:
+ rtc_nvram_value = (uint8_t)strtoul(optarg, NULL, 0);
+ set_rtc_nvram = 1;
+ break;
+ case RTC_NVRAM_OFFSET:
+ rtc_nvram_offset = strtoul(optarg, NULL, 0);
+ break;
+ case GET_GPA_PMAP:
+ gpa_pmap = strtoul(optarg, NULL, 0);
+ get_gpa_pmap = 1;
+ break;
+ case CAPNAME:
+ capname = optarg;
+ break;
+ case UNASSIGN_PPTDEV:
+ unassign_pptdev = 1;
+ if (sscanf(optarg, "%d/%d/%d", &bus, &slot, &func) != 3)
+ usage(cpu_intel);
+ break;
+ case ASSERT_LAPIC_LVT:
+ assert_lapic_lvt = atoi(optarg);
+ break;
+ default:
+ usage(cpu_intel);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (vmname == NULL)
+ usage(cpu_intel);
+
+ error = 0;
+
+ if (!error && create)
+ error = vm_create(vmname);
+
+ if (!error) {
+ ctx = vm_open(vmname);
+ if (ctx == NULL) {
+ printf("VM:%s is not created.\n", vmname);
+ exit (1);
+ }
+ }
+
+ if (!error && memsize)
+ error = vm_setup_memory(ctx, memsize, VM_MMAP_ALL);
+
+ if (!error && set_efer)
+ error = vm_set_register(ctx, vcpu, VM_REG_GUEST_EFER, efer);
+
+ if (!error && set_cr0)
+ error = vm_set_register(ctx, vcpu, VM_REG_GUEST_CR0, cr0);
+
+ if (!error && set_cr3)
+ error = vm_set_register(ctx, vcpu, VM_REG_GUEST_CR3, cr3);
+
+ if (!error && set_cr4)
+ error = vm_set_register(ctx, vcpu, VM_REG_GUEST_CR4, cr4);
+
+ if (!error && set_dr7)
+ error = vm_set_register(ctx, vcpu, VM_REG_GUEST_DR7, dr7);
+
+ if (!error && set_rsp)
+ error = vm_set_register(ctx, vcpu, VM_REG_GUEST_RSP, rsp);
+
+ if (!error && set_rip)
+ error = vm_set_register(ctx, vcpu, VM_REG_GUEST_RIP, rip);
+
+ if (!error && set_rax)
+ error = vm_set_register(ctx, vcpu, VM_REG_GUEST_RAX, rax);
+
+ if (!error && set_rflags) {
+ error = vm_set_register(ctx, vcpu, VM_REG_GUEST_RFLAGS,
+ rflags);
+ }
+
+ if (!error && set_desc_ds) {
+ error = vm_set_desc(ctx, vcpu, VM_REG_GUEST_DS,
+ desc_base, desc_limit, desc_access);
+ }
+
+ if (!error && set_desc_es) {
+ error = vm_set_desc(ctx, vcpu, VM_REG_GUEST_ES,
+ desc_base, desc_limit, desc_access);
+ }
+
+ if (!error && set_desc_ss) {
+ error = vm_set_desc(ctx, vcpu, VM_REG_GUEST_SS,
+ desc_base, desc_limit, desc_access);
+ }
+
+ if (!error && set_desc_cs) {
+ error = vm_set_desc(ctx, vcpu, VM_REG_GUEST_CS,
+ desc_base, desc_limit, desc_access);
+ }
+
+ if (!error && set_desc_fs) {
+ error = vm_set_desc(ctx, vcpu, VM_REG_GUEST_FS,
+ desc_base, desc_limit, desc_access);
+ }
+
+ if (!error && set_desc_gs) {
+ error = vm_set_desc(ctx, vcpu, VM_REG_GUEST_GS,
+ desc_base, desc_limit, desc_access);
+ }
+
+ if (!error && set_desc_tr) {
+ error = vm_set_desc(ctx, vcpu, VM_REG_GUEST_TR,
+ desc_base, desc_limit, desc_access);
+ }
+
+ if (!error && set_desc_ldtr) {
+ error = vm_set_desc(ctx, vcpu, VM_REG_GUEST_LDTR,
+ desc_base, desc_limit, desc_access);
+ }
+
+ if (!error && set_desc_gdtr) {
+ error = vm_set_desc(ctx, vcpu, VM_REG_GUEST_GDTR,
+ desc_base, desc_limit, 0);
+ }
+
+ if (!error && set_desc_idtr) {
+ error = vm_set_desc(ctx, vcpu, VM_REG_GUEST_IDTR,
+ desc_base, desc_limit, 0);
+ }
+
+ if (!error && set_cs)
+ error = vm_set_register(ctx, vcpu, VM_REG_GUEST_CS, cs);
+
+ if (!error && set_ds)
+ error = vm_set_register(ctx, vcpu, VM_REG_GUEST_DS, ds);
+
+ if (!error && set_es)
+ error = vm_set_register(ctx, vcpu, VM_REG_GUEST_ES, es);
+
+ if (!error && set_fs)
+ error = vm_set_register(ctx, vcpu, VM_REG_GUEST_FS, fs);
+
+ if (!error && set_gs)
+ error = vm_set_register(ctx, vcpu, VM_REG_GUEST_GS, gs);
+
+ if (!error && set_ss)
+ error = vm_set_register(ctx, vcpu, VM_REG_GUEST_SS, ss);
+
+ if (!error && set_tr)
+ error = vm_set_register(ctx, vcpu, VM_REG_GUEST_TR, tr);
+
+ if (!error && set_ldtr)
+ error = vm_set_register(ctx, vcpu, VM_REG_GUEST_LDTR, ldtr);
+
+ if (!error && set_x2apic_state)
+ error = vm_set_x2apic_state(ctx, vcpu, x2apic_state);
+
+ if (!error && unassign_pptdev)
+ error = vm_unassign_pptdev(ctx, bus, slot, func);
+
+ if (!error && set_exception_bitmap) {
+ if (cpu_intel)
+ error = vm_set_vmcs_field(ctx, vcpu,
+ VMCS_EXCEPTION_BITMAP,
+ exception_bitmap);
+ else
+ error = vm_set_vmcb_field(ctx, vcpu,
+ VMCB_OFF_EXC_INTERCEPT,
+ 4, exception_bitmap);
+ }
+
+ if (!error && cpu_intel && set_vmcs_entry_interruption_info) {
+ error = vm_set_vmcs_field(ctx, vcpu, VMCS_ENTRY_INTR_INFO,
+ vmcs_entry_interruption_info);
+ }
+
+ if (!error && inject_nmi) {
+ error = vm_inject_nmi(ctx, vcpu);
+ }
+
+ if (!error && assert_lapic_lvt != -1) {
+ error = vm_lapic_local_irq(ctx, vcpu, assert_lapic_lvt);
+ }
+
+ if (!error && (get_memseg || get_all))
+ error = show_memseg(ctx);
+
+ if (!error && (get_memmap || get_all))
+ error = show_memmap(ctx);
+
+ if (!error)
+ error = get_all_registers(ctx, vcpu);
+
+ if (!error)
+ error = get_all_segments(ctx, vcpu);
+
+ if (!error) {
+ if (cpu_intel)
+ error = get_misc_vmcs(ctx, vcpu);
+ else
+ error = get_misc_vmcb(ctx, vcpu);
+ }
+
+ if (!error && (get_x2apic_state || get_all)) {
+ error = vm_get_x2apic_state(ctx, vcpu, &x2apic_state);
+ if (error == 0)
+ printf("x2apic_state[%d]\t%d\n", vcpu, x2apic_state);
+ }
+
+ if (!error && (get_eptp || get_all)) {
+ if (cpu_intel)
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_EPTP, &eptp);
+ else
+ error = vm_get_vmcb_field(ctx, vcpu, VMCB_OFF_NPT_BASE,
+ 8, &eptp);
+ if (error == 0)
+ printf("%s[%d]\t\t0x%016lx\n",
+ cpu_intel ? "eptp" : "rvi/npt", vcpu, eptp);
+ }
+
+ if (!error && (get_exception_bitmap || get_all)) {
+ if(cpu_intel)
+ error = vm_get_vmcs_field(ctx, vcpu,
+ VMCS_EXCEPTION_BITMAP, &bm);
+ else
+ error = vm_get_vmcb_field(ctx, vcpu,
+ VMCB_OFF_EXC_INTERCEPT,
+ 4, &bm);
+ if (error == 0)
+ printf("exception_bitmap[%d]\t%#lx\n", vcpu, bm);
+ }
+
+ if (!error && (get_io_bitmap || get_all)) {
+ if (cpu_intel) {
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_IO_BITMAP_A,
+ &bm);
+ if (error == 0)
+ printf("io_bitmap_a[%d]\t%#lx\n", vcpu, bm);
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_IO_BITMAP_B,
+ &bm);
+ if (error == 0)
+ printf("io_bitmap_b[%d]\t%#lx\n", vcpu, bm);
+ } else {
+ error = vm_get_vmcb_field(ctx, vcpu,
+ VMCB_OFF_IO_PERM, 8, &bm);
+ if (error == 0)
+ printf("io_bitmap[%d]\t%#lx\n", vcpu, bm);
+ }
+ }
+
+ if (!error && (get_tsc_offset || get_all)) {
+ uint64_t tscoff;
+ if (cpu_intel)
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_TSC_OFFSET,
+ &tscoff);
+ else
+ error = vm_get_vmcb_field(ctx, vcpu,
+ VMCB_OFF_TSC_OFFSET,
+ 8, &tscoff);
+ if (error == 0)
+ printf("tsc_offset[%d]\t0x%016lx\n", vcpu, tscoff);
+ }
+
+ if (!error && (get_msr_bitmap_address || get_all)) {
+ if (cpu_intel)
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_MSR_BITMAP,
+ &addr);
+ else
+ error = vm_get_vmcb_field(ctx, vcpu,
+ VMCB_OFF_MSR_PERM, 8, &addr);
+ if (error == 0)
+ printf("msr_bitmap[%d]\t\t%#lx\n", vcpu, addr);
+ }
+
+ if (!error && (get_msr_bitmap || get_all)) {
+ if (cpu_intel) {
+ error = vm_get_vmcs_field(ctx, vcpu,
+ VMCS_MSR_BITMAP, &addr);
+ } else {
+ error = vm_get_vmcb_field(ctx, vcpu,
+ VMCB_OFF_MSR_PERM, 8,
+ &addr);
+ }
+
+ if (error == 0)
+ error = dump_msr_bitmap(vcpu, addr, cpu_intel);
+ }
+
+ if (!error && (get_vpid_asid || get_all)) {
+ uint64_t vpid;
+ if (cpu_intel)
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_VPID, &vpid);
+ else
+ error = vm_get_vmcb_field(ctx, vcpu, VMCB_OFF_ASID,
+ 4, &vpid);
+ if (error == 0)
+ printf("%s[%d]\t\t0x%04lx\n",
+ cpu_intel ? "vpid" : "asid", vcpu, vpid);
+ }
+
+ if (!error && (get_guest_pat || get_all)) {
+ if (cpu_intel)
+ error = vm_get_vmcs_field(ctx, vcpu,
+ VMCS_GUEST_IA32_PAT, &pat);
+ else
+ error = vm_get_vmcb_field(ctx, vcpu,
+ VMCB_OFF_GUEST_PAT, 8, &pat);
+ if (error == 0)
+ printf("guest_pat[%d]\t\t0x%016lx\n", vcpu, pat);
+ }
+
+ if (!error && (get_guest_sysenter || get_all)) {
+ if (cpu_intel)
+ error = vm_get_vmcs_field(ctx, vcpu,
+ VMCS_GUEST_IA32_SYSENTER_CS,
+ &cs);
+ else
+ error = vm_get_vmcb_field(ctx, vcpu,
+ VMCB_OFF_SYSENTER_CS, 8,
+ &cs);
+
+ if (error == 0)
+ printf("guest_sysenter_cs[%d]\t%#lx\n", vcpu, cs);
+ if (cpu_intel)
+ error = vm_get_vmcs_field(ctx, vcpu,
+ VMCS_GUEST_IA32_SYSENTER_ESP,
+ &rsp);
+ else
+ error = vm_get_vmcb_field(ctx, vcpu,
+ VMCB_OFF_SYSENTER_ESP, 8,
+ &rsp);
+
+ if (error == 0)
+ printf("guest_sysenter_sp[%d]\t%#lx\n", vcpu, rsp);
+ if (cpu_intel)
+ error = vm_get_vmcs_field(ctx, vcpu,
+ VMCS_GUEST_IA32_SYSENTER_EIP,
+ &rip);
+ else
+ error = vm_get_vmcb_field(ctx, vcpu,
+ VMCB_OFF_SYSENTER_EIP, 8,
+ &rip);
+ if (error == 0)
+ printf("guest_sysenter_ip[%d]\t%#lx\n", vcpu, rip);
+ }
+
+ if (!error && (get_exit_reason || get_all)) {
+ if (cpu_intel)
+ error = vm_get_vmcs_field(ctx, vcpu, VMCS_EXIT_REASON,
+ &u64);
+ else
+ error = vm_get_vmcb_field(ctx, vcpu,
+ VMCB_OFF_EXIT_REASON, 8,
+ &u64);
+ if (error == 0)
+ printf("exit_reason[%d]\t%#lx\n", vcpu, u64);
+ }
+
+ if (!error && setcap) {
+ int captype;
+ captype = vm_capability_name2type(capname);
+ error = vm_set_capability(ctx, vcpu, captype, capval);
+ if (error != 0 && errno == ENOENT)
+ printf("Capability \"%s\" is not available\n", capname);
+ }
+
+ if (!error && get_gpa_pmap) {
+ error = vm_get_gpa_pmap(ctx, gpa_pmap, pteval, &ptenum);
+ if (error == 0) {
+ printf("gpa %#lx:", gpa_pmap);
+ pte = &pteval[0];
+ while (ptenum-- > 0)
+ printf(" %#lx", *pte++);
+ printf("\n");
+ }
+ }
+
+ if (!error && set_rtc_nvram)
+ error = vm_rtc_write(ctx, rtc_nvram_offset, rtc_nvram_value);
+
+ if (!error && (get_rtc_nvram || get_all)) {
+ error = vm_rtc_read(ctx, rtc_nvram_offset, &rtc_nvram_value);
+ if (error == 0) {
+ printf("rtc nvram[%03d]: 0x%02x\n", rtc_nvram_offset,
+ rtc_nvram_value);
+ }
+ }
+
+ if (!error && set_rtc_time)
+ error = vm_rtc_settime(ctx, rtc_secs);
+
+ if (!error && (get_rtc_time || get_all)) {
+ error = vm_rtc_gettime(ctx, &rtc_secs);
+ if (error == 0) {
+ gmtime_r(&rtc_secs, &tm);
+ printf("rtc time %#lx: %s %s %02d %02d:%02d:%02d %d\n",
+ rtc_secs, wday_str(tm.tm_wday), mon_str(tm.tm_mon),
+ tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
+ 1900 + tm.tm_year);
+ }
+ }
+
+ if (!error && (getcap || get_all)) {
+ int captype, val, getcaptype;
+
+ if (getcap && capname)
+ getcaptype = vm_capability_name2type(capname);
+ else
+ getcaptype = -1;
+
+ for (captype = 0; captype < VM_CAP_MAX; captype++) {
+ if (getcaptype >= 0 && captype != getcaptype)
+ continue;
+ error = vm_get_capability(ctx, vcpu, captype, &val);
+ if (error == 0) {
+ printf("Capability \"%s\" is %s on vcpu %d\n",
+ vm_capability_type2name(captype),
+ val ? "set" : "not set", vcpu);
+ } else if (errno == ENOENT) {
+ error = 0;
+ printf("Capability \"%s\" is not available\n",
+ vm_capability_type2name(captype));
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (!error && (get_active_cpus || get_all)) {
+ error = vm_active_cpus(ctx, &cpus);
+ if (!error)
+ print_cpus("active cpus", &cpus);
+ }
+
+ if (!error && (get_suspended_cpus || get_all)) {
+ error = vm_suspended_cpus(ctx, &cpus);
+ if (!error)
+ print_cpus("suspended cpus", &cpus);
+ }
+
+ if (!error && (get_intinfo || get_all)) {
+ error = vm_get_intinfo(ctx, vcpu, &info[0], &info[1]);
+ if (!error) {
+ print_intinfo("pending", info[0]);
+ print_intinfo("current", info[1]);
+ }
+ }
+
+ if (!error && (get_stats || get_all)) {
+ int i, num_stats;
+ uint64_t *stats;
+ struct timeval tv;
+ const char *desc;
+
+ stats = vm_get_stats(ctx, vcpu, &tv, &num_stats);
+ if (stats != NULL) {
+ printf("vcpu%d stats:\n", vcpu);
+ for (i = 0; i < num_stats; i++) {
+ desc = vm_get_stat_desc(ctx, i);
+ printf("%-40s\t%ld\n", desc, stats[i]);
+ }
+ }
+ }
+
+ if (!error && run) {
+ error = vm_run(ctx, vcpu, &vmexit);
+ if (error == 0)
+ dump_vm_run_exitcode(&vmexit, vcpu);
+ else
+ printf("vm_run error %d\n", error);
+ }
+
+ if (!error && force_reset)
+ error = vm_suspend(ctx, VM_SUSPEND_RESET);
+
+ if (!error && force_poweroff)
+ error = vm_suspend(ctx, VM_SUSPEND_POWEROFF);
+
+ if (error)
+ printf("errno = %d\n", errno);
+
+ if (!error && destroy)
+ vm_destroy(ctx);
+
+ free (opts);
+ exit(error);
+}
diff --git a/usr.sbin/bhyveload/Makefile b/usr.sbin/bhyveload/Makefile
new file mode 100644
index 0000000..fce0c1b
--- /dev/null
+++ b/usr.sbin/bhyveload/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+PROG= bhyveload
+SRCS= bhyveload.c
+MAN= bhyveload.8
+
+LIBADD= vmmapi
+
+WARNS?= 3
+
+CFLAGS+=-I${.CURDIR}/../../sys/boot/userboot
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bhyveload/Makefile.depend b/usr.sbin/bhyveload/Makefile.depend
new file mode 100644
index 0000000..2d0f324
--- /dev/null
+++ b/usr.sbin/bhyveload/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+ lib/libvmmapi \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bhyveload/bhyveload.8 b/usr.sbin/bhyveload/bhyveload.8
new file mode 100644
index 0000000..918e1f0
--- /dev/null
+++ b/usr.sbin/bhyveload/bhyveload.8
@@ -0,0 +1,173 @@
+.\"
+.\" Copyright (c) 2012 NetApp 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$
+.\"
+.Dd October 7, 2015
+.Dt BHYVELOAD 8
+.Os
+.Sh NAME
+.Nm bhyveload
+.Nd load a
+.Fx
+guest inside a bhyve virtual machine
+.Sh SYNOPSIS
+.Nm
+.Op Fl S
+.Op Fl c Ar cons-dev
+.Op Fl d Ar disk-path
+.Op Fl e Ar name=value
+.Op Fl h Ar host-path
+.Op Fl l Ar os-loader
+.Op Fl m Ar mem-size
+.Ar vmname
+.Sh DESCRIPTION
+.Nm
+is used to load a
+.Fx
+guest inside a
+.Xr bhyve 4
+virtual machine.
+.Pp
+.Nm
+is based on
+.Xr loader 8
+and will present an interface identical to the
+.Fx
+loader on the user's terminal.
+This behavior can be changed by specifying a different OS loader.
+.Pp
+The virtual machine is identified as
+.Ar vmname
+and will be created if it does not already exist.
+.Sh OPTIONS
+The following options are available:
+.Bl -tag -width indent
+.It Fl c Ar cons-dev
+.Ar cons-dev
+is a
+.Xr tty 4
+device to use for
+.Nm
+terminal I/O.
+.Pp
+The text string "stdio" is also accepted and selects the use of
+unbuffered standard I/O. This is the default value.
+.It Fl d Ar disk-path
+The
+.Ar disk-path
+is the pathname of the guest's boot disk image.
+.It Fl e Ar name=value
+Set the
+.Fx
+loader environment variable
+.Ar name
+to
+.Ar value .
+.Pp
+The option may be used more than once to set more than one environment
+variable.
+.It Fl h Ar host-path
+The
+.Ar host-path
+is the directory at the top of the guest's boot filesystem.
+.It Fl l Ar os-loader
+Specify a different OS loader.
+By default
+.Nm
+will use
+.Pa /boot/userboot.so ,
+which presents a standard
+.Fx
+loader.
+.It Fl m Ar mem-size Xo
+.Sm off
+.Op Cm K | k | M | m | G | g | T | t
+.Xc
+.Sm on
+.Ar mem-size
+is the amount of memory allocated to the guest.
+.Pp
+The
+.Ar mem-size
+argument may be suffixed with one of
+.Cm K ,
+.Cm M ,
+.Cm G
+or
+.Cm T
+(either upper or lower case) to indicate a multiple of
+Kilobytes, Megabytes, Gigabytes or Terabytes
+respectively.
+.Pp
+The default value of
+.Ar mem-size
+is 256M.
+.It Fl S
+Wire guest memory.
+.El
+.Sh EXAMPLES
+To create a virtual machine named
+.Ar freebsd-vm
+that boots off the ISO image
+.Pa /freebsd/release.iso
+and has 1GB memory allocated to it:
+.Pp
+.Dl "bhyveload -m 1G -d /freebsd/release.iso freebsd-vm"
+.Pp
+To create a virtual machine named
+.Ar test-vm
+with 256MB of memory allocated, the guest root filesystem under the host
+directory
+.Pa /user/images/test
+and terminal I/O sent to the
+.Xr nmdm 4
+device
+.Pa /dev/nmdm1B
+.Pp
+.Dl "bhyveload -m 256MB -h /usr/images/test -c /dev/nmdm1B test-vm"
+.Sh SEE ALSO
+.Xr bhyve 4 ,
+.Xr nmdm 4 ,
+.Xr vmm 4 ,
+.Xr bhyve 8 ,
+.Xr loader 8
+.Sh HISTORY
+.Nm
+first appeared in
+.Fx 10.0 ,
+and was developed at NetApp Inc.
+.Sh AUTHORS
+.Nm
+was developed by
+.An -nosplit
+.An Neel Natu Aq Mt neel@FreeBSD.org
+at NetApp Inc with a lot of help from
+.An Doug Rabson Aq Mt dfr@FreeBSD.org .
+.Sh BUGS
+.Nm
+can only load
+.Fx
+as a guest.
diff --git a/usr.sbin/bhyveload/bhyveload.c b/usr.sbin/bhyveload/bhyveload.c
new file mode 100644
index 0000000..badf5f4
--- /dev/null
+++ b/usr.sbin/bhyveload/bhyveload.c
@@ -0,0 +1,772 @@
+/*-
+ * Copyright (c) 2011 NetApp, 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 NETAPP, 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 NETAPP, INC OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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) 2011 Google, 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$
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/disk.h>
+#include <sys/queue.h>
+
+#include <machine/specialreg.h>
+#include <machine/vmm.h>
+
+#include <dirent.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <err.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <vmmapi.h>
+
+#include "userboot.h"
+
+#define MB (1024 * 1024UL)
+#define GB (1024 * 1024 * 1024UL)
+#define BSP 0
+
+#define NDISKS 32
+
+static char *host_base;
+static struct termios term, oldterm;
+static int disk_fd[NDISKS];
+static int ndisks;
+static int consin_fd, consout_fd;
+
+static char *vmname, *progname;
+static struct vmctx *ctx;
+
+static uint64_t gdtbase, cr3, rsp;
+
+static void cb_exit(void *arg, int v);
+
+/*
+ * Console i/o callbacks
+ */
+
+static void
+cb_putc(void *arg, int ch)
+{
+ char c = ch;
+
+ (void) write(consout_fd, &c, 1);
+}
+
+static int
+cb_getc(void *arg)
+{
+ char c;
+
+ if (read(consin_fd, &c, 1) == 1)
+ return (c);
+ return (-1);
+}
+
+static int
+cb_poll(void *arg)
+{
+ int n;
+
+ if (ioctl(consin_fd, FIONREAD, &n) >= 0)
+ return (n > 0);
+ return (0);
+}
+
+/*
+ * Host filesystem i/o callbacks
+ */
+
+struct cb_file {
+ int cf_isdir;
+ size_t cf_size;
+ struct stat cf_stat;
+ union {
+ int fd;
+ DIR *dir;
+ } cf_u;
+};
+
+static int
+cb_open(void *arg, const char *filename, void **hp)
+{
+ struct stat st;
+ struct cb_file *cf;
+ char path[PATH_MAX];
+
+ if (!host_base)
+ return (ENOENT);
+
+ strlcpy(path, host_base, PATH_MAX);
+ if (path[strlen(path) - 1] == '/')
+ path[strlen(path) - 1] = 0;
+ strlcat(path, filename, PATH_MAX);
+ cf = malloc(sizeof(struct cb_file));
+ if (stat(path, &cf->cf_stat) < 0) {
+ free(cf);
+ return (errno);
+ }
+
+ cf->cf_size = st.st_size;
+ if (S_ISDIR(cf->cf_stat.st_mode)) {
+ cf->cf_isdir = 1;
+ cf->cf_u.dir = opendir(path);
+ if (!cf->cf_u.dir)
+ goto out;
+ *hp = cf;
+ return (0);
+ }
+ if (S_ISREG(cf->cf_stat.st_mode)) {
+ cf->cf_isdir = 0;
+ cf->cf_u.fd = open(path, O_RDONLY);
+ if (cf->cf_u.fd < 0)
+ goto out;
+ *hp = cf;
+ return (0);
+ }
+
+out:
+ free(cf);
+ return (EINVAL);
+}
+
+static int
+cb_close(void *arg, void *h)
+{
+ struct cb_file *cf = h;
+
+ if (cf->cf_isdir)
+ closedir(cf->cf_u.dir);
+ else
+ close(cf->cf_u.fd);
+ free(cf);
+
+ return (0);
+}
+
+static int
+cb_isdir(void *arg, void *h)
+{
+ struct cb_file *cf = h;
+
+ return (cf->cf_isdir);
+}
+
+static int
+cb_read(void *arg, void *h, void *buf, size_t size, size_t *resid)
+{
+ struct cb_file *cf = h;
+ ssize_t sz;
+
+ if (cf->cf_isdir)
+ return (EINVAL);
+ sz = read(cf->cf_u.fd, buf, size);
+ if (sz < 0)
+ return (EINVAL);
+ *resid = size - sz;
+ return (0);
+}
+
+static int
+cb_readdir(void *arg, void *h, uint32_t *fileno_return, uint8_t *type_return,
+ size_t *namelen_return, char *name)
+{
+ struct cb_file *cf = h;
+ struct dirent *dp;
+
+ if (!cf->cf_isdir)
+ return (EINVAL);
+
+ dp = readdir(cf->cf_u.dir);
+ if (!dp)
+ return (ENOENT);
+
+ /*
+ * Note: d_namlen is in the range 0..255 and therefore less
+ * than PATH_MAX so we don't need to test before copying.
+ */
+ *fileno_return = dp->d_fileno;
+ *type_return = dp->d_type;
+ *namelen_return = dp->d_namlen;
+ memcpy(name, dp->d_name, dp->d_namlen);
+ name[dp->d_namlen] = 0;
+
+ return (0);
+}
+
+static int
+cb_seek(void *arg, void *h, uint64_t offset, int whence)
+{
+ struct cb_file *cf = h;
+
+ if (cf->cf_isdir)
+ return (EINVAL);
+ if (lseek(cf->cf_u.fd, offset, whence) < 0)
+ return (errno);
+ return (0);
+}
+
+static int
+cb_stat(void *arg, void *h, int *mode, int *uid, int *gid, uint64_t *size)
+{
+ struct cb_file *cf = h;
+
+ *mode = cf->cf_stat.st_mode;
+ *uid = cf->cf_stat.st_uid;
+ *gid = cf->cf_stat.st_gid;
+ *size = cf->cf_stat.st_size;
+ return (0);
+}
+
+/*
+ * Disk image i/o callbacks
+ */
+
+static int
+cb_diskread(void *arg, int unit, uint64_t from, void *to, size_t size,
+ size_t *resid)
+{
+ ssize_t n;
+
+ if (unit < 0 || unit >= ndisks )
+ return (EIO);
+ n = pread(disk_fd[unit], to, size, from);
+ if (n < 0)
+ return (errno);
+ *resid = size - n;
+ return (0);
+}
+
+static int
+cb_diskioctl(void *arg, int unit, u_long cmd, void *data)
+{
+ struct stat sb;
+
+ if (unit < 0 || unit >= ndisks)
+ return (EBADF);
+
+ switch (cmd) {
+ case DIOCGSECTORSIZE:
+ *(u_int *)data = 512;
+ break;
+ case DIOCGMEDIASIZE:
+ if (fstat(disk_fd[unit], &sb) == 0)
+ *(off_t *)data = sb.st_size;
+ else
+ return (ENOTTY);
+ break;
+ default:
+ return (ENOTTY);
+ }
+
+ return (0);
+}
+
+/*
+ * Guest virtual machine i/o callbacks
+ */
+static int
+cb_copyin(void *arg, const void *from, uint64_t to, size_t size)
+{
+ char *ptr;
+
+ to &= 0x7fffffff;
+
+ ptr = vm_map_gpa(ctx, to, size);
+ if (ptr == NULL)
+ return (EFAULT);
+
+ memcpy(ptr, from, size);
+ return (0);
+}
+
+static int
+cb_copyout(void *arg, uint64_t from, void *to, size_t size)
+{
+ char *ptr;
+
+ from &= 0x7fffffff;
+
+ ptr = vm_map_gpa(ctx, from, size);
+ if (ptr == NULL)
+ return (EFAULT);
+
+ memcpy(to, ptr, size);
+ return (0);
+}
+
+static void
+cb_setreg(void *arg, int r, uint64_t v)
+{
+ int error;
+ enum vm_reg_name vmreg;
+
+ vmreg = VM_REG_LAST;
+
+ switch (r) {
+ case 4:
+ vmreg = VM_REG_GUEST_RSP;
+ rsp = v;
+ break;
+ default:
+ break;
+ }
+
+ if (vmreg == VM_REG_LAST) {
+ printf("test_setreg(%d): not implemented\n", r);
+ cb_exit(NULL, USERBOOT_EXIT_QUIT);
+ }
+
+ error = vm_set_register(ctx, BSP, vmreg, v);
+ if (error) {
+ perror("vm_set_register");
+ cb_exit(NULL, USERBOOT_EXIT_QUIT);
+ }
+}
+
+static void
+cb_setmsr(void *arg, int r, uint64_t v)
+{
+ int error;
+ enum vm_reg_name vmreg;
+
+ vmreg = VM_REG_LAST;
+
+ switch (r) {
+ case MSR_EFER:
+ vmreg = VM_REG_GUEST_EFER;
+ break;
+ default:
+ break;
+ }
+
+ if (vmreg == VM_REG_LAST) {
+ printf("test_setmsr(%d): not implemented\n", r);
+ cb_exit(NULL, USERBOOT_EXIT_QUIT);
+ }
+
+ error = vm_set_register(ctx, BSP, vmreg, v);
+ if (error) {
+ perror("vm_set_msr");
+ cb_exit(NULL, USERBOOT_EXIT_QUIT);
+ }
+}
+
+static void
+cb_setcr(void *arg, int r, uint64_t v)
+{
+ int error;
+ enum vm_reg_name vmreg;
+
+ vmreg = VM_REG_LAST;
+
+ switch (r) {
+ case 0:
+ vmreg = VM_REG_GUEST_CR0;
+ break;
+ case 3:
+ vmreg = VM_REG_GUEST_CR3;
+ cr3 = v;
+ break;
+ case 4:
+ vmreg = VM_REG_GUEST_CR4;
+ break;
+ default:
+ break;
+ }
+
+ if (vmreg == VM_REG_LAST) {
+ printf("test_setcr(%d): not implemented\n", r);
+ cb_exit(NULL, USERBOOT_EXIT_QUIT);
+ }
+
+ error = vm_set_register(ctx, BSP, vmreg, v);
+ if (error) {
+ perror("vm_set_cr");
+ cb_exit(NULL, USERBOOT_EXIT_QUIT);
+ }
+}
+
+static void
+cb_setgdt(void *arg, uint64_t base, size_t size)
+{
+ int error;
+
+ error = vm_set_desc(ctx, BSP, VM_REG_GUEST_GDTR, base, size - 1, 0);
+ if (error != 0) {
+ perror("vm_set_desc(gdt)");
+ cb_exit(NULL, USERBOOT_EXIT_QUIT);
+ }
+
+ gdtbase = base;
+}
+
+static void
+cb_exec(void *arg, uint64_t rip)
+{
+ int error;
+
+ if (cr3 == 0)
+ error = vm_setup_freebsd_registers_i386(ctx, BSP, rip, gdtbase,
+ rsp);
+ else
+ error = vm_setup_freebsd_registers(ctx, BSP, rip, cr3, gdtbase,
+ rsp);
+ if (error) {
+ perror("vm_setup_freebsd_registers");
+ cb_exit(NULL, USERBOOT_EXIT_QUIT);
+ }
+
+ cb_exit(NULL, 0);
+}
+
+/*
+ * Misc
+ */
+
+static void
+cb_delay(void *arg, int usec)
+{
+
+ usleep(usec);
+}
+
+static void
+cb_exit(void *arg, int v)
+{
+
+ tcsetattr(consout_fd, TCSAFLUSH, &oldterm);
+ exit(v);
+}
+
+static void
+cb_getmem(void *arg, uint64_t *ret_lowmem, uint64_t *ret_highmem)
+{
+
+ *ret_lowmem = vm_get_lowmem_size(ctx);
+ *ret_highmem = vm_get_highmem_size(ctx);
+}
+
+struct env {
+ const char *str; /* name=value */
+ SLIST_ENTRY(env) next;
+};
+
+static SLIST_HEAD(envhead, env) envhead;
+
+static void
+addenv(const char *str)
+{
+ struct env *env;
+
+ env = malloc(sizeof(struct env));
+ env->str = str;
+ SLIST_INSERT_HEAD(&envhead, env, next);
+}
+
+static const char *
+cb_getenv(void *arg, int num)
+{
+ int i;
+ struct env *env;
+
+ i = 0;
+ SLIST_FOREACH(env, &envhead, next) {
+ if (i == num)
+ return (env->str);
+ i++;
+ }
+
+ return (NULL);
+}
+
+static struct loader_callbacks cb = {
+ .getc = cb_getc,
+ .putc = cb_putc,
+ .poll = cb_poll,
+
+ .open = cb_open,
+ .close = cb_close,
+ .isdir = cb_isdir,
+ .read = cb_read,
+ .readdir = cb_readdir,
+ .seek = cb_seek,
+ .stat = cb_stat,
+
+ .diskread = cb_diskread,
+ .diskioctl = cb_diskioctl,
+
+ .copyin = cb_copyin,
+ .copyout = cb_copyout,
+ .setreg = cb_setreg,
+ .setmsr = cb_setmsr,
+ .setcr = cb_setcr,
+ .setgdt = cb_setgdt,
+ .exec = cb_exec,
+
+ .delay = cb_delay,
+ .exit = cb_exit,
+ .getmem = cb_getmem,
+
+ .getenv = cb_getenv,
+};
+
+static int
+altcons_open(char *path)
+{
+ struct stat sb;
+ int err;
+ int fd;
+
+ /*
+ * Allow stdio to be passed in so that the same string
+ * can be used for the bhyveload console and bhyve com-port
+ * parameters
+ */
+ if (!strcmp(path, "stdio"))
+ return (0);
+
+ err = stat(path, &sb);
+ if (err == 0) {
+ if (!S_ISCHR(sb.st_mode))
+ err = ENOTSUP;
+ else {
+ fd = open(path, O_RDWR | O_NONBLOCK);
+ if (fd < 0)
+ err = errno;
+ else
+ consin_fd = consout_fd = fd;
+ }
+ }
+
+ return (err);
+}
+
+static int
+disk_open(char *path)
+{
+ int err, fd;
+
+ if (ndisks >= NDISKS)
+ return (ERANGE);
+
+ err = 0;
+ fd = open(path, O_RDONLY);
+
+ if (fd > 0) {
+ disk_fd[ndisks] = fd;
+ ndisks++;
+ } else
+ err = errno;
+
+ return (err);
+}
+
+static void
+usage(void)
+{
+
+ fprintf(stderr,
+ "usage: %s [-S][-c <console-device>] [-d <disk-path>] [-e <name=value>]\n"
+ " %*s [-h <host-path>] [-m mem-size] <vmname>\n",
+ progname,
+ (int)strlen(progname), "");
+ exit(1);
+}
+
+int
+main(int argc, char** argv)
+{
+ char *loader;
+ void *h;
+ void (*func)(struct loader_callbacks *, void *, int, int);
+ uint64_t mem_size;
+ int opt, error, need_reinit, memflags;
+
+ progname = basename(argv[0]);
+
+ loader = NULL;
+
+ memflags = 0;
+ mem_size = 256 * MB;
+
+ consin_fd = STDIN_FILENO;
+ consout_fd = STDOUT_FILENO;
+
+ while ((opt = getopt(argc, argv, "Sc:d:e:h:l:m:")) != -1) {
+ switch (opt) {
+ case 'c':
+ error = altcons_open(optarg);
+ if (error != 0)
+ errx(EX_USAGE, "Could not open '%s'", optarg);
+ break;
+
+ case 'd':
+ error = disk_open(optarg);
+ if (error != 0)
+ errx(EX_USAGE, "Could not open '%s'", optarg);
+ break;
+
+ case 'e':
+ addenv(optarg);
+ break;
+
+ case 'h':
+ host_base = optarg;
+ break;
+
+ case 'l':
+ if (loader != NULL)
+ errx(EX_USAGE, "-l can only be given once");
+ loader = strdup(optarg);
+ if (loader == NULL)
+ err(EX_OSERR, "malloc");
+ break;
+
+ case 'm':
+ error = vm_parse_memsize(optarg, &mem_size);
+ if (error != 0)
+ errx(EX_USAGE, "Invalid memsize '%s'", optarg);
+ break;
+ case 'S':
+ memflags |= VM_MEM_F_WIRED;
+ break;
+ case '?':
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage();
+
+ vmname = argv[0];
+
+ need_reinit = 0;
+ error = vm_create(vmname);
+ if (error) {
+ if (errno != EEXIST) {
+ perror("vm_create");
+ exit(1);
+ }
+ need_reinit = 1;
+ }
+
+ ctx = vm_open(vmname);
+ if (ctx == NULL) {
+ perror("vm_open");
+ exit(1);
+ }
+
+ if (need_reinit) {
+ error = vm_reinit(ctx);
+ if (error) {
+ perror("vm_reinit");
+ exit(1);
+ }
+ }
+
+ vm_set_memflags(ctx, memflags);
+ error = vm_setup_memory(ctx, mem_size, VM_MMAP_ALL);
+ if (error) {
+ perror("vm_setup_memory");
+ exit(1);
+ }
+
+ if (loader == NULL) {
+ loader = strdup("/boot/userboot.so");
+ if (loader == NULL)
+ err(EX_OSERR, "malloc");
+ }
+ h = dlopen(loader, RTLD_LOCAL);
+ if (!h) {
+ printf("%s\n", dlerror());
+ free(loader);
+ return (1);
+ }
+ func = dlsym(h, "loader_main");
+ if (!func) {
+ printf("%s\n", dlerror());
+ free(loader);
+ return (1);
+ }
+
+ tcgetattr(consout_fd, &term);
+ oldterm = term;
+ cfmakeraw(&term);
+ term.c_cflag |= CLOCAL;
+
+ tcsetattr(consout_fd, TCSAFLUSH, &term);
+
+ addenv("smbios.bios.vendor=BHYVE");
+ addenv("boot_serial=1");
+
+ func(&cb, NULL, USERBOOT_VERSION_3, ndisks);
+
+ free(loader);
+ return (0);
+}
diff --git a/usr.sbin/binmiscctl/Makefile b/usr.sbin/binmiscctl/Makefile
new file mode 100644
index 0000000..f2c2870
--- /dev/null
+++ b/usr.sbin/binmiscctl/Makefile
@@ -0,0 +1,10 @@
+#
+# $FreeBSD$
+#
+
+.include <bsd.own.mk>
+
+PROG= binmiscctl
+MAN= binmiscctl.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/binmiscctl/Makefile.depend b/usr.sbin/binmiscctl/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/binmiscctl/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/binmiscctl/binmiscctl.8 b/usr.sbin/binmiscctl/binmiscctl.8
new file mode 100644
index 0000000..4bc9986
--- /dev/null
+++ b/usr.sbin/binmiscctl/binmiscctl.8
@@ -0,0 +1,309 @@
+.\"-
+.\" Copyright (c) 2013 Stacey D. Son
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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$
+.\"
+.\" Support for miscellaneous binary image activators
+.\"
+.Dd December 30, 2014
+.Dt BINMISCCTL 8
+.Os
+.Sh NAME
+.Nm binmiscctl
+.Nd manage binary image activators
+.Sh SYNOPSIS
+.Nm
+.Cm add
+.Ar name
+.Cm --interpreter
+.Ar path
+.Cm --magic
+.Ar magic
+.Cm --size
+.Ar size
+.Op Cm --mask Ar mask
+.Op Cm --offset Ar offset
+.Op Cm --set-enabled
+.Nm
+.Cm remove
+.Ar name
+.Nm
+.Cm disable
+.Ar name
+.Nm
+.Cm enable
+.Ar name
+.Nm
+.Cm lookup
+.Ar name
+.Nm
+.Cm list
+.Sh DESCRIPTION
+The
+.Nm
+utility
+is the management utility for configuring miscellaneous binaries image
+activators in the kernel.
+It allows adding, deleting, disabling,
+enabling, and looking up interpreters.
+Also, all the interpreters can
+be listed.
+.Pp
+The first argument on the command line indicates the operation to be
+performed.
+Operation must be one of the following:
+.Bl -tag -width indent
+.It Xo
+.Cm add
+.Ar name
+.Cm --interpreter
+.Ar path
+.Cm --magic
+.Ar magic
+.Cm --size
+.Ar size
+.Op Cm --mask Ar mask
+.Op Cm --offset Ar offset
+.Op Cm --set-enabled
+.Xc
+Add a new activator entry in the kernel.
+You must specify a
+unique
+.Ar name,
+interpreter path and its arguments
+.Ar path,
+header
+.Ar magic
+bytes that uniquely identify a suitable binary for the activator,
+and the
+.Ar size
+of the
+.Ar magic
+in bytes.
+.Pp
+Optionally you may specify a
+.Ar mask
+to do a bitwise AND with the header bytes.
+This effectively allows you to ignore fields in the binary header that
+do not uniquely indentify the binary file's type.
+.Pp
+An
+.Ar offset
+may be specified for the magic bytes using the
+.Cm --offset
+option.
+By default the
+.Ar offset
+is zero.
+.Pp
+To enable the activator entry the
+.Cm --set-enabled
+option is used.
+The activator default state is disabled.
+.Pp
+The interpreter
+.Ar path
+may also contain arguments for the interpreter including
+.Ar #a
+which gets replaced by the old
+.Dv argv0
+value in the interpreter string.
+.It Cm remove Ar name
+Remove the activator entry identified with
+.Ar name .
+.It Cm disable Ar name
+Disable the activator entry identified with
+.Ar name .
+.It Cm enable Ar name
+Enable the activator entry identified with
+.Ar name .
+.It Cm lookup Ar name
+Look up and print out the activator entry identified with
+.Ar name .
+.It Cm list
+Take a snapshot and print all the activator entries currently configured.
+.El
+.Sh EXAMPLES
+Add an image activator to run the LLVM interpreter (lli) on bitcode
+compiled files:
+.Bd -ragged -offset indent
+# binmiscctl add llvmbc --interpreter ''/usr/bin/lli --fake-argv0=#a''
+--magic ''BC\\xc0\\xde'' --size 4 --set-enabled
+.Ed
+.Pp
+.Ar #a
+is replaced with the old
+.Dv argv0
+value so that 'lli' can fake its
+.Dv argv0 .
+Set its state to enabled.
+.Pp
+Set the state of the
+.Ar llvmbc
+image activator to disabled:
+.Dl # binmiscctl disable llvmbc
+.Pp
+Set the state of the
+.Ar llvmbc
+image activator to enabled:
+.Dl # binmiscctl enable llvmbc
+.Pp
+Delete the
+.Ar llvmbc
+image activator:
+.Dl # binmiscctl remove llvmbc
+.Pp
+Look up and list the record for the
+.Ar llvmbc
+image activator:
+.Dl # binmiscctl lookup llvmbc
+.Pp
+Add QEMU bsd-user program as an image activator for ARM AARCH64 binaries:
+.Bd -literal -offset indent
+# binmiscctl add arm64 \e
+ --interpreter "/usr/local/bin/qemu-aarch64-static" \e
+ --magic "\ex7f\ex45\ex4c\ex46\ex02\ex01\ex01\ex00\ex00\ex00\e
+ \ex00\ex00\ex00\ex00\ex00\ex00\ex02\ex00\exb7\ex00" \e
+ --mask "\exff\exff\exff\exff\exff\exff\exff\ex00\exff\exff\e
+ \exff\exff\exff\exff\exff\exff\exfe\exff\exff\exff" \e
+ --size 20 --set-enabled
+.Ed
+.Pp
+Add QEMU bsd-user program as an image activator for ARM little-endian binaries:
+.Bd -literal -offset indent
+# binmiscctl add armelf \e
+ --interpreter "/usr/local/bin/qemu-arm-static" \e
+ --magic "\ex7f\ex45\ex4c\ex46\ex01\ex01\ex01\ex00\ex00\ex00\e
+ \ex00\ex00\ex00\ex00\ex00\ex00\ex02\ex00\ex28\ex00" \e
+ --mask "\exff\exff\exff\exff\exff\exff\exff\ex00\exff\exff\e
+ \exff\exff\exff\exff\exff\exff\exfe\exff\exff\exff" \e
+ --size 20 --set-enabled
+.Ed
+.Pp
+Add QEMU bsd-user program as an image activator for ARM big-endian binaries:
+.Bd -literal -offset indent
+# binmiscctl add armebelf \e
+ --interpreter "/usr/local/bin/qemu-arm-static" \e
+ --magic "\ex7f\ex45\ex4c\ex46\ex01\ex02\ex01\ex00\ex00\ex00\e
+ \ex00\ex00\ex00\ex00\ex00\ex00\ex00\ex02\ex00\ex28" \e
+ --mask "\exff\exff\exff\exff\exff\exff\exff\ex00\exff\exff\e
+ \exff\exff\exff\exff\exff\exff\exff\exfe\exff\exff" \e
+ --size 20 --set-enabled
+.Ed
+.Pp
+Add QEMU bsd-user program as an image activator for MIPS32 binaries:
+.Bd -literal -offset indent
+# binmiscctl add mips32 \e
+ --interpreter "/usr/local/bin/qemu-mips-static" \e
+ --magic "\ex7f\ex45\ex4c\ex46\ex01\ex02\ex01\ex00\ex00\ex00\e
+ \ex00\ex00\ex00\ex00\ex00\ex00\ex00\ex02\ex00\ex08" \e
+ --mask "\exff\exff\exff\exff\exff\exff\exff\ex00\exff\exff\e
+ \exff\exff\exff\exff\exff\exff\exff\exfe\exff\exff" \e
+ --size 20 --set-enabled
+.Ed
+.Pp
+Add QEMU bsd-user program as an image activator for MIPS64 binaries:
+.Bd -literal -offset indent
+# binmiscctl add mips64 \e
+ --interpreter "/usr/local/bin/qemu-mips64-static" \e
+ --magic "\ex7f\ex45\ex4c\ex46\ex02\ex02\ex01\ex00\ex00\ex00\e
+ \ex00\ex00\ex00\ex00\ex00\ex00\ex00\ex02\ex00\ex08" \e
+ --mask "\exff\exff\exff\exff\exff\exff\exff\ex00\exff\exff\e
+ \exff\exff\exff\exff\exff\exff\exff\exfe\exff\exff" \e
+ --size 20 --set-enabled
+.Ed
+.Pp
+Add QEMU bsd-user program as an image activator for PowerPC binaries:
+.Bd -literal -offset indent
+# binmiscctl add powerpc \e
+ --interpreter "/usr/local/bin/qemu-ppc-static" \e
+ --magic "\ex7f\ex45\ex4c\ex46\ex01\ex02\ex01\ex00\ex00\ex00\e
+ \ex00\ex00\ex00\ex00\ex00\ex00\ex00\ex02\ex00\ex14" \e
+ --mask "\exff\exff\exff\exff\exff\exff\exff\ex00\exff\exff\e
+ \exff\exff\exff\exff\exff\exff\exff\exfe\exff\exff" \e
+ --size 20 --set-enabled
+.Ed
+.Pp
+Add QEMU bsd-user program as an image activator for PowerPC64 binaries:
+.Bd -literal -offset indent
+# binmiscctl add powerpc64 \e
+ --interpreter "/usr/local/bin/qemu-ppc64-static" \e
+ --magic "\ex7f\ex45\ex4c\ex46\ex01\ex02\ex01\ex00\ex00\ex00\e
+ \ex00\ex00\ex00\ex00\ex00\ex00\ex00\ex02\ex00\ex15" \e
+ --mask "\exff\exff\exff\exff\exff\exff\exff\ex00\exff\exff\e
+ \exff\exff\exff\exff\exff\exff\exff\exfe\exff\exff" \e
+ --size 20 --set-enabled
+.Ed
+.Pp
+Add QEMU bsd-user program as an image activator for SPARC64 binaries:
+.Bd -literal -offset indent
+# binmiscctl add sparc64 \e
+ --interpreter "/usr/local/bin/qemu-sparc64-static" \e
+ --magic "\ex7f\ex45\ex4c\ex46\ex02\ex02\ex01\ex00\ex00\ex00\e
+ \ex00\ex00\ex00\ex00\ex00\ex00\ex00\ex02\ex00\ex2b" \e
+ --mask "\exff\exff\exff\exff\exff\exff\exff\ex00\exff\exff\e
+ \exff\exff\exff\exff\exff\exff\exff\exfe\exff\exff" \e
+ --size 20 --set-enabled
+.Ed
+.Pp
+.Ss "Create and use an ARMv6 chroot on an AMD64 host"
+Use an existing source tree to build a chroot host with architecture
+overrides:
+.Bd -literal
+D=/path/to/chroot
+cd /usr/src
+mkdir -p $D
+make world TARGET=arm TARGET_ARCH=armv6 DESTDIR=$D
+make distribution TARGET=arm TARGET_ARCH=armv6 DESTDIR=$D
+.Ed
+.Pp
+With
+.Pa emulators/qemu-user-static
+from the
+.Fx
+Ports Collection, the emulator must be copied into the jail path
+specified in the binmiscctl command.
+Using the example above:
+.Bd -literal
+mkdir $D/usr/local/bin
+cp /usr/local/bin/qemu-arm-static $D/usr/local/bin
+.Ed
+.Pp
+Now the user can chroot into the environment normally, as root:
+.Bd -literal
+chroot $D
+.Ed
+.Sh SEE ALSO
+.Xr lli 1 ,
+.Xr execve 2 ,
+.Xr jail 8
+.Sh HISTORY
+The
+.Cm binmiscctl
+command was added in
+.Fx 10.1 .
+It was developed to support the imgact_binmisc kernel module.
+.Sh AUTHORS
+Stacey D Son
diff --git a/usr.sbin/binmiscctl/binmiscctl.c b/usr.sbin/binmiscctl/binmiscctl.c
new file mode 100644
index 0000000..5ab82e4
--- /dev/null
+++ b/usr.sbin/binmiscctl/binmiscctl.c
@@ -0,0 +1,510 @@
+/*-
+ * Copyright (c) 2013 Stacey D. Son
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/imgact_binmisc.h>
+#include <sys/linker.h>
+#include <sys/sysctl.h>
+
+enum cmd {
+ CMD_ADD = 0,
+ CMD_REMOVE,
+ CMD_DISABLE,
+ CMD_ENABLE,
+ CMD_LOOKUP,
+ CMD_LIST,
+};
+
+extern char *__progname;
+
+typedef int (*cmd_func_t)(int argc, char *argv[], ximgact_binmisc_entry_t *xbe);
+
+int add_cmd(int argc, char *argv[], ximgact_binmisc_entry_t *xbe);
+int name_cmd(int argc, char *argv[], ximgact_binmisc_entry_t *xbe);
+int noname_cmd(int argc, char *argv[], ximgact_binmisc_entry_t *xbe);
+
+static const struct {
+ const int token;
+ const char *name;
+ cmd_func_t func;
+ const char *desc;
+ const char *args;
+} cmds[] = {
+ {
+ CMD_ADD,
+ "add",
+ add_cmd,
+ "Add a new binary image activator (requires 'root' privilege)",
+ "<name> --interpreter <path_and_arguments> \\\n"
+ "\t\t--magic <magic_bytes> [--mask <mask_bytes>] \\\n"
+ "\t\t--size <magic_size> [--offset <magic_offset>] \\\n"
+ "\t\t[--set-enabled]"
+ },
+ {
+ CMD_REMOVE,
+ "remove",
+ name_cmd,
+ "Remove a binary image activator (requires 'root' privilege)",
+ "<name>"
+ },
+ {
+ CMD_DISABLE,
+ "disable",
+ name_cmd,
+ "Disable a binary image activator (requires 'root' privilege)",
+ "<name>"
+ },
+ {
+ CMD_ENABLE,
+ "enable",
+ name_cmd,
+ "Enable a binary image activator (requires 'root' privilege)",
+ "<name>"
+ },
+ {
+ CMD_LOOKUP,
+ "lookup",
+ name_cmd,
+ "Lookup a binary image activator",
+ "<name>"
+ },
+ {
+ CMD_LIST,
+ "list",
+ noname_cmd,
+ "List all the binary image activators",
+ ""
+ },
+};
+
+static const struct option
+add_opts[] = {
+ { "set-enabled", no_argument, NULL, 'e' },
+ { "interpreter", required_argument, NULL, 'i' },
+ { "mask", required_argument, NULL, 'M' },
+ { "magic", required_argument, NULL, 'm' },
+ { "offset", required_argument, NULL, 'o' },
+ { "size", required_argument, NULL, 's' },
+ { NULL, 0, NULL, 0 }
+};
+
+static char const *cmd_sysctl_name[] = {
+ IBE_SYSCTL_NAME_ADD,
+ IBE_SYSCTL_NAME_REMOVE,
+ IBE_SYSCTL_NAME_DISABLE,
+ IBE_SYSCTL_NAME_ENABLE,
+ IBE_SYSCTL_NAME_LOOKUP,
+ IBE_SYSCTL_NAME_LIST
+};
+
+static void
+usage(const char *format, ...)
+{
+ va_list args;
+ size_t i;
+ int error = 0;
+
+ va_start(args, format);
+ if (format) {
+ vfprintf(stderr, format, args);
+ error = -1;
+ }
+ va_end(args);
+ fprintf(stderr, "\n");
+ fprintf(stderr, "usage: %s command [args...]\n\n", __progname);
+
+ for(i = 0; i < ( sizeof (cmds) / sizeof (cmds[0])); i++) {
+ fprintf(stderr, "%s:\n", cmds[i].desc);
+ fprintf(stderr, "\t%s %s %s\n\n", __progname, cmds[i].name,
+ cmds[i].args);
+ }
+
+ exit (error);
+}
+
+static void
+fatal(const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ if (format)
+ vfprintf(stderr, format, args);
+ fprintf(stderr, "\n");
+
+ exit(-1);
+}
+
+static void
+getoptstr(char *str, size_t size, const char *argname)
+{
+ if (strlen(optarg) > size)
+ usage("'%s' too large", argname);
+ strlcpy(str, optarg, size);
+}
+
+static void
+printxbe(ximgact_binmisc_entry_t *xbe)
+{
+ uint32_t i, flags = xbe->xbe_flags;
+
+ if (xbe->xbe_version != IBE_VERSION) {
+ fprintf(stderr, "Error: XBE version mismatch\n");
+ return;
+ }
+
+ printf("name: %s\n", xbe->xbe_name);
+ printf("interpreter: %s\n", xbe->xbe_interpreter);
+ printf("flags: %s%s\n", (flags & IBF_ENABLED) ? "ENABLED " : "",
+ (flags & IBF_USE_MASK) ? "USE_MASK " : "");
+ printf("magic size: %u\n", xbe->xbe_msize);
+ printf("magic offset: %u\n", xbe->xbe_moffset);
+
+ printf("magic: ");
+ for(i = 0; i < xbe->xbe_msize; i++) {
+ if (i && !(i % 12))
+ printf("\n ");
+ else
+ if (i && !(i % 4))
+ printf(" ");
+ printf("0x%02x ", xbe->xbe_magic[i]);
+ }
+ printf("\n");
+
+ if (flags & IBF_USE_MASK) {
+ printf("mask: ");
+ for(i = 0; i < xbe->xbe_msize; i++) {
+ if (i && !(i % 12))
+ printf("\n ");
+ else
+ if (i && !(i % 4))
+ printf(" ");
+ printf("0x%02x ", xbe->xbe_mask[i]);
+ }
+ printf("\n");
+ }
+
+ printf("\n");
+}
+
+static int
+demux_cmd(__unused int argc, char *const argv[])
+{
+ size_t i;
+
+ optind = 1;
+ optreset = 1;
+
+ for(i = 0; i < ( sizeof (cmds) / sizeof (cmds[0])); i++) {
+ if (!strcasecmp(cmds[i].name, argv[0])) {
+ return (i);
+ }
+ }
+
+ /* Unknown command */
+ return (-1);
+}
+
+static int
+strlit2bin_cpy(uint8_t *d, char *s, size_t size)
+{
+ int c;
+ size_t cnt = 0;
+
+ while((c = *s++) != '\0') {
+ if (c == '\\') {
+ /* Do '\' escapes. */
+ switch (*s) {
+ case '\\':
+ *d++ = '\\';
+ break;
+
+ case 'x':
+ s++;
+ c = toupper(*s++);
+ *d = (c - (isdigit(c) ? '0' : ('A' - 10))) << 4;
+ c = toupper(*s++);
+ *d++ |= c - (isdigit(c) ? '0' : ('A' - 10));
+ break;
+
+ default:
+ return (-1);
+ }
+ } else
+ *d++ = c;
+
+ if (++cnt > size)
+ return (-1);
+ }
+
+ return (cnt);
+}
+
+int
+add_cmd(__unused int argc, char *argv[], ximgact_binmisc_entry_t *xbe)
+{
+ int ch;
+ char *magic = NULL, *mask = NULL;
+ int sz;
+
+ if (strlen(argv[0]) > IBE_NAME_MAX)
+ usage("'%s' string length longer than IBE_NAME_MAX (%d)",
+ IBE_NAME_MAX);
+ strlcpy(&xbe->xbe_name[0], argv[0], IBE_NAME_MAX);
+
+ while ((ch = getopt_long(argc, argv, "ei:m:M:o:s:", add_opts, NULL))
+ != -1) {
+
+ switch(ch) {
+ case 'i':
+ getoptstr(xbe->xbe_interpreter, IBE_INTERP_LEN_MAX,
+ "interpreter");
+ break;
+
+ case 'm':
+ magic = strdup(optarg);
+ break;
+
+ case 'M':
+ mask = strdup(optarg);
+ xbe->xbe_flags |= IBF_USE_MASK;
+ break;
+
+ case 'e':
+ xbe->xbe_flags |= IBF_ENABLED;
+ break;
+
+ case 'o':
+ xbe->xbe_moffset = atol(optarg);
+ break;
+
+ case 's':
+ xbe->xbe_msize = atol(optarg);
+ if (xbe->xbe_msize == 0 ||
+ xbe->xbe_msize > IBE_MAGIC_MAX)
+ usage("Error: Not valid '--size' value. "
+ "(Must be > 0 and < %u.)\n",
+ xbe->xbe_msize);
+ break;
+
+ default:
+ usage("Unknown argument: '%c'", ch);
+ }
+ }
+
+ if (xbe->xbe_msize == 0) {
+ if (NULL != magic)
+ free(magic);
+ if (NULL != mask)
+ free(mask);
+ usage("Error: Missing '--size' argument");
+ }
+
+ if (NULL != magic) {
+ if (xbe->xbe_msize == 0) {
+ if (magic)
+ free(magic);
+ if (mask)
+ free(mask);
+ usage("Error: Missing magic size argument");
+ }
+ sz = strlit2bin_cpy(xbe->xbe_magic, magic, IBE_MAGIC_MAX);
+ free(magic);
+ if (sz == -1 || (uint32_t)sz != xbe->xbe_msize) {
+ if (mask)
+ free(mask);
+ usage("Error: invalid magic argument");
+ }
+ if (mask) {
+ sz = strlit2bin_cpy(xbe->xbe_mask, mask, IBE_MAGIC_MAX);
+ free(mask);
+ if (sz == -1 || (uint32_t)sz != xbe->xbe_msize)
+ usage("Error: invalid mask argument");
+ }
+ } else {
+ if (mask)
+ free(mask);
+ usage("Error: Missing magic argument");
+ }
+
+ if (!strnlen(xbe->xbe_interpreter, IBE_INTERP_LEN_MAX)) {
+ usage("Error: Missing 'interpreter' argument");
+ }
+
+ return (0);
+}
+
+int
+name_cmd(int argc, char *argv[], ximgact_binmisc_entry_t *xbe)
+{
+ if (argc == 0)
+ usage("Required argument missing\n");
+ if (strlen(argv[0]) > IBE_NAME_MAX)
+ usage("'%s' string length longer than IBE_NAME_MAX (%d)",
+ IBE_NAME_MAX);
+ strlcpy(&xbe->xbe_name[0], argv[0], IBE_NAME_MAX);
+
+ return (0);
+}
+
+int
+noname_cmd(__unused int argc, __unused char *argv[],
+ __unused ximgact_binmisc_entry_t *xbe)
+{
+
+ return (0);
+}
+
+int
+main(int argc, char **argv)
+{
+ int error = 0, cmd = -1;
+ ximgact_binmisc_entry_t xbe_in, *xbe_inp = NULL;
+ ximgact_binmisc_entry_t xbe_out, *xbe_outp = NULL;
+ size_t xbe_in_sz = 0;
+ size_t xbe_out_sz = 0, *xbe_out_szp = NULL;
+ uint32_t i;
+
+ if (kldfind(KMOD_NAME) == -1) {
+ if (kldload(KMOD_NAME) == -1)
+ fatal("Can't load %s kernel module: %s",
+ KMOD_NAME, strerror(errno));
+ }
+
+ bzero(&xbe_in, sizeof(xbe_in));
+ bzero(&xbe_out, sizeof(xbe_out));
+ xbe_in.xbe_version = IBE_VERSION;
+
+ if (argc < 2)
+ usage("Error: requires at least one argument");
+
+ argc--, argv++;
+ cmd = demux_cmd(argc, argv);
+ if (cmd == -1)
+ usage("Error: Unknown command \"%s\"", argv[0]);
+ argc--, argv++;
+
+ error = (*cmds[cmd].func)(argc, argv, &xbe_in);
+ if (error)
+ usage("Can't parse command-line for '%s' command",
+ cmds[cmd].name);
+
+ if (cmd != CMD_LIST) {
+ xbe_inp = &xbe_in;
+ xbe_in_sz = sizeof(xbe_in);
+ } else
+ xbe_out_szp = &xbe_out_sz;
+ if (cmd == CMD_LOOKUP) {
+ xbe_out_sz = sizeof(xbe_out);
+ xbe_outp = &xbe_out;
+ xbe_out_szp = &xbe_out_sz;
+ }
+
+ error = sysctlbyname(cmd_sysctl_name[cmd], xbe_outp, xbe_out_szp,
+ xbe_inp, xbe_in_sz);
+
+ if (error)
+ switch(errno) {
+ case EINVAL:
+ usage("Invalid interpreter name or --interpreter, "
+ "--magic, --mask, or --size argument value");
+ break;
+
+ case EEXIST:
+ usage("'%s' is not unique in activator list",
+ xbe_in.xbe_name);
+ break;
+
+ case ENOENT:
+ usage("'%s' is not found in activator list",
+ xbe_in.xbe_name);
+ break;
+
+ case ENOSPC:
+ fatal("Fatal: no more room in the activator list "
+ "(limited to %d enties)", IBE_MAX_ENTRIES);
+ break;
+
+ case EPERM:
+ usage("Insufficient privileges for '%s' command",
+ cmds[cmd].name);
+ break;
+
+ default:
+ fatal("Fatal: sysctlbyname() returned: %s",
+ strerror(errno));
+ break;
+ }
+
+
+ if (cmd == CMD_LOOKUP)
+ printxbe(xbe_outp);
+
+ if (cmd == CMD_LIST && xbe_out_sz > 0) {
+ xbe_outp = malloc(xbe_out_sz);
+ if (!xbe_outp)
+ fatal("Fatal: out of memory");
+ while(1) {
+ size_t osize = xbe_out_sz;
+ error = sysctlbyname(cmd_sysctl_name[cmd], xbe_outp,
+ &xbe_out_sz, NULL, 0);
+
+ if (error == -1 && errno == ENOMEM &&
+ xbe_out_sz == osize) {
+ /*
+ * Buffer too small. Increase it by one
+ * entry.
+ */
+ xbe_out_sz += sizeof(xbe_out);
+ xbe_outp = realloc(xbe_outp, xbe_out_sz);
+ if (!xbe_outp)
+ fatal("Fatal: out of memory");
+ } else
+ break;
+ }
+ if (error) {
+ free(xbe_outp);
+ fatal("Fatal: %s", strerror(errno));
+ }
+ for(i = 0; i < (xbe_out_sz / sizeof(xbe_out)); i++)
+ printxbe(&xbe_outp[i]);
+ }
+
+ return (error);
+}
diff --git a/usr.sbin/bluetooth/Makefile b/usr.sbin/bluetooth/Makefile
new file mode 100644
index 0000000..1737107
--- /dev/null
+++ b/usr.sbin/bluetooth/Makefile
@@ -0,0 +1,26 @@
+# $Id: Makefile,v 1.5 2003/09/08 02:28:35 max Exp $
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+SUBDIR= \
+ bt3cfw \
+ btpand \
+ hccontrol \
+ hcsecd \
+ hcseriald \
+ l2control \
+ l2ping \
+ rfcomm_pppd \
+ sdpcontrol \
+ sdpd
+
+.if ${MK_USB} != "no"
+SUBDIR+= ath3kfw
+SUBDIR+= bcmfw
+SUBDIR+= bthidcontrol
+SUBDIR+= bthidd
+.endif
+
+.include <bsd.subdir.mk>
+
diff --git a/usr.sbin/bluetooth/Makefile.inc b/usr.sbin/bluetooth/Makefile.inc
new file mode 100644
index 0000000..c0e05cf
--- /dev/null
+++ b/usr.sbin/bluetooth/Makefile.inc
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+.include "${.CURDIR}/../../Makefile.inc"
+
diff --git a/usr.sbin/bluetooth/ath3kfw/Makefile b/usr.sbin/bluetooth/ath3kfw/Makefile
new file mode 100644
index 0000000..26ce06e
--- /dev/null
+++ b/usr.sbin/bluetooth/ath3kfw/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= ath3kfw
+MAN= ath3kfw.8
+LIBADD+= usb
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bluetooth/ath3kfw/Makefile.depend b/usr.sbin/bluetooth/ath3kfw/Makefile.depend
new file mode 100644
index 0000000..3ddb39c
--- /dev/null
+++ b/usr.sbin/bluetooth/ath3kfw/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libthr \
+ lib/libusb \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bluetooth/ath3kfw/ath3kfw.8 b/usr.sbin/bluetooth/ath3kfw/ath3kfw.8
new file mode 100644
index 0000000..3a37343
--- /dev/null
+++ b/usr.sbin/bluetooth/ath3kfw/ath3kfw.8
@@ -0,0 +1,78 @@
+.\" Copyright (c) 2010 Maksim Yevmenkin <m_evmenkin@yahoo.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 November 9, 2010
+.Dt ATH3KFW 8
+.Os
+.Sh NAME
+.Nm ath3kfw
+.Nd firmware download utility for Atheros AR3011 chip based Bluetooth USB devices
+.Sh SYNOPSIS
+.Nm
+.Fl d Ar device_name
+.Fl f Ar firmware_file_name
+.Nm
+.Fl h
+.Sh DESCRIPTION
+The
+.Nm
+utility downloads the specified firmware file to the specified
+.Xr ugen 4
+device.
+.Pp
+This utility will
+.Em only
+work with Atheros AR3011 chip based Bluetooth USB devices.
+The identification is currently based on USB vendor ID/product ID pair.
+The vendor ID should be 0x0cf3
+.Pq Dv USB_VENDOR_ATHEROS2
+and the product ID should be 0x3000.
+.Pp
+Firmware files ath3k-1.fw and ath3k-2.fw can be obtained from the
+linux-firmware RPM.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl d Ar device_name
+Specify
+.Xr ugen 4
+device name.
+.It Fl f Ar firmware_file_name
+Specify firmware file name for download.
+.It Fl h
+Display usage message and exit.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr libusb 3 ,
+.Xr ugen 4 ,
+.Xr devd 8
+.Sh AUTHORS
+.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com
+.Sh BUGS
+Most likely.
+Please report if found.
diff --git a/usr.sbin/bluetooth/ath3kfw/ath3kfw.c b/usr.sbin/bluetooth/ath3kfw/ath3kfw.c
new file mode 100644
index 0000000..37191d1
--- /dev/null
+++ b/usr.sbin/bluetooth/ath3kfw/ath3kfw.c
@@ -0,0 +1,297 @@
+/*
+ * ath3kfw.c
+ */
+
+/*-
+ * Copyright (c) 2010 Maksim Yevmenkin <m_evmenkin@yahoo.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$
+ */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libusb20_desc.h>
+#include <libusb20.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#define ATH3KFW "ath3kfw"
+#define ATH3KFW_VENDOR_ID 0x0cf3
+#define ATH3KFW_PRODUCT_ID 0x3000
+#define ATH3KFW_FW "/usr/local/etc/ath3k-1.fw"
+#define ATH3KFW_BULK_EP 0x02
+#define ATH3KFW_REQ_DFU_DNLOAD 1
+#define ATH3KFW_MAX_BSIZE 4096
+
+static int parse_ugen_name (char const *ugen, uint8_t *bus,
+ uint8_t *addr);
+static int find_device (struct libusb20_backend *be,
+ uint8_t bus, uint8_t addr,
+ struct libusb20_device **dev);
+static int download_firmware (struct libusb20_device *dev,
+ char const *firmware);
+static void usage (void);
+
+static int vendor_id = ATH3KFW_VENDOR_ID;
+static int product_id = ATH3KFW_PRODUCT_ID;
+
+/*
+ * Firmware downloader for Atheros AR3011 based USB Bluetooth devices
+ */
+
+int
+main(int argc, char **argv)
+{
+ uint8_t bus, addr;
+ char const *firmware;
+ struct libusb20_backend *be;
+ struct libusb20_device *dev;
+ int n;
+
+ openlog(ATH3KFW, LOG_NDELAY|LOG_PERROR|LOG_PID, LOG_USER);
+
+ bus = 0;
+ addr = 0;
+ firmware = ATH3KFW_FW;
+
+ while ((n = getopt(argc, argv, "d:f:hp:v:")) != -1) {
+ switch (n) {
+ case 'd': /* ugen device name */
+ if (parse_ugen_name(optarg, &bus, &addr) < 0)
+ usage();
+ break;
+
+ case 'f': /* firmware file */
+ firmware = optarg;
+ break;
+ case 'p': /* product id */
+ product_id = strtol(optarg, NULL, 0);
+ break;
+ case 'v': /* vendor id */
+ vendor_id = strtol(optarg, NULL, 0);
+ break;
+ case 'h':
+ default:
+ usage();
+ break;
+ /* NOT REACHED */
+ }
+ }
+
+ be = libusb20_be_alloc_default();
+ if (be == NULL) {
+ syslog(LOG_ERR, "libusb20_be_alloc_default() failed");
+ return (-1);
+ }
+
+ if (find_device(be, bus, addr, &dev) < 0) {
+ syslog(LOG_ERR, "ugen%d.%d is not recognized as " \
+ "Atheros AR3011 based device " \
+ "(possibly caused by lack of permissions)", bus, addr);
+ return (-1);
+ }
+
+ if (download_firmware(dev, firmware) < 0) {
+ syslog(LOG_ERR, "could not download %s firmare to ugen%d.%d",
+ firmware, bus, addr);
+ return (-1);
+ }
+
+ libusb20_be_free(be);
+ closelog();
+
+ return (0);
+}
+
+/*
+ * Parse ugen name and extract device's bus and address
+ */
+
+static int
+parse_ugen_name(char const *ugen, uint8_t *bus, uint8_t *addr)
+{
+ char *ep;
+
+ if (strncmp(ugen, "ugen", 4) != 0)
+ return (-1);
+
+ *bus = (uint8_t) strtoul(ugen + 4, &ep, 10);
+ if (*ep != '.')
+ return (-1);
+
+ *addr = (uint8_t) strtoul(ep + 1, &ep, 10);
+ if (*ep != '\0')
+ return (-1);
+
+ return (0);
+}
+
+/*
+ * Find USB device
+ */
+
+static int
+find_device(struct libusb20_backend *be, uint8_t bus, uint8_t addr,
+ struct libusb20_device **dev)
+{
+ struct LIBUSB20_DEVICE_DESC_DECODED *desc;
+
+ *dev = NULL;
+
+ while ((*dev = libusb20_be_device_foreach(be, *dev)) != NULL) {
+ if (libusb20_dev_get_bus_number(*dev) != bus ||
+ libusb20_dev_get_address(*dev) != addr)
+ continue;
+
+ desc = libusb20_dev_get_device_desc(*dev);
+ if (desc == NULL)
+ continue;
+
+ if (desc->idVendor != vendor_id ||
+ desc->idProduct != product_id)
+ continue;
+
+ break;
+ }
+
+ return ((*dev == NULL)? -1 : 0);
+}
+
+/*
+ * Download firmware
+ */
+
+static int
+download_firmware(struct libusb20_device *dev, char const *firmware)
+{
+ struct libusb20_transfer *bulk;
+ struct LIBUSB20_CONTROL_SETUP_DECODED req;
+ int fd, n, error;
+ uint8_t buf[ATH3KFW_MAX_BSIZE];
+
+ error = -1;
+
+ if (libusb20_dev_open(dev, 1) != 0) {
+ syslog(LOG_ERR, "libusb20_dev_open() failed");
+ return (error);
+ }
+
+ if ((bulk = libusb20_tr_get_pointer(dev, 0)) == NULL) {
+ syslog(LOG_ERR, "libusb20_tr_get_pointer() failed");
+ goto out;
+ }
+
+ if (libusb20_tr_open(bulk, ATH3KFW_MAX_BSIZE, 1, ATH3KFW_BULK_EP) != 0) {
+ syslog(LOG_ERR, "libusb20_tr_open(%d, 1, %d) failed",
+ ATH3KFW_MAX_BSIZE, ATH3KFW_BULK_EP);
+ goto out;
+ }
+
+ if ((fd = open(firmware, O_RDONLY)) < 0) {
+ syslog(LOG_ERR, "open(%s) failed. %s",
+ firmware, strerror(errno));
+ goto out1;
+ }
+
+ n = read(fd, buf, 20);
+ if (n != 20) {
+ syslog(LOG_ERR, "read(%s, 20) failed. %s",
+ firmware, strerror(errno));
+ goto out2;
+ }
+
+ LIBUSB20_INIT(LIBUSB20_CONTROL_SETUP, &req);
+ req.bmRequestType = LIBUSB20_REQUEST_TYPE_VENDOR;
+ req.bRequest = ATH3KFW_REQ_DFU_DNLOAD;
+ req.wLength = 20;
+
+ if (libusb20_dev_request_sync(dev, &req, buf, NULL, 5000, 0) != 0) {
+ syslog(LOG_ERR, "libusb20_dev_request_sync() failed");
+ goto out2;
+ }
+
+ for (;;) {
+ n = read(fd, buf, sizeof(buf));
+ if (n < 0) {
+ syslog(LOG_ERR, "read(%s, %d) failed. %s",
+ firmware, (int) sizeof(buf), strerror(errno));
+ goto out2;
+ }
+ if (n == 0)
+ break;
+
+ libusb20_tr_setup_bulk(bulk, buf, n, 3000);
+ libusb20_tr_start(bulk);
+
+ while (libusb20_dev_process(dev) == 0) {
+ if (libusb20_tr_pending(bulk) == 0)
+ break;
+
+ libusb20_dev_wait_process(dev, -1);
+ }
+
+ if (libusb20_tr_get_status(bulk) != 0) {
+ syslog(LOG_ERR, "bulk transfer failed with status %d",
+ libusb20_tr_get_status(bulk));
+ goto out2;
+ }
+ }
+
+ error = 0;
+out2:
+ close(fd);
+out1:
+ libusb20_tr_close(bulk);
+out:
+ libusb20_dev_close(dev);
+
+ return (error);
+}
+
+/*
+ * Display usage and exit
+ */
+
+static void
+usage(void)
+{
+ printf(
+"Usage: %s -d ugenX.Y -f firmware_file\n"
+"Usage: %s -h\n" \
+"Where:\n" \
+"\t-d ugenX.Y ugen device name\n" \
+"\t-f firmware image firmware image file name for download\n" \
+"\t-v vendor_id vendor id\n" \
+"\t-p vendor_id product id\n" \
+"\t-h display this message\n", ATH3KFW, ATH3KFW);
+
+ exit(255);
+}
+
diff --git a/usr.sbin/bluetooth/bcmfw/BCM-LEGAL.txt b/usr.sbin/bluetooth/bcmfw/BCM-LEGAL.txt
new file mode 100644
index 0000000..e3a235e
--- /dev/null
+++ b/usr.sbin/bluetooth/bcmfw/BCM-LEGAL.txt
@@ -0,0 +1,8 @@
+$FreeBSD$
+
+BCM firmware version 2.15
+Copyright (c) 2000-2002 Broadcom Corporation
+
+Permission to distribute from..
+Contact info:
+ bluetooth_help@broadcom.com
diff --git a/usr.sbin/bluetooth/bcmfw/Makefile b/usr.sbin/bluetooth/bcmfw/Makefile
new file mode 100644
index 0000000..2427cf4
--- /dev/null
+++ b/usr.sbin/bluetooth/bcmfw/Makefile
@@ -0,0 +1,8 @@
+# $Id: Makefile,v 1.6 2003/08/14 20:05:58 max Exp $
+# $FreeBSD$
+
+PROG= bcmfw
+MAN= bcmfw.8
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bluetooth/bcmfw/Makefile.depend b/usr.sbin/bluetooth/bcmfw/Makefile.depend
new file mode 100644
index 0000000..92ae034
--- /dev/null
+++ b/usr.sbin/bluetooth/bcmfw/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libnetgraph \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bluetooth/bcmfw/README b/usr.sbin/bluetooth/bcmfw/README
new file mode 100644
index 0000000..0d769d8
--- /dev/null
+++ b/usr.sbin/bluetooth/bcmfw/README
@@ -0,0 +1,22 @@
+$FreeBSD$
+
+This directory will eventually also contain copies of the broadcom firmware.
+
+It WILL look like:
+total 190
+drwxr-xr-x 3 julian wheel 512 May 10 00:40 .
+drwxr-xr-x 11 julian wheel 512 May 10 14:48 ..
+-rw-r--r-- 1 julian wheel 154 May 10 00:41 BCM-LEGAL.txt
+-rw-r--r-- 1 julian wheel 56 May 10 00:14 BCM2033-FW.bin.md5
+-rw-r--r-- 1 julian wheel 158049 May 10 00:14 BCM2033-FW.bin.uue
+-rw-r--r-- 1 julian wheel 56 May 10 00:14 BCM2033-MD.hex.md5
+-rw-r--r-- 1 julian wheel 4505 May 10 00:14 BCM2033-MD.hex.uue
+drwxr-xr-x 2 julian wheel 512 May 10 00:52 CVS
+-rw-r--r-- 1 julian wheel 516 May 10 00:14 Makefile
+-rw-r--r-- 1 julian wheel 3013 May 10 00:14 bcmfw.8
+-rw-r--r-- 1 julian wheel 6806 May 10 00:14 bcmfw.c
+
+Until then, the firmware files can be fetched as part of
+ http://bluez.sourceforge.net/download/bluez-bluefw-0.9.tar.gz
+
+
diff --git a/usr.sbin/bluetooth/bcmfw/bcmfw.8 b/usr.sbin/bluetooth/bcmfw/bcmfw.8
new file mode 100644
index 0000000..d82d701
--- /dev/null
+++ b/usr.sbin/bluetooth/bcmfw/bcmfw.8
@@ -0,0 +1,105 @@
+.\" Copyright (c) 2003 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+.\"
+.\" $Id: bcmfw.8,v 1.7 2003/05/21 00:33:40 max Exp $
+.\" $FreeBSD$
+.\"
+.Dd March 31, 2003
+.Dt BCMFW 8
+.Os
+.Sh NAME
+.Nm bcmfw
+.Nd firmware download utility for Broadcom BCM2033 chip based Bluetooth USB devices
+.Sh SYNOPSIS
+.Nm
+.Op Fl h
+.Fl f Ar firmware_file_name
+.Fl m Ar mini-driver_file_name
+.Fl n Ar device_name
+.Sh DESCRIPTION
+The
+.Nm
+utility downloads the specified mini-driver and firmware files to the specified
+device.
+.Pp
+This utility will
+.Em only
+work with Broadcom BCM2033 chip based Bluetooth USB devices.
+The identification is currently based on USB vendor ID/product ID pair.
+The vendor ID should be 0x0a5c
+.Pq Dv USB_VENDOR_BROADCOM
+and the product ID should be 0x2033.
+.Pp
+Due to copyright issues I will no longer provide mini-driver and firmware
+files for the device.
+These files can be obtained from the Linux BlueZ bluez-firmware package.
+.Pp
+Visit
+.Pa http://www.bluez.org/download.html
+for details.
+.Pp
+I am using the following files from the bluez-firmware-1.0 package:
+.Pp
+.Dl "MD5 (BCM2033-MD.hex) = 5580317158d07fc4ace90af04f8e1c73"
+.Dl "MD5 (BCM2033-FW.bin) = b4e142b3272cfe5a84b32fda6b4b032f"
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl f Ar firmware_file_name
+Specify firmware file name for download.
+.It Fl h
+Display usage message and exit.
+.It Fl m Ar mini-driver_file_name
+Specify mini-driver file name for download.
+.It Fl n Ar device_name
+Specify device name.
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /dev/ubtbcmfw Ns Ar N Ns Pa \&. Ns Ar EE" -compact
+.It Pa BCM2033-MD.hex
+Mini-driver image.
+.It Pa BCM2033-FW.bin
+Firmware image.
+.It Pa /dev/ubtbcmfw Ns Ar N Ns Pa \&. Ns Ar EE
+Endpoint
+.Ar EE
+of device
+.Ar N .
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+To download the firmware into the
+.Pa /dev/ubtbcmfw0
+device:
+.Pp
+.Dl "bcmfw -n ubtbcmfw0 -m BCM2033-MD.hex -f BCM2033-FW.bin"
+.Sh SEE ALSO
+.Xr ubtbcmfw 4 ,
+.Xr ugen 4
+.Sh AUTHORS
+.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com
+.Sh BUGS
+Most likely.
+Please report if found.
diff --git a/usr.sbin/bluetooth/bcmfw/bcmfw.c b/usr.sbin/bluetooth/bcmfw/bcmfw.c
new file mode 100644
index 0000000..29d9996c
--- /dev/null
+++ b/usr.sbin/bluetooth/bcmfw/bcmfw.c
@@ -0,0 +1,308 @@
+/*
+ * bcmfw.c
+ *
+ * Copyright (c) 2003 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: bcmfw.c,v 1.4 2003/04/27 19:28:09 max Exp $
+ * $FreeBSD$
+ *
+ * Based on Linux BlueZ BlueFW-0.9 package
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_ioctl.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netgraph.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#define BCMFW "bcmfw"
+#define BCMFW_INTR_EP 1
+#define BCMFW_BULK_EP 2
+#define BCMFW_BSIZE 4096
+
+#define USB_VENDOR_BROADCOM 0x0a5c
+#define USB_PRODUCT_BROADCOM_BCM2033 0x2033
+
+static int bcmfw_check_device
+ (char const *name);
+static int bcmfw_load_firmware
+ (char const *name, char const *md, char const *fw);
+static void bcmfw_usage
+ (void);
+
+/*
+ * Main
+ */
+
+int
+main(int argc, char *argv[])
+{
+ char *name = NULL, *md = NULL, *fw = NULL;
+ int x;
+
+ while ((x = getopt(argc, argv, "f:hn:m:")) != -1) {
+ switch (x) {
+ case 'f': /* firmware file */
+ fw = optarg;
+ break;
+
+ case 'n': /* name */
+ name = optarg;
+ break;
+
+ case 'm': /* Mini-driver */
+ md = optarg;
+ break;
+
+ case 'h':
+ default:
+ bcmfw_usage();
+ /* NOT REACHED */
+ }
+ }
+
+ if (name == NULL || md == NULL || fw == NULL)
+ bcmfw_usage();
+ /* NOT REACHED */
+
+ openlog(BCMFW, LOG_NDELAY|LOG_PERROR|LOG_PID, LOG_USER);
+
+ if (bcmfw_check_device(name) < 0)
+ exit(1);
+
+ if (bcmfw_load_firmware(name, md, fw) < 0)
+ exit(1);
+
+ closelog();
+
+ return (0);
+} /* main */
+
+/*
+ * Check device VendorID/ProductID
+ */
+
+static int
+bcmfw_check_device(char const *name)
+{
+ usb_device_descriptor_t desc;
+ char path[BCMFW_BSIZE];
+ int fd = -1, error = -1;
+
+ snprintf(path, sizeof(path), "/dev/%s", name);
+
+ if ((fd = open(path, O_WRONLY)) < 0) {
+ syslog(LOG_ERR, "Could not open(%s). %s (%d)",
+ path, strerror(errno), errno);
+ goto out;
+ }
+
+ if (ioctl(fd, USB_GET_DEVICE_DESC, &desc) < 0) {
+ syslog(LOG_ERR, "Could not ioctl(%d, %ld, %p). %s (%d)",
+ fd, USB_GET_DEVICE_DESC, &desc,
+ strerror(errno), errno);
+ goto out;
+ }
+
+ if (UGETW(desc.idVendor) != USB_VENDOR_BROADCOM ||
+ UGETW(desc.idProduct) != USB_PRODUCT_BROADCOM_BCM2033) {
+ syslog(LOG_ERR, "Unsupported device, VendorID=%#x, " \
+ "ProductID=%#x", UGETW(desc.idVendor),
+ UGETW(desc.idProduct));
+ error = -1;
+ } else
+ error = 0;
+out:
+ if (fd != -1)
+ close(fd);
+
+ return (error);
+} /* bcmfw_check_device */
+
+/*
+ * Download minidriver and firmware
+ */
+
+static int
+bcmfw_load_firmware(char const *name, char const *md, char const *fw)
+{
+ char buf[BCMFW_BSIZE];
+ int intr = -1, bulk = -1, fd = -1, error = -1, len;
+
+ /* Open interrupt endpoint device */
+ snprintf(buf, sizeof(buf), "/dev/%s.%d", name, BCMFW_INTR_EP);
+ if ((intr = open(buf, O_RDONLY)) < 0) {
+ syslog(LOG_ERR, "Could not open(%s). %s (%d)",
+ buf, strerror(errno), errno);
+ goto out;
+ }
+
+ /* Open bulk endpoint device */
+ snprintf(buf, sizeof(buf), "/dev/%s.%d", name, BCMFW_BULK_EP);
+ if ((bulk = open(buf, O_WRONLY)) < 0) {
+ syslog(LOG_ERR, "Could not open(%s). %s (%d)",
+ buf, strerror(errno), errno);
+ goto out;
+ }
+
+ /*
+ * Load mini-driver
+ */
+
+ if ((fd = open(md, O_RDONLY)) < 0) {
+ syslog(LOG_ERR, "Could not open(%s). %s (%d)",
+ md, strerror(errno), errno);
+ goto out;
+ }
+
+ for (;;) {
+ len = read(fd, buf, sizeof(buf));
+ if (len < 0) {
+ syslog(LOG_ERR, "Could not read(%s). %s (%d)",
+ md, strerror(errno), errno);
+ goto out;
+ }
+ if (len == 0)
+ break;
+
+ len = write(bulk, buf, len);
+ if (len < 0) {
+ syslog(LOG_ERR, "Could not write(/dev/%s.%d). %s (%d)",
+ name, BCMFW_BULK_EP, strerror(errno),
+ errno);
+ goto out;
+ }
+ }
+
+ close(fd);
+ fd = -1;
+
+ usleep(10);
+
+ /*
+ * Memory select
+ */
+
+ if (write(bulk, "#", 1) < 0) {
+ syslog(LOG_ERR, "Could not write(/dev/%s.%d). %s (%d)",
+ name, BCMFW_BULK_EP, strerror(errno), errno);
+ goto out;
+ }
+
+ if (read(intr, buf, sizeof(buf)) < 0) {
+ syslog(LOG_ERR, "Could not read(/dev/%s.%d). %s (%d)",
+ name, BCMFW_INTR_EP, strerror(errno), errno);
+ goto out;
+ }
+
+ if (buf[0] != '#') {
+ syslog(LOG_ERR, "%s: Memory select failed (%c)", name, buf[0]);
+ goto out;
+ }
+
+ /*
+ * Load firmware
+ */
+
+ if ((fd = open(fw, O_RDONLY)) < 0) {
+ syslog(LOG_ERR, "Could not open(%s). %s (%d)",
+ fw, strerror(errno), errno);
+ goto out;
+ }
+
+ for (;;) {
+ len = read(fd, buf, sizeof(buf));
+ if (len < 0) {
+ syslog(LOG_ERR, "Could not read(%s). %s (%d)",
+ fw, strerror(errno), errno);
+ goto out;
+ }
+ if (len == 0)
+ break;
+
+ len = write(bulk, buf, len);
+ if (len < 0) {
+ syslog(LOG_ERR, "Could not write(/dev/%s.%d). %s (%d)",
+ name, BCMFW_BULK_EP, strerror(errno),
+ errno);
+ goto out;
+ }
+ }
+
+ close(fd);
+ fd = -1;
+
+ if (read(intr, buf, sizeof(buf)) < 0) {
+ syslog(LOG_ERR, "Could not read(/dev/%s.%d). %s (%d)",
+ name, BCMFW_INTR_EP, strerror(errno), errno);
+ goto out;
+ }
+
+ if (buf[0] != '.') {
+ syslog(LOG_ERR, "%s: Could not load firmware (%c)",
+ name, buf[0]);
+ goto out;
+ }
+
+ usleep(500000);
+ error = 0;
+out:
+ if (fd != -1)
+ close(fd);
+ if (bulk != -1)
+ close(bulk);
+ if (intr != -1)
+ close(intr);
+
+ return (error);
+} /* bcmfw_load_firmware */
+
+/*
+ * Display usage message and quit
+ */
+
+static void
+bcmfw_usage(void)
+{
+ fprintf(stdout,
+"Usage: %s -n name -m md_file -f fw_file\n"
+"Where:\n" \
+"\t-n name device name\n" \
+"\t-m mini-driver image mini-driver image file name for download\n" \
+"\t-f firmware image firmware image file name for download\n" \
+"\t-h display this message\n", BCMFW);
+
+ exit(255);
+} /* bcmfw_usage */
+
diff --git a/usr.sbin/bluetooth/bt3cfw/Makefile b/usr.sbin/bluetooth/bt3cfw/Makefile
new file mode 100644
index 0000000..f9da6ca
--- /dev/null
+++ b/usr.sbin/bluetooth/bt3cfw/Makefile
@@ -0,0 +1,10 @@
+# $Id: Makefile,v 1.5 2003/08/14 20:06:00 max Exp $
+# $FreeBSD$
+
+PROG= bt3cfw
+MAN= bt3cfw.8
+WARNS?= 2
+
+LIBADD+= netgraph
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bluetooth/bt3cfw/Makefile.depend b/usr.sbin/bluetooth/bt3cfw/Makefile.depend
new file mode 100644
index 0000000..92ae034
--- /dev/null
+++ b/usr.sbin/bluetooth/bt3cfw/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libnetgraph \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bluetooth/bt3cfw/bt3cfw.8 b/usr.sbin/bluetooth/bt3cfw/bt3cfw.8
new file mode 100644
index 0000000..ec1ef34
--- /dev/null
+++ b/usr.sbin/bluetooth/bt3cfw/bt3cfw.8
@@ -0,0 +1,73 @@
+.\" Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+.\"
+.\" $Id: bt3cfw.8,v 1.4 2003/05/21 00:34:51 max Exp $
+.\" $FreeBSD$
+.\"
+.Dd November 11, 2002
+.Dt BT3CFW 8
+.Os
+.Sh NAME
+.Nm bt3cfw
+.Nd firmware download utility for 3Com Bluetooth PC card driver
+.Sh SYNOPSIS
+.Nm
+.Op Fl h
+.Fl f Ar Firmware_file_name
+.Fl n Ar Netgraph_node_name
+.Sh DESCRIPTION
+The
+.Nm
+utility connects to the specified Netgraph driver node of type
+.Dv BTCCC
+and downloads the specified firmware file.
+.Pp
+Due to copyright issues, I will no longer provide firmware with the card
+driver.
+The firmware can be obtained from the Windows driver package that
+can be downloaded from the 3COM web site at no charge.
+The firmware name is
+.Pa BT3CPCC.BIN .
+I am using the original firmware that came with the card on CD-ROM.
+.Pp
+.Dl "MD5 (BT3CPCC.BIN) = 36170fda56ea9fdbf1702c966f8a97f1"
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl f Ar Firmware_file_name
+Specify firmware file name for download.
+.It Fl h
+Display usage message and exit.
+.It Fl n Ar Netgraph_node_name
+Connect to the specified Netgraph driver node of type
+.Dv BTCCC .
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr ng_bt3c 4
+.Sh AUTHORS
+.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com
+.Sh BUGS
+Please report if found.
diff --git a/usr.sbin/bluetooth/bt3cfw/bt3cfw.c b/usr.sbin/bluetooth/bt3cfw/bt3cfw.c
new file mode 100644
index 0000000..fcc8d1d
--- /dev/null
+++ b/usr.sbin/bluetooth/bt3cfw/bt3cfw.c
@@ -0,0 +1,228 @@
+/*
+ * bt3cfw.c
+ *
+ * Copyright (c) 2001 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: bt3cfw.c,v 1.2 2003/05/21 22:40:29 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <netgraph.h>
+#include <netgraph/bluetooth/include/ng_bt3c.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#define BT3CFW_IDENT "bt3cfw"
+#define BT3CFW_MAX_FIRMWARE_SIZE 0xffff
+
+/* Convert hex ASCII to int4 */
+static int
+hexa2int4(const char *a)
+{
+ if ('0' <= *a && *a <= '9')
+ return (*a - '0');
+
+ if ('A' <= *a && *a <= 'F')
+ return (*a - 'A' + 0xa);
+
+ if ('a' <= *a && *a <= 'f')
+ return (*a - 'a' + 0xa);
+
+ syslog(LOG_ERR, "Invalid hex character: '%c' (%#x)", *a, *a);
+ exit(255);
+}
+
+/* Convert hex ASCII to int8 */
+static int
+hexa2int8(const char *a)
+{
+ return ((hexa2int4(a) << 4) | hexa2int4(a + 1));
+}
+
+/* Convert hex ASCII to int16 */
+static int
+hexa2int16(const char *a)
+{
+ return ((hexa2int8(a) << 8) | hexa2int8(a + 2));
+}
+
+/* Convert hex ASCII to int32 */
+static int
+hexa2int32(const char *a)
+{
+ return ((hexa2int16(a) << 16) | hexa2int16(a + 4));
+}
+
+/* Display usage() and exit */
+static void
+usage(void)
+{
+ syslog(LOG_ERR, "Usage: %s -f FirmwareFile -n NodeName", BT3CFW_IDENT);
+ exit(255);
+}
+
+/* Main */
+int
+main(int argc, char *argv[])
+{
+ FILE *firmware_file = NULL;
+ char buffer[80], path[NG_PATHSIZ],
+ *firmware_filename = NULL;
+ uint8_t *firmware = NULL;
+ int firmware_size, opt, cs, ds;
+
+ memset(path, 0, sizeof(path));
+ openlog(BT3CFW_IDENT, LOG_NDELAY|LOG_PID|LOG_PERROR, LOG_USER);
+
+ while ((opt = getopt(argc, argv, "f:hn:")) != -1) {
+ switch (opt) {
+ case 'f':
+ firmware_filename = optarg;
+ break;
+
+ case 'n':
+ snprintf(path, sizeof(path), "%s:", optarg);
+ break;
+
+ case 'h':
+ default:
+ usage();
+ /* NOT REACHED */
+ }
+ }
+
+ if (firmware_filename == NULL || path[0] == 0)
+ usage();
+ /* NOT REACHED */
+
+ firmware = (uint8_t *) calloc(BT3CFW_MAX_FIRMWARE_SIZE,
+ sizeof(uint8_t));
+ if (firmware == NULL) {
+ syslog(LOG_ERR, "Could not allocate firmware buffer");
+ exit(255);
+ }
+
+ if ((firmware_file = fopen(firmware_filename, "r")) == NULL) {
+ syslog(LOG_ERR, "Could not open BT3C firmware file %s. %s (%d)",
+ firmware_filename, strerror(errno), errno);
+ exit(255);
+ }
+
+ firmware_size = 0;
+
+ while (fgets(buffer, sizeof(buffer), firmware_file)) {
+ int i, size, address, cs, fcs;
+
+ size = hexa2int8(buffer + 2);
+ address = hexa2int32(buffer + 4);
+ fcs = hexa2int8(buffer + 2 + size * 2);
+
+ if (buffer[1] == '3') {
+ ng_bt3c_firmware_block_ep *block = NULL;
+ uint16_t *data = NULL;
+
+ block = (ng_bt3c_firmware_block_ep *)
+ (firmware + firmware_size);
+
+ firmware_size += sizeof(*block);
+ if (firmware_size >= BT3CFW_MAX_FIRMWARE_SIZE) {
+ syslog(LOG_ERR, "Could not add new firmware " \
+ "block. Firmware file %s is " \
+ "too big, firmware_size=%d",
+ firmware_filename,
+ firmware_size);
+ exit(255);
+ }
+
+ block->block_address = address;
+ block->block_size = (size - 4) / 2;
+ block->block_alignment = (block->block_size * 2) % 3;
+ if (block->block_alignment != 0)
+ block->block_alignment = 3 - block->block_alignment;
+
+ firmware_size += (block->block_size * 2);
+ firmware_size += block->block_alignment;
+ if (firmware_size >= BT3CFW_MAX_FIRMWARE_SIZE) {
+ syslog(LOG_ERR, "Could not add new firmware " \
+ "data. Firmware file %s is " \
+ "too big, firmware_size=%d",
+ firmware_filename,
+ firmware_size);
+ exit(255);
+ }
+
+ /* First part of the cheksum: size and address */
+ cs = 0;
+ for (i = 0; i < 5; i++)
+ cs += hexa2int8(buffer + 2 + i * 2);
+
+ /* Data + second part of the cheksum: data */
+ data = (uint16_t *)(block + 1);
+ for (i = 0; i < block->block_size; i++) {
+ data[i] = hexa2int16(buffer + (i * 4) + 12);
+ cs += (((data[i] & 0xff00) >> 8) & 0xff);
+ cs += (data[i] & 0x00ff);
+ }
+ } else
+ for (cs = 0, i = 0; i < size; i++)
+ cs += hexa2int8(buffer + 2 + i * 2);
+
+ if (((cs + fcs) & 0xff) != 0xff) {
+ syslog(LOG_ERR, "Invalid firmware file %s. Checksum " \
+ "error, cs=%#x, fcs=%#x, checksum=%#x",
+ firmware_filename, (cs & 0xff), fcs,
+ ((cs + fcs) & 0xff));
+ exit(255);
+ }
+ }
+
+ /* Send firmware to the card */
+ if (NgMkSockNode(NULL, &cs, &ds) < 0) {
+ syslog(LOG_ERR, "Could not create Netgraph socket. %s (%d)",
+ strerror(errno), errno);
+ exit(255);
+ }
+
+ if (NgSendMsg(cs, path, NGM_BT3C_COOKIE,
+ NGM_BT3C_NODE_DOWNLOAD_FIRMWARE,
+ (void const *) firmware, firmware_size) < 0) {
+ syslog(LOG_ERR, "Could not send Netgraph message. %s (%d)",
+ strerror(errno), errno);
+ exit(255);
+ }
+
+ free(firmware);
+ firmware = NULL;
+ fclose(firmware_file);
+
+ return (0);
+}
+
diff --git a/usr.sbin/bluetooth/bthidcontrol/Makefile b/usr.sbin/bluetooth/bthidcontrol/Makefile
new file mode 100644
index 0000000..09128d6
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidcontrol/Makefile
@@ -0,0 +1,14 @@
+# $Id: Makefile,v 1.2 2004/02/13 21:44:41 max Exp $
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../bthidd
+
+PROG= bthidcontrol
+MAN= bthidcontrol.8
+SRCS= bthidcontrol.c hid.c lexer.l parser.y sdp.c
+WARNS?= 1
+CFLAGS+= -DBTHIDCONTROL=1 -I${.CURDIR}/../bthidd
+
+LIBADD+= bluetooth sdp usbhid
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bluetooth/bthidcontrol/Makefile.depend b/usr.sbin/bluetooth/bthidcontrol/Makefile.depend
new file mode 100644
index 0000000..32222f2
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidcontrol/Makefile.depend
@@ -0,0 +1,28 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbluetooth \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libsdp \
+ lib/libusbhid \
+ usr.bin/yacc.host \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+lexer.o: lexer.c
+lexer.o: parser.h
+lexer.po: lexer.c
+lexer.po: parser.h
+parser.o: parser.c
+parser.po: parser.c
+.endif
diff --git a/usr.sbin/bluetooth/bthidcontrol/bthidcontrol.8 b/usr.sbin/bluetooth/bthidcontrol/bthidcontrol.8
new file mode 100644
index 0000000..50774f1
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidcontrol/bthidcontrol.8
@@ -0,0 +1,102 @@
+.\" Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+.\"
+.\" $Id: bthidcontrol.8,v 1.1 2004/02/13 21:44:41 max Exp $
+.\" $FreeBSD$
+.\"
+.Dd October 30, 2006
+.Dt BTHIDCONTROL 8
+.Os
+.Sh NAME
+.Nm bthidcontrol
+.Nd Bluetooth HID control utility
+.Sh SYNOPSIS
+.Nm
+.Fl h
+.Nm
+.Op Fl a Ar BD_ADDR
+.Op Fl c Ar file
+.Op Fl H Ar file
+.Op Fl v
+.Ar command
+.Sh DESCRIPTION
+The
+.Nm
+utility can be used to query remote Bluetooth HID devices, dump HID descriptors
+in human readable form and perform simple manipulations on the Bluetooth HID
+daemon configuration files.
+.Pp
+The
+.Nm
+utility will print results to the standard output and error messages to the
+standard error.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl a Ar BD_ADDR
+Specify BD_ADDR for the HID device.
+Example:
+.Fl a Li 00:01:02:03:04:05 .
+.It Fl c Ar file
+Specify path to the Bluetooth HID daemon configuration file.
+The default path is
+.Pa /etc/bluetooth/bthidd.conf .
+.It Fl H Ar file
+Specify path to the Bluetooth HID daemon known HIDs file.
+The default path is
+.Pa /var/db/bthidd.hids .
+.It Fl h
+Display usage message and exit.
+.It Fl v
+Be verbose and show items that are being used for padding.
+.It Ar command
+One of the supported commands (see below).
+Special command
+.Cm help
+can be used to obtain the list of all supported commands.
+To get more information about specific command use
+.Cm help Ar command .
+.El
+.Sh COMMANDS
+The currently supported node commands in
+.Nm
+are:
+.Pp
+.Bl -tag -width "Forget" -offset indent -compact
+.It Cm Query
+.It Cm Dump
+.It Cm Known
+.It Cm Forget
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /etc/bluetooth/bthidd.conf" -compact
+.It Pa /etc/bluetooth/bthidd.conf
+.It Pa /var/db/bthidd.hids
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr bthidd 8
+.Sh AUTHORS
+.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com
diff --git a/usr.sbin/bluetooth/bthidcontrol/bthidcontrol.c b/usr.sbin/bluetooth/bthidcontrol/bthidcontrol.c
new file mode 100644
index 0000000..0f795dd
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidcontrol/bthidcontrol.c
@@ -0,0 +1,216 @@
+/*
+ * bthidcontrol.c
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: bthidcontrol.c,v 1.2 2004/02/13 21:44:41 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#include <assert.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <usbhid.h>
+#include "bthid_config.h"
+#include "bthidcontrol.h"
+
+static int do_bthid_command(bdaddr_p bdaddr, int argc, char **argv);
+static struct bthid_command * find_bthid_command(char const *command, struct bthid_command *category);
+static void print_bthid_command(struct bthid_command *category);
+static void usage(void);
+
+int32_t hid_sdp_query(bdaddr_t const *local, bdaddr_t const *remote, int32_t *error);
+
+uint32_t verbose = 0;
+
+/*
+ * bthidcontrol
+ */
+
+int
+main(int argc, char *argv[])
+{
+ bdaddr_t bdaddr;
+ int opt;
+
+ hid_init(NULL);
+ memcpy(&bdaddr, NG_HCI_BDADDR_ANY, sizeof(bdaddr));
+
+ while ((opt = getopt(argc, argv, "a:c:H:hv")) != -1) {
+ switch (opt) {
+ case 'a': /* bdaddr */
+ if (!bt_aton(optarg, &bdaddr)) {
+ struct hostent *he = NULL;
+
+ if ((he = bt_gethostbyname(optarg)) == NULL)
+ errx(1, "%s: %s", optarg, hstrerror(h_errno));
+
+ memcpy(&bdaddr, he->h_addr, sizeof(bdaddr));
+ }
+ break;
+
+ case 'c': /* config file */
+ config_file = optarg;
+ break;
+
+ case 'H': /* HIDs file */
+ hids_file = optarg;
+ break;
+
+ case 'v': /* verbose */
+ verbose++;
+ break;
+
+ case 'h':
+ default:
+ usage();
+ /* NOT REACHED */
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (*argv == NULL)
+ usage();
+
+ return (do_bthid_command(&bdaddr, argc, argv));
+} /* main */
+
+/* Execute commands */
+static int
+do_bthid_command(bdaddr_p bdaddr, int argc, char **argv)
+{
+ char *cmd = argv[0];
+ struct bthid_command *c = NULL;
+ int e, help;
+
+ help = 0;
+ if (strcasecmp(cmd, "help") == 0) {
+ argc --;
+ argv ++;
+
+ if (argc <= 0) {
+ fprintf(stdout, "Supported commands:\n");
+ print_bthid_command(sdp_commands);
+ print_bthid_command(hid_commands);
+ fprintf(stdout, "\nFor more information use " \
+ "'help command'\n");
+
+ return (OK);
+ }
+
+ help = 1;
+ cmd = argv[0];
+ }
+
+ c = find_bthid_command(cmd, sdp_commands);
+ if (c == NULL)
+ c = find_bthid_command(cmd, hid_commands);
+
+ if (c == NULL) {
+ fprintf(stdout, "Unknown command: \"%s\"\n", cmd);
+ return (ERROR);
+ }
+
+ if (!help)
+ e = (c->handler)(bdaddr, -- argc, ++ argv);
+ else
+ e = USAGE;
+
+ switch (e) {
+ case OK:
+ case FAILED:
+ break;
+
+ case ERROR:
+ fprintf(stdout, "Could not execute command \"%s\". %s\n",
+ cmd, strerror(errno));
+ break;
+
+ case USAGE:
+ fprintf(stdout, "Usage: %s\n%s\n", c->command, c->description);
+ break;
+
+ default: assert(0); break;
+ }
+
+ return (e);
+} /* do_bthid_command */
+
+/* Try to find command in specified category */
+static struct bthid_command *
+find_bthid_command(char const *command, struct bthid_command *category)
+{
+ struct bthid_command *c = NULL;
+
+ for (c = category; c->command != NULL; c++) {
+ char *c_end = strchr(c->command, ' ');
+
+ if (c_end != NULL) {
+ int len = c_end - c->command;
+
+ if (strncasecmp(command, c->command, len) == 0)
+ return (c);
+ } else if (strcasecmp(command, c->command) == 0)
+ return (c);
+ }
+
+ return (NULL);
+} /* find_bthid_command */
+
+/* Print commands in specified category */
+static void
+print_bthid_command(struct bthid_command *category)
+{
+ struct bthid_command *c = NULL;
+
+ for (c = category; c->command != NULL; c++)
+ fprintf(stdout, "\t%s\n", c->command);
+} /* print_bthid_command */
+
+/* Usage */
+static void
+usage(void)
+{
+ fprintf(stderr,
+"Usage: bthidcontrol options command\n" \
+"Where options are:\n"
+" -a bdaddr specify bdaddr\n" \
+" -c file specify path to the bthidd config file\n" \
+" -H file specify path to the bthidd HIDs file\n" \
+" -h display usage and quit\n" \
+" -v be verbose\n" \
+" command one of the supported commands\n");
+ exit(255);
+} /* usage */
+
diff --git a/usr.sbin/bluetooth/bthidcontrol/bthidcontrol.h b/usr.sbin/bluetooth/bthidcontrol/bthidcontrol.h
new file mode 100644
index 0000000..50ed2fa
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidcontrol/bthidcontrol.h
@@ -0,0 +1,50 @@
+/*
+ * bthidcontrol.h
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: bthidcontrol.h,v 1.1 2004/02/12 23:25:51 max Exp $
+ * $FreeBSD$
+ */
+
+#ifndef __BTHIDCONTROL_H__
+#define __BTHIDCONTROL_H__
+
+#define OK 0 /* everything was OK */
+#define ERROR 1 /* could not execute command */
+#define FAILED 2 /* error was reported */
+#define USAGE 3 /* invalid parameters */
+
+struct bthid_command {
+ char const *command;
+ char const *description;
+ int (*handler)(bdaddr_t *, int, char **);
+};
+
+extern struct bthid_command hid_commands[];
+extern struct bthid_command sdp_commands[];
+
+#endif /* __BTHIDCONTROL_H__ */
+
diff --git a/usr.sbin/bluetooth/bthidcontrol/hid.c b/usr.sbin/bluetooth/bthidcontrol/hid.c
new file mode 100644
index 0000000..e43ef6a
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidcontrol/hid.c
@@ -0,0 +1,215 @@
+/*
+ * hid.c
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: hid.c,v 1.3 2004/02/17 22:14:57 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <dev/usb/usb.h>
+#include <dev/usb/usbhid.h>
+#include <stdio.h>
+#include <string.h>
+#include <usbhid.h>
+#include "bthid_config.h"
+#include "bthidcontrol.h"
+
+extern uint32_t verbose;
+
+static void hid_dump_descriptor (report_desc_t r);
+static void hid_dump_item (char const *label, struct hid_item *h);
+
+static int
+hid_dump(bdaddr_t *bdaddr, int argc, char **argv)
+{
+ struct hid_device *hd = NULL;
+ int e = FAILED;
+
+ if (read_config_file() == 0) {
+ if ((hd = get_hid_device(bdaddr)) != NULL) {
+ hid_dump_descriptor(hd->desc);
+ e = OK;
+ }
+
+ clean_config();
+ }
+
+ return (e);
+}
+
+static int
+hid_forget(bdaddr_t *bdaddr, int argc, char **argv)
+{
+ struct hid_device *hd = NULL;
+ int e = FAILED;
+
+ if (read_config_file() == 0) {
+ if (read_hids_file() == 0) {
+ if ((hd = get_hid_device(bdaddr)) != NULL) {
+ hd->new_device = 1;
+ if (write_hids_file() == 0)
+ e = OK;
+ }
+ }
+
+ clean_config();
+ }
+
+ return (e);
+}
+
+static int
+hid_known(bdaddr_t *bdaddr, int argc, char **argv)
+{
+ struct hid_device *hd = NULL;
+ struct hostent *he = NULL;
+ int e = FAILED;
+
+ if (read_config_file() == 0) {
+ if (read_hids_file() == 0) {
+ e = OK;
+
+ for (hd = get_next_hid_device(hd);
+ hd != NULL;
+ hd = get_next_hid_device(hd)) {
+ if (hd->new_device)
+ continue;
+
+ he = bt_gethostbyaddr((char *) &hd->bdaddr,
+ sizeof(hd->bdaddr),
+ AF_BLUETOOTH);
+
+ fprintf(stdout,
+"%s %s\n", bt_ntoa(&hd->bdaddr, NULL),
+ (he != NULL && he->h_name != NULL)?
+ he->h_name : "");
+ }
+ }
+
+ clean_config();
+ }
+
+ return (e);
+}
+
+static void
+hid_dump_descriptor(report_desc_t r)
+{
+ struct hid_data *d = NULL;
+ struct hid_item h;
+
+ for (d = hid_start_parse(r, ~0, -1); hid_get_item(d, &h); ) {
+ switch (h.kind) {
+ case hid_collection:
+ fprintf(stdout,
+"Collection page=%s usage=%s\n", hid_usage_page(HID_PAGE(h.usage)),
+ hid_usage_in_page(h.usage));
+ break;
+
+ case hid_endcollection:
+ fprintf(stdout, "End collection\n");
+ break;
+
+ case hid_input:
+ hid_dump_item("Input ", &h);
+ break;
+
+ case hid_output:
+ hid_dump_item("Output ", &h);
+ break;
+
+ case hid_feature:
+ hid_dump_item("Feature", &h);
+ break;
+ }
+ }
+
+ hid_end_parse(d);
+}
+
+static void
+hid_dump_item(char const *label, struct hid_item *h)
+{
+ if ((h->flags & HIO_CONST) && !verbose)
+ return;
+
+ fprintf(stdout,
+"%s id=%u size=%u count=%u page=%s usage=%s%s%s%s%s%s%s%s%s%s",
+ label, (uint8_t) h->report_ID, h->report_size, h->report_count,
+ hid_usage_page(HID_PAGE(h->usage)),
+ hid_usage_in_page(h->usage),
+ h->flags & HIO_CONST ? " Const" : "",
+ h->flags & HIO_VARIABLE ? " Variable" : "",
+ h->flags & HIO_RELATIVE ? " Relative" : "",
+ h->flags & HIO_WRAP ? " Wrap" : "",
+ h->flags & HIO_NONLINEAR ? " NonLinear" : "",
+ h->flags & HIO_NOPREF ? " NoPref" : "",
+ h->flags & HIO_NULLSTATE ? " NullState" : "",
+ h->flags & HIO_VOLATILE ? " Volatile" : "",
+ h->flags & HIO_BUFBYTES ? " BufBytes" : "");
+
+ fprintf(stdout,
+", logical range %d..%d",
+ h->logical_minimum, h->logical_maximum);
+
+ if (h->physical_minimum != h->physical_maximum)
+ fprintf(stdout,
+", physical range %d..%d",
+ h->physical_minimum, h->physical_maximum);
+
+ if (h->unit)
+ fprintf(stdout,
+", unit=0x%02x exp=%d", h->unit, h->unit_exponent);
+
+ fprintf(stdout, "\n");
+}
+
+struct bthid_command hid_commands[] = {
+{
+"Dump",
+"Dump HID descriptor for the specified device in human readable form. The\n" \
+"device must have an entry in the Bluetooth HID daemon configuration file.\n",
+hid_dump
+},
+{
+"Known",
+"List all known to the Bluetooth HID daemon devices.\n",
+hid_known
+},
+{
+"Forget",
+"Forget (mark as new) specified HID device. This command is useful when it\n" \
+"is required to remove device from the known HIDs file. This should be done\n" \
+"when reset button was pressed on the device or the battery was changed. The\n"\
+"Bluetooth HID daemon should be restarted.\n",
+hid_forget
+},
+{ NULL, NULL, NULL }
+};
+
diff --git a/usr.sbin/bluetooth/bthidcontrol/sdp.c b/usr.sbin/bluetooth/bthidcontrol/sdp.c
new file mode 100644
index 0000000..3beabc1
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidcontrol/sdp.c
@@ -0,0 +1,435 @@
+/*
+ * sdp.c
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: sdp.c,v 1.3 2004/02/17 22:14:57 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <dev/usb/usb.h>
+#include <dev/usb/usbhid.h>
+#include <errno.h>
+#include <sdp.h>
+#include <stdio.h>
+#include <string.h>
+#include <usbhid.h>
+#include "bthid_config.h"
+#include "bthidcontrol.h"
+
+static int32_t hid_sdp_query (bdaddr_t const *local, struct hid_device *hd, int32_t *error);
+static int32_t hid_sdp_parse_protocol_descriptor_list (sdp_attr_p a);
+static int32_t hid_sdp_parse_hid_descriptor (sdp_attr_p a);
+static int32_t hid_sdp_parse_boolean (sdp_attr_p a);
+
+static uint16_t service = SDP_SERVICE_CLASS_HUMAN_INTERFACE_DEVICE;
+
+static uint32_t attrs[] = {
+SDP_ATTR_RANGE( SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST,
+ SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST),
+SDP_ATTR_RANGE (SDP_ATTR_ADDITIONAL_PROTOCOL_DESCRIPTOR_LISTS,
+ SDP_ATTR_ADDITIONAL_PROTOCOL_DESCRIPTOR_LISTS),
+SDP_ATTR_RANGE( 0x0205, /* HIDReconnectInitiate */
+ 0x0205),
+SDP_ATTR_RANGE( 0x0206, /* HIDDescriptorList */
+ 0x0206),
+SDP_ATTR_RANGE( 0x0209, /* HIDBatteryPower */
+ 0x0209),
+SDP_ATTR_RANGE( 0x020d, /* HIDNormallyConnectable */
+ 0x020d)
+ };
+#define nattrs (sizeof(attrs)/sizeof(attrs[0]))
+
+static sdp_attr_t values[8];
+#define nvalues (sizeof(values)/sizeof(values[0]))
+
+static uint8_t buffer[nvalues][512];
+
+/*
+ * Query remote device
+ */
+
+#undef hid_sdp_query_exit
+#define hid_sdp_query_exit(e) { \
+ if (error != NULL) \
+ *error = (e); \
+ if (ss != NULL) { \
+ sdp_close(ss); \
+ ss = NULL; \
+ } \
+ return (((e) == 0)? 0 : -1); \
+}
+
+static int32_t
+hid_sdp_query(bdaddr_t const *local, struct hid_device *hd, int32_t *error)
+{
+ void *ss = NULL;
+ uint8_t *hid_descriptor = NULL;
+ int32_t i, control_psm = -1, interrupt_psm = -1,
+ reconnect_initiate = -1,
+ normally_connectable = 0, battery_power = 0,
+ hid_descriptor_length = -1;
+
+ if (local == NULL)
+ local = NG_HCI_BDADDR_ANY;
+ if (hd == NULL)
+ hid_sdp_query_exit(EINVAL);
+
+ for (i = 0; i < nvalues; i ++) {
+ values[i].flags = SDP_ATTR_INVALID;
+ values[i].attr = 0;
+ values[i].vlen = sizeof(buffer[i]);
+ values[i].value = buffer[i];
+ }
+
+ if ((ss = sdp_open(local, &hd->bdaddr)) == NULL)
+ hid_sdp_query_exit(ENOMEM);
+ if (sdp_error(ss) != 0)
+ hid_sdp_query_exit(sdp_error(ss));
+ if (sdp_search(ss, 1, &service, nattrs, attrs, nvalues, values) != 0)
+ hid_sdp_query_exit(sdp_error(ss));
+
+ sdp_close(ss);
+ ss = NULL;
+
+ for (i = 0; i < nvalues; i ++) {
+ if (values[i].flags != SDP_ATTR_OK)
+ continue;
+
+ switch (values[i].attr) {
+ case SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST:
+ control_psm = hid_sdp_parse_protocol_descriptor_list(&values[i]);
+ break;
+
+ case SDP_ATTR_ADDITIONAL_PROTOCOL_DESCRIPTOR_LISTS:
+ interrupt_psm = hid_sdp_parse_protocol_descriptor_list(&values[i]);
+ break;
+
+ case 0x0205: /* HIDReconnectInitiate */
+ reconnect_initiate = hid_sdp_parse_boolean(&values[i]);
+ break;
+
+ case 0x0206: /* HIDDescriptorList */
+ if (hid_sdp_parse_hid_descriptor(&values[i]) == 0) {
+ hid_descriptor = values[i].value;
+ hid_descriptor_length = values[i].vlen;
+ }
+ break;
+
+ case 0x0209: /* HIDBatteryPower */
+ battery_power = hid_sdp_parse_boolean(&values[i]);
+ break;
+
+ case 0x020d: /* HIDNormallyConnectable */
+ normally_connectable = hid_sdp_parse_boolean(&values[i]);
+ break;
+ }
+ }
+
+ if (control_psm == -1 || interrupt_psm == -1 ||
+ reconnect_initiate == -1 ||
+ hid_descriptor == NULL || hid_descriptor_length == -1)
+ hid_sdp_query_exit(ENOATTR);
+
+ hd->control_psm = control_psm;
+ hd->interrupt_psm = interrupt_psm;
+ hd->reconnect_initiate = reconnect_initiate? 1 : 0;
+ hd->battery_power = battery_power? 1 : 0;
+ hd->normally_connectable = normally_connectable? 1 : 0;
+ hd->desc = hid_use_report_desc(hid_descriptor, hid_descriptor_length);
+ if (hd->desc == NULL)
+ hid_sdp_query_exit(ENOMEM);
+
+ return (0);
+}
+
+/*
+ * seq len 2
+ * seq len 2
+ * uuid value 3
+ * uint16 value 3
+ * seq len 2
+ * uuid value 3
+ */
+
+static int32_t
+hid_sdp_parse_protocol_descriptor_list(sdp_attr_p a)
+{
+ uint8_t *ptr = a->value;
+ uint8_t *end = a->value + a->vlen;
+ int32_t type, len, uuid, psm;
+
+ if (end - ptr < 15)
+ return (-1);
+
+ if (a->attr == SDP_ATTR_ADDITIONAL_PROTOCOL_DESCRIPTOR_LISTS) {
+ SDP_GET8(type, ptr);
+ switch (type) {
+ case SDP_DATA_SEQ8:
+ SDP_GET8(len, ptr);
+ break;
+
+ case SDP_DATA_SEQ16:
+ SDP_GET16(len, ptr);
+ break;
+
+ case SDP_DATA_SEQ32:
+ SDP_GET32(len, ptr);
+ break;
+
+ default:
+ return (-1);
+ }
+ if (ptr + len > end)
+ return (-1);
+ }
+
+ SDP_GET8(type, ptr);
+ switch (type) {
+ case SDP_DATA_SEQ8:
+ SDP_GET8(len, ptr);
+ break;
+
+ case SDP_DATA_SEQ16:
+ SDP_GET16(len, ptr);
+ break;
+
+ case SDP_DATA_SEQ32:
+ SDP_GET32(len, ptr);
+ break;
+
+ default:
+ return (-1);
+ }
+ if (ptr + len > end)
+ return (-1);
+
+ /* Protocol */
+ SDP_GET8(type, ptr);
+ switch (type) {
+ case SDP_DATA_SEQ8:
+ SDP_GET8(len, ptr);
+ break;
+
+ case SDP_DATA_SEQ16:
+ SDP_GET16(len, ptr);
+ break;
+
+ case SDP_DATA_SEQ32:
+ SDP_GET32(len, ptr);
+ break;
+
+ default:
+ return (-1);
+ }
+ if (ptr + len > end)
+ return (-1);
+
+ /* UUID */
+ if (ptr + 3 > end)
+ return (-1);
+ SDP_GET8(type, ptr);
+ switch (type) {
+ case SDP_DATA_UUID16:
+ SDP_GET16(uuid, ptr);
+ if (uuid != SDP_UUID_PROTOCOL_L2CAP)
+ return (-1);
+ break;
+
+ case SDP_DATA_UUID32: /* XXX FIXME can we have 32-bit UUID */
+ case SDP_DATA_UUID128: /* XXX FIXME can we have 128-bit UUID */
+ default:
+ return (-1);
+ }
+
+ /* PSM */
+ if (ptr + 3 > end)
+ return (-1);
+ SDP_GET8(type, ptr);
+ if (type != SDP_DATA_UINT16)
+ return (-1);
+ SDP_GET16(psm, ptr);
+
+ return (psm);
+}
+
+/*
+ * seq len 2
+ * seq len 2
+ * uint8 value8 2
+ * str value 3
+ */
+
+static int32_t
+hid_sdp_parse_hid_descriptor(sdp_attr_p a)
+{
+ uint8_t *ptr = a->value;
+ uint8_t *end = a->value + a->vlen;
+ int32_t type, len, descriptor_type;
+
+ if (end - ptr < 9)
+ return (-1);
+
+ SDP_GET8(type, ptr);
+ switch (type) {
+ case SDP_DATA_SEQ8:
+ SDP_GET8(len, ptr);
+ break;
+
+ case SDP_DATA_SEQ16:
+ SDP_GET16(len, ptr);
+ break;
+
+ case SDP_DATA_SEQ32:
+ SDP_GET32(len, ptr);
+ break;
+
+ default:
+ return (-1);
+ }
+ if (ptr + len > end)
+ return (-1);
+
+ while (ptr < end) {
+ /* Descriptor */
+ SDP_GET8(type, ptr);
+ switch (type) {
+ case SDP_DATA_SEQ8:
+ if (ptr + 1 > end)
+ return (-1);
+ SDP_GET8(len, ptr);
+ break;
+
+ case SDP_DATA_SEQ16:
+ if (ptr + 2 > end)
+ return (-1);
+ SDP_GET16(len, ptr);
+ break;
+
+ case SDP_DATA_SEQ32:
+ if (ptr + 4 > end)
+ return (-1);
+ SDP_GET32(len, ptr);
+ break;
+
+ default:
+ return (-1);
+ }
+
+ /* Descripor type */
+ if (ptr + 1 > end)
+ return (-1);
+ SDP_GET8(type, ptr);
+ if (type != SDP_DATA_UINT8 || ptr + 1 > end)
+ return (-1);
+ SDP_GET8(descriptor_type, ptr);
+
+ /* Descriptor value */
+ if (ptr + 1 > end)
+ return (-1);
+ SDP_GET8(type, ptr);
+ switch (type) {
+ case SDP_DATA_STR8:
+ if (ptr + 1 > end)
+ return (-1);
+ SDP_GET8(len, ptr);
+ break;
+
+ case SDP_DATA_STR16:
+ if (ptr + 2 > end)
+ return (-1);
+ SDP_GET16(len, ptr);
+ break;
+
+ case SDP_DATA_STR32:
+ if (ptr + 4 > end)
+ return (-1);
+ SDP_GET32(len, ptr);
+ break;
+
+ default:
+ return (-1);
+ }
+ if (ptr + len > end)
+ return (-1);
+
+ if (descriptor_type == UDESC_REPORT && len > 0) {
+ a->value = ptr;
+ a->vlen = len;
+
+ return (0);
+ }
+
+ ptr += len;
+ }
+
+ return (-1);
+}
+
+/* bool8 int8 */
+static int32_t
+hid_sdp_parse_boolean(sdp_attr_p a)
+{
+ if (a->vlen != 2 || a->value[0] != SDP_DATA_BOOL)
+ return (-1);
+
+ return (a->value[1]);
+}
+
+/* Perform SDP query */
+static int32_t
+hid_query(bdaddr_t *bdaddr, int argc, char **argv)
+{
+ struct hid_device hd;
+ int e;
+
+ memcpy(&hd.bdaddr, bdaddr, sizeof(hd.bdaddr));
+ if (hid_sdp_query(NULL, &hd, &e) < 0) {
+ fprintf(stderr, "Could not perform SDP query on the " \
+ "device %s. %s (%d)\n", bt_ntoa(bdaddr, NULL),
+ strerror(e), e);
+ return (FAILED);
+ }
+
+ print_hid_device(&hd, stdout);
+
+ return (OK);
+}
+
+struct bthid_command sdp_commands[] =
+{
+{
+"Query",
+"Perform SDP query to the specified device and print HID configuration entry\n"\
+"for the device. The configuration entry should be appended to the Bluetooth\n"\
+"HID daemon configuration file and the daemon should be restarted.\n",
+hid_query
+},
+{ NULL, NULL, NULL }
+};
+
diff --git a/usr.sbin/bluetooth/bthidd/Makefile b/usr.sbin/bluetooth/bthidd/Makefile
new file mode 100644
index 0000000..f36d216
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/Makefile
@@ -0,0 +1,16 @@
+# $Id: Makefile,v 1.6 2006/09/07 21:36:55 max Exp $
+# $FreeBSD$
+
+PROG= bthidd
+MAN= bthidd.8
+# bthidd.conf.5
+SRCS= bthidd.c client.c hid.c kbd.c lexer.l parser.y server.c \
+ session.c
+
+CFLAGS+= -I${.CURDIR}
+
+LIBADD+= bluetooth usbhid
+
+NO_WMISSING_VARIABLE_DECLARATIONS=
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bluetooth/bthidd/Makefile.depend b/usr.sbin/bluetooth/bthidd/Makefile.depend
new file mode 100644
index 0000000..a1ed9d7
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/Makefile.depend
@@ -0,0 +1,27 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbluetooth \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libusbhid \
+ usr.bin/yacc.host \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+lexer.o: lexer.c
+lexer.o: parser.h
+lexer.po: lexer.c
+lexer.po: parser.h
+parser.o: parser.c
+parser.po: parser.c
+.endif
diff --git a/usr.sbin/bluetooth/bthidd/bthid_config.h b/usr.sbin/bluetooth/bthidd/bthid_config.h
new file mode 100644
index 0000000..71cb425
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/bthid_config.h
@@ -0,0 +1,70 @@
+/*
+ * bthid_config.h
+ */
+
+/*-
+ * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: bthid_config.h,v 1.4 2006/09/07 21:06:53 max Exp $
+ * $FreeBSD$
+ */
+
+#ifndef _BTHID_CONFIG_H_
+#define _BTHID_CONFIG_H_ 1
+
+#define BTHIDD_CONFFILE "/etc/bluetooth/bthidd.conf"
+#define BTHIDD_HIDSFILE "/var/db/bthidd.hids"
+
+struct hid_device
+{
+ bdaddr_t bdaddr; /* HID device BDADDR */
+ uint16_t control_psm; /* control PSM */
+ uint16_t interrupt_psm; /* interrupt PSM */
+ unsigned new_device : 1;
+ unsigned reconnect_initiate : 1;
+ unsigned battery_power : 1;
+ unsigned normally_connectable : 1;
+ unsigned keyboard : 1;
+ unsigned reserved : 11;
+ report_desc_t desc; /* HID report descriptor */
+ LIST_ENTRY(hid_device) next; /* link to the next */
+};
+typedef struct hid_device hid_device_t;
+typedef struct hid_device * hid_device_p;
+
+extern char const *config_file;
+extern char const *hids_file;
+
+int32_t read_config_file (void);
+void clean_config (void);
+hid_device_p get_hid_device (bdaddr_p bdaddr);
+hid_device_p get_next_hid_device (hid_device_p d);
+void print_hid_device (hid_device_p hid_device, FILE *f);
+
+int32_t read_hids_file (void);
+int32_t write_hids_file (void);
+
+#endif /* ndef _BTHID_CONFIG_H_ */
+
diff --git a/usr.sbin/bluetooth/bthidd/bthidd.8 b/usr.sbin/bluetooth/bthidd/bthidd.8
new file mode 100644
index 0000000..53db4cb
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/bthidd.8
@@ -0,0 +1,127 @@
+.\" Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+.\"
+.\" $Id: bthidd.8,v 1.1 2006/09/07 21:36:55 max Exp $
+.\" $FreeBSD$
+.\"
+.Dd September 7, 2006
+.Dt BTHIDD 8
+.Os
+.Sh NAME
+.Nm bthidd
+.Nd Bluetooth HID daemon
+.Sh SYNOPSIS
+.Nm
+.Fl h
+.Nm
+.Op Fl a Ar BD_ADDR
+.Op Fl c Ar file
+.Op Fl H Ar file
+.Op Fl p Ar file
+.Op Fl t Ar val
+.Sh DESCRIPTION
+The
+.Nm
+daemon handles remote Bluetooth HID devices.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl a Ar BD_ADDR
+Specify the local address to listen on.
+By default, the server will listen on
+.Dv ANY
+address.
+The address can be specified as BD_ADDR or name.
+If a name was specified, the
+.Nm
+daemon will attempt to resolve the name via
+.Xr bt_gethostbyname 3 .
+.It Fl c Ar file
+Specify path to the configuration file.
+The default path is
+.Pa /etc/bluetooth/bthidd.conf .
+.It Fl d
+Do not detach from the controlling terminal, i.e., run in foreground.
+.It Fl H Ar file
+Specify path to the known HIDs file.
+The default path is
+.Pa /var/db/bthidd.hids .
+.It Fl h
+Display usage message and exit.
+.It Fl p Ar file
+Specify path to the PID file.
+The default path is
+.Pa /var/run/bthidd.pid .
+.It Fl t Ar val
+Specify client rescan interval in seconds.
+The
+.Nm
+daemon will periodically scan for newly configured Bluetooth HID devices or
+disconnected
+.Dq passive
+Bluetooth HID devices and will attempt to establish an outgoing connection.
+The default rescan interval is 10 seconds.
+.El
+.Sh KNOWN LIMITATIONS
+The
+.Nm
+daemon currently does not handle key auto repeat and double click mouse events.
+Those events work under
+.Xr X 7
+just fine,
+but not in text console.
+.Pp
+This manual page needs more work.
+A manual page documenting the format of the
+.Pa /etc/bluetooth/bthidd.conf
+configuration file is needed as well.
+.Sh FILES
+.Bl -tag -width ".Pa /etc/bluetooth/bthidd.conf" -compact
+.It Pa /etc/bluetooth/bthidd.conf
+.It Pa /var/db/bthidd.hids
+.It Pa /var/run/bthidd.pid
+.El
+.Sh SEE ALSO
+.Xr kbdmux 4 ,
+.Xr vkbd 4 ,
+.Xr bthidcontrol 8
+.Sh AUTHORS
+.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com
+.Sh CAVEATS
+Any Bluetooth HID device that has
+.Dv HUP_KEYBOARD
+or
+.Dv HUP_CONSUMER
+entries in its descriptor is considered as
+.Dq keyboard .
+For each
+.Dq keyboard
+Bluetooth HID device,
+the
+.Nm
+daemon will use a separate instance of the virtual keyboard interface
+.Xr vkbd 4 .
+Therefore the
+.Xr kbdmux 4
+driver must be used to properly multiplex input from multiple keyboards.
diff --git a/usr.sbin/bluetooth/bthidd/bthidd.c b/usr.sbin/bluetooth/bthidd/bthidd.c
new file mode 100644
index 0000000..7e988fc
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/bthidd.c
@@ -0,0 +1,267 @@
+/*
+ * bthidd.c
+ */
+
+/*-
+ * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: bthidd.c,v 1.8 2006/09/07 21:06:53 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/time.h>
+#include <sys/queue.h>
+#include <assert.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <err.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <usbhid.h>
+#include "bthid_config.h"
+#include "bthidd.h"
+
+static int32_t write_pid_file (char const *file);
+static int32_t remove_pid_file (char const *file);
+static int32_t elapsed (int32_t tval);
+static void sighandler (int32_t s);
+static void usage (void);
+
+/*
+ * bthidd
+ */
+
+static int32_t done = 0; /* are we done? */
+
+int32_t
+main(int32_t argc, char *argv[])
+{
+ struct bthid_server srv;
+ struct sigaction sa;
+ char const *pid_file = BTHIDD_PIDFILE;
+ char *ep;
+ int32_t opt, detach, tval;
+
+ memset(&srv, 0, sizeof(srv));
+ memset(&srv.bdaddr, 0, sizeof(srv.bdaddr));
+ detach = 1;
+ tval = 10; /* sec */
+
+ while ((opt = getopt(argc, argv, "a:c:dH:hp:t:")) != -1) {
+ switch (opt) {
+ case 'a': /* BDADDR */
+ if (!bt_aton(optarg, &srv.bdaddr)) {
+ struct hostent *he;
+
+ if ((he = bt_gethostbyname(optarg)) == NULL)
+ errx(1, "%s: %s", optarg, hstrerror(h_errno));
+
+ memcpy(&srv.bdaddr, he->h_addr, sizeof(srv.bdaddr));
+ }
+ break;
+
+ case 'c': /* config file */
+ config_file = optarg;
+ break;
+
+ case 'd': /* do not detach */
+ detach = 0;
+ break;
+
+ case 'H': /* hids file */
+ hids_file = optarg;
+ break;
+
+ case 'p': /* pid file */
+ pid_file = optarg;
+ break;
+
+ case 't': /* rescan interval */
+ tval = strtol(optarg, (char **) &ep, 10);
+ if (*ep != '\0' || tval <= 0)
+ usage();
+ break;
+
+ case 'h':
+ default:
+ usage();
+ /* NOT REACHED */
+ }
+ }
+
+ openlog(BTHIDD_IDENT, LOG_PID|LOG_PERROR|LOG_NDELAY, LOG_USER);
+
+ /* Become daemon if required */
+ if (detach && daemon(0, 0) < 0) {
+ syslog(LOG_CRIT, "Could not become daemon. %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ /* Install signal handler */
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = sighandler;
+
+ if (sigaction(SIGTERM, &sa, NULL) < 0 ||
+ sigaction(SIGHUP, &sa, NULL) < 0 ||
+ sigaction(SIGINT, &sa, NULL) < 0) {
+ syslog(LOG_CRIT, "Could not install signal handlers. %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ sa.sa_handler = SIG_IGN;
+ if (sigaction(SIGPIPE, &sa, NULL) < 0) {
+ syslog(LOG_CRIT, "Could not install signal handlers. %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ sa.sa_handler = SIG_IGN;
+ sa.sa_flags = SA_NOCLDSTOP|SA_NOCLDWAIT;
+ if (sigaction(SIGCHLD, &sa, NULL) < 0) {
+ syslog(LOG_CRIT, "Could not install signal handlers. %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ if (read_config_file() < 0 || read_hids_file() < 0 ||
+ server_init(&srv) < 0 || write_pid_file(pid_file) < 0)
+ exit(1);
+
+ for (done = 0; !done; ) {
+ if (elapsed(tval))
+ client_rescan(&srv);
+
+ if (server_do(&srv) < 0)
+ break;
+ }
+
+ server_shutdown(&srv);
+ remove_pid_file(pid_file);
+ clean_config();
+ closelog();
+
+ return (0);
+}
+
+/*
+ * Write pid file
+ */
+
+static int32_t
+write_pid_file(char const *file)
+{
+ FILE *pid;
+
+ assert(file != NULL);
+
+ if ((pid = fopen(file, "w")) == NULL) {
+ syslog(LOG_ERR, "Could not open file %s. %s (%d)",
+ file, strerror(errno), errno);
+ return (-1);
+ }
+
+ fprintf(pid, "%d", getpid());
+ fclose(pid);
+
+ return (0);
+}
+
+/*
+ * Remote pid file
+ */
+
+static int32_t
+remove_pid_file(char const *file)
+{
+ assert(file != NULL);
+
+ if (unlink(file) < 0) {
+ syslog(LOG_ERR, "Could not unlink file %s. %s (%d)",
+ file, strerror(errno), errno);
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Returns true if desired time interval has elapsed
+ */
+
+static int32_t
+elapsed(int32_t tval)
+{
+ static struct timeval last = { 0, 0 };
+ struct timeval now;
+
+ gettimeofday(&now, NULL);
+
+ if (now.tv_sec - last.tv_sec >= tval) {
+ last = now;
+ return (1);
+ }
+
+ return (0);
+}
+
+/*
+ * Signal handler
+ */
+
+static void
+sighandler(int32_t s)
+{
+ syslog(LOG_NOTICE, "Got signal %d, total number of signals %d",
+ s, ++ done);
+}
+
+/*
+ * Display usage and exit
+ */
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+"Usage: %s [options]\n" \
+"Where options are:\n" \
+" -a address specify address to listen on (default ANY)\n" \
+" -c file specify config file name\n" \
+" -d run in foreground\n" \
+" -H file specify known HIDs file name\n" \
+" -h display this message\n" \
+" -p file specify PID file name\n" \
+" -t tval specify client rescan interval (sec)\n" \
+"", BTHIDD_IDENT);
+ exit(255);
+}
+
diff --git a/usr.sbin/bluetooth/bthidd/bthidd.conf.sample b/usr.sbin/bluetooth/bthidd/bthidd.conf.sample
new file mode 100644
index 0000000..e23dc12
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/bthidd.conf.sample
@@ -0,0 +1,72 @@
+# $FreeBSD$
+
+device {
+ bdaddr 00:50:f2:e5:68:84;
+ control_psm 0x11;
+ interrupt_psm 0x13;
+ reconnect_initiate true;
+ normally_connectable false;
+ hid_descriptor {
+ 0x05 0x01 0x09 0x02 0xa1 0x01 0x85 0x02
+ 0x09 0x01 0xa1 0x00 0x05 0x09 0x19 0x01
+ 0x29 0x05 0x15 0x00 0x25 0x01 0x75 0x01
+ 0x95 0x05 0x81 0x02 0x75 0x03 0x95 0x01
+ 0x81 0x01 0x05 0x01 0x09 0x30 0x09 0x31
+ 0x09 0x38 0x15 0x81 0x25 0x7f 0x75 0x08
+ 0x95 0x03 0x81 0x06 0xc0 0xc0 0x05 0x0c
+ 0x09 0x01 0xa1 0x01 0x85 0x03 0x05 0x01
+ 0x09 0x02 0xa1 0x02 0x06 0x00 0xff 0x15
+ 0x00 0x25 0x03 0x95 0x01 0x75 0x02 0x0a
+ 0x01 0xfe 0x81 0x02 0x75 0x06 0x81 0x01
+ 0xc0 0xc0
+ };
+}
+
+device {
+ bdaddr 00:50:f2:e3:fb:e1;
+ control_psm 0x11;
+ interrupt_psm 0x13;
+ reconnect_initiate true;
+ normally_connectable false;
+ hid_descriptor {
+ 0x05 0x01 0x09 0x06 0xa1 0x01 0x85 0x01
+ 0x05 0x08 0x19 0x01 0x29 0x03 0x15 0x00
+ 0x25 0x01 0x75 0x01 0x95 0x03 0x91 0x02
+ 0x09 0x4b 0x95 0x01 0x91 0x02 0x95 0x04
+ 0x91 0x01 0x05 0x07 0x19 0xe0 0x29 0xe7
+ 0x95 0x08 0x81 0x02 0x75 0x08 0x95 0x01
+ 0x81 0x01 0x19 0x00 0x29 0x91 0x26 0xff
+ 0x00 0x95 0x06 0x81 0x00 0xc0 0x05 0x0c
+ 0x09 0x01 0xa1 0x01 0x85 0x02 0x05 0x0c
+ 0x15 0x00 0x25 0x01 0x75 0x01 0x95 0x1c
+ 0x09 0xe2 0x09 0xb7 0x09 0xcd 0x09 0xea
+ 0x09 0xe9 0x09 0xb6 0x09 0xb5 0x0a 0x83
+ 0x01 0x0a 0x1a 0x02 0x0a 0x79 0x02 0x0a
+ 0xab 0x01 0x0a 0x08 0x02 0x0a 0x02 0x02
+ 0x0a 0x03 0x02 0x0a 0x07 0x02 0x0a 0x01
+ 0x02 0x0a 0x92 0x01 0x0a 0x9c 0x01 0x09
+ 0x95 0x0a 0x23 0x02 0x0a 0x89 0x02 0x0a
+ 0x8b 0x02 0x0a 0x8c 0x02 0x0a 0x8a 0x01
+ 0x0a 0x99 0x01 0x0a 0xa7 0x01 0x0a 0xb6
+ 0x01 0x0a 0xb7 0x01 0x81 0x02 0x75 0x01
+ 0x95 0x04 0x81 0x01 0x06 0x00 0xff 0x0a
+ 0x02 0xff 0x26 0xff 0x00 0x95 0x01 0x75
+ 0x08 0x81 0x02 0xc0 0x05 0x01 0x09 0x80
+ 0xa1 0x01 0x85 0x03 0x19 0x81 0x29 0x83
+ 0x25 0x01 0x95 0x03 0x75 0x01 0x81 0x02
+ 0x95 0x05 0x81 0x01 0xc0 0x05 0x0c 0x09
+ 0x01 0xa1 0x01 0x85 0x04 0x05 0x01 0x09
+ 0x06 0xa1 0x02 0x06 0x00 0xff 0x15 0x00
+ 0x25 0x03 0x95 0x01 0x75 0x02 0x0a 0x01
+ 0xfe 0x81 0x02 0x75 0x06 0x81 0x01 0xc0
+ 0xc0 0x05 0x0c 0x09 0x01 0xa1 0x01 0x85
+ 0x05 0x05 0x01 0x09 0x06 0xa1 0x02 0x06
+ 0x00 0xff 0x25 0x01 0x75 0x01 0x95 0x02
+ 0x0a 0x03 0xfe 0x0a 0x04 0xfe 0x81 0x02
+ 0x95 0x06 0x81 0x01 0xc0 0xc0 0x05 0x0c
+ 0x09 0x01 0xa1 0x01 0x85 0xff 0x05 0x06
+ 0x95 0x01 0x75 0x02 0x19 0x24 0x29 0x26
+ 0x81 0x02 0x75 0x06 0x81 0x01 0xc0
+ };
+}
+
diff --git a/usr.sbin/bluetooth/bthidd/bthidd.h b/usr.sbin/bluetooth/bthidd/bthidd.h
new file mode 100644
index 0000000..3485fc3
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/bthidd.h
@@ -0,0 +1,93 @@
+/*
+ * bthidd.h
+ */
+
+/*-
+ * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: bthidd.h,v 1.7 2006/09/07 21:06:53 max Exp $
+ * $FreeBSD$
+ */
+
+#ifndef _BTHIDD_H_
+#define _BTHIDD_H_ 1
+
+#define BTHIDD_IDENT "bthidd"
+#define BTHIDD_PIDFILE "/var/run/" BTHIDD_IDENT ".pid"
+
+struct bthid_session;
+
+struct bthid_server
+{
+ bdaddr_t bdaddr; /* local bdaddr */
+ int32_t cons; /* /dev/consolectl */
+ int32_t ctrl; /* control channel (listen) */
+ int32_t intr; /* intr. channel (listen) */
+ int32_t maxfd; /* max fd in sets */
+ fd_set rfdset; /* read descriptor set */
+ fd_set wfdset; /* write descriptor set */
+ LIST_HEAD(, bthid_session) sessions;
+};
+
+typedef struct bthid_server bthid_server_t;
+typedef struct bthid_server * bthid_server_p;
+
+struct bthid_session
+{
+ bthid_server_p srv; /* pointer back to server */
+ int32_t ctrl; /* control channel */
+ int32_t intr; /* interrupt channel */
+ int32_t vkbd; /* virual keyboard */
+ bdaddr_t bdaddr;/* remote bdaddr */
+ uint16_t state; /* session state */
+#define CLOSED 0
+#define W4CTRL 1
+#define W4INTR 2
+#define OPEN 3
+ bitstr_t *keys1; /* keys map (new) */
+ bitstr_t *keys2; /* keys map (old) */
+ LIST_ENTRY(bthid_session) next; /* link to next */
+};
+
+typedef struct bthid_session bthid_session_t;
+typedef struct bthid_session * bthid_session_p;
+
+int32_t server_init (bthid_server_p srv);
+void server_shutdown (bthid_server_p srv);
+int32_t server_do (bthid_server_p srv);
+
+int32_t client_rescan (bthid_server_p srv);
+int32_t client_connect (bthid_server_p srv, int fd);
+
+bthid_session_p session_open (bthid_server_p srv, hid_device_p const d);
+bthid_session_p session_by_bdaddr(bthid_server_p srv, bdaddr_p bdaddr);
+bthid_session_p session_by_fd (bthid_server_p srv, int32_t fd);
+void session_close (bthid_session_p s);
+
+int32_t hid_control (bthid_session_p s, uint8_t *data, int32_t len);
+int32_t hid_interrupt (bthid_session_p s, uint8_t *data, int32_t len);
+
+#endif /* ndef _BTHIDD_H_ */
+
diff --git a/usr.sbin/bluetooth/bthidd/client.c b/usr.sbin/bluetooth/bthidd/client.c
new file mode 100644
index 0000000..5f01133
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/client.c
@@ -0,0 +1,259 @@
+/*
+ * client.c
+ */
+
+/*-
+ * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: client.c,v 1.7 2006/09/07 21:06:53 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#include <assert.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <usbhid.h>
+#include "bthid_config.h"
+#include "bthidd.h"
+
+static int32_t client_socket(bdaddr_p bdaddr, uint16_t psm);
+
+/*
+ * Get next config entry and create outbound connection (if required)
+ *
+ * XXX Do only one device at a time. At least one of my devices (3COM
+ * Bluetooth PCCARD) rejects Create_Connection command if another
+ * Create_Connection command is still pending. Weird...
+ */
+
+static int32_t connect_in_progress = 0;
+
+int32_t
+client_rescan(bthid_server_p srv)
+{
+ static hid_device_p d;
+ bthid_session_p s;
+
+ assert(srv != NULL);
+
+ if (connect_in_progress)
+ return (0); /* another connect is still pending */
+
+ d = get_next_hid_device(d);
+ if (d == NULL)
+ return (0); /* XXX should not happen? empty config? */
+
+ if ((s = session_by_bdaddr(srv, &d->bdaddr)) != NULL)
+ return (0); /* session already active */
+
+ if (!d->new_device) {
+ if (d->reconnect_initiate)
+ return (0); /* device will initiate reconnect */
+ }
+
+ syslog(LOG_NOTICE, "Opening outbound session for %s " \
+ "(new_device=%d, reconnect_initiate=%d)",
+ bt_ntoa(&d->bdaddr, NULL), d->new_device, d->reconnect_initiate);
+
+ if ((s = session_open(srv, d)) == NULL) {
+ syslog(LOG_CRIT, "Could not create outbound session for %s",
+ bt_ntoa(&d->bdaddr, NULL));
+ return (-1);
+ }
+
+ /* Open control channel */
+ s->ctrl = client_socket(&s->bdaddr, d->control_psm);
+ if (s->ctrl < 0) {
+ syslog(LOG_ERR, "Could not open control channel to %s. %s (%d)",
+ bt_ntoa(&s->bdaddr, NULL), strerror(errno), errno);
+ session_close(s);
+ return (-1);
+ }
+
+ s->state = W4CTRL;
+
+ FD_SET(s->ctrl, &srv->wfdset);
+ if (s->ctrl > srv->maxfd)
+ srv->maxfd = s->ctrl;
+
+ connect_in_progress = 1;
+
+ return (0);
+}
+
+/*
+ * Process connect on the socket
+ */
+
+int32_t
+client_connect(bthid_server_p srv, int32_t fd)
+{
+ bthid_session_p s;
+ hid_device_p d;
+ int32_t error;
+ socklen_t len;
+
+ assert(srv != NULL);
+ assert(fd >= 0);
+
+ s = session_by_fd(srv, fd);
+ assert(s != NULL);
+
+ d = get_hid_device(&s->bdaddr);
+ assert(d != NULL);
+
+ error = 0;
+ len = sizeof(error);
+ if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
+ syslog(LOG_ERR, "Could not get socket error for %s. %s (%d)",
+ bt_ntoa(&s->bdaddr, NULL), strerror(errno), errno);
+ session_close(s);
+ connect_in_progress = 0;
+
+ return (-1);
+ }
+
+ if (error != 0) {
+ syslog(LOG_ERR, "Could not connect to %s. %s (%d)",
+ bt_ntoa(&s->bdaddr, NULL), strerror(error), error);
+ session_close(s);
+ connect_in_progress = 0;
+
+ return (0);
+ }
+
+ switch (s->state) {
+ case W4CTRL: /* Control channel is open */
+ assert(s->ctrl == fd);
+ assert(s->intr == -1);
+
+ /* Open interrupt channel */
+ s->intr = client_socket(&s->bdaddr, d->interrupt_psm);
+ if (s->intr < 0) {
+ syslog(LOG_ERR, "Could not open interrupt channel " \
+ "to %s. %s (%d)", bt_ntoa(&s->bdaddr, NULL),
+ strerror(errno), errno);
+ session_close(s);
+ connect_in_progress = 0;
+
+ return (-1);
+ }
+
+ s->state = W4INTR;
+
+ FD_SET(s->intr, &srv->wfdset);
+ if (s->intr > srv->maxfd)
+ srv->maxfd = s->intr;
+
+ d->new_device = 0; /* reset new device flag */
+ write_hids_file();
+ break;
+
+ case W4INTR: /* Interrupt channel is open */
+ assert(s->ctrl != -1);
+ assert(s->intr == fd);
+
+ s->state = OPEN;
+ connect_in_progress = 0;
+
+ /* Register session's vkbd descriptor (if any) for read */
+ if (s->state == OPEN && d->keyboard) {
+ assert(s->vkbd != -1);
+
+ FD_SET(s->vkbd, &srv->rfdset);
+ if (s->vkbd > srv->maxfd)
+ srv->maxfd = s->vkbd;
+ }
+ break;
+
+ default:
+ assert(0);
+ break;
+ }
+
+ /* Move fd to from the write fd set into read fd set */
+ FD_CLR(fd, &srv->wfdset);
+ FD_SET(fd, &srv->rfdset);
+
+ return (0);
+}
+
+/*
+ * Create bound non-blocking socket and initiate connect
+ */
+
+static int
+client_socket(bdaddr_p bdaddr, uint16_t psm)
+{
+ struct sockaddr_l2cap l2addr;
+ int32_t s, m;
+
+ s = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP);
+ if (s < 0)
+ return (-1);
+
+ m = fcntl(s, F_GETFL);
+ if (m < 0) {
+ close(s);
+ return (-1);
+ }
+
+ if (fcntl(s, F_SETFL, (m|O_NONBLOCK)) < 0) {
+ close(s);
+ return (-1);
+ }
+
+ l2addr.l2cap_len = sizeof(l2addr);
+ l2addr.l2cap_family = AF_BLUETOOTH;
+ memset(&l2addr.l2cap_bdaddr, 0, sizeof(l2addr.l2cap_bdaddr));
+ l2addr.l2cap_psm = 0;
+ l2addr.l2cap_bdaddr_type = BDADDR_BREDR;
+ l2addr.l2cap_cid = 0;
+
+ if (bind(s, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0) {
+ close(s);
+ return (-1);
+ }
+
+ memcpy(&l2addr.l2cap_bdaddr, bdaddr, sizeof(l2addr.l2cap_bdaddr));
+ l2addr.l2cap_psm = htole16(psm);
+
+ if (connect(s, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0 &&
+ errno != EINPROGRESS) {
+ close(s);
+ return (-1);
+ }
+
+ return (s);
+}
+
diff --git a/usr.sbin/bluetooth/bthidd/hid.c b/usr.sbin/bluetooth/bthidd/hid.c
new file mode 100644
index 0000000..69a6fdc
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/hid.c
@@ -0,0 +1,415 @@
+/*
+ * hid.c
+ */
+
+/*-
+ * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: hid.c,v 1.5 2006/09/07 21:06:53 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/consio.h>
+#include <sys/mouse.h>
+#include <sys/queue.h>
+#include <assert.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <dev/usb/usb.h>
+#include <dev/usb/usbhid.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <usbhid.h>
+#include "bthid_config.h"
+#include "bthidd.h"
+#include "kbd.h"
+
+/*
+ * Process data from control channel
+ */
+
+int32_t
+hid_control(bthid_session_p s, uint8_t *data, int32_t len)
+{
+ assert(s != NULL);
+ assert(data != NULL);
+ assert(len > 0);
+
+ switch (data[0] >> 4) {
+ case 0: /* Handshake (response to command) */
+ if (data[0] & 0xf)
+ syslog(LOG_ERR, "Got handshake message with error " \
+ "response 0x%x from %s",
+ data[0], bt_ntoa(&s->bdaddr, NULL));
+ break;
+
+ case 1: /* HID Control */
+ switch (data[0] & 0xf) {
+ case 0: /* NOP */
+ break;
+
+ case 1: /* Hard reset */
+ case 2: /* Soft reset */
+ syslog(LOG_WARNING, "Device %s requested %s reset",
+ bt_ntoa(&s->bdaddr, NULL),
+ ((data[0] & 0xf) == 1)? "hard" : "soft");
+ break;
+
+ case 3: /* Suspend */
+ syslog(LOG_NOTICE, "Device %s requested Suspend",
+ bt_ntoa(&s->bdaddr, NULL));
+ break;
+
+ case 4: /* Exit suspend */
+ syslog(LOG_NOTICE, "Device %s requested Exit Suspend",
+ bt_ntoa(&s->bdaddr, NULL));
+ break;
+
+ case 5: /* Virtual cable unplug */
+ syslog(LOG_NOTICE, "Device %s unplugged virtual cable",
+ bt_ntoa(&s->bdaddr, NULL));
+ session_close(s);
+ break;
+
+ default:
+ syslog(LOG_WARNING, "Device %s sent unknown " \
+ "HID_Control message 0x%x",
+ bt_ntoa(&s->bdaddr, NULL), data[0]);
+ break;
+ }
+ break;
+
+ default:
+ syslog(LOG_WARNING, "Got unexpected message 0x%x on Control " \
+ "channel from %s", data[0], bt_ntoa(&s->bdaddr, NULL));
+ break;
+ }
+
+ return (0);
+}
+
+/*
+ * Process data from the interrupt channel
+ */
+
+int32_t
+hid_interrupt(bthid_session_p s, uint8_t *data, int32_t len)
+{
+ hid_device_p hid_device;
+ hid_data_t d;
+ hid_item_t h;
+ int32_t report_id, usage, page, val,
+ mouse_x, mouse_y, mouse_z, mouse_butt,
+ mevents, kevents, i;
+
+ assert(s != NULL);
+ assert(s->srv != NULL);
+ assert(data != NULL);
+
+ if (len < 3) {
+ syslog(LOG_ERR, "Got short message (%d bytes) on Interrupt " \
+ "channel from %s", len, bt_ntoa(&s->bdaddr, NULL));
+ return (-1);
+ }
+
+ if (data[0] != 0xa1) {
+ syslog(LOG_ERR, "Got unexpected message 0x%x on " \
+ "Interrupt channel from %s",
+ data[0], bt_ntoa(&s->bdaddr, NULL));
+ return (-1);
+ }
+
+ report_id = data[1];
+ data ++;
+ len --;
+
+ hid_device = get_hid_device(&s->bdaddr);
+ assert(hid_device != NULL);
+
+ mouse_x = mouse_y = mouse_z = mouse_butt = mevents = kevents = 0;
+
+ for (d = hid_start_parse(hid_device->desc, 1 << hid_input, -1);
+ hid_get_item(d, &h) > 0; ) {
+ if ((h.flags & HIO_CONST) || (h.report_ID != report_id) ||
+ (h.kind != hid_input))
+ continue;
+
+ page = HID_PAGE(h.usage);
+ val = hid_get_data(data, &h);
+
+ /*
+ * When the input field is an array and the usage is specified
+ * with a range instead of an ID, we have to derive the actual
+ * usage by using the item value as an index in the usage range
+ * list.
+ */
+ if ((h.flags & HIO_VARIABLE)) {
+ usage = HID_USAGE(h.usage);
+ } else {
+ const uint32_t usage_offset = val - h.logical_minimum;
+ usage = HID_USAGE(h.usage_minimum + usage_offset);
+ }
+
+ switch (page) {
+ case HUP_GENERIC_DESKTOP:
+ switch (usage) {
+ case HUG_X:
+ mouse_x = val;
+ mevents ++;
+ break;
+
+ case HUG_Y:
+ mouse_y = val;
+ mevents ++;
+ break;
+
+ case HUG_WHEEL:
+ mouse_z = -val;
+ mevents ++;
+ break;
+
+ case HUG_SYSTEM_SLEEP:
+ if (val)
+ syslog(LOG_NOTICE, "Sleep button pressed");
+ break;
+ }
+ break;
+
+ case HUP_KEYBOARD:
+ kevents ++;
+
+ if (h.flags & HIO_VARIABLE) {
+ if (val && usage < kbd_maxkey())
+ bit_set(s->keys1, usage);
+ } else {
+ if (val && val < kbd_maxkey())
+ bit_set(s->keys1, val);
+
+ for (i = 1; i < h.report_count; i++) {
+ h.pos += h.report_size;
+ val = hid_get_data(data, &h);
+ if (val && val < kbd_maxkey())
+ bit_set(s->keys1, val);
+ }
+ }
+ break;
+
+ case HUP_BUTTON:
+ if (usage != 0) {
+ if (usage == 2)
+ usage = 3;
+ else if (usage == 3)
+ usage = 2;
+
+ mouse_butt |= (val << (usage - 1));
+ mevents ++;
+ }
+ break;
+
+ case HUP_CONSUMER:
+ if (!val)
+ break;
+
+ switch (usage) {
+ case HUC_AC_PAN:
+ /* Horizontal scroll */
+ if (val < 0)
+ mouse_butt |= (1 << 5);
+ else
+ mouse_butt |= (1 << 6);
+
+ mevents ++;
+ val = 0;
+ break;
+
+ case 0xb5: /* Scan Next Track */
+ val = 0x19;
+ break;
+
+ case 0xb6: /* Scan Previous Track */
+ val = 0x10;
+ break;
+
+ case 0xb7: /* Stop */
+ val = 0x24;
+ break;
+
+ case 0xcd: /* Play/Pause */
+ val = 0x22;
+ break;
+
+ case 0xe2: /* Mute */
+ val = 0x20;
+ break;
+
+ case 0xe9: /* Volume Up */
+ val = 0x30;
+ break;
+
+ case 0xea: /* Volume Down */
+ val = 0x2E;
+ break;
+
+ case 0x183: /* Media Select */
+ val = 0x6D;
+ break;
+
+ case 0x018a: /* Mail */
+ val = 0x6C;
+ break;
+
+ case 0x192: /* Calculator */
+ val = 0x21;
+ break;
+
+ case 0x194: /* My Computer */
+ val = 0x6B;
+ break;
+
+ case 0x221: /* WWW Search */
+ val = 0x65;
+ break;
+
+ case 0x223: /* WWW Home */
+ val = 0x32;
+ break;
+
+ case 0x224: /* WWW Back */
+ val = 0x6A;
+ break;
+
+ case 0x225: /* WWW Forward */
+ val = 0x69;
+ break;
+
+ case 0x226: /* WWW Stop */
+ val = 0x68;
+ break;
+
+ case 0x227: /* WWW Refresh */
+ val = 0x67;
+ break;
+
+ case 0x22a: /* WWW Favorites */
+ val = 0x66;
+ break;
+
+ default:
+ val = 0;
+ break;
+ }
+
+ /* XXX FIXME - UGLY HACK */
+ if (val != 0) {
+ if (hid_device->keyboard) {
+ int32_t buf[4] = { 0xe0, val,
+ 0xe0, val|0x80 };
+
+ assert(s->vkbd != -1);
+ write(s->vkbd, buf, sizeof(buf));
+ } else
+ syslog(LOG_ERR, "Keyboard events " \
+ "received from non-keyboard " \
+ "device %s. Please report",
+ bt_ntoa(&s->bdaddr, NULL));
+ }
+ break;
+
+ case HUP_MICROSOFT:
+ switch (usage) {
+ case 0xfe01:
+ if (!hid_device->battery_power)
+ break;
+
+ switch (val) {
+ case 1:
+ syslog(LOG_INFO, "Battery is OK on %s",
+ bt_ntoa(&s->bdaddr, NULL));
+ break;
+
+ case 2:
+ syslog(LOG_NOTICE, "Low battery on %s",
+ bt_ntoa(&s->bdaddr, NULL));
+ break;
+
+ case 3:
+ syslog(LOG_WARNING, "Very low battery "\
+ "on %s",
+ bt_ntoa(&s->bdaddr, NULL));
+ break;
+ }
+ break;
+ }
+ break;
+ }
+ }
+ hid_end_parse(d);
+
+ /*
+ * XXX FIXME Feed keyboard events into kernel.
+ * The code below works, bit host also needs to track
+ * and handle repeat.
+ *
+ * Key repeat currently works in X, but not in console.
+ */
+
+ if (kevents > 0) {
+ if (hid_device->keyboard) {
+ assert(s->vkbd != -1);
+ kbd_process_keys(s);
+ } else
+ syslog(LOG_ERR, "Keyboard events received from " \
+ "non-keyboard device %s. Please report",
+ bt_ntoa(&s->bdaddr, NULL));
+ }
+
+ /*
+ * XXX FIXME Feed mouse events into kernel.
+ * The code block below works, but it is not good enough.
+ * Need to track double-clicks etc.
+ *
+ * Double click currently works in X, but not in console.
+ */
+
+ if (mevents > 0) {
+ struct mouse_info mi;
+
+ mi.operation = MOUSE_ACTION;
+ mi.u.data.x = mouse_x;
+ mi.u.data.y = mouse_y;
+ mi.u.data.z = mouse_z;
+ mi.u.data.buttons = mouse_butt;
+
+ if (ioctl(s->srv->cons, CONS_MOUSECTL, &mi) < 0)
+ syslog(LOG_ERR, "Could not process mouse events from " \
+ "%s. %s (%d)", bt_ntoa(&s->bdaddr, NULL),
+ strerror(errno), errno);
+ }
+
+ return (0);
+}
diff --git a/usr.sbin/bluetooth/bthidd/kbd.c b/usr.sbin/bluetooth/bthidd/kbd.c
new file mode 100644
index 0000000..cd9f70b
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/kbd.c
@@ -0,0 +1,581 @@
+/*
+ * kbd.c
+ */
+
+/*-
+ * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: kbd.c,v 1.4 2006/09/07 21:06:53 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/consio.h>
+#include <sys/ioctl.h>
+#include <sys/kbio.h>
+#include <sys/queue.h>
+#include <sys/wait.h>
+#include <assert.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <dev/usb/usb.h>
+#include <dev/usb/usbhid.h>
+#include <dev/vkbd/vkbd_var.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <usbhid.h>
+#include "bthid_config.h"
+#include "bthidd.h"
+#include "kbd.h"
+
+static void kbd_write(bitstr_t *m, int32_t fb, int32_t make, int32_t fd);
+static int32_t kbd_xlate(int32_t code, int32_t make, int32_t *b, int32_t const *eob);
+
+/*
+ * HID code to PS/2 set 1 code translation table.
+ *
+ * http://www.microsoft.com/whdc/device/input/Scancode.mspx
+ *
+ * The table only contains "make" (key pressed) codes.
+ * The "break" (key released) code is generated as "make" | 0x80
+ */
+
+#define E0PREFIX (1U << 31)
+#define NOBREAK (1 << 30)
+#define CODEMASK (~(E0PREFIX|NOBREAK))
+
+static int32_t const x[] =
+{
+/*==================================================*/
+/* Name HID code Make Break*/
+/*==================================================*/
+/* No Event 00 */ -1, /* None */
+/* Overrun Error 01 */ NOBREAK|0xFF, /* None */
+/* POST Fail 02 */ NOBREAK|0xFC, /* None */
+/* ErrorUndefined 03 */ -1, /* Unassigned */
+/* a A 04 */ 0x1E, /* 9E */
+/* b B 05 */ 0x30, /* B0 */
+/* c C 06 */ 0x2E, /* AE */
+/* d D 07 */ 0x20, /* A0 */
+/* e E 08 */ 0x12, /* 92 */
+/* f F 09 */ 0x21, /* A1 */
+/* g G 0A */ 0x22, /* A2 */
+/* h H 0B */ 0x23, /* A3 */
+/* i I 0C */ 0x17, /* 97 */
+/* j J 0D */ 0x24, /* A4 */
+/* k K 0E */ 0x25, /* A5 */
+/* l L 0F */ 0x26, /* A6 */
+/* m M 10 */ 0x32, /* B2 */
+/* n N 11 */ 0x31, /* B1 */
+/* o O 12 */ 0x18, /* 98 */
+/* p P 13 */ 0x19, /* 99 */
+/* q Q 14 */ 0x10, /* 90 */
+/* r R 15 */ 0x13, /* 93 */
+/* s S 16 */ 0x1F, /* 9F */
+/* t T 17 */ 0x14, /* 94 */
+/* u U 18 */ 0x16, /* 96 */
+/* v V 19 */ 0x2F, /* AF */
+/* w W 1A */ 0x11, /* 91 */
+/* x X 1B */ 0x2D, /* AD */
+/* y Y 1C */ 0x15, /* 95 */
+/* z Z 1D */ 0x2C, /* AC */
+/* 1 ! 1E */ 0x02, /* 82 */
+/* 2 @ 1F */ 0x03, /* 83 */
+/* 3 # 20 */ 0x04, /* 84 */
+/* 4 $ 21 */ 0x05, /* 85 */
+/* 5 % 22 */ 0x06, /* 86 */
+/* 6 ^ 23 */ 0x07, /* 87 */
+/* 7 & 24 */ 0x08, /* 88 */
+/* 8 * 25 */ 0x09, /* 89 */
+/* 9 ( 26 */ 0x0A, /* 8A */
+/* 0 ) 27 */ 0x0B, /* 8B */
+/* Return 28 */ 0x1C, /* 9C */
+/* Escape 29 */ 0x01, /* 81 */
+/* Backspace 2A */ 0x0E, /* 8E */
+/* Tab 2B */ 0x0F, /* 8F */
+/* Space 2C */ 0x39, /* B9 */
+/* - _ 2D */ 0x0C, /* 8C */
+/* = + 2E */ 0x0D, /* 8D */
+/* [ { 2F */ 0x1A, /* 9A */
+/* ] } 30 */ 0x1B, /* 9B */
+/* \ | 31 */ 0x2B, /* AB */
+/* Europe 1 32 */ 0x2B, /* AB */
+/* ; : 33 */ 0x27, /* A7 */
+/* " ' 34 */ 0x28, /* A8 */
+/* ` ~ 35 */ 0x29, /* A9 */
+/* comma < 36 */ 0x33, /* B3 */
+/* . > 37 */ 0x34, /* B4 */
+/* / ? 38 */ 0x35, /* B5 */
+/* Caps Lock 39 */ 0x3A, /* BA */
+/* F1 3A */ 0x3B, /* BB */
+/* F2 3B */ 0x3C, /* BC */
+/* F3 3C */ 0x3D, /* BD */
+/* F4 3D */ 0x3E, /* BE */
+/* F5 3E */ 0x3F, /* BF */
+/* F6 3F */ 0x40, /* C0 */
+/* F7 40 */ 0x41, /* C1 */
+/* F8 41 */ 0x42, /* C2 */
+/* F9 42 */ 0x43, /* C3 */
+/* F10 43 */ 0x44, /* C4 */
+/* F11 44 */ 0x57, /* D7 */
+/* F12 45 */ 0x58, /* D8 */
+/* Print Screen 46 */ E0PREFIX|0x37, /* E0 B7 */
+/* Scroll Lock 47 */ 0x46, /* C6 */
+#if 0
+/* Break (Ctrl-Pause) 48 */ E0 46 E0 C6, /* None */
+/* Pause 48 */ E1 1D 45 E1 9D C5, /* None */
+#else
+/* Break (Ctrl-Pause)/Pause 48 */ NOBREAK /* Special case */, /* None */
+#endif
+/* Insert 49 */ E0PREFIX|0x52, /* E0 D2 */
+/* Home 4A */ E0PREFIX|0x47, /* E0 C7 */
+/* Page Up 4B */ E0PREFIX|0x49, /* E0 C9 */
+/* Delete 4C */ E0PREFIX|0x53, /* E0 D3 */
+/* End 4D */ E0PREFIX|0x4F, /* E0 CF */
+/* Page Down 4E */ E0PREFIX|0x51, /* E0 D1 */
+/* Right Arrow 4F */ E0PREFIX|0x4D, /* E0 CD */
+/* Left Arrow 50 */ E0PREFIX|0x4B, /* E0 CB */
+/* Down Arrow 51 */ E0PREFIX|0x50, /* E0 D0 */
+/* Up Arrow 52 */ E0PREFIX|0x48, /* E0 C8 */
+/* Num Lock 53 */ 0x45, /* C5 */
+/* Keypad / 54 */ E0PREFIX|0x35, /* E0 B5 */
+/* Keypad * 55 */ 0x37, /* B7 */
+/* Keypad - 56 */ 0x4A, /* CA */
+/* Keypad + 57 */ 0x4E, /* CE */
+/* Keypad Enter 58 */ E0PREFIX|0x1C, /* E0 9C */
+/* Keypad 1 End 59 */ 0x4F, /* CF */
+/* Keypad 2 Down 5A */ 0x50, /* D0 */
+/* Keypad 3 PageDn 5B */ 0x51, /* D1 */
+/* Keypad 4 Left 5C */ 0x4B, /* CB */
+/* Keypad 5 5D */ 0x4C, /* CC */
+/* Keypad 6 Right 5E */ 0x4D, /* CD */
+/* Keypad 7 Home 5F */ 0x47, /* C7 */
+/* Keypad 8 Up 60 */ 0x48, /* C8 */
+/* Keypad 9 PageUp 61 */ 0x49, /* C9 */
+/* Keypad 0 Insert 62 */ 0x52, /* D2 */
+/* Keypad . Delete 63 */ 0x53, /* D3 */
+/* Europe 2 64 */ 0x56, /* D6 */
+/* App 65 */ E0PREFIX|0x5D, /* E0 DD */
+/* Keyboard Power 66 */ E0PREFIX|0x5E, /* E0 DE */
+/* Keypad = 67 */ 0x59, /* D9 */
+/* F13 68 */ 0x64, /* E4 */
+/* F14 69 */ 0x65, /* E5 */
+/* F15 6A */ 0x66, /* E6 */
+/* F16 6B */ 0x67, /* E7 */
+/* F17 6C */ 0x68, /* E8 */
+/* F18 6D */ 0x69, /* E9 */
+/* F19 6E */ 0x6A, /* EA */
+/* F20 6F */ 0x6B, /* EB */
+/* F21 70 */ 0x6C, /* EC */
+/* F22 71 */ 0x6D, /* ED */
+/* F23 72 */ 0x6E, /* EE */
+/* F24 73 */ 0x76, /* F6 */
+/* Keyboard Execute 74 */ -1, /* Unassigned */
+/* Keyboard Help 75 */ -1, /* Unassigned */
+/* Keyboard Menu 76 */ -1, /* Unassigned */
+/* Keyboard Select 77 */ -1, /* Unassigned */
+/* Keyboard Stop 78 */ -1, /* Unassigned */
+/* Keyboard Again 79 */ -1, /* Unassigned */
+/* Keyboard Undo 7A */ -1, /* Unassigned */
+/* Keyboard Cut 7B */ -1, /* Unassigned */
+/* Keyboard Copy 7C */ -1, /* Unassigned */
+/* Keyboard Paste 7D */ -1, /* Unassigned */
+/* Keyboard Find 7E */ -1, /* Unassigned */
+/* Keyboard Mute 7F */ -1, /* Unassigned */
+/* Keyboard Volume Up 80 */ -1, /* Unassigned */
+/* Keyboard Volume Dn 81 */ -1, /* Unassigned */
+/* Keyboard Locking Caps Lock 82 */ -1, /* Unassigned */
+/* Keyboard Locking Num Lock 83 */ -1, /* Unassigned */
+/* Keyboard Locking Scroll Lock 84 */ -1, /* Unassigned */
+/* Keypad comma 85 */ 0x7E, /* FE */
+/* Keyboard Equal Sign 86 */ -1, /* Unassigned */
+/* Keyboard Int'l 1 87 */ 0x73, /* F3 */
+/* Keyboard Int'l 2 88 */ 0x70, /* F0 */
+/* Keyboard Int'l 2 89 */ 0x7D, /* FD */
+/* Keyboard Int'l 4 8A */ 0x79, /* F9 */
+/* Keyboard Int'l 5 8B */ 0x7B, /* FB */
+/* Keyboard Int'l 6 8C */ 0x5C, /* DC */
+/* Keyboard Int'l 7 8D */ -1, /* Unassigned */
+/* Keyboard Int'l 8 8E */ -1, /* Unassigned */
+/* Keyboard Int'l 9 8F */ -1, /* Unassigned */
+/* Keyboard Lang 1 90 */ 0x71, /* Kana */
+/* Keyboard Lang 2 91 */ 0x72, /* Eisu */
+/* Keyboard Lang 3 92 */ 0x78, /* F8 */
+/* Keyboard Lang 4 93 */ 0x77, /* F7 */
+/* Keyboard Lang 5 94 */ 0x76, /* F6 */
+/* Keyboard Lang 6 95 */ -1, /* Unassigned */
+/* Keyboard Lang 7 96 */ -1, /* Unassigned */
+/* Keyboard Lang 8 97 */ -1, /* Unassigned */
+/* Keyboard Lang 9 98 */ -1, /* Unassigned */
+/* Keyboard Alternate Erase 99 */ -1, /* Unassigned */
+/* Keyboard SysReq/Attention 9A */ -1, /* Unassigned */
+/* Keyboard Cancel 9B */ -1, /* Unassigned */
+/* Keyboard Clear 9C */ -1, /* Unassigned */
+/* Keyboard Prior 9D */ -1, /* Unassigned */
+/* Keyboard Return 9E */ -1, /* Unassigned */
+/* Keyboard Separator 9F */ -1, /* Unassigned */
+/* Keyboard Out A0 */ -1, /* Unassigned */
+/* Keyboard Oper A1 */ -1, /* Unassigned */
+/* Keyboard Clear/Again A2 */ -1, /* Unassigned */
+/* Keyboard CrSel/Props A3 */ -1, /* Unassigned */
+/* Keyboard ExSel A4 */ -1, /* Unassigned */
+/* Reserved A5 */ -1, /* Reserved */
+/* Reserved A6 */ -1, /* Reserved */
+/* Reserved A7 */ -1, /* Reserved */
+/* Reserved A8 */ -1, /* Reserved */
+/* Reserved A9 */ -1, /* Reserved */
+/* Reserved AA */ -1, /* Reserved */
+/* Reserved AB */ -1, /* Reserved */
+/* Reserved AC */ -1, /* Reserved */
+/* Reserved AD */ -1, /* Reserved */
+/* Reserved AE */ -1, /* Reserved */
+/* Reserved AF */ -1, /* Reserved */
+/* Reserved B0 */ -1, /* Reserved */
+/* Reserved B1 */ -1, /* Reserved */
+/* Reserved B2 */ -1, /* Reserved */
+/* Reserved B3 */ -1, /* Reserved */
+/* Reserved B4 */ -1, /* Reserved */
+/* Reserved B5 */ -1, /* Reserved */
+/* Reserved B6 */ -1, /* Reserved */
+/* Reserved B7 */ -1, /* Reserved */
+/* Reserved B8 */ -1, /* Reserved */
+/* Reserved B9 */ -1, /* Reserved */
+/* Reserved BA */ -1, /* Reserved */
+/* Reserved BB */ -1, /* Reserved */
+/* Reserved BC */ -1, /* Reserved */
+/* Reserved BD */ -1, /* Reserved */
+/* Reserved BE */ -1, /* Reserved */
+/* Reserved BF */ -1, /* Reserved */
+/* Reserved C0 */ -1, /* Reserved */
+/* Reserved C1 */ -1, /* Reserved */
+/* Reserved C2 */ -1, /* Reserved */
+/* Reserved C3 */ -1, /* Reserved */
+/* Reserved C4 */ -1, /* Reserved */
+/* Reserved C5 */ -1, /* Reserved */
+/* Reserved C6 */ -1, /* Reserved */
+/* Reserved C7 */ -1, /* Reserved */
+/* Reserved C8 */ -1, /* Reserved */
+/* Reserved C9 */ -1, /* Reserved */
+/* Reserved CA */ -1, /* Reserved */
+/* Reserved CB */ -1, /* Reserved */
+/* Reserved CC */ -1, /* Reserved */
+/* Reserved CD */ -1, /* Reserved */
+/* Reserved CE */ -1, /* Reserved */
+/* Reserved CF */ -1, /* Reserved */
+/* Reserved D0 */ -1, /* Reserved */
+/* Reserved D1 */ -1, /* Reserved */
+/* Reserved D2 */ -1, /* Reserved */
+/* Reserved D3 */ -1, /* Reserved */
+/* Reserved D4 */ -1, /* Reserved */
+/* Reserved D5 */ -1, /* Reserved */
+/* Reserved D6 */ -1, /* Reserved */
+/* Reserved D7 */ -1, /* Reserved */
+/* Reserved D8 */ -1, /* Reserved */
+/* Reserved D9 */ -1, /* Reserved */
+/* Reserved DA */ -1, /* Reserved */
+/* Reserved DB */ -1, /* Reserved */
+/* Reserved DC */ -1, /* Reserved */
+/* Reserved DD */ -1, /* Reserved */
+/* Reserved DE */ -1, /* Reserved */
+/* Reserved DF */ -1, /* Reserved */
+/* Left Control E0 */ 0x1D, /* 9D */
+/* Left Shift E1 */ 0x2A, /* AA */
+/* Left Alt E2 */ 0x38, /* B8 */
+/* Left GUI E3 */ E0PREFIX|0x5B, /* E0 DB */
+/* Right Control E4 */ E0PREFIX|0x1D, /* E0 9D */
+/* Right Shift E5 */ 0x36, /* B6 */
+/* Right Alt E6 */ E0PREFIX|0x38, /* E0 B8 */
+/* Right GUI E7 */ E0PREFIX|0x5C /* E0 DC */
+};
+
+#define xsize ((int32_t)(sizeof(x)/sizeof(x[0])))
+
+/*
+ * Get a max HID keycode (aligned)
+ */
+
+int32_t
+kbd_maxkey(void)
+{
+ return (xsize);
+}
+
+/*
+ * Process keys
+ */
+
+int32_t
+kbd_process_keys(bthid_session_p s)
+{
+ bitstr_t diff[bitstr_size(xsize)];
+ int32_t f1, f2, i;
+
+ assert(s != NULL);
+ assert(s->srv != NULL);
+
+ /* Check if the new keys have been pressed */
+ bit_ffs(s->keys1, xsize, &f1);
+
+ /* Check if old keys still pressed */
+ bit_ffs(s->keys2, xsize, &f2);
+
+ if (f1 == -1) {
+ /* no new key pressed */
+ if (f2 != -1) {
+ /* release old keys */
+ kbd_write(s->keys2, f2, 0, s->vkbd);
+ memset(s->keys2, 0, bitstr_size(xsize));
+ }
+
+ return (0);
+ }
+
+ if (f2 == -1) {
+ /* no old keys, but new keys pressed */
+ assert(f1 != -1);
+
+ memcpy(s->keys2, s->keys1, bitstr_size(xsize));
+ kbd_write(s->keys1, f1, 1, s->vkbd);
+ memset(s->keys1, 0, bitstr_size(xsize));
+
+ return (0);
+ }
+
+ /* new keys got pressed, old keys got released */
+ memset(diff, 0, bitstr_size(xsize));
+
+ for (i = f2; i < xsize; i ++) {
+ if (bit_test(s->keys2, i)) {
+ if (!bit_test(s->keys1, i)) {
+ bit_clear(s->keys2, i);
+ bit_set(diff, i);
+ }
+ }
+ }
+
+ for (i = f1; i < xsize; i++) {
+ if (bit_test(s->keys1, i)) {
+ if (!bit_test(s->keys2, i))
+ bit_set(s->keys2, i);
+ else
+ bit_clear(s->keys1, i);
+ }
+ }
+
+ bit_ffs(diff, xsize, &f2);
+ if (f2 > 0)
+ kbd_write(diff, f2, 0, s->vkbd);
+
+ bit_ffs(s->keys1, xsize, &f1);
+ if (f1 > 0) {
+ kbd_write(s->keys1, f1, 1, s->vkbd);
+ memset(s->keys1, 0, bitstr_size(xsize));
+ }
+
+ return (0);
+}
+
+/*
+ * Translate given keymap and write keyscodes
+ */
+
+static void
+kbd_write(bitstr_t *m, int32_t fb, int32_t make, int32_t fd)
+{
+ int32_t i, *b, *eob, n, buf[64];
+
+ b = buf;
+ eob = b + sizeof(buf)/sizeof(buf[0]);
+ i = fb;
+
+ while (i < xsize) {
+ if (bit_test(m, i)) {
+ n = kbd_xlate(i, make, b, eob);
+ if (n == -1) {
+ write(fd, buf, (b - buf) * sizeof(buf[0]));
+ b = buf;
+ continue;
+ }
+
+ b += n;
+ }
+
+ i ++;
+ }
+
+ if (b != buf)
+ write(fd, buf, (b - buf) * sizeof(buf[0]));
+}
+
+/*
+ * Translate HID code into PS/2 code and put codes into buffer b.
+ * Returns the number of codes put in b. Return -1 if buffer has not
+ * enough space.
+ */
+
+#undef PUT
+#define PUT(c, n, b, eob) \
+do { \
+ if ((b) >= (eob)) \
+ return (-1); \
+ *(b) = (c); \
+ (b) ++; \
+ (n) ++; \
+} while (0)
+
+static int32_t
+kbd_xlate(int32_t code, int32_t make, int32_t *b, int32_t const *eob)
+{
+ int32_t c, n;
+
+ n = 0;
+
+ if (code >= xsize)
+ return (0); /* HID code is not in the table */
+
+ /* Handle special case - Pause/Break */
+ if (code == 0x48) {
+ if (!make)
+ return (0); /* No break code */
+
+#if 0
+XXX FIXME
+ if (ctrl_is_pressed) {
+ /* Break (Ctrl-Pause) */
+ PUT(0xe0, n, b, eob);
+ PUT(0x46, n, b, eob);
+ PUT(0xe0, n, b, eob);
+ PUT(0xc6, n, b, eob);
+ } else {
+ /* Pause */
+ PUT(0xe1, n, b, eob);
+ PUT(0x1d, n, b, eob);
+ PUT(0x45, n, b, eob);
+ PUT(0xe1, n, b, eob);
+ PUT(0x9d, n, b, eob);
+ PUT(0xc5, n, b, eob);
+ }
+#endif
+
+ return (n);
+ }
+
+ if ((c = x[code]) == -1)
+ return (0); /* HID code translation is not defined */
+
+ if (make) {
+ if (c & E0PREFIX)
+ PUT(0xe0, n, b, eob);
+
+ PUT((c & CODEMASK), n, b, eob);
+ } else if (!(c & NOBREAK)) {
+ if (c & E0PREFIX)
+ PUT(0xe0, n, b, eob);
+
+ PUT((0x80|(c & CODEMASK)), n, b, eob);
+ }
+
+ return (n);
+}
+
+/*
+ * Process status change from vkbd(4)
+ */
+
+int32_t
+kbd_status_changed(bthid_session_p s, uint8_t *data, int32_t len)
+{
+ vkbd_status_t st;
+ uint8_t leds, report_id;
+ hid_device_p hid_device;
+ hid_data_t d;
+ hid_item_t h;
+
+ assert(s != NULL);
+ assert(len == sizeof(vkbd_status_t));
+
+ memcpy(&st, data, sizeof(st));
+ leds = 0;
+ report_id = NO_REPORT_ID;
+
+ hid_device = get_hid_device(&s->bdaddr);
+ assert(hid_device != NULL);
+
+ for (d = hid_start_parse(hid_device->desc, 1 << hid_output, -1);
+ hid_get_item(d, &h) > 0; ) {
+ if (HID_PAGE(h.usage) == HUP_LEDS) {
+ if (report_id == NO_REPORT_ID)
+ report_id = h.report_ID;
+ else if (h.report_ID != report_id)
+ syslog(LOG_WARNING, "Output HID report IDs " \
+ "for %s do not match: %d vs. %d. " \
+ "Please report",
+ bt_ntoa(&s->bdaddr, NULL),
+ h.report_ID, report_id);
+
+ switch(HID_USAGE(h.usage)) {
+ case 0x01: /* Num Lock LED */
+ if (st.leds & LED_NUM)
+ hid_set_data(&leds, &h, 1);
+ break;
+
+ case 0x02: /* Caps Lock LED */
+ if (st.leds & LED_CAP)
+ hid_set_data(&leds, &h, 1);
+ break;
+
+ case 0x03: /* Scroll Lock LED */
+ if (st.leds & LED_SCR)
+ hid_set_data(&leds, &h, 1);
+ break;
+
+ /* XXX add other LEDs ? */
+ }
+ }
+ }
+ hid_end_parse(d);
+
+ data[0] = 0xa2; /* DATA output (HID output report) */
+
+ if (report_id != NO_REPORT_ID) {
+ data[1] = report_id;
+ data[2] = leds;
+ len = 3;
+ } else {
+ data[1] = leds;
+ len = 2;
+ }
+
+ write(s->intr, data, len);
+
+ return (0);
+}
+
diff --git a/usr.sbin/bluetooth/bthidd/kbd.h b/usr.sbin/bluetooth/bthidd/kbd.h
new file mode 100644
index 0000000..552f310
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/kbd.h
@@ -0,0 +1,41 @@
+/*
+ * kbd.h
+ */
+
+/*-
+ * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: kbd.h,v 1.3 2006/09/07 21:06:53 max Exp $
+ * $FreeBSD$
+ */
+
+#ifndef _KBD_H_
+#define _KBD_H_
+
+int32_t kbd_maxkey (void);
+int32_t kbd_process_keys (bthid_session_p s);
+int32_t kbd_status_changed(bthid_session_p s, uint8_t *data, int32_t len);
+
+#endif /* ndef _KBD_H_ */
diff --git a/usr.sbin/bluetooth/bthidd/lexer.l b/usr.sbin/bluetooth/bthidd/lexer.l
new file mode 100644
index 0000000..6d913ee
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/lexer.l
@@ -0,0 +1,106 @@
+%{
+/*
+ * lexer.l
+ */
+
+/*-
+ * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: lexer.l,v 1.3 2006/09/07 21:06:53 max Exp $
+ * $FreeBSD$
+ */
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <stdlib.h>
+#include "parser.h"
+
+ int yylex (void);
+
+#define YY_DECL int yylex(void)
+%}
+
+%option yylineno noyywrap nounput noinput
+
+delim [ \t\n]
+ws {delim}+
+empty {delim}*
+comment \#.*
+
+hexdigit [0-9a-fA-F]
+hexbyte {hexdigit}{hexdigit}?
+
+device_word device
+bdaddr_word bdaddr
+control_psm_word control_psm
+interrupt_psm_word interrupt_psm
+reconnect_initiate_word reconnect_initiate
+battery_power_word battery_power
+normally_connectable_word normally_connectable
+hid_descriptor_word hid_descriptor
+true_word true
+false_word false
+
+bdaddrstring {hexbyte}:{hexbyte}:{hexbyte}:{hexbyte}:{hexbyte}:{hexbyte}
+hexbytestring 0x{hexbyte}
+
+%%
+
+\; return (';');
+\: return (':');
+\{ return ('{');
+\} return ('}');
+
+{ws} ;
+{empty} ;
+{comment} ;
+
+{device_word} return (T_DEVICE);
+{bdaddr_word} return (T_BDADDR);
+{control_psm_word} return (T_CONTROL_PSM);
+{interrupt_psm_word} return (T_INTERRUPT_PSM);
+{reconnect_initiate_word} return (T_RECONNECT_INITIATE);
+{battery_power_word} return (T_BATTERY_POWER);
+{normally_connectable_word} return (T_NORMALLY_CONNECTABLE);
+{hid_descriptor_word} return (T_HID_DESCRIPTOR);
+{true_word} return (T_TRUE);
+{false_word} return (T_FALSE);
+
+{bdaddrstring} {
+ return (bt_aton(yytext, &yylval.bdaddr)?
+ T_BDADDRSTRING : T_ERROR);
+ }
+
+{hexbytestring} {
+ char *ep;
+
+ yylval.num = strtoul(yytext, &ep, 16);
+
+ return (*ep == '\0'? T_HEXBYTE : T_ERROR);
+ }
+
+. return (T_ERROR);
+
+%%
+
diff --git a/usr.sbin/bluetooth/bthidd/parser.y b/usr.sbin/bluetooth/bthidd/parser.y
new file mode 100644
index 0000000..dbb2763
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/parser.y
@@ -0,0 +1,476 @@
+%{
+/*
+ * parser.y
+ */
+
+/*-
+ * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: parser.y,v 1.7 2006/09/07 21:06:53 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <dev/usb/usb.h>
+#include <dev/usb/usbhid.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <usbhid.h>
+
+#ifndef BTHIDCONTROL
+#include <stdarg.h>
+#include <syslog.h>
+#define SYSLOG syslog
+#define LOGCRIT LOG_CRIT
+#define LOGERR LOG_ERR
+#define LOGWARNING LOG_WARNING
+#define EOL
+#else
+#define SYSLOG fprintf
+#define LOGCRIT stderr
+#define LOGERR stderr
+#define LOGWARNING stderr
+#define EOL "\n"
+#endif /* ndef BTHIDCONTROL */
+
+#include "bthid_config.h"
+
+ int yylex (void);
+ void yyerror (char const *);
+static int32_t check_hid_device(hid_device_p hid_device);
+static void free_hid_device (hid_device_p hid_device);
+
+extern FILE *yyin;
+extern int yylineno;
+ char const *config_file = BTHIDD_CONFFILE;
+ char const *hids_file = BTHIDD_HIDSFILE;
+
+static char buffer[1024];
+static int32_t hid_descriptor_size;
+static hid_device_t *hid_device = NULL;
+static LIST_HEAD(, hid_device) hid_devices;
+
+%}
+
+%union {
+ bdaddr_t bdaddr;
+ int32_t num;
+}
+
+%token <bdaddr> T_BDADDRSTRING
+%token <num> T_HEXBYTE
+%token T_DEVICE T_BDADDR T_CONTROL_PSM T_INTERRUPT_PSM T_RECONNECT_INITIATE
+%token T_BATTERY_POWER T_NORMALLY_CONNECTABLE T_HID_DESCRIPTOR
+%token T_TRUE T_FALSE T_ERROR
+
+%%
+
+config: line
+ | config line
+ ;
+
+line: T_DEVICE
+ {
+ hid_device = (hid_device_t *) calloc(1, sizeof(*hid_device));
+ if (hid_device == NULL) {
+ SYSLOG(LOGCRIT, "Could not allocate new " \
+ "config entry" EOL);
+ YYABORT;
+ }
+
+ hid_device->new_device = 1;
+ }
+ '{' options '}'
+ {
+ if (check_hid_device(hid_device))
+ LIST_INSERT_HEAD(&hid_devices,hid_device,next);
+ else
+ free_hid_device(hid_device);
+
+ hid_device = NULL;
+ }
+ ;
+
+options: option ';'
+ | options option ';'
+ ;
+
+option: bdaddr
+ | control_psm
+ | interrupt_psm
+ | reconnect_initiate
+ | battery_power
+ | normally_connectable
+ | hid_descriptor
+ | parser_error
+ ;
+
+bdaddr: T_BDADDR T_BDADDRSTRING
+ {
+ memcpy(&hid_device->bdaddr, &$2, sizeof(hid_device->bdaddr));
+ }
+ ;
+
+control_psm: T_CONTROL_PSM T_HEXBYTE
+ {
+ hid_device->control_psm = $2;
+ }
+ ;
+
+interrupt_psm: T_INTERRUPT_PSM T_HEXBYTE
+ {
+ hid_device->interrupt_psm = $2;
+ }
+ ;
+
+reconnect_initiate: T_RECONNECT_INITIATE T_TRUE
+ {
+ hid_device->reconnect_initiate = 1;
+ }
+ | T_RECONNECT_INITIATE T_FALSE
+ {
+ hid_device->reconnect_initiate = 0;
+ }
+ ;
+
+battery_power: T_BATTERY_POWER T_TRUE
+ {
+ hid_device->battery_power = 1;
+ }
+ | T_BATTERY_POWER T_FALSE
+ {
+ hid_device->battery_power = 0;
+ }
+ ;
+
+normally_connectable: T_NORMALLY_CONNECTABLE T_TRUE
+ {
+ hid_device->normally_connectable = 1;
+ }
+ | T_NORMALLY_CONNECTABLE T_FALSE
+ {
+ hid_device->normally_connectable = 0;
+ }
+ ;
+
+hid_descriptor: T_HID_DESCRIPTOR
+ {
+ hid_descriptor_size = 0;
+ }
+ '{' hid_descriptor_bytes '}'
+ {
+ if (hid_device->desc != NULL)
+ hid_dispose_report_desc(hid_device->desc);
+
+ hid_device->desc = hid_use_report_desc((unsigned char *) buffer, hid_descriptor_size);
+ if (hid_device->desc == NULL) {
+ SYSLOG(LOGCRIT, "Could not use HID descriptor" EOL);
+ YYABORT;
+ }
+ }
+ ;
+
+hid_descriptor_bytes: hid_descriptor_byte
+ | hid_descriptor_bytes hid_descriptor_byte
+ ;
+
+hid_descriptor_byte: T_HEXBYTE
+ {
+ if (hid_descriptor_size >= (int32_t) sizeof(buffer)) {
+ SYSLOG(LOGCRIT, "HID descriptor is too big" EOL);
+ YYABORT;
+ }
+
+ buffer[hid_descriptor_size ++] = $1;
+ }
+ ;
+
+parser_error: T_ERROR
+ {
+ YYABORT;
+ }
+
+%%
+
+/* Display parser error message */
+void
+yyerror(char const *message)
+{
+ SYSLOG(LOGERR, "%s in line %d" EOL, message, yylineno);
+}
+
+/* Re-read config file */
+int32_t
+read_config_file(void)
+{
+ int32_t e;
+
+ if (config_file == NULL) {
+ SYSLOG(LOGERR, "Unknown config file name!" EOL);
+ return (-1);
+ }
+
+ if ((yyin = fopen(config_file, "r")) == NULL) {
+ SYSLOG(LOGERR, "Could not open config file '%s'. %s (%d)" EOL,
+ config_file, strerror(errno), errno);
+ return (-1);
+ }
+
+ clean_config();
+ if (yyparse() < 0) {
+ SYSLOG(LOGERR, "Could not parse config file '%s'" EOL,
+ config_file);
+ e = -1;
+ } else
+ e = 0;
+
+ fclose(yyin);
+ yyin = NULL;
+
+ return (e);
+}
+
+/* Clean config */
+void
+clean_config(void)
+{
+ while (!LIST_EMPTY(&hid_devices)) {
+ hid_device_p d = LIST_FIRST(&hid_devices);
+
+ LIST_REMOVE(d, next);
+ free_hid_device(d);
+ }
+}
+
+/* Lookup config entry */
+hid_device_p
+get_hid_device(bdaddr_p bdaddr)
+{
+ hid_device_p d;
+
+ LIST_FOREACH(d, &hid_devices, next)
+ if (memcmp(&d->bdaddr, bdaddr, sizeof(bdaddr_t)) == 0)
+ break;
+
+ return (d);
+}
+
+/* Get next config entry */
+hid_device_p
+get_next_hid_device(hid_device_p d)
+{
+ return ((d == NULL)? LIST_FIRST(&hid_devices) : LIST_NEXT(d, next));
+}
+
+/* Print config entry */
+void
+print_hid_device(hid_device_p d, FILE *f)
+{
+ /* XXX FIXME hack! */
+ struct report_desc {
+ unsigned int size;
+ unsigned char data[1];
+ };
+ /* XXX FIXME hack! */
+
+ struct report_desc *desc = (struct report_desc *) d->desc;
+ uint32_t i;
+
+ fprintf(f,
+"device {\n" \
+" bdaddr %s;\n" \
+" control_psm 0x%x;\n" \
+" interrupt_psm 0x%x;\n" \
+" reconnect_initiate %s;\n" \
+" battery_power %s;\n" \
+" normally_connectable %s;\n" \
+" hid_descriptor {",
+ bt_ntoa(&d->bdaddr, NULL),
+ d->control_psm, d->interrupt_psm,
+ d->reconnect_initiate? "true" : "false",
+ d->battery_power? "true" : "false",
+ d->normally_connectable? "true" : "false");
+
+ for (i = 0; i < desc->size; i ++) {
+ if ((i % 8) == 0)
+ fprintf(f, "\n ");
+
+ fprintf(f, "0x%2.2x ", desc->data[i]);
+ }
+
+ fprintf(f,
+"\n" \
+" };\n" \
+"}\n");
+}
+
+/* Check config entry */
+static int32_t
+check_hid_device(hid_device_p d)
+{
+ hid_data_t hd;
+ hid_item_t hi;
+ int32_t page;
+
+ if (get_hid_device(&d->bdaddr) != NULL) {
+ SYSLOG(LOGERR, "Ignoring duplicated entry for bdaddr %s" EOL,
+ bt_ntoa(&d->bdaddr, NULL));
+ return (0);
+ }
+
+ if (d->control_psm == 0) {
+ SYSLOG(LOGERR, "Ignoring entry with invalid control PSM" EOL);
+ return (0);
+ }
+
+ if (d->interrupt_psm == 0) {
+ SYSLOG(LOGERR, "Ignoring entry with invalid interrupt PSM" EOL);
+ return (0);
+ }
+
+ if (d->desc == NULL) {
+ SYSLOG(LOGERR, "Ignoring entry without HID descriptor" EOL);
+ return (0);
+ }
+
+ /* XXX somehow need to make sure descriptor is valid */
+ for (hd = hid_start_parse(d->desc, ~0, -1); hid_get_item(hd, &hi) > 0; ) {
+ switch (hi.kind) {
+ case hid_collection:
+ case hid_endcollection:
+ case hid_output:
+ case hid_feature:
+ break;
+
+ case hid_input:
+ /* Check if the device may send keystrokes */
+ page = HID_PAGE(hi.usage);
+ if (page == HUP_KEYBOARD)
+ d->keyboard = 1;
+ break;
+ }
+ }
+ hid_end_parse(hd);
+
+ return (1);
+}
+
+/* Free config entry */
+static void
+free_hid_device(hid_device_p d)
+{
+ if (d->desc != NULL)
+ hid_dispose_report_desc(d->desc);
+
+ memset(d, 0, sizeof(*d));
+ free(d);
+}
+
+/* Re-read hids file */
+int32_t
+read_hids_file(void)
+{
+ FILE *f;
+ hid_device_t *d;
+ char *line;
+ bdaddr_t bdaddr;
+ int32_t lineno;
+
+ if (hids_file == NULL) {
+ SYSLOG(LOGERR, "Unknown HIDs file name!" EOL);
+ return (-1);
+ }
+
+ if ((f = fopen(hids_file, "r")) == NULL) {
+ if (errno == ENOENT)
+ return (0);
+
+ SYSLOG(LOGERR, "Could not open HIDs file '%s'. %s (%d)" EOL,
+ hids_file, strerror(errno), errno);
+ return (-1);
+ }
+
+ for (lineno = 1; fgets(buffer, sizeof(buffer), f) != NULL; lineno ++) {
+ if ((line = strtok(buffer, "\r\n\t ")) == NULL)
+ continue; /* ignore empty lines */
+
+ if (!bt_aton(line, &bdaddr)) {
+ SYSLOG(LOGWARNING, "Ignoring unparseable BD_ADDR in " \
+ "%s:%d" EOL, hids_file, lineno);
+ continue;
+ }
+
+ if ((d = get_hid_device(&bdaddr)) != NULL)
+ d->new_device = 0;
+ }
+
+ fclose(f);
+
+ return (0);
+}
+
+/* Write hids file */
+int32_t
+write_hids_file(void)
+{
+ char path[PATH_MAX];
+ FILE *f;
+ hid_device_t *d;
+
+ if (hids_file == NULL) {
+ SYSLOG(LOGERR, "Unknown HIDs file name!" EOL);
+ return (-1);
+ }
+
+ snprintf(path, sizeof(path), "%s.new", hids_file);
+
+ if ((f = fopen(path, "w")) == NULL) {
+ SYSLOG(LOGERR, "Could not open HIDs file '%s'. %s (%d)" EOL,
+ path, strerror(errno), errno);
+ return (-1);
+ }
+
+ LIST_FOREACH(d, &hid_devices, next)
+ if (!d->new_device)
+ fprintf(f, "%s\n", bt_ntoa(&d->bdaddr, NULL));
+
+ fclose(f);
+
+ if (rename(path, hids_file) < 0) {
+ SYSLOG(LOGERR, "Could not rename new HIDs file '%s' to '%s'. " \
+ "%s (%d)" EOL, path, hids_file, strerror(errno), errno);
+ unlink(path);
+ return (-1);
+ }
+
+ return (0);
+}
+
diff --git a/usr.sbin/bluetooth/bthidd/server.c b/usr.sbin/bluetooth/bthidd/server.c
new file mode 100644
index 0000000..26aeb4a
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/server.c
@@ -0,0 +1,352 @@
+/*
+ * server.c
+ */
+
+/*-
+ * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: server.c,v 1.9 2006/09/07 21:06:53 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#include <assert.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <dev/vkbd/vkbd_var.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <usbhid.h>
+#include "bthid_config.h"
+#include "bthidd.h"
+#include "kbd.h"
+
+#undef max
+#define max(x, y) (((x) > (y))? (x) : (y))
+
+static int32_t server_accept (bthid_server_p srv, int32_t fd);
+static int32_t server_process(bthid_server_p srv, int32_t fd);
+
+/*
+ * Initialize server
+ */
+
+int32_t
+server_init(bthid_server_p srv)
+{
+ struct sockaddr_l2cap l2addr;
+
+ assert(srv != NULL);
+
+ srv->ctrl = srv->intr = -1;
+ FD_ZERO(&srv->rfdset);
+ FD_ZERO(&srv->wfdset);
+ LIST_INIT(&srv->sessions);
+
+ /* Open /dev/consolectl */
+ srv->cons = open("/dev/consolectl", O_RDWR);
+ if (srv->cons < 0) {
+ syslog(LOG_ERR, "Could not open /dev/consolectl. %s (%d)",
+ strerror(errno), errno);
+ return (-1);
+ }
+
+ /* Create control socket */
+ srv->ctrl = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP);
+ if (srv->ctrl < 0) {
+ syslog(LOG_ERR, "Could not create control L2CAP socket. " \
+ "%s (%d)", strerror(errno), errno);
+ close(srv->cons);
+ return (-1);
+ }
+
+ l2addr.l2cap_len = sizeof(l2addr);
+ l2addr.l2cap_family = AF_BLUETOOTH;
+ memcpy(&l2addr.l2cap_bdaddr, &srv->bdaddr, sizeof(l2addr.l2cap_bdaddr));
+ l2addr.l2cap_psm = htole16(0x11);
+ l2addr.l2cap_bdaddr_type = BDADDR_BREDR;
+ l2addr.l2cap_cid = 0;
+
+ if (bind(srv->ctrl, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0) {
+ syslog(LOG_ERR, "Could not bind control L2CAP socket. " \
+ "%s (%d)", strerror(errno), errno);
+ close(srv->ctrl);
+ close(srv->cons);
+ return (-1);
+ }
+
+ if (listen(srv->ctrl, 10) < 0) {
+ syslog(LOG_ERR, "Could not listen on control L2CAP socket. " \
+ "%s (%d)", strerror(errno), errno);
+ close(srv->ctrl);
+ close(srv->cons);
+ return (-1);
+ }
+
+ /* Create intrrupt socket */
+ srv->intr = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP);
+ if (srv->intr < 0) {
+ syslog(LOG_ERR, "Could not create interrupt L2CAP socket. " \
+ "%s (%d)", strerror(errno), errno);
+ close(srv->ctrl);
+ close(srv->cons);
+ return (-1);
+ }
+
+ l2addr.l2cap_psm = htole16(0x13);
+
+ if (bind(srv->intr, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0) {
+ syslog(LOG_ERR, "Could not bind interrupt L2CAP socket. " \
+ "%s (%d)", strerror(errno), errno);
+ close(srv->intr);
+ close(srv->ctrl);
+ close(srv->cons);
+ return (-1);
+ }
+
+ if (listen(srv->intr, 10) < 0) {
+ syslog(LOG_ERR, "Could not listen on interrupt L2CAP socket. "\
+ "%s (%d)", strerror(errno), errno);
+ close(srv->intr);
+ close(srv->ctrl);
+ close(srv->cons);
+ return (-1);
+ }
+
+ FD_SET(srv->ctrl, &srv->rfdset);
+ FD_SET(srv->intr, &srv->rfdset);
+ srv->maxfd = max(srv->ctrl, srv->intr);
+
+ return (0);
+}
+
+/*
+ * Shutdown server
+ */
+
+void
+server_shutdown(bthid_server_p srv)
+{
+ assert(srv != NULL);
+
+ close(srv->cons);
+ close(srv->ctrl);
+ close(srv->intr);
+
+ while (!LIST_EMPTY(&srv->sessions))
+ session_close(LIST_FIRST(&srv->sessions));
+
+ memset(srv, 0, sizeof(*srv));
+}
+
+/*
+ * Do one server iteration
+ */
+
+int32_t
+server_do(bthid_server_p srv)
+{
+ struct timeval tv;
+ fd_set rfdset, wfdset;
+ int32_t n, fd;
+
+ assert(srv != NULL);
+
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+
+ /* Copy cached version of the fd sets and call select */
+ memcpy(&rfdset, &srv->rfdset, sizeof(rfdset));
+ memcpy(&wfdset, &srv->wfdset, sizeof(wfdset));
+
+ n = select(srv->maxfd + 1, &rfdset, &wfdset, NULL, &tv);
+ if (n < 0) {
+ if (errno == EINTR)
+ return (0);
+
+ syslog(LOG_ERR, "Could not select(%d, %p, %p). %s (%d)",
+ srv->maxfd + 1, &rfdset, &wfdset, strerror(errno), errno);
+
+ return (-1);
+ }
+
+ /* Process descriptors (if any) */
+ for (fd = 0; fd < srv->maxfd + 1 && n > 0; fd ++) {
+ if (FD_ISSET(fd, &rfdset)) {
+ n --;
+
+ if (fd == srv->ctrl || fd == srv->intr)
+ server_accept(srv, fd);
+ else
+ server_process(srv, fd);
+ } else if (FD_ISSET(fd, &wfdset)) {
+ n --;
+
+ client_connect(srv, fd);
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * Accept new connection
+ */
+
+static int32_t
+server_accept(bthid_server_p srv, int32_t fd)
+{
+ bthid_session_p s;
+ hid_device_p d;
+ struct sockaddr_l2cap l2addr;
+ int32_t new_fd;
+ socklen_t len;
+
+ len = sizeof(l2addr);
+ if ((new_fd = accept(fd, (struct sockaddr *) &l2addr, &len)) < 0) {
+ syslog(LOG_ERR, "Could not accept %s connection. %s (%d)",
+ (fd == srv->ctrl)? "control" : "interrupt",
+ strerror(errno), errno);
+ return (-1);
+ }
+
+ /* Is device configured? */
+ if ((d = get_hid_device(&l2addr.l2cap_bdaddr)) == NULL) {
+ syslog(LOG_ERR, "Rejecting %s connection from %s. " \
+ "Device not configured",
+ (fd == srv->ctrl)? "control" : "interrupt",
+ bt_ntoa(&l2addr.l2cap_bdaddr, NULL));
+ close(new_fd);
+ return (-1);
+ }
+
+ /* Check if we have session for the device */
+ if ((s = session_by_bdaddr(srv, &l2addr.l2cap_bdaddr)) == NULL) {
+ d->new_device = 0; /* reset new device flag */
+ write_hids_file();
+
+ /* Create new inbound session */
+ if ((s = session_open(srv, d)) == NULL) {
+ syslog(LOG_CRIT, "Could not open inbound session "
+ "for %s", bt_ntoa(&l2addr.l2cap_bdaddr, NULL));
+ close(new_fd);
+ return (-1);
+ }
+ }
+
+ /* Update descriptors */
+ if (fd == srv->ctrl) {
+ assert(s->ctrl == -1);
+ s->ctrl = new_fd;
+ s->state = (s->intr == -1)? W4INTR : OPEN;
+ } else {
+ assert(s->intr == -1);
+ s->intr = new_fd;
+ s->state = (s->ctrl == -1)? W4CTRL : OPEN;
+ }
+
+ FD_SET(new_fd, &srv->rfdset);
+ if (new_fd > srv->maxfd)
+ srv->maxfd = new_fd;
+
+ syslog(LOG_NOTICE, "Accepted %s connection from %s",
+ (fd == srv->ctrl)? "control" : "interrupt",
+ bt_ntoa(&l2addr.l2cap_bdaddr, NULL));
+
+ /* Register session's vkbd descriptor (if needed) for read */
+ if (s->state == OPEN && d->keyboard) {
+ assert(s->vkbd != -1);
+
+ FD_SET(s->vkbd, &srv->rfdset);
+ if (s->vkbd > srv->maxfd)
+ srv->maxfd = s->vkbd;
+ }
+
+ return (0);
+}
+
+/*
+ * Process data on the connection
+ */
+
+static int32_t
+server_process(bthid_server_p srv, int32_t fd)
+{
+ bthid_session_p s = session_by_fd(srv, fd);
+ int32_t len, to_read;
+ int32_t (*cb)(bthid_session_p, uint8_t *, int32_t);
+ union {
+ uint8_t b[1024];
+ vkbd_status_t s;
+ } data;
+
+ if (s == NULL)
+ return (0); /* can happen on device disconnect */
+
+
+ if (fd == s->ctrl) {
+ cb = hid_control;
+ to_read = sizeof(data.b);
+ } else if (fd == s->intr) {
+ cb = hid_interrupt;
+ to_read = sizeof(data.b);
+ } else {
+ assert(fd == s->vkbd);
+
+ cb = kbd_status_changed;
+ to_read = sizeof(data.s);
+ }
+
+ do {
+ len = read(fd, &data, to_read);
+ } while (len < 0 && errno == EINTR);
+
+ if (len < 0) {
+ syslog(LOG_ERR, "Could not read data from %s (%s). %s (%d)",
+ bt_ntoa(&s->bdaddr, NULL),
+ (fd == s->ctrl)? "control" : "interrupt",
+ strerror(errno), errno);
+ session_close(s);
+ return (0);
+ }
+
+ if (len == 0) {
+ syslog(LOG_NOTICE, "Remote device %s has closed %s connection",
+ bt_ntoa(&s->bdaddr, NULL),
+ (fd == s->ctrl)? "control" : "interrupt");
+ session_close(s);
+ return (0);
+ }
+
+ (*cb)(s, (uint8_t *) &data, len);
+
+ return (0);
+}
+
diff --git a/usr.sbin/bluetooth/bthidd/session.c b/usr.sbin/bluetooth/bthidd/session.c
new file mode 100644
index 0000000..260cb86
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/session.c
@@ -0,0 +1,185 @@
+/*
+ * session.c
+ */
+
+/*-
+ * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: session.c,v 1.3 2006/09/07 21:06:53 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#include <assert.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <usbhid.h>
+#include "bthid_config.h"
+#include "bthidd.h"
+#include "kbd.h"
+
+/*
+ * Create new session
+ */
+
+bthid_session_p
+session_open(bthid_server_p srv, hid_device_p const d)
+{
+ bthid_session_p s;
+
+ assert(srv != NULL);
+ assert(d != NULL);
+
+ if ((s = (bthid_session_p) malloc(sizeof(*s))) == NULL)
+ return (NULL);
+
+ s->srv = srv;
+ memcpy(&s->bdaddr, &d->bdaddr, sizeof(s->bdaddr));
+ s->ctrl = -1;
+ s->intr = -1;
+
+ if (d->keyboard) {
+ /* Open /dev/vkbdctl */
+ s->vkbd = open("/dev/vkbdctl", O_RDWR);
+ if (s->vkbd < 0) {
+ syslog(LOG_ERR, "Could not open /dev/vkbdctl " \
+ "for %s. %s (%d)", bt_ntoa(&s->bdaddr, NULL),
+ strerror(errno), errno);
+ free(s);
+ return (NULL);
+ }
+ } else
+ s->vkbd = -1;
+
+ s->state = CLOSED;
+
+ s->keys1 = bit_alloc(kbd_maxkey());
+ if (s->keys1 == NULL) {
+ free(s);
+ return (NULL);
+ }
+
+ s->keys2 = bit_alloc(kbd_maxkey());
+ if (s->keys2 == NULL) {
+ free(s->keys1);
+ free(s);
+ return (NULL);
+ }
+
+ LIST_INSERT_HEAD(&srv->sessions, s, next);
+
+ return (s);
+}
+
+/*
+ * Lookup session by bdaddr
+ */
+
+bthid_session_p
+session_by_bdaddr(bthid_server_p srv, bdaddr_p bdaddr)
+{
+ bthid_session_p s;
+
+ assert(srv != NULL);
+ assert(bdaddr != NULL);
+
+ LIST_FOREACH(s, &srv->sessions, next)
+ if (memcmp(&s->bdaddr, bdaddr, sizeof(s->bdaddr)) == 0)
+ break;
+
+ return (s);
+}
+
+/*
+ * Lookup session by fd
+ */
+
+bthid_session_p
+session_by_fd(bthid_server_p srv, int32_t fd)
+{
+ bthid_session_p s;
+
+ assert(srv != NULL);
+ assert(fd >= 0);
+
+ LIST_FOREACH(s, &srv->sessions, next)
+ if (s->ctrl == fd || s->intr == fd || s->vkbd == fd)
+ break;
+
+ return (s);
+}
+
+/*
+ * Close session
+ */
+
+void
+session_close(bthid_session_p s)
+{
+ assert(s != NULL);
+ assert(s->srv != NULL);
+
+ LIST_REMOVE(s, next);
+
+ if (s->intr != -1) {
+ FD_CLR(s->intr, &s->srv->rfdset);
+ FD_CLR(s->intr, &s->srv->wfdset);
+ close(s->intr);
+
+ if (s->srv->maxfd == s->intr)
+ s->srv->maxfd --;
+ }
+
+ if (s->ctrl != -1) {
+ FD_CLR(s->ctrl, &s->srv->rfdset);
+ FD_CLR(s->ctrl, &s->srv->wfdset);
+ close(s->ctrl);
+
+ if (s->srv->maxfd == s->ctrl)
+ s->srv->maxfd --;
+ }
+
+ if (s->vkbd != -1) {
+ FD_CLR(s->vkbd, &s->srv->rfdset);
+ close(s->vkbd);
+
+ if (s->srv->maxfd == s->vkbd)
+ s->srv->maxfd --;
+ }
+
+ free(s->keys1);
+ free(s->keys2);
+
+ memset(s, 0, sizeof(*s));
+ free(s);
+}
+
diff --git a/usr.sbin/bluetooth/btpand/Makefile b/usr.sbin/bluetooth/btpand/Makefile
new file mode 100644
index 0000000..0689d17
--- /dev/null
+++ b/usr.sbin/bluetooth/btpand/Makefile
@@ -0,0 +1,12 @@
+# $NetBSD: Makefile,v 1.2 2008/08/18 08:25:32 plunky Exp $
+# $FreeBSD$
+
+PROG= btpand
+MAN= btpand.8
+SRCS= btpand.c bnep.c channel.c client.c event.c packet.c server.c sdp.c tap.c
+
+WARNS?= 3
+
+LIBADD= bluetooth sdp util
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bluetooth/btpand/Makefile.depend b/usr.sbin/bluetooth/btpand/Makefile.depend
new file mode 100644
index 0000000..4633528
--- /dev/null
+++ b/usr.sbin/bluetooth/btpand/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbluetooth \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libsdp \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bluetooth/btpand/bnep.c b/usr.sbin/bluetooth/btpand/bnep.c
new file mode 100644
index 0000000..4065b1b
--- /dev/null
+++ b/usr.sbin/bluetooth/btpand/bnep.c
@@ -0,0 +1,756 @@
+/* $NetBSD: bnep.c,v 1.1 2008/08/17 13:20:57 plunky Exp $ */
+
+/*-
+ * Copyright (c) 2008 Iain Hibbert
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: bnep.c,v 1.1 2008/08/17 13:20:57 plunky Exp $");
+
+#include <sys/uio.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <sdp.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "btpand.h"
+#include "bnep.h"
+
+static bool bnep_recv_extension(packet_t *);
+static size_t bnep_recv_control(channel_t *, uint8_t *, size_t, bool);
+static size_t bnep_recv_control_command_not_understood(channel_t *, uint8_t *, size_t);
+static size_t bnep_recv_setup_connection_req(channel_t *, uint8_t *, size_t);
+static size_t bnep_recv_setup_connection_rsp(channel_t *, uint8_t *, size_t);
+static size_t bnep_recv_filter_net_type_set(channel_t *, uint8_t *, size_t);
+static size_t bnep_recv_filter_net_type_rsp(channel_t *, uint8_t *, size_t);
+static size_t bnep_recv_filter_multi_addr_set(channel_t *, uint8_t *, size_t);
+static size_t bnep_recv_filter_multi_addr_rsp(channel_t *, uint8_t *, size_t);
+
+static bool bnep_pfilter(channel_t *, packet_t *);
+static bool bnep_mfilter(channel_t *, packet_t *);
+
+static uint8_t NAP_UUID[] = {
+ 0x00, 0x00, 0x11, 0x16,
+ 0x00, 0x00,
+ 0x10, 0x00,
+ 0x80, 0x00,
+ 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb
+};
+
+static uint8_t GN_UUID[] = {
+ 0x00, 0x00, 0x11, 0x17,
+ 0x00, 0x00,
+ 0x10, 0x00,
+ 0x80, 0x00,
+ 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb,
+};
+
+static uint8_t PANU_UUID[] = {
+ 0x00, 0x00, 0x11, 0x15,
+ 0x00, 0x00,
+ 0x10, 0x00,
+ 0x80, 0x00,
+ 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb
+};
+
+/*
+ * receive BNEP packet
+ * return true if packet is to be forwarded
+ */
+bool
+bnep_recv(packet_t *pkt)
+{
+ size_t len;
+ uint8_t type;
+
+ if (pkt->len < 1)
+ return false;
+
+ type = pkt->ptr[0];
+ packet_adj(pkt, 1);
+
+ switch (BNEP_TYPE(type)) {
+ case BNEP_GENERAL_ETHERNET:
+ if (pkt->len < (ETHER_ADDR_LEN * 2) + ETHER_TYPE_LEN) {
+ log_debug("dropped short packet (type 0x%2.2x)", type);
+ return false;
+ }
+
+ pkt->dst = pkt->ptr;
+ packet_adj(pkt, ETHER_ADDR_LEN);
+ pkt->src = pkt->ptr;
+ packet_adj(pkt, ETHER_ADDR_LEN);
+ pkt->type = pkt->ptr;
+ packet_adj(pkt, ETHER_TYPE_LEN);
+ break;
+
+ case BNEP_CONTROL:
+ len = bnep_recv_control(pkt->chan, pkt->ptr, pkt->len, false);
+ if (len == 0)
+ return false;
+
+ packet_adj(pkt, len);
+ break;
+
+ case BNEP_COMPRESSED_ETHERNET:
+ if (pkt->len < ETHER_TYPE_LEN) {
+ log_debug("dropped short packet (type 0x%2.2x)", type);
+ return false;
+ }
+
+ pkt->dst = pkt->chan->laddr;
+ pkt->src = pkt->chan->raddr;
+ pkt->type = pkt->ptr;
+ packet_adj(pkt, ETHER_TYPE_LEN);
+ break;
+
+ case BNEP_COMPRESSED_ETHERNET_SRC_ONLY:
+ if (pkt->len < ETHER_ADDR_LEN + ETHER_TYPE_LEN) {
+ log_debug("dropped short packet (type 0x%2.2x)", type);
+ return false;
+ }
+
+ pkt->dst = pkt->chan->laddr;
+ pkt->src = pkt->ptr;
+ packet_adj(pkt, ETHER_ADDR_LEN);
+ pkt->type = pkt->ptr;
+ packet_adj(pkt, ETHER_TYPE_LEN);
+ break;
+
+ case BNEP_COMPRESSED_ETHERNET_DST_ONLY:
+ if (pkt->len < ETHER_ADDR_LEN + ETHER_TYPE_LEN) {
+ log_debug("dropped short packet (type 0x%2.2x)", type);
+ return false;
+ }
+
+ pkt->dst = pkt->ptr;
+ packet_adj(pkt, ETHER_ADDR_LEN);
+ pkt->src = pkt->chan->raddr;
+ pkt->type = pkt->ptr;
+ packet_adj(pkt, ETHER_TYPE_LEN);
+ break;
+
+ default:
+ /*
+ * Any packet containing a reserved BNEP
+ * header packet type SHALL be dropped.
+ */
+
+ log_debug("dropped packet with reserved type 0x%2.2x", type);
+ return false;
+ }
+
+ if (BNEP_TYPE_EXT(type)
+ && !bnep_recv_extension(pkt))
+ return false; /* invalid extensions */
+
+ if (BNEP_TYPE(type) == BNEP_CONTROL
+ || pkt->chan->state != CHANNEL_OPEN)
+ return false; /* no forwarding */
+
+ return true;
+}
+
+static bool
+bnep_recv_extension(packet_t *pkt)
+{
+ exthdr_t *eh;
+ size_t len, size;
+ uint8_t type;
+
+ do {
+ if (pkt->len < 2)
+ return false;
+
+ type = pkt->ptr[0];
+ size = pkt->ptr[1];
+
+ if (pkt->len < size + 2)
+ return false;
+
+ switch (type) {
+ case BNEP_EXTENSION_CONTROL:
+ len = bnep_recv_control(pkt->chan, pkt->ptr + 2, size, true);
+ if (len != size)
+ log_err("ignored spurious data in exthdr");
+
+ break;
+
+ default:
+ /* Unknown extension headers in data packets */
+ /* SHALL be forwarded irrespective of any */
+ /* network protocol or multicast filter settings */
+ /* and any local filtering policy. */
+
+ eh = malloc(sizeof(exthdr_t));
+ if (eh == NULL) {
+ log_err("exthdr malloc() failed: %m");
+ break;
+ }
+
+ eh->ptr = pkt->ptr;
+ eh->len = size;
+ STAILQ_INSERT_TAIL(&pkt->extlist, eh, next);
+ break;
+ }
+
+ packet_adj(pkt, size + 2);
+ } while (BNEP_TYPE_EXT(type));
+
+ return true;
+}
+
+static size_t
+bnep_recv_control(channel_t *chan, uint8_t *ptr, size_t size, bool isext)
+{
+ uint8_t type;
+ size_t len;
+
+ if (size-- < 1)
+ return 0;
+
+ type = *ptr++;
+
+ switch (type) {
+ case BNEP_CONTROL_COMMAND_NOT_UNDERSTOOD:
+ len = bnep_recv_control_command_not_understood(chan, ptr, size);
+ break;
+
+ case BNEP_SETUP_CONNECTION_REQUEST:
+ if (isext)
+ return 0; /* not allowed in extension headers */
+
+ len = bnep_recv_setup_connection_req(chan, ptr, size);
+ break;
+
+ case BNEP_SETUP_CONNECTION_RESPONSE:
+ if (isext)
+ return 0; /* not allowed in extension headers */
+
+ len = bnep_recv_setup_connection_rsp(chan, ptr, size);
+ break;
+
+ case BNEP_FILTER_NET_TYPE_SET:
+ len = bnep_recv_filter_net_type_set(chan, ptr, size);
+ break;
+
+ case BNEP_FILTER_NET_TYPE_RESPONSE:
+ len = bnep_recv_filter_net_type_rsp(chan, ptr, size);
+ break;
+
+ case BNEP_FILTER_MULTI_ADDR_SET:
+ len = bnep_recv_filter_multi_addr_set(chan, ptr, size);
+ break;
+
+ case BNEP_FILTER_MULTI_ADDR_RESPONSE:
+ len = bnep_recv_filter_multi_addr_rsp(chan, ptr, size);
+ break;
+
+ default:
+ len = 0;
+ break;
+ }
+
+ if (len == 0)
+ bnep_send_control(chan, BNEP_CONTROL_COMMAND_NOT_UNDERSTOOD, type);
+
+ return len;
+}
+
+static size_t
+bnep_recv_control_command_not_understood(channel_t *chan, uint8_t *ptr, size_t size)
+{
+ uint8_t type;
+
+ if (size < 1)
+ return 0;
+
+ type = *ptr++;
+ log_err("received Control Command Not Understood (0x%2.2x)", type);
+
+ /* we didn't send any reserved commands, just cut them off */
+ channel_close(chan);
+
+ return 1;
+}
+
+static size_t
+bnep_recv_setup_connection_req(channel_t *chan, uint8_t *ptr, size_t size)
+{
+ uint8_t off;
+ int src, dst, rsp;
+ size_t len;
+
+ if (size < 1)
+ return 0;
+
+ len = *ptr++;
+ if (size < (len * 2 + 1))
+ return 0;
+
+ if (chan->state != CHANNEL_WAIT_CONNECT_REQ
+ && chan->state != CHANNEL_OPEN) {
+ log_debug("ignored");
+ return (len * 2 + 1);
+ }
+
+ if (len == 2)
+ off = 2;
+ else if (len == 4)
+ off = 0;
+ else if (len == 16)
+ off = 0;
+ else {
+ rsp = BNEP_SETUP_INVALID_UUID_SIZE;
+ goto done;
+ }
+
+ if (memcmp(ptr, NAP_UUID + off, len) == 0)
+ dst = SDP_SERVICE_CLASS_NAP;
+ else if (memcmp(ptr, GN_UUID + off, len) == 0)
+ dst = SDP_SERVICE_CLASS_GN;
+ else if (memcmp(ptr, PANU_UUID + off, len) == 0)
+ dst = SDP_SERVICE_CLASS_PANU;
+ else
+ dst = 0;
+
+ if (dst != service_class) {
+ rsp = BNEP_SETUP_INVALID_DST_UUID;
+ goto done;
+ }
+
+ ptr += len;
+
+ if (memcmp(ptr, NAP_UUID + off, len) == 0)
+ src = SDP_SERVICE_CLASS_NAP;
+ else if (memcmp(ptr, GN_UUID + off, len) == 0)
+ src = SDP_SERVICE_CLASS_GN;
+ else if (memcmp(ptr, PANU_UUID + off, len) == 0)
+ src = SDP_SERVICE_CLASS_PANU;
+ else
+ src = 0;
+
+ if ((dst != SDP_SERVICE_CLASS_PANU && src != SDP_SERVICE_CLASS_PANU)
+ || src == 0) {
+ rsp = BNEP_SETUP_INVALID_SRC_UUID;
+ goto done;
+ }
+
+ rsp = BNEP_SETUP_SUCCESS;
+ chan->state = CHANNEL_OPEN;
+ channel_timeout(chan, 0);
+
+done:
+ log_debug("addr %s response 0x%2.2x",
+ ether_ntoa((struct ether_addr *)chan->raddr), rsp);
+
+ bnep_send_control(chan, BNEP_SETUP_CONNECTION_RESPONSE, rsp);
+ return (len * 2 + 1);
+}
+
+static size_t
+bnep_recv_setup_connection_rsp(channel_t *chan, uint8_t *ptr, size_t size)
+{
+ int rsp;
+
+ if (size < 2)
+ return 0;
+
+ rsp = be16dec(ptr);
+
+ if (chan->state != CHANNEL_WAIT_CONNECT_RSP) {
+ log_debug("ignored");
+ return 2;
+ }
+
+ log_debug("addr %s response 0x%2.2x",
+ ether_ntoa((struct ether_addr *)chan->raddr), rsp);
+
+ if (rsp == BNEP_SETUP_SUCCESS) {
+ chan->state = CHANNEL_OPEN;
+ channel_timeout(chan, 0);
+ } else {
+ channel_close(chan);
+ }
+
+ return 2;
+}
+
+static size_t
+bnep_recv_filter_net_type_set(channel_t *chan, uint8_t *ptr, size_t size)
+{
+ pfilter_t *pf;
+ int i, nf, rsp;
+ size_t len;
+
+ if (size < 2)
+ return 0;
+
+ len = be16dec(ptr);
+ ptr += 2;
+
+ if (size < (len + 2))
+ return 0;
+
+ if (chan->state != CHANNEL_OPEN) {
+ log_debug("ignored");
+ return (len + 2);
+ }
+
+ nf = len / 4;
+ pf = malloc(nf * sizeof(pfilter_t));
+ if (pf == NULL) {
+ rsp = BNEP_FILTER_TOO_MANY_FILTERS;
+ goto done;
+ }
+
+ log_debug("nf = %d", nf);
+
+ for (i = 0; i < nf; i++) {
+ pf[i].start = be16dec(ptr);
+ ptr += 2;
+ pf[i].end = be16dec(ptr);
+ ptr += 2;
+
+ if (pf[i].start > pf[i].end) {
+ free(pf);
+ rsp = BNEP_FILTER_INVALID_RANGE;
+ goto done;
+ }
+
+ log_debug("pf[%d] = %#4.4x, %#4.4x", i, pf[i].start, pf[i].end);
+ }
+
+ if (chan->pfilter)
+ free(chan->pfilter);
+
+ chan->pfilter = pf;
+ chan->npfilter = nf;
+
+ rsp = BNEP_FILTER_SUCCESS;
+
+done:
+ log_debug("addr %s response 0x%2.2x",
+ ether_ntoa((struct ether_addr *)chan->raddr), rsp);
+
+ bnep_send_control(chan, BNEP_FILTER_NET_TYPE_RESPONSE, rsp);
+ return (len + 2);
+}
+
+static size_t
+bnep_recv_filter_net_type_rsp(channel_t *chan, uint8_t *ptr, size_t size)
+{
+ int rsp;
+
+ if (size < 2)
+ return 0;
+
+ if (chan->state != CHANNEL_OPEN) {
+ log_debug("ignored");
+ return 2;
+ }
+
+ rsp = be16dec(ptr);
+
+ log_debug("addr %s response 0x%2.2x",
+ ether_ntoa((struct ether_addr *)chan->raddr), rsp);
+
+ /* we did not send any filter_net_type_set message */
+ return 2;
+}
+
+static size_t
+bnep_recv_filter_multi_addr_set(channel_t *chan, uint8_t *ptr, size_t size)
+{
+ mfilter_t *mf;
+ int i, nf, rsp;
+ size_t len;
+
+ if (size < 2)
+ return 0;
+
+ len = be16dec(ptr);
+ ptr += 2;
+
+ if (size < (len + 2))
+ return 0;
+
+ if (chan->state != CHANNEL_OPEN) {
+ log_debug("ignored");
+ return (len + 2);
+ }
+
+ nf = len / (ETHER_ADDR_LEN * 2);
+ mf = malloc(nf * sizeof(mfilter_t));
+ if (mf == NULL) {
+ rsp = BNEP_FILTER_TOO_MANY_FILTERS;
+ goto done;
+ }
+
+ log_debug("nf = %d", nf);
+
+ for (i = 0; i < nf; i++) {
+ memcpy(mf[i].start, ptr, ETHER_ADDR_LEN);
+ ptr += ETHER_ADDR_LEN;
+
+ memcpy(mf[i].end, ptr, ETHER_ADDR_LEN);
+ ptr += ETHER_ADDR_LEN;
+
+ if (memcmp(mf[i].start, mf[i].end, ETHER_ADDR_LEN) > 0) {
+ free(mf);
+ rsp = BNEP_FILTER_INVALID_RANGE;
+ goto done;
+ }
+
+ log_debug("pf[%d] = "
+ "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x, "
+ "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x", i,
+ mf[i].start[0], mf[i].start[1], mf[i].start[2],
+ mf[i].start[3], mf[i].start[4], mf[i].start[5],
+ mf[i].end[0], mf[i].end[1], mf[i].end[2],
+ mf[i].end[3], mf[i].end[4], mf[i].end[5]);
+ }
+
+ if (chan->mfilter)
+ free(chan->mfilter);
+
+ chan->mfilter = mf;
+ chan->nmfilter = nf;
+
+ rsp = BNEP_FILTER_SUCCESS;
+
+done:
+ log_debug("addr %s response 0x%2.2x",
+ ether_ntoa((struct ether_addr *)chan->raddr), rsp);
+
+ bnep_send_control(chan, BNEP_FILTER_MULTI_ADDR_RESPONSE, rsp);
+ return (len + 2);
+}
+
+static size_t
+bnep_recv_filter_multi_addr_rsp(channel_t *chan, uint8_t *ptr, size_t size)
+{
+ int rsp;
+
+ if (size < 2)
+ return false;
+
+ if (chan->state != CHANNEL_OPEN) {
+ log_debug("ignored");
+ return 2;
+ }
+
+ rsp = be16dec(ptr);
+ log_debug("addr %s response 0x%2.2x",
+ ether_ntoa((struct ether_addr *)chan->raddr), rsp);
+
+ /* we did not send any filter_multi_addr_set message */
+ return 2;
+}
+
+void
+bnep_send_control(channel_t *chan, uint8_t type, ...)
+{
+ packet_t *pkt;
+ uint8_t *p;
+ va_list ap;
+
+ assert(chan->state != CHANNEL_CLOSED);
+
+ pkt = packet_alloc(chan);
+ if (pkt == NULL)
+ return;
+
+ p = pkt->ptr;
+ va_start(ap, type);
+
+ *p++ = BNEP_CONTROL;
+ *p++ = type;
+
+ switch(type) {
+ case BNEP_CONTROL_COMMAND_NOT_UNDERSTOOD:
+ *p++ = va_arg(ap, int);
+ break;
+
+ case BNEP_SETUP_CONNECTION_REQUEST:
+ *p++ = va_arg(ap, int);
+ be16enc(p, va_arg(ap, int));
+ p += 2;
+ be16enc(p, va_arg(ap, int));
+ p += 2;
+ break;
+
+ case BNEP_SETUP_CONNECTION_RESPONSE:
+ case BNEP_FILTER_NET_TYPE_RESPONSE:
+ case BNEP_FILTER_MULTI_ADDR_RESPONSE:
+ be16enc(p, va_arg(ap, int));
+ p += 2;
+ break;
+
+ case BNEP_FILTER_NET_TYPE_SET: /* TODO */
+ case BNEP_FILTER_MULTI_ADDR_SET: /* TODO */
+ default:
+ log_err("Can't send control type 0x%2.2x", type);
+ break;
+ }
+
+ va_end(ap);
+ pkt->len = p - pkt->ptr;
+
+ channel_put(chan, pkt);
+ packet_free(pkt);
+}
+
+/*
+ * BNEP send packet routine
+ * return true if packet can be removed from queue
+ */
+bool
+bnep_send(channel_t *chan, packet_t *pkt)
+{
+ struct iovec iov[2];
+ uint8_t *p, *type, *proto;
+ exthdr_t *eh;
+ bool src, dst;
+ size_t nw;
+
+ if (pkt->type == NULL) {
+ iov[0].iov_base = pkt->ptr;
+ iov[0].iov_len = pkt->len;
+ iov[1].iov_base = NULL;
+ iov[1].iov_len = 0;
+ } else {
+ p = chan->sendbuf;
+
+ dst = (memcmp(pkt->dst, chan->raddr, ETHER_ADDR_LEN) != 0);
+ src = (memcmp(pkt->src, chan->laddr, ETHER_ADDR_LEN) != 0);
+
+ type = p;
+ p += 1;
+
+ if (dst && src)
+ *type = BNEP_GENERAL_ETHERNET;
+ else if (dst && !src)
+ *type = BNEP_COMPRESSED_ETHERNET_DST_ONLY;
+ else if (!dst && src)
+ *type = BNEP_COMPRESSED_ETHERNET_SRC_ONLY;
+ else /* (!dst && !src) */
+ *type = BNEP_COMPRESSED_ETHERNET;
+
+ if (dst) {
+ memcpy(p, pkt->dst, ETHER_ADDR_LEN);
+ p += ETHER_ADDR_LEN;
+ }
+
+ if (src) {
+ memcpy(p, pkt->src, ETHER_ADDR_LEN);
+ p += ETHER_ADDR_LEN;
+ }
+
+ proto = p;
+ memcpy(p, pkt->type, ETHER_TYPE_LEN);
+ p += ETHER_TYPE_LEN;
+
+ STAILQ_FOREACH(eh, &pkt->extlist, next) {
+ if (p + eh->len > chan->sendbuf + chan->mtu)
+ break;
+
+ *type |= BNEP_EXT;
+ type = p;
+
+ memcpy(p, eh->ptr, eh->len);
+ p += eh->len;
+ }
+
+ *type &= ~BNEP_EXT;
+
+ iov[0].iov_base = chan->sendbuf;
+ iov[0].iov_len = (p - chan->sendbuf);
+
+ if ((chan->npfilter == 0 || bnep_pfilter(chan, pkt))
+ && (chan->nmfilter == 0 || bnep_mfilter(chan, pkt))) {
+ iov[1].iov_base = pkt->ptr;
+ iov[1].iov_len = pkt->len;
+ } else if (be16dec(proto) == ETHERTYPE_VLAN
+ && pkt->len >= ETHER_VLAN_ENCAP_LEN) {
+ iov[1].iov_base = pkt->ptr;
+ iov[1].iov_len = ETHER_VLAN_ENCAP_LEN;
+ } else {
+ iov[1].iov_base = NULL;
+ iov[1].iov_len = 0;
+ memset(proto, 0, ETHER_TYPE_LEN);
+ }
+ }
+
+ if (iov[0].iov_len + iov[1].iov_len > chan->mtu) {
+ log_err("packet exceeded MTU (dropped)");
+ return false;
+ }
+
+ nw = writev(chan->fd, iov, __arraycount(iov));
+ return (nw > 0);
+}
+
+static bool
+bnep_pfilter(channel_t *chan, packet_t *pkt)
+{
+ int proto, i;
+
+ proto = be16dec(pkt->type);
+ if (proto == ETHERTYPE_VLAN) { /* IEEE 802.1Q tag header */
+ if (pkt->len < 4)
+ return false;
+
+ proto = be16dec(pkt->ptr + 2);
+ }
+
+ for (i = 0; i < chan->npfilter; i++) {
+ if (chan->pfilter[i].start <= proto
+ && chan->pfilter[i].end >=proto)
+ return true;
+ }
+
+ return false;
+}
+
+static bool
+bnep_mfilter(channel_t *chan, packet_t *pkt)
+{
+ int i;
+
+ if (!ETHER_IS_MULTICAST(pkt->dst))
+ return true;
+
+ for (i = 0; i < chan->nmfilter; i++) {
+ if (memcmp(pkt->dst, chan->mfilter[i].start, ETHER_ADDR_LEN) >= 0
+ && memcmp(pkt->dst, chan->mfilter[i].end, ETHER_ADDR_LEN) <= 0)
+ return true;
+ }
+
+ return false;
+}
diff --git a/usr.sbin/bluetooth/btpand/bnep.h b/usr.sbin/bluetooth/btpand/bnep.h
new file mode 100644
index 0000000..2fc6a77
--- /dev/null
+++ b/usr.sbin/bluetooth/btpand/bnep.h
@@ -0,0 +1,72 @@
+/* $NetBSD: bnep.h,v 1.1 2008/08/17 13:20:57 plunky Exp $ */
+
+/*-
+ * Copyright (c) 2008 Iain Hibbert
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$ */
+
+/*
+ * Constants defined in the Bluetooth Network Encapsulation
+ * Protocol (BNEP) specification v1.0
+ */
+
+#define BNEP_MTU_MIN 1691
+
+#define BNEP_EXT 0x80
+#define BNEP_TYPE(x) ((x) & 0x7f)
+#define BNEP_TYPE_EXT(x) (((x) & BNEP_EXT) == BNEP_EXT)
+
+/* BNEP packet types */
+#define BNEP_GENERAL_ETHERNET 0x00
+#define BNEP_CONTROL 0x01
+#define BNEP_COMPRESSED_ETHERNET 0x02
+#define BNEP_COMPRESSED_ETHERNET_SRC_ONLY 0x03
+#define BNEP_COMPRESSED_ETHERNET_DST_ONLY 0x04
+
+/* BNEP extension header types */
+#define BNEP_EXTENSION_CONTROL 0x00
+
+/* BNEP control types */
+#define BNEP_CONTROL_COMMAND_NOT_UNDERSTOOD 0x00
+#define BNEP_SETUP_CONNECTION_REQUEST 0x01
+#define BNEP_SETUP_CONNECTION_RESPONSE 0x02
+#define BNEP_FILTER_NET_TYPE_SET 0x03
+#define BNEP_FILTER_NET_TYPE_RESPONSE 0x04
+#define BNEP_FILTER_MULTI_ADDR_SET 0x05
+#define BNEP_FILTER_MULTI_ADDR_RESPONSE 0x06
+
+/* BNEP setup response codes */
+#define BNEP_SETUP_SUCCESS 0x0000
+#define BNEP_SETUP_INVALID_SRC_UUID 0x0001
+#define BNEP_SETUP_INVALID_DST_UUID 0x0002
+#define BNEP_SETUP_INVALID_UUID_SIZE 0x0003
+#define BNEP_SETUP_NOT_ALLOWED 0x0004
+
+/* BNEP filter return codes */
+#define BNEP_FILTER_SUCCESS 0x0000
+#define BNEP_FILTER_UNSUPPORTED_REQUEST 0x0001
+#define BNEP_FILTER_INVALID_RANGE 0x0002
+#define BNEP_FILTER_TOO_MANY_FILTERS 0x0003
+#define BNEP_FILTER_SECURITY_FAILURE 0x0004
diff --git a/usr.sbin/bluetooth/btpand/btpand.8 b/usr.sbin/bluetooth/btpand/btpand.8
new file mode 100644
index 0000000..4f6ede6
--- /dev/null
+++ b/usr.sbin/bluetooth/btpand/btpand.8
@@ -0,0 +1,240 @@
+.\" $NetBSD: btpand.8,v 1.3 2008/08/17 14:43:07 plunky Exp $
+.\" $FreeBSD$
+.\"
+.\" Copyright (c) 2008 Iain Hibbert
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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.
+.\"
+.Dd August 17, 2008
+.Dt BTPAND 8
+.Os
+.Sh NAME
+.Nm btpand
+.Nd Bluetooth PAN daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl i Ar ifname
+.Op Fl m Ar mode
+.Fl a Ar addr
+.Fl d Ar device
+.Brq Fl s Ar service | Fl S Ar service Op Fl p Ar psm
+.Nm
+.Op Fl c Ar path
+.Op Fl i Ar ifname
+.Op Fl l Ar limit
+.Op Fl m Ar mode
+.Op Fl p Ar psm
+.Fl d Ar device
+.Brq Fl s Ar service | Fl S Ar service
+.Sh DESCRIPTION
+The
+.Nm
+daemon handles Bluetooth Personal Area Networking services
+in the system.
+It can operate in client mode as a Personal Area Networking User
+.Pq PANU
+or in server mode as Network Access Point
+.Pq NAP ,
+Group ad-hoc Network
+.Pq GN
+or PANU host.
+.Nm
+connects to the system via a
+.Xr tap 4
+virtual Ethernet device and forwards Ethernet packets to
+remote Bluetooth devices using the Bluetooth Network Encapsulation
+Protocol
+.Pq BNEP .
+.Pp
+The PANU client is the device that uses either the NAP or GN
+service, or can talk directly to a PANU host in a crossover
+cable fashion.
+.Pp
+A GN host forwards Ethernet packets to each of the connected PAN
+users as needed but does not provide access to any additional networks.
+.Pp
+The NAP service provides some of the features of an Ethernet bridge,
+with the NAP host forwarding Ethernet packets between each of the
+connected PAN users, and a different network
+media.
+.Pp
+Note, the only differences between NAP and GN services as implemented by
+.Nm
+are in the SDP service record.
+The bridging of packets by the NAP must be configured separately.
+.Pp
+The options are as follows:
+.Bl -tag -width ".Fl a Ar address"
+.It Fl a Ar address
+In client mode, address of remote server.
+May be given as BDADDR or name, in which case
+.Nm
+will attempt to resolve the address via the
+.Xr bt_gethostbyname 3
+call.
+.It Fl c Ar path
+In server mode, specify
+.Ar path
+to the
+.Xr sdpd 8
+control socket.
+The default path is
+.Pa /var/run/sdp .
+.It Fl d Ar device
+Restrict connections to the local
+.Ar device .
+May be given as BDADDR or name, in which case
+.Nm
+will attempt to resolve the address via the
+.Xr bt_devaddr 3
+call.
+.Nm
+will set the
+.Xr tap 4
+interface physical address to the BDADDR
+of the Bluetooth radio.
+.It Fl i Ar ifname
+.Nm
+uses the
+.Xr tap 4
+driver to create a new network interface for use.
+Use this option to select a specific
+.Xr tap 4
+device interface which must already be created.
+.It Fl l Ar limit
+In server mode, limit the number of simultaneous connections.
+The default limit is 7 for NAP and GN servers,
+and 1 for a PANU server.
+.It Fl m Ar mode
+Set L2CAP connection link mode.
+Supported modes are:
+.Pp
+.Bl -tag -width 8n -compact
+.It auth
+require devices to be paired.
+.It encrypt
+auth, plus enable encryption.
+.It secure
+encryption, plus change of link key.
+.El
+.Pp
+NOT YET SUPPORTED.
+Use global device settings to set authentication and encryption.
+.It Fl p Ar psm
+Use an alternative L2CAP Protocol/Service Multiplexer
+.Pq PSM
+for server mode or client mode
+.Pq when not using Service Discovery .
+The default PSM for BNEP is 15
+.Pq 0x000f .
+.It Fl s Ar service
+Name of
+.Ar service
+to provide or connect to, the following services are recognised:
+.Pp
+.Bl -tag -width 8n -compact
+.It GN
+Group ad-hoc Network.
+.It NAP
+Network Access Point.
+.It PANU
+Personal Area Networking User.
+.El
+.It Fl S Ar service
+As per
+.Fl s
+except that
+.Nm
+will not use SDP services for connection setup.
+.El
+.Pp
+When providing networking services, the Bluetooth PAN profile says that the
+.Sq Class of Device
+property of the bluetooth controller SHALL include Networking capability
+.Pq set bit 0x020000 .
+See
+.Xr hccontrol 8
+for details.
+.Pp
+After
+.Nm
+has set up the client or server connection and opened the
+.Xr tap 4
+interface, it will create a pid file and detach.
+.Sh FILES
+.Bl -tag -width "Pa /etc/bluetooth/hosts" -compact
+.It Pa /dev/tap
+.It Pa /etc/bluetooth/hosts
+.It Pa /var/run/sdp
+.It Pa /var/run/tap Ns Em N Ns No .pid
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+.Dl ifconfig tap1 create
+.Dl btpand -a host -d mydevice -s NAP -i tap1
+.Dl dhclient tap1
+.Pp
+Will create a connection to the NAP on
+.Ar host ,
+and link that to the
+.Ar tap1
+interface.
+.Pp
+.Dl btpand -d mydevice -s GN
+.Pp
+Will create a Group Network and register the GN service with the local
+SDP server.
+.Sh SEE ALSO
+.Xr bluetooth 3 ,
+.Xr bridge 4 ,
+.Xr tap 4 ,
+.Xr dhclient 8 ,
+.Xr hccontrol 8 ,
+.Xr ifconfig 8 ,
+.Xr sdpd 8
+.Pp
+The
+.Qq Personal Area Networking Profile
+and
+.Qq Bluetooth Network Encapsulation Protocol
+specifications are available at
+.Dl http://www.bluetooth.com/
+.Sh AUTHORS
+.An Iain Hibbert
+.Sh BUGS
+There is no way to supply alternative values for the SDP record.
+.Pp
+There is no way to set net type or multicast address filters.
+.Pp
+.Nm
+does not do any address routing except to directly connected
+unicast addresses.
+All other packets are multicast.
+.Pp
+As
+.Nm
+uses the BDADDR of the Bluetooth radio as the physical address
+of the tap, only one instance can be run per radio.
+.Pp
+.Nm
+can only provide a single service.
diff --git a/usr.sbin/bluetooth/btpand/btpand.c b/usr.sbin/bluetooth/btpand/btpand.c
new file mode 100644
index 0000000..243fcf5
--- /dev/null
+++ b/usr.sbin/bluetooth/btpand/btpand.c
@@ -0,0 +1,294 @@
+/* $NetBSD: btpand.c,v 1.1 2008/08/17 13:20:57 plunky Exp $ */
+
+/*-
+ * Copyright (c) 2008 Iain Hibbert
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$ */
+
+#include <sys/cdefs.h>
+__COPYRIGHT("@(#) Copyright (c) 2008 Iain Hibbert. All rights reserved.");
+__RCSID("$NetBSD: btpand.c,v 1.1 2008/08/17 13:20:57 plunky Exp $");
+
+#include <sys/wait.h>
+
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <err.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <sdp.h>
+#include <stdio.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "btpand.h"
+
+/* global variables */
+const char * control_path; /* -c <path> */
+const char * interface_name; /* -i <ifname> */
+const char * service_name; /* -s <service> */
+uint16_t service_class;
+
+bdaddr_t local_bdaddr; /* -d <addr> */
+bdaddr_t remote_bdaddr; /* -a <addr> */
+uint16_t l2cap_psm; /* -p <psm> */
+int l2cap_mode; /* -m <mode> */
+
+int server_limit; /* -n <limit> */
+
+static const struct {
+ const char * name;
+ uint16_t class;
+ const char * desc;
+} services[] = {
+ { "PANU", SDP_SERVICE_CLASS_PANU, "Personal Area Networking User" },
+ { "NAP", SDP_SERVICE_CLASS_NAP, "Network Acess Point" },
+ { "GN", SDP_SERVICE_CLASS_GN, "Group Network" },
+};
+
+static void main_exit(int);
+static void main_detach(void);
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ unsigned long ul;
+ char * ep;
+ int ch, status;
+
+ while ((ch = getopt(argc, argv, "a:c:d:i:l:m:p:S:s:")) != -1) {
+ switch (ch) {
+ case 'a': /* remote address */
+ if (!bt_aton(optarg, &remote_bdaddr)) {
+ struct hostent *he;
+
+ if ((he = bt_gethostbyname(optarg)) == NULL)
+ errx(EXIT_FAILURE, "%s: %s",
+ optarg, hstrerror(h_errno));
+
+ bdaddr_copy(&remote_bdaddr,
+ (bdaddr_t *)he->h_addr);
+ }
+
+ break;
+
+ case 'c': /* control socket path */
+ control_path = optarg;
+ break;
+
+ case 'd': /* local address */
+ if (!bt_devaddr(optarg, &local_bdaddr)) {
+ struct hostent *he;
+
+ if ((he = bt_gethostbyname(optarg)) == NULL)
+ errx(EXIT_FAILURE, "%s: %s",
+ optarg, hstrerror(h_errno));
+
+ bdaddr_copy(&local_bdaddr,
+ (bdaddr_t *)he->h_addr);
+ }
+ break;
+
+ case 'i': /* tap interface name */
+ if (strchr(optarg, '/') == NULL) {
+ asprintf(&ep, "/dev/%s", optarg);
+ interface_name = ep;
+ } else
+ interface_name = optarg;
+ break;
+
+ case 'l': /* limit server sessions */
+ ul = strtoul(optarg, &ep, 10);
+ if (*optarg == '\0' || *ep != '\0' || ul == 0)
+ errx(EXIT_FAILURE, "%s: invalid session limit",
+ optarg);
+
+ server_limit = ul;
+ break;
+
+ case 'm': /* link mode */
+ warnx("Setting link mode is not yet supported");
+ break;
+
+ case 'p': /* protocol/service multiplexer */
+ ul = strtoul(optarg, &ep, 0);
+ if (*optarg == '\0' || *ep != '\0'
+ || ul > 0xffff || L2CAP_PSM_INVALID(ul))
+ errx(EXIT_FAILURE, "%s: invalid PSM", optarg);
+
+ l2cap_psm = ul;
+ break;
+
+ case 's': /* service */
+ case 'S': /* service (no SDP) */
+ for (ul = 0; strcasecmp(optarg, services[ul].name); ul++) {
+ if (ul == __arraycount(services))
+ errx(EXIT_FAILURE, "%s: unknown service", optarg);
+ }
+
+ if (ch == 's')
+ service_name = services[ul].name;
+
+ service_class = services[ul].class;
+ break;
+
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ /* validate options */
+ if (bdaddr_any(&local_bdaddr) || service_class == 0)
+ usage();
+
+ if (!bdaddr_any(&remote_bdaddr) && (server_limit != 0 ||
+ control_path != 0 || (service_name != NULL && l2cap_psm != 0)))
+ usage();
+
+ /* default options */
+ if (interface_name == NULL)
+ interface_name = "/dev/tap";
+
+ if (l2cap_psm == 0)
+ l2cap_psm = L2CAP_PSM_BNEP;
+
+ if (bdaddr_any(&remote_bdaddr) && server_limit == 0) {
+ if (service_class == SDP_SERVICE_CLASS_PANU)
+ server_limit = 1;
+ else
+ server_limit = 7;
+ }
+
+#ifdef L2CAP_LM_MASTER
+ if (server_limit > 1 && service_class != SDP_SERVICE_CLASS_PANU)
+ l2cap_mode |= L2CAP_LM_MASTER;
+#endif
+
+ /*
+ * fork() now so that the setup can be done in the child process
+ * (as kqueue is not inherited) but block in the parent until the
+ * setup is finished so we can return an error if necessary.
+ */
+ switch(fork()) {
+ case -1: /* bad */
+ err(EXIT_FAILURE, "fork() failed");
+
+ case 0: /* child */
+ signal(SIGPIPE, SIG_IGN);
+
+ openlog(getprogname(), LOG_NDELAY | LOG_PERROR | LOG_PID, LOG_DAEMON);
+
+ channel_init();
+ server_init();
+ event_init();
+ client_init();
+ tap_init();
+
+ main_detach();
+
+ event_dispatch();
+ break;
+
+ default: /* parent */
+ signal(SIGUSR1, main_exit);
+ wait(&status);
+
+ if (WIFEXITED(status))
+ exit(WEXITSTATUS(status));
+
+ break;
+ }
+
+ err(EXIT_FAILURE, "exiting");
+}
+
+static void
+main_exit(int s)
+{
+
+ /* child is all grown up */
+ _exit(EXIT_SUCCESS);
+}
+
+static void
+main_detach(void)
+{
+ int fd;
+
+ if (kill(getppid(), SIGUSR1) == -1)
+ log_err("Could not signal main process: %m");
+
+ if (setsid() == -1)
+ log_err("setsid() failed");
+
+ fd = open(_PATH_DEVNULL, O_RDWR, 0);
+ if (fd == -1) {
+ log_err("Could not open %s", _PATH_DEVNULL);
+ } else {
+ (void)dup2(fd, STDIN_FILENO);
+ (void)dup2(fd, STDOUT_FILENO);
+ (void)dup2(fd, STDERR_FILENO);
+ close(fd);
+ }
+}
+
+static void
+usage(void)
+{
+ const char *p = getprogname();
+ int n = strlen(p);
+
+ fprintf(stderr,
+ "usage: %s [-i ifname] [-m mode] -a address -d device\n"
+ " %*s {-s service | -S service [-p psm]}\n"
+ " %s [-c path] [-i ifname] [-l limit] [-m mode] [-p psm] -d device\n"
+ " %*s {-s service | -S service}\n"
+ "\n"
+ "Where:\n"
+ "\t-a address remote bluetooth device\n"
+ "\t-c path SDP server socket\n"
+ "\t-d device local bluetooth device\n"
+ "\t-i ifname tap interface\n"
+ "\t-l limit limit server sessions\n"
+ "\t-m mode L2CAP link mode (NOT YET SUPPORTED)\n"
+ "\t-p psm L2CAP PSM\n"
+ "\t-S service service name (no SDP)\n"
+ "\t-s service service name\n"
+ "\n"
+ "Known services:\n"
+ "", p, n, "", p, n, "");
+
+ for (n = 0; n < __arraycount(services); n++)
+ fprintf(stderr, "\t%s\t%s\n", services[n].name, services[n].desc);
+
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.sbin/bluetooth/btpand/btpand.h b/usr.sbin/bluetooth/btpand/btpand.h
new file mode 100644
index 0000000..c5f7204
--- /dev/null
+++ b/usr.sbin/bluetooth/btpand/btpand.h
@@ -0,0 +1,212 @@
+/* $NetBSD: btpand.h,v 1.1 2008/08/17 13:20:57 plunky Exp $ */
+
+/*-
+ * Copyright (c) 2008 Iain Hibbert
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <net/ethernet.h>
+
+#include <assert.h>
+#include <bluetooth.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "event.h"
+
+#ifndef __arraycount
+#define __arraycount(__x) (int)(sizeof((__x)) / sizeof((__x)[0]))
+#endif
+
+#ifndef L2CAP_PSM_INVALID
+#define L2CAP_PSM_INVALID(psm) (((psm) & 0x0101) != 0x0001)
+#endif
+
+#ifndef L2CAP_PSM_BNEP
+#define L2CAP_PSM_BNEP 15
+#endif
+
+typedef struct channel channel_t;
+typedef struct pfilter pfilter_t;
+typedef struct mfilter mfilter_t;
+typedef struct packet packet_t;
+typedef struct pkthdr pkthdr_t;
+typedef struct pktlist pktlist_t;
+typedef struct exthdr exthdr_t;
+typedef struct extlist extlist_t;
+
+LIST_HEAD(chlist, channel);
+STAILQ_HEAD(extlist, exthdr);
+STAILQ_HEAD(pktlist, pkthdr);
+
+enum channel_state {
+ CHANNEL_CLOSED,
+ CHANNEL_WAIT_CONNECT_REQ,
+ CHANNEL_WAIT_CONNECT_RSP,
+ CHANNEL_OPEN,
+};
+
+#define CHANNEL_MAXQLEN 128
+
+/* BNEP or tap channel */
+struct channel {
+ enum channel_state state;
+ bool oactive;
+
+ uint8_t laddr[ETHER_ADDR_LEN];
+ uint8_t raddr[ETHER_ADDR_LEN];
+ size_t mru;
+ size_t mtu;
+
+ int npfilter;
+ pfilter_t * pfilter;
+
+ int nmfilter;
+ mfilter_t * mfilter;
+
+ pktlist_t pktlist;
+ int qlen;
+
+ int fd;
+ struct event rd_ev;
+ struct event wr_ev;
+ uint8_t * sendbuf;
+
+ bool (*send)(channel_t *, packet_t *);
+ bool (*recv)(packet_t *);
+
+ int tick;
+
+ struct pidfh *pfh;
+
+ int refcnt;
+ LIST_ENTRY(channel) next;
+};
+
+/* network protocol type filter */
+struct pfilter {
+ uint16_t start;
+ uint16_t end;
+};
+
+/* multicast address filter */
+struct mfilter {
+ uint8_t start[ETHER_ADDR_LEN];
+ uint8_t end[ETHER_ADDR_LEN];
+};
+
+/* packet data buffer */
+struct packet {
+ channel_t * chan; /* source channel */
+ uint8_t * dst; /* dest address */
+ uint8_t * src; /* source address */
+ uint8_t * type; /* protocol type */
+ uint8_t * ptr; /* data pointer */
+ size_t len; /* data length */
+ int refcnt; /* reference count */
+ extlist_t extlist;/* extension headers */
+ uint8_t buf[0]; /* data starts here */
+};
+
+/* extension header */
+struct exthdr {
+ STAILQ_ENTRY(exthdr) next;
+ uint8_t * ptr;
+ uint8_t len;
+};
+
+/* packet header */
+struct pkthdr {
+ STAILQ_ENTRY(pkthdr) next;
+ packet_t * data;
+};
+
+/* global variables */
+extern const char * control_path;
+extern const char * service_name;
+extern const char * interface_name;
+extern bdaddr_t local_bdaddr;
+extern bdaddr_t remote_bdaddr;
+extern uint16_t l2cap_psm;
+extern int l2cap_mode;
+extern uint16_t service_class;
+extern int server_limit;
+
+/*
+ * Bluetooth addresses are stored the other way around than
+ * Ethernet addresses even though they are of the same family
+ */
+static inline void
+b2eaddr(void *dst, bdaddr_t *src)
+{
+ uint8_t *d = dst;
+ int i;
+
+ for (i = 0; i < ETHER_ADDR_LEN; i++)
+ d[i] = src->b[ETHER_ADDR_LEN - i - 1];
+}
+
+#define log_err(fmt, args...) syslog(LOG_ERR, fmt , ##args)
+#define log_info(fmt, args...) syslog(LOG_INFO, fmt , ##args)
+#define log_notice(fmt, args...) syslog(LOG_NOTICE, fmt , ##args)
+#define log_debug(fmt, args...) syslog(LOG_DEBUG, "%s: " fmt, __func__ , ##args)
+
+/* bnep.c */
+bool bnep_send(channel_t *, packet_t *);
+bool bnep_recv(packet_t *);
+void bnep_send_control(channel_t *, uint8_t, ...);
+
+/* channel.c */
+void channel_init(void);
+channel_t * channel_alloc(void);
+bool channel_open(channel_t *, int);
+void channel_close(channel_t *);
+void channel_free(channel_t *);
+void channel_timeout(channel_t *, int);
+void channel_put(channel_t *, packet_t *);
+
+/* client.c */
+void client_init(void);
+
+/* packet.c */
+packet_t * packet_alloc(channel_t *);
+void packet_free(packet_t *);
+void packet_adj(packet_t *, size_t);
+pkthdr_t * pkthdr_alloc(packet_t *);
+void pkthdr_free(pkthdr_t *);
+
+/* server.c */
+void server_init(void);
+void server_update(int);
+
+/* tap.c */
+void tap_init(void);
diff --git a/usr.sbin/bluetooth/btpand/channel.c b/usr.sbin/bluetooth/btpand/channel.c
new file mode 100644
index 0000000..32f2487
--- /dev/null
+++ b/usr.sbin/bluetooth/btpand/channel.c
@@ -0,0 +1,335 @@
+/* $NetBSD: channel.c,v 1.1 2008/08/17 13:20:57 plunky Exp $ */
+
+/*-
+ * Copyright (c) 2008 Iain Hibbert
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: channel.c,v 1.1 2008/08/17 13:20:57 plunky Exp $");
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+
+#include <libutil.h>
+#include <unistd.h>
+#define L2CAP_SOCKET_CHECKED
+#include "btpand.h"
+
+static struct chlist channel_list;
+static int channel_count;
+static int channel_tick;
+
+static void channel_start(int, short, void *);
+static void channel_read(int, short, void *);
+static void channel_dispatch(packet_t *);
+static void channel_watchdog(int, short, void *);
+
+void
+channel_init(void)
+{
+
+ LIST_INIT(&channel_list);
+}
+
+channel_t *
+channel_alloc(void)
+{
+ channel_t *chan;
+
+ chan = malloc(sizeof(channel_t));
+ if (chan == NULL) {
+ log_err("%s() failed: %m", __func__);
+ return NULL;
+ }
+
+ memset(chan, 0, sizeof(channel_t));
+ STAILQ_INIT(&chan->pktlist);
+ chan->state = CHANNEL_CLOSED;
+ LIST_INSERT_HEAD(&channel_list, chan, next);
+
+ server_update(++channel_count);
+
+ return chan;
+}
+
+bool
+channel_open(channel_t *chan, int fd)
+{
+ int n;
+
+ assert(chan->refcnt == 0);
+ assert(chan->state != CHANNEL_CLOSED);
+
+ if (chan->mtu > 0) {
+ chan->sendbuf = malloc(chan->mtu);
+ if (chan->sendbuf == NULL) {
+ log_err("Could not malloc channel sendbuf: %m");
+ return false;
+ }
+ }
+
+ n = 1;
+ if (ioctl(fd, FIONBIO, &n) == -1) {
+ log_err("Could not set non-blocking IO: %m");
+ return false;
+ }
+
+ event_set(&chan->rd_ev, fd, EV_READ | EV_PERSIST, channel_read, chan);
+ if (event_add(&chan->rd_ev, NULL) == -1) {
+ log_err("Could not add channel read event: %m");
+ return false;
+ }
+
+ event_set(&chan->wr_ev, fd, EV_WRITE, channel_start, chan);
+
+ chan->refcnt++;
+ chan->fd = fd;
+
+ log_debug("(fd#%d)", chan->fd);
+
+ return true;
+}
+
+void
+channel_close(channel_t *chan)
+{
+ pkthdr_t *ph;
+
+ assert(chan->state != CHANNEL_CLOSED);
+
+ log_debug("(fd#%d)", chan->fd);
+
+ chan->state = CHANNEL_CLOSED;
+ event_del(&chan->rd_ev);
+ event_del(&chan->wr_ev);
+ close(chan->fd);
+ chan->refcnt--;
+ chan->tick = 0;
+
+ while ((ph = STAILQ_FIRST(&chan->pktlist)) != NULL) {
+ STAILQ_REMOVE_HEAD(&chan->pktlist, next);
+ pkthdr_free(ph);
+ chan->qlen--;
+ }
+
+ if (chan->pfh != NULL) {
+ pidfile_remove(chan->pfh);
+ chan->pfh = NULL;
+ }
+
+ if (chan->refcnt == 0)
+ channel_free(chan);
+}
+
+void
+channel_free(channel_t *chan)
+{
+
+ assert(chan->refcnt == 0);
+ assert(chan->state == CHANNEL_CLOSED);
+ assert(chan->qlen == 0);
+ assert(STAILQ_EMPTY(&chan->pktlist));
+
+ LIST_REMOVE(chan, next);
+ free(chan->pfilter);
+ free(chan->mfilter);
+ free(chan->sendbuf);
+ free(chan);
+
+ server_update(--channel_count);
+
+ if (server_limit == 0) {
+ log_info("connection closed, exiting");
+ exit(EXIT_SUCCESS);
+ }
+}
+
+static void
+channel_start(int fd, short ev, void *arg)
+{
+ channel_t *chan = arg;
+ pkthdr_t *ph;
+
+ chan->oactive = true;
+
+ while (chan->qlen > 0) {
+ ph = STAILQ_FIRST(&chan->pktlist);
+
+ channel_timeout(chan, 10);
+ if (chan->send(chan, ph->data) == false) {
+ if (event_add(&chan->wr_ev, NULL) == -1) {
+ log_err("Could not add channel write event: %m");
+ channel_close(chan);
+ }
+ return;
+ }
+
+ STAILQ_REMOVE_HEAD(&chan->pktlist, next);
+ pkthdr_free(ph);
+ chan->qlen--;
+ }
+
+ channel_timeout(chan, 0);
+ chan->oactive = false;
+}
+
+static void
+channel_read(int fd, short ev, void *arg)
+{
+ channel_t *chan = arg;
+ packet_t *pkt;
+ ssize_t nr;
+
+ pkt = packet_alloc(chan);
+ if (pkt == NULL) {
+ channel_close(chan);
+ return;
+ }
+
+ nr = read(fd, pkt->buf, chan->mru);
+ if (nr == -1) {
+ log_err("channel read error: %m");
+ packet_free(pkt);
+ channel_close(chan);
+ return;
+ }
+ if (nr == 0) { /* EOF */
+ log_debug("(fd#%d) EOF", fd);
+ packet_free(pkt);
+ channel_close(chan);
+ return;
+ }
+ pkt->len = nr;
+
+ if (chan->recv(pkt) == true)
+ channel_dispatch(pkt);
+
+ packet_free(pkt);
+}
+
+static void
+channel_dispatch(packet_t *pkt)
+{
+ channel_t *chan;
+
+ /*
+ * This is simple routing. I'm not sure if its allowed by
+ * the PAN or BNEP specifications, but it seems logical
+ * to send unicast packets to connected destinations where
+ * possible.
+ */
+ if (!ETHER_IS_MULTICAST(pkt->dst)) {
+ LIST_FOREACH(chan, &channel_list, next) {
+ if (chan == pkt->chan
+ || chan->state != CHANNEL_OPEN)
+ continue;
+
+ if (memcmp(pkt->dst, chan->raddr, ETHER_ADDR_LEN) == 0) {
+ if (chan->qlen > CHANNEL_MAXQLEN)
+ log_notice("Queue overflow");
+ else
+ channel_put(chan, pkt);
+
+ return;
+ }
+ }
+ }
+
+ LIST_FOREACH(chan, &channel_list, next) {
+ if (chan == pkt->chan
+ || chan->state != CHANNEL_OPEN)
+ continue;
+
+ if (chan->qlen > CHANNEL_MAXQLEN) {
+ log_notice("Queue overflow");
+ continue;
+ }
+
+ channel_put(chan, pkt);
+ }
+}
+
+void
+channel_put(channel_t *chan, packet_t *pkt)
+{
+ pkthdr_t *ph;
+
+ ph = pkthdr_alloc(pkt);
+ if (ph == NULL)
+ return;
+
+ chan->qlen++;
+ STAILQ_INSERT_TAIL(&chan->pktlist, ph, next);
+
+ if (!chan->oactive)
+ channel_start(chan->fd, EV_WRITE, chan);
+}
+
+/*
+ * Simple watchdog timer, only ticks when it is required and
+ * closes the channel down if it times out.
+ */
+void
+channel_timeout(channel_t *chan, int to)
+{
+ static struct event ev;
+
+ if (to == 0)
+ chan->tick = 0;
+ else
+ chan->tick = (channel_tick + to) % 60;
+
+ if (channel_tick == 0) {
+ evtimer_set(&ev, channel_watchdog, &ev);
+ channel_watchdog(0, 0, &ev);
+ }
+}
+
+static void
+channel_watchdog(int fd, short ev, void *arg)
+{
+ static struct timeval tv = { .tv_sec = 1 };
+ channel_t *chan, *next;
+ int tick;
+
+ tick = (channel_tick % 60) + 1;
+ channel_tick = 0;
+
+ next = LIST_FIRST(&channel_list);
+ while ((chan = next) != NULL) {
+ next = LIST_NEXT(chan, next);
+
+ if (chan->tick == tick)
+ channel_close(chan);
+ else if (chan->tick != 0)
+ channel_tick = tick;
+ }
+
+ if (channel_tick != 0 && evtimer_add(arg, &tv) < 0) {
+ log_err("Could not add watchdog event: %m");
+ exit(EXIT_FAILURE);
+ }
+}
diff --git a/usr.sbin/bluetooth/btpand/client.c b/usr.sbin/bluetooth/btpand/client.c
new file mode 100644
index 0000000..f88dc6e
--- /dev/null
+++ b/usr.sbin/bluetooth/btpand/client.c
@@ -0,0 +1,228 @@
+/* $NetBSD: client.c,v 1.2 2008/12/06 20:01:14 plunky Exp $ */
+
+/*-
+ * Copyright (c) 2008 Iain Hibbert
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: client.c,v 1.2 2008/12/06 20:01:14 plunky Exp $");
+
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <errno.h>
+#include <sdp.h>
+#include <unistd.h>
+
+#include "btpand.h"
+#include "bnep.h"
+#include "sdp.h"
+
+static void client_query(void);
+
+void
+client_init(void)
+{
+ struct sockaddr_l2cap sa;
+ channel_t *chan;
+ socklen_t len;
+ int fd, n;
+ uint16_t mru, mtu;
+
+ if (bdaddr_any(&remote_bdaddr))
+ return;
+
+ if (service_name)
+ client_query();
+
+ fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP);
+ if (fd == -1) {
+ log_err("Could not open L2CAP socket: %m");
+ exit(EXIT_FAILURE);
+ }
+
+ memset(&sa, 0, sizeof(sa));
+ sa.l2cap_family = AF_BLUETOOTH;
+ sa.l2cap_len = sizeof(sa);
+ sa.l2cap_bdaddr_type = BDADDR_BREDR;
+ sa.l2cap_cid = 0;
+
+ bdaddr_copy(&sa.l2cap_bdaddr, &local_bdaddr);
+ if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
+ log_err("Could not bind client socket: %m");
+ exit(EXIT_FAILURE);
+ }
+
+ mru = BNEP_MTU_MIN;
+ if (setsockopt(fd, SOL_L2CAP, SO_L2CAP_IMTU, &mru, sizeof(mru)) == -1) {
+ log_err("Could not set L2CAP IMTU (%d): %m", mru);
+ exit(EXIT_FAILURE);
+ }
+
+ log_info("Opening connection to service 0x%4.4x at %s",
+ service_class, bt_ntoa(&remote_bdaddr, NULL));
+
+ sa.l2cap_psm = htole16(l2cap_psm);
+ bdaddr_copy(&sa.l2cap_bdaddr, &remote_bdaddr);
+ if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
+ log_err("Could not connect: %m");
+ exit(EXIT_FAILURE);
+ }
+
+ len = sizeof(mru);
+ if (getsockopt(fd, SOL_L2CAP, SO_L2CAP_IMTU, &mru, &len) == -1) {
+ log_err("Could not get IMTU: %m");
+ exit(EXIT_FAILURE);
+ }
+ if (mru < BNEP_MTU_MIN) {
+ log_err("L2CAP IMTU too small (%d)", mru);
+ exit(EXIT_FAILURE);
+ }
+
+ len = sizeof(n);
+ if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &n, &len) == -1) {
+ log_err("Could not read SO_RCVBUF");
+ exit(EXIT_FAILURE);
+ }
+ if (n < (mru * 10)) {
+ n = mru * 10;
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)) == -1)
+ log_info("Could not increase SO_RCVBUF (from %d)", n);
+ }
+
+ len = sizeof(mtu);
+ if (getsockopt(fd, SOL_L2CAP, SO_L2CAP_OMTU, &mtu, &len) == -1) {
+ log_err("Could not get L2CAP OMTU: %m");
+ exit(EXIT_FAILURE);
+ }
+ if (mtu < BNEP_MTU_MIN) {
+ log_err("L2CAP OMTU too small (%d)", mtu);
+ exit(EXIT_FAILURE);
+ }
+
+ len = sizeof(n);
+ if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &n, &len) == -1) {
+ log_err("Could not get socket send buffer size: %m");
+ close(fd);
+ return;
+ }
+ if (n < (mtu * 2)) {
+ n = mtu * 2;
+ if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &n, sizeof(n)) == -1) {
+ log_err("Could not set socket send buffer size (%d): %m", n);
+ close(fd);
+ return;
+ }
+ }
+ n = mtu;
+ if (setsockopt(fd, SOL_SOCKET, SO_SNDLOWAT, &n, sizeof(n)) == -1) {
+ log_err("Could not set socket low water mark (%d): %m", n);
+ close(fd);
+ return;
+ }
+
+ chan = channel_alloc();
+ if (chan == NULL)
+ exit(EXIT_FAILURE);
+
+ chan->send = bnep_send;
+ chan->recv = bnep_recv;
+ chan->mru = mru;
+ chan->mtu = mtu;
+ b2eaddr(chan->raddr, &remote_bdaddr);
+ b2eaddr(chan->laddr, &local_bdaddr);
+ chan->state = CHANNEL_WAIT_CONNECT_RSP;
+ channel_timeout(chan, 10);
+ if (!channel_open(chan, fd))
+ exit(EXIT_FAILURE);
+
+ bnep_send_control(chan, BNEP_SETUP_CONNECTION_REQUEST,
+ 2, service_class, SDP_SERVICE_CLASS_PANU);
+}
+
+static void
+client_query(void)
+{
+ uint8_t buffer[512];
+ sdp_attr_t attr;
+ uint32_t range;
+ void *ss;
+ int rv;
+ uint8_t *seq0, *seq1;
+
+ attr.flags = SDP_ATTR_INVALID;
+ attr.attr = 0;
+ attr.vlen = sizeof(buffer);
+ attr.value = buffer;
+
+ range = SDP_ATTR_RANGE(SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST,
+ SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST);
+
+ ss = sdp_open(&local_bdaddr, &remote_bdaddr);
+ if (ss == NULL || (errno = sdp_error(ss)) != 0) {
+ log_err("%s: %m", service_name);
+ exit(EXIT_FAILURE);
+ }
+
+ log_info("Searching for %s service at %s",
+ service_name, bt_ntoa(&remote_bdaddr, NULL));
+
+ rv = sdp_search(ss, 1, &service_class, 1, &range, 1, &attr);
+ if (rv != 0) {
+ log_err("%s: %s", service_name, strerror(sdp_error(ss)));
+ exit(EXIT_FAILURE);
+ }
+
+ sdp_close(ss);
+
+ if (attr.flags != SDP_ATTR_OK
+ || attr.attr != SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST) {
+ log_err("%s service not found", service_name);
+ exit(EXIT_FAILURE);
+ }
+
+ /*
+ * we expect the following protocol descriptor list
+ *
+ * seq len
+ * seq len
+ * uuid value == L2CAP
+ * uint16 value16 => PSM
+ * seq len
+ * uuid value == BNEP
+ */
+ if (_sdp_get_seq(&attr.value, attr.value + attr.vlen, &seq0)
+ && _sdp_get_seq(&seq0, attr.value, &seq1)
+ && _sdp_match_uuid16(&seq1, seq0, SDP_UUID_PROTOCOL_L2CAP)
+ && _sdp_get_uint16(&seq1, seq0, &l2cap_psm)
+ && _sdp_get_seq(&seq0, attr.value, &seq1)
+ && _sdp_match_uuid16(&seq1, seq0, SDP_UUID_PROTOCOL_BNEP)) {
+ log_info("Found PSM %d for service %s", l2cap_psm, service_name);
+ return;
+ }
+
+ log_err("%s query failed", service_name);
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.sbin/bluetooth/btpand/event.c b/usr.sbin/bluetooth/btpand/event.c
new file mode 100644
index 0000000..ac51f57
--- /dev/null
+++ b/usr.sbin/bluetooth/btpand/event.c
@@ -0,0 +1,310 @@
+/*
+ * event.h
+ */
+
+/*-
+ * Copyright (c) 2009 Maksim Yevmenkin <m_evmenkin@yahoo.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$ */
+
+/*
+ * Hack to provide libevent (see devel/libevent port) like API.
+ * Should be removed if FreeBSD ever decides to import libevent into base.
+ */
+
+#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/queue.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "event.h"
+#define L2CAP_SOCKET_CHECKED
+#include "btpand.h"
+
+#define __event_link(ev) \
+do { \
+ TAILQ_INSERT_TAIL(&pending, ev, next); \
+ ev->flags |= EV_PENDING; \
+} while (0)
+
+static void tv_add(struct timeval *, struct timeval const *);
+static void tv_sub(struct timeval *, struct timeval const *);
+static int tv_cmp(struct timeval const *, struct timeval const *);
+static int __event_dispatch(void);
+static void __event_add_current(struct event *);
+static void __event_del_current(struct event *);
+
+
+static TAILQ_HEAD(, event) pending;
+static TAILQ_HEAD(, event) current;
+
+void
+event_init(void)
+{
+ TAILQ_INIT(&pending);
+}
+
+int
+event_dispatch(void)
+{
+ while (__event_dispatch() == 0)
+ ;
+
+ return (-1);
+}
+
+static int
+__event_dispatch(void)
+{
+ fd_set r, w;
+ int nfd;
+ struct event *ev;
+ struct timeval now, timeout, t;
+
+ FD_ZERO(&r);
+ FD_ZERO(&w);
+
+ nfd = 0;
+
+ gettimeofday(&now, NULL);
+
+ timeout.tv_sec = 10; /* arbitrary */
+ timeout.tv_usec = 0;
+
+ TAILQ_INIT(&current);
+
+ /*
+ * Build fd_set's
+ */
+
+ event_log_debug("%s: building fd set...", __func__);
+
+ while (!TAILQ_EMPTY(&pending)) {
+ ev = TAILQ_FIRST(&pending);
+ event_del(ev);
+
+ if (ev->flags & EV_HAS_TIMEOUT) {
+ if (tv_cmp(&now, &ev->expire) >= 0)
+ t.tv_sec = t.tv_usec = 0;
+ else {
+ t = ev->expire;
+ tv_sub(&t, &now);
+ }
+
+ if (tv_cmp(&t, &timeout) < 0)
+ timeout = t;
+ }
+
+ if (ev->fd >= 0) {
+ if (ev->flags & EV_READ) {
+ FD_SET(ev->fd, &r);
+ nfd = (nfd > ev->fd) ? nfd : ev->fd;
+ }
+
+ if (ev->flags & EV_WRITE) {
+ FD_SET(ev->fd, &w);
+ nfd = (nfd > ev->fd) ? nfd : ev->fd;
+ }
+ }
+
+ __event_add_current(ev);
+ }
+
+ event_log_debug("%s: waiting for events...", __func__);
+
+ nfd = select(nfd + 1, &r, &w, NULL, &timeout);
+ if (nfd < 0)
+ return (-1);
+
+ /*
+ * Process current pending
+ */
+
+ event_log_debug("%s: processing events...", __func__);
+
+ gettimeofday(&now, NULL);
+
+ while (!TAILQ_EMPTY(&current)) {
+ ev = TAILQ_FIRST(&current);
+ __event_del_current(ev);
+
+ /* check if fd is ready for reading/writing */
+ if (nfd > 0 && ev->fd >= 0) {
+ if (FD_ISSET(ev->fd, &r) || FD_ISSET(ev->fd, &w)) {
+ if (ev->flags & EV_PERSIST) {
+ if (ev->flags & EV_HAS_TIMEOUT)
+ event_add(ev, &ev->timeout);
+ else
+ event_add(ev, NULL);
+ }
+
+ nfd --;
+
+ event_log_debug("%s: calling %p(%d, %p), " \
+ "ev=%p", __func__, ev->cb, ev->fd,
+ ev->cbarg, ev);
+
+ (ev->cb)(ev->fd,
+ (ev->flags & (EV_READ|EV_WRITE)),
+ ev->cbarg);
+
+ continue;
+ }
+ }
+
+ /* if event has no timeout - just requeue */
+ if ((ev->flags & EV_HAS_TIMEOUT) == 0) {
+ event_add(ev, NULL);
+ continue;
+ }
+
+ /* check if event has expired */
+ if (tv_cmp(&now, &ev->expire) >= 0) {
+ if (ev->flags & EV_PERSIST)
+ event_add(ev, &ev->timeout);
+
+ event_log_debug("%s: calling %p(%d, %p), ev=%p",
+ __func__, ev->cb, ev->fd, ev->cbarg, ev);
+
+ (ev->cb)(ev->fd,
+ (ev->flags & (EV_READ|EV_WRITE)),
+ ev->cbarg);
+
+ continue;
+ }
+
+ assert((ev->flags & (EV_PENDING|EV_CURRENT)) == 0);
+ __event_link(ev);
+ }
+
+ return (0);
+}
+
+void
+__event_set(struct event *ev, int fd, short flags,
+ void (*cb)(int, short, void *), void *cbarg)
+{
+ ev->fd = fd;
+ ev->flags = flags;
+ ev->cb = cb;
+ ev->cbarg = cbarg;
+}
+
+int
+__event_add(struct event *ev, const struct timeval *timeout)
+{
+ assert((ev->flags & (EV_PENDING|EV_CURRENT)) == 0);
+
+ if (timeout != NULL) {
+ gettimeofday(&ev->expire, NULL);
+ tv_add(&ev->expire, timeout);
+ ev->timeout = *timeout;
+ ev->flags |= EV_HAS_TIMEOUT;
+ } else
+ ev->flags &= ~EV_HAS_TIMEOUT;
+
+ __event_link(ev);
+
+ return (0);
+}
+
+int
+__event_del(struct event *ev)
+{
+ assert((ev->flags & EV_CURRENT) == 0);
+
+ if ((ev->flags & EV_PENDING) != 0) {
+ TAILQ_REMOVE(&pending, ev, next);
+ ev->flags &= ~EV_PENDING;
+ }
+
+ return (0);
+}
+
+static void
+__event_add_current(struct event *ev)
+{
+ assert((ev->flags & (EV_PENDING|EV_CURRENT)) == 0);
+
+ TAILQ_INSERT_TAIL(&current, ev, next);
+ ev->flags |= EV_CURRENT;
+}
+
+static void
+__event_del_current(struct event *ev)
+{
+ assert((ev->flags & (EV_CURRENT|EV_PENDING)) == EV_CURRENT);
+
+ TAILQ_REMOVE(&current, ev, next);
+ ev->flags &= ~EV_CURRENT;
+}
+
+static void
+tv_add(struct timeval *a, struct timeval const *b)
+{
+ a->tv_sec += b->tv_sec;
+ a->tv_usec += b->tv_usec;
+
+ if(a->tv_usec >= 1000000) {
+ a->tv_usec -= 1000000;
+ a->tv_sec += 1;
+ }
+}
+
+static void
+tv_sub(struct timeval *a, struct timeval const *b)
+{
+ if (a->tv_usec < b->tv_usec) {
+ a->tv_usec += 1000000;
+ a->tv_sec -= 1;
+ }
+
+ a->tv_usec -= b->tv_usec;
+ a->tv_sec -= b->tv_sec;
+}
+
+static int
+tv_cmp(struct timeval const *a, struct timeval const *b)
+{
+ if (a->tv_sec > b->tv_sec)
+ return (1);
+
+ if (a->tv_sec < b->tv_sec)
+ return (-1);
+
+ if (a->tv_usec > b->tv_usec)
+ return (1);
+
+ if (a->tv_usec < b->tv_usec)
+ return (-1);
+
+ return (0);
+}
+
diff --git a/usr.sbin/bluetooth/btpand/event.h b/usr.sbin/bluetooth/btpand/event.h
new file mode 100644
index 0000000..e53ba77
--- /dev/null
+++ b/usr.sbin/bluetooth/btpand/event.h
@@ -0,0 +1,147 @@
+/*
+ * event.h
+ */
+
+/*-
+ * Copyright (c) 2009 Maksim Yevmenkin <m_evmenkin@yahoo.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$ */
+
+/*
+ * Hack to provide libevent (see devel/libevent port) like API.
+ * Should be removed if FreeBSD ever decides to import libevent into base.
+ */
+
+#ifndef _EVENT_H_
+#define _EVENT_H_ 1
+
+#define EV_READ 0x02
+#define EV_WRITE 0x04
+#define EV_PERSIST 0x10 /* Persistent event */
+#define EV_PENDING (1 << 13) /* internal use only! */
+#define EV_HAS_TIMEOUT (1 << 14) /* internal use only! */
+#define EV_CURRENT (1 << 15) /* internal use only! */
+
+struct event
+{
+ int fd;
+ short flags;
+ void (*cb)(int, short, void *);
+ void *cbarg;
+ struct timeval timeout;
+ struct timeval expire;
+
+#ifdef EVENT_DEBUG
+ char const *files[3];
+ int lines[3];
+#endif
+
+ TAILQ_ENTRY(event) next;
+};
+
+void event_init (void);
+int event_dispatch (void);
+
+void __event_set (struct event *, int, short,
+ void (*)(int, short, void *), void *);
+int __event_add (struct event *, struct timeval const *);
+int __event_del (struct event *);
+
+#ifdef EVENT_DEBUG
+#define event_log_err(fmt, args...) syslog(LOG_ERR, fmt, ##args)
+#define event_log_info(fmt, args...) syslog(LOG_INFO, fmt, ##args)
+#define event_log_notice(fmt, args...) syslog(LOG_NOTICE, fmt, ##args)
+#define event_log_debug(fmt, args...) syslog(LOG_DEBUG, fmt, ##args)
+
+#define event_set(ev, fd, flags, cb, cbarg) \
+ _event_set(__FILE__, __LINE__, ev, fd, flags, cb, cbarg)
+#define event_add(ev, timeout) \
+ _event_add(__FILE__, __LINE__, ev, timeout)
+#define event_del(ev) \
+ _event_del(__FILE__, __LINE__, ev)
+
+#define evtimer_set(ev, cb, cbarg) \
+ _event_set(__FILE__, __LINE__, ev, -1, 0, cb, cbarg)
+#define evtimer_add(ev, timeout) \
+ _event_add(__FILE__, __LINE__, ev, timeout)
+
+static inline void
+_event_set(char const *file, int line, struct event *ev, int fd, short flags,
+ void (*cb)(int, short, void *), void *cbarg)
+{
+ event_log_debug("set %s:%d ev=%p, fd=%d, flags=%#x, cb=%p, cbarg=%p",
+ file, line, ev, fd, flags, cb, cbarg);
+
+ ev->files[0] = file;
+ ev->lines[0] = line;
+
+ __event_set(ev, fd, flags, cb, cbarg);
+}
+
+static inline int
+_event_add(char const *file, int line, struct event *ev,
+ struct timeval const *timeout) {
+ event_log_debug("add %s:%d ev=%p, fd=%d, flags=%#x, cb=%p, cbarg=%p, " \
+ "timeout=%p", file, line, ev, ev->fd, ev->flags, ev->cb,
+ ev->cbarg, timeout);
+
+ ev->files[1] = file;
+ ev->lines[1] = line;
+
+ return (__event_add(ev, timeout));
+}
+
+static inline int
+_event_del(char const *file, int line, struct event *ev)
+{
+ event_log_debug("del %s:%d ev=%p, fd=%d, flags=%#x, cb=%p, cbarg=%p",
+ file, line, ev, ev->fd, ev->flags, ev->cb, ev->cbarg);
+
+ ev->files[2] = file;
+ ev->lines[2] = line;
+
+ return (__event_del(ev));
+}
+#else
+#define event_log_err(fmt, args...)
+#define event_log_info(fmt, args...)
+#define event_log_notice(fmt, args...)
+#define event_log_debug(fmt, args...)
+
+#define event_set(ev, fd, flags, cb, cbarg) \
+ __event_set(ev, fd, flags, cb, cbarg)
+#define event_add(ev, timeout) \
+ __event_add(ev, timeout)
+#define event_del(ev) \
+ __event_del(ev)
+
+#define evtimer_set(ev, cb, cbarg) \
+ __event_set(ev, -1, 0, cb, cbarg)
+#define evtimer_add(ev, timeout) \
+ __event_add(ev, timeout)
+#endif /* EVENT_DEBUG */
+
+#endif /* ndef _EVENT_H_ */
diff --git a/usr.sbin/bluetooth/btpand/packet.c b/usr.sbin/bluetooth/btpand/packet.c
new file mode 100644
index 0000000..21a563c
--- /dev/null
+++ b/usr.sbin/bluetooth/btpand/packet.c
@@ -0,0 +1,111 @@
+/* $NetBSD: packet.c,v 1.1 2008/08/17 13:20:57 plunky Exp $ */
+
+/*-
+ * Copyright (c) 2008 Iain Hibbert
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: packet.c,v 1.1 2008/08/17 13:20:57 plunky Exp $");
+
+#define L2CAP_SOCKET_CHECKED
+#include "btpand.h"
+
+packet_t *
+packet_alloc(channel_t *chan)
+{
+ packet_t *pkt;
+
+ pkt = malloc(sizeof(packet_t) + chan->mru);
+ if (pkt == NULL) {
+ log_err("%s() failed: %m", __func__);
+ return NULL;
+ }
+
+ memset(pkt, 0, sizeof(packet_t));
+ STAILQ_INIT(&pkt->extlist);
+ pkt->ptr = pkt->buf;
+
+ pkt->chan = chan;
+ chan->refcnt++;
+
+ return pkt;
+}
+
+void
+packet_free(packet_t *pkt)
+{
+ exthdr_t *eh;
+
+ if (pkt->refcnt-- > 0)
+ return;
+
+ while ((eh = STAILQ_FIRST(&pkt->extlist)) != NULL) {
+ STAILQ_REMOVE_HEAD(&pkt->extlist, next);
+ free(eh);
+ }
+
+ pkt->chan->refcnt--;
+ if (pkt->chan->refcnt == 0)
+ channel_free(pkt->chan);
+
+ free(pkt);
+}
+
+void
+packet_adj(packet_t *pkt, size_t size)
+{
+
+ assert(pkt->refcnt == 0);
+ assert(pkt->len >= size);
+
+ pkt->ptr += size;
+ pkt->len -= size;
+}
+
+pkthdr_t *
+pkthdr_alloc(packet_t *pkt)
+{
+ pkthdr_t *ph;
+
+ ph = malloc(sizeof(pkthdr_t));
+ if (ph == NULL) {
+ log_err("%s() failed: %m", __func__);
+ return NULL;
+ }
+
+ ph->data = pkt;
+ pkt->refcnt++;
+
+ return ph;
+}
+
+void
+pkthdr_free(pkthdr_t *ph)
+{
+
+ packet_free(ph->data);
+ free(ph);
+}
diff --git a/usr.sbin/bluetooth/btpand/sdp.c b/usr.sbin/bluetooth/btpand/sdp.c
new file mode 100644
index 0000000..3cad3f8
--- /dev/null
+++ b/usr.sbin/bluetooth/btpand/sdp.c
@@ -0,0 +1,210 @@
+/* $NetBSD: sdp.c,v 1.2 2008/12/06 20:01:14 plunky Exp $ */
+
+/*-
+ * Copyright (c) 2008 Iain Hibbert
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: sdp.c,v 1.2 2008/12/06 20:01:14 plunky Exp $");
+
+#include <string.h>
+
+#define L2CAP_SOCKET_CHECKED
+#include "sdp.h"
+
+/*
+ * SDP data stream manipulation routines
+ */
+
+/* Bluetooth Base UUID */
+static const uuid_t BASE_UUID = {
+ 0x00000000,
+ 0x0000,
+ 0x1000,
+ 0x80,
+ 0x00,
+ { 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb }
+};
+
+/*
+ * _sdp_match_uuid16(ptr, limit, uuid)
+ *
+ * examine SDP data stream at ptr for a UUID, and return
+ * true if it matches the supplied short alias bluetooth UUID.
+ * limit is the first address past the end of valid data.
+ */
+bool
+_sdp_match_uuid16(uint8_t **ptr, uint8_t *limit, uint16_t uuid)
+{
+ uint8_t *p = *ptr;
+ uuid_t u1, u2;
+
+ memcpy(&u1, &BASE_UUID, sizeof(uuid_t));
+ u1.time_low = uuid;
+
+ if (!_sdp_get_uuid(&p, limit, &u2)
+ || !uuid_equal(&u1, &u2, NULL))
+ return false;
+
+ *ptr = p;
+ return true;
+}
+
+/*
+ * _sdp_get_uuid(ptr, limit, uuid)
+ *
+ * examine SDP data stream at ptr for a UUID, and extract
+ * to given storage, advancing ptr.
+ * limit is the first address past the end of valid data.
+ */
+bool
+_sdp_get_uuid(uint8_t **ptr, uint8_t *limit, uuid_t *uuid)
+{
+ uint8_t *p = *ptr;
+
+ if (p + 1 > limit)
+ return false;
+
+ switch (*p++) {
+ case SDP_DATA_UUID16:
+ if (p + 2 > limit)
+ return false;
+
+ memcpy(uuid, &BASE_UUID, sizeof(uuid_t));
+ uuid->time_low = be16dec(p);
+ p += 2;
+ break;
+
+ case SDP_DATA_UUID32:
+ if (p + 4 > limit)
+ return false;
+
+ memcpy(uuid, &BASE_UUID, sizeof(uuid_t));
+ uuid->time_low = be32dec(p);
+ p += 4;
+ break;
+
+ case SDP_DATA_UUID128:
+ if (p + 16 > limit)
+ return false;
+
+ uuid_dec_be(p, uuid);
+ p += 16;
+ break;
+
+ default:
+ return false;
+ }
+
+ *ptr = p;
+ return true;
+}
+
+/*
+ * _sdp_get_seq(ptr, limit, seq)
+ *
+ * examine SDP data stream at ptr for a sequence. return
+ * seq pointer if found and advance ptr to next object.
+ * limit is the first address past the end of valid data.
+ */
+bool
+_sdp_get_seq(uint8_t **ptr, uint8_t *limit, uint8_t **seq)
+{
+ uint8_t *p = *ptr;
+ int32_t l;
+
+ if (p + 1 > limit)
+ return false;
+
+ switch (*p++) {
+ case SDP_DATA_SEQ8:
+ if (p + 1 > limit)
+ return false;
+
+ l = *p;
+ p += 1;
+ break;
+
+ case SDP_DATA_SEQ16:
+ if (p + 2 > limit)
+ return false;
+
+ l = be16dec(p);
+ p += 2;
+ break;
+
+ case SDP_DATA_SEQ32:
+ if (p + 4 > limit)
+ return false;
+
+ l = be32dec(p);
+ p += 4;
+ break;
+
+ default:
+ return false;
+ }
+ if (p + l > limit)
+ return false;
+
+ *seq = p;
+ *ptr = p + l;
+ return true;
+}
+
+/*
+ * _sdp_get_uint16(ptr, limit, value)
+ *
+ * examine SDP data stream at ptr for a uint16_t, and
+ * extract to given storage, advancing ptr.
+ * limit is the first address past the end of valid data.
+ */
+bool
+_sdp_get_uint16(uint8_t **ptr, uint8_t *limit, uint16_t *value)
+{
+ uint8_t *p = *ptr;
+ uint16_t v;
+
+ if (p + 1 > limit)
+ return false;
+
+ switch (*p++) {
+ case SDP_DATA_UINT16:
+ if (p + 2 > limit)
+ return false;
+
+ v = be16dec(p);
+ p += 2;
+ break;
+
+ default:
+ return false;
+ }
+
+ *value = v;
+ *ptr = p;
+ return true;
+}
diff --git a/usr.sbin/bluetooth/btpand/sdp.h b/usr.sbin/bluetooth/btpand/sdp.h
new file mode 100644
index 0000000..32dd95d
--- /dev/null
+++ b/usr.sbin/bluetooth/btpand/sdp.h
@@ -0,0 +1,38 @@
+/* $NetBSD: sdp.h,v 1.2 2008/12/06 20:01:15 plunky Exp $ */
+
+/*-
+ * Copyright (c) 2008 Iain Hibbert
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$ */
+
+#include <bluetooth.h>
+#include <sdp.h>
+#include <stdbool.h>
+#include <uuid.h>
+
+bool _sdp_match_uuid16(uint8_t **, uint8_t *, uint16_t);
+bool _sdp_get_uuid(uint8_t **, uint8_t *, uuid_t *);
+bool _sdp_get_seq(uint8_t **, uint8_t *, uint8_t **);
+bool _sdp_get_uint16(uint8_t **, uint8_t *, uint16_t *);
diff --git a/usr.sbin/bluetooth/btpand/server.c b/usr.sbin/bluetooth/btpand/server.c
new file mode 100644
index 0000000..b72b032
--- /dev/null
+++ b/usr.sbin/bluetooth/btpand/server.c
@@ -0,0 +1,293 @@
+/* $NetBSD: server.c,v 1.2 2009/01/24 17:29:28 plunky Exp $ */
+
+/*-
+ * Copyright (c) 2008 Iain Hibbert
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: server.c,v 1.2 2009/01/24 17:29:28 plunky Exp $");
+
+#include <sys/ioctl.h>
+
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <sdp.h>
+#include <unistd.h>
+
+#include "btpand.h"
+#include "bnep.h"
+
+static struct event server_ev;
+static int server_fd;
+static int server_avail;
+
+static void * server_ss;
+static uint32_t server_handle;
+
+static void server_open(void);
+static void server_close(void);
+static void server_read(int, short, void *);
+static void server_register(void);
+
+void
+server_init(void)
+{
+
+ server_fd = -1;
+}
+
+/*
+ * The server_update() function is called whenever the channel count is
+ * changed. We maintain the SDP record and open or close the server socket
+ * as required.
+ */
+void
+server_update(int count)
+{
+
+ if (server_limit == 0)
+ return;
+
+ log_debug("count %d", count);
+
+ server_avail = UINT8_MAX - (count - 1) * UINT8_MAX / server_limit;
+ log_info("Service Availability: %d/%d", server_avail, UINT8_MAX);
+
+ if (server_avail == 0 && server_fd != -1)
+ server_close();
+
+ if (server_avail > 0 && server_fd == -1)
+ server_open();
+
+ if (service_name)
+ server_register();
+}
+
+static void
+server_open(void)
+{
+ struct sockaddr_l2cap sa;
+ uint16_t mru;
+
+ server_fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP);
+ if (server_fd == -1) {
+ log_err("Could not open L2CAP socket: %m");
+ exit(EXIT_FAILURE);
+ }
+
+ memset(&sa, 0, sizeof(sa));
+ sa.l2cap_family = AF_BLUETOOTH;
+ sa.l2cap_len = sizeof(sa);
+ sa.l2cap_psm = htole16(l2cap_psm);
+ sa.l2cap_bdaddr_type = BDADDR_BREDR;
+ sa.l2cap_cid = 0;
+
+ bdaddr_copy(&sa.l2cap_bdaddr, &local_bdaddr);
+ if (bind(server_fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
+ log_err("Could not bind server socket: %m");
+ exit(EXIT_FAILURE);
+ }
+
+ mru = BNEP_MTU_MIN;
+ if (setsockopt(server_fd, SOL_L2CAP,
+ SO_L2CAP_IMTU, &mru, sizeof(mru)) == -1) {
+ log_err("Could not set L2CAP IMTU (%d): %m", mru);
+ exit(EXIT_FAILURE);
+ }
+
+ if (listen(server_fd, 0) == -1) {
+ log_err("Could not listen on server socket: %m");
+ exit(EXIT_FAILURE);
+ }
+
+ event_set(&server_ev, server_fd, EV_READ | EV_PERSIST, server_read, NULL);
+ if (event_add(&server_ev, NULL) == -1) {
+ log_err("Could not add server event: %m");
+ exit(EXIT_FAILURE);
+ }
+
+ log_info("server socket open");
+}
+
+static void
+server_close(void)
+{
+
+ event_del(&server_ev);
+ close(server_fd);
+ server_fd = -1;
+
+ log_info("server socket closed");
+}
+
+/*
+ * handle connection request
+ */
+static void
+server_read(int s, short ev, void *arg)
+{
+ struct sockaddr_l2cap ra, la;
+ channel_t *chan;
+ socklen_t len;
+ int fd, n;
+ uint16_t mru, mtu;
+
+ len = sizeof(ra);
+ fd = accept(s, (struct sockaddr *)&ra, &len);
+ if (fd == -1)
+ return;
+
+ n = 1;
+ if (ioctl(fd, FIONBIO, &n) == -1) {
+ log_err("Could not set NonBlocking IO: %m");
+ close(fd);
+ return;
+ }
+
+ len = sizeof(mru);
+ if (getsockopt(fd, SOL_L2CAP, SO_L2CAP_IMTU, &mru, &len) == -1) {
+ log_err("Could not get L2CAP IMTU: %m");
+ close(fd);
+ return;
+ }
+ if(mru < BNEP_MTU_MIN) {
+ log_err("L2CAP IMTU too small (%d)", mru);
+ close(fd);
+ return;
+ }
+
+ len = sizeof(n);
+ if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &n, &len) == -1) {
+ log_err("Could not read SO_RCVBUF");
+ close(fd);
+ return;
+ }
+ if (n < (mru * 10)) {
+ n = mru * 10;
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)) == -1)
+ log_info("Could not increase SO_RCVBUF (from %d)", n);
+ }
+
+ len = sizeof(mtu);
+ if (getsockopt(fd, SOL_L2CAP, SO_L2CAP_OMTU, &mtu, &len) == -1) {
+ log_err("Could not get L2CAP OMTU: %m");
+ close(fd);
+ return;
+ }
+ if (mtu < BNEP_MTU_MIN) {
+ log_err("L2CAP OMTU too small (%d)", mtu);
+ close(fd);
+ return;
+ }
+
+ len = sizeof(n);
+ if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &n, &len) == -1) {
+ log_err("Could not get socket send buffer size: %m");
+ close(fd);
+ return;
+ }
+
+ if (n < (mtu * 2)) {
+ n = mtu * 2;
+ if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &n, sizeof(n)) == -1) {
+ log_err("Could not set socket send buffer size (%d): %m", n);
+ close(fd);
+ return;
+ }
+ }
+
+ n = mtu;
+ if (setsockopt(fd, SOL_SOCKET, SO_SNDLOWAT, &n, sizeof(n)) == -1) {
+ log_err("Could not set socket low water mark (%d): %m", n);
+ close(fd);
+ return;
+ }
+
+ len = sizeof(la);
+ if (getsockname(fd, (struct sockaddr *)&la, &len) == -1) {
+ log_err("Could not get socket address: %m");
+ close(fd);
+ return;
+ }
+
+ log_info("Accepted connection from %s", bt_ntoa(&ra.l2cap_bdaddr, NULL));
+
+ chan = channel_alloc();
+ if (chan == NULL) {
+ close(fd);
+ return;
+ }
+
+ chan->send = bnep_send;
+ chan->recv = bnep_recv;
+ chan->mru = mru;
+ chan->mtu = mtu;
+ b2eaddr(chan->raddr, &ra.l2cap_bdaddr);
+ b2eaddr(chan->laddr, &la.l2cap_bdaddr);
+ chan->state = CHANNEL_WAIT_CONNECT_REQ;
+ channel_timeout(chan, 10);
+ if (!channel_open(chan, fd)) {
+ chan->state = CHANNEL_CLOSED;
+ channel_free(chan);
+ close(fd);
+ return;
+ }
+}
+
+static void
+server_register(void)
+{
+ sdp_nap_profile_t p;
+ int rv;
+
+ if (server_ss == NULL) {
+ server_ss = sdp_open_local(control_path);
+ if (server_ss == NULL || sdp_error(server_ss) != 0) {
+ log_err("failed to contact SDP server");
+ return;
+ }
+ }
+
+ memset(&p, 0, sizeof(p));
+ p.psm = l2cap_psm;
+ p.load_factor = server_avail;
+ p.security_description = (l2cap_mode == 0 ? 0x0000 : 0x0001);
+
+ if (server_handle)
+ rv = sdp_change_service(server_ss, server_handle,
+ (uint8_t *)&p, sizeof(p));
+ else
+ rv = sdp_register_service(server_ss, service_class,
+ &local_bdaddr, (uint8_t *)&p, sizeof(p), &server_handle);
+
+ if (rv != 0) {
+ errno = sdp_error(server_ss);
+ log_err("%s: %m", service_name);
+ exit(EXIT_FAILURE);
+ }
+}
diff --git a/usr.sbin/bluetooth/btpand/tap.c b/usr.sbin/bluetooth/btpand/tap.c
new file mode 100644
index 0000000..644bf02
--- /dev/null
+++ b/usr.sbin/bluetooth/btpand/tap.c
@@ -0,0 +1,168 @@
+/* $NetBSD: tap.c,v 1.1 2008/08/17 13:20:57 plunky Exp $ */
+
+/*-
+ * Copyright (c) 2008 Iain Hibbert
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: tap.c,v 1.1 2008/08/17 13:20:57 plunky Exp $");
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/uio.h>
+
+#include <net/if_tap.h>
+
+#include <fcntl.h>
+#include <libutil.h>
+#include <paths.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#define L2CAP_SOCKET_CHECKED
+#include "btpand.h"
+
+static bool tap_send(channel_t *, packet_t *);
+static bool tap_recv(packet_t *);
+
+void
+tap_init(void)
+{
+ channel_t *chan;
+ struct ifreq ifr;
+ int fd, s;
+ char pidfile[PATH_MAX];
+
+ fd = open(interface_name, O_RDWR);
+ if (fd == -1) {
+ log_err("Could not open \"%s\": %m", interface_name);
+ exit(EXIT_FAILURE);
+ }
+
+ memset(&ifr, 0, sizeof(ifr));
+ if (ioctl(fd, TAPGIFNAME, &ifr) == -1) {
+ log_err("Could not get interface name: %m");
+ exit(EXIT_FAILURE);
+ }
+
+ s = socket(AF_INET, SOCK_DGRAM, 0);
+ if (s == -1) {
+ log_err("Could not open PF_LINK socket: %m");
+ exit(EXIT_FAILURE);
+ }
+
+ ifr.ifr_addr.sa_family = AF_LINK;
+ ifr.ifr_addr.sa_len = ETHER_ADDR_LEN;
+ b2eaddr(ifr.ifr_addr.sa_data, &local_bdaddr);
+
+ if (ioctl(s, SIOCSIFLLADDR, &ifr) == -1) {
+ log_err("Could not set %s physical address: %m", ifr.ifr_name);
+ exit(EXIT_FAILURE);
+ }
+
+ if (ioctl(s, SIOCGIFFLAGS, &ifr) == -1) {
+ log_err("Could not get interface flags: %m");
+ exit(EXIT_FAILURE);
+ }
+
+ if ((ifr.ifr_flags & IFF_UP) == 0) {
+ ifr.ifr_flags |= IFF_UP;
+
+ if (ioctl(s, SIOCSIFFLAGS, &ifr) == -1) {
+ log_err("Could not set IFF_UP: %m");
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ close(s);
+
+ log_info("Using interface %s with addr %s", ifr.ifr_name,
+ ether_ntoa((struct ether_addr *)&ifr.ifr_addr.sa_data));
+
+ chan = channel_alloc();
+ if (chan == NULL)
+ exit(EXIT_FAILURE);
+
+ chan->send = tap_send;
+ chan->recv = tap_recv;
+ chan->mru = ETHER_HDR_LEN + ETHER_MAX_LEN;
+ memcpy(chan->raddr, ifr.ifr_addr.sa_data, ETHER_ADDR_LEN);
+ memcpy(chan->laddr, ifr.ifr_addr.sa_data, ETHER_ADDR_LEN);
+ chan->state = CHANNEL_OPEN;
+ if (!channel_open(chan, fd))
+ exit(EXIT_FAILURE);
+
+ snprintf(pidfile, sizeof(pidfile), "%s/%s.pid",
+ _PATH_VARRUN, ifr.ifr_name);
+ chan->pfh = pidfile_open(pidfile, 0600, NULL);
+ if (chan->pfh == NULL)
+ log_err("can't create pidfile");
+ else if (pidfile_write(chan->pfh) < 0) {
+ log_err("can't write pidfile");
+ pidfile_remove(chan->pfh);
+ chan->pfh = NULL;
+ }
+}
+
+static bool
+tap_send(channel_t *chan, packet_t *pkt)
+{
+ struct iovec iov[4];
+ ssize_t nw;
+
+ iov[0].iov_base = pkt->dst;
+ iov[0].iov_len = ETHER_ADDR_LEN;
+ iov[1].iov_base = pkt->src;
+ iov[1].iov_len = ETHER_ADDR_LEN;
+ iov[2].iov_base = pkt->type;
+ iov[2].iov_len = ETHER_TYPE_LEN;
+ iov[3].iov_base = pkt->ptr;
+ iov[3].iov_len = pkt->len;
+
+ /* tap device write never fails */
+ nw = writev(chan->fd, iov, __arraycount(iov));
+ assert(nw > 0);
+
+ return true;
+}
+
+static bool
+tap_recv(packet_t *pkt)
+{
+
+ if (pkt->len < ETHER_HDR_LEN)
+ return false;
+
+ pkt->dst = pkt->ptr;
+ packet_adj(pkt, ETHER_ADDR_LEN);
+ pkt->src = pkt->ptr;
+ packet_adj(pkt, ETHER_ADDR_LEN);
+ pkt->type = pkt->ptr;
+ packet_adj(pkt, ETHER_TYPE_LEN);
+
+ return true;
+}
diff --git a/usr.sbin/bluetooth/hccontrol/Makefile b/usr.sbin/bluetooth/hccontrol/Makefile
new file mode 100644
index 0000000..a81fda4
--- /dev/null
+++ b/usr.sbin/bluetooth/hccontrol/Makefile
@@ -0,0 +1,13 @@
+# $Id: Makefile,v 1.7 2003/08/14 20:06:17 max Exp $
+# $FreeBSD$
+
+PROG= hccontrol
+MAN= hccontrol.8
+SRCS= send_recv.c link_policy.c link_control.c le.c\
+ host_controller_baseband.c info.c status.c node.c hccontrol.c \
+ util.c
+WARNS?= 2
+
+LIBADD= bluetooth
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bluetooth/hccontrol/Makefile.depend b/usr.sbin/bluetooth/hccontrol/Makefile.depend
new file mode 100644
index 0000000..5d21038
--- /dev/null
+++ b/usr.sbin/bluetooth/hccontrol/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbluetooth \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bluetooth/hccontrol/hccontrol.8 b/usr.sbin/bluetooth/hccontrol/hccontrol.8
new file mode 100644
index 0000000..d8c5263
--- /dev/null
+++ b/usr.sbin/bluetooth/hccontrol/hccontrol.8
@@ -0,0 +1,184 @@
+.\" Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+.\"
+.\" $Id: hccontrol.8,v 1.6 2003/08/06 21:26:38 max Exp $
+.\" $FreeBSD$
+.\"
+.Dd February 7, 2015
+.Dt HCCONTROL 8
+.Os
+.Sh NAME
+.Nm hccontrol
+.Nd Bluetooth HCI configuration utility
+.Sh SYNOPSIS
+.Nm
+.Op Fl hN
+.Op Fl n Ar HCI_node_name
+.Ar command
+.Op Ar parameters ...
+.Sh DESCRIPTION
+The
+.Nm
+utility connects to the specified Netgraph node of type
+.Dv HCI
+or the first one found if none is specified and attempts to send the specified
+command to the HCI Netgraph node or to the associated Bluetooth device.
+The
+.Nm
+utility will print results to the standard output and error messages to
+the standard error.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl h
+Display usage message and exit.
+.It Fl N
+Show Bluetooth addresses as numbers.
+Normally
+.Nm
+attempts to resolve Bluetooth addresses, and display them symbolically.
+.It Fl n Ar HCI_node_name
+Connect to the specified HCI Netgraph node.
+.It Ar command
+One of the supported commands (see below).
+The special command
+.Cm help
+can be used to obtain the list of all supported commands.
+To get more information about a specific command use
+.Cm help Ar command .
+.It Ar parameters
+One or more optional space separated command parameters.
+Many commands require a remote device address as one of the parameters.
+The remote device address can be specified as BD_ADDR or a name.
+If a name was specified then the
+.Nm
+utility will attempt to resolve the name via
+.Xr bt_gethostbyname 3 .
+.El
+.Sh COMMANDS
+The currently supported HCI commands in
+.Nm
+are:
+.Pp
+.Bl -tag -width 40n -offset indent -compact
+.It Cm Inquiry
+.It Cm Create_Connection
+.It Cm Disconnect
+.It Cm Add_SCO_Connection
+.It Cm Change_Connection_Packet_Type
+.It Cm Remote_Name_Request
+.It Cm Read_Remote_Supported_Features
+.It Cm Read_Remote_Version_Information
+.It Cm Read_Clock_Offset
+.It Cm Role_Discovery
+.It Cm Switch_Role
+.It Cm Read_Link_Policy_Settings
+.It Cm Write_Link_Policy_Settings
+.It Cm Reset
+.It Cm Read_Pin_Type
+.It Cm Write_Pin_Type
+.It Cm Read_Stored_Link_Key
+.It Cm Write_Stored_Link_Key
+.It Cm Delete_Stored_Link_Key
+.It Cm Change_Local_Name
+.It Cm Read_Local_Name
+.It Cm Read_Connection_Accept_Timeout
+.It Cm Write_Connection_Accept_Timeout
+.It Cm Read_Page_Timeout
+.It Cm Write_Page_Timeout
+.It Cm Read_Scan_Enable
+.It Cm Write_Scan_Enable
+.It Cm Read_Page_Scan_Activity
+.It Cm Write_Page_Scan_Activity
+.It Cm Read_Inquiry_Scan_Activity
+.It Cm Write_Inquiry_Scan_Activity
+.It Cm Read_Authentication_Enable
+.It Cm Write_Authentication_Enable
+.It Cm Read_Encryption_Mode
+.It Cm Write_Encryption_Mode
+.It Cm Read_Class_Of_Device
+.It Cm Write_Class_Of_Device
+.It Cm Read_Voice_Settings
+.It Cm Write_Voice_Settings
+.It Cm Read_Number_Broadcast_Retransmissions
+.It Cm Write_Number_Broadcast_Retransmissions
+.It Cm Read_Hold_Mode_Activity
+.It Cm Write_Hold_Mode_Activity
+.It Cm Read_SCO_Flow_Control_Enable
+.It Cm Write_SCO_Flow_Control_Enable
+.It Cm Read_Link_Supervision_Timeout
+.It Cm Write_Link_Supervision_Timeout
+.It Cm Read_Page_Scan_Period_Mode
+.It Cm Write_Page_Scan_Period_Mode
+.It Cm Read_Page_Scan_Mode
+.It Cm Write_Page_Scan_Mode
+.It Cm Read_Local_Version_Information
+.It Cm Read_Local_Supported_Features
+.It Cm Read_Buffer_Size
+.It Cm Read_Country_Code
+.It Cm Read_BD_ADDR
+.It Cm Read_Failed_Contact_Counter
+.It Cm Reset_Failed_Contact_Counter
+.It Cm Get_Link_Quality
+.It Cm Read_RSSI
+.El
+.Pp
+The currently supported node commands in
+.Nm
+are:
+.Pp
+.Bl -tag -width 40n -offset indent -compact
+.It Cm Read_Node_State
+.It Cm Initialize
+.It Cm Read_Debug_Level
+.It Cm Write_Debug_Level
+.It Cm Read_Node_Buffer_Size
+.It Cm Read_Node_BD_ADDR
+.It Cm Read_Node_Features
+.It Cm Read_Node_Stat
+.It Cm Reset_Node_Stat
+.It Cm Flush_Neighbor_Cache
+.It Cm Read_Neighbor_Cache
+.It Cm Read_Connection_List
+.It Cm Read_Node_Link_Policy_Settings_Mask
+.It Cm Write_Node_Link_Policy_Settings_Mask
+.It Cm Read_Node_Packet_Mask
+.It Cm Write_Node_Packet_Mask
+.It Cm Read_Node_Role_Switch
+.It Cm Write_Node_Role_Switch
+.It Cm Read_Node_List
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr bluetooth 3 ,
+.Xr netgraph 3 ,
+.Xr netgraph 4 ,
+.Xr ng_hci 4 ,
+.Xr hcseriald 8
+.Sh AUTHORS
+.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com
+.Sh BUGS
+Most likely.
+Please report if found.
diff --git a/usr.sbin/bluetooth/hccontrol/hccontrol.c b/usr.sbin/bluetooth/hccontrol/hccontrol.c
new file mode 100644
index 0000000..b72854f
--- /dev/null
+++ b/usr.sbin/bluetooth/hccontrol/hccontrol.c
@@ -0,0 +1,330 @@
+/*
+ * hccontrol.c
+ *
+ * Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: hccontrol.c,v 1.5 2003/09/05 00:38:24 max Exp $
+ * $FreeBSD$
+ */
+
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <sys/ioctl.h>
+#include <sys/sysctl.h>
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <netgraph/ng_message.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "hccontrol.h"
+
+/* Prototypes */
+static int do_hci_command (char const *, int, char **);
+static struct hci_command * find_hci_command (char const *, struct hci_command *);
+static int find_hci_nodes (struct nodeinfo **);
+static void print_hci_command (struct hci_command *);
+static void usage (void);
+
+/* Globals */
+int verbose = 0;
+int timeout;
+int numeric_bdaddr = 0;
+
+/* Main */
+int
+main(int argc, char *argv[])
+{
+ char *node = NULL;
+ int n;
+
+ /* Process command line arguments */
+ while ((n = getopt(argc, argv, "n:Nvh")) != -1) {
+ switch (n) {
+ case 'n':
+ node = optarg;
+ break;
+
+ case 'N':
+ numeric_bdaddr = 1;
+ break;
+
+ case 'v':
+ verbose = 1;
+ break;
+
+ case 'h':
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (*argv == NULL)
+ usage();
+
+ n = do_hci_command(node, argc, argv);
+
+ return (n);
+} /* main */
+
+/* Create socket and bind it */
+static int
+socket_open(char const *node)
+{
+ struct sockaddr_hci addr;
+ struct ng_btsocket_hci_raw_filter filter;
+ int s, mib[4], num;
+ size_t size;
+ struct nodeinfo *nodes;
+
+ num = find_hci_nodes(&nodes);
+ if (num == 0)
+ errx(7, "Could not find HCI nodes");
+
+ if (node == NULL) {
+ node = strdup(nodes[0].name);
+ if (num > 1)
+ fprintf(stdout, "Using HCI node: %s\n", node);
+ }
+
+ free(nodes);
+
+ s = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_HCI);
+ if (s < 0)
+ err(1, "Could not create socket");
+
+ memset(&addr, 0, sizeof(addr));
+ addr.hci_len = sizeof(addr);
+ addr.hci_family = AF_BLUETOOTH;
+ strncpy(addr.hci_node, node, sizeof(addr.hci_node));
+ if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0)
+ err(2, "Could not bind socket, node=%s", node);
+
+ if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0)
+ err(3, "Could not connect socket, node=%s", node);
+
+ memset(&filter, 0, sizeof(filter));
+ bit_set(filter.event_mask, NG_HCI_EVENT_COMMAND_COMPL - 1);
+ bit_set(filter.event_mask, NG_HCI_EVENT_COMMAND_STATUS - 1);
+ bit_set(filter.event_mask, NG_HCI_EVENT_INQUIRY_COMPL - 1);
+ bit_set(filter.event_mask, NG_HCI_EVENT_INQUIRY_RESULT - 1);
+ bit_set(filter.event_mask, NG_HCI_EVENT_CON_COMPL - 1);
+ bit_set(filter.event_mask, NG_HCI_EVENT_DISCON_COMPL - 1);
+ bit_set(filter.event_mask, NG_HCI_EVENT_REMOTE_NAME_REQ_COMPL - 1);
+ bit_set(filter.event_mask, NG_HCI_EVENT_READ_REMOTE_FEATURES_COMPL - 1);
+ bit_set(filter.event_mask, NG_HCI_EVENT_READ_REMOTE_VER_INFO_COMPL - 1);
+ bit_set(filter.event_mask, NG_HCI_EVENT_RETURN_LINK_KEYS - 1);
+ bit_set(filter.event_mask, NG_HCI_EVENT_READ_CLOCK_OFFSET_COMPL - 1);
+ bit_set(filter.event_mask, NG_HCI_EVENT_CON_PKT_TYPE_CHANGED - 1);
+ bit_set(filter.event_mask, NG_HCI_EVENT_ROLE_CHANGE - 1);
+ bit_set(filter.event_mask, NG_HCI_EVENT_LE -1);
+
+ if (setsockopt(s, SOL_HCI_RAW, SO_HCI_RAW_FILTER,
+ (void * const) &filter, sizeof(filter)) < 0)
+ err(4, "Could not setsockopt()");
+
+ size = (sizeof(mib)/sizeof(mib[0]));
+ if (sysctlnametomib("net.bluetooth.hci.command_timeout",mib,&size) < 0)
+ err(5, "Could not sysctlnametomib()");
+
+ if (sysctl(mib, sizeof(mib)/sizeof(mib[0]),
+ (void *) &timeout, &size, NULL, 0) < 0)
+ err(6, "Could not sysctl()");
+
+ timeout ++;
+
+ return (s);
+} /* socket_open */
+
+/* Execute commands */
+static int
+do_hci_command(char const *node, int argc, char **argv)
+{
+ char *cmd = argv[0];
+ struct hci_command *c = NULL;
+ int s, e, help;
+
+ help = 0;
+ if (strcasecmp(cmd, "help") == 0) {
+ argc --;
+ argv ++;
+
+ if (argc <= 0) {
+ fprintf(stdout, "Supported commands:\n");
+ print_hci_command(link_control_commands);
+ print_hci_command(link_policy_commands);
+ print_hci_command(host_controller_baseband_commands);
+ print_hci_command(info_commands);
+ print_hci_command(status_commands);
+ print_hci_command(le_commands);
+ print_hci_command(node_commands);
+ fprintf(stdout, "\nFor more information use " \
+ "'help command'\n");
+
+ return (OK);
+ }
+
+ help = 1;
+ cmd = argv[0];
+ }
+
+ c = find_hci_command(cmd, link_control_commands);
+ if (c != NULL)
+ goto execute;
+
+ c = find_hci_command(cmd, link_policy_commands);
+ if (c != NULL)
+ goto execute;
+
+ c = find_hci_command(cmd, host_controller_baseband_commands);
+ if (c != NULL)
+ goto execute;
+
+ c = find_hci_command(cmd, info_commands);
+ if (c != NULL)
+ goto execute;
+
+ c = find_hci_command(cmd, status_commands);
+ if (c != NULL)
+ goto execute;
+
+ c = find_hci_command(cmd, le_commands);
+ if (c != NULL)
+ goto execute;
+
+
+ c = find_hci_command(cmd, node_commands);
+ if (c == NULL) {
+ fprintf(stdout, "Unknown command: \"%s\"\n", cmd);
+ return (ERROR);
+ }
+execute:
+ if (!help) {
+ s = socket_open(node);
+ e = (c->handler)(s, -- argc, ++ argv);
+ close(s);
+ } else
+ e = USAGE;
+
+ switch (e) {
+ case OK:
+ case FAILED:
+ break;
+
+ case ERROR:
+ fprintf(stdout, "Could not execute command \"%s\". %s\n",
+ cmd, strerror(errno));
+ break;
+
+ case USAGE:
+ fprintf(stdout, "Usage: %s\n%s\n", c->command, c->description);
+ break;
+
+ default: assert(0); break;
+ }
+
+
+ return (e);
+} /* do_hci_command */
+
+/* Try to find command in specified category */
+static struct hci_command *
+find_hci_command(char const *command, struct hci_command *category)
+{
+ struct hci_command *c = NULL;
+
+ for (c = category; c->command != NULL; c++) {
+ char *c_end = strchr(c->command, ' ');
+
+ if (c_end != NULL) {
+ int len = c_end - c->command;
+
+ if (strncasecmp(command, c->command, len) == 0)
+ return (c);
+ } else if (strcasecmp(command, c->command) == 0)
+ return (c);
+ }
+
+ return (NULL);
+} /* find_hci_command */
+
+/* Find all HCI nodes */
+static int
+find_hci_nodes(struct nodeinfo** nodes)
+{
+ struct ng_btsocket_hci_raw_node_list_names r;
+ struct sockaddr_hci addr;
+ int s;
+ const char * node = "ubt0hci";
+
+ r.num_names = MAX_NODE_NUM;
+ r.names = (struct nodeinfo*)calloc(MAX_NODE_NUM, sizeof(struct nodeinfo));
+ if (r.names == NULL)
+ err(8, "Could not allocate memory");
+
+ s = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_HCI);
+ if (s < 0)
+ err(9, "Could not create socket");
+
+ memset(&addr, 0, sizeof(addr));
+ addr.hci_len = sizeof(addr);
+ addr.hci_family = AF_BLUETOOTH;
+ strncpy(addr.hci_node, node, sizeof(addr.hci_node));
+ if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0)
+ err(10, "Could not bind socket");
+
+ if (ioctl(s, SIOC_HCI_RAW_NODE_LIST_NAMES, &r, sizeof(r)) < 0)
+ err(11, "Could not get list of HCI nodes");
+
+ close(s);
+
+ *nodes = r.names;
+
+ return (r.num_names);
+} /* find_hci_nodes */
+
+/* Print commands in specified category */
+static void
+print_hci_command(struct hci_command *category)
+{
+ struct hci_command *c = NULL;
+
+ for (c = category; c->command != NULL; c++)
+ fprintf(stdout, "\t%s\n", c->command);
+} /* print_hci_command */
+
+/* Usage */
+static void
+usage(void)
+{
+ fprintf(stdout, "Usage: hccontrol [-hN] [-n HCI_node_name] cmd [p1] [..]\n");
+ exit(255);
+} /* usage */
+
diff --git a/usr.sbin/bluetooth/hccontrol/hccontrol.h b/usr.sbin/bluetooth/hccontrol/hccontrol.h
new file mode 100644
index 0000000..c96aab0
--- /dev/null
+++ b/usr.sbin/bluetooth/hccontrol/hccontrol.h
@@ -0,0 +1,80 @@
+/*
+ * hccontrol.h
+ *
+ * Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: hccontrol.h,v 1.2 2003/05/19 17:29:29 max Exp $
+ * $FreeBSD$
+ */
+
+#ifndef _HCCONTROL_H_
+#define _HCCONTROL_H_
+
+#define OK 0 /* everything was OK */
+#define ERROR 1 /* could not execute command */
+#define FAILED 2 /* error was reported */
+#define USAGE 3 /* invalid parameters */
+
+#define MAX_NODE_NUM 16 /* max number of nodes */
+
+struct hci_command {
+ char const *command;
+ char const *description;
+ int (*handler)(int, int, char **);
+};
+
+extern int timeout;
+extern int verbose;
+extern struct hci_command link_control_commands[];
+extern struct hci_command link_policy_commands[];
+extern struct hci_command host_controller_baseband_commands[];
+extern struct hci_command info_commands[];
+extern struct hci_command status_commands[];
+extern struct hci_command node_commands[];
+extern struct hci_command le_commands[];
+
+int hci_request (int, int, char const *, int, char *, int *);
+int hci_simple_request (int, int, char *, int *);
+int hci_send (int, char const *, int);
+int hci_recv (int, char *, int *);
+
+char const * hci_link2str (int);
+char const * hci_pin2str (int);
+char const * hci_scan2str (int);
+char const * hci_encrypt2str (int, int);
+char const * hci_coding2str (int);
+char const * hci_vdata2str (int);
+char const * hci_hmode2str (int, char *, int);
+char const * hci_ver2str (int);
+char const * hci_lmpver2str (int);
+char const * hci_manufacturer2str(int);
+char const * hci_features2str (uint8_t *, char *, int);
+char const * hci_cc2str (int);
+char const * hci_con_state2str (int);
+char const * hci_status2str (int);
+char const * hci_bdaddr2str (bdaddr_t const *);
+
+#endif /* _HCCONTROL_H_ */
+
diff --git a/usr.sbin/bluetooth/hccontrol/host_controller_baseband.c b/usr.sbin/bluetooth/hccontrol/host_controller_baseband.c
new file mode 100644
index 0000000..532ca1c
--- /dev/null
+++ b/usr.sbin/bluetooth/hccontrol/host_controller_baseband.c
@@ -0,0 +1,1961 @@
+/*
+ * host_controller_baseband.c
+ *
+ * Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: host_controller_baseband.c,v 1.4 2003/08/18 19:19:53 max Exp $
+ * $FreeBSD$
+ */
+
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include "hccontrol.h"
+
+/* Convert hex ASCII to int4 */
+static int
+hci_hexa2int4(const char *a)
+{
+ if ('0' <= *a && *a <= '9')
+ return (*a - '0');
+
+ if ('A' <= *a && *a <= 'F')
+ return (*a - 'A' + 0xa);
+
+ if ('a' <= *a && *a <= 'f')
+ return (*a - 'a' + 0xa);
+
+ return (-1);
+}
+
+/* Convert hex ASCII to int8 */
+static int
+hci_hexa2int8(const char *a)
+{
+ int hi = hci_hexa2int4(a);
+ int lo = hci_hexa2int4(a + 1);
+
+ if (hi < 0 || lo < 0)
+ return (-1);
+
+ return ((hi << 4) | lo);
+}
+
+/* Convert ascii hex string to the uint8_t[] */
+static int
+hci_hexstring2array(char const *s, uint8_t *a, int asize)
+{
+ int i, l, b;
+
+ l = strlen(s) / 2;
+ if (l > asize)
+ l = asize;
+
+ for (i = 0; i < l; i++) {
+ b = hci_hexa2int8(s + i * 2);
+ if (b < 0)
+ return (-1);
+
+ a[i] = (b & 0xff);
+ }
+
+ return (0);
+}
+
+/* Send RESET to the unit */
+static int
+hci_reset(int s, int argc, char **argv)
+{
+ ng_hci_status_rp rp;
+ int n;
+
+ n = sizeof(rp);
+ if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_RESET), (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ return (OK);
+} /* hci_reset */
+
+/* Send Read_PIN_Type command to the unit */
+static int
+hci_read_pin_type(int s, int argc, char **argv)
+{
+ ng_hci_read_pin_type_rp rp;
+ int n;
+
+ n = sizeof(rp);
+ if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_READ_PIN_TYPE),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "PIN type: %s [%#02x]\n",
+ hci_pin2str(rp.pin_type), rp.pin_type);
+
+ return (OK);
+} /* hci_read_pin_type */
+
+/* Send Write_PIN_Type command to the unit */
+static int
+hci_write_pin_type(int s, int argc, char **argv)
+{
+ ng_hci_write_pin_type_cp cp;
+ ng_hci_write_pin_type_rp rp;
+ int n;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 1:
+ if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 1)
+ return (USAGE);
+
+ cp.pin_type = (uint8_t) n;
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send command */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_WRITE_PIN_TYPE),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp , &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ return (OK);
+} /* hci_write_pin_type */
+
+/* Send Read_Stored_Link_Key command to the unit */
+static int
+hci_read_stored_link_key(int s, int argc, char **argv)
+{
+ struct {
+ ng_hci_cmd_pkt_t hdr;
+ ng_hci_read_stored_link_key_cp cp;
+ } __attribute__ ((packed)) cmd;
+
+ struct {
+ ng_hci_event_pkt_t hdr;
+ union {
+ ng_hci_command_compl_ep cc;
+ ng_hci_return_link_keys_ep key;
+ uint8_t b[NG_HCI_EVENT_PKT_SIZE];
+ } ep;
+ } __attribute__ ((packed)) event;
+
+ int n, n1;
+
+ /* Send command */
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.hdr.type = NG_HCI_CMD_PKT;
+ cmd.hdr.opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_READ_STORED_LINK_KEY));
+ cmd.hdr.length = sizeof(cmd.cp);
+
+ switch (argc) {
+ case 1:
+ /* parse BD_ADDR */
+ if (!bt_aton(argv[0], &cmd.cp.bdaddr)) {
+ struct hostent *he = NULL;
+
+ if ((he = bt_gethostbyname(argv[0])) == NULL)
+ return (USAGE);
+
+ memcpy(&cmd.cp.bdaddr, he->h_addr, sizeof(cmd.cp.bdaddr));
+ }
+ break;
+
+ default:
+ cmd.cp.read_all = 1;
+ break;
+ }
+
+ if (hci_send(s, (char const *) &cmd, sizeof(cmd)) != OK)
+ return (ERROR);
+
+ /* Receive events */
+again:
+ memset(&event, 0, sizeof(event));
+ n = sizeof(event);
+ if (hci_recv(s, (char *) &event, &n) != OK)
+ return (ERROR);
+
+ if (n <= sizeof(event.hdr)) {
+ errno = EMSGSIZE;
+ return (ERROR);
+ }
+
+ if (event.hdr.type != NG_HCI_EVENT_PKT) {
+ errno = EIO;
+ return (ERROR);
+ }
+
+ /* Parse event */
+ switch (event.hdr.event) {
+ case NG_HCI_EVENT_COMMAND_COMPL: {
+ ng_hci_read_stored_link_key_rp *rp = NULL;
+
+ if (event.ep.cc.opcode == 0x0000 ||
+ event.ep.cc.opcode != cmd.hdr.opcode)
+ goto again;
+
+ rp = (ng_hci_read_stored_link_key_rp *)(event.ep.b +
+ sizeof(event.ep.cc));
+
+ fprintf(stdout, "Complete: Status: %s [%#x]\n",
+ hci_status2str(rp->status), rp->status);
+ fprintf(stdout, "Maximum Number of keys: %d\n",
+ le16toh(rp->max_num_keys));
+ fprintf(stdout, "Number of keys read: %d\n",
+ le16toh(rp->num_keys_read));
+ } break;
+
+ case NG_HCI_EVENT_RETURN_LINK_KEYS: {
+ struct _key {
+ bdaddr_t bdaddr;
+ uint8_t key[NG_HCI_KEY_SIZE];
+ } __attribute__ ((packed)) *k = NULL;
+
+ fprintf(stdout, "Event: Number of keys: %d\n",
+ event.ep.key.num_keys);
+
+ k = (struct _key *)(event.ep.b + sizeof(event.ep.key));
+ for (n = 0; n < event.ep.key.num_keys; n++) {
+ fprintf(stdout, "\t%d: %s ",
+ n + 1, hci_bdaddr2str(&k->bdaddr));
+
+ for (n1 = 0; n1 < sizeof(k->key); n1++)
+ fprintf(stdout, "%02x", k->key[n1]);
+ fprintf(stdout, "\n");
+
+ k ++;
+ }
+
+ goto again;
+
+ } break;
+
+ default:
+ goto again;
+ }
+
+ return (OK);
+} /* hci_read_store_link_key */
+
+/* Send Write_Stored_Link_Key command to the unit */
+static int
+hci_write_stored_link_key(int s, int argc, char **argv)
+{
+ struct {
+ ng_hci_write_stored_link_key_cp p;
+ bdaddr_t bdaddr;
+ uint8_t key[NG_HCI_KEY_SIZE];
+ } cp;
+ ng_hci_write_stored_link_key_rp rp;
+ int32_t n;
+
+ memset(&cp, 0, sizeof(cp));
+
+ switch (argc) {
+ case 2:
+ cp.p.num_keys_write = 1;
+
+ /* parse BD_ADDR */
+ if (!bt_aton(argv[0], &cp.bdaddr)) {
+ struct hostent *he = NULL;
+
+ if ((he = bt_gethostbyname(argv[0])) == NULL)
+ return (USAGE);
+
+ memcpy(&cp.bdaddr, he->h_addr, sizeof(cp.bdaddr));
+ }
+
+ /* parse key */
+ if (hci_hexstring2array(argv[1], cp.key, sizeof(cp.key)) < 0)
+ return (USAGE);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send command */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_WRITE_STORED_LINK_KEY),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "Number of keys written: %d\n", rp.num_keys_written);
+
+ return (OK);
+} /* hci_write_stored_link_key */
+
+
+/* Send Delete_Stored_Link_Key command to the unit */
+static int
+hci_delete_stored_link_key(int s, int argc, char **argv)
+{
+ ng_hci_delete_stored_link_key_cp cp;
+ ng_hci_delete_stored_link_key_rp rp;
+ int32_t n;
+
+ memset(&cp, 0, sizeof(cp));
+
+ switch (argc) {
+ case 1:
+ /* parse BD_ADDR */
+ if (!bt_aton(argv[0], &cp.bdaddr)) {
+ struct hostent *he = NULL;
+
+ if ((he = bt_gethostbyname(argv[0])) == NULL)
+ return (USAGE);
+
+ memcpy(&cp.bdaddr, he->h_addr, sizeof(cp.bdaddr));
+ }
+ break;
+
+ default:
+ cp.delete_all = 1;
+ break;
+ }
+
+ /* send command */
+ n = sizeof(cp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_DELETE_STORED_LINK_KEY),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "Number of keys deleted: %d\n", rp.num_keys_deleted);
+
+ return (OK);
+} /* hci_delete_stored_link_key */
+
+/* Send Change_Local_Name command to the unit */
+static int
+hci_change_local_name(int s, int argc, char **argv)
+{
+ ng_hci_change_local_name_cp cp;
+ ng_hci_change_local_name_rp rp;
+ int n;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 1:
+ snprintf(cp.name, sizeof(cp.name), "%s", argv[0]);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send command */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_CHANGE_LOCAL_NAME),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ return (OK);
+} /* hci_change_local_name */
+
+/* Send Read_Local_Name command to the unit */
+static int
+hci_read_local_name(int s, int argc, char **argv)
+{
+ ng_hci_read_local_name_rp rp;
+ int n;
+
+ n = sizeof(rp);
+ if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_READ_LOCAL_NAME),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "Local name: %s\n", rp.name);
+
+ return (OK);
+} /* hci_read_local_name */
+
+/* Send Read_Connection_Accept_Timeout to the unit */
+static int
+hci_read_connection_accept_timeout(int s, int argc, char **argv)
+{
+ ng_hci_read_con_accept_timo_rp rp;
+ int n;
+
+ n = sizeof(rp);
+ if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_READ_CON_ACCEPT_TIMO),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ rp.timeout = le16toh(rp.timeout);
+ fprintf(stdout, "Connection accept timeout: %.2f msec [%d slots]\n",
+ rp.timeout * 0.625, rp.timeout);
+
+ return (OK);
+} /* hci_read_connection_accept_timeout */
+
+/* Send Write_Connection_Accept_Timeout to the unit */
+static int
+hci_write_connection_accept_timeout(int s, int argc, char **argv)
+{
+ ng_hci_write_con_accept_timo_cp cp;
+ ng_hci_write_con_accept_timo_rp rp;
+ int n;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 1:
+ if (sscanf(argv[0], "%d", &n) != 1 || n < 1 || n > 0xb540)
+ return (USAGE);
+
+ cp.timeout = (uint16_t) n;
+ cp.timeout = htole16(cp.timeout);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send command */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_WRITE_CON_ACCEPT_TIMO),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ return (OK);
+} /* hci_write_connection_accept_timeout */
+
+/* Send Read_Page_Timeout command to the unit */
+static int
+hci_read_page_timeout(int s, int argc, char **argv)
+{
+ ng_hci_read_page_timo_rp rp;
+ int n;
+
+ n = sizeof(rp);
+ if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_READ_PAGE_TIMO),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ rp.timeout = le16toh(rp.timeout);
+ fprintf(stdout, "Page timeout: %.2f msec [%d slots]\n",
+ rp.timeout * 0.625, rp.timeout);
+
+ return (OK);
+} /* hci_read_page_timeoout */
+
+/* Send Write_Page_Timeout command to the unit */
+static int
+hci_write_page_timeout(int s, int argc, char **argv)
+{
+ ng_hci_write_page_timo_cp cp;
+ ng_hci_write_page_timo_rp rp;
+ int n;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 1:
+ if (sscanf(argv[0], "%d", &n) != 1 || n < 1 || n > 0xffff)
+ return (USAGE);
+
+ cp.timeout = (uint16_t) n;
+ cp.timeout = htole16(cp.timeout);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send command */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_WRITE_PAGE_TIMO),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ return (OK);
+} /* hci_write_page_timeout */
+
+/* Send Read_Scan_Enable command to the unit */
+static int
+hci_read_scan_enable(int s, int argc, char **argv)
+{
+ ng_hci_read_scan_enable_rp rp;
+ int n;
+
+ n = sizeof(rp);
+ if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_READ_SCAN_ENABLE),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "Scan enable: %s [%#02x]\n",
+ hci_scan2str(rp.scan_enable), rp.scan_enable);
+
+ return (OK);
+} /* hci_read_scan_enable */
+
+/* Send Write_Scan_Enable command to the unit */
+static int
+hci_write_scan_enable(int s, int argc, char **argv)
+{
+ ng_hci_write_scan_enable_cp cp;
+ ng_hci_write_scan_enable_rp rp;
+ int n;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 1:
+ if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 3)
+ return (USAGE);
+
+ cp.scan_enable = (uint8_t) n;
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_WRITE_SCAN_ENABLE),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ return (OK);
+} /* hci_write_scan_enable */
+
+/* Send Read_Page_Scan_Activity command to the unit */
+static int
+hci_read_page_scan_activity(int s, int argc, char **argv)
+{
+ ng_hci_read_page_scan_activity_rp rp;
+ int n;
+
+ n = sizeof(rp);
+ if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_READ_PAGE_SCAN_ACTIVITY),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ rp.page_scan_interval = le16toh(rp.page_scan_interval);
+ rp.page_scan_window = le16toh(rp.page_scan_window);
+
+ fprintf(stdout, "Page Scan Interval: %.2f msec [%d slots]\n",
+ rp.page_scan_interval * 0.625, rp.page_scan_interval);
+ fprintf(stdout, "Page Scan Window: %.2f msec [%d slots]\n",
+ rp.page_scan_window * 0.625, rp.page_scan_window);
+
+ return (OK);
+} /* hci_read_page_scan_activity */
+
+/* Send Write_Page_Scan_Activity command to the unit */
+static int
+hci_write_page_scan_activity(int s, int argc, char **argv)
+{
+ ng_hci_write_page_scan_activity_cp cp;
+ ng_hci_write_page_scan_activity_rp rp;
+ int n;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 2:
+ /* page scan interval */
+ if (sscanf(argv[0], "%d", &n) != 1 || n < 0x12 || n > 0x1000)
+ return (USAGE);
+
+ cp.page_scan_interval = (uint16_t) n;
+
+ /* page scan window */
+ if (sscanf(argv[1], "%d", &n) != 1 || n < 0x12 || n > 0x1000)
+ return (USAGE);
+
+ cp.page_scan_window = (uint16_t) n;
+
+ if (cp.page_scan_window > cp.page_scan_interval)
+ return (USAGE);
+
+ cp.page_scan_interval = htole16(cp.page_scan_interval);
+ cp.page_scan_window = htole16(cp.page_scan_window);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send command */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_WRITE_PAGE_SCAN_ACTIVITY),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ return (OK);
+} /* hci_write_page_scan_activity */
+
+/* Send Read_Inquiry_Scan_Activity command to the unit */
+static int
+hci_read_inquiry_scan_activity(int s, int argc, char **argv)
+{
+ ng_hci_read_inquiry_scan_activity_rp rp;
+ int n;
+
+ n = sizeof(rp);
+ if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_READ_INQUIRY_SCAN_ACTIVITY),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ rp.inquiry_scan_interval = le16toh(rp.inquiry_scan_interval);
+ rp.inquiry_scan_window = le16toh(rp.inquiry_scan_window);
+
+ fprintf(stdout, "Inquiry Scan Interval: %.2f msec [%d slots]\n",
+ rp.inquiry_scan_interval * 0.625, rp.inquiry_scan_interval);
+ fprintf(stdout, "Inquiry Scan Window: %.2f msec [%d slots]\n",
+ rp.inquiry_scan_window * 0.625, rp.inquiry_scan_interval);
+
+ return (OK);
+} /* hci_read_inquiry_scan_activity */
+
+/* Send Write_Inquiry_Scan_Activity command to the unit */
+static int
+hci_write_inquiry_scan_activity(int s, int argc, char **argv)
+{
+ ng_hci_write_inquiry_scan_activity_cp cp;
+ ng_hci_write_inquiry_scan_activity_rp rp;
+ int n;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 2:
+ /* inquiry scan interval */
+ if (sscanf(argv[0], "%d", &n) != 1 || n < 0x12 || n > 0x1000)
+ return (USAGE);
+
+ cp.inquiry_scan_interval = (uint16_t) n;
+
+ /* inquiry scan window */
+ if (sscanf(argv[1], "%d", &n) != 1 || n < 0x12 || n > 0x1000)
+ return (USAGE);
+
+ cp.inquiry_scan_window = (uint16_t) n;
+
+ if (cp.inquiry_scan_window > cp.inquiry_scan_interval)
+ return (USAGE);
+
+ cp.inquiry_scan_interval =
+ htole16(cp.inquiry_scan_interval);
+ cp.inquiry_scan_window = htole16(cp.inquiry_scan_window);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send command */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_WRITE_INQUIRY_SCAN_ACTIVITY),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ return (OK);
+} /* hci_write_inquiry_scan_activity */
+
+/* Send Read_Authentication_Enable command to the unit */
+static int
+hci_read_authentication_enable(int s, int argc, char **argv)
+{
+ ng_hci_read_auth_enable_rp rp;
+ int n;
+
+ n = sizeof(rp);
+ if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_READ_AUTH_ENABLE),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "Authentication Enable: %s [%d]\n",
+ rp.auth_enable? "Enabled" : "Disabled", rp.auth_enable);
+
+ return (OK);
+} /* hci_read_authentication_enable */
+
+/* Send Write_Authentication_Enable command to the unit */
+static int
+hci_write_authentication_enable(int s, int argc, char **argv)
+{
+ ng_hci_write_auth_enable_cp cp;
+ ng_hci_write_auth_enable_rp rp;
+ int n;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 1:
+ if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 1)
+ return (USAGE);
+
+ cp.auth_enable = (uint8_t) n;
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send command */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_WRITE_AUTH_ENABLE),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ return (OK);
+} /* hci_write_authentication_enable */
+
+/* Send Read_Encryption_Mode command to the unit */
+static int
+hci_read_encryption_mode(int s, int argc, char **argv)
+{
+ ng_hci_read_encryption_mode_rp rp;
+ int n;
+
+ n = sizeof(rp);
+ if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_READ_ENCRYPTION_MODE),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "Encryption mode: %s [%#02x]\n",
+ hci_encrypt2str(rp.encryption_mode, 0), rp.encryption_mode);
+
+ return (OK);
+} /* hci_read_encryption_mode */
+
+/* Send Write_Encryption_Mode command to the unit */
+static int
+hci_write_encryption_mode(int s, int argc, char **argv)
+{
+ ng_hci_write_encryption_mode_cp cp;
+ ng_hci_write_encryption_mode_rp rp;
+ int n;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 1:
+ if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 2)
+ return (USAGE);
+
+ cp.encryption_mode = (uint8_t) n;
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send command */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_WRITE_ENCRYPTION_MODE),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ return (OK);
+} /* hci_write_encryption_mode */
+
+/* Send Read_Class_Of_Device command to the unit */
+static int
+hci_read_class_of_device(int s, int argc, char **argv)
+{
+ ng_hci_read_unit_class_rp rp;
+ int n;
+
+ n = sizeof(rp);
+ if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_READ_UNIT_CLASS),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "Class: %02x:%02x:%02x\n",
+ rp.uclass[2], rp.uclass[1], rp.uclass[0]);
+
+ return (0);
+} /* hci_read_class_of_device */
+
+/* Send Write_Class_Of_Device command to the unit */
+static int
+hci_write_class_of_device(int s, int argc, char **argv)
+{
+ ng_hci_write_unit_class_cp cp;
+ ng_hci_write_unit_class_rp rp;
+ int n0, n1, n2;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 1:
+ if (sscanf(argv[0], "%x:%x:%x", &n2, &n1, &n0) != 3)
+ return (USAGE);
+
+ cp.uclass[0] = (n0 & 0xff);
+ cp.uclass[1] = (n1 & 0xff);
+ cp.uclass[2] = (n2 & 0xff);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send command */
+ n0 = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_WRITE_UNIT_CLASS),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n0) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ return (OK);
+} /* hci_write_class_of_device */
+
+/* Send Read_Voice_Settings command to the unit */
+static int
+hci_read_voice_settings(int s, int argc, char **argv)
+{
+ ng_hci_read_voice_settings_rp rp;
+ int n,
+ input_coding,
+ input_data_format,
+ input_sample_size;
+
+ n = sizeof(rp);
+ if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_READ_VOICE_SETTINGS),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ rp.settings = le16toh(rp.settings);
+
+ input_coding = (rp.settings & 0x0300) >> 8;
+ input_data_format = (rp.settings & 0x00c0) >> 6;
+ input_sample_size = (rp.settings & 0x0020) >> 5;
+
+ fprintf(stdout, "Voice settings: %#04x\n", rp.settings);
+ fprintf(stdout, "Input coding: %s [%d]\n",
+ hci_coding2str(input_coding), input_coding);
+ fprintf(stdout, "Input data format: %s [%d]\n",
+ hci_vdata2str(input_data_format), input_data_format);
+
+ if (input_coding == 0x00) /* Only for Linear PCM */
+ fprintf(stdout, "Input sample size: %d bit [%d]\n",
+ input_sample_size? 16 : 8, input_sample_size);
+
+ return (OK);
+} /* hci_read_voice_settings */
+
+/* Send Write_Voice_Settings command to the unit */
+static int
+hci_write_voice_settings(int s, int argc, char **argv)
+{
+ ng_hci_write_voice_settings_cp cp;
+ ng_hci_write_voice_settings_rp rp;
+ int n;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 1:
+ if (sscanf(argv[0], "%x", &n) != 1)
+ return (USAGE);
+
+ cp.settings = (uint16_t) n;
+ cp.settings = htole16(cp.settings);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send command */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_WRITE_VOICE_SETTINGS),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ return (OK);
+} /* hci_write_voice_settings */
+
+/* Send Read_Number_Broadcast_Restransmissions */
+static int
+hci_read_number_broadcast_retransmissions(int s, int argc, char **argv)
+{
+ ng_hci_read_num_broadcast_retrans_rp rp;
+ int n;
+
+ n = sizeof(rp);
+ if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_READ_NUM_BROADCAST_RETRANS),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "Number of broadcast retransmissions: %d\n",
+ rp.counter);
+
+ return (OK);
+} /* hci_read_number_broadcast_retransmissions */
+
+/* Send Write_Number_Broadcast_Restransmissions */
+static int
+hci_write_number_broadcast_retransmissions(int s, int argc, char **argv)
+{
+ ng_hci_write_num_broadcast_retrans_cp cp;
+ ng_hci_write_num_broadcast_retrans_rp rp;
+ int n;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 1:
+ if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 0xff)
+ return (USAGE);
+
+ cp.counter = (uint8_t) n;
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send command */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_WRITE_NUM_BROADCAST_RETRANS),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ return (OK);
+} /* hci_write_number_broadcast_retransmissions */
+
+/* Send Read_Hold_Mode_Activity command to the unit */
+static int
+hci_read_hold_mode_activity(int s, int argc, char **argv)
+{
+ ng_hci_read_hold_mode_activity_rp rp;
+ int n;
+ char buffer[1024];
+
+ n = sizeof(rp);
+ if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_READ_HOLD_MODE_ACTIVITY),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "Hold Mode Activities: %#02x\n", rp.hold_mode_activity);
+ if (rp.hold_mode_activity == 0)
+ fprintf(stdout, "Maintain current Power State");
+ else
+ fprintf(stdout, "%s", hci_hmode2str(rp.hold_mode_activity,
+ buffer, sizeof(buffer)));
+
+ fprintf(stdout, "\n");
+
+ return (OK);
+} /* hci_read_hold_mode_activity */
+
+/* Send Write_Hold_Mode_Activity command to the unit */
+static int
+hci_write_hold_mode_activity(int s, int argc, char **argv)
+{
+ ng_hci_write_hold_mode_activity_cp cp;
+ ng_hci_write_hold_mode_activity_rp rp;
+ int n;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 1:
+ if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 4)
+ return (USAGE);
+
+ cp.hold_mode_activity = (uint8_t) n;
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send command */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_WRITE_HOLD_MODE_ACTIVITY),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ return (OK);
+} /* hci_write_hold_mode_activity */
+
+/* Send Read_SCO_Flow_Control_Enable command to the unit */
+static int
+hci_read_sco_flow_control_enable(int s, int argc, char **argv)
+{
+ ng_hci_read_sco_flow_control_rp rp;
+ int n;
+
+ n = sizeof(rp);
+ if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_READ_SCO_FLOW_CONTROL),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "SCO flow control %s [%d]\n",
+ rp.flow_control? "enabled" : "disabled", rp.flow_control);
+
+ return (OK);
+} /* hci_read_sco_flow_control_enable */
+
+/* Send Write_SCO_Flow_Control_Enable command to the unit */
+static int
+hci_write_sco_flow_control_enable(int s, int argc, char **argv)
+{
+ ng_hci_write_sco_flow_control_cp cp;
+ ng_hci_write_sco_flow_control_rp rp;
+ int n;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 1:
+ if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 1)
+ return (USAGE);
+
+ cp.flow_control = (uint8_t) n;
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send command */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_WRITE_SCO_FLOW_CONTROL),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ return (OK);
+} /* hci_write_sco_flow_control_enable */
+
+/* Send Read_Link_Supervision_Timeout command to the unit */
+static int
+hci_read_link_supervision_timeout(int s, int argc, char **argv)
+{
+ ng_hci_read_link_supervision_timo_cp cp;
+ ng_hci_read_link_supervision_timo_rp rp;
+ int n;
+
+ switch (argc) {
+ case 1:
+ /* connection handle */
+ if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff)
+ return (USAGE);
+
+ cp.con_handle = (uint16_t) (n & 0x0fff);
+ cp.con_handle = htole16(cp.con_handle);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send command */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_READ_LINK_SUPERVISION_TIMO),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ rp.timeout = le16toh(rp.timeout);
+
+ fprintf(stdout, "Connection handle: %d\n", le16toh(rp.con_handle));
+ fprintf(stdout, "Link supervision timeout: %.2f msec [%d slots]\n",
+ rp.timeout * 0.625, rp.timeout);
+
+ return (OK);
+} /* hci_read_link_supervision_timeout */
+
+/* Send Write_Link_Supervision_Timeout command to the unit */
+static int
+hci_write_link_supervision_timeout(int s, int argc, char **argv)
+{
+ ng_hci_write_link_supervision_timo_cp cp;
+ ng_hci_write_link_supervision_timo_rp rp;
+ int n;
+
+ switch (argc) {
+ case 2:
+ /* connection handle */
+ if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff)
+ return (USAGE);
+
+ cp.con_handle = (uint16_t) (n & 0x0fff);
+ cp.con_handle = htole16(cp.con_handle);
+
+ /* link supervision timeout */
+ if (sscanf(argv[1], "%d", &n) != 1 || n < 0 || n > 0xffff)
+ return (USAGE);
+
+ cp.timeout = (uint16_t) (n & 0x0fff);
+ cp.timeout = htole16(cp.timeout);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send command */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_WRITE_LINK_SUPERVISION_TIMO),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ return (OK);
+} /* hci_write_link_supervision_timeout */
+
+/* Send Read_Page_Scan_Period_Mode command to the unit */
+static int
+hci_read_page_scan_period_mode(int s, int argc, char **argv)
+{
+ ng_hci_read_page_scan_period_rp rp;
+ int n;
+
+ n = sizeof(rp);
+ if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_READ_PAGE_SCAN_PERIOD),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "Page scan period mode: %#02x\n",
+ rp.page_scan_period_mode);
+
+ return (OK);
+} /* hci_read_page_scan_period_mode */
+
+/* Send Write_Page_Scan_Period_Mode command to the unit */
+static int
+hci_write_page_scan_period_mode(int s, int argc, char **argv)
+{
+ ng_hci_write_page_scan_period_cp cp;
+ ng_hci_write_page_scan_period_rp rp;
+ int n;
+
+ /* parse command arguments */
+ switch (argc) {
+ case 1:
+ if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 2)
+ return (USAGE);
+
+ cp.page_scan_period_mode = (n & 0xff);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send command */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_WRITE_PAGE_SCAN_PERIOD),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ return (OK);
+} /* hci_write_page_scan_period_mode */
+
+/* Send Read_Page_Scan_Mode command to the unit */
+static int
+hci_read_page_scan_mode(int s, int argc, char **argv)
+{
+ ng_hci_read_page_scan_rp rp;
+ int n;
+
+ n = sizeof(rp);
+ if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_READ_PAGE_SCAN),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "Page scan mode: %#02x\n", rp.page_scan_mode);
+
+ return (OK);
+} /* hci_read_page_scan_mode */
+
+/* Send Write_Page_Scan_Mode command to the unit */
+static int
+hci_write_page_scan_mode(int s, int argc, char **argv)
+{
+ ng_hci_write_page_scan_cp cp;
+ ng_hci_write_page_scan_rp rp;
+ int n;
+
+ /* parse command arguments */
+ switch (argc) {
+ case 1:
+ if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 3)
+ return (USAGE);
+
+ cp.page_scan_mode = (n & 0xff);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send command */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_WRITE_PAGE_SCAN),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ return (OK);
+} /* hci_write_page_scan_mode */
+
+static int
+hci_read_le_host_supported_command(int s, int argc, char **argv)
+{
+ ng_hci_read_le_host_supported_rp rp;
+ int n;
+ n = sizeof(rp);
+ if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_READ_LE_HOST_SUPPORTED),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "LE Host support: %#02x\n", rp.le_supported_host);
+ fprintf(stdout, "Simulateneouse LE Host : %#02x\n", rp.simultaneous_le_host);
+
+ return (OK);
+
+}
+static int
+hci_write_le_host_supported_command(int s, int argc, char **argv)
+{
+ ng_hci_write_le_host_supported_cp cp;
+ ng_hci_write_le_host_supported_rp rp;
+
+ int n;
+
+ cp.le_supported_host = 0;
+ cp.simultaneous_le_host = 0;
+ switch (argc) {
+ case 2:
+ if (sscanf(argv[1], "%d", &n) != 1 || (n != 0 && n != 1)){
+ printf("ARGC2: %d\n", n);
+ return (USAGE);
+ }
+ cp.simultaneous_le_host = (n &1);
+
+ case 1:
+ if (sscanf(argv[0], "%d", &n) != 1 || (n != 0 && n != 1)){
+ printf("ARGC1: %d\n", n);
+ return (USAGE);
+ }
+
+ cp.le_supported_host = (n &1);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+
+ /* send command */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_WRITE_LE_HOST_SUPPORTED),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ return (OK);
+}
+
+struct hci_command host_controller_baseband_commands[] = {
+{
+"reset",
+"\nThe Reset command will reset the Host Controller and the Link Manager.\n" \
+"After the reset is completed, the current operational state will be lost,\n" \
+"the Bluetooth unit will enter standby mode and the Host Controller will\n" \
+"automatically revert to the default values for the parameters for which\n" \
+"default values are defined in the specification.",
+&hci_reset
+},
+{
+"read_pin_type",
+"\nThe Read_PIN_Type command is used for the Host to read whether the Link\n" \
+"Manager assumes that the Host supports variable PIN codes only a fixed PIN\n" \
+"code.",
+&hci_read_pin_type
+},
+{
+"write_pin_type <pin_type>",
+"\nThe Write_PIN_Type command is used for the Host to write to the Host\n" \
+"Controller whether the Host supports variable PIN codes or only a fixed PIN\n"\
+"code.\n\n" \
+"\t<pin_type> - dd; 0 - Variable; 1 - Fixed",
+&hci_write_pin_type
+},
+{
+"read_stored_link_key [<BD_ADDR>]",
+"\nThe Read_Stored_Link_Key command provides the ability to read one or\n" \
+"more link keys stored in the Bluetooth Host Controller. The Bluetooth Host\n" \
+"Controller can store a limited number of link keys for other Bluetooth\n" \
+"devices.\n\n" \
+"\t<BD_ADDR> - xx:xx:xx:xx:xx:xx BD_ADDR or name",
+&hci_read_stored_link_key
+},
+{
+"write_stored_link_key <BD_ADDR> <key>",
+"\nThe Write_Stored_Link_Key command provides the ability to write one\n" \
+"or more link keys to be stored in the Bluetooth Host Controller. The\n" \
+"Bluetooth Host Controller can store a limited number of link keys for other\n"\
+"Bluetooth devices. If no additional space is available in the Bluetooth\n"\
+"Host Controller then no additional link keys will be stored.\n\n" \
+"\t<BD_ADDR> - xx:xx:xx:xx:xx:xx BD_ADDR or name\n" \
+"\t<key> - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx up to 16 bytes link key",
+&hci_write_stored_link_key
+},
+{
+"delete_stored_link_key [<BD_ADDR>]",
+"\nThe Delete_Stored_Link_Key command provides the ability to remove one\n" \
+"or more of the link keys stored in the Bluetooth Host Controller. The\n" \
+"Bluetooth Host Controller can store a limited number of link keys for other\n"\
+"Bluetooth devices.\n\n" \
+"\t<BD_ADDR> - xx:xx:xx:xx:xx:xx BD_ADDR or name",
+&hci_delete_stored_link_key
+},
+{
+"change_local_name <name>",
+"\nThe Change_Local_Name command provides the ability to modify the user\n" \
+"friendly name for the Bluetooth unit.\n\n" \
+"\t<name> - string",
+&hci_change_local_name
+},
+{
+"read_local_name",
+"\nThe Read_Local_Name command provides the ability to read the\n" \
+"stored user-friendly name for the Bluetooth unit.",
+&hci_read_local_name
+},
+{
+"read_connection_accept_timeout",
+"\nThis command will read the value for the Connection_Accept_Timeout\n" \
+"configuration parameter. The Connection_Accept_Timeout configuration\n" \
+"parameter allows the Bluetooth hardware to automatically deny a\n" \
+"connection request after a specified time period has occurred and\n" \
+"the new connection is not accepted. Connection Accept Timeout\n" \
+"measured in Number of Baseband slots.",
+&hci_read_connection_accept_timeout
+},
+{
+"write_connection_accept_timeout <timeout>",
+"\nThis command will write the value for the Connection_Accept_Timeout\n" \
+"configuration parameter.\n\n" \
+"\t<timeout> - dddd; measured in number of baseband slots.",
+&hci_write_connection_accept_timeout
+},
+{
+"read_page_timeout",
+"\nThis command will read the value for the Page_Timeout configuration\n" \
+"parameter. The Page_Timeout configuration parameter defines the\n" \
+"maximum time the local Link Manager will wait for a baseband page\n" \
+"response from the remote unit at a locally initiated connection\n" \
+"attempt. Page Timeout measured in Number of Baseband slots.",
+&hci_read_page_timeout
+},
+{
+"write_page_timeout <timeout>",
+"\nThis command will write the value for the Page_Timeout configuration\n" \
+"parameter.\n\n" \
+"\t<timeout> - dddd; measured in number of baseband slots.",
+&hci_write_page_timeout
+},
+{
+"read_scan_enable",
+"\nThis command will read the value for the Scan_Enable parameter. The\n" \
+"Scan_Enable parameter controls whether or not the Bluetooth uint\n" \
+"will periodically scan for page attempts and/or inquiry requests\n" \
+"from other Bluetooth unit.\n\n" \
+"\t0x00 - No Scans enabled.\n" \
+"\t0x01 - Inquiry Scan enabled. Page Scan disabled.\n" \
+"\t0x02 - Inquiry Scan disabled. Page Scan enabled.\n" \
+"\t0x03 - Inquiry Scan enabled. Page Scan enabled.",
+&hci_read_scan_enable
+},
+{
+"write_scan_enable <scan_enable>",
+"\nThis command will write the value for the Scan_Enable parameter.\n" \
+"The Scan_Enable parameter controls whether or not the Bluetooth\n" \
+"unit will periodically scan for page attempts and/or inquiry\n" \
+"requests from other Bluetooth unit.\n\n" \
+"\t<scan_enable> - dd;\n" \
+"\t0 - No Scans enabled.\n" \
+"\t1 - Inquiry Scan enabled. Page Scan disabled.\n" \
+"\t2 - Inquiry Scan disabled. Page Scan enabled.\n" \
+"\t3 - Inquiry Scan enabled. Page Scan enabled.",
+&hci_write_scan_enable
+},
+{
+"read_page_scan_activity",
+"\nThis command will read the value for Page_Scan_Activity configuration\n" \
+"parameters. The Page_Scan_Interval configuration parameter defines the\n" \
+"amount of time between consecutive page scans. This time interval is \n" \
+"defined from when the Host Controller started its last page scan until\n" \
+"it begins the next page scan. The Page_Scan_Window configuration parameter\n" \
+"defines the amount of time for the duration of the page scan. The\n" \
+"Page_Scan_Window can only be less than or equal to the Page_Scan_Interval.",
+&hci_read_page_scan_activity
+},
+{
+"write_page_scan_activity interval(dddd) window(dddd)",
+"\nThis command will write the value for Page_Scan_Activity configuration\n" \
+"parameter. The Page_Scan_Interval configuration parameter defines the\n" \
+"amount of time between consecutive page scans. This is defined as the time\n" \
+"interval from when the Host Controller started its last page scan until it\n" \
+"begins the next page scan. The Page_Scan_Window configuration parameter\n" \
+"defines the amount of time for the duration of the page scan. \n" \
+"The Page_Scan_Window can only be less than or equal to the Page_Scan_Interval.\n\n" \
+"\t<interval> - Range: 0x0012 -- 0x100, Time = N * 0.625 msec\n" \
+"\t<window> - Range: 0x0012 -- 0x100, Time = N * 0.625 msec",
+&hci_write_page_scan_activity
+},
+{
+"read_inquiry_scan_activity",
+"\nThis command will read the value for Inquiry_Scan_Activity configuration\n" \
+"parameter. The Inquiry_Scan_Interval configuration parameter defines the\n" \
+"amount of time between consecutive inquiry scans. This is defined as the\n" \
+"time interval from when the Host Controller started its last inquiry scan\n" \
+"until it begins the next inquiry scan.",
+&hci_read_inquiry_scan_activity
+},
+{
+"write_inquiry_scan_activity interval(dddd) window(dddd)",
+"\nThis command will write the value for Inquiry_Scan_Activity configuration\n"\
+"parameter. The Inquiry_Scan_Interval configuration parameter defines the\n" \
+"amount of time between consecutive inquiry scans. This is defined as the\n" \
+"time interval from when the Host Controller started its last inquiry scan\n" \
+"until it begins the next inquiry scan. The Inquiry_Scan_Window configuration\n" \
+"parameter defines the amount of time for the duration of the inquiry scan.\n" \
+"The Inquiry_Scan_Window can only be less than or equal to the Inquiry_Scan_Interval.\n\n" \
+"\t<interval> - Range: 0x0012 -- 0x100, Time = N * 0.625 msec\n" \
+"\t<window> - Range: 0x0012 -- 0x100, Time = N * 0.625 msec",
+&hci_write_inquiry_scan_activity
+},
+{
+"read_authentication_enable",
+"\nThis command will read the value for the Authentication_Enable parameter.\n"\
+"The Authentication_Enable parameter controls if the local unit requires\n"\
+"to authenticate the remote unit at connection setup (between the\n" \
+"Create_Connection command or acceptance of an incoming ACL connection\n"\
+"and the corresponding Connection Complete event). At connection setup, only\n"\
+"the unit(s) with the Authentication_Enable parameter enabled will try to\n"\
+"authenticate the other unit.",
+&hci_read_authentication_enable
+},
+{
+"write_authentication_enable enable(0|1)",
+"\nThis command will write the value for the Authentication_Enable parameter.\n"\
+"The Authentication_Enable parameter controls if the local unit requires to\n"\
+"authenticate the remote unit at connection setup (between the\n" \
+"Create_Connection command or acceptance of an incoming ACL connection\n" \
+"and the corresponding Connection Complete event). At connection setup, only\n"\
+"the unit(s) with the Authentication_Enable parameter enabled will try to\n"\
+"authenticate the other unit.",
+&hci_write_authentication_enable
+},
+{
+"read_encryption_mode",
+"\nThis command will read the value for the Encryption_Mode parameter. The\n" \
+"Encryption_Mode parameter controls if the local unit requires encryption\n" \
+"to the remote unit at connection setup (between the Create_Connection\n" \
+"command or acceptance of an incoming ACL connection and the corresponding\n" \
+"Connection Complete event). At connection setup, only the unit(s) with\n" \
+"the Authentication_Enable parameter enabled and Encryption_Mode parameter\n" \
+"enabled will try to encrypt the connection to the other unit.\n\n" \
+"\t<encryption_mode>:\n" \
+"\t0x00 - Encryption disabled.\n" \
+"\t0x01 - Encryption only for point-to-point packets.\n" \
+"\t0x02 - Encryption for both point-to-point and broadcast packets.",
+&hci_read_encryption_mode
+},
+{
+"write_encryption_mode mode(0|1|2)",
+"\tThis command will write the value for the Encryption_Mode parameter.\n" \
+"The Encryption_Mode parameter controls if the local unit requires\n" \
+"encryption to the remote unit at connection setup (between the\n" \
+"Create_Connection command or acceptance of an incoming ACL connection\n" \
+"and the corresponding Connection Complete event). At connection setup,\n" \
+"only the unit(s) with the Authentication_Enable parameter enabled and\n" \
+"Encryption_Mode parameter enabled will try to encrypt the connection to\n" \
+"the other unit.\n\n" \
+"\t<encryption_mode> (dd)\n" \
+"\t0 - Encryption disabled.\n" \
+"\t1 - Encryption only for point-to-point packets.\n" \
+"\t2 - Encryption for both point-to-point and broadcast packets.",
+&hci_write_encryption_mode
+},
+{
+"read_class_of_device",
+"\nThis command will read the value for the Class_of_Device parameter.\n" \
+"The Class_of_Device parameter is used to indicate the capabilities of\n" \
+"the local unit to other units.",
+&hci_read_class_of_device
+},
+{
+"write_class_of_device class(xx:xx:xx)",
+"\nThis command will write the value for the Class_of_Device parameter.\n" \
+"The Class_of_Device parameter is used to indicate the capabilities of \n" \
+"the local unit to other units.\n\n" \
+"\t<class> (xx:xx:xx) - class of device",
+&hci_write_class_of_device
+},
+{
+"read_voice_settings",
+"\nThis command will read the values for the Voice_Setting parameter.\n" \
+"The Voice_Setting parameter controls all the various settings for voice\n" \
+"connections. These settings apply to all voice connections, and cannot be\n" \
+"set for individual voice connections. The Voice_Setting parameter controls\n" \
+"the configuration for voice connections: Input Coding, Air coding format,\n" \
+"input data format, Input sample size, and linear PCM parameter.",
+&hci_read_voice_settings
+},
+{
+"write_voice_settings settings(xxxx)",
+"\nThis command will write the values for the Voice_Setting parameter.\n" \
+"The Voice_Setting parameter controls all the various settings for voice\n" \
+"connections. These settings apply to all voice connections, and cannot be\n" \
+"set for individual voice connections. The Voice_Setting parameter controls\n" \
+"the configuration for voice connections: Input Coding, Air coding format,\n" \
+"input data format, Input sample size, and linear PCM parameter.\n\n" \
+"\t<voice_settings> (xxxx) - voice settings",
+&hci_write_voice_settings
+},
+{
+"read_number_broadcast_retransmissions",
+"\nThis command will read the unit's parameter value for the Number of\n" \
+"Broadcast Retransmissions. Broadcast packets are not acknowledged and are\n" \
+"unreliable.",
+&hci_read_number_broadcast_retransmissions
+},
+{
+"write_number_broadcast_retransmissions count(dd)",
+"\nThis command will write the unit's parameter value for the Number of\n" \
+"Broadcast Retransmissions. Broadcast packets are not acknowledged and are\n" \
+"unreliable.\n\n" \
+"\t<count> (dd) - number of broadcast retransimissions",
+&hci_write_number_broadcast_retransmissions
+},
+{
+"read_hold_mode_activity",
+"\nThis command will read the value for the Hold_Mode_Activity parameter.\n" \
+"The Hold_Mode_Activity value is used to determine what activities should\n" \
+"be suspended when the unit is in hold mode.",
+&hci_read_hold_mode_activity
+},
+{
+"write_hold_mode_activity settings(0|1|2|4)",
+"\nThis command will write the value for the Hold_Mode_Activity parameter.\n" \
+"The Hold_Mode_Activity value is used to determine what activities should\n" \
+"be suspended when the unit is in hold mode.\n\n" \
+"\t<settings> (dd) - bit mask:\n" \
+"\t0 - Maintain current Power State. Default\n" \
+"\t1 - Suspend Page Scan.\n" \
+"\t2 - Suspend Inquiry Scan.\n" \
+"\t4 - Suspend Periodic Inquiries.",
+&hci_write_hold_mode_activity
+},
+{
+"read_sco_flow_control_enable",
+"\nThe Read_SCO_Flow_Control_Enable command provides the ability to read\n" \
+"the SCO_Flow_Control_Enable setting. By using this setting, the Host can\n" \
+"decide if the Host Controller will send Number Of Completed Packets events\n" \
+"for SCO Connection Handles. This setting allows the Host to enable and\n" \
+"disable SCO flow control.",
+&hci_read_sco_flow_control_enable
+},
+{
+"write_sco_flow_control_enable enable(0|1)",
+"\nThe Write_SCO_Flow_Control_Enable command provides the ability to write\n" \
+"the SCO_Flow_Control_Enable setting. By using this setting, the Host can\n" \
+"decide if the Host Controller will send Number Of Completed Packets events\n" \
+"for SCO Connection Handles. This setting allows the Host to enable and\n" \
+"disable SCO flow control. The SCO_Flow_Control_Enable setting can only be\n" \
+"changed if no connections exist.",
+&hci_write_sco_flow_control_enable
+},
+{
+"read_link_supervision_timeout <connection_handle>",
+"\nThis command will read the value for the Link_Supervision_Timeout\n" \
+"parameter for the device. The Link_Supervision_Timeout parameter is used\n" \
+"by the master or slave Bluetooth device to monitor link loss. If, for any\n" \
+"reason, no Baseband packets are received from that Connection Handle for a\n" \
+"duration longer than the Link_Supervision_Timeout, the connection is\n"
+"disconnected.\n\n" \
+"\t<connection_handle> - dddd; connection handle\n",
+&hci_read_link_supervision_timeout
+},
+{
+"write_link_supervision_timeout <connection_handle> <timeout>",
+"\nThis command will write the value for the Link_Supervision_Timeout\n" \
+"parameter for the device. The Link_Supervision_Timeout parameter is used\n" \
+"by the master or slave Bluetooth device to monitor link loss. If, for any\n" \
+"reason, no Baseband packets are received from that connection handle for a\n" \
+"duration longer than the Link_Supervision_Timeout, the connection is\n" \
+"disconnected.\n\n" \
+"\t<connection_handle> - dddd; connection handle\n" \
+"\t<timeout> - dddd; timeout measured in number of baseband slots\n",
+&hci_write_link_supervision_timeout
+},
+{
+"read_page_scan_period_mode",
+"\nThis command is used to read the mandatory Page_Scan_Period_Mode of the\n" \
+"local Bluetooth device. Every time an inquiry response message is sent, the\n"\
+"Bluetooth device will start a timer (T_mandatory_pscan), the value of which\n"\
+"is dependent on the Page_Scan_Period_Mode. As long as this timer has not\n" \
+"expired, the Bluetooth device will use the Page_Scan_Period_Mode for all\n" \
+"following page scans.",
+&hci_read_page_scan_period_mode
+},
+{
+"write_page_scan_period_mode <page_scan_period_mode>",
+"\nThis command is used to write the mandatory Page_Scan_Period_Mode of the\n" \
+"local Bluetooth device. Every time an inquiry response message is sent, the\n"\
+"Bluetooth device will start a timer (T_mandatory_pscan), the value of which\n"\
+"is dependent on the Page_Scan_Period_Mode. As long as this timer has not\n" \
+"expired, the Bluetooth device will use the Page_Scan_Period_Mode for all\n" \
+"following page scans.\n\n" \
+"\t<page_scan_period_mode> - dd; page scan period mode:\n" \
+"\t0x00 - P0 (Default)\n" \
+"\t0x01 - P1\n" \
+"\t0x02 - P2",
+&hci_write_page_scan_period_mode
+},
+{
+"read_page_scan_mode",
+"\nThis command is used to read the default page scan mode of the local\n" \
+"Bluetooth device. The Page_Scan_Mode parameter indicates the page scan mode\n"\
+"that is used for the default page scan. Currently one mandatory page scan\n"\
+"mode and three optional page scan modes are defined. Following an inquiry\n" \
+"response, if the Baseband timer T_mandatory_pscan has not expired, the\n" \
+"mandatory page scan mode must be applied.",
+&hci_read_page_scan_mode
+},
+{
+"write_page_scan_mode <page_scan_mode>",
+"\nThis command is used to write the default page scan mode of the local\n" \
+"Bluetooth device. The Page_Scan_Mode parameter indicates the page scan mode\n"\
+"that is used for the default page scan. Currently, one mandatory page scan\n"\
+"mode and three optional page scan modes are defined. Following an inquiry\n"\
+"response, if the Baseband timer T_mandatory_pscan has not expired, the\n" \
+"mandatory page scan mode must be applied.\n\n" \
+"\t<page_scan_mode> - dd; page scan mode:\n" \
+"\t0x00 - Mandatory Page Scan Mode (Default)\n" \
+"\t0x01 - Optional Page Scan Mode I\n" \
+"\t0x02 - Optional Page Scan Mode II\n" \
+"\t0x03 - Optional Page Scan Mode III",
+&hci_write_page_scan_mode
+},
+{
+"read_le_host_supported_command", \
+"Read if this host is in le supported mode and stimulatenouse le supported mode",
+&hci_read_le_host_supported_command,
+},
+{
+"write_le_host_supported_command", \
+"write_le_host_supported_command le_host[0|1] stimultajeous_le[0|1]",
+&hci_write_le_host_supported_command,
+},
+
+{ NULL, }
+};
+
diff --git a/usr.sbin/bluetooth/hccontrol/info.c b/usr.sbin/bluetooth/hccontrol/info.c
new file mode 100644
index 0000000..ee9d1a1
--- /dev/null
+++ b/usr.sbin/bluetooth/hccontrol/info.c
@@ -0,0 +1,217 @@
+/*
+ * info.c
+ *
+ * Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: info.c,v 1.3 2003/08/18 19:19:54 max Exp $
+ * $FreeBSD$
+ */
+
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include "hccontrol.h"
+
+/* Send Read_Local_Version_Information command to the unit */
+static int
+hci_read_local_version_information(int s, int argc, char **argv)
+{
+ ng_hci_read_local_ver_rp rp;
+ int n;
+
+ n = sizeof(rp);
+ if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_INFO,
+ NG_HCI_OCF_READ_LOCAL_VER), (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ rp.manufacturer = le16toh(rp.manufacturer);
+
+ fprintf(stdout, "HCI version: %s [%#02x]\n",
+ hci_ver2str(rp.hci_version), rp.hci_version);
+ fprintf(stdout, "HCI revision: %#04x\n",
+ le16toh(rp.hci_revision));
+ fprintf(stdout, "LMP version: %s [%#02x]\n",
+ hci_lmpver2str(rp.lmp_version), rp.lmp_version);
+ fprintf(stdout, "LMP sub-version: %#04x\n",
+ le16toh(rp.lmp_subversion));
+ fprintf(stdout, "Manufacturer: %s [%#04x]\n",
+ hci_manufacturer2str(rp.manufacturer), rp.manufacturer);
+
+ return (OK);
+} /* hci_read_local_version_information */
+
+/* Send Read_Local_Supported_Features command to the unit */
+static int
+hci_read_local_supported_features(int s, int argc, char **argv)
+{
+ ng_hci_read_local_features_rp rp;
+ int n;
+ char buffer[1024];
+
+ n = sizeof(rp);
+ if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_INFO,
+ NG_HCI_OCF_READ_LOCAL_FEATURES),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "Features: ");
+ for (n = 0; n < sizeof(rp.features); n++)
+ fprintf(stdout, "%#02x ", rp.features[n]);
+ fprintf(stdout, "\n%s\n", hci_features2str(rp.features,
+ buffer, sizeof(buffer)));
+
+ return (OK);
+} /* hci_read_local_supported_features */
+
+/* Sent Read_Buffer_Size command to the unit */
+static int
+hci_read_buffer_size(int s, int argc, char **argv)
+{
+ ng_hci_read_buffer_size_rp rp;
+ int n;
+
+ n = sizeof(rp);
+ if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_INFO,
+ NG_HCI_OCF_READ_BUFFER_SIZE),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "Max. ACL packet size: %d bytes\n",
+ le16toh(rp.max_acl_size));
+ fprintf(stdout, "Number of ACL packets: %d\n",
+ le16toh(rp.num_acl_pkt));
+ fprintf(stdout, "Max. SCO packet size: %d bytes\n",
+ rp.max_sco_size);
+ fprintf(stdout, "Number of SCO packets: %d\n",
+ le16toh(rp.num_sco_pkt));
+
+ return (OK);
+} /* hci_read_buffer_size */
+
+/* Send Read_Country_Code command to the unit */
+static int
+hci_read_country_code(int s, int argc, char **argv)
+{
+ ng_hci_read_country_code_rp rp;
+ int n;
+
+ n = sizeof(rp);
+ if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_INFO,
+ NG_HCI_OCF_READ_COUNTRY_CODE),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "Country code: %s [%#02x]\n",
+ hci_cc2str(rp.country_code), rp.country_code);
+
+ return (OK);
+} /* hci_read_country_code */
+
+/* Send Read_BD_ADDR command to the unit */
+static int
+hci_read_bd_addr(int s, int argc, char **argv)
+{
+ ng_hci_read_bdaddr_rp rp;
+ int n;
+
+ n = sizeof(rp);
+ if (hci_simple_request(s, NG_HCI_OPCODE(NG_HCI_OGF_INFO,
+ NG_HCI_OCF_READ_BDADDR), (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "BD_ADDR: %s\n", bt_ntoa(&rp.bdaddr, NULL));
+
+ return (OK);
+} /* hci_read_bd_addr */
+
+struct hci_command info_commands[] = {
+{
+"read_local_version_information",
+"\nThis command will read the values for the version information for the\n" \
+"local Bluetooth unit.",
+&hci_read_local_version_information
+},
+{
+"read_local_supported_features",
+"\nThis command requests a list of the supported features for the local\n" \
+"unit. This command will return a list of the LMP features.",
+&hci_read_local_supported_features
+},
+{
+"read_buffer_size",
+"\nThe Read_Buffer_Size command is used to read the maximum size of the\n" \
+"data portion of HCI ACL and SCO Data Packets sent from the Host to the\n" \
+"Host Controller.",
+&hci_read_buffer_size
+},
+{
+"read_country_code",
+"\nThis command will read the value for the Country_Code return parameter.\n" \
+"The Country_Code defines which range of frequency band of the ISM 2.4 GHz\n" \
+"band will be used by the unit.",
+&hci_read_country_code
+},
+{
+"read_bd_addr",
+"\nThis command will read the value for the BD_ADDR parameter. The BD_ADDR\n" \
+"is a 48-bit unique identifier for a Bluetooth unit.",
+&hci_read_bd_addr
+},
+{
+NULL,
+}};
+
diff --git a/usr.sbin/bluetooth/hccontrol/le.c b/usr.sbin/bluetooth/hccontrol/le.c
new file mode 100644
index 0000000..c7be20d
--- /dev/null
+++ b/usr.sbin/bluetooth/hccontrol/le.c
@@ -0,0 +1,356 @@
+/*
+ * le.c
+ *
+ * Copyright (c) 2015 Takanori Watanabe <takawata@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.
+ *
+ * $Id: hccontrol.c,v 1.5 2003/09/05 00:38:24 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/sysctl.h>
+#include <sys/bitstring.h>
+#include <sys/select.h>
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <netgraph/ng_message.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdint.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include "hccontrol.h"
+
+static int le_set_scan_param(int s, int argc, char *argv[]);
+static int le_set_scan_enable(int s, int argc, char *argv[]);
+static int parse_param(int argc, char *argv[], char *buf, int *len);
+static int le_set_scan_response(int s, int argc, char *argv[]);
+static int le_read_supported_status(int s, int argc, char *argv[]);
+static int le_read_local_supported_features(int s, int argc ,char *argv[]);
+static int set_le_event_mask(int s, uint64_t mask);
+static int set_event_mask(int s, uint64_t mask);
+static int le_enable(int s, int argc, char *argv[]);
+
+static int
+le_set_scan_param(int s, int argc, char *argv[])
+{
+ int type;
+ int interval;
+ int window;
+ int adrtype;
+ int policy;
+ int e, n;
+
+ ng_hci_le_set_scan_parameters_cp cp;
+ ng_hci_le_set_scan_parameters_rp rp;
+
+ if (argc != 5)
+ return USAGE;
+
+ if (strcmp(argv[0], "active") == 0)
+ type = 1;
+ else if (strcmp(argv[0], "passive") == 0)
+ type = 0;
+ else
+ return USAGE;
+
+ interval = (int)(atof(argv[1])/0.625);
+ interval = (interval < 4)? 4: interval;
+ window = (int)(atof(argv[2])/0.625);
+ window = (window < 4) ? 4 : interval;
+
+ if (strcmp(argv[3], "public") == 0)
+ adrtype = 0;
+ else if (strcmp(argv[3], "random") == 0)
+ adrtype = 1;
+ else
+ return USAGE;
+
+ if (strcmp(argv[4], "all") == 0)
+ policy = 0;
+ else if (strcmp(argv[4], "whitelist") == 0)
+ policy = 1;
+ else
+ return USAGE;
+
+ cp.le_scan_type = type;
+ cp.le_scan_interval = interval;
+ cp.own_address_type = adrtype;
+ cp.le_scan_window = window;
+ cp.scanning_filter_policy = policy;
+ n = sizeof(rp);
+ e = hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE,
+ NG_HCI_OCF_LE_SET_SCAN_PARAMETERS),
+ (void *)&cp, sizeof(cp), (void *)&rp, &n);
+
+ return 0;
+}
+
+static int
+le_set_scan_enable(int s, int argc, char *argv[])
+{
+ ng_hci_le_set_scan_enable_cp cp;
+ ng_hci_le_set_scan_enable_rp rp;
+ int e, n, enable = 0;
+
+ if (argc != 1)
+ return USAGE;
+
+ if (strcmp(argv[0], "enable") == 0)
+ enable = 1;
+ else if (strcmp(argv[0], "disable") != 0)
+ return USAGE;
+
+ n = sizeof(rp);
+ cp.le_scan_enable = enable;
+ cp.filter_duplicates = 0;
+ e = hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE,
+ NG_HCI_OCF_LE_SET_SCAN_ENABLE),
+ (void *)&cp, sizeof(cp), (void *)&rp, &n);
+
+ if (e != 0 || rp.status != 0)
+ return ERROR;
+
+ return OK;
+}
+
+static int
+parse_param(int argc, char *argv[], char *buf, int *len)
+{
+ char *buflast = buf + (*len);
+ char *curbuf = buf;
+ char *token,*lenpos;
+ int ch;
+ int datalen;
+ uint16_t value;
+ optreset = 1;
+ optind = 0;
+ while ((ch = getopt(argc, argv , "n:f:u:")) != -1) {
+ switch(ch){
+ case 'n':
+ datalen = strlen(optarg);
+ if ((curbuf + datalen + 2) >= buflast)
+ goto done;
+ curbuf[0] = datalen + 1;
+ curbuf[1] = 8;
+ curbuf += 2;
+ memcpy(curbuf, optarg, datalen);
+ curbuf += datalen;
+ break;
+ case 'f':
+ if (curbuf+3 > buflast)
+ goto done;
+ curbuf[0] = 2;
+ curbuf[1] = 1;
+ curbuf[2] = atoi(optarg);
+ curbuf += 3;
+ break;
+ case 'u':
+ lenpos = buf;
+ if ((buf+2) >= buflast)
+ goto done;
+ curbuf[1] = 2;
+ *lenpos = 1;
+ curbuf += 2;
+ while ((token = strsep(&optarg, ",")) != NULL) {
+ value = strtol(token, NULL, 16);
+ if ((curbuf+2) >= buflast)
+ break;
+ curbuf[0] = value &0xff;
+ curbuf[1] = (value>>8)&0xff;
+ curbuf += 2;
+ }
+
+ }
+ }
+done:
+ *len = curbuf - buf;
+
+ return OK;
+}
+
+static int
+le_set_scan_response(int s, int argc, char *argv[])
+{
+ ng_hci_le_set_scan_response_data_cp cp;
+ ng_hci_le_set_scan_response_data_rp rp;
+ int n;
+ int e;
+ int len;
+ char buf[NG_HCI_ADVERTISING_DATA_SIZE];
+
+ len = sizeof(buf);
+ parse_param(argc, argv, buf, &len);
+ memset(cp.scan_response_data, 0, sizeof(cp.scan_response_data));
+ cp.scan_response_data_length = len;
+ memcpy(cp.scan_response_data, buf, len);
+ n = sizeof(rp);
+ e = hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE,
+ NG_HCI_OCF_LE_SET_SCAN_RESPONSE_DATA),
+ (void *)&cp, sizeof(cp), (void *)&rp, &n);
+
+ printf("SET SCAN RESPONSE %d %d %d\n", e, rp.status, n);
+
+ return OK;
+}
+
+static int
+le_read_local_supported_features(int s, int argc ,char *argv[])
+{
+ ng_hci_le_read_local_supported_features_rp rp;
+ int e;
+ int n = sizeof(rp);
+
+ e = hci_simple_request(s,
+ NG_HCI_OPCODE(NG_HCI_OGF_LE,
+ NG_HCI_OCF_LE_READ_LOCAL_SUPPORTED_FEATURES),
+ (void *)&rp, &n);
+
+ printf("LOCAL SUPPORTED: %d %d %jx\n", e, rp.status,
+ (uintmax_t) rp.le_features);
+
+ return 0;
+}
+
+static int
+le_read_supported_status(int s, int argc, char *argv[])
+{
+ ng_hci_le_read_supported_status_rp rp;
+ int e;
+ int n = sizeof(rp);
+
+ e = hci_simple_request(s, NG_HCI_OPCODE(
+ NG_HCI_OGF_LE,
+ NG_HCI_OCF_LE_READ_SUPPORTED_STATUS),
+ (void *)&rp, &n);
+
+ printf("LE_STATUS: %d %d %jx\n", e, rp.status, (uintmax_t)rp.le_status);
+
+ return 0;
+}
+
+static int
+set_le_event_mask(int s, uint64_t mask)
+{
+ ng_hci_le_set_event_mask_cp semc;
+ ng_hci_le_set_event_mask_rp rp;
+ int i, n ,e;
+
+ n = sizeof(rp);
+
+ for (i=0; i < NG_HCI_LE_EVENT_MASK_SIZE; i++) {
+ semc.event_mask[i] = mask&0xff;
+ mask >>= 8;
+ }
+ e = hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LE,
+ NG_HCI_OCF_LE_SET_EVENT_MASK),
+ (void *)&semc, sizeof(semc), (void *)&rp, &n);
+
+ return 0;
+}
+
+static int
+set_event_mask(int s, uint64_t mask)
+{
+ ng_hci_set_event_mask_cp semc;
+ ng_hci_set_event_mask_rp rp;
+ int i, n, e;
+
+ n = sizeof(rp);
+
+ for (i=0; i < NG_HCI_EVENT_MASK_SIZE; i++) {
+ semc.event_mask[i] = mask&0xff;
+ mask >>= 8;
+ }
+ e = hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_HC_BASEBAND,
+ NG_HCI_OCF_SET_EVENT_MASK),
+ (void *)&semc, sizeof(semc), (void *)&rp, &n);
+
+ return 0;
+}
+
+static
+int le_enable(int s, int argc, char *argv[])
+{
+ if (argc != 1)
+ return USAGE;
+
+ if (strcasecmp(argv[0], "enable") == 0) {
+ set_event_mask(s, NG_HCI_EVENT_MASK_DEFAULT |
+ NG_HCI_EVENT_MASK_LE);
+ set_le_event_mask(s, NG_HCI_LE_EVENT_MASK_ALL);
+ } else if (strcasecmp(argv[0], "disble") == 0)
+ set_event_mask(s, NG_HCI_EVENT_MASK_DEFAULT);
+ else
+ return USAGE;
+
+ return OK;
+}
+
+struct hci_command le_commands[] = {
+{
+ "le_enable",
+ "le_enable [enable|disable] \n"
+ "Enable LE event ",
+ &le_enable,
+},
+ {
+ "le_read_local_supported_features",
+ "le_read_local_supported_features\n"
+ "read local supported features mask",
+ &le_read_local_supported_features,
+ },
+ {
+ "le_read_supported_status",
+ "le_read_supported_status\n"
+ "read supported status"
+ ,
+ &le_read_supported_status,
+ },
+ {
+ "le_set_scan_response",
+ "le_set_scan_response -n $name -f $flag -u $uuid16,$uuid16 \n"
+ "set LE scan response data"
+ ,
+ &le_set_scan_response,
+ },
+ {
+ "le_set_scan_enable",
+ "le_set_scan_enable [enable|disable] \n"
+ "enable or disable LE device scan",
+ &le_set_scan_enable
+ },
+ {
+ "le_set_scan_param",
+ "le_set_scan_param [active|passive] interval(ms) window(ms) [public|random] [all|whitelist] \n"
+ "set LE device scan parameter",
+ &le_set_scan_param
+ },
+};
diff --git a/usr.sbin/bluetooth/hccontrol/link_control.c b/usr.sbin/bluetooth/hccontrol/link_control.c
new file mode 100644
index 0000000..a55426c
--- /dev/null
+++ b/usr.sbin/bluetooth/hccontrol/link_control.c
@@ -0,0 +1,961 @@
+/*
+ * link_control.c
+ *
+ * Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: link_control.c,v 1.4 2003/08/18 19:19:54 max Exp $
+ * $FreeBSD$
+ */
+
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include "hccontrol.h"
+
+static void hci_inquiry_response (int n, uint8_t **b);
+
+/* Send Inquiry command to the unit */
+static int
+hci_inquiry(int s, int argc, char **argv)
+{
+ int n0, n1, n2, timo;
+ char b[512];
+ ng_hci_inquiry_cp cp;
+ ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) b;
+
+ /* set defaults */
+ cp.lap[2] = 0x9e;
+ cp.lap[1] = 0x8b;
+ cp.lap[0] = 0x33;
+ cp.inquiry_length = 5;
+ cp.num_responses = 8;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 3:
+ /* number of responses, range 0x00 - 0xff */
+ if (sscanf(argv[2], "%d", &n0) != 1 || n0 < 0 || n0 > 0xff)
+ return (USAGE);
+
+ cp.num_responses = (n0 & 0xff);
+
+ case 2:
+ /* inquiry length (N * 1.28) sec, range 0x01 - 0x30 */
+ if (sscanf(argv[1], "%d", &n0) != 1 || n0 < 0x1 || n0 > 0x30)
+ return (USAGE);
+
+ cp.inquiry_length = (n0 & 0xff);
+
+ case 1:
+ /* LAP */
+ if (sscanf(argv[0], "%x:%x:%x", &n2, &n1, &n0) != 3)
+ return (USAGE);
+
+ cp.lap[0] = (n0 & 0xff);
+ cp.lap[1] = (n1 & 0xff);
+ cp.lap[2] = (n2 & 0xff);
+
+ case 0:
+ /* use defaults */
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send request and expect status back */
+ n0 = sizeof(b);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL,
+ NG_HCI_OCF_INQUIRY), (char const *) &cp, sizeof(cp),
+ b, &n0) == ERROR)
+ return (ERROR);
+
+ if (*b != 0x00)
+ return (FAILED);
+
+ timo = timeout;
+ timeout = cp.inquiry_length * 1.28 + 1;
+
+wait_for_more:
+ /* wait for inquiry events */
+ n0 = sizeof(b);
+ if (hci_recv(s, b, &n0) == ERROR) {
+ timeout = timo;
+ return (ERROR);
+ }
+
+ if (n0 < sizeof(*e)) {
+ timeout = timo;
+ errno = EIO;
+ return (ERROR);
+ }
+
+ switch (e->event) {
+ case NG_HCI_EVENT_INQUIRY_RESULT: {
+ ng_hci_inquiry_result_ep *ir =
+ (ng_hci_inquiry_result_ep *)(e + 1);
+ uint8_t *r = (uint8_t *)(ir + 1);
+
+ fprintf(stdout, "Inquiry result, num_responses=%d\n",
+ ir->num_responses);
+
+ for (n0 = 0; n0 < ir->num_responses; n0++)
+ hci_inquiry_response(n0, &r);
+
+ goto wait_for_more;
+ }
+
+ case NG_HCI_EVENT_INQUIRY_COMPL:
+ fprintf(stdout, "Inquiry complete. Status: %s [%#02x]\n",
+ hci_status2str(*(b + sizeof(*e))), *(b + sizeof(*e)));
+ break;
+
+ default:
+ goto wait_for_more;
+ }
+
+ timeout = timo;
+
+ return (OK);
+} /* hci_inquiry */
+
+/* Print Inquiry_Result event */
+static void
+hci_inquiry_response(int n, uint8_t **b)
+{
+ ng_hci_inquiry_response *ir = (ng_hci_inquiry_response *)(*b);
+
+ fprintf(stdout, "Inquiry result #%d\n", n);
+ fprintf(stdout, "\tBD_ADDR: %s\n", hci_bdaddr2str(&ir->bdaddr));
+ fprintf(stdout, "\tPage Scan Rep. Mode: %#02x\n",
+ ir->page_scan_rep_mode);
+ fprintf(stdout, "\tPage Scan Period Mode: %#02x\n",
+ ir->page_scan_period_mode);
+ fprintf(stdout, "\tPage Scan Mode: %#02x\n",
+ ir->page_scan_mode);
+ fprintf(stdout, "\tClass: %02x:%02x:%02x\n",
+ ir->uclass[2], ir->uclass[1], ir->uclass[0]);
+ fprintf(stdout, "\tClock offset: %#04x\n",
+ le16toh(ir->clock_offset));
+
+ *b += sizeof(*ir);
+} /* hci_inquiry_response */
+
+/* Send Create_Connection command to the unit */
+static int
+hci_create_connection(int s, int argc, char **argv)
+{
+ int n0;
+ char b[512];
+ ng_hci_create_con_cp cp;
+ ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) b;
+
+ /* Set defaults */
+ memset(&cp, 0, sizeof(cp));
+ cp.pkt_type = htole16( NG_HCI_PKT_DM1 | NG_HCI_PKT_DH1 |
+ NG_HCI_PKT_DM3 | NG_HCI_PKT_DH3 |
+ NG_HCI_PKT_DM5);
+ cp.page_scan_rep_mode = NG_HCI_SCAN_REP_MODE0;
+ cp.page_scan_mode = NG_HCI_MANDATORY_PAGE_SCAN_MODE;
+ cp.clock_offset = 0;
+ cp.accept_role_switch = 1;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 6:
+ /* accept role switch */
+ if (sscanf(argv[5], "%d", &n0) != 1)
+ return (USAGE);
+
+ cp.accept_role_switch = n0 ? 1 : 0;
+
+ case 5:
+ /* clock offset */
+ if (sscanf(argv[4], "%d", &n0) != 1)
+ return (USAGE);
+
+ cp.clock_offset = (n0 & 0xffff);
+ cp.clock_offset = htole16(cp.clock_offset);
+
+ case 4:
+ /* page scan mode */
+ if (sscanf(argv[3], "%d", &n0) != 1 || n0 < 0 || n0 > 3)
+ return (USAGE);
+
+ cp.page_scan_mode = (n0 & 0xff);
+
+ case 3:
+ /* page scan rep mode */
+ if (sscanf(argv[2], "%d", &n0) != 1 || n0 < 0 || n0 > 2)
+ return (USAGE);
+
+ cp.page_scan_rep_mode = (n0 & 0xff);
+
+ case 2:
+ /* packet type */
+ if (sscanf(argv[1], "%x", &n0) != 1)
+ return (USAGE);
+
+ n0 &= ( NG_HCI_PKT_DM1 | NG_HCI_PKT_DH1 |
+ NG_HCI_PKT_DM3 | NG_HCI_PKT_DH3 |
+ NG_HCI_PKT_DM5);
+ if (n0 == 0)
+ return (USAGE);
+
+ cp.pkt_type = (n0 & 0xffff);
+ cp.pkt_type = htole16(cp.pkt_type);
+
+ case 1:
+ /* BD_ADDR */
+ if (!bt_aton(argv[0], &cp.bdaddr)) {
+ struct hostent *he = NULL;
+
+ if ((he = bt_gethostbyname(argv[0])) == NULL)
+ return (USAGE);
+
+ memcpy(&cp.bdaddr, he->h_addr, sizeof(cp.bdaddr));
+ }
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send request and expect status response */
+ n0 = sizeof(b);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL,
+ NG_HCI_OCF_CREATE_CON),
+ (char const *) &cp, sizeof(cp), b, &n0) == ERROR)
+ return (ERROR);
+
+ if (*b != 0x00)
+ return (FAILED);
+
+ /* wait for event */
+again:
+ n0 = sizeof(b);
+ if (hci_recv(s, b, &n0) == ERROR)
+ return (ERROR);
+ if (n0 < sizeof(*e)) {
+ errno = EIO;
+ return (ERROR);
+ }
+
+ if (e->event == NG_HCI_EVENT_CON_COMPL) {
+ ng_hci_con_compl_ep *ep = (ng_hci_con_compl_ep *)(e + 1);
+
+ if (ep->status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(ep->status), ep->status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "BD_ADDR: %s\n", hci_bdaddr2str(&ep->bdaddr));
+ fprintf(stdout, "Connection handle: %d\n",
+ le16toh(ep->con_handle));
+ fprintf(stdout, "Encryption mode: %s [%d]\n",
+ hci_encrypt2str(ep->encryption_mode, 0),
+ ep->encryption_mode);
+ } else
+ goto again;
+
+ return (OK);
+} /* hci_create_connection */
+
+/* Send Disconnect command to the unit */
+static int
+hci_disconnect(int s, int argc, char **argv)
+{
+ int n;
+ char b[512];
+ ng_hci_discon_cp cp;
+ ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) b;
+
+ /* Set defaults */
+ memset(&cp, 0, sizeof(cp));
+ cp.reason = 0x13;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 2:
+ /* reason */
+ if (sscanf(argv[1], "%d", &n) != 1 || n <= 0x00 || n > 0xff)
+ return (USAGE);
+
+ cp.reason = (uint8_t) (n & 0xff);
+
+ case 1:
+ /* connection handle */
+ if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff)
+ return (USAGE);
+
+ cp.con_handle = (uint16_t) (n & 0x0fff);
+ cp.con_handle = htole16(cp.con_handle);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send request and expect status response */
+ n = sizeof(b);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL,
+ NG_HCI_OCF_DISCON),
+ (char const *) &cp, sizeof(cp), b, &n) == ERROR)
+ return (ERROR);
+
+ if (*b != 0x00)
+ return (FAILED);
+
+ /* wait for event */
+again:
+ n = sizeof(b);
+ if (hci_recv(s, b, &n) == ERROR)
+ return (ERROR);
+ if (n < sizeof(*e)) {
+ errno = EIO;
+ return (ERROR);
+ }
+
+ if (e->event == NG_HCI_EVENT_DISCON_COMPL) {
+ ng_hci_discon_compl_ep *ep = (ng_hci_discon_compl_ep *)(e + 1);
+
+ if (ep->status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(ep->status), ep->status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "Connection handle: %d\n",
+ le16toh(ep->con_handle));
+ fprintf(stdout, "Reason: %s [%#02x]\n",
+ hci_status2str(ep->reason), ep->reason);
+ } else
+ goto again;
+
+ return (OK);
+} /* hci_disconnect */
+
+/* Send Add_SCO_Connection command to the unit */
+static int
+hci_add_sco_connection(int s, int argc, char **argv)
+{
+ int n;
+ char b[512];
+ ng_hci_add_sco_con_cp cp;
+ ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) b;
+
+ /* Set defaults */
+ memset(&cp, 0, sizeof(cp));
+ cp.pkt_type = htole16(NG_HCI_PKT_HV1 | NG_HCI_PKT_HV2 | NG_HCI_PKT_HV3);
+
+ /* parse command parameters */
+ switch (argc) {
+ case 2:
+ /* packet type */
+ if (sscanf(argv[1], "%x", &n) != 1)
+ return (USAGE);
+
+ n &= (NG_HCI_PKT_HV1 | NG_HCI_PKT_HV2 | NG_HCI_PKT_HV3);
+ if (n == 0)
+ return (USAGE);
+
+ cp.pkt_type = (uint16_t) (n & 0x0fff);
+ cp.pkt_type = htole16(cp.pkt_type);
+
+ case 1:
+ /* acl connection handle */
+ if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff)
+ return (USAGE);
+
+ cp.con_handle = (uint16_t) (n & 0x0fff);
+ cp.con_handle = htole16(cp.con_handle);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send request and expect status response */
+ n = sizeof(b);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL,
+ NG_HCI_OCF_ADD_SCO_CON),
+ (char const *) &cp, sizeof(cp), b, &n) == ERROR)
+ return (ERROR);
+
+ if (*b != 0x00)
+ return (FAILED);
+
+ /* wait for event */
+again:
+ n = sizeof(b);
+ if (hci_recv(s, b, &n) == ERROR)
+ return (ERROR);
+ if (n < sizeof(*e)) {
+ errno = EIO;
+ return (ERROR);
+ }
+
+ if (e->event == NG_HCI_EVENT_CON_COMPL) {
+ ng_hci_con_compl_ep *ep = (ng_hci_con_compl_ep *)(e + 1);
+
+ if (ep->status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(ep->status), ep->status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "BD_ADDR: %s\n", hci_bdaddr2str(&ep->bdaddr));
+ fprintf(stdout, "Connection handle: %d\n",
+ le16toh(ep->con_handle));
+ fprintf(stdout, "Encryption mode: %s [%d]\n",
+ hci_encrypt2str(ep->encryption_mode, 0),
+ ep->encryption_mode);
+ } else
+ goto again;
+
+ return (OK);
+} /* Add_SCO_Connection */
+
+/* Send Change_Connection_Packet_Type command to the unit */
+static int
+hci_change_connection_packet_type(int s, int argc, char **argv)
+{
+ int n;
+ char b[512];
+ ng_hci_change_con_pkt_type_cp cp;
+ ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) b;
+
+ switch (argc) {
+ case 2:
+ /* connection handle */
+ if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff)
+ return (USAGE);
+
+ cp.con_handle = (uint16_t) (n & 0x0fff);
+ cp.con_handle = htole16(cp.con_handle);
+
+ /* packet type */
+ if (sscanf(argv[1], "%x", &n) != 1)
+ return (USAGE);
+
+ cp.pkt_type = (uint16_t) (n & 0xffff);
+ cp.pkt_type = htole16(cp.pkt_type);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send request and expect status response */
+ n = sizeof(b);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL,
+ NG_HCI_OCF_CHANGE_CON_PKT_TYPE),
+ (char const *) &cp, sizeof(cp), b, &n) == ERROR)
+ return (ERROR);
+
+ if (*b != 0x00)
+ return (FAILED);
+
+ /* wait for event */
+again:
+ n = sizeof(b);
+ if (hci_recv(s, b, &n) == ERROR)
+ return (ERROR);
+ if (n < sizeof(*e)) {
+ errno = EIO;
+ return (ERROR);
+ }
+
+ if (e->event == NG_HCI_EVENT_CON_PKT_TYPE_CHANGED) {
+ ng_hci_con_pkt_type_changed_ep *ep =
+ (ng_hci_con_pkt_type_changed_ep *)(e + 1);
+
+ if (ep->status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(ep->status), ep->status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "Connection handle: %d\n",
+ le16toh(ep->con_handle));
+ fprintf(stdout, "Packet type: %#04x\n",
+ le16toh(ep->pkt_type));
+ } else
+ goto again;
+
+ return (OK);
+} /* hci_change_connection_packet_type */
+
+/* Send Remote_Name_Request command to the unit */
+static int
+hci_remote_name_request(int s, int argc, char **argv)
+{
+ int n0;
+ char b[512];
+ ng_hci_remote_name_req_cp cp;
+ ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) b;
+
+ memset(&cp, 0, sizeof(cp));
+ cp.page_scan_rep_mode = NG_HCI_SCAN_REP_MODE0;
+ cp.page_scan_mode = NG_HCI_MANDATORY_PAGE_SCAN_MODE;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 4:
+ /* clock_offset */
+ if (sscanf(argv[3], "%x", &n0) != 1)
+ return (USAGE);
+
+ cp.clock_offset = (n0 & 0xffff);
+ cp.clock_offset = htole16(cp.clock_offset);
+
+ case 3:
+ /* page_scan_mode */
+ if (sscanf(argv[2], "%d", &n0) != 1 || n0 < 0x00 || n0 > 0x03)
+ return (USAGE);
+
+ cp.page_scan_mode = (n0 & 0xff);
+
+ case 2:
+ /* page_scan_rep_mode */
+ if (sscanf(argv[1], "%d", &n0) != 1 || n0 < 0x00 || n0 > 0x02)
+ return (USAGE);
+
+ cp.page_scan_rep_mode = (n0 & 0xff);
+
+ case 1:
+ /* BD_ADDR */
+ if (!bt_aton(argv[0], &cp.bdaddr)) {
+ struct hostent *he = NULL;
+
+ if ((he = bt_gethostbyname(argv[0])) == NULL)
+ return (USAGE);
+
+ memcpy(&cp.bdaddr, he->h_addr, sizeof(cp.bdaddr));
+ }
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send request and expect status response */
+ n0 = sizeof(b);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL,
+ NG_HCI_OCF_REMOTE_NAME_REQ),
+ (char const *) &cp, sizeof(cp), b, &n0) == ERROR)
+ return (ERROR);
+
+ if (*b != 0x00)
+ return (FAILED);
+
+ /* wait for event */
+again:
+ n0 = sizeof(b);
+ if (hci_recv(s, b, &n0) == ERROR)
+ return (ERROR);
+ if (n0 < sizeof(*e)) {
+ errno = EIO;
+ return (ERROR);
+ }
+
+ if (e->event == NG_HCI_EVENT_REMOTE_NAME_REQ_COMPL) {
+ ng_hci_remote_name_req_compl_ep *ep =
+ (ng_hci_remote_name_req_compl_ep *)(e + 1);
+
+ if (ep->status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(ep->status), ep->status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "BD_ADDR: %s\n", hci_bdaddr2str(&ep->bdaddr));
+ fprintf(stdout, "Name: %s\n", ep->name);
+ } else
+ goto again;
+
+ return (OK);
+} /* hci_remote_name_request */
+
+/* Send Read_Remote_Supported_Features command to the unit */
+static int
+hci_read_remote_supported_features(int s, int argc, char **argv)
+{
+ int n;
+ char b[512];
+ ng_hci_read_remote_features_cp cp;
+ ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) b;
+ char buffer[1024];
+
+ /* parse command parameters */
+ switch (argc) {
+ case 1:
+ /* connecton handle */
+ if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 0x0eff)
+ return (USAGE);
+
+ cp.con_handle = (n & 0x0fff);
+ cp.con_handle = htole16(cp.con_handle);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send request and expect status response */
+ n = sizeof(b);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL,
+ NG_HCI_OCF_READ_REMOTE_FEATURES),
+ (char const *) &cp, sizeof(cp), b, &n) == ERROR)
+ return (ERROR);
+
+ if (*b != 0x00)
+ return (FAILED);
+
+ /* wait for event */
+again:
+ n = sizeof(b);
+ if (hci_recv(s, b, &n) == ERROR)
+ return (ERROR);
+
+ if (n < sizeof(*e)) {
+ errno = EIO;
+ return (ERROR);
+ }
+
+ if (e->event == NG_HCI_EVENT_READ_REMOTE_FEATURES_COMPL) {
+ ng_hci_read_remote_features_compl_ep *ep =
+ (ng_hci_read_remote_features_compl_ep *)(e + 1);
+
+ if (ep->status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(ep->status), ep->status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "Connection handle: %d\n",
+ le16toh(ep->con_handle));
+ fprintf(stdout, "Features: ");
+ for (n = 0; n < sizeof(ep->features); n++)
+ fprintf(stdout, "%#02x ", ep->features[n]);
+ fprintf(stdout, "\n%s\n", hci_features2str(ep->features,
+ buffer, sizeof(buffer)));
+ } else
+ goto again;
+
+ return (OK);
+} /* hci_read_remote_supported_features */
+
+/* Send Read_Remote_Version_Information command to the unit */
+static int
+hci_read_remote_version_information(int s, int argc, char **argv)
+{
+ int n;
+ char b[512];
+ ng_hci_read_remote_ver_info_cp cp;
+ ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) b;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 1:
+ /* connecton handle */
+ if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 0x0eff)
+ return (USAGE);
+
+ cp.con_handle = (n & 0x0fff);
+ cp.con_handle = htole16(cp.con_handle);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send request and expect status response */
+ n = sizeof(b);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL,
+ NG_HCI_OCF_READ_REMOTE_VER_INFO),
+ (char const *) &cp, sizeof(cp), b, &n) == ERROR)
+ return (ERROR);
+
+ if (*b != 0x00)
+ return (FAILED);
+
+ /* wait for event */
+again:
+ n = sizeof(b);
+ if (hci_recv(s, b, &n) == ERROR)
+ return (ERROR);
+
+ if (n < sizeof(*e)) {
+ errno = EIO;
+ return (ERROR);
+ }
+
+ if (e->event == NG_HCI_EVENT_READ_REMOTE_VER_INFO_COMPL) {
+ ng_hci_read_remote_ver_info_compl_ep *ep =
+ (ng_hci_read_remote_ver_info_compl_ep *)(e + 1);
+
+ if (ep->status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(ep->status), ep->status);
+ return (FAILED);
+ }
+
+ ep->manufacturer = le16toh(ep->manufacturer);
+
+ fprintf(stdout, "Connection handle: %d\n",
+ le16toh(ep->con_handle));
+ fprintf(stdout, "LMP version: %s [%#02x]\n",
+ hci_lmpver2str(ep->lmp_version), ep->lmp_version);
+ fprintf(stdout, "LMP sub-version: %#04x\n",
+ le16toh(ep->lmp_subversion));
+ fprintf(stdout, "Manufacturer: %s [%#04x]\n",
+ hci_manufacturer2str(ep->manufacturer),
+ ep->manufacturer);
+ } else
+ goto again;
+
+ return (OK);
+} /* hci_read_remote_version_information */
+
+/* Send Read_Clock_Offset command to the unit */
+static int
+hci_read_clock_offset(int s, int argc, char **argv)
+{
+ int n;
+ char b[512];
+ ng_hci_read_clock_offset_cp cp;
+ ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) b;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 1:
+ /* connecton handle */
+ if (sscanf(argv[0], "%d", &n) != 1 || n < 0 || n > 0x0eff)
+ return (USAGE);
+
+ cp.con_handle = (n & 0x0fff);
+ cp.con_handle = htole16(cp.con_handle);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send request and expect status response */
+ n = sizeof(b);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL,
+ NG_HCI_OCF_READ_CLOCK_OFFSET),
+ (char const *) &cp, sizeof(cp), b, &n) == ERROR)
+ return (ERROR);
+
+ if (*b != 0x00)
+ return (FAILED);
+
+ /* wait for event */
+again:
+ n = sizeof(b);
+ if (hci_recv(s, b, &n) == ERROR)
+ return (ERROR);
+
+ if (n < sizeof(*e)) {
+ errno = EIO;
+ return (ERROR);
+ }
+
+ if (e->event == NG_HCI_EVENT_READ_CLOCK_OFFSET_COMPL) {
+ ng_hci_read_clock_offset_compl_ep *ep =
+ (ng_hci_read_clock_offset_compl_ep *)(e + 1);
+
+ if (ep->status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(ep->status), ep->status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "Connection handle: %d\n",
+ le16toh(ep->con_handle));
+ fprintf(stdout, "Clock offset: %#04x\n",
+ le16toh(ep->clock_offset));
+ } else
+ goto again;
+
+ return (OK);
+} /* hci_read_clock_offset */
+
+struct hci_command link_control_commands[] = {
+{
+"inquiry <LAP> <inquiry_length> <num_reponses>",
+"\nThis command will cause the Bluetooth unit to enter Inquiry Mode.\n" \
+"Inquiry Mode is used to discover other nearby Bluetooth units. The LAP\n" \
+"input parameter contains the LAP from which the inquiry access code shall\n" \
+"be derived when the inquiry procedure is made. The Inquiry_Length parameter\n"\
+"specifies the total duration of the Inquiry Mode and, when this time\n" \
+"expires, Inquiry will be halted. The Num_Responses parameter specifies the\n" \
+"number of responses that can be received before the Inquiry is halted.\n\n" \
+"\t<LAP> - xx:xx:xx; 9e:8b:33 (GIAC), 93:8b:00 (LDIAC)\n" \
+"\t<inquiry_length> - dd; total length == dd * 1.28 sec\n" \
+"\t<num_responses> - dd",
+&hci_inquiry
+},
+{
+"create_connection <BD_ADDR> <pkt> <rep_mode> <ps_mode> <clck_off> <role_sw>",
+"" \
+"\t<BD_ADDR> - xx:xx:xx:xx:xx:xx BD_ADDR or name\n\n" \
+"\t<pkt> - xxxx; packet type\n" \
+"" \
+"\t\tACL packets\n" \
+"\t\t-----------\n" \
+"\t\t0x0008 DM1\n" \
+"\t\t0x0010 DH1\n" \
+"\t\t0x0400 DM3\n" \
+"\t\t0x0800 DH3\n" \
+"\t\t0x4000 DM5\n" \
+"\t\t0x8000 DH5\n\n" \
+"" \
+"\trep_mode - d; page scan repetition mode\n" \
+"" \
+"\t\tPage scan repetition modes\n" \
+"\t\t--------------------------\n" \
+"\t\t0 Page scan repetition mode 0\n" \
+"\t\t1 Page scan repetition mode 1\n" \
+"\t\t2 Page scan repetition mode 2\n" \
+"\n" \
+"\tps_mode - d; Page scan mode\n" \
+"" \
+"\t\tPage scan modes\n" \
+"\t\t---------------\n" \
+"\t\t0 Mandatory page scan mode\n" \
+"\t\t1 Optional page scan mode1\n" \
+"\t\t2 Optional page scan mode2\n" \
+"\t\t3 Optional page scan mode3\n" \
+"\n" \
+"\tclck_off - dddd; clock offset. Use 0 if unknown\n\n" \
+"\trole_sw - d; allow (1) or deny role switch\n",
+&hci_create_connection
+},
+{
+"disconnect <connection_handle> <reason>",
+"\nThe Disconnection command is used to terminate an existing connection.\n" \
+"The connection handle command parameter indicates which connection is to\n" \
+"be disconnected. The Reason command parameter indicates the reason for\n" \
+"ending the connection.\n\n" \
+"\t<connection_handle> - dddd; connection handle\n" \
+"\t<reason> - dd; reason; usually 19 (0x13) - user ended;\n" \
+"\t also 0x05, 0x13-0x15, 0x1A, 0x29",
+&hci_disconnect
+},
+{
+"add_sco_connection <acl connection handle> <packet type>",
+"This command will cause the link manager to create a SCO connection using\n" \
+"the ACL connection specified by the connection handle command parameter.\n" \
+"The Link Manager will determine how the new connection is established. This\n"\
+"connection is determined by the current state of the device, its piconet,\n" \
+"and the state of the device to be connected. The packet type command parameter\n" \
+"specifies which packet types the Link Manager should use for the connection.\n"\
+"The Link Manager must only use the packet type(s) specified by the packet\n" \
+"type command parameter for sending HCI SCO data packets. Multiple packet\n" \
+"types may be specified for the packet type command parameter by performing\n" \
+"a bitwise OR operation of the different packet types. Note: An SCO connection\n" \
+"can only be created when an ACL connection already exists and when it is\n" \
+"not put in park mode.\n\n" \
+"\t<connection_handle> - dddd; ACL connection handle\n" \
+"\t<packet_type> - xxxx; packet type\n" \
+"" \
+"\t\tSCO packets\n" \
+"\t\t-----------\n" \
+"\t\t0x0020 HV1\n" \
+"\t\t0x0040 HV2\n" \
+"\t\t0x0080 HV3\n",
+&hci_add_sco_connection
+},
+{
+"change_connection_packet_type <connection_hande> <packet_type>",
+"The Change_Connection_Packet_Type command is used to change which packet\n" \
+"types can be used for a connection that is currently established. This\n" \
+"allows current connections to be dynamically modified to support different\n" \
+"types of user data. The Packet_Type command parameter specifies which\n" \
+"packet types the Link Manager can use for the connection. Multiple packet\n" \
+"types may be specified for the Packet_Type command parameter by bitwise OR\n" \
+"operation of the different packet types.\n\n" \
+"\t<connection_handle> - dddd; connection handle\n" \
+"\t<packet_type> - xxxx; packet type mask\n" \
+"" \
+"\t\tACL packets\n" \
+"\t\t-----------\n" \
+"\t\t0x0008 DM1\n" \
+"\t\t0x0010 DH1\n" \
+"\t\t0x0400 DM3\n" \
+"\t\t0x0800 DH3\n" \
+"\t\t0x4000 DM5\n" \
+"\t\t0x8000 DH5\n\n" \
+"" \
+"\t\tSCO packets\n" \
+"\t\t-----------\n" \
+"\t\t0x0020 HV1\n" \
+"\t\t0x0040 HV2\n" \
+"\t\t0x0080 HV3\n" \
+"",
+&hci_change_connection_packet_type
+},
+{
+"remote_name_request <BD_ADDR> <ps_rep_mode> <ps_mode> <clock_offset>",
+"\nThe Remote_Name_Request command is used to obtain the user-friendly\n" \
+"name of another Bluetooth unit.\n\n" \
+"\t<BD_ADDR> - xx:xx:xx:xx:xx:xx BD_ADDR or name\n" \
+"\t<ps_rep_mode> - dd; page scan repetition mode [0-2]\n" \
+"\t<ps_mode> - dd; page scan mode [0-3]\n" \
+"\t<clock_offset> - xxxx; clock offset [0 - 0xffff]",
+&hci_remote_name_request
+},
+{
+"read_remote_supported_features <connection_handle>",
+"\nThis command requests a list of the supported features for the remote\n" \
+"unit identified by the connection handle parameter. The connection handle\n" \
+"must be a connection handle for an ACL connection.\n\n" \
+"\t<connection_handle> - dddd; connection handle",
+&hci_read_remote_supported_features
+},
+{
+"read_remote_version_information <connection_handle>",
+"\nThis command will obtain the values for the version information for the\n" \
+"remote Bluetooth unit identified by the connection handle parameter. The\n" \
+"connection handle must be a connection handle for an ACL connection.\n\n" \
+"\t<connection_handle> - dddd; connection handle",
+&hci_read_remote_version_information
+},
+{
+"read_clock_offset <connection_handle>",
+"\nThis command allows the Host to read the clock offset from the remote unit.\n" \
+"\t<connection_handle> - dddd; connection handle",
+&hci_read_clock_offset
+},
+{
+NULL,
+}};
+
diff --git a/usr.sbin/bluetooth/hccontrol/link_policy.c b/usr.sbin/bluetooth/hccontrol/link_policy.c
new file mode 100644
index 0000000..8142b23
--- /dev/null
+++ b/usr.sbin/bluetooth/hccontrol/link_policy.c
@@ -0,0 +1,306 @@
+/*
+ * link_policy.c
+ *
+ * Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: link_policy.c,v 1.3 2003/08/18 19:19:54 max Exp $
+ * $FreeBSD$
+ */
+
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include "hccontrol.h"
+
+/* Send Role Discovery to the unit */
+static int
+hci_role_discovery(int s, int argc, char **argv)
+{
+ ng_hci_role_discovery_cp cp;
+ ng_hci_role_discovery_rp rp;
+ int n;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 1:
+ /* connection handle */
+ if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff)
+ return (USAGE);
+
+ cp.con_handle = (uint16_t) (n & 0x0fff);
+ cp.con_handle = htole16(cp.con_handle);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send request */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_POLICY,
+ NG_HCI_OCF_ROLE_DISCOVERY),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "Connection handle: %d\n", le16toh(rp.con_handle));
+ fprintf(stdout, "Role: %s [%#x]\n",
+ (rp.role == NG_HCI_ROLE_MASTER)? "Master" : "Slave", rp.role);
+
+ return (OK);
+} /* hci_role_discovery */
+
+/* Send Swith Role to the unit */
+static int
+hci_switch_role(int s, int argc, char **argv)
+{
+ int n0;
+ char b[512];
+ ng_hci_switch_role_cp cp;
+ ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) b;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 2:
+ /* bdaddr */
+ if (!bt_aton(argv[0], &cp.bdaddr)) {
+ struct hostent *he = NULL;
+
+ if ((he = bt_gethostbyname(argv[0])) == NULL)
+ return (USAGE);
+
+ memcpy(&cp.bdaddr, he->h_addr, sizeof(cp.bdaddr));
+ }
+
+ /* role */
+ if (sscanf(argv[1], "%d", &n0) != 1)
+ return (USAGE);
+
+ cp.role = n0? NG_HCI_ROLE_SLAVE : NG_HCI_ROLE_MASTER;
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send request and expect status response */
+ n0 = sizeof(b);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_POLICY,
+ NG_HCI_OCF_SWITCH_ROLE),
+ (char const *) &cp, sizeof(cp), b, &n0) == ERROR)
+ return (ERROR);
+
+ if (*b != 0x00)
+ return (FAILED);
+
+ /* wait for event */
+again:
+ n0 = sizeof(b);
+ if (hci_recv(s, b, &n0) == ERROR)
+ return (ERROR);
+ if (n0 < sizeof(*e)) {
+ errno = EIO;
+ return (ERROR);
+ }
+
+ if (e->event == NG_HCI_EVENT_ROLE_CHANGE) {
+ ng_hci_role_change_ep *ep = (ng_hci_role_change_ep *)(e + 1);
+
+ if (ep->status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(ep->status), ep->status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "BD_ADDR: %s\n", hci_bdaddr2str(&ep->bdaddr));
+ fprintf(stdout, "Role: %s [%#x]\n",
+ (ep->role == NG_HCI_ROLE_MASTER)? "Master" : "Slave",
+ ep->role);
+ } else
+ goto again;
+
+ return (OK);
+} /* hci_switch_role */
+
+/* Send Read_Link_Policy_Settings command to the unit */
+static int
+hci_read_link_policy_settings(int s, int argc, char **argv)
+{
+ ng_hci_read_link_policy_settings_cp cp;
+ ng_hci_read_link_policy_settings_rp rp;
+ int n;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 1:
+ /* connection handle */
+ if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff)
+ return (USAGE);
+
+ cp.con_handle = (uint16_t) (n & 0x0fff);
+ cp.con_handle = htole16(cp.con_handle);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send request */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_POLICY,
+ NG_HCI_OCF_READ_LINK_POLICY_SETTINGS),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "Connection handle: %d\n", le16toh(rp.con_handle));
+ fprintf(stdout, "Link policy settings: %#x\n", le16toh(rp.settings));
+
+ return (OK);
+} /* hci_read_link_policy_settings */
+
+/* Send Write_Link_Policy_Settings command to the unit */
+static int
+hci_write_link_policy_settings(int s, int argc, char **argv)
+{
+ ng_hci_write_link_policy_settings_cp cp;
+ ng_hci_write_link_policy_settings_rp rp;
+ int n;
+
+ /* parse command parameters */
+ switch (argc) {
+ case 2:
+ /* connection handle */
+ if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff)
+ return (USAGE);
+
+ cp.con_handle = (uint16_t) (n & 0x0fff);
+ cp.con_handle = htole16(cp.con_handle);
+
+ /* link policy settings */
+ if (sscanf(argv[1], "%x", &n) != 1)
+ return (USAGE);
+
+ cp.settings = (uint16_t) (n & 0x0ffff);
+ cp.settings = htole16(cp.settings);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send request */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_LINK_POLICY,
+ NG_HCI_OCF_WRITE_LINK_POLICY_SETTINGS),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ return (OK);
+} /* hci_write_link_policy_settings */
+
+struct hci_command link_policy_commands[] = {
+{
+"role_discovery <connection_handle>",
+"\nThe Role_Discovery command is used for a Bluetooth device to determine\n" \
+"which role the device is performing for a particular Connection Handle.\n" \
+"The connection handle must be a connection handle for an ACL connection.\n\n" \
+"\t<connection_handle> - dddd; connection handle",
+&hci_role_discovery
+},
+{
+"switch_role <BD_ADDR> <role>",
+"\nThe Switch_Role command is used for a Bluetooth device to switch the\n" \
+"current role the device is performing for a particular connection with\n" \
+"another specified Bluetooth device. The BD_ADDR command parameter indicates\n"\
+"for which connection the role switch is to be performed. The Role indicates\n"\
+"the requested new role that the local device performs. Note: the BD_ADDR\n" \
+"command parameter must specify a Bluetooth device for which a connection\n"
+"already exists.\n\n" \
+"\t<BD_ADDR> - xx:xx:xx:xx:xx:xx BD_ADDR or name\n" \
+"\t<role> - dd; role; 0 - Master, 1 - Slave",
+&hci_switch_role
+},
+{
+"read_link_policy_settings <connection_handle>",
+"\nThis command will read the Link Policy setting for the specified connection\n"\
+"handle. The link policy settings parameter determines the behavior of the\n" \
+"local Link Manager when it receives a request from a remote device or it\n" \
+"determines itself to change the master-slave role or to enter the hold,\n" \
+"sniff, or park mode. The local Link Manager will automatically accept or\n" \
+"reject such a request from the remote device, and may even autonomously\n" \
+"request itself, depending on the value of the link policy settings parameter\n"\
+"for the corresponding connection handle. The connection handle must be a\n" \
+"connection handle for an ACL connection.\n\n" \
+"\t<connection_handle> - dddd; connection handle",
+&hci_read_link_policy_settings
+},
+{
+"write_link_policy_settings <connection_handle> <settings>",
+"\nThis command will write the Link Policy setting for the specified connection\n"\
+"handle. The link policy settings parameter determines the behavior of the\n" \
+"local Link Manager when it receives a request from a remote device or it\n" \
+"determines itself to change the master-slave role or to enter the hold,\n" \
+"sniff, or park mode. The local Link Manager will automatically accept or\n" \
+"reject such a request from the remote device, and may even autonomously\n" \
+"request itself, depending on the value of the link policy settings parameter\n"\
+"for the corresponding connection handle. The connection handle must be a\n" \
+"connection handle for an ACL connection. Multiple Link Manager policies may\n"\
+"be specified for the link policy settings parameter by performing a bitwise\n"\
+"OR operation of the different activity types.\n\n" \
+"\t<connection_handle> - dddd; connection handle\n" \
+"\t<settings> - xxxx; settings\n" \
+"\t\t0x0000 - Disable All LM Modes (Default)\n" \
+"\t\t0x0001 - Enable Master Slave Switch\n" \
+"\t\t0x0002 - Enable Hold Mode\n" \
+"\t\t0x0004 - Enable Sniff Mode\n" \
+"\t\t0x0008 - Enable Park Mode\n",
+&hci_write_link_policy_settings
+},
+{
+NULL,
+}};
+
diff --git a/usr.sbin/bluetooth/hccontrol/node.c b/usr.sbin/bluetooth/hccontrol/node.c
new file mode 100644
index 0000000..fb6fd19
--- /dev/null
+++ b/usr.sbin/bluetooth/hccontrol/node.c
@@ -0,0 +1,608 @@
+/*
+ * node.c
+ *
+ * Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: node.c,v 1.6 2003/07/22 21:14:02 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/ioctl.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <errno.h>
+#include <netgraph/ng_message.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "hccontrol.h"
+
+/* Send Read_Node_State command to the node */
+static int
+hci_read_node_state(int s, int argc, char **argv)
+{
+ struct ng_btsocket_hci_raw_node_state r;
+
+ memset(&r, 0, sizeof(r));
+ if (ioctl(s, SIOC_HCI_RAW_NODE_GET_STATE, &r, sizeof(r)) < 0)
+ return (ERROR);
+
+ fprintf(stdout, "State: %#x\n", r.state);
+
+ return (OK);
+} /* hci_read_node_state */
+
+/* Send Intitialize command to the node */
+static int
+hci_node_initialize(int s, int argc, char **argv)
+{
+ if (ioctl(s, SIOC_HCI_RAW_NODE_INIT) < 0)
+ return (ERROR);
+
+ return (OK);
+} /* hci_node_initialize */
+
+/* Send Read_Debug_Level command to the node */
+static int
+hci_read_debug_level(int s, int argc, char **argv)
+{
+ struct ng_btsocket_hci_raw_node_debug r;
+
+ memset(&r, 0, sizeof(r));
+ if (ioctl(s, SIOC_HCI_RAW_NODE_GET_DEBUG, &r, sizeof(r)) < 0)
+ return (ERROR);
+
+ fprintf(stdout, "Debug level: %d\n", r.debug);
+
+ return (OK);
+} /* hci_read_debug_level */
+
+/* Send Write_Debug_Level command to the node */
+static int
+hci_write_debug_level(int s, int argc, char **argv)
+{
+ struct ng_btsocket_hci_raw_node_debug r;
+
+ memset(&r, 0, sizeof(r));
+ switch (argc) {
+ case 1:
+ r.debug = atoi(argv[0]);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ if (ioctl(s, SIOC_HCI_RAW_NODE_SET_DEBUG, &r, sizeof(r)) < 0)
+ return (ERROR);
+
+ return (OK);
+} /* hci_write_debug_level */
+
+/* Send Read_Node_Buffer_Size command to the node */
+static int
+hci_read_node_buffer_size(int s, int argc, char **argv)
+{
+ struct ng_btsocket_hci_raw_node_buffer r;
+
+ memset(&r, 0, sizeof(r));
+ if (ioctl(s, SIOC_HCI_RAW_NODE_GET_BUFFER, &r, sizeof(r)) < 0)
+ return (ERROR);
+
+ fprintf(stdout, "Number of free command buffers: %d\n",
+ r.buffer.cmd_free);
+ fprintf(stdout, "Max. ACL packet size: %d\n",
+ r.buffer.acl_size);
+ fprintf(stdout, "Numbef of free ACL buffers: %d\n",
+ r.buffer.acl_free);
+ fprintf(stdout, "Total number of ACL buffers: %d\n",
+ r.buffer.acl_pkts);
+ fprintf(stdout, "Max. SCO packet size: %d\n",
+ r.buffer.sco_size);
+ fprintf(stdout, "Numbef of free SCO buffers: %d\n",
+ r.buffer.sco_free);
+ fprintf(stdout, "Total number of SCO buffers: %d\n",
+ r.buffer.sco_pkts);
+
+ return (OK);
+} /* hci_read_node_buffer_size */
+
+/* Send Read_Node_BD_ADDR command to the node */
+static int
+hci_read_node_bd_addr(int s, int argc, char **argv)
+{
+ struct ng_btsocket_hci_raw_node_bdaddr r;
+
+ memset(&r, 0, sizeof(r));
+ if (ioctl(s, SIOC_HCI_RAW_NODE_GET_BDADDR, &r, sizeof(r)) < 0)
+ return (ERROR);
+
+ fprintf(stdout, "BD_ADDR: %s\n", bt_ntoa(&r.bdaddr, NULL));
+
+ return (OK);
+} /* hci_read_node_bd_addr */
+
+/* Send Read_Node_Features command to the node */
+static int
+hci_read_node_features(int s, int argc, char **argv)
+{
+ struct ng_btsocket_hci_raw_node_features r;
+ int n;
+ char buffer[1024];
+
+ memset(&r, 0, sizeof(r));
+ if (ioctl(s, SIOC_HCI_RAW_NODE_GET_FEATURES, &r, sizeof(r)) < 0)
+ return (ERROR);
+
+ fprintf(stdout, "Features: ");
+ for (n = 0; n < sizeof(r.features)/sizeof(r.features[0]); n++)
+ fprintf(stdout, "%#02x ", r.features[n]);
+ fprintf(stdout, "\n%s\n", hci_features2str(r.features,
+ buffer, sizeof(buffer)));
+
+ return (OK);
+} /* hci_read_node_features */
+
+/* Send Read_Node_Stat command to the node */
+static int
+hci_read_node_stat(int s, int argc, char **argv)
+{
+ struct ng_btsocket_hci_raw_node_stat r;
+
+ memset(&r, 0, sizeof(r));
+ if (ioctl(s, SIOC_HCI_RAW_NODE_GET_STAT, &r, sizeof(r)) < 0)
+ return (ERROR);
+
+ fprintf(stdout, "Commands sent: %d\n", r.stat.cmd_sent);
+ fprintf(stdout, "Events received: %d\n", r.stat.evnt_recv);
+ fprintf(stdout, "ACL packets received: %d\n", r.stat.acl_recv);
+ fprintf(stdout, "ACL packets sent: %d\n", r.stat.acl_sent);
+ fprintf(stdout, "SCO packets received: %d\n", r.stat.sco_recv);
+ fprintf(stdout, "SCO packets sent: %d\n", r.stat.sco_sent);
+ fprintf(stdout, "Bytes received: %d\n", r.stat.bytes_recv);
+ fprintf(stdout, "Bytes sent: %d\n", r.stat.bytes_sent);
+
+ return (OK);
+} /* hci_read_node_stat */
+
+/* Send Reset_Node_Stat command to the node */
+static int
+hci_reset_node_stat(int s, int argc, char **argv)
+{
+ if (ioctl(s, SIOC_HCI_RAW_NODE_RESET_STAT) < 0)
+ return (ERROR);
+
+ return (OK);
+} /* hci_reset_node_stat */
+
+/* Send Flush_Neighbor_Cache command to the node */
+static int
+hci_flush_neighbor_cache(int s, int argc, char **argv)
+{
+ if (ioctl(s, SIOC_HCI_RAW_NODE_FLUSH_NEIGHBOR_CACHE) < 0)
+ return (ERROR);
+
+ return (OK);
+} /* hci_flush_neighbor_cache */
+
+/* Send Read_Neighbor_Cache command to the node */
+static int
+hci_read_neighbor_cache(int s, int argc, char **argv)
+{
+ struct ng_btsocket_hci_raw_node_neighbor_cache r;
+ int n, error = OK;
+
+ memset(&r, 0, sizeof(r));
+ r.num_entries = NG_HCI_MAX_NEIGHBOR_NUM;
+ r.entries = calloc(NG_HCI_MAX_NEIGHBOR_NUM,
+ sizeof(ng_hci_node_neighbor_cache_entry_ep));
+ if (r.entries == NULL) {
+ errno = ENOMEM;
+ return (ERROR);
+ }
+
+ if (ioctl(s, SIOC_HCI_RAW_NODE_GET_NEIGHBOR_CACHE, &r,
+ sizeof(r)) < 0) {
+ error = ERROR;
+ goto out;
+ }
+
+ fprintf(stdout,
+"BD_ADDR " \
+"Features " \
+"Clock offset " \
+"Page scan " \
+"Rep. scan\n");
+
+ for (n = 0; n < r.num_entries; n++) {
+ fprintf(stdout,
+"%-17.17s " \
+"%02x %02x %02x %02x %02x %02x %02x %02x " \
+"%#12x " \
+"%#9x " \
+"%#9x\n",
+ hci_bdaddr2str(&r.entries[n].bdaddr),
+ r.entries[n].features[0], r.entries[n].features[1],
+ r.entries[n].features[2], r.entries[n].features[3],
+ r.entries[n].features[4], r.entries[n].features[5],
+ r.entries[n].features[6], r.entries[n].features[7],
+ r.entries[n].clock_offset, r.entries[n].page_scan_mode,
+ r.entries[n].page_scan_rep_mode);
+ }
+out:
+ free(r.entries);
+
+ return (error);
+} /* hci_read_neightbor_cache */
+
+/* Send Read_Connection_List command to the node */
+static int
+hci_read_connection_list(int s, int argc, char **argv)
+{
+ struct ng_btsocket_hci_raw_con_list r;
+ int n, error = OK;
+
+ memset(&r, 0, sizeof(r));
+ r.num_connections = NG_HCI_MAX_CON_NUM;
+ r.connections = calloc(NG_HCI_MAX_CON_NUM, sizeof(ng_hci_node_con_ep));
+ if (r.connections == NULL) {
+ errno = ENOMEM;
+ return (ERROR);
+ }
+
+ if (ioctl(s, SIOC_HCI_RAW_NODE_GET_CON_LIST, &r, sizeof(r)) < 0) {
+ error = ERROR;
+ goto out;
+ }
+
+ fprintf(stdout,
+"Remote BD_ADDR " \
+"Handle " \
+"Type " \
+"Mode " \
+"Role " \
+"Encrypt " \
+"Pending " \
+"Queue " \
+"State\n");
+
+ for (n = 0; n < r.num_connections; n++) {
+ fprintf(stdout,
+"%-17.17s " \
+"%6d " \
+"%4.4s " \
+"%4d " \
+"%4.4s " \
+"%7.7s " \
+"%7d " \
+"%5d " \
+"%s\n",
+ hci_bdaddr2str(&r.connections[n].bdaddr),
+ r.connections[n].con_handle,
+ (r.connections[n].link_type == NG_HCI_LINK_ACL)?
+ "ACL" : "SCO",
+ r.connections[n].mode,
+ (r.connections[n].role == NG_HCI_ROLE_MASTER)?
+ "MAST" : "SLAV",
+ hci_encrypt2str(r.connections[n].encryption_mode, 1),
+ r.connections[n].pending,
+ r.connections[n].queue_len,
+ hci_con_state2str(r.connections[n].state));
+ }
+out:
+ free(r.connections);
+
+ return (error);
+} /* hci_read_connection_list */
+
+/* Send Read_Node_Link_Policy_Settings_Mask command to the node */
+int
+hci_read_node_link_policy_settings_mask(int s, int argc, char **argv)
+{
+ struct ng_btsocket_hci_raw_node_link_policy_mask r;
+
+ memset(&r, 0, sizeof(r));
+ if (ioctl(s, SIOC_HCI_RAW_NODE_GET_LINK_POLICY_MASK, &r, sizeof(r)) < 0)
+ return (ERROR);
+
+ fprintf(stdout, "Link Policy Settings mask: %#04x\n", r.policy_mask);
+
+ return (OK);
+} /* hci_read_node_link_policy_settings_mask */
+
+/* Send Write_Node_Link_Policy_Settings_Mask command to the node */
+int
+hci_write_node_link_policy_settings_mask(int s, int argc, char **argv)
+{
+ struct ng_btsocket_hci_raw_node_link_policy_mask r;
+ int m;
+
+ memset(&r, 0, sizeof(r));
+
+ switch (argc) {
+ case 1:
+ if (sscanf(argv[0], "%x", &m) != 1)
+ return (USAGE);
+
+ r.policy_mask = (m & 0xffff);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ if (ioctl(s, SIOC_HCI_RAW_NODE_SET_LINK_POLICY_MASK, &r, sizeof(r)) < 0)
+ return (ERROR);
+
+ return (OK);
+} /* hci_write_node_link_policy_settings_mask */
+
+/* Send Read_Node_Packet_Mask command to the node */
+int
+hci_read_node_packet_mask(int s, int argc, char **argv)
+{
+ struct ng_btsocket_hci_raw_node_packet_mask r;
+
+ memset(&r, 0, sizeof(r));
+ if (ioctl(s, SIOC_HCI_RAW_NODE_GET_PACKET_MASK, &r, sizeof(r)) < 0)
+ return (ERROR);
+
+ fprintf(stdout, "Packet mask: %#04x\n", r.packet_mask);
+
+ return (OK);
+} /* hci_read_node_packet_mask */
+
+/* Send Write_Node_Packet_Mask command to the node */
+int
+hci_write_node_packet_mask(int s, int argc, char **argv)
+{
+ struct ng_btsocket_hci_raw_node_packet_mask r;
+ int m;
+
+ memset(&r, 0, sizeof(r));
+
+ switch (argc) {
+ case 1:
+ if (sscanf(argv[0], "%x", &m) != 1)
+ return (USAGE);
+
+ r.packet_mask = (m & 0xffff);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ if (ioctl(s, SIOC_HCI_RAW_NODE_SET_PACKET_MASK, &r, sizeof(r)) < 0)
+ return (ERROR);
+
+ return (OK);
+} /* hci_write_node_packet_mask */
+
+/* Send Read_Node_Role_Switch command to the node */
+int
+hci_read_node_role_switch(int s, int argc, char **argv)
+{
+ struct ng_btsocket_hci_raw_node_role_switch r;
+
+ memset(&r, 0, sizeof(r));
+ if (ioctl(s, SIOC_HCI_RAW_NODE_GET_ROLE_SWITCH, &r, sizeof(r)) < 0)
+ return (ERROR);
+
+ fprintf(stdout, "Role switch: %d\n", r.role_switch);
+
+ return (OK);
+} /* hci_read_node_role_switch */
+
+/* Send Write_Node_Role_Switch command to the node */
+int
+hci_write_node_role_switch(int s, int argc, char **argv)
+{
+ struct ng_btsocket_hci_raw_node_role_switch r;
+ int m;
+
+ memset(&r, 0, sizeof(r));
+
+ switch (argc) {
+ case 1:
+ if (sscanf(argv[0], "%d", &m) != 1)
+ return (USAGE);
+
+ r.role_switch = m? 1 : 0;
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ if (ioctl(s, SIOC_HCI_RAW_NODE_SET_ROLE_SWITCH, &r, sizeof(r)) < 0)
+ return (ERROR);
+
+ return (OK);
+} /* hci_write_node_role_switch */
+
+/* Send Read_Node_List command to the node */
+int
+hci_read_node_list(int s, int argc, char **argv)
+{
+ struct ng_btsocket_hci_raw_node_list_names r;
+ int i;
+
+ r.num_names = MAX_NODE_NUM;
+ r.names = (struct nodeinfo*)calloc(MAX_NODE_NUM, sizeof(struct nodeinfo));
+ if (r.names == NULL)
+ return (ERROR);
+
+ if (ioctl(s, SIOC_HCI_RAW_NODE_LIST_NAMES, &r, sizeof(r)) < 0) {
+ free(r.names);
+ return (ERROR);
+ }
+
+ fprintf(stdout, "Name ID Num hooks\n");
+ for (i = 0; i < r.num_names; ++i)
+ fprintf(stdout, "%-15s %08x %9d\n",
+ r.names[i].name, r.names[i].id, r.names[i].hooks);
+
+ free(r.names);
+
+ return (OK);
+} /* hci_read_node_list */
+
+struct hci_command node_commands[] = {
+{
+"read_node_state",
+"Get the HCI node state",
+&hci_read_node_state
+},
+{
+"initialize",
+"Initialize the HCI node",
+&hci_node_initialize
+},
+{
+"read_debug_level",
+"Read the HCI node debug level",
+&hci_read_debug_level
+},
+{
+"write_debug_level <level>",
+"Write the HCI node debug level",
+&hci_write_debug_level
+},
+{
+"read_node_buffer_size",
+"Read the HCI node buffer information. This will return current state of the\n"\
+"HCI buffer for the HCI node",
+&hci_read_node_buffer_size
+},
+{
+"read_node_bd_addr",
+"Read the HCI node BD_ADDR. Returns device BD_ADDR as cached by the HCI node",
+&hci_read_node_bd_addr
+},
+{
+"read_node_features",
+"Read the HCI node features. This will return list of supported features as\n" \
+"cached by the HCI node",
+&hci_read_node_features
+},
+{
+"read_node_stat",
+"Read packets and bytes counters for the HCI node",
+&hci_read_node_stat
+},
+{
+"reset_node_stat",
+"Reset packets and bytes counters for the HCI node",
+&hci_reset_node_stat
+},
+{
+"flush_neighbor_cache",
+"Flush content of the HCI node neighbor cache",
+&hci_flush_neighbor_cache
+},
+{
+"read_neighbor_cache",
+"Read content of the HCI node neighbor cache",
+&hci_read_neighbor_cache
+},
+{
+"read_connection_list",
+"Read the baseband connection descriptors list for the HCI node",
+&hci_read_connection_list
+},
+{
+"read_node_link_policy_settings_mask",
+"Read the value of the Link Policy Settinngs mask for the HCI node",
+&hci_read_node_link_policy_settings_mask
+},
+{
+"write_node_link_policy_settings_mask <policy_mask>",
+"Write the value of the Link Policy Settings mask for the HCI node. By default\n" \
+"all supported Link Policy modes (as reported by the local device features) are\n"\
+"enabled. The particular Link Policy mode is enabled if local device supports\n"\
+"it and correspinding bit in the mask was set\n\n" \
+"\t<policy_mask> - xxxx; Link Policy mask\n" \
+"\t\t0x0000 - Disable All LM Modes\n" \
+"\t\t0x0001 - Enable Master Slave Switch\n" \
+"\t\t0x0002 - Enable Hold Mode\n" \
+"\t\t0x0004 - Enable Sniff Mode\n" \
+"\t\t0x0008 - Enable Park Mode\n",
+&hci_write_node_link_policy_settings_mask
+},
+{
+"read_node_packet_mask",
+"Read the value of the Packet mask for the HCI node",
+&hci_read_node_packet_mask
+},
+{
+"write_node_packet_mask <packet_mask>",
+"Write the value of the Packet mask for the HCI node. By default all supported\n" \
+"packet types (as reported by the local device features) are enabled. The\n" \
+"particular packet type is enabled if local device supports it and corresponding\n" \
+"bit in the mask was set\n\n" \
+"\t<packet_mask> - xxxx; packet type mask\n" \
+"" \
+"\t\tACL packets\n" \
+"\t\t-----------\n" \
+"\t\t0x0008 DM1\n" \
+"\t\t0x0010 DH1\n" \
+"\t\t0x0400 DM3\n" \
+"\t\t0x0800 DH3\n" \
+"\t\t0x4000 DM5\n" \
+"\t\t0x8000 DH5\n" \
+"\n" \
+"\t\tSCO packets\n" \
+"\t\t-----------\n" \
+"\t\t0x0020 HV1\n" \
+"\t\t0x0040 HV2\n" \
+"\t\t0x0080 HV3\n",
+&hci_write_node_packet_mask
+},
+{
+"read_node_role_switch",
+"Read the value of the Role Switch parameter for the HCI node",
+&hci_read_node_role_switch
+},
+{
+"write_node_role_switch {0|1}",
+"Write the value of the Role Switch parameter for the HCI node. By default,\n" \
+"if Role Switch is supported, local device will try to perform Role Switch\n" \
+"and become Master on incoming connection. Some devices do not support Role\n" \
+"Switch and thus incomming connections from such devices will fail. Setting\n" \
+"this parameter to zero will prevent Role Switch and thus accepting device\n" \
+"will remain Slave",
+&hci_write_node_role_switch
+},
+{
+"read_node_list",
+"Get a list of HCI nodes, their Netgraph IDs and connected hooks.",
+&hci_read_node_list
+},
+{
+NULL,
+}};
+
diff --git a/usr.sbin/bluetooth/hccontrol/send_recv.c b/usr.sbin/bluetooth/hccontrol/send_recv.c
new file mode 100644
index 0000000..5c2aea9
--- /dev/null
+++ b/usr.sbin/bluetooth/hccontrol/send_recv.c
@@ -0,0 +1,184 @@
+/*
+ * send_recv.c
+ *
+ * Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: send_recv.c,v 1.2 2003/05/21 22:40:30 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/endian.h>
+#include <assert.h>
+#include <errno.h>
+#include <netgraph/bluetooth/include/ng_hci.h>
+#include <string.h>
+#include <unistd.h>
+#include "hccontrol.h"
+
+/* Send HCI request to the unit */
+int
+hci_request(int s, int opcode, char const *cp, int cp_size, char *rp, int *rp_size)
+{
+ char buffer[512];
+ int n;
+ ng_hci_cmd_pkt_t *c = (ng_hci_cmd_pkt_t *) buffer;
+ ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) buffer;
+
+ assert(rp != NULL);
+ assert(rp_size != NULL);
+ assert(*rp_size > 0);
+
+ c->type = NG_HCI_CMD_PKT;
+ c->opcode = (uint16_t) opcode;
+ c->opcode = htole16(c->opcode);
+
+ if (cp != NULL) {
+ assert(0 < cp_size && cp_size <= NG_HCI_CMD_PKT_SIZE);
+
+ c->length = (uint8_t) cp_size;
+ memcpy(buffer + sizeof(*c), cp, cp_size);
+ } else
+ c->length = 0;
+
+ if (hci_send(s, buffer, sizeof(*c) + cp_size) == ERROR)
+ return (ERROR);
+
+again:
+ n = sizeof(buffer);
+ if (hci_recv(s, buffer, &n) == ERROR)
+ return (ERROR);
+
+ if (n < sizeof(*e)) {
+ errno = EMSGSIZE;
+ return (ERROR);
+ }
+
+ if (e->type != NG_HCI_EVENT_PKT) {
+ errno = EIO;
+ return (ERROR);
+ }
+
+ switch (e->event) {
+ case NG_HCI_EVENT_COMMAND_COMPL: {
+ ng_hci_command_compl_ep *cc =
+ (ng_hci_command_compl_ep *)(e + 1);
+
+ cc->opcode = le16toh(cc->opcode);
+
+ if (cc->opcode == 0x0000 || cc->opcode != opcode)
+ goto again;
+
+ n -= (sizeof(*e) + sizeof(*cc));
+ if (n < *rp_size)
+ *rp_size = n;
+
+ memcpy(rp, buffer + sizeof(*e) + sizeof(*cc), *rp_size);
+ } break;
+
+ case NG_HCI_EVENT_COMMAND_STATUS: {
+ ng_hci_command_status_ep *cs =
+ (ng_hci_command_status_ep *)(e + 1);
+
+ cs->opcode = le16toh(cs->opcode);
+
+ if (cs->opcode == 0x0000 || cs->opcode != opcode)
+ goto again;
+
+ *rp_size = 1;
+ *rp = cs->status;
+ } break;
+
+ default:
+ goto again;
+ }
+
+ return (OK);
+} /* hci_request */
+
+/* Send simple HCI request - Just HCI command packet (no parameters) */
+int
+hci_simple_request(int s, int opcode, char *rp, int *rp_size)
+{
+ return (hci_request(s, opcode, NULL, 0, rp, rp_size));
+} /* hci_simple_request */
+
+/* Send HCI data to the unit */
+int
+hci_send(int s, char const *buffer, int size)
+{
+ assert(buffer != NULL);
+ assert(size >= sizeof(ng_hci_cmd_pkt_t));
+ assert(size <= sizeof(ng_hci_cmd_pkt_t) + NG_HCI_CMD_PKT_SIZE);
+
+ if (send(s, buffer, size, 0) < 0)
+ return (ERROR);
+
+ return (OK);
+} /* hci_send */
+
+/* Receive HCI data from the unit */
+int
+hci_recv(int s, char *buffer, int *size)
+{
+ struct timeval tv;
+ fd_set rfd;
+ int n;
+
+ assert(buffer != NULL);
+ assert(size != NULL);
+ assert(*size > sizeof(ng_hci_event_pkt_t));
+
+again:
+ FD_ZERO(&rfd);
+ FD_SET(s, &rfd);
+
+ tv.tv_sec = timeout;
+ tv.tv_usec = 0;
+
+ n = select(s + 1, &rfd, NULL, NULL, &tv);
+ if (n <= 0) {
+ if (n < 0) {
+ if (errno == EINTR)
+ goto again;
+ } else
+ errno = ETIMEDOUT;
+
+ return (ERROR);
+ }
+
+ assert(FD_ISSET(s, &rfd));
+
+ n = recv(s, buffer, *size, 0);
+ if (n < 0)
+ return (ERROR);
+
+ *size = n;
+
+ return (OK);
+} /* hci_recv */
+
diff --git a/usr.sbin/bluetooth/hccontrol/status.c b/usr.sbin/bluetooth/hccontrol/status.c
new file mode 100644
index 0000000..3364576
--- /dev/null
+++ b/usr.sbin/bluetooth/hccontrol/status.c
@@ -0,0 +1,245 @@
+/*
+ * status.c
+ *
+ * Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: status.c,v 1.2 2003/05/21 22:40:30 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <sys/endian.h>
+#include <errno.h>
+#include <netgraph/bluetooth/include/ng_hci.h>
+#include <stdio.h>
+#include "hccontrol.h"
+
+/* Send Read_Failed_Contact_Counter command to the unit */
+static int
+hci_read_failed_contact_counter(int s, int argc, char **argv)
+{
+ ng_hci_read_failed_contact_cntr_cp cp;
+ ng_hci_read_failed_contact_cntr_rp rp;
+ int n;
+
+ switch (argc) {
+ case 1:
+ /* connection handle */
+ if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff)
+ return (USAGE);
+
+ cp.con_handle = (uint16_t) (n & 0x0fff);
+ cp.con_handle = htole16(cp.con_handle);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send command */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_STATUS,
+ NG_HCI_OCF_READ_FAILED_CONTACT_CNTR),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "Connection handle: %d\n", le16toh(rp.con_handle));
+ fprintf(stdout, "Failed contact counter: %d\n", le16toh(rp.counter));
+
+ return (OK);
+} /* hci_read_failed_contact_counter */
+
+/* Send Reset_Failed_Contact_Counter command to the unit */
+static int
+hci_reset_failed_contact_counter(int s, int argc, char **argv)
+{
+ ng_hci_reset_failed_contact_cntr_cp cp;
+ ng_hci_reset_failed_contact_cntr_rp rp;
+ int n;
+
+ switch (argc) {
+ case 1:
+ /* connection handle */
+ if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff)
+ return (USAGE);
+
+ cp.con_handle = (uint16_t) (n & 0x0fff);
+ cp.con_handle = htole16(cp.con_handle);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send command */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_STATUS,
+ NG_HCI_OCF_RESET_FAILED_CONTACT_CNTR),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ return (OK);
+} /* hci_reset_failed_contact_counter */
+
+/* Sent Get_Link_Quality command to the unit */
+static int
+hci_get_link_quality(int s, int argc, char **argv)
+{
+ ng_hci_get_link_quality_cp cp;
+ ng_hci_get_link_quality_rp rp;
+ int n;
+
+ switch (argc) {
+ case 1:
+ /* connection handle */
+ if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff)
+ return (USAGE);
+
+ cp.con_handle = (uint16_t) (n & 0x0fff);
+ cp.con_handle = htole16(cp.con_handle);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send command */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_STATUS,
+ NG_HCI_OCF_GET_LINK_QUALITY),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "Connection handle: %d\n", le16toh(rp.con_handle));
+ fprintf(stdout, "Link quality: %d\n", le16toh(rp.quality));
+
+ return (OK);
+} /* hci_get_link_quality */
+
+/* Send Read_RSSI command to the unit */
+static int
+hci_read_rssi(int s, int argc, char **argv)
+{
+ ng_hci_read_rssi_cp cp;
+ ng_hci_read_rssi_rp rp;
+ int n;
+
+ switch (argc) {
+ case 1:
+ /* connection handle */
+ if (sscanf(argv[0], "%d", &n) != 1 || n <= 0 || n > 0x0eff)
+ return (USAGE);
+
+ cp.con_handle = (uint16_t) (n & 0x0fff);
+ cp.con_handle = htole16(cp.con_handle);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* send command */
+ n = sizeof(rp);
+ if (hci_request(s, NG_HCI_OPCODE(NG_HCI_OGF_STATUS,
+ NG_HCI_OCF_READ_RSSI),
+ (char const *) &cp, sizeof(cp),
+ (char *) &rp, &n) == ERROR)
+ return (ERROR);
+
+ if (rp.status != 0x00) {
+ fprintf(stdout, "Status: %s [%#02x]\n",
+ hci_status2str(rp.status), rp.status);
+ return (FAILED);
+ }
+
+ fprintf(stdout, "Connection handle: %d\n", le16toh(rp.con_handle));
+ fprintf(stdout, "RSSI: %d dB\n", (int) rp.rssi);
+
+ return (OK);
+} /* hci_read_rssi */
+
+struct hci_command status_commands[] = {
+{
+"read_failed_contact_counter <connection_handle>",
+"\nThis command will read the value for the Failed_Contact_Counter\n" \
+"parameter for a particular ACL connection to another device.\n\n" \
+"\t<connection_handle> - dddd; ACL connection handle\n",
+&hci_read_failed_contact_counter
+},
+{
+"reset_failed_contact_counter <connection_handle>",
+"\nThis command will reset the value for the Failed_Contact_Counter\n" \
+"parameter for a particular ACL connection to another device.\n\n" \
+"\t<connection_handle> - dddd; ACL connection handle\n",
+&hci_reset_failed_contact_counter
+},
+{
+"get_link_quality <connection_handle>",
+"\nThis command will return the value for the Link_Quality for the\n" \
+"specified ACL connection handle. This command will return a Link_Quality\n" \
+"value from 0-255, which represents the quality of the link between two\n" \
+"Bluetooth devices. The higher the value, the better the link quality is.\n" \
+"Each Bluetooth module vendor will determine how to measure the link quality." \
+"\n\n" \
+"\t<connection_handle> - dddd; ACL connection handle\n",
+&hci_get_link_quality
+},
+{
+"read_rssi <connection_handle>",
+"\nThis command will read the value for the difference between the\n" \
+"measured Received Signal Strength Indication (RSSI) and the limits of\n" \
+"the Golden Receive Power Range for a ACL connection handle to another\n" \
+"Bluetooth device. Any positive RSSI value returned by the Host Controller\n" \
+"indicates how many dB the RSSI is above the upper limit, any negative\n" \
+"value indicates how many dB the RSSI is below the lower limit. The value\n" \
+"zero indicates that the RSSI is inside the Golden Receive Power Range.\n\n" \
+"\t<connection_handle> - dddd; ACL connection handle\n",
+&hci_read_rssi
+},
+{
+NULL,
+}};
+
diff --git a/usr.sbin/bluetooth/hccontrol/util.c b/usr.sbin/bluetooth/hccontrol/util.c
new file mode 100644
index 0000000..1b05170
--- /dev/null
+++ b/usr.sbin/bluetooth/hccontrol/util.c
@@ -0,0 +1,421 @@
+/*
+ * util.c
+ *
+ * Copyright (c) 2001 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: util.c,v 1.2 2003/05/19 17:29:29 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <stdio.h>
+#include <string.h>
+
+#define SIZE(x) (sizeof((x))/sizeof((x)[0]))
+
+char const *
+hci_link2str(int link_type)
+{
+ static char const * const t[] = {
+ /* NG_HCI_LINK_SCO */ "SCO",
+ /* NG_HCI_LINK_ACL */ "ACL"
+ };
+
+ return (link_type >= SIZE(t)? "?" : t[link_type]);
+} /* hci_link2str */
+
+char const *
+hci_pin2str(int type)
+{
+ static char const * const t[] = {
+ /* 0x00 */ "Variable PIN",
+ /* 0x01 */ "Fixed PIN"
+ };
+
+ return (type >= SIZE(t)? "?" : t[type]);
+} /* hci_pin2str */
+
+char const *
+hci_scan2str(int scan)
+{
+ static char const * const t[] = {
+ /* 0x00 */ "No Scan enabled",
+ /* 0x01 */ "Inquiry Scan enabled. Page Scan disabled",
+ /* 0x02 */ "Inquiry Scan disabled. Page Scan enabled",
+ /* 0x03 */ "Inquiry Scan enabled. Page Scan enabled"
+ };
+
+ return (scan >= SIZE(t)? "?" : t[scan]);
+} /* hci_scan2str */
+
+char const *
+hci_encrypt2str(int encrypt, int brief)
+{
+ static char const * const t[] = {
+ /* 0x00 */ "Disabled",
+ /* 0x01 */ "Only for point-to-point packets",
+ /* 0x02 */ "Both point-to-point and broadcast packets"
+ };
+
+ static char const * const t1[] = {
+ /* NG_HCI_ENCRYPTION_MODE_NONE */ "NONE",
+ /* NG_HCI_ENCRYPTION_MODE_P2P */ "P2P",
+ /* NG_HCI_ENCRYPTION_MODE_ALL */ "ALL",
+ };
+
+ if (brief)
+ return (encrypt >= SIZE(t1)? "?" : t1[encrypt]);
+
+ return (encrypt >= SIZE(t)? "?" : t[encrypt]);
+} /* hci_encrypt2str */
+
+char const *
+hci_coding2str(int coding)
+{
+ static char const * const t[] = {
+ /* 0x00 */ "Linear",
+ /* 0x01 */ "u-law",
+ /* 0x02 */ "A-law",
+ /* 0x03 */ "Reserved"
+ };
+
+ return (coding >= SIZE(t)? "?" : t[coding]);
+} /* hci_coding2str */
+
+char const *
+hci_vdata2str(int data)
+{
+ static char const * const t[] = {
+ /* 0x00 */ "1's complement",
+ /* 0x01 */ "2's complement",
+ /* 0x02 */ "Sign-Magnitude",
+ /* 0x03 */ "Reserved"
+ };
+
+ return (data >= SIZE(t)? "?" : t[data]);
+} /* hci_vdata2str */
+
+char const *
+hci_hmode2str(int mode, char *buffer, int size)
+{
+ static char const * const t[] = {
+ /* 0x01 */ "Suspend Page Scan ",
+ /* 0x02 */ "Suspend Inquiry Scan ",
+ /* 0x04 */ "Suspend Periodic Inquiries "
+ };
+
+ if (buffer != NULL && size > 0) {
+ int n;
+
+ memset(buffer, 0, size);
+ for (n = 0; n < SIZE(t); n++) {
+ int len = strlen(buffer);
+
+ if (len >= size)
+ break;
+ if (mode & (1 << n))
+ strncat(buffer, t[n], size - len);
+ }
+ }
+
+ return (buffer);
+} /* hci_hmode2str */
+
+char const *
+hci_ver2str(int ver)
+{
+ static char const * const t[] = {
+ /* 0x00 */ "Bluetooth HCI Specification 1.0B",
+ /* 0x01 */ "Bluetooth HCI Specification 1.1",
+ /* 0x02 */ "Bluetooth HCI Specification 1.2",
+ /* 0x03 */ "Bluetooth HCI Specification 2.0",
+ /* 0x04 */ "Bluetooth HCI Specification 2.1",
+ /* 0x05 */ "Bluetooth HCI Specification 3.0",
+ /* 0x06 */ "Bluetooth HCI Specification 4.0",
+ /* 0x07 */ "Bluetooth HCI Specification 4.1",
+ /* 0x08 */ "Bluetooth HCI Specification 4.2"
+ };
+
+ return (ver >= SIZE(t)? "?" : t[ver]);
+} /* hci_ver2str */
+
+char const *
+hci_lmpver2str(int ver)
+{
+ static char const * const t[] = {
+ /* 0x00 */ "Bluetooth LMP 1.0",
+ /* 0x01 */ "Bluetooth LMP 1.1",
+ /* 0x02 */ "Bluetooth LMP 1.2",
+ /* 0x03 */ "Bluetooth LMP 2.0",
+ /* 0x04 */ "Bluetooth LMP 2.1",
+ /* 0x04 */ "Bluetooth LMP 3.0",
+ /* 0x04 */ "Bluetooth LMP 4.0",
+ /* 0x04 */ "Bluetooth LMP 4.1",
+ /* 0x04 */ "Bluetooth LMP 4.2"
+ };
+
+ return (ver >= SIZE(t)? "?" : t[ver]);
+} /* hci_lmpver2str */
+
+char const *
+hci_manufacturer2str(int m)
+{
+ static char const * const t[] = {
+ /* 0000 */ "Ericsson Technology Licensing",
+ /* 0001 */ "Nokia Mobile Phones",
+ /* 0002 */ "Intel Corp.",
+ /* 0003 */ "IBM Corp.",
+ /* 0004 */ "Toshiba Corp.",
+ /* 0005 */ "3Com",
+ /* 0006 */ "Microsoft",
+ /* 0007 */ "Lucent",
+ /* 0008 */ "Motorola",
+ /* 0009 */ "Infineon Technologies AG",
+ /* 0010 */ "Cambridge Silicon Radio",
+ /* 0011 */ "Silicon Wave",
+ /* 0012 */ "Digianswer A/S",
+ /* 0013 */ "Texas Instruments Inc.",
+ /* 0014 */ "Parthus Technologies Inc.",
+ /* 0015 */ "Broadcom Corporation",
+ /* 0016 */ "Mitel Semiconductor",
+ /* 0017 */ "Widcomm, Inc.",
+ /* 0018 */ "Zeevo, Inc.",
+ /* 0019 */ "Atmel Corporation",
+ /* 0020 */ "Mitsubishi Electric Corporation",
+ /* 0021 */ "RTX Telecom A/S",
+ /* 0022 */ "KC Technology Inc.",
+ /* 0023 */ "Newlogic",
+ /* 0024 */ "Transilica, Inc.",
+ /* 0025 */ "Rohde & Schwartz GmbH & Co. KG",
+ /* 0026 */ "TTPCom Limited",
+ /* 0027 */ "Signia Technologies, Inc.",
+ /* 0028 */ "Conexant Systems Inc.",
+ /* 0029 */ "Qualcomm",
+ /* 0030 */ "Inventel",
+ /* 0031 */ "AVM Berlin",
+ /* 0032 */ "BandSpeed, Inc.",
+ /* 0033 */ "Mansella Ltd",
+ /* 0034 */ "NEC Corporation",
+ /* 0035 */ "WavePlus Technology Co., Ltd.",
+ /* 0036 */ "Alcatel",
+ /* 0037 */ "Philips Semiconductors",
+ /* 0038 */ "C Technologies",
+ /* 0039 */ "Open Interface",
+ /* 0040 */ "R F Micro Devices",
+ /* 0041 */ "Hitachi Ltd",
+ /* 0042 */ "Symbol Technologies, Inc.",
+ /* 0043 */ "Tenovis",
+ /* 0044 */ "Macronix International Co. Ltd.",
+ /* 0045 */ "GCT Semiconductor",
+ /* 0046 */ "Norwood Systems",
+ /* 0047 */ "MewTel Technology Inc.",
+ /* 0048 */ "ST Microelectronics",
+ /* 0049 */ "Synopsys",
+ /* 0050 */ "Red-M (Communications) Ltd",
+ /* 0051 */ "Commil Ltd",
+ /* 0052 */ "Computer Access Technology Corporation (CATC)",
+ /* 0053 */ "Eclipse (HQ Espana) S.L.",
+ /* 0054 */ "Renesas Technology Corp.",
+ /* 0055 */ "Mobilian Corporation",
+ /* 0056 */ "Terax",
+ /* 0057 */ "Integrated System Solution Corp.",
+ /* 0058 */ "Matsushita Electric Industrial Co., Ltd.",
+ /* 0059 */ "Gennum Corporation",
+ /* 0060 */ "Research In Motion",
+ /* 0061 */ "IPextreme, Inc.",
+ /* 0062 */ "Systems and Chips, Inc",
+ /* 0063 */ "Bluetooth SIG, Inc",
+ /* 0064 */ "Seiko Epson Corporation"
+ };
+
+ return (m >= SIZE(t)? "?" : t[m]);
+} /* hci_manufacturer2str */
+
+char const *
+hci_features2str(uint8_t *features, char *buffer, int size)
+{
+ static char const * const t[][8] = {
+ { /* byte 0 */
+ /* 0 */ "<3-Slot> ",
+ /* 1 */ "<5-Slot> ",
+ /* 2 */ "<Encryption> ",
+ /* 3 */ "<Slot offset> ",
+ /* 4 */ "<Timing accuracy> ",
+ /* 5 */ "<Switch> ",
+ /* 6 */ "<Hold mode> ",
+ /* 7 */ "<Sniff mode> "
+ },
+ { /* byte 1 */
+ /* 0 */ "<Park mode> ",
+ /* 1 */ "<RSSI> ",
+ /* 2 */ "<Channel quality> ",
+ /* 3 */ "<SCO link> ",
+ /* 4 */ "<HV2 packets> ",
+ /* 5 */ "<HV3 packets> ",
+ /* 6 */ "<u-law log> ",
+ /* 7 */ "<A-law log> "
+ },
+ { /* byte 2 */
+ /* 0 */ "<CVSD> ",
+ /* 1 */ "<Paging scheme> ",
+ /* 2 */ "<Power control> ",
+ /* 3 */ "<Transparent SCO data> ",
+ /* 4 */ "<Flow control lag (bit0)> ",
+ /* 5 */ "<Flow control lag (bit1)> ",
+ /* 6 */ "<Flow control lag (bit2)> ",
+ /* 7 */ "<Unknown2.7> "
+ }};
+
+ if (buffer != NULL && size > 0) {
+ int n, i, len0, len1;
+
+ memset(buffer, 0, size);
+ len1 = 0;
+
+ for (n = 0; n < SIZE(t); n++) {
+ for (i = 0; i < SIZE(t[n]); i++) {
+ len0 = strlen(buffer);
+ if (len0 >= size)
+ goto done;
+
+ if (features[n] & (1 << i)) {
+ if (len1 + strlen(t[n][i]) > 60) {
+ len1 = 0;
+ buffer[len0 - 1] = '\n';
+ }
+
+ len1 += strlen(t[n][i]);
+ strncat(buffer, t[n][i], size - len0);
+ }
+ }
+ }
+ }
+done:
+ return (buffer);
+} /* hci_features2str */
+
+char const *
+hci_cc2str(int cc)
+{
+ static char const * const t[] = {
+ /* 0x00 */ "North America, Europe, Japan",
+ /* 0x01 */ "France"
+ };
+
+ return (cc >= SIZE(t)? "?" : t[cc]);
+} /* hci_cc2str */
+
+char const *
+hci_con_state2str(int state)
+{
+ static char const * const t[] = {
+ /* NG_HCI_CON_CLOSED */ "CLOSED",
+ /* NG_HCI_CON_W4_LP_CON_RSP */ "W4_LP_CON_RSP",
+ /* NG_HCI_CON_W4_CONN_COMPLETE */ "W4_CONN_COMPLETE",
+ /* NG_HCI_CON_OPEN */ "OPEN"
+ };
+
+ return (state >= SIZE(t)? "UNKNOWN" : t[state]);
+} /* hci_con_state2str */
+
+char const *
+hci_status2str(int status)
+{
+ static char const * const t[] = {
+ /* 0x00 */ "No error",
+ /* 0x01 */ "Unknown HCI command",
+ /* 0x02 */ "No connection",
+ /* 0x03 */ "Hardware failure",
+ /* 0x04 */ "Page timeout",
+ /* 0x05 */ "Authentication failure",
+ /* 0x06 */ "Key missing",
+ /* 0x07 */ "Memory full",
+ /* 0x08 */ "Connection timeout",
+ /* 0x09 */ "Max number of connections",
+ /* 0x0a */ "Max number of SCO connections to a unit",
+ /* 0x0b */ "ACL connection already exists",
+ /* 0x0c */ "Command disallowed",
+ /* 0x0d */ "Host rejected due to limited resources",
+ /* 0x0e */ "Host rejected due to security reasons",
+ /* 0x0f */ "Host rejected due to remote unit is a personal unit",
+ /* 0x10 */ "Host timeout",
+ /* 0x11 */ "Unsupported feature or parameter value",
+ /* 0x12 */ "Invalid HCI command parameter",
+ /* 0x13 */ "Other end terminated connection: User ended connection",
+ /* 0x14 */ "Other end terminated connection: Low resources",
+ /* 0x15 */ "Other end terminated connection: About to power off",
+ /* 0x16 */ "Connection terminated by local host",
+ /* 0x17 */ "Repeated attempts",
+ /* 0x18 */ "Pairing not allowed",
+ /* 0x19 */ "Unknown LMP PDU",
+ /* 0x1a */ "Unsupported remote feature",
+ /* 0x1b */ "SCO offset rejected",
+ /* 0x1c */ "SCO interval rejected",
+ /* 0x1d */ "SCO air mode rejected",
+ /* 0x1e */ "Invalid LMP parameters",
+ /* 0x1f */ "Unspecified error",
+ /* 0x20 */ "Unsupported LMP parameter value",
+ /* 0x21 */ "Role change not allowed",
+ /* 0x22 */ "LMP response timeout",
+ /* 0x23 */ "LMP error transaction collision",
+ /* 0x24 */ "LMP PSU not allowed",
+ /* 0x25 */ "Encryption mode not acceptable",
+ /* 0x26 */ "Unit key used",
+ /* 0x27 */ "QoS is not supported",
+ /* 0x28 */ "Instant passed",
+ /* 0x29 */ "Pairing with unit key not supported"
+ };
+
+ return (status >= SIZE(t)? "Unknown error" : t[status]);
+} /* hci_status2str */
+
+char const *
+hci_bdaddr2str(bdaddr_t const *ba)
+{
+ extern int numeric_bdaddr;
+ static char buffer[MAXHOSTNAMELEN];
+ struct hostent *he = NULL;
+
+ if (memcmp(ba, NG_HCI_BDADDR_ANY, sizeof(*ba)) == 0) {
+ buffer[0] = '*';
+ buffer[1] = 0;
+
+ return (buffer);
+ }
+
+ if (!numeric_bdaddr &&
+ (he = bt_gethostbyaddr((char *)ba, sizeof(*ba), AF_BLUETOOTH)) != NULL) {
+ strlcpy(buffer, he->h_name, sizeof(buffer));
+
+ return (buffer);
+ }
+
+ bt_ntoa(ba, buffer);
+
+ return (buffer);
+} /* hci_bdaddr2str */
+
diff --git a/usr.sbin/bluetooth/hcsecd/Makefile b/usr.sbin/bluetooth/hcsecd/Makefile
new file mode 100644
index 0000000..684243d
--- /dev/null
+++ b/usr.sbin/bluetooth/hcsecd/Makefile
@@ -0,0 +1,12 @@
+# $Id: Makefile,v 1.8 2003/08/14 20:06:20 max Exp $
+# $FreeBSD$
+
+PROG= hcsecd
+MAN= hcsecd.8 hcsecd.conf.5
+SRCS= hcsecd.c lexer.l parser.y
+WARNS?= 2
+CFLAGS+= -I${.CURDIR}
+
+LIBADD= bluetooth
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bluetooth/hcsecd/Makefile.depend b/usr.sbin/bluetooth/hcsecd/Makefile.depend
new file mode 100644
index 0000000..4a1ee3f
--- /dev/null
+++ b/usr.sbin/bluetooth/hcsecd/Makefile.depend
@@ -0,0 +1,26 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbluetooth \
+ lib/libc \
+ lib/libcompiler_rt \
+ usr.bin/yacc.host \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+lexer.o: lexer.c
+lexer.o: parser.h
+lexer.po: lexer.c
+lexer.po: parser.h
+parser.o: parser.c
+parser.po: parser.c
+.endif
diff --git a/usr.sbin/bluetooth/hcsecd/hcsecd.8 b/usr.sbin/bluetooth/hcsecd/hcsecd.8
new file mode 100644
index 0000000..a55e85e
--- /dev/null
+++ b/usr.sbin/bluetooth/hcsecd/hcsecd.8
@@ -0,0 +1,128 @@
+.\" Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+.\"
+.\" $Id: hcsecd.8,v 1.8 2003/09/08 18:54:20 max Exp $
+.\" $FreeBSD$
+.\"
+.Dd November 16, 2002
+.Dt HCSECD 8
+.Os
+.Sh NAME
+.Nm hcsecd
+.Nd control link keys and PIN codes for Bluetooth devices
+.Sh SYNOPSIS
+.Nm
+.Op Fl dh
+.Fl f Ar configfile
+.Sh DESCRIPTION
+The
+.Nm
+daemon controls link keys and PIN codes for Bluetooth devices.
+It opens a raw HCI socket and listens for
+.Dv Link_Key_Request ,
+.Dv PIN_Code_Request
+and
+.Dv Link_Key_Notification
+HCI events.
+.Pp
+Once a
+.Dv Link_Key_Request
+or
+.Dv PIN_Code_Request
+HCI event is received, the daemon scans the configuration file for a
+matching entry.
+The remote device BD_ADDR is used as a key.
+If no matching entry was found, the default entry will be used.
+If no default entry was found then it is assumed that no link key and no
+PIN code exists.
+For any given entry, the link key takes precedence over the PIN code.
+If a link key was not specified, the device must generate the link key from
+the PIN code.
+If an entry was found and the link key (or PIN code) exists, the
+.Dv Link_Key_Request_Reply
+(or
+.Dv PIN_Code_Request_Reply )
+command will be sent back to the device.
+Otherwise, the
+.Dv Link_Key_Request_Negative_Reply
+(or
+.Dv PIN_Code_Request_Negative_Reply )
+command will be sent back to the device.
+.Pp
+The
+.Nm
+daemon also handles HCI
+.Dv Link_Key_Notification
+events and caches link keys created from the PIN codes in memory.
+To preserve link keys between restarts the
+.Nm
+daemon dumps link keys for all entries in the
+.Pa /var/db/hcsecd.keys
+link keys file.
+If it exists, the link keys file gets processed by the
+.Nm
+daemon after it processes its main configuration file.
+The link keys file gets written every time the
+.Nm
+daemon shuts down gracefully.
+It is possible to force the
+.Nm
+daemon to re-read its main configuration file and dump the link keys file by
+sending the
+.Dv HUP
+signal to the
+.Nm
+process.
+The user is expected to not modify the link keys file by hand.
+.Pp
+The command line options are as follows:
+.Bl -tag -width indent
+.It Fl d
+Do not detach from the controlling terminal.
+.It Fl f Ar configfile
+Specify the name of the configuration file.
+The default is
+.Pa /etc/bluetooth/hcsecd.conf .
+.It Fl h
+Display usage message and exit.
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /etc/bluetooth/hcsecd.conf" -compact
+.It Pa /etc/bluetooth/hcsecd.conf
+.It Pa /var/db/hcsecd.keys
+.It Pa /var/run/hcsecd.pid
+.El
+.Sh SEE ALSO
+.Xr ng_btsocket 4 ,
+.Xr ng_hci 4 ,
+.Xr hcsecd.conf 5 ,
+.Xr hccontrol 8 ,
+.Xr hcseriald 8
+.Sh AUTHORS
+.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com
+.Sh BUGS
+Currently there is no way to select the link key or the PIN code based on
+which local device received the request.
+Everything is based on the remote device BD_ADDR.
+An interface for external helpers to obtain link keys and PIN codes is missing.
diff --git a/usr.sbin/bluetooth/hcsecd/hcsecd.c b/usr.sbin/bluetooth/hcsecd/hcsecd.c
new file mode 100644
index 0000000..15b5ca4
--- /dev/null
+++ b/usr.sbin/bluetooth/hcsecd/hcsecd.c
@@ -0,0 +1,447 @@
+/*
+ * hcsecd.c
+ *
+ * Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: hcsecd.c,v 1.6 2003/08/18 19:19:55 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <err.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include "hcsecd.h"
+
+static int done = 0;
+
+static int process_pin_code_request_event
+ (int sock, struct sockaddr_hci *addr, bdaddr_p bdaddr);
+static int process_link_key_request_event
+ (int sock, struct sockaddr_hci *addr, bdaddr_p bdaddr);
+static int send_pin_code_reply
+ (int sock, struct sockaddr_hci *addr, bdaddr_p bdaddr, char const *pin);
+static int send_link_key_reply
+ (int sock, struct sockaddr_hci *addr, bdaddr_p bdaddr, uint8_t *key);
+static int process_link_key_notification_event
+ (int sock, struct sockaddr_hci *addr, ng_hci_link_key_notification_ep *ep);
+static void sighup
+ (int s);
+static void sigint
+ (int s);
+static void usage
+ (void);
+
+/* Main */
+int
+main(int argc, char *argv[])
+{
+ int n, detach, sock;
+ socklen_t size;
+ struct sigaction sa;
+ struct sockaddr_hci addr;
+ struct ng_btsocket_hci_raw_filter filter;
+ char buffer[HCSECD_BUFFER_SIZE];
+ ng_hci_event_pkt_t *event = NULL;
+
+ detach = 1;
+
+ while ((n = getopt(argc, argv, "df:h")) != -1) {
+ switch (n) {
+ case 'd':
+ detach = 0;
+ break;
+
+ case 'f':
+ config_file = optarg;
+ break;
+
+ case 'h':
+ default:
+ usage();
+ /* NOT REACHED */
+ }
+ }
+
+ if (config_file == NULL)
+ usage();
+ /* NOT REACHED */
+
+ if (getuid() != 0)
+ errx(1, "** ERROR: You should run %s as privileged user!",
+ HCSECD_IDENT);
+
+ /* Set signal handlers */
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = sigint;
+ sa.sa_flags = SA_NOCLDWAIT;
+ if (sigaction(SIGINT, &sa, NULL) < 0)
+ err(1, "Could not sigaction(SIGINT)");
+ if (sigaction(SIGTERM, &sa, NULL) < 0)
+ err(1, "Could not sigaction(SIGINT)");
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = sighup;
+ if (sigaction(SIGHUP, &sa, NULL) < 0)
+ err(1, "Could not sigaction(SIGHUP)");
+
+ /* Open socket and set filter */
+ sock = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_HCI);
+ if (sock < 0)
+ err(1, "Could not create HCI socket");
+
+ memset(&filter, 0, sizeof(filter));
+ bit_set(filter.event_mask, NG_HCI_EVENT_PIN_CODE_REQ - 1);
+ bit_set(filter.event_mask, NG_HCI_EVENT_LINK_KEY_REQ - 1);
+ bit_set(filter.event_mask, NG_HCI_EVENT_LINK_KEY_NOTIFICATION - 1);
+
+ if (setsockopt(sock, SOL_HCI_RAW, SO_HCI_RAW_FILTER,
+ (void * const) &filter, sizeof(filter)) < 0)
+ err(1, "Could not set HCI socket filter");
+
+ if (detach && daemon(0, 0) < 0)
+ err(1, "Could not daemon()ize");
+
+ openlog(HCSECD_IDENT, LOG_NDELAY|LOG_PERROR|LOG_PID, LOG_DAEMON);
+
+ read_config_file();
+ read_keys_file();
+
+ if (detach) {
+ FILE *pid = NULL;
+
+ if ((pid = fopen(HCSECD_PIDFILE, "w")) == NULL) {
+ syslog(LOG_ERR, "Could not create PID file %s. %s (%d)",
+ HCSECD_PIDFILE, strerror(errno), errno);
+ exit(1);
+ }
+
+ fprintf(pid, "%d", getpid());
+ fclose(pid);
+ }
+
+ event = (ng_hci_event_pkt_t *) buffer;
+ while (!done) {
+ size = sizeof(addr);
+ n = recvfrom(sock, buffer, sizeof(buffer), 0,
+ (struct sockaddr *) &addr, &size);
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+
+ syslog(LOG_ERR, "Could not receive from HCI socket. " \
+ "%s (%d)", strerror(errno), errno);
+ exit(1);
+ }
+
+ if (event->type != NG_HCI_EVENT_PKT) {
+ syslog(LOG_ERR, "Received unexpected HCI packet, " \
+ "type=%#x", event->type);
+ continue;
+ }
+
+ switch (event->event) {
+ case NG_HCI_EVENT_PIN_CODE_REQ:
+ process_pin_code_request_event(sock, &addr,
+ (bdaddr_p)(event + 1));
+ break;
+
+ case NG_HCI_EVENT_LINK_KEY_REQ:
+ process_link_key_request_event(sock, &addr,
+ (bdaddr_p)(event + 1));
+ break;
+
+ case NG_HCI_EVENT_LINK_KEY_NOTIFICATION:
+ process_link_key_notification_event(sock, &addr,
+ (ng_hci_link_key_notification_ep *)(event + 1));
+ break;
+
+ default:
+ syslog(LOG_ERR, "Received unexpected HCI event, " \
+ "event=%#x", event->event);
+ break;
+ }
+ }
+
+ if (detach)
+ if (remove(HCSECD_PIDFILE) < 0)
+ syslog(LOG_ERR, "Could not remove PID file %s. %s (%d)",
+ HCSECD_PIDFILE, strerror(errno), errno);
+
+ dump_keys_file();
+ clean_config();
+ closelog();
+ close(sock);
+
+ return (0);
+}
+
+/* Process PIN_Code_Request event */
+static int
+process_pin_code_request_event(int sock, struct sockaddr_hci *addr,
+ bdaddr_p bdaddr)
+{
+ link_key_p key = NULL;
+
+ syslog(LOG_DEBUG, "Got PIN_Code_Request event from '%s', " \
+ "remote bdaddr %s", addr->hci_node,
+ bt_ntoa(bdaddr, NULL));
+
+ if ((key = get_key(bdaddr, 0)) != NULL) {
+ syslog(LOG_DEBUG, "Found matching entry, " \
+ "remote bdaddr %s, name '%s', PIN code %s",
+ bt_ntoa(&key->bdaddr, NULL),
+ (key->name != NULL)? key->name : "No name",
+ (key->pin != NULL)? "exists" : "doesn't exist");
+
+ return (send_pin_code_reply(sock, addr, bdaddr, key->pin));
+ }
+
+ syslog(LOG_DEBUG, "Could not PIN code for remote bdaddr %s",
+ bt_ntoa(bdaddr, NULL));
+
+ return (send_pin_code_reply(sock, addr, bdaddr, NULL));
+}
+
+/* Process Link_Key_Request event */
+static int
+process_link_key_request_event(int sock, struct sockaddr_hci *addr,
+ bdaddr_p bdaddr)
+{
+ link_key_p key = NULL;
+
+ syslog(LOG_DEBUG, "Got Link_Key_Request event from '%s', " \
+ "remote bdaddr %s", addr->hci_node,
+ bt_ntoa(bdaddr, NULL));
+
+ if ((key = get_key(bdaddr, 0)) != NULL) {
+ syslog(LOG_DEBUG, "Found matching entry, " \
+ "remote bdaddr %s, name '%s', link key %s",
+ bt_ntoa(&key->bdaddr, NULL),
+ (key->name != NULL)? key->name : "No name",
+ (key->key != NULL)? "exists" : "doesn't exist");
+
+ return (send_link_key_reply(sock, addr, bdaddr, key->key));
+ }
+
+ syslog(LOG_DEBUG, "Could not find link key for remote bdaddr %s",
+ bt_ntoa(bdaddr, NULL));
+
+ return (send_link_key_reply(sock, addr, bdaddr, NULL));
+}
+
+/* Send PIN_Code_[Negative]_Reply */
+static int
+send_pin_code_reply(int sock, struct sockaddr_hci *addr,
+ bdaddr_p bdaddr, char const *pin)
+{
+ uint8_t buffer[HCSECD_BUFFER_SIZE];
+ ng_hci_cmd_pkt_t *cmd = NULL;
+
+ memset(buffer, 0, sizeof(buffer));
+
+ cmd = (ng_hci_cmd_pkt_t *) buffer;
+ cmd->type = NG_HCI_CMD_PKT;
+
+ if (pin != NULL) {
+ ng_hci_pin_code_rep_cp *cp = NULL;
+
+ cmd->opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL,
+ NG_HCI_OCF_PIN_CODE_REP));
+ cmd->length = sizeof(*cp);
+
+ cp = (ng_hci_pin_code_rep_cp *)(cmd + 1);
+ memcpy(&cp->bdaddr, bdaddr, sizeof(cp->bdaddr));
+ strncpy((char *) cp->pin, pin, sizeof(cp->pin));
+ cp->pin_size = strlen((char const *) cp->pin);
+
+ syslog(LOG_DEBUG, "Sending PIN_Code_Reply to '%s' " \
+ "for remote bdaddr %s",
+ addr->hci_node, bt_ntoa(bdaddr, NULL));
+ } else {
+ ng_hci_pin_code_neg_rep_cp *cp = NULL;
+
+ cmd->opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL,
+ NG_HCI_OCF_PIN_CODE_NEG_REP));
+ cmd->length = sizeof(*cp);
+
+ cp = (ng_hci_pin_code_neg_rep_cp *)(cmd + 1);
+ memcpy(&cp->bdaddr, bdaddr, sizeof(cp->bdaddr));
+
+ syslog(LOG_DEBUG, "Sending PIN_Code_Negative_Reply to '%s' " \
+ "for remote bdaddr %s",
+ addr->hci_node, bt_ntoa(bdaddr, NULL));
+ }
+
+again:
+ if (sendto(sock, buffer, sizeof(*cmd) + cmd->length, 0,
+ (struct sockaddr *) addr, sizeof(*addr)) < 0) {
+ if (errno == EINTR)
+ goto again;
+
+ syslog(LOG_ERR, "Could not send PIN code reply to '%s' " \
+ "for remote bdaddr %s. %s (%d)",
+ addr->hci_node, bt_ntoa(bdaddr, NULL),
+ strerror(errno), errno);
+ return (-1);
+ }
+
+ return (0);
+}
+
+/* Send Link_Key_[Negative]_Reply */
+static int
+send_link_key_reply(int sock, struct sockaddr_hci *addr,
+ bdaddr_p bdaddr, uint8_t *key)
+{
+ uint8_t buffer[HCSECD_BUFFER_SIZE];
+ ng_hci_cmd_pkt_t *cmd = NULL;
+
+ memset(buffer, 0, sizeof(buffer));
+
+ cmd = (ng_hci_cmd_pkt_t *) buffer;
+ cmd->type = NG_HCI_CMD_PKT;
+
+ if (key != NULL) {
+ ng_hci_link_key_rep_cp *cp = NULL;
+
+ cmd->opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL,
+ NG_HCI_OCF_LINK_KEY_REP));
+ cmd->length = sizeof(*cp);
+
+ cp = (ng_hci_link_key_rep_cp *)(cmd + 1);
+ memcpy(&cp->bdaddr, bdaddr, sizeof(cp->bdaddr));
+ memcpy(&cp->key, key, sizeof(cp->key));
+
+ syslog(LOG_DEBUG, "Sending Link_Key_Reply to '%s' " \
+ "for remote bdaddr %s",
+ addr->hci_node, bt_ntoa(bdaddr, NULL));
+ } else {
+ ng_hci_link_key_neg_rep_cp *cp = NULL;
+
+ cmd->opcode = htole16(NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL,
+ NG_HCI_OCF_LINK_KEY_NEG_REP));
+ cmd->length = sizeof(*cp);
+
+ cp = (ng_hci_link_key_neg_rep_cp *)(cmd + 1);
+ memcpy(&cp->bdaddr, bdaddr, sizeof(cp->bdaddr));
+
+ syslog(LOG_DEBUG, "Sending Link_Key_Negative_Reply to '%s' " \
+ "for remote bdaddr %s",
+ addr->hci_node, bt_ntoa(bdaddr, NULL));
+ }
+
+again:
+ if (sendto(sock, buffer, sizeof(*cmd) + cmd->length, 0,
+ (struct sockaddr *) addr, sizeof(*addr)) < 0) {
+ if (errno == EINTR)
+ goto again;
+
+ syslog(LOG_ERR, "Could not send link key reply to '%s' " \
+ "for remote bdaddr %s. %s (%d)",
+ addr->hci_node, bt_ntoa(bdaddr, NULL),
+ strerror(errno), errno);
+ return (-1);
+ }
+
+ return (0);
+}
+
+/* Process Link_Key_Notification event */
+static int
+process_link_key_notification_event(int sock, struct sockaddr_hci *addr,
+ ng_hci_link_key_notification_ep *ep)
+{
+ link_key_p key = NULL;
+
+ syslog(LOG_DEBUG, "Got Link_Key_Notification event from '%s', " \
+ "remote bdaddr %s", addr->hci_node,
+ bt_ntoa(&ep->bdaddr, NULL));
+
+ if ((key = get_key(&ep->bdaddr, 1)) == NULL) {
+ syslog(LOG_ERR, "Could not find entry for remote bdaddr %s",
+ bt_ntoa(&ep->bdaddr, NULL));
+ return (-1);
+ }
+
+ syslog(LOG_DEBUG, "Updating link key for the entry, " \
+ "remote bdaddr %s, name '%s', link key %s",
+ bt_ntoa(&key->bdaddr, NULL),
+ (key->name != NULL)? key->name : "No name",
+ (key->key != NULL)? "exists" : "doesn't exist");
+
+ if (key->key == NULL) {
+ key->key = (uint8_t *) malloc(NG_HCI_KEY_SIZE);
+ if (key->key == NULL) {
+ syslog(LOG_ERR, "Could not allocate link key");
+ exit(1);
+ }
+ }
+
+ memcpy(key->key, &ep->key, NG_HCI_KEY_SIZE);
+
+ return (0);
+}
+
+/* Signal handlers */
+static void
+sighup(int s)
+{
+ syslog(LOG_DEBUG, "Got SIGHUP (%d)", s);
+
+ dump_keys_file();
+ read_config_file();
+ read_keys_file();
+}
+
+static void
+sigint(int s)
+{
+ syslog(LOG_DEBUG, "Got signal %d, total number of signals %d",
+ s, ++ done);
+}
+
+/* Display usage and exit */
+static void
+usage(void)
+{
+ fprintf(stderr,
+"Usage: %s [-d] -f config_file [-h]\n" \
+"Where:\n" \
+"\t-d do not detach from terminal\n" \
+"\t-f config_file use <config_file>\n" \
+"\t-h display this message\n", HCSECD_IDENT);
+
+ exit(255);
+}
+
diff --git a/usr.sbin/bluetooth/hcsecd/hcsecd.conf b/usr.sbin/bluetooth/hcsecd/hcsecd.conf
new file mode 100644
index 0000000..127ce04
--- /dev/null
+++ b/usr.sbin/bluetooth/hcsecd/hcsecd.conf
@@ -0,0 +1,64 @@
+#
+# $Id: hcsecd.conf,v 1.1 2002/11/24 20:22:39 max Exp $
+# $FreeBSD$
+#
+# HCI security daemon configuration file
+#
+# Format:
+#
+# device {
+# option value ;
+# }
+#
+# Possible options and values
+#
+# Options Values
+# ----------------------------------
+# bdaddr xx:xx:xx:xx:xx:xx ; - remote device BD_ADDR
+# name "any char" ; - to set user friendly device name
+# key 0x11223344 | nokey ; - to set link key for the device
+# pin "secret" | nopin ; - to PIN code for the device
+#
+# Notes:
+#
+# Currently there is no way to select keys/PIN code based on which
+# local device received the request. Everything is based on remote
+# device BD_ADDR.
+#
+# "nokey" means that no link key has been defined and we should
+# send Link_Key_Negative_Reply command to the device.
+#
+# "nopin" means that no PIN code has been defined and we should
+# send PIN_Code_Negative_Reply command to the device
+#
+
+# Default entry applied if no better match found
+# It MUST have 00:00:00:00:00:00 as bdaddr
+device {
+ bdaddr 00:00:00:00:00:00;
+ name "Default entry";
+ key nokey;
+ pin nopin;
+}
+
+device {
+ bdaddr 00:80:37:5e:4d:d4;
+ name "Ericsson T68 phone";
+ key nokey;
+ pin "0000"; # PIN code (string up to 16 character)
+}
+
+device {
+ bdaddr 00:01:03:fc:6e:ec;
+ name "3COM PCCARD";
+ key nokey;
+ pin "0000";
+}
+
+device {
+ bdaddr 00:11:22:33:44:55;
+ name "Dummy";
+ key 0x00112233445566778899aabbccddeeff; # 16 bytes key (hex string)
+ pin nopin;
+}
+
diff --git a/usr.sbin/bluetooth/hcsecd/hcsecd.conf.5 b/usr.sbin/bluetooth/hcsecd/hcsecd.conf.5
new file mode 100644
index 0000000..a6342aa
--- /dev/null
+++ b/usr.sbin/bluetooth/hcsecd/hcsecd.conf.5
@@ -0,0 +1,131 @@
+.\" Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+.\"
+.\" $Id: hcsecd.conf.5,v 1.1 2003/05/26 22:49:23 max Exp $
+.\" $FreeBSD$
+.\"
+.Dd May 26, 2003
+.Dt HCSECD.CONF 5
+.Os
+.Sh NAME
+.Nm hcsecd.conf
+.Nd
+.Xr hcsecd 8
+configuration file
+.Sh DESCRIPTION
+The
+.Nm
+file is the configuration file for the
+.Xr hcsecd 8
+Bluetooth link keys/PIN codes management daemon.
+.Pp
+The
+.Nm
+file is a free-form
+.Tn ASCII
+text file.
+It is parsed by the recursive-descent parser built into
+.Xr hcsecd 8 .
+The file may contain extra tabs and newlines for formatting purposes.
+Keywords in the file are case-sensitive.
+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.
+.Sh FILE FORMAT
+The
+.Nm
+file consists of a list of
+.Cm device
+entries.
+Each
+.Cm device
+entry defines a link key or PIN code for a remote Bluetooth device.
+Each remote Bluetooth device is identified by its unique BD_ADDR.
+.Pp
+The
+.Cm device
+entry
+.Pp
+.Cm device
+{
+.Cm option Ar argument ;
+.Oo
+.Cm option Ar argument ;
+.Oc
+}
+.Pp
+The following section describes all supported options and arguments.
+.Bl -tag -width indent
+.It Cm bdaddr Ar BD_ADDR
+Specify remote device BD_ADDR for the entry.
+.It Cm name Ar device_name
+Specify user friendly name for the entry.
+Name is a string in straight double quotes.
+.It Cm key Ar link_key
+Specify link key for the entry.
+Link key is hexadecimal string up to 32 characters in length starting with
+.Ql 0x .
+.It Cm key nokey
+Specify no link key for the entry.
+.It Cm pin Ar PIN_code
+Specify PIN code for the entry.
+PIN code is a string up to 16 characters in length in straight double quotes.
+.It Cm pin nopin
+Specify no PIN code for the entry.
+.El
+.Sh EXAMPLES
+A sample
+.Nm
+file:
+.Bd -literal
+# Default entry is applied if no better match found
+# It MUST have 00:00:00:00:00:00 as bdaddr
+device {
+ bdaddr 00:00:00:00:00:00;
+ name "Default entry";
+ key nokey;
+ pin nopin;
+}
+
+# Ericsson T68 phone
+device {
+ bdaddr 00:80:37:5e:4d:d4;
+ name "Ericsson T68 phone";
+ key nokey;
+ pin "0000"; # PIN code
+}
+
+# Dummy device
+device {
+ bdaddr 00:11:22:33:44:55;
+ name "Dummy";
+ key 0x00112233445566778899aabbccddeeff; # 16 bytes key
+ pin nopin;
+}
+.Ed
+.Sh SEE ALSO
+.Xr hcsecd 8
+.Sh AUTHORS
+.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com
diff --git a/usr.sbin/bluetooth/hcsecd/hcsecd.h b/usr.sbin/bluetooth/hcsecd/hcsecd.h
new file mode 100644
index 0000000..acfc887
--- /dev/null
+++ b/usr.sbin/bluetooth/hcsecd/hcsecd.h
@@ -0,0 +1,65 @@
+/*
+ * hcsecd.h
+ *
+ * Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: hcsecd.h,v 1.3 2003/09/08 18:54:21 max Exp $
+ * $FreeBSD$
+ */
+
+#ifndef _HCSECD_H_
+#define _HCSECD_H_ 1
+
+#define HCSECD_BUFFER_SIZE 512
+#define HCSECD_IDENT "hcsecd"
+#define HCSECD_PIDFILE "/var/run/" HCSECD_IDENT ".pid"
+#define HCSECD_KEYSFILE "/var/db/" HCSECD_IDENT ".keys"
+
+struct link_key
+{
+ bdaddr_t bdaddr; /* remote device BDADDR */
+ char *name; /* remote device name */
+ uint8_t *key; /* link key (or NULL if no key) */
+ char *pin; /* pin (or NULL if no pin) */
+ LIST_ENTRY(link_key) next; /* link to the next */
+};
+typedef struct link_key link_key_t;
+typedef struct link_key * link_key_p;
+
+extern char *config_file;
+
+#if __config_debug__
+void dump_config (void);
+#endif
+
+void read_config_file(void);
+void clean_config (void);
+link_key_p get_key (bdaddr_p bdaddr, int exact_match);
+
+int read_keys_file (void);
+int dump_keys_file (void);
+
+#endif /* ndef _HCSECD_H_ */
+
diff --git a/usr.sbin/bluetooth/hcsecd/lexer.l b/usr.sbin/bluetooth/hcsecd/lexer.l
new file mode 100644
index 0000000..578b42f
--- /dev/null
+++ b/usr.sbin/bluetooth/hcsecd/lexer.l
@@ -0,0 +1,95 @@
+%{
+/*
+ * lexer.l
+ *
+ * Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: lexer.l,v 1.1 2002/11/24 20:22:39 max Exp $
+ * $FreeBSD$
+ */
+
+#include <string.h>
+#include "parser.h"
+%}
+
+%option yylineno noyywrap nounput noinput
+
+delim [ \t\n]
+ws {delim}+
+empty {delim}*
+comment \#.*
+
+hexdigit [0-9a-fA-F]
+hexbyte {hexdigit}{hexdigit}
+
+device_word device
+bdaddr_word bdaddr
+name_word name
+key_word key
+nokey_word nokey
+pin_word pin
+nopin_word nopin
+
+bdaddrstring {hexbyte}:{hexbyte}:{hexbyte}:{hexbyte}:{hexbyte}:{hexbyte}
+hexstring 0x{hexbyte}+
+string \".+\"
+
+%%
+
+\; return (';');
+\: return (':');
+\{ return ('{');
+\} return ('}');
+
+{ws} ;
+{empty} ;
+{comment} ;
+
+{device_word} return (T_DEVICE);
+{bdaddr_word} return (T_BDADDR);
+{name_word} return (T_NAME);
+{key_word} return (T_KEY);
+{nokey_word} return (T_NOKEY);
+{pin_word} return (T_PIN);
+{nopin_word} return (T_NOPIN);
+
+{bdaddrstring} {
+ yylval.string = yytext;
+ return (T_BDADDRSTRING);
+ }
+
+{hexstring} {
+ yylval.string = &yytext[2];
+ return (T_HEXSTRING);
+ }
+
+{string} {
+ yytext[strlen(yytext) - 1] = 0;
+ yylval.string = &yytext[1];
+ return (T_STRING);
+ }
+
+%%
+
diff --git a/usr.sbin/bluetooth/hcsecd/parser.y b/usr.sbin/bluetooth/hcsecd/parser.y
new file mode 100644
index 0000000..cfaeb02
--- /dev/null
+++ b/usr.sbin/bluetooth/hcsecd/parser.y
@@ -0,0 +1,435 @@
+%{
+/*
+ * parser.y
+ *
+ * Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: parser.y,v 1.5 2003/06/07 21:22:30 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/fcntl.h>
+#include <sys/queue.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include "hcsecd.h"
+
+ int yyparse (void);
+ int yylex (void);
+
+static void free_key (link_key_p key);
+static int hexa2int4(char *a);
+static int hexa2int8(char *a);
+
+extern int yylineno;
+static LIST_HEAD(, link_key) link_keys;
+ char *config_file = "/etc/bluetooth/hcsecd.conf";
+
+static link_key_p key = NULL;
+%}
+
+%union {
+ char *string;
+}
+
+%token <string> T_BDADDRSTRING T_HEXSTRING T_STRING
+%token T_DEVICE T_BDADDR T_NAME T_KEY T_PIN T_NOKEY T_NOPIN T_JUNK
+
+%%
+
+config: line
+ | config line
+ ;
+
+line: T_DEVICE
+ {
+ key = (link_key_p) malloc(sizeof(*key));
+ if (key == NULL) {
+ syslog(LOG_ERR, "Could not allocate new " \
+ "config entry");
+ exit(1);
+ }
+
+ memset(key, 0, sizeof(*key));
+ }
+ '{' options '}'
+ {
+ if (get_key(&key->bdaddr, 1) != NULL) {
+ syslog(LOG_ERR, "Ignoring duplicated entry " \
+ "for bdaddr %s",
+ bt_ntoa(&key->bdaddr, NULL));
+ free_key(key);
+ } else
+ LIST_INSERT_HEAD(&link_keys, key, next);
+
+ key = NULL;
+ }
+ ;
+
+options: option ';'
+ | options option ';'
+ ;
+
+option: bdaddr
+ | name
+ | key
+ | pin
+ ;
+
+bdaddr: T_BDADDR T_BDADDRSTRING
+ {
+ if (!bt_aton($2, &key->bdaddr)) {
+ syslog(LOG_ERR, "Cound not parse BD_ADDR " \
+ "'%s'", $2);
+ exit(1);
+ }
+ }
+ ;
+
+name: T_NAME T_STRING
+ {
+ if (key->name != NULL)
+ free(key->name);
+
+ key->name = strdup($2);
+ if (key->name == NULL) {
+ syslog(LOG_ERR, "Could not allocate new " \
+ "device name");
+ exit(1);
+ }
+ }
+ ;
+
+key: T_KEY T_HEXSTRING
+ {
+ int i, len;
+
+ if (key->key != NULL)
+ free(key->key);
+
+ key->key = (uint8_t *) malloc(NG_HCI_KEY_SIZE);
+ if (key->key == NULL) {
+ syslog(LOG_ERR, "Could not allocate new " \
+ "link key");
+ exit(1);
+ }
+
+ memset(key->key, 0, NG_HCI_KEY_SIZE);
+
+ len = strlen($2) / 2;
+ if (len > NG_HCI_KEY_SIZE)
+ len = NG_HCI_KEY_SIZE;
+
+ for (i = 0; i < len; i ++)
+ key->key[i] = hexa2int8((char *)($2) + 2*i);
+ }
+ | T_KEY T_NOKEY
+ {
+ if (key->key != NULL)
+ free(key->key);
+
+ key->key = NULL;
+ }
+ ;
+
+pin: T_PIN T_STRING
+ {
+ if (key->pin != NULL)
+ free(key->pin);
+
+ key->pin = strdup($2);
+ if (key->pin == NULL) {
+ syslog(LOG_ERR, "Could not allocate new " \
+ "PIN code");
+ exit(1);
+ }
+ }
+ | T_PIN T_NOPIN
+ {
+ if (key->pin != NULL)
+ free(key->pin);
+
+ key->pin = NULL;
+ }
+ ;
+
+%%
+
+/* Display parser error message */
+void
+yyerror(char const *message)
+{
+ syslog(LOG_ERR, "%s in line %d", message, yylineno);
+}
+
+/* Re-read config file */
+void
+read_config_file(void)
+{
+ extern FILE *yyin;
+
+ if (config_file == NULL) {
+ syslog(LOG_ERR, "Unknown config file name!");
+ exit(1);
+ }
+
+ if ((yyin = fopen(config_file, "r")) == NULL) {
+ syslog(LOG_ERR, "Could not open config file '%s'. %s (%d)",
+ config_file, strerror(errno), errno);
+ exit(1);
+ }
+
+ clean_config();
+ if (yyparse() < 0) {
+ syslog(LOG_ERR, "Could not parse config file '%s'",config_file);
+ exit(1);
+ }
+
+ fclose(yyin);
+ yyin = NULL;
+
+#if __config_debug__
+ dump_config();
+#endif
+}
+
+/* Clean config */
+void
+clean_config(void)
+{
+ link_key_p key = NULL;
+
+ while ((key = LIST_FIRST(&link_keys)) != NULL) {
+ LIST_REMOVE(key, next);
+ free_key(key);
+ }
+}
+
+/* Find link key entry in the list. Return exact or default match */
+link_key_p
+get_key(bdaddr_p bdaddr, int exact_match)
+{
+ link_key_p key = NULL, defkey = NULL;
+
+ LIST_FOREACH(key, &link_keys, next) {
+ if (memcmp(bdaddr, &key->bdaddr, sizeof(key->bdaddr)) == 0)
+ break;
+
+ if (!exact_match)
+ if (memcmp(NG_HCI_BDADDR_ANY, &key->bdaddr,
+ sizeof(key->bdaddr)) == 0)
+ defkey = key;
+ }
+
+ return ((key != NULL)? key : defkey);
+}
+
+#if __config_debug__
+/* Dump config */
+void
+dump_config(void)
+{
+ link_key_p key = NULL;
+ char buffer[64];
+
+ LIST_FOREACH(key, &link_keys, next) {
+ if (key->key != NULL)
+ snprintf(buffer, sizeof(buffer),
+"0x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
+ key->key[0], key->key[1], key->key[2],
+ key->key[3], key->key[4], key->key[5],
+ key->key[6], key->key[7], key->key[8],
+ key->key[9], key->key[10], key->key[11],
+ key->key[12], key->key[13], key->key[14],
+ key->key[15]);
+
+ syslog(LOG_DEBUG,
+"device %s " \
+"bdaddr %s " \
+"pin %s " \
+"key %s",
+ (key->name != NULL)? key->name : "noname",
+ bt_ntoa(&key->bdaddr, NULL),
+ (key->pin != NULL)? key->pin : "nopin",
+ (key->key != NULL)? buffer : "nokey");
+ }
+}
+#endif
+
+/* Read keys file */
+int
+read_keys_file(void)
+{
+ FILE *f = NULL;
+ link_key_t *key = NULL;
+ char buf[HCSECD_BUFFER_SIZE], *p = NULL, *cp = NULL;
+ bdaddr_t bdaddr;
+ int i, len;
+
+ if ((f = fopen(HCSECD_KEYSFILE, "r")) == NULL) {
+ if (errno == ENOENT)
+ return (0);
+
+ syslog(LOG_ERR, "Could not open keys file %s. %s (%d)\n",
+ HCSECD_KEYSFILE, strerror(errno), errno);
+
+ return (-1);
+ }
+
+ while ((p = fgets(buf, sizeof(buf), f)) != NULL) {
+ if (*p == '#')
+ continue;
+ if ((cp = strpbrk(p, " ")) == NULL)
+ continue;
+
+ *cp++ = '\0';
+
+ if (!bt_aton(p, &bdaddr))
+ continue;
+
+ if ((key = get_key(&bdaddr, 1)) == NULL)
+ continue;
+
+ if (key->key == NULL) {
+ key->key = (uint8_t *) malloc(NG_HCI_KEY_SIZE);
+ if (key->key == NULL) {
+ syslog(LOG_ERR, "Could not allocate link key");
+ exit(1);
+ }
+ }
+
+ memset(key->key, 0, NG_HCI_KEY_SIZE);
+
+ len = strlen(cp) / 2;
+ if (len > NG_HCI_KEY_SIZE)
+ len = NG_HCI_KEY_SIZE;
+
+ for (i = 0; i < len; i ++)
+ key->key[i] = hexa2int8(cp + 2*i);
+
+ syslog(LOG_DEBUG, "Restored link key for the entry, " \
+ "remote bdaddr %s, name '%s'",
+ bt_ntoa(&key->bdaddr, NULL),
+ (key->name != NULL)? key->name : "No name");
+ }
+
+ fclose(f);
+
+ return (0);
+}
+
+/* Dump keys file */
+int
+dump_keys_file(void)
+{
+ link_key_p key = NULL;
+ char tmp[PATH_MAX], buf[HCSECD_BUFFER_SIZE];
+ int f;
+
+ snprintf(tmp, sizeof(tmp), "%s.tmp", HCSECD_KEYSFILE);
+ if ((f = open(tmp, O_RDWR|O_CREAT|O_TRUNC|O_EXCL, 0600)) < 0) {
+ syslog(LOG_ERR, "Could not create temp keys file %s. %s (%d)\n",
+ tmp, strerror(errno), errno);
+ return (-1);
+ }
+
+ LIST_FOREACH(key, &link_keys, next) {
+ if (key->key == NULL)
+ continue;
+
+ snprintf(buf, sizeof(buf),
+"%s %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
+ bt_ntoa(&key->bdaddr, NULL),
+ key->key[0], key->key[1], key->key[2], key->key[3],
+ key->key[4], key->key[5], key->key[6], key->key[7],
+ key->key[8], key->key[9], key->key[10], key->key[11],
+ key->key[12], key->key[13], key->key[14], key->key[15]);
+
+ if (write(f, buf, strlen(buf)) < 0) {
+ syslog(LOG_ERR, "Could not write temp keys file. " \
+ "%s (%d)\n", strerror(errno), errno);
+ break;
+ }
+ }
+
+ close(f);
+
+ if (rename(tmp, HCSECD_KEYSFILE) < 0) {
+ syslog(LOG_ERR, "Could not rename(%s, %s). %s (%d)\n",
+ tmp, HCSECD_KEYSFILE, strerror(errno), errno);
+ unlink(tmp);
+ return (-1);
+ }
+
+ return (0);
+}
+
+/* Free key entry */
+static void
+free_key(link_key_p key)
+{
+ if (key->name != NULL)
+ free(key->name);
+ if (key->key != NULL)
+ free(key->key);
+ if (key->pin != NULL)
+ free(key->pin);
+
+ memset(key, 0, sizeof(*key));
+ free(key);
+}
+
+/* Convert hex ASCII to int4 */
+static int
+hexa2int4(char *a)
+{
+ if ('0' <= *a && *a <= '9')
+ return (*a - '0');
+
+ if ('A' <= *a && *a <= 'F')
+ return (*a - 'A' + 0xa);
+
+ if ('a' <= *a && *a <= 'f')
+ return (*a - 'a' + 0xa);
+
+ syslog(LOG_ERR, "Invalid hex character: '%c' (%#x)", *a, *a);
+ exit(1);
+}
+
+/* Convert hex ASCII to int8 */
+static int
+hexa2int8(char *a)
+{
+ return ((hexa2int4(a) << 4) | hexa2int4(a + 1));
+}
+
diff --git a/usr.sbin/bluetooth/hcseriald/Makefile b/usr.sbin/bluetooth/hcseriald/Makefile
new file mode 100644
index 0000000..e02e1ae
--- /dev/null
+++ b/usr.sbin/bluetooth/hcseriald/Makefile
@@ -0,0 +1,10 @@
+# $Id: Makefile,v 1.5 2003/08/14 20:06:21 max Exp $
+# $FreeBSD$
+
+PROG= hcseriald
+MAN= hcseriald.8
+WARNS?= 2
+
+LIBADD= netgraph
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bluetooth/hcseriald/Makefile.depend b/usr.sbin/bluetooth/hcseriald/Makefile.depend
new file mode 100644
index 0000000..92ae034
--- /dev/null
+++ b/usr.sbin/bluetooth/hcseriald/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libnetgraph \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bluetooth/hcseriald/hcseriald.8 b/usr.sbin/bluetooth/hcseriald/hcseriald.8
new file mode 100644
index 0000000..c2d94ef
--- /dev/null
+++ b/usr.sbin/bluetooth/hcseriald/hcseriald.8
@@ -0,0 +1,86 @@
+.\" Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+.\"
+.\" $Id: hcseriald.8,v 1.3 2003/05/21 00:47:26 max Exp $
+.\" $FreeBSD$
+.\"
+.Dd June 14, 2002
+.Dt HCSERIALD 8
+.Os
+.Sh NAME
+.Nm hcseriald
+.Nd supervise serial Bluetooth devices
+.Sh SYNOPSIS
+.Nm
+.Op Fl dh
+.Fl f Ar device
+.Fl n Ar node_name
+.Op Fl s Ar speed
+.Sh DESCRIPTION
+The
+.Nm
+utility handles serial Bluetooth devices.
+It does one simple thing:
+it opens the specified serial device, sets the device parameters, and pushes
+the
+.Dv H4
+line discipline.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl d
+Do not disassociate from the controlling terminal, i.e., run in foreground.
+.It Fl f Ar device
+Callout device name.
+Example:
+.Fl f Pa /dev/cuau0 .
+.It Fl h
+Display usage message and exit.
+.It Fl n Ar node_name
+Set H4 Netgraph node name.
+Example:
+.Fl n Li sio0 .
+.It Fl s Ar speed
+Set serial device speed to
+.Ar speed .
+Example:
+.Fl s Li 115200 .
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /var/run/hcserial. Ns Ar * Ns Pa .pid" -compact
+.It Pa /var/run/hcserial. Ns Ar * Ns Pa .pid
+Process ID of the currently running
+.Nm
+daemon.
+Where
+.Ar *
+is an H4 Netgraph node name.
+.El
+.Sh SEE ALSO
+.Xr ng_h4 4 ,
+.Xr ng_hci 4 ,
+.Xr tty 4 ,
+.Xr hccontrol 8
+.Sh AUTHORS
+.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com
diff --git a/usr.sbin/bluetooth/hcseriald/hcseriald.c b/usr.sbin/bluetooth/hcseriald/hcseriald.c
new file mode 100644
index 0000000..440b0d4
--- /dev/null
+++ b/usr.sbin/bluetooth/hcseriald/hcseriald.c
@@ -0,0 +1,268 @@
+/*
+ * hcseriald.c
+ *
+ * Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: hcseriald.c,v 1.3 2003/05/21 22:40:32 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#include <netgraph/ng_message.h>
+#include <netgraph.h>
+#include <netgraph/bluetooth/include/ng_h4.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <termios.h>
+#include <unistd.h>
+
+/* Prototypes */
+static int open_device (char const *, speed_t, char const *);
+static void sighandler (int);
+static void usage ();
+
+static char const * const hcseriald = "hcseriald";
+static int done = 0;
+
+int
+main(int argc, char *argv[])
+{
+ char *device = NULL, *name = NULL;
+ speed_t speed = 115200;
+ int n, detach = 1;
+ char p[FILENAME_MAX];
+ FILE *f = NULL;
+ struct sigaction sa;
+
+ /* Process command line arguments */
+ while ((n = getopt(argc, argv, "df:n:s:h")) != -1) {
+ switch (n) {
+ case 'd':
+ detach = 0;
+ break;
+
+ case 'f':
+ device = optarg;
+ break;
+
+ case 'n':
+ name = optarg;
+ break;
+
+ case 's':
+ speed = atoi(optarg);
+ if (speed < 0)
+ usage(argv[0]);
+ break;
+
+ case 'h':
+ default:
+ usage(argv[0]);
+ break;
+ }
+ }
+
+ if (device == NULL || name == NULL)
+ usage(argv[0]);
+
+ openlog(hcseriald, LOG_PID | LOG_NDELAY, LOG_USER);
+
+ /* Open device */
+ n = open_device(device, speed, name);
+
+ if (detach && daemon(0, 0) < 0) {
+ syslog(LOG_ERR, "Could not daemon(0, 0). %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ /* Write PID file */
+ snprintf(p, sizeof(p), "/var/run/%s.%s.pid", hcseriald, name);
+ f = fopen(p, "w");
+ if (f == NULL) {
+ syslog(LOG_ERR, "Could not fopen(%s). %s (%d)",
+ p, strerror(errno), errno);
+ exit(1);
+ }
+ fprintf(f, "%d", getpid());
+ fclose(f);
+
+ /* Install signal handler */
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = sighandler;
+
+ if (sigaction(SIGTERM, &sa, NULL) < 0) {
+ syslog(LOG_ERR, "Could not sigaction(SIGTERM). %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ if (sigaction(SIGHUP, &sa, NULL) < 0) {
+ syslog(LOG_ERR, "Could not sigaction(SIGHUP). %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ if (sigaction(SIGINT, &sa, NULL) < 0) {
+ syslog(LOG_ERR, "Could not sigaction(SIGINT). %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ /* Keep running */
+ while (!done)
+ select(0, NULL, NULL, NULL, NULL);
+
+ /* Remove PID file and close device */
+ unlink(p);
+ close(n);
+ closelog();
+
+ return (0);
+} /* main */
+
+/* Open terminal, set settings, push H4 line discipline and set node name */
+static int
+open_device(char const *device, speed_t speed, char const *name)
+{
+ int fd, disc, cs, ds;
+ struct termios t;
+ struct nodeinfo ni;
+ struct ngm_name n;
+ char p[NG_NODESIZ];
+
+ /* Open terminal device and setup H4 line discipline */
+ fd = open(device, O_RDWR|O_NOCTTY);
+ if (fd < 0) {
+ syslog(LOG_ERR, "Could not open(%s). %s (%d)",
+ device, strerror(errno), errno);
+ exit(1);
+ }
+
+ tcflush(fd, TCIOFLUSH);
+
+ if (tcgetattr(fd, &t) < 0) {
+ syslog(LOG_ERR, "Could not tcgetattr(%s). %s (%d)",
+ device, strerror(errno), errno);
+ exit(1);
+ }
+
+ cfmakeraw(&t);
+
+ t.c_cflag |= CLOCAL; /* clocal */
+ t.c_cflag &= ~CSIZE; /* cs8 */
+ t.c_cflag |= CS8; /* cs8 */
+ t.c_cflag &= ~PARENB; /* -parenb */
+ t.c_cflag &= ~CSTOPB; /* -cstopb */
+ t.c_cflag |= CRTSCTS; /* crtscts */
+
+ if (tcsetattr(fd, TCSANOW, &t) < 0) {
+ syslog(LOG_ERR, "Could not tcsetattr(%s). %s (%d)",
+ device, strerror(errno), errno);
+ exit(1);
+ }
+
+ tcflush(fd, TCIOFLUSH);
+
+ if (cfsetspeed(&t, speed) < 0) {
+ syslog(LOG_ERR, "Could not cfsetspeed(%s). %s (%d)",
+ device, strerror(errno), errno);
+ exit(1);
+ }
+
+ if (tcsetattr(fd, TCSANOW, &t) < 0) {
+ syslog(LOG_ERR, "Could not tcsetattr(%s). %s (%d)",
+ device, strerror(errno), errno);
+ exit(1);
+ }
+
+ disc = H4DISC;
+ if (ioctl(fd, TIOCSETD, &disc) < 0) {
+ syslog(LOG_ERR, "Could not ioctl(%s, TIOCSETD, %d). %s (%d)",
+ device, disc, strerror(errno), errno);
+ exit(1);
+ }
+
+ /* Get default name of the Netgraph node */
+ memset(&ni, 0, sizeof(ni));
+ if (ioctl(fd, NGIOCGINFO, &ni) < 0) {
+ syslog(LOG_ERR, "Could not ioctl(%d, NGIOGINFO). %s (%d)",
+ fd, strerror(errno), errno);
+ exit(1);
+ }
+
+ /* Assign new name to the Netgraph node */
+ snprintf(p, sizeof(p), "%s:", ni.name);
+ snprintf(n.name, sizeof(n.name), "%s", name);
+
+ if (NgMkSockNode(NULL, &cs, &ds) < 0) {
+ syslog(LOG_ERR, "Could not NgMkSockNode(). %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ if (NgSendMsg(cs, p, NGM_GENERIC_COOKIE, NGM_NAME, &n, sizeof(n)) < 0) {
+ syslog(LOG_ERR, "Could not NgSendMsg(%d, %s, NGM_NAME, %s). " \
+ "%s (%d)", cs, p, n.name, strerror(errno), errno);
+ exit(1);
+ }
+
+ close(cs);
+ close(ds);
+
+ return (fd);
+} /* open_device */
+
+/* Signal handler */
+static void
+sighandler(int s)
+{
+ done = 1;
+} /* sighandler */
+
+/* Usage */
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: %s -f device -n node_name [-s speed -d -h]\n" \
+ "Where:\n" \
+ "\t-f device tty device name, ex. /dev/cuau1\n" \
+ "\t-n node_name set Netgraph node name to node_name\n" \
+ "\t-s speed set tty speed, ex. 115200\n" \
+ "\t-d run in foreground\n" \
+ "\t-h display this message\n",
+ hcseriald);
+ exit(255);
+} /* usage */
+
diff --git a/usr.sbin/bluetooth/l2control/Makefile b/usr.sbin/bluetooth/l2control/Makefile
new file mode 100644
index 0000000..8f17e02
--- /dev/null
+++ b/usr.sbin/bluetooth/l2control/Makefile
@@ -0,0 +1,11 @@
+# $Id: Makefile,v 1.7 2003/08/14 20:06:22 max Exp $
+# $FreeBSD$
+
+PROG= l2control
+MAN= l2control.8
+SRCS= l2cap.c l2control.c
+WARNS?= 2
+
+LIBADD= bluetooth
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bluetooth/l2control/Makefile.depend b/usr.sbin/bluetooth/l2control/Makefile.depend
new file mode 100644
index 0000000..5d21038
--- /dev/null
+++ b/usr.sbin/bluetooth/l2control/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbluetooth \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bluetooth/l2control/l2cap.c b/usr.sbin/bluetooth/l2control/l2cap.c
new file mode 100644
index 0000000..44009ef
--- /dev/null
+++ b/usr.sbin/bluetooth/l2control/l2cap.c
@@ -0,0 +1,314 @@
+/*
+ * l2cap.c
+ *
+ * Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: l2cap.c,v 1.5 2003/05/16 19:52:37 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/ioctl.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "l2control.h"
+
+#define SIZE(x) (sizeof((x))/sizeof((x)[0]))
+
+/* Print BDADDR */
+static char *
+bdaddrpr(bdaddr_t const *ba)
+{
+ extern int numeric_bdaddr;
+ static char str[24];
+ struct hostent *he = NULL;
+
+ if (memcmp(ba, NG_HCI_BDADDR_ANY, sizeof(*ba)) == 0) {
+ str[0] = '*';
+ str[1] = 0;
+
+ return (str);
+ }
+
+ if (!numeric_bdaddr &&
+ (he = bt_gethostbyaddr((char *)ba, sizeof(*ba), AF_BLUETOOTH)) != NULL) {
+ strlcpy(str, he->h_name, sizeof(str));
+
+ return (str);
+ }
+
+ bt_ntoa(ba, str);
+
+ return (str);
+} /* bdaddrpr */
+
+/* Send read_node_flags command to the node */
+static int
+l2cap_read_node_flags(int s, int argc, char **argv)
+{
+ struct ng_btsocket_l2cap_raw_node_flags r;
+
+ memset(&r, 0, sizeof(r));
+ if (ioctl(s, SIOC_L2CAP_NODE_GET_FLAGS, &r, sizeof(r)) < 0)
+ return (ERROR);
+
+ fprintf(stdout, "Connectionless traffic flags:\n");
+ fprintf(stdout, "\tSDP: %s\n",
+ (r.flags & NG_L2CAP_CLT_SDP_DISABLED)? "disabled" : "enabled");
+ fprintf(stdout, "\tRFCOMM: %s\n",
+ (r.flags & NG_L2CAP_CLT_RFCOMM_DISABLED)? "disabled":"enabled");
+ fprintf(stdout, "\tTCP: %s\n",
+ (r.flags & NG_L2CAP_CLT_TCP_DISABLED)? "disabled" : "enabled");
+
+ return (OK);
+} /* l2cap_read_node_flags */
+
+/* Send read_debug_level command to the node */
+static int
+l2cap_read_debug_level(int s, int argc, char **argv)
+{
+ struct ng_btsocket_l2cap_raw_node_debug r;
+
+ memset(&r, 0, sizeof(r));
+ if (ioctl(s, SIOC_L2CAP_NODE_GET_DEBUG, &r, sizeof(r)) < 0)
+ return (ERROR);
+
+ fprintf(stdout, "Debug level: %d\n", r.debug);
+
+ return (OK);
+} /* l2cap_read_debug_level */
+
+/* Send write_debug_level command to the node */
+static int
+l2cap_write_debug_level(int s, int argc, char **argv)
+{
+ struct ng_btsocket_l2cap_raw_node_debug r;
+
+ memset(&r, 0, sizeof(r));
+ switch (argc) {
+ case 1:
+ r.debug = atoi(argv[0]);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ if (ioctl(s, SIOC_L2CAP_NODE_SET_DEBUG, &r, sizeof(r)) < 0)
+ return (ERROR);
+
+ return (OK);
+} /* l2cap_write_debug_level */
+
+/* Send read_connection_list command to the node */
+static int
+l2cap_read_connection_list(int s, int argc, char **argv)
+{
+ static char const * const state[] = {
+ /* NG_L2CAP_CON_CLOSED */ "CLOSED",
+ /* NG_L2CAP_W4_LP_CON_CFM */ "W4_LP_CON_CFM",
+ /* NG_L2CAP_CON_OPEN */ "OPEN"
+ };
+#define con_state2str(x) ((x) >= SIZE(state)? "UNKNOWN" : state[(x)])
+
+ struct ng_btsocket_l2cap_raw_con_list r;
+ int n, error = OK;
+
+ memset(&r, 0, sizeof(r));
+ r.num_connections = NG_L2CAP_MAX_CON_NUM;
+ r.connections = calloc(NG_L2CAP_MAX_CON_NUM,
+ sizeof(ng_l2cap_node_con_ep));
+ if (r.connections == NULL) {
+ errno = ENOMEM;
+ return (ERROR);
+ }
+
+ if (ioctl(s, SIOC_L2CAP_NODE_GET_CON_LIST, &r, sizeof(r)) < 0) {
+ error = ERROR;
+ goto out;
+ }
+
+ fprintf(stdout, "L2CAP connections:\n");
+ fprintf(stdout,
+"Remote BD_ADDR Handle Flags Pending State\n");
+ for (n = 0; n < r.num_connections; n++) {
+ fprintf(stdout,
+ "%-17.17s " \
+ "%6d " \
+ "%c%c%c%c%c " \
+ "%7d " \
+ "%s\n",
+ bdaddrpr(&r.connections[n].remote),
+ r.connections[n].con_handle,
+ ((r.connections[n].flags & NG_L2CAP_CON_OUTGOING)? 'O' : 'I'),
+ ((r.connections[n].flags & NG_L2CAP_CON_LP_TIMO)? 'L' : ' '),
+ ((r.connections[n].flags & NG_L2CAP_CON_AUTO_DISCON_TIMO)? 'D' : ' '),
+ ((r.connections[n].flags & NG_L2CAP_CON_TX)? 'T' : ' '),
+ ((r.connections[n].flags & NG_L2CAP_CON_RX)? 'R' : ' '),
+ r.connections[n].pending,
+ con_state2str(r.connections[n].state));
+ }
+out:
+ free(r.connections);
+
+ return (error);
+} /* l2cap_read_connection_list */
+
+/* Send read_channel_list command to the node */
+static int
+l2cap_read_channel_list(int s, int argc, char **argv)
+{
+ static char const * const state[] = {
+ /* NG_L2CAP_CLOSED */ "CLOSED",
+ /* NG_L2CAP_W4_L2CAP_CON_RSP */ "W4_L2CAP_CON_RSP",
+ /* NG_L2CAP_W4_L2CA_CON_RSP */ "W4_L2CA_CON_RSP",
+ /* NG_L2CAP_CONFIG */ "CONFIG",
+ /* NG_L2CAP_OPEN */ "OPEN",
+ /* NG_L2CAP_W4_L2CAP_DISCON_RSP */ "W4_L2CAP_DISCON_RSP",
+ /* NG_L2CAP_W4_L2CA_DISCON_RSP */ "W4_L2CA_DISCON_RSP"
+ };
+#define ch_state2str(x) ((x) >= SIZE(state)? "UNKNOWN" : state[(x)])
+
+ struct ng_btsocket_l2cap_raw_chan_list r;
+ int n, error = OK;
+
+ memset(&r, 0, sizeof(r));
+ r.num_channels = NG_L2CAP_MAX_CHAN_NUM;
+ r.channels = calloc(NG_L2CAP_MAX_CHAN_NUM,
+ sizeof(ng_l2cap_node_chan_ep));
+ if (r.channels == NULL) {
+ errno = ENOMEM;
+ return (ERROR);
+ }
+
+ if (ioctl(s, SIOC_L2CAP_NODE_GET_CHAN_LIST, &r, sizeof(r)) < 0) {
+ error = ERROR;
+ goto out;
+ }
+
+ fprintf(stdout, "L2CAP channels:\n");
+ fprintf(stdout,
+"Remote BD_ADDR SCID/ DCID PSM IMTU/ OMTU State\n");
+ for (n = 0; n < r.num_channels; n++) {
+ fprintf(stdout,
+ "%-17.17s " \
+ "%5d/%5d %5d " \
+ "%5d/%5d " \
+ "%s\n",
+ bdaddrpr(&r.channels[n].remote),
+ r.channels[n].scid, r.channels[n].dcid,
+ r.channels[n].psm, r.channels[n].imtu,
+ r.channels[n].omtu,
+ ch_state2str(r.channels[n].state));
+ }
+out:
+ free(r.channels);
+
+ return (error);
+} /* l2cap_read_channel_list */
+
+/* Send read_auto_disconnect_timeout command to the node */
+static int
+l2cap_read_auto_disconnect_timeout(int s, int argc, char **argv)
+{
+ struct ng_btsocket_l2cap_raw_auto_discon_timo r;
+
+ memset(&r, 0, sizeof(r));
+ if (ioctl(s, SIOC_L2CAP_NODE_GET_AUTO_DISCON_TIMO, &r, sizeof(r)) < 0)
+ return (ERROR);
+
+ if (r.timeout != 0)
+ fprintf(stdout, "Auto disconnect timeout: %d sec\n", r.timeout);
+ else
+ fprintf(stdout, "Auto disconnect disabled\n");
+
+ return (OK);
+} /* l2cap_read_auto_disconnect_timeout */
+
+/* Send write_auto_disconnect_timeout command to the node */
+static int
+l2cap_write_auto_disconnect_timeout(int s, int argc, char **argv)
+{
+ struct ng_btsocket_l2cap_raw_auto_discon_timo r;
+
+ memset(&r, 0, sizeof(r));
+ switch (argc) {
+ case 1:
+ r.timeout = atoi(argv[0]);
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ if (ioctl(s, SIOC_L2CAP_NODE_SET_AUTO_DISCON_TIMO, &r, sizeof(r)) < 0)
+ return (ERROR);
+
+ return (OK);
+} /* l2cap_write_auto_disconnect_timeout */
+
+struct l2cap_command l2cap_commands[] = {
+{
+"read_node_flags",
+"Get L2CAP node flags",
+&l2cap_read_node_flags
+},
+{
+"read_debug_level",
+"Get L2CAP node debug level",
+&l2cap_read_debug_level
+},
+{
+"write_debug_level <level>",
+"Set L2CAP node debug level",
+&l2cap_write_debug_level
+},
+{
+"read_connection_list",
+"Read list of the L2CAP connections",
+&l2cap_read_connection_list
+},
+{
+"read_channel_list",
+"Read list of the L2CAP channels",
+&l2cap_read_channel_list
+},
+{
+"read_auto_disconnect_timeout",
+"Get L2CAP node auto disconnect timeout (in sec)",
+&l2cap_read_auto_disconnect_timeout
+},
+{
+"write_auto_disconnect_timeout <timeout>",
+"Set L2CAP node auto disconnect timeout (in sec)",
+&l2cap_write_auto_disconnect_timeout
+},
+{
+NULL,
+}};
+
diff --git a/usr.sbin/bluetooth/l2control/l2control.8 b/usr.sbin/bluetooth/l2control/l2control.8
new file mode 100644
index 0000000..5974167
--- /dev/null
+++ b/usr.sbin/bluetooth/l2control/l2control.8
@@ -0,0 +1,97 @@
+.\" Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+.\"
+.\" $Id: l2control.8,v 1.5 2003/05/21 00:53:00 max Exp $
+.\" $FreeBSD$
+.\"
+.Dd April 9, 2011
+.Dt L2CONTROL 8
+.Os
+.Sh NAME
+.Nm l2control
+.Nd L2CAP configuration utility
+.Sh SYNOPSIS
+.Nm
+.Op Fl hn
+.Fl a Ar local
+.Ar command
+.Op Ar parameters ...
+.Sh DESCRIPTION
+The
+.Nm
+utility connects to the local device with the specified BD_ADDR or name
+and attempts to send the specified command.
+The
+.Nm
+utility will print results to the standard output and error messages to
+the standard error output.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl a Ar local
+Connect to the local device with the specified BD_ADDR or name.
+Example:
+.Fl a Li 00:01:02:03:04:05
+or
+.Fl a Li bt_device .
+.It Fl h
+Display usage message and exit.
+.It Fl n
+Show Bluetooth addresses as numbers.
+Normally
+.Nm
+attempts to resolve Bluetooth addresses, and display them symbolically.
+.It Ar command
+One of the supported commands (see below).
+The special command
+.Cm help
+can be used to obtain a list of all supported commands.
+To get more information about a specific command use
+.Cm help Ar command .
+.It Ar parameters
+One or more optional space separated command parameters.
+.El
+.Sh COMMANDS
+The currently supported node commands in
+.Nm
+are:
+.Pp
+.Bl -tag -width "Write_Auto_Disconnect_Timeout" -offset indent -compact
+.It Cm Read_Node_Flags
+.It Cm Read_Debug_Level
+.It Cm Write_Debug_Level
+.It Cm Read_Connection_List
+.It Cm Read_Channel_List
+.It Cm Read_Auto_Disconnect_Timeout
+.It Cm Write_Auto_Disconnect_Timeout
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr netgraph 3 ,
+.Xr netgraph 4 ,
+.Xr ng_l2cap 4 ,
+.Xr l2ping 8
+.Sh AUTHORS
+.An Maksim Yevmenkin Aq Mt emax@FreeBSD.org
diff --git a/usr.sbin/bluetooth/l2control/l2control.c b/usr.sbin/bluetooth/l2control/l2control.c
new file mode 100644
index 0000000..87ec237
--- /dev/null
+++ b/usr.sbin/bluetooth/l2control/l2control.c
@@ -0,0 +1,221 @@
+/*
+ * l2control.c
+ *
+ * Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: l2control.c,v 1.6 2003/09/05 00:38:25 max Exp $
+ * $FreeBSD$
+ */
+
+#include <assert.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "l2control.h"
+
+/* Prototypes */
+static int do_l2cap_command (bdaddr_p, int, char **);
+static struct l2cap_command * find_l2cap_command (char const *,
+ struct l2cap_command *);
+static void print_l2cap_command (struct l2cap_command *);
+static void usage (void);
+
+/* Main */
+
+int numeric_bdaddr = 0;
+
+int
+main(int argc, char *argv[])
+{
+ int n;
+ bdaddr_t bdaddr;
+
+ memset(&bdaddr, 0, sizeof(bdaddr));
+
+ /* Process command line arguments */
+ while ((n = getopt(argc, argv, "a:nh")) != -1) {
+ switch (n) {
+ case 'a':
+ if (!bt_aton(optarg, &bdaddr)) {
+ struct hostent *he = NULL;
+
+ if ((he = bt_gethostbyname(optarg)) == NULL)
+ errx(1, "%s: %s", optarg, hstrerror(h_errno));
+
+ memcpy(&bdaddr, he->h_addr, sizeof(bdaddr));
+ }
+ break;
+
+ case 'n':
+ numeric_bdaddr = 1;
+ break;
+
+ case 'h':
+ default:
+ usage();
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (*argv == NULL)
+ usage();
+
+ return (do_l2cap_command(&bdaddr, argc, argv));
+} /* main */
+
+/* Execute commands */
+static int
+do_l2cap_command(bdaddr_p bdaddr, int argc, char **argv)
+{
+ char *cmd = argv[0];
+ struct l2cap_command *c = NULL;
+ struct sockaddr_l2cap sa;
+ int s, e, help;
+
+ help = 0;
+ if (strcasecmp(cmd, "help") == 0) {
+ argc --;
+ argv ++;
+
+ if (argc <= 0) {
+ fprintf(stdout, "Supported commands:\n");
+ print_l2cap_command(l2cap_commands);
+ fprintf(stdout, "\nFor more information use " \
+ "'help command'\n");
+
+ return (OK);
+ }
+
+ help = 1;
+ cmd = argv[0];
+ }
+
+ c = find_l2cap_command(cmd, l2cap_commands);
+ if (c == NULL) {
+ fprintf(stdout, "Unknown command: \"%s\"\n", cmd);
+ return (ERROR);
+ }
+
+ if (!help) {
+ if (memcmp(bdaddr, NG_HCI_BDADDR_ANY, sizeof(*bdaddr)) == 0)
+ usage();
+
+ memset(&sa, 0, sizeof(sa));
+ sa.l2cap_len = sizeof(sa);
+ sa.l2cap_family = AF_BLUETOOTH;
+ memcpy(&sa.l2cap_bdaddr, bdaddr, sizeof(sa.l2cap_bdaddr));
+
+ s = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_L2CAP);
+ if (s < 0)
+ err(1, "Could not create socket");
+
+ if (bind(s, (struct sockaddr *) &sa, sizeof(sa)) < 0)
+ err(2,
+"Could not bind socket, bdaddr=%s", bt_ntoa(&sa.l2cap_bdaddr, NULL));
+
+ e = 0x0ffff;
+ if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &e, sizeof(e)) < 0)
+ err(3, "Coult not setsockopt(RCVBUF, %d)", e);
+
+ e = (c->handler)(s, -- argc, ++ argv);
+
+ close(s);
+ } else
+ e = USAGE;
+
+ switch (e) {
+ case OK:
+ case FAILED:
+ break;
+
+ case ERROR:
+ fprintf(stdout, "Could not execute command \"%s\". %s\n",
+ cmd, strerror(errno));
+ break;
+
+ case USAGE:
+ fprintf(stdout, "Usage: %s\n%s\n", c->command, c->description);
+ break;
+
+ default: assert(0); break;
+ }
+
+ return (e);
+} /* do_l2cap_command */
+
+/* Try to find command in specified category */
+static struct l2cap_command *
+find_l2cap_command(char const *command, struct l2cap_command *category)
+{
+ struct l2cap_command *c = NULL;
+
+ for (c = category; c->command != NULL; c++) {
+ char *c_end = strchr(c->command, ' ');
+
+ if (c_end != NULL) {
+ int len = c_end - c->command;
+
+ if (strncasecmp(command, c->command, len) == 0)
+ return (c);
+ } else if (strcasecmp(command, c->command) == 0)
+ return (c);
+ }
+
+ return (NULL);
+} /* find_l2cap_command */
+
+/* Print commands in specified category */
+static void
+print_l2cap_command(struct l2cap_command *category)
+{
+ struct l2cap_command *c = NULL;
+
+ for (c = category; c->command != NULL; c++)
+ fprintf(stdout, "\t%s\n", c->command);
+} /* print_l2cap_command */
+
+/* Usage */
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: l2control [-hn] -a local cmd [params ..]\n");
+ fprintf(stderr, "Where:\n");
+ fprintf(stderr, " -a local Specify local device to connect to\n");
+ fprintf(stderr, " -h Display this message\n");
+ fprintf(stderr, " -n Show addresses as numbers\n");
+ fprintf(stderr, " cmd Supported command " \
+ "(see l2control help)\n");
+ fprintf(stderr, " params Optional command parameters\n");
+ exit(255);
+} /* usage */
+
diff --git a/usr.sbin/bluetooth/l2control/l2control.h b/usr.sbin/bluetooth/l2control/l2control.h
new file mode 100644
index 0000000..43d4561
--- /dev/null
+++ b/usr.sbin/bluetooth/l2control/l2control.h
@@ -0,0 +1,49 @@
+/*
+ * l2control.h
+ *
+ * Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: l2control.h,v 1.1 2002/11/24 20:22:41 max Exp $
+ * $FreeBSD$
+ */
+
+#ifndef _L2CONTROL_H_
+#define _L2CONTROL_H_
+
+#define OK 0 /* everything was OK */
+#define ERROR 1 /* could not execute command */
+#define FAILED 2 /* error was reported */
+#define USAGE 3 /* invalid parameters */
+
+struct l2cap_command {
+ char const *command;
+ char const *description;
+ int (*handler)(int, int, char **);
+};
+
+extern struct l2cap_command l2cap_commands[];
+
+#endif /* _L2CONTROL_H_ */
+
diff --git a/usr.sbin/bluetooth/l2ping/Makefile b/usr.sbin/bluetooth/l2ping/Makefile
new file mode 100644
index 0000000..572366a
--- /dev/null
+++ b/usr.sbin/bluetooth/l2ping/Makefile
@@ -0,0 +1,10 @@
+# $Id: Makefile,v 1.6 2003/08/14 20:06:24 max Exp $
+# $FreeBSD$
+
+PROG= l2ping
+MAN= l2ping.8
+WARNS?= 2
+
+LIBADD= bluetooth
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bluetooth/l2ping/Makefile.depend b/usr.sbin/bluetooth/l2ping/Makefile.depend
new file mode 100644
index 0000000..179dbbe
--- /dev/null
+++ b/usr.sbin/bluetooth/l2ping/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbluetooth \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bluetooth/l2ping/l2ping.8 b/usr.sbin/bluetooth/l2ping/l2ping.8
new file mode 100644
index 0000000..b6233e8
--- /dev/null
+++ b/usr.sbin/bluetooth/l2ping/l2ping.8
@@ -0,0 +1,114 @@
+.\" Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+.\"
+.\" $Id: l2ping.8,v 1.3 2003/05/21 01:00:19 max Exp $
+.\" $FreeBSD$
+.\"
+.Dd March 29, 2011
+.Dt L2PING 8
+.Os
+.Sh NAME
+.Nm l2ping
+.Nd send L2CAP ECHO_REQUEST to remote devices
+.Sh SYNOPSIS
+.Nm
+.Op Fl fhn
+.Fl a Ar remote
+.Op Fl c Ar count
+.Op Fl i Ar wait
+.Op Fl S Ar source
+.Op Fl s Ar size
+.Sh DESCRIPTION
+The
+.Nm
+utility uses L2CAP
+.Dv ECHO_REQUEST
+datagram to elicit an L2CAP
+.Dv ECHO_RESPONSE
+datagram from a remote device.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl a Ar remote
+Specify the remote device to ping.
+The remote device can be specified by either its BD_ADDR or name.
+If name was specified then the
+.Nm
+utility will attempt to resolve the name via
+.Xr bt_gethostbyname 3 .
+.It Fl c Ar count
+Number of packets to send.
+If this option is not specified,
+.Nm
+will operate until interrupted.
+.It Fl f
+Do not wait between sending each packet.
+.It Fl h
+Display usage message and exit.
+.It Fl i Ar wait
+Wait
+.Ar wait
+seconds between sending each packet.
+The default is to wait for one second between each packet.
+This option is ignored if
+.Fl f
+has been specified.
+.It Fl n
+Numeric output only.
+No attempt will be made to look up symbolic names for host addresses.
+.It Fl S Ar source
+Specify the local device which should be used to send L2CAP
+.Dv ECHO_REQUEST
+datagrams.
+The local device can be specified by either its BD_ADDR or name.
+If name was specified then the
+.Nm
+utility will attempt to resolve the name via
+.Xr bt_gethostbyname 3 .
+.It Fl s Ar size
+Specify the number of payload bytes to be sent.
+The default size is 44 bytes.
+It is calculated as minimum L2CAP MTU (48 bytes) minus the size of the L2CAP
+signalling command header (4 bytes).
+The maximum size is 65531 bytes.
+Is is calculated as maximum L2CAP MTU
+(65535 bytes) minus four bytes of payload reserved for
+.Nm
+internal use.
+Use this option with caution.
+Some implementations may not like large sizes and may hang or even crash.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr bluetooth 3 ,
+.Xr netgraph 3 ,
+.Xr netgraph 4 ,
+.Xr ng_l2cap 4 ,
+.Xr l2control 8
+.Sh AUTHORS
+.An Maksim Yevmenkin Aq Mt emax@FreeBSD.org
+.Sh BUGS
+Could collect more statistic.
+Could check for duplicated, corrupted and lost packets.
diff --git a/usr.sbin/bluetooth/l2ping/l2ping.c b/usr.sbin/bluetooth/l2ping/l2ping.c
new file mode 100644
index 0000000..92e7a0a
--- /dev/null
+++ b/usr.sbin/bluetooth/l2ping/l2ping.c
@@ -0,0 +1,293 @@
+/*
+ * l2ping.c
+ *
+ * Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: l2ping.c,v 1.5 2003/05/16 19:54:40 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <assert.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static void usage (void);
+static void tv_sub (struct timeval *, struct timeval const *);
+static double tv2msec (struct timeval const *);
+
+#undef min
+#define min(x, y) (((x) > (y))? (y) : (x))
+
+static char const pattern[] = "1234567890-";
+#define PATTERN_SIZE (sizeof(pattern) - 1)
+
+/*
+ * Main
+ */
+
+int
+main(int argc, char *argv[])
+{
+ bdaddr_t src, dst;
+ struct hostent *he;
+ uint8_t *echo_data;
+ struct sockaddr_l2cap sa;
+ int32_t n, s, count, wait, flood, echo_size, numeric;
+ char *endp, *rname;
+
+ /* Set defaults */
+ memcpy(&src, NG_HCI_BDADDR_ANY, sizeof(src));
+ memcpy(&dst, NG_HCI_BDADDR_ANY, sizeof(dst));
+
+ echo_data = (uint8_t *) calloc(NG_L2CAP_MAX_ECHO_SIZE, sizeof(uint8_t));
+ if (echo_data == NULL) {
+ fprintf(stderr, "Failed to allocate echo data buffer");
+ exit(1);
+ }
+
+ /*
+ * Set default echo size to the NG_L2CAP_MTU_MINIMUM minus
+ * the size of the L2CAP signalling command header.
+ */
+
+ echo_size = NG_L2CAP_MTU_MINIMUM - sizeof(ng_l2cap_cmd_hdr_t);
+ count = -1; /* unimited */
+ wait = 1; /* sec */
+ flood = 0;
+ numeric = 0;
+
+ /* Parse command line arguments */
+ while ((n = getopt(argc, argv, "a:c:fi:nS:s:h")) != -1) {
+ switch (n) {
+ case 'a':
+ if (!bt_aton(optarg, &dst)) {
+ if ((he = bt_gethostbyname(optarg)) == NULL)
+ errx(1, "%s: %s", optarg, hstrerror(h_errno));
+
+ memcpy(&dst, he->h_addr, sizeof(dst));
+ }
+ break;
+
+ case 'c':
+ count = strtol(optarg, &endp, 10);
+ if (count <= 0 || *endp != '\0')
+ usage();
+ break;
+
+ case 'f':
+ flood = 1;
+ break;
+
+ case 'i':
+ wait = strtol(optarg, &endp, 10);
+ if (wait <= 0 || *endp != '\0')
+ usage();
+ break;
+
+ case 'n':
+ numeric = 1;
+ break;
+
+ case 'S':
+ if (!bt_aton(optarg, &src)) {
+ if ((he = bt_gethostbyname(optarg)) == NULL)
+ errx(1, "%s: %s", optarg, hstrerror(h_errno));
+
+ memcpy(&src, he->h_addr, sizeof(src));
+ }
+ break;
+
+ case 's':
+ echo_size = strtol(optarg, &endp, 10);
+ if (echo_size < sizeof(int32_t) ||
+ echo_size > NG_L2CAP_MAX_ECHO_SIZE ||
+ *endp != '\0')
+ usage();
+ break;
+
+ case 'h':
+ default:
+ usage();
+ break;
+ }
+ }
+
+ if (memcmp(&dst, NG_HCI_BDADDR_ANY, sizeof(dst)) == 0)
+ usage();
+
+ he = bt_gethostbyaddr((const char *)&dst, sizeof(dst), AF_BLUETOOTH);
+ if (he == NULL || he->h_name == NULL || he->h_name[0] == '\0' || numeric)
+ asprintf(&rname, "%s", bt_ntoa(&dst, NULL));
+ else
+ rname = strdup(he->h_name);
+
+ if (rname == NULL)
+ errx(1, "Failed to create remote hostname");
+
+ s = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_L2CAP);
+ if (s < 0)
+ err(2, "Could not create socket");
+
+ memset(&sa, 0, sizeof(sa));
+ sa.l2cap_len = sizeof(sa);
+ sa.l2cap_family = AF_BLUETOOTH;
+ memcpy(&sa.l2cap_bdaddr, &src, sizeof(sa.l2cap_bdaddr));
+
+ if (bind(s, (struct sockaddr *) &sa, sizeof(sa)) < 0)
+ err(3,
+"Could not bind socket, src bdaddr=%s", bt_ntoa(&sa.l2cap_bdaddr, NULL));
+
+ memset(&sa, 0, sizeof(sa));
+ sa.l2cap_len = sizeof(sa);
+ sa.l2cap_family = AF_BLUETOOTH;
+ memcpy(&sa.l2cap_bdaddr, &dst, sizeof(sa.l2cap_bdaddr));
+
+ if (connect(s, (struct sockaddr *) &sa, sizeof(sa)) < 0)
+ err(4,
+"Could not connect socket, dst bdaddr=%s", bt_ntoa(&sa.l2cap_bdaddr, NULL));
+
+ /* Fill pattern */
+ for (n = 0; n < echo_size; ) {
+ int32_t avail = min(echo_size - n, PATTERN_SIZE);
+
+ memcpy(echo_data + n, pattern, avail);
+ n += avail;
+ }
+
+ /* Start ping'ing */
+ for (n = 0; count == -1 || count > 0; n ++) {
+ struct ng_btsocket_l2cap_raw_ping r;
+ struct timeval a, b;
+ int32_t fail;
+
+ if (gettimeofday(&a, NULL) < 0)
+ err(5, "Could not gettimeofday(a)");
+
+ fail = 0;
+ *((int32_t *) echo_data) = htonl(n);
+
+ r.result = 0;
+ r.echo_size = echo_size;
+ r.echo_data = echo_data;
+ if (ioctl(s, SIOC_L2CAP_L2CA_PING, &r, sizeof(r)) < 0) {
+ r.result = errno;
+ fail = 1;
+/*
+ warn("Could not ping, dst bdaddr=%s",
+ bt_ntoa(&r.echo_dst, NULL));
+*/
+ }
+
+ if (gettimeofday(&b, NULL) < 0)
+ err(7, "Could not gettimeofday(b)");
+
+ tv_sub(&b, &a);
+
+ fprintf(stdout,
+"%d bytes from %s seq_no=%d time=%.3f ms result=%#x %s\n",
+ r.echo_size,
+ rname,
+ ntohl(*((int32_t *)(r.echo_data))),
+ tv2msec(&b), r.result,
+ ((fail == 0)? "" : strerror(errno)));
+
+ if (!flood) {
+ /* Wait */
+ a.tv_sec = wait;
+ a.tv_usec = 0;
+ select(0, NULL, NULL, NULL, &a);
+ }
+
+ if (count != -1)
+ count --;
+ }
+
+ free(rname);
+ free(echo_data);
+ close(s);
+
+ return (0);
+} /* main */
+
+/*
+ * a -= b, for timevals
+ */
+
+static void
+tv_sub(struct timeval *a, struct timeval const *b)
+{
+ if (a->tv_usec < b->tv_usec) {
+ a->tv_usec += 1000000;
+ a->tv_sec -= 1;
+ }
+
+ a->tv_usec -= b->tv_usec;
+ a->tv_sec -= b->tv_sec;
+} /* tv_sub */
+
+/*
+ * convert tv to msec
+ */
+
+static double
+tv2msec(struct timeval const *tvp)
+{
+ return(((double)tvp->tv_usec)/1000.0 + ((double)tvp->tv_sec)*1000.0);
+} /* tv2msec */
+
+/*
+ * Usage
+ */
+
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: l2ping [-fhn] -a remote " \
+ "[-c count] [-i wait] [-S source] [-s size]\n");
+ fprintf(stderr, "Where:\n");
+ fprintf(stderr, " -a remote Specify remote device to ping\n");
+ fprintf(stderr, " -c count Number of packets to send\n");
+ fprintf(stderr, " -f No delay between packets\n");
+ fprintf(stderr, " -h Display this message\n");
+ fprintf(stderr, " -i wait Delay between packets (sec)\n");
+ fprintf(stderr, " -n Numeric output only\n");
+ fprintf(stderr, " -S source Specify source device\n");
+ fprintf(stderr, " -s size Packet size (bytes), " \
+ "between %zd and %zd\n", sizeof(int32_t), NG_L2CAP_MAX_ECHO_SIZE);
+
+ exit(255);
+} /* usage */
+
diff --git a/usr.sbin/bluetooth/rfcomm_pppd/Makefile b/usr.sbin/bluetooth/rfcomm_pppd/Makefile
new file mode 100644
index 0000000..f31e1e5
--- /dev/null
+++ b/usr.sbin/bluetooth/rfcomm_pppd/Makefile
@@ -0,0 +1,13 @@
+# $Id: Makefile,v 1.7 2003/09/07 18:32:11 max Exp $
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../../usr.bin/bluetooth/rfcomm_sppd
+
+PROG= rfcomm_pppd
+MAN= rfcomm_pppd.8
+SRCS= rfcomm_pppd.c rfcomm_sdp.c
+WARNS?= 2
+
+LIBADD= bluetooth sdp
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bluetooth/rfcomm_pppd/Makefile.depend b/usr.sbin/bluetooth/rfcomm_pppd/Makefile.depend
new file mode 100644
index 0000000..8fea47e
--- /dev/null
+++ b/usr.sbin/bluetooth/rfcomm_pppd/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbluetooth \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libsdp \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bluetooth/rfcomm_pppd/rfcomm_pppd.8 b/usr.sbin/bluetooth/rfcomm_pppd/rfcomm_pppd.8
new file mode 100644
index 0000000..6763008
--- /dev/null
+++ b/usr.sbin/bluetooth/rfcomm_pppd/rfcomm_pppd.8
@@ -0,0 +1,354 @@
+.\" Copyright (c) 2001-2003 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+.\"
+.\" $Id: rfcomm_pppd.8,v 1.7 2003/09/07 18:32:11 max Exp $
+.\" $FreeBSD$
+.\"
+.Dd February 4, 2003
+.Dt RFCOMM_PPPD 8
+.Os
+.Sh NAME
+.Nm rfcomm_pppd
+.Nd RFCOMM PPP daemon
+.Sh SYNOPSIS
+.Nm
+.Fl c
+.Op Fl dh
+.Fl a Ar address
+.Fl C Ar channel
+.Fl l Ar label
+.Fl u Ar N
+.Nm
+.Fl s
+.Op Fl dDhS
+.Op Fl a Ar address
+.Fl C Ar channel
+.Fl l Ar label
+.Sh DESCRIPTION
+The
+.Nm
+daemon is a simple wrapper daemon that allows the use of
+.Xr ppp 8
+via an RFCOMM connection.
+It can operate in two modes: client and server.
+.Pp
+In client mode,
+.Nm
+opens an RFCOMM connection to the specified server's
+.Ar BD_ADRR
+and
+.Ar channel .
+Once the RFCOMM connection is established,
+.Nm
+executes
+.Xr ppp 8
+in
+.Fl direct
+mode with the specified
+.Ar label .
+Likewise,
+.Xr ppp 8
+operates over the RFCOMM connection just like it would over a standard serial
+port, thus allowing a user to
+.Dq "dial out"
+and connect to the Internet.
+.Pp
+In server mode,
+.Nm
+opens an RFCOMM socket and listens for incoming connections from remote clients.
+Once the new incoming connection is accepted,
+.Nm
+forks and executes
+.Xr ppp 8
+in
+.Fl direct
+mode with the specified
+.Ar label .
+Likewise,
+.Xr ppp 8
+operates over the RFCOMM connection just like it would over a standard serial
+port, thus providing network connectivity to remote clients.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl a Ar address
+In client mode, this required option specifies the address of the remote
+RFCOMM server.
+In server mode, this option can be used to specify the local
+address to listen on.
+By default, in server mode, the daemon will listen on
+.Dv ANY
+address.
+The address can be specified as BD_ADDR or name.
+If a name was specified, the
+.Nm
+utility will attempt to resolve the name via
+.Xr bt_gethostbyname 3 .
+.It Fl C Ar channel
+In both client and server mode, this required option specifies the RFCOMM
+channel to connect to or listen on.
+In server mode, the channel should be a number between 1 and 30.
+In client mode, the channel could either be a number between 1 and 30
+or a service name.
+Supported service names are:
+.Cm DUN
+(Dial-Up Networking) and
+.Cm LAN
+(LAN Access Using PPP).
+If a service name is used instead of a numeric channel number, then
+.Nm
+will try to obtain an RFCOMM channel number via SDP
+(Service Discovery Protocol).
+.It Fl c
+Act as an RFCOMM client.
+This is the default mode.
+.It Fl d
+Do not detach from the controlling terminal, i.e., run in foreground.
+.It Fl D
+In server mode, register the
+.Cm DUN
+(Dial-Up Networking) service in addition to the
+.Cm LAN
+(LAN Access Using PPP) service.
+AT-command exchange can be faked with
+.Xr ppp 8
+chat script.
+.It Fl h
+Display usage message and exit.
+.It Fl l Ar label
+In both client and server mode, this required option specifies which
+.Xr ppp 8
+label will be used.
+.It Fl S
+In server mode, register the
+.Cm SP
+(Serial Port) service in addition to the
+.Cm LAN
+(LAN Access Using PPP) service.
+.Pp
+It appears that some cell phones are using the so-called
+.Dq "callback mechanism" .
+In this scenario, the user is trying to connect his cell phone to the Internet,
+while the user's host computer is acting as the gateway server.
+It seems that it is not possible to tell the phone to just connect and start
+using the
+.Cm LAN
+service.
+Instead, the user's host computer must
+.Dq "jump start"
+the phone by connecting to the phone's
+.Cm SP
+service.
+What happens next is the phone kills the existing connection and opens another
+connection back to the user's host computer.
+The phone really wants to use the
+.Cm LAN
+service, but for whatever reason it looks for the
+.Cm SP
+service on the user's host computer.
+This brain-damaged behavior was reported for the Nokia 6600 and the
+Sony/Ericsson P900.
+.It Fl s
+Act as an RFCOMM server.
+.It Fl u Ar N
+This option maps directly to the
+.Fl unit
+.Xr ppp 8
+command-line option and tells
+.Nm
+to instruct
+.Xr ppp 8
+to only attempt to open
+.Pa /dev/tun Ns Ar N .
+This option only works in client mode.
+.El
+.Sh PPP CONFIGURATION
+.Ss Important Notes on PPP Configuration
+Special attention is required when adding new RFCOMM configurations to the
+existing PPP configuration.
+Please keep in mind that PPP will
+.Em always
+execute commands in the
+.Dq Li default
+label of your
+.Pa /etc/ppp/ppp.conf
+file.
+Please make sure that the
+.Dq Li default
+label
+.Em only
+contains commands that apply to
+.Em every
+other label.
+If you need to use PPP for both dialing out and accepting incoming
+RFCOMM connections, please make sure you have moved all commands related to
+dialing out from the
+.Dq Li default
+section into an appropriate outgoing label.
+.Ss RFCOMM Server
+One of the typical examples is the LAN access.
+In this example, an RFCOMM connection
+is used as a null-modem connection between a client and a server.
+Both client and server will start talking PPP right after the RFCOMM
+connection has been established.
+.Bd -literal -offset indent
+rfcomm-server:
+ set timeout 0
+ set lqrperiod 10
+ set ifaddr 10.0.0.1 10.0.0.2 255.255.255.0
+ enable lqr
+ accept lqr
+ accept dns
+ # Do not use PPP authentication. Assume that
+ # Bluetooth connection was authenticated already
+ disable pap
+ deny pap
+ disable chap
+ deny chap
+.Ed
+.Ss RFCOMM Client
+The
+.Nm
+utility supports both
+.Cm LAN
+(LAN Access Using PPP) and
+.Cm DUN
+(Dial-Up Networking) access.
+The client's configuration for
+.Cm LAN
+access is very similar to the server's and might look like this:
+.Bd -literal -offset indent
+rfcomm-client:
+ enable lqr
+ accept lqr
+ set dial
+ set timeout 0
+ disable iface-alias
+ set ifaddr 10.0.0.1/0 10.0.0.2/0 255.255.255.0 0.0.0.0
+ # Do not use PPP authentication. Assume that
+ # Bluetooth connection was authenticated already
+ deny pap
+ disable pap
+ deny chap
+ disable chap
+.Ed
+.Pp
+The client's configuration for
+.Cm DUN
+access is different.
+In this scenario, the client gets connected to the virtual serial port on the
+server.
+To open a PPP session, the client must dial a number.
+Note that by default
+.Xr ppp 8
+will not execute any configured chat scripts.
+The
+.Ic force-scripts
+option can be used to override this behavior.
+An example configuration is shown below:
+.Bd -literal -offset indent
+rfcomm-dialup:
+ # This is IMPORTANT option
+ enable force-scripts
+
+ # You might want to change these
+ set authname
+ set authkey
+ set phone "*99***1#"
+
+ # You might want to adjust dial string as well
+ set dial "ABORT BUSY ABORT NO\\\\sCARRIER TIMEOUT 5 \\
+ \\"\\" AT OK-AT-OK ATE1Q0 OK \\\\dATDT\\\\T TIMEOUT 40 CONNECT"
+ set login
+ set timeout 30
+ enable dns
+ resolv rewrite
+
+ set ifaddr 10.0.0.1/0 10.0.0.2/0 255.255.255.0 0.0.0.0
+ add default HISADDR
+.Ed
+.Pp
+Note that by adjusting the initialization string, one can make a CSD (Circuit
+Switched Data), HSCSD (High Speed Circuit Switched Data) or GPRS (General
+Packet Radio Service) connection.
+The availability of the particular connection
+type depends on the phone model and service plan activated on the phone.
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+.Dl "rfcomm_pppd -s -a 00:01:02:03:04:05 -C 1 -l rfcomm-server"
+.Pp
+This command will start
+.Nm
+in the server mode.
+The RFCOMM server will listen on local address
+.Li 00:01:02:03:04:05
+and channel
+.Li 1 .
+Once the incoming connection has been accepted,
+.Nm
+will execute
+.Xr ppp 8
+in
+.Fl direct
+mode with the
+.Dq Li rfcomm-server
+label.
+.Pp
+.Dl "rfcomm_pppd -c -a 00:01:02:03:04:05 -C 1 -l rfcomm-client"
+.Pp
+This command will start
+.Nm
+in the client mode.
+.Nm
+will try to connect to the RFCOMM server at
+.Li 00:01:02:03:04:05
+address and channel
+.Li 1 .
+Once connected,
+.Nm
+will execute
+.Xr ppp 8
+in
+.Fl direct
+mode with the
+.Dq Li rfcomm-client
+label.
+.Sh SEE ALSO
+.Xr rfcomm_sppd 1 ,
+.Xr bluetooth 3 ,
+.Xr ng_btsocket 4 ,
+.Xr ppp 8 ,
+.Xr sdpcontrol 8 ,
+.Xr sdpd 8
+.Sh AUTHORS
+.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com
+.Sh CAVEATS
+The
+.Nm
+utility in server mode will try to register the Bluetooth LAN Access Over PPP
+service with the local SPD daemon.
+If the local SDP daemon is not running,
+.Nm
+will exit with an error.
diff --git a/usr.sbin/bluetooth/rfcomm_pppd/rfcomm_pppd.c b/usr.sbin/bluetooth/rfcomm_pppd/rfcomm_pppd.c
new file mode 100644
index 0000000..e970fb0
--- /dev/null
+++ b/usr.sbin/bluetooth/rfcomm_pppd/rfcomm_pppd.c
@@ -0,0 +1,471 @@
+/*
+ * rfcomm_pppd.c
+ */
+
+/*-
+ * Copyright (c) 2001-2008 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: rfcomm_pppd.c,v 1.5 2003/09/07 18:32:11 max Exp $
+ * $FreeBSD$
+ */
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sdp.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#define RFCOMM_PPPD "rfcomm_pppd"
+
+int rfcomm_channel_lookup (bdaddr_t const *local,
+ bdaddr_t const *remote,
+ int service, int *channel, int *error);
+
+static void exec_ppp (int s, char *unit, char *label);
+static void sighandler (int s);
+static void usage (void);
+
+static int done;
+
+/* Main */
+int
+main(int argc, char *argv[])
+{
+ struct sockaddr_rfcomm sock_addr;
+ char *label = NULL, *unit = NULL, *ep = NULL;
+ bdaddr_t addr;
+ int s, channel, detach, server, service,
+ regdun, regsp;
+ pid_t pid;
+
+ memcpy(&addr, NG_HCI_BDADDR_ANY, sizeof(addr));
+ channel = 0;
+ detach = 1;
+ server = 0;
+ service = 0;
+ regdun = 0;
+ regsp = 0;
+
+ /* Parse command line arguments */
+ while ((s = getopt(argc, argv, "a:cC:dDhl:sSu:")) != -1) {
+ switch (s) {
+ case 'a': /* BDADDR */
+ if (!bt_aton(optarg, &addr)) {
+ struct hostent *he = NULL;
+
+ if ((he = bt_gethostbyname(optarg)) == NULL)
+ errx(1, "%s: %s", optarg, hstrerror(h_errno));
+
+ memcpy(&addr, he->h_addr, sizeof(addr));
+ }
+ break;
+
+ case 'c': /* client */
+ server = 0;
+ break;
+
+ case 'C': /* RFCOMM channel */
+ channel = strtoul(optarg, &ep, 10);
+ if (*ep != '\0') {
+ channel = 0;
+ switch (tolower(optarg[0])) {
+ case 'd': /* DialUp Networking */
+ service = SDP_SERVICE_CLASS_DIALUP_NETWORKING;
+ break;
+
+ case 'l': /* LAN Access Using PPP */
+ service = SDP_SERVICE_CLASS_LAN_ACCESS_USING_PPP;
+ break;
+ }
+ }
+ break;
+
+ case 'd': /* do not detach */
+ detach = 0;
+ break;
+
+ case 'D': /* Register DUN service as well as LAN service */
+ regdun = 1;
+ break;
+
+ case 'l': /* PPP label */
+ label = optarg;
+ break;
+
+ case 's': /* server */
+ server = 1;
+ break;
+
+ case 'S': /* Register SP service as well as LAN service */
+ regsp = 1;
+ break;
+
+ case 'u': /* PPP -unit option */
+ strtoul(optarg, &ep, 10);
+ if (*ep != '\0')
+ usage();
+ /* NOT REACHED */
+
+ unit = optarg;
+ break;
+
+ case 'h':
+ default:
+ usage();
+ /* NOT REACHED */
+ }
+ }
+
+ /* Check if we got everything we wanted */
+ if (label == NULL)
+ errx(1, "Must specify PPP label");
+
+ if (!server) {
+ if (memcmp(&addr, NG_HCI_BDADDR_ANY, sizeof(addr)) == 0)
+ errx(1, "Must specify server BD_ADDR");
+
+ /* Check channel, if was not set then obtain it via SDP */
+ if (channel == 0 && service != 0)
+ if (rfcomm_channel_lookup(NULL, &addr, service,
+ &channel, &s) != 0)
+ errc(1, s, "Could not obtain RFCOMM channel");
+ }
+
+ if (channel <= 0 || channel > 30)
+ errx(1, "Invalid RFCOMM channel number %d", channel);
+
+ openlog(RFCOMM_PPPD, LOG_PID | LOG_PERROR | LOG_NDELAY, LOG_USER);
+
+ if (detach && daemon(0, 0) < 0) {
+ syslog(LOG_ERR, "Could not daemon(0, 0). %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ s = socket(PF_BLUETOOTH, SOCK_STREAM, BLUETOOTH_PROTO_RFCOMM);
+ if (s < 0) {
+ syslog(LOG_ERR, "Could not create socket. %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ if (server) {
+ struct sigaction sa;
+ void *ss = NULL;
+ sdp_lan_profile_t lan;
+
+ /* Install signal handler */
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = sighandler;
+
+ if (sigaction(SIGTERM, &sa, NULL) < 0) {
+ syslog(LOG_ERR, "Could not sigaction(SIGTERM). %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ if (sigaction(SIGHUP, &sa, NULL) < 0) {
+ syslog(LOG_ERR, "Could not sigaction(SIGHUP). %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ if (sigaction(SIGINT, &sa, NULL) < 0) {
+ syslog(LOG_ERR, "Could not sigaction(SIGINT). %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ sa.sa_handler = SIG_IGN;
+ sa.sa_flags = SA_NOCLDWAIT;
+
+ if (sigaction(SIGCHLD, &sa, NULL) < 0) {
+ syslog(LOG_ERR, "Could not sigaction(SIGCHLD). %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ /* bind socket and listen for incoming connections */
+ sock_addr.rfcomm_len = sizeof(sock_addr);
+ sock_addr.rfcomm_family = AF_BLUETOOTH;
+ memcpy(&sock_addr.rfcomm_bdaddr, &addr,
+ sizeof(sock_addr.rfcomm_bdaddr));
+ sock_addr.rfcomm_channel = channel;
+
+ if (bind(s, (struct sockaddr *) &sock_addr,
+ sizeof(sock_addr)) < 0) {
+ syslog(LOG_ERR, "Could not bind socket. %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ if (listen(s, 10) < 0) {
+ syslog(LOG_ERR, "Could not listen on socket. %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ ss = sdp_open_local(NULL);
+ if (ss == NULL) {
+ syslog(LOG_ERR, "Unable to create local SDP session");
+ exit(1);
+ }
+
+ if (sdp_error(ss) != 0) {
+ syslog(LOG_ERR, "Unable to open local SDP session. " \
+ "%s (%d)", strerror(sdp_error(ss)),
+ sdp_error(ss));
+ exit(1);
+ }
+
+ memset(&lan, 0, sizeof(lan));
+ lan.server_channel = channel;
+
+ if (sdp_register_service(ss,
+ SDP_SERVICE_CLASS_LAN_ACCESS_USING_PPP,
+ &addr, (void *) &lan, sizeof(lan), NULL) != 0) {
+ syslog(LOG_ERR, "Unable to register LAN service with " \
+ "local SDP daemon. %s (%d)",
+ strerror(sdp_error(ss)), sdp_error(ss));
+ exit(1);
+ }
+
+ /*
+ * Register DUN (Dial-Up Networking) service on the same
+ * RFCOMM channel if requested. There is really no good reason
+ * to not to support this. AT-command exchange can be faked
+ * with chat script in ppp.conf
+ */
+
+ if (regdun) {
+ sdp_dun_profile_t dun;
+
+ memset(&dun, 0, sizeof(dun));
+ dun.server_channel = channel;
+
+ if (sdp_register_service(ss,
+ SDP_SERVICE_CLASS_DIALUP_NETWORKING,
+ &addr, (void *) &dun, sizeof(dun),
+ NULL) != 0) {
+ syslog(LOG_ERR, "Unable to register DUN " \
+ "service with local SDP daemon. " \
+ "%s (%d)", strerror(sdp_error(ss)),
+ sdp_error(ss));
+ exit(1);
+ }
+ }
+
+ /*
+ * Register SP (Serial Port) service on the same RFCOMM channel
+ * if requested. It appears that some cell phones are using so
+ * called "callback mechanism". In this scenario user is trying
+ * to connect his cell phone to the Internet, and, user's host
+ * computer is acting as the gateway server. It seems that it
+ * is not possible to tell the phone to just connect and start
+ * using the LAN service. Instead the user's host computer must
+ * "jump start" the phone by connecting to the phone's SP
+ * service. What happens next is the phone kills the existing
+ * connection and opens another connection back to the user's
+ * host computer. The phone really wants to use LAN service,
+ * but for whatever reason it looks for SP service on the
+ * user's host computer. This brain damaged behavior was
+ * reported for Nokia 6600 and Sony/Ericsson P900. Both phones
+ * are Symbian-based phones. Perhaps this is a Symbian problem?
+ */
+
+ if (regsp) {
+ sdp_sp_profile_t sp;
+
+ memset(&sp, 0, sizeof(sp));
+ sp.server_channel = channel;
+
+ if (sdp_register_service(ss,
+ SDP_SERVICE_CLASS_SERIAL_PORT,
+ &addr, (void *) &sp, sizeof(sp),
+ NULL) != 0) {
+ syslog(LOG_ERR, "Unable to register SP " \
+ "service with local SDP daemon. " \
+ "%s (%d)", strerror(sdp_error(ss)),
+ sdp_error(ss));
+ exit(1);
+ }
+ }
+
+ for (done = 0; !done; ) {
+ socklen_t len = sizeof(sock_addr);
+ int s1 = accept(s, (struct sockaddr *) &sock_addr, &len);
+
+ if (s1 < 0) {
+ syslog(LOG_ERR, "Could not accept connection " \
+ "on socket. %s (%d)", strerror(errno),
+ errno);
+ exit(1);
+ }
+
+ pid = fork();
+ if (pid == (pid_t) -1) {
+ syslog(LOG_ERR, "Could not fork(). %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ if (pid == 0) {
+ sdp_close(ss);
+ close(s);
+
+ /* Reset signal handler */
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = SIG_DFL;
+
+ sigaction(SIGTERM, &sa, NULL);
+ sigaction(SIGHUP, &sa, NULL);
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGCHLD, &sa, NULL);
+
+ /* Become daemon */
+ daemon(0, 0);
+
+ /*
+ * XXX Make sure user does not shoot himself
+ * in the foot. Do not pass unit option to the
+ * PPP when operating in the server mode.
+ */
+
+ exec_ppp(s1, NULL, label);
+ } else
+ close(s1);
+ }
+ } else {
+ sock_addr.rfcomm_len = sizeof(sock_addr);
+ sock_addr.rfcomm_family = AF_BLUETOOTH;
+ memcpy(&sock_addr.rfcomm_bdaddr, NG_HCI_BDADDR_ANY,
+ sizeof(sock_addr.rfcomm_bdaddr));
+ sock_addr.rfcomm_channel = 0;
+
+ if (bind(s, (struct sockaddr *) &sock_addr,
+ sizeof(sock_addr)) < 0) {
+ syslog(LOG_ERR, "Could not bind socket. %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ memcpy(&sock_addr.rfcomm_bdaddr, &addr,
+ sizeof(sock_addr.rfcomm_bdaddr));
+ sock_addr.rfcomm_channel = channel;
+
+ if (connect(s, (struct sockaddr *) &sock_addr,
+ sizeof(sock_addr)) < 0) {
+ syslog(LOG_ERR, "Could not connect socket. %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ exec_ppp(s, unit, label);
+ }
+
+ exit(0);
+} /* main */
+
+/*
+ * Redirects stdin/stdout to s, stderr to /dev/null and exec
+ * 'ppp -direct -quiet [-unit N] label'. Never returns.
+ */
+
+static void
+exec_ppp(int s, char *unit, char *label)
+{
+ char ppp[] = "/usr/sbin/ppp";
+ char *ppp_args[] = { ppp, "-direct", "-quiet",
+ NULL, NULL, NULL, NULL };
+
+ close(0);
+ if (dup(s) < 0) {
+ syslog(LOG_ERR, "Could not dup(0). %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ close(1);
+ if (dup(s) < 0) {
+ syslog(LOG_ERR, "Could not dup(1). %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ close(2);
+ open("/dev/null", O_RDWR);
+
+ if (unit != NULL) {
+ ppp_args[3] = "-unit";
+ ppp_args[4] = unit;
+ ppp_args[5] = label;
+ } else
+ ppp_args[3] = label;
+
+ if (execv(ppp, ppp_args) < 0) {
+ syslog(LOG_ERR, "Could not exec(%s -direct -quiet%s%s %s). " \
+ "%s (%d)", ppp, (unit != NULL)? " -unit " : "",
+ (unit != NULL)? unit : "", label,
+ strerror(errno), errno);
+ exit(1);
+ }
+} /* run_ppp */
+
+/* Signal handler */
+static void
+sighandler(int s)
+{
+ done = 1;
+} /* sighandler */
+
+/* Display usage and exit */
+static void
+usage(void)
+{
+ fprintf(stdout,
+"Usage: %s options\n" \
+"Where options are:\n" \
+"\t-a address Address to listen on or connect to (required for client)\n" \
+"\t-c Act as a clinet (default)\n" \
+"\t-C channel RFCOMM channel to listen on or connect to (required)\n" \
+"\t-d Run in foreground\n" \
+"\t-D Register Dial-Up Networking service (server mode only)\n" \
+"\t-l label Use PPP label (required)\n" \
+"\t-s Act as a server\n" \
+"\t-S Register Serial Port service (server mode only)\n" \
+"\t-u N Tell PPP to operate on /dev/tunN (client mode only)\n" \
+"\t-h Display this message\n", RFCOMM_PPPD);
+
+ exit(255);
+} /* usage */
+
diff --git a/usr.sbin/bluetooth/sdpcontrol/Makefile b/usr.sbin/bluetooth/sdpcontrol/Makefile
new file mode 100644
index 0000000..e3ebcd8
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpcontrol/Makefile
@@ -0,0 +1,11 @@
+# $Id: Makefile,v 1.1 2003/09/08 02:27:27 max Exp $
+# $FreeBSD$
+
+PROG= sdpcontrol
+MAN= sdpcontrol.8
+SRCS= sdpcontrol.c search.c
+WARNS?= 2
+
+LIBADD= bluetooth sdp
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bluetooth/sdpcontrol/Makefile.depend b/usr.sbin/bluetooth/sdpcontrol/Makefile.depend
new file mode 100644
index 0000000..8fea47e
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpcontrol/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbluetooth \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libsdp \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bluetooth/sdpcontrol/sdpcontrol.8 b/usr.sbin/bluetooth/sdpcontrol/sdpcontrol.8
new file mode 100644
index 0000000..82aafad
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpcontrol/sdpcontrol.8
@@ -0,0 +1,118 @@
+.\" Copyright (c) 2003 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+.\"
+.\" $Id: sdpcontrol.8,v 1.1 2003/09/08 02:27:27 max Exp $
+.\" $FreeBSD$
+.\"
+.Dd February 7, 2015
+.Dt SDPCONTROL 8
+.Os
+.Sh NAME
+.Nm sdpcontrol
+.Nd Bluetooth Service Discovery Protocol query utility
+.Sh SYNOPSIS
+.Nm
+.Fl h
+.Nm
+.Fl a Ar address
+.Ar command
+.Op Ar parameters ...
+.Nm
+.Fl l
+.Op Fl c Ar path
+.Ar command
+.Op Ar parameters ...
+.Sh DESCRIPTION
+The
+.Nm
+utility attempts to query the specified Service Discovery Protocol (SDP) server.
+Remote SDP servers are identified by their address.
+Connection to the local SDP server is made via the control socket.
+The
+.Nm
+utility uses Service Search Attribute Requests and prints results to
+standard output and error messages to standard error.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl a Ar address
+Connect to the remote device with the specified address.
+The address can be specified as BD_ADDR or a name.
+If a name was specified, the
+.Nm
+utility attempts to resolve the name via
+.Xr bt_gethostbyname 3 .
+.It Fl c Ar path
+Specify path to the control socket.
+The default path is
+.Pa /var/run/sdp .
+.It Fl h
+Display usage message and exit.
+.It Fl l
+Query the local SDP server via the control socket.
+.It Ar command
+One of the supported commands (see below).
+The special command
+.Cm help
+can be used to obtain a list of all supported commands.
+To get more information about a specific command, use
+.Cm help Ar command .
+.It Ar parameters
+One or more optional space separated command parameters.
+.El
+.Sh COMMANDS
+The currently supported node commands in
+.Nm
+are:
+.Pp
+.Bl -tag -width "Browse" -offset indent -compact
+.It Cm Browse
+.It Cm Search
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr bluetooth 3 ,
+.Xr sdp 3
+.Sh AUTHORS
+.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com
+.Sh CAVEATS
+The
+.Nm
+utility only implements client side functionality.
+.Pp
+The
+.Nm
+utility only requests the following attributes from the SDP server:
+.Pp
+.Bl -enum -offset indent -compact
+.It
+Service Record Handle
+.It
+Service Class ID List
+.It
+Protocol Descriptor List
+.It
+Bluetooth Profile Descriptor List
+.El
diff --git a/usr.sbin/bluetooth/sdpcontrol/sdpcontrol.c b/usr.sbin/bluetooth/sdpcontrol/sdpcontrol.c
new file mode 100644
index 0000000..65ee3d0
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpcontrol/sdpcontrol.c
@@ -0,0 +1,220 @@
+/*
+ * sdpcontrol.c
+ *
+ * Copyright (c) 2001-2003 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: sdpcontrol.c,v 1.1 2003/09/08 02:27:27 max Exp $
+ * $FreeBSD$
+ */
+
+#include <assert.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <err.h>
+#include <errno.h>
+#include <sdp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "sdpcontrol.h"
+
+/* Prototypes */
+static int do_sdp_command (bdaddr_p, char const *, int,
+ int, char **);
+static struct sdp_command * find_sdp_command (char const *,
+ struct sdp_command *);
+static void print_sdp_command (struct sdp_command *);
+static void usage (void);
+
+/* Main */
+int
+main(int argc, char *argv[])
+{
+ char const *control = SDP_LOCAL_PATH;
+ int n, local;
+ bdaddr_t bdaddr;
+
+ memset(&bdaddr, 0, sizeof(bdaddr));
+ local = 0;
+
+ /* Process command line arguments */
+ while ((n = getopt(argc, argv, "a:c:lh")) != -1) {
+ switch (n) {
+ case 'a': /* bdaddr */
+ if (!bt_aton(optarg, &bdaddr)) {
+ struct hostent *he = NULL;
+
+ if ((he = bt_gethostbyname(optarg)) == NULL)
+ errx(1, "%s: %s", optarg, hstrerror(h_errno));
+
+ memcpy(&bdaddr, he->h_addr, sizeof(bdaddr));
+ }
+ break;
+
+ case 'c': /* control socket */
+ control = optarg;
+ break;
+
+ case 'l': /* local sdpd */
+ local = 1;
+ break;
+
+ case 'h':
+ default:
+ usage();
+ /* NOT REACHED */
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (*argv == NULL)
+ usage();
+
+ return (do_sdp_command(&bdaddr, control, local, argc, argv));
+}
+
+/* Execute commands */
+static int
+do_sdp_command(bdaddr_p bdaddr, char const *control, int local,
+ int argc, char **argv)
+{
+ char *cmd = argv[0];
+ struct sdp_command *c = NULL;
+ void *xs = NULL;
+ int e, help;
+
+ help = 0;
+ if (strcasecmp(cmd, "help") == 0) {
+ argc --;
+ argv ++;
+
+ if (argc <= 0) {
+ fprintf(stdout, "Supported commands:\n");
+ print_sdp_command(sdp_commands);
+ fprintf(stdout, "\nFor more information use " \
+ "'help command'\n");
+
+ return (OK);
+ }
+
+ help = 1;
+ cmd = argv[0];
+ }
+
+ c = find_sdp_command(cmd, sdp_commands);
+ if (c == NULL) {
+ fprintf(stdout, "Unknown command: \"%s\"\n", cmd);
+ return (ERROR);
+ }
+
+ if (!help) {
+ if (!local) {
+ if (memcmp(bdaddr, NG_HCI_BDADDR_ANY, sizeof(*bdaddr)) == 0)
+ usage();
+
+ xs = sdp_open(NG_HCI_BDADDR_ANY, bdaddr);
+ } else
+ xs = sdp_open_local(control);
+
+ if (xs == NULL)
+ errx(1, "Could not create SDP session object");
+ if (sdp_error(xs) == 0)
+ e = (c->handler)(xs, -- argc, ++ argv);
+ else
+ e = ERROR;
+ } else
+ e = USAGE;
+
+ switch (e) {
+ case OK:
+ case FAILED:
+ break;
+
+ case ERROR:
+ fprintf(stdout, "Could not execute command \"%s\". %s\n",
+ cmd, strerror(sdp_error(xs)));
+ break;
+
+ case USAGE:
+ fprintf(stdout, "Usage: %s\n%s\n", c->command, c->description);
+ break;
+
+ default: assert(0); break;
+ }
+
+ sdp_close(xs);
+
+ return (e);
+} /* do_sdp_command */
+
+/* Try to find command in specified category */
+static struct sdp_command *
+find_sdp_command(char const *command, struct sdp_command *category)
+{
+ struct sdp_command *c = NULL;
+
+ for (c = category; c->command != NULL; c++) {
+ char *c_end = strchr(c->command, ' ');
+
+ if (c_end != NULL) {
+ int len = c_end - c->command;
+
+ if (strncasecmp(command, c->command, len) == 0)
+ return (c);
+ } else if (strcasecmp(command, c->command) == 0)
+ return (c);
+ }
+
+ return (NULL);
+} /* find_sdp_command */
+
+/* Print commands in specified category */
+static void
+print_sdp_command(struct sdp_command *category)
+{
+ struct sdp_command *c = NULL;
+
+ for (c = category; c->command != NULL; c++)
+ fprintf(stdout, "\t%s\n", c->command);
+} /* print_sdp_command */
+
+/* Usage */
+static void
+usage(void)
+{
+ fprintf(stderr,
+"Usage: sdpcontrol options command\n" \
+"Where options are:\n"
+" -a address address to connect to\n" \
+" -c path path to the control socket (default is %s)\n" \
+" -h display usage and quit\n" \
+" -l connect to the local SDP server via control socket\n" \
+" command one of the supported commands\n", SDP_LOCAL_PATH);
+ exit(255);
+} /* usage */
+
diff --git a/usr.sbin/bluetooth/sdpcontrol/sdpcontrol.h b/usr.sbin/bluetooth/sdpcontrol/sdpcontrol.h
new file mode 100644
index 0000000..4a1ee76f
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpcontrol/sdpcontrol.h
@@ -0,0 +1,49 @@
+/*
+ * sdpcontrol.h
+ *
+ * Copyright (c) 2001-2003 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: sdpcontrol.h,v 1.1 2003/09/08 02:27:27 max Exp $
+ * $FreeBSD$
+ */
+
+#ifndef __SDPCONTROL_H__
+#define __SDPCONTROL_H__
+
+#define OK 0 /* everything was OK */
+#define ERROR 1 /* could not execute command */
+#define FAILED 2 /* error was reported */
+#define USAGE 3 /* invalid parameters */
+
+struct sdp_command {
+ char const *command;
+ char const *description;
+ int (*handler)(void *, int, char **);
+};
+
+extern struct sdp_command sdp_commands[];
+
+#endif /* __SDPCONTROL_H__ */
+
diff --git a/usr.sbin/bluetooth/sdpcontrol/search.c b/usr.sbin/bluetooth/sdpcontrol/search.c
new file mode 100644
index 0000000..6a2808c
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpcontrol/search.c
@@ -0,0 +1,748 @@
+/*
+ * search.c
+ *
+ * Copyright (c) 2001-2003 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: search.c,v 1.2 2003/09/08 17:35:15 max Exp $
+ * $FreeBSD$
+ */
+
+#include <netinet/in.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <ctype.h>
+#include <sdp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "sdpcontrol.h"
+
+/* List of the attributes we are looking for */
+static uint32_t attrs[] =
+{
+ SDP_ATTR_RANGE( SDP_ATTR_SERVICE_RECORD_HANDLE,
+ SDP_ATTR_SERVICE_RECORD_HANDLE),
+ SDP_ATTR_RANGE( SDP_ATTR_SERVICE_CLASS_ID_LIST,
+ SDP_ATTR_SERVICE_CLASS_ID_LIST),
+ SDP_ATTR_RANGE( SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST,
+ SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST),
+ SDP_ATTR_RANGE( SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST,
+ SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST)
+};
+#define attrs_len (sizeof(attrs)/sizeof(attrs[0]))
+
+/* Buffer for the attributes */
+#define NRECS 25 /* request this much records from the SDP server */
+#define BSIZE 256 /* one attribute buffer size */
+static uint8_t buffer[NRECS * attrs_len][BSIZE];
+
+/* SDP attributes */
+static sdp_attr_t values[NRECS * attrs_len];
+#define values_len (sizeof(values)/sizeof(values[0]))
+
+/*
+ * Print Service Class ID List
+ *
+ * The ServiceClassIDList attribute consists of a data element sequence in
+ * which each data element is a UUID representing the service classes that
+ * a given service record conforms to. The UUIDs are listed in order from
+ * the most specific class to the most general class. The ServiceClassIDList
+ * must contain at least one service class UUID.
+ */
+
+static void
+print_service_class_id_list(uint8_t const *start, uint8_t const *end)
+{
+ uint32_t type, len, value;
+
+ if (end - start < 2) {
+ fprintf(stderr, "Invalid Service Class ID List. " \
+ "Too short, len=%zd\n", end - start);
+ return;
+ }
+
+ SDP_GET8(type, start);
+ switch (type) {
+ case SDP_DATA_SEQ8:
+ SDP_GET8(len, start);
+ break;
+
+ case SDP_DATA_SEQ16:
+ SDP_GET16(len, start);
+ break;
+
+ case SDP_DATA_SEQ32:
+ SDP_GET32(len, start);
+ break;
+
+ default:
+ fprintf(stderr, "Invalid Service Class ID List. " \
+ "Not a sequence, type=%#x\n", type);
+ return;
+ /* NOT REACHED */
+ }
+
+ if (len > (end - start)) {
+ fprintf(stderr, "Invalid Service Class ID List. " \
+ "Too long len=%d\n", len);
+ return;
+ }
+
+ while (start < end) {
+ SDP_GET8(type, start);
+ switch (type) {
+ case SDP_DATA_UUID16:
+ SDP_GET16(value, start);
+ fprintf(stdout, "\t%s (%#4.4x)\n",
+ sdp_uuid2desc(value), value);
+ break;
+
+ case SDP_DATA_UUID32:
+ SDP_GET32(value, start);
+ fprintf(stdout, "\t%#8.8x\n", value);
+ break;
+
+ case SDP_DATA_UUID128: {
+ int128_t uuid;
+
+ SDP_GET_UUID128(&uuid, start);
+ fprintf(stdout, "\t%#8.8x-%4.4x-%4.4x-%4.4x-%4.4x%8.8x\n",
+ ntohl(*(uint32_t *)&uuid.b[0]),
+ ntohs(*(uint16_t *)&uuid.b[4]),
+ ntohs(*(uint16_t *)&uuid.b[6]),
+ ntohs(*(uint16_t *)&uuid.b[8]),
+ ntohs(*(uint16_t *)&uuid.b[10]),
+ ntohl(*(uint32_t *)&uuid.b[12]));
+ } break;
+
+ default:
+ fprintf(stderr, "Invalid Service Class ID List. " \
+ "Not a UUID, type=%#x\n", type);
+ return;
+ /* NOT REACHED */
+ }
+ }
+} /* print_service_class_id_list */
+
+/*
+ * Print Protocol Descriptor List
+ *
+ * If the ProtocolDescriptorList describes a single stack, it takes the form
+ * of a data element sequence in which each element of the sequence is a
+ * protocol descriptor. Each protocol descriptor is, in turn, a data element
+ * sequence whose first element is a UUID identifying the protocol and whose
+ * successive elements are protocol-specific parameters. The protocol
+ * descriptors are listed in order from the lowest layer protocol to the
+ * highest layer protocol used to gain access to the service. If it is possible
+ * for more than one kind of protocol stack to be used to gain access to the
+ * service, the ProtocolDescriptorList takes the form of a data element
+ * alternative where each member is a data element sequence as described above.
+ */
+
+static void
+print_protocol_descriptor(uint8_t const *start, uint8_t const *end)
+{
+ union {
+ uint8_t uint8;
+ uint16_t uint16;
+ uint32_t uint32;
+ uint64_t uint64;
+ int128_t int128;
+ } value;
+ uint32_t type, len, param;
+
+ /* Get Protocol UUID */
+ SDP_GET8(type, start);
+ switch (type) {
+ case SDP_DATA_UUID16:
+ SDP_GET16(value.uint16, start);
+ fprintf(stdout, "\t%s (%#4.4x)\n", sdp_uuid2desc(value.uint16),
+ value.uint16);
+ break;
+
+ case SDP_DATA_UUID32:
+ SDP_GET32(value.uint32, start);
+ fprintf(stdout, "\t%#8.8x\n", value.uint32);
+ break;
+
+ case SDP_DATA_UUID128:
+ SDP_GET_UUID128(&value.int128, start);
+ fprintf(stdout, "\t%#8.8x-%4.4x-%4.4x-%4.4x-%4.4x%8.8x\n",
+ ntohl(*(uint32_t *)&value.int128.b[0]),
+ ntohs(*(uint16_t *)&value.int128.b[4]),
+ ntohs(*(uint16_t *)&value.int128.b[6]),
+ ntohs(*(uint16_t *)&value.int128.b[8]),
+ ntohs(*(uint16_t *)&value.int128.b[10]),
+ ntohl(*(uint32_t *)&value.int128.b[12]));
+ break;
+
+ default:
+ fprintf(stderr, "Invalid Protocol Descriptor. " \
+ "Not a UUID, type=%#x\n", type);
+ return;
+ /* NOT REACHED */
+ }
+
+ /* Protocol specific parameters */
+ for (param = 1; start < end; param ++) {
+ fprintf(stdout, "\t\tProtocol specific parameter #%d: ", param);
+
+ SDP_GET8(type, start);
+ switch (type) {
+ case SDP_DATA_NIL:
+ fprintf(stdout, "nil\n");
+ break;
+
+ case SDP_DATA_UINT8:
+ case SDP_DATA_INT8:
+ case SDP_DATA_BOOL:
+ SDP_GET8(value.uint8, start);
+ fprintf(stdout, "u/int8/bool %u\n", value.uint8);
+ break;
+
+ case SDP_DATA_UINT16:
+ case SDP_DATA_INT16:
+ case SDP_DATA_UUID16:
+ SDP_GET16(value.uint16, start);
+ fprintf(stdout, "u/int/uuid16 %u\n", value.uint16);
+ break;
+
+ case SDP_DATA_UINT32:
+ case SDP_DATA_INT32:
+ case SDP_DATA_UUID32:
+ SDP_GET32(value.uint32, start);
+ fprintf(stdout, "u/int/uuid32 %u\n", value.uint32);
+ break;
+
+ case SDP_DATA_UINT64:
+ case SDP_DATA_INT64:
+ SDP_GET64(value.uint64, start);
+ fprintf(stdout, "u/int64 %ju\n", value.uint64);
+ break;
+
+ case SDP_DATA_UINT128:
+ case SDP_DATA_INT128:
+ SDP_GET128(&value.int128, start);
+ fprintf(stdout, "u/int128 %#8.8x%8.8x%8.8x%8.8x\n",
+ *(uint32_t *)&value.int128.b[0],
+ *(uint32_t *)&value.int128.b[4],
+ *(uint32_t *)&value.int128.b[8],
+ *(uint32_t *)&value.int128.b[12]);
+ break;
+
+ case SDP_DATA_UUID128:
+ SDP_GET_UUID128(&value.int128, start);
+ fprintf(stdout, "uuid128 %#8.8x-%4.4x-%4.4x-%4.4x-%4.4x%8.8x\n",
+ ntohl(*(uint32_t *)&value.int128.b[0]),
+ ntohs(*(uint16_t *)&value.int128.b[4]),
+ ntohs(*(uint16_t *)&value.int128.b[6]),
+ ntohs(*(uint16_t *)&value.int128.b[8]),
+ ntohs(*(uint16_t *)&value.int128.b[10]),
+ ntohl(*(uint32_t *)&value.int128.b[12]));
+ break;
+
+ case SDP_DATA_STR8:
+ case SDP_DATA_URL8:
+ SDP_GET8(len, start);
+ for (; start < end && len > 0; start ++, len --)
+ fprintf(stdout, "%c", *start);
+ fprintf(stdout, "\n");
+ break;
+
+ case SDP_DATA_STR16:
+ case SDP_DATA_URL16:
+ SDP_GET16(len, start);
+ for (; start < end && len > 0; start ++, len --)
+ fprintf(stdout, "%c", *start);
+ fprintf(stdout, "\n");
+ break;
+
+ case SDP_DATA_STR32:
+ case SDP_DATA_URL32:
+ SDP_GET32(len, start);
+ for (; start < end && len > 0; start ++, len --)
+ fprintf(stdout, "%c", *start);
+ fprintf(stdout, "\n");
+ break;
+
+ case SDP_DATA_SEQ8:
+ case SDP_DATA_ALT8:
+ SDP_GET8(len, start);
+ for (; start < end && len > 0; start ++, len --)
+ fprintf(stdout, "%#2.2x ", *start);
+ fprintf(stdout, "\n");
+ break;
+
+ case SDP_DATA_SEQ16:
+ case SDP_DATA_ALT16:
+ SDP_GET16(len, start);
+ for (; start < end && len > 0; start ++, len --)
+ fprintf(stdout, "%#2.2x ", *start);
+ fprintf(stdout, "\n");
+ break;
+
+ case SDP_DATA_SEQ32:
+ case SDP_DATA_ALT32:
+ SDP_GET32(len, start);
+ for (; start < end && len > 0; start ++, len --)
+ fprintf(stdout, "%#2.2x ", *start);
+ fprintf(stdout, "\n");
+ break;
+
+ default:
+ fprintf(stderr, "Invalid Protocol Descriptor. " \
+ "Unknown data type: %#02x\n", type);
+ return;
+ /* NOT REACHED */
+ }
+ }
+} /* print_protocol_descriptor */
+
+static void
+print_protocol_descriptor_list(uint8_t const *start, uint8_t const *end)
+{
+ uint32_t type, len;
+
+ if (end - start < 2) {
+ fprintf(stderr, "Invalid Protocol Descriptor List. " \
+ "Too short, len=%zd\n", end - start);
+ return;
+ }
+
+ SDP_GET8(type, start);
+ switch (type) {
+ case SDP_DATA_SEQ8:
+ SDP_GET8(len, start);
+ break;
+
+ case SDP_DATA_SEQ16:
+ SDP_GET16(len, start);
+ break;
+
+ case SDP_DATA_SEQ32:
+ SDP_GET32(len, start);
+ break;
+
+ default:
+ fprintf(stderr, "Invalid Protocol Descriptor List. " \
+ "Not a sequence, type=%#x\n", type);
+ return;
+ /* NOT REACHED */
+ }
+
+ if (len > (end - start)) {
+ fprintf(stderr, "Invalid Protocol Descriptor List. " \
+ "Too long, len=%d\n", len);
+ return;
+ }
+
+ while (start < end) {
+ SDP_GET8(type, start);
+ switch (type) {
+ case SDP_DATA_SEQ8:
+ SDP_GET8(len, start);
+ break;
+
+ case SDP_DATA_SEQ16:
+ SDP_GET16(len, start);
+ break;
+
+ case SDP_DATA_SEQ32:
+ SDP_GET32(len, start);
+ break;
+
+ default:
+ fprintf(stderr, "Invalid Protocol Descriptor List. " \
+ "Not a sequence, type=%#x\n", type);
+ return;
+ /* NOT REACHED */
+ }
+
+ if (len > (end - start)) {
+ fprintf(stderr, "Invalid Protocol Descriptor List. " \
+ "Too long, len=%d\n", len);
+ return;
+ }
+
+ print_protocol_descriptor(start, start + len);
+ start += len;
+ }
+} /* print_protocol_descriptor_list */
+
+/*
+ * Print Bluetooth Profile Descriptor List
+ *
+ * The BluetoothProfileDescriptorList attribute consists of a data element
+ * sequence in which each element is a profile descriptor that contains
+ * information about a Bluetooth profile to which the service represented by
+ * this service record conforms. Each profile descriptor is a data element
+ * sequence whose first element is the UUID assigned to the profile and whose
+ * second element is a 16-bit profile version number. Each version of a profile
+ * is assigned a 16-bit unsigned integer profile version number, which consists
+ * of two 8-bit fields. The higher-order 8 bits contain the major version
+ * number field and the lower-order 8 bits contain the minor version number
+ * field.
+ */
+
+static void
+print_bluetooth_profile_descriptor_list(uint8_t const *start, uint8_t const *end)
+{
+ uint32_t type, len, value;
+
+ if (end - start < 2) {
+ fprintf(stderr, "Invalid Bluetooth Profile Descriptor List. " \
+ "Too short, len=%zd\n", end - start);
+ return;
+ }
+
+ SDP_GET8(type, start);
+ switch (type) {
+ case SDP_DATA_SEQ8:
+ SDP_GET8(len, start);
+ break;
+
+ case SDP_DATA_SEQ16:
+ SDP_GET16(len, start);
+ break;
+
+ case SDP_DATA_SEQ32:
+ SDP_GET32(len, start);
+ break;
+
+ default:
+ fprintf(stderr, "Invalid Bluetooth Profile Descriptor List. " \
+ "Not a sequence, type=%#x\n", type);
+ return;
+ /* NOT REACHED */
+ }
+
+ if (len > (end - start)) {
+ fprintf(stderr, "Invalid Bluetooth Profile Descriptor List. " \
+ "Too long, len=%d\n", len);
+ return;
+ }
+
+ while (start < end) {
+ SDP_GET8(type, start);
+ switch (type) {
+ case SDP_DATA_SEQ8:
+ SDP_GET8(len, start);
+ break;
+
+ case SDP_DATA_SEQ16:
+ SDP_GET16(len, start);
+ break;
+
+ case SDP_DATA_SEQ32:
+ SDP_GET32(len, start);
+ break;
+
+ default:
+ fprintf(stderr, "Invalid Bluetooth Profile " \
+ "Descriptor List. " \
+ "Not a sequence, type=%#x\n", type);
+ return;
+ /* NOT REACHED */
+ }
+
+ if (len > (end - start)) {
+ fprintf(stderr, "Invalid Bluetooth Profile " \
+ "Descriptor List. " \
+ "Too long, len=%d\n", len);
+ return;
+ }
+
+ /* Get UUID */
+ SDP_GET8(type, start);
+ switch (type) {
+ case SDP_DATA_UUID16:
+ SDP_GET16(value, start);
+ fprintf(stdout, "\t%s (%#4.4x) ",
+ sdp_uuid2desc(value), value);
+ break;
+
+ case SDP_DATA_UUID32:
+ SDP_GET32(value, start);
+ fprintf(stdout, "\t%#8.8x ", value);
+ break;
+
+ case SDP_DATA_UUID128: {
+ int128_t uuid;
+
+ SDP_GET_UUID128(&uuid, start);
+ fprintf(stdout, "\t%#8.8x-%4.4x-%4.4x-%4.4x-%4.4x%8.8x ",
+ ntohl(*(uint32_t *)&uuid.b[0]),
+ ntohs(*(uint16_t *)&uuid.b[4]),
+ ntohs(*(uint16_t *)&uuid.b[6]),
+ ntohs(*(uint16_t *)&uuid.b[8]),
+ ntohs(*(uint16_t *)&uuid.b[10]),
+ ntohl(*(uint32_t *)&uuid.b[12]));
+ } break;
+
+ default:
+ fprintf(stderr, "Invalid Bluetooth Profile " \
+ "Descriptor List. " \
+ "Not a UUID, type=%#x\n", type);
+ return;
+ /* NOT REACHED */
+ }
+
+ /* Get version */
+ SDP_GET8(type, start);
+ if (type != SDP_DATA_UINT16) {
+ fprintf(stderr, "Invalid Bluetooth Profile " \
+ "Descriptor List. " \
+ "Invalid version type=%#x\n", type);
+ return;
+ }
+
+ SDP_GET16(value, start);
+ fprintf(stdout, "ver. %d.%d\n",
+ (value >> 8) & 0xff, value & 0xff);
+ }
+} /* print_bluetooth_profile_descriptor_list */
+
+/* Perform SDP search command */
+static int
+do_sdp_search(void *xs, int argc, char **argv)
+{
+ char *ep = NULL;
+ int32_t n, type, value;
+ uint16_t service;
+
+ /* Parse command line arguments */
+ switch (argc) {
+ case 1:
+ n = strtoul(argv[0], &ep, 16);
+ if (*ep != 0) {
+ switch (tolower(argv[0][0])) {
+ case 'c': /* CIP/CTP */
+ switch (tolower(argv[0][1])) {
+ case 'i':
+ service = SDP_SERVICE_CLASS_COMMON_ISDN_ACCESS;
+ break;
+
+ case 't':
+ service = SDP_SERVICE_CLASS_CORDLESS_TELEPHONY;
+ break;
+
+ default:
+ return (USAGE);
+ /* NOT REACHED */
+ }
+ break;
+
+ case 'd': /* DialUp Networking */
+ service = SDP_SERVICE_CLASS_DIALUP_NETWORKING;
+ break;
+
+ case 'f': /* Fax/OBEX File Transfer */
+ switch (tolower(argv[0][1])) {
+ case 'a':
+ service = SDP_SERVICE_CLASS_FAX;
+ break;
+
+ case 't':
+ service = SDP_SERVICE_CLASS_OBEX_FILE_TRANSFER;
+ break;
+
+ default:
+ return (USAGE);
+ /* NOT REACHED */
+ }
+ break;
+
+ case 'g': /* GN */
+ service = SDP_SERVICE_CLASS_GN;
+ break;
+
+ case 'h': /* Headset/HID */
+ switch (tolower(argv[0][1])) {
+ case 'i':
+ service = SDP_SERVICE_CLASS_HUMAN_INTERFACE_DEVICE;
+ break;
+
+ case 's':
+ service = SDP_SERVICE_CLASS_HEADSET;
+ break;
+
+ default:
+ return (USAGE);
+ /* NOT REACHED */
+ }
+ break;
+
+ case 'l': /* LAN Access Using PPP */
+ service = SDP_SERVICE_CLASS_LAN_ACCESS_USING_PPP;
+ break;
+
+ case 'n': /* NAP */
+ service = SDP_SERVICE_CLASS_NAP;
+ break;
+
+ case 'o': /* OBEX Object Push */
+ service = SDP_SERVICE_CLASS_OBEX_OBJECT_PUSH;
+ break;
+
+ case 's': /* Serial Port */
+ service = SDP_SERVICE_CLASS_SERIAL_PORT;
+ break;
+
+ default:
+ return (USAGE);
+ /* NOT REACHED */
+ }
+ } else
+ service = (uint16_t) n;
+ break;
+
+ default:
+ return (USAGE);
+ }
+
+ /* Initialize attribute values array */
+ for (n = 0; n < values_len; n ++) {
+ values[n].flags = SDP_ATTR_INVALID;
+ values[n].attr = 0;
+ values[n].vlen = BSIZE;
+ values[n].value = buffer[n];
+ }
+
+ /* Do SDP Service Search Attribute Request */
+ n = sdp_search(xs, 1, &service, attrs_len, attrs, values_len, values);
+ if (n != 0)
+ return (ERROR);
+
+ /* Print attributes values */
+ for (n = 0; n < values_len; n ++) {
+ if (values[n].flags != SDP_ATTR_OK)
+ break;
+
+ switch (values[n].attr) {
+ case SDP_ATTR_SERVICE_RECORD_HANDLE:
+ fprintf(stdout, "\n");
+ if (values[n].vlen == 5) {
+ SDP_GET8(type, values[n].value);
+ if (type == SDP_DATA_UINT32) {
+ SDP_GET32(value, values[n].value);
+ fprintf(stdout, "Record Handle: " \
+ "%#8.8x\n", value);
+ } else
+ fprintf(stderr, "Invalid type=%#x " \
+ "Record Handle " \
+ "attribute!\n", type);
+ } else
+ fprintf(stderr, "Invalid size=%d for Record " \
+ "Handle attribute\n",
+ values[n].vlen);
+ break;
+
+ case SDP_ATTR_SERVICE_CLASS_ID_LIST:
+ fprintf(stdout, "Service Class ID List:\n");
+ print_service_class_id_list(values[n].value,
+ values[n].value + values[n].vlen);
+ break;
+
+ case SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST:
+ fprintf(stdout, "Protocol Descriptor List:\n");
+ print_protocol_descriptor_list(values[n].value,
+ values[n].value + values[n].vlen);
+ break;
+
+ case SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST:
+ fprintf(stdout, "Bluetooth Profile Descriptor List:\n");
+ print_bluetooth_profile_descriptor_list(values[n].value,
+ values[n].value + values[n].vlen);
+ break;
+
+ default:
+ fprintf(stderr, "Unexpected attribute ID=%#4.4x\n",
+ values[n].attr);
+ break;
+ }
+ }
+
+ return (OK);
+} /* do_sdp_search */
+
+/* Perform SDP browse command */
+static int
+do_sdp_browse(void *xs, int argc, char **argv)
+{
+#undef _STR
+#undef STR
+#define _STR(x) #x
+#define STR(x) _STR(x)
+
+ static char const * const av[] = {
+ STR(SDP_SERVICE_CLASS_PUBLIC_BROWSE_GROUP),
+ NULL
+ };
+
+ switch (argc) {
+ case 0:
+ argc = 1;
+ argv = (char **) av;
+ /* FALL THROUGH */
+ case 1:
+ return (do_sdp_search(xs, argc, argv));
+ }
+
+ return (USAGE);
+} /* do_sdp_browse */
+
+/* List of SDP commands */
+struct sdp_command sdp_commands[] = {
+{
+"Browse [<Group>]",
+"Browse for services. The <Group> parameter is a 16-bit UUID of the group\n" \
+"to browse. If omitted <Group> is set to Public Browse Group.\n\n" \
+"\t<Group> - xxxx; 16-bit UUID of the group to browse\n",
+do_sdp_browse
+},
+{
+"Search <Service>",
+"Search for the <Service>. The <Service> parameter is a 16-bit UUID of the\n" \
+"service to search for. For some services it is possible to use service name\n"\
+"instead of service UUID\n\n" \
+"\t<Service> - xxxx; 16-bit UUID of the service to search for\n\n" \
+"\tKnown service names\n" \
+"\t===================\n" \
+"\tCIP - Common ISDN Access\n" \
+"\tCTP - Cordless Telephony\n" \
+"\tDUN - DialUp Networking\n" \
+"\tFAX - Fax\n" \
+"\tFTRN - OBEX File Transfer\n" \
+"\tGN - GN\n" \
+"\tHID - Human Interface Device\n" \
+"\tHSET - Headset\n" \
+"\tLAN - LAN Access Using PPP\n" \
+"\tNAP - Network Access Point\n" \
+"\tOPUSH - OBEX Object Push\n" \
+"\tSP - Serial Port\n",
+do_sdp_search
+},
+{ NULL, NULL, NULL }
+};
+
diff --git a/usr.sbin/bluetooth/sdpd/Makefile b/usr.sbin/bluetooth/sdpd/Makefile
new file mode 100644
index 0000000..c47441f
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/Makefile
@@ -0,0 +1,13 @@
+# $Id: Makefile,v 1.1 2004/01/20 21:27:55 max Exp $
+# $FreeBSD$
+
+PROG= sdpd
+MAN= sdpd.8
+SRCS= bgd.c dun.c ftrn.c gn.c irmc.c irmc_command.c lan.c log.c \
+ main.c nap.c opush.c panu.c profile.c provider.c sar.c scr.c \
+ sd.c server.c sp.c srr.c ssar.c ssr.c sur.c uuid.c
+
+CFLAGS+= -I${.CURDIR}
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bluetooth/sdpd/Makefile.depend b/usr.sbin/bluetooth/sdpd/Makefile.depend
new file mode 100644
index 0000000..ce65fbb
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbluetooth \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libsdp \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bluetooth/sdpd/bgd.c b/usr.sbin/bluetooth/sdpd/bgd.c
new file mode 100644
index 0000000..2c4e4d9
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/bgd.c
@@ -0,0 +1,102 @@
+/*
+ * bgd.c
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: bgd.c,v 1.4 2004/01/13 01:54:39 max Exp $
+ * $FreeBSD$
+ */
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <sdp.h>
+#include <string.h>
+#include "profile.h"
+
+static int32_t
+bgd_profile_create_service_class_id_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static uint16_t service_classes[] = {
+ SDP_SERVICE_CLASS_BROWSE_GROUP_DESCRIPTOR
+ };
+
+ return (common_profile_create_service_class_id_list(
+ buf, eob,
+ (uint8_t const *) service_classes,
+ sizeof(service_classes)));
+}
+
+static int32_t
+bgd_profile_create_service_name(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static char service_name[] = "Public Browse Group Root";
+
+ return (common_profile_create_string8(
+ buf, eob,
+ (uint8_t const *) service_name, strlen(service_name)));
+}
+
+static int32_t
+bgd_profile_create_group_id(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ if (buf + 3 > eob)
+ return (-1);
+
+ SDP_PUT8(SDP_DATA_UUID16, buf);
+ SDP_PUT16(SDP_SERVICE_CLASS_PUBLIC_BROWSE_GROUP, buf);
+
+ return (3);
+}
+
+static attr_t bgd_profile_attrs[] = {
+ { SDP_ATTR_SERVICE_RECORD_HANDLE,
+ common_profile_create_service_record_handle },
+ { SDP_ATTR_SERVICE_CLASS_ID_LIST,
+ bgd_profile_create_service_class_id_list },
+ { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST,
+ common_profile_create_language_base_attribute_id_list },
+ { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET,
+ bgd_profile_create_service_name },
+ { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_DESCRIPTION_OFFSET,
+ bgd_profile_create_service_name },
+ { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_PROVIDER_NAME_OFFSET,
+ common_profile_create_service_provider_name },
+ { SDP_ATTR_GROUP_ID,
+ bgd_profile_create_group_id },
+ { 0, NULL } /* end entry */
+};
+
+profile_t bgd_profile_descriptor = {
+ SDP_SERVICE_CLASS_BROWSE_GROUP_DESCRIPTOR,
+ 0,
+ (profile_data_valid_p) NULL,
+ (attr_t const * const) &bgd_profile_attrs
+};
+
diff --git a/usr.sbin/bluetooth/sdpd/dun.c b/usr.sbin/bluetooth/sdpd/dun.c
new file mode 100644
index 0000000..1496285
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/dun.c
@@ -0,0 +1,137 @@
+/*
+ * dun.c
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: dun.c,v 1.5 2004/01/13 01:54:39 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <sdp.h>
+#include <string.h>
+#include "profile.h"
+#include "provider.h"
+
+static int32_t
+dun_profile_create_service_class_id_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static uint16_t service_classes[] = {
+ SDP_SERVICE_CLASS_DIALUP_NETWORKING
+ };
+
+ return (common_profile_create_service_class_id_list(
+ buf, eob,
+ (uint8_t const *) service_classes,
+ sizeof(service_classes)));
+}
+
+static int32_t
+dun_profile_create_bluetooth_profile_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static uint16_t profile_descriptor_list[] = {
+ SDP_SERVICE_CLASS_DIALUP_NETWORKING,
+ 0x0100
+ };
+
+ return (common_profile_create_bluetooth_profile_descriptor_list(
+ buf, eob,
+ (uint8_t const *) profile_descriptor_list,
+ sizeof(profile_descriptor_list)));
+}
+
+static int32_t
+dun_profile_create_service_name(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static char service_name[] = "DialUp networking";
+
+ return (common_profile_create_string8(
+ buf, eob,
+ (uint8_t const *) service_name, strlen(service_name)));
+}
+
+static int32_t
+dun_profile_create_protocol_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = (provider_p) data;
+ sdp_dun_profile_p dun = (sdp_dun_profile_p) provider->data;
+
+ return (rfcomm_profile_create_protocol_descriptor_list(
+ buf, eob,
+ (uint8_t const *) &dun->server_channel, 1));
+}
+
+static int32_t
+dun_profile_create_audio_feedback_support(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = (provider_p) data;
+ sdp_dun_profile_p dun = (sdp_dun_profile_p) provider->data;
+
+ if (buf + 2 > eob)
+ return (-1);
+
+ SDP_PUT8(SDP_DATA_BOOL, buf);
+ SDP_PUT8(dun->audio_feedback_support, buf);
+
+ return (2);
+}
+
+static attr_t dun_profile_attrs[] = {
+ { SDP_ATTR_SERVICE_RECORD_HANDLE,
+ common_profile_create_service_record_handle },
+ { SDP_ATTR_SERVICE_CLASS_ID_LIST,
+ dun_profile_create_service_class_id_list },
+ { SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST,
+ dun_profile_create_bluetooth_profile_descriptor_list },
+ { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST,
+ common_profile_create_language_base_attribute_id_list },
+ { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET,
+ dun_profile_create_service_name },
+ { SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST,
+ dun_profile_create_protocol_descriptor_list },
+ { SDP_ATTR_AUDIO_FEEDBACK_SUPPORT,
+ dun_profile_create_audio_feedback_support },
+ { 0, NULL } /* end entry */
+};
+
+profile_t dun_profile_descriptor = {
+ SDP_SERVICE_CLASS_DIALUP_NETWORKING,
+ sizeof(sdp_dun_profile_t),
+ common_profile_server_channel_valid,
+ (attr_t const * const) &dun_profile_attrs
+};
+
diff --git a/usr.sbin/bluetooth/sdpd/ftrn.c b/usr.sbin/bluetooth/sdpd/ftrn.c
new file mode 100644
index 0000000..bc1095e
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/ftrn.c
@@ -0,0 +1,118 @@
+/*
+ * ftrn.c
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: ftrn.c,v 1.5 2004/01/13 01:54:39 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <sdp.h>
+#include <string.h>
+#include "profile.h"
+#include "provider.h"
+
+static int32_t
+ftrn_profile_create_service_class_id_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static uint16_t service_classes[] = {
+ SDP_SERVICE_CLASS_OBEX_FILE_TRANSFER
+ };
+
+ return (common_profile_create_service_class_id_list(
+ buf, eob,
+ (uint8_t const *) service_classes,
+ sizeof(service_classes)));
+}
+
+static int32_t
+ftrn_profile_create_bluetooth_profile_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static uint16_t profile_descriptor_list[] = {
+ SDP_SERVICE_CLASS_OBEX_FILE_TRANSFER,
+ 0x0100
+ };
+
+ return (common_profile_create_bluetooth_profile_descriptor_list(
+ buf, eob,
+ (uint8_t const *) profile_descriptor_list,
+ sizeof(profile_descriptor_list)));
+}
+
+static int32_t
+ftrn_profile_create_service_name(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static char service_name[] = "OBEX File Transfer";
+
+ return (common_profile_create_string8(
+ buf, eob,
+ (uint8_t const *) service_name, strlen(service_name)));
+}
+
+static int32_t
+ftrn_profile_create_protocol_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = (provider_p) data;
+ sdp_ftrn_profile_p ftrn = (sdp_ftrn_profile_p) provider->data;
+
+ return (obex_profile_create_protocol_descriptor_list(
+ buf, eob,
+ (uint8_t const *) &ftrn->server_channel, 1));
+}
+
+static attr_t ftrn_profile_attrs[] = {
+ { SDP_ATTR_SERVICE_RECORD_HANDLE,
+ common_profile_create_service_record_handle },
+ { SDP_ATTR_SERVICE_CLASS_ID_LIST,
+ ftrn_profile_create_service_class_id_list },
+ { SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST,
+ ftrn_profile_create_bluetooth_profile_descriptor_list },
+ { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST,
+ common_profile_create_language_base_attribute_id_list },
+ { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET,
+ ftrn_profile_create_service_name },
+ { SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST,
+ ftrn_profile_create_protocol_descriptor_list },
+ { 0, NULL } /* end entry */
+};
+
+profile_t ftrn_profile_descriptor = {
+ SDP_SERVICE_CLASS_OBEX_FILE_TRANSFER,
+ sizeof(sdp_ftrn_profile_t),
+ common_profile_server_channel_valid,
+ (attr_t const * const) &ftrn_profile_attrs
+};
+
diff --git a/usr.sbin/bluetooth/sdpd/gn.c b/usr.sbin/bluetooth/sdpd/gn.c
new file mode 100644
index 0000000..c2bea48
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/gn.c
@@ -0,0 +1,173 @@
+/*
+ * gn.c
+ */
+
+/*-
+ * Copyright (c) 2008 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: gn.c,v 1.1 2008/03/11 00:02:42 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <sdp.h>
+#include <string.h>
+#include "profile.h"
+#include "provider.h"
+
+static int32_t
+gn_profile_create_service_class_id_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static uint16_t service_classes[] = {
+ SDP_SERVICE_CLASS_GN
+ };
+
+ return (common_profile_create_service_class_id_list(
+ buf, eob,
+ (uint8_t const *) service_classes,
+ sizeof(service_classes)));
+}
+
+static int32_t
+gn_profile_create_bluetooth_profile_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static uint16_t profile_descriptor_list[] = {
+ SDP_SERVICE_CLASS_GN,
+ 0x0100
+ };
+
+ return (common_profile_create_bluetooth_profile_descriptor_list(
+ buf, eob,
+ (uint8_t const *) profile_descriptor_list,
+ sizeof(profile_descriptor_list)));
+}
+
+static int32_t
+gn_profile_create_service_name(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static char service_name[] = "Group Ad-hoc Network";
+
+ return (common_profile_create_string8(
+ buf, eob,
+ (uint8_t const *) service_name, strlen(service_name)));
+}
+
+static int32_t
+gn_profile_create_service_description(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static char service_descr[] =
+ "Personal Group Ad-hoc Network Service";
+
+ return (common_profile_create_string8(
+ buf, eob,
+ (uint8_t const *) service_descr,
+ strlen(service_descr)));
+}
+
+static int32_t
+gn_profile_create_protocol_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = (provider_p) data;
+ sdp_gn_profile_p gn = (sdp_gn_profile_p) provider->data;
+
+ return (bnep_profile_create_protocol_descriptor_list(
+ buf, eob, (uint8_t const *) &gn->psm,
+ sizeof(gn->psm)));
+}
+
+static int32_t
+gn_profile_create_security_description(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = (provider_p) data;
+ sdp_gn_profile_p gn = (sdp_gn_profile_p) provider->data;
+
+ return (bnep_profile_create_security_description(buf, eob,
+ (uint8_t const *) &gn->security_description,
+ sizeof(gn->security_description)));
+}
+
+static int32_t
+gn_profile_create_service_availability(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = (provider_p) data;
+ sdp_gn_profile_p gn = (sdp_gn_profile_p) provider->data;
+
+ return (common_profile_create_service_availability(buf, eob,
+ &gn->load_factor, 1));
+}
+
+static int32_t
+gn_profile_data_valid(uint8_t const *data, uint32_t datalen)
+{
+ sdp_gn_profile_p gn = (sdp_gn_profile_p) data;
+
+ return ((gn->psm == 0)? 0 : 1);
+}
+
+static attr_t gn_profile_attrs[] = {
+ { SDP_ATTR_SERVICE_RECORD_HANDLE,
+ common_profile_create_service_record_handle },
+ { SDP_ATTR_SERVICE_CLASS_ID_LIST,
+ gn_profile_create_service_class_id_list },
+ { SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST,
+ gn_profile_create_protocol_descriptor_list },
+ { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST,
+ common_profile_create_language_base_attribute_id_list },
+ { SDP_ATTR_SERVICE_AVAILABILITY,
+ gn_profile_create_service_availability },
+ { SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST,
+ gn_profile_create_bluetooth_profile_descriptor_list },
+ { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET,
+ gn_profile_create_service_name },
+ { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_DESCRIPTION_OFFSET,
+ gn_profile_create_service_description },
+ { SDP_ATTR_SECURITY_DESCRIPTION,
+ gn_profile_create_security_description },
+ { 0, NULL } /* end entry */
+};
+
+profile_t gn_profile_descriptor = {
+ SDP_SERVICE_CLASS_GN,
+ sizeof(sdp_gn_profile_t),
+ gn_profile_data_valid,
+ (attr_t const * const) &gn_profile_attrs
+};
+
diff --git a/usr.sbin/bluetooth/sdpd/irmc.c b/usr.sbin/bluetooth/sdpd/irmc.c
new file mode 100644
index 0000000..2b8f317
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/irmc.c
@@ -0,0 +1,134 @@
+/*
+ * irmc.c
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: irmc.c,v 1.6 2004/01/13 19:31:54 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <sdp.h>
+#include <string.h>
+#include "profile.h"
+#include "provider.h"
+
+static int32_t
+irmc_profile_create_service_class_id_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static uint16_t service_classes[] = {
+ SDP_SERVICE_CLASS_IR_MC_SYNC
+ };
+
+ return (common_profile_create_service_class_id_list(
+ buf, eob,
+ (uint8_t const *) service_classes,
+ sizeof(service_classes)));
+}
+
+static int32_t
+irmc_profile_create_bluetooth_profile_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static uint16_t profile_descriptor_list[] = {
+ SDP_SERVICE_CLASS_IR_MC_SYNC,
+ 0x0100
+ };
+
+ return (common_profile_create_bluetooth_profile_descriptor_list(
+ buf, eob,
+ (uint8_t const *) profile_descriptor_list,
+ sizeof(profile_descriptor_list)));
+}
+
+static int32_t
+irmc_profile_create_service_name(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static char service_name[] = "IrMC Synchronization";
+
+ return (common_profile_create_string8(
+ buf, eob,
+ (uint8_t const *) service_name, strlen(service_name)));
+}
+
+static int32_t
+irmc_profile_create_protocol_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = (provider_p) data;
+ sdp_irmc_profile_p irmc = (sdp_irmc_profile_p) provider->data;
+
+ return (obex_profile_create_protocol_descriptor_list(
+ buf, eob,
+ (uint8_t const *) &irmc->server_channel, 1));
+}
+
+static int32_t
+irmc_profile_create_supported_formats_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = (provider_p) data;
+ sdp_irmc_profile_p irmc = (sdp_irmc_profile_p) provider->data;
+
+ return (obex_profile_create_supported_formats_list(
+ buf, eob,
+ (uint8_t const *) irmc->supported_formats,
+ irmc->supported_formats_size));
+}
+
+static attr_t irmc_profile_attrs[] = {
+ { SDP_ATTR_SERVICE_RECORD_HANDLE,
+ common_profile_create_service_record_handle },
+ { SDP_ATTR_SERVICE_CLASS_ID_LIST,
+ irmc_profile_create_service_class_id_list },
+ { SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST,
+ irmc_profile_create_bluetooth_profile_descriptor_list },
+ { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST,
+ common_profile_create_language_base_attribute_id_list },
+ { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET,
+ irmc_profile_create_service_name },
+ { SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST,
+ irmc_profile_create_protocol_descriptor_list },
+ { SDP_ATTR_SUPPORTED_FORMATS_LIST,
+ irmc_profile_create_supported_formats_list },
+ { 0, NULL } /* end entry */
+};
+
+profile_t irmc_profile_descriptor = {
+ SDP_SERVICE_CLASS_IR_MC_SYNC,
+ sizeof(sdp_irmc_profile_t),
+ obex_profile_data_valid,
+ (attr_t const * const) &irmc_profile_attrs
+};
+
diff --git a/usr.sbin/bluetooth/sdpd/irmc_command.c b/usr.sbin/bluetooth/sdpd/irmc_command.c
new file mode 100644
index 0000000..a55e133
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/irmc_command.c
@@ -0,0 +1,118 @@
+/*
+ * irmc_command_command_command.c
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: irmc_command.c,v 1.5 2004/01/13 01:54:39 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <sdp.h>
+#include <string.h>
+#include "profile.h"
+#include "provider.h"
+
+static int32_t
+irmc_command_profile_create_service_class_id_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static uint16_t service_classes[] = {
+ SDP_SERVICE_CLASS_IR_MC_SYNC_COMMAND
+ };
+
+ return (common_profile_create_service_class_id_list(
+ buf, eob,
+ (uint8_t const *) service_classes,
+ sizeof(service_classes)));
+}
+
+static int32_t
+irmc_command_profile_create_bluetooth_profile_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static uint16_t profile_descriptor_list[] = {
+ SDP_SERVICE_CLASS_IR_MC_SYNC_COMMAND,
+ 0x0100
+ };
+
+ return (common_profile_create_bluetooth_profile_descriptor_list(
+ buf, eob,
+ (uint8_t const *) profile_descriptor_list,
+ sizeof(profile_descriptor_list)));
+}
+
+static int32_t
+irmc_command_profile_create_service_name(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static char service_name[] = "Sync Command Service";
+
+ return (common_profile_create_string8(
+ buf, eob,
+ (uint8_t const *) service_name, strlen(service_name)));
+}
+
+static int32_t
+irmc_command_profile_create_protocol_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = (provider_p) data;
+ sdp_irmc_command_profile_p irmc_command = (sdp_irmc_command_profile_p) provider->data;
+
+ return (obex_profile_create_protocol_descriptor_list(
+ buf, eob,
+ (uint8_t const *) &irmc_command->server_channel, 1));
+}
+
+static attr_t irmc_command_profile_attrs[] = {
+ { SDP_ATTR_SERVICE_RECORD_HANDLE,
+ common_profile_create_service_record_handle },
+ { SDP_ATTR_SERVICE_CLASS_ID_LIST,
+ irmc_command_profile_create_service_class_id_list },
+ { SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST,
+ irmc_command_profile_create_bluetooth_profile_descriptor_list },
+ { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST,
+ common_profile_create_language_base_attribute_id_list },
+ { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET,
+ irmc_command_profile_create_service_name },
+ { SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST,
+ irmc_command_profile_create_protocol_descriptor_list },
+ { 0, NULL } /* end entry */
+};
+
+profile_t irmc_command_profile_descriptor = {
+ SDP_SERVICE_CLASS_IR_MC_SYNC_COMMAND,
+ sizeof(sdp_irmc_command_profile_t),
+ common_profile_server_channel_valid,
+ (attr_t const * const) &irmc_command_profile_attrs
+};
+
diff --git a/usr.sbin/bluetooth/sdpd/lan.c b/usr.sbin/bluetooth/sdpd/lan.c
new file mode 100644
index 0000000..3ed2b12
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/lan.c
@@ -0,0 +1,173 @@
+/*
+ * lan.c
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: lan.c,v 1.5 2004/01/13 01:54:39 max Exp $
+ * $FreeBSD$
+ */
+
+#include <arpa/inet.h>
+#include <sys/queue.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <sdp.h>
+#include <stdio.h>
+#include <string.h>
+#include "profile.h"
+#include "provider.h"
+
+static int32_t
+lan_profile_create_service_class_id_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static uint16_t service_classes[] = {
+ SDP_SERVICE_CLASS_LAN_ACCESS_USING_PPP
+ };
+
+ return (common_profile_create_service_class_id_list(
+ buf, eob,
+ (uint8_t const *) service_classes,
+ sizeof(service_classes)));
+}
+
+static int32_t
+lan_profile_create_bluetooth_profile_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static uint16_t profile_descriptor_list[] = {
+ SDP_SERVICE_CLASS_LAN_ACCESS_USING_PPP,
+ 0x0100
+ };
+
+ return (common_profile_create_bluetooth_profile_descriptor_list(
+ buf, eob,
+ (uint8_t const *) profile_descriptor_list,
+ sizeof(profile_descriptor_list)));
+}
+
+static int32_t
+lan_profile_create_service_name(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static char service_name[] = "LAN Access using PPP";
+
+ return (common_profile_create_string8(
+ buf, eob,
+ (uint8_t const *) service_name, strlen(service_name)));
+}
+
+static int32_t
+lan_profile_create_protocol_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = (provider_p) data;
+ sdp_lan_profile_p lan = (sdp_lan_profile_p) provider->data;
+
+ return (rfcomm_profile_create_protocol_descriptor_list(
+ buf, eob,
+ (uint8_t const *) &lan->server_channel, 1));
+}
+
+static int32_t
+lan_profile_create_service_availability(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = (provider_p) data;
+ sdp_lan_profile_p lan = (sdp_lan_profile_p) provider->data;
+
+ return (common_profile_create_service_availability(buf, eob,
+ &lan->load_factor, 1));
+}
+
+static int32_t
+lan_profile_create_ip_subnet(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = (provider_p) data;
+ sdp_lan_profile_p lan = (sdp_lan_profile_p) provider->data;
+ char net[32];
+ int32_t len;
+
+ len = snprintf(net, sizeof(net), "%s/%d",
+ inet_ntoa(* (struct in_addr *) &lan->ip_subnet),
+ lan->ip_subnet_radius);
+
+ if (len < 0 || buf + 2 + len > eob)
+ return (-1);
+
+ SDP_PUT8(SDP_DATA_STR8, buf);
+ SDP_PUT8(len, buf);
+ memcpy(buf, net, len);
+
+ return (2 + len);
+}
+
+static int32_t
+lan_profile_data_valid(uint8_t const *data, uint32_t datalen)
+{
+ sdp_lan_profile_p lan = (sdp_lan_profile_p) data;
+
+ if (lan->server_channel < 1 ||
+ lan->server_channel > 30 ||
+ lan->ip_subnet_radius > 32)
+ return (0);
+
+ return (1);
+}
+
+static attr_t lan_profile_attrs[] = {
+ { SDP_ATTR_SERVICE_RECORD_HANDLE,
+ common_profile_create_service_record_handle },
+ { SDP_ATTR_SERVICE_CLASS_ID_LIST,
+ lan_profile_create_service_class_id_list },
+ { SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST,
+ lan_profile_create_bluetooth_profile_descriptor_list },
+ { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST,
+ common_profile_create_language_base_attribute_id_list },
+ { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET,
+ lan_profile_create_service_name },
+ { SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST,
+ lan_profile_create_protocol_descriptor_list },
+ { SDP_ATTR_SERVICE_AVAILABILITY,
+ lan_profile_create_service_availability },
+ { SDP_ATTR_IP_SUBNET,
+ lan_profile_create_ip_subnet },
+ { 0, NULL } /* end entry */
+};
+
+profile_t lan_profile_descriptor = {
+ SDP_SERVICE_CLASS_LAN_ACCESS_USING_PPP,
+ sizeof(sdp_lan_profile_t),
+ lan_profile_data_valid,
+ (attr_t const * const) &lan_profile_attrs
+};
+
diff --git a/usr.sbin/bluetooth/sdpd/log.c b/usr.sbin/bluetooth/sdpd/log.c
new file mode 100644
index 0000000..b03f7e1
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/log.c
@@ -0,0 +1,127 @@
+/*
+ * log.c
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: log.c,v 1.1 2004/01/07 23:15:00 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <stdarg.h>
+#include <syslog.h>
+
+void
+log_open(char const *prog, int32_t log2stderr)
+{
+ openlog(prog, LOG_PID|LOG_NDELAY|(log2stderr? LOG_PERROR:0), LOG_USER);
+}
+
+void
+log_close(void)
+{
+ closelog();
+}
+
+void
+log_emerg(char const *message, ...)
+{
+ va_list ap;
+
+ va_start(ap, message);
+ vsyslog(LOG_EMERG, message, ap);
+ va_end(ap);
+}
+
+void
+log_alert(char const *message, ...)
+{
+ va_list ap;
+
+ va_start(ap, message);
+ vsyslog(LOG_ALERT, message, ap);
+ va_end(ap);
+}
+
+void
+log_crit(char const *message, ...)
+{
+ va_list ap;
+
+ va_start(ap, message);
+ vsyslog(LOG_CRIT, message, ap);
+ va_end(ap);
+}
+
+void
+log_err(char const *message, ...)
+{
+ va_list ap;
+
+ va_start(ap, message);
+ vsyslog(LOG_ERR, message, ap);
+ va_end(ap);
+}
+
+void
+log_warning(char const *message, ...)
+{
+ va_list ap;
+
+ va_start(ap, message);
+ vsyslog(LOG_WARNING, message, ap);
+ va_end(ap);
+}
+
+void
+log_notice(char const *message, ...)
+{
+ va_list ap;
+
+ va_start(ap, message);
+ vsyslog(LOG_NOTICE, message, ap);
+ va_end(ap);
+}
+
+void
+log_info(char const *message, ...)
+{
+ va_list ap;
+
+ va_start(ap, message);
+ vsyslog(LOG_INFO, message, ap);
+ va_end(ap);
+}
+
+void
+log_debug(char const *message, ...)
+{
+ va_list ap;
+
+ va_start(ap, message);
+ vsyslog(LOG_DEBUG, message, ap);
+ va_end(ap);
+}
+
diff --git a/usr.sbin/bluetooth/sdpd/log.h b/usr.sbin/bluetooth/sdpd/log.h
new file mode 100644
index 0000000..8c9ce77
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/log.h
@@ -0,0 +1,47 @@
+/*
+ * log.h
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: log.h,v 1.1 2004/01/07 23:15:00 max Exp $
+ * $FreeBSD$
+ */
+
+#ifndef _LOG_H_
+#define _LOG_H_
+
+void log_open (char const *prog, int32_t log2stderr);
+void log_close (void);
+void log_emerg (char const *message, ...);
+void log_alert (char const *message, ...);
+void log_crit (char const *message, ...);
+void log_err (char const *message, ...);
+void log_warning (char const *message, ...);
+void log_notice (char const *message, ...);
+void log_info (char const *message, ...);
+void log_debug (char const *message, ...);
+
+#endif /* ndef _LOG_H_ */
+
diff --git a/usr.sbin/bluetooth/sdpd/main.c b/usr.sbin/bluetooth/sdpd/main.c
new file mode 100644
index 0000000..a287b9b
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/main.c
@@ -0,0 +1,236 @@
+/*
+ * main.c
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: main.c,v 1.8 2004/01/13 19:31:54 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/select.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <signal.h>
+#include <sdp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "log.h"
+#include "server.h"
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/queue.h>
+#include "profile.h"
+#include "provider.h"
+
+#define SDPD "sdpd"
+
+static int32_t drop_root (char const *user, char const *group);
+static void sighandler (int32_t s);
+static void usage (void);
+
+static int32_t done;
+
+/*
+ * Bluetooth Service Discovery Procotol (SDP) daemon
+ */
+
+int
+main(int argc, char *argv[])
+{
+ server_t server;
+ char const *control = SDP_LOCAL_PATH;
+ char const *user = "nobody", *group = "nobody";
+ int32_t detach = 1, opt;
+ struct sigaction sa;
+
+ while ((opt = getopt(argc, argv, "c:dg:hu:")) != -1) {
+ switch (opt) {
+ case 'c': /* control */
+ control = optarg;
+ break;
+
+ case 'd': /* do not detach */
+ detach = 0;
+ break;
+
+ case 'g': /* group */
+ group = optarg;
+ break;
+
+ case 'u': /* user */
+ user = optarg;
+ break;
+
+ case 'h':
+ default:
+ usage();
+ /* NOT REACHED */
+ }
+ }
+
+ log_open(SDPD, !detach);
+
+ /* Become daemon if required */
+ if (detach && daemon(0, 0) < 0) {
+ log_crit("Could not become daemon. %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ /* Set signal handlers */
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = sighandler;
+
+ if (sigaction(SIGTERM, &sa, NULL) < 0 ||
+ sigaction(SIGHUP, &sa, NULL) < 0 ||
+ sigaction(SIGINT, &sa, NULL) < 0) {
+ log_crit("Could not install signal handlers. %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ sa.sa_handler = SIG_IGN;
+ if (sigaction(SIGPIPE, &sa, NULL) < 0) {
+ log_crit("Could not install signal handlers. %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ /* Initialize server */
+ if (server_init(&server, control) < 0)
+ exit(1);
+
+ if ((user != NULL || group != NULL) && drop_root(user, group) < 0)
+ exit(1);
+
+ for (done = 0; !done; ) {
+ if (server_do(&server) != 0)
+ done ++;
+ }
+
+ server_shutdown(&server);
+ log_close();
+
+ return (0);
+}
+
+/*
+ * Drop root
+ */
+
+static int32_t
+drop_root(char const *user, char const *group)
+{
+ int uid, gid;
+ char *ep;
+
+ if ((uid = getuid()) != 0) {
+ log_notice("Cannot set uid/gid. Not a superuser");
+ return (0); /* dont do anything unless root */
+ }
+
+ gid = getgid();
+
+ if (user != NULL) {
+ uid = strtol(user, &ep, 10);
+ if (*ep != '\0') {
+ struct passwd *pwd = getpwnam(user);
+
+ if (pwd == NULL) {
+ log_err("Could not find passwd entry for " \
+ "user %s", user);
+ return (-1);
+ }
+
+ uid = pwd->pw_uid;
+ }
+ }
+
+ if (group != NULL) {
+ gid = strtol(group, &ep, 10);
+ if (*ep != '\0') {
+ struct group *grp = getgrnam(group);
+
+ if (grp == NULL) {
+ log_err("Could not find group entry for " \
+ "group %s", group);
+ return (-1);
+ }
+
+ gid = grp->gr_gid;
+ }
+ }
+
+ if (setgid(gid) < 0) {
+ log_err("Could not setgid(%s). %s (%d)",
+ group, strerror(errno), errno);
+ return (-1);
+ }
+
+ if (setuid(uid) < 0) {
+ log_err("Could not setuid(%s). %s (%d)",
+ user, strerror(errno), errno);
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Signal handler
+ */
+
+static void
+sighandler(int32_t s)
+{
+ log_notice("Got signal %d. Total number of signals received %d",
+ s, ++ done);
+}
+
+/*
+ * Display usage information and quit
+ */
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+"Usage: %s [options]\n" \
+"Where options are:\n" \
+" -c specify control socket name (default %s)\n" \
+" -d do not detach (run in foreground)\n" \
+" -g grp specify group\n" \
+" -h display usage and exit\n" \
+" -u usr specify user\n",
+ SDPD, SDP_LOCAL_PATH);
+ exit(255);
+}
+
diff --git a/usr.sbin/bluetooth/sdpd/nap.c b/usr.sbin/bluetooth/sdpd/nap.c
new file mode 100644
index 0000000..c034ee6
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/nap.c
@@ -0,0 +1,210 @@
+/*
+ * nap.c
+ */
+
+/*-
+ * Copyright (c) 2008 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: nap.c,v 1.1 2008/03/11 00:02:42 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <sdp.h>
+#include <string.h>
+#include "profile.h"
+#include "provider.h"
+
+static int32_t
+nap_profile_create_service_class_id_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static uint16_t service_classes[] = {
+ SDP_SERVICE_CLASS_NAP
+ };
+
+ return (common_profile_create_service_class_id_list(
+ buf, eob,
+ (uint8_t const *) service_classes,
+ sizeof(service_classes)));
+}
+
+static int32_t
+nap_profile_create_bluetooth_profile_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static uint16_t profile_descriptor_list[] = {
+ SDP_SERVICE_CLASS_NAP,
+ 0x0100
+ };
+
+ return (common_profile_create_bluetooth_profile_descriptor_list(
+ buf, eob,
+ (uint8_t const *) profile_descriptor_list,
+ sizeof(profile_descriptor_list)));
+}
+
+static int32_t
+nap_profile_create_service_name(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static char service_name[] = "Network Access Point";
+
+ return (common_profile_create_string8(
+ buf, eob,
+ (uint8_t const *) service_name, strlen(service_name)));
+}
+
+static int32_t
+nap_profile_create_service_description(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static char service_descr[] = "Personal Ad-hoc Network Service";
+
+ return (common_profile_create_string8(
+ buf, eob,
+ (uint8_t const *) service_descr,
+ strlen(service_descr)));
+}
+
+static int32_t
+nap_profile_create_protocol_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = (provider_p) data;
+ sdp_nap_profile_p nap = (sdp_nap_profile_p) provider->data;
+
+ return (bnep_profile_create_protocol_descriptor_list(
+ buf, eob, (uint8_t const *) &nap->psm,
+ sizeof(nap->psm)));
+}
+
+static int32_t
+nap_profile_create_security_description(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = (provider_p) data;
+ sdp_nap_profile_p nap = (sdp_nap_profile_p) provider->data;
+
+ return (bnep_profile_create_security_description(buf, eob,
+ (uint8_t const *) &nap->security_description,
+ sizeof(nap->security_description)));
+}
+
+static int32_t
+nap_profile_create_net_access_type(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = (provider_p) data;
+ sdp_nap_profile_p nap = (sdp_nap_profile_p) provider->data;
+
+ if (buf + 3 > eob)
+ return (-1);
+
+ SDP_PUT8(SDP_DATA_UINT16, buf);
+ SDP_PUT16(nap->net_access_type, buf);
+
+ return (3);
+}
+
+static int32_t
+nap_profile_create_max_net_access_rate(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = (provider_p) data;
+ sdp_nap_profile_p nap = (sdp_nap_profile_p) provider->data;
+
+ if (buf + 3 > eob)
+ return (-1);
+
+ SDP_PUT8(SDP_DATA_UINT16, buf);
+ SDP_PUT16(nap->max_net_access_rate, buf);
+
+ return (3);
+}
+
+static int32_t
+nap_profile_create_service_availability(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = (provider_p) data;
+ sdp_nap_profile_p nap = (sdp_nap_profile_p) provider->data;
+
+ return (common_profile_create_service_availability(buf, eob,
+ &nap->load_factor, 1));
+}
+
+static int32_t
+nap_profile_data_valid(uint8_t const *data, uint32_t datalen)
+{
+ sdp_nap_profile_p nap = (sdp_nap_profile_p) data;
+
+ return ((nap->psm == 0)? 0 : 1);
+}
+
+static attr_t nap_profile_attrs[] = {
+ { SDP_ATTR_SERVICE_RECORD_HANDLE,
+ common_profile_create_service_record_handle },
+ { SDP_ATTR_SERVICE_CLASS_ID_LIST,
+ nap_profile_create_service_class_id_list },
+ { SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST,
+ nap_profile_create_protocol_descriptor_list },
+ { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST,
+ common_profile_create_language_base_attribute_id_list },
+ { SDP_ATTR_SERVICE_AVAILABILITY,
+ nap_profile_create_service_availability },
+ { SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST,
+ nap_profile_create_bluetooth_profile_descriptor_list },
+ { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET,
+ nap_profile_create_service_name },
+ { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_DESCRIPTION_OFFSET,
+ nap_profile_create_service_description },
+ { SDP_ATTR_SECURITY_DESCRIPTION,
+ nap_profile_create_security_description },
+ { SDP_ATTR_NET_ACCESS_TYPE,
+ nap_profile_create_net_access_type },
+ { SDP_ATTR_MAX_NET_ACCESS_RATE,
+ nap_profile_create_max_net_access_rate },
+ { 0, NULL } /* end entry */
+};
+
+profile_t nap_profile_descriptor = {
+ SDP_SERVICE_CLASS_NAP,
+ sizeof(sdp_nap_profile_t),
+ nap_profile_data_valid,
+ (attr_t const * const) &nap_profile_attrs
+};
+
diff --git a/usr.sbin/bluetooth/sdpd/opush.c b/usr.sbin/bluetooth/sdpd/opush.c
new file mode 100644
index 0000000..bcdfda2
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/opush.c
@@ -0,0 +1,134 @@
+/*
+ * opush.c
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: opush.c,v 1.6 2004/01/13 19:31:54 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <sdp.h>
+#include <string.h>
+#include "profile.h"
+#include "provider.h"
+
+static int32_t
+opush_profile_create_service_class_id_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static uint16_t service_classes[] = {
+ SDP_SERVICE_CLASS_OBEX_OBJECT_PUSH
+ };
+
+ return (common_profile_create_service_class_id_list(
+ buf, eob,
+ (uint8_t const *) service_classes,
+ sizeof(service_classes)));
+}
+
+static int32_t
+opush_profile_create_bluetooth_profile_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static uint16_t profile_descriptor_list[] = {
+ SDP_SERVICE_CLASS_OBEX_OBJECT_PUSH,
+ 0x0100
+ };
+
+ return (common_profile_create_bluetooth_profile_descriptor_list(
+ buf, eob,
+ (uint8_t const *) profile_descriptor_list,
+ sizeof(profile_descriptor_list)));
+}
+
+static int32_t
+opush_profile_create_service_name(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static char service_name[] = "OBEX Object Push";
+
+ return (common_profile_create_string8(
+ buf, eob,
+ (uint8_t const *) service_name, strlen(service_name)));
+}
+
+static int32_t
+opush_profile_create_protocol_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = (provider_p) data;
+ sdp_opush_profile_p opush = (sdp_opush_profile_p) provider->data;
+
+ return (obex_profile_create_protocol_descriptor_list(
+ buf, eob,
+ (uint8_t const *) &opush->server_channel, 1));
+}
+
+static int32_t
+opush_profile_create_supported_formats_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = (provider_p) data;
+ sdp_opush_profile_p opush = (sdp_opush_profile_p) provider->data;
+
+ return (obex_profile_create_supported_formats_list(
+ buf, eob,
+ (uint8_t const *) opush->supported_formats,
+ opush->supported_formats_size));
+}
+
+static attr_t opush_profile_attrs[] = {
+ { SDP_ATTR_SERVICE_RECORD_HANDLE,
+ common_profile_create_service_record_handle },
+ { SDP_ATTR_SERVICE_CLASS_ID_LIST,
+ opush_profile_create_service_class_id_list },
+ { SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST,
+ opush_profile_create_bluetooth_profile_descriptor_list },
+ { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST,
+ common_profile_create_language_base_attribute_id_list },
+ { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET,
+ opush_profile_create_service_name },
+ { SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST,
+ opush_profile_create_protocol_descriptor_list },
+ { SDP_ATTR_SUPPORTED_FORMATS_LIST,
+ opush_profile_create_supported_formats_list },
+ { 0, NULL } /* end entry */
+};
+
+profile_t opush_profile_descriptor = {
+ SDP_SERVICE_CLASS_OBEX_OBJECT_PUSH,
+ sizeof(sdp_opush_profile_t),
+ obex_profile_data_valid,
+ (attr_t const * const) &opush_profile_attrs
+};
+
diff --git a/usr.sbin/bluetooth/sdpd/panu.c b/usr.sbin/bluetooth/sdpd/panu.c
new file mode 100644
index 0000000..5b2773e
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/panu.c
@@ -0,0 +1,173 @@
+/*
+ * panu.c
+ */
+
+/*-
+ * Copyright (c) 2008 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: panu.c,v 1.1 2008/03/11 00:02:42 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <sdp.h>
+#include <string.h>
+#include "profile.h"
+#include "provider.h"
+
+static int32_t
+panu_profile_create_service_class_id_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static uint16_t service_classes[] = {
+ SDP_SERVICE_CLASS_PANU
+ };
+
+ return (common_profile_create_service_class_id_list(
+ buf, eob,
+ (uint8_t const *) service_classes,
+ sizeof(service_classes)));
+}
+
+static int32_t
+panu_profile_create_bluetooth_profile_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static uint16_t profile_descriptor_list[] = {
+ SDP_SERVICE_CLASS_PANU,
+ 0x0100
+ };
+
+ return (common_profile_create_bluetooth_profile_descriptor_list(
+ buf, eob,
+ (uint8_t const *) profile_descriptor_list,
+ sizeof(profile_descriptor_list)));
+}
+
+static int32_t
+panu_profile_create_service_name(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static char service_name[] = "Personal Ad-hoc User Service";
+
+ return (common_profile_create_string8(
+ buf, eob,
+ (uint8_t const *) service_name, strlen(service_name)));
+}
+
+static int32_t
+panu_profile_create_service_description(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static char service_descr[] =
+ "Personal Ad-hoc User Service";
+
+ return (common_profile_create_string8(
+ buf, eob,
+ (uint8_t const *) service_descr,
+ strlen(service_descr)));
+}
+
+static int32_t
+panu_profile_create_protocol_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = (provider_p) data;
+ sdp_panu_profile_p panu = (sdp_panu_profile_p) provider->data;
+
+ return (bnep_profile_create_protocol_descriptor_list(
+ buf, eob, (uint8_t const *) &panu->psm,
+ sizeof(panu->psm)));
+}
+
+static int32_t
+panu_profile_data_valid(uint8_t const *data, uint32_t datalen)
+{
+ sdp_panu_profile_p panu = (sdp_panu_profile_p) data;
+
+ return ((panu->psm == 0)? 0 : 1);
+}
+
+static int32_t
+panu_profile_create_service_availability(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = (provider_p) data;
+ sdp_panu_profile_p panu = (sdp_panu_profile_p) provider->data;
+
+ return (common_profile_create_service_availability( buf, eob,
+ &panu->load_factor, 1));
+}
+
+static int32_t
+panu_profile_create_security_description(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = (provider_p) data;
+ sdp_panu_profile_p panu = (sdp_panu_profile_p) provider->data;
+
+ return (bnep_profile_create_security_description(buf, eob,
+ (uint8_t const *) &panu->security_description,
+ sizeof(panu->security_description)));
+}
+
+static attr_t panu_profile_attrs[] = {
+ { SDP_ATTR_SERVICE_RECORD_HANDLE,
+ common_profile_create_service_record_handle },
+ { SDP_ATTR_SERVICE_CLASS_ID_LIST,
+ panu_profile_create_service_class_id_list },
+ { SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST,
+ panu_profile_create_protocol_descriptor_list },
+ { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST,
+ common_profile_create_language_base_attribute_id_list },
+ { SDP_ATTR_SERVICE_AVAILABILITY,
+ panu_profile_create_service_availability },
+ { SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST,
+ panu_profile_create_bluetooth_profile_descriptor_list },
+ { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET,
+ panu_profile_create_service_name },
+ { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_DESCRIPTION_OFFSET,
+ panu_profile_create_service_description },
+ { SDP_ATTR_SECURITY_DESCRIPTION,
+ panu_profile_create_security_description },
+ { 0, NULL } /* end entry */
+};
+
+profile_t panu_profile_descriptor = {
+ SDP_SERVICE_CLASS_PANU,
+ sizeof(sdp_panu_profile_t),
+ panu_profile_data_valid,
+ (attr_t const * const) &panu_profile_attrs
+};
+
diff --git a/usr.sbin/bluetooth/sdpd/profile.c b/usr.sbin/bluetooth/sdpd/profile.c
new file mode 100644
index 0000000..5c25d03
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/profile.c
@@ -0,0 +1,498 @@
+/*
+ * profile.c
+ */
+
+/*-
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: profile.c,v 1.6 2004/01/13 19:31:54 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <sdp.h>
+#include <string.h>
+#include "profile.h"
+#include "provider.h"
+
+/*
+ * Lookup profile descriptor
+ */
+
+profile_p
+profile_get_descriptor(uint16_t uuid)
+{
+ extern profile_t dun_profile_descriptor;
+ extern profile_t ftrn_profile_descriptor;
+ extern profile_t irmc_profile_descriptor;
+ extern profile_t irmc_command_profile_descriptor;
+ extern profile_t lan_profile_descriptor;
+ extern profile_t opush_profile_descriptor;
+ extern profile_t sp_profile_descriptor;
+ extern profile_t nap_profile_descriptor;
+ extern profile_t gn_profile_descriptor;
+ extern profile_t panu_profile_descriptor;
+
+ static const profile_p profiles[] = {
+ &dun_profile_descriptor,
+ &ftrn_profile_descriptor,
+ &irmc_profile_descriptor,
+ &irmc_command_profile_descriptor,
+ &lan_profile_descriptor,
+ &opush_profile_descriptor,
+ &sp_profile_descriptor,
+ &nap_profile_descriptor,
+ &gn_profile_descriptor,
+ &panu_profile_descriptor
+ };
+
+ int32_t i;
+
+ for (i = 0; i < sizeof(profiles)/sizeof(profiles[0]); i++)
+ if (profiles[i]->uuid == uuid)
+ return (profiles[i]);
+
+ return (NULL);
+}
+
+/*
+ * Look attribute in the profile descripror
+ */
+
+profile_attr_create_p
+profile_get_attr(const profile_p profile, uint16_t attr)
+{
+ attr_p ad = (attr_p) profile->attrs;
+
+ for (; ad->create != NULL; ad ++)
+ if (ad->attr == attr)
+ return (ad->create);
+
+ return (NULL);
+}
+
+/*
+ * uint32 value32 - 5 bytes
+ */
+
+int32_t
+common_profile_create_service_record_handle(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ if (buf + 5 > eob)
+ return (-1);
+
+ SDP_PUT8(SDP_DATA_UINT32, buf);
+ SDP_PUT32(((provider_p) data)->handle, buf);
+
+ return (5);
+}
+
+/*
+ * seq8 len8 - 2 bytes
+ * uuid16 value16 - 3 bytes
+ * [ uuid16 value ]
+ */
+
+int32_t
+common_profile_create_service_class_id_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ int32_t len = 3 * (datalen >>= 1);
+
+ if (len <= 0 || len > 0xff || buf + 2 + len > eob)
+ return (-1);
+
+ SDP_PUT8(SDP_DATA_SEQ8, buf);
+ SDP_PUT8(len, buf);
+
+ for (; datalen > 0; datalen --) {
+ SDP_PUT8(SDP_DATA_UUID16, buf);
+ SDP_PUT16(*((uint16_t const *)data), buf);
+ data += sizeof(uint16_t);
+ }
+
+ return (2 + len);
+}
+
+/*
+ * seq8 len8 - 2 bytes
+ * seq 8 len8 - 2 bytes
+ * uuid16 value16 - 3 bytes
+ * uint16 value16 - 3 bytes
+ * [ seq 8 len8
+ * uuid16 value16
+ * uint16 value16 ]
+ */
+
+int32_t
+common_profile_create_bluetooth_profile_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ int32_t len = 8 * (datalen >>= 2);
+
+ if (len <= 0 || len > 0xff || buf + 2 + len > eob)
+ return (-1);
+
+ SDP_PUT8(SDP_DATA_SEQ8, buf);
+ SDP_PUT8(len, buf);
+
+ for (; datalen > 0; datalen --) {
+ SDP_PUT8(SDP_DATA_SEQ8, buf);
+ SDP_PUT8(6, buf);
+ SDP_PUT8(SDP_DATA_UUID16, buf);
+ SDP_PUT16(*((uint16_t const *)data), buf);
+ data += sizeof(uint16_t);
+ SDP_PUT8(SDP_DATA_UINT16, buf);
+ SDP_PUT16(*((uint16_t const *)data), buf);
+ data += sizeof(uint16_t);
+ }
+
+ return (2 + len);
+}
+
+/*
+ * seq8 len8 - 2 bytes
+ * uint16 value16 - 3 bytes
+ * uint16 value16 - 3 bytes
+ * uint16 value16 - 3 bytes
+ */
+
+int32_t
+common_profile_create_language_base_attribute_id_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ if (buf + 11 > eob)
+ return (-1);
+
+ SDP_PUT8(SDP_DATA_SEQ8, buf);
+ SDP_PUT8(9, buf);
+
+ /*
+ * Language code per ISO 639:1988. Use "en".
+ */
+
+ SDP_PUT8(SDP_DATA_UINT16, buf);
+ SDP_PUT16(((0x65 << 8) | 0x6e), buf);
+
+ /*
+ * Encoding. Recommended is UTF-8. ISO639 UTF-8 MIBenum is 106
+ * (http://www.iana.org/assignments/character-sets)
+ */
+
+ SDP_PUT8(SDP_DATA_UINT16, buf);
+ SDP_PUT16(106, buf);
+
+ /*
+ * Offset (Primary Language Base is 0x100)
+ */
+
+ SDP_PUT8(SDP_DATA_UINT16, buf);
+ SDP_PUT16(SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID, buf);
+
+ return (11);
+}
+
+/*
+ * Common provider name is "FreeBSD"
+ */
+
+int32_t
+common_profile_create_service_provider_name(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ char provider_name[] = "FreeBSD";
+
+ return (common_profile_create_string8(buf, eob,
+ (uint8_t const *) provider_name,
+ strlen(provider_name)));
+}
+
+/*
+ * str8 len8 string
+ */
+
+int32_t
+common_profile_create_string8(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ if (datalen == 0 || datalen > 0xff || buf + 2 + datalen > eob)
+ return (-1);
+
+ SDP_PUT8(SDP_DATA_STR8, buf);
+ SDP_PUT8(datalen, buf);
+ memcpy(buf, data, datalen);
+
+ return (2 + datalen);
+}
+
+/*
+ * Service Availability
+ */
+
+int32_t
+common_profile_create_service_availability(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ if (datalen != 1 || buf + 2 > eob)
+ return (-1);
+
+ SDP_PUT8(SDP_DATA_UINT8, buf);
+ SDP_PUT8(data[0], buf);
+
+ return (2);
+}
+
+/*
+ * seq8 len8 - 2 bytes
+ * seq8 len8 - 2 bytes
+ * uuid16 value16 - 3 bytes
+ * seq8 len8 - 2 bytes
+ * uuid16 value16 - 3 bytes
+ * uint8 value8 - 2 bytes
+ */
+
+int32_t
+rfcomm_profile_create_protocol_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ if (datalen != 1 || buf + 14 > eob)
+ return (-1);
+
+ SDP_PUT8(SDP_DATA_SEQ8, buf);
+ SDP_PUT8(12, buf);
+
+ SDP_PUT8(SDP_DATA_SEQ8, buf);
+ SDP_PUT8(3, buf);
+ SDP_PUT8(SDP_DATA_UUID16, buf);
+ SDP_PUT16(SDP_UUID_PROTOCOL_L2CAP, buf);
+
+ SDP_PUT8(SDP_DATA_SEQ8, buf);
+ SDP_PUT8(5, buf);
+ SDP_PUT8(SDP_DATA_UUID16, buf);
+ SDP_PUT16(SDP_UUID_PROTOCOL_RFCOMM, buf);
+ SDP_PUT8(SDP_DATA_UINT8, buf);
+ SDP_PUT8(*data, buf);
+
+ return (14);
+}
+
+/*
+ * seq8 len8 - 2 bytes
+ * seq8 len8 - 2 bytes
+ * uuid16 value16 - 3 bytes
+ * seq8 len8 - 2 bytes
+ * uuid16 value16 - 3 bytes
+ * uint8 value8 - 2 bytes
+ * seq8 len8 - 2 bytes
+ * uuid16 value16 - 3 bytes
+ */
+
+int32_t
+obex_profile_create_protocol_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ if (datalen != 1 || buf + 19 > eob)
+ return (-1);
+
+ SDP_PUT8(SDP_DATA_SEQ8, buf);
+ SDP_PUT8(17, buf);
+
+ SDP_PUT8(SDP_DATA_SEQ8, buf);
+ SDP_PUT8(3, buf);
+ SDP_PUT8(SDP_DATA_UUID16, buf);
+ SDP_PUT16(SDP_UUID_PROTOCOL_L2CAP, buf);
+
+ SDP_PUT8(SDP_DATA_SEQ8, buf);
+ SDP_PUT8(5, buf);
+ SDP_PUT8(SDP_DATA_UUID16, buf);
+ SDP_PUT16(SDP_UUID_PROTOCOL_RFCOMM, buf);
+ SDP_PUT8(SDP_DATA_UINT8, buf);
+ SDP_PUT8(*data, buf);
+
+ SDP_PUT8(SDP_DATA_SEQ8, buf);
+ SDP_PUT8(3, buf);
+ SDP_PUT8(SDP_DATA_UUID16, buf);
+ SDP_PUT16(SDP_UUID_PROTOCOL_OBEX, buf);
+
+ return (19);
+}
+
+/*
+ * seq8 len8
+ * uint8 value8 - bytes
+ * [ uint8 value 8 ]
+ */
+
+int32_t
+obex_profile_create_supported_formats_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ int32_t len = 2 * datalen;
+
+ if (len <= 0 || len > 0xff || buf + 2 + len > eob)
+ return (-1);
+
+ SDP_PUT8(SDP_DATA_SEQ8, buf);
+ SDP_PUT8(len, buf);
+
+ for (; datalen > 0; datalen --) {
+ SDP_PUT8(SDP_DATA_UINT8, buf);
+ SDP_PUT8(*data++, buf);
+ }
+
+ return (2 + len);
+}
+
+/*
+ * do not check anything
+ */
+
+int32_t
+common_profile_always_valid(uint8_t const *data, uint32_t datalen)
+{
+ return (1);
+}
+
+/*
+ * verify server channel number (the first byte in the data)
+ */
+
+int32_t
+common_profile_server_channel_valid(uint8_t const *data, uint32_t datalen)
+{
+ if (data[0] < 1 || data[0] > 30)
+ return (0);
+
+ return (1);
+}
+
+/*
+ * verify server channel number and supported_formats_size
+ * sdp_opush_profile and sdp_irmc_profile
+ */
+
+int32_t
+obex_profile_data_valid(uint8_t const *data, uint32_t datalen)
+{
+ sdp_opush_profile_p opush = (sdp_opush_profile_p) data;
+
+ if (opush->server_channel < 1 ||
+ opush->server_channel > 30 ||
+ opush->supported_formats_size == 0 ||
+ opush->supported_formats_size > sizeof(opush->supported_formats))
+ return (0);
+
+ return (1);
+}
+
+/*
+ * BNEP protocol descriptor
+ */
+
+int32_t
+bnep_profile_create_protocol_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ /* supported protocol types */
+ uint16_t ptype[] = {
+ 0x0800, /* IPv4 */
+ 0x0806, /* ARP */
+#ifdef INET6
+ 0x86dd, /* IPv6 */
+#endif
+ };
+
+ uint16_t i, psm, version = 0x0100,
+ nptypes = sizeof(ptype)/sizeof(ptype[0]),
+ nptypes_size = nptypes * 3;
+
+ if (datalen != 2 || 18 + nptypes_size > 255 ||
+ buf + 20 + nptypes_size > eob)
+ return (-1);
+
+ memcpy(&psm, data, sizeof(psm));
+
+ SDP_PUT8(SDP_DATA_SEQ8, buf);
+ SDP_PUT8(18 + nptypes_size, buf);
+
+ SDP_PUT8(SDP_DATA_SEQ8, buf);
+ SDP_PUT8(6, buf);
+ SDP_PUT8(SDP_DATA_UUID16, buf);
+ SDP_PUT16(SDP_UUID_PROTOCOL_L2CAP, buf);
+ SDP_PUT8(SDP_DATA_UINT16, buf);
+ SDP_PUT16(psm, buf);
+
+ SDP_PUT8(SDP_DATA_SEQ8, buf);
+ SDP_PUT8(8 + nptypes_size, buf);
+ SDP_PUT8(SDP_DATA_UUID16, buf);
+ SDP_PUT16(SDP_UUID_PROTOCOL_BNEP, buf);
+ SDP_PUT8(SDP_DATA_UINT16, buf);
+ SDP_PUT16(version, buf);
+ SDP_PUT8(SDP_DATA_SEQ8, buf);
+ SDP_PUT8(nptypes_size, buf);
+ for (i = 0; i < nptypes; i ++) {
+ SDP_PUT8(SDP_DATA_UINT16, buf);
+ SDP_PUT16(ptype[i], buf);
+ }
+
+ return (20 + nptypes_size);
+}
+
+/*
+ * BNEP security description
+ */
+
+int32_t
+bnep_profile_create_security_description(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ uint16_t security_descr;
+
+ if (datalen != 2 || buf + 3 > eob)
+ return (-1);
+
+ memcpy(&security_descr, data, sizeof(security_descr));
+
+ SDP_PUT8(SDP_DATA_UINT16, buf);
+ SDP_PUT16(security_descr, buf);
+
+ return (3);
+}
+
diff --git a/usr.sbin/bluetooth/sdpd/profile.h b/usr.sbin/bluetooth/sdpd/profile.h
new file mode 100644
index 0000000..0b0d86d
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/profile.h
@@ -0,0 +1,96 @@
+/*
+ * profile.h
+ */
+
+/*-
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: profile.h,v 1.6 2004/01/13 19:31:54 max Exp $
+ * $FreeBSD$
+ */
+
+#ifndef _PROFILE_H_
+#define _PROFILE_H_
+
+/*
+ * Attribute descriptor
+ */
+
+typedef int32_t (profile_attr_create_t)(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen);
+typedef profile_attr_create_t * profile_attr_create_p;
+
+typedef int32_t (profile_data_valid_t)(
+ uint8_t const *data, uint32_t datalen);
+typedef profile_data_valid_t * profile_data_valid_p;
+
+struct attr
+{
+ uint16_t attr; /* attribute id */
+ profile_attr_create_p create; /* create attr value */
+};
+
+typedef struct attr attr_t;
+typedef struct attr * attr_p;
+
+/*
+ * Profile descriptor
+ */
+
+
+struct profile
+{
+ uint16_t uuid; /* profile uuid */
+ uint16_t dsize; /* profile data size */
+ profile_data_valid_p valid; /* profile data validator */
+ attr_t const * const attrs; /* supported attributes */
+};
+
+typedef struct profile profile_t;
+typedef struct profile *profile_p;
+
+profile_p profile_get_descriptor(uint16_t uuid);
+profile_attr_create_p profile_get_attr(const profile_p profile, uint16_t attr);
+
+profile_attr_create_t common_profile_create_service_record_handle;
+profile_attr_create_t common_profile_create_service_class_id_list;
+profile_attr_create_t common_profile_create_bluetooth_profile_descriptor_list;
+profile_attr_create_t common_profile_create_language_base_attribute_id_list;
+profile_attr_create_t common_profile_create_service_provider_name;
+profile_attr_create_t common_profile_create_string8;
+profile_attr_create_t common_profile_create_service_availability;
+profile_attr_create_t rfcomm_profile_create_protocol_descriptor_list;
+profile_attr_create_t obex_profile_create_protocol_descriptor_list;
+profile_attr_create_t obex_profile_create_supported_formats_list;
+profile_attr_create_t bnep_profile_create_protocol_descriptor_list;
+profile_attr_create_t bnep_profile_create_security_description;
+
+profile_data_valid_t common_profile_always_valid;
+profile_data_valid_t common_profile_server_channel_valid;
+profile_data_valid_t obex_profile_data_valid;
+
+#endif /* ndef _PROFILE_H_ */
+
diff --git a/usr.sbin/bluetooth/sdpd/provider.c b/usr.sbin/bluetooth/sdpd/provider.c
new file mode 100644
index 0000000..0243305
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/provider.c
@@ -0,0 +1,197 @@
+/*
+ * provider.c
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: provider.c,v 1.5 2004/01/13 01:54:39 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <string.h>
+#include <stdlib.h>
+#include "profile.h"
+#include "provider.h"
+
+static TAILQ_HEAD(, provider) providers = TAILQ_HEAD_INITIALIZER(providers);
+static uint32_t change_state = 0;
+static uint32_t handle = 0;
+
+/*
+ * Register Service Discovery provider.
+ * Should not be called more the once.
+ */
+
+int32_t
+provider_register_sd(int32_t fd)
+{
+ extern profile_t sd_profile_descriptor;
+ extern profile_t bgd_profile_descriptor;
+
+ provider_p sd = calloc(1, sizeof(*sd));
+ provider_p bgd = calloc(1, sizeof(*bgd));
+
+ if (sd == NULL || bgd == NULL) {
+ if (sd != NULL)
+ free(sd);
+
+ if (bgd != NULL)
+ free(bgd);
+
+ return (-1);
+ }
+
+ sd->profile = &sd_profile_descriptor;
+ bgd->handle = 0;
+ sd->fd = fd;
+ TAILQ_INSERT_HEAD(&providers, sd, provider_next);
+
+ bgd->profile = &bgd_profile_descriptor;
+ bgd->handle = 1;
+ sd->fd = fd;
+ TAILQ_INSERT_AFTER(&providers, sd, bgd, provider_next);
+
+ change_state ++;
+
+ return (0);
+}
+
+/*
+ * Register new provider for a given profile, bdaddr and session.
+ */
+
+provider_p
+provider_register(profile_p const profile, bdaddr_p const bdaddr, int32_t fd,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = calloc(1, sizeof(*provider));
+
+ if (provider != NULL) {
+ provider->data = malloc(datalen);
+ if (provider->data != NULL) {
+ provider->profile = profile;
+ memcpy(provider->data, data, datalen);
+
+ /*
+ * Record handles 0x0 and 0x1 are reserved
+ * for SDP itself
+ */
+
+ if (++ handle <= 1)
+ handle = 2;
+
+ provider->handle = handle;
+
+ memcpy(&provider->bdaddr, bdaddr,
+ sizeof(provider->bdaddr));
+ provider->fd = fd;
+
+ TAILQ_INSERT_TAIL(&providers, provider, provider_next);
+ change_state ++;
+ } else {
+ free(provider);
+ provider = NULL;
+ }
+ }
+
+ return (provider);
+}
+
+/*
+ * Unregister provider
+ */
+
+void
+provider_unregister(provider_p provider)
+{
+ TAILQ_REMOVE(&providers, provider, provider_next);
+ if (provider->data != NULL)
+ free(provider->data);
+ free(provider);
+ change_state ++;
+}
+
+/*
+ * Update provider data
+ */
+
+int32_t
+provider_update(provider_p provider, uint8_t const *data, uint32_t datalen)
+{
+ uint8_t *new_data = (uint8_t *) realloc(provider->data, datalen);
+
+ if (new_data == NULL)
+ return (-1);
+
+ memcpy(new_data, data, datalen);
+ provider->data = new_data;
+
+ return (0);
+}
+
+/*
+ * Get a provider for given record handle
+ */
+
+provider_p
+provider_by_handle(uint32_t handle)
+{
+ provider_p provider = NULL;
+
+ TAILQ_FOREACH(provider, &providers, provider_next)
+ if (provider->handle == handle)
+ break;
+
+ return (provider);
+}
+
+/*
+ * Cursor access
+ */
+
+provider_p
+provider_get_first(void)
+{
+ return (TAILQ_FIRST(&providers));
+}
+
+provider_p
+provider_get_next(provider_p provider)
+{
+ return (TAILQ_NEXT(provider, provider_next));
+}
+
+/*
+ * Return change state
+ */
+
+uint32_t
+provider_get_change_state(void)
+{
+ return (change_state);
+}
+
diff --git a/usr.sbin/bluetooth/sdpd/provider.h b/usr.sbin/bluetooth/sdpd/provider.h
new file mode 100644
index 0000000..b48bc8d
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/provider.h
@@ -0,0 +1,75 @@
+/*
+ * provider.h
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: provider.h,v 1.6 2004/01/13 01:54:39 max Exp $
+ * $FreeBSD$
+ */
+
+#ifndef _PROVIDER_H_
+#define _PROVIDER_H_
+
+/*
+ * Provider of service
+ */
+
+struct profile;
+
+struct provider
+{
+ struct profile *profile; /* profile */
+ void *data; /* profile data */
+ uint32_t handle; /* record handle */
+ bdaddr_t bdaddr; /* provider's BDADDR */
+ int32_t fd; /* session descriptor */
+ TAILQ_ENTRY(provider) provider_next; /* all providers */
+};
+
+typedef struct provider provider_t;
+typedef struct provider * provider_p;
+
+#define provider_match_bdaddr(p, b) \
+ (memcmp(b, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0 || \
+ memcmp(&(p)->bdaddr, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0 || \
+ memcmp(&(p)->bdaddr, b, sizeof(bdaddr_t)) == 0)
+
+int32_t provider_register_sd (int32_t fd);
+provider_p provider_register (profile_p const profile,
+ bdaddr_p const bdaddr,
+ int32_t fd,
+ uint8_t const *data,
+ uint32_t datalen);
+
+void provider_unregister (provider_p provider);
+int32_t provider_update (provider_p provider,
+ uint8_t const *data,
+ uint32_t datalen);
+provider_p provider_by_handle (uint32_t handle);
+provider_p provider_get_first (void);
+provider_p provider_get_next (provider_p provider);
+uint32_t provider_get_change_state (void);
+
+#endif /* ndef _PROVIDER_H_ */
diff --git a/usr.sbin/bluetooth/sdpd/sar.c b/usr.sbin/bluetooth/sdpd/sar.c
new file mode 100644
index 0000000..705f716
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/sar.c
@@ -0,0 +1,318 @@
+/*
+ * sar.c
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: sar.c,v 1.2 2004/01/08 23:46:51 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#include <sys/uio.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <assert.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <errno.h>
+#include <sdp.h>
+#include <stdio.h> /* for NULL */
+#include "profile.h"
+#include "provider.h"
+#include "server.h"
+
+/*
+ * Prepare SDP attr/value pair. Check if profile implements the attribute
+ * and if so call the attribute value function.
+ *
+ * uint16 value16 - 3 bytes (attribute)
+ * value - N bytes (value)
+ */
+
+static int32_t
+server_prepare_attr_value_pair(
+ provider_p const provider, uint16_t attr,
+ uint8_t *buf, uint8_t const * const eob)
+{
+ profile_attr_create_p cf = profile_get_attr(provider->profile, attr);
+ int32_t len;
+
+ if (cf == NULL)
+ return (0); /* no attribute */
+
+ if (buf + 3 > eob)
+ return (-1);
+
+ SDP_PUT8(SDP_DATA_UINT16, buf);
+ SDP_PUT16(attr, buf);
+
+ len = cf(buf, eob, (uint8_t const *) provider, sizeof(*provider));
+ if (len < 0)
+ return (-1);
+
+ return (3 + len);
+}
+
+/*
+ * seq16 value16 - 3 bytes
+ * attr value - 3+ bytes
+ * [ attr value ]
+ */
+
+int32_t
+server_prepare_attr_list(provider_p const provider,
+ uint8_t const *req, uint8_t const * const req_end,
+ uint8_t *rsp, uint8_t const * const rsp_end)
+{
+ uint8_t *ptr = rsp + 3;
+ int32_t type, hi, lo, len;
+
+ if (ptr > rsp_end)
+ return (-1);
+
+ while (req < req_end) {
+ SDP_GET8(type, req);
+
+ switch (type) {
+ case SDP_DATA_UINT16:
+ if (req + 2 > req_end)
+ return (-1);
+
+ SDP_GET16(lo, req);
+ hi = lo;
+ break;
+
+ case SDP_DATA_UINT32:
+ if (req + 4 > req_end)
+ return (-1);
+
+ SDP_GET16(lo, req);
+ SDP_GET16(hi, req);
+ break;
+
+ default:
+ return (-1);
+ /* NOT REACHED */
+ }
+
+ for (; lo <= hi; lo ++) {
+ len = server_prepare_attr_value_pair(provider, lo, ptr, rsp_end);
+ if (len < 0)
+ return (-1);
+
+ ptr += len;
+ }
+ }
+
+ len = ptr - rsp; /* we put this much bytes in rsp */
+
+ /* Fix SEQ16 header for the rsp */
+ SDP_PUT8(SDP_DATA_SEQ16, rsp);
+ SDP_PUT16(len - 3, rsp);
+
+ return (len);
+}
+
+/*
+ * Prepare SDP Service Attribute Response
+ */
+
+int32_t
+server_prepare_service_attribute_response(server_p srv, int32_t fd)
+{
+ uint8_t const *req = srv->req + sizeof(sdp_pdu_t);
+ uint8_t const *req_end = req + ((sdp_pdu_p)(srv->req))->len;
+ uint8_t *rsp = srv->fdidx[fd].rsp;
+ uint8_t const *rsp_end = rsp + NG_L2CAP_MTU_MAXIMUM;
+
+ uint8_t *ptr = NULL;
+ provider_t *provider = NULL;
+ uint32_t handle;
+ int32_t type, rsp_limit, aidlen, cslen, cs;
+
+ /*
+ * Minimal Service Attribute Request request
+ *
+ * value32 - 4 bytes ServiceRecordHandle
+ * value16 - 2 bytes MaximumAttributeByteCount
+ * seq8 len8 - 2 bytes
+ * uint16 value16 - 3 bytes AttributeIDList
+ * value8 - 1 byte ContinuationState
+ */
+
+ if (req_end - req < 12)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ /* Get ServiceRecordHandle and MaximumAttributeByteCount */
+ SDP_GET32(handle, req);
+ SDP_GET16(rsp_limit, req);
+ if (rsp_limit <= 0)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ /* Get size of AttributeIDList */
+ aidlen = 0;
+ SDP_GET8(type, req);
+ switch (type) {
+ case SDP_DATA_SEQ8:
+ SDP_GET8(aidlen, req);
+ break;
+
+ case SDP_DATA_SEQ16:
+ SDP_GET16(aidlen, req);
+ break;
+
+ case SDP_DATA_SEQ32:
+ SDP_GET32(aidlen, req);
+ break;
+ }
+ if (aidlen <= 0)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ ptr = (uint8_t *) req + aidlen;
+
+ /* Get ContinuationState */
+ if (ptr + 1 > req_end)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ SDP_GET8(cslen, ptr);
+ if (cslen != 0) {
+ if (cslen != 2 || req_end - ptr != 2)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ SDP_GET16(cs, ptr);
+ } else
+ cs = 0;
+
+ /* Process the request. First, check continuation state */
+ if (srv->fdidx[fd].rsp_cs != cs)
+ return (SDP_ERROR_CODE_INVALID_CONTINUATION_STATE);
+ if (srv->fdidx[fd].rsp_size > 0)
+ return (0);
+
+ /* Lookup record handle */
+ if ((provider = provider_by_handle(handle)) == NULL)
+ return (SDP_ERROR_CODE_INVALID_SERVICE_RECORD_HANDLE);
+
+ /*
+ * Service Attribute Response format
+ *
+ * value16 - 2 bytes AttributeListByteCount (not incl.)
+ * seq8 len16 - 3 bytes
+ * attr value - 3+ bytes AttributeList
+ * [ attr value ]
+ */
+
+ cs = server_prepare_attr_list(provider, req, req+aidlen, rsp, rsp_end);
+ if (cs < 0)
+ return (SDP_ERROR_CODE_INSUFFICIENT_RESOURCES);
+
+ /* Set reply size (not counting PDU header and continuation state) */
+ srv->fdidx[fd].rsp_limit = srv->fdidx[fd].omtu - sizeof(sdp_pdu_t) - 2;
+ if (srv->fdidx[fd].rsp_limit > rsp_limit)
+ srv->fdidx[fd].rsp_limit = rsp_limit;
+
+ srv->fdidx[fd].rsp_size = cs;
+ srv->fdidx[fd].rsp_cs = 0;
+
+ return (0);
+}
+
+/*
+ * Send SDP Service [Search] Attribute Response
+ */
+
+int32_t
+server_send_service_attribute_response(server_p srv, int32_t fd)
+{
+ uint8_t *rsp = srv->fdidx[fd].rsp + srv->fdidx[fd].rsp_cs;
+ uint8_t *rsp_end = srv->fdidx[fd].rsp + srv->fdidx[fd].rsp_size;
+
+ struct iovec iov[4];
+ sdp_pdu_t pdu;
+ uint16_t bcount;
+ uint8_t cs[3];
+ int32_t size;
+
+ /* First update continuation state (assume we will send all data) */
+ size = rsp_end - rsp;
+ srv->fdidx[fd].rsp_cs += size;
+
+ if (size + 1 > srv->fdidx[fd].rsp_limit) {
+ /*
+ * We need to split out response. Add 3 more bytes for the
+ * continuation state and move rsp_end and rsp_cs backwards.
+ */
+
+ while ((rsp_end - rsp) + 3 > srv->fdidx[fd].rsp_limit) {
+ rsp_end --;
+ srv->fdidx[fd].rsp_cs --;
+ }
+
+ cs[0] = 2;
+ cs[1] = srv->fdidx[fd].rsp_cs >> 8;
+ cs[2] = srv->fdidx[fd].rsp_cs & 0xff;
+ } else
+ cs[0] = 0;
+
+ assert(rsp_end >= rsp);
+
+ bcount = rsp_end - rsp;
+
+ if (((sdp_pdu_p)(srv->req))->pid == SDP_PDU_SERVICE_ATTRIBUTE_REQUEST)
+ pdu.pid = SDP_PDU_SERVICE_ATTRIBUTE_RESPONSE;
+ else
+ pdu.pid = SDP_PDU_SERVICE_SEARCH_ATTRIBUTE_RESPONSE;
+
+ pdu.tid = ((sdp_pdu_p)(srv->req))->tid;
+ pdu.len = htons(sizeof(bcount) + bcount + 1 + cs[0]);
+
+ bcount = htons(bcount);
+
+ iov[0].iov_base = &pdu;
+ iov[0].iov_len = sizeof(pdu);
+
+ iov[1].iov_base = &bcount;
+ iov[1].iov_len = sizeof(bcount);
+
+ iov[2].iov_base = rsp;
+ iov[2].iov_len = rsp_end - rsp;
+
+ iov[3].iov_base = cs;
+ iov[3].iov_len = 1 + cs[0];
+
+ do {
+ size = writev(fd, (struct iovec const *) &iov, sizeof(iov)/sizeof(iov[0]));
+ } while (size < 0 && errno == EINTR);
+
+ /* Check if we have sent (or failed to sent) last response chunk */
+ if (srv->fdidx[fd].rsp_cs == srv->fdidx[fd].rsp_size) {
+ srv->fdidx[fd].rsp_cs = 0;
+ srv->fdidx[fd].rsp_size = 0;
+ srv->fdidx[fd].rsp_limit = 0;
+ }
+
+ return ((size < 0)? errno : 0);
+}
+
diff --git a/usr.sbin/bluetooth/sdpd/scr.c b/usr.sbin/bluetooth/sdpd/scr.c
new file mode 100644
index 0000000..1df72d1
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/scr.c
@@ -0,0 +1,93 @@
+/*
+ * scr.c
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: scr.c,v 1.1 2004/01/13 01:54:39 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <errno.h>
+#include <sdp.h>
+#include <string.h>
+#include "profile.h"
+#include "provider.h"
+#include "server.h"
+
+/*
+ * Prepare Service Change response
+ */
+
+int32_t
+server_prepare_service_change_response(server_p srv, int32_t fd)
+{
+ uint8_t const *req = srv->req + sizeof(sdp_pdu_t);
+ uint8_t const *req_end = req + ((sdp_pdu_p)(srv->req))->len;
+ uint8_t *rsp = srv->fdidx[fd].rsp;
+
+ provider_t *provider = NULL;
+ uint32_t handle;
+
+ /*
+ * Minimal Service Change Request
+ *
+ * value32 - handle 4 bytes
+ */
+
+ if (!srv->fdidx[fd].control ||
+ !srv->fdidx[fd].priv || req_end - req < 4)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ /* Get handle */
+ SDP_GET32(handle, req);
+
+ /* Lookup provider */
+ provider = provider_by_handle(handle);
+ if (provider == NULL || provider->fd != fd)
+ return (SDP_ERROR_CODE_INVALID_SERVICE_RECORD_HANDLE);
+
+ /* Validate user data */
+ if (req_end - req < provider->profile->dsize ||
+ provider->profile->valid == NULL ||
+ (provider->profile->valid)(req, req_end - req) == 0)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ /* Update provider */
+ if (provider_update(provider, req, req_end - req) < 0)
+ return (SDP_ERROR_CODE_INSUFFICIENT_RESOURCES);
+
+ SDP_PUT16(0, rsp);
+
+ /* Set reply size */
+ srv->fdidx[fd].rsp_limit = srv->fdidx[fd].omtu - sizeof(sdp_pdu_t);
+ srv->fdidx[fd].rsp_size = rsp - srv->fdidx[fd].rsp;
+ srv->fdidx[fd].rsp_cs = 0;
+
+ return (0);
+}
+
diff --git a/usr.sbin/bluetooth/sdpd/sd.c b/usr.sbin/bluetooth/sdpd/sd.c
new file mode 100644
index 0000000..c5397ce
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/sd.c
@@ -0,0 +1,229 @@
+/*
+ * sd.c
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: sd.c,v 1.4 2004/01/13 01:54:39 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <sdp.h>
+#include <string.h>
+#include "profile.h"
+#include "provider.h"
+
+static int32_t
+sd_profile_create_service_class_id_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static uint16_t service_classes[] = {
+ SDP_SERVICE_CLASS_SERVICE_DISCOVERY_SERVER
+ };
+
+ return (common_profile_create_service_class_id_list(
+ buf, eob,
+ (uint8_t const *) service_classes,
+ sizeof(service_classes)));
+}
+
+static int32_t
+sd_profile_create_bluetooth_profile_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static uint16_t profile_descriptor_list[] = {
+ SDP_SERVICE_CLASS_SERVICE_DISCOVERY_SERVER,
+ 0x0100
+ };
+
+ return (common_profile_create_bluetooth_profile_descriptor_list(
+ buf, eob,
+ (uint8_t const *) profile_descriptor_list,
+ sizeof(profile_descriptor_list)));
+}
+
+static int32_t
+sd_profile_create_service_id(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ if (buf + 3 > eob)
+ return (-1);
+
+ /*
+ * The ServiceID is a UUID that universally and uniquely identifies
+ * the service instance described by the service record. This service
+ * attribute is particularly useful if the same service is described
+ * by service records in more than one SDP server
+ */
+
+ SDP_PUT8(SDP_DATA_UUID16, buf);
+ SDP_PUT16(SDP_UUID_PROTOCOL_SDP, buf); /* XXX ??? */
+
+ return (3);
+}
+
+static int32_t
+sd_profile_create_service_name(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static char service_name[] = "Bluetooth service discovery";
+
+ return (common_profile_create_string8(
+ buf, eob,
+ (uint8_t const *) service_name, strlen(service_name)));
+}
+
+static int32_t
+sd_profile_create_protocol_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ if (buf + 12 > eob)
+ return (-1);
+
+ SDP_PUT8(SDP_DATA_SEQ8, buf);
+ SDP_PUT8(10, buf);
+
+ SDP_PUT8(SDP_DATA_SEQ8, buf);
+ SDP_PUT8(3, buf);
+ SDP_PUT8(SDP_DATA_UUID16, buf);
+ SDP_PUT16(SDP_UUID_PROTOCOL_L2CAP, buf);
+
+ SDP_PUT8(SDP_DATA_SEQ8, buf);
+ SDP_PUT8(3, buf);
+ SDP_PUT8(SDP_DATA_UUID16, buf);
+ SDP_PUT16(SDP_UUID_PROTOCOL_SDP, buf);
+
+ return (12);
+}
+
+static int32_t
+sd_profile_create_browse_group_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ if (buf + 5 > eob)
+ return (-1);
+
+ SDP_PUT8(SDP_DATA_SEQ8, buf);
+ SDP_PUT8(3, buf);
+
+ /*
+ * The top-level browse group ID, called PublicBrowseRoot and
+ * representing the root of the browsing hierarchy, has the value
+ * 00001002-0000-1000-8000-00805F9B34FB (UUID16: 0x1002) from the
+ * Bluetooth Assigned Numbers document
+ */
+
+ SDP_PUT8(SDP_DATA_UUID16, buf);
+ SDP_PUT16(SDP_SERVICE_CLASS_PUBLIC_BROWSE_GROUP, buf);
+
+ return (5);
+}
+
+static int32_t
+sd_profile_create_version_number_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ if (buf + 5 > eob)
+ return (-1);
+
+ SDP_PUT8(SDP_DATA_SEQ8, buf);
+ SDP_PUT8(3, buf);
+
+ /*
+ * The VersionNumberList is a data element sequence in which each
+ * element of the sequence is a version number supported by the SDP
+ * server. A version number is a 16-bit unsigned integer consisting
+ * of two fields. The higher-order 8 bits contain the major version
+ * number field and the low-order 8 bits contain the minor version
+ * number field. The initial version of SDP has a major version of
+ * 1 and a minor version of 0
+ */
+
+ SDP_PUT8(SDP_DATA_UINT16, buf);
+ SDP_PUT16(0x0100, buf);
+
+ return (5);
+}
+
+static int32_t
+sd_profile_create_service_database_state(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ uint32_t change_state = provider_get_change_state();
+
+ if (buf + 5 > eob)
+ return (-1);
+
+ SDP_PUT8(SDP_DATA_UINT32, buf);
+ SDP_PUT32(change_state, buf);
+
+ return (5);
+}
+
+static attr_t sd_profile_attrs[] = {
+ { SDP_ATTR_SERVICE_RECORD_HANDLE,
+ common_profile_create_service_record_handle },
+ { SDP_ATTR_SERVICE_CLASS_ID_LIST,
+ sd_profile_create_service_class_id_list },
+ { SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST,
+ sd_profile_create_bluetooth_profile_descriptor_list },
+ { SDP_ATTR_SERVICE_ID,
+ sd_profile_create_service_id },
+ { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST,
+ common_profile_create_language_base_attribute_id_list },
+ { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET,
+ sd_profile_create_service_name },
+ { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_DESCRIPTION_OFFSET,
+ sd_profile_create_service_name },
+ { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_PROVIDER_NAME_OFFSET,
+ common_profile_create_service_provider_name },
+ { SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST,
+ sd_profile_create_protocol_descriptor_list },
+ { SDP_ATTR_BROWSE_GROUP_LIST,
+ sd_profile_create_browse_group_list },
+ { SDP_ATTR_VERSION_NUMBER_LIST,
+ sd_profile_create_version_number_list },
+ { SDP_ATTR_SERVICE_DATABASE_STATE,
+ sd_profile_create_service_database_state },
+ { 0, NULL } /* end entry */
+};
+
+profile_t sd_profile_descriptor = {
+ SDP_SERVICE_CLASS_SERVICE_DISCOVERY_SERVER,
+ 0,
+ (profile_data_valid_p) NULL,
+ (attr_t const * const) &sd_profile_attrs
+};
+
diff --git a/usr.sbin/bluetooth/sdpd/sdpd.8 b/usr.sbin/bluetooth/sdpd/sdpd.8
new file mode 100644
index 0000000..6a7b162
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/sdpd.8
@@ -0,0 +1,140 @@
+.\" Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+.\"
+.\" $Id: sdpd.8,v 1.1 2004/01/13 19:31:54 max Exp $
+.\" $FreeBSD$
+.\"
+.Dd January 13, 2004
+.Dt SDPD 8
+.Os
+.Sh NAME
+.Nm sdpd
+.Nd Bluetooth Service Discovery Protocol daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl dh
+.Op Fl c Ar path
+.Op Fl g Ar group
+.Op Fl u Ar user
+.Sh DESCRIPTION
+The
+.Nm
+daemon keeps track of the Bluetooth services registered on the host
+and responds to Service Discovery inquiries from the remote Bluetooth devices.
+.Pp
+In order to use any service remote Bluetooth device need to send Service
+Search and Service Attribute or Service Search Attribute request over
+Bluetooth L2CAP connection on SDP PSM (0x0001).
+The
+.Nm
+daemon will try to find matching Service Record in its Service Database
+and will send appropriate response back.
+The remote device then will process the response, extract all required
+information and will make a separate connection in order to use the service.
+.Pp
+Bluetooth applications, running on the host, register services with
+the local
+.Nm
+daemon.
+Operation like service registration, service removal and service change are
+performed over the control socket.
+It is possible to query entire content of the
+.Nm
+Service Database with
+.Xr sdpcontrol 8
+by issuing
+.Cm browse
+command on the control socket.
+.Pp
+The command line options are as follows:
+.Bl -tag -width indent
+.It Fl d
+Do not detach from the controlling terminal.
+.It Fl c Ar path
+Specify path to the control socket.
+The default path is
+.Pa /var/run/sdp .
+.It Fl g Ar group
+Specifies the group the
+.Nm
+should run as after it initializes.
+The value specified may be either a group name or a numeric group ID.
+This only works if
+.Nm
+was started as root.
+The default group name is
+.Dq Li nobody .
+.It Fl h
+Display usage message and exit.
+.It Fl u Ar user
+Specifies the user the
+.Nm
+should run as after it initializes.
+The value specified may be either a user name or a numeric user ID.
+This only works if
+.Nm
+was started as root.
+The default user name is
+.Dq Li nobody .
+.El
+.Sh CAVEAT
+The
+.Nm
+daemon
+will listen for incoming L2CAP connections on a wildcard BD_ADDR.
+.Pp
+In case of multiple Bluetooth devices connected to the same host it is
+possible to specify which services should be
+.Dq bound
+to which Bluetooth device.
+Such assignment should be done at service registration time.
+.Pp
+Requests to register, remove or change service can only be made via the
+control socket.
+The
+.Nm
+daemon will check peer's credentials and will only accept the request if
+the application has the same effective user ID as the
+.Dq Li root
+user ID.
+.Pp
+The
+.Nm
+daemon does not check for duplicated Service Records.
+It only performs minimal checking on the service data sent in the Service
+Register request.
+It is assumed that application must obtain all required resources such
+as RFCOMM channels etc., before registering the service.
+.Sh FILES
+.Bl -tag -width ".Pa /var/run/sdp" -compact
+.It Pa /var/run/sdp
+.El
+.Sh SEE ALSO
+.Xr sdp 3 ,
+.Xr sdpcontrol 8
+.Sh AUTHORS
+.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com
+.Sh BUGS
+Most likely.
+Please report if found.
diff --git a/usr.sbin/bluetooth/sdpd/server.c b/usr.sbin/bluetooth/sdpd/server.c
new file mode 100644
index 0000000..abd1815
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/server.c
@@ -0,0 +1,590 @@
+/*
+ * server.c
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: server.c,v 1.6 2004/01/13 01:54:39 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/queue.h>
+#include <sys/ucred.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <assert.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <errno.h>
+#include <pwd.h>
+#include <sdp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "log.h"
+#include "profile.h"
+#include "provider.h"
+#include "server.h"
+
+static void server_accept_client (server_p srv, int32_t fd);
+static int32_t server_process_request (server_p srv, int32_t fd);
+static int32_t server_send_error_response (server_p srv, int32_t fd,
+ uint16_t error);
+static void server_close_fd (server_p srv, int32_t fd);
+
+/*
+ * Initialize server
+ */
+
+int32_t
+server_init(server_p srv, char const *control)
+{
+ struct sockaddr_un un;
+ struct sockaddr_l2cap l2;
+ int32_t unsock, l2sock;
+ socklen_t size;
+ uint16_t imtu;
+
+ assert(srv != NULL);
+ assert(control != NULL);
+
+ memset(srv, 0, sizeof(*srv));
+
+ /* Open control socket */
+ if (unlink(control) < 0 && errno != ENOENT) {
+ log_crit("Could not unlink(%s). %s (%d)",
+ control, strerror(errno), errno);
+ return (-1);
+ }
+
+ unsock = socket(PF_LOCAL, SOCK_STREAM, 0);
+ if (unsock < 0) {
+ log_crit("Could not create control socket. %s (%d)",
+ strerror(errno), errno);
+ return (-1);
+ }
+
+ memset(&un, 0, sizeof(un));
+ un.sun_len = sizeof(un);
+ un.sun_family = AF_LOCAL;
+ strlcpy(un.sun_path, control, sizeof(un.sun_path));
+
+ if (bind(unsock, (struct sockaddr *) &un, sizeof(un)) < 0) {
+ log_crit("Could not bind control socket. %s (%d)",
+ strerror(errno), errno);
+ close(unsock);
+ return (-1);
+ }
+
+ if (chmod(control, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) < 0) {
+ log_crit("Could not change permissions on control socket. " \
+ "%s (%d)", strerror(errno), errno);
+ close(unsock);
+ return (-1);
+ }
+
+ if (listen(unsock, 10) < 0) {
+ log_crit("Could not listen on control socket. %s (%d)",
+ strerror(errno), errno);
+ close(unsock);
+ return (-1);
+ }
+
+ /* Open L2CAP socket */
+ l2sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP);
+ if (l2sock < 0) {
+ log_crit("Could not create L2CAP socket. %s (%d)",
+ strerror(errno), errno);
+ close(unsock);
+ return (-1);
+ }
+
+ size = sizeof(imtu);
+ if (getsockopt(l2sock, SOL_L2CAP, SO_L2CAP_IMTU, &imtu, &size) < 0) {
+ log_crit("Could not get L2CAP IMTU. %s (%d)",
+ strerror(errno), errno);
+ close(unsock);
+ close(l2sock);
+ return (-1);
+ }
+
+ memset(&l2, 0, sizeof(l2));
+ l2.l2cap_len = sizeof(l2);
+ l2.l2cap_family = AF_BLUETOOTH;
+ memcpy(&l2.l2cap_bdaddr, NG_HCI_BDADDR_ANY, sizeof(l2.l2cap_bdaddr));
+ l2.l2cap_psm = htole16(NG_L2CAP_PSM_SDP);
+
+ if (bind(l2sock, (struct sockaddr *) &l2, sizeof(l2)) < 0) {
+ log_crit("Could not bind L2CAP socket. %s (%d)",
+ strerror(errno), errno);
+ close(unsock);
+ close(l2sock);
+ return (-1);
+ }
+
+ if (listen(l2sock, 10) < 0) {
+ log_crit("Could not listen on L2CAP socket. %s (%d)",
+ strerror(errno), errno);
+ close(unsock);
+ close(l2sock);
+ return (-1);
+ }
+
+ /* Allocate incoming buffer */
+ srv->imtu = (imtu > SDP_LOCAL_MTU)? imtu : SDP_LOCAL_MTU;
+ srv->req = (uint8_t *) calloc(srv->imtu, sizeof(srv->req[0]));
+ if (srv->req == NULL) {
+ log_crit("Could not allocate request buffer");
+ close(unsock);
+ close(l2sock);
+ return (-1);
+ }
+
+ /* Allocate memory for descriptor index */
+ srv->fdidx = (fd_idx_p) calloc(FD_SETSIZE, sizeof(srv->fdidx[0]));
+ if (srv->fdidx == NULL) {
+ log_crit("Could not allocate fd index");
+ free(srv->req);
+ close(unsock);
+ close(l2sock);
+ return (-1);
+ }
+
+ /* Register Service Discovery profile (attach it to control socket) */
+ if (provider_register_sd(unsock) < 0) {
+ log_crit("Could not register Service Discovery profile");
+ free(srv->fdidx);
+ free(srv->req);
+ close(unsock);
+ close(l2sock);
+ return (-1);
+ }
+
+ /*
+ * If we got here then everything is fine. Add both control sockets
+ * to the index.
+ */
+
+ FD_ZERO(&srv->fdset);
+ srv->maxfd = (unsock > l2sock)? unsock : l2sock;
+
+ FD_SET(unsock, &srv->fdset);
+ srv->fdidx[unsock].valid = 1;
+ srv->fdidx[unsock].server = 1;
+ srv->fdidx[unsock].control = 1;
+ srv->fdidx[unsock].priv = 0;
+ srv->fdidx[unsock].rsp_cs = 0;
+ srv->fdidx[unsock].rsp_size = 0;
+ srv->fdidx[unsock].rsp_limit = 0;
+ srv->fdidx[unsock].omtu = SDP_LOCAL_MTU;
+ srv->fdidx[unsock].rsp = NULL;
+
+ FD_SET(l2sock, &srv->fdset);
+ srv->fdidx[l2sock].valid = 1;
+ srv->fdidx[l2sock].server = 1;
+ srv->fdidx[l2sock].control = 0;
+ srv->fdidx[l2sock].priv = 0;
+ srv->fdidx[l2sock].rsp_cs = 0;
+ srv->fdidx[l2sock].rsp_size = 0;
+ srv->fdidx[l2sock].rsp_limit = 0;
+ srv->fdidx[l2sock].omtu = 0; /* unknown */
+ srv->fdidx[l2sock].rsp = NULL;
+
+ return (0);
+}
+
+/*
+ * Shutdown server
+ */
+
+void
+server_shutdown(server_p srv)
+{
+ int fd;
+
+ assert(srv != NULL);
+
+ for (fd = 0; fd < srv->maxfd + 1; fd ++)
+ if (srv->fdidx[fd].valid)
+ server_close_fd(srv, fd);
+
+ free(srv->req);
+ free(srv->fdidx);
+
+ memset(srv, 0, sizeof(*srv));
+}
+
+/*
+ * Do one server iteration
+ */
+
+int32_t
+server_do(server_p srv)
+{
+ fd_set fdset;
+ int32_t n, fd;
+
+ assert(srv != NULL);
+
+ /* Copy cached version of the fd set and call select */
+ memcpy(&fdset, &srv->fdset, sizeof(fdset));
+ n = select(srv->maxfd + 1, &fdset, NULL, NULL, NULL);
+ if (n < 0) {
+ if (errno == EINTR)
+ return (0);
+
+ log_err("Could not select(%d, %p). %s (%d)",
+ srv->maxfd + 1, &fdset, strerror(errno), errno);
+
+ return (-1);
+ }
+
+ /* Process descriptors */
+ for (fd = 0; fd < srv->maxfd + 1 && n > 0; fd ++) {
+ if (!FD_ISSET(fd, &fdset))
+ continue;
+
+ assert(srv->fdidx[fd].valid);
+ n --;
+
+ if (srv->fdidx[fd].server)
+ server_accept_client(srv, fd);
+ else if (server_process_request(srv, fd) != 0)
+ server_close_fd(srv, fd);
+ }
+
+ return (0);
+
+}
+
+/*
+ * Accept new client connection and register it with index
+ */
+
+static void
+server_accept_client(server_p srv, int32_t fd)
+{
+ uint8_t *rsp = NULL;
+ int32_t cfd, priv;
+ uint16_t omtu;
+ socklen_t size;
+
+ do {
+ cfd = accept(fd, NULL, NULL);
+ } while (cfd < 0 && errno == EINTR);
+
+ if (cfd < 0) {
+ log_err("Could not accept connection on %s socket. %s (%d)",
+ srv->fdidx[fd].control? "control" : "L2CAP",
+ strerror(errno), errno);
+ return;
+ }
+
+ assert(!FD_ISSET(cfd, &srv->fdset));
+ assert(!srv->fdidx[cfd].valid);
+
+ priv = 0;
+
+ if (!srv->fdidx[fd].control) {
+ /* Get local BD_ADDR */
+ size = sizeof(srv->req_sa);
+ if (getsockname(cfd,(struct sockaddr*)&srv->req_sa,&size) < 0) {
+ log_err("Could not get local BD_ADDR. %s (%d)",
+ strerror(errno), errno);
+ close(cfd);
+ return;
+ }
+
+ /* Get outgoing MTU */
+ size = sizeof(omtu);
+ if (getsockopt(cfd,SOL_L2CAP,SO_L2CAP_OMTU,&omtu,&size) < 0) {
+ log_err("Could not get L2CAP OMTU. %s (%d)",
+ strerror(errno), errno);
+ close(cfd);
+ return;
+ }
+
+ /*
+ * The maximum size of the L2CAP packet is 65536 bytes.
+ * The minimum L2CAP MTU is 43 bytes. That means we need
+ * 65536 / 43 = ~1524 chunks to transfer maximum packet
+ * size with minimum MTU. The "rsp_cs" field in fd_idx_t
+ * is 11 bits wide, which gives us up to 2048 chunks.
+ */
+
+ if (omtu < NG_L2CAP_MTU_MINIMUM) {
+ log_err("L2CAP OMTU is too small (%d bytes)", omtu);
+ close(cfd);
+ return;
+ }
+ } else {
+ struct xucred cr;
+ struct passwd *pw;
+
+ /* Get peer's credentials */
+ memset(&cr, 0, sizeof(cr));
+ size = sizeof(cr);
+
+ if (getsockopt(cfd, 0, LOCAL_PEERCRED, &cr, &size) < 0) {
+ log_err("Could not get peer's credentials. %s (%d)",
+ strerror(errno), errno);
+ close(cfd);
+ return;
+ }
+
+ /* Check credentials */
+ pw = getpwuid(cr.cr_uid);
+ if (pw != NULL)
+ priv = (strcmp(pw->pw_name, "root") == 0);
+ else
+ log_warning("Could not verify credentials for uid %d",
+ cr.cr_uid);
+
+ memcpy(&srv->req_sa.l2cap_bdaddr, NG_HCI_BDADDR_ANY,
+ sizeof(srv->req_sa.l2cap_bdaddr));
+
+ omtu = srv->fdidx[fd].omtu;
+ }
+
+ /*
+ * Allocate buffer. This is an overkill, but we can not know how
+ * big our reply is going to be.
+ */
+
+ rsp = (uint8_t *) calloc(NG_L2CAP_MTU_MAXIMUM, sizeof(rsp[0]));
+ if (rsp == NULL) {
+ log_crit("Could not allocate response buffer");
+ close(cfd);
+ return;
+ }
+
+ /* Add client descriptor to the index */
+ FD_SET(cfd, &srv->fdset);
+ if (srv->maxfd < cfd)
+ srv->maxfd = cfd;
+ srv->fdidx[cfd].valid = 1;
+ srv->fdidx[cfd].server = 0;
+ srv->fdidx[cfd].control = srv->fdidx[fd].control;
+ srv->fdidx[cfd].priv = priv;
+ srv->fdidx[cfd].rsp_cs = 0;
+ srv->fdidx[cfd].rsp_size = 0;
+ srv->fdidx[cfd].rsp_limit = 0;
+ srv->fdidx[cfd].omtu = omtu;
+ srv->fdidx[cfd].rsp = rsp;
+}
+
+/*
+ * Process request from the client
+ */
+
+static int32_t
+server_process_request(server_p srv, int32_t fd)
+{
+ sdp_pdu_p pdu = (sdp_pdu_p) srv->req;
+ int32_t len, error;
+
+ assert(srv->imtu > 0);
+ assert(srv->req != NULL);
+ assert(FD_ISSET(fd, &srv->fdset));
+ assert(srv->fdidx[fd].valid);
+ assert(!srv->fdidx[fd].server);
+ assert(srv->fdidx[fd].rsp != NULL);
+ assert(srv->fdidx[fd].omtu >= NG_L2CAP_MTU_MINIMUM);
+
+ do {
+ len = read(fd, srv->req, srv->imtu);
+ } while (len < 0 && errno == EINTR);
+
+ if (len < 0) {
+ log_err("Could not receive SDP request from %s socket. %s (%d)",
+ srv->fdidx[fd].control? "control" : "L2CAP",
+ strerror(errno), errno);
+ return (-1);
+ }
+ if (len == 0) {
+ log_info("Client on %s socket has disconnected",
+ srv->fdidx[fd].control? "control" : "L2CAP");
+ return (-1);
+ }
+
+ if (len >= sizeof(*pdu) &&
+ sizeof(*pdu) + (pdu->len = ntohs(pdu->len)) == len) {
+ switch (pdu->pid) {
+ case SDP_PDU_SERVICE_SEARCH_REQUEST:
+ error = server_prepare_service_search_response(srv, fd);
+ break;
+
+ case SDP_PDU_SERVICE_ATTRIBUTE_REQUEST:
+ error = server_prepare_service_attribute_response(srv, fd);
+ break;
+
+ case SDP_PDU_SERVICE_SEARCH_ATTRIBUTE_REQUEST:
+ error = server_prepare_service_search_attribute_response(srv, fd);
+ break;
+
+ case SDP_PDU_SERVICE_REGISTER_REQUEST:
+ error = server_prepare_service_register_response(srv, fd);
+ break;
+
+ case SDP_PDU_SERVICE_UNREGISTER_REQUEST:
+ error = server_prepare_service_unregister_response(srv, fd);
+ break;
+
+ case SDP_PDU_SERVICE_CHANGE_REQUEST:
+ error = server_prepare_service_change_response(srv, fd);
+ break;
+
+ default:
+ error = SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX;
+ break;
+ }
+ } else
+ error = SDP_ERROR_CODE_INVALID_PDU_SIZE;
+
+ if (error == 0) {
+ switch (pdu->pid) {
+ case SDP_PDU_SERVICE_SEARCH_REQUEST:
+ error = server_send_service_search_response(srv, fd);
+ break;
+
+ case SDP_PDU_SERVICE_ATTRIBUTE_REQUEST:
+ error = server_send_service_attribute_response(srv, fd);
+ break;
+
+ case SDP_PDU_SERVICE_SEARCH_ATTRIBUTE_REQUEST:
+ error = server_send_service_search_attribute_response(srv, fd);
+ break;
+
+ case SDP_PDU_SERVICE_REGISTER_REQUEST:
+ error = server_send_service_register_response(srv, fd);
+ break;
+
+ case SDP_PDU_SERVICE_UNREGISTER_REQUEST:
+ error = server_send_service_unregister_response(srv, fd);
+ break;
+
+ case SDP_PDU_SERVICE_CHANGE_REQUEST:
+ error = server_send_service_change_response(srv, fd);
+ break;
+
+ default:
+ error = SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX;
+ break;
+ }
+
+ if (error != 0)
+ log_err("Could not send SDP response to %s socket, " \
+ "pdu->pid=%d, pdu->tid=%d, error=%d",
+ srv->fdidx[fd].control? "control" : "L2CAP",
+ pdu->pid, ntohs(pdu->tid), error);
+ } else {
+ log_err("Could not process SDP request from %s socket, " \
+ "pdu->pid=%d, pdu->tid=%d, pdu->len=%d, len=%d, " \
+ "error=%d",
+ srv->fdidx[fd].control? "control" : "L2CAP",
+ pdu->pid, ntohs(pdu->tid), pdu->len, len, error);
+
+ error = server_send_error_response(srv, fd, error);
+ if (error != 0)
+ log_err("Could not send SDP error response to %s " \
+ "socket, pdu->pid=%d, pdu->tid=%d, error=%d",
+ srv->fdidx[fd].control? "control" : "L2CAP",
+ pdu->pid, ntohs(pdu->tid), error);
+ }
+
+ /* On error forget response (if any) */
+ if (error != 0) {
+ srv->fdidx[fd].rsp_cs = 0;
+ srv->fdidx[fd].rsp_size = 0;
+ srv->fdidx[fd].rsp_limit = 0;
+ }
+
+ return (error);
+}
+
+/*
+ * Send SDP_Error_Response PDU
+ */
+
+static int32_t
+server_send_error_response(server_p srv, int32_t fd, uint16_t error)
+{
+ int32_t size;
+
+ struct {
+ sdp_pdu_t pdu;
+ uint16_t error;
+ } __attribute__ ((packed)) rsp;
+
+ /* Prepare and send SDP error response */
+ rsp.pdu.pid = SDP_PDU_ERROR_RESPONSE;
+ rsp.pdu.tid = ((sdp_pdu_p)(srv->req))->tid;
+ rsp.pdu.len = htons(sizeof(rsp.error));
+ rsp.error = htons(error);
+
+ do {
+ size = write(fd, &rsp, sizeof(rsp));
+ } while (size < 0 && errno == EINTR);
+
+ return ((size < 0)? errno : 0);
+}
+
+/*
+ * Close descriptor and remove it from index
+ */
+
+static void
+server_close_fd(server_p srv, int32_t fd)
+{
+ provider_p provider = NULL, provider_next = NULL;
+
+ assert(FD_ISSET(fd, &srv->fdset));
+ assert(srv->fdidx[fd].valid);
+
+ close(fd);
+
+ FD_CLR(fd, &srv->fdset);
+ if (fd == srv->maxfd)
+ srv->maxfd --;
+
+ if (srv->fdidx[fd].rsp != NULL)
+ free(srv->fdidx[fd].rsp);
+
+ memset(&srv->fdidx[fd], 0, sizeof(srv->fdidx[fd]));
+
+ for (provider = provider_get_first();
+ provider != NULL;
+ provider = provider_next) {
+ provider_next = provider_get_next(provider);
+
+ if (provider->fd == fd)
+ provider_unregister(provider);
+ }
+}
+
diff --git a/usr.sbin/bluetooth/sdpd/server.h b/usr.sbin/bluetooth/sdpd/server.h
new file mode 100644
index 0000000..d7ef55e
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/server.h
@@ -0,0 +1,102 @@
+/*
+ * server.h
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: server.h,v 1.5 2004/01/13 01:54:39 max Exp $
+ * $FreeBSD$
+ */
+
+#ifndef _SERVER_H_
+#define _SERVER_H_
+
+/*
+ * File descriptor index entry
+ */
+
+struct fd_idx
+{
+ unsigned valid : 1; /* descriptor is valid */
+ unsigned server : 1; /* descriptor is listening */
+ unsigned control : 1; /* descriptor is a control socket */
+ unsigned priv : 1; /* descriptor is privileged */
+ unsigned reserved : 1;
+ unsigned rsp_cs : 11; /* response continuation state */
+ uint16_t rsp_size; /* response size */
+ uint16_t rsp_limit; /* response limit */
+ uint16_t omtu; /* outgoing MTU */
+ uint8_t *rsp; /* outgoing buffer */
+};
+
+typedef struct fd_idx fd_idx_t;
+typedef struct fd_idx * fd_idx_p;
+
+/*
+ * SDP server
+ */
+
+struct server
+{
+ uint32_t imtu; /* incoming MTU */
+ uint8_t *req; /* incoming buffer */
+ int32_t maxfd; /* max. descriptor is the set */
+ fd_set fdset; /* current descriptor set */
+ fd_idx_p fdidx; /* descriptor index */
+ struct sockaddr_l2cap req_sa; /* local address */
+};
+
+typedef struct server server_t;
+typedef struct server * server_p;
+
+/*
+ * External API
+ */
+
+int32_t server_init(server_p srv, const char *control);
+void server_shutdown(server_p srv);
+int32_t server_do(server_p srv);
+
+int32_t server_prepare_service_search_response(server_p srv, int32_t fd);
+int32_t server_send_service_search_response(server_p srv, int32_t fd);
+
+int32_t server_prepare_service_attribute_response(server_p srv, int32_t fd);
+int32_t server_send_service_attribute_response(server_p srv, int32_t fd);
+
+int32_t server_prepare_service_search_attribute_response(server_p srv, int32_t fd);
+#define server_send_service_search_attribute_response \
+ server_send_service_attribute_response
+
+int32_t server_prepare_service_register_response(server_p srv, int32_t fd);
+int32_t server_send_service_register_response(server_p srv, int32_t fd);
+
+int32_t server_prepare_service_unregister_response(server_p srv, int32_t fd);
+#define server_send_service_unregister_response \
+ server_send_service_register_response
+
+int32_t server_prepare_service_change_response(server_p srv, int32_t fd);
+#define server_send_service_change_response \
+ server_send_service_register_response
+
+#endif /* ndef _SERVER_H_ */
diff --git a/usr.sbin/bluetooth/sdpd/sp.c b/usr.sbin/bluetooth/sdpd/sp.c
new file mode 100644
index 0000000..48edc77
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/sp.c
@@ -0,0 +1,118 @@
+/*
+ * sp.c
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: sp.c,v 1.5 2004/01/13 01:54:39 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <sdp.h>
+#include <string.h>
+#include "profile.h"
+#include "provider.h"
+
+static int32_t
+sp_profile_create_service_class_id_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static uint16_t service_classes[] = {
+ SDP_SERVICE_CLASS_SERIAL_PORT
+ };
+
+ return (common_profile_create_service_class_id_list(
+ buf, eob,
+ (uint8_t const *) service_classes,
+ sizeof(service_classes)));
+}
+
+static int32_t
+sp_profile_create_bluetooth_profile_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static uint16_t profile_descriptor_list[] = {
+ SDP_SERVICE_CLASS_SERIAL_PORT,
+ 0x0100
+ };
+
+ return (common_profile_create_bluetooth_profile_descriptor_list(
+ buf, eob,
+ (uint8_t const *) profile_descriptor_list,
+ sizeof(profile_descriptor_list)));
+}
+
+static int32_t
+sp_profile_create_service_name(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ static char service_name[] = "Serial Port";
+
+ return (common_profile_create_string8(
+ buf, eob,
+ (uint8_t const *) service_name, strlen(service_name)));
+}
+
+static int32_t
+sp_profile_create_protocol_descriptor_list(
+ uint8_t *buf, uint8_t const * const eob,
+ uint8_t const *data, uint32_t datalen)
+{
+ provider_p provider = (provider_p) data;
+ sdp_sp_profile_p sp = (sdp_sp_profile_p) provider->data;
+
+ return (rfcomm_profile_create_protocol_descriptor_list(
+ buf, eob,
+ (uint8_t const *) &sp->server_channel, 1));
+}
+
+static attr_t sp_profile_attrs[] = {
+ { SDP_ATTR_SERVICE_RECORD_HANDLE,
+ common_profile_create_service_record_handle },
+ { SDP_ATTR_SERVICE_CLASS_ID_LIST,
+ sp_profile_create_service_class_id_list },
+ { SDP_ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST,
+ sp_profile_create_bluetooth_profile_descriptor_list },
+ { SDP_ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST,
+ common_profile_create_language_base_attribute_id_list },
+ { SDP_ATTR_PRIMARY_LANGUAGE_BASE_ID + SDP_ATTR_SERVICE_NAME_OFFSET,
+ sp_profile_create_service_name },
+ { SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST,
+ sp_profile_create_protocol_descriptor_list },
+ { 0, NULL } /* end entry */
+};
+
+profile_t sp_profile_descriptor = {
+ SDP_SERVICE_CLASS_SERIAL_PORT,
+ sizeof(sdp_sp_profile_t),
+ common_profile_server_channel_valid,
+ (attr_t const * const) &sp_profile_attrs
+};
+
diff --git a/usr.sbin/bluetooth/sdpd/srr.c b/usr.sbin/bluetooth/sdpd/srr.c
new file mode 100644
index 0000000..60f48ca
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/srr.c
@@ -0,0 +1,140 @@
+/*
+ * srr.c
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: srr.c,v 1.1 2004/01/13 01:54:39 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#include <sys/uio.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <assert.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <errno.h>
+#include <sdp.h>
+#include <string.h>
+#include "profile.h"
+#include "provider.h"
+#include "server.h"
+
+/*
+ * Prepare Service Register response
+ */
+
+int32_t
+server_prepare_service_register_response(server_p srv, int32_t fd)
+{
+ uint8_t const *req = srv->req + sizeof(sdp_pdu_t);
+ uint8_t const *req_end = req + ((sdp_pdu_p)(srv->req))->len;
+ uint8_t *rsp = srv->fdidx[fd].rsp;
+
+ profile_t *profile = NULL;
+ provider_t *provider = NULL;
+ bdaddr_t *bdaddr = NULL;
+ int32_t uuid;
+
+ /*
+ * Minimal Service Register Request
+ *
+ * value16 - uuid 2 bytes
+ * bdaddr - BD_ADDR 6 bytes
+ */
+
+ if (!srv->fdidx[fd].control ||
+ !srv->fdidx[fd].priv || req_end - req < 8)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ /* Get ServiceClass UUID */
+ SDP_GET16(uuid, req);
+
+ /* Get BD_ADDR */
+ bdaddr = (bdaddr_p) req;
+ req += sizeof(*bdaddr);
+
+ /* Lookup profile descriptror */
+ profile = profile_get_descriptor(uuid);
+ if (profile == NULL)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ /* Validate user data */
+ if (req_end - req < profile->dsize ||
+ profile->valid == NULL ||
+ (profile->valid)(req, req_end - req) == 0)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ /* Register provider */
+ provider = provider_register(profile, bdaddr, fd, req, req_end - req);
+ if (provider == NULL)
+ return (SDP_ERROR_CODE_INSUFFICIENT_RESOURCES);
+
+ SDP_PUT16(0, rsp);
+ SDP_PUT32(provider->handle, rsp);
+
+ /* Set reply size */
+ srv->fdidx[fd].rsp_limit = srv->fdidx[fd].omtu - sizeof(sdp_pdu_t);
+ srv->fdidx[fd].rsp_size = rsp - srv->fdidx[fd].rsp;
+ srv->fdidx[fd].rsp_cs = 0;
+
+ return (0);
+}
+
+/*
+ * Send Service Register Response
+ */
+
+int32_t
+server_send_service_register_response(server_p srv, int32_t fd)
+{
+ struct iovec iov[2];
+ sdp_pdu_t pdu;
+ int32_t size;
+
+ assert(srv->fdidx[fd].rsp_size < srv->fdidx[fd].rsp_limit);
+
+ pdu.pid = SDP_PDU_ERROR_RESPONSE;
+ pdu.tid = ((sdp_pdu_p)(srv->req))->tid;
+ pdu.len = htons(srv->fdidx[fd].rsp_size);
+
+ iov[0].iov_base = &pdu;
+ iov[0].iov_len = sizeof(pdu);
+
+ iov[1].iov_base = srv->fdidx[fd].rsp;
+ iov[1].iov_len = srv->fdidx[fd].rsp_size;
+
+ do {
+ size = writev(fd, (struct iovec const *) &iov, sizeof(iov)/sizeof(iov[0]));
+ } while (size < 0 && errno == EINTR);
+
+ srv->fdidx[fd].rsp_cs = 0;
+ srv->fdidx[fd].rsp_size = 0;
+ srv->fdidx[fd].rsp_limit = 0;
+
+ return ((size < 0)? errno : 0);
+}
+
diff --git a/usr.sbin/bluetooth/sdpd/ssar.c b/usr.sbin/bluetooth/sdpd/ssar.c
new file mode 100644
index 0000000..5e52e80
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/ssar.c
@@ -0,0 +1,253 @@
+/*
+ * ssar.c
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: ssar.c,v 1.4 2004/01/12 22:54:31 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <sdp.h>
+#include <string.h>
+#include "profile.h"
+#include "provider.h"
+#include "server.h"
+#include "uuid-private.h"
+
+/* from sar.c */
+int32_t server_prepare_attr_list(provider_p const provider,
+ uint8_t const *req, uint8_t const * const req_end,
+ uint8_t *rsp, uint8_t const * const rsp_end);
+
+/*
+ * Prepare SDP Service Search Attribute Response
+ */
+
+int32_t
+server_prepare_service_search_attribute_response(server_p srv, int32_t fd)
+{
+ uint8_t const *req = srv->req + sizeof(sdp_pdu_t);
+ uint8_t const *req_end = req + ((sdp_pdu_p)(srv->req))->len;
+ uint8_t *rsp = srv->fdidx[fd].rsp;
+ uint8_t const *rsp_end = rsp + NG_L2CAP_MTU_MAXIMUM;
+
+ uint8_t const *sspptr = NULL, *aidptr = NULL;
+ uint8_t *ptr = NULL;
+
+ provider_t *provider = NULL;
+ int32_t type, rsp_limit, ssplen, aidlen, cslen, cs;
+ uint128_t uuid, puuid;
+
+ /*
+ * Minimal Service Search Attribute Request request
+ *
+ * seq8 len8 - 2 bytes
+ * uuid16 value16 - 3 bytes ServiceSearchPattern
+ * value16 - 2 bytes MaximumAttributeByteCount
+ * seq8 len8 - 2 bytes
+ * uint16 value16 - 3 bytes AttributeIDList
+ * value8 - 1 byte ContinuationState
+ */
+
+ if (req_end - req < 13)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ /* Get size of ServiceSearchPattern */
+ ssplen = 0;
+ SDP_GET8(type, req);
+ switch (type) {
+ case SDP_DATA_SEQ8:
+ SDP_GET8(ssplen, req);
+ break;
+
+ case SDP_DATA_SEQ16:
+ SDP_GET16(ssplen, req);
+ break;
+
+ case SDP_DATA_SEQ32:
+ SDP_GET32(ssplen, req);
+ break;
+ }
+ if (ssplen <= 0)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ sspptr = req;
+ req += ssplen;
+
+ /* Get MaximumAttributeByteCount */
+ if (req + 2 > req_end)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ SDP_GET16(rsp_limit, req);
+ if (rsp_limit <= 0)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ /* Get size of AttributeIDList */
+ if (req + 1 > req_end)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ aidlen = 0;
+ SDP_GET8(type, req);
+ switch (type) {
+ case SDP_DATA_SEQ8:
+ if (req + 1 > req_end)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ SDP_GET8(aidlen, req);
+ break;
+
+ case SDP_DATA_SEQ16:
+ if (req + 2 > req_end)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ SDP_GET16(aidlen, req);
+ break;
+
+ case SDP_DATA_SEQ32:
+ if (req + 4 > req_end)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ SDP_GET32(aidlen, req);
+ break;
+ }
+ if (aidlen <= 0)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ aidptr = req;
+ req += aidlen;
+
+ /* Get ContinuationState */
+ if (req + 1 > req_end)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ SDP_GET8(cslen, req);
+ if (cslen != 0) {
+ if (cslen != 2 || req_end - req != 2)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ SDP_GET16(cs, req);
+ } else
+ cs = 0;
+
+ /* Process the request. First, check continuation state */
+ if (srv->fdidx[fd].rsp_cs != cs)
+ return (SDP_ERROR_CODE_INVALID_CONTINUATION_STATE);
+ if (srv->fdidx[fd].rsp_size > 0)
+ return (0);
+
+ /*
+ * Service Search Attribute Response format
+ *
+ * value16 - 2 bytes AttributeListByteCount (not incl.)
+ * seq8 len16 - 3 bytes
+ * attr list - 3+ bytes AttributeLists
+ * [ attr list ]
+ */
+
+ ptr = rsp + 3;
+
+ while (ssplen > 0) {
+ SDP_GET8(type, sspptr);
+ ssplen --;
+
+ switch (type) {
+ case SDP_DATA_UUID16:
+ if (ssplen < 2)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ memcpy(&uuid, &uuid_base, sizeof(uuid));
+ uuid.b[2] = *sspptr ++;
+ uuid.b[3] = *sspptr ++;
+ ssplen -= 2;
+ break;
+
+ case SDP_DATA_UUID32:
+ if (ssplen < 4)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ memcpy(&uuid, &uuid_base, sizeof(uuid));
+ uuid.b[0] = *sspptr ++;
+ uuid.b[1] = *sspptr ++;
+ uuid.b[2] = *sspptr ++;
+ uuid.b[3] = *sspptr ++;
+ ssplen -= 4;
+ break;
+
+ case SDP_DATA_UUID128:
+ if (ssplen < 16)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ memcpy(uuid.b, sspptr, 16);
+ sspptr += 16;
+ ssplen -= 16;
+ break;
+
+ default:
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+ /* NOT REACHED */
+ }
+
+ for (provider = provider_get_first();
+ provider != NULL;
+ provider = provider_get_next(provider)) {
+ if (!provider_match_bdaddr(provider, &srv->req_sa.l2cap_bdaddr))
+ continue;
+
+ memcpy(&puuid, &uuid_base, sizeof(puuid));
+ puuid.b[2] = provider->profile->uuid >> 8;
+ puuid.b[3] = provider->profile->uuid;
+
+ if (memcmp(&uuid, &puuid, sizeof(uuid)) != 0 &&
+ memcmp(&uuid, &uuid_public_browse_group, sizeof(uuid)) != 0)
+ continue;
+
+ cs = server_prepare_attr_list(provider,
+ aidptr, aidptr + aidlen, ptr, rsp_end);
+ if (cs < 0)
+ return (SDP_ERROR_CODE_INSUFFICIENT_RESOURCES);
+
+ ptr += cs;
+ }
+ }
+
+ /* Set reply size (not counting PDU header and continuation state) */
+ srv->fdidx[fd].rsp_limit = srv->fdidx[fd].omtu - sizeof(sdp_pdu_t) - 2;
+ if (srv->fdidx[fd].rsp_limit > rsp_limit)
+ srv->fdidx[fd].rsp_limit = rsp_limit;
+
+ srv->fdidx[fd].rsp_size = ptr - rsp;
+ srv->fdidx[fd].rsp_cs = 0;
+
+ /* Fix AttributeLists sequence header */
+ ptr = rsp;
+ SDP_PUT8(SDP_DATA_SEQ16, ptr);
+ SDP_PUT16(srv->fdidx[fd].rsp_size - 3, ptr);
+
+ return (0);
+}
+
diff --git a/usr.sbin/bluetooth/sdpd/ssr.c b/usr.sbin/bluetooth/sdpd/ssr.c
new file mode 100644
index 0000000..785c8ca
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/ssr.c
@@ -0,0 +1,283 @@
+/*
+ * ssr.c
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: ssr.c,v 1.5 2004/01/13 01:54:39 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#include <sys/uio.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <assert.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <errno.h>
+#include <sdp.h>
+#include <string.h>
+#include "profile.h"
+#include "provider.h"
+#include "server.h"
+#include "uuid-private.h"
+
+/*
+ * Prepare SDP Service Search Response
+ */
+
+int32_t
+server_prepare_service_search_response(server_p srv, int32_t fd)
+{
+ uint8_t const *req = srv->req + sizeof(sdp_pdu_t);
+ uint8_t const *req_end = req + ((sdp_pdu_p)(srv->req))->len;
+ uint8_t *rsp = srv->fdidx[fd].rsp;
+ uint8_t const *rsp_end = rsp + NG_L2CAP_MTU_MAXIMUM;
+
+ uint8_t *ptr = NULL;
+ provider_t *provider = NULL;
+ int32_t type, ssplen, rsp_limit, rcount, cslen, cs;
+ uint128_t uuid, puuid;
+
+ /*
+ * Minimal SDP Service Search Request
+ *
+ * seq8 len8 - 2 bytes
+ * uuid16 value16 - 3 bytes ServiceSearchPattern
+ * value16 - 2 bytes MaximumServiceRecordCount
+ * value8 - 1 byte ContinuationState
+ */
+
+ if (req_end - req < 8)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ /* Get size of ServiceSearchPattern */
+ ssplen = 0;
+ SDP_GET8(type, req);
+ switch (type) {
+ case SDP_DATA_SEQ8:
+ SDP_GET8(ssplen, req);
+ break;
+
+ case SDP_DATA_SEQ16:
+ SDP_GET16(ssplen, req);
+ break;
+
+ case SDP_DATA_SEQ32:
+ SDP_GET32(ssplen, req);
+ break;
+ }
+ if (ssplen <= 0)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ ptr = (uint8_t *) req + ssplen;
+
+ /* Get MaximumServiceRecordCount */
+ if (ptr + 2 > req_end)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ SDP_GET16(rsp_limit, ptr);
+ if (rsp_limit <= 0)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ /* Get ContinuationState */
+ if (ptr + 1 > req_end)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ SDP_GET8(cslen, ptr);
+ if (cslen != 0) {
+ if (cslen != 2 || req_end - ptr != 2)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ SDP_GET16(cs, ptr);
+ } else
+ cs = 0;
+
+ /* Process the request. First, check continuation state */
+ if (srv->fdidx[fd].rsp_cs != cs)
+ return (SDP_ERROR_CODE_INVALID_CONTINUATION_STATE);
+ if (srv->fdidx[fd].rsp_size > 0)
+ return (0);
+
+ /*
+ * Service Search Response format
+ *
+ * value16 - 2 bytes TotalServiceRecordCount (not incl.)
+ * value16 - 2 bytes CurrentServiceRecordCount (not incl.)
+ * value32 - 4 bytes handle
+ * [ value32 ]
+ *
+ * Calculate how many record handles we can fit
+ * in our reply buffer and adjust rlimit.
+ */
+
+ ptr = rsp;
+ rcount = (rsp_end - ptr) / 4;
+ if (rcount < rsp_limit)
+ rsp_limit = rcount;
+
+ /* Look for the record handles */
+ for (rcount = 0; ssplen > 0 && rcount < rsp_limit; ) {
+ SDP_GET8(type, req);
+ ssplen --;
+
+ switch (type) {
+ case SDP_DATA_UUID16:
+ if (ssplen < 2)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ memcpy(&uuid, &uuid_base, sizeof(uuid));
+ uuid.b[2] = *req ++;
+ uuid.b[3] = *req ++;
+ ssplen -= 2;
+ break;
+
+ case SDP_DATA_UUID32:
+ if (ssplen < 4)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ memcpy(&uuid, &uuid_base, sizeof(uuid));
+ uuid.b[0] = *req ++;
+ uuid.b[1] = *req ++;
+ uuid.b[2] = *req ++;
+ uuid.b[3] = *req ++;
+ ssplen -= 4;
+ break;
+
+ case SDP_DATA_UUID128:
+ if (ssplen < 16)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ memcpy(uuid.b, req, 16);
+ req += 16;
+ ssplen -= 16;
+ break;
+
+ default:
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+ /* NOT REACHED */
+ }
+
+ for (provider = provider_get_first();
+ provider != NULL && rcount < rsp_limit;
+ provider = provider_get_next(provider)) {
+ if (!provider_match_bdaddr(provider, &srv->req_sa.l2cap_bdaddr))
+ continue;
+
+ memcpy(&puuid, &uuid_base, sizeof(puuid));
+ puuid.b[2] = provider->profile->uuid >> 8;
+ puuid.b[3] = provider->profile->uuid;
+
+ if (memcmp(&uuid, &puuid, sizeof(uuid)) == 0 ||
+ memcmp(&uuid, &uuid_public_browse_group, sizeof(uuid)) == 0) {
+ SDP_PUT32(provider->handle, ptr);
+ rcount ++;
+ }
+ }
+ }
+
+ /* Set reply size (not counting PDU header and continuation state) */
+ srv->fdidx[fd].rsp_limit = srv->fdidx[fd].omtu - sizeof(sdp_pdu_t) - 4;
+ srv->fdidx[fd].rsp_size = ptr - rsp;
+ srv->fdidx[fd].rsp_cs = 0;
+
+ return (0);
+}
+
+/*
+ * Send SDP Service Search Response
+ */
+
+int32_t
+server_send_service_search_response(server_p srv, int32_t fd)
+{
+ uint8_t *rsp = srv->fdidx[fd].rsp + srv->fdidx[fd].rsp_cs;
+ uint8_t *rsp_end = srv->fdidx[fd].rsp + srv->fdidx[fd].rsp_size;
+
+ struct iovec iov[4];
+ sdp_pdu_t pdu;
+ uint16_t rcounts[2];
+ uint8_t cs[3];
+ int32_t size;
+
+ /* First update continuation state (assume we will send all data) */
+ size = rsp_end - rsp;
+ srv->fdidx[fd].rsp_cs += size;
+
+ if (size + 1 > srv->fdidx[fd].rsp_limit) {
+ /*
+ * We need to split out response. Add 3 more bytes for the
+ * continuation state and move rsp_end and rsp_cs backwards.
+ */
+
+ while ((rsp_end - rsp) + 3 > srv->fdidx[fd].rsp_limit) {
+ rsp_end -= 4;
+ srv->fdidx[fd].rsp_cs -= 4;
+ }
+
+ cs[0] = 2;
+ cs[1] = srv->fdidx[fd].rsp_cs >> 8;
+ cs[2] = srv->fdidx[fd].rsp_cs & 0xff;
+ } else
+ cs[0] = 0;
+
+ assert(rsp_end >= rsp);
+
+ rcounts[0] = srv->fdidx[fd].rsp_size / 4; /* TotalServiceRecordCount */
+ rcounts[1] = (rsp_end - rsp) / 4; /* CurrentServiceRecordCount */
+
+ pdu.pid = SDP_PDU_SERVICE_SEARCH_RESPONSE;
+ pdu.tid = ((sdp_pdu_p)(srv->req))->tid;
+ pdu.len = htons(sizeof(rcounts) + rcounts[1] * 4 + 1 + cs[0]);
+
+ rcounts[0] = htons(rcounts[0]);
+ rcounts[1] = htons(rcounts[1]);
+
+ iov[0].iov_base = &pdu;
+ iov[0].iov_len = sizeof(pdu);
+
+ iov[1].iov_base = rcounts;
+ iov[1].iov_len = sizeof(rcounts);
+
+ iov[2].iov_base = rsp;
+ iov[2].iov_len = rsp_end - rsp;
+
+ iov[3].iov_base = cs;
+ iov[3].iov_len = 1 + cs[0];
+
+ do {
+ size = writev(fd, (struct iovec const *) &iov, sizeof(iov)/sizeof(iov[0]));
+ } while (size < 0 && errno == EINTR);
+
+ /* Check if we have sent (or failed to sent) last response chunk */
+ if (srv->fdidx[fd].rsp_cs == srv->fdidx[fd].rsp_size) {
+ srv->fdidx[fd].rsp_cs = 0;
+ srv->fdidx[fd].rsp_size = 0;
+ srv->fdidx[fd].rsp_limit = 0;
+ }
+
+ return ((size < 0)? errno : 0);
+}
+
diff --git a/usr.sbin/bluetooth/sdpd/sur.c b/usr.sbin/bluetooth/sdpd/sur.c
new file mode 100644
index 0000000..581b593
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/sur.c
@@ -0,0 +1,84 @@
+/*
+ * sur.c
+ *
+ * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: sur.c,v 1.1 2004/01/13 01:54:39 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <errno.h>
+#include <sdp.h>
+#include <string.h>
+#include "profile.h"
+#include "provider.h"
+#include "server.h"
+
+/*
+ * Prepare Service Unregister response
+ */
+
+int32_t
+server_prepare_service_unregister_response(server_p srv, int32_t fd)
+{
+ uint8_t const *req = srv->req + sizeof(sdp_pdu_t);
+ uint8_t const *req_end = req + ((sdp_pdu_p)(srv->req))->len;
+ uint8_t *rsp = srv->fdidx[fd].rsp;
+
+ provider_t *provider = NULL;
+ uint32_t handle;
+
+ /*
+ * Minimal Service Unregister Request
+ *
+ * value32 - uuid 4 bytes
+ */
+
+ if (!srv->fdidx[fd].control ||
+ !srv->fdidx[fd].priv || req_end - req < 4)
+ return (SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX);
+
+ /* Get handle */
+ SDP_GET32(handle, req);
+
+ /* Lookup provider */
+ provider = provider_by_handle(handle);
+ if (provider == NULL || provider->fd != fd)
+ return (SDP_ERROR_CODE_INVALID_SERVICE_RECORD_HANDLE);
+
+ provider_unregister(provider);
+ SDP_PUT16(0, rsp);
+
+ /* Set reply size */
+ srv->fdidx[fd].rsp_limit = srv->fdidx[fd].omtu - sizeof(sdp_pdu_t);
+ srv->fdidx[fd].rsp_size = rsp - srv->fdidx[fd].rsp;
+ srv->fdidx[fd].rsp_cs = 0;
+
+ return (0);
+}
+
diff --git a/usr.sbin/bluetooth/sdpd/uuid-private.h b/usr.sbin/bluetooth/sdpd/uuid-private.h
new file mode 100644
index 0000000..db8d13b
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/uuid-private.h
@@ -0,0 +1,39 @@
+/*
+ * uuid-private.h
+ *
+ * Copyright (c) 2005 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: uuid-private.h,v 1.1 2004/12/09 18:20:26 max Exp $
+ * $FreeBSD$
+ */
+
+#ifndef _UUID_PRIVATE_H_
+#define _UUID_PRIVATE_H_
+
+extern uint128_t uuid_base;
+extern uint128_t uuid_public_browse_group;
+
+#endif /* ndef _UUID_PRIVATE_H_ */
+
diff --git a/usr.sbin/bluetooth/sdpd/uuid.c b/usr.sbin/bluetooth/sdpd/uuid.c
new file mode 100644
index 0000000..dff8d2c
--- /dev/null
+++ b/usr.sbin/bluetooth/sdpd/uuid.c
@@ -0,0 +1,56 @@
+/*
+ * uuid.c
+ *
+ * Copyright (c) 2005 Maksim Yevmenkin <m_evmenkin@yahoo.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.
+ *
+ * $Id: uuid.c,v 1.1 2004/12/09 18:20:26 max Exp $
+ * $FreeBSD$
+ */
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <sdp.h>
+#include <uuid.h>
+#include "uuid-private.h"
+
+uint128_t uuid_base = {
+ .b = {
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ 0x10, 0x00,
+ 0x80, 0x00,
+ 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb
+ }
+};
+
+uint128_t uuid_public_browse_group = {
+ .b = {
+ 0x00, 0x00, 0x10, 0x02,
+ 0x00, 0x00,
+ 0x10, 0x00,
+ 0x80, 0x00,
+ 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb
+ }
+};
+
diff --git a/usr.sbin/boot0cfg/Makefile b/usr.sbin/boot0cfg/Makefile
new file mode 100644
index 0000000..d41945a
--- /dev/null
+++ b/usr.sbin/boot0cfg/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+PROG= boot0cfg
+MAN= boot0cfg.8
+
+LIBADD= geom
+
+NO_WCAST_ALIGN=
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/boot0cfg/Makefile.depend b/usr.sbin/boot0cfg/Makefile.depend
new file mode 100644
index 0000000..851372c
--- /dev/null
+++ b/usr.sbin/boot0cfg/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libexpat \
+ lib/libgeom \
+ lib/libsbuf \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/boot0cfg/boot0cfg.8 b/usr.sbin/boot0cfg/boot0cfg.8
new file mode 100644
index 0000000..bf0d35d
--- /dev/null
+++ b/usr.sbin/boot0cfg/boot0cfg.8
@@ -0,0 +1,204 @@
+.\" Copyright (c) 1999 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 AND CONTRIBUTORS``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED 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 BOOT0CFG 8
+.Os
+.Sh NAME
+.Nm boot0cfg
+.Nd boot manager installation/configuration utility
+.Sh SYNOPSIS
+.Nm
+.Op Fl Bv
+.Op Fl b Ar boot0
+.Op Fl d Ar drive
+.Op Fl e Ar bell character
+.Op Fl f Ar file
+.Op Fl i Ar volume-id
+.Op Fl m Ar mask
+.Op Fl o Ar options
+.Op Fl s Ar slice
+.Op Fl t Ar ticks
+.Ar disk
+.Sh DESCRIPTION
+The
+.Fx
+.Sq boot0
+boot manager permits the operator to select from which disk and
+slice an i386 machine (PC) is booted.
+.Pp
+Note that what are referred to here as
+.Dq slices
+are typically called
+.Dq partitions
+in
+.No non- Ns Bx
+documentation relating to the PC.
+Typically, only non-removable disks are sliced.
+.Pp
+The
+.Nm
+utility optionally installs the
+.Sq boot0
+boot manager on the specified
+.Ar disk ;
+and allows various operational parameters to be configured.
+.Pp
+On PCs, a boot manager typically occupies sector 0 of a disk, which is
+known as the Master Boot Record (MBR).
+The MBR contains both code (to which control is passed by the PC BIOS)
+and data (an embedded table of defined slices).
+.Pp
+The options are:
+.Bl -tag -width indent
+.It Fl B
+Install the
+.Sq boot0
+boot manager.
+This option causes MBR code to be replaced, without
+affecting the embedded slice table.
+.It Fl b Ar boot0
+Specify which
+.Sq boot0
+image to use.
+The default is
+.Pa /boot/boot0
+which will use the video card as output, alternatively
+.Pa /boot/boot0sio
+can be used for output to the COM1 port.
+(Be aware that nothing will be output to the COM1 port unless the
+modem signals DSR and CTS are active.)
+.It Fl d Ar drive
+Specify the drive number used by the PC BIOS in referencing the drive
+which contains the specified
+.Ar disk .
+Typically this will be 0x80 for the first hard drive, 0x81 for the
+second hard drive, and so on; however any integer between 0 and 0xff
+is acceptable here.
+.It Fl e Ar bell character
+Set the character to be printed in case of input error.
+.It Fl f Ar file
+Specify that a backup copy of the preexisting MBR should be written to
+.Ar file .
+This file is created if it does not exist, and replaced if it does.
+.It Fl i Ar volume-id
+Specifies a volume-id (in the form XXXX-XXXX) to be saved at location
+0x1b8 in the MBR. This information is sometimes used by NT, XP and Vista
+to identify the disk drive. The option is only compatible with version 2.00
+of the 512-byte boot block.
+.It Fl m Ar mask
+Specify slices to be enabled/disabled, where
+.Ar mask
+is an integer between 0 (no slices enabled) and 0xf (all four slices
+enabled).
+Each mask bit enables corresponding slice if set to 1.
+The least significant bit of the mask corresponds to slice 1,
+the most significant bit of the mask corresponds to slice 4.
+.It Fl o Ar options
+A comma-separated string of any of the following options may be
+specified (with
+.Dq no
+prepended as necessary):
+.Bl -tag -width indent
+.It packet
+Use the disk packet (BIOS INT 0x13 extensions) interface,
+as opposed to the legacy (CHS) interface, when doing disk I/O.
+This allows booting above cylinder 1023, but requires specific
+BIOS support.
+The default is
+.Sq packet .
+.It setdrv
+Forces the drive containing the disk to be referenced using drive
+number definable by means of the -d option.
+The default is
+.Sq nosetdrv .
+.It update
+Allow the MBR to be updated by the boot manager.
+(The MBR may be updated to flag slices as
+.Sq active ,
+and to save slice selection information.)
+This is the default; a
+.Sq noupdate
+option causes the MBR to be treated as read-only.
+.El
+.It Fl s Ar slice
+Set the default boot selection to
+.Ar slice .
+Values between 1 and 4 refer to slices; a value of 5 refers to the
+option of booting from a second disk.
+The special string
+.Dq PXE
+or a value of 6 can be used to boot via PXE.
+.It Fl t Ar ticks
+Set the timeout value to
+.Ar ticks .
+(There are approximately 18.2 ticks per second.)
+.It Fl v
+Verbose: display information about the slices defined, etc.
+.El
+.Sh FILES
+.Bl -tag -width /boot/boot0sio -compact
+.It Pa /boot/boot0
+The default
+.Sq boot0
+image
+.It Pa /boot/boot0sio
+Image for serial consoles (COM1,9600,8,N,1,MODEM)
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+To boot slice 2 on the next boot:
+.Pp
+.Dl "boot0cfg -s 2 ada0"
+.Pp
+To enable just slices 1 and 3 in the menu:
+.Pp
+.Dl "boot0cfg -m 0x5 ada0"
+.Pp
+To go back to non-interactive booting, use
+.Xr gpart 8
+to install the default MBR:
+.Pp
+.Dl "gpart bootcode -b /boot/mbr ada0"
+.Sh SEE ALSO
+.Xr geom 4 ,
+.Xr boot 8 ,
+.Xr gpart 8
+.Sh AUTHORS
+.An Robert Nordier Aq Mt rnordier@FreeBSD.org
+.Sh BUGS
+Use of the
+.Sq packet
+option may cause
+.Sq boot0
+to fail, depending on the nature of BIOS support.
+.Pp
+Use of the
+.Sq setdrv
+option with an incorrect -d operand may cause the boot0 code
+to write the MBR to the wrong disk, thus trashing its previous
+content. Be careful.
diff --git a/usr.sbin/boot0cfg/boot0cfg.c b/usr.sbin/boot0cfg/boot0cfg.c
new file mode 100644
index 0000000..f2cbf67
--- /dev/null
+++ b/usr.sbin/boot0cfg/boot0cfg.c
@@ -0,0 +1,607 @@
+/*
+ * Copyright (c) 2008 Luigi Rizzo
+ * Copyright (c) 1999 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 AND CONTRIBUTORS``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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/disklabel.h>
+#include <sys/diskmbr.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgeom.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define MBRSIZE 512 /* master boot record size */
+
+#define OFF_VERSION 0x1b0 /* offset: version number, only boot0version */
+#define OFF_SERIAL 0x1b8 /* offset: volume serial number */
+#define OFF_PTBL 0x1be /* offset: partition table */
+#define OFF_MAGIC 0x1fe /* offset: magic number */
+/*
+ * Offsets to the parameters of the 512-byte boot block.
+ * For historical reasons they are set as macros
+ */
+struct opt_offsets {
+ int opt;
+ int drive;
+ int flags;
+ int ticks;
+};
+
+static struct opt_offsets b0_ofs[] = {
+ { 0x0, 0x0, 0x0, 0x0 }, /* no boot block */
+ { 0x1b9, 0x1ba, 0x1bb, 0x1bc }, /* original block */
+ { 0x1b5, 0x1b6, 0x1b7, 0x1bc }, /* NT_SERIAL block */
+};
+
+static int b0_ver; /* boot block version set by boot0bs */
+
+#define OFF_OPT (b0_ofs[b0_ver].opt) /* default boot option */
+#define OFF_DRIVE (b0_ofs[b0_ver].drive) /* setdrv drive */
+#define OFF_FLAGS (b0_ofs[b0_ver].flags) /* option flags */
+#define OFF_TICKS (b0_ofs[b0_ver].ticks) /* clock ticks */
+
+
+#define cv2(p) ((p)[0] | (p)[1] << 010)
+
+#define mk2(p, x) \
+ (p)[0] = (u_int8_t)(x), \
+ (p)[1] = (u_int8_t)((x) >> 010)
+
+static const struct {
+ const char *tok;
+ int def;
+} opttbl[] = {
+ {"packet", 0},
+ {"update", 1},
+ {"setdrv", 0}
+};
+static const int nopt = sizeof(opttbl) / sizeof(opttbl[0]);
+
+static const char fmt0[] = "# flag start chs type"
+ " end chs offset size\n";
+
+static const char fmt1[] = "%d 0x%02x %4u:%3u:%2u 0x%02x"
+ " %4u:%3u:%2u %10u %10u\n";
+
+static int geom_class_available(const char *);
+static int read_mbr(const char *, u_int8_t **, int);
+static void write_mbr(const char *, int, u_int8_t *, int);
+static void display_mbr(u_int8_t *);
+static int boot0version(const u_int8_t *);
+static int boot0bs(const u_int8_t *);
+static void stropt(const char *, int *, int *);
+static int argtoi(const char *, int, int, int);
+static int set_bell(u_int8_t *, int, int);
+static void usage(void);
+
+static unsigned vol_id[5]; /* 4 plus 1 for flag */
+
+static int v_flag;
+/*
+ * Boot manager installation/configuration utility.
+ */
+int
+main(int argc, char *argv[])
+{
+ u_int8_t *mbr, *boot0;
+ int boot0_size, mbr_size;
+ const char *bpath, *fpath;
+ char *disk;
+ int B_flag, o_flag;
+ int d_arg, m_arg, s_arg, t_arg;
+ int o_and, o_or, o_e = -1;
+ int up, c;
+
+ bpath = "/boot/boot0";
+ fpath = NULL;
+ B_flag = v_flag = o_flag = 0;
+ d_arg = m_arg = s_arg = t_arg = -1;
+ o_and = 0xff;
+ o_or = 0;
+ while ((c = getopt(argc, argv, "Bvb:d:e:f:i:m:o:s:t:")) != -1)
+ switch (c) {
+ case 'B':
+ B_flag = 1;
+ break;
+ case 'v':
+ v_flag = 1;
+ break;
+ case 'b':
+ bpath = optarg;
+ break;
+ case 'd':
+ d_arg = argtoi(optarg, 0, 0xff, 'd');
+ break;
+ case 'e':
+ if (optarg[0] == '0' && optarg[1] == 'x')
+ sscanf(optarg, "0x%02x", &o_e);
+ else
+ o_e = optarg[0];
+ break;
+ case 'f':
+ fpath = optarg;
+ break;
+ case 'i':
+ if (sscanf(optarg, "%02x%02x-%02x%02x",
+ vol_id, vol_id+1, vol_id+2, vol_id+3) == 4)
+ vol_id[4] = 1;
+ else
+ errx(1, "bad argument %s", optarg);
+ break;
+ case 'm':
+ m_arg = argtoi(optarg, 0, 0xf, 'm');
+ break;
+ case 'o':
+ stropt(optarg, &o_and, &o_or);
+ o_flag = 1;
+ break;
+ case 's':
+ if (strcasecmp(optarg, "pxe") == 0)
+ s_arg = 6;
+ else
+ s_arg = argtoi(optarg, 1, 6, 's');
+ break;
+ case 't':
+ t_arg = argtoi(optarg, 1, 0xffff, 't');
+ break;
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 1)
+ usage();
+ disk = g_device_path(*argv);
+ if (disk == NULL)
+ errx(1, "Unable to get providername for %s\n", *argv);
+ up = B_flag || d_arg != -1 || m_arg != -1 || o_flag || s_arg != -1
+ || t_arg != -1;
+
+ /* open the disk and read in the existing mbr. Either here or
+ * when reading the block from disk, we do check for the version
+ * and abort if a suitable block is not found.
+ */
+ mbr_size = read_mbr(disk, &mbr, !B_flag);
+
+ /* save the existing MBR if we are asked to do so */
+ if (fpath)
+ write_mbr(fpath, O_CREAT | O_TRUNC, mbr, mbr_size);
+
+ /*
+ * If we are installing the boot loader, read it from disk and copy the
+ * slice table over from the existing MBR. If not, then point boot0
+ * back at the MBR we just read in. After this, boot0 is the data to
+ * write back to disk if we are going to do a write.
+ */
+ if (B_flag) {
+ boot0_size = read_mbr(bpath, &boot0, 1);
+ memcpy(boot0 + OFF_PTBL, mbr + OFF_PTBL,
+ sizeof(struct dos_partition) * NDOSPART);
+ if (b0_ver == 2) /* volume serial number support */
+ memcpy(boot0 + OFF_SERIAL, mbr + OFF_SERIAL, 4);
+ } else {
+ boot0 = mbr;
+ boot0_size = mbr_size;
+ }
+
+ /* set the drive */
+ if (d_arg != -1)
+ boot0[OFF_DRIVE] = d_arg;
+
+ /* set various flags */
+ if (m_arg != -1) {
+ boot0[OFF_FLAGS] &= 0xf0;
+ boot0[OFF_FLAGS] |= m_arg;
+ }
+ if (o_flag) {
+ boot0[OFF_FLAGS] &= o_and;
+ boot0[OFF_FLAGS] |= o_or;
+ }
+
+ /* set the default boot selection */
+ if (s_arg != -1)
+ boot0[OFF_OPT] = s_arg - 1;
+
+ /* set the timeout */
+ if (t_arg != -1)
+ mk2(boot0 + OFF_TICKS, t_arg);
+
+ /* set the bell char */
+ if (o_e != -1 && set_bell(boot0, o_e, 0) != -1)
+ up = 1;
+
+ if (vol_id[4]) {
+ if (b0_ver != 2)
+ errx(1, "incompatible boot block, cannot set volume ID");
+ boot0[OFF_SERIAL] = vol_id[0];
+ boot0[OFF_SERIAL+1] = vol_id[1];
+ boot0[OFF_SERIAL+2] = vol_id[2];
+ boot0[OFF_SERIAL+3] = vol_id[3];
+ up = 1; /* force update */
+ }
+ /* write the MBR back to disk */
+ if (up)
+ write_mbr(disk, 0, boot0, boot0_size);
+
+ /* display the MBR */
+ if (v_flag)
+ display_mbr(boot0);
+
+ /* clean up */
+ if (mbr != boot0)
+ free(boot0);
+ free(mbr);
+ free(disk);
+
+ return 0;
+}
+
+/* get or set the 'bell' character to be used in case of errors.
+ * Lookup for a certain code sequence, return -1 if not found.
+ */
+static int
+set_bell(u_int8_t *mbr, int new_bell, int report)
+{
+ /* lookup sequence: 0x100 means skip, 0x200 means done */
+ static unsigned seq[] =
+ { 0xb0, 0x100, 0xe8, 0x100, 0x100, 0x30, 0xe4, 0x200 };
+ int ofs, i, c;
+ for (ofs = 0x60; ofs < 0x180; ofs++) { /* search range */
+ if (mbr[ofs] != seq[0]) /* search initial pattern */
+ continue;
+ for (i=0;; i++) {
+ if (seq[i] == 0x200) { /* found */
+ c = mbr[ofs+1];
+ if (!report)
+ mbr[ofs+1] = c = new_bell;
+ else
+ printf(" bell=%c (0x%x)",
+ (c >= ' ' && c < 0x7f) ? c : ' ', c);
+ return c;
+ }
+ if (seq[i] != 0x100 && seq[i] != mbr[ofs+i])
+ break;
+ }
+ }
+ warn("bell not found");
+ return -1;
+}
+/*
+ * Read in the MBR of the disk. If it is boot0, then use the version to
+ * read in all of it if necessary. Use pointers to return a malloc'd
+ * buffer containing the MBR and then return its size.
+ */
+static int
+read_mbr(const char *disk, u_int8_t **mbr, int check_version)
+{
+ u_int8_t buf[MBRSIZE];
+ int mbr_size, fd;
+ int ver;
+ ssize_t n;
+
+ if ((fd = open(disk, O_RDONLY)) == -1)
+ err(1, "open %s", disk);
+ if ((n = read(fd, buf, MBRSIZE)) == -1)
+ err(1, "read %s", disk);
+ if (n != MBRSIZE)
+ errx(1, "%s: short read", disk);
+ if (cv2(buf + OFF_MAGIC) != 0xaa55)
+ errx(1, "%s: bad magic", disk);
+
+ if (! (ver = boot0bs(buf))) {
+ if (check_version)
+ errx(1, "%s: unknown or incompatible boot code", disk);
+ } else if (boot0version(buf) == 0x101) {
+ mbr_size = 1024;
+ if ((*mbr = malloc(mbr_size)) == NULL)
+ errx(1, "%s: unable to allocate read buffer", disk);
+ if (lseek(fd, 0, SEEK_SET) == -1 ||
+ (n = read(fd, *mbr, mbr_size)) == -1)
+ err(1, "%s", disk);
+ if (n != mbr_size)
+ errx(1, "%s: short read", disk);
+ close(fd);
+ return (mbr_size);
+ }
+ *mbr = malloc(sizeof(buf));
+ if (*mbr == NULL)
+ errx(1, "%s: unable to allocate MBR buffer", disk);
+ memcpy(*mbr, buf, sizeof(buf));
+ close(fd);
+
+ return sizeof(buf);
+}
+
+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);
+}
+
+/*
+ * Write out the mbr to the specified file.
+ */
+static void
+write_mbr(const char *fname, int flags, u_int8_t *mbr, int mbr_size)
+{
+ struct gctl_req *grq;
+ const char *errmsg;
+ char *pname;
+ ssize_t n;
+ int fd;
+
+ fd = open(fname, O_WRONLY | flags, 0666);
+ if (fd != -1) {
+ n = write(fd, mbr, mbr_size);
+ close(fd);
+ if (n != mbr_size)
+ errx(1, "%s: short write", fname);
+ return;
+ }
+
+ /*
+ * If we're called to write to a backup file, don't try to
+ * write through GEOM.
+ */
+ if (flags != 0)
+ err(1, "can't open file %s to write backup", fname);
+
+ /* Try open it read only. */
+ fd = open(fname, O_RDONLY);
+ if (fd == -1) {
+ warn("error opening %s", fname);
+ return;
+ }
+
+ pname = g_providername(fd);
+ if (pname == NULL) {
+ warn("error getting providername for %s", fname);
+ return;
+ }
+
+ /* First check that GEOM_PART is available */
+ if (geom_class_available("PART") != 0) {
+ grq = gctl_get_handle();
+ gctl_ro_param(grq, "class", -1, "PART");
+ gctl_ro_param(grq, "arg0", -1, pname);
+ gctl_ro_param(grq, "verb", -1, "bootcode");
+ gctl_ro_param(grq, "bootcode", mbr_size, mbr);
+ gctl_ro_param(grq, "flags", -1, "C");
+ errmsg = gctl_issue(grq);
+ if (errmsg != NULL && errmsg[0] != '\0')
+ errx(1, "GEOM_PART: write bootcode to %s failed: %s",
+ fname, errmsg);
+ gctl_free(grq);
+ } else 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");
+ gctl_ro_param(grq, "geom", -1, pname);
+ gctl_ro_param(grq, "data", mbr_size, mbr);
+ errmsg = gctl_issue(grq);
+ if (errmsg != NULL)
+ err(1, "GEOM_MBR: write MBR to %s failed", fname);
+ gctl_free(grq);
+ } else
+ errx(1, "can't write MBR to %s", fname);
+ free(pname);
+}
+
+/*
+ * Outputs an informative dump of the data in the MBR to stdout.
+ */
+static void
+display_mbr(u_int8_t *mbr)
+{
+ struct dos_partition *part;
+ int i, version;
+
+ part = (struct dos_partition *)(mbr + DOSPARTOFF);
+ printf(fmt0);
+ for (i = 0; i < NDOSPART; i++)
+ if (part[i].dp_typ)
+ printf(fmt1, 1 + i, part[i].dp_flag,
+ part[i].dp_scyl + ((part[i].dp_ssect & 0xc0) << 2),
+ part[i].dp_shd, part[i].dp_ssect & 0x3f, part[i].dp_typ,
+ part[i].dp_ecyl + ((part[i].dp_esect & 0xc0) << 2),
+ part[i].dp_ehd, part[i].dp_esect & 0x3f, part[i].dp_start,
+ part[i].dp_size);
+ printf("\n");
+ version = boot0version(mbr);
+ printf("version=%d.%d drive=0x%x mask=0x%x ticks=%u",
+ version >> 8, version & 0xff, mbr[OFF_DRIVE],
+ mbr[OFF_FLAGS] & 0xf, cv2(mbr + OFF_TICKS));
+ set_bell(mbr, 0, 1);
+ printf("\noptions=");
+ for (i = 0; i < nopt; i++) {
+ if (i)
+ printf(",");
+ if (!(mbr[OFF_FLAGS] & 1 << (7 - i)) ^ opttbl[i].def)
+ printf("no");
+ printf("%s", opttbl[i].tok);
+ }
+ printf("\n");
+ if (b0_ver == 2)
+ printf("volume serial ID %02x%02x-%02x%02x\n",
+ mbr[OFF_SERIAL], mbr[OFF_SERIAL+1],
+ mbr[OFF_SERIAL+2], mbr[OFF_SERIAL+3]);
+ printf("default_selection=F%d (", mbr[OFF_OPT] + 1);
+ if (mbr[OFF_OPT] < 4)
+ printf("Slice %d", mbr[OFF_OPT] + 1);
+ else if (mbr[OFF_OPT] == 4)
+ printf("Drive 1");
+ else
+ printf("PXE");
+ printf(")\n");
+}
+
+/*
+ * Return the boot0 version with the minor revision in the low byte, and
+ * the major revision in the next higher byte.
+ */
+static int
+boot0version(const u_int8_t *bs)
+{
+ /* Check for old version, and return 0x100 if found. */
+ int v = boot0bs(bs);
+ if (v != 0)
+ return v << 8;
+
+ /* We have a newer boot0, so extract the version number and return it. */
+ return *(const int *)(bs + OFF_VERSION) & 0xffff;
+}
+
+/* descriptor of a pattern to match.
+ * Start from the first entry trying to match the chunk of bytes,
+ * if you hit an entry with len=0 terminate the search and report
+ * off as the version. Otherwise skip to the next block after len=0
+ * An entry with len=0, off=0 is the end marker.
+ */
+struct byte_pattern {
+ unsigned off;
+ unsigned len;
+ u_int8_t *key;
+};
+
+/*
+ * Decide if we have valid boot0 boot code by looking for
+ * characteristic byte sequences at fixed offsets.
+ */
+static int
+boot0bs(const u_int8_t *bs)
+{
+ /* the initial code sequence */
+ static u_int8_t id0[] = {0xfc, 0x31, 0xc0, 0x8e, 0xc0, 0x8e, 0xd8,
+ 0x8e, 0xd0, 0xbc, 0x00, 0x7c };
+ /* the drive id */
+ static u_int8_t id1[] = {'D', 'r', 'i', 'v', 'e', ' '};
+ static struct byte_pattern patterns[] = {
+ {0x0, sizeof(id0), id0},
+ {0x1b2, sizeof(id1), id1},
+ {1, 0, NULL},
+ {0x0, sizeof(id0), id0}, /* version with NT support */
+ {0x1ae, sizeof(id1), id1},
+ {2, 0, NULL},
+ {0, 0, NULL},
+ };
+ struct byte_pattern *p = patterns;
+
+ for (; p->off || p->len; p++) {
+ if (p->len == 0)
+ break;
+ if (!memcmp(bs + p->off, p->key, p->len)) /* match */
+ continue;
+ while (p->len) /* skip to next block */
+ p++;
+ }
+ b0_ver = p->off; /* XXX ugly side effect */
+ return p->off;
+}
+
+/*
+ * Adjust "and" and "or" masks for a -o option argument.
+ */
+static void
+stropt(const char *arg, int *xa, int *xo)
+{
+ const char *q;
+ char *s, *s1;
+ int inv, i, x;
+
+ if (!(s = strdup(arg)))
+ err(1, NULL);
+ for (s1 = s; (q = strtok(s1, ",")); s1 = NULL) {
+ if ((inv = !strncmp(q, "no", 2)))
+ q += 2;
+ for (i = 0; i < nopt; i++)
+ if (!strcmp(q, opttbl[i].tok))
+ break;
+ if (i == nopt)
+ errx(1, "%s: Unknown -o option", q);
+ if (opttbl[i].def)
+ inv ^= 1;
+ x = 1 << (7 - i);
+ if (inv)
+ *xa &= ~x;
+ else
+ *xo |= x;
+ }
+ free(s);
+}
+
+/*
+ * Convert and check an option argument.
+ */
+static int
+argtoi(const char *arg, int lo, int hi, int opt)
+{
+ char *s;
+ long x;
+
+ errno = 0;
+ x = strtol(arg, &s, 0);
+ if (errno || !*arg || *s || x < lo || x > hi)
+ errx(1, "%s: Bad argument to -%c option", arg, opt);
+ return x;
+}
+
+/*
+ * Display usage information.
+ */
+static void
+usage(void)
+{
+ fprintf(stderr, "%s\n%s\n",
+ "usage: boot0cfg [-Bv] [-b boot0] [-d drive] [-f file] [-m mask]",
+ " [-o options] [-s slice] [-t ticks] disk");
+ exit(1);
+}
diff --git a/usr.sbin/boot98cfg/Makefile b/usr.sbin/boot98cfg/Makefile
new file mode 100644
index 0000000..57dfe94
--- /dev/null
+++ b/usr.sbin/boot98cfg/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+PROG= boot98cfg
+MAN= boot98cfg.8
+
+WARNS?= 2
+
+LIBADD= geom
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/boot98cfg/Makefile.depend b/usr.sbin/boot98cfg/Makefile.depend
new file mode 100644
index 0000000..2df49d0
--- /dev/null
+++ b/usr.sbin/boot98cfg/Makefile.depend
@@ -0,0 +1,17 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libgeom \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/boot98cfg/boot98cfg.8 b/usr.sbin/boot98cfg/boot98cfg.8
new file mode 100644
index 0000000..96ebbcd
--- /dev/null
+++ b/usr.sbin/boot98cfg/boot98cfg.8
@@ -0,0 +1,104 @@
+.\" Copyright (c) KATO Takenori, 2000.
+.\"
+.\" All rights reserved. Unpublished rights reserved under the copyright
+.\" laws of Japan.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\"
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer as
+.\" the first lines of this file unmodified.
+.\" 2. Redistributions in binary form must 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 July 10, 2000
+.Dt BOOT98CFG 8
+.Os
+.Sh NAME
+.Nm boot98cfg
+.Nd HDD boot manager installation utility
+.Sh SYNOPSIS
+.Nm
+.Op Fl B
+.Op Fl i Ar boot0
+.Op Fl m Ar boot0.5
+.Op Fl s Ar secsize
+.Op Fl v Ar version
+.Op Fl f Ar boot0.bak
+.Op Fl F Ar boot0.5.bak
+.Ar disk
+.Sh DESCRIPTION
+On NEC PC-98s,
+.Sq boot loader
+consists of the
+.Sq IPL
+and
+.Sq HDD boot menu .
+The IPL occupies sector 0 of a disk and is followed by the partition
+table.
+The IPL loads the HDD boot menu that starts from 0x400.
+.Pp
+The
+.Nm
+utility installs and makes backup copy of the IPL and the HDD boot menu; and
+allows changing the version number field in the sector 0.
+.Pp
+Note that the format command in NEC's OSs replaces the HDD boot menu
+with its own HDD boot menu when the version number field is smaller
+than that in the format command.
+.Pp
+The options are:
+.Bl -tag -width indent
+.It Fl B
+Install the IPL and HDD boot menu.
+This option causes the IPL and HDD
+boot menu code to be replaced.
+.It Fl i Ar boot0
+Specify which IPL image to use.
+The default is /boot/boot0.
+.It Fl m Ar boot0.5
+Specify which HDD boot menu image to use.
+The default is
+/boot/boot0.5.
+.It Fl f Ar boot0.bak
+Specify that a backup copy of the preexisting IPL should be written to
+.Ar boot0.bak .
+This file is created if it does not exist, and truncated if it does.
+.It Fl F Ar boot0.5.bak
+Specify that a backup copy of the preexisting HDD boot menu should be
+written to
+.Ar boot0.5.bak .
+This file is created if it does not exist, and truncated if it does.
+.It Fl v Ar version
+Specify the version number.
+.It Fl s Ar secsize
+Specify the sector size.
+The default sector size is 512
+(bytes/sector).
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr boot 8 ,
+.Xr fdisk 8
+.Sh AUTHORS
+.An NOKUBI Hirotaka ,
+.An KATO Takenori
diff --git a/usr.sbin/boot98cfg/boot98cfg.c b/usr.sbin/boot98cfg/boot98cfg.c
new file mode 100644
index 0000000..0ed1b17
--- /dev/null
+++ b/usr.sbin/boot98cfg/boot98cfg.c
@@ -0,0 +1,319 @@
+/*
+ * Copyright (c) KATO Takenori, 2000.
+ *
+ * All rights reserved. Unpublished rights reserved under the copyright
+ * laws of Japan.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer as
+ * the first lines of this file unmodified.
+ * 2. Redistributions in binary form must 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.
+ */
+
+/*
+ * Copyright (c) 1999 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 AND CONTRIBUTORS``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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/diskpc98.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgeom.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define BOOTSIZE 0x2000
+#define IPLSIZE 512 /* IPL size */
+#define BOOTMENUSIZE 7168 /* Max HDD boot menu size */
+#define BOOTMENUOFF 0x400
+
+u_char boot0buf[BOOTSIZE];
+u_char ipl[IPLSIZE];
+u_char menu[BOOTMENUSIZE];
+
+static int read_boot(const char *, u_char *);
+static int write_boot(const char *, u_char *);
+static char *mkrdev(const char *);
+static void usage(void);
+
+/*
+ * Boot manager installation/configuration utility.
+ */
+int
+main(int argc, char *argv[])
+{
+ char *endptr;
+ const char *iplpath = "/boot/boot0", *menupath = "/boot/boot0.5";
+ char *iplbakpath = NULL, *menubakpath = NULL;
+ char *disk;
+ int B_flag = 0;
+ int c;
+ int fd1;
+ int n;
+ int secsize = 512;
+ int v_flag = 0, version;
+
+ while ((c = getopt(argc, argv, "BF:f:i:m:s:v:")) != -1) {
+ switch (c) {
+ case 'B':
+ B_flag = 1;
+ break;
+ case 'F':
+ menubakpath = optarg;
+ break;
+ case 'f':
+ iplbakpath = optarg;
+ break;
+ case 'i':
+ iplpath = optarg;
+ break;
+ case 'm':
+ menupath = optarg;
+ break;
+ case 's':
+ secsize = strtol(optarg, &endptr, 0);
+ if (errno || *optarg == '\0' || *endptr)
+ errx(1, "%s: Bad argument to -s option",
+ optarg);
+ switch (secsize) {
+ case 256:
+ case 512:
+ case 1024:
+ case 2048:
+ break;
+ default:
+ errx(1, "%s: unsupported sector size", optarg);
+ break;
+ }
+ break;
+ case 'v':
+ v_flag = 1;
+ version = strtol(optarg, &endptr, 0);
+ if (errno || *optarg == '\0' || *endptr ||
+ version < 0 || version > 255)
+ errx(1, "%s: Bad argument to -v option",
+ optarg);
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 1)
+ usage();
+ disk = mkrdev(*argv);
+
+ read_boot(disk, boot0buf);
+
+ if (iplbakpath != NULL) {
+ fd1 = open(iplbakpath, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (fd1 < 0)
+ err(1, "%s", iplbakpath);
+ n = write(fd1, boot0buf, IPLSIZE);
+ if (n == -1)
+ err(1, "%s", iplbakpath);
+ if (n != IPLSIZE)
+ errx(1, "%s: short write", iplbakpath);
+ close(fd1);
+ }
+
+ if (menubakpath != NULL) {
+ fd1 = open(menubakpath, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (fd1 < 0)
+ err(1, "%s", menubakpath);
+ n = write(fd1, boot0buf + BOOTMENUOFF, BOOTMENUSIZE);
+ if (n == -1)
+ err(1, "%s", menubakpath);
+ if (n != BOOTMENUSIZE)
+ errx(1, "%s: short write", menubakpath);
+ close(fd1);
+ }
+
+ if (B_flag) {
+ /* Read IPL (boot0). */
+ fd1 = open(iplpath, O_RDONLY);
+ if (fd1 < 0)
+ err(1, "%s", disk);
+ n = read(fd1, ipl, IPLSIZE);
+ if (n < 0)
+ err(1, "%s", iplpath);
+ if (n != IPLSIZE)
+ errx(1, "%s: invalid file", iplpath);
+ close(fd1);
+
+ /* Read HDD boot menu (boot0.5). */
+ fd1 = open(menupath, O_RDONLY);
+ if (fd1 < 0)
+ err(1, "%s", disk);
+ n = read(fd1, menu, BOOTMENUSIZE);
+ if (n < 0)
+ err(1, "%s", menupath);
+ if (n != BOOTMENUSIZE)
+ errx(1, "%s: invalid file", menupath);
+ close(fd1);
+
+ memcpy(boot0buf, ipl, IPLSIZE);
+ memcpy(boot0buf + BOOTMENUOFF, menu, BOOTMENUSIZE);
+ }
+
+ /* Set version number field. */
+ if (v_flag)
+ *(boot0buf + secsize - 4) = (u_char)version;
+
+ if (B_flag || v_flag)
+ write_boot(disk, boot0buf);
+
+ return 0;
+}
+
+static int
+read_boot(const char *disk, u_char *boot)
+{
+ int fd, n;
+
+ /* Read IPL, partition table and HDD boot menu. */
+ fd = open(disk, O_RDONLY);
+ if (fd < 0)
+ err(1, "%s", disk);
+ n = read(fd, boot, BOOTSIZE);
+ if (n != BOOTSIZE)
+ errx(1, "%s: short read", disk);
+ close(fd);
+
+ return 0;
+}
+
+static int
+write_boot(const char *disk, u_char *boot)
+{
+ int fd, n, i;
+ char buf[MAXPATHLEN];
+ const char *q;
+ struct gctl_req *grq;
+
+ fd = open(disk, O_WRONLY, 0666);
+ if (fd != -1) {
+ if ((n = write(fd, boot, BOOTSIZE)) < 0)
+ err(1, "%s", disk);
+ if (n != BOOTSIZE)
+ errx(1, "%s: short write", disk);
+ close(fd);
+ return 0;
+ }
+
+ 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", BOOTSIZE, boot);
+ q = gctl_issue(grq);
+ if (q == NULL)
+ return 0;
+
+ warnx("%s: %s", disk, q);
+ gctl_free(grq);
+
+ for (i = 0; i < PC98_NPARTS; i++) {
+ snprintf(buf, sizeof(buf), "%ss%d", disk, i + 1);
+ fd = open(buf, O_RDONLY);
+ if (fd < 0)
+ continue;
+ n = ioctl(fd, DIOCSPC98, boot);
+ if (n != 0)
+ err(1, "%s: ioctl DIOCSPC98", disk);
+ close(fd);
+ return 0;
+ }
+
+ err(1, "%s", disk);
+}
+
+/*
+ * Produce a device path for a "canonical" name, where appropriate.
+ */
+static char *
+mkrdev(const char *fname)
+{
+ char buf[MAXPATHLEN];
+ char *s;
+
+ if (!strchr(fname, '/')) {
+ snprintf(buf, sizeof(buf), "%s%s", _PATH_DEV, fname);
+ s = strdup(buf);
+ } else
+ s = strdup(fname);
+
+ if (s == NULL)
+ errx(1, "No more memory");
+ return s;
+}
+
+/*
+ * Display usage information.
+ */
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "boot98cfg [-B][-i boot0][-m boot0.5][-s secsize][-v version]\n"
+ " [-f ipl.bak][-F menu.bak] disk\n");
+ exit(1);
+}
diff --git a/usr.sbin/bootparamd/Makefile b/usr.sbin/bootparamd/Makefile
new file mode 100644
index 0000000..c4e3361
--- /dev/null
+++ b/usr.sbin/bootparamd/Makefile
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+SUBDIR= bootparamd callbootd
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/bootparamd/Makefile.inc b/usr.sbin/bootparamd/Makefile.inc
new file mode 100644
index 0000000..5c01215
--- /dev/null
+++ b/usr.sbin/bootparamd/Makefile.inc
@@ -0,0 +1,6 @@
+# @(#)Makefile.inc 5.1 (Berkeley) 5/11/90
+# $FreeBSD$
+
+BINDIR?= /usr/sbin
+
+WARNS?= 2
diff --git a/usr.sbin/bootparamd/bootparamd/Makefile b/usr.sbin/bootparamd/bootparamd/Makefile
new file mode 100644
index 0000000..0596993
--- /dev/null
+++ b/usr.sbin/bootparamd/bootparamd/Makefile
@@ -0,0 +1,29 @@
+# from: @(#)Makefile 5.8 (Berkeley) 7/28/90
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= bootparamd
+MAN= bootparams.5 bootparamd.8
+SRCS= bootparamd.c main.c ${GENSRCS}
+GENSRCS=bootparam_prot.h bootparam_prot_svc.c bootparam_prot_xdr.c
+
+CFLAGS+= -DTFTP_DIR=\"/tftpboot\" -I.
+.if ${MK_NIS} != "no"
+CFLAGS+= -DYP
+.endif
+
+CLEANFILES= ${GENSRCS}
+
+RPCSRC= ${DESTDIR}/usr/include/rpcsvc/bootparam_prot.x
+
+bootparam_prot_svc.c: ${RPCSRC}
+ RPCGEN_CPP=${CPP:Q} rpcgen -C -m -o ${.TARGET} ${RPCSRC}
+
+bootparam_prot_xdr.c: ${RPCSRC}
+ RPCGEN_CPP=${CPP:Q} rpcgen -C -c -o ${.TARGET} ${RPCSRC}
+
+bootparam_prot.h: ${RPCSRC}
+ RPCGEN_CPP=${CPP:Q} rpcgen -C -h -o ${.TARGET} ${RPCSRC}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bootparamd/bootparamd/Makefile.depend b/usr.sbin/bootparamd/bootparamd/Makefile.depend
new file mode 100644
index 0000000..a1d24b5
--- /dev/null
+++ b/usr.sbin/bootparamd/bootparamd/Makefile.depend
@@ -0,0 +1,33 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+bootparam_prot_svc.o: bootparam_prot.h
+bootparam_prot_svc.o: bootparam_prot_svc.c
+bootparam_prot_svc.po: bootparam_prot.h
+bootparam_prot_svc.po: bootparam_prot_svc.c
+bootparam_prot_xdr.o: bootparam_prot.h
+bootparam_prot_xdr.o: bootparam_prot_xdr.c
+bootparam_prot_xdr.po: bootparam_prot.h
+bootparam_prot_xdr.po: bootparam_prot_xdr.c
+bootparamd.o: bootparam_prot.h
+bootparamd.po: bootparam_prot.h
+main.o: bootparam_prot.h
+main.po: bootparam_prot.h
+.endif
diff --git a/usr.sbin/bootparamd/bootparamd/README b/usr.sbin/bootparamd/bootparamd/README
new file mode 100644
index 0000000..c49b990
--- /dev/null
+++ b/usr.sbin/bootparamd/bootparamd/README
@@ -0,0 +1,61 @@
+$FreeBSD$
+
+This directory contains a version of the rpc.bootparamd, which have been
+written using the Sun's RPC protocol for bootparamd. To use it you must
+have a copy of the bootparam_prot.x file which on Sun systems you find in
+
+ /usr/include/rpcsvc/bootparam_prot.x
+
+(( This file was retrieved from the Sun-RPC source package ))
+
+
+This code is not copyright, and is placed in the public domain. Feel free to
+use and modify. Please send modifications and/or suggestions + bug fixes to
+
+ Klas Heggemann <klas@nada.kth.se>
+
+
+RPC.BOOTPARAMD
+
+
+The rpc.bootparamd program does NOT use the yellow pages for the bootparams
+database. This data should reside in /etc/bootparams on the local host,
+or another file given when the server is started.
+
+The default router is set to the address of the machine running the server.
+This may not be a good thing to do, so it can be modified using the -r
+option when startning the daemon.
+
+This program was written with the need to keep short hostnames in the
+/etc/bootparams file and long (canonical) names in the hosts database.
+It probably also will work in conjunction with a nameserver, since matching
+is done by comparing the canonical name of the booting machine with the
+canonical name of the hosts found in the bootparams database.
+
+It is kept simple, e g there is no caching of data, but the bootparameter file
+is read at each request.
+
+
+CALLBOOTD
+
+The debugging tool callbootd is used to check the response you get
+to a specific (booting) request. It can be used as
+ callbootd server inet-address
+or
+ callbootd server hostname file
+where "server" is a machine running the rpc.bootparamd program, "inet-address"
+is the internet address of a booting machine, "hostname" is the name of a
+booting machine and "file" the requested file, typically "root", "swap" or
+"dump".
+
+You may also use "all" instead of a specific server, in which case a RPC
+broadcast is performed. The broadcast is performed 4 times and then the
+program times out, after printing all responses.
+
+
+TODO
+
+Cache the date, instead of rereading it.
+Maybe match by comparing the inet address instead. (But beware that caching
+will prevent the server from detecting that a machine has changed name
+or address.)
diff --git a/usr.sbin/bootparamd/bootparamd/bootparamd.8 b/usr.sbin/bootparamd/bootparamd/bootparamd.8
new file mode 100644
index 0000000..183302a
--- /dev/null
+++ b/usr.sbin/bootparamd/bootparamd/bootparamd.8
@@ -0,0 +1,76 @@
+.\" @(#)bootparamd.8
+.\" $FreeBSD$
+.Dd December 14, 2000
+.Dt BOOTPARAMD 8
+.Os
+.Sh NAME
+.Nm bootparamd
+.Nd boot parameter server
+.Sh SYNOPSIS
+.Nm
+.Op Fl ds
+.Op Fl r Ar router
+.Op Fl f Ar file
+.Sh DESCRIPTION
+The
+.Nm
+utility is a server process that provides information to
+.Xr diskless 8
+clients necessary for booting.
+It consults the
+.Pa /etc/bootparams
+file.
+.Pp
+This version will allow the use of aliases on the hostname in the
+.Pa /etc/bootparams
+file.
+The returned hostname to the
+.Em whoami
+request done by the booting client
+will be the name that appears in
+.Pa /etc/bootparams
+and not the canonical name.
+In this way you can keep the answer short enough
+so that machines that cannot handle long hostnames will not fail during boot.
+.Sh OPTIONS
+.Bl -tag -width Fl
+.It Fl d
+Display the debugging information.
+.It Fl s
+Log the debugging information with
+.Xr syslog 3 .
+.It Fl r Ar router
+The default router (a machine or an IP-address).
+This defaults to the machine running the server.
+.It Fl f Ar file
+The file to use as boot parameter file instead of
+.Pa /etc/bootparams .
+.El
+.Sh FILES
+.Bl -tag -width /etc/bootparams -compact
+.It Pa /etc/bootparams
+default boot parameter file
+.El
+.Sh EXAMPLES
+When netbooting diskless SunOS/Xkernel SPARCstations the booted SunOS kernel
+also broadcasts to the all-0 address.
+The SunOS kernel hangs until it receives a reply.
+To accommodate this behaviour add an alias address
+that responds to an all-0 broadcast.
+So, add something like
+.Ql "ifconfig xl0 192.168.200.254 netmask 255.255.255.255 broadcast 192.168.200.0 alias"
+on the relevant network interface on your
+.Nm
+server.
+The alias address must of course be free for use.
+.Sh SEE ALSO
+.Xr syslog 3 ,
+.Xr bootparams 5 ,
+.Xr diskless 8
+.Sh AUTHORS
+Written by
+.An Klas Heggemann Aq Mt klas@nada.kth.se .
+.Sh BUGS
+You may find the
+.Xr syslog 3
+loggings to be verbose.
diff --git a/usr.sbin/bootparamd/bootparamd/bootparamd.c b/usr.sbin/bootparamd/bootparamd/bootparamd.c
new file mode 100644
index 0000000..0921305
--- /dev/null
+++ b/usr.sbin/bootparamd/bootparamd/bootparamd.c
@@ -0,0 +1,358 @@
+/*
+
+This code is not copyright, and is placed in the public domain. Feel free to
+use and modify. Please send modifications and/or suggestions + bug fixes to
+
+ Klas Heggemann <klas@nada.kth.se>
+
+*/
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#ifdef YP
+#include <rpc/rpc.h>
+#include <rpcsvc/yp_prot.h>
+#include <rpcsvc/ypclnt.h>
+#endif
+#include "bootparam_prot.h"
+#include <ctype.h>
+#include <err.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+extern int debug, dolog;
+extern in_addr_t route_addr;
+extern char *bootpfile;
+
+#define MAXLEN 800
+
+struct hostent *he;
+static char buffer[MAXLEN];
+static char hostname[MAX_MACHINE_NAME];
+static char askname[MAX_MACHINE_NAME];
+static char path[MAX_PATH_LEN];
+static char domain_name[MAX_MACHINE_NAME];
+
+int getthefile(char *, char *, char *, int);
+int checkhost(char *, char *, int);
+
+bp_whoami_res *
+bootparamproc_whoami_1_svc(whoami, req)
+bp_whoami_arg *whoami;
+struct svc_req *req;
+{
+ in_addr_t haddr;
+ static bp_whoami_res res;
+ if (debug)
+ fprintf(stderr,"whoami got question for %d.%d.%d.%d\n",
+ 255 & whoami->client_address.bp_address_u.ip_addr.net,
+ 255 & whoami->client_address.bp_address_u.ip_addr.host,
+ 255 & whoami->client_address.bp_address_u.ip_addr.lh,
+ 255 & whoami->client_address.bp_address_u.ip_addr.impno);
+ if (dolog)
+ syslog(LOG_NOTICE, "whoami got question for %d.%d.%d.%d\n",
+ 255 & whoami->client_address.bp_address_u.ip_addr.net,
+ 255 & whoami->client_address.bp_address_u.ip_addr.host,
+ 255 & whoami->client_address.bp_address_u.ip_addr.lh,
+ 255 & whoami->client_address.bp_address_u.ip_addr.impno);
+
+ bcopy((char *)&whoami->client_address.bp_address_u.ip_addr, (char *)&haddr,
+ sizeof(haddr));
+ he = gethostbyaddr((char *)&haddr,sizeof(haddr),AF_INET);
+ if ( ! he ) goto failed;
+
+ if (debug) warnx("this is host %s", he->h_name);
+ if (dolog) syslog(LOG_NOTICE,"This is host %s\n", he->h_name);
+
+ strncpy(askname, he->h_name, sizeof(askname));
+ askname[sizeof(askname)-1] = 0;
+
+ if (checkhost(askname, hostname, sizeof hostname) ) {
+ res.client_name = hostname;
+ getdomainname(domain_name, MAX_MACHINE_NAME);
+ res.domain_name = domain_name;
+
+ if ( res.router_address.address_type != IP_ADDR_TYPE ) {
+ res.router_address.address_type = IP_ADDR_TYPE;
+ bcopy( &route_addr, &res.router_address.bp_address_u.ip_addr, sizeof(in_addr_t));
+ }
+ if (debug) fprintf(stderr,
+ "Returning %s %s %d.%d.%d.%d\n",
+ res.client_name,
+ res.domain_name,
+ 255 & res.router_address.bp_address_u.ip_addr.net,
+ 255 & res.router_address.bp_address_u.ip_addr.host,
+ 255 & res.router_address.bp_address_u.ip_addr.lh,
+ 255 & res.router_address.bp_address_u.ip_addr.impno);
+ if (dolog) syslog(LOG_NOTICE,
+ "Returning %s %s %d.%d.%d.%d\n",
+ res.client_name,
+ res.domain_name,
+ 255 & res.router_address.bp_address_u.ip_addr.net,
+ 255 & res.router_address.bp_address_u.ip_addr.host,
+ 255 & res.router_address.bp_address_u.ip_addr.lh,
+ 255 & res.router_address.bp_address_u.ip_addr.impno);
+
+ return(&res);
+ }
+ failed:
+ if (debug) warnx("whoami failed");
+ if (dolog) syslog(LOG_NOTICE,"whoami failed\n");
+ return(NULL);
+}
+
+
+bp_getfile_res *
+ bootparamproc_getfile_1_svc(getfile, req)
+bp_getfile_arg *getfile;
+struct svc_req *req;
+{
+ char *where;
+ static bp_getfile_res res;
+
+ if (debug)
+ warnx("getfile got question for \"%s\" and file \"%s\"",
+ getfile->client_name, getfile->file_id);
+
+ if (dolog)
+ syslog(LOG_NOTICE,"getfile got question for \"%s\" and file \"%s\"\n",
+ getfile->client_name, getfile->file_id);
+
+ he = NULL;
+ he = gethostbyname(getfile->client_name);
+ if (! he ) goto failed;
+
+ strncpy(askname, he->h_name, sizeof(askname));
+ askname[sizeof(askname)-1] = 0;
+
+ if (getthefile(askname, getfile->file_id,buffer,sizeof(buffer))) {
+ if ( (where = strchr(buffer,':')) ) {
+ /* buffer is re-written to contain the name of the info of file */
+ strncpy(hostname, buffer, where - buffer);
+ hostname[where - buffer] = '\0';
+ where++;
+ strcpy(path, where);
+ he = gethostbyname(hostname);
+ if ( !he ) goto failed;
+ bcopy( he->h_addr, &res.server_address.bp_address_u.ip_addr, 4);
+ res.server_name = hostname;
+ res.server_path = path;
+ res.server_address.address_type = IP_ADDR_TYPE;
+ }
+ else { /* special for dump, answer with null strings */
+ if (!strcmp(getfile->file_id, "dump")) {
+ res.server_name = "";
+ res.server_path = "";
+ res.server_address.address_type = IP_ADDR_TYPE;
+ bzero(&res.server_address.bp_address_u.ip_addr,4);
+ } else goto failed;
+ }
+ if (debug)
+ fprintf(stderr, "returning server:%s path:%s address: %d.%d.%d.%d\n",
+ res.server_name, res.server_path,
+ 255 & res.server_address.bp_address_u.ip_addr.net,
+ 255 & res.server_address.bp_address_u.ip_addr.host,
+ 255 & res.server_address.bp_address_u.ip_addr.lh,
+ 255 & res.server_address.bp_address_u.ip_addr.impno);
+ if (dolog)
+ syslog(LOG_NOTICE, "returning server:%s path:%s address: %d.%d.%d.%d\n",
+ res.server_name, res.server_path,
+ 255 & res.server_address.bp_address_u.ip_addr.net,
+ 255 & res.server_address.bp_address_u.ip_addr.host,
+ 255 & res.server_address.bp_address_u.ip_addr.lh,
+ 255 & res.server_address.bp_address_u.ip_addr.impno);
+ return(&res);
+ }
+ failed:
+ if (debug) warnx("getfile failed for %s", getfile->client_name);
+ if (dolog) syslog(LOG_NOTICE,
+ "getfile failed for %s\n", getfile->client_name);
+ return(NULL);
+}
+
+/* getthefile return 1 and fills the buffer with the information
+ of the file, e g "host:/export/root/client" if it can be found.
+ If the host is in the database, but the file is not, the buffer
+ will be empty. (This makes it possible to give the special
+ empty answer for the file "dump") */
+
+int
+getthefile(askname,fileid,buffer,blen)
+char *askname;
+char *fileid, *buffer;
+int blen;
+{
+ FILE *bpf;
+ char *where;
+#ifdef YP
+ static char *result;
+ int resultlen;
+ static char *yp_domain;
+#endif
+
+ int ch, pch, fid_len, res = 0;
+ int match = 0;
+ char info[MAX_FILEID + MAX_PATH_LEN+MAX_MACHINE_NAME + 3];
+
+ bpf = fopen(bootpfile, "r");
+ if ( ! bpf )
+ errx(1, "no %s", bootpfile);
+
+ /* XXX see comment below */
+ while ( fscanf(bpf, "%255s", hostname) > 0 && !match ) {
+ if ( *hostname != '#' ) { /* comment */
+ if ( ! strcmp(hostname, askname) ) {
+ match = 1;
+ } else {
+ he = gethostbyname(hostname);
+ if (he && !strcmp(he->h_name, askname)) match = 1;
+ }
+ }
+ if (*hostname == '+' ) { /* NIS */
+#ifdef YP
+ if (yp_get_default_domain(&yp_domain)) {
+ if (debug) warn("NIS");
+ return(0);
+ }
+ if (yp_match(yp_domain, "bootparams", askname, strlen(askname),
+ &result, &resultlen))
+ return (0);
+ if (strstr(result, fileid) == NULL) {
+ buffer[0] = '\0';
+ } else {
+ snprintf(buffer, blen,
+ "%s",strchr(strstr(result,fileid), '=') + 1);
+ if (strchr(buffer, ' ') != NULL)
+ *(char *)(strchr(buffer, ' ')) = '\0';
+ }
+ if (fclose(bpf))
+ warnx("could not close %s", bootpfile);
+ return(1);
+#else
+ return(0); /* ENOTSUP */
+#endif
+ }
+ /* skip to next entry */
+ if ( match ) break;
+ pch = ch = getc(bpf);
+ while ( ! ( ch == '\n' && pch != '\\') && ch != EOF) {
+ pch = ch; ch = getc(bpf);
+ }
+ }
+
+ /* if match is true we read the rest of the line to get the
+ info of the file */
+
+ if (match) {
+ fid_len = strlen(fileid);
+ while ( ! res && (fscanf(bpf,"%s", info)) > 0) { /* read a string */
+ ch = getc(bpf); /* and a character */
+ if ( *info != '#' ) { /* Comment ? */
+ if (! strncmp(info, fileid, fid_len) && *(info + fid_len) == '=') {
+ where = info + fid_len + 1;
+ if ( isprint( *where )) {
+ strcpy(buffer, where); /* found file */
+ res = 1; break;
+ }
+ } else {
+ while (isspace(ch) && ch != '\n') ch = getc(bpf);
+ /* read to end of line */
+ if ( ch == '\n' ) { /* didn't find it */
+ res = -1; break; /* but host is there */
+ }
+ if ( ch == '\\' ) { /* more info */
+ ch = getc(bpf); /* maybe on next line */
+ if (ch == '\n') continue; /* read it in next loop */
+ ungetc(ch, bpf); ungetc('\\',bpf); /* push the character(s) back */
+ } else ungetc(ch, bpf); /* but who know what a `\` is */
+ } /* needed for. */
+ } else break; /* a commented rest-of-line */
+ }
+ }
+ if (fclose(bpf)) { warnx("could not close %s", bootpfile); }
+ if ( res == -1) buffer[0] = '\0'; /* host found, file not */
+ return(match);
+}
+
+/* checkhost puts the hostname found in the database file in
+ the hostname-variable and returns 1, if askname is a valid
+ name for a host in the database */
+
+int
+checkhost(askname, hostname, len)
+char *askname;
+char *hostname;
+int len;
+{
+ int ch, pch;
+ FILE *bpf;
+ int res = 0;
+#ifdef YP
+ static char *result;
+ int resultlen;
+ static char *yp_domain;
+#endif
+
+/* struct hostent *cmp_he;*/
+
+ bpf = fopen(bootpfile, "r");
+ if ( ! bpf )
+ errx(1, "no %s", bootpfile);
+
+ /* XXX there is no way in ISO C to specify the maximal length for a
+ conversion in a variable way */
+ while ( fscanf(bpf, "%254s", hostname) > 0 ) {
+ if ( *hostname != '#' ) { /* comment */
+ if ( ! strcmp(hostname, askname) ) {
+ /* return true for match of hostname */
+ res = 1;
+ break;
+ } else {
+ /* check the alias list */
+ he = NULL;
+ he = gethostbyname(hostname);
+ if (he && !strcmp(askname, he->h_name)) {
+ res = 1;
+ break;
+ }
+ }
+ }
+ if (*hostname == '+' ) { /* NIS */
+#ifdef YP
+ if (yp_get_default_domain(&yp_domain)) {
+ if (debug) warn("NIS");
+ return(0);
+ }
+ if (!yp_match(yp_domain, "bootparams", askname, strlen(askname),
+ &result, &resultlen)) {
+ /* return true for match of hostname */
+ he = NULL;
+ he = gethostbyname(askname);
+ if (he && !strcmp(askname, he->h_name)) {
+ res = 1;
+ snprintf(hostname, len, "%s", he->h_name);
+ }
+ }
+ if (fclose(bpf))
+ warnx("could not close %s", bootpfile);
+ return(res);
+#else
+ return(0); /* ENOTSUP */
+#endif
+ }
+ /* skip to next entry */
+ pch = ch = getc(bpf);
+ while ( ! ( ch == '\n' && pch != '\\') && ch != EOF) {
+ pch = ch; ch = getc(bpf);
+ }
+ }
+ if (fclose(bpf)) { warnx("could not close %s", bootpfile); }
+ return(res);
+}
diff --git a/usr.sbin/bootparamd/bootparamd/bootparams.5 b/usr.sbin/bootparamd/bootparamd/bootparams.5
new file mode 100644
index 0000000..c7c6785
--- /dev/null
+++ b/usr.sbin/bootparamd/bootparamd/bootparams.5
@@ -0,0 +1,86 @@
+.\"
+.\" Copyright (c) 1994 Gordon W. Ross
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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.
+.\"
+.\" from: Id: bootparams.5,v 1.2 1994/10/03 19:26:13 gwr Exp
+.\" $FreeBSD$
+.\"
+.Dd October 2, 1994
+.Dt BOOTPARAMS 5
+.Os
+.Sh NAME
+.Nm bootparams
+.Nd boot parameter database
+.Sh SYNOPSIS
+.Nm /etc/bootparams
+.Sh DESCRIPTION
+The
+.Nm
+file specifies the boot parameters that
+.Xr diskless 8
+clients may request when booting over the network.
+Each client supported by this server must have an entry in the
+.Nm
+file containing the pathnames for its
+.Dq root
+and (optionally)
+.Dq swap
+areas.
+.Pp
+Each line in the file
+(other than comment lines that begin with a
+.Ql # )
+specifies the client name followed by the pathnames that
+the client may request by their logical names.
+The components of the line are delimited with blank or tab,
+and may be continued onto multiple lines with a backslash.
+.Pp
+For example:
+.Bd -literal -offset indent
+dummy root=host:/export/dummy/root \\
+ swap=host:/export/dummy/swap \\
+ dump=host:/export/dummy/swap
+.Ed
+.Pp
+When the client named
+.Dq dummy
+requests the pathname for its logical
+.Dq root
+it will be given the pathname
+.Dq Pa host:/export/dummy/root
+as the response to its
+.Tn RPC
+request.
+The
+.Dq host:
+component must be supplied.
+.Sh FILES
+.Bl -tag -width /etc/bootparams -compact
+.It Pa /etc/bootparams
+default configuration file
+.El
+.Sh SEE ALSO
+.Xr bootparamd 8 ,
+.Xr diskless 8
diff --git a/usr.sbin/bootparamd/bootparamd/main.c b/usr.sbin/bootparamd/bootparamd/main.c
new file mode 100644
index 0000000..04f5cef
--- /dev/null
+++ b/usr.sbin/bootparamd/bootparamd/main.c
@@ -0,0 +1,115 @@
+/*
+
+This code is not copyright, and is placed in the public domain. Feel free to
+use and modify. Please send modifications and/or suggestions + bug fixes to
+
+ Klas Heggemann <klas@nada.kth.se>
+
+*/
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <ctype.h>
+#include <err.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <rpc/rpc.h>
+#include <rpc/pmap_clnt.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include "bootparam_prot.h"
+
+int debug = 0;
+int dolog = 0;
+in_addr_t route_addr = -1;
+struct sockaddr_in my_addr;
+char *bootpfile = "/etc/bootparams";
+
+static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ SVCXPRT *transp;
+ struct hostent *he;
+ struct stat buf;
+ int c;
+
+ while ((c = getopt(argc, argv,"dsr:f:")) != -1)
+ switch (c) {
+ case 'd':
+ debug = 1;
+ break;
+ case 'r':
+ if (isdigit((unsigned char)*optarg)) {
+ route_addr = inet_addr(optarg);
+ break;
+ } else {
+ he = gethostbyname(optarg);
+ if (he) {
+ bcopy(he->h_addr, (char *)&route_addr, sizeof(route_addr));
+ break;
+ } else {
+ errx(1, "no such host %s", optarg);
+ }
+ }
+ case 'f':
+ bootpfile = optarg;
+ break;
+ case 's':
+ dolog = 1;
+#ifndef LOG_DAEMON
+ openlog("bootparamd", 0 , 0);
+#else
+ openlog("bootparamd", 0 , LOG_DAEMON);
+ setlogmask(LOG_UPTO(LOG_NOTICE));
+#endif
+ break;
+ default:
+ usage();
+ }
+
+ if ( stat(bootpfile, &buf ) )
+ err(1, "%s", bootpfile);
+
+ if (route_addr == INADDR_NONE) {
+ get_myaddress(&my_addr);
+ bcopy(&my_addr.sin_addr.s_addr, &route_addr, sizeof (route_addr));
+ }
+
+ if (!debug) {
+ if (daemon(0,0))
+ err(1, "fork");
+ }
+
+
+ (void)pmap_unset(BOOTPARAMPROG, BOOTPARAMVERS);
+
+ transp = svcudp_create(RPC_ANYSOCK);
+ if (transp == NULL)
+ errx(1, "cannot create udp service");
+ if (!svc_register(transp, BOOTPARAMPROG, BOOTPARAMVERS, bootparamprog_1, IPPROTO_UDP))
+ errx(1, "unable to register (BOOTPARAMPROG, BOOTPARAMVERS, udp)");
+
+ svc_run();
+ errx(1, "svc_run returned");
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: bootparamd [-d] [-s] [-r router] [-f bootparmsfile]\n");
+ exit(1);
+}
diff --git a/usr.sbin/bootparamd/callbootd/Makefile b/usr.sbin/bootparamd/callbootd/Makefile
new file mode 100644
index 0000000..28b1e26
--- /dev/null
+++ b/usr.sbin/bootparamd/callbootd/Makefile
@@ -0,0 +1,24 @@
+# from: @(#)Makefile 5.8 (Berkeley) 7/28/90
+# $FreeBSD$
+
+PROG= callbootd
+MAN=
+SRCS= callbootd.c ${GENSRCS}
+GENSRCS=bootparam_prot.h bootparam_prot_clnt.c bootparam_prot_xdr.c
+
+CFLAGS+= -I.
+
+CLEANFILES= ${GENSRCS}
+
+RPCSRC= ${DESTDIR}/usr/include/rpcsvc/bootparam_prot.x
+
+bootparam_prot_clnt.c: ${RPCSRC}
+ RPCGEN_CPP=${CPP:Q} rpcgen -C -l -o ${.TARGET} ${RPCSRC}
+
+bootparam_prot_xdr.c: ${RPCSRC}
+ RPCGEN_CPP=${CPP:Q} rpcgen -C -c -o ${.TARGET} ${RPCSRC}
+
+bootparam_prot.h: ${RPCSRC}
+ RPCGEN_CPP=${CPP:Q} rpcgen -C -h -o ${.TARGET} ${RPCSRC}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bootparamd/callbootd/Makefile.depend b/usr.sbin/bootparamd/callbootd/Makefile.depend
new file mode 100644
index 0000000..e34ede1
--- /dev/null
+++ b/usr.sbin/bootparamd/callbootd/Makefile.depend
@@ -0,0 +1,31 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+bootparam_prot_clnt.o: bootparam_prot.h
+bootparam_prot_clnt.o: bootparam_prot_clnt.c
+bootparam_prot_clnt.po: bootparam_prot.h
+bootparam_prot_clnt.po: bootparam_prot_clnt.c
+bootparam_prot_xdr.o: bootparam_prot.h
+bootparam_prot_xdr.o: bootparam_prot_xdr.c
+bootparam_prot_xdr.po: bootparam_prot.h
+bootparam_prot_xdr.po: bootparam_prot_xdr.c
+callbootd.o: bootparam_prot.h
+callbootd.po: bootparam_prot.h
+.endif
diff --git a/usr.sbin/bootparamd/callbootd/callbootd.c b/usr.sbin/bootparamd/callbootd/callbootd.c
new file mode 100644
index 0000000..7c32fee
--- /dev/null
+++ b/usr.sbin/bootparamd/callbootd/callbootd.c
@@ -0,0 +1,204 @@
+/*
+
+This code is not copyright, and is placed in the public domain. Feel free to
+use and modify. Please send modifications and/or suggestions + bug fixes to
+
+ Klas Heggemann <klas@nada.kth.se>
+
+*/
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include "bootparam_prot.h"
+#include <rpc/rpc.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <err.h>
+#include <netdb.h>
+#include <stdlib.h>
+
+
+/* #define bp_address_u bp_address */
+#include <stdio.h>
+#include <string.h>
+
+int broadcast;
+
+char cln[MAX_MACHINE_NAME+1];
+char dmn[MAX_MACHINE_NAME+1];
+char path[MAX_PATH_LEN+1];
+extern char *inet_ntoa();
+static void usage(void);
+int printgetfile(bp_getfile_res *);
+int printwhoami(bp_whoami_res *);
+
+bool_t
+eachres_whoami(resultp, raddr)
+bp_whoami_res *resultp;
+struct sockaddr_in *raddr;
+{
+ struct hostent *he;
+
+ he = gethostbyaddr((char *)&raddr->sin_addr.s_addr,4,AF_INET);
+ printf("%s answered:\n", he ? he->h_name : inet_ntoa(raddr->sin_addr));
+ printwhoami(resultp);
+ printf("\n");
+ return(0);
+}
+
+bool_t
+eachres_getfile(resultp, raddr)
+bp_getfile_res *resultp;
+struct sockaddr_in *raddr;
+{
+ struct hostent *he;
+
+ he = gethostbyaddr((char *)&raddr->sin_addr.s_addr,4,AF_INET);
+ printf("%s answered:\n", he ? he->h_name : inet_ntoa(raddr->sin_addr));
+ printgetfile(resultp);
+ printf("\n");
+ return(0);
+}
+
+
+int
+main(argc, argv)
+int argc;
+char **argv;
+{
+ char *server;
+
+ bp_whoami_arg whoami_arg;
+ bp_whoami_res *whoami_res, stat_whoami_res;
+ bp_getfile_arg getfile_arg;
+ bp_getfile_res *getfile_res, stat_getfile_res;
+
+
+ long the_inet_addr;
+ CLIENT *clnt;
+
+ stat_whoami_res.client_name = cln;
+ stat_whoami_res.domain_name = dmn;
+
+ stat_getfile_res.server_name = cln;
+ stat_getfile_res.server_path = path;
+
+ if (argc < 3)
+ usage();
+
+ server = argv[1];
+ if ( ! strcmp(server , "all") ) broadcast = 1;
+
+ if ( ! broadcast ) {
+ clnt = clnt_create(server,BOOTPARAMPROG, BOOTPARAMVERS, "udp");
+ if ( clnt == NULL )
+ errx(1, "could not contact bootparam server on host %s", server);
+ }
+
+ switch (argc) {
+ case 3:
+ whoami_arg.client_address.address_type = IP_ADDR_TYPE;
+ the_inet_addr = inet_addr(argv[2]);
+ if ( the_inet_addr == INADDR_NONE)
+ errx(2, "bogus addr %s", argv[2]);
+ bcopy(&the_inet_addr,&whoami_arg.client_address.bp_address_u.ip_addr,4);
+
+ if (! broadcast ) {
+ whoami_res = bootparamproc_whoami_1(&whoami_arg, clnt);
+ printf("Whoami returning:\n");
+ if (printwhoami(whoami_res)) {
+ errx(1, "bad answer returned from server %s", server);
+ } else
+ exit(0);
+ } else {
+ (void)clnt_broadcast(BOOTPARAMPROG, BOOTPARAMVERS,
+ BOOTPARAMPROC_WHOAMI,
+ (xdrproc_t)xdr_bp_whoami_arg,
+ (char *)&whoami_arg,
+ (xdrproc_t)xdr_bp_whoami_res,
+ (char *)&stat_whoami_res,
+ (resultproc_t)eachres_whoami);
+ exit(0);
+ }
+
+ case 4:
+
+ getfile_arg.client_name = argv[2];
+ getfile_arg.file_id = argv[3];
+
+ if (! broadcast ) {
+ getfile_res = bootparamproc_getfile_1(&getfile_arg,clnt);
+ printf("getfile returning:\n");
+ if (printgetfile(getfile_res)) {
+ errx(1, "bad answer returned from server %s", server);
+ } else
+ exit(0);
+ } else {
+ (void)clnt_broadcast(BOOTPARAMPROG, BOOTPARAMVERS,
+ BOOTPARAMPROC_GETFILE,
+ (xdrproc_t)xdr_bp_getfile_arg,
+ (char *)&getfile_arg,
+ (xdrproc_t)xdr_bp_getfile_res,
+ (char *)&stat_getfile_res,
+ (resultproc_t)eachres_getfile);
+ exit(0);
+ }
+
+ default:
+
+ usage();
+ }
+
+}
+
+
+static void
+usage()
+{
+ fprintf(stderr,
+ "usage: callbootd server procnum (IP-addr | host fileid)\n");
+ exit(1);
+}
+
+int
+printwhoami(res)
+bp_whoami_res *res;
+{
+ if ( res) {
+ printf("client_name:\t%s\ndomain_name:\t%s\n",
+ res->client_name, res->domain_name);
+ printf("router:\t%d.%d.%d.%d\n",
+ 255 & res->router_address.bp_address_u.ip_addr.net,
+ 255 & res->router_address.bp_address_u.ip_addr.host,
+ 255 & res->router_address.bp_address_u.ip_addr.lh,
+ 255 & res->router_address.bp_address_u.ip_addr.impno);
+ return(0);
+ } else {
+ warnx("null answer!!!");
+ return(1);
+ }
+ }
+
+
+
+
+int
+printgetfile(res)
+bp_getfile_res *res;
+{
+ if (res) {
+ printf("server_name:\t%s\nserver_address:\t%s\npath:\t%s\n",
+ res->server_name,
+ inet_ntoa(*(struct in_addr *)&res->server_address.bp_address_u.ip_addr),
+ res->server_path);
+ return(0);
+ } else {
+ warnx("null answer!!!");
+ return(1);
+ }
+ }
diff --git a/usr.sbin/bsdconfig/Makefile b/usr.sbin/bsdconfig/Makefile
new file mode 100644
index 0000000..1f1ec9e
--- /dev/null
+++ b/usr.sbin/bsdconfig/Makefile
@@ -0,0 +1,28 @@
+# $FreeBSD$
+
+SUBDIR= console \
+ diskmgmt \
+ docsinstall \
+ dot \
+ examples \
+ include \
+ includes \
+ mouse \
+ networking \
+ packages \
+ password \
+ security \
+ share \
+ startup \
+ timezone \
+ ttys \
+ usermgmt
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig
+FILES= USAGE
+
+SCRIPTS= bsdconfig
+
+MAN= bsdconfig.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/Makefile.depend b/usr.sbin/bsdconfig/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/USAGE b/usr.sbin/bsdconfig/USAGE
new file mode 100644
index 0000000..6b70bd6
--- /dev/null
+++ b/usr.sbin/bsdconfig/USAGE
@@ -0,0 +1,47 @@
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+Usage:
+ @PROGRAM_NAME@ [-h]
+ @PROGRAM_NAME@ command [-h]
+ @PROGRAM_NAME@ [OPTIONS] [command [OPTIONS]]
+
+OPTIONS:
+ -d Provide lots of debugging info on standard-out when running.
+ -D file Send debugging info to file. If file begins with a plus-sign
+ debug info is sent to both standard-out and file (minus the
+ leading plus).
+ -f file Load file as script and then exit. If multiple occurrences,
+ program will only exit after last occurrence. If file is a
+ single dash (`-'), @PROGRAM_NAME@ reads from standard input.
+ -h Print this usage statement and exit.
+ -S Secure X11 mode (implies `-X'). As root, always prompt-for
+ and validate sudo(8) username/password before starting.
+ -X Use Xdialog(1) in place of dialog(1).
+
+COMMANDS:
+@COMMAND_LIST@
diff --git a/usr.sbin/bsdconfig/bsdconfig b/usr.sbin/bsdconfig/bsdconfig
new file mode 100755
index 0000000..993865c
--- /dev/null
+++ b/usr.sbin/bsdconfig/bsdconfig
@@ -0,0 +1,428 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012-2014 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+# When common.subr is included, it automatically scans "$@" for `-d' and/or
+# `-D file' arguments to conditionally enable debugging. Similarly, when
+# dialog.subr is included, it automatically scans "$@" for `-X' and/or `-S'.
+# To prevent this scanning from becoming confused by extra options, define
+# any/all extra arguments to use in the optstring to getopts when scanning
+# for dedicated options such as those described.
+#
+# NOTE: This needs to be declared before including `common.subr'.
+# NOTE: You really only need to list flags that require an argument as unknown
+# flags are silently accepted unless they take an argument (in which case
+# the following argument will terminate option processing unless it looks
+# like a flag).
+#
+GETOPTS_EXTRA="f:"
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/strings.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+
+BSDCONFIG_HELPFILE=$BSDCFG_LIBE/include/bsdconfig.hlp
+USAGE_HELPFILE=$BSDCFG_LIBE/include/usage.hlp
+
+############################################################ CONFIGURATION
+
+#
+# Alternate `local' libexec directory for add-on modules (e.g., from ports)
+#
+BSDCFG_LOCAL_LIBE="/usr/local/libexec/bsdconfig"
+
+############################################################ FUNCTIONS
+
+# usage
+#
+# display usage and exit
+#
+usage()
+{
+ local index="INDEX"
+ local cmd_list # Calculated below
+
+ cd $BSDCFG_LIBE
+ # No need to preserve CWD (headed toward exit)
+
+ # Test for language-specific indices
+ f_quietly ls */"$index.${LANG:-$LC_ALL}" &&
+ index="$index.${LANG:-$LC_ALL}"
+
+ cmd_list=$(
+ awk '/^menu_selection="/ {
+ sub(/\|.*/, "")
+ sub(/^menu_selection="/, "")
+ print
+ }' */$index | sort
+ )
+
+ local alt_cmd_list # Calculated below (if $BSDCFG_LOCAL_LIBE exists)
+ if f_quietly cd $BSDCFG_LOCAL_LIBE; then
+ # No need to preserve CWD (headed toward exit)
+
+ # Test for language-specific indices
+ f_quietly ls */"$index.${LANG:-$LC_ALL}" &&
+ index="$index.${LANG:-$LC_ALL}"
+
+ alt_cmd_list=$(
+ awk '/^menu_selection="/ {
+ sub(/\|.*/, "")
+ sub(/^menu_selection="/, "")
+ print
+ }' */$index 2> /dev/null | sort
+ )
+
+ # Conflate lists, removing duplicates
+ cmd_list=$( printf "%s\n%s\n" \
+ "$cmd_list" "$alt_cmd_list" | sort -u )
+ fi
+
+ #
+ # Determine the longest command-length (in characters)
+ #
+ local longest_cmd
+ longest_cmd=$( echo "$cmd_list" | f_longest_line_length )
+ f_dprintf "longest_cmd=[%s]" "$longest_cmd"
+
+ #
+ # Determine the maximum width of terminal/console
+ #
+ local max_size="$( stty size 2> /dev/null )"
+ : ${max_size:="24 80"}
+ local max_width="${max_size#*[$IFS]}"
+ f_dprintf "max_width=[%s]" "$max_width"
+
+ #
+ # Using the longest command-length as the width of a single column,
+ # determine if we can use more than one column to display commands.
+ #
+ local x=$longest_cmd ncols=1
+ x=$(( $x + 8 )) # Accommodate leading tab character
+ x=$(( $x + 3 + $longest_cmd )) # Preload end of next column
+ while [ $x -lt $max_width ]; do
+ ncols=$(( $ncols + 1 ))
+ x=$(( $x + 3 + $longest_cmd ))
+ done
+ f_dprintf "ncols=[%u] x=[%u]" $ncols $x
+
+ #
+ # Re-format the command-list into multiple columns
+ #
+ cmd_list=$( eval "$( echo "$cmd_list" |
+ awk -v ncols=$ncols -v size=$longest_cmd '
+ BEGIN {
+ n = 0
+ row_item[1] = ""
+ }
+ function print_row()
+ {
+ fmt = "printf \"\\t%-" size "s"
+ for (i = 1; i < cur_col; i++)
+ fmt = fmt " %-" size "s"
+ fmt = fmt "\\n\""
+ printf "%s", fmt
+ for (i = 1; i <= cur_col; i++)
+ printf " \"%s\"", row_item[i]
+ print ""
+ }
+ {
+ n++
+ cur_col = (( n - 1 ) % ncols ) + 1
+ printf "f_dprintf \"row_item[%u]=[%%s]\" \"%s\"\n",
+ cur_col, $0
+ row_item[cur_col] = $0
+ if ( cur_col == ncols ) print_row()
+ }
+ END {
+ if ( cur_col < ncols ) print_row()
+ }' )"
+ )
+
+ f_usage $BSDCFG_LIBE/USAGE \
+ "PROGRAM_NAME" "$pgm" \
+ "COMMAND_LIST" "$cmd_list"
+
+ # Never reached
+}
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local title="$DIALOG_TITLE"
+ local btitle="$DIALOG_BACKTITLE"
+ local prompt="$msg_menu_text"
+ local menu_list="
+ 'X' '$msg_exit' '$msg_exit_bsdconfig'
+ '1' '$msg_usage' '$msg_quick_start_how_to_use_this_menu_system'
+ " # END-QUOTE
+ local defaultitem= # Calculated below
+ local hline=
+
+ #
+ # Pick up the base modules (directories named `[0-9][0-9][0-9].*')
+ #
+ local menuitem menu_title menu_help menu_selection index=2
+ for menuitem in $( cd $BSDCFG_LIBE && ls -d [0-9][0-9][0-9].* ); do
+ [ -f "$BSDCFG_LIBE/$menuitem/INDEX" ] || continue
+ [ $index -lt ${#DIALOG_MENU_TAGS} ] || break
+
+ menu_program= menu_title= menu_help=
+ f_include_lang $BSDCFG_LIBE/$menuitem/INDEX
+ [ "$menu_program" ] || continue
+
+ case "$menu_program" in
+ /*) : already fully qualified ;;
+ *) menu_program="$menuitem/$menu_program"
+ esac
+
+ tag=$( f_substr "$DIALOG_MENU_TAGS" $index 1 )
+ setvar "menu_program$tag" "$menu_program"
+
+ f_shell_escape "$menu_title" menu_title
+ f_shell_escape "$menu_help" menu_help
+ menu_list="$menu_list '$tag' '$menu_title' '$menu_help'"
+
+ index=$(( $index + 1 ))
+ done
+
+ #
+ # Process the `local' libexec sources.
+ #
+ # Whereas modules in $BSDCFG_LIBE must be named [0-9][0-9][0-9].*
+ # modules in $BSDCFG_LOCAL_LIBE should NOT be named this way (making it
+ # more practical for port-maintainers).
+ #
+ # This also has the fortunate side-effect of making the de-duplication
+ # effort rather simple (because so-called `base' modules must be named
+ # differently than add-on modules).
+ #
+ local separator_added=
+ for menuitem in $( cd "$BSDCFG_LOCAL_LIBE" 2> /dev/null && ls -d * )
+ do
+ # Skip the module if it looks like a `base' module
+ case "$menuitem" in [0-9][0-9][0-9].*) continue;; esac
+
+ [ -f "$BSDCFG_LOCAL_LIBE/$menuitem/INDEX" ] || continue
+ [ $index -lt ${#DIALOG_MENU_TAGS} ] || break
+
+ menu_program= menu_title= menu_help=
+ f_include_lang $BSDCFG_LOCAL_LIBE/$menuitem/INDEX || continue
+ [ "$menu_program" ] || continue
+
+ if [ ! "$separator_added" ]; then
+ menu_list="$menu_list '-' '-' ''"
+ separator_added=1
+ fi
+
+ case "$menu_program" in
+ /*) : already fully qualified ;;
+ *) menu_program="$BSDCFG_LOCAL_LIBE/$menuitem/$menu_program"
+ esac
+
+ tag=$( f_substr "$DIALOG_MENU_TAGS" $index 1 )
+ setvar "menu_program$tag" "$menu_program"
+
+ f_shell_escape "$menu_title" menu_title
+ f_shell_escape "$menu_help" menu_help
+ menu_list="$menu_list '$tag' '$menu_title' '$menu_help'"
+
+ index=$(( $index + 1 ))
+ done
+
+ local height width rows
+ eval f_dialog_menu_with_help_size height width rows \
+ \"\$title\" \
+ \"\$btitle\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ # Obtain default-item from previously stored selection
+ f_dialog_default_fetch defaultitem
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --clear \
+ --title \"\$title\" \
+ --backtitle \"\$btitle\" \
+ --hline \"\$hline\" \
+ --item-help \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_exit_bsdconfig\" \
+ --help-button \
+ --help-label \"\$msg_help\" \
+ ${USE_XDIALOG:+--help \"\"} \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize menu_choice
+ f_dialog_menutag_store "$menu_choice"
+
+ # Only update default-item on success
+ [ $retval -eq $DIALOG_OK ] && f_dialog_default_store "$menu_choice"
+
+ return $retval
+}
+
+############################################################ MAIN
+
+#
+# If $0 is not "bsdconfig", interpret it either as a keyword to a menuitem or
+# as a valid resword (see script.subr for additional details about reswords).
+#
+if [ "$pgm" != "bsdconfig" ]; then
+ if indexfile=$( f_index_file "$pgm" ) &&
+ cmd=$( f_index_menusel_command "$indexfile" "$pgm" )
+ then
+ f_dprintf "pgm=[%s] cmd=[%s] *=[%s]" "$pgm" "$cmd" "$*"
+ exec "$cmd" "$@" || exit 1
+ else
+ f_include $BSDCFG_SHARE/script.subr
+ for resword in $RESWORDS; do
+ [ "$pgm" = "$resword" ] || continue
+ # Found a match
+ f_dprintf "pgm=[%s] A valid resWord!" "$pgm"
+ f_dispatch $resword $resword "$@"
+ exit $?
+ done
+ fi
+fi
+
+#
+# Process command-line arguments
+#
+scripts_loaded=0
+while getopts f:h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ f) [ $scripts_loaded -eq 0 ] && f_include $BSDCFG_SHARE/script.subr
+ f_script_load "$OPTARG"
+ scripts_loaded=$(( $scripts_loaded + 1 )) ;;
+ h|\?) usage ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+# If we've loaded any scripts, do not continue any further
+[ $scripts_loaded -gt 0 ] && exit
+
+#
+# Initialize
+#
+f_dialog_title "$msg_main_menu"
+
+[ "$SECURE" ] && f_mustberoot_init
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# If a non-option argument was passed, process it as a menuitem selection...
+#
+if [ "$1" ]; then
+ #
+ # ...unless it's a long-option for usage.
+ #
+ case "$1" in -help|--help|-\?)
+ usage
+ # Not reached
+ esac
+
+ #
+ # Find the INDEX (possibly i18n) claiming this keyword and get the
+ # command to execute from the menu_selection line.
+ #
+ if ! { indexfile=$( f_index_file "$1" ) &&
+ cmd=$( f_index_menusel_command "$indexfile" "$1" )
+ }; then
+ # no matches, display usage (which shows valid keywords)
+ f_err "%s: %s: $msg_not_found\n" "$pgm" "$1"
+ usage
+ # Not reached
+ fi
+
+ f_dprintf "cmd=[%s] *=[%s]" "$cmd" "$*"
+ shift
+ exec $cmd ${USE_XDIALOG:+-X} "$@" || exit 1
+ # Not reached
+fi
+
+#
+# Launch application main menu
+#
+while :; do
+ dialog_menu_main
+ retval=$?
+ f_dialog_menutag_fetch mtag
+ f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
+
+ if [ $retval -eq $DIALOG_HELP ]; then
+ f_show_help "$BSDCONFIG_HELPFILE"
+ continue
+ elif [ $retval -ne $DIALOG_OK ]; then
+ f_die
+ fi
+
+ case "$mtag" in
+ X) break ;;
+ 1) # Usage
+ f_show_help "$USAGE_HELPFILE"
+ continue
+ esac
+
+ # Anything else is a dynamically loaded menuitem
+
+ f_getvar menu_program$mtag menu_program
+ case "$menu_program" in
+ /*) cmd="$menu_program" ;;
+ *) cmd="$BSDCFG_LIBE/$menu_program"
+ esac
+ f_dprintf "cmd=[%s]" "$cmd"
+ $cmd ${USE_XDIALOG:+-X}
+done
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/bsdconfig.8 b/usr.sbin/bsdconfig/bsdconfig.8
new file mode 100644
index 0000000..849f85b
--- /dev/null
+++ b/usr.sbin/bsdconfig/bsdconfig.8
@@ -0,0 +1,254 @@
+.\" Copyright (c) 2012 Ron McDowell
+.\" Copyright (c) 2012-2013 Devin Teske
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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$
+.\"
+.Dd Jun 5, 2013
+.Dt BSDCONFIG 8
+.Os
+.Sh NAME
+.Nm bsdconfig
+.Nd system configuration utility
+.Sh SYNOPSIS
+.Nm
+.Op Fl h
+.Nm
+.Ar command
+.Op Fl h
+.Nm
+.Op OPTIONS
+.Op command Op OPTIONS
+.Sh DESCRIPTION
+.Nm
+is used to set up many system configuration settings, both for new systems, as
+well as changing configuration settings of existing systems.
+.Pp
+.Nm
+optionally takes a command as an argument.
+If invoked with no arguments, it will bring up an interactive menu listing the
+available modules.
+.Pp
+The following options are available:
+.Bl -tag -width indent+
+.It Fl d
+Provide lots of debugging info on standard-out when running.
+.It Fl D Ar file
+Send debugging info to file.
+If file begins with a plus-sign debug info is sent to both standard-out and
+file (minus the leading plus).
+.It Fl f Ar file
+Load
+.Ar file
+as script and then exit.
+If multiple occurrences, program will only exit after last occurrence.
+If
+.Ar file
+is a single dash
+.Pq Sq Fl ,
+.Nm
+reads from standard input.
+.It Fl h
+Print usage statement and exit.
+.It Fl S
+Secure X11 mode
+.Pq implies Fl X .
+As root, always prompt-for and validate
+.Xr sudo 8
+username/password before starting.
+.It Fl X
+Use
+.Xr Xdialog 1
+in place of
+.Xr dialog 1 .
+.El
+.Sh COMMANDS
+The following commands
+.Pq sorted alphabetically
+are currently included in the base
+.Nm
+program, with more to be added soon.
+Other commands can be added, as detailed below in the
+.Cm ADDING COMMANDS
+section, and once added, will appear in the master menu as well as in the
+.Cm -h
+listing.
+.Bl -tag -width ".Cm syscons_screenmap"
+.It Cm console
+Utilities to customize the behavior of the system console.
+.It Cm defaultrouter
+Shortcut to the Default Router/Gateway menu under networking.
+.It Cm diskmgmt
+Manage disk partitions and/or labels.
+Executes
+.Xr sade 8 .
+.It Cm docsinstall
+Executes the
+.Cm bsdinstall docsinstall
+sub-utility to allow installation/re-installation of the FreeBSD Documentation
+set(s).
+.It Cm dot
+Generate a graphviz
+.Xr dot 1
+language file
+.Pq printed on stdout
+visualizing the
+.Nm
+menu, include, and shortcut structure relationships.
+See
+.Dq bsdconfig dot -h
+for more details.
+.It Cm groupadd
+Shortcut to the Add Groups menu under groupmgmt.
+.It Cm groupdel
+Shortcut to the Delete Groups menu under groupmgmt.
+.It Cm groupedit
+Shortcut to the Edit/View Groups menu under groupmgmt.
+.It Cm groupmgmt
+Utilities to Add/Change/View/Delete Group Accounts.
+.It Cm hostname
+Shortcut to the Hostname/Domain menu under networking.
+.It Cm kern_securelevel
+Shortcut to the kern.securelevel menu under security.
+.It Cm mouse
+Utilities for configuring, exploring, and enabling console mouse support.
+.It Cm mouse_disable
+Shortcut to the Disable menu under mouse.
+.It Cm mouse_enable
+Shortcut to the Enable menu under mouse.
+.It Cm mouse_flags
+Shortcut to the Flags menu under mouse.
+.It Cm mouse_port
+Shortcut to the Port menu under mouse.
+.It Cm mouse_type
+Shortcut to the Type menu under mouse.
+.It Cm nameservers
+Shortcut to the DNS Nameservers menu under networking.
+.It Cm netdev
+Shortcut to the Network Interfaces menu under networking.
+.It Cm networking
+Utilities to set/change Hostname/Domain, Network Interfaces, Default
+Router/Gateway, and DNS Nameservers.
+.It Cm packages
+Browse, install, uninstall, or re-install packaged software.
+.It Cm password
+Set the system administrator
+.Pq root
+password.
+.It Cm security
+Configure various system security settings.
+.It Cm startup
+Configure various aspects of system startup.
+.It Cm startup_misc
+Shortcut to the Miscellaneous Startup Services menu under startup.
+.It Cm startup_rcadd
+Shortcut to the Add New menu under the View/Edit Startup Configuration menu
+(startup_rcconf) of startup.
+.It Cm startup_rcconf
+Shortcut to the View/Edit Startup Configuration menu under startup.
+.It Cm startup_rcdelete
+Shortcut to the Delete menu under the View/Edit Startup Configuration menu
+(startup_rcconf) of startup.
+.It Cm startup_rcvar
+Shortcut to the Toggle Startup Services menu under startup.
+.\" use neutral name, e.g. console_keymap instead of syscons_keymap?
+.\" font (encoding) selection not applicable to vt(4)!
+.It Cm syscons_font
+Shortcut to the Font menu under console.
+.\" .It Cm console_keymap
+.\" Shortcut to the Keymap menu under console.
+.It Cm syscons_keymap
+Shortcut to the Keymap menu under console.
+.\" .It Cm vt_repeat
+.\" Shortcut to the Repeat menu under console.
+.It Cm syscons_repeat
+Shortcut to the Repeat menu under console.
+.\" .It Cm vt_saver
+.\" Shortcut to the Saver menu under console.
+.It Cm syscons_saver
+Shortcut to the Saver menu under console.
+.\" screenmap (encoding) selection not applicable to vt(4)!
+.It Cm syscons_screenmap
+Shortcut to the Screenmap menu under console.
+.\" .It Cm vt_syscons_ttys
+.\" Shortcut to the Ttys menu under console.
+.It Cm syscons_ttys
+Shortcut to the Ttys menu under console.
+.It Cm timezone
+Set the regional timezone of the local machine.
+.It Cm ttys
+Edit the
+.Xr ttys 5
+database with your favorite editor.
+.It Cm useradd
+Shortcut to the Add Users menu under usermgmt.
+.It Cm userdel
+Shortcut to the Delete Users menu under usermgmt.
+.It Cm useredit
+Shortcut to the Edit/View Users menu under usermgmt.
+.It Cm usermgmt
+Utilities to Add/Edit/View/Delete User Accounts.
+.El
+.Sh INTERNATIONALIZATION
+i18n features are built into
+.Nm
+and language-specific translation files will be added as they become available.
+In the absence of language-specific translation files, the default
+.Pq en_US.ISO8859-1
+files will be used.
+.Sh ADDING COMMANDS
+To be documented later.
+Document menu_selection="command|*" syntax of INDEX files.
+.Sh ENVIRONMENT VARIABLES
+The following environment variables affect the execution of
+.Nm :
+.Bl -tag -width ".Ev LC_ALL"
+.It Ev LANG
+If LANG is set, messages and index information will be read from files named
+messages.$LANG and INDEX.$LANG and fall back to files named messages and INDEX
+if messages.$LANG and INDEX.$LANG do not exist.
+LANG takes precedence over LC_ALL.
+.It Ev LC_ALL
+If LC_ALL is set, messages and index information will be read from files named
+messages.$LC_ALL and INDEX.$LC_ALL and fall back to files named messages and
+INDEX if messages.$LC_ALL and INDEX.$LC_ALL do not exist.
+.El
+.Sh FILES
+/usr/share/examples/bsdconfig/bsdconfigrc can be copied to $HOME/.bsdconfigrc
+and customized as needed.
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr bsdinstall 8
+.Sh HISTORY
+.Nm
+first appeared in
+.Fx 9.2 .
+.Sh AUTHORS
+.An Ron McDowell
+.An Devin Teske Aq Mt dteske@FreeBSD.org
+.Sh BUGS
+The docsinstall and diskmgmt modules call bsdinstall.
+Bugs found in these modules should be considered those of bsdinstall, not
+.Nm .
diff --git a/usr.sbin/bsdconfig/console/INDEX b/usr.sbin/bsdconfig/console/INDEX
new file mode 100644
index 0000000..e298f34
--- /dev/null
+++ b/usr.sbin/bsdconfig/console/INDEX
@@ -0,0 +1,70 @@
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012-2015 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+#
+# Title that will be shown in the bsdconfig menu.
+#
+menu_title="Console"
+
+#
+# A short descriptive line shown at the bottom of the bsdconfig menu. keep it
+# short because any line longer than the terminal width will be truncated.
+#
+menu_help="Customize system console behavior"
+
+#
+# Two-part variable that defines an action to take when `keyword' is passed on
+# a bsdconfig command line. Variable takes the form "keyword|command" and
+# multiple occurrences of the variable (with different `keyword's, or different
+# `keyword's AND `command's) are allowed. If `command' begins with a '/' then
+# the full path to the program is needed. If `command' begins with anything
+# else it is a path relative to the directory this INDEX file is in. `keyword'
+# can be i18n'ed but `command' is the name of a script.
+#
+menu_selection="console|console"
+menu_selection="vt_font|font"
+menu_selection="vt_keymap|keymap"
+menu_selection="vt_repeat|repeat"
+menu_selection="vt_saver|saver"
+menu_selection="vt_screenmap|screenmap"
+menu_selection="vt_ttys|ttys"
+# For backward compatibility
+menu_selection="syscons_font|font"
+menu_selection="syscons_keymap|keymap"
+menu_selection="syscons_repeat|repeat"
+menu_selection="syscons_saver|saver"
+menu_selection="syscons_screenmap|screenmap"
+menu_selection="syscons_ttys|ttys"
+
+#
+# ------------ Items below this line do NOT need i18n translation ------------
+#
+# Name of the program to be run when this menu choice is selected. If it begins
+# with a '/' then the full path to the program is needed. If it begins with
+# anything else it is a path relative to the directory this INDEX file is in.
+#
+menu_program="console"
diff --git a/usr.sbin/bsdconfig/console/Makefile b/usr.sbin/bsdconfig/console/Makefile
new file mode 100644
index 0000000..bcb2ebd
--- /dev/null
+++ b/usr.sbin/bsdconfig/console/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+SUBDIR= include
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/080.console
+FILES= INDEX USAGE
+
+SCRIPTSDIR= ${FILESDIR}
+SCRIPTS= console font keymap repeat saver screenmap ttys
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/console/Makefile.depend b/usr.sbin/bsdconfig/console/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/console/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/console/USAGE b/usr.sbin/bsdconfig/console/USAGE
new file mode 100644
index 0000000..a06d8af
--- /dev/null
+++ b/usr.sbin/bsdconfig/console/USAGE
@@ -0,0 +1,37 @@
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+Usage: bsdconfig @PROGRAM_NAME@ [OPTIONS]
+
+OPTIONS:
+ -d Provide lots of debugging info on standard-out when running.
+ -D file Send debugging info to file. If file begins with a plus-sign
+ debug info is sent to both standard-out and file (minus the
+ leading plus).
+ -h Print this usage statement and exit.
+ -S Secure X11 mode (implies `-X'). As root, always prompt-for
+ and validate sudo(8) username/password before starting.
+ -X Use Xdialog(1) in place of dialog(1).
diff --git a/usr.sbin/bsdconfig/console/console b/usr.sbin/bsdconfig/console/console
new file mode 100755
index 0000000..cfa44be
--- /dev/null
+++ b/usr.sbin/bsdconfig/console/console
@@ -0,0 +1,146 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="080.console"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ FUNCTIONS
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local prompt="$msg_console_menu_text"
+ local menu_list="
+ 'X $msg_exit' '$msg_exit_this_menu'
+ '2 $msg_font' '$msg_choose_alternate_screen_font'
+ '3 $msg_keymap' '$msg_choose_alternate_keyboard_map'
+ '4 $msg_repeat' '$msg_set_repeat_rate'
+ '5 $msg_saver' '$msg_configure_screen_saver'
+ '6 $msg_screenmap' '$msg_choose_alternate_screenmap'
+ '7 $msg_ttys' '$msg_choose_console_terminal_type'
+ " # END-QUOTE
+ local defaultitem= # Calculated below
+ local hline="$hline_configure_system_console_settings"
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ # Obtain default-item from previously stored selection
+ f_dialog_default_fetch defaultitem
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize menu_choice
+ f_dialog_menutag_store "$menu_choice"
+ f_dialog_default_store "$menu_choice"
+ return $retval
+}
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_system_console_configuration"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Launch application main menu
+#
+while :; do
+ dialog_menu_main || f_die
+ f_dialog_menutag_fetch mtag
+
+ command=
+ case "$mtag" in
+ "X $msg_exit") break ;;
+ "2 $msg_font") command=font ;; # Choose alternate screen font
+ "3 $msg_keymap") command=keymap ;; # Choose alt. keyboard map
+ "4 $msg_repeat") command=repeat ;; # Set key repeat rate
+ "5 $msg_saver") command=saver ;; # Configure the screen saver
+ "6 $msg_screenmap") command=screenmap ;; # Choose alternate screenmap
+ "7 $msg_ttys") command=ttys ;; # Choose console terminal type
+ esac
+
+ if [ "$command" ]; then
+ $BSDCFG_LIBE/$APP_DIR/$command ${USE_XDIALOG:+-X}
+ else
+ f_die 1 "$msg_unknown_console_menu_selection"
+ fi
+done
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/console/font b/usr.sbin/bsdconfig/console/font
new file mode 100755
index 0000000..ce16730
--- /dev/null
+++ b/usr.sbin/bsdconfig/console/font
@@ -0,0 +1,193 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="080.console"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ FUNCTIONS
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local prompt="$msg_font_menu_text"
+ local menu_list="
+ '1 $msg_none' '$msg_use_hardware_default_font'
+ '2 $msg_ibm_437' '$msg_ibm_437_desc'
+ '3 $msg_ibm_850' '$msg_ibm_850_desc'
+ '4 $msg_ibm_865' '$msg_ibm_865_desc'
+ '5 $msg_ibm_866' '$msg_ibm_866_desc'
+ '6 $msg_ibm_866u' '$msg_ibm_866u_desc'
+ '7 $msg_ibm_1251' '$msg_ibm_1251_desc'
+ '8 $msg_iso_8859_1' '$msg_iso_8859_1_desc'
+ '9 $msg_iso_8859_2' '$msg_iso_8859_2_desc'
+ 'a $msg_iso_8859_4' '$msg_iso_8859_4_desc'
+ 'b $msg_iso_8859_7' '$msg_iso_8859_7_desc'
+ 'c $msg_iso_8859_8' '$msg_iso_8859_8_desc'
+ 'd $msg_iso_8859_15' '$msg_iso_8859_15_desc'
+ 'e $msg_swiss' '$msg_swiss_desc'
+ " # END-QUOTE
+ local defaultitem= # Calculated below
+ local hline="$hline_choose_a_font"
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ case "$( f_sysrc_get font8x8 )" in
+ [Nn][Oo]|'') defaultitem="1 $msg_none" ;;
+ cp437-8x8) defaultitem="2 $msg_ibm_437" ;;
+ cp850-8x8) defaultitem="3 $msg_ibm_850" ;;
+ cp865-8x8) defaultitem="4 $msg_ibm_865" ;;
+ cp866-8x8) defaultitem="5 $msg_ibm_866" ;;
+ cp866u-8x8) defaultitem="6 $msg_ibm_866u" ;;
+ cp1251-8x8) defaultitem="7 $msg_ibm_1251" ;;
+ iso-8x8) defaultitem="8 $msg_iso_8859_1" ;;
+ iso02-8x8) defaultitem="9 $msg_iso_8859_2" ;;
+ iso04-8x8) defaultitem="a $msg_iso_8859_4" ;;
+ iso07-8x8) defaultitem="b $msg_iso_8859_7" ;;
+ iso08-8x8) defaultitem="c $msg_iso_8859_8" ;;
+ iso15-8x8) defaultitem="d $msg_iso_8859_15" ;;
+ swiss-8x8) defaultitem="e $msg_swiss" ;;
+ esac
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_menutag_store -s "$menu_choice"
+ return $retval
+}
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_system_console_font"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Launch application main menu
+#
+dialog_menu_main || f_die
+f_dialog_menutag_fetch mtag
+
+f8= f14= f16= mc_start=
+case "$mtag" in
+"1 $msg_none") # Use hardware default font
+ f8="NO" f14="NO" f16="NO" ;;
+"2 $msg_ibm_437") # English and others, VGA default
+ f8="cp437-8x8" f14="cp437-8x14" f16="cp437-8x16" ;;
+"3 $msg_ibm_850") # Western Europe, IBM encoding
+ f8="cp850-8x8" f14="cp850-8x14" f16="cp850-8x16" ;;
+"4 $msg_ibm_865") # Norwegian, IBM encoding
+ f8="cp865-8x8" f14="cp865-8x14" f16="cp865-8x16" ;;
+"5 $msg_ibm_866") # Russian, IBM encoding (use with KOI8-R screenmap)
+ f8="cp866-8x8" f14="cp866-8x14" f16="cp866b-8x16" mc_start="3" ;;
+"6 $msg_ibm_866u") # Ukrainian, IBM encoding (use w/ KOI8-U screenmap)
+ f8="cp866u-8x8" f14="cp866u-8x14" f16="cp866u-8x16" mc_start="3" ;;
+"7 $msg_ibm_1251") # Cyrillic, MS Windows encoding
+ f8="cp1251-8x8" f14="cp1251-8x14" f16="cp1251-8x16" mc_start="3" ;;
+"8 $msg_iso_8859_1") # Western Europe, ISO encoding
+ f8="iso-8x8" f14="iso-8x14" f16="iso-8x16" ;;
+"9 $msg_iso_8859_2") # Eastern Europe, ISO encoding
+ f8="iso02-8x8" f14="iso02-8x14" f16="iso02-8x16" ;;
+"a $msg_iso_8859_4") # Baltic, ISO encoding
+ f8="iso04-8x8" f14="iso04-8x14" f16="iso04-8x16" ;;
+"b $msg_iso_8859_7") # Greek, ISO encoding
+ f8="iso07-8x8" f14="iso07-8x14" f16="iso07-8x16" ;;
+"c $msg_iso_8859_8") # Hebrew, ISO encoding
+ f8="iso08-8x8" f14="iso08-8x14" f16="iso08-8x16" ;;
+"d $msg_iso_8859_15") # Europe, ISO encoding
+ f8="iso15-8x8" f14="iso15-8x14" f16="iso15-8x16" ;;
+"e $msg_swiss") # English, better resolution
+ f8="swiss-8x8" f14="NO" f16="swiss-8x16" ;;
+esac
+
+[ "$f8" -a "$f14" -a "$f16" ] || f_die 1 "$msg_unknown_font_selection"
+
+f_eval_catch "$0" f_sysrc_set 'f_sysrc_set font8x8 "%s"' "$f8" || f_die
+f_eval_catch "$0" f_sysrc_set 'f_sysrc_set font8x14 "%s"' "$f14" || f_die
+f_eval_catch "$0" f_sysrc_set 'f_sysrc_set font8x16 "%s"' "$f16" || f_die
+
+if [ "$mc_start" ]; then
+ f_eval_catch "$0" f_sysrc_set \
+ 'f_sysrc_set mousechar_start "%s"' "$mc_start" || f_die
+else
+ f_eval_catch "$0" f_sysrc_delete \
+ 'f_sysrc_delete mousechar_start' || f_die
+fi
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/console/include/Makefile b/usr.sbin/bsdconfig/console/include/Makefile
new file mode 100644
index 0000000..52cb0be
--- /dev/null
+++ b/usr.sbin/bsdconfig/console/include/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/080.console/include
+FILES= messages.subr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/console/include/Makefile.depend b/usr.sbin/bsdconfig/console/include/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/console/include/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/console/include/messages.subr b/usr.sbin/bsdconfig/console/include/messages.subr
new file mode 100644
index 0000000..295f3e3
--- /dev/null
+++ b/usr.sbin/bsdconfig/console/include/messages.subr
@@ -0,0 +1,270 @@
+# Copyright (c) 2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+hline_choose_a_font="Choose a font"
+hline_choose_a_keyboard_map="Choose a keyboard map"
+hline_choose_a_keyboard_repeat_rate="Choose a keyboard repeat rate"
+hline_choose_a_screen_map="Choose a screen map"
+hline_choose_a_screen_saver="Choose a nifty-looking screen saver"
+hline_choose_a_terminal_type="Choose a terminal type"
+hline_configure_system_console_settings="Configure your system console settings"
+msg_beastie="Beastie"
+msg_beastie_desc="\"BSD Daemon\" animated screen saver (graphics)"
+msg_belgian="Belgian"
+msg_belgian_desc="Belgian ISO keymap"
+msg_blank="Blank"
+msg_blank_desc="Blank screen"
+msg_brazil_cp850="Brazil CP850"
+msg_brazil_cp850_desc="Brazil CP850 keymap"
+msg_brazil_iso="Brazil ISO"
+msg_brazil_iso_accent="Brazil ISO (accent)"
+msg_brazil_iso_accent_desc="Brazil ISO keymap (accent keys)"
+msg_brazil_iso_desc="Brazil ISO keymap"
+msg_bulgarian_bds="Bulgarian BDS"
+msg_bulgarian_bds_desc="Bulgarian BDS keymap"
+msg_bulgarian_phonetic="Bulgarian Phonetic"
+msg_bulgarian_phonetic_desc="Bulgarian Phonetic keymap"
+msg_cancel="Cancel"
+msg_central_european_iso="Central European ISO"
+msg_central_european_iso_desc="Central European ISO keymap"
+msg_choose_alternate_keyboard_map="Choose an alternate keyboard map"
+msg_choose_alternate_screen_font="Choose an alternate screen font"
+msg_choose_alternate_screenmap="Choose an alternate screenmap"
+msg_choose_console_terminal_type="Choose console terminal type"
+msg_configure_screen_saver="Configure the screen saver"
+msg_console_menu_text="The system console driver for FreeBSD has a number of configuration\noptions which may be set according to your preference.\n\nWhen you are done setting configuration options, select Cancel."
+msg_croatian_iso="Croation ISO"
+msg_croatian_iso_desc="Croation ISO keymap"
+msg_czech_iso_accent="Czech ISO (accent)"
+msg_czech_iso_accent_desc="Czech ISO keymap (accent keys)"
+msg_daemon="Daemon"
+msg_daemon_desc="\"BSD Daemon\" animated screen saver (text)"
+msg_danish_cp865="Danish CP865"
+msg_danish_cp865_desc="Danish Code Page 865 keymap"
+msg_danish_iso="Danish ISO"
+msg_danish_iso_desc="Danish ISO keymap"
+msg_default="Default"
+msg_default_desc="Use default keyboard repeat rate"
+msg_dragon="Dragon"
+msg_dragon_desc="Dragon screensaver (graphics)"
+msg_enter_timeout_period="Enter time-out period in seconds for screen saver"
+msg_estonian_cp850="Estonian CP850"
+msg_estonian_cp850_desc="Estonian Code Page 850 keymap"
+msg_estonian_iso="Estonian ISO"
+msg_estonian_iso_15="Estonian ISO 15"
+msg_estonian_iso_15_desc="Estonian ISO 8859-15 keymap"
+msg_estonian_iso_desc="Estonian ISO keymap"
+msg_exit="Exit"
+msg_exit_this_menu="Exit this menu"
+msg_fade="Fade"
+msg_fade_desc="Fade out effect screen saver"
+msg_fast="Fast"
+msg_fast_desc="Fast keyboard repeat rate"
+msg_finnish_cp850="Finnish CP850"
+msg_finnish_cp850_desc="Finnish Code Page 850 keymap"
+msg_finnish_iso="Finnish ISO"
+msg_finnish_iso_desc="Finnish ISO keymap"
+msg_fire="Fire"
+msg_fire_desc="Flames effect screen saver"
+msg_font="Font"
+msg_font_menu_text="Most PC hardware defaults to displaying characters in the\nIBM 437 character set. However, in the Unix world, this\ncharacter set is very rarely used. Most Western European\ncountries, for example, prefer ISO 8859-1.\nAmerican users won't notice the difference since the bottom half\nof all these charactersets is ANSI anyway. However, they might\nwant to load a font anyway to use the 30- or 50-line displays.\nIf your hardware is capable of downloading a new display font,\nyou can select the appropriate font below."
+msg_french_iso="French ISO"
+msg_french_iso_accent="French ISO (accent)"
+msg_french_iso_accent_desc="French ISO keymap (accent keys)"
+msg_french_iso_desc="French ISO keymap"
+msg_french_iso_macbook="French ISO/Macbook"
+msg_french_iso_macbook_desc="French ISO keymap on macbook"
+msg_german_cp850="German CP850"
+msg_german_cp850_desc="German Code Page 850 keymap"
+msg_german_iso="German ISO"
+msg_german_iso_desc="German ISO keymap"
+msg_greek_101="Greek 101"
+msg_greek_101_desc="Greek ISO keymap (101 keys)"
+msg_greek_104="Greek 104"
+msg_greek_104_desc="Greek ISO keymap (104 keys)"
+msg_greek_elot="Greek ELOT"
+msg_greek_elot_desc="Greek ISO keymap (ELOT 1000)"
+msg_green="Green"
+msg_green_desc="\"Green\" power saving mode (if supported by monitor)"
+msg_hungarian_101="Hungarian 101"
+msg_hungarian_101_desc="Hungarian ISO keymap (101 key)"
+msg_hungarian_102="Hungarian 102"
+msg_hungarian_102_desc="Hungarian ISO keymap (102 key)"
+msg_ibm_1251="IBM 1251"
+msg_ibm_1251_desc="Cyrillic, MS Windows encoding"
+msg_ibm_437="IBM 437"
+msg_ibm_437_desc="English and others, VGA default"
+msg_ibm_437_vga_default="IBM437 (VGA default)"
+msg_ibm_850="IBM 850"
+msg_ibm_850_desc="Western Europe, IBM encoding"
+msg_ibm_865="IBM 865"
+msg_ibm_865_desc="Norwegian, IBM encoding"
+msg_ibm_866="IBM 866"
+msg_ibm_866_desc="Russian, IBM encoding (use with KOI8-R screenmap)"
+msg_ibm_866u="IBM 866u"
+msg_ibm_866u_desc="Ukrainian, IBM encoding (use with KOI8-U screenmap)"
+msg_icelandic="Icelandic"
+msg_icelandic_accent="Icelandic (accent)"
+msg_icelandic_accent_desc="Icelandic ISO keymap (accent keys)"
+msg_icelandic_desc="Icelandic ISO keymap"
+msg_iso_8859_15="ISO 8859-15"
+msg_iso_8859_15_desc="Europe, ISO encoding"
+msg_iso_8859_1="ISO 8859-1"
+msg_iso_8859_1_desc="Western Europe, ISO encoding"
+msg_iso_8859_1_to_ibm437="ISO 8859-1 to IBM437"
+msg_iso_8859_1_to_ibm437_desc="W-Europe ISO 8859-1 to IBM 437 screenmap"
+msg_iso_8859_2="ISO 8859-2"
+msg_iso_8859_2_desc="Eastern Europe, ISO encoding"
+msg_iso_8859_4="ISO 8859-4"
+msg_iso_8859_4_desc="Baltic, ISO encoding"
+msg_iso_8859_7="ISO 8859-7"
+msg_iso_8859_7_desc="Greek, ISO encoding"
+msg_iso_8859_7_to_ibm437="ISO 8859-7 to IBM437"
+msg_iso_8859_7_to_ibm437_desc="Greek ISO 8859-1 to IBM 437 screenmap"
+msg_iso_8859_8="ISO 8859-8"
+msg_iso_8859_8_desc="Hebrew, ISO encoding"
+msg_italian="Italian"
+msg_italian_desc="Italian ISO keymap"
+msg_japanese_106="Japanese 106"
+msg_japanese_106_desc="Japanese 106 keymap"
+msg_keymap="Keymap"
+msg_keymap_menu_text="The system console driver for FreeBSD defaults to a standard\n\"US\" keyboard map. Users may wish to choose one of the\nother keymaps below."
+msg_koi8_r="KOI8-R"
+msg_koi8_r_to_ibm866="KOI8-R to IBM866"
+msg_koi8_r_to_ibm866_desc="Russian KOI8-R to IBM 866 screenmap"
+msg_koi8_u="KOI8-U"
+msg_koi8_u_to_ibm866u="KOI8-U to IBM866u"
+msg_koi8_u_to_ibm866u_desc="Ukrainian KOI8-U to IBM 866u screenmap"
+msg_latin_american="Latin American"
+msg_latin_american_accent="Latin American (accent)"
+msg_latin_american_accent_desc="Latin American ISO keymap (accent keys)"
+msg_latin_american_desc="Latin American ISO keymap"
+msg_logo="Logo"
+msg_logo_desc="FreeBSD \"logo\" animated screen saver (graphics)"
+msg_none="None"
+msg_none_saver_desc="Disable the screensaver"
+msg_none_screenmap_desc="No screenmap, don'\''t touch font"
+msg_none_ttys_desc="Don'\''t touch anything"
+msg_normal="Normal"
+msg_normal_desc="\"Normal\" keyboard repeat rate"
+msg_norway_iso="Norway ISO"
+msg_norway_iso_desc="Norwegian ISO keymap"
+msg_ok="OK"
+msg_polish_iso="Polish ISO"
+msg_polish_iso_desc="Polish ISO keymap"
+msg_portuguese="Portuguese"
+msg_portuguese_accent="Portuguese (accent)"
+msg_portuguese_accent_desc="Portuguese ISO keymap (accent keys)"
+msg_portuguese_desc="Portuguese ISO keymap"
+msg_rain="Rain"
+msg_rain_desc="Rain drops screen saver"
+msg_repeat="Repeat"
+msg_repeat_menu_text="This menu allows you to set the speed at which keys repeat\nwhen held down."
+msg_russia_koi8_r="Russia KOI8-R"
+msg_russia_koi8_r_desc="Russia KOI8-R keymap"
+msg_saver="Saver"
+msg_saver_menu_text="By default, the console driver will not attempt to do anything\nspecial with your screen when it's idle. If you expect to leave your\nmonitor switched on and idle for long periods of time then you should\nprobably enable one of these screen savers to prevent burn-in."
+msg_screenmap="Screenmap"
+msg_screenmap_menu_text="Unless you load a specific font, most PC hardware defaults to\ndisplaying characters in the IBM 437 character set. However,\nin the Unix world, this character set is very rarely used. Most\nWestern European countries, for example, prefer ISO 8859-1.\nAmerican users won't notice the difference since the bottom half\nof all these character sets is ANSI anyway.\nIf your hardware is capable of downloading a new display font,\nyou should probably choose that option. However, for hardware\nwhere this is not possible (e.g. monochrome adapters), a screen\nmap will give you the best approximation that your hardware can\ndisplay at all."
+msg_set_repeat_rate="Set the rate at which keys repeat"
+msg_slovak="Slovak"
+msg_slovak_desc="Slovak ISO keymap"
+msg_slovenian="Slovenian"
+msg_slovenian_desc="Slovenian ISO keymap"
+msg_slow="Slow"
+msg_slow_desc="Slow keyboard repeat rate"
+msg_snake="Snake"
+msg_snake_desc="Draw a FreeBSD \"snake\" on your screen"
+msg_spanish="Spanish"
+msg_spanish_accent="Spanish (accent)"
+msg_spanish_accent_desc="Spanish ISO keymap (accent keys)"
+msg_spanish_desc="Spanish ISO keymap"
+msg_star="Star"
+msg_star_desc="A \"twinkling stars\" effect"
+msg_swedish_cp850="Swedish CP850"
+msg_swedish_cp850_desc="Swedish Code Page 850 keymap"
+msg_swedish_iso="Swedish ISO"
+msg_swedish_iso_desc="Swedish ISO keymap"
+msg_swiss="SWISS"
+msg_swiss_desc="English, better resolution"
+msg_swiss_french_cp850="Swiss French CP850"
+msg_swiss_french_cp850_desc="Swiss French Code Page 850 keymap"
+msg_swiss_french_iso="Swiss French ISO"
+msg_swiss_french_iso_accent="Swiss French ISO (accent)"
+msg_swiss_french_iso_accent_desc="Swiss French ISO keymap (accent keys)"
+msg_swiss_french_iso_desc="Swiss French ISO keymap"
+msg_swiss_german_cp850="Swiss German CP850"
+msg_swiss_german_cp850_desc="Swiss German Code Page 850 keymap"
+msg_swiss_german_iso="Swiss German ISO"
+msg_swiss_german_iso_accent="Swiss German ISO (accent)"
+msg_swiss_german_iso_accent_desc="Swiss German ISO keymap (accent keys)"
+msg_swiss_german_iso_desc="Swiss German ISO keymap"
+msg_system_console_configuration="System Console Configuration"
+msg_system_console_font="System Console Font"
+msg_system_console_keyboard_repeat_rate="System Console Keyboard Repeat Rate"
+msg_system_console_keymap="System Console Keymap"
+msg_system_console_screen_saver="System Console Screen Saver"
+msg_system_console_screenmap="System Console Screenmap"
+msg_system_console_terminal_type="System Console Terminal Type"
+msg_timeout="Timeout"
+msg_timeout_desc="Set the screen saver timeout interval"
+msg_ttys="Ttys"
+msg_ttys_menu_text="For various console encodings, a corresponding terminal type\nmust be chosen in /etc/ttys.\n\nWARNING: For compatibility reasons, only entries starting with\nttyv and terminal types starting with cons[0-9] can be changed\nvia this menu."
+msg_uk_cp850="UK CP850"
+msg_uk_cp850_desc="UK Code Page 850 keymap"
+msg_uk_iso="UK ISO"
+msg_uk_iso_desc="UK ISO keymap"
+msg_ukrainian_koi8_u="Ukranian KOI8-U"
+msg_ukrainian_koi8_u_desc="Ukranian KOI8-U keymap"
+msg_ukrainian_koi8_u_koi8_r="Ukranian KOI8-U+KOI8-R"
+msg_ukrainian_koi8_u_koi8_r_desc="Ukranian KOI8-U+KOI8-R keymap (alter)"
+msg_unknown_console_menu_selection="Unknown console menu selection"
+msg_unknown_font_selection="Unknown font selection"
+msg_unknown_keymap="Unknown keymap"
+msg_unknown_repeat_rate="Unknown repeat rate"
+msg_unknown_saver="Unknown saver"
+msg_unknown_screenmap_selection="Unknown screenmap selection"
+msg_us_ascii="US-ASCII"
+msg_us_ascii_to_ibm327="US-ASCII to IBM437"
+msg_us_ascii_to_ibm327_desc="US-ASCII to IBM 437 screenmap"
+msg_usa_capslock_ctrl="USA CapsLock->Ctrl"
+msg_usa_capslock_ctrl_desc="US standard (Caps as L-Control)"
+msg_usa_dvorak="USA Dvorak"
+msg_usa_dvorak_desc="US Dvorak keymap"
+msg_usa_dvorak_left="USA Dvorak (left)"
+msg_usa_dvorak_left_desc="US left handed Dvorak keymap"
+msg_usa_dvorak_right="USA Dvorak (right)"
+msg_usa_dvorak_right_desc="US right handed Dvorak keymap"
+msg_usa_emacs="USA Emacs"
+msg_usa_emacs_desc="US standard optimized for EMACS"
+msg_usa_iso="USA ISO"
+msg_usa_iso_desc="US ISO keymap"
+msg_usa_unix="USA UNIX"
+msg_usa_unix_desc="US traditional UNIX-workstation"
+msg_use_hardware_default_font="Use hardware default font"
+msg_value_required="Value Required"
+msg_warp="Warp"
+msg_warp_desc="A \"stars warping\" effect"
diff --git a/usr.sbin/bsdconfig/console/keymap b/usr.sbin/bsdconfig/console/keymap
new file mode 100755
index 0000000..c163a1b
--- /dev/null
+++ b/usr.sbin/bsdconfig/console/keymap
@@ -0,0 +1,332 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="080.console"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ CONFIGURATION
+
+#
+# List of keymap names
+#
+KEYMAP_NAMES="
+ belgian brazil_cp850 brazil_iso
+ brazil_iso_accent bulgarian_bds bulgarian_phonetic
+ central_european_iso croatian_iso czech_iso_accent
+ danish_cp865 danish_iso estonian_cp850
+ estonian_iso estonian_iso_15 finnish_cp850
+ finnish_iso french_iso french_iso_accent
+ french_iso_macbook german_cp850 german_iso
+ greek_101 greek_104 greek_elot
+ hungarian_101 hungarian_102 icelandic
+ icelandic_accent italian japanese_106
+ latin_american latin_american_accent norway_iso
+ polish_iso portuguese portuguese_accent
+ russia_koi8_r slovak slovenian
+ spanish spanish_accent swedish_cp850
+ swedish_iso swiss_french_cp850 swiss_french_iso
+ swiss_french_iso_accent swiss_german_cp850 swiss_german_iso
+ swiss_german_iso_accent uk_cp850 uk_iso
+ ukrainian_koi8_u ukrainian_koi8_u_koi8_r usa_capslock_ctrl
+ usa_dvorak usa_dvorak_left usa_dvorak_right
+ usa_emacs usa_iso usa_unix
+" # END-QUOTE
+
+############################################################ FUNCTIONS
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local prompt="$msg_keymap_menu_text"
+ local menu_list defaultitem= # Calculated below
+ local hline="$hline_choose_a_keyboard_map"
+
+ #
+ # Export variables for awk(1) ENVIRON visibility
+ #
+ local name
+ for name in $KEYMAP_NAMES; do
+ export msg_$name msg_${name}_desc
+ done
+
+ #
+ # Generate a sorted list of keymaps. If the first letter of the keymap
+ # name is unique (case-insensitive) then it is used as the tag to allow
+ # the user to jump to that entry.
+ #
+ menu_list=$(
+ for name in $KEYMAP_NAMES; do
+ eval echo \"\$msg_$name\" msg_$name
+ done | sort | awk 'BEGIN { prefix = "" }
+ {
+ cur_prefix = tolower(substr(ENVIRON[$NF], 1, 1))
+ printf "'\''"
+ if ( prefix != cur_prefix )
+ prefix = cur_prefix
+ else
+ printf " "
+ printf "%s'\'' '\''%s'\''\n",
+ ENVIRON[$NF], ENVIRON[$NF"_desc"]
+ }'
+ )
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ case "$( f_sysrc_get keymap )" in
+ be.iso) defaultitem="$msg_belgian" ;;
+ br275.cp850) defaultitem="$msg_brazil_cp850" ;;
+ br275.iso) defaultitem="$msg_brazil_iso" ;;
+ br275.iso.acc) defaultitem="$msg_brazil_iso_accent" ;;
+ bg.bds.ctrlcaps) defaultitem="$msg_bulgarian_bds" ;;
+ bg.phonetic.ctrlcaps) defaultitem="$msg_bulgarian_phonetic" ;;
+ ce.iso2) defaultitem="$msg_central_european_iso" ;;
+ hr.iso) defaultitem="$msg_croatian_iso" ;;
+ cs.latin2.qwertz) defaultitem="$msg_czech_iso_accent" ;;
+ danish.cp865) defaultitem="$msg_danish_cp865" ;;
+ danish.iso) defaultitem="$msg_danish_iso" ;;
+ estonian.cp850) defaultitem="$msg_estonian_cp850" ;;
+ estonian.iso) defaultitem="$msg_estonian_iso" ;;
+ estonian.iso15) defaultitem="$msg_estonian_iso_15" ;;
+ finnish.cp850) defaultitem="$msg_finnish_cp850" ;;
+ finnish.iso) defaultitem="$msg_finnish_iso" ;;
+ fr.iso) defaultitem="$msg_french_iso" ;;
+ fr.iso.acc) defaultitem="$msg_french_iso_accent" ;;
+ fr.macbook.acc) defaultitem="$msg_french_iso_macbook" ;;
+ german.cp850) defaultitem="$msg_german_cp850" ;;
+ german.iso) defaultitem="$msg_german_iso" ;;
+ gr.us101.acc) defaultitem="$msg_greek_101" ;;
+ el.iso07) defaultitem="$msg_greek_104" ;;
+ gr.elot.acc) defaultitem="$msg_greek_elot" ;;
+ hu.iso2.101keys) defaultitem="$msg_hungarian_101" ;;
+ hu.iso2.102keys) defaultitem="$msg_hungarian_102" ;;
+ icelandic.iso) defaultitem="$msg_icelandic" ;;
+ icelandic.iso.acc) defaultitem="$msg_icelandic_accent" ;;
+ it.iso) defaultitem="$msg_italian" ;;
+ jp.106) defaultitem="$msg_japanese_106" ;;
+ latinamerican) defaultitem="$msg_latin_american" ;;
+ latinamerican.iso.acc) defaultitem="$msg_latin_american_accent" ;;
+ norwegian.iso) defaultitem="$msg_norway_iso" ;;
+ pl_PL.ISO8859-2) defaultitem="$msg_polish_iso" ;;
+ pt.iso) defaultitem="$msg_portuguese" ;;
+ pt.iso.acc) defaultitem="$msg_portuguese_accent" ;;
+ ru.koi8-r) defaultitem="$msg_russia_koi8_r" ;;
+ sk.iso2) defaultitem="$msg_slovak" ;;
+ si.iso) defaultitem="$msg_slovenian" ;;
+ spanish.iso) defaultitem="$msg_spanish" ;;
+ spanish.iso.acc) defaultitem="$msg_spanish_accent" ;;
+ swedish.cp850) defaultitem="$msg_swedish_cp850" ;;
+ swedish.iso) defaultitem="$msg_swedish_iso" ;;
+ swissfrench.cp850) defaultitem="$msg_swiss_french_cp850" ;;
+ swissfrench.iso) defaultitem="$msg_swiss_french_iso" ;;
+ swissfrench.iso.acc) defaultitem="$msg_swiss_french_iso_accent" ;;
+ swissgerman.cp850) defaultitem="$msg_swiss_german_cp850" ;;
+ swissgerman.iso) defaultitem="$msg_swiss_german_iso" ;;
+ swissgerman.iso.acc) defaultitem="$msg_swiss_german_iso_accent" ;;
+ uk.cp850) defaultitem="$msg_uk_cp850" ;;
+ uk.iso) defaultitem="$msg_uk_iso" ;;
+ ua.koi8-u) defaultitem="$msg_ukrainian_koi8_u" ;;
+ ua.koi8-u.shift.alt) defaultitem="$msg_ukrainian_koi8_u_koi8_r" ;;
+ us.pc-ctrl) defaultitem="$msg_usa_capslock_ctrl" ;;
+ us.dvorak) defaultitem="$msg_usa_dvorak" ;;
+ us.dvorakl) defaultitem="$msg_usa_dvorak_left" ;;
+ us.dvorakr) defaultitem="$msg_usa_dvorak_right" ;;
+ us.emacs) defaultitem="$msg_usa_emacs" ;;
+ us.iso) defaultitem="$msg_usa_iso" ;;
+ us.unix) defaultitem="$msg_usa_unix" ;;
+ esac
+
+ # The defaultitem may have to be indented to match the menu_list
+ if [ "$defaultitem" ]; then
+ ( eval set -- $menu_list
+ while [ $# -gt 0 ]; do
+ [ "$defaultitem" = "$1" ] && break
+ [ " $defaultitem" = "$1" ] && exit 0
+ shift 2 # tag/item
+ done
+ exit 1 # No modification needed
+ ) && defaultitem=" $defaultitem"
+ fi
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_menutag_store -s "$menu_choice"
+ return $retval
+}
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_system_console_keymap"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Launch application main menu
+#
+while :; do
+ dialog_menu_main || f_die
+ f_dialog_menutag_fetch mtag
+
+ #
+ # Reverse the users choice into the variable name
+ #
+ keymap_name=
+ mtag="${mtag# }" # remove single leading-space if-present
+ for name in $KEYMAP_NAMES; do
+ debug= f_getvar msg_$name msg
+ [ "$msg" = "$mtag" ] && keymap_name="$name" break
+ done
+
+ [ "$keymap_name" ] || continue
+
+ keymap_to_set=
+ case "$keymap_name" in
+ belgian) keymap_to_set="be.iso" ;;
+ brazil_cp850) keymap_to_set="br275.cp850" ;;
+ brazil_iso) keymap_to_set="br275.iso" ;;
+ brazil_iso_accent) keymap_to_set="br275.iso.acc" ;;
+ bulgarian_bds) keymap_to_set="bg.bds.ctrlcaps" ;;
+ bulgarian_phonetic) keymap_to_set="bg.phonetic.ctrlcaps" ;;
+ central_european_iso) keymap_to_set="ce.iso2" ;;
+ croatian_iso) keymap_to_set="hr.iso" ;;
+ czech_iso_accent) keymap_to_set="cs.latin2.qwertz" ;;
+ danish_cp865) keymap_to_set="danish.cp865" ;;
+ danish_iso) keymap_to_set="danish.iso" ;;
+ estonian_cp850) keymap_to_set="estonian.cp850" ;;
+ estonian_iso) keymap_to_set="estonian.iso" ;;
+ estonian_iso_15) keymap_to_set="estonian.iso15" ;;
+ finnish_cp850) keymap_to_set="finnish.cp850" ;;
+ finnish_iso) keymap_to_set="finnish.iso" ;;
+ french_iso) keymap_to_set="fr.iso" ;;
+ french_iso_accent) keymap_to_set="fr.iso.acc" ;;
+ french_iso_macbook) keymap_to_set="fr.macbook.acc" ;;
+ german_cp850) keymap_to_set="german.cp850" ;;
+ german_iso) keymap_to_set="german.iso" ;;
+ greek_101) keymap_to_set="gr.us101.acc" ;;
+ greek_104) keymap_to_set="el.iso07" ;;
+ greek_elot) keymap_to_set="gr.elot.acc" ;;
+ hungarian_101) keymap_to_set="hu.iso2.101keys" ;;
+ hungarian_102) keymap_to_set="hu.iso2.102keys" ;;
+ icelandic) keymap_to_set="icelandic.iso" ;;
+ icelandic_accent) keymap_to_set="icelandic.iso.acc" ;;
+ italian) keymap_to_set="it.iso" ;;
+ japanese_106) keymap_to_set="jp.106" ;;
+ latin_american) keymap_to_set="latinamerican" ;;
+ latin_american_accent) keymap_to_set="latinamerican.iso.acc" ;;
+ norway_iso) keymap_to_set="norwegian.iso" ;;
+ polish_iso) keymap_to_set="pl_PL.ISO8859-2" ;;
+ portuguese) keymap_to_set="pt.iso" ;;
+ portuguese_accent) keymap_to_set="pt.iso.acc" ;;
+ russia_koi8_r) keymap_to_set="ru.koi8-r" ;;
+ slovak) keymap_to_set="sk.iso2" ;;
+ slovenian) keymap_to_set="si.iso" ;;
+ spanish) keymap_to_set="spanish.iso" ;;
+ spanish_accent) keymap_to_set="spanish.iso.acc" ;;
+ swedish_cp850) keymap_to_set="swedish.cp850" ;;
+ swedish_iso) keymap_to_set="swedish.iso" ;;
+ swiss_french_cp850) keymap_to_set="swissfrench.cp850" ;;
+ swiss_french_iso) keymap_to_set="swissfrench.iso" ;;
+ swiss_french_iso_accent) keymap_to_set="swissfrench.iso.acc" ;;
+ swiss_german_cp850) keymap_to_set="swissgerman.cp850" ;;
+ swiss_german_iso) keymap_to_set="swissgerman.iso" ;;
+ swiss_german_iso_accent) keymap_to_set="swissgerman.iso.acc" ;;
+ uk_cp850) keymap_to_set="uk.cp850" ;;
+ uk_iso) keymap_to_set="uk.iso" ;;
+ ukrainian_koi8_u) keymap_to_set="ua.koi8-u" ;;
+ ukrainian_koi8_u_koi8_r) keymap_to_set="ua.koi8-u.shift.alt" ;;
+ usa_capslock_ctrl) keymap_to_set="us.pc-ctrl" ;;
+ usa_dvorak) keymap_to_set="us.dvorak" ;;
+ usa_dvorak_left) keymap_to_set="us.dvorakl" ;;
+ usa_dvorak_right) keymap_to_set="us.dvorakr" ;;
+ usa_emacs) keymap_to_set="us.emacs" ;;
+ usa_iso) keymap_to_set="us.iso" ;;
+ usa_unix) keymap_to_set="us.unix" ;;
+ esac
+
+ if [ "$keymap_to_set" ]; then
+ f_eval_catch "$0" f_sysrc_set \
+ 'f_sysrc_set keymap "%s"' "$keymap_to_set" || f_die
+ break
+ else
+ f_die 1 "$msg_unknown_keymap"
+ fi
+done
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/console/repeat b/usr.sbin/bsdconfig/console/repeat
new file mode 100755
index 0000000..ff00023
--- /dev/null
+++ b/usr.sbin/bsdconfig/console/repeat
@@ -0,0 +1,143 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="080.console"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ FUNCTIONS
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local prompt="$msg_repeat_menu_text"
+ local menu_list="
+ '$msg_default' '$msg_default_desc'
+ '$msg_slow' '$msg_slow_desc'
+ '$msg_normal' '$msg_normal_desc'
+ '$msg_fast' '$msg_fast_desc'
+ " # END-QUOTE
+ local defaultitem= # Calculated below
+ local hline="$hline_choose_a_keyboard_repeat_rate"
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ case "$( f_sysrc_get keyrate )" in
+ [Nn][Oo]|'') defaultitem="$msg_default" ;;
+ slow) defaultitem="$msg_slow" ;;
+ normal) defaultitem="$msg_normal" ;;
+ fast) defaultitem="$msg_fast" ;;
+ esac
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_menutag_store -s "$menu_choice"
+ return $retval
+}
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_system_console_keyboard_repeat_rate"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Launch application main menu
+#
+dialog_menu_main || f_die
+f_dialog_menutag_fetch mtag
+
+repeat_rate_to_set=
+case "$mtag" in
+"$msg_default") repeat_rate_to_set="NO" ;; # Use default repeat rate
+"$msg_slow") repeat_rate_to_set="slow" ;; # Slow keyboard repeat rate
+"$msg_normal") repeat_rate_to_set="normal" ;; # "Normal" keyboard repeat rate
+"$msg_fast") repeat_rate_to_set="fast" ;; # Fast keyboard repeat rate
+esac
+
+if [ "$repeat_rate_to_set" ]; then
+ f_eval_catch "$0" f_sysrc_set \
+ 'f_sysrc_set keyrate "%s"' "$repeat_rate_to_set" || f_die
+ break
+else
+ f_die 1 "$msg_unknown_repeat_rate"
+fi
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/console/saver b/usr.sbin/bsdconfig/console/saver
new file mode 100755
index 0000000..0df487c
--- /dev/null
+++ b/usr.sbin/bsdconfig/console/saver
@@ -0,0 +1,195 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="080.console"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ FUNCTIONS
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local prompt="$msg_saver_menu_text"
+ local menu_list="
+ '1 $msg_none' '$msg_none_saver_desc'
+ '2 $msg_blank' '$msg_blank_desc'
+ '3 $msg_beastie' '$msg_beastie_desc'
+ '4 $msg_daemon' '$msg_daemon_desc'
+ '5 $msg_dragon' '$msg_dragon_desc'
+ '6 $msg_fade' '$msg_fade_desc'
+ '7 $msg_fire' '$msg_fire_desc'
+ '8 $msg_green' '$msg_green_desc'
+ '9 $msg_logo' '$msg_logo_desc'
+ 'a $msg_rain' '$msg_rain_desc'
+ 'b $msg_snake' '$msg_snake_desc'
+ 'c $msg_star' '$msg_star_desc'
+ 'd $msg_warp' '$msg_warp_desc'
+ '$msg_timeout' '$msg_timeout_desc'
+ " # END-QUOTE
+ local defaultitem= # Calculated below
+ local hline="$hline_choose_a_screen_saver"
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ case "$( f_sysrc_get saver )" in
+ [Nn][Oo]|'') defaultitem="1 $msg_none" ;;
+ blank) defaultitem="2 $msg_blank" ;;
+ beastie) defaultitem="3 $msg_beastie" ;;
+ daemon) defaultitem="4 $msg_daemon" ;;
+ dragon) defaultitem="5 $msg_dragon" ;;
+ fade) defaultitem="6 $msg_fade" ;;
+ fire) defaultitem="7 $msg_fire" ;;
+ green) defaultitem="8 $msg_green" ;;
+ logo) defaultitem="9 $msg_logo" ;;
+ rain) defaultitem="a $msg_rain" ;;
+ snake) defaultitem="b $msg_snake" ;;
+ star) defaultitem="c $msg_star" ;;
+ warp) defaultitem="d $msg_warp" ;;
+ esac
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_menutag_store -s "$menu_choice"
+ return $retval
+}
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_system_console_screen_saver"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Launch application main menu
+#
+dialog_menu_main || f_die
+f_dialog_menutag_fetch mtag
+
+case "$mtag" in
+"$msg_timeout") # Set the screen saver timeout interval
+ f_dialog_title "$msg_value_required"
+ f_dialog_input blanktime "$msg_enter_timeout_period" \
+ "$( f_sysrc_get blanktime )" &&
+ f_eval_catch "$0" f_sysrc_set \
+ 'f_sysrc_set blanktime "%s"' "$blanktime" || f_die
+ f_dialog_title_restore
+ exit $SUCCESS
+esac
+
+saver_to_set=
+case "$mtag" in
+"1 $msg_none") # Disable the screensaver
+ saver_to_set="NO" ;;
+"2 $msg_blank") # Simply blank the screen
+ saver_to_set="blank" ;;
+"3 $msg_beastie") # "BSD Daemon" animated screen saver (graphics)
+ saver_to_set="beastie" ;;
+"4 $msg_daemon") # "BSD Daemon" animated screen saver (text)
+ saver_to_set="daemon" ;;
+"5 $msg_dragon") # Dragon screensaver (graphics)
+ saver_to_set="dragon" ;;
+"6 $msg_fade") # Fade out effect screen saver
+ saver_to_set="fade" ;;
+"7 $msg_fire") # Flames effect screen saver
+ saver_to_set="fire" ;;
+"8 $msg_green") # "Green" power saving mode (if supported by monitor)
+ saver_to_set="green" ;;
+"9 $msg_logo") # FreeBSD "logo" animated screen saver (graphics)
+ saver_to_set="logo" ;;
+"a $msg_rain") # Rain drops screen saver
+ saver_to_set="rain" ;;
+"b $msg_snake") # Draw a FreeBSD "snake" on your screen
+ saver_to_set="snake" ;;
+"c $msg_star") # A "twinkling stars" effect
+ saver_to_set="star" ;;
+"d $msg_warp") # A "stars warping" effect
+ saver_to_set="warp" ;;
+esac
+
+if [ "$saver_to_set" ]; then
+ f_eval_catch "$0" f_sysrc_set \
+ 'f_sysrc_set saver "%s"' "$saver_to_set" || f_die
+ break
+else
+ f_die 1 "$msg_unknown_saver"
+fi
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/console/screenmap b/usr.sbin/bsdconfig/console/screenmap
new file mode 100755
index 0000000..5bf5bd8
--- /dev/null
+++ b/usr.sbin/bsdconfig/console/screenmap
@@ -0,0 +1,155 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="080.console"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ FUNCTIONS
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local prompt="$msg_screenmap_menu_text"
+ local menu_list="
+ '1 $msg_none' '$msg_none_screenmap_desc'
+ '2 $msg_iso_8859_1_to_ibm437' '$msg_iso_8859_1_to_ibm437_desc'
+ '3 $msg_iso_8859_7_to_ibm437' '$msg_iso_8859_7_to_ibm437_desc'
+ '4 $msg_us_ascii_to_ibm327' '$msg_us_ascii_to_ibm327_desc'
+ '5 $msg_koi8_r_to_ibm866' '$msg_koi8_r_to_ibm866_desc'
+ '6 $msg_koi8_u_to_ibm866u' '$msg_koi8_u_to_ibm866u_desc'
+ " # END-QUOTE
+ local defaultitem= # Calculated below
+ local hline="$hline_choose_a_screen_map"
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ case "$( f_sysrc_get scrnmap )" in
+ [Nn][Oo]|'') defaultitem="1 $msg_none" ;;
+ iso-8859-1_to_cp437) defaultitem="2 $msg_iso_8859_1_to_ibm437" ;;
+ iso-8859-7_to_cp437) defaultitem="3 $msg_iso_8859_7_to_ibm437" ;;
+ us-ascii_to_cp437) defaultitem="4 $msg_us_ascii_to_ibm327" ;;
+ koi8-r2cp866) defaultitem="5 $msg_koi8_r_to_ibm866" ;;
+ koi8-u2cp866u) defaultitem="6 $msg_koi8_u_to_ibm866u" ;;
+ esac
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_menutag_store -s "$menu_choice"
+ return $retval
+}
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_system_console_screenmap"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Launch application main menu
+#
+dialog_menu_main || f_die
+f_dialog_menutag_fetch mtag
+
+scrnmap_to_set=
+case "$mtag" in
+"1 $msg_none") # No screenmap, don't touch font
+ scrnmap_to_set="NO" ;;
+"2 $msg_iso_8859_1_to_ibm437") # W-Europe ISO 8859-1 to IBM 437 scrnmap
+ scrnmap_to_set="iso-8859-1_to_cp437" ;;
+"3 $msg_iso_8859_7_to_ibm437") # Greek ISO 8859-7 to IBM 437 screenmap
+ scrnmap_to_set="iso-8859-7_to_cp437" ;;
+"4 $msg_us_ascii_to_ibm327") # US-ASCII to IBM 437 screenmap
+ scrnmap_to_set="us-ascii_to_cp437" ;;
+"5 $msg_koi8_r_to_ibm866") # Russian KOI8-R to IBM 866 screenmap
+ scrnmap_to_set="koi8-r2cp866" ;;
+"6 $msg_koi8_u_to_ibm866u") # Ukrainian KOI8-U to IBM 866u screenmap
+ scrnmap_to_set="koi8-u2cp866u" ;;
+esac
+
+if [ "$scrnmap_to_set" ]; then
+ f_eval_catch "$0" f_sysrc_set \
+ 'f_sysrc_set scrnmap "%s"' "$scrnmap_to_set" || f_die
+ break
+else
+ f_die 1 "$msg_unknown_screenmap_selection"
+fi
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/console/ttys b/usr.sbin/bsdconfig/console/ttys
new file mode 100755
index 0000000..54dcb66
--- /dev/null
+++ b/usr.sbin/bsdconfig/console/ttys
@@ -0,0 +1,207 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="080.console"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ CONFIGURATION
+
+#
+# Location of ttys(5)
+#
+ETC_TTYS=/etc/ttys
+
+############################################################ FUNCTIONS
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local prompt="$msg_ttys_menu_text"
+ local menu_list="
+ '1 $msg_none' '$msg_none_ttys_desc'
+ '2 $msg_ibm_437_vga_default' 'cons25'
+ '3 $msg_iso_8859_1' 'cons25l1'
+ '4 $msg_iso_8859_2' 'cons25l2'
+ '5 $msg_iso_8859_7' 'cons25l7'
+ '6 $msg_koi8_r' 'cons25r'
+ '7 $msg_koi8_u' 'cons25u'
+ '8 $msg_us_ascii' 'cons25w'
+ " # END-QUOTE
+ local hline="$hline_choose_a_terminal_type"
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_menutag_store -s "$menu_choice"
+
+ if [ $retval -eq $DIALOG_OK ]; then
+ local item
+ item=$( eval f_dialog_menutag2item \
+ \"\$menu_choice\" $menu_list )
+ f_dialog_menuitem_store "$item"
+ fi
+
+ return $retval
+}
+
+# ttys_set_type $consterm
+#
+# Set terminal type of `ttyv*' and `cons[0-9]' entries in ttys(5) to $consterm.
+#
+ttys_set_type()
+{
+ local funcname=ttys_set_type
+ local consterm="$1" err
+
+ #
+ # Create new temporary file to write our ttys(5) update with new types.
+ #
+ local tmpfile
+ f_eval_catch -k tmpfile $funcname mktemp 'mktemp -t "%s"' "$pgm" ||
+ return $FAILURE
+
+ #
+ # Fixup permissions and ownership (mktemp(1) creates the temporary file
+ # with 0600 permissions -- change the permissions and ownership to
+ # match ttys(5) before we write it out and mv(1) it into place).
+ #
+ local mode owner
+ f_eval_catch -dk mode $funcname stat \
+ 'stat -f "%%#Lp" "%s"' "$ETC_TTYS" || mode=0644
+ f_eval_catch -dk owner $funcname stat \
+ 'stat -f "%%u:%%g" "%s"' "$ETC_TTYS" || owner="root:wheel"
+ f_eval_catch -d $funcname chmod 'chmod "%s" "%s"' "$mode" "$tmpfile"
+ f_eval_catch -d $funcname chown 'chown "%s" "%s"' "$owner" "$tmpfile"
+
+ #
+ # Operate on ttys(5), replacing only the types of `ttyv*' and
+ # `cons[0-9]' terminals with the new type.
+ #
+ if ! err=$( awk -v consterm="$consterm" '
+ BEGIN {
+ }
+ {
+ # "Skip" blank-lines, lines containing only whitespace, and
+ # lines containing only a comment or whitespace-then-comment.
+ #
+ if ( $0 ~ /^[[:space:]]*(#|$)/ ) { print; next }
+
+ # "Skip" terminal types other than those supported
+ #
+ if ( $1 !~ /^(ttyv.*|cons[0-9])$/ ) { print; next }
+
+ # Change the terminal type to the new value
+ #
+ match($0, /[[:alnum:]\.\+-_]+[[:space:]]+(on|off).*$/)
+ if ( ! RSTART ) { print; next }
+ left = substr($0, 0, RSTART - 1)
+ match($0, /[[:space:]]+(on|off).*$/)
+ right = substr($0, RSTART)
+ printf "%s%s%s\n", left, consterm, right
+ }
+ ' "$ETC_TTYS" > "$tmpfile" 2>&1 ); then
+ f_dialog_msgbox "$err"
+ return $FAILURE
+ fi
+ f_eval_catch $funcname mv 'mv -f "%s" "%s"' "$tmpfile" "$ETC_TTYS" ||
+ return $FAILURE
+
+ return $SUCCESS
+}
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_system_console_terminal_type"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Launch application main menu
+#
+dialog_menu_main || f_die
+f_dialog_menutag_fetch mtag
+
+[ "$mtag" = "1 $msg_none" ] && exit $SUCCESS
+
+f_dialog_menuitem_fetch consterm
+ttys_set_type "$consterm" || f_die
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/diskmgmt/INDEX b/usr.sbin/bsdconfig/diskmgmt/INDEX
new file mode 100644
index 0000000..e04bb79
--- /dev/null
+++ b/usr.sbin/bsdconfig/diskmgmt/INDEX
@@ -0,0 +1,57 @@
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+#
+# Title that will be shown in the bsdconfig menu.
+#
+menu_title="Disk Management"
+
+#
+# A short descriptive line shown at the bottom of the bsdconfig menu. keep it
+# short because any line longer than the terminal width will be truncated.
+#
+menu_help="Manage disk partitions and/or labels"
+
+#
+# Two-part variable that defines an action to take when `keyword' is passed on
+# a bsdconfig command line. Variable takes the form "keyword|command" and
+# multiple occurrences of the variable (with different `keyword's, or different
+# `keyword's AND `command's) are allowed. If `command' begins with a '/' then
+# the full path to the program is needed. If `command' begins with anything
+# else it is a path relative to the directory this INDEX file is in. `keyword'
+# can be i18n'ed but `command' is the name of a script.
+#
+menu_selection="diskmgmt|diskmgmt"
+
+#
+# ------------ Items below this line do NOT need i18n translation ------------
+#
+# Name of the program to be run when this menu choice is selected. If it begins
+# with a '/' then the full path to the program is needed. If it begins with
+# anything else it is a path relative to the directory this INDEX file is in.
+#
+menu_program="diskmgmt"
diff --git a/usr.sbin/bsdconfig/diskmgmt/Makefile b/usr.sbin/bsdconfig/diskmgmt/Makefile
new file mode 100644
index 0000000..16a8900
--- /dev/null
+++ b/usr.sbin/bsdconfig/diskmgmt/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+SUBDIR= include
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/050.diskmgmt
+FILES= INDEX USAGE
+
+SCRIPTSDIR= ${FILESDIR}
+SCRIPTS= diskmgmt
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/diskmgmt/Makefile.depend b/usr.sbin/bsdconfig/diskmgmt/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/diskmgmt/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/diskmgmt/USAGE b/usr.sbin/bsdconfig/diskmgmt/USAGE
new file mode 100644
index 0000000..a06d8af
--- /dev/null
+++ b/usr.sbin/bsdconfig/diskmgmt/USAGE
@@ -0,0 +1,37 @@
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+Usage: bsdconfig @PROGRAM_NAME@ [OPTIONS]
+
+OPTIONS:
+ -d Provide lots of debugging info on standard-out when running.
+ -D file Send debugging info to file. If file begins with a plus-sign
+ debug info is sent to both standard-out and file (minus the
+ leading plus).
+ -h Print this usage statement and exit.
+ -S Secure X11 mode (implies `-X'). As root, always prompt-for
+ and validate sudo(8) username/password before starting.
+ -X Use Xdialog(1) in place of dialog(1).
diff --git a/usr.sbin/bsdconfig/diskmgmt/diskmgmt b/usr.sbin/bsdconfig/diskmgmt/diskmgmt
new file mode 100755
index 0000000..ec567f5
--- /dev/null
+++ b/usr.sbin/bsdconfig/diskmgmt/diskmgmt
@@ -0,0 +1,85 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="050.diskmgmt"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ CONFIGURATION
+
+#
+# If X11 is requested, which terminal and what options should we use?
+#
+X11TERM=xterm
+X11TERM_OPTS=
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_mustberoot_init
+
+#
+# If Xdialog(1) is requested, we'll need to wrap bsdinstall(8) into xterm(1)
+#
+if [ "$USE_XDIALOG" ]; then
+ f_have "$X11TERM" || f_die 1 \
+ "$msg_no_such_file_or_directory" "$pgm" "$X11TERM"
+
+ exec $X11TERM $X11TERM_OPTS -e /usr/sbin/bsdinstall partedit
+else
+ exec /usr/sbin/bsdinstall partedit
+fi
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/diskmgmt/include/Makefile b/usr.sbin/bsdconfig/diskmgmt/include/Makefile
new file mode 100644
index 0000000..c927153
--- /dev/null
+++ b/usr.sbin/bsdconfig/diskmgmt/include/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/050.diskmgmt/include
+FILES= messages.subr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/diskmgmt/include/Makefile.depend b/usr.sbin/bsdconfig/diskmgmt/include/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/diskmgmt/include/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/diskmgmt/include/messages.subr b/usr.sbin/bsdconfig/diskmgmt/include/messages.subr
new file mode 100644
index 0000000..f0b563f
--- /dev/null
+++ b/usr.sbin/bsdconfig/diskmgmt/include/messages.subr
@@ -0,0 +1,27 @@
+# Copyright (c) 2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+msg_no_such_file_or_directory="%s: %s: No such file or directory"
diff --git a/usr.sbin/bsdconfig/docsinstall/INDEX b/usr.sbin/bsdconfig/docsinstall/INDEX
new file mode 100644
index 0000000..6308f30
--- /dev/null
+++ b/usr.sbin/bsdconfig/docsinstall/INDEX
@@ -0,0 +1,57 @@
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+#
+# Title that will be shown in the bsdconfig menu.
+#
+menu_title="Documentation installation"
+
+#
+# A short descriptive line shown at the bottom of the bsdconfig menu. keep it
+# short because any line longer than the terminal width will be truncated.
+#
+menu_help="Install FreeBSD Documentation set"
+
+#
+# Two-part variable that defines an action to take when `keyword' is passed on
+# a bsdconfig command line. Variable takes the form "keyword|command" and
+# multiple occurrences of the variable (with different `keyword's, or different
+# `keyword's AND `command's) are allowed. If `command' begins with a '/' then
+# the full path to the program is needed. If `command' begins with anything
+# else it is a path relative to the directory this INDEX file is in. `keyword'
+# can be i18n'ed but `command' is the name of a script.
+#
+menu_selection="docsinstall|docsinstall"
+
+#
+# ------------ Items below this line do NOT need i18n translation ------------
+#
+# Name of the program to be run when this menu choice is selected. If it begins
+# with a '/' then the full path to the program is needed. If it begins with
+# anything else it is a path relative to the directory this INDEX file is in.
+#
+menu_program="docsinstall"
diff --git a/usr.sbin/bsdconfig/docsinstall/Makefile b/usr.sbin/bsdconfig/docsinstall/Makefile
new file mode 100644
index 0000000..2ab3a68
--- /dev/null
+++ b/usr.sbin/bsdconfig/docsinstall/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+SUBDIR= include
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/020.docsinstall
+FILES= INDEX USAGE
+
+SCRIPTSDIR= ${FILESDIR}
+SCRIPTS= docsinstall
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/docsinstall/Makefile.depend b/usr.sbin/bsdconfig/docsinstall/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/docsinstall/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/docsinstall/USAGE b/usr.sbin/bsdconfig/docsinstall/USAGE
new file mode 100644
index 0000000..a06d8af
--- /dev/null
+++ b/usr.sbin/bsdconfig/docsinstall/USAGE
@@ -0,0 +1,37 @@
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+Usage: bsdconfig @PROGRAM_NAME@ [OPTIONS]
+
+OPTIONS:
+ -d Provide lots of debugging info on standard-out when running.
+ -D file Send debugging info to file. If file begins with a plus-sign
+ debug info is sent to both standard-out and file (minus the
+ leading plus).
+ -h Print this usage statement and exit.
+ -S Secure X11 mode (implies `-X'). As root, always prompt-for
+ and validate sudo(8) username/password before starting.
+ -X Use Xdialog(1) in place of dialog(1).
diff --git a/usr.sbin/bsdconfig/docsinstall/docsinstall b/usr.sbin/bsdconfig/docsinstall/docsinstall
new file mode 100755
index 0000000..19128fe
--- /dev/null
+++ b/usr.sbin/bsdconfig/docsinstall/docsinstall
@@ -0,0 +1,97 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="020.docsinstall"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ CONFIGURATION
+
+#
+# If X11 is requested, which terminal and what options should we use?
+#
+X11TERM=xterm
+X11TERM_OPTS=
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_mustberoot_init
+
+#
+# If Xdialog(1) is requested, we'll need to wrap bsdinstall(8) into xterm(1)
+#
+if [ "$USE_XDIALOG" ]; then
+ #
+ # Make sure $X11TERM exists and is executable
+ #
+ case "$X11TERM" in
+ */*)
+ [ -e "$X11TERM" ] || f_die 1 \
+ "$msg_no_such_file_or_directory" "$pgm" "$X11TERM"
+ [ -x "$X11TERM" ] || f_die 1 \
+ "$msg_permission_denied" "$pgm" "$X11TERM"
+ ;;
+ *)
+ f_have "$X11TERM" || f_die 1 \
+ "$msg_no_such_file_or_directory" "$pgm" "$X11TERM"
+ esac
+
+ exec $X11TERM $X11TERM_OPTS -e /usr/sbin/bsdinstall docsinstall
+else
+ exec /usr/sbin/bsdinstall docsinstall
+fi
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/docsinstall/include/Makefile b/usr.sbin/bsdconfig/docsinstall/include/Makefile
new file mode 100644
index 0000000..a3ca529
--- /dev/null
+++ b/usr.sbin/bsdconfig/docsinstall/include/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/020.docsinstall/include
+FILES= messages.subr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/docsinstall/include/Makefile.depend b/usr.sbin/bsdconfig/docsinstall/include/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/docsinstall/include/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/docsinstall/include/messages.subr b/usr.sbin/bsdconfig/docsinstall/include/messages.subr
new file mode 100644
index 0000000..4c55f0f
--- /dev/null
+++ b/usr.sbin/bsdconfig/docsinstall/include/messages.subr
@@ -0,0 +1,28 @@
+# Copyright (c) 2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+msg_no_such_file_or_directory="%s: %s: No such file or directory"
+msg_permission_denied="%s: %s: permission denied"
diff --git a/usr.sbin/bsdconfig/dot/INDEX b/usr.sbin/bsdconfig/dot/INDEX
new file mode 100644
index 0000000..fa641e0
--- /dev/null
+++ b/usr.sbin/bsdconfig/dot/INDEX
@@ -0,0 +1,57 @@
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+#
+# Title that will be shown in the bsdconfig menu.
+#
+menu_title=""
+
+#
+# A short descriptive line shown at the bottom of the bsdconfig menu. keep it
+# short because any line longer than the terminal width will be truncated.
+#
+menu_help=""
+
+#
+# Two-part variable that defines an action to take when `keyword' is passed on
+# a bsdconfig command line. Variable takes the form "keyword|command" and
+# multiple occurrences of the variable (with different `keyword's, or different
+# `keyword's AND `command's) are allowed. If `command' begins with a '/' then
+# the full path to the program is needed. If `command' begins with anything
+# else it is a path relative to the directory this INDEX file is in. `keyword'
+# can be i18n'ed but `command' is the name of a script.
+#
+menu_selection="dot|dot"
+
+#
+# ------------ Items below this line do NOT need i18n translation ------------
+#
+# Name of the program to be run when this menu choice is selected. If it begins
+# with a '/' then the full path to the program is needed. If it begins with
+# anything else it is a path relative to the directory this INDEX file is in.
+#
+menu_program=""
diff --git a/usr.sbin/bsdconfig/dot/Makefile b/usr.sbin/bsdconfig/dot/Makefile
new file mode 100644
index 0000000..ccd10b4
--- /dev/null
+++ b/usr.sbin/bsdconfig/dot/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+SUBDIR= include
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/dot
+FILES= INDEX USAGE
+
+SCRIPTSDIR= ${FILESDIR}
+SCRIPTS= dot
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/dot/Makefile.depend b/usr.sbin/bsdconfig/dot/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/dot/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/dot/USAGE b/usr.sbin/bsdconfig/dot/USAGE
new file mode 100644
index 0000000..5bc38cc
--- /dev/null
+++ b/usr.sbin/bsdconfig/dot/USAGE
@@ -0,0 +1,143 @@
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+Usage: bsdconfig @PROGRAM_NAME@ [OPTIONS]
+
+OPTIONS:
+ -h Print this usage statement and exit.
+ -c Don't show command-line shortcut relationships.
+ -d Don't show the date in the graph label.
+ -i Don't show include relationships.
+
+EXAMPLES:
+ View dot(1) language output describing bsdconfig(8) layout/make-up:
+
+ bsdconfig @PROGRAM_NAME@ | less
+
+ Render dot(1) output in SVG format (displays in most modern browsers):
+
+ bsdconfig @PROGRAM_NAME@ | dot -Tsvg -o bsdconfig.svg
+
+ NOTE: Requires `graphics/graphviz' from ports/packages.
+
+ View the above-rendered SVG file using your favorite X11-based viewer:
+
+ gimmage bsdconfig.svg
+
+ NOTE: Requires `graphics/gimmage' from ports/packages.
+
+ or
+
+ gthumb bsdconfig.svg
+
+ NOTE: Image is scaled to fit window on launch.
+ NOTE: Requires `graphics/gthumb' from ports/packages.
+
+ or
+
+ gqview bsdconfig.svg
+
+ NOTE: Requires `graphics/gqview' from ports/packages.
+
+ or
+
+ gx bsdconfig.svg
+
+ NOTE: Image is scaled to fit window on launch.
+ NOTE: Requires `graphics/gx' from ports/packages.
+
+ or
+
+ eog bsdconfig.svg
+
+ NOTE: Requires `graphics/eog' from ports/packages.
+
+ Render dot(1) output as PostScript print output consisting of multiple
+ US-Letter sized pages that can be assembled into a large poster (using
+ traditional tools such as scissors and tape):
+
+ bsdconfig @PROGRAM_NAME@ | dot -Teps -o bsdconfig.eps
+ poster -v -mLet -s1 -o bsdconfig.ps bsdconfig.eps
+
+ NOTE: Change "-s1" above to "-s0.5" to halve the size of the
+ poster or "-s2", for example, to double the poster size.
+
+ NOTE: Requires both `graphics/graphviz' and `print/poster' from
+ ports/packages.
+
+ Render dot(1) output as PostScript scaled to fit on a poster consisting
+ of 2x-wide and 4x-tall US-Letter sized pages:
+
+ bsdconfig @PROGRAM_NAME@ | dot -Teps -o bsdconfig.eps
+ poster -v -mLet -p2x4Letter -o bsdconfig.ps bsdconfig.eps
+
+ NOTE: Requires both `graphics/graphviz' and `print/poster' from
+ ports/packages.
+
+ View the above-rendered PostScript poster using X11:
+
+ gsview bsdconfig.ps
+
+ NOTE: Requires `print/gsview' from ports/packages.
+
+ or
+
+ convert bsdconfig.ps bsdconfig.pdf
+ xpdf bsdconfig.pdf
+
+ NOTE: Requires both `graphics/ImageMagick' and
+ `graphics/xdpf' from ports/packages.
+
+ NOTE: The converted PDF file is not suitable for
+ printing due to loss of quality during the
+ conversion process.
+
+ Print the above-rendered PostScript poster:
+
+ lpr -h bsdconfig.ps
+
+ NOTE: Requires configuration of a printer in `/etc/printcap'.
+
+ Extract each page of the poster into a separate PNG file:
+
+ gs -q -dNOPAUSE -dBATCH -sPAPERSIZE=letter \
+ -dTextAlphaBits=4 -dGraphicsAlphaBits=4 \
+ -sDEVICE=png16m -sOutputFile=bsdconfig%03d.png \
+ bsdconfig.ps
+
+ NOTE: Requires `print/ghostscript9' from ports/packages.
+
+ NOTE: The converted PNG files are not suitable for printing
+ due to loss of quality during the conversion process.
+
+ Extract a single page of the poster into a separate PostScript file for
+ printing individual pages from the command-line:
+
+ psselect 1 bsdconfig.ps bsdconfig-page1.ps
+ lpr -h bsdconfig-page1.ps
+
+ NOTE: Change "1" to "2" for the second page, ad-infinitum.
+ NOTE: Requires `print/psutils-letter' from ports/packages.
diff --git a/usr.sbin/bsdconfig/dot/dot b/usr.sbin/bsdconfig/dot/dot
new file mode 100755
index 0000000..f71c0e8
--- /dev/null
+++ b/usr.sbin/bsdconfig/dot/dot
@@ -0,0 +1,678 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+# Prevent common.subr from auto initializing debugging (this is not an inter-
+# active utility so does not require debugging; also `-d' has been repurposed).
+#
+DEBUG_SELF_INITIALIZE=NO
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="dot"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ CONFIGURATION
+
+#
+# Location of bsdconfig(8)
+#
+BSDCONFIG=/usr/sbin/bsdconfig
+
+############################################################ GLOBALS
+
+#
+# Options
+#
+SHOW_GRAPH_LABEL_DATE=1
+SHOW_INCLUDES=1
+SHOW_CMDLINE=1
+
+############################################################ FUNCTIONS
+
+# begin_nodelist $shape $color $fillcolor $style
+#
+# Create a new multi-node list rendering nodes in a specific style described by
+# the arguments passed.
+#
+begin_nodelist()
+{
+ local shape="$1" color="$2" fillcolor="$3" style="$4"
+
+ printf "\tnode [\n"
+ [ "$shape" ] &&
+ printf '\t\tshape = "%s",\n' "$shape"
+ [ "$color" ] &&
+ printf '\t\tcolor = "%s",\n' "$color"
+ [ "$fillcolor" ] &&
+ printf '\t\tfillcolor = "%s",\n' "$fillcolor"
+ [ "$style" ] &&
+ printf '\t\tstyle = "%s",\n' "$style"
+ printf "\t] {\n"
+}
+
+# print_node $node [$attributes ...]
+#
+# Print a node within a multi-node list.
+#
+print_node()
+{
+ local node="$1"
+
+ shift 1 # node
+
+ case "$node" in
+ edge) printf '\t\t%s' "$node" ;;
+ *) printf '\t\t"%s"' "$node" ;;
+ esac
+
+ if [ $# -gt 0 ]; then
+ echo -n ' ['
+ while [ $# -gt 0 ]; do
+ printf " %s" "$1"
+ shift 1
+ [ $# -gt 0 ] && echo -n ","
+ done
+ echo -n " ]"
+ fi
+
+ echo ";"
+}
+
+# print_node2 $node $node [$attributes ...]
+#
+# Print a directed node-node connection within a multi-node list.
+#
+print_node2()
+{
+ local node1="$1" node2="$2"
+
+ shift 2 # node1 node2
+
+ printf '\t\t"%s" -> "%s"' "$node1" "$node2"
+
+ if [ $# -gt 0 ]; then
+ echo -n ' ['
+ while [ $# -gt 0 ]; do
+ printf " %s" "$1"
+ shift 1
+ [ $# -gt 0 ] && echo -n ","
+ done
+ echo -n " ]"
+ fi
+
+ echo ";"
+}
+
+# end_nodelist
+#
+# Close a multi-node list.
+#
+end_nodelist()
+{
+ printf "\t};\n"
+}
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts cdhi flag; do
+ case "$flag" in
+ i) SHOW_INCLUDES= ;;
+ d) SHOW_GRAPH_LABEL_DATE= ;;
+ c) SHOW_CMDLINE= ;;
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+cd $BSDCFG_LIBE || f_die # Pedantic
+
+#
+# Get a list of menu programs
+#
+menu_program_list=
+for file in [0-9][0-9][0-9].*/INDEX; do
+ menu_program_list="$menu_program_list $(
+ tail -r "$file" | awk -v item="${file%%/*}" '
+ /^[[:space:]]*menu_program="/ {
+ sub(/^.*="/, "")
+ sub(/"$/, "")
+ if ( ! $0 ) next
+ if ( $0 !~ "^/" ) sub(/^/, item "/")
+ print; exit
+ }'
+ )"
+done
+
+#
+# Get a list of submenu programs
+#
+submenu_program_list=
+for menu_program in $menu_program_list; do
+ case "$menu_program" in
+ [0-9][0-9][0-9].*/*) : fall-through ;;
+ *) continue # No sub-menus we can process
+ esac
+
+ submenu_program_list="$submenu_program_list $(
+ awk -v menu_program="$menu_program" \
+ -v item="${menu_program%%/*}" \
+ '
+ /^menu_selection="/ {
+ sub(/.*\|/, "")
+ sub(/"$/, "")
+ if ( ! $0 ) next
+ if ( $0 !~ "^/" )
+ sub(/^/, item "/")
+ if ( $0 == menu_program ) next
+ print
+ }
+ ' "${menu_program%%/*}/INDEX"
+ )"
+done
+
+#
+# Get a list of command-line programs
+#
+cmd_program_list=
+for file in */INDEX; do
+ cmd_program_list="$cmd_program_list $(
+ awk -v item="${file%%/*}" '
+ /^menu_selection="/ {
+ sub(/.*\|/, "")
+ sub(/"$/, "")
+
+ if ( ! $0 ) next
+
+ if ( $0 !~ "^/" )
+ sub(/^/, item "/")
+
+ print
+ }
+ ' $file
+ )"
+done
+
+#
+# [Optionally] Calculate list of include files
+#
+if [ "$SHOW_INCLUDES" ]; then
+ print_includes_awk='
+ BEGIN { regex = "^f_include \\$BSDCFG_SHARE/" }
+ ( $0 ~ regex ) { sub(regex, ""); print }
+ ' # END-QUOTE
+
+ #
+ # Build list of files in which to search for includes
+ #
+ file_list=$(
+ for file in \
+ $BSDCONFIG \
+ $menu_program_list \
+ $submenu_program_list \
+ $cmd_program_list \
+ $BSDCFG_SHARE/script.subr \
+ ; do
+ [ -e "$file" ] && echo $file
+ done | sort -u
+ )
+
+ #
+ # Build list of includes used by the above files
+ #
+ include_file_list=
+ for file in $file_list; do
+ include_file_list="$include_file_list $(
+ awk "$print_includes_awk" $file
+ )"
+ done
+
+ #
+ # Sort the list of includes and remove duplicate entries
+ #
+ include_file_list=$(
+ for include_file in $include_file_list; do
+ echo "$include_file"
+ done | sort -u
+ )
+
+ #
+ # Search previously-discovered include files for further includes
+ #
+ before="$include_file_list"
+ while :; do
+ for file in $include_file_list; do
+ include_file_list="$include_file_list $(
+ awk "$print_includes_awk" $BSDCFG_SHARE/$file
+ )"
+ done
+
+ #
+ # Sort list of includes and remove duplicate entries [again]
+ #
+ include_file_list=$(
+ for include_file in $include_file_list; do
+ echo "$include_file"
+ done | sort -u
+ )
+
+ [ "$include_file_list" = "$before" ] && break
+ before="$include_file_list"
+ done
+fi
+
+#
+# Start the directional-graph (digraph) output
+#
+printf 'strict digraph "" { // Empty name to prevent SVG Auto-Tooltip\n'
+label_format="$msg_graph_label_with_command"
+[ "$SHOW_GRAPH_LABEL_DATE" ] &&
+ label_format="$msg_graph_label_with_command_and_date"
+lang="${LANG:-$LC_ALL}"
+printf "\n\tlabel = \"$label_format\"\n" \
+ "${lang:+LANG=${lang%%[$IFS]*} }bsdconfig $pgm${ARGV:+ $ARGV}" \
+ "$( date +"%c %Z" )"
+
+#
+# Print graph-specific properties
+#
+printf '\n\t/*\n\t * Graph setup and orientation\n\t */\n'
+printf '\tlabelloc = top;\t\t// display above label at top of graph\n'
+printf '\trankdir = LR;\t\t// create ranks left-to-right\n'
+printf '\torientation = portrait;\t// default\n'
+printf '\tratio = fill;\t\t// approximate aspect ratio\n'
+printf '\tcenter = 1;\t\t// center drawing on page\n'
+
+#
+# Perform edge-concentration when displaying a lot of information
+#
+# NOTE: This is disabled because dot(1) version 2.28.0 (current) and older have
+# a bug that causes a crash when rankdir = LR and concentrate = true
+#
+# NOTE: Do not re-enable until said bug is fixed in some future revision.
+#
+#[ "$SHOW_INCLUDES" -a "$SHOW_CMDLINE" ] &&
+# printf '\tconcentrate = true;\t// enable edge concentrators\n'
+
+#
+# Print font details for graph/cluster label(s)
+#
+printf '\n\t/*\n\t * Font details for graph/cluster label(s)\n\t */\n'
+printf '\tfontname = "Times-Italic";\n'
+printf '\tfontsize = 14;\n'
+
+#
+# Print default node attributes
+#
+printf '\n\t/*\n\t * Default node attributes\n\t */\n'
+printf '\tnode [\n'
+printf '\t\tfontname = "Times-Roman",\n'
+printf '\t\tfontsize = 12,\n'
+printf '\t\twidth = 2.5, // arbitrary minimum width for all nodes\n'
+printf '\t\tfixedsize = true, // turn minimum width into exact width\n'
+printf '\t];\n'
+
+#
+# Print top-level item(s)
+#
+printf '\n\t/*\n\t * bsdconfig(8)\n\t */\n'
+shape=circle color=black fillcolor=yellow style=filled
+begin_nodelist "$shape" "$color" "$fillcolor" "$style"
+print_node "bsdconfig" "fontname = \"Times-Bold\"" "fontsize = 16"
+end_nodelist
+
+#
+# Print menus
+#
+printf '\n\t/*\n\t * Menu items\n\t */\n'
+shape=box color=black fillcolor=lightblue style=filled
+begin_nodelist "$shape" "$color" "$fillcolor" "$style"
+for menu_program in $menu_program_list; do
+ print_node "$menu_program" "label = \"${menu_program#*/}\""
+done
+end_nodelist
+
+#
+# Print sub-menus
+#
+printf '\n\t/*\n\t * Sub-menu items\n\t */\n'
+shape=box color=black fillcolor=lightblue style=filled
+begin_nodelist "$shape" "$color" "$fillcolor" "$style"
+for submenu_program in $submenu_program_list; do
+ print_node "$submenu_program" "label = \"${submenu_program#*/}\""
+done
+end_nodelist
+
+#
+# Print menu relationships
+#
+printf '\n\t/*\n\t * Menu item relationships\n\t */\n'
+shape=box color=black fillcolor=lightblue style=filled edge_color=blue
+begin_nodelist "$shape" "$color" "$fillcolor" "$style"
+print_node edge "penwidth = 5.0" "style = bold" "color = $edge_color"
+for menu_program in $menu_program_list; do
+ print_node2 "bsdconfig" "$menu_program"
+done
+end_nodelist
+
+#
+# Print sub-menu relationships
+#
+printf '\n\t/*\n\t * Sub-menu item relationships\n\t */\n'
+shape=box color=black fillcolor=lightblue style=filled edge_color=blue
+begin_nodelist "$shape" "$color" "$fillcolor" "$style"
+# Lock sub-menu headport to the West (unless `-c' was passed)
+[ "$SHOW_CMDLINE" -o ! "$SHOW_INCLUDES" ] && print_node edge "headport = w"
+print_node edge "style = bold" "color = $edge_color"
+for submenu_program in $submenu_program_list; do
+ for menu_program in $menu_program_list; do
+ case "$menu_program" in
+ [0-9][0-9][0-9].*/*) : fall-through ;;
+ *) continue # Not a menu item
+ esac
+
+ # Continue if program directories do not match
+ [ "${menu_program%%/*}" = "${submenu_program%%/*}" ] ||
+ continue
+
+ print_node2 "$menu_program" "$submenu_program"
+ break
+ done
+done
+end_nodelist
+
+#
+# [Optionally] Print include files
+#
+if [ "$SHOW_INCLUDES" ]; then
+ printf '\n\t/*\n\t * Include files\n\t */\n'
+ shape=oval color=black fillcolor=white style=filled
+ begin_nodelist "$shape" "$color" "$fillcolor" "$style"
+ printf '\t\tconstraint = false;\n'
+ for include_file in $include_file_list; do
+ print_node "$include_file" \
+ "label = \"${include_file##*/}\""
+ done
+ end_nodelist
+fi
+
+#
+# [Optionally] Print f_include() usage/relationships
+#
+if [ "$SHOW_INCLUDES" ]; then
+ printf '\n\t/*\n\t * Include usage\n\t */\n'
+ shape=oval color=black fillcolor=white style=filled edge_color=grey
+ begin_nodelist "$shape" "$color" "$fillcolor" "$style"
+ print_node edge "style = dashed" "color = $edge_color"
+ #print_node edge "label = \"\\T\"" "fontsize = 9"
+ # NOTE: Edge labels are buggy on large graphs
+ file_list=$(
+ for file in \
+ $BSDCONFIG \
+ $menu_program_list \
+ $submenu_program_list \
+ $cmd_program_list \
+ $include_file_list \
+ ; do
+ [ -f "$BSDCFG_SHARE/$file" ] &&
+ echo $BSDCFG_SHARE/$file
+ [ -e "$file" ] && echo $file
+ done | sort -u
+ )
+ for file in $file_list; do
+ # Skip binary files and text files that don't use f_include()
+ grep -qlI f_include $file || continue
+
+ awk \
+ -v file="${file#$BSDCFG_SHARE/}" \
+ -v bsdconfig="$BSDCONFIG" \
+ '
+ BEGIN { regex = "^f_include \\$BSDCFG_SHARE/" }
+ ( $0 ~ regex ) {
+ sub(regex, "")
+ if ( file == bsdconfig ) sub(".*/", "", file)
+ printf "\t\t\"%s\" -> \"%s\";\n", $0, file
+ }
+ ' $file
+ done | sort
+ end_nodelist
+fi
+
+#
+# Print command-line shortcuts
+#
+if [ "$SHOW_CMDLINE" ]; then
+ printf '\n\t/*\n\t * Command-line shortcuts\n\t */\n'
+ shape=parallelogram color=black fillcolor=lightseagreen style=filled
+ begin_nodelist "$shape" "$color" "$fillcolor" "$style"
+ for file in */INDEX; do
+ awk -v item="${file%%/*}" '
+ /^menu_selection="/ {
+ sub(/^.*="/, "")
+ sub(/\|.*/, "")
+ printf "\t\t\"bsdconfig %s\"", $0
+ printf " [ label = \"%s\" ];\n", $0
+ }
+ ' $file
+ done
+ end_nodelist
+fi
+
+#
+# Print command-line shortcut relationships
+#
+if [ "$SHOW_CMDLINE" ]; then
+ printf '\n\t/*\n\t * Command-line shortcut relationships\n\t */\n'
+ shape=box color=black fillcolor=lightseagreen style=filled
+ begin_nodelist "$shape" "$color" "$fillcolor" "$style"
+ print_node edge "headport = w" "weight = 100.0"
+ print_node edge "style = bold" "color = $fillcolor"
+ for file in */INDEX; do
+ awk -v item="${file%%/*}" \
+ -v node_fillcolor="$node_fillcolor" \
+ -v edge_color="$edge_color" \
+ '
+ /^menu_selection="/ {
+ sub(/^.*="/, "")
+ sub(/"$/, "")
+
+ if ( ! $0 ) next
+
+ split($0, menusel, "|")
+ if ( menusel[2] !~ "^/" )
+ sub(/^/, item "/", menusel[2])
+
+ printf "\t\t\"bsdconfig %s\" -> \"%s\";\n",
+ menusel[1], menusel[2]
+ }
+ ' $file
+ done
+ end_nodelist
+fi
+
+#
+# Print clusters
+#
+bgcolor_bsdconfig="lightyellow"
+bgcolor_includes="gray98"
+bgcolor_menuitem="aliceblue"
+bgcolor_shortcuts="honeydew"
+printf '\n\t/*\n\t * Clusters\n\t */\n'
+printf '\tsubgraph "cluster_bsdconfig" {\n'
+printf '\t\tbgcolor = "%s";\n' "$bgcolor_bsdconfig"
+printf '\t\tlabel = "bsdconfig(8)";\n'
+printf '\t\ttooltip = "bsdconfig(8)";\n'
+print_node "bsdconfig"
+end_nodelist
+if [ "$SHOW_INCLUDES" ]; then
+ for include_file in $include_file_list; do
+ echo $include_file
+ done | awk \
+ -v bgcolor="$bgcolor_bsdconfig" \
+ -v msg_subroutines="$msg_subroutines" \
+ '
+ BEGIN { created = 0 }
+ function end_subgraph() { printf "\t};\n" }
+ ( $0 !~ "/" ) {
+ if ( ! created )
+ {
+ printf "\tsubgraph \"%s\" {\n",
+ "cluster_bsdconfig_includes"
+ printf "\t\tbgcolor = \"%s\";\n", bgcolor
+ printf "\t\tlabel = \"bsdconfig %s\";\n",
+ msg_subroutines
+ created++
+ }
+ printf "\t\t\"%s\";\n", $1
+ }
+ END { created && end_subgraph() }
+ ' # END-QUOTE
+
+ for include_file in $include_file_list; do
+ echo $include_file
+ done | awk -v msg_subroutines="$msg_subroutines" '
+ BEGIN { created = 0 }
+ function end_subgraph() { printf "\t};\n" }
+ ( $0 ~ "/" ) {
+ include_dir_tmp = $1
+ sub("/[^/]*$", "", include_dir_tmp)
+ gsub(/[^[:alnum:]_]/, "_", include_dir_tmp)
+
+ if ( created && include_dir != include_dir_tmp )
+ {
+ end_subgraph()
+ created = 0
+ }
+
+ if ( ! created )
+ {
+ include_dir = include_dir_tmp
+ printf "\tsubgraph \"cluster_%s_includes\" {\n",
+ include_dir
+ printf "\t\tbgcolor = \"thistle\";\n"
+ printf "\t\tlabel = \"%s %s\";\n", include_dir,
+ msg_subroutines
+ created++
+ }
+
+ printf "\t\t\"%s\";\n", $1
+ }
+ END { created && end_subgraph() }'
+fi
+for INDEX in */INDEX; do
+ menu_title=
+ menu_help=
+ f_include_lang "$INDEX"
+
+ item="${INDEX%%/*}"
+ printf '\tsubgraph "cluster_%s" {\n' "$item"
+
+ case "$item" in
+ [0-9][0-9][0-9].*) bgcolor="$bgcolor_menuitem" ;;
+ *) bgcolor="$bgcolor_shortcuts"
+ esac
+ printf '\t\tbgcolor = "%s";\n' "$bgcolor"
+ if [ "$menu_title" ]; then
+ printf '\t\tlabel = "%s\\n\\"%s\\"";\n' "$item" "$menu_title"
+ else
+ printf '\t\tlabel = "%s";\n' "$item"
+ fi
+ printf '\t\ttooltip = "%s";\n' "${menu_help:-$item}"
+
+ program_list=$(
+ for program in \
+ $menu_program_list \
+ $submenu_program_list \
+ $cmd_program_list \
+ ; do
+ echo "$program"
+ done | sort -u
+ )
+ for program in $program_list; do
+ case "$program" in "$item"/*)
+ print_node "$program" "label = \"${program#*/}\""
+ esac
+ done
+
+ if [ "$SHOW_INCLUDES" ]; then
+ item_include_list=
+ [ -d "$item/include" ] &&
+ item_include_list=$( find "$item/include" -type f )
+ item_include_list=$(
+ for item_include in $item_include_list; do
+ for include_file in $include_file_list; do
+ [ "$item_include" = "$include_file" ] ||
+ continue
+ echo "$item_include"; break
+ done
+ done
+ )
+ if [ "$item_include_list" ]; then
+ printf '\t\tsubgraph "cluster_%s_includes" {\n' "$item"
+ printf '\t\t\tbgcolor = "%s";\n' "$bgcolor_includes"
+ printf '\t\t\tlabel = "%s";\n' "$msg_includes"
+ fi
+ for item_include in $item_include_list; do
+ printf '\t\t\t"%s";\n' "$item_include"
+ done
+ [ "$item_include_list" ] && printf '\t\t};\n'
+ fi
+
+ if [ "$SHOW_CMDLINE" ]; then
+ printf '\t\tsubgraph "cluster_%s_shortcuts" {\n' "$item"
+ printf '\t\t\tbgcolor = "%s";\n' "$bgcolor_shortcuts"
+ printf '\t\t\tlabel = "%s";\n' "$msg_shortcuts"
+ awk '/^menu_selection="/ {
+ sub(/^.*="/, "")
+ sub(/\|.*/, "")
+ printf "\t\t\t\"bsdconfig %s\";\n", $0
+ }' "$INDEX"
+ printf '\t\t};\n'
+ fi
+
+ end_nodelist
+done
+
+printf '\n}\n'
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/dot/include/Makefile b/usr.sbin/bsdconfig/dot/include/Makefile
new file mode 100644
index 0000000..b687b88
--- /dev/null
+++ b/usr.sbin/bsdconfig/dot/include/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/dot/include
+FILES= messages.subr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/dot/include/Makefile.depend b/usr.sbin/bsdconfig/dot/include/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/dot/include/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/dot/include/messages.subr b/usr.sbin/bsdconfig/dot/include/messages.subr
new file mode 100644
index 0000000..f1095f7
--- /dev/null
+++ b/usr.sbin/bsdconfig/dot/include/messages.subr
@@ -0,0 +1,31 @@
+# Copyright (c) 2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+msg_graph_label_with_command="bsdconfig(8)\\\ndot(1) output generated by \`\`%s''"
+msg_graph_label_with_command_and_date="bsdconfig(8)\\\ndot(1) output generated by \`\`%s'' on\\\n%s"
+msg_includes="Includes"
+msg_shortcuts="Shortcuts"
+msg_subroutines="Subroutines"
diff --git a/usr.sbin/bsdconfig/examples/Makefile b/usr.sbin/bsdconfig/examples/Makefile
new file mode 100644
index 0000000..f66c2b1
--- /dev/null
+++ b/usr.sbin/bsdconfig/examples/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+FILESDIR= ${SHAREDIR}/examples/bsdconfig
+FILES= add_some_packages.sh browse_packages_http.sh bsdconfigrc
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/examples/Makefile.depend b/usr.sbin/bsdconfig/examples/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/examples/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/examples/add_some_packages.sh b/usr.sbin/bsdconfig/examples/add_some_packages.sh
new file mode 100755
index 0000000..8750908
--- /dev/null
+++ b/usr.sbin/bsdconfig/examples/add_some_packages.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+# $FreeBSD$
+#
+# This sample installs a short list of packages from the main HTTP site.
+#
+[ "$_SCRIPT_SUBR" ] || . /usr/share/bsdconfig/script.subr || exit 1
+nonInteractive=1
+_httpPath=http://pkg.freebsd.org
+mediaSetHTTP
+mediaOpen
+for package in wget bash rsync; do
+ packageAdd
+done
diff --git a/usr.sbin/bsdconfig/examples/browse_packages_http.sh b/usr.sbin/bsdconfig/examples/browse_packages_http.sh
new file mode 100755
index 0000000..91b928d
--- /dev/null
+++ b/usr.sbin/bsdconfig/examples/browse_packages_http.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+# $FreeBSD$
+#
+# This sample downloads the package digests.txz and packagesite.txz files from
+# HTTP to /tmp (if they don't already exist) and then displays the package
+# configuration/management screen using the local files (resulting in faster
+# browsing of packages from-start since digests.txz/packagesite.txz can be
+# loaded from local media).
+#
+# NOTE: Packages cannot be installed unless staged to
+# /tmp/packages/$PKG_ABI/All
+#
+[ "$_SCRIPT_SUBR" ] || . /usr/share/bsdconfig/script.subr || exit 1
+nonInteractive=1
+f_musthavepkg_init # Make sure we have a usable pkg(8) with $PKG_ABI
+TMPDIR=/tmp
+PKGDIR=$TMPDIR/packages/$PKG_ABI
+[ -d "$PKGDIR" ] || mkdir -p "$PKGDIR" || exit 1
+for file in digests.txz packagesite.txz; do
+ [ -s "$PKGDIR/$file" ] && continue
+ if [ ! "$HTTP_INITIALIZED" ]; then
+ _httpPath=http://pkg.freebsd.org
+ mediaSetHTTP
+ mediaOpen
+ fi
+ f_show_info "Downloading %s from\n %s" "$file" "$_httpPath"
+ f_device_get device_media "/$PKG_ABI/latest/$file" > $PKGDIR/$file ||
+ exit 1
+done
+_directoryPath=$TMPDIR
+mediaSetDirectory
+configPackages
diff --git a/usr.sbin/bsdconfig/examples/bsdconfigrc b/usr.sbin/bsdconfig/examples/bsdconfigrc
new file mode 100644
index 0000000..2e3155f
--- /dev/null
+++ b/usr.sbin/bsdconfig/examples/bsdconfigrc
@@ -0,0 +1,42 @@
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+# This file allows you to customize the behavior of bsdconfig.
+# Copy it to your $HOME/.bsdconfigrc and edit to suit.
+
+# Debugging aids for development
+#
+#debug=1
+#debugFile=$HOME/out
+#debugFile=+$HOME/out # includes debug to stdout
+
+# Optionally override functions to be more verbose (like including the date)
+#
+# f_dprintf() {
+# local format="$1"; shift
+# printf "$(date):$pgm:$format\n" "$@" >> $HOME/out
+# }
diff --git a/usr.sbin/bsdconfig/include/Makefile b/usr.sbin/bsdconfig/include/Makefile
new file mode 100644
index 0000000..913481c
--- /dev/null
+++ b/usr.sbin/bsdconfig/include/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/include
+FILES= bsdconfig.hlp media.hlp messages.subr network_device.hlp \
+ options.hlp tcp.hlp usage.hlp
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/include/Makefile.depend b/usr.sbin/bsdconfig/include/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/include/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/include/bsdconfig.hlp b/usr.sbin/bsdconfig/include/bsdconfig.hlp
new file mode 100644
index 0000000..b6eeaf3
--- /dev/null
+++ b/usr.sbin/bsdconfig/include/bsdconfig.hlp
@@ -0,0 +1,12 @@
+This menu allows you to configure your system after the installation
+process is complete. At the minimum, you should probably set the root
+password and the system time zone.
+
+For extra goodies like bash, emacs, firefox, etc., you should look at
+the Packages item in this menu.
+
+For setting the timezone after the system is installed, use the 'Time
+Zone' item in this menu.
+
+For more information on the overall general system configuration, see
+the /etc/rc.conf and /etc/defaults/rc.conf files.
diff --git a/usr.sbin/bsdconfig/include/media.hlp b/usr.sbin/bsdconfig/include/media.hlp
new file mode 100644
index 0000000..6fd6094
--- /dev/null
+++ b/usr.sbin/bsdconfig/include/media.hlp
@@ -0,0 +1,54 @@
+You can install from the following types of media:
+
+ CDROM requires one of the following supported CDROM drives:
+ ATAPI - Any standard ATAPI CDROM drive hooked to
+ a supported controller (see Hardware Guide).
+ SCSI - Any standard SCSI CDROM drive hooked to
+ a supported controller (see Hardware Guide).
+
+
+ DOS A DOS primary partition with the required FreeBSD
+ distribution files copied onto it (e.g. C:\FREEBSD\)
+
+
+ UFS Assuming a disk or partition with an existing
+ FreeBSD file system and distribution set on it,
+ get the distribution files from there.
+
+
+ Floppy Get distribution files from one or more DOS or UFS
+ formatted floppies. Such floppies are assumed to
+ contain the appropriate distribution pieces - see
+ ABOUT.TXT for more information about making floppy
+ distribution media.
+
+
+ FTP Get the distribution files from an anonymous ftp server
+ (you will be presented with a list). Please note that
+ you may invoke FTP in "Active"/"Passive" auto-mode, or
+ via an HTTP proxy.
+
+ By default, ftp(1) will automatically use the best mode
+ for the server. Using an HTTP proxy is sometimes necessary
+ for firewalls which block all FTP connections.
+
+ If you chose to enter your own URL in the FTP menu, please
+ note that all paths are *relative* to the home directory
+ of the user being logged in as. By default, this is the
+ user "ftp" (anonymous ftp) but you may change this in the
+ Options screen.
+
+
+ HTTP Direct
+ Get the distribution files directly from an HTTP server.
+
+ If you chose to enter your own URL in the HTTP Direct menu,
+ please note that all paths are *relative* to the root
+ directory of the web server.
+
+
+ NFS Get the distribution files from an NFS server somewhere
+ (make sure that permissions on the server allow this!).
+ If this install method hangs on you or refuses to work
+ properly, you may need to set some special options for
+ your NFS server. See the Options screen for more details.
diff --git a/usr.sbin/bsdconfig/include/messages.subr b/usr.sbin/bsdconfig/include/messages.subr
new file mode 100644
index 0000000..04f4485
--- /dev/null
+++ b/usr.sbin/bsdconfig/include/messages.subr
@@ -0,0 +1,427 @@
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+field_password="Password:"
+field_username="Username:"
+hline_alnum_arrows_punc_tab_enter="Use alnum, arrows, punctuation, TAB or ENTER"
+hline_alnum_punc_tab_enter="Use alpha-numeric, punctuation, TAB or ENTER"
+hline_arrows_tab_enter="Press arrows, TAB or ENTER"
+hline_arrows_tab_punc_enter="Use arrows, TAB, punctuation, ENTER"
+hline_choose_help_for_more_information_on_media_types="Choose Help for more information on the various media types"
+msg_accept_continue="Accept/Continue"
+msg_accessibility_desc="Ports to help disabled users."
+msg_adding_package_as_a_dependency_from_media="Adding %s (as a dependency) from %s"
+msg_adding_package_from_media="Adding %s from %s"
+msg_afterstep_desc="Ports to support the AfterStep window manager."
+msg_all="All"
+msg_all_desc="All available packages in all categories."
+msg_always_try_sudo_when_run_as="Always try sudo(8) when run as %s"
+msg_an_unknown_error_occurred="An unknown error occurred"
+msg_arabic_desc="Ported software for Arab countries."
+msg_archivers_desc="Utilities for archiving and unarchiving data."
+msg_armenia="Armenia"
+msg_assume_network_is_already_configured="Running multi-user, assume that the network is already configured?"
+msg_assume_yes_to_all_non_critical_dialogs="Assume \"Yes\" answers to all non-critical dialogs"
+msg_astro_desc="Applications related to astronomy."
+msg_attempt_automatic_dhcp_configuration="Attempt automatic DHCP configuration of interfaces"
+msg_attempt_ipv6_configuration_of_interfaces="Attempt IPv6 configuration of interfaces"
+msg_attempting_to_update_repository_catalogue="Attempting to update repository catalogue from selected media."
+msg_audio_desc="Audio utilities - most require a supported sound card."
+msg_australia="Australia"
+msg_austria="Austria"
+msg_back="Back"
+msg_becoming_root_via_sudo="Becoming root via sudo(8)..."
+msg_benchmarks_desc="Utilities for measuring system performance."
+msg_biology_desc="Software related to biology."
+msg_bootstrapping_pkg="Bootstrapping pkg(8)..."
+msg_brazil="Brazil"
+msg_building_package_menus="Building package menu(s)..."
+msg_cad_desc="Computer Aided Design utilities."
+msg_canada="Canada"
+msg_cancel="Cancel"
+msg_cancel_exit="Cancel/Exit"
+msg_cannot_create_permission_denied="%s: cannot create %s: Permission denied"
+msg_cannot_resolve_hostname="Cannot resolve \`%s'! Are you sure that your\nname server, gateway and network interface are correctly configured?"
+msg_cant_find_distribution="Warning: Can't find the \`%s' distribution on this\nFTP server. You may need to visit a different server for\nthe release you are trying to fetch or go to the Options\nmenu and set the release name to explicitly match what's\navailable on %s (or set to \"any\").\n\nWould you like to select another FTP server?"
+msg_cant_seem_to_write_out_resolv_conf="Can't seem to write out %s. Net cannot be used."
+msg_cd_dvd="CD/DVD"
+msg_cdrom="CDROM"
+msg_checking_access_to="Checking access to\n %s"
+msg_china="China"
+msg_chinese_desc="Ported software for the Chinese market."
+msg_choose_a_cd_dvd_type="Choose a CD/DVD type"
+msg_choose_a_dos_partition="Choose a DOS partition"
+msg_choose_a_floppy_drive="Choose a Floppy drive"
+msg_choose_a_ufs_partition="Choose a UFS partition"
+msg_choose_a_usb_drive="Choose a USB drive"
+msg_choose_installation_media="Choose Installation Media"
+msg_choose_installation_media_description="FreeBSD can be installed from a variety of different installation\nmedia, ranging from floppies to an Internet FTP server. If you're\ninstalling FreeBSD from a supported CD/DVD drive then this is generally\nthe best media to use if you have no overriding reason for using other\nmedia."
+msg_client_error="Client error, you could try an other server"
+msg_command_failed_rest_of_script_aborted="Command \`%s' failed - rest of script aborted."
+msg_comms_desc="Communications utilities."
+msg_configuration_for_interface="Configuration for Interface"
+msg_converters_desc="Format conversion utilities."
+msg_could_not_unmount_the_cdrom_dvd="Could not unmount the CDROM/DVD from %s: %s"
+msg_could_not_unmount_the_dos_partition="Could not unmount the DOS partition from %s: %s"
+msg_could_not_unmount_the_nfs_partition="Could not unmount the NFS partition from %s: %s"
+msg_could_not_unmount_the_ufs_partition="Could not unmount the UFS partition from %s: %s"
+msg_couldnt_connect_to_ftp_server="Couldn't connect to FTP server"
+msg_couldnt_connect_to_proxy="Couldn't connect to proxy"
+msg_couldnt_connect_to_server="Couldn't connect to server"
+msg_couldnt_open_ftp_connection="Couldn't open FTP connection to %s:\n %s."
+msg_created_path="Created %s"
+msg_czech_republic="Czech Republic"
+msg_databases_desc="Database software."
+msg_debugging="Debugging"
+msg_denmark="Denmark"
+msg_deskutils_desc="Various Desktop utilities."
+msg_devel_desc="Software development utilities and libraries."
+msg_device_is_not_configured="The %s device is not configured. You will need to do so\nin the Networking configuration menu before proceeding."
+msg_dhcp="DHCP"
+msg_dialog_mixedform_navigation_help="Use <up>/<down> arrows to navigate between fields, TAB to focus buttons, and Enter for OK/Cancel."
+msg_directory="Directory"
+msg_directory_not_found="%s: Directory not found."
+msg_directory_where_package_temporary_files_go="The directory where package temporary files should go"
+msg_dns_desc="Domain Name Service tools."
+msg_docs_desc="Meta-ports for FreeBSD documentation."
+msg_done="Done"
+msg_dos="DOS"
+msg_editor="Editor"
+msg_editors_desc="Editors."
+msg_elisp_desc="Things related to Emacs Lisp."
+msg_emit_extra_debugging_output="Emit extra debugging output"
+msg_emulators_desc="Utilities for emulating other operating systems."
+msg_enlightenment_desc="Software for the Enlightenment Desktop Environment."
+msg_enter_a_fully_qualified_pathname_for_the_directory="Enter a fully qualified pathname for the directory\ncontaining the FreeBSD distribution files:"
+msg_enter_the_device_name_of_a_ufs_formatted_partition="Enter the device-name of a UFS formatted partition"
+msg_error="Error"
+msg_error_mounting_device="Error mounting %s on %s: %s"
+msg_error_mounting_floppy_device="Error mounting floppy %s (%s) on %s: %s"
+msg_error_mounting_usb_drive="Error mounting USB drive %s on %s: %s"
+msg_error_when_requesting_url="Error when requesting %s, you could try an other server"
+msg_estonia="Estonia"
+msg_exit="Exit"
+msg_exit_bsdconfig="Exit bsdconfig"
+msg_extra_options_to_ifconfig="Extra options to ifconfig (usually empty):"
+msg_failed_to_add_default_route="Failed to add a default route; please check your network configuration"
+msg_file_system="File System"
+msg_finance_desc="Monetary, financial and related applications."
+msg_finland="Finland"
+msg_floppy="Floppy"
+msg_france="France"
+msg_french_desc="Ported software for French countries."
+msg_ftp="FTP"
+msg_ftp_desc="FTP client and server utilities."
+msg_ftp_passive="FTP Passive"
+msg_ftp_username="FTP username"
+msg_generating_index_from_pkg_database="Generating INDEX from pkg(8) database\n(this can take a while)..."
+msg_geography_desc="Geography-related software."
+msg_german_desc="Ported software for Germanic countries."
+msg_germany="Germany"
+msg_gnome_desc="Components of the Gnome Desktop environment."
+msg_gnustep_desc="Software for GNUstep desktop environment."
+msg_graphics_desc="Graphics libraries and utilities."
+msg_greece="Greece"
+msg_hamradio_desc="Software for amateur radio."
+msg_haskell_desc="Software related to the Haskell language."
+msg_hebrew_desc="Ported software for Hebrew language."
+msg_help="Help"
+msg_host_name_including_domain="Host name (including domain)"
+msg_hostname_variable_not_set="WARNING: hostname variable not set and is a non-optional\nparameter. Please add this to your installation script\nor set the netInteractive variable (see bsdconfig man page)"
+msg_http_direct="HTTP Direct"
+msg_http_proxy="HTTP Proxy"
+msg_hungarian_desc="Ported software for the Hungarian market."
+msg_iceland="Iceland"
+msg_install_from_a_dos_partition="Install from a DOS partition"
+msg_install_from_a_floppy_disk_set="Install from a floppy disk set"
+msg_install_from_a_freebsd_cd_dvd="Install from a FreeBSD CD/DVD"
+msg_install_from_a_ufs_partition="Install from a UFS partition"
+msg_install_from_a_usb_drive="Install from a USB drive"
+msg_install_from_an_ftp_server="Install from an FTP server"
+msg_install_from_an_ftp_server_thru_firewall="Install from an FTP server through a firewall"
+msg_install_from_an_ftp_server_thru_proxy="Install from an FTP server through an HTTP proxy"
+msg_install_from_an_http_server="Install from an HTTP server"
+msg_install_from_the_existing_filesystem="Install from the existing filesystem"
+msg_install_over_nfs="Install over NFS"
+msg_installed="Installed"
+msg_installed_desc="Leave package as-is, installed"
+msg_installed_lc="installed"
+msg_invalid_gateway_ipv4_address_specified="Invalid gateway IPv4 address specified"
+msg_invalid_hostname_value="Invalid hostname value"
+msg_invalid_ipv4_address="Invalid IPv4 address"
+msg_invalid_name_server_ip_address_specified="Invalid name server IP address specified"
+msg_invalid_netmask_value="Invalid netmask value"
+msg_invalid_nfs_path_specification="Invalid NFS path specification. Must be of the form:\nhost:/full/pathname/to/FreeBSD/distdir"
+msg_io_error_while_reading_in_the_package="I/O error while reading in the %s package."
+msg_io_or_format_error_on_index_file="I/O or format error on INDEX file.\nPlease verify media (or path to media) and try again."
+msg_ipv4_address="IPv4 Address"
+msg_ipv4_gateway="IPv4 Gateway"
+msg_ipv6="IPv6"
+msg_ipv6_desc="IPv6-related software."
+msg_ipv6_ready="IPv6 ready"
+msg_irc_desc="Internet Relay Chat utilities."
+msg_ireland="Ireland"
+msg_israel="Israel"
+msg_italy="Italy"
+msg_japan="Japan"
+msg_japanese_desc="Ported software for the Japanese market."
+msg_java_desc="Java language support."
+msg_kde_desc="Software for the K Desktop Environment."
+msg_kld_desc="Kernel loadable modules."
+msg_korea="Korea"
+msg_korean_desc="Ported software for the Korean market."
+msg_lang_desc="Computer languages."
+msg_latvia="Latvia"
+msg_length_of_specified_url_is_too_long="Length of specified URL is %u characters. Allowable maximum is %u."
+msg_linux_desc="Linux programs that can run under binary compatibility."
+msg_lisp_desc="Software related to the Lisp language."
+msg_lithuania="Lithuania"
+msg_loading_of_dependent_package_failed="Loading of dependent package %s failed"
+msg_located_index_now_reading_package_data_from_it="Located INDEX, now reading package data from it..."
+msg_logging_in_to_user_at_host="Logging in to %s@%s.."
+msg_looking_for_keymap_files="Looking for keymap files..."
+msg_looking_up_host="Looking up host %s"
+msg_mail_desc="Electronic mail packages and utilities."
+msg_main_menu="Main Menu"
+msg_main_site="Main Site"
+msg_math_desc="Mathematical computation software."
+msg_mbone_desc="Applications and utilities for the MBONE."
+msg_media_timeout="Media Timeout"
+msg_media_type="Media Type"
+msg_menu_text="If you've already installed FreeBSD, you may use\nthis menu to customize it somewhat to suit your\nparticular configuration. Most importantly, you\ncan use the Packages utility to load extra '3rd\nparty' software not provided in the base\ndistributions."
+msg_misc_desc="Miscellaneous utilities."
+msg_missing_ftp_host_or_directory="Missing FTP host or directory specification. FTP media not initialized."
+msg_multimedia_desc="Multimedia software."
+msg_must_be_root_to_execute="%s: must be root to execute"
+msg_must_have_pkg_to_execute="%s: must have pkg(8) to execute"
+msg_must_specify_a_host_name_of_some_sort="Must specify a host name of some sort!"
+msg_name_server="Name server"
+msg_net_desc="Networking utilities."
+msg_net_device_init_failed="Net device init failed."
+msg_net_im_desc="Instant messaging software."
+msg_net_mgmt_desc="Network management tools."
+msg_net_p2p_desc="Peer to peer network applications."
+msg_netherlands="Netherlands"
+msg_netmask="Netmask"
+msg_network_configuration="Network Configuration"
+msg_network_interface_information_required="Network interface information required"
+msg_new_zealand="New Zealand"
+msg_news_desc="USENET News support software."
+msg_next_page="Next page"
+msg_nfailed_attempts="%u incorrect password attempts"
+msg_nfs="NFS"
+msg_nfs_secure="NFS Secure"
+msg_nfs_server_talks_only_on_a_secure_port="NFS server talks only on a secure port"
+msg_nfs_slow="NFS Slow"
+msg_nfs_tcp="NFS TCP"
+msg_nfs_version_3="NFS version 3"
+msg_no="No"
+msg_no_cd_dvd_devices_found="No CD/DVD devices found! Please check that your system's\nconfiguration is correct and that the CD/DVD drive is of a\nsupported type. For more information, consult the hardware\nguide in the Doc menu."
+msg_no_description_provided="No description provided"
+msg_no_dos_primary_partitions_found="No DOS primary partitions found! This installation method is unavailable"
+msg_no_floppy_devices_found="No floppy devices found! Please check that your system's configuration\nis correct. For more information, consult the hardware guide in the Doc\nmenu."
+msg_no_gateway_has_been_set="No gateway has been set. You will be unable to access hosts\nnot on your local network"
+msg_no_network_devices="No network devices available!"
+msg_no_package_name_passed_in_package_variable="No package name passed in package variable"
+msg_no_packages_were_selected_for_extraction="No packages were selected for extraction."
+msg_no_pkg_database_found="No pkg(8) database found!"
+msg_no_such_file_or_directory="%s: %s: No such file or directory"
+msg_no_usb_devices_found="No USB devices found (try Options/Re-scan Devices)"
+msg_no_username="No username provided!"
+msg_norway="Norway"
+msg_not_a_directory="%s: %s: Not a directory"
+msg_not_found="not found"
+msg_not_yet_set="not yet set"
+msg_ok="OK"
+msg_options="Options"
+msg_options_editor="Options Editor"
+msg_other="other"
+msg_package_is_needed_by_other_installed_packages="Warning: Package %s is needed by\n %d other installed package%s."
+msg_package_not_installed_cannot_delete="Warning: package %s not installed\n No package can be deleted."
+msg_package_temp="Package Temp"
+msg_package_was_added_successfully="Package %s was added successfully"
+msg_packages="packages"
+msg_page_of_npages="(Page %s of %s)"
+msg_palm_desc="Software support for the Palm(tm) series."
+msg_parallel_desc="Applications dealing with parallelism in computing."
+msg_pear_desc="Software related to the Pear PHP framework."
+msg_perl5_desc="Utilities/modules for the PERL5 language."
+msg_permission_denied="%s: %s: Permission denied"
+msg_pkg_delete_failed="Warning: pkg-delete(8) of %s failed.\n Run with debugging for details."
+msg_pkg_install_apparently_did_not_like_the_package="pkg-install(8) apparently did not like the %s package."
+msg_pkg_not_yet_installed_install_now="pkg(8) not yet installed. Install now?"
+msg_plan9_desc="Software from the Plan9 operating system."
+msg_please_check_the_url_and_try_again="No such directory: %s\nplease check the URL and try again.\n"
+msg_please_enter_password="Please enter your password for sudo(8):"
+msg_please_enter_the_address_of_the_http_proxy="Please enter the address of the HTTP proxy in this format:\n hostname:port (the ':port' is optional, default is 3128)"
+msg_please_enter_the_full_nfs_file_specification="Please enter the full NFS file specification for the remote\nhost and directory containing the FreeBSD distribution files.\nThis should be in the format: hostname:/some/freebsd/dir"
+msg_please_enter_the_password_for_this_user="Please enter the password for this user:"
+msg_please_enter_the_username_you_wish_to_login_as="Please enter the username you wish to login as:"
+msg_please_enter_username_password="Please enter a username and password for sudo(8):"
+msg_please_insert_floppy_containing="Please insert floppy containing %s in %s"
+msg_please_insert_floppy_in_drive="Please insert floppy in %s"
+msg_please_select_a_category_to_display="Please select a category to display."
+msg_please_select_a_cd_dvd_drive="FreeBSD can be installed directly from a CD/DVD containing a valid\nFreeBSD distribution. If you are seeing this menu it is because\nmore than one CD/DVD drive was found on your system. Please select\none of the following CD/DVD drives as your installation drive."
+msg_please_select_a_floppy_drive="You have more than one floppy drive. Please choose which drive\nyou would like to use."
+msg_please_select_a_freebsd_ftp_distribution_site="Please select a FreeBSD FTP distribution site"
+msg_please_select_a_freebsd_http_distribution_site="Please select a FreeBSD HTTP distribution site"
+msg_please_select_a_usb_drive="You have more than one USB drive. Please choose which drive\nyou would like to use."
+msg_please_select_dos_partition="FreeBSD can be installed directly from a DOS partition assuming,\nof course, that you have copied the relevant distributions into\nyour DOS partition before starting this installation. If this is\nnot the case then you should reboot DOS at this time and copy the\ndistributions you wish to install into a \"FREEBSD\" subdirectory\non one of your DOS partitions. Otherwise, please select the DOS\npartition containing the FreeBSD distribution files."
+msg_please_select_ethernet_device_to_configure="Please select the ethernet or PLIP device to configure."
+msg_please_select_the_site_closest_to_you_or_other="Please select the site closest to you or \"other\" if you'd like to\nspecify a different choice. Also note that not every site listed here\ncarries more than the base distribution kits. Only Primary sites are\nguaranteed to carry the full range of possible distributions."
+msg_please_select_ufs_partition="FreeBSD can be installed directly from another FreeBSD partition\nthat is UFS formatted assuming, of course, that you have copied\nthe relevant distributions into said partition before starting\ninstallation."
+msg_please_specify_a_temporary_directory="Please specify a temporary directory with lots of free space:"
+msg_please_specify_the_name_of_the_text_editor="Please specify the name of the text editor you wish to use:"
+msg_please_specify_the_number_of_seconds_to_wait="Please specify the number of seconds to wait for slow media:"
+msg_please_specify_the_release_you_wish_to_load="Please specify the release you wish to load or\n\"any\" for a generic release install:"
+msg_please_specify_url_of_a_freebsd_distribution="Please specify the URL of a FreeBSD distribution on a\nremote ftp site. This site must accept either anonymous\nftp or you should have set an ftp username and password\nin the Options screen.\n\nA URL looks like this: ftp://<hostname>/<path>\nWhere <path> is relative to the anonymous ftp directory or the\nhome directory of the user being logged in as."
+msg_please_specify_url_of_freebsd_http_distribution="Please specify the URL of a FreeBSD distribution on a\nremote http site.\nA URL looks like this: http://<hostname>/<path>"
+msg_poland="Poland"
+msg_polish_desc="Ported software for the Polish market."
+msg_ports_mgmt_desc="Utilities for managing ports and packages."
+msg_portuguese_desc="Ported software for the Portuguese market."
+msg_previous_page="Previous page"
+msg_previous_syntax_errors="%s: Not overwriting \`%s' due to previous syntax errors"
+msg_primary="Primary"
+msg_print_desc="Utilities for dealing with printing."
+msg_probing_devices_please_wait_this_can_take_a_while="Probing devices, please wait (this can take a while)..."
+msg_proceed="Proceed"
+msg_processing_selection="Processing selection..."
+msg_python_desc="Software related to the Python language."
+msg_quick_start_how_to_use_this_menu_system="Quick start - How to use this menu system"
+msg_reinstall="Reinstall"
+msg_reinstall_desc="Mark this package for reinstall"
+msg_release_name="Release Name"
+msg_required_package_not_found="Warning: %s is a required package but was not found."
+msg_rerun_bsdconfig_initial_device_probe="Re-run bsdconfig initial device probe"
+msg_rescan_devices="Re-scan Devices"
+msg_reset="RESET!"
+msg_reset_all_values_to_startup_defaults="Reset all values to startup defaults"
+msg_reuse_old_ftp_site_selection_values="Re-use old FTP site selection values?"
+msg_reuse_old_http_site_settings="Re-use old HTTP site settings?"
+msg_review="Review"
+msg_review_desc="Review/perform pending actions"
+msg_review_help="Install, Re-Install, or Un-install selected packages and dependencies"
+msg_reviewing_selected_packages="Reviewing %u selected packages:"
+msg_ruby_desc="Software related to the Ruby language."
+msg_rubygems_desc="Ports of RubyGems packages."
+msg_russia="Russia"
+msg_russian_desc="Ported software for the Russian market."
+msg_scanning_for_dhcp_servers="Scanning for DHCP servers..."
+msg_scanning_for_ra_servers="Scanning for RA servers..."
+msg_scheme_desc="Software related to the Scheme language."
+msg_science_desc="Scientific software."
+msg_secure_mode_requires_root="Secure-mode requires root-access!"
+msg_secure_mode_requires_x11="Secure-mode requires X11 (use \`-X')!"
+msg_security_desc="System security software."
+msg_select="Select"
+msg_select_a_site_thats_close="Select a site that's close!"
+msg_selected="selected"
+msg_server_error_when_requesting_url="Server error when requesting %s, you could try an other server"
+msg_shells_desc="Various shells (tcsh, bash, etc)."
+msg_slovak_republic="Slovak Republic"
+msg_slovenia="Slovenia"
+msg_sorry_invalid_url="Sorry, %s is an invalid URL!"
+msg_sorry_package_was_not_found_in_the_index="Sorry, package %s was not found in the INDEX."
+msg_sorry_try_again="Sorry, try again."
+msg_south_africa="South Africa"
+msg_spain="Spain"
+msg_spanish_desc="Ported software for the Spanish market."
+msg_specify_some_other_ftp_site="Specify some other ftp site by URL"
+msg_specify_some_other_http_site="Specify some other http site by URL"
+msg_sweden="Sweden"
+msg_switzerland="Switzerland"
+msg_sysutils_desc="Various system utilities."
+msg_taiwan="Taiwan"
+msg_tcl_desc="TCL and packages that depend on it."
+msg_textproc_desc="Text processing/search utilities."
+msg_the_current_installation_media_type="The current installation media type."
+msg_timeout_value_in_seconds_for_slow_media="Timeout value in seconds for slow media."
+msg_tk_desc="Tk and packages that depend on it."
+msg_try_dhcp_configuration="Do you want to try DHCP configuration of the interface?"
+msg_try_ipv6_configuration="Do you want to try IPv6 configuration of the interface?"
+msg_try_sudo_only_this_once="Try sudo(8) only this once"
+msg_ufs="UFS"
+msg_uk="UK"
+msg_ukraine="Ukraine"
+msg_ukrainian_desc="Ported software for the Ukrainian market."
+msg_unable_to_configure_device="Unable to configure the %s interface!\nThis installation method cannot be used."
+msg_unable_to_fetch_package_from_selected_media="Unable to fetch package %s from selected media.\nNo package add will be done."
+msg_unable_to_get_file_from_selected_media="Unable to get %s file from selected media.\n\nThis may be because the packages collection is not available\non the distribution media you've chosen, most likely an FTP site\nwithout the packages collection mirrored. Please verify that\nyour media, or your path to the media, is correct and try again."
+msg_unable_to_get_proper_ftp_path="Unable to get proper FTP path. FTP media not initialized."
+msg_unable_to_initialize_media_type_for_package_extract="Unable to initialize media type for package extract."
+msg_unable_to_make_directory_mountpoint="Unable to make %s directory mountpoint for %s!"
+msg_unable_to_open="Unable to open %s"
+msg_unable_to_update_pkg_from_selected_media="Unable to update pkg(8) from selected media."
+msg_uninstall="Uninstall"
+msg_uninstall_desc="Mark this package for deletion"
+msg_uninstalling_package_waiting_for_pkg_delete="Uninstalling %s package - waiting for pkg-delete(8)"
+msg_unknown="unknown"
+msg_unknown_user="Unknown user: %s"
+msg_url_was_not_found="%s was not found,\nmaybe directory or release-version are wrong?"
+msg_usa="USA"
+msg_usage="Usage"
+msg_usb="USB"
+msg_use_defaults="Use Defaults"
+msg_use_nfs_version_3="Use NFS version 3"
+msg_use_tcp_protocol_for_nfs="Use TCP protocol for NFS"
+msg_user_disallowed="User disallowed: %s"
+msg_user_is_using_a_slow_pc_or_ethernet_card="User is using a slow PC or Ethernet card"
+msg_username_and_password_to_use="Username and password to use instead of anonymous"
+msg_using_interface="Using interface %s"
+msg_using_usb_device="Using USB device: %s"
+msg_vietnamese_desc="Ported software for the Vietnamese market."
+msg_view_set_various_media_options="View/Set various media options"
+msg_what_would_you_like_to_do_with="What would you like to do with %s?"
+msg_which_release_to_attempt_to_load="Which release to attempt to load from installation media"
+msg_which_text_editor_to_use="Which text editor to use during installation"
+msg_windowmaker_desc="Ports to support the WindowMaker window manager."
+msg_would_you_like_to_bring_interface_up="Would you like to bring the %s interface up right now?"
+msg_www_desc="Web utilities (browsers, HTTP servers, etc)."
+msg_x11_clocks_desc="X Window System based clocks."
+msg_x11_desc="X Window System based utilities."
+msg_x11_drivers_desc="X Window System drivers."
+msg_x11_fm_desc="X Window System based file managers."
+msg_x11_fonts_desc="X Window System fonts and font utilties."
+msg_x11_servers_desc="X Window System servers."
+msg_x11_themes_desc="X Window System themes."
+msg_x11_toolkits_desc="X Window System based development toolkits."
+msg_x11_wm_desc="X Window System window managers."
+msg_xfce_desc="Software related to the Xfce Desktop Environment."
+msg_yes="Yes"
+msg_yes_to_all="Yes to All"
+msg_you_are_not_root_but="You are not root but %s can use sudo(8).\nWhat would you like to do?"
+msg_you_may_remove_the_floppy="You may remove the floppy from %s"
+msg_youve_already_done_the_network_configuration="You've already done the network configuration once,\nwould you like to skip over it now?"
+msg_zope_desc="Software related to the Zope platform."
+tcplayout_extras_help="Any interface-specific options to ifconfig you would like to add"
+tcplayout_extras_help_for_plip="For PLIP configuration, you must enter the peer's IP address here."
+tcplayout_gateway_help="IPv4 address of host forwarding packets to non-local destinations"
+tcplayout_hostname_help="Your fully-qualified hostname, e.g. foo.example.com"
+tcplayout_ipaddr_help="The IPv4 address to be used for this interface"
+tcplayout_nameserver_help="IPv4 or IPv6 address of your local DNS server"
+tcplayout_netmask_help="The netmask for this interface, e.g. 255.255.255.0 for a class C network"
diff --git a/usr.sbin/bsdconfig/include/network_device.hlp b/usr.sbin/bsdconfig/include/network_device.hlp
new file mode 100644
index 0000000..affa86a
--- /dev/null
+++ b/usr.sbin/bsdconfig/include/network_device.hlp
@@ -0,0 +1,58 @@
+You can do network installations over 3 types of communications links:
+
+ Serial port: SLIP / PPP
+ Parallel port: PLIP (laplink cable)
+ Ethernet: A standard Ethernet controller (includes some
+ PCMCIA networking cards).
+
+SLIP support is rather primitive and limited primarily to directly
+connected links, such as a serial cable running between a laptop
+computer and another PC. The link must be hard-wired as the SLIP
+installation doesn't currently offer a dialing capability (that
+facility is offered by the PPP utility, which should be used in
+preference to SLIP whenever possible). When you choose the SLIP
+option, you'll be given the option of later editing the slattach
+command before it's run on the serial line. It is expected that
+you'll run slattach (or some equivalent command) on the other end of
+the link at that time and bring up the line. FreeBSD will then
+install itself at serial speeds of up to 115.2K/baud (the recommended
+speed for a hardwired cable).
+
+If you're using a modem then PPP is almost certainly your only choice.
+Make sure that you have your service provider's information handy as
+you'll need to know it fairly early in the installation process. You
+will need to know your service provider's IP address, the IP address
+of your provider's DNS server, and possibly your own IP address unless
+your ISP supports dynamic negotiation, most do. If you do not choose
+a PAP or CHAP login you will also need to know how to use the various
+"AT commands" to dial the ISP with your particular brand of modem as
+the PPP dialer provides only a very simple terminal emulator and has no
+"modem capabilities database". If you choose a PAP or CHAP login you
+can simply enter `dial' (without the quotes) at the ppp prompt if your
+modem uses the Hayes compatible AT command set.
+
+If a hard-wired connection to another FreeBSD (2.0R or later) machine
+is available, you might also consider installing over a "laplink"
+parallel port cable. The data rate over the parallel port is much
+higher than what is typically possible over a serial line, and speeds
+of over 50KB/sec are not uncommon.
+
+Finally, for the fastest possible network installation, an Ethernet
+adaptor is always a good choice! FreeBSD supports most common PC
+Ethernet cards, a table of which is provided in the FreeBSD Hardware
+Guide (see the `Documentation' entry in the main menu). If you are
+using one of the supported PCMCIA Ethernet cards, also be sure that
+it's plugged in BEFORE the laptop is powered on! Sysinstall does not,
+unfortunately, currently support "hot insertion" of PCMCIA cards.
+
+You will also need to know your IP address on the network, the
+"netmask" value for your address class, and the name of your machine.
+Your system administrator can tell you which values to use for your
+particular network setup. If you will be referring to other hosts by
+name rather than IP address, you'll also need a name server and
+possibly the address of a gateway (if you're using PPP, it's your
+provider's IP address) to use in talking to it. If you do not know
+the answers to all or most of these questions then you should really
+probably talk to your system administrator FIRST before trying this
+type of installation! Choosing the wrong IP address on a busy network
+will NOT make you popular with your systems administrator! :-)
diff --git a/usr.sbin/bsdconfig/include/options.hlp b/usr.sbin/bsdconfig/include/options.hlp
new file mode 100644
index 0000000..f47df27
--- /dev/null
+++ b/usr.sbin/bsdconfig/include/options.hlp
@@ -0,0 +1,115 @@
+The following options may be set from this screen.
+
+NFS Secure: NFS server talks only on a secure port
+
+ This is most commonly used when talking to Sun workstations, which
+ will not talk NFS over "non privileged" ports.
+
+
+NFS Slow: User is using a slow PC or Ethernet card
+
+ Use this option if you have a slow PC (386) or an Ethernet card
+ with poor performance being "fed" by NFS on a higher-performance
+ workstation. This will throttle the workstation back to prevent
+ the PC from becoming swamped with data.
+
+
+NFS TCP: Use TCP for the NFS mount
+
+ This option can be used if your NFS server supports TCP
+ connections; not all do! This may be useful if your NFS server
+ is at a remote site in which case it may offer some additional
+ stability.
+
+
+NFS version 3: Use NFS version 3
+
+ This option forces the use of NFS version 3 and is on by default.
+ If your NFS server only supports NFS version 2, disable this option.
+
+
+Debugging: Turn on the extra debugging flag
+
+ This turns on a lot of extra noise in between dialogs (unless
+ debugFile has been set, sending the data to a logfile instead).
+ Optionally, if debugFile begins with a plus sign (`+'), output will
+ occur both on standard output and to debugFile (minus leading plus).
+ If your installation should fail for any reason, PLEASE turn this
+ flag on when attempting to reproduce the problem. It will provide a
+ lot of extra debugging at the failure point and may be very helpful
+ to the developers in tracking such problems down!
+
+
+Yes To All: Assume "Yes" answers to all non-critical dialogs
+
+ This flag should be used with caution. It will essentially
+ decide NOT to ask the user about any "boundary" conditions that
+ might not constitute actual errors but may be warnings indicative
+ of other problems. It's most useful to those who are doing unattended
+ installs.
+
+
+DHCP: Enable DHCP configuration of interfaces
+
+ This option specifies whether DHCP configuration of interfaces
+ may be attempted. The default setting is to interactively ask
+ the user.
+
+
+IPv6: Enable IPv6 router solicitation configuration
+
+ This option specifies whether automatic configuration of IPv6
+ interfaces may be attempted. This uses the router solicitation
+ method of automatic configuration. The default setting is to
+ interactively ask the user.
+
+
+FTP username: Specify username and password instead of anonymous.
+
+ By default, the installation attempts to log in as the
+ anonymous user. If you wish to log in as someone else,
+ specify the username and password with this option.
+
+
+Editor: Specify which screen editor to use.
+
+ At various points during the installation it may be necessary
+ to customize some text file, at which point the user will be
+ thrown unceremoniously into a screen editor. A relatively
+ simplistic editor which shows its command set on-screen is
+ selected by default, but UNIX purists may wish to change this
+ setting to `/usr/bin/vi'.
+
+
+Release Name: Which release to attempt to load from installation media.
+
+ You should only change this option if you're really sure you know
+ what you are doing! This will change the release name used by
+ bsdconfig when fetching components of any distributions, and
+ is a useful way of using a more recent installation boot floppy
+ with an older release (say, on CDROM).
+
+
+Media Type: Which media type is being used.
+
+ This is mostly informational and indicates which media type (if any)
+ was last selected in the Media menu. It's also a convenient short-cut
+ to the media menu itself.
+
+
+Package Temp: Where package temporary files should go
+
+ Some packages, like emacs, can use a LOT of temporary space - up to
+ 20 or 30MB. If you are going to configure a small / directory and no
+ separate /var (and hence a small /var/tmp), then you may wish to set
+ this to point at another location (say, /usr/tmp).
+
+
+Re-scan Devices:
+
+ Reprobe the system for devices.
+
+
+Use Defaults: Use default values.
+
+ Reset all options back to their default values.
diff --git a/usr.sbin/bsdconfig/include/tcp.hlp b/usr.sbin/bsdconfig/include/tcp.hlp
new file mode 100644
index 0000000..6d0ba10
--- /dev/null
+++ b/usr.sbin/bsdconfig/include/tcp.hlp
@@ -0,0 +1,33 @@
+This screen allows you to set up your general network parameters
+(hostname, domain name, DNS server, etc) as well as the settings for a
+given interface (which was selected from the menu before this screen).
+
+PLIP/SLIP users - please read through to the end of this doc!
+
+The "options" field is kind of special (read: a hack :-):
+
+Any valid options to ifconfig can be specified here, so if you need
+to do something "special" to get your interface working, then here
+is the place to do it.
+
+If you're running SLIP or PLIP, you also need to use it for specifying
+the remote end of the link (simply type the foreign IP address in).
+In the specific case where you're running PLIP with a Linux host peer
+rather than a FreeBSD one, you also must add the "-link0" flag after the
+foreign address.
+
+If you're dealing with an ethernet adaptor with multiple media
+connectors (e.g. AUI, 10BT, 10B2, etc), you can use this field to
+specify which one to use. Examples of valid strings include:
+
+ "media 10base5/AUI" - Select the AUI port.
+ "media 10baseT/UTP" - Select the twisted pair port.
+ "media 10base2/BNC" - Select the BNC connector.
+ "media 100baseTX" - Select 100BaseT on a 100/10 dual adaptor.
+
+If you have a wireless interface and must specify arguments such as a
+WEP key here, you may use something like:
+
+ "wepmode on wepkey 0xFEEDFACE"
+
+When you're done with this form, select OK.
diff --git a/usr.sbin/bsdconfig/include/usage.hlp b/usr.sbin/bsdconfig/include/usage.hlp
new file mode 100644
index 0000000..4412da7
--- /dev/null
+++ b/usr.sbin/bsdconfig/include/usage.hlp
@@ -0,0 +1,64 @@
+HOW TO USE THIS SYSTEM
+======================
+
+[press the PageDown key to go to the next screen when you finish
+ reading this one]
+
+The following keys are recognized in most of the dialogs you'll
+encounter during this installation:
+
+KEY ACTION
+--- ------
+SPACE Select or toggle the current item.
+ENTER Finish with a menu or item.
+UP ARROW Move to previous item (or up, in a text display box).
+DOWN ARROW Move to next item (or down, in a text display box).
+TAB Move to next item or group.
+RIGHT ARROW Move to next item or group (same as TAB).
+SHIFT-TAB Move to previous item or group.
+LEFT ARROW Move to previous item or group (same as SHIFT-TAB).
+PAGE UP In text display boxes, scrolls up one page.
+PAGE DOWN In text display boxes, scrolls down one page.
+F1 Display associated help text.
+
+If you see small "^(-)" or "v(+)" symbols at the edges of a menu, it
+means that there are more items above or below the current one that
+aren't being shown (due to insufficient screen space). In text
+display boxes, the amount of text above the current point will be
+displayed as a percentage in the lower right corner. Using the
+Up/Down arrow keys will cause the object to scroll by line. The
+PageUp and PageDown keys will scroll by entire screens.
+
+Selecting OK in a menu will confirm whatever action it's controlling.
+Selecting Cancel will cancel the operation and generally return you to
+the previous menu. Use TAB to move the cursor around and select the
+buttons.
+
+Most screens offer a Help button - USE IT! It generally offers useful
+context-specific hints on what to do and if you're at all unsure about
+what to do at a given configuration menu, choose Help!
+
+
+SPECIAL FEATURES:
+=================
+
+It is possible to select a menu item by typing the first character of
+its name, if unique. This will generally be an item number.
+
+The console driver contains a scroll-back buffer for reviewing things
+that may have scrolled off the screen. To use scroll-back, press the
+"Scroll Lock" key on your keyboard and use the arrow or Page Up/Page
+Down keys to move through the saved text. To leave scroll-back mode,
+press the Scroll Lock key again. This feature is most useful for
+reading back through your boot messages (go ahead, try it now!) though
+it's also useful when dealing with sub-shells or other "expert modes"
+that don't use menus and tend to scroll their output off the top of
+the screen.
+
+FreeBSD also supports multiple "virtual consoles" which you can use
+in order to have several active sessions at once. Use ALT-F<n> to
+switch between screens, where `F<n>' is the function key corresponding
+to the screen you wish to see. By default, the system comes with 8
+virtual consoles enabled - you can enable more by editing the
+/etc/ttys file and turning the "off" field to "on" in the relevant vty
+entries (up to 12).
diff --git a/usr.sbin/bsdconfig/includes/INDEX b/usr.sbin/bsdconfig/includes/INDEX
new file mode 100644
index 0000000..a6a6c49
--- /dev/null
+++ b/usr.sbin/bsdconfig/includes/INDEX
@@ -0,0 +1,57 @@
+# Copyright (c) 2013-2015 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+#
+# Title that will be shown in the bsdconfig menu.
+#
+menu_title=""
+
+#
+# A short descriptive line shown at the bottom of the bsdconfig menu. keep it
+# short because any line longer than the terminal width will be truncated.
+#
+menu_help=""
+
+#
+# Two-part variable that defines an action to take when `keyword' is passed on
+# a bsdconfig command line. Variable takes the form "keyword|command" and
+# multiple occurrences of the variable (with different `keyword's, or different
+# `keyword's AND `command's) are allowed. If `command' begins with a '/' then
+# the full path to the program is needed. If `command' begins with anything
+# else it is a path relative to the directory this INDEX file is in. `keyword'
+# can be i18n'ed but `command' is the name of a script.
+#
+menu_selection="includes|includes"
+menu_selection="api|includes"
+
+#
+# ------------ Items below this line do NOT need i18n translation ------------
+#
+# Name of the program to be run when this menu choice is selected. If it begins
+# with a '/' then the full path to the program is needed. If it begins with
+# anything else it is a path relative to the directory this INDEX file is in.
+#
+menu_program=""
diff --git a/usr.sbin/bsdconfig/includes/Makefile b/usr.sbin/bsdconfig/includes/Makefile
new file mode 100644
index 0000000..367776c
--- /dev/null
+++ b/usr.sbin/bsdconfig/includes/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+SUBDIR= include
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/includes
+FILES= INDEX USAGE
+
+SCRIPTSDIR= ${FILESDIR}
+SCRIPTS= includes.sh
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/includes/Makefile.depend b/usr.sbin/bsdconfig/includes/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/includes/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/includes/USAGE b/usr.sbin/bsdconfig/includes/USAGE
new file mode 100644
index 0000000..4af1b73
--- /dev/null
+++ b/usr.sbin/bsdconfig/includes/USAGE
@@ -0,0 +1,71 @@
+# Copyright (c) 2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+Usage: bsdconfig @PROGRAM_NAME@ [OPTIONS] [include ...]
+
+OPTIONS:
+ -a Always use color even when output is not to a terminal.
+ -d Print description for each function selected. Implies `-f'.
+ -f Show functions for selected includes.
+ -F pattern
+ If `-f', only print functions matching pattern. Without `-f'
+ print only includes containing functions matching pattern.
+ -h Print this usage statement and exit.
+ -n Disable the use of color.
+
+EXAMPLES:
+ View a list of available includes:
+
+ bsdconfig @PROGRAM_NAME@
+
+ View functions for all available includes (function names are
+ highlighted):
+
+ bsdconfig @PROGRAM_NAME@ -f
+
+ View functions with less(1) (function names are not highlighted):
+
+ bsdconfig @PROGRAM_NAME@ -f | less
+
+ View functions with less(1) and color:
+
+ bsdconfig @PROGRAM_NAME@ -af | less -R
+
+ View functions from `common.subr':
+
+ bsdconfig @PROGRAM_NAME@ common.subr
+
+ NB: The `-f' flag is implied when given an include.
+
+ Show only functions containing the word `show' in common.subr:
+
+ bsdconfig @PROGRAM_NAME@ -F show common
+
+ NB: The `.subr' suffix on the end of the include is optional.
+
+ Show descriptions of each of the `show' functions:
+
+ bsdconfig @PROGRAM_NAME@ -dF show
diff --git a/usr.sbin/bsdconfig/includes/include/Makefile b/usr.sbin/bsdconfig/includes/include/Makefile
new file mode 100644
index 0000000..3b8b3ec
--- /dev/null
+++ b/usr.sbin/bsdconfig/includes/include/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/includes/include
+FILES= messages.subr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/includes/include/Makefile.depend b/usr.sbin/bsdconfig/includes/include/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/includes/include/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/includes/include/messages.subr b/usr.sbin/bsdconfig/includes/include/messages.subr
new file mode 100644
index 0000000..8fa881b
--- /dev/null
+++ b/usr.sbin/bsdconfig/includes/include/messages.subr
@@ -0,0 +1,28 @@
+# Copyright (c) 2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+msg_functions_in="Functions in %s:"
+msg_functions_in_matching="Functions in %s matching \`%s':"
diff --git a/usr.sbin/bsdconfig/includes/includes.sh b/usr.sbin/bsdconfig/includes/includes.sh
new file mode 100755
index 0000000..6e9906f
--- /dev/null
+++ b/usr.sbin/bsdconfig/includes/includes.sh
@@ -0,0 +1,205 @@
+#!/bin/sh
+#-
+# Copyright (c) 2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+# Prevent common.subr from auto initializing debugging (this is not an inter-
+# active utility that requires debugging; also `-d' has been repurposed).
+#
+DEBUG_SELF_INITIALIZE=NO
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="includes"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ GLOBALS
+
+#
+# Options
+#
+USE_COLOR=1
+SHOW_DESC=
+SHOW_FUNCS=
+FUNC_PATTERN=
+
+############################################################ FUNCTIONS
+
+# show_functions $file
+#
+# Show the functions in the given include file.
+#
+show_include()
+{
+ local file="${1#./}"
+
+ local pattern="${FUNC_PATTERN:-.*}"
+ output=$( awk \
+ -v use_color=${USE_COLOR:-0} \
+ -v re="$pattern" \
+ -v show_desc=${SHOW_DESC:-0} '
+ function _asorti(src, dest)
+ {
+ k = nitems = 0;
+
+ # Copy src indices to dest and calculate array length
+ for (i in src) dest[++nitems] = i
+
+ # Sort the array of indices (dest) using insertion sort method
+ for (i = 1; i <= nitems; k = i++)
+ {
+ idx = dest[i]
+ while ((k > 0) && (dest[k] > idx))
+ {
+ dest[k+1] = dest[k]
+ k--
+ }
+ dest[k+1] = idx
+ }
+
+ return nitems
+ }
+ /^$/,/^#/ {
+ if ($0 ~ /^# f_/) {
+ if (!match($2, re)) next
+ fn = $2
+ if (use_color)
+ syntax[fn] = sprintf("+%s%s%s\n",
+ substr($0, 2, RSTART),
+ substr($0, 2 + RSTART, RLENGTH),
+ substr($0, 2 + RSTART + RLENGTH))
+ else
+ syntax[fn] = "+" substr($0, 2) "\n"
+ if (show_desc)
+ print_more = 1
+ else
+ print_more = substr($0, length($0)) == "\\"
+ }
+ if (show_desc && print_more) {
+ getline
+ while ($0 ~ /^#/) {
+ syntax[fn] = syntax[fn] " " substr($0, 2) "\n"
+ getline
+ }
+ print_more = 0
+ } else while (print_more) {
+ getline
+ syntax[fn] = syntax[fn] " " substr($0, 2) "\n"
+ print_more = substr($0, length($0)) == "\\"
+ }
+ }
+ END {
+ n = _asorti(syntax, sorted_indices)
+ for (i = 1; i <= n; i++)
+ printf "%s", syntax[sorted_indices[i]]
+ }' "$file" )
+ if [ "$output" ]; then
+ if [ ! "$SHOW_FUNCS" ]; then
+ echo "$file"
+ return $SUCCESS
+ fi
+ if [ "$FUNC_PATTERN" ]; then
+ printf ">>> $msg_functions_in_matching\n" \
+ "$file" "$FUNC_PATTERN"
+ else
+ printf ">>> $msg_functions_in\n" "$file"
+ fi
+ echo "$output"
+ echo # blank line to simplify awk(1)-based reparse
+ fi
+}
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+# Are we in a terminal?
+[ -t 1 ] || USE_COLOR=
+
+#
+# Process command-line arguments
+#
+while getopts adfF:hn flag; do
+ case "$flag" in
+ a) USE_COLOR=1 ;;
+ d) SHOW_DESC=1 SHOW_FUNCS=1 ;;
+ f) SHOW_FUNCS=1 ;;
+ F) FUNC_PATTERN="$OPTARG" ;;
+ n) USE_COLOR= ;;
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+# cd(1) to `share' dir so relative paths work for find and positional args
+cd $BSDCFG_SHARE || f_die # Pedantic
+
+#
+# If given an argument, operate on it specifically (implied `-f') and exit
+#
+[ $# -gt 0 ] && SHOW_FUNCS=1
+for include in "$@"; do
+ # See if they've just omitted the `*.subr' suffix
+ [ -f "$include.subr" -a ! -f "$include" ] && include="$include.subr"
+ if [ ! -f "$include" ]; then
+ printf "$msg_no_such_file_or_directory\n" "$0" "$include"
+ exit $FAILURE
+ elif [ ! -r "$include" ]; then
+ printf "$msg_permission_denied\n" "$0" "$include"
+ exit $FAILURE
+ fi
+ show_include "$include" || f_die
+done
+
+# Exit if we processed some include arguments
+[ $# -gt 0 ] && exit $SUCCESS
+
+#
+# Operate an all known include files
+# NB: If we get this far, we had no include arguments
+#
+find -s . -type f -and -iname '*.subr' | while read file; do
+ if [ "$SHOW_FUNCS" -o "$FUNC_PATTERN" ]; then
+ show_include "$file"
+ else
+ echo "${file#./}"
+ fi
+done
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/mouse/INDEX b/usr.sbin/bsdconfig/mouse/INDEX
new file mode 100644
index 0000000..4a33a79
--- /dev/null
+++ b/usr.sbin/bsdconfig/mouse/INDEX
@@ -0,0 +1,62 @@
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+#
+# Title that will be shown in the bsdconfig menu.
+#
+menu_title="Mouse"
+
+#
+# A short descriptive line shown at the bottom of the bsdconfig menu. keep it
+# short because any line longer than the terminal width will be truncated.
+#
+menu_help="Configure the Mouse"
+
+#
+# Two-part variable that defines an action to take when `keyword' is passed on
+# a bsdconfig command line. Variable takes the form "keyword|command" and
+# multiple occurrences of the variable (with different `keyword's, or different
+# `keyword's AND `command's) are allowed. If `command' begins with a '/' then
+# the full path to the program is needed. If `command' begins with anything
+# else it is a path relative to the directory this INDEX file is in. `keyword'
+# can be i18n'ed but `command' is the name of a script.
+#
+menu_selection="mouse|mouse"
+menu_selection="mouse_enable|enable"
+menu_selection="mouse_type|type"
+menu_selection="mouse_port|port"
+menu_selection="mouse_flags|flags"
+menu_selection="mouse_disable|disable"
+
+#
+# ------------ Items below this line do NOT need i18n translation ------------
+#
+# Name of the program to be run when this menu choice is selected. If it begins
+# with a '/' then the full path to the program is needed. If it begins with
+# anything else it is a path relative to the directory this INDEX file is in.
+#
+menu_program="mouse"
diff --git a/usr.sbin/bsdconfig/mouse/Makefile b/usr.sbin/bsdconfig/mouse/Makefile
new file mode 100644
index 0000000..97fa52b
--- /dev/null
+++ b/usr.sbin/bsdconfig/mouse/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+SUBDIR= include
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/110.mouse
+FILES= INDEX USAGE
+
+SCRIPTSDIR= ${FILESDIR}
+SCRIPTS= disable enable flags mouse port type
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/mouse/Makefile.depend b/usr.sbin/bsdconfig/mouse/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/mouse/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/mouse/USAGE b/usr.sbin/bsdconfig/mouse/USAGE
new file mode 100644
index 0000000..a06d8af
--- /dev/null
+++ b/usr.sbin/bsdconfig/mouse/USAGE
@@ -0,0 +1,37 @@
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+Usage: bsdconfig @PROGRAM_NAME@ [OPTIONS]
+
+OPTIONS:
+ -d Provide lots of debugging info on standard-out when running.
+ -D file Send debugging info to file. If file begins with a plus-sign
+ debug info is sent to both standard-out and file (minus the
+ leading plus).
+ -h Print this usage statement and exit.
+ -S Secure X11 mode (implies `-X'). As root, always prompt-for
+ and validate sudo(8) username/password before starting.
+ -X Use Xdialog(1) in place of dialog(1).
diff --git a/usr.sbin/bsdconfig/mouse/disable b/usr.sbin/bsdconfig/mouse/disable
new file mode 100755
index 0000000..f2c528f
--- /dev/null
+++ b/usr.sbin/bsdconfig/mouse/disable
@@ -0,0 +1,97 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="110.mouse"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ CONFIGURATION
+
+#
+# Location of moused(8) pidfile
+#
+MOUSED_PIDFILE=/var/run/moused.pid
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_mouse_disable"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Stop the mouse daemon
+#
+if [ -r "$MOUSED_PIDFILE" ]; then
+ f_eval_catch -dk pid "$0" cat 'cat "%s"' "$MOUSED_PIDFILE" &&
+ f_isinteger "$pid" &&
+ [ $pid -gt 0 ] &&
+ f_eval_catch -d "$0" kill 'kill %s' $pid
+fi
+f_eval_catch "$0" f_sysrc_set 'f_sysrc_set moused_enable NO' || f_die
+f_eval_catch "$0" f_sysrc_set 'f_sysrc_set moused_type NO' || f_die
+f_eval_catch "$0" f_sysrc_delete 'f_sysrc_delete moused_port' || f_die
+f_eval_catch "$0" f_sysrc_delete 'f_sysrc_delete moused_flags' || f_die
+
+#
+# Message box
+#
+f_dialog_title "$msg_message"
+f_dialog_msgbox "$msg_mouse_daemon_is_disabled" "$hline_press_enter_or_space"
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/mouse/enable b/usr.sbin/bsdconfig/mouse/enable
new file mode 100755
index 0000000..9ad6857
--- /dev/null
+++ b/usr.sbin/bsdconfig/mouse/enable
@@ -0,0 +1,128 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="110.mouse"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ CONFIGURATION
+
+#
+# Location of moused(8) pidfile
+#
+MOUSED_PIDFILE=/var/run/moused.pid
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_mouse_enable"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Get the type, port, and flags
+#
+type=$( f_sysrc_get moused_type )
+case "$type" in
+[Nn][Oo]|"") f_die 1 "$msg_please_select_protocol_and_port_first" ;;
+esac
+port=$( f_sysrc_get moused_port )
+[ "$port" ] || f_die 1 "$msg_please_select_protocol_and_port_first"
+flags=$( f_sysrc_get moused_flags )
+
+#
+# Start the mouse daemon
+#
+f_dialog_info "$msg_trying_to_start_the_mouse_daemon"
+if [ -r "$MOUSED_PIDFILE" ]; then
+ f_eval_catch -dk pid "$0" cat 'cat "%s"' "$MOUSED_PIDFILE" &&
+ f_isinteger "$pid" &&
+ [ $pid -gt 0 ] &&
+ f_eval_catch -d "$0" kill 'kill %s' $pid
+fi
+f_eval_catch -d "$0" vidcontrol 'vidcontrol -m on'
+f_eval_catch -d "$0" moused \
+ 'moused -t "%s" -p "%s" %s' "$type" "$port" "$flags"
+
+#
+# Confirm with the user that the mouse is working
+#
+f_dialog_title "$msg_user_confirmation_requested"
+f_dialog_yesno "$msg_now_move_the_mouse"
+retval=$?
+f_dialog_title_restore
+
+#
+# Stop the mouse daemon
+#
+f_eval_catch -d "$0" vidcontrol 'vidcontrol -m off'
+if [ $retval -eq $DIALOG_OK ]; then
+ f_eval_catch "$0" f_sysrc_set 'f_sysrc_set moused_enable YES' || f_die
+ f_eval_catch "$0" ln \
+ 'ln -fs /dev/sysmouse /dev/mouse' || f_die # backwards compat
+else
+ if [ -r "$MOUSED_PIDFILE" ]; then
+ f_eval_catch -dk pid "$0" cat 'cat "%s"' "$MOUSED_PIDFILE" &&
+ f_isinteger "$pid" &&
+ [ $pid -gt 0 ] &&
+ f_eval_catch -d "$0" kill 'kill %s' $pid
+ fi
+ f_eval_catch "$0" f_sysrc_set 'f_sysrc_set moused_enable NO' || f_die
+fi
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/mouse/flags b/usr.sbin/bsdconfig/mouse/flags
new file mode 100755
index 0000000..181ee3f
--- /dev/null
+++ b/usr.sbin/bsdconfig/mouse/flags
@@ -0,0 +1,95 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="110.mouse"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ CONFIGURATION
+
+#
+# Location of moused(8) pidfile
+#
+MOUSED_PIDFILE=/var/run/moused.pid
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_mouse_flags"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Get the current flags
+#
+flags=$( f_sysrc_get moused_flags )
+
+#
+# Prompt the user with the current value
+#
+f_dialog_title "$msg_value_required"
+f_dialog_input flags "$msg_please_specify_the_mouse_daemon_flags" \
+ "$flags" || f_die
+f_dialog_title_restore
+
+#
+# Save the new value
+#
+f_eval_catch "$0" f_sysrc_set 'f_sysrc_set moused_flags "%s"' "$flags" || f_die
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/mouse/include/Makefile b/usr.sbin/bsdconfig/mouse/include/Makefile
new file mode 100644
index 0000000..2cc852f
--- /dev/null
+++ b/usr.sbin/bsdconfig/mouse/include/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/110.mouse/include
+FILES= messages.subr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/mouse/include/Makefile.depend b/usr.sbin/bsdconfig/mouse/include/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/mouse/include/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/mouse/include/messages.subr b/usr.sbin/bsdconfig/mouse/include/messages.subr
new file mode 100644
index 0000000..82cbad3
--- /dev/null
+++ b/usr.sbin/bsdconfig/mouse/include/messages.subr
@@ -0,0 +1,93 @@
+# Copyright (c) 2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+hline_press_enter_or_space="Press enter or space"
+msg_auto="Auto"
+msg_auto_desc="Bus mouse, PS/2 style mouse or PnP serial mouse"
+msg_busmouse="BusMouse"
+msg_busmouse_desc="Logitech, ATI or MS bus mouse (/dev/mse0)"
+msg_cancel="Cancel"
+msg_com1="COM1"
+msg_com1_desc="Serial mouse on COM1 (/dev/cuau0)"
+msg_com2="COM2"
+msg_com2_desc="Serial mouse on COM2 (/dev/cuau1)"
+msg_com3="COM3"
+msg_com3_desc="Serial mouse on COM3 (/dev/cuau2)"
+msg_com4="COM4"
+msg_com4_desc="Serial mouse on COM4 (/dev/cuau3)"
+msg_disable="Disable"
+msg_disable_the_mouse_daemon="Disable the mouse daemon"
+msg_enable="Enable"
+msg_exit="Exit"
+msg_exit_this_menu="Exit this menu"
+msg_flags="Flags"
+msg_glidepoint="GlidePoint"
+msg_glidepoint_desc="ALPS GlidePoint pad (serial)"
+msg_hitachi="Hitachi"
+msg_hitachi_desc="Hitachi tablet (serial)"
+msg_intellimouse="IntelliMouse"
+msg_intellimouse_desc="Microsoft IntelliMouse (serial)"
+msg_logitech="Logitech"
+msg_logitech_desc="Logitech protocol (old models) (serial)"
+msg_menu_text="You can cut and paste text in the text console by running the mouse\ndaemon. Specify a port and a protocol type of your mouse and enable\nthe mouse daemon. If you don't want this feature, select 6 to disable\nthe daemon.\nOnce you've enabled the mouse daemon, you can specify \"/dev/sysmouse\"\nas your mouse device and \"SysMouse\" or \"MouseSystems\" as mouse\nprotocol when running the X configuration utility (see Configuration\nmenu)."
+msg_message="Message"
+msg_microsoft="Microsoft"
+msg_microsoft_desc="Microsoft protocol (serial)"
+msg_mm_series="MM Series"
+msg_mm_series_desc="MM Series protocol (serial)"
+msg_mouse_daemon_is_disabled="The mouse daemon is disabled."
+msg_mouse_disable="Mouse Disable"
+msg_mouse_enable="Mouse Enable"
+msg_mouse_flags="Mouse Flags"
+msg_mouseman="MouseMan"
+msg_mouseman_desc="Logitech MouseMan/TrackMan models (serial)"
+msg_mousesystems="MouseSystems"
+msg_mousesystems_desc="MouseSystems protocol (serial)"
+msg_now_move_the_mouse="Now move the mouse and see if it works.\n(Note that buttons don't have any effect for now.)\n\n Is the mouse cursor moving?\n"
+msg_ok="OK"
+msg_please_configure_your_mouse="Please configure your mouse"
+msg_please_select_protocol_and_port_first="Please select a mouse protocol and a port first."
+msg_please_specify_the_mouse_daemon_flags="Please specify the mouse daemon flags. If you would like to\nemulate 3 buttons, use -3 here.\n "
+msg_port="Port"
+msg_port_menu_text="The built-in pointing device of laptop/notebook computers is usually\na PS/2 style device."
+msg_protocol_menu_text="If your mouse is attached to the PS/2 mouse port or the bus mouse port,\nyou should always choose \"Auto\", regardless of the model and the brand\nof the mouse. All other protocol types are for serial mice and should\nnot be used with the PS/2 port mouse or the bus mouse. If you have\na serial mouse and are not sure about its protocol, you should also try\n\"Auto\". It may not work for the serial mouse if the mouse does not\nsupport the PnP standard. But, it won't hurt. Many 2-button serial mice\nare compatible with \"Microsoft\" or \"MouseMan\". 3-button serial mice\nmay be compatible with \"MouseSystems\" or \"MouseMan\". If the serial\nmouse has a wheel, it may be compatible with \"IntelliMouse\"."
+msg_ps2="PS/2"
+msg_ps2_desc="PS/2 style mouse (/dev/psm0)"
+msg_select_a_protocol_type_for_your_mouse="Select a protocol type for your mouse"
+msg_select_mouse_port="Select mouse port"
+msg_select_mouse_protocol_type="Select mouse protocol type"
+msg_select_your_mouse_port_from_the_following_menu="Select your mouse port from the following menu"
+msg_set_additional_flags="Set additional flags"
+msg_test_and_run_the_mouse_daemon="Test and run the mouse daemon"
+msg_thinkingmouse="ThinkingMouse"
+msg_thinkingmouse_desc="Kensington ThinkingMouse (serial)"
+msg_trying_to_start_the_mouse_daemon="Trying to start the mouse daemon..."
+msg_type="Type"
+msg_unknown_mouse_menu_selection="Unknown mouse menu selection"
+msg_unknown_mouse_port_selection="Unknown mouse port selection"
+msg_unknown_mouse_protocol_selection="Unknown mouse protocol selection"
+msg_user_confirmation_requested="User Confirmation Requested"
+msg_value_required="Value Required"
diff --git a/usr.sbin/bsdconfig/mouse/mouse b/usr.sbin/bsdconfig/mouse/mouse
new file mode 100755
index 0000000..0dce574
--- /dev/null
+++ b/usr.sbin/bsdconfig/mouse/mouse
@@ -0,0 +1,144 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="110.mouse"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ FUNCTIONS
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local prompt="$msg_menu_text"
+ local menu_list="
+ 'X $msg_exit' '$msg_exit_this_menu'
+ '2 $msg_enable' '$msg_test_and_run_the_mouse_daemon'
+ '3 $msg_type' '$msg_select_mouse_protocol_type'
+ '4 $msg_port' '$msg_select_mouse_port'
+ '5 $msg_flags' '$msg_set_additional_flags'
+ '6 $msg_disable' '$msg_disable_the_mouse_daemon'
+ " # END-QUOTE
+ local defaultitem= # Calculated below
+ local hline=
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ # Obtain default-item from previously stored selection
+ f_dialog_default_fetch defaultitem
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize menu_choice
+ f_dialog_menutag_store "$menu_choice"
+ f_dialog_default_store "$menu_choice"
+ return $retval
+}
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_please_configure_your_mouse"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Launch application main menu
+#
+while :; do
+ dialog_menu_main || f_die
+ f_dialog_menutag_fetch mtag
+
+ command=
+ case "$mtag" in
+ "X $msg_exit") break ;;
+ "2 $msg_enable") command=enable ;; # Test and run the mouse daemon
+ "3 $msg_type") command=type ;; # Select mouse protocol type
+ "4 $msg_port") command=port ;; # Select mouse port
+ "5 $msg_flags") command=flags ;; # Set additional flags
+ "6 $msg_disable") command=disable ;; # Disable the mouse daemon
+ esac
+
+ if [ "$command" ]; then
+ $BSDCFG_LIBE/$APP_DIR/$command ${USE_XDIALOG:+-X}
+ else
+ f_die 1 "$msg_unknown_mouse_menu_selection"
+ fi
+done
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/mouse/port b/usr.sbin/bsdconfig/mouse/port
new file mode 100755
index 0000000..1040795
--- /dev/null
+++ b/usr.sbin/bsdconfig/mouse/port
@@ -0,0 +1,154 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="110.mouse"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ FUNCTIONS
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local prompt="$msg_port_menu_text"
+ local menu_list="
+ '1 $msg_ps2' '$msg_ps2_desc'
+ '2 $msg_com1' '$msg_com1_desc'
+ '3 $msg_com2' '$msg_com2_desc'
+ '4 $msg_com3' '$msg_com3_desc'
+ '5 $msg_com4' '$msg_com4_desc'
+ '6 $msg_busmouse' '$msg_busmouse_desc'
+ " # END-QUOTE
+ local defaultitem= # Calculated below
+ local hline=
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ case "$( f_sysrc_get moused_port )" in
+ /dev/psm0) defaultitem="1 $msg_ps2" ;;
+ /dev/cuau0) defaultitem="2 $msg_com1" ;;
+ /dev/cuau1) defaultitem="3 $msg_com2" ;;
+ /dev/cuau2) defaultitem="4 $msg_com3" ;;
+ /dev/cuau3) defaultitem="5 $msg_com4" ;;
+ /dev/mse0) defaultitem="6 $msg_busmouse" ;;
+ esac
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_menutag_store -s "$menu_choice"
+ return $retval
+}
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_select_your_mouse_port_from_the_following_menu"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Launch application main menu
+#
+dialog_menu_main || f_die
+f_dialog_menutag_fetch mtag
+
+moused_port_to_set=
+case "$mtag" in
+"1 $msg_ps2") # PS/2 style mouse (/dev/psm0)
+ moused_port_to_set="/dev/psm0" ;;
+"2 $msg_com1") # Serial mouse on COM1 (/dev/cuau0)
+ moused_port_to_set="/dev/cuau0" ;;
+"3 $msg_com2") # Serial mouse on COM2 (/dev/cuau1)
+ moused_port_to_set="/dev/cuau1" ;;
+"4 $msg_com3") # Serial mouse on COM3 (/dev/cuau2)
+ moused_port_to_set="/dev/cuau2" ;;
+"5 $msg_com4") # Serial mouse on COM4 (/dev/cuau3)
+ moused_port_to_set="/dev/cuau3" ;;
+"6 $msg_busmouse") # Logitech, ATI or MS bus mouse (/dev/mse0)
+ moused_port_to_set="/dev/mse0" ;;
+esac
+
+if [ "$moused_port_to_set" ]; then
+ f_eval_catch "$0" f_sysrc_set \
+ 'f_sysrc_set moused_port "%s"' "$moused_port_to_set" || f_die
+else
+ f_die 1 "$msg_unknown_mouse_port_selection"
+fi
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/mouse/type b/usr.sbin/bsdconfig/mouse/type
new file mode 100755
index 0000000..7d99753
--- /dev/null
+++ b/usr.sbin/bsdconfig/mouse/type
@@ -0,0 +1,170 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="110.mouse"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ FUNCTIONS
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local prompt="$msg_protocol_menu_text"
+ local menu_list="
+ '1 $msg_auto' '$msg_auto_desc'
+ '2 $msg_glidepoint' '$msg_glidepoint_desc'
+ '3 $msg_hitachi' '$msg_hitachi_desc'
+ '4 $msg_intellimouse' '$msg_intellimouse_desc'
+ '5 $msg_logitech' '$msg_logitech_desc'
+ '6 $msg_microsoft' '$msg_microsoft_desc'
+ '7 $msg_mm_series' '$msg_mm_series_desc'
+ '8 $msg_mouseman' '$msg_mouseman_desc'
+ '9 $msg_mousesystems' '$msg_mousesystems_desc'
+ 'A $msg_thinkingmouse' '$msg_thinkingmouse_desc'
+ " # END-QUOTE
+ local defaultitem= # Calculated below
+ local hline=
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ case "$( f_sysrc_get moused_type )" in
+ auto) defaultitem="1 $msg_auto" ;;
+ glidepoint) defaultitem="2 $msg_glidepoint" ;;
+ mmhittab) defaultitem="3 $msg_hitachi" ;;
+ intellimouse) defaultitem="4 $msg_intellimouse" ;;
+ logitech) defaultitem="5 $msg_logitech" ;;
+ microsoft) defaultitem="6 $msg_microsoft" ;;
+ mmseries) defaultitem="7 $msg_mm_series" ;;
+ mouseman) defaultitem="8 $msg_mouseman" ;;
+ mousesystems) defaultitem="9 $msg_mousesystems" ;;
+ thinkingmouse) defaultitem="A $msg_thinkingmouse" ;;
+ esac
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_menutag_store -s "$menu_choice"
+ return $retval
+}
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_select_a_protocol_type_for_your_mouse"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Launch application main menu
+#
+dialog_menu_main || f_die
+f_dialog_menutag_fetch mtag
+
+moused_type_to_set=
+case "$mtag" in
+"1 $msg_auto") # Bus mouse, PS/2 style mouse or PnP serial mouse
+ moused_type_to_set="auto" ;;
+"2 $msg_glidepoint") # ALPS GlidePoint pad (serial)
+ moused_type_to_set="glidepoint" ;;
+"3 $msg_hitachi") # Hitachi tablet (serial)
+ moused_type_to_set="mmhittab" ;;
+"4 $msg_intellimouse") # Microsoft Intellimouse (serial)
+ moused_type_to_set="intellimouse" ;;
+"5 $msg_logitech") # Logitech protocol (old models) (serial)
+ moused_type_to_set="logitech" ;;
+"6 $msg_microsoft") # Microsoft protocol (serial)
+ moused_type_to_set="microsoft" ;;
+"7 $msg_mm_series") # MM Series protocol (serial)
+ moused_type_to_set="mmseries" ;;
+"8 $msg_mouseman") # Logitech MouseMan/TrackMan models (serial)
+ moused_type_to_set="mouseman" ;;
+"9 $msg_mousesystems") # MouseSystems protocol (serial)
+ moused_type_to_set="mousesystems" ;;
+"A $msg_thinkingmouse") # Kensignton ThinkingMouse (serial)
+ moused_type_to_set="thinkingmouse" ;;
+esac
+
+if [ "$moused_type_to_set" ]; then
+ f_eval_catch "$0" f_sysrc_set \
+ 'f_sysrc_set moused_type "%s"' "$moused_type_to_set" || f_die
+else
+ f_die 1 "$msg_unknown_mouse_protocol_selection"
+fi
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/networking/INDEX b/usr.sbin/bsdconfig/networking/INDEX
new file mode 100644
index 0000000..8527124
--- /dev/null
+++ b/usr.sbin/bsdconfig/networking/INDEX
@@ -0,0 +1,61 @@
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+#
+# Title that will be shown in the bsdconfig menu.
+#
+menu_title="Networking Management"
+
+#
+# A short descriptive line shown at the bottom of the bsdconfig menu. keep it
+# short because any line longer than the terminal width will be truncated.
+#
+menu_help="Setup Networking interfaces, services, etc."
+
+#
+# Two-part variable that defines an action to take when `keyword' is passed on
+# a bsdconfig command line. Variable takes the form "keyword|command" and
+# multiple occurrences of the variable (with different `keyword's, or different
+# `keyword's AND `command's) are allowed. If `command' begins with a '/' then
+# the full path to the program is needed. If `command' begins with anything
+# else it is a path relative to the directory this INDEX file is in. `keyword'
+# can be i18n'ed but `command' is the name of a script.
+#
+menu_selection="networking|networking"
+menu_selection="defaultrouter|defaultrouter"
+menu_selection="hostname|hostname"
+menu_selection="nameservers|nameservers"
+menu_selection="netdev|devices"
+
+#
+# ------------ Items below this line do NOT need i18n translation ------------
+#
+# Name of the program to be run when this menu choice is selected. If it begins
+# with a '/' then the full path to the program is needed. If it begins with
+# anything else it is a path relative to the directory this INDEX file is in.
+#
+menu_program="networking"
diff --git a/usr.sbin/bsdconfig/networking/Makefile b/usr.sbin/bsdconfig/networking/Makefile
new file mode 100644
index 0000000..8a88067
--- /dev/null
+++ b/usr.sbin/bsdconfig/networking/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+SUBDIR= include share
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/120.networking
+FILES= INDEX USAGE
+
+SCRIPTSDIR= ${FILESDIR}
+SCRIPTS= defaultrouter devices hostname nameservers networking
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/networking/Makefile.depend b/usr.sbin/bsdconfig/networking/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/networking/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/networking/USAGE b/usr.sbin/bsdconfig/networking/USAGE
new file mode 100644
index 0000000..8720946
--- /dev/null
+++ b/usr.sbin/bsdconfig/networking/USAGE
@@ -0,0 +1,37 @@
+# Copyright (c) 2006-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+Usage: bsdconfig @PROGRAM_NAME@ [OPTIONS]
+
+OPTIONS:
+ -d Provide lots of debugging info on standard-out when running.
+ -D file Send debugging info to file. If file begins with a plus-sign
+ debug info is sent to both standard-out and file (minus the
+ leading plus).
+ -h Print this usage statement and exit.
+ -S Secure X11 mode (implies `-X'). As root, always prompt-for
+ and validate sudo(8) username/password before starting.
+ -X Use Xdialog(1) in place of dialog(1).
diff --git a/usr.sbin/bsdconfig/networking/defaultrouter b/usr.sbin/bsdconfig/networking/defaultrouter
new file mode 100755
index 0000000..b65981f
--- /dev/null
+++ b/usr.sbin/bsdconfig/networking/defaultrouter
@@ -0,0 +1,76 @@
+#!/bin/sh
+#-
+# Copyright (c) 2006-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+# Prevent device.subr (included indirectly) from auto scanning on load
+DEVICE_SELF_SCAN_ALL=NO
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/networking/routing.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="120.networking"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_default_router"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Change the default router/gateway
+#
+f_dialog_input_defaultrouter
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/networking/devices b/usr.sbin/bsdconfig/networking/devices
new file mode 100755
index 0000000..433f25c
--- /dev/null
+++ b/usr.sbin/bsdconfig/networking/devices
@@ -0,0 +1,164 @@
+#!/bin/sh
+#-
+# Copyright (c) 2006-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+# Prevent device.subr (included indirectly) from auto scanning; this will be
+# performed indirectly later via f_dialog_menu_netdev() -- but only after we've
+# successfully completed f_mustberoot_init().
+#
+DEVICE_SELF_SCAN_ALL=NO
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+f_include $BSDCFG_SHARE/media/tcpip.subr
+f_include $BSDCFG_SHARE/networking/device.subr
+f_include $BSDCFG_SHARE/networking/ipaddr.subr
+f_include $BSDCFG_SHARE/networking/media.subr
+f_include $BSDCFG_SHARE/networking/netmask.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="120.networking"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line options
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_networking_devices"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Launch application main menu
+#
+defaultitem=
+while :; do
+ f_dialog_menu_netdev "$defaultitem" || break
+ f_dialog_menutag_fetch interface
+ defaultitem="$interface"
+
+ #
+ # dialog_menu_netdev adds an asterisk (*) to the right of the
+ # device name if the interface is active. Remove the asterisk
+ # from the device name if present.
+ #
+ case "$interface" in
+ *\*) interface="${interface%?}" ;;
+ esac
+
+ #
+ # Obtain initial interface settings to be configured. These will be
+ # passed to the f_dialog_menu_netdev_edit function-call below which
+ # will block until the user has either cancelled or finished editing
+ # the values.
+ #
+ # First, attempt to read stored configuration from rc.conf(5) and
+ # fallback to reading the active configuration if not configured in
+ # the rc.conf(5) file(s).
+ #
+ dhcp=
+ _ipaddr=
+ _netmask=
+ _ifconfig=$( f_sysrc_get ifconfig_$interface )
+ if [ "$_ifconfig" ]; then
+ # If DHCP, get IP address/netmask later from ifconfig(8)
+ glob="[Dd][Hh][Cc][Pp]"
+ case "$_ifconfig" in
+ $glob) dhcp=1 ;;
+ [Ss][Yy][Nn][Cc]$glob) dhcp=1 ;;
+ [Nn][Oo][Ss][Yy][Nn][Cc]$glob) dhcp=1 ;;
+ *)
+ #
+ # Get IP address/netmask from rc.conf(5) configuration
+ #
+ dhcp=
+ eval "$(
+ exec 2> /dev/null
+ set -- $_ifconfig
+ while [ $# -gt 0 ]; do
+ case "$1" in
+ inet)
+ shift 1
+ echo "_ipaddr='$1'"
+ ;;
+ netmask)
+ shift 1
+ echo "_netmask='$1'"
+ ;;
+ esac
+ shift 1
+ done
+ )"
+ ;;
+ esac
+ fi
+
+ #
+ # Fill in IP address/netmask from active settings if no
+ # configuration could be extrapolated from rc.conf(5)
+ #
+ [ "$_ipaddr" ] || f_ifconfig_inet $interface _ipaddr
+ [ "$_netmask" ] || f_ifconfig_netmask $interface _netmask
+
+ # Get the extra options (this always comes from rc.conf(5))
+ _options=$( f_ifconfig_options $interface )
+
+ # Block on user-configuration of the probed settings
+ f_dialog_menu_netdev_edit \
+ "$interface" "$_ipaddr" "$_netmask" "$_options" $dhcp
+
+ # Return to root menu if above returns success
+ [ $? -eq $DIALOG_OK ] && break
+done
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/networking/hostname b/usr.sbin/bsdconfig/networking/hostname
new file mode 100755
index 0000000..d0fff01
--- /dev/null
+++ b/usr.sbin/bsdconfig/networking/hostname
@@ -0,0 +1,76 @@
+#!/bin/sh
+#-
+# Copyright (c) 2006-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+# Prevent device.subr (included indirectly) from auto scanning on load
+DEVICE_SELF_SCAN_ALL=NO
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/networking/hostname.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="120.networking"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_hostname_domain"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Change the local hostname
+#
+f_dialog_input_hostname
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/networking/include/Makefile b/usr.sbin/bsdconfig/networking/include/Makefile
new file mode 100644
index 0000000..54f66e1
--- /dev/null
+++ b/usr.sbin/bsdconfig/networking/include/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/120.networking/include
+FILES= messages.subr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/networking/include/Makefile.depend b/usr.sbin/bsdconfig/networking/include/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/networking/include/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/networking/include/messages.subr b/usr.sbin/bsdconfig/networking/include/messages.subr
new file mode 100644
index 0000000..bb94c36
--- /dev/null
+++ b/usr.sbin/bsdconfig/networking/include/messages.subr
@@ -0,0 +1,105 @@
+# Copyright (c) 2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+hline_alnum_punc_tab_enter="Use alpha-numeric, punctuation, TAB or ENTER"
+hline_arrows_tab_enter="Press arrows, TAB or ENTER"
+hline_num_punc_tab_enter="Use numbers, punctuation, TAB or ENTER"
+msg_activate_default_router="Would you like to activate the new defaultrouter right now?\nIf you choose NO or press ESC, changes will be applied\nduring the next boot.\n\n Current Default Router: %s\n New Default Router: %s\n"
+msg_activate_hostname="Would you like to activate the new hostname right now?\nIf you choose NO or press ESC, changes will be applied\nduring the next boot.\n\n Current Hostname: %s\n New Hostname: %s\n\nNOTE: Your shell prompt may still reflect the original\nhostname until your next login."
+msg_activate_hostname_x11warning="WARNING! Activating the new hostname during an X11-Forwarded\n ssh(1) session will cause an X11 authentication error.\n\n Current Hostname: %s\n New Hostname: %s\n\nNOTE: Settings will become active upon reboot or if you\n relaunch this utility either locally or on the console."
+msg_add="Add"
+msg_add_nameserver="Add a new nameserver"
+msg_bring_interface_up="Would you like to bring the %s interface up right now?"
+msg_cancel="Cancel"
+msg_current_default_router="Current Default Route/Gateway: %s"
+msg_current_dhcp_status="Current DHCP status for %s: %s"
+msg_current_ipaddr="Current IP Address for %s: %s"
+msg_current_options="Current Options for %s: %s"
+msg_current_subnet="Current Subnet Mask for %s: %s"
+msg_custom="Custom (Manual)"
+msg_default_router="Default Router/Gateway"
+msg_dhcp="DHCP"
+msg_disabled="Disabled"
+msg_dns_configuration="DNS Nameserver Configuration:\nChoose Exit when finished else Cancel."
+msg_dns_nameservers="DNS nameservers"
+msg_enabled="Enabled"
+msg_exit="Exit"
+msg_hostname_domain="Hostname/Domain"
+msg_hostname_exceeds_max_length="ERROR! The hostname entered exceeds the maximum length of\n255 characters.\n\nInvalid Hostname: %s"
+msg_hostname_label_contains_invalid_chars="ERROR! One or more individual labels within the hostname\n(separated by dots) contains one or more invalid characters.\nLabels are case-insensitive and must contain only 0-9, a-z,\n or dash (though must not begin with or end with a dash).\n\nInvalid Hostname: %s"
+msg_hostname_label_exceeds_max_length="ERROR! One or more individual labels within the hostname\n(separated by dots) exceeds the maximum of 63 characters.\n\nInvalid Hostname: %s"
+msg_hostname_label_is_null="ERROR! One or more individual labels within the hostname\n(separated by dots) are null.\n\nInvalid Hostname: %s"
+msg_hostname_label_starts_or_ends_with_hyphen="ERROR! One or more individual labels within the hostname\n(separated by dots) starts or ends with a hyphen (hyphens\nare allowed, but a label cannot begin or end with a hyphen).\n\nInvalid Hostname: %s"
+msg_internal_error_nsindex_value="FATAL! dialog_input_nameserver_edit_awk: variable\nnsindex must be a whole positive integer greater-\nthan or equal-to zero.\n\nInvalid nsindex: %s"
+msg_ipaddr4="ipaddr"
+msg_ipv4_addr_octet_contains_invalid_chars="ERROR! One or more individual octets within the IPv4 address\n(separated by dots) contains one or more invalid characters.\nOctets must contain only the characters 0-9.\n\nInvalid IP Address: %s"
+msg_ipv4_addr_octet_exceeds_max_value="ERROR! One or more individual octets within the IPv4 address\n(separated by dots) exceeds the maximum of 255.\n\nInvalid IP Address: %s"
+msg_ipv4_addr_octet_is_null="ERROR! One or more individual octets within the IPv4 address\n(separated by dots) are null and/or missing.\n\nInvalid IP Address: %s"
+msg_ipv4_addr_octet_missing_or_extra="ERROR! The IPv4 address entered has either too few (less than\nfour) or too many (more than four) octets, separated by dots.\n\nInvalid IP Address: %s"
+msg_ipv4_mask_field_contains_invalid_chars="ERROR! One or more individual fields within the subnet mask\n(separated by dots) contains one or more invalid characters.\n\nInvalid Subnet Mask: %s"
+msg_ipv4_mask_field_exceeds_max_value="ERROR! One or more individual fields within the subnet mask\n(separated by dots) exceeds the maximum of 255.\n\nInvalid Subnet Mask: %s"
+msg_ipv4_mask_field_invalid_value="ERROR! One or more individual fields within the subnet mask\n(separated by dots) contains one or more invalid integers.\nFields must be one of 0/128/192/224/240/248/252/254/255.\n\nInvalid Subnet Mask: %s"
+msg_ipv4_mask_field_is_null="ERROR! One or more individual fields within the subnet mask\n(separated by dots) are null and/or missing.\n\nInvalid Subnet Mask: %s"
+msg_ipv4_mask_field_missing_or_extra="ERROR! The subnet mask entered has either too few or too many\nfields.\n\nInvalid Subnet Mask: %s"
+msg_ipv6_addr_segment_contains_invalid_chars="ERROR! One or more individual segments within the IP address\n(separated by colons) contains one or more invalid characters.\nSegments must contain only combinations of the characters 0-9,\nA-F, or a-f.\n\nInvalid IPv6 Address: %s"
+msg_ipv6_addr_segment_contains_too_many_chars="ERROR! One or more individual segments within the IP address\n(separated by colons) exceeds the length of 4 hex-digits.\n\nInvalid IPv6 Address: %s"
+msg_ipv6_addr_too_few_or_extra_segments="ERROR! The IP address entered has either too few (less than 3), too\nmany (more than 8), or not enough segments, separated by colons.\n\nInvalid IPv6 Address: %s"
+msg_ipv6_addr_too_many_null_segments="ERROR! Too many/incorrect null segments. A single null\nsegment is allowed within the IP address (separated by\ncolons) but not allowed at the beginning or end (unless\na double-null segment; i.e., \"::*\" or \"*::\").\n\nInvalid IPv6 Address: %s"
+msg_netmask="netmask"
+msg_network_configuration="%s Network Configuration:\nChoose Save/Exit when finished or Cancel."
+msg_network_interfaces="Network Interfaces"
+msg_network_management="Network Management"
+msg_networking_devices="Networking Devices"
+msg_nfs_mounts_may_cause_hang="WARNING! Changing this setting while NFS directories are\nmounted may cause the system to hang. Are you sure you\nwant to proceed?\n\n%s"
+msg_no_network_interfaces="No network interfaces detected."
+msg_no_options="No options (Default)"
+msg_ok="OK"
+msg_options="options"
+msg_please_enter_default_router="Please enter the IP address of your default\nrouter/gateway. The address entered will be\napplied as the default gateway for all interfaces\nusing route(4)."
+msg_please_enter_fqhn="Please enter your fully qualified hostname (e.g. full.example.com). The\ndomain portion of the hostname will be configured in resolv.conf(5)."
+msg_please_enter_mediaopts="Please enter additional network media options to be passed to ifconfig(8) for the %s interface:"
+msg_please_enter_nameserver="Please enter the new IP address of the DNS nameserver:"
+msg_please_enter_nameserver_existing="Please enter the new IP address of the DNS nameserver\n(set to the NULL string [Ctrl-U] to remove entry):"
+msg_please_enter_new_ip_addr="Please enter the new IP address of the %s interface:"
+msg_please_enter_subnet_mask="Please enter the new network subnet mask for the %s interface:"
+msg_probing_network_interfaces="Probing network interface devices..."
+msg_removing_nameserver="Removing DNS nameserver from resolv.conf(5)..."
+msg_resolv_conf_changed_while_editing="ERROR! resolv.conf(5) has changed while editing this\nvalue. Please try again after waiting a few seconds."
+msg_resolv_conf_entry_no_longer_exists="ERROR! The entry you are trying to edit no longer\nexists in resolv.conf(5). Please try again after\nwaiting a few seconds."
+msg_return_to_previous_menu="Return to previous menu"
+msg_save_exit="Save/Exit"
+msg_saving_default_router="Saving new default router/gateway settings..."
+msg_saving_hostname="Saving new hostname/domain settings..."
+msg_saving_nameserver="Saving new DNS nameserver to resolv.conf(5)..."
+msg_saving_nameserver_existing="Editing DNS nameserver in resolv.conf(5)..."
+msg_saving_network_interface="Saving %s network interface settings..."
+msg_scanning_for_dhcp="Scanning for DHCP servers on %s interface..."
+msg_select_network_interface="Select a network interface to configure.\n\n* Interface is marked as \"active\""
+msg_supported_media_options="Below is a list of supported media options for the %s interface. Please select the options that you would like to set for the %s network interface"
+msg_unknown_networking_menu_selection="Unknown networking menu selection"
+xmsg_please_enter_fqhn="Please enter your fully qualified hostname (e.g. foo.bar.com).\n The domain portion of the hostname will be configured in resolv.conf(5)."
+xmsg_please_enter_nameserver_existing="Please enter the new IP address of the DNS nameserver\n(set to the NULL string to remove entry):"
+xmsg_supported_media_options="Below is a list of supported media options for the %s interface. Please\nselect the options that you would like to set for the %s network interface"
diff --git a/usr.sbin/bsdconfig/networking/nameservers b/usr.sbin/bsdconfig/networking/nameservers
new file mode 100755
index 0000000..e18f004
--- /dev/null
+++ b/usr.sbin/bsdconfig/networking/nameservers
@@ -0,0 +1,76 @@
+#!/bin/sh
+#-
+# Copyright (c) 2006-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+# Prevent device.subr (included indirectly) from auto scanning on load
+DEVICE_SELF_SCAN_ALL=NO
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/networking/resolv.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="120.networking"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_dns_nameservers"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Change the DNS nameservers
+#
+f_dialog_menu_nameservers
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/networking/networking b/usr.sbin/bsdconfig/networking/networking
new file mode 100755
index 0000000..c61ce8f
--- /dev/null
+++ b/usr.sbin/bsdconfig/networking/networking
@@ -0,0 +1,151 @@
+#!/bin/sh
+#-
+# Copyright (c) 2006-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="120.networking"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ FUNCTIONS
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local prompt=
+ local menu_list="
+ 'X' '$msg_exit'
+ '1' '$msg_hostname_domain'
+ '2' '$msg_network_interfaces'
+ '3' '$msg_default_router'
+ '4' '$msg_dns_nameservers'
+ " # END-QUOTE
+ local defaultitem= # Calculated below
+ local hline="$hline_arrows_tab_enter"
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ # Obtain default-item from previously stored selection
+ f_dialog_default_fetch defaultitem
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize menu_choice
+ f_dialog_menutag_store "$menu_choice"
+ f_dialog_default_store "$menu_choice"
+ return $retval
+}
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_network_management"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Launch application main menu
+#
+while :; do
+ dialog_menu_main || f_die
+ f_dialog_menutag_fetch mtag
+
+ command=
+ case "$mtag" in
+ X) break ;;
+ 1) command=hostname ;; # Hostname/Domain
+ 2) command=devices ;; # Network Interfaces
+ 3) command=defaultrouter ;; # Default Router/Gateway
+ 4) command=nameservers ;; # DNS nameservers
+ esac
+
+ if [ "$command" ]; then
+ $BSDCFG_LIBE/$APP_DIR/$command ${USE_XDIALOG:+-X}
+ else
+ f_die 1 "$msg_unknown_networking_menu_selection"
+ fi
+
+ if [ "$mtag" = "devices" ]; then
+ #
+ # Make subsequent uses of this menu faster by not performing
+ # "ifconfig up" (limiting the pain one must endure). See also
+ # `$BSDCFG_SHARE/networking/device.subr'.
+ #
+ export DIALOG_MENU_NETDEV_KICK_INTERFACES=
+ fi
+done
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/networking/share/Makefile b/usr.sbin/bsdconfig/networking/share/Makefile
new file mode 100644
index 0000000..6d77f25
--- /dev/null
+++ b/usr.sbin/bsdconfig/networking/share/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+FILESDIR= ${SHAREDIR}/bsdconfig/networking
+FILES= common.subr device.subr hostname.subr ipaddr.subr media.subr \
+ netmask.subr resolv.subr routing.subr services.subr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/networking/share/Makefile.depend b/usr.sbin/bsdconfig/networking/share/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/networking/share/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/networking/share/common.subr b/usr.sbin/bsdconfig/networking/share/common.subr
new file mode 100644
index 0000000..152d1ac
--- /dev/null
+++ b/usr.sbin/bsdconfig/networking/share/common.subr
@@ -0,0 +1,58 @@
+if [ ! "$_NETWORKING_COMMON_SUBR" ]; then _NETWORKING_COMMON_SUBR=1
+#
+# Copyright (c) 2006-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+
+############################################################ FUNCTIONS
+
+# f_jailed
+#
+# Returns true if the current process is jail(8)ed.
+#
+f_jailed()
+{
+ ! f_quietly ps 1
+}
+
+# f_nfs_mounted
+#
+# Returns true if there are any NFS mounts currently active, otherwise false.
+#
+f_nfs_mounted()
+{
+ [ "$( df -t nfs )" ]
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." networking/common.subr
+
+fi # ! $_NETWORKING_COMMON_SUBR
diff --git a/usr.sbin/bsdconfig/networking/share/device.subr b/usr.sbin/bsdconfig/networking/share/device.subr
new file mode 100644
index 0000000..14758e6
--- /dev/null
+++ b/usr.sbin/bsdconfig/networking/share/device.subr
@@ -0,0 +1,385 @@
+if [ ! "$_NETWORKING_DEVICE_SUBR" ]; then _NETWORKING_DEVICE_SUBR=1
+#
+# Copyright (c) 2006-2015 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." networking/device.subr
+f_include $BSDCFG_SHARE/device.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/media/tcpip.subr
+f_include $BSDCFG_SHARE/networking/common.subr
+f_include $BSDCFG_SHARE/networking/ipaddr.subr
+f_include $BSDCFG_SHARE/networking/media.subr
+f_include $BSDCFG_SHARE/networking/netmask.subr
+f_include $BSDCFG_SHARE/networking/resolv.subr
+f_include $BSDCFG_SHARE/networking/routing.subr
+f_include $BSDCFG_SHARE/strings.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="120.networking"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+############################################################ GLOBALS
+
+#
+# Settings used while interacting with various dialog(1) menus
+#
+: ${DIALOG_MENU_NETDEV_KICK_INTERFACES=1}
+: ${DIALOG_MENU_NETDEV_SLEEP_AFTER_KICK=3}
+
+############################################################ FUNCTIONS
+
+# f_dialog_menu_netdev [$default]
+#
+# Display a list of network devices with descriptions. Optionally, if present
+# and non-NULL, initially highlight $default interface.
+#
+f_dialog_menu_netdev()
+{
+ local menu_list # Calculated below
+ local defaultitem="${1%\*}" # Trim trailing asterisk if present
+
+ #
+ # Display a message to let the user know we're working...
+ # (message will remain until we throw up the next dialog)
+ #
+ f_dialog_info "$msg_probing_network_interfaces"
+
+ #
+ # Get list of usable network interfaces
+ #
+ local dev devs if iflist= # Calculated below
+ f_device_rescan_network
+ f_device_find "" $DEVICE_TYPE_NETWORK devs
+ for dev in $devs; do
+ f_struct "$dev" get name if || continue
+ # Skip unsavory interfaces
+ case "$if" in
+ lo[0-9]*|ppp[0-9]*|sl[0-9]*) continue ;;
+ esac
+ iflist="$iflist $if"
+ done
+ iflist="${iflist# }"
+
+ #
+ # Optionally kick interfaces in the head to get them to accurately
+ # track the carrier status in realtime (required on FreeBSD).
+ #
+ if [ "$DIALOG_MENU_NETDEV_KICK_INTERFACES" ]; then
+ DIALOG_MENU_NETDEV_KICK_INTERFACES=
+
+ for if in $iflist; do
+ f_quietly ifconfig $if up
+ done
+
+ if [ "$DIALOG_MENU_NETDEV_SLEEP_AFTER_KICK" ]; then
+ # interfaces need time to update carrier status
+ sleep $DIALOG_MENU_NETDEV_SLEEP_AFTER_KICK
+ fi
+ fi
+
+ #
+ # Mark any "active" interfaces with an asterisk (*)
+ # to the right of the device name.
+ #
+ menu_list=$(
+ for if in $iflist; do
+ f_device_desc $if $DEVICE_TYPE_NETWORK desc
+ f_shell_escape "$desc" desc
+ if f_device_is_active $if; then
+ printf "'%s\*' '%s'\n" $if "$desc"
+ else
+ printf "'%s' '%s'\n" $if "$desc"
+ fi
+ done
+ )
+ if [ ! "$menu_list" ]; then
+ f_show_msg "$msg_no_network_interfaces"
+ return $DIALOG_CANCEL
+ fi
+
+ # Maybe the default item was marked as active
+ f_device_is_active "$defaultitem" && defaultitem="$defaultitem*"
+
+ #
+ # Ask user to select an interface
+ #
+ local prompt="$msg_select_network_interface"
+ local hline="$hline_arrows_tab_enter"
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_menutag_store -s "$menu_choice"
+ return $retval
+}
+
+# f_dialog_menu_netdev_edit $interface $ipaddr $netmask $options $dhcp
+#
+# Allow a user to edit network interface settings. Current values are not
+# probed but rather taken from the positional arguments.
+#
+f_dialog_menu_netdev_edit()
+{
+ local funcname=f_dialog_menu_netdev_edit
+ local interface="$1" ipaddr="$2" netmask="$3" options="$4" dhcp="$5"
+ local prompt menu_list height width rows
+
+ #
+ # Create a duplicate set of variables for change-tracking...
+ #
+ local ipaddr_orig="$2" \
+ netmask_orig="$3" \
+ options_orig="$4" \
+ dhcp_orig="$5"
+
+ local hline="$hline_arrows_tab_enter"
+ f_sprintf prompt "$msg_network_configuration" "$interface"
+
+ #
+ # Loop forever until the user has finished configuring the different
+ # components of the network interface.
+ #
+ # To apply the settings, we need to know each of the following:
+ # - IP Address
+ # - Network subnet mask
+ # - Additional ifconfig(8) options
+ #
+ # It is only when we have all of the above values that we can make the
+ # changes effective because all three options must be specified at-once
+ # to ifconfig(8).
+ #
+ local defaultitem=
+ while :; do
+ local dhcp_status="$msg_disabled"
+ [ "$dhcp" ] && dhcp_status="$msg_enabled"
+
+ #
+ # Display configuration-edit menu
+ #
+ menu_list="
+ 'X $msg_save_exit' '$msg_return_to_previous_menu'
+ '2 $msg_dhcp' '$dhcp_status'
+ '3 $msg_ipaddr4' '$ipaddr'
+ '4 $msg_netmask' '$netmask'
+ '5 $msg_options' '$options'
+ " # END-QUOTE
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+ local tag
+ tag=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --help-button \
+ --help-label \"\$msg_help\" \
+ ${USE_XDIALOG:+--help \"\"} \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize tag
+
+ if [ $retval -eq $DIALOG_HELP ]; then
+ f_show_help "$TCP_HELPFILE"
+ continue
+ elif [ $retval -ne $DIALOG_OK ]; then
+ return $retval
+ else
+ # Only update default-item on success
+ defaultitem="$tag"
+ fi
+
+ #
+ # Call the below ``modifier functions'' whose job it is to take
+ # input from the user and assign the newly-acquired values back
+ # to the ipaddr, netmask, and options variables for us to re-
+ # read and display in the summary dialog.
+ #
+ case "$tag" in
+ X\ *) break ;;
+ 2\ *) #
+ # Proceed cautiously (confirm with the user) if/when NFS-
+ # mounts are active. If the network on which these mounts
+ # are made is changed parts of the system may hang.
+ #
+ if f_nfs_mounted && ! f_jailed; then
+ local setting
+ f_sprintf setting "$msg_current_dhcp_status" \
+ "$interface" "$dhcp_status"
+ f_noyes "$msg_nfs_mounts_may_cause_hang" "$setting" ||
+ continue
+ fi
+
+ #
+ # Toggle DHCP status
+ #
+ if [ "$dhcp_status" = "$msg_enabled" ]; then
+ dhcp=
+ else
+ trap - SIGINT
+ ( # Execute within sub-shell to allow/catch Ctrl-C
+ trap 'exit $FAILURE' SIGINT
+ f_sprintf msg "$msg_scanning_for_dhcp" "$interface"
+ if [ "$USE_XDIALOG" ]; then
+ (
+ f_quietly ifconfig "$interface" delete
+ f_quietly dhclient "$interface"
+ ) |
+ f_xdialog_info "$msg"
+ else
+ f_dialog_info "$msg"
+ f_quietly ifconfig "$interface" delete
+ f_quietly dhclient "$interface"
+ fi
+ )
+ retval=$?
+ trap 'interrupt' SIGINT
+ if [ $retval -eq $DIALOG_OK ]; then
+ dhcp=1
+ f_ifconfig_inet "$interface" ipaddr
+ f_ifconfig_inet6 "$interface" ipaddr6
+ f_ifconfig_netmask "$interface" netmask
+ options=
+
+ # Fixup search/domain in resolv.conf(5)
+ hostname=$( f_sysrc_get \
+ 'hostname:-$(hostname)' )
+ f_dialog_resolv_conf_update "$hostname"
+ fi
+ fi
+ ;;
+ 3\ *) f_dialog_input_ipaddr "$interface" "$ipaddr"
+ [ $? -eq $DIALOG_OK ] && dhcp= ;;
+ 4\ *) f_dialog_input_netmask "$interface" "$netmask"
+ [ $? -eq $DIALOG_OK -a "$_netmask" ] && dhcp= ;;
+ 5\ *) f_dialog_menu_media_options "$interface" "$options"
+ [ $? -eq $DIALOG_OK ] && dhcp= ;;
+ esac
+ done
+
+ #
+ # Save only if the user changed at least one feature of the interface
+ #
+ if [ "$ipaddr" != "$ipaddr_orig" -o \
+ "$netmask" != "$netmask_orig" -o \
+ "$options" != "$options_orig" -o \
+ "$dhcp" != "$dhcp_orig" ]
+ then
+ f_show_info "$msg_saving_network_interface" "$interface"
+
+ local value=
+ if [ "$dhcp" ]; then
+ f_eval_catch $funcname f_sysrc_delete \
+ 'f_sysrc_delete defaultrouter'
+ value=DHCP
+ else
+ value="inet $ipaddr netmask $netmask"
+ value="$value${options:+ }$options"
+ fi
+
+ f_eval_catch $funcname f_sysrc_set \
+ 'f_sysrc_set "ifconfig_%s" "%s"' "$interface" "$value"
+ fi
+
+ #
+ # Re/Apply the settings if desired
+ #
+ if [ ! "$dhcp" ]; then
+ if f_yesno "$msg_bring_interface_up" "$interface"
+ then
+ f_show_info "$msg_bring_interface_up" "$interface"
+
+ local dr="$( f_sysrc_get defaultrouter )"
+ if [ "$dr" = "NO" -o ! "$dr" ]; then
+ f_route_get_default dr
+ [ "$dr" ] && f_eval_catch \
+ $funcname f_sysrc_set \
+ 'f_sysrc_set defaultrouter "%s"' "$dr"
+ fi
+ #
+ # Make a backup of resolv.conf(5) before using
+ # ifconfig(8) and then restore it afterward. This
+ # allows preservation of nameservers acquired via
+ # DHCP on FreeBSD-8.x (normally lost as ifconfig(8)
+ # usage causes dhclient(8) to exit which scrubs
+ # resolv.conf(5) by-default upon termination).
+ #
+ f_quietly cp -fp "$RESOLV_CONF" "$RESOLV_CONF.$$"
+ if f_eval_catch $funcname ifconfig \
+ 'ifconfig "%s" inet "%s" netmask "%s" %s' \
+ "$interface" "$ipaddr" "$netmask" "$options"
+ then
+ [ "$dr" -a "$dr" != "NO" ] &&
+ f_eval_catch $funcname route \
+ 'route add default "%s"' "$dr"
+ fi
+ if cmp -s "$RESOLV_CONF" "$RESOLV_CONF.$$"; then
+ f_quietly rm -f "$RESOLV_CONF.$$"
+ else
+ f_quietly mv -f "$RESOLV_CONF.$$" "$RESOLV_CONF"
+ fi
+ fi
+ fi
+
+ return $DIALOG_OK
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." networking/device.subr
+
+fi # ! $_NETWORKING_DEVICE_SUBR
diff --git a/usr.sbin/bsdconfig/networking/share/hostname.subr b/usr.sbin/bsdconfig/networking/share/hostname.subr
new file mode 100644
index 0000000..1c3a67b
--- /dev/null
+++ b/usr.sbin/bsdconfig/networking/share/hostname.subr
@@ -0,0 +1,162 @@
+if [ ! "$_NETWORKING_HOSTNAME_SUBR" ]; then _NETWORKING_HOSTNAME_SUBR=1
+#
+# Copyright (c) 2006-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." networking/hostname.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/networking/common.subr
+f_include $BSDCFG_SHARE/networking/resolv.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="120.networking"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+############################################################ FUNCTIONS
+
+# f_dialog_hnerror $error $hostname
+#
+# Display a msgbox with the appropriate error message for an error returned by
+# the f_validate_hostname function.
+#
+f_dialog_hnerror()
+{
+ local error="$1" fqhn="$2"
+
+ [ ${error:-0} -ne 0 ] || return $SUCCESS
+
+ case "$error" in
+ 1) f_show_msg "$msg_hostname_label_contains_invalid_chars" "$fqhn" ;;
+ 2) f_show_msg \
+ "$msg_hostname_label_starts_or_ends_with_hyphen" "$fqhn" ;;
+ 3) f_show_msg "$msg_hostname_label_is_null" "$fqhn" ;;
+ 63) f_show_msg "$msg_hostname_label_exceeds_max_length" "$fqhn" ;;
+ 255) f_show_msg "$msg_hostname_exceeds_max_length" "$fqhn" ;;
+ esac
+}
+
+# f_dialog_validate_hostname $hostname
+#
+# Returns zero if the given argument (a fully-qualified hostname) is compliant
+# with standards set-forth in RFC's 952 and 1123 of the Network Working Group:
+#
+# RFC 952 - DoD Internet host table specification
+# http://tools.ietf.org/html/rfc952
+#
+# RFC 1123 - Requirements for Internet Hosts - Application and Support
+# http://tools.ietf.org/html/rfc1123
+#
+# If the hostname is determined to be invalid, the appropriate error will be
+# displayed using the f_dialog_hnerror function above.
+#
+f_dialog_validate_hostname()
+{
+ local fqhn="$1"
+
+ f_validate_hostname "$fqhn"
+ local retval=$?
+
+ # Produce an appropriate error message if necessary.
+ [ $retval -eq $SUCCESS ] || f_dialog_hnerror $retval "$fqhn"
+
+ return $retval
+}
+
+# f_dialog_input_hostname
+#
+# Edits the current hostname.
+#
+f_dialog_input_hostname()
+{
+ local funcname=f_dialog_input_hostname
+ local hostname="$( f_sysrc_get 'hostname:-$(hostname)' )"
+ local hostname_orig="$hostname" # for change-tracking
+
+ local msg
+ if [ "$USE_XDIALOG" ]; then
+ msg="$xmsg_please_enter_fqhn"
+ else
+ msg="$msg_please_enter_fqhn"
+ fi
+
+ #
+ # Loop until the user provides taint-free input.
+ #
+ while :; do
+ f_dialog_input hostname "$msg" "$hostname" \
+ "$hline_alnum_punc_tab_enter" || return $?
+ # Taint-check the user's input
+ f_dialog_validate_hostname "$hostname" && break
+ done
+
+ #
+ # Save hostname only if the user changed the hostname.
+ #
+ if [ "$hostname" != "$hostname_orig" ]; then
+ f_dialog_info "$msg_saving_hostname"
+ f_eval_catch $funcname f_sysrc_set \
+ 'f_sysrc_set hostname "%s"' "$hostname"
+ fi
+
+ #
+ # Update resolv.conf(5) search/domain directives
+ #
+ f_dialog_resolv_conf_update "$hostname"
+
+ #
+ # Only ask to apply setting if the current hostname is different than
+ # the stored configuration (in rc.conf(5)).
+ #
+ if [ "$( hostname )" != "$( f_sysrc_get hostname )" ]; then
+ [ ! "$USE_XDIALOG" ] && f_dialog_clear
+
+ #
+ # If connected via ssh(1) and performing X11-Forwarding, don't
+ # allow the hostname to be changed to prevent the fatal error
+ # "X11 connection rejected because of wrong authentication."
+ #
+ if [ "$USE_XDIALOG" -a "$SSH_CONNECTION" ]; then
+ f_show_msg "$msg_activate_hostname_x11warning" \
+ "$( hostname )" "$hostname"
+ else
+ f_yesno "$msg_activate_hostname" \
+ "$( hostname )" "$hostname" \
+ && hostname "$hostname"
+ fi
+ fi
+
+ return $DIALOG_OK
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." networking/hostname.subr
+
+fi # ! $_NETWORKING_HOSTNAME_SUBR
diff --git a/usr.sbin/bsdconfig/networking/share/ipaddr.subr b/usr.sbin/bsdconfig/networking/share/ipaddr.subr
new file mode 100644
index 0000000..b7624cc
--- /dev/null
+++ b/usr.sbin/bsdconfig/networking/share/ipaddr.subr
@@ -0,0 +1,219 @@
+if [ ! "$_NETWORKING_IPADDR_SUBR" ]; then _NETWORKING_IPADDR_SUBR=1
+#
+# Copyright (c) 2006-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." networking/ipaddr.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/networking/common.subr
+f_include $BSDCFG_SHARE/strings.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="120.networking"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+############################################################ FUNCTIONS
+
+# f_dialog_iperror $error $ipaddr
+#
+# Display a msgbox with the appropriate error message for an error returned by
+# the f_validate_ipaddr function.
+#
+f_dialog_iperror()
+{
+ local error="$1" ip="$2"
+
+ [ ${error:-0} -ne 0 ] || return $SUCCESS
+
+ case "$error" in
+ 1) f_show_msg "$msg_ipv4_addr_octet_contains_invalid_chars" "$ip" ;;
+ 2) f_show_msg "$msg_ipv4_addr_octet_is_null" "$ip" ;;
+ 3) f_show_msg "$msg_ipv4_addr_octet_exceeds_max_value" "$ip" ;;
+ 4) f_show_msg "$msg_ipv4_addr_octet_missing_or_extra" "$ip" ;;
+ esac
+}
+
+# f_dialog_validate_ipaddr $ipaddr
+#
+# Returns zero if the given argument (an IP address) is of the proper format.
+#
+# If the IP address is determined to be invalid, the appropriate error will be
+# displayed using the f_dialog_iperror function above.
+#
+f_dialog_validate_ipaddr()
+{
+ local ip="$1"
+
+ f_validate_ipaddr "$ip"
+ local retval=$?
+
+ # Produce an appropriate error message if necessary.
+ [ $retval -eq $SUCCESS ] || f_dialog_iperror $retval "$ip"
+
+ return $retval
+}
+
+# f_dialog_ip6error $error $ipv6_addr
+#
+# Display a msgbox with the appropriate error message for an error returned by
+# the f_validate_ipaddr6 function above.
+#
+f_dialog_ip6error()
+{
+ local error="$1" ip="$2"
+
+ [ ${error:-0} -ne 0 ] || return $SUCCESS
+
+ case "$error" in
+ 1) f_show_msg "$msg_ipv6_addr_segment_contains_invalid_chars" "$ip" ;;
+ 2) f_show_msg "$msg_ipv6_addr_too_many_null_segments" "$ip" ;;
+ 3) f_show_msg "$msg_ipv6_addr_segment_contains_too_many_chars" "$ip" ;;
+ 4) f_show_msg "$msg_ipv6_addr_too_few_or_extra_segments" "$ip" ;;
+ *)
+ if [ $(( $error & 0xF )) -eq 5 ]; then
+ # IPv4 at the end of IPv6 address is invalid
+ f_dialog_iperror $(( $error >> 4 )) "$ip"
+ fi
+ esac
+}
+
+# f_dialog_validate_ipaddr6 $ipv6_addr
+#
+# Returns zero if the given argument (an IPv6 address) is of the proper format.
+#
+# If the IP address is determined to be invalid, the appropriate error will be
+# displayed using the f_dialog_ip6error function above.
+#
+f_dialog_validate_ipaddr6()
+{
+ local ip="$1"
+
+ f_validate_ipaddr6 "$ip"
+ local retval=$?
+
+ # Produce an appropriate error message if necessary.
+ [ $retval -eq $SUCCESS ] || f_dialog_ip6error $retval "$ip"
+
+ return $retval
+}
+
+# f_dialog_input_ipaddr $interface $ipaddr
+#
+# Allows the user to edit a given IP address. If the user does not cancel or
+# press ESC, the $ipaddr environment variable will hold the newly-configured
+# value upon return.
+#
+# Optionally, the user can enter the format "IP_ADDRESS/NBITS" to set the
+# netmask at the same time as the IP address. If such a format is entered by
+# the user, the $netmask environment variable will hold the newly-configured
+# netmask upon return.
+#
+f_dialog_input_ipaddr()
+{
+ local interface="$1" _ipaddr="$2" _input
+
+ #
+ # Return with-error when there are NFS-mounts currently active. If the
+ # IP address is changed while NFS-exported directories are mounted, the
+ # system may hang (if any NFS mounts are using that interface).
+ #
+ if f_nfs_mounted && ! f_jailed; then
+ local setting
+ f_sprintf setting "$msg_current_ipaddr" "$interface" "$_ipaddr"
+ f_noyes "$msg_nfs_mounts_may_cause_hang" "$setting" ||
+ return $DIALOG_CANCEL
+ fi
+
+ local msg
+ f_sprintf msg "$msg_please_enter_new_ip_addr" "$interface"
+
+ #
+ # Loop until the user provides taint-free input.
+ #
+ local retval
+ while :; do
+ #
+ # Return error status if:
+ # - User has either pressed ESC or chosen Cancel/No
+ # - User has not made any changes to the given value
+ #
+ f_dialog_input _input "$msg" "$_ipaddr" \
+ "$hline_num_punc_tab_enter" || return $?
+ [ "$_ipaddr" = "$_input" ] && return $DIALOG_CANCEL
+
+ # Return success if NULL value was entered
+ [ "$_input" ] || return $DIALOG_OK
+
+ # Take only the first "word" of the user's input
+ _ipaddr="$_input"
+ _ipaddr="${_ipaddr%%[$IFS]*}"
+
+ # Taint-check the user's input
+ f_dialog_validate_ipaddr "${_ipaddr%%/*}" && break
+ done
+
+ #
+ # Support the syntax: IP_ADDRESS/NBITS
+ #
+ local _netmask=""
+ case "$_ipaddr" in
+ */*)
+ local nbits="${_ipaddr#*/}" n=0
+ _ipaddr="${_ipaddr%%/*}"
+
+ #
+ # Taint-check $nbits to be (a) a positive whole-integer,
+ # and (b) to be less than or equal to 32. Otherwise, set
+ # $n so that the below loop never executes.
+ #
+ ( f_isinteger "$nbits" && [ $nbits -ge 0 -a $nbits -le 32 ] ) \
+ || n=4
+
+ while [ $n -lt 4 ]; do
+ _netmask="$_netmask${_netmask:+.}$((
+ (65280 >> ($nbits - 8 * $n) & 255)
+ * ((8*$n) < $nbits & $nbits <= (8*($n+1)))
+ + 255 * ($nbits > (8*($n+1)))
+ ))"
+ n=$(( $n + 1 ))
+ done
+ ;;
+ esac
+
+ ipaddr="$_ipaddr"
+ [ "$_netmask" ] && netmask="$_netmask"
+
+ return $DIALOG_OK
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." networking/ipaddr.subr
+
+fi # ! $_NETWORKING_IPADDR_SUBR
diff --git a/usr.sbin/bsdconfig/networking/share/media.subr b/usr.sbin/bsdconfig/networking/share/media.subr
new file mode 100644
index 0000000..1cb77f8
--- /dev/null
+++ b/usr.sbin/bsdconfig/networking/share/media.subr
@@ -0,0 +1,247 @@
+if [ ! "$_NETWORKING_MEDIA_SUBR" ]; then _NETWORKING_MEDIA_SUBR=1
+#
+# Copyright (c) 2006-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." networking/media.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/networking/common.subr
+f_include $BSDCFG_SHARE/strings.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="120.networking"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+############################################################ FUNCTIONS
+
+# f_ifconfig_options $interface
+#
+# Returns any/all extra ifconfig(8) parameters associated with $interface.
+#
+f_ifconfig_options()
+{
+ local interface="$1"
+ [ "$interface" ] || return $SUCCESS
+
+ #
+ # Loop over the options, removing what we don't want
+ #
+ (
+ set -- $( f_sysrc_get ifconfig_$interface )
+
+ #
+ # Return if the interface is configured for DHCP
+ #
+ glob="[Dd][Hh][Cc][Pp]"
+ case "$*" in
+ $glob|[Ss][Yy][Nn][Cc]$glob|[Nn][Oo][Ss][Yy][Nn][Cc]$glob)
+ exit $SUCCESS
+ esac
+
+ output=
+ while [ $# -gt 0 ]; do
+ case "$1" in
+ inet|netmask) shift 1 ;;
+ *) output="$output${output:+ }$1"
+ esac
+ shift 1
+ done
+ echo "$output"
+ )
+}
+
+# f_ifconfig_media $interface
+#
+# Returns list of supported media for $interface.
+#
+f_ifconfig_media()
+{
+ local interface="$1"
+ ifconfig -m "$interface" 2> /dev/null | awk \
+ '
+ BEGIN { media_found = 0 }
+ {
+ if ( media_found == 1 ) { print; next }
+ }
+ ( $1 $2 == "supported" "media:" ) \
+ {
+ media_found = 1
+ next
+ }
+ END { exit ! media_found }
+ '
+}
+
+# f_dialog_input_options $interface
+#
+# Input custom interface options. If the user does not press ESC or choose
+# Cancel/No, $options will hold the user's input. Default input is taken from
+# the same variable ($options).
+#
+f_dialog_input_options()
+{
+ local interface="$1"
+
+ #
+ # Return with-error when there are NFS-mounts currently active. If the
+ # options are changed while NFS-exported directories are mounted,
+ # the system may hang (if any NFS mounts are using that interface).
+ #
+ if f_nfs_mounted && ! f_jailed; then
+ local setting
+ f_sprintf setting "$msg_current_options" \
+ "$interface" "$options"
+ f_noyes "$msg_nfs_mounts_may_cause_hang" "$setting" ||
+ return $DIALOG_CANCEL
+ fi
+
+ local msg
+ f_sprintf msg "$msg_please_enter_mediaopts" "$interface"
+ local hline="$hline_alnum_punc_tab_enter"
+
+ local _options
+ _options=$( $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --hline "$hline" \
+ --ok-label "$msg_ok" \
+ --cancel-label "$msg_cancel" \
+ --inputbox "$msg" 9 70 \
+ "$options" \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_line_sanitize _options
+
+ [ $retval -eq $DIALOG_OK ] && options="$_options"
+
+ return $retval
+}
+
+# f_dialog_menu_media_options $interface
+#
+# Display a menu of additional media options for the given network interface.
+#
+f_dialog_menu_media_options()
+{
+ local interface="$1" _options="$2"
+ #
+ # Not all network interfaces support additional media options, but
+ # when available we should prompt the user to select from a list
+ # of available options (or none, as is the first/default option).
+ #
+
+ #
+ # Return with-error when there are NFS-mounts currently active. If the
+ # media options are changed while NFS-exported directories are mounted,
+ # the system may hang (if any NFS mounts are using that interface).
+ #
+ if f_nfs_mounted && ! f_jailed; then
+ local setting
+ f_sprintf setting "$msg_current_options" \
+ "$interface" "$_options"
+ f_noyes "$msg_nfs_mounts_may_cause_hang" "$setting" ||
+ return $DIALOG_CANCEL
+ fi
+
+ #
+ # Build list of additional media options
+ #
+ local opt_none="$msg_no_options"
+ local opt_cust="$msg_custom"
+ local supported_media="$(
+ f_ifconfig_media $interface | \
+ ( index=1
+
+ echo "'$( f_substr "$DIALOG_MENU_TAGS" $index 1 )'"
+ echo "'$opt_none'"
+ index=$(( $index + 1 ))
+
+ echo "'$( f_substr "$DIALOG_MENU_TAGS" $index 1 )'"
+ echo "'$opt_cust'"
+ index=$(( $index + 1 ))
+
+ while read media_options; do
+ [ $index -lt ${#DIALOG_MENU_TAGS} ] || break
+ echo "'$( f_substr "$DIALOG_MENU_TAGS" $index 1 )'"
+ echo "'$media_options'"
+ index=$(( $index + 1 ))
+ done
+ )
+ )"
+
+ local msg
+ if [ "$USE_XDIALOG" ]; then
+ f_sprintf msg "$xmsg_supported_media_options" \
+ "$interface" "$interface"
+ else
+ f_sprintf msg "$msg_supported_media_options" \
+ "$interface" "$interface"
+ fi
+
+ local hline="$hline_arrows_tab_enter"
+
+ local tag
+ tag=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --menu \"\$msg\" 21 60 12 \
+ $supported_media \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize tag
+
+ if [ $retval -eq $DIALOG_OK ]; then
+ options=$( eval f_dialog_menutag2item \"\$tag\" \
+ $supported_media )
+ case "$options" in
+ "$opt_none")
+ options=
+ ;;
+ "$opt_cust")
+ options="$_options"
+ f_dialog_input_options "$interface"
+ retval=$?
+ ;;
+ esac
+ fi
+
+ return $retval
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." networking/media.subr
+
+fi # ! $_NETWORKING_MEDIA_SUBR
diff --git a/usr.sbin/bsdconfig/networking/share/netmask.subr b/usr.sbin/bsdconfig/networking/share/netmask.subr
new file mode 100644
index 0000000..a7d44a5
--- /dev/null
+++ b/usr.sbin/bsdconfig/networking/share/netmask.subr
@@ -0,0 +1,137 @@
+if [ ! "$_NETWORKING_NETMASK_SUBR" ]; then _NETWORKING_NETMASK_SUBR=1
+#
+# Copyright (c) 2006-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." networking/netmask.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/networking/common.subr
+f_include $BSDCFG_SHARE/strings.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="120.networking"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+############################################################ FUNCTIONS
+
+# f_dialog_maskerror $error $netmask
+#
+# Display a msgbox with the appropriate error message for an error returned by
+# the f_validate_netmask function.
+#
+f_dialog_maskerror()
+{
+ local error="$1" netmask="$2"
+
+ [ ${error:-0} -ne 0 ] || return $SUCCESS
+
+ case "$error" in
+ 1) f_show_msg "$msg_ipv4_mask_field_contains_invalid_chars" "$mask" ;;
+ 2) f_show_msg "$msg_ipv4_mask_field_is_null" "$mask" ;;
+ 3) f_show_msg "$msg_ipv4_mask_field_exceeds_max_value" "$mask" ;;
+ 4) f_show_msg "$msg_ipv4_mask_field_missing_or_extra" "$mask" ;;
+ 5) f_show_msg "$msg_ipv4_mask_field_invalid_value" "$mask" ;;
+ esac
+}
+
+# f_dialog_validate_netmask $netmask
+#
+# Returns zero if the given argument (a subnet mask) is of the proper format.
+#
+# If the subnet mask is determined to be invalid, the appropriate error will be
+# displayed using the f_dialog_maskerror function above.
+#
+f_dialog_validate_netmask()
+{
+ local netmask="$1"
+
+ f_validate_netmask "$netmask"
+ local retval=$?
+
+ # Produce an appropriate error message if necessary.
+ [ $retval -eq $SUCCESS ] || f_dialog_maskerror $retval "$netmask"
+
+ return $retval
+}
+
+# f_dialog_input_netmask $interface $netmask
+#
+# Edits the IP netmask of the given interface.
+#
+f_dialog_input_netmask()
+{
+ local interface="$1" _netmask="$2" _input
+
+ #
+ # Return with-error when there are NFS-mounts currently active. If the
+ # subnet mask is changed while NFS-exported directories are mounted,
+ # the system may hang (if any NFS mounts are using that interface).
+ #
+ if f_nfs_mounted && ! f_jailed; then
+ local setting
+ f_sprintf setting "$msg_current_subnet" \
+ "$interface" "$_netmask"
+ f_noyes "$msg_nfs_mounts_may_cause_hang" "$setting" ||
+ return $DIALOG_CANCEL
+ fi
+
+ #
+ # Loop until the user provides taint-free input.
+ #
+ local msg
+ f_sprintf msg "$msg_please_enter_subnet_mask" "$interface"
+ while :; do
+ #
+ # Return error status if:
+ # - User has either pressed ESC or chosen Cancel/No
+ # - User has not made any changes to the given value
+ #
+ f_dialog_input _input "$msg" "$_netmask" \
+ "$hline_num_punc_tab_enter" || return $?
+ [ "$_netmask" = "$_input" ] && return $DIALOG_CANCEL
+
+ # Return success if NULL value was entered
+ [ "$_input" ] || return $DIALOG_OK
+
+ # Take only the first "word" of the user's input
+ _netmask="$_input"
+ _netmask="${_netmask%%[$IFS]*}"
+
+ # Taint-check the user's input
+ f_dialog_validate_netmask "$_netmask" && break
+ done
+
+ netmask="$_netmask"
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." networking/netmask.subr
+
+fi # ! $_NETWORKING_NETMASK_SUBR
diff --git a/usr.sbin/bsdconfig/networking/share/resolv.subr b/usr.sbin/bsdconfig/networking/share/resolv.subr
new file mode 100644
index 0000000..fc42e12
--- /dev/null
+++ b/usr.sbin/bsdconfig/networking/share/resolv.subr
@@ -0,0 +1,502 @@
+if [ ! "$_NETWORKING_RESOLV_SUBR" ]; then _NETWORKING_RESOLV_SUBR=1
+#
+# Copyright (c) 2006-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." networking/resolv.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/media/tcpip.subr
+f_include $BSDCFG_SHARE/networking/common.subr
+f_include $BSDCFG_SHARE/networking/ipaddr.subr
+f_include $BSDCFG_SHARE/strings.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="120.networking"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+############################################################ CONFIGURATION
+
+#
+# When updating resolv.conf(5), should we populate the `search' directive with
+# all possible sub-domains? In example, if the domain is "sub.domain.com", when
+# the below option is set to 1, include both "sub.domain.com" and "domain.com"
+# in the `search' directive, otherwise use only "sub.domain.com".
+#
+# When enabled (set to 1), specify the minimum number of dots required for each
+# `search' domain by setting the second option below, `RESOLVER_SEARCH_NDOTS'.
+#
+: ${RESOLVER_SEARCH_DOMAINS_ALL:=1}
+: ${RESOLVER_SEARCH_NDOTS:=1}
+
+############################################################ FUNCTIONS
+
+# f_resolv_conf_domain
+#
+# Returns the domain configured in resolv.conf(5).
+#
+f_resolv_conf_domain()
+{
+ tail -r "$RESOLV_CONF" 2> /dev/null | awk \
+ '
+ BEGIN { found = 0 }
+ ( tolower($1) == "domain" ) \
+ {
+ print $2
+ found = 1
+ exit
+ }
+ END { exit ! found }
+ '
+}
+
+# f_resolv_conf_search
+#
+# Returns the search configured in resolv.conf(5).
+#
+f_resolv_conf_search()
+{
+ tail -r "$RESOLV_CONF" 2> /dev/null | awk \
+ '
+ BEGIN { found = 0 }
+ {
+ tl0 = tolower($0)
+ if ( match(tl0, /^[[:space:]]*search[[:space:]]+/) ) {
+ search = substr($0, RLENGTH + 1)
+ sub(/[[:space:]]*#.*$/, "", search)
+ gsub(/[[:space:]]+/, " ", search)
+ print search
+ found = 1
+ exit
+ }
+ }
+ END { exit ! found }
+ '
+}
+
+# f_dialog_resolv_conf_update $hostname
+#
+# Updates the search/domain directives in resolv.conf(5) given a valid fully-
+# qualified hostname.
+#
+# This function is a two-parter. Below is the awk(1) portion of the function,
+# afterward is the sh(1) function which utilizes the below awk script.
+#
+f_dialog_resolv_conf_update_awk='
+# Variables that should be defined on the invocation line:
+# -v domain="domain"
+# -v search_all="0|1"
+# -v search_ndots="1+"
+#
+BEGIN {
+ domain_found = search_found = 0
+
+ if ( search_all ) {
+ search = ""
+ subdomain = domain
+ if ( search_ndots < 1 )
+ search_ndots = 1
+
+ ndots = split(subdomain, labels, ".") - 1
+ while ( ndots-- >= search_ndots ) {
+ if ( length(search) ) search = search " "
+ search = search subdomain
+ sub(/[^.]*\./, "", subdomain)
+ }
+ }
+ else search = domain
+}
+{
+ if ( domain_found && search_found ) { print; next }
+
+ tl0 = tolower($0)
+ if ( ! domain_found && \
+ match(tl0, /^[[:space:]]*domain[[:space:]]+/) ) \
+ {
+ if ( length(domain) ) {
+ printf "%s%s\n", substr($0, 0, RLENGTH), domain
+ domain_found = 1
+ }
+ }
+ else if ( ! search_found && \
+ match(tl0, /^[[:space:]]*search[[:space:]]+/) ) \
+ {
+ if ( length(search) ) {
+ printf "%s%s\n", substr($0, 0, RLENGTH), search
+ search_found = 1
+ }
+ }
+ else print
+}
+END {
+ if ( ! search_found && length(search) )
+ printf "search\t%s\n", search
+ if ( ! domain_found && length(domain) )
+ printf "domain\t%s\n", domain
+}
+'
+f_dialog_resolv_conf_update()
+{
+ local funcname=f_dialog_resolv_conf_update
+ local hostname="$1"
+
+ #
+ # Extrapolate the desired domain search parameter for resolv.conf(5)
+ #
+ local search nfields ndots domain="${hostname#*.}"
+ if [ "$RESOLVER_SEARCH_DOMAINS_ALL" = "1" ]; then
+ search=
+ IFS=. f_count_ifs nfields "$domain"
+ ndots=$(( $nfields - 1 ))
+ while [ $ndots -ge ${RESOLVER_SEARCH_NDOTS:-1} ]; do
+ search="$search $domain"
+ domain="${domain#*.}"
+ ndots=$(( $ndots - 1 ))
+ done
+ search="${search# }"
+ domain="${hostname#*.}"
+ else
+ search="$domain"
+ fi
+
+ #
+ # Save domain/search information only if different from resolv.conf(5)
+ #
+ if [ "$domain" != "$( f_resolv_conf_domain )" -o \
+ "$search" != "$( f_resolv_conf_search )" ]
+ then
+ f_dialog_info "Saving new domain/search settings" \
+ "to resolv.conf(5)..."
+
+ #
+ # Create a new temporary file to write our resolv.conf(5)
+ # update with our new `domain' and `search' directives.
+ #
+ local tmpfile
+ f_eval_catch -dk tmpfile $funcname mktemp \
+ 'mktemp -t "%s"' "$tmpfile" || return $DIALOG_CANCEL
+
+ #
+ # Fixup permissions and ownership (mktemp(1) creates the
+ # temporary file with 0600 permissions -- change the
+ # permissions and ownership to match resolv.conf(5) before
+ # we write it out and mv(1) it into place).
+ #
+ local mode owner
+ f_eval_catch -dk mode $funcname stat \
+ 'stat -f "%%#Lp" "%s"' "$RESOLV_CONF" || mode=0644
+ f_eval_catch -dk owner $funcname stat \
+ 'stat -f "%%u:%%g" "%s"' "$RESOLV_CONF" ||
+ owner="root:wheel"
+ f_eval_catch -d $funcname chmod \
+ 'chmod "%s" "%s"' "$mode" "$tmpfile"
+ f_eval_catch -d $funcname chown \
+ 'chown "%s" "%s"' "$owner" "$tmpfile"
+
+ #
+ # Operate on resolv.conf(5), replacing only the last
+ # occurrences of `domain' and `search' directives (or add
+ # them to the top if not found), in strict-adherence to the
+ # following entry in resolver(5):
+ #
+ # The domain and search keywords are mutually exclusive.
+ # If more than one instance of these keywords is present,
+ # the last instance will override.
+ #
+ # NOTE: If RESOLVER_SEARCH_DOMAINS_ALL is set to `1' in the
+ # environment, all sub-domains will be added to the `search'
+ # directive, not just the FQDN.
+ #
+ local domain="${hostname#*.}" new_contents
+ [ "$domain" = "$hostname" ] && domain=
+ new_contents=$( tail -r "$RESOLV_CONF" 2> /dev/null )
+ new_contents=$( echo "$new_contents" | awk \
+ -v domain="$domain" \
+ -v search_all="${RESOLVER_SEARCH_DOMAINS_ALL:-1}" \
+ -v search_ndots="${RESOLVER_SEARCH_NDOTS:-1}" \
+ "$f_dialog_resolv_conf_update_awk" )
+
+ #
+ # Write the temporary file contents and move the temporary
+ # file into place.
+ #
+ echo "$new_contents" | tail -r > "$tmpfile" ||
+ return $DIALOG_CANCEL
+ f_eval_catch -d $funcname mv \
+ 'mv "%s" "%s"' "$tmpfile" "$RESOLV_CONF"
+
+ fi
+}
+
+# f_dialog_input_nameserver [ $n $nameserver ]
+#
+# Allows the user to edit a given nameserver. The first argument is the
+# resolv.conf(5) nameserver ``instance'' integer. For example, this will be one
+# if editing the first nameserver instance, two if editing the second, three if
+# the third, ad nauseum. If this argument is zero, null, or missing, the value
+# entered by the user (if non-null) will be added to resolv.conf(5) as a new
+# `nameserver' entry. The second argument is the IPv4 address of the nameserver
+# to be edited -- this will be displayed as the initial value during the edit.
+#
+# Taint-checking is performed when editing an existing entry (when the second
+# argument is one or higher) in that the first argument must match the current
+# value of the Nth `nameserver' instance in resolv.conf(5) else an error is
+# generated discarding any/all changes.
+#
+# This function is a two-parter. Below is the awk(1) portion of the function,
+# afterward is the sh(1) function which utilizes the below awk script.
+#
+f_dialog_input_nameserver_edit_awk='
+# Variables that should be defined on the invocation line:
+# -v nsindex="1+"
+# -v old_value="..."
+# -v new_value="..."
+#
+BEGIN {
+ if ( nsindex < 1 ) exit 1
+ found = n = 0
+}
+{
+ if ( found ) { print; next }
+
+ if ( match(tolower($0), /^[[:space:]]*nameserver[[:space:]]+/)) {
+ if ( ++n == nsindex ) {
+ if ( $2 != old_value ) exit 2
+ if ( new_value != "" ) printf "%s%s\n", \
+ substr($0, 0, RLENGTH), new_value
+ found = 1
+ }
+ else print
+ }
+ else print
+}
+END { if ( ! found ) exit 3 }
+'
+f_dialog_input_nameserver()
+{
+ local funcname=f_dialog_input_nameserver
+ local index="${1:-0}" old_ns="$2" new_ns
+ local ns="$old_ns"
+
+ #
+ # Perform sanity checks
+ #
+ f_isinteger "$index" || return $DIALOG_CANCEL
+ [ $index -ge 0 ] || return $DIALOG_CANCEL
+
+ local msg
+ if [ $index -gt 0 ]; then
+ if [ "$USE_XDIALOG" ]; then
+ msg="$xmsg_please_enter_nameserver_existing"
+ else
+ msg="$msg_please_enter_nameserver_existing"
+ fi
+ else
+ msg="$msg_please_enter_nameserver"
+ fi
+
+ #
+ # Loop until the user provides taint-free input.
+ #
+ while :; do
+ f_dialog_input new_ns "$msg" "$ns" \
+ "$hline_num_punc_tab_enter" || return $?
+
+ # Take only the first "word" of the user's input
+ new_ns="${new_ns%%[$IFS]*}"
+
+ # Taint-check the user's input
+ [ "$new_ns" ] || break
+ f_dialog_validate_ipaddr "$new_ns" && break
+
+ # Update prompt to allow user to re-edit previous entry
+ ns="$new_ns"
+ done
+
+ #
+ # Save only if the user changed the nameserver.
+ #
+ if [ $index -eq "0" -a "$new_ns" ]; then
+ f_dialog_info "$msg_saving_nameserver"
+ printf "nameserver\t%s\n" "$new_ns" >> "$RESOLV_CONF"
+ return $DIALOG_OK
+ elif [ $index -gt 0 -a "$old_ns" != "$new_ns" ]; then
+ if [ "$new_ns" ]; then
+ msg="$msg_saving_nameserver_existing"
+ else
+ msg="$msg_removing_nameserver"
+ fi
+ f_dialog_info "$msg"
+
+ #
+ # Create a new temporary file to write our new resolv.conf(5)
+ #
+ local tmpfile
+ f_eval_catch -dk tmpfile $funcname mktemp \
+ 'mktemp -t "%s"' "$pgm" || return $DIALOG_CANCEL
+
+ #
+ # Quietly fixup permissions and ownership
+ #
+ local mode owner
+ f_eval_catch -dk mode $funcname stat \
+ 'stat -f "%%#Lp" "%s"' "$RESOLV_CONF" || mode=0644
+ f_eval_catch -dk owner $funcname stat \
+ 'stat -f "%%u:%%g" "%s"' "$RESOLV_CONF" ||
+ owner="root:wheel"
+ f_eval_catch -d $funcname chmod \
+ 'chmod "%s" "%s"' "$mode" "$tmpfile"
+ f_eval_catch -d $funcname chown \
+ 'chown "%s" "%s"' "$owner" "$tmpfile"
+
+ #
+ # Operate on resolv.conf(5)
+ #
+ local new_contents
+ new_contents=$( awk -v nsindex="$index" \
+ -v old_value="$old_ns" \
+ -v new_value="$new_ns" \
+ "$f_dialog_input_nameserver_edit_awk" \
+ "$RESOLV_CONF" )
+
+ #
+ # Produce an appropriate error message if necessary.
+ #
+ local retval=$?
+ case $retval in
+ 1) f_die 1 "$msg_internal_error_nsindex_value" "$nsindex" ;;
+ 2) f_show_msg "$msg_resolv_conf_changed_while_editing"
+ return $retval ;;
+ 3) f_show_msg "$msg_resolv_conf_entry_no_longer_exists"
+ return $retval ;;
+ esac
+
+ #
+ # Write the temporary file contents and move the temporary
+ # file into place.
+ #
+ echo "$new_contents" > "$tmpfile" || return $DIALOG_CANCEL
+ f_eval_catch -d $funcname mv \
+ 'mv "%s" "%s"' "$tmpfile" "$RESOLV_CONF"
+ fi
+}
+
+# f_dialog_menu_nameservers
+#
+# Edit the nameservers in resolv.conf(5).
+#
+f_dialog_menu_nameservers()
+{
+ local prompt="$msg_dns_configuration"
+ local menu_list # Calculated below
+ local hline="$hline_arrows_tab_enter"
+ local defaultitem=
+
+ local height width rows
+ local opt_exit="$msg_return_to_previous_menu"
+ local opt_add="$msg_add_nameserver"
+
+ #
+ # Loop forever until the user has finished configuring nameservers
+ #
+ while :; do
+ #
+ # Re/Build list of nameservers
+ #
+ local nameservers
+ f_resolv_conf_nameservers nameservers
+ menu_list=$(
+ index=1
+
+ echo "'X $msg_exit' '$opt_exit'"
+ index=$(( $index + 1 ))
+
+ echo "'A $msg_add' '$opt_add'"
+ index=$(( $index + 1 ))
+
+ for ns in $nameservers; do
+ [ $index -lt ${#DIALOG_MENU_TAGS} ] || break
+ tag=$( f_substr "$DIALOG_MENU_TAGS" $index 1 )
+ echo "'$tag nameserver' '$ns'"
+ index=$(( $index + 1 ))
+ done
+ )
+
+ #
+ # Display configuration-edit menu
+ #
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+ local tag
+ tag=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize tag
+
+ # Return if "Cancel" was chosen (-1) or ESC was pressed (255)
+ if [ $retval -ne $DIALOG_OK ]; then
+ return $retval
+ else
+ # Only update default-item on success
+ defaultitem="$tag"
+ fi
+
+ case "$tag" in
+ "X $msg_exit") break ;;
+ "A $msg_add")
+ f_dialog_input_nameserver
+ ;;
+ *)
+ local n ns
+ n=$( eval f_dialog_menutag2index \"\$tag\" $menu_list )
+ ns=$( eval f_dialog_menutag2item \"\$tag\" $menu_list )
+ f_dialog_input_nameserver $(( $n - 2 )) "$ns"
+ ;;
+ esac
+ done
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." networking/resolv.subr
+
+fi # ! $_NETWORKING_RESOLV_SUBR
diff --git a/usr.sbin/bsdconfig/networking/share/routing.subr b/usr.sbin/bsdconfig/networking/share/routing.subr
new file mode 100644
index 0000000..826ac6e
--- /dev/null
+++ b/usr.sbin/bsdconfig/networking/share/routing.subr
@@ -0,0 +1,133 @@
+if [ ! "$_NETWORKING_ROUTING_SUBR" ]; then _NETWORKING_ROUTING_SUBR=1
+#
+# Copyright (c) 2006-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." networking/routing.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/media/tcpip.subr
+f_include $BSDCFG_SHARE/networking/common.subr
+f_include $BSDCFG_SHARE/networking/ipaddr.subr
+f_include $BSDCFG_SHARE/strings.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="120.networking"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+############################################################ FUNCTIONS
+
+# f_dialog_input_defaultrouter
+#
+# Edits the default router.
+#
+f_dialog_input_defaultrouter()
+{
+ local funcname=f_dialog_input_defaultrouter
+
+ #
+ # Get the defaultrouter. When this is not configured, the default is
+ # "NO", however we don't ever want to present this default to the user
+ # in the following dialog. If the current value is "NO", then try to
+ # obtain the value from the running system using route(8).
+ #
+ # NOTE: Our `f_route_get_default' function will return NULL if the
+ # system does not have an active default router set (which is what we
+ # want).
+ #
+ local defaultrouter="$( f_sysrc_get 'defaultrouter:-NO' )"
+ local defaultrouter_orig="$defaultrouter" # for change-tracking
+ case "$defaultrouter" in
+ [Nn][Oo]) f_route_get_default defaultrouter ;;
+ esac
+
+ #
+ # Return with-error when there are NFS-mounts currently active. If the
+ # default router/gateway is changed while NFS-exported directories are
+ # mounted, the system will hang.
+ #
+ if f_nfs_mounted && ! f_jailed; then
+ local setting
+ f_sprintf setting "$msg_current_default_router" \
+ "$defaultrouter"
+ f_noyes "$msg_nfs_mounts_may_cause_hang" "$setting" ||
+ return $DIALOG_CANCEL
+ fi
+
+ #
+ # Loop until the user provides taint-free input.
+ #
+ local retval
+ while :; do
+ f_dialog_input defaultrouter \
+ "$msg_please_enter_default_router" \
+ "$defaultrouter" "$hline_num_punc_tab_enter"
+ retval=$?
+ [ "$defaultrouter" ] || return $DIALOG_OK
+ [ $retval -eq $DIALOG_OK ] || return $retval
+
+ # Taint-check the user's input
+ f_dialog_validate_ipaddr "$defaultrouter" && break
+ done
+
+ #
+ # Save only if the user changed the default router/gateway.
+ #
+ if [ "$defaultrouter" != "$defaultrouter_orig" ]; then
+ f_dialog_info "$msg_saving_default_router"
+
+ # Save the default router/gateway
+ f_eval_catch $funcname f_sysrc_set \
+ 'f_sysrc_set defaultrouter "%s"' "$defaultrouter"
+ fi
+
+ #
+ # Only ask to apply setting if the current defaultrouter is different
+ # than the stored configuration (in rc.conf(5)).
+ #
+ local dr
+ f_route_get_default dr
+ if [ "$dr" != "$defaultrouter" ]; then
+ f_dialog_clear
+ f_yesno "$msg_activate_default_router" "$dr" "$defaultrouter"
+ if [ $? -eq $DIALOG_OK ]; then
+ # Apply the default router/gateway
+ f_eval_catch -d $funcname route 'route delete default'
+ f_eval_catch $funcname route \
+ 'route add default "%s"' "$defaultrouter" ||
+ return $DIALOG_CANCEL
+ fi
+ fi
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." networking/routing.subr
+
+fi # ! $_NETWORKING_ROUTING_SUBR
diff --git a/usr.sbin/bsdconfig/networking/share/services.subr b/usr.sbin/bsdconfig/networking/share/services.subr
new file mode 100644
index 0000000..246d895
--- /dev/null
+++ b/usr.sbin/bsdconfig/networking/share/services.subr
@@ -0,0 +1,55 @@
+if [ ! "$_NETWORKING_SERVICES_SUBR" ]; then _NETWORKING_SERVICES_SUBR=1
+#
+# Copyright (c) 2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." networking/services.subr
+f_include $BSDCFG_SHARE/packages/packages.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+
+############################################################ FUNCTIONS
+
+# f_config_pcnfsd
+#
+# Load pcnfsd package and adjust mountd_flags in rc.conf(5).
+#
+f_config_pcnfsd()
+{
+ local funcname=f_config_pcnfsd
+ f_package_add "pcnfsd" || return $?
+ f_eval_catch $funcname f_sysrc_set 'f_sysrc_set mountd_flags -n'
+ return $SUCCESS
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." networking/services.subr
+
+fi # ! $_NETWORKING_SERVICES_SUBR
diff --git a/usr.sbin/bsdconfig/packages/INDEX b/usr.sbin/bsdconfig/packages/INDEX
new file mode 100644
index 0000000..8043477
--- /dev/null
+++ b/usr.sbin/bsdconfig/packages/INDEX
@@ -0,0 +1,56 @@
+# Copyright (c) 2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+#
+# Title that will be shown in the bsdconfig menu.
+#
+menu_title="Packages"
+
+#
+# A short descriptive line shown at the bottom of the bsdconfig menu. keep it
+# short because any line longer than the terminal width will be truncated.
+#
+menu_help="Install pre-packaged software for FreeBSD"
+
+#
+# Two-part variable that defines an action to take when `keyword' is passed on
+# a bsdconfig command line. Variable takes the form "keyword|command" and
+# multiple occurrences of the variable (with different `keyword's, or different
+# `keyword's AND `command's) are allowed. If `command' begins with a '/' then
+# the full path to the program is needed. If `command' begins with anything
+# else it is a path relative to the directory this INDEX file is in. `keyword'
+# can be i18n'ed but `command' is the name of a script.
+#
+menu_selection="packages|packages"
+
+#
+# ------------ Items below this line do NOT need i18n translation ------------
+#
+# Name of the program to be run when this menu choice is selected. If it begins
+# with a '/' then the full path to the program is needed. If it begins with
+# anything else it is a path relative to the directory this INDEX file is in.
+#
+menu_program="packages"
diff --git a/usr.sbin/bsdconfig/packages/Makefile b/usr.sbin/bsdconfig/packages/Makefile
new file mode 100644
index 0000000..fa5bd30
--- /dev/null
+++ b/usr.sbin/bsdconfig/packages/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+SUBDIR= include
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/030.packages
+FILES= INDEX USAGE
+
+SCRIPTSDIR= ${FILESDIR}
+SCRIPTS= packages
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/packages/Makefile.depend b/usr.sbin/bsdconfig/packages/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/packages/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/packages/USAGE b/usr.sbin/bsdconfig/packages/USAGE
new file mode 100644
index 0000000..5f99b38
--- /dev/null
+++ b/usr.sbin/bsdconfig/packages/USAGE
@@ -0,0 +1,37 @@
+# Copyright (c) 2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+Usage: bsdconfig @PROGRAM_NAME@ [OPTIONS]
+
+OPTIONS:
+ -d Provide lots of debugging info on standard-out when running.
+ -D file Send debugging info to file. If file begins with a plus-sign
+ debug info is sent to both standard-out and file (minus the
+ leading plus).
+ -h Print this usage statement and exit.
+ -S Secure X11 mode (implies `-X'). As root, always prompt-for
+ and validate sudo(8) username/password before starting.
+ -X Use Xdialog(1) in place of dialog(1).
diff --git a/usr.sbin/bsdconfig/packages/include/Makefile b/usr.sbin/bsdconfig/packages/include/Makefile
new file mode 100644
index 0000000..3d30fd2
--- /dev/null
+++ b/usr.sbin/bsdconfig/packages/include/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/030.packages/include
+FILES= messages.subr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/packages/include/Makefile.depend b/usr.sbin/bsdconfig/packages/include/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/packages/include/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/packages/include/messages.subr b/usr.sbin/bsdconfig/packages/include/messages.subr
new file mode 100755
index 0000000..8e945ac
--- /dev/null
+++ b/usr.sbin/bsdconfig/packages/include/messages.subr
@@ -0,0 +1,27 @@
+# Copyright (c) 2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+msg_package_selection="Package Selection"
diff --git a/usr.sbin/bsdconfig/packages/packages b/usr.sbin/bsdconfig/packages/packages
new file mode 100755
index 0000000..ec429f2
--- /dev/null
+++ b/usr.sbin/bsdconfig/packages/packages
@@ -0,0 +1,82 @@
+#!/bin/sh
+#-
+# Copyright (c) 2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+# Prevent device.subr (included indirectly via media/*.subr below) from auto
+# scanning; we'll perform this manually using f_device_get_all() during init
+# but only after we've successfully completed f_mustberoot_init().
+#
+DEVICE_SELF_SCAN_ALL=NO
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/media/common.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/packages/packages.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="030.packages"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_package_selection"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+f_device_get_all
+
+#
+# Display the package configuration menu and exit
+#
+trap 'f_media_close' EXIT
+f_package_config
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/password/INDEX b/usr.sbin/bsdconfig/password/INDEX
new file mode 100644
index 0000000..10f23db
--- /dev/null
+++ b/usr.sbin/bsdconfig/password/INDEX
@@ -0,0 +1,57 @@
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+#
+# Title that will be shown in the bsdconfig menu.
+#
+menu_title="Root Password"
+
+#
+# A short descriptive line shown at the bottom of the bsdconfig menu. keep it
+# short because any line longer than the terminal width will be truncated.
+#
+menu_help="Set the system manager's password"
+
+#
+# Two-part variable that defines an action to take when `keyword' is passed on
+# a bsdconfig command line. Variable takes the form "keyword|command" and
+# multiple occurrences of the variable (with different `keyword's, or different
+# `keyword's AND `command's) are allowed. If `command' begins with a '/' then
+# the full path to the program is needed. If `command' begins with anything
+# else it is a path relative to the directory this INDEX file is in. `keyword'
+# can be i18n'ed but `command' is the name of a script.
+#
+menu_selection="password|password"
+
+#
+# ------------ Items below this line do NOT need i18n translation ------------
+#
+# Name of the program to be run when this menu choice is selected. If it begins
+# with a '/' then the full path to the program is needed. If it begins with
+# anything else it is a path relative to the directory this INDEX file is in.
+#
+menu_program="password"
diff --git a/usr.sbin/bsdconfig/password/Makefile b/usr.sbin/bsdconfig/password/Makefile
new file mode 100644
index 0000000..d660e45
--- /dev/null
+++ b/usr.sbin/bsdconfig/password/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+SUBDIR= include share
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/040.password
+FILES= INDEX USAGE
+
+SCRIPTSDIR= ${FILESDIR}
+SCRIPTS= password
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/password/Makefile.depend b/usr.sbin/bsdconfig/password/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/password/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/password/USAGE b/usr.sbin/bsdconfig/password/USAGE
new file mode 100644
index 0000000..a06d8af
--- /dev/null
+++ b/usr.sbin/bsdconfig/password/USAGE
@@ -0,0 +1,37 @@
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+Usage: bsdconfig @PROGRAM_NAME@ [OPTIONS]
+
+OPTIONS:
+ -d Provide lots of debugging info on standard-out when running.
+ -D file Send debugging info to file. If file begins with a plus-sign
+ debug info is sent to both standard-out and file (minus the
+ leading plus).
+ -h Print this usage statement and exit.
+ -S Secure X11 mode (implies `-X'). As root, always prompt-for
+ and validate sudo(8) username/password before starting.
+ -X Use Xdialog(1) in place of dialog(1).
diff --git a/usr.sbin/bsdconfig/password/include/Makefile b/usr.sbin/bsdconfig/password/include/Makefile
new file mode 100644
index 0000000..a146eb2
--- /dev/null
+++ b/usr.sbin/bsdconfig/password/include/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/040.password/include
+FILES= messages.subr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/password/include/Makefile.depend b/usr.sbin/bsdconfig/password/include/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/password/include/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/password/include/messages.subr b/usr.sbin/bsdconfig/password/include/messages.subr
new file mode 100644
index 0000000..655160d
--- /dev/null
+++ b/usr.sbin/bsdconfig/password/include/messages.subr
@@ -0,0 +1,35 @@
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+hline_alnum_punc_tab_enter="Use alpha-numeric, punctuation, TAB or ENTER"
+msg_cancel="Cancel"
+msg_enter_new_password="Enter New Password"
+msg_ok="OK"
+msg_password_changed="Password successfully changed."
+msg_password_is_empty="Password is empty."
+msg_passwords_do_not_match="Passwords do not match."
+msg_reenter_password="Re-enter Password"
+msg_root_password="Root Password"
diff --git a/usr.sbin/bsdconfig/password/password b/usr.sbin/bsdconfig/password/password
new file mode 100755
index 0000000..b73a8d0
--- /dev/null
+++ b/usr.sbin/bsdconfig/password/password
@@ -0,0 +1,85 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/password/password.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="040.password"
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ CONFIGURATION
+
+#
+# Default login of system manager
+#
+USER_ROOT=root
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_root_password"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Prompt the user to input a new password (and change it if they don't cancel)
+#
+if f_dialog_input_password; then
+ echo "$pw_password" | f_eval_catch "$0" pw \
+ 'pw usermod "%s" -h 0' "$USER_ROOT" || f_die
+ f_show_msg "$msg_password_changed"
+fi
+
+return $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/password/share/Makefile b/usr.sbin/bsdconfig/password/share/Makefile
new file mode 100644
index 0000000..65f9e8c
--- /dev/null
+++ b/usr.sbin/bsdconfig/password/share/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+FILESDIR= ${SHAREDIR}/bsdconfig/password
+FILES= password.subr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/password/share/Makefile.depend b/usr.sbin/bsdconfig/password/share/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/password/share/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/password/share/password.subr b/usr.sbin/bsdconfig/password/share/password.subr
new file mode 100644
index 0000000..2280339
--- /dev/null
+++ b/usr.sbin/bsdconfig/password/share/password.subr
@@ -0,0 +1,124 @@
+if [ ! "$_PASSWORD_PASSWORD_SUBR" ]; then _PASSWORD_PASSWORD_SUBR=1
+#
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." password/password.subr
+f_include $BSDCFG_SHARE/dialog.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="040.password"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+############################################################ FUNCTIONS
+
+# f_dialog_input_password
+#
+# Prompt the user to enter a password (twice). If the user does not cancel or
+# press ESC, the $pw_password environment variable will hold the password.
+#
+f_dialog_input_password()
+{
+ local prompt1="$msg_enter_new_password"
+ local prompt2="$msg_reenter_password"
+ local hline="$hline_alnum_punc_tab_enter"
+
+ local height1 width1
+ f_dialog_inputbox_size height1 width1 \
+ "$DIALOG_TITLE" \
+ "$DIALOG_BACKTITLE" \
+ "$prompt1" \
+ "" \
+ "$hline"
+
+ local height2 width2
+ f_dialog_inputbox_size height2 width2 \
+ "$DIALOG_TITLE" \
+ "$DIALOG_BACKTITLE" \
+ "$prompt2" \
+ "" \
+ "$hline"
+
+ #
+ # Loop until the user provides taint-free/valid input
+ #
+ local _password1 _password2
+ while :; do
+ _password1=$( $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --hline "$hline" \
+ --ok-label "$msg_ok" \
+ --cancel-label "$msg_cancel" \
+ --insecure \
+ --passwordbox "$prompt1" \
+ $height1 $width1 \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ ) || return $?
+ # Return if user either pressed ESC or chose Cancel/No
+ debug= f_dialog_line_sanitize _password1
+
+ _password2=$( $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --hline "$hline" \
+ --ok-label "$msg_ok" \
+ --cancel-label "$msg_cancel" \
+ --insecure \
+ --passwordbox "$prompt2" \
+ $height2 $width2 \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ ) || return $?
+ # Return if user either pressed ESC or chose Cancel/No
+ debug= f_dialog_line_sanitize _password2
+
+ # Check for NULL entry
+ if ! [ "$_password1" -o "$_password2" ]; then
+ f_show_msg "$msg_password_is_empty"
+ continue
+ fi
+
+ # Check for password mismatch
+ if [ "$_password1" != "$_password2" ]; then
+ f_show_msg "$msg_passwords_do_not_match"
+ continue
+ fi
+
+ pw_password="$_password1"
+ break
+ done
+
+ return $DIALOG_OK
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." password/password.subr
+
+fi # ! $_PASSWORD_PASSWORD_SUBR
diff --git a/usr.sbin/bsdconfig/security/INDEX b/usr.sbin/bsdconfig/security/INDEX
new file mode 100644
index 0000000..9e77186
--- /dev/null
+++ b/usr.sbin/bsdconfig/security/INDEX
@@ -0,0 +1,58 @@
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+#
+# Title that will be shown in the bsdconfig menu.
+#
+menu_title="Security"
+
+#
+# A short descriptive line shown at the bottom of the bsdconfig menu. keep it
+# short because any line longer than the terminal width will be truncated.
+#
+menu_help="Set Security Parameters"
+
+#
+# Two-part variable that defines an action to take when `keyword' is passed on
+# a bsdconfig command line. Variable takes the form "keyword|command" and
+# multiple occurrences of the variable (with different `keyword's, or different
+# `keyword's AND `command's) are allowed. If `command' begins with a '/' then
+# the full path to the program is needed. If `command' begins with anything
+# else it is a path relative to the directory this INDEX file is in. `keyword'
+# can be i18n'ed but `command' is the name of a script.
+#
+menu_selection="security|security"
+menu_selection="kern_securelevel|kern_securelevel"
+
+#
+# ------------ Items below this line do NOT need i18n translation ------------
+#
+# Name of the program to be run when this menu choice is selected. If it begins
+# with a '/' then the full path to the program is needed. If it begins with
+# anything else it is a path relative to the directory this INDEX file is in.
+#
+menu_program="security"
diff --git a/usr.sbin/bsdconfig/security/Makefile b/usr.sbin/bsdconfig/security/Makefile
new file mode 100644
index 0000000..af83cee
--- /dev/null
+++ b/usr.sbin/bsdconfig/security/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+SUBDIR= include
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/130.security
+FILES= INDEX USAGE
+
+SCRIPTSDIR= ${FILESDIR}
+SCRIPTS= kern_securelevel security
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/security/Makefile.depend b/usr.sbin/bsdconfig/security/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/security/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/security/USAGE b/usr.sbin/bsdconfig/security/USAGE
new file mode 100644
index 0000000..a06d8af
--- /dev/null
+++ b/usr.sbin/bsdconfig/security/USAGE
@@ -0,0 +1,37 @@
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+Usage: bsdconfig @PROGRAM_NAME@ [OPTIONS]
+
+OPTIONS:
+ -d Provide lots of debugging info on standard-out when running.
+ -D file Send debugging info to file. If file begins with a plus-sign
+ debug info is sent to both standard-out and file (minus the
+ leading plus).
+ -h Print this usage statement and exit.
+ -S Secure X11 mode (implies `-X'). As root, always prompt-for
+ and validate sudo(8) username/password before starting.
+ -X Use Xdialog(1) in place of dialog(1).
diff --git a/usr.sbin/bsdconfig/security/include/Makefile b/usr.sbin/bsdconfig/security/include/Makefile
new file mode 100644
index 0000000..2efcc0f
--- /dev/null
+++ b/usr.sbin/bsdconfig/security/include/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/130.security/include
+FILES= messages.subr securelevel.hlp
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/security/include/Makefile.depend b/usr.sbin/bsdconfig/security/include/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/security/include/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/security/include/messages.subr b/usr.sbin/bsdconfig/security/include/messages.subr
new file mode 100644
index 0000000..f6df667
--- /dev/null
+++ b/usr.sbin/bsdconfig/security/include/messages.subr
@@ -0,0 +1,50 @@
+# Copyright (c) 2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+hline_arrows_tab_enter="Press arrows, TAB or ENTER"
+hline_select_securelevel_to_operate_at="Select a securelevel to operate at"
+msg_cancel="Cancel"
+msg_disable_securelevels="Disable securelevels"
+msg_disabled="Disabled"
+msg_exit="Exit"
+msg_exit_this_menu="Exit this menu"
+msg_highly_secure="Highly Secure"
+msg_highly_secure_mode="Highly secure mode"
+msg_menu_text="This menu allows you to configure aspects of the operating system security\npolicy. Please read the system documentation carefully before modifying\nthese settings, as they may cause service disruption if used improperly.\n\nMost settings will take affect only following a system reboot."
+msg_network_secure="Network Secure"
+msg_network_secure_mode="Network secure mode"
+msg_nfs_port="NFS port"
+msg_nfs_port_desc="Require that the NFS clients use reserved ports"
+msg_ok="OK"
+msg_secure="Secure"
+msg_secure_mode="Secure mode"
+msg_securelevel="Securelevel"
+msg_securelevel_desc="Configure securelevels for the system"
+msg_securelevels_menu_text="This menu allows you to select the securelevel your system runs with.\nWhen operating at a securelevel, certain root privileges are disabled,\nwhich may increase resistance to exploits and protect system integrity.\nIn secure mode system flags may not be overridden by the root user,\naccess to direct kernel memory is limited, and kernel modules may not\nbe changed. In highly secure mode, mounted file systems may not be\nmodified on-disk, tampering with the system clock is prohibited. In\nnetwork secure mode configuration changes to firewalling are prohibited.\n "
+msg_securelevels_menu_title="Securelevel Configuration Menu"
+msg_system_security_options_menu="System Security Options Menu"
+msg_unknown_kern_securelevel_selection="Unknown kern.securelevel selection"
+msg_unknown_security_menu_selection="Unknown security menu selection"
diff --git a/usr.sbin/bsdconfig/security/include/securelevel.hlp b/usr.sbin/bsdconfig/security/include/securelevel.hlp
new file mode 100644
index 0000000..27eb1ec
--- /dev/null
+++ b/usr.sbin/bsdconfig/security/include/securelevel.hlp
@@ -0,0 +1,40 @@
+This menu allows you to configure the Securelevel mechanism in FreeBSD.
+
+Securelevels may be used to limit the privileges assigned to the
+root user in multi-user mode, which in turn may limit the effects of
+a root compromise, at the cost of reducing administrative functions.
+Refer to the security(7) and init(8) manual pages for complete details.
+
+ -1 Permanently insecure mode - always run the system in level 0
+ mode. This is the default initial value.
+
+ 0 Insecure mode - immutable and append-only flags may be turned
+ off. All devices may be read or written subject to their
+ permissions.
+
+ 1 Secure mode - the system immutable and system append-only
+ flags may not be turned off; disks for mounted file systems,
+ /dev/mem, /dev/kmem and /dev/io (if your platform has it)
+ may not be opened for writing; kernel modules (see kld(4))
+ may not be loaded or unloaded.
+
+ 2 Highly secure mode - same as secure mode, plus disks may not
+ be opened for writing (except by mount(2)) whether mounted or
+ not. This level precludes tampering with file systems by
+ unmounting them, but also inhibits running newfs(8) while the
+ system is multi-user.
+
+ In addition, kernel time changes are restricted to less than
+ or equal to one second. Attempts to change the time by more
+ than this will log the message ``Time adjustment clamped to +1
+ second''.
+
+ 3 Network secure mode - same as highly secure mode, plus IP
+ packet filter rules (see ipfw(8), ipfirewall(4) and pfctl(8))
+ cannot be changed and dummynet(4) or pf(4) configuration
+ cannot be adjusted.
+
+Securelevels must be used in combination with careful system design and
+application of protective mechanisms to prevent system configuration
+files from being modified in a way that compromises the protections of
+the securelevel variable upon reboot.
diff --git a/usr.sbin/bsdconfig/security/kern_securelevel b/usr.sbin/bsdconfig/security/kern_securelevel
new file mode 100755
index 0000000..9aa79b7
--- /dev/null
+++ b/usr.sbin/bsdconfig/security/kern_securelevel
@@ -0,0 +1,175 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="130.security"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+SECURELEVEL_HELPFILE=$BSDCFG_LIBE/$APP_DIR/include/securelevel.hlp
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ FUNCTIONS
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local prompt="$msg_securelevels_menu_text"
+ local menu_list="
+ '$msg_disabled' '$msg_disable_securelevels'
+ '$msg_secure' '$msg_secure_mode'
+ '$msg_highly_secure' '$msg_highly_secure_mode'
+ '$msg_network_secure' '$msg_network_secure_mode'
+ " # END-QUOTE
+ local defaultitem= # Calculated below
+ local hline="$hline_select_securelevel_to_operate_at"
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ case "$( f_sysrc_get kern_securelevel_enable )" in
+ [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
+ case "$( f_sysrc_get kern_securelevel )" in
+ 1) defaultitem="$msg_secure" ;;
+ 2) defaultitem="$msg_highly_secure" ;;
+ 3) defaultitem="$msg_network_secure" ;;
+ esac ;;
+ *)
+ defaultitem="$msg_disabled"
+ esac
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --help-button \
+ --help-label \"\$msg_help\" \
+ ${USE_XDIALOG:+--help \"\"} \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_menutag_store -s "$menu_choice"
+ return $retval
+}
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_securelevels_menu_title"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Launch application main menu (loop for additional `Help' button)
+#
+while :; do
+ dialog_menu_main
+ retval=$?
+ f_dialog_menutag_fetch mtag
+
+ if [ $retval -eq $DIALOG_HELP ]; then
+ f_show_help "$SECURELEVEL_HELPFILE"
+ continue
+ elif [ $retval -ne $DIALOG_OK ]; then
+ f_die
+ fi
+
+ break
+done
+
+case "$mtag" in
+"$msg_disabled")
+ f_eval_catch "$0" f_sysrc_set \
+ 'f_sysrc_set kern_securelevel_enable NO' || f_die
+ ;;
+"$msg_secure")
+ f_eval_catch "$0" f_sysrc_set \
+ 'f_sysrc_set kern_securelevel_enable YES' || f_die
+ f_eval_catch "$0" f_sysrc_set \
+ 'f_sysrc_set kern_securelevel 1' || f_die
+ ;;
+"$msg_highly_secure")
+ f_eval_catch "$0" f_sysrc_set \
+ 'f_sysrc_set kern_securelevel_enable YES' || f_die
+ f_eval_catch "$0" f_sysrc_set \
+ 'f_sysrc_set kern_securelevel 2' || f_die
+ ;;
+"$msg_network_secure")
+ f_eval_catch "$0" f_sysrc_set \
+ 'f_sysrc_set kern_securelevel_enable YES' || f_die
+ f_eval_catch "$0" f_sysrc_set \
+ 'f_sysrc_set kern_securelevel 3' || f_die
+ ;;
+*)
+ f_die 1 "$msg_unknown_kern_securelevel_selection"
+esac
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/security/security b/usr.sbin/bsdconfig/security/security
new file mode 100755
index 0000000..e045ad8
--- /dev/null
+++ b/usr.sbin/bsdconfig/security/security
@@ -0,0 +1,179 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="130.security"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ FUNCTIONS
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local prompt="$msg_menu_text"
+ local menu_list="
+ 'X $msg_exit' '$msg_exit_this_menu'
+ " # END-QUOTE
+ local defaultitem= # Calculated below
+ local hline="$hline_arrows_tab_enter"
+
+ # Obtain default-item (adjusted below for dynamic tags)
+ f_dialog_default_fetch defaultitem
+ local ditem="${defaultitem%%[$IFS]*}"
+
+ #
+ # Add dynamically tagged entry for kern_securelevels
+ #
+ local mark=" "
+ case "$( f_sysrc_get kern_securelevel_enable )" in
+ [Yy][Ee][Ss])
+ local kern_securelevel="$( f_sysrc_get kern_securelevel )"
+ if [ ${#kern_securelevel} -eq 1 ] &&
+ f_isinteger "$kern_securelevel" &&
+ [ $kern_securelevel -lt 9 ]
+ then
+ mark="$kern_securelevel"
+ else
+ mark="X"
+ fi ;;
+ *)
+ mark=" "
+ esac
+ menu_list="$menu_list
+ '2 [$mark] $msg_securelevel' '$msg_securelevel_desc'"
+
+ # Update default-item if appropriate
+ [ "$ditem" = 2 ] && defaultitem="2 [$mark] $msg_securelevel"
+
+ #
+ # Add dynamically tagged entry for nfs_reserved_port_only
+ #
+ case "$( f_sysrc_get nfs_reserved_port_only )" in
+ [Yy][Ee][Ss]) mark="X" ;;
+ *) mark=" " ;;
+ esac
+ menu_list="$menu_list
+ '3 [$mark] $msg_nfs_port' '$msg_nfs_port_desc'"
+
+ # Update default-item if appropriate
+ [ "$ditem" = 3 ] && defaultitem="3 [$mark] $msg_nfs_port"
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize menu_choice
+ f_dialog_menutag_store "$menu_choice"
+
+ # Only update default-item on success
+ [ $retval -eq $DIALOG_OK ] && f_dialog_default_store "$menu_choice"
+
+ return $retval
+}
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_system_security_options_menu"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Launch application main menu (loop for menu update after selection)
+#
+while :; do
+ dialog_menu_main || f_die
+ f_dialog_menutag_fetch mtag
+
+ case "$mtag" in
+ "X $msg_exit") break ;;
+ "2 ["?"] $msg_securelevel") # Configure securelevels for the system
+ $BSDCFG_LIBE/$APP_DIR/kern_securelevel ${USE_XDIALOG:+-X} ;;
+ "3 [X] $msg_nfs_port") # Require that NFS clients use reserved ports
+ f_eval_catch "$0" f_sysrc_set \
+ 'f_sysrc_set nfs_reserved_port_only NO' ;;
+ "3 [ ] $msg_nfs_port") # Same; Toggle value
+ f_eval_catch "$0" f_sysrc_set \
+ 'f_sysrc_set nfs_reserved_port_only YES' ;;
+ *)
+ f_die 1 "$msg_unknown_security_menu_selection"
+ esac
+done
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/share/Makefile b/usr.sbin/bsdconfig/share/Makefile
new file mode 100644
index 0000000..a09697a
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+SUBDIR= media packages
+
+FILESDIR= ${SHAREDIR}/bsdconfig
+FILES= common.subr device.subr dialog.subr geom.subr keymap.subr \
+ mustberoot.subr script.subr strings.subr struct.subr \
+ sysrc.subr variable.subr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/share/Makefile.depend b/usr.sbin/bsdconfig/share/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/share/common.subr b/usr.sbin/bsdconfig/share/common.subr
new file mode 100644
index 0000000..5996446
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/common.subr
@@ -0,0 +1,1047 @@
+if [ ! "$_COMMON_SUBR" ]; then _COMMON_SUBR=1
+#
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012-2015 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ CONFIGURATION
+
+#
+# Default file descriptors to link to stdout/stderr for passthru allowing
+# redirection within a sub-shell to bypass directly to the terminal.
+#
+: ${TERMINAL_STDOUT_PASSTHRU:=3}
+: ${TERMINAL_STDERR_PASSTHRU:=4}
+
+############################################################ GLOBALS
+
+#
+# Program name
+#
+pgm="${0##*/}"
+
+#
+# Program arguments
+#
+ARGC="$#"
+ARGV="$@"
+
+#
+# Global exit status variables
+#
+SUCCESS=0
+FAILURE=1
+
+#
+# Operating environment details
+#
+export UNAME_S="$( uname -s )" # Operating System (i.e. FreeBSD)
+export UNAME_P="$( uname -p )" # Processor Architecture (i.e. i386)
+export UNAME_M="$( uname -m )" # Machine platform (i.e. i386)
+export UNAME_R="$( uname -r )" # Release Level (i.e. X.Y-RELEASE)
+
+#
+# Default behavior is to call f_debug_init() automatically when loaded.
+#
+: ${DEBUG_SELF_INITIALIZE=1}
+
+#
+# Default behavior of f_debug_init() is to truncate $debugFile (set to NULL to
+# disable truncating the debug file when initializing). To get child processes
+# to append to the same log file, export this variarable (with a NULL value)
+# and also export debugFile with the desired value.
+#
+: ${DEBUG_INITIALIZE_FILE=1}
+
+#
+# Define standard optstring arguments that should be supported by all programs
+# using this include (unless DEBUG_SELF_INITIALIZE is set to NULL to prevent
+# f_debug_init() from autamatically processing "$@" for the below arguments):
+#
+# d Sets $debug to 1
+# D: Sets $debugFile to $OPTARG
+#
+GETOPTS_STDARGS="dD:"
+
+#
+# The getopts builtin will return 1 either when the end of "$@" or the first
+# invalid flag is reached. This makes it impossible to determine if you've
+# processed all the arguments or simply have hit an invalid flag. In the cases
+# where we want to tolerate invalid flags (f_debug_init() for example), the
+# following variable can be appended to your optstring argument to getopts,
+# preventing it from prematurely returning 1 before the end of the arguments.
+#
+# NOTE: This assumes that all unknown flags are argument-less.
+#
+GETOPTS_ALLFLAGS="abcdefghijklmnopqrstuvwxyz"
+GETOPTS_ALLFLAGS="${GETOPTS_ALLFLAGS}ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+GETOPTS_ALLFLAGS="${GETOPTS_ALLFLAGS}0123456789"
+
+#
+# When we get included, f_debug_init() will fire (unless $DEBUG_SELF_INITIALIZE
+# is set to disable automatic initialization) and process "$@" for a few global
+# options such as `-d' and/or `-D file'. However, if your program takes custom
+# flags that take arguments, this automatic processing may fail unexpectedly.
+#
+# The solution to this problem is to pre-define (before including this file)
+# the following variable (which defaults to NULL) to indicate that there are
+# extra flags that should be considered when performing automatic processing of
+# globally persistent flags.
+#
+: ${GETOPTS_EXTRA:=}
+
+############################################################ FUNCTIONS
+
+# f_dprintf $format [$arguments ...]
+#
+# Sensible debug function. Override in ~/.bsdconfigrc if desired.
+# See /usr/share/examples/bsdconfig/bsdconfigrc for example.
+#
+# If $debug is set and non-NULL, prints DEBUG info using printf(1) syntax:
+# + To $debugFile, if set and non-NULL
+# + To standard output if $debugFile is either NULL or unset
+# + To both if $debugFile begins with a single plus-sign (`+')
+#
+f_dprintf()
+{
+ [ "$debug" ] || return $SUCCESS
+ local fmt="$1"; shift
+ case "$debugFile" in ""|+*)
+ printf "DEBUG: $fmt${fmt:+\n}" "$@" >&${TERMINAL_STDOUT_PASSTHRU:-1}
+ esac
+ [ "${debugFile#+}" ] &&
+ printf "DEBUG: $fmt${fmt:+\n}" "$@" >> "${debugFile#+}"
+ return $SUCCESS
+}
+
+# f_debug_init
+#
+# Initialize debugging. Truncates $debugFile to zero bytes if set.
+#
+f_debug_init()
+{
+ #
+ # Process stored command-line arguments
+ #
+ set -- $ARGV
+ local OPTIND OPTARG flag
+ f_dprintf "f_debug_init: ARGV=[%s] GETOPTS_STDARGS=[%s]" \
+ "$ARGV" "$GETOPTS_STDARGS"
+ while getopts "$GETOPTS_STDARGS$GETOPTS_EXTRA$GETOPTS_ALLFLAGS" flag \
+ > /dev/null; do
+ case "$flag" in
+ d) debug=1 ;;
+ D) debugFile="$OPTARG" ;;
+ esac
+ done
+ shift $(( $OPTIND - 1 ))
+ f_dprintf "f_debug_init: debug=[%s] debugFile=[%s]" \
+ "$debug" "$debugFile"
+
+ #
+ # Automagically enable debugging if debugFile is set (and non-NULL)
+ #
+ [ "$debugFile" ] && { [ "${debug+set}" ] || debug=1; }
+
+ #
+ # Make debugging persistant if set
+ #
+ [ "$debug" ] && export debug
+ [ "$debugFile" ] && export debugFile
+
+ #
+ # Truncate debug file unless requested otherwise. Note that we will
+ # trim a leading plus (`+') from the value of debugFile to support
+ # persistant meaning that f_dprintf() should print both to standard
+ # output and $debugFile (minus the leading plus, of course).
+ #
+ local _debug_file="${debugFile#+}"
+ if [ "$_debug_file" -a "$DEBUG_INITIALIZE_FILE" ]; then
+ if ( umask 022 && :> "$_debug_file" ); then
+ f_dprintf "Successfully initialized debugFile \`%s'" \
+ "$_debug_file"
+ f_isset debug || debug=1 # turn debugging on if not set
+ else
+ unset debugFile
+ f_dprintf "Unable to initialize debugFile \`%s'" \
+ "$_debug_file"
+ fi
+ fi
+}
+
+# f_err $format [$arguments ...]
+#
+# Print a message to stderr (fd=2).
+#
+f_err()
+{
+ printf "$@" >&2
+}
+
+# f_quietly $command [$arguments ...]
+#
+# Run a command quietly (quell any output to stdout or stderr)
+#
+f_quietly()
+{
+ "$@" > /dev/null 2>&1
+}
+
+# f_have $anything ...
+#
+# A wrapper to the `type' built-in. Returns true if argument is a valid shell
+# built-in, keyword, or externally-tracked binary, otherwise false.
+#
+f_have()
+{
+ f_quietly type "$@"
+}
+
+# setvar $var_to_set [$value]
+#
+# Implement setvar for shells unlike FreeBSD sh(1).
+#
+if ! f_have setvar; then
+setvar()
+{
+ [ $# -gt 0 ] || return $SUCCESS
+ local __setvar_var_to_set="$1" __setvar_right="$2" __setvar_left=
+ case $# in
+ 1) unset "$__setvar_var_to_set"
+ return $? ;;
+ 2) : fall through ;;
+ *) f_err "setvar: too many arguments\n"
+ return $FAILURE
+ esac
+ case "$__setvar_var_to_set" in *[!0-9A-Za-z_]*)
+ f_err "setvar: %s: bad variable name\n" "$__setvar_var_to_set"
+ return 2
+ esac
+ while case "$__setvar_r" in *\'*) : ;; *) false ; esac
+ do
+ __setvar_left="$__setvar_left${__setvar_right%%\'*}'\\''"
+ __setvar_right="${__setvar_right#*\'}"
+ done
+ __setvar_left="$__setvar_left${__setvar_right#*\'}"
+ eval "$__setvar_var_to_set='$__setvar_left'"
+}
+fi
+
+# f_which $anything [$var_to_set]
+#
+# A fast built-in replacement for syntaxes such as foo=$( which bar ). In a
+# comparison of 10,000 runs of this function versus which, this function
+# completed in under 3 seconds, while `which' took almost a full minute.
+#
+# If $var_to_set is missing or NULL, output is (like which) to standard out.
+# Returns success if a match was found, failure otherwise.
+#
+f_which()
+{
+ local __name="$1" __var_to_set="$2"
+ case "$__name" in */*|'') return $FAILURE; esac
+ local __p __exec IFS=":" __found=
+ for __p in $PATH; do
+ __exec="$__p/$__name"
+ [ -f "$__exec" -a -x "$__exec" ] && __found=1 break
+ done
+ if [ "$__found" ]; then
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" "$__exec"
+ else
+ echo "$__exec"
+ fi
+ return $SUCCESS
+ fi
+ return $FAILURE
+}
+
+# f_getvar $var_to_get [$var_to_set]
+#
+# Utility function designed to go along with the already-builtin setvar.
+# Allows clean variable name indirection without forking or sub-shells.
+#
+# Returns error status if the requested variable ($var_to_get) is not set.
+#
+# If $var_to_set is missing or NULL, the value of $var_to_get is printed to
+# standard output for capturing in a sub-shell (which is less-recommended
+# because of performance degredation; for example, when called in a loop).
+#
+f_getvar()
+{
+ local __var_to_get="$1" __var_to_set="$2"
+ [ "$__var_to_set" ] || local value
+ eval [ \"\${$__var_to_get+set}\" ]
+ local __retval=$?
+ eval ${__var_to_set:-value}=\"\${$__var_to_get}\"
+ eval f_dprintf '"f_getvar: var=[%s] value=[%s] r=%u"' \
+ \"\$__var_to_get\" \"\$${__var_to_set:-value}\" \$__retval
+ [ "$__var_to_set" ] || { [ "$value" ] && echo "$value"; }
+ return $__retval
+}
+
+# f_isset $var
+#
+# Check if variable $var is set. Returns success if variable is set, otherwise
+# returns failure.
+#
+f_isset()
+{
+ eval [ \"\${${1%%[$IFS]*}+set}\" ]
+}
+
+# f_die [$status [$format [$arguments ...]]]
+#
+# Abruptly terminate due to an error optionally displaying a message in a
+# dialog box using printf(1) syntax.
+#
+f_die()
+{
+ local status=$FAILURE
+
+ # If there is at least one argument, take it as the status
+ if [ $# -gt 0 ]; then
+ status=$1
+ shift 1 # status
+ fi
+
+ # If there are still arguments left, pass them to f_show_msg
+ [ $# -gt 0 ] && f_show_msg "$@"
+
+ # Optionally call f_clean_up() function if it exists
+ f_have f_clean_up && f_clean_up
+
+ exit $status
+}
+
+# f_interrupt
+#
+# Interrupt handler.
+#
+f_interrupt()
+{
+ exec 2>&1 # fix sh(1) bug where stderr gets lost within async-trap
+ f_die
+}
+
+# f_show_info $format [$arguments ...]
+#
+# Display a message in a dialog infobox using printf(1) syntax.
+#
+f_show_info()
+{
+ local msg
+ msg=$( printf "$@" )
+
+ #
+ # Use f_dialog_infobox from dialog.subr if possible, otherwise fall
+ # back to dialog(1) (without options, making it obvious when using
+ # un-aided system dialog).
+ #
+ if f_have f_dialog_info; then
+ f_dialog_info "$msg"
+ else
+ dialog --infobox "$msg" 0 0
+ fi
+}
+
+# f_show_msg $format [$arguments ...]
+#
+# Display a message in a dialog box using printf(1) syntax.
+#
+f_show_msg()
+{
+ local msg
+ msg=$( printf "$@" )
+
+ #
+ # Use f_dialog_msgbox from dialog.subr if possible, otherwise fall
+ # back to dialog(1) (without options, making it obvious when using
+ # un-aided system dialog).
+ #
+ if f_have f_dialog_msgbox; then
+ f_dialog_msgbox "$msg"
+ else
+ dialog --msgbox "$msg" 0 0
+ fi
+}
+
+# f_show_err $format [$arguments ...]
+#
+# Display a message in a dialog box with ``Error'' i18n title (overridden by
+# setting msg_error) using printf(1) syntax.
+#
+f_show_err()
+{
+ local msg
+ msg=$( printf "$@" )
+
+ : ${msg:=${msg_an_unknown_error_occurred:-An unknown error occurred}}
+
+ if [ "$_DIALOG_SUBR" ]; then
+ f_dialog_title "${msg_error:-Error}"
+ f_dialog_msgbox "$msg"
+ f_dialog_title_restore
+ else
+ dialog --title "${msg_error:-Error}" --msgbox "$msg" 0 0
+ fi
+ return $SUCCESS
+}
+
+# f_yesno $format [$arguments ...]
+#
+# Display a message in a dialog yes/no box using printf(1) syntax.
+#
+f_yesno()
+{
+ local msg
+ msg=$( printf "$@" )
+
+ #
+ # Use f_dialog_yesno from dialog.subr if possible, otherwise fall
+ # back to dialog(1) (without options, making it obvious when using
+ # un-aided system dialog).
+ #
+ if f_have f_dialog_yesno; then
+ f_dialog_yesno "$msg"
+ else
+ dialog --yesno "$msg" 0 0
+ fi
+}
+
+# f_noyes $format [$arguments ...]
+#
+# Display a message in a dialog yes/no box using printf(1) syntax.
+# NOTE: THis is just like the f_yesno function except "No" is default.
+#
+f_noyes()
+{
+ local msg
+ msg=$( printf "$@" )
+
+ #
+ # Use f_dialog_noyes from dialog.subr if possible, otherwise fall
+ # back to dialog(1) (without options, making it obvious when using
+ # un-aided system dialog).
+ #
+ if f_have f_dialog_noyes; then
+ f_dialog_noyes "$msg"
+ else
+ dialog --defaultno --yesno "$msg" 0 0
+ fi
+}
+
+# f_show_help $file
+#
+# Display a language help-file. Automatically takes $LANG and $LC_ALL into
+# consideration when displaying $file (suffix ".$LC_ALL" or ".$LANG" will
+# automatically be added prior to loading the language help-file).
+#
+# If a language has been requested by setting either $LANG or $LC_ALL in the
+# environment and the language-specific help-file does not exist we will fall
+# back to $file without-suffix.
+#
+# If the language help-file does not exist, an error is displayed instead.
+#
+f_show_help()
+{
+ local file="$1"
+ local lang="${LANG:-$LC_ALL}"
+
+ [ -f "$file.$lang" ] && file="$file.$lang"
+
+ #
+ # Use f_dialog_textbox from dialog.subr if possible, otherwise fall
+ # back to dialog(1) (without options, making it obvious when using
+ # un-aided system dialog).
+ #
+ if f_have f_dialog_textbox; then
+ f_dialog_textbox "$file"
+ else
+ dialog --msgbox "$( cat "$file" 2>&1 )" 0 0
+ fi
+}
+
+# f_include $file
+#
+# Include a shell subroutine file.
+#
+# If the subroutine file exists but returns error status during loading, exit
+# is called and execution is prematurely terminated with the same error status.
+#
+f_include()
+{
+ local file="$1"
+ f_dprintf "f_include: file=[%s]" "$file"
+ . "$file" || exit $?
+}
+
+# f_include_lang $file
+#
+# Include a language file. Automatically takes $LANG and $LC_ALL into
+# consideration when including $file (suffix ".$LC_ALL" or ".$LANG" will
+# automatically by added prior to loading the language file).
+#
+# No error is produced if (a) a language has been requested (by setting either
+# $LANG or $LC_ALL in the environment) and (b) the language file does not
+# exist -- in which case we will fall back to loading $file without-suffix.
+#
+# If the language file exists but returns error status during loading, exit
+# is called and execution is prematurely terminated with the same error status.
+#
+f_include_lang()
+{
+ local file="$1"
+ local lang="${LANG:-$LC_ALL}"
+
+ f_dprintf "f_include_lang: file=[%s] lang=[%s]" "$file" "$lang"
+ if [ -f "$file.$lang" ]; then
+ . "$file.$lang" || exit $?
+ else
+ . "$file" || exit $?
+ fi
+}
+
+# f_usage $file [$key1 $value1 ...]
+#
+# Display USAGE file with optional pre-processor macro definitions. The first
+# argument is the template file containing the usage text to be displayed. If
+# $LANG or $LC_ALL (in order of preference, respectively) is set, ".encoding"
+# will automatically be appended as a suffix to the provided $file pathname.
+#
+# When processing $file, output begins at the first line containing that is
+# (a) not a comment, (b) not empty, and (c) is not pure-whitespace. All lines
+# appearing after this first-line are output, including (a) comments (b) empty
+# lines, and (c) lines that are purely whitespace-only.
+#
+# If additional arguments appear after $file, substitutions are made while
+# printing the contents of the USAGE file. The pre-processor macro syntax is in
+# the style of autoconf(1), for example:
+#
+# f_usage $file "FOO" "BAR"
+#
+# Will cause instances of "@FOO@" appearing in $file to be replaced with the
+# text "BAR" before being printed to the screen.
+#
+# This function is a two-parter. Below is the awk(1) portion of the function,
+# afterward is the sh(1) function which utilizes the below awk script.
+#
+f_usage_awk='
+BEGIN { found = 0 }
+{
+ if ( !found && $0 ~ /^[[:space:]]*($|#)/ ) next
+ found = 1
+ print
+}
+'
+f_usage()
+{
+ local file="$1"
+ local lang="${LANG:-$LC_ALL}"
+
+ f_dprintf "f_usage: file=[%s] lang=[%s]" "$file" "$lang"
+
+ shift 1 # file
+
+ local usage
+ if [ -f "$file.$lang" ]; then
+ usage=$( awk "$f_usage_awk" "$file.$lang" ) || exit $FAILURE
+ else
+ usage=$( awk "$f_usage_awk" "$file" ) || exit $FAILURE
+ fi
+
+ while [ $# -gt 0 ]; do
+ local key="$1"
+ export value="$2"
+ usage=$( echo "$usage" | awk \
+ "{ gsub(/@$key@/, ENVIRON[\"value\"]); print }" )
+ shift 2
+ done
+
+ f_err "%s\n" "$usage"
+
+ exit $FAILURE
+}
+
+# f_index_file $keyword [$var_to_set]
+#
+# Process all INDEX files known to bsdconfig and return the path to first file
+# containing a menu_selection line with a keyword portion matching $keyword.
+#
+# If $LANG or $LC_ALL (in order of preference, respectively) is set,
+# "INDEX.encoding" files will be searched first.
+#
+# If no file is found, error status is returned along with the NULL string.
+#
+# If $var_to_set is NULL or missing, output is printed to stdout (which is less
+# recommended due to performance degradation; in a loop for example).
+#
+# This function is a two-parter. Below is the awk(1) portion of the function,
+# afterward is the sh(1) function which utilizes the below awk script.
+#
+f_index_file_awk='
+# Variables that should be defined on the invocation line:
+# -v keyword="keyword"
+BEGIN { found = 0 }
+( $0 ~ "^menu_selection=\"" keyword "\\|" ) {
+ print FILENAME
+ found++
+ exit
+}
+END { exit ! found }
+'
+f_index_file()
+{
+ local __keyword="$1" __var_to_set="$2"
+ local __lang="${LANG:-$LC_ALL}"
+ local __indexes="$BSDCFG_LIBE${BSDCFG_LIBE:+/}*/INDEX"
+
+ f_dprintf "f_index_file: keyword=[%s] lang=[%s]" "$__keyword" "$__lang"
+
+ if [ "$__lang" ]; then
+ if [ "$__var_to_set" ]; then
+ eval "$__var_to_set"='"$( awk -v keyword="$__keyword" \
+ "$f_index_file_awk" $__indexes.$__lang
+ )"' && return $SUCCESS
+ else
+ awk -v keyword="$__keyword" "$f_index_file_awk" \
+ $__indexes.$__lang && return $SUCCESS
+ fi
+ # No match, fall-thru to non-i18n sources
+ fi
+ if [ "$__var_to_set" ]; then
+ eval "$__var_to_set"='"$( awk -v keyword="$__keyword" \
+ "$f_index_file_awk" $__indexes )"' && return $SUCCESS
+ else
+ awk -v keyword="$__keyword" "$f_index_file_awk" $__indexes &&
+ return $SUCCESS
+ fi
+
+ # No match? Fall-thru to `local' libexec sources (add-on modules)
+
+ [ "$BSDCFG_LOCAL_LIBE" ] || return $FAILURE
+ __indexes="$BSDCFG_LOCAL_LIBE/*/INDEX"
+ if [ "$__lang" ]; then
+ if [ "$__var_to_set" ]; then
+ eval "$__var_to_set"='"$( awk -v keyword="$__keyword" \
+ "$f_index_file_awk" $__indexes.$__lang
+ )"' && return $SUCCESS
+ else
+ awk -v keyword="$__keyword" "$f_index_file_awk" \
+ $__indexes.$__lang && return $SUCCESS
+ fi
+ # No match, fall-thru to non-i18n sources
+ fi
+ if [ "$__var_to_set" ]; then
+ eval "$__var_to_set"='$( awk -v keyword="$__keyword" \
+ "$f_index_file_awk" $__indexes )"'
+ else
+ awk -v keyword="$__keyword" "$f_index_file_awk" $__indexes
+ fi
+}
+
+# f_index_menusel_keyword $indexfile $pgm [$var_to_set]
+#
+# Process $indexfile and return only the keyword portion of the menu_selection
+# line with a command portion matching $pgm.
+#
+# This function is for internationalization (i18n) mapping of the on-disk
+# scriptname ($pgm) into the localized language (given language-specific
+# $indexfile). If $LANG or $LC_ALL (in orderder of preference, respectively) is
+# set, ".encoding" will automatically be appended as a suffix to the provided
+# $indexfile pathname.
+#
+# If, within $indexfile, multiple $menu_selection values map to $pgm, only the
+# first one will be returned. If no mapping can be made, the NULL string is
+# returned.
+#
+# If $indexfile does not exist, error status is returned with NULL.
+#
+# If $var_to_set is NULL or missing, output is printed to stdout (which is less
+# recommended due to performance degradation; in a loop for example).
+#
+# This function is a two-parter. Below is the awk(1) portion of the function,
+# afterward is the sh(1) function which utilizes the below awk script.
+#
+f_index_menusel_keyword_awk='
+# Variables that should be defined on the invocation line:
+# -v pgm="program_name"
+#
+BEGIN {
+ prefix = "menu_selection=\""
+ plen = length(prefix)
+ found = 0
+}
+{
+ if (!match($0, "^" prefix ".*\\|.*\"")) next
+
+ keyword = command = substr($0, plen + 1, RLENGTH - plen - 1)
+ sub(/^.*\|/, "", command)
+ sub(/\|.*$/, "", keyword)
+
+ if ( command == pgm )
+ {
+ print keyword
+ found++
+ exit
+ }
+}
+END { exit ! found }
+'
+f_index_menusel_keyword()
+{
+ local __indexfile="$1" __pgm="$2" __var_to_set="$3"
+ local __lang="${LANG:-$LC_ALL}" __file="$__indexfile"
+
+ [ -f "$__indexfile.$__lang" ] && __file="$__indexfile.$__lang"
+ f_dprintf "f_index_menusel_keyword: index=[%s] pgm=[%s] lang=[%s]" \
+ "$__file" "$__pgm" "$__lang"
+
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" "$( awk \
+ -v pgm="$__pgm" "$f_index_menusel_keyword_awk" "$__file"
+ )"
+ else
+ awk -v pgm="$__pgm" "$f_index_menusel_keyword_awk" "$__file"
+ fi
+}
+
+# f_index_menusel_command $indexfile $keyword [$var_to_set]
+#
+# Process $indexfile and return only the command portion of the menu_selection
+# line with a keyword portion matching $keyword.
+#
+# This function is for mapping [possibly international] keywords into the
+# command to be executed. If $LANG or $LC_ALL (order of preference) is set,
+# ".encoding" will automatically be appended as a suffix to the provided
+# $indexfile pathname.
+#
+# If, within $indexfile, multiple $menu_selection values map to $keyword, only
+# the first one will be returned. If no mapping can be made, the NULL string is
+# returned.
+#
+# If $indexfile doesn't exist, error status is returned with NULL.
+#
+# If $var_to_set is NULL or missing, output is printed to stdout (which is less
+# recommended due to performance degradation; in a loop for example).
+#
+# This function is a two-parter. Below is the awk(1) portion of the function,
+# afterward is the sh(1) function which utilizes the below awk script.
+#
+f_index_menusel_command_awk='
+# Variables that should be defined on the invocation line:
+# -v key="keyword"
+#
+BEGIN {
+ prefix = "menu_selection=\""
+ plen = length(prefix)
+ found = 0
+}
+{
+ if (!match($0, "^" prefix ".*\\|.*\"")) next
+
+ keyword = command = substr($0, plen + 1, RLENGTH - plen - 1)
+ sub(/^.*\|/, "", command)
+ sub(/\|.*$/, "", keyword)
+
+ if ( keyword == key )
+ {
+ print command
+ found++
+ exit
+ }
+}
+END { exit ! found }
+'
+f_index_menusel_command()
+{
+ local __indexfile="$1" __keyword="$2" __var_to_set="$3" __command
+ local __lang="${LANG:-$LC_ALL}" __file="$__indexfile"
+
+ [ -f "$__indexfile.$__lang" ] && __file="$__indexfile.$__lang"
+ f_dprintf "f_index_menusel_command: index=[%s] key=[%s] lang=[%s]" \
+ "$__file" "$__keyword" "$__lang"
+
+ [ -f "$__file" ] || return $FAILURE
+ __command=$( awk -v key="$__keyword" \
+ "$f_index_menusel_command_awk" "$__file" ) || return $FAILURE
+
+ #
+ # If the command pathname is not fully qualified fix-up/force to be
+ # relative to the $indexfile directory.
+ #
+ case "$__command" in
+ /*) : already fully qualified ;;
+ *)
+ local __indexdir="${__indexfile%/*}"
+ [ "$__indexdir" != "$__indexfile" ] || __indexdir="."
+ __command="$__indexdir/$__command"
+ esac
+
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" "$__command"
+ else
+ echo "$__command"
+ fi
+}
+
+# f_running_as_init
+#
+# Returns true if running as init(1).
+#
+f_running_as_init()
+{
+ #
+ # When a custom init(8) performs an exec(3) to invoke a shell script,
+ # PID 1 becomes sh(1) and $PPID is set to 1 in the executed script.
+ #
+ [ ${PPID:-0} -eq 1 ] # Return status
+}
+
+# f_mounted $local_directory
+# f_mounted -b $device
+#
+# Return success if a filesystem is mounted on a particular directory. If `-b'
+# is present, instead check that the block device (or a partition thereof) is
+# mounted.
+#
+f_mounted()
+{
+ local OPTIND OPTARG flag use_device=
+ while getopts b flag; do
+ case "$flag" in
+ b) use_device=1 ;;
+ esac
+ done
+ shift $(( $OPTIND - 1 ))
+ if [ "$use_device" ]; then
+ local device="$1"
+ mount | grep -Eq \
+ "^$device([[:space:]]|p[0-9]|s[0-9]|\.nop|\.eli)"
+ else
+ [ -d "$dir" ] || return $FAILURE
+ mount | grep -Eq " on $dir \([^)]+\)$"
+ fi
+ # Return status is that of last grep(1)
+}
+
+# f_eval_catch [-de] [-k $var_to_set] $funcname $utility \
+# $format [$arguments ...]
+#
+# Silently evaluate a command in a sub-shell and test for error. If debugging
+# is enabled a copy of the command and its output is sent to debug (either
+# stdout or file depending on environment). If an error occurs, output of the
+# command is displayed in a dialog(1) msgbox using the [above] f_show_err()
+# function (unless optional `-d' flag is given, then no dialog).
+#
+# The $funcname argument is sent to debugging while the $utility argument is
+# used in the title of the dialog box. The command that is executed as well as
+# sent to debugging with $funcname is the product of the printf(1) syntax
+# produced by $format with optional $arguments.
+#
+# The following options are supported:
+#
+# -d Do not use dialog(1).
+# -e Produce error text from failed command on stderr.
+# -k var Save output from the command in var.
+#
+# Example 1:
+#
+# debug=1
+# f_eval_catch myfunc echo 'echo "%s"' "Hello, World!"
+#
+# Produces the following debug output:
+#
+# DEBUG: myfunc: echo "Hello, World!"
+# DEBUG: myfunc: retval=0 <output below>
+# Hello, World!
+#
+# Example 2:
+#
+# debug=1
+# f_eval_catch -k contents myfunc cat 'cat "%s"' /some/file
+# # dialog(1) Error ``cat: /some/file: No such file or directory''
+# # contents=[cat: /some/file: No such file or directory]
+#
+# Produces the following debug output:
+#
+# DEBUG: myfunc: cat "/some/file"
+# DEBUG: myfunc: retval=1 <output below>
+# cat: /some/file: No such file or directory
+#
+# Example 3:
+#
+# debug=1
+# echo 123 | f_eval_catch myfunc rev rev
+#
+# Produces the following debug output:
+#
+# DEBUG: myfunc: rev
+# DEBUG: myfunc: retval=0 <output below>
+# 321
+#
+# Example 4:
+#
+# debug=1
+# f_eval_catch myfunc true true
+#
+# Produces the following debug output:
+#
+# DEBUG: myfunc: true
+# DEBUG: myfunc: retval=0 <no output>
+#
+# Example 5:
+#
+# f_eval_catch -de myfunc ls 'ls "%s"' /some/dir
+# # Output on stderr ``ls: /some/dir: No such file or directory''
+#
+# Example 6:
+#
+# f_eval_catch -dek contents myfunc ls 'ls "%s"' /etc
+# # Output from `ls' sent to stderr and also saved in $contents
+#
+f_eval_catch()
+{
+ local __no_dialog= __show_err= __var_to_set=
+
+ #
+ # Process local function arguments
+ #
+ local OPTIND OPTARG __flag
+ while getopts "dek:" __flag > /dev/null; do
+ case "$__flag" in
+ d) __no_dialog=1 ;;
+ e) __show_err=1 ;;
+ k) __var_to_set="$OPTARG" ;;
+ esac
+ done
+ shift $(( $OPTIND - 1 ))
+
+ local __funcname="$1" __utility="$2"; shift 2
+ local __cmd __output __retval
+
+ __cmd=$( printf -- "$@" )
+ f_dprintf "%s: %s" "$__funcname" "$__cmd" # Log command *before* eval
+ __output=$( exec 2>&1; eval "$__cmd" )
+ __retval=$?
+ if [ "$__output" ]; then
+ [ "$__show_err" ] && echo "$__output" >&2
+ f_dprintf "%s: retval=%i <output below>\n%s" "$__funcname" \
+ $__retval "$__output"
+ else
+ f_dprintf "%s: retval=%i <no output>" "$__funcname" $__retval
+ fi
+
+ ! [ "$__no_dialog" -o "$nonInteractive" -o $__retval -eq $SUCCESS ] &&
+ msg_error="${msg_error:-Error}${__utility:+: $__utility}" \
+ f_show_err "%s" "$__output"
+ # NB: f_show_err will handle NULL output appropriately
+
+ [ "$__var_to_set" ] && setvar "$__var_to_set" "$__output"
+
+ return $__retval
+}
+
+# f_count $var_to_set arguments ...
+#
+# Sets $var_to_set to the number of arguments minus one (the effective number
+# of arguments following $var_to_set).
+#
+# Example:
+# f_count count dog house # count=[2]
+#
+f_count()
+{
+ setvar "$1" $(( $# - 1 ))
+}
+
+# f_count_ifs $var_to_set string ...
+#
+# Sets $var_to_set to the number of words (split by the internal field
+# separator, IFS) following $var_to_set.
+#
+# Example 1:
+#
+# string="word1 word2 word3"
+# f_count_ifs count "$string" # count=[3]
+# f_count_ifs count $string # count=[3]
+#
+# Example 2:
+#
+# IFS=. f_count_ifs count www.freebsd.org # count=[3]
+#
+# NB: Make sure to use double-quotes if you are using a custom value for IFS
+# and you don't want the current value to effect the result. See example 3.
+#
+# Example 3:
+#
+# string="a-b c-d"
+# IFS=- f_count_ifs count "$string" # count=[3]
+# IFS=- f_count_ifs count $string # count=[4]
+#
+f_count_ifs()
+{
+ local __var_to_set="$1"
+ shift 1
+ set -- $*
+ setvar "$__var_to_set" $#
+}
+
+############################################################ MAIN
+
+#
+# Trap signals so we can recover gracefully
+#
+trap 'f_interrupt' SIGINT
+trap 'f_die' SIGTERM SIGPIPE SIGXCPU SIGXFSZ \
+ SIGFPE SIGTRAP SIGABRT SIGSEGV
+trap '' SIGALRM SIGPROF SIGUSR1 SIGUSR2 SIGHUP SIGVTALRM
+
+#
+# Clone terminal stdout/stderr so we can redirect to it from within sub-shells
+#
+eval exec $TERMINAL_STDOUT_PASSTHRU\>\&1
+eval exec $TERMINAL_STDERR_PASSTHRU\>\&2
+
+#
+# Self-initialize unless requested otherwise
+#
+f_dprintf "%s: DEBUG_SELF_INITIALIZE=[%s]" \
+ dialog.subr "$DEBUG_SELF_INITIALIZE"
+case "$DEBUG_SELF_INITIALIZE" in
+""|0|[Nn][Oo]|[Oo][Ff][Ff]|[Ff][Aa][Ll][Ss][Ee]) : do nothing ;;
+*) f_debug_init
+esac
+
+#
+# Log our operating environment for debugging purposes
+#
+f_dprintf "UNAME_S=[%s] UNAME_P=[%s] UNAME_R=[%s]" \
+ "$UNAME_S" "$UNAME_P" "$UNAME_R"
+
+f_dprintf "%s: Successfully loaded." common.subr
+
+fi # ! $_COMMON_SUBR
diff --git a/usr.sbin/bsdconfig/share/device.subr b/usr.sbin/bsdconfig/share/device.subr
new file mode 100644
index 0000000..d95684d
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/device.subr
@@ -0,0 +1,1396 @@
+if [ ! "$_DEVICE_SUBR" ]; then _DEVICE_SUBR=1
+#
+# Copyright (c) 2012-2014 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." device.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/geom.subr
+f_include $BSDCFG_SHARE/strings.subr
+f_include $BSDCFG_SHARE/struct.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+
+############################################################ GLOBALS
+
+NDEVICES=0 # Set by f_device_register(), used by f_device_*()
+
+#
+# A "device" from legacy sysinstall's point of view (mostly)
+#
+# NB: Disk devices have their `private' property set to GEOM ident
+# NB: USB devices have their `private' property set to USB disk device name
+#
+f_struct_define DEVICE \
+ capacity \
+ desc \
+ devname \
+ enabled \
+ flags \
+ get \
+ init \
+ name \
+ private \
+ shutdown \
+ type \
+ volume
+
+# Network devices have their `private' property set to this
+f_struct_define DEVICE_INFO \
+ extras \
+ ipaddr \
+ ipv6addr \
+ netmask \
+ use_dhcp \
+ use_rtsol
+
+#
+# Device types for f_device_register(), f_device_find(), et al.
+#
+setvar DEVICE_TYPE_ANY "any" # Any
+setvar DEVICE_TYPE_NONE "NONE" # Unknown
+setvar DEVICE_TYPE_DISK "DISK" # GEOM `DISK'
+setvar DEVICE_TYPE_FLOPPY "FD" # GEOM `FD'
+setvar DEVICE_TYPE_FTP "FTP" # Dynamic network device
+setvar DEVICE_TYPE_NETWORK "NETWORK" # See f_device_get_all_network
+setvar DEVICE_TYPE_CDROM "CDROM" # GEOM `DISK'
+setvar DEVICE_TYPE_USB "USB" # GEOM `PART'
+setvar DEVICE_TYPE_DOS "DOS" # GEOM `DISK' `PART' or `LABEL'
+setvar DEVICE_TYPE_UFS "UFS" # GEOM `DISK' `PART' or `LABEL'
+setvar DEVICE_TYPE_NFS "NFS" # Dynamic network device
+setvar DEVICE_TYPE_HTTP_PROXY "HTTP_PROXY" # Dynamic network device
+setvar DEVICE_TYPE_HTTP "HTTP" # Dynamic network device
+
+# Network devices have the following flags available
+setvar IF_ETHERNET 1
+setvar IF_WIRELESS 2
+setvar IF_ACTIVE 4
+
+#
+# Default behavior is to call f_device_get_all() automatically when loaded.
+#
+: ${DEVICE_SELF_SCAN_ALL=1}
+
+#
+# Device Catalog variables
+#
+DEVICE_CATALOG_APPEND_ONLY= # Used by f_device_catalog_set()
+NCATALOG_DEVICES=0 # Used by f_device_catalog_*() and MAIN
+
+#
+# A ``catalog'' device is for mapping GEOM devices to media devices (for
+# example, determining if a $GEOM_CLASS_DISK geom is $DEVICE_TYPE_CDROM or
+# $DEVICE_TYPE_DISK) and also getting default descriptions for devices that
+# either lack a GEOM provided description or lack a presence in GEOM)
+#
+f_struct_define CATALOG_DEVICE \
+ desc \
+ name \
+ type
+
+############################################################ FUNCTIONS
+
+# f_device_register $var_to_set $name $desc $devname $type $enabled
+# $init_function $get_function $shutdown_function
+# $private $capacity
+#
+# Register a device. A `structure' (see struct.subr) is created and if
+# $var_to_set is non-NULL, upon success holds the name of the struct created.
+# The remaining positional arguments correspond to the properties of the
+# `DEVICE' structure-type to be assigned (defined above).
+#
+# If not already registered (based on $name and $type), a new device is created
+# and $NDEVICES is incremented.
+#
+f_device_register()
+{
+ local __var_to_set="$1" __name="$2" __desc="$3" __devname="$4"
+ local __type="$5" __enabled="$6" __init_func="$7" __get_func="$8"
+ local __shutdown_func="$9" __private="${10}" __capacity="${11}"
+
+ # Required parameter(s)
+ [ "$__name" ] || return $FAILURE
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" "" || return $FAILURE
+ fi
+
+ local __device
+ if f_device_find -1 "$__name" "$__type" __device; then
+ f_struct_free "$__device"
+ f_struct_new DEVICE "$__device" || return $FAILURE
+ else
+ __device=device_$(( NDEVICES + 1 ))
+ f_struct_new DEVICE "$__device" || return $FAILURE
+ NDEVICES=$(( $NDEVICES + 1 ))
+ fi
+ $__device set name "$__name"
+ $__device set desc "$__desc"
+ $__device set devname "$__devname"
+ $__device set type "$__type"
+ $__device set enabled "$__enabled"
+ $__device set init "$__init_func"
+ $__device set get "$__get_func"
+ $__device set shutdown "$__shutdown_func"
+ $__device set private "$__private"
+ $__device set capacity "$__capacity"
+
+ [ "$__var_to_set" ] && setvar "$__var_to_set" "$__device"
+ return $SUCCESS
+}
+
+# f_device_reset
+#
+# Reset the registered device chain.
+#
+f_device_reset()
+{
+ local n=1
+ while [ $n -le $NDEVICES ]; do
+ f_device_shutdown device_$n
+
+ #
+ # XXX This potentially leaks $dev->private if it's being
+ # used to point to something dynamic, but you're not supposed
+ # to call this routine at such times that some open instance
+ # has its private member pointing somewhere anyway.
+ #
+ f_struct_free device_$n
+
+ n=$(( $n + 1 ))
+ done
+ NDEVICES=0
+}
+
+# f_device_reset_network
+#
+# Reset the registered network device chain.
+#
+f_device_reset_network()
+{
+ local n=1 device type private i
+ while [ $n -le $NDEVICES ]; do
+ device=device_$n
+ f_struct $device || continue
+ $device get type type
+ [ "$type" = "$DEVICE_TYPE_NETWORK" ] || continue
+
+ #
+ # Leave the device up (don't call shutdown routine)
+ #
+
+ # Network devices may have DEVICE_INFO private member
+ $device get private private
+ [ "$private" ] && f_struct_free "$private"
+
+ # Free the network device
+ f_struct_free $device
+
+ # Fill the gap we just created
+ i=$n
+ while [ $i -lt $NDEVICES ]; do
+ f_struct_copy device_$(( $i + 1 )) device_$i
+ done
+ f_struct_free device_$NDEVICES
+
+ # Finally decrement the number of devices
+ NDEVICES=$(( $NDEVICES - 1 ))
+
+ n=$(( $n + 1 ))
+ done
+}
+
+# f_device_get_all
+#
+# Get all device information for all devices.
+#
+f_device_get_all()
+{
+ local devname type desc capacity
+
+ f_dprintf "f_device_get_all: Probing devices..."
+ f_dialog_info "$msg_probing_devices_please_wait_this_can_take_a_while"
+
+ # First go for the network interfaces
+ f_device_get_all_network
+
+ # Next, go for the GEOM devices we might want to use as media
+ local geom geoms geom_name
+ debug= f_geom_find "" $GEOM_CLASS_DEV geoms
+ for geom in $geoms; do
+ if ! f_device_probe_geom $geom; then
+ debug= $geom get name geom_name
+ f_dprintf "WARNING! Unable to classify %s as %s" \
+ "GEOM device $geom_name" "media source"
+ fi
+ done
+}
+
+# f_device_get_all_network
+#
+# Get all network device information for attached network devices.
+#
+f_device_get_all_network()
+{
+ local devname desc device flags
+ for devname in $( ifconfig -l ); do
+ # Eliminate network devices that don't make sense
+ case "$devname" in
+ lo*) continue ;;
+ esac
+
+ # Try and find its description
+ f_device_desc "$devname" $DEVICE_TYPE_NETWORK desc
+
+ f_dprintf "Found network device named %s" "$devname"
+ debug= f_device_register device $devname "$desc" \
+ "$devname" $DEVICE_TYPE_NETWORK 1 \
+ f_media_init_network "" f_media_shutdown_network "" -1
+
+ # Set flags based on media and status
+ flags=0
+ eval "$( ifconfig $devname 2> /dev/null | awk -v var=flags '
+ function _or(var, mask) {
+ printf "%s=$(( $%s | $%s ))\n", var, var, mask
+ }
+ BEGIN { S = "[[:space:]]+" }
+ {
+ if (!match($0, "^" S "(media|status):" S)) next
+ value = substr($0, RLENGTH + 1)
+ if ($1 == "media:") {
+ if (value ~ /Ethernet/) _or(var, "IF_ETHERNET")
+ if (value ~ /802\.11/) _or(var, "IF_WIRELESS")
+ } else if ($1 == "status:") {
+ if (value ~ /^active/) _or(var, "IF_ACTIVE")
+ }
+ }' )"
+ $device set flags $flags
+ done
+}
+
+# f_device_rescan
+#
+# Rescan all devices, after closing previous set - convenience function.
+#
+f_device_rescan()
+{
+ f_device_reset
+ f_geom_rescan
+ f_device_get_all
+}
+
+# f_device_rescan_network
+#
+# Rescan all network devices, after closing previous set - for convenience.
+#
+f_device_rescan_network()
+{
+ f_device_reset_network
+ f_device_get_all_network
+}
+
+# f_device_probe_geom $geom
+#
+# Probe a single GEOM device and if it can be classified as a media source,
+# register it using f_device_register() with known type-specific arguments.
+#
+f_device_probe_geom()
+{
+ local geom="$1"
+
+ f_struct "$geom" || return $FAILURE
+
+ # geom associated variables
+ local geom_name geom_consumer provider_ref geom_provider=
+ local provider_geom provider_config provider_class=
+ local provider_config_type catalog_struct catalog_type
+ local disk_ident
+
+ # gnop(8)/geli(8) associated variables (p for `parent device')
+ local p_devname p_geom p_consumer p_provider_ref p_provider
+ local p_provider_config p_provider_geom p_provider_class
+
+ # md(4) associated variables
+ local config config_type config_file magic=
+
+ # Temporarily disable debugging to keep debug output light
+ local old_debug="$debug" debug=
+
+ #
+ # Get the GEOM name (for use below in device registration)
+ #
+ $geom get name devname || continue
+
+ #
+ # Attempt to get the consumer, provider, provider config, and
+ # provider class for this geom (errors ignored).
+ #
+ # NB: Each GEOM in the `DEV' class should have one consumer.
+ # That consumer should have a reference to its provider.
+ #
+ $geom get consumer1 geom_consumer
+ f_struct "$geom_consumer" get provider_ref provider_ref &&
+ f_geom_find_by id "$provider_ref" provider geom_provider
+ if f_struct "$geom_provider"; then
+ $geom_provider get config provider_config
+ f_geom_parent $geom_provider provider_geom &&
+ f_geom_parent $provider_geom provider_class
+ fi
+
+ #
+ # Get values for device registration (errors ignored)
+ #
+ f_struct "$provider_class" get name type
+ f_struct "$geom_provider" get mediasize capacity
+ f_struct "$provider_config" get descr desc
+
+ #
+ # For gnop(8), geli(8), or combination thereof, change device type to
+ # that of the consumer
+ #
+ p_devname= p_geom= p_provider= p_provider_config=
+ case "$devname" in
+ *.nop.eli) p_devname="${devname%.nop.eli}" ;;
+ *.eli.nop) p_devname="${devname%.eli.nop}" ;;
+ *.eli) p_devname="${devname%.eli}" ;;
+ *.nop) p_devname="${devname%.nop}" ;;
+ esac
+ [ "$p_devname" ] && f_geom_find "$p_devname" $GEOM_CLASS_DEV p_geom
+ if [ "${p_geom:-$geom}" != "$geom" ]; then
+ f_struct "$p_geom" get consumer1 p_consumer
+ f_struct "$p_consumer" get provider_ref p_provider_ref &&
+ f_geom_find_by id "$p_provider_ref" provider p_provider
+ if f_struct "$p_provider"; then
+ $p_provider get config p_provider_config
+ f_geom_parent $p_provider p_provider_geom &&
+ f_geom_parent $p_provider_geom p_provider_class
+ fi
+ f_struct "$p_provider_class" get name type
+ fi
+
+ # Look up geom device in device catalog for default description
+ f_device_catalog_get \
+ $DEVICE_TYPE_ANY "${p_devname:-$devname}" catalog_struct
+ [ "$desc" ] || f_struct "catalog_device_$catalog_struct" get desc desc
+
+ # Use device catalog entry for potential re-classification(s)
+ f_struct "catalog_device_$catalog_struct" get type catalog_type
+
+ # Restore debugging for this next part (device registration)
+ debug="$old_debug"
+
+ #
+ # Register the device
+ #
+ local retval device
+ case "$type" in
+ $GEOM_CLASS_DISK)
+ # First attempt to classify by device catalog (see MAIN)
+ case "$catalog_type" in
+ $DEVICE_TYPE_CDROM)
+ f_dprintf "Found CDROM device for disk %s" "$devname"
+ debug= f_device_register device "$devname" "$desc" \
+ "/dev/$devname" $DEVICE_TYPE_CDROM 1 \
+ f_media_init_cdrom f_media_get_cdrom \
+ f_media_shutdown_cdrom "" "$capacity" &&
+ return $SUCCESS
+ ;;
+ esac
+
+ # Fall back to register label device as a disk and taste it
+ f_dprintf "Found disk device named %s" "$devname"
+ debug= f_struct "$p_provider_config" get \
+ ident disk_ident ||
+ debug= f_struct "$provider_config" get \
+ ident disk_ident
+ debug= f_device_register device "$devname" "$desc" \
+ "/dev/$devname" $DEVICE_TYPE_DISK 1 \
+ "" "" "" "$disk_ident" "$capacity"
+ retval=$?
+
+ # Detect ``dangerously dedicated'' filesystems (errors ignored)
+ f_device_probe_disk_fs device "$devname" "$capacity" &&
+ retval=$SUCCESS
+
+ return $retval
+ ;;
+ $GEOM_CLASS_FD)
+ f_dprintf "Found floppy device named %s" "$devname"
+ debug= f_device_register device "$devname" "$desc" \
+ "/dev/$devname" $DEVICE_TYPE_FLOPPY 1 \
+ f_media_init_floppy f_media_get_floppy \
+ f_media_shutdown_floppy "" "$capacity"
+ return $?
+ ;;
+ $GEOM_CLASS_LABEL)
+ : fall through to below section # reduces indentation level
+ ;;
+ $GEOM_CLASS_MD)
+ f_dprintf "Found disk device named %s" "$devname"
+ debug= f_device_register device "$devname" "$desc" \
+ "/dev/$devname" $DEVICE_TYPE_DISK 1 \
+ "" "" "" "" "$capacity"
+ retval=$?
+
+ #
+ # Attempt to get file(1) magic to potentially classify as
+ # alternate media type. If unable to get magic, fall back to
+ # md(4) characteristics (such as vnode filename).
+ #
+ [ -r "/dev/$devname" ] &&
+ magic=$( file -bs "/dev/$devname" 2> /dev/null )
+ if [ ! "$magic" ]; then
+ # Fall back to md(4) characteristics
+ if f_struct "$p_provider_config"; then
+ config="$p_provider_config"
+ else
+ config="$provider_config"
+ fi
+ debug= f_struct "$config" get type config_type
+ debug= f_struct "$config" get file config_file
+
+ # Substitute magic for below based on type and file
+ case "$config_type=$config_file" in
+ vnode=*.iso) magic="ISO 9660" ;;
+ esac
+ fi
+ f_device_probe_disk_fs device \
+ "$devname" "$capacity" "$magic" &&
+ retval=$SUCCESS # Errors ignored
+
+ return $retval
+ ;;
+ $GEOM_CLASS_PART)
+ if f_struct "$p_provider_config"; then
+ config="$p_provider_config"
+ else
+ config="$provider_config"
+ fi
+ debug= f_struct "$config" get type provider_config_type
+ f_device_probe_geom_part device \
+ "$provider_config_type" "$devname" "$capacity"
+ retval=$?
+ device_type=$DEVICE_TYPE_NONE
+ [ $retval -eq $SUCCESS ] &&
+ debug= f_struct "$device" get type device_type
+
+ # Potentially re-classify as USB device
+ if [ "$device_type" = "$DEVICE_TYPE_UFS" -a \
+ "$catalog_type" = "$DEVICE_TYPE_USB" ]
+ then
+ f_dprintf "Found USB device for partition %s" \
+ "$devname"
+ debug= f_struct "$p_provider_geom" get \
+ name disk_name ||
+ debug= f_struct "$provider_geom" get \
+ name disk_name
+ debug= f_device_register device "$devname" "$desc" \
+ "/dev/$devname" $DEVICE_TYPE_USB 1 \
+ f_media_init_usb f_media_get_usb \
+ f_media_shutdown_usb "$disk_name" "$capacity"
+ retval=$?
+ fi
+
+ return $retval
+ ;;
+ $GEOM_CLASS_RAID)
+ # Use the provider geom name as the description
+ if [ ! "$desc" ]; then
+ f_struct "$p_provider_geom" get name desc ||
+ f_struct "$provider_geom" get name desc
+ fi
+
+ f_dprintf "Found disk device named %s" "$devname"
+ debug= f_device_register device \
+ "$devname" "${desc:-GEOM RAID device}" \
+ "/dev/$devname" $DEVICE_TYPE_DISK 1 \
+ "" "" "" "" "$capacity"
+ retval=$?
+
+ # Detect ``dangerously dedicated'' filesystems
+ f_device_probe_disk_fs device "$devname" "$capacity" &&
+ retval=$SUCCESS # Errors ignored
+
+ return $retval
+ ;;
+ $GEOM_CLASS_ZFS_ZVOL)
+ f_dprintf "Found disk device named %s" "$devname"
+ debug= f_device_register device \
+ "$devname" "${desc:-GEOM ZFS::ZVOL device}" \
+ "/dev/$devname" $DEVICE_TYPE_DISK 1 \
+ "" "" "" "" "$capacity"
+ retval=$?
+
+ # Detect ``dangerously dedicated'' filesystems
+ f_device_probe_disk_fs device "$devname" "$capacity" &&
+ retval=$SUCCESS # Errors ignored
+
+ return $retval
+ ;;
+ *)
+ return $FAILURE # Unknown GEOM class
+ esac
+
+ #
+ # Still here? Must be $GEOM_CLASS_LABEL
+ #
+
+ local label_geom label_devname label_devgeom= label_devconsumer
+ local label_devprovider= label_devprovider_ref label_devprovider_config
+ local label_gpart_type
+
+ if f_struct "$p_provider"; then
+ label_geom="$p_provider_geom"
+ else
+ label_geom="$provider_geom"
+ fi
+
+ case "$devname" in
+ gpt/*|gptid/*)
+ #
+ # Attempt to get the partition type by getting the `config'
+ # member of the provider for our device (which is named in the
+ # parent geom of our current provider).
+ #
+ debug= f_struct "$label_geom" get name label_devname &&
+ debug= f_geom_find "$label_devname" $GEOM_CLASS_DEV \
+ label_devgeom
+ debug= f_struct "$label_devgeom" get \
+ consumer1 label_devconsumer
+ debug= f_struct "$label_devconsumer" get \
+ provider_ref label_devprovider_ref &&
+ debug= f_geom_find_by id "$label_devprovider_ref" \
+ provider label_devprovider
+ debug= f_struct "$label_devprovider" get \
+ config label_devprovider_config
+ debug= f_struct "$label_devprovider_config" get \
+ type label_gpart_type
+
+ #
+ # Register device label based on partition type
+ #
+ f_device_probe_geom_part device \
+ "$label_gpart_type" "$devname" "$capacity"
+ return $?
+ ;;
+ iso9660/*)
+ f_dprintf "Found CDROM device labeled %s" "$devname"
+ debug= f_device_register device \
+ "$devname" "ISO9660 file system" \
+ "/dev/$devname" $DEVICE_TYPE_CDROM 1 \
+ f_media_init_cdrom f_media_get_cdrom \
+ f_media_shutdown_cdrom "" "$capacity"
+ return $?
+ ;;
+ label/*)
+ # For generic labels, use provider geom name as real device
+ debug= f_struct "$label_geom" get name label_devname
+
+ # Look up label geom device in device catalog for default desc
+ debug= f_device_catalog_get \
+ $DEVICE_TYPE_ANY "$label_devname" catalog_struct
+ [ "$desc" ] || debug= f_struct \
+ "catalog_device_$catalog_struct" get desc desc
+
+ # Use device catalog entry for potential re-classification(s)
+ debug= f_struct "catalog_device_$catalog_struct" get \
+ type catalog_type
+
+ # First attempt to classify by device catalog (see MAIN)
+ case "$catalog_type" in
+ $DEVICE_TYPE_CDROM)
+ f_dprintf "Found CDROM device for disk %s" "$devname"
+ debug= f_device_register device "$devname" "$desc" \
+ "/dev/$devname" $DEVICE_TYPE_CDROM 1 \
+ f_media_init_cdrom f_media_get_cdrom \
+ f_media_shutdown_cdrom "" "$capacity" &&
+ return $SUCCESS
+ ;;
+ esac
+
+ # Fall back to register label device as a disk and taste it
+ f_dprintf "Found disk device labeled %s" "$devname"
+ debug= f_device_register device \
+ "$devname" "GEOM LABEL device" \
+ "/dev/$devname" $DEVICE_TYPE_DISK 1 \
+ "" "" "" "" "$capacity"
+ retval=$?
+
+ # Detect ``dangerously dedicated'' filesystems (errors ignored)
+ f_device_probe_disk_fs device "$devname" "$capacity" &&
+ retval=$SUCCESS
+
+ return $retval
+ ;;
+ msdosfs/*)
+ f_dprintf "Found DOS partition labeled %s" "$devname"
+ debug= f_device_register device "$devname" "DOS file system" \
+ "/dev/$devname" $DEVICE_TYPE_DOS 1 \
+ f_media_init_dos f_media_get_dos \
+ f_media_shutdown_dos "" "$capacity"
+ return $?
+ ;;
+ ufs/*|ufsid/*)
+ f_dprintf "Found UFS partition labeled %s" "$devname"
+ debug= f_device_register device "$devname" "UFS file system" \
+ "/dev/$devname" $DEVICE_TYPE_UFS 1 \
+ f_media_init_ufs f_media_get_ufs \
+ f_media_shutdown_ufs "" "$capacity"
+ return $?
+ ;;
+ ext2fs/*|ntfs/*|reiserfs/*)
+ return $FAILURE # No media device handlers for these labels
+ ;;
+ esac
+
+ # Unable to classify GEOM label
+ return $FAILURE
+}
+
+# f_device_probe_geom_part $var_to_set $gpart_type $devname $capacity [$magic]
+#
+# Given a gpart(8) partition type and a device name, register the device if it
+# is a known partition type that we can handle. If $var_to_set is non-NULL,
+# upon success holds the DEVICE struct name of the registered device.
+#
+# Returns success if the device was successfully registered, failure otherwise.
+#
+f_device_probe_geom_part()
+{
+ local __var_to_set="$1" __gpart_type="$2" __devname="$3"
+ local __capacity="${4:--1}" __magic="$5"
+
+ #
+ # Register device based on partition type
+ # NB: !0 equates to `unused' bsdlabel
+ #
+ case "$__gpart_type" in
+ fat16|fat32)
+ f_dprintf "Found DOS partition named %s" "$__devname"
+ debug= f_device_register "$__var_to_set" \
+ "$__devname" "DOS file system" \
+ "/dev/$__devname" $DEVICE_TYPE_DOS 1 \
+ f_media_init_dos f_media_get_dos \
+ f_media_shutdown_dos "" "$__capacity"
+ return $?
+ ;;
+ freebsd|!0) # Commonly used inappropriately, taste for FreeBSD
+ [ -r "/dev/$__devname" -a ! "$__magic" ] &&
+ __magic=$( file -bs "/dev/$__devname" 2> /dev/null )
+ case "$__magic" in
+ *"Unix Fast File system"*)
+ f_dprintf "Found UFS partition named %s" "$__devname"
+ debug= f_device_register "$__var_to_set" \
+ "$__devname" "UFS file system" \
+ "/dev/$__devname" $DEVICE_TYPE_UFS 1 \
+ f_media_init_ufs f_media_get_ufs \
+ f_media_shutdown_ufs "" "$__capacity"
+ return $?
+ esac
+ return $FAILURE
+ ;;
+ freebsd-ufs)
+ f_dprintf "Found UFS partition named %s" "$__devname"
+ debug= f_device_register "$__var_to_set" \
+ "$__devname" "UFS file system" \
+ "/dev/$__devname" $DEVICE_TYPE_UFS 1 \
+ f_media_init_ufs f_media_get_ufs \
+ f_media_shutdown_ufs "" "$__capacity"
+ return $?
+ ;;
+ apple-*|linux-*|ms-*|netbsd-*|ntfs|vmware-*)
+ return $FAILURE # No device types for these
+ ;;
+ bios-*|ebr|efi|mbr|freebsd-boot|freebsd-swap)
+ return $FAILURE # Not a source for media
+ ;;
+ freebsd-nandfs|freebsd-vinum|freebsd-zfs)
+ return $FAILURE # Unsupported as media source
+ ;;
+ esac
+
+ return $FAILURE # Unknown partition type
+}
+
+# f_device_probe_disk_fs $var_to_set $devname [$capacity [$magic]]
+#
+# Given a device name, taste it and register the device if it is a so-called
+# ``dangerously dedicated'' file system written without a partition table.
+# Tasting is done using file(1) (specifically `file -bs') but if $magic is
+# present and non-NULL it is used instead. If $var_to_set is non-NULL, upon
+# success holds the DEVICE struct name of the registered device.
+#
+# Returns success if the device was successfully registered, failure otherwise.
+#
+f_device_probe_disk_fs()
+{
+ local __var_to_set="$1" __devname="$2" __capacity="${3:--1}"
+ local __magic="$4"
+
+ [ -r "/dev/${__devname#/dev/}" -a ! "$__magic" ] &&
+ __magic=$( file -bs "/dev/$__devname" 2> /dev/null )
+
+ case "$__magic" in
+ *"ISO 9660"*)
+ f_dprintf "Found CDROM device for disk %s" "$__devname"
+ debug= f_device_register "$__var_to_set" \
+ "$__devname" "ISO9660 file system" \
+ "/dev/$__devname" $DEVICE_TYPE_CDROM 1 \
+ f_media_init_cdrom f_media_get_cdrom \
+ f_media_shutdown_cdrom "" "$__capacity"
+ return $?
+ ;;
+ *"Unix Fast File system"*)
+ f_dprintf "Found UFS device for disk %s" "$__devname"
+ debug= f_device_register "$__var_to_set" \
+ "$__devname" "UFS file system" \
+ "/dev/$__devname" $DEVICE_TYPE_UFS 1 \
+ f_media_init_ufs f_media_get_ufs \
+ f_media_shutdown_ufs "" "$__capacity"
+ return $?
+ ;;
+ *"FAT (12 bit)"*|*"FAT (16 bit)"*|*"FAT (32 bit)"*)
+ f_dprintf "Found DOS device for disk %s" "$__devname"
+ debug= f_device_register "$__var_to_set" \
+ "$__devname" "DOS file system" \
+ "/dev/$__devname" $DEVICE_TYPE_DOS 1 \
+ f_media_init_dos f_media_get_dos \
+ f_media_shutdown_dos "" "$__capacity"
+ return $?
+ ;;
+ esac
+
+ return $FAILURE # Unknown file system type
+}
+
+# f_device_catalog_get $type $name [$var_to_set]
+#
+# Fetch the struct name of the catalog device matching device $name. If $type
+# is either NULL, missing, or set to $DEVICE_TYPE_ANY then only $name is used.
+# Returns success if a match was found, otherwise failure.
+#
+# If $var_to_set is missing or NULL, the struct name is printed to standard out
+# for capturing in a sub-shell (which is less-recommended because of
+# performance degredation; for example, when called in a loop).
+#
+f_device_catalog_get()
+{
+ local __type="$1" __name="$2" __var_to_set="$3"
+ local __dname=
+
+ # Return failure if no $name
+ [ "$__name" ] || return $FAILURE
+
+ # Disable debugging to keep debug output light
+ local debug=
+
+ #
+ # Attempt to create an alternate-form of $__name that contains the
+ # first contiguous string of numbers replaced with `%d' for comparison
+ # against stored pattern names (see MAIN).
+ #
+ local __left="${__name%%[0-9]*}" __right="${__name#*[0-9]}"
+ if [ "$__left" != "$__name" ]; then
+ # Chop leading digits from right 'til we hit first non-digit
+ while :; do
+ case "$__right" in
+ [0-9]*) __right="${__right#[0-9]}" ;;
+ *) break
+ esac
+ done
+ __dname="${__left}%d$__right"
+ fi
+
+ [ "$__type" = "$DEVICE_TYPE_ANY" ] && __type=
+ local __dev __dev_name __dev_type
+ for __dev in $DEVICE_CATALOG; do
+ catalog_device_$__dev get name __dev_name
+ [ "$__dev_name" = "$__name" -o "$__dev_name" = "$__dname" ] ||
+ continue
+ catalog_device_$__dev get type __dev_type
+ [ "${__type:-$__dev_type}" = "$__dev_type" ] || continue
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" $__dev
+ else
+ echo $__dev
+ fi
+ return $?
+ done
+
+ [ "$__var_to_set" ] && setvar "$__var_to_set" ""
+ return $FAILURE
+}
+
+# f_device_catalog_set $type $name $desc
+#
+# Store a description (desc) in-association with device $type and $name.
+# Returns success unless $name is NULL or missing. Use f_device_catalog_get()
+# routine with the same $name and optionally $type to retrieve catalog device
+# structure (see CATALOG_DEVICE struct definition in GLOBALS section).
+#
+f_device_catalog_set()
+{
+ local type="$1" name="$2" desc="$3"
+ local struct dev dev_type found=
+
+ [ "$name" ] || return $FAILURE
+
+ # Disable debugging to keep debug output light
+ local debug=
+
+ f_str2varname "$name" struct
+ if [ ! "$DEVICE_CATALOG_APPEND_ONLY" ]; then
+ for dev in $DEVICE_CATALOG; do
+ [ "$dev" = "$struct" ] || continue
+ found=1 break
+ done
+ fi
+ if [ "$found" ]; then
+ f_struct_free "catalog_device_$struct"
+ else
+ DEVICE_CATALOG="$DEVICE_CATALOG $struct"
+ fi
+ f_struct_new CATALOG_DEVICE "catalog_device_$struct" || return $FAILURE
+ catalog_device_$struct set type "$type"
+ catalog_device_$struct set name "$name"
+ catalog_device_$struct set desc "$desc"
+ return $SUCCESS
+}
+
+# f_device_desc $device_name $device_type [$var_to_set]
+#
+# Print a description for a device name (eg., `fxp0') given a specific device
+# type/class.
+#
+# If $var_to_set is missing or NULL, the device description is printed to
+# standard out for capturing in a sub-shell (which is less-recommended because
+# of performance degredation; for example, when called in a loop).
+#
+f_device_desc()
+{
+ local __name="$1" __type="$2" __var_to_set="$3"
+ local __devname __devunit __cp
+
+ # Check variables
+ [ "$__name" ] || return $SUCCESS
+ [ "$__type" = "$DEVICE_TYPE_ANY" ] && type=
+ [ "$__var_to_set" ] && { setvar "$__var_to_set" "" || return; }
+
+ #
+ # Return sysctl MIB dev.NAME.UNIT.%desc if it exists, otherwise fall
+ # through to further alternate methods.
+ #
+ if f_have sysctl; then
+ __devname="${__name%%[0-9]*}"
+ __devunit="${__name#$__devname}"
+ __devunit="${__devunit%%[!0-9]*}"
+ if [ "$__var_to_set" ]; then
+ if __cp=$(
+ sysctl -n "dev.$__devname.$__devunit.%desc" \
+ 2> /dev/null
+ ); then
+ setvar "$__var_to_set" "$__cp" &&
+ return $SUCCESS
+ fi
+ else
+ sysctl -n "dev.$__devname.$__devunit.%desc" \
+ 2> /dev/null && return $SUCCESS
+ fi
+ fi
+
+ # Look up device in catalog for default description
+ local __catalog_struct
+ debug= f_device_catalog_get "$__type" "$__name" __catalog_struct
+ debug= f_struct "catalog_device_$__catalog_struct" get \
+ desc "$__var_to_set" && return $SUCCESS
+
+ #
+ # Sensible fall-backs for specific types
+ #
+ case "$__type" in
+ $DEVICE_TYPE_CDROM) __cp="<unknown cdrom device type>" ;;
+ $DEVICE_TYPE_DISK) __cp="<unknown disk device type>" ;;
+ $DEVICE_TYPE_FLOPPY) __cp="<unknown floppy device type>" ;;
+ $DEVICE_TYPE_USB) __cp="<unknown USB storage device type>" ;;
+ $DEVICE_TYPE_NETWORK) __cp="<unknown network interface type>" ;;
+ *)
+ __cp="<unknown device type>"
+ esac
+
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" "$__cp"
+ else
+ echo "$__cp"
+ fi
+
+ return $FAILURE
+}
+
+# f_device_is_ethernet $device
+#
+# Returns true if $device is a wired Ethernet network interface. Otherwise
+# returns false. Example wired interfaces include: fxp0 em0 bge0 rl0 etc.
+#
+f_device_is_ethernet()
+{
+ local dev="$1" type flags
+
+ # Make sure we have an actual device by that name
+ f_struct "$dev" || return $FAILURE
+
+ # Make sure that the device is a network device
+ $dev get type type
+ [ "$type" = "$DEVICE_TYPE_NETWORK" ] || return $FAILURE
+
+ # Make sure that the media flags indicate that it is Ethernet
+ $dev get flags flags
+ [ $(( ${flags:-0} & $IF_ETHERNET )) -eq $IF_ETHERNET ]
+}
+
+# f_device_is_wireless $device
+#
+# Returns true if $device is a Wireless network interface. Otherwise returns
+# false. Examples of wireless interfaces include: iwn0
+#
+f_device_is_wireless()
+{
+ local dev="$1" type flags
+
+ # Make sure we have an actual device by that name
+ f_struct "$dev" || return $FAILURE
+
+ # Make sure that the device is a network device
+ $dev get type type
+ [ "$type" = "$DEVICE_TYPE_NETWORK" ] || return $FAILURE
+
+ # Make sure that the media flags indicate that it is 802.11 wireless
+ $dev get flags flags
+ [ $(( ${flags:-0} & $IF_WIRELESS )) -eq $IF_WIRELESS ]
+}
+
+# f_device_is_active $device
+#
+# Returns true if $device is active. Otherwise returns false. Currently this
+# only works for network interfaces.
+#
+f_device_is_active()
+{
+ local dev="$1" type flags=0
+
+ # Make sure we have an actual device by that name
+ f_struct "$dev" || return $FAILURE
+
+ $dev get type type
+ case "$type" in
+ $DEVICE_TYPE_NETWORK)
+ # Make sure that the media flags indicate that it is active
+ $dev get flags flags
+ [ $(( ${flags:-0} & $IF_ACTIVE )) -eq $IF_ACTIVE ]
+ ;;
+ *)
+ return $FAILURE
+ esac
+}
+
+# f_device_find [-1] $name [$type [$var_to_set]]
+#
+# Find one or more registered devices by name, type, or both. Returns a space-
+# separated list of devices matching the search criterion.
+#
+# If `-1' option flag is given, only the first matching device is returned.
+#
+# If $var_to_set is missing or NULL, the device name(s) are printed to standard
+# out for capturing in a sub-shell (which is less-recommended because of
+# performance degredation; for example, when called in a loop).
+#
+f_device_find()
+{
+ local OPTIND OPTARG flag only_one=
+ while getopts 1 flag; do
+ case "$flag" in
+ 1) only_one=1 ;;
+ esac
+ done
+ shift $(( $OPTIND - 1 ))
+
+ local __name="$1" __type="${2:-$DEVICE_TYPE_ANY}" __var_to_set="$3"
+ local __n=1 __devname __devtype __found=
+ while [ $__n -le $NDEVICES ]; do
+ device_$__n get name __devname
+ device_$__n get type __devtype
+ if [ "$__name" = "$__devname" -o ! "$__name" ] &&
+ [ "$__type" = "$DEVICE_TYPE_ANY" -o \
+ "$__type" = "$__devtype" ]
+ then
+ __found="$__found device_$__n"
+ [ "$only_one" ] && break
+ fi
+ __n=$(( $__n + 1 ))
+ done
+
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" "${__found# }"
+ else
+ echo $__found
+ fi
+ [ "$__found" ] # Return status
+}
+
+# f_device_init $device
+#
+# Initialize a device by evaluating its `init' function. The $device argument
+# is a DEVICE struct name.
+#
+f_device_init()
+{
+ local device="$1" init_func
+ f_struct "$device" || return $?
+ $device get init init_func
+ ${init_func:-:} "$device"
+}
+
+# f_device_get $device $file [$probe]
+#
+# Read $file by evaluating the device's `get' function. The file is commonly
+# produced on standard output (but it truly depends on the function called).
+# The $device argument is a DEVICE struct name.
+#
+f_device_get()
+{
+ local device="$1" file="$2" probe="$3" get_func
+ f_struct "$device" || return $?
+ $device get get get_func
+ ${get_func:-:} "$device" "$file" ${3+"$probe"}
+}
+
+# f_device_shutdown $device
+#
+# Shutdown a device by evaluating its `shutdown' function. The $device argument
+# is a DEVICE struct name.
+#
+f_device_shutdown()
+{
+ local device="$1" shutdown_func
+ f_struct "$device" || return $?
+ $device get shutdown shutdown_func
+ ${shutdown_func:-:} "$device"
+}
+
+# f_devices_sort_by $property $var_to_get [$var_to_set]
+#
+# Take list of devices from $var_to_get (separated by whitespace, newline
+# included) and sort them by $property (e.g., `name'). The sorted list of
+# DEVICE struct names is returned on standard output separated by whitespace
+# (newline to be specific) unless $var_to_set is present and non-NULL.
+#
+# This function is a two-parter. Below is the awk(1) portion of the function,
+# afterward is the sh(1) function which utilizes the below awk script.
+#
+f_device_sort_by_awk='
+# Variables that should be defined on the invocation line:
+# -v prop="property"
+function _asorti(src, dest)
+{
+ k = nitems = 0
+ for (i in src) dest[++nitems] = i
+ for (i = 1; i <= nitems; k = i++) {
+ idx = dest[i]
+ while ((k > 0) && (dest[k] > idx)) {
+ dest[k+1] = dest[k]; k--
+ }
+ dest[k+1] = idx
+ }
+ return nitems
+}
+{
+ split($0, devs, FS)
+ for (d in devs) {
+ name = ENVIRON["_struct_value_" devs[d] "_" prop]
+ devices[name] = devs[d]
+ }
+}
+END {
+ nitems = _asorti(devices, devices_sorted)
+ for (i = 1; i <= nitems; i++) print devices[devices_sorted[i]]
+}
+'
+f_device_sort_by()
+{
+ local __property="${1:-name}" __var_to_get="$2" __var_to_set="$3"
+
+ f_isset "$__var_to_get" || return $FAILURE
+
+ local __dev
+ for __dev in $( f_getvar "$__var_to_get" ); do
+ export _struct_value_${__dev}_$__property
+ done
+
+ local __cp
+ setvar "${__var_to_set:-__cp}" "$(
+ f_getvar "$__var_to_get" |
+ awk -v prop="$__property" "$f_device_sort_by_awk"
+ )"
+ [ "$__var_to_set" ] || echo "$__cp"
+}
+
+# f_device_menu $title $prompt $hline $device_type [$helpfile]
+#
+# Display a menu listing all the devices of a certain type in the system.
+#
+f_device_menu()
+{
+ f_dialog_title "$1"
+ local title="$DIALOG_TITLE" btitle="$DIALOG_BACKTITLE"
+ f_dialog_title_restore
+
+ local prompt="$2" hline="$3" type="$4" helpfile="$5"
+
+ local devs
+ f_device_find "" "$type" devs || return $DIALOG_CANCEL
+
+ local name desc menu_list=
+ f_device_sort_by name devs devs
+ for dev in $devs; do
+ $dev get name name
+ $dev get desc desc
+ f_shell_escape "$name" name
+ f_shell_escape "$desc" desc
+ menu_list="$menu_list
+ '$name' '$desc'" # END-QUOTE
+ done
+ menu_list="${menu_list#$NL}"
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$title\" \
+ \"\$btitle\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ local errexit=
+ case $- in *e*) errexit=1; esac
+ set +e
+
+ local mtag
+ while :; do
+ mtag=$( eval $DIALOG \
+ --title \"\$title\" \
+ --backtitle \"\$btitle\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ ${helpfile:+ \
+ --help-button \
+ --help-label \"\$msg_help\" \
+ ${USE_XDIALOG:+--help \"\"} \
+ } \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+
+ [ $retval -ne $DIALOG_HELP ] && break
+ # Otherwise, the Help button was pressed
+ f_show_help "$helpfile"
+ # ...then loop back to menu
+ done
+ f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
+
+ [ "$errexit" ] && set -e
+
+ if [ $retval -eq $DIALOG_OK ]; then
+ # Clean up the output of [X]dialog(1)
+ f_dialog_data_sanitize mtag
+
+ # Map the user's choice back to a struct name
+ local index device
+ index=$( eval f_dialog_menutag2index \"\$mtag\" $menu_list )
+ device=$( set -- $devs; eval echo \${$index} )
+
+ echo "$device" >&2
+ fi
+
+ return $retval
+}
+
+#
+# Short-hand
+#
+f_cdrom() { f_device_catalog_set $DEVICE_TYPE_CDROM "$1" "$2"; }
+f_disk() { f_device_catalog_set $DEVICE_TYPE_DISK "$1" "$2"; }
+f_floppy() { f_device_catalog_set $DEVICE_TYPE_FLOPPY "$1" "$2"; }
+f_usb() { f_device_catalog_set $DEVICE_TYPE_USB "$1" "$2"; }
+f_network() { f_device_catalog_set $DEVICE_TYPE_NETWORK "$1" "$2"; }
+
+############################################################ MAIN
+
+#
+# The below classifications allow us to re-group the GEOM devices from the
+# `DEV' GEOM class appropriately while providing fall-back descriptions both
+# for making the below code more maintainable and handling the rare case the
+# GEOM device lacks a description.
+#
+
+DEVICE_CATALOG_APPEND_ONLY=1 # Make initial loading faster
+
+# CDROM, Disk, Floppy, and USB devices/names
+f_cdrom "cd%d" "SCSI CDROM drive"
+f_cdrom "mcd%d" "Mitsumi (old model) CDROM drive"
+f_cdrom "scd%d" "Sony CDROM drive - CDU31/33A type"
+f_disk "aacd%d" "Adaptec FSA RAID array"
+f_disk "ada%d" "ATA/SATA disk device"
+f_disk "amrd%d" "AMI MegaRAID drive"
+f_disk "da%d" "SCSI disk device"
+f_disk "idad%d" "Compaq RAID array"
+f_disk "ipsd%d" "IBM ServeRAID RAID array"
+f_disk "md%d" "md(4) disk device"
+f_disk "mfid%d" "LSI MegaRAID SAS array"
+f_disk "mlxd%d" "Mylex RAID disk"
+f_disk "twed%d" "3ware ATA RAID array"
+f_disk "vtbd%d" "VirtIO Block Device"
+f_floppy "fd%d" "Floppy Drive unit A"
+f_usb "da%da" "USB Mass Storage Device"
+
+# Network interfaces/names
+f_network "ae%d" "Attansic/Atheros L2 Fast Ethernet"
+f_network "age%d" "Attansic/Atheros L1 Gigabit Ethernet"
+f_network "alc%d" "Atheros AR8131/AR8132 PCIe Ethernet"
+f_network "ale%d" "Atheros AR8121/AR8113/AR8114 PCIe Ethernet"
+f_network "an%d" "Aironet 4500/4800 802.11 wireless adapter"
+f_network "ath%d" "Atheros IEEE 802.11 wireless adapter"
+f_network "aue%d" "ADMtek USB Ethernet adapter"
+f_network "axe%d" "ASIX Electronics USB Ethernet adapter"
+f_network "bce%d" "Broadcom NetXtreme II Gigabit Ethernet card"
+f_network "bfe%d" "Broadcom BCM440x PCI Ethernet card"
+f_network "bge%d" "Broadcom BCM570x PCI Gigabit Ethernet card"
+f_network "bm%d" "Apple BMAC Built-in Ethernet"
+f_network "bwn%d" "Broadcom BCM43xx IEEE 802.11 wireless adapter"
+f_network "cas%d" "Sun Cassini/Cassini+ or NS DP83065 Saturn Ethernet"
+f_network "cc3i%d" "SDL HSSI sync serial PCI card"
+f_network "cue%d" "CATC USB Ethernet adapter"
+f_network "cxgb%d" "Chelsio T3 10Gb Ethernet card"
+f_network "dc%d" "DEC/Intel 21143 (and clones) PCI Fast Ethernet card"
+f_network "de%d" "DEC DE435 PCI NIC or other DC21040-AA based card"
+f_network "disc%d" "Software discard network interface"
+f_network "ed%d" "Novell NE1000/2000; 3C503; NE2000-compatible PCMCIA"
+f_network "el%d" "3Com 3C501 Ethernet card"
+f_network "em%d" "Intel(R) PRO/1000 Ethernet card"
+f_network "en%d" "Efficient Networks ATM PCI card"
+f_network "ep%d" "3Com 3C509 Ethernet card/3C589 PCMCIA"
+f_network "et%d" "Agere ET1310 based PCI Express Gigabit Ethernet card"
+f_network "ex%d" "Intel EtherExpress Pro/10 Ethernet card"
+f_network "fe%d" "Fujitsu MB86960A/MB86965A Ethernet card"
+f_network "fpa%d" "DEC DEFPA PCI FDDI card"
+f_network "fwe%d" "FireWire Ethernet emulation"
+f_network "fwip%d" "IP over FireWire"
+f_network "fxp%d" "Intel EtherExpress Pro/100B PCI Fast Ethernet card"
+f_network "gem%d" "Apple GMAC or Sun ERI/GEM Ethernet adapter"
+f_network "hme%d" "Sun HME (Happy Meal Ethernet) Ethernet adapter"
+f_network "ie%d" "AT&T StarLAN 10 and EN100; 3Com 3C507; NI5210"
+f_network "igb%d" "Intel(R) PRO/1000 PCI Express Gigabit Ethernet card"
+f_network "ipw%d" "Intel PRO/Wireless 2100 IEEE 802.11 adapter"
+f_network "iwi%d" "Intel PRO/Wireless 2200BG/2225BG/2915ABG adapter"
+f_network "iwn%d" "Intel Wireless WiFi Link 4965AGN IEEE 802.11n adapter"
+f_network "ix%d" "Intel Etherexpress Ethernet card"
+f_network "ixgb%d" "Intel(R) PRO/10Gb Ethernet card"
+f_network "ixgbe%d" "Intel(R) PRO/10Gb Ethernet card"
+f_network "jme%d" "JMicron JMC250 Gigabit/JMC260 Fast Ethernet"
+f_network "kue%d" "Kawasaki LSI USB Ethernet adapter"
+f_network "le%d" "AMD Am7900 LANCE or Am79C9xx PCnet Ethernet adapter"
+f_network "lge%d" "Level 1 LXT1001 Gigabit Ethernet card"
+f_network "lnc%d" "Lance/PCnet (Isolan/Novell NE2100/NE32-VL) Ethernet"
+f_network "lo%d" "Loop-back (local) network interface"
+f_network "lp%d" "Parallel Port IP (PLIP) peer connection"
+f_network "malo%d" "Marvell Libertas 88W8335 802.11 wireless adapter"
+f_network "msk%d" "Marvell/SysKonnect Yukon II Gigabit Ethernet"
+f_network "mxge%d" "Myricom Myri10GE 10Gb Ethernet card"
+f_network "nfe%d" "NVIDIA nForce MCP Ethernet"
+f_network "ng%d" "Vimage netgraph(4) bridged Ethernet device"
+f_network "nge%d" "NatSemi PCI Gigabit Ethernet card"
+f_network "nve%d" "NVIDIA nForce MCP Ethernet"
+f_network "nxge%d" "Neterion Xframe 10GbE Server/Storage adapter"
+f_network "pcn%d" "AMD Am79c79x PCI Ethernet card"
+f_network "plip%d" "Parallel Port IP (PLIP) peer connection"
+f_network "ral%d" "Ralink Technology IEEE 802.11 wireless adapter"
+f_network "ray%d" "Raytheon Raylink 802.11 wireless adapter"
+f_network "re%d" "RealTek 8139C+/8169/8169S/8110S PCI Ethernet adapter"
+f_network "rl%d" "RealTek 8129/8139 PCI Ethernet card"
+f_network "rue%d" "RealTek USB Ethernet card"
+f_network "rum%d" "Ralink Technology USB IEEE 802.11 wireless adapter"
+f_network "sf%d" "Adaptec AIC-6915 PCI Ethernet card"
+f_network "sge%d" "Silicon Integrated Systems SiS190/191 Ethernet"
+f_network "sis%d" "SiS 900/SiS 7016 PCI Ethernet card"
+f_network "sk%d" "SysKonnect PCI Gigabit Ethernet card"
+f_network "sn%d" "SMC/Megahertz Ethernet card"
+f_network "snc%d" "SONIC Ethernet card"
+f_network "sr%d" "SDL T1/E1 sync serial PCI card"
+f_network "ste%d" "Sundance ST201 PCI Ethernet card"
+f_network "stge%d" "Sundance/Tamarack TC9021 Gigabit Ethernet"
+f_network "ti%d" "Alteon Networks PCI Gigabit Ethernet card"
+f_network "tl%d" "Texas Instruments ThunderLAN PCI Ethernet card"
+f_network "tx%d" "SMC 9432TX Ethernet card"
+f_network "txp%d" "3Com 3cR990 Ethernet card"
+f_network "uath%d" "Atheros AR5005UG and AR5005UX USB wireless adapter"
+f_network "upgt%d" "Conexant/Intersil PrismGT USB wireless adapter"
+f_network "ural%d" "Ralink Technology RT2500USB 802.11 wireless adapter"
+f_network "urtw%d" "Realtek 8187L USB wireless adapter"
+f_network "vge%d" "VIA VT612x PCI Gigabit Ethernet card"
+f_network "vlan%d" "IEEE 802.1Q VLAN network interface"
+f_network "vr%d" "VIA VT3043/VT86C100A Rhine PCI Ethernet card"
+f_network "vx%d" "3COM 3c590 / 3c595 Ethernet card"
+f_network "wb%d" "Winbond W89C840F PCI Ethernet card"
+f_network "wi%d" "Lucent WaveLAN/IEEE 802.11 wireless adapter"
+f_network "wpi%d" "Intel 3945ABG IEEE 802.11 wireless adapter"
+f_network "wx%d" "Intel Gigabit Ethernet (82452) card"
+f_network "xe%d" "Xircom/Intel EtherExpress Pro100/16 Ethernet card"
+f_network "xl%d" "3COM 3c90x / 3c90xB PCI Ethernet card"
+f_network "zyd%d" "ZyDAS ZD1211/ZD1211B USB 802.11 wireless adapter"
+
+DEVICE_CATALOG_APPEND_ONLY= # Additional loading modifies existing devices
+
+f_count NCATALOG_DEVICES $DEVICE_CATALOG
+f_dprintf "%s: Initialized device catalog with %u names/descriptions." \
+ device.subr $NCATALOG_DEVICES
+
+#
+# Scan for the above devices unless requeted otherwise
+#
+f_dprintf "%s: DEVICE_SELF_SCAN_ALL=[%s]" device.subr "$DEVICE_SELF_SCAN_ALL"
+case "$DEVICE_SELF_SCAN_ALL" in
+""|0|[Nn][Oo]|[Oo][Ff][Ff]|[Ff][Aa][Ll][Ss][Ee]) : do nothing ;;
+*) f_device_get_all
+esac
+
+f_dprintf "%s: Successfully loaded." device.subr
+
+fi # ! $_DEVICE_SUBR
diff --git a/usr.sbin/bsdconfig/share/dialog.subr b/usr.sbin/bsdconfig/share/dialog.subr
new file mode 100644
index 0000000..d7c2d2c
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/dialog.subr
@@ -0,0 +1,2341 @@
+if [ ! "$_DIALOG_SUBR" ]; then _DIALOG_SUBR=1
+#
+# Copyright (c) 2006-2015 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." dialog.subr
+f_include $BSDCFG_SHARE/strings.subr
+f_include $BSDCFG_SHARE/variable.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+
+############################################################ CONFIGURATION
+
+#
+# Default file descriptor to link to stdout for dialog(1) passthru allowing
+# execution of dialog from within a sub-shell (so-long as its standard output
+# is explicitly redirected to this file descriptor).
+#
+: ${DIALOG_TERMINAL_PASSTHRU_FD:=${TERMINAL_STDOUT_PASSTHRU:-3}}
+
+############################################################ GLOBALS
+
+#
+# Default name of dialog(1) utility
+# NOTE: This is changed to "Xdialog" by the optional `-X' argument
+#
+DIALOG="dialog"
+
+#
+# Default dialog(1) title and backtitle text
+#
+DIALOG_TITLE="$pgm"
+DIALOG_BACKTITLE="bsdconfig"
+
+#
+# Settings used while interacting with dialog(1)
+#
+DIALOG_MENU_TAGS="123456789ABCDEFGHIJKLMNOPQRSTUVWYZabcdefghijklmnopqrstuvwxyz"
+
+#
+# Declare that we are fully-compliant with Xdialog(1) by unset'ing all
+# compatibility settings.
+#
+unset XDIALOG_HIGH_DIALOG_COMPAT
+unset XDIALOG_FORCE_AUTOSIZE
+unset XDIALOG_INFOBOX_TIMEOUT
+
+#
+# Exit codes for [X]dialog(1)
+#
+DIALOG_OK=${SUCCESS:-0}
+DIALOG_CANCEL=${FAILURE:-1}
+DIALOG_HELP=2
+DIALOG_ITEM_HELP=2
+DIALOG_EXTRA=3
+DIALOG_ITEM_HELP=4
+export DIALOG_ERROR=254 # sh(1) can't handle the default of `-1'
+DIALOG_ESC=255
+
+#
+# Default behavior is to call f_dialog_init() automatically when loaded.
+#
+: ${DIALOG_SELF_INITIALIZE=1}
+
+#
+# Default terminal size (used if/when running without a controlling terminal)
+#
+: ${DEFAULT_TERMINAL_SIZE:=24 80}
+
+#
+# Minimum width(s) for various dialog(1) implementations (sensible global
+# default(s) for all widgets of a given variant)
+#
+: ${DIALOG_MIN_WIDTH:=24}
+: ${XDIALOG_MIN_WIDTH:=35}
+
+#
+# When manually sizing Xdialog(1) widgets such as calendar and timebox, you'll
+# need to know the size of the embedded GUI objects because the height passed
+# to Xdialog(1) for these widgets has to be tall enough to accomodate them.
+#
+# These values are helpful when manually sizing with dialog(1) too, but in a
+# different way. dialog(1) does not make you accomodate the custom items in the
+# height (but does for width) -- a height of 3 will display three lines and a
+# full calendar, for example (whereas Xdialog will truncate the calendar if
+# given a height of 3). For dialog(1), use these values for making sure that
+# the height does not exceed max_height (obtained by f_dialog_max_size()).
+#
+DIALOG_CALENDAR_HEIGHT=15
+DIALOG_TIMEBOX_HEIGHT=6
+
+############################################################ GENERIC FUNCTIONS
+
+# f_dialog_data_sanitize $var_to_edit ...
+#
+# When using dialog(1) or Xdialog(1) sometimes unintended warnings or errors
+# are generated from underlying libraries. For example, if $LANG is set to an
+# invalid or unknown locale, the warnings from the Xdialog(1) libraries will
+# clutter the output. This function helps by providing a centralied function
+# that removes spurious warnings from the dialog(1) (or Xdialog(1)) response.
+#
+# Simply pass the name of one or more variables that need to be sanitized.
+# After execution, the variables will hold their newly-sanitized data.
+#
+f_dialog_data_sanitize()
+{
+ if [ "$#" -eq 0 ]; then
+ f_dprintf "%s: called with zero arguments" \
+ f_dialog_response_sanitize
+ return $FAILURE
+ fi
+
+ local __var_to_edit
+ for __var_to_edit in $*; do
+ # Skip warnings and trim leading/trailing whitespace
+ setvar $__var_to_edit "$( f_getvar $__var_to_edit | awk '
+ BEGIN { data = 0 }
+ {
+ if ( ! data )
+ {
+ if ( $0 ~ /^$/ ) next
+ if ( $0 ~ /^Gdk-WARNING \*\*:/ ) next
+ data = 1
+ }
+ print
+ }
+ ' )"
+ done
+}
+
+# f_dialog_line_sanitize $var_to_edit ...
+#
+# When using dialog(1) or Xdialog(1) sometimes unintended warnings or errors
+# are generated from underlying libraries. For example, if $LANG is set to an
+# invalid or unknown locale, the warnings from the Xdialog(1) libraries will
+# clutter the output. This function helps by providing a centralied function
+# that removes spurious warnings from the dialog(1) (or Xdialog(1)) response.
+#
+# Simply pass the name of one or more variables that need to be sanitized.
+# After execution, the variables will hold their newly-sanitized data.
+#
+# This function, unlike f_dialog_data_sanitize(), also removes leading/trailing
+# whitespace from each line.
+#
+f_dialog_line_sanitize()
+{
+ if [ "$#" -eq 0 ]; then
+ f_dprintf "%s: called with zero arguments" \
+ f_dialog_response_sanitize
+ return $FAILURE
+ fi
+
+ local __var_to_edit
+ for __var_to_edit in $*; do
+ # Skip warnings and trim leading/trailing whitespace
+ setvar $__var_to_edit "$( f_getvar $__var_to_edit | awk '
+ BEGIN { data = 0 }
+ {
+ if ( ! data )
+ {
+ if ( $0 ~ /^$/ ) next
+ if ( $0 ~ /^Gdk-WARNING \*\*:/ ) next
+ data = 1
+ }
+ sub(/^[[:space:]]*/, "")
+ sub(/[[:space:]]*$/, "")
+ print
+ }
+ ' )"
+ done
+}
+
+############################################################ TITLE FUNCTIONS
+
+# f_dialog_title [$new_title]
+#
+# Set the title of future dialog(1) ($DIALOG_TITLE) or backtitle of Xdialog(1)
+# ($DIALOG_BACKTITLE) invocations. If no arguments are given or the first
+# argument is NULL, the current title is returned.
+#
+# Each time this function is called, a backup of the current values is made
+# allowing a one-time (single-level) restoration of the previous title using
+# the f_dialog_title_restore() function (below).
+#
+f_dialog_title()
+{
+ local new_title="$1"
+
+ if [ "${1+set}" ]; then
+ if [ "$USE_XDIALOG" ]; then
+ _DIALOG_BACKTITLE="$DIALOG_BACKTITLE"
+ DIALOG_BACKTITLE="$new_title"
+ else
+ _DIALOG_TITLE="$DIALOG_TITLE"
+ DIALOG_TITLE="$new_title"
+ fi
+ else
+ if [ "$USE_XDIALOG" ]; then
+ echo "$DIALOG_BACKTITLE"
+ else
+ echo "$DIALOG_TITLE"
+ fi
+ fi
+}
+
+# f_dialog_title_restore
+#
+# Restore the previous title set by the last call to f_dialog_title().
+# Restoration is non-recursive and only works to restore the most-recent title.
+#
+f_dialog_title_restore()
+{
+ if [ "$USE_XDIALOG" ]; then
+ DIALOG_BACKTITLE="$_DIALOG_BACKTITLE"
+ else
+ DIALOG_TITLE="$_DIALOG_TITLE"
+ fi
+}
+
+# f_dialog_backtitle [$new_backtitle]
+#
+# Set the backtitle of future dialog(1) ($DIALOG_BACKTITLE) or title of
+# Xdialog(1) ($DIALOG_TITLE) invocations. If no arguments are given or the
+# first argument is NULL, the current backtitle is returned.
+#
+f_dialog_backtitle()
+{
+ local new_backtitle="$1"
+
+ if [ "${1+set}" ]; then
+ if [ "$USE_XDIALOG" ]; then
+ _DIALOG_TITLE="$DIALOG_TITLE"
+ DIALOG_TITLE="$new_backtitle"
+ else
+ _DIALOG_BACKTITLE="$DIALOG_BACKTITLE"
+ DIALOG_BACKTITLE="$new_backtitle"
+ fi
+ else
+ if [ "$USE_XDIALOG" ]; then
+ echo "$DIALOG_TITLE"
+ else
+ echo "$DIALOG_BACKTITLE"
+ fi
+ fi
+}
+
+# f_dialog_backtitle_restore
+#
+# Restore the previous backtitle set by the last call to f_dialog_backtitle().
+# Restoration is non-recursive and only works to restore the most-recent
+# backtitle.
+#
+f_dialog_backtitle_restore()
+{
+ if [ "$USE_XDIALOG" ]; then
+ DIALOG_TITLE="$_DIALOG_TITLE"
+ else
+ DIALOG_BACKTITLE="$_DIALOG_BACKTITLE"
+ fi
+}
+
+############################################################ SIZE FUNCTIONS
+
+# f_dialog_max_size $var_height $var_width
+#
+# Get the maximum height and width for a dialog widget and store the values in
+# $var_height and $var_width (respectively).
+#
+f_dialog_max_size()
+{
+ local funcname=f_dialog_max_size
+ local __var_height="$1" __var_width="$2" __max_size
+ [ "$__var_height" -o "$__var_width" ] || return $FAILURE
+ if [ "$USE_XDIALOG" ]; then
+ __max_size="$XDIALOG_MAXSIZE" # see CONFIGURATION
+ else
+ if __max_size=$( $DIALOG --print-maxsize \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD )
+ then
+ f_dprintf "$funcname: %s --print-maxsize = [%s]" \
+ "$DIALOG" "$__max_size"
+ # usually "MaxSize: 24, 80"
+ __max_size="${__max_size#*: }"
+ f_replaceall "$__max_size" "," "" __max_size
+ else
+ f_eval_catch -dk __max_size $funcname stty \
+ 'stty size' || __max_size=
+ # usually "24 80"
+ fi
+ : ${__max_size:=$DEFAULT_TERMINAL_SIZE}
+ fi
+ if [ "$__var_height" ]; then
+ local __height="${__max_size%%[$IFS]*}"
+ #
+ # If we're not using Xdialog(1), we should assume that $DIALOG
+ # will render --backtitle behind the widget. In such a case, we
+ # should prevent a widget from obscuring the backtitle (unless
+ # $NO_BACKTITLE is set and non-NULL, allowing a trap-door).
+ #
+ if [ ! "$USE_XDIALOG" ] && [ ! "$NO_BACKTITLE" ]; then
+ #
+ # If use_shadow (in ~/.dialogrc) is OFF, we need to
+ # subtract 4, otherwise 5. However, don't check this
+ # every time, rely on an initialization variable set
+ # by f_dialog_init().
+ #
+ local __adjust=5
+ [ "$NO_SHADOW" ] && __adjust=4
+
+ # Don't adjust height if already too small (allowing
+ # obscured backtitle for small values of __height).
+ [ ${__height:-0} -gt 11 ] &&
+ __height=$(( $__height - $__adjust ))
+ fi
+ setvar "$__var_height" "$__height"
+ fi
+ [ "$__var_width" ] && setvar "$__var_width" "${__max_size##*[$IFS]}"
+}
+
+# f_dialog_size_constrain $var_height $var_width [$min_height [$min_width]]
+#
+# Modify $var_height to be no-less-than $min_height (if given; zero otherwise)
+# and no-greater-than terminal height (or screen height if $USE_XDIALOG is
+# set).
+#
+# Also modify $var_width to be no-less-than $XDIALOG_MIN_WIDTH (or
+# $XDIALOG_MIN_WIDTH if $_USE_XDIALOG is set) and no-greater-than terminal
+# or screen width. The use of $[X]DIALOG_MIN_WIDTH can be overridden by
+# passing $min_width.
+#
+# Return status is success unless one of the passed arguments is invalid
+# or all of the $var_* arguments are either NULL or missing.
+#
+f_dialog_size_constrain()
+{
+ local __var_height="$1" __var_width="$2"
+ local __min_height="$3" __min_width="$4"
+ local __retval=$SUCCESS
+
+ # Return failure unless at least one var_* argument is passed
+ [ "$__var_height" -o "$__var_width" ] || return $FAILURE
+
+ #
+ # Print debug warnings if any given (non-NULL) argument are invalid
+ # NOTE: Don't change the name of $__{var,min,}{height,width}
+ #
+ local __height __width
+ local __arg __cp __fname=f_dialog_size_constrain
+ for __arg in height width; do
+ debug= f_getvar __var_$__arg __cp
+ [ "$__cp" ] || continue
+ if ! debug= f_getvar "$__cp" __$__arg; then
+ f_dprintf "%s: var_%s variable \`%s' not set" \
+ $__fname $__arg "$__cp"
+ __retval=$FAILURE
+ elif ! eval f_isinteger \$__$__arg; then
+ f_dprintf "%s: var_%s variable value not a number" \
+ $__fname $__arg
+ __retval=$FAILURE
+ fi
+ done
+ for __arg in height width; do
+ debug= f_getvar __min_$__arg __cp
+ [ "$__cp" ] || continue
+ f_isinteger "$__cp" && continue
+ f_dprintf "%s: min_%s value not a number" $__fname $__arg
+ __retval=$FAILURE
+ setvar __min_$__arg ""
+ done
+
+ # Obtain maximum height and width values
+ # NOTE: Function name appended to prevent __var_{height,width} values
+ # from becoming local (and thus preventing setvar from working).
+ local __max_height_size_constain __max_width_size_constrain
+ f_dialog_max_size \
+ __max_height_size_constrain __max_width_size_constrain
+
+ # Adjust height if desired
+ if [ "$__var_height" ]; then
+ if [ $__height -lt ${__min_height:-0} ]; then
+ setvar "$__var_height" $__min_height
+ elif [ $__height -gt $__max_height_size_constrain ]; then
+ setvar "$__var_height" $__max_height_size_constrain
+ fi
+ fi
+
+ # Adjust width if desired
+ if [ "$__var_width" ]; then
+ if [ "$USE_XDIALOG" ]; then
+ : ${__min_width:=${XDIALOG_MIN_WIDTH:-35}}
+ else
+ : ${__min_width:=${DIALOG_MIN_WIDTH:-24}}
+ fi
+ if [ $__width -lt $__min_width ]; then
+ setvar "$__var_width" $__min_width
+ elif [ $__width -gt $__max_width_size_constrain ]; then
+ setvar "$__var_width" $__max_width_size_constrain
+ fi
+ fi
+
+ if [ "$debug" ]; then
+ # Print final constrained values to debugging
+ [ "$__var_height" ] && f_quietly f_getvar "$__var_height"
+ [ "$__var_width" ] && f_quietly f_getvar "$__var_width"
+ fi
+
+ return $__retval # success if no debug warnings were printed
+}
+
+# f_dialog_menu_constrain $var_height $var_width $var_rows "$prompt" \
+# [$min_height [$min_width [$min_rows]]]
+#
+# Modify $var_height to be no-less-than $min_height (if given; zero otherwise)
+# and no-greater-than terminal height (or screen height if $USE_XDIALOG is
+# set).
+#
+# Also modify $var_width to be no-less-than $XDIALOG_MIN_WIDTH (or
+# $XDIALOG_MIN_WIDTH if $_USE_XDIALOG is set) and no-greater-than terminal
+# or screen width. The use of $[X]DIALOG_MIN_WIDTH can be overridden by
+# passing $min_width.
+#
+# Last, modify $var_rows to be no-less-than $min_rows (if specified; zero
+# otherwise) and no-greater-than (max_height - 8) where max_height is the
+# terminal height (or screen height if $USE_XDIALOG is set). If $prompt is NULL
+# or missing, dialog(1) allows $var_rows to be (max_height - 7), maximizing the
+# number of visible rows.
+#
+# Return status is success unless one of the passed arguments is invalid
+# or all of the $var_* arguments are either NULL or missing.
+#
+f_dialog_menu_constrain()
+{
+ local __var_height="$1" __var_width="$2" __var_rows="$3" __prompt="$4"
+ local __min_height="$5" __min_width="$6" __min_rows="$7"
+
+ # Return failure unless at least one var_* argument is passed
+ [ "$__var_height" -o "$__var_width" -o "$__var_rows" ] ||
+ return $FAILURE
+
+ #
+ # Print debug warnings if any given (non-NULL) argument are invalid
+ # NOTE: Don't change the name of $__{var,min,}{height,width,rows}
+ #
+ local __height_menu_constrain __width_menu_constrain
+ local __rows_menu_constrain
+ local __arg __cp __fname=f_dialog_menu_constrain
+ for __arg in height width rows; do
+ debug= f_getvar __var_$__arg __cp
+ [ "$__cp" ] || continue
+ if ! debug= f_getvar "$__cp" __${__arg}_menu_constrain; then
+ f_dprintf "%s: var_%s variable \`%s' not set" \
+ $__fname $__arg "$__cp"
+ __retval=$FAILURE
+ elif ! eval f_isinteger \$__${__arg}_menu_constrain; then
+ f_dprintf "%s: var_%s variable value not a number" \
+ $__fname $__arg
+ __retval=$FAILURE
+ fi
+ done
+ for __arg in height width rows; do
+ debug= f_getvar __min_$__arg __cp
+ [ "$__cp" ] || continue
+ f_isinteger "$__cp" && continue
+ f_dprintf "%s: min_%s value not a number" $__fname $__arg
+ __retval=$FAILURE
+ setvar __min_$__arg ""
+ done
+
+ # Obtain maximum height and width values
+ # NOTE: Function name appended to prevent __var_{height,width} values
+ # from becoming local (and thus preventing setvar from working).
+ local __max_height_menu_constrain __max_width_menu_constrain
+ f_dialog_max_size \
+ __max_height_menu_constrain __max_width_menu_constrain
+
+ # Adjust height if desired
+ if [ "$__var_height" ]; then
+ if [ $__height_menu_constrain -lt ${__min_height:-0} ]; then
+ setvar "$__var_height" $__min_height
+ elif [ $__height_menu_constrain -gt \
+ $__max_height_menu_constrain ]
+ then
+ setvar "$__var_height" $__max_height_menu_constrain
+ fi
+ fi
+
+ # Adjust width if desired
+ if [ "$__var_width" ]; then
+ if [ "$USE_XDIALOG" ]; then
+ : ${__min_width:=${XDIALOG_MIN_WIDTH:-35}}
+ else
+ : ${__min_width:=${DIALOG_MIN_WIDTH:-24}}
+ fi
+ if [ $__width_menu_constrain -lt $__min_width ]; then
+ setvar "$__var_width" $__min_width
+ elif [ $__width_menu_constrain -gt \
+ $__max_width_menu_constrain ]
+ then
+ setvar "$__var_width" $__max_width_menu_constrain
+ fi
+ fi
+
+ # Adjust rows if desired
+ if [ "$__var_rows" ]; then
+ if [ "$USE_XDIALOG" ]; then
+ : ${__min_rows:=1}
+ else
+ : ${__min_rows:=0}
+ fi
+
+ local __max_rows_menu_constrain=$((
+ $__max_height_menu_constrain - 7
+ ))
+ # If prompt_len is zero (no prompt), bump the max-rows by 1
+ # Default assumption is (if no argument) that there's no prompt
+ [ ${__prompt_len:-0} -gt 0 ] || __max_rows_menu_constrain=$((
+ $__max_rows_menu_constrain + 1
+ ))
+
+ if [ $__rows_menu_constrain -lt $__min_rows ]; then
+ setvar "$__var_rows" $__min_rows
+ elif [ $__rows_menu_constrain -gt $__max_rows_menu_constrain ]
+ then
+ setvar "$__var_rows" $__max_rows_menu_constrain
+ fi
+ fi
+
+ if [ "$debug" ]; then
+ # Print final constrained values to debugging
+ [ "$__var_height" ] && f_quietly f_getvar "$__var_height"
+ [ "$__var_width" ] && f_quietly f_getvar "$__var_width"
+ [ "$__var_rows" ] && f_quietly f_getvar "$__var_rows"
+ fi
+
+ return $__retval # success if no debug warnings were printed
+}
+
+# f_dialog_infobox_size [-n] $var_height $var_width \
+# $title $backtitle $prompt [$hline]
+#
+# Not all versions of dialog(1) perform auto-sizing of the width and height of
+# `--infobox' boxes sensibly.
+#
+# This function helps solve this issue by taking two sets of sequential
+# arguments. The first set of arguments are the variable names to use when
+# storing the calculated height and width. The second set of arguments are the
+# title, backtitle, prompt, and [optionally] hline. The optimal height and
+# width for the described widget (not exceeding the actual terminal height or
+# width) is stored in $var_height and $var_width (respectively).
+#
+# If the first argument is `-n', the calculated sizes ($var_height and
+# $var_width) are not constrained to minimum/maximum values.
+#
+# Newline character sequences (``\n'') in $prompt are expanded as-is done by
+# dialog(1).
+#
+f_dialog_infobox_size()
+{
+ local __constrain=1
+ [ "$1" = "-n" ] && __constrain= && shift 1 # -n
+ local __var_height="$1" __var_width="$2"
+ local __title="$3" __btitle="$4" __prompt="$5" __hline="$6"
+
+ # Return unless at least one size aspect has been requested
+ [ "$__var_height" -o "$__var_width" ] || return $FAILURE
+
+ # Default height/width of zero for auto-sizing
+ local __height=0 __width=0 __n
+
+ # Adjust height if desired
+ if [ "$__var_height" ]; then
+ #
+ # Set height based on number of rows in prompt
+ #
+ __n=$( echo -n "$__prompt" | f_number_of_lines )
+ __n=$(( $__n + 2 ))
+ [ $__n -gt $__height ] && __height=$__n
+
+ #
+ # For Xdialog(1) bump height if backtitle is enabled (displayed
+ # in the X11 window with a separator line between the backtitle
+ # and msg text).
+ #
+ if [ "$USE_XDIALOG" -a "$__btitle" ]; then
+ __n=$( echo "$__btitle" | f_number_of_lines )
+ __height=$(( $__height + $__n + 2 ))
+ fi
+
+ setvar "$__var_height" $__height
+ fi
+
+ # Adjust width if desired
+ if [ "$__var_width" ]; then
+ #
+ # Bump width for long titles
+ #
+ __n=$(( ${#__title} + 4 ))
+ [ $__n -gt $__width ] && __width=$__n
+
+ #
+ # If using Xdialog(1), bump width for long backtitles (which
+ # appear within the window).
+ #
+ if [ "$USE_XDIALOG" ]; then
+ __n=$(( ${#__btitle} + 4 ))
+ [ $__n -gt $__width ] && __width=$__n
+ fi
+
+ #
+ # Bump width for long prompts
+ #
+ __n=$( echo "$__prompt" | f_longest_line_length )
+ __n=$(( $__n + 4 )) # add width for border
+ [ $__n -gt $__width ] && __width=$__n
+
+ #
+ # Bump width for long hlines. Xdialog(1) supports `--hline' but
+ # it's currently not used (so don't do anything here if using
+ # Xdialog(1)).
+ #
+ if [ ! "$USE_XDIALOG" ]; then
+ __n=$(( ${#__hline} + 10 ))
+ [ $__n -gt $__width ] && __width=$__n
+ fi
+
+ # Bump width by 16.6% if using Xdialog(1)
+ [ "$USE_XDIALOG" ] && __width=$(( $__width + $__width / 6 ))
+
+ setvar "$__var_width" $__width
+ fi
+
+ # Constrain values to sensible minimums/maximums unless `-n' was passed
+ # Return success if no-constrain, else return status from constrain
+ [ ! "$__constrain" ] ||
+ f_dialog_size_constrain "$__var_height" "$__var_width"
+}
+
+# f_dialog_buttonbox_size [-n] $var_height $var_width \
+# $title $backtitle $prompt [$hline]
+#
+# Not all versions of dialog(1) perform auto-sizing of the width and height of
+# `--msgbox' and `--yesno' boxes sensibly.
+#
+# This function helps solve this issue by taking two sets of sequential
+# arguments. The first set of arguments are the variable names to use when
+# storing the calculated height and width. The second set of arguments are the
+# title, backtitle, prompt, and [optionally] hline. The optimal height and
+# width for the described widget (not exceeding the actual terminal height or
+# width) is stored in $var_height and $var_width (respectively).
+#
+# If the first argument is `-n', the calculated sizes ($var_height and
+# $var_width) are not constrained to minimum/maximum values.
+#
+# Newline character sequences (``\n'') in $prompt are expanded as-is done by
+# dialog(1).
+#
+f_dialog_buttonbox_size()
+{
+ local __constrain=1
+ [ "$1" = "-n" ] && __constrain= && shift 1 # -n
+ local __var_height="$1" __var_width="$2"
+ local __title="$3" __btitle="$4" __prompt="$5" __hline="$6"
+
+ # Return unless at least one size aspect has been requested
+ [ "$__var_height" -o "$__var_width" ] || return $FAILURE
+
+ # Calculate height/width of infobox (adjusted/constrained below)
+ # NOTE: Function name appended to prevent __var_{height,width} values
+ # from becoming local (and thus preventing setvar from working).
+ local __height_bbox_size __width_bbox_size
+ f_dialog_infobox_size -n \
+ "${__var_height:+__height_bbox_size}" \
+ "${__var_width:+__width_bbox_size}" \
+ "$__title" "$__btitle" "$__prompt" "$__hline"
+
+ # Adjust height if desired
+ if [ "$__var_height" ]; then
+ # Add height to accomodate the buttons
+ __height_bbox_size=$(( $__height_bbox_size + 2 ))
+
+ # Adjust for clipping with Xdialog(1) on Linux/GTK2
+ [ "$USE_XDIALOG" ] &&
+ __height_bbox_size=$(( $__height_bbox_size + 3 ))
+
+ setvar "$__var_height" $__height_bbox_size
+ fi
+
+ # No adjustemnts to width, just pass-thru the infobox width
+ if [ "$__var_width" ]; then
+ setvar "$__var_width" $__width_bbox_size
+ fi
+
+ # Constrain values to sensible minimums/maximums unless `-n' was passed
+ # Return success if no-constrain, else return status from constrain
+ [ ! "$__constrain" ] ||
+ f_dialog_size_constrain "$__var_height" "$__var_width"
+}
+
+# f_dialog_inputbox_size [-n] $var_height $var_width \
+# $title $backtitle $prompt $init [$hline]
+#
+# Not all versions of dialog(1) perform auto-sizing of the width and height of
+# `--inputbox' boxes sensibly.
+#
+# This function helps solve this issue by taking two sets of sequential
+# arguments. The first set of arguments are the variable names to use when
+# storing the calculated height and width. The second set of arguments are the
+# title, backtitle, prompt, and [optionally] hline. The optimal height and
+# width for the described widget (not exceeding the actual terminal height or
+# width) is stored in $var_height and $var_width (respectively).
+#
+# If the first argument is `-n', the calculated sizes ($var_height and
+# $var_width) are not constrained to minimum/maximum values.
+#
+# Newline character sequences (``\n'') in $prompt are expanded as-is done by
+# dialog(1).
+#
+f_dialog_inputbox_size()
+{
+ local __constrain=1
+ [ "$1" = "-n" ] && __constrain= && shift 1 # -n
+ local __var_height="$1" __var_width="$2"
+ local __title="$3" __btitle="$4" __prompt="$5" __init="$6" __hline="$7"
+
+ # Return unless at least one size aspect has been requested
+ [ "$__var_height" -o "$__var_width" ] || return $FAILURE
+
+ # Calculate height/width of buttonbox (adjusted/constrained below)
+ # NOTE: Function name appended to prevent __var_{height,width} values
+ # from becoming local (and thus preventing setvar from working).
+ local __height_ibox_size __width_ibox_size
+ f_dialog_buttonbox_size -n \
+ "${__var_height:+__height_ibox_size}" \
+ "${__var_width:+__width_ibox_size}" \
+ "$__title" "$__btitle" "$__prompt" "$__hline"
+
+ # Adjust height if desired
+ if [ "$__var_height" ]; then
+ # Add height for input box (not needed for Xdialog(1))
+ [ ! "$USE_XDIALOG" ] &&
+ __height_ibox_size=$(( $__height_ibox_size + 3 ))
+
+ setvar "$__var_height" $__height_ibox_size
+ fi
+
+ # Adjust width if desired
+ if [ "$__var_width" ]; then
+ # Bump width for initial text (something neither dialog(1) nor
+ # Xdialog(1) do, but worth it!; add 16.6% if using Xdialog(1))
+ local __n=$(( ${#__init} + 7 ))
+ [ "$USE_XDIALOG" ] && __n=$(( $__n + $__n / 6 ))
+ [ $__n -gt $__width_ibox_size ] && __width_ibox_size=$__n
+
+ setvar "$__var_width" $__width_ibox_size
+ fi
+
+ # Constrain values to sensible minimums/maximums unless `-n' was passed
+ # Return success if no-constrain, else return status from constrain
+ [ ! "$__constrain" ] ||
+ f_dialog_size_constrain "$__var_height" "$__var_width"
+}
+
+# f_xdialog_2inputsbox_size [-n] $var_height $var_width \
+# $title $backtitle $prompt \
+# $label1 $init1 $label2 $init2
+#
+# Xdialog(1) does not perform auto-sizing of the width and height of
+# `--2inputsbox' boxes sensibly.
+#
+# This function helps solve this issue by taking two sets of sequential
+# arguments. The first set of arguments are the variable names to use when
+# storing the calculated height and width. The second set of arguments are the
+# title, backtitle, prompt, label for the first field, initial text for said
+# field, label for the second field, and initial text for said field. The
+# optimal height and width for the described widget (not exceeding the actual
+# terminal height or width) is stored in $var_height and $var_width
+# (respectively).
+#
+# If the first argument is `-n', the calculated sizes ($var_height and
+# $var_width) are not constrained to minimum/maximum values.
+#
+# Newline character sequences (``\n'') in $prompt are expanded as-is done by
+# Xdialog(1).
+#
+f_xdialog_2inputsbox_size()
+{
+ local __constrain=1
+ [ "$1" = "-n" ] && __constrain= && shift 1 # -n
+ local __var_height="$1" __var_width="$2"
+ local __title="$3" __btitle="$4" __prompt="$5"
+ local __label1="$6" __init1="$7" __label2="$8" __init2="$9"
+
+ # Return unless at least one size aspect has been requested
+ [ "$__var_height" -o "$__var_width" ] || return $FAILURE
+
+ # Calculate height/width of inputbox (adjusted/constrained below)
+ # NOTE: Function name appended to prevent __var_{height,width} values
+ # from becoming local (and thus preventing setvar from working).
+ local __height_2ibox_size __width_2ibox_size
+ f_dialog_inputbox_size -n \
+ "${__var_height:+__height_2ibox_size}" \
+ "${__var_width:+__width_2ibox_size}" \
+ "$__title" "$__btitle" "$__prompt" "$__hline" "$__init1"
+
+ # Adjust height if desired
+ if [ "$__var_height" ]; then
+ # Add height for 1st label, 2nd label, and 2nd input box
+ __height_2ibox_size=$(( $__height_2ibox_size + 2 + 2 + 2 ))
+ setvar "$__var_height" $__height_2ibox_size
+ fi
+
+ # Adjust width if desired
+ if [ "$__var_width" ]; then
+ local __n
+
+ # Bump width for first label text (+16.6% since Xdialog(1))
+ __n=$(( ${#__label1} + 7 ))
+ __n=$(( $__n + $__n / 6 ))
+ [ $__n -gt $__width_2ibox_size ] && __width_2ibox_size=$__n
+
+ # Bump width for second label text (+16.6% since Xdialog(1))
+ __n=$(( ${#__label2} + 7 ))
+ __n=$(( $__n + $__n / 6 ))
+ [ $__n -gt $__width_2ibox_size ] && __width_2ibox_size=$__n
+
+ # Bump width for 2nd initial text (something neither dialog(1)
+ # nor Xdialog(1) do, but worth it!; +16.6% since Xdialog(1))
+ __n=$(( ${#__init2} + 7 ))
+ __n=$(( $__n + $__n / 6 ))
+ [ $__n -gt $__width_2ibox_size ] && __width_2ibox_size=$__n
+
+ setvar "$__var_width" $__width_2ibox_size
+ fi
+
+ # Constrain values to sensible minimums/maximums unless `-n' was passed
+ # Return success if no-constrain, else return status from constrain
+ [ ! "$__constrain" ] ||
+ f_dialog_size_constrain "$__var_height" "$__var_width"
+}
+
+# f_dialog_menu_size [-n] $var_height $var_width $var_rows \
+# $title $backtitle $prompt $hline \
+# $tag1 $item1 $tag2 $item2 ...
+#
+# Not all versions of dialog(1) perform auto-sizing of the width and height of
+# `--menu' boxes sensibly.
+#
+# This function helps solve this issue by taking three sets of sequential
+# arguments. The first set of arguments are the variable names to use when
+# storing the calculated height, width, and rows. The second set of arguments
+# are the title, backtitle, prompt, and hline. The [optional] third set of
+# arguments are the menu list itself (comprised of tag/item couplets). The
+# optimal height, width, and rows for the described widget (not exceeding the
+# actual terminal height or width) is stored in $var_height, $var_width, and
+# $var_rows (respectively).
+#
+# If the first argument is `-n', the calculated sizes ($var_height, $var_width,
+# and $var_rows) are not constrained to minimum/maximum values.
+#
+f_dialog_menu_size()
+{
+ local __constrain=1
+ [ "$1" = "-n" ] && __constrain= && shift 1 # -n
+ local __var_height="$1" __var_width="$2" __var_rows="$3"
+ local __title="$4" __btitle="$5" __prompt="$6" __hline="$7"
+ shift 7 # var_height/var_width/var_rows/title/btitle/prompt/hline
+
+ # Return unless at least one size aspect has been requested
+ [ "$__var_height" -o "$__var_width" -o "$__var_rows" ] ||
+ return $FAILURE
+
+ # Calculate height/width of infobox (adjusted/constrained below)
+ # NOTE: Function name appended to prevent __var_{height,width} values
+ # from becoming local (and thus preventing setvar from working).
+ local __height_menu_size __width_menu_size
+ f_dialog_infobox_size -n \
+ "${__var_height:+__height_menu_size}" \
+ "${__var_width:+__width_menu_size}" \
+ "$__title" "$__btitle" "$__prompt" "$__hline"
+
+ #
+ # Always process the menu-item arguments to get the longest tag-length,
+ # longest item-length (both used to bump the width), and the number of
+ # rows (used to bump the height).
+ #
+ local __longest_tag=0 __longest_item=0 __rows=0
+ while [ $# -ge 2 ]; do
+ local __tag="$1" __item="$2"
+ shift 2 # tag/item
+ [ ${#__tag} -gt $__longest_tag ] && __longest_tag=${#__tag}
+ [ ${#__item} -gt $__longest_item ] && __longest_item=${#__item}
+ __rows=$(( $__rows + 1 ))
+ done
+
+ # Adjust rows early (for up-comning height calculation)
+ if [ "$__var_height" -o "$__var_rows" ]; then
+ # Add a row for visual aid if using Xdialog(1)
+ [ "$USE_XDIALOG" ] && __rows=$(( $__rows + 1 ))
+ fi
+
+ # Adjust height if desired
+ if [ "$__var_height" ]; then
+ # Add rows to height
+ if [ "$USE_XDIALOG" ]; then
+ __height_menu_size=$((
+ $__height_menu_size + $__rows + 7 ))
+ else
+ __height_menu_size=$((
+ $__height_menu_size + $__rows + 4 ))
+ fi
+ setvar "$__var_height" $__height_menu_size
+ fi
+
+ # Adjust width if desired
+ if [ "$__var_width" ]; then
+ # The sum total between the longest tag-length and the
+ # longest item-length should be used to bump menu width
+ local __n=$(( $__longest_tag + $__longest_item + 10 ))
+ [ "$USE_XDIALOG" ] && __n=$(( $__n + $__n / 6 )) # plus 16.6%
+ [ $__n -gt $__width_menu_size ] && __width_menu_size=$__n
+
+ setvar "$__var_width" $__width_menu_size
+ fi
+
+ # Store adjusted rows if desired
+ [ "$__var_rows" ] && setvar "$__var_rows" $__rows
+
+ # Constrain height, width, and rows to sensible minimum/maximum values
+ # Return success if no-constrain, else return status from constrain
+ [ ! "$__constrain" ] || f_dialog_menu_constrain \
+ "$__var_height" "$__var_width" "$__var_rows" "$__prompt"
+}
+
+# f_dialog_menu_with_help_size [-n] $var_height $var_width $var_rows \
+# $title $backtitle $prompt $hline \
+# $tag1 $item1 $help1 $tag2 $item2 $help2 ...
+#
+# Not all versions of dialog(1) perform auto-sizing of the width and height of
+# `--menu' boxes sensibly.
+#
+# This function helps solve this issue by taking three sets of sequential
+# arguments. The first set of arguments are the variable names to use when
+# storing the calculated height, width, and rows. The second set of arguments
+# are the title, backtitle, prompt, and hline. The [optional] third set of
+# arguments are the menu list itself (comprised of tag/item/help triplets). The
+# optimal height, width, and rows for the described widget (not exceeding the
+# actual terminal height or width) is stored in $var_height, $var_width, and
+# $var_rows (respectively).
+#
+# If the first argument is `-n', the calculated sizes ($var_height, $var_width,
+# and $var_rows) are not constrained to minimum/maximum values.
+#
+f_dialog_menu_with_help_size()
+{
+ local __constrain=1
+ [ "$1" = "-n" ] && __constrain= && shift 1 # -n
+ local __var_height="$1" __var_width="$2" __var_rows="$3"
+ local __title="$4" __btitle="$5" __prompt="$6" __hline="$7"
+ shift 7 # var_height/var_width/var_rows/title/btitle/prompt/hline
+
+ # Return unless at least one size aspect has been requested
+ [ "$__var_height" -o "$__var_width" -o "$__var_rows" ] ||
+ return $FAILURE
+
+ # Calculate height/width of infobox (adjusted/constrained below)
+ # NOTE: Function name appended to prevent __var_{height,width} values
+ # from becoming local (and thus preventing setvar from working).
+ local __height_menu_with_help_size __width_menu_with_help_size
+ f_dialog_infobox_size -n \
+ "${__var_height:+__height_menu_with_help_size}" \
+ "${__var_width:+__width_menu_with_help_size}" \
+ "$__title" "$__btitle" "$__prompt" "$__hline"
+
+ #
+ # Always process the menu-item arguments to get the longest tag-length,
+ # longest item-length, longest help-length (help-length only considered
+ # if using Xdialog(1), as it places the help string in the widget) --
+ # all used to bump the width -- and the number of rows (used to bump
+ # the height).
+ #
+ local __longest_tag=0 __longest_item=0 __longest_help=0 __rows=0
+ while [ $# -ge 3 ]; do
+ local __tag="$1" __item="$2" __help="$3"
+ shift 3 # tag/item/help
+ [ ${#__tag} -gt $__longest_tag ] && __longest_tag=${#__tag}
+ [ ${#__item} -gt $__longest_item ] && __longest_item=${#__item}
+ [ ${#__help} -gt $__longest_help ] && __longest_help=${#__help}
+ __rows=$(( $__rows + 1 ))
+ done
+
+ # Adjust rows early (for up-coming height calculation)
+ if [ "$__var_height" -o "$__var_rows" ]; then
+ # Add a row for visual aid if using Xdialog(1)
+ [ "$USE_XDIALOG" ] && __rows=$(( $__rows + 1 ))
+ fi
+
+ # Adjust height if desired
+ if [ "$__var_height" ]; then
+ # Add rows to height
+ if [ "$USE_XDIALOG" ]; then
+ __height_menu_with_help_size=$((
+ $__height_menu_with_help_size + $__rows + 8 ))
+ else
+ __height_menu_with_help_size=$((
+ $__height_menu_with_help_size + $__rows + 4 ))
+ fi
+ setvar "$__var_height" $__height_menu_with_help_size
+ fi
+
+ # Adjust width if desired
+ if [ "$__var_width" ]; then
+ # The sum total between the longest tag-length and the
+ # longest item-length should be used to bump menu width
+ local __n=$(( $__longest_tag + $__longest_item + 10 ))
+ [ "$USE_XDIALOG" ] && __n=$(( $__n + $__n / 6 )) # plus 16.6%
+ [ $__n -gt $__width_menu_with_help_size ] &&
+ __width_menu_with_help_size=$__n
+
+ # Update width for help text if using Xdialog(1)
+ if [ "$USE_XDIALOG" ]; then
+ __n=$(( $__longest_help + 10 ))
+ __n=$(( $__n + $__n / 6 )) # plus 16.6%
+ [ $__n -gt $__width_menu_with_help_size ] &&
+ __width_menu_with_help_size=$__n
+ fi
+
+ setvar "$__var_width" $__width_menu_with_help_size
+ fi
+
+ # Store adjusted rows if desired
+ [ "$__var_rows" ] && setvar "$__var_rows" $__rows
+
+ # Constrain height, width, and rows to sensible minimum/maximum values
+ # Return success if no-constrain, else return status from constrain
+ [ ! "$__constrain" ] || f_dialog_menu_constrain \
+ "$__var_height" "$__var_width" "$__var_rows" "$__prompt"
+}
+
+# f_dialog_radiolist_size [-n] $var_height $var_width $var_rows \
+# $title $backtitle $prompt $hline \
+# $tag1 $item1 $status1 $tag2 $item2 $status2 ...
+#
+# Not all versions of dialog(1) perform auto-sizing of the width and height of
+# `--radiolist' boxes sensibly.
+#
+# This function helps solve this issue by taking three sets of sequential
+# arguments. The first set of arguments are the variable names to use when
+# storing the calculated height, width, and rows. The second set of arguments
+# are the title, backtitle, prompt, and hline. The [optional] third set of
+# arguments are the radio list itself (comprised of tag/item/status triplets).
+# The optimal height, width, and rows for the described widget (not exceeding
+# the actual terminal height or width) is stored in $var_height, $var_width,
+# and $var_rows (respectively).
+#
+# If the first argument is `-n', the calculated sizes ($var_height, $var_width,
+# and $var_rows) are not constrained to minimum/maximum values.
+#
+f_dialog_radiolist_size()
+{
+ local __constrain=1
+ [ "$1" = "-n" ] && __constrain= && shift 1 # -n
+ local __var_height="$1" __var_width="$2" __var_rows="$3"
+ local __title="$4" __btitle="$5" __prompt="$6" __hline="$7"
+ shift 7 # var_height/var_width/var_rows/title/btitle/prompt/hline
+
+ # Return unless at least one size aspect has been requested
+ [ "$__var_height" -o "$__var_width" -o "$__var_rows" ] ||
+ return $FAILURE
+
+ # Calculate height/width of infobox (adjusted/constrained below)
+ # NOTE: Function name appended to prevent __var_{height,width} values
+ # from becoming local (and thus preventing setvar from working).
+ local __height_rlist_size __width_rlist_size
+ f_dialog_infobox_size -n \
+ "${__var_height:+__height_rlist_size}" \
+ "${__var_width:+__width_rlist_size}" \
+ "$__title" "$__btitle" "$__prompt" "$__hline"
+
+ #
+ # Always process the menu-item arguments to get the longest tag-length,
+ # longest item-length (both used to bump the width), and the number of
+ # rows (used to bump the height).
+ #
+ local __longest_tag=0 __longest_item=0 __rows_rlist_size=0
+ while [ $# -ge 3 ]; do
+ local __tag="$1" __item="$2"
+ shift 3 # tag/item/status
+ [ ${#__tag} -gt $__longest_tag ] && __longest_tag=${#__tag}
+ [ ${#__item} -gt $__longest_item ] && __longest_item=${#__item}
+ __rows_rlist_size=$(( $__rows_rlist_size + 1 ))
+ done
+
+ # Adjust rows early (for up-coming height calculation)
+ if [ "$__var_height" -o "$__var_rows" ]; then
+ # Add a row for visual aid if using Xdialog(1)
+ [ "$USE_XDIALOG" ] &&
+ __rows_rlist_size=$(( $__rows_rlist_size + 1 ))
+ fi
+
+ # Adjust height if desired
+ if [ "$__var_height" ]; then
+ # Add rows to height
+ if [ "$USE_XDIALOG" ]; then
+ __height_rlist_size=$((
+ $__height_rlist_size + $__rows_rlist_size + 7
+ ))
+ else
+ __height_rlist_size=$((
+ $__height_rlist_size + $__rows_rlist_size + 4
+ ))
+ fi
+ setvar "$__var_height" $__height_rlist_size
+ fi
+
+ # Adjust width if desired
+ if [ "$__var_width" ]; then
+ # Sum total between longest tag-length, longest item-length,
+ # and radio-button width should be used to bump menu width
+ local __n=$(( $__longest_tag + $__longest_item + 13 ))
+ [ "$USE_XDIALOG" ] && __n=$(( $__n + $__n / 6 )) # plus 16.6%
+ [ $__n -gt $__width_rlist_size ] && __width_rlist_size=$__n
+
+ setvar "$__var_width" $__width_rlist_size
+ fi
+
+ # Store adjusted rows if desired
+ [ "$__var_rows" ] && setvar "$__var_rows" $__rows_rlist_size
+
+ # Constrain height, width, and rows to sensible minimum/maximum values
+ # Return success if no-constrain, else return status from constrain
+ [ ! "$__constrain" ] || f_dialog_menu_constrain \
+ "$__var_height" "$__var_width" "$__var_rows" "$__prompt"
+}
+
+# f_dialog_checklist_size [-n] $var_height $var_width $var_rows \
+# $title $backtitle $prompt $hline \
+# $tag1 $item1 $status1 $tag2 $item2 $status2 ...
+#
+# Not all versions of dialog(1) perform auto-sizing of the width and height of
+# `--checklist' boxes sensibly.
+#
+# This function helps solve this issue by taking three sets of sequential
+# arguments. The first set of arguments are the variable names to use when
+# storing the calculated height, width, and rows. The second set of arguments
+# are the title, backtitle, prompt, and hline. The [optional] third set of
+# arguments are the check list itself (comprised of tag/item/status triplets).
+# The optimal height, width, and rows for the described widget (not exceeding
+# the actual terminal height or width) is stored in $var_height, $var_width,
+# and $var_rows (respectively).
+#
+# If the first argument is `-n', the calculated sizes ($var_height, $var_width,
+# and $var_rows) are not constrained to minimum/maximum values.
+#
+f_dialog_checklist_size()
+{
+ f_dialog_radiolist_size "$@"
+}
+
+# f_dialog_radiolist_with_help_size [-n] $var_height $var_width $var_rows \
+# $title $backtitle $prompt $hline \
+# $tag1 $item1 $status1 $help1 \
+# $tag2 $item2 $status2 $help2 ...
+#
+# Not all versions of dialog(1) perform auto-sizing of the width and height of
+# `--radiolist' boxes sensibly.
+#
+# This function helps solve this issue by taking three sets of sequential
+# arguments. The first set of arguments are the variable names to use when
+# storing the calculated height, width, and rows. The second set of arguments
+# are the title, backtitle, prompt, and hline. The [optional] third set of
+# arguments are the radio list itself (comprised of tag/item/status/help
+# quadruplets). The optimal height, width, and rows for the described widget
+# (not exceeding the actual terminal height or width) is stored in $var_height,
+# $var_width, and $var_rows (respectively).
+#
+# If the first argument is `-n', the calculated sizes ($var_height, $var_width,
+# and $var_rows) are not constrained to minimum/maximum values.
+#
+f_dialog_radiolist_with_help_size()
+{
+ local __constrain=1
+ [ "$1" = "-n" ] && __constrain= && shift 1 # -n
+ local __var_height="$1" __var_width="$2" __var_rows="$3"
+ local __title="$4" __btitle="$5" __prompt="$6" __hline="$7"
+ shift 7 # var_height/var_width/var_rows/title/btitle/prompt/hline
+
+ # Return unless at least one size aspect has been requested
+ [ "$__var_height" -o "$__var_width" -o "$__var_rows" ] ||
+ return $FAILURE
+
+ # Calculate height/width of infobox (adjusted/constrained below)
+ # NOTE: Function name appended to prevent __var_{height,width} values
+ # from becoming local (and thus preventing setvar from working).
+ local __height_rlist_with_help_size __width_rlist_with_help_size
+ f_dialog_infobox_size -n \
+ "${__var_height:+__height_rlist_with_help_size}" \
+ "${__var_width:+__width_rlist_with_help_size}" \
+ "$__title" "$__btitle" "$__prompt" "$__hline"
+
+ #
+ # Always process the menu-item arguments to get the longest tag-length,
+ # longest item-length, longest help-length (help-length only considered
+ # if using Xdialog(1), as it places the help string in the widget) --
+ # all used to bump the width -- and the number of rows (used to bump
+ # the height).
+ #
+ local __longest_tag=0 __longest_item=0 __longest_help=0
+ local __rows_rlist_with_help_size=0
+ while [ $# -ge 4 ]; do
+ local __tag="$1" __item="$2" __status="$3" __help="$4"
+ shift 4 # tag/item/status/help
+ [ ${#__tag} -gt $__longest_tag ] && __longest_tag=${#__tag}
+ [ ${#__item} -gt $__longest_item ] && __longest_item=${#__item}
+ [ ${#__help} -gt $__longest_help ] && __longest_help=${#__help}
+ __rows_rlist_with_help_size=$((
+ $__rows_rlist_with_help_size + 1
+ ))
+ done
+
+ # Adjust rows early (for up-coming height calculation)
+ if [ "$__var_height" -o "$__var_rows" ]; then
+ # Add a row for visual aid if using Xdialog(1)
+ [ "$USE_XDIALOG" ] &&
+ __rows_rlist_with_help_size=$((
+ $__rows_rlist_with_help_size + 1
+ ))
+ fi
+
+ # Adjust height if desired
+ if [ "$__var_height" ]; then
+ # Add rows to height
+ if [ "$USE_XDIALOG" ]; then
+ __height_rlist_with_help_size=$((
+ $__height_rlist_with_help_size +
+ $__rows_rlist_with_help_size + 7
+ ))
+ else
+ __height_rlist_with_help_size=$((
+ $__height_rlist_with_help_size +
+ $__rows_rlist_with_help_size + 4
+ ))
+ fi
+ setvar "$__var_height" $__height
+ fi
+
+ # Adjust width if desired
+ if [ "$__var_width" ]; then
+ # Sum total between longest tag-length, longest item-length,
+ # and radio-button width should be used to bump menu width
+ local __n=$(( $__longest_tag + $__longest_item + 13 ))
+ [ "$USE_XDIALOG" ] && __n=$(( $__n + $__n / 6 )) # plus 16.6%
+ [ $__n -gt $__width_rlist_with_help_size ] &&
+ __width_rlist_with_help_size=$__n
+
+ # Update width for help text if using Xdialog(1)
+ if [ "$USE_XDIALOG" ]; then
+ __n=$(( $__longest_help + 10 ))
+ __n=$(( $__n + $__n / 6 )) # plus 16.6%
+ [ $__n -gt $__width_rlist_with_help_size ] &&
+ __width_rlist_with_help_size=$__n
+ fi
+
+ setvar "$__var_width" $__width_rlist_with_help_size
+ fi
+
+ # Store adjusted rows if desired
+ [ "$__var_rows" ] && setvar "$__var_rows" $__rows_rlist_with_help_size
+
+ # Constrain height, width, and rows to sensible minimum/maximum values
+ # Return success if no-constrain, else return status from constrain
+ [ ! "$__constrain" ] || f_dialog_menu_constrain \
+ "$__var_height" "$__var_width" "$__var_rows" "$__prompt"
+}
+
+# f_dialog_checklist_with_help_size [-n] $var_height $var_width $var_rows \
+# $title $backtitle $prompt $hline \
+# $tag1 $item1 $status1 $help1 \
+# $tag2 $item2 $status2 $help2 ...
+#
+# Not all versions of dialog(1) perform auto-sizing of the width and height of
+# `--checklist' boxes sensibly.
+#
+# This function helps solve this issue by taking three sets of sequential
+# arguments. The first set of arguments are the variable names to use when
+# storing the calculated height, width, and rows. The second set of arguments
+# are the title, backtitle, prompt, and hline. The [optional] third set of
+# arguments are the check list itself (comprised of tag/item/status/help
+# quadruplets). The optimal height, width, and rows for the described widget
+# (not exceeding the actual terminal height or width) is stored in $var_height,
+# $var_width, and $var_rows (respectively).
+#
+# If the first argument is `-n', the calculated sizes ($var_height, $var_width,
+# and $var_rows) are not constrained to minimum/maximum values.
+#
+f_dialog_checklist_with_help_size()
+{
+ f_dialog_radiolist_with_help_size "$@"
+}
+
+# f_dialog_calendar_size [-n] $var_height $var_width \
+# $title $backtitle $prompt [$hline]
+#
+# Not all versions of dialog(1) perform auto-sizing of the width and height of
+# `--calendar' boxes sensibly.
+#
+# This function helps solve this issue by taking two sets of sequential
+# arguments. The first set of arguments are the variable names to use when
+# storing the calculated height and width. The second set of arguments are the
+# title, backtitle, prompt, and [optionally] hline. The optimal height and
+# width for the described widget (not exceeding the actual terminal height or
+# width) is stored in $var_height and $var_width (respectively).
+#
+# If the first argument is `-n', the calculated sizes ($var_height and
+# $var_width) are not constrained to minimum/maximum values.
+#
+# Newline character sequences (``\n'') in $prompt are expanded as-is done by
+# dialog(1).
+#
+f_dialog_calendar_size()
+{
+ local __constrain=1
+ [ "$1" = "-n" ] && __constrain= && shift 1 # -n
+ local __var_height="$1" __var_width="$2"
+ local __title="$3" __btitle="$4" __prompt="$5" __hline="$6"
+
+ # Return unless at least one size aspect has been requested
+ [ "$__var_height" -o "$__var_width" ] || return $FAILURE
+
+ #
+ # Obtain/Adjust minimum and maximum thresholds
+ # NOTE: Function name appended to prevent __var_{height,width} values
+ # from becoming local (and thus preventing setvar from working).
+ #
+ local __max_height_cal_size __max_width_cal_size
+ f_dialog_max_size __max_height_cal_size __max_width_cal_size
+ __max_width_cal_size=$(( $__max_width_cal_size - 2 ))
+ # the calendar box will refuse to display if too wide
+ local __min_width
+ if [ "$USE_XDIALOG" ]; then
+ __min_width=55
+ else
+ __min_width=40
+ __max_height_cal_size=$((
+ $__max_height_cal_size - $DIALOG_CALENDAR_HEIGHT ))
+ # When using dialog(1), we can't predict whether the user has
+ # disabled shadow's in their `$HOME/.dialogrc' file, so we'll
+ # subtract one for the potential shadow around the widget
+ __max_height_cal_size=$(( $__max_height_cal_size - 1 ))
+ fi
+
+ # Calculate height if desired
+ if [ "$__var_height" ]; then
+ local __height
+ __height=$( echo "$__prompt" | f_number_of_lines )
+
+ if [ "$USE_XDIALOG" ]; then
+ # Add height to accomodate for embedded calendar widget
+ __height=$(( $__height + $DIALOG_CALENDAR_HEIGHT - 1 ))
+
+ # Also, bump height if backtitle is enabled
+ if [ "$__btitle" ]; then
+ local __n
+ __n=$( echo "$__btitle" | f_number_of_lines )
+ __height=$(( $__height + $__n + 2 ))
+ fi
+ else
+ [ "$__prompt" ] && __height=$(( $__height + 1 ))
+ fi
+
+ # Enforce maximum height, unless `-n' was passed
+ [ "$__constrain" -a $__height -gt $__max_height_cal_size ] &&
+ __height=$__max_height_cal_size
+
+ setvar "$__var_height" $__height
+ fi
+
+ # Calculate width if desired
+ if [ "$__var_width" ]; then
+ # NOTE: Function name appended to prevent __var_{height,width}
+ # values from becoming local (and thus preventing setvar
+ # from working).
+ local __width_cal_size
+ f_dialog_infobox_size -n "" __width_cal_size \
+ "$__title" "$__btitle" "$__prompt" "$__hline"
+
+ # Enforce minimum/maximum width, unless `-n' was passed
+ if [ "$__constrain" ]; then
+ if [ $__width_cal_size -lt $__min_width ]; then
+ __width_cal_size=$__min_width
+ elif [ $__width_cal_size -gt $__max_width_cal_size ]
+ then
+ __width_cal_size=$__max_width_size
+ fi
+ fi
+
+ setvar "$__var_width" $__width_cal_size
+ fi
+
+ return $SUCCESS
+}
+
+# f_dialog_timebox_size [-n] $var_height $var_width \
+# $title $backtitle $prompt [$hline]
+#
+# Not all versions of dialog(1) perform auto-sizing of the width and height of
+# `--timebox' boxes sensibly.
+#
+# This function helps solve this issue by taking two sets of sequential
+# arguments. The first set of arguments are the variable names to use when
+# storing the calculated height and width. The second set of arguments are the
+# title, backtitle, prompt, and [optionally] hline. The optional height and
+# width for the described widget (not exceeding the actual terminal height or
+# width) is stored in $var_height and $var_width (respectively).
+#
+# If the first argument is `-n', the calculated sizes ($var_height and
+# $var_width) are not constrained to minimum/maximum values.
+#
+# Newline character sequences (``\n'') in $prompt are expanded as-is done by
+# dialog(1).
+#
+f_dialog_timebox_size()
+{
+ local __constrain=1
+ [ "$1" = "-n" ] && __constrain= && shift 1 # -n
+ local __var_height="$1" __var_width="$2"
+ local __title="$3" __btitle="$4" __prompt="$5" __hline="$6"
+
+ # Return unless at least one size aspect has been requested
+ [ "$__var_height" -o "$__var_width" ] || return $FAILURE
+
+ #
+ # Obtain/Adjust minimum and maximum thresholds
+ # NOTE: Function name appended to prevent __var_{height,width} values
+ # from becoming local (and thus preventing setvar from working).
+ #
+ local __max_height_tbox_size __max_width_tbox_size
+ f_dialog_max_size __max_height_tbox_size __max_width_tbox_size
+ __max_width_tbox_size=$(( $__max_width_tbox_size - 2 ))
+ # the timebox widget refuses to display if too wide
+ local __min_width
+ if [ "$USE_XDIALOG" ]; then
+ __min_width=40
+ else
+ __min_width=20
+ __max_height_tbox_size=$(( \
+ $__max_height_tbox_size - $DIALOG_TIMEBOX_HEIGHT ))
+ # When using dialog(1), we can't predict whether the user has
+ # disabled shadow's in their `$HOME/.dialogrc' file, so we'll
+ # subtract one for the potential shadow around the widget
+ __max_height_tbox_size=$(( $__max_height_tbox_size - 1 ))
+ fi
+
+ # Calculate height if desired
+ if [ "$__var_height" -a "$USE_XDIALOG" ]; then
+ # When using Xdialog(1), the height seems to have
+ # no effect. All values provide the same results.
+ setvar "$__var_height" 0 # autosize
+ elif [ "$__var_height" ]; then
+ local __height
+ __height=$( echo "$__prompt" | f_number_of_lines )
+ __height=$(( $__height ${__prompt:++1} + 1 ))
+
+ # Enforce maximum height, unless `-n' was passed
+ [ "$__constrain" -a $__height -gt $__max_height_tbox_size ] &&
+ __height=$__max_height_tbox_size
+
+ setvar "$__var_height" $__height
+ fi
+
+ # Calculate width if desired
+ if [ "$__var_width" ]; then
+ # NOTE: Function name appended to prevent __var_{height,width}
+ # values from becoming local (and thus preventing setvar
+ # from working).
+ local __width_tbox_size
+ f_dialog_infobox_size -n "" __width_tbox_size \
+ "$__title" "$__btitle" "$__prompt" "$__hline"
+
+ # Enforce the minimum width for displaying the timebox
+ if [ "$__constrain" ]; then
+ if [ $__width_tbox_size -lt $__min_width ]; then
+ __width_tbox_size=$__min_width
+ elif [ $__width_tbox_size -ge $__max_width_tbox_size ]
+ then
+ __width_tbox_size=$__max_width_tbox_size
+ fi
+ fi
+
+ setvar "$__var_width" $__width_tbox_size
+ fi
+
+ return $SUCCESS
+}
+
+############################################################ CLEAR FUNCTIONS
+
+# f_dialog_clear
+#
+# Clears any/all previous dialog(1) displays.
+#
+f_dialog_clear()
+{
+ $DIALOG --clear
+}
+
+############################################################ INFO FUNCTIONS
+
+# f_dialog_info $info_text ...
+#
+# Throw up a dialog(1) infobox. The infobox remains until another dialog is
+# displayed or `dialog --clear' (or f_dialog_clear) is called.
+#
+f_dialog_info()
+{
+ local info_text="$*" height width
+ f_dialog_infobox_size height width \
+ "$DIALOG_TITLE" "$DIALOG_BACKTITLE" "$info_text"
+ $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ ${USE_XDIALOG:+--ignore-eof} \
+ ${USE_XDIALOG:+--no-buttons} \
+ --infobox "$info_text" $height $width
+}
+
+# f_xdialog_info $info_text ...
+#
+# Throw up an Xdialog(1) infobox and do not dismiss it until stdin produces
+# EOF. This implies that you must execute this either as an rvalue to a pipe,
+# lvalue to indirection or in a sub-shell that provides data on stdin.
+#
+# To open an Xdialog(1) infobox that does not disappear until expeclitly dis-
+# missed, use the following:
+#
+# f_xdialog_info "$info_text" < /dev/tty &
+# pid=$!
+# # Perform some lengthy actions
+# kill $pid
+#
+# NB: Check $USE_XDIALOG if you need to support both dialog(1) and Xdialog(1).
+#
+f_xdialog_info()
+{
+ local info_text="$*" height width
+ f_dialog_infobox_size height width \
+ "$DIALOG_TITLE" "$DIALOG_BACKTITLE" "$info_text"
+ exec $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --no-close --no-buttons \
+ --infobox "$info_text" $height $width \
+ -1 # timeout of -1 means abort when EOF on stdin
+}
+
+############################################################ PAUSE FUNCTIONS
+
+# f_dialog_pause $msg_text $duration [$hline]
+#
+# Display a message in a widget with a progress bar that runs backward for
+# $duration seconds.
+#
+f_dialog_pause()
+{
+ local pause_text="$1" duration="$2" hline="$3" height width
+ f_isinteger "$duration" || return $FAILURE
+ f_dialog_buttonbox_size height width \
+ "$DIALOG_TITLE" "$DIALOG_BACKTITLE" "$pause_text" "$hline"
+ if [ "$USE_XDIALOG" ]; then
+ $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --ok-label "$msg_skip" \
+ --cancel-label "$msg_cancel" \
+ ${noCancel:+--no-cancel} \
+ --timeout "$duration" \
+ --yesno "$pause_text" \
+ $height $width
+ else
+ [ $duration -gt 0 ] && duration=$(( $duration - 1 ))
+ [ $duration -gt 1 ] && duration=$(( $duration - 1 ))
+ height=$(( $height + 3 )) # Add height for progress bar
+ $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --hline "$hline" \
+ --ok-label "$msg_skip" \
+ --cancel-label "$msg_cancel" \
+ ${noCancel:+--no-cancel} \
+ --pause "$pause_text" \
+ $height $width "$duration"
+ fi
+}
+
+# f_dialog_pause_no_cancel $msg_text $duration [$hline]
+#
+# Display a message in a widget with a progress bar that runs backward for
+# $duration seconds. No cancel button is provided. Always returns success.
+#
+f_dialog_pause_no_cancel()
+{
+ noCancel=1 f_dialog_pause "$@"
+ return $SUCCESS
+}
+
+############################################################ MSGBOX FUNCTIONS
+
+# f_dialog_msgbox $msg_text [$hline]
+#
+# Throw up a dialog(1) msgbox. The msgbox remains until the user presses ENTER
+# or ESC, acknowledging the modal dialog.
+#
+# If the user presses ENTER, the exit status is zero (success), otherwise if
+# the user presses ESC the exit status is 255.
+#
+f_dialog_msgbox()
+{
+ local msg_text="$1" hline="$2" height width
+ f_dialog_buttonbox_size height width \
+ "$DIALOG_TITLE" "$DIALOG_BACKTITLE" "$msg_text" "$hline"
+ $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --hline "$hline" \
+ --ok-label "$msg_ok" \
+ --msgbox "$msg_text" $height $width
+}
+
+############################################################ TEXTBOX FUNCTIONS
+
+# f_dialog_textbox $file
+#
+# Display the contents of $file (or an error if $file does not exist, etc.) in
+# a dialog(1) textbox (which has a scrollable region for the text). The textbox
+# remains until the user presses ENTER or ESC, acknowledging the modal dialog.
+#
+# If the user presses ENTER, the exit status is zero (success), otherwise if
+# the user presses ESC the exit status is 255.
+#
+f_dialog_textbox()
+{
+ local file="$1"
+ local contents height width retval
+
+ contents=$( cat "$file" 2>&1 )
+ retval=$?
+
+ f_dialog_buttonbox_size height width \
+ "$DIALOG_TITLE" "$DIALOG_BACKTITLE" "$contents"
+
+ if [ $retval -eq $SUCCESS ]; then
+ $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --exit-label "$msg_ok" \
+ --no-cancel \
+ --textbox "$file" $height $width
+ else
+ $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --ok-label "$msg_ok" \
+ --msgbox "$contents" $height $width
+ fi
+}
+
+############################################################ YESNO FUNCTIONS
+
+# f_dialog_yesno $msg_text [$hline]
+#
+# Display a dialog(1) Yes/No prompt to allow the user to make some decision.
+# The yesno prompt remains until the user presses ENTER or ESC, acknowledging
+# the modal dialog.
+#
+# If the user chooses YES the exit status is zero, or chooses NO the exit
+# status is one, or presses ESC the exit status is 255.
+#
+f_dialog_yesno()
+{
+ local msg_text="$1" height width
+ local hline="${2-$hline_arrows_tab_enter}"
+
+ f_interactive || return 0 # If non-interactive, return YES all the time
+
+ f_dialog_buttonbox_size height width \
+ "$DIALOG_TITLE" "$DIALOG_BACKTITLE" "$msg_text" "$hline"
+
+ if [ "$USE_XDIALOG" ]; then
+ $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --hline "$hline" \
+ --ok-label "$msg_yes" \
+ --cancel-label "$msg_no" \
+ --yesno "$msg_text" $height $width
+ else
+ $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --hline "$hline" \
+ --yes-label "$msg_yes" \
+ --no-label "$msg_no" \
+ --yesno "$msg_text" $height $width
+ fi
+}
+
+# f_dialog_noyes $msg_text [$hline]
+#
+# Display a dialog(1) No/Yes prompt to allow the user to make some decision.
+# The noyes prompt remains until the user presses ENTER or ESC, acknowledging
+# the modal dialog.
+#
+# If the user chooses YES the exit status is zero, or chooses NO the exit
+# status is one, or presses ESC the exit status is 255.
+#
+# NOTE: This is just like the f_dialog_yesno function except "No" is default.
+#
+f_dialog_noyes()
+{
+ local msg_text="$1" height width
+ local hline="${2-$hline_arrows_tab_enter}"
+
+ f_interactive || return 1 # If non-interactive, return NO all the time
+
+ f_dialog_buttonbox_size height width \
+ "$DIALOG_TITLE" "$DIALOG_BACKTITLE" "$msg_text" "$hline"
+
+ if [ "$USE_XDIALOG" ]; then
+ $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --hline "$hline" \
+ --default-no \
+ --ok-label "$msg_yes" \
+ --cancel-label "$msg_no" \
+ --yesno "$msg_text" $height $width
+ else
+ $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --hline "$hline" \
+ --defaultno \
+ --yes-label "$msg_yes" \
+ --no-label "$msg_no" \
+ --yesno "$msg_text" $height $width
+ fi
+}
+
+############################################################ INPUT FUNCTIONS
+
+# f_dialog_inputstr_store [-s] $text
+#
+# Store some text from a dialog(1) inputbox to be retrieved later by
+# f_dialog_inputstr_fetch(). If the first argument is `-s', the text is
+# sanitized before being stored.
+#
+f_dialog_inputstr_store()
+{
+ local sanitize=
+ [ "$1" = "-s" ] && sanitize=1 && shift 1 # -s
+ local text="$1"
+
+ # Sanitize the line before storing it if desired
+ [ "$sanitize" ] && f_dialog_line_sanitize text
+
+ setvar DIALOG_INPUTBOX_$$ "$text"
+}
+
+# f_dialog_inputstr_fetch [$var_to_set]
+#
+# Obtain the inputstr entered by the user from the most recently displayed
+# dialog(1) inputbox (previously stored with f_dialog_inputstr_store() above).
+# If $var_to_set is NULL or missing, output is printed to stdout (which is less
+# recommended due to performance degradation; in a loop for example).
+#
+f_dialog_inputstr_fetch()
+{
+ local __var_to_set="$1" __cp
+
+ debug= f_getvar DIALOG_INPUTBOX_$$ "${__var_to_set:-__cp}" # get data
+ setvar DIALOG_INPUTBOX_$$ "" # scrub memory in case data was sensitive
+
+ # Return the line on standard-out if desired
+ [ "$__var_to_set" ] || echo "$__cp"
+
+ return $SUCCESS
+}
+
+# f_dialog_input $var_to_set $prompt [$init [$hline]]
+#
+# Prompt the user with a dialog(1) inputbox to enter some value. The inputbox
+# remains until the the user presses ENTER or ESC, or otherwise ends the
+# editing session (by selecting `Cancel' for example).
+#
+# If the user presses ENTER, the exit status is zero (success), otherwise if
+# the user presses ESC the exit status is 255, or if the user chose Cancel, the
+# exit status is instead 1.
+#
+# NOTE: The hline should correspond to the type of data you want from the user.
+# NOTE: Should not be used to edit multiline values.
+#
+f_dialog_input()
+{
+ local __var_to_set="$1" __prompt="$2" __init="$3" __hline="$4"
+
+ # NOTE: Function name appended to prevent __var_{height,width} values
+ # from becoming local (and thus preventing setvar from working).
+ local __height_input __width_input
+ f_dialog_inputbox_size __height_input __width_input \
+ "$DIALOG_TITLE" "$DIALOG_BACKTITLE" \
+ "$__prompt" "$__init" "$__hline"
+
+ local __opterm="--"
+ [ "$USE_XDIALOG" ] && __opterm=
+
+ local __dialog_input
+ __dialog_input=$(
+ $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --hline "$__hline" \
+ --ok-label "$msg_ok" \
+ --cancel-label "$msg_cancel" \
+ --inputbox "$__prompt" \
+ $__height_input $__width_input \
+ $__opterm "$__init" \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local __retval=$?
+
+ # Remove warnings and leading/trailing whitespace from user input
+ f_dialog_line_sanitize __dialog_input
+
+ setvar "$__var_to_set" "$__dialog_input"
+ return $__retval
+}
+
+############################################################ MENU FUNCTIONS
+
+# f_dialog_menutag_store [-s] $text
+#
+# Store some text from a dialog(1) menu to be retrieved later by
+# f_dialog_menutag_fetch(). If the first argument is `-s', the text is
+# sanitized before being stored.
+#
+f_dialog_menutag_store()
+{
+ local sanitize=
+ [ "$1" = "-s" ] && sanitize=1 && shift 1 # -s
+ local text="$1"
+
+ # Sanitize the menutag before storing it if desired
+ [ "$sanitize" ] && f_dialog_data_sanitize text
+
+ setvar DIALOG_MENU_$$ "$text"
+}
+
+# f_dialog_menutag_fetch [$var_to_set]
+#
+# Obtain the menutag chosen by the user from the most recently displayed
+# dialog(1) menu (previously stored with f_dialog_menutag_store() above). If
+# $var_to_set is NULL or missing, output is printed to stdout (which is less
+# recommended due to performance degradation; in a loop for example).
+#
+f_dialog_menutag_fetch()
+{
+ local __var_to_set="$1" __cp
+
+ debug= f_getvar DIALOG_MENU_$$ "${__var_to_set:-__cp}" # get the data
+ setvar DIALOG_MENU_$$ "" # scrub memory in case data was sensitive
+
+ # Return the data on standard-out if desired
+ [ "$__var_to_set" ] || echo "$__cp"
+
+ return $SUCCESS
+}
+
+# f_dialog_menuitem_store [-s] $text
+#
+# Store the item from a dialog(1) menu (see f_dialog_menutag2item()) to be
+# retrieved later by f_dialog_menuitem_fetch(). If the first argument is `-s',
+# the text is sanitized before being stored.
+#
+f_dialog_menuitem_store()
+{
+ local sanitize=
+ [ "$1" = "-s" ] && sanitize=1 && shift 1 # -s
+ local text="$1"
+
+ # Sanitize the menuitem before storing it if desired
+ [ "$sanitize" ] && f_dialog_data_sanitize text
+
+ setvar DIALOG_MENUITEM_$$ "$text"
+}
+
+# f_dialog_menuitem_fetch [$var_to_set]
+#
+# Obtain the menuitem chosen by the user from the most recently displayed
+# dialog(1) menu (previously stored with f_dialog_menuitem_store() above). If
+# $var_to_set is NULL or missing, output is printed to stdout (which is less
+# recommended due to performance degradation; in a loop for example).
+#
+f_dialog_menuitem_fetch()
+{
+ local __var_to_set="$1" __cp
+
+ debug= f_getvar DIALOG_MENUITEM_$$ "${__var_to_set:-__cp}" # get data
+ setvar DIALOG_MENUITEM_$$ "" # scrub memory in case data was sensitive
+
+ # Return the data on standard-out if desired
+ [ "$__var_to_set" ] || echo "$__cp"
+
+ return $SUCCESS
+}
+
+# f_dialog_default_store [-s] $text
+#
+# Store some text to be used later as the --default-item argument to dialog(1)
+# (or Xdialog(1)) for --menu, --checklist, and --radiolist widgets. Retrieve
+# the text later with f_dialog_menutag_fetch(). If the first argument is `-s',
+# the text is sanitized before being stored.
+#
+f_dialog_default_store()
+{
+ local sanitize=
+ [ "$1" = "-s" ] && sanitize=1 && shift 1 # -s
+ local text="$1"
+
+ # Sanitize the defaulitem before storing it if desired
+ [ "$sanitize" ] && f_dialog_data_sanitize text
+
+ setvar DEFAULTITEM_$$ "$text"
+}
+
+# f_dialog_default_fetch [$var_to_set]
+#
+# Obtain text to be used with the --default-item argument of dialog(1) (or
+# Xdialog(1)) (previously stored with f_dialog_default_store() above). If
+# $var_to_set is NULL or missing, output is printed to stdout (which is less
+# recommended due to performance degradation; in a loop for example).
+#
+f_dialog_default_fetch()
+{
+ local __var_to_set="$1" __cp
+
+ debug= f_getvar DEFAULTITEM_$$ "${__var_to_set:-__cp}" # get the data
+ setvar DEFAULTITEM_$$ "" # scrub memory in case data was sensitive
+
+ # Return the data on standard-out if desired
+ [ "$__var_to_set" ] || echo "$__cp"
+
+ return $SUCCESS
+}
+
+# f_dialog_menutag2item $tag_chosen $tag1 $item1 $tag2 $item2 ...
+#
+# To use the `--menu' option of dialog(1) you must pass an ordered list of
+# tag/item pairs on the command-line. When the user selects a menu option the
+# tag for that item is printed to stderr.
+#
+# This function allows you to dereference the tag chosen by the user back into
+# the item associated with said tag.
+#
+# Pass the tag chosen by the user as the first argument, followed by the
+# ordered list of tag/item pairs (HINT: use the same tag/item list as was
+# passed to dialog(1) for consistency).
+#
+# If the tag cannot be found, NULL is returned.
+#
+f_dialog_menutag2item()
+{
+ local tag="$1" tagn item
+ shift 1 # tag
+
+ while [ $# -gt 0 ]; do
+ tagn="$1"
+ item="$2"
+ shift 2 # tagn/item
+
+ if [ "$tag" = "$tagn" ]; then
+ echo "$item"
+ return $SUCCESS
+ fi
+ done
+ return $FAILURE
+}
+
+# f_dialog_menutag2item_with_help $tag_chosen $tag1 $item1 $help1 \
+# $tag2 $item2 $help2 ...
+#
+# To use the `--menu' option of dialog(1) with the `--item-help' option, you
+# must pass an ordered list of tag/item/help triplets on the command-line. When
+# the user selects a menu option the tag for that item is printed to stderr.
+#
+# This function allows you to dereference the tag chosen by the user back into
+# the item associated with said tag (help is discarded/ignored).
+#
+# Pass the tag chosen by the user as the first argument, followed by the
+# ordered list of tag/item/help triplets (HINT: use the same tag/item/help list
+# as was passed to dialog(1) for consistency).
+#
+# If the tag cannot be found, NULL is returned.
+#
+f_dialog_menutag2item_with_help()
+{
+ local tag="$1" tagn item
+ shift 1 # tag
+
+ while [ $# -gt 0 ]; do
+ tagn="$1"
+ item="$2"
+ shift 3 # tagn/item/help
+
+ if [ "$tag" = "$tagn" ]; then
+ echo "$item"
+ return $SUCCESS
+ fi
+ done
+ return $FAILURE
+}
+
+# f_dialog_menutag2index $tag_chosen $tag1 $item1 $tag2 $item2 ...
+#
+# To use the `--menu' option of dialog(1) you must pass an ordered list of
+# tag/item pairs on the command-line. When the user selects a menu option the
+# tag for that item is printed to stderr.
+#
+# This function allows you to dereference the tag chosen by the user back into
+# the index associated with said tag. The index is the one-based tag/item pair
+# array position within the ordered list of tag/item pairs passed to dialog(1).
+#
+# Pass the tag chosen by the user as the first argument, followed by the
+# ordered list of tag/item pairs (HINT: use the same tag/item list as was
+# passed to dialog(1) for consistency).
+#
+# If the tag cannot be found, NULL is returned.
+#
+f_dialog_menutag2index()
+{
+ local tag="$1" tagn n=1
+ shift 1 # tag
+
+ while [ $# -gt 0 ]; do
+ tagn="$1"
+ shift 2 # tagn/item
+
+ if [ "$tag" = "$tagn" ]; then
+ echo $n
+ return $SUCCESS
+ fi
+ n=$(( $n + 1 ))
+ done
+ return $FAILURE
+}
+
+# f_dialog_menutag2index_with_help $tag_chosen $tag1 $item1 $help1 \
+# $tag2 $item2 $help2 ...
+#
+# To use the `--menu' option of dialog(1) with the `--item-help' option, you
+# must pass an ordered list of tag/item/help triplets on the command-line. When
+# the user selects a menu option the tag for that item is printed to stderr.
+#
+# This function allows you to dereference the tag chosen by the user back into
+# the index associated with said tag. The index is the one-based tag/item/help
+# triplet array position within the ordered list of tag/item/help triplets
+# passed to dialog(1).
+#
+# Pass the tag chosen by the user as the first argument, followed by the
+# ordered list of tag/item/help triplets (HINT: use the same tag/item/help list
+# as was passed to dialog(1) for consistency).
+#
+# If the tag cannot be found, NULL is returned.
+#
+f_dialog_menutag2index_with_help()
+{
+ local tag="$1" tagn n=1
+ shift 1 # tag
+
+ while [ $# -gt 0 ]; do
+ tagn="$1"
+ shift 3 # tagn/item/help
+
+ if [ "$tag" = "$tagn" ]; then
+ echo $n
+ return $SUCCESS
+ fi
+ n=$(( $n + 1 ))
+ done
+ return $FAILURE
+}
+
+# f_dialog_menutag2help $tag_chosen $tag1 $item1 $help1 $tag2 $item2 $help2 ...
+#
+# To use the `--menu' option of dialog(1) with the `--item-help' option, you
+# must pass an ordered list of tag/item/help triplets on the command-line. When
+# the user selects a menu option the tag for that item is printed to stderr.
+#
+# This function allows you to dereference the tag chosen by the user back into
+# the help associated with said tag (item is discarded/ignored).
+#
+# Pass the tag chosen by the user as the first argument, followed by the
+# ordered list of tag/item/help triplets (HINT: use the same tag/item/help list
+# as was passed to dialog(1) for consistency).
+#
+# If the tag cannot be found, NULL is returned.
+#
+f_dialog_menutag2help()
+{
+ local tag="$1" tagn help
+ shift 1 # tag
+
+ while [ $# -gt 0 ]; do
+ tagn="$1"
+ help="$3"
+ shift 3 # tagn/item/help
+
+ if [ "$tag" = "$tagn" ]; then
+ echo "$help"
+ return $SUCCESS
+ fi
+ done
+ return $FAILURE
+}
+
+############################################################ INIT FUNCTIONS
+
+# f_dialog_init
+#
+# Initialize (or re-initialize) the dialog module after setting/changing any
+# of the following environment variables:
+#
+# USE_XDIALOG Either NULL or Non-NULL. If given a value will indicate
+# that Xdialog(1) should be used instead of dialog(1).
+#
+# SECURE Either NULL or Non-NULL. If given a value will indicate
+# that (while running as root) sudo(8) authentication is
+# required to proceed.
+#
+# Also reads ~/.dialogrc for the following information:
+#
+# NO_SHADOW Either NULL or Non-NULL. If use_shadow is OFF (case-
+# insensitive) in ~/.dialogrc this is set to "1" (otherwise
+# unset).
+#
+f_dialog_init()
+{
+ local funcname=f_dialog_init
+
+ DIALOG_SELF_INITIALIZE=
+ USE_DIALOG=1
+
+ #
+ # Clone terminal stdout so we can redirect to it from within sub-shells
+ #
+ eval exec $DIALOG_TERMINAL_PASSTHRU_FD\>\&1
+
+ #
+ # Add `-S' and `-X' to the list of standard arguments supported by all
+ #
+ case "$GETOPTS_STDARGS" in
+ *SX*) : good ;; # already present
+ *) GETOPTS_STDARGS="${GETOPTS_STDARGS}SX"
+ esac
+
+ #
+ # Process stored command-line arguments
+ #
+ # NB: Using backticks instead of $(...) for portability since Linux
+ # bash(1) balks at the right parentheses encountered in the case-
+ # statement (incorrectly interpreting it as the close of $(...)).
+ #
+ f_dprintf "f_dialog_init: ARGV=[%s] GETOPTS_STDARGS=[%s]" \
+ "$ARGV" "$GETOPTS_STDARGS"
+ SECURE=`set -- $ARGV
+ OPTIND=1
+ while getopts \
+ "$GETOPTS_STDARGS$GETOPTS_EXTRA$GETOPTS_ALLFLAGS" \
+ flag > /dev/null; do
+ case "$flag" in
+ S) echo 1 ;;
+ esac
+ done
+ ` # END-BACKTICK
+ USE_XDIALOG=`set -- $ARGV
+ OPTIND=1
+ while getopts \
+ "$GETOPTS_STDARGS$GETOPTS_EXTRA$GETOPTS_ALLFLAGS" \
+ flag > /dev/null; do
+ case "$flag" in
+ S|X) echo 1 ;;
+ esac
+ done
+ ` # END-BACKTICK
+ f_dprintf "f_dialog_init: SECURE=[%s] USE_XDIALOG=[%s]" \
+ "$SECURE" "$USE_XDIALOG"
+
+ #
+ # Process `-X' command-line option
+ #
+ [ "$USE_XDIALOG" ] && DIALOG=Xdialog USE_DIALOG=
+
+ #
+ # Sanity check, or die gracefully
+ #
+ if ! f_have $DIALOG; then
+ unset USE_XDIALOG
+ local failed_dialog="$DIALOG"
+ DIALOG=dialog
+ f_die 1 "$msg_no_such_file_or_directory" "$pgm" "$failed_dialog"
+ fi
+
+ #
+ # Read ~/.dialogrc (unless using Xdialog(1)) for properties
+ #
+ if [ -f ~/.dialogrc -a ! "$USE_XDIALOG" ]; then
+ eval "$(
+ awk -v param=use_shadow -v expect=OFF \
+ -v set="NO_SHADOW=1" '
+ !/^[[:space:]]*(#|$)/ && \
+ tolower($1) ~ "^"param"(=|$)" && \
+ /[^#]*=/ {
+ sub(/^[^=]*=[[:space:]]*/, "")
+ if ( toupper($1) == expect ) print set";"
+ }' ~/.dialogrc
+ )"
+ fi
+
+ #
+ # If we're already running as root but we got there by way of sudo(8)
+ # and we have X11, we should merge the xauth(1) credentials from our
+ # original user.
+ #
+ if [ "$USE_XDIALOG" ] &&
+ [ "$( id -u )" = "0" ] &&
+ [ "$SUDO_USER" -a "$DISPLAY" ]
+ then
+ if ! f_have xauth; then
+ # Die gracefully, as we [likely] can't use Xdialog(1)
+ unset USE_XDIALOG
+ DIALOG=dialog
+ f_die 1 "$msg_no_such_file_or_directory" "$pgm" "xauth"
+ fi
+ HOSTNAME=$( hostname )
+ local displaynum="${DISPLAY#*:}"
+ eval xauth -if \~$SUDO_USER/.Xauthority extract - \
+ \"\$HOSTNAME/unix:\$displaynum\" \
+ \"\$HOSTNAME:\$displaynum\" | sudo sh -c 'xauth -ivf \
+ ~root/.Xauthority merge - > /dev/null 2>&1'
+ fi
+
+ #
+ # Probe Xdialog(1) for maximum height/width constraints, or die
+ # gracefully
+ #
+ if [ "$USE_XDIALOG" ]; then
+ local maxsize
+ if ! f_eval_catch -dk maxsize $funcname "$DIALOG" \
+ 'LANG= LC_ALL= %s --print-maxsize' "$DIALOG"
+ then
+ # Xdialog(1) failed, fall back to dialog(1)
+ unset USE_XDIALOG
+
+ # Display the error message produced by Xdialog(1)
+ local height width
+ f_dialog_buttonbox_size height width \
+ "$DIALOG_TITLE" "$DIALOG_BACKTITLE" "$maxsize"
+ dialog \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --ok-label "$msg_ok" \
+ --msgbox "$maxsize" $height $width
+ exit $FAILURE
+ fi
+
+ XDIALOG_MAXSIZE=$(
+ set -- ${maxsize##*:}
+
+ height=${1%,}
+ width=$2
+
+ echo $height $width
+ )
+ fi
+
+ #
+ # If using Xdialog(1), swap DIALOG_TITLE with DIALOG_BACKTITLE.
+ # The reason for this is because many dialog(1) applications use
+ # --backtitle for the program name (which is better suited as
+ # --title with Xdialog(1)).
+ #
+ if [ "$USE_XDIALOG" ]; then
+ local _DIALOG_TITLE="$DIALOG_TITLE"
+ DIALOG_TITLE="$DIALOG_BACKTITLE"
+ DIALOG_BACKTITLE="$_DIALOG_TITLE"
+ fi
+
+ f_dprintf "f_dialog_init: dialog(1) API initialized."
+}
+
+############################################################ MAIN
+
+#
+# Self-initialize unless requested otherwise
+#
+f_dprintf "%s: DIALOG_SELF_INITIALIZE=[%s]" \
+ dialog.subr "$DIALOG_SELF_INITIALIZE"
+case "$DIALOG_SELF_INITIALIZE" in
+""|0|[Nn][Oo]|[Oo][Ff][Ff]|[Ff][Aa][Ll][Ss][Ee]) : do nothing ;;
+*) f_dialog_init
+esac
+
+f_dprintf "%s: Successfully loaded." dialog.subr
+
+fi # ! $_DIALOG_SUBR
diff --git a/usr.sbin/bsdconfig/share/geom.subr b/usr.sbin/bsdconfig/share/geom.subr
new file mode 100644
index 0000000..912a5c7
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/geom.subr
@@ -0,0 +1,430 @@
+if [ ! "$_GEOM_SUBR" ]; then _GEOM_SUBR=1
+#
+# Copyright (c) 2012-2014 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." geom.subr
+f_include $BSDCFG_SHARE/strings.subr
+f_include $BSDCFG_SHARE/struct.subr
+
+############################################################ GLOBALS
+
+NGEOM_CLASSES=0 # Set by f_geom_get_all()/f_geom_reset()
+
+#
+# GEOM classes for use with f_geom_find()
+#
+# NB: Since $GEOM_CLASS_ANY is the NULL string, make sure you quote it whenever
+# you put arguments after it.
+#
+setvar GEOM_CLASS_ANY "any"
+setvar GEOM_CLASS_DEV "DEV"
+setvar GEOM_CLASS_DISK "DISK"
+setvar GEOM_CLASS_ELI "ELI"
+setvar GEOM_CLASS_FD "FD"
+setvar GEOM_CLASS_LABEL "LABEL"
+setvar GEOM_CLASS_MD "MD"
+setvar GEOM_CLASS_NOP "NOP"
+setvar GEOM_CLASS_PART "PART"
+setvar GEOM_CLASS_RAID "RAID"
+setvar GEOM_CLASS_SWAP "SWAP"
+setvar GEOM_CLASS_VFS "VFS"
+setvar GEOM_CLASS_ZFS_VDEV "ZFS::VDEV"
+setvar GEOM_CLASS_ZFS_ZVOL "ZFS::ZVOL"
+
+#
+# GEOM structure definitions
+#
+f_struct_define GEOM_CLASS \
+ id name ngeoms
+f_struct_define GEOM_GEOM \
+ id class_ref config name nconsumers nproviders rank
+ # Also consumerN where N is 1 through nconsumers
+ # Also providerN where N is 1 through nproviders
+f_struct_define GEOM_CONSUMER \
+ id geom_ref config mode provider_ref
+f_struct_define GEOM_PROVIDER \
+ id geom_ref config mode name mediasize
+
+# The config property of GEOM_GEOM struct is defined as this
+f_struct_define GEOM_GEOM_CONFIG \
+ entries first fwheads fwsectors last modified scheme state
+
+# The config property of GEOM_PROVIDER struct is defined as this
+f_struct_define GEOM_PROVIDER_CONFIG \
+ descr file fwheads fwsectors ident length type unit
+
+#
+# Default behavior is to call f_geom_get_all() automatically when loaded.
+#
+: ${GEOM_SELF_SCAN_ALL=1}
+
+############################################################ FUNCTIONS
+
+# f_geom_get_all
+#
+# Parse sysctl(8) `kern.geom.confxml' data into a series of structs. GEOM
+# classes are at the top of the heirarchy and are stored as numbered structs
+# from 1 to $NGEOM_CLASSES (set by this function) named `geom_class_C'. GEOM
+# objects within each class are stored as numbered structs from 1 to `ngeoms'
+# (a property of the GEOM class struct) named `geom_class_C_geom_N' (where C
+# is the class number and N is the geom number).
+#
+# Use the function f_geom_find() to get a list of geoms (execute without
+# arguments) or find specific geoms by class or name.
+#
+f_geom_get_all()
+{
+ eval "$( sysctl -n kern.geom.confxml | awk '
+ BEGIN {
+ struct_count["class"] = 0
+ struct_count["geom"] = 0
+ struct_count["consumer"] = 0
+ struct_count["provider"] = 0
+ }
+ ############################################### FUNCTIONS
+ function set_value(prop, value)
+ {
+ if (!struct_stack[cur_struct]) return
+ printf "%s set %s \"%s\"\n",
+ struct_stack[cur_struct], prop, value
+ }
+ function create(type, id)
+ {
+ if (struct = created[type "_" id])
+ print "f_struct_free", struct
+ else {
+ struct = struct_stack[cur_struct]
+ struct = struct ( struct ? "" : "geom" )
+ struct = struct "_" type "_" ++struct_count[type]
+ created[type "_" id] = struct
+ }
+ print "debug= f_struct_new GEOM_" toupper(type), struct
+ cur_struct++
+ struct_stack[cur_struct] = struct
+ type_stack[cur_struct] = type
+ set_value("id", id)
+ }
+ function create_config()
+ {
+ struct = struct_stack[cur_struct]
+ struct = struct ( struct ? "" : "geom" )
+ struct = struct "_config"
+ set_value("config", struct)
+ type = type_stack[cur_struct]
+ print "debug= f_struct_new GEOM_" toupper(type) "_CONFIG", \
+ struct
+ cur_struct++
+ struct_stack[cur_struct] = struct
+ type_stack[cur_struct] = type "_config"
+ }
+ function extract_attr(field, attr)
+ {
+ if (match(field, attr "=\"0x[[:xdigit:]]+\"")) {
+ len = length(attr)
+ return substr($2, len + 3, RLENGTH - len - 3)
+ }
+ }
+ function extract_data(type)
+ {
+ data = $0
+ sub("^[[:space:]]*<" type ">", "", data)
+ sub("</" type ">.*$", "", data)
+ return data
+ }
+ ############################################### OPENING PATTERNS
+ $1 == "<mesh>" { mesh = 1 }
+ $1 ~ /^<(class|geom)$/ && mesh {
+ prop = substr($1, 2)
+ if ((ref = extract_attr($2, "ref")) != "")
+ set_value(prop "_ref", ref)
+ else if ((id = extract_attr($2, "id")) != "")
+ create(prop, id)
+ }
+ $1 ~ /^<(consumer|provider)$/ && mesh {
+ prop = substr($1, 2)
+ if ((ref = extract_attr($2, "ref")) != "")
+ set_value(prop "_ref", ref)
+ else if ((id = extract_attr($2, "id")) != "") {
+ create(prop, id)
+ cur_struct--
+ propn = struct_count[prop]
+ set_value(prop propn, struct_stack[cur_struct+1])
+ cur_struct++
+ }
+ }
+ $1 == "<config>" && mesh { create_config() }
+ ############################################### PROPERTIES
+ $1 ~ /^<[[:alnum:]]+>/ {
+ prop = $1
+ sub(/^</, "", prop); sub(/>.*/, "", prop)
+ set_value(prop, extract_data(prop))
+ }
+ ############################################### CLOSING PATTERNS
+ $1 ~ "^</(consumer|provider|config)>$" { cur_struct-- }
+ $1 == "</geom>" {
+ set_value("nconsumers", struct_count["consumer"])
+ set_value("nproviders", struct_count["provider"])
+ cur_struct--
+ struct_count["consumer"] = 0
+ struct_count["provider"] = 0
+ }
+ $1 == "</class>" {
+ set_value("ngeoms", struct_count["geom"])
+ cur_struct--
+ struct_count["consumer"] = 0
+ struct_count["provider"] = 0
+ struct_count["geom"] = 0
+ }
+ $1 == "</mesh>" {
+ printf "NGEOM_CLASSES=%u\n", struct_count["class"]
+ delete struct_count
+ mesh = 0
+ }' )"
+}
+
+# f_geom_reset
+#
+# Reset the registered GEOM chain.
+#
+f_geom_reset()
+{
+ local classn=1 class ngeoms geomn geom
+ while [ $classn -le ${NGEOM_CLASSES:-0} ]; do
+ class=geom_class_$classn
+ $class get ngeoms ngeoms
+ geomn=1
+ while [ $geomn -le $ngeoms ]; do
+ f_struct_free ${class}_geom_$geomn
+ geomn=$(( $geomn + 1 ))
+ done
+ classn=$(( $classn + 1 ))
+ done
+ NGEOM_CLASSES=0
+}
+
+# f_geom_rescan
+#
+# Rescan all GEOMs - convenience function.
+#
+f_geom_rescan()
+{
+ f_geom_reset
+ f_geom_get_all
+}
+
+# f_geom_find $name [$type [$var_to_set]]
+#
+# Find one or more registered GEOMs by name, type, or both. Returns a space-
+# separated list of GEOMs matching the search criterion. The $type argument
+# should be the GEOM class (see $GEOM_CLASS_* variables in GLOBALS above).
+#
+# If $var_to_set is missing or NULL, the GEOM name(s) are printed to standard
+# out for capturing in a sub-shell (which is less-recommended because of
+# performance degredation; for example, when called in a loop).
+#
+f_geom_find()
+{
+ local __name="$1" __type="${2:-$GEOM_CLASS_ANY}" __var_to_set="$3"
+ local __classn=1 __class __class_name __ngeoms
+ local __geomn __geom __geom_name __found=
+ while [ $__classn -le ${NGEOM_CLASSES:-0} ]; do
+ __class=geom_class_$__classn
+ $__class get name __class_name
+ if [ "$__type" != "$GEOM_CLASS_ANY" -a \
+ "$__type" != "$__class_name" ]
+ then
+ __classn=$(( $__classn + 1 ))
+ continue
+ fi
+
+ __geomn=1
+ $__class get ngeoms __ngeoms || __ngeoms=0
+ while [ $__geomn -le $__ngeoms ]; do
+ __geom=${__class}_geom_$__geomn
+ $__geom get name __geom_name
+ [ "$__name" = "$__geom_name" -o ! "$__name" ] &&
+ __found="$__found $__geom"
+ __geomn=$(( $__geomn + 1 ))
+ done
+ __classn=$(( $__classn + 1 ))
+ done
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" "${__found# }"
+ else
+ echo $__found
+ fi
+ [ "$__found" ] # Return status
+}
+
+# f_geom_find_by $prop $find [$type [$var_to_set]]
+#
+# Find GEOM-related struct where $prop of the struct is equal to $find. Returns
+# NULL or the name of the first GEOM struct to match. The $type argument should
+# be one of the following:
+#
+# NULL Find any of the below
+# class Find GEOM_CLASS struct
+# geom Find GEOM_GEOM struct
+# consumer Find GEOM_CONSUMER struct
+# provider Find GEOM_PROVIDER struct
+#
+# The $prop argument can be any property of the given type of struct. Some
+# properties are common to all types (such as id) so the $type argument is
+# optional (allowing you to return any struct whose property matches $find).
+#
+# If $var_to_set is missing or NULL, the GEOM struct name is printed to
+# standard out for capturing in a sub-shell (which is less-recommended because
+# of performance degredation; for example when called in a loop).
+#
+f_geom_find_by()
+{
+ local __prop="$1" __find="$2" __type="$3" __var_to_set="$4"
+ local __classn=1 __class __ngeoms
+ local __geomn __geom __nitems
+ local __itype __itemn __item
+ local __value __found=
+
+ if [ ! "$__prop" ]; then
+ [ "$__var_to_set" ] && setvar "$__var_to_set" ""
+ return $FAILURE
+ fi
+
+ case "$__type" in
+ "") : OK ;;
+ class|GEOM_CLASS) __type=class ;;
+ geom|GEOM_GEOM) __type=geom ;;
+ consumer|GEOM_CONSUMER) __type=consumer ;;
+ provider|GEOM_PROVIDER) __type=provider ;;
+ *)
+ [ "$__var_to_set" ] && setvar "$__var_to_set" ""
+ return $FAILURE
+ esac
+
+ while [ $__classn -le ${NGEOM_CLASSES:-0} ]; do
+ __class=geom_class_$__classn
+
+ if [ "${__type:-class}" = "class" ]; then
+ $__class get "$__prop" __value || __value=
+ [ "$__value" = "$__find" ] && __found="$__class" break
+ [ "$__type" ] && __classn=$(( $__classn + 1 )) continue
+ fi
+
+ __geomn=1
+ $__class get ngeoms __ngeoms || __ngeoms=0
+ while [ $__geomn -le $__ngeoms ]; do
+ __geom=${__class}_geom_$__geomn
+
+ if [ "${__type:-geom}" = "geom" ]; then
+ $__geom get "$__prop" __value || __value=
+ [ "$__value" = "$__find" ] &&
+ __found="$__geom" break
+ [ "$__type" ] &&
+ __geomn=$(( $__geomn + 1 )) continue
+ fi
+
+ for __itype in ${__type:-consumer provider}; do
+ $__geom get n${__itype}s __nitems || continue
+ __itemn=1
+ while [ $__itemn -le $__nitems ]; do
+ __item=${__geom}_${__itype}_$__itemn
+
+ $__item get "$__prop" __value ||
+ __value=
+ [ "$__value" = "$__find" ] &&
+ __found="$__item" break
+ __itemn=$(( $__itemn + 1 ))
+ done
+ [ "$__found" ] && break
+ done
+ [ "$__found" ] && break
+ __geomn=$(( $__geomn + 1 ))
+ done
+ [ "$__found" ] && break
+ __classn=$(( $__classn + 1 ))
+ done
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" "$__found"
+ else
+ [ "$__found" ] && echo "$__found"
+ fi
+ [ "$__found" ] # Return status
+}
+
+# f_geom_parent $geom|$consumer|$provider|$config [$var_to_set]
+#
+# Get the GEOM class associated with one of $geom, $consumer, $provider or
+# $config.
+#
+# If $var_to_set is missing or NULL, the GEOM class name is printed to standard
+# out for capturing in a sub-shell (which is less-recommended because of
+# performance degredation; for example when called in a loop).
+#
+f_geom_parent()
+{
+ local __struct="$1" __var_to_set="$2"
+ # NB: Order of pattern matches below is important
+ case "$__struct" in
+ *_config*) __struct="${__struct%_config*}" ;;
+ *_consumer_*) __struct="${__struct%_consumer_[0-9]*}" ;;
+ *_provider_*) __struct="${__struct%_provider_[0-9]*}" ;;
+ *_geom_*) __struct="${__struct%_geom_[0-9]*}" ;;
+ *) __struct=
+ esac
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" "$__struct"
+ else
+ echo "$__struct"
+ fi
+ f_struct "$__struct" # Return status
+}
+
+############################################################ MAIN
+
+#
+# Parse GEOM configuration unless requested otherwise
+#
+f_dprintf "%s: GEOM_SELF_SCAN_ALL=[%s]" geom.subr "$GEOM_SELF_SCAN_ALL"
+case "$GEOM_SELF_SCAN_ALL" in
+""|0|[Nn][Oo]|[Oo][Ff][Ff]|[Ff][Aa][Ll][Ss][Ee]) : do nothing ;;
+*)
+ f_geom_get_all
+ if [ "$debug" ]; then
+ debug= f_geom_find "" "$GEOM_CLASS_ANY" geoms
+ f_count ngeoms $geoms
+ f_dprintf "%s: Initialized %u geom devices in %u classes." \
+ geom.subr "$ngeoms" "$NGEOM_CLASSES"
+ unset geoms ngeoms
+ fi
+esac
+
+f_dprintf "%s: Successfully loaded." geom.subr
+
+fi # ! $_GEOM_SUBR
diff --git a/usr.sbin/bsdconfig/share/keymap.subr b/usr.sbin/bsdconfig/share/keymap.subr
new file mode 100644
index 0000000..7f2f87c
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/keymap.subr
@@ -0,0 +1,263 @@
+if [ ! "$_KEYMAP_SUBR" ]; then _KEYMAP_SUBR=1
+#
+# Copyright (c) 2013-2015 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." keymap.subr
+f_include $BSDCFG_SHARE/struct.subr
+
+############################################################ CONFIGURATION
+
+#
+# Defaults taken from usr.sbin/kbdmap/kbdmap.h
+#
+: ${DEFAULT_LANG:=en}
+: ${DEFAULT_KEYMAP_DIR:=/usr/share/syscons/keymaps}
+
+############################################################ GLOBALS
+
+KEYMAPS=
+NKEYMAPS=0
+
+# A "keymap" from kbdmap's point of view
+f_struct_define KEYMAP \
+ desc \
+ keym \
+ mark
+
+#
+# Default behavior is to call f_keymap_get_all() automatically when loaded.
+#
+: ${KEYMAP_SELF_SCAN_ALL=1}
+
+############################################################ FUNCTIONS
+
+# f_keymap_register $name $desc $keym $mark
+#
+# Register a keymap. A `structure' (see struct.subr) is created with the name
+# keymap_$name (so make sure $name contains only alpha-numeric characters or
+# the underscore, `_'). The remaining arguments after $name correspond to the
+# propertise of the `KEYMAP' structure-type (defined above).
+#
+# If not already registered, the keymap is then appended to the KEYMAPS
+# environment variable, a space-separated list of all registered keymaps.
+#
+f_keymap_register()
+{
+ local name="$1" desc="$2" keym="$3" mark="$4"
+
+ f_struct_new KEYMAP "keymap_$name" || return $FAILURE
+ keymap_$name set desc "$desc"
+ keymap_$name set keym "$keym"
+ keymap_$name set mark "$mark"
+
+ # Scan our global register to see if needs ammending
+ local k found=
+ for k in $KEYMAPS; do
+ [ "$k" = "$name" ] || continue
+ found=1 && break
+ done
+ [ "$found" ] || KEYMAPS="$KEYMAPS $name"
+
+ return $SUCCESS
+}
+
+# f_keymap_checkfile $keymap
+#
+# Check that $keymap is a readable kbdmap(5) file. Returns success if $keymap
+# is a file, is readable, and exists in $DEFAULT_KEYMAP_DIR; otherwise failure.
+# If debugging is enabled, an appropriate debug error message is printed if
+# $keymap is not available.
+#
+f_keymap_checkfile()
+{
+ local keym="$1"
+
+ # Fixup keymap if it doesn't already contain at least one `/'
+ [ "${keym#*/}" = "$keym" ] && keym="$DEFAULT_KEYMAP_DIR/$keym"
+
+ # Short-cuts
+ [ -f "$keym" -a -r "$keym" ] && return $SUCCESS
+ f_debugging || return $FAILURE
+
+ # Print an appropriate debug error message
+ if [ ! -e "$keym" ]; then
+ f_dprintf "%s: No such file or directory" "$keym"
+ elif [ ! -f "$keym" ]; then
+ f_dprintf "%s: Not a file!" "$keym"
+ elif [ ! -r "$keym" ]; then
+ f_dprintf "%s: Permission denied" "$keym"
+ fi
+
+ return $FAILURE
+}
+
+# f_keymap_get_all
+#
+# Get all keymap information for kbdmap(5) entries both in the database and
+# loosely existing in $DEFAULT_KEYMAP_DIR.
+#
+f_keymap_get_all()
+{
+ local fname=f_keymap_get_all
+ local lang="${LC_ALL:-${LC_CTYPE:-${LANG:-$DEFAULT_LANG}}}"
+ [ "$lang" = "C" ] && lang="$DEFAULT_LANG"
+
+ f_dprintf "%s: Looking for keymap files..." $fname
+ f_dialog_info "$msg_looking_for_keymap_files"
+ f_dprintf "DEFAULT_LANG=[%s]" "$DEFAULT_LANG"
+
+ eval "$( awk -F: -v lang="$lang" -v lang_default="$DEFAULT_LANG" '
+ BEGIN {
+ # en_US.ISO8859-1 -> en_..\.ISO8859-1
+ dialect = lang
+ if (length(dialect) >= 6 &&
+ substr(dialect, 3, 1) == "_")
+ dialect = substr(dialect, 1, 3) ".." \
+ substr(dialect, 6)
+ printf "f_dprintf \"dialect=[%%s]\" \"%s\";\n", dialect
+
+ # en_US.ISO8859-1 -> en
+ lang_abk = lang
+ if (length(lang_abk) >= 3 &&
+ substr(lang_abk, 3, 1) == "_")
+ lang_abk = substr(lang_abk, 1, 2)
+ printf "f_dprintf \"lang_abk=[%%s]\" \"%s\";\n",
+ lang_abk
+ }
+ function find_token(buffer, token)
+ {
+ if (split(buffer, tokens, /,/) == 0) return 0
+ found = 0
+ for (t in tokens)
+ if (token == tokens[t]) { found = 1; break }
+ return found
+ }
+ function add_keymap(desc,mark,keym)
+ {
+ marks[keym] = mark
+ name = keym
+ gsub(/[^[:alnum:]_]/, "_", name)
+ gsub(/'\''/, "'\''\\'\'\''", desc);
+ printf "f_keymap_checkfile %s && " \
+ "f_keymap_register %s '\'%s\'' %s %u\n",
+ keym, name, desc, keym, mark
+ }
+ !/^[[:space:]]*(#|$)/ {
+ sub(/^[[:space:]]*/, "", $0)
+ keym = $1
+ if (keym ~ /^(MENU|FONT)$/) next
+ lg = ($2 == "" ? lang_default : $2)
+
+ # Match the entry and store the type of match we made
+ # as the mark value (so that if we make a better match
+ # later on with a higher mark, it overwrites previous)
+
+ mark = marks[keym];
+ if (find_token(lg, lang))
+ add_keymap($3, 4, keym) # Best match
+ else if (mark <= 3 && find_token(lg, dialect))
+ add_keymap($3, 3, keym)
+ else if (mark <= 2 && find_token(lg, lang_abk))
+ add_keymap($3, 2, keym)
+ else if (mark <= 1 && find_token(lg, lang_default))
+ add_keymap($3, 1, keym)
+ else if (mark <= 0)
+ add_keymap($3, 0, keym)
+ }
+ ' "$DEFAULT_KEYMAP_DIR/INDEX.${DEFAULT_KEYMAP_DIR##*/}" )"
+
+
+ #
+ # Look for keymaps not in database
+ #
+ local direntry keym name
+ set +f # glob
+ for direntry in "$DEFAULT_KEYMAP_DIR"/*; do
+ [ "${direntry##*.}" = ".kbd" ] || continue
+ keym="${direntry##*/}"
+ f_str2varname "$keym" name
+ f_struct keymap_$name && continue
+ f_keymap_checkfile "$keym" &&
+ f_keymap_register $name "${keym%.*}" "$keym" 0
+ f_dprintf "%s: not in kbdmap(5) database" "$keym"
+ done
+
+ #
+ # Sort the items by their descriptions
+ #
+ f_dprintf "%s: Sorting keymap entries by description..." $fname
+ KEYMAPS=$(
+ for k in $KEYMAPS; do
+ echo -n "$k "
+ # NOTE: Translate '8x8' to '8x08' before sending to
+ # sort(1) so that things work out as we might expect.
+ debug= keymap_$k get desc | awk 'gsub(/8x8/,"8x08")||1'
+ done | sort -k2 | awk '{
+ printf "%s%s", (started ? " " : ""), $1; started = 1
+ }'
+ )
+
+ return $SUCCESS
+}
+
+# f_keymap_kbdcontrol $keymap
+#
+# Install keyboard map file from $keymap.
+#
+f_keymap_kbdcontrol()
+{
+ local keymap="$1"
+
+ [ "$keymap" ] || return $SUCCESS
+
+ # Fixup keymap if it doesn't already contain at least one `/'
+ [ "${keymap#*/}" = "$keymap" ] && keymap="$DEFAULT_KEYMAP_DIR/$keymap"
+
+ [ "$USE_XDIALOG" ] || kbdcontrol -l "$keymap"
+}
+
+############################################################ MAIN
+
+#
+# Scan for keymaps unless requeted otherwise
+#
+f_dprintf "%s: KEYMAP_SELF_SCAN_ALL=[%s]" keymap.subr "$KEYMAP_SELF_SCAN_ALL"
+case "$KEYMAP_SELF_SCAN_ALL" in
+""|0|[Nn][Oo]|[Oo][Ff][Ff]|[Ff][Aa][Ll][Ss][Ee]) : do nothing ;;
+*) f_keymap_get_all
+esac
+
+f_count NKEYMAPS $KEYMAPS
+f_dprintf "%s: Found %u keymap file(s)." keymap.subr $NKEYMAPS
+
+f_dprintf "%s: Successfully loaded." keymap.subr
+
+fi # ! $_KEYMAP_SUBR
diff --git a/usr.sbin/bsdconfig/share/media/Makefile b/usr.sbin/bsdconfig/share/media/Makefile
new file mode 100644
index 0000000..135515b
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/media/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+FILESDIR= ${SHAREDIR}/bsdconfig/media
+FILES= any.subr cdrom.subr common.subr directory.subr dos.subr \
+ floppy.subr ftp.subr http.subr httpproxy.subr network.subr \
+ nfs.subr options.subr tcpip.subr ufs.subr usb.subr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/share/media/Makefile.depend b/usr.sbin/bsdconfig/share/media/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/media/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/share/media/any.subr b/usr.sbin/bsdconfig/share/media/any.subr
new file mode 100644
index 0000000..516d2d2
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/media/any.subr
@@ -0,0 +1,149 @@
+if [ ! "$_MEDIA_ANY_SUBR" ]; then _MEDIA_ANY_SUBR=1
+#
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." media/any.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/media/cdrom.subr
+f_include $BSDCFG_SHARE/media/directory.subr
+f_include $BSDCFG_SHARE/media/dos.subr
+f_include $BSDCFG_SHARE/media/floppy.subr
+f_include $BSDCFG_SHARE/media/ftp.subr
+f_include $BSDCFG_SHARE/media/http.subr
+f_include $BSDCFG_SHARE/media/httpproxy.subr
+f_include $BSDCFG_SHARE/media/nfs.subr
+f_include $BSDCFG_SHARE/media/options.subr
+f_include $BSDCFG_SHARE/media/ufs.subr
+f_include $BSDCFG_SHARE/media/usb.subr
+f_include $BSDCFG_SHARE/struct.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+
+MEDIA_HELPFILE=$BSDCFG_LIBE/include/media.hlp
+
+############################################################ FUNCTIONS
+
+# f_media_get_type
+#
+# Prompt the user to select amongst the known media types (included above).
+#
+# If the user does not cancel or press Esc, invokes the f_media_set_* function
+# associated with the chosen media type. If after all that we have a struct
+# named `device_media' then success is returned, otherwise failure.
+#
+# NOTE: The f_media_set_* function should create the `device_media' struct.
+# See `struct.subr' and the above `media/*.subr' includes for more details.
+#
+f_media_get_type()
+{
+ f_dialog_title "$msg_choose_installation_media"
+ local title="$DIALOG_TITLE" btitle="$DIALOG_BACKTITLE"
+ f_dialog_title_restore
+ local prompt="$msg_choose_installation_media_description"
+ local menu_list="
+ '1 $msg_cd_dvd' '$msg_install_from_a_freebsd_cd_dvd'
+ '2 $msg_ftp' '$msg_install_from_an_ftp_server'
+ '3 $msg_http_proxy'
+ '$msg_install_from_an_ftp_server_thru_proxy'
+ '4 $msg_http_direct' '$msg_install_from_an_http_server'
+ '5 $msg_directory' '$msg_install_from_the_existing_filesystem'
+ '6 $msg_nfs' '$msg_install_over_nfs'
+ '7 $msg_dos' '$msg_install_from_a_dos_partition'
+ '8 $msg_ufs' '$msg_install_from_a_ufs_partition'
+ '9 $msg_floppy' '$msg_install_from_a_floppy_disk_set'
+ 'A $msg_usb' '$msg_install_from_a_usb_drive'
+ 'X $msg_options' '$msg_view_set_various_media_options'
+ " # END-QUOTE
+ local hline="$hline_choose_help_for_more_information_on_media_types"
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$title\" \
+ \"\$btitle\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ local mtag
+ while :; do
+ mtag=$( eval $DIALOG \
+ --title \"\$title\" \
+ --backtitle \"\$btitle\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --help-button \
+ --help-label \"\$msg_help\" \
+ ${USE_XDIALOG:+--help \"\"} \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize mtag
+ f_dprintf "retval=%s mtag=[%s]" $retval "$mtag"
+
+ if [ $retval -eq $DIALOG_HELP ]; then
+ f_show_help "$MEDIA_HELPFILE"
+ continue
+ elif [ $retval -ne $DIALOG_OK ]; then
+ return $FAILURE
+ fi
+
+ case "$mtag" in
+ ?" $msg_cd_dvd") f_media_set_cdrom ;;
+ ?" $msg_ftp") f_media_set_ftp ;;
+ ?" $msg_http_proxy") f_media_set_http_proxy ;;
+ ?" $msg_http_direct") f_media_set_http ;;
+ ?" $msg_directory") f_media_set_directory ;;
+ ?" $msg_dos") f_media_set_dos ;;
+ ?" $msg_nfs") f_media_set_nfs ;;
+ ?" $msg_ufs") f_media_set_ufs ;;
+ ?" $msg_floppy") f_media_set_floppy ;;
+ ?" $msg_usb") f_media_set_usb ;;
+ ?" $msg_options")
+ f_media_options_menu
+ continue
+ ;;
+ esac
+ break
+ done
+
+ f_struct device_media || return $FAILURE
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." media/any.subr
+
+fi # ! $_MEDIA_ANY_SUBR
diff --git a/usr.sbin/bsdconfig/share/media/cdrom.subr b/usr.sbin/bsdconfig/share/media/cdrom.subr
new file mode 100644
index 0000000..bbbd638
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/media/cdrom.subr
@@ -0,0 +1,217 @@
+if [ ! "$_MEDIA_CDROM_SUBR" ]; then _MEDIA_CDROM_SUBR=1
+#
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." media/cdrom.subr
+f_include $BSDCFG_SHARE/device.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/media/common.subr
+f_include $BSDCFG_SHARE/struct.subr
+f_include $BSDCFG_SHARE/variable.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+
+############################################################ GLOBALS
+
+CDROM_MOUNTED=
+CDROM_PREVIOUSLY_MOUNTED=
+CDROM_INIT_QUIET=
+
+############################################################ FUNCTIONS
+
+# f_media_set_cdrom
+#
+# Return success if we both found and set the media type to be a CD.
+#
+f_media_set_cdrom()
+{
+ f_media_close
+
+ local devs ndevs
+ f_device_find "" $DEVICE_TYPE_CDROM devs
+ f_count ndevs $devs
+
+ if [ ${ndevs:=0} -eq 0 ]; then
+ f_interactive && f_show_msg "$msg_no_cd_dvd_devices_found"
+ return $FAILURE
+ elif [ $ndevs -eq 1 ]; then
+ f_struct_copy $devs device_media
+ else
+ local dev
+ local title="$msg_choose_a_cd_dvd_type"
+ local prompt="$msg_please_select_a_cd_dvd_drive"
+ local hline=
+
+ dev=$( f_device_menu \
+ "$title" "$prompt" "$hline" $DEVICE_TYPE_CDROM \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD ) ||
+ return $FAILURE
+
+ f_struct_copy "$dev" device_media
+ fi
+
+ f_struct device_media || return $FAILURE
+}
+
+# f_media_init_cdrom $device
+#
+# Initializes the CDROM media device. Returns success if able to mount the CD
+# device using mount_cd9660(8).
+#
+f_media_init_cdrom()
+{
+ local funcname=f_media_init_cdrom
+ local dev="$1" devname err
+
+ f_struct "$dev" get devname devname || return $FAILURE
+ f_dprintf "Init routine called for CDROM device. devname=[%s]" \
+ "$devname"
+
+ if [ "$CDROM_MOUNTED" ]; then
+ f_dprintf "CDROM device already mounted."
+ return $SUCCESS
+ fi
+
+ if [ ! -e "$MOUNTPOINT" ]; then
+ f_eval_catch $funcname mkdir 'mkdir -p "%s"' "$MOUNTPOINT" ||
+ return $FAILURE
+ fi
+
+ if ! f_eval_catch -dk err $funcname mount_cd9660 \
+ 'mount_cd9660 "%s" "%s"' "$devname" "$MOUNTPOINT"
+ then
+ err="${err#mount_cd9660: }"; err="${err#$devname: }"
+ case "$err" in
+ "Device busy")
+ # Perhaps the CDROM drive is already mounted as /cdrom
+ if f_mounted /cdrom; then
+ CDROM_PREVIOUSLY_MOUNTED=1
+ MOUNTPOINT=/cdrom
+ err=
+ fi
+ ;;
+ esac
+ case "$err" in
+ "") : good ;; # no error
+ *)
+ [ "$CDROM_INIT_QUIET" ] ||
+ f_show_msg "$msg_error_mounting_device" \
+ "$devname" "$MOUNTPOINT" "$err"
+ return $FAILURE
+ esac
+ fi
+ CDROM_MOUNTED=1
+
+ : xxx # /cdrom.inf has been deprecated since 9.0-R
+
+ # No other CDROM media validation at this time
+
+ return $SUCCESS
+}
+
+# f_media_get_cdrom $device $file [$probe_type]
+#
+# Returns data from $file on a mounted CDROM device. Similar to cat(1). If
+# $probe_type is present and non-NULL, returns success if $file exists. If
+# $probe_type is equal to $PROBE_SIZE, prints the size of $file in bytes to
+# standard-out.
+#
+f_media_get_cdrom()
+{
+ local dev="$1" file="$2" probe_type="$3"
+ local name
+
+ $dev get name name
+ f_dprintf "f_media_get_cdrom: dev=[%s] file=[%s] probe_type=%s" \
+ "$name" "$file" "$probe_type"
+
+ f_media_generic_get "$MOUNTPOINT" "$file" "$probe_type"
+}
+
+# f_media_shutdown_cdrom $device
+#
+# Shuts down the CDROM device. Return status should be ignored.
+#
+f_media_shutdown_cdrom()
+{
+ local funcname=f_media_shutdown_cdrom
+ local dev="$1" err
+
+ [ "$CDROM_MOUNTED" ] || return $FAILURE
+
+ if [ "$CDROM_PREVIOUSLY_MOUNTED" ]; then
+ CDROM_MOUNTED=
+ return $SUCCESS
+ fi
+
+ if ! f_eval_catch -dk err $funcname umount \
+ 'umount -f "%s"' "$MOUNTPOINT"
+ then
+ err="${err#umount: }"; err="${err#*: }"
+ f_show_msg "$msg_could_not_unmount_the_cdrom_dvd" \
+ "$MOUNTPOINT" "$err"
+ else
+ CDROM_MOUNTED=
+ fi
+}
+
+# f_media_eject_cdrom $device
+#
+# Eject the media from the CDROM device. Returns success.
+#
+f_media_eject_cdrom()
+{
+ local funcname=f_media_eject_cdrom
+ local dev="$1" name devname err
+
+ f_struct "$dev" || return $SUCCESS
+ $dev get name name || return $SUCCESS
+ $dev get devname devname || return $SUCCESS
+
+ # Don't eject labels
+ case "$name" in */*) return $SUCCESS; esac
+
+ f_dprintf "Ejecting CDROM/DVD at %s" "$devname"
+ if ! f_eval_catch -dk err $funcname cdcontrol \
+ 'cdcontrol -f "%s" eject' "$devname"
+ then
+ f_dprintf "Could not eject the CDROM/DVD from %s: %s" \
+ "$devname" "${err#cdcontrol: }"
+ fi
+ return $SUCCESS
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." media/cdrom.subr
+
+fi # ! $_MEDIA_CDROM_SUBR
diff --git a/usr.sbin/bsdconfig/share/media/common.subr b/usr.sbin/bsdconfig/share/media/common.subr
new file mode 100644
index 0000000..0bd420d
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/media/common.subr
@@ -0,0 +1,155 @@
+if [ ! "$_MEDIA_COMMON_SUBR" ]; then _MEDIA_COMMON_SUBR=1
+#
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." media/common.subr
+f_include $BSDCFG_SHARE/device.subr
+f_include $BSDCFG_SHARE/media/any.subr
+f_include $BSDCFG_SHARE/struct.subr
+
+############################################################ GLOBALS
+
+#
+# Where to mount media
+#
+MOUNTPOINT=/dist
+
+#
+# Media probe values to use for `f_media_get_TYPE media $file $PROBE' or
+# `f_device_get device_media $file $PROBE' (where $PROBE is one of the below
+# values).
+#
+PROBE_EXIST=1
+PROBE_SIZE=2
+
+############################################################ FUNCTIONS
+
+# f_media_open
+#
+# Returms success if able to initialize the media device.
+#
+f_media_open()
+{
+ f_dprintf "f_media_open: Verifying and initiliazing media device"
+ { # Verify and initialize device media if-defined
+ f_struct device_media &&
+ f_media_verify &&
+ f_device_init device_media
+ } || return $FAILURE
+}
+
+# f_media_close
+#
+# Shuts down the media device, see f_device_shutdown() from device.subr for
+# more details.
+#
+f_media_close()
+{
+ f_dprintf "f_media_close: Shutting down media device"
+ f_struct device_media &&
+ f_device_shutdown device_media
+ f_struct_free device_media
+}
+
+# f_media_verify
+#
+# Returns success if the media device is available, and if not, prompts the
+# user to select a media type. See f_media_get_type() from media/any.subr for
+# more details.
+#
+f_media_verify()
+{
+ f_dprintf "f_media_verify: Verifying media device"
+ f_struct device_media || f_media_get_type
+}
+
+# f_media_generic_get $base $file [$probe_type]
+#
+# A generic open which follows a well-known "path" of places to look. If
+# $probe_type is present and non-NULL, returns success if $file exists. If
+# $probe_type is equal to $PROBE_SIZE, prints the size of $file in bytes to
+# standard-out.
+#
+f_media_generic_get()
+{
+ local funcname=f_media_generic_get
+ local base="$1" file="$2" probe_type="$3"
+
+ local fname=f_media_generic_get
+ f_dprintf "%s: base=[%s] files=[%s] probe_type=%s" \
+ $fname "$base" "$file" "$probe_type"
+
+ local rel path
+ f_getvar $VAR_RELNAME rel
+ for path in \
+ "$base/$file" \
+ "$base/FreeBSD/$file" \
+ "$base/releases/$file" \
+ "$base/$rel/$file" \
+ ; do
+ if [ -f "$path" -a -r "$path" ]; then
+ f_dprintf "%s: file exists path=[%s]" $fname "$path"
+ if [ "$probe_type" = "$PROBE_SIZE" ]; then
+ local size
+ f_eval_catch -dk size $funcname stat \
+ 'stat -f %%z "%s"' "$path" || size=-1
+ f_isinteger "$size" || size=-1
+ echo $size
+ fi
+ [ "$probe_type" ] && return $SUCCESS
+ cat "$path"
+ return $?
+ fi
+ done
+
+ path="$base/releases/$rel/$file" # Final path to try
+ if [ -f "$path" -a -r "$path" ]; then
+ f_dprintf "%s: file exists path=[%s]" $fname "$path"
+ if [ "$probe_type" = "$PROBE_SIZE" ]; then
+ local size
+ f_eval_catch -dk size $funcname stat \
+ 'stat -f %%z "%s"' "$path" || size=-1
+ f_isinteger "$size" || size=-1
+ echo $size
+ fi
+ [ "$probe_type" ] && return $SUCCESS
+ elif [ "$probe_type" ]; then
+ [ "$probe_type" = "$PROBE_SIZE" ] && echo "-1"
+ return $FAILURE
+ fi
+ cat "$base/releases/$rel/$file" # Final path to try
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." media/common.subr
+
+fi # ! $_MEDIA_COMMON_SUBR
diff --git a/usr.sbin/bsdconfig/share/media/directory.subr b/usr.sbin/bsdconfig/share/media/directory.subr
new file mode 100644
index 0000000..004cb74
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/media/directory.subr
@@ -0,0 +1,151 @@
+if [ ! "$_MEDIA_DIRECTORY_SUBR" ]; then _MEDIA_DIRECTORY_SUBR=1
+#
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." media/directory.subr
+f_include $BSDCFG_SHARE/device.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/media/common.subr
+f_include $BSDCFG_SHARE/struct.subr
+f_include $BSDCFG_SHARE/variable.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+
+############################################################ GLOBALS
+
+DIRECTORY_CHECKED=
+
+############################################################ FUNCTIONS
+
+# f_media_set_directory
+#
+# Return success if we both found and set the media type to be a local
+# directory.
+#
+# Variables from variable.subr that can be used to script user input:
+#
+# VAR_DIRECTORY_PATH
+# Path to an existing directory containing the FreeBSD
+# distribution files.
+#
+f_media_set_directory()
+{
+ local path
+
+ f_media_close
+
+ f_variable_get_value $VAR_DIRECTORY_PATH \
+ "$msg_enter_a_fully_qualified_pathname_for_the_directory"
+ f_getvar $VAR_DIRECTORY_PATH path
+ [ "$path" ] || return $FAILURE
+
+ f_struct_new DEVICE device_directory
+ device_directory set name "$path"
+ device_directory set get f_media_get_directory
+ device_directory set init f_media_init_directory
+ device_directory set shutdown f_media_shutdown_directory
+ device_directory set private "$path"
+
+ f_struct_copy device_directory device_media
+ f_struct_free device_directory
+
+ f_struct device_media || return $FAILURE
+}
+
+# f_media_init_directory $device
+#
+# Initializes the Directory media device. Returns success if the directory path
+# both exists and is a directory.
+#
+f_media_init_directory()
+{
+ local dev="$1" path
+
+ $dev get private path || return $FAILURE
+ f_dprintf "Init routine called for Directory device. path=[%s]" \
+ "$path"
+
+ # Track whether we've been through here before (for remote filesystems
+ # mounted in the directory path, not repeating these queries saves us
+ # valuable time for slow/uncooperative links).
+ if [ "$DIRECTORY_CHECKED" ]; then
+ f_dprintf "Directory device already checked."
+ return $SUCCESS
+ fi
+
+ if [ ! -e "$path" ]; then
+ f_show_msg "$msg_no_such_file_or_directory" \
+ "f_media_init_directory" "$path"
+ return $FAILURE
+ elif [ ! -d "$path" ]; then
+ f_show_msg "$msg_not_a_directory" \
+ "f_media_init_directory" "$path"
+ return $FAILURE
+ fi
+ DIRECTORY_CHECKED=1
+ return $SUCCESS
+}
+
+# f_media_get_directory $device $file [$probe_type]
+#
+# Returns data from $file in the existing/current filesystem. Similar to
+# cat(1). If $probe_type is present and non-NULL, returns success if $file
+# exists. If $probe_type is equal to $PROBE_SIZE, prints the size of $file in
+# bytes to standard-out.
+#
+f_media_get_directory()
+{
+ local dev="$1" file="$2" probe_type="$3" path
+ local name
+
+ $dev get name name
+ f_dprintf "f_media_get_directory: dev=[%s] file=[%s] probe_type=%s" \
+ "$name" "$file" "$probe_type"
+
+ $dev get private path
+ f_media_generic_get "$path" "$file" "$probe_type"
+}
+
+# f_media_shutdown_directory $device
+#
+# Shuts down the Directory device. Return status should be ignored.
+#
+f_media_shutdown_directory()
+{
+ DIRECTORY_CHECKED=
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." media/directory.subr
+
+fi # ! $_MEDIA_DIRECTORY_SUBR
diff --git a/usr.sbin/bsdconfig/share/media/dos.subr b/usr.sbin/bsdconfig/share/media/dos.subr
new file mode 100644
index 0000000..df91aeb
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/media/dos.subr
@@ -0,0 +1,165 @@
+if [ ! "$_MEDIA_DOS_SUBR" ]; then _MEDIA_DOS_SUBR=1
+#
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." media/dos.subr
+f_include $BSDCFG_SHARE/device.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/media/common.subr
+f_include $BSDCFG_SHARE/struct.subr
+f_include $BSDCFG_SHARE/variable.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+
+############################################################ GLOBALS
+
+DOS_MOUNTED=
+
+############################################################ FUNCTIONS
+
+# f_media_set_dos
+#
+# Return success if we both found and set the media type to be a DOS partition.
+#
+f_media_set_dos()
+{
+ f_media_close
+
+ local devs ndevs
+ f_device_find "" $DEVICE_TYPE_DOS devs
+ f_count ndevs $devs
+
+ if [ ${ndevs:=0} -eq 0 ]; then
+ f_show_msg "$msg_no_dos_primary_partitions_found"
+ return $FAILURE
+ elif [ $ndevs -eq 1 ]; then
+ f_struct_copy $devs device_media
+ else
+ local dev
+ local title="$msg_choose_a_dos_partition"
+ local prompt="$msg_please_select_dos_partition"
+ local hline=
+
+ dev=$( f_device_menu \
+ "$title" "$prompt" "$hline" $DEVICE_TYPE_DOS \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD ) ||
+ return $FAILURE
+
+ f_struct_copy "$dev" device_media
+ fi
+
+ f_struct device_media || return $FAILURE
+}
+
+# f_media_init_dos $device
+#
+# Initializes the DOS media device. Returns success if able to mount the DOS
+# partition device using mount_msdosfs(8).
+#
+f_media_init_dos()
+{
+ local funcname=f_media_init_dos
+ local dev="$1" devname err
+
+ $dev get devname devname || return $FAILURE
+ f_dprintf "Init routine called for DOS device. devname=[%s]" \
+ "$devname"
+
+ if [ "$DOS_MOUNTED" ]; then
+ f_dprintf "DOS device already mounted."
+ return $SUCCESS
+ fi
+
+ if [ ! -e "$MOUNTPOINT" ]; then
+ f_eval_catch $funcname mkdir 'mkdir -p "%s"' "$MOUNTPOINT" ||
+ return $FAILURE
+ fi
+
+ if ! f_eval_catch -dk err $funcname mount_msdosfs \
+ 'mount_msdosfs "%s" "%s"' "$devname" "$MOUNTPOINT"
+ then
+ err="${err#mount_msdosfs: }"; err="${err#$devname: }"
+ f_show_msg "$msg_error_mounting_device" \
+ "$devname" "$MOUNTPOINT" "$err"
+ return $FAILURE
+ fi
+ DOS_MOUNTED=1
+ return $SUCCESS
+}
+
+# f_media_get_dos $device $file [$probe_type]
+#
+# Returns data from $file on a mounted DOS partition device. Similar to cat(1).
+# If $probe_type is present and non-NULL, returns success if $file exists. If
+# $probe_type is equal to $PROBE_SIZE, prints the size of $file in bytes to
+# standard-out.
+#
+f_media_get_dos()
+{
+ local dev="$1" file="$2" probe_type="$3"
+ local name
+
+ $dev get name name
+ f_dprintf "f_media_get_dos: dev=[%s] file=[%s] probe_type=%s" \
+ "$name" "$file" "$probe_type"
+
+ f_media_generic_get "$MOUNTPOINT" "$file" "$probe_type"
+}
+
+# f_media_shutdown_dos $device
+#
+# Shuts down the DOS partition device using umount(8). Return status should be
+# ignored.
+#
+f_media_shutdown_dos()
+{
+ local funcname=f_media_shutdown_dos
+ local dev="$1" err
+
+ [ "$DOS_MOUNTED" ] || return $FAILURE
+
+ if ! f_eval_catch -dk err $funcname umount \
+ 'umount -f "%s"' "$MOUNTPOINT"
+ then
+ err="${err#umount: }"; err="${err#*: }"
+ f_show_msg "$msg_could_not_unmount_the_dos_partition" \
+ "$MOUNTPOINT" "$err"
+ else
+ DOS_MOUNTED=
+ fi
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." media/dos.subr
+
+fi # ! $_MEDIA_DOS_SUBR
diff --git a/usr.sbin/bsdconfig/share/media/floppy.subr b/usr.sbin/bsdconfig/share/media/floppy.subr
new file mode 100644
index 0000000..bf402e5
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/media/floppy.subr
@@ -0,0 +1,229 @@
+if [ ! "$_MEDIA_FLOPPY_SUBR" ]; then _MEDIA_FLOPPY_SUBR=1
+#
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." media/floppy.subr
+f_include $BSDCFG_SHARE/device.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/media/common.subr
+f_include $BSDCFG_SHARE/struct.subr
+f_include $BSDCFG_SHARE/variable.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+
+############################################################ GLOBALS
+
+FLOPPY_MOUNTED=
+FLOPPY_DISTWANTED=
+
+############################################################ FUNCTIONS
+
+# f_media_set_floppy
+#
+# Return success if we both found and set the media type to be a floppy.
+#
+f_media_set_floppy()
+{
+ f_media_close
+
+ local devs ndevs
+ f_device_find "" $DEVICE_TYPE_FLOPPY devs
+ f_count ndevs $devs
+
+ if [ ${ndevs:=0} -eq 0 ]; then
+ f_interactive && f_show_msg "$msg_no_floppy_devices_found"
+ return $FAILURE
+ elif [ $ndevs -eq 1 ]; then
+ f_struct_copy $devs device_media
+ else
+ local dev
+ local title="$msg_choose_a_floppy_drive"
+ local prompt="$msg_please_select_a_floppy_drive"
+ local hline=
+
+ dev=$( f_device_menu \
+ "$title" "$prompt" "$hline" $DEVICE_TYPE_FLOPPY \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD ) ||
+ return $FAILURE
+
+ f_struct_copy "$dev" device_media
+ fi
+
+ f_struct device_media &&
+ device_media unset private
+
+ f_struct device_media || return $FAILURE
+}
+
+# f_media_init_floppy $device
+#
+# Initializes the Floppy media device. Returns success if able to mount the
+# Floppy disk device using either mount_msdosfs(8) or mount(8) (tried in that
+# order).
+#
+f_media_init_floppy()
+{
+ local funcname=f_media_init_floppy
+ local dev="$1" devname err
+
+ $dev get devname devname || return $FAILURE
+ f_dprintf "Init floppy called for %s distribution. devname=[%s]" \
+ "${FLOPPY_DISTWANTED:-some}" "$devname"
+
+ if [ "$FLOPPY_MOUNTED" ]; then
+ f_dprintf "Floppy device already mounted."
+ return $SUCCESS
+ fi
+
+ local mp
+ $dev get private mp
+ if [ ! -e "${mp:=$MOUNTPOINT}" ] && ! f_quietly mkdir -p "$mp"; then
+ f_show_msg "$msg_unable_to_make_directory_mountpoint" \
+ "$mp" "$devname"
+ return $FAILURE
+ fi
+
+ if f_interactive; then
+ local desc
+ $dev get desc desc
+ if [ "$FLOPPY_DISTWANTED" ]; then
+ f_show_msg "$msg_please_insert_floppy_in_drive" "$desc"
+ else
+ f_show_msg "$msg_please_insert_floppy_containing" \
+ "$FLOPPY_DISTWANTED" "$desc"
+ fi
+ fi
+
+ if ! {
+ f_eval_catch -dk err $funcname mount_msdosfs \
+ 'mount_msdosfs -o ro -m 0777 -u 0 -g 0 "%s" "%s"' \
+ "$devname" "$mp" ||
+ f_eval_catch -dk err $funcname mount \
+ 'mount -o ro "%s" "%s"' "$devname" "$mp"
+ }; then
+ err="${err#mount: }"; err="${err#*: }"
+ local name
+ $dev get name name
+ f_show_msg "$msg_error_mounting_floppy_device" \
+ "$name" "$devname" "$mp" "$err"
+ return $FAILURE
+ fi
+ FLOPPY_MOUNTED=1
+ FLOPPY_DISTWANTED=
+ return $SUCCESS
+}
+
+# f_media_get_floppy $device $file [$probe_type]
+#
+# Returns data from $file on a mounted Floppy disk device. Similar to cat(1).
+# If $probe_type is present and non-NULL, limits retries to zero and returns
+# success if $file exists. If $probe_type is equal to $PROBE_SIZE, prints the
+# size of $file in bytes to standard-out.
+#
+f_media_get_floppy()
+{
+ local funcname=f_media_get_floppy
+ local dev="$1" file="$2" probe_type="$3"
+ local name
+
+ $dev get name name
+ f_dprintf "f_media_get_floppy: dev=[%s] file=[%s] probe_type=%s" \
+ "$name" "$file" "$probe_type"
+
+ #
+ # floppies don't use f_media_generic_get() because it's too expensive
+ # to speculatively open files on a floppy disk. Make user get it
+ # right or give up with floppies.
+ #
+ local mp
+ $dev get private mp
+ local fp="${mp:=$MOUNTPOINT}/$file"
+ if ! [ -f "$fp" -a -r "$fp" ]; then
+ local nretries=4
+ [ "$probe_type" = "$PROBE_SIZE" ] && echo "-1"
+ [ "$probe_type" ] && return $FAILURE
+ while ! [ -f "$fp" -a -r "$fp" ]; do
+ if [ $nretries -eq 0 ]; then
+ f_show_msg "$msg_failed_to_get_floppy_file" \
+ "$fp"
+ [ "$probe_type" = "$PROBE_SIZE" ] && echo "-1"
+ return $FAILURE
+ fi
+ FLOPPY_DISTWANTED="$fp"
+ f_media_shutdown_floppy "$dev"
+ f_media_init_floppy "$dev" || return $FAILURE
+ nretries=$(( $nretries - 1 ))
+ done
+ fi
+ #
+ # If we reach here, $file exists
+ #
+ if [ "$probe_type" = "$PROBE_SIZE" ]; then
+ local size
+ f_eval_catch -dk size $funcname stat 'stat -f %%z "%s"' "$fp"
+ f_isinteger "$size" || size=-1
+ echo "$size"
+ fi
+ [ "$probe_type" ] && return $SUCCESS
+ cat "$fp"
+}
+
+# f_media_shutdown_floppy $device
+#
+# Shuts down the Floppy disk device using umount(8). Return status should be
+# ignored.
+#
+f_media_shutdown_floppy()
+{
+ local funcname=f_media_shutdown_floppy
+ local dev="$1" err mp
+
+ [ "$FLOPPY_MOUNTED" ] || return $FAILURE
+
+ $dev get private mp
+ if f_eval_catch -d $funcname umount \
+ 'umount -f "%s"' "${mp:=$MOUNTPOINT}"
+ then
+ FLOPPY_MOUNTED=
+ if f_interactive && [ "$_systemState" != "fixit" ]; then
+ local desc
+ $dev get desc desc
+ f_show_msg "$msg_you_may_remove_the_floppy" "$desc"
+ fi
+ fi
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." media/floppy.subr
+
+fi # ! $_MEDIA_FLOPPY_SUBR
diff --git a/usr.sbin/bsdconfig/share/media/ftp.subr b/usr.sbin/bsdconfig/share/media/ftp.subr
new file mode 100644
index 0000000..a249a01
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/media/ftp.subr
@@ -0,0 +1,911 @@
+if [ ! "$_MEDIA_FTP_SUBR" ]; then _MEDIA_FTP_SUBR=1
+#
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." media/ftp.subr
+f_include $BSDCFG_SHARE/device.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/media/common.subr
+f_include $BSDCFG_SHARE/media/tcpip.subr
+f_include $BSDCFG_SHARE/strings.subr
+f_include $BSDCFG_SHARE/struct.subr
+f_include $BSDCFG_SHARE/variable.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+
+############################################################ GLOBALS
+
+FTP_SKIP_RESOLV=
+
+URL_MAX=261261 # according to actual fetch(1) test-results
+
+FTP_DIRS="
+ .
+ releases/$UNAME_P
+ snapshots/$UNAME_P
+ pub/FreeBSD
+ pub/FreeBSD/releases/$UNAME_P
+ pub/FreeBSD/snapshots/$UNAME_P
+ pub/FreeBSD-Archive/old-releases/$UNAME_P
+" # END-QUOTE
+
+############################################################ FUNCTIONS
+
+# f_dialog_menu_media_ftp
+#
+# Prompt the user to select from a range of ``built-in'' FTP servers or specify
+# their own. If the user makes a choice and doesn't cancel or press Esc, stores
+# the user's choice in VAR_FTP_PATH (see variables.subr) and returns success.
+#
+f_dialog_menu_media_ftp()
+{
+ f_dialog_title "$msg_please_select_a_freebsd_ftp_distribution_site"
+ local title="$DIALOG_TITLE" btitle="$DIALOG_BACKTITLE"
+ f_dialog_title_restore
+ local prompt="$msg_please_select_the_site_closest_to_you_or_other"
+ local menu_list="
+ '$msg_main_site' 'ftp.freebsd.org'
+ 'URL' '$msg_specify_some_other_ftp_site'
+ 'IPv6 $msg_main_site' 'ftp.freebsd.org'
+ ' IPv6 $msg_france' 'ftp4.fr.freebsd.org'
+ ' IPv6 $msg_france #8' 'ftp8.fr.freebsd.org'
+ ' IPv6 $msg_ireland' 'ftp3.ie.freebsd.org'
+ ' IPv6 $msg_japan' 'ftp2.jp.freebsd.org'
+ ' IPv6 $msg_sweden' 'ftp4.se.freebsd.org'
+ ' IPv6 $msg_usa' 'ftp4.us.freebsd.org'
+ ' IPv6 $msg_turkey' 'ftp2.tr.freebsd.org'
+ '$msg_primary' 'ftp1.freebsd.org'
+ ' $msg_primary #2' 'ftp2.freebsd.org'
+ ' $msg_primary #3' 'ftp3.freebsd.org'
+ ' $msg_primary #4' 'ftp4.freebsd.org'
+ ' $msg_primary #5' 'ftp5.freebsd.org'
+ ' $msg_primary #6' 'ftp6.freebsd.org'
+ ' $msg_primary #7' 'ftp7.freebsd.org'
+ ' $msg_primary #10' 'ftp10.freebsd.org'
+ ' $msg_primary #11' 'ftp11.freebsd.org'
+ ' $msg_primary #12' 'ftp12.freebsd.org'
+ ' $msg_primary #13' 'ftp13.freebsd.org'
+ ' $msg_primary #14' 'ftp14.freebsd.org'
+ '$msg_armenia' 'ftp1.am.freebsd.org'
+ '$msg_australia' 'ftp.au.freebsd.org'
+ ' $msg_australia #2' 'ftp2.au.freebsd.org'
+ ' $msg_australia #3' 'ftp3.au.freebsd.org'
+ '$msg_austria' 'ftp.at.freebsd.org'
+ '$msg_brazil' 'ftp2.br.freebsd.org'
+ ' $msg_brazil #3' 'ftp3.br.freebsd.org'
+ ' $msg_brazil #4' 'ftp4.br.freebsd.org'
+ '$msg_canada' 'ftp.ca.freebsd.org'
+ '$msg_china' 'ftp.cn.freebsd.org'
+ '$msg_czech_republic' 'ftp.cz.freebsd.org'
+ '$msg_denmark' 'ftp.dk.freebsd.org'
+ '$msg_estonia' 'ftp.ee.freebsd.org'
+ '$msg_finland' 'ftp.fi.freebsd.org'
+ '$msg_france' 'ftp.fr.freebsd.org'
+ ' $msg_france #3' 'ftp3.fr.freebsd.org'
+ ' $msg_france #4' 'ftp4.fr.freebsd.org'
+ ' $msg_france #5' 'ftp5.fr.freebsd.org'
+ ' $msg_france #6' 'ftp6.fr.freebsd.org'
+ ' $msg_france #7' 'ftp7.fr.freebsd.org'
+ ' $msg_france #8' 'ftp8.fr.freebsd.org'
+ '$msg_germany' 'ftp.de.freebsd.org'
+ ' $msg_germany #2' 'ftp2.de.freebsd.org'
+ ' $msg_germany #4' 'ftp4.de.freebsd.org'
+ ' $msg_germany #5' 'ftp5.de.freebsd.org'
+ ' $msg_germany #6' 'ftp6.de.freebsd.org'
+ ' $msg_germany #7' 'ftp7.de.freebsd.org'
+ ' $msg_germany #8' 'ftp8.de.freebsd.org'
+ '$msg_greece' 'ftp.gr.freebsd.org'
+ ' $msg_greece #2' 'ftp2.gr.freebsd.org'
+ '$msg_ireland' 'ftp3.ie.freebsd.org'
+ '$msg_israel' 'ftp.il.freebsd.org'
+ '$msg_italy' 'ftp.it.freebsd.org'
+ '$msg_japan' 'ftp.jp.freebsd.org'
+ ' $msg_japan #2' 'ftp2.jp.freebsd.org'
+ ' $msg_japan #3' 'ftp3.jp.freebsd.org'
+ ' $msg_japan #4' 'ftp4.jp.freebsd.org'
+ ' $msg_japan #5' 'ftp5.jp.freebsd.org'
+ ' $msg_japan #6' 'ftp6.jp.freebsd.org'
+ ' $msg_japan #7' 'ftp7.jp.freebsd.org'
+ ' $msg_japan #8' 'ftp8.jp.freebsd.org'
+ ' $msg_japan #9' 'ftp9.jp.freebsd.org'
+ '$msg_korea' 'ftp.kr.freebsd.org'
+ ' $msg_korea #2' 'ftp2.kr.freebsd.org'
+ '$msg_latvia' 'ftp.lv.freebsd.org'
+ '$msg_lithuania' 'ftp.lt.freebsd.org'
+ '$msg_netherlands' 'ftp.nl.freebsd.org'
+ ' $msg_netherlands #2' 'ftp2.nl.freebsd.org'
+ '$msg_new_zealand' 'ftp.nz.freebsd.org'
+ '$msg_norway' 'ftp.no.freebsd.org'
+ '$msg_poland' 'ftp.pl.freebsd.org'
+ ' $msg_poland #2' 'ftp2.pl.freebsd.org'
+ '$msg_russia' 'ftp.ru.freebsd.org'
+ ' $msg_russia #2' 'ftp2.ru.freebsd.org'
+ ' $msg_russia #4' 'ftp4.ru.freebsd.org'
+ ' $msg_russia #5' 'ftp5.ru.freebsd.org'
+ ' $msg_russia #6' 'ftp6.ru.freebsd.org'
+ '$msg_slovak_republic' 'ftp.sk.freebsd.org'
+ ' $msg_slovak_republic #2' 'ftp2.sk.freebsd.org'
+ '$msg_slovenia' 'ftp.si.freebsd.org'
+ '$msg_south_africa' 'ftp.za.freebsd.org'
+ ' $msg_south_africa #2' 'ftp2.za.freebsd.org'
+ ' $msg_south_africa #4' 'ftp4.za.freebsd.org'
+ '$msg_spain' 'ftp.es.freebsd.org'
+ ' $msg_spain #3' 'ftp3.es.freebsd.org'
+ '$msg_sweden' 'ftp.se.freebsd.org'
+ ' $msg_sweden #2' 'ftp2.se.freebsd.org'
+ ' $msg_sweden #3' 'ftp3.se.freebsd.org'
+ ' $msg_sweden #4' 'ftp4.se.freebsd.org'
+ ' $msg_sweden #6' 'ftp6.se.freebsd.org'
+ '$msg_switzerland' 'ftp.ch.freebsd.org'
+ '$msg_taiwan' 'ftp.tw.freebsd.org'
+ ' $msg_taiwan #2' 'ftp2.tw.freebsd.org'
+ ' $msg_taiwan #3' 'ftp3.tw.freebsd.org'
+ ' $msg_taiwan #4' 'ftp4.tw.freebsd.org'
+ ' $msg_taiwan #6' 'ftp6.tw.freebsd.org'
+ ' $msg_taiwan #11' 'ftp11.tw.freebsd.org'
+ '$msg_uk' 'ftp.uk.freebsd.org'
+ ' $msg_uk #2' 'ftp2.uk.freebsd.org'
+ ' $msg_uk #3' 'ftp3.uk.freebsd.org'
+ ' $msg_uk #4' 'ftp4.uk.freebsd.org'
+ ' $msg_uk #5' 'ftp5.uk.freebsd.org'
+ '$msg_ukraine' 'ftp.ua.freebsd.org'
+ ' $msg_ukraine #7' 'ftp7.ua.freebsd.org'
+ '$msg_usa #1' 'ftp1.us.freebsd.org'
+ ' $msg_usa #2' 'ftp2.us.freebsd.org'
+ ' $msg_usa #3' 'ftp3.us.freebsd.org'
+ ' $msg_usa #4' 'ftp4.us.freebsd.org'
+ ' $msg_usa #5' 'ftp5.us.freebsd.org'
+ ' $msg_usa #6' 'ftp6.us.freebsd.org'
+ ' $msg_usa #8' 'ftp8.us.freebsd.org'
+ ' $msg_usa #10' 'ftp10.us.freebsd.org'
+ ' $msg_usa #11' 'ftp11.us.freebsd.org'
+ ' $msg_usa #13' 'ftp13.us.freebsd.org'
+ ' $msg_usa #14' 'ftp14.us.freebsd.org'
+ ' $msg_usa #15' 'ftp15.us.freebsd.org'
+ " # END-QUOTE
+ local hline="$msg_select_a_site_thats_close"
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$title\" \
+ \"\$btitle\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ local mtag
+ mtag=$( eval $DIALOG \
+ --title \"\$title\" \
+ --backtitle \"\$btitle\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ ) || return $DIALOG_CANCEL
+ f_dialog_data_sanitize mtag
+
+ case "$mtag" in
+ URL) setvar $VAR_FTP_PATH "other" ;;
+ *)
+ local value
+ value=$( eval f_dialog_menutag2item \"\$mtag\" $menu_list )
+ setvar $VAR_FTP_PATH "ftp://$value"
+ esac
+
+ return $DIALOG_OK
+}
+
+# f_media_set_ftp
+#
+# Return success if we both found and set the media type to be an FTP server.
+# Variables from variable.subr that can be used to script user input:
+#
+# VAR_FTP_PATH
+# Can be a URL (including "ftp://" protocol-prefix) or "other"
+# (user is prompted to enter FTP URL). If a URL, can optionally
+# contain directory prefix after hostname/port. Valid examples
+# include:
+# ftp://myhost
+# ftp://somename:21/pub/
+# ftp://192.168.2.3/pub/
+# ftp://[::1]:21/
+# The default port if not specified is 21.
+# VAR_NAMESERVER [Optional]
+# If set, overrides resolv.conf(5) and sets the nameserver that
+# is used to convert names into addresses (when a name converts
+# into multiple addresses, the first address to successfully
+# connect is used).
+#
+# Meanwhile, the following variables from variable.subr are set after
+# successful execution:
+#
+# VAR_FTP_HOST
+# The FTP host to connect to, parsed from VAR_FTP_PATH. In the
+# example case of IPv6 where VAR_FTP_PATH is "ftp://[::1]", this
+# variable will be set to "::1" (the outer brackets are removed).
+# VAR_FTP_PORT
+# The TCP port to connect to, parsed from VAR_FTP_PATH. Usually
+# 21 unless VAR_FTP_PATH was of one of the following forms:
+# ftp://hostname:OTHER_PORT
+# ftp://hostname:OTHER_PORT/*
+# ftp://ip:OTHER_PORT
+# ftp://ip:OTHER_PORT/*
+# ftp://[ip6]:OTHER_PORT
+# ftp://[ip6]:OTHER_PORT/*
+# VAR_FTP_DIR
+# If VAR_FTP_PATH contained a directory element (e.g.,
+# "ftp://localhost/pub") this variable contains only the
+# directory element (e.g., "/pub").
+#
+f_media_set_ftp()
+{
+ f_media_close
+
+ local url
+ f_getvar $VAR_FTP_PATH url
+
+ # If we've been through here before ...
+ if f_struct device_network && [ "${url#$msg_other}" ]; then
+ f_dialog_yesno "$msg_reuse_old_ftp_site_selection_values" ||
+ url=
+ fi
+
+ if [ ! "$url" ]; then
+ f_dialog_menu_media_ftp || return $FAILURE
+ f_getvar $VAR_FTP_PATH url
+ fi
+ [ "$url" ] || return $FAILURE
+
+ case "$url" in
+ other)
+ setvar $VAR_FTP_PATH "ftp://"
+ f_variable_get_value $VAR_FTP_PATH \
+ "$msg_please_specify_url_of_a_freebsd_distribution"
+ f_getvar $VAR_FTP_PATH url
+ if [ ! "${url#ftp://}" ]; then
+ unset $VAR_FTP_PATH
+ return $FAILURE
+ fi
+ if [ ${#url} -gt ${URL_MAX:-261261} ]; then
+ f_show_msg "$msg_length_of_specified_url_is_too_long" \
+ ${#url} ${URL_MAX:-261261}
+ unset $VAR_FTP_PATH
+ return $FAILURE
+ fi
+ case "$url" in
+ ftp://*) : valid URL ;;
+ *)
+ f_show_msg "$msg_sorry_invalid_url" "$url"
+ unset $VAR_FTP_PATH
+ return $FAILURE
+ esac
+ esac
+ case "$url" in
+ ftp://*) : valid URL ;;
+ *)
+ f_show_msg "$msg_sorry_invalid_url" "$url"
+ unset $VAR_FTP_PATH
+ return $FAILURE
+ esac
+
+ # Set the name of the FTP device to the URL
+ f_struct_new DEVICE device_ftp
+ device_ftp set name "$url"
+
+ if ! f_struct device_network ||
+ ! f_dialog_yesno "$msg_youve_already_done_the_network_configuration"
+ then
+ f_struct device_network &&
+ f_device_shutdown device_network
+ if ! f_device_select_tcp; then
+ unset $VAR_FTP_PATH
+ return $FAILURE
+ fi
+ local dev if
+ f_getvar $VAR_NETWORK_DEVICE if
+ f_device_find -1 "$if" $DEVICE_TYPE_NETWORK dev
+ f_struct_copy "$dev" device_network
+ fi
+ if ! f_device_init device_network; then
+ f_dprintf "f_media_set_ftp: %s" "$msg_net_device_init_failed"
+ unset $VAR_FTP_PATH
+ return $FAILURE
+ fi
+
+ local hostname="${url#*://}" port=21 dir=/
+ case "$hostname" in
+ #
+ # The order in-which the below individual cases appear is important!
+ #
+ "["*"]":*/*) # IPv6 address with port and directory
+ f_dprintf "Looks like an IPv6 addr with port/dir: %s" \
+ "$hostname"
+ hostname="${hostname#\[}"
+ port="${hostname#*\]:}"
+ port="${port%%[!0-9]*}"
+ dir="/${hostname#*/}"
+ hostname="${hostname%%\]:*}"
+ ;;
+ "["*"]":*) # IPv6 address with port
+ f_dprintf "Looks like an IPv6 addr with port: %s" "$hostname"
+ hostname="${hostname#\[}"
+ port="${hostname#*\]:}"
+ port="${port%%[!0-9]*}"
+ hostname="${hostname%%\]:*}"
+ ;;
+ "["*"]"/*) # IPv6 address with directory
+ f_dprintf "Looks like an IPv6 addr with dir: %s" "$hostname"
+ hostname="${hostname#\[}"
+ dir="/${hostname#*/}"
+ hostname="${hostname%%\]*}"
+ ;;
+ "["*"]") # IPv6 address
+ f_dprintf "Looks like an IPv6 addr: %s" "$hostname"
+ hostname="${hostname#\[}"
+ hostname="${hostname%\]}"
+ ;;
+ #
+ # ^^^ IPv6 above / DNS Name or IPv4 below vvv
+ #
+ *:*/*) # DNS name or IPv4 address with port and directory
+ f_dprintf "Looks like a %s with port/dir: %s" \
+ "DNS name or IPv4 addr" "$hostname"
+ port="${hostname#*:}"
+ port="${port%%[!0-9]*}"
+ dir="/${hostname#*/}"
+ hostname="${hostname%%:*}"
+ ;;
+ *:*) # DNS name or IPv4 address with port
+ f_dprintf "Looks like a DNS name or IPv4 addr with port: %s" \
+ "$hostname"
+ port="${hostname#*:}"
+ hostname="${hostname%%:*}"
+ ;;
+ */*) # DNS name or IPv4 address with directory
+ f_dprintf "Looks like a DNS name or IPv4 addr with dir: %s" \
+ "$hostname"
+ dir="/${hostname#*/}"
+ hostname="${hostname%%/*}"
+ ;;
+ *) # DNS name or IPv4 address
+ f_dprintf "Looks like a DNS name or IPv4 addr: %s" "$hostname"
+ : leave hostname as-is
+ esac
+
+ f_dprintf "hostname = \`%s'" "$hostname"
+ f_dprintf "dir = \`%s'" "$dir"
+ f_dprintf "port \# = \`%d'" "$port"
+
+ local ns
+ f_getvar $VAR_NAMESERVER ns
+ [ "$ns" ] || f_resolv_conf_nameservers ns
+ if [ "$ns" -a ! "$FTP_SKIP_RESOLV" ] && ! {
+ f_validate_ipaddr "$hostname" ||
+ f_validate_ipaddr6 "$hostname"
+ }; then
+ f_show_info "$msg_looking_up_host" "$hostname"
+ f_dprintf "%s: Looking up hostname, %s, using host(1)" \
+ "f_media_set_ftp" "$hostname"
+ if ! f_quietly f_host_lookup "$hostname"; then
+ f_show_msg "$msg_cannot_resolve_hostname" "$hostname"
+ f_struct device_network &&
+ f_device_shutdown device_network
+ f_struct_free device_network
+ unset $VAR_FTP_PATH
+ return $FAILURE
+ fi
+ f_dprintf "Found DNS entry for %s successfully." "$hostname"
+ fi
+
+ setvar $VAR_FTP_HOST "$hostname"
+ setvar $VAR_FTP_PORT "$port"
+ setvar $VAR_FTP_DIR "$dir"
+
+ device_ftp set type $DEVICE_TYPE_FTP
+ device_ftp set init f_media_init_ftp
+ device_ftp set get f_media_get_ftp
+ device_ftp set shutdown f_media_shutdown_ftp
+ device_ftp set private device_network
+ f_struct_copy device_ftp device_media
+ f_struct_free device_ftp
+
+ return $SUCCESS
+}
+
+# f_media_set_ftp_active
+#
+# Wrapper to f_media_set_ftp to access FTP servers actively.
+#
+f_media_set_ftp_active()
+{
+ setvar $VAR_FTP_STATE "active"
+ f_media_set_ftp
+}
+
+# f_media_set_ftp_passive
+#
+# Wrapper to f_media_set_ftp to access FTP servers passively.
+#
+f_media_set_ftp_passive()
+{
+ setvar $VAR_FTP_STATE "passive"
+ f_media_set_ftp
+}
+
+# f_media_set_ftp_userpass
+#
+# Prompt the user to enter/confirm the username/password variables that will
+# be used to communicate with the FTP servers. Returns success if the user does
+# not cancel or press Esc to either username or password.
+#
+# Variables from variable.subr that can be used to script user input:
+#
+# VAR_FTP_USER
+# The username to send via ftp(1) when connecting to an FTP
+# server.
+# VAR_FTP_PASS
+# The password to send with the above username.
+#
+# Does not prompt for confirmation of values if VAR_NONINTERACTIVE is set (see
+# variable.subr for more information).
+#
+f_media_set_ftp_userpass()
+{
+ local user pass
+ f_variable_get_value $VAR_FTP_USER \
+ "$msg_please_enter_the_username_you_wish_to_login_as"
+ f_getvar $VAR_FTP_USER user
+ if [ "$user" ]; then
+ f_variable_get_value $VAR_FTP_PASS \
+ "$msg_please_enter_the_password_for_this_user"
+ f_getvar $VAR_FTP_PASS pass
+ else
+ pass=
+ fi
+ [ "$pass" ] # Return status
+}
+
+# f_device_network_up $device
+#
+# Brings up attached network device, if any - takes FTP device as arg.
+#
+f_device_network_up()
+{
+ local dev="$1" netDev
+ f_struct "$dev" || return $FAILURE
+ $dev get private netDev || return $SUCCESS # No net == happy net
+debug=1 f_dprintf "netDev=[$netDev]"
+ f_device_init $netDev
+}
+
+# f_device_network_down $device
+#
+# Brings down attached network device, if any - takes FTP device as arg.
+#
+f_device_network_down()
+{
+ local dev="$1" netDev
+ f_struct "$dev" || return $FAILURE
+ $dev get private netDev || return $SUCCESS
+ f_device_shutdown $netDev
+}
+
+# f_media_init_ftp $device
+#
+# Initializes the FTP media device. Returns success if both able to log into
+# the FTP server and confirm the existence of at least one known release path
+# using ftp(1).
+#
+# Variables from variable.subr used to initialize the connection are as follows
+# (all of which are configured by f_media_set_ftp above):
+#
+# VAR_FTP_PATH
+# The unparsed FTP URL representing the server to contact.
+# Usually "ftp://server" for example. Can contain TCP port number
+# and/or directory path (but should not contain username/password
+# info).
+# VAR_FTP_HOST
+# The FTP host to connect to. Can be an IPv4 address (e.g.,
+# 127.0.0.1), IPv6 address (e.g., ::1), or DNS hostname. Usually
+# set automatically in f_media_set_ftp() by parsing VAR_FTP_PATH.
+# VAR_FTP_PORT
+# The TCP port to connect to. Usually set automatically in
+# f_media_set_ftp() by parsing VAR_FTP_PATH.
+# VAR_FTP_DIR
+# The base FTP directory to use when downloading files from the
+# FTP server. Usually set automatically in f_media_set_ftp() by
+# parsing VAR_FTP_PATH.
+# VAR_FTP_USER [Optional]
+# If unset, defaults to using anonymous access.
+# VAR_FTP_PASS [Optional]
+# If unset, defaults to a sensible value.
+#
+# In addition, the following (managed either manually or by f_media_set_ftp_*):
+#
+# VAR_FTP_STATE
+# Sets FTPMODE for ftp(1) and can be one of:
+# active active mode FTP only
+# auto automatic determination of passive or active
+# (this is the default)
+# gate gate-ftp mode
+# passive passive mode FTP only
+# See ftp(1) for additional information.
+#
+# And last, but not least (managed automatically or manually):
+#
+# VAR_RELNAME
+# Defaults to being set to $(uname -r) but can be overridden.
+# This sets the name of a release to look for as part of a well
+# known set of paths to search for release data once connected
+# via FTP. If set to "__RELEASE" or "any" then the VAR_FTP_DIR is
+# taken as the absolute path to the release and no further
+# searching is done (see FTP_DIRS above in the GLOBALS section
+# for a list of well known paths that are used when searching for
+# a VAR_RELNAME sub-directory).
+#
+f_media_init_ftp()
+{
+ local dev="$1"
+ local url
+
+ $dev get name url
+ f_dprintf "Init routine called for FTP device. url=[%s]" "$url"
+
+ if [ "$FTP_INITIALIZED" ]; then
+ f_dprintf "FTP device already initialized."
+ return $SUCCESS
+ fi
+
+ # If we can't initialize the network, bag it!
+ f_device_network_up $dev || return $FAILURE
+
+ local cp
+ while :; do
+ f_getvar $VAR_FTP_PATH cp
+ if [ ! "$cp" ]; then
+ if ! f_media_set_ftp ||
+ ! f_getvar $VAR_FTP_PATH cp ||
+ [ ! "$cp" ]
+ then
+ f_show_msg "$msg_unable_to_get_proper_ftp_path"
+ f_device_network_down $dev
+ return $FAILURE
+ fi
+ fi
+
+ local ftp_host ftp_dir
+ if ! {
+ f_getvar $VAR_FTP_HOST ftp_host &&
+ f_getvar $VAR_FTP_DIR ftp_dir
+ }; then
+ f_show_msg "$msg_missing_ftp_host_or_directory"
+ f_device_network_down $dev
+ return $FAILURE
+ fi
+
+ local ftp_port
+ f_getvar $VAR_FTP_PORT ftp_port
+ local host="$ftp_host" port="${ftp_port:+:$ftp_port}"
+ case "$host" in *:*) host="[$host]"; esac
+
+ local user pass use_anon=
+ f_getvar $VAR_FTP_USER user
+ if [ ! "$user" ]; then
+ user="anonymous"
+ use_anon=1
+ fi
+ if ! f_getvar $VAR_FTP_PASS pass; then
+ f_getvar $VAR_HOSTNAME cp
+ if f_running_as_init; then
+ pass="installer@$cp"
+ else
+ local name="$( id -un 2> /dev/null )"
+ pass="${name:-ftp}@$cp"
+ fi
+ fi
+
+ f_show_info "$msg_logging_in_to_user_at_host" \
+ "$user" "$ftp_host"
+
+ local userpass=""
+ if [ ! "$use_anon" ] && [ "$user" -o "$pass" ]; then
+ userpass="$user${pass:+:$( f_uriencode "$pass" )}"
+ userpass="$userpass${userpass:+@}"
+ fi
+
+ local mode rx
+ f_getvar $VAR_FTP_STATE mode
+
+ if [ "$ftp_dir" ]; then
+ if ! rx=$(
+ printf 'cd "%s"\npwd\n' "$ftp_dir" | eval \
+ FTPMODE=\"\$mode\" \
+ ${use_anon:+FTPANONPASS=\"\$pass\"} \
+ ftp -V ${use_anon:+-a} \
+ \"ftp://\$userpass\$host\$port\" \
+ 2>&1
+ ); then
+ f_show_msg "$msg_couldnt_open_ftp_connection" \
+ "$ftp_host" "$rx"
+ break # to failure
+ fi
+ if echo "$rx" | awk -v dir="/${ftp_dir#/}" '
+ BEGIN {
+ found = 0
+ if ( dir != "/" ) sub("/$", "", dir)
+ }
+ /^Remote directory: / {
+ sub(/^[^:]*:[[:space:]]*/, "")
+ if ($0 != dir) next
+ found = 1; exit
+ }
+ END { exit ! found }
+ '; then
+ setvar $VAR_FTP_DIR "$ftp_dir"
+ setvar $VAR_FTP_PATH \
+ "ftp://$ftp_host/${ftp_dir#/}"
+ else
+ f_show_msg \
+ "$msg_please_check_the_url_and_try_again" \
+ "ftp://$ftp_host/${ftp_dir#/}"
+ break # to failure
+ fi
+ fi
+
+ #
+ # Now that we've verified that the path we're given is ok,
+ # let's try to be a bit intelligent in locating the release we
+ # are looking for. First off, if the release is specified as
+ # "__RELEASE" or "any", then just assume that the current
+ # directory is the one we want and give up.
+ #
+ local rel
+ f_getvar $VAR_RELNAME rel
+ f_dprintf "f_media_init_ftp: rel=[%s]" "$rel"
+
+ case "$rel" in
+ __RELEASE|any)
+ FTP_INITIALIZED=YES
+ return $SUCCESS
+ ;;
+ *)
+ #
+ # Ok, since we have a release variable, let's walk
+ # through the list of directories looking for a release
+ # directory. First successful CWD wins.
+ #
+ if ! rx=$(
+ for dir in $FTP_DIRS; do
+ # Avoid confusing some servers
+ [ "$dir" = "." ] && continue
+ printf 'cd "/%s/%s"\npwd\n' \
+ "$dir" "$rel"
+ done | eval \
+ FTPMODE=\"\$mode\" \
+ ${use_anon:+FTPANONPASS=\"\$pass\"} \
+ ftp -V ${use_anon:+-a} \
+ \"ftp://\$userpass\$host\$port\" \
+ 2>&1
+ ); then
+ f_show_msg "$msg_couldnt_open_ftp_connection" \
+ "$ftp_host" "$rx"
+ break # to failure
+ fi
+
+ local fdir
+ if fdir=$( echo "$rx" | awk '
+ /^Remote directory: / {
+ sub(/^[^:]*:[[:space:]]*/, "")
+ if ($0 == "/") next
+ # Exit after the first dir
+ found++; print; exit
+ }
+ END { exit ! found }
+ ' ); then
+ setvar $VAR_FTP_DIR "$fdir"
+ setvar $VAR_FTP_PATH "ftp://$ftp_host$fdir"
+ FTP_INITIALIZED=YES
+ return $SUCCESS
+ else
+ f_yesno "$msg_cant_find_distribution" \
+ "$rel" "$ftp_host"
+ if [ $? -eq $DIALOG_OK ]; then
+ unset $VAR_FTP_PATH
+ f_media_set_ftp && continue
+ fi
+ fi
+ esac
+ break # to failure
+ done
+
+ unset FTP_INITIALIZED $VAR_FTP_PATH
+ f_device_network_down $dev
+ return $FAILURE
+}
+
+# f_media_get_ftp $device $file [$probe_type]
+#
+# Returns data from $file on an FTP server using ftp(1). Please note that
+# $device is unused but must be present (even if null). Information is instead
+# gathered from the environment. If $probe_type is present and non-NULL,
+# returns success if $file exists. If $probe_type is equal to $PROBE_SIZE,
+# prints the size of $file in bytes to standard-out.
+#
+# Variables from variable.subr used to configure the connection are as follows
+# (all of which are configured by f_media_set_ftp above):
+#
+# VAR_FTP_HOST
+# FTP host to connect to. Can be an IPv4 address, IPv6 address,
+# or DNS hostname of your choice.
+# VAR_FTP_PORT
+# TCP port to connect on; see f_media_set_ftp() above.
+# VAR_FTP_USER [Optional]
+# If unset, defaults to using anonymous access.
+# VAR_FTP_PASS [Optional]
+# If unset, defaults to a sensible value.
+#
+# In addition, the following (managed either manually or by f_media_set_ftp_*):
+#
+# VAR_FTP_STATE
+# Sets FTPMODE for ftp(1) and can be one of:
+# active active mode FTP only
+# auto automatic determination of passive or active
+# (this is the default)
+# gate gate-ftp mode
+# passive passive mode FTP only
+# See ftp(1) for additional information.
+#
+# See variable.subr for additional information.
+#
+# Example usage:
+# f_media_set_ftp
+# f_media_get_ftp media $file
+#
+f_media_get_ftp()
+{
+ local funcname=f_media_get_ftp
+ local dev="$1" file="$2" probe_type="$3" hosts=
+
+ f_dprintf "f_media_get_ftp: dev=[%s] file=[%s] probe_type=%s" \
+ "$dev" "$file" "$probe_type"
+
+ local ftp_host ftp_port
+ f_getvar $VAR_FTP_HOST ftp_host
+ f_getvar $VAR_FTP_PORT ftp_port
+
+ if [ ! "$FTP_INITIALIZED" ]; then
+ f_dprintf "No FTP connection open, can't get file %s" "$file"
+ return $FAILURE
+ fi
+
+ if ! {
+ f_validate_ipaddr "$ftp_host" ||
+ f_validate_ipaddr6 "$ftp_host" ||
+ {
+ f_dprintf "%s: Looking up hostname, %s, using host(1)" \
+ "f_media_get_ftp" "$ftp_host"
+ f_host_lookup "$ftp_host" hosts
+ }
+ }; then
+ # All the above validations failed
+ [ "$hosts" ] && f_dialog_msgbox "$hosts"
+ return $FAILURE
+ elif [ ! "$hosts" ]; then
+ # One of the first two validations passed
+ hosts="$ftp_host"
+ fi
+
+ local host connected=
+ for host in $hosts; do
+ f_quietly nc -nz "$host" "$ftp_port" || continue
+ connected=1; break
+ done
+ if [ ! "$connected" ]; then
+ f_show_msg "$msg_couldnt_connect_to_ftp_server %s:%s" \
+ "$ftp_host" "$ftp_port"
+ return $FAILURE
+ fi
+
+ local user pass use_anon=
+ f_getvar $VAR_FTP_USER user
+ if [ ! "$user" ]; then
+ user="anonymous"
+ use_anon=1
+ fi
+ if ! f_getvar $VAR_FTP_PASS pass; then
+ f_getvar $VAR_HOSTNAME cp
+ if f_running_as_init; then
+ pass="installer@$cp"
+ else
+ local name="$( id -un 2> /dev/null )"
+ pass="${name:-ftp}@$cp"
+ fi
+ fi
+
+ local userpass=""
+ if [ ! "$use_anon" ] && [ "$user" -o "$pass" ]; then
+ userpass="$user${pass:+:$( f_uriencode "$pass" )}"
+ userpass="$userpass${userpass:+@}"
+ fi
+
+ local dir mode rx
+ f_getvar $VAR_FTP_DIR\#/ dir
+ f_getvar $VAR_FTP_STATE mode
+
+ local port="${ftp_port:+:$ftp_port}"
+ case "$host" in *:*) host="[$host]"; esac
+
+ f_dprintf "sending ftp request for: %s" "ftp://$host$port/$dir/$file"
+
+ if [ "$probe_type" ]; then
+ local url="ftp://$userpass$host$port/$dir/$file" size
+ [ "$use_anon" ] && url="ftp://$host$port/$dir/$file"
+ if ! f_eval_catch -dk size $funcname fetch \
+ 'fetch -s "%s"' "$url" || ! f_isinteger "$size"
+ then
+ f_dprintf "size request failed!"
+ [ "$probe_type" = "$PROBE_SIZE" ] && echo "-1"
+ return $FAILURE
+ fi
+ [ "$probe_type" = "$PROBE_SIZE" ] && echo "$size"
+ return $SUCCESS
+ fi
+
+ eval FTPMODE=\"\$mode\" ${use_anon:+FTPANONPASS=\"\$pass\"} \
+ ftp -V ${use_anon:+-a} -o - \
+ \"ftp://\$userpass\$host\$port/\$dir/\$file\" 2> /dev/null
+ local retval=$?
+
+ [ $retval -eq $SUCCESS ] || f_dprintf "request failed!"
+ return $retval
+}
+
+# f_media_shutdown_ftp $device
+#
+# Shuts down the FTP device. Return status should be ignored. Note that since
+# we don't maintain an open connection to the FTP server there's nothing to do.
+#
+f_media_shutdown_ftp()
+{
+ [ "$FTP_INITIALIZED" ] || return $SUCCESS
+
+ unset FTP_INITIALIZED
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." media/ftp.subr
+
+fi # ! $_MEDIA_FTP_SUBR
diff --git a/usr.sbin/bsdconfig/share/media/http.subr b/usr.sbin/bsdconfig/share/media/http.subr
new file mode 100644
index 0000000..b928f7d
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/media/http.subr
@@ -0,0 +1,688 @@
+if [ ! "$_MEDIA_HTTP_SUBR" ]; then _MEDIA_HTTP_SUBR=1
+#
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." media/http.subr
+f_include $BSDCFG_SHARE/device.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/media/common.subr
+f_include $BSDCFG_SHARE/media/tcpip.subr
+f_include $BSDCFG_SHARE/strings.subr
+f_include $BSDCFG_SHARE/struct.subr
+f_include $BSDCFG_SHARE/variable.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+
+############################################################ GLOBALS
+
+HTTP_SKIP_RESOLV=
+
+URL_MAX=261261
+ # NOTE: This is according to actual fetch(1) test-results. We actually
+ # use nc(1) to retrieve files, but it's still a good idea to keep the
+ # URLs short enough that fetch(1) won't complain.
+
+HTTP_DIRS="
+ .
+ releases/$UNAME_P
+ snapshots/$UNAME_P
+ pub/FreeBSD
+ pub/FreeBSD/releases/$UNAME_P
+ pub/FreeBSD/snapshots/$UNAME_P
+ pub/FreeBSD-Archive/old-releases/$UNAME_P
+" # END-QUOTE
+
+############################################################ FUNCTIONS
+
+# f_dialog_menu_media_http
+#
+# Prompt the user to select from a range of ``built-in'' HTTP servers or
+# specify their own. If the user makes a choice and doesn't cancel or press
+# Esc, stores the user's choice in VAR_FTP_PATH (see variable.subr) and returns
+# success.
+#
+f_dialog_menu_media_http()
+{
+ f_dialog_title "$msg_please_select_a_freebsd_http_distribution_site"
+ local title="$DIALOG_TITLE" btitle="$DIALOG_BACKTITLE"
+ f_dialog_title_restore
+ local prompt="$msg_please_select_the_site_closest_to_you_or_other"
+ local menu_list="
+ 'dist $msg_main_site' 'ftp.freebsd.org'
+ 'pkg $msg_main_site' 'pkg.freebsd.org'
+ 'URL' '$msg_specify_some_other_http_site'
+ " # END-QUOTE
+ local hline="$msg_select_a_site_thats_close"
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$title\" \
+ \"\$btitle\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ local mtag
+ mtag=$( eval $DIALOG \
+ --title \"\$title\" \
+ --backtitle \"\$btitle\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ ) || return $DIALOG_CANCEL
+ f_dialog_data_sanitize mtag
+
+ case "$mtag" in
+ URL) setvar $VAR_HTTP_PATH "other" ;;
+ *)
+ local value
+ value=$( eval f_dialog_menutag2item \"\$mtag\" $menu_list )
+ setvar $VAR_HTTP_PATH "http://$value"
+ esac
+
+ return $DIALOG_OK
+}
+
+# f_media_set_http
+#
+# Return success if we both found and set the media type to be an HTTP server.
+#
+# Variables from variable.subr that can be used to script user input:
+#
+# VAR_HTTP_PATH
+# URL containing host and optionally a target path to the release
+# repository on the HTTP server. Valid examples include:
+# http://myhost
+# http://somename:80/pub/
+# http://192.168.2.3/pub/
+# http://[::1]:8000/
+# The default port if not specified is 80.
+# VAR_NAMESERVER [Optional]
+# If set, overrides resolv.conf(5) and sets the nameserver that
+# is used to convert names into addresses (when a name converts
+# into multiple addresses, the first address to successfully
+# connect is used).
+#
+# Meanwhile, the following variables from variable.subr are set after
+# successful execution:
+#
+# VAR_HTTP_HOST
+# The HTTP host to connect to, parsed from VAR_HTTP_PATH. In the
+# example case of IPv6 where VAR_HTTP_PATH is "http://[::1]" this
+# variable will be set to "::1" (the outer brackets are removed).
+# VAR_HTTP_PORT
+# The TCP port to connect to, parsed from VAR_HTTP_PATH. Usually
+# 80 unless VAR_HTTP_PATH was one of the following forms:
+# http://hostname:OTHER_PORT
+# http://hostname:OTHER_PORT/*
+# http://ip:OTHER_PORT
+# http://ip:OTHER_PORT/*
+# http://[ip6]:OTHER_PORT
+# http://[ip6]:OTHER_PORT/*
+# VAR_HTTP_DIR
+# If VAR_HTTP_PATH contained a directory element (e.g.,
+# "http://localhost/pub") this variable contains only the
+# directory element (e.g., "/pub").
+#
+f_media_set_http()
+{
+ f_media_close
+
+ local url
+ f_getvar $VAR_HTTP_PATH url
+
+ # If we've been through here before ...
+ if f_struct device_network && [ "${url#$msg_other}" ]; then
+ f_dialog_yesno "$msg_reuse_old_http_site_settings" || url=
+ fi
+
+ if [ ! "$url" ]; then
+ f_dialog_menu_media_http || return $FAILURE
+ f_getvar $VAR_HTTP_PATH url
+ fi
+ [ "$url" ] || return $FAILURE
+
+ case "$url" in
+ other)
+ setvar $VAR_HTTP_PATH "http://"
+ f_variable_get_value $VAR_HTTP_PATH \
+ "$msg_please_specify_url_of_freebsd_http_distribution"
+ f_getvar $VAR_HTTP_PATH url
+ if [ ! "${url#http://}" ]; then
+ unset $VAR_HTTP_PATH
+ return $FAILURE
+ fi
+ if [ ${#url} -gt ${URL_MAX:-261261} ]; then
+ f_show_msg "$msg_length_of_specified_url_is_too_long" \
+ ${#url} ${URL_MAX:-261261}
+ unset $VAR_HTTP_PATH
+ return $FAILURE
+ fi
+ case "$url" in
+ http://*) : valid URL ;;
+ *)
+ f_show_msg "$msg_sorry_invalid_url" "$url"
+ unset $VAR_HTTP_PATH
+ return $FAILURE
+ esac
+ esac
+ case "$url" in
+ http://*) : valid URL ;;
+ *)
+ f_show_msg "$msg_sorry_invalid_url" "$url"
+ unset $VAR_HTTP_PATH
+ return $FAILURE
+ esac
+
+ # Set the name of the HTTP device to the URL
+ f_struct_new DEVICE device_http
+ device_http set name "$url"
+
+ if ! f_struct device_network ||
+ ! f_dialog_yesno "$msg_youve_already_done_the_network_configuration"
+ then
+ f_struct device_network &&
+ f_device_shutdown device_network
+ if ! f_device_select_tcp; then
+ unset $VAR_HTTP_PATH
+ return $FAILURE
+ fi
+ local dev if
+ f_getvar $VAR_NETWORK_DEVICE if
+ f_device_find -1 "$if" $DEVICE_TYPE_NETWORK dev
+ f_struct_copy "$dev" device_network
+ fi
+ if ! f_device_init device_network; then
+ f_dprintf "f_media_set_http: %s" "$msg_net_device_init_failed"
+ unset $VAR_HTTP_PATH
+ return $FAILURE
+ fi
+
+ local hostname="${url#*://}" port=80 dir=/
+ case "$hostname" in
+ #
+ # The order in-which the below individual cases appear is important!
+ #
+ "["*"]":*/*) # IPv6 address with port and directory
+ f_dprintf "Looks like an IPv6 addr with port/dir: %s" \
+ "$hostname"
+ hostname="${hostname#\[}"
+ port="${hostname#*\]:}"
+ port="${port%%[!0-9]*}"
+ dir="/${hostname#*/}"
+ hostname="${hostname%%\]:*}"
+ ;;
+ "["*"]":*) # IPv6 address with port
+ f_dprintf "Looks like an IPv6 addr with port: %s" "$hostname"
+ hostname="${hostname#\[}"
+ port="${hostname#*\]:}"
+ port="${port%%[!0-9]*}"
+ hostname="${hostname%%\]:*}"
+ ;;
+ "["*"]"/*) # IPv6 address with directory
+ f_dprintf "Looks like an IPv6 addr with dir: %s" "$hostname"
+ hostname="${hostname#\[}"
+ dir="/${hostname#*/}"
+ hostname="${hostname%%\]*}"
+ ;;
+ "["*"]") # IPv6 address
+ f_dprintf "Looks like an IPv6 addr: %s" "$hostname"
+ hostname="${hostname#\[}"
+ hostname="${hostname%\]}"
+ ;;
+ #
+ # ^^^ IPv6 above / DNS Name or IPv4 below vvv
+ #
+ *:*/*) # DNS name or IPv4 address with port and directory
+ f_dprintf "Looks like a %s with port/dir: %s" \
+ "DNS name or IPv4 addr" "$hostname"
+ port="${hostname#*:}"
+ port="${port%%[!0-9]*}"
+ dir="/${hostname#*/}"
+ hostname="${hostname%%:*}"
+ ;;
+ *:*) # DNS name or IPv4 address with port
+ f_dprintf "Looks like a DNS name or IPv4 addr with port: %s" \
+ "$hostname"
+ port="${hostname#*:}"
+ hostname="${hostname%%:*}"
+ ;;
+ */*) # DNS name or IPv4 address with directory
+ f_dprintf "Looks like a DNS name or IPv4 addr with dir: %s" \
+ "$hostname"
+ dir="/${hostname#*/}"
+ hostname="${hostname%%/*}"
+ ;;
+ *) # DNS name or IPv4 address
+ f_dprintf "Looks like a DNS name or IPv4 addr: %s" "$hostname"
+ : leave hostname as-is
+ esac
+
+ f_dprintf "hostname = \`%s'" "$hostname"
+ f_dprintf "dir = \`%s'" "$dir"
+ f_dprintf "port \# = \`%d'" "$port"
+
+ local ns
+ f_getvar $VAR_NAMESERVER ns
+ [ "$ns" ] || f_resolv_conf_nameservers ns
+ if [ "$ns" -a ! "$HTTP_SKIP_RESOLV" ] && ! {
+ f_validate_ipaddr "$hostname" ||
+ f_validate_ipaddr6 "$hostname"
+ }; then
+ f_show_info "$msg_looking_up_host" "$hostname"
+ f_dprintf "%s: Looking up hostname, %s, using host(1)" \
+ "f_media_set_http" "$hostname"
+ if ! f_quietly f_host_lookup "$hostname"; then
+ f_show_msg "$msg_cannot_resolve_hostname" "$hostname"
+ f_struct device_network &&
+ f_device_shutdown device_network
+ f_struct_free device_network
+ unset $VAR_HTTP_PATH
+ return $FAILURE
+ fi
+ f_dprintf "Found DNS entry for %s successfully." "$hostname"
+ fi
+
+ setvar $VAR_HTTP_HOST "$hostname"
+ setvar $VAR_HTTP_PORT "$port"
+ setvar $VAR_HTTP_DIR "$dir"
+
+ device_http set type $DEVICE_TYPE_HTTP
+ device_http set init f_media_init_http
+ device_http set get f_media_get_http
+ device_http set shutdown f_media_shutdown_http
+ device_http set private device_network
+ f_struct_copy device_http device_media
+ f_struct_free device_http
+
+ return $SUCCESS
+}
+
+# f_http_check_access [$connect_only]
+#
+# Return success if able list a remote HTTP directory. If $connect_only is
+# present and non-null, then returns success if a connection can be made.
+# Variables from variable.subr that can be used to script user input:
+#
+# VAR_HTTP_HOST
+# The HTTP server host name, IPv4 address or IPv6 address.
+# Valid examples include:
+# myhost
+# 192.168.2.3
+# ::1
+# VAR_HTTP_PORT
+# The TCP port to connect to when communicating with the server.
+# VAR_HTTP_PATH
+# The HTTP path sent to the server. Unused if $connect_only is
+# present and non-NULL.
+#
+f_http_check_access()
+{
+ local connect_only="$1" hosts=
+
+ local http_host http_port
+ f_getvar $VAR_HTTP_HOST http_host
+ f_getvar $VAR_HTTP_PORT http_port
+
+ if ! {
+ f_validate_ipaddr "$http_host" ||
+ f_validate_ipaddr6 "$http_host" ||
+ {
+ f_dprintf "%s: Looking up hostname, %s, using host(1)" \
+ "f_http_check_access" "$http_host"
+ f_host_lookup "$http_host" hosts
+ }
+ }; then
+ # All the above validations failed
+ [ "$hosts" ] && f_dialog_msgbox "$hosts"
+ unset $VAR_HTTP_HOST
+ return $FAILURE
+ elif [ ! "$hosts" ]; then
+ # One of the first two validations passed
+ hosts="$http_host"
+ fi
+
+ local host connected=
+ for host in $hosts; do
+ f_quietly nc -nz "$host" "$http_port" || continue
+ connected=1; break
+ done
+ if [ ! "$connected" ]; then
+ f_show_msg "$msg_couldnt_connect_to_server http://%s:%s/" \
+ "$http_host" "$http_port"
+ unset $VAR_HTTP_HOST
+ return $FAILURE
+ fi
+ [ "$connect_only" ] && return $SUCCESS
+
+ local http_path
+ f_getvar $VAR_HTTP_PATH http_path
+ f_show_info "$msg_checking_access_to" "$http_path"
+
+ local rx
+ case "$http_path" in
+ http://*|/*) : valid request ;;
+ *) http_path="/$http_path" # full URI requests only
+ esac
+ if ! rx=$(
+ printf "GET %s/ HTTP/1.0\r\n\r\n" "${http_path%/}" |
+ nc -n "$host" "$http_port"
+ ); then
+ f_show_msg "$msg_couldnt_connect_to_server http://%s:%s/" \
+ "$http_host" "$http_port"
+ unset $VAR_HTTP_HOST
+ return $FAILURE
+ fi
+
+ local hdr
+ hdr=$( echo "$rx" | awk '/^\r$/{exit}{print}' )
+
+ local http_found=$FAILURE
+ if echo "$hdr" | awk '
+ BEGIN { found = 0 }
+ /^HTTP.... 200 / {
+ found = 1
+ exit
+ }
+ END { exit ! found }
+ '; then
+ http_found=$SUCCESS
+ fi
+
+ return $http_found
+}
+
+# f_media_init_http $device
+#
+# Initializes the HTTP media device. Returns success if able to confirm the
+# existence of at least one known HTTP server release path directly via HTTP
+# using f_http_check_access(), above.
+#
+# Variables from variable.subr that can be used to script user input:
+#
+# VAR_HTTP_HOST
+# The HTTP server to connect to. Must be set. Also see
+# f_http_check_access() for additional variables.
+# VAR_RELNAME
+# Usually set to `uname -r' but can be overridden.
+# VAR_HTTP_PATH
+# The HTTP path sent to the server. Usually set by calling
+# f_media_set_http().
+#
+# Meanwhile, after successful execution, the following variables (also from
+# variable.subr) are set:
+#
+# VAR_HTTP_PATH
+# The [possibly] adjusted VAR_HTTP_PATH that was found to contain
+# a valid FreeBSD repository.
+#
+f_media_init_http()
+{
+ local dev="$1"
+ f_dprintf "Init routine called for HTTP device. dev=[%s]" "$dev"
+
+ if [ "$HTTP_INITIALIZED" ]; then
+ f_dprintf "HTTP device already initialized."
+ return $SUCCESS
+ fi
+
+ #
+ # First verify access
+ #
+ local connect_only=1
+ f_http_check_access $connect_only
+
+ local http_host
+ f_getvar $VAR_HTTP_HOST http_host
+ while [ ! "$http_host" ]; do
+ f_media_set_http || return $FAILURE
+ f_http_check_access $connect_only
+ f_getvar $VAR_HTTP_HOST http_host
+ done
+
+ local http_path http_found=$FAILURE
+ while :; do
+ #
+ # Now that we've verified that the path we're given is ok,
+ # let's try to be a bit intelligent in locating the release we
+ # are looking for. First off, if the release is specified as
+ # "__RELEASE" or "any", then just assume that the current
+ # directory is the one we want and give up.
+ #
+ local rel
+ f_getvar $VAR_RELNAME rel
+ f_dprintf "f_media_init_http: rel=[%s]" "$rel"
+
+ case "$rel" in
+ __RELEASE|any)
+ f_getvar $VAR_HTTP_DIR $VAR_HTTP_PATH
+ f_http_check_access
+ http_found=$?
+ ;;
+ *)
+ #
+ # Ok, since we have a release variable, let's walk
+ # through the list of directories looking for a release
+ # directory. First successful path wins.
+ #
+ local fdir hp
+ f_getvar $VAR_HTTP_PATH%/ hp
+ setvar $VAR_HTTP_PATH "$hp/$PKG_ABI/latest"
+ if [ "$PKG_ABI" ] && f_http_check_access; then
+ http_found=$SUCCESS
+ setvar $VAR_HTTP_PATH "$hp"
+ else
+ for fdir in $HTTP_DIRS; do
+ setvar $VAR_HTTP_PATH "$hp/$fdir/$rel"
+ if f_http_check_access; then
+ http_found=$SUCCESS
+ break
+ fi
+ done
+ fi
+ esac
+
+ [ $http_found -eq $SUCCESS ] && HTTP_INITIALIZED=YES break
+
+ f_getvar $VAR_HTTP_PATH http_path
+ f_show_msg "$msg_please_check_the_url_and_try_again" \
+ "$http_path"
+
+ unset HTTP_INITIALIZED $VAR_HTTP_PATH
+ f_media_set_http || break
+ done
+
+ return $http_found
+}
+
+# f_media_get_http $device $file [$probe_type]
+#
+# Returns data from $file on an HTTP server using nc(1). Please note that
+# $device is unused but must be present (even if null). Information is instead
+# gathered from the environment. If $probe_type is both present and non-NULL,
+# this function exits after receiving the HTTP header response from the server
+# (if the HTTP response code is 200, success is returned; otherwise failure).
+# If $probe_type is equal to $PROBE_SIZE, prints the content-length in bytes
+# from the response (or -1 if not found) to standard-out.
+#
+# The variables used to configure the connection are as follows (all of which
+# are configured by f_media_set_http above):
+#
+# VAR_HTTP_HOST
+# HTTP server which to connect. Can be an IPv4 address, IPv6
+# address, or DNS hostname of your choice.
+# VAR_HTTP_PORT
+# TCP port to connect on; see f_media_set_http above.
+# VAR_HTTP_PATH
+# Directory prefix to use when requesting $file. Default is `/'
+# unless f_media_init_http was able to use f_http_check_access
+# to validate one of the defaults in $HTTP_DIRS (see GLOBALS at
+# the top of this file); assuming VAR_RELNAME was not set to
+# either `__RELEASE' or `any' (indicating that the global set of
+# $HTTP_DIRS should be ignored).
+#
+# See variable.subr for additional information.
+#
+# Example usage:
+# f_media_set_http
+# f_media_get_http media $file
+#
+f_media_get_http()
+{
+ local dev="$1" file="$2" probe_type="$3" hosts=
+ local name
+
+ $dev get name name
+ f_dprintf "f_media_get_http: dev=[%s] file=[%s] probe_type=%s" \
+ "$name" "$file" "$probe_type"
+
+ local http_host http_port
+ f_getvar $VAR_HTTP_HOST http_host
+ f_getvar $VAR_HTTP_PORT http_port
+
+ if [ ! "$HTTP_INITIALIZED" ]; then
+ f_dprintf "No HTTP connection open, can't get file %s" "$file"
+ return $FAILURE
+ fi
+
+ if ! {
+ f_validate_ipaddr "$http_host" ||
+ f_validate_ipaddr6 "$http_host" ||
+ {
+ f_dprintf "%s: Looking up hostname, %s, using host(1)" \
+ "f_media_get_http" "$http_host"
+ f_host_lookup "$http_host" hosts
+ }
+ }; then
+ # All the above validations failed
+ [ "$hosts" ] && f_dialog_msgbox "$hosts"
+ return $FAILURE
+ elif [ ! "$hosts" ]; then
+ # One of the first two validations passed
+ hosts="$http_host"
+ fi
+
+ local host connected=
+ for host in $hosts; do
+ f_quietly nc -nz "$host" "$http_port" || continue
+ connected=1; break
+ done
+ if [ ! "$connected" ]; then
+ f_show_msg "$msg_couldnt_connect_to_server http://%s:%s/" \
+ "$http_host" "$http_port"
+ return $FAILURE
+ fi
+
+ local http_path
+ f_getvar $VAR_HTTP_PATH%/ http_path
+ case "$http_path" in
+ http://*|/*) : valid request ;;
+ *) http_path="/$http_path" # full URI requests only
+ esac
+
+ local url="$http_path/$file" rx
+ f_dprintf "sending http request for: %s" "$url"
+ f_dprintf "using nc to connect to: %s:%s" "$host" "$http_port"
+ printf "GET %s HTTP/1.0\r\n\r\n" "$url" | nc -n "$host" "$http_port" |
+ (
+ #
+ # scan the headers of the response
+ # this is extremely quick'n dirty
+ #
+
+ rv=0 length=-1
+ while read LINE; do
+ case "$LINE" in
+ HTTP*)
+ f_dprintf "received response: %s" "$LINE"
+ set -- $LINE; rv=$2
+ f_isinteger "$rv" || rv=0
+ ;;
+ "Content-Length: "*)
+ length="${LINE% }"
+ length="${length#Content-Length: }"
+ f_dprintf "received content-length: %s" \
+ "$length"
+ ;;
+ *)
+ [ "${LINE% }" ] || break # End of headers
+ esac
+ done
+
+ [ $rv -ge 500 ] && exit 5
+ [ $rv -eq 404 ] && exit 44
+ [ $rv -ge 400 ] && exit 4
+ [ $rv -ge 300 ] && exit 3
+ [ $rv -eq 200 ] || exit $FAILURE
+
+ if [ ! "$probe_type" ]; then
+ cat # output the rest ``as-is''
+ elif [ "$probe_type" = "$PROBE_SIZE" ]; then
+ f_isinteger "$length" || length=-1
+ echo "$length"
+ fi
+ exit 200
+ )
+ local retval=$?
+ [ $retval -eq 200 ] && return $SUCCESS
+ [ "$probe_type" ] && return $FAILURE
+
+ case "$retval" in
+ 5) f_show_msg "$msg_server_error_when_requesting_url" "$url" ;;
+ 44) f_show_msg "$msg_url_was_not_found" "$url" ;;
+ 4) f_show_msg "$msg_client_error" ;;
+ *) f_show_msg "$msg_error_when_requesting_url" "$url" ;;
+ esac 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ return $FAILURE
+}
+
+# f_media_shutdown_http $device
+#
+# Shuts down the HTTP device. Return status should be ignored. Note that since
+# we don't maintain an open connection to the HTTP server, nothing to do.
+#
+f_media_shutdown_http()
+{
+ [ "$HTTP_INITIALIZED" ] || return $SUCCESS
+
+ unset HTTP_INITIALIZED
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." media/http.subr
+
+fi # ! $_MEDIA_HTTP_SUBR
diff --git a/usr.sbin/bsdconfig/share/media/httpproxy.subr b/usr.sbin/bsdconfig/share/media/httpproxy.subr
new file mode 100644
index 0000000..1ef516f
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/media/httpproxy.subr
@@ -0,0 +1,463 @@
+if [ ! "$_MEDIA_HTTPPROXY_SUBR" ]; then _MEDIA_HTTPPROXY_SUBR=1
+#
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." media/httpproxy.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/media/ftp.subr
+f_include $BSDCFG_SHARE/media/tcpip.subr
+f_include $BSDCFG_SHARE/variable.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+
+############################################################ FUNCTIONS
+
+# f_media_set_http_proxy
+#
+# Return success if we both found and set the media type to be an ftp server,
+# accessed via http proxy.
+#
+# Variables from variable.subr that can be used to script user input:
+#
+# VAR_HTTP_PROXY
+# HTTP Proxy server to use. Valid examples include:
+# myhost
+# somename:3128
+# 192.168.2.3
+# [::1]:8080
+# The default port if not specified is 3128.
+#
+# Variables from variable.subr that are set after successful execution include
+# the following:
+#
+# VAR_HTTP_PROXY_HOST The host portion of VAR_HTTP_PROXY.
+# VAR_HTTP_PROXY_PORT The TCP port parsed from VAR_HTTP_PROXY.
+#
+# See also f_media_set_ftp() for additional variables.
+#
+f_media_set_http_proxy()
+{
+ FTP_SKIP_RESOLV=1 f_media_set_ftp || return $FAILURE
+
+ f_variable_get_value $VAR_HTTP_PROXY \
+ "$msg_please_enter_the_address_of_the_http_proxy"
+
+ local proxy
+ f_getvar $VAR_HTTP_PROXY proxy
+ [ "$proxy" ] || return $FAILURE
+
+ local hostname="$proxy" port=3128
+ case "$hostname" in
+ #
+ # The order in-which the below individual cases appear is important!
+ #
+ "["*"]":*) # IPv6 address with port
+ f_dprintf "Looks like an IPv6 addr with port: %s" "$hostname"
+ hostname="${hostname#\[}"
+ port="${hostname#*\]:}"
+ port="${port%%[!0-9]*}"
+ hostname="${hostname%%\]:*}"
+ ;;
+ "["*"]") # IPv6 address
+ f_dprintf "Looks like an IPv6 addr: %s" "$hostname"
+ hostname="${hostname#\[}"
+ hostname="${hostname%\]}"
+ ;;
+ #
+ # ^^^ IPv6 above / DNS Name or IPv4 below vvv
+ #
+ *:*) # DNS name or IPv4 address with port
+ f_dprintf "Looks like a DNS name or IPv4 addr with port: %s" \
+ "$hostname"
+ port="${hostname#*:}"
+ hostname="${hostname%%:*}"
+ ;;
+ *) # DNS name or IPv4 address
+ f_dprintf "Looks like a DNS name or IPv4 addr: %s" "$hostname"
+ : leave hostname as-is
+ esac
+
+ setvar $VAR_HTTP_PROXY_HOST "$hostname"
+ setvar $VAR_HTTP_PROXY_PORT "$port"
+
+ if f_debugging; then
+ f_dprintf "VAR_FTP_PATH : %s" "$( f_getvar $VAR_FTP_PATH )"
+ f_dprintf "VAR_HTTP_PROXY_HOST, _PORT: %s:%s" \
+ "$( f_getvar $VAR_HTTP_PROXY_HOST )" \
+ "$( f_getvar $VAR_HTTP_PROXY_PORT )"
+ fi
+
+ # media device has been set by f_media_set_ftp(), overwrite partly:
+ device_media set type $DEVICE_TYPE_HTTP_PROXY
+ device_media set init f_media_init_http_proxy
+ device_media set get f_media_get_http_proxy
+ device_media unset shutdown
+
+ return $SUCCESS
+}
+
+# f_http_proxy_check_access [$connect_only]
+#
+# Return success if able list a remote FTP directory via HTTP proxy. If
+# $connect_only is present and non-null, then returns success if a connection
+# can be made. Variables from variable.subr that can be used to script user
+# input:
+#
+# VAR_HTTP_PROXY_HOST
+# The HTTP proxy server host name, IPv4 address or IPv6 address.
+# Valid examples include:
+# myhost
+# 192.168.2.3
+# ::1
+# VAR_HTTP_PROXY_PORT
+# The TCP port to connect to when communicating with the HTTP
+# proxy server.
+# VAR_HTTP_PROXY_PATH
+# The FTP URL sent to the HTTP proxy server. Unused if
+# $connect_only is present and non-NULL.
+#
+f_http_proxy_check_access()
+{
+ local connect_only="$1" hosts=
+
+ local proxy_host proxy_port
+ f_getvar $VAR_HTTP_PROXY_HOST proxy_host
+ f_getvar $VAR_HTTP_PROXY_PORT proxy_port
+
+ if ! {
+ f_validate_ipaddr "$proxy_host" ||
+ f_validate_ipaddr6 "$proxy_host" ||
+ {
+ f_dprintf "%s: Looking up hostname, %s, using host(1)" \
+ "f_http_proxy_check_access" "$proxy_host"
+ f_host_lookup "$proxy_host" hosts
+ }
+ }; then
+ # All the above validations failed
+ [ "$hosts" ] && f_dialog_msgbox "$hosts"
+ unset $VAR_HTTP_PROXY_HOST
+ return $FAILURE
+ elif [ ! "$hosts" ]; then
+ # One of the first two validations passed
+ hosts="$proxy_host"
+ fi
+
+ local host connected=
+ for host in $hosts; do
+ f_quietly nc -nz "$host" "$proxy_port" || continue
+ connected=1; break
+ done
+ if [ ! "$connected" ]; then
+ f_show_msg "$msg_couldnt_connect_to_proxy %s:%s" \
+ "$proxy_host" "$proxy_port"
+ unset $VAR_HTTP_PROXY_HOST
+ return $FAILURE
+ fi
+ [ "$connect_only" ] && return $SUCCESS
+
+ #
+ # Some proxies fetch files with certain extensions in "ascii mode"
+ # instead of "binary mode" for FTP. The FTP server then translates all
+ # LF to CRLF.
+ #
+ # You can force Squid to use binary mode by appending ";type=i" to the
+ # URL, which is what sysinstall(8) has traditionally done.
+ #
+
+ local proxy_path
+ f_getvar $VAR_HTTP_PROXY_PATH proxy_path
+ f_show_info "$msg_checking_access_to" "$proxy_path"
+
+ local rx
+ if ! rx=$(
+ printf "GET %s/ HTTP/1.0\r\n\r\n" "${proxy_path%/}" |
+ nc -n "$host" "$proxy_port"
+ ); then
+ f_show_msg "$msg_couldnt_connect_to_proxy %s:%s" \
+ "$proxy_host" "$proxy_port"
+ unset $VAR_HTTP_PROXY_HOST
+ return $FAILURE
+ fi
+
+ local hdr
+ hdr=$( echo "$rx" | awk '/^\r$/{exit}{print}' )
+
+ local http_found=$FAILURE
+ if echo "$hdr" | awk '
+ BEGIN { found = 0 }
+ /^HTTP.... 200 / {
+ found = 1
+ exit
+ }
+ END { exit ! found }
+ '; then
+ http_found=$SUCCESS
+ fi
+
+ #
+ # Scan the headers of the response
+ # this is extremely quick'n dity
+ #
+
+ unset $VAR_HTTP_FTP_MODE
+ if echo "$hdr" | awk '
+ BEGIN { found = 0 }
+ {
+ if (!match($0, /^Server: /)) next
+ found = ( substr($0, 9, 5) ~ /[Ss]quid/ )
+ }
+ END { exit ! found }
+ '; then
+ setvar $VAR_HTTP_FTP_MODE ";type=i"
+ else
+ setvar $VAR_HTTP_FTP_MODE ""
+ fi
+
+ return $http_found
+}
+
+# f_media_init_http_proxy $device
+#
+# Initializes the HTTP Proxy media device. Returns success if able to confirm
+# the existence of at least one known FTP server release path via HTTP proxy
+# using f_http_proxy_check_access(), above.
+#
+# Variables from variable.subr that can be used to script user input:
+#
+# VAR_HTTP_PROXY_HOST
+# The HTTP proxy server to connect to. Usually set by having
+# f_media_set_http_proxy() parse VAR_HTTP_PROXY. Must be set.
+# Also see f_http_proxy_check_access() for additional variables.
+# VAR_RELNAME
+# Usually set to `uname -r' but can be overridden.
+# VAR_FTP_PATH
+# The FTP URL to send to the HTTP proxy server. Usually set by
+# calling f_media_set_ftp().
+#
+# Meanwhile, after successful execution, the following variables (also from
+# variable.subr) are set:
+#
+# VAR_HTTP_PROXY_PATH
+# The [possibly] adjusted VAR_FTP_PATH that was found to contain
+# a valid FreeBSD repository.
+#
+f_media_init_http_proxy()
+{
+ local dev="$1"
+ f_dprintf "Init routine called for HTTP Proxy device. dev=[%s]" "$dev"
+
+ #
+ # First verify access
+ #
+ local connect_only=1
+ f_http_proxy_check_access $connect_only
+
+ local proxy_host
+ f_getvar $VAR_HTTP_PROXY_HOST proxy_host
+ while [ ! "$proxy_host" ]; do
+ f_media_set_http_proxy || return $FAILURE
+ f_http_proxy_check_access $connect_only
+ f_getvar $VAR_HTTP_PROXY_HOST proxy_host
+ done
+
+ local rel proxy_path http_found=$FAILURE
+ while :; do
+ #
+ # If the release is specified as "__RELEASE" or "any", then
+ # just assume that the path the user gave is ok.
+ #
+ f_getvar $VAR_RELNAME rel
+ f_dprintf "f_media_init_http_proxy: rel=[%s]" "$rel"
+
+ case "$rel" in
+ __RELEASE|any)
+ f_getvar $VAR_FTP_PATH $VAR_HTTP_PROXY_PATH
+ f_http_proxy_check_access
+ http_found=$?
+ ;;
+ *)
+ local fdir fp
+ f_getvar $VAR_FTP_PATH%/ fp
+ for fdir in $FTP_DIRS; do
+ setvar $VAR_HTTP_PROXY_PATH "$fp/$fdir/$rel"
+ if f_http_proxy_check_access; then
+ http_found=$SUCCESS
+ break
+ fi
+ done
+ esac
+
+ [ $http_found -eq $SUCCESS ] && break
+
+ f_getvar $VAR_HTTP_PROXY_PATH proxy_path
+ f_show_msg "$msg_please_check_the_url_and_try_again" \
+ "$proxy_path"
+
+ unset $VAR_HTTP_PROXY_PATH
+ f_media_set_http_proxy || break
+ done
+
+ return $http_found
+}
+
+# f_media_get_http_proxy $device $file [$probe_type]
+#
+# Returns data from $file on an FTP server via HTTP proxy using nc(1). Please
+# note that $device is unused but must be present (even if null). Information
+# is instead gathered from the environment. If $probe_type is both present and
+# non-NULL, this function exits after receiving the HTTP header response from
+# the proxy server (if the HTTP response code is 200, success is returned;
+# otherwise failure). If $probe_type is equal to $PROBE_SIZE, prints the
+# content-length in bytes from the response (or -1 if not found) to standard-
+# out.
+#
+# The variables used to configure the connection are as follows (all of which
+# are configured by f_media_set_http_proxy above):
+#
+# VAR_HTTP_PROXY_HOST
+# HTTP proxy host to connect. Can be an IPv4 address, IPv6
+# address, or DNS hostname of your choice.
+# VAR_HTTP_PROXY_PORT
+# TCP port to connect on; see f_media_set_http_proxy above.
+# VAR_HTTP_PROXY_PATH
+# URL (including "ftp://" protocol-prefix) of FTP directory to
+# use as a prefix when requesting $file via HTTP proxy.
+#
+# See variable.subr for additional information.
+#
+# Example usage:
+# f_media_set_http_proxy
+# f_media_get_http_proxy media $file
+#
+f_media_get_http_proxy()
+{
+ local dev="$1" file="$2" probe_type="$3" hosts=
+
+ f_dprintf "f_media_get_http_proxy: dev=[%s] file=[%s] probe_type=%s" \
+ "$dev" "$file" "$probe_type"
+
+ local proxy_host proxy_port
+ f_getvar $VAR_HTTP_PROXY_HOST proxy_host
+ f_getvar $VAR_HTTP_PROXY_PORT proxy_port
+
+ if ! {
+ f_validate_ipaddr "$proxy_host" ||
+ f_validate_ipaddr6 "$proxy_host" ||
+ {
+ f_dprintf "%s: Looking up hostname, %s, using host(1)" \
+ "f_media_get_http_proxy" "$proxy_host"
+ f_host_lookup "$proxy_host" hosts
+ }
+ }; then
+ # All the above validations failed
+ [ "$hosts" ] && f_dialog_msgbox "$hosts"
+ return $FAILURE
+ elif [ ! "$hosts" ]; then
+ # One of the first two validations passed
+ hosts="$proxy_host"
+ fi
+
+ local host connected=
+ for host in $hosts; do
+ f_quietly nc -nz "$host" "$proxy_port" || continue
+ connected=1; break
+ done
+ if [ ! "$connected" ]; then
+ f_show_msg "$msg_couldnt_connect_to_proxy %s:%s" \
+ "$proxy_host" "$proxy_port"
+ return $FAILURE
+ fi
+
+ local proxy_path mode
+ f_getvar $VAR_HTTP_PROXY_PATH%/ proxy_path
+ f_getvar $VAR_HTTP_FTP_MODE mode
+ local url="$proxy_path/$file$mode" rx
+
+ f_dprintf "sending http request for: %s" "$url"
+ printf "GET %s HTTP/1.0\r\n\r\n" "$url" | nc -n "$host" "$proxy_port" |
+ (
+ #
+ # scan the headers of the response
+ # this is extremely quick'n dirty
+ #
+
+ rv=0 length=-1
+ while read LINE; do
+ case "$LINE" in
+ HTTP*)
+ f_dprintf "received response: %s" "$LINE"
+ set -- $LINE; rv=$2
+ f_isinteger "$rv" || rv=0
+ ;;
+ "Content-Length: "*)
+ length="${LINE% }"
+ length="${length#Content-Length: }"
+ f_dprintf "received content-length: %s" \
+ "$length"
+ ;;
+ *)
+ [ "${LINE% }" ] || break # End of headers
+ esac
+ done
+
+ [ $rv -ge 500 ] && exit 5
+ [ $rv -eq 404 ] && exit 44
+ [ $rv -ge 400 ] && exit 4
+ [ $rv -ge 300 ] && exit 3
+ [ $rv -eq 200 ] || exit $FAILURE
+
+ if [ ! "$probe_type" ]; then
+ cat # output the rest ``as-is''
+ elif [ "$probe_type" = "$PROBE_SIZE" ]; then
+ f_isinteger "$length" || length=-1
+ echo "$length"
+ fi
+ exit 200
+ )
+ local retval=$?
+ [ $retval -eq 200 ] && return $SUCCESS
+ [ "$probe_type" ] && return $FAILURE
+
+ case "$retval" in
+ 5) f_show_msg "$msg_server_error_when_requesting_url" "$url" ;;
+ 44) f_show_msg "$msg_url_was_not_found" "$url" ;;
+ 4) f_show_msg "$msg_client_error" ;;
+ *) f_show_msg "$msg_error_when_requesting_url" "$url" ;;
+ esac 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ return $FAILURE
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." media/httpproxy.subr
+
+fi # ! $_MEDIA_HTTPPROXY_SUBR
diff --git a/usr.sbin/bsdconfig/share/media/network.subr b/usr.sbin/bsdconfig/share/media/network.subr
new file mode 100644
index 0000000..6e17200
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/media/network.subr
@@ -0,0 +1,182 @@
+if [ ! "$_MEDIA_NETWORK_SUBR" ]; then _MEDIA_NETWORK_SUBR=1
+#
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." media/network.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/media/tcpip.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+
+############################################################ GLOBALS
+
+NETWORK_INITIALIZED=
+
+############################################################ FUNCTIONS
+
+# f_media_init_network $device
+#
+# Initialize a network device (such as `fxp0', `em0', etc.). Returns success if
+# able to successfully initialize the device. If not running as init (basically
+# from the FreeBSD install media) then assume that the network has already been
+# initialized and returns success.
+#
+# The variables (from variable.subr) used to initialize the network are as
+# follows (all of which are configured either automatically or manaully):
+#
+# VAR_IFCONFIG + device_name (e.g., `ifconfig_em0')
+# Automatically populated but can be overridden in a script. This
+# defines the ifconfig(8) properties specific to a chosen network
+# interface device. Optional if VAR_IPV6ADDR is set.
+# VAR_IPV6ADDR [Optional]
+# If not running as init (and setting up RTSOL connections for
+# the interface), then must be set manually. If set, used as the
+# IPv6 configuration for the given network interface device.
+# VAR_GATEWAY [Optional]
+# If not running as init (and setting up a static connection for
+# the interface) then must be set (usually via rc.conf(5), but
+# can be set manually to override). If unset, the user is warned
+# but not prevented from proceeding (as most connections need a
+# default route but not everyone).
+#
+f_media_init_network()
+{
+ local dev="$1"
+
+ f_dprintf "Init routine called for network device \`%s'." "$dev"
+ if [ "$NETWORK_INITIALIZED" ]; then
+ f_dprintf "Network already initialized."
+ return $SUCCESS
+ elif ! f_running_as_init; then
+ f_dprintf "Not running as init -- calling the deed done."
+ NETWORK_INITIALIZED=1
+ return $SUCCESS
+ fi
+
+ if [ ! -e "$RESOLV_CONF" ]; then
+ if ! f_config_resolv; then
+ f_show_msg "$msg_cant_seem_to_write_out_resolv_conf" \
+ "$RESOLV_CONF"
+ return $FAILURE
+ fi
+ fi
+
+ local cp
+ if f_getvar $VAR_IFCONFIG$dev cp; then
+ #
+ # If this interface isn't a DHCP one, bring it up.
+ # If it is, then it's already up.
+ #
+ case "$cp" in
+ *DHCP*)
+ f_dprintf "A DHCP interface. Should already be up."
+ ;;
+ *)
+ f_dprintf "Not a DHCP interface."
+ if ! f_quietly ifconfig "$dev" $cp; then
+ f_show_msg "$msg_unable_to_configure_device" \
+ "$dev"
+ return $FAILURE
+ fi
+ local rp
+ f_getvar $VAR_GATEWAY rp
+ if [ ! "$rp" ]; then
+ f_show_msg "$msg_no_gateway_has_been_set"
+ else
+ #
+ # Explicitly flush all routes to get back to a
+ # known sane state. We don't need to check this
+ # exit code because if anything fails it will
+ # show up in the route add below.
+ #
+ f_quietly route -n flush
+ f_dprintf "Adding default route to %s." "$rp"
+ if ! f_quietly route -n add default "$rp"; then
+ f_show_msg \
+ "$msg_failed_to_add_default_route"
+ return $FAILURE
+ fi
+ fi
+ esac
+ elif ! { f_getvar $VAR_IPV6ADDR cp && [ "$cp" ]; }; then
+ f_show_msg "$msg_device_is_not_configured" "$dev"
+ return $FAILURE
+ fi
+
+ f_dprintf "Network initialized successfully."
+ NETWORK_INITIALIZED=1
+ return $SUCCESS
+}
+
+# f_media_shutdown_network $device
+#
+# Shuts down the configured network device (e.g., `fxp0', `em0', etc.) and
+# deletes the default route (if configured). Returns failure if the device
+# passed has not been configured. If not running as init (basically from the
+# FreeBSD install media) then does nothing and returns success.
+#
+f_media_shutdown_network()
+{
+ local dev="$1" cp
+
+ f_dprintf "Shutdown called for network device %s" "$dev"
+ if [ ! "$NETWORK_INITIALIZED" ]; then
+ f_dprintf "Network not initialized -- nothing to do."
+ return $SUCCESS
+ fi
+
+ unset NETWORK_INITIALIZED
+ unset $VAR_NETWORK_DEVICE
+
+ if ! f_running_as_init; then
+ f_dprintf "Not running as init -- calling the deed done."
+ return $SUCCESS
+ fi
+
+ f_getvar $VAR_IFCONFIG$dev cp || return $FAILURE
+ f_dprintf "ifconfig %s down" "$dev"
+ f_quietly ifconfig $dev down ||
+ f_show_msg "$msg_unable_to_down_the_interface_properly" "$dev"
+
+ if f_getvar $VAR_GATEWAY cp; then
+ f_dprintf "Deleting default route."
+ f_quietly route -n delete default
+ fi
+
+ return $SUCCESS
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." media/network.subr
+
+fi # ! $_MEDIA_NETWORK_SUBR
diff --git a/usr.sbin/bsdconfig/share/media/nfs.subr b/usr.sbin/bsdconfig/share/media/nfs.subr
new file mode 100644
index 0000000..d83661f
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/media/nfs.subr
@@ -0,0 +1,258 @@
+if [ ! "$_MEDIA_NFS_SUBR" ]; then _MEDIA_NFS_SUBR=1
+#
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." media/nfs.subr
+f_include $BSDCFG_SHARE/device.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/media/common.subr
+f_include $BSDCFG_SHARE/media/network.subr
+f_include $BSDCFG_SHARE/media/tcpip.subr
+f_include $BSDCFG_SHARE/struct.subr
+f_include $BSDCFG_SHARE/variable.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+
+############################################################ GLOBALS
+
+NFS_MOUNTED=
+
+############################################################ FUNCTIONS
+
+# f_media_set_nfs
+#
+# Return success if we both found and set the media type to be an NFS server.
+# Variables from variable.subr that can be used to script user input:
+#
+# VAR_NFS_PATH
+# The NFS path specification (host:path) to use when mounting the
+# remote repository.
+# VAR_NAMESERVER [Optional]
+# Automatically populated from resolv.conf(5) but can be
+# overridden. If set, the host portion of VAR_NFS_PATH is
+# looked up using f_host_lookup() from `tcpip.subr'.
+#
+# Meanwhile, the following variables from variable.subr are set after
+# successful execution:
+#
+# VAR_NFS_HOST
+# The host portion of the NFS path specification, parsed from
+# VAR_NFS_PATH.
+#
+f_media_set_nfs()
+{
+ local nfs
+
+ f_media_close
+
+ f_variable_get_value $VAR_NFS_PATH \
+ "$msg_please_enter_the_full_nfs_file_specification"
+ f_getvar $VAR_NFS_PATH nfs
+ [ "$nfs" ] || return $FAILURE
+
+ case "$nfs" in
+ *:*) : valid NFS path ;;
+ *)
+ f_show_msg "$msg_invalid_nfs_path_specification"
+ return $FAILURE
+ esac
+
+ f_struct_new DEVICE device_nfs
+ device_nfs set name "$nfs"
+
+ if ! f_struct device_network ||
+ ! f_dialog_yesno "$msg_youve_already_done_the_network_configuration"
+ then
+ f_struct device_network &&
+ f_device_shutdown device_network
+ f_device_select_tcp || return $FAILURE
+ local dev if
+ f_getvar $VAR_NETWORK_DEVICE if
+ f_device_find -1 "$if" $DEVICE_TYPE_NETWORK dev
+ f_struct_copy "$dev" device_network
+ fi
+ f_device_init device_network ||
+ f_dprintf "%s: $msg_net_device_init_failed\n" f_media_set_nfs
+
+ local hostname="${nfs%%:*}"
+ if f_isset $VAR_NAMESERVER && ! {
+ f_validate_ipaddr "$hostname" || f_validate_ipaddr6 "$hostname"
+ }; then
+ f_show_info "$msg_looking_up_host" "$hostname"
+ f_dprintf "%s Looking up hostname, %s, using host(1)" \
+ "f_media_set_nfs" "$hostname"
+ if ! f_quietly f_host_lookup "$hostname"; then
+ f_show_msg "$msg_cannot_resolve_hostname" "$hostname"
+ f_struct device_network &&
+ f_device_shutdown device_network
+ f_struct_free device_network
+ unset $VAR_NFS_PATH
+ return $FAILURE
+ fi
+ f_dprintf "Found DNS entry for %s successfully." "$hostname"
+ fi
+
+ setvar $VAR_NFS_HOST "$hostname"
+
+ device_nfs set type $DEVICE_TYPE_NFS
+ device_nfs set init f_media_init_nfs
+ device_nfs set get f_media_get_nfs
+ device_nfs set shutdown f_media_shutdown_nfs
+ device_nfs set private device_network # in name only (deref'd later)
+
+ f_struct_copy device_nfs device_media
+ f_struct_free device_nfs
+
+ return $SUCCESS
+}
+
+# f_media_init_nfs $device
+#
+# Initializes the NFS media device. Returns success if able to mount the NFS
+# device using mount_nfs(1).
+#
+# The variables (from variable.subr) used to initialize the NFS mount are as
+# follows (all of which are configured manually/optionally from the options
+# menu):
+#
+# VAR_NFS_TCP [Optional]
+# If non-NULL, adds the "tcp" option via `-o' to mount_nfs(8).
+# VAR_NFS_V3 [Optional]
+# If non-NULL, adds the "nfsv3" option via `-o' to mount_nfs(8).
+# VAR_NFS_SECURE [Optional]
+# If non-NULL, adds the "-P" flag to mount_nfs(8).
+# VAR_SLOW_ETHER [Optional]
+# If non-NULL, adjusts the read/write size to avoid timeouts.
+#
+f_media_init_nfs()
+{
+ local funcname=f_media_init_nfs
+ local dev="$1" name err
+
+ $dev get name name || return $FAILURE
+ f_dprintf "Init routine called for NFS device. name=[%s]" \
+ "$name"
+
+ if [ "$NFS_MOUNTED" ]; then
+ f_dprintf "NFS device already mounted."
+ return $SUCCESS
+ fi
+
+ if ! f_device_init device_network; then
+ f_dprintf "f_media_init_nfs: %s" "$msg_net_device_init_failed"
+ return $FAILURE
+ fi
+
+ if [ ! -e "$MOUNTPOINT" ]; then
+ f_eval_catch $funcname mkdir 'mkdir -p "%s"' "$MOUNTPOINT" ||
+ return $FAILURE
+ fi
+
+ local cp tcp="" use3="" secure="" readsize=4096 writesize=4096
+ f_getvar $VAR_NFS_TCP cp
+ [ "$cp" = "YES" ] && tcp=1
+ f_getvar $VAR_NFS_V3 cp
+ [ "$cp" = "YES" ] && use3=1
+ f_getvar $VAR_NFS_SECURE cp
+ [ "$cp" = "YES" ] && secure=1
+ f_getvar $VAR_SLOW_ETHER cp
+ [ "$cp" = "YES" ] && readsize=1024 writesize=1024
+
+ local options="rsize=$readsize,wsize=$writesize"
+ [ "$use3" ] && options="$options,nfsv3"
+ [ "$tcp" ] && options="$options,tcp"
+
+ if ! f_eval_catch -dk err $funcname mount_nfs \
+ 'mount_nfs %s -o "%s" "%s" "%s"' \
+ "${secure:+-P}" "$options" "$name" "$MOUNTPOINT"
+ then
+ err="${err#mount_nfs: }"
+ f_show_msg "$msg_error_mounting_device" \
+ "$name" "$MOUNTPOINT" "$err"
+ f_struct device_network &&
+ f_device_shutdown device_network
+ return $FAILURE
+ fi
+ NFS_MOUNTED=1
+
+ f_dprintf "Mounted NFS device %s onto %s" "$name" "$MOUNTPOINT"
+
+ return $SUCCESS
+}
+
+# f_media_get_nfs $device $file [$probe_type]
+#
+# Returns data from $file on a mounted NFS device. Similar to cat(1). If
+# $probe_type is present and non-NULL, returns success if $file exists. If
+# $probe_type is equal to $PROBE_SIZE, prints the size of $file in bytes to
+# standard-out.
+#
+f_media_get_nfs()
+{
+ local dev="$1" file="$2" probe_type="$3"
+ local name
+
+ $dev get name name
+ f_dprintf "f_media_get_nfs: dev=[%s] file=[%s] probe_type=%s" \
+ "$name" "$file" "$probe_type"
+
+ f_media_generic_get "$MOUNTPOINT" "$file" "$probe_type"
+}
+
+# f_media_shutdown_nfs $device
+#
+# Shuts down the NFS device using umount(8). Return status should be ignored.
+#
+f_media_shutdown_nfs()
+{
+ local funcname=f_media_shutdown_nfs
+ local dev="$1" err
+
+ [ "$NFS_MOUNTED" ] || return $FAILURE
+
+ f_dprintf "Unmounting NFS partition on %s" "$MOUNTPOINT"
+ if ! f_eval_catch -dk err $funcname umount \
+ 'umount -f "%s"' "$MOUNTPOINT"
+ then
+ err="${err#umount: }"; err="${err#*: }"
+ f_show_msg "$msg_could_not_unmount_the_nfs_partition" \
+ "$MOUNTPOINT" "$err"
+ else
+ NFS_MOUNTED=
+ fi
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." media/nfs.subr
+
+fi # ! $_MEDIA_NFS_SUBR
diff --git a/usr.sbin/bsdconfig/share/media/options.subr b/usr.sbin/bsdconfig/share/media/options.subr
new file mode 100644
index 0000000..bc9568e
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/media/options.subr
@@ -0,0 +1,327 @@
+if [ ! "$_MEDIA_OPTIONS_SUBR" ]; then _MEDIA_OPTIONS_SUBR=1
+#
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." media/options.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/media/any.subr
+f_include $BSDCFG_SHARE/media/ftp.subr
+f_include $BSDCFG_SHARE/struct.subr
+f_include $BSDCFG_SHARE/variable.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+
+OPTIONS_HELPFILE=$BSDCFG_LIBE/include/options.hlp
+
+############################################################ FUNCTIONS
+
+# f_media_options_menu
+#
+# Prompt the user to confirm/edit various media settings. Returns success.
+#
+f_media_options_menu()
+{
+ f_dialog_title "$msg_options_editor"
+ local title="$DIALOG_TITLE" btitle="$DIALOG_BACKTITLE"
+ f_dialog_title_restore
+ local prompt=
+ local menu_list # Calculated below
+ local defaultitem=
+ local hline="$hline_arrows_tab_enter"
+
+ #
+ # A hack so that the dialogs below are always interactive in a script
+ #
+ local old_interactive=
+ if ! f_interactive; then
+ f_getvar $VAR_NONINTERACTIVE old_interactive
+ unset $VAR_NONINTERACTIVE
+ fi
+
+ local cp
+ while :; do
+ menu_list=
+
+ f_getvar $VAR_NFS_SECURE cp
+ if [ "$cp" = "YES" ]; then menu_list="$menu_list
+ ' $msg_nfs_secure' 'YES'
+ '$msg_nfs_server_talks_only_on_a_secure_port'"
+ else menu_list="$menu_list
+ ' $msg_nfs_secure' 'NO'
+ '$msg_nfs_server_talks_only_on_a_secure_port'"
+ fi
+
+ f_getvar $VAR_SLOW_ETHER cp
+ if [ "$cp" = "YES" ]; then menu_list="$menu_list
+ ' $msg_nfs_slow' 'YES'
+ '$msg_user_is_using_a_slow_pc_or_ethernet_card'"
+ else menu_list="$menu_list
+ ' $msg_nfs_slow' 'NO'
+ '$msg_user_is_using_a_slow_pc_or_ethernet_card'"
+ fi
+
+ f_getvar $VAR_NFS_TCP cp
+ if [ "$cp" = "YES" ]; then menu_list="$menu_list
+ ' $msg_nfs_tcp' 'YES' '$msg_use_tcp_protocol_for_nfs'"
+ else menu_list="$menu_list
+ ' $msg_nfs_tcp' 'NO' '$msg_use_tcp_protocol_for_nfs'"
+ fi
+
+ f_getvar $VAR_NFS_V3 cp
+ if [ "$cp" = "YES" ]; then menu_list="$menu_list
+ ' $msg_nfs_version_3' 'YES' '$msg_use_nfs_version_3'"
+ else menu_list="$menu_list
+ ' $msg_nfs_version_3' 'NO' '$msg_use_nfs_version_3'"
+ fi
+
+ f_getvar $VAR_DEBUG cp
+ if [ "$cp" ]; then menu_list="$menu_list
+ ' $msg_debugging' 'YES'
+ '$msg_emit_extra_debugging_output'"
+ else menu_list="$menu_list
+ ' $msg_debugging' 'NO'
+ '$msg_emit_extra_debugging_output'"
+ fi
+
+ f_getvar $VAR_NO_CONFIRM cp
+ if [ "$cp" ]; then menu_list="$menu_list
+ ' $msg_yes_to_all' 'YES'
+ '$msg_assume_yes_to_all_non_critical_dialogs'"
+ else menu_list="$menu_list
+ ' $msg_yes_to_all' 'NO'
+ '$msg_assume_yes_to_all_non_critical_dialogs'"
+ fi
+
+ f_getvar $VAR_TRY_DHCP cp
+ if [ "$cp" = "YES" ]; then menu_list="$menu_list
+ ' $msg_dhcp' 'YES'
+ '$msg_attempt_automatic_dhcp_configuration'"
+ else menu_list="$menu_list
+ ' $msg_dhcp' 'NO'
+ '$msg_attempt_automatic_dhcp_configuration'"
+ fi
+
+ f_getvar $VAR_TRY_RTSOL cp
+ if [ "$cp" = "YES" ]; then menu_list="$menu_list
+ ' $msg_ipv6' 'YES'
+ '$msg_attempt_ipv6_configuration_of_interfaces'"
+ else menu_list="$menu_list
+ ' $msg_ipv6' 'NO'
+ '$msg_attempt_ipv6_configuration_of_interfaces'"
+ fi
+
+ f_getvar $VAR_FTP_USER cp
+ menu_list="$menu_list
+ ' $msg_ftp_username' '$cp'
+ '$msg_username_and_password_to_use'"
+
+ f_getvar $VAR_EDITOR cp
+ menu_list="$menu_list
+ ' $msg_editor' '$cp' '$msg_which_text_editor_to_use'"
+
+ f_getvar $VAR_RELNAME cp
+ menu_list="$menu_list
+ ' $msg_release_name' '$cp'
+ '$msg_which_release_to_attempt_to_load'"
+
+ if f_struct device_media; then
+ device_media get type cp
+ case "$cp" in
+ $DEVICE_TYPE_UFS|$DEVICE_TYPE_DISK)
+ cp="$msg_file_system" ;;
+ $DEVICE_TYPE_DIRECTORY) cp="$msg_directory" ;;
+ $DEVICE_TYPE_FLOPPY) cp="$msg_floppy" ;;
+ $DEVICE_TYPE_FTP) cp="$msg_ftp" ;;
+ $DEVICE_TYPE_HTTP_PROXY) cp="$msg_http_proxy" ;;
+ $DEVICE_TYPE_HTTP) cp="$msg_http_direct" ;;
+ $DEVICE_TYPE_CDROM) cp="$msg_cdrom" ;;
+ $DEVICE_TYPE_USB) cp="$msg_usb" ;;
+ $DEVICE_TYPE_DOS) cp="$msg_dos" ;;
+ $DEVICE_TYPE_NFS) cp="$msg_nfs" ;;
+ *)
+ cp="<$msg_unknown>"
+ esac
+ else
+ cp="<$msg_not_yet_set>"
+ fi
+ menu_list="$menu_list
+ ' $msg_media_type' '$cp'
+ '$msg_the_current_installation_media_type'"
+
+ f_getvar $VAR_MEDIA_TIMEOUT cp
+ menu_list="$menu_list
+ ' $msg_media_timeout' '$cp'
+ '$msg_timeout_value_in_seconds_for_slow_media'"
+
+ f_getvar $VAR_PKG_TMPDIR cp
+ menu_list="$menu_list
+ ' $msg_package_temp' '$cp'
+ '$msg_directory_where_package_temporary_files_go'"
+
+ menu_list="$menu_list
+ ' $msg_rescan_devices' '<*>'
+ '$msg_rerun_bsdconfig_initial_device_probe'
+ ' $msg_use_defaults' '[${msg_reset}]'
+ '$msg_reset_all_values_to_startup_defaults'
+ " # END-QUOTE
+
+ local height width rows
+ eval f_dialog_menu_with_help_size height width rows \
+ \"\$title\" \
+ \"\$btitle\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ local mtag
+ mtag=$( eval $DIALOG \
+ --title \"\$title\" \
+ --backtitle \"\$btitle\" \
+ --hline \"\$hline\" \
+ --item-help \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_done\" \
+ --help-button \
+ --help-label \"\$msg_help\" \
+ --default-item \"\$defaultitem\" \
+ ${USE_XDIALOG:+--help \"\"} \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize mtag
+ defaultitem="$mtag"
+ f_dprintf "retval=%s mtag=[%s]" $retval "$mtag"
+
+ if [ $retval -eq $DIALOG_HELP ]; then
+ f_show_help "$OPTIONS_HELPFILE"
+ continue
+ elif [ $retval -ne $DIALOG_OK ]; then
+ break # to success
+ fi
+
+ case "$mtag" in
+ " $msg_nfs_secure")
+ f_getvar $VAR_NFS_SECURE cp
+ if [ "$cp" = "YES" ]; then
+ export $VAR_NFS_SECURE="NO"
+ else
+ export $VAR_NFS_SECURE="YES"
+ fi ;;
+ " $msg_nfs_slow")
+ f_getvar $VAR_SLOW_ETHER cp
+ if [ "$cp" = "YES" ]; then
+ export $VAR_SLOW_ETHER="NO"
+ else
+ export $VAR_SLOW_ETHER="YES"
+ fi ;;
+ " $msg_nfs_tcp")
+ f_getvar $VAR_NFS_TCP cp
+ if [ "$cp" = "YES" ]; then
+ export $VAR_NFS_TCP="NO"
+ else
+ export $VAR_NFS_TCP="YES"
+ fi ;;
+ " $msg_nfs_version_3")
+ f_getvar $VAR_NFS_V3 cp
+ if [ "$cp" = "YES" ]; then
+ export $VAR_NFS_V3="NO"
+ else
+ export $VAR_NFS_V3="YES"
+ fi ;;
+ " $msg_debugging")
+ if f_getvar $VAR_DEBUG cp && [ "$cp" ]; then
+ unset $VAR_DEBUG
+ else
+ export $VAR_DEBUG=1
+ fi ;;
+ " $msg_yes_to_all")
+ if f_getvar $VAR_NO_CONFIRM cp && [ "$cp" ]; then
+ unset $VAR_NO_CONFIRM
+ else
+ export $VAR_NO_CONFIRM=1
+ fi ;;
+ " $msg_dhcp")
+ f_getvar $VAR_TRY_DHCP cp
+ if [ "$cp" = "YES" ]; then
+ export $VAR_TRY_DHCP="NO"
+ else
+ export $VAR_TRY_DHCP="YES"
+ fi ;;
+ " $msg_ipv6")
+ f_getvar $VAR_TRY_RTSOL cp
+ if [ "$cp" = "YES" ]; then
+ export $VAR_TRY_RTSOL="NO"
+ else
+ export $VAR_TRY_RTSOL="YES"
+ fi ;;
+ " $msg_ftp_username")
+ f_media_set_ftp_userpass ;;
+ " $msg_editor")
+ f_variable_get_value $VAR_EDITOR \
+ "$msg_please_specify_the_name_of_the_text_editor"
+ ;;
+ " $msg_release_name")
+ f_variable_get_value $VAR_RELNAME \
+ "$msg_please_specify_the_release_you_wish_to_load"
+ ;;
+ " $msg_media_type")
+ f_media_get_type ;;
+ " $msg_media_timeout")
+ f_variable_get_value $VAR_MEDIA_TIMEOUT \
+ "$msg_please_specify_the_number_of_seconds_to_wait"
+ ;;
+ " $msg_package_temp")
+ f_variable_get_value $VAR_PKG_TMPDIR \
+ "$msg_please_specify_a_temporary_directory"
+ ;;
+ " $msg_rescan_devices")
+ f_device_rescan ;;
+ " $msg_use_defaults")
+ f_variable_set_defaults ;;
+ esac
+ done
+
+ # Restore old VAR_NONINTERACTIVE if needed.
+ [ "$old_interactive" ] &&
+ setvar $VAR_NONINTERACTIVE "$old_interactive"
+
+ return $SUCCESS
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." media/options.subr
+
+fi # ! $_MEDIA_OPTIONS_SUBR
diff --git a/usr.sbin/bsdconfig/share/media/tcpip.subr b/usr.sbin/bsdconfig/share/media/tcpip.subr
new file mode 100644
index 0000000..42c6e20
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/media/tcpip.subr
@@ -0,0 +1,1713 @@
+if [ ! "$_MEDIA_TCPIP_SUBR" ]; then _MEDIA_TCPIP_SUBR=1
+#
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." media/tcpip.subr
+f_include $BSDCFG_SHARE/device.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/strings.subr
+f_include $BSDCFG_SHARE/struct.subr
+f_include $BSDCFG_SHARE/variable.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+
+TCP_HELPFILE=$BSDCFG_LIBE/include/tcp.hlp
+NETWORK_DEVICE_HELPFILE=$BSDCFG_LIBE/include/network_device.hlp
+
+############################################################ GLOBALS
+
+#
+# Path to resolv.conf(5).
+#
+: ${RESOLV_CONF:="/etc/resolv.conf"}
+
+#
+# Path to nsswitch.conf(5).
+#
+: ${NSSWITCH_CONF:="/etc/nsswitch.conf"}
+
+#
+# Path to hosts(5)
+#
+: ${ETC_HOSTS:="/etc/hosts"}
+
+#
+# Structure of dhclient.leases(5) lease { ... } entry
+#
+f_struct_define DHCP_LEASE \
+ interface \
+ fixed_address \
+ filename \
+ server_name \
+ script \
+ medium \
+ host_name \
+ subnet_mask \
+ routers \
+ domain_name_servers \
+ domain_name \
+ broadcast_address \
+ dhcp_lease_time \
+ dhcp_message_type \
+ dhcp_server_identifier \
+ dhcp_renewal_time \
+ dhcp_rebinding_time \
+ renew \
+ rebind \
+ expire
+
+############################################################ FUNCTIONS
+
+# f_validate_hostname $hostname
+#
+# Returns zero if the given argument (a fully-qualified hostname) is compliant
+# with standards set-forth in RFC's 952 and 1123 of the Network Working Group:
+#
+# RFC 952 - DoD Internet host table specification
+# http://tools.ietf.org/html/rfc952
+#
+# RFC 1123 - Requirements for Internet Hosts - Application and Support
+# http://tools.ietf.org/html/rfc1123
+#
+# See http://en.wikipedia.org/wiki/Hostname for a brief overview.
+#
+# The return status for invalid hostnames is one of:
+# 255 Entire hostname exceeds the maximum length of 255 characters.
+# 63 One or more individual labels within the hostname (separated by
+# dots) exceeds the maximum of 63 characters.
+# 1 One or more individual labels within the hostname contains one
+# or more invalid characters.
+# 2 One or more individual labels within the hostname starts or
+# ends with a hyphen (hyphens are allowed, but a label cannot
+# begin or end with a hyphen).
+# 3 One or more individual labels within the hostname are null.
+#
+# To call this function and display an appropriate error message to the user
+# based on the above error codes, use the following function defined in
+# dialog.subr:
+#
+# f_dialog_validate_hostname $hostname
+#
+f_validate_hostname()
+{
+ local fqhn="$1"
+
+ # Return error if the hostname exceeds 255 characters
+ [ ${#fqhn} -gt 255 ] && return 255
+
+ local IFS="." # Split on `dot'
+ for label in $fqhn; do
+ # Return error if the label exceeds 63 characters
+ [ ${#label} -gt 63 ] && return 63
+
+ # Return error if the label is null
+ [ "$label" ] || return 3
+
+ # Return error if label begins/ends with dash
+ case "$label" in -*|*-) return 2; esac
+
+ # Return error if the label contains any invalid chars
+ case "$label" in *[!0-9a-zA-Z-]*) return 1; esac
+ done
+
+ return $SUCCESS
+}
+
+# f_inet_atoi $ipv4_address [$var_to_set]
+#
+# Convert an IPv4 address or mask from dotted-quad notation (e.g., `127.0.0.1'
+# or `255.255.255.0') to a 32-bit unsigned integer for the purpose of network
+# and broadcast calculations. For example, one can validate that two addresses
+# are on the same network:
+#
+# f_inet_atoi 1.2.3.4 ip1num
+# f_inet_atoi 1.2.4.5 ip2num
+# f_inet_atoi 255.255.0.0 masknum
+# if [ $(( $ip1num & $masknum )) -eq \
+# $(( $ip2num & $masknum )) ]
+# then
+# : IP addresses are on same network
+# fi
+#
+# See f_validate_ipaddr() below for an additional example usage, on calculating
+# network and broadcast addresses.
+#
+# If $var_to_set is missing or NULL, the converted IP address is printed to
+# standard output for capturing in a sub-shell (which is less-recommended
+# because of performance degredation; for example, when called in a loop).
+#
+f_inet_atoi()
+{
+ local __addr="$1" __var_to_set="$2" __num=0
+ if f_validate_ipaddr "$__addr"; then
+ local IFS=.
+ set -- $__addr
+ __num=$(( ($1 << 24) + ($2 << 16) + ($3 << 8) + $4 ))
+ fi
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" $__num
+ else
+ echo $__num
+ fi
+}
+
+# f_validate_ipaddr $ipaddr [$netmask]
+#
+# Returns zero if the given argument (an IP address) is of the proper format.
+#
+# The return status for invalid IP address is one of:
+# 1 One or more individual octets within the IP address (separated
+# by dots) contains one or more invalid characters.
+# 2 One or more individual octets within the IP address are null
+# and/or missing.
+# 3 One or more individual octets within the IP address exceeds the
+# maximum of 255 (or 2^8, being an octet comprised of 8 bits).
+# 4 The IP address has either too few or too many octets.
+#
+# If a netmask is provided, the IP address is checked further:
+#
+# 5 The IP address must not be the network or broadcast address.
+#
+f_validate_ipaddr()
+{
+ local ip="$1" mask="$2"
+
+ # Track number of octets for error checking
+ local noctets=0
+
+ local oldIFS="$IFS" IFS="." # Split on `dot'
+ for octet in $ip; do
+ # Return error if the octet is null
+ [ "$octet" ] || return 2
+
+ # Return error if not a whole integer
+ f_isinteger "$octet" || return 1
+
+ # Return error if not a positive integer
+ [ $octet -ge 0 ] || return 1
+
+ # Return error if the octet exceeds 255
+ [ $octet -gt 255 ] && return 3
+
+ noctets=$(( $noctets + 1 ))
+ done
+ IFS="$oldIFS"
+
+ [ $noctets -eq 4 ] || return 4
+
+ #
+ # The IP address must not be network or broadcast address.
+ #
+ if [ "$mask" ]; then
+ local ipnum masknum netnum bcastnum
+ local max_addr=4294967295 # 255.255.255.255
+
+ f_inet_atoi $ip ipnum
+ f_inet_atoi $mask masknum
+
+ netnum=$(( $ipnum & $masknum ))
+ bcastnum=$(( ($ipnum & $masknum)+$max_addr-$masknum ))
+
+ if [ "$masknum" ] &&
+ [ $ipnum -eq $netnum -o $ipnum -eq $bcastnum ]
+ then
+ return 5
+ fi
+ fi
+
+ return $SUCCESS
+}
+
+# f_validate_ipaddr6 $ipv6_addr
+#
+# Returns zero if the given argument (an IPv6 address) is of the proper format.
+#
+# The return status for invalid IP address is one of:
+# 1 One or more individual segments within the IP address
+# (separated by colons) contains one or more invalid characters.
+# Segments must contain only combinations of the characters 0-9,
+# A-F, or a-f.
+# 2 Too many/incorrect null segments. A single null segment is
+# allowed within the IP address (separated by colons) but not
+# allowed at the beginning or end (unless a double-null segment;
+# i.e., "::*" or "*::").
+# 3 One or more individual segments within the IP address
+# (separated by colons) exceeds the length of 4 hex-digits.
+# 4 The IP address entered has either too few (less than 3), too
+# many (more than 8), or not enough segments, separated by
+# colons.
+# 5* The IPv4 address at the end of the IPv6 address is invalid.
+# * When there is an error with the dotted-quad IPv4 address at the
+# end of the IPv6 address, the return value of 5 is OR'd with a
+# bit-shifted (<< 4) return of f_validate_ipaddr.
+#
+f_validate_ipaddr6()
+{
+ local ip="${1%\%*}" # removing the interface specification if-present
+
+ local IFS=":" # Split on `colon'
+ set -- $ip:
+
+ # Return error if too many or too few segments
+ # Using 9 as max in case of leading or trailing null spanner
+ [ $# -gt 9 -o $# -lt 3 ] && return 4
+
+ local h="[0-9A-Fa-f]"
+ local nulls=0 nsegments=$# contains_ipv4_segment=
+
+ while [ $# -gt 0 ]; do
+
+ segment="${1%:}"
+ shift
+
+ #
+ # Return error if this segment makes one null too-many. A
+ # single null segment is allowed anywhere in the middle as well
+ # as double null segments are allowed at the beginning or end
+ # (but not both).
+ #
+ if [ ! "$segment" ]; then
+ nulls=$(( $nulls + 1 ))
+ if [ $nulls -eq 3 ]; then
+ # Only valid syntax for 3 nulls is `::'
+ [ "$ip" = "::" ] || return 2
+ elif [ $nulls -eq 2 ]; then
+ # Only valid if begins/ends with `::'
+ case "$ip" in
+ ::*|*::) : fall thru ;;
+ *) return 2
+ esac
+ fi
+ continue
+ fi
+
+ #
+ # Return error if not a valid hexadecimal short
+ #
+ case "$segment" in
+ $h|$h$h|$h$h$h|$h$h$h$h)
+ : valid segment of 1-4 hexadecimal digits
+ ;;
+ *[!0-9A-Fa-f]*)
+ # Segment contains at least one invalid char
+
+ # Return error immediately if not last segment
+ [ $# -eq 0 ] || return 1
+
+ # Otherwise, check for legacy IPv4 notation
+ case "$segment" in
+ *[!0-9.]*)
+ # Segment contains at least one invalid
+ # character even for an IPv4 address
+ return 1
+ esac
+
+ # Return error if not enough segments
+ if [ $nulls -eq 0 ]; then
+ [ $nsegments -eq 7 ] || return 4
+ fi
+
+ contains_ipv4_segment=1
+
+ # Validate the IPv4 address
+ f_validate_ipaddr "$segment" ||
+ return $(( 5 | $? << 4 ))
+ ;;
+ *)
+ # Segment characters are all valid but too many
+ return 3
+ esac
+
+ done
+
+ if [ $nulls -eq 1 ]; then
+ # Single null segment cannot be at beginning/end
+ case "$ip" in
+ :*|*:) return 2
+ esac
+ fi
+
+ #
+ # A legacy IPv4 address can span the last two 16-bit segments,
+ # reducing the amount of maximum allowable segments by-one.
+ #
+ maxsegments=8
+ if [ "$contains_ipv4_segment" ]; then
+ maxsegments=7
+ fi
+
+ case $nulls in
+ # Return error if missing segments with no null spanner
+ 0) [ $nsegments -eq $maxsegments ] || return 4 ;;
+ # Return error if null spanner with too many segments
+ 1) [ $nsegments -le $maxsegments ] || return 4 ;;
+ # Return error if leading/trailing `::' with too many segments
+ 2) [ $nsegments -le $(( $maxsegments + 1 )) ] || return 4 ;;
+ esac
+
+ return $SUCCESS
+}
+
+# f_validate_netmask $netmask
+#
+# Returns zero if the given argument (a subnet mask) is of the proper format.
+#
+# The return status for invalid netmask is one of:
+# 1 One or more individual fields within the subnet mask (separated
+# by dots) contains one or more invalid characters.
+# 2 One or more individual fields within the subnet mask are null
+# and/or missing.
+# 3 One or more individual fields within the subnet mask exceeds
+# the maximum of 255 (a full 8-bit register).
+# 4 The subnet mask has either too few or too many fields.
+# 5 One or more individual fields within the subnet mask is an
+# invalid integer (only 0,128,192,224,240,248,252,254,255 are
+# valid integers).
+#
+f_validate_netmask()
+{
+ local mask="$1"
+
+ # Track number of fields for error checking
+ local nfields=0
+
+ local IFS="." # Split on `dot'
+ for field in $mask; do
+ # Return error if the field is null
+ [ "$field" ] || return 2
+
+ # Return error if not a whole positive integer
+ f_isinteger "$field" || return 1
+
+ # Return error if the field exceeds 255
+ [ $field -gt 255 ] && return 3
+
+ # Return error if the field is an invalid integer
+ case "$field" in
+ 0|128|192|224|240|248|252|254|255) : ;;
+ *) return 5 ;;
+ esac
+
+ nfields=$(( $nfields + 1 ))
+ done
+
+ [ $nfields -eq 4 ] || return 4
+}
+
+# f_validate_gateway $gateway $ipaddr $netmask
+#
+# Validate an IPv4 default gateway (aka router) address for a given IP address
+# making sure the two are in the same network (able to ``talk'' to each other).
+# Returns success if $ipaddr and $gateway are in the same network given subnet
+# mask $netmask.
+#
+f_validate_gateway()
+{
+ local gateway="$1" ipaddr="$2" netmask="$3"
+ local gwnum ipnum masknum
+
+ f_validate_ipaddr "$gateway" "$netmask" || return $FAILURE
+
+ f_inet_atoi "$netmask" masknum
+ f_inet_atoi "$ipaddr" ipnum
+ f_inet_atoi "$gateway" gwnum
+
+ # Gateway must be within set of IPs reachable through interface
+ [ $(( $ipnum & $masknum )) -eq \
+ $(( $gwnum & $masknum )) ] # Return status
+}
+
+# f_dialog_validate_tcpip $hostname $gateway $nameserver $ipaddr $netmask
+#
+# Returns success if the arguments provided are valid for accessing a TCP/IP
+# network, otherwise returns failure.
+#
+f_dialog_validate_tcpip()
+{
+ local hostname="$1" gateway="$2" nameserver="$3"
+ local ipaddr="$4" netmask="$5"
+ local ipnum masknum
+
+ if [ ! "$hostname" ]; then
+ f_show_msg "$msg_must_specify_a_host_name_of_some_sort"
+ elif ! f_validate_hostname "$hostname"; then
+ f_show_msg "$msg_invalid_hostname_value"
+ elif [ "$netmask" ] && ! f_validate_netmask "$netmask"; then
+ f_show_msg "$msg_invalid_netmask_value"
+ elif [ "$nameserver" ] &&
+ ! f_validate_ipaddr "$nameserver" &&
+ ! f_validate_ipaddr6 "$nameserver"; then
+ f_show_msg "$msg_invalid_name_server_ip_address_specified"
+ elif [ "$ipaddr" ] && ! f_validate_ipaddr "$ipaddr" "$netmask"; then
+ f_show_msg "$msg_invalid_ipv4_address"
+ elif [ "$gateway" -a "$gateway" != "NO" ] &&
+ ! f_validate_gateway "$gateway" "$ipaddr" "$netmask"; then
+ f_show_msg "$msg_invalid_gateway_ipv4_address_specified"
+ else
+ return $DIALOG_OK
+ fi
+
+ return $DIALOG_CANCEL
+}
+
+# f_ifconfig_inet $interface [$var_to_set]
+#
+# Returns the IPv4 address associated with $interface. If $var_to_set is
+# missing or NULL, the IP address is printed to standard output for capturing
+# in a sub-shell (which is less-recommended because of performance degredation;
+# for example, when called in a loop).
+#
+# This function is a two-parter. Below is the awk(1) portion of the function,
+# afterward is the sh(1) function which utilizes the below awk script.
+#
+f_ifconfig_inet_awk='
+BEGIN { found = 0 }
+( $1 == "inet" ) \
+{
+ print $2
+ found = 1
+ exit
+}
+END { exit ! found }
+'
+f_ifconfig_inet()
+{
+ local __interface="$1" __var_to_set="$2"
+ if [ "$__var_to_set" ]; then
+ local __ip
+ __ip=$( ifconfig "$__interface" 2> /dev/null |
+ awk "$f_ifconfig_inet_awk" )
+ setvar "$__var_to_set" "$__ip"
+ else
+ ifconfig "$__interface" 2> /dev/null |
+ awk "$f_ifconfig_inet_awk"
+ fi
+}
+
+# f_ifconfig_inet6 $interface [$var_to_set]
+#
+# Returns the IPv6 address associated with $interface. If $var_to_set is
+# missing or NULL, the IP address is printed to standard output for capturing
+# in a sub-shell (which is less-recommended because of performance degredation;
+# for example, when called in a loop).
+#
+# This function is a two-parter. Below is the awk(1) portion of the function,
+# afterward is the sh(1) function which utilizes the below awk script.
+#
+f_ifconfig_inet6_awk='
+BEGIN { found = 0 }
+( $1 == "inet6" ) \
+{
+ print $2
+ found = 1
+ exit
+}
+END { exit ! found }
+'
+f_ifconfig_inet6()
+{
+ local __interface="$1" __var_to_set="$2"
+ if [ "$__var_to_set" ]; then
+ local __ip6
+ __ip6=$( ifconfig "$__interface" 2> /dev/null |
+ awk "$f_ifconfig_inet6_awk" )
+ setvar "$__var_to_set" "$__ip6"
+ else
+ ifconfig "$__interface" 2> /dev/null |
+ awk "$f_ifconfig_inet6_awk"
+ fi
+}
+
+# f_ifconfig_netmask $interface [$var_to_set]
+#
+# Returns the IPv4 subnet mask associated with $interface. If $var_to_set is
+# missing or NULL, the netmask is printed to standard output for capturing in a
+# sub-shell (which is less-recommended because of performance degredation; for
+# example, when called in a loop).
+#
+f_ifconfig_netmask()
+{
+ local __interface="$1" __var_to_set="$2" __octets
+ __octets=$( ifconfig "$__interface" 2> /dev/null | awk \
+ '
+ BEGIN { found = 0 }
+ ( $1 == "inet" ) \
+ {
+ printf "%s %s %s %s\n",
+ substr($4,3,2),
+ substr($4,5,2),
+ substr($4,7,2),
+ substr($4,9,2)
+ found = 1
+ exit
+ }
+ END { exit ! found }
+ ' ) || return $FAILURE
+
+ local __octet __netmask=
+ for __octet in $__octets; do
+ f_sprintf __netmask "%s.%u" "$__netmask" "0x$__octet"
+ done
+ __netmask="${__netmask#.}"
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" "$__netmask"
+ else
+ echo $__netmask
+ fi
+}
+
+# f_route_get_default [$var_to_set]
+#
+# Returns the IP address of the currently active default router. If $var_to_set
+# is missing or NULL, the IP address is printed to standard output for
+# capturing in a sub-shell (which is less-recommended because of performance
+# degredation; for example, when called in a loop).
+#
+# This function is a two-parter. Below is the awk(1) portion of the function,
+# afterward is the sh(1) function which utilizes the below awk script.
+#
+f_route_get_default_awk='
+BEGIN { found = 0 }
+( $1 == "gateway:" ) \
+{
+ print $2
+ found = 1
+ exit
+}
+END { exit ! found }
+'
+f_route_get_default()
+{
+ local __var_to_set="$1"
+ if [ "$__var_to_set" ]; then
+ local __ip
+ __ip=$( route -n get default 2> /dev/null |
+ awk "$f_route_get_default_awk" )
+ setvar "$__var_to_set" "$__ip"
+ else
+ route -n get default 2> /dev/null |
+ awk "$f_route_get_default_awk"
+ fi
+}
+
+# f_resolv_conf_nameservers [$var_to_set]
+#
+# Returns nameserver(s) configured in resolv.conf(5). If $var_to_set is missing
+# or NULL, the list of nameservers is printed to standard output for capturing
+# in a sub-shell (which is less-recommended because of performance degredation;
+# for example, when called in a loop).
+#
+# This function is a two-parter. Below is the awk(1) portion of the function,
+# afterward is the sh(1) function which utilizes the below awk script.
+#
+f_resolv_conf_nameservers_awk='
+BEGIN { found = 0 }
+( $1 == "nameserver" ) \
+{
+ print $2
+ found = 1
+}
+END { exit ! found }
+'
+f_resolv_conf_nameservers()
+{
+ local __var_to_set="$1"
+ if [ "$__var_to_set" ]; then
+ local __ns
+ __ns=$( awk "$f_resolv_conf_nameservers_awk" "$RESOLV_CONF" \
+ 2> /dev/null )
+ setvar "$__var_to_set" "$__ns"
+ else
+ awk "$f_resolv_conf_nameservers_awk" "$RESOLV_CONF" \
+ 2> /dev/null
+ fi
+}
+
+# f_config_resolv
+#
+# Attempts to configure resolv.conf(5) and ilk. Returns success if able to
+# write the file(s), otherwise returns error status.
+#
+# Variables from variable.subr that are used in configuring resolv.conf(5) are
+# as follows (all of which can be configured automatically through functions
+# like f_dhcp_get_info() or manually):
+#
+# VAR_NAMESERVER
+# The nameserver to add in resolv.conf(5).
+# VAR_DOMAINNAME
+# The domain to configure in resolv.conf(5). Also used in the
+# configuration of hosts(5).
+# VAR_IPADDR
+# The IPv4 address to configure in hosts(5).
+# VAR_IPV6ADDR
+# The IPv6 address to configure in hosts(5).
+# VAR_HOSTNAME
+# The hostname to associate with the IPv4 and/or IPv6 address in
+# hosts(5).
+#
+f_config_resolv()
+{
+ local cp c6p dp hp
+
+ f_getvar $VAR_NAMESERVER cp
+ if [ "$cp" ]; then
+ case "$RESOLV_CONF" in
+ */*) f_quietly mkdir -p "${RESOLV_CONF%/*}" ;;
+ esac
+
+ # Attempt to create/truncate the file
+ ( :> "$RESOLV_CONF" ) 2> /dev/null || return $FAILURE
+
+ f_getvar $VAR_DOMAINNAME dp &&
+ printf "domain\t%s\n" "$dp" >> "$RESOLV_CONF"
+ printf "nameserver\t%s\n" "$cp" >> "$RESOLV_CONF"
+
+ f_dprintf "Wrote out %s" "$RESOLV_CONF"
+ fi
+
+ f_getvar $VAR_DOMAINNAME dp
+ f_getvar $VAR_IPADDR cp
+ f_getvar $VAR_IPV6ADDR c6p
+ f_getvar $VAR_HOSTNAME hp
+
+ # Attempt to create the file if it doesn't already exist
+ if [ ! -e "$ETC_HOSTS" ]; then
+ case "$ETC_HOSTS" in
+ */*) f_quietly mkdir -p "${ETC_HOSTS%/*}" ;;
+ esac
+
+ ( :> "$ETC_HOSTS" ) 2> /dev/null || return $FAILURE
+ fi
+
+ # Scan the file and add ourselves if not already configured
+ awk -v dn="$dp" -v ip4="$cp" -v ip6="$c6p" -v hn="$hp" '
+ BEGIN {
+ local4found = local6found = 0
+ hn4found = hn6found = h4found = h6found = 0
+ h = ( match(hn, /\./) ? substr(hn, 0, RSTART-1) : "" )
+ }
+ ($1 == "127.0.0.1") { local4found = 1 }
+ ($1 == "::1") { local6found = 1 }
+ {
+ for (n = 2; n <= NF; n++)
+ {
+ if ( $1 == ip4 ) {
+ if ( $n == h ) h4found = 1
+ if ( $n == hn ) hn4found = 1
+ if ( $n == hn "." ) hn4found = 1
+ }
+ if ( $1 == ip6 ) {
+ if ( $n == h ) h6found = 1
+ if ( $n == hn ) hn6found = 1
+ if ( $n == hn "." ) hn6found = 1
+ }
+ }
+ }
+ END {
+ hosts = FILENAME
+
+ if ( ! local6found )
+ printf "::1\t\t\tlocalhost%s\n",
+ ( dn ? " localhost." dn : "" ) >> hosts
+ if ( ! local4found )
+ printf "127.0.0.1\t\tlocalhost%s\n",
+ ( dn ? " localhost." dn : "" ) >> hosts
+
+ if ( ip6 && ! (h6found && hn6found))
+ {
+ printf "%s\t%s %s\n", ip6, hn, h >> hosts
+ printf "%s\t%s.\n", ip6, hn >> hosts
+ }
+ else if ( ip6 )
+ {
+ if ( ! h6found )
+ printf "%s\t%s.\n", ip6, h >> hosts
+ if ( ! hn6found )
+ printf "%s\t%s\n", ip6, hn >> hosts
+ }
+
+ if ( ip4 && ! (h4found && hn4found))
+ {
+ printf "%s\t\t%s %s\n", ip4, hn, h >> hosts
+ printf "%s\t\t%s.\n", ip4, hn >> hosts
+ }
+ else if ( ip4 )
+ {
+ if ( ! h4found )
+ printf "%s\t\t%s.\n", ip4, h >> hosts
+ if ( ! hn4found )
+ printf "%s\t\t%s\n", ip4, hn >> hosts
+ }
+ }
+ ' "$ETC_HOSTS" 2> /dev/null || return $FAILURE
+
+ f_dprintf "Wrote out %s" "$ETC_HOSTS"
+ return $SUCCESS
+}
+
+# f_dhcp_parse_leases $leasefile struct_name
+#
+# Parse $leasefile and store the information for the most recent lease in a
+# struct (see struct.subr for additional details) named `struct_name'. See
+# DHCP_LEASE struct definition in the GLOBALS section above.
+#
+f_dhcp_parse_leases()
+{
+ local leasefile="$1" struct_name="$2"
+
+ [ "$struct_name" ] || return $FAILURE
+
+ if [ ! -e "$leasefile" ]; then
+ f_dprintf "%s: No such file or directory" "$leasefile"
+ return $FAILURE
+ fi
+
+ f_struct "$struct_name" && f_struct_free "$struct_name"
+ f_struct_new DHCP_LEASE "$struct_name"
+
+ eval "$( awk -v struct="$struct_name" '
+ BEGIN {
+ lease_found = 0
+ keyword_list = " \
+ interface \
+ fixed-address \
+ filename \
+ server-name \
+ script \
+ medium \
+ "
+ split(keyword_list, keywords, FS)
+
+ time_list = "renew rebind expire"
+ split(time_list, times, FS)
+
+ option_list = " \
+ host-name \
+ subnet-mask \
+ routers \
+ domain-name-servers \
+ domain-name \
+ broadcast-address \
+ dhcp-lease-time \
+ dhcp-message-type \
+ dhcp-server-identifier \
+ dhcp-renewal-time \
+ dhcp-rebinding-time \
+ "
+ split(option_list, options, FS)
+ }
+ function set_value(prop,value)
+ {
+ lease_found = 1
+ gsub(/[^[:alnum:]_]/, "_", prop)
+ sub(/;$/, "", value)
+ sub(/^"/, "", value)
+ sub(/"$/, "", value)
+ sub(/,.*/, "", value)
+ printf "%s set %s \"%s\"\n", struct, prop, value
+ }
+ /^lease {$/, /^}$/ \
+ {
+ if ( $0 ~ /^lease {$/ ) next
+ if ( $0 ~ /^}$/ ) exit
+
+ for (k in keywords)
+ {
+ keyword = keywords[k]
+ if ( $1 == keyword )
+ {
+ set_value(keyword, $2)
+ next
+ }
+ }
+
+ for (t in times)
+ {
+ time = times[t]
+ if ( $1 == time )
+ {
+ set_value(time, $2 " " $3 " " $4)
+ next
+ }
+ }
+
+ if ( $1 != "option" ) next
+ for (o in options)
+ {
+ option = options[o]
+ if ( $2 == option )
+ {
+ set_value(option, $3)
+ next
+ }
+ }
+ }
+ EXIT {
+ if ( ! lease_found )
+ {
+ printf "f_struct_free \"%s\"\n", struct
+ print "return $FAILURE"
+ }
+ }
+ ' "$leasefile" )"
+}
+
+# f_dhcp_get_info $interface
+#
+# Parse the dhclient(8) lease database for $interface to obtain all the
+# necessary IPv4 details necessary to communicate on the network. The retrieved
+# information is stored in VAR_IPADDR, VAR_NETMASK, VAR_GATEWAY, and
+# VAR_NAMESERVER.
+#
+# If reading the lease database fails, values are obtained from ifconfig(8) and
+# route(8). If the DHCP lease did not provide a nameserver (or likewise, we
+# were unable to parse the lease database), fall-back to resolv.conf(5) for
+# obtaining the nameserver. Always returns success.
+#
+f_dhcp_get_info()
+{
+ local interface="$1" cp
+ local leasefile="/var/db/dhclient.leases.$interface"
+
+ # If it fails, do it the old-fashioned way
+ if f_dhcp_parse_leases "$leasefile" lease; then
+ lease get fixed_address $VAR_IPADDR
+ lease get subnet_mask $VAR_NETMASK
+ lease get routers cp
+ setvar $VAR_GATEWAY "${cp%%,*}"
+ lease get domain_name_servers cp
+ setvar $VAR_NAMESERVER "${cp%%,*}"
+ lease get host_name cp &&
+ setvar $VAR_HOSTNAME "$cp"
+ f_struct_free lease
+ else
+ # Bah, now we have to get the information from ifconfig
+ if f_debugging; then
+ f_dprintf "DHCP configured interface returns %s" \
+ "$( ifconfig "$interface" )"
+ fi
+ f_ifconfig_inet "$interface" $VAR_IPADDR
+ f_ifconfig_netmask "$interface" $VAR_NETMASK
+ f_route_get_default $VAR_GATEWAY
+ fi
+
+ # If we didn't get a name server value, hunt for it in resolv.conf
+ local ns
+ if [ -r "$RESOLV_CONF" ] && ! {
+ f_getvar $VAR_NAMESERVER ns || [ "$ns" ]
+ }; then
+ f_resolv_conf_nameservers cp &&
+ setvar $VAR_NAMESERVER ${cp%%[$IFS]*}
+ fi
+
+ return $SUCCESS
+}
+
+# f_rtsol_get_info $interface
+#
+# Returns the rtsol-provided IPv6 address associated with $interface. The
+# retrieved IP address is stored in VAR_IPV6ADDR. Always returns success.
+#
+f_rtsol_get_info()
+{
+ local interface="$1" cp
+ cp=$( ifconfig "$interface" 2> /dev/null | awk \
+ '
+ BEGIN { found = 0 }
+ ( $1 == "inet6" ) && ( $2 ~ /^fe80:/ ) \
+ {
+ print $2
+ found = 1
+ exit
+ }
+ END { exit ! found }
+ ' ) && setvar $VAR_IPV6ADDR "$cp"
+}
+
+# f_host_lookup $host [$var_to_set]
+#
+# Use host(1) to lookup (or reverse) an Internet number from (or to) a name.
+# Multiple answers are returned separated by a single space. If host(1) does
+# not exit cleanly, its full output is provided and the return status is 1.
+#
+# If nsswitch.conf(5) has been configured to query local access first for the
+# `hosts' database, we'll manually check hosts(5) first (preventing host(1)
+# from hanging in the event that DNS goes awry).
+#
+# If $var_to_set is missing or NULL, the list of IP addresses is printed to
+# standard output for capturing in a sub-shell (which is less-recommended
+# because of performance degredation; for example, when called in a loop).
+#
+# The variables from variable.subr used in looking up the host are as follows
+# (which are set manually):
+#
+# VAR_IPV6_ENABLE [Optional]
+# If set to "YES", enables the lookup of IPv6 addresses and IPv4
+# address. IPv6 addresses, if any, will come before IPv4. Note
+# that if nsswitch.conf(5) shows an affinity for "files" for the
+# "host" database and there is a valid entry in hosts(5) for
+# $host, this setting currently has no effect (an IPv4 address
+# can supersede an IPv6 address). By design, hosts(5) overrides
+# any preferential treatment. Otherwise, if this variable is not
+# set, IPv6 addresses will not be used (IPv4 addresses will
+# specifically be requested from DNS).
+#
+# This function is a two-parter. Below is the awk(1) portion of the function,
+# afterward is the sh(1) function which utilizes the below awk script.
+#
+f_host_lookup_awk='
+BEGIN{ addrs = "" }
+!/^[[:space:]]*(#|$)/ \
+{
+ for (n=1; n++ < NF;) if ($n == name)
+ addrs = addrs (addrs ? " " : "") $1
+}
+END {
+ if (addrs) print addrs
+ exit !addrs
+}
+'
+f_host_lookup()
+{
+ local __host="$1" __var_to_set="$2"
+ f_dprintf "f_host_lookup: host=[%s]" "$__host"
+
+ # If we're configured to look at local files first, do that
+ if awk '/^hosts:/{exit !($2=="files")}' "$NSSWITCH_CONF"; then
+ if [ "$__var_to_set" ]; then
+ local __cp
+ if __cp=$( awk -v name="$__host" \
+ "$f_host_lookup_awk" "$ETC_HOSTS" )
+ then
+ setvar "$__var_to_set" "$__cp"
+ return $SUCCESS
+ fi
+ else
+ awk -v name="$__host" \
+ "$f_host_lookup_awk" "$ETC_HOSTS" &&
+ return $SUCCESS
+ fi
+ fi
+
+ #
+ # Fall back to host(1) -- which is further governed by nsswitch.conf(5)
+ #
+
+ local __output __ip6 __addrs=
+ f_getvar $VAR_IPV6_ENABLE __ip6
+
+ # If we have a TCP media type configured, check for an SRV record
+ local __srvtypes=
+ { f_quietly f_getvar $VAR_HTTP_PATH ||
+ f_quietly f_getvar $VAR_HTTP_PROXY_PATH
+ } && __srvtypes="$__srvtypes _http._tcp"
+ f_quietly f_getvar $VAR_FTP_PATH && __srvtypes="$__srvtypes _ftp._tcp"
+ f_quietly f_getvar $VAR_NFS_PATH &&
+ __srvtypes="$__srvtypes _nfs._tcp _nfs._udp"
+
+ # Calculate wait time as dividend of total time and host(1) invocations
+ local __host_runs __wait
+ f_count __host_runs $__srvtypes
+ if [ "$__ip6" = "YES" ]; then
+ __host_runs=$(( $__host_runs + 2 ))
+ else
+ __host_runs=$(( $__host_runs + 1 ))
+ fi
+ f_getvar $VAR_MEDIA_TIMEOUT __wait
+ [ "$__wait" ] && __wait="-W $(( $__wait / $__host_runs ))"
+
+ # Query SRV types first (1st host response taken as new host to query)
+ for __type in $__srvtypes; do
+ if __output=$(
+ host -t SRV $__wait -- "$__type.$__host" \
+ 2> /dev/null
+ ); then
+ __host=$( echo "$__output" |
+ awk '/ SRV /{print $NF;exit}' )
+ break
+ fi
+ done
+
+ # Try IPv6 first (if enabled)
+ if [ "$__ip6" = "YES" ]; then
+ if ! __output=$( host -t AAAA $__wait -- "$__host" 2>&1 ); then
+ # An error occurred, display in-full and return error
+ [ "$__var_to_set" ] &&
+ setvar "$__var_to_set" "$__output"
+ return $FAILURE
+ fi
+ # Add the IPv6 addresses and fall-through to collect IPv4 too
+ __addrs=$( echo "$__output" | awk '/ address /{print $NF}' )
+ fi
+
+ # Good ol' IPv4
+ if ! __output=$( host -t A $__wait -- "$__host" 2>&1 ); then
+ # An error occurred, display it in-full and return error
+ [ "$__var_to_set" ] && setvar "$__var_to_set" "$__output"
+ return $FAILURE
+ fi
+
+ __addrs="$__addrs${__addrs:+ }$(
+ echo "$__output" | awk '/ address /{print $NF}' )"
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" "$__addrs"
+ else
+ echo $__addrs
+ fi
+}
+
+# f_device_dialog_tcp $device
+#
+# This is it - how to get TCP setup values. Prompt the user to edit/confirm the
+# interface, gateway, nameserver, and hostname settings -- all required for
+# general TCP/IP access.
+#
+# Variables from variable.subr that can be used to sript user input:
+#
+# VAR_NO_INET6
+# If set, prevents asking the user if they would like to use
+# rtsol(8) to check for an IPv6 router.
+# VAR_TRY_RTSOL
+# If set to "YES" (and VAR_NONINTERACTIVE is unset), asks the
+# user if they would like to try the IPv6 RouTer SOLicitation
+# utility (rtsol(8)) to get IPv6 information. Ignored if
+# VAR_NO_INET6 is set.
+# VAR_TRY_DHCP
+# If set to "YES" (and VAR_NONINTERACTIVE is unset), asks the
+# user if they would like to try to acquire IPv4 connection
+# settings from a DHCP server using dhclient(8).
+#
+# VAR_GATEWAY Default gateway to use.
+# VAR_IPADDR Interface address to assign.
+# VAR_NETMASK Interface subnet mask.
+# VAR_EXTRAS Extra interface options to ifconfig(8).
+# VAR_HOSTNAME Hostname to set.
+# VAR_DOMAINNAME Domain name to use.
+# VAR_NAMESERVER DNS nameserver to use when making lookups.
+# VAR_IPV6ADDR IPv6 interface address.
+#
+# In addition, the following variables are used in acquiring network settings
+# from the user:
+#
+# VAR_NONINTERACTIVE
+# If set (such as when running in a script), prevents asking the
+# user questions or displaying the usual prompts, etc.
+# VAR_NETINTERACTIVE
+# The one exception to VAR_NONINTERACTIVE is VAR_NETINTERACTIVE,
+# which if set will prompt the user to try RTSOL (unless
+# VAR_TRY_RTSOL has been set), try DHCP (unless VAR_TRY_DHCP has
+# been set), and display the network verification dialog. This
+# allows you to have a mostly non-interactive script that still
+# prompts for network setup/confirmation.
+#
+# After successfull execution, the following variables are set:
+#
+# VAR_IFCONFIG + $device (e.g., `ifconfig_em0')
+# Defines the ifconfig(8) properties specific to $device.
+#
+f_device_dialog_tcp()
+{
+ local dev="$1" devname cp n
+ local use_dhcp="" use_rtsol=""
+ local _ipaddr _netmask _extras
+
+ [ "$dev" ] || return $DIALOG_CANCEL
+ f_struct "$dev" get name devname || return $DIALOG_CANCEL
+
+ # Initialize vars from previous device values
+ local private
+ $dev get private private
+ if [ "$private" ] && f_struct "$private"; then
+ $private get ipaddr _ipaddr
+ $private get netmask _netmask
+ $private get extras _extras
+ $private get use_dhcp use_dhcp
+ $private get use_rtsol use_rtsol
+ else # See if there are any defaults
+
+ #
+ # This is a hack so that the dialogs below are interactive in a
+ # script if we have requested interactive behavior.
+ #
+ local old_interactive=
+ if ! f_interactive && f_netinteractive; then
+ f_getvar $VAR_NONINTERACTIVE old_interactive
+ unset $VAR_NONINTERACTIVE
+ fi
+
+ #
+ # Try a RTSOL scan if such behavior is desired.
+ # If the variable was configured and is YES, do it.
+ # If it was configured to anything else, treat it as NO.
+ # Otherwise, ask the question interactively.
+ #
+ local try6
+ if ! f_isset $VAR_NO_INET6 && {
+ { f_getvar $VAR_TRY_RTSOL try6 && [ "$try6" = "YES" ]; } ||
+ {
+ # Only prompt the user when VAR_TRY_RTSOL is unset
+ ! f_isset $VAR_TRY_RTSOL &&
+ f_dialog_noyes "$msg_try_ipv6_configuration"
+ }
+ }; then
+ local i
+
+ f_quietly sysctl net.inet6.ip6.forwarding=0
+ f_quietly sysctl net.inet6.ip6.accept_rtadv=1
+ f_quietly ifconfig $devname up
+
+ i=$( sysctl -n net.inet6.ip6.dad_count )
+ sleep $(( $i + 1 ))
+
+ f_quietly mkdir -p /var/run
+ f_dialog_info "$msg_scanning_for_ra_servers"
+ if f_quietly rtsol $devname; then
+ i=$( sysctl -n net.inet6.ip6.dad_count )
+ sleep $(( $i + 1 ))
+ f_rtsol_get_info $devname
+ use_rtsol=1
+ else
+ use_rtsol=
+ fi
+ fi
+
+ #
+ # Try a DHCP scan if such behavior is desired.
+ # If the variable was configured and is YES, do it.
+ # If it was configured to anything else, treat it as NO.
+ # Otherwise, ask the question interactively.
+ #
+ local try4
+ if { f_getvar $VAR_TRY_DHCP try4 && [ "$try4" = "YES" ]; } || {
+ # Only prompt the user when VAR_TRY_DHCP is unset
+ ! f_isset $VAR_TRY_DHCP &&
+ f_dialog_noyes "$msg_try_dhcp_configuration"
+ }; then
+ f_quietly ifconfig $devname delete
+ f_quietly mkdir -p /var/db
+ f_quietly mkdir -p /var/run
+ f_quietly mkdir -p /tmp
+
+ local msg="$msg_scanning_for_dhcp_servers"
+ trap - SIGINT
+ ( # Execute in sub-shell to allow/catch Ctrl-C
+ trap 'exit $FAILURE' SIGINT
+ if [ "$USE_XDIALOG" ]; then
+ f_quietly dhclient $devname |
+ f_xdialog_info "$msg"
+ else
+ f_dialog_info "$msg"
+ f_quietly dhclient $devname
+ fi
+ )
+ local retval=$?
+ trap 'f_interrupt' SIGINT
+ if [ $retval -eq $SUCCESS ]; then
+ f_dhcp_get_info $devname
+ use_dhcp=1
+ else
+ use_dhcp=
+ fi
+ fi
+
+ # Restore old VAR_NONINTERACTIVE if needed.
+ [ "$old_interactive" ] &&
+ setvar $VAR_NONINTERACTIVE "$old_interactive"
+
+ # Special hack so it doesn't show up oddly in the menu
+ local gw
+ if f_getvar $VAR_GATEWAY gw && [ "$gw" = "NO" ]; then
+ setvar $VAR_GATEWAY ""
+ fi
+
+ # Get old IP address from variable space, if available
+ if [ ! "$_ipaddr" ]; then
+ if f_getvar $VAR_IPADDR cp; then
+ _ipaddr="$cp"
+ elif f_getvar ${devname}_$VAR_IPADDR cp; then
+ _ipaddr="$cp"
+ fi
+ fi
+
+ # Get old netmask from variable space, if available
+ if [ ! "$_netmask" ]; then
+ if f_getvar $VAR_NETMASK cp; then
+ _netmask="$cp"
+ elif f_getvar ${devname}_$VAR_NETMASK cp; then
+ _netmask="$cp"
+ fi
+ fi
+
+ # Get old extras string from variable space, if available
+ if [ ! "$_extras" ]; then
+ if f_getvar $VAR_EXTRAS cp; then
+ _extras="$cp"
+ elif f_getvar ${devname}_$VAR_EXTRAS cp; then
+ _extras="$cp"
+ fi
+ fi
+ fi
+
+ # Look up values already recorded with the system, or blank the string
+ # variables ready to accept some new data
+ local _hostname _gateway _nameserver
+ f_getvar $VAR_HOSTNAME _hostname
+ case "$_hostname" in
+ *.*) : do nothing ;; # Already fully-qualified
+ *)
+ f_getvar $VAR_DOMAINNAME cp
+ [ "$cp" ] && _hostname="$_hostname.$cp"
+ esac
+ f_getvar $VAR_GATEWAY _gateway
+ f_getvar $VAR_NAMESERVER _nameserver
+
+ # Re-check variables for initial inheritance before heading into dialog
+ [ "$_hostname" ] || _hostname="${HOSTNAME:-$( hostname )}"
+ [ "$_gateway" ] || f_route_get_default _gateway
+ [ ! "$_nameserver" ] &&
+ f_resolv_conf_nameservers cp && _nameserver=${cp%%[$IFS]*}
+ [ "$_ipaddr" ] || f_ifconfig_inet $devname _ipaddr
+ [ "$_netmask" ] || f_ifconfig_netmask $devname _netmask
+
+ # If non-interactive, jump over dialog section and into config section
+ if f_netinteractive || f_interactive || [ ! "$_hostname" ]
+ then
+ [ ! "$_hostname" ] && f_interactive &&
+ f_show_msg "$msg_hostname_variable_not_set"
+
+ local title=" $msg_network_configuration "
+ local hline="$hline_alnum_arrows_punc_tab_enter"
+ local extras_help="$tcplayout_extras_help"
+
+ # Modify the help line for PLIP config
+ [ "${devname#plip}" != "$devname" ] &&
+ extras_help="$tcplayout_extras_help_for_plip"
+
+ f_getvar $VAR_IPV6ADDR cp && [ "$cp" ] &&
+ title="$title($msg_ipv6_ready) "
+
+ if [ ! "$USE_XDIALOG" ]; then
+ local prompt="$msg_dialog_mixedform_navigation_help"
+ # Calculate center position for displaying device label
+ local devlabel="$msg_configuration_for_interface"
+ devlabel="$devlabel $devname"
+ local width=54
+ local n=$(( $width/2 - (${#devlabel} + 4)/2 - 2 ))
+
+ while :; do
+ cp=$( $DIALOG \
+ --title "$title" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --hline "$hline" \
+ --item-help \
+ --ok-label "$msg_ok" \
+ --cancel-label "$msg_cancel" \
+ --help-button \
+ --help-label "$msg_help" \
+ --mixedform "$prompt" 16 $width 9 \
+ "$msg_host_name_including_domain:" 1 2 \
+ "$_hostname" 2 3 45 255 0 \
+ "$tcplayout_hostname_help" \
+ "$msg_ipv4_gateway:" 3 2 \
+ "$_gateway" 4 3 16 15 0 \
+ "$tcplayout_gateway_help" \
+ "$msg_name_server:" 3 31 \
+ "$_nameserver" 4 32 16 15 0 \
+ "$tcplayout_nameserver_help" \
+ "- $devlabel -" 5 $n "" 0 0 0 0 3 "" \
+ "$msg_ipv4_address:" 6 6 \
+ "$_ipaddr" 7 7 16 15 0 \
+ "$tcplayout_ipaddr_help" \
+ "$msg_netmask:" 6 31 \
+ "$_netmask" 7 32 16 15 0 \
+ "$tcplayout_netmask_help" \
+ "$msg_extra_options_to_ifconfig" 8 6 \
+ "$_extras" 9 7 41 2048 0 \
+ "$extras_help" \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD )
+
+ # --mixed-form always returns 0, we have to
+ # use the returned data to determine button
+ if [ ! "$cp" ]; then
+ # User either chose "Cancel", pressed
+ # ESC, or blanked every form field
+ return $DIALOG_CANCEL
+ else
+ n=$( echo "$cp" | f_number_of_lines )
+ [ $n -eq 1 ] && case "$cp" in HELP*)
+ # User chose "Help"
+ f_show_help "$TCP_HELPFILE"
+ continue
+ esac
+ fi
+
+ # Turn mixed-form results into env variables
+ eval "$( echo "$cp" | awk '
+ BEGIN {
+ n = 0
+ field[++n] = "_hostname"
+ field[++n] = "_gateway"
+ field[++n] = "_nameserver"
+ field[++n] = "_ipaddr"
+ field[++n] = "_netmask"
+ field[++n] = "_extras"
+ nfields = n
+ n = 0
+ }
+ {
+ gsub(/'\''/, "'\'\\\\\'\''")
+ sub(/[[:space:]]*$/, "")
+ value[field[++n]] = $0
+ }
+ END {
+ for ( n = 1; n <= nfields; n++ )
+ {
+ printf "%s='\''%s'\'';\n",
+ field[n],
+ value[field[n]]
+ }
+ }' )"
+
+ f_dialog_validate_tcpip \
+ "$_hostname" \
+ "$_gateway" \
+ "$_nameserver" \
+ "$_ipaddr" \
+ "$_netmask" \
+ && break
+ done
+ else
+ # Xdialog(1) does not support --mixed-form
+ # Create a persistent menu instead
+
+ f_dialog_title "$msg_network_configuration"
+ local prompt=
+
+ while :; do
+ cp=$( $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --hline "$hline" \
+ --item-help \
+ --ok-label "$msg_ok" \
+ --cancel-label "$msg_cancel" \
+ --help "" \
+ --menu "$prompt" 21 60 8 \
+ "$msg_accept_continue" "" \
+ "$tcplayout_accept_cont_help" \
+ "$msg_host_name_including_domain:" \
+ "$_hostname" \
+ "$tcplayout_hostname_help" \
+ "$msg_ipv4_gateway:" "$_gateway" \
+ "$tcplayout_gateway_help" \
+ "$msg_name_server:" "$_nameserver" \
+ "$tcplayout_nameserver_help" \
+ "$msg_ipv4_address:" "$_ipaddr" \
+ "$tcplayout_ipaddr_help" \
+ "$msg_netmask:" "$_netmask" \
+ "$tcplayout_netmask_help" \
+ "$msg_extra_options_to_ifconfig" \
+ "$_extras" "$extras_help" \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize cp
+ f_dprintf "retval=%u mtag=[%s]" $retval "$cp"
+
+ if [ $retval -eq $DIALOG_HELP ]; then
+ f_show_help "$TCP_HELPFILE"
+ continue
+ elif [ $retval -ne $DIALOG_OK ]; then
+ f_dialog_title_restore
+ return $DIALOG_CANCEL
+ fi
+
+ case "$cp" in
+ "$msg_accept_continue")
+ f_dialog_validate_tcpip \
+ "$_hostname" \
+ "$_gateway" \
+ "$_nameserver" \
+ "$_ipaddr" \
+ "$_netmask" \
+ && break ;;
+ "$msg_host_name_including_domain:")
+ f_dialog_input cp "$cp" "$_hostname" \
+ && _hostname="$cp" ;;
+ "$msg_ipv4_gateway:")
+ f_dialog_input cp "$cp" "$_gateway" \
+ && _gateway="$cp" ;;
+ "$msg_name_server:")
+ f_dialog_input cp "$cp" "$_nameserver" \
+ && _nameserver="$cp" ;;
+ "$msg_ipv4_address:")
+ f_dialog_input cp "$cp" "$_ipaddr" \
+ && _ipaddr="$cp" ;;
+ "$msg_netmask:")
+ f_dialog_input cp "$cp" "$_netmask" \
+ && _netmask="$cp" ;;
+ "$msg_extra_options_to_ifconfig")
+ f_dialog_input cp "$cp" "$_extras" \
+ && _extras="$cp" ;;
+ esac
+ done
+
+ f_dialog_title_restore
+
+ fi # XDIALOG
+
+ fi # interactive
+
+ # We actually need to inform the rest of bsdconfig about this
+ # data now if the user hasn't selected cancel.
+
+ if [ "$_hostname" ]; then
+ setvar $VAR_HOSTNAME "$_hostname"
+ f_quietly hostname "$_hostname"
+ case "$_hostname" in
+ *.*) setvar $VAR_DOMAINNAME "${_hostname#*.}" ;;
+ esac
+ fi
+ [ "$_gateway" ] && setvar $VAR_GATEWAY "$_gateway"
+ [ "$_nameserver" ] && setvar $VAR_NAMESERVER "$_nameserver"
+ [ "$_ipaddr" ] && setvar $VAR_IPADDR "$_ipaddr"
+ [ "$_netmask" ] && setvar $VAR_NETMASK "$_netmask"
+ [ "$_extras" ] && setvar $VAR_EXTRAS "$_extras"
+
+ f_dprintf "Creating struct DEVICE_INFO devinfo_%s" "$dev"
+ f_struct_new DEVICE_INFO devinfo_$dev
+ $dev set private devinfo_$dev
+
+ devinfo_$dev set ipaddr $_ipaddr
+ devinfo_$dev set netmask $_netmask
+ devinfo_$dev set extras $_extras
+ devinfo_$dev set use_rtsol $use_rtsol
+ devinfo_$dev set use_dhcp $use_dhcp
+
+ if [ "$use_dhcp" -o "$_ipaddr" ]; then
+ if [ "$use_dhcp" ]; then
+ cp="DHCP${extras:+ $extras}"
+ else
+ cp="inet $_ipaddr netmask $_netmask${extras:+ $extras}"
+ fi
+ setvar $VAR_IFCONFIG$devname "$cp"
+ fi
+ [ "$use_rtsol" ] &&
+ setvar $VAR_IPV6_ENABLE "YES"
+
+ [ "$use_dhcp" ] ||
+ f_config_resolv # XXX this will do it on the MFS copy
+
+ return $DIALOG_OK
+}
+
+# f_device_scan_tcp [$var_to_set]
+#
+# Scan for the first active/configured TCP/IP device. The name of the interface
+# is printed to stderr like other dialog(1)-based functions (stdout is reserved
+# for dialog(1) interaction) if $var_to_set is missing or NULL. Returns failure
+# if no active/configured interface
+#
+f_device_scan_tcp()
+{
+ local __var_to_set="$1" __iface
+ for __iface in $( ifconfig -l ); do
+ if ifconfig $__iface | awk '
+ BEGIN {
+ has_inet = has_inet6 = is_ethernet = 0
+ is_usable = 1
+ }
+ ( $1 == "status:" && $2 != "active" ) { is_usable = 0; exit }
+ ( $1 == "inet" ) {
+ if ($2 == "0.0.0.0") { is_usable = 0; exit }
+ has_inet++
+ }
+ ( $1 == "inet6") { has_inet6++ }
+ ( $1 == "media:" ) {
+ if ($2 != "Ethernet") { is_usable = 0; exit }
+ is_ethernet = 1
+ }
+ END {
+ if (!(is_ethernet && (has_inet || has_inet6)))
+ is_usable = 0
+ exit ! is_usable
+ }'; then
+ f_interactive &&
+ f_show_msg "$msg_using_interface" "$__iface"
+ f_dprintf "f_device_scan_tcp found %s" "$__iface"
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" "$__iface"
+ else
+ echo "$__iface" >&2
+ fi
+ return $SUCCESS
+ fi
+ done
+
+ return $FAILURE
+}
+
+# f_device_select_tcp
+#
+# Prompt the user to select network interface to use for TCP/IP access.
+# Variables from variable.subr that can be used to script user input:
+#
+# VAR_NETWORK_DEVICE [Optional]
+# Either a comma-separated list of network interfaces to try when
+# setting up network access (e.g., "fxp0,em0") or "ANY" (case-
+# sensitive) to indicate that the first active and configured
+# interface is acceptable. If unset, the user is presented with a
+# menu of all available network interfaces.
+#
+# Returns success if a valid network interface has been selected.
+#
+f_device_select_tcp()
+{
+ local devs dev cnt if network_dev
+ f_getvar $VAR_NETWORK_DEVICE network_dev
+
+ f_dprintf "f_device_select_tcp: %s=[%s]" \
+ VAR_NETWORK_DEVICE "$network_dev"
+
+ if [ "$network_dev" ]; then
+ #
+ # This can be set to several types of values. If set to ANY,
+ # scan all network devices looking for a valid link, and go
+ # with the first device found. Can also be specified as a
+ # comma delimited list, with each network device tried in
+ # order. Can also be set to a single network device.
+ #
+ [ "$network_dev" = "ANY" ] && f_device_scan_tcp network_dev
+
+ while [ "$network_dev" ]; do
+ case "$network_dev" in
+ *,*) if="${network_dev%%,*}"
+ network_dev="${network_dev#*,}"
+ ;;
+ *) if="$network_dev"
+ network_dev=
+ esac
+
+ f_device_find -1 "$if" $DEVICE_TYPE_NETWORK dev
+ f_device_dialog_tcp $dev
+ if [ $? -eq $DIALOG_OK ]; then
+ setvar $VAR_NETWORK_DEVICE $if
+ return $DIALOG_OK
+ fi
+ done
+
+ f_interactive && f_show_msg "$msg_no_network_devices"
+ return $DIALOG_CANCEL
+
+ fi # $network_dev
+
+ f_device_find "" $DEVICE_TYPE_NETWORK devs
+ f_count cnt $devs
+ dev="${devs%%[$IFS]*}"
+ $dev get name if
+
+ f_quietly f_getvar NETWORK_CONFIGURED # for debugging info
+ if ! f_running_as_init &&
+ ! [ "${NETWORK_CONFIGURED+set}" -a "$NETWORK_CONFIGURED" = "NO" ]
+ then
+ trap 'f_interrupt' SIGINT
+ if f_dialog_yesno "$msg_assume_network_is_already_configured"
+ then
+ setvar $VAR_NETWORK_DEVICE $if
+ return $DIALOG_OK
+ fi
+ fi
+
+ local retval=$SUCCESS
+ if [ ${cnt:=0} -eq 0 ]; then
+ f_show_msg "$msg_no_network_devices"
+ retval=$DIALOG_CANCEL
+ elif [ $cnt -eq 1 ]; then
+ f_device_dialog_tcp $dev
+ retval=$?
+ [ $retval -eq $DIALOG_OK ] && setvar $VAR_NETWORK_DEVICE $if
+ else
+ local title="$msg_network_interface_information_required"
+ local prompt="$msg_please_select_ethernet_device_to_configure"
+ local hline="$hline_arrows_tab_enter"
+
+ dev=$( f_device_menu \
+ "$title" "$prompt" "$hline" $DEVICE_TYPE_NETWORK \
+ "$NETWORK_DEVICE_HELPFILE" \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD ) ||
+ return $DIALOG_CANCEL
+
+ f_device_dialog_tcp $dev
+ retval=$?
+ if [ $retval -eq $DIALOG_OK ]; then
+ f_struct_copy "$dev" device_network
+ setvar $VAR_NETWORK_DEVICE device_network
+ else
+ f_struct_free device_network
+ fi
+ fi
+
+ return $retval
+}
+
+# f_dialog_menu_select_tcp
+#
+# Like f_dialog_select_tcp() above, but do it from a menu that doesn't care
+# about status. In other words, where f_dialog_select_tcp() will not display a
+# menu if scripted, this function will always display the menu of available
+# network interfaces.
+#
+f_dialog_menu_select_tcp()
+{
+ local private use_dhcp name
+ NETWORK_CONFIGURED=NO f_device_select_tcp
+ if f_struct device_network &&
+ device_network get private private &&
+ f_struct_copy "$private" di &&
+ di get use_dhcp use_dhcp &&
+ [ ! "$use_dhcp" ] &&
+ device_network get name name &&
+ f_yesno "$msg_would_you_like_to_bring_interface_up" "$name"
+ then
+ if ! f_device_init device_network; then
+ f_show_msg "$msg_initialization_of_device_failed" \
+ "$name"
+ fi
+ fi
+ return $DIALOG_OK
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." media/tcpip.subr
+
+fi # ! $_MEDIA_TCPIP_SUBR
diff --git a/usr.sbin/bsdconfig/share/media/ufs.subr b/usr.sbin/bsdconfig/share/media/ufs.subr
new file mode 100644
index 0000000..27e2f27
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/media/ufs.subr
@@ -0,0 +1,198 @@
+if [ ! "$_MEDIA_UFS_SUBR" ]; then _MEDIA_UFS_SUBR=1
+#
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." media/ufs.subr
+f_include $BSDCFG_SHARE/device.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/media/common.subr
+f_include $BSDCFG_SHARE/struct.subr
+f_include $BSDCFG_SHARE/variable.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+
+############################################################ GLOBALS
+
+UFS_MOUNTED=
+
+############################################################ FUNCTIONS
+
+# f_media_set_ufs
+#
+# Return success if we both found and set the media type to be a UFS partition.
+# Variables from variable.subr that can be used to script user input:
+#
+# VAR_UFS_PATH
+# Path to a UFS character device node to be used with mount(8) in
+# mounting a UFS formatted partition. Valid examples include:
+# /dev/da0s1a
+# /dev/ad4s1e
+# However, other forms may be valid (see mount(8) for additional
+# information).
+#
+f_media_set_ufs()
+{
+ local ufs
+
+ f_media_close
+
+ local devs ndevs
+ f_device_find "" $DEVICE_TYPE_UFS devs
+ f_count ndevs $devs
+
+ if [ ${ndevs:=0} -eq 0 ]; then
+ f_variable_get_value $VAR_UFS_PATH \
+ "$msg_enter_the_device_name_of_a_ufs_formatted_partition"
+ f_getvar $VAR_UFS_PATH ufs
+ [ "$ufs" ] || return $FAILURE
+
+ local fstype
+ fstype=$( df -nT $ufs 2> /dev/null |
+ awk '!/Type/{print $2;exit}' )
+
+ f_struct_new DEVICE device_ufs
+ device_ufs set name ${fstype:-ufs}
+ device_ufs set devname "$ufs"
+ device_ufs set get f_media_get_ufs
+ device_ufs set init f_media_init_ufs
+ device_ufs set shutdown f_media_shutdown_ufs
+ device_ufs unset private
+
+ f_struct_copy device_ufs device_media
+ f_struct_free device_ufs
+ elif [ $ndevs -eq 1 ]; then
+ f_struct_copy $devs device_media
+ else
+ local dev
+ local title="$msg_choose_a_ufs_partition"
+ local prompt="$msg_please_select_ufs_partition"
+ local hline=""
+
+ dev=$( f_device_menu \
+ "$title" "$prompt" "$hline" $DEVICE_TYPE_UFS \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD ) ||
+ return $FAILURE
+
+ f_struct_copy "$dev" device_media
+ fi
+
+ f_struct device_media || return $FAILURE
+}
+
+# f_media_init_ufs $device
+#
+# Initializes the UFS media device. Returns success if able to mount the UFS
+# partition device using mount(1).
+#
+f_media_init_ufs()
+{
+ local funcname=f_media_init_ufs
+ local dev="$1" devname err
+
+ $dev get devname devname || return $FAILURE
+ f_dprintf "Init routine called for UFS device. devname=[%s]" \
+ "$devname"
+
+ if [ "$UFS_MOUNTED" ]; then
+ f_dprintf "UFS device already mounted."
+ return $SUCCESS
+ fi
+
+ if [ ! -e "$devname" ]; then
+ f_show_msg "$msg_no_such_file_or_directory" \
+ "f_media_init_ufs" "$devname"
+ return $FAILURE
+ fi
+
+ if [ ! -e "$MOUNTPOINT" ]; then
+ f_eval_catch $funcname mkdir 'mkdir -p "%s"' "$MOUNTPOINT" ||
+ return $FAILURE
+ fi
+
+ if ! f_eval_catch -dk err $funcname mount \
+ 'mount "%s" "%s"' "$devname" "$MOUNTPOINT"
+ then
+ err="${err#mount: }"; err="${err#$devname : }"
+ f_show_msg "$msg_error_mounting_device" \
+ "$devname" "$MOUNTPOINT" "$err"
+ return $FAILURE
+ fi
+ UFS_MOUNTED=1
+ return $SUCCESS
+}
+
+# f_media_get_ufs $device $file [$probe_type]
+#
+# Returns data from $file on a mounted UFS partition device. Similar to cat(1).
+# If $probe_type is present and non-NULL, returns success if $file exists. If
+# $probe_type is equal to $PROBE_SIZE, prints the size of $file in bytes to
+# standard-out.
+#
+f_media_get_ufs()
+{
+ local dev="$1" file="$2" probe_type="$3"
+ local name
+
+ $dev get name name
+ f_dprintf "f_media_get_ufs: dev=[%s] file=[%s] probe_type=%s" \
+ "$name" "$file" "$probe_type"
+
+ f_media_generic_get "$MOUNTPOINT" "$file" "$probe_type"
+}
+
+# f_media_shutdown_ufs $device
+#
+# Shuts down the UFS device using umount(8). Return status should be ignored.
+#
+f_media_shutdown_ufs()
+{
+ local funcname=f_media_shutdown_ufs
+ local dev="$1" err
+
+ [ "$UFS_MOUNTED" ] || return $FAILURE
+
+ if ! f_eval_catch -dk err $funcname umount \
+ 'umount -f "%s"' "$MOUNTPOINT"
+ then
+ err="${err#umount: }"; err="${err#*: }"
+ f_show_msg "$msg_could_not_unmount_the_ufs_partition" \
+ "$MOUNTPOINT" "$err"
+ else
+ UFS_MOUNTED=
+ fi
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." media/ufs.subr
+
+fi # ! $_MEDIA_UFS_SUBR
diff --git a/usr.sbin/bsdconfig/share/media/usb.subr b/usr.sbin/bsdconfig/share/media/usb.subr
new file mode 100644
index 0000000..5ee9bc0
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/media/usb.subr
@@ -0,0 +1,176 @@
+if [ ! "$_MEDIA_USB_SUBR" ]; then _MEDIA_USB_SUBR=1
+#
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." media/usb.subr
+f_include $BSDCFG_SHARE/device.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/media/common.subr
+f_include $BSDCFG_SHARE/struct.subr
+f_include $BSDCFG_SHARE/variable.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+
+############################################################ GLOBALS
+
+USB_MOUNTED=
+
+############################################################ FUNCTIONS
+
+# f_media_set_usb
+#
+# Attempt to use USB as the media type. Return success if we both found and set
+# the media type to be a USB drive.
+#
+f_media_set_usb()
+{
+ f_media_close
+
+ local devs ndevs
+ f_device_find "" $DEVICE_TYPE_USB devs
+ f_count ndevs $devs
+
+ if [ ${ndevs:=0} -eq 0 ]; then
+ f_show_msg "$msg_no_usb_devices_found"
+ return $FAILURE
+ elif [ $ndevs -eq 1 ]; then
+ f_struct_copy $devs device_media
+ else
+ local dev
+ local title="$msg_choose_a_usb_drive"
+ local prompt="$msg_please_select_a_usb_drive"
+ local hline=
+
+ dev=$( f_device_menu \
+ "$title" "$prompt" "$hline" $DEVICE_TYPE_USB \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD ) ||
+ return $FAILURE
+
+ f_struct_copy "$dev" device_media
+ fi
+
+ f_struct device_media &&
+ device_media unset private
+
+ if f_interactive; then
+ local name
+ f_struct device_media get name name
+ f_show_msg "$msg_using_usb_device" "$name"
+ fi
+
+ f_struct device_media || return $FAILURE
+}
+
+# f_media_init_usb $device
+#
+# Initializes the USB media device. Returns success if able to mount the USB
+# disk device using mount(8).
+#
+f_media_init_usb()
+{
+ local funcname=f_media_init_usb
+ local dev="$1" devname err
+
+ $dev get devname devname || return $FAILURE
+ f_dprintf "Init routine called for USB device. devname=[%s]" \
+ "$devname"
+
+ if [ "$USB_MOUNTED" ]; then
+ f_dprintf "USB device already mounted."
+ return $SUCCESS
+ fi
+
+ if [ ! -e "$MOUNTPOINT" ]; then
+ f_eval_catch $funcname mkdir 'mkdir -p "%s"' "$MOUNTPOINT" ||
+ return $FAILURE
+ fi
+
+ if f_eval_catch -dk err $funcname mount \
+ 'mount "%s" "%s"' "$devname" "$MOUNTPOINT"
+ then
+ USB_MOUNTED=1
+ return $SUCCESS
+ fi
+
+ err="${err#mount: }"; err="${err#$devname: }"
+ f_show_msg "$msg_error_mounting_usb_drive" \
+ "$devname" "$MOUNTPOINT" "$err"
+ return $FAILURE
+}
+
+# f_media_get_usb $device $file [$probe_type]
+#
+# Returns data from $file on a mounted USB disk device. Similar to cat(1). If
+# $probe_type is present and non-NULL, returns success if $file exists. If
+# $probe_type is equal to $PROBE_SIZE, prints the size of $file in bytes to
+# standard-out.
+#
+f_media_get_usb()
+{
+ local dev="$1" file="$2" probe_type="$3"
+ local name
+
+ $dev get name name
+ f_dprintf "f_media_get_usb: dev=[%s] file=[%s] probe_type=%s" \
+ "$name" "$file" "$probe_type"
+
+ f_media_generic_get "$MOUNTPOINT" "$file" "$probe_type"
+}
+
+# f_media_shutdown_usb $device
+#
+# Shuts down the USB disk device using umount(8). Return status should be
+# ignored.
+#
+f_media_shutdown_usb()
+{
+ local funcname=f_media_shutdown_usb
+ local dev="$1" err
+
+ [ "$USB_MOUNTED" ] || return $FAILURE
+
+ if ! f_eval_catch -dk err $funcname umount \
+ 'umount -f "%s"' "$MOUNTPOINT"
+ then
+ err="${err#umount: }"; err="${err#*: }"
+ f_show_msg "$msg_could_not_unmount_the_ufs_partition" \
+ "$MOUNTPOINT" "$err"
+ else
+ USB_MOUNTED=
+ fi
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." media/usb.subr
+
+fi # ! $_MEDIA_USB_SUBR
diff --git a/usr.sbin/bsdconfig/share/mustberoot.subr b/usr.sbin/bsdconfig/share/mustberoot.subr
new file mode 100644
index 0000000..88ff818
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/mustberoot.subr
@@ -0,0 +1,424 @@
+if [ ! "$_MUSTBEROOT_SUBR" ]; then _MUSTBEROOT_SUBR=1
+#
+# Copyright (c) 2006-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." mustberoot.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/strings.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+
+############################################################ CONFIGURATION
+# NOTE: These are not able to be overridden/inherited for security purposes.
+
+#
+# Number of tries a user gets to enter his/her password before we log the
+# sudo(8) failure and exit.
+#
+PASSWD_TRIES=3
+
+#
+# While in SECURE mode, should authentication as `root' be allowed? Set to
+# non-NULL to enable authentication as `root', otherwise disabled.
+#
+# WARNING:
+# Unless using a custom sudo(8) configuration, user `root' should not be
+# allowed because no password is required to become `root' when already `root'
+# and therefore, any value entered as password will work.
+#
+SECURE_ALLOW_ROOT=
+
+#
+# While in SECURE mode, should we divulge (through error message) when the
+# requested authentication user does not exist? Set to non-NULL to enable,
+# otherwise a non-existent user is treated like an invalid password.
+#
+SECURE_DIVULGE_UNKNOWN_USER=
+
+############################################################ FUNCTIONS
+
+# f_become_root_via_sudo
+#
+# If not running as root, prompt for sudo(8) credentials to become root.
+# Re-execution of the current program via sudo is automatically handled.
+#
+# The following environment variables effect functionality:
+#
+# USE_XDIALOG Either NULL or Non-NULL. If given a value will indicate
+# that Xdialog(1) should be used instead of dialog(1).
+#
+f_become_root_via_sudo()
+{
+ local funcname=f_become_root_via_sudo
+ local prompt hline height width rows msg
+
+ [ "$( id -u )" = "0" ] && return $SUCCESS
+
+ f_have sudo || f_die 1 "$msg_must_be_root_to_execute" "$pgm"
+
+ #
+ # Ask the user if it's OK to become root via sudo(8) and give them
+ # the option to save this preference (by touch(1)ing a file in the
+ # user's $HOME directory).
+ #
+ local checkpath="${HOME%/}/.bsdconfig_uses_sudo"
+ if [ ! -e "$checkpath" ]; then
+ f_sprintf prompt "$msg_you_are_not_root_but" bsdconfig
+ f_sprintf msg "$msg_always_try_sudo_when_run_as" "$USER"
+ local menu_list="
+ 'X' '$msg_cancel_exit'
+ '1' '$msg'
+ '2' '$msg_try_sudo_only_this_once'
+ " # END-QUOTE
+ hline="$hline_arrows_tab_enter"
+
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ local mtag
+ mtag=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ ) || f_die
+ f_dialog_data_sanitize mtag
+
+ case "$mtag" in
+ X) # Cancel/Exit
+ f_die ;;
+ 1) # Always try sudo(8) when run as $user
+ f_eval_catch $funcname touch \
+ 'touch "%s"' "$checkpath" &&
+ f_show_msg "$msg_created_path" "$checkpath"
+ esac
+ else
+ #
+ # This user has created the path signing-off on sudo(8)-use
+ # but let's still give them a short/quick/unobtrusive reminder
+ #
+ f_dialog_info "$msg_becoming_root_via_sudo"
+ [ "$USE_XDIALOG" ] || sleep 0.6
+ fi
+
+ #
+ # Check sudo(8) access before prompting for password.
+ #
+ :| sudo -S -v 2> /dev/null
+ if [ $? -ne $SUCCESS ]; then
+ #
+ # sudo(8) access denied. Prompt for their password.
+ #
+ prompt="$msg_please_enter_password"
+ hline="$hline_alnum_punc_tab_enter"
+ f_dialog_inputbox_size height width \
+ "$DIALOG_TITLE" \
+ "$DIALOG_BACKTITLE" \
+ "$prompt" \
+ "$hline"
+
+ #
+ # Continue prompting until they either Cancel, succeed
+ # or exceed the number of allowed failures.
+ #
+ local password nfailures=0 retval
+ while [ $nfailures -lt $PASSWD_TRIES ]; do
+ if [ "$USE_XDIALOG" ]; then
+ password=$( $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --hline "$hline" \
+ --ok-label "$msg_ok" \
+ --cancel-label "$msg_cancel" \
+ --password --inputbox "$prompt" \
+ $height $width \
+ 2>&1 > /dev/null
+ )
+ retval=$?
+
+ # Catch X11-related errors
+ if [ $retval -eq $DIALOG_ESC ]; then
+ f_die $retval "$password"
+ elif [ $retval -ne $DIALOG_OK ]; then
+ # User cancelled
+ exit $retval
+ fi
+ else
+ password=$( $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --hline "$hline" \
+ --ok-label "$msg_ok" \
+ --cancel-label "$msg_cancel" \
+ --insecure \
+ --passwordbox "$prompt" \
+ $height $width \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ ) || exit $?
+ fi
+ debug= f_dialog_line_sanitize password
+
+ #
+ # Validate sudo(8) credentials
+ #
+ sudo -S -v 2> /dev/null <<-EOF
+ $password
+ EOF
+ retval=$?
+ unset password # scrub memory
+ if [ $retval -eq $SUCCESS ]; then
+ # Access granted...
+ break
+ else
+ # Access denied...
+ nfailures=$(( $nfailures + 1 ))
+
+ # introduce a short delay
+ if [ $nfailures -lt $PASSWD_TRIES ]; then
+ f_dialog_info "$msg_sorry_try_again"
+ sleep 1
+ fi
+ fi
+ done
+
+ #
+ # If user exhausted number of allowed password tries, log
+ # the security event and exit immediately.
+ #
+ if [ $nfailures -ge $PASSWD_TRIES ]; then
+ f_sprintf msg "$msg_nfailed_attempts" "$nfailures"
+ logger -p auth.notice -t sudo " " \
+ "$USER : $msg" \
+ "; TTY=$(tty)" \
+ "; PWD=$PWD" \
+ "; USER=root" \
+ "; COMMAND=$0"
+ f_die 1 "sudo: $msg"
+ fi
+ fi
+
+ # Use xauth(1) to grant root the ability to use this X11/SSH session
+ if [ "$USE_XDIALOG" -a "$SSH_CONNECTION" -a "$DISPLAY" ]; then
+ f_have xauth || f_die 1 \
+ "$msg_no_such_file_or_directory" "$pgm" "xauth"
+ local HOSTNAME displaynum
+ HOSTNAME=$(hostname)
+ displaynum="${DISPLAY#*:}"
+ xauth -f ~/.Xauthority extract - $HOSTNAME/unix:$displaynum \
+ $HOSTNAME:$displaynum | sudo sh -c 'xauth -ivf \
+ ~root/.Xauthority merge - > /dev/null 2>&1'
+ fi
+
+ # Re-execute ourselves with sudo(8)
+ f_dprintf "%s: Becoming root via sudo(8)..." mustberoot.subr
+ if [ $ARGC -gt 0 ]; then
+ exec sudo "$0" $ARGV
+ else
+ exec sudo "$0"
+ fi
+ exit $? # Never reached unless error
+}
+
+# f_authenticate_some_user
+#
+# Only used if running as root and requires X11 (see USE_XDIALOG below).
+# Prompts the user to enter a username and password to be authenticated via
+# sudo(8) to proceed.
+#
+# The following environment variables effect functionality:
+#
+# USE_XDIALOG Either NULL or Non-NULL. If given a value will indicate
+# that Xdialog(1) should be used instead of dialog(1).
+#
+f_authenticate_some_user()
+{
+ local msg hline height width
+
+ f_have sudo || f_die 1 "$msg_must_be_root_to_execute" "$pgm"
+
+ #
+ # Secure-mode has been requested.
+ #
+
+ [ "$USE_XDIALOG" ] || f_die 1 "$msg_secure_mode_requires_x11"
+ [ "$(id -u)" = "0" ] || f_die 1 "$msg_secure_mode_requires_root"
+
+ #
+ # Prompt for sudo(8) credentials.
+ #
+
+ msg="$msg_please_enter_username_password"
+ hline="$hline_alnum_punc_tab_enter"
+ f_xdialog_2inputsbox_size height width \
+ "$DIALOG_TITLE" \
+ "$DIALOG_BACKTITLE" \
+ "$msg" \
+ "$field_username" "" \
+ "$field_password" ""
+ height=$(( $height + 2 )) # Add height for --password
+
+ #
+ # Continue prompting until they either Cancel, succeed or exceed the
+ # number of allowed failures.
+ #
+ local user_pass nfailures=0 retval
+ while [ $nfailures -lt $PASSWD_TRIES ]; do
+ user_pass=$( $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --hline "$hline" \
+ --ok-label "$msg_ok" \
+ --cancel-label "$msg_cancel" \
+ --password --2inputsbox "$msg" \
+ $height $width \
+ "$field_username" "" \
+ "$field_password" "" \
+ 2>&1 > /dev/null )
+ retval=$?
+
+ # Catch X11-related errors
+ [ $retval -eq $DIALOG_ESC ] && f_die $retval "$user_pass"
+
+ # Exit if the user cancelled.
+ [ $retval -eq $DIALOG_OK ] || exit $retval
+
+ #
+ # Make sure the user exists and is non-root
+ #
+ local user password
+ user="${user_pass%%/*}"
+ password="${user_pass#*/}"
+ unset user_pass # scrub memory
+ if [ ! "$user" ]; then
+ nfailures=$(( $nfailures + 1 ))
+ f_show_msg "$msg_no_username"
+ continue
+ fi
+ if [ ! "$SECURE_ALLOW_ROOT" ]; then
+ case "$user" in
+ root|toor)
+ nfailures=$(( $nfailures + 1 ))
+ f_show_msg "$msg_user_disallowed" "$user"
+ continue
+ esac
+ fi
+ if ! f_quietly id "$user"; then
+ nfailures=$(( $nfailures + 1 ))
+ if [ "$SECURE_DIVULGE_UNKNOWN_USER" ]; then
+ f_show_msg "$msg_unknown_user" "$user"
+ elif [ $nfailures -lt $PASSWD_TRIES ]; then
+ f_dialog_info "$msg_sorry_try_again"
+ sleep 1
+ fi
+ continue
+ fi
+
+ #
+ # Validate sudo(8) credentials for given user
+ #
+ su -m "$user" <<-EOF
+ sh <<EOS
+ sudo -k
+ sudo -S -v 2> /dev/null <<EOP
+ $password
+ EOP
+ EOS
+ EOF
+ retval=$?
+ unset user
+ unset password # scrub memory
+
+ if [ $retval -eq $SUCCESS ]; then
+ # Access granted...
+ break
+ else
+ # Access denied...
+ nfailures=$(( $nfailures + 1 ))
+
+ # introduce a short delay
+ if [ $nfailures -lt $PASSWD_TRIES ]; then
+ f_dialog_info "$msg_sorry_try_again"
+ sleep 1
+ fi
+ fi
+ done
+
+ #
+ # If user exhausted number of allowed password tries, log
+ # the security event and exit immediately.
+ #
+ if [ $nfailures -ge $PASSWD_TRIES ]; then
+ f_sprintf msg "$msg_nfailed_attempts" "$nfailures"
+ logger -p auth.notice -t sudo " " \
+ "${SUDO_USER:-$USER} : $msg" \
+ "; TTY=$(tty)" \
+ "; PWD=$PWD" \
+ "; USER=root" \
+ "; COMMAND=$0"
+ f_die 1 "sudo: $message"
+ fi
+}
+
+# f_mustberoot_init
+#
+# If not already root, make the switch to root by re-executing ourselves via
+# sudo(8) using user-supplied credentials.
+#
+# The following environment variables effect functionality:
+#
+# SECURE Either NULL or Non-NULL. If given a value will indicate
+# that (while running as root) sudo(8) authentication is
+# required to proceed.
+#
+f_mustberoot_init()
+{
+ if [ "$(id -u)" != "0" -a ! "$SECURE" ]; then
+ f_become_root_via_sudo
+ elif [ "$SECURE" ]; then
+ f_authenticate_some_user
+ fi
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." mustberoot.subr
+
+fi # ! $_MUSTBEROOT_SUBR
diff --git a/usr.sbin/bsdconfig/share/packages/Makefile b/usr.sbin/bsdconfig/share/packages/Makefile
new file mode 100644
index 0000000..bc0e59a
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/packages/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+FILESDIR= ${SHAREDIR}/bsdconfig/packages
+FILES= categories.subr index.subr musthavepkg.subr packages.subr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/share/packages/Makefile.depend b/usr.sbin/bsdconfig/share/packages/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/packages/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/share/packages/categories.subr b/usr.sbin/bsdconfig/share/packages/categories.subr
new file mode 100644
index 0000000..474c41d
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/packages/categories.subr
@@ -0,0 +1,209 @@
+if [ ! "$_PACKAGES_CATEGORIES_SUBR" ]; then _PACKAGES_CATEGORIES_SUBR=1
+#
+# Copyright (c) 2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." packages/categories.subr
+f_include $BSDCFG_SHARE/strings.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+
+############################################################ GLOBALS
+
+CATEGORIES=
+NCATEGORIES=0
+
+############################################################ FUNCTIONS
+
+# f_category_desc_get $category [$var_to_set]
+#
+# Fetch the description of a given category. Returns success if a match was
+# found, otherwise failure.
+#
+# If $var_to_set is missing or NULL, the category description is printed to
+# standard out for capturing in a sub-shell (which is less-recommended because
+# of performance degredation; for example, when called in a loop).
+#
+f_category_desc_get()
+{
+ local __category="$1" __var_to_set="$2" __cat __varcat
+
+ # Return failure if $category
+ [ "$__category" ] || return $FAILURE
+
+ for __cat in $CATEGORIES; do
+ [ "$__cat" = "$__category" ] || continue
+ f_str2varname $__cat __varcat
+ f_getvar _category_$__varcat $__var_to_set
+ return $?
+ done
+ return $FAILURE
+}
+
+# f_category_desc_set $category $desc
+#
+# Store a description in-association with a category. $category should be
+# alphanumeric and can include the underscore [_] but should not contain
+# whitespace. Returns success unless $category is NULL or no arguments. Use the
+# f_category_desc_get() routine with the same $category to retrieve the stored
+# description.
+#
+f_category_desc_set()
+{
+ local category="$1" desc="$2"
+ local cat varcat found=
+ [ "$category" ] || return $FAILURE
+ for cat in $CATEGORIES; do
+ [ "$cat" = "$category" ] || continue
+ f_str2varname $cat varcat
+ f_isset _category_$varcat || continue
+ found=1 && break
+ done
+ if [ ! "$found" ]; then
+ CATEGORIES="$CATEGORIES $category"
+ fi
+ f_str2varname $category varcat
+ setvar "_category_$varcat" "$desc"
+ # Export the variable for awk(1) ENVIRON visibility
+ export "_category_$varcat"
+ return $SUCCESS
+}
+
+############################################################ MAIN
+
+#
+# Load descriptions for package categories. Note that we don't internationalize
+# category names because this would be confusing for people used to browsing
+# the FTP mirrors or are otherwise familiar with an interface that does not
+# provide internationalized names. The descriptions can be used to provide i18n
+# users a description of the non-i18n category name.
+#
+f_category() { f_category_desc_set "$1" "$2"; }
+f_category All "$msg_all_desc"
+f_category accessibility "$msg_accessibility_desc"
+f_category afterstep "$msg_afterstep_desc"
+f_category arabic "$msg_arabic_desc"
+f_category archivers "$msg_archivers_desc"
+f_category astro "$msg_astro_desc"
+f_category audio "$msg_audio_desc"
+f_category benchmarks "$msg_benchmarks_desc"
+f_category biology "$msg_biology_desc"
+f_category cad "$msg_cad_desc"
+f_category chinese "$msg_chinese_desc"
+f_category comms "$msg_comms_desc"
+f_category converters "$msg_converters_desc"
+f_category databases "$msg_databases_desc"
+f_category deskutils "$msg_deskutils_desc"
+f_category devel "$msg_devel_desc"
+f_category dns "$msg_dns_desc"
+f_category docs "$msg_docs_desc"
+f_category editors "$msg_editors_desc"
+f_category elisp "$msg_elisp_desc"
+f_category emulators "$msg_emulators_desc"
+f_category enlightenment "$msg_enlightenment_desc"
+f_category finance "$msg_finance_desc"
+f_category french "$msg_french_desc"
+f_category ftp "$msg_ftp_desc"
+f_category games "$msg_games_desc"
+f_category geography "$msg_geography_desc"
+f_category german "$msg_german_desc"
+f_category gnome "$msg_gnome_desc"
+f_category gnustep "$msg_gnustep_desc"
+f_category graphics "$msg_graphics_desc"
+f_category hamradio "$msg_hamradio_desc"
+f_category haskell "$msg_haskell_desc"
+f_category hebrew "$msg_hebrew_desc"
+f_category hungarian "$msg_hungarian_desc"
+f_category ipv6 "$msg_ipv6_desc"
+f_category irc "$msg_irc_desc"
+f_category japanese "$msg_japanese_desc"
+f_category java "$msg_java_desc"
+f_category kde "$msg_kde_desc"
+f_category kld "$msg_kld_desc"
+f_category korean "$msg_korean_desc"
+f_category lang "$msg_lang_desc"
+f_category linux "$msg_linux_desc"
+f_category lisp "$msg_lisp_desc"
+f_category mail "$msg_mail_desc"
+f_category math "$msg_math_desc"
+f_category mbone "$msg_mbone_desc"
+f_category misc "$msg_misc_desc"
+f_category multimedia "$msg_multimedia_desc"
+f_category net "$msg_net_desc"
+f_category net-im "$msg_net_im_desc"
+f_category net-mgmt "$msg_net_mgmt_desc"
+f_category net-p2p "$msg_net_p2p_desc"
+f_category news "$msg_news_desc"
+f_category palm "$msg_palm_desc"
+f_category parallel "$msg_parallel_desc"
+f_category pear "$msg_pear_desc"
+f_category perl5 "$msg_perl5_desc"
+f_category plan9 "$msg_plan9_desc"
+f_category polish "$msg_polish_desc"
+f_category ports-mgmt "$msg_ports_mgmt_desc"
+f_category portuguese "$msg_portuguese_desc"
+f_category print "$msg_print_desc"
+f_category python "$msg_python_desc"
+f_category ruby "$msg_ruby_desc"
+f_category rubygems "$msg_rubygems_desc"
+f_category russian "$msg_russian_desc"
+f_category scheme "$msg_scheme_desc"
+f_category science "$msg_science_desc"
+f_category security "$msg_security_desc"
+f_category shells "$msg_shells_desc"
+f_category spanish "$msg_spanish_desc"
+f_category sysutils "$msg_sysutils_desc"
+f_category tcl "$msg_tcl_desc"
+f_category textproc "$msg_textproc_desc"
+f_category tk "$msg_tk_desc"
+f_category ukrainian "$msg_ukrainian_desc"
+f_category vietnamese "$msg_vietnamese_desc"
+f_category windowmaker "$msg_windowmaker_desc"
+f_category www "$msg_www_desc"
+f_category x11 "$msg_x11_desc"
+f_category x11-clocks "$msg_x11_clocks_desc"
+f_category x11-drivers "$msg_x11_drivers_desc"
+f_category x11-fm "$msg_x11_fm_desc"
+f_category x11-fonts "$msg_x11_fonts_desc"
+f_category x11-servers "$msg_x11_servers_desc"
+f_category x11-themes "$msg_x11_themes_desc"
+f_category x11-toolkits "$msg_x11_toolkits_desc"
+f_category x11-wm "$msg_x11_wm_desc"
+f_category xfce "$msg_xfce_desc"
+f_category zope "$msg_zope_desc"
+
+f_count NCATEGORIES $CATEGORIES
+f_dprintf "%s: Initialized %u package category descriptions." \
+ packages/categories.subr $NCATEGORIES
+
+f_dprintf "%s: Successfully loaded." packages/categories.subr
+
+fi # ! $_PACKAGES_CATEGORIES_SUBR
diff --git a/usr.sbin/bsdconfig/share/packages/index.subr b/usr.sbin/bsdconfig/share/packages/index.subr
new file mode 100644
index 0000000..f3c1713
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/packages/index.subr
@@ -0,0 +1,414 @@
+if [ ! "$_PACKAGES_INDEX_SUBR" ]; then _PACKAGES_INDEX_SUBR=1
+#
+# Copyright (c) 2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." packages/index.subr
+f_include $BSDCFG_SHARE/device.subr
+f_include $BSDCFG_SHARE/media/common.subr
+f_include $BSDCFG_SHARE/packages/musthavepkg.subr
+f_include $BSDCFG_SHARE/strings.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+
+############################################################ GLOBALS
+
+PACKAGE_INDEX=
+_INDEX_INITTED=
+
+#
+# Default path to pkg(8) repo-packagesite.sqlite database
+#
+SQLITE_REPO="/var/db/pkg/repo-packagesite.sqlite"
+
+#
+# Default path to on-disk cache INDEX file
+#
+PACKAGES_INDEX_CACHEFILE="/var/run/bsdconfig/packages_INDEX.cache"
+
+############################################################ FUNCTIONS
+
+# f_index_initialize [$var_to_set]
+#
+# Read and initialize the global index. Returns success unless media cannot be
+# initialized for any reason (e.g. user cancels media selection dialog or an
+# error occurs). The index is sorted before being loaded into $var_to_set.
+#
+# NOTE: The index is processed with f_index_read() [below] after being loaded.
+#
+f_index_initialize()
+{
+ local __funcname=f_index_initialize
+ local __var_to_set="${1:-PACKAGE_INDEX}"
+
+ [ "$_INDEX_INITTED" ] && return $SUCCESS
+
+ # Got any media?
+ f_media_verify || return $FAILURE
+
+ # Make sure we have a usable pkg(8) with $PKG_ABI
+ f_musthavepkg_init
+
+ # Does it move when you kick it?
+ f_device_init device_media || return $FAILURE
+
+ f_show_info "$msg_attempting_to_update_repository_catalogue"
+
+ #
+ # Generate $PACKAGESITE variable for pkg(8) based on media type
+ #
+ local __type __data __site
+ device_media get type __type
+ device_media get private __data
+ case "$__type" in
+ $DEVICE_TYPE_DIRECTORY)
+ __site="file://$__data/packages/$PKG_ABI" ;;
+ $DEVICE_TYPE_FLOPPY)
+ __site="file://${__data:-$MOUNTPOINT}/packages/$PKG_ABI" ;;
+ $DEVICE_TYPE_FTP)
+ f_getvar $VAR_FTP_PATH __site
+ __site="$__site/packages/$PKG_ABI" ;;
+ $DEVICE_TYPE_HTTP)
+ f_getvar $VAR_HTTP_PATH __site
+ __site="$__site/$PKG_ABI/latest" ;;
+ $DEVICE_TYPE_HTTP_PROXY)
+ f_getvar $VAR_HTTP_PROXY_PATH __site
+ __site="$__site/packages/$PKG_ABI" ;;
+ $DEVICE_TYPE_CDROM)
+ __site="file://$MOUNTPOINT/packages/$PKG_ABI"
+ export REPOS_DIR="$MOUNTPOINT/packages/repos" ;;
+ *) # UFS, DISK, CDROM, USB, DOS, NFS, etc.
+ __site="file://$MOUNTPOINT/packages/$PKG_ABI"
+ esac
+
+ f_dprintf "PACKAGESITE=[%s]" "$__site"
+ if ! f_eval_catch $__funcname pkg \
+ 'PACKAGESITE="%s" pkg update' "$__site"
+ then
+ f_show_err "$msg_unable_to_update_pkg_from_selected_media"
+ f_device_shutdown device_media
+ return $FAILURE
+ fi
+
+ #
+ # Try to get contents from validated on-disk cache
+ #
+
+ #
+ # Calculate digest used to determine if the on-disk persistant cache
+ # INDEX (containing this digest on the first line) is valid and can be
+ # used to quickly populate the environment.
+ #
+ local __sqlite_digest
+ if ! __sqlite_digest=$( md5 < "$SQLITE_REPO" 2> /dev/null ); then
+ f_show_err "$msg_no_pkg_database_found"
+ f_device_shutdown device_media
+ return $FAILURE
+ fi
+
+ #
+ # Check to see if the persistant cache INDEX file exists
+ #
+ if [ -f "$PACKAGES_INDEX_CACHEFILE" ]; then
+ #
+ # Attempt to populate the environment with the (soon to be)
+ # validated on-disk cache. If validation fails, fall-back to
+ # generating a fresh cache.
+ #
+ if eval $__var_to_set='$(
+ ( # Get digest as the first word on first line
+ read digest rest_ignored
+
+ #
+ # If the stored digest matches the calculated-
+ # one populate the environment from the on-disk
+ # cache and provide success exit status.
+ #
+ if [ "$digest" = "$__sqlite_digest" ]; then
+ cat
+ exit $SUCCESS
+ else
+ # Otherwise, return the current value
+ eval echo \"\$__var_to_set\"
+ exit $FAILURE
+ fi
+ ) < "$PACKAGES_INDEX_CACHEFILE" 2> /dev/null
+ )'; then
+ f_show_info \
+ "$msg_located_index_now_reading_package_data_from_it"
+ if ! f_index_read "$__var_to_set"; then
+ f_show_err \
+ "$msg_io_or_format_error_on_index_file"
+ return $FAILURE
+ fi
+ _INDEX_INITTED=1
+ return $SUCCESS
+ fi
+ # Otherwise, fall-thru to create a fresh cache from scratch
+ fi
+
+ #
+ # If we reach this point, we need to generate the data from scratch
+ #
+
+ f_show_info "$msg_generating_index_from_pkg_database"
+ eval "$__var_to_set"='$( pkg rquery -I | sort )'
+
+ #
+ # Attempt to create the persistant on-disk cache
+ #
+
+ # Create a new temporary file to write to
+ local __tmpfile
+ if f_eval_catch -dk __tmpfile $__funcname mktemp \
+ 'mktemp -t "%s"' "$pgm"
+ then
+ # Write the temporary file contents
+ echo "$__sqlite_digest" > "$__tmpfile"
+ debug= f_getvar "$__var_to_set" >> "$__tmpfile"
+
+ # Finally, move the temporary file into place
+ case "$PACKAGES_INDEX_CACHEFILE" in
+ */*) f_eval_catch -d $__funcname mkdir \
+ 'mkdir -p "%s"' "${PACKAGES_INDEX_CACHEFILE%/*}"
+ esac
+ f_eval_catch -d $__funcname mv 'mv -f "%s" "%s"' \
+ "$__tmpfile" "$PACKAGES_INDEX_CACHEFILE"
+ fi
+
+ f_show_info "$msg_located_index_now_reading_package_data_from_it"
+ if ! f_index_read "$__var_to_set"; then
+ f_show_err "$msg_io_or_format_error_on_index_file"
+ return $FAILURE
+ fi
+
+ _INDEX_INITTED=1
+ return $SUCCESS
+}
+
+# f_index_read [$var_to_get]
+#
+# Process the INDEX file (contents contained in $var_to_get) and...
+#
+# 1. create a list ($CATEGORY_MENU_LIST) of categories with package counts
+# 2. For convenience, create $_npkgs holding the total number of all packages
+# 3. extract associative categories for each package into $_categories_$varpkg
+# 4. extract runtime dependencies for each package into $_rundeps_$varpkg
+# 5. extract a [sorted] list of categories into $PACKAGE_CATEGORIES
+# 6. create $_npkgs_$varcat holding the total number of packages in category
+#
+# NOTE: $varpkg is the product of f_str2varname $package varpkg
+# NOTE: $package is the name as it appears in the INDEX (no archive suffix)
+# NOTE: We only show categories for which there are at least one package.
+# NOTE: $varcat is the product of f_str2varname $category varcat
+#
+f_index_read()
+{
+ local var_to_get="${1:-PACKAGE_INDEX}"
+
+ # Export variables required by awk(1) below
+ export msg_no_description_provided
+ export msg_all msg_all_desc
+ export VALID_VARNAME_CHARS
+ export msg_packages
+
+ eval "$( debug= f_getvar "$var_to_get" | awk -F'|' '
+ function _asorti(src, dest)
+ {
+ k = nitems = 0
+
+ # Copy src indices to dest and calculate array length
+ for (i in src) dest[++nitems] = i
+
+ # Sort the array of indices (dest) using insertion sort method
+ for (i = 1; i <= nitems; k = i++)
+ {
+ idx = dest[i]
+ while ((k > 0) && (dest[k] > idx))
+ {
+ dest[k+1] = dest[k]
+ k--
+ }
+ dest[k+1] = idx
+ }
+
+ return nitems
+ }
+ function print_category(category, npkgs, desc)
+ {
+ cat = category
+ # Accent the category if the first page has been
+ # cached (also acting as a visitation indicator)
+ if ( ENVIRON["_index_page_" varcat "_1"] )
+ cat = cat "*"
+ printf "'\''%s'\'' '\''%s " packages "'\'' '\''%s'\''\n",
+ cat, npkgs, desc
+ }
+ BEGIN {
+ valid_chars = ENVIRON["VALID_VARNAME_CHARS"]
+ default_desc = ENVIRON["msg_no_description_provided"]
+ packages = ENVIRON["msg_packages"]
+ tpkgs = 0
+ prefix = ""
+ }
+ {
+ tpkgs++
+ varpkg = $1
+ gsub("[^" valid_chars "]", "_", varpkg)
+ print "_categories_" varpkg "=\"" $7 "\""
+ split($7, pkg_categories, /[[:space:]]+/)
+ for (pkg_category in pkg_categories)
+ categories[pkg_categories[pkg_category]]++
+ print "_rundeps_" varpkg "=\"" $9 "\""
+ }
+ END {
+ print "_npkgs=" tpkgs # For convenience, total package count
+
+ n = _asorti(categories, categories_sorted)
+
+ # Produce package counts for each category
+ for (i = 1; i <= n; i++)
+ {
+ cat = varcat = categories_sorted[i]
+ npkgs = categories[cat]
+ gsub("[^" valid_chars "]", "_", varcat)
+ print "_npkgs_" varcat "=\"" npkgs "\""
+ }
+
+ # Create menu list and generate list of categories at same time
+ print "CATEGORY_MENU_LIST=\""
+ print_category(ENVIRON["msg_all"], tpkgs,
+ ENVIRON["msg_all_desc"])
+ category_list = ""
+ for (i = 1; i <= n; i++)
+ {
+ cat = varcat = categories_sorted[i]
+ npkgs = categories[cat]
+ cur_prefix = tolower(substr(cat, 1, 1))
+ if ( prefix != cur_prefix )
+ prefix = cur_prefix
+ else
+ cat = " " cat
+ gsub("[^" valid_chars "]", "_", varcat)
+ desc = ENVIRON["_category_" varcat]
+ if ( ! desc ) desc = default_desc
+ print_category(cat, npkgs, desc)
+ category_list = category_list " " cat
+ }
+ print "\""
+
+ # Produce the list of categories (calculated in above block)
+ sub(/^ /, "", category_list)
+ print "PACKAGE_CATEGORIES=\"" category_list "\""
+
+ }' )" # End-Quote
+}
+
+# f_index_extract_pages $var_to_get $var_basename $pagesize [$category]
+#
+# Extracts the package INDEX ($PACKAGE_INDEX by default if/when $var_to_get is
+# NULL; but should not be missing) into a series of sequential variables
+# corresponding to "pages" containing up to $pagesize packages. The package
+# INDEX data must be contained in the variable $var_to_get. The extracted pages
+# are stored in variables ${var_basename}_# -- where "#" is a the page number.
+# If $category is set, only packages for that category are extracted.
+# Otherwise, if $category is "All", missing, or NULL, all packages are
+# extracted and no filtering is done.
+#
+f_index_extract_pages()
+{
+ local var_to_get="${1:-PACKAGE_INDEX}" var_basename="$2" pagesize="$3"
+ local category="$4" # Optional
+
+ eval "$(
+ debug= f_getvar "$var_to_get" | awk -F'|' \
+ -v cat="$category" \
+ -v pagesize="$pagesize" \
+ -v var_basename="$var_basename" \
+ -v i18n_all="$msg_all" '
+ BEGIN { n = page = 0 }
+ /'\''/{ gsub(/'\''/, "'\''\\'\'\''") }
+ {
+ if ( cat !~ "(^$|^" i18n_all "$)" && $7 !~ \
+ "(^|[[:space:]])" cat "([[:space:]]|$)" ) next
+ starting_new_page = (n++ == (pagesize * page))
+ if ( starting_new_page )
+ printf "%s%s", ( n > 1 ? "'\''\n" : "" ),
+ var_basename "_" ++page "='\''"
+ printf "%s%s", ( starting_new_page ? "" : "\n" ), $0
+ }
+ END { if ( n > 0 ) print "'\''" }'
+ )"
+}
+
+# f_index_search $var_to_get $name [$var_to_set]
+#
+# Search the package INDEX ($PACKAGE_INDEX by default if/when $var_to_get is
+# NULL; but should not be missing) for $name, returning the first match.
+# Matches are strict (not regular expressions) and must match the beginning
+# portion of the package name to be considered a match. If $var_to_set is
+# missing or NULL, output is sent to standard output. If a match is found,
+# returns success; otherwise failure.
+#
+f_index_search()
+{
+ local __var_to_get="${1:-PACKAGE_INDEX}" __pkg_basename="$2"
+ local __var_to_set="$3"
+
+ f_dprintf "f_index_search: Searching package data (in %s) for %s" \
+ "$__var_to_get" "$__pkg_basename"
+
+ local __pkg=
+ __pkg=$( debug= f_getvar "$__var_to_get" |
+ awk -F'|' -v basename="$__pkg_basename" '
+ BEGIN { n = length(basename) }
+ substr($1, 0, n) == basename { print $1; exit }
+ ' )
+ if [ ! "$__pkg" ]; then
+ f_dprintf "f_index_search: No packages matching %s found" \
+ "$__pkg_basename"
+ return $FAILURE
+ fi
+
+ f_dprintf "f_index_search: Found package %s" "$__pkg"
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" "$__pkg"
+ else
+ echo "$__pkg"
+ fi
+ return $SUCCESS
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." packages/index.subr
+
+fi # ! $_PACKAGES_INDEX_SUBR
diff --git a/usr.sbin/bsdconfig/share/packages/musthavepkg.subr b/usr.sbin/bsdconfig/share/packages/musthavepkg.subr
new file mode 100644
index 0000000..929823e
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/packages/musthavepkg.subr
@@ -0,0 +1,87 @@
+if [ ! "$_PACKAGES_MUSTHAVEPKG_SUBR" ]; then _PACKAGES_MUSTHAVEPKG_SUBR=1
+#
+# Copyright (c) 2014 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." packages/musthavepkg.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+
+############################################################ FUNCTIONS
+
+# f_musthavepkg_init
+#
+# Validate pkg(8) is installed and set $PKG_ABI global if not already set.
+# Returns success unless pkg(8) is not installed and user refuses to install
+# it (upon prompt when running interactively).
+#
+f_musthavepkg_init()
+{
+ local funcname=f_musthavepkg_init
+ local pkg_abi_awk='$1~/^ABI/{print $NF; exit}'
+
+ if [ "$PKG_ABI" ]; then # Already set
+ f_dprintf "PKG_ABI=[%s]" "$PKG_ABI"
+ export PKG_ABI
+ f_quietly pkg -N -vv # return status (pkg(8) functional?)
+ return $?
+ fi
+
+ # Attempt to get PKG_ABI without prematurely bootstrapping pkg(8)
+ if f_eval_catch -k PKG_ABI $funcname pkg \
+ "pkg -N -vv | awk '%s'" "$pkg_abi_awk"
+ then
+ f_dprintf "PKG_ABI=[%s]" "$PKG_ABI"
+ export PKG_ABI
+ return $SUCCESS
+ fi
+
+ # pkg(8) not yet bootstrapped; ask for permission unless nonInteractive
+ f_dialog_yesno "$msg_pkg_not_yet_installed_install_now" ||
+ f_die 1 "$msg_must_have_pkg_to_execute" "$pgm"
+
+ f_mustberoot_init # Have to be root to install pkg(8)
+
+ # Bootstrap pkg(8)
+ f_dialog_info "$msg_bootstrapping_pkg"
+ f_eval_catch -k PKG_ABI $funcname pkg \
+ "ASSUME_ALWAYS_YES=1 pkg -vv | awk '%s'" "$pkg_abi_awk" ||
+ f_die 1 "$msg_must_have_pkg_to_execute" "$pgm"
+
+ f_dprintf "PKG_ABI=[%s]" "$PKG_ABI"
+ export PKG_ABI
+ return $SUCCESS
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." packages/musthavepkg.subr
+
+fi # ! $_PACKAGES_MUSTHAVEPKG_SUBR
diff --git a/usr.sbin/bsdconfig/share/packages/packages.subr b/usr.sbin/bsdconfig/share/packages/packages.subr
new file mode 100644
index 0000000..f3ec707
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/packages/packages.subr
@@ -0,0 +1,1192 @@
+if [ ! "$_PACKAGES_PACKAGES_SUBR" ]; then _PACKAGES_PACKAGES_SUBR=1
+#
+# Copyright (c) 2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/device.subr
+f_include $BSDCFG_SHARE/media/common.subr
+f_include $BSDCFG_SHARE/packages/categories.subr
+f_include $BSDCFG_SHARE/packages/index.subr
+f_include $BSDCFG_SHARE/packages/musthavepkg.subr
+f_include $BSDCFG_SHARE/strings.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+
+############################################################ CONFIGURATION
+
+#
+# How many packages to display (maximum) per dialog menubox.
+#
+: ${PACKAGE_MENU_PAGESIZE:=2000}
+
+############################################################ GLOBALS
+
+#
+# Package extensions to try
+#
+PACKAGE_EXTENSIONS=".txz .tbz .tbz2 .tgz"
+
+#
+# Variables used to track runtime states
+#
+PACKAGES_DETECTED= # Boolean (NULL/non-NULL); detected installed packages?
+PACKAGE_CATEGORIES= # List of package categories parsed from INDEX
+SELECTED_PACKAGES= # Packages selected by user in [X]dialog(1) interface
+
+#
+# Options
+#
+[ "${SHOW_DESC+set}" ] || SHOW_DESC=1
+
+############################################################ FUNCTIONS
+
+# eval f_package_accent_category_menu $var_to_set $CATEGORY_MENU_LIST
+#
+# Accent the CATEGORY_MENU_LIST produced by f_index_read() (see
+# packages/index.subr). Accented information includes adding an asterisk to the
+# category name if its index has been cached, adding the number of installed
+# packages for each category, and adding the number _selected_ packages for
+# each category.
+#
+# NOTE: The reason `eval' is recommended/shown for the syntax above is because
+# the $CATEGORY_MENU_LIST generated by f_index_read() is meant to be expanded
+# prior to execution (it contains a series of pre-quoted strings which act as
+# the interpolated command arguments).
+#
+f_package_accent_category_menu()
+{
+ local var_to_set="$1" category cat desc help varcat menu_buf n
+ shift 1 # var_to_set
+ while [ $# -gt 0 ]; do
+ category="${1%\*}" desc="${2%%; *}" help="$3"
+ shift 3 # cat/desc/help
+
+ cat="${category# }" # Trim lead space inserted by sort-method
+ f_str2varname "$cat" varcat
+
+ # Add number of installed packages for this category (if any)
+ n=0
+ case "$cat" in
+ "$msg_all") debug= f_getvar "_All_ninstalled" n ;;
+ *) debug= f_getvar "_${varcat}_ninstalled" n ;;
+ esac &&
+ [ $n -ge 1 ] && desc="$desc; $n $msg_installed_lc"
+
+ # Add number of selected packages for this category (if any)
+ n=0
+ case "$cat" in
+ "$msg_all") debug= f_getvar "_All_nselected" n ;;
+ *) debug= f_getvar "_${varcat}_nselected" n ;;
+ esac &&
+ [ $n -ge 1 ] && desc="$desc; $n $msg_selected"
+
+ # Re-Add asterisk to the category if its index has been cached
+ f_isset _index_page_${varcat}_1 && category="$category*"
+
+ # Update buffer with modified elements
+ menu_buf="$menu_buf
+ '$category' '$desc' '$help'" # End-Quote
+ done
+ setvar "$var_to_set" "$menu_buf" # return our buffer
+}
+
+# f_package_select $package ...
+#
+# Add $package to the list of tracked/selected packages. If $package is already
+# being tracked (already apears in $SELECTED_PACKAGES), this function amounts
+# to having no effect.
+#
+f_package_select()
+{
+ local package pkgsel
+ while [ $# -gt 0 ]; do
+ package="$1"
+ shift 1 # package
+ for pkgsel in $SELECTED_PACKAGES; do
+ [ "$package" = "$pkgsel" ] && return $SUCCESS
+ done
+ SELECTED_PACKAGES="$SELECTED_PACKAGES $package"
+ f_dprintf "Added %s to selection list" "$package"
+ done
+ SELECTED_PACKAGES="${SELECTED_PACKAGES# }" # Trim leading space
+}
+
+# f_package_deselect $package ...
+#
+# Remove $package from teh list of tracked/selected packages. If $package is
+# not being tracked (doesn't appear in $SELECTED_PACKAGES), this function
+# amounts to having no effet.
+#
+f_package_deselect()
+{
+ local package pkgsel
+ while [ $# -gt 1 ]; do
+ local new_list=""
+ package="$1"
+ shift 1 # package
+ for pkgsel in $SELECTED_PACKAGES; do
+ [ "$pkgsel" = "$package" ] && continue
+ new_list="$new_list${new_list:+ }$pkgsel"
+ done
+ SELECTED_PACKAGES="$new_list"
+ f_dprintf "Removed %s from selection list" "$package"
+ done
+}
+
+# f_package_detect_installed
+#
+# Detect installed packages. Currently this uses pkg-query(8) for querying
+# entries and marks each entry as an installed/selected package.
+#
+f_package_detect_installed()
+{
+ local package varpkg
+ for package in $( pkg query "%n-%v" ); do
+ f_str2varname $package varpkg
+ export _mark_$varpkg=X # exported for awk(1) ENVIRON[]
+ f_package_select $package
+ done
+}
+
+# f_package_calculate_totals
+#
+# Calculate number of installed/selected packages for each category listed in
+# $PACKAGE_CATEGORIES (the number of installed packages for $category is stored
+# as $_${varcat}_ninstalled -- where $varcat is the product of `f_str2varname
+# $category varcat' -- and number selected packages as $_${varcat}_nselected).
+# Also calculates the total number of installed/selected packages stored as
+# $_All_ninstalled and $_All_nselected.
+#
+# Calculations are peformed by checking "marks". A "mark" is stored as
+# $_mark_$varpkg -- where $varpkg is the product of `f_str2varname $package
+# varpkg'. A mark can be "X" for an installed package, `I' for a package that
+# is marked for installation, "R" for a package that is marked for re-install,
+# and "U" for a package that is marked for uninstallation. If a package mark is
+# NULL or a single space (e.g., " "), the package is considered to be NOT
+# selected (and therefore does not increment the counts calculated herein).
+#
+f_package_calculate_totals()
+{
+ local pkg varpkg mark cat varcat pkgcat n tselected=0 tinstalled=0
+ for cat in $PACKAGE_CATEGORIES; do
+ f_str2varname $cat varcat
+ setvar _${varcat}_ninstalled=0
+ setvar _${varcat}_nselected=0
+ done
+ for pkg in $SELECTED_PACKAGES; do
+ f_str2varname $pkg varpkg
+ mark=
+ f_getvar _mark_$varpkg mark
+ case "$mark" in
+ ""|" ") : ;;
+ X) tinstalled=$(( $tinstalled + 1 )) ;;
+ *) tselected=$(( $tselected + 1 ))
+ esac
+ f_getvar _categories_$varpkg pkgcat
+ for cat in $pkgcat; do
+ f_str2varname $cat varcat
+ case "$mark" in
+ ""|" ") : ;;
+ X) debug= f_getvar _${varcat}_ninstalled n
+ setvar _${varcat}_ninstalled $(( $n + 1 )) ;;
+ *) debug= f_getvar _${varcat}_nselected n
+ setvar _${varcat}_nselected $(( $n + 1 ))
+ esac
+ done
+ done
+ _All_nselected=$tselected
+ _All_ninstalled=$tinstalled
+}
+
+# f_package_calculate_rundeps
+#
+# Update package dependencies by first unmarking all dependencies and then
+# re-marking all dependencies of packages marked for either install ("I") or
+# re-install ("R").
+#
+f_package_calculate_rundeps()
+{
+ local pkg varpkg mark rundeps dep vardep
+
+ #
+ # First unmark all the existing run-dependencies
+ #
+ f_dprintf "Unselecting package run-dependencies..."
+ for pkg in $SELECTED_PACKAGES; do
+ f_str2varname $pkg varpkg
+ mark=
+ debug= f_getvar _mark_$varpkg mark
+ # Only unmark if it's marked as a Dependency
+ if [ "$mark" = "D" ]; then
+ f_dprintf "%s unselected" $pkg
+ unset _mark_$varpkg
+ f_package_deselect $pkg
+ fi
+ done
+
+ #
+ # Processes selected packages, adding dependencies
+ #
+ f_dprintf "Re-selecting package run-dependencies..."
+ for pkg in $SELECTED_PACKAGES; do
+ f_str2varname $pkg varpkg
+ mark=
+ debug= f_getvar _mark_$varpkg mark
+ # Skip pkg unless marked for [Re-]Install
+ [ "$mark" = "I" -o "$mark" = "R" ] || continue
+ f_getvar _rundeps_$varpkg rundeps
+ for dep in $rundeps; do
+ f_str2varname $dep vardep
+ mark=
+ debug= f_getvar _mark_$vardep mark
+ # Skip dep if already marked
+ [ "${mark:- }" = " " ] || continue
+ export _mark_$vardep="D"
+ f_package_select $dep
+ done
+ done
+
+ f_dprintf "Finished recalculating dependencies."
+}
+
+# f_package_menu_categories $var_to_set $defaultitem
+#
+# Dislay the menu of package categories, complete with package counts for each
+# category, accents, and other miscellany. If $defaultitem is non-NULL and
+# matches one of the existing menu-items, it will be pre-highlighted in the
+# menu dialog (HINT: Use f_dialog_menutag_fetch() to populate a local variable
+# that is passed as $defaultitem to highlight the user's last selection).
+#
+f_package_menu_categories()
+{
+ local var_to_get="$1" defaultitem="$2"
+ local prompt="$msg_please_select_a_category_to_display"
+ local menu_list="
+ '> $msg_review' '$msg_review_desc' '$msg_review_help'
+ " # End-Quote
+ local hline=
+
+ f_package_calculate_rundeps
+ # updates package mark variables and SELECTED_PACKAGES
+ f_package_calculate_totals
+ # creates _{varcat}_ninstalled and _{varcat}_nselected
+
+ local category_list
+ debug= f_getvar "$var_to_get" category_list || return $DIALOG_CANCEL
+
+ # Accent the category menu list with ninstalled/nselected
+ eval f_package_accent_category_menu category_list $category_list
+
+ # Add list of categories to menu list
+ menu_list="$menu_list $category_list"
+
+ local height width rows
+ eval f_dialog_menu_with_help_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --item-help \
+ --default-item \"\$defaultitem\" \
+ --ok-label \"$msg_select\" \
+ --cancel-label \"$msg_cancel\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_menutag_store -s "$menu_choice"
+ return $retval
+}
+
+# f_package_index_get_page $category $page [$var_to_set [$var_to_get]]
+#
+# Obtain a [potentially cached] page of the INDEX file for a given $category.
+# If $page is 1 and the cache has not yet been generated, the cache-generating
+# function f_index_extract_pages() (above) is called to generate all pages
+# (not just the requested page) in cache before returning the requested page.
+# If $page is not 1 and there is no cached page, failure status is returned.
+#
+f_package_index_get_page()
+{
+ local category="$1" page="$2" var_to_set="$3" var_to_get="$4" varcat
+ f_str2varname "$category" varcat
+ if ! debug= f_getvar "_index_page_${varcat}_$page" $var_to_set &&
+ [ "$page" = "1" ]
+ then
+ f_show_info "$msg_building_package_menus"
+ local pagesize="$PACKAGE_MENU_PAGESIZE"
+ f_index_extract_pages "${var_to_get:-PACKAGE_INDEX}" \
+ _index_page_${varcat} "$pagesize" "$category"
+ debug= f_getvar _index_page_${varcat}_$page $var_to_set
+
+ # Update category default-item because now we're cached
+ [ $page -eq 1 ] &&
+ category_defaultitem="${category_defaultitem%\*}*"
+ else
+ return $FAILURE
+ fi
+}
+
+# f_package_menu_select $category [$page [$defaultitem]]
+#
+# Display list of packages for $category, optionally $page N and with a default
+# item selected. If $page is omitted, the first page is displayed (but this
+# only matters if there are multiple pages; which is determined by the global
+# maximum $PACKAGE_MENU_PAGESIZE).
+#
+# On success, if the user doesn't press ESC or choose Cancel, use
+# f_dialog_menuitem_fetch() to populate a local variable with the item (not
+# tag) corresponding to the user's selection. The tag portion of the user's
+# selection is available through f_dialog_menutag_fetch().
+#
+f_package_menu_select()
+{
+ local category="$1" page="${2:-1}"
+ local prompt= # Calculated below
+ local menu_list # Calculated below
+ local defaultitem="$3"
+ local hline="$hline_arrows_tab_punc_enter"
+
+ f_isinteger "$page" || return $DIALOG_CANCEL
+
+ local varcat
+ f_str2varname "$category" varcat
+
+ # Get number of packages for this category
+ local npkgs=0
+ case "$category" in
+ "$msg_all"|"") npkgs="${_npkgs:-0}" ;;
+ *) f_getvar _npkgs_$varcat npkgs
+ esac
+
+ # Calculate number of pages
+ local npages=$(( ${npkgs:=0} / $PACKAGE_MENU_PAGESIZE ))
+
+ # Add a page to the pagecount if not evenly divisible
+ [ $(( $npages * $PACKAGE_MENU_PAGESIZE )) -lt $npkgs ] &&
+ npages=$(( $npages + 1 ))
+
+ # Print some debugging information
+ f_dprintf "f_package_menu_select: category=[%s] npkgs=%u npages=%u" \
+ "$category" "$npkgs" "$npages"
+
+ local add_prev="" add_next=""
+ local previous_page="$msg_previous_page" next_page="$msg_next_page"
+ if [ $page -gt 1 ]; then
+ add_prev=1
+ # Accent the `Previous Page' item with an asterisk
+ # if the page-before-previous is loaded/cached
+ f_isset _index_page_${varcat}_$(( $page - 1 )) &&
+ previous_page="$previous_page*"
+ fi
+ if [ $page -lt $npages ]; then
+ add_next=1
+ # Accent the `Next Page' item with an asterisk
+ # if the page-after-next is loaded/cached
+ f_isset _index_page_${varcat}_$(( $page + 1 )) &&
+ next_page="$next_page*"
+ fi
+
+ local index_page
+ f_package_index_get_page "$category" $page index_page
+
+ menu_list="
+ ${add_prev:+'> $previous_page' '' ${SHOW_DESC:+''}}
+ ${add_next:+'> $next_page' '' ${SHOW_DESC:+''}}
+ $(
+ export SHOW_DESC
+ export VALID_VARNAME_CHARS
+ echo "$index_page" | awk -F'|' -v view="port" '
+ BEGIN {
+ valid_chars = ENVIRON["VALID_VARNAME_CHARS"]
+ prefix = ""
+ }
+ {
+ cur_prefix = tolower(substr($1, 1, 1))
+ printf "'\''"
+ if ( prefix != cur_prefix )
+ prefix = cur_prefix
+ else
+ printf " "
+ package = $1
+ if ( view == "port" )
+ desc = $2
+ varpkg = package
+ gsub("[^" valid_chars "]", "_", varpkg)
+ mark = ENVIRON["_mark_" varpkg]
+ if ( ! mark ) mark = " "
+ printf "%s'\'' '\''[%c] %s'\''",
+ package, mark, desc
+ if ( ENVIRON["SHOW_DESC"] ) {
+ help = $4
+ gsub(/'\''/, "'\''\\'\'\''", help)
+ printf " '\''%s'\''", help
+ }
+ printf "\n"
+ }'
+ )
+ ${add_prev:+'> $previous_page' '' ${SHOW_DESC:+''}}
+ ${add_next:+'> $next_page' '' ${SHOW_DESC:+''}}
+ " # End-Quote
+
+ # Accept/Translate i18n "All" but other category names must
+ # match tree definitions from INDEX, ports, FTP, etc.
+ case "$category" in
+ "$msg_all"|"") f_category_desc_get "All" prompt ;;
+ *) f_category_desc_get "$category" prompt ;;
+ esac
+ f_sprintf prompt "%s $msg_page_of_npages" "$prompt" "$page" "$npages"
+
+ local mheight mwidth mrows
+ eval f_dialog_menu${SHOW_DESC:+_with_help}_size mheight mwidth mrows \
+ \"\$DIALOG_TITLE\" \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \"\$hline\" $menu_list
+ local iheight iwidth
+ f_dialog_infobox_size iheight iwidth \
+ "$DIALOG_TITLE" "$DIALOG_BACKTITLE" \
+ "$msg_processing_selection"
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --keep-tite \
+ --ok-label \"$msg_select\" \
+ --cancel-label \"$msg_back\" \
+ ${SHOW_DESC:+--item-help} \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $mheight $mwidth $mrows \
+ $menu_list \
+ --and-widget \
+ ${USE_XDIALOG:+--no-buttons} \
+ --infobox \"\$msg_processing_selection\" \
+ $iheight $iwidth \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize menu_choice
+ f_dialog_menutag_store "$menu_choice"
+
+ if [ $retval -eq $DIALOG_OK ]; then
+ local item
+ item=$( eval f_dialog_menutag2item${SHOW_DESC:+_with_help} \
+ \"\$menu_choice\" $menu_list )
+ f_dialog_menuitem_store "$item"
+ fi
+
+ return $retval
+}
+
+# f_package_menu_deselect $package
+#
+# Display a menu, asking the user what they would like to do with $package
+# with regard to "deselecting" an already installed package. Choices include
+# uninstall, re-install, or cancel (leave $package marked as installed).
+# Returns success if the user does not press ESC or choose Cnacel. Use the
+# f_dialog_menutag_fetch() function upon success to retrieve the user's choice.
+#
+f_package_menu_deselect()
+{
+ local package="$1"
+ local prompt # Calculated below
+ local menu_list="
+ 'X $msg_installed' '$msg_installed_desc'
+ 'R $msg_reinstall' '$msg_reinstall_desc'
+ 'U $msg_uninstall' '$msg_uninstall_desc'
+ " # End-Quote
+ local hline="$hline_alnum_arrows_punc_tab_enter"
+
+ f_sprintf prompt "$msg_what_would_you_like_to_do_with" "$package"
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"$msg_select\" \
+ --cancel-label \"$msg_cancel\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_menutag_store -s "$menu_choice"
+ return $retval
+}
+
+# f_package_review
+#
+# Display a review screen, showing selected packages and what they are marked
+# for, before proceeding (if the user does not press ESC or choose Cancel) to
+# operate on each selection. Returns error if no packages have been selected,
+# or the user has pressed ESC, or if they have chosen Cancel.
+#
+f_package_review()
+{
+ local funcname=f_package_review
+ local prompt # Calculated below
+ local menu_list # Calculated below
+ local hline="$hline_alnum_arrows_punc_tab_enter"
+
+ f_dprintf "$funcname: SELECTED_PACKAGES=[%s]" "$SELECTED_PACKAGES"
+
+ f_sprintf prompt "$msg_reviewing_selected_packages" "$_All_nselected"
+
+ local package varpkg mark
+ for package in $SELECTED_PACKAGES; do
+ mark=
+ f_str2varname "$package" varpkg
+ f_getvar _mark_$varpkg mark
+ [ "$mark" -a ! "${mark#[IRUD]}" ] || continue
+ menu_list="$menu_list
+ '$mark' '$package'
+ " # End-Quote
+ done
+ if [ ! "$menu_list" ]; then
+ f_show_msg "$msg_no_packages_were_selected_for_extraction"
+ return $DIALOG_CANCEL # Might have selected this by accident
+ fi
+ menu_list=$( echo "$menu_list" | sort )
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ # Show the review menu (ignore menu choice)
+ eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_proceed\" \
+ --cancel-label \"\$msg_cancel\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2> /dev/null || return $?
+ # Return if the user pressed ESC or chose Cancel/No
+
+ #
+ # Process each of the selected packages:
+ # + First, process packages marked for Install.
+ # + Second, process packages marked for Re-install.
+ # + Finally, process packages marked for Uninstall.
+ #
+ for package in $SELECTED_PACKAGES; do
+ mark=
+ f_str2varname "$package" varpkg
+ debug= f_getvar _mark_$varpkg mark
+ [ "$mark" = "I" ] || continue
+ f_dprintf "$funcname: Installing %s package" "$package"
+ f_package_add "$package"
+ done
+ for package in $SELECTED_PACKAGES; do
+ mark=
+ f_str2varname "$package" varpkg
+ debug= f_getvar _mark_$varpkg mark
+ [ "$mark" = "R" ] || continue
+ f_dprintf "$funcname: Reinstalling %s package" "$package"
+ f_package_reinstall "$package"
+ done
+ for package in $SELECTED_PACKAGES; do
+ mark=
+ f_str2varname "$package" varpkg
+ debug= f_getvar _mark_$varpkg mark
+ [ "$mark" = "U" ] || continue
+ f_dprintf "$funcname: Uninstalling %s package" "$package"
+ f_package_delete "$package" || continue
+ f_package_deselect "$package"
+ done
+
+ return $DIALOG_OK
+}
+
+# f_package_config
+#
+# Allow the user to configure packages and install them. Initially, a list of
+# package categories is loaded/displayed. When the user selects a category,
+# the menus for that category are built (unlike sysinstall which built all
+# category menus up-front -- which also took forever, despite the fact that
+# few people visit more than a couple of categories each time).
+#
+f_package_config()
+{
+ # Did we get an INDEX?
+ f_index_initialize || return $FAILURE
+ # Creates following variables (indirectly via f_index_read())
+ # CATEGORY_MENU_LIST _categories_{varpkg} _rundeps_{varpkg}
+ # PACKAGE_CATEGORIES _npkgs
+
+ # Detect installed packages (updates marks/SELECTED_PACKAGES)
+ f_package_detect_installed
+ export PACKAGES_DETECTED=1 # exported for awk(1) ENVIRON[]
+
+ local retval category varcat defaultitem category_defaultitem=""
+ while :; do
+ # Display the list of package categories
+ f_package_menu_categories \
+ CATEGORY_MENU_LIST "$category_defaultitem"
+ retval=$?
+ f_dialog_menutag_fetch category
+ f_dprintf "retval=%u mtag=[%s]" $retval "$category"
+ category_defaultitem="$category"
+
+ [ $retval -eq $DIALOG_OK ] || break
+
+ # Maybe the user chose an action (like `Review')
+ case "$category" in
+ "> $msg_review")
+ f_package_review && break
+ continue ;;
+ "> "*)
+ continue
+ esac
+
+ # Anything else is a package category
+
+ category=${category# } # Trim leading space if present
+ category=${category%\*} # Trim trailing asterisk if present
+
+ f_str2varname "$category" varcat
+
+ local page package varpkg mark menu_choice
+ while :; do
+ # Display the list of packages for selected category
+ page=1 defaultitem=""
+ f_getvar _defaultitem_$varcat defaultitem
+ f_getvar _defaultpage_$varcat page
+ f_package_menu_select \
+ "$category" "${page:=1}" "$defaultitem"
+ retval=$?
+ f_dialog_menutag_fetch menu_choice
+ f_dprintf "retval=%u mtag=[%s]" $retval "$menu_choice"
+
+ # NOTE: When --and-widget is used only ESC will cause
+ # dialog(1) to return without going to the next widget.
+ # This is alright in our case as we can still detect
+ # the Cancel button because stdout will be NULL.
+ # Alternatively, Xdialog(1) will terminate with 1
+ # if/when Cancel is chosen on any widget.
+ if [ $retval -eq $DIALOG_ESC -o ! "$menu_choice" ]
+ then
+ break
+ elif [ $retval -eq $DIALOG_CANCEL ]; then
+ # Using X11, Xdialog(1) returned 1 for Cancel
+ f_show_msg "%s" "$menu_choice"
+ break
+ elif [ $retval -ne $DIALOG_OK ]; then
+ # X11-related error occurred using Xdialog(1)
+ f_show_msg "%s" "$menu_choice"
+ break
+ fi
+
+ defaultitem="$menu_choice"
+
+ # NOTE: f_package_menu_select() does not show the
+ # `Previous Page' or `Next Page' items unless needed
+ case "$menu_choice" in
+ "> $msg_previous_page"|"> $msg_previous_page*")
+ page=$(( $page - 1 ))
+ setvar _defaultpage_$varcat $page
+ # Update default-item to match accent that will
+ # be applied by f_package_menu_select(); if the
+ # page-before-prev is cached, add an asterisk.
+ if f_isset \
+ _index_page_${varcat}_$(( $page - 1 ))
+ then
+ defaultitem="${defaultitem%\*}*"
+ else
+ defaultitem="${defaultitem%\*}"
+ fi
+ setvar _defaultitem_$varcat "$defaultitem"
+ continue ;;
+ "> $msg_next_page"|"> $msg_next_page*")
+ page=$(( $page + 1 ))
+ setvar _defaultpage_$varcat $page
+ # Update default-item to match accent that will
+ # be applied by f_package_menu_select(); if the
+ # page-after-next is cached, add an asterisk.
+ if f_isset \
+ _index_page_${varcat}_$(( $page + 1 ))
+ then
+ defaultitem="${defaultitem%\*}*"
+ else
+ defaultitem="${defaultitem%\*}"
+ fi
+ setvar _defaultitem_$varcat "$defaultitem"
+ continue ;;
+ "> "*) # Unknown navigation/action item
+ setvar _defaultpage_$varcat $page
+ continue ;; # Do not treat as a package
+ *)
+ setvar _defaultitem_$varcat "$defaultitem"
+ esac
+
+ # Treat any other selection as a package
+ package="${menu_choice# }" # Trim leading space
+ f_str2varname $package varpkg
+ f_dialog_menuitem_fetch mark
+ mark="${mark#?}"
+ mark="${mark%%\] *}"
+ case "$mark" in
+ "I")
+ mark=" "
+ f_package_deselect $package
+ ;;
+ " "|"D")
+ mark="I"
+ f_package_select $package
+ ;;
+ "X"|"R"|"U")
+ f_package_menu_deselect $package || continue
+ f_dialog_menutag_fetch menu_choice
+ case "$menu_choice" in
+ "X $msg_installed")
+ f_package_deselect "$package"
+ mark="X"
+ ;;
+ "R $msg_reinstall")
+ f_package_select "$package"
+ mark="R"
+ ;;
+ "U $msg_uninstall")
+ f_package_select "$package"
+ mark="U"
+ ;;
+ esac
+ ;;
+ esac
+ export _mark_$varpkg="$mark"
+ # NOTE: exported for awk(1) ENVIRON[]
+ done
+ done
+}
+
+# f_package_add $package_name [$depended]
+#
+# Like f_package_extract(), but assumes current media device and chases deps.
+# Note that $package_name should not contain the archive suffix (e.g., `.tbz').
+# If $depended is present and non-NULL, the package is treated as a dependency
+# (in this function, dependencies are not handled any differently, but the
+# f_package_extract() function is passed this value and it displays a different
+# message when installing a dependency versus non-dependency).
+#
+f_package_add()
+{
+ local name="$1" depended="$2" status=$SUCCESS retval
+
+ local alert=f_show_msg no_confirm=
+ f_getvar $VAR_NO_CONFIRM no_confirm
+ [ "$no_confirm" ] && alert=f_show_info
+
+ if ! { [ "$name" ] || { f_getvar $VAR_PACKAGE name && [ "$name" ]; }; }
+ then
+ f_dprintf "packageAdd: %s" \
+ "$msg_no_package_name_passed_in_package_variable"
+ return $FAILURE
+ fi
+
+ { # Verify and initialize device media if-defined
+ f_media_verify &&
+ f_device_init device_media &&
+ f_index_initialize
+ } || return $FAILURE
+
+ # Now we have (indirectly via f_index_read()):
+ # CATEGORY_MENU_LIST _categories_{varpkg} _rundeps_{varpkg}
+ # PACKAGE_CATEGORIES _npkgs
+
+ local varpkg
+ f_str2varname "$name" varpkg
+
+ # Just as-in the user-interface (opposed to scripted-use), only allow
+ # packages with at least one category to be recognized.
+ #
+ local pkgcat=
+ if ! f_getvar _categories_$varpkg pkgcat || [ ! "$pkgcat" ]; then
+ # $pkg may be a partial name, search the index (this is slow)
+ f_index_search PACKAGE_INDEX $name name
+ if [ ! "$name" ]; then
+ f_show_msg \
+ "$msg_sorry_package_was_not_found_in_the_index" \
+ "$name"
+ return $FAILURE
+ fi
+ f_str2varname "$name" varpkg
+ fi
+
+ # If invoked through the scripted interface, we likely have not yet
+ # detected the installed packages -- something we should do only once.
+ #
+ if [ ! "$PACKAGES_DETECTED" ]; then
+ f_dprintf "f_package_add: Detecting installed packages"
+ f_package_detect_installed
+ export PACKAGES_DETECTED=1 # exported for awk(1) ENVIRON[]
+ fi
+ # Now we have: _mark_{varpkg}=X for all installed packages
+
+ #
+ # Since we're maintaining data structures for installed packages,
+ # short-circuit the package dependency checks if the package is already
+ # installed. This prevents wasted cycles, minor delays between package
+ # extractions, and worst-case an infinite loop with a certain faulty
+ # INDEX file.
+ #
+ local mark=
+ f_getvar _mark_$varpkg mark && [ "$mark" = "X" ] && return $SUCCESS
+
+ local dep vardep rundeps=
+ f_getvar _rundeps_$varpkg rundeps
+ for dep in $rundeps; do
+ f_str2varname "$dep" vardep
+
+ # Skip dependency if already installed
+ mark=
+ f_getvar _mark_$vardep mark && [ "$mark" = "X" ] && continue
+
+ # Just as-in the user-interface (opposed to scripted-use), only
+ # allow packages with at least one category to be recognized.
+ #
+ local depcat=
+ if ! f_getvar _categories_$vardep depcat || [ ! "$depcat" ]
+ then
+ $alert "$msg_required_package_not_found" "$dep"
+ [ "$no_confirm" ] && sleep 2
+ fi
+
+ f_package_add "$dep"
+ retval=$?
+ if [ $retval -ne $SUCCESS ]; then
+ status=$(( $status | $retval ))
+
+ # XXX package could be on a future disc volume
+ # XXX (not supporting multiple disc volumes yet)
+
+ $alert "$msg_loading_of_dependent_package_failed" \
+ "$dep"
+ [ "$no_confirm" ] && sleep 2
+ fi
+ done
+ [ $status -eq $SUCCESS ] || return $status
+
+ #
+ # Done with the deps? Try to load the real m'coy.
+ #
+
+ f_package_extract device_media "$name" "$depended"
+ retval=$?
+ if [ $retval -ne $SUCCESS ]; then
+ status=$(( $status | $retval ))
+ else
+ setvar _mark_$varpkg X
+ fi
+
+ return $status
+}
+
+# f_package_extract $device $name [$depended]
+#
+# Extract a package based on a namespec and media device. If $depended is
+# present and non-NULL, the notification displayed while installing the package
+# has "as a dependency" appended.
+#
+f_package_extract()
+{
+ local funcname=f_package_extract
+ local device="$1" name="$2" depended="$3"
+ local devname=
+
+ f_musthavepkg_init # Make sure we have a usable pkg(8) with $PKG_ABI
+
+ $device get name devname
+ f_dprintf "$funcname: device=[%s] name=[%s] depended=[%s]" \
+ "$devname" "$name" "$depended"
+
+ # Check to make sure it's not already there
+ local varpkg mark=
+ f_str2varname "$name" varpkg
+ f_getvar _mark_$varpkg mark
+ [ "$mark" = "X" ] && return $SUCCESS
+
+ if ! f_device_init $device; then
+ f_show_msg \
+ "$msg_unable_to_initialize_media_type_for_package_extract"
+ return $FAILURE
+ fi
+
+ # If necessary, initialize the ldconfig hints
+ [ -f "/var/run/ld-elf.so.hints" ] ||
+ f_quietly ldconfig /usr/lib /usr/lib/compat /usr/local/lib
+
+ # Make a couple paranoid locations for temp
+ # files to live if user specified none
+ local tmpdir
+ f_getvar $VAR_PKG_TMPDIR:-/var/tmp tmpdir
+ f_quietly mkdir -p -m 1777 "$tmpdir"
+
+ local path device_type
+ $device get type device_type
+ case "$name" in
+ */*) path="$name" ;;
+ *)
+ if [ "$device_type" = "$DEVICE_TYPE_HTTP" ]; then
+ path="$PKG_ABI/latest/All/$name"
+ else
+ path="packages/$PKG_ABI/All/$name"
+ fi
+ esac
+
+ # We have a path, call the device strategy routine to check the file
+ local pkg_ext found=
+ for pkg_ext in "" $PACKAGE_EXTENSIONS; do
+ if f_device_get $device "$path$pkg_ext" $PROBE_EXIST; then
+ path="$path$pkg_ext"
+ found=1
+ break
+ elif [ "$device_type" = "$DEVICE_TYPE_HTTP" ] &&
+ f_device_get $device \
+ "packages/$PKG_ABI/All/$name$pkg_ext" $PROBE_EXIST
+ then
+ # Mirroring physical media over HTTP
+ path="packages/$PKG_ABI/All/$name$pkg_ext"
+ found=1
+ break
+ fi
+ done
+ [ "$found" ] && f_dprintf "$funcname: found path=[%s] dev=[%s]" \
+ "$path" "$devname"
+
+ local alert=f_show_msg no_confirm=
+ f_getvar $VAR_NO_CONFIRM no_confirm
+ [ "$no_confirm" ] && alert=f_show_info
+
+ if [ ! "$found" ]; then
+ f_dprintf "$funcname: No such %s file on %s device" \
+ "$path" "$devname"
+ $alert "$msg_unable_to_fetch_package_from_selected_media" \
+ "$name"
+ [ "$no_confirm" ] && sleep 2
+ return $FAILURE
+ fi
+
+ if [ "$depended" ]; then
+ f_show_info "$msg_adding_package_as_a_dependency_from_media" \
+ "$name" "$devname"
+ else
+ f_show_info "$msg_adding_package_from_media" "$name" "$devname"
+ fi
+
+ # Request the package be added via pkg-install(8)
+ if f_debugging; then
+ f_eval_catch $funcname pkg \
+ 'pkg -d install -${depended:+A}y "%s"' "$name"
+ else
+ f_eval_catch $funcname pkg \
+ 'pkg install -${depended:+A}y "%s"' "$name"
+ fi
+ if [ $? -ne $SUCCESS ]; then
+ $alert "$msg_pkg_install_apparently_did_not_like_the_package" \
+ "$name"
+ [ "$no_confirm" ] && sleep 2
+ else
+ f_show_info "$msg_package_was_added_successfully" "$name"
+ sleep 1
+ fi
+
+ return $SUCCESS
+}
+
+# f_package_delete $name
+#
+# Delete package by full $name (lacks archive suffix; e.g., `.tbz').
+#
+f_package_delete()
+{
+ local funcname=f_package_delete
+ local name="$1"
+
+ if ! { [ "$name" ] || { f_getvar $VAR_PACKAGE name && [ "$name" ]; }; }
+ then
+ f_dprintf "packageDelete: %s" \
+ "$msg_no_package_name_passed_in_package_variable"
+ return $FAILURE
+ fi
+
+ f_dprintf "$funcname: name=[%s]" "$name"
+
+ [ "$name" ] || return $FAILURE
+
+ { # Verify and initialize device media if-defined
+ f_media_verify &&
+ f_device_init device_media &&
+ f_index_initialize
+ } || return $FAILURE
+
+ # Now we have (indirectly via f_index_read()):
+ # CATEGORY_MENU_LIST _categories_{varpkg} _rundeps_{varpkg}
+ # PACKAGE_CATEGORIES _npkgs
+
+ local varpkg
+ f_str2varname "$name" varpkg
+
+ # Just as-in the user-interface (opposed to scripted-use), only allow
+ # packages with at least one category to be recognized.
+ #
+ local pkgcat=
+ if ! f_getvar _categories_$varpkg pkgcat || [ ! "$pkgcat" ]; then
+ # $pkg may be a partial name, search the index (this is slow)
+ f_index_search PACKAGE_INDEX "$name" name
+ if [ ! "$name" ]; then
+ f_show_msg \
+ "$msg_sorry_package_was_not_found_in_the_index" \
+ "$name"
+ return $FAILURE
+ fi
+ f_str2varname "$name" varpkg
+ fi
+
+ # If invoked through the scripted interface, we likely have not yet
+ # detected the installed packages -- something we should do only once.
+ #
+ if [ ! "$PACKAGES_DETECTED" ]; then
+ f_dprintf "$funcname: Detecting installed packages"
+ f_package_detect_installed
+ export PACKAGES_DETECTED=1 # exported for awk(1) ENVIRON[]
+ fi
+ # Now we have: _mark_{varpkg}=X for all installed packages
+
+ #
+ # Return failure if the package is not already installed.
+ #
+ local pkgmark=
+ f_getvar _mark_$varpkg pkgmark
+ if ! [ "$pkgmark" -a ! "${pkgmark#[XUR]}" ]; then
+ f_show_msg "$msg_package_not_installed_cannot_delete" "$name"
+ return $FAILURE
+ fi
+
+ #
+ # Check for dependencies
+ #
+ local pkgsel depc=0 udeps=
+ for pkgsel in $SELECTED_PACKAGES; do
+ local mark=
+ f_str2varname $pkgsel varpkg
+ debug= f_getvar _mark_$varpkg mark
+ [ "$mark" -a ! "${mark#[XUR]}" ] || continue
+ local dep rundeps=
+ debug= f_getvar _rundeps_$varpkg rundeps
+ for dep in $rundeps; do
+ if [ "$dep" = "$name" ]; then
+ # Maybe this package is marked for deletion too
+ if [ "$mark" = "U" ]; then
+ udeps="$udeps $pkgsel"
+ else
+ depc=$(( $depc + 1 ))
+ fi
+ break
+ fi
+ done
+ done
+ if [ $depc -gt 0 ]; then
+ local grammatical_s=
+ [ $depc -gt 1 ] && grammatical_s=s
+ f_show_msg \
+ "$msg_package_is_needed_by_other_installed_packages" \
+ "$name" "$depc" "$grammatical_s"
+ return $FAILURE
+ fi
+
+ #
+ # Chase dependencies that are marked for uninstallation
+ #
+ for pkgsel in $udeps; do
+ f_dprintf "$funcname: Uninstalling dependency %s (%s)" \
+ "$pkgsel" "marked for delete"
+ f_package_delete "$pkgsel"
+ done
+
+ #
+ # OK to perform the delete (no other packages depend on it)...
+ #
+ f_show_info "$msg_uninstalling_package_waiting_for_pkg_delete" "$name"
+ if f_debugging; then
+ f_eval_catch $funcname pkg 'pkg -d delete -y "%s"' "$name"
+ else
+ f_eval_catch $funcname pkg 'pkg delete -y "%s"' "$name"
+ fi
+ if [ $? -ne $SUCCESS ]; then
+ f_show_msg "$msg_pkg_delete_failed" "$name"
+ return $FAILURE
+ else
+ f_dprintf "$funcname: pkg-delete(8) of %s successful" "$name"
+ f_str2varname "$name" varpkg
+ setvar _mark_$varpkg ""
+ fi
+}
+
+# f_package_reinstall $name
+#
+# A simple wrapper to f_package_delete() + f_package_add()
+#
+f_package_reinstall()
+{
+ f_package_delete "$1" && f_package_add "$1"
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." packages/packages.subr
+
+fi # ! $_PACKAGES_PACKAGES_SUBR
diff --git a/usr.sbin/bsdconfig/share/script.subr b/usr.sbin/bsdconfig/share/script.subr
new file mode 100644
index 0000000..b562e99
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/script.subr
@@ -0,0 +1,219 @@
+if [ ! "$_SCRIPT_SUBR" ]; then _SCRIPT_SUBR=1
+#
+# Copyright (c) 2012-2014 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." script.subr
+f_include $BSDCFG_SHARE/device.subr
+f_include $BSDCFG_SHARE/media/any.subr
+f_include $BSDCFG_SHARE/media/tcpip.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/networking/services.subr
+f_include $BSDCFG_SHARE/packages/packages.subr
+f_include $BSDCFG_SHARE/usermgmt/group.subr
+f_include $BSDCFG_SHARE/usermgmt/user.subr
+f_include $BSDCFG_SHARE/variable.subr
+
+############################################################ GLOBALS
+
+RESWORDS=
+
+############################################################ FUNCTIONS
+
+# f_resword_new $resword $function
+#
+# Create a new `reserved' word for scripting purposes. Reswords call pre-
+# defined functions but differ from those functions in the following ways:
+#
+# + Unless noError is set (must be non-NULL), if calling the resword
+# results in failure, the application will terminate prematurely.
+# + noError is unset after each/every resword is called.
+#
+# Reswords should not be used in bsdconfig itself (hence the name `reserved
+# word') but instead only in scripts loaded through f_script_load().
+#
+f_resword_new()
+{
+ local resword="$1" func="$2"
+ [ "$resword" ] || return $FAILURE
+ f_dprintf "script.subr: New resWord %s -> %s" "$resword" "$func"
+ eval $resword\(\){ f_dispatch $func $resword \"\$@\"\; }
+ RESWORDS="$RESWORDS${RESWORDS:+ }$resword"
+}
+
+# f_dispatch $func $resword
+#
+# Wrapper function used by `reserved words' (reswords) to call other functions.
+# If $noError is set and non-NULL, a failure result from $func is ignored,
+# otherwise the application is prematurely terminated using f_die().
+#
+# NOTE: $noError is unset after every call.
+#
+f_dispatch()
+{
+ local func="$1" resword="$2"
+ shift 2 # func resword
+ f_dprintf "f_dispatch: calling resword \`%s'" "$resword"
+ eval $func "$@"
+ local retval=$?
+ if [ $retval -ne $SUCCESS ]; then
+ local _ignore_this_error
+ f_getvar $VAR_NO_ERROR _ignore_this_error
+ [ "$_ignore_this_error" ] || f_die $retval \
+ "$msg_command_failed_rest_of_script_aborted" "$resword"
+ fi
+ unset $VAR_NO_ERROR
+}
+
+# f_script_load [$file]
+#
+# Load a script (usually filled with reswords). If $file is missing or NULL,
+# use one of the following instead (in order):
+#
+# $configFile (global)
+# install.cfg
+# /stand/install.fg
+# /tmp/install.cfg
+#
+# Unknown/unregistered reswords will generate sh(1) syntax errors but not cause
+# premature termination.
+#
+# Returns success if a script was loaded and itself returned success.
+#
+f_script_load()
+{
+ local funcname=f_script_load
+ local script="$1" config_file retval=$SUCCESS
+
+ f_dprintf "$funcname: script=[%s]" "$script"
+ if [ ! "$script" ]; then
+ f_getvar $VAR_CONFIG_FILE config_file
+ for script in \
+ $config_file \
+ install.cfg \
+ /stand/install.cfg \
+ /tmp/install.cfg \
+ ; do
+ [ -e "$script" ] && break
+ done
+ fi
+
+ local old_interactive=
+ f_getvar $VAR_NONINTERACTIVE old_interactive # save a copy
+
+ # Hint to others that we're running from a script, should they care
+ setvar $VAR_NONINTERACTIVE yes
+
+ if [ "$script" = "-" ]; then
+ f_dprintf "$funcname: Loading script from stdin"
+ eval "$( cat )"
+ retval=$?
+ else
+ f_dprintf "$funcname: Loading script \`%s'" "$script"
+ if [ ! -e "$script" ]; then
+ f_show_msg "$msg_unable_to_open" "$script"
+ return $FAILURE
+ fi
+ . "$script"
+ retval=$?
+ fi
+
+ [ "$old_interactive" ] &&
+ setvar $VAR_NONINTERACTIVE "$old_interactive"
+
+ return $retval
+}
+
+############################################################ MAIN
+
+#
+# Reserved words meant for scripting
+#
+
+# this file
+f_resword_new loadConfig f_script_load
+
+# device.subr
+f_resword_new deviceRescan f_device_rescan
+
+# media/common.subr
+f_resword_new mediaOpen f_media_open
+f_resword_new mediaClose f_media_close
+
+# media includes
+f_resword_new mediaGetType f_media_get_type # media/any.subr
+f_resword_new mediaSetCDROM f_media_set_cdrom # media/cdrom.subr
+f_resword_new mediaSetDOS f_media_set_dos # media/dos.subr
+f_resword_new mediaSetDirectory f_media_set_directory # media/directory.subr
+f_resword_new mediaSetFloppy f_media_set_floppy # media/floppy.subr
+f_resword_new mediaSetNFS f_media_set_nfs # media/nfs.subr
+f_resword_new mediaSetUFS f_media_set_ufs # media/ufs.subr
+f_resword_new mediaSetUSB f_media_set_usb # media/usb.subr
+f_resword_new optionsEditor f_media_options_menu # media/options.subr
+f_resword_new tcpMenuSelect f_dialog_menu_select_tcp # media/tcp.subr
+
+# media/ftp.subr
+f_resword_new mediaSetFTP f_media_set_ftp
+f_resword_new mediaSetFTPActive f_media_set_ftp_active
+f_resword_new mediaSetFTPPassive f_media_set_ftp_passive
+f_resword_new mediaSetFTPUserPass f_media_set_ftp_userpass
+
+# media/http.subr
+f_resword_new mediaSetHTTP f_media_set_http
+
+# media/httpproxy.subr
+f_resword_new mediaSetHTTPProxy f_media_set_http_proxy
+
+# networking/services.subr
+f_resword_new configPCNFSD f_config_pcnfsd
+
+# packages/packages.subr
+f_resword_new configPackages f_package_config
+f_resword_new packageAdd f_package_add
+f_resword_new packageDelete f_package_delete
+f_resword_new packageReinstall f_package_reinstall
+
+# usermgmt/group.subr
+f_resword_new addGroup f_group_add
+f_resword_new deleteGroup f_group_delete
+f_resword_new editGroup f_group_edit
+
+# usermgmt/user.subr
+f_resword_new addUser f_user_add
+f_resword_new deleteUser f_user_delete
+f_resword_new editUser f_user_edit
+
+# variable.subr
+f_resword_new installVarDefaults f_variable_set_defaults
+f_resword_new dumpVariables f_dump_variables
+
+f_dprintf "%s: Successfully loaded." script.subr
+
+fi # ! $_SCRIPT_SUBR
diff --git a/usr.sbin/bsdconfig/share/strings.subr b/usr.sbin/bsdconfig/share/strings.subr
new file mode 100644
index 0000000..487e061
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/strings.subr
@@ -0,0 +1,454 @@
+if [ ! "$_STRINGS_SUBR" ]; then _STRINGS_SUBR=1
+#
+# Copyright (c) 2006-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+
+############################################################ GLOBALS
+
+#
+# A Literal newline (for use with f_replace_all(), or IFS, or whatever)
+#
+NL="
+" # END-QUOTE
+
+#
+# Valid characters that can appear in an sh(1) variable name
+#
+# Please note that the character ranges A-Z and a-z should be avoided because
+# these can include accent characters (which are not valid in a variable name).
+# For example, A-Z matches any character that sorts after A but before Z,
+# including A and Z. Although ASCII order would make more sense, that is not
+# how it works.
+#
+VALID_VARNAME_CHARS="0-9ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"
+
+############################################################ FUNCTIONS
+
+# f_substr "$string" $start [$length]
+#
+# Simple wrapper to awk(1)'s `substr' function.
+#
+f_substr()
+{
+ local string="$1" start="${2:-0}" len="${3:-0}"
+ echo "$string" | awk "{ print substr(\$0, $start, $len) }"
+}
+
+# f_snprintf $var_to_set $size $format [$arguments ...]
+#
+# Similar to snprintf(3), write at most $size number of bytes into $var_to_set
+# using printf(1) syntax (`$format [$arguments ...]'). The value of $var_to_set
+# is NULL unless at-least one byte is stored from the output.
+#
+f_snprintf()
+{
+ local __var_to_set="$1" __size="$2"
+ shift 2 # var_to_set size
+ eval "$__var_to_set"=\$\( printf -- \"\$@\" \| \
+ awk -v max=\"\$__size\" \''
+ {
+ len = length($0)
+ max -= len
+ print substr($0,0,(max > 0 ? len : max + len))
+ if ( max < 0 ) exit
+ max--
+ }'\' \)
+}
+
+# f_sprintf $var_to_set $format [$arguments ...]
+#
+# Similar to sprintf(3), write a string into $var_to_set using printf(1) syntax
+# (`$format [$arguments ...]').
+#
+f_sprintf()
+{
+ local __var_to_set="$1"
+ shift 1 # var_to_set
+ eval "$__var_to_set"=\$\( printf -- \"\$@\" \)
+}
+
+# f_vsnprintf $var_to_set $size $format $format_args
+#
+# Similar to vsnprintf(3), write at most $size number of bytes into $var_to_set
+# using printf(1) syntax (`$format $format_args'). The value of $var_to_set is
+# NULL unless at-least one byte is stored from the output.
+#
+# Example 1:
+#
+# limit=7 format="%s"
+# format_args="'abc 123'" # 3-spaces between abc and 123
+# f_vsnprintf foo $limit "$format" "$format_args" # foo=[abc 1]
+#
+# Example 2:
+#
+# limit=12 format="%s %s"
+# format_args=" 'doghouse' 'foxhound' "
+# # even more spaces added to illustrate escape-method
+# f_vsnprintf foo $limit "$format" "$format_args" # foo=[doghouse fox]
+#
+# Example 3:
+#
+# limit=13 format="%s %s"
+# f_shell_escape arg1 'aaa"aaa' # arg1=[aaa"aaa] (no change)
+# f_shell_escape arg2 "aaa'aaa" # arg2=[aaa'\''aaa] (escaped s-quote)
+# format_args="'$arg1' '$arg2'" # use single-quotes to surround args
+# f_vsnprintf foo $limit "$format" "$format_args" # foo=[aaa"aaa aaa'a]
+#
+# In all of the above examples, the call to f_vsnprintf() does not change. Only
+# the contents of $limit, $format, and $format_args changes in each example.
+#
+f_vsnprintf()
+{
+ eval f_snprintf \"\$1\" \"\$2\" \"\$3\" $4
+}
+
+# f_vsprintf $var_to_set $format $format_args
+#
+# Similar to vsprintf(3), write a string into $var_to_set using printf(1)
+# syntax (`$format $format_args').
+#
+f_vsprintf()
+{
+ eval f_sprintf \"\$1\" \"\$2\" $3
+}
+
+# f_longest_line_length
+#
+# Simple wrapper to an awk(1) script to print the length of the longest line of
+# input (read from stdin). Supports the newline escape-sequence `\n' for
+# splitting a single line into multiple lines.
+#
+f_longest_line_length_awk='
+BEGIN { longest = 0 }
+{
+ if (split($0, lines, /\\n/) > 1)
+ {
+ for (n in lines)
+ {
+ len = length(lines[n])
+ longest = ( len > longest ? len : longest )
+ }
+ }
+ else
+ {
+ len = length($0)
+ longest = ( len > longest ? len : longest )
+ }
+}
+END { print longest }
+'
+f_longest_line_length()
+{
+ awk "$f_longest_line_length_awk"
+}
+
+# f_number_of_lines
+#
+# Simple wrapper to an awk(1) script to print the number of lines read from
+# stdin. Supports newline escape-sequence `\n' for splitting a single line into
+# multiple lines.
+#
+f_number_of_lines_awk='
+BEGIN { num_lines = 0 }
+{
+ num_lines += split(" "$0, unused, /\\n/)
+}
+END { print num_lines }
+'
+f_number_of_lines()
+{
+ awk "$f_number_of_lines_awk"
+}
+
+# f_isinteger $arg
+#
+# Returns true if argument is a positive/negative whole integer.
+#
+f_isinteger()
+{
+ local arg="${1#-}"
+ [ "${arg:-x}" = "${arg%[!0-9]*}" ]
+}
+
+# f_uriencode [$text]
+#
+# Encode $text for the purpose of embedding safely into a URL. Non-alphanumeric
+# characters are converted to `%XX' sequence where XX represents the hexa-
+# decimal ordinal of the non-alphanumeric character. If $text is missing, data
+# is instead read from standard input.
+#
+f_uriencode_awk='
+BEGIN {
+ output = ""
+ for (n = 0; n < 256; n++) pack[sprintf("%c", n)] = sprintf("%%%02x", n)
+}
+{
+ sline = ""
+ slen = length($0)
+ for (n = 1; n <= slen; n++) {
+ char = substr($0, n, 1)
+ if ( char !~ /^[[:alnum:]_]$/ ) char = pack[char]
+ sline = sline char
+ }
+ output = output ( output ? "%0a" : "" ) sline
+}
+END { print output }
+'
+f_uriencode()
+{
+ if [ $# -gt 0 ]; then
+ echo "$1" | awk "$f_uriencode_awk"
+ else
+ awk "$f_uriencode_awk"
+ fi
+}
+
+# f_uridecode [$text]
+#
+# Decode $text from a URI. Encoded characters are converted from their `%XX'
+# sequence into original unencoded ASCII sequences. If $text is missing, data
+# is instead read from standard input.
+#
+f_uridecode_awk='
+BEGIN { for (n = 0; n < 256; n++) chr[n] = sprintf("%c", n) }
+{
+ sline = ""
+ slen = length($0)
+ for (n = 1; n <= slen; n++)
+ {
+ seq = substr($0, n, 3)
+ if ( seq ~ /^%[[:xdigit:]][[:xdigit:]]$/ ) {
+ hex = substr(seq, 2, 2)
+ sline = sline chr[sprintf("%u", "0x"hex)]
+ n += 2
+ } else
+ sline = sline substr(seq, 1, 1)
+ }
+ print sline
+}
+'
+f_uridecode()
+{
+ if [ $# -gt 0 ]; then
+ echo "$1" | awk "$f_uridecode_awk"
+ else
+ awk "$f_uridecode_awk"
+ fi
+}
+
+# f_replaceall $string $find $replace [$var_to_set]
+#
+# Replace all occurrences of $find in $string with $replace. If $var_to_set is
+# either missing or NULL, the variable name is produced on standard out for
+# capturing in a sub-shell (which is less recommended due to performance
+# degradation).
+#
+# To replace newlines or a sequence containing the newline character, use $NL
+# as `\n' is not supported.
+#
+f_replaceall()
+{
+ local __left="" __right="$1"
+ local __find="$2" __replace="$3" __var_to_set="$4"
+ while :; do
+ case "$__right" in *$__find*)
+ __left="$__left${__right%%$__find*}$__replace"
+ __right="${__right#*$__find}"
+ continue
+ esac
+ break
+ done
+ __left="$__left${__right#*$__find}"
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" "$__left"
+ else
+ echo "$__left"
+ fi
+}
+
+# f_str2varname $string [$var_to_set]
+#
+# Convert a string into a suitable value to be used as a variable name
+# by converting unsuitable characters into the underscrore [_]. If $var_to_set
+# is either missing or NULL, the variable name is produced on standard out for
+# capturing in a sub-shell (which is less recommended due to performance
+# degradation).
+#
+f_str2varname()
+{
+ local __string="$1" __var_to_set="$2"
+ f_replaceall "$__string" "[!$VALID_VARNAME_CHARS]" "_" "$__var_to_set"
+}
+
+# f_shell_escape $string [$var_to_set]
+#
+# Escape $string for shell eval statement(s) by replacing all single-quotes
+# with a special sequence that creates a compound string when interpolated
+# by eval with surrounding single-quotes.
+#
+# For example:
+#
+# foo="abc'123"
+# f_shell_escape "$foo" bar # bar=[abc'\''123]
+# eval echo \'$bar\' # produces abc'123
+#
+# This is helpful when processing an argument list that has to retain its
+# escaped structure for later evaluations.
+#
+# WARNING: Surrounding single-quotes are not added; this is the responsibility
+# of the code passing the escaped values to eval (which also aids readability).
+#
+f_shell_escape()
+{
+ local __string="$1" __var_to_set="$2"
+ f_replaceall "$__string" "'" "'\\''" "$__var_to_set"
+}
+
+# f_shell_unescape $string [$var_to_set]
+#
+# The antithesis of f_shell_escape(), this function takes an escaped $string
+# and expands it.
+#
+# For example:
+#
+# foo="abc'123"
+# f_shell_escape "$foo" bar # bar=[abc'\''123]
+# f_shell_unescape "$bar" # produces abc'123
+#
+f_shell_unescape()
+{
+ local __string="$1" __var_to_set="$2"
+ f_replaceall "$__string" "'\\''" "'" "$__var_to_set"
+}
+
+# f_expand_number $string [$var_to_set]
+#
+# Unformat $string into a number, optionally to be stored in $var_to_set. This
+# function follows the SI power of two convention.
+#
+# The prefixes are:
+#
+# Prefix Description Multiplier
+# k kilo 1024
+# M mega 1048576
+# G giga 1073741824
+# T tera 1099511627776
+# P peta 1125899906842624
+# E exa 1152921504606846976
+#
+# NOTE: Prefixes are case-insensitive.
+#
+# Upon successful completion, success status is returned; otherwise the number
+# -1 is produced ($var_to_set set to -1 or if $var_to_set is NULL or missing)
+# on standard output. In the case of failure, the error status will be one of:
+#
+# Status Reason
+# 1 Given $string contains no digits
+# 2 An unrecognized prefix was given
+# 3 Result too large to calculate
+#
+f_expand_number()
+{
+ local __string="$1" __var_to_set="$2"
+ local __cp __num __bshift __maxinput
+
+ # Remove any leading non-digits
+ __string="${__string#${__string%%[0-9]*}}"
+
+ # Store the numbers (no trailing suffix)
+ __num="${__string%%[!0-9]*}"
+
+ # Produce `-1' if string didn't contain any digits
+ if [ ! "$__num" ]; then
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" -1
+ else
+ echo -1
+ fi
+ return 1 # 1 = "Given $string contains no digits"
+ fi
+
+ # Remove all the leading numbers from the string to get at the prefix
+ __string="${__string#"$__num"}"
+
+ #
+ # Test for invalid prefix (and determine bitshift length)
+ #
+ case "$__string" in
+ ""|[[:space:]]*) # Shortcut
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" $__num
+ else
+ echo $__num
+ fi
+ return $SUCCESS ;;
+ [Kk]*) __bshift=10 ;;
+ [Mm]*) __bshift=20 ;;
+ [Gg]*) __bshift=30 ;;
+ [Tt]*) __bshift=40 ;;
+ [Pp]*) __bshift=50 ;;
+ [Ee]*) __bshift=60 ;;
+ *)
+ # Unknown prefix
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" -1
+ else
+ echo -1
+ fi
+ return 2 # 2 = "An unrecognized prefix was given"
+ esac
+
+ # Determine if the wheels fall off
+ __maxinput=$(( 0x7fffffffffffffff >> $__bshift ))
+ if [ $__num -gt $__maxinput ]; then
+ # Input (before expanding) would exceed 64-bit signed int
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" -1
+ else
+ echo -1
+ fi
+ return 3 # 3 = "Result too large to calculate"
+ fi
+
+ # Shift the number out and produce it
+ __num=$(( $__num << $__bshift ))
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" $__num
+ else
+ echo $__num
+ fi
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." strings.subr
+
+fi # ! $_STRINGS_SUBR
diff --git a/usr.sbin/bsdconfig/share/struct.subr b/usr.sbin/bsdconfig/share/struct.subr
new file mode 100644
index 0000000..78c785f
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/struct.subr
@@ -0,0 +1,206 @@
+if [ ! "$_STRUCT_SUBR" ]; then _STRUCT_SUBR=1
+#
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+
+############################################################ FUNCTIONS
+
+# f_struct_define $type $member_name1 ...
+#
+# Define a new `structure' type $type made up of the properties $member_name1
+# $member_name2 and so-on. Properties are not typed and can hold any type of
+# data (including names of other structs).
+#
+# Before creating instances of a struct (using f_struct_new $type $name) you
+# should use this function to define $type.
+#
+# Both $type and member names should consist only of alpha-numeric letters or
+# the underscore.
+#
+f_struct_define()
+{
+ local type="$1"
+ [ "$type" ] || return $FAILURE
+ shift
+ setvar "_struct_typedef_$type" "$*"
+}
+
+# f_struct_new $type $name
+#
+# Create a new `structure' named $name of type $type. There are two ways to
+# access properties of a struct, but they are not equal (each method has its
+# own unique benefits, discussed below).
+#
+# The primary method of accessing (both setting and getting) properties of any
+# struct is through the f_struct() function below.
+#
+# The secondary method of accessing data is by using $name as a function.
+#
+# Both access methods are cross-platform compatible with any version of sh(1).
+# Below is an example of the primary access method:
+#
+# f_struct_new MY_STRUCT_TYPE my_struct
+# f_struct my_struct set abc 123
+# f_struct my_struct get abc # prints 123 to stdout
+# f_struct my_struct get abc abc # sets local variable $abc to 123
+#
+# Alternatively, the secondary access method (details below):
+#
+# f_struct_new MY_STRUCT_TYPE my_struct
+# my_struct set abc 123
+# my_struct get abc # prints 123 to stdout
+# my_struct get abc abc # sets local variable $abc to 123
+#
+# The secondary form should only be used if/when:
+# + You are certain that the structure already exists
+# + You want a syntax error if/when the struct does not exist
+#
+# The primary benefit to the secondary form is syntax cleanliness and read-
+# ability. If you are unsure if a given struct exists (which would cause a
+# syntax error when using this form), you can use the primary access method to
+# first test for the existence of the struct. For example:
+#
+# if f_struct my_struct; then
+# my_struct get abc # only executed if my_struct exists
+# fi
+#
+# For more information, see the f_struct() function.
+#
+f_struct_new()
+{
+ local type="$1" name="$2"
+ f_dprintf "f_struct_new: type=[%s] name=[%s]" "$type" "$name"
+ [ "$name" ] || return $FAILURE
+ setvar "_struct_type_$name" "$type" || return $FAILURE
+ # OK to use bare $name at this point
+ eval $name\(\){ f_struct $name \"\$@\"\; }
+}
+
+# f_struct $name
+# f_struct $name get $property [$var_to_set]
+# f_struct $name set $property $new_value
+# f_struct $name unset $property
+#
+# Access routine for getting, setting, unsetting, and testing properties of
+# `structures'.
+#
+# If only given $name, returns success if struct $name has been created (using
+# the f_struct_new() function above).
+#
+# For getting properties of a struct (versus setting) there are two methods of
+# access. If $var_to_set is missing or NULL, the value of the property is
+# printed to standard output for capturing in a sub-shell (which is less-
+# recommended because of performance degredation; for example, when called in a
+# loop). Returns success unless the property is unset.
+#
+# For setting properties of a struct, sets the value of $property to $new_value
+# and returns success.
+#
+# For unsetting, the underlying environment variable associated with the given
+# $property is unset.
+#
+f_struct()
+{
+ local __name="$1" __action="$2" __property="$3"
+ case $# in
+ 0) return $FAILURE ;;
+ 1) f_have "$__name" ;;
+ *) case "$__action" in
+ get) local __var_to_set="$4"
+ f_getvar "_struct_value_${__name}_$__property" "$__var_to_set"
+ ;;
+ set) local new_value="$4"
+ setvar "_struct_value_${__name}_$__property" "$new_value" ;;
+ unset) unset "_struct_value_${__name}_$__property" ;;
+ esac
+ esac
+ # Return the status of the last command above
+}
+
+# f_struct_free $name
+#
+# Unset the collection of environment variables and accessor-function
+# associated with struct $name.
+#
+f_struct_free()
+{
+ local name="$1" type member members
+ f_getvar "_struct_type_$name" type
+ f_dprintf "f_struct_free: name=[%s] type=[%s]" "$name" "$type"
+ [ "$name" ] || return $FAILURE
+ f_getvar "_struct_typedef_$type" members
+ for member in $members; do
+ f_struct "$name" unset $member
+ done
+ unset -f "$name"
+ unset "_struct_type_$name"
+}
+
+# f_struct_copy $from_name $to_name
+#
+# Copy the properties of one struct to another. If struct $to_name does not
+# exist, it is created. If struct $from_name does not exist, nothing is done
+# and struct $to_name remains unmodified.
+#
+# Returns success unless struct $to_name did not exist and f_struct_new() was
+# unable to create it.
+#
+f_struct_copy()
+{
+ local from_name="$1" to_name="$2" type
+ f_dprintf "f_struct_copy: from_name=[%s] to_name=[%s]" \
+ "$from_name" "$to_name"
+ f_getvar "_struct_type_$from_name" type
+ f_struct "$to_name" ||
+ f_struct_new "$type" "$to_name" || return $FAILURE
+ f_struct "$from_name" || return $SUCCESS
+ f_dprintf "f_struct_copy: copying properties from %s to %s" \
+ "$from_name" "$to_name"
+ local property properties from_value n=0 k=0
+ f_getvar "_struct_typedef_$type" properties
+ for property in $properties; do
+ k=$(( $k + 1 ))
+ if f_struct "$from_name" get $property from_value; then
+ f_struct "$to_name" set $property "$from_value"
+ n=$(( $n + 1 ))
+ else
+ f_struct "$to_name" unset $property
+ fi
+ done
+ f_dprintf "f_struct_copy: copied %u of %u properties from %s to %s" \
+ "$n" "$k" "$from_name" "$to_name"
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." struct.subr
+
+fi # ! $_STRUCT_SUBR
diff --git a/usr.sbin/bsdconfig/share/sysrc.subr b/usr.sbin/bsdconfig/share/sysrc.subr
new file mode 100644
index 0000000..346bf10
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/sysrc.subr
@@ -0,0 +1,746 @@
+if [ ! "$_SYSRC_SUBR" ]; then _SYSRC_SUBR=1
+#
+# Copyright (c) 2006-2015 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+[ "$_COMMON_SUBR" ] || . $BSDCFG_SHARE/common.subr || exit 1
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig"
+if [ ! "$_SYSRC_JAILED" ]; then
+ f_dprintf "%s: loading includes..." sysrc.subr
+ f_include_lang $BSDCFG_LIBE/include/messages.subr
+fi
+
+############################################################ CONFIGURATION
+
+#
+# Standard pathnames (inherit values from shell if available)
+#
+: ${RC_DEFAULTS:="/etc/defaults/rc.conf"}
+
+############################################################ GLOBALS
+
+#
+# Global exit status variables
+#
+SUCCESS=0
+FAILURE=1
+
+#
+# Valid characters that can appear in an sh(1) variable name
+#
+# Please note that the character ranges A-Z and a-z should be avoided because
+# these can include accent characters (which are not valid in a variable name).
+# For example, A-Z matches any character that sorts after A but before Z,
+# including A and Z. Although ASCII order would make more sense, that is not
+# how it works.
+#
+VALID_VARNAME_CHARS="0-9ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"
+
+############################################################ FUNCTIONS
+
+# f_clean_env [ --except $varname ... ]
+#
+# Unset all environment variables in the current scope. An optional list of
+# arguments can be passed, indicating which variables to avoid unsetting; the
+# `--except' is required to enable the exclusion-list as the remainder of
+# positional arguments.
+#
+# Be careful not to call this in a shell that you still expect to perform
+# $PATH expansion in, because this will blow $PATH away. This is best used
+# within a sub-shell block "(...)" or "$(...)" or "`...`".
+#
+f_clean_env()
+{
+ local var arg except=
+
+ #
+ # Should we process an exclusion-list?
+ #
+ if [ "$1" = "--except" ]; then
+ except=1
+ shift 1
+ fi
+
+ #
+ # Loop over a list of variable names from set(1) built-in.
+ #
+ for var in $( set | awk -F= \
+ '/^[[:alpha:]_][[:alnum:]_]*=/ {print $1}' \
+ | grep -v '^except$'
+ ); do
+ #
+ # In POSIX bourne-shell, attempting to unset(1) OPTIND results
+ # in "unset: Illegal number:" and causes abrupt termination.
+ #
+ [ "$var" = OPTIND ] && continue
+
+ #
+ # Process the exclusion-list?
+ #
+ if [ "$except" ]; then
+ for arg in "$@" ""; do
+ [ "$var" = "$arg" ] && break
+ done
+ [ "$arg" ] && continue
+ fi
+
+ unset "$var"
+ done
+}
+
+# f_sysrc_get $varname
+#
+# Get a system configuration setting from the collection of system-
+# configuration files (in order: /etc/defaults/rc.conf /etc/rc.conf and
+# /etc/rc.conf.local)
+#
+# NOTE: Additional shell parameter-expansion formats are supported. For
+# example, passing an argument of "hostname%%.*" (properly quoted) will
+# return the hostname up to (but not including) the first `.' (see sh(1),
+# "Parameter Expansion" for more information on additional formats).
+#
+f_sysrc_get()
+{
+ # Sanity check
+ [ -f "$RC_DEFAULTS" -a -r "$RC_DEFAULTS" ] || return $FAILURE
+
+ # Taint-check variable name
+ case "$1" in
+ [0-9]*)
+ # Don't expand possible positional parameters
+ return $FAILURE ;;
+ *)
+ [ "$1" ] || return $FAILURE
+ esac
+
+ ( # Execute within sub-shell to protect parent environment
+
+ #
+ # Clear the environment of all variables, preventing the
+ # expansion of normals such as `PS1', `TERM', etc.
+ #
+ f_clean_env --except IFS RC_CONFS RC_DEFAULTS
+
+ . "$RC_DEFAULTS" > /dev/null 2>&1
+
+ unset RC_DEFAULTS
+ # no longer needed
+
+ #
+ # If the query is for `rc_conf_files' then store the value that
+ # we inherited from sourcing RC_DEFAULTS (above) so that we may
+ # conditionally restore this value after source_rc_confs in the
+ # event that RC_CONFS does not customize the value.
+ #
+ if [ "$1" = "rc_conf_files" ]; then
+ _rc_conf_files="$rc_conf_files"
+ fi
+
+ #
+ # If RC_CONFS is defined, set $rc_conf_files to an explicit
+ # value, modifying the default behavior of source_rc_confs().
+ #
+ if [ "${RC_CONFS+set}" ]; then
+ rc_conf_files="$RC_CONFS"
+ _rc_confs_set=1
+ fi
+
+ source_rc_confs > /dev/null 2>&1
+
+ #
+ # If the query was for `rc_conf_files' AND after calling
+ # source_rc_confs the value has not changed, then we should
+ # restore the value to the one inherited from RC_DEFAULTS
+ # before performing the final query (preventing us from
+ # returning what was set via RC_CONFS when the intent was
+ # instead to query the value from the file(s) specified).
+ #
+ if [ "$1" = "rc_conf_files" -a \
+ "$_rc_confs_set" -a \
+ "$rc_conf_files" = "$RC_CONFS" \
+ ]; then
+ rc_conf_files="$_rc_conf_files"
+ unset _rc_conf_files
+ unset _rc_confs_set
+ fi
+
+ unset RC_CONFS
+ # no longer needed
+
+ #
+ # This must be the last functional line for both the sub-shell
+ # and the function to preserve the return status from formats
+ # such as "${varname?}" and "${varname:?}" (see "Parameter
+ # Expansion" in sh(1) for more information).
+ #
+ eval echo '"${'"$1"'}"' 2> /dev/null
+ )
+}
+
+# f_sysrc_service_configs [-a|-p] $name [$var_to_set]
+#
+# Get a list of optional `rc.conf.d' entries sourced by system `rc.d' script
+# $name (see rc.subr(8) for additional information on `rc.conf.d'). If $name
+# exists in `/etc/rc.d' or $local_startup directories and is an rc(8) script
+# the result is a space separated list of `rc.conf.d' entries sourced by the
+# $name `rc.d' script. Otherwise, if $name exists as a binary `rc.d' script,
+# the result is ``/etc/rc.conf.d/$name /usr/local/etc/rc.conf.d/$name''. The
+# result is NULL if $name does not exist.
+#
+# If $var_to_set is missing or NULL, output is to standard out. Returns success
+# if $name was found, failure otherwise.
+#
+# If `-a' flag is given and $var_to_set is non-NULL, append result to value of
+# $var_to_set rather than overwriting current contents.
+#
+# If `-p' flag is given and $var_to_set is non-NULL, prepend result to value of
+# $var_to_set rather than overwriting current contents.
+#
+# NB: The `-a' and `-p' option flags are mutually exclusive.
+#
+f_sysrc_service_configs()
+{
+ local OPTIND=1 OPTARG __flag __append= __prepend=
+ local __local_startup __dir __spath __stype __names=
+
+ while getopts ap __flag; do
+ case "$__flag" in
+ a) __append=1 __prepend= ;;
+ p) __prepend=1 __append= ;;
+ esac
+ done
+ shift $(( $OPTIND - 1 ))
+
+ [ $# -gt 0 ] || return $FAILURE
+ local __sname="$1" __var_to_set="$2"
+
+ __local_startup=$( f_sysrc_get local_startup )
+ for __dir in /etc/rc.d $__local_startup; do
+ __spath="$__dir/$__sname"
+ [ -f "$__spath" -a -x "$__spath" ] || __spath= continue
+ break
+ done
+ [ "$__spath" ] || return $FAILURE
+
+ __stype=$( file -b "$__spath" 2> /dev/null )
+ case "$__stype" in
+ *"shell script"*)
+ __names=$( exec 9<&1 1>&- 2>&-
+ last_name=
+ print_name() {
+ local name="$1"
+ [ "$name" = "$last_name" ] && return
+ echo "$name" >&9
+ last_name="$name"
+ }
+ eval "$( awk '{
+ gsub(/load_rc_config /, "print_name ")
+ gsub(/run_rc_command /, ": ")
+ print
+ }' "$__spath" )"
+ ) ;;
+ *)
+ __names="$__sname"
+ esac
+
+ local __name __test_path __configs=
+ for __name in $__names; do
+ for __dir in /etc/rc.d $__local_startup; do
+ __test_path="${__dir%/rc.d}/rc.conf.d/$__name"
+ [ -d "$__test_path" ] ||
+ __configs="$__configs $__test_path" continue
+ for __test_path in "$__test_path"/*; do
+ [ -f "$__test_path" ] || continue
+ __configs="$__configs $__test_path"
+ done
+ done
+ done
+ __configs="${__configs# }"
+
+ if [ "$__var_to_set" ]; then
+ local __cur=
+ [ "$__append" -o "$__prepend" ] &&
+ f_getvar "$__var_to_set" __cur
+ [ "$__append" ] && __configs="$__cur{$__cur:+ }$__configs"
+ [ "$__prepend" ] && __configs="$__configs${__cur:+ }$__cur"
+ setvar "$__var_to_set" "$__configs"
+ else
+ echo "$__configs"
+ fi
+
+ return $SUCCESS
+}
+
+# f_sysrc_get_default $varname
+#
+# Get a system configuration default setting from the default rc.conf(5) file
+# (or whatever RC_DEFAULTS points at).
+#
+f_sysrc_get_default()
+{
+ # Sanity check
+ [ -f "$RC_DEFAULTS" -a -r "$RC_DEFAULTS" ] || return $FAILURE
+
+ # Taint-check variable name
+ case "$1" in
+ [0-9]*)
+ # Don't expand possible positional parameters
+ return $FAILURE ;;
+ *)
+ [ "$1" ] || return $FAILURE
+ esac
+
+ ( # Execute within sub-shell to protect parent environment
+
+ #
+ # Clear the environment of all variables, preventing the
+ # expansion of normals such as `PS1', `TERM', etc.
+ #
+ f_clean_env --except RC_DEFAULTS
+
+ . "$RC_DEFAULTS" > /dev/null 2>&1
+
+ unset RC_DEFAULTS
+ # no longer needed
+
+ #
+ # This must be the last functional line for both the sub-shell
+ # and the function to preserve the return status from formats
+ # such as "${varname?}" and "${varname:?}" (see "Parameter
+ # Expansion" in sh(1) for more information).
+ #
+ eval echo '"${'"$1"'}"' 2> /dev/null
+ )
+}
+
+# f_sysrc_find $varname
+#
+# Find which file holds the effective last-assignment to a given variable
+# within the rc.conf(5) file(s).
+#
+# If the variable is found in any of the rc.conf(5) files, the function prints
+# the filename it was found in and then returns success. Otherwise output is
+# NULL and the function returns with error status.
+#
+f_sysrc_find()
+{
+ local varname="${1%%[!$VALID_VARNAME_CHARS]*}"
+ local regex="^[[:space:]]*$varname="
+ local rc_conf_files="$( f_sysrc_get rc_conf_files )"
+ local conf_files=
+ local file
+
+ # Check parameters
+ case "$varname" in
+ ""|[0-9]*) return $FAILURE
+ esac
+
+ #
+ # If RC_CONFS is defined, set $rc_conf_files to an explicit
+ # value, modifying the default behavior of source_rc_confs().
+ #
+ [ "${RC_CONFS+set}" ] && rc_conf_files="$RC_CONFS"
+
+ #
+ # Reverse the order of files in rc_conf_files (the boot process sources
+ # these in order, so we will search them in reverse-order to find the
+ # last-assignment -- the one that ultimately effects the environment).
+ #
+ for file in $rc_conf_files; do
+ conf_files="$file${conf_files:+ }$conf_files"
+ done
+
+ #
+ # Append the defaults file (since directives in the defaults file
+ # indeed affect the boot process, we'll want to know when a directive
+ # is found there).
+ #
+ conf_files="$conf_files${conf_files:+ }$RC_DEFAULTS"
+
+ #
+ # Find which file matches assignment to the given variable name.
+ #
+ for file in $conf_files; do
+ [ -f "$file" -a -r "$file" ] || continue
+ if grep -Eq "$regex" $file; then
+ echo $file
+ return $SUCCESS
+ fi
+ done
+
+ return $FAILURE # Not found
+}
+
+# f_sysrc_desc $varname
+#
+# Attempts to return the comments associated with varname from the rc.conf(5)
+# defaults file `/etc/defaults/rc.conf' (or whatever RC_DEFAULTS points to).
+#
+# Multi-line comments are joined together. Results are NULL if no description
+# could be found.
+#
+# This function is a two-parter. Below is the awk(1) portion of the function,
+# afterward is the sh(1) function which utilizes the below awk script.
+#
+f_sysrc_desc_awk='
+# Variables that should be defined on the invocation line:
+# -v varname="varname"
+#
+BEGIN {
+ regex = "^[[:space:]]*"varname"="
+ found = 0
+ buffer = ""
+}
+{
+ if ( ! found )
+ {
+ if ( ! match($0, regex) ) next
+
+ found = 1
+ sub(/^[^#]*(#[[:space:]]*)?/, "")
+ buffer = $0
+ next
+ }
+
+ if ( !/^[[:space:]]*#/ ||
+ /^[[:space:]]*[[:alpha:]_][[:alnum:]_]*=/ ||
+ /^[[:space:]]*#[[:alpha:]_][[:alnum:]_]*=/ ||
+ /^[[:space:]]*$/ ) exit
+
+ sub(/(.*#)*[[:space:]]*/, "")
+ buffer = buffer" "$0
+}
+END {
+ # Clean up the buffer
+ sub(/^[[:space:]]*/, "", buffer)
+ sub(/[[:space:]]*$/, "", buffer)
+
+ print buffer
+ exit ! found
+}
+'
+f_sysrc_desc()
+{
+ awk -v varname="$1" "$f_sysrc_desc_awk" < "$RC_DEFAULTS"
+}
+
+# f_sysrc_set $varname $new_value
+#
+# Change a setting in the system configuration files (edits the files in-place
+# to change the value in the last assignment to the variable). If the variable
+# does not appear in the source file, it is appended to the end of the primary
+# system configuration file `/etc/rc.conf'.
+#
+# This function is a two-parter. Below is the awk(1) portion of the function,
+# afterward is the sh(1) function which utilizes the below awk script.
+#
+f_sysrc_set_awk='
+# Variables that should be defined on the invocation line:
+# -v varname="varname"
+# -v new_value="new_value"
+#
+BEGIN {
+ regex = "^[[:space:]]*"varname"="
+ found = retval = 0
+}
+{
+ # If already found... just spew
+ if ( found ) { print; next }
+
+ # Does this line match an assignment to our variable?
+ if ( ! match($0, regex) ) { print; next }
+
+ # Save important match information
+ found = 1
+ matchlen = RSTART + RLENGTH - 1
+
+ # Store the value text for later munging
+ value = substr($0, matchlen + 1, length($0) - matchlen)
+
+ # Store the first character of the value
+ t1 = t2 = substr(value, 0, 1)
+
+ # Assignment w/ back-ticks, expression, or misc.
+ # We ignore these since we did not generate them
+ #
+ if ( t1 ~ /[`$\\]/ ) { retval = 1; print; next }
+
+ # Assignment w/ single-quoted value
+ else if ( t1 == "'\''" ) {
+ sub(/^'\''[^'\'']*/, "", value)
+ if ( length(value) == 0 ) t2 = ""
+ sub(/^'\''/, "", value)
+ }
+
+ # Assignment w/ double-quoted value
+ else if ( t1 == "\"" ) {
+ sub(/^"(.*\\\\+")*[^"]*/, "", value)
+ if ( length(value) == 0 ) t2 = ""
+ sub(/^"/, "", value)
+ }
+
+ # Assignment w/ non-quoted value
+ else if ( t1 ~ /[^[:space:];]/ ) {
+ t1 = t2 = "\""
+ sub(/^[^[:space:]]*/, "", value)
+ }
+
+ # Null-assignment
+ else if ( t1 ~ /[[:space:];]/ ) { t1 = t2 = "\"" }
+
+ printf "%s%c%s%c%s\n", substr($0, 0, matchlen), \
+ t1, new_value, t2, value
+}
+END { exit retval }
+'
+f_sysrc_set()
+{
+ local funcname=f_sysrc_set
+ local varname="$1" new_value="$2"
+
+ # Check arguments
+ [ "$varname" ] || return $FAILURE
+
+ #
+ # Find which rc.conf(5) file contains the last-assignment
+ #
+ local not_found=
+ local file="$( f_sysrc_find "$varname" )"
+ if [ "$file" = "$RC_DEFAULTS" -o ! "$file" ]; then
+ #
+ # We either got a null response (not found) or the variable
+ # was only found in the rc.conf(5) defaults. In either case,
+ # let's instead modify the first file from $rc_conf_files.
+ #
+
+ not_found=1
+
+ #
+ # If RC_CONFS is defined, use $RC_CONFS
+ # rather than $rc_conf_files.
+ #
+ if [ "${RC_CONFS+set}" ]; then
+ file="${RC_CONFS%%[$IFS]*}"
+ else
+ file=$( f_sysrc_get 'rc_conf_files%%[$IFS]*' )
+ fi
+ fi
+
+ #
+ # If not found, append new value to last file and return.
+ #
+ if [ "$not_found" ]; then
+ echo "$varname=\"$new_value\"" >> "$file"
+ return $?
+ fi
+
+ #
+ # Perform sanity checks.
+ #
+ if [ ! -w "$file" ]; then
+ f_err "$msg_cannot_create_permission_denied\n" \
+ "$pgm" "$file"
+ return $FAILURE
+ fi
+
+ #
+ # Create a new temporary file to write to.
+ #
+ local tmpfile
+ if ! f_eval_catch -dk tmpfile $funcname mktemp 'mktemp -t "%s"' "$pgm"
+ then
+ echo "$tmpfile" >&2
+ return $FAILURE
+ fi
+
+ #
+ # Fixup permissions (else we're in for a surprise, as mktemp(1) creates
+ # the temporary file with 0600 permissions, and if we simply mv(1) the
+ # temporary file over the destination, the destination will inherit the
+ # permissions from the temporary file).
+ #
+ local mode
+ f_eval_catch -dk mode $funcname stat 'stat -f "%%#Lp" "%s"' "$file" ||
+ mode=0644
+ f_eval_catch -d $funcname chmod 'chmod "%s" "%s"' "$mode" "$tmpfile"
+
+ #
+ # Fixup ownership. The destination file _is_ writable (we tested
+ # earlier above). However, this will fail if we don't have sufficient
+ # permissions (so we throw stderr into the bit-bucket).
+ #
+ local owner
+ f_eval_catch -dk owner $funcname stat \
+ 'stat -f "%%u:%%g" "%s"' "$file" || owner="root:wheel"
+ f_eval_catch -d $funcname chown 'chown "%s" "%s"' "$owner" "$tmpfile"
+
+ #
+ # Operate on the matching file, replacing only the last occurrence.
+ #
+ local new_contents retval
+ new_contents=$( tail -r $file 2> /dev/null )
+ new_contents=$( echo "$new_contents" | awk -v varname="$varname" \
+ -v new_value="$new_value" "$f_sysrc_set_awk" )
+ retval=$?
+
+ #
+ # Write the temporary file contents.
+ #
+ echo "$new_contents" | tail -r > "$tmpfile" || return $FAILURE
+ if [ $retval -ne $SUCCESS ]; then
+ echo "$varname=\"$new_value\"" >> "$tmpfile"
+ fi
+
+ #
+ # Taint-check our results.
+ #
+ if ! f_eval_catch -d $funcname sh '/bin/sh -n "%s"' "$tmpfile"; then
+ f_err "$msg_previous_syntax_errors\n" "$pgm" "$file"
+ rm -f "$tmpfile"
+ return $FAILURE
+ fi
+
+ #
+ # Finally, move the temporary file into place.
+ #
+ f_eval_catch -de $funcname mv 'mv "%s" "%s"' "$tmpfile" "$file"
+}
+
+# f_sysrc_delete $varname
+#
+# Remove a setting from the system configuration files (edits files in-place).
+# Deletes all assignments to the given variable in all config files. If the
+# `-f file' option is passed, the removal is restricted to only those files
+# specified, otherwise the system collection of rc_conf_files is used.
+#
+# This function is a two-parter. Below is the awk(1) portion of the function,
+# afterward is the sh(1) function which utilizes the below awk script.
+#
+f_sysrc_delete_awk='
+# Variables that should be defined on the invocation line:
+# -v varname="varname"
+#
+BEGIN {
+ regex = "^[[:space:]]*"varname"="
+ found = 0
+}
+{
+ if ( $0 ~ regex )
+ found = 1
+ else
+ print
+}
+END { exit ! found }
+'
+f_sysrc_delete()
+{
+ local funcname=f_sysrc_delete
+ local varname="$1"
+ local file
+
+ # Check arguments
+ [ "$varname" ] || return $FAILURE
+
+ #
+ # Operate on each of the specified files
+ #
+ local tmpfile
+ for file in ${RC_CONFS-$( f_sysrc_get rc_conf_files )}; do
+ [ -e "$file" ] || continue
+
+ #
+ # Create a new temporary file to write to.
+ #
+ if ! f_eval_catch -dk tmpfile $funcname mktemp \
+ 'mktemp -t "%s"' "$pgm"
+ then
+ echo "$tmpfile" >&2
+ return $FAILURE
+ fi
+
+ #
+ # Fixup permissions and ownership (mktemp(1) defaults to 0600
+ # permissions) to instead match the destination file.
+ #
+ local mode owner
+ f_eval_catch -dk mode $funcname stat \
+ 'stat -f "%%#Lp" "%s"' "$file" || mode=0644
+ f_eval_catch -dk owner $funcname stat \
+ 'stat -f "%%u:%%g" "%s"' "$file" || owner="root:wheel"
+ f_eval_catch -d $funcname chmod \
+ 'chmod "%s" "%s"' "$mode" "$tmpfile"
+ f_eval_catch -d $funcname chown \
+ 'chown "%s" "%s"' "$owner" "$tmpfile"
+
+ #
+ # Operate on the file, removing all occurrences, saving the
+ # output in our temporary file.
+ #
+ awk -v varname="$varname" "$f_sysrc_delete_awk" "$file" \
+ > "$tmpfile"
+ if [ $? -ne $SUCCESS ]; then
+ # The file didn't contain any assignments
+ rm -f "$tmpfile"
+ continue
+ fi
+
+ #
+ # Taint-check our results.
+ #
+ if ! f_eval_catch -d $funcname sh '/bin/sh -n "%s"' "$tmpfile"
+ then
+ f_err "$msg_previous_syntax_errors\n" \
+ "$pgm" "$file"
+ rm -f "$tmpfile"
+ return $FAILURE
+ fi
+
+ #
+ # Perform sanity checks
+ #
+ if [ ! -w "$file" ]; then
+ f_err "$msg_permission_denied\n" "$pgm" "$file"
+ rm -f "$tmpfile"
+ return $FAILURE
+ fi
+
+ #
+ # Finally, move the temporary file into place.
+ #
+ f_eval_catch -de $funcname mv \
+ 'mv "%s" "%s"' "$tmpfile" "$file" || return $FAILURE
+ done
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." sysrc.subr
+
+fi # ! $_SYSRC_SUBR
diff --git a/usr.sbin/bsdconfig/share/variable.subr b/usr.sbin/bsdconfig/share/variable.subr
new file mode 100644
index 0000000..c453f67
--- /dev/null
+++ b/usr.sbin/bsdconfig/share/variable.subr
@@ -0,0 +1,315 @@
+if [ ! "$_VARIABLE_SUBR" ]; then _VARIABLE_SUBR=1
+#
+# Copyright (c) 2012-2014 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." variable.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/strings.subr
+
+############################################################ GLOBALS
+
+VARIABLES=
+
+#
+# Default behavior is to call f_variable_set_defaults() when loaded.
+#
+: ${VARIABLE_SELF_INITIALIZE=1}
+
+#
+# File to write when f_dump_variables() is called.
+#
+: ${VARIABLE_DUMPFILE:=/etc/bsdconfig.vars}
+
+############################################################ FUNCTIONS
+
+# f_variable_new $handle $variable
+#
+# Register a new variable named $variable with the given reference-handle
+# $handle. The environment variable $handle is set to $variable allowing you to
+# use the f_getvar() function (from common.subr) with $handle to get the value
+# of environment variable $variable. For example:
+#
+# f_variable_new VAR_ABC abc
+#
+# allows the later indirection:
+#
+# f_getvar $VAR_ABC
+#
+# to return the value of environment variable `abc'. Variables registered in
+# this manner are recorded in the $VARIABLES environment variable for later
+# allowing dynamic enumeration of so-called `registered/advertised' variables.
+#
+f_variable_new()
+{
+ local handle="$1" variable="$2"
+ [ "$handle" ] || return $FAILURE
+ f_dprintf "variable.subr: New variable %s -> %s" "$handle" "$variable"
+ setvar $handle $variable
+ VARIABLES="$VARIABLES${VARIABLES:+ }$handle"
+}
+
+# f_variable_get_value $var [ $fmt [ $opts ... ] ]
+#
+# Unless nonInteractive is set, prompt the user with a given value (pre-filled
+# with the value of $var) and give them the chance to change the value.
+#
+# Unlike f_getvar() (from common.subr) which can return a variable to the
+# caller on standard output, this function has no [meaningful] output.
+#
+# Returns success unless $var is either NULL or missing.
+#
+f_variable_get_value()
+{
+ local var="$1" cp
+
+ [ "$var" ] || return $FAILURE
+
+ if ! { f_getvar $var cp && ! f_interactive; }; then
+ shift 1 # var
+ f_dialog_input cp "$( printf "$@" )" "$cp" && setvar $var "$cp"
+ fi
+
+ return $SUCCESS
+}
+
+# f_variable_set_defaults
+#
+# Installs sensible defaults for registered/advertised variables.
+#
+f_variable_set_defaults()
+{
+ f_dprintf "f_variable_set_defaults: Initializing defaults..."
+
+ #
+ # Initialize various user-edittable values to their defaults
+ #
+ setvar $VAR_EDITOR "${EDITOR:-/usr/bin/ee}"
+ setvar $VAR_FTP_STATE "auto"
+ setvar $VAR_FTP_USER "ftp"
+ setvar $VAR_HOSTNAME "$( hostname )"
+ setvar $VAR_MEDIA_TIMEOUT "300"
+ setvar $VAR_NFS_SECURE "NO"
+ setvar $VAR_NFS_TCP "NO"
+ setvar $VAR_NFS_V3 "YES"
+ setvar $VAR_PKG_TMPDIR "/var/tmp"
+ setvar $VAR_RELNAME "$UNAME_R"
+
+ #
+ # Debugging
+ #
+ if f_debugging; then
+ local var
+ for var in \
+ $VAR_EDITOR \
+ $VAR_FTP_STATE \
+ $VAR_FTP_USER \
+ $VAR_HOSTNAME \
+ $VAR_MEDIA_TIMEOUT \
+ $VAR_NFS_SECURE \
+ $VAR_NFS_TCP \
+ $VAR_NFS_V3 \
+ $VAR_PKG_TMPDIR \
+ $VAR_RELNAME \
+ ; do
+ f_quietly f_getvar $var
+ done
+ fi
+
+ f_dprintf "f_variable_set_defaults: Defaults initialized."
+}
+
+# f_dump_variables
+#
+# Dump a list of registered/advertised variables and their respective values to
+# $VARIABLE_DUMPFILE. Returns success unless the file couldn't be written. If
+# an error occurs, it is displayed using f_dialog_msgbox() (from dialog.subr).
+#
+f_dump_variables()
+{
+ local err
+ if ! err=$(
+ ( for handle in $VARIABLES; do
+ f_getvar $handle var || continue
+ f_getvar $var value || continue
+ f_shell_escape "$value" value
+ printf "%s='%s'\n" "$var" "$value"
+ done > "$VARIABLE_DUMPFILE" ) 2>&1
+ ); then
+ f_dialog_msgbox "$err"
+ return $FAILURE
+ fi
+}
+
+# f_debugging
+#
+# Are we in debug mode? Returns success if extra DEBUG information has been
+# requested (by setting $debug to non-NULL), otherwise false.
+#
+f_debugging()
+{
+ local value
+ f_getvar $VAR_DEBUG value && [ "$value" ]
+}
+
+# f_interactive
+#
+# Are we running interactively? Return error if $nonInteractive is set and non-
+# NULL, otherwise return success.
+#
+f_interactive()
+{
+ local value
+ ! f_getvar $VAR_NONINTERACTIVE value || [ ! "$value" ]
+}
+
+# f_netinteractive
+#
+# Has the user specifically requested the network-portion of configuration and
+# setup to be performed interactively? Returns success if the user has asked
+# for the network configuration to be done interactively even if perhaps over-
+# all non-interactive mode has been requested (by setting nonInteractive).
+#
+# Returns success if $netInteractive is set and non-NULL.
+#
+f_netinteractive()
+{
+ local value
+ f_getvar $VAR_NETINTERACTIVE value && [ "$value" ]
+}
+
+# f_zfsinteractive
+#
+# Has the user specifically requested the ZFS-portion of configuration and
+# setup to be performed interactively? Returns success if the user has asked
+# for the ZFS configuration to be done interactively even if perhaps overall
+# non-interactive mode has been requested (by setting nonInteractive).
+#
+# Returns success if $zfsInteractive is set and non-NULL.
+#
+f_zfsinteractive()
+{
+ local value
+ f_getvar $VAR_ZFSINTERACTIVE value && [ "$value" ]
+}
+
+############################################################ MAIN
+
+#
+# Variables that can be tweaked from config files
+#
+# Handle Variable Name
+f_variable_new VAR_CONFIG_FILE configFile
+f_variable_new VAR_DEBUG debug
+f_variable_new VAR_DEBUG_FILE debugFile
+f_variable_new VAR_DIRECTORY_PATH _directoryPath
+f_variable_new VAR_DOMAINNAME domainname
+f_variable_new VAR_EDITOR editor
+f_variable_new VAR_EXTRAS ifconfig_
+f_variable_new VAR_FTP_DIR ftpDirectory
+f_variable_new VAR_FTP_HOST ftpHost
+f_variable_new VAR_FTP_PASS ftpPass
+f_variable_new VAR_FTP_PATH _ftpPath
+f_variable_new VAR_FTP_PORT ftpPort
+f_variable_new VAR_FTP_STATE ftpState
+f_variable_new VAR_FTP_USER ftpUser
+f_variable_new VAR_GATEWAY defaultrouter
+f_variable_new VAR_GROUP group
+f_variable_new VAR_GROUP_GID groupGid
+f_variable_new VAR_GROUP_MEMBERS groupMembers
+f_variable_new VAR_GROUP_PASSWORD groupPassword
+f_variable_new VAR_HOSTNAME hostname
+f_variable_new VAR_HTTP_DIR httpDirectory
+f_variable_new VAR_HTTP_FTP_MODE httpFtpMode
+f_variable_new VAR_HTTP_HOST httpHost
+f_variable_new VAR_HTTP_PATH _httpPath
+f_variable_new VAR_HTTP_PORT httpPort
+f_variable_new VAR_HTTP_PROXY httpProxy
+f_variable_new VAR_HTTP_PROXY_HOST httpProxyHost
+f_variable_new VAR_HTTP_PROXY_PATH _httpProxyPath
+f_variable_new VAR_HTTP_PROXY_PORT httpProxyPort
+f_variable_new VAR_IFCONFIG ifconfig_
+f_variable_new VAR_IPADDR ipaddr
+f_variable_new VAR_IPV6ADDR ipv6addr
+f_variable_new VAR_IPV6_ENABLE ipv6_activate_all_interfaces
+f_variable_new VAR_KEYMAP keymap
+f_variable_new VAR_MEDIA_TIMEOUT MEDIA_TIMEOUT
+f_variable_new VAR_MEDIA_TYPE mediaType
+f_variable_new VAR_NAMESERVER nameserver
+f_variable_new VAR_NETINTERACTIVE netInteractive
+f_variable_new VAR_NETMASK netmask
+f_variable_new VAR_NETWORK_DEVICE netDev
+f_variable_new VAR_NFS_HOST nfsHost
+f_variable_new VAR_NFS_PATH nfsPath
+f_variable_new VAR_NFS_SECURE nfs_reserved_port_only
+f_variable_new VAR_NFS_TCP nfs_use_tcp
+f_variable_new VAR_NFS_V3 nfs_use_v3
+f_variable_new VAR_NONINTERACTIVE nonInteractive
+f_variable_new VAR_NO_CONFIRM noConfirm
+f_variable_new VAR_NO_ERROR noError
+f_variable_new VAR_NO_INET6 noInet6
+f_variable_new VAR_PACKAGE package
+f_variable_new VAR_PKG_TMPDIR PKG_TMPDIR
+f_variable_new VAR_PORTS_PATH ports
+f_variable_new VAR_RELNAME releaseName
+f_variable_new VAR_SLOW_ETHER slowEthernetCard
+f_variable_new VAR_TRY_DHCP tryDHCP
+f_variable_new VAR_TRY_RTSOL tryRTSOL
+f_variable_new VAR_UFS_PATH ufs
+f_variable_new VAR_USER user
+f_variable_new VAR_USER_ACCOUNT_EXPIRE userAccountExpire
+f_variable_new VAR_USER_DOTFILES_CREATE userDotfilesCreate
+f_variable_new VAR_USER_GECOS userGecos
+f_variable_new VAR_USER_GID userGid
+f_variable_new VAR_USER_GROUPS userGroups
+f_variable_new VAR_USER_GROUP_DELETE userGroupDelete
+f_variable_new VAR_USER_HOME userHome
+f_variable_new VAR_USER_HOME_CREATE userHomeCreate
+f_variable_new VAR_USER_HOME_DELETE userHomeDelete
+f_variable_new VAR_USER_LOGIN_CLASS userLoginClass
+f_variable_new VAR_USER_PASSWORD userPassword
+f_variable_new VAR_USER_PASSWORD_EXPIRE userPasswordExpire
+f_variable_new VAR_USER_SHELL userShell
+f_variable_new VAR_USER_UID userUid
+f_variable_new VAR_ZFSINTERACTIVE zfsInteractive
+
+#
+# Self-initialize unless requested otherwise
+#
+f_dprintf "%s: VARIABLE_SELF_INITIALIZE=[%s]" \
+ variable.subr "$VARIABLE_SELF_INITIALIZE"
+case "$VARIABLE_SELF_INITIALIZE" in
+""|0|[Nn][Oo]|[Oo][Ff][Ff]|[Ff][Aa][Ll][Ss][Ee]) : do nothing ;;
+*) f_variable_set_defaults
+esac
+
+f_dprintf "%s: Successfully loaded." variable.subr
+
+fi # ! $_VARIABLE_SUBR
diff --git a/usr.sbin/bsdconfig/startup/INDEX b/usr.sbin/bsdconfig/startup/INDEX
new file mode 100644
index 0000000..afad348
--- /dev/null
+++ b/usr.sbin/bsdconfig/startup/INDEX
@@ -0,0 +1,62 @@
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+#
+# Title that will be shown in the bsdconfig menu.
+#
+menu_title="Startup"
+
+#
+# A short descriptive line shown at the bottom of the bsdconfig menu. keep it
+# short because any line longer than the terminal width will be truncated.
+#
+menu_help="Set Startup Parameters"
+
+#
+# Two-part variable that defines an action to take when `keyword' is passed on
+# a bsdconfig command line. Variable takes the form "keyword|command" and
+# multiple occurrences of the variable (with different `keyword's, or different
+# `keyword's AND `command's) are allowed. If `command' begins with a '/' then
+# the full path to the program is needed. If `command' begins with anything
+# else it is a path relative to the directory this INDEX file is in. `keyword'
+# can be i18n'ed but `command' is the name of a script.
+#
+menu_selection="startup|startup"
+menu_selection="startup_misc|misc"
+menu_selection="startup_rcadd|rcadd"
+menu_selection="startup_rcconf|rcconf"
+menu_selection="startup_rcdelete|rcdelete"
+menu_selection="startup_rcvar|rcvar"
+
+#
+# ------------ Items below this line do NOT need i18n translation ------------
+#
+# Name of the program to be run when this menu choice is selected. If it begins
+# with a '/' then the full path to the program is needed. If it begins with
+# anything else it is a path relative to the directory this INDEX file is in.
+#
+menu_program="startup"
diff --git a/usr.sbin/bsdconfig/startup/Makefile b/usr.sbin/bsdconfig/startup/Makefile
new file mode 100644
index 0000000..62a9e71
--- /dev/null
+++ b/usr.sbin/bsdconfig/startup/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+SUBDIR= include share
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/140.startup
+FILES= INDEX USAGE
+
+SCRIPTSDIR= ${FILESDIR}
+SCRIPTS= misc rcadd rcconf rcdelete rcedit rcvar startup
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/startup/Makefile.depend b/usr.sbin/bsdconfig/startup/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/startup/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/startup/USAGE b/usr.sbin/bsdconfig/startup/USAGE
new file mode 100644
index 0000000..a06d8af
--- /dev/null
+++ b/usr.sbin/bsdconfig/startup/USAGE
@@ -0,0 +1,37 @@
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+Usage: bsdconfig @PROGRAM_NAME@ [OPTIONS]
+
+OPTIONS:
+ -d Provide lots of debugging info on standard-out when running.
+ -D file Send debugging info to file. If file begins with a plus-sign
+ debug info is sent to both standard-out and file (minus the
+ leading plus).
+ -h Print this usage statement and exit.
+ -S Secure X11 mode (implies `-X'). As root, always prompt-for
+ and validate sudo(8) username/password before starting.
+ -X Use Xdialog(1) in place of dialog(1).
diff --git a/usr.sbin/bsdconfig/startup/include/Makefile b/usr.sbin/bsdconfig/startup/include/Makefile
new file mode 100644
index 0000000..aab9842
--- /dev/null
+++ b/usr.sbin/bsdconfig/startup/include/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/140.startup/include
+FILES= messages.subr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/startup/include/Makefile.depend b/usr.sbin/bsdconfig/startup/include/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/startup/include/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/startup/include/messages.subr b/usr.sbin/bsdconfig/startup/include/messages.subr
new file mode 100644
index 0000000..78daa8a
--- /dev/null
+++ b/usr.sbin/bsdconfig/startup/include/messages.subr
@@ -0,0 +1,112 @@
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+hline_alnum_punc_tab_enter="Use alpha-numeric, punctuation, TAB or ENTER"
+hline_arrows_tab_enter="Press arrows, TAB or ENTER"
+msg_accounting="Accounting"
+msg_accounting_desc="This host wishes to run process accounting."
+msg_add_custom="Add Custom"
+msg_add_from_list="Add From List"
+msg_add_new="Add New"
+msg_add_new_desc="Add new directive"
+msg_add_new_help="Add new rc.conf(5) configuration directive"
+msg_add_startup_directive="Add Startup Directive"
+msg_all="All"
+msg_all_desc="Select all directives"
+msg_all_help="Select all displayed rc.conf(5) configuration directives"
+msg_apm="APM"
+msg_apm_desc="Auto-power management services (typically laptops)"
+msg_are_you_sure_you_want_delete_the_following="Are you sure you want to delete the following directives\nfrom the rc.conf(5) collection of system configuration files?"
+msg_are_you_sure_you_want_to_delete="Are you sure you want to delete the \`%s' directive\nfrom the rc.conf(5) collection of system configuration files?"
+msg_cancel="Cancel"
+msg_choose_view_details="Choose View Details"
+msg_creating_menu_list="Creating menu list...\nThis may take a while."
+msg_creating_rcconf_map="Creating rc.conf(5) map...\nThis may take a while."
+msg_creating_rcvar_map="Creating rcvar map...\nThis may take a while."
+msg_default_value="Default: %s"
+msg_delete="Delete"
+msg_delete_desc="Delete directive(s)"
+msg_delete_help="Select one or more directives from a list to delete"
+msg_delete_selected="Delete Selected"
+msg_delete_selected_desc="Delete selected directive(s)"
+msg_delete_selected_help="Delete each of the selected rc.conf(5) configuration directives"
+msg_delete_startup_directives="Delete Startup Directive(s)"
+msg_deleting_selected_directives="Deleting selected directive(s)..."
+msg_desc="Description"
+msg_desc_desc="Toggle display of system description"
+msg_details="Details"
+msg_exit="Exit"
+msg_exit_cancel="Exit/Cancel"
+msg_exit_cancel_desc="Return to previous menu"
+msg_exit_cancel_help="Return to the previous menu (selected items are untouched)"
+msg_exit_desc="Return to previous menu"
+msg_exit_this_menu="Exit this menu"
+msg_info="Info"
+msg_lpd="lpd"
+msg_lpd_desc="This host has a printer and wants to run lpd."
+msg_miscellaneous_menu_text="This menu allows you to configure various aspects of your system's\nstartup configuration. Use [SPACE] or [ENTER] to select items, and\n[TAB] to move to the buttons. Select Exit to leave this menu."
+msg_miscellaneous_startup_services="Miscellaneous Startup Services"
+msg_named="named"
+msg_named_desc="Run a local name server on this host"
+msg_named_flags="named flags"
+msg_named_flags_desc="Set default flags to named (if enabled)"
+msg_nis_client="NIS client"
+msg_nis_client_desc="This host wishes to be an NIS client."
+msg_nis_domainname="NIS domainname"
+msg_nis_domainname_desc="Set NIS domainname (if enabled)"
+msg_nis_server="NIS Server"
+msg_nis_server_desc="This host wishes to be an NIS server."
+msg_none="None"
+msg_none_desc="Un-Select all directives"
+msg_none_help="Un-Select all rc.conf(5) configuration directives"
+msg_ok="OK"
+msg_please_enter_a_new_value="Please enter a new value for \`%s' (Default: %s):"
+msg_please_enter_rcvar_name="Please enter rc.conf(5) variable name:"
+msg_please_select_an_rcconf_directive="Please select an rc.conf(5) directive:"
+msg_rcvar_contains_invalid_chars="ERROR! rc.conf(5) variable name contains invalid characters.\n Name may only consist of letters [a-zA-Z], numbers [0-9],\n or underscore [_] and must not start with a number."
+msg_rcvar_must_start_with="ERROR! rc.conf(5) variable name must start with\n a letter [a-zA-Z] or underscore [_]."
+msg_reset="Reset"
+msg_reset_desc="Reset to default view settings"
+msg_sco="SCO"
+msg_sco_desc="This host wants to be able to run IBCS2 binaries."
+msg_show_configured="Show Configured"
+msg_show_configured_desc="Calculate rc.conf(5) locations (slowest)"
+msg_show_default_value="Show Default/Value"
+msg_show_default_value_desc="Show default/configured values (slow)"
+msg_show_value="Show Value"
+msg_show_value_desc="Show configured startup value (fast)"
+msg_startup="Startup"
+msg_startup_dirs="Startup dirs"
+msg_startup_dirs_desc="Set the list of dirs to look for startup scripts"
+msg_svr4="SVR4"
+msg_svr4_desc="This host wants to be able to run SVR4 binaries."
+msg_toggle_startup_services="Toggle Startup Services"
+msg_unknown_startup_menu_selection="Unknown startup menu selection"
+msg_value_required="Value Required"
+msg_view_details="View Details"
+msg_view_details_desc="Choose view details"
+msg_view_details_help="Choose which details are shown in the current view"
+msg_view_edit_startup_configuration="View/Edit Startup Configuration"
diff --git a/usr.sbin/bsdconfig/startup/misc b/usr.sbin/bsdconfig/startup/misc
new file mode 100755
index 0000000..a792daf
--- /dev/null
+++ b/usr.sbin/bsdconfig/startup/misc
@@ -0,0 +1,406 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="140.startup"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ FUNCTIONS
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local prompt="$msg_miscellaneous_menu_text"
+ local menu_list="
+ 'X $msg_exit' '$msg_exit_this_menu'
+ " # END-QUOTE
+ local defaultitem= # Calculated below
+ local hline="$hline_arrows_tab_enter"
+
+ # List of variables we'll need from rc.conf(5)
+ local var_list="
+ accounting_enable
+ local_startup
+ lpd_enable
+ named_enable
+ named_flags
+ nis_client_enable
+ nis_server_enable
+ nisdomainname
+ startup_dirs
+ " # END-QUOTE
+
+ # Add i386-specific variables if appropriate
+ if [ "$UNAME_P" = "i386" ]; then
+ var_list="$var_list
+ apm_enable
+ ibcs2_enable
+ svr4_enable
+ " # END-QUOTE
+ fi
+
+ # Obtain default-item (adjusted below for dynamic tags)
+ f_dialog_default_fetch defaultitem
+ local ditem="${defaultitem%%[$IFS]*}"
+
+ eval "$(
+ . "$RC_DEFAULTS"
+ source_rc_confs
+ export $var_list
+ export msg_apm msg_apm_desc
+ export msg_startup_dirs msg_startup_dirs_desc
+ export msg_named msg_named_desc
+ export msg_named_flags msg_named_flags_desc
+ export msg_nis_client msg_nis_client_desc
+ export msg_nis_domainname msg_nis_domainname_desc
+ export msg_nis_server msg_nis_server_desc
+ export msg_accounting msg_accounting_desc
+ export msg_lpd msg_lpd_desc
+ export msg_sco msg_sco_desc
+ export msg_svr4 msg_svr4_desc
+ :| awk \
+ -v uname_p="$UNAME_P" \
+ -v menu_tags="$DIALOG_MENU_TAGS" \
+ -v menu_fmt="'%s' '%s'\n" \
+ -v mtag_fmt="%c [%c] %s" \
+ -v separator="' ' ' -- '\n" \
+ -v ditem="$ditem" \
+ '
+ function mprint(tag,item)
+ {
+ printf menu_fmt, tag, item
+ }
+ END {
+ i = 1
+ defaultitem = ""
+
+ printf "menu_list=\"$menu_list\n"
+
+ if ( uname_p == "i386" )
+ {
+ #
+ # APM: Auto-power management services
+ # (typically laptops)
+ #
+ char = substr(menu_tags, i++, 1)
+ mark = ( ENVIRON["apm_enable"] ~ \
+ /^[Yy][Ee][Ss]$/ ? "X" : " " )
+ subtag = ENVIRON["msg_apm"]
+ tag = sprintf(mtag_fmt, char, mark, subtag)
+ mprint(tag, ENVIRON["msg_apm_desc"])
+ if (ditem == char) defaultitem = tag
+ }
+
+ printf separator
+
+ #
+ # Startup dirs: Set the list of dirs to look for
+ # startup scripts
+ #
+ char = substr(menu_tags, i++, 1)
+ mark = ( length(ENVIRON["local_startup"]) > 0 \
+ ? "X" : " " )
+ subtag = ENVIRON["msg_startup_dirs"]
+ tag = sprintf(mtag_fmt, char, mark, subtag)
+ mprint(tag, ENVIRON["msg_startup_dirs_desc"])
+ if (ditem == char) defaultitem = tag
+
+ #
+ # named: Run a local name server on this host
+ #
+ char = substr(menu_tags, i++, 1)
+ mark = ( ENVIRON["named_enable"] ~ \
+ /^[Yy][Ee][Ss]$/ ? "X" : " " )
+ subtag = ENVIRON["msg_named"]
+ tag = sprintf(mtag_fmt, char, mark, subtag)
+ mprint(tag, ENVIRON["msg_named_desc"])
+ if (ditem == char) defaultitem = tag
+
+ #
+ # named flags: Set default flags to named (if enabled)
+ #
+ char = substr(menu_tags, i++, 1)
+ mark = ( length(ENVIRON["named_flags"]) > 0 \
+ ? "X" : " " )
+ subtag = ENVIRON["msg_named_flags"]
+ tag = sprintf(mtag_fmt, char, mark, subtag)
+ mprint(tag, ENVIRON["msg_named_flags_desc"])
+ if (ditem == char) defaultitem = tag
+
+ #
+ # NIS client: This host wishes to be an NIS client.
+ #
+ char = substr(menu_tags, i++, 1)
+ mark = ( ENVIRON["nis_client_enable"] ~ \
+ /^[Yy][Ee][Ss]$/ ? "X" : " " )
+ subtag = ENVIRON["msg_nis_client"]
+ tag = sprintf(mtag_fmt, char, mark, subtag)
+ mprint(tag, ENVIRON["msg_nis_client_desc"])
+ if (ditem == char) defaultitem = tag
+
+ #
+ # NIS domainname: Set NIS domainname (if enabled)
+ #
+ char = substr(menu_tags, i++, 1)
+ mark = ( length(ENVIRON["nisdomainname"]) > 0 && \
+ ENVIRON["nisdomainname"] != "NO" \
+ ? "X" : " " )
+ subtag = ENVIRON["msg_nis_domainname"]
+ tag = sprintf(mtag_fmt, char, mark, subtag)
+ mprint(tag, ENVIRON["msg_nis_domainname_desc"])
+ if (ditem == char) defaultitem = tag
+
+ #
+ # NIS server: This host wishes to be an NIS server.
+ #
+ char = substr(menu_tags, i++, 1)
+ mark = ( ENVIRON["nis_server_enable"] ~ \
+ /^[Yy][Ee][Ss]$/ ? "X" : " " )
+ subtag = ENVIRON["msg_nis_server"]
+ tag = sprintf(mtag_fmt, char, mark, subtag)
+ mprint(tag, ENVIRON["msg_nis_server_desc"])
+ if (ditem == char) defaultitem = tag
+
+ printf separator
+
+ #
+ # Accounting: This host wishes to run process
+ # accounting.
+ #
+ char = substr(menu_tags, i++, 1)
+ mark = ( ENVIRON["accounting_enable"] ~ \
+ /^[Yy][Ee][Ss]$/ ? "X" : " " )
+ subtag = ENVIRON["msg_accounting"]
+ tag = sprintf(mtag_fmt, char, mark, subtag)
+ mprint(tag, ENVIRON["msg_accounting_desc"])
+ if (ditem == char) defaultitem = tag
+
+ #
+ # lpd: This host has a printer and wants to run lpd.
+ #
+ char = substr(menu_tags, i++, 1)
+ mark = ( ENVIRON["lpd_enable"] ~ \
+ /^[Yy][Ee][Ss]$/ ? "X" : " " )
+ subtag = ENVIRON["msg_lpd"]
+ tag = sprintf(mtag_fmt, char, mark, subtag)
+ mprint(tag, ENVIRON["msg_lpd_desc"])
+ if (ditem == char) defaultitem = tag
+
+ if ( uname_p == "i386" )
+ {
+ #
+ # SCO: This host wants to be able to run IBCS2
+ # binaries.
+ #
+ char = substr(menu_tags, i++, 1)
+ mark = ( ENVIRON["ibcs2_enable"] ~ \
+ /^[Yy][Ee][Ss]$/ ? "X" : " " )
+ subtag = ENVIRON["msg_sco"]
+ tag = sprintf(mtag_fmt, char, mark, subtag)
+ mprint(tag, ENVIRON["msg_sco_desc"])
+ if (ditem == char) defaultitem = tag
+
+ #
+ # SVR4: This host wants to be able to run SVR4
+ # binaries.
+ #
+ char = substr(menu_tags, i++, 1)
+ mark = ( ENVIRON["svr4_enable"] ~ \
+ /^[Yy][Ee][Ss]$/ ? "X" : " " )
+ subtag = ENVIRON["msg_svr4"]
+ tag = sprintf(mtag_fmt, char, mark, subtag)
+ mprint(tag, ENVIRON["msg_svr4_desc"])
+ if (ditem == char) defaultitem = tag
+ }
+
+ printf "\"\n"
+
+ if ( defaultitem )
+ printf "defaultitem=\"%s\"\n", defaultitem
+ }'
+ )"
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize menu_choice
+ f_dialog_menutag_store "$menu_choice"
+ f_dialog_default_store "$menu_choice"
+ return $retval
+}
+
+# dialog_input_value [ $prompt [ $init ] ]
+#
+# Prompt the user to input a value. If the user does not cancel or press ESC,
+# the return value is zero ($SUCCESS) and $value holds the user's input.
+#
+dialog_input_value()
+{
+ local prompt="$1" _input="$2"
+
+ f_dialog_title "$msg_value_required"
+ f_dialog_input _input "$prompt" "$_input" "$hline_alnum_tab_enter"
+ local retval=$?
+ f_dialog_title_restore
+
+ # Return if user has either pressed ESC or chosen Cancel/No
+ [ $retval -eq $DIALOG_OK ] || return $retval
+
+ value="$_input"
+ return $DIALOG_OK
+}
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_miscellaneous_startup_services"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Launch application main menu
+#
+while :; do
+ dialog_menu_main || f_die
+ f_dialog_menutag_fetch mtag
+
+ case "$mtag" in
+ "X $msg_exit") break ;;
+ ?" [X] "*) toggled=1 reverse=NO ;;
+ *) toggled= reverse=YES
+ esac
+
+ case "$mtag" in
+ # Simple On/Off toggle bits
+ ?" ["?"] $msg_apm")
+ f_eval_catch -dk err "$0" f_sysrc_set \
+ 'f_sysrc_set apm_enable "%s"' "$reverse" ;;
+ ?" ["?"] $msg_named")
+ f_eval_catch -dk err "$0" f_sysrc_set \
+ 'f_sysrc_set named_enable "%s"' "$reverse" ;;
+ ?" ["?"] $msg_accounting")
+ f_eval_catch -dk err "$0" f_sysrc_set \
+ 'f_sysrc_set accounting_enable "%s"' "$reverse" ;;
+ ?" ["?"] $msg_lpd")
+ f_eval_catch -dk err "$0" f_sysrc_set \
+ 'f_sysrc_set lpd_enable "%s"' "$reverse" ;;
+ ?" ["?"] $msg_sco")
+ f_eval_catch -dk err "$0" f_sysrc_set \
+ 'f_sysrc_set ibcs2_enable "%s"' "$reverse" ;;
+ ?" ["?"] $msg_svr4")
+ f_eval_catch -dk err "$0" f_sysrc_set \
+ 'f_sysrc_set svr4_enable "%s"' "$reverse" ;;
+ # Multi-variable On/Off toggle bits
+ ?" ["?"] $msg_nis_client")
+ if f_eval_catch -dk err "$0" f_sysrc_set \
+ 'f_sysrc_set nis_client_enable "%s"' "$reverse"
+ then
+ [ "$reverse" = "NO" ] || f_eval_catch -dk err "$0" \
+ f_sysrc_set 'f_sysrc_set rpcbind_enable YES'
+ fi ;;
+ ?" ["?"] $msg_nis_server")
+ if f_eval_catch -dk err "$0" f_sysrc_set \
+ 'f_sysrc_set nis_server_enable "%s"' "$reverse"
+ then
+ [ "$reverse" = "NO" ] || f_eval_catch -dk err "$0" \
+ f_sysrc_set 'f_sysrc_set rpcbind_enable YES'
+ fi ;;
+ # Checkboxes for non-boolean options
+ ?" ["?"] $msg_nis_domainname")
+ dialog_input_value "$msg_nis_domainname_desc" \
+ "$( f_sysrc_get nisdomainname )" || continue
+ f_eval_catch -dk err "$0" f_sysrc_set \
+ 'f_sysrc_set nisdomainname "%s"' "$value" ;;
+ ?" ["?"] $msg_startup_dirs")
+ dialog_input_value "$msg_startup_dirs_desc" \
+ "$( f_sysrc_get local_startup )" || continue
+ f_eval_catch -dk err "$0" f_sysrc_set \
+ 'f_sysrc_set local_startup "%s"' "$value" ;;
+ ?" ["?"] $msg_named_flags")
+ dialog_input_value "$msg_named_flags_desc" \
+ "$( f_sysrc_get named_flags )" || continue
+ f_eval_catch -dk err "$0" f_sysrc_set \
+ 'f_sysrc_set named_flags "%s"' "$value" ;;
+ esac
+
+ [ $? -eq $DIALOG_OK ] || f_dialog_msgbox "$err\n"
+done
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/startup/rcadd b/usr.sbin/bsdconfig/startup/rcadd
new file mode 100755
index 0000000..7b536c2
--- /dev/null
+++ b/usr.sbin/bsdconfig/startup/rcadd
@@ -0,0 +1,149 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/startup/rcconf.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="140.startup"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ GLOBALS
+
+#
+# Options
+#
+# Inherit SHOW_DESC value if set, otherwise default to 1
+[ "${SHOW_DESC+set}" ] || SHOW_DESC=1
+
+############################################################ FUNCTIONS
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local prompt=
+ local menu_list="
+ '1' '$msg_add_from_list'
+ '2' '$msg_add_custom'
+ " # END-QUOTE
+ local hline="$hline_arrows_tab_enter"
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_menutag_store -s "$menu_choice"
+ return $retval
+}
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_add_startup_directive"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Launch application main menu
+#
+while :; do
+ dialog_menu_main || f_die
+ f_dialog_menutag_fetch mtag
+
+ case "$mtag" in
+ 1) # Add From List
+ # Loop to allow adding multiple variables quickly
+ defaultitem=
+ while :; do
+ f_dialog_input_rclist "$defaultitem" || f_die
+ f_dialog_menutag_fetch mtag
+ defaultitem="$mtag"
+
+ [ "$mtag" = "X $msg_exit" ] && break
+
+ # Anything else is a directive
+
+ $BSDCFG_LIBE/$APP_DIR/rcedit ${USE_XDIALOG:+-X} \
+ "${mtag# }"
+ done
+ ;;
+ 2) # Add Custom
+ f_dialog_input_rcvar || continue
+ $BSDCFG_LIBE/$APP_DIR/rcedit ${USE_XDIALOG:+-X} "$rcvar"
+ ;;
+ esac
+done
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/startup/rcconf b/usr.sbin/bsdconfig/startup/rcconf
new file mode 100755
index 0000000..6a6247b
--- /dev/null
+++ b/usr.sbin/bsdconfig/startup/rcconf
@@ -0,0 +1,264 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+f_include $BSDCFG_SHARE/startup/rcconf.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="140.startup"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ GLOBALS
+
+#
+# Global map/menu-list for the main menu
+#
+RCCONF_MAP=
+_RCCONF_MAP=
+
+#
+# Options
+#
+# Inherit SHOW_DESC value if set, otherwise default to 1
+[ "${SHOW_DESC+set}" ] || SHOW_DESC=1
+# Selectively inherit SHOW_* value (in order of preference)
+if [ "$SHOW_DEFAULT_VALUE" ]; then
+ SHOW_DEFAULT_VALUE=1
+ SHOW_CONFIGURED=
+ SHOW_VALUE=
+elif [ "$SHOW_CONFIGURED" ]; then
+ SHOW_DEFAULT_VALUE=
+ SHOW_CONFIGURED=1
+ SHOW_VALUE=
+else
+ SHOW_DEFAULT_VALUE=
+ SHOW_CONFIGURED=
+ SHOW_VALUE=1
+fi
+
+############################################################ FUNCTIONS
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local prompt=
+ local menu_list="
+ 'X $msg_exit' '$msg_exit_desc'
+ ${SHOW_DESC:+'$msg_exit_this_menu'}
+ '> $msg_add_new' '$msg_add_new_desc'
+ ${SHOW_DESC:+'$msg_add_new_help'}
+ '> $msg_delete' '$msg_delete_desc'
+ ${SHOW_DESC:+'$msg_delete_help'}
+ ${USE_XDIALOG:+
+ '> $msg_view_details' '$msg_view_details_desc'
+ ${SHOW_DESC:+'$msg_view_details_help'}
+ }
+ " # END-QUOTE
+ local defaultitem= # Calculated below
+ local hline="$hline_arrows_tab_enter"
+
+ if [ ! "$_RCCONF_MAP" ]; then
+ # Genreate RCCONF_MAP of `var desc ...' per-line
+ f_dialog_info "$msg_creating_rcconf_map"
+ f_startup_rcconf_map RCCONF_MAP
+ export RCCONF_MAP
+ # Generate _${var}_desc variables from $RCCONF_MAP
+ f_startup_rcconf_map_expand RCCONF_MAP
+ export _RCCONF_MAP=1
+ fi
+
+ # Show infobox for modes that take a while to calculate/display
+ [ "$SHOW_DEFAULT_VALUE" -o "$SHOW_CONFIGURED" ] &&
+ f_dialog_info "$msg_creating_menu_list"
+
+ menu_list="$menu_list $(
+ . "$RC_DEFAULTS" > /dev/null
+ source_rc_confs > /dev/null
+ var_list=$( f_startup_rcconf_list )
+ for var in $var_list; do
+ eval export $var
+ [ "$SHOW_DEFAULT_VALUE" ] && export \
+ _${var}_default="$( f_sysrc_get_default $var )"
+ [ "$SHOW_CONFIGURED" ] && export \
+ _${var}_file="$( f_sysrc_find $var )"
+ done
+ export SHOW_VALUE SHOW_DESC SHOW_DEFAULT_VALUE SHOW_CONFIGURED
+ export msg_default_value
+ echo "$var_list" | awk '
+ BEGIN {
+ prefix = ""
+ }
+ {
+ cur_prefix = tolower(substr($1, 1, 1))
+ printf "'\''"
+ if ( prefix != cur_prefix )
+ prefix = cur_prefix
+ else
+ printf " "
+ var = $1
+ printf "%s'\'' '\''", var
+ if ( ENVIRON["SHOW_DEFAULT_VALUE"] ) {
+ default = ENVIRON["_" var "_default"]
+ gsub(/'\''/, "'\''\\'\'\''", default)
+ value = ENVIRON[var]
+ gsub(/'\''/, "'\''\\'\'\''", value)
+ printf ENVIRON["msg_default_value"] "; %s",
+ default, value
+ } else if ( ENVIRON["SHOW_CONFIGURED"] ) {
+ printf "%s", ENVIRON["_" var "_file"]
+ } else { # SHOW_VALUE (default behavior)
+ value = ENVIRON[var]
+ gsub(/'\''/, "'\''\\'\'\''", value)
+ printf "%s", value
+ }
+ printf "'\''"
+ if ( ENVIRON["SHOW_DESC"] ) {
+ desc = ENVIRON["_" var "_desc"]
+ gsub(/'\''/, "'\''\\'\'\''", desc)
+ printf " '\''%s'\''", desc
+ }
+ printf "\n"
+ }'
+ )"
+
+ set -f # set noglob because descriptions in the $menu_list may contain
+ # `*' and get expanded by dialog(1) (doesn't affect Xdialog(1)).
+ # This prevents dialog(1) from expanding wildcards in help line.
+
+ local height width rows
+ eval f_dialog_menu${SHOW_DESC:+_with_help}_size \
+ height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ # Obtain default-item from previously stored selection
+ f_dialog_default_fetch defaultitem
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --help-button \
+ --help-label \"\$msg_details\" \
+ ${SHOW_DESC:+--item-help} \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize menu_choice
+ f_dialog_menutag_store "$menu_choice"
+
+ # Only update default-item on success
+ [ $retval -eq $DIALOG_OK ] && f_dialog_default_store "$menu_choice"
+
+ return $retval
+}
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_view_edit_startup_configuration"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Launch application main menu
+#
+while :; do
+ dialog_menu_main
+ retval=$?
+ f_dialog_menutag_fetch mtag
+
+ if [ "$USE_XDIALOG" ]; then
+ case "$mtag" in
+ "> $msg_view_details")
+ f_dialog_input_view_details
+ continue
+ esac
+ elif [ $retval -eq $DIALOG_HELP ]; then
+ # The ``Help'' button (labeled "Details") was pressed
+ f_dialog_input_view_details
+ continue
+ fi
+
+ [ $retval -eq $DIALOG_OK ] || f_die
+
+ case "$mtag" in
+ "X $msg_exit") break ;;
+ "> $msg_add_new") $BSDCFG_LIBE/$APP_DIR/rcadd ${USE_XDIALOG:+-X} ;;
+ "> $msg_delete")
+ # rcdelete has a similar interface that can inherit the below:
+ export SHOW_VALUE SHOW_DESC SHOW_DEFAULT_VALUE SHOW_CONFIGURED
+ $BSDCFG_LIBE/$APP_DIR/rcdelete ${USE_XDIALOG:+-X}
+ ;;
+ *) # Anything else is a variable to edit
+ $BSDCFG_LIBE/$APP_DIR/rcedit ${USE_XDIALOG:+-X} "${mtag# }"
+ esac
+done
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/startup/rcdelete b/usr.sbin/bsdconfig/startup/rcdelete
new file mode 100755
index 0000000..ccc15ba
--- /dev/null
+++ b/usr.sbin/bsdconfig/startup/rcdelete
@@ -0,0 +1,414 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+f_include $BSDCFG_SHARE/startup/rcconf.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="140.startup"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ GLOBALS
+
+#
+# Global map/menu-list for the main menu
+#
+RCCONF_MAP=
+RCCONF_MENU_LIST=
+
+#
+# Options
+#
+# Inherit SHOW_DESC value if set, otherwise default to 1
+[ "${SHOW_DESC+set}" ] || SHOW_DESC=1
+# Selectively inherit SHOW_* value (in order of preference)
+if [ "$SHOW_DEFAULT_VALUE" ]; then
+ SHOW_DEFAULT_VALUE=1
+ SHOW_CONFIGURED=
+ SHOW_VALUE=
+elif [ "$SHOW_CONFIGURED" ]; then
+ SHOW_DEFAULT_VALUE=
+ SHOW_CONFIGURED=1
+ SHOW_VALUE=
+else
+ SHOW_DEFAULT_VALUE=
+ SHOW_CONFIGURED=
+ SHOW_VALUE=1
+fi
+
+############################################################ FUNCTIONS
+
+# dialog_create_main
+#
+# Create the dialog(1) main menu. Separated from dialog_menu_main (used to
+# display the menu) to speed up execution (we only call this function when
+# initializing or changing the view details).
+#
+dialog_create_main()
+{
+ # Show infobox for modes that take a while to calculate/display
+ [ "$SHOW_DEFAULT_VALUE" -o "$SHOW_CONFIGURED" ] &&
+ f_dialog_info "$msg_creating_menu_list"
+
+ RCCONF_MENU_LIST=$(
+ . "$RC_DEFAULTS" > /dev/null
+ source_rc_confs > /dev/null
+ var_list=$( f_startup_rcconf_list )
+ for var in $var_list; do
+ eval export $var
+ [ "$SHOW_DEFAULT_VALUE" ] && export \
+ _${var}_default="$( f_sysrc_get_default $var )"
+ [ "$SHOW_CONFIGURED" ] && export \
+ _${var}_file="$( f_sysrc_find $var )"
+ done
+ export SHOW_VALUE SHOW_DESC SHOW_DEFAULT_VALUE SHOW_CONFIGURED
+ export msg_default_value
+ echo "$var_list" | awk '
+ BEGIN {
+ prefix = ""
+ }
+ {
+ cur_prefix = tolower(substr($1, 1, 1))
+ printf "'\''"
+ if ( prefix != cur_prefix )
+ prefix = cur_prefix
+ else
+ printf " "
+ var = $1
+ printf "%s'\'' '\''[", var
+ if ( ENVIRON["_" var "_delete"] )
+ printf "X"
+ else
+ printf " "
+ printf "] "
+ if ( ENVIRON["SHOW_DEFAULT_VALUE"] ) {
+ default = ENVIRON["_" var "_default"]
+ gsub(/'\''/, "'\''\\'\'\''", default)
+ value = ENVIRON[var]
+ gsub(/'\''/, "'\''\\'\'\''", value)
+ printf ENVIRON["msg_default_value"] "; %s",
+ default, value
+ } else if ( ENVIRON["SHOW_CONFIGURED"] ) {
+ printf "%s", ENVIRON["_" var "_file"]
+ } else { # SHOW_VALUE (default behavior)
+ value = ENVIRON[var]
+ gsub(/'\''/, "'\''\\'\'\''", value)
+ printf "%s", value
+ }
+ printf "'\''"
+ if ( ENVIRON["SHOW_DESC"] ) {
+ desc = ENVIRON["_" var "_desc"]
+ gsub(/'\''/, "'\''\\'\'\''", desc)
+ printf " '\''%s'\''", desc
+ }
+ printf "\n"
+ }'
+ )
+}
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local prompt=
+ local menu_list="
+ 'X $msg_exit_cancel' '$msg_exit_cancel_desc'
+ ${SHOW_DESC:+'$msg_exit_cancel_help'}
+ '> $msg_delete_selected' '$msg_delete_selected_desc'
+ ${SHOW_DESC:+'$msg_delete_selected_help'}
+ '> $msg_all' '$msg_all_desc'
+ ${SHOW_DESC:+'$msg_all_help'}
+ '> $msg_none' '$msg_none_desc'
+ ${SHOW_DESC:+'$msg_none_help'}
+ ${USE_XDIALOG:+
+ '> $msg_view_details' '$msg_view_details_desc'
+ ${SHOW_DESC:+'$msg_view_details_help'}
+ }
+ " # END-QUOTE
+ local defaultitem= # Calculated below
+ local hline="$hline_arrows_tab_enter"
+
+ #
+ # [Re-]Accent the menu list before incorporating it
+ #
+ local rcconf_var details help menu_buf delete
+ eval set -- $RCCONF_MENU_LIST
+ while [ $# -gt 0 ]; do
+ rcconf_var="$1" details="$2" delete=
+ f_shell_escape "$details" details
+ if [ "$SHOW_DESC" ]; then
+ help="$3"
+ f_shell_escape "$help" help
+ shift 3 # rcconf_var/details/help
+ else
+ shift 2 # rcconf_var/details
+ fi
+
+ # Update mark
+ f_getvar _${rcconf_var# }_delete delete
+ if [ "$delete" ]; then
+ details="[X]${details#???}"
+ else
+ details="[ ]${details#???}"
+ fi
+
+ # Update buffer with modified elements
+ menu_buf="$menu_buf
+ '$rcconf_var' '$details' ${SHOW_DESC:+'$help'}" # End-Quote
+ done
+ menu_list="$menu_list $menu_buf"
+
+ set -f # set noglob because descriptions in the $menu_list may contain
+ # `*' and get expanded by dialog(1) (doesn't affect Xdialog(1)).
+ # This prevents dialog(1) from expanding wildcards in help line.
+
+ local height width rows
+ eval f_dialog_menu${SHOW_DESC:+_with_help}_size \
+ height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ # Obtain default-item from previously stored selection
+ f_dialog_default_fetch defaultitem
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --keep-tite \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --help-button \
+ --help-label \"\$msg_details\" \
+ ${SHOW_DESC:+--item-help} \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize menu_choice
+ f_dialog_menutag_store "$menu_choice"
+
+ # Only update default-item on success
+ [ $retval -eq $DIALOG_OK ] && f_dialog_default_store "$menu_choice"
+
+ return $retval
+}
+
+# dialog_menu_confirm_delete $var1 [$var2 ...]
+#
+# Get the user's blessing to delete one or more variables. Returns success if
+# (and only-if) the user confirms (does not press ESC or Cancel/NO). Does NOT
+# return the user's menu-choice.
+#
+dialog_menu_confirm_delete()
+{
+ local prompt="$msg_are_you_sure_you_want_delete_the_following"
+ local menu_list # Calculated below
+ local hline="$hline_arrows_tab_enter"
+
+ [ $# -ge 1 ] || return $DIALOG_CANCEL
+
+ # If asked to delete only one variable, simply ask and return
+ if [ $# -eq 1 ]; then
+ f_noyes "$msg_are_you_sure_you_want_to_delete" "$1"
+ return $?
+ fi
+ # Not reached unless requested to delete multiple variables
+
+ # Generate a menu to cleanly display the variables to be deleted
+ local var_list
+ var_list=$( for var in $*; do echo "$var"; done | sort -u )
+ menu_list=$(
+ . "$RC_DEFAULTS"
+ source_rc_confs
+ echo "$var_list" | awk '
+ BEGIN {
+ prefix = ""
+ }
+ {
+ cur_prefix = tolower(substr($1, 1, 1))
+ printf "'\''"
+ if ( prefix != cur_prefix )
+ prefix = cur_prefix
+ else
+ printf " "
+ var = $1
+ printf "%s'\'' '\'\''\n", var
+ }'
+ )
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ local defaultno="defaultno"
+ [ "$USE_XDIALOG" ] && defaultno="default-no"
+
+ eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --$defaultno \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2> /dev/null
+
+ # Menu choice ignored; status of above command returned
+}
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_delete_startup_directives"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+# Genreate $RCCONF_MAP of `var desc ...' per-line (see share/rcconf.subr)
+f_dialog_info "$msg_creating_rcconf_map"
+f_startup_rcconf_map RCCONF_MAP
+
+# Generate _${var}_desc variables from $RCCONF_MAP
+f_startup_rcconf_map_expand RCCONF_MAP
+
+# Generate RCCONF_MENU_LIST from $RCCONF_MAP
+dialog_create_main
+
+#
+# Launch application main menu
+#
+while :; do
+ dialog_menu_main
+ retval=$?
+ f_dialog_menutag_fetch mtag
+
+ if [ "$USE_XDIALOG" ]; then
+ case "$mtag" in "> $msg_view_details")
+ f_dialog_input_view_details && dialog_create_main
+ continue
+ esac
+ elif [ $retval -eq $DIALOG_HELP ]; then
+ # The ``Help'' button (labeled "Details") was pressed
+ f_dialog_input_view_details && dialog_create_main
+ continue
+ fi
+
+ [ $retval -eq $DIALOG_OK ] || f_die
+
+ case "$mtag" in
+ "X $msg_exit_cancel") break ;;
+ "> $msg_delete_selected")
+ delete_vars=
+ for var in $( f_startup_rcconf_list ); do
+ f_getvar _${var}_delete _delete
+ [ "$_delete" ] || continue
+ delete_vars="$delete_vars $var"
+ done
+ if dialog_menu_confirm_delete $delete_vars; then
+ f_dialog_title "$msg_info"
+ f_dialog_info "$msg_deleting_selected_directives"
+ f_dialog_title_restore
+ for var in $delete_vars; do
+ f_eval_catch "$0" f_sysrc_delete \
+ 'f_sysrc_delete "%s"' "$var" || break
+ done
+ dialog_create_main
+ fi
+ ;;
+ "> $msg_all")
+ for var in $( f_startup_rcconf_list ); do
+ setvar _${var}_delete 1
+ export _${var}_delete
+ done
+ ;;
+ "> $msg_none")
+ var_list=$( set | awk -F= "
+ /$STARTUP_RCCONF_REGEX/ {
+ if (\$1 ~ /^_[[:alpha:]_][[:alnum:]_]*_delete/)
+ print \$1
+ }"
+ )
+ [ "$var_list" ] && unset $var_list
+ ;;
+ *) # Anything else is a variable to edit
+ var="${mtag# }"
+
+ # Toggle the state-variable and loop back to menu
+ if f_isset _${var}_delete; then
+ unset _${var}_delete
+ else
+ setvar _${var}_delete 1
+ export _${var}_delete
+ fi
+ esac
+done
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/startup/rcedit b/usr.sbin/bsdconfig/startup/rcedit
new file mode 100755
index 0000000..54061b5
--- /dev/null
+++ b/usr.sbin/bsdconfig/startup/rcedit
@@ -0,0 +1,72 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/startup/rcedit.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="140.startup"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Edit the rc.conf(5) variable(s) passed as argument(s)
+#
+f_dialog_rcedit "$@"
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/startup/rcvar b/usr.sbin/bsdconfig/startup/rcvar
new file mode 100755
index 0000000..ddca906
--- /dev/null
+++ b/usr.sbin/bsdconfig/startup/rcvar
@@ -0,0 +1,220 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+f_include $BSDCFG_SHARE/startup/rcvar.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="140.startup"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ GLOBALS
+
+#
+# Global map/menu-list for the main menu
+#
+RCVAR_MAP=
+_RCVAR_MAP=
+
+#
+# Options
+#
+# Inherit SHOW_DESC value if set, otherwise default to 1
+[ "${SHOW_DESC+set}" ] || SHOW_DESC=1
+
+############################################################ FUNCTIONS
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local prompt=
+ local menu_list="
+ 'X $msg_exit' '$msg_exit_this_menu'
+ ${SHOW_DESC:+'$msg_exit_this_menu'}
+ " # END-QUOTE
+ local hline="$hline_arrows_tab_enter"
+ local defaultitem= # Calculated below
+
+ if [ ! "$_RCVAR_MAP" ]; then
+ # Generate RCVAR_MAP of `rcvar dflt script desc ...' per-line
+ f_dialog_info "$msg_creating_rcvar_map"
+ RCVAR_MAP=$( f_startup_rcvar_map )
+ export RCVAR_MAP
+ export _RCVAR_MAP=1
+ fi
+
+ menu_list="$menu_list $(
+ . "$RC_DEFAULTS" > /dev/null
+ source_rc_confs > /dev/null
+ for rcvar in $( echo "$RCVAR_MAP" | awk '{print $1}' ); do
+ eval export $rcvar
+ done
+ export SHOW_DESC msg_default_value
+ echo "$RCVAR_MAP" | awk '
+ BEGIN {
+ prefix = ""
+ rword = "^[[:space:]]*[^[:space:]]*[[:space:]]*"
+ }
+ {
+ cur_prefix = tolower(substr($1, 1, 1))
+ printf "'\''"
+ if ( prefix != cur_prefix )
+ prefix = cur_prefix
+ else
+ printf " "
+ rcvar = $1
+ default = $2
+ script = $3
+ printf "%s'\'' '\''", rcvar
+ if ( ENVIRON[rcvar] ~ /[Yy][Ee][Ss]/ )
+ printf "[X] "
+ else
+ printf "[ ] "
+ printf "%s; " ENVIRON["msg_default_value"],
+ script, default
+ printf "'\''"
+ if ( ENVIRON["SHOW_DESC"] ) {
+ desc = $0
+ sub(rword, "", desc)
+ sub(rword, "", desc)
+ sub(rword, "", desc)
+ gsub(/'\''/, "'\''\\'\'\''", desc)
+ printf " '\''%s'\''", desc
+ }
+ printf "\n"
+ }'
+ )"
+
+ set -f # set noglob because descriptions in the $menu_list may
+ # contain `*' and get expanded by dialog(1). This prevents
+ # dialog(1) from expanding wildcards in the help line.
+
+ local height width rows
+ eval f_dialog_menu${SHOW_DESC:+_with_help}_size \
+ height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ # Obtain default-item from previously stored selection
+ f_dialog_default_fetch defaultitem
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --keep-tite \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ ${SHOW_DESC:+--item-help} \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize menu_choice
+ f_dialog_menutag_store "$menu_choice"
+ f_dialog_default_store "$menu_choice"
+
+ if [ $retval -eq $DIALOG_OK ]; then
+ local item
+ item=$( eval f_dialog_menutag2item${SHOW_DESC:+_with_help} \
+ \"\$menu_choice\" $menu_list )
+ f_dialog_menuitem_store "$item"
+ fi
+
+ return $retval
+}
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_toggle_startup_services"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Launch application main menu
+#
+while :; do
+ dialog_menu_main || f_die
+ f_dialog_menutag_fetch mtag
+
+ [ "$mtag" = "X $msg_exit" ] && break
+
+ # Anything else is an rcvar to toggle
+
+ rcvar="${mtag# }"
+ f_dialog_menuitem_fetch value
+
+ # Determine the new [toggled] value to use
+ case "$value" in
+ "[X]"*) value="NO" ;;
+ *) value="YES"
+ esac
+
+ f_eval_catch "$0" f_sysrc_set 'f_sysrc_set "%s" "%s"' "$rcvar" "$value"
+done
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/startup/share/Makefile b/usr.sbin/bsdconfig/startup/share/Makefile
new file mode 100644
index 0000000..134914c
--- /dev/null
+++ b/usr.sbin/bsdconfig/startup/share/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+FILESDIR= ${SHAREDIR}/bsdconfig/startup
+FILES= rcconf.subr rcedit.subr rcvar.subr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/startup/share/Makefile.depend b/usr.sbin/bsdconfig/startup/share/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/startup/share/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/startup/share/rcconf.subr b/usr.sbin/bsdconfig/startup/share/rcconf.subr
new file mode 100644
index 0000000..38b4cc8
--- /dev/null
+++ b/usr.sbin/bsdconfig/startup/share/rcconf.subr
@@ -0,0 +1,500 @@
+if [ ! "$_STARTUP_RCCONF_SUBR" ]; then _STARTUP_RCCONF_SUBR=1
+#
+# Copyright (c) 2006-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." startup/rcconf.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="140.startup"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+############################################################ GLOBALS
+
+#
+# Initialize in-memory cache variables
+#
+STARTUP_RCCONF_MAP=
+_STARTUP_RCCONF_MAP=
+
+#
+# Define what a variable looks like
+#
+STARTUP_RCCONF_REGEX="^[[:alpha:]_][[:alnum:]_]*="
+
+#
+# Default path to on-disk cache file(s)
+#
+STARTUP_RCCONF_MAP_CACHEFILE="/var/run/bsdconfig/startup_rcconf_map.cache"
+
+############################################################ FUNCTIONS
+
+# f_startup_rcconf_list
+#
+# Produce a list of non-default configuration variables configured in the
+# rc.conf(5) collection of files.
+#
+f_startup_rcconf_list()
+{
+ ( # Operate within a sub-shell to protect the parent environment
+ . "$RC_DEFAULTS" > /dev/null
+ f_clean_env --except PATH STARTUP_RCCONF_REGEX rc_conf_files
+ source_rc_confs > /dev/null
+ export _rc_conf_files_file="$( f_sysrc_find rc_conf_files )"
+ export RC_DEFAULTS
+ set | awk -F= "
+ function test_print(var)
+ {
+ if ( var == \"OPTIND\" ) return
+ if ( var == \"PATH\" ) return
+ if ( var == \"RC_DEFAULTS\" ) return
+ if ( var == \"STARTUP_RCCONF_REGEX\" ) return
+ if ( var == \"_rc_conf_files_file\" ) return
+ if ( var == \"rc_conf_files\" )
+ {
+ if ( ENVIRON[\"_rc_conf_files_file\"] == \
+ ENVIRON[\"RC_DEFAULTS\"] ) return
+ }
+ print var
+ }
+ /$STARTUP_RCCONF_REGEX/ { test_print(\$1) }"
+ )
+}
+
+# f_startup_rcconf_map [$var_to_set]
+#
+# Produce a map (beit from in-memory cache or on-disk cache) of rc.conf(5)
+# variables and their descriptions. The map returned has the following format:
+#
+# var description
+#
+# With each as follows:
+#
+# var the rc.conf(5) variable
+# description description of the variable
+#
+# If $var_to_set is missing or NULL, the map is printed to standard output for
+# capturing in a sub-shell (which is less-recommended because of performance
+# degredation; for example, when called in a loop).
+#
+f_startup_rcconf_map()
+{
+ local __funcname=f_startup_rcconf_map
+ local __var_to_set="$1"
+
+ # If the in-memory cached value is available, return it immediately
+ if [ "$_STARTUP_RCCONF_MAP" ]; then
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" "$STARTUP_RCCONF_MAP"
+ else
+ echo "$STARTUP_RCCONF_MAP"
+ fi
+ return $SUCCESS
+ fi
+
+ #
+ # Create the in-memory cache (potentially from validated on-disk cache)
+ #
+
+ #
+ # Calculate digest used to determine if the on-disk global persistant
+ # cache file (containing this digest on the first line) is valid and
+ # can be used to quickly populate the cache value for immediate return.
+ #
+ local __rc_defaults_digest
+ __rc_defaults_digest=$( exec 2> /dev/null; md5 < "$RC_DEFAULTS" )
+
+ #
+ # Check to see if the global persistant cache file exists
+ #
+ if [ -f "$STARTUP_RCCONF_MAP_CACHEFILE" ]; then
+ #
+ # Attempt to populate the in-memory cache with the (soon to be)
+ # validated on-disk cache. If validation fails, fall-back to
+ # the current value and provide error exit status.
+ #
+ STARTUP_RCCONF_MAP=$(
+ ( # Get digest as the first word on first line
+ read digest rest_ignored
+
+ #
+ # If the stored digest matches the calculated-
+ # one populate the in-memory cache from the on-
+ # disk cache and provide success exit status.
+ #
+ if [ "$digest" = "$__rc_defaults_digest" ]
+ then
+ cat
+ exit $SUCCESS
+ else
+ # Otherwise, return the current value
+ echo "$STARTUP_RCCONF_MAP"
+ exit $FAILURE
+ fi
+ ) < "$STARTUP_RCCONF_MAP_CACHEFILE"
+ )
+ local __retval=$?
+ export STARTUP_RCCONF_MAP # Make children faster (export cache)
+ if [ $__retval -eq $SUCCESS ]; then
+ export _STARTUP_RCCONF_MAP=1
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" "$STARTUP_RCCONF_MAP"
+ else
+ echo "$STARTUP_RCCONF_MAP"
+ fi
+ return $SUCCESS
+ fi
+ # Otherwise, fall-thru to create in-memory cache from scratch
+ fi
+
+ #
+ # If we reach this point, we need to generate the data from scratch
+ # (and after we do, we'll attempt to create the global persistant
+ # cache file to speed up future executions).
+ #
+
+ STARTUP_RCCONF_MAP=$(
+ f_clean_env --except \
+ PATH \
+ RC_DEFAULTS \
+ STARTUP_RCCONF_REGEX \
+ f_sysrc_desc_awk
+ . "$RC_DEFAULTS"
+
+ # Unset variables we don't want reported
+ unset source_rc_confs_defined
+
+ for var in $( set | awk -F= "
+ function test_print(var)
+ {
+ if ( var == \"OPTIND\" ) return
+ if ( var == \"PATH\" ) return
+ if ( var == \"RC_DEFAULTS\" ) return
+ if ( var == \"STARTUP_RCCONF_REGEX\" ) return
+ if ( var == \"f_sysrc_desc_awk\" ) return
+ print var
+ }
+ /$STARTUP_RCCONF_REGEX/ { test_print(\$1) }
+ " ); do
+ echo $var "$( f_sysrc_desc $var )"
+ done
+ )
+ export STARTUP_RCCONF_MAP
+ export _STARTUP_RCCONF_MAP=1
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" "$STARTUP_RCCONF_MAP"
+ else
+ echo "$STARTUP_RCCONF_MAP"
+ fi
+
+ #
+ # Attempt to create the persistant global cache
+ #
+
+ # Create a new temporary file to write to
+ local __tmpfile
+ f_eval_catch -dk __tmpfile $__funcname mktemp \
+ 'mktemp -t "%s"' "$pgm" || return $FAILURE
+
+ # Write the temporary file contents
+ echo "$__rc_defaults_digest" > "$__tmpfile"
+ echo "$STARTUP_RCCONF_MAP" >> "$__tmpfile"
+
+ # Finally, move the temporary file into place
+ case "$STARTUP_RCCONF_MAP_CACHEFILE" in
+ */*) f_eval_catch -d $__funcname mkdir \
+ 'mkdir -p "%s"' "${STARTUP_RCCONF_MAP_CACHEFILE%/*}"
+ esac
+ f_eval_catch -d $__funcname mv \
+ 'mv "%s" "%s"' "$__tmpfile" "$STARTUP_RCCONF_MAP_CACHEFILE"
+}
+
+# f_startup_rcconf_map_expand $var_to_get
+#
+# Expands the map ($var_to_get) into the shell environment namespace by
+# creating _${var}_desc variables containing the description of each variable
+# encountered.
+#
+# NOTE: Variables are exported for later-required awk(1) ENVIRON visibility.
+#
+f_startup_rcconf_map_expand()
+{
+ local var_to_get="$1"
+ eval "$( debug= f_getvar "$var_to_get" | awk '
+ BEGIN {
+ rword = "^[[:space:]]*[^[:space:]]*[[:space:]]*"
+ }
+ {
+ var = $1
+ desc = $0
+ sub(rword, "", desc)
+ gsub(/'\''/, "'\''\\'\'\''", desc)
+ printf "_%s_desc='\''%s'\''\n", var, desc
+ printf "export _%s_desc\n", var
+ }' )"
+}
+
+# f_dialog_input_view_details
+#
+# Display a menu for selecting which details are to be displayed. The following
+# variables are tracked/modified by the menu/user's selection:
+#
+# SHOW_DESC Show or hide descriptions
+#
+# Mutually exclusive options:
+#
+# SHOW_VALUE Show the value (default; override only)
+# SHOW_DEFAULT_VALUE Show both value and default
+# SHOW_CONFIGURED Show rc.conf(5) file variable is configured in
+#
+# Each variable is treated as a boolean (NULL for false, non-NULL for true).
+#
+# Variables are exported for later-required awk(1) ENVIRON visibility. Returns
+# success unless the user chose `Cancel' or pressed Escape.
+#
+f_dialog_input_view_details()
+{
+ local prompt=
+ local menu_list # calculated below
+ local defaultitem= # calculated below
+ local hline="$hline_arrows_tab_enter"
+
+ # Calculate marks for checkboxes and radio buttons
+ local md=" "
+ if [ "$SHOW_DESC" ]; then
+ md="X"
+ fi
+ local m1=" " m2=" " m3=" "
+ if [ "$SHOW_VALUE" ]; then
+ m1="*"
+ defaultitem="1 ($m1) $msg_show_value"
+ elif [ "$SHOW_DEFAULT_VALUE" ]; then
+ m2="*"
+ defaultitem="2 ($m2) $msg_show_default_value"
+ elif [ "$SHOW_CONFIGURED" ]; then
+ m3="*"
+ defaultitem="3 ($m3) $msg_show_configured"
+ fi
+
+ # Create the menu list with the above-calculated marks
+ menu_list="
+ 'R $msg_reset' '$msg_reset_desc'
+ 'D [$md] $msg_desc' '$msg_desc_desc'
+ '1 ($m1) $msg_show_value' '$msg_show_value_desc'
+ '2 ($m2) $msg_show_default_value' '$msg_show_default_value_desc'
+ '3 ($m3) $msg_show_configured' '$msg_show_configured_desc'
+ " # END-QUOTE
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ f_dialog_title "$msg_choose_view_details"
+
+ local mtag
+ mtag=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize mtag
+
+ f_dialog_title_restore
+
+ [ $retval -eq $DIALOG_OK ] || return $DIALOG_CANCEL
+
+ case "$mtag" in
+ "R $msg_reset")
+ SHOW_VALUE=1
+ SHOW_DESC=1
+ SHOW_DEFAULT_VALUE=
+ SHOW_CONFIGURED=
+ ;;
+ "D [X] $msg_desc") SHOW_DESC= ;;
+ "D [ ] $msg_desc") SHOW_DESC=1 ;;
+ "1 ("?") $msg_show_value")
+ SHOW_VALUE=1
+ SHOW_DEFAULT_VALUE=
+ SHOW_CONFIGURED=
+ ;;
+ "2 ("?") $msg_show_default_value")
+ SHOW_VALUE=
+ SHOW_DEFAULT_VALUE=1
+ SHOW_CONFIGURED=
+ ;;
+ "3 ("?") $msg_show_configured")
+ SHOW_VALUE=
+ SHOW_DEFAULT_VALUE=
+ SHOW_CONFIGURED=1
+ ;;
+ esac
+}
+
+# f_dialog_input_rclist [$default]
+#
+# Presents a menu of rc.conf(5) defaults (with, or without descriptions). This
+# function should be treated like a call to dialog(1) (the exit status should
+# be captured and f_dialog_menutag_fetch() should be used to get the user's
+# response). Optionally if present and non-null, highlight $default rcvar.
+#
+f_dialog_input_rclist()
+{
+ local prompt="$msg_please_select_an_rcconf_directive"
+ local menu_list="
+ 'X $msg_exit' '' ${SHOW_DESC:+'$msg_exit_this_menu'}
+ " # END-QUOTE
+ local defaultitem="$1"
+ local hline="$hline_arrows_tab_enter"
+
+ if [ ! "$_RCCONF_MAP" ]; then
+ # Generate RCCONF_MAP of `var desc ...' per-line
+ f_dialog_info "$msg_creating_rcconf_map"
+ RCCONF_MAP=$( f_startup_rcconf_map )
+ export RCCONF_MAP
+ # Generate _${var}_desc variables from $RCCONF_MAP
+ f_startup_rcconf_map_expand
+ export _RCCONF_MAP=1
+ fi
+
+ menu_list="$menu_list $(
+ export SHOW_DESC
+ echo "$RCCONF_MAP" | awk '
+ BEGIN {
+ prefix = ""
+ rword = "^[[:space:]]*[^[:space:]]*[[:space:]]*"
+ }
+ {
+ cur_prefix = tolower(substr($1, 1, 1))
+ printf "'\''"
+ if ( prefix != cur_prefix )
+ prefix = cur_prefix
+ else
+ printf " "
+ rcvar = $1
+ printf "%s'\'' '\'\''", rcvar
+ if ( ENVIRON["SHOW_DESC"] ) {
+ desc = $0
+ sub(rword, "", desc)
+ gsub(/'\''/, "'\''\\'\'\''", desc)
+ printf " '\''%s'\''", desc
+ }
+ printf "\n"
+ }'
+ )"
+
+ set -f # set noglob because descriptions in the $menu_list may contain
+ # `*' and get expanded by dialog(1) (doesn't affect Xdialog(1)).
+ # This prevents dialog(1) from expanding wildcards in help line.
+
+ local height width rows
+ eval f_dialog_menu${SHOW_DESC:+_with_help}_size \
+ height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --default-item \"\$defaultitem\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ ${SHOW_DESC:+--item-help} \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_menutag_store -s "$menu_choice"
+ return $retval
+}
+
+# f_dialog_input_rcvar [$init]
+#
+# Allows the user to enter the name for a new rc.conf(5) variable. If the user
+# does not cancel or press ESC, the $rcvar variable will hold the newly-
+# configured value upon return.
+#
+f_dialog_input_rcvar()
+{
+ #
+ # Loop until the user provides taint-free/valid input
+ #
+ local _input="$1"
+ while :; do
+
+ # Return if user either pressed ESC or chosen Cancel/No
+ f_dialog_input _input "$msg_please_enter_rcvar_name" \
+ "$_input" "$hline_alnum_tab_enter" || return $?
+
+ # Check for invalid entry (1of2)
+ if ! echo "$_input" | grep -q "^[[:alpha:]_]"; then
+ f_show_msg "$msg_rcvar_must_start_with"
+ continue
+ fi
+
+ # Check for invalid entry (2of2)
+ if ! echo "$_input" | grep -q "^[[:alpha:]_][[:alnum:]_]*$"
+ then
+ f_show_msg "$msg_rcvar_contains_invalid_chars"
+ continue
+ fi
+
+ rcvar="$_input"
+ break
+ done
+
+ f_dprintf "f_dialog_input_rcvar: rcvar->[%s]" "$rcvar"
+
+ return $DIALOG_OK
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." startup/rcconf.subr
+
+fi # ! $_STARTUP_RCCONF_SUBR
diff --git a/usr.sbin/bsdconfig/startup/share/rcedit.subr b/usr.sbin/bsdconfig/startup/share/rcedit.subr
new file mode 100644
index 0000000..1adca47
--- /dev/null
+++ b/usr.sbin/bsdconfig/startup/share/rcedit.subr
@@ -0,0 +1,90 @@
+if [ ! "$_STARTUP_RCEDIT_SUBR" ]; then _STARTUP_RCEDIT_SUBR=1
+#
+# Copyright (c) 2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." startup/rcedit.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/strings.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="140.startup"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+############################################################ FUNCTIONS
+
+# f_dialog_rcedit $var [[--] $init ...]
+#
+# Allow the user to enter a new value for a given rc.conf(5) variable. If the
+# user does not cancel or press ESC, the variable will be saved without
+# confirmation.
+#
+# If the second argument is non-NULL, it will be processed as the initial text
+# to be displayed, overriding the default behavior to display the currently
+# configured value as the initial text.
+#
+# If instead the second argument is "--", then the third argument (NULL or
+# otherwise) will be treated as the initial text.
+#
+f_dialog_rcedit()
+{
+ local funcname=f_dialog_rcedit
+ local msg var="$1" _input
+
+ f_sprintf msg "$msg_please_enter_a_new_value" \
+ "$var" "$( f_sysrc_get_default "$var" )"
+
+ shift 1 # var
+ if [ "$1" ]; then
+ [ "$1" = "--" ] && shift 1 # --
+ _input="$1"
+ else
+ _input=$( f_sysrc_get "$var" )
+ fi
+
+ # Return if user has either pressed ESC or chosen Cancel/No
+ f_dialog_input _input "$msg" "$_input" \
+ "$hline_alnum_punc_tab_enter" || return $?
+
+ # Return if the value has not changed from current
+ local cur_val="$( f_sysrc_get "$var" )"
+ [ "$_input" = "$cur_val" ] && return $DIALOG_OK
+
+ f_dprintf "%s: [%s]->[%s]" "$var" "$cur_val" "$_input"
+
+ f_eval_catch $funcname f_sysrc_set \
+ 'f_sysrc_set "%s" "%s"' "$var" "$_input"
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." startup/rcedit.subr
+
+fi # ! $_STARTUP_RCEDIT_SUBR
diff --git a/usr.sbin/bsdconfig/startup/share/rcvar.subr b/usr.sbin/bsdconfig/startup/share/rcvar.subr
new file mode 100644
index 0000000..c5a7885
--- /dev/null
+++ b/usr.sbin/bsdconfig/startup/share/rcvar.subr
@@ -0,0 +1,236 @@
+if [ ! "$_STARTUP_RCVAR_SUBR" ]; then _STARTUP_RCVAR_SUBR=1
+#
+# Copyright (c) 2006-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." startup/rcvar.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+
+############################################################ CONFIGURATION
+
+#
+# Default path to the `/etc/rc.d' directory where service(8) scripts are stored
+#
+: ${ETC_RC_D:=/etc/rc.d}
+
+#
+# Default path to `/etc/rc.subr' (for find_local_scripts_new())
+#
+: ${ETC_RC_SUBR:=/etc/rc.subr}
+
+############################################################ GLOBALS
+
+#
+# Initialize in-memory cache variables
+#
+STARTUP_RCVAR_MAP=
+_STARTUP_RCVAR_MAP=
+
+#
+# Define what an rcvar looks like
+#
+STARTUP_RCVAR_REGEX='[[:alpha:]_][[:alnum:]_]*="([Yy][Ee][Ss]|[Nn][Oo])"'
+
+#
+# Default path to on-disk cache file(s)
+#
+STARTUP_RCVAR_MAP_CACHEFILE="/var/run/bsdconfig/startup_rcvar_map.cache"
+
+############################################################ FUNCTIONS
+
+# f_startup_rcvar_map [$var_to_set]
+#
+# Produce a map (beit from in-memory cache or on-disk cache) of rc.d scripts
+# and their associated rcvar's. The map returned has the following format:
+#
+# rcvar default script description
+#
+# With each as follows:
+#
+# rcvar the variable used to enable this rc.d script
+# default default value for this variable
+# script the rc.d script in-question
+# description description of the variable from rc.conf(5) defaults
+#
+# If $var_to_set is missing or NULL, the map is printed to standard output for
+# capturing in a sub-shell (which is less-recommended because of performance
+# degredation; for example, when called in a loop).
+#
+f_startup_rcvar_map()
+{
+ local __funcname=f_startup_rcvar_map
+ local __var_to_set="$1"
+
+ # If the in-memory cached value is available, return it immediately
+ if [ "$_STARTUP_RCVAR_MAP" ]; then
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" "$STARTUP_RCVAR_MAP"
+ else
+ echo "$STARTUP_RCVAR_MAP"
+ fi
+ return $SUCCESS
+ fi
+
+ #
+ # create the in-memory cache (potentially from validated on-disk cache)
+ #
+
+ # Get a list of /etc/rc.d scripts ...
+ local __file __rc_script_list=
+ for __file in "$ETC_RC_D"/*; do
+ [ -f "$__file" ] || continue
+ [ -x "$__file" ] || continue
+ __rc_script_list="$__rc_script_list $__file"
+ done
+ # ... and /usr/local/etc/rc.d scripts
+ __rc_script_list="$__rc_script_list $(
+ local_startup=$( f_sysrc_get local_startup )
+ f_include "$ETC_RC_SUBR"
+ find_local_scripts_new
+ echo $local_rc
+ )"
+ __rc_script_list="${__rc_script_list# }" # Trim leading space
+
+ #
+ # Calculate a digest given the checksums of all dependencies (scripts
+ # and the defaults file). This digest will be used to determine if an
+ # on-disk global persistant cache file (containg this digest on the
+ # first line) is valid and can be used to quickly populate the cache
+ # value for immediate return.
+ #
+ local __rc_script_list_digest
+ __rc_script_list_digest=$( cd "$ETC_RC_D" 2> /dev/null &&
+ cksum "$RC_DEFAULTS" $__rc_script_list 2> /dev/null | md5 )
+
+ #
+ # Check to see if the global persistant cache file exists
+ #
+ if [ -f "$STARTUP_RCVAR_MAP_CACHEFILE" ]; then
+ #
+ # Attempt to populate the in-memory cache with the (soon to be)
+ # validated on-disk cache. If validation fails, fall-back to
+ # the current value and return error.
+ #
+ STARTUP_RCVAR_MAP=$(
+ ( # Get digest as first word on first line
+ read digest rest_ignored
+
+ #
+ # If the stored digest matches the calculated-
+ # one populate the in-memory cache from the on-
+ # disk cache and return success.
+ #
+ if [ "$digest" = "$__rc_script_list_digest" ]
+ then
+ cat
+ exit $SUCCESS
+ else
+ # Otherwise, return the current value
+ echo "$STARTUP_RCVAR_MAP"
+ exit $FAILURE
+ fi
+ ) < "$STARTUP_RCVAR_MAP_CACHEFILE"
+ )
+ local __retval=$?
+ export STARTUP_RCVAR_MAP # Make children faster (export cache)
+ if [ $__retval -eq $SUCCESS ]; then
+ export _STARTUP_RCVAR_MAP=1
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" "$STARTUP_RCVAR_MAP"
+ else
+ echo "$STARTUP_RCVAR_MAP"
+ fi
+ return $SUCCESS
+ fi
+ # Otherwise, fall-thru to create in-memory cache from scratch
+ fi
+
+ #
+ # If we reach this point, we need to generate the data from scratch
+ # (and after we do, we'll attempt to create the global persistant
+ # cache file to speed up future executions).
+ #
+
+ STARTUP_RCVAR_MAP=$(
+ for script in $__rc_script_list; do
+ rcvar_list=$( $script rcvar 2> /dev/null | awk -F= \
+ -v script="$script" '
+ /^'"$STARTUP_RCVAR_REGEX"'/ {
+ if ( $2 ~ /^"[Yy][Ee][Ss]"$/ )
+ print $1 ",YES"
+ else
+ print $1 ",NO"
+ }' )
+ for entry in $rcvar_list; do
+ rcvar="${entry%%,*}"
+ rcvar_default=$( f_sysrc_get_default "$rcvar" )
+ [ "$rcvar_default" ] ||
+ rcvar_default="${entry#*,}"
+ rcvar_desc=$( f_sysrc_desc "$rcvar" )
+ echo $rcvar ${rcvar_default:-NO} \
+ $script "$rcvar_desc"
+ done
+ done | sort -u
+ )
+ export STARTUP_RCVAR_MAP
+ export _STARTUP_RCVAR_MAP=1
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" "$STARTUP_RCVAR_MAP"
+ else
+ echo "$STARTUP_RCVAR_MAP"
+ fi
+
+ #
+ # Attempt to create/update the persistant global cache
+ #
+
+ # Create a new temporary file to write to
+ local __tmpfile
+ f_eval_catch -dk __tmpfile $__funcname mktemp \
+ 'mktemp -t "%s"' "$__tmpfile" || return $FAILURE
+
+ # Write the temporary file contents
+ echo "$__rc_script_list_digest" > "$__tmpfile"
+ echo "$STARTUP_RCVAR_MAP" >> "$__tmpfile"
+
+ # Finally, move the temporary file into place
+ case "$STARTUP_RCVAR_MAP_CACHEFILE" in
+ */*) f_eval_catch -d $__funcname mkdir \
+ 'mkdir -p "%s"' "${STARTUP_RCVAR_MAP_CACHEFILE%/*}"
+ esac
+ f_eval_catch -d $__funcname mv \
+ 'mv "%s" "%s"' "$__tmpfile" "$STARTUP_RCVAR_MAP_CACHEFILE"
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." startup/rcvar.subr
+
+fi # ! $_STARTUP_RCVAR_SUBR
diff --git a/usr.sbin/bsdconfig/startup/startup b/usr.sbin/bsdconfig/startup/startup
new file mode 100755
index 0000000..17755d1
--- /dev/null
+++ b/usr.sbin/bsdconfig/startup/startup
@@ -0,0 +1,140 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="140.startup"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ FUNCTIONS
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local prompt=
+ local menu_list="
+ 'X' '$msg_exit'
+ '1' '$msg_toggle_startup_services'
+ '2' '$msg_view_edit_startup_configuration'
+ '3' '$msg_miscellaneous_startup_services'
+ " # END-QUOTE
+ local defaultitem= # Calculated below
+ local hline="$hline_arrows_tab_enter"
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ # Obtain default-item from previously stored selection
+ f_dialog_default_fetch defaultitem
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize menu_choice
+ f_dialog_menutag_store "$menu_choice"
+ f_dialog_default_store "$menu_choice"
+ return $retval
+}
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_startup"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Launch application main menu
+#
+while :; do
+ dialog_menu_main || f_die
+ f_dialog_menutag_fetch mtag
+
+ command=
+ case "$mtag" in
+ X) break ;;
+ 1) command=rcvar ;; # Toggle Startup Services
+ 2) command=rcconf ;; # View/Edit Startup Configuration
+ 3) command=misc ;; # Miscellaneous Startup Services
+ esac
+
+ if [ "$command" ]; then
+ $BSDCFG_LIBE/$APP_DIR/$command ${USE_XDIALOG:+-X}
+ else
+ f_die 1 "$msg_unknown_startup_menu_selection"
+ fi
+done
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/timezone/INDEX b/usr.sbin/bsdconfig/timezone/INDEX
new file mode 100644
index 0000000..67fc50b
--- /dev/null
+++ b/usr.sbin/bsdconfig/timezone/INDEX
@@ -0,0 +1,57 @@
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+#
+# Title that will be shown in the bsdconfig menu.
+#
+menu_title="Timezone"
+
+#
+# A short descriptive line shown at the bottom of the bsdconfig menu. keep it
+# short because any line longer than the terminal width will be truncated.
+#
+menu_help="Set up Time Zone"
+
+#
+# Two-part variable that defines an action to take when `keyword' is passed on
+# a bsdconfig command line. Variable takes the form "keyword|command" and
+# multiple occurrences of the variable (with different `keyword's, or different
+# `keyword's AND `command's) are allowed. If `command' begins with a '/' then
+# the full path to the program is needed. If `command' begins with anything
+# else it is a path relative to the directory this INDEX file is in. `keyword'
+# can be i18n'ed but `command' is the name of a script.
+#
+menu_selection="timezone|timezone"
+
+#
+# ------------ Items below this line do NOT need i18n translation ------------
+#
+# Name of the program to be run when this menu choice is selected. If it begins
+# with a '/' then the full path to the program is needed. If it begins with
+# anything else it is a path relative to the directory this INDEX file is in.
+#
+menu_program="timezone"
diff --git a/usr.sbin/bsdconfig/timezone/Makefile b/usr.sbin/bsdconfig/timezone/Makefile
new file mode 100644
index 0000000..1569e7e
--- /dev/null
+++ b/usr.sbin/bsdconfig/timezone/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+SUBDIR= include share
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/090.timezone
+FILES= INDEX USAGE
+
+SCRIPTSDIR= ${FILESDIR}
+SCRIPTS= timezone
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/timezone/Makefile.depend b/usr.sbin/bsdconfig/timezone/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/timezone/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/timezone/USAGE b/usr.sbin/bsdconfig/timezone/USAGE
new file mode 100644
index 0000000..6dbe96d
--- /dev/null
+++ b/usr.sbin/bsdconfig/timezone/USAGE
@@ -0,0 +1,46 @@
+# Copyright (c) 2011-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+Usage: bsdconfig @PROGRAM_NAME@ [-ehnrSsvX] [-C chroot_dir] [zinfo_file | zinfo_name]
+OPTIONS:
+ -d Provide lots of debugging info on standard-out when running.
+ -D file Send debugging info to file. If file begins with a plus-sign
+ debug info is sent to both standard-out and file (minus the
+ leading plus).
+ -e Only return success on exit if user selects a timezone AND
+ the selected timezone was successfully installed. By default
+ (without this flag), success is always returned unless an
+ error has occurred.
+ -h Print this usage statement and exit.
+ -n Do not create or copy files.
+ -r Reinstall the zoneinfo file installed last time. The name is
+ obtained from /var/db/zoneinfo.
+ -S Secure X11 mode (implies `-X'). As root, always prompt-for
+ and validate sudo(8) username/password before starting.
+ -s Skip the initial question about adjusting the clock if
+ not set to UTC.
+ -v Verbose. Enable extra output when installing the zone file.
+ -X Enable the use of Xdialog(1) instead of dialog(1).
diff --git a/usr.sbin/bsdconfig/timezone/include/Makefile b/usr.sbin/bsdconfig/timezone/include/Makefile
new file mode 100644
index 0000000..8fc6dfc
--- /dev/null
+++ b/usr.sbin/bsdconfig/timezone/include/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/090.timezone/include
+FILES= messages.subr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/timezone/include/Makefile.depend b/usr.sbin/bsdconfig/timezone/include/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/timezone/include/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/timezone/include/messages.subr b/usr.sbin/bsdconfig/timezone/include/messages.subr
new file mode 100644
index 0000000..6a7f045
--- /dev/null
+++ b/usr.sbin/bsdconfig/timezone/include/messages.subr
@@ -0,0 +1,78 @@
+# Copyright (c) 2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+continent_africa_title="Africa"
+continent_america_title="America -- North and South"
+continent_antarctica_title="Antarctica"
+continent_arctic_title="Arctic Ocean"
+continent_asia_title="Asia"
+continent_atlantic_title="Atlantic Ocean"
+continent_australia_title="Australia"
+continent_europe_title="Europe"
+continent_indian_title="Indian Ocean"
+continent_pacific_title="Pacific Ocean"
+continent_utc_title="UTC"
+msg_cancel="Cancel"
+msg_cannot_open_for_reading="Cannot open %s for reading. Does it exist?"
+msg_confirmation="Confirmation"
+msg_conflicting_zone_definition="%s:%d: conflicting zone definition"
+msg_copied_timezone_file="Copied timezone file from %s to %s"
+msg_copying_file="Copying %s to %s"
+msg_country_code_invalid="%s:%d: country code \`%s' invalid"
+msg_country_code_multiply_defined="%s:%d: country code \`%s' multiply defined: %s"
+msg_country_code_unknown="%s:%d: country code \`%s' unknown"
+msg_country_time_zones="%s Time Zones"
+msg_country_title="Countries in %s"
+msg_created_symlink="Created symbolic link from %s to %s"
+msg_creating_symlink="Creating symbolic link %s to %s"
+msg_default_zone_provided="Default timezone provided"
+msg_done="Done"
+msg_error="Error"
+msg_error_reading="Error reading %s."
+msg_info="Info"
+msg_invalid_code="%s:%d: invalid code \`%s'"
+msg_invalid_country_code="%s:%d: invalid country code \`%s'"
+msg_invalid_format="%s:%d: invalid format"
+msg_invalid_region="%s:%d: invalid region \`%s'"
+msg_invalid_zone_name="%s:%d: invalid zone name \`%s'"
+msg_is_machine_clock_utc="Is the machine's CMOS clock set to UTC? If it is set to local time,\nor you don't know, please choose NO here!"
+msg_island_and_group_title="Islands and groups in the %s"
+msg_look_reasonable="Does the abbreviation \`%s' look reasonable?"
+msg_no="No"
+msg_ok="OK"
+msg_removed_file="Removed %s"
+msg_removing_file="Removing %s"
+msg_select_country="Select a country"
+msg_select_island_or_group="Select an island or group"
+msg_select_local_or_utc="Select local or UTC (Greenwhich Mean Time) clock"
+msg_select_region="Select a region"
+msg_select_zone="Select a zone which observes the same time as your locality."
+msg_time_zone="Time Zone"
+msg_unable_to_determine_name_from_db="Unable to determine earlier installed zoneinfo name. Check %s"
+msg_use_default_zone="Use the default \`%s' zone?"
+msg_yes="Yes"
+msg_zone_multiply_defined="%s:%d: zone multiply defined"
+msg_zone_must_have_description="%s:%d: zone must have description"
diff --git a/usr.sbin/bsdconfig/timezone/share/Makefile b/usr.sbin/bsdconfig/timezone/share/Makefile
new file mode 100644
index 0000000..166fb10
--- /dev/null
+++ b/usr.sbin/bsdconfig/timezone/share/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+FILESDIR= ${SHAREDIR}/bsdconfig/timezone
+FILES= continents.subr countries.subr iso3166.subr menus.subr \
+ zones.subr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/timezone/share/Makefile.depend b/usr.sbin/bsdconfig/timezone/share/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/timezone/share/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/timezone/share/continents.subr b/usr.sbin/bsdconfig/timezone/share/continents.subr
new file mode 100644
index 0000000..764f33f
--- /dev/null
+++ b/usr.sbin/bsdconfig/timezone/share/continents.subr
@@ -0,0 +1,166 @@
+if [ ! "$_TIMEZONE_CONTINENTS_SUBR" ]; then _TIMEZONE_CONTINENTS_SUBR=1
+#
+# Copyright (c) 2011-2015 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." timezone/continents.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="090.timezone"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+############################################################ CONFIGURATION
+
+#
+# List of worldly continents/oceans (export'ed for awk(1) ENVIRON visibility)
+#
+export CONTINENTS="
+ africa
+ america
+ antarctica
+ arctic
+ asia
+ atlantic
+ australia
+ europe
+ indian
+ pacific
+ utc
+"
+
+#
+# Directory name of each continent/ocean (in _PATH_ZONEINFO)
+#
+export continent_africa_name="Africa"
+export continent_america_name="America"
+export continent_antarctica_name="Antarctica"
+export continent_arctic_name="Arctic"
+export continent_asia_name="Asia"
+export continent_atlantic_name="Atlantic"
+export continent_australia_name="Australia"
+export continent_europe_name="Europe"
+export continent_indian_name="Indian"
+export continent_pacific_name="Pacific"
+export continent_utc_name="UTC"
+
+#
+# Export i18n menu texts of continents/oceans for awk(1) ENVIRON visibility
+# NOTE: These are defined in messages.subr included above.
+#
+export continent_africa_title
+export continent_america_title
+export continent_antarctica_title
+export continent_arctic_title
+export continent_asia_title
+export continent_atlantic_title
+export continent_australia_title
+export continent_europe_title
+export continent_indian_title
+export continent_pacific_title
+export continent_utc_title
+
+############################################################ FUNCTIONS
+
+# f_continent $cont $property [$var_to_set]
+#
+# Returns a single property of a given continent. Available properties are:
+#
+# name Directory name of continent/ocean as it appears in
+# _PATH_ZONEINFO.
+# title Menu text of this continent/ocean to be displayed in the
+# continent-selection menu.
+# nitems Number of submenu items associated with this
+# continent/ocean.
+# tlc_N 2-character country code of the Nth submenu item associated
+# with this continent displayed in the country-selection menu
+# (which appears after continent selection).
+# menu_list Menu-list of regions for this continent.
+#
+# If $var_to_set is missing or NULL, the value of $var_to_get is printed to
+# standard output for capturing in a sub-shell (which is less-recommended
+# because of performance degredation; for example, when called in a loop).
+#
+f_continent()
+{
+ f_getvar "continent_${1}_$2" $3
+}
+
+# f_find_continent $title [$var_to_set]
+#
+# Returns continent identifier given continent title.
+#
+# If $var_to_set is missing or NULL, the value of $var_to_get is printed to
+# standard output for capturing in a sub-shell (which is less-recommended
+# because of performance degredation; for example, when called in a loop).
+#
+f_find_continent()
+{
+ local __cont __title
+ for __cont in $CONTINENTS; do
+ f_continent $__cont title __title
+ if [ "$1" = "$__title" ]; then
+ if [ "$2" ]; then
+ setvar "$2" $__cont
+ else
+ echo "$__cont"
+ fi
+ return $SUCCESS
+ fi
+ done
+ return $FAILURE
+}
+
+# f_OCEANP $cont [$var_to_set]
+#
+# Returns "1" if the first argument is an ocean, otherwise NULL.
+#
+# If $var_to_set is missing or NULL, the value of $var_to_get is printed to
+# standard output for capturing in a sub-shell (which is less-recommended
+# because of performance degredation; for example, when called in a loop).
+#
+f_OCEANP()
+{
+ case "$1" in
+ arctic|atlantic|indian|pacific)
+ if [ "$2" ]; then
+ setvar "$2" 1
+ else
+ echo 1
+ fi
+ ;;
+ *)
+ [ "$2" ] && setvar "$2" ""
+ esac
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." timezone/continents.subr
+
+fi # ! $_TIMEZONE_CONTINENTS_SUBR
diff --git a/usr.sbin/bsdconfig/timezone/share/countries.subr b/usr.sbin/bsdconfig/timezone/share/countries.subr
new file mode 100644
index 0000000..8958e87
--- /dev/null
+++ b/usr.sbin/bsdconfig/timezone/share/countries.subr
@@ -0,0 +1,105 @@
+if [ ! "$_TIMEZONE_COUNTRIES_SUBR" ]; then _TIMEZONE_COUNTRIES_SUBR=1
+#
+# Copyright (c) 2011-2015 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ FUNCTIONS
+
+# f_country $code $property [$var_to_set]
+#
+# Returns a single property of a given country. Available properties are:
+#
+# name Name of the country as read from _PATH_ISO3166.
+# nzones Number of zones within the country (-1 if country has
+# only a single zone).
+# filename The filename portion of the TZ field (after the `/') as
+# read from _PATH_ZONETAB.
+# cont The principal continent in which the country lies (appears
+# before the `/' in the TZ field of _PATH_ZONETAB).
+# filename_N Like filename, but for the Nth zone when the country has
+# multiple zones (nzones > 0).
+# cont_N Like cont, but for the Nth zone when the country has
+# multiple zones (nzones > 0).
+# descr_N Like name, but for the Nth zone when the country has
+# multiple zones (nzones > 0)
+#
+# If $var_to_set is missing or NULL, the value of $var_to_get is printed to
+# standard output for capturing in a sub-shell (which is less-recommended
+# because of performance degredation; for example, when called in a loop).
+#
+f_country()
+{
+ f_getvar "country_${1}_$2" $3
+}
+
+# f_sort_countries
+#
+# Sorts alphabetically the 2-character country codes listed in $COUNTRIES based
+# on the name of each country.
+#
+# This function is a two-parter. Below is the awk(1) portion of the function,
+# afterward is the sh(1) function which utilizes the below awk script.
+#
+f_sort_countries_awk='
+function _asorti(src, dest)
+{
+ k = nitems = 0
+ for (i in src) dest[++nitems] = i
+ for (i = 1; i <= nitems; k = i++) {
+ idx = dest[i]
+ while ((k > 0) && (dest[k] > idx)) {
+ dest[k+1] = dest[k]; k--
+ }
+ dest[k+1] = idx
+ }
+ return nitems
+}
+BEGIN {
+ split(ENVIRON["COUNTRIES"], array, /[[:space:]]+/)
+ for (item in array)
+ {
+ tlc = array[item]
+ name = ENVIRON["country_" tlc "_name"]
+ countries[name] = tlc
+ }
+ n = _asorti(countries, sorted_countries)
+ for (i = 1; i <= n; i++)
+ print countries[sorted_countries[i]]
+ exit
+}
+'
+f_sort_countries()
+{
+ export COUNTRIES # for awk(1) ENVIRON[] visibility
+ COUNTRIES=$( awk "$f_sort_countries_awk" )
+ export COUNTRIES # Pedantic
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." timezone/countries.subr
+
+fi # ! $_TIMEZONE_COUNTRIES_SUBR
diff --git a/usr.sbin/bsdconfig/timezone/share/iso3166.subr b/usr.sbin/bsdconfig/timezone/share/iso3166.subr
new file mode 100644
index 0000000..205d5f5
--- /dev/null
+++ b/usr.sbin/bsdconfig/timezone/share/iso3166.subr
@@ -0,0 +1,202 @@
+if [ ! "$_TIMEZONE_ISO3166_SUBR" ]; then _TIMEZONE_ISO3166_SUBR=1
+#
+# Copyright (c) 2011-2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." timezone/iso3166.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="090.timezone"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+############################################################ CONFIGURATION
+
+#
+# Standard pathnames
+#
+_PATH_ISO3166="/usr/share/misc/iso3166"
+
+#
+# Export required i18n messages for awk(1) ENVIRON visibility
+#
+export msg_country_code_multiply_defined
+export msg_invalid_code
+export msg_invalid_format
+
+############################################################ FUNCTIONS
+
+# f_read_iso3166_table
+#
+# Read the ISO 3166 country code database in _PATH_ISO3166:
+# /usr/share/misc/iso3166 on FreeBSD
+# /usr/share/zoneinfo/iso3166.tab on Linux, Mac OS X, and Cygwin
+#
+# The format of this file on FreeBSD is:
+# two three number name
+#
+# The format of this file on Linux, Mac OS X, and Cygwin is:
+# two name
+#
+# With each of the following elements (described below) being separated by a
+# single tab character:
+#
+# two ISO 3166 2-character country code
+# three ISO 3166 3-character country code (if provided)
+# number ISO 3166 numeric country code (if provided)
+# name Human-readable country name (may contain spaces)
+#
+# Variables created by this function:
+#
+# COUNTRIES
+# A space-separated list of 2-character country codes.
+# country_CODE_name
+# The country `name' (as described above).
+#
+# where CODE is the 2-character country code.
+#
+# This function is a two-parter. Below is the awk(1) portion of the function,
+# afterward is the sh(1) function which utilizes the below awk script.
+#
+f_read_iso3166_table_awk='
+# Variables that should be defined on the invocation line:
+# -v progname="progname"
+#
+BEGIN {
+ lineno = 0
+ failed = 0
+}
+function die(fmt, argc, argv)
+{
+ printf "f_die 1 \"%%s: %s\" \"%s\"", fmt, progname
+ for (n = 1; n <= argc; n++)
+ printf " \"%s\"", argv[n]
+ print ""
+ failed++
+ exit 1
+}
+function add_country(tlc, name)
+{
+ if (country_name[tlc])
+ {
+ argv[1] = FILENAME
+ argv[2] = lineno
+ argv[3] = tlc
+ argv[4] = name
+ die(ENVIRON["msg_country_code_multiply_defined"], 4, argv)
+ }
+
+ country_name[tlc] = name
+}
+function print_country_name(tlc)
+{
+ name = country_name[tlc]
+ gsub(/"/, "\\\"", name)
+ printf "country_%s_name=\"%s\"\n", tlc, name
+ printf "export country_%s_name\n", tlc
+}
+/^#/ {
+ lineno++
+ next
+}
+!/^#/ {
+ lineno++
+
+ # Split the current record (on TAB) into an array
+ split($0, line, /\t/)
+
+ # Get the ISO3166-1 (Alpha 1) 2-letter country code
+ tlc = line[1]
+
+ #
+ # Validate the two-character country code
+ #
+ if (length(tlc) != 2)
+ {
+ argv[1] = FILENAME
+ argv[2] = lineno
+ die(ENVIRON["msg_invalid_format"], 2, argv)
+ }
+ if (!match(tlc, /^[A-Z][A-Z]$/))
+ {
+ argv[1] = FILENAME
+ argv[2] = lineno
+ argv[3] = tlc
+ die(ENVIRON["msg_invalid_code"], 3, argv)
+ }
+
+ #
+ # Calculate the substr start-position of the name
+ #
+ name_start = 0
+ n = 4
+ if (FILENAME ~ /\.tab$/)
+ n = 2
+ while (--n)
+ {
+ #
+ # Validate field-length of 2nd/3rd columns while we are here
+ #
+ if (n > 1 && length(line[n]) != 3)
+ {
+ argv[1] = FILENAME
+ argv[2] = lineno
+ die(ENVIRON["msg_invalid_format"], 2, argv)
+ }
+
+ name_start += length(line[n]) + 1
+ }
+
+ # Get the name field
+ name = substr($0, name_start + 1)
+
+ add_country(tlc, name)
+}
+END {
+ list = ""
+ for (tlc in country_name)
+ {
+ list = list (length(list) > 0 ? " " : "") tlc
+ print_country_name(tlc)
+ }
+ printf "COUNTRIES=\"%s\"\n", list
+ print "export COUNTRIES"
+}
+'
+f_read_iso3166_table()
+{
+ eval $( awk -v progname="$pgm" \
+ "$f_read_iso3166_table_awk" \
+ "$_PATH_ISO3166" )
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." timezone/iso3166.subr
+
+fi # ! $_TIMEZONE_ISO3166_SUBR
diff --git a/usr.sbin/bsdconfig/timezone/share/menus.subr b/usr.sbin/bsdconfig/timezone/share/menus.subr
new file mode 100644
index 0000000..ef3979b
--- /dev/null
+++ b/usr.sbin/bsdconfig/timezone/share/menus.subr
@@ -0,0 +1,225 @@
+if [ ! "$_TIMEZONE_MENUS_SUBR" ]; then _TIMEZONE_MENUS_SUBR=1
+#
+# Copyright (c) 2011-2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." timezone/menus.subr
+f_include $BSDCFG_SHARE/dialog.subr
+
+############################################################ GLOBALS
+
+#
+# Export special included variables required by awk(1) for `ENVIRON' visibility
+#
+export DIALOG_MENU_TAGS
+
+############################################################ FUNCTIONS
+
+# f_make_menus
+#
+# Creates the tag/item ordered-pair list environment variables for the
+# continent and country menus.
+#
+# Required variables [from continents.subr]:
+#
+# CONTINENTS
+# Space-separated list of continents.
+# continent_*_title
+# Desired menu text for the continent represented by *.
+#
+# Required variables [created by f_read_iso3166_table from iso3166.subr]:
+#
+# COUNTRIES
+# Space-separated list of 2-character country codes.
+# country_*_name :: when country_*_nzones < 0
+# Desired menu text for the country-zone represented by *, the 2-
+# character country code.
+#
+# Required variables [created by f_read_zones from zones.subr]:
+#
+# country_*_nzones
+# Number of zones for the country represented by *, the 2-
+# character country code. Should be -1 if the country has only
+# one single zone, otherwise 1 or greater to indicate how many
+# zones the country has.
+# country_*_cont :: when country_*_nzones < 0
+# Principal continent (or ocean) in which the country-zone
+# represented by *, the 2-character country code, resides.
+# country_*_cont_N :: when country_*_nzones > 0
+# Principal continent (or ocean) in which zone-N of the country
+# represented by * resides, the 2-character country code.
+# country_*_descr_N :: when country_*_nzones > 0
+# Desired submenu text for zone-N of the country represented by
+# *, the 2-character country code.
+#
+# Variables created by this function:
+#
+# continent_menu_list
+# Menu-list of continents.
+# continent_*_nitems
+# Number of items associated with the continent represented by *,
+# the continent identifier.
+# continent_*_tlc_N
+# 2-character country code of the Nth item in the continent menu
+# for the continent represented by *, the continent identifier.
+# continent_*_menu_list
+# Menu-list of countries/zones for each continent represented by
+# *, the continent identifier.
+# country_*_menu_list
+# For countries that have multiple zones, this is the submenu-
+# list of zones for said country represented by *, the 2-
+# character country code.
+#
+# This function is a two-parter. Below is the awk(1) portion of the function,
+# afterward is the sh(1) function which utilizes the below awk script.
+#
+f_make_menus_awk='
+function add_zone_n_to_country_menu(tlc, n)
+{
+ zone_title = ENVIRON["country_" tlc "_descr_" n]
+ gsub(/'\''/, "'\''\\'\'\''", zone_title)
+ country_menu_list[tlc] = country_menu_list[tlc] \
+ ( length(country_menu_list[tlc]) > 0 ? "\n" : "" ) \
+ n " '\''" zone_title "'\''"
+}
+BEGIN {
+ #
+ # First, count up all the countries in each continent/ocean.
+ # Be careful to count those countries which have multiple zones
+ # only once for each. NB: some countries are in multiple
+ # continents/oceans.
+ #
+ i = split(ENVIRON["COUNTRIES"], countries, /[[:space:]]+/)
+ for (cp = 1; cp <= i; cp++)
+ {
+ tlc = countries[cp]
+ title = ENVIRON["country_" tlc "_name"]
+ gsub(/'\''/, "'\''\\'\'\''", title)
+ nzones = ENVIRON["country_" tlc "_nzones"]
+ if (!nzones)
+ {
+ # Country has no zones
+ continue
+ }
+ else if (nzones < 0)
+ {
+ # Country has only one zone
+ cont = ENVIRON["country_" tlc "_cont"]
+ nitems = ++continent_nitems[cont]
+ continent_tlc[cont,nitems] = tlc
+ continent_title[cont,nitems] = title
+ }
+ else
+ {
+ # Country has one or more zones
+ for (n = 1; n <= nzones; n++)
+ {
+ add_zone_n_to_country_menu(tlc, n)
+ cont = ENVIRON["country_" tlc "_cont_" n]
+ for (x = 1; x < n; x++)
+ {
+ contx = ENVIRON["country_"tlc"_cont_"x]
+ if (cont == contx) break
+ }
+ if (x == n)
+ {
+ nitems = ++continent_nitems[cont]
+ continent_tlc[cont,nitems] = tlc
+ continent_title[cont,nitems] = title
+ }
+ }
+ }
+ }
+}
+END {
+ tags = ENVIRON["DIALOG_MENU_TAGS"]
+ cont_menu_list = ""
+ tagn = 0
+
+ #
+ # Assemble the menu items in the menu list for each continent/ocean.
+ #
+ i = split(ENVIRON["CONTINENTS"], array, /[[:space:]]+/)
+ for (item = 1; item <= i; item++)
+ {
+ cont = array[item]
+ if (!cont) continue
+
+ if (++tagn >= length(tags)) break
+ tag = substr(tags, tagn, 1)
+ cont_menu_list = cont_menu_list \
+ ( length(cont_menu_list) > 0 ? "\n" : "" ) \
+ "'\''" tag "'\'' '\''" \
+ ENVIRON["continent_" cont "_title"] "'\''"
+
+ nitems = continent_nitems[cont]
+ printf "continent_%s_nitems=%d\n", cont, nitems
+
+ menu_list = ""
+ for (n = 1; n <= nitems; n++)
+ {
+ printf "continent_%s_tlc_%d=%s\n",
+ cont, n, continent_tlc[cont,n]
+
+ title = continent_title[cont,n]
+ menu_list = menu_list \
+ ( length(menu_list) > 0 ? "\n" : "" ) \
+ n " '\''" title "'\''"
+ }
+
+ gsub(/"/, "\\\"", menu_list)
+ printf "continent_%s_menu_list=\"%s\"\n", cont, menu_list
+ }
+
+ gsub(/"/, "\\\"", continent_menu_list)
+ printf "continent_menu_list=\"%s\"\n", cont_menu_list
+ print "export continent_menu_list"
+
+ #
+ # Dump the submenus of countries with multiple zones
+ #
+ for (tlc in country_menu_list)
+ {
+ menu_list = country_menu_list[tlc]
+ gsub(/"/, "\\\"", menu_list)
+ printf "country_%s_menu_list=\"%s\"\n", tlc, menu_list
+ }
+}
+'
+f_make_menus()
+{
+ eval $( :| awk "$f_make_menus_awk" )
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." timezone/menus.subr
+
+fi # ! $_TIMEZONE_MENUS_SUBR
diff --git a/usr.sbin/bsdconfig/timezone/share/zones.subr b/usr.sbin/bsdconfig/timezone/share/zones.subr
new file mode 100644
index 0000000..59a9330
--- /dev/null
+++ b/usr.sbin/bsdconfig/timezone/share/zones.subr
@@ -0,0 +1,523 @@
+if [ ! "$_TIMEZONE_ZONES_SUBR" ]; then _TIMEZONE_ZONES_SUBR=1
+#
+# Copyright (c) 2011-2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." timezone/zones.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/strings.subr
+f_include $BSDCFG_SHARE/timezone/continents.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="090.timezone"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+############################################################ CONFIGURATION
+
+#
+# Standard pathnames
+#
+_PATH_ZONETAB="/usr/share/zoneinfo/zone.tab"
+_PATH_ZONEINFO="/usr/share/zoneinfo"
+_PATH_LOCALTIME="/etc/localtime"
+_PATH_DB="/var/db/zoneinfo"
+
+#
+# Export required i18n messages for awk(1) ENVIRON visibility
+#
+export msg_conflicting_zone_definition
+export msg_country_code_invalid
+export msg_country_code_unknown
+export msg_invalid_country_code
+export msg_invalid_format
+export msg_invalid_region
+export msg_invalid_zone_name
+export msg_zone_multiply_defined
+export msg_zone_must_have_description
+
+############################################################ FUNCTIONS
+
+# f_read_zones
+#
+# Read the zone descriptions database in _PATH_ZONETAB:
+# /usr/share/zoneinfo/zone.tab on all OSes
+#
+# The format of this file (on all OSes) is:
+# code coordinates TZ comments
+#
+# With each of the following elements (described below) being separated by a
+# single tab character:
+#
+# code
+# The ISO 3166 2-character country code.
+# coordinates
+# Latitude and logitude of the zone's principal location in ISO
+# 6709 sign-degrees-minutes-seconds format, either +-DDMM+-DDDMM
+# or +-DDMMSS+-DDDMMSS, first latitude (+ is north), then long-
+# itude (+ is east).
+# TZ
+# Zone name used in value of TZ environment variable.
+# comments
+# Comments; present if and only if the country has multiple rows.
+#
+# Required variables [from continents.subr]:
+#
+# CONTINENTS
+# Space-separated list of continents.
+# continent_*_name
+# Directory element in _PATH_ZONEINFO for the continent
+# represented by *.
+#
+# Required variables [created by f_read_iso3166_table from iso3166.subr]:
+#
+# country_CODE_name
+# Country name of the country represented by CODE, the 2-
+# character country code.
+#
+# Variables created by this function:
+#
+# country_CODE_nzones
+# Either set to `-1' to indicate that the 2-character country
+# code has only a single zone associated with it (and therefore
+# you should query the `country_CODE_*' environment variables),
+# or set to `0' or higher to indicate how many zones are assoc-
+# iated with the given country code. When multiple zones are
+# configured for a single code, you should instead query the
+# `country_CODE_*_N' environment variables (e.g., `echo
+# $country_AQ_descr_1' prints the description of the first
+# timezone in Antarctica).
+# country_CODE_filename
+# The ``filename'' portion of the TZ value that appears after the
+# `/' (e.g., `Hong_Kong' from `Asia/Hong_Kong' or `Isle_of_Man'
+# from `Europe/Isle_of_Man').
+# country_CODE_cont
+# The ``continent'' portion of the TZ value that appears before
+# the `/' (e.g., `Asia' from `Asia/Hong_Kong' or `Europe' from
+# `Europe/Isle_of_Man').
+# country_CODE_descr
+# The comments associated with the ISO 3166 code entry (if any).
+#
+# NOTE: CODE is the 2-character country code.
+#
+# This function is a two-parter. Below is the awk(1) portion of the function,
+# afterward is the sh(1) function which utilizes the below awk script.
+#
+f_read_zones_awk='
+# Variables that should be defined on the invocation line:
+# -v progname="progname"
+#
+BEGIN {
+ lineno = 0
+ failed = 0
+
+ #
+ # Initialize continents array/map (name => id)
+ #
+ split(ENVIRON["CONTINENTS"], array, /[[:space:]]+/)
+ for (item in array)
+ {
+ cont = array[item]
+ if (!cont) continue
+ name = ENVIRON["continent_" cont "_name"]
+ continents[name] = cont
+ }
+}
+function die(fmt, argc, argv)
+{
+ printf "f_die 1 \"%%s: %s\" \"%s\"", fmt, progname
+ for (n = 1; n <= argc; n++)
+ printf " \"%s\"", argv[n]
+ print ""
+ failed++
+ exit 1
+}
+function find_continent(name)
+{
+ return continents[name]
+}
+function add_zone_to_country(lineno, tlc, descr, file, cont)
+{
+ #
+ # Validate the two-character country code
+ #
+ if (!match(tlc, /^[A-Z][A-Z]$/))
+ {
+ argv[1] = FILENAME
+ argv[2] = lineno
+ argv[3] = tlc
+ die(ENVRION["msg_country_code_invalid"], 3, argv)
+ }
+ if (!ENVIRON["country_" tlc "_name"])
+ {
+ argv[1] = FILENAME
+ argv[2] = lineno
+ argv[3] = tlc
+ die(ENVIRON["msg_country_code_unknown"], 3, argv)
+ }
+
+ #
+ # Add Zone to an array that we will parse at the end
+ #
+ if (length(descr) > 0)
+ {
+ if (country_nzones[tlc] < 0)
+ {
+ argv[1] = FILENAME
+ argv[2] = lineno
+ die(ENVIRON["msg_conflicting_zone_definition"], 2, argv)
+ }
+
+ n = ++country_nzones[tlc]
+ country_cont[tlc,n] = cont
+ country_filename[tlc,n] = file
+ country_descr[tlc,n] = descr
+ }
+ else
+ {
+ if (country_nzones[tlc] > 0)
+ {
+ argv[1] = FILENAME
+ argv[2] = lineno
+ die(ENVIRON["msg_zone_must_have_description"], 2, argv)
+ }
+ if (country_nzones[tlc] < 0)
+ {
+ argv[1] = FILENAME
+ argv[2] = lineno
+ die(ENVIRON["msg_zone_multiply_defined"], 2, argv)
+ }
+
+ country_nzones[tlc] = -1
+ country_cont[tlc] = cont
+ country_filename[tlc] = file
+ }
+}
+function print_country_code(tlc)
+{
+ nz = country_nzones[tlc]
+
+ printf "country_%s_nzones=%d\n", tlc, nz
+ printf "export country_%s_nzones\n", tlc
+
+ if (nz < 0)
+ {
+ printf "country_%s_cont=\"%s\"\n", tlc, country_cont[tlc]
+ printf "export country_%s_cont\n", tlc
+ printf "country_%s_filename=\"%s\"\n",
+ tlc, country_filename[tlc]
+ }
+ else
+ {
+ n = 0
+ while ( ++n <= nz )
+ {
+ printf "country_%s_cont_%d=\"%s\"\n",
+ tlc, n, country_cont[tlc,n]
+ printf "export country_%s_cont_%d\n", tlc, n
+ printf "country_%s_filename_%d=\"%s\"\n",
+ tlc, n, country_filename[tlc,n]
+ printf "country_%s_descr_%d=\"%s\"\n",
+ tlc, n, country_descr[tlc,n]
+ }
+ }
+}
+/^#/ {
+ lineno++
+ next
+}
+!/^#/ {
+ lineno++
+
+ #
+ # Split the current record (on TAB) into an array
+ #
+ if (split($0, line, /\t/) < 2)
+ {
+ argv[1] = FILENAME
+ argv[2] = lineno
+ die(ENVIRON["msg_invalid_format"], 2, argv)
+ }
+
+ # Get the ISO3166-1 (Alpha 1) 2-letter country code
+ tlc = line[1]
+
+ #
+ # Validate the two-character country code
+ #
+ if (length(tlc) != 2)
+ {
+ argv[1] = FILENAME
+ argv[2] = lineno
+ argv[3] = tlc
+ die(ENVIRON["msg_invalid_country_code"], 3, argv)
+ }
+
+ # Get the TZ field
+ tz = line[3]
+
+ #
+ # Validate the TZ field
+ #
+ if (!match(tz, "/"))
+ {
+ argv[1] = FILENAME
+ argv[2] = lineno
+ argv[3] = tz
+ die(ENVIRON["msg_invalid_zone_name"], 3, argv)
+ }
+
+ #
+ # Get the continent portion of the TZ field
+ #
+ contbuf = tz
+ sub("/.*$", "", contbuf)
+
+ #
+ # Validate the continent
+ #
+ cont = find_continent(contbuf)
+ if (!cont)
+ {
+ argv[1] = FILENAME
+ argv[2] = lineno
+ argv[3] = contbuf
+ die(ENVIRON["msg_invalid_region"], 3, argv)
+ }
+
+ #
+ # Get the filename portion of the TZ field
+ #
+ filename = tz
+ sub("^[^/]*/", "", filename)
+
+ #
+ # Calculate the substr start-position of the comment
+ #
+ descr_start = 0
+ n = 4
+ while (--n)
+ descr_start += length(line[n]) + 1
+
+ # Get the comment field
+ descr = substr($0, descr_start + 1)
+
+ add_zone_to_country(lineno, tlc, descr, filename, cont)
+}
+END {
+ if (failed) exit failed
+ for (tlc in country_nzones)
+ print_country_code(tlc)
+}
+'
+f_read_zones()
+{
+ eval $( awk -v progname="$pgm" \
+ "$f_read_zones_awk" \
+ "$_PATH_ZONETAB" )
+}
+
+# f_install_zoneinfo_file $filename
+#
+# Installs a zone file to _PATH_LOCALTIME.
+#
+f_install_zoneinfo_file()
+{
+ local funcname=f_install_zoneinfo_file
+ local zoneinfo_file="$1"
+ local copymode title msg height width
+
+ if [ -L "$_PATH_LOCALTIME" ]; then
+ copymode=
+ elif [ ! -e "$_PATH_LOCALTIME" ]; then
+ # Nothing there yet...
+ copymode=1
+ else
+ copymode=1
+ fi
+
+ if [ "$VERBOSE" ]; then
+ if [ ! "$zoneinfo_file" ]; then
+ f_sprintf msg "$msg_removing_file" "$_PATH_LOCALTIME"
+ elif [ "$copymode" ]; then
+ f_sprintf msg "$msg_copying_file" \
+ "$zoneinfo_file" "$_PATH_LOCALTIME"
+ else
+ f_sprintf msg "$msg_creating_symlink" \
+ "$_PATH_LOCALTIME" "$zoneinfo_file"
+ fi
+ if [ "$USEDIALOG" ]; then
+ f_dialog_title "$msg_info"
+ f_dialog_msgbox "$msg"
+ f_dialog_title_restore
+ else
+ printf "%s\n" "$msg"
+ fi
+ fi
+
+ [ "$REALLYDOIT" ] || return $SUCCESS
+
+ local catch_args="-de"
+ [ "$USEDIALOG" ] && catch_args=
+
+ if [ ! "$zoneinfo_file" ]; then
+ f_eval_catch $catch_args $funcname rm \
+ 'rm -f "%s"' "$_PATH_LOCALTIME" || return $FAILURE
+ f_eval_catch $catch_args $funcname rm \
+ 'rm -f "%s"' "$_PATH_DB" || return $FAILURE
+
+ if [ "$VERBOSE" ]; then
+ f_sprintf msg "$msg_removed_file" "$_PATH_LOCALTIME"
+ if [ "$USEDIALOG" ]; then
+ f_dialog_title "$msg_done"
+ f_dialog_msgbox "$msg"
+ f_dialog_title_restore
+ else
+ printf "%s\n" "$msg"
+ fi
+ fi
+ return $SUCCESS
+ fi # ! zoneinfo_file
+
+ if [ "$copymode" ]; then
+ f_eval_catch $catch_args $funcname rm \
+ 'rm -f "%s"' "$_PATH_LOCALTIME" || return $FAILURE
+ f_eval_catch $catch_args $funcname sh \
+ 'umask 222 && :> "%s"' "$_PATH_LOCALTIME" ||
+ return $FAILURE
+ f_eval_catch $catch_args $funcname sh \
+ 'cat "%s" > "%s"' \
+ "$zoneinfo_file" "$_PATH_LOCALTIME" || return $FAILURE
+ else
+ f_eval_catch $catch_args $funcname sh \
+ '( :< "%s" )' "$zoneinfo_file" || return $FAILURE
+ f_eval_catch $catch_args $funcname rm \
+ 'rm -f "%s"' "$_PATH_LOCALTIME" || return $FAILURE
+ f_eval_catch $catch_args $funcname ln \
+ 'ln -s "%s" "%s"' \
+ "$zoneinfo_file" "$_PATH_LOCALTIME" || return $FAILURE
+ fi # copymode
+
+ if [ "$VERBOSE" ]; then
+ if [ "$copymode" ]; then
+ f_sprintf msg "$msg_copied_timezone_file" \
+ "$zoneinfo_file" "$_PATH_LOCALTIME"
+ else
+ f_sprintf msg "$msg_created_symlink" \
+ "$_PATH_LOCALTIME" "$zoneinfo_file"
+ fi
+ if [ "$USEDIALOG" ]; then
+ f_dialog_title "$msg_done"
+ f_dialog_msgbox "$msg"
+ f_dialog_title_restore
+ else
+ printf "%s\n" "$msg"
+ fi
+ fi
+
+ return $SUCCESS
+}
+
+# f_install_zoneinfo $zoneinfo
+#
+# Install a zoneinfo file relative to _PATH_ZONEINFO. The given $zoneinfo
+# will be written to _PATH_DB (usable later with the `-r' flag).
+#
+f_install_zoneinfo()
+{
+ local zoneinfo="$1"
+ local rv
+
+ f_install_zoneinfo_file "$_PATH_ZONEINFO/$zoneinfo"
+ rv=$?
+
+ # Save knowledge for later
+ if [ "$REALLYDOIT" -a $rv -eq $SUCCESS ]; then
+ if true 2> /dev/null > "$_PATH_DB"; then
+ cat <<-EOF > "$_PATH_DB"
+ $zoneinfo
+ EOF
+ fi
+ fi
+
+ return $rv
+}
+
+# f_confirm_zone $filename
+#
+# Prompt the user to confirm the new timezone data. The first (and only)
+# argument should be the pathname to the zoneinfo file, either absolute or
+# relative to `/usr/share/zoneinfo' (e.g., "America/Los_Angeles").
+#
+# The return status is 0 if "Yes" is chosen, 1 if "No", and 255 if Esc is
+# pressed (see dialog(1) for additional details).
+#
+f_confirm_zone()
+{
+ local filename="$1"
+ f_dialog_title "$msg_confirmation"
+ local title="$DIALOG_TITLE" btitle="$DIALOG_BACKTITLE"
+ f_dialog_title_restore
+ local tm_zone="$( TZ="$filename" date +%Z )"
+ local prompt # Calculated below
+ local height=5 width=72
+
+ f_sprintf prompt "$msg_look_reasonable" "$tm_zone"
+ if [ "$USE_XDIALOG" ]; then
+ height=$(( $height + 4 ))
+ $DIALOG \
+ --title "$title" \
+ --backtitle "$btitle" \
+ --ok-label "$msg_yes" \
+ --cancel-label "$msg_no" \
+ --yesno "$prompt" $height $width
+ else
+ $DIALOG \
+ --title "$title" \
+ --backtitle "$btitle" \
+ --yes-label "$msg_yes" \
+ --no-label "$msg_no" \
+ --yesno "$prompt" $height $width
+ fi
+}
+
+# f_set_zone_utc
+#
+# Resets to the UTC timezone.
+#
+f_set_zone_utc()
+{
+ f_confirm_zone "" || return $FAILURE
+ f_install_zoneinfo_file ""
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." timezone/zones.subr
+
+fi # ! $_TIMEZONE_ZONES_SUBR
diff --git a/usr.sbin/bsdconfig/timezone/timezone b/usr.sbin/bsdconfig/timezone/timezone
new file mode 100755
index 0000000..a291442
--- /dev/null
+++ b/usr.sbin/bsdconfig/timezone/timezone
@@ -0,0 +1,457 @@
+#!/bin/sh
+#-
+# Copyright (c) 2011-2015 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/strings.subr
+f_include $BSDCFG_SHARE/timezone/continents.subr
+f_include $BSDCFG_SHARE/timezone/countries.subr
+f_include $BSDCFG_SHARE/timezone/iso3166.subr
+f_include $BSDCFG_SHARE/timezone/menus.subr
+f_include $BSDCFG_SHARE/timezone/zones.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="090.timezone"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ CONFIGURATION
+
+#
+# Standard pathnames
+#
+_PATH_DB="/var/db/zoneinfo"
+_PATH_WALL_CMOS_CLOCK="/etc/wall_cmos_clock"
+
+############################################################ GLOBALS
+
+#
+# Options
+#
+REALLYDOIT=1
+REINSTALL=
+USEDIALOG=1
+SKIPUTC= # See MAIN
+VERBOSE=
+TZ_OR_FAIL=
+CHROOTENV=
+
+#
+# Dummy vars (populated dynamically)
+#
+COUNTRIES= # list of 2-character country codes created by f_read_iso3166_table
+
+############################################################ FUNCTIONS
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local title="$DIALOG_TITLE"
+ local btitle="$DIALOG_BACKTITLE"
+ local prompt="$msg_select_region"
+ local defaultitem= # Calculated below
+ local hline=
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$title\" \
+ \"\$btitle\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $continent_menu_list
+
+ # Obtain default-item from previously stored selection
+ f_dialog_default_fetch defaultitem
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$title\" \
+ --backtitle \"\$btitle\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $continent_menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize menu_choice
+ f_dialog_menutag_store "$menu_choice"
+ f_dialog_default_store "$menu_choice"
+ return $retval
+}
+
+############################################################ MAIN
+
+# Skip initial question regarding UTC v. Wall-Clock time if run in VM
+[ "$( sysctl -n kern.vm_guest 2> /dev/null )" = "none" ] || SKIPUTC=1
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts C:ehnrsv$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ C) CHROOTENV="$OPTARG" ;;
+ e) TZ_OR_FAIL=1 ;;
+ n) REALLYDOIT= ;;
+ r) REINSTALL=1
+ USEDIALOG= ;;
+ s) SKIPUTC=1 ;;
+ v) VERBOSE=1 ;;
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_time_zone"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Process `-C chroot_directory' command-line argument
+#
+if [ "$CHROOTENV" ]; then
+ _PATH_ZONETAB="$CHROOTENV$_PATH_ZONETAB"
+ _PATH_ISO3166="$CHROOTENV$_PATH_ISO3166"
+ _PATH_ZONEINFO="$CHROOTENV$_PATH_ZONEINFO"
+ _PATH_LOCALTIME="$CHROOTENV$_PATH_LOCALTIME"
+ _PATH_DB="$CHROOTENV$_PATH_DB"
+ _PATH_WALL_CMOS_CLOCK="$CHROOTENV$_PATH_WALL_CMOS_CLOCK"
+fi
+
+#
+# Process `-r' command-line option
+#
+if [ "$REINSTALL" ]; then
+ [ -f "$_PATH_DB" -a -r "$_PATH_DB" ] ||
+ f_die 1 "$msg_cannot_open_for_reading" "$_PATH_DB"
+ f_eval_catch -dk zoneinfo "$0" cat 'cat "%s"' "$_PATH_DB" ||
+ f_die 1 "$msg_error_reading" "$_PATH_DB"
+ [ "$zoneinfo" ] ||
+ f_die 1 "$msg_unable_to_determine_name_from_db" "$_PATH_DB"
+ f_install_zoneinfo "$zoneinfo"
+ exit $?
+fi
+
+#
+# If the arguments on the command-line do not specify a file,
+# then interpret it as a zoneinfo name
+#
+if [ $# -ge 1 ]; then
+ zoneinfo="$1"
+
+ if [ ! -f "$zoneinfo" ]; then
+ USEDIALOG=
+ f_install_zoneinfo "$zoneinfo"
+ exit $?
+ fi
+
+ # FALLTHROUGH
+fi
+
+#
+# Process the UTC option
+#
+if [ "$_PATH_WALL_CMOS_CLOCK" -a ! "$SKIPUTC" ]; then
+ f_dialog_title "$msg_select_local_or_utc"
+ title="$DIALOG_TITLE"
+ btitle="$DIALOG_BACKTITLE"
+ f_dialog_title_restore
+ msg="$msg_is_machine_clock_utc"
+
+ if [ "$USE_XDIALOG" ]; then
+ defaultno="default-no"
+ height=10 width=77
+ else
+ defaultno="defaultno"
+ height=7 width=73
+ fi
+
+ if [ "$USE_XDIALOG" ]; then
+ $DIALOG \
+ --title "$title" \
+ --backtitle "$btitle" \
+ --$defaultno \
+ --ok-label "$msg_yes" \
+ --cancel-label "$msg_no" \
+ --yesno "$msg" $height $width
+ result=$?
+ else
+ $DIALOG \
+ --title "$title" \
+ --backtitle "$btitle" \
+ --$defaultno \
+ --yes-label "$msg_yes" \
+ --no-label "$msg_no" \
+ --yesno "$msg" $height $width
+ result=$?
+ fi
+
+ if [ $result -eq $DIALOG_OK ]; then
+ # User chose YES
+ [ "$REALLYDOIT" ] &&
+ f_quietly rm -f "$_PATH_WALL_CMOS_CLOCK"
+ else
+ # User chose NO, pressed ESC (or Ctrl-C), or closed box
+ [ "$REALLYDOIT" ] &&
+ ( umask 222 && :> "$_PATH_WALL_CMOS_CLOCK" )
+ fi
+fi
+
+#
+# Process optional default zone argument
+#
+if [ $# -ge 1 ]; then
+ default="$1"
+
+ f_dialog_title "$msg_default_zone_provided"
+ f_sprintf msg "\n$msg_use_default_zone" "$default"
+ hline=
+ f_dialog_yesno "$msg" "$hline"
+ result=$?
+ f_dialog_title_restore
+
+ if [ $result -eq $DIALOG_OK ]; then
+ # User chose YES
+ f_install_zoneinfo_file "$default"
+ result=$?
+ [ ! "$USE_XDIALOG" ] && f_dialog_clear
+ exit $result
+ fi
+
+ [ ! "$USE_XDIALOG" ] && f_dialog_clear
+fi
+
+#
+# Override the user-supplied umask
+#
+umask 022
+
+#
+# Read databases and perform initialization
+#
+f_read_iso3166_table # creates $COUNTRIES and $country_*_name
+f_read_zones # creates $country_*_{descr,cont,filename}
+f_sort_countries # sorts the countries listed for each continent
+f_make_menus # creates $continent_menu_list and $continent_*_menu_list
+
+#
+# Launch application main menu
+#
+defaultctry=
+defaultzone=
+NEED_CONTINENT=1
+NEED_COUNTRY=1
+while :; do
+ if [ "$NEED_CONTINENT" ]; then
+ dialog_menu_main # prompt the user to select a continent/ocean
+ retval=$?
+ f_dialog_menutag_fetch mtag
+
+ if [ $retval -ne $DIALOG_OK ]; then
+ [ "$TZ_OR_FAIL" ] && f_die
+ exit $SUCCESS
+ fi
+
+ NEED_CONTINENT=
+
+ continent=$( eval f_dialog_menutag2item \"\$mtag\" \
+ $continent_menu_list )
+ f_find_continent "$continent" cont
+ f_continent $cont title cont_title
+ f_continent $cont nitems nitems
+ f_OCEANP $cont isocean
+ fi
+
+ if [ "$NEED_COUNTRY" ]; then
+ if [ "$cont_title" = "$continent_utc_title" ]; then
+ if f_set_zone_utc; then
+ break
+ else
+ NEED_CONTINENT=1
+ continue
+ fi
+ fi
+
+ #
+ # Short cut -- if there's only one country, don't post a menu.
+ #
+ if [ $nitems -eq 1 ]; then
+ tag=1
+ else
+ #
+ # It's amazing how much good grammar really matters...
+ #
+ if [ ! "$isocean" ]; then
+ f_sprintf title "$msg_country_title" \
+ "$cont_title"
+ f_dialog_title "$title"
+ title="$DIALOG_TITLE"
+ btitle="$DIALOG_BACKTITLE"
+ f_dialog_title_restore
+ prompt="$msg_select_country"
+ else
+ f_sprintf title "$msg_island_and_group_title" \
+ "$cont_title"
+ f_dialog_title "$title"
+ title="$DIALOG_TITLE"
+ btitle="$DIALOG_BACKTITLE"
+ f_dialog_title_restore
+ prompt="$msg_select_island_or_group"
+ fi
+
+ #
+ # Calculate size of menu
+ #
+ f_continent $cont menu_list menu_list
+ eval f_dialog_menu_size height width rows \
+ \"\$title\" \
+ \"\$btitle\" \
+ \"\$prompt\" \
+ \"\" \
+ $menu_list
+
+ #
+ # Launch the country selection menu
+ #
+ tag=$( eval $DIALOG \
+ --title \"\$title\" \
+ --backtitle \"\$btitle\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultctry\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ retval=$?
+ f_dialog_data_sanitize tag
+ defaultctry="$tag"
+
+ if [ $retval -ne $DIALOG_OK ]; then
+ NEED_CONTINENT=1
+ continue # back to main menu
+ fi
+ fi
+
+ # Get the country code from the user's selection
+ f_continent $cont tlc_$tag tlc
+
+ NEED_COUNTRY=
+ fi
+
+ #
+ # If the selection has only one zone (nzones == -1),
+ # just set it.
+ #
+ f_country $tlc nzones nzones
+ if [ $nzones -lt 0 ]; then
+ f_country $tlc cont real_cont
+ f_continent $real_cont name real_continent
+ f_country $tlc name name
+ f_country $tlc filename filename
+
+ if ! f_confirm_zone "$real_continent/$filename"; then
+ [ $nitems -eq 1 ] && NEED_CONTINENT=1
+ NEED_COUNTRY=1
+ continue
+ fi
+ else
+ f_country $tlc name name
+ f_sprintf title "$msg_country_time_zones" "$name"
+ f_dialog_title "$title"
+ title="$DIALOG_TITLE" btitle="$DIALOG_BACKTITLE"
+ f_dialog_title_restore
+ prompt="$msg_select_zone"
+ f_country $tlc menu_list menu_list
+ eval f_dialog_menu_size height width rows \
+ \"\$title\" \"\$btitle\" \"\$prompt\" \"\" $menu_list
+
+ #
+ # Launch the zone selection menu
+ # NOTE: This is as deep as we go
+ #
+ n=$( eval $DIALOG \
+ --title \"\$title\" \
+ --backtitle \"\$btitle\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultzone\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ retval=$?
+ f_dialog_data_sanitize n
+ defaultzone="$n"
+
+ if [ $retval -ne $DIALOG_OK ]; then
+ [ $nitems -eq 1 ] && NEED_CONTINENT=1
+ NEED_COUNTRY=1
+ continue
+ fi
+
+ f_country $tlc cont_$n real_cont
+ f_continent $real_cont name real_continent
+ f_country $tlc name name
+ f_country $tlc filename_$n filename
+
+ f_confirm_zone "$real_continent/$filename" || continue
+ fi
+
+ [ $retval -eq $DIALOG_OK ] || continue # back to main menu
+
+ if ! f_install_zoneinfo "$real_continent/$filename"; then
+ [ $nzones -lt 0 ] && NEED_COUNTRY=1
+ else
+ break
+ fi
+done
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/ttys/INDEX b/usr.sbin/bsdconfig/ttys/INDEX
new file mode 100644
index 0000000..83ca021
--- /dev/null
+++ b/usr.sbin/bsdconfig/ttys/INDEX
@@ -0,0 +1,57 @@
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+#
+# Title that will be shown in the bsdconfig menu.
+#
+menu_title="Ttys"
+
+#
+# A short descriptive line shown at the bottom of the bsdconfig menu. keep it
+# short because any line longer than the terminal width will be truncated.
+#
+menu_help="Configure Ttys"
+
+#
+# Two-part variable that defines an action to take when `keyword' is passed on
+# a bsdconfig command line. Variable takes the form "keyword|command" and
+# multiple occurrences of the variable (with different `keyword's, or different
+# `keyword's AND `command's) are allowed. If `command' begins with a '/' then
+# the full path to the program is needed. If `command' begins with anything
+# else it is a path relative to the directory this INDEX file is in. `keyword'
+# can be i18n'ed but `command' is the name of a script.
+#
+menu_selection="ttys|ttys"
+
+#
+# ------------ Items below this line do NOT need i18n translation ------------
+#
+# Name of the program to be run when this menu choice is selected. If it begins
+# with a '/' then the full path to the program is needed. If it begins with
+# anything else it is a path relative to the directory this INDEX file is in.
+#
+menu_program="ttys"
diff --git a/usr.sbin/bsdconfig/ttys/Makefile b/usr.sbin/bsdconfig/ttys/Makefile
new file mode 100644
index 0000000..044fdcf
--- /dev/null
+++ b/usr.sbin/bsdconfig/ttys/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+SUBDIR= include
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/150.ttys
+FILES= INDEX USAGE
+
+SCRIPTSDIR= ${FILESDIR}
+SCRIPTS= ttys
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/ttys/Makefile.depend b/usr.sbin/bsdconfig/ttys/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/ttys/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/ttys/USAGE b/usr.sbin/bsdconfig/ttys/USAGE
new file mode 100644
index 0000000..a06d8af
--- /dev/null
+++ b/usr.sbin/bsdconfig/ttys/USAGE
@@ -0,0 +1,37 @@
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+Usage: bsdconfig @PROGRAM_NAME@ [OPTIONS]
+
+OPTIONS:
+ -d Provide lots of debugging info on standard-out when running.
+ -D file Send debugging info to file. If file begins with a plus-sign
+ debug info is sent to both standard-out and file (minus the
+ leading plus).
+ -h Print this usage statement and exit.
+ -S Secure X11 mode (implies `-X'). As root, always prompt-for
+ and validate sudo(8) username/password before starting.
+ -X Use Xdialog(1) in place of dialog(1).
diff --git a/usr.sbin/bsdconfig/ttys/include/Makefile b/usr.sbin/bsdconfig/ttys/include/Makefile
new file mode 100644
index 0000000..45c72ad
--- /dev/null
+++ b/usr.sbin/bsdconfig/ttys/include/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/150.ttys/include
+FILES= messages.subr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/ttys/include/Makefile.depend b/usr.sbin/bsdconfig/ttys/include/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/ttys/include/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/ttys/include/messages.subr b/usr.sbin/bsdconfig/ttys/include/messages.subr
new file mode 100644
index 0000000..296f756
--- /dev/null
+++ b/usr.sbin/bsdconfig/ttys/include/messages.subr
@@ -0,0 +1,31 @@
+# Copyright (c) 2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+msg_configure_ttys="Configure TTYs"
+msg_help_text="Configuration of system TTYs requires editing the /etc/ttys file.\nTypical configuration activities might include enabling getty(8)\non the first serial port to allow login via serial console after\nreboot, or to enable xdm. The default ttys file enables normal\nvirtual consoles, and most sites will not need to perform manual\nconfiguration.\n\nTo load /etc/ttys in the editor, select [Yes], otherwise, [No]."
+msg_no_such_file_or_directory="%s: %s: No such file or directory"
+msg_permission_denied="%s: %s: permission denied"
+msg_user_confirmation_requested="User Confirmation Requested"
diff --git a/usr.sbin/bsdconfig/ttys/ttys b/usr.sbin/bsdconfig/ttys/ttys
new file mode 100755
index 0000000..ed7ba75
--- /dev/null
+++ b/usr.sbin/bsdconfig/ttys/ttys
@@ -0,0 +1,128 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="150.ttys"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ CONFIGURATION
+
+#
+# Default text-editor to use
+#
+: ${EDITOR:=ee}
+
+
+#
+# If X11 is requested, which terminal and what options should we use?
+#
+X11TERM=xterm
+X11TERM_OPTS=
+
+#
+# Location of ttys(5)
+#
+ETC_TTYS=/etc/ttys
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_configure_ttys"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+f_dialog_title "$msg_user_confirmation_requested"
+f_dialog_yesno "$msg_help_text" || exit $SUCCESS
+f_dialog_title_restore
+
+#
+# Make sure $EDITOR exists and is executable
+#
+case "$EDITOR" in
+*/*)
+ [ -e "$EDITOR" ] ||
+ f_die 1 "$msg_no_such_file_or_directory" "$pgm" "$EDITOR"
+ [ -x "$EDITOR" ] ||
+ f_die 1 "$msg_permission_denied" "$pgm" "$EDITOR"
+ ;;
+*)
+ f_have "$EDITOR" ||
+ f_die 1 "$msg_no_such_file_or_directory" "$pgm" "$EDITOR"
+esac
+
+#
+# If Xdialog(1) is requested, we'll need to wrap bsdinstall(8) into xterm(1)
+#
+if [ "$USE_XDIALOG" ]; then
+ #
+ # Make sure $X11TERM exists and is executable
+ #
+ case "$X11TERM" in
+ */*)
+ [ -e "$X11TERM" ] || f_die 1 \
+ "$msg_no_such_file_or_directory" "$pgm" "$X11TERM"
+ [ -x "$X11TERM" ] || f_die 1 \
+ "$msg_permission_denied" "$pgm" "$X11TERM"
+ ;;
+ *)
+ f_have "$X11TERM" || f_die 1 \
+ "$msg_no_such_file_or_directory" "$pgm" "$X11TERM"
+ esac
+
+ exec $X11TERM $X11TERM_OPTS -e $EDITOR $ETC_TTYS
+else
+ exec $EDITOR $ETC_TTYS
+fi
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/usermgmt/INDEX b/usr.sbin/bsdconfig/usermgmt/INDEX
new file mode 100644
index 0000000..6fc61ea
--- /dev/null
+++ b/usr.sbin/bsdconfig/usermgmt/INDEX
@@ -0,0 +1,64 @@
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+#
+# Title that will be shown in the bsdconfig menu.
+#
+menu_title="Login/Group Management"
+
+#
+# A short descriptive line shown at the bottom of the bsdconfig menu. keep it
+# short because any line longer than the terminal width will be truncated.
+#
+menu_help="Manage system user and/or group information"
+
+#
+# Two-part variable that defines an action to take when `keyword' is passed on
+# a bsdconfig command line. Variable takes the form "keyword|command" and
+# multiple occurrences of the variable (with different `keyword's, or different
+# `keyword's AND `command's) are allowed. If `command' begins with a '/' then
+# the full path to the program is needed. If `command' begins with anything
+# else it is a path relative to the directory this INDEX file is in. `keyword'
+# can be i18n'ed but `command' is the name of a script.
+#
+menu_selection="usermgmt|usermgmt"
+menu_selection="useradd|useradd"
+menu_selection="useredit|useredit"
+menu_selection="userdel|userdel"
+menu_selection="groupmgmt|usermgmt"
+menu_selection="groupadd|groupadd"
+menu_selection="groupedit|groupedit"
+menu_selection="groupdel|groupdel"
+
+#
+# ------------ Items below this line do NOT need i18n translation ------------
+#
+# Name of the program to be run when this menu choice is selected. If it begins
+# with a '/' then the full path to the program is needed. If it begins with
+# anything else it is a path relative to the directory this INDEX file is in.
+#
+menu_program="usermgmt"
diff --git a/usr.sbin/bsdconfig/usermgmt/Makefile b/usr.sbin/bsdconfig/usermgmt/Makefile
new file mode 100644
index 0000000..127c874
--- /dev/null
+++ b/usr.sbin/bsdconfig/usermgmt/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+SUBDIR= include share
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/070.usermgmt
+FILES= INDEX USAGE
+
+SCRIPTSDIR= ${FILESDIR}
+SCRIPTS= groupadd groupdel groupedit useradd userdel useredit usermgmt
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/usermgmt/Makefile.depend b/usr.sbin/bsdconfig/usermgmt/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/usermgmt/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/usermgmt/USAGE b/usr.sbin/bsdconfig/usermgmt/USAGE
new file mode 100644
index 0000000..a06d8af
--- /dev/null
+++ b/usr.sbin/bsdconfig/usermgmt/USAGE
@@ -0,0 +1,37 @@
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+Usage: bsdconfig @PROGRAM_NAME@ [OPTIONS]
+
+OPTIONS:
+ -d Provide lots of debugging info on standard-out when running.
+ -D file Send debugging info to file. If file begins with a plus-sign
+ debug info is sent to both standard-out and file (minus the
+ leading plus).
+ -h Print this usage statement and exit.
+ -S Secure X11 mode (implies `-X'). As root, always prompt-for
+ and validate sudo(8) username/password before starting.
+ -X Use Xdialog(1) in place of dialog(1).
diff --git a/usr.sbin/bsdconfig/usermgmt/groupadd b/usr.sbin/bsdconfig/usermgmt/groupadd
new file mode 100755
index 0000000..cba0f2b
--- /dev/null
+++ b/usr.sbin/bsdconfig/usermgmt/groupadd
@@ -0,0 +1,77 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012-2014 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/usermgmt/group.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="070.usermgmt"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_add $msg_group"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Add a group
+#
+# NB: If given an argument on the command-line use it; otherwise fall-back to
+# environment variable $group (handle $VAR_GROUP).
+#
+f_group_add ${1:+"$1"}
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/usermgmt/groupdel b/usr.sbin/bsdconfig/usermgmt/groupdel
new file mode 100755
index 0000000..ea55489
--- /dev/null
+++ b/usr.sbin/bsdconfig/usermgmt/groupdel
@@ -0,0 +1,100 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012-2014 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/usermgmt/group.subr
+f_include $BSDCFG_SHARE/usermgmt/group_input.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="070.usermgmt"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_delete $msg_group"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# If given a group name, operate on it and exit
+#
+if [ "$1" ]; then
+ f_group_delete "$1"
+ exit $SUCCESS
+fi
+
+#
+# Loop until the user Exits, Cancels or presses ESC
+#
+defaultitem=
+while :; do
+ f_dialog_menu_group_list "$defaultitem"
+ retval=$?
+ f_dialog_menutag_fetch mtag
+ f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
+ defaultitem="$mtag"
+
+ [ $retval -eq $DIALOG_OK ] || f_die
+
+ [ "$mtag" = "X $msg_exit" ] && break
+
+ # Anything else is a group name
+
+ f_group_delete "$mtag"
+done
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/usermgmt/groupedit b/usr.sbin/bsdconfig/usermgmt/groupedit
new file mode 100755
index 0000000..2338d57
--- /dev/null
+++ b/usr.sbin/bsdconfig/usermgmt/groupedit
@@ -0,0 +1,100 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012-2014 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/usermgmt/group.subr
+f_include $BSDCFG_SHARE/usermgmt/group_input.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="070.usermgmt"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_edit_view $msg_group"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# If given a group name, operate on it and exit
+#
+if [ "$1" ]; then
+ f_group_edit "$1"
+ exit $SUCCESS
+fi
+
+#
+# Present a list of groups and loop until user Exits, Cancels or presses ESC
+#
+defaultitem=
+while :; do
+ f_dialog_menu_group_list "$defaultitem"
+ retval=$?
+ f_dialog_menutag_fetch mtag
+ f_dprintf "retval=%s mtag=[%s]" $retval "$mtag"
+ defaultitem="$mtag"
+
+ [ $retval -eq $DIALOG_OK ] || f_die
+
+ [ "$mtag" = "X $msg_exit" ] && break
+
+ # Anything else is a group name
+
+ f_group_edit "$mtag"
+done
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/usermgmt/include/Makefile b/usr.sbin/bsdconfig/usermgmt/include/Makefile
new file mode 100644
index 0000000..4839654
--- /dev/null
+++ b/usr.sbin/bsdconfig/usermgmt/include/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+FILESDIR= ${LIBEXECDIR}/bsdconfig/070.usermgmt/include
+FILES= messages.subr usermgmt.hlp
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/usermgmt/include/Makefile.depend b/usr.sbin/bsdconfig/usermgmt/include/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/usermgmt/include/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/usermgmt/include/messages.subr b/usr.sbin/bsdconfig/usermgmt/include/messages.subr
new file mode 100644
index 0000000..f56b844
--- /dev/null
+++ b/usr.sbin/bsdconfig/usermgmt/include/messages.subr
@@ -0,0 +1,119 @@
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012-2014 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+hline_alnum_punc_tab_enter="Use alpha-numeric, punctuation, TAB or ENTER"
+hline_alnum_space_tab_enter="Use alpha-numeric, SPACE, TAB or ENTER"
+hline_alnum_tab_enter="Use alpha-numeric, TAB or ENTER"
+hline_arrows_space_tab_enter="Use arrows, SPACE, TAB or ENTER"
+hline_arrows_tab_enter="Press arrows, TAB or ENTER"
+hline_num_arrows_tab_enter="Use numbers, arrows, TAB or ENTER"
+hline_num_tab_enter="Use numbers, TAB or ENTER"
+msg_account_does_not_expire="Account does not expire"
+msg_account_expire_manual_edit="Enter account expiration time. Format is one of:\n\n a) decimal for UNIX time since %s\n b) dd-mmm-yy[yy] for day, month, and 2- or 4-digit year\n c) +n[mhdwoy] for relative time from current date\n\nNOTE: Value of zero disables expiration."
+msg_account_expires_on="Account Expires on"
+msg_add="Add"
+msg_add_group="Add Group"
+msg_add_login="Add Login"
+msg_cancel="Cancel"
+msg_continue="Continue"
+msg_create_dotfiles="Create Dotfiles"
+msg_create_home_directory="Create Home Directory"
+msg_delete="Delete"
+msg_delete_exit_or_cancel="Choose Delete/Exit when finished or Cancel."
+msg_delete_group="Delete Group"
+msg_delete_home_directory="Delete Home Directory"
+msg_delete_login="Delete Login"
+msg_delete_primary_group="Delete Primary Group"
+msg_deleting_home_directory="Deleting home directory..."
+msg_disable_password_auth_for_account="Disable password authentication for this account?"
+msg_disable_password_auth_for_group="Disable password authentication for this group?"
+msg_edit_date_time_with_a_calendar="Edit date/time with a calendar"
+msg_edit_group="Edit/View Group"
+msg_edit_login="Edit/View Login"
+msg_edit_view="Edit/View"
+msg_enter_group_members_manually="Enter Group Members manually"
+msg_enter_groups_manually="Enter Groups manually"
+msg_enter_number_of_days_into_the_future="Enter number of days into the future"
+msg_enter_value_manually="Edit value manually"
+msg_error="ERROR!"
+msg_exit="Exit"
+msg_full_name="Full Name"
+msg_group="Group"
+msg_group_added="Group Added"
+msg_group_already_used="%s: Group is already used."
+msg_group_deleted="Group Deleted"
+msg_group_id="Group ID"
+msg_group_id_leave_empty_for_default="Group ID (Leave empty for default)"
+msg_group_is_empty="Group is empty."
+msg_group_members="Group Members"
+msg_group_must_start_with_letter="Group must start with a letter."
+msg_group_not_found="%s: Group not found."
+msg_group_password="Group Password"
+msg_group_passwords_do_not_match="Group Passwords do not match."
+msg_group_updated="Group Updated"
+msg_groups="Groups"
+msg_home_directory="Home Directory"
+msg_login="Login"
+msg_login_added="Login Added"
+msg_login_already_used="%s: Login is already used."
+msg_login_class="Login Class"
+msg_login_deleted="Login Deleted"
+msg_login_is_empty="Login is empty."
+msg_login_management="Login/Group Management"
+msg_login_must_start_with_letter="Login must start with a letter."
+msg_login_not_found="%s: Login not found."
+msg_login_updated="Login Updated"
+msg_member_of_groups="Member of Groups"
+msg_n_a="N/A"
+msg_no="No"
+msg_no_group_specified="No group specified!"
+msg_no_user_specified="No user specified!"
+msg_number_of_seconds_since_epoch="Number of seconds since the Epoch\n(1 = %s)\nNULL or zero to disable:"
+msg_ok="OK"
+msg_password="Password"
+msg_password_does_not_expire="Password does not expire"
+msg_password_expire_manual_edit="Enter password expiration time. Format is one of:\n\n a) decimal for UNIX time since %s\n b) dd-mmm-yy[yy] for day, month, and 2- or 4-digit year\n c) +n[mhdwoy] for relative time from current date\n\nNOTE: Value of zero disables expiration."
+msg_password_expires_on="Password Expires on"
+msg_passwords_do_not_match="Passwords do not match."
+msg_please_enter_a_group_name="Please enter a group name!"
+msg_please_enter_a_user_name="Please enter a user name!"
+msg_reenter_group_password="Re-enter Group Password"
+msg_reenter_password="Re-enter Password"
+msg_save="Save"
+msg_save_exit_or_cancel="Choose Save/Exit when finished or Cancel."
+msg_select_group_members_from_list="Select Group Members from a list"
+msg_select_groups_from_list="Select Groups from a list"
+msg_select_login_shell="Select Login Shell"
+msg_separated_by_commas="Separated by commas"
+msg_shell="Shell"
+msg_unknown_user_management_menu_selection="Unknown user management menu selection"
+msg_use_default_values_for_all_account_details="Use default values for all account details?"
+msg_user="User"
+msg_user_id="UID"
+msg_user_id_leave_empty_for_default="UID (Leave empty for default)"
+msg_warning="WARNING!"
+msg_yes="Yes"
diff --git a/usr.sbin/bsdconfig/usermgmt/include/usermgmt.hlp b/usr.sbin/bsdconfig/usermgmt/include/usermgmt.hlp
new file mode 100644
index 0000000..77be9bd
--- /dev/null
+++ b/usr.sbin/bsdconfig/usermgmt/include/usermgmt.hlp
@@ -0,0 +1,76 @@
+These screens allow you to add groups and users to your system.
+
+Many of the settings get reasonable defaults if you leave them blank.
+The first time you have entered the name of the new group or user, the
+system will show you what it would chose for most of these fields.
+You are free to change them, of course.
+
+
+User groups
+===========
+
+It's certainly almost generally a good idea to first create a new
+group for your users. Common names for such a group are "users", or
+even simply "other". Group names are used to control file access
+permissions for users that belong to the same group. Several group
+names are already used for system files.
+
+The numerical user or group IDs are often nothing you want to care for
+explicitly. If you don't fill in these fields, the system will choose
+reasonable defaults. However, these numbers (rather than the
+associated names) are what the operating system actually uses to
+distinguish users and groups -- hence they should normally be unique
+to each person or group, respectively.
+
+
+Users
+=====
+
+The user's login ID is a short (up to 15 characters) alphanumeric ID
+that the user must enter when logging into the system. It's often the
+initial letters of the user's name, and commonly used in lower case.
+It's also the local mail name for this user (though it's possible to
+also set up more descriptive mail alias names later).
+
+The user's login group determines which group access rights the user
+will initially get when logging in. If an additional list of groups is
+provided which the user will become a member of, (s)he will also be
+able to access files of those groups later without providing any
+additional password etc. Except for the "wheel" case mentioned below,
+the additional group membership list should normally not contain the
+login group again.
+
+The user's password can also be set here, and should be chosen with
+care - 6 or more characters, intermixing punctuation and numerics, and
+*not* a word from the dictionary or related to the username is a good
+password choice.
+
+Some of the system's groups have a special meaning. In particular,
+members of group "wheel" are the only people who are later allowed to
+become superuser using the command su(1). So if you're going to add a
+new user who should later perform administrative tasks, don't forget
+to add him to this group! (Well, ``he'' will most likely be yourself
+in the very first place. :)
+
+Also, members of group "operator" will by default get permissions for
+minor administrative operations, like performing system backups, or
+shutting down the system -- without first becoming superuser! So,
+take care when adding people to this group.
+
+The ``full name'' field serves as a comment only. It is also used by
+mail front ends to determine the real name of the user, hence you
+should actually fill in the first and last name of this user. By
+convention, this field can be divided into comma-separated subfields,
+where the office location, the work phone number, and the home phone
+number follow the full name of the user.
+
+The home directory is the directory in the filesystem where the user
+is being logged into, and where his personalized setup files (``dot
+files'', since they usually begin with a `.' and are not displayed by
+the ls(1) command by default) will be looked up. It is often created
+under /usr/home/ or /home/.
+
+Finally, the shell is the user's initial command interpreter. The
+default shell is /bin/sh, some users prefer the more historic
+/bin/csh. Other, often more user-friendly and comfortable shells can
+be found in the ports and packages collection.
diff --git a/usr.sbin/bsdconfig/usermgmt/share/Makefile b/usr.sbin/bsdconfig/usermgmt/share/Makefile
new file mode 100644
index 0000000..eba7c1c
--- /dev/null
+++ b/usr.sbin/bsdconfig/usermgmt/share/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+FILESDIR= ${SHAREDIR}/bsdconfig/usermgmt
+FILES= group.subr group_input.subr user.subr user_input.subr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdconfig/usermgmt/share/Makefile.depend b/usr.sbin/bsdconfig/usermgmt/share/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdconfig/usermgmt/share/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdconfig/usermgmt/share/group.subr b/usr.sbin/bsdconfig/usermgmt/share/group.subr
new file mode 100644
index 0000000..e9c8b16
--- /dev/null
+++ b/usr.sbin/bsdconfig/usermgmt/share/group.subr
@@ -0,0 +1,518 @@
+if [ ! "$_USERMGMT_GROUP_SUBR" ]; then _USERMGMT_GROUP_SUBR=1
+#
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012-2014 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." usermgmt/group.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/usermgmt/group_input.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="070.usermgmt"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+############################################################ CONFIGURATION
+
+# set some reasonable defaults if /etc/adduser.conf does not exist.
+[ -f /etc/adduser.conf ] && f_include /etc/adduser.conf
+: ${passwdtype:="yes"}
+
+############################################################ FUNCTIONS
+
+# f_group_add [$group]
+#
+# Add a group. If both $group (as a first argument) and $VAR_GROUP are unset
+# or NULL and we are running interactively, prompt the user to enter the name
+# of a new group and (if $VAR_NO_CONFIRM is unset or NULL) prompt the user to
+# answer some questions about the new group. Variables that can be used to
+# script user input:
+#
+# VAR_GROUP [Optional if running interactively]
+# The group to add. Ignored if given non-NULL first-argument.
+# VAR_GROUP_GID [Optional]
+# Numerical group ID to use. If NULL or unset, the group ID is
+# automatically chosen.
+# VAR_GROUP_MEMBERS [Optional]
+# Comma separated list of users that are a member of this group.
+# VAR_GROUP_PASSWORD [Optional]
+# newgrp(1) password to set for the group. Default if NULL or
+# unset is to disable newgrp(1) password authentication.
+#
+# Returns success if the group was successfully added.
+#
+f_group_add()
+{
+ local funcname=f_group_add
+ local title # Calculated below
+ local alert=f_show_msg no_confirm=
+
+ f_getvar $VAR_NO_CONFIRM no_confirm
+ [ "$no_confirm" ] && alert=f_show_info
+
+ local input
+ f_getvar 3:-\$$VAR_GROUP input "$1"
+
+ #
+ # NB: pw(8) has a ``feature'' wherein `-n name' can be taken as GID
+ # instead of name. Work-around is to also pass `-g GID' at the same
+ # time (the GID is ignored in this case, so any GID will do).
+ #
+ if [ "$input" ] && f_quietly pw groupshow -n "$input" -g 1337; then
+ f_show_err "$msg_group_already_used" "$input"
+ return $FAILURE
+ fi
+
+ local group_name="$input"
+ while f_interactive && [ ! "$group_name" ]; do
+ f_dialog_input_group_name group_name "$group_name" ||
+ return $SUCCESS
+ [ "$group_name" ] ||
+ f_show_err "$msg_please_enter_a_group_name"
+ done
+ if [ ! "$group_name" ]; then
+ f_show_err "$msg_no_group_specified"
+ return $FAILURE
+ fi
+
+ local group_password group_gid group_members
+ f_getvar $VAR_GROUP_PASSWORD group_password
+ f_getvar $VAR_GROUP_GID group_gid
+ f_getvar $VAR_GROUP_MEMBERS group_members
+
+ local group_password_disable=
+ f_interactive || [ "$group_password" ] || group_password_disable=1
+
+ if f_interactive && [ ! "$no_confirm" ]; then
+ f_dialog_noyes \
+ "$msg_use_default_values_for_all_account_details"
+ retval=$?
+ if [ $retval -eq $DIALOG_ESC ]; then
+ return $SUCCESS
+ elif [ $retval -ne $DIALOG_OK ]; then
+ #
+ # Ask series of questions to pre-fill the editor screen
+ #
+ # Defaults used in each dialog should allow the user to
+ # simply hit ENTER to proceed and cancelling a single
+ # dialog cause them to return to the previous menu.
+ #
+
+ if [ "$passwdtype" = "yes" ]; then
+ f_dialog_input_group_password group_password \
+ group_password_disable ||
+ return $FAILURE
+ fi
+ f_dialog_input_group_gid group_gid "$group_gid" ||
+ return $FAILURE
+ f_dialog_input_group_members group_members \
+ "$group_members" || return $FAILURE
+ fi
+ fi
+
+ #
+ # Loop until the user decides to Exit, Cancel, or presses ESC
+ #
+ title="$msg_add $msg_group: $group_name"
+ if f_interactive; then
+ local mtag retval defaultitem=
+ while :; do
+ f_dialog_title "$title"
+ f_dialog_menu_group_add "$defaultitem"
+ retval=$?
+ f_dialog_title_restore
+ f_dialog_menutag_fetch mtag
+ f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
+ defaultitem="$mtag"
+
+ # Return if user either pressed ESC or chose Cancel/No
+ [ $retval -eq $DIALOG_OK ] || return $FAILURE
+
+ case "$mtag" in
+ X) # Add/Exit
+ local var
+ for var in gid members name; do
+ local _group_$var
+ eval f_shell_escape \
+ \"\$group_$var\" _group_$var
+ done
+
+ local cmd="pw groupadd -n '$_group_name'"
+ [ "$group_gid" ] && cmd="$cmd -g '$_group_gid'"
+ [ "$group_members" ] &&
+ cmd="$cmd -M '$_group_members'"
+
+ # Execute the command (break on success)
+ if [ "$group_password_disable" ]; then
+ f_eval_catch $funcname pw '%s -h -' "$cmd"
+ elif [ "$group_password" ]; then
+ echo "$group_password" |
+ f_eval_catch $funcname \
+ pw '%s -h 0' "$cmd"
+ else
+ f_eval_catch $funcname pw '%s' "$cmd"
+ fi && break
+ ;;
+ 1) # Group Name (prompt for new group name)
+ f_dialog_input_group_name input "$group_name" ||
+ continue
+ if f_quietly pw groupshow -n "$input" -g 1337; then
+ f_show_err "$msg_group_already_used" "$input"
+ continue
+ fi
+ group_name="$input"
+ title="$msg_add $msg_group: $group_name"
+ ;;
+ 2) # Password
+ f_dialog_input_group_password group_password \
+ group_password_disable
+ ;;
+ 3) # Group ID
+ f_dialog_input_group_gid group_gid "$group_gid"
+ ;;
+ 4) # Group Members
+ f_dialog_input_group_members group_members \
+ "$group_members"
+ ;;
+ esac
+ done
+ else
+ local var
+ for var in gid members name; do
+ local _group_$var
+ eval f_shell_escape \"\$group_$var\" _group_$var
+ done
+
+ # Form the command
+ local cmd="pw groupadd -n '$_group_name'"
+ [ "$group_gid" ] && cmd="$cmd -g '$_group_gid'"
+ [ "$group_members" ] && cmd="$cmd -M '$_group_members'"
+
+ # Execute the command
+ local retval err
+ if [ "$group_password_disable" ]; then
+ f_eval_catch -k err $funcname pw '%s -h -' "$cmd"
+ elif [ "$group_password" ]; then
+ err=$( echo "$group_password" | f_eval_catch -de \
+ $funcname pw '%s -h 0' "$cmd" 2>&1 )
+ else
+ f_eval_catch -k err $funcname pw '%s' "$cmd"
+ fi
+ retval=$?
+ if [ $retval -ne $SUCCESS ]; then
+ f_show_err "%s" "$err"
+ return $retval
+ fi
+ fi
+
+ f_dialog_title "$title"
+ $alert "$msg_group_added"
+ f_dialog_title_restore
+ [ "$no_confirm" -a "$USE_DIALOG" ] && sleep 1
+
+ return $SUCCESS
+}
+
+# f_group_delete [$group]
+#
+# Delete a group. If both $group (as a first argument) and $VAR_GROUP are unset
+# or NULL and we are running interactively, prompt the user to select a group
+# from a list of available groups. Variables that can be used to script user
+# input:
+#
+# VAR_GROUP [Optional if running interactively]
+# The group to delete. Ignored if given non-NULL first-argument.
+#
+# Returns success if the group was successfully deleted.
+#
+f_group_delete()
+{
+ local funcname=f_group_delete
+ local title # Calculated below
+ local alert=f_show_msg no_confirm=
+
+ f_getvar $VAR_NO_CONFIRM no_confirm
+ [ "$no_confirm" ] && alert=f_show_info
+
+ local input
+ f_getvar 3:-\$$VAR_GROUP input "$1"
+
+ local group_name group_password group_gid group_members
+ if [ "$input" ] && ! f_input_group "$input"; then
+ f_show_err "$msg_group_not_found" "$input"
+ return $FAILURE
+ fi
+
+ #
+ # Loop until the user decides to Exit, Cancel, or presses ESC
+ #
+ title="$msg_delete $msg_group: $group_name"
+ if f_interactive; then
+ local mtag retval defaultitem=
+ while :; do
+ f_dialog_title "$title"
+ f_dialog_menu_group_delete "$group_name" "$defaultitem"
+ retval=$?
+ f_dialog_title_restore
+ f_dialog_menutag_fetch mtag
+ f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
+ defaultitem="$mtag"
+
+ # Return if user either pressed ESC or chose Cancel/No
+ [ $retval -eq $DIALOG_OK ] || return $FAILURE
+
+ case "$mtag" in
+ X) # Delete/Exit
+ local _group_name
+ f_shell_escape "$group_name" _group_name
+ f_eval_catch $funcname pw 'pw groupdel "%s"' \
+ "$_group_name" && break
+ ;;
+ 1) # Group Name (select different group from list)
+ f_dialog_menu_group_list "$group_name" || continue
+ f_dialog_menutag_fetch mtag
+
+ [ "$mtag" = "X $msg_exit" ] && continue
+
+ if ! f_input_group "$mtag"; then
+ f_show_err "$msg_group_not_found" "$mtag"
+ # Attempt to fall back to previous selection
+ f_input_group "$input" || return $FAILURE
+ else
+ input="$mtag"
+ fi
+ ;;
+ esac
+ done
+ else
+ local retval err _group_name
+ f_shell_escape "$group_name" _group_name
+ f_eval_catch -k err $funcname pw \
+ "pw groupdel '%s'" "$_group_name"
+ retval=$?
+ if [ $retval -ne $SUCCESS ]; then
+ f_show_err "%s" "$err"
+ return $retval
+ fi
+ fi
+
+ f_dialog_title "$title"
+ $alert "$msg_group_deleted"
+ f_dialog_title_restore
+ [ "$no_confirm" -a "$USE_DIALOG" ] && sleep 1
+
+ return $SUCCESS
+}
+
+# f_group_edit [$group]
+#
+# Modify a group. If both $group (as a first argument) and $VAR_GROUP are unset
+# or NULL and we are running interactively, prompt the user to select a group
+# from a list of available groups. Variables that can be used to script user
+# input:
+#
+# VAR_GROUP [Optional if running interactively]
+# The group to modify. Ignored if given non-NULL first-argument.
+# VAR_GROUP_GID [Optional]
+# Numerical group ID to set. If NULL or unset, the group ID is
+# unchanged.
+# VAR_GROUP_MEMBERS [Optional]
+# Comma separated list of users that are a member of this group.
+# If set but NULL, group memberships are reset (no users will be
+# a member of this group). If unset, group membership is
+# unmodified.
+# VAR_GROUP_PASSWORD [Optional]
+# newgrp(1) password to set for the group. If unset, the password
+# is unmodified. If NULL, the newgrp(1) password is disabled.
+#
+# Returns success if the group was successfully modified.
+#
+f_group_edit()
+{
+ local funcname=f_group_edit
+ local title # Calculated below
+ local alert=f_show_msg no_confirm=
+
+ f_getvar $VAR_NO_CONFIRM no_confirm
+ [ "$no_confirm" ] && alert=f_show_info
+
+ local input
+ f_getvar 3:-\$$VAR_GROUP input "$1"
+
+ #
+ # NB: pw(8) has a ``feature'' wherein `-n name' can be taken as GID
+ # instead of name. Work-around is to also pass `-g GID' at the same
+ # time (the GID is ignored in this case, so any GID will do).
+ #
+ if [ "$input" ] && ! f_quietly pw groupshow -n "$input" -g 1337; then
+ f_show_err "$msg_group_not_found" "$input"
+ return $FAILURE
+ fi
+
+ if f_interactive && [ ! "$input" ]; then
+ f_dialog_menu_group_list || return $SUCCESS
+ f_dialog_menutag_fetch input
+ [ "$input" = "X $msg_exit" ] && return $SUCCESS
+ elif [ ! "$input" ]; then
+ f_show_err "$msg_no_group_specified"
+ return $FAILURE
+ fi
+
+ local group_name group_password group_gid group_members
+ if ! f_input_group "$input"; then
+ f_show_err "$msg_group_not_found" "$input"
+ return $FAILURE
+ fi
+
+ f_isset $VAR_GROUP_GID && f_getvar $VAR_GROUP_GID group_gid
+ local null_members=
+ if f_isset $VAR_GROUP_MEMBERS; then
+ f_getvar $VAR_GROUP_MEMBERS group_members
+ [ "$group_members" ] || null_members=1
+ fi
+ local group_password_disable=
+ if f_isset $VAR_GROUP_PASSWORD; then
+ f_getvar $VAR_GROUP_PASSWORD group_password
+ [ "$group_password" ] || group_password_disable=1
+ fi
+
+ #
+ # Loop until the user decides to Exit, Cancel, or presses ESC
+ #
+ title="$msg_edit_view $msg_group: $group_name"
+ if f_interactive; then
+ local mtag retval defaultitem=
+ while :; do
+ f_dialog_title "$title"
+ f_dialog_menu_group_edit "$defaultitem"
+ retval=$?
+ f_dialog_title_restore
+ f_dialog_menutag_fetch mtag
+ f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
+ defaultitem="$mtag"
+
+ # Return if user either pressed ESC or chose Cancel/No
+ [ $retval -eq $DIALOG_OK ] || return $FAILURE
+
+ case "$mtag" in
+ X) # Save/Exit
+ local var
+ for var in gid members name; do
+ local _group_$var
+ eval f_shell_escape \
+ \"\$group_$var\" _group_$var
+ done
+
+ local cmd="pw groupmod -n '$_group_name'"
+ [ "$group_gid" ] && cmd="$cmd -g '$_group_gid'"
+ [ "$group_members" -o "$null_members" ] &&
+ cmd="$cmd -M '$_group_members'"
+
+ # Execute the command (break on success)
+ if [ "$group_password_disable" ]; then
+ f_eval_catch $funcname pw '%s -h -' "$cmd"
+ elif [ "$group_password" ]; then
+ echo "$group_password" | f_eval_catch \
+ $funcname pw '%s -h 0' "$cmd"
+ else
+ f_eval_catch $funcname pw '%s' "$cmd"
+ fi && break
+ ;;
+ 1) # Group Name (select different group from list)
+ f_dialog_menu_group_list "$group_name" || continue
+ f_dialog_menutag_fetch mtag
+
+ [ "$mtag" = "X $msg_exit" ] && continue
+
+ if ! f_input_group "$mtag"; then
+ f_show_err "$msg_group_not_found" "$mtag"
+ # Attempt to fall back to previous selection
+ f_input_group "$input" || return $FAILURE
+ else
+ input="$mtag"
+ fi
+ title="$msg_edit_view $msg_group: $group_name"
+ ;;
+ 2) # Password
+ f_dialog_input_group_password group_password \
+ group_password_disable
+ ;;
+ 3) # Group ID
+ f_dialog_input_group_gid group_gid "$group_gid"
+ ;;
+ 4) # Group Members
+ f_dialog_input_group_members group_members \
+ "$group_members" && [ ! "$group_members" ] &&
+ null_members=1
+ ;;
+ esac
+ done
+ else
+ local var
+ for var in gid members name; do
+ local _group_$var
+ eval f_shell_escape \"\$group_$var\" _group_$var
+ done
+
+ # Form the command
+ local cmd="pw groupmod -n '$_group_name'"
+ [ "$group_gid" ] && cmd="$cmd -g '$_group_gid'"
+ [ "$group_members" -o "$null_members" ] &&
+ cmd="$cmd -M '$_group_members'"
+
+ # Execute the command
+ local retval err
+ if [ "$group_password_disable" ]; then
+ f_eval_catch -k err $funcname pw '%s -h -' "$cmd"
+ elif [ "$group_password" -o "$null_password" ]; then
+ err=$( echo "$group_password" | f_eval_catch -de \
+ $funcname pw '%s -h 0' "$cmd" 2>&1 )
+ else
+ f_eval_catch -k err $funcname pw '%s' "$cmd"
+ fi
+ retval=$?
+ if [ $retval -ne $SUCCESS ]; then
+ f_show_err "%s" "$err"
+ return $retval
+ fi
+ fi
+
+ f_dialog_title "$title"
+ $alert "$msg_group_updated"
+ f_dialog_title_restore
+ [ "$no_confirm" -a "$USE_DIALOG" ] && sleep 1
+
+ return $SUCCESS
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." usermgmt/group.subr
+
+fi # ! $_USERMGMT_GROUP_SUBR
diff --git a/usr.sbin/bsdconfig/usermgmt/share/group_input.subr b/usr.sbin/bsdconfig/usermgmt/share/group_input.subr
new file mode 100644
index 0000000..2e8c086
--- /dev/null
+++ b/usr.sbin/bsdconfig/usermgmt/share/group_input.subr
@@ -0,0 +1,596 @@
+if [ ! "$_USERMGMT_GROUP_INPUT_SUBR" ]; then _USERMGMT_GROUP_INPUT_SUBR=1
+#
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012-2014 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." usermgmt/group_input.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/strings.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="070.usermgmt"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+############################################################ FUNCTIONS
+
+# f_input_group $group
+#
+# Given $group name or id, create the environment variables group_name,
+# group_gid, and group_members (and group_password is reset to NULL).
+#
+f_input_group()
+{
+ local funcname=f_input_group
+ local group="$1"
+
+ f_dprintf "$funcname: Getting info for group \`%s'" "$group"
+ eval "$( pw groupshow "$group" 2> /dev/null | awk -F: '
+ function set_value(var, value) {
+ gsub(/'\''/, "'\''\\'\'\''", value)
+ printf "group_%s='\'%s\''\n", var, value
+ }
+ {
+ found = $1 != ""
+ set_value("name", $1)
+ set_value("password", "")
+ set_value("gid", $3)
+ set_value("members", $4)
+ exit
+ }
+ END { if (!found) print "false" }' )"
+}
+
+# f_dialog_menu_group_list [$default]
+#
+# Allows the user to select a group from a list. Optionally, if present and
+# non-NULL, initially highlight $default group.
+#
+f_dialog_menu_group_list()
+{
+ local prompt=
+ local menu_list="
+ 'X $msg_exit' ''
+ " # END-QUOTE
+ local defaultitem="$1"
+ local hline="$hline_alnum_punc_tab_enter"
+
+ # Add groups from group(5)
+ menu_list="$menu_list $( pw groupshow -a | awk -F: '
+ function mprint(tag, item) {
+ gsub(/'\''/, "'\''\\'\'\''", tag)
+ gsub(/'\''/, "'\''\\'\'\''", item)
+ printf "'\'%s\'\ \'%s\''\n", tag, item
+ }
+ !/^[[:space:]]*(#|$)/ { mprint($1, $1) }
+ ' )"
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_menutag_store -s "$menu_choice"
+ return $retval
+}
+
+# f_dialog_input_group_name $var_to_set [$group_name]
+#
+# Allows the user to enter a name for a new group. If the user does not cancel
+# or press ESC, the $var_to_set variable will hold the newly-configured value
+# upon return.
+#
+f_dialog_input_group_name()
+{
+ local __var_to_set="$1" __name="$2"
+
+ #
+ # Loop until the user provides taint-free/valid input
+ #
+ local __input="$__name"
+ while :; do
+ # Return if user has either pressed ESC or chosen Cancel/No
+ f_dialog_input __input "$msg_group" "$__input" \
+ "$hline_alnum_tab_enter" || return $?
+
+ # Check for no-change
+ if [ "$__input" = "$__name" ]; then
+ setvar "$__var_to_set" "$__input"
+ return $DIALOG_OK
+ fi
+
+ # Check for NULL entry
+ if [ ! "$__input" ]; then
+ f_show_msg "$msg_group_is_empty"
+ continue
+ fi
+
+ # Check for invalid entry
+ case "$__input" in [!a-zA-Z]*)
+ f_show_msg "$msg_group_must_start_with_letter"
+ continue
+ esac
+
+ # Check for duplicate entry
+ if f_quietly pw groupshow -n "$__input"; then
+ f_show_msg "$msg_group_already_used" "$__input"
+ continue
+ fi
+
+ setvar "$__var_to_set" "$__input"
+ break
+ done
+
+ return $DIALOG_OK
+}
+
+# f_dialog_input_group_password $var_to_set $dvar_to_set
+#
+# Prompt the user to enter a password (twice). If the user does not cancel or
+# press ESC, $var_to_set will hold the confirmed user entry. Otherwise, if the
+# user cancels or enters a NULL password (twice), they are given the choice to
+# disable password authentication for the given group, wherein $dvar_to_set has
+# a value of 1 to indicate password authentication should be disabled.
+#
+f_dialog_input_group_password()
+{
+ local __var_to_set="$1" __dvar_to_set="$2"
+ local __prompt1="$msg_group_password"
+ local __prompt2="$msg_reenter_group_password"
+ local __hline="$hline_alnum_punc_tab_enter"
+
+ local __height1 __width1
+ f_dialog_inputbox_size __height1 __width1 \
+ "$DIALOG_TITLE" \
+ "$DIALOG_BACKTITLE" \
+ "$__prompt1" \
+ "" \
+ "$__hline"
+
+ local __height2 __width2
+ f_dialog_inputbox_size __height2 __width2 \
+ "$DIALOG_TITLE" \
+ "$DIALOG_BACKTITLE" \
+ "$__prompt2" \
+ "" \
+ "$__hline"
+
+ #
+ # Loop until the user provides taint-free/valid input
+ #
+ local __retval __password1 __password2
+ while :; do
+ __password1=$( $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --hline "$__hline" \
+ --ok-label "$msg_ok" \
+ --cancel-label "$msg_cancel" \
+ --insecure \
+ --passwordbox "$__prompt1" \
+ $__height1 $__width1 \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ __retval=$?
+ debug= f_dialog_line_sanitize __password1
+
+ # Return if user has either pressed ESC or chosen Cancel/No
+ [ $__retval -eq $DIALOG_OK ] || return $__retval
+
+ __password2=$( $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --hline "$__hline" \
+ --ok-label "$msg_ok" \
+ --cancel-label "$msg_cancel" \
+ --insecure \
+ --passwordbox "$__prompt2" \
+ $__height2 $__width2 \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ __retval=$?
+ debug= f_dialog_line_sanitize __password2
+
+ # Return if user has either pressed ESC or chosen Cancel/No
+ [ $__retval -eq $DIALOG_OK ] || return $__retval
+
+ # Check for password mismatch
+ if [ "$__password1" != "$__password2" ]; then
+ f_show_msg "$msg_group_passwords_do_not_match"
+ continue
+ fi
+
+ # Check for NULL entry
+ if [ ! "$__password1" ]; then
+ f_dialog_yesno "$msg_disable_password_auth_for_group"
+ __retval=$?
+ if [ $__retval -eq $DIALOG_ESC ]; then
+ return $__retval
+ elif [ $__retval -eq $DIALOG_OK ]; then
+ setvar "$__dvar_to_set" 1
+ else
+ continue # back to password prompt
+ fi
+ else
+ setvar "$__dvar_to_set" ""
+ fi
+
+ setvar "$__var_to_set" "$__password1"
+ break
+ done
+
+ return $DIALOG_OK
+}
+
+# f_dialog_input_group_gid $var_to_set [$group_gid]
+#
+# Allow the user to enter a new GID for a given group. If the user does not
+# cancel or press ESC, the $var_to_set variable will hold the newly-configured
+# value upon return.
+#
+f_dialog_input_group_gid()
+{
+ local __var_to_set="$1" __input="$2"
+
+ # Return if user has either pressed ESC or chosen Cancel/No
+ f_dialog_input __input "$msg_group_id_leave_empty_for_default" \
+ "$__input" "$hline_num_tab_enter" || return $?
+
+ setvar "$__var_to_set" "$__input"
+ return $DIALOG_OK
+}
+
+# f_dialog_input_group_members $var_to_set [$group_members]
+#
+# Allow the user to modify a list of members for a given group. If the user
+# does not cancel or press ESC, the $var_to_set variable will hold the newly-
+# configured value upon return.
+#
+f_dialog_input_group_members()
+{
+ local __var_to_set="$1" __input="$2"
+ local __prompt="$msg_group_members:"
+ local __menu_list="
+ 'X' '$msg_continue'
+ '1' '$msg_select_group_members_from_list'
+ '2' '$msg_enter_group_members_manually'
+ " # END-QUOTE
+ local __defaultitem=
+ local __hline="$hline_num_arrows_tab_enter"
+
+ local __mheight __mwidth __mrows
+ eval f_dialog_menu_size __mheight __mwidth __mrows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$__prompt\" \
+ \"\$__hline\" \
+ $__menu_list
+
+ local __menu_choice __retval
+ while :; do
+ __menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$__hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$__defaultitem\" \
+ --menu \"\$__prompt\" \
+ $__mheight $__mwidth $__mrows \
+ $__menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ __retval=$?
+ f_dialog_data_sanitize __menu_choice
+ __defaultitem="$__menu_choice"
+ f_dprintf "retval=%u menu_choice=[%s]" \
+ $__retval "$__menu_choice"
+
+ # Return if user has either pressed ESC or chosen Cancel/No
+ [ $__retval -eq $DIALOG_OK ] || return $__retval
+
+ local __group_members
+ case "$__menu_choice" in
+ X) # Exit
+ break ;;
+ 1) # Select Group Members from a list
+ local __check_list= # Calculated below
+ local __user_list __u __user __length=0
+ __user_list=$( pw usershow -a |
+ awk -F: '!/^[[:space:]]*(#|$)/{print $1}' )
+ while [ $__length -ne ${#__user_list} ]; do
+ __u="${__user_list%%$NL*}" # First line
+ f_shell_escape "$__u" __user
+
+ # Format of a checklist entry: tag item status
+ __check_list="$__check_list '$__user' ''"
+ case "$__input" in
+ "$__u"|"$__u",*|*,"$__u",*|*,"$__u")
+ __check_list="$__check_list on" ;;
+ *)
+ __check_list="$__check_list off"
+ esac
+
+ __length=${#__user_list}
+ __user_list="${__user_list#*$NL}" # Kill line
+ done
+
+ local __cheight __cwidth __crows
+ eval f_dialog_checklist_size \
+ __cheight __cwidth __crows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$__prompt\" \
+ \"\$__hline\" \
+ $__check_list
+ __group_members=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --separate-output \
+ --hline \"\$__hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --checklist \"\$__prompt\" \
+ $__cheight $__cwidth $__crows \
+ $__check_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ ) || continue
+ # Return to previous menu if user either
+ # pressed ESC or chose Cancel/No
+ f_dialog_data_sanitize __group_members
+
+ #
+ # Convert the newline separated list into a comma-
+ # separated one so that if the user switches over to
+ # manual editing, list reflects checklist selections
+ #
+ f_replaceall "$__group_members" "[$NL]" "," __input
+ ;;
+ 2) # Enter Group Members manually
+ local __prompt2="$msg_group_members"
+ __prompt2="$__prompt2 ($msg_separated_by_commas)"
+
+ f_dialog_input __group_members \
+ "$__prompt2" "$__input" \
+ "$hline_num_tab_enter" || continue
+ # Return to previous menu if user either
+ # pressed ESC or chose Cancel/No
+
+ __input="$__group_members"
+ ;;
+ esac
+ done
+
+ setvar "$__var_to_set" "$__input"
+ return $DIALOG_OK
+}
+
+# f_dialog_menu_group_add [$defaultitem]
+#
+# Present a menu detailing the properties of a group that is about to be added.
+# The user's menu choice is available using f_dialog_menutag_fetch(). Returns
+# success unless the user chose Cancel or pressed ESC. Data to display is taken
+# from environment variables group_name, group_gid, and group_members. If
+# $defaultitem is present and non-NULL, initially highlight the item in the
+# menu.
+#
+f_dialog_menu_group_add()
+{
+ local prompt="$msg_save_exit_or_cancel"
+ local menu_list # Calculated below
+ local defaultitem="$1"
+ local hline="$hline_arrows_tab_enter"
+
+ # Localize potentially hostile variables and escape their values
+ # to the local variable (see f_shell_escape() of `strings.subr')
+ local var
+ for var in gid members name; do
+ local _group_$var
+ eval f_shell_escape \"\$group_$var\" _group_$var
+ done
+
+ menu_list="
+ 'X' '$msg_add/$msg_exit'
+ '1' '$msg_group: $_group_name'
+ '2' '$msg_password: -----'
+ '3' '$msg_group_id: $_group_gid'
+ '4' '$msg_group_members: $_group_members'
+ " # END-QUOTE
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize menu_choice
+ f_dialog_menutag_store "$menu_choice"
+ return $retval
+}
+
+# f_dialog_menu_group_delete $group [$defaultitem]
+#
+# Present a menu detailing the properties of a group that is about to be
+# deleted. The user's menu choice is available using f_dialog_menutag_fetch().
+# Returns success unless the user chose Cancel or pressed ESC. Data to display
+# is populated automatically from the system accounting database for the given
+# $group argument. If $defaultitem is present and non-NULL, initially highlight
+# the item in the menu.
+#
+f_dialog_menu_group_delete()
+{
+ local prompt="$msg_delete_exit_or_cancel"
+ local menu_list # Calculated below
+ local defaultitem="$2"
+ local hline="$hline_arrows_tab_enter"
+
+ local group_name group_password group_gid group_members
+ f_input_group "$1"
+
+ # Localize potentially hostile variables and escape their values
+ # to the local variable (see f_shell_escape() of `strings.subr')
+ local var
+ for var in gid members name; do
+ local _group_$var
+ eval f_shell_escape \"\$group_$var\" _group_$var
+ done
+
+ menu_list="
+ 'X' '$msg_delete/$msg_exit'
+ '1' '$msg_group: $_group_name'
+ '-' '$msg_password: -----'
+ '-' '$msg_group_id: $_group_gid'
+ '-' '$msg_group_members: $_group_members'
+ " # END-QUOTE
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize menu_choice
+ f_dialog_menutag_store "$menu_choice"
+ return $retval
+}
+
+# f_dialog_menu_group_edit [$defaultitem]
+#
+# Present a menu detailing the properties of a group that is about to be
+# modified. The user's menu choice is available using f_dialog_menutag_fetch().
+# Returns success unless the user chose Cancel or pressed ESC. Data to display
+# is taken from environment variables group_name, group_gid, and group_members.
+# If $defaultitem is present and non-NULL, initially highlight the item in the
+# menu.
+#
+f_dialog_menu_group_edit()
+{
+ local prompt="$msg_save_exit_or_cancel"
+ local menu_list # Calculated below
+ local defaultitem="$1"
+ local hline="$hline_arrows_tab_enter"
+
+ # Localize potentially hostile variables and escape their values
+ # to the local variable (see f_shell_escape() of `strings.subr')
+ local var
+ for var in gid members name; do
+ local _group_$var
+ eval f_shell_escape \"\$group_$var\" _group_$var
+ done
+
+ menu_list="
+ 'X' '$msg_save/$msg_exit'
+ '1' '$msg_group: $_group_name'
+ '2' '$msg_password: -----'
+ '3' '$msg_group_id: $_group_gid'
+ '4' '$msg_group_members: $_group_members'
+ " # END-QUOTE
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize menu_choice
+ f_dialog_menutag_store "$menu_choice"
+ return $retval
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." usermgmt/group_input.subr
+
+fi # ! $_USERMGMT_GROUP_INPUT_SUBR
diff --git a/usr.sbin/bsdconfig/usermgmt/share/user.subr b/usr.sbin/bsdconfig/usermgmt/share/user.subr
new file mode 100644
index 0000000..27d9d66
--- /dev/null
+++ b/usr.sbin/bsdconfig/usermgmt/share/user.subr
@@ -0,0 +1,1183 @@
+if [ ! "$_USERMGMT_USER_SUBR" ]; then _USERMGMT_USER_SUBR=1
+#
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012-2015 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." usermgmt/user.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/strings.subr
+f_include $BSDCFG_SHARE/usermgmt/group_input.subr
+f_include $BSDCFG_SHARE/usermgmt/user_input.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="070.usermgmt"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+############################################################ CONFIGURATION
+
+# set some reasonable defaults if /etc/adduser.conf does not exist.
+[ -f /etc/adduser.conf ] && f_include /etc/adduser.conf
+: ${defaultclass:=""}
+: ${defaultshell:="/bin/sh"}
+: ${homeprefix:="/home"}
+: ${passwdtype:="yes"}
+: ${udotdir:="/usr/share/skel"}
+: ${uexpire:=""}
+ # Default account expire time. Format is similar to upwexpire variable.
+: ${ugecos:="User &"}
+: ${upwexpire:=""}
+ # The default password expiration time. Format of the date is either a
+ # UNIX time in decimal, or a date in dd-mmm-yy[yy] format, where dd is
+ # the day, mmm is the month in either numeric or alphabetic format, and
+ # yy[yy] is either a two or four digit year. This variable also accepts
+ # a relative date in the form of n[mhdwoy] where n is a decimal, octal
+ # (leading 0) or hexadecimal (leading 0x) digit followed by the number
+ # of Minutes, Hours, Days, Weeks, Months or Years from the current date
+ # at which the expiration time is to be set.
+
+#
+# uexpire and upwexpire from adduser.conf(5) differ only slightly from what
+# pw(8) accepts as `date' argument(s); pw(8) requires a leading `+' for the
+# relative date syntax (n[mhdwoy]).
+#
+case "$uexpire" in *[mhdwoy])
+ f_isinteger "${uexpire%[mhdwoy]}" && uexpire="+$uexpire"
+esac
+case "$upwexpire" in *[mhdwoy])
+ f_isinteger "${upwexpire%[mhdwoy]}" && upwexpire="+$upwexpire"
+esac
+
+############################################################ FUNCTIONS
+
+# f_user_create_homedir $user
+#
+# Create home directory for $user.
+#
+f_user_create_homedir()
+{
+ local funcname=f_user_create_homedir
+ local user="$1"
+
+ [ "$user" ] || return $FAILURE
+
+ local user_account_expire user_class user_gecos user_gid user_home_dir
+ local user_member_groups user_name user_password user_password_expire
+ local user_shell user_uid # Variables created by f_input_user() below
+ f_input_user "$user" || return $FAILURE
+
+ f_dprintf "Creating home directory \`%s' for user \`%s'" \
+ "$user_home_dir" "$user"
+
+ local _user_gid _user_home_dir _user_uid
+ f_shell_escape "$user_gid" _user_gid
+ f_shell_escape "$user_home_dir" _user_home_dir
+ f_shell_escape "$user_uid" _user_uid
+ f_eval_catch $funcname mkdir "mkdir -p '%s'" "$_user_home_dir" ||
+ return $FAILURE
+ f_eval_catch $funcname chown "chown '%i:%i' '%s'" \
+ "$_user_uid" "$_user_gid" "$_user_home_dir" || return $FAILURE
+}
+
+# f_user_copy_dotfiles $user
+#
+# Copy `skel' dot-files from $udotdir (global inherited from /etc/adduser.conf)
+# to the home-directory of $user. Attempts to create the home-directory first
+# if it doesn't exist.
+#
+f_user_copy_dotfiles()
+{
+ local funcname=f_user_copy_dotfiles
+ local user="$1"
+
+ [ "$udotdir" ] || return $FAILURE
+ [ "$user" ] || return $FAILURE
+
+ local user_account_expire user_class user_gecos user_gid user_home_dir
+ local user_member_groups user_name user_password user_password_expire
+ local user_shell user_uid # Variables created by f_input_user() below
+ f_input_user "$user" || return $FAILURE
+
+ f_dprintf "Copying dot-files from \`%s' to \`%s'" \
+ "$udotdir" "$user_home_dir"
+
+ # Attempt to create the home directory if it doesn't exist
+ [ -d "$user_home_dir" ] ||
+ f_user_create_homedir "$user" || return $FAILURE
+
+ local _user_gid _user_home_dir _user_uid
+ f_shell_escape "$user_gid" _user_gid
+ f_shell_escape "$user_home_dir" _user_home_dir
+ f_shell_escape "$user_uid" _user_uid
+
+ local - # Localize `set' to this function
+ set +f # Enable glob pattern-matching for paths
+ cd "$udotdir" || return $FAILURE
+
+ local _file file retval
+ for file in dot.*; do
+ [ -e "$file" ] || continue # no-match
+
+ f_shell_escape "$file" "_file"
+ f_eval_catch $funcname cp "cp -n '%s' '%s'" \
+ "$_file" "$_user_home_dir/${_file#dot}"
+ retval=$?
+ [ $retval -eq $SUCCESS ] || break
+ f_eval_catch $funcname chown \
+ "chown -h '%i:%i' '%s'" \
+ "$_user_uid" "$_user_gid" \
+ "$_user_home_dir/${_file#dot}"
+ retval=$?
+ [ $retval -eq $SUCCESS ] || break
+ done
+
+ cd -
+ return $retval
+}
+
+# f_user_add [$user]
+#
+# Create a login account. If both $user (as a first argument) and $VAR_USER are
+# unset or NULL and we are running interactively, prompt the end-user to enter
+# the name of a new login account and (if $VAR_NO_CONFIRM is unset or NULL)
+# prompt the end-user to answer some questions about the new account. Variables
+# that can be used to script user input:
+#
+# VAR_USER [Optional if running interactively]
+# The login to add. Ignored if given non-NULL first-argument.
+# VAR_USER_ACCOUNT_EXPIRE [Optional]
+# The account expiration time. Format is similar to
+# VAR_USER_PASSWORD_EXPIRE variable below. Default is to never
+# expire the account.
+# VAR_USER_DOTFILES_CREATE [Optional]
+# If non-NULL, populate the user's home directory with the
+# template files found in $udotdir (`/usr/share/skel' default).
+# VAR_USER_GECOS [Optional]
+# Often the full name of the account holder. Default is NULL.
+# VAR_USER_GID [Optional]
+# Numerical primary-group ID to use. If NULL or unset, the group
+# ID is automatically chosen.
+# VAR_USER_GROUPS [Optional]
+# Comma-separated list of additional groups to which the user is
+# a member of. Default is NULL (no additional groups).
+# VAR_USER_HOME [Optional]
+# The home directory to set. If NULL or unset, the home directory
+# is automatically calculated.
+# VAR_USER_HOME_CREATE [Optional]
+# If non-NULL, create the user's home directory if it doesn't
+# already exist.
+# VAR_USER_LOGIN_CLASS [Optional]
+# Login class to use when creating the login. Default is NULL.
+# VAR_USER_PASSWORD [Optional]
+# Unencrypted password to use. If unset or NULL, password
+# authentication for the login is disabled.
+# VAR_USER_PASSWORD_EXPIRE [Optional]
+# The password expiration time. Format of the date is either a
+# UNIX time in decimal, or a date in dd-mmm-yy[yy] format, where
+# dd is the day, mmm is the month in either numeric or alphabetic
+# format, and yy[yy] is either a two or four digit year. This
+# variable also accepts a relative date in the form of +n[mhdwoy]
+# where n is a decimal, octal (leading 0) or hexadecimal (leading
+# 0x) digit followed by the number of Minutes, Hours, Days,
+# Weeks, Months or Years from the current date at which the
+# expiration time is to be set. Default is to never expire the
+# account password.
+# VAR_USER_SHELL [Optional]
+# Path to login shell to use. Default is `/bin/sh'.
+# VAR_USER_UID [Optional]
+# Numerical user ID to use. If NULL or unset, the user ID is
+# automatically chosen.
+#
+# Returns success if the user account was successfully created.
+#
+f_user_add()
+{
+ local funcname=f_user_add
+ local title # Calculated below
+ local alert=f_show_msg no_confirm=
+
+ f_getvar $VAR_NO_CONFIRM no_confirm
+ [ "$no_confirm" ] && alert=f_show_info
+
+ local input
+ f_getvar 3:-\$$VAR_USER input "$1"
+
+ #
+ # NB: pw(8) has a ``feature'' wherein `-n name' can be taken as UID
+ # instead of name. Work-around is to also pass `-u UID' at the same
+ # time (the UID is ignored in this case, so any UID will do).
+ #
+ if [ "$input" ] && f_quietly pw usershow -n "$input" -u 1337; then
+ f_show_err "$msg_login_already_used" "$input"
+ return $FAILURE
+ fi
+
+ local user_name="$input"
+ while f_interactive && [ ! "$user_name" ]; do
+ f_dialog_input_name user_name "$user_name" ||
+ return $SUCCESS
+ [ "$user_name" ] ||
+ f_show_err "$msg_please_enter_a_user_name"
+ done
+ if [ ! "$user_name" ]; then
+ f_show_err "$msg_no_user_specified"
+ return $FAILURE
+ fi
+
+ local user_account_expire user_class user_gecos user_gid user_home_dir
+ local user_member_groups user_password user_password_expire user_shell
+ local user_uid user_dotfiles_create= user_home_create=
+ f_getvar $VAR_USER_ACCOUNT_EXPIRE-\$uexpire user_account_expire
+ f_getvar $VAR_USER_DOTFILES_CREATE:+\$msg_yes user_dotfiles_create
+ f_getvar $VAR_USER_GECOS-\$ugecos user_gecos
+ f_getvar $VAR_USER_GID user_gid
+ f_getvar $VAR_USER_GROUPS user_member_groups
+ f_getvar $VAR_USER_HOME:-\${homeprefix%/}/\$user_name \
+ user_home_dir
+ f_getvar $VAR_USER_HOME_CREATE:+\$msg_yes user_home_create
+ f_getvar $VAR_USER_LOGIN_CLASS-\$defaultclass user_class
+ f_getvar $VAR_USER_PASSWORD user_password
+ f_getvar $VAR_USER_PASSWORD_EXPIRE-\$upwexpire user_password_expire
+ f_getvar $VAR_USER_SHELL-\$defaultshell user_shell
+ f_getvar $VAR_USER_UID user_uid
+
+ # Create home-dir if no script-override and does not exist
+ f_isset $VAR_USER_HOME_CREATE || [ -d "$user_home_dir" ] ||
+ user_home_create="$msg_yes"
+ # Copy dotfiles if home-dir creation is desired, does not yet exist,
+ # and no script-override has been set
+ f_isset $VAR_USER_DOTFILES_CREATE ||
+ [ "$user_home_create" != "$msg_yes" ] ||
+ [ -d "$user_home_dir" ] || user_dotfiles_create="$msg_yes"
+ # Create home-dir if copying dotfiles but home-dir does not exist
+ [ "$user_dotfiles_create" -a ! -d "$user_home_dir" ] &&
+ user_home_create="$msg_yes"
+
+ # Set flags for meaningful NULL values if-provided
+ local no_account_expire= no_password_expire= null_gecos= null_members=
+ local user_password_disable=
+ f_isset $VAR_USER_ACCOUNT_EXPIRE &&
+ [ ! "$user_account_expire" ] && no_account_expire=1
+ f_isset $VAR_USER_GECOS &&
+ [ ! "$user_gecos" ] && null_gecos=1
+ f_isset $VAR_USER_GROUPS &&
+ [ ! "$user_member_groups" ] && null_members=1
+ f_isset $VAR_USER_PASSWORD &&
+ [ ! "$user_password" ] && user_password_disable=1
+ f_isset $VAR_USER_PASSWORD_EXPIRE &&
+ [ ! "$user_password_expire" ] && no_password_expire=1
+
+ if f_interactive && [ ! "$no_confirm" ]; then
+ f_dialog_noyes \
+ "$msg_use_default_values_for_all_account_details"
+ retval=$?
+ if [ $retval -eq $DIALOG_ESC ]; then
+ return $SUCCESS
+ elif [ $retval -ne $DIALOG_OK ]; then
+ #
+ # Ask series of questions to pre-fill the editor screen
+ #
+ # Defaults used in each dialog should allow the user to
+ # simply hit ENTER to proceed, because cancelling any
+ # single dialog will cause them to be returned to the
+ # previous menu.
+ #
+
+ f_dialog_input_gecos user_gecos "$user_gecos" ||
+ return $FAILURE
+ if [ "$passwdtype" = "yes" ]; then
+ f_dialog_input_password user_password \
+ user_password_disable ||
+ return $FAILURE
+ fi
+ f_dialog_input_uid user_uid "$user_uid" ||
+ return $FAILURE
+ f_dialog_input_gid user_gid "$user_gid" ||
+ return $FAILURE
+ f_dialog_input_member_groups user_member_groups \
+ "$user_member_groups" || return $FAILURE
+ f_dialog_input_class user_class "$user_class" ||
+ return $FAILURE
+ f_dialog_input_expire_password user_password_expire \
+ "$user_password_expire" || return $FAILURE
+ f_dialog_input_expire_account user_account_expire \
+ "$user_account_expire" || return $FAILURE
+ f_dialog_input_home_dir user_home_dir \
+ "$user_home_dir" || return $FAILURE
+ if [ ! -d "$user_home_dir" ]; then
+ f_dialog_input_home_create user_home_create ||
+ return $FAILURE
+ if [ "$user_home_create" = "$msg_yes" ]; then
+ f_dialog_input_dotfiles_create \
+ user_dotfiles_create ||
+ return $FAILURE
+ fi
+ fi
+ f_dialog_input_shell user_shell "$user_shell" ||
+ return $FAILURE
+ fi
+ fi
+
+ #
+ # Loop until the user decides to Exit, Cancel, or presses ESC
+ #
+ title="$msg_add $msg_user: $user_name"
+ if f_interactive; then
+ local mtag retval defaultitem=
+ while :; do
+ f_dialog_title "$title"
+ f_dialog_menu_user_add "$defaultitem"
+ retval=$?
+ f_dialog_title_restore
+ f_dialog_menutag_fetch mtag
+ f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
+ defaultitem="$mtag"
+
+ # Return if user either pressed ESC or chose Cancel/No
+ [ $retval -eq $DIALOG_OK ] || return $FAILURE
+
+ case "$mtag" in
+ X) # Add/Exit
+ local var
+ for var in account_expire class gecos gid home_dir \
+ member_groups name password_expire shell uid \
+ ; do
+ local _user_$var
+ eval f_shell_escape \"\$user_$var\" _user_$var
+ done
+
+ local cmd="pw useradd -n '$_user_name'"
+ [ "$user_gid" ] && cmd="$cmd -g '$_user_gid'"
+ [ "$user_shell" ] && cmd="$cmd -s '$_user_shell'"
+ [ "$user_uid" ] && cmd="$cmd -u '$_user_uid'"
+ [ "$user_account_expire" -o \
+ "$no_account_expire" ] &&
+ cmd="$cmd -e '$_user_account_expire'"
+ [ "$user_class" -o "$null_class" ] &&
+ cmd="$cmd -L '$_user_class'"
+ [ "$user_gecos" -o "$null_gecos" ] &&
+ cmd="$cmd -c '$_user_gecos'"
+ [ "$user_home_dir" ] &&
+ cmd="$cmd -d '$_user_home_dir'"
+ [ "$user_member_groups" ] &&
+ cmd="$cmd -G '$_user_member_groups'"
+ [ "$user_password_expire" -o \
+ "$no_password_expire" ] &&
+ cmd="$cmd -p '$_user_password_expire'"
+
+ # Execute the command
+ if [ "$user_password_disable" ]; then
+ f_eval_catch $funcname pw '%s -h -' "$cmd"
+ elif [ "$user_password" ]; then
+ echo "$user_password" | f_eval_catch \
+ $funcname pw '%s -h 0' "$cmd"
+ else
+ f_eval_catch $funcname pw '%s' "$cmd"
+ fi || continue
+
+ # Create home directory if desired
+ [ "${user_home_create:-$msg_no}" != "$msg_no" ] &&
+ f_user_create_homedir "$user_name"
+
+ # Copy dotfiles if desired
+ [ "${user_dotfiles_create:-$msg_no}" != \
+ "$msg_no" ] && f_user_copy_dotfiles "$user_name"
+
+ break # to success
+ ;;
+ 1) # Login (prompt for new login name)
+ f_dialog_input_name input "$user_name" ||
+ continue
+ if f_quietly pw usershow -n "$input" -u 1337; then
+ f_show_err "$msg_login_already_used" "$input"
+ continue
+ fi
+ user_name="$input"
+ title="$msg_add $msg_user: $user_name"
+ user_home_dir="${homeprefix%/}/$user_name"
+ ;;
+ 2) # Full Name
+ f_dialog_input_gecos user_gecos "$user_gecos" &&
+ [ ! "$user_gecos" ] && null_gecos=1 ;;
+ 3) # Password
+ f_dialog_input_password \
+ user_password user_password_disable ;;
+ 4) # User ID
+ f_dialog_input_uid user_uid "$user_uid" ;;
+ 5) # Group ID
+ f_dialog_input_gid user_gid "$user_gid" ;;
+ 6) # Member of Groups
+ f_dialog_input_member_groups \
+ user_member_groups "$user_member_groups" &&
+ [ ! "$user_member_groups" ] &&
+ null_members=1 ;;
+ 7) # Login Class
+ f_dialog_input_class user_class "$user_class" &&
+ [ ! "$user_class" ] && null_class=1 ;;
+ 8) # Password Expires On
+ f_dialog_input_expire_password \
+ user_password_expire "$user_password_expire" &&
+ [ ! "$user_password_expire" ] &&
+ no_password_expire=1 ;;
+ 9) # Account Expires On
+ f_dialog_input_expire_account \
+ user_account_expire "$user_account_expire" &&
+ [ ! "$user_account_expire" ] &&
+ no_account_expire=1 ;;
+ A) # Home Directory
+ f_dialog_input_home_dir \
+ user_home_dir "$user_home_dir" ;;
+ B) # Shell
+ f_dialog_input_shell user_shell "$user_shell" ;;
+ C) # Create Home Directory?
+ if [ "${user_home_create:-$msg_no}" != "$msg_no" ]
+ then
+ user_home_create="$msg_no"
+ else
+ user_home_create="$msg_yes"
+ fi ;;
+ D) # Create Dotfiles?
+ if [ "${user_dotfiles_create:-$msg_no}" != \
+ "$msg_no" ]
+ then
+ user_dotfiles_create="$msg_no"
+ else
+ user_dotfiles_create="$msg_yes"
+ fi ;;
+ esac
+ done
+ else
+ local var
+ for var in account_expire class gecos gid home_dir \
+ member_groups name password_expire shell uid \
+ ; do
+ local _user_$var
+ eval f_shell_escape \"\$user_$var\" _user_$var
+ done
+
+ # Form the command
+ local cmd="pw useradd -n '$_user_name'"
+ [ "$user_gid" ] && cmd="$cmd -g '$_user_gid'"
+ [ "$user_home_dir" ] && cmd="$cmd -d '$_user_home_dir'"
+ [ "$user_shell" ] && cmd="$cmd -s '$_user_shell'"
+ [ "$user_uid" ] && cmd="$cmd -u '$_user_uid'"
+ [ "$user_account_expire" -o "$no_account_expire" ] &&
+ cmd="$cmd -e '$_user_account_expire'"
+ [ "$user_class" -o "$null_class" ] &&
+ cmd="$cmd -L '$_user_class'"
+ [ "$user_gecos" -o "$null_gecos" ] &&
+ cmd="$cmd -c '$_user_gecos'"
+ [ "$user_member_groups" -o "$null_members" ] &&
+ cmd="$cmd -G '$_user_member_groups'"
+ [ "$user_password_expire" -o "$no_password_expire" ] &&
+ cmd="$cmd -p '$_user_password_expire'"
+
+ # Execute the command
+ local retval err
+ if [ "$user_password_disable" ]; then
+ f_eval_catch -k err $funcname pw '%s -h -' "$cmd"
+ elif [ "$user_password" ]; then
+ err=$( echo "$user_password" | f_eval_catch -de \
+ $funcname pw '%s -h 0' "$cmd" 2>&1 )
+ else
+ f_eval_catch -k err $funcname pw '%s' "$cmd"
+ fi
+ retval=$?
+ if [ $retval -ne $SUCCESS ]; then
+ f_show_err "%s" "$err"
+ return $retval
+ fi
+
+ # Create home directory if desired
+ [ "${user_home_create:-$msg_no}" != "$msg_no" ] &&
+ f_user_create_homedir "$user_name"
+
+ # Copy dotfiles if desired
+ [ "${user_dotfiles_create:-$msg_no}" != "$msg_no" ] &&
+ f_user_copy_dotfiles "$user_name"
+ fi
+
+ f_dialog_title "$title"
+ $alert "$msg_login_added"
+ f_dialog_title_restore
+ [ "$no_confirm" -a "$USE_DIALOG" ] && sleep 1
+
+ return $SUCCESS
+}
+
+# f_user_delete [$user]
+#
+# Delete a user. If both $user (as a first argument) and $VAR_USER are unset or
+# NULL and we are running interactively, prompt the end-user to select a user
+# account from a list of those available. Variables that can be used to script
+# user input:
+#
+# VAR_USER [Optional if running interactively]
+# The user to delete. Ignored if given non-NULL first-argument.
+#
+# Returns success if the user account was successfully deleted.
+#
+f_user_delete()
+{
+ local funcname=f_user_delete
+ local title # Calculated below
+ local alert=f_show_msg no_confirm=
+
+ f_getvar $VAR_NO_CONFIRM no_confirm
+ [ "$no_confirm" ] && alert=f_show_info
+
+ local input
+ f_getvar 3:-\$$VAR_USER input "$1"
+
+ if f_interactive && [ ! "$input" ]; then
+ f_dialog_menu_user_list || return $SUCCESS
+ f_dialog_menutag_fetch input
+ [ "$input" = "X $msg_exit" ] && return $SUCCESS
+ elif [ ! "$input" ]; then
+ f_show_err "$msg_no_user_specified"
+ return $FAILURE
+ fi
+
+ local user_account_expire user_class user_gecos user_gid user_home_dir
+ local user_member_groups user_name user_password user_password_expire
+ local user_shell user_uid # Variables created by f_input_user() below
+ if [ "$input" ] && ! f_input_user "$input"; then
+ f_show_err "$msg_login_not_found" "$input"
+ return $FAILURE
+ fi
+
+ local user_group_delete= user_home_delete=
+ f_getvar $VAR_USER_GROUP_DELETE:-\$msg_no user_group_delete
+ f_getvar $VAR_USER_HOME_DELETE:-\$msg_no user_home_delete
+
+ # Attempt to translate user GID into a group name
+ local user_group
+ if user_group=$( pw groupshow -g "$user_gid" 2> /dev/null ); then
+ user_group="${user_group%%:*}"
+ # Default to delete the primary group if no script-override and
+ # exists with same name as the user (same logic used by pw(8))
+ f_isset $VAR_USER_GROUP_DELETE ||
+ [ "$user_group" != "$user_name" ] ||
+ user_group_delete="$msg_yes"
+ fi
+
+ #
+ # Loop until the user decides to Exit, Cancel, or presses ESC
+ #
+ title="$msg_delete $msg_user: $user_name"
+ if f_interactive; then
+ local mtag retval defaultitem=
+ while :; do
+ f_dialog_title "$title"
+ f_dialog_menu_user_delete "$user_name" "$defaultitem"
+ retval=$?
+ f_dialog_title_restore
+ f_dialog_menutag_fetch mtag
+ f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
+ defaultitem="$mtag"
+
+ # Return if user either pressed ESC or chose Cancel/No
+ [ $retval -eq $DIALOG_OK ] || return $FAILURE
+
+ case "$mtag" in
+ X) # Delete/Exit
+ f_shell_escape "$user_uid" _user_uid
+
+ # Save group information in case pw(8) deletes it
+ # and we wanted to keep it (to be restored below)
+ if [ "${user_group_delete:-$msg_no}" = "$msg_no" ]
+ then
+ local v vars="gid members name password"
+ for v in $vars; do local group_$var; done
+ f_input_group "$user_group"
+
+ # Remove user-to-delete from group members
+ # NB: Otherwise group restoration could fail
+ local name length=0 _members=
+ while [ $length -ne ${#group_members} ]; do
+ name="${group_members%%,*}"
+ [ "$name" != "$user_name" ] &&
+ _members="$_members,$name"
+ length=${#group_members}
+ group_members="${group_members#*,}"
+ done
+ group_members="${_members#,}"
+
+ # Create escaped variables for f_eval_catch()
+ for v in $vars; do
+ local _group_$v
+ eval f_shell_escape \
+ \"\$group_$v\" _group_$v
+ done
+ fi
+
+ # Delete the user (if asked to delete home directory
+ # display [X]dialog notification to show activity)
+ local cmd="pw userdel -u '$_user_uid'"
+ if [ "$user_home_delete" = "$msg_yes" -a \
+ "$USE_XDIALOG" ]
+ then
+ local err
+ err=$(
+ exec 9>&1
+ f_eval_catch -e $funcname pw \
+ "%s -r" "$cmd" \
+ >&$DIALOG_TERMINAL_PASSTHRU_FD 2>&9 |
+ f_xdialog_info \
+ "$msg_deleting_home_directory"
+ )
+ [ ! "$err" ]
+ elif [ "$user_home_delete" = "$msg_yes" ]; then
+ f_dialog_info "$msg_deleting_home_directory"
+ f_eval_catch $funcname pw '%s -r' "$cmd"
+ else
+ f_eval_catch $funcname pw '%s' "$cmd"
+ fi || continue
+
+ #
+ # pw(8) may conditionally delete the primary group,
+ # which may not be what is desired.
+ #
+ # If we've been asked to delete the group and pw(8)
+ # chose not to, delete it. Otherwise, if we're told
+ # to NOT delete the group, we may need to restore it
+ # since pw(8) doesn't have a flag to tell `userdel'
+ # to not delete the group.
+ #
+ # NB: If primary group and user have different names
+ # the group may not have been deleted (again, see PR
+ # 169471 and SVN r263114 for details).
+ #
+ if [ "${user_group_delete:-$msg_no}" != "$msg_no" ]
+ then
+ f_quietly pw groupshow -g "$user_gid" &&
+ f_eval_catch $funcname pw \
+ "pw groupdel -g '%s'" "$_user_gid"
+ elif ! f_quietly pw groupshow -g "$group_gid" &&
+ [ "$group_name" -a "$group_gid" ]
+ then
+ # Group deleted by pw(8), so restore it
+ local cmd="pw groupadd -n '$_group_name'"
+ cmd="$cmd -g '$_group_gid'"
+ cmd="$cmd -M '$_group_members'"
+
+ # Get the group password (pw(8) groupshow does
+ # NOT provide this (even if running privileged)
+ local group_password_enc
+ group_password_enc=$( getent group | awk -F: '
+ !/^[[:space:]]*(#|$)/ && \
+ $1 == ENVIRON["group_name"] && \
+ $3 == ENVIRON["group_gid"] && \
+ $4 == ENVIRON["group_members"] \
+ { print $2; exit }
+ ' )
+ if [ "$group_password_enc" ]; then
+ echo "$group_password_enc" |
+ f_eval_catch $funcname \
+ pw '%s -H 0' "$cmd"
+ else
+ f_eval_catch $funcname \
+ pw '%s -h -' "$cmd"
+ fi
+ fi
+
+ break # to success
+ ;;
+ 1) # Login (select different login from list)
+ f_dialog_menu_user_list "$user_name" || continue
+ f_dialog_menutag_fetch mtag
+
+ [ "$mtag" = "X $msg_exit" ] && continue
+
+ if ! f_input_user "$mtag"; then
+ f_show_err "$msg_login_not_found" "$mtag"
+ # Attempt to fall back to previous selection
+ f_input_user "$input" || return $FAILURE
+ else
+ input="$mtag"
+ fi
+ title="$msg_delete $msg_user: $user_name"
+ ;;
+ C) # Delete Primary Group?
+ if [ "${user_group_delete:-$msg_no}" != "$msg_no" ]
+ then
+ user_group_delete="$msg_no"
+ else
+ user_group_delete="$msg_yes"
+ fi ;;
+ D) # Delete Home Directory?
+ if [ "${user_home_delete:-$msg_no}" != "$msg_no" ]
+ then
+ user_home_delete="$msg_no"
+ else
+ user_home_delete="$msg_yes"
+ fi ;;
+ esac
+ done
+ else
+ f_shell_escape "$user_uid" _user_uid
+
+ # Save group information in case pw(8) deletes it
+ # and we wanted to keep it (to be restored below)
+ if [ "${user_group_delete:-$msg_no}" = "$msg_no" ]; then
+ local v vars="gid members name password"
+ for v in $vars; do local group_$v; done
+ f_input_group "$user_group"
+
+ # Remove user we're about to delete from group members
+ # NB: Otherwise group restoration could fail
+ local name length=0 _members=
+ while [ $length -ne ${#group_members} ]; do
+ name="${group_members%%,*}"
+ [ "$name" != "$user_name" ] &&
+ _members="$_members,$name"
+ length=${#group_members}
+ group_members="${group_members#*,}"
+ done
+ group_members="${_members#,}"
+
+ # Create escaped variables for later f_eval_catch()
+ for v in $vars; do
+ local _group_$v
+ eval f_shell_escape \"\$group_$v\" _group_$v
+ done
+ fi
+
+ # Delete the user (if asked to delete home directory
+ # display [X]dialog notification to show activity)
+ local err cmd="pw userdel -u '$_user_uid'"
+ if [ "$user_home_delete" = "$msg_yes" -a "$USE_XDIALOG" ]; then
+ err=$(
+ exec 9>&1
+ f_eval_catch -de $funcname pw \
+ '%s -r' "$cmd" 2>&9 | f_xdialog_info \
+ "$msg_deleting_home_directory"
+ )
+ [ ! "$err" ]
+ elif [ "$user_home_delete" = "$msg_yes" ]; then
+ f_dialog_info "$msg_deleting_home_directory"
+ f_eval_catch -k err $funcname pw '%s -r' "$cmd"
+ else
+ f_eval_catch -k err $funcname pw '%s' "$cmd"
+ fi
+ local retval=$?
+ if [ $retval -ne $SUCCESS ]; then
+ f_show_err "%s" "$err"
+ return $retval
+ fi
+
+ #
+ # pw(8) may conditionally delete the primary group, which may
+ # not be what is desired.
+ #
+ # If we've been asked to delete the group and pw(8) chose not
+ # to, delete it. Otherwise, if we're told to NOT delete the
+ # group, we may need to restore it since pw(8) doesn't have a
+ # flag to tell `userdel' to not delete the group.
+ #
+ # NB: If primary group and user have different names the group
+ # may not have been deleted (again, see PR 169471 and SVN
+ # r263114 for details).
+ #
+ if [ "${user_group_delete:-$msg_no}" != "$msg_no" ]
+ then
+ f_quietly pw groupshow -g "$user_gid" &&
+ f_eval_catch $funcname pw \
+ "pw groupdel -g '%s'" "$_user_gid"
+ elif ! f_quietly pw groupshow -g "$group_gid" &&
+ [ "$group_name" -a "$group_gid" ]
+ then
+ # Group deleted by pw(8), so restore it
+ local cmd="pw groupadd -n '$_group_name'"
+ cmd="$cmd -g '$_group_gid'"
+ cmd="$cmd -M '$_group_members'"
+ local group_password_enc
+ group_password_enc=$( getent group | awk -F: '
+ !/^[[:space:]]*(#|$)/ && \
+ $1 == ENVIRON["group_name"] && \
+ $3 == ENVIRON["group_gid"] && \
+ $4 == ENVIRON["group_members"] \
+ { print $2; exit }
+ ' )
+ if [ "$group_password_enc" ]; then
+ echo "$group_password_enc" |
+ f_eval_catch $funcname \
+ pw '%s -H 0' "$cmd"
+ else
+ f_eval_catch $funcname pw '%s -h -' "$cmd"
+ fi
+ fi
+ fi
+
+ f_dialog_title "$title"
+ $alert "$msg_login_deleted"
+ f_dialog_title_restore
+ [ "$no_confirm" -a "$USE_DIALOG" ] && sleep 1
+
+ return $SUCCESS
+}
+
+# f_user_edit [$user]
+#
+# Modify a login account. If both $user (as a first argument) and $VAR_USER are
+# unset or NULL and we are running interactively, prompt the end-user to select
+# a login account from a list of those available. Variables that can be used to
+# script user input:
+#
+# VAR_USER [Optional if running interactively]
+# The login to modify. Ignored if given non-NULL first-argument.
+# VAR_USER_ACCOUNT_EXPIRE [Optional]
+# The account expiration time. Format is similar to
+# VAR_USER_PASSWORD_EXPIRE variable below. If unset, account
+# expiry is unchanged. If set but NULL, account expiration is
+# disabled (same as setting a value of `0').
+# VAR_USER_DOTFILES_CREATE [Optional]
+# If non-NULL, re-populate the user's home directory with the
+# template files found in $udotdir (`/usr/share/skel' default).
+# VAR_USER_GECOS [Optional]
+# Often the full name of the account holder. If unset, the GECOS
+# field is unmodified. If set but NULL, the field is blanked.
+# VAR_USER_GID [Optional]
+# Numerical primary-group ID to set. If NULL or unset, the group
+# ID is unchanged.
+# VAR_USER_GROUPS [Optional]
+# Comma-separated list of additional groups to which the user is
+# a member of. If set but NULL, group memberships are reset (this
+# login will not be a member of any additional groups besides the
+# primary group). If unset, group membership is unmodified.
+# VAR_USER_HOME [Optional]
+# The home directory to set. If NULL or unset, the home directory
+# is unchanged.
+# VAR_USER_HOME_CREATE [Optional]
+# If non-NULL, create the user's home directory if it doesn't
+# already exist.
+# VAR_USER_LOGIN_CLASS [Optional]
+# Login class to set. If unset, the login class is unchanged. If
+# set but NULL, the field is blanked.
+# VAR_USER_PASSWORD [Optional]
+# Unencrypted password to set. If unset, the login password is
+# unmodified. If set but NULL, password authentication for the
+# login is disabled.
+# VAR_USER_PASSWORD_EXPIRE [Optional]
+# The password expiration time. Format of the date is either a
+# UNIX time in decimal, or a date in dd-mmm-yy[yy] format, where
+# dd is the day, mmm is the month in either numeric or alphabetic
+# format, and yy[yy] is either a two or four digit year. This
+# variable also accepts a relative date in the form of +n[mhdwoy]
+# where n is a decimal, octal (leading 0) or hexadecimal (leading
+# 0x) digit followed by the number of Minutes, Hours, Days,
+# Weeks, Months or Years from the current date at which the
+# expiration time is to be set. If unset, password expiry is
+# unchanged. If set but NULL, password expiration is disabled
+# (same as setting a value of `0').
+# VAR_USER_SHELL [Optional]
+# Path to login shell to set. If NULL or unset, the shell is
+# unchanged.
+# VAR_USER_UID [Optional]
+# Numerical user ID to set. If NULL or unset, the user ID is
+# unchanged.
+#
+# Returns success if the user account was successfully modified.
+#
+f_user_edit()
+{
+ local funcname=f_user_edit
+ local title # Calculated below
+ local alert=f_show_msg no_confirm=
+
+ f_getvar $VAR_NO_CONFIRM no_confirm
+ [ "$no_confirm" ] && alert=f_show_info
+
+ local input
+ f_getvar 3:-\$$VAR_USER input "$1"
+
+ #
+ # NB: pw(8) has a ``feature'' wherein `-n name' can be taken as UID
+ # instead of name. Work-around is to also pass `-u UID' at the same
+ # time (the UID is ignored in this case, so any UID will do).
+ #
+ if [ "$input" ] && ! f_quietly pw usershow -n "$input" -u 1337; then
+ f_show_err "$msg_login_not_found" "$input"
+ return $FAILURE
+ fi
+
+ if f_interactive && [ ! "$input" ]; then
+ f_dialog_menu_user_list || return $SUCCESS
+ f_dialog_menutag_fetch input
+ [ "$input" = "X $msg_exit" ] && return $SUCCESS
+ elif [ ! "$input" ]; then
+ f_show_err "$msg_no_user_specified"
+ return $FAILURE
+ fi
+
+ local user_account_expire user_class user_gecos user_gid user_home_dir
+ local user_member_groups user_name user_password user_password_expire
+ local user_shell user_uid # Variables created by f_input_user() below
+ if ! f_input_user "$input"; then
+ f_show_err "$msg_login_not_found" "$input"
+ return $FAILURE
+ fi
+
+ #
+ # Override values probed by f_input_user() with desired values
+ #
+ f_isset $VAR_USER_GID && f_getvar $VAR_USER_GID user_gid
+ f_isset $VAR_USER_HOME && f_getvar $VAR_USER_HOME user_home_dir
+ f_isset $VAR_USER_SHELL && f_getvar $VAR_USER_SHELL user_shell
+ f_isset $VAR_USER_UID && f_getvar $VAR_USER_UID user_uid
+ local user_dotfiles_create= user_home_create=
+ f_getvar $VAR_USER_DOTFILES_CREATE:+\$msg_yes user_dotfiles_create
+ f_getvar $VAR_USER_HOME_CREATE:+\$msg_yes user_home_create
+ local no_account_expire=
+ if f_isset $VAR_USER_ACCOUNT_EXPIRE; then
+ f_getvar $VAR_USER_ACCOUNT_EXPIRE user_account_expire
+ [ "$user_account_expire" ] || no_account_expire=1
+ fi
+ local null_gecos=
+ if f_isset $VAR_USER_GECOS; then
+ f_getvar $VAR_USER_GECOS user_gecos
+ [ "$user_gecos" ] || null_gecos=1
+ fi
+ local null_members=
+ if f_isset $VAR_USER_GROUPS; then
+ f_getvar $VAR_USER_GROUPS user_member_groups
+ [ "$user_member_groups" ] || null_members=1
+ fi
+ local null_class=
+ if f_isset $VAR_USER_LOGIN_CLASS; then
+ f_getvar $VAR_USER_LOGIN_CLASS user_class
+ [ "$user_class" ] || null_class=1
+ fi
+ local user_password_disable=
+ if f_isset $VAR_USER_PASSWORD; then
+ f_getvar $VAR_USER_PASSWORD user_password
+ [ "$user_password" ] || user_password_disable=1
+ fi
+ local no_password_expire=
+ if f_isset $VAR_USER_PASSWORD_EXPIRE; then
+ f_getvar $VAR_USER_PASSWORD_EXPIRE user_password_expire
+ [ "$user_password_expire" ] || no_password_expire=1
+ fi
+
+ #
+ # Loop until the user decides to Exit, Cancel, or presses ESC
+ #
+ title="$msg_edit_view $msg_user: $user_name"
+ if f_interactive; then
+ local mtag retval defaultitem=
+ while :; do
+ f_dialog_title "$title"
+ f_dialog_menu_user_edit "$defaultitem"
+ retval=$?
+ f_dialog_title_restore
+ f_dialog_menutag_fetch mtag
+ f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
+ defaultitem="$mtag"
+
+ # Return if user either pressed ESC or chose Cancel/No
+ [ $retval -eq $DIALOG_OK ] || return $FAILURE
+
+ case "$mtag" in
+ X) # Save/Exit
+ local var
+ for var in account_expire class gecos gid home_dir \
+ member_groups name password_expire shell uid \
+ ; do
+ local _user_$var
+ eval f_shell_escape \"\$user_$var\" _user_$var
+ done
+
+ local cmd="pw usermod -n '$_user_name'"
+ [ "$user_gid" ] && cmd="$cmd -g '$_user_gid'"
+ [ "$user_shell" ] && cmd="$cmd -s '$_user_shell'"
+ [ "$user_uid" ] && cmd="$cmd -u '$_user_uid'"
+ [ "$user_account_expire" -o \
+ "$no_account_expire" ] &&
+ cmd="$cmd -e '$_user_account_expire'"
+ [ "$user_class" -o "$null_class" ] &&
+ cmd="$cmd -L '$_user_class'"
+ [ "$user_gecos" -o "$null_gecos" ] &&
+ cmd="$cmd -c '$_user_gecos'"
+ [ "$user_home_dir" ] &&
+ cmd="$cmd -d '$_user_home_dir'"
+ [ "$user_member_groups" -o "$null_members" ] &&
+ cmd="$cmd -G '$_user_member_groups'"
+ [ "$user_password_expire" -o \
+ "$no_password_expire" ] &&
+ cmd="$cmd -p '$_user_password_expire'"
+
+ # Execute the command
+ if [ "$user_password_disable" ]; then
+ f_eval_catch $funcname pw '%s -h -' "$cmd"
+ elif [ "$user_password" ]; then
+ echo "$user_password" | f_eval_catch \
+ $funcname pw '%s -h 0' "$cmd"
+ else
+ f_eval_catch $funcname pw '%s' "$cmd"
+ fi || continue
+
+ # Create home directory if desired
+ [ "${user_home_create:-$msg_no}" != "$msg_no" ] &&
+ f_user_create_homedir "$user_name"
+
+ # Copy dotfiles if desired
+ [ "${user_dotfiles_create:-$msg_no}" != \
+ "$msg_no" ] && f_user_copy_dotfiles "$user_name"
+
+ break # to success
+ ;;
+ 1) # Login (select different login from list)
+ f_dialog_menu_user_list "$user_name" || continue
+ f_dialog_menutag_fetch mtag
+
+ [ "$mtag" = "X $msg_exit" ] && continue
+
+ if ! f_input_user "$mtag"; then
+ f_show_err "$msg_login_not_found" "$mtag"
+ # Attempt to fall back to previous selection
+ f_input_user "$input" || return $FAILURE
+ else
+ input="$mtag"
+ fi
+ title="$msg_edit_view $msg_user: $user_name"
+ ;;
+ 2) # Full Name
+ f_dialog_input_gecos user_gecos "$user_gecos" &&
+ [ ! "$user_gecos" ] && null_gecos=1 ;;
+ 3) # Password
+ f_dialog_input_password \
+ user_password user_password_disable ;;
+ 4) # User ID
+ f_dialog_input_uid user_uid "$user_uid" ;;
+ 5) # Group ID
+ f_dialog_input_gid user_gid "$user_gid" ;;
+ 6) # Member of Groups
+ f_dialog_input_member_groups \
+ user_member_groups "$user_member_groups" &&
+ [ ! "$user_member_groups" ] &&
+ null_members=1 ;;
+ 7) # Login Class
+ f_dialog_input_class user_class "$user_class" &&
+ [ ! "$user_class" ] && null_class=1 ;;
+ 8) # Password Expires On
+ f_dialog_input_expire_password \
+ user_password_expire "$user_password_expire" &&
+ [ ! "$user_password_expire" ] &&
+ no_password_expire=1 ;;
+ 9) # Account Expires On
+ f_dialog_input_expire_account \
+ user_account_expire "$user_account_expire" &&
+ [ ! "$user_account_expire" ] &&
+ no_account_expire=1 ;;
+ A) # Home Directory
+ f_dialog_input_home_dir \
+ user_home_dir "$user_home_dir" ;;
+ B) # Shell
+ f_dialog_input_shell user_shell "$user_shell" ;;
+ C) # Create Home Directory?
+ if [ "${user_home_create:-$msg_no}" != "$msg_no" ]
+ then
+ user_home_create="$msg_no"
+ else
+ user_home_create="$msg_yes"
+ fi ;;
+ D) # Create Dotfiles?
+ if [ "${user_dotfiles_create:-$msg_no}" != \
+ "$msg_no" ]
+ then
+ user_dotfiles_create="$msg_no"
+ else
+ user_dotfiles_create="$msg_yes"
+ fi ;;
+ esac
+ done
+ else
+ local var
+ for var in account_expire class gecos gid home_dir \
+ member_groups name password_expire shell uid \
+ ; do
+ local _user_$var
+ eval f_shell_escape \"\$user_$var\" _user_$var
+ done
+
+ # Form the command
+ local cmd="pw usermod -n '$_user_name'"
+ [ "$user_gid" ] && cmd="$cmd -g '$_user_gid'"
+ [ "$user_home_dir" ] && cmd="$cmd -d '$_user_home_dir'"
+ [ "$user_shell" ] && cmd="$cmd -s '$_user_shell'"
+ [ "$user_uid" ] && cmd="$cmd -u '$_user_uid'"
+ [ "$user_account_expire" -o "$no_account_expire" ] &&
+ cmd="$cmd -e '$_user_account_expire'"
+ [ "$user_class" -o "$null_class" ] &&
+ cmd="$cmd -L '$_user_class'"
+ [ "$user_gecos" -o "$null_gecos" ] &&
+ cmd="$cmd -c '$_user_gecos'"
+ [ "$user_member_groups" -o "$null_members" ] &&
+ cmd="$cmd -G '$_user_member_groups'"
+ [ "$user_password_expire" -o "$no_password_expire" ] &&
+ cmd="$cmd -p '$_user_password_expire'"
+
+ # Execute the command
+ local retval err
+ if [ "$user_password_disable" ]; then
+ f_eval_catch -k err $funcname pw '%s -h -' "$cmd"
+ elif [ "$user_password" ]; then
+ err=$( echo "$user_password" | f_eval_catch -de \
+ $funcname pw '%s -h 0' "$cmd" 2>&1 )
+ else
+ f_eval_catch -k err $funcname pw '%s' "$cmd"
+ fi
+ retval=$?
+ if [ $retval -ne $SUCCESS ]; then
+ f_show_err "%s" "$err"
+ return $retval
+ fi
+
+ # Create home directory if desired
+ [ "${user_home_create:-$msg_no}" != "$msg_no" ] &&
+ f_user_create_homedir "$user_name"
+
+ # Copy dotfiles if desired
+ [ "${user_dotfiles_create:-$msg_no}" != "$msg_no" ] &&
+ f_user_copy_dotfiles "$user_name"
+ fi
+
+ f_dialog_title "$title"
+ $alert "$msg_login_updated"
+ f_dialog_title_restore
+ [ "$no_confirm" -a "$USE_DIALOG" ] && sleep 1
+
+ return $SUCCESS
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." usermgmt/user.subr
+
+fi # ! $_USERMGMT_USER_SUBR
diff --git a/usr.sbin/bsdconfig/usermgmt/share/user_input.subr b/usr.sbin/bsdconfig/usermgmt/share/user_input.subr
new file mode 100644
index 0000000..39578c8
--- /dev/null
+++ b/usr.sbin/bsdconfig/usermgmt/share/user_input.subr
@@ -0,0 +1,1338 @@
+if [ ! "$_USERMGMT_USER_INPUT_SUBR" ]; then _USERMGMT_USER_INPUT_SUBR=1
+#
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012-2014 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." usermgmt/user_input.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/strings.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="070.usermgmt"
+f_include_lang $BSDCFG_LIBE/include/messages.subr
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+############################################################ CONFIGURATION
+
+#
+# Default location of shells(5)
+#
+: ${ETC_SHELLS:=/etc/shells}
+
+############################################################ FUNCTIONS
+
+# f_get_member_groups $var_to_set $user
+#
+# Get a list of additional groups $user is a member of in group(5).
+#
+f_get_member_groups()
+{
+ f_replaceall "$( pw groupshow -a | awk -F: -v user="$2" '{
+ if (!split($4, users, /,/)) next
+ for (u in users) if (users[u] == user) { print $1; next }
+ }' )" "[$NL]" "," "$1"
+}
+
+# f_input_user $user
+#
+# Given $user name or id, create the environment variables user_name, user_uid,
+# user_gid, user_class, user_password_expire, user_account_expire, user_gecos,
+# user_home_dir, user_shell, and user_member_groups (and user_password is reset
+# to NULL).
+#
+f_input_user()
+{
+ local funcname=f_input_user
+ local user="$1"
+
+ f_dprintf "$funcname: Getting info for user \`%s'" "$user"
+ eval "$( pw usershow "$user" 2> /dev/null | awk -F: '
+ function set_value(var, value) {
+ gsub(/'\''/, "'\''\\'\'\''", value)
+ printf "user_%s='\'%s\''\n", var, value
+ }
+ {
+ found = $1 != ""
+ set_value("name", $1 )
+ set_value("password", "" )
+ set_value("uid", $3 )
+ set_value("gid", $4 )
+ set_value("class", $5 )
+ set_value("password_expire", $6 )
+ set_value("account_expire", $7 )
+ set_value("gecos", $8 )
+ set_value("home_dir", $9 )
+ set_value("shell", $10)
+ exit
+ }
+ END { if (!found) print "false" }' )"
+ local retval=$?
+
+ f_dprintf "$funcname: Getting group memberships for user \`%s'" "$user"
+ f_get_member_groups user_member_groups "$user"
+
+ return $retval
+}
+
+# f_dialog_menu_user_list [$default]
+#
+# Allows the user to select a login from a list. Optionally, if present and
+# non-NULL, initially highlight $default user.
+#
+f_dialog_menu_user_list()
+{
+ local prompt=
+ local menu_list="
+ 'X $msg_exit' ''
+ " # END-QUOTE
+ local defaultitem="$1"
+ local hline="$hline_alnum_punc_tab_enter"
+
+ # Add users from passwd(5)
+ menu_list="$menu_list $( pw usershow -a | awk -F: '
+ function mprint(tag, item) {
+ gsub(/'\''/, "'\''\\'\'\''", tag)
+ gsub(/'\''/, "'\''\\'\'\''", item)
+ printf "'\'%s\'\ \'%s\''\n", tag, item
+ }
+ !/^[[:space:]]*(#|$)/ { mprint($1, $8) }
+ ' )"
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_menutag_store -s "$menu_choice"
+ return $retval
+}
+
+# f_dialog_input_member_groups $var_to_set [$member_groups]
+#
+# Allows the user to edit group memberships for a given user. If the user does
+# not cancel or press ESC, the $var_to_set variable will hold the newly-
+# configured value upon return.
+#
+f_dialog_input_member_groups()
+{
+ local __var_to_set="$1" __input="$2"
+ local __prompt="$msg_member_of_groups"
+ local __menu_list="
+ 'X' '$msg_continue'
+ '1' '$msg_select_groups_from_list'
+ '2' '$msg_enter_groups_manually'
+ " # END-QUOTE
+ local __defaultitem=
+ local __hline="$hline_alnum_space_tab_enter"
+
+ local __mheight __mwidth __mrows
+ eval f_dialog_menu_size __mheight __mwidth __mrows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$__prompt\" \
+ \"\$__hline\" \
+ $__menu_list
+
+ local __menu_choice __retval
+ while :; do
+ __menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$__hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$__defaultitem\" \
+ --menu \"\$__prompt\" \
+ $__mheight $__mwidth $__mrows \
+ $__menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ __retval=$?
+ f_dialog_data_sanitize __menu_choice
+ __defaultitem="$__menu_choice"
+ f_dprintf "retval=%u menu_choice=[%s]" \
+ $__retval "$__menu_choice"
+
+ # Return if user has either pressed ESC or chosen Cancel/No
+ [ $__retval -eq $DIALOG_OK ] || return $__retval
+
+ local __member_groups
+ case "$__menu_choice" in
+ X) # Exit
+ break ;;
+ 1) # Select Groups from a list
+ local __check_list= # Calculated below
+ local __group_list __g __grp __length=0
+ __group_list=$( pw groupshow -a |
+ awk -F: '!/^[[:space:]]*(#|$)/{print $1}' )
+ while [ $__length -ne ${#__group_list} ]; do
+ __g="${__group_list%%$NL*}" # First line
+ f_shell_escape "$__g" __grp
+
+ # Format of a checklist entry: tag item status
+ # NB: Setting both tag/item to group name below
+ __check_list="$__check_list '$__grp' '$__grp'"
+ case "$__input" in
+ "$__g"|"$__g",*|*,"$__g",*|*,"$__g")
+ __check_list="$__check_list on" ;;
+ *)
+ __check_list="$__check_list off"
+ esac
+
+ __length=${#__group_list}
+ __group_list="${__group_list#*$NL}" # Kill line
+ done
+
+ local __cheight __cwidth __crows
+
+ eval f_dialog_checklist_size \
+ __cheight __cwidth __crows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$__prompt\" \
+ \"\$__hline\" \
+ $__check_list
+ __member_groups=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --separate-output \
+ --hline \"\$__hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --checklist \"\$__prompt\" \
+ $__cheight $__cwidth $__crows \
+ $__check_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ ) || continue
+ # Return to previous menu if user either
+ # pressed ESC or chose Cancel/No
+ f_dialog_data_sanitize __member_groups
+
+ #
+ # Convert the newline separated list into a comma-
+ # separated one so that if the user switches over to
+ # manual editing, list reflects checklist selections
+ #
+ f_replaceall "$__member_groups" "[$NL]" "," __input
+ ;;
+ 2) # Enter Groups manually
+ local __prompt2="$msg_groups"
+ __prompt2="$__prompt2 ($msg_separated_by_commas)"
+
+ f_dialog_input __member_groups \
+ "$__prompt2" "$__input" \
+ "$hline_num_tab_enter" || continue
+ # Return to previous menu if user either
+ # pressed ESC or chose Cancel/No
+
+ #
+ # Validate each of the groups the user has entered
+ #
+ local __all_groups_valid=1 __grp __grp_list
+ f_replaceall "$__member_groups" "," " " __grp_list
+ for __grp in $__grp_list; do
+ if ! f_quietly pw groupshow -n "$__grp"; then
+ f_show_msg "$msg_group_not_found" \
+ "$__grp"
+ __all_groups_valid=
+ break
+ fi
+ done
+ [ "$__all_groups_valid" ] || continue
+
+ __input="$__member_groups"
+ ;;
+ esac
+ done
+
+ setvar "$__var_to_set" "$__input"
+ return $DIALOG_OK
+}
+
+# f_dialog_input_name $var_to_set [$name]
+#
+# Allows the user to enter a new username for a given user. If the user does
+# not cancel or press ESC, the $var_to_set variable will hold the newly-
+# configured value upon return.
+#
+f_dialog_input_name()
+{
+ local __var_to_set="$1" __name="$2"
+
+ #
+ # Loop until the user provides taint-free/valid input
+ #
+ local __input="$__name"
+ while :; do
+ # Return if user has either pressed ESC or chosen Cancel/No
+ f_dialog_input __input "$msg_login" "$__input" \
+ "$hline_alnum_tab_enter" || return $?
+
+ # Check for no-change
+ if [ "$__input" = "$__name" ]; then
+ setvar "$__var_to_set" "$__input"
+ return $DIALOG_OK
+ fi
+
+ # Check for NULL entry
+ if [ ! "$__input" ]; then
+ f_show_msg "$msg_login_is_empty"
+ continue
+ fi
+
+ # Check for invalid entry
+ case "$__input" in [!a-zA-Z]*)
+ f_show_msg "$msg_login_must_start_with_letter"
+ continue
+ esac
+
+ # Check for duplicate entry
+ if f_quietly pw usershow -n "$__input"; then
+ f_show_msg "$msg_login_already_used" "$__input"
+ continue
+ fi
+
+ setvar "$__var_to_set" "$__input"
+ break
+ done
+
+ return $DIALOG_OK
+}
+
+# f_dialog_input_password $var_to_set $dvar_to_set
+#
+# Prompt the user to enter a password (twice). If the user does not cancel or
+# press ESC, $var_to_set will hold the confirmed user entry. Otherwise, if the
+# user cancels or enters a NULL password (twice), they are given the choice to
+# disable password authentication for the given login, wherein $dvar_to_set has
+# a value of 1 to indicate password authentication should be disabled.
+#
+f_dialog_input_password()
+{
+ local __var_to_set="$1" __dvar_to_set="$2"
+ local __prompt1="$msg_password"
+ local __prompt2="$msg_reenter_password"
+ local __hline="$hline_alnum_punc_tab_enter"
+
+ local __height1 __width1
+ f_dialog_inputbox_size __height1 __width1 \
+ "$DIALOG_TITLE" \
+ "$DIALOG_BACKTITLE" \
+ "$__prompt1" \
+ "" \
+ "$__hline"
+ local __height2 __width2
+ f_dialog_inputbox_size __height2 __width2 \
+ "$DIALOG_TITLE" \
+ "$DIALOG_BACKTITLE" \
+ "$__prompt2" \
+ "" \
+ "$__hline"
+
+ #
+ # Loop until the user provides taint-free/valid input
+ #
+ local __retval __password1 __password2
+ while :; do
+ __password1=$( $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --hline "$__hline" \
+ --ok-label "$msg_ok" \
+ --cancel-label "$msg_cancel" \
+ --insecure \
+ --passwordbox "$__prompt1" \
+ $__height1 $__width1 \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ ) || return $?
+ # Return if user either pressed ESC or chose Cancel/No
+ debug= f_dialog_line_sanitize __password1
+
+ __password2=$( $DIALOG \
+ --title "$DIALOG_TITLE" \
+ --backtitle "$DIALOG_BACKTITLE" \
+ --hline "$__hline" \
+ --ok-label "$msg_ok" \
+ --cancel-label "$msg_cancel" \
+ --insecure \
+ --passwordbox "$__prompt2" \
+ $__height2 $__width2 \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ ) || return $?
+ # Return if user either pressed ESC or chose Cancel/No
+ debug= f_dialog_line_sanitize __password2
+
+ # Check for password mismatch
+ if [ "$__password1" != "$__password2" ]; then
+ f_show_msg "$msg_passwords_do_not_match"
+ continue
+ fi
+
+ # Check for NULL entry
+ if [ ! "$__password1" ]; then
+ f_dialog_yesno "$msg_disable_password_auth_for_account"
+ __retval=$?
+ if [ $__retval -eq $DIALOG_ESC ]; then
+ return $__retval
+ elif [ $__retval -eq $DIALOG_OK ]; then
+ setvar "$__dvar_to_set" 1
+ else
+ continue # back to password prompt
+ fi
+ else
+ setvar "$__dvar_to_set" ""
+ fi
+
+ setvar "$__var_to_set" "$__password1"
+ break
+ done
+
+ return $DIALOG_OK
+}
+
+# f_dialog_input_gecos $var_to_set [$gecos]
+#
+# Allow the user to enter new GECOS information for a given user. This
+# information is commonly used to store the ``Full Name'' of the user. If the
+# user does not cancel or press ESC, the $var_to_set variable will hold the
+# newly-configured value upon return.
+#
+f_dialog_input_gecos()
+{
+ local __var_to_set="$1" __input="$2"
+
+ # Return if user has either pressed ESC or chosen Cancel/No
+ f_dialog_input __input "$msg_full_name" "$__input" \
+ "$hline_alnum_punc_tab_enter" || return $?
+
+ setvar "$__var_to_set" "$__input"
+ return $DIALOG_OK
+}
+
+# f_dialog_input_uid $var_to_set [$uid]
+#
+# Allow the user to enter a new UID for a given user. If the user does not
+# cancel or press ESC, the $var_to_set variable will hold the newly-configured
+# value upon return.
+#
+f_dialog_input_uid()
+{
+ local __var_to_set="$1" __input="$2"
+
+ # Return if user has either pressed ESC or chosen Cancel/No
+ f_dialog_input __input "$msg_user_id_leave_empty_for_default" \
+ "$__input" "$hline_num_tab_enter" || return $?
+
+ setvar "$__var_to_set" "$__input"
+ return $DIALOG_OK
+}
+
+# f_dialog_input_gid $var_to_set [$gid]
+#
+# Allow the user to enter a new primary GID for a given user. If the user does
+# not cancel or press ESC, the $var_to_set variable will hold the newly-
+# configured value upon return.
+#
+f_dialog_input_gid()
+{
+ local __var_to_set="$1" __input="$2"
+
+ # Return if user has either pressed ESC or chosen Cancel/No
+ f_dialog_input __input "$msg_group_id_leave_empty_for_default" \
+ "$__input" "$hline_num_tab_enter" || return $?
+
+ setvar "$__var_to_set" "$__input"
+ return $DIALOG_OK
+}
+
+# f_dialog_input_class $var_to_set [$class]
+#
+# Allow the user to enter a new login class for a given user. If the user does
+# not cancel or press ESC, the $var_to_set variable will hold the newly-
+# configured value upon return.
+#
+f_dialog_input_class()
+{
+ local __var_to_set="$1" __input="$2"
+
+ # Return if user has either pressed ESC or chosen Cancel/No
+ f_dialog_input __input "$msg_login_class" "$__input" \
+ "$hline_alnum_tab_enter" || return $?
+
+ setvar "$__var_to_set" "$__input"
+ return $DIALOG_OK
+}
+
+# f_dialog_input_expire_password $var_to_set [$seconds]
+#
+# Allow the user to enter a date/time (in number-of-seconds since the `epoch')
+# for when a given user's password must be changed. If the user does not cancel
+# or press ESC, the $var_to_set variable will hold the newly-configured value
+# upon return.
+#
+f_dialog_input_expire_password()
+{
+ local __var_to_set="$1" __input="$2"
+ local __prompt="$msg_password_expires_on"
+ local __menu_list="
+ '1' '$msg_password_does_not_expire'
+ '2' '$msg_edit_date_time_with_a_calendar'
+ '3' '$msg_enter_value_manually'
+ " # END-QUOTE
+ local __defaultitem= # Calculated below
+ local __hline="$hline_num_arrows_tab_enter"
+
+ local __mheight __mwidth __mrows
+ eval f_dialog_menu_size __mheight __mwidth __mrows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$__prompt\" \
+ \"\$__hline\" \
+ $__menu_list
+ local __cheight __cwidth
+ f_dialog_calendar_size __cheight __cwidth \
+ "$DIALOG_TITLE" \
+ "$DIALOG_BACKTITLE" \
+ "$__prompt" \
+ "$__hline"
+ local __theight __twidth
+ f_dialog_timebox_size __theight __twidth \
+ "$DIALOG_TITLE" \
+ "$DIALOG_BACKTITLE" \
+ "$__prompt" \
+ "$__hline"
+
+ #
+ # Loop until the user provides taint-free/cancellation-free input
+ #
+ local __retval __date_type
+ while :; do
+ __date_type=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$__hline\" \
+ --default-item \"\$__defaultitem\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --menu \"\$__prompt\" \
+ $__mheight $__mwidth $__mrows \
+ $__menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ __retval=$?
+ f_dialog_data_sanitize __date_type
+ __defaultitem="$__date_type"
+ f_dprintf "retval=%u date_type=[%s]" $__retval "$__date_type"
+
+ # Return if user has either pressed ESC or chosen Cancel/No
+ [ $__retval -eq $DIALOG_OK ] || return $__retval
+
+ case "$__date_type" in
+ 1) # Password does not expire
+ __input= break ;;
+
+ 2) # Edit date/time with a calendar
+ local __input_date __input_time __ret_date __ret_time
+
+ local __seconds="$__input"
+ { f_isinteger "$__seconds" && [ $__seconds -gt 0 ]; } ||
+ __seconds=
+ __input_date=$( date -j -f "%s" -- "$__seconds" \
+ "+%d %m %Y" 2> /dev/null )
+ __ret_date=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$__hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --calendar \"\$__prompt\" \
+ $__cheight $__cwidth \
+ $__input_date \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ __retval=$?
+ f_dialog_data_sanitize __ret_date
+ f_dprintf "retval=%u ret_date=[%s]" \
+ $__retval "$__ret_date"
+
+ # Return to menu if either ESC or Cancel/No
+ [ $__retval -eq $DIALOG_OK ] || continue
+
+ __input_time=
+ [ "$__seconds" ] && __input_time=$( date -j \
+ -f %s -- "$__input" "+%H %M %S" 2> /dev/null )
+ __ret_time=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$__hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --timebox \"\$__prompt\" \
+ $__theight $__twidth \
+ $__input_time \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ __retval=$?
+ f_dialog_data_sanitize __ret_time
+ f_dprintf "retval=%u ret_time=[%s]" \
+ $__retval "$__ret_time"
+
+ # Return to menu if either ESC or Cancel/No
+ [ $__retval -eq $DIALOG_OK ] || continue
+
+ __input=$( date -j -f "%d/%m/%Y %T" -- \
+ "$__ret_date $__ret_time" +%s 2> /dev/null )
+ f_dprintf "input=[%s]" "$__input"
+ break ;;
+
+ 3) # Enter value manually
+ local __msg __new_input
+ f_sprintf __msg "$msg_password_expire_manual_edit" \
+ "$( date -r 0 "+%c %Z" )"
+
+ # Return to menu if either ESC or Cancel/No
+ f_dialog_input __new_input \
+ "$__msg" "$__input" "$__hline" || continue
+
+ __input="$__new_input"
+ f_dprintf "input=[%s]" "$__input"
+ break ;;
+
+ esac
+
+ done # Loop forever
+
+ setvar "$__var_to_set" "$__input"
+ return $DIALOG_OK
+}
+
+# f_dialog_input_expire_account $var_to_set [$seconds]
+#
+# Allow the user to enter a date/time (in number-of-seconds since the `epoch')
+# for when a given user's account should become expired. If the user does not
+# cancel or press ESC, the $var_to_set variable will hold the newly-configured
+# value upon return.
+#
+f_dialog_input_expire_account()
+{
+ local __var_to_set="$1" __input="$2"
+ local __prompt="$msg_account_expires_on"
+ local __menu_list="
+ '1' '$msg_account_does_not_expire'
+ '2' '$msg_edit_date_time_with_a_calendar'
+ '3' '$msg_enter_value_manually'
+ " # END-QUOTE
+ local __defaultitem= # Calculated below
+ local __hline="$hline_num_arrows_tab_enter"
+
+ local __mheight __mwidth __mrows
+ eval f_dialog_menu_size __mheight __mwidth __mrows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$__prompt\" \
+ \"\$__hline\" \
+ $__menu_list
+ local __cheight __cwidth
+ f_dialog_calendar_size __cheight __cwidth \
+ "$DIALOG_TITLE" \
+ "$DIALOG_BACKTITLE" \
+ "$__prompt" \
+ "$__hline"
+ local __theight __twidth
+ f_dialog_timebox_size __theight __twidth \
+ "$DIALOG_TITLE" \
+ "$DIALOG_BACKTITLE" \
+ "$__prompt" \
+ "$__hline"
+
+ #
+ # Loop until the user provides taint-free/cancellation-free input
+ #
+ local __retval __date_type
+ while :; do
+ __date_type=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$__hline\" \
+ --default-item \"\$__defaultitem\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --menu \"\$__prompt\" \
+ $__mheight $__mwidth $__mrows \
+ $__menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ __retval=$?
+ f_dialog_data_sanitize __date_type
+ __defaultitem="$__date_type"
+ f_dprintf "retval=%u date_type=[%s]" $__retval "$__date_type"
+
+ # Return if user has either pressed ESC or chosen Cancel/No
+ [ $__retval -eq $DIALOG_OK ] || return $__retval
+
+ case "$__date_type" in
+ 1) # Account does not expire
+ __input= break ;;
+
+ 2) # Edit date/time with a calendar
+ local __input_date __input_time __ret_date __ret_time
+
+ local __seconds="$__input"
+ { f_isinteger "$__seconds" && [ $__seconds -gt 0 ]; } ||
+ __seconds=
+ __input_date=$( date -j -f "%s" -- "$__seconds" \
+ "+%d %m %Y" 2> /dev/null )
+ __ret_date=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$__hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --calendar \"\$__prompt\" \
+ $__cheight $__cwidth \
+ $__input_date \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ __retval=$?
+ f_dialog_data_sanitize __ret_date
+ f_dprintf "retval=%u ret_date=[%s]" \
+ $__retval "$__ret_date"
+
+ # Return to menu if either ESC or Cancel/No
+ [ $__retval -eq $DIALOG_OK ] || continue
+
+ __input_time=
+ [ "$__seconds" ] && __input_time=$( date -j \
+ -f %s -- "$__input" "+%H %M %S" 2> /dev/null )
+ __ret_time=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$__hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --timebox \"\$__prompt\" \
+ $__theight $__twidth \
+ $__input_time \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ __retval=$?
+ f_dialog_data_sanitize __ret_time
+ f_dprintf "retval=%u ret_time=[%s]" \
+ $__retval "$__ret_time"
+
+ # Return to menu if either ESC or Cancel/No
+ [ $__retval -eq $DIALOG_OK ] || continue
+
+ __input=$( date -j -f "%d/%m/%Y %T" -- \
+ "$ret_date $ret_time" +%s 2> /dev/null )
+ f_dprintf "input=[%s]" "$__input"
+ break ;;
+
+ 3) # Enter value manually
+ local __msg __new_input
+ f_sprintf __msg "$msg_account_expire_manual_edit" \
+ "$( date -r 0 "+%c %Z" )"
+
+ # Return to menu if either ESC or Cancel/No
+ f_dialog_input __new_input \
+ "$__msg" "$__input" "$__hline" || continue
+
+ __input="$__new_input"
+ f_dprintf "input=[%s]" "$__input"
+ break ;;
+
+ esac
+
+ done # Loop forever
+
+ setvar "$__var_to_set" "$__input"
+ return $DIALOG_OK
+}
+
+# f_dialog_input_home_dir $var_to_set [$home_dir]
+#
+# Allow the user to enter a new home directory for a given login. If the user
+# does not cancel or press ESC, the $var_to_set variable will hold the newly-
+# configured value upon return.
+#
+f_dialog_input_home_dir()
+{
+ local __var_to_set="$1" __input="$2"
+
+ # Return if user has either pressed ESC or chosen Cancel/No
+ f_dialog_input __input "$msg_home_directory" "$__input" \
+ "$hline_alnum_punc_tab_enter" || return $?
+
+ setvar "$__var_to_set" "$__input"
+ return $DIALOG_OK
+}
+
+# f_dialog_input_home_create $var_to_set
+#
+# Prompt the user to confirm creation of a given login's home directory. If the
+# user does not cancel (by choosing "No") or press ESC, the $var_to_set
+# variable will hold $msg_yes upon return, otherwise $msg_no. Use these return
+# variables ($msg_yes and $msg_no) for comparisons to be i18n-compatible.
+#
+f_dialog_input_home_create()
+{
+ local __var_to_set="$1"
+
+ f_dialog_yesno "$msg_create_home_directory"
+ local __retval=$?
+
+ if [ $__retval -eq $DIALOG_OK ]; then
+ setvar "$__var_to_set" "$msg_yes"
+ else
+ setvar "$__var_to_set" "$msg_no"
+ fi
+
+ [ $__retval -ne $DIALOG_ESC ] # return failure if user pressed ESC
+}
+
+# f_dialog_input_group_delete $var_to_set [$group]
+#
+# Prompt the user to confirm deletion of a given login's primary group. If the
+# user does not cancel (by choosing "No") or press ESC, the $var_to_set
+# variable will hold $msg_yes upon return, otherwise $msg_no. Use these return
+# variables ($msg_yes and $msg_no) for comparisons to be i18n-compatible.
+#
+f_dialog_input_group_delete()
+{
+ local __var_to_set="$1" __group="$2"
+
+ if f_isinteger "$__group"; then
+ if [ $__group -lt 1000 ]; then
+ f_dialog_noyes "$msg_delete_primary_group"
+ else
+ f_dialog_yesno "$msg_delete_primary_group"
+ fi
+ elif [ "$__group" ]; then
+ local __gid=0
+ __gid=$( pw groupshow "$__group" | awk -F: '{print $3}' )
+ if f_isinteger "$__gid" && [ $__gid -lt 1000 ]; then
+ f_dialog_noyes "$msg_delete_primary_group"
+ else
+ f_dialog_yesno "$msg_delete_primary_group"
+ fi
+ else
+ f_dialog_yesno "$msg_delete_primary_group"
+ fi
+ local __retval=$?
+
+ if [ $__retval -eq $DIALOG_OK ]; then
+ setvar "$__var_to_set" "$msg_yes"
+ else
+ setvar "$__var_to_set" "$msg_no"
+ fi
+
+ [ $__retval -ne $DIALOG_ESC ] # return failure if user pressed ESC
+}
+
+# f_dialog_input_home_delete $var_to_set
+#
+# Prompt the user to confirm deletion of a given login's home directory. If the
+# user does not cancel (by choosing "No") or press ESC, the $var_to_set
+# variable will hold $msg_yes upon return, otherwise $msg_no. Use these return
+# variables ($msg_yes and $msg_no) for comparisons to be i18n-compatible.
+#
+f_dialog_input_home_delete()
+{
+ local __var_to_set="$1"
+
+ f_dialog_yesno "$msg_delete_home_directory"
+ local __retval=$?
+
+ if [ $__retval -eq $DIALOG_OK ]; then
+ setvar "$__var_to_set" "$msg_yes"
+ else
+ setvar "$__var_to_set" "$msg_no"
+ fi
+
+ [ $__retval -ne $DIALOG_ESC ] # return failure if user pressed ESC
+}
+
+# f_dialog_input_dotfiles_create $var_to_set
+#
+# Prompt the user to confirm population of a given login's home directory with
+# sample dotfiles. If the user does not cancel (by choosing "No") or press ESC,
+# the $var_to_set variable will hold $msg_yes upon return, otherwise $msg_no.
+# Use these return variables ($msg_yes and $msg_no) for comparison to be i18n-
+# compatible.
+#
+f_dialog_input_dotfiles_create()
+{
+ local __var_to_set="$1"
+
+ f_dialog_yesno "$msg_create_dotfiles"
+ local __retval=$?
+
+ if [ $__retval -eq $DIALOG_OK ]; then
+ setvar "$__var_to_set" "$msg_yes"
+ else
+ setvar "$__var_to_set" "$msg_no"
+ fi
+
+ [ $__retval -ne $DIALOG_ESC ] # return failure if user pressed ESC
+}
+
+# f_dialog_input_shell $var_to_set [$shell]
+#
+# Allow the user to select a new shell for a given login. If the user does not
+# cancel or press ESC, the $var_to_set variable will hold the newly-configured
+# value upon return.
+#
+f_dialog_input_shell()
+{
+ local __funcname=f_dialog_input_shell
+ local __var_to_set="$1" __input="$2"
+ local __prompt="$msg_select_login_shell"
+ local __radio_list= # Calculated below
+ local __defaultitem="$2"
+ local __hline="$hline_arrows_space_tab_enter"
+
+ #
+ # Generate the radiolist of shells
+ #
+ local __shell_list __s __shell __length=0
+ f_eval_catch -k __shell_list $__funcname awk "awk '%s' \"%s\"" \
+ '!/^[[:space:]]*(#|$)/{print}' "$ETC_SHELLS" || return $FAILURE
+ while [ $__length -ne ${#__shell_list} ]; do
+ __s="${__shell_list%%$NL*}" # First line
+ f_shell_escape "$__s" __shell
+
+ # Format of a radiolist entry: tag item status
+ if [ "$__s" = "$__input" ]; then
+ __radio_list="$__radio_list '$__shell' '' 'on'"
+ else
+ __radio_list="$__radio_list '$__shell' '' 'off'"
+ fi
+
+ __length=${#__shell_list}
+ __shell_list="${__shell_list#*$NL}" # Kill line
+ done
+
+ local __height __width __rows
+ eval f_dialog_radiolist_size __height __width __rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$__prompt\" \
+ \"\$__hline\" \
+ $__radio_list
+
+ __input=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$__hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$__defaultitem\" \
+ --radiolist \"\$__prompt\" \
+ $__height $__width $__rows \
+ $__radio_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ ) || return $?
+ # Return if user either pressed ESC or chose Cancel/No
+ f_dialog_data_sanitize __input
+
+ setvar "$__var_to_set" "$__input"
+ return $DIALOG_OK
+}
+
+# f_dialog_menu_user_add [$defaultitem]
+#
+# Present a menu detailing the properties of a login that is about to be added.
+# The user's menu choice is available using f_dialog_menutag_fetch(). Returns
+# success unless the user chose Cancel or pressed ESC. Data to display is taken
+# from environment variables user_account_expire, user_class,
+# user_dotfiles_create, user_gecos, user_gid, user_home_create, user_home_dir,
+# user_member_groups, user_name, user_password_expire, user_shell, and
+# user_uid. If $defaultitem is present and non-NULL, initially highlight the
+# item in the menu.
+#
+f_dialog_menu_user_add()
+{
+ local funcname=f_dialog_menu_user_add
+ local prompt="$msg_save_exit_or_cancel"
+ local menu_list # Calculated below
+ local defaultitem="$1"
+ local hline="$hline_arrows_tab_enter"
+
+ # Attempt to convert numeric UNIX time to calendar date/time
+ local user_account_expires_on=
+ if f_isinteger "$user_account_expire"; then
+ [ "$user_account_expire" -ne 0 ] && user_account_expires_on=$(
+ date -r "$user_account_expire" "+%F %T %Z"
+ )
+ else
+ user_account_expires_on="$user_account_expire"
+ fi
+ local user_password_expires_on=
+ if f_isinteger "$user_password_expire"; then
+ [ $user_password_expire -ne 0 ] && user_password_expires_on=$(
+ date -r "$user_password_expire" "+%F %T %Z"
+ )
+ else
+ user_password_expires_on="$user_password_expire"
+ fi
+
+ # Attempt to translate a numeric GID into `number (name)'
+ if f_isinteger "$user_gid"; then
+ local user_group
+ user_group=$( pw groupshow -g "$user_gid" 2> /dev/null ) &&
+ user_gid="$user_gid (${user_group%%:*})"
+ fi
+
+ # Localize potentially hostile variables and escape their values
+ # to the local variable (see f_shell_escape() of `strings.subr')
+ local var
+ for var in account_expires_on class dotfiles_create gecos gid \
+ home_create home_dir member_groups name password_expires_on \
+ shell uid \
+ ; do
+ local _user_$var
+ eval f_shell_escape \"\$user_$var\" _user_$var
+ done
+
+ menu_list="
+ 'X' '$msg_add/$msg_exit'
+ '1' '$msg_login: $_user_name'
+ '2' '$msg_full_name: $_user_gecos'
+ '3' '$msg_password: -----'
+ '4' '$msg_user_id: $_user_uid'
+ '5' '$msg_group_id: $_user_gid'
+ '6' '$msg_member_of_groups: $_user_member_groups'
+ '7' '$msg_login_class: $_user_class'
+ '8' '$msg_password_expires_on: $_user_password_expires_on'
+ '9' '$msg_account_expires_on: $_user_account_expires_on'
+ 'A' '$msg_home_directory: $_user_home_dir'
+ 'B' '$msg_shell: $_user_shell'
+ " # END-QUOTE
+ case "$user_home_dir" in
+ /|/nonexistent|/var/empty) menu_list="$menu_list
+ '-' '$msg_create_home_directory: $msg_n_a'
+ '-' '$msg_create_dotfiles: $msg_n_a'
+ " # END-QUOTE
+ ;;
+ *) if [ -d "$user_home_dir" ]; then menu_list="$menu_list
+ '-' '$msg_create_home_directory: $msg_n_a'
+ 'D' '$msg_create_dotfiles: ${_user_dotfiles_create:-$msg_no}'
+ " # END-QUOTE
+ else menu_list="$menu_list
+ 'C' '$msg_create_home_directory: ${_user_home_create:-$msg_no}'
+ 'D' '$msg_create_dotfiles: ${_user_dotfiles_create:-$msg_no}'
+ " # END-QUOTE
+ fi
+ esac
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --keep-tite \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize menu_choice
+ f_dialog_menutag_store "$menu_choice"
+ return $retval
+}
+
+# f_dialog_menu_user_delete $user [$defaultitem]
+#
+# Present a menu detailing the properties of a login that is about to be
+# deleted. The user's menu choice is available using f_dialog_menutag_fetch().
+# Returns success unless the user chose Cancel or pressed ESC. Data to display
+# is populated automatically from the system accounting database for the given
+# $user argument with the exception of two environment variables:
+# user_group_delete and user_home_delete. If $defaultitem is present and non-
+# NULL, initially highlight the item in the menu.
+#
+f_dialog_menu_user_delete()
+{
+ local prompt="$msg_delete_exit_or_cancel"
+ local menu_list # Calculated below
+ local defaultitem="$2"
+ local hline="$hline_arrows_tab_enter"
+
+ local user_name user_password user_uid user_gid user_class
+ local user_password_expire user_account_expire user_gecos
+ local user_home_dir user_shell user_member_groups
+ f_input_user "$1"
+
+ # Attempt to convert numeric UNIX time to calendar date/time
+ local user_account_expires_on=
+ if f_isinteger "$user_account_expire"; then
+ [ "$user_account_expire" -ne 0 ] && user_account_expires_on=$(
+ date -r "$user_account_expire" "+%F %T %Z"
+ )
+ else
+ user_account_expires_on="$user_account_expire"
+ fi
+ local user_password_expires_on=
+ if f_isinteger "$user_password_expire"; then
+ [ $user_password_expire -ne 0 ] && user_password_expires_on=$(
+ date -r "$user_password_expire" "+%F %T %Z"
+ )
+ else
+ user_password_expires_on="$user_password_expire"
+ fi
+
+ # Attempt to translate a numeric GID into `number (name)'
+ if f_isinteger "$user_gid"; then
+ local user_group
+ user_group=$( pw groupshow -g "$user_gid" 2> /dev/null ) &&
+ user_gid="$user_gid (${user_group%%:*})"
+ fi
+
+ # Localize potentially hostile variables and escape their values
+ # to the local variable (see f_shell_escape() of `strings.subr')
+ local var
+ for var in account_expires_on class gecos gid group_delete \
+ home_delete home_dir member_groups name password_expires_on \
+ shell uid \
+ ; do
+ local _user_$var
+ eval f_shell_escape \"\$user_$var\" _user_$var
+ done
+
+ menu_list="
+ 'X' '$msg_delete/$msg_exit'
+ '1' '$msg_login: $_user_name'
+ '-' '$msg_full_name: $_user_gecos'
+ '-' '$msg_password: -----'
+ '-' '$msg_user_id: $_user_uid'
+ '-' '$msg_group_id: $_user_gid'
+ '-' '$msg_group_members: $_user_member_groups'
+ '-' '$msg_login_class: $_user_class'
+ '-' '$msg_password_expires_on: $_user_password_expires_on'
+ '-' '$msg_account_expires_on: $_user_account_expires_on'
+ '-' '$msg_home_directory: $_user_home_dir'
+ '-' '$msg_shell: $_user_shell'
+ " # END-QUOTE
+ if f_quietly pw groupshow -g "$user_gid"; then menu_list="$menu_list
+ 'C' '$msg_delete_primary_group: ${_user_group_delete:-$msg_no}'
+ " # END-QUOTE
+ else menu_list="$menu_list
+ '-' '$msg_delete_primary_group: $msg_n_a'
+ " # END-QUOTE
+ fi
+ case "$user_home_dir" in
+ /|/nonexistent|/var/empty) menu_list="$menu_list
+ '-' '$msg_delete_home_directory: $msg_n_a'
+ " # END-QUOTE
+ ;;
+ *) if [ -d "$user_home_dir" ]; then menu_list="$menu_list
+ 'D' '$msg_delete_home_directory: ${_user_home_delete:-$msg_no}'
+ " # END-QUOTE
+ else menu_list="$menu_list
+ '-' '$msg_delete_home_directory: $msg_n_a'
+ " # END-QUOTE
+ fi
+ esac
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --keep-tite \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize menu_choice
+ f_dialog_menutag_store "$menu_choice"
+ return $retval
+}
+
+# f_dialog_menu_user_edit [$defaultitem]
+#
+# Present a menu detailing the properties of a login that is about to be
+# modified. The user's menu choice is available using f_dialog_menutag_fetch().
+# Returns success unless the user chose Cancel or pressed ESC. Data to display
+# is taken from environment variables user_account_expire, user_class,
+# user_dotfiles_create, user_gecos, user_gid, user_home_create, user_home_dir,
+# user_member_groups, user_name, user_password_expire, user_shell, and
+# user_uid. If $defaultitem is present and non-NULL, initially highlight the
+# item in the menu.
+#
+f_dialog_menu_user_edit()
+{
+ local prompt="$msg_save_exit_or_cancel"
+ local menu_list # Calculated below
+ local defaultitem="$1"
+ local hline="$hline_arrows_tab_enter"
+
+ # Attempt to convert numeric UNIX time to calendar date/time
+ local user_account_expires_on=
+ if f_isinteger "$user_account_expire"; then
+ [ "$user_account_expire" -ne 0 ] && user_account_expires_on=$(
+ date -r "$user_account_expire" "+%F %T %Z"
+ )
+ else
+ user_account_expires_on="$user_account_expire"
+ fi
+ local user_password_expires_on=
+ if f_isinteger "$user_password_expire"; then
+ [ $user_password_expire -ne 0 ] && user_password_expires_on=$(
+ date -r "$user_password_expire" "+%F %T %Z"
+ )
+ else
+ user_password_expires_on="$user_password_expire"
+ fi
+
+ # Attempt to translate a numeric GID into `number (name)'
+ if f_isinteger "$user_gid"; then
+ local user_group
+ user_group=$( pw groupshow -g "$user_gid" 2> /dev/null ) &&
+ user_gid="$user_gid (${user_group%%:*})"
+ fi
+
+ # Localize potentially hostile variables and escape their values
+ # to the local variable (see f_shell_escape() of `strings.subr')
+ local var
+ for var in account_expires_on class dotfiles_create gecos gid \
+ home_create home_dir member_groups name password_expires_on \
+ shell uid \
+ ; do
+ local _user_$var
+ eval f_shell_escape \"\$user_$var\" _user_$var
+ done
+
+ menu_list="
+ 'X' '$msg_save/$msg_exit'
+ '1' '$msg_login: $_user_name'
+ '2' '$msg_full_name: $_user_gecos'
+ '3' '$msg_password: -----'
+ '4' '$msg_user_id: $_user_uid'
+ '5' '$msg_group_id: $_user_gid'
+ '6' '$msg_member_of_groups: $_user_member_groups'
+ '7' '$msg_login_class: $_user_class'
+ '8' '$msg_password_expires_on: $_user_password_expires_on'
+ '9' '$msg_account_expires_on: $_user_account_expires_on'
+ 'A' '$msg_home_directory: $_user_home_dir'
+ 'B' '$msg_shell: $_user_shell'
+ " # END-QUOTE
+ case "$user_home_dir" in
+ /|/nonexistent|/var/empty) menu_list="$menu_list
+ '-' '$msg_create_home_directory: $msg_n_a'
+ '-' '$msg_create_dotfiles: $msg_n_a'
+ " # END-QUOTE
+ ;;
+ *) if [ -d "$user_home_dir" ]; then menu_list="$menu_list
+ '-' '$msg_create_home_directory: $msg_n_a'
+ 'D' '$msg_create_dotfiles: ${_user_dotfiles_create:-$msg_no}'
+ " # END-QUOTE
+ else menu_list="$menu_list
+ 'C' '$msg_create_home_directory: ${_user_home_create:-$msg_no}'
+ 'D' '$msg_create_dotfiles: ${_user_dotfiles_create:-$msg_no}'
+ " # END-QUOTE
+ fi
+ esac
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --keep-tite \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize menu_choice
+ f_dialog_menutag_store "$menu_choice"
+ return $retval
+}
+
+############################################################ MAIN
+
+f_dprintf "%s: Successfully loaded." usermgmt/user_input.subr
+
+fi # ! $_USERMGMT_USER_INPUT_SUBR
diff --git a/usr.sbin/bsdconfig/usermgmt/useradd b/usr.sbin/bsdconfig/usermgmt/useradd
new file mode 100755
index 0000000..d372be4
--- /dev/null
+++ b/usr.sbin/bsdconfig/usermgmt/useradd
@@ -0,0 +1,77 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012-2014 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/usermgmt/user.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="070.usermgmt"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_add $msg_user"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Add a user
+#
+# NB: If given an argument on the command-line use it; otherwise fall-back to
+# environment variable $user (handle $VAR_USER).
+#
+f_user_add ${1:+"$1"}
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/usermgmt/userdel b/usr.sbin/bsdconfig/usermgmt/userdel
new file mode 100755
index 0000000..9425c7b
--- /dev/null
+++ b/usr.sbin/bsdconfig/usermgmt/userdel
@@ -0,0 +1,100 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012-2014 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/usermgmt/user.subr
+f_include $BSDCFG_SHARE/usermgmt/user_input.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="070.usermgmt"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_delete $msg_login"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# If given a user name, operate on it and exit
+#
+if [ "$1" ]; then
+ f_user_delete "$1"
+ exit $SUCCESS
+fi
+
+#
+# Loop until the user Exits, Cancels or presses ESC
+#
+defaultitem=
+while :; do
+ f_dialog_menu_user_list "$defaultitem"
+ retval=$?
+ f_dialog_menutag_fetch mtag
+ f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
+ defaultitem="$mtag"
+
+ [ $retval -eq $DIALOG_OK ] || f_die
+
+ [ "$mtag" = "X $msg_exit" ] && break
+
+ # Anything else is a userid
+
+ f_user_delete "$mtag"
+done
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/usermgmt/useredit b/usr.sbin/bsdconfig/usermgmt/useredit
new file mode 100755
index 0000000..612f3a6
--- /dev/null
+++ b/usr.sbin/bsdconfig/usermgmt/useredit
@@ -0,0 +1,100 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012-2014 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/usermgmt/user.subr
+f_include $BSDCFG_SHARE/usermgmt/user_input.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="070.usermgmt"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_edit_view $msg_login"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# If given a user name, operate on it and exit
+#
+if [ "$1" ]; then
+ f_user_edit "$1"
+ exit $SUCCESS
+fi
+
+#
+# Present a list of users and loop until user Exits, Cancels or presses ESC
+#
+defaultitem=
+while :; do
+ f_dialog_menu_user_list "$defaultitem"
+ retval=$?
+ f_dialog_menutag_fetch mtag
+ f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
+ defaultitem="$mtag"
+
+ [ $retval -eq $DIALOG_OK ] || f_die
+
+ [ "$mtag" = "X $msg_exit" ] && break
+
+ # Anything else is a userid
+
+ f_user_edit "$mtag"
+done
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdconfig/usermgmt/usermgmt b/usr.sbin/bsdconfig/usermgmt/usermgmt
new file mode 100755
index 0000000..79fbc95
--- /dev/null
+++ b/usr.sbin/bsdconfig/usermgmt/usermgmt
@@ -0,0 +1,168 @@
+#!/bin/sh
+#-
+# Copyright (c) 2012 Ron McDowell
+# Copyright (c) 2012-2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+
+BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="070.usermgmt"
+f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
+
+USERMGMT_HELPFILE=$BSDCFG_LIBE/$APP_DIR/include/usermgmt.hlp
+
+f_index_menusel_keyword $BSDCFG_LIBE/$APP_DIR/INDEX "$pgm" ipgm &&
+ pgm="${ipgm:-$pgm}"
+
+############################################################ FUNCTIONS
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local prompt=
+ local menu_list="
+ 'X' '$msg_exit'
+ '1' '$msg_add_login'
+ '2' '$msg_edit_login'
+ '3' '$msg_delete_login'
+ '-' '-'
+ '4' '$msg_add_group'
+ '5' '$msg_edit_group'
+ '6' '$msg_delete_group'
+ " # END-QUOTE
+ local defaultitem= # Calculated below
+ local hline="$hline_arrows_tab_enter"
+
+ local height width rows
+ eval f_dialog_menu_size height width rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+
+ # When using Xdialog(1) we need to bump the width for the buttons
+ [ "$USE_XDIALOG" ] && width=40
+
+ # Obtain default-item from previously stored selection
+ f_dialog_default_fetch defaultitem
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --help-button \
+ --help-label \"\$msg_help\" \
+ ${USE_XDIALOG:+--help \"\"} \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize menu_choice
+ f_dialog_menutag_store "$menu_choice"
+
+ # Only update default-item on success
+ [ $retval -eq $DIALOG_OK ] && f_dialog_default_store "$menu_choice"
+
+ return $retval
+}
+
+############################################################ MAIN
+
+# Incorporate rc-file if it exists
+[ -f "$HOME/.bsdconfigrc" ] && f_include "$HOME/.bsdconfigrc"
+
+#
+# Process command-line arguments
+#
+while getopts h$GETOPTS_STDARGS flag; do
+ case "$flag" in
+ h|\?) f_usage $BSDCFG_LIBE/$APP_DIR/USAGE "PROGRAM_NAME" "$pgm" ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Initialize
+#
+f_dialog_title "$msg_login_management"
+f_dialog_backtitle "${ipgm:+bsdconfig }$pgm"
+f_mustberoot_init
+
+#
+# Launch application main menu
+#
+while :; do
+ dialog_menu_main
+ retval=$?
+ f_dialog_menutag_fetch mtag
+ f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
+
+ if [ $retval -eq $DIALOG_HELP ]; then
+ f_show_help "$USERMGMT_HELPFILE"
+ continue
+ elif [ $retval -ne $DIALOG_OK ]; then
+ f_die
+ fi
+
+ command=
+ case "$mtag" in
+ X) break ;;
+ 1) command=useradd ;; # Add User
+ 2) command=useredit ;; # Edit/View User
+ 3) command=userdel ;; # Delete User
+ 4) command=groupadd ;; # Add Group
+ 5) command=groupedit ;; # Edit/View Group
+ 6) command=groupdel ;; # Delete Group
+ esac
+
+ if [ "$command" ]; then
+ $BSDCFG_LIBE/$APP_DIR/$command ${USE_XDIALOG:+-X}
+ else
+ f_die 1 "$msg_unknown_user_management_menu_selection"
+ fi
+done
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdinstall/Makefile b/usr.sbin/bsdinstall/Makefile
new file mode 100644
index 0000000..4e3b7ef
--- /dev/null
+++ b/usr.sbin/bsdinstall/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+SUBDIR= distextract distfetch partedit scripts
+SUBDIR_PARALLEL=
+SCRIPTS= bsdinstall
+MAN= bsdinstall.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdinstall/Makefile.depend b/usr.sbin/bsdinstall/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdinstall/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdinstall/bsdinstall b/usr.sbin/bsdinstall/bsdinstall
new file mode 100755
index 0000000..a258e47
--- /dev/null
+++ b/usr.sbin/bsdinstall/bsdinstall
@@ -0,0 +1,88 @@
+#!/bin/sh
+#-
+# Copyright (c) 2011 Nathan Whitehorn
+# Copyright (c) 2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+# Delay processing of debug flags as the parent until MAIN. export'd to disable
+# re-processing of flags (all children log to the parent's log file).
+#
+export DEBUG_SELF_INITIALIZE=
+export DEBUG_INITIALIZE_FILE=
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+
+############################################################ GLOBALS
+
+: ${BSDINSTALL_TMPETC="/tmp/bsdinstall_etc"}; export BSDINSTALL_TMPETC
+: ${BSDINSTALL_TMPBOOT="/tmp/bsdinstall_boot"}; export BSDINSTALL_TMPBOOT
+: ${PATH_FSTAB="$BSDINSTALL_TMPETC/fstab"}; export PATH_FSTAB
+: ${BSDINSTALL_DISTDIR="/usr/freebsd-dist"}; export BSDINSTALL_DISTDIR
+: ${BSDINSTALL_CHROOT="/mnt"}; export BSDINSTALL_CHROOT
+
+export debugFile="${debugFile-${BSDINSTALL_LOG-/tmp/bsdinstall_log}}"
+
+############################################################ MAIN
+
+#
+# Process command-line arguments
+#
+while getopts $GETOPTS_STDARGS ignored; do
+ : just skipping known flags
+done
+shift $(( $OPTIND - 1 ))
+
+# What are we here to do?
+VERB="${1:-auto}"; shift
+
+[ -d "$BSDINSTALL_TMPETC" ] || mkdir -p "$BSDINSTALL_TMPETC"
+[ -d "$BSDINSTALL_TMPBOOT" ] || mkdir -p "$BSDINSTALL_TMPBOOT"
+
+# Only enable debugging if debugFile is non-NULL and can be initialized
+f_quietly f_debug_init
+f_isset debugFile || debug=
+
+f_dprintf "Running installation step: %s %s" "$VERB" "$*"
+if [ "$debug" ]; then
+ case "$debugFile" in
+ # If NULL, send errors to the bit-bucket
+ "") exec "/usr/libexec/bsdinstall/$VERB" "$@" 2> /dev/null ;;
+ # If begins with `+', send errors to both terminal and file (no `+')
+ +*) exec "/usr/libexec/bsdinstall/$VERB" "$@" \
+ 2>&1 >&$TERMINAL_STDOUT_PASSTHRU | tee "${debugFile#+}" ;;
+ # Otherwise, just send errors to the file specified
+ *) exec "/usr/libexec/bsdinstall/$VERB" "$@" 2>> "$debugFile"
+ esac
+else
+ exec "/usr/libexec/bsdinstall/$VERB" "$@" 2> /dev/null
+fi
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdinstall/bsdinstall.8 b/usr.sbin/bsdinstall/bsdinstall.8
new file mode 100644
index 0000000..a08eff9
--- /dev/null
+++ b/usr.sbin/bsdinstall/bsdinstall.8
@@ -0,0 +1,374 @@
+.\"-
+.\" Copyright (c) 2011-2013 Nathan Whitehorn <nwhitehorn@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 ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED 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 31, 2014
+.Dt BSDINSTALL 8
+.Os
+.Sh NAME
+.Nm bsdinstall
+.Nd system installer
+.Sh SYNOPSIS
+.Nm
+.Op Ar options
+.Op Ar target
+.Op Ar ...
+.Sh DESCRIPTION
+.Nm
+is used for installation of new systems, both for system setup from
+installation media (e.g. CD-ROMs) and for use on live systems to prepare
+VM images and jails.
+.Pp
+Much like
+.Xr make 1 , Nm
+takes a target and possible parameters of the target as arguments. If
+invoked with no arguments, it will invoke the
+.Cm auto
+target, which provides a standard interactive installation, invoking the
+others in sequence. To perform a scripted installation, these subtargets
+can be invoked separately by an installation script.
+.Sh OPTIONS
+.Nm
+supports the following options, global to all targets:
+.Bl -tag -width indent+
+.It Fl D Ar file
+Provide a path for the installation log file
+.Pq overrides Ev BSDINSTALL_LOG .
+See
+.Sx ENVIRONMENT VARIABLES
+for more information on
+.Ev BSDINSTALL_LOG .
+.El
+.Sh TARGETS
+Most of the following targets are only useful for scripting the installer.
+For interactive use, most users will be interested only in the
+.Cm auto ,
+.Cm jail ,
+and
+.Cm script
+targets.
+.Bl -tag -width ".Cm jail Ar destination"
+.It Cm auto
+Run the standard interactive installation, including disk partitioning.
+.It Cm jail Ar destination
+Sets up a new chroot system at
+.Pa destination ,
+suitable for use with
+.Xr jail 8 .
+Behavior is generally similar to
+.Cm auto ,
+except that disk partitioning and network setup are skipped and a kernel is
+not installed into the new system.
+.It Cm script Ar script
+Runs the installation script at
+.Pa script .
+See
+.Sx SCRIPTING
+for more information on this target.
+.It Cm keymap
+If the current controlling TTY is a
+.Xr syscons 4
+or
+.Xr vt 4
+console, asks the user to set the current keymap, and saves the result to the
+new system's
+.Pa rc.conf .
+.It Cm hostname
+Prompts the user for a host name for the new system and saves the result to the
+new system's
+.Pa rc.conf .
+If
+.Ev BSDINSTALL_CONFIGCURRENT
+is set, also sets the host name of the current system.
+.It Cm netconfig
+Interactively configures network interfaces (first invoking
+.Cm wlanconfig
+on wireless interfaces), saving the result to the new system's
+.Pa rc.conf
+and
+.Pa resolv.conf .
+If
+.Ev BSDINSTALL_CONFIGCURRENT
+is set, also configures the network interfaces of the current system to match.
+.It Cm autopart
+Provides the installer's interactive guided disk partitioner for single-disk
+installations. Defaults to UFS.
+.It Cm zfsboot
+Provides an alternative ZFS-only automatic interactive disk partitioner.
+Creates a single
+.Ic zpool
+with separate datasets for
+.Pa /tmp ,
+.Pa /usr ,
+.Pa /usr/home ,
+.Pa /usr/ports ,
+.Pa /usr/src ,
+and
+.Pa /var .
+Optionally can set up
+.Xr geli 8
+to encrypt the disk.
+.It Cm partedit
+Provides the installer's interactive manual disk partitioner with an interface
+identical to
+.Xr sade 8 .
+Supports multiple disks as well as UFS, ZFS, and FAT file systems. ZFS
+is set up with one pool and dataset per partition.
+.It Cm scriptedpart Ar parameters
+Sets up disks like
+.Cm autopart
+and
+.Cm partedit ,
+but non-interactively according to the disk setup specified in
+.Ar parameters .
+Each disk setup is specified by a three-part argument:
+.Pp
+.Ar disk
+.Op Ar scheme
+.Op Ar {partitions}
+.Pp
+Multiple disk setups are separated by semicolons. The
+.Ar disk
+argument specifies the disk on which to operate (which will be erased),
+while the
+.Ar scheme
+argument specifies the
+.Xr gpart 8
+partition scheme to apply to the disk. If
+.Ar scheme
+is unspecified,
+.Cm scriptedpart
+will apply the default bootable scheme on your platform.
+The
+.Ar partitions
+argument is also optional and specifies how to partition
+.Ar disk .
+It consists of a comma-separated list of partitions to create enclosed in
+curly braces. Each partition declaration takes the form
+.Pp
+.Ar size
+.Ar type
+.Op Ar mount point
+.Pp
+.Ar size
+specifies the partition size to create in bytes (K, M, and G suffixes
+can be appended to specify kilobytes, megabytes, and gigabytes respectively),
+while the
+.Em auto
+keyword causes the partition to take all the remaining space on the disk. The
+.Ar type
+option chooses the
+.Xr gpart 8
+filesystem type (e.g. freebsd-ufs, freebsd-zfs, or freebsd-swap).
+The optional
+.Ar mount point
+argument sets where the created partition is to be mounted in the installed
+system. As an example, a typical invocation looks like:
+.Pp
+bsdinstall scriptedpart ada0 { 20G freebsd-ufs /, 4G freebsd-swap, 20G freebsd-ufs /var, auto freebsd-ufs /usr }
+.Pp
+A shorter invocation to use the default partitioning (as
+.Cm autopart
+would have used) on the same disk:
+.Pp
+bsdinstall scriptedpart ada0
+.It Cm mount
+Mounts the file systems previously configured by
+.Cm autopart ,
+.Cm partedit ,
+or
+.Cm scriptedpart
+under
+.Ev BSDINSTALL_CHROOT .
+.It Cm distfetch
+Fetches the distributions in
+.Ev DISTRIBUTIONS
+to
+.Ev BSDINSTALL_DISTDIR
+from
+.Ev BSDINSTALL_DISTSITE .
+.It Cm checksum
+Verifies the checksums of the distributions listed in
+.Ev DISTRIBUTIONS
+against the distribution manifest.
+.It Cm distextract
+Extracts the distributions listed in
+.Ev DISTRIBUTIONS
+into
+.Ev BSDINSTALL_CHROOT .
+.It Cm rootpass
+Interactively invokes
+.Xr passwd 1
+in the new system to set the root user's password.
+.It Cm adduser
+Interactively invokes
+.Xr adduser 8
+in the new system.
+.It Cm time
+Interactively sets the time, date, and time zone of the new system.
+.It Cm services
+Queries the user for the system daemons to begin at system startup,
+writing the result into the new system's
+.Pa rc.conf .
+.It Cm entropy
+Reads a small amount of data from
+.Pa /dev/random
+and stores it in a file in the new system's root directory.
+.It Cm config
+Installs the configuration files destined for the new system (e.g. rc.conf
+fragments generated by
+.Cm netconfig ,
+etc.) onto the new system.
+.El
+.Sh ENVIRONMENT VARIABLES
+The following environment variables control various aspects of the installation
+process. Many are used internally during installation and have reasonable
+default values for most installation scenarios. Others are set by various
+interactive user prompts, and can be usefully overridden when making scripted
+or customized installers.
+.Bl -tag -width ".Ev BSDINSTALL_DISTSITE"
+.It Ev DISTRIBUTIONS
+The set of distributions to install (e.g. "base kernel ports"). Default: none
+.It Ev BSDINSTALL_DISTDIR
+The directory in which the distribution files can be found (or to which they
+should be downloaded). Default:
+.Pa /usr/freebsd-dist
+.It Ev BSDINSTALL_DISTSITE
+URL from which the distribution files should be downloaded if they are not
+already present in the directory defined by
+.Ev BSDINSTALL_DISTDIR .
+This should be a full path to the files, including architecture and release
+names. Most targets (e.g.
+.Cm auto
+and
+.Cm jail )
+that prompt for a
+.Fx
+mirror will skip that step if this variable is already defined in the
+environment. Example:
+.Pa ftp://ftp.freebsd.org/pub/FreeBSD/releases/powerpc/powerpc64/9.1-RELEASE
+.It Ev BSDINSTALL_CHROOT
+The directory into which the distribution files should be unpacked and the
+directory at which the root file system of the new system should be mounted.
+Default:
+.Pa /mnt
+.It Ev BSDINSTALL_LOG
+Path to a log file for the installation. Default:
+.Pa /tmp/bsdinstall_log
+.It Ev BSDINSTALL_TMPETC
+Directory where files destined for the new system's
+.Pa /etc
+will be stored until the
+.Cm config
+target is executed. If this directory does not already exist, it will be
+created. Default:
+.Pa /tmp/bsdinstall_etc
+.It Ev BSDINSTALL_TMPBOOT
+Directory where files destined for the new system's
+.Pa /boot
+will be stored until the
+.Cm config
+target is executed. If this directory does not already exist, it will be
+created. Default:
+.Pa /tmp/bsdinstall_boot
+.El
+.Sh SCRIPTING
+.Nm
+scripts consist of two parts: a
+.Em preamble
+and a
+.Em setup script .
+The preamble sets up the options for the installation (how to partition the
+disk[s], which distributions to install, etc.) and the optional second part is
+a shell script run under
+.Xr chroot 8
+in the newly installed system before
+.Nm
+exits. The two parts are separated by the usual script header (#!), which
+also sets the interpreter for the setup script.
+.Pp
+A typical bsdinstall script looks like this:
+.Bd -literal -offset indent
+PARTITIONS=ada0
+DISTRIBUTIONS="kernel.txz base.txz"
+
+#!/bin/sh
+echo "ifconfig_em0=DHCP" >> /etc/rc.conf
+echo "sshd_enable=YES" >> /etc/rc.conf
+pkg install puppet
+.Ed
+.Pp
+On
+.Fx
+release media, such a script placed at
+.Pa /etc/installerconfig
+will be run at boot time and the system will be rebooted automatically after
+the installation has completed. This can be used for unattended network
+installation of new systems; see
+.Xr diskless 8
+for details.
+.Ss PREAMBLE
+The preamble consists of installer settings. These control global installation
+parameters (see
+.Sx ENVIRONMENT VARIABLES )
+as well as disk partitioning. The preamble is interpreted as a
+.Xr sh 1
+script run at the very beginning of the install. If more complicated behavior
+than setting these variables is desired, arbitrary commands can be run here
+to extend the installer. In addition to the variables in
+.Sx ENVIRONMENT VARIABLES ,
+in particular
+.Ev DISTRIBUTIONS ,
+the preamble can contain a variable
+.Ev PARTITIONS
+which is passed to the
+.Cm scriptedpart
+target to control disk setup.
+Alternatively,
+to use
+.Cm zfsboot
+instead of
+.Cm partedit ,
+the preamble can contain the variable
+.Ev ZFSBOOT_DATASETS
+instead of
+.Ev PARTITIONS .
+.Ss SETUP SCRIPT
+Following the preamble is an optional shell script, beginning with a #!
+declaration. This script will be run at the end of the installation process
+inside a
+.Xr chroot 8
+environment in the newly installed system and can be used to set up
+configuration files, install packages, etc. Note that newly configured
+system services (e.g. networking) have not been started in the installed
+system at this time and only installation host services are available.
+.Sh HISTORY
+This version of
+.Nm
+first appeared in
+.Fx 9.0 .
+.Sh AUTHORS
+.An Nathan Whitehorn Aq Mt nwhitehorn@FreeBSD.org
diff --git a/usr.sbin/bsdinstall/distextract/Makefile b/usr.sbin/bsdinstall/distextract/Makefile
new file mode 100644
index 0000000..d3008f6
--- /dev/null
+++ b/usr.sbin/bsdinstall/distextract/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+BINDIR= ${LIBEXECDIR}/bsdinstall
+PROG= distextract
+LIBADD= archive dpv figpar ncursesw dialog m
+
+WARNS?= 6
+MAN=
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdinstall/distextract/Makefile.depend b/usr.sbin/bsdinstall/distextract/Makefile.depend
new file mode 100644
index 0000000..d7411cc
--- /dev/null
+++ b/usr.sbin/bsdinstall/distextract/Makefile.depend
@@ -0,0 +1,31 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libdialog \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libarchive \
+ lib/libbz2 \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libdpv \
+ lib/libexpat \
+ lib/libfigpar \
+ lib/liblzma \
+ lib/libthr \
+ lib/libutil \
+ lib/libz \
+ lib/msun \
+ lib/ncurses/ncursesw \
+ secure/lib/libcrypto \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdinstall/distextract/distextract.c b/usr.sbin/bsdinstall/distextract/distextract.c
new file mode 100644
index 0000000..94536bc
--- /dev/null
+++ b/usr.sbin/bsdinstall/distextract/distextract.c
@@ -0,0 +1,329 @@
+/*-
+ * Copyright (c) 2011 Nathan Whitehorn
+ * Copyright (c) 2014 Devin Teske <dteske@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/param.h>
+#include <archive.h>
+#include <ctype.h>
+#include <dialog.h>
+#include <dpv.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Data to process */
+static char *distdir = NULL;
+static struct archive *archive = NULL;
+static struct dpv_file_node *dists = NULL;
+
+/* Function prototypes */
+static void sig_int(int sig);
+static int count_files(const char *file);
+static int extract_files(struct dpv_file_node *file, int out);
+
+#if __FreeBSD_version <= 1000008 /* r232154: bump for libarchive update */
+#define archive_read_support_filter_all(x) \
+ archive_read_support_compression_all(x)
+#endif
+
+#define _errx(...) (end_dialog(), errx(__VA_ARGS__))
+
+int
+main(void)
+{
+ char *chrootdir;
+ char *distributions;
+ int retval;
+ size_t config_size = sizeof(struct dpv_config);
+ size_t file_node_size = sizeof(struct dpv_file_node);
+ size_t span;
+ struct dpv_config *config;
+ struct dpv_file_node *dist = dists;
+ static char backtitle[] = "FreeBSD Installer";
+ static char title[] = "Archive Extraction";
+ static char aprompt[] = "\n Overall Progress:";
+ static char pprompt[] = "Extracting distribution files...\n";
+ struct sigaction act;
+ char error[PATH_MAX + 512];
+
+ if ((distributions = getenv("DISTRIBUTIONS")) == NULL)
+ errx(EXIT_FAILURE, "DISTRIBUTIONS variable is not set");
+ if ((distdir = getenv("BSDINSTALL_DISTDIR")) == NULL)
+ distdir = __DECONST(char *, "");
+
+ /* Initialize dialog(3) */
+ init_dialog(stdin, stdout);
+ dialog_vars.backtitle = backtitle;
+ dlg_put_backtitle();
+
+ dialog_msgbox("",
+ "Checking distribution archives.\nPlease wait...", 4, 35, FALSE);
+
+ /*
+ * Parse $DISTRIBUTIONS into dpv(3) linked-list
+ */
+ while (*distributions != '\0') {
+ span = strcspn(distributions, "\t\n\v\f\r ");
+ if (span < 1) { /* currently on whitespace */
+ distributions++;
+ continue;
+ }
+
+ /* Allocate a new struct for the distribution */
+ if (dist == NULL) {
+ if ((dist = calloc(1, file_node_size)) == NULL)
+ _errx(EXIT_FAILURE, "Out of memory!");
+ dists = dist;
+ } else {
+ dist->next = calloc(1, file_node_size);
+ if (dist->next == NULL)
+ _errx(EXIT_FAILURE, "Out of memory!");
+ dist = dist->next;
+ }
+
+ /* Set path */
+ if ((dist->path = malloc(span + 1)) == NULL)
+ _errx(EXIT_FAILURE, "Out of memory!");
+ snprintf(dist->path, span + 1, "%s", distributions);
+ dist->path[span] = '\0';
+
+ /* Set display name */
+ dist->name = strrchr(dist->path, '/');
+ if (dist->name == NULL)
+ dist->name = dist->path;
+
+ /* Set initial length in files (-1 == error) */
+ dist->length = count_files(dist->path);
+ if (dist->length < 0) {
+ end_dialog();
+ return (EXIT_FAILURE);
+ }
+
+ distributions += span;
+ }
+
+ /* Optionally chdir(2) into $BSDINSTALL_CHROOT */
+ chrootdir = getenv("BSDINSTALL_CHROOT");
+ if (chrootdir != NULL && chdir(chrootdir) != 0) {
+ snprintf(error, sizeof(error),
+ "Could not change to directory %s: %s\n",
+ chrootdir, strerror(errno));
+ dialog_msgbox("Error", error, 0, 0, TRUE);
+ end_dialog();
+ return (EXIT_FAILURE);
+ }
+
+ /* Set cleanup routine for Ctrl-C action */
+ act.sa_handler = sig_int;
+ sigaction(SIGINT, &act, 0);
+
+ /*
+ * Hand off to dpv(3)
+ */
+ if ((config = calloc(1, config_size)) == NULL)
+ _errx(EXIT_FAILURE, "Out of memory!");
+ config->backtitle = backtitle;
+ config->title = title;
+ config->pprompt = pprompt;
+ config->aprompt = aprompt;
+ config->options |= DPV_WIDE_MODE;
+ config->label_size = -1;
+ config->action = extract_files;
+ config->status_solo =
+ "%10lli files read @ %'9.1f files/sec.";
+ config->status_many =
+ "%10lli files read @ %'9.1f files/sec. [%i/%i busy/wait]";
+ end_dialog();
+ retval = dpv(config, dists);
+
+ dpv_free();
+ while ((dist = dists) != NULL) {
+ dists = dist->next;
+ if (dist->path != NULL)
+ free(dist->path);
+ free(dist);
+ }
+
+ return (retval);
+}
+
+static void
+sig_int(int sig __unused)
+{
+ dpv_interrupt = TRUE;
+}
+
+/*
+ * Returns number of files in archive file. Parses $BSDINSTALL_DISTDIR/MANIFEST
+ * if it exists, otherwise uses archive(3) to read the archive file.
+ */
+static int
+count_files(const char *file)
+{
+ static FILE *manifest = NULL;
+ char *p;
+ int file_count;
+ int retval;
+ size_t span;
+ struct archive_entry *entry;
+ char line[512];
+ char path[PATH_MAX];
+ char errormsg[PATH_MAX + 512];
+
+ if (manifest == NULL) {
+ snprintf(path, sizeof(path), "%s/MANIFEST", distdir);
+ manifest = fopen(path, "r");
+ }
+
+ if (manifest != NULL) {
+ rewind(manifest);
+ while (fgets(line, sizeof(line), manifest) != NULL) {
+ p = &line[0];
+ span = strcspn(p, "\t") ;
+ if (span < 1 || strncmp(p, file, span) != 0)
+ continue;
+
+ /*
+ * We're at the right manifest line. The file count is
+ * in the third element
+ */
+ span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");
+ span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");
+ if (span > 0) {
+ file_count = (int)strtol(p, (char **)NULL, 10);
+ if (file_count == 0 && errno == EINVAL)
+ continue;
+ return (file_count);
+ }
+ }
+ }
+
+ /*
+ * Either no manifest, or manifest didn't mention this archive.
+ * Use archive(3) to read the archive, counting files within.
+ */
+ if ((archive = archive_read_new()) == NULL) {
+ snprintf(errormsg, sizeof(errormsg),
+ "Error: %s\n", archive_error_string(NULL));
+ dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
+ return (-1);
+ }
+ archive_read_support_format_all(archive);
+ archive_read_support_filter_all(archive);
+ snprintf(path, sizeof(path), "%s/%s", distdir, file);
+ retval = archive_read_open_filename(archive, path, 4096);
+ if (retval != ARCHIVE_OK) {
+ snprintf(errormsg, sizeof(errormsg),
+ "Error while extracting %s: %s\n", file,
+ archive_error_string(archive));
+ dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
+ archive = NULL;
+ return (-1);
+ }
+
+ file_count = 0;
+ while (archive_read_next_header(archive, &entry) == ARCHIVE_OK)
+ file_count++;
+ archive_read_free(archive);
+ archive = NULL;
+
+ return (file_count);
+}
+
+static int
+extract_files(struct dpv_file_node *file, int out __unused)
+{
+ int retval;
+ struct archive_entry *entry;
+ char path[PATH_MAX];
+ char errormsg[PATH_MAX + 512];
+
+ /* Open the archive if necessary */
+ if (archive == NULL) {
+ if ((archive = archive_read_new()) == NULL) {
+ snprintf(errormsg, sizeof(errormsg),
+ "Error: %s\n", archive_error_string(NULL));
+ dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
+ dpv_abort = 1;
+ return (-1);
+ }
+ archive_read_support_format_all(archive);
+ archive_read_support_filter_all(archive);
+ snprintf(path, sizeof(path), "%s/%s", distdir, file->path);
+ retval = archive_read_open_filename(archive, path, 4096);
+ if (retval != 0) {
+ snprintf(errormsg, sizeof(errormsg),
+ "Error opening %s: %s\n", file->name,
+ archive_error_string(archive));
+ dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
+ file->status = DPV_STATUS_FAILED;
+ dpv_abort = 1;
+ return (-1);
+ }
+ }
+
+ /* Read the next archive header */
+ retval = archive_read_next_header(archive, &entry);
+
+ /* If that went well, perform the extraction */
+ if (retval == ARCHIVE_OK)
+ retval = archive_read_extract(archive, entry,
+ ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_OWNER |
+ ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL |
+ ARCHIVE_EXTRACT_XATTR | ARCHIVE_EXTRACT_FFLAGS);
+
+ /* Test for either EOF or error */
+ if (retval == ARCHIVE_EOF) {
+ archive_read_free(archive);
+ archive = NULL;
+ file->status = DPV_STATUS_DONE;
+ return (100);
+ } else if (retval != ARCHIVE_OK) {
+ snprintf(errormsg, sizeof(errormsg),
+ "Error while extracting %s: %s\n", file->name,
+ archive_error_string(archive));
+ dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
+ file->status = DPV_STATUS_FAILED;
+ dpv_abort = 1;
+ return (-1);
+ }
+
+ dpv_overall_read++;
+ file->read++;
+
+ /* Calculate [overall] percentage of completion (if possible) */
+ if (file->length >= 0)
+ return (file->read * 100 / file->length);
+ else
+ return (-1);
+}
diff --git a/usr.sbin/bsdinstall/distfetch/Makefile b/usr.sbin/bsdinstall/distfetch/Makefile
new file mode 100644
index 0000000..1620c9b
--- /dev/null
+++ b/usr.sbin/bsdinstall/distfetch/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+BINDIR= ${LIBEXECDIR}/bsdinstall
+PROG= distfetch
+LIBADD= fetch ncursesw dialog m
+
+WARNS?= 6
+MAN=
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdinstall/distfetch/Makefile.depend b/usr.sbin/bsdinstall/distfetch/Makefile.depend
new file mode 100644
index 0000000..5dca986
--- /dev/null
+++ b/usr.sbin/bsdinstall/distfetch/Makefile.depend
@@ -0,0 +1,24 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libdialog \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libfetch \
+ lib/msun \
+ lib/ncurses/ncursesw \
+ secure/lib/libcrypto \
+ secure/lib/libssl \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdinstall/distfetch/distfetch.c b/usr.sbin/bsdinstall/distfetch/distfetch.c
new file mode 100644
index 0000000..219847d
--- /dev/null
+++ b/usr.sbin/bsdinstall/distfetch/distfetch.c
@@ -0,0 +1,223 @@
+/*-
+ * Copyright (c) 2011 Nathan Whitehorn
+ * Copyright (c) 2014 Devin Teske <dteske@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/param.h>
+#include <ctype.h>
+#include <err.h>
+#include <dialog.h>
+#include <errno.h>
+#include <fetch.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static int fetch_files(int nfiles, char **urls);
+
+int
+main(void)
+{
+ char *diststring;
+ char **urls;
+ int i;
+ int ndists = 0;
+ int nfetched;
+ char error[PATH_MAX + 512];
+
+ if (getenv("DISTRIBUTIONS") == NULL)
+ errx(EXIT_FAILURE, "DISTRIBUTIONS variable is not set");
+
+ diststring = strdup(getenv("DISTRIBUTIONS"));
+ for (i = 0; diststring[i] != 0; i++)
+ if (isspace(diststring[i]) && !isspace(diststring[i+1]))
+ ndists++;
+ ndists++; /* Last one */
+
+ urls = calloc(ndists, sizeof(const char *));
+ if (urls == NULL) {
+ free(diststring);
+ errx(EXIT_FAILURE, "Out of memory!");
+ }
+
+ init_dialog(stdin, stdout);
+ dialog_vars.backtitle = __DECONST(char *, "FreeBSD Installer");
+ dlg_put_backtitle();
+
+ for (i = 0; i < ndists; i++) {
+ urls[i] = malloc(PATH_MAX);
+ snprintf(urls[i], PATH_MAX, "%s/%s",
+ getenv("BSDINSTALL_DISTSITE"), strsep(&diststring, " \t"));
+ }
+
+ if (chdir(getenv("BSDINSTALL_DISTDIR")) != 0) {
+ snprintf(error, sizeof(error),
+ "Could not change to directory %s: %s\n",
+ getenv("BSDINSTALL_DISTDIR"), strerror(errno));
+ dialog_msgbox("Error", error, 0, 0, TRUE);
+ end_dialog();
+ return (EXIT_FAILURE);
+ }
+
+ nfetched = fetch_files(ndists, urls);
+
+ end_dialog();
+
+ free(diststring);
+ for (i = 0; i < ndists; i++)
+ free(urls[i]);
+ free(urls);
+
+ return ((nfetched == ndists) ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+static int
+fetch_files(int nfiles, char **urls)
+{
+ FILE *fetch_out;
+ FILE *file_out;
+ const char **items;
+ int i;
+ int last_progress;
+ int nsuccess = 0; /* Number of files successfully downloaded */
+ int progress = 0;
+ size_t chunk;
+ off_t current_bytes;
+ off_t fsize;
+ off_t total_bytes;
+ char status[8];
+ struct url_stat ustat;
+ char errormsg[PATH_MAX + 512];
+ uint8_t block[4096];
+
+ /* Make the transfer list for dialog */
+ items = calloc(sizeof(char *), nfiles * 2);
+ if (items == NULL)
+ errx(EXIT_FAILURE, "Out of memory!");
+
+ for (i = 0; i < nfiles; i++) {
+ items[i*2] = strrchr(urls[i], '/');
+ if (items[i*2] != NULL)
+ items[i*2]++;
+ else
+ items[i*2] = urls[i];
+ items[i*2 + 1] = "Pending";
+ }
+
+ dialog_msgbox("", "Connecting to server.\nPlease wait...", 0, 0, FALSE);
+
+ /* Try to stat all the files */
+ total_bytes = 0;
+ for (i = 0; i < nfiles; i++) {
+ if (fetchStatURL(urls[i], &ustat, "") == 0 && ustat.size > 0)
+ total_bytes += ustat.size;
+ }
+
+ current_bytes = 0;
+ for (i = 0; i < nfiles; i++) {
+ last_progress = progress;
+ if (total_bytes == 0)
+ progress = (i*100)/nfiles;
+
+ fetchLastErrCode = 0;
+ fetch_out = fetchXGetURL(urls[i], &ustat, "");
+ if (fetch_out == NULL) {
+ snprintf(errormsg, sizeof(errormsg),
+ "Error while fetching %s: %s\n", urls[i],
+ fetchLastErrString);
+ items[i*2 + 1] = "Failed";
+ dialog_msgbox("Fetch Error", errormsg, 0, 0,
+ TRUE);
+ continue;
+ }
+
+ items[i*2 + 1] = "In Progress";
+ fsize = 0;
+ file_out = fopen(items[i*2], "w+");
+ if (file_out == NULL) {
+ snprintf(errormsg, sizeof(errormsg),
+ "Error while fetching %s: %s\n",
+ urls[i], strerror(errno));
+ items[i*2 + 1] = "Failed";
+ dialog_msgbox("Fetch Error", errormsg, 0, 0,
+ TRUE);
+ fclose(fetch_out);
+ continue;
+ }
+
+ while ((chunk = fread(block, 1, sizeof(block), fetch_out))
+ > 0) {
+ if (fwrite(block, 1, chunk, file_out) < chunk)
+ break;
+
+ current_bytes += chunk;
+ fsize += chunk;
+
+ if (total_bytes > 0) {
+ last_progress = progress;
+ progress = (current_bytes*100)/total_bytes;
+ }
+
+ if (ustat.size > 0) {
+ snprintf(status, sizeof(status), "-%jd",
+ (fsize*100)/ustat.size);
+ items[i*2 + 1] = status;
+ }
+
+ if (progress > last_progress)
+ dialog_mixedgauge("Fetching Distribution",
+ "Fetching distribution files...", 0, 0,
+ progress, nfiles,
+ __DECONST(char **, items));
+ }
+
+ if (ustat.size > 0 && fsize < ustat.size) {
+ if (fetchLastErrCode == 0)
+ snprintf(errormsg, sizeof(errormsg),
+ "Error while fetching %s: %s\n",
+ urls[i], strerror(errno));
+ else
+ snprintf(errormsg, sizeof(errormsg),
+ "Error while fetching %s: %s\n",
+ urls[i], fetchLastErrString);
+ items[i*2 + 1] = "Failed";
+ dialog_msgbox("Fetch Error", errormsg, 0, 0,
+ TRUE);
+ } else {
+ items[i*2 + 1] = "Done";
+ nsuccess++;
+ }
+
+ fclose(fetch_out);
+ fclose(file_out);
+ }
+
+ free(items);
+ return (nsuccess);
+}
diff --git a/usr.sbin/bsdinstall/partedit/Makefile b/usr.sbin/bsdinstall/partedit/Makefile
new file mode 100644
index 0000000..21d842e
--- /dev/null
+++ b/usr.sbin/bsdinstall/partedit/Makefile
@@ -0,0 +1,24 @@
+# $FreeBSD$
+
+BINDIR= ${LIBEXECDIR}/bsdinstall
+PROG= partedit
+LINKS= ${BINDIR}/partedit ${BINDIR}/autopart \
+ ${BINDIR}/partedit ${BINDIR}/scriptedpart
+SYMLINKS= ${BINDIR}/partedit /usr/sbin/sade
+LIBADD+= geom ncursesw util dialog m
+
+PARTEDIT_ARCH= ${MACHINE}
+.if ${MACHINE} == "i386" || ${MACHINE} == "amd64"
+PARTEDIT_ARCH= x86
+.endif
+.if !exists(partedit_${PARTEDIT_ARCH}.c)
+PARTEDIT_ARCH= generic
+.endif
+
+SRCS= diskeditor.c partedit.c gpart_ops.c partedit_${PARTEDIT_ARCH}.c \
+ part_wizard.c scripted.c
+
+WARNS?= 3
+MAN= sade.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdinstall/partedit/Makefile.depend b/usr.sbin/bsdinstall/partedit/Makefile.depend
new file mode 100644
index 0000000..c0a768d
--- /dev/null
+++ b/usr.sbin/bsdinstall/partedit/Makefile.depend
@@ -0,0 +1,25 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libdialog \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libexpat \
+ lib/libgeom \
+ lib/libsbuf \
+ lib/libutil \
+ lib/msun \
+ lib/ncurses/ncursesw \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdinstall/partedit/diskeditor.c b/usr.sbin/bsdinstall/partedit/diskeditor.c
new file mode 100644
index 0000000..4840ae7
--- /dev/null
+++ b/usr.sbin/bsdinstall/partedit/diskeditor.c
@@ -0,0 +1,290 @@
+/*-
+ * Copyright (c) 2011 Nathan Whitehorn
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <stdio.h>
+#include <unistd.h>
+#include <libutil.h>
+#include <dialog.h>
+#include <dlg_keys.h>
+
+#include "diskeditor.h"
+
+static void
+print_partedit_item(WINDOW *partitions, struct partedit_item *items,
+ int item, int nscroll, int selected)
+{
+ chtype attr = A_NORMAL;
+ char sizetext[16];
+ int y = item - nscroll + 1;
+
+ wattrset(partitions, selected ? item_selected_attr : item_attr);
+ wmove(partitions, y, MARGIN + items[item].indentation*2);
+ dlg_print_text(partitions, items[item].name, 10, &attr);
+ wmove(partitions, y, 17);
+ wattrset(partitions, item_attr);
+
+ humanize_number(sizetext, 7, items[item].size, "B", HN_AUTOSCALE,
+ HN_DECIMAL);
+ dlg_print_text(partitions, sizetext, 8, &attr);
+ wmove(partitions, y, 25);
+ dlg_print_text(partitions, items[item].type, 15, &attr);
+ wmove(partitions, y, 40);
+ if (items[item].mountpoint != NULL)
+ dlg_print_text(partitions, items[item].mountpoint, 8, &attr);
+}
+
+int
+diskeditor_show(const char *title, const char *cprompt,
+ struct partedit_item *items, int nitems, int *selected, int *nscroll)
+{
+ WINDOW *dialog, *partitions;
+ char *prompt;
+ const char *buttons[] =
+ { "Create", "Delete", "Modify", "Revert", "Auto", "Finish", NULL };
+ const char *help_text[] = {
+ "Add a new partition", "Delete selected partition or partitions",
+ "Change partition type or mountpoint",
+ "Revert changes to disk setup", "Use guided partitioning tool",
+ "Exit partitioner (will ask whether to save changes)", NULL };
+ int x, y;
+ int i;
+ int height, width, min_width;
+ int partlist_height, partlist_width;
+ int cur_scroll = 0;
+ int key, fkey;
+ int cur_button = 5, cur_part = 0;
+ int result = DLG_EXIT_UNKNOWN;
+
+ static DLG_KEYS_BINDING binding[] = {
+ ENTERKEY_BINDINGS,
+ DLG_KEYS_DATA( DLGK_ENTER, ' ' ),
+ DLG_KEYS_DATA( DLGK_ITEM_NEXT, KEY_DOWN ),
+ DLG_KEYS_DATA( DLGK_ITEM_PREV, KEY_UP ),
+ DLG_KEYS_DATA( DLGK_FIELD_NEXT, KEY_RIGHT ),
+ DLG_KEYS_DATA( DLGK_FIELD_NEXT, TAB ),
+ DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_BTAB ),
+ DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_LEFT ),
+
+ SCROLLKEY_BINDINGS,
+ END_KEYS_BINDING
+ };
+
+ static DLG_KEYS_BINDING binding2[] = {
+ INPUTSTR_BINDINGS,
+ ENTERKEY_BINDINGS,
+ DLG_KEYS_DATA( DLGK_FIELD_NEXT, TAB ),
+ DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_BTAB ),
+ DLG_KEYS_DATA( DLGK_ITEM_NEXT, CHR_NEXT ),
+ DLG_KEYS_DATA( DLGK_ITEM_NEXT, KEY_DOWN ),
+ DLG_KEYS_DATA( DLGK_ITEM_NEXT, KEY_NEXT ),
+ DLG_KEYS_DATA( DLGK_ITEM_PREV, CHR_PREVIOUS ),
+ DLG_KEYS_DATA( DLGK_ITEM_PREV, KEY_PREVIOUS ),
+ DLG_KEYS_DATA( DLGK_ITEM_PREV, KEY_UP ),
+ DLG_KEYS_DATA( DLGK_PAGE_NEXT, KEY_NPAGE ),
+ DLG_KEYS_DATA( DLGK_PAGE_PREV, KEY_PPAGE ),
+ END_KEYS_BINDING
+ };
+
+ /*
+ * Set up editor window.
+ */
+ prompt = dlg_strclone(cprompt);
+
+ min_width = 50;
+ height = width = 0;
+ partlist_height = 10;
+ dlg_tab_correct_str(prompt);
+ dlg_button_layout(buttons, &min_width);
+ dlg_auto_size(title, prompt, &height, &width, 2, min_width);
+ height += partlist_height;
+ partlist_width = width - 2*MARGIN;
+ dlg_print_size(height, width);
+ dlg_ctl_size(height, width);
+
+ x = dlg_box_x_ordinate(width);
+ y = dlg_box_y_ordinate(height);
+
+ dialog = dlg_new_window(height, width, y, x);
+ dlg_register_window(dialog, "diskeditorbox", binding);
+ dlg_register_buttons(dialog, "diskeditorbox", buttons);
+
+ dlg_draw_box(dialog, 0, 0, height, width, dialog_attr, border_attr);
+ dlg_draw_bottom_box(dialog);
+ dlg_draw_title(dialog, title);
+ wattrset(dialog, dialog_attr);
+
+ /* Partition list sub-window */
+ partitions = dlg_sub_window(dialog, partlist_height, partlist_width,
+ y + 3, x + 1);
+ dlg_register_window(partitions, "partlist", binding2);
+ dlg_register_buttons(partitions, "partlist", buttons);
+ wattrset(partitions, menubox_attr);
+
+ dlg_item_help(help_text[cur_button]);
+ dlg_draw_buttons(dialog, height - 2*MARGIN, 0, buttons,
+ cur_button, FALSE, width);
+ dlg_print_autowrap(dialog, prompt, height, width);
+
+ if (selected != NULL)
+ cur_part = *selected;
+ if (nscroll != NULL)
+ cur_scroll = *nscroll;
+ if (cur_part - cur_scroll >= partlist_height - 2 ||
+ cur_part - cur_scroll < 0)
+ cur_scroll = cur_part;
+
+repaint:
+ dlg_draw_box(dialog, 3, 1, partlist_height, partlist_width,
+ menubox_border_attr, menubox_attr);
+ for (i = cur_scroll; i < MIN(cur_scroll + partlist_height - 2, nitems);
+ i++)
+ print_partedit_item(partitions, items, i, cur_scroll,
+ i == cur_part);
+ if (nitems > partlist_height - 2)
+ dlg_draw_arrows(partitions, cur_scroll > 0,
+ nitems > cur_scroll + partlist_height - 2,
+ partlist_width - 5, 0, partlist_height - 1);
+ wrefresh(partitions);
+
+ while (result == DLG_EXIT_UNKNOWN) {
+ key = dlg_mouse_wgetch(dialog, &fkey);
+ if ((i = dlg_char_to_button(key, buttons)) >= 0) {
+ cur_button = i;
+ dlg_item_help(help_text[cur_button]);
+ dlg_draw_buttons(dialog, height - 2*MARGIN, 0, buttons,
+ cur_button, FALSE, width);
+ break;
+ }
+
+ if (!fkey)
+ continue;
+
+ switch (key) {
+ case DLGK_FIELD_NEXT:
+ cur_button = dlg_next_button(buttons, cur_button);
+ if (cur_button < 0)
+ cur_button = 0;
+ dlg_item_help(help_text[cur_button]);
+ dlg_draw_buttons(dialog, height - 2*MARGIN, 0, buttons,
+ cur_button, FALSE, width);
+ break;
+ case DLGK_FIELD_PREV:
+ cur_button = dlg_prev_button(buttons, cur_button);
+ if (cur_button < 0)
+ cur_button = 0;
+ dlg_item_help(help_text[cur_button]);
+ dlg_draw_buttons(dialog, height - 2*MARGIN, 0, buttons,
+ cur_button, FALSE, width);
+ break;
+ case DLGK_ITEM_NEXT:
+ if (cur_part == nitems - 1)
+ break; /* End of list */
+
+ /* Deselect old item */
+ print_partedit_item(partitions, items, cur_part,
+ cur_scroll, 0);
+ /* Select new item */
+ cur_part++;
+ if (cur_part - cur_scroll >= partlist_height - 2) {
+ cur_scroll = cur_part;
+ goto repaint;
+ }
+ print_partedit_item(partitions, items, cur_part,
+ cur_scroll, 1);
+ wrefresh(partitions);
+ break;
+ case DLGK_ITEM_PREV:
+ if (cur_part == 0)
+ break; /* Start of list */
+
+ /* Deselect old item */
+ print_partedit_item(partitions, items, cur_part,
+ cur_scroll, 0);
+ /* Select new item */
+ cur_part--;
+ if (cur_part - cur_scroll < 0) {
+ cur_scroll = cur_part;
+ goto repaint;
+ }
+ print_partedit_item(partitions, items, cur_part,
+ cur_scroll, 1);
+ wrefresh(partitions);
+ break;
+ case DLGK_PAGE_NEXT:
+ cur_scroll += (partlist_height - 2);
+ if (cur_scroll + partlist_height - 2 >= nitems)
+ cur_scroll = nitems - (partlist_height - 2);
+ if (cur_scroll < 0)
+ cur_scroll = 0;
+ if (cur_part < cur_scroll)
+ cur_part = cur_scroll;
+ goto repaint;
+ case DLGK_PAGE_PREV:
+ cur_scroll -= (partlist_height - 2);
+ if (cur_scroll < 0)
+ cur_scroll = 0;
+ if (cur_part >= cur_scroll + partlist_height - 2)
+ cur_part = cur_scroll;
+ goto repaint;
+ case DLGK_PAGE_FIRST:
+ cur_scroll = 0;
+ cur_part = cur_scroll;
+ goto repaint;
+ case DLGK_PAGE_LAST:
+ cur_scroll = nitems - (partlist_height - 2);
+ if (cur_scroll < 0)
+ cur_scroll = 0;
+ cur_part = cur_scroll;
+ goto repaint;
+ case DLGK_ENTER:
+ goto done;
+ default:
+ if (is_DLGK_MOUSE(key)) {
+ cur_button = key - M_EVENT;
+ dlg_item_help(help_text[cur_button]);
+ dlg_draw_buttons(dialog, height - 2*MARGIN, 0,
+ buttons, cur_button, FALSE, width);
+ goto done;
+ }
+ break;
+ }
+ }
+
+done:
+ if (selected != NULL)
+ *selected = cur_part;
+ if (nscroll != NULL)
+ *nscroll = cur_scroll;
+
+ dlg_del_window(partitions);
+ dlg_del_window(dialog);
+ dlg_mouse_free_regions();
+
+ return (cur_button);
+}
+
diff --git a/usr.sbin/bsdinstall/partedit/diskeditor.h b/usr.sbin/bsdinstall/partedit/diskeditor.h
new file mode 100644
index 0000000..f122267
--- /dev/null
+++ b/usr.sbin/bsdinstall/partedit/diskeditor.h
@@ -0,0 +1,47 @@
+/*-
+ * Copyright (c) 2011 Nathan Whitehorn
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 _PARTEDIT_DISKEDITOR_H
+#define _PARTEDIT_DISKEDITOR_H
+
+#include <inttypes.h>
+
+struct partedit_item {
+ int indentation;
+ const char *name;
+ intmax_t size;
+ const char *type;
+ char *mountpoint;
+
+ void *cookie;
+};
+
+int diskeditor_show(const char *title, const char *prompt,
+ struct partedit_item *items, int nitems, int *selected, int *scroll);
+
+#endif
diff --git a/usr.sbin/bsdinstall/partedit/gpart_ops.c b/usr.sbin/bsdinstall/partedit/gpart_ops.c
new file mode 100644
index 0000000..48bbb68
--- /dev/null
+++ b/usr.sbin/bsdinstall/partedit/gpart_ops.c
@@ -0,0 +1,1386 @@
+/*-
+ * Copyright (c) 2011 Nathan Whitehorn
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/stat.h>
+#include <errno.h>
+#include <libutil.h>
+#include <inttypes.h>
+
+#include <libgeom.h>
+#include <dialog.h>
+#include <dlg_keys.h>
+
+#include "partedit.h"
+
+#define GPART_FLAGS "x" /* Do not commit changes by default */
+
+static void
+gpart_show_error(const char *title, const char *explanation, const char *errstr)
+{
+ char *errmsg;
+ char message[512];
+ int error;
+
+ if (explanation == NULL)
+ explanation = "";
+
+ error = strtol(errstr, &errmsg, 0);
+ if (errmsg != errstr) {
+ while (errmsg[0] == ' ')
+ errmsg++;
+ if (errmsg[0] != '\0')
+ sprintf(message, "%s%s. %s", explanation,
+ strerror(error), errmsg);
+ else
+ sprintf(message, "%s%s", explanation, strerror(error));
+ } else {
+ sprintf(message, "%s%s", explanation, errmsg);
+ }
+
+ dialog_msgbox(title, message, 0, 0, TRUE);
+}
+
+static int
+scheme_supports_labels(const char *scheme)
+{
+ if (strcmp(scheme, "APM") == 0)
+ return (1);
+ if (strcmp(scheme, "GPT") == 0)
+ return (1);
+ if (strcmp(scheme, "PC98") == 0)
+ return (1);
+
+ return (0);
+}
+
+static void
+newfs_command(const char *fstype, char *command, int use_default)
+{
+ if (strcmp(fstype, "freebsd-ufs") == 0) {
+ int i;
+ DIALOG_LISTITEM items[] = {
+ {"UFS1", "UFS Version 1",
+ "Use version 1 of the UFS file system instead "
+ "of version 2 (not recommended)", 0 },
+ {"SU", "Softupdates",
+ "Enable softupdates (default)", 1 },
+ {"SUJ", "Softupdates journaling",
+ "Enable file system journaling (default - "
+ "turn off for SSDs)", 1 },
+ {"TRIM", "Enable SSD TRIM support",
+ "Enable TRIM support, useful on solid-state drives",
+ 0 },
+ };
+
+ if (!use_default) {
+ int choice;
+ choice = dlg_checklist("UFS Options", "", 0, 0, 0,
+ sizeof(items)/sizeof(items[0]), items, NULL,
+ FLAG_CHECK, &i);
+ if (choice == 1) /* Cancel */
+ return;
+ }
+
+ strcpy(command, "newfs ");
+ for (i = 0; i < (int)(sizeof(items)/sizeof(items[0])); i++) {
+ if (items[i].state == 0)
+ continue;
+ if (strcmp(items[i].name, "UFS1") == 0)
+ strcat(command, "-O1 ");
+ else if (strcmp(items[i].name, "SU") == 0)
+ strcat(command, "-U ");
+ else if (strcmp(items[i].name, "SUJ") == 0)
+ strcat(command, "-j ");
+ else if (strcmp(items[i].name, "TRIM") == 0)
+ strcat(command, "-t ");
+ }
+ } else if (strcmp(fstype, "freebsd-zfs") == 0) {
+ int i;
+ DIALOG_LISTITEM items[] = {
+ {"fletcher4", "checksum algorithm: fletcher4",
+ "Use fletcher4 for data integrity checking. "
+ "(default)", 1 },
+ {"fletcher2", "checksum algorithm: fletcher2",
+ "Use fletcher2 for data integrity checking. "
+ "(not recommended)", 0 },
+ {"sha256", "checksum algorithm: sha256",
+ "Use sha256 for data integrity checking. "
+ "(not recommended)", 0 },
+ {"atime", "Update atimes for files",
+ "Disable atime update", 0 },
+ };
+
+ if (!use_default) {
+ int choice;
+ choice = dlg_checklist("ZFS Options", "", 0, 0, 0,
+ sizeof(items)/sizeof(items[0]), items, NULL,
+ FLAG_CHECK, &i);
+ if (choice == 1) /* Cancel */
+ return;
+ }
+
+ strcpy(command, "zpool create -f -m none ");
+ if (getenv("BSDINSTALL_TMPBOOT") != NULL) {
+ char zfsboot_path[MAXPATHLEN];
+ sprintf(zfsboot_path, "%s/zfs",
+ getenv("BSDINSTALL_TMPBOOT"));
+ mkdir(zfsboot_path, S_IRWXU | S_IRGRP | S_IXGRP |
+ S_IROTH | S_IXOTH);
+ sprintf(command, "%s -o cachefile=%s/zpool.cache ",
+ command, zfsboot_path);
+ }
+ for (i = 0; i < (int)(sizeof(items)/sizeof(items[0])); i++) {
+ if (items[i].state == 0)
+ continue;
+ if (strcmp(items[i].name, "fletcher4") == 0)
+ strcat(command, "-O checksum=fletcher4 ");
+ else if (strcmp(items[i].name, "fletcher2") == 0)
+ strcat(command, "-O checksum=fletcher2 ");
+ else if (strcmp(items[i].name, "sha256") == 0)
+ strcat(command, "-O checksum=sha256 ");
+ else if (strcmp(items[i].name, "atime") == 0)
+ strcat(command, "-O atime=off ");
+ }
+ } else if (strcmp(fstype, "fat32") == 0 || strcmp(fstype, "efi") == 0) {
+ int i;
+ DIALOG_LISTITEM items[] = {
+ {"FAT32", "FAT Type 32",
+ "Create a FAT32 filesystem (default)", 1 },
+ {"FAT16", "FAT Type 16",
+ "Create a FAT16 filesystem", 0 },
+ {"FAT12", "FAT Type 12",
+ "Create a FAT12 filesystem", 0 },
+ };
+
+ if (!use_default) {
+ int choice;
+ choice = dlg_checklist("FAT Options", "", 0, 0, 0,
+ sizeof(items)/sizeof(items[0]), items, NULL,
+ FLAG_RADIO, &i);
+ if (choice == 1) /* Cancel */
+ return;
+ }
+
+ strcpy(command, "newfs_msdos ");
+ for (i = 0; i < (int)(sizeof(items)/sizeof(items[0])); i++) {
+ if (items[i].state == 0)
+ continue;
+ if (strcmp(items[i].name, "FAT32") == 0)
+ strcat(command, "-F 32 ");
+ else if (strcmp(items[i].name, "FAT16") == 0)
+ strcat(command, "-F 16 ");
+ else if (strcmp(items[i].name, "FAT12") == 0)
+ strcat(command, "-F 12 ");
+ }
+ } else {
+ if (!use_default)
+ dialog_msgbox("Error", "No configurable options exist "
+ "for this filesystem.", 0, 0, TRUE);
+ command[0] = '\0';
+ }
+}
+
+const char *
+choose_part_type(const char *def_scheme)
+{
+ int cancel, choice;
+ const char *scheme = NULL;
+
+ DIALOG_LISTITEM items[] = {
+ {"APM", "Apple Partition Map",
+ "Bootable on PowerPC Apple Hardware", 0 },
+ {"BSD", "BSD Labels",
+ "Bootable on most x86 systems", 0 },
+ {"GPT", "GUID Partition Table",
+ "Bootable on most x86 systems", 0 },
+ {"MBR", "DOS Partitions",
+ "Bootable on most x86 systems", 0 },
+ {"PC98", "NEC PC9801 Partition Table",
+ "Bootable on NEC PC9801 systems", 0 },
+ {"VTOC8", "Sun VTOC8 Partition Table",
+ "Bootable on Sun SPARC systems", 0 },
+ };
+
+parttypemenu:
+ dialog_vars.default_item = __DECONST(char *, def_scheme);
+ cancel = dlg_menu("Partition Scheme",
+ "Select a partition scheme for this volume:", 0, 0, 0,
+ sizeof(items) / sizeof(items[0]), items, &choice, NULL);
+ dialog_vars.default_item = NULL;
+
+ if (cancel)
+ return NULL;
+
+ if (!is_scheme_bootable(items[choice].name)) {
+ char message[512];
+ sprintf(message, "This partition scheme (%s) is not "
+ "bootable on this platform. Are you sure you want "
+ "to proceed?", items[choice].name);
+ dialog_vars.defaultno = TRUE;
+ cancel = dialog_yesno("Warning", message, 0, 0);
+ dialog_vars.defaultno = FALSE;
+ if (cancel) /* cancel */
+ goto parttypemenu;
+ }
+
+ scheme = items[choice].name;
+
+ return scheme;
+}
+
+int
+gpart_partition(const char *lg_name, const char *scheme)
+{
+ int cancel;
+ struct gctl_req *r;
+ const char *errstr;
+
+schememenu:
+ if (scheme == NULL) {
+ scheme = choose_part_type(default_scheme());
+
+ if (scheme == NULL)
+ return (-1);
+
+ if (!is_scheme_bootable(scheme)) {
+ char message[512];
+ sprintf(message, "This partition scheme (%s) is not "
+ "bootable on this platform. Are you sure you want "
+ "to proceed?", scheme);
+ dialog_vars.defaultno = TRUE;
+ cancel = dialog_yesno("Warning", message, 0, 0);
+ dialog_vars.defaultno = FALSE;
+ if (cancel) { /* cancel */
+ /* Reset scheme so user can choose another */
+ scheme = NULL;
+ goto schememenu;
+ }
+ }
+ }
+
+ r = gctl_get_handle();
+ gctl_ro_param(r, "class", -1, "PART");
+ gctl_ro_param(r, "arg0", -1, lg_name);
+ gctl_ro_param(r, "flags", -1, GPART_FLAGS);
+ gctl_ro_param(r, "scheme", -1, scheme);
+ gctl_ro_param(r, "verb", -1, "create");
+
+ errstr = gctl_issue(r);
+ if (errstr != NULL && errstr[0] != '\0') {
+ gpart_show_error("Error", NULL, errstr);
+ gctl_free(r);
+ scheme = NULL;
+ goto schememenu;
+ }
+ gctl_free(r);
+
+ if (bootcode_path(scheme) != NULL)
+ get_part_metadata(lg_name, 1)->bootcode = 1;
+ return (0);
+}
+
+static void
+gpart_activate(struct gprovider *pp)
+{
+ struct gconfig *gc;
+ struct gctl_req *r;
+ const char *errstr, *scheme;
+ const char *attribute = NULL;
+ intmax_t idx;
+
+ /*
+ * Some partition schemes need this partition to be marked 'active'
+ * for it to be bootable.
+ */
+ LIST_FOREACH(gc, &pp->lg_geom->lg_config, lg_config) {
+ if (strcmp(gc->lg_name, "scheme") == 0) {
+ scheme = gc->lg_val;
+ break;
+ }
+ }
+
+ if (strcmp(scheme, "MBR") == 0 || strcmp(scheme, "EBR") == 0 ||
+ strcmp(scheme, "PC98") == 0)
+ attribute = "active";
+ else
+ return;
+
+ LIST_FOREACH(gc, &pp->lg_config, lg_config) {
+ if (strcmp(gc->lg_name, "index") == 0) {
+ idx = atoi(gc->lg_val);
+ break;
+ }
+ }
+
+ r = gctl_get_handle();
+ gctl_ro_param(r, "class", -1, "PART");
+ gctl_ro_param(r, "arg0", -1, pp->lg_geom->lg_name);
+ gctl_ro_param(r, "verb", -1, "set");
+ gctl_ro_param(r, "attrib", -1, attribute);
+ gctl_ro_param(r, "index", sizeof(idx), &idx);
+
+ errstr = gctl_issue(r);
+ if (errstr != NULL && errstr[0] != '\0')
+ gpart_show_error("Error", "Error marking partition active:",
+ errstr);
+ gctl_free(r);
+}
+
+void
+gpart_set_root(const char *lg_name, const char *attribute)
+{
+ struct gctl_req *r;
+ const char *errstr;
+
+ r = gctl_get_handle();
+ gctl_ro_param(r, "class", -1, "PART");
+ gctl_ro_param(r, "arg0", -1, lg_name);
+ gctl_ro_param(r, "flags", -1, "C");
+ gctl_ro_param(r, "verb", -1, "set");
+ gctl_ro_param(r, "attrib", -1, attribute);
+
+ errstr = gctl_issue(r);
+ if (errstr != NULL && errstr[0] != '\0')
+ gpart_show_error("Error", "Error setting parameter on disk:",
+ errstr);
+ gctl_free(r);
+}
+
+static void
+gpart_bootcode(struct ggeom *gp)
+{
+ const char *bootcode;
+ struct gconfig *gc;
+ struct gctl_req *r;
+ const char *errstr, *scheme;
+ uint8_t *boot;
+ size_t bootsize, bytes;
+ int bootfd;
+
+ /*
+ * Write default bootcode to the newly partitioned disk, if that
+ * applies on this platform.
+ */
+ LIST_FOREACH(gc, &gp->lg_config, lg_config) {
+ if (strcmp(gc->lg_name, "scheme") == 0) {
+ scheme = gc->lg_val;
+ break;
+ }
+ }
+
+ bootcode = bootcode_path(scheme);
+ if (bootcode == NULL)
+ return;
+
+ bootfd = open(bootcode, O_RDONLY);
+ if (bootfd < 0) {
+ dialog_msgbox("Bootcode Error", strerror(errno), 0, 0,
+ TRUE);
+ return;
+ }
+
+ bootsize = lseek(bootfd, 0, SEEK_END);
+ boot = malloc(bootsize);
+ lseek(bootfd, 0, SEEK_SET);
+ bytes = 0;
+ while (bytes < bootsize)
+ bytes += read(bootfd, boot + bytes, bootsize - bytes);
+ close(bootfd);
+
+ r = gctl_get_handle();
+ gctl_ro_param(r, "class", -1, "PART");
+ gctl_ro_param(r, "arg0", -1, gp->lg_name);
+ gctl_ro_param(r, "verb", -1, "bootcode");
+ gctl_ro_param(r, "bootcode", bootsize, boot);
+
+ errstr = gctl_issue(r);
+ if (errstr != NULL && errstr[0] != '\0')
+ gpart_show_error("Bootcode Error", NULL, errstr);
+ gctl_free(r);
+ free(boot);
+}
+
+static void
+gpart_partcode(struct gprovider *pp, const char *fstype)
+{
+ struct gconfig *gc;
+ const char *scheme;
+ const char *indexstr;
+ char message[255], command[255];
+
+ LIST_FOREACH(gc, &pp->lg_geom->lg_config, lg_config) {
+ if (strcmp(gc->lg_name, "scheme") == 0) {
+ scheme = gc->lg_val;
+ break;
+ }
+ }
+
+ /* Make sure this partition scheme needs partcode on this platform */
+ if (partcode_path(scheme, fstype) == NULL)
+ return;
+
+ LIST_FOREACH(gc, &pp->lg_config, lg_config) {
+ if (strcmp(gc->lg_name, "index") == 0) {
+ indexstr = gc->lg_val;
+ break;
+ }
+ }
+
+ /* Shell out to gpart for partcode for now */
+ sprintf(command, "gpart bootcode -p %s -i %s %s",
+ partcode_path(scheme, fstype), indexstr, pp->lg_geom->lg_name);
+ if (system(command) != 0) {
+ sprintf(message, "Error installing partcode on partition %s",
+ pp->lg_name);
+ dialog_msgbox("Error", message, 0, 0, TRUE);
+ }
+}
+
+void
+gpart_destroy(struct ggeom *lg_geom)
+{
+ struct gctl_req *r;
+ struct gprovider *pp;
+ const char *errstr;
+ int force = 1;
+
+ /* Delete all child metadata */
+ LIST_FOREACH(pp, &lg_geom->lg_provider, lg_provider)
+ gpart_delete(pp);
+
+ /* Revert any local changes to get this geom into a pristine state */
+ r = gctl_get_handle();
+ gctl_ro_param(r, "class", -1, "PART");
+ gctl_ro_param(r, "arg0", -1, lg_geom->lg_name);
+ gctl_ro_param(r, "verb", -1, "undo");
+ gctl_issue(r); /* Ignore errors -- these are non-fatal */
+ gctl_free(r);
+
+ /* Now destroy the geom itself */
+ r = gctl_get_handle();
+ gctl_ro_param(r, "class", -1, "PART");
+ gctl_ro_param(r, "arg0", -1, lg_geom->lg_name);
+ gctl_ro_param(r, "flags", -1, GPART_FLAGS);
+ gctl_ro_param(r, "force", sizeof(force), &force);
+ gctl_ro_param(r, "verb", -1, "destroy");
+ errstr = gctl_issue(r);
+ if (errstr != NULL && errstr[0] != '\0') {
+ /*
+ * Check if we reverted away the existence of the geom
+ * altogether. Show all other errors to the user.
+ */
+ if (strtol(errstr, NULL, 0) != EINVAL)
+ gpart_show_error("Error", NULL, errstr);
+ }
+ gctl_free(r);
+
+ /* And any metadata associated with the partition scheme itself */
+ delete_part_metadata(lg_geom->lg_name);
+}
+
+void
+gpart_edit(struct gprovider *pp)
+{
+ struct gctl_req *r;
+ struct gconfig *gc;
+ struct gconsumer *cp;
+ struct ggeom *geom;
+ const char *errstr, *oldtype, *scheme;
+ struct partition_metadata *md;
+ char sizestr[32];
+ char newfs[255];
+ intmax_t idx;
+ int hadlabel, choice, junk, nitems;
+ unsigned i;
+
+ DIALOG_FORMITEM items[] = {
+ {0, "Type:", 5, 0, 0, FALSE, "", 11, 0, 12, 15, 0,
+ FALSE, "Filesystem type (e.g. freebsd-ufs, freebsd-zfs, "
+ "freebsd-swap)", FALSE},
+ {0, "Size:", 5, 1, 0, FALSE, "", 11, 1, 12, 0, 0,
+ FALSE, "Partition size. Append K, M, G for kilobytes, "
+ "megabytes or gigabytes.", FALSE},
+ {0, "Mountpoint:", 11, 2, 0, FALSE, "", 11, 2, 12, 15, 0,
+ FALSE, "Path at which to mount this partition (leave blank "
+ "for swap, set to / for root filesystem)", FALSE},
+ {0, "Label:", 7, 3, 0, FALSE, "", 11, 3, 12, 15, 0, FALSE,
+ "Partition name. Not all partition schemes support this.",
+ FALSE},
+ };
+
+ /*
+ * Find the PART geom we are manipulating. This may be a consumer of
+ * this provider, or its parent. Check the consumer case first.
+ */
+ geom = NULL;
+ LIST_FOREACH(cp, &pp->lg_consumers, lg_consumers)
+ if (strcmp(cp->lg_geom->lg_class->lg_name, "PART") == 0) {
+ /* Check for zombie geoms, treating them as blank */
+ scheme = NULL;
+ LIST_FOREACH(gc, &cp->lg_geom->lg_config, lg_config) {
+ if (strcmp(gc->lg_name, "scheme") == 0) {
+ scheme = gc->lg_val;
+ break;
+ }
+ }
+ if (scheme == NULL || strcmp(scheme, "(none)") == 0) {
+ gpart_partition(cp->lg_geom->lg_name, NULL);
+ return;
+ }
+
+ /* If this is a nested partition, edit as usual */
+ if (strcmp(pp->lg_geom->lg_class->lg_name, "PART") == 0)
+ break;
+
+ /* Destroy the geom and all sub-partitions */
+ gpart_destroy(cp->lg_geom);
+
+ /* Now re-partition and return */
+ gpart_partition(cp->lg_geom->lg_name, NULL);
+ return;
+ }
+
+ if (geom == NULL && strcmp(pp->lg_geom->lg_class->lg_name, "PART") == 0)
+ geom = pp->lg_geom;
+
+ if (geom == NULL) {
+ /* Disk not partitioned, so partition it */
+ gpart_partition(pp->lg_name, NULL);
+ return;
+ }
+
+ LIST_FOREACH(gc, &geom->lg_config, lg_config) {
+ if (strcmp(gc->lg_name, "scheme") == 0) {
+ scheme = gc->lg_val;
+ break;
+ }
+ }
+
+ nitems = scheme_supports_labels(scheme) ? 4 : 3;
+
+ /* Edit editable parameters of a partition */
+ hadlabel = 0;
+ LIST_FOREACH(gc, &pp->lg_config, lg_config) {
+ if (strcmp(gc->lg_name, "type") == 0) {
+ oldtype = gc->lg_val;
+ items[0].text = gc->lg_val;
+ }
+ if (strcmp(gc->lg_name, "label") == 0 && gc->lg_val != NULL) {
+ hadlabel = 1;
+ items[3].text = gc->lg_val;
+ }
+ if (strcmp(gc->lg_name, "index") == 0)
+ idx = atoi(gc->lg_val);
+ }
+
+ TAILQ_FOREACH(md, &part_metadata, metadata) {
+ if (md->name != NULL && strcmp(md->name, pp->lg_name) == 0) {
+ if (md->fstab != NULL)
+ items[2].text = md->fstab->fs_file;
+ break;
+ }
+ }
+
+ humanize_number(sizestr, 7, pp->lg_mediasize, "B", HN_AUTOSCALE,
+ HN_NOSPACE | HN_DECIMAL);
+ items[1].text = sizestr;
+
+editpart:
+ choice = dlg_form("Edit Partition", "", 0, 0, 0, nitems, items, &junk);
+
+ if (choice) /* Cancel pressed */
+ goto endedit;
+
+ /* Check if the label has a / in it */
+ if (strchr(items[3].text, '/') != NULL) {
+ dialog_msgbox("Error", "Label contains a /, which is not an "
+ "allowed character.", 0, 0, TRUE);
+ goto editpart;
+ }
+
+ r = gctl_get_handle();
+ gctl_ro_param(r, "class", -1, "PART");
+ gctl_ro_param(r, "arg0", -1, geom->lg_name);
+ gctl_ro_param(r, "flags", -1, GPART_FLAGS);
+ gctl_ro_param(r, "verb", -1, "modify");
+ gctl_ro_param(r, "index", sizeof(idx), &idx);
+ if (hadlabel || items[3].text[0] != '\0')
+ gctl_ro_param(r, "label", -1, items[3].text);
+ gctl_ro_param(r, "type", -1, items[0].text);
+ errstr = gctl_issue(r);
+ if (errstr != NULL && errstr[0] != '\0') {
+ gpart_show_error("Error", NULL, errstr);
+ gctl_free(r);
+ goto editpart;
+ }
+ gctl_free(r);
+
+ newfs_command(items[0].text, newfs, 1);
+ set_default_part_metadata(pp->lg_name, scheme, items[0].text,
+ items[2].text, (strcmp(oldtype, items[0].text) != 0) ?
+ newfs : NULL);
+
+endedit:
+ if (strcmp(oldtype, items[0].text) != 0 && cp != NULL)
+ gpart_destroy(cp->lg_geom);
+ if (strcmp(oldtype, items[0].text) != 0 && strcmp(items[0].text,
+ "freebsd") == 0)
+ gpart_partition(pp->lg_name, "BSD");
+
+ for (i = 0; i < (sizeof(items) / sizeof(items[0])); i++)
+ if (items[i].text_free)
+ free(items[i].text);
+}
+
+void
+set_default_part_metadata(const char *name, const char *scheme,
+ const char *type, const char *mountpoint, const char *newfs)
+{
+ struct partition_metadata *md;
+ char *zpool_name = NULL;
+ int i;
+
+ /* Set part metadata */
+ md = get_part_metadata(name, 1);
+
+ if (newfs) {
+ if (md->newfs != NULL) {
+ free(md->newfs);
+ md->newfs = NULL;
+ }
+
+ if (newfs != NULL && newfs[0] != '\0') {
+ md->newfs = malloc(strlen(newfs) + strlen(" /dev/") +
+ strlen(mountpoint) + 5 + strlen(name) + 1);
+ if (strcmp("freebsd-zfs", type) == 0) {
+ zpool_name = strdup((strlen(mountpoint) == 1) ?
+ "root" : &mountpoint[1]);
+ for (i = 0; zpool_name[i] != 0; i++)
+ if (!isalnum(zpool_name[i]))
+ zpool_name[i] = '_';
+ sprintf(md->newfs, "%s %s /dev/%s", newfs,
+ zpool_name, name);
+ } else {
+ sprintf(md->newfs, "%s /dev/%s", newfs, name);
+ }
+ }
+ }
+
+ if (strcmp(type, "freebsd-swap") == 0)
+ mountpoint = "none";
+ if (strcmp(type, bootpart_type(scheme)) == 0)
+ md->bootcode = 1;
+
+ /* VTOC8 needs partcode at the start of partitions */
+ if (strcmp(scheme, "VTOC8") == 0 && (strcmp(type, "freebsd-ufs") == 0
+ || strcmp(type, "freebsd-zfs") == 0))
+ md->bootcode = 1;
+
+ if (mountpoint == NULL || mountpoint[0] == '\0') {
+ if (md->fstab != NULL) {
+ free(md->fstab->fs_spec);
+ free(md->fstab->fs_file);
+ free(md->fstab->fs_vfstype);
+ free(md->fstab->fs_mntops);
+ free(md->fstab->fs_type);
+ free(md->fstab);
+ md->fstab = NULL;
+ }
+ } else {
+ if (md->fstab == NULL) {
+ md->fstab = malloc(sizeof(struct fstab));
+ } else {
+ free(md->fstab->fs_spec);
+ free(md->fstab->fs_file);
+ free(md->fstab->fs_vfstype);
+ free(md->fstab->fs_mntops);
+ free(md->fstab->fs_type);
+ }
+ if (strcmp("freebsd-zfs", type) == 0) {
+ md->fstab->fs_spec = strdup(zpool_name);
+ } else {
+ md->fstab->fs_spec = malloc(strlen(name) +
+ strlen("/dev/") + 1);
+ sprintf(md->fstab->fs_spec, "/dev/%s", name);
+ }
+ md->fstab->fs_file = strdup(mountpoint);
+ /* Get VFS from text after freebsd-, if possible */
+ if (strncmp("freebsd-", type, 8) == 0)
+ md->fstab->fs_vfstype = strdup(&type[8]);
+ else if (strcmp("fat32", type) == 0 || strcmp("efi", type) == 0)
+ md->fstab->fs_vfstype = strdup("msdosfs");
+ else
+ md->fstab->fs_vfstype = strdup(type); /* Guess */
+ if (strcmp(type, "freebsd-swap") == 0) {
+ md->fstab->fs_type = strdup(FSTAB_SW);
+ md->fstab->fs_freq = 0;
+ md->fstab->fs_passno = 0;
+ } else if (strcmp(type, "freebsd-zfs") == 0) {
+ md->fstab->fs_type = strdup(FSTAB_RW);
+ md->fstab->fs_freq = 0;
+ md->fstab->fs_passno = 0;
+ } else {
+ md->fstab->fs_type = strdup(FSTAB_RW);
+ if (strcmp(mountpoint, "/") == 0) {
+ md->fstab->fs_freq = 1;
+ md->fstab->fs_passno = 1;
+ } else {
+ md->fstab->fs_freq = 2;
+ md->fstab->fs_passno = 2;
+ }
+ }
+ md->fstab->fs_mntops = strdup(md->fstab->fs_type);
+ }
+
+ if (zpool_name != NULL)
+ free(zpool_name);
+}
+
+static
+int part_compare(const void *xa, const void *xb)
+{
+ struct gprovider **a = (struct gprovider **)xa;
+ struct gprovider **b = (struct gprovider **)xb;
+ intmax_t astart, bstart;
+ struct gconfig *gc;
+
+ astart = bstart = 0;
+ LIST_FOREACH(gc, &(*a)->lg_config, lg_config)
+ if (strcmp(gc->lg_name, "start") == 0) {
+ astart = strtoimax(gc->lg_val, NULL, 0);
+ break;
+ }
+ LIST_FOREACH(gc, &(*b)->lg_config, lg_config)
+ if (strcmp(gc->lg_name, "start") == 0) {
+ bstart = strtoimax(gc->lg_val, NULL, 0);
+ break;
+ }
+
+ if (astart < bstart)
+ return -1;
+ else if (astart > bstart)
+ return 1;
+ else
+ return 0;
+}
+
+intmax_t
+gpart_max_free(struct ggeom *geom, intmax_t *npartstart)
+{
+ struct gconfig *gc;
+ struct gprovider *pp, **providers;
+ intmax_t lastend;
+ intmax_t start, end;
+ intmax_t maxsize, maxstart;
+ intmax_t partstart, partend;
+ int i, nparts;
+
+ /* Now get the maximum free size and free start */
+ start = end = 0;
+ LIST_FOREACH(gc, &geom->lg_config, lg_config) {
+ if (strcmp(gc->lg_name, "first") == 0)
+ start = strtoimax(gc->lg_val, NULL, 0);
+ if (strcmp(gc->lg_name, "last") == 0)
+ end = strtoimax(gc->lg_val, NULL, 0);
+ }
+
+ i = nparts = 0;
+ LIST_FOREACH(pp, &geom->lg_provider, lg_provider)
+ nparts++;
+ providers = calloc(nparts, sizeof(providers[0]));
+ LIST_FOREACH(pp, &geom->lg_provider, lg_provider)
+ providers[i++] = pp;
+ qsort(providers, nparts, sizeof(providers[0]), part_compare);
+
+ lastend = start - 1;
+ maxsize = 0;
+ for (i = 0; i < nparts; i++) {
+ pp = providers[i];
+
+ LIST_FOREACH(gc, &pp->lg_config, lg_config) {
+ if (strcmp(gc->lg_name, "start") == 0)
+ partstart = strtoimax(gc->lg_val, NULL, 0);
+ if (strcmp(gc->lg_name, "end") == 0)
+ partend = strtoimax(gc->lg_val, NULL, 0);
+ }
+
+ if (partstart - lastend > maxsize) {
+ maxsize = partstart - lastend - 1;
+ maxstart = lastend + 1;
+ }
+
+ lastend = partend;
+ }
+
+ if (end - lastend > maxsize) {
+ maxsize = end - lastend - 1;
+ maxstart = lastend + 1;
+ }
+
+ pp = LIST_FIRST(&geom->lg_consumer)->lg_provider;
+
+ /* Compute beginning of new partition and maximum available space */
+ if (pp->lg_stripesize > 0 &&
+ (maxstart*pp->lg_sectorsize % pp->lg_stripesize) != 0) {
+ intmax_t offset = (pp->lg_stripesize -
+ ((maxstart*pp->lg_sectorsize) % pp->lg_stripesize)) /
+ pp->lg_sectorsize;
+ maxstart += offset;
+ maxsize -= offset;
+ }
+
+ if (npartstart != NULL)
+ *npartstart = maxstart;
+
+ return (maxsize);
+}
+
+void
+gpart_create(struct gprovider *pp, char *default_type, char *default_size,
+ char *default_mountpoint, char **partname, int interactive)
+{
+ struct gctl_req *r;
+ struct gconfig *gc;
+ struct gconsumer *cp;
+ struct ggeom *geom;
+ const char *errstr, *scheme;
+ char sizestr[32], startstr[32], output[64], *newpartname;
+ char newfs[255], options_fstype[64];
+ intmax_t maxsize, size, sector, firstfree, stripe;
+ uint64_t bytes;
+ int nitems, choice, junk;
+ unsigned i;
+
+ DIALOG_FORMITEM items[] = {
+ {0, "Type:", 5, 0, 0, FALSE, "freebsd-ufs", 11, 0, 12, 15, 0,
+ FALSE, "Filesystem type (e.g. freebsd-ufs, freebsd-zfs, "
+ "freebsd-swap)", FALSE},
+ {0, "Size:", 5, 1, 0, FALSE, "", 11, 1, 12, 15, 0,
+ FALSE, "Partition size. Append K, M, G for kilobytes, "
+ "megabytes or gigabytes.", FALSE},
+ {0, "Mountpoint:", 11, 2, 0, FALSE, "", 11, 2, 12, 15, 0,
+ FALSE, "Path at which to mount partition (blank for "
+ "swap, / for root filesystem)", FALSE},
+ {0, "Label:", 7, 3, 0, FALSE, "", 11, 3, 12, 15, 0, FALSE,
+ "Partition name. Not all partition schemes support this.",
+ FALSE},
+ };
+
+ if (partname != NULL)
+ *partname = NULL;
+
+ /* Record sector and stripe sizes */
+ sector = pp->lg_sectorsize;
+ stripe = pp->lg_stripesize;
+
+ /*
+ * Find the PART geom we are manipulating. This may be a consumer of
+ * this provider, or its parent. Check the consumer case first.
+ */
+ geom = NULL;
+ LIST_FOREACH(cp, &pp->lg_consumers, lg_consumers)
+ if (strcmp(cp->lg_geom->lg_class->lg_name, "PART") == 0) {
+ geom = cp->lg_geom;
+ break;
+ }
+
+ if (geom == NULL && strcmp(pp->lg_geom->lg_class->lg_name, "PART") == 0)
+ geom = pp->lg_geom;
+
+ /* Now get the partition scheme */
+ scheme = NULL;
+ if (geom != NULL) {
+ LIST_FOREACH(gc, &geom->lg_config, lg_config)
+ if (strcmp(gc->lg_name, "scheme") == 0)
+ scheme = gc->lg_val;
+ }
+
+ if (geom == NULL || scheme == NULL || strcmp(scheme, "(none)") == 0) {
+ if (gpart_partition(pp->lg_name, NULL) == 0)
+ dialog_msgbox("",
+ "The partition table has been successfully created."
+ " Please press Create again to create partitions.",
+ 0, 0, TRUE);
+
+ return;
+ }
+
+ /*
+ * If we still don't have a geom, either the user has
+ * canceled partitioning or there has been an error which has already
+ * been displayed, so bail.
+ */
+ if (geom == NULL)
+ return;
+
+ maxsize = size = gpart_max_free(geom, &firstfree);
+ if (size <= 0) {
+ dialog_msgbox("Error", "No free space left on device.", 0, 0,
+ TRUE);
+ return;
+ }
+
+ humanize_number(sizestr, 7, size*sector, "B", HN_AUTOSCALE,
+ HN_NOSPACE | HN_DECIMAL);
+ items[1].text = sizestr;
+
+ /* Special-case the MBR default type for nested partitions */
+ if (strcmp(scheme, "MBR") == 0 || strcmp(scheme, "PC98") == 0) {
+ items[0].text = "freebsd";
+ items[0].help = "Filesystem type (e.g. freebsd, fat32)";
+ }
+
+ nitems = scheme_supports_labels(scheme) ? 4 : 3;
+
+ if (default_type != NULL)
+ items[0].text = default_type;
+ if (default_size != NULL)
+ items[1].text = default_size;
+ if (default_mountpoint != NULL)
+ items[2].text = default_mountpoint;
+
+ /* Default options */
+ strncpy(options_fstype, items[0].text,
+ sizeof(options_fstype));
+ newfs_command(options_fstype, newfs, 1);
+addpartform:
+ if (interactive) {
+ dialog_vars.extra_label = "Options";
+ dialog_vars.extra_button = TRUE;
+ choice = dlg_form("Add Partition", "", 0, 0, 0, nitems,
+ items, &junk);
+ dialog_vars.extra_button = FALSE;
+ switch (choice) {
+ case 0: /* OK */
+ break;
+ case 1: /* Cancel */
+ return;
+ case 3: /* Options */
+ strncpy(options_fstype, items[0].text,
+ sizeof(options_fstype));
+ newfs_command(options_fstype, newfs, 0);
+ goto addpartform;
+ }
+ }
+
+ /*
+ * If the user changed the fs type after specifying options, undo
+ * their choices in favor of the new filesystem's defaults.
+ */
+ if (strcmp(options_fstype, items[0].text) != 0) {
+ strncpy(options_fstype, items[0].text, sizeof(options_fstype));
+ newfs_command(options_fstype, newfs, 1);
+ }
+
+ size = maxsize;
+ if (strlen(items[1].text) > 0) {
+ if (expand_number(items[1].text, &bytes) != 0) {
+ char error[512];
+
+ sprintf(error, "Invalid size: %s\n", strerror(errno));
+ dialog_msgbox("Error", error, 0, 0, TRUE);
+ goto addpartform;
+ }
+ size = MIN((intmax_t)(bytes/sector), maxsize);
+ }
+
+ /* Check if the label has a / in it */
+ if (strchr(items[3].text, '/') != NULL) {
+ dialog_msgbox("Error", "Label contains a /, which is not an "
+ "allowed character.", 0, 0, TRUE);
+ goto addpartform;
+ }
+
+ /* Warn if no mountpoint set */
+ if (strcmp(items[0].text, "freebsd-ufs") == 0 &&
+ items[2].text[0] != '/') {
+ dialog_vars.defaultno = TRUE;
+ choice = dialog_yesno("Warning",
+ "This partition does not have a valid mountpoint "
+ "(for the partition from which you intend to boot the "
+ "operating system, the mountpoint should be /). Are you "
+ "sure you want to continue?"
+ , 0, 0);
+ dialog_vars.defaultno = FALSE;
+ if (choice == 1) /* cancel */
+ goto addpartform;
+ }
+
+ /*
+ * Error if this scheme needs nested partitions, this is one, and
+ * a mountpoint was set.
+ */
+ if (strcmp(items[0].text, "freebsd") == 0 &&
+ strlen(items[2].text) > 0) {
+ dialog_msgbox("Error", "Partitions of type \"freebsd\" are "
+ "nested BSD-type partition schemes and cannot have "
+ "mountpoints. After creating one, select it and press "
+ "Create again to add the actual file systems.", 0, 0, TRUE);
+ goto addpartform;
+ }
+
+ /* If this is the root partition, check that this scheme is bootable */
+ if (strcmp(items[2].text, "/") == 0 && !is_scheme_bootable(scheme)) {
+ char message[512];
+ sprintf(message, "This partition scheme (%s) is not bootable "
+ "on this platform. Are you sure you want to proceed?",
+ scheme);
+ dialog_vars.defaultno = TRUE;
+ choice = dialog_yesno("Warning", message, 0, 0);
+ dialog_vars.defaultno = FALSE;
+ if (choice == 1) /* cancel */
+ goto addpartform;
+ }
+
+ /* If this is the root partition, check that this fs is bootable */
+ if (strcmp(items[2].text, "/") == 0 && !is_fs_bootable(scheme,
+ items[0].text)) {
+ char message[512];
+ sprintf(message, "This file system (%s) is not bootable "
+ "on this system. Are you sure you want to proceed?",
+ items[0].text);
+ dialog_vars.defaultno = TRUE;
+ choice = dialog_yesno("Warning", message, 0, 0);
+ dialog_vars.defaultno = FALSE;
+ if (choice == 1) /* cancel */
+ goto addpartform;
+ }
+
+ /*
+ * If this is the root partition, and we need a boot partition, ask
+ * the user to add one.
+ */
+
+ /* Check for existing freebsd-boot partition */
+ LIST_FOREACH(pp, &geom->lg_provider, lg_provider) {
+ struct partition_metadata *md;
+ md = get_part_metadata(pp->lg_name, 0);
+ if (md == NULL || !md->bootcode)
+ continue;
+ LIST_FOREACH(gc, &pp->lg_config, lg_config)
+ if (strcmp(gc->lg_name, "type") == 0)
+ break;
+ if (gc != NULL && strcmp(gc->lg_val,
+ bootpart_type(scheme)) == 0)
+ break;
+ }
+
+ /* If there isn't one, and we need one, ask */
+ if ((strcmp(items[0].text, "freebsd") == 0 ||
+ strcmp(items[2].text, "/") == 0) && bootpart_size(scheme) > 0 &&
+ pp == NULL) {
+ if (interactive)
+ choice = dialog_yesno("Boot Partition",
+ "This partition scheme requires a boot partition "
+ "for the disk to be bootable. Would you like to "
+ "make one now?", 0, 0);
+ else
+ choice = 0;
+
+ if (choice == 0) { /* yes */
+ r = gctl_get_handle();
+ gctl_ro_param(r, "class", -1, "PART");
+ gctl_ro_param(r, "arg0", -1, geom->lg_name);
+ gctl_ro_param(r, "flags", -1, GPART_FLAGS);
+ gctl_ro_param(r, "verb", -1, "add");
+ gctl_ro_param(r, "type", -1, bootpart_type(scheme));
+ snprintf(sizestr, sizeof(sizestr), "%jd",
+ bootpart_size(scheme) / sector);
+ gctl_ro_param(r, "size", -1, sizestr);
+ snprintf(startstr, sizeof(startstr), "%jd", firstfree);
+ gctl_ro_param(r, "start", -1, startstr);
+ gctl_rw_param(r, "output", sizeof(output), output);
+ errstr = gctl_issue(r);
+ if (errstr != NULL && errstr[0] != '\0')
+ gpart_show_error("Error", NULL, errstr);
+ gctl_free(r);
+
+ get_part_metadata(strtok(output, " "), 1)->bootcode = 1;
+
+ /* Now adjust the part we are really adding forward */
+ firstfree += bootpart_size(scheme) / sector;
+ size -= (bootpart_size(scheme) + stripe)/sector;
+ if (stripe > 0 && (firstfree*sector % stripe) != 0)
+ firstfree += (stripe - ((firstfree*sector) %
+ stripe)) / sector;
+ }
+ }
+
+ r = gctl_get_handle();
+ gctl_ro_param(r, "class", -1, "PART");
+ gctl_ro_param(r, "arg0", -1, geom->lg_name);
+ gctl_ro_param(r, "flags", -1, GPART_FLAGS);
+ gctl_ro_param(r, "verb", -1, "add");
+
+ gctl_ro_param(r, "type", -1, items[0].text);
+ snprintf(sizestr, sizeof(sizestr), "%jd", size);
+ gctl_ro_param(r, "size", -1, sizestr);
+ snprintf(startstr, sizeof(startstr), "%jd", firstfree);
+ gctl_ro_param(r, "start", -1, startstr);
+ if (items[3].text[0] != '\0')
+ gctl_ro_param(r, "label", -1, items[3].text);
+ gctl_rw_param(r, "output", sizeof(output), output);
+ errstr = gctl_issue(r);
+ if (errstr != NULL && errstr[0] != '\0') {
+ gpart_show_error("Error", NULL, errstr);
+ gctl_free(r);
+ goto addpartform;
+ }
+ newpartname = strtok(output, " ");
+ gctl_free(r);
+
+ /*
+ * Try to destroy any geom that gpart picked up already here from
+ * dirty blocks.
+ */
+ r = gctl_get_handle();
+ gctl_ro_param(r, "class", -1, "PART");
+ gctl_ro_param(r, "arg0", -1, newpartname);
+ gctl_ro_param(r, "flags", -1, GPART_FLAGS);
+ junk = 1;
+ gctl_ro_param(r, "force", sizeof(junk), &junk);
+ gctl_ro_param(r, "verb", -1, "destroy");
+ gctl_issue(r); /* Error usually expected and non-fatal */
+ gctl_free(r);
+
+ if (strcmp(items[0].text, bootpart_type(scheme)) == 0)
+ get_part_metadata(newpartname, 1)->bootcode = 1;
+ else if (strcmp(items[0].text, "freebsd") == 0)
+ gpart_partition(newpartname, "BSD");
+ else
+ set_default_part_metadata(newpartname, scheme,
+ items[0].text, items[2].text, newfs);
+
+ for (i = 0; i < (sizeof(items) / sizeof(items[0])); i++)
+ if (items[i].text_free)
+ free(items[i].text);
+
+ if (partname != NULL)
+ *partname = strdup(newpartname);
+}
+
+void
+gpart_delete(struct gprovider *pp)
+{
+ struct gconfig *gc;
+ struct ggeom *geom;
+ struct gconsumer *cp;
+ struct gctl_req *r;
+ const char *errstr;
+ intmax_t idx;
+ int is_partition;
+
+ /* Is it a partition? */
+ is_partition = (strcmp(pp->lg_geom->lg_class->lg_name, "PART") == 0);
+
+ /* Find out if this is the root of a gpart geom */
+ geom = NULL;
+ LIST_FOREACH(cp, &pp->lg_consumers, lg_consumers)
+ if (strcmp(cp->lg_geom->lg_class->lg_name, "PART") == 0) {
+ geom = cp->lg_geom;
+ break;
+ }
+
+ /* If so, destroy all children */
+ if (geom != NULL) {
+ gpart_destroy(geom);
+
+ /* If this is a partition, revert it, so it can be deleted */
+ if (is_partition) {
+ r = gctl_get_handle();
+ gctl_ro_param(r, "class", -1, "PART");
+ gctl_ro_param(r, "arg0", -1, geom->lg_name);
+ gctl_ro_param(r, "verb", -1, "undo");
+ gctl_issue(r); /* Ignore non-fatal errors */
+ gctl_free(r);
+ }
+ }
+
+ /*
+ * If this is not a partition, see if that is a problem, complain if
+ * necessary, and return always, since we need not do anything further,
+ * error or no.
+ */
+ if (!is_partition) {
+ if (geom == NULL)
+ dialog_msgbox("Error",
+ "Only partitions can be deleted.", 0, 0, TRUE);
+ return;
+ }
+
+ r = gctl_get_handle();
+ gctl_ro_param(r, "class", -1, pp->lg_geom->lg_class->lg_name);
+ gctl_ro_param(r, "arg0", -1, pp->lg_geom->lg_name);
+ gctl_ro_param(r, "flags", -1, GPART_FLAGS);
+ gctl_ro_param(r, "verb", -1, "delete");
+
+ LIST_FOREACH(gc, &pp->lg_config, lg_config) {
+ if (strcmp(gc->lg_name, "index") == 0) {
+ idx = atoi(gc->lg_val);
+ gctl_ro_param(r, "index", sizeof(idx), &idx);
+ break;
+ }
+ }
+
+ errstr = gctl_issue(r);
+ if (errstr != NULL && errstr[0] != '\0') {
+ gpart_show_error("Error", NULL, errstr);
+ gctl_free(r);
+ return;
+ }
+
+ gctl_free(r);
+
+ delete_part_metadata(pp->lg_name);
+}
+
+void
+gpart_revert_all(struct gmesh *mesh)
+{
+ struct gclass *classp;
+ struct gconfig *gc;
+ struct ggeom *gp;
+ struct gctl_req *r;
+ const char *modified;
+
+ LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
+ if (strcmp(classp->lg_name, "PART") == 0)
+ break;
+ }
+
+ if (strcmp(classp->lg_name, "PART") != 0) {
+ dialog_msgbox("Error", "gpart not found!", 0, 0, TRUE);
+ return;
+ }
+
+ LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
+ modified = "true"; /* XXX: If we don't know (kernel too old),
+ * assume there are modifications. */
+ LIST_FOREACH(gc, &gp->lg_config, lg_config) {
+ if (strcmp(gc->lg_name, "modified") == 0) {
+ modified = gc->lg_val;
+ break;
+ }
+ }
+
+ if (strcmp(modified, "false") == 0)
+ continue;
+
+ r = gctl_get_handle();
+ gctl_ro_param(r, "class", -1, "PART");
+ gctl_ro_param(r, "arg0", -1, gp->lg_name);
+ gctl_ro_param(r, "verb", -1, "undo");
+
+ gctl_issue(r);
+ gctl_free(r);
+ }
+}
+
+void
+gpart_commit(struct gmesh *mesh)
+{
+ struct partition_metadata *md;
+ struct gclass *classp;
+ struct ggeom *gp;
+ struct gconfig *gc;
+ struct gconsumer *cp;
+ struct gprovider *pp;
+ struct gctl_req *r;
+ const char *errstr;
+ const char *modified;
+ const char *rootfs;
+
+ LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
+ if (strcmp(classp->lg_name, "PART") == 0)
+ break;
+ }
+
+ /* Figure out what filesystem / uses */
+ rootfs = "ufs"; /* Assume ufs if nothing else present */
+ TAILQ_FOREACH(md, &part_metadata, metadata) {
+ if (md->fstab != NULL && strcmp(md->fstab->fs_file, "/") == 0) {
+ rootfs = md->fstab->fs_vfstype;
+ break;
+ }
+ }
+
+ if (strcmp(classp->lg_name, "PART") != 0) {
+ dialog_msgbox("Error", "gpart not found!", 0, 0, TRUE);
+ return;
+ }
+
+ LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
+ modified = "true"; /* XXX: If we don't know (kernel too old),
+ * assume there are modifications. */
+ LIST_FOREACH(gc, &gp->lg_config, lg_config) {
+ if (strcmp(gc->lg_name, "modified") == 0) {
+ modified = gc->lg_val;
+ break;
+ }
+ }
+
+ if (strcmp(modified, "false") == 0)
+ continue;
+
+ /* Add bootcode if necessary, before the commit */
+ md = get_part_metadata(gp->lg_name, 0);
+ if (md != NULL && md->bootcode)
+ gpart_bootcode(gp);
+
+ /* Now install partcode on its partitions, if necessary */
+ LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
+ md = get_part_metadata(pp->lg_name, 0);
+ if (md == NULL || !md->bootcode)
+ continue;
+
+ /* Mark this partition active if that's required */
+ gpart_activate(pp);
+
+ /* Check if the partition has sub-partitions */
+ LIST_FOREACH(cp, &pp->lg_consumers, lg_consumers)
+ if (strcmp(cp->lg_geom->lg_class->lg_name,
+ "PART") == 0)
+ break;
+
+ if (cp == NULL) /* No sub-partitions */
+ gpart_partcode(pp, rootfs);
+ }
+
+ r = gctl_get_handle();
+ gctl_ro_param(r, "class", -1, "PART");
+ gctl_ro_param(r, "arg0", -1, gp->lg_name);
+ gctl_ro_param(r, "verb", -1, "commit");
+
+ errstr = gctl_issue(r);
+ if (errstr != NULL && errstr[0] != '\0')
+ gpart_show_error("Error", NULL, errstr);
+ gctl_free(r);
+ }
+}
+
diff --git a/usr.sbin/bsdinstall/partedit/part_wizard.c b/usr.sbin/bsdinstall/partedit/part_wizard.c
new file mode 100644
index 0000000..1e9c899
--- /dev/null
+++ b/usr.sbin/bsdinstall/partedit/part_wizard.c
@@ -0,0 +1,365 @@
+/*-
+ * Copyright (c) 2011 Nathan Whitehorn
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <errno.h>
+#include <libutil.h>
+#include <inttypes.h>
+
+#include <sys/sysctl.h>
+#include <string.h>
+
+#include <libgeom.h>
+#include <dialog.h>
+#include <dlg_keys.h>
+
+#include "partedit.h"
+
+#define MIN_FREE_SPACE (1024*1024*1024) /* 1 GB */
+#define SWAP_SIZE(available) MIN(available/20, 4*1024*1024*1024LL)
+
+static char *boot_disk(struct gmesh *mesh);
+static char *wizard_partition(struct gmesh *mesh, const char *disk);
+
+int
+part_wizard(const char *fsreq) {
+ int error;
+ struct gmesh mesh;
+ char *disk, *schemeroot;
+ const char *fstype;
+
+ if (fsreq != NULL)
+ fstype = fsreq;
+ else
+ fstype = "ufs";
+
+startwizard:
+ error = geom_gettree(&mesh);
+
+ dlg_put_backtitle();
+ error = geom_gettree(&mesh);
+ disk = boot_disk(&mesh);
+ if (disk == NULL)
+ return (1);
+
+ dlg_clear();
+ dlg_put_backtitle();
+ schemeroot = wizard_partition(&mesh, disk);
+ free(disk);
+ if (schemeroot == NULL)
+ return (1);
+
+ geom_deletetree(&mesh);
+ dlg_clear();
+ dlg_put_backtitle();
+ error = geom_gettree(&mesh);
+
+ error = wizard_makeparts(&mesh, schemeroot, fstype, 1);
+ if (error)
+ goto startwizard;
+ free(schemeroot);
+
+ geom_deletetree(&mesh);
+
+ return (0);
+}
+
+static char *
+boot_disk(struct gmesh *mesh)
+{
+ struct gclass *classp;
+ struct gconfig *gc;
+ struct ggeom *gp;
+ struct gprovider *pp;
+ DIALOG_LISTITEM *disks = NULL;
+ const char *type, *desc;
+ char diskdesc[512];
+ char *chosen;
+ int i, err, selected, n = 0;
+
+ LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
+ if (strcmp(classp->lg_name, "DISK") != 0 &&
+ strcmp(classp->lg_name, "RAID") != 0 &&
+ strcmp(classp->lg_name, "MD") != 0)
+ continue;
+
+ LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
+ if (LIST_EMPTY(&gp->lg_provider))
+ continue;
+
+ LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
+ desc = type = NULL;
+ LIST_FOREACH(gc, &pp->lg_config, lg_config) {
+ if (strcmp(gc->lg_name, "type") == 0)
+ type = gc->lg_val;
+ if (strcmp(gc->lg_name, "descr") == 0)
+ desc = gc->lg_val;
+ }
+
+ /* Skip swap-backed md and WORM devices */
+ if (strcmp(classp->lg_name, "MD") == 0 &&
+ type != NULL && strcmp(type, "swap") == 0)
+ continue;
+ if (strncmp(pp->lg_name, "cd", 2) == 0)
+ continue;
+
+ disks = realloc(disks, (++n)*sizeof(disks[0]));
+ disks[n-1].name = pp->lg_name;
+ humanize_number(diskdesc, 7, pp->lg_mediasize,
+ "B", HN_AUTOSCALE, HN_DECIMAL);
+ if (strncmp(pp->lg_name, "ad", 2) == 0)
+ strcat(diskdesc, " ATA Hard Disk");
+ else if (strncmp(pp->lg_name, "md", 2) == 0)
+ strcat(diskdesc, " Memory Disk");
+ else
+ strcat(diskdesc, " Disk");
+
+ if (desc != NULL)
+ snprintf(diskdesc, sizeof(diskdesc),
+ "%s <%s>", diskdesc, desc);
+
+ disks[n-1].text = strdup(diskdesc);
+ disks[n-1].help = NULL;
+ disks[n-1].state = 0;
+ }
+ }
+ }
+
+ if (n > 1) {
+ err = dlg_menu("Partitioning",
+ "Select the disk on which to install FreeBSD.", 0, 0, 0,
+ n, disks, &selected, NULL);
+
+ chosen = (err == 0) ? strdup(disks[selected].name) : NULL;
+ } else if (n == 1) {
+ chosen = strdup(disks[0].name);
+ } else {
+ chosen = NULL;
+ }
+
+ for (i = 0; i < n; i++)
+ free(disks[i].text);
+
+ return (chosen);
+}
+
+static struct gprovider *
+provider_for_name(struct gmesh *mesh, const char *name)
+{
+ struct gclass *classp;
+ struct gprovider *pp = NULL;
+ struct ggeom *gp;
+
+ LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
+ LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
+ if (LIST_EMPTY(&gp->lg_provider))
+ continue;
+
+ LIST_FOREACH(pp, &gp->lg_provider, lg_provider)
+ if (strcmp(pp->lg_name, name) == 0)
+ break;
+
+ if (pp != NULL) break;
+ }
+
+ if (pp != NULL) break;
+ }
+
+ return (pp);
+}
+
+static char *
+wizard_partition(struct gmesh *mesh, const char *disk)
+{
+ struct gclass *classp;
+ struct ggeom *gpart = NULL;
+ struct gconfig *gc;
+ char message[512];
+ const char *scheme = NULL;
+ char *retval = NULL;
+ int choice;
+
+ LIST_FOREACH(classp, &mesh->lg_class, lg_class)
+ if (strcmp(classp->lg_name, "PART") == 0)
+ break;
+
+ if (classp != NULL) {
+ LIST_FOREACH(gpart, &classp->lg_geom, lg_geom)
+ if (strcmp(gpart->lg_name, disk) == 0)
+ break;
+ }
+
+ if (gpart != NULL) {
+ LIST_FOREACH(gc, &gpart->lg_config, lg_config) {
+ if (strcmp(gc->lg_name, "scheme") == 0) {
+ scheme = gc->lg_val;
+ break;
+ }
+ }
+ }
+
+ /* Treat uncommitted scheme deletions as no scheme */
+ if (scheme != NULL && strcmp(scheme, "(none)") == 0)
+ scheme = NULL;
+
+query:
+ dialog_vars.yes_label = "Entire Disk";
+ dialog_vars.no_label = "Partition";
+ if (gpart != NULL)
+ dialog_vars.defaultno = TRUE;
+
+ snprintf(message, sizeof(message), "Would you like to use this entire "
+ "disk (%s) for FreeBSD or partition it to share it with other "
+ "operating systems? Using the entire disk will erase any data "
+ "currently stored there.", disk);
+ choice = dialog_yesno("Partition", message, 0, 0);
+
+ dialog_vars.yes_label = NULL;
+ dialog_vars.no_label = NULL;
+ dialog_vars.defaultno = FALSE;
+
+ if (choice == 1 && scheme != NULL && !is_scheme_bootable(scheme)) {
+ char warning[512];
+ int subchoice;
+
+ sprintf(warning, "The existing partition scheme on this "
+ "disk (%s) is not bootable on this platform. To install "
+ "FreeBSD, it must be repartitioned. This will destroy all "
+ "data on the disk. Are you sure you want to proceed?",
+ scheme);
+ subchoice = dialog_yesno("Non-bootable Disk", warning, 0, 0);
+ if (subchoice != 0)
+ goto query;
+
+ gpart_destroy(gpart);
+ scheme = choose_part_type(default_scheme());
+ if (scheme == NULL)
+ return NULL;
+ gpart_partition(disk, scheme);
+ }
+
+ if (scheme == NULL || choice == 0) {
+ if (gpart != NULL && scheme != NULL) {
+ /* Erase partitioned disk */
+ choice = dialog_yesno("Confirmation", "This will erase "
+ "the disk. Are you sure you want to proceed?", 0, 0);
+ if (choice != 0)
+ goto query;
+
+ gpart_destroy(gpart);
+ }
+
+ scheme = choose_part_type(default_scheme());
+ if (scheme == NULL)
+ return NULL;
+ gpart_partition(disk, scheme);
+ }
+
+ if (strcmp(scheme, "PC98") == 0 || strcmp(scheme, "MBR") == 0) {
+ struct gmesh submesh;
+ geom_gettree(&submesh);
+ gpart_create(provider_for_name(&submesh, disk),
+ "freebsd", NULL, NULL, &retval,
+ choice /* Non-interactive for "Entire Disk" */);
+ geom_deletetree(&submesh);
+ } else {
+ retval = strdup(disk);
+ }
+
+ return (retval);
+}
+
+int
+wizard_makeparts(struct gmesh *mesh, const char *disk, const char *fstype, int interactive)
+{
+ struct gmesh submesh;
+ struct gclass *classp;
+ struct ggeom *gp;
+ struct gprovider *pp;
+ intmax_t swapsize, available;
+ char swapsizestr[10], rootsizestr[10], *fsname;
+ char *fsnames[] = {"freebsd-ufs", "freebsd-zfs"};
+ int retval;
+
+ if (strcmp(fstype, "zfs") == 0) {
+ fsname = fsnames[1];
+ } else {
+ /* default to UFS */
+ fsname = fsnames[0];
+ }
+
+ LIST_FOREACH(classp, &mesh->lg_class, lg_class)
+ if (strcmp(classp->lg_name, "PART") == 0)
+ break;
+
+ LIST_FOREACH(gp, &classp->lg_geom, lg_geom)
+ if (strcmp(gp->lg_name, disk) == 0)
+ break;
+
+ pp = provider_for_name(mesh, disk);
+
+ available = gpart_max_free(gp, NULL)*pp->lg_sectorsize;
+ if (interactive && available < MIN_FREE_SPACE) {
+ char availablestr[10], neededstr[10], message[512];
+ humanize_number(availablestr, 7, available, "B", HN_AUTOSCALE,
+ HN_DECIMAL);
+ humanize_number(neededstr, 7, MIN_FREE_SPACE, "B", HN_AUTOSCALE,
+ HN_DECIMAL);
+ sprintf(message, "There is not enough free space on %s to "
+ "install FreeBSD (%s free, %s required). Would you like "
+ "to choose another disk or to open the partition editor?",
+ disk, availablestr, neededstr);
+
+ dialog_vars.yes_label = "Another Disk";
+ dialog_vars.no_label = "Editor";
+ retval = dialog_yesno("Warning", message, 0, 0);
+ dialog_vars.yes_label = NULL;
+ dialog_vars.no_label = NULL;
+
+ return (!retval); /* Editor -> return 0 */
+ }
+
+ swapsize = SWAP_SIZE(available);
+ humanize_number(swapsizestr, 7, swapsize, "B", HN_AUTOSCALE,
+ HN_NOSPACE | HN_DECIMAL);
+ humanize_number(rootsizestr, 7, available - swapsize - 1024*1024,
+ "B", HN_AUTOSCALE, HN_NOSPACE | HN_DECIMAL);
+
+ geom_gettree(&submesh);
+ pp = provider_for_name(&submesh, disk);
+ gpart_create(pp, fsname, rootsizestr, "/", NULL, 0);
+ geom_deletetree(&submesh);
+
+ geom_gettree(&submesh);
+ pp = provider_for_name(&submesh, disk);
+ gpart_create(pp, "freebsd-swap", swapsizestr, NULL, NULL, 0);
+ geom_deletetree(&submesh);
+
+ return (0);
+}
+
diff --git a/usr.sbin/bsdinstall/partedit/partedit.c b/usr.sbin/bsdinstall/partedit/partedit.c
new file mode 100644
index 0000000..f022044
--- /dev/null
+++ b/usr.sbin/bsdinstall/partedit/partedit.c
@@ -0,0 +1,562 @@
+/*-
+ * Copyright (c) 2011 Nathan Whitehorn
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <libgen.h>
+#include <libutil.h>
+#include <inttypes.h>
+#include <errno.h>
+
+#include <fstab.h>
+#include <libgeom.h>
+#include <dialog.h>
+#include <dlg_keys.h>
+
+#include "diskeditor.h"
+#include "partedit.h"
+
+struct pmetadata_head part_metadata;
+static int sade_mode = 0;
+
+static int apply_changes(struct gmesh *mesh);
+static void apply_workaround(struct gmesh *mesh);
+static struct partedit_item *read_geom_mesh(struct gmesh *mesh, int *nitems);
+static void add_geom_children(struct ggeom *gp, int recurse,
+ struct partedit_item **items, int *nitems);
+static void init_fstab_metadata(void);
+static void get_mount_points(struct partedit_item *items, int nitems);
+static int validate_setup(void);
+
+static void
+sigint_handler(int sig)
+{
+ struct gmesh mesh;
+
+ /* Revert all changes and exit dialog-mode cleanly on SIGINT */
+ geom_gettree(&mesh);
+ gpart_revert_all(&mesh);
+ geom_deletetree(&mesh);
+
+ end_dialog();
+
+ exit(1);
+}
+
+int
+main(int argc, const char **argv)
+{
+ struct partition_metadata *md;
+ const char *prompt;
+ struct partedit_item *items = NULL;
+ struct gmesh mesh;
+ int i, op, nitems, nscroll;
+ int error;
+
+ if (strcmp(basename(argv[0]), "sade") == 0)
+ sade_mode = 1;
+
+ TAILQ_INIT(&part_metadata);
+
+ init_fstab_metadata();
+
+ init_dialog(stdin, stdout);
+ if (!sade_mode)
+ dialog_vars.backtitle = __DECONST(char *, "FreeBSD Installer");
+ dialog_vars.item_help = TRUE;
+ nscroll = i = 0;
+
+ /* Revert changes on SIGINT */
+ signal(SIGINT, sigint_handler);
+
+ if (strcmp(basename(argv[0]), "autopart") == 0) { /* Guided */
+ prompt = "Please review the disk setup. When complete, press "
+ "the Finish button.";
+ /* Experimental ZFS autopartition support */
+ if (argc > 1 && strcmp(argv[1], "zfs") == 0) {
+ part_wizard("zfs");
+ } else {
+ part_wizard("ufs");
+ }
+ } else if (strcmp(basename(argv[0]), "scriptedpart") == 0) {
+ error = scripted_editor(argc, argv);
+ prompt = NULL;
+ if (error != 0) {
+ end_dialog();
+ return (error);
+ }
+ } else {
+ prompt = "Create partitions for FreeBSD. No changes will be "
+ "made until you select Finish.";
+ }
+
+ /* Show the part editor either immediately, or to confirm wizard */
+ while (prompt != NULL) {
+ dlg_clear();
+ dlg_put_backtitle();
+
+ error = geom_gettree(&mesh);
+ if (error == 0)
+ items = read_geom_mesh(&mesh, &nitems);
+ if (error || items == NULL) {
+ dialog_msgbox("Error", "No disks found. If you need to "
+ "install a kernel driver, choose Shell at the "
+ "installation menu.", 0, 0, TRUE);
+ break;
+ }
+
+ get_mount_points(items, nitems);
+
+ if (i >= nitems)
+ i = nitems - 1;
+ op = diskeditor_show("Partition Editor", prompt,
+ items, nitems, &i, &nscroll);
+
+ switch (op) {
+ case 0: /* Create */
+ gpart_create((struct gprovider *)(items[i].cookie),
+ NULL, NULL, NULL, NULL, 1);
+ break;
+ case 1: /* Delete */
+ gpart_delete((struct gprovider *)(items[i].cookie));
+ break;
+ case 2: /* Modify */
+ gpart_edit((struct gprovider *)(items[i].cookie));
+ break;
+ case 3: /* Revert */
+ gpart_revert_all(&mesh);
+ while ((md = TAILQ_FIRST(&part_metadata)) != NULL) {
+ if (md->fstab != NULL) {
+ free(md->fstab->fs_spec);
+ free(md->fstab->fs_file);
+ free(md->fstab->fs_vfstype);
+ free(md->fstab->fs_mntops);
+ free(md->fstab->fs_type);
+ free(md->fstab);
+ }
+ if (md->newfs != NULL)
+ free(md->newfs);
+ free(md->name);
+
+ TAILQ_REMOVE(&part_metadata, md, metadata);
+ free(md);
+ }
+ init_fstab_metadata();
+ break;
+ case 4: /* Auto */
+ part_wizard("ufs");
+ break;
+ }
+
+ error = 0;
+ if (op == 5) { /* Finished */
+ dialog_vars.ok_label = __DECONST(char *, "Commit");
+ dialog_vars.extra_label =
+ __DECONST(char *, "Revert & Exit");
+ dialog_vars.extra_button = TRUE;
+ dialog_vars.cancel_label = __DECONST(char *, "Back");
+ op = dialog_yesno("Confirmation", "Your changes will "
+ "now be written to disk. If you have chosen to "
+ "overwrite existing data, it will be PERMANENTLY "
+ "ERASED. Are you sure you want to commit your "
+ "changes?", 0, 0);
+ dialog_vars.ok_label = NULL;
+ dialog_vars.extra_button = FALSE;
+ dialog_vars.cancel_label = NULL;
+
+ if (op == 0 && validate_setup()) { /* Save */
+ error = apply_changes(&mesh);
+ if (!error)
+ apply_workaround(&mesh);
+ break;
+ } else if (op == 3) { /* Quit */
+ gpart_revert_all(&mesh);
+ error = -1;
+ break;
+ }
+ }
+
+ geom_deletetree(&mesh);
+ free(items);
+ }
+
+ if (prompt == NULL) {
+ error = geom_gettree(&mesh);
+ if (validate_setup()) {
+ error = apply_changes(&mesh);
+ } else {
+ gpart_revert_all(&mesh);
+ error = -1;
+ }
+ }
+
+ geom_deletetree(&mesh);
+ free(items);
+ end_dialog();
+
+ return (error);
+}
+
+struct partition_metadata *
+get_part_metadata(const char *name, int create)
+{
+ struct partition_metadata *md;
+
+ TAILQ_FOREACH(md, &part_metadata, metadata)
+ if (md->name != NULL && strcmp(md->name, name) == 0)
+ break;
+
+ if (md == NULL && create) {
+ md = calloc(1, sizeof(*md));
+ md->name = strdup(name);
+ TAILQ_INSERT_TAIL(&part_metadata, md, metadata);
+ }
+
+ return (md);
+}
+
+void
+delete_part_metadata(const char *name)
+{
+ struct partition_metadata *md;
+
+ TAILQ_FOREACH(md, &part_metadata, metadata) {
+ if (md->name != NULL && strcmp(md->name, name) == 0) {
+ if (md->fstab != NULL) {
+ free(md->fstab->fs_spec);
+ free(md->fstab->fs_file);
+ free(md->fstab->fs_vfstype);
+ free(md->fstab->fs_mntops);
+ free(md->fstab->fs_type);
+ free(md->fstab);
+ }
+ if (md->newfs != NULL)
+ free(md->newfs);
+ free(md->name);
+
+ TAILQ_REMOVE(&part_metadata, md, metadata);
+ free(md);
+ break;
+ }
+ }
+}
+
+static int
+validate_setup(void)
+{
+ struct partition_metadata *md, *root = NULL;
+ int cancel;
+
+ TAILQ_FOREACH(md, &part_metadata, metadata) {
+ if (md->fstab != NULL && strcmp(md->fstab->fs_file, "/") == 0)
+ root = md;
+
+ /* XXX: Check for duplicate mountpoints */
+ }
+
+ if (root == NULL) {
+ dialog_msgbox("Error", "No root partition was found. "
+ "The root FreeBSD partition must have a mountpoint of '/'.",
+ 0, 0, TRUE);
+ return (FALSE);
+ }
+
+ /*
+ * Check for root partitions that we aren't formatting, which is
+ * usually a mistake
+ */
+ if (root->newfs == NULL && !sade_mode) {
+ dialog_vars.defaultno = TRUE;
+ cancel = dialog_yesno("Warning", "The chosen root partition "
+ "has a preexisting filesystem. If it contains an existing "
+ "FreeBSD system, please update it with freebsd-update "
+ "instead of installing a new system on it. The partition "
+ "can also be erased by pressing \"No\" and then deleting "
+ "and recreating it. Are you sure you want to proceed?",
+ 0, 0);
+ dialog_vars.defaultno = FALSE;
+ if (cancel)
+ return (FALSE);
+ }
+
+ return (TRUE);
+}
+
+static int
+apply_changes(struct gmesh *mesh)
+{
+ struct partition_metadata *md;
+ char message[512];
+ int i, nitems, error;
+ const char **items;
+ const char *fstab_path;
+ FILE *fstab;
+
+ nitems = 1; /* Partition table changes */
+ TAILQ_FOREACH(md, &part_metadata, metadata) {
+ if (md->newfs != NULL)
+ nitems++;
+ }
+ items = calloc(nitems * 2, sizeof(const char *));
+ items[0] = "Writing partition tables";
+ items[1] = "7"; /* In progress */
+ i = 1;
+ TAILQ_FOREACH(md, &part_metadata, metadata) {
+ if (md->newfs != NULL) {
+ char *item;
+ item = malloc(255);
+ sprintf(item, "Initializing %s", md->name);
+ items[i*2] = item;
+ items[i*2 + 1] = "Pending";
+ i++;
+ }
+ }
+
+ i = 0;
+ dialog_mixedgauge("Initializing",
+ "Initializing file systems. Please wait.", 0, 0, i*100/nitems,
+ nitems, __DECONST(char **, items));
+ gpart_commit(mesh);
+ items[i*2 + 1] = "3";
+ i++;
+
+ if (getenv("BSDINSTALL_LOG") == NULL)
+ setenv("BSDINSTALL_LOG", "/dev/null", 1);
+
+ TAILQ_FOREACH(md, &part_metadata, metadata) {
+ if (md->newfs != NULL) {
+ items[i*2 + 1] = "7"; /* In progress */
+ dialog_mixedgauge("Initializing",
+ "Initializing file systems. Please wait.", 0, 0,
+ i*100/nitems, nitems, __DECONST(char **, items));
+ sprintf(message, "(echo %s; %s) >>%s 2>>%s",
+ md->newfs, md->newfs, getenv("BSDINSTALL_LOG"),
+ getenv("BSDINSTALL_LOG"));
+ error = system(message);
+ items[i*2 + 1] = (error == 0) ? "3" : "1";
+ i++;
+ }
+ }
+ dialog_mixedgauge("Initializing",
+ "Initializing file systems. Please wait.", 0, 0,
+ i*100/nitems, nitems, __DECONST(char **, items));
+
+ for (i = 1; i < nitems; i++)
+ free(__DECONST(char *, items[i*2]));
+ free(items);
+
+ if (getenv("PATH_FSTAB") != NULL)
+ fstab_path = getenv("PATH_FSTAB");
+ else
+ fstab_path = "/etc/fstab";
+ fstab = fopen(fstab_path, "w+");
+ if (fstab == NULL) {
+ sprintf(message, "Cannot open fstab file %s for writing (%s)\n",
+ getenv("PATH_FSTAB"), strerror(errno));
+ dialog_msgbox("Error", message, 0, 0, TRUE);
+ return (-1);
+ }
+ fprintf(fstab, "# Device\tMountpoint\tFStype\tOptions\tDump\tPass#\n");
+ TAILQ_FOREACH(md, &part_metadata, metadata) {
+ if (md->fstab != NULL)
+ fprintf(fstab, "%s\t%s\t\t%s\t%s\t%d\t%d\n",
+ md->fstab->fs_spec, md->fstab->fs_file,
+ md->fstab->fs_vfstype, md->fstab->fs_mntops,
+ md->fstab->fs_freq, md->fstab->fs_passno);
+ }
+ fclose(fstab);
+
+ return (0);
+}
+
+static void
+apply_workaround(struct gmesh *mesh)
+{
+ struct gclass *classp;
+ struct ggeom *gp;
+ struct gconfig *gc;
+ const char *scheme = NULL, *modified = NULL;
+
+ LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
+ if (strcmp(classp->lg_name, "PART") == 0)
+ break;
+ }
+
+ if (strcmp(classp->lg_name, "PART") != 0) {
+ dialog_msgbox("Error", "gpart not found!", 0, 0, TRUE);
+ return;
+ }
+
+ LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
+ LIST_FOREACH(gc, &gp->lg_config, lg_config) {
+ if (strcmp(gc->lg_name, "scheme") == 0) {
+ scheme = gc->lg_val;
+ } else if (strcmp(gc->lg_name, "modified") == 0) {
+ modified = gc->lg_val;
+ }
+ }
+
+ if (scheme && strcmp(scheme, "GPT") == 0 &&
+ modified && strcmp(modified, "true") == 0) {
+ if (getenv("WORKAROUND_LENOVO"))
+ gpart_set_root(gp->lg_name, "lenovofix");
+ if (getenv("WORKAROUND_GPTACTIVE"))
+ gpart_set_root(gp->lg_name, "active");
+ }
+ }
+}
+
+static struct partedit_item *
+read_geom_mesh(struct gmesh *mesh, int *nitems)
+{
+ struct gclass *classp;
+ struct ggeom *gp;
+ struct partedit_item *items;
+
+ *nitems = 0;
+ items = NULL;
+
+ /*
+ * Build the device table. First add all disks (and CDs).
+ */
+
+ LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
+ if (strcmp(classp->lg_name, "DISK") != 0 &&
+ strcmp(classp->lg_name, "MD") != 0)
+ continue;
+
+ /* Now recurse into all children */
+ LIST_FOREACH(gp, &classp->lg_geom, lg_geom)
+ add_geom_children(gp, 0, &items, nitems);
+ }
+
+ return (items);
+}
+
+static void
+add_geom_children(struct ggeom *gp, int recurse, struct partedit_item **items,
+ int *nitems)
+{
+ struct gconsumer *cp;
+ struct gprovider *pp;
+ struct gconfig *gc;
+
+ if (strcmp(gp->lg_class->lg_name, "PART") == 0 &&
+ !LIST_EMPTY(&gp->lg_config)) {
+ LIST_FOREACH(gc, &gp->lg_config, lg_config) {
+ if (strcmp(gc->lg_name, "scheme") == 0)
+ (*items)[*nitems-1].type = gc->lg_val;
+ }
+ }
+
+ if (LIST_EMPTY(&gp->lg_provider))
+ return;
+
+ LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
+ if (strcmp(gp->lg_class->lg_name, "LABEL") == 0)
+ continue;
+
+ /* Skip WORM media */
+ if (strncmp(pp->lg_name, "cd", 2) == 0)
+ continue;
+
+ *items = realloc(*items,
+ (*nitems+1)*sizeof(struct partedit_item));
+ (*items)[*nitems].indentation = recurse;
+ (*items)[*nitems].name = pp->lg_name;
+ (*items)[*nitems].size = pp->lg_mediasize;
+ (*items)[*nitems].mountpoint = NULL;
+ (*items)[*nitems].type = "";
+ (*items)[*nitems].cookie = pp;
+
+ LIST_FOREACH(gc, &pp->lg_config, lg_config) {
+ if (strcmp(gc->lg_name, "type") == 0)
+ (*items)[*nitems].type = gc->lg_val;
+ }
+
+ /* Skip swap-backed MD devices */
+ if (strcmp(gp->lg_class->lg_name, "MD") == 0 &&
+ strcmp((*items)[*nitems].type, "swap") == 0)
+ continue;
+
+ (*nitems)++;
+
+ LIST_FOREACH(cp, &pp->lg_consumers, lg_consumers)
+ add_geom_children(cp->lg_geom, recurse+1, items,
+ nitems);
+
+ /* Only use first provider for acd */
+ if (strcmp(gp->lg_class->lg_name, "ACD") == 0)
+ break;
+ }
+}
+
+static void
+init_fstab_metadata(void)
+{
+ struct fstab *fstab;
+ struct partition_metadata *md;
+
+ setfsent();
+ while ((fstab = getfsent()) != NULL) {
+ md = calloc(1, sizeof(struct partition_metadata));
+
+ md->name = NULL;
+ if (strncmp(fstab->fs_spec, "/dev/", 5) == 0)
+ md->name = strdup(&fstab->fs_spec[5]);
+
+ md->fstab = malloc(sizeof(struct fstab));
+ md->fstab->fs_spec = strdup(fstab->fs_spec);
+ md->fstab->fs_file = strdup(fstab->fs_file);
+ md->fstab->fs_vfstype = strdup(fstab->fs_vfstype);
+ md->fstab->fs_mntops = strdup(fstab->fs_mntops);
+ md->fstab->fs_type = strdup(fstab->fs_type);
+ md->fstab->fs_freq = fstab->fs_freq;
+ md->fstab->fs_passno = fstab->fs_passno;
+
+ md->newfs = NULL;
+
+ TAILQ_INSERT_TAIL(&part_metadata, md, metadata);
+ }
+}
+
+static void
+get_mount_points(struct partedit_item *items, int nitems)
+{
+ struct partition_metadata *md;
+ int i;
+
+ for (i = 0; i < nitems; i++) {
+ TAILQ_FOREACH(md, &part_metadata, metadata) {
+ if (md->name != NULL && md->fstab != NULL &&
+ strcmp(md->name, items[i].name) == 0) {
+ items[i].mountpoint = md->fstab->fs_file;
+ break;
+ }
+ }
+ }
+}
diff --git a/usr.sbin/bsdinstall/partedit/partedit.h b/usr.sbin/bsdinstall/partedit/partedit.h
new file mode 100644
index 0000000..3eb9173
--- /dev/null
+++ b/usr.sbin/bsdinstall/partedit/partedit.h
@@ -0,0 +1,87 @@
+/*-
+ * Copyright (c) 2011 Nathan Whitehorn
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 _PARTEDIT_PARTEDIT_H
+#define _PARTEDIT_PARTEDIT_H
+
+#include <sys/queue.h>
+#include <inttypes.h>
+#include <fstab.h>
+
+struct gprovider;
+struct gmesh;
+struct ggeom;
+
+TAILQ_HEAD(pmetadata_head, partition_metadata);
+extern struct pmetadata_head part_metadata;
+
+struct partition_metadata {
+ char *name; /* name of this partition, as in GEOM */
+
+ struct fstab *fstab; /* fstab data for this partition */
+ char *newfs; /* shell command to initialize partition */
+
+ int bootcode;
+
+ TAILQ_ENTRY(partition_metadata) metadata;
+};
+
+struct partition_metadata *get_part_metadata(const char *name, int create);
+void delete_part_metadata(const char *name);
+
+int part_wizard(const char *fstype);
+int scripted_editor(int argc, const char **argv);
+int wizard_makeparts(struct gmesh *mesh, const char *disk, const char *fstype,
+ int interactive);
+
+/* gpart operations */
+void gpart_delete(struct gprovider *pp);
+void gpart_destroy(struct ggeom *lg_geom);
+void gpart_edit(struct gprovider *pp);
+void gpart_create(struct gprovider *pp, char *default_type, char *default_size,
+ char *default_mountpoint, char **output, int interactive);
+intmax_t gpart_max_free(struct ggeom *gp, intmax_t *start);
+void gpart_revert(struct gprovider *pp);
+void gpart_revert_all(struct gmesh *mesh);
+void gpart_commit(struct gmesh *mesh);
+int gpart_partition(const char *lg_name, const char *scheme);
+void set_default_part_metadata(const char *name, const char *scheme,
+ const char *type, const char *mountpoint, const char *newfs);
+void gpart_set_root(const char *lg_name, const char *attribute);
+const char *choose_part_type(const char *def_scheme);
+
+/* machine-dependent bootability checks */
+const char *default_scheme(void);
+int is_scheme_bootable(const char *scheme);
+int is_fs_bootable(const char *scheme, const char *fs);
+size_t bootpart_size(const char *scheme);
+const char *bootpart_type(const char *scheme);
+const char *bootcode_path(const char *scheme);
+const char *partcode_path(const char *scheme, const char *fs_type);
+
+#endif
diff --git a/usr.sbin/bsdinstall/partedit/partedit_generic.c b/usr.sbin/bsdinstall/partedit/partedit_generic.c
new file mode 100644
index 0000000..ceee95a
--- /dev/null
+++ b/usr.sbin/bsdinstall/partedit/partedit_generic.c
@@ -0,0 +1,79 @@
+/*-
+ * Copyright (c) 2011 Nathan Whitehorn
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <string.h>
+
+#include "partedit.h"
+
+const char *
+default_scheme(void) {
+ /*
+ * Our loader can parse GPT, so pick that as the default for lack of
+ * a better idea.
+ */
+
+ return ("GPT");
+}
+
+int
+is_scheme_bootable(const char *part_type) {
+ /*
+ * We don't know anything about this platform, so don't irritate the
+ * user by claiming the chosen partition scheme isn't bootable.
+ */
+
+ return (1);
+}
+
+int
+is_fs_bootable(const char *part_type, const char *fs) {
+ return (1);
+}
+
+/* No clue => no boot partition, bootcode, or partcode */
+
+size_t
+bootpart_size(const char *part_type) {
+ return (0);
+}
+
+const char *
+bootpart_type(const char *scheme) {
+ return ("freebsd-boot");
+}
+
+const char *
+bootcode_path(const char *part_type) {
+ return (NULL);
+}
+
+const char *
+partcode_path(const char *part_type, const char *fs_type) {
+ return (NULL);
+}
+
diff --git a/usr.sbin/bsdinstall/partedit/partedit_pc98.c b/usr.sbin/bsdinstall/partedit/partedit_pc98.c
new file mode 100644
index 0000000..8e914ea
--- /dev/null
+++ b/usr.sbin/bsdinstall/partedit/partedit_pc98.c
@@ -0,0 +1,83 @@
+/*-
+ * Copyright (c) 2011 Nathan Whitehorn
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <string.h>
+
+#include "partedit.h"
+
+const char *
+default_scheme(void) {
+ return ("PC98");
+}
+
+int
+is_scheme_bootable(const char *part_type) {
+ if (strcmp(part_type, "BSD") == 0)
+ return (1);
+ if (strcmp(part_type, "PC98") == 0)
+ return (1);
+
+ return (0);
+}
+
+int
+is_fs_bootable(const char *part_type, const char *fs)
+{
+ if (strcmp(fs, "freebsd-ufs") == 0)
+ return (1);
+
+ return (0);
+}
+
+size_t
+bootpart_size(const char *part_type) {
+ /* No boot partition */
+ return (0);
+}
+
+const char *
+bootpart_type(const char *scheme) {
+ return ("freebsd-boot");
+}
+
+const char *
+bootcode_path(const char *part_type) {
+ if (strcmp(part_type, "PC98") == 0)
+ return ("/boot/pc98boot");
+ if (strcmp(part_type, "BSD") == 0)
+ return ("/boot/boot");
+
+ return (NULL);
+}
+
+const char *
+partcode_path(const char *part_type, const char *fs_type) {
+ /* No partcode */
+ return (NULL);
+}
+
diff --git a/usr.sbin/bsdinstall/partedit/partedit_powerpc.c b/usr.sbin/bsdinstall/partedit/partedit_powerpc.c
new file mode 100644
index 0000000..6a5dbb2
--- /dev/null
+++ b/usr.sbin/bsdinstall/partedit/partedit_powerpc.c
@@ -0,0 +1,124 @@
+/*-
+ * Copyright (c) 2011 Nathan Whitehorn
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/sysctl.h>
+#include <string.h>
+
+#include "partedit.h"
+
+static char platform[255] = "";
+
+const char *
+default_scheme(void) {
+ size_t platlen = sizeof(platform);
+ if (strlen(platform) == 0)
+ sysctlbyname("hw.platform", platform, &platlen, NULL, -1);
+
+ if (strcmp(platform, "powermac") == 0)
+ return ("APM");
+ if (strcmp(platform, "chrp") == 0)
+ return ("MBR");
+
+ /* Pick GPT (bootable on PS3) as a generic default */
+ return ("GPT");
+}
+
+int
+is_scheme_bootable(const char *part_type) {
+ size_t platlen = sizeof(platform);
+ if (strlen(platform) == 0)
+ sysctlbyname("hw.platform", platform, &platlen, NULL, -1);
+
+ if (strcmp(platform, "powermac") == 0 && strcmp(part_type, "APM") == 0)
+ return (1);
+ if (strcmp(platform, "ps3") == 0 && strcmp(part_type, "GPT") == 0)
+ return (1);
+ if (strcmp(platform, "chrp") == 0 &&
+ (strcmp(part_type, "MBR") == 0 || strcmp(part_type, "BSD") == 0 ||
+ strcmp(part_type, "GPT") == 0))
+ return (1);
+
+ return (0);
+}
+
+int
+is_fs_bootable(const char *part_type, const char *fs)
+{
+ if (strcmp(fs, "freebsd-ufs") == 0)
+ return (1);
+
+ return (0);
+}
+
+size_t
+bootpart_size(const char *part_type) {
+ size_t platlen = sizeof(platform);
+ if (strlen(platform) == 0)
+ sysctlbyname("hw.platform", platform, &platlen, NULL, -1);
+
+ if (strcmp(part_type, "APM") == 0 || strcmp(part_type, "MBR") == 0)
+ return (800*1024);
+ if (strcmp(platform, "chrp") == 0 && strcmp(part_type, "GPT") == 0)
+ return (800*1024);
+ return (0);
+}
+
+const char *
+bootpart_type(const char *scheme) {
+ size_t platlen = sizeof(platform);
+ if (strlen(platform) == 0)
+ sysctlbyname("hw.platform", platform, &platlen, NULL, -1);
+
+ if (strcmp(platform, "chrp") == 0)
+ return ("prep-boot");
+ if (strcmp(platform, "powermac") == 0)
+ return ("apple-boot");
+
+ return ("freebsd-boot");
+}
+
+const char *
+bootcode_path(const char *part_type) {
+ return (NULL);
+}
+
+const char *
+partcode_path(const char *part_type, const char *fs_type) {
+ size_t platlen = sizeof(platform);
+ if (strlen(platform) == 0)
+ sysctlbyname("hw.platform", platform, &platlen, NULL, -1);
+
+ if (strcmp(part_type, "APM") == 0)
+ return ("/boot/boot1.hfs");
+ if (strcmp(part_type, "MBR") == 0 ||
+ (strcmp(platform, "chrp") == 0 && strcmp(part_type, "GPT") == 0))
+ return ("/boot/boot1.elf");
+ return (NULL);
+}
+
diff --git a/usr.sbin/bsdinstall/partedit/partedit_sparc64.c b/usr.sbin/bsdinstall/partedit/partedit_sparc64.c
new file mode 100644
index 0000000..c420f6a
--- /dev/null
+++ b/usr.sbin/bsdinstall/partedit/partedit_sparc64.c
@@ -0,0 +1,82 @@
+/*-
+ * Copyright (c) 2011 Nathan Whitehorn
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <string.h>
+
+#include "partedit.h"
+
+const char *
+default_scheme(void) {
+ return ("VTOC8");
+}
+
+int
+is_scheme_bootable(const char *part_type) {
+ if (strcmp(part_type, "VTOC8") == 0)
+ return (1);
+ return (0);
+}
+
+int
+is_fs_bootable(const char *part_type, const char *fs)
+{
+ if (strcmp(fs, "freebsd-ufs") == 0 || strcmp(fs, "freebsd-zfs") == 0)
+ return (1);
+ return (0);
+}
+
+
+size_t
+bootpart_size(const char *part_type) {
+ /* No standalone boot partition */
+
+ return (0);
+}
+
+const char *
+bootpart_type(const char *scheme) {
+ return ("freebsd-boot");
+}
+
+const char *
+bootcode_path(const char *part_type) {
+ return (NULL);
+}
+
+const char *
+partcode_path(const char *part_type, const char *fs_type) {
+ if (strcmp(part_type, "VTOC8") == 0) {
+ if (strcmp(fs_type, "ufs") == 0) {
+ return ("/boot/boot1");
+ } else if (strcmp(fs_type, "zfs") == 0) {
+ return ("/boot/zfsboot");
+ }
+ }
+ return (NULL);
+}
+
diff --git a/usr.sbin/bsdinstall/partedit/partedit_x86.c b/usr.sbin/bsdinstall/partedit/partedit_x86.c
new file mode 100644
index 0000000..6a60678
--- /dev/null
+++ b/usr.sbin/bsdinstall/partedit/partedit_x86.c
@@ -0,0 +1,150 @@
+/*-
+ * Copyright (c) 2011 Nathan Whitehorn
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/sysctl.h>
+#include <string.h>
+
+#include "partedit.h"
+
+static const char *
+x86_bootmethod(void)
+{
+ static char fw[255] = "";
+ size_t len = sizeof(fw);
+ int error;
+
+ if (strlen(fw) == 0) {
+ error = sysctlbyname("machdep.bootmethod", fw, &len, NULL, -1);
+ if (error != 0)
+ return ("BIOS");
+ }
+
+ return (fw);
+}
+
+const char *
+default_scheme(void)
+{
+ if (strcmp(x86_bootmethod(), "UEFI") == 0)
+ return ("GPT");
+ else
+ return ("MBR");
+}
+
+int
+is_scheme_bootable(const char *part_type)
+{
+
+ if (strcmp(part_type, "GPT") == 0)
+ return (1);
+ if (strcmp(x86_bootmethod(), "BIOS") == 0) {
+ if (strcmp(part_type, "BSD") == 0)
+ return (1);
+ if (strcmp(part_type, "MBR") == 0)
+ return (1);
+ }
+
+ return (0);
+}
+
+int
+is_fs_bootable(const char *part_type, const char *fs)
+{
+
+ if (strcmp(fs, "freebsd-ufs") == 0)
+ return (1);
+
+ if (strcmp(fs, "freebsd-zfs") == 0 &&
+ strcmp(part_type, "GPT") == 0 &&
+ strcmp(x86_bootmethod(), "BIOS") == 0)
+ return (1);
+
+ return (0);
+}
+
+size_t
+bootpart_size(const char *scheme)
+{
+
+ /* No partcode except for GPT */
+ if (strcmp(scheme, "GPT") != 0)
+ return (0);
+
+ if (strcmp(x86_bootmethod(), "BIOS") == 0)
+ return (512*1024);
+ else
+ return (800*1024);
+
+ return (0);
+}
+
+const char *
+bootpart_type(const char *scheme)
+{
+
+ if (strcmp(x86_bootmethod(), "UEFI") == 0)
+ return ("efi");
+
+ return ("freebsd-boot");
+}
+
+const char *
+bootcode_path(const char *part_type)
+{
+
+ if (strcmp(x86_bootmethod(), "UEFI") == 0)
+ return (NULL);
+
+ if (strcmp(part_type, "GPT") == 0)
+ return ("/boot/pmbr");
+ if (strcmp(part_type, "MBR") == 0)
+ return ("/boot/mbr");
+ if (strcmp(part_type, "BSD") == 0)
+ return ("/boot/boot");
+
+ return (NULL);
+}
+
+const char *
+partcode_path(const char *part_type, const char *fs_type)
+{
+
+ if (strcmp(part_type, "GPT") == 0) {
+ if (strcmp(x86_bootmethod(), "UEFI") == 0)
+ return ("/boot/boot1.efifat");
+ else if (strcmp(fs_type, "zfs") == 0)
+ return ("/boot/gptzfsboot");
+ else
+ return ("/boot/gptboot");
+ }
+
+ /* No partcode except for GPT */
+ return (NULL);
+}
+
diff --git a/usr.sbin/bsdinstall/partedit/sade.8 b/usr.sbin/bsdinstall/partedit/sade.8
new file mode 100644
index 0000000..3bb1c65
--- /dev/null
+++ b/usr.sbin/bsdinstall/partedit/sade.8
@@ -0,0 +1,72 @@
+.\" Copyright (c) 1997
+.\" Jordan Hubbard <jkh@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 Jordan Hubbard AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL Jordan Hubbard OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (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, 2012
+.Dt SADE 8
+.Os
+.Sh NAME
+.Nm sade
+.Nd sysadmins disk editor
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+The
+.Nm
+utility is used for various disk administration tasks on
+.Fx
+systems.
+.Pp
+It is generally invoked without arguments for the default
+behavior, where the main menu is presented.
+.Sh NOTES
+The
+.Nm
+utility aims to provide a handy tool for disk management
+tasks on an already installed system.
+The goal is to provide the same text interface for disk management in
+.Xr bsdinstall 8
+in the post-installation environment.
+.Sh SEE ALSO
+.Xr bsdinstall 8 ,
+.Xr gpart 8
+.Sh HISTORY
+A program called
+.Nm
+first appeared in
+.Fx 6.3
+as a utility encapsulating features from the
+.Xr sysinstall 8
+installer. It was replaced in
+.Fx 10.0
+with the equivalent part of
+.Xr bsdinstall 8 .
+.Sh AUTHORS
+.An Nathan Whitehorn Aq Mt nwhitehorn@FreeBSD.org
+.Sh BUGS
+The utility misses a lot of nice features, such as tools for
+manipulating
+.Xr gmirror 8 .
+These will be added later.
diff --git a/usr.sbin/bsdinstall/partedit/scripted.c b/usr.sbin/bsdinstall/partedit/scripted.c
new file mode 100644
index 0000000..876df54
--- /dev/null
+++ b/usr.sbin/bsdinstall/partedit/scripted.c
@@ -0,0 +1,213 @@
+/*-
+ * Copyright (c) 2013 Nathan Whitehorn
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <errno.h>
+#include <libutil.h>
+#include <inttypes.h>
+
+#include <libgeom.h>
+#include <dialog.h>
+#include <dlg_keys.h>
+
+#include "partedit.h"
+
+static struct gprovider *
+provider_for_name(struct gmesh *mesh, const char *name)
+{
+ struct gclass *classp;
+ struct gprovider *pp = NULL;
+ struct ggeom *gp;
+
+ LIST_FOREACH(classp, &mesh->lg_class, lg_class) {
+ LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
+ if (LIST_EMPTY(&gp->lg_provider))
+ continue;
+
+ LIST_FOREACH(pp, &gp->lg_provider, lg_provider)
+ if (strcmp(pp->lg_name, name) == 0)
+ break;
+
+ if (pp != NULL) break;
+ }
+
+ if (pp != NULL) break;
+ }
+
+ return (pp);
+}
+
+static int
+part_config(char *disk, const char *scheme, char *config)
+{
+ char *partition, *ap, *size = NULL, *type = NULL, *mount = NULL;
+ struct gclass *classp;
+ struct gmesh mesh;
+ struct ggeom *gpart = NULL;
+ int error;
+
+ if (scheme == NULL)
+ scheme = default_scheme();
+
+ error = geom_gettree(&mesh);
+ if (provider_for_name(&mesh, disk) == NULL) {
+ fprintf(stderr, "GEOM provider %s not found\n", disk);
+ geom_deletetree(&mesh);
+ return (-1);
+ }
+
+ /* Remove any existing partitioning and create new scheme */
+ LIST_FOREACH(classp, &mesh.lg_class, lg_class)
+ if (strcmp(classp->lg_name, "PART") == 0)
+ break;
+ if (classp != NULL) {
+ LIST_FOREACH(gpart, &classp->lg_geom, lg_geom)
+ if (strcmp(gpart->lg_name, disk) == 0)
+ break;
+ }
+ if (gpart != NULL)
+ gpart_destroy(gpart);
+ gpart_partition(disk, scheme);
+
+ if (strcmp(scheme, "PC98") == 0 || strcmp(scheme, "MBR") == 0) {
+ struct gmesh submesh;
+ geom_gettree(&submesh);
+ gpart_create(provider_for_name(&submesh, disk),
+ "freebsd", NULL, NULL, &disk, 0);
+ geom_deletetree(&submesh);
+ } else {
+ disk= strdup(disk);
+ }
+
+ geom_deletetree(&mesh);
+ error = geom_gettree(&mesh);
+
+ /* Create partitions */
+ if (config == NULL) {
+ wizard_makeparts(&mesh, disk, "ufs", 0);
+ goto finished;
+ }
+
+ while ((partition = strsep(&config, ",")) != NULL) {
+ while ((ap = strsep(&partition, " \t\n")) != NULL) {
+ if (*ap == '\0')
+ continue;
+ if (size == NULL)
+ size = ap;
+ else if (type == NULL)
+ type = ap;
+ else if (mount == NULL)
+ mount = ap;
+ }
+ if (size == NULL)
+ continue;
+ if (strcmp(size, "auto") == 0)
+ size = NULL;
+ gpart_create(provider_for_name(&mesh, disk), type, size, mount,
+ NULL, 0);
+ geom_deletetree(&mesh);
+ error = geom_gettree(&mesh);
+ size = type = mount = NULL;
+ }
+
+finished:
+ geom_deletetree(&mesh);
+ free(disk);
+
+ return (0);
+}
+
+static
+int parse_disk_config(char *input)
+{
+ char *ap;
+ char *disk = NULL, *scheme = NULL, *partconfig = NULL;
+
+ while (input != NULL && *input != 0) {
+ if (isspace(*input)) {
+ input++;
+ continue;
+ }
+
+ switch(*input) {
+ case '{':
+ input++;
+ partconfig = strchr(input, '}');
+ if (partconfig == NULL) {
+ fprintf(stderr, "Malformed partition setup "
+ "string: %s\n", input);
+ return (1);
+ }
+ *partconfig = '\0';
+ ap = partconfig+1;
+ partconfig = input;
+ input = ap;
+ break;
+ default:
+ if (disk == NULL)
+ disk = strsep(&input, " \t\n");
+ else if (scheme == NULL)
+ scheme = strsep(&input, " \t\n");
+ else {
+ fprintf(stderr, "Unknown directive: %s\n",
+ strsep(&input, " \t\n"));
+ return (1);
+ }
+ }
+ } while (input != NULL && *input != 0);
+
+ if (disk != NULL)
+ return (part_config(disk, scheme, partconfig));
+
+ return (0);
+}
+
+int
+scripted_editor(int argc, const char **argv)
+{
+ char *token;
+ int i, error = 0, len = 0;
+
+ for (i = 1; i < argc; i++)
+ len += strlen(argv[i]) + 1;
+ char inputbuf[len], *input = inputbuf;
+ strcpy(input, argv[1]);
+ for (i = 2; i < argc; i++) {
+ strcat(input, " ");
+ strcat(input, argv[i]);
+ }
+
+ while ((token = strsep(&input, ";")) != NULL) {
+ error = parse_disk_config(token);
+ if (error != 0)
+ return (error);
+ }
+
+ return (0);
+}
+
diff --git a/usr.sbin/bsdinstall/scripts/Makefile b/usr.sbin/bsdinstall/scripts/Makefile
new file mode 100644
index 0000000..c0d6ac2
--- /dev/null
+++ b/usr.sbin/bsdinstall/scripts/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+SCRIPTS= auto adduser checksum config docsinstall entropy hostname jail \
+ keymap mirrorselect mount netconfig netconfig_ipv4 netconfig_ipv6 \
+ rootpass script services time umount wlanconfig zfsboot
+BINDIR= ${LIBEXECDIR}/bsdinstall
+
+MAN=
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdinstall/scripts/Makefile.depend b/usr.sbin/bsdinstall/scripts/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsdinstall/scripts/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsdinstall/scripts/adduser b/usr.sbin/bsdinstall/scripts/adduser
new file mode 100755
index 0000000..456f76b
--- /dev/null
+++ b/usr.sbin/bsdinstall/scripts/adduser
@@ -0,0 +1,34 @@
+#!/bin/sh
+#-
+# Copyright (c) 2011 Nathan Whitehorn
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+clear
+echo "FreeBSD Installer"
+echo "========================"
+echo "Add Users"
+echo
+chroot $BSDINSTALL_CHROOT adduser 2>&1
diff --git a/usr.sbin/bsdinstall/scripts/auto b/usr.sbin/bsdinstall/scripts/auto
new file mode 100755
index 0000000..c681a12
--- /dev/null
+++ b/usr.sbin/bsdinstall/scripts/auto
@@ -0,0 +1,470 @@
+#!/bin/sh
+#-
+# Copyright (c) 2011 Nathan Whitehorn
+# Copyright (c) 2013 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_include $BSDCFG_SHARE/dialog.subr
+
+############################################################ FUNCTIONS
+
+error() {
+ local msg
+ if [ -n "$1" ]; then
+ msg="$1\n\n"
+ fi
+ test -n "$DISTDIR_IS_UNIONFS" && umount -f $BSDINSTALL_DISTDIR
+ test -f $PATH_FSTAB && bsdinstall umount
+ dialog --backtitle "FreeBSD Installer" --title "Abort" \
+ --no-label "Exit" --yes-label "Restart" --yesno \
+ "${msg}An installation step has been aborted. Would you like to restart the installation or exit the installer?" 0 0
+ if [ $? -ne 0 ]; then
+ exit 1
+ else
+ exec $0
+ fi
+}
+
+hline_arrows_tab_enter="Press arrows, TAB or ENTER"
+msg_gpt_active_fix="Your hardware is known to have issues booting in CSM/Legacy/BIOS mode from GPT partitions that are not set active. Would you like the installer to apply this workaround for you?"
+msg_lenovo_fix="Your model of Lenovo is known to have a BIOS bug that prevents it booting from GPT partitions without UEFI. Would you like the installer to apply a workaround for you?"
+msg_no="NO"
+msg_yes="YES"
+
+# dialog_workaround
+#
+# Ask the user if they wish to apply a workaround
+#
+dialog_workaround()
+{
+ local passed_msg="$1"
+ local title="$DIALOG_TITLE"
+ local btitle="$DIALOG_BACKTITLE"
+ local prompt # Calculated below
+ local hline="$hline_arrows_tab_enter"
+
+ local height=8 width=50 prefix=" "
+ local plen=${#prefix} list= line=
+ local max_width=$(( $width - 3 - $plen ))
+
+ local yes no defaultno extra_args format
+ if [ "$USE_XDIALOG" ]; then
+ yes=ok no=cancel defaultno=default-no
+ extra_args="--wrap --left"
+ format="$passed_msg"
+ else
+ yes=yes no=no defaultno=defaultno
+ extra_args="--cr-wrap"
+ format="$passed_msg"
+ fi
+
+ # Add height for Xdialog(1)
+ [ "$USE_XDIALOG" ] && height=$(( $height + $height / 5 + 3 ))
+
+ prompt=$( printf "$format" )
+ f_dprintf "%s: Workaround prompt" "$0"
+ $DIALOG \
+ --title "$title" \
+ --backtitle "$btitle" \
+ --hline "$hline" \
+ --$yes-label "$msg_yes" \
+ --$no-label "$msg_no" \
+ $extra_args \
+ --yesno "$prompt" $height $width
+}
+
+############################################################ MAIN
+
+f_dprintf "Began Installation at %s" "$( date )"
+
+rm -rf $BSDINSTALL_TMPETC
+mkdir $BSDINSTALL_TMPETC
+
+trap true SIGINT # This section is optional
+bsdinstall keymap
+
+trap error SIGINT # Catch cntrl-C here
+bsdinstall hostname || error "Set hostname failed"
+
+export DISTRIBUTIONS="base.txz kernel.txz"
+if [ -f $BSDINSTALL_DISTDIR/MANIFEST ]; then
+ DISTMENU=`awk -F'\t' '!/^(kernel\.txz|base\.txz)/{print $1,$5,$6}' $BSDINSTALL_DISTDIR/MANIFEST`
+ DISTMENU="$(echo ${DISTMENU} | sed -E 's/\.txz//g')"
+
+ exec 3>&1
+ EXTRA_DISTS=$( eval dialog \
+ --backtitle \"FreeBSD Installer\" \
+ --title \"Distribution Select\" --nocancel --separate-output \
+ --checklist \"Choose optional system components to install:\" \
+ 0 0 0 $DISTMENU \
+ 2>&1 1>&3 )
+ for dist in $EXTRA_DISTS; do
+ export DISTRIBUTIONS="$DISTRIBUTIONS $dist.txz"
+ done
+fi
+
+LOCAL_DISTRIBUTIONS="MANIFEST"
+FETCH_DISTRIBUTIONS=""
+for dist in $DISTRIBUTIONS; do
+ if [ ! -f $BSDINSTALL_DISTDIR/$dist ]; then
+ FETCH_DISTRIBUTIONS="$FETCH_DISTRIBUTIONS $dist"
+ else
+ LOCAL_DISTRIBUTIONS="$LOCAL_DISTRIBUTIONS $dist"
+ fi
+done
+LOCAL_DISTRIBUTIONS=`echo $LOCAL_DISTRIBUTIONS` # Trim white space
+FETCH_DISTRIBUTIONS=`echo $FETCH_DISTRIBUTIONS` # Trim white space
+
+if [ -n "$FETCH_DISTRIBUTIONS" -a -n "$BSDINSTALL_CONFIGCURRENT" ]; then
+ dialog --backtitle "FreeBSD Installer" --title "Network Installation" --msgbox "Some installation files were not found on the boot volume. The next few screens will allow you to configure networking so that they can be downloaded from the Internet." 0 0
+ bsdinstall netconfig || error
+ NETCONFIG_DONE=yes
+fi
+
+if [ -n "$FETCH_DISTRIBUTIONS" ]; then
+ exec 3>&1
+ BSDINSTALL_DISTSITE=$(`dirname $0`/mirrorselect 2>&1 1>&3)
+ MIRROR_BUTTON=$?
+ exec 3>&-
+ test $MIRROR_BUTTON -eq 0 || error "No mirror selected"
+ export BSDINSTALL_DISTSITE
+fi
+
+rm -f $PATH_FSTAB
+touch $PATH_FSTAB
+
+#
+# Try to detect known broken platforms and apply their workarounds
+#
+
+if f_interactive; then
+ sys_maker=$( kenv -q smbios.system.maker )
+ f_dprintf "smbios.system.maker=[%s]" "$sys_maker"
+ sys_model=$( kenv -q smbios.system.product )
+ f_dprintf "smbios.system.product=[%s]" "$sys_model"
+ sys_version=$( kenv -q smbios.system.version )
+ f_dprintf "smbios.system.version=[%s]" "$sys_version"
+ sys_mb_maker=$( kenv -q smbios.planar.maker )
+ f_dprintf "smbios.planar.maker=[%s]" "$sys_mb_maker"
+ sys_mb_product=$( kenv -q smbios.planar.product )
+ f_dprintf "smbios.planar.product=[%s]" "$sys_mb_product"
+
+ #
+ # Laptop Models
+ #
+ case "$sys_maker" in
+ "LENOVO")
+ case "$sys_version" in
+ "ThinkPad X220"|"ThinkPad T420"|"ThinkPad T520")
+ dialog_workaround "$msg_lenovo_fix"
+ retval=$?
+ f_dprintf "lenovofix_prompt=[%s]" "$retval"
+ if [ $retval -eq $DIALOG_OK ]; then
+ export ZFSBOOT_PARTITION_SCHEME="GPT + Lenovo Fix"
+ export WORKAROUND_LENOVO=1
+ fi
+ ;;
+ esac
+ ;;
+ "Dell Inc.")
+ case "$sys_model" in
+ "Latitude E7440"|"Latitude E7240")
+ dialog_workaround "$msg_gpt_active_fix"
+ retval=$?
+ f_dprintf "gpt_active_fix_prompt=[%s]" "$retval"
+ if [ $retval -eq $DIALOG_OK ]; then
+ export ZFSBOOT_PARTITION_SCHEME="GPT + Active"
+ export WORKAROUND_GPTACTIVE=1
+ fi
+ ;;
+ esac
+ ;;
+ "Hewlett-Packard")
+ case "$sys_model" in
+ "HP ProBook 4330s")
+ dialog_workaround "$msg_gpt_active_fix"
+ retval=$?
+ f_dprintf "gpt_active_fix_prompt=[%s]" "$retval"
+ if [ $retval -eq $DIALOG_OK ]; then
+ export ZFSBOOT_PARTITION_SCHEME="GPT + Active"
+ export WORKAROUND_GPTACTIVE=1
+ fi
+ ;;
+ esac
+ ;;
+ esac
+ #
+ # Motherboard Models
+ #
+ case "$sys_mb_maker" in
+ "Intel Corporation")
+ case "$sys_mb_product" in
+ "DP965LT"|"D510MO")
+ dialog_workaround "$msg_gpt_active_fix"
+ retval=$?
+ f_dprintf "gpt_active_fix_prompt=[%s]" "$retval"
+ if [ $retval -eq $DIALOG_OK ]; then
+ export ZFSBOOT_PARTITION_SCHEME="GPT + Active"
+ export WORKAROUND_GPTACTIVE=1
+ fi
+ ;;
+ esac
+ ;;
+ "Acer")
+ case "$sys_mb_product" in
+ "Veriton M6630G")
+ dialog_workaround "$msg_gpt_active_fix"
+ retval=$?
+ f_dprintf "gpt_active_fix_prompt=[%s]" "$retval"
+ if [ $retval -eq $DIALOG_OK ]; then
+ export ZFSBOOT_PARTITION_SCHEME="GPT + Active"
+ export WORKAROUND_GPTACTIVE=1
+ fi
+ ;;
+ esac
+ ;;
+ esac
+fi
+
+PMODES="\
+\"Auto (UFS)\" \"Guided Disk Setup\" \
+Manual \"Manual Disk Setup (experts)\" \
+Shell \"Open a shell and partition by hand\""
+
+CURARCH=$( uname -m )
+case $CURARCH in
+ amd64|i386) # Booting ZFS Supported
+ PMODES="$PMODES \"Auto (ZFS)\" \"Guided Root-on-ZFS\""
+ ;;
+ *) # Booting ZFS Unspported
+ ;;
+esac
+
+exec 3>&1
+PARTMODE=`echo $PMODES | xargs dialog --backtitle "FreeBSD Installer" \
+ --title "Partitioning" \
+ --menu "How would you like to partition your disk?" \
+ 0 0 0 2>&1 1>&3` || exit 1
+exec 3>&-
+
+case "$PARTMODE" in
+"Auto (UFS)") # Guided
+ bsdinstall autopart || error "Partitioning error"
+ bsdinstall mount || error "Failed to mount filesystem"
+ ;;
+"Shell") # Shell
+ clear
+ echo "Use this shell to set up partitions for the new system. When finished, mount the system at $BSDINSTALL_CHROOT and place an fstab file for the new system at $PATH_FSTAB. Then type 'exit'. You can also enter the partition editor at any time by entering 'bsdinstall partedit'."
+ sh 2>&1
+ ;;
+"Manual") # Manual
+ if f_isset debugFile; then
+ # Give partedit the path to our logfile so it can append
+ BSDINSTALL_LOG="${debugFile#+}" bsdinstall partedit || error "Partitioning error"
+ else
+ bsdinstall partedit || error "Partitioning error"
+ fi
+ bsdinstall mount || error "Failed to mount filesystem"
+ ;;
+"Auto (ZFS)") # ZFS
+ bsdinstall zfsboot || error "ZFS setup failed"
+ bsdinstall mount || error "Failed to mount filesystem"
+ ;;
+*)
+ error "Unknown partitioning mode"
+ ;;
+esac
+
+if [ ! -z "$FETCH_DISTRIBUTIONS" ]; then
+ ALL_DISTRIBUTIONS="$DISTRIBUTIONS"
+ WANT_DEBUG=
+
+ # Download to a directory in the new system as scratch space
+ BSDINSTALL_FETCHDEST="$BSDINSTALL_CHROOT/usr/freebsd-dist"
+ mkdir -p "$BSDINSTALL_FETCHDEST" || error "Could not create directory $BSDINSTALL_FETCHDEST"
+
+ export DISTRIBUTIONS="$FETCH_DISTRIBUTIONS"
+ # Try to use any existing distfiles
+ if [ -d $BSDINSTALL_DISTDIR ]; then
+ DISTDIR_IS_UNIONFS=1
+ mount_nullfs -o union "$BSDINSTALL_FETCHDEST" "$BSDINSTALL_DISTDIR"
+ else
+ export DISTRIBUTIONS="$FETCH_DISTRIBUTIONS"
+ export BSDINSTALL_DISTDIR="$BSDINSTALL_FETCHDEST"
+ fi
+
+ export FTP_PASSIVE_MODE=YES
+ # Iterate through the distribution list and set a flag if debugging
+ # distributions have been selected.
+ for _DISTRIBUTION in $DISTRIBUTIONS; do
+ case $_DISTRIBUTION in
+ *-dbg.*)
+ [ -e $BSDINSTALL_DISTDIR/$_DISTRIBUTION ] \
+ && continue
+ WANT_DEBUG=1
+ DEBUG_LIST="\n$DEBUG_LIST\n$_DISTRIBUTION"
+ ;;
+ *)
+ ;;
+ esac
+ done
+
+ # Fetch the distributions.
+ bsdinstall distfetch
+ rc=$?
+
+ if [ $rc -ne 0 ]; then
+ # If unable to fetch the remote distributions, recommend
+ # deselecting the debugging distributions, and retrying the
+ # installation, since failure to fetch *-dbg.txz should not
+ # be considered a fatal installation error.
+ msg="Failed to fetch remote distribution"
+ if [ ! -z "$WANT_DEBUG" ]; then
+ # Trim leading and trailing newlines.
+ DEBUG_LIST="${DEBUG_LIST%%\n}"
+ DEBUG_LIST="${DEBUG_LIST##\n}"
+ msg="$msg\n\nPlease deselect the following distributions"
+ msg="$msg and retry the installation:"
+ msg="$msg\n$DEBUG_LIST"
+ fi
+ error "$msg"
+ fi
+ export DISTRIBUTIONS="$ALL_DISTRIBUTIONS"
+fi
+
+if [ ! -z "$LOCAL_DISTRIBUTIONS" ]; then
+ # Download to a directory in the new system as scratch space
+ BSDINSTALL_FETCHDEST="$BSDINSTALL_CHROOT/usr/freebsd-dist"
+ mkdir -p "$BSDINSTALL_FETCHDEST" || error "Could not create directory $BSDINSTALL_FETCHDEST"
+ # Try to use any existing distfiles
+ if [ -d $BSDINSTALL_DISTDIR ]; then
+ DISTDIR_IS_UNIONFS=1
+ mount_nullfs -o union "$BSDINSTALL_FETCHDEST" "$BSDINSTALL_DISTDIR"
+ export BSDINSTALL_DISTDIR="$BSDINSTALL_FETCHDEST"
+ fi
+ env DISTRIBUTIONS="$LOCAL_DISTRIBUTIONS" \
+ BSDINSTALL_DISTSITE="file:///usr/freebsd-dist" \
+ bsdinstall distfetch || \
+ error "Failed to fetch distribution from local media"
+fi
+
+bsdinstall checksum || error "Distribution checksum failed"
+bsdinstall distextract || error "Distribution extract failed"
+bsdinstall rootpass || error "Could not set root password"
+
+trap true SIGINT # This section is optional
+if [ "$NETCONFIG_DONE" != yes ]; then
+ bsdinstall netconfig # Don't check for errors -- the user may cancel
+fi
+bsdinstall time
+bsdinstall services
+
+dialog --backtitle "FreeBSD Installer" --title "Add User Accounts" --yesno \
+ "Would you like to add users to the installed system now?" 0 0 && \
+ bsdinstall adduser
+
+finalconfig() {
+ exec 3>&1
+ REVISIT=$(dialog --backtitle "FreeBSD Installer" \
+ --title "Final Configuration" --no-cancel --menu \
+ "Setup of your FreeBSD system is nearly complete. You can now modify your configuration choices. After this screen, you will have an opportunity to make more complex changes using a shell." 0 0 0 \
+ "Exit" "Apply configuration and exit installer" \
+ "Add User" "Add a user to the system" \
+ "Root Password" "Change root password" \
+ "Hostname" "Set system hostname" \
+ "Network" "Networking configuration" \
+ "Services" "Set daemons to run on startup" \
+ "Time Zone" "Set system timezone" \
+ "Handbook" "Install FreeBSD Handbook (requires network)" 2>&1 1>&3)
+ exec 3>&-
+
+ case "$REVISIT" in
+ "Add User")
+ bsdinstall adduser
+ finalconfig
+ ;;
+ "Root Password")
+ bsdinstall rootpass
+ finalconfig
+ ;;
+ "Hostname")
+ bsdinstall hostname
+ finalconfig
+ ;;
+ "Network")
+ bsdinstall netconfig
+ finalconfig
+ ;;
+ "Services")
+ bsdinstall services
+ finalconfig
+ ;;
+ "Time Zone")
+ bsdinstall time
+ finalconfig
+ ;;
+ "Handbook")
+ bsdinstall docsinstall
+ finalconfig
+ ;;
+ esac
+}
+
+# Allow user to change his mind
+finalconfig
+
+trap error SIGINT # SIGINT is bad again
+bsdinstall config || error "Failed to save config"
+
+if [ ! -z "$BSDINSTALL_FETCHDEST" ]; then
+ [ "$BSDINSTALL_FETCHDEST" != "$BSDINSTALL_DISTDIR" ] && \
+ umount "$BSDINSTALL_DISTDIR"
+ rm -rf "$BSDINSTALL_FETCHDEST"
+fi
+
+dialog --backtitle "FreeBSD Installer" --title "Manual Configuration" \
+ --default-button no --yesno \
+ "The installation is now finished. Before exiting the installer, would you like to open a shell in the new system to make any final manual modifications?" 0 0
+if [ $? -eq 0 ]; then
+ clear
+ mount -t devfs devfs "$BSDINSTALL_CHROOT/dev"
+ echo This shell is operating in a chroot in the new system. \
+ When finished making configuration changes, type \"exit\".
+ chroot "$BSDINSTALL_CHROOT" /bin/sh 2>&1
+fi
+
+bsdinstall entropy
+bsdinstall umount
+
+f_dprintf "Installation Completed at %s" "$( date )"
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdinstall/scripts/checksum b/usr.sbin/bsdinstall/scripts/checksum
new file mode 100755
index 0000000..1c537f3
--- /dev/null
+++ b/usr.sbin/bsdinstall/scripts/checksum
@@ -0,0 +1,71 @@
+#!/bin/sh
+#-
+# Copyright (c) 2011 Nathan Whitehorn
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+test -f $BSDINSTALL_DISTDIR/MANIFEST || exit 0
+
+percentage=0
+for dist in $DISTRIBUTIONS; do
+ distname=$(basename $dist .txz)
+ eval "status_$distname=7"
+
+ items=""
+ for i in $DISTRIBUTIONS; do
+ items="$items $i `eval echo \\\${status_$(basename $i .txz):-Pending}`"
+ done
+ dialog --backtitle "FreeBSD Installer" --title "Checksum Verification" \
+ --mixedgauge "Verifying checksums of selected distributions." \
+ 0 0 $percentage $items
+
+ CK=`sha256 -q $BSDINSTALL_DISTDIR/$dist`
+ awk -v checksum=$CK -v dist=$dist -v found=0 '{
+ if (dist == $1) {
+ found = 1
+ if (checksum == $2)
+ exit(0)
+ else
+ exit(2)
+ }
+ } END {if (!found) exit(1);}' $BSDINSTALL_DISTDIR/MANIFEST
+
+ CK_VALID=$?
+ if [ $CK_VALID -le 1 ]; then
+ if [ $CK_VALID -eq 0 ]; then
+ eval "status_$distname=2"
+ else
+ eval "status_$distname=6"
+ fi
+ percentage=$(echo $percentage + 100/`echo $DISTRIBUTIONS | wc -w` | bc)
+ else
+ eval "status_$distname=1"
+ dialog --backtitle "FreeBSD Installer" --title "Error" \
+ --msgbox "The checksum for $dist does not match. It may have become corrupted, and should be redownloaded." 0 0
+ exit 1
+ fi
+done
+
+exit 0
diff --git a/usr.sbin/bsdinstall/scripts/config b/usr.sbin/bsdinstall/scripts/config
new file mode 100755
index 0000000..ebb1dff
--- /dev/null
+++ b/usr.sbin/bsdinstall/scripts/config
@@ -0,0 +1,52 @@
+#!/bin/sh
+#-
+# Copyright (c) 2011 Nathan Whitehorn
+# Copyright (c) 2013-2015 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ MAIN
+
+cat $BSDINSTALL_TMPETC/rc.conf.* >> $BSDINSTALL_TMPETC/rc.conf
+rm $BSDINSTALL_TMPETC/rc.conf.*
+
+cp $BSDINSTALL_TMPETC/* $BSDINSTALL_CHROOT/etc
+
+cat $BSDINSTALL_TMPBOOT/loader.conf.* >> $BSDINSTALL_TMPBOOT/loader.conf
+rm $BSDINSTALL_TMPBOOT/loader.conf.*
+df -t zfs $BSDINSTALL_CHROOT > /dev/null && echo "zfs_load=\"YES\"" >> $BSDINSTALL_TMPBOOT/loader.conf
+
+cp $BSDINSTALL_TMPBOOT/* $BSDINSTALL_CHROOT/boot
+
+[ "${debugFile#+}" ] && cp "${debugFile#+}" $BSDINSTALL_CHROOT/var/log/
+
+# Set up other things from installed config
+chroot $BSDINSTALL_CHROOT /usr/bin/newaliases > /dev/null 2>&1
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdinstall/scripts/docsinstall b/usr.sbin/bsdinstall/scripts/docsinstall
new file mode 100755
index 0000000..a600054
--- /dev/null
+++ b/usr.sbin/bsdinstall/scripts/docsinstall
@@ -0,0 +1,166 @@
+#!/bin/sh
+#-
+# Copyright (c) 2011 Marc Fonvieille
+# Copyright (c) 2013-2015 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/mustberoot.subr
+f_include $BSDCFG_SHARE/packages/packages.subr
+
+############################################################ CONFIGURATION
+
+#
+# List of languages to display (descriptions pulled from $msg_{lang}doc_desc)
+#
+: ${DOCSINSTALL_LANGS:=\
+ bn da de el en es fr hu it ja mn nl pl pt ru sr tr zh_cn zh_tw \
+}
+
+############################################################ GLOBALS
+
+#
+# Strings that should be moved to an i18n file and loaded with f_include_lang()
+#
+hline_arrows_space_tab_enter="Use arrows, SPACE, TAB or ENTER"
+msg_bndoc_desc="Bengali Documentation"
+msg_cancel="Cancel"
+msg_dadoc_desc="Danish Documentation"
+msg_dedoc_desc="German Documentation"
+msg_docsinstall_menu_text="This menu allows you to install the whole documentation set from\nthe FreeBSD Documentation Project: Handbook, FAQ, and articles.\n\nPlease select the language versions you wish to install. At\nminimum, you should install the English version, the original\nversion of the documentation."
+msg_eldoc_desc="Greek Documentation"
+msg_endoc_desc="English Documentation (recommended)"
+msg_esdoc_desc="Spanish Documentation"
+msg_frdoc_desc="French Documentation"
+msg_freebsd_documentation_installation="FreeBSD Documentation Installation"
+msg_freebsd_installer="FreeBSD Installer"
+msg_hudoc_desc="Hungarian Documentation"
+msg_itdoc_desc="Italian Documentation"
+msg_jadoc_desc="Japanese Documentation"
+msg_mndoc_desc="Mongolian Documentation"
+msg_nldoc_desc="Dutch Documentation"
+msg_ok="OK"
+msg_pldoc_desc="Polish Documentation"
+msg_ptdoc_desc="Portuguese Documentation"
+msg_rudoc_desc="Russian Documentation"
+msg_srdoc_desc="Serbian Documentation"
+msg_trdoc_desc="Turkish Documentation"
+msg_zh_cndoc_desc="Simplified Chinese Documentation"
+msg_zh_twdoc_desc="Traditional Chinese Documentation"
+
+############################################################ FUNCTIONS
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local title="$DIALOG_TITLE"
+ local btitle="$DIALOG_BACKTITLE"
+ local prompt="$msg_docsinstall_menu_text"
+ local check_list= # Calculated below
+ local hline="$hline_arrows_space_tab_enter"
+
+ local lang desc upper status
+ for lang in $DOCSINSTALL_LANGS; do
+ # Fetch the i18n description to display
+ f_getvar msg_${lang}doc_desc desc
+ f_shell_escape "$desc" desc
+
+ # Get default status for each language
+ upper=$( echo "$lang" | awk '{print toupper($0)}' )
+ case "$lang" in
+ en) f_getvar DIST_DOC_$upper:-on status ;;
+ *) f_getvar DIST_DOC_$upper:-off status
+ esac
+
+ check_list="$check_list
+ '$lang' '$desc' '$status'
+ " # END-QUOTE
+ done
+
+ local height width rows
+ eval f_dialog_checklist_size height width rows \
+ \"\$title\" \
+ \"\$btitle\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $check_list
+ local selected
+ selected=$( eval $DIALOG \
+ --title \"\$title\" \
+ --backtitle \"\$btitle\" \
+ --separate-output \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --checklist \"\$prompt\" \
+ $height $width $rows \
+ $check_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_menutag_store -s "$selected"
+ return $retval
+}
+
+############################################################ MAIN
+
+#
+# Initialize
+#
+f_dialog_title "$msg_freebsd_documentation_installation"
+f_dialog_backtitle "$msg_freebsd_installer"
+f_mustberoot_init
+
+#
+# Launch application main menu
+#
+dialog_menu_main || f_die
+f_dialog_menutag_fetch selected
+
+# Let pkg_add be able to use name servers
+f_quietly cp -f $BSDINSTALL_TMPETC/resolv.conf $BSDINSTALL_CHROOT/etc/
+
+#
+# Install each of the selected packages
+#
+docsets=""
+for lang in $selected; do
+ docsets="$docsets $lang-freebsd-doc"
+done
+
+ASSUME_ALWAYS_YES=YES chroot $BSDINSTALL_CHROOT pkg install $docsets
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdinstall/scripts/entropy b/usr.sbin/bsdinstall/scripts/entropy
new file mode 100755
index 0000000..d3938f1
--- /dev/null
+++ b/usr.sbin/bsdinstall/scripts/entropy
@@ -0,0 +1,34 @@
+#!/bin/sh
+#-
+# Copyright (c) 2013 Dag-Erling Smørgrav
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+umask 077
+for i in /entropy /boot/entropy; do
+ i="$BSDINSTALL_CHROOT/$i"
+ dd if=/dev/random of="$i" bs=4096 count=1
+ chown 0:0 "$i"
+done
diff --git a/usr.sbin/bsdinstall/scripts/hostname b/usr.sbin/bsdinstall/scripts/hostname
new file mode 100755
index 0000000..511db67
--- /dev/null
+++ b/usr.sbin/bsdinstall/scripts/hostname
@@ -0,0 +1,52 @@
+#!/bin/sh
+#-
+# Copyright (c) 2011 Nathan Whitehorn
+# Copyright (c) 2015 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+: ${DIALOG_OK=0}
+: ${DIALOG_CANCEL=1}
+: ${DIALOG_HELP=2}
+: ${DIALOG_EXTRA=3}
+: ${DIALOG_ITEM_HELP=4}
+: ${DIALOG_ESC=255}
+
+exec 3>&1
+HOSTNAME=`dialog --backtitle 'FreeBSD Installer' --title 'Set Hostname' --nocancel --inputbox \
+ 'Please choose a hostname for this machine.
+
+If you are running on a managed network, please ask your network administrator for an appropriate name.' \
+ 0 0 $(hostname) 2>&1 1>&3`
+if [ $? -eq $DIALOG_CANCEL ]; then exit 1; fi
+exec 3>&-
+
+echo "hostname=\"$HOSTNAME\"" > $BSDINSTALL_TMPETC/rc.conf.hostname
+retval=$?
+if [ "$BSDINSTALL_CONFIGCURRENT" ]; then
+ hostname -s "$HOSTNAME"
+ retval=$?
+fi
+exit $retval
diff --git a/usr.sbin/bsdinstall/scripts/jail b/usr.sbin/bsdinstall/scripts/jail
new file mode 100755
index 0000000..ecfbb78
--- /dev/null
+++ b/usr.sbin/bsdinstall/scripts/jail
@@ -0,0 +1,132 @@
+#!/bin/sh
+#-
+# Copyright (c) 2011 Nathan Whitehorn
+# Copyright (c) 2013-2015 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+
+############################################################ MAIN
+
+f_dprintf "Began Installation at %s" "$( date )"
+export BSDINSTALL_CHROOT=$1
+
+error() {
+ local msg
+ if [ -n "$1" ]; then
+ msg="$1\n\n"
+ fi
+ dialog --backtitle "FreeBSD Installer" --title "Abort" \
+ --no-label "Exit" --yes-label "Restart" --yesno \
+ "${msg}An installation step has been aborted. Would you like to restart the installation or exit the installer?" 0 0
+ if [ $? -ne 0 ]; then
+ exit
+ else
+ exec $0 $BSDINSTALL_CHROOT
+ fi
+}
+
+
+rm -rf $BSDINSTALL_TMPETC
+mkdir $BSDINSTALL_TMPETC
+mkdir -p $1 || error "mkdir failed for $1"
+
+test ! -d $BSDINSTALL_DISTDIR && mkdir -p $BSDINSTALL_DISTDIR
+
+if [ ! -f $BSDINSTALL_DISTDIR/MANIFEST -a -z "$BSDINSTALL_DISTSITE" ]; then
+ exec 3>&1
+ BSDINSTALL_DISTSITE=$(`dirname $0`/mirrorselect 2>&1 1>&3)
+ MIRROR_BUTTON=$?
+ exec 3>&-
+ test $MIRROR_BUTTON -eq 0 || error "No mirror selected"
+ export BSDINSTALL_DISTSITE
+ fetch -o $BSDINSTALL_DISTDIR/MANIFEST $BSDINSTALL_DISTSITE/MANIFEST || error "Could not download $BSDINSTALL_DISTSITE/MANIFEST"
+fi
+
+export DISTRIBUTIONS="base.txz"
+if [ -f $BSDINSTALL_DISTDIR/MANIFEST ]; then
+ DISTMENU=`cut -f 4,5,6 $BSDINSTALL_DISTDIR/MANIFEST | grep -v -e ^kernel -e ^base`
+
+ exec 3>&1
+ EXTRA_DISTS=$(echo $DISTMENU | xargs dialog \
+ --backtitle "FreeBSD Installer" \
+ --title "Distribution Select" --nocancel --separate-output \
+ --checklist "Choose optional system components to install:" \
+ 0 0 0 \
+ 2>&1 1>&3)
+ for dist in $EXTRA_DISTS; do
+ export DISTRIBUTIONS="$DISTRIBUTIONS $dist.txz"
+ done
+fi
+
+FETCH_DISTRIBUTIONS=""
+for dist in $DISTRIBUTIONS; do
+ if [ ! -f $BSDINSTALL_DISTDIR/$dist ]; then
+ FETCH_DISTRIBUTIONS="$FETCH_DISTRIBUTIONS $dist"
+ fi
+done
+FETCH_DISTRIBUTIONS=`echo $FETCH_DISTRIBUTIONS` # Trim white space
+
+if [ -n "$FETCH_DISTRIBUTIONS" -a -z "$BSDINSTALL_DISTSITE" ]; then
+ exec 3>&1
+ BSDINSTALL_DISTSITE=`bsdinstall mirrorselect 2>&1 1>&3`
+ MIRROR_BUTTON=$?
+ exec 3>&-
+ test $MIRROR_BUTTON -eq 0 || error "No mirror selected"
+ export BSDINSTALL_DISTSITE
+fi
+
+if [ ! -z "$FETCH_DISTRIBUTIONS" ]; then
+ bsdinstall distfetch || error "Failed to fetch distribution"
+fi
+
+bsdinstall checksum || error "Distribution checksum failed"
+bsdinstall distextract || error "Distribution extract failed"
+bsdinstall rootpass || error "Could not set root password"
+
+trap true SIGINT # This section is optional
+bsdinstall services
+
+dialog --backtitle "FreeBSD Installer" --title "Add User Accounts" --yesno \
+ "Would you like to add users to the installed system now?" 0 0 && \
+ bsdinstall adduser
+
+trap error SIGINT # SIGINT is bad again
+bsdinstall config || error "Failed to save config"
+cp /etc/resolv.conf $1/etc
+cp /etc/localtime $1/etc
+
+bsdinstall entropy
+
+f_dprintf "Installation Completed at %s" "$(date)"
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdinstall/scripts/keymap b/usr.sbin/bsdinstall/scripts/keymap
new file mode 100755
index 0000000..7b42571
--- /dev/null
+++ b/usr.sbin/bsdinstall/scripts/keymap
@@ -0,0 +1,238 @@
+#!/bin/sh
+#-
+# Copyright (c) 2011 Nathan Whitehorn
+# Copyright (c) 2013-2015 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/keymap.subr
+f_include $BSDCFG_SHARE/sysrc.subr
+
+############################################################ CONFIGURATION
+
+#
+# Default file to store keymap selection in
+#
+: ${KEYMAPFILE:=$BSDINSTALL_TMPETC/rc.conf.keymap}
+
+#
+# Default path to keymap INDEX containing descriptions
+#
+: ${MAPDESCFILE:=/usr/share/syscons/keymaps/INDEX.keymaps}
+
+############################################################ GLOBALS
+
+#
+# Strings that should be moved to an i18n file and loaded with f_include_lang()
+#
+hline_arrows_tab_enter="Press arrows, TAB or ENTER"
+msg_continue_with_keymap="Continue with %s keymap"
+msg_default="default"
+msg_error="Error"
+msg_freebsd_installer="FreeBSD Installer"
+msg_keymap_menu_text="The system console driver for FreeBSD defaults to standard \"US\"\nkeyboard map. Other keymaps can be chosen below."
+msg_keymap_selection="Keymap Selection"
+msg_ok="OK"
+msg_select="Select"
+msg_test_keymap="Test %s keymap"
+msg_test_the_currently_selected_keymap="Test the currently selected keymap"
+msg_test_the_keymap_by_typing="Test the keymap by typing letters, numbers, and symbols. Characters\nshould match labels on the keyboard keys. Press Enter to stop testing."
+
+############################################################ FUNCTIONS
+
+# dialog_keymap_test $keymap
+#
+# Activate $keymap and display an input box (without cancel button) for the
+# user to test keyboard input and return. Always returns success.
+#
+dialog_keymap_test()
+{
+ local keym="$1"
+ local title= # Calculated below
+ local btitle= # Calculated below
+ local prompt="$msg_test_the_keymap_by_typing"
+ local hline=
+
+ # Attempt to activate the keymap
+ if [ "$keym" ]; then
+ local err
+ err=$( f_keymap_kbdcontrol "$keym" 2>&1 > /dev/null )
+ if [ "$err" ]; then
+ f_dialog_title "$msg_error"
+ f_dialog_msgbox "$err"
+ f_dialog_title_restore
+ return $FAILURE
+ fi
+ fi
+
+ f_dialog_title "$( printf "$msg_test_keymap" "${keym:-$msg_default}" )"
+ title="$DIALOG_TITLE"
+ btitle="$DIALOG_BACKTITLE"
+ f_dialog_title_restore
+
+ local height width
+ f_dialog_inputbox_size height width \
+ "$title" "$btitle" "$prompt" "" "$hline"
+
+ $DIALOG \
+ --title "$title" \
+ --backtitle "$btitle" \
+ --hline "$hline" \
+ --ok-label "$msg_ok" \
+ --no-cancel \
+ --inputbox "$prompt" \
+ $height $width \
+ 2>/dev/null >&$DIALOG_TERMINAL_PASSTHRU_FD
+
+ return $DIALOG_OK
+}
+
+############################################################ MAIN
+
+#
+# Initialize
+#
+f_dialog_title "$msg_keymap_selection"
+f_dialog_backtitle "$msg_freebsd_installer"
+
+#
+# Die immediately if we can't dump the current keyboard map
+#
+#error=$( kbdcontrol -d 2>&1 > /dev/null ) || f_die $FAILURE "%s" "$error"
+
+# Capture Ctrl-C for clean-up
+trap 'rm -f $KEYMAPFILE; exit $FAILURE' SIGINT
+
+# Get a value from rc.conf(5) as initial value (if not being scripted)
+f_getvar $VAR_KEYMAP keymap
+if [ ! "$keymap" ]; then
+ keymap=$( f_sysrc_get keymap )
+ case "$keymap" in [Nn][Oo]) keymap="";; esac
+fi
+
+#
+# Loop until the user has finalized their selection (by clicking the
+# [relabeled] Cancel button).
+#
+width=67 first_pass=1 back_from_testing=
+[ "$USE_XDIALOG" ] && width=70
+prompt="$msg_keymap_menu_text"
+hline="$hline_arrows_tab_enter"
+while :; do
+ #
+ # Re/Build list of keymaps
+ #
+ cont_msg=$( printf "$msg_continue_with_keymap" \
+ "${keymap:-$msg_default}" )
+ test_msg=$( printf "$msg_test_keymap" "${keymap:-$msg_default}" )
+ menu_list="
+ '>>> $cont_msg' '' '$msg_continue_with_current_keymap'
+ '->- $test_msg' '' '$msg_test_the_currently_selected_keymap'
+ " # END-QUOTE
+ if [ "$first_pass" ]; then
+ defaultitem=
+ first_pass=
+ else
+ defaultitem="->- $test_msg"
+ fi
+ for k in $KEYMAPS; do
+ keymap_$k get keym keym
+ keymap_$k get desc desc
+ radio=" "
+ if [ "$keym" = "$keymap" ]; then
+ radio="*"
+ if [ "$back_from_testing" ]; then
+ defaultitem="(*) $desc"
+ back_from_testing=
+ fi
+ fi
+ f_shell_escape "$desc" desc
+ menu_list="$menu_list
+ '($radio) $desc' '' '$keym: $desc'
+ " # END-QUOTE
+ done
+ back_from_testing=
+
+ #
+ # Display keymap configuration menu
+ #
+ eval f_dialog_menu_with_help_size height \"\" rows \
+ \"\$DIALOG_TITLE\" \
+ \"\$DIALOG_BACKTITLE\" \
+ \"\$prompt\" \
+ \"\$hline\" \
+ $menu_list
+ menu_choice=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --hline \"\$hline\" \
+ --keep-tite \
+ --item-help \
+ --ok-label \"\$msg_select\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ ) || {
+ f_quietly rm -f "$KEYMAPFILE"
+ exit $FAILURE # Exit with an error so bsdinstall restarts
+ }
+ f_dialog_data_sanitize menu_choice
+
+ case "$menu_choice" in
+ ">>> "*) # Continue with keymap
+ break ;;
+ "->-"*) # Test keymap
+ dialog_keymap_test "$keymap"
+ back_from_testing=1
+ continue ;;
+ esac
+
+ # Turn the user's choice into a number
+ n=$( eval f_dialog_menutag2index_with_help \
+ \"\$menu_choice\" $menu_list )
+
+ # Turn that number ithe name of the keymap struct
+ k=$( set -- $KEYMAPS; eval echo \"\${$(( $n - 2))}\" )
+
+ # Get actual keymap setting while we update $keymap and $KEYMAPFILE
+ keymap_$k get keym keymap
+ echo "keymap=\"$keymap\"" > "$KEYMAPFILE"
+done
+
+f_quietly f_keymap_kbdcontrol "$keymap"
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdinstall/scripts/mirrorselect b/usr.sbin/bsdinstall/scripts/mirrorselect
new file mode 100755
index 0000000..af87f44
--- /dev/null
+++ b/usr.sbin/bsdinstall/scripts/mirrorselect
@@ -0,0 +1,193 @@
+#!/bin/sh
+#-
+# Copyright (c) 2011 Nathan Whitehorn
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+: ${DIALOG_OK=0}
+: ${DIALOG_CANCEL=1}
+: ${DIALOG_HELP=2}
+: ${DIALOG_EXTRA=3}
+: ${DIALOG_ITEM_HELP=4}
+: ${DIALOG_ESC=255}
+
+exec 3>&1
+MIRROR=`dialog --backtitle "FreeBSD Installer" \
+ --title "Mirror Selection" --extra-button --extra-label "Other" \
+ --menu "Please select the site closest to you or \"other\" if you'd like to specify a different choice. Also note that not every site listed here carries more than the base distribution kits. Only Primary sites are guaranteed to carry the full range of possible distributions. Select a site that's close!" \
+ 0 0 0 \
+ ftp://ftp.freebsd.org "Main Site"\
+ ftp://ftp.freebsd.org "IPv6 Main Site"\
+ ftp://ftp3.ie.freebsd.org "IPv6 Ireland"\
+ ftp://ftp2.jp.freebsd.org "IPv6 Japan"\
+ ftp://ftp4.se.freebsd.org "IPv6 Sweden"\
+ ftp://ftp4.us.freebsd.org "IPv6 USA"\
+ ftp://ftp2.tr.freebsd.org "IPv6 Turkey"\
+ ftp://ftp1.freebsd.org "Primary"\
+ ftp://ftp2.freebsd.org "Primary #2"\
+ ftp://ftp3.freebsd.org "Primary #3"\
+ ftp://ftp4.freebsd.org "Primary #4"\
+ ftp://ftp5.freebsd.org "Primary #5"\
+ ftp://ftp6.freebsd.org "Primary #6"\
+ ftp://ftp7.freebsd.org "Primary #7"\
+ ftp://ftp10.freebsd.org "Primary #10"\
+ ftp://ftp11.freebsd.org "Primary #11"\
+ ftp://ftp12.freebsd.org "Primary #12"\
+ ftp://ftp13.freebsd.org "Primary #13"\
+ ftp://ftp14.freebsd.org "Primary #14"\
+ ftp://ftp1.am.freebsd.org "Armenia"\
+ ftp://ftp.au.freebsd.org "Australia"\
+ ftp://ftp2.au.freebsd.org "Australia #2"\
+ ftp://ftp3.au.freebsd.org "Australia #3"\
+ ftp://ftp.at.freebsd.org "Austria"\
+ ftp://ftp2.br.freebsd.org "Brazil #2"\
+ ftp://ftp3.br.freebsd.org "Brazil #3"\
+ ftp://ftp4.br.freebsd.org "Brazil #4"\
+ ftp://ftp.ca.freebsd.org "Canada"\
+ ftp://ftp.cn.freebsd.org "China"\
+ ftp://ftp.cz.freebsd.org "Czech Republic"\
+ ftp://ftp.dk.freebsd.org "Denmark"\
+ ftp://ftp.ee.freebsd.org "Estonia"\
+ ftp://ftp.fi.freebsd.org "Finland"\
+ ftp://ftp.fr.freebsd.org "France"\
+ ftp://ftp3.fr.freebsd.org "France #3"\
+ ftp://ftp4.fr.freebsd.org "IPv6 France #4"\
+ ftp://ftp5.fr.freebsd.org "France #5"\
+ ftp://ftp6.fr.freebsd.org "France #6"\
+ ftp://ftp7.fr.freebsd.org "France #7"\
+ ftp://ftp8.fr.freebsd.org "IPv6 France #8"\
+ ftp://ftp.de.freebsd.org "Germany"\
+ ftp://ftp2.de.freebsd.org "Germany #2"\
+ ftp://ftp4.de.freebsd.org "Germany #4"\
+ ftp://ftp5.de.freebsd.org "Germany #5"\
+ ftp://ftp6.de.freebsd.org "Germany #6"\
+ ftp://ftp7.de.freebsd.org "Germany #7"\
+ ftp://ftp8.de.freebsd.org "Germany #8"\
+ ftp://ftp.gr.freebsd.org "Greece"\
+ ftp://ftp2.gr.freebsd.org "Greece #2"\
+ ftp://ftp3.ie.freebsd.org "Ireland #3"\
+ ftp://ftp.il.freebsd.org "Israel"\
+ ftp://ftp.it.freebsd.org "Italy"\
+ ftp://ftp.jp.freebsd.org "Japan"\
+ ftp://ftp2.jp.freebsd.org "Japan #2"\
+ ftp://ftp3.jp.freebsd.org "Japan #3"\
+ ftp://ftp4.jp.freebsd.org "Japan #4"\
+ ftp://ftp5.jp.freebsd.org "Japan #5"\
+ ftp://ftp6.jp.freebsd.org "Japan #6"\
+ ftp://ftp7.jp.freebsd.org "Japan #7"\
+ ftp://ftp8.jp.freebsd.org "Japan #8"\
+ ftp://ftp9.jp.freebsd.org "Japan #9"\
+ ftp://ftp.kr.freebsd.org "Korea"\
+ ftp://ftp2.kr.freebsd.org "Korea #2"\
+ ftp://ftp.lv.freebsd.org "Latvia"\
+ ftp://ftp.lt.freebsd.org "Lithuania"\
+ ftp://ftp.nl.freebsd.org "Netherlands"\
+ ftp://ftp2.nl.freebsd.org "Netherlands #2"\
+ ftp://ftp.nz.freebsd.org "New Zealand"\
+ ftp://ftp.no.freebsd.org "Norway"\
+ ftp://ftp.pl.freebsd.org "Poland"\
+ ftp://ftp2.pl.freebsd.org "Poland #2"\
+ ftp://ftp.ru.freebsd.org "Russia"\
+ ftp://ftp2.ru.freebsd.org "Russia #2"\
+ ftp://ftp4.ru.freebsd.org "Russia #4"\
+ ftp://ftp5.ru.freebsd.org "Russia #5"\
+ ftp://ftp6.ru.freebsd.org "Russia #6"\
+ ftp://ftp.sk.freebsd.org "Slovak Republic"\
+ ftp://ftp2.sk.freebsd.org "Slovak Republic #2"\
+ ftp://ftp.si.freebsd.org "Slovenia"\
+ ftp://ftp.za.freebsd.org "South Africa"\
+ ftp://ftp2.za.freebsd.org "South Africa #2"\
+ ftp://ftp4.za.freebsd.org "South Africa #4"\
+ ftp://ftp.es.freebsd.org "Spain"\
+ ftp://ftp3.es.freebsd.org "Spain #3"\
+ ftp://ftp.se.freebsd.org "Sweden"\
+ ftp://ftp2.se.freebsd.org "Sweden #2"\
+ ftp://ftp3.se.freebsd.org "Sweden #3"\
+ ftp://ftp4.se.freebsd.org "Sweden #4"\
+ ftp://ftp6.se.freebsd.org "Sweden #6"\
+ ftp://ftp.ch.freebsd.org "Switzerland"\
+ ftp://ftp.tw.freebsd.org "Taiwan"\
+ ftp://ftp2.tw.freebsd.org "Taiwan #2"\
+ ftp://ftp3.tw.freebsd.org "Taiwan #3"\
+ ftp://ftp4.tw.freebsd.org "Taiwan #4"\
+ ftp://ftp6.tw.freebsd.org "Taiwan #6"\
+ ftp://ftp11.tw.freebsd.org "Taiwan #11"\
+ ftp://ftp.uk.freebsd.org "UK"\
+ ftp://ftp2.uk.freebsd.org "UK #2"\
+ ftp://ftp3.uk.freebsd.org "UK #3"\
+ ftp://ftp4.uk.freebsd.org "UK #4"\
+ ftp://ftp5.uk.freebsd.org "UK #5"\
+ ftp://ftp.ua.freebsd.org "Ukraine"\
+ ftp://ftp7.ua.freebsd.org "Ukraine #7"\
+ ftp://ftp1.us.freebsd.org "USA #1"\
+ ftp://ftp2.us.freebsd.org "USA #2"\
+ ftp://ftp3.us.freebsd.org "USA #3"\
+ ftp://ftp4.us.freebsd.org "USA #4"\
+ ftp://ftp5.us.freebsd.org "USA #5"\
+ ftp://ftp6.us.freebsd.org "USA #6"\
+ ftp://ftp8.us.freebsd.org "USA #8"\
+ ftp://ftp10.us.freebsd.org "USA #10"\
+ ftp://ftp11.us.freebsd.org "USA #11"\
+ ftp://ftp13.us.freebsd.org "USA #13"\
+ ftp://ftp14.us.freebsd.org "USA #14"\
+ ftp://ftp15.us.freebsd.org "USA #15"\
+ 2>&1 1>&3`
+MIRROR_BUTTON=$?
+exec 3>&-
+
+_UNAME_R=`uname -r`
+_UNAME_R=${_UNAME_R%-p*}
+
+case ${_UNAME_R} in
+ *-CURRENT|*-STABLE|*-PRERELEASE)
+ RELDIR="snapshots"
+ ;;
+ *)
+ RELDIR="releases"
+ ;;
+esac
+
+BSDINSTALL_DISTSITE="$MIRROR/pub/FreeBSD/${RELDIR}/`uname -m`/`uname -p`/${_UNAME_R}"
+
+case $MIRROR_BUTTON in
+$DIALOG_CANCEL)
+ exit 1
+ ;;
+$DIALOG_OK)
+ ;;
+$DIALOG_EXTRA)
+ exec 3>&1
+ BSDINSTALL_DISTSITE=`dialog --backtitle "FreeBSD Installer" \
+ --title "Mirror Selection" \
+ --inputbox "Please enter the URL to an alternate FreeBSD mirror:" \
+ 0 0 "$BSDINSTALL_DISTSITE" 2>&1 1>&3`
+ MIRROR_BUTTON=$?
+ exec 3>&-
+ test $MIRROR_BUTTON -eq 0 || exec $0 $@
+ ;;
+esac
+
+export BSDINSTALL_DISTSITE
+echo $BSDINSTALL_DISTSITE >&2
diff --git a/usr.sbin/bsdinstall/scripts/mount b/usr.sbin/bsdinstall/scripts/mount
new file mode 100755
index 0000000..fca8000
--- /dev/null
+++ b/usr.sbin/bsdinstall/scripts/mount
@@ -0,0 +1,55 @@
+#!/bin/sh
+#-
+# Copyright (c) 2011 Nathan Whitehorn
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+TMP_FSTAB=/tmp/bsdinstall-tmp-fstab
+
+cat $PATH_FSTAB | awk -v BSDINSTALL_CHROOT=$BSDINSTALL_CHROOT '{
+ if ($2 ~ "^/.*") {
+ fsname = $2;
+ if (fsname == "/")
+ fsname = ""
+ printf("%s\t%s%s\t%s\t%s\t%s\t%s\n", $1, BSDINSTALL_CHROOT,
+ fsname, $3, $4, $5, $6);
+ }
+}' > $TMP_FSTAB
+
+FILESYSTEMS=`cat $TMP_FSTAB | awk '/^[^#].*/ {if ($2 ~ "^/.*") printf("%s\n", $2);}' | sort -t /`
+
+for i in $FILESYSTEMS; do
+ mkdir -p $i 2>/dev/null
+ MNTERROR=`mount -F $TMP_FSTAB $i 2>&1`
+ if [ $? -ne 0 ]; then
+ dialog --backtitle "FreeBSD Installer" --title "Error" \
+ --msgbox "Error mounting partition $i:\n$MNTERROR" 0 0
+ exit 1
+ fi
+done
+
+# User might want a shell and require devfs, so mount it
+mkdir $BSDINSTALL_CHROOT/dev 2>/dev/null
+mount -t devfs devfs $BSDINSTALL_CHROOT/dev
diff --git a/usr.sbin/bsdinstall/scripts/netconfig b/usr.sbin/bsdinstall/scripts/netconfig
new file mode 100755
index 0000000..f9913c3
--- /dev/null
+++ b/usr.sbin/bsdinstall/scripts/netconfig
@@ -0,0 +1,217 @@
+#!/bin/sh
+#-
+# Copyright (c) 2011 Nathan Whitehorn
+# All rights reserved.
+# Copyright (c) 2011 The FreeBSD Foundation
+# All rights reserved.
+#
+# Portions of this software were developed by Bjoern Zeeb
+# 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$
+
+INTERFACES=""
+DIALOG_TAGS=""
+
+: ${DIALOG_OK=0}
+: ${DIALOG_CANCEL=1}
+: ${DIALOG_HELP=2}
+: ${DIALOG_EXTRA=3}
+: ${DIALOG_ITEM_HELP=4}
+: ${DIALOG_ESC=255}
+
+for IF in `ifconfig -l`; do
+ test "$IF" = "lo0" && continue
+ (ifconfig -g wlan | egrep -wq $IF) && continue
+ INTERFACES="$INTERFACES $IF"
+done
+
+INTERFACES="$INTERFACES $(sysctl -in net.wlan.devices)"
+is_wireless_if() {
+ for IF in $(sysctl -in net.wlan.devices); do
+ if [ $IF = $1 ]; then
+ return 0
+ fi
+ done
+ return 1
+}
+
+for IF in $INTERFACES; do
+ DESC=`sysctl -n dev.$(echo $IF | sed -E 's/([[:alpha:]]*)([[:digit:]]*)/\1.\2/g').%desc`
+ DIALOG_TAGS="$DIALOG_TAGS $IF \"$DESC\""
+done
+
+if [ -z "$INTERFACES" ]; then
+ dialog --backtitle 'FreeBSD Installer' \
+ --title 'Network Configuration Error' \
+ --msgbox 'No network interfaces present to configure.' 0 0
+ exit 1
+fi
+
+exec 3>&1
+INTERFACE=`echo $DIALOG_TAGS | xargs dialog --backtitle 'FreeBSD Installer' --title 'Network Configuration' --menu 'Please select a network interface to configure:' 0 0 0 2>&1 1>&3`
+if [ $? -eq $DIALOG_CANCEL ]; then exit 1; fi
+exec 3>&-
+
+: > $BSDINSTALL_TMPETC/._rc.conf.net
+
+IFCONFIG_PREFIX=""
+if is_wireless_if $INTERFACE; then
+ NEXT_WLAN_IFACE=wlan0 # XXX
+ echo wlans_$INTERFACE=\"$NEXT_WLAN_IFACE\" >> $BSDINSTALL_TMPETC/._rc.conf.net
+ IFCONFIG_PREFIX="WPA "
+ if [ ! -z $BSDINSTALL_CONFIGCURRENT ]; then
+ ifconfig $NEXT_WLAN_IFACE create wlandev $INTERFACE
+ ifconfig $NEXT_WLAN_IFACE up
+ fi
+ bsdinstall wlanconfig $NEXT_WLAN_IFACE || exec $0
+ INTERFACE="$NEXT_WLAN_IFACE"
+fi
+
+IPV6_AVAIL=0
+IPV4_AVAIL=0
+sysctl -N kern.features.inet6 > /dev/null 2>&1
+case $? in
+0) IPV6_AVAIL=1 ;;
+esac
+sysctl -N kern.features.inet > /dev/null 2>&1
+case $? in
+0) IPV4_AVAIL=1 ;;
+esac
+
+if [ ${IPV4_AVAIL} -eq 1 ]; then
+ dialog --backtitle 'FreeBSD Installer' --title 'Network Configuration' \
+ --yesno 'Would you like to configure IPv4 for this interface?' 0 0
+ if [ $? -eq $DIALOG_OK ]; then
+ bsdinstall netconfig_ipv4 ${INTERFACE} "${IFCONFIG_PREFIX}" || \
+ exec $0
+ else
+ IPV4_AVAIL=0
+ fi
+fi
+# In case wlanconfig left an option and we do not support IPv4 we need to write
+# it out on its own. We cannot write it out with IPv6 as that suffix.
+if [ ${IPV4_AVAIL} -eq 0 -a -n ${IFCONFIG_PREFIX} ]; then
+ echo ifconfig_${INTERFACE}=\"${IFCONFIG_PREFIX}\" >> $BSDINSTALL_TMPETC/._rc.conf.net
+fi
+if [ ${IPV6_AVAIL} -eq 1 ]; then
+ dialog --backtitle 'FreeBSD Installer' --title 'Network Configuration' \
+ --yesno 'Would you like to configure IPv6 for this interface?' 0 0
+ if [ $? -eq $DIALOG_OK ]; then
+ bsdinstall netconfig_ipv6 ${INTERFACE} || exec $0
+ else
+ IPV6_AVAIL=0
+ fi
+fi
+
+SEARCH=""
+IP4_1=""
+IP4_2=""
+IP6_1=""
+IP6_2=""
+while read key value; do
+ case "${key}" in
+ search) SEARCH="${value}" ;;
+ nameserver) # is more trick as we have to distinguish v4 and v6
+ case "${value}" in
+ [0-9]*\.[0-9]*\.[0-9]*\.[0-9]*)
+ if [ -z "${IP4_1}" ] ; then
+ IP4_1="${value}"
+ elif [ -z "${IP4_2}" ]; then
+ IP4_2="${value}"
+ fi
+ ;;
+ [0-9A-Fa-f:]*:*)
+ if [ -z "${IP6_1}" ] ; then
+ IP6_1="${value}"
+ elif [ -z "${IP6_2}" ]; then
+ IP6_2="${value}"
+ fi
+ ;;
+ esac
+ ;;
+ # ignore others
+ esac
+done < ${BSDINSTALL_TMPETC}/resolv.conf
+
+RESOLV=""
+if [ ${IPV6_AVAIL} -eq 1 -a ${IPV4_AVAIL} -eq 1 ]; then
+ RESOLV="
+ 'Search' 1 0 \"${SEARCH}\" 1 16 50 0 0
+ 'Nameserver' 2 0 \"Nameserver\" 2 16 50 0 2
+ 'IPv6 DNS #1' 2 0 \"${IP6_1}\" 2 16 50 0 0
+ 'IPv6 DNS #2' 3 0 \"${IP6_2}\" 3 16 50 0 0
+ 'IPv4 DNS #1' 4 0 \"${IP4_1}\" 4 16 16 0 0
+ 'IPv4 DNS #2' 5 0 \"${IP4_2}\" 5 16 16 0 0"
+elif [ ${IPV6_AVAIL} -eq 1 ]; then
+ RESOLV="
+ 'Search' 1 0 \"${SEARCH}\" 1 16 50 0 0
+ 'Nameserver' 2 0 \"Nameserver\" 2 16 50 0 2
+ 'IPv6 DNS #1' 2 0 \"${IP6_1}\" 2 16 50 0 0
+ 'IPv6 DNS #2' 3 0 \"${IP6_2}\" 3 16 50 0 0"
+elif [ ${IPV4_AVAIL} -eq 1 ]; then
+ RESOLV="
+ 'Search' 1 0 \"${SEARCH}\" 1 16 50 0 0
+ 'Nameserver' 2 0 \"Nameserver\" 2 16 50 0 2
+ 'IPv4 DNS #1' 2 0 \"${IP4_1}\" 2 16 16 0 0
+ 'IPv4 DNS #2' 3 0 \"${IP4_2}\" 3 16 16 0 0"
+else
+ exit 0
+fi
+
+exec 3>&1
+RESOLV=$(echo "${RESOLV}" | xargs dialog --backtitle 'FreeBSD Installer' \
+ --title 'Network Configuration' \
+ --mixedform 'Resolver Configuration' 0 0 0 \
+2>&1 1>&3)
+if [ $? -eq $DIALOG_CANCEL ]; then exec $0; fi
+exec 3>&-
+
+echo ${RESOLV} | tr ' ' '\n' | \
+awk '
+BEGIN {
+ search=-1;
+}
+{
+ if (/^[[:space:]]+$/) {
+ next;
+ }
+ if (/^Nameserver$/) {
+ printf "\n";
+ search=0;
+ next;
+ }
+ if (search == -1) {
+ printf "search ";
+ search=1;
+ }
+ if (search > 0) {
+ printf "%s%s", (search > 1) ? " " : "", $1;
+ search++;
+ next;
+ }
+ printf "nameserver %s\n", $1;
+}' > ${BSDINSTALL_TMPETC}/resolv.conf
+
+mv $BSDINSTALL_TMPETC/._rc.conf.net $BSDINSTALL_TMPETC/rc.conf.net
diff --git a/usr.sbin/bsdinstall/scripts/netconfig_ipv4 b/usr.sbin/bsdinstall/scripts/netconfig_ipv4
new file mode 100755
index 0000000..88a0fa2
--- /dev/null
+++ b/usr.sbin/bsdinstall/scripts/netconfig_ipv4
@@ -0,0 +1,99 @@
+#!/bin/sh
+#-
+# Copyright (c) 2011 Nathan Whitehorn
+# Copyright (c) 2013-2015 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+
+############################################################ MAIN
+
+INTERFACE=$1
+IFCONFIG_PREFIX="$2"
+test -z "$IFCONFIG_PREFIX" || IFCONFIG_PREFIX="$2 "
+case "${INTERFACE}" in
+"") dialog --backtitle 'FreeBSD Installer' --title 'Network Configuration' \
+ --msgbox 'No interface specified for IPv4 configuration.' 0 0
+ exit 1
+ ;;
+esac
+
+dialog --backtitle 'FreeBSD Installer' --title 'Network Configuration' --yesno 'Would you like to use DHCP to configure this interface?' 0 0
+if [ $? -eq $DIALOG_OK ]; then
+ echo ifconfig_$INTERFACE=\"${IFCONFIG_PREFIX}DHCP\" >> $BSDINSTALL_TMPETC/._rc.conf.net
+
+ if [ ! -z $BSDINSTALL_CONFIGCURRENT ]; then
+ dialog --backtitle 'FreeBSD Installer' --infobox "Acquiring DHCP lease..." 0 0
+ err=$( dhclient $INTERFACE 2>&1 )
+ if [ $? -ne 0 ]; then
+ f_dprintf "%s" "$err"
+ dialog --backtitle 'FreeBSD Installer' --msgbox "DHCP lease acquisition failed." 0 0
+ exec $0 ${INTERFACE} "${IFCONFIG_PREFIX}"
+ fi
+ fi
+ exit 0
+fi
+
+IP_ADDRESS=`ifconfig $INTERFACE inet | awk '/inet/ {printf("%s\n", $2); }'`
+NETMASK=`ifconfig $INTERFACE inet | awk '/inet/ {printf("%s\n", $4); }'`
+ROUTER=`netstat -rn -f inet | awk '/default/ {printf("%s\n", $2);}'`
+
+exec 3>&1
+IF_CONFIG=$(dialog --backtitle 'FreeBSD Installer' --title 'Network Configuration' --form 'Static Network Interface Configuration' 0 0 0 \
+ 'IP Address' 1 0 "$IP_ADDRESS" 1 20 16 0 \
+ 'Subnet Mask' 2 0 "$NETMASK" 2 20 16 0 \
+ 'Default Router' 3 0 "$ROUTER" 3 20 16 0 \
+2>&1 1>&3)
+if [ $? -eq $DIALOG_CANCEL ]; then exit 1; fi
+exec 3>&-
+
+echo $INTERFACE $IF_CONFIG |
+ awk -v prefix="$IFCONFIG_PREFIX" '{
+ printf("ifconfig_%s=\"%s\inet %s netmask %s\"\n", $1, prefix, $2, $3);
+ printf("defaultrouter=\"%s\"\n", $4);
+ }' >> $BSDINSTALL_TMPETC/._rc.conf.net
+retval=$?
+
+if [ "$BSDINSTALL_CONFIGCURRENT" ]; then
+ . $BSDINSTALL_TMPETC/._rc.conf.net
+ ifconfig $INTERFACE `eval echo \\\$ifconfig_$INTERFACE`
+ if [ "$defaultrouter" ]; then
+ route delete -inet default
+ route add -inet default $defaultrouter
+ retval=$?
+ fi
+fi
+
+exit $retval
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdinstall/scripts/netconfig_ipv6 b/usr.sbin/bsdinstall/scripts/netconfig_ipv6
new file mode 100755
index 0000000..ff4258f
--- /dev/null
+++ b/usr.sbin/bsdinstall/scripts/netconfig_ipv6
@@ -0,0 +1,160 @@
+#!/bin/sh
+#-
+# Copyright (c) 2011 Nathan Whitehorn
+# Copyright (c) 2011 The FreeBSD Foundation
+# Copyright (c) 2013-2015 Devin Teske
+# All rights reserved.
+#
+# Portions of this software were developed by Bjoern Zeeb
+# 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+
+############################################################ MAIN
+
+#
+# TODO:
+# - Add DHCPv6 support once FreeBSD ships with it.
+#
+
+INTERFACE=$1
+case "${INTERFACE}" in
+"") dialog --backtitle 'FreeBSD Installer' --title 'Network Configuration' \
+ --msgbox 'No interface specified for IPv6 configuration.' 0 0
+ exit 1
+ ;;
+esac
+
+AGAIN=""
+while : ; do
+ MSG="Would you like to try stateless address autoconfiguration (SLAAC)${AGAIN}?"
+ dialog --backtitle 'FreeBSD Installer' --title 'Network Configuration' \
+ --yesno "${MSG}" 0 0
+ if [ $? -eq $DIALOG_OK ]; then
+ if [ ! -z $BSDINSTALL_CONFIGCURRENT ]; then
+ dialog --backtitle 'FreeBSD Installer' \
+ --infobox "Sending Router Solicitation ..." 0 0
+ ifconfig ${INTERFACE} inet6 -ifdisabled accept_rtadv up
+ err=$( rtsol -F $INTERFACE 2>&1 )
+ if [ $? -ne 0 ]; then
+ f_dprintf "%s" "$err"
+ dialog --backtitle 'FreeBSD Installer' --msgbox "SLAAC failed." 0 0
+ AGAIN=" again"
+ continue
+ fi
+ fi
+ echo ifconfig_${INTERFACE}_ipv6=\"inet6 accept_rtadv\" >> $BSDINSTALL_TMPETC/._rc.conf.net
+ exit 0
+ else
+ break
+ fi
+done
+
+ROUTER6=`netstat -Wrn -f inet6 | awk '/default/ {printf("%s\n", $2);}'`
+ADDRS=`ifconfig ${INTERFACE} inet6 | \
+awk -v dfr="${ROUTER6}" '
+BEGIN {
+ n=0;
+}
+{
+ if (/inet6/) {
+ if (match($2, "^fe80:")) { next; };
+ # For the moment ignore all but the first address; it might confuse the user.
+ if (n > 0) { next; };
+ n++;
+ printf "\"IPv6 Address\" %d 0 \"%s/%s\" %d 16 50 0 0 ", n, $2, $4, n;
+ }
+}
+END {
+ if (n == 0) {
+ n++;
+ printf "\"IPv6 Address\" %d 0 \"\" %d 16 50 0 0 ", n, n;
+ }
+ n++;
+ # Nasty trick adding a (hidden, same y) read-only field as a marker
+ # to separate interface address(es) from the default router.
+ printf "\"Default Router\" %d 0 \"%s\" %d 16 50 0 2 ", n, "DefaultRouter", n;
+ printf "\"Default Router\" %d 0 \"%s\" %d 16 50 0 0 ", n, dfr, n;
+}'`
+
+exec 3>&1
+IF_CONFIG=$(echo ${ADDRS} | xargs dialog --backtitle 'FreeBSD Installer' \
+ --title 'Network Configuration' \
+ --mixedform 'Static IPv6 Network Interface Configuration' 0 0 0 \
+2>&1 1>&3)
+if [ $? -eq $DIALOG_CANCEL ]; then exit 1; fi
+exec 3>&-
+
+echo ${IF_CONFIG} | tr ' ' '\n' | \
+awk -v iface="${INTERFACE}" '
+BEGIN {
+ dfr=0;
+ count=0;
+}
+{
+ if (/^[[:space:]]+$/) {
+ next;
+ }
+ if (/DefaultRouter/) {
+ dfr=1;
+ next;
+ }
+ if (dfr == 1) {
+ printf("ipv6_defaultrouter=\"%s\"\n", $1);
+ next;
+ }
+ if (count > 0) {
+ # Ignore all but the first IP address for now.
+ next;
+ }
+ count++;
+ if (!match($1, "/")) {
+ sub("$", "/64", $1);
+ }
+ printf("ifconfig_%s_ipv6=\"inet6 %s\"\n", iface, $1);
+}' >> $BSDINSTALL_TMPETC/._rc.conf.net
+retval=$?
+
+if [ "$BSDINSTALL_CONFIGCURRENT" ]; then
+ . $BSDINSTALL_TMPETC/._rc.conf.net
+ ifconfig ${INTERFACE} `eval echo \\\$ifconfig_${INTERFACE}_ipv6`
+ if [ "$ipv6_defaultrouter" ]; then
+ route delete -inet6 default
+ route add -inet6 default ${ipv6_defaultrouter}
+ retval=$?
+ fi
+fi
+
+exit $retval
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdinstall/scripts/rootpass b/usr.sbin/bsdinstall/scripts/rootpass
new file mode 100755
index 0000000..7bfd823
--- /dev/null
+++ b/usr.sbin/bsdinstall/scripts/rootpass
@@ -0,0 +1,36 @@
+#!/bin/sh
+#-
+# Copyright (c) 2011 Nathan Whitehorn
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+clear
+echo "FreeBSD Installer"
+echo "========================"
+echo
+
+echo "Please select a password for the system management account (root):"
+
+chroot $BSDINSTALL_CHROOT passwd root 2>&1
diff --git a/usr.sbin/bsdinstall/scripts/script b/usr.sbin/bsdinstall/scripts/script
new file mode 100755
index 0000000..bb48873
--- /dev/null
+++ b/usr.sbin/bsdinstall/scripts/script
@@ -0,0 +1,137 @@
+#!/bin/sh
+#-
+# Copyright (c) 2013 Nathan Whitehorn
+# Copyright (c) 2013-2015 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/variable.subr
+
+############################################################ CONFIGURATION
+
+# VARIABLES:
+# PARTITIONS
+# DISTRIBUTIONS
+# BSDINSTALL_DISTDIR
+
+############################################################ GLOBALS
+
+#
+# Strings that should be moved to an i18n file and loaded with f_include_lang()
+#
+msg_installation_error="Installation Error!"
+
+############################################################ FUNCTIONS
+
+error()
+{
+ [ -f "$PATH_FSTAB" ] && bsdinstall umount
+
+ local file
+ f_getvar "$VAR_DEBUG_FILE#+" file
+ if [ "$file" ]; then
+ f_dialog_title "$msg_installation_error"
+ f_dialog_textbox "$file"
+ # No need to restore title, pining for the fjords
+ fi
+
+ exit 1
+}
+
+############################################################ MAIN
+
+set -e
+trap error EXIT
+
+SCRIPT="$1"
+shift
+
+f_dprintf "Began Installation at %s" "$( date )"
+rm -rf $BSDINSTALL_TMPETC
+mkdir $BSDINSTALL_TMPETC
+
+split -a 2 -p '^#!.*' "$SCRIPT" /tmp/bsdinstall-installscript-
+
+. /tmp/bsdinstall-installscript-aa
+: ${DISTRIBUTIONS="kernel.txz base.txz"}; export DISTRIBUTIONS
+export BSDINSTALL_DISTDIR
+
+# Re-initialize a new log if preamble changed BSDINSTALL_LOG
+if [ "$BSDINSTALL_LOG" != "${debugFile#+}" ]; then
+ export debugFile="$BSDINSTALL_LOG"
+ f_quietly f_debug_init
+ # NB: Being scripted, let debug go to terminal for invalid debugFile
+ f_dprintf "Began Instalation at %s" "$( date )"
+fi
+
+# Make partitions
+rm -f $PATH_FSTAB
+touch $PATH_FSTAB
+if [ "$ZFSBOOT_DISKS" ]; then
+ bsdinstall zfsboot
+else
+ bsdinstall scriptedpart "$PARTITIONS"
+fi
+bsdinstall mount
+
+# Unpack distributions
+bsdinstall checksum
+bsdinstall distextract
+
+# Finalize install
+bsdinstall config
+
+# Make sure networking is functional, if we can arrange that
+if [ ! -f $BSDINSTALL_CHROOT/etc/resolv.conf -a -f /etc/resolv.conf ]; then
+ cp /etc/resolv.conf $BSDINSTALL_CHROOT/etc/resolv.conf
+fi
+
+# Run post-install script
+if [ -f /tmp/bsdinstall-installscript-ab ]; then
+ cp /tmp/bsdinstall-installscript-ab $BSDINSTALL_CHROOT/tmp/installscript
+ chmod a+x $BSDINSTALL_CHROOT/tmp/installscript
+ mount -t devfs devfs "$BSDINSTALL_CHROOT/dev"
+ chroot $BSDINSTALL_CHROOT /tmp/installscript $@ 2>&1
+ umount "$BSDINSTALL_CHROOT/dev"
+ rm $BSDINSTALL_CHROOT/tmp/installscript
+fi
+
+bsdinstall entropy
+bsdinstall umount
+
+f_dprintf "Installation Completed at %s" "$( date )"
+
+trap - EXIT
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdinstall/scripts/services b/usr.sbin/bsdinstall/scripts/services
new file mode 100755
index 0000000..83786c2
--- /dev/null
+++ b/usr.sbin/bsdinstall/scripts/services
@@ -0,0 +1,68 @@
+#!/bin/sh
+#-
+# Copyright (c) 2011 Nathan Whitehorn
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+: ${DIALOG_OK=0}
+
+if [ -f $BSDINSTALL_TMPETC/rc.conf.services ]; then
+ eval $( sed -e s/YES/on/i -e s/NO/off/i \
+ $BSDINSTALL_TMPETC/rc.conf.services )
+else
+ # Default service states. Everything is off if not enabled.
+ sshd_enable="on"
+fi
+
+echo -n > $BSDINSTALL_TMPETC/rc.conf.services
+
+exec 3>&1
+DAEMONS=$( dialog --backtitle "FreeBSD Installer" \
+ --title "System Configuration" --nocancel --separate-output \
+ --checklist "Choose the services you would like to be started at boot:" \
+ 0 0 0 \
+ local_unbound "Local caching validating resolver" ${local_unbound:-off} \
+ sshd "Secure shell daemon" ${sshd_enable:-off} \
+ moused "PS/2 mouse pointer on console" ${moused_enable:-off} \
+ ntpd "Synchronize system and network time" ${ntpd_enable:-off} \
+ powerd "Adjust CPU frequency dynamically if supported" \
+ ${powerd_enable:-off} \
+ dumpdev "Enable kernel crash dumps to /var/crash" ${dumpdev:-on} \
+2>&1 1>&3 )
+exec 3>&-
+
+havedump=
+for daemon in $DAEMONS; do
+ [ "$daemon" = "dumpdev" ] && havedump=1 continue
+ echo ${daemon}_enable=\"YES\" >> $BSDINSTALL_TMPETC/rc.conf.services
+done
+
+echo '# Set dumpdev to "AUTO" to enable crash dumps, "NO"' \
+ 'to disable' >> $BSDINSTALL_TMPETC/rc.conf.services
+if [ "$havedump" ]; then
+ echo dumpdev=\"AUTO\" >> $BSDINSTALL_TMPETC/rc.conf.services
+else
+ echo dumpdev=\"NO\" >> $BSDINSTALL_TMPETC/rc.conf.services
+fi
diff --git a/usr.sbin/bsdinstall/scripts/time b/usr.sbin/bsdinstall/scripts/time
new file mode 100755
index 0000000..74400b6
--- /dev/null
+++ b/usr.sbin/bsdinstall/scripts/time
@@ -0,0 +1,29 @@
+#!/bin/sh
+#-
+# Copyright (c) 2011 Nathan Whitehorn
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+chroot $BSDINSTALL_CHROOT tzsetup
diff --git a/usr.sbin/bsdinstall/scripts/umount b/usr.sbin/bsdinstall/scripts/umount
new file mode 100755
index 0000000..c19f6f3
--- /dev/null
+++ b/usr.sbin/bsdinstall/scripts/umount
@@ -0,0 +1,42 @@
+#!/bin/sh
+#-
+# Copyright (c) 2011 Nathan Whitehorn
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+TMP_FSTAB=/tmp/bsdinstall-tmp-fstab
+
+cat $PATH_FSTAB | awk -v BSDINSTALL_CHROOT=$BSDINSTALL_CHROOT '{
+ if ($2 ~ "^/.*") {
+ fsname = $2;
+ if (fsname == "/")
+ fsname = ""
+ printf("%s\t%s%s\t%s\t%s\t%s\t%s\n", $1, BSDINSTALL_CHROOT,
+ fsname, $3, $4, $5, $6);
+ }
+}' > $TMP_FSTAB
+
+umount $BSDINSTALL_CHROOT/dev 2>/dev/null
+umount -F $TMP_FSTAB -a 2>/dev/null
diff --git a/usr.sbin/bsdinstall/scripts/wlanconfig b/usr.sbin/bsdinstall/scripts/wlanconfig
new file mode 100755
index 0000000..4eba2b0
--- /dev/null
+++ b/usr.sbin/bsdinstall/scripts/wlanconfig
@@ -0,0 +1,175 @@
+#!/bin/sh
+#-
+# Copyright (c) 2011 Nathan Whitehorn
+# Copyright (c) 2013-2015 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+
+############################################################ MAIN
+
+echo -n > $BSDINSTALL_TMPETC/wpa_supplicant.conf
+chmod 0600 $BSDINSTALL_TMPETC/wpa_supplicant.conf
+
+echo "ctrl_interface=/var/run/wpa_supplicant" >> $BSDINSTALL_TMPETC/wpa_supplicant.conf
+echo "eapol_version=2" >> $BSDINSTALL_TMPETC/wpa_supplicant.conf
+echo "ap_scan=1" >> $BSDINSTALL_TMPETC/wpa_supplicant.conf
+echo "fast_reauth=1" >> $BSDINSTALL_TMPETC/wpa_supplicant.conf
+echo >> $BSDINSTALL_TMPETC/wpa_supplicant.conf
+
+# Try to reach wpa_supplicant. If it isn't running and we can modify the
+# existing system, start it. Otherwise, fail.
+(wpa_cli ping >/dev/null 2>/dev/null || ([ ! -z $BSDINSTALL_CONFIGCURRENT ] && \
+ wpa_supplicant -B -i $1 -c $BSDINSTALL_TMPETC/wpa_supplicant.conf)) || \
+ (dialog --backtitle "FreeBSD Installer" --title "Error" --msgbox \
+ "Could not start wpa_supplicant!" 0 0; exit 1) || exit 1
+
+# See if we succeeded
+wpa_cli ping >/dev/null 2>/dev/null
+if [ $? -ne 0 -a -z $BSDINSTALL_CONFIGCURRENT ]; then
+ dialog --backtitle "FreeBSD Installer" --title "Error" --msgbox \
+ "Wireless cannot be configured without making changes to the local system!" \ 0 0
+ exit 1
+fi
+
+output=$( wpa_cli scan 2>&1 )
+f_dprintf "%s" "$output"
+dialog --backtitle "FreeBSD Installer" --title "Scanning" --ok-label "Skip" \
+ --pause "Waiting 5 seconds to scan for wireless networks..." \
+ 9 40 5 || exit 1
+
+SCAN_RESULTS=`wpa_cli scan_results`
+NETWORKS=`echo "$SCAN_RESULTS" | awk -F '\t' \
+ '/..:..:..:..:..:../ {if (length($5) > 0) printf("\"%s\"\t%s\n", $5, $4);}' |
+ sort | uniq`
+
+if [ -z "$NETWORKS" ]; then
+ dialog --backtitle "FreeBSD Installer" --title "Error" \
+ --yesno "No wireless networks were found. Rescan?" 0 0 && \
+ exec $0 $@
+ exit 1
+fi
+
+exec 3>&1
+NETWORK=`sh -c "dialog --extra-button --extra-label \"Rescan\" \
+ --backtitle \"FreeBSD Installer\" --title \"Network Selection\" --menu \
+ \"Select a wireless network to connect to.\" 0 0 0 \
+ $(echo $NETWORKS | tr '\n' ' ')" 2>&1 1>&3`
+case $? in
+0) # OK
+ ;;
+1) # Cancel
+ exit 1
+ ;;
+3) # Rescan
+ exec $0 $@
+ ;;
+esac
+exec 3>&-
+
+ENCRYPTION=`echo "$NETWORKS" | awk -F '\t' \
+ "/^\"$NETWORK\"\t/ {printf(\"%s\n\", \\\$2 );}"`
+
+if echo $ENCRYPTION | grep -q 'PSK'; then
+ exec 3>&1
+ PASS=`dialog --insecure --backtitle "FreeBSD Installer" \
+ --title "WPA Setup" --mixedform "" 0 0 0 \
+ "SSID" 1 0 "$NETWORK" 1 12 0 0 2 \
+ "Password" 2 0 "" 2 12 15 63 1 \
+ 2>&1 1>&3` \
+ || exec $0 $@
+ exec 3>&-
+echo "network={
+ ssid=\"$NETWORK\"
+ psk=\"$PASS\"
+ priority=5
+}" >> $BSDINSTALL_TMPETC/wpa_supplicant.conf
+elif echo $ENCRYPTION | grep -q EAP; then
+ exec 3>&1
+ USERPASS=`dialog --insecure --backtitle "FreeBSD Installer" \
+ --title "WPA-Enterprise Setup" --mixedform "" 0 0 0 \
+ "SSID" 1 0 "$NETWORK" 1 12 0 0 2 \
+ "Username" 2 0 "" 2 12 25 63 0 \
+ "Password" 3 0 "" 3 12 25 63 1 \
+ 2>&1 1>&3` \
+ || exec $0 $@
+ exec 3>&-
+echo "network={
+ ssid=\"$NETWORK\"
+ key_mgmt=WPA-EAP" >> $BSDINSTALL_TMPETC/wpa_supplicant.conf
+echo "$USERPASS" | awk '
+{
+ if (NR == 1) {
+ printf " identity=\"%s\"\n", $1;
+ } else if (NR == 2) {
+ printf " password=\"%s\"\n", $1;
+ }
+}' >> $BSDINSTALL_TMPETC/wpa_supplicant.conf
+echo " priority=5
+}" >> $BSDINSTALL_TMPETC/wpa_supplicant.conf
+elif echo $ENCRYPTION | grep -q WEP; then
+ exec 3>&1
+ WEPKEY=`dialog --insecure --backtitle "FreeBSD Installer" \
+ --title "WEP Setup" --mixedform "" 0 0 0 \
+ "SSID" 1 0 "$NETWORK" 1 12 0 0 2 \
+ "WEP Key 0" 2 0 "" 2 12 15 0 1 \
+ 2>&1 1>&3` \
+ || exec $0 $@
+echo "network={
+ ssid=\"$NETWORK\"
+ key_mgmt=NONE
+ wep_key0=\"$WEPKEY\"
+ wep_tx_keyidx=0
+ priority=5
+}" >> $BSDINSTALL_TMPETC/wpa_supplicant.conf
+else # Open
+echo "network={
+ ssid=\"$NETWORK\"
+ key_mgmt=NONE
+ priority=5
+}" >> $BSDINSTALL_TMPETC/wpa_supplicant.conf
+fi
+
+# Connect to any open networks policy
+echo "network={
+ priority=0
+ key_mgmt=NONE
+}" >> $BSDINSTALL_TMPETC/wpa_supplicant.conf
+
+# Bring up new network
+if [ "$BSDINSTALL_CONFIGCURRENT" ]; then
+ output=$( wpa_cli reconfigure 2>&1 )
+ f_dprintf "%s" "$output"
+fi
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsdinstall/scripts/zfsboot b/usr.sbin/bsdinstall/scripts/zfsboot
new file mode 100755
index 0000000..b42591b
--- /dev/null
+++ b/usr.sbin/bsdinstall/scripts/zfsboot
@@ -0,0 +1,1654 @@
+#!/bin/sh
+#-
+# Copyright (c) 2013-2015 Allan Jude
+# Copyright (c) 2013-2015 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+. $BSDCFG_SHARE/common.subr || exit 1
+f_dprintf "%s: loading includes..." "$0"
+f_include $BSDCFG_SHARE/device.subr
+f_include $BSDCFG_SHARE/dialog.subr
+f_include $BSDCFG_SHARE/password/password.subr
+f_include $BSDCFG_SHARE/variable.subr
+
+############################################################ CONFIGURATION
+
+#
+# Default name of the boot-pool
+#
+: ${ZFSBOOT_POOL_NAME:=zroot}
+
+#
+# Default options to use when creating zroot pool
+#
+: ${ZFSBOOT_POOL_CREATE_OPTIONS:=-O compress=lz4 -O atime=off}
+
+#
+# Default name for the boot environment parent dataset
+#
+: ${ZFSBOOT_BEROOT_NAME:=ROOT}
+
+#
+# Default name for the primany boot environment
+#
+: ${ZFSBOOT_BOOTFS_NAME:=default}
+
+#
+# Default Virtual Device (vdev) type to create
+#
+: ${ZFSBOOT_VDEV_TYPE:=stripe}
+
+#
+# Should we use sysctl(8) vfs.zfs.min_auto_ashift=12 to force 4K sectors?
+#
+: ${ZFSBOOT_FORCE_4K_SECTORS:=1}
+
+#
+# Should we use geli(8) to encrypt the drives?
+# NB: Automatically enables ZFSBOOT_BOOT_POOL
+#
+: ${ZFSBOOT_GELI_ENCRYPTION=}
+
+#
+# Default path to the geli(8) keyfile used in drive encryption
+#
+: ${ZFSBOOT_GELI_KEY_FILE:=/boot/encryption.key}
+
+#
+# Create a separate boot pool?
+# NB: Automatically set when using geli(8) or MBR
+#
+: ${ZFSBOOT_BOOT_POOL=}
+
+#
+# Options to use when creating separate boot pool (if any)
+#
+: ${ZFSBOOT_BOOT_POOL_CREATE_OPTIONS:=}
+
+#
+# Default name for boot pool when enabled (e.g., geli(8) or MBR)
+#
+: ${ZFSBOOT_BOOT_POOL_NAME:=bootpool}
+
+#
+# Default size for boot pool when enabled (e.g., geli(8) or MBR)
+#
+: ${ZFSBOOT_BOOT_POOL_SIZE:=2g}
+
+#
+# Default disks to use (always empty unless being scripted)
+#
+: ${ZFSBOOT_DISKS:=}
+
+#
+# Default partitioning scheme to use on disks
+#
+: ${ZFSBOOT_PARTITION_SCHEME:=GPT}
+
+#
+# How much swap to put on each block device in the boot zpool
+# NOTE: Value passed to gpart(8); which supports SI unit suffixes.
+#
+: ${ZFSBOOT_SWAP_SIZE:=2g}
+
+#
+# Should we use geli(8) to encrypt the swap?
+#
+: ${ZFSBOOT_SWAP_ENCRYPTION=}
+
+#
+# Should we use gmirror(8) to mirror the swap?
+#
+: ${ZFSBOOT_SWAP_MIRROR=}
+
+#
+# Default ZFS datasets for root zpool
+#
+# NOTE: Requires /tmp, /var/tmp, /$ZFSBOOT_BOOTFS_NAME/$ZFSBOOT_BOOTFS_NAME
+# NOTE: Anything after pound/hash character [#] is ignored as a comment.
+#
+f_isset ZFSBOOT_DATASETS || ZFSBOOT_DATASETS="
+ # DATASET OPTIONS (comma or space separated; or both)
+
+ # Boot Environment [BE] root and default boot dataset
+ /$ZFSBOOT_BEROOT_NAME mountpoint=none
+ /$ZFSBOOT_BEROOT_NAME/$ZFSBOOT_BOOTFS_NAME mountpoint=/
+
+ # Compress /tmp, allow exec but not setuid
+ /tmp mountpoint=/tmp,exec=on,setuid=off
+
+ # Don't mount /usr so that 'base' files go to the BEROOT
+ /usr mountpoint=/usr,canmount=off
+
+ # Home directories separated so they are common to all BEs
+ /usr/home # NB: /home is a symlink to /usr/home
+
+ # Ports tree
+ /usr/ports setuid=off
+
+ # Source tree (compressed)
+ /usr/src
+
+ # Create /var and friends
+ /var mountpoint=/var,canmount=off
+ /var/audit exec=off,setuid=off
+ /var/crash exec=off,setuid=off
+ /var/log exec=off,setuid=off
+ /var/mail atime=on
+ /var/tmp setuid=off
+" # END-QUOTE
+
+#
+# If interactive and the user has not explicitly chosen a vdev type or disks,
+# make the user confirm scripted/default choices when proceeding to install.
+#
+: ${ZFSBOOT_CONFIRM_LAYOUT:=1}
+
+############################################################ GLOBALS
+
+#
+# Format of a line in printf(1) syntax to add to fstab(5)
+#
+FSTAB_FMT="%s\t\t%s\t%s\t%s\t\t%s\t%s\n"
+
+#
+# Command strings for various tasks
+#
+CHMOD_MODE='chmod %s "%s"'
+DD_WITH_OPTIONS='dd if="%s" of="%s" %s'
+ECHO_APPEND='echo "%s" >> "%s"'
+GELI_ATTACH='geli attach -j - -k "%s" "%s"'
+GELI_DETACH_F='geli detach -f "%s"'
+GELI_PASSWORD_INIT='geli init -b -B "%s" -e %s -J - -K "%s" -l 256 -s 4096 "%s"'
+GPART_ADD_ALIGN='gpart add %s -t %s "%s"'
+GPART_ADD_ALIGN_INDEX='gpart add %s -i %s -t %s "%s"'
+GPART_ADD_ALIGN_INDEX_WITH_SIZE='gpart add %s -i %s -t %s -s %s "%s"'
+GPART_ADD_ALIGN_LABEL='gpart add %s -l %s -t %s "%s"'
+GPART_ADD_ALIGN_LABEL_WITH_SIZE='gpart add %s -l %s -t %s -s %s "%s"'
+GPART_BOOTCODE='gpart bootcode -b "%s" "%s"'
+GPART_BOOTCODE_PART='gpart bootcode -b "%s" -p "%s" -i %s "%s"'
+GPART_CREATE='gpart create -s %s "%s"'
+GPART_DESTROY_F='gpart destroy -F "%s"'
+GPART_SET_ACTIVE='gpart set -a active -i %s "%s"'
+GPART_SET_LENOVOFIX='gpart set -a lenovofix "%s"'
+GPART_SET_PMBR_ACTIVE='gpart set -a active "%s"'
+GRAID_DELETE='graid delete "%s"'
+LN_SF='ln -sf "%s" "%s"'
+MKDIR_P='mkdir -p "%s"'
+MOUNT_TYPE='mount -t %s "%s" "%s"'
+PRINTF_CONF="printf '%s=\"%%s\"\\\n' %s >> \"%s\""
+PRINTF_FSTAB='printf "$FSTAB_FMT" "%s" "%s" "%s" "%s" "%s" "%s" >> "%s"'
+SHELL_TRUNCATE=':> "%s"'
+SWAP_GMIRROR_LABEL='gmirror label swap %s'
+SYSCTL_ZFS_MIN_ASHIFT_12='sysctl vfs.zfs.min_auto_ashift=12'
+UMOUNT='umount "%s"'
+ZFS_CREATE_WITH_OPTIONS='zfs create %s "%s"'
+ZFS_SET='zfs set "%s" "%s"'
+ZFS_UNMOUNT='zfs unmount "%s"'
+ZPOOL_CREATE_WITH_OPTIONS='zpool create %s "%s" %s %s'
+ZPOOL_DESTROY='zpool destroy "%s"'
+ZPOOL_EXPORT='zpool export "%s"'
+ZPOOL_IMPORT_WITH_OPTIONS='zpool import %s "%s"'
+ZPOOL_LABELCLEAR_F='zpool labelclear -f "%s"'
+ZPOOL_SET='zpool set %s "%s"'
+
+#
+# Strings that should be moved to an i18n file and loaded with f_include_lang()
+#
+hline_alnum_arrows_punc_tab_enter="Use alnum, arrows, punctuation, TAB or ENTER"
+hline_arrows_space_tab_enter="Use arrows, SPACE, TAB or ENTER"
+hline_arrows_tab_enter="Press arrows, TAB or ENTER"
+msg_an_unknown_error_occurred="An unknown error occurred"
+msg_back="Back"
+msg_cancel="Cancel"
+msg_change_selection="Change Selection"
+msg_configure_options="Configure Options:"
+msg_detailed_disk_info="gpart(8) show %s:\n%s\n\ncamcontrol(8) inquiry %s:\n%s\n\n\ncamcontrol(8) identify %s:\n%s\n"
+msg_disk_info="Disk Info"
+msg_disk_info_help="Get detailed information on disk device(s)"
+msg_disk_singular="disk"
+msg_disk_plural="disks"
+msg_encrypt_disks="Encrypt Disks?"
+msg_encrypt_disks_help="Use geli(8) to encrypt all data partitions"
+msg_error="Error"
+msg_force_4k_sectors="Force 4K Sectors?"
+msg_force_4k_sectors_help="Align partitions to 4K sector boundries and set vfs.zfs.min_auto_ashift=12"
+msg_freebsd_installer="FreeBSD Installer"
+msg_geli_password="Enter a strong passphrase, used to protect your encryption keys. You will be required to enter this passphrase each time the system is booted"
+msg_geli_setup="Initializing encryption on selected disks,\n this will take several seconds per disk"
+msg_install="Install"
+msg_install_desc="Proceed with Installation"
+msg_install_help="Create ZFS boot pool with displayed options"
+msg_invalid_boot_pool_size="Invalid boot pool size \`%s'"
+msg_invalid_disk_argument="Invalid disk argument \`%s'"
+msg_invalid_index_argument="Invalid index argument \`%s'"
+msg_invalid_swap_size="Invalid swap size \`%s'"
+msg_invalid_virtual_device_type="Invalid Virtual Device type \`%s'"
+msg_last_chance_are_you_sure="Last Chance! Are you sure you want to destroy\nthe current contents of the following disks:\n\n %s"
+msg_last_chance_are_you_sure_color='\\ZrLast Chance!\\ZR Are you \\Z1sure\\Zn you want to \\Zr\\Z1destroy\\Zn\nthe current contents of the following disks:\n\n %s'
+msg_mirror_desc="Mirror - n-Way Mirroring"
+msg_mirror_help="[2+ Disks] Mirroring provides the best performance, but the least storage"
+msg_missing_disk_arguments="missing disk arguments"
+msg_missing_one_or_more_scripted_disks="Missing one or more scripted disks!"
+msg_no="NO"
+msg_no_disks_present_to_configure="No disk(s) present to configure"
+msg_no_disks_selected="No disks selected."
+msg_not_enough_disks_selected="Not enough disks selected. (%u < %u minimum)"
+msg_null_disk_argument="NULL disk argument"
+msg_null_index_argument="NULL index argument"
+msg_null_poolname="NULL poolname"
+msg_ok="OK"
+msg_partition_scheme="Partition Scheme"
+msg_partition_scheme_help="Select partitioning scheme. GPT is recommended."
+msg_please_enter_a_name_for_your_zpool="Please enter a name for your zpool:"
+msg_please_enter_amount_of_swap_space="Please enter amount of swap space (SI-Unit suffixes\nrecommended; e.g., \`2g' for 2 Gigabytes):"
+msg_please_select_one_or_more_disks="Please select one or more disks to create a zpool:"
+msg_pool_name="Pool Name"
+msg_pool_name_cannot_be_empty="Pool name cannot be empty."
+msg_pool_name_help="Customize the name of the zpool to be created (Required)"
+msg_pool_type_disks="Pool Type/Disks:"
+msg_pool_type_disks_help="Choose type of ZFS Virtual Device and disks to use (Required)"
+msg_processing_selection="Processing selection..."
+msg_raidz1_desc="RAID-Z1 - Single Redundant RAID"
+msg_raidz1_help="[3+ Disks] Withstand failure of 1 disk. Recommended for: 3, 5 or 9 disks"
+msg_raidz2_desc="RAID-Z2 - Double Redundant RAID"
+msg_raidz2_help="[4+ Disks] Withstand failure of 2 disks. Recommended for: 4, 6 or 10 disks"
+msg_raidz3_desc="RAID-Z3 - Triple Redundant RAID"
+msg_raidz3_help="[5+ Disks] Withstand failure of 3 disks. Recommended for: 5, 7 or 11 disks"
+msg_rescan_devices="Rescan Devices"
+msg_rescan_devices_help="Scan for device changes"
+msg_select="Select"
+msg_select_a_disk_device="Select a disk device"
+msg_select_virtual_device_type="Select Virtual Device type:"
+msg_stripe_desc="Stripe - No Redundancy"
+msg_stripe_help="[1+ Disks] Striping provides maximum storage but no redundancy"
+msg_swap_encrypt="Encrypt Swap?"
+msg_swap_encrypt_help="Encrypt swap partitions with temporary keys, discarded on reboot"
+msg_swap_invalid="The selected swap size (%s) is invalid. Enter a number optionally followed by units. Example: 2G"
+msg_swap_mirror="Mirror Swap?"
+msg_swap_mirror_help="Mirror swap partitions for redundancy, breaks crash dumps"
+msg_swap_size="Swap Size"
+msg_swap_size_help="Customize how much swap space is allocated to each selected disk"
+msg_swap_toosmall="The selected swap size (%s) is to small. Please enter a value greater than 100MB or enter 0 for no swap"
+msg_these_disks_are_too_small="These disks are too small given the amount of requested\nswap (%s) and/or geli(8) (%s) partitions, which would\ntake 50%% or more of each of the following selected disk\ndevices (not recommended):\n\n %s\n\nRecommend changing partition size(s) and/or selecting a\ndifferent set of devices."
+msg_uefi_not_supported="The FreeBSD UEFI loader does not currently support booting root-on-ZFS. Your system will need to boot in legacy (CSM) mode.\nDo you want to continue?"
+msg_unable_to_get_disk_capacity="Unable to get disk capacity of \`%s'"
+msg_unsupported_partition_scheme="%s is an unsupported partition scheme"
+msg_user_cancelled="User Cancelled."
+msg_yes="YES"
+msg_zfs_configuration="ZFS Configuration"
+
+############################################################ FUNCTIONS
+
+# dialog_menu_main
+#
+# Display the dialog(1)-based application main menu.
+#
+dialog_menu_main()
+{
+ local title="$DIALOG_TITLE"
+ local btitle="$DIALOG_BACKTITLE"
+ local prompt="$msg_configure_options"
+ local force4k="$msg_no"
+ local usegeli="$msg_no"
+ local swapgeli="$msg_no"
+ local swapmirror="$msg_no"
+ [ "$ZFSBOOT_FORCE_4K_SECTORS" ] && force4k="$msg_yes"
+ [ "$ZFSBOOT_GELI_ENCRYPTION" ] && usegeli="$msg_yes"
+ [ "$ZFSBOOT_SWAP_ENCRYPTION" ] && swapgeli="$msg_yes"
+ [ "$ZFSBOOT_SWAP_MIRROR" ] && swapmirror="$msg_yes"
+ local disks n disks_grammar
+ f_count n $ZFSBOOT_DISKS
+ { [ $n -eq 1 ] && disks_grammar=$msg_disk_singular; } ||
+ disks_grammar=$msg_disk_plural # grammar
+ local menu_list="
+ '>>> $msg_install' '$msg_install_desc'
+ '$msg_install_help'
+ 'T $msg_pool_type_disks'
+ '$ZFSBOOT_VDEV_TYPE: $n $disks_grammar'
+ '$msg_pool_type_disks_help'
+ '- $msg_rescan_devices' '*'
+ '$msg_rescan_devices_help'
+ '- $msg_disk_info' '*'
+ '$msg_disk_info_help'
+ 'N $msg_pool_name' '$ZFSBOOT_POOL_NAME'
+ '$msg_pool_name_help'
+ '4 $msg_force_4k_sectors'
+ '$force4k'
+ '$msg_force_4k_sectors_help'
+ 'E $msg_encrypt_disks' '$usegeli'
+ '$msg_encrypt_disks_help'
+ 'P $msg_partition_scheme'
+ '$ZFSBOOT_PARTITION_SCHEME'
+ '$msg_partition_scheme_help'
+ 'S $msg_swap_size' '$ZFSBOOT_SWAP_SIZE'
+ '$msg_swap_size_help'
+ 'M $msg_swap_mirror' '$swapmirror'
+ '$msg_swap_mirror_help'
+ 'W $msg_swap_encrypt' '$swapgeli'
+ '$msg_swap_encrypt_help'
+ " # END-QUOTE
+ local defaultitem= # Calculated below
+ local hline="$hline_alnum_arrows_punc_tab_enter"
+
+ local height width rows
+ eval f_dialog_menu_with_help_size height width rows \
+ \"\$title\" \"\$btitle\" \"\$prompt\" \"\$hline\" $menu_list
+
+ # Obtain default-item from previously stored selection
+ f_dialog_default_fetch defaultitem
+
+ local menu_choice
+ menu_choice=$( eval $DIALOG \
+ --title \"\$title\" \
+ --backtitle \"\$btitle\" \
+ --hline \"\$hline\" \
+ --item-help \
+ --ok-label \"\$msg_select\" \
+ --cancel-label \"\$msg_cancel\" \
+ --default-item \"\$defaultitem\" \
+ --menu \"\$prompt\" \
+ $height $width $rows \
+ $menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ )
+ local retval=$?
+ f_dialog_data_sanitize menu_choice
+ f_dialog_menutag_store "$menu_choice"
+
+ # Only update default-item on success
+ [ $retval -eq $DIALOG_OK ] && f_dialog_default_store "$menu_choice"
+
+ return $retval
+}
+
+# dialog_last_chance $disks ...
+#
+# Display a list of the disks that the user is about to destroy. The default
+# action is to return error status unless the user explicitly (non-default)
+# selects "Yes" from the noyes dialog.
+#
+dialog_last_chance()
+{
+ local title="$DIALOG_TITLE"
+ local btitle="$DIALOG_BACKTITLE"
+ local prompt # Calculated below
+ local hline="$hline_arrows_tab_enter"
+
+ local height=8 width=50 prefix=" "
+ local plen=${#prefix} list= line=
+ local max_width=$(( $width - 3 - $plen ))
+
+ local yes no defaultno extra_args format
+ if [ "$USE_XDIALOG" ]; then
+ yes=ok no=cancel defaultno=default-no
+ extra_args="--wrap --left"
+ format="$msg_last_chance_are_you_sure"
+ else
+ yes=yes no=no defaultno=defaultno
+ extra_args="--colors --cr-wrap"
+ format="$msg_last_chance_are_you_sure_color"
+ fi
+
+ local disk line_width
+ for disk in $*; do
+ if [ "$line" ]; then
+ line_width=${#line}
+ else
+ line_width=$plen
+ fi
+ line_width=$(( $line_width + 1 + ${#disk} ))
+ # Add newline before disk if it would exceed max_width
+ if [ $line_width -gt $max_width ]; then
+ list="$list$line\n"
+ line="$prefix"
+ height=$(( $height + 1 ))
+ fi
+ # Add the disk to the list
+ line="$line $disk"
+ done
+ # Append the left-overs
+ if [ "${line#$prefix}" ]; then
+ list="$list$line"
+ height=$(( $height + 1 ))
+ fi
+
+ # Add height for Xdialog(1)
+ [ "$USE_XDIALOG" ] && height=$(( $height + $height / 5 + 3 ))
+
+ prompt=$( printf "$format" "$list" )
+ f_dprintf "%s: Last Chance!" "$0"
+ $DIALOG \
+ --title "$title" \
+ --backtitle "$btitle" \
+ --hline "$hline" \
+ --$defaultno \
+ --$yes-label "$msg_yes" \
+ --$no-label "$msg_no" \
+ $extra_args \
+ --yesno "$prompt" $height $width
+}
+
+# dialog_menu_layout
+#
+# Configure Virtual Device type and disks to use for the ZFS boot pool. User
+# must select enough disks to satisfy the chosen vdev type.
+#
+dialog_menu_layout()
+{
+ local funcname=dialog_menu_layout
+ local title="$DIALOG_TITLE"
+ local btitle="$DIALOG_BACKTITLE"
+ local vdev_prompt="$msg_select_virtual_device_type"
+ local disk_prompt="$msg_please_select_one_or_more_disks"
+ local vdev_menu_list="
+ 'stripe' '$msg_stripe_desc' '$msg_stripe_help'
+ 'mirror' '$msg_mirror_desc' '$msg_mirror_help'
+ 'raidz1' '$msg_raidz1_desc' '$msg_raidz1_help'
+ 'raidz2' '$msg_raidz2_desc' '$msg_raidz2_help'
+ 'raidz3' '$msg_raidz3_desc' '$msg_raidz3_help'
+ " # END-QUOTE
+ local disk_check_list= # Calculated below
+ local vdev_hline="$hline_arrows_tab_enter"
+ local disk_hline="$hline_arrows_space_tab_enter"
+
+ # Warn the user if vdev type is not valid
+ case "$ZFSBOOT_VDEV_TYPE" in
+ stripe|mirror|raidz1|raidz2|raidz3) : known good ;;
+ *)
+ f_dprintf "%s: Invalid virtual device type \`%s'" \
+ $funcname "$ZFSBOOT_VDEV_TYPE"
+ f_show_err "$msg_invalid_virtual_device_type" \
+ "$ZFSBOOT_VDEV_TYPE"
+ f_interactive || return $FAILURE
+ esac
+
+ # Calculate size of vdev menu once only
+ local vheight vwidth vrows
+ eval f_dialog_menu_with_help_size vheight vwidth vrows \
+ \"\$title\" \"\$btitle\" \"\$vdev_prompt\" \"\$vdev_hline\" \
+ $vdev_menu_list
+
+ # Get a list of probed disk devices
+ local disks=
+ debug= f_device_find "" $DEVICE_TYPE_DISK disks
+
+ # Prune out mounted md(4) devices that may be part of the boot process
+ local disk name new_list=
+ for disk in $disks; do
+ debug= $disk get name name
+ case "$name" in
+ md[0-9]*) f_mounted -b "/dev/$name" && continue ;;
+ esac
+ new_list="$new_list $disk"
+ done
+ disks="${new_list# }"
+
+ # Debugging
+ if [ "$debug" ]; then
+ local disk_names=
+ for disk in $disks; do
+ debug= $disk get name name
+ disk_names="$disk_names $name"
+ done
+ f_dprintf "$funcname: disks=[%s]" "${disk_names# }"
+ fi
+
+ if [ ! "$disks" ]; then
+ f_dprintf "No disk(s) present to configure"
+ f_show_err "$msg_no_disks_present_to_configure"
+ return $FAILURE
+ fi
+
+ # Lets sort the disks array to be more user friendly
+ f_device_sort_by name disks disks
+
+ #
+ # Operate in a loop so we can (if interactive) repeat if not enough
+ # disks are selected to satisfy the chosen vdev type or user wants to
+ # back-up to the previous menu.
+ #
+ local vardisk ndisks onoff selections vdev_choice breakout device
+ local valid_disks all_valid want_disks desc height width rows
+ while :; do
+ #
+ # Confirm the vdev type that was selected
+ #
+ if f_interactive && [ "$ZFSBOOT_CONFIRM_LAYOUT" ]; then
+ vdev_choice=$( eval $DIALOG \
+ --title \"\$title\" \
+ --backtitle \"\$btitle\" \
+ --hline \"\$vdev_hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_cancel\" \
+ --item-help \
+ --default-item \"\$ZFSBOOT_VDEV_TYPE\" \
+ --menu \"\$vdev_prompt\" \
+ $vheight $vwidth $vrows \
+ $vdev_menu_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ ) || return $?
+ # Exit if user pressed ESC or chose Cancel/No
+ f_dialog_data_sanitize vdev_choice
+
+ ZFSBOOT_VDEV_TYPE="$vdev_choice"
+ f_dprintf "$funcname: ZFSBOOT_VDEV_TYPE=[%s]" \
+ "$ZFSBOOT_VDEV_TYPE"
+ fi
+
+ # Determine the number of disks needed for this vdev type
+ want_disks=0
+ case "$ZFSBOOT_VDEV_TYPE" in
+ stripe) want_disks=1 ;;
+ mirror) want_disks=2 ;;
+ raidz1) want_disks=3 ;;
+ raidz2) want_disks=4 ;;
+ raidz3) want_disks=5 ;;
+ esac
+
+ #
+ # Warn the user if any scripted disks are invalid
+ #
+ valid_disks= all_valid=${ZFSBOOT_DISKS:+1} # optimism
+ for disk in $ZFSBOOT_DISKS; do
+ if debug= f_device_find -1 \
+ $disk $DEVICE_TYPE_DISK device
+ then
+ valid_disks="$valid_disks $disk"
+ continue
+ fi
+ f_dprintf "$funcname: \`%s' is not a real disk" "$disk"
+ all_valid=
+ done
+ if [ ! "$all_valid" ]; then
+ if [ "$ZFSBOOT_DISKS" ]; then
+ f_show_err \
+ "$msg_missing_one_or_more_scripted_disks"
+ else
+ f_dprintf "No disks selected."
+ f_interactive ||
+ f_show_err "$msg_no_disks_selected"
+ fi
+ f_interactive || return $FAILURE
+ fi
+ ZFSBOOT_DISKS="${valid_disks# }"
+
+ #
+ # Short-circuit if we're running non-interactively
+ #
+ if ! f_interactive || [ ! "$ZFSBOOT_CONFIRM_LAYOUT" ]; then
+ f_count ndisks $ZFSBOOT_DISKS
+ [ $ndisks -ge $want_disks ] && break # to success
+
+ # Not enough disks selected
+ f_dprintf "$funcname: %s: %s (%u < %u minimum)" \
+ "$ZFSBOOT_VDEV_TYPE" \
+ "Not enough disks selected." \
+ $ndisks $want_disks
+ f_interactive || return $FAILURE
+ msg_yes="$msg_change_selection" msg_no="$msg_cancel" \
+ f_yesno "%s: $msg_not_enough_disks_selected" \
+ "$ZFSBOOT_VDEV_TYPE" $ndisks $want_disks ||
+ return $FAILURE
+ fi
+
+ #
+ # Confirm the disks that were selected
+ # Loop until the user cancels or selects enough disks
+ #
+ breakout=
+ while :; do
+ # Loop over list of available disks, resetting state
+ for disk in $disks; do
+ f_isset _${disk}_status && _${disk}_status=
+ done
+
+ # Loop over list of selected disks and create temporary
+ # locals to map statuses onto up-to-date list of disks
+ for disk in $ZFSBOOT_DISKS; do
+ debug= f_device_find -1 \
+ $disk $DEVICE_TYPE_DISK disk
+ f_isset _${disk}_status ||
+ local _${disk}_status
+ _${disk}_status=on
+ done
+
+ # Create the checklist menu of discovered disk devices
+ disk_check_list=
+ for disk in $disks; do
+ desc=
+ $disk get name name
+ $disk get desc desc
+ f_shell_escape "$desc" desc
+ f_getvar _${disk}_status:-off onoff
+ disk_check_list="$disk_check_list
+ $name '$desc' $onoff"
+ done
+
+ eval f_dialog_checklist_size height width rows \
+ \"\$title\" \"\$btitle\" \"\$prompt\" \
+ \"\$hline\" $disk_check_list
+
+ selections=$( eval $DIALOG \
+ --title \"\$DIALOG_TITLE\" \
+ --backtitle \"\$DIALOG_BACKTITLE\" \
+ --separate-output \
+ --hline \"\$hline\" \
+ --ok-label \"\$msg_ok\" \
+ --cancel-label \"\$msg_back\" \
+ --checklist \"\$prompt\" \
+ $height $width $rows \
+ $disk_check_list \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ ) || break
+ # Loop if user pressed ESC or chose Cancel/No
+ f_dialog_data_sanitize selections
+
+ ZFSBOOT_DISKS="$selections"
+ f_dprintf "$funcname: ZFSBOOT_DISKS=[%s]" \
+ "$ZFSBOOT_DISKS"
+
+ f_count ndisks $ZFSBOOT_DISKS
+ [ $ndisks -ge $want_disks ] &&
+ breakout=break && break
+
+ # Not enough disks selected
+ f_dprintf "$funcname: %s: %s (%u < %u minimum)" \
+ "$ZFSBOOT_VDEV_TYPE" \
+ "Not enough disks selected." \
+ $ndisks $want_disks
+ msg_yes="$msg_change_selection" msg_no="$msg_cancel" \
+ f_yesno "%s: $msg_not_enough_disks_selected" \
+ "$ZFSBOOT_VDEV_TYPE" $ndisks $want_disks ||
+ break
+ done
+ [ "$breakout" = "break" ] && break
+ [ "$ZFSBOOT_CONFIRM_LAYOUT" ] || return $FAILURE
+ done
+
+ return $DIALOG_OK
+}
+
+# dialog_uefi_prompt
+#
+# Confirm that the user wants to continue with the installation on a BIOS
+# system when they have booted with UEFI
+#
+dialog_uefi_prompt()
+{
+ local title="$DIALOG_TITLE"
+ local btitle="$DIALOG_BACKTITLE"
+ local prompt # Calculated below
+ local hline="$hline_arrows_tab_enter"
+
+ local height=8 width=50 prefix=" "
+ local plen=${#prefix} list= line=
+ local max_width=$(( $width - 3 - $plen ))
+
+ local yes no defaultno extra_args format
+ if [ "$USE_XDIALOG" ]; then
+ yes=ok no=cancel defaultno=default-no
+ extra_args="--wrap --left"
+ format="$msg_uefi_not_supported"
+ else
+ yes=yes no=no defaultno=defaultno
+ extra_args="--cr-wrap"
+ format="$msg_uefi_not_supported"
+ fi
+
+ # Add height for Xdialog(1)
+ [ "$USE_XDIALOG" ] && height=$(( $height + $height / 5 + 3 ))
+
+ prompt=$( printf "$format" )
+ f_dprintf "%s: UEFI prompt" "$0"
+ $DIALOG \
+ --title "$title" \
+ --backtitle "$btitle" \
+ --hline "$hline" \
+ --$yes-label "$msg_yes" \
+ --$no-label "$msg_no" \
+ $extra_args \
+ --yesno "$prompt" $height $width
+}
+
+# zfs_create_diskpart $disk $index
+#
+# For each block device to be used in the zpool, rather than just create the
+# zpool with the raw block devices (e.g., da0, da1, etc.) we create partitions
+# so we can have some real swap. This also provides wiggle room incase your
+# replacement drivers do not have the exact same sector counts.
+#
+# NOTE: $swapsize and $bootsize should be defined by the calling function.
+# NOTE: Sets $bootpart and $targetpart for the calling function.
+#
+zfs_create_diskpart()
+{
+ local funcname=zfs_create_diskpart
+ local disk="$1" index="$2"
+
+ # Check arguments
+ if [ ! "$disk" ]; then
+ f_dprintf "$funcname: NULL disk argument"
+ msg_error="$msg_error: $funcname" \
+ f_show_err "$msg_null_disk_argument"
+ return $FAILURE
+ fi
+ if [ "${disk#*[$IFS]}" != "$disk" ]; then
+ f_dprintf "$funcname: Invalid disk argument \`%s'" "$disk"
+ msg_error="$msg_error: $funcname" \
+ f_show_err "$msg_invalid_disk_argument" "$disk"
+ return $FAILURE
+ fi
+ if [ ! "$index" ]; then
+ f_dprintf "$funcname: NULL index argument"
+ msg_error="$msg_error: $funcname" \
+ f_show_err "$msg_null_index_argument"
+ return $FAILURE
+ fi
+ if ! f_isinteger "$index"; then
+ f_dprintf "$funcname: Invalid index argument \`%s'" "$index"
+ msg_error="$msg_error: $funcname" \
+ f_show_err "$msg_invalid_index_argument" "$index"
+ return $FAILURE
+ fi
+ f_dprintf "$funcname: disk=[%s] index=[%s]" "$disk" "$index"
+
+ # Check for unknown partition scheme before proceeding further
+ case "$ZFSBOOT_PARTITION_SCHEME" in
+ ""|MBR|GPT*) : known good ;;
+ *)
+ f_dprintf "$funcname: %s is an unsupported partition scheme" \
+ "$ZFSBOOT_PARTITION_SCHEME"
+ msg_error="$msg_error: $funcname" f_show_err \
+ "$msg_unsupported_partition_scheme" \
+ "$ZFSBOOT_PARTITION_SCHEME"
+ return $FAILURE
+ esac
+
+ #
+ # Destroy whatever partition layout is currently on disk.
+ # NOTE: `-F' required to destroy if partitions still exist.
+ # NOTE: Failure is ok here, blank disk will have nothing to destroy.
+ #
+ f_dprintf "$funcname: Destroying all data/layouts on \`%s'..." "$disk"
+ f_eval_catch -d $funcname gpart "$GPART_DESTROY_F" $disk
+ f_eval_catch -d $funcname graid "$GRAID_DELETE" $disk
+ f_eval_catch -d $funcname zpool "$ZPOOL_LABELCLEAR_F" /dev/$disk
+
+ # Make doubly-sure backup GPT is destroyed
+ f_eval_catch -d $funcname gpart "$GPART_CREATE" gpt $disk
+ f_eval_catch -d $funcname gpart "$GPART_DESTROY_F" $disk
+
+ #
+ # Enable boot pool if encryption is desired
+ #
+ [ "$ZFSBOOT_GELI_ENCRYPTION" ] && ZFSBOOT_BOOT_POOL=1
+
+ #
+ # Lay down the desired type of partition scheme
+ #
+ local setsize mbrindex align_small align_big
+ #
+ # If user has requested 4 K alignment, add these params to the
+ # gpart add calls. With GPT, we align large partitions to 1 M for
+ # improved performance on SSDs. MBR does not always play well with gaps
+ # between partitions, so all alignment is only 4k for that case.
+ # With MBR, we align the BSD partition that contains the MBR, otherwise
+ # the system fails to boot.
+ #
+ if [ "$ZFSBOOT_FORCE_4K_SECTORS" ]; then
+ align_small="-a 4k"
+ align_big="-a 1m"
+ fi
+
+ case "$ZFSBOOT_PARTITION_SCHEME" in
+ ""|GPT*) f_dprintf "$funcname: Creating GPT layout..."
+ #
+ # 1. Create GPT layout using labels
+ #
+ f_eval_catch $funcname gpart "$GPART_CREATE" gpt $disk ||
+ return $FAILURE
+
+ #
+ # Apply workarounds if requested by the user
+ #
+ if [ "$ZFSBOOT_PARTITION_SCHEME" = "GPT + Lenovo Fix" ]; then
+ f_eval_catch $funcname gpart "$GPART_SET_LENOVOFIX" \
+ $disk || return $FAILURE
+ elif [ "$ZFSBOOT_PARTITION_SCHEME" = "GPT + Active" ]; then
+ f_eval_catch $funcname gpart "$GPART_SET_PMBR_ACTIVE" \
+ $disk || return $FAILURE
+ fi
+
+ #
+ # 2. Add small freebsd-boot partition labeled `boot#'
+ #
+ f_eval_catch $funcname gpart "$GPART_ADD_ALIGN_LABEL_WITH_SIZE" \
+ "$align_small" gptboot$index freebsd-boot 512k $disk ||
+ return $FAILURE
+ f_eval_catch $funcname gpart "$GPART_BOOTCODE_PART" \
+ /boot/pmbr /boot/gptzfsboot 1 $disk ||
+ return $FAILURE
+
+ # NB: zpool will use the `zfs#' GPT labels
+ bootpart=p2 swappart=p2 targetpart=p2
+ [ ${swapsize:-0} -gt 0 ] && targetpart=p3
+
+ #
+ # Prepare boot pool if enabled (e.g., for geli(8))
+ #
+ if [ "$ZFSBOOT_BOOT_POOL" ]; then
+ bootpart=p2 swappart=p3 targetpart=p3
+ [ ${swapsize:-0} -gt 0 ] && targetpart=p4
+ f_eval_catch $funcname gpart \
+ "$GPART_ADD_ALIGN_LABEL_WITH_SIZE" \
+ "$align_big" boot$index freebsd-zfs \
+ ${bootsize}b $disk ||
+ return $FAILURE
+ # Pedantically nuke any old labels
+ f_eval_catch -d $funcname zpool "$ZPOOL_LABELCLEAR_F" \
+ /dev/$disk$bootpart
+ if [ "$ZFSBOOT_GELI_ENCRYPTION" ]; then
+ # Pedantically detach targetpart for later
+ f_eval_catch -d $funcname geli \
+ "$GELI_DETACH_F" \
+ /dev/$disk$targetpart
+ fi
+ fi
+
+ #
+ # 3. Add freebsd-swap partition labeled `swap#'
+ #
+ if [ ${swapsize:-0} -gt 0 ]; then
+ f_eval_catch $funcname gpart \
+ "$GPART_ADD_ALIGN_LABEL_WITH_SIZE" \
+ "$align_big" swap$index freebsd-swap \
+ ${swapsize}b $disk ||
+ return $FAILURE
+ # Pedantically nuke any old labels on the swap
+ f_eval_catch -d $funcname zpool "$ZPOOL_LABELCLEAR_F" \
+ /dev/$disk$swappart
+ fi
+
+ #
+ # 4. Add freebsd-zfs partition labeled `zfs#' for zroot
+ #
+ f_eval_catch $funcname gpart "$GPART_ADD_ALIGN_LABEL" \
+ "$align_big" zfs$index freebsd-zfs $disk ||
+ return $FAILURE
+ f_eval_catch -d $funcname zpool "$ZPOOL_LABELCLEAR_F" \
+ /dev/$disk$targetpart
+ ;;
+
+ MBR) f_dprintf "$funcname: Creating MBR layout..."
+ #
+ # 1. Create MBR layout (no labels)
+ #
+ f_eval_catch $funcname gpart "$GPART_CREATE" mbr $disk ||
+ return $FAILURE
+ f_eval_catch $funcname gpart "$GPART_BOOTCODE" /boot/mbr \
+ $disk || return $FAILURE
+
+ #
+ # 2. Add freebsd slice with all available space
+ #
+ f_eval_catch $funcname gpart "$GPART_ADD_ALIGN" "$align_small" \
+ freebsd $disk ||
+ return $FAILURE
+ f_eval_catch $funcname gpart "$GPART_SET_ACTIVE" 1 $disk ||
+ return $FAILURE
+ # Pedantically nuke any old labels
+ f_eval_catch -d $funcname zpool "$ZPOOL_LABELCLEAR_F" \
+ /dev/${disk}s1
+ # Pedantically nuke any old scheme
+ f_eval_catch -d $funcname gpart "$GPART_DESTROY_F" ${disk}s1
+
+ #
+ # 3. Write BSD scheme to the freebsd slice
+ #
+ f_eval_catch $funcname gpart "$GPART_CREATE" BSD ${disk}s1 ||
+ return $FAILURE
+
+ # NB: zpool will use s1a (no labels)
+ bootpart=s1a swappart=s1b targetpart=s1d mbrindex=4
+
+ #
+ # Always prepare a boot pool on MBR
+ #
+ ZFSBOOT_BOOT_POOL=1
+ f_eval_catch $funcname gpart \
+ "$GPART_ADD_ALIGN_INDEX_WITH_SIZE" \
+ "$align_small" 1 freebsd-zfs ${bootsize}b ${disk}s1 ||
+ return $FAILURE
+ # Pedantically nuke any old labels
+ f_eval_catch -d $funcname zpool "$ZPOOL_LABELCLEAR_F" \
+ /dev/$disk$bootpart
+ if [ "$ZFSBOOT_GELI_ENCRYPTION" ]; then
+ # Pedantically detach targetpart for later
+ f_eval_catch -d $funcname geli \
+ "$GELI_DETACH_F" \
+ /dev/$disk$targetpart
+ fi
+
+ #
+ # 4. Add freebsd-swap partition
+ #
+ if [ ${swapsize:-0} -gt 0 ]; then
+ f_eval_catch $funcname gpart \
+ "$GPART_ADD_ALIGN_INDEX_WITH_SIZE" \
+ "$align_small" 2 freebsd-swap ${swapsize}b ${disk}s1 ||
+ return $FAILURE
+ # Pedantically nuke any old labels on the swap
+ f_eval_catch -d $funcname zpool "$ZPOOL_LABELCLEAR_F" \
+ /dev/${disk}s1b
+ fi
+
+ #
+ # 5. Add freebsd-zfs partition for zroot
+ #
+ f_eval_catch $funcname gpart "$GPART_ADD_ALIGN_INDEX" \
+ "$align_small" $mbrindex freebsd-zfs ${disk}s1 || return $FAILURE
+ f_eval_catch -d $funcname zpool "$ZPOOL_LABELCLEAR_F" \
+ /dev/$disk$targetpart # Pedantic
+ f_eval_catch $funcname dd "$DD_WITH_OPTIONS" \
+ /boot/zfsboot /dev/${disk}s1 count=1 ||
+ return $FAILURE
+ ;;
+
+ esac # $ZFSBOOT_PARTITION_SCHEME
+
+ # Update fstab(5)
+ local swapsize
+ f_expand_number "$ZFSBOOT_SWAP_SIZE" swapsize
+ if [ "$isswapmirror" ]; then
+ # This is not the first disk in the mirror, do nothing
+ elif [ "$ZFSBOOT_SWAP_ENCRYPTION" -a "$ZFSBOOT_SWAP_MIRROR" ]; then
+ f_eval_catch $funcname printf "$PRINTF_FSTAB" \
+ /dev/mirror/swap.eli none swap sw 0 0 \
+ $BSDINSTALL_TMPETC/fstab ||
+ return $FAILURE
+ isswapmirror=1
+ elif [ "$ZFSBOOT_SWAP_MIRROR" ]; then
+ f_eval_catch $funcname printf "$PRINTF_FSTAB" \
+ /dev/mirror/swap none swap sw 0 0 \
+ $BSDINSTALL_TMPETC/fstab ||
+ return $FAILURE
+ isswapmirror=1
+ elif [ "$ZFSBOOT_SWAP_ENCRYPTION" ]; then
+ f_eval_catch $funcname printf "$PRINTF_FSTAB" \
+ /dev/$disk${swappart}.eli none swap sw 0 0 \
+ $BSDINSTALL_TMPETC/fstab ||
+ return $FAILURE
+ elif [ ${swapsize:-0} -eq 0 ]; then
+ # If swap is 0 sized, don't add it to fstab
+ else
+ f_eval_catch $funcname printf "$PRINTF_FSTAB" \
+ /dev/$disk$swappart none swap sw 0 0 \
+ $BSDINSTALL_TMPETC/fstab ||
+ return $FAILURE
+ fi
+
+ return $SUCCESS
+}
+
+# zfs_create_boot $poolname $vdev_type $disks ...
+#
+# Creates boot pool and dataset layout. Returns error if something goes wrong.
+# Errors are printed to stderr for collection and display.
+#
+zfs_create_boot()
+{
+ local funcname=zfs_create_boot
+ local zroot_name="$1"
+ local zroot_vdevtype="$2"
+ local zroot_vdevs= # Calculated below
+ local swap_devs= # Calculated below
+ local boot_vdevs= # Used for geli(8) and/or MBR layouts
+ shift 2 # poolname vdev_type
+ local disks="$*" disk
+ local isswapmirror
+ local bootpart targetpart swappart # Set by zfs_create_diskpart() below
+ local create_options
+
+ #
+ # Pedantic checks; should never be seen
+ #
+ if [ ! "$zroot_name" ]; then
+ f_dprintf "$funcname: NULL poolname"
+ msg_error="$msg_error: $funcname" \
+ f_show_err "$msg_null_poolname"
+ return $FAILURE
+ fi
+ if [ $# -lt 1 ]; then
+ f_dprintf "$funcname: missing disk arguments"
+ msg_error="$msg_error: $funcname" \
+ f_show_err "$msg_missing_disk_arguments"
+ return $FAILURE
+ fi
+ f_dprintf "$funcname: poolname=[%s] vdev_type=[%s]" \
+ "$zroot_name" "$zroot_vdevtype"
+
+ #
+ # Initialize fstab(5)
+ #
+ f_dprintf "$funcname: Initializing temporary fstab(5) file..."
+ f_eval_catch $funcname sh "$SHELL_TRUNCATE" $BSDINSTALL_TMPETC/fstab ||
+ return $FAILURE
+ f_eval_catch $funcname printf "$PRINTF_FSTAB" \
+ "# Device" Mountpoint FStype Options Dump "Pass#" \
+ $BSDINSTALL_TMPETC/fstab || return $FAILURE
+
+ #
+ # Expand SI units in desired sizes
+ #
+ f_dprintf "$funcname: Expanding supplied size values..."
+ local swapsize bootsize
+ if ! f_expand_number "$ZFSBOOT_SWAP_SIZE" swapsize; then
+ f_dprintf "$funcname: Invalid swap size \`%s'" \
+ "$ZFSBOOT_SWAP_SIZE"
+ f_show_err "$msg_invalid_swap_size" "$ZFSBOOT_SWAP_SIZE"
+ return $FAILURE
+ fi
+ if ! f_expand_number "$ZFSBOOT_BOOT_POOL_SIZE" bootsize; then
+ f_dprintf "$funcname: Invalid boot pool size \`%s'" \
+ "$ZFSBOOT_BOOT_POOL_SIZE"
+ f_show_err "$msg_invalid_boot_pool_size" \
+ "$ZFSBOOT_BOOT_POOL_SIZE"
+ return $FAILURE
+ fi
+ f_dprintf "$funcname: ZFSBOOT_SWAP_SIZE=[%s] swapsize=[%s]" \
+ "$ZFSBOOT_SWAP_SIZE" "$swapsize"
+ f_dprintf "$funcname: ZFSBOOT_BOOT_POOL_SIZE=[%s] bootsize=[%s]" \
+ "$ZFSBOOT_BOOT_POOL_SIZE" "$bootsize"
+
+ #
+ # Destroy the pool in-case this is our second time 'round (case of
+ # failure and installer presented ``Retry'' option to come back).
+ #
+ # NB: If we don't destroy the pool, later gpart(8) destroy commands
+ # that try to clear existing partitions (see zfs_create_diskpart())
+ # will fail with a `Device Busy' error, leading to `GEOM exists'.
+ #
+ f_eval_catch -d $funcname zpool "$ZPOOL_DESTROY" "$zroot_name"
+
+ #
+ # Prepare the disks and build pool device list(s)
+ #
+ f_dprintf "$funcname: Preparing disk partitions for ZFS pool..."
+
+ # Force 4K sectors using vfs.zfs.min_auto_ashift=12
+ if [ "$ZFSBOOT_FORCE_4K_SECTORS" ]; then
+ f_dprintf "$funcname: With 4K sectors..."
+ f_eval_catch $funcname sysctl "$SYSCTL_ZFS_MIN_ASHIFT_12" \
+ || return $FAILURE
+ fi
+ local n=0
+ for disk in $disks; do
+ zfs_create_diskpart $disk $n || return $FAILURE
+ # Now $bootpart, $targetpart, and $swappart are set (suffix
+ # for $disk)
+ if [ "$ZFSBOOT_BOOT_POOL" ]; then
+ boot_vdevs="$boot_vdevs $disk$bootpart"
+ fi
+ zroot_vdevs="$zroot_vdevs $disk$targetpart"
+ if [ "$ZFSBOOT_GELI_ENCRYPTION" ]; then
+ zroot_vdevs="$zroot_vdevs.eli"
+ fi
+
+ n=$(( $n + 1 ))
+ done # disks
+
+ #
+ # If we need/want a boot pool, create it
+ #
+ if [ "$ZFSBOOT_BOOT_POOL" ]; then
+ local bootpool_vdevtype= # Calculated below
+ local bootpool_options= # Calculated below
+ local bootpool_name="$ZFSBOOT_BOOT_POOL_NAME"
+ local bootpool="$BSDINSTALL_CHROOT/$bootpool_name"
+ local zroot_key="${ZFSBOOT_GELI_KEY_FILE#/}"
+
+ f_dprintf "$funcname: Setting up boot pool..."
+ [ "$ZFSBOOT_GELI_ENCRYPTION" ] &&
+ f_dprintf "$funcname: For encrypted root disk..."
+
+ # Create parent directory for boot pool
+ f_eval_catch -d $funcname umount "$UMOUNT" /mnt
+ f_eval_catch $funcname mount "$MOUNT_TYPE" tmpfs none \
+ $BSDINSTALL_CHROOT || return $FAILURE
+
+ # Create mirror across the boot partition on all disks
+ local nvdevs
+ f_count nvdevs $boot_vdevs
+ [ $nvdevs -gt 1 ] && bootpool_vdevtype=mirror
+
+ create_options="$ZFSBOOT_BOOT_POOL_CREATE_OPTIONS"
+ bootpool_options="-o altroot=$BSDINSTALL_CHROOT"
+ bootpool_options="$bootpool_options $create_options"
+ bootpool_options="$bootpool_options -m \"/$bootpool_name\" -f"
+ f_eval_catch $funcname zpool "$ZPOOL_CREATE_WITH_OPTIONS" \
+ "$bootpool_options" "$bootpool_name" \
+ "$bootpool_vdevtype" "$boot_vdevs" ||
+ return $FAILURE
+
+ f_eval_catch $funcname mkdir "$MKDIR_P" "$bootpool/boot" ||
+ return $FAILURE
+
+ if [ "$ZFSBOOT_GELI_ENCRYPTION" ]; then
+ # Generate an encryption key using random(4)
+ f_eval_catch $funcname dd "$DD_WITH_OPTIONS" \
+ /dev/random "$bootpool/$zroot_key" \
+ "bs=4096 count=1" || return $FAILURE
+ f_eval_catch $funcname chmod "$CHMOD_MODE" \
+ go-wrx "$bootpool/$zroot_key" ||
+ return $FAILURE
+ else
+ # Clean up
+ f_eval_catch $funcname zfs "$ZFS_UNMOUNT" \
+ "$bootpool_name" || return $FAILURE
+ f_eval_catch -d $funcname umount "$UMOUNT" /mnt # tmpfs
+ fi
+
+ fi
+
+ #
+ # Create the geli(8) GEOMS
+ #
+ if [ "$ZFSBOOT_GELI_ENCRYPTION" ]; then
+ # Prompt user for password (twice)
+ if ! msg_enter_new_password="$msg_geli_password" \
+ f_dialog_input_password
+ then
+ f_dprintf "$funcname: User cancelled"
+ f_show_err "$msg_user_cancelled"
+ return $FAILURE
+ fi
+
+ # Initialize geli(8) on each of the target partitions
+ for disk in $disks; do
+ f_dialog_info "$msg_geli_setup" \
+ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
+ if ! echo "$pw_password" | f_eval_catch \
+ $funcname geli "$GELI_PASSWORD_INIT" \
+ "$bootpool/boot/$disk$targetpart.eli" \
+ AES-XTS "$bootpool/$zroot_key" \
+ $disk$targetpart
+ then
+ f_interactive || f_die
+ unset pw_password # Sensitive info
+ return $FAILURE
+ fi
+ if ! echo "$pw_password" | f_eval_catch \
+ $funcname geli "$GELI_ATTACH" \
+ "$bootpool/$zroot_key" $disk$targetpart
+ then
+ f_interactive || f_die
+ unset pw_password # Sensitive info
+ return $FAILURE
+ fi
+ done
+ unset pw_password # Sensitive info
+
+ # Clean up
+ f_eval_catch $funcname zfs "$ZFS_UNMOUNT" "$bootpool_name" ||
+ return $FAILURE
+ f_eval_catch -d $funcname umount "$UMOUNT" /mnt # tmpfs
+ fi
+
+ #
+ # Create the gmirror(8) GEOMS for swap
+ #
+ if [ "$ZFSBOOT_SWAP_MIRROR" ]; then
+ for disk in $disks; do
+ swap_devs="$swap_devs $disk$swappart"
+ done
+ f_eval_catch $funcname gmirror "$SWAP_GMIRROR_LABEL" \
+ "$swap_devs" || return $FAILURE
+ fi
+
+ #
+ # Create the ZFS root pool with desired type and disk devices
+ #
+ f_dprintf "$funcname: Creating root pool..."
+ create_options="$ZFSBOOT_POOL_CREATE_OPTIONS"
+ f_eval_catch $funcname zpool "$ZPOOL_CREATE_WITH_OPTIONS" \
+ "-o altroot=$BSDINSTALL_CHROOT $create_options -m none -f" \
+ "$zroot_name" "$zroot_vdevtype" "$zroot_vdevs" ||
+ return $FAILURE
+
+ #
+ # Create ZFS dataset layout within the new root pool
+ #
+ f_dprintf "$funcname: Creating ZFS datasets..."
+ echo "$ZFSBOOT_DATASETS" | while read dataset options; do
+ # Skip blank lines and comments
+ case "$dataset" in "#"*|"") continue; esac
+ # Remove potential inline comments in options
+ options="${options%%#*}"
+ # Replace tabs with spaces
+ f_replaceall "$options" " " " " options
+ # Reduce contiguous runs of space to one single space
+ oldoptions=
+ while [ "$oldoptions" != "$options" ]; do
+ oldoptions="$options"
+ f_replaceall "$options" " " " " options
+ done
+ # Replace both commas and spaces with ` -o '
+ f_replaceall "$options" "[ ,]" " -o " options
+ # Create the dataset with desired options
+ f_eval_catch $funcname zfs "$ZFS_CREATE_WITH_OPTIONS" \
+ "${options:+-o $options}" "$zroot_name$dataset" ||
+ return $FAILURE
+ done
+
+ #
+ # Set a mountpoint for the root of the pool so newly created datasets
+ # have a mountpoint to inherit
+ #
+ f_dprintf "$funcname: Setting mountpoint for root of the pool..."
+ f_eval_catch $funcname zfs "$ZFS_SET" \
+ "mountpoint=/$zroot_name" "$zroot_name" ||
+ return $FAILURE
+
+ # Touch up permissions on the tmp directories
+ f_dprintf "$funcname: Modifying directory permissions..."
+ local dir
+ for dir in /tmp /var/tmp; do
+ f_eval_catch $funcname chmod "$CHMOD_MODE" 1777 \
+ $BSDINSTALL_CHROOT$dir || return $FAILURE
+ done
+
+ # Create symlink(s)
+ if [ "$ZFSBOOT_BOOT_POOL" ]; then
+ f_dprintf "$funcname: Creating /boot symlink for boot pool..."
+ f_eval_catch $funcname ln "$LN_SF" "$bootpool_name/boot" \
+ $BSDINSTALL_CHROOT/boot || return $FAILURE
+ fi
+
+ # Set bootfs property
+ local zroot_bootfs="$ZFSBOOT_BEROOT_NAME/$ZFSBOOT_BOOTFS_NAME"
+ f_dprintf "$funcname: Setting bootfs property..."
+ f_eval_catch $funcname zpool "$ZPOOL_SET" \
+ "bootfs=\"$zroot_name/$zroot_bootfs\"" "$zroot_name" ||
+ return $FAILURE
+
+ # Export the pool(s)
+ f_dprintf "$funcname: Temporarily exporting ZFS pool(s)..."
+ f_eval_catch $funcname zpool "$ZPOOL_EXPORT" "$zroot_name" ||
+ return $FAILURE
+ if [ "$ZFSBOOT_BOOT_POOL" ]; then
+ f_eval_catch $funcname zpool "$ZPOOL_EXPORT" \
+ "$bootpool_name" || return $FAILURE
+ fi
+
+ # MBR boot loader touch-up
+ if [ "$ZFSBOOT_PARTITION_SCHEME" = "MBR" ]; then
+ f_dprintf "$funcname: Updating MBR boot loader on disks..."
+ # Stick the ZFS boot loader in the "convienient hole" after
+ # the ZFS internal metadata
+ for disk in $disks; do
+ f_eval_catch $funcname dd "$DD_WITH_OPTIONS" \
+ /boot/zfsboot /dev/$disk$bootpart \
+ "skip=1 seek=1024" || return $FAILURE
+ done
+ fi
+
+ # Re-import the ZFS pool(s)
+ f_dprintf "$funcname: Re-importing ZFS pool(s)..."
+ f_eval_catch $funcname zpool "$ZPOOL_IMPORT_WITH_OPTIONS" \
+ "-o altroot=\"$BSDINSTALL_CHROOT\"" "$zroot_name" ||
+ return $FAILURE
+ if [ "$ZFSBOOT_BOOT_POOL" ]; then
+ f_eval_catch $funcname zpool "$ZPOOL_IMPORT_WITH_OPTIONS" \
+ "-o altroot=\"$BSDINSTALL_CHROOT\"" \
+ "$bootpool_name" || return $FAILURE
+ fi
+
+ # While this is apparently not needed, it seems to help MBR
+ f_dprintf "$funcname: Configuring zpool.cache for zroot..."
+ f_eval_catch $funcname mkdir "$MKDIR_P" $BSDINSTALL_CHROOT/boot/zfs ||
+ return $FAILURE
+ f_eval_catch $funcname zpool "$ZPOOL_SET" \
+ "cachefile=\"$BSDINSTALL_CHROOT/boot/zfs/zpool.cache\"" \
+ "$zroot_name" || return $FAILURE
+
+ # Last, but not least... required lines for rc.conf(5)/loader.conf(5)
+ # NOTE: We later concatenate these into their destination
+ f_dprintf "%s: Configuring rc.conf(5)/loader.conf(5) additions..." \
+ "$funcname"
+ f_eval_catch $funcname echo "$ECHO_APPEND" 'zfs_enable=\"YES\"' \
+ $BSDINSTALL_TMPETC/rc.conf.zfs || return $FAILURE
+ f_eval_catch $funcname echo "$ECHO_APPEND" \
+ 'kern.geom.label.disk_ident.enable=\"0\"' \
+ $BSDINSTALL_TMPBOOT/loader.conf.zfs || return $FAILURE
+ f_eval_catch $funcname echo "$ECHO_APPEND" \
+ 'kern.geom.label.gptid.enable=\"0\"' \
+ $BSDINSTALL_TMPBOOT/loader.conf.zfs || return $FAILURE
+
+ if [ "$ZFSBOOT_SWAP_MIRROR" ]; then
+ f_eval_catch $funcname echo "$ECHO_APPEND" \
+ 'geom_mirror_load=\"YES\"' \
+ $BSDINSTALL_TMPBOOT/loader.conf.gmirror ||
+ return $FAILURE
+ fi
+
+ # We're all done unless we should go on for boot pool
+ [ "$ZFSBOOT_BOOT_POOL" ] || return $SUCCESS
+
+ # Set cachefile for boot pool so it auto-imports at system start
+ f_dprintf "$funcname: Configuring zpool.cache for boot pool..."
+ f_eval_catch $funcname zpool "$ZPOOL_SET" \
+ "cachefile=\"$BSDINSTALL_CHROOT/boot/zfs/zpool.cache\"" \
+ "$bootpool_name" || return $FAILURE
+
+ # Some additional geli(8) requirements for loader.conf(5)
+ for option in \
+ 'zpool_cache_load=\"YES\"' \
+ 'zpool_cache_type=\"/boot/zfs/zpool.cache\"' \
+ 'zpool_cache_name=\"/boot/zfs/zpool.cache\"' \
+ ; do
+ f_eval_catch $funcname echo "$ECHO_APPEND" "$option" \
+ $BSDINSTALL_TMPBOOT/loader.conf.zfs ||
+ return $FAILURE
+ done
+ f_eval_catch $funcname printf "$PRINTF_CONF" vfs.root.mountfrom \
+ "\"zfs:$zroot_name/$zroot_bootfs\"" \
+ $BSDINSTALL_TMPBOOT/loader.conf.root || return $FAILURE
+
+ # We're all done unless we should go on to do encryption
+ [ "$ZFSBOOT_GELI_ENCRYPTION" ] || return $SUCCESS
+
+ #
+ # Configure geli(8)-based encryption
+ #
+ f_dprintf "$funcname: Configuring disk encryption..."
+ f_eval_catch $funcname echo "$ECHO_APPEND" 'aesni_load=\"YES\"' \
+ $BSDINSTALL_TMPBOOT/loader.conf.aesni || return $FAILURE
+ f_eval_catch $funcname echo "$ECHO_APPEND" 'geom_eli_load=\"YES\"' \
+ $BSDINSTALL_TMPBOOT/loader.conf.geli || return $FAILURE
+ f_eval_catch $funcname echo "$ECHO_APPEND" \
+ 'geom_eli_passphrase_prompt=\"YES\"' \
+ $BSDINSTALL_TMPBOOT/loader.conf.geli || return $FAILURE
+ for disk in $disks; do
+ f_eval_catch $funcname printf "$PRINTF_CONF" \
+ geli_%s_keyfile0_load "$disk$targetpart YES" \
+ $BSDINSTALL_TMPBOOT/loader.conf.$disk$targetpart ||
+ return $FAILURE
+ f_eval_catch $funcname printf "$PRINTF_CONF" \
+ geli_%s_keyfile0_type \
+ "$disk$targetpart $disk$targetpart:geli_keyfile0" \
+ $BSDINSTALL_TMPBOOT/loader.conf.$disk$targetpart ||
+ return $FAILURE
+ f_eval_catch $funcname printf "$PRINTF_CONF" \
+ geli_%s_keyfile0_name \
+ "$disk$targetpart \"$ZFSBOOT_GELI_KEY_FILE\"" \
+ $BSDINSTALL_TMPBOOT/loader.conf.$disk$targetpart ||
+ return $FAILURE
+ done
+
+ return $SUCCESS
+}
+
+# dialog_menu_diskinfo
+#
+# Prompt the user to select a disk and then provide detailed info on it.
+#
+dialog_menu_diskinfo()
+{
+ local device disk
+
+ #
+ # Break from loop when user cancels disk selection
+ #
+ while :; do
+ device=$( msg_cancel="$msg_back" f_device_menu \
+ "$DIALOG_TITLE" "$msg_select_a_disk_device" "" \
+ $DEVICE_TYPE_DISK 2>&1 ) || break
+ $device get name disk
+
+ # Show gpart(8) `show' and camcontrol(8) `inquiry' data
+ f_show_msg "$msg_detailed_disk_info" \
+ "$disk" "$( gpart show $disk 2> /dev/null )" \
+ "$disk" "$( camcontrol inquiry $disk 2> /dev/null )" \
+ "$disk" "$( camcontrol identify $disk 2> /dev/null )"
+ done
+
+ return $SUCCESS
+}
+
+############################################################ MAIN
+
+#
+# Initialize
+#
+f_dialog_title "$msg_zfs_configuration"
+f_dialog_backtitle "$msg_freebsd_installer"
+
+# User may have specifically requested ZFS-related operations be interactive
+! f_interactive && f_zfsinteractive && unset $VAR_NONINTERACTIVE
+
+#
+# Debugging
+#
+f_dprintf "BSDINSTALL_CHROOT=[%s]" "$BSDINSTALL_CHROOT"
+f_dprintf "BSDINSTALL_TMPETC=[%s]" "$BSDINSTALL_TMPETC"
+f_dprintf "FSTAB_FMT=[%s]" "$FSTAB_FMT"
+
+#
+# If the system was booted with UEFI, warn the user that FreeBSD can't do
+# ZFS with UEFI yet
+#
+if f_interactive; then
+ bootmethod=$( sysctl -n machdep.bootmethod )
+ f_dprintf "machdep.bootmethod=[%s]" "$bootmethod"
+ if [ "$bootmethod" != "BIOS" ]; then
+ dialog_uefi_prompt
+ retval=$?
+ f_dprintf "uefi_prompt=[%s]" "$retval"
+ [ $retval -eq $DIALOG_OK ] || f_die
+ fi
+fi
+
+#
+# Loop over the main menu until we've accomplished what we came here to do
+#
+while :; do
+ if ! f_interactive; then
+ retval=$DIALOG_OK
+ mtag=">>> $msg_install"
+ else
+ dialog_menu_main
+ retval=$?
+ f_dialog_menutag_fetch mtag
+ fi
+
+ f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
+ [ $retval -eq $DIALOG_OK ] || f_die
+
+ case "$mtag" in
+ ">>> $msg_install")
+ #
+ # First, validate the user's selections
+ #
+
+ # Make sure they gave us a name for the pool
+ if [ ! "$ZFSBOOT_POOL_NAME" ]; then
+ f_dprintf "Pool name cannot be empty."
+ f_show_err "$msg_pool_name_cannot_be_empty"
+ continue
+ fi
+
+ # Validate vdev type against number of disks selected/scripted
+ # (also validates that ZFSBOOT_DISKS are real [probed] disks)
+ # NB: dialog_menu_layout supports running non-interactively
+ dialog_menu_layout || continue
+
+ # Make sure each disk will be at least 50% ZFS
+ if f_expand_number "$ZFSBOOT_SWAP_SIZE" swapsize &&
+ f_expand_number "$ZFSBOOT_BOOT_POOL_SIZE" bootsize
+ then
+ minsize=$swapsize teeny_disks=
+ [ "$ZFSBOOT_BOOT_POOL" ] &&
+ minsize=$(( $minsize + $bootsize ))
+ for disk in $ZFSBOOT_DISKS; do
+ debug= f_device_find -1 \
+ $disk $DEVICE_TYPE_DISK device
+ $device get capacity disksize || continue
+ [ ${disksize:-0} -ge 0 ] || disksize=0
+ disksize=$(( $disksize - $minsize ))
+ [ $disksize -lt $minsize ] &&
+ teeny_disks="$teeny_disks $disk"
+ done
+ if [ "$teeny_disks" ]; then
+ f_dprintf "swapsize=[%s] bootsize[%s] %s" \
+ "$ZFSBOOT_SWAP_SIZE" \
+ "$ZFSBOOT_BOOT_POOL_SIZE" \
+ "minsize=[$minsize]"
+ f_dprintf "These disks are too small: %s" \
+ "$teeny_disks"
+ f_show_err "$msg_these_disks_are_too_small" \
+ "$ZFSBOOT_SWAP_SIZE" \
+ "$ZFSBOOT_BOOT_POOL_SIZE" \
+ "$teeny_disks"
+ continue
+ fi
+ fi
+
+ #
+ # Last Chance!
+ #
+ if f_interactive; then
+ dialog_last_chance $ZFSBOOT_DISKS || continue
+ fi
+
+ #
+ # Let's do this
+ #
+
+ vdev_type="$ZFSBOOT_VDEV_TYPE"
+
+ # Blank the vdev type for the default layout
+ [ "$vdev_type" = "stripe" ] && vdev_type=
+
+ zfs_create_boot "$ZFSBOOT_POOL_NAME" \
+ "$vdev_type" $ZFSBOOT_DISKS || continue
+
+ break # to success
+ ;;
+ ?" $msg_pool_type_disks")
+ ZFSBOOT_CONFIRM_LAYOUT=1
+ dialog_menu_layout
+ # User has poked settings, disable later confirmation
+ ZFSBOOT_CONFIRM_LAYOUT=
+ ;;
+ "- $msg_rescan_devices") f_device_rescan ;;
+ "- $msg_disk_info") dialog_menu_diskinfo ;;
+ ?" $msg_pool_name")
+ # Prompt the user to input/change the name for the new pool
+ f_dialog_input input \
+ "$msg_please_enter_a_name_for_your_zpool" \
+ "$ZFSBOOT_POOL_NAME" &&
+ ZFSBOOT_POOL_NAME="$input"
+ ;;
+ ?" $msg_force_4k_sectors")
+ # Toggle the variable referenced both by the menu and later
+ if [ "$ZFSBOOT_FORCE_4K_SECTORS" ]; then
+ ZFSBOOT_FORCE_4K_SECTORS=
+ else
+ ZFSBOOT_FORCE_4K_SECTORS=1
+ fi
+ ;;
+ ?" $msg_encrypt_disks")
+ # Toggle the variable referenced both by the menu and later
+ if [ "$ZFSBOOT_GELI_ENCRYPTION" ]; then
+ ZFSBOOT_GELI_ENCRYPTION=
+ else
+ ZFSBOOT_FORCE_4K_SECTORS=1
+ ZFSBOOT_GELI_ENCRYPTION=1
+ fi
+ ;;
+ ?" $msg_partition_scheme")
+ # Toggle between GPT and MBR
+ if [ "$ZFSBOOT_PARTITION_SCHEME" = GPT ]; then
+ ZFSBOOT_PARTITION_SCHEME=MBR
+ elif [ "$ZFSBOOT_PARTITION_SCHEME" = MBR ]; then
+ ZFSBOOT_PARTITION_SCHEME="GPT + Active"
+ elif [ "$ZFSBOOT_PARTITION_SCHEME" = "GPT + Active" ]; then
+ ZFSBOOT_PARTITION_SCHEME="GPT + Lenovo Fix"
+ else
+ ZFSBOOT_PARTITION_SCHEME=GPT
+ fi
+ ;;
+ ?" $msg_swap_size")
+ # Prompt the user to input/change the swap size for each disk
+ while :; do
+ f_dialog_input input \
+ "$msg_please_enter_amount_of_swap_space" \
+ "$ZFSBOOT_SWAP_SIZE" &&
+ ZFSBOOT_SWAP_SIZE="${input:-0}"
+ if f_expand_number "$ZFSBOOT_SWAP_SIZE" swapsize
+ then
+ if [ $swapsize -ne 0 -a $swapsize -lt 104857600 ]; then
+ f_show_err "$msg_swap_toosmall" \
+ "$ZFSBOOT_SWAP_SIZE"
+ continue;
+ else
+ break;
+ fi
+ else
+ f_show_err "$msg_swap_invalid" \
+ "$ZFSBOOT_SWAP_SIZE"
+ continue;
+ fi
+ done
+ ;;
+ ?" $msg_swap_mirror")
+ # Toggle the variable referenced both by the menu and later
+ if [ "$ZFSBOOT_SWAP_MIRROR" ]; then
+ ZFSBOOT_SWAP_MIRROR=
+ else
+ ZFSBOOT_SWAP_MIRROR=1
+ fi
+ ;;
+ ?" $msg_swap_encrypt")
+ # Toggle the variable referenced both by the menu and later
+ if [ "$ZFSBOOT_SWAP_ENCRYPTION" ]; then
+ ZFSBOOT_SWAP_ENCRYPTION=
+ else
+ ZFSBOOT_SWAP_ENCRYPTION=1
+ fi
+ ;;
+ esac
+done
+
+exit $SUCCESS
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/bsnmpd/Makefile b/usr.sbin/bsnmpd/Makefile
new file mode 100644
index 0000000..632753d
--- /dev/null
+++ b/usr.sbin/bsnmpd/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+SUBDIR= gensnmptree \
+ bsnmpd \
+ modules \
+ tools
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/bsnmpd/Makefile.inc b/usr.sbin/bsnmpd/Makefile.inc
new file mode 100644
index 0000000..265f86d
--- /dev/null
+++ b/usr.sbin/bsnmpd/Makefile.inc
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+.include "../Makefile.inc"
diff --git a/usr.sbin/bsnmpd/bsnmpd/Makefile b/usr.sbin/bsnmpd/bsnmpd/Makefile
new file mode 100644
index 0000000..a426345
--- /dev/null
+++ b/usr.sbin/bsnmpd/bsnmpd/Makefile
@@ -0,0 +1,51 @@
+# $FreeBSD$
+#
+# Author: Harti Brandt <harti@freebsd.org>
+
+.include <src.opts.mk>
+
+CONTRIB=${.CURDIR}/../../../contrib/bsnmp
+.PATH: ${CONTRIB}/snmpd
+
+PROG= bsnmpd
+SRCS= main.c action.c config.c export.c trap.c trans_udp.c trans_lsock.c
+SRCS+= oid.h tree.c tree.h
+XSYM= snmpMIB begemotSnmpdModuleTable begemotSnmpd begemotTrapSinkTable \
+ sysUpTime snmpTrapOID coldStart authenticationFailure \
+ begemotSnmpdTransUdp begemotSnmpdTransLsock begemotSnmpdLocalPortTable \
+ freeBSD freeBSDVersion
+CLEANFILES= oid.h tree.c tree.h
+MAN= bsnmpd.1 snmpmod.3
+NO_WERROR=
+
+FILESGROUPS= BMIBS DEFS
+
+BMIBS= FOKUS-MIB.txt BEGEMOT-MIB.txt BEGEMOT-SNMPD.txt
+BMIBSDIR= ${SHAREDIR}/snmp/mibs
+DEFS= tree.def
+DEFSDIR= ${SHAREDIR}/snmp/defs
+
+CFLAGS+= -DSNMPTREE_TYPES
+CFLAGS+= -I${CONTRIB}/lib -I${CONTRIB}/snmpd -I. -DUSE_LIBBEGEMOT
+CFLAGS+= -DUSE_TCPWRAPPERS -DQUADFMT='"llu"' -DQUADXFMT='"llx"'
+CFLAGS+= -DHAVE_STDINT_H -DHAVE_INTTYPES_H -DHAVE_ERR_H -DHAVE_STRLCPY
+LIBADD= begemot bsnmp wrap
+
+LDFLAGS= -Wl,-export-dynamic
+
+.if ${MK_OPENSSL} != "no"
+CFLAGS+= -DHAVE_LIBCRYPTO
+.endif
+
+oid.h: tree.def Makefile
+ gensnmptree -e ${XSYM} < ${.ALLSRC:M*.def} > ${.TARGET}
+
+.ORDER: tree.c tree.h
+tree.c tree.h: tree.def
+ gensnmptree -l < ${.ALLSRC}
+
+MANFILTER= sed -e 's%@MODPATH@%${LIBDIR}/%g' \
+ -e 's%@DEFPATH@%${DEFSDIR}/%g' \
+ -e 's%@MIBSPATH@%${BMIBSDIR}/%g'
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsnmpd/bsnmpd/Makefile.depend b/usr.sbin/bsnmpd/bsnmpd/Makefile.depend
new file mode 100644
index 0000000..3df74f7
--- /dev/null
+++ b/usr.sbin/bsnmpd/bsnmpd/Makefile.depend
@@ -0,0 +1,51 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbegemot \
+ lib/libbsnmp/libbsnmp \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libwrap \
+ secure/lib/libcrypto \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+action.o: oid.h
+action.o: tree.h
+action.po: oid.h
+action.po: tree.h
+config.o: tree.h
+config.po: tree.h
+export.o: tree.h
+export.po: tree.h
+main.o: oid.h
+main.o: tree.h
+main.po: oid.h
+main.po: tree.h
+trans_lsock.o: oid.h
+trans_lsock.o: tree.h
+trans_lsock.po: oid.h
+trans_lsock.po: tree.h
+trans_udp.o: oid.h
+trans_udp.o: tree.h
+trans_udp.po: oid.h
+trans_udp.po: tree.h
+trap.o: oid.h
+trap.o: tree.h
+trap.po: oid.h
+trap.po: tree.h
+tree.o: tree.c
+tree.o: tree.h
+tree.po: tree.c
+tree.po: tree.h
+.endif
diff --git a/usr.sbin/bsnmpd/gensnmptree/Makefile b/usr.sbin/bsnmpd/gensnmptree/Makefile
new file mode 100644
index 0000000..a92f4eb
--- /dev/null
+++ b/usr.sbin/bsnmpd/gensnmptree/Makefile
@@ -0,0 +1,15 @@
+# $FreeBSD$
+#
+# Author: Harti Brandt <harti@freebsd.org>
+
+CONTRIB=${.CURDIR}/../../../contrib/bsnmp
+.PATH: ${CONTRIB}/gensnmptree
+
+PROG= gensnmptree
+CFLAGS+= -I${CONTRIB}/lib
+CFLAGS+= -DQUADFMT='"llu"' -DQUADXFMT='"llx"' -DHAVE_STDINT_H
+CFLAGS+= -DHAVE_INTTYPES_H
+
+WARNS?= 5
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsnmpd/gensnmptree/Makefile.depend b/usr.sbin/bsnmpd/gensnmptree/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/bsnmpd/gensnmptree/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsnmpd/modules/Makefile b/usr.sbin/bsnmpd/modules/Makefile
new file mode 100644
index 0000000..18d3085
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/Makefile
@@ -0,0 +1,33 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+.PATH: ${.CURDIR}/../../../contrib/bsnmp/snmpd
+
+.if ${MK_ATM} != "no"
+_snmp_atm= snmp_atm
+.endif
+
+SUBDIR= ${_snmp_atm} \
+ snmp_bridge \
+ snmp_hast \
+ snmp_hostres \
+ snmp_lm75 \
+ snmp_mibII \
+ snmp_target \
+ snmp_usm \
+ snmp_vacm \
+ snmp_wlan
+
+.if ${MK_PF} != "no"
+SUBDIR+=snmp_pf
+.endif
+
+.if ${MK_NETGRAPH_SUPPORT} != "no"
+SUBDIR+=snmp_netgraph
+.endif
+
+INCS= snmpmod.h
+INCSDIR= ${INCLUDEDIR}/bsnmp
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsnmpd/modules/Makefile.depend b/usr.sbin/bsnmpd/modules/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsnmpd/modules/Makefile.inc b/usr.sbin/bsnmpd/modules/Makefile.inc
new file mode 100644
index 0000000..357b441
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/Makefile.inc
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+SHLIB_MAJOR= 6
+
+MANFILTER= sed -e 's%@MODPATH@%${LIBDIR}/%g' \
+ -e 's%@DEFPATH@%${DEFSDIR}/%g' \
+ -e 's%@MIBSPATH@%${BMIBSDIR}/%g'
+
+NO_WMISSING_VARIABLE_DECLARATIONS=
+
+.include "../Makefile.inc"
diff --git a/usr.sbin/bsnmpd/modules/snmp_atm/BEGEMOT-ATM-FREEBSD-MIB.txt b/usr.sbin/bsnmpd/modules/snmp_atm/BEGEMOT-ATM-FREEBSD-MIB.txt
new file mode 100644
index 0000000..83c4e5c
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_atm/BEGEMOT-ATM-FREEBSD-MIB.txt
@@ -0,0 +1,99 @@
+--
+-- 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 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$
+--
+-- Private Begemot MIB for ATM interfaces on FreeBSD
+--
+BEGEMOT-ATM-FREEBSD-MIB DEFINITIONS ::= BEGIN
+
+IMPORTS
+ MODULE-IDENTITY, OBJECT-TYPE
+ FROM SNMPv2-SMI
+ NgNodeIdOrZero
+ FROM BEGEMOT-NETGRAPH-MIB
+ begemotAtmSysGroup, begemotAtmIfEntry
+ FROM BEGEMOT-ATM-MIB;
+
+begemotAtmFreeBSDGroup MODULE-IDENTITY
+ LAST-UPDATED "200408060000Z"
+ ORGANIZATION "German Aerospace Centre"
+ CONTACT-INFO
+ " Hartmut Brandt
+
+ Postal: German Aerospace Centre (DLR)
+ Institute of Communications and Navigation
+ 82234 Wessling
+ Germany
+
+ Fax: +49 8153 28 2844
+
+ E-mail: harti@freebsd.org"
+ DESCRIPTION
+ "The FreeBSD specific Begemot MIB for ATM interfaces."
+
+ ::= { begemotAtmSysGroup 1 }
+
+-- Netgraph
+begemotAtmNgGroup OBJECT IDENTIFIER ::= { begemotAtmFreeBSDGroup 1 }
+
+--
+-- Interfaces table
+--
+begemotAtmNgIfTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF BegemotAtmNgIfEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "This table contains an entry for each hardware ATM
+ interface. The table is indexed by the interface index."
+ ::= { begemotAtmNgGroup 1 }
+
+begemotAtmNgIfEntry OBJECT-TYPE
+ SYNTAX BegemotAtmNgIfEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "This is a table entry describing one ATM hardware interface."
+ AUGMENTS { begemotAtmIfEntry }
+ ::= { begemotAtmNgIfTable 1 }
+
+BegemotAtmNgIfEntry ::= SEQUENCE {
+ begemotAtmNgIfNodeId NgNodeIdOrZero
+}
+
+begemotAtmNgIfNodeId OBJECT-TYPE
+ SYNTAX NgNodeIdOrZero
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The netgraph node id of the interface. If there is no
+ node corresponding to the interface, this is 0."
+ ::= { begemotAtmNgIfEntry 1 }
+
+END
diff --git a/usr.sbin/bsnmpd/modules/snmp_atm/Makefile b/usr.sbin/bsnmpd/modules/snmp_atm/Makefile
new file mode 100644
index 0000000..4bba7cb
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_atm/Makefile
@@ -0,0 +1,21 @@
+# $FreeBSD$
+#
+# Author: Harti Brandt <harti@freebsd.org>
+
+CONTRIB= ${.CURDIR}/../../../../contrib/ngatm
+.PATH: ${CONTRIB}/snmp_atm
+
+MOD= atm
+SRCS= snmp_atm.c atm_sys.c
+XSYM= begemotAtm
+MAN= snmp_atm.3
+
+BMIBS= BEGEMOT-ATM.txt BEGEMOT-ATM-FREEBSD-MIB.txt
+DEFS= ${MOD}_tree.def atm_freebsd.def
+INCS= snmp_${MOD}.h
+
+EXTRAMIBDEFS= atm_freebsd.def
+
+CFLAGS+= -I${CONTRIB}/snmp_atm
+
+.include <bsd.snmpmod.mk>
diff --git a/usr.sbin/bsnmpd/modules/snmp_atm/Makefile.depend b/usr.sbin/bsnmpd/modules/snmp_atm/Makefile.depend
new file mode 100644
index 0000000..aecafbc
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_atm/Makefile.depend
@@ -0,0 +1,34 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbsnmp/libbsnmp \
+ lib/libc \
+ lib/libcompiler_rt \
+ usr.sbin/bsnmpd/modules \
+ usr.sbin/bsnmpd/modules/snmp_mibII \
+ usr.sbin/bsnmpd/modules/snmp_netgraph \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+atm_sys.So: atm_oid.h
+atm_sys.So: atm_tree.h
+atm_sys.po: atm_oid.h
+atm_sys.po: atm_tree.h
+atm_tree.So: atm_tree.c
+atm_tree.So: atm_tree.h
+atm_tree.po: atm_tree.c
+atm_tree.po: atm_tree.h
+snmp_atm.So: atm_oid.h
+snmp_atm.So: atm_tree.h
+snmp_atm.po: atm_oid.h
+snmp_atm.po: atm_tree.h
+.endif
diff --git a/usr.sbin/bsnmpd/modules/snmp_atm/atm_freebsd.def b/usr.sbin/bsnmpd/modules/snmp_atm/atm_freebsd.def
new file mode 100644
index 0000000..6cc61d0
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_atm/atm_freebsd.def
@@ -0,0 +1,56 @@
+#
+# 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 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$
+#
+# SNMP module for ATM hardware interfaces.
+#
+# $Begemot: libunimsg/snmp_atm/atm_tree.def,v 1.2 2004/08/05 07:14:22 brandt Exp $
+#
+(1 internet
+ (4 private
+ (1 enterprises
+ (12325 fokus
+ (1 begemot
+ (101 begemotAtm
+ (1 begemotAtmObjects
+ (4 begemotAtmSysGroup
+ (1 begemotAtmFreeBSDGroup
+ (1 begemotAtmNgGroup
+ (1 begemotAtmNgIfTable
+ (1 begemotAtmNgIfEntry : INTEGER op_atmif_ng
+ (1 begemotAtmIfNodeId UNSIGNED32 GET)
+ ))
+ )
+ )
+ )
+ )
+ ))
+ )
+ )
+))
diff --git a/usr.sbin/bsnmpd/modules/snmp_atm/atm_sys.c b/usr.sbin/bsnmpd/modules/snmp_atm/atm_sys.c
new file mode 100644
index 0000000..525e805
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_atm/atm_sys.c
@@ -0,0 +1,301 @@
+/*
+ * 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 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$
+ *
+ * SNMP module for ATM hardware interfaces - FreeBSD/Ng specific part.
+ */
+
+#include "atm.h"
+#include "atm_tree.h"
+#include "atm_oid.h"
+
+#include <stdlib.h>
+#include <syslog.h>
+#include <string.h>
+
+#include <net/if_atm.h>
+
+#include <bsnmp/snmp_netgraph.h>
+#include <netgraph/ng_message.h>
+#include <netgraph/atm/ng_atm.h>
+
+static const struct hwinfo {
+ const char *device;
+ const char *vendor;
+} hwinfo[] = {
+ ATM_DEVICE_NAMES
+};
+
+struct atmif_sys {
+ ng_ID_t atm_node;
+ void *regc; /* cookie registration */
+};
+
+/*
+ * Find the interface for a given node
+ */
+struct atmif *
+atm_node2if(u_int node)
+{
+ struct atmif_priv *aif;
+
+ if (node != 0)
+ TAILQ_FOREACH(aif, &atmif_list, link)
+ if (aif->sys->atm_node == node)
+ return (&aif->pub);
+ return (NULL);
+}
+
+u_int
+atm_if2node(struct atmif *pub)
+{
+ struct atmif_priv *aif = (struct atmif_priv *)pub;
+
+ return (aif->sys->atm_node);
+}
+
+/*
+ * Destroy system dependend stuff.
+ */
+void
+atmif_sys_destroy(struct atmif_priv *aif)
+{
+
+ ng_unregister_cookie(aif->sys->regc);
+ free(aif->sys);
+ free(aif->pub.mib);
+}
+
+/*
+ * Handle a message from the ATM node
+ */
+static void
+handle_atm_message(const struct ng_mesg *mesg, const char *path __unused,
+ ng_ID_t node, void *uarg)
+{
+ struct atmif_priv *aif = uarg;
+ enum atmif_carrier_state ost;
+
+ switch (mesg->header.cmd) {
+
+ case NGM_ATM_IF_CHANGE:
+ {
+ const struct ngm_atm_if_change *arg;
+
+ ost = aif->pub.carrier;
+ if (mesg->header.arglen != sizeof(*arg)) {
+ syslog(LOG_ERR, "ATM_IF_CHANGE: wrong size");
+ atmif_check_carrier(aif);
+ return;
+ }
+ arg = (const struct ngm_atm_if_change *)
+ (const void *)mesg->data;
+
+ if (arg->carrier)
+ aif->pub.carrier = ATMIF_CARRIER_ON;
+ else
+ aif->pub.carrier = ATMIF_CARRIER_OFF;
+
+ if (ost != aif->pub.carrier)
+ atmif_send_notification(aif, ATMIF_NOTIFY_CARRIER,
+ (uintptr_t)ost);
+ return;
+ }
+
+ case NGM_ATM_VCC_CHANGE:
+ {
+ const struct ngm_atm_vcc_change *arg;
+
+ if (mesg->header.arglen != sizeof(*arg)) {
+ syslog(LOG_ERR, "ATM_VCC_CHANGE: wrong size");
+ return;
+ }
+ arg = (const struct ngm_atm_vcc_change *)
+ (const void *)mesg->data;
+ atmif_send_notification(aif, ATMIF_NOTIFY_VCC,
+ (uintptr_t)(((arg->vpi & 0xff) << 24) |
+ ((arg->vci & 0xffff) << 8) | (arg->state & 1)));
+ return;
+ }
+ }
+ syslog(LOG_WARNING, "spurious message %u from node [%x]",
+ mesg->header.cmd, node);
+}
+
+/*
+ * Attach to an ATM interface
+ */
+int
+atmif_sys_attach_if(struct atmif_priv *aif)
+{
+ struct ng_mesg *resp, *resp1;
+ struct namelist *list;
+ u_int i;
+
+ if ((aif->sys = malloc(sizeof(*aif->sys))) == NULL) {
+ syslog(LOG_CRIT, "out of memory");
+ return (-1);
+ }
+ memset(aif->sys, 0, sizeof(*aif->sys));
+
+ if ((aif->pub.mib = malloc(sizeof(*aif->pub.mib))) == NULL) {
+ free(aif->sys);
+ syslog(LOG_CRIT, "out of memory");
+ return (-1);
+ }
+
+ atmif_sys_fill_mib(aif);
+
+ /*
+ * Get ATM node Id. Must do it the hard way by scanning all nodes
+ * because the name may be wrong.
+ */
+ if ((resp = ng_dialog_id(snmp_node, NGM_GENERIC_COOKIE, NGM_LISTNODES,
+ NULL, 0)) == NULL) {
+ syslog(LOG_ERR, "cannot fetch node list: %m");
+ free(aif->sys);
+ return (-1);
+ }
+ list = (struct namelist *)(void *)resp->data;
+
+ for (i = 0; i < list->numnames; i++) {
+ if (strcmp(list->nodeinfo[i].type, NG_ATM_NODE_TYPE) != 0)
+ continue;
+ if ((resp1 = ng_dialog_id(list->nodeinfo[i].id,
+ NGM_ATM_COOKIE, NGM_ATM_GET_IFNAME, NULL, 0)) == NULL)
+ continue;
+ if (strcmp(resp1->data, aif->pub.ifp->name) == 0) {
+ free(resp1);
+ break;
+ }
+ free(resp1);
+ }
+ if (i == list->numnames)
+ aif->sys->atm_node = 0;
+ else
+ aif->sys->atm_node = list->nodeinfo[i].id;
+
+ free(resp);
+
+ if ((aif->sys->regc = ng_register_cookie(module, NGM_ATM_COOKIE,
+ aif->sys->atm_node, handle_atm_message, aif)) == NULL) {
+ syslog(LOG_ERR, "cannot register cookie: %m");
+ free(aif->sys);
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * Table of all ATM interfaces - Ng part
+ */
+int
+op_atmif_ng(struct snmp_context *ctx __unused, struct snmp_value *value,
+ u_int sub, u_int vindex __unused, enum snmp_op op)
+{
+ struct atmif_priv *aif;
+ int err;
+
+ if ((err = atmif_get_aif(value, sub, op, &aif)) != SNMP_ERR_NOERROR)
+ return (err);
+
+ if (op == SNMP_OP_SET) {
+ switch (value->var.subs[sub - 1]) {
+
+ default:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ }
+ }
+
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_begemotAtmIfNodeId:
+ value->v.uint32 = aif->sys->atm_node;
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+}
+
+/*
+ * Get vendor string
+ */
+int
+atm_sys_get_hw_vendor(struct atmif_priv *aif, struct snmp_value *value)
+{
+
+ if (aif->pub.mib->device >= sizeof(hwinfo) / sizeof(hwinfo[0]))
+ return (string_get(value, "unknown", -1));
+ return (string_get(value, hwinfo[aif->pub.mib->device].vendor, -1));
+}
+
+/*
+ * Get device string
+ */
+int
+atm_sys_get_hw_device(struct atmif_priv *aif, struct snmp_value *value)
+{
+
+ if (aif->pub.mib->device >= sizeof(hwinfo) / sizeof(hwinfo[0]))
+ return (string_get(value, "unknown", -1));
+ return (string_get(value, hwinfo[aif->pub.mib->device].device, -1));
+}
+
+/*
+ * Extract the ATM MIB from the interface's private MIB
+ */
+void
+atmif_sys_fill_mib(struct atmif_priv *aif)
+{
+ struct ifatm_mib *mib;
+
+ if (aif->pub.ifp->specmiblen != sizeof(struct ifatm_mib)) {
+ syslog(LOG_ERR, "atmif MIB has wrong size %zu",
+ aif->pub.ifp->specmiblen);
+ memset(aif->pub.mib, 0, sizeof(*aif->pub.mib));
+ aif->pub.mib->version = 0;
+ return;
+ }
+ mib = (struct ifatm_mib *)aif->pub.ifp->specmib;
+
+ aif->pub.mib->device = mib->device;
+ aif->pub.mib->serial = mib->serial;
+ aif->pub.mib->hw_version = mib->hw_version;
+ aif->pub.mib->sw_version = mib->sw_version;
+ aif->pub.mib->media = mib->media;
+
+ memcpy(aif->pub.mib->esi, mib->esi, 6);
+ aif->pub.mib->pcr = mib->pcr;
+ aif->pub.mib->vpi_bits = mib->vpi_bits;
+ aif->pub.mib->vci_bits = mib->vci_bits;
+ aif->pub.mib->max_vpcs = mib->max_vpcs;
+ aif->pub.mib->max_vccs = mib->max_vccs;
+}
diff --git a/usr.sbin/bsnmpd/modules/snmp_bridge/BEGEMOT-BRIDGE-MIB.txt b/usr.sbin/bsnmpd/modules/snmp_bridge/BEGEMOT-BRIDGE-MIB.txt
new file mode 100644
index 0000000..d55ea3c
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_bridge/BEGEMOT-BRIDGE-MIB.txt
@@ -0,0 +1,1166 @@
+--
+-- Copyright (C) 2006 Shteryana Shopova <syrinx@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 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$
+--
+
+BEGEMOT-BRIDGE-MIB DEFINITIONS ::= BEGIN
+
+IMPORTS
+ MODULE-IDENTITY, OBJECT-TYPE, NOTIFICATION-TYPE,
+ Counter32, Integer32, TimeTicks, mib-2
+ FROM SNMPv2-SMI
+ TEXTUAL-CONVENTION, MacAddress, TruthValue, RowStatus
+ FROM SNMPv2-TC
+ BridgeId, Timeout
+ FROM BRIDGE-MIB
+ InterfaceIndex FROM IF-MIB
+ begemot
+ FROM BEGEMOT-MIB;
+
+begemotBridge MODULE-IDENTITY
+ LAST-UPDATED "200708060000Z"
+ ORGANIZATION "Sofia University St. Kliment Ohridski"
+ CONTACT-INFO
+ " Shteryana Shopova
+
+ Postal: Faculty of Mathematics and Informatics
+ 5 James Bourchier Blvd.
+ 1164 Sofia
+ Bulgaria
+
+ Fax: +359 2 687 180
+
+ E-Mail: syrinx@FreeBSD.org"
+ DESCRIPTION
+ "The Begemot MIB for managing bridge interfaces."
+ REVISION "200708060000Z"
+ DESCRIPTION
+ "Third revision adds begemotBridgeBasePortPrivate
+ object."
+ REVISION "200611210000Z"
+ DESCRIPTION
+ "Second revision adds support for monitoring RSTP
+ specific variables."
+ REVISION "200607270000Z"
+ DESCRIPTION
+ "Initial revision."
+ ::= { begemot 205 }
+
+-- ---------------------------------------------------------- --
+BridgeIfName ::= TEXTUAL-CONVENTION
+ DISPLAY-HINT "16a"
+ STATUS current
+ DESCRIPTION
+ "Name of a bridge interface."
+ SYNTAX OCTET STRING (SIZE(1..16))
+
+BridgeIfNameOrEmpty ::= TEXTUAL-CONVENTION
+ DISPLAY-HINT "16a"
+ STATUS current
+ DESCRIPTION
+ "Name of a bridge interface."
+ SYNTAX OCTET STRING (SIZE(0..16))
+
+BridgePortId ::= TEXTUAL-CONVENTION
+ DISPLAY-HINT "1x.1x"
+ STATUS current
+ DESCRIPTION
+ "A port identifier that contains a bridge port's STP priority
+ in the first octet and the port number in the second octet."
+ SYNTAX OCTET STRING (SIZE(2))
+
+-- ---------------------------------------------------------- --
+-- subtrees in the Begemot Bridge MIB
+-- ---------------------------------------------------------- --
+begemotBridgeNotifications OBJECT IDENTIFIER ::= { begemotBridge 0 }
+
+begemotBridgeBase OBJECT IDENTIFIER ::= { begemotBridge 1 }
+
+begemotBridgeStp OBJECT IDENTIFIER ::= { begemotBridge 2 }
+
+begemotBridgeTp OBJECT IDENTIFIER ::= { begemotBridge 3 }
+
+begemotBridgePf OBJECT IDENTIFIER ::= { begemotBridge 4 }
+
+begemotBridgeConfigObjects OBJECT IDENTIFIER ::= { begemotBridge 5 }
+
+-- ---------------------------------------------------------- --
+-- the base Bridge interface table
+-- ---------------------------------------------------------- --
+
+begemotBridgeBaseTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF BegemotBridgeBaseEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains generic information for each
+ bridge interface on the managed device."
+ ::= { begemotBridgeBase 1 }
+
+begemotBridgeBaseEntry OBJECT-TYPE
+ SYNTAX BegemotBridgeBaseEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A list of information for the bridge interfaces on
+ the managed device."
+ INDEX { begemotBridgeBaseName }
+ ::= { begemotBridgeBaseTable 1 }
+
+BegemotBridgeBaseEntry ::= SEQUENCE {
+ begemotBridgeBaseName BridgeIfName,
+ begemotBridgeBaseAddress MacAddress,
+ begemotBridgeBaseNumPorts Integer32,
+ begemotBridgeBaseType INTEGER,
+ begemotBridgeBaseStatus RowStatus
+}
+
+begemotBridgeBaseName OBJECT-TYPE
+ SYNTAX BridgeIfName
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The name of the bridge interface for which this
+ entry contains management information."
+ ::= { begemotBridgeBaseEntry 1 }
+
+begemotBridgeBaseAddress OBJECT-TYPE
+ SYNTAX MacAddress
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The MAC address of the bridge interface."
+ ::= { begemotBridgeBaseEntry 2 }
+
+begemotBridgeBaseNumPorts OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of ports, members of this bridge."
+ ::= { begemotBridgeBaseEntry 3 }
+
+begemotBridgeBaseType OBJECT-TYPE
+ SYNTAX INTEGER {
+ unknown(1),
+ transparent-only(2),
+ sourceroute-only(3),
+ srt(4)
+ }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Indicates what type of bridging this bridge can
+ perform."
+ ::= { begemotBridgeBaseEntry 4 }
+
+begemotBridgeBaseStatus OBJECT-TYPE
+ SYNTAX RowStatus
+ MAX-ACCESS read-create
+ STATUS current
+ DESCRIPTION
+ "Used to create/destroy bridge interfaces on the
+ managed device."
+ ::= { begemotBridgeBaseEntry 5 }
+
+-- ---------------------------------------------------------- --
+-- the base Bridge ports table
+-- ---------------------------------------------------------- --
+
+begemotBridgeBasePortTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF BegemotBridgeBasePortEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table containing generic information about ports,
+ members of each bridge interface."
+ ::= { begemotBridgeBase 2 }
+
+begemotBridgeBasePortEntry OBJECT-TYPE
+ SYNTAX BegemotBridgeBasePortEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A list of information about a specific port, member of
+ a bridge interface."
+ INDEX { begemotBridgeBaseName, begemotBridgeBasePortIfIndex }
+ ::= { begemotBridgeBasePortTable 1 }
+
+BegemotBridgeBasePortEntry ::= SEQUENCE {
+ begemotBridgeBasePort Integer32,
+ begemotBridgeBasePortIfIndex InterfaceIndex,
+ begemotBridgeBaseSpanEnabled INTEGER,
+ begemotBridgeBasePortDelayExceededDiscards Counter32,
+ begemotBridgeBasePortMtuExceededDiscards Counter32,
+ begemotBridgeBasePortStatus RowStatus,
+ begemotBridgeBasePortPrivate TruthValue
+}
+
+begemotBridgeBasePort OBJECT-TYPE
+ SYNTAX Integer32 (1..65535)
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The system interface index of the interface corresponding
+ to this port."
+ ::= { begemotBridgeBasePortEntry 1 }
+
+begemotBridgeBasePortIfIndex OBJECT-TYPE
+ SYNTAX InterfaceIndex
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The value of the instance of the ifIndex object,
+ defined in IF-MIB, for the interface corresponding
+ to this port."
+ ::= { begemotBridgeBasePortEntry 2 }
+
+begemotBridgeBaseSpanEnabled OBJECT-TYPE
+ SYNTAX INTEGER {
+ enabled(1),
+ disabled(2)
+ }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this objects reflects whether the port
+ is a span port on the specified bridge interface."
+ ::= { begemotBridgeBasePortEntry 3 }
+
+begemotBridgeBasePortDelayExceededDiscards OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames discarded by this port due
+ to excessive transit delay through the bridge."
+ ::= { begemotBridgeBasePortEntry 4 }
+
+begemotBridgeBasePortMtuExceededDiscards OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames discarded by this port due
+ to an excessive size."
+ ::= { begemotBridgeBasePortEntry 5 }
+
+begemotBridgeBasePortStatus OBJECT-TYPE
+ SYNTAX RowStatus
+ MAX-ACCESS read-create
+ STATUS current
+ DESCRIPTION
+ "Used to control addition of member ports to or
+ removal of member ports from a specified bridge."
+ ::= { begemotBridgeBasePortEntry 6 }
+
+begemotBridgeBasePortPrivate OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this objects reflects whether the port
+ has a PRIVATE flag set. A port with this flags set
+ can only communicate with ports not having the
+ PRIVATE flag set."
+ ::= { begemotBridgeBasePortEntry 7 }
+
+-- ---------------------------------------------------------- --
+-- the Bridge interface STP table
+-- ---------------------------------------------------------- --
+
+begemotBridgeStpTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF BegemotBridgeStpEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains Spanning Tree Protocol information
+ for each bridge interface on the managed device."
+ ::= { begemotBridgeStp 1 }
+
+begemotBridgeStpEntry OBJECT-TYPE
+ SYNTAX BegemotBridgeStpEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A list of information about the Spanning Tree Protocol
+ operation on a bridge interface."
+ AUGMENTS { begemotBridgeBaseEntry }
+ ::= { begemotBridgeStpTable 1 }
+
+BegemotBridgeStpEntry ::= SEQUENCE {
+ begemotBridgeStpProtocolSpecification INTEGER,
+ begemotBridgeStpPriority Integer32,
+ begemotBridgeStpTimeSinceTopologyChange TimeTicks,
+ begemotBridgeStpTopChanges Counter32,
+ begemotBridgeStpDesignatedRoot BridgeId,
+ begemotBridgeStpRootCost Integer32,
+ begemotBridgeStpRootPort Integer32,
+ begemotBridgeStpMaxAge Timeout,
+ begemotBridgeStpHelloTime Timeout,
+ begemotBridgeStpHoldTime Integer32,
+ begemotBridgeStpForwardDelay Timeout,
+ begemotBridgeStpBridgeMaxAge Timeout,
+ begemotBridgeStpBridgeHelloTime Timeout,
+ begemotBridgeStpBridgeForwardDelay Timeout,
+ begemotBridgeStpVersion INTEGER,
+ begemotBridgeStpTxHoldCount Integer32
+}
+
+begemotBridgeStpProtocolSpecification OBJECT-TYPE
+ SYNTAX INTEGER {
+ unknown(1),
+ decLb100(2),
+ ieee8021d(3)
+ }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The Spanning Tree Protocol version being run on the
+ bridge interface. The value 'decLb100(2)' indicates the
+ DEC LANbridge 100 Spanning Tree protocol, 'ieee8021d(3)'
+ indicates the bridge is running IEEE 802.1D STP
+ implementation."
+ ::= { begemotBridgeStpEntry 1 }
+
+begemotBridgeStpPriority OBJECT-TYPE
+ SYNTAX Integer32 (0..65535)
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The priority value of the bridge interface forming the
+ first two octets of the bridge identifier. Acceptable
+ values are 0-61440, in steps of 4096."
+ ::= { begemotBridgeStpEntry 2 }
+
+begemotBridgeStpTimeSinceTopologyChange OBJECT-TYPE
+ SYNTAX TimeTicks
+ UNITS "centi-seconds"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The time (in hundreds of a second) since a topology change
+ was last detected by this bridge."
+ ::= { begemotBridgeStpEntry 3 }
+
+begemotBridgeStpTopChanges OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of times a topology change was detected by the
+ bridge interface since the management entity was initialized
+ or reset."
+ ::= { begemotBridgeStpEntry 4 }
+
+begemotBridgeStpDesignatedRoot OBJECT-TYPE
+ SYNTAX BridgeId
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The bridge identifier of the root of the spanning tree as
+ calculated by the Spanning Tree Protocol."
+ ::= { begemotBridgeStpEntry 5 }
+
+begemotBridgeStpRootCost OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The cost of the path from this bridge to the root bridge."
+ ::= { begemotBridgeStpEntry 6 }
+
+begemotBridgeStpRootPort OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The port number of the port that offers the lowest
+ cost path from this bridge to the root bridge of
+ the spanning tree. If this bridge is the root bridge,
+ this object shall have a value of zero."
+ ::= { begemotBridgeStpEntry 7 }
+
+begemotBridgeStpMaxAge OBJECT-TYPE
+ SYNTAX Timeout
+ UNITS "centi-seconds"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The maximum age of Spanning Tree Protocol information
+ received from the network on any port, before that
+ information is discarded. This is the actual value that
+ the bridge is currently using."
+ ::= { begemotBridgeStpEntry 8 }
+
+begemotBridgeStpHelloTime OBJECT-TYPE
+ SYNTAX Timeout
+ UNITS "centi-seconds"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The amount of time between transmission of
+ Configuration BPDUs by this bridge on any port,
+ when it is the root of the spanning tree or is
+ trying to become so. This is the actual value that
+ this bridge is currently using."
+ ::= { begemotBridgeStpEntry 9 }
+
+begemotBridgeStpHoldTime OBJECT-TYPE
+ SYNTAX Integer32
+ UNITS "centi-seconds"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "This time value determines the interval length
+ during which no more than two Configuration BPDUs
+ shall be transmitted by this node, in units of
+ hundredths of a second."
+ ::= { begemotBridgeStpEntry 10 }
+
+begemotBridgeStpForwardDelay OBJECT-TYPE
+ SYNTAX Timeout
+ UNITS "centi-seconds"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "This value, measured in units of hundredths of a second
+ determines how long a port will stay consecutively in the
+ Listening and Learning states before transitioning to
+ Forwarding state.
+ This is the actual value currently used by the bridge
+ as opposed to begemotBridgeStpBridgeForwardDelay, which
+ is the value this and all bridges participating in the
+ spanning tree were to use, if this was the root bridge."
+ ::= { begemotBridgeStpEntry 11 }
+
+begemotBridgeStpBridgeMaxAge OBJECT-TYPE
+ SYNTAX Timeout (600..4000)
+ UNITS "centi-seconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value that all bridges participating in the
+ spanning tree would use for MaxAge if this bridge
+ was the root of the spanning tree."
+ ::= { begemotBridgeStpEntry 12 }
+
+begemotBridgeStpBridgeHelloTime OBJECT-TYPE
+ SYNTAX Timeout (100..1000)
+ UNITS "centi-seconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value that all bridges participating in the
+ spanning tree would use for HelloTime if this
+ bridge was the root of the spanning tree."
+ ::= { begemotBridgeStpEntry 13 }
+
+begemotBridgeStpBridgeForwardDelay OBJECT-TYPE
+ SYNTAX Timeout (400..3000)
+ UNITS "centi-seconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value that all bridges participating in the
+ spanning tree would use for ForwardDelay if this
+ bridge was the root of the spanning tree."
+ ::= { begemotBridgeStpEntry 14 }
+
+begemotBridgeStpVersion OBJECT-TYPE
+ SYNTAX INTEGER {
+ stpCompatible(0),
+ rstp(2)
+ }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The version of Spanning Tree Protocol the bridge is
+ currently running. The value 'stpCompatible(0)'
+ indicates the Spanning Tree Protocol specified in
+ IEEE 802.1D-1998 and 'rstp(2)' indicates the Rapid
+ Spanning Tree Protocol specified in IEEE 802.1w and
+ clause 17 of 802.1D-2004. The values are directly from
+ the IEEE standard. New values may be defined as future
+ versions of the protocol become available.
+
+ The value of this object MUST be retained across
+ reinitializations of the management system."
+ DEFVAL { rstp }
+ ::= { begemotBridgeStpEntry 15 }
+
+begemotBridgeStpTxHoldCount OBJECT-TYPE
+ SYNTAX Integer32 (1..10)
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value used by the Port Transmit state machine to limit
+ the maximum transmission rate of BPDUs on the bridge interface.
+
+ The value of this object MUST be retained across
+ reinitializations of the management system."
+ DEFVAL { 3 }
+ ::= { begemotBridgeStpEntry 16 }
+
+-- ---------------------------------------------------------- --
+-- the Bridge STP ports table
+-- ---------------------------------------------------------- --
+
+begemotBridgeStpPortTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF BegemotBridgeStpPortEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table containing Spanning Tree Protocol information
+ about the members of each bridge interface."
+ ::= { begemotBridgeStp 2 }
+
+begemotBridgeStpPortEntry OBJECT-TYPE
+ SYNTAX BegemotBridgeStpPortEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A list of Spanning Tree Protocol information about
+ a specific member of a bridge interface."
+ INDEX { begemotBridgeBaseName, begemotBridgeBasePortIfIndex }
+ ::= { begemotBridgeStpPortTable 1 }
+
+BegemotBridgeStpPortEntry ::= SEQUENCE {
+ begemotBridgeStpPort Integer32,
+ begemotBridgeStpPortPriority Integer32,
+ begemotBridgeStpPortState INTEGER,
+ begemotBridgeStpPortEnable INTEGER,
+ begemotBridgeStpPortPathCost Integer32,
+ begemotBridgeStpPortDesignatedRoot BridgeId,
+ begemotBridgeStpPortDesignatedCost Integer32,
+ begemotBridgeStpPortDesignatedBridge BridgeId,
+ begemotBridgeStpPortDesignatedPort BridgePortId,
+ begemotBridgeStpPortForwardTransitions Counter32
+}
+
+begemotBridgeStpPort OBJECT-TYPE
+ SYNTAX Integer32 (1..65535)
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The system interface index of the interface corresponding
+ to this port, for which the management entity has Spanning
+ Tree Protocol information."
+ ::= { begemotBridgeStpPortEntry 1 }
+
+begemotBridgeStpPortPriority OBJECT-TYPE
+ SYNTAX Integer32 (0..255)
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The STP priority of this port that is contained in the first
+ octet of its Port Identifier. The second octet contains the
+ value of begemotBridgeStpPort."
+ ::= { begemotBridgeStpPortEntry 2 }
+
+begemotBridgeStpPortState OBJECT-TYPE
+ SYNTAX INTEGER {
+ disabled(1),
+ blocking(2),
+ listening(3),
+ learning(4),
+ forwarding(5),
+ broken(6)
+ }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The current state of the port as defined by the operation
+ of the Spanning Tree Protocol. If the Spanning Tree Protocol
+ is administratively disabled on the port, this object shall
+ have value disabled(1). A value of broken(6) does not correspond
+ to any legal state of a port, and if present should indicate
+ error in the operation of either the Spanning Tree Protocol
+ implementation running on the device or the management entity."
+ ::= { begemotBridgeStpPortEntry 3 }
+
+begemotBridgeStpPortEnable OBJECT-TYPE
+ SYNTAX INTEGER {
+ enabled(1),
+ disabled(2)
+ }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The administrative Spanning Tree Protocol state of the
+ port - value of enabled(1) indicates that the port is
+ participating in the Spanning Tree Protocol operation."
+ ::= { begemotBridgeStpPortEntry 4 }
+
+begemotBridgeStpPortPathCost OBJECT-TYPE
+ SYNTAX Integer32 (1..65535)
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The contribution of the path through this port, when the port
+ is the Root Port, to the total cost of the path to the root
+ bridge for this bridge."
+ ::= { begemotBridgeStpPortEntry 5 }
+
+begemotBridgeStpPortDesignatedRoot OBJECT-TYPE
+ SYNTAX BridgeId
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The unique Bridge Identifier of the bridge recorded as the
+ root in the Root Identifier parameter of Configuration BPDUs
+ transmitted by the Designated Bridge for the LAN to which
+ the port is attached."
+ ::= { begemotBridgeStpPortEntry 6 }
+
+begemotBridgeStpPortDesignatedCost OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "For a Designated port, the path cost (equal to the Root
+ Path Cost of the bridge) offered to the LAN to which the
+ port is attached otherwise the cost of the path to the Root
+ offered by the Designated Port on the LAN to which this
+ Port is attached."
+ ::= { begemotBridgeStpPortEntry 7 }
+
+begemotBridgeStpPortDesignatedBridge OBJECT-TYPE
+ SYNTAX BridgeId
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The unique Bridge Identifier of the bridge to which the
+ port belongs, in the case when the port is a designated
+ port, otherwise the bridge believed to be the Designated
+ Bridge for the LAN to which this port is attached."
+ ::= { begemotBridgeStpPortEntry 8 }
+
+begemotBridgeStpPortDesignatedPort OBJECT-TYPE
+ SYNTAX BridgePortId
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The Port Identifier of the Bridge port, on the Designated
+ Bridge, through which the Designated Bridge transmits the
+ Configuration Message information stored by this port."
+ ::= { begemotBridgeStpPortEntry 9 }
+
+begemotBridgeStpPortForwardTransitions OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of times this port has transitioned
+ from the Learning state to the Forwarding state."
+ ::= { begemotBridgeStpPortEntry 10 }
+
+-- ---------------------------------------------------------- --
+-- the Bridge STP extended ports table
+-- ---------------------------------------------------------- --
+
+begemotBridgeStpExtPortTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF BegemotBridgeStpExtPortEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains port-specific Rapid Spanning Tree
+ information for the bridge interface members."
+ ::= { begemotBridgeStp 3 }
+
+begemotBridgeStpExtPortEntry OBJECT-TYPE
+ SYNTAX BegemotBridgeStpExtPortEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A list of Rapid Spanning Tree information maintained by
+ each bridge interface member."
+ AUGMENTS { begemotBridgeStpPortEntry }
+ ::= { begemotBridgeStpExtPortTable 1 }
+
+BegemotBridgeStpExtPortEntry ::= SEQUENCE {
+ begemotBridgeStpPortProtocolMigration TruthValue,
+ begemotBridgeStpPortAdminEdgePort TruthValue,
+ begemotBridgeStpPortOperEdgePort TruthValue,
+ begemotBridgeStpPortAdminPointToPoint INTEGER,
+ begemotBridgeStpPortOperPointToPoint TruthValue,
+ begemotBridgeStpPortAdminPathCost Integer32
+}
+
+begemotBridgeStpPortProtocolMigration OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "When operating in RSTP (version 2) mode, writing true(1)
+ to this object forces this port to transmit RSTP BPDUs.
+ Any other operation on this object has no effect and
+ it always returns false(2) when read."
+ ::= { begemotBridgeStpExtPortEntry 1 }
+
+begemotBridgeStpPortAdminEdgePort OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The administrative value of the Edge Port parameter. A
+ value of true(1) indicates that this port should be
+ assumed as an edge-port, and a value of false(2) indicates
+ that this port should be assumed as a non-edge-port.
+ Setting this object will also cause the corresponding
+ instance of begemotBridgeStpPortOperEdgePort to change to
+ the same value. Note that even when this object's value
+ is true, the value of the corresponding instance of
+ begemotBridgeStpPortOperEdgePort can be false if a BPDU
+ has been received.
+
+ The value of this object MUST be retained across
+ reinitializations of the management system."
+ ::= { begemotBridgeStpExtPortEntry 2 }
+
+begemotBridgeStpPortOperEdgePort OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The operational value of the Edge Port parameter. The
+ object is initialized to the value of the corresponding
+ instance of begemotBridgeStpPortAdminEdgePort. When the
+ corresponding instance of begemotBridgeStpPortAdminEdgePort
+ is set, this object will be changed as well. This object
+ will also be changed to false on reception of a BPDU."
+ ::= { begemotBridgeStpExtPortEntry 3 }
+
+begemotBridgeStpPortAdminPointToPoint OBJECT-TYPE
+ SYNTAX INTEGER {
+ forceTrue(0),
+ forceFalse(1),
+ auto(2)
+ }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The administrative point-to-point status of the LAN segment
+ attached to this port, using the enumeration values of the
+ IEEE 802.1w clause. A value of forceTrue(0) indicates
+ that this port should always be treated as if it is
+ connected to a point-to-point link. A value of
+ forceFalse(1) indicates that this port should be treated as
+ having a shared media connection. A value of auto(2)
+ indicates that this port is considered to have a
+ point-to-point link if it is an Aggregator and all of its
+ members are aggregatable, or if the MAC entity
+ is configured for full duplex operation, either through
+ auto-negotiation or by management means. Manipulating this
+ object changes the underlying adminPortToPortMAC.
+
+ The value of this object MUST be retained across
+ reinitializations of the management system."
+ ::= { begemotBridgeStpExtPortEntry 4 }
+
+begemotBridgeStpPortOperPointToPoint OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The operational point-to-point status of the LAN segment
+ attached to this port. It indicates whether a port is
+ considered to have a point-to-point connection.
+ If adminPointToPointMAC is set to auto(2), then the value
+ of operPointToPointMAC is determined in accordance with the
+ specific procedures defined for the MAC entity concerned,
+ as defined in IEEE 802.1w, clause 6.5. The value is
+ determined dynamically; that is, it is re-evaluated whenever
+ the value of adminPointToPointMAC changes, and whenever
+ the specific procedures defined for the MAC entity evaluates
+ a change in its point-to-point status."
+ ::= { begemotBridgeStpExtPortEntry 5 }
+
+begemotBridgeStpPortAdminPathCost OBJECT-TYPE
+ SYNTAX Integer32 (0..200000000)
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The administratively assigned value for the contribution
+ of this port to the path cost of paths toward the spanning
+ tree root.
+
+ Writing a value of '0' assigns the automatically calculated
+ default Path Cost value to the port. If the default Path
+ Cost is being used, this object returns '0' when read.
+
+ This complements the object begemotBridgeStpPortPathCost or
+ begemotBridgeStpPortPathCost32, which returns the operational
+ value of the path cost.
+
+ The value of this object MUST be retained across
+ reinitializations of the management system."
+ ::= { begemotBridgeStpExtPortEntry 6 }
+
+-- ---------------------------------------------------------- --
+-- the Bridge interface Transparent bridging table
+-- ---------------------------------------------------------- --
+
+begemotBridgeTpTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF BegemotBridgeTpEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains information regarding transparent
+ bridging for each bridge interface on the managed device."
+ ::= { begemotBridgeTp 1 }
+
+begemotBridgeTpEntry OBJECT-TYPE
+ SYNTAX BegemotBridgeTpEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A list of information regarding transparent bridging
+ on a bridge interface."
+ AUGMENTS { begemotBridgeBaseEntry }
+ ::= { begemotBridgeTpTable 1 }
+
+BegemotBridgeTpEntry ::= SEQUENCE {
+ begemotBridgeTpLearnedEntryDiscards Counter32,
+ begemotBridgeTpAgingTime Integer32,
+ begemotBridgeTpMaxAddresses Integer32
+}
+
+begemotBridgeTpLearnedEntryDiscards OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The total number of Forwarding Database entries that would
+ have been learnt, but have been discarded due to Forwarding
+ Address Table having reached it's maximum entries limit."
+ ::= { begemotBridgeTpEntry 1 }
+
+begemotBridgeTpAgingTime OBJECT-TYPE
+ SYNTAX Integer32 (10..1000000)
+ UNITS "seconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The timeout period in seconds before aging out
+ dynamically learnt forwarding entries."
+ ::= { begemotBridgeTpEntry 2 }
+
+begemotBridgeTpMaxAddresses OBJECT-TYPE
+ SYNTAX Integer32 (1..10000)
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The maximum number of entires that this bridge can
+ learn in it's Forwarding Address Table and use for
+ making forwarding decisions."
+ ::= { begemotBridgeTpEntry 3 }
+
+-- ---------------------------------------------------------- --
+-- The Forwarding Database for Transparent Bridging interfaces
+-- ---------------------------------------------------------- --
+
+begemotBridgeTpFdbTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF BegemotBridgeTpFdbEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains information about unicast entries
+ for which the bridge interfaces have forwarding and/or
+ filtering information. This information is used by the
+ bridge interfaces to make forwarding decisions."
+ ::= { begemotBridgeTp 2 }
+
+begemotBridgeTpFdbEntry OBJECT-TYPE
+ SYNTAX BegemotBridgeTpFdbEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Information about a specific unicast MAC address
+ for which the bridge interface has some forwarding
+ and/or filtering information."
+ INDEX { begemotBridgeBaseName, begemotBridgeTpFdbAddress }
+ ::= { begemotBridgeTpFdbTable 1 }
+
+BegemotBridgeTpFdbEntry ::= SEQUENCE {
+ begemotBridgeTpFdbAddress MacAddress,
+ begemotBridgeTpFdbPort Integer32,
+ begemotBridgeTpFdbStatus INTEGER
+}
+
+begemotBridgeTpFdbAddress OBJECT-TYPE
+ SYNTAX MacAddress
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "A unicast MAC address for which the bridge has which the
+ bridge interface has some forwarding and/or filtering
+ information."
+ ::= { begemotBridgeTpFdbEntry 1 }
+
+begemotBridgeTpFdbPort OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The port number of the bridge port on which a frame having
+ a source address equal to the value of the corresponding
+ instance of begemotBridgeTpFdbAddress has been seen."
+ ::= { begemotBridgeTpFdbEntry 2 }
+
+begemotBridgeTpFdbStatus OBJECT-TYPE
+ SYNTAX INTEGER {
+ other(1),
+ invalid(2),
+ learned(3),
+ self(4),
+ mgmt(5)
+ }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The status of this entry. The meanings of the
+ values are:
+ other(1) - none of the following.
+ invalid(2) - this entry is no longer valid (e.g.,
+ it was learned but has since aged out), but has
+ not yet been flushed from the table.
+ learned(3) - the value of the corresponding instance
+ of begemotBridgeTpFdbPort was learned, and is being
+ used.
+ self(4) - the value of the corresponding instance of
+ begemotBridgeTpFdbAddress represents one of the
+ bridge's addresses. The corresponding instance of
+ begemotBridgeTpFdbPort indicates which of the bridge's
+ ports has this address.
+ mgmt(5) - the value of the corresponding instance of
+ begemotBridgeTpFdbAddress has been added to the
+ bridge's Forwarding Database by some management
+ means."
+ ::= { begemotBridgeTpFdbEntry 3 }
+
+-- ---------------------------------------------------------- --
+-- Ports table for Transparent Bridging interfaces
+-- ---------------------------------------------------------- --
+
+begemotBridgeTpPortTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF BegemotBridgeTpPortEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains information about every bridge port,
+ member of a bridge interface, associated with the transparent
+ bridging function of the bridge."
+ ::= { begemotBridgeTp 3 }
+
+begemotBridgeTpPortEntry OBJECT-TYPE
+ SYNTAX BegemotBridgeTpPortEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A list of information about every bridge port, member of a
+ bridge interface, associated with the bridge's transparent
+ bridging function."
+ INDEX { begemotBridgeBaseName, begemotBridgeBasePortIfIndex }
+ ::= { begemotBridgeTpPortTable 1 }
+
+BegemotBridgeTpPortEntry ::= SEQUENCE {
+ begemotBridgeTpPort Integer32,
+ begemotBridgeTpPortMaxInfo Integer32,
+ begemotBridgeTpPortInFrames Counter32,
+ begemotBridgeTpPortOutFrames Counter32,
+ begemotBridgeTpPortInDiscards Counter32
+}
+
+begemotBridgeTpPort OBJECT-TYPE
+ SYNTAX Integer32 (1..65535)
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The system interface index of the port for which this entry
+ contains Transparent bridging management information."
+ ::= { begemotBridgeTpPortEntry 1 }
+
+begemotBridgeTpPortMaxInfo OBJECT-TYPE
+ SYNTAX Integer32
+ UNITS "bytes"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The maximum size of the INFO (non-MAC) field that this port
+ will receive or transmit."
+ ::= { begemotBridgeTpPortEntry 2 }
+
+begemotBridgeTpPortInFrames OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames that have been received by this
+ port from its segment. Note that a frame received on the
+ interface corresponding to this port is only counted by
+ this object if and only if it is for a protocol being
+ processed by the local bridging function, including
+ bridge management frames."
+ ::= { begemotBridgeTpPortEntry 3 }
+
+begemotBridgeTpPortOutFrames OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames that have been transmitted by this
+ port to its segment. Note that a frame transmitted on
+ the interface corresponding to this port is only counted
+ by this object if and only if it is for a protocol being
+ processed by the local bridging function, including
+ bridge management frames."
+ ::= { begemotBridgeTpPortEntry 4 }
+
+begemotBridgeTpPortInDiscards OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Count of received valid frames that were discarded
+ (i.e., filtered) by the Forwarding Process."
+ ::= { begemotBridgeTpPortEntry 5 }
+
+-- ---------------------------------------------------------- --
+-- the begemotBridgePf objects
+-- ---------------------------------------------------------- --
+
+begemotBridgePfilStatus OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "Indicates whether packet filtering by some firewall
+ package is enabled on the bridge interface."
+ ::= { begemotBridgePf 1 }
+
+begemotBridgePfilMembers OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "A value of true(1) indicates that packet filtering is
+ enabled on both incoming and outgoing bridge member
+ interfaces."
+ ::= { begemotBridgePf 2 }
+
+begemotBridgePfilIpOnly OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "This value controls the handling of non-IP packets which
+ are not passed on for further processing to a firewall
+ package. A value of false(0) indicates that all non-IP
+ Ethernet frames are passed unconditionally."
+ ::= { begemotBridgePf 3 }
+
+begemotBridgeLayer2PfStatus OBJECT-TYPE
+ SYNTAX INTEGER {
+ enabled(1),
+ disabled(2)
+ }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "This value indicates whether layer2 filtering by a
+ firewall package is enabled for bridge interfaces."
+ ::= { begemotBridgePf 4 }
+
+-- ---------------------------------------------------------- --
+-- the begemotBridgeConfigObjects objects
+-- ---------------------------------------------------------- --
+
+begemotBridgeDefaultBridgeIf OBJECT-TYPE
+
+ SYNTAX BridgeIfNameOrEmpty
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The name of the bridge interface that will be managed
+ via objects in IETF BRIDGE-MIB (RFC4188). If the
+ object's value is set to an empty string, bridge interfaces
+ will only be managed via objects in this MIB module."
+ DEFVAL { "bridge0" }
+ ::= { begemotBridgeConfigObjects 1 }
+
+begemotBridgeDataUpdate OBJECT-TYPE
+
+ SYNTAX Timeout (1..300)
+ UNITS "seconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The maximum age in seconds of the cached data."
+ DEFVAL { 10 }
+ ::= { begemotBridgeConfigObjects 2 }
+
+begemotBridgeDataPoll OBJECT-TYPE
+
+ SYNTAX Timeout (1..3600)
+ UNITS "seconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The polling rate of data when the module is idle."
+ DEFVAL { 300 }
+ ::= { begemotBridgeConfigObjects 3 }
+
+-- ---------------------------------------------------------- --
+-- Notifications for the Spanning Tree Protocol
+-- ---------------------------------------------------------- --
+
+begemotBridgeNewRoot NOTIFICATION-TYPE
+ OBJECTS { begemotBridgeBaseName }
+ STATUS current
+ DESCRIPTION
+ "The begemotBridgeNewRoot trap indicates that one of the
+ bridge interfaces on the sending agent's device has
+ become the new root of the spanning tree topology it is
+ participating in."
+ ::= { begemotBridgeNotifications 1 }
+
+begemotBridgeTopologyChange NOTIFICATION-TYPE
+ OBJECTS { begemotBridgeBaseName }
+ STATUS current
+ DESCRIPTION
+ "A begemotBridgeTopologyChange trap is send when a member
+ port on one of the bridge interfaces, monitored by the agent,
+ transitions from the Learning state to the Forwarding state,
+ or from the Forwarding state to the Blocking state. The trap
+ is not sent if a begemotBridgeNewRoot trap is sent for the
+ same transition."
+ ::= { begemotBridgeNotifications 2 }
+
+END
diff --git a/usr.sbin/bsnmpd/modules/snmp_bridge/BRIDGE-MIB.txt b/usr.sbin/bsnmpd/modules/snmp_bridge/BRIDGE-MIB.txt
new file mode 100644
index 0000000..9f87b65
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_bridge/BRIDGE-MIB.txt
@@ -0,0 +1,1483 @@
+--
+-- Copyright (C) The Internet Society (2005).
+--
+-- This document is subject to the rights, licenses and restrictions
+-- contained in BCP 78, and except as set forth therein, the authors
+-- retain all their rights.
+--
+-- This document and the information contained herein are provided on an
+-- "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS
+-- OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET
+-- ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED,
+-- INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE
+-- INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED
+-- WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+--
+-- $FreeBSD$
+--
+
+BRIDGE-MIB DEFINITIONS ::= BEGIN
+
+-- ---------------------------------------------------------- --
+-- MIB for IEEE 802.1D devices
+-- ---------------------------------------------------------- --
+IMPORTS
+ MODULE-IDENTITY, OBJECT-TYPE, NOTIFICATION-TYPE,
+ Counter32, Integer32, TimeTicks, mib-2
+ FROM SNMPv2-SMI
+ TEXTUAL-CONVENTION, MacAddress
+ FROM SNMPv2-TC
+ MODULE-COMPLIANCE, OBJECT-GROUP, NOTIFICATION-GROUP
+ FROM SNMPv2-CONF
+ InterfaceIndex FROM IF-MIB
+ ;
+
+dot1dBridge MODULE-IDENTITY
+ LAST-UPDATED "200509190000Z"
+ ORGANIZATION "IETF Bridge MIB Working Group"
+ CONTACT-INFO
+ "Email: bridge-mib@ietf.org
+
+ K.C. Norseth (Editor)
+ L-3 Communications
+ Tel: +1 801-594-2809
+ Email: kenyon.c.norseth@L-3com.com
+ Postal: 640 N. 2200 West.
+ Salt Lake City, Utah 84116-0850
+ Les Bell (Editor)
+ 3Com Europe Limited
+ Phone: +44 1442 438025
+ Email: elbell@ntlworld.com
+ Postal: 3Com Centre, Boundary Way
+ Hemel Hempstead
+ Herts. HP2 7YU
+ UK
+
+ Send comments to <bridge-mib@ietf.org>"
+ DESCRIPTION
+ "The Bridge MIB module for managing devices that support
+ IEEE 802.1D.
+
+ Copyright (C) The Internet Society (2005). This version of
+ this MIB module is part of RFC 4188; see the RFC itself for
+ full legal notices."
+ REVISION "200509190000Z"
+ DESCRIPTION
+ "Third revision, published as part of RFC 4188.
+
+ The MIB module has been converted to SMIv2 format.
+ Conformance statements have been added and some
+ description and reference clauses have been updated.
+
+ The object dot1dStpPortPathCost32 was added to
+ support IEEE 802.1t and the permissible values of
+ dot1dStpPriority and dot1dStpPortPriority have been
+ clarified for bridges supporting IEEE 802.1t or
+ IEEE 802.1w.
+
+ The interpretation of dot1dStpTimeSinceTopologyChange
+ has been clarified for bridges supporting the Rapid
+ Spanning Tree Protocol (RSTP)."
+ REVISION "199307310000Z"
+ DESCRIPTION
+ "Second revision, published as part of RFC 1493."
+ REVISION "199112310000Z"
+ DESCRIPTION
+ "Initial revision, published as part of RFC 1286."
+ ::= { mib-2 17 }
+
+
+-- ---------------------------------------------------------- --
+-- Textual Conventions
+-- ---------------------------------------------------------- --
+
+BridgeId ::= TEXTUAL-CONVENTION
+ STATUS current
+ DESCRIPTION
+ "The Bridge-Identifier, as used in the Spanning Tree
+ Protocol, to uniquely identify a bridge. Its first two
+ octets (in network byte order) contain a priority value,
+ and its last 6 octets contain the MAC address used to
+ refer to a bridge in a unique fashion (typically, the
+ numerically smallest MAC address of all ports on the
+ bridge)."
+ SYNTAX OCTET STRING (SIZE (8))
+
+Timeout ::= TEXTUAL-CONVENTION
+ DISPLAY-HINT "d"
+ STATUS current
+ DESCRIPTION
+ "A Spanning Tree Protocol (STP) timer in units of 1/100
+ seconds. Several objects in this MIB module represent
+ values of timers used by the Spanning Tree Protocol.
+ In this MIB, these timers have values in units of
+ hundredths of a second (i.e., 1/100 secs).
+
+ These timers, when stored in a Spanning Tree Protocol's
+ BPDU, are in units of 1/256 seconds. Note, however, that
+ 802.1D-1998 specifies a settable granularity of no more
+ than one second for these timers. To avoid ambiguity,
+ a conversion algorithm is defined below for converting
+ between the different units, which ensures a timer's
+ value is not distorted by multiple conversions.
+
+ To convert a Timeout value into a value in units of
+ 1/256 seconds, the following algorithm should be used:
+
+ b = floor( (n * 256) / 100)
+
+ where:
+ floor = quotient [ignore remainder]
+ n is the value in 1/100 second units
+ b is the value in 1/256 second units
+
+ To convert the value from 1/256 second units back to
+ 1/100 seconds, the following algorithm should be used:
+
+ n = ceiling( (b * 100) / 256)
+
+ where:
+ ceiling = quotient [if remainder is 0], or
+ quotient + 1 [if remainder is nonzero]
+ n is the value in 1/100 second units
+ b is the value in 1/256 second units
+
+ Note: it is important that the arithmetic operations are
+ done in the order specified (i.e., multiply first,
+ divide second)."
+ SYNTAX Integer32
+
+-- ---------------------------------------------------------- --
+-- subtrees in the Bridge MIB
+-- ---------------------------------------------------------- --
+
+dot1dNotifications OBJECT IDENTIFIER ::= { dot1dBridge 0 }
+
+dot1dBase OBJECT IDENTIFIER ::= { dot1dBridge 1 }
+dot1dStp OBJECT IDENTIFIER ::= { dot1dBridge 2 }
+
+dot1dSr OBJECT IDENTIFIER ::= { dot1dBridge 3 }
+-- documented in RFC 1525
+
+dot1dTp OBJECT IDENTIFIER ::= { dot1dBridge 4 }
+dot1dStatic OBJECT IDENTIFIER ::= { dot1dBridge 5 }
+
+-- Subtrees used by Bridge MIB Extensions:
+-- pBridgeMIB MODULE-IDENTITY ::= { dot1dBridge 6 }
+-- qBridgeMIB MODULE-IDENTITY ::= { dot1dBridge 7 }
+-- Note that the practice of registering related MIB modules
+-- below dot1dBridge has been discouraged since there is no
+-- robust mechanism to track such registrations.
+
+dot1dConformance OBJECT IDENTIFIER ::= { dot1dBridge 8 }
+
+-- ---------------------------------------------------------- --
+-- the dot1dBase subtree
+-- ---------------------------------------------------------- --
+-- Implementation of the dot1dBase subtree is mandatory for all
+-- bridges.
+-- ---------------------------------------------------------- --
+
+dot1dBaseBridgeAddress OBJECT-TYPE
+
+ SYNTAX MacAddress
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The MAC address used by this bridge when it must be
+ referred to in a unique fashion. It is recommended
+ that this be the numerically smallest MAC address of
+ all ports that belong to this bridge. However, it is only
+ required to be unique. When concatenated with
+ dot1dStpPriority, a unique BridgeIdentifier is formed,
+ which is used in the Spanning Tree Protocol."
+ REFERENCE
+ "IEEE 802.1D-1998: clauses 14.4.1.1.3 and 7.12.5"
+ ::= { dot1dBase 1 }
+
+dot1dBaseNumPorts OBJECT-TYPE
+ SYNTAX Integer32
+ UNITS "ports"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of ports controlled by this bridging
+ entity."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 14.4.1.1.3"
+ ::= { dot1dBase 2 }
+
+dot1dBaseType OBJECT-TYPE
+ SYNTAX INTEGER {
+ unknown(1),
+ transparent-only(2),
+ sourceroute-only(3),
+ srt(4)
+ }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Indicates what type of bridging this bridge can
+ perform. If a bridge is actually performing a
+ certain type of bridging, this will be indicated by
+ entries in the port table for the given type."
+ ::= { dot1dBase 3 }
+
+-- ---------------------------------------------------------- --
+-- The Generic Bridge Port Table
+-- ---------------------------------------------------------- --
+dot1dBasePortTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF Dot1dBasePortEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains generic information about every
+ port that is associated with this bridge. Transparent,
+ source-route, and srt ports are included."
+ ::= { dot1dBase 4 }
+
+dot1dBasePortEntry OBJECT-TYPE
+ SYNTAX Dot1dBasePortEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+
+ DESCRIPTION
+ "A list of information for each port of the bridge."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 14.4.2, 14.6.1"
+ INDEX { dot1dBasePort }
+ ::= { dot1dBasePortTable 1 }
+
+Dot1dBasePortEntry ::=
+ SEQUENCE {
+ dot1dBasePort
+ Integer32,
+ dot1dBasePortIfIndex
+ InterfaceIndex,
+ dot1dBasePortCircuit
+ OBJECT IDENTIFIER,
+ dot1dBasePortDelayExceededDiscards
+ Counter32,
+ dot1dBasePortMtuExceededDiscards
+ Counter32
+ }
+
+dot1dBasePort OBJECT-TYPE
+ SYNTAX Integer32 (1..65535)
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The port number of the port for which this entry
+ contains bridge management information."
+ ::= { dot1dBasePortEntry 1 }
+
+dot1dBasePortIfIndex OBJECT-TYPE
+ SYNTAX InterfaceIndex
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The value of the instance of the ifIndex object,
+ defined in IF-MIB, for the interface corresponding
+ to this port."
+ ::= { dot1dBasePortEntry 2 }
+
+dot1dBasePortCircuit OBJECT-TYPE
+ SYNTAX OBJECT IDENTIFIER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "For a port that (potentially) has the same value of
+ dot1dBasePortIfIndex as another port on the same bridge.
+ This object contains the name of an object instance
+ unique to this port. For example, in the case where
+ multiple ports correspond one-to-one with multiple X.25
+ virtual circuits, this value might identify an (e.g.,
+ the first) object instance associated with the X.25
+ virtual circuit corresponding to this port.
+
+ For a port which has a unique value of
+ dot1dBasePortIfIndex, this object can have the value
+ { 0 0 }."
+ ::= { dot1dBasePortEntry 3 }
+
+dot1dBasePortDelayExceededDiscards OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames discarded by this port due
+ to excessive transit delay through the bridge. It
+ is incremented by both transparent and source
+ route bridges."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 14.6.1.1.3"
+ ::= { dot1dBasePortEntry 4 }
+
+dot1dBasePortMtuExceededDiscards OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames discarded by this port due
+ to an excessive size. It is incremented by both
+ transparent and source route bridges."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 14.6.1.1.3"
+ ::= { dot1dBasePortEntry 5 }
+
+-- ---------------------------------------------------------- --
+-- the dot1dStp subtree
+-- ---------------------------------------------------------- --
+-- Implementation of the dot1dStp subtree is optional. It is
+-- implemented by those bridges that support the Spanning Tree
+-- Protocol.
+-- ---------------------------------------------------------- --
+dot1dStpProtocolSpecification OBJECT-TYPE
+ SYNTAX INTEGER {
+ unknown(1),
+ decLb100(2),
+ ieee8021d(3)
+ }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "An indication of what version of the Spanning Tree
+ Protocol is being run. The value 'decLb100(2)'
+ indicates the DEC LANbridge 100 Spanning Tree protocol.
+ IEEE 802.1D implementations will return 'ieee8021d(3)'.
+ If future versions of the IEEE Spanning Tree Protocol
+ that are incompatible with the current version
+ are released a new value will be defined."
+ ::= { dot1dStp 1 }
+
+dot1dStpPriority OBJECT-TYPE
+ SYNTAX Integer32 (0..65535)
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of the write-able portion of the Bridge ID
+ (i.e., the first two octets of the (8 octet long) Bridge
+ ID). The other (last) 6 octets of the Bridge ID are
+ given by the value of dot1dBaseBridgeAddress.
+ On bridges supporting IEEE 802.1t or IEEE 802.1w,
+ permissible values are 0-61440, in steps of 4096."
+ REFERENCE
+ "IEEE 802.1D-1998 clause 8.10.2, Table 8-4,
+ IEEE 802.1t clause 8.10.2, Table 8-4, clause 14.3."
+ ::= { dot1dStp 2 }
+
+dot1dStpTimeSinceTopologyChange OBJECT-TYPE
+ SYNTAX TimeTicks
+ UNITS "centi-seconds"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The time (in hundredths of a second) since the
+ last time a topology change was detected by the
+ bridge entity.
+ For RSTP, this reports the time since the tcWhile
+ timer for any port on this Bridge was nonzero."
+ REFERENCE
+ "IEEE 802.1D-1998 clause 14.8.1.1.,
+ IEEE 802.1w clause 14.8.1.1."
+ ::= { dot1dStp 3 }
+
+dot1dStpTopChanges OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The total number of topology changes detected by
+ this bridge since the management entity was last
+ reset or initialized."
+ REFERENCE
+ "IEEE 802.1D-1998 clause 14.8.1.1."
+ ::= { dot1dStp 4 }
+
+dot1dStpDesignatedRoot OBJECT-TYPE
+ SYNTAX BridgeId
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The bridge identifier of the root of the spanning
+ tree, as determined by the Spanning Tree Protocol,
+ as executed by this node. This value is used as
+ the Root Identifier parameter in all Configuration
+ Bridge PDUs originated by this node."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 8.5.3.1"
+ ::= { dot1dStp 5 }
+
+dot1dStpRootCost OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The cost of the path to the root as seen from
+ this bridge."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 8.5.3.2"
+ ::= { dot1dStp 6 }
+
+dot1dStpRootPort OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The port number of the port that offers the lowest
+ cost path from this bridge to the root bridge."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 8.5.3.3"
+ ::= { dot1dStp 7 }
+
+dot1dStpMaxAge OBJECT-TYPE
+ SYNTAX Timeout
+ UNITS "centi-seconds"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The maximum age of Spanning Tree Protocol information
+ learned from the network on any port before it is
+ discarded, in units of hundredths of a second. This is
+ the actual value that this bridge is currently using."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 8.5.3.4"
+ ::= { dot1dStp 8 }
+
+dot1dStpHelloTime OBJECT-TYPE
+ SYNTAX Timeout
+ UNITS "centi-seconds"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The amount of time between the transmission of
+ Configuration bridge PDUs by this node on any port when
+ it is the root of the spanning tree, or trying to become
+ so, in units of hundredths of a second. This is the
+ actual value that this bridge is currently using."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 8.5.3.5"
+ ::= { dot1dStp 9 }
+
+dot1dStpHoldTime OBJECT-TYPE
+ SYNTAX Integer32
+ UNITS "centi-seconds"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "This time value determines the interval length
+ during which no more than two Configuration bridge
+ PDUs shall be transmitted by this node, in units
+ of hundredths of a second."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 8.5.3.14"
+ ::= { dot1dStp 10 }
+
+dot1dStpForwardDelay OBJECT-TYPE
+ SYNTAX Timeout
+ UNITS "centi-seconds"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "This time value, measured in units of hundredths of a
+ second, controls how fast a port changes its spanning
+ state when moving towards the Forwarding state. The
+ value determines how long the port stays in each of the
+ Listening and Learning states, which precede the
+ Forwarding state. This value is also used when a
+ topology change has been detected and is underway, to
+ age all dynamic entries in the Forwarding Database.
+ [Note that this value is the one that this bridge is
+ currently using, in contrast to
+ dot1dStpBridgeForwardDelay, which is the value that this
+ bridge and all others would start using if/when this
+ bridge were to become the root.]"
+ REFERENCE
+ "IEEE 802.1D-1998: clause 8.5.3.6"
+ ::= { dot1dStp 11 }
+
+dot1dStpBridgeMaxAge OBJECT-TYPE
+ SYNTAX Timeout (600..4000)
+ UNITS "centi-seconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value that all bridges use for MaxAge when this
+ bridge is acting as the root. Note that 802.1D-1998
+ specifies that the range for this parameter is related
+ to the value of dot1dStpBridgeHelloTime. The
+ granularity of this timer is specified by 802.1D-1998 to
+ be 1 second. An agent may return a badValue error if a
+ set is attempted to a value that is not a whole number
+ of seconds."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 8.5.3.8"
+ ::= { dot1dStp 12 }
+
+dot1dStpBridgeHelloTime OBJECT-TYPE
+ SYNTAX Timeout (100..1000)
+ UNITS "centi-seconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value that all bridges use for HelloTime when this
+ bridge is acting as the root. The granularity of this
+ timer is specified by 802.1D-1998 to be 1 second. An
+ agent may return a badValue error if a set is attempted
+ to a value that is not a whole number of seconds."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 8.5.3.9"
+ ::= { dot1dStp 13 }
+
+dot1dStpBridgeForwardDelay OBJECT-TYPE
+ SYNTAX Timeout (400..3000)
+ UNITS "centi-seconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value that all bridges use for ForwardDelay when
+ this bridge is acting as the root. Note that
+ 802.1D-1998 specifies that the range for this parameter
+ is related to the value of dot1dStpBridgeMaxAge. The
+ granularity of this timer is specified by 802.1D-1998 to
+ be 1 second. An agent may return a badValue error if a
+ set is attempted to a value that is not a whole number
+ of seconds."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 8.5.3.10"
+ ::= { dot1dStp 14 }
+
+-- ---------------------------------------------------------- --
+-- The Spanning Tree Port Table
+-- ---------------------------------------------------------- --
+
+dot1dStpPortTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF Dot1dStpPortEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains port-specific information
+ for the Spanning Tree Protocol."
+ ::= { dot1dStp 15 }
+
+dot1dStpPortEntry OBJECT-TYPE
+ SYNTAX Dot1dStpPortEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A list of information maintained by every port about
+ the Spanning Tree Protocol state for that port."
+ INDEX { dot1dStpPort }
+ ::= { dot1dStpPortTable 1 }
+
+Dot1dStpPortEntry ::=
+ SEQUENCE {
+ dot1dStpPort
+ Integer32,
+ dot1dStpPortPriority
+ Integer32,
+ dot1dStpPortState
+ INTEGER,
+ dot1dStpPortEnable
+ INTEGER,
+ dot1dStpPortPathCost
+ Integer32,
+ dot1dStpPortDesignatedRoot
+ BridgeId,
+ dot1dStpPortDesignatedCost
+ Integer32,
+ dot1dStpPortDesignatedBridge
+ BridgeId,
+ dot1dStpPortDesignatedPort
+ OCTET STRING,
+ dot1dStpPortForwardTransitions
+ Counter32,
+ dot1dStpPortPathCost32
+ Integer32
+ }
+
+dot1dStpPort OBJECT-TYPE
+ SYNTAX Integer32 (1..65535)
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The port number of the port for which this entry
+ contains Spanning Tree Protocol management information."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 14.8.2.1.2"
+ ::= { dot1dStpPortEntry 1 }
+
+dot1dStpPortPriority OBJECT-TYPE
+ SYNTAX Integer32 (0..255)
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of the priority field that is contained in
+ the first (in network byte order) octet of the (2 octet
+ long) Port ID. The other octet of the Port ID is given
+ by the value of dot1dStpPort.
+ On bridges supporting IEEE 802.1t or IEEE 802.1w,
+ permissible values are 0-240, in steps of 16."
+ REFERENCE
+ "IEEE 802.1D-1998 clause 8.10.2, Table 8-4,
+ IEEE 802.1t clause 8.10.2, Table 8-4, clause 14.3."
+ ::= { dot1dStpPortEntry 2 }
+
+dot1dStpPortState OBJECT-TYPE
+ SYNTAX INTEGER {
+ disabled(1),
+ blocking(2),
+ listening(3),
+ learning(4),
+ forwarding(5),
+ broken(6)
+ }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The port's current state, as defined by application of
+ the Spanning Tree Protocol. This state controls what
+ action a port takes on reception of a frame. If the
+ bridge has detected a port that is malfunctioning, it
+ will place that port into the broken(6) state. For
+ ports that are disabled (see dot1dStpPortEnable), this
+ object will have a value of disabled(1)."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 8.5.5.2"
+ ::= { dot1dStpPortEntry 3 }
+
+dot1dStpPortEnable OBJECT-TYPE
+ SYNTAX INTEGER {
+ enabled(1),
+ disabled(2)
+ }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The enabled/disabled status of the port."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 8.5.5.2"
+ ::= { dot1dStpPortEntry 4 }
+
+dot1dStpPortPathCost OBJECT-TYPE
+ SYNTAX Integer32 (1..65535)
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The contribution of this port to the path cost of
+ paths towards the spanning tree root which include
+ this port. 802.1D-1998 recommends that the default
+ value of this parameter be in inverse proportion to
+ the speed of the attached LAN.
+
+ New implementations should support dot1dStpPortPathCost32.
+ If the port path costs exceeds the maximum value of this
+ object then this object should report the maximum value,
+ namely 65535. Applications should try to read the
+ dot1dStpPortPathCost32 object if this object reports
+ the maximum value."
+ REFERENCE "IEEE 802.1D-1998: clause 8.5.5.3"
+ ::= { dot1dStpPortEntry 5 }
+
+dot1dStpPortDesignatedRoot OBJECT-TYPE
+ SYNTAX BridgeId
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The unique Bridge Identifier of the Bridge
+ recorded as the Root in the Configuration BPDUs
+ transmitted by the Designated Bridge for the
+ segment to which the port is attached."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 8.5.5.4"
+ ::= { dot1dStpPortEntry 6 }
+
+dot1dStpPortDesignatedCost OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The path cost of the Designated Port of the segment
+ connected to this port. This value is compared to the
+ Root Path Cost field in received bridge PDUs."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 8.5.5.5"
+ ::= { dot1dStpPortEntry 7 }
+
+dot1dStpPortDesignatedBridge OBJECT-TYPE
+ SYNTAX BridgeId
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The Bridge Identifier of the bridge that this
+ port considers to be the Designated Bridge for
+ this port's segment."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 8.5.5.6"
+ ::= { dot1dStpPortEntry 8 }
+
+dot1dStpPortDesignatedPort OBJECT-TYPE
+ SYNTAX OCTET STRING (SIZE (2))
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The Port Identifier of the port on the Designated
+ Bridge for this port's segment."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 8.5.5.7"
+ ::= { dot1dStpPortEntry 9 }
+
+dot1dStpPortForwardTransitions OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of times this port has transitioned
+ from the Learning state to the Forwarding state."
+ ::= { dot1dStpPortEntry 10 }
+
+dot1dStpPortPathCost32 OBJECT-TYPE
+ SYNTAX Integer32 (1..200000000)
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The contribution of this port to the path cost of
+ paths towards the spanning tree root which include
+ this port. 802.1D-1998 recommends that the default
+ value of this parameter be in inverse proportion to
+ the speed of the attached LAN.
+
+ This object replaces dot1dStpPortPathCost to support
+ IEEE 802.1t."
+ REFERENCE
+ "IEEE 802.1t clause 8.10.2, Table 8-5."
+ ::= { dot1dStpPortEntry 11 }
+
+-- ---------------------------------------------------------- --
+-- the dot1dTp subtree
+-- ---------------------------------------------------------- --
+-- Implementation of the dot1dTp subtree is optional. It is
+-- implemented by those bridges that support the transparent
+-- bridging mode. A transparent or SRT bridge will implement
+-- this subtree.
+-- ---------------------------------------------------------- --
+
+dot1dTpLearnedEntryDiscards OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The total number of Forwarding Database entries that
+ have been or would have been learned, but have been
+ discarded due to a lack of storage space in the
+ Forwarding Database. If this counter is increasing, it
+ indicates that the Forwarding Database is regularly
+ becoming full (a condition that has unpleasant
+ performance effects on the subnetwork). If this counter
+ has a significant value but is not presently increasing,
+ it indicates that the problem has been occurring but is
+ not persistent."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 14.7.1.1.3"
+ ::= { dot1dTp 1 }
+
+dot1dTpAgingTime OBJECT-TYPE
+ SYNTAX Integer32 (10..1000000)
+ UNITS "seconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The timeout period in seconds for aging out
+ dynamically-learned forwarding information.
+ 802.1D-1998 recommends a default of 300 seconds."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 14.7.1.1.3"
+ ::= { dot1dTp 2 }
+
+
+-- ---------------------------------------------------------- --
+-- The Forwarding Database for Transparent Bridges
+-- ---------------------------------------------------------- --
+
+dot1dTpFdbTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF Dot1dTpFdbEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains information about unicast
+ entries for which the bridge has forwarding and/or
+ filtering information. This information is used
+ by the transparent bridging function in
+ determining how to propagate a received frame."
+ ::= { dot1dTp 3 }
+
+dot1dTpFdbEntry OBJECT-TYPE
+ SYNTAX Dot1dTpFdbEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Information about a specific unicast MAC address
+ for which the bridge has some forwarding and/or
+ filtering information."
+ INDEX { dot1dTpFdbAddress }
+ ::= { dot1dTpFdbTable 1 }
+
+Dot1dTpFdbEntry ::=
+ SEQUENCE {
+ dot1dTpFdbAddress
+ MacAddress,
+ dot1dTpFdbPort
+ Integer32,
+ dot1dTpFdbStatus
+ INTEGER
+ }
+
+dot1dTpFdbAddress OBJECT-TYPE
+ SYNTAX MacAddress
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "A unicast MAC address for which the bridge has
+ forwarding and/or filtering information."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 7.9.1, 7.9.2"
+ ::= { dot1dTpFdbEntry 1 }
+
+dot1dTpFdbPort OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Either the value '0', or the port number of the port on
+ which a frame having a source address equal to the value
+ of the corresponding instance of dot1dTpFdbAddress has
+ been seen. A value of '0' indicates that the port
+ number has not been learned, but that the bridge does
+ have some forwarding/filtering information about this
+ address (e.g., in the dot1dStaticTable). Implementors
+ are encouraged to assign the port value to this object
+ whenever it is learned, even for addresses for which the
+ corresponding value of dot1dTpFdbStatus is not
+ learned(3)."
+ ::= { dot1dTpFdbEntry 2 }
+dot1dTpFdbStatus OBJECT-TYPE
+ SYNTAX INTEGER {
+ other(1),
+ invalid(2),
+ learned(3),
+ self(4),
+ mgmt(5)
+ }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The status of this entry. The meanings of the
+ values are:
+ other(1) - none of the following. This would
+ include the case where some other MIB object
+ (not the corresponding instance of
+ dot1dTpFdbPort, nor an entry in the
+ dot1dStaticTable) is being used to determine if
+ and how frames addressed to the value of the
+ corresponding instance of dot1dTpFdbAddress are
+ being forwarded.
+ invalid(2) - this entry is no longer valid (e.g.,
+ it was learned but has since aged out), but has
+ not yet been flushed from the table.
+ learned(3) - the value of the corresponding instance
+ of dot1dTpFdbPort was learned, and is being
+ used.
+ self(4) - the value of the corresponding instance of
+ dot1dTpFdbAddress represents one of the bridge's
+ addresses. The corresponding instance of
+ dot1dTpFdbPort indicates which of the bridge's
+ ports has this address.
+ mgmt(5) - the value of the corresponding instance of
+ dot1dTpFdbAddress is also the value of an
+ existing instance of dot1dStaticAddress."
+ ::= { dot1dTpFdbEntry 3 }
+
+-- ---------------------------------------------------------- --
+-- Port Table for Transparent Bridges
+-- ---------------------------------------------------------- --
+
+dot1dTpPortTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF Dot1dTpPortEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains information about every port that
+ is associated with this transparent bridge."
+ ::= { dot1dTp 4 }
+
+dot1dTpPortEntry OBJECT-TYPE
+ SYNTAX Dot1dTpPortEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A list of information for each port of a transparent
+ bridge."
+ INDEX { dot1dTpPort }
+ ::= { dot1dTpPortTable 1 }
+
+Dot1dTpPortEntry ::=
+ SEQUENCE {
+ dot1dTpPort
+ Integer32,
+ dot1dTpPortMaxInfo
+ Integer32,
+ dot1dTpPortInFrames
+ Counter32,
+ dot1dTpPortOutFrames
+ Counter32,
+ dot1dTpPortInDiscards
+ Counter32
+ }
+
+dot1dTpPort OBJECT-TYPE
+ SYNTAX Integer32 (1..65535)
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The port number of the port for which this entry
+ contains Transparent bridging management information."
+ ::= { dot1dTpPortEntry 1 }
+
+-- It would be nice if we could use ifMtu as the size of the
+-- largest INFO field, but we can't because ifMtu is defined
+-- to be the size that the (inter-)network layer can use, which
+-- can differ from the MAC layer (especially if several layers
+-- of encapsulation are used).
+
+dot1dTpPortMaxInfo OBJECT-TYPE
+ SYNTAX Integer32
+ UNITS "bytes"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The maximum size of the INFO (non-MAC) field that
+ this port will receive or transmit."
+ ::= { dot1dTpPortEntry 2 }
+
+dot1dTpPortInFrames OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames that have been received by this
+ port from its segment. Note that a frame received on the
+ interface corresponding to this port is only counted by
+ this object if and only if it is for a protocol being
+ processed by the local bridging function, including
+ bridge management frames."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 14.6.1.1.3"
+ ::= { dot1dTpPortEntry 3 }
+
+dot1dTpPortOutFrames OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames that have been transmitted by this
+ port to its segment. Note that a frame transmitted on
+ the interface corresponding to this port is only counted
+ by this object if and only if it is for a protocol being
+ processed by the local bridging function, including
+ bridge management frames."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 14.6.1.1.3"
+ ::= { dot1dTpPortEntry 4 }
+
+dot1dTpPortInDiscards OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Count of received valid frames that were discarded
+ (i.e., filtered) by the Forwarding Process."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 14.6.1.1.3"
+ ::= { dot1dTpPortEntry 5 }
+
+-- ---------------------------------------------------------- --
+-- The Static (Destination-Address Filtering) Database
+-- ---------------------------------------------------------- --
+-- Implementation of this subtree is optional.
+-- ---------------------------------------------------------- --
+
+dot1dStaticTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF Dot1dStaticEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table containing filtering information configured
+ into the bridge by (local or network) management
+ specifying the set of ports to which frames received
+ from specific ports and containing specific destination
+ addresses are allowed to be forwarded. The value of
+ zero in this table, as the port number from which frames
+ with a specific destination address are received, is
+ used to specify all ports for which there is no specific
+ entry in this table for that particular destination
+ address. Entries are valid for unicast and for
+ group/broadcast addresses."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 14.7.2"
+ ::= { dot1dStatic 1 }
+
+dot1dStaticEntry OBJECT-TYPE
+ SYNTAX Dot1dStaticEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Filtering information configured into the bridge by
+ (local or network) management specifying the set of
+ ports to which frames received from a specific port and
+ containing a specific destination address are allowed to
+ be forwarded."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 14.7.2"
+ INDEX { dot1dStaticAddress, dot1dStaticReceivePort }
+ ::= { dot1dStaticTable 1 }
+
+Dot1dStaticEntry ::=
+ SEQUENCE {
+ dot1dStaticAddress MacAddress,
+ dot1dStaticReceivePort Integer32,
+ dot1dStaticAllowedToGoTo OCTET STRING,
+ dot1dStaticStatus INTEGER
+ }
+
+dot1dStaticAddress OBJECT-TYPE
+ SYNTAX MacAddress
+ MAX-ACCESS read-create
+ STATUS current
+ DESCRIPTION
+ "The destination MAC address in a frame to which this
+ entry's filtering information applies. This object can
+ take the value of a unicast address, a group address, or
+ the broadcast address."
+ REFERENCE
+ "IEEE 802.1D-1998: clause 7.9.1, 7.9.2"
+ ::= { dot1dStaticEntry 1 }
+
+dot1dStaticReceivePort OBJECT-TYPE
+ SYNTAX Integer32 (0..65535)
+ MAX-ACCESS read-create
+ STATUS current
+ DESCRIPTION
+ "Either the value '0', or the port number of the port
+ from which a frame must be received in order for this
+ entry's filtering information to apply. A value of zero
+ indicates that this entry applies on all ports of the
+ bridge for which there is no other applicable entry."
+ ::= { dot1dStaticEntry 2 }
+
+dot1dStaticAllowedToGoTo OBJECT-TYPE
+ SYNTAX OCTET STRING (SIZE (0..512))
+ MAX-ACCESS read-create
+ STATUS current
+ DESCRIPTION
+ "The set of ports to which frames received from a
+ specific port and destined for a specific MAC address,
+ are allowed to be forwarded. Each octet within the
+ value of this object specifies a set of eight ports,
+ with the first octet specifying ports 1 through 8, the
+ second octet specifying ports 9 through 16, etc. Within
+ each octet, the most significant bit represents the
+ lowest numbered port, and the least significant bit
+ represents the highest numbered port. Thus, each port
+ of the bridge is represented by a single bit within the
+ value of this object. If that bit has a value of '1',
+ then that port is included in the set of ports; the port
+ is not included if its bit has a value of '0'. (Note
+ that the setting of the bit corresponding to the port
+ from which a frame is received is irrelevant.) The
+ default value of this object is a string of ones of
+ appropriate length.
+
+ The value of this object may exceed the required minimum
+ maximum message size of some SNMP transport (484 bytes,
+ in the case of SNMP over UDP, see RFC 3417, section 3.2).
+ SNMP engines on bridges supporting a large number of
+ ports must support appropriate maximum message sizes."
+ ::= { dot1dStaticEntry 3 }
+
+dot1dStaticStatus OBJECT-TYPE
+ SYNTAX INTEGER {
+ other(1),
+ invalid(2),
+ permanent(3),
+ deleteOnReset(4),
+ deleteOnTimeout(5)
+ }
+ MAX-ACCESS read-create
+ STATUS current
+ DESCRIPTION
+ "This object indicates the status of this entry.
+ The default value is permanent(3).
+ other(1) - this entry is currently in use but the
+ conditions under which it will remain so are
+ different from each of the following values.
+ invalid(2) - writing this value to the object
+ removes the corresponding entry.
+ permanent(3) - this entry is currently in use and
+ will remain so after the next reset of the
+ bridge.
+ deleteOnReset(4) - this entry is currently in use
+ and will remain so until the next reset of the
+ bridge.
+ deleteOnTimeout(5) - this entry is currently in use
+ and will remain so until it is aged out."
+ ::= { dot1dStaticEntry 4 }
+
+-- ---------------------------------------------------------- --
+-- Notifications for use by Bridges
+-- ---------------------------------------------------------- --
+-- Notifications for the Spanning Tree Protocol
+-- ---------------------------------------------------------- --
+
+newRoot NOTIFICATION-TYPE
+ -- OBJECTS { }
+ STATUS current
+ DESCRIPTION
+ "The newRoot trap indicates that the sending agent has
+ become the new root of the Spanning Tree; the trap is
+ sent by a bridge soon after its election as the new
+ root, e.g., upon expiration of the Topology Change Timer,
+ immediately subsequent to its election. Implementation
+ of this trap is optional."
+ ::= { dot1dNotifications 1 }
+
+topologyChange NOTIFICATION-TYPE
+ -- OBJECTS { }
+ STATUS current
+ DESCRIPTION
+ "A topologyChange trap is sent by a bridge when any of
+ its configured ports transitions from the Learning state
+ to the Forwarding state, or from the Forwarding state to
+ the Blocking state. The trap is not sent if a newRoot
+ trap is sent for the same transition. Implementation of
+ this trap is optional."
+ ::= { dot1dNotifications 2 }
+
+-- ---------------------------------------------------------- --
+-- IEEE 802.1D MIB - Conformance Information
+-- ---------------------------------------------------------- --
+
+dot1dGroups OBJECT IDENTIFIER ::= { dot1dConformance 1 }
+dot1dCompliances OBJECT IDENTIFIER ::= { dot1dConformance 2 }
+
+-- ---------------------------------------------------------- --
+-- units of conformance
+-- ---------------------------------------------------------- --
+
+-- ---------------------------------------------------------- --
+-- the dot1dBase group
+-- ---------------------------------------------------------- --
+
+dot1dBaseBridgeGroup OBJECT-GROUP
+ OBJECTS {
+ dot1dBaseBridgeAddress,
+ dot1dBaseNumPorts,
+ dot1dBaseType
+ }
+ STATUS current
+ DESCRIPTION
+ "Bridge level information for this device."
+ ::= { dot1dGroups 1 }
+
+dot1dBasePortGroup OBJECT-GROUP
+ OBJECTS {
+ dot1dBasePort,
+ dot1dBasePortIfIndex,
+ dot1dBasePortCircuit,
+ dot1dBasePortDelayExceededDiscards,
+ dot1dBasePortMtuExceededDiscards
+ }
+ STATUS current
+ DESCRIPTION
+ "Information for each port on this device."
+ ::= { dot1dGroups 2 }
+
+-- ---------------------------------------------------------- --
+-- the dot1dStp group
+-- ---------------------------------------------------------- --
+
+dot1dStpBridgeGroup OBJECT-GROUP
+ OBJECTS {
+ dot1dStpProtocolSpecification,
+ dot1dStpPriority,
+ dot1dStpTimeSinceTopologyChange,
+ dot1dStpTopChanges,
+ dot1dStpDesignatedRoot,
+ dot1dStpRootCost,
+ dot1dStpRootPort,
+ dot1dStpMaxAge,
+ dot1dStpHelloTime,
+ dot1dStpHoldTime,
+ dot1dStpForwardDelay,
+ dot1dStpBridgeMaxAge,
+ dot1dStpBridgeHelloTime,
+ dot1dStpBridgeForwardDelay
+ }
+ STATUS current
+ DESCRIPTION
+ "Bridge level Spanning Tree data for this device."
+ ::= { dot1dGroups 3 }
+
+dot1dStpPortGroup OBJECT-GROUP
+ OBJECTS {
+ dot1dStpPort,
+ dot1dStpPortPriority,
+ dot1dStpPortState,
+ dot1dStpPortEnable,
+ dot1dStpPortPathCost,
+ dot1dStpPortDesignatedRoot,
+ dot1dStpPortDesignatedCost,
+ dot1dStpPortDesignatedBridge,
+ dot1dStpPortDesignatedPort,
+ dot1dStpPortForwardTransitions
+ }
+ STATUS current
+ DESCRIPTION
+ "Spanning Tree data for each port on this device."
+ ::= { dot1dGroups 4 }
+
+dot1dStpPortGroup2 OBJECT-GROUP
+ OBJECTS {
+ dot1dStpPort,
+ dot1dStpPortPriority,
+ dot1dStpPortState,
+ dot1dStpPortEnable,
+ dot1dStpPortDesignatedRoot,
+ dot1dStpPortDesignatedCost,
+ dot1dStpPortDesignatedBridge,
+ dot1dStpPortDesignatedPort,
+ dot1dStpPortForwardTransitions,
+ dot1dStpPortPathCost32
+ }
+ STATUS current
+ DESCRIPTION
+ "Spanning Tree data for each port on this device."
+ ::= { dot1dGroups 5 }
+
+dot1dStpPortGroup3 OBJECT-GROUP
+ OBJECTS {
+ dot1dStpPortPathCost32
+ }
+ STATUS current
+ DESCRIPTION
+ "Spanning Tree data for devices supporting 32-bit
+ path costs."
+ ::= { dot1dGroups 6 }
+
+-- ---------------------------------------------------------- --
+-- the dot1dTp group
+-- ---------------------------------------------------------- --
+
+dot1dTpBridgeGroup OBJECT-GROUP
+ OBJECTS {
+ dot1dTpLearnedEntryDiscards,
+ dot1dTpAgingTime
+ }
+ STATUS current
+ DESCRIPTION
+ "Bridge level Transparent Bridging data."
+ ::= { dot1dGroups 7 }
+
+dot1dTpFdbGroup OBJECT-GROUP
+ OBJECTS {
+ dot1dTpFdbAddress,
+ dot1dTpFdbPort,
+ dot1dTpFdbStatus
+ }
+
+ STATUS current
+ DESCRIPTION
+ "Filtering Database information for the Bridge."
+ ::= { dot1dGroups 8 }
+
+dot1dTpGroup OBJECT-GROUP
+ OBJECTS {
+ dot1dTpPort,
+ dot1dTpPortMaxInfo,
+ dot1dTpPortInFrames,
+ dot1dTpPortOutFrames,
+ dot1dTpPortInDiscards
+ }
+ STATUS current
+ DESCRIPTION
+ "Dynamic Filtering Database information for each port of
+ the Bridge."
+ ::= { dot1dGroups 9 }
+
+-- ---------------------------------------------------------- --
+-- The Static (Destination-Address Filtering) Database
+-- ---------------------------------------------------------- --
+
+dot1dStaticGroup OBJECT-GROUP
+ OBJECTS {
+ dot1dStaticAddress,
+ dot1dStaticReceivePort,
+ dot1dStaticAllowedToGoTo,
+ dot1dStaticStatus
+ }
+ STATUS current
+ DESCRIPTION
+ "Static Filtering Database information for each port of
+ the Bridge."
+ ::= { dot1dGroups 10 }
+
+-- ---------------------------------------------------------- --
+-- The Trap Notification Group
+-- ---------------------------------------------------------- --
+
+dot1dNotificationGroup NOTIFICATION-GROUP
+ NOTIFICATIONS {
+ newRoot,
+ topologyChange
+ }
+ STATUS current
+ DESCRIPTION
+ "Group of objects describing notifications (traps)."
+ ::= { dot1dGroups 11 }
+
+-- ---------------------------------------------------------- --
+-- compliance statements
+-- ---------------------------------------------------------- --
+
+bridgeCompliance1493 MODULE-COMPLIANCE
+ STATUS current
+ DESCRIPTION
+ "The compliance statement for device support of bridging
+ services, as per RFC1493."
+
+ MODULE
+ MANDATORY-GROUPS {
+ dot1dBaseBridgeGroup,
+ dot1dBasePortGroup
+ }
+
+ GROUP dot1dStpBridgeGroup
+ DESCRIPTION
+ "Implementation of this group is mandatory for bridges
+ that support the Spanning Tree Protocol."
+
+ GROUP dot1dStpPortGroup
+ DESCRIPTION
+ "Implementation of this group is mandatory for bridges
+ that support the Spanning Tree Protocol."
+
+ GROUP dot1dTpBridgeGroup
+ DESCRIPTION
+ "Implementation of this group is mandatory for bridges
+ that support the transparent bridging mode. A
+ transparent or SRT bridge will implement this group."
+
+ GROUP dot1dTpFdbGroup
+ DESCRIPTION
+ "Implementation of this group is mandatory for bridges
+ that support the transparent bridging mode. A
+ transparent or SRT bridge will implement this group."
+
+ GROUP dot1dTpGroup
+ DESCRIPTION
+ "Implementation of this group is mandatory for bridges
+ that support the transparent bridging mode. A
+ transparent or SRT bridge will implement this group."
+
+ GROUP dot1dStaticGroup
+ DESCRIPTION
+ "Implementation of this group is optional."
+
+ GROUP dot1dNotificationGroup
+ DESCRIPTION
+ "Implementation of this group is optional."
+ ::= { dot1dCompliances 1 }
+
+bridgeCompliance4188 MODULE-COMPLIANCE
+ STATUS current
+ DESCRIPTION
+ "The compliance statement for device support of bridging
+ services. This supports 32-bit Path Cost values and the
+ more restricted bridge and port priorities, as per IEEE
+ 802.1t.
+
+ Full support for the 802.1D management objects requires that
+ the SNMPv2-MIB [RFC3418] objects sysDescr, and sysUpTime, as
+ well as the IF-MIB [RFC2863] objects ifIndex, ifType,
+ ifDescr, ifPhysAddress, and ifLastChange are implemented."
+
+ MODULE
+ MANDATORY-GROUPS {
+ dot1dBaseBridgeGroup,
+ dot1dBasePortGroup
+ }
+
+ GROUP dot1dStpBridgeGroup
+ DESCRIPTION
+ "Implementation of this group is mandatory for
+ bridges that support the Spanning Tree Protocol."
+
+ OBJECT dot1dStpPriority
+ SYNTAX Integer32 (0|4096|8192|12288|16384|20480|24576
+ |28672|32768|36864|40960|45056|49152
+ |53248|57344|61440)
+ DESCRIPTION
+ "The possible values defined by IEEE 802.1t."
+
+ GROUP dot1dStpPortGroup2
+ DESCRIPTION
+ "Implementation of this group is mandatory for
+ bridges that support the Spanning Tree Protocol."
+
+ GROUP dot1dStpPortGroup3
+ DESCRIPTION
+ "Implementation of this group is mandatory for bridges
+ that support the Spanning Tree Protocol and 32-bit path
+ costs. In particular, this includes devices supporting
+ IEEE 802.1t and IEEE 802.1w."
+
+ OBJECT dot1dStpPortPriority
+ SYNTAX Integer32 (0|16|32|48|64|80|96|112|128
+ |144|160|176|192|208|224|240)
+ DESCRIPTION
+ "The possible values defined by IEEE 802.1t."
+
+ GROUP dot1dTpBridgeGroup
+ DESCRIPTION
+ "Implementation of this group is mandatory for
+ bridges that support the transparent bridging
+ mode. A transparent or SRT bridge will implement
+ this group."
+
+ GROUP dot1dTpFdbGroup
+ DESCRIPTION
+ "Implementation of this group is mandatory for
+ bridges that support the transparent bridging
+ mode. A transparent or SRT bridge will implement
+ this group."
+
+ GROUP dot1dTpGroup
+ DESCRIPTION
+ "Implementation of this group is mandatory for
+ bridges that support the transparent bridging
+ mode. A transparent or SRT bridge will implement
+ this group."
+
+ GROUP dot1dStaticGroup
+ DESCRIPTION
+ "Implementation of this group is optional."
+
+ GROUP dot1dNotificationGroup
+ DESCRIPTION
+ "Implementation of this group is optional."
+
+ ::= { dot1dCompliances 2 }
+
+END
diff --git a/usr.sbin/bsnmpd/modules/snmp_bridge/Makefile b/usr.sbin/bsnmpd/modules/snmp_bridge/Makefile
new file mode 100644
index 0000000..5d0afec
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_bridge/Makefile
@@ -0,0 +1,19 @@
+#
+# $FreeBSD$
+#
+
+MOD= bridge
+SRCS= bridge_snmp.c bridge_if.c bridge_port.c bridge_addrs.c \
+ bridge_pf.c bridge_sys.c
+CFLAGS+= -DSNMPTREE_TYPES
+
+XSYM= dot1dBridge newRoot topologyChange begemotBridgeNewRoot \
+ begemotBridgeTopologyChange begemotBridgeBaseName
+
+MAN= snmp_bridge.3
+
+BMIBS= BRIDGE-MIB.txt BEGEMOT-BRIDGE-MIB.txt RSTP-MIB.txt
+DEFS= ${MOD}_tree.def
+INCS= ${MOD}_snmp.h
+
+.include <bsd.snmpmod.mk>
diff --git a/usr.sbin/bsnmpd/modules/snmp_bridge/Makefile.depend b/usr.sbin/bsnmpd/modules/snmp_bridge/Makefile.depend
new file mode 100644
index 0000000..f391300
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_bridge/Makefile.depend
@@ -0,0 +1,41 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbsnmp/libbsnmp \
+ lib/libc \
+ lib/libcompiler_rt \
+ usr.sbin/bsnmpd/modules \
+ usr.sbin/bsnmpd/modules/snmp_mibII \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+bridge_addrs.So: bridge_tree.h
+bridge_addrs.po: bridge_tree.h
+bridge_if.So: bridge_oid.h
+bridge_if.So: bridge_tree.h
+bridge_if.po: bridge_oid.h
+bridge_if.po: bridge_tree.h
+bridge_pf.So: bridge_tree.h
+bridge_pf.po: bridge_tree.h
+bridge_port.So: bridge_tree.h
+bridge_port.po: bridge_tree.h
+bridge_snmp.So: bridge_oid.h
+bridge_snmp.So: bridge_tree.h
+bridge_snmp.po: bridge_oid.h
+bridge_snmp.po: bridge_tree.h
+bridge_sys.So: bridge_tree.h
+bridge_sys.po: bridge_tree.h
+bridge_tree.So: bridge_tree.c
+bridge_tree.So: bridge_tree.h
+bridge_tree.po: bridge_tree.c
+bridge_tree.po: bridge_tree.h
+.endif
diff --git a/usr.sbin/bsnmpd/modules/snmp_bridge/RSTP-MIB.txt b/usr.sbin/bsnmpd/modules/snmp_bridge/RSTP-MIB.txt
new file mode 100644
index 0000000..ea6648e
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_bridge/RSTP-MIB.txt
@@ -0,0 +1,325 @@
+--
+-- Copyright (C) The Internet Society (2005).
+--
+-- This document is subject to the rights, licenses and restrictions
+-- contained in BCP 78, and except as set forth therein, the authors
+-- retain all their rights.
+--
+-- This document and the information contained herein are provided on an
+-- "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS
+-- OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET
+-- ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED,
+-- INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE
+-- INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED
+-- WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+--
+-- $FreeBSD$
+--
+
+RSTP-MIB DEFINITIONS ::= BEGIN
+
+-- -------------------------------------------------------------
+-- MIB for IEEE 802.1w Rapid Spanning Tree Protocol
+-- -------------------------------------------------------------
+
+IMPORTS
+ MODULE-IDENTITY, OBJECT-TYPE, Integer32, mib-2
+ FROM SNMPv2-SMI
+ TruthValue
+ FROM SNMPv2-TC
+ MODULE-COMPLIANCE, OBJECT-GROUP
+ FROM SNMPv2-CONF
+ dot1dStp, dot1dStpPortEntry
+ FROM BRIDGE-MIB;
+
+rstpMIB MODULE-IDENTITY
+ LAST-UPDATED "200512070000Z"
+ ORGANIZATION "IETF Bridge MIB Working Group"
+ CONTACT-INFO
+ "Email: Bridge-mib@ietf.org"
+ DESCRIPTION
+ "The Bridge MIB Extension module for managing devices
+ that support the Rapid Spanning Tree Protocol defined
+ by IEEE 802.1w.
+
+ Copyright (C) The Internet Society (2005). This version of
+ this MIB module is part of RFC 4318; See the RFC itself for
+ full legal notices."
+
+ REVISION "200512070000Z"
+ DESCRIPTION
+ "The initial version of this MIB module as published in
+ RFC 4318."
+ ::= { mib-2 134 }
+
+-- ---------------------------------------------------------- --
+-- subtrees in the RSTP-MIB
+-- ---------------------------------------------------------- --
+
+rstpNotifications OBJECT IDENTIFIER ::= { rstpMIB 0 }
+rstpObjects OBJECT IDENTIFIER ::= { rstpMIB 1 }
+rstpConformance OBJECT IDENTIFIER ::= { rstpMIB 2 }
+
+-- -------------------------------------------------------------
+-- Addition to the dot1dStp group
+-- -------------------------------------------------------------
+
+dot1dStpVersion OBJECT-TYPE
+ SYNTAX INTEGER {
+ stpCompatible(0),
+ rstp(2)
+ }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The version of Spanning Tree Protocol the bridge is
+ currently running. The value 'stpCompatible(0)'
+ indicates the Spanning Tree Protocol specified in
+ IEEE 802.1D-1998 and 'rstp(2)' indicates the Rapid
+ Spanning Tree Protocol specified in IEEE 802.1w and
+ clause 17 of 802.1D-2004. The values are directly from
+ the IEEE standard. New values may be defined as future
+ versions of the protocol become available.
+
+ The value of this object MUST be retained across
+ reinitializations of the management system."
+ REFERENCE
+ "IEEE 802.1w clause 14.8.1, 17.12, 17.16.1"
+ DEFVAL { rstp }
+ ::= { dot1dStp 16 }
+
+dot1dStpTxHoldCount OBJECT-TYPE
+ SYNTAX Integer32 (1..10)
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value used by the Port Transmit state machine to limit
+ the maximum transmission rate.
+
+ The value of this object MUST be retained across
+ reinitializations of the management system."
+
+ REFERENCE
+ "IEEE 802.1w clause 17.16.6"
+ DEFVAL { 3 }
+ ::= { dot1dStp 17 }
+
+--
+-- { dot1dStp 18 } was used to represent dot1dStpPathCostDefault
+-- in an earlier version of this MIB. It has since been
+-- obsoleted, and should not be used.
+--
+
+dot1dStpExtPortTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF Dot1dStpExtPortEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains port-specific Rapid Spanning Tree
+ information."
+ ::= { dot1dStp 19 }
+
+dot1dStpExtPortEntry OBJECT-TYPE
+ SYNTAX Dot1dStpExtPortEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A list of Rapid Spanning Tree information maintained by
+ each port."
+ AUGMENTS { dot1dStpPortEntry }
+ ::= { dot1dStpExtPortTable 1 }
+
+Dot1dStpExtPortEntry ::=
+ SEQUENCE {
+ dot1dStpPortProtocolMigration
+ TruthValue,
+ dot1dStpPortAdminEdgePort
+ TruthValue,
+ dot1dStpPortOperEdgePort
+ TruthValue,
+ dot1dStpPortAdminPointToPoint
+ INTEGER,
+ dot1dStpPortOperPointToPoint
+ TruthValue,
+ dot1dStpPortAdminPathCost
+ Integer32
+ }
+
+dot1dStpPortProtocolMigration OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "When operating in RSTP (version 2) mode, writing true(1)
+ to this object forces this port to transmit RSTP BPDUs.
+ Any other operation on this object has no effect and
+ it always returns false(2) when read."
+ REFERENCE
+ "IEEE 802.1w clause 14.8.2.4, 17.18.10, 17.26"
+ ::= { dot1dStpExtPortEntry 1 }
+
+dot1dStpPortAdminEdgePort OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The administrative value of the Edge Port parameter. A
+ value of true(1) indicates that this port should be
+ assumed as an edge-port, and a value of false(2) indicates
+ that this port should be assumed as a non-edge-port.
+ Setting this object will also cause the corresponding
+ instance of dot1dStpPortOperEdgePort to change to the
+ same value. Note that even when this object's value
+ is true, the value of the corresponding instance of
+ dot1dStpPortOperEdgePort can be false if a BPDU has
+ been received.
+
+ The value of this object MUST be retained across
+ reinitializations of the management system."
+
+ REFERENCE
+ "IEEE 802.1t clause 14.8.2, 18.3.3"
+ ::= { dot1dStpExtPortEntry 2 }
+
+dot1dStpPortOperEdgePort OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The operational value of the Edge Port parameter. The
+ object is initialized to the value of the corresponding
+ instance of dot1dStpPortAdminEdgePort. When the
+ corresponding instance of dot1dStpPortAdminEdgePort is
+ set, this object will be changed as well. This object
+ will also be changed to false on reception of a BPDU."
+
+ REFERENCE
+ "IEEE 802.1t clause 14.8.2, 18.3.4"
+ ::= { dot1dStpExtPortEntry 3 }
+
+dot1dStpPortAdminPointToPoint OBJECT-TYPE
+ SYNTAX INTEGER {
+ forceTrue(0),
+ forceFalse(1),
+ auto(2)
+ }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The administrative point-to-point status of the LAN segment
+ attached to this port, using the enumeration values of the
+ IEEE 802.1w clause. A value of forceTrue(0) indicates
+ that this port should always be treated as if it is
+ connected to a point-to-point link. A value of
+ forceFalse(1) indicates that this port should be treated as
+ having a shared media connection. A value of auto(2)
+ indicates that this port is considered to have a
+ point-to-point link if it is an Aggregator and all of its
+ members are aggregatable, or if the MAC entity
+ is configured for full duplex operation, either through
+ auto-negotiation or by management means. Manipulating this
+ object changes the underlying adminPortToPortMAC.
+
+ The value of this object MUST be retained across
+ reinitializations of the management system."
+
+ REFERENCE
+ "IEEE 802.1w clause 6.4.3, 6.5, 14.8.2"
+ ::= { dot1dStpExtPortEntry 4 }
+
+dot1dStpPortOperPointToPoint OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The operational point-to-point status of the LAN segment
+ attached to this port. It indicates whether a port is
+ considered to have a point-to-point connection.
+ If adminPointToPointMAC is set to auto(2), then the value
+ of operPointToPointMAC is determined in accordance with the
+ specific procedures defined for the MAC entity concerned,
+ as defined in IEEE 802.1w, clause 6.5. The value is
+ determined dynamically; that is, it is re-evaluated whenever
+ the value of adminPointToPointMAC changes, and whenever
+ the specific procedures defined for the MAC entity evaluate
+ a change in its point-to-point status."
+ REFERENCE
+ "IEEE 802.1w clause 6.4.3, 6.5, 14.8.2"
+ ::= { dot1dStpExtPortEntry 5 }
+
+dot1dStpPortAdminPathCost OBJECT-TYPE
+ SYNTAX Integer32 (0..200000000)
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The administratively assigned value for the contribution
+ of this port to the path cost of paths toward the spanning
+ tree root.
+
+ Writing a value of '0' assigns the automatically calculated
+ default Path Cost value to the port. If the default Path
+ Cost is being used, this object returns '0' when read.
+
+ This complements the object dot1dStpPortPathCost or
+ dot1dStpPortPathCost32, which returns the operational value
+ of the path cost.
+
+ The value of this object MUST be retained across
+ reinitializations of the management system."
+ REFERENCE
+ "IEEE 802.1D-1998: Section 8.5.5.3"
+ ::= { dot1dStpExtPortEntry 6 }
+
+-- -------------------------------------------------------------
+-- rstpMIB - Conformance Information
+-- -------------------------------------------------------------
+
+rstpGroups OBJECT IDENTIFIER ::= { rstpConformance 1 }
+
+rstpCompliances OBJECT IDENTIFIER ::= { rstpConformance 2 }
+
+-- -------------------------------------------------------------
+-- Units of conformance
+-- -------------------------------------------------------------
+
+rstpBridgeGroup OBJECT-GROUP
+ OBJECTS {
+ dot1dStpVersion,
+ dot1dStpTxHoldCount
+ }
+ STATUS current
+ DESCRIPTION
+ "Rapid Spanning Tree information for the bridge."
+ ::= { rstpGroups 1 }
+
+rstpPortGroup OBJECT-GROUP
+ OBJECTS {
+ dot1dStpPortProtocolMigration,
+ dot1dStpPortAdminEdgePort,
+ dot1dStpPortOperEdgePort,
+ dot1dStpPortAdminPointToPoint,
+ dot1dStpPortOperPointToPoint,
+ dot1dStpPortAdminPathCost
+ }
+ STATUS current
+ DESCRIPTION
+ "Rapid Spanning Tree information for individual ports."
+ ::= { rstpGroups 2 }
+
+-- -------------------------------------------------------------
+-- Compliance statements
+-- -------------------------------------------------------------
+
+rstpCompliance MODULE-COMPLIANCE
+ STATUS current
+ DESCRIPTION
+ "The compliance statement for device support of Rapid
+ Spanning Tree Protocol (RSTP) bridging services."
+ MODULE
+ MANDATORY-GROUPS {
+ rstpBridgeGroup,
+ rstpPortGroup
+ }
+ ::= { rstpCompliances 1 }
+
+END
diff --git a/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_addrs.c b/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_addrs.c
new file mode 100644
index 0000000..0daceec
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_addrs.c
@@ -0,0 +1,589 @@
+/*-
+ * Copyright (c) 2006 Shteryana Shopova <syrinx@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.
+ *
+ * Bridge MIB implementation for SNMPd.
+ * Bridge addresses.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_mib.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+#include <syslog.h>
+
+#include <bsnmp/snmpmod.h>
+#include <bsnmp/snmp_mibII.h>
+
+#include "bridge_tree.h"
+#include "bridge_snmp.h"
+
+TAILQ_HEAD(tp_entries, tp_entry);
+
+/*
+ * Free the bridge address list.
+ */
+static void
+bridge_tpe_free(struct tp_entries *headp)
+{
+ struct tp_entry *t;
+
+ while ((t = TAILQ_FIRST(headp)) != NULL) {
+ TAILQ_REMOVE(headp, t, tp_e);
+ free(t);
+ }
+}
+
+/*
+ * Free the bridge address entries from the address list,
+ * for the specified bridge interface only.
+ */
+static void
+bridge_tpe_bif_free(struct tp_entries *headp,
+ struct bridge_if *bif)
+{
+ struct tp_entry *tp;
+
+ while (bif->f_tpa != NULL && bif->sysindex == bif->f_tpa->sysindex) {
+ tp = TAILQ_NEXT(bif->f_tpa, tp_e);
+ TAILQ_REMOVE(headp, bif->f_tpa, tp_e);
+ free(bif->f_tpa);
+ bif->f_tpa = tp;
+ }
+}
+
+/*
+ * Compare two mac addresses.
+ * m1 < m2 : -1
+ * m1 > m2 : +1
+ * m1 = m2 : 0
+ */
+static int
+bridge_compare_macs(const uint8_t *m1, const uint8_t *m2)
+{
+ int i;
+
+ for (i = 0; i < ETHER_ADDR_LEN; i++) {
+ if (m1[i] < m2[i])
+ return (-1);
+ if (m1[i] > m2[i])
+ return (1);
+ }
+
+ return (0);
+}
+
+/*
+ * Insert an address entry in the bridge address TAILQ starting to search
+ * for its place from the position of the first bridge address for the bridge
+ * interface. Update the first bridge address if necessary.
+ */
+static void
+bridge_addrs_insert_at(struct tp_entries *headp,
+ struct tp_entry *ta, struct tp_entry **f_tpa)
+{
+ struct tp_entry *t1;
+
+ assert(f_tpa != NULL);
+
+ for (t1 = *f_tpa;
+ t1 != NULL && ta->sysindex == t1->sysindex;
+ t1 = TAILQ_NEXT(t1, tp_e)) {
+ if (bridge_compare_macs(ta->tp_addr, t1->tp_addr) < 0) {
+ TAILQ_INSERT_BEFORE(t1, ta, tp_e);
+ if (*f_tpa == t1)
+ (*f_tpa) = ta;
+ return;
+ }
+ }
+
+ if (t1 == NULL)
+ TAILQ_INSERT_TAIL(headp, ta, tp_e);
+ else
+ TAILQ_INSERT_BEFORE(t1, ta, tp_e);
+}
+
+/*
+ * Find an address entry's position in the address list
+ * according to bridge interface name.
+ */
+static struct tp_entry *
+bridge_addrs_find_pos(struct tp_entries *headp, uint32_t b_idx)
+{
+ uint32_t t_idx;
+ struct tp_entry *t1;
+
+ if ((t1 = TAILQ_FIRST(headp)) == NULL ||
+ bridge_compare_sysidx(b_idx, t1->sysindex) < 0)
+ return (NULL);
+
+ t_idx = t1->sysindex;
+
+ for (t1 = TAILQ_NEXT(t1, tp_e); t1 != NULL; t1 = TAILQ_NEXT(t1, tp_e)) {
+
+ if (t1->sysindex != t_idx) {
+ if (bridge_compare_sysidx(b_idx, t1->sysindex) < 0)
+ return (TAILQ_PREV(t1, tp_entries, tp_e));
+ else
+ t_idx = t1->sysindex;
+ }
+ }
+
+ if (t1 == NULL)
+ t1 = TAILQ_LAST(headp, tp_entries);
+
+ return (t1);
+}
+
+/*
+ * Insert a bridge address in the bridge addresses list.
+ */
+static void
+bridge_addrs_bif_insert(struct tp_entries *headp, struct tp_entry *te,
+ struct tp_entry **f_tpa)
+{
+ struct tp_entry *temp;
+
+ if (*f_tpa != NULL)
+ bridge_addrs_insert_at(headp, te, f_tpa);
+ else {
+ temp = bridge_addrs_find_pos(headp, te->sysindex);
+
+ if (temp == NULL)
+ TAILQ_INSERT_HEAD(headp, te, tp_e);
+ else
+ TAILQ_INSERT_AFTER(headp, temp, te, tp_e);
+ *f_tpa = te;
+ }
+}
+
+static struct tp_entries tp_entries = TAILQ_HEAD_INITIALIZER(tp_entries);
+static time_t address_list_age;
+
+void
+bridge_addrs_update_listage(void)
+{
+ address_list_age = time(NULL);
+}
+
+void
+bridge_addrs_fini(void)
+{
+ bridge_tpe_free(&tp_entries);
+}
+
+void
+bridge_addrs_free(struct bridge_if *bif)
+{
+ bridge_tpe_bif_free(&tp_entries, bif);
+}
+
+/*
+ * Find the first address in the list.
+ */
+static struct tp_entry *
+bridge_addrs_first(void)
+{
+ return (TAILQ_FIRST(&tp_entries));
+}
+
+/*
+ * Find the next address in the list.
+ */
+static struct tp_entry *
+bridge_addrs_next(struct tp_entry *te)
+{
+ return (TAILQ_NEXT(te, tp_e));
+}
+
+/*
+ * Find the first address, learnt by the specified bridge interface.
+ */
+struct tp_entry *
+bridge_addrs_bif_first(struct bridge_if *bif)
+{
+ return (bif->f_tpa);
+}
+
+/*
+ * Find the next address, learnt by the specified bridge interface.
+ */
+struct tp_entry *
+bridge_addrs_bif_next(struct tp_entry *te)
+{
+ struct tp_entry *te_next;
+
+ if ((te_next = TAILQ_NEXT(te, tp_e)) == NULL ||
+ te_next->sysindex != te->sysindex)
+ return (NULL);
+
+ return (te_next);
+}
+
+/*
+ * Remove a bridge address from the list.
+ */
+void
+bridge_addrs_remove(struct tp_entry *te, struct bridge_if *bif)
+{
+ if (bif->f_tpa == te)
+ bif->f_tpa = bridge_addrs_bif_next(te);
+
+ TAILQ_REMOVE(&tp_entries, te, tp_e);
+ free(te);
+}
+
+/*
+ * Allocate memory for a new bridge address and insert it in the list.
+ */
+struct tp_entry *
+bridge_new_addrs(uint8_t *mac, struct bridge_if *bif)
+{
+ struct tp_entry *te;
+
+ if ((te = (struct tp_entry *) malloc(sizeof(*te))) == NULL) {
+ syslog(LOG_ERR, "bridge new address: failed: %s",
+ strerror(errno));
+ return (NULL);
+ }
+
+ bzero(te, sizeof(*te));
+
+ te->sysindex = bif->sysindex;
+ bcopy(mac, te->tp_addr, ETHER_ADDR_LEN);
+ bridge_addrs_bif_insert(&tp_entries, te, &(bif->f_tpa));
+
+ return (te);
+}
+
+/*
+ * Given a mac address, learnt on a bridge,
+ * find the corrsponding TP entry for it.
+ */
+struct tp_entry *
+bridge_addrs_find(uint8_t *mac, struct bridge_if *bif)
+{
+ struct tp_entry *te;
+
+ for (te = bif->f_tpa; te != NULL; te = TAILQ_NEXT(te, tp_e)) {
+ if (te->sysindex != bif->sysindex) {
+ te = NULL;
+ break;
+ }
+
+ if (bridge_compare_macs(te->tp_addr, mac) == 0)
+ break;
+ }
+
+ return (te);
+}
+
+void
+bridge_addrs_dump(struct bridge_if *bif)
+{
+ struct tp_entry *te;
+
+ syslog(LOG_ERR, "Addresses count - %d", bif->num_addrs);
+ for (te = bridge_addrs_bif_first(bif); te != NULL;
+ te = bridge_addrs_bif_next(te)) {
+ syslog(LOG_ERR, "address %x:%x:%x:%x:%x:%x on port %d.%d",
+ te->tp_addr[0], te->tp_addr[1], te->tp_addr[2],
+ te->tp_addr[3], te->tp_addr[4], te->tp_addr[5],
+ te->sysindex, te->port_no);
+ }
+}
+
+/*
+ * RFC4188 specifics.
+ */
+
+/*
+ * Construct the SNMP index from the address DST Mac.
+ */
+static void
+bridge_addrs_index_append(struct asn_oid *oid, uint sub,
+ const struct tp_entry *te)
+{
+ int i;
+
+ oid->len = sub + ETHER_ADDR_LEN + 1;
+ oid->subs[sub] = ETHER_ADDR_LEN;
+
+ for (i = 1; i <= ETHER_ADDR_LEN; i++)
+ oid->subs[sub + i] = te->tp_addr[i - 1];
+}
+
+/*
+ * Find the address entry for the SNMP index from the default bridge only.
+ */
+static struct tp_entry *
+bridge_addrs_get(const struct asn_oid *oid, uint sub,
+ struct bridge_if *bif)
+{
+ int i;
+ uint8_t tp_addr[ETHER_ADDR_LEN];
+
+ if (oid->len - sub != ETHER_ADDR_LEN + 1 ||
+ oid->subs[sub] != ETHER_ADDR_LEN)
+ return (NULL);
+
+ for (i = 0; i < ETHER_ADDR_LEN; i++)
+ tp_addr[i] = oid->subs[sub + i + 1];
+
+ return (bridge_addrs_find(tp_addr, bif));
+}
+
+/*
+ * Find the next address entry for the SNMP index
+ * from the default bridge only.
+ */
+static struct tp_entry *
+bridge_addrs_getnext(const struct asn_oid *oid, uint sub,
+ struct bridge_if *bif)
+{
+ int i;
+ uint8_t tp_addr[ETHER_ADDR_LEN];
+ static struct tp_entry *te;
+
+ if (oid->len - sub == 0)
+ return (bridge_addrs_bif_first(bif));
+
+ if (oid->len - sub != ETHER_ADDR_LEN + 1 ||
+ oid->subs[sub] != ETHER_ADDR_LEN)
+ return (NULL);
+
+ for (i = 0; i < ETHER_ADDR_LEN; i++)
+ tp_addr[i] = oid->subs[sub + i + 1];
+
+ if ((te = bridge_addrs_find(tp_addr, bif)) == NULL)
+ return (NULL);
+
+ return (bridge_addrs_bif_next(te));
+}
+
+int
+op_dot1d_tp_fdb(struct snmp_context *c __unused, struct snmp_value *val,
+ uint sub, uint iidx __unused, enum snmp_op op)
+{
+ struct bridge_if *bif;
+ struct tp_entry *te;
+
+ if ((bif = bridge_get_default()) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ if (time(NULL) - bif->addrs_age > bridge_get_data_maxage() &&
+ bridge_update_addrs(bif) <= 0)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((te = bridge_addrs_get(&val->var, sub, bif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_GETNEXT:
+ if ((te = bridge_addrs_getnext(&val->var, sub, bif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ bridge_addrs_index_append(&val->var, sub, te);
+ goto get;
+
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ break;
+ }
+ abort();
+
+get:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_dot1dTpFdbAddress:
+ return (string_get(val, te->tp_addr, ETHER_ADDR_LEN));
+ case LEAF_dot1dTpFdbPort :
+ val->v.integer = te->port_no;
+ return (SNMP_ERR_NOERROR);
+ case LEAF_dot1dTpFdbStatus:
+ val->v.integer = te->status;
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+/*
+ * Private BEGEMOT-BRIDGE-MIB specifics.
+ */
+
+/*
+ * Construct the SNMP index from the bridge interface name
+ * and the address DST Mac.
+ */
+static int
+bridge_addrs_begemot_index_append(struct asn_oid *oid, uint sub,
+ const struct tp_entry *te)
+{
+ uint i, n_len;
+ const char *b_name;
+
+ if ((b_name = bridge_if_find_name(te->sysindex)) == NULL)
+ return (-1);
+
+ n_len = strlen(b_name);
+ oid->len = sub++;
+ oid->subs[oid->len++] = n_len;
+
+ for (i = 1; i <= n_len; i++)
+ oid->subs[oid->len++] = b_name[i - 1];
+
+ oid->subs[oid->len++] = ETHER_ADDR_LEN;
+ for (i = 1 ; i <= ETHER_ADDR_LEN; i++)
+ oid->subs[oid->len++] = te->tp_addr[i - 1];
+
+ return (0);
+}
+
+/*
+ * Find a bridge address entry by the bridge interface name
+ * and the address DST Mac.
+ */
+static struct tp_entry *
+bridge_addrs_begemot_get(const struct asn_oid *oid, uint sub)
+{
+ uint i, n_len;
+ uint8_t tp_addr[ETHER_ADDR_LEN];
+ char bif_name[IFNAMSIZ];
+ struct bridge_if *bif;
+
+ n_len = oid->subs[sub];
+ if (oid->len - sub != n_len + ETHER_ADDR_LEN + 3 ||
+ n_len >= IFNAMSIZ || oid->subs[sub + n_len + 1] != ETHER_ADDR_LEN)
+ return (NULL);
+
+ for (i = 0; i < n_len; i++)
+ bif_name[i] = oid->subs[n_len + i + 1];
+ bif_name[i] = '\0';
+
+ for (i = 1; i <= ETHER_ADDR_LEN; i++)
+ tp_addr[i - 1] = oid->subs[n_len + i + 1];
+
+ if ((bif = bridge_if_find_ifname(bif_name)) == NULL)
+ return (NULL);
+
+ return (bridge_addrs_find(tp_addr, bif));
+}
+
+/*
+ * Find the next bridge address entry by the bridge interface name
+ * and the address DST Mac.
+ */
+static struct tp_entry *
+bridge_addrs_begemot_getnext(const struct asn_oid *oid, uint sub)
+{
+ uint i, n_len;
+ uint8_t tp_addr[ETHER_ADDR_LEN];
+ char bif_name[IFNAMSIZ];
+ struct bridge_if *bif;
+ struct tp_entry *tp;
+
+ if (oid->len - sub == 0)
+ return (bridge_addrs_first());
+
+ n_len = oid->subs[sub];
+ if (oid->len - sub != n_len + ETHER_ADDR_LEN + 2 ||
+ n_len >= IFNAMSIZ || oid->subs[sub + n_len + 1] != ETHER_ADDR_LEN)
+ return (NULL);
+
+ for (i = 1; i <= n_len; i++)
+ bif_name[i - 1] = oid->subs[sub + i];
+
+ bif_name[i - 1] = '\0';
+
+ for (i = 1; i <= ETHER_ADDR_LEN; i++)
+ tp_addr[i - 1] = oid->subs[sub + n_len + i + 1];
+
+ if ((bif = bridge_if_find_ifname(bif_name)) == NULL ||
+ (tp = bridge_addrs_find(tp_addr, bif)) == NULL)
+ return (NULL);
+
+ return (bridge_addrs_next(tp));
+}
+
+int
+op_begemot_tp_fdb(struct snmp_context *c __unused, struct snmp_value *val,
+ uint sub, uint iidx __unused, enum snmp_op op)
+{
+ struct tp_entry *te;
+
+ if (time(NULL) - address_list_age > bridge_get_data_maxage())
+ bridge_update_all_addrs();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((te = bridge_addrs_begemot_get(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_GETNEXT:
+ if ((te = bridge_addrs_begemot_getnext(&val->var,
+ sub)) == NULL ||
+ bridge_addrs_begemot_index_append(&val->var,
+ sub, te) < 0)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ break;
+ }
+ abort();
+
+get:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_begemotBridgeTpFdbAddress:
+ return (string_get(val, te->tp_addr, ETHER_ADDR_LEN));
+ case LEAF_begemotBridgeTpFdbPort:
+ val->v.integer = te->port_no;
+ return (SNMP_ERR_NOERROR);
+ case LEAF_begemotBridgeTpFdbStatus:
+ val->v.integer = te->status;
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
diff --git a/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_if.c b/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_if.c
new file mode 100644
index 0000000..e5f5c50
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_if.c
@@ -0,0 +1,1479 @@
+/*-
+ * Copyright (c) 2006 Shteryana Shopova <syrinx@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.
+ *
+ * Bridge MIB implementation for SNMPd.
+ * Bridge interface objects.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_mib.h>
+#include <net/if_types.h>
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include <bsnmp/snmpmod.h>
+#include <bsnmp/snmp_mibII.h>
+
+#include "bridge_tree.h"
+#include "bridge_snmp.h"
+#include "bridge_oid.h"
+
+static const struct asn_oid oid_newRoot = OIDX_newRoot;
+static const struct asn_oid oid_TopologyChange = OIDX_topologyChange;
+static const struct asn_oid oid_begemotBrigeName = \
+ OIDX_begemotBridgeBaseName;
+static const struct asn_oid oid_begemotNewRoot = OIDX_begemotBridgeNewRoot;
+static const struct asn_oid oid_begemotTopologyChange = \
+ OIDX_begemotBridgeTopologyChange;
+
+TAILQ_HEAD(bridge_ifs, bridge_if);
+
+/*
+ * Free the bridge interface list.
+ */
+static void
+bridge_ifs_free(struct bridge_ifs *headp)
+{
+ struct bridge_if *b;
+
+ while ((b = TAILQ_FIRST(headp)) != NULL) {
+ TAILQ_REMOVE(headp, b, b_if);
+ free(b);
+ }
+}
+
+/*
+ * Insert an entry in the bridge interface TAILQ. Keep the
+ * TAILQ sorted by the bridge's interface name.
+ */
+static void
+bridge_ifs_insert(struct bridge_ifs *headp,
+ struct bridge_if *b)
+{
+ struct bridge_if *temp;
+
+ if ((temp = TAILQ_FIRST(headp)) == NULL ||
+ strcmp(b->bif_name, temp->bif_name) < 0) {
+ TAILQ_INSERT_HEAD(headp, b, b_if);
+ return;
+ }
+
+ TAILQ_FOREACH(temp, headp, b_if)
+ if(strcmp(b->bif_name, temp->bif_name) < 0)
+ TAILQ_INSERT_BEFORE(temp, b, b_if);
+
+ TAILQ_INSERT_TAIL(headp, b, b_if);
+}
+
+/* The global bridge interface list. */
+static struct bridge_ifs bridge_ifs = TAILQ_HEAD_INITIALIZER(bridge_ifs);
+static time_t bridge_list_age;
+
+/*
+ * Free the global list.
+ */
+void
+bridge_ifs_fini(void)
+{
+ bridge_ifs_free(&bridge_ifs);
+}
+
+/*
+ * Find a bridge interface entry by the bridge interface system index.
+ */
+struct bridge_if *
+bridge_if_find_ifs(uint32_t sysindex)
+{
+ struct bridge_if *b;
+
+ TAILQ_FOREACH(b, &bridge_ifs, b_if)
+ if (b->sysindex == sysindex)
+ return (b);
+
+ return (NULL);
+}
+
+/*
+ * Find a bridge interface entry by the bridge interface name.
+ */
+struct bridge_if *
+bridge_if_find_ifname(const char *b_name)
+{
+ struct bridge_if *b;
+
+ TAILQ_FOREACH(b, &bridge_ifs, b_if)
+ if (strcmp(b_name, b->bif_name) == 0)
+ return (b);
+
+ return (NULL);
+}
+
+/*
+ * Find a bridge name by the bridge interface system index.
+ */
+const char *
+bridge_if_find_name(uint32_t sysindex)
+{
+ struct bridge_if *b;
+
+ TAILQ_FOREACH(b, &bridge_ifs, b_if)
+ if (b->sysindex == sysindex)
+ return (b->bif_name);
+
+ return (NULL);
+}
+
+/*
+ * Given two bridge interfaces' system indexes, find their
+ * corresponding names and return the result of the name
+ * comparison. Returns:
+ * error : -2
+ * i1 < i2 : -1
+ * i1 > i2 : +1
+ * i1 = i2 : 0
+ */
+int
+bridge_compare_sysidx(uint32_t i1, uint32_t i2)
+{
+ int c;
+ const char *b1, *b2;
+
+ if (i1 == i2)
+ return (0);
+
+ if ((b1 = bridge_if_find_name(i1)) == NULL) {
+ syslog(LOG_ERR, "Bridge interface %d does not exist", i1);
+ return (-2);
+ }
+
+ if ((b2 = bridge_if_find_name(i2)) == NULL) {
+ syslog(LOG_ERR, "Bridge interface %d does not exist", i2);
+ return (-2);
+ }
+
+ if ((c = strcmp(b1, b2)) < 0)
+ return (-1);
+ else if (c > 0)
+ return (1);
+
+ return (0);
+}
+
+/*
+ * Fetch the first bridge interface from the list.
+ */
+struct bridge_if *
+bridge_first_bif(void)
+{
+ return (TAILQ_FIRST(&bridge_ifs));
+}
+
+/*
+ * Fetch the next bridge interface from the list.
+ */
+struct bridge_if *
+bridge_next_bif(struct bridge_if *b_pr)
+{
+ return (TAILQ_NEXT(b_pr, b_if));
+}
+
+/*
+ * Create a new entry for a bridge interface and insert
+ * it in the list.
+ */
+static struct bridge_if *
+bridge_new_bif(const char *bif_n, uint32_t sysindex, const u_char *physaddr)
+{
+ struct bridge_if *bif;
+
+ if ((bif = (struct bridge_if *) malloc(sizeof(*bif)))== NULL) {
+ syslog(LOG_ERR, "bridge new interface failed: %s",
+ strerror(errno));
+ return (NULL);
+ }
+
+ bzero(bif, sizeof(struct bridge_if));
+ strlcpy(bif->bif_name, bif_n, IFNAMSIZ);
+ bcopy(physaddr, bif->br_addr.octet, ETHER_ADDR_LEN);
+ bif->sysindex = sysindex;
+ bif->br_type = BaseType_transparent_only;
+ /* 1 - all bridges default hold time * 100 - centi-seconds */
+ bif->hold_time = 1 * 100;
+ bif->prot_spec = dot1dStpProtocolSpecification_ieee8021d;
+ bridge_ifs_insert(&bridge_ifs, bif);
+
+ return (bif);
+}
+
+/*
+ * Remove a bridge interface from the list, freeing all it's ports
+ * and address entries.
+ */
+void
+bridge_remove_bif(struct bridge_if *bif)
+{
+ bridge_members_free(bif);
+ bridge_addrs_free(bif);
+ TAILQ_REMOVE(&bridge_ifs, bif, b_if);
+ free(bif);
+}
+
+
+/*
+ * Prepare the variable (bridge interface name) for the private
+ * begemot notifications.
+ */
+static struct snmp_value*
+bridge_basename_var(struct bridge_if *bif, struct snmp_value* b_val)
+{
+ uint i;
+
+ b_val->var = oid_begemotBrigeName;
+ b_val->var.subs[b_val->var.len++] = strlen(bif->bif_name);
+
+ if ((b_val->v.octetstring.octets = (u_char *)
+ malloc(strlen(bif->bif_name))) == NULL)
+ return (NULL);
+
+ for (i = 0; i < strlen(bif->bif_name); i++)
+ b_val->var.subs[b_val->var.len++] = bif->bif_name[i];
+
+ b_val->v.octetstring.len = strlen(bif->bif_name);
+ bcopy(bif->bif_name, b_val->v.octetstring.octets,
+ strlen(bif->bif_name));
+ b_val->syntax = SNMP_SYNTAX_OCTETSTRING;
+
+ return (b_val);
+}
+
+/*
+ * Compare the values of the old and the new root port and
+ * send a new root notification, if they are not matching.
+ */
+static void
+bridge_new_root(struct bridge_if *bif)
+{
+ struct snmp_value bif_idx;
+
+ if (bridge_get_default() == bif)
+ snmp_send_trap(&oid_newRoot, (struct snmp_value *) NULL);
+
+ if (bridge_basename_var(bif, &bif_idx) == NULL)
+ return;
+
+ snmp_send_trap(&oid_begemotTopologyChange,
+ &bif_idx, (struct snmp_value *) NULL);
+}
+
+/*
+ * Compare the new and old topology change times and send a
+ * topology change notification if necessary.
+ */
+static void
+bridge_top_change(struct bridge_if *bif)
+{
+ struct snmp_value bif_idx;
+
+ if (bridge_get_default() == bif)
+ snmp_send_trap(&oid_TopologyChange,
+ (struct snmp_value *) NULL);
+
+ if (bridge_basename_var(bif, &bif_idx) == NULL)
+ return;
+
+ snmp_send_trap(&oid_begemotNewRoot,
+ &bif_idx, (struct snmp_value *) NULL);
+}
+
+static int
+bridge_if_create(const char* b_name, int8_t up)
+{
+ if (bridge_create(b_name) < 0)
+ return (-1);
+
+ if (up == 1 && (bridge_set_if_up(b_name, 1) < 0))
+ return (-1);
+
+ /*
+ * Do not create a new bridge entry here -
+ * wait until the mibII module notifies us.
+ */
+ return (0);
+}
+
+static int
+bridge_if_destroy(struct bridge_if *bif)
+{
+ if (bridge_destroy(bif->bif_name) < 0)
+ return (-1);
+
+ bridge_remove_bif(bif);
+
+ return (0);
+}
+
+/*
+ * Calculate the timeticks since the last topology change.
+ */
+static int
+bridge_get_time_since_tc(struct bridge_if *bif, uint32_t *ticks)
+{
+ struct timeval ct;
+
+ if (gettimeofday(&ct, NULL) < 0) {
+ syslog(LOG_ERR, "bridge get time since last TC:"
+ "getttimeofday failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ if (ct.tv_usec - bif->last_tc_time.tv_usec < 0) {
+ ct.tv_sec -= 1;
+ ct.tv_usec += 1000000;
+ }
+
+ ct.tv_sec -= bif->last_tc_time.tv_sec;
+ ct.tv_usec -= bif->last_tc_time.tv_usec;
+
+ *ticks = ct.tv_sec * 100 + ct.tv_usec/10000;
+
+ return (0);
+}
+
+/*
+ * Update the info we have for a single bridge interface.
+ * Return:
+ * 1, if successful
+ * 0, if the interface was deleted
+ * -1, error occurred while fetching the info from the kernel.
+ */
+static int
+bridge_update_bif(struct bridge_if *bif)
+{
+ struct mibif *ifp;
+
+ /* Walk through the mibII interface list. */
+ for (ifp = mib_first_if(); ifp != NULL; ifp = mib_next_if(ifp))
+ if (strcmp(ifp->name, bif->bif_name) == 0)
+ break;
+
+ if (ifp == NULL) {
+ /* Ops, we do not exist anymore. */
+ bridge_remove_bif(bif);
+ return (0);
+ }
+
+ if (ifp->physaddr != NULL )
+ bcopy(ifp->physaddr, bif->br_addr.octet, ETHER_ADDR_LEN);
+ else
+ bridge_get_basemac(bif->bif_name, bif->br_addr.octet,
+ ETHER_ADDR_LEN);
+
+ if (ifp->mib.ifmd_flags & IFF_RUNNING)
+ bif->if_status = RowStatus_active;
+ else
+ bif->if_status = RowStatus_notInService;
+
+ switch (bridge_getinfo_bif(bif)) {
+ case 2:
+ bridge_new_root(bif);
+ break;
+ case 1:
+ bridge_top_change(bif);
+ break;
+ case -1:
+ bridge_remove_bif(bif);
+ return (-1);
+ default:
+ break;
+ }
+
+ /*
+ * The number of ports is accessible via SNMP -
+ * update the ports each time the bridge interface data
+ * is refreshed too.
+ */
+ bif->num_ports = bridge_update_memif(bif);
+ bif->entry_age = time(NULL);
+
+ return (1);
+}
+
+/*
+ * Update all bridge interfaces' ports only -
+ * make sure each bridge interface exists first.
+ */
+void
+bridge_update_all_ports(void)
+{
+ struct mibif *ifp;
+ struct bridge_if *bif, *t_bif;
+
+ for (bif = bridge_first_bif(); bif != NULL; bif = t_bif) {
+ t_bif = bridge_next_bif(bif);
+
+ for (ifp = mib_first_if(); ifp != NULL;
+ ifp = mib_next_if(ifp))
+ if (strcmp(ifp->name, bif->bif_name) == 0)
+ break;
+
+ if (ifp != NULL)
+ bif->num_ports = bridge_update_memif(bif);
+ else /* Ops, we do not exist anymore. */
+ bridge_remove_bif(bif);
+ }
+
+ bridge_ports_update_listage();
+}
+
+/*
+ * Update all addresses only.
+ */
+void
+bridge_update_all_addrs(void)
+{
+ struct mibif *ifp;
+ struct bridge_if *bif, *t_bif;
+
+ for (bif = bridge_first_bif(); bif != NULL; bif = t_bif) {
+ t_bif = bridge_next_bif(bif);
+
+ for (ifp = mib_first_if(); ifp != NULL;
+ ifp = mib_next_if(ifp))
+ if (strcmp(ifp->name, bif->bif_name) == 0)
+ break;
+
+ if (ifp != NULL)
+ bif->num_addrs = bridge_update_addrs(bif);
+ else /* Ops, we don't exist anymore. */
+ bridge_remove_bif(bif);
+ }
+
+ bridge_addrs_update_listage();
+}
+
+/*
+ * Update only the bridge interfaces' data - skip addresses.
+ */
+void
+bridge_update_all_ifs(void)
+{
+ struct bridge_if *bif, *t_bif;
+
+ for (bif = bridge_first_bif(); bif != NULL; bif = t_bif) {
+ t_bif = bridge_next_bif(bif);
+ bridge_update_bif(bif);
+ }
+
+ bridge_ports_update_listage();
+ bridge_list_age = time(NULL);
+}
+
+/*
+ * Update all info we have for all bridges.
+ */
+void
+bridge_update_all(void *arg __unused)
+{
+ struct bridge_if *bif, *t_bif;
+
+ for (bif = bridge_first_bif(); bif != NULL; bif = t_bif) {
+ t_bif = bridge_next_bif(bif);
+ if (bridge_update_bif(bif) <= 0)
+ continue;
+
+ /* Update our learnt addresses. */
+ bif->num_addrs = bridge_update_addrs(bif);
+ }
+
+ bridge_list_age = time(NULL);
+ bridge_ports_update_listage();
+ bridge_addrs_update_listage();
+}
+
+/*
+ * Callback for polling our last topology change time -
+ * check whether we are root or whether a TC was detected once every
+ * 30 seconds, so that we can send the newRoot and TopologyChange traps
+ * on time. The rest of the data is polled only once every 5 min.
+ */
+void
+bridge_update_tc_time(void *arg __unused)
+{
+ struct bridge_if *bif;
+ struct mibif *ifp;
+
+ TAILQ_FOREACH(bif, &bridge_ifs, b_if) {
+ /* Walk through the mibII interface list. */
+ for (ifp = mib_first_if(); ifp != NULL; ifp = mib_next_if(ifp))
+ if (strcmp(ifp->name, bif->bif_name) == 0)
+ break;
+
+ if (ifp == NULL) {
+ bridge_remove_bif(bif);
+ continue;
+ }
+
+ switch (bridge_get_op_param(bif)) {
+ case 2:
+ bridge_new_root(bif);
+ break;
+ case 1:
+ bridge_top_change(bif);
+ break;
+ }
+ }
+}
+
+/*
+ * Callback for handling new bridge interface creation.
+ */
+int
+bridge_attach_newif(struct mibif *ifp)
+{
+ u_char mac[ETHER_ADDR_LEN];
+ struct bridge_if *bif;
+
+ if (ifp->mib.ifmd_data.ifi_type != IFT_BRIDGE)
+ return (0);
+
+ /* Make sure it does not exist in our list. */
+ TAILQ_FOREACH(bif, &bridge_ifs, b_if)
+ if(strcmp(bif->bif_name, ifp->name) == 0) {
+ syslog(LOG_ERR, "bridge interface %s already "
+ "in list", bif->bif_name);
+ return (-1);
+ }
+
+ if (ifp->physaddr == NULL) {
+ if (bridge_get_basemac(ifp->name, mac, sizeof(mac)) == NULL) {
+ syslog(LOG_ERR, "bridge attach new %s failed - "
+ "no bridge mac address", ifp->name);
+ return (-1);
+ }
+ } else
+ bcopy(ifp->physaddr, &mac, sizeof(mac));
+
+ if ((bif = bridge_new_bif(ifp->name, ifp->sysindex, mac)) == NULL)
+ return (-1);
+
+ if (ifp->mib.ifmd_flags & IFF_RUNNING)
+ bif->if_status = RowStatus_active;
+ else
+ bif->if_status = RowStatus_notInService;
+
+ /* Skip sending notifications if the interface was just created. */
+ if (bridge_getinfo_bif(bif) < 0 ||
+ (bif->num_ports = bridge_getinfo_bif_ports(bif)) < 0 ||
+ (bif->num_addrs = bridge_getinfo_bif_addrs(bif)) < 0) {
+ bridge_remove_bif(bif);
+ return (-1);
+ }
+
+ /* Check whether we are the default bridge interface. */
+ if (strcmp(ifp->name, bridge_get_default_name()) == 0)
+ bridge_set_default(bif);
+
+ return (0);
+}
+
+void
+bridge_ifs_dump(void)
+{
+ struct bridge_if *bif;
+
+ for (bif = bridge_first_bif(); bif != NULL;
+ bif = bridge_next_bif(bif)) {
+ syslog(LOG_ERR, "Bridge %s, index - %d", bif->bif_name,
+ bif->sysindex);
+ bridge_ports_dump(bif);
+ bridge_addrs_dump(bif);
+ }
+}
+
+/*
+ * RFC4188 specifics.
+ */
+int
+op_dot1d_base(struct snmp_context *ctx __unused, struct snmp_value *value,
+ uint sub, uint iidx __unused, enum snmp_op op)
+{
+ struct bridge_if *bif;
+
+ if ((bif = bridge_get_default()) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ if (time(NULL) - bif->entry_age > bridge_get_data_maxage() &&
+ bridge_update_bif(bif) <= 0) /* It was just deleted. */
+ return (SNMP_ERR_NOSUCHNAME);
+
+ switch (op) {
+ case SNMP_OP_GET:
+ switch (value->var.subs[sub - 1]) {
+ case LEAF_dot1dBaseBridgeAddress:
+ return (string_get(value, bif->br_addr.octet,
+ ETHER_ADDR_LEN));
+ case LEAF_dot1dBaseNumPorts:
+ value->v.integer = bif->num_ports;
+ return (SNMP_ERR_NOERROR);
+ case LEAF_dot1dBaseType:
+ value->v.integer = bif->br_type;
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case SNMP_OP_GETNEXT:
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ break;
+ }
+
+ abort();
+}
+
+int
+op_dot1d_stp(struct snmp_context *ctx, struct snmp_value *val, uint sub,
+ uint iidx __unused, enum snmp_op op)
+{
+ struct bridge_if *bif;
+
+ if ((bif = bridge_get_default()) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ if (time(NULL) - bif->entry_age > bridge_get_data_maxage() &&
+ bridge_update_bif(bif) <= 0) /* It was just deleted. */
+ return (SNMP_ERR_NOSUCHNAME);
+
+ switch (op) {
+ case SNMP_OP_GET:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_dot1dStpProtocolSpecification:
+ val->v.integer = bif->prot_spec;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpPriority:
+ val->v.integer = bif->priority;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpTimeSinceTopologyChange:
+ if (bridge_get_time_since_tc(bif,
+ &(val->v.uint32)) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpTopChanges:
+ val->v.uint32 = bif->top_changes;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpDesignatedRoot:
+ return (string_get(val, bif->design_root,
+ SNMP_BRIDGE_ID_LEN));
+
+ case LEAF_dot1dStpRootCost:
+ val->v.integer = bif->root_cost;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpRootPort:
+ val->v.integer = bif->root_port;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpMaxAge:
+ val->v.integer = bif->max_age;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpHelloTime:
+ val->v.integer = bif->hello_time;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpHoldTime:
+ val->v.integer = bif->hold_time;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpForwardDelay:
+ val->v.integer = bif->fwd_delay;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpBridgeMaxAge:
+ val->v.integer = bif->bridge_max_age;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpBridgeHelloTime:
+ val->v.integer = bif->bridge_hello_time;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpBridgeForwardDelay:
+ val->v.integer = bif->bridge_fwd_delay;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpVersion:
+ val->v.integer = bif->stp_version;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpTxHoldCount:
+ val->v.integer = bif->tx_hold_count;
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+
+ case SNMP_OP_GETNEXT:
+ abort();
+
+ case SNMP_OP_SET:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_dot1dStpPriority:
+ if (val->v.integer > SNMP_BRIDGE_MAX_PRIORITY ||
+ val->v.integer % 4096 != 0)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bif->priority;
+ if (bridge_set_priority(bif, val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpBridgeMaxAge:
+ if (val->v.integer < SNMP_BRIDGE_MIN_MAGE ||
+ val->v.integer > SNMP_BRIDGE_MAX_MAGE)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bif->bridge_max_age;
+ if (bridge_set_maxage(bif, val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpBridgeHelloTime:
+ if (val->v.integer < SNMP_BRIDGE_MIN_HTIME ||
+ val->v.integer > SNMP_BRIDGE_MAX_HTIME)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bif->bridge_hello_time;
+ if (bridge_set_hello_time(bif, val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpBridgeForwardDelay:
+ if (val->v.integer < SNMP_BRIDGE_MIN_FDELAY ||
+ val->v.integer > SNMP_BRIDGE_MAX_FDELAY)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bif->bridge_fwd_delay;
+ if (bridge_set_forward_delay(bif, val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpVersion:
+ if (val->v.integer != dot1dStpVersion_stpCompatible &&
+ val->v.integer != dot1dStpVersion_rstp)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bif->stp_version;
+ if (bridge_set_stp_version(bif, val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpTxHoldCount:
+ if (val->v.integer < SNMP_BRIDGE_MIN_TXHC ||
+ val->v.integer > SNMP_BRIDGE_MAX_TXHC)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bif->tx_hold_count;
+ if (bridge_set_tx_hold_count(bif, val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpProtocolSpecification:
+ case LEAF_dot1dStpTimeSinceTopologyChange:
+ case LEAF_dot1dStpTopChanges:
+ case LEAF_dot1dStpDesignatedRoot:
+ case LEAF_dot1dStpRootCost:
+ case LEAF_dot1dStpRootPort:
+ case LEAF_dot1dStpMaxAge:
+ case LEAF_dot1dStpHelloTime:
+ case LEAF_dot1dStpHoldTime:
+ case LEAF_dot1dStpForwardDelay:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ }
+ abort();
+
+ case SNMP_OP_ROLLBACK:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_dot1dStpPriority:
+ bridge_set_priority(bif, ctx->scratch->int1);
+ break;
+ case LEAF_dot1dStpBridgeMaxAge:
+ bridge_set_maxage(bif, ctx->scratch->int1);
+ break;
+ case LEAF_dot1dStpBridgeHelloTime:
+ bridge_set_hello_time(bif, ctx->scratch->int1);
+ break;
+ case LEAF_dot1dStpBridgeForwardDelay:
+ bridge_set_forward_delay(bif, ctx->scratch->int1);
+ break;
+ case LEAF_dot1dStpVersion:
+ bridge_set_stp_version(bif, ctx->scratch->int1);
+ break;
+ case LEAF_dot1dStpTxHoldCount:
+ bridge_set_tx_hold_count(bif, ctx->scratch->int1);
+ break;
+ }
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_COMMIT:
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+int
+op_dot1d_tp(struct snmp_context *ctx, struct snmp_value *value,
+ uint sub, uint iidx __unused, enum snmp_op op)
+{
+ struct bridge_if *bif;
+
+ if ((bif = bridge_get_default()) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ if (time(NULL) - bif->entry_age > bridge_get_data_maxage() &&
+ bridge_update_bif(bif) <= 0) /* It was just deleted. */
+ return (SNMP_ERR_NOSUCHNAME);
+
+ switch (op) {
+ case SNMP_OP_GET:
+ switch (value->var.subs[sub - 1]) {
+ case LEAF_dot1dTpLearnedEntryDiscards:
+ value->v.uint32 = bif->lrnt_drops;
+ return (SNMP_ERR_NOERROR);
+ case LEAF_dot1dTpAgingTime:
+ value->v.integer = bif->age_time;
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+
+ case SNMP_OP_GETNEXT:
+ abort();
+
+ case SNMP_OP_SET:
+ switch (value->var.subs[sub - 1]) {
+ case LEAF_dot1dTpLearnedEntryDiscards:
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case LEAF_dot1dTpAgingTime:
+ if (value->v.integer < SNMP_BRIDGE_MIN_AGE_TIME ||
+ value->v.integer > SNMP_BRIDGE_MAX_AGE_TIME)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bif->age_time;
+ if (bridge_set_aging_time(bif, value->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+
+ case SNMP_OP_ROLLBACK:
+ if (value->var.subs[sub - 1] == LEAF_dot1dTpAgingTime)
+ bridge_set_aging_time(bif, ctx->scratch->int1);
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_COMMIT:
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+/*
+ * Private BEGEMOT-BRIDGE-MIB specifics.
+ */
+
+/*
+ * Get the bridge name from an OID index.
+ */
+static char *
+bridge_name_index_get(const struct asn_oid *oid, uint sub, char *b_name)
+{
+ uint i;
+
+ if (oid->len - sub != oid->subs[sub] + 1 || oid->subs[sub] >= IFNAMSIZ)
+ return (NULL);
+
+ for (i = 0; i < oid->subs[sub]; i++)
+ b_name[i] = oid->subs[sub + i + 1];
+ b_name[i] = '\0';
+
+ return (b_name);
+}
+
+static void
+bridge_if_index_append(struct asn_oid *oid, uint sub,
+ const struct bridge_if *bif)
+{
+ uint i;
+
+ oid->len = sub + strlen(bif->bif_name) + 1;
+ oid->subs[sub] = strlen(bif->bif_name);
+
+ for (i = 1; i <= strlen(bif->bif_name); i++)
+ oid->subs[sub + i] = bif->bif_name[i - 1];
+}
+
+static struct bridge_if *
+bridge_if_index_get(const struct asn_oid *oid, uint sub)
+{
+ uint i;
+ char bif_name[IFNAMSIZ];
+
+ if (oid->len - sub != oid->subs[sub] + 1 || oid->subs[sub] >= IFNAMSIZ)
+ return (NULL);
+
+ for (i = 0; i < oid->subs[sub]; i++)
+ bif_name[i] = oid->subs[sub + i + 1];
+ bif_name[i] = '\0';
+
+ return (bridge_if_find_ifname(bif_name));
+}
+
+static struct bridge_if *
+bridge_if_index_getnext(const struct asn_oid *oid, uint sub)
+{
+ uint i;
+ char bif_name[IFNAMSIZ];
+ struct bridge_if *bif;
+
+ if (oid->len - sub == 0)
+ return (bridge_first_bif());
+
+ if (oid->len - sub != oid->subs[sub] + 1 || oid->subs[sub] >= IFNAMSIZ)
+ return (NULL);
+
+ for (i = 0; i < oid->subs[sub]; i++)
+ bif_name[i] = oid->subs[sub + i + 1];
+ bif_name[i] = '\0';
+
+ if ((bif = bridge_if_find_ifname(bif_name)) == NULL)
+ return (NULL);
+
+ return (bridge_next_bif(bif));
+}
+
+static int
+bridge_set_if_status(struct snmp_context *ctx,
+ struct snmp_value *val, uint sub)
+{
+ struct bridge_if *bif;
+ char bif_name[IFNAMSIZ];
+
+ bif = bridge_if_index_get(&val->var, sub);
+
+ switch (val->v.integer) {
+ case RowStatus_active:
+ if (bif == NULL)
+ return (SNMP_ERR_INCONS_VALUE);
+
+ ctx->scratch->int1 = bif->if_status;
+
+ switch (bif->if_status) {
+ case RowStatus_active:
+ return (SNMP_ERR_NOERROR);
+ case RowStatus_notInService:
+ if (bridge_set_if_up(bif->bif_name, 1) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+ default:
+ break;
+ }
+ return (SNMP_ERR_INCONS_VALUE);
+
+ case RowStatus_notInService:
+ if (bif == NULL)
+ return (SNMP_ERR_INCONS_VALUE);
+
+ ctx->scratch->int1 = bif->if_status;
+
+ switch (bif->if_status) {
+ case RowStatus_active:
+ if (bridge_set_if_up(bif->bif_name, 1) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+ case RowStatus_notInService:
+ return (SNMP_ERR_NOERROR);
+ default:
+ break;
+ }
+ return (SNMP_ERR_INCONS_VALUE);
+
+ case RowStatus_notReady:
+ return (SNMP_ERR_INCONS_VALUE);
+
+ case RowStatus_createAndGo:
+ if (bif != NULL)
+ return (SNMP_ERR_INCONS_VALUE);
+
+ ctx->scratch->int1 = RowStatus_destroy;
+
+ if (bridge_name_index_get(&val->var, sub, bif_name) == NULL)
+ return (SNMP_ERR_BADVALUE);
+ if (bridge_if_create(bif_name, 1) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case RowStatus_createAndWait:
+ if (bif != NULL)
+ return (SNMP_ERR_INCONS_VALUE);
+
+ if (bridge_name_index_get(&val->var, sub, bif_name) == NULL)
+ return (SNMP_ERR_BADVALUE);
+
+ ctx->scratch->int1 = RowStatus_destroy;
+
+ if (bridge_if_create(bif_name, 0) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case RowStatus_destroy:
+ if (bif == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ ctx->scratch->int1 = bif->if_status;
+ bif->if_status = RowStatus_destroy;
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+static int
+bridge_rollback_if_status(struct snmp_context *ctx,
+ struct snmp_value *val, uint sub)
+{
+ struct bridge_if *bif;
+
+ if ((bif = bridge_if_index_get(&val->var, sub)) == NULL)
+ return (SNMP_ERR_GENERR);
+
+ switch (ctx->scratch->int1) {
+ case RowStatus_destroy:
+ bridge_if_destroy(bif);
+ return (SNMP_ERR_NOERROR);
+
+ case RowStatus_notInService:
+ if (bif->if_status != ctx->scratch->int1)
+ bridge_set_if_up(bif->bif_name, 0);
+ bif->if_status = RowStatus_notInService;
+ return (SNMP_ERR_NOERROR);
+
+ case RowStatus_active:
+ if (bif->if_status != ctx->scratch->int1)
+ bridge_set_if_up(bif->bif_name, 1);
+ bif->if_status = RowStatus_active;
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+static int
+bridge_commit_if_status(struct snmp_value *val, uint sub)
+{
+ struct bridge_if *bif;
+
+ if ((bif = bridge_if_index_get(&val->var, sub)) == NULL)
+ return (SNMP_ERR_GENERR);
+
+ if (bif->if_status == RowStatus_destroy &&
+ bridge_if_destroy(bif) < 0)
+ return (SNMP_ERR_COMMIT_FAILED);
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+op_begemot_base_bridge(struct snmp_context *ctx, struct snmp_value *val,
+ uint sub, uint iidx __unused, enum snmp_op op)
+{
+ struct bridge_if *bif;
+
+ if (time(NULL) - bridge_list_age > bridge_get_data_maxage())
+ bridge_update_all_ifs();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((bif = bridge_if_index_get(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_GETNEXT:
+ if ((bif = bridge_if_index_getnext(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ bridge_if_index_append(&val->var, sub, bif);
+ goto get;
+
+ case SNMP_OP_SET:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_begemotBridgeBaseStatus:
+ return (bridge_set_if_status(ctx, val, sub));
+ case LEAF_begemotBridgeBaseName:
+ case LEAF_begemotBridgeBaseAddress:
+ case LEAF_begemotBridgeBaseNumPorts:
+ case LEAF_begemotBridgeBaseType:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ }
+ abort();
+
+ case SNMP_OP_ROLLBACK:
+ return (bridge_rollback_if_status(ctx, val, sub));
+
+ case SNMP_OP_COMMIT:
+ return (bridge_commit_if_status(val, sub));
+ }
+ abort();
+
+get:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_begemotBridgeBaseName:
+ return (string_get(val, bif->bif_name, -1));
+
+ case LEAF_begemotBridgeBaseAddress:
+ return (string_get(val, bif->br_addr.octet, ETHER_ADDR_LEN));
+
+ case LEAF_begemotBridgeBaseNumPorts:
+ val->v.integer = bif->num_ports;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeBaseType:
+ val->v.integer = bif->br_type;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeBaseStatus:
+ val->v.integer = bif->if_status;
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+int
+op_begemot_stp(struct snmp_context *ctx, struct snmp_value *val,
+ uint sub, uint iidx __unused, enum snmp_op op)
+{
+ struct bridge_if *bif;
+
+ if (time(NULL) - bridge_list_age > bridge_get_data_maxage())
+ bridge_update_all_ifs();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((bif = bridge_if_index_get(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_GETNEXT:
+ if ((bif = bridge_if_index_getnext(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ bridge_if_index_append(&val->var, sub, bif);
+ goto get;
+
+ case SNMP_OP_SET:
+ if ((bif = bridge_if_index_get(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_begemotBridgeStpPriority:
+ if (val->v.integer > SNMP_BRIDGE_MAX_PRIORITY ||
+ val->v.integer % 4096 != 0)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bif->priority;
+ if (bridge_set_priority(bif, val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpBridgeMaxAge:
+ if (val->v.integer < SNMP_BRIDGE_MIN_MAGE ||
+ val->v.integer > SNMP_BRIDGE_MAX_MAGE)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bif->bridge_max_age;
+ if (bridge_set_maxage(bif, val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpBridgeHelloTime:
+ if (val->v.integer < SNMP_BRIDGE_MIN_HTIME ||
+ val->v.integer > SNMP_BRIDGE_MAX_HTIME)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bif->bridge_hello_time;
+ if (bridge_set_hello_time(bif, val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpBridgeForwardDelay:
+ if (val->v.integer < SNMP_BRIDGE_MIN_FDELAY ||
+ val->v.integer > SNMP_BRIDGE_MAX_FDELAY)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bif->bridge_fwd_delay;
+ if (bridge_set_forward_delay(bif, val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpVersion:
+ if (val->v.integer !=
+ begemotBridgeStpVersion_stpCompatible &&
+ val->v.integer != begemotBridgeStpVersion_rstp)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bif->stp_version;
+ if (bridge_set_stp_version(bif, val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpTxHoldCount:
+ if (val->v.integer < SNMP_BRIDGE_MIN_TXHC ||
+ val->v.integer > SNMP_BRIDGE_MAX_TXHC)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bif->tx_hold_count;
+ if (bridge_set_tx_hold_count(bif, val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpProtocolSpecification:
+ case LEAF_begemotBridgeStpTimeSinceTopologyChange:
+ case LEAF_begemotBridgeStpTopChanges:
+ case LEAF_begemotBridgeStpDesignatedRoot:
+ case LEAF_begemotBridgeStpRootCost:
+ case LEAF_begemotBridgeStpRootPort:
+ case LEAF_begemotBridgeStpMaxAge:
+ case LEAF_begemotBridgeStpHelloTime:
+ case LEAF_begemotBridgeStpHoldTime:
+ case LEAF_begemotBridgeStpForwardDelay:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ }
+ abort();
+
+ case SNMP_OP_ROLLBACK:
+ if ((bif = bridge_if_index_get(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_begemotBridgeStpPriority:
+ bridge_set_priority(bif, ctx->scratch->int1);
+ break;
+
+ case LEAF_begemotBridgeStpBridgeMaxAge:
+ bridge_set_maxage(bif, ctx->scratch->int1);
+ break;
+
+ case LEAF_begemotBridgeStpBridgeHelloTime:
+ bridge_set_hello_time(bif, ctx->scratch->int1);
+ break;
+
+ case LEAF_begemotBridgeStpBridgeForwardDelay:
+ bridge_set_forward_delay(bif, ctx->scratch->int1);
+ break;
+
+ case LEAF_begemotBridgeStpVersion:
+ bridge_set_stp_version(bif, ctx->scratch->int1);
+ break;
+
+ case LEAF_begemotBridgeStpTxHoldCount:
+ bridge_set_tx_hold_count(bif, ctx->scratch->int1);
+ break;
+ }
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_COMMIT:
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+
+get:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_begemotBridgeStpProtocolSpecification:
+ val->v.integer = bif->prot_spec;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpPriority:
+ val->v.integer = bif->priority;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpTimeSinceTopologyChange:
+ if (bridge_get_time_since_tc(bif, &(val->v.uint32)) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpTopChanges:
+ val->v.uint32 = bif->top_changes;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpDesignatedRoot:
+ return (string_get(val, bif->design_root, SNMP_BRIDGE_ID_LEN));
+
+ case LEAF_begemotBridgeStpRootCost:
+ val->v.integer = bif->root_cost;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpRootPort:
+ val->v.integer = bif->root_port;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpMaxAge:
+ val->v.integer = bif->max_age;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpHelloTime:
+ val->v.integer = bif->hello_time;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpHoldTime:
+ val->v.integer = bif->hold_time;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpForwardDelay:
+ val->v.integer = bif->fwd_delay;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpBridgeMaxAge:
+ val->v.integer = bif->bridge_max_age;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpBridgeHelloTime:
+ val->v.integer = bif->bridge_hello_time;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpBridgeForwardDelay:
+ val->v.integer = bif->bridge_fwd_delay;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpVersion:
+ val->v.integer = bif->stp_version;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpTxHoldCount:
+ val->v.integer = bif->tx_hold_count;
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+int
+op_begemot_tp(struct snmp_context *ctx, struct snmp_value *val,
+ uint sub, uint iidx __unused, enum snmp_op op)
+{
+ struct bridge_if *bif;
+
+ if (time(NULL) - bridge_list_age > bridge_get_data_maxage())
+ bridge_update_all_ifs();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((bif = bridge_if_index_get(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_GETNEXT:
+ if ((bif = bridge_if_index_getnext(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ bridge_if_index_append(&val->var, sub, bif);
+ goto get;
+
+ case SNMP_OP_SET:
+ if ((bif = bridge_if_index_get(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_begemotBridgeTpAgingTime:
+ if (val->v.integer < SNMP_BRIDGE_MIN_AGE_TIME ||
+ val->v.integer > SNMP_BRIDGE_MAX_AGE_TIME)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bif->age_time;
+ if (bridge_set_aging_time(bif, val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeTpMaxAddresses:
+ ctx->scratch->int1 = bif->max_addrs;
+ if (bridge_set_max_cache(bif, val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeTpLearnedEntryDiscards:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ }
+ abort();
+
+ case SNMP_OP_ROLLBACK:
+ if ((bif = bridge_if_index_get(&val->var, sub)) == NULL)
+ return (SNMP_ERR_GENERR);
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_begemotBridgeTpAgingTime:
+ bridge_set_aging_time(bif, ctx->scratch->int1);
+ break;
+
+ case LEAF_begemotBridgeTpMaxAddresses:
+ bridge_set_max_cache(bif, ctx->scratch->int1);
+ break;
+ }
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_COMMIT:
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+
+get:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_begemotBridgeTpLearnedEntryDiscards:
+ val->v.uint32 = bif->lrnt_drops;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeTpAgingTime:
+ val->v.integer = bif->age_time;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeTpMaxAddresses:
+ val->v.integer = bif->max_addrs;
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
diff --git a/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_pf.c b/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_pf.c
new file mode 100644
index 0000000..0ad8cbf
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_pf.c
@@ -0,0 +1,116 @@
+/*-
+ * Copyright (c) 2006 Shteryana Shopova <syrinx@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.
+ *
+ * Bridge MIB implementation for SNMPd.
+ * Bridge pfil controls.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <sys/socket.h>
+
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_mib.h>
+#include <net/if_types.h>
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <syslog.h>
+
+#include <bsnmp/snmpmod.h>
+#include <bsnmp/snmp_mibII.h>
+
+#include "bridge_tree.h"
+#include "bridge_snmp.h"
+
+static int
+val2snmp_truth(uint8_t val)
+{
+ if (val == 0)
+ return (2);
+
+ return (1);
+}
+
+static int
+snmp_truth2val(int32_t truth)
+{
+ if (truth == 2)
+ return (0);
+ else if (truth == 1)
+ return (1);
+
+ return (-1);
+}
+
+int
+op_begemot_bridge_pf(struct snmp_context *ctx, struct snmp_value *val,
+ uint sub, uint iidx __unused, enum snmp_op op)
+{
+ int k_val;
+
+ if (val->var.subs[sub - 1] > LEAF_begemotBridgeLayer2PfStatus)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ switch (op) {
+ case SNMP_OP_GETNEXT:
+ abort();
+ case SNMP_OP_ROLLBACK:
+ bridge_do_pfctl(val->var.subs[sub - 1] - 1,
+ op, &(ctx->scratch->int1));
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_COMMIT:
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_SET:
+ ctx->scratch->int1 =
+ bridge_get_pfval(val->var.subs[sub - 1]);
+
+ if ((k_val = snmp_truth2val(val->v.integer)) < 0)
+ return (SNMP_ERR_BADVALUE);
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_GET:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_begemotBridgePfilStatus:
+ case LEAF_begemotBridgePfilMembers:
+ case LEAF_begemotBridgePfilIpOnly:
+ case LEAF_begemotBridgeLayer2PfStatus:
+ if (bridge_do_pfctl(val->var.subs[sub - 1] - 1,
+ op, &k_val) < 0)
+ return (SNMP_ERR_GENERR);
+ val->v.integer = val2snmp_truth(k_val);
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+ }
+
+ abort();
+}
diff --git a/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_port.c b/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_port.c
new file mode 100644
index 0000000..0127cea
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_port.c
@@ -0,0 +1,1513 @@
+/*-
+ * Copyright (c) 2006 Shteryana Shopova <syrinx@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.
+ *
+ * Bridge MIB implementation for SNMPd.
+ * Bridge ports.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_mib.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+#include <syslog.h>
+
+#include <bsnmp/snmpmod.h>
+#include <bsnmp/snmp_mibII.h>
+
+#include "bridge_tree.h"
+#include "bridge_snmp.h"
+
+TAILQ_HEAD(bridge_ports, bridge_port);
+
+/*
+ * Free the bridge base ports list.
+ */
+static void
+bridge_ports_free(struct bridge_ports *headp)
+{
+ struct bridge_port *bp;
+
+ while ((bp = TAILQ_FIRST(headp)) != NULL) {
+ TAILQ_REMOVE(headp, bp, b_p);
+ free(bp);
+ }
+}
+
+/*
+ * Free the bridge base ports from the base ports list,
+ * members of a specified bridge interface only.
+ */
+static void
+bridge_port_memif_free(struct bridge_ports *headp,
+ struct bridge_if *bif)
+{
+ struct bridge_port *bp;
+
+ while (bif->f_bp != NULL && bif->sysindex == bif->f_bp->sysindex) {
+ bp = TAILQ_NEXT(bif->f_bp, b_p);
+ TAILQ_REMOVE(headp, bif->f_bp, b_p);
+ free(bif->f_bp);
+ bif->f_bp = bp;
+ }
+}
+
+/*
+ * Insert a port entry in the base port TAILQ starting to search
+ * for its place from the position of the first bridge port for the bridge
+ * interface. Update the first bridge port if necessary.
+ */
+static void
+bridge_port_insert_at(struct bridge_ports *headp,
+ struct bridge_port *bp, struct bridge_port **f_bp)
+{
+ struct bridge_port *t1;
+
+ assert(f_bp != NULL);
+
+ for (t1 = *f_bp;
+ t1 != NULL && bp->sysindex == t1->sysindex;
+ t1 = TAILQ_NEXT(t1, b_p)) {
+ if (bp->if_idx < t1->if_idx) {
+ TAILQ_INSERT_BEFORE(t1, bp, b_p);
+ if (*f_bp == t1)
+ *f_bp = bp;
+ return;
+ }
+ }
+
+ /*
+ * Handle the case when our first port was actually the
+ * last element of the TAILQ.
+ */
+ if (t1 == NULL)
+ TAILQ_INSERT_TAIL(headp, bp, b_p);
+ else
+ TAILQ_INSERT_BEFORE(t1, bp, b_p);
+}
+
+/*
+ * Find a port entry's position in the ports list according
+ * to it's parent bridge interface name. Returns a NULL if
+ * we should be at the TAILQ head, otherwise the entry after
+ * which we should be inserted.
+ */
+static struct bridge_port *
+bridge_port_find_pos(struct bridge_ports *headp, uint32_t b_idx)
+{
+ uint32_t t_idx;
+ struct bridge_port *t1;
+
+ if ((t1 = TAILQ_FIRST(headp)) == NULL ||
+ bridge_compare_sysidx(b_idx, t1->sysindex) < 0)
+ return (NULL);
+
+ t_idx = t1->sysindex;
+
+ for (t1 = TAILQ_NEXT(t1, b_p); t1 != NULL; t1 = TAILQ_NEXT(t1, b_p)) {
+ if (t1->sysindex != t_idx) {
+ if (bridge_compare_sysidx(b_idx, t1->sysindex) < 0)
+ return (TAILQ_PREV(t1, bridge_ports, b_p));
+ else
+ t_idx = t1->sysindex;
+ }
+ }
+
+ if (t1 == NULL)
+ t1 = TAILQ_LAST(headp, bridge_ports);
+
+ return (t1);
+}
+
+/*
+ * Insert a bridge member interface in the ports TAILQ.
+ */
+static void
+bridge_port_memif_insert(struct bridge_ports *headp,
+ struct bridge_port *bp, struct bridge_port **f_bp)
+{
+ struct bridge_port *temp;
+
+ if (*f_bp != NULL)
+ bridge_port_insert_at(headp, bp, f_bp);
+ else {
+ temp = bridge_port_find_pos(headp, bp->sysindex);
+
+ if (temp == NULL)
+ TAILQ_INSERT_HEAD(headp, bp, b_p);
+ else
+ TAILQ_INSERT_AFTER(headp, temp, bp, b_p);
+ *f_bp = bp;
+ }
+}
+
+/* The global ports list. */
+static struct bridge_ports bridge_ports = TAILQ_HEAD_INITIALIZER(bridge_ports);
+static time_t ports_list_age;
+
+void
+bridge_ports_update_listage(void)
+{
+ ports_list_age = time(NULL);
+}
+
+void
+bridge_ports_fini(void)
+{
+ bridge_ports_free(&bridge_ports);
+}
+
+void
+bridge_members_free(struct bridge_if *bif)
+{
+ bridge_port_memif_free(&bridge_ports, bif);
+}
+
+/*
+ * Find the first port in the ports list.
+ */
+static struct bridge_port *
+bridge_port_first(void)
+{
+ return (TAILQ_FIRST(&bridge_ports));
+}
+
+/*
+ * Find the next port in the ports list.
+ */
+static struct bridge_port *
+bridge_port_next(struct bridge_port *bp)
+{
+ return (TAILQ_NEXT(bp, b_p));
+}
+
+/*
+ * Find the first member of the specified bridge interface.
+ */
+struct bridge_port *
+bridge_port_bif_first(struct bridge_if *bif)
+{
+ return (bif->f_bp);
+}
+
+/*
+ * Find the next member of the specified bridge interface.
+ */
+struct bridge_port *
+bridge_port_bif_next(struct bridge_port *bp)
+{
+ struct bridge_port *bp_next;
+
+ if ((bp_next = TAILQ_NEXT(bp, b_p)) == NULL ||
+ bp_next->sysindex != bp->sysindex)
+ return (NULL);
+
+ return (bp_next);
+}
+
+/*
+ * Remove a bridge port from the ports list.
+ */
+void
+bridge_port_remove(struct bridge_port *bp, struct bridge_if *bif)
+{
+ if (bif->f_bp == bp)
+ bif->f_bp = bridge_port_bif_next(bp);
+
+ TAILQ_REMOVE(&bridge_ports, bp, b_p);
+ free(bp);
+}
+
+/*
+ * Allocate memory for a new bridge port and insert it
+ * in the base ports list. Return a pointer to the port's
+ * structure in case we want to do anything else with it.
+ */
+struct bridge_port *
+bridge_new_port(struct mibif *mif, struct bridge_if *bif)
+{
+ struct bridge_port *bp;
+
+ if ((bp = (struct bridge_port *) malloc(sizeof(*bp))) == NULL) {
+ syslog(LOG_ERR, "bridge new member: failed: %s",
+ strerror(errno));
+ return (NULL);
+ }
+
+ bzero(bp, sizeof(*bp));
+
+ bp->sysindex = bif->sysindex;
+ bp->if_idx = mif->index;
+ bp->port_no = mif->sysindex;
+ strlcpy(bp->p_name, mif->name, IFNAMSIZ);
+ bp->circuit = oid_zeroDotZero;
+
+ /*
+ * Initialize all rstpMib specific values to false/default.
+ * These will be set to their true values later if the bridge
+ * supports RSTP.
+ */
+ bp->proto_migr = TruthValue_false;
+ bp->admin_edge = TruthValue_false;
+ bp->oper_edge = TruthValue_false;
+ bp->oper_ptp = TruthValue_false;
+ bp->admin_ptp = StpPortAdminPointToPointType_auto;
+
+ bridge_port_memif_insert(&bridge_ports, bp, &(bif->f_bp));
+
+ return (bp);
+}
+
+/*
+ * Update our info from the corresponding mibII interface info.
+ */
+void
+bridge_port_getinfo_mibif(struct mibif *m_if, struct bridge_port *bp)
+{
+ bp->max_info = m_if->mib.ifmd_data.ifi_mtu;
+ bp->in_frames = m_if->mib.ifmd_data.ifi_ipackets;
+ bp->out_frames = m_if->mib.ifmd_data.ifi_opackets;
+ bp->in_drops = m_if->mib.ifmd_data.ifi_iqdrops;
+}
+
+/*
+ * Find a port, whose SNMP's mibII ifIndex matches one of the ports,
+ * members of the specified bridge interface.
+ */
+struct bridge_port *
+bridge_port_find(int32_t if_idx, struct bridge_if *bif)
+{
+ struct bridge_port *bp;
+
+ for (bp = bif->f_bp; bp != NULL; bp = TAILQ_NEXT(bp, b_p)) {
+ if (bp->sysindex != bif->sysindex) {
+ bp = NULL;
+ break;
+ }
+
+ if (bp->if_idx == if_idx)
+ break;
+ }
+
+ return (bp);
+}
+
+void
+bridge_ports_dump(struct bridge_if *bif)
+{
+ struct bridge_port *bp;
+
+ for (bp = bridge_port_bif_first(bif); bp != NULL;
+ bp = bridge_port_bif_next(bp)) {
+ syslog(LOG_ERR, "memif - %s, index - %d",
+ bp->p_name, bp->port_no);
+ }
+}
+
+/*
+ * RFC4188 specifics.
+ */
+int
+op_dot1d_base_port(struct snmp_context *c __unused, struct snmp_value *val,
+ uint sub, uint iidx __unused, enum snmp_op op)
+{
+ struct bridge_if *bif;
+ struct bridge_port *bp;
+
+ if ((bif = bridge_get_default()) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ if (time(NULL) - bif->ports_age > bridge_get_data_maxage() &&
+ bridge_update_memif(bif) <= 0)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if (val->var.len - sub != 1)
+ return (SNMP_ERR_NOSUCHNAME);
+ if ((bp = bridge_port_find(val->var.subs[sub],
+ bif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_GETNEXT:
+ if (val->var.len - sub == 0) {
+ if ((bp = bridge_port_bif_first(bif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ } else {
+ if ((bp = bridge_port_find(val->var.subs[sub],
+ bif)) == NULL ||
+ (bp = bridge_port_bif_next(bp)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ }
+ val->var.len = sub + 1;
+ val->var.subs[sub] = bp->port_no;
+ goto get;
+
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ break;
+ }
+ abort();
+
+get:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_dot1dBasePort:
+ val->v.integer = bp->port_no;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dBasePortIfIndex:
+ val->v.integer = bp->if_idx;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dBasePortCircuit:
+ val->v.oid = bp->circuit;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dBasePortDelayExceededDiscards:
+ val->v.uint32 = bp->dly_ex_drops;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dBasePortMtuExceededDiscards:
+ val->v.uint32 = bp->dly_mtu_drops;
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+int
+op_dot1d_stp_port(struct snmp_context *ctx, struct snmp_value *val,
+ uint sub, uint iidx __unused, enum snmp_op op)
+{
+ struct bridge_if *bif;
+ struct bridge_port *bp;
+
+ if ((bif = bridge_get_default()) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ if (time(NULL) - bif->ports_age > bridge_get_data_maxage() &&
+ bridge_update_memif(bif) <= 0)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if (val->var.len - sub != 1)
+ return (SNMP_ERR_NOSUCHNAME);
+ if ((bp = bridge_port_find(val->var.subs[sub],
+ bif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_GETNEXT:
+ if (val->var.len - sub == 0) {
+ if ((bp = bridge_port_bif_first(bif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ } else {
+ if ((bp = bridge_port_find(val->var.subs[sub],
+ bif)) == NULL ||
+ (bp = bridge_port_bif_next(bp)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ }
+ val->var.len = sub + 1;
+ val->var.subs[sub] = bp->port_no;
+ goto get;
+
+ case SNMP_OP_SET:
+ if (val->var.len - sub != 1)
+ return (SNMP_ERR_NOSUCHNAME);
+ if ((bp = bridge_port_find(val->var.subs[sub],
+ bif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_dot1dStpPortPriority:
+ if (val->v.integer < 0 || val->v.integer > 255)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bp->priority;
+ if (bridge_port_set_priority(bif->bif_name, bp,
+ val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpPortEnable:
+ if (val->v.integer != dot1dStpPortEnable_enabled &&
+ val->v.integer != dot1dStpPortEnable_disabled)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bp->enable;
+ if (bridge_port_set_stp_enable(bif->bif_name,
+ bp, val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpPortPathCost:
+ if (val->v.integer < SNMP_PORT_MIN_PATHCOST ||
+ val->v.integer > SNMP_PORT_MAX_PATHCOST)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bp->path_cost;
+ if (bridge_port_set_path_cost(bif->bif_name, bp,
+ val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpPort:
+ case LEAF_dot1dStpPortState:
+ case LEAF_dot1dStpPortDesignatedRoot:
+ case LEAF_dot1dStpPortDesignatedCost:
+ case LEAF_dot1dStpPortDesignatedBridge:
+ case LEAF_dot1dStpPortDesignatedPort:
+ case LEAF_dot1dStpPortForwardTransitions:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ }
+ abort();
+
+ case SNMP_OP_ROLLBACK:
+ if ((bp = bridge_port_find(val->var.subs[sub],
+ bif)) == NULL)
+ return (SNMP_ERR_GENERR);
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_dot1dStpPortPriority:
+ bridge_port_set_priority(bif->bif_name, bp,
+ ctx->scratch->int1);
+ break;
+ case LEAF_dot1dStpPortEnable:
+ bridge_port_set_stp_enable(bif->bif_name, bp,
+ ctx->scratch->int1);
+ break;
+ case LEAF_dot1dStpPortPathCost:
+ bridge_port_set_path_cost(bif->bif_name, bp,
+ ctx->scratch->int1);
+ break;
+ }
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_COMMIT:
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+
+get:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_dot1dStpPort:
+ val->v.integer = bp->port_no;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpPortPriority:
+ val->v.integer = bp->priority;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpPortState:
+ val->v.integer = bp->state;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpPortEnable:
+ val->v.integer = bp->enable;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpPortPathCost:
+ val->v.integer = bp->path_cost;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpPortDesignatedRoot:
+ return (string_get(val, bp->design_root,
+ SNMP_BRIDGE_ID_LEN));
+
+ case LEAF_dot1dStpPortDesignatedCost:
+ val->v.integer = bp->design_cost;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpPortDesignatedBridge:
+ return (string_get(val, bp->design_bridge,
+ SNMP_BRIDGE_ID_LEN));
+
+ case LEAF_dot1dStpPortDesignatedPort:
+ return (string_get(val, bp->design_port, 2));
+
+ case LEAF_dot1dStpPortForwardTransitions:
+ val->v.uint32 = bp->fwd_trans;
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+int
+op_dot1d_stp_ext_port(struct snmp_context *ctx, struct snmp_value *val,
+ uint sub, uint iidx __unused, enum snmp_op op)
+{
+ struct bridge_if *bif;
+ struct bridge_port *bp;
+
+ if ((bif = bridge_get_default()) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ if (time(NULL) - bif->ports_age > bridge_get_data_maxage() &&
+ bridge_update_memif(bif) <= 0)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if (val->var.len - sub != 1)
+ return (SNMP_ERR_NOSUCHNAME);
+ if ((bp = bridge_port_find(val->var.subs[sub],
+ bif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_GETNEXT:
+ if (val->var.len - sub == 0) {
+ if ((bp = bridge_port_bif_first(bif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ } else {
+ if ((bp = bridge_port_find(val->var.subs[sub],
+ bif)) == NULL ||
+ (bp = bridge_port_bif_next(bp)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ }
+ val->var.len = sub + 1;
+ val->var.subs[sub] = bp->port_no;
+ goto get;
+
+ case SNMP_OP_SET:
+ if (val->var.len - sub != 1)
+ return (SNMP_ERR_NOSUCHNAME);
+ if ((bp = bridge_port_find(val->var.subs[sub],
+ bif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_dot1dStpPortAdminEdgePort:
+ if (val->v.integer != TruthValue_true &&
+ val->v.integer != TruthValue_false)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bp->admin_edge;
+ if (bridge_port_set_admin_edge(bif->bif_name, bp,
+ val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpPortAdminPointToPoint:
+ if (val->v.integer < 0 || val->v.integer >
+ StpPortAdminPointToPointType_auto)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bp->admin_ptp;
+ if (bridge_port_set_admin_ptp(bif->bif_name, bp,
+ val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpPortAdminPathCost:
+ if (val->v.integer < SNMP_PORT_MIN_PATHCOST ||
+ val->v.integer > SNMP_PORT_MAX_PATHCOST)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bp->admin_path_cost;
+ if (bridge_port_set_path_cost(bif->bif_name, bp,
+ val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpPortProtocolMigration:
+ case LEAF_dot1dStpPortOperEdgePort:
+ case LEAF_dot1dStpPortOperPointToPoint:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ }
+ abort();
+
+ case SNMP_OP_ROLLBACK:
+ if ((bp = bridge_port_find(val->var.subs[sub],
+ bif)) == NULL)
+ return (SNMP_ERR_GENERR);
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_dot1dStpPortAdminEdgePort:
+ bridge_port_set_admin_edge(bif->bif_name, bp,
+ ctx->scratch->int1);
+ break;
+ case LEAF_dot1dStpPortAdminPointToPoint:
+ bridge_port_set_admin_ptp(bif->bif_name, bp,
+ ctx->scratch->int1);
+ break;
+ case LEAF_dot1dStpPortAdminPathCost:
+ bridge_port_set_path_cost(bif->bif_name, bp,
+ ctx->scratch->int1);
+ break;
+ }
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_COMMIT:
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+
+get:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_dot1dStpPortProtocolMigration:
+ val->v.integer = bp->proto_migr;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpPortAdminEdgePort:
+ val->v.integer = bp->admin_edge;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpPortOperEdgePort:
+ val->v.integer = bp->oper_edge;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpPortAdminPointToPoint:
+ val->v.integer = bp->admin_ptp;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpPortOperPointToPoint:
+ val->v.integer = bp->oper_ptp;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dStpPortAdminPathCost:
+ val->v.integer = bp->admin_path_cost;
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+int
+op_dot1d_tp_port(struct snmp_context *c __unused, struct snmp_value *val,
+ uint sub, uint iidx __unused, enum snmp_op op)
+{
+ struct bridge_if *bif;
+ struct bridge_port *bp;
+
+ if ((bif = bridge_get_default()) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ if (time(NULL) - bif->ports_age > bridge_get_data_maxage() &&
+ bridge_update_memif(bif) <= 0)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if (val->var.len - sub != 1)
+ return (SNMP_ERR_NOSUCHNAME);
+ if ((bp = bridge_port_find(val->var.subs[sub],
+ bif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_GETNEXT:
+ if (val->var.len - sub == 0) {
+ if ((bp = bridge_port_bif_first(bif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ } else {
+ if ((bp = bridge_port_find(val->var.subs[sub],
+ bif)) == NULL ||
+ (bp = bridge_port_bif_next(bp)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ }
+ val->var.len = sub + 1;
+ val->var.subs[sub] = bp->port_no;
+ goto get;
+
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ break;
+ }
+ abort();
+
+get:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_dot1dTpPort:
+ val->v.integer = bp->port_no;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dTpPortMaxInfo:
+ val->v.integer = bp->max_info;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dTpPortInFrames:
+ val->v.uint32 = bp->in_frames;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dTpPortOutFrames:
+ val->v.uint32 = bp->out_frames;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_dot1dTpPortInDiscards:
+ val->v.uint32 = bp->in_drops;
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+/*
+ * Private BEGEMOT-BRIDGE-MIB specifics.
+ */
+
+/*
+ * Construct a bridge port entry index.
+ */
+static int
+bridge_port_index_append(struct asn_oid *oid, uint sub,
+ const struct bridge_port *bp)
+{
+ uint i;
+ const char *b_name;
+
+ if ((b_name = bridge_if_find_name(bp->sysindex)) == NULL)
+ return (-1);
+
+ oid->len = sub + strlen(b_name) + 1 + 1;
+ oid->subs[sub] = strlen(b_name);
+
+ for (i = 1; i <= strlen(b_name); i++)
+ oid->subs[sub + i] = b_name[i - 1];
+
+ oid->subs[sub + i] = bp->port_no;
+
+ return (0);
+}
+
+/*
+ * Get the port entry from an entry's index.
+ */
+static struct bridge_port *
+bridge_port_index_get(const struct asn_oid *oid, uint sub, int8_t status)
+{
+ uint i;
+ int32_t port_no;
+ char bif_name[IFNAMSIZ];
+ struct bridge_if *bif;
+ struct bridge_port *bp;
+
+ if (oid->len - sub != oid->subs[sub] + 2 ||
+ oid->subs[sub] >= IFNAMSIZ)
+ return (NULL);
+
+ for (i = 0; i < oid->subs[sub]; i++)
+ bif_name[i] = oid->subs[sub + i + 1];
+ bif_name[i] = '\0';
+
+ port_no = oid->subs[sub + i + 1];
+
+ if ((bif = bridge_if_find_ifname(bif_name)) == NULL)
+ return (NULL);
+
+ if ((bp = bridge_port_find(port_no, bif)) == NULL ||
+ (status == 0 && bp->status != RowStatus_active))
+ return (NULL);
+
+ return (bp);
+}
+
+/*
+ * Get the next port entry from an entry's index.
+ */
+static struct bridge_port *
+bridge_port_index_getnext(const struct asn_oid *oid, uint sub, int8_t status)
+{
+ uint i;
+ int32_t port_no;
+ char bif_name[IFNAMSIZ];
+ struct bridge_if *bif;
+ struct bridge_port *bp;
+
+ if (oid->len - sub == 0)
+ bp = bridge_port_first();
+ else {
+ if (oid->len - sub != oid->subs[sub] + 2 ||
+ oid->subs[sub] >= IFNAMSIZ)
+ return (NULL);
+
+ for (i = 0; i < oid->subs[sub]; i++)
+ bif_name[i] = oid->subs[sub + i + 1];
+ bif_name[i] = '\0';
+
+ port_no = oid->subs[sub + i + 1];
+
+ if ((bif = bridge_if_find_ifname(bif_name)) == NULL ||
+ (bp = bridge_port_find(port_no, bif)) == NULL)
+ return (NULL);
+
+ bp = bridge_port_next(bp);
+ }
+
+ if (status == 1)
+ return (bp);
+
+ while (bp != NULL) {
+ if (bp->status == RowStatus_active)
+ break;
+ bp = bridge_port_next(bp);
+ }
+
+ return (bp);
+}
+
+/*
+ * Read the bridge name and port index from a ASN OID structure.
+ */
+static int
+bridge_port_index_decode(const struct asn_oid *oid, uint sub,
+ char *b_name, int32_t *idx)
+{
+ uint i;
+
+ if (oid->len - sub != oid->subs[sub] + 2 ||
+ oid->subs[sub] >= IFNAMSIZ)
+ return (-1);
+
+ for (i = 0; i < oid->subs[sub]; i++)
+ b_name[i] = oid->subs[sub + i + 1];
+ b_name[i] = '\0';
+
+ *idx = oid->subs[sub + i + 1];
+ return (0);
+}
+
+static int
+bridge_port_set_status(struct snmp_context *ctx,
+ struct snmp_value *val, uint sub)
+{
+ int32_t if_idx;
+ char b_name[IFNAMSIZ];
+ struct bridge_if *bif;
+ struct bridge_port *bp;
+ struct mibif *mif;
+
+ if (bridge_port_index_decode(&val->var, sub, b_name, &if_idx) < 0)
+ return (SNMP_ERR_INCONS_VALUE);
+
+ if ((bif = bridge_if_find_ifname(b_name)) == NULL ||
+ (mif = mib_find_if(if_idx)) == NULL)
+ return (SNMP_ERR_INCONS_VALUE);
+
+ bp = bridge_port_find(if_idx, bif);
+
+ switch (val->v.integer) {
+ case RowStatus_active:
+ if (bp == NULL)
+ return (SNMP_ERR_INCONS_VALUE);
+
+ if (bp->span_enable == 0)
+ return (SNMP_ERR_INCONS_VALUE);
+
+ ctx->scratch->int1 = bp->status;
+ bp->status = RowStatus_active;
+ break;
+
+ case RowStatus_notInService:
+ if (bp == NULL || bp->span_enable == 0 ||
+ bp->status == RowStatus_active)
+ return (SNMP_ERR_INCONS_VALUE);
+
+ ctx->scratch->int1 = bp->status;
+ bp->status = RowStatus_notInService;
+
+ case RowStatus_notReady:
+ /* FALLTHROUGH */
+ case RowStatus_createAndGo:
+ return (SNMP_ERR_INCONS_VALUE);
+
+ case RowStatus_createAndWait:
+ if (bp != NULL)
+ return (SNMP_ERR_INCONS_VALUE);
+
+ if ((bp = bridge_new_port(mif, bif)) == NULL)
+ return (SNMP_ERR_GENERR);
+
+ ctx->scratch->int1 = RowStatus_destroy;
+ bp->status = RowStatus_notReady;
+ break;
+
+ case RowStatus_destroy:
+ if (bp == NULL)
+ return (SNMP_ERR_INCONS_VALUE);
+
+ ctx->scratch->int1 = bp->status;
+ bp->status = RowStatus_destroy;
+ break;
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+static int
+bridge_port_rollback_status(struct snmp_context *ctx,
+ struct snmp_value *val, uint sub)
+{
+ int32_t if_idx;
+ char b_name[IFNAMSIZ];
+ struct bridge_if *bif;
+ struct bridge_port *bp;
+
+ if (bridge_port_index_decode(&val->var, sub, b_name, &if_idx) < 0)
+ return (SNMP_ERR_GENERR);
+
+ if ((bif = bridge_if_find_ifname(b_name)) == NULL ||
+ (bp = bridge_port_find(if_idx, bif)) == NULL)
+ return (SNMP_ERR_GENERR);
+
+ if (ctx->scratch->int1 == RowStatus_destroy)
+ bridge_port_remove(bp, bif);
+ else
+ bp->status = ctx->scratch->int1;
+
+ return (SNMP_ERR_NOERROR);
+}
+
+static int
+bridge_port_commit_status(struct snmp_value *val, uint sub)
+{
+ int32_t if_idx;
+ char b_name[IFNAMSIZ];
+ struct bridge_if *bif;
+ struct bridge_port *bp;
+
+ if (bridge_port_index_decode(&val->var, sub, b_name, &if_idx) < 0)
+ return (SNMP_ERR_GENERR);
+
+ if ((bif = bridge_if_find_ifname(b_name)) == NULL ||
+ (bp = bridge_port_find(if_idx, bif)) == NULL)
+ return (SNMP_ERR_GENERR);
+
+ switch (bp->status) {
+ case RowStatus_active:
+ if (bridge_port_addm(bp, b_name) < 0)
+ return (SNMP_ERR_COMMIT_FAILED);
+ break;
+
+ case RowStatus_destroy:
+ if (bridge_port_delm(bp, b_name) < 0)
+ return (SNMP_ERR_COMMIT_FAILED);
+ bridge_port_remove(bp, bif);
+ break;
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+static int
+bridge_port_set_span_enable(struct snmp_context *ctx,
+ struct snmp_value *val, uint sub)
+{
+ int32_t if_idx;
+ char b_name[IFNAMSIZ];
+ struct bridge_if *bif;
+ struct bridge_port *bp;
+ struct mibif *mif;
+
+ if (val->v.integer != begemotBridgeBaseSpanEnabled_enabled &&
+ val->v.integer != begemotBridgeBaseSpanEnabled_disabled)
+ return (SNMP_ERR_BADVALUE);
+
+ if (bridge_port_index_decode(&val->var, sub, b_name, &if_idx) < 0)
+ return (SNMP_ERR_INCONS_VALUE);
+
+ if ((bif = bridge_if_find_ifname(b_name)) == NULL)
+ return (SNMP_ERR_INCONS_VALUE);
+
+ if ((bp = bridge_port_find(if_idx, bif)) == NULL) {
+ if ((mif = mib_find_if(if_idx)) == NULL)
+ return (SNMP_ERR_INCONS_VALUE);
+
+ if ((bp = bridge_new_port(mif, bif)) == NULL)
+ return (SNMP_ERR_GENERR);
+
+ ctx->scratch->int1 = RowStatus_destroy;
+ } else if (bp->status == RowStatus_active) {
+ return (SNMP_ERR_INCONS_VALUE);
+ } else {
+ ctx->scratch->int1 = bp->status;
+ }
+
+ bp->span_enable = val->v.integer;
+ bp->status = RowStatus_notInService;
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+op_begemot_base_port(struct snmp_context *ctx, struct snmp_value *val,
+ uint sub, uint iidx __unused, enum snmp_op op)
+{
+ int8_t status, which;
+ const char *bname;
+ struct bridge_port *bp;
+
+ if (time(NULL) - ports_list_age > bridge_get_data_maxage())
+ bridge_update_all_ports();
+
+ which = val->var.subs[sub - 1];
+ status = 0;
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if (which == LEAF_begemotBridgeBaseSpanEnabled ||
+ which == LEAF_begemotBridgeBasePortStatus)
+ status = 1;
+ if ((bp = bridge_port_index_get(&val->var, sub,
+ status)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_GETNEXT:
+ if (which == LEAF_begemotBridgeBaseSpanEnabled ||
+ which == LEAF_begemotBridgeBasePortStatus)
+ status = 1;
+ if ((bp = bridge_port_index_getnext(&val->var, sub,
+ status)) == NULL ||
+ bridge_port_index_append(&val->var, sub, bp) < 0)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_SET:
+ switch (which) {
+ case LEAF_begemotBridgeBaseSpanEnabled:
+ return (bridge_port_set_span_enable(ctx, val, sub));
+
+ case LEAF_begemotBridgeBasePortStatus:
+ return (bridge_port_set_status(ctx, val, sub));
+
+ case LEAF_begemotBridgeBasePortPrivate:
+ if ((bp = bridge_port_index_get(&val->var, sub,
+ status)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ if ((bname = bridge_if_find_name(bp->sysindex)) == NULL)
+ return (SNMP_ERR_GENERR);
+ ctx->scratch->int1 = bp->priv_set;
+ return (bridge_port_set_private(bname, bp,
+ val->v.integer));
+
+ case LEAF_begemotBridgeBasePort:
+ case LEAF_begemotBridgeBasePortIfIndex:
+ case LEAF_begemotBridgeBasePortDelayExceededDiscards:
+ case LEAF_begemotBridgeBasePortMtuExceededDiscards:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ }
+ abort();
+
+ case SNMP_OP_ROLLBACK:
+ switch (which) {
+ case LEAF_begemotBridgeBaseSpanEnabled:
+ /* FALLTHROUGH */
+ case LEAF_begemotBridgeBasePortStatus:
+ return (bridge_port_rollback_status(ctx, val, sub));
+ case LEAF_begemotBridgeBasePortPrivate:
+ if ((bp = bridge_port_index_get(&val->var, sub,
+ status)) == NULL)
+ return (SNMP_ERR_GENERR);
+ if ((bname = bridge_if_find_name(bp->sysindex)) == NULL)
+ return (SNMP_ERR_GENERR);
+ return (bridge_port_set_private(bname, bp,
+ ctx->scratch->int1));
+ }
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_COMMIT:
+ if (which == LEAF_begemotBridgeBasePortStatus)
+ return (bridge_port_commit_status(val, sub));
+
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+
+get:
+ switch (which) {
+ case LEAF_begemotBridgeBasePort:
+ val->v.integer = bp->port_no;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeBasePortIfIndex:
+ val->v.integer = bp->if_idx;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeBaseSpanEnabled:
+ val->v.integer = bp->span_enable;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeBasePortDelayExceededDiscards:
+ val->v.uint32 = bp->dly_ex_drops;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeBasePortMtuExceededDiscards:
+ val->v.uint32 = bp->dly_mtu_drops;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeBasePortStatus:
+ val->v.integer = bp->status;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeBasePortPrivate:
+ val->v.integer = bp->priv_set;
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+int
+op_begemot_stp_port(struct snmp_context *ctx, struct snmp_value *val,
+ uint sub, uint iidx __unused, enum snmp_op op)
+{
+ struct bridge_port *bp;
+ const char *b_name;
+
+ if (time(NULL) - ports_list_age > bridge_get_data_maxage())
+ bridge_update_all_ports();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((bp = bridge_port_index_get(&val->var, sub, 0)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_GETNEXT:
+ if ((bp = bridge_port_index_getnext(&val->var, sub, 0)) ==
+ NULL || bridge_port_index_append(&val->var, sub, bp) < 0)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_SET:
+ if ((bp = bridge_port_index_get(&val->var, sub, 0)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ if ((b_name = bridge_if_find_name(bp->sysindex)) == NULL)
+ return (SNMP_ERR_GENERR);
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_begemotBridgeStpPortPriority:
+ if (val->v.integer < 0 || val->v.integer > 255)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bp->priority;
+ if (bridge_port_set_priority(b_name, bp,
+ val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpPortEnable:
+ if (val->v.integer !=
+ begemotBridgeStpPortEnable_enabled ||
+ val->v.integer !=
+ begemotBridgeStpPortEnable_disabled)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bp->enable;
+ if (bridge_port_set_stp_enable(b_name, bp,
+ val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpPortPathCost:
+ if (val->v.integer < SNMP_PORT_MIN_PATHCOST ||
+ val->v.integer > SNMP_PORT_MAX_PATHCOST)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bp->path_cost;
+ if (bridge_port_set_path_cost(b_name, bp,
+ val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpPort:
+ case LEAF_begemotBridgeStpPortState:
+ case LEAF_begemotBridgeStpPortDesignatedRoot:
+ case LEAF_begemotBridgeStpPortDesignatedCost:
+ case LEAF_begemotBridgeStpPortDesignatedBridge:
+ case LEAF_begemotBridgeStpPortDesignatedPort:
+ case LEAF_begemotBridgeStpPortForwardTransitions:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ }
+ abort();
+
+ case SNMP_OP_ROLLBACK:
+ if ((bp = bridge_port_index_get(&val->var, sub, 0)) == NULL ||
+ (b_name = bridge_if_find_name(bp->sysindex)) == NULL)
+ return (SNMP_ERR_GENERR);
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_begemotBridgeStpPortPriority:
+ bridge_port_set_priority(b_name, bp,
+ ctx->scratch->int1);
+ break;
+ case LEAF_begemotBridgeStpPortEnable:
+ bridge_port_set_stp_enable(b_name, bp,
+ ctx->scratch->int1);
+ break;
+ case LEAF_begemotBridgeStpPortPathCost:
+ bridge_port_set_path_cost(b_name, bp,
+ ctx->scratch->int1);
+ break;
+ }
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_COMMIT:
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+
+get:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_begemotBridgeStpPort:
+ val->v.integer = bp->port_no;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpPortPriority:
+ val->v.integer = bp->priority;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpPortState:
+ val->v.integer = bp->state;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpPortEnable:
+ val->v.integer = bp->enable;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpPortPathCost:
+ val->v.integer = bp->path_cost;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpPortDesignatedRoot:
+ return (string_get(val, bp->design_root, SNMP_BRIDGE_ID_LEN));
+
+ case LEAF_begemotBridgeStpPortDesignatedCost:
+ val->v.integer = bp->design_cost;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpPortDesignatedBridge:
+ return (string_get(val, bp->design_bridge, SNMP_BRIDGE_ID_LEN));
+
+ case LEAF_begemotBridgeStpPortDesignatedPort:
+ return (string_get(val, bp->design_port, 2));
+
+ case LEAF_begemotBridgeStpPortForwardTransitions:
+ val->v.uint32 = bp->fwd_trans;
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+int
+op_begemot_stp_ext_port(struct snmp_context *ctx, struct snmp_value *val,
+ uint sub, uint iidx __unused, enum snmp_op op)
+{
+ struct bridge_port *bp;
+ const char *b_name;
+
+ if (time(NULL) - ports_list_age > bridge_get_data_maxage())
+ bridge_update_all_ports();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((bp = bridge_port_index_get(&val->var, sub, 0)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_GETNEXT:
+ if ((bp = bridge_port_index_getnext(&val->var, sub, 0)) ==
+ NULL || bridge_port_index_append(&val->var, sub, bp) < 0)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_SET:
+ if ((bp = bridge_port_index_get(&val->var, sub, 0)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ if ((b_name = bridge_if_find_name(bp->sysindex)) == NULL)
+ return (SNMP_ERR_GENERR);
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_begemotBridgeStpPortAdminEdgePort:
+ if (val->v.integer != TruthValue_true &&
+ val->v.integer != TruthValue_false)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bp->admin_edge;
+ if (bridge_port_set_admin_edge(b_name, bp,
+ val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpPortAdminPointToPoint:
+ if (val->v.integer < 0 || val->v.integer >
+ StpPortAdminPointToPointType_auto)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bp->admin_ptp;
+ if (bridge_port_set_admin_ptp(b_name, bp,
+ val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpPortAdminPathCost:
+ if (val->v.integer < SNMP_PORT_MIN_PATHCOST ||
+ val->v.integer > SNMP_PORT_MAX_PATHCOST)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ctx->scratch->int1 = bp->admin_path_cost;
+ if (bridge_port_set_path_cost(b_name, bp,
+ val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpPortProtocolMigration:
+ case LEAF_begemotBridgeStpPortOperEdgePort:
+ case LEAF_begemotBridgeStpPortOperPointToPoint:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ }
+ abort();
+
+ case SNMP_OP_ROLLBACK:
+ if ((bp = bridge_port_index_get(&val->var, sub, 0)) == NULL ||
+ (b_name = bridge_if_find_name(bp->sysindex)) == NULL)
+ return (SNMP_ERR_GENERR);
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_begemotBridgeStpPortAdminEdgePort:
+ bridge_port_set_admin_edge(b_name, bp,
+ ctx->scratch->int1);
+ break;
+ case LEAF_begemotBridgeStpPortAdminPointToPoint:
+ bridge_port_set_admin_ptp(b_name, bp,
+ ctx->scratch->int1);
+ break;
+ case LEAF_begemotBridgeStpPortAdminPathCost:
+ bridge_port_set_path_cost(b_name, bp,
+ ctx->scratch->int1);
+ break;
+ }
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_COMMIT:
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+
+get:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_begemotBridgeStpPortProtocolMigration:
+ val->v.integer = bp->proto_migr;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpPortAdminEdgePort:
+ val->v.integer = bp->admin_edge;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpPortOperEdgePort:
+ val->v.integer = bp->oper_edge;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpPortAdminPointToPoint:
+ val->v.integer = bp->admin_ptp;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpPortOperPointToPoint:
+ val->v.integer = bp->oper_ptp;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeStpPortAdminPathCost:
+ val->v.integer = bp->admin_path_cost;
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+int
+op_begemot_tp_port(struct snmp_context *c __unused, struct snmp_value *val,
+ uint sub, uint iidx __unused, enum snmp_op op)
+{
+ struct bridge_port *bp;
+
+ if (time(NULL) - ports_list_age > bridge_get_data_maxage())
+ bridge_update_all_ports();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((bp = bridge_port_index_get(&val->var, sub, 0)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_GETNEXT:
+ if ((bp = bridge_port_index_getnext(&val->var, sub, 0)) ==
+ NULL || bridge_port_index_append(&val->var, sub, bp) < 0)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ break;
+ }
+ abort();
+
+get:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_begemotBridgeTpPort:
+ val->v.integer = bp->port_no;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeTpPortMaxInfo:
+ val->v.integer = bp->max_info;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeTpPortInFrames:
+ val->v.uint32 = bp->in_frames;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeTpPortOutFrames:
+ val->v.uint32 = bp->out_frames;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeTpPortInDiscards:
+ val->v.uint32 = bp->in_drops;
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
diff --git a/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_snmp.c b/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_snmp.c
new file mode 100644
index 0000000..81acc4d
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_snmp.c
@@ -0,0 +1,338 @@
+/*-
+ * Copyright (c) 2006 Shteryana Shopova <syrinx@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.
+ *
+ * Bridge MIB implementation for SNMPd.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_mib.h>
+#include <net/if_types.h>
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+
+#include <bsnmp/snmpmod.h>
+#include <bsnmp/snmp_mibII.h>
+
+#include "bridge_tree.h"
+#include "bridge_snmp.h"
+#include "bridge_oid.h"
+
+static struct lmodule *bridge_module;
+
+/* For the registration. */
+static const struct asn_oid oid_dot1Bridge = OIDX_dot1dBridge;
+/* The registration. */
+static uint reg_bridge;
+
+/* Periodic timer for polling all bridges' data. */
+static void *bridge_data_timer;
+static void *bridge_tc_timer;
+
+static int bridge_data_maxage = SNMP_BRIDGE_DATA_MAXAGE;
+static int bridge_poll_ticks = SNMP_BRIDGE_POLL_INTERVAL * 100;
+static int bridge_tc_poll_ticks = SNMP_BRIDGE_TC_POLL_INTERVAL * 100;
+
+/*
+ * Our default bridge, whose info will be visible under
+ * the dot1dBridge subtree and functions to set/fetch it.
+ */
+static char bif_default_name[IFNAMSIZ] = "bridge0";
+static struct bridge_if *bif_default;
+
+struct bridge_if *
+bridge_get_default(void)
+{
+ struct mibif *ifp;
+
+ if (bif_default != NULL) {
+
+ /* Walk through the mibII interface list. */
+ for (ifp = mib_first_if(); ifp != NULL; ifp = mib_next_if(ifp))
+ if (strcmp(ifp->name, bif_default->bif_name) == 0)
+ break;
+
+ if (ifp == NULL)
+ bif_default = NULL;
+ }
+
+ return (bif_default);
+}
+
+void
+bridge_set_default(struct bridge_if *bif)
+{
+ bif_default = bif;
+
+ syslog(LOG_ERR, "Set default bridge interface to: %s",
+ bif == NULL ? "(none)" : bif->bif_name);
+}
+
+const char *
+bridge_get_default_name(void)
+{
+ return (bif_default_name);
+}
+
+static int
+bridge_set_default_name(const char *bif_name, uint len)
+{
+ struct bridge_if *bif;
+
+ if (len >= IFNAMSIZ)
+ return (-1);
+
+ bcopy(bif_name, bif_default_name, len);
+ bif_default_name[len] = '\0';
+
+ if ((bif = bridge_if_find_ifname(bif_default_name)) == NULL) {
+ bif_default = NULL;
+ return (0);
+ }
+
+ bif_default = bif;
+ return (1);
+}
+
+int
+bridge_get_data_maxage(void)
+{
+ return (bridge_data_maxage);
+}
+
+static void
+bridge_set_poll_ticks(int poll_ticks)
+{
+ if (bridge_data_timer != NULL)
+ timer_stop(bridge_data_timer);
+
+ bridge_poll_ticks = poll_ticks;
+ bridge_data_timer = timer_start_repeat(bridge_poll_ticks,
+ bridge_poll_ticks, bridge_update_all, NULL, bridge_module);
+}
+/*
+ * The bridge module configuration via SNMP.
+ */
+static int
+bridge_default_name_save(struct snmp_context *ctx, const char *bridge_default)
+{
+ if ((ctx->scratch->int1 = strlen(bridge_default)) >= IFNAMSIZ)
+ return (-1);
+
+ if ((ctx->scratch->ptr1 = malloc(IFNAMSIZ)) == NULL)
+ return (-1);
+
+ strncpy(ctx->scratch->ptr1, bridge_default, ctx->scratch->int1);
+ return (0);
+}
+
+int
+op_begemot_bridge_config(struct snmp_context *ctx, struct snmp_value *val,
+ uint sub, uint iidx __unused, enum snmp_op op)
+{
+ switch (op) {
+ case SNMP_OP_GET:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_begemotBridgeDefaultBridgeIf:
+ return (string_get(val, bridge_get_default_name(), -1));
+
+ case LEAF_begemotBridgeDataUpdate:
+ val->v.integer = bridge_data_maxage;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeDataPoll:
+ val->v.integer = bridge_poll_ticks / 100;
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+
+ case SNMP_OP_GETNEXT:
+ abort();
+
+ case SNMP_OP_SET:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_begemotBridgeDefaultBridgeIf:
+ /*
+ * Cannot use string_save() here - requires either
+ * a fixed-sized or var-length string - not less
+ * than or equal.
+ */
+ if (bridge_default_name_save(ctx,
+ bridge_get_default_name()) < 0)
+ return (SNMP_ERR_RES_UNAVAIL);
+
+ if (bridge_set_default_name(val->v.octetstring.octets,
+ val->v.octetstring.len) < 0)
+ return (SNMP_ERR_BADVALUE);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeDataUpdate:
+ if (val->v.integer < SNMP_BRIDGE_DATA_MAXAGE_MIN ||
+ val->v.integer > SNMP_BRIDGE_DATA_MAXAGE_MAX)
+ return (SNMP_ERR_WRONG_VALUE);
+ ctx->scratch->int1 = bridge_data_maxage;
+ bridge_data_maxage = val->v.integer;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotBridgeDataPoll:
+ if (val->v.integer < SNMP_BRIDGE_POLL_INTERVAL_MIN ||
+ val->v.integer > SNMP_BRIDGE_POLL_INTERVAL_MAX)
+ return (SNMP_ERR_WRONG_VALUE);
+ ctx->scratch->int1 = val->v.integer;
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+
+ case SNMP_OP_ROLLBACK:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_begemotBridgeDefaultBridgeIf:
+ bridge_set_default_name(ctx->scratch->ptr1,
+ ctx->scratch->int1);
+ free(ctx->scratch->ptr1);
+ break;
+ case LEAF_begemotBridgeDataUpdate:
+ bridge_data_maxage = ctx->scratch->int1;
+ break;
+ }
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_COMMIT:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_begemotBridgeDefaultBridgeIf:
+ free(ctx->scratch->ptr1);
+ break;
+ case LEAF_begemotBridgeDataPoll:
+ bridge_set_poll_ticks(ctx->scratch->int1 * 100);
+ break;
+ }
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+/*
+ * Bridge mib module initialization hook.
+ * Returns 0 on success, < 0 on error.
+ */
+static int
+bridge_init(struct lmodule * mod, int argc __unused, char *argv[] __unused)
+{
+ bridge_module = mod;
+
+ if (bridge_kmod_load() < 0)
+ return (-1);
+
+ if (bridge_ioctl_init() < 0)
+ return (-1);
+
+ /* Register to get creation messages for bridge interfaces. */
+ if (mib_register_newif(bridge_attach_newif, bridge_module)) {
+ syslog(LOG_ERR, "Cannot register newif function: %s",
+ strerror(errno));
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Bridge mib module finalization hook.
+ */
+static int
+bridge_fini(void)
+{
+ mib_unregister_newif(bridge_module);
+ or_unregister(reg_bridge);
+
+ if (bridge_data_timer != NULL) {
+ timer_stop(bridge_data_timer);
+ bridge_data_timer = NULL;
+ }
+
+ if (bridge_tc_timer != NULL) {
+ timer_stop(bridge_tc_timer);
+ bridge_tc_timer = NULL;
+ }
+
+ bridge_ifs_fini();
+ bridge_ports_fini();
+ bridge_addrs_fini();
+
+ return (0);
+}
+
+/*
+ * Bridge mib module start operation.
+ */
+static void
+bridge_start(void)
+{
+ reg_bridge = or_register(&oid_dot1Bridge,
+ "The IETF MIB for Bridges (RFC 4188).", bridge_module);
+
+ bridge_data_timer = timer_start_repeat(bridge_poll_ticks,
+ bridge_poll_ticks, bridge_update_all, NULL, bridge_module);
+
+ bridge_tc_timer = timer_start_repeat(bridge_tc_poll_ticks,
+ bridge_tc_poll_ticks, bridge_update_tc_time, NULL, bridge_module);
+}
+
+static void
+bridge_dump(void)
+{
+ struct bridge_if *bif;
+
+ if ((bif = bridge_get_default()) == NULL)
+ syslog(LOG_ERR, "Dump: no default bridge interface");
+ else
+ syslog(LOG_ERR, "Dump: default bridge interface %s",
+ bif->bif_name);
+
+ bridge_ifs_dump();
+ bridge_pf_dump();
+}
+
+const struct snmp_module config = {
+ .comment = "This module implements the bridge mib (RFC 4188).",
+ .init = bridge_init,
+ .fini = bridge_fini,
+ .start = bridge_start,
+ .tree = bridge_ctree,
+ .dump = bridge_dump,
+ .tree_size = bridge_CTREE_SIZE,
+};
diff --git a/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_snmp.h b/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_snmp.h
new file mode 100644
index 0000000..7f48950
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_snmp.h
@@ -0,0 +1,357 @@
+/*-
+ * Copyright (c) 2006 Shteryana Shopova <syrinx@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.
+ *
+ * Bridge MIB implementation for SNMPd.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef SNMP_BRIDGE_H
+#define SNMP_BRIDGE_H
+
+#define SNMP_BRIDGE_ID_LEN 8
+
+typedef uint8_t port_id[2];
+typedef u_char bridge_id[SNMP_BRIDGE_ID_LEN];
+
+#define SNMP_BRIDGE_MAX_PRIORITY 65535
+
+#define SNMP_BRIDGE_MIN_AGE_TIME 10
+#define SNMP_BRIDGE_MAX_AGE_TIME 1000000
+
+#define SNMP_BRIDGE_MIN_TXHC 1
+#define SNMP_BRIDGE_MAX_TXHC 10
+
+#define SNMP_BRIDGE_MIN_MAGE 600
+#define SNMP_BRIDGE_MAX_MAGE 4000
+
+#define SNMP_BRIDGE_MIN_HTIME 100
+#define SNMP_BRIDGE_MAX_HTIME 1000
+
+#define SNMP_BRIDGE_MIN_FDELAY 400
+#define SNMP_BRIDGE_MAX_FDELAY 3000
+
+#define SNMP_PORT_PATHCOST_OBSOLETE 65535
+#define SNMP_PORT_MIN_PATHCOST 0
+#define SNMP_PORT_MAX_PATHCOST 200000000
+#define SNMP_PORT_PATHCOST_AUTO 0
+
+#define SNMP_BRIDGE_DATA_MAXAGE 10
+#define SNMP_BRIDGE_DATA_MAXAGE_MIN 1
+#define SNMP_BRIDGE_DATA_MAXAGE_MAX 300
+
+/* By default poll kernel data every 5 minutes. */
+#define SNMP_BRIDGE_POLL_INTERVAL (5 * 60)
+#define SNMP_BRIDGE_POLL_INTERVAL_MIN 1
+#define SNMP_BRIDGE_POLL_INTERVAL_MAX 3600
+
+/* Poll for a topology change once every 30 seconds. */
+#define SNMP_BRIDGE_TC_POLL_INTERVAL 30
+
+struct bridge_if *bridge_get_default(void);
+
+void bridge_set_default(struct bridge_if *bif);
+
+const char *bridge_get_default_name(void);
+
+int bridge_get_data_maxage(void);
+
+/*
+ * Bridge Addresses Table.
+ */
+struct tp_entry {
+ uint32_t sysindex; /* The bridge if sysindex. */
+ int32_t port_no;
+ enum TpFdbStatus status;
+ uint8_t tp_addr[ETHER_ADDR_LEN];
+ uint8_t flags;
+ TAILQ_ENTRY(tp_entry) tp_e;
+};
+
+/*
+ * Bridge ports.
+ * The bridge port system interface index is used for a
+ * port number. Transparent bridging statistics and STP
+ * information for a port are also contained here.
+ */
+struct bridge_port {
+ /* dot1dBase subtree objects. */
+ uint32_t sysindex; /* The bridge interface sysindex. */
+ int32_t port_no; /* The bridge member system index. */
+ int32_t if_idx; /* SNMP ifIndex from mibII. */
+ int8_t span_enable; /* Span flag set - private MIB. */
+ struct asn_oid circuit; /* Unused. */
+ uint32_t dly_ex_drops; /* Drops on output. */
+ uint32_t dly_mtu_drops; /* MTU exceeded drops. */
+ int32_t status; /* The entry status. */
+ enum TruthValue priv_set; /* The private flag. */
+
+ /* dot1dStp subtree objects. */
+ int32_t path_cost;
+ int32_t priority;
+ int32_t design_cost;
+ uint32_t fwd_trans;
+ char p_name[IFNAMSIZ]; /* Not in BRIDGE-MIB. */
+ enum StpPortState state;
+ enum dot1dStpPortEnable enable;
+ port_id design_port;
+ bridge_id design_root;
+ bridge_id design_bridge;
+
+ /* rstpMib extensions. */
+ int32_t admin_path_cost;
+ enum TruthValue proto_migr;
+ enum TruthValue admin_edge;
+ enum TruthValue oper_edge;
+ enum TruthValue oper_ptp;
+ enum StpPortAdminPointToPointType admin_ptp;
+
+ /* dot1dTp subtree objects. */
+ int32_t max_info;
+ int32_t in_frames;
+ int32_t out_frames;
+ int32_t in_drops;
+
+ uint8_t flags;
+ TAILQ_ENTRY(bridge_port) b_p;
+};
+
+/*
+ * A bridge interface.
+ * The system interface index of the bridge is not required neither by the
+ * standard BRIDGE-MIB nor by the private BEGEMOT-BRIDGE-MIB, but is used
+ * as key for looking up the other info for this bridge.
+ */
+struct bridge_if {
+ /* dot1dBase subtree objects. */
+ uint32_t sysindex; /* The system interface index. */
+ int32_t num_ports; /* Number of ports. */
+ enum BaseType br_type; /* Bridge type. */
+ enum RowStatus if_status; /* Bridge status. */
+ char bif_name[IFNAMSIZ]; /* Bridge interface name. */
+ struct ether_addr br_addr; /* Bridge address. */
+ struct bridge_port *f_bp; /* This bridge's first entry
+ * in the base ports TAILQ. */
+ /* dot1dStp subtree objects. */
+ int32_t priority;
+ int32_t root_cost;
+ int32_t root_port;
+ int32_t max_age; /* Current max age. */
+ int32_t hello_time; /* Current hello time. */
+ int32_t fwd_delay; /* Current forward delay. */
+ int32_t hold_time;
+ int32_t bridge_max_age; /* Configured max age. */
+ int32_t bridge_hello_time; /* Configured hello time. */
+ int32_t bridge_fwd_delay; /* Configured forward delay. */
+ int32_t tx_hold_count;
+ uint32_t top_changes;
+ enum dot1dStpVersion stp_version;
+ enum dot1dStpProtocolSpecification prot_spec;
+ struct timeval last_tc_time;
+ bridge_id design_root;
+
+ /* dot1dTp subtree objects. */
+ int32_t lrnt_drops; /* Dropped addresses. */
+ int32_t age_time; /* Address entry timeout. */
+ int32_t num_addrs; /* Current # of addresses in cache. */
+ int32_t max_addrs; /* Max # of addresses in cache. */
+ struct tp_entry *f_tpa; /* This bridge's first entry in
+ * the tp addresses TAILQ. */
+
+ time_t entry_age;
+ time_t ports_age;
+ time_t addrs_age;
+ TAILQ_ENTRY(bridge_if) b_if;
+};
+
+void bridge_ifs_fini(void);
+
+struct bridge_if *bridge_if_find_ifs(uint32_t sysindex);
+
+struct bridge_if *bridge_if_find_ifname(const char *b_name);
+
+const char *bridge_if_find_name(uint32_t sysindex);
+
+int bridge_compare_sysidx(uint32_t i1, uint32_t i2);
+
+int bridge_attach_newif(struct mibif *ifp);
+
+struct bridge_if *bridge_first_bif(void);
+
+struct bridge_if *bridge_next_bif(struct bridge_if *b_pr);
+
+void bridge_remove_bif(struct bridge_if *bif);
+
+void bridge_update_all_ports(void);
+
+void bridge_update_all_addrs(void);
+
+void bridge_update_all_ifs(void);
+
+void bridge_update_all(void *arg);
+
+void bridge_update_tc_time(void *arg);
+
+void bridge_ifs_dump(void);
+
+/* Bridge ports. */
+void bridge_ports_update_listage(void);
+
+void bridge_ports_fini(void);
+
+void bridge_members_free(struct bridge_if *bif);
+
+struct bridge_port *bridge_new_port(struct mibif *mif, struct bridge_if *bif);
+
+void bridge_port_remove(struct bridge_port *bp, struct bridge_if *bif);
+
+struct bridge_port *bridge_port_bif_first(struct bridge_if *bif);
+
+struct bridge_port *bridge_port_bif_next(struct bridge_port *bp);
+
+struct bridge_port *bridge_port_find(int32_t if_idx, struct bridge_if *bif);
+
+void bridge_port_getinfo_mibif(struct mibif *m_if, struct bridge_port *bp);
+
+int bridge_getinfo_bif_ports(struct bridge_if *bif);
+
+int bridge_update_memif(struct bridge_if *bif);
+
+void bridge_ports_dump(struct bridge_if *bif);
+
+/* Bridge addresses. */
+void bridge_addrs_update_listage(void);
+
+void bridge_addrs_fini(void);
+
+void bridge_addrs_free(struct bridge_if *bif);
+
+struct tp_entry *bridge_new_addrs(uint8_t *mac, struct bridge_if *bif);
+
+void bridge_addrs_remove(struct tp_entry *te, struct bridge_if *bif);
+
+struct tp_entry *bridge_addrs_find(uint8_t *mac, struct bridge_if *bif);
+
+struct tp_entry *bridge_addrs_bif_first(struct bridge_if *bif);
+
+struct tp_entry *bridge_addrs_bif_next(struct tp_entry *te);
+
+int bridge_getinfo_bif_addrs(struct bridge_if *bif);
+
+int bridge_update_addrs(struct bridge_if *bif);
+
+void bridge_addrs_dump(struct bridge_if *bif);
+
+/* Bridge PF. */
+
+void bridge_pf_dump(void);
+
+/* System specific. */
+
+/* Open the socket for the ioctls. */
+int bridge_ioctl_init(void);
+
+/* Load bridge kernel module. */
+int bridge_kmod_load(void);
+
+/* Get the bridge interface information. */
+int bridge_getinfo_bif(struct bridge_if *bif);
+
+/* Get the bridge interface STP parameters. */
+int bridge_get_op_param(struct bridge_if *bif);
+
+/* Set the bridge priority. */
+int bridge_set_priority(struct bridge_if *bif, int32_t priority);
+
+/* Set the bridge max age. */
+int bridge_set_maxage(struct bridge_if *bif, int32_t max_age);
+
+/* Set the bridge hello time.*/
+int bridge_set_hello_time(struct bridge_if *bif, int32_t hello_time);
+
+/* Set the bridge forward delay.*/
+int bridge_set_forward_delay(struct bridge_if *bif, int32_t fwd_delay);
+
+/* Set the bridge address cache max age. */
+int bridge_set_aging_time(struct bridge_if *bif, int32_t age_time);
+
+/* Set the max number of entries in the bridge address cache. */
+int bridge_set_max_cache(struct bridge_if *bif, int32_t max_cache);
+
+/* Set the bridge TX hold count. */
+int bridge_set_tx_hold_count(struct bridge_if *bif, int32_t tx_hc);
+
+/* Set the bridge STP protocol version. */
+int bridge_set_stp_version(struct bridge_if *bif, int32_t stp_proto);
+
+/* Set the bridge interface status to up/down. */
+int bridge_set_if_up(const char* b_name, int8_t up);
+
+/* Create a bridge interface. */
+int bridge_create(const char *b_name);
+
+/* Destroy a bridge interface. */
+int bridge_destroy(const char *b_name);
+
+/* Fetch the bridge mac address. */
+u_char *bridge_get_basemac(const char *bif_name, u_char *mac, size_t mlen);
+
+/* Set a bridge member priority. */
+int bridge_port_set_priority(const char *bif_name, struct bridge_port *bp,
+ int32_t priority);
+
+/* Set a bridge member STP-enabled flag. */
+int bridge_port_set_stp_enable(const char *bif_name, struct bridge_port *bp,
+ uint32_t enable);
+
+/* Set a bridge member STP path cost. */
+int bridge_port_set_path_cost(const char *bif_name, struct bridge_port *bp,
+ int32_t path_cost);
+
+/* Set admin point-to-point link. */
+int bridge_port_set_admin_ptp(const char *bif_name, struct bridge_port *bp,
+ uint32_t admin_ptp);
+
+/* Set admin edge. */
+int bridge_port_set_admin_edge(const char *bif_name, struct bridge_port *bp,
+ uint32_t enable);
+
+/* Set 'private' flag. */
+int bridge_port_set_private(const char *bif_name, struct bridge_port *bp,
+ uint32_t priv_set);
+
+/* Add a bridge member port. */
+int bridge_port_addm(struct bridge_port *bp, const char *b_name);
+
+/* Delete a bridge member port. */
+int bridge_port_delm(struct bridge_port *bp, const char *b_name);
+
+/* Get the current value from the module for bridge PF control. */
+int32_t bridge_get_pfval(uint8_t which);
+
+/* Get/Set a bridge PF control. */
+int32_t bridge_do_pfctl(int32_t bridge_ctl, enum snmp_op op, int32_t *val);
+
+#endif /* SNMP_BRIDGE_H */
diff --git a/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_sys.c b/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_sys.c
new file mode 100644
index 0000000..b8e66a6
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_sys.c
@@ -0,0 +1,1503 @@
+/*-
+ * Copyright (c) 2006 Shteryana Shopova <syrinx@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.
+ *
+ * Bridge MIB implementation for SNMPd.
+ * Bridge OS specific ioctls.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/module.h>
+#include <sys/linker.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <net/bridgestp.h>
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_bridgevar.h>
+#include <net/if_dl.h>
+#include <net/if_mib.h>
+#include <net/if_types.h>
+#include <netinet/in.h>
+
+#include <errno.h>
+#include <ifaddrs.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+
+#include <bsnmp/snmpmod.h>
+#include <bsnmp/snmp_mibII.h>
+
+#include "bridge_tree.h"
+#include "bridge_snmp.h"
+
+int sock = -1;
+
+int
+bridge_ioctl_init(void)
+{
+ if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
+ syslog(LOG_ERR, "cannot open socket : %s", strerror(errno));
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Load the if_bridge.ko module in kernel if not already there.
+ */
+int
+bridge_kmod_load(void)
+{
+ int fileid, modid;
+ const char mod_name[] = "if_bridge";
+ struct module_stat mstat;
+
+ /* 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;
+
+ if (strcmp(mod_name, mstat.name) == 0)
+ return (0);
+ }
+ }
+
+ /* Not present - load it. */
+ if (kldload(mod_name) < 0) {
+ syslog(LOG_ERR, "failed to load %s kernel module", mod_name);
+ return (-1);
+ }
+
+ return (1);
+}
+
+/************************************************************************
+ * Bridge interfaces.
+ */
+
+/*
+ * Convert the kernel uint64_t value for a bridge id
+ */
+static void
+snmp_uint64_to_bridgeid(uint64_t id, bridge_id b_id)
+{
+ int i;
+ u_char *o;
+
+ o = (u_char *) &id;
+
+ for (i = 0; i < SNMP_BRIDGE_ID_LEN; i++, o++)
+ b_id[SNMP_BRIDGE_ID_LEN - i - 1] = *o;
+}
+
+/*
+ * Fetch the bridge configuration parameters from the kernel excluding
+ * it's base MAC address.
+ */
+static int
+bridge_get_conf_param(struct bridge_if *bif)
+{
+ struct ifdrv ifd;
+ struct ifbrparam b_param;
+
+ strlcpy(ifd.ifd_name, bif->bif_name, IFNAMSIZ);
+ ifd.ifd_len = sizeof(b_param);
+ ifd.ifd_data = &b_param;
+
+ /* Bridge priority. */
+ ifd.ifd_cmd = BRDGGPRI;
+ if (ioctl(sock, SIOCGDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "update bridge: ioctl(BRDGGPRI) failed: %s",
+ strerror(errno));
+ return (-1);
+ }
+
+ bif->priority = b_param.ifbrp_prio;
+
+ /* Configured max age. */
+ ifd.ifd_cmd = BRDGGMA;
+ if (ioctl(sock, SIOCGDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "update bridge: ioctl(BRDGGMA) failed: %s",
+ strerror(errno));
+ return (-1);
+ }
+
+ /* Centi-seconds. */
+ bif->bridge_max_age = 100 * b_param.ifbrp_maxage;
+
+ /* Configured hello time. */
+ ifd.ifd_cmd = BRDGGHT;
+ if (ioctl(sock, SIOCGDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "update bridge: ioctl(BRDGGHT) failed: %s",
+ strerror(errno));
+ return (-1);
+ }
+ bif->bridge_hello_time = 100 * b_param.ifbrp_hellotime;
+
+ /* Forward delay. */
+ ifd.ifd_cmd = BRDGGFD;
+ if (ioctl(sock, SIOCGDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "update bridge: ioctl(BRDGGFD) failed: %s",
+ strerror(errno));
+ return (-1);
+ }
+ bif->bridge_fwd_delay = 100 * b_param.ifbrp_fwddelay;
+
+ /* Number of dropped addresses. */
+ ifd.ifd_cmd = BRDGGRTE;
+ if (ioctl(sock, SIOCGDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "update bridge: ioctl(BRDGGRTE) failed: %s",
+ strerror(errno));
+ return (-1);
+ }
+ bif->lrnt_drops = b_param.ifbrp_cexceeded;
+
+ /* Address table timeout. */
+ ifd.ifd_cmd = BRDGGTO;
+ if (ioctl(sock, SIOCGDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "update bridge: ioctl(BRDGGTO) failed: %s",
+ strerror(errno));
+ return (-1);
+ }
+ bif->age_time = b_param.ifbrp_ctime;
+
+ /* Address table size. */
+ ifd.ifd_cmd = BRDGGCACHE;
+ if (ioctl(sock, SIOCGDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "update bridge: ioctl(BRDGGCACHE) "
+ "failed: %s", strerror(errno));
+ return (-1);
+ }
+ bif->max_addrs = b_param.ifbrp_csize;
+
+ return (0);
+}
+
+/*
+ * Fetch the current bridge STP operational parameters.
+ * Returns: -1 - on error;
+ * 0 - old TC time and Root Port values are same;
+ * 1 - topologyChange notification should be sent;
+ * 2 - newRoot notification should be sent.
+ */
+int
+bridge_get_op_param(struct bridge_if *bif)
+{
+ int new_root_send;
+ struct ifdrv ifd;
+ struct ifbropreq b_req;
+
+ strlcpy(ifd.ifd_name, bif->bif_name, IFNAMSIZ);
+ ifd.ifd_len = sizeof(b_req);
+ ifd.ifd_data = &b_req;
+ ifd.ifd_cmd = BRDGPARAM;
+
+ if (ioctl(sock, SIOCGDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "update bridge: ioctl(BRDGPARAM) failed: %s",
+ strerror(errno));
+ return (-1);
+ }
+
+ bif->max_age = 100 * b_req.ifbop_maxage;
+ bif->hello_time = 100 * b_req.ifbop_hellotime;
+ bif->fwd_delay = 100 * b_req.ifbop_fwddelay;
+ bif->stp_version = b_req.ifbop_protocol;
+ bif->tx_hold_count = b_req.ifbop_holdcount;
+
+ if (b_req.ifbop_root_port == 0 &&
+ bif->root_port != b_req.ifbop_root_port)
+ new_root_send = 2;
+ else
+ new_root_send = 0;
+
+ bif->root_port = b_req.ifbop_root_port;
+ bif->root_cost = b_req.ifbop_root_path_cost;
+ snmp_uint64_to_bridgeid(b_req.ifbop_designated_root,
+ bif->design_root);
+
+ if (bif->last_tc_time.tv_sec != b_req.ifbop_last_tc_time.tv_sec) {
+ bif->top_changes++;
+ bif->last_tc_time.tv_sec = b_req.ifbop_last_tc_time.tv_sec;
+ bif->last_tc_time.tv_usec = b_req.ifbop_last_tc_time.tv_usec;
+
+ /*
+ * "The trap is not sent if a (begemotBridge)NewRoot
+ * trap is sent for the same transition."
+ */
+ if (new_root_send == 0)
+ return (1);
+ }
+
+ return (new_root_send);
+}
+
+int
+bridge_getinfo_bif(struct bridge_if *bif)
+{
+ if (bridge_get_conf_param(bif) < 0)
+ return (-1);
+
+ return (bridge_get_op_param(bif));
+}
+
+int
+bridge_set_priority(struct bridge_if *bif, int32_t priority)
+{
+ struct ifdrv ifd;
+ struct ifbrparam b_param;
+
+ strlcpy(ifd.ifd_name, bif->bif_name, IFNAMSIZ);
+ ifd.ifd_len = sizeof(b_param);
+ ifd.ifd_data = &b_param;
+ b_param.ifbrp_prio = (uint32_t) priority;
+ ifd.ifd_cmd = BRDGSPRI;
+
+ if (ioctl(sock, SIOCSDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "set bridge param: ioctl(BRDGSPRI) "
+ "failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ /*
+ * Re-fetching the data from the driver after that might be a good
+ * idea, since changing our bridge's priority should invoke
+ * recalculation of the active spanning tree topology in the network.
+ */
+ bif->priority = priority;
+ return (0);
+}
+
+/*
+ * Convert 1/100 of seconds to 1/256 of seconds.
+ * Timeout ::= TEXTUAL-CONVENTION.
+ * To convert a Timeout value into a value in units of
+ * 1/256 seconds, the following algorithm should be used:
+ * b = floor( (n * 256) / 100)
+ * The conversion to 1/256 of a second happens in the kernel -
+ * just make sure we correctly convert the seconds to Timout
+ * and vice versa.
+ */
+static uint32_t
+snmp_timeout2_sec(int32_t secs)
+{
+ return (secs / 100);
+}
+
+int
+bridge_set_maxage(struct bridge_if *bif, int32_t max_age)
+{
+ struct ifdrv ifd;
+ struct ifbrparam b_param;
+
+ strlcpy(ifd.ifd_name, bif->bif_name, IFNAMSIZ);
+ ifd.ifd_len = sizeof(b_param);
+ ifd.ifd_data = &b_param;
+ b_param.ifbrp_maxage = snmp_timeout2_sec(max_age);
+ ifd.ifd_cmd = BRDGSMA;
+
+ if (ioctl(sock, SIOCSDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "set bridge param: ioctl(BRDGSMA) "
+ "failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ bif->bridge_max_age = max_age;
+ return (0);
+}
+
+int
+bridge_set_hello_time(struct bridge_if *bif, int32_t hello_time)
+{
+ struct ifdrv ifd;
+ struct ifbrparam b_param;
+
+ strlcpy(ifd.ifd_name, bif->bif_name, IFNAMSIZ);
+ ifd.ifd_len = sizeof(b_param);
+ ifd.ifd_data = &b_param;
+ b_param.ifbrp_hellotime = snmp_timeout2_sec(hello_time);
+ ifd.ifd_cmd = BRDGSHT;
+
+ if (ioctl(sock, SIOCSDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "set bridge param: ioctl(BRDGSHT) "
+ "failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ bif->bridge_hello_time = b_param.ifbrp_hellotime;
+ return (0);
+}
+
+int
+bridge_set_forward_delay(struct bridge_if *bif, int32_t fwd_delay)
+{
+ struct ifdrv ifd;
+ struct ifbrparam b_param;
+
+ strlcpy(ifd.ifd_name, bif->bif_name, IFNAMSIZ);
+ ifd.ifd_len = sizeof(b_param);
+ ifd.ifd_data = &b_param;
+ b_param.ifbrp_fwddelay = snmp_timeout2_sec(fwd_delay);
+ ifd.ifd_cmd = BRDGSFD;
+
+ if (ioctl(sock, SIOCSDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "set bridge param: ioctl(BRDGSFD) "
+ "failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ bif->bridge_fwd_delay = b_param.ifbrp_fwddelay;
+ return (0);
+}
+
+int
+bridge_set_aging_time(struct bridge_if *bif, int32_t age_time)
+{
+ struct ifdrv ifd;
+ struct ifbrparam b_param;
+
+ strlcpy(ifd.ifd_name, bif->bif_name, IFNAMSIZ);
+ ifd.ifd_len = sizeof(b_param);
+ ifd.ifd_data = &b_param;
+ b_param.ifbrp_ctime = (uint32_t) age_time;
+ ifd.ifd_cmd = BRDGSTO;
+
+ if (ioctl(sock, SIOCSDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "set bridge param: ioctl(BRDGSTO) "
+ "failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ bif->age_time = age_time;
+ return (0);
+}
+
+int
+bridge_set_max_cache(struct bridge_if *bif, int32_t max_cache)
+{
+ struct ifdrv ifd;
+ struct ifbrparam b_param;
+
+ strlcpy(ifd.ifd_name, bif->bif_name, IFNAMSIZ);
+ ifd.ifd_len = sizeof(b_param);
+ ifd.ifd_data = &b_param;
+ b_param.ifbrp_csize = max_cache;
+ ifd.ifd_cmd = BRDGSCACHE;
+
+ if (ioctl(sock, SIOCSDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "set bridge param: ioctl(BRDGSCACHE) "
+ "failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ bif->max_addrs = b_param.ifbrp_csize;
+ return (0);
+}
+
+int
+bridge_set_tx_hold_count(struct bridge_if *bif, int32_t tx_hc)
+{
+ struct ifdrv ifd;
+ struct ifbrparam b_param;
+
+ if (tx_hc < SNMP_BRIDGE_MIN_TXHC || tx_hc > SNMP_BRIDGE_MAX_TXHC)
+ return (-1);
+
+ strlcpy(ifd.ifd_name, bif->bif_name, IFNAMSIZ);
+ ifd.ifd_len = sizeof(b_param);
+ ifd.ifd_data = &b_param;
+ b_param.ifbrp_txhc = tx_hc;
+ ifd.ifd_cmd = BRDGSTXHC;
+
+ if (ioctl(sock, SIOCSDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "set bridge param: ioctl(BRDGSTXHC) "
+ "failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ bif->tx_hold_count = b_param.ifbrp_txhc;
+ return (0);
+}
+
+int
+bridge_set_stp_version(struct bridge_if *bif, int32_t stp_proto)
+{
+ struct ifdrv ifd;
+ struct ifbrparam b_param;
+
+ strlcpy(ifd.ifd_name, bif->bif_name, IFNAMSIZ);
+ ifd.ifd_len = sizeof(b_param);
+ ifd.ifd_data = &b_param;
+ b_param.ifbrp_proto = stp_proto;
+ ifd.ifd_cmd = BRDGSPROTO;
+
+ if (ioctl(sock, SIOCSDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "set bridge param: ioctl(BRDGSPROTO) "
+ "failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ bif->stp_version = b_param.ifbrp_proto;
+ return (0);
+}
+
+/*
+ * Set the bridge interface status to up/down.
+ */
+int
+bridge_set_if_up(const char* b_name, int8_t up)
+{
+ int flags;
+ struct ifreq ifr;
+
+ bzero(&ifr, sizeof(ifr));
+ strcpy(ifr.ifr_name, b_name);
+ if (ioctl(sock, SIOCGIFFLAGS, (caddr_t) &ifr) < 0) {
+ syslog(LOG_ERR, "set bridge up: ioctl(SIOCGIFFLAGS) "
+ "failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ flags = (ifr.ifr_flags & 0xffff) | (ifr.ifr_flagshigh << 16);
+ if (up == 1)
+ flags |= IFF_UP;
+ else
+ flags &= ~IFF_UP;
+
+ ifr.ifr_flags = flags & 0xffff;
+ ifr.ifr_flagshigh = flags >> 16;
+ if (ioctl(sock, SIOCSIFFLAGS, (caddr_t) &ifr) < 0) {
+ syslog(LOG_ERR, "set bridge up: ioctl(SIOCSIFFLAGS) "
+ "failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+bridge_create(const char *b_name)
+{
+ char *new_name;
+ struct ifreq ifr;
+
+ bzero(&ifr, sizeof(ifr));
+ strcpy(ifr.ifr_name, b_name);
+
+ if (ioctl(sock, SIOCIFCREATE, &ifr) < 0) {
+ syslog(LOG_ERR, "create bridge: ioctl(SIOCIFCREATE) "
+ "failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ if (strcmp(b_name, ifr.ifr_name) == 0)
+ return (0);
+
+ if ((new_name = strdup(b_name)) == NULL) {
+ syslog(LOG_ERR, "create bridge: strdup() failed");
+ return (-1);
+ }
+
+ ifr.ifr_data = new_name;
+ if (ioctl(sock, SIOCSIFNAME, (caddr_t) &ifr) < 0) {
+ syslog(LOG_ERR, "create bridge: ioctl(SIOCSIFNAME) "
+ "failed: %s", strerror(errno));
+ free(new_name);
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+bridge_destroy(const char *b_name)
+{
+ struct ifreq ifr;
+
+ bzero(&ifr, sizeof(ifr));
+ strcpy(ifr.ifr_name, b_name);
+
+ if (ioctl(sock, SIOCIFDESTROY, &ifr) < 0) {
+ syslog(LOG_ERR, "destroy bridge: ioctl(SIOCIFDESTROY) "
+ "failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Fetch the bridge base MAC address. Return pointer to the
+ * buffer containing the MAC address, NULL on failure.
+ */
+u_char *
+bridge_get_basemac(const char *bif_name, u_char *mac, size_t mlen)
+{
+ int len;
+ char if_name[IFNAMSIZ];
+ struct ifaddrs *ifap, *ifa;
+ struct sockaddr_dl sdl;
+
+ if (getifaddrs(&ifap) != 0) {
+ syslog(LOG_ERR, "bridge get mac: getifaddrs() failed - %s",
+ strerror(errno));
+ return (NULL);
+ }
+
+ for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr->sa_family != AF_LINK)
+ continue;
+
+ /*
+ * Not just casting because of alignment constraints
+ * on sparc64.
+ */
+ bcopy(ifa->ifa_addr, &sdl, sizeof(struct sockaddr_dl));
+
+ if (sdl.sdl_alen > mlen)
+ continue;
+
+ if ((len = sdl.sdl_nlen) >= IFNAMSIZ)
+ len = IFNAMSIZ - 1;
+
+ bcopy(sdl.sdl_data, if_name, len);
+ if_name[len] = '\0';
+
+ if (strcmp(bif_name, if_name) == 0) {
+ bcopy(sdl.sdl_data + sdl.sdl_nlen, mac, sdl.sdl_alen);
+ freeifaddrs(ifap);
+ return (mac);
+ }
+ }
+
+ freeifaddrs(ifap);
+ return (NULL);
+}
+
+/************************************************************************
+ * Bridge ports.
+ */
+
+/*
+ * Convert the kernel STP port state into
+ * the corresopnding enumerated type from SNMP Bridge MIB.
+ */
+static int
+state2snmp_st(uint8_t ifbr_state)
+{
+ switch (ifbr_state) {
+ case BSTP_IFSTATE_DISABLED:
+ return (StpPortState_disabled);
+ case BSTP_IFSTATE_LISTENING:
+ return (StpPortState_listening);
+ case BSTP_IFSTATE_LEARNING:
+ return (StpPortState_learning);
+ case BSTP_IFSTATE_FORWARDING:
+ return (StpPortState_forwarding);
+ case BSTP_IFSTATE_BLOCKING:
+ case BSTP_IFSTATE_DISCARDING:
+ return (StpPortState_blocking);
+ }
+
+ return (StpPortState_broken);
+}
+
+/*
+ * Fill in a bridge member information according to data polled from kernel.
+ */
+static void
+bridge_port_getinfo_conf(struct ifbreq *k_info, struct bridge_port *bp)
+{
+ bp->state = state2snmp_st(k_info->ifbr_state);
+ bp->priority = k_info->ifbr_priority;
+
+ /*
+ * RFC 4188:
+ * "New implementations should support dot1dStpPortPathCost32.
+ * If the port path costs exceeds the maximum value of this
+ * object then this object should report the maximum value,
+ * namely 65535. Applications should try to read the
+ * dot1dStpPortPathCost32 object if this object reports
+ * the maximum value."
+ */
+
+ if (k_info->ifbr_ifsflags & IFBIF_BSTP_ADMCOST)
+ bp->admin_path_cost = k_info->ifbr_path_cost;
+ else
+ bp->admin_path_cost = 0;
+
+ bp->path_cost = k_info->ifbr_path_cost;
+
+ if (k_info->ifbr_ifsflags & IFBIF_STP)
+ bp->enable = dot1dStpPortEnable_enabled;
+ else
+ bp->enable = dot1dStpPortEnable_disabled;
+
+ /* Begemot Bridge MIB only. */
+ if (k_info->ifbr_ifsflags & IFBIF_SPAN)
+ bp->span_enable = begemotBridgeBaseSpanEnabled_enabled;
+ else
+ bp->span_enable = begemotBridgeBaseSpanEnabled_disabled;
+
+ if (k_info->ifbr_ifsflags & IFBIF_PRIVATE)
+ bp->priv_set = TruthValue_true;
+ else
+ bp->priv_set = TruthValue_false;
+
+ if (k_info->ifbr_ifsflags & IFBIF_BSTP_ADMEDGE)
+ bp->admin_edge = TruthValue_true;
+ else
+ bp->admin_edge = TruthValue_false;
+
+ if (k_info->ifbr_ifsflags & IFBIF_BSTP_EDGE)
+ bp->oper_edge = TruthValue_true;
+ else
+ bp->oper_edge = TruthValue_false;
+
+ if (k_info->ifbr_ifsflags & IFBIF_BSTP_AUTOPTP) {
+ bp->admin_ptp = StpPortAdminPointToPointType_auto;
+ if (k_info->ifbr_ifsflags & IFBIF_BSTP_PTP)
+ bp->oper_ptp = TruthValue_true;
+ else
+ bp->oper_ptp = TruthValue_false;
+ } else if (k_info->ifbr_ifsflags & IFBIF_BSTP_PTP) {
+ bp->admin_ptp = StpPortAdminPointToPointType_forceTrue;
+ bp->oper_ptp = TruthValue_true;
+ } else {
+ bp->admin_ptp = StpPortAdminPointToPointType_forceFalse;
+ bp->oper_ptp = TruthValue_false;
+ }
+}
+
+/*
+ * Fill in a bridge interface STP information according to
+ * data polled from kernel.
+ */
+static void
+bridge_port_getinfo_opstp(struct ifbpstpreq *bp_stp, struct bridge_port *bp)
+{
+ bp->enable = dot1dStpPortEnable_enabled;
+ bp->fwd_trans = bp_stp->ifbp_fwd_trans;
+ bp->design_cost = bp_stp->ifbp_design_cost;
+ snmp_uint64_to_bridgeid(bp_stp->ifbp_design_root, bp->design_root);
+ snmp_uint64_to_bridgeid(bp_stp->ifbp_design_bridge, bp->design_bridge);
+ bcopy(&(bp_stp->ifbp_design_port), &(bp->design_port),
+ sizeof(uint16_t));
+}
+
+/*
+ * Clear a bridge interface STP information.
+ */
+static void
+bridge_port_clearinfo_opstp(struct bridge_port *bp)
+{
+ if (bp->enable == dot1dStpPortEnable_enabled) {
+ bp->design_cost = 0;
+ bzero(&(bp->design_root), sizeof(bridge_id));
+ bzero(&(bp->design_bridge), sizeof(bridge_id));
+ bzero(&(bp->design_port), sizeof(port_id));
+ bp->fwd_trans = 0;
+ }
+
+ bp->enable = dot1dStpPortEnable_disabled;
+}
+
+/*
+ * Set a bridge member priority.
+ */
+int
+bridge_port_set_priority(const char *bif_name, struct bridge_port *bp,
+ int32_t priority)
+{
+ struct ifdrv ifd;
+ struct ifbreq b_req;
+
+ strlcpy(ifd.ifd_name, bif_name, sizeof(ifd.ifd_name));
+ ifd.ifd_len = sizeof(b_req);
+ ifd.ifd_data = &b_req;
+ strlcpy(b_req.ifbr_ifsname, bp->p_name, sizeof(b_req.ifbr_ifsname));
+
+ b_req.ifbr_priority = (uint8_t) priority;
+ ifd.ifd_cmd = BRDGSIFPRIO;
+
+ if (ioctl(sock, SIOCSDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "set member %s param: ioctl(BRDGSIFPRIO) "
+ "failed: %s", bp->p_name, strerror(errno));
+ return (-1);
+ }
+
+ bp->priority = priority;
+ return (0);
+}
+
+/*
+ * Set a bridge member STP-enabled flag.
+ */
+int
+bridge_port_set_stp_enable(const char *bif_name, struct bridge_port *bp,
+ uint32_t enable)
+{
+ struct ifdrv ifd;
+ struct ifbreq b_req;
+
+ if (bp->enable == enable)
+ return (0);
+
+ bzero(&b_req, sizeof(b_req));
+ strlcpy(ifd.ifd_name, bif_name, sizeof(ifd.ifd_name));
+ ifd.ifd_len = sizeof(b_req);
+ ifd.ifd_data = &b_req;
+ strlcpy(b_req.ifbr_ifsname, bp->p_name, sizeof(b_req.ifbr_ifsname));
+ ifd.ifd_cmd = BRDGGIFFLGS;
+
+ if (ioctl(sock, SIOCGDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "get member %s param: ioctl(BRDGGIFFLGS) "
+ "failed: %s", bp->p_name, strerror(errno));
+ return (-1);
+ }
+
+ if (enable == dot1dStpPortEnable_enabled)
+ b_req.ifbr_ifsflags |= IFBIF_STP;
+ else
+ b_req.ifbr_ifsflags &= ~IFBIF_STP;
+
+ ifd.ifd_cmd = BRDGSIFFLGS;
+ if (ioctl(sock, SIOCSDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "set member %s param: ioctl(BRDGSIFFLGS) "
+ "failed: %s", bp->p_name, strerror(errno));
+ return (-1);
+ }
+
+ bp->enable = enable;
+ return (0);
+}
+
+/*
+ * Set a bridge member STP path cost.
+ */
+int
+bridge_port_set_path_cost(const char *bif_name, struct bridge_port *bp,
+ int32_t path_cost)
+{
+ struct ifdrv ifd;
+ struct ifbreq b_req;
+
+ if (path_cost < SNMP_PORT_MIN_PATHCOST ||
+ path_cost > SNMP_PORT_PATHCOST_OBSOLETE)
+ return (-2);
+
+ strlcpy(ifd.ifd_name, bif_name, sizeof(ifd.ifd_name));
+ ifd.ifd_len = sizeof(b_req);
+ ifd.ifd_data = &b_req;
+ strlcpy(b_req.ifbr_ifsname, bp->p_name, sizeof(b_req.ifbr_ifsname));
+
+ b_req.ifbr_path_cost = path_cost;
+ ifd.ifd_cmd = BRDGSIFCOST;
+
+ if (ioctl(sock, SIOCSDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "set member %s param: ioctl(BRDGSIFCOST) "
+ "failed: %s", bp->p_name, strerror(errno));
+ return (-1);
+ }
+
+ bp->admin_path_cost = path_cost;
+
+ return (0);
+}
+
+/*
+ * Set the PonitToPoint status of the link administratively.
+ */
+int
+bridge_port_set_admin_ptp(const char *bif_name, struct bridge_port *bp,
+ uint32_t admin_ptp)
+{
+ struct ifdrv ifd;
+ struct ifbreq b_req;
+
+ if (bp->admin_ptp == admin_ptp)
+ return (0);
+
+ bzero(&b_req, sizeof(b_req));
+ strlcpy(ifd.ifd_name, bif_name, sizeof(ifd.ifd_name));
+ ifd.ifd_len = sizeof(b_req);
+ ifd.ifd_data = &b_req;
+ strlcpy(b_req.ifbr_ifsname, bp->p_name, sizeof(b_req.ifbr_ifsname));
+ ifd.ifd_cmd = BRDGGIFFLGS;
+
+ if (ioctl(sock, SIOCGDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "get member %s param: ioctl(BRDGGIFFLGS) "
+ "failed: %s", bp->p_name, strerror(errno));
+ return (-1);
+ }
+
+ switch (admin_ptp) {
+ case StpPortAdminPointToPointType_forceTrue:
+ b_req.ifbr_ifsflags &= ~IFBIF_BSTP_AUTOPTP;
+ b_req.ifbr_ifsflags |= IFBIF_BSTP_PTP;
+ break;
+ case StpPortAdminPointToPointType_forceFalse:
+ b_req.ifbr_ifsflags &= ~IFBIF_BSTP_AUTOPTP;
+ b_req.ifbr_ifsflags &= ~IFBIF_BSTP_PTP;
+ break;
+ case StpPortAdminPointToPointType_auto:
+ b_req.ifbr_ifsflags |= IFBIF_BSTP_AUTOPTP;
+ break;
+ }
+
+ ifd.ifd_cmd = BRDGSIFFLGS;
+ if (ioctl(sock, SIOCSDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "set member %s param: ioctl(BRDGSIFFLGS) "
+ "failed: %s", bp->p_name, strerror(errno));
+ return (-1);
+ }
+
+ bp->admin_ptp = admin_ptp;
+ return (0);
+}
+
+/*
+ * Set admin edge.
+ */
+int
+bridge_port_set_admin_edge(const char *bif_name, struct bridge_port *bp,
+ uint32_t enable)
+{
+ struct ifdrv ifd;
+ struct ifbreq b_req;
+
+ if (bp->admin_edge == enable)
+ return (0);
+
+ bzero(&b_req, sizeof(b_req));
+ strlcpy(ifd.ifd_name, bif_name, sizeof(ifd.ifd_name));
+ ifd.ifd_len = sizeof(b_req);
+ ifd.ifd_data = &b_req;
+ strlcpy(b_req.ifbr_ifsname, bp->p_name, sizeof(b_req.ifbr_ifsname));
+ ifd.ifd_cmd = BRDGGIFFLGS;
+
+ if (ioctl(sock, SIOCGDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "get member %s param: ioctl(BRDGGIFFLGS) "
+ "failed: %s", bp->p_name, strerror(errno));
+ return (-1);
+ }
+
+ if (enable == TruthValue_true) {
+ b_req.ifbr_ifsflags &= ~IFBIF_BSTP_AUTOEDGE;
+ b_req.ifbr_ifsflags |= IFBIF_BSTP_EDGE;
+ } else
+ b_req.ifbr_ifsflags &= ~IFBIF_BSTP_EDGE;
+
+ ifd.ifd_cmd = BRDGSIFFLGS;
+ if (ioctl(sock, SIOCSDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "set member %s param: ioctl(BRDGSIFFLGS) "
+ "failed: %s", bp->p_name, strerror(errno));
+ return (-1);
+ }
+
+ bp->admin_edge = enable;
+
+ return (0);
+}
+
+/*
+ * Set 'private' flag.
+ */
+int
+bridge_port_set_private(const char *bif_name, struct bridge_port *bp,
+ uint32_t priv_set)
+{
+ struct ifdrv ifd;
+ struct ifbreq b_req;
+
+ if (bp->priv_set == priv_set)
+ return (0);
+
+ bzero(&b_req, sizeof(b_req));
+ strlcpy(ifd.ifd_name, bif_name, sizeof(ifd.ifd_name));
+ ifd.ifd_len = sizeof(b_req);
+ ifd.ifd_data = &b_req;
+ strlcpy(b_req.ifbr_ifsname, bp->p_name, sizeof(b_req.ifbr_ifsname));
+ ifd.ifd_cmd = BRDGGIFFLGS;
+
+ if (ioctl(sock, SIOCGDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "get member %s param: ioctl(BRDGGIFFLGS) "
+ "failed: %s", bp->p_name, strerror(errno));
+ return (-1);
+ }
+
+ if (priv_set == TruthValue_true)
+ b_req.ifbr_ifsflags |= IFBIF_PRIVATE;
+ else if (priv_set == TruthValue_false)
+ b_req.ifbr_ifsflags &= ~IFBIF_PRIVATE;
+ else
+ return (SNMP_ERR_WRONG_VALUE);
+
+ ifd.ifd_cmd = BRDGSIFFLGS;
+ if (ioctl(sock, SIOCSDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "set member %s param: ioctl(BRDGSIFFLGS) "
+ "failed: %s", bp->p_name, strerror(errno));
+ return (-1);
+ }
+
+ bp->priv_set = priv_set;
+
+ return (0);
+}
+
+
+/*
+ * Add a bridge member port.
+ */
+int
+bridge_port_addm(struct bridge_port *bp, const char *b_name)
+{
+ struct ifdrv ifd;
+ struct ifbreq b_req;
+
+ bzero(&ifd, sizeof(ifd));
+ bzero(&b_req, sizeof(b_req));
+
+ strlcpy(ifd.ifd_name, b_name, sizeof(ifd.ifd_name));
+ ifd.ifd_len = sizeof(b_req);
+ ifd.ifd_data = &b_req;
+ strlcpy(b_req.ifbr_ifsname, bp->p_name, sizeof(b_req.ifbr_ifsname));
+
+ if (bp->span_enable == begemotBridgeBaseSpanEnabled_enabled)
+ ifd.ifd_cmd = BRDGADDS;
+ else
+ ifd.ifd_cmd = BRDGADD;
+
+ if (ioctl(sock, SIOCSDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "%s - add member : ioctl(%s) failed: %s",
+ bp->p_name,
+ (ifd.ifd_cmd == BRDGADDS ? "BRDGADDS" : "BRDGADD"),
+ strerror(errno));
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Delete a bridge member port.
+ */
+int
+bridge_port_delm(struct bridge_port *bp, const char *b_name)
+{
+ struct ifdrv ifd;
+ struct ifbreq b_req;
+
+ bzero(&ifd, sizeof(ifd));
+ bzero(&b_req, sizeof(b_req));
+
+ strlcpy(ifd.ifd_name, b_name, sizeof(ifd.ifd_name));
+ ifd.ifd_len = sizeof(b_req);
+ ifd.ifd_data = &b_req;
+ strlcpy(b_req.ifbr_ifsname, bp->p_name, sizeof(b_req.ifbr_ifsname));
+
+ if (bp->span_enable == begemotBridgeBaseSpanEnabled_enabled)
+ ifd.ifd_cmd = BRDGDELS;
+ else
+ ifd.ifd_cmd = BRDGDEL;
+
+ if (ioctl(sock, SIOCSDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "%s - add member : ioctl(%s) failed: %s",
+ bp->p_name,
+ (ifd.ifd_cmd == BRDGDELS ? "BRDGDELS" : "BRDGDEL"),
+ strerror(errno));
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Fetch the bridge member list from kernel.
+ * Return -1 on error, or buffer len if successful.
+ */
+static int32_t
+bridge_port_get_iflist(struct bridge_if *bif, struct ifbreq **buf)
+{
+ int n = 128;
+ uint32_t len;
+ struct ifbreq *ninbuf;
+ struct ifbifconf ifbc;
+ struct ifdrv ifd;
+
+ *buf = NULL;
+ strlcpy(ifd.ifd_name, bif->bif_name, IFNAMSIZ);
+ ifd.ifd_cmd = BRDGGIFS;
+ ifd.ifd_len = sizeof(ifbc);
+ ifd.ifd_data = &ifbc;
+
+ for ( ; ; ) {
+ len = n * sizeof(struct ifbreq);
+ if ((ninbuf = (struct ifbreq *)realloc(*buf, len)) == NULL) {
+ syslog(LOG_ERR, "get bridge member list: "
+ "realloc failed: %s", strerror(errno));
+ free(*buf);
+ *buf = NULL;
+ return (-1);
+ }
+
+ ifbc.ifbic_len = len;
+ ifbc.ifbic_req = *buf = ninbuf;
+
+ if (ioctl(sock, SIOCGDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "get bridge member list: ioctl "
+ "(BRDGGIFS) failed: %s", strerror(errno));
+ free(*buf);
+ buf = NULL;
+ return (-1);
+ }
+
+ if ((ifbc.ifbic_len + sizeof(struct ifbreq)) < len)
+ break;
+
+ n += 64;
+ }
+
+ return (ifbc.ifbic_len);
+}
+
+/*
+ * Fetch the bridge STP member list from kernel.
+ * Return -1 on error, or buffer len if successful.
+ */
+static int32_t
+bridge_port_get_ifstplist(struct bridge_if *bif, struct ifbpstpreq **buf)
+{
+ int n = 128;
+ uint32_t len;
+ struct ifbpstpreq *ninbuf;
+ struct ifbpstpconf ifbstp;
+ struct ifdrv ifd;
+
+ *buf = NULL;
+ strlcpy(ifd.ifd_name, bif->bif_name, IFNAMSIZ);
+ ifd.ifd_cmd = BRDGGIFSSTP;
+ ifd.ifd_len = sizeof(ifbstp);
+ ifd.ifd_data = &ifbstp;
+
+ for ( ; ; ) {
+ len = n * sizeof(struct ifbpstpreq);
+ if ((ninbuf = (struct ifbpstpreq *)
+ realloc(*buf, len)) == NULL) {
+ syslog(LOG_ERR, "get bridge STP ports list: "
+ "realloc failed: %s", strerror(errno));
+ free(*buf);
+ *buf = NULL;
+ return (-1);
+ }
+
+ ifbstp.ifbpstp_len = len;
+ ifbstp.ifbpstp_req = *buf = ninbuf;
+
+ if (ioctl(sock, SIOCGDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "get bridge STP ports list: ioctl "
+ "(BRDGGIFSSTP) failed: %s", strerror(errno));
+ free(*buf);
+ buf = NULL;
+ return (-1);
+ }
+
+ if ((ifbstp.ifbpstp_len + sizeof(struct ifbpstpreq)) < len)
+ break;
+
+ n += 64;
+ }
+
+ return (ifbstp.ifbpstp_len);
+}
+
+/*
+ * Locate a bridge if STP params structure in a buffer.
+ */
+static struct ifbpstpreq *
+bridge_port_find_ifstplist(uint8_t port_no, struct ifbpstpreq *buf,
+ uint32_t buf_len)
+{
+ uint32_t i;
+ struct ifbpstpreq *bstp;
+
+ for (i = 0; i < buf_len / sizeof(struct ifbpstpreq); i++) {
+ bstp = buf + i;
+ if (bstp->ifbp_portno == port_no)
+ return (bstp);
+ }
+
+ return (NULL);
+}
+
+/*
+ * Read the initial info for all members of a bridge interface.
+ * Returns the number of ports, 0 - if none, otherwise
+ * -1 if some other error occurred.
+ */
+int
+bridge_getinfo_bif_ports(struct bridge_if *bif)
+{
+ uint32_t i;
+ int32_t buf_len;
+ struct ifbreq *b_req_buf, *b_req;
+ struct ifbpstpreq *bs_req_buf, *bs_req;
+ struct bridge_port *bp;
+ struct mibif *m_if;
+
+ if ((buf_len = bridge_port_get_iflist(bif, &b_req_buf)) < 0)
+ return (-1);
+
+ for (i = 0; i < buf_len / sizeof(struct ifbreq); i++) {
+ b_req = b_req_buf + i;
+
+ if ((m_if = mib_find_if_sys(b_req->ifbr_portno)) != NULL) {
+ /* Hopefully we will not fail here. */
+ if ((bp = bridge_new_port(m_if, bif)) != NULL) {
+ bp->status = RowStatus_active;
+ bridge_port_getinfo_conf(b_req, bp);
+ bridge_port_getinfo_mibif(m_if, bp);
+ }
+ } else {
+ syslog(LOG_ERR, "bridge member %s not present "
+ "in mibII ifTable", b_req->ifbr_ifsname);
+ }
+ }
+ free(b_req_buf);
+
+ if ((buf_len = bridge_port_get_ifstplist(bif, &bs_req_buf)) < 0)
+ return (-1);
+
+ for (bp = bridge_port_bif_first(bif); bp != NULL;
+ bp = bridge_port_bif_next(bp)) {
+ if ((bs_req = bridge_port_find_ifstplist(bp->port_no,
+ bs_req_buf, buf_len)) == NULL)
+ bridge_port_clearinfo_opstp(bp);
+ else
+ bridge_port_getinfo_opstp(bs_req, bp);
+ }
+ free(bs_req_buf);
+
+ return (i);
+}
+
+/*
+ * Update the information for the bridge interface members.
+ */
+int
+bridge_update_memif(struct bridge_if *bif)
+{
+ int added, updated;
+ uint32_t i;
+ int32_t buf_len;
+ struct ifbreq *b_req_buf, *b_req;
+ struct ifbpstpreq *bs_req_buf, *bs_req;
+ struct bridge_port *bp, *bp_next;
+ struct mibif *m_if;
+
+ if ((buf_len = bridge_port_get_iflist(bif, &b_req_buf)) < 0)
+ return (-1);
+
+ added = updated = 0;
+
+#define BP_FOUND 0x01
+ for (i = 0; i < buf_len / sizeof(struct ifbreq); i++) {
+ b_req = b_req_buf + i;
+
+ if ((m_if = mib_find_if_sys(b_req->ifbr_portno)) == NULL) {
+ syslog(LOG_ERR, "bridge member %s not present "
+ "in mibII ifTable", b_req->ifbr_ifsname);
+ continue;
+ }
+
+ if ((bp = bridge_port_find(m_if->index, bif)) == NULL &&
+ (bp = bridge_new_port(m_if, bif)) != NULL) {
+ bp->status = RowStatus_active;
+ added++;
+ }
+
+ if (bp != NULL) {
+ updated++;
+ bridge_port_getinfo_conf(b_req, bp);
+ bridge_port_getinfo_mibif(m_if, bp);
+ bp->flags |= BP_FOUND;
+ }
+ }
+ free(b_req_buf);
+
+ /* Clean up list. */
+ for (bp = bridge_port_bif_first(bif); bp != NULL; bp = bp_next) {
+ bp_next = bridge_port_bif_next(bp);
+
+ if ((bp->flags & BP_FOUND) == 0 &&
+ bp->status == RowStatus_active)
+ bridge_port_remove(bp, bif);
+ else
+ bp->flags |= ~BP_FOUND;
+ }
+#undef BP_FOUND
+
+ if ((buf_len = bridge_port_get_ifstplist(bif, &bs_req_buf)) < 0)
+ return (-1);
+
+ for (bp = bridge_port_bif_first(bif); bp != NULL;
+ bp = bridge_port_bif_next(bp)) {
+ if ((bs_req = bridge_port_find_ifstplist(bp->port_no,
+ bs_req_buf, buf_len)) == NULL)
+ bridge_port_clearinfo_opstp(bp);
+ else
+ bridge_port_getinfo_opstp(bs_req, bp);
+ }
+ free(bs_req_buf);
+ bif->ports_age = time(NULL);
+
+ return (updated);
+}
+
+/************************************************************************
+ * Bridge addresses.
+ */
+
+/*
+ * Update the bridge address info according to the polled data.
+ */
+static void
+bridge_addrs_info_ifaddrlist(struct ifbareq *ifba, struct tp_entry *tpe)
+{
+ tpe->port_no = if_nametoindex(ifba->ifba_ifsname);
+
+ if ((ifba->ifba_flags & IFBAF_TYPEMASK) == IFBAF_STATIC)
+ tpe->status = TpFdbStatus_mgmt;
+ else
+ tpe->status = TpFdbStatus_learned;
+}
+
+/*
+ * Read the bridge addresses from kernel.
+ * Return -1 on error, or buffer len if successful.
+ */
+static int32_t
+bridge_addrs_getinfo_ifalist(struct bridge_if *bif, struct ifbareq **buf)
+{
+ int n = 128;
+ uint32_t len;
+ struct ifbareq *ninbuf;
+ struct ifbaconf bac;
+ struct ifdrv ifd;
+
+ *buf = NULL;
+ strlcpy(ifd.ifd_name, bif->bif_name, IFNAMSIZ);
+ ifd.ifd_cmd = BRDGRTS;
+ ifd.ifd_len = sizeof(bac);
+ ifd.ifd_data = &bac;
+
+ for ( ; ; ) {
+ len = n * sizeof(struct ifbareq);
+ if ((ninbuf = (struct ifbareq *)realloc(*buf, len)) == NULL) {
+ syslog(LOG_ERR, "get bridge address list: "
+ " realloc failed: %s", strerror(errno));
+ free(*buf);
+ *buf = NULL;
+ return (-1);
+ }
+
+ bac.ifbac_len = len;
+ bac.ifbac_req = *buf = ninbuf;
+
+ if (ioctl(sock, SIOCGDRVSPEC, &ifd) < 0) {
+ syslog(LOG_ERR, "get bridge address list: "
+ "ioctl(BRDGRTS) failed: %s", strerror(errno));
+ free(*buf);
+ buf = NULL;
+ return (-1);
+ }
+
+ if ((bac.ifbac_len + sizeof(struct ifbareq)) < len)
+ break;
+
+ n += 64;
+ }
+
+ return (bac.ifbac_len);
+}
+
+/*
+ * Read the initial info for all addresses on a bridge interface.
+ * Returns the number of addresses, 0 - if none, otherwise
+ * -1 if some other error occurred.
+ */
+int
+bridge_getinfo_bif_addrs(struct bridge_if *bif)
+{
+ uint32_t i;
+ int32_t buf_len;
+ struct ifbareq *addr_req_buf, *addr_req;
+ struct tp_entry *te;
+
+ if ((buf_len = bridge_addrs_getinfo_ifalist(bif, &addr_req_buf)) < 0)
+ return (-1);
+
+ for (i = 0; i < buf_len / sizeof(struct ifbareq); i++) {
+ addr_req = addr_req_buf + i;
+
+ if ((te = bridge_new_addrs(addr_req->ifba_dst, bif)) != NULL)
+ bridge_addrs_info_ifaddrlist(addr_req, te);
+ }
+
+ free(addr_req_buf);
+ return (i);
+}
+
+/*
+ * Update the addresses for the bridge interface.
+ */
+int
+bridge_update_addrs(struct bridge_if *bif)
+{
+ int added, updated;
+ uint32_t i;
+ int32_t buf_len;
+ struct tp_entry *te, *te_next;
+ struct ifbareq *addr_req_buf, *addr_req;
+
+ if ((buf_len = bridge_addrs_getinfo_ifalist(bif, &addr_req_buf)) < 0)
+ return (-1);
+
+ added = updated = 0;
+
+#define BA_FOUND 0x01
+ for (i = 0; i < buf_len / sizeof(struct ifbareq); i++) {
+ addr_req = addr_req_buf + i;
+
+ if ((te = bridge_addrs_find(addr_req->ifba_dst, bif)) == NULL) {
+ added++;
+
+ if ((te = bridge_new_addrs(addr_req->ifba_dst, bif))
+ == NULL)
+ continue;
+ } else
+ updated++;
+
+ bridge_addrs_info_ifaddrlist(addr_req, te);
+ te-> flags |= BA_FOUND;
+ }
+ free(addr_req_buf);
+
+ for (te = bridge_addrs_bif_first(bif); te != NULL; te = te_next) {
+ te_next = bridge_addrs_bif_next(te);
+
+ if ((te-> flags & BA_FOUND) == 0)
+ bridge_addrs_remove(te, bif);
+ else
+ te-> flags &= ~BA_FOUND;
+ }
+#undef BA_FOUND
+
+ bif->addrs_age = time(NULL);
+ return (updated + added);
+}
+
+/************************************************************************
+ * Bridge packet filtering.
+ */
+const char bridge_sysctl[] = "net.link.bridge.";
+
+static struct {
+ int32_t val;
+ const char *name;
+} bridge_pf_sysctl[] = {
+ { 1, "pfil_bridge" },
+ { 1, "pfil_member" },
+ { 1, "pfil_onlyip" },
+ { 0, "ipfw" },
+};
+
+int32_t
+bridge_get_pfval(uint8_t which)
+{
+ if (which > sizeof(bridge_pf_sysctl) / sizeof(bridge_pf_sysctl[0])
+ || which < 1)
+ return (-1);
+
+ return (bridge_pf_sysctl[which - 1].val);
+}
+
+int32_t
+bridge_do_pfctl(int32_t bridge_ctl, enum snmp_op op, int32_t *val)
+{
+ char mib_name[100];
+ int32_t i, s_i;
+ size_t len, s_len;
+
+ if (bridge_ctl >= LEAF_begemotBridgeLayer2PfStatus)
+ return (-2);
+
+ if (op == SNMP_OP_SET) {
+ s_i = *val;
+ s_len = sizeof(s_i);
+ } else
+ s_len = 0;
+
+ len = sizeof(i);
+
+ strcpy(mib_name, bridge_sysctl);
+
+ if (sysctlbyname(strcat(mib_name,
+ bridge_pf_sysctl[bridge_ctl].name), &i, &len,
+ (op == SNMP_OP_SET ? &s_i : NULL), s_len) == -1) {
+ syslog(LOG_ERR, "sysctl(%s%s) failed - %s", bridge_sysctl,
+ bridge_pf_sysctl[bridge_ctl].name, strerror(errno));
+ return (-1);
+ }
+
+ bridge_pf_sysctl[bridge_ctl].val = i;
+ *val = i;
+
+ return (i);
+}
+
+void
+bridge_pf_dump(void)
+{
+ uint8_t i;
+
+ for (i = 0; i < sizeof(bridge_pf_sysctl) / sizeof(bridge_pf_sysctl[0]);
+ i++) {
+ syslog(LOG_ERR, "%s%s = %d", bridge_sysctl,
+ bridge_pf_sysctl[i].name, bridge_pf_sysctl[i].val);
+ }
+}
diff --git a/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_tree.def b/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_tree.def
new file mode 100644
index 0000000..2e88e5e
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_bridge/bridge_tree.def
@@ -0,0 +1,283 @@
+#-
+# Copyright (c) 2006 Shteryana Shopova <syrinx@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 "tc.def"
+
+typedef RowStatus ENUM (
+ 1 active
+ 2 notInService
+ 3 notReady
+ 4 createAndGo
+ 5 createAndWait
+ 6 destroy
+)
+
+typedef TruthValue ENUM (
+ 1 true
+ 2 false
+)
+
+typedef StpPortState ENUM (
+ 1 disabled
+ 2 blocking
+ 3 listening
+ 4 learning
+ 5 forwarding
+ 6 broken
+)
+
+typedef StpPortAdminPointToPointType ENUM (
+ 0 forceTrue
+ 1 forceFalse
+ 2 auto
+)
+
+typedef BaseType ENUM (
+ 1 unknown
+ 2 transparent-only
+ 3 sourceroute-only
+ 4 srt
+)
+
+typedef TpFdbStatus ENUM (
+ 1 other
+ 2 invalid
+ 3 learned
+ 4 self
+ 5 mgmt
+)
+
+(1 internet
+ (2 mgmt
+ (1 mib_2
+ (17 dot1dBridge
+ (0 dot1dNotifications
+ (1 newRoot OID op_snmp_trap)
+ (2 topologyChange OID op_snmp_trap)
+ )
+ (1 dot1dBase
+ (1 dot1dBaseBridgeAddress OCTETSTRING | MacAddress op_dot1d_base GET)
+ (2 dot1dBaseNumPorts INTEGER32 op_dot1d_base GET)
+ (3 dot1dBaseType BaseType op_dot1d_base GET)
+ (4 dot1dBasePortTable
+ (1 dot1dBasePortEntry : INTEGER op_dot1d_base_port
+ (1 dot1dBasePort INTEGER GET)
+ (2 dot1dBasePortIfIndex INTEGER GET)
+ (3 dot1dBasePortCircuit OID GET)
+ (4 dot1dBasePortDelayExceededDiscards COUNTER GET)
+ (5 dot1dBasePortMtuExceededDiscards COUNTER GET)
+ ))
+ )
+ (2 dot1dStp
+ (1 dot1dStpProtocolSpecification ENUM ( 1 unknown 2 decLb100 3 ieee8021d ) op_dot1d_stp GET)
+ (2 dot1dStpPriority INTEGER op_dot1d_stp GET SET)
+ (3 dot1dStpTimeSinceTopologyChange TIMETICKS op_dot1d_stp GET)
+ (4 dot1dStpTopChanges COUNTER op_dot1d_stp GET)
+ (5 dot1dStpDesignatedRoot OCTETSTRING | BridgeId op_dot1d_stp GET)
+ (6 dot1dStpRootCost INTEGER32 op_dot1d_stp GET)
+ (7 dot1dStpRootPort INTEGER32 op_dot1d_stp GET)
+ (8 dot1dStpMaxAge INTEGER op_dot1d_stp GET)
+ (9 dot1dStpHelloTime INTEGER op_dot1d_stp GET)
+ (10 dot1dStpHoldTime INTEGER32 op_dot1d_stp GET)
+ (11 dot1dStpForwardDelay INTEGER op_dot1d_stp GET)
+ (12 dot1dStpBridgeMaxAge INTEGER op_dot1d_stp GET SET)
+ (13 dot1dStpBridgeHelloTime INTEGER op_dot1d_stp GET SET)
+ (14 dot1dStpBridgeForwardDelay INTEGER op_dot1d_stp GET SET)
+ (15 dot1dStpPortTable
+ (1 dot1dStpPortEntry : INTEGER op_dot1d_stp_port
+ (1 dot1dStpPort INTEGER GET)
+ (2 dot1dStpPortPriority INTEGER GET SET)
+ (3 dot1dStpPortState StpPortState GET)
+ (4 dot1dStpPortEnable ENUM ( 1 enabled 2 disabled ) GET SET)
+ (5 dot1dStpPortPathCost INTEGER GET SET)
+ (6 dot1dStpPortDesignatedRoot OCTETSTRING | BridgeId GET)
+ (7 dot1dStpPortDesignatedCost INTEGER32 GET)
+ (8 dot1dStpPortDesignatedBridge OCTETSTRING | BridgeId GET)
+ (9 dot1dStpPortDesignatedPort OCTETSTRING | BridgePortId GET)
+ (10 dot1dStpPortForwardTransitions COUNTER GET)
+ ))
+ (16 dot1dStpVersion ENUM ( 0 stpCompatible 2 rstp ) op_dot1d_stp GET SET)
+ (17 dot1dStpTxHoldCount INTEGER op_dot1d_stp GET SET)
+ (19 dot1dStpExtPortTable
+ (1 dot1dStpExtPortEntry : INTEGER op_dot1d_stp_ext_port
+ (1 dot1dStpPortProtocolMigration TruthValue GET) # SET
+ (2 dot1dStpPortAdminEdgePort TruthValue GET SET)
+ (3 dot1dStpPortOperEdgePort TruthValue GET)
+ (4 dot1dStpPortAdminPointToPoint StpPortAdminPointToPointType GET SET)
+ (5 dot1dStpPortOperPointToPoint TruthValue GET)
+ (6 dot1dStpPortAdminPathCost INTEGER GET SET)
+ ))
+ )
+ (3 dot1dSr
+ )
+ (4 dot1dTp
+ (1 dot1dTpLearnedEntryDiscards COUNTER op_dot1d_tp GET)
+ (2 dot1dTpAgingTime INTEGER op_dot1d_tp GET SET)
+ (3 dot1dTpFdbTable
+ (1 dot1dTpFdbEntry : OCTETSTRING | MacAddress op_dot1d_tp_fdb
+ (1 dot1dTpFdbAddress OCTETSTRING | MacAddress GET)
+ (2 dot1dTpFdbPort INTEGER32 GET)
+ (3 dot1dTpFdbStatus TpFdbStatus GET)
+ ))
+ (4 dot1dTpPortTable
+ (1 dot1dTpPortEntry : INTEGER op_dot1d_tp_port
+ (1 dot1dTpPort INTEGER GET)
+ (2 dot1dTpPortMaxInfo INTEGER32 GET)
+ (3 dot1dTpPortInFrames COUNTER GET)
+ (4 dot1dTpPortOutFrames COUNTER GET)
+ (5 dot1dTpPortInDiscards COUNTER GET)
+ ))
+ )
+ (5 dot1dStatic
+ )
+ (8 dot1dConformance
+ (1 dot1dGroups
+ )
+ (2 dot1dCompliances
+ )
+ )
+ )
+ (134 rstpMIB
+ (0 rstpNotifications
+ )
+ (1 rstpObjects
+ )
+ (2 rstpConformance
+ (1 rstpGroups
+ )
+ (2 rstpCompliances
+ )
+ )
+ )))
+ (4 private
+ (1 enterprises
+ (12325 fokus
+ (1 begemot
+ (205 begemotBridge
+ (0 begemotBridgeNotifications
+ (1 begemotBridgeNewRoot OID op_snmp_trap)
+ (2 begemotBridgeTopologyChange OID op_snmp_trap)
+ )
+ (1 begemotBridgeBase
+ (1 begemotBridgeBaseTable
+ (1 begemotBridgeBaseEntry : OCTETSTRING | BridgeIfName op_begemot_base_bridge
+ (1 begemotBridgeBaseName OCTETSTRING | BridgeIfName GET)
+ (2 begemotBridgeBaseAddress OCTETSTRING | MacAddress GET)
+ (3 begemotBridgeBaseNumPorts INTEGER32 GET)
+ (4 begemotBridgeBaseType BaseType GET)
+ (5 begemotBridgeBaseStatus RowStatus GET SET)
+ ))
+ (2 begemotBridgeBasePortTable
+ (1 begemotBridgeBasePortEntry : OCTETSTRING | BridgeIfName INTEGER op_begemot_base_port
+ (1 begemotBridgeBasePort INTEGER GET)
+ (2 begemotBridgeBasePortIfIndex INTEGER GET)
+ (3 begemotBridgeBaseSpanEnabled ENUM ( 1 enabled 2 disabled ) GET SET)
+ (4 begemotBridgeBasePortDelayExceededDiscards COUNTER GET)
+ (5 begemotBridgeBasePortMtuExceededDiscards COUNTER GET)
+ (6 begemotBridgeBasePortStatus RowStatus GET SET)
+ (7 begemotBridgeBasePortPrivate TruthValue GET SET)
+ ))
+ )
+ (2 begemotBridgeStp
+ (1 begemotBridgeStpTable
+ (1 begemotBridgeStpEntry : OCTETSTRING | BridgeIfName op_begemot_stp
+ (1 begemotBridgeStpProtocolSpecification ENUM ( 1 unknown 2 decLb100 3 ieee8021d ) GET)
+ (2 begemotBridgeStpPriority INTEGER GET SET)
+ (3 begemotBridgeStpTimeSinceTopologyChange TIMETICKS GET)
+ (4 begemotBridgeStpTopChanges COUNTER GET)
+ (5 begemotBridgeStpDesignatedRoot OCTETSTRING | BridgeId GET)
+ (6 begemotBridgeStpRootCost INTEGER32 GET)
+ (7 begemotBridgeStpRootPort INTEGER32 GET)
+ (8 begemotBridgeStpMaxAge INTEGER GET)
+ (9 begemotBridgeStpHelloTime INTEGER GET)
+ (10 begemotBridgeStpHoldTime INTEGER32 GET)
+ (11 begemotBridgeStpForwardDelay INTEGER GET)
+ (12 begemotBridgeStpBridgeMaxAge INTEGER GET SET)
+ (13 begemotBridgeStpBridgeHelloTime INTEGER GET SET)
+ (14 begemotBridgeStpBridgeForwardDelay INTEGER GET SET)
+ (15 begemotBridgeStpVersion ENUM ( 0 stpCompatible 2 rstp ) GET SET)
+ (16 begemotBridgeStpTxHoldCount INTEGER GET SET)
+ ))
+ (2 begemotBridgeStpPortTable
+ (1 begemotBridgeStpPortEntry : OCTETSTRING | BridgeIfName INTEGER op_begemot_stp_port
+ (1 begemotBridgeStpPort INTEGER GET)
+ (2 begemotBridgeStpPortPriority INTEGER GET SET)
+ (3 begemotBridgeStpPortState StpPortState GET)
+ (4 begemotBridgeStpPortEnable ENUM ( 1 enabled 2 disabled ) GET SET)
+ (5 begemotBridgeStpPortPathCost INTEGER GET SET)
+ (6 begemotBridgeStpPortDesignatedRoot OCTETSTRING | BridgeId GET)
+ (7 begemotBridgeStpPortDesignatedCost INTEGER32 GET)
+ (8 begemotBridgeStpPortDesignatedBridge OCTETSTRING | BridgeId GET)
+ (9 begemotBridgeStpPortDesignatedPort OCTETSTRING | BridgePortId GET)
+ (10 begemotBridgeStpPortForwardTransitions COUNTER GET)
+ ))
+ (3 begemotBridgeStpExtPortTable
+ (1 begemotBridgeStpExtPortEntry : OCTETSTRING | BridgeIfName INTEGER op_begemot_stp_ext_port
+ (1 begemotBridgeStpPortProtocolMigration TruthValue GET) # SET
+ (2 begemotBridgeStpPortAdminEdgePort TruthValue GET SET)
+ (3 begemotBridgeStpPortOperEdgePort TruthValue GET)
+ (4 begemotBridgeStpPortAdminPointToPoint StpPortAdminPointToPointType GET SET)
+ (5 begemotBridgeStpPortOperPointToPoint TruthValue GET)
+ (6 begemotBridgeStpPortAdminPathCost INTEGER GET SET)
+ ))
+ )
+ (3 begemotBridgeTp
+ (1 begemotBridgeTpTable
+ (1 begemotBridgeTpEntry : OCTETSTRING | BridgeIfName op_begemot_tp
+ (1 begemotBridgeTpLearnedEntryDiscards COUNTER GET)
+ (2 begemotBridgeTpAgingTime INTEGER GET SET)
+ (3 begemotBridgeTpMaxAddresses INTEGER GET SET)
+ ))
+ (2 begemotBridgeTpFdbTable
+ (1 begemotBridgeTpFdbEntry : OCTETSTRING | BridgeIfName OCTETSTRING | MacAddress op_begemot_tp_fdb
+ (1 begemotBridgeTpFdbAddress OCTETSTRING | MacAddress GET)
+ (2 begemotBridgeTpFdbPort INTEGER32 GET)
+ (3 begemotBridgeTpFdbStatus TpFdbStatus GET)
+ ))
+ (3 begemotBridgeTpPortTable
+ (1 begemotBridgeTpPortEntry : OCTETSTRING | BridgeIfName INTEGER op_begemot_tp_port
+ (1 begemotBridgeTpPort INTEGER GET)
+ (2 begemotBridgeTpPortMaxInfo INTEGER32 GET)
+ (3 begemotBridgeTpPortInFrames COUNTER GET)
+ (4 begemotBridgeTpPortOutFrames COUNTER GET)
+ (5 begemotBridgeTpPortInDiscards COUNTER GET)
+ ))
+ )
+ (4 begemotBridgePf
+ (1 begemotBridgePfilStatus TruthValue op_begemot_bridge_pf GET SET)
+ (2 begemotBridgePfilMembers TruthValue op_begemot_bridge_pf GET SET)
+ (3 begemotBridgePfilIpOnly TruthValue op_begemot_bridge_pf GET SET)
+ (4 begemotBridgeLayer2PfStatus ENUM ( 1 enabled 2 disabled ) op_begemot_bridge_pf GET SET)
+ )
+ (5 begemotBridgeConfigObjects
+ (1 begemotBridgeDefaultBridgeIf OCTETSTRING | BridgeIfNameOrEmpty op_begemot_bridge_config GET SET)
+ (2 begemotBridgeDataUpdate INTEGER op_begemot_bridge_config GET SET)
+ (3 begemotBridgeDataPoll INTEGER op_begemot_bridge_config GET SET)
+ )
+ )))))
+)
diff --git a/usr.sbin/bsnmpd/modules/snmp_bridge/snmp_bridge.3 b/usr.sbin/bsnmpd/modules/snmp_bridge/snmp_bridge.3
new file mode 100644
index 0000000..09484cf
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_bridge/snmp_bridge.3
@@ -0,0 +1,119 @@
+.\"-
+.\" Copyright (C) 2006 Shteryana Shopova <syrinx@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 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 August 6, 2007
+.Dt SNMP_BRIDGE 3
+.Os
+.Sh NAME
+.Nm snmp_bridge
+.Nd "bridge module for snmpd"
+.Sh LIBRARY
+.Pq begemotSnmpdModulePath."bridge" = "/usr/lib/snmp_bridge.so"
+.Sh DESCRIPTION
+The
+.Nm snmp_bridge
+module implements the BRIDGE-MIB as standardized in RFC 4188, the RSTP-MIB
+standardized in RFC4318 and a private BEGEMOT-BRIDGE-MIB, which allows
+management of multiple bridge interfaces.
+Most of the objects defined in the private BEGEMOT-BRIDGE-MIB are duplicates
+of the original objects defined by the standard BRIDGE-MIB, but the private
+MIB also defines additional objects which make the functionality of
+.Nm
+similar to
+.Xr ifconfig 8
+for configuring bridge interfaces.
+Therefore one should consider adding write communities or loading the
+.Nm
+module on systems where security is crucial.
+.Sh IMPLEMENTATION NOTES
+The additional objects to configure a bridge are:
+.Bl -tag -width "XXXXXXXXX"
+.It Va begemotBridgeBaseStatus
+Bridge interfaces can be created and destroyed via this object.
+SNMP SET operations with the following values are allowed:
+.Bl -tag -width ".It Va createAndWait"
+.It Va createAndWait
+will attempt to create a bridge interface with the name given by the table
+index.
+.It Va createAndGo
+will attempt to create a bridge interface with the name given by the table
+index and set the status of the interface to "active/up".
+.It Va destroy
+will attempt to destroy the bridge interface.
+.El
+.It Va begemotBridgeBaseSpanEnabled
+A SNMP SET operation on this object is only successful if the corresponding
+port has not been added as member of the bridge interface on the system.
+.It Va begemotBridgeBasePortStatus
+SNMP SET operations with the following values are allowed:
+.Bl -tag -width ".It Va createAndWait"
+.It Va createAndWait
+will create a new row for the bridge member in the SNMP
+.Va begemotBridgeBasePortTable
+but will not try to commit the information to the system.
+.It Va active
+will attempt to commit the information to the system and will be successful
+only if a value for
+.Va begemotBridgeBaseSpanEnabled
+has been SET already.
+.It Va destroy
+will attempt to remove the interface from the system bridge interface.
+.El
+.It Va begemotBridgeBasePortPrivate
+This object controls a bridge interface flag called PRIVATE where any private
+port can not communicate with another private port.
+.El
+.Sh RESTRICTIONS
+Not all information in the MIBs is currently available in FreeBSD.
+The following variables carry no information:
+.Bl -tag -width "XXXXXXXXX"
+.It Va dot1dBasePortCircuit
+.It Va dot1dBasePortDelayExceededDiscards
+.It Va dot1dBasePortMtuExceededDiscards
+.It Va begemotBridgeBasePortDelayExceededDiscards
+.It Va begemotBridgeBasePortMtuExceededDiscards
+.El
+.Sh FILES
+.Bl -tag -width "XXXXXXXXX"
+.It Pa /usr/share/snmp/defs/bridge_tree.def
+The description of the MIB tree implemented by
+.Nm .
+.It Pa /usr/share/snmp/mibs/BRIDGE-MIB.txt
+This is the BRIDGE-MIB that is implemented by this module.
+.It Pa /usr/share/snmp/mibs/RSTP-MIB.txt
+This is the RSTP-MIB implemented by this module.
+.It Pa /usr/share/snmp/mibs/BEGEMOT-BRIDGE-MIB.txt
+This is the private BEGEMOT-BRIDGE-MIB that is implemented by this module.
+.El
+.Sh SEE ALSO
+.Xr bsnmpd 1 ,
+.Xr gensnmptree 1 ,
+.Xr snmpmod 3 ,
+.Xr if_bridge 4 ,
+.Xr ifconfig 8
+.Sh AUTHORS
+.An Shteryana Shopova Aq Mt syrinx@FreeBSD.org
diff --git a/usr.sbin/bsnmpd/modules/snmp_hast/BEGEMOT-HAST-MIB.txt b/usr.sbin/bsnmpd/modules/snmp_hast/BEGEMOT-HAST-MIB.txt
new file mode 100644
index 0000000..0f330c1
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hast/BEGEMOT-HAST-MIB.txt
@@ -0,0 +1,362 @@
+--
+-- Copyright (c) 2013 Mikolaj Golub <trociny@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 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$
+--
+
+BEGEMOT-HAST-MIB DEFINITIONS ::= BEGIN
+
+IMPORTS
+ MODULE-IDENTITY, OBJECT-TYPE, NOTIFICATION-TYPE,
+ Counter64, Integer32
+ FROM SNMPv2-SMI
+ TEXTUAL-CONVENTION, RowStatus
+ FROM SNMPv2-TC
+ InterfaceIndex, ifIndex
+ FROM IF-MIB
+ begemot
+ FROM BEGEMOT-MIB;
+
+begemotHast MODULE-IDENTITY
+ LAST-UPDATED "201304130000Z"
+ ORGANIZATION "FreeBSD"
+ CONTACT-INFO
+ " Mikolaj Golub
+
+ Postal: Bluhera 27v 11
+ 61146 Kharkiv
+ Ukraine
+
+ Fax: N/A
+
+ E-Mail: trociny@FreeBSD.org"
+ DESCRIPTION
+ "The Begemot MIB for managing HAST."
+ REVISION "201304130000Z"
+ DESCRIPTION
+ "Initial revision."
+ REVISION "201307010000Z"
+ DESCRIPTION
+ "Added hastResourceWorkerPid."
+ REVISION "201312290000Z"
+ DESCRIPTION
+ "Added hastResourceLocalQueue, hastResourceSendQueue,
+ hastResourceRecvQueue, hastResourceDoneQueue,
+ hastResourceIdleQueue."
+ ::= { begemot 220 }
+
+begemotHastObjects OBJECT IDENTIFIER ::= { begemotHast 1 }
+
+-- ---------------------------------------------------------- --
+-- Configuration parameters
+-- ---------------------------------------------------------- --
+
+hastConfig OBJECT IDENTIFIER ::= { begemotHastObjects 1 }
+
+hastConfigFile OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "HAST configuration file location."
+ ::= { hastConfig 1 }
+
+-- ---------------------------------------------------------- --
+-- Resource Table
+-- ---------------------------------------------------------- --
+hastResourceTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF HastResourceEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table containing information about all HAST resources."
+ ::= { begemotHastObjects 2 }
+
+hastResourceEntry OBJECT-TYPE
+ SYNTAX HastResourceEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Table entry that describes one HAST resource."
+ INDEX { hastResourceIndex }
+ ::= { hastResourceTable 1 }
+
+HastResourceEntry ::= SEQUENCE {
+ hastResourceIndex Integer32,
+ hastResourceName OCTET STRING,
+ hastResourceRole INTEGER,
+ hastResourceProvName OCTET STRING,
+ hastResourceLocalPath OCTET STRING,
+ hastResourceExtentSize Integer32,
+ hastResourceKeepDirty Integer32,
+ hastResourceRemoteAddr OCTET STRING,
+ hastResourceSourceAddr OCTET STRING,
+ hastResourceReplication INTEGER,
+ hastResourceStatus INTEGER,
+ hastResourceDirty Counter64,
+ hastResourceReads Counter64,
+ hastResourceWrites Counter64,
+ hastResourceDeletes Counter64,
+ hastResourceFlushes Counter64,
+ hastResourceActivemapUpdates Counter64,
+ hastResourceReadErrors Counter64,
+ hastResourceWriteErrors Counter64,
+ hastResourceDeleteErrors Counter64,
+ hastResourceFlushErrors Counter64,
+ hastResourceWorkerPid INTEGER,
+ hastResourceLocalQueue UNSIGNED32,
+ hastResourceSendQueue UNSIGNED32,
+ hastResourceRecvQueue UNSIGNED32,
+ hastResourceDoneQueue UNSIGNED32,
+ hastResourceIdleQueue UNSIGNED32
+}
+
+hastResourceIndex OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Resource index."
+ ::= { hastResourceEntry 1 }
+
+hastResourceName OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Resource name."
+ ::= { hastResourceEntry 2 }
+
+hastResourceRole OBJECT-TYPE
+ SYNTAX INTEGER { undef(0), init(1), primary(2), secondary(3) }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "Resource role."
+ ::= { hastResourceEntry 3 }
+
+hastResourceProvName OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Resource GEOM provider name that appears as /dev/hast/<name>."
+ ::= { hastResourceEntry 4 }
+
+hastResourceLocalPath OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Path to the local component which is used as a backend
+ provider for the resource."
+ ::= { hastResourceEntry 5 }
+
+hastResourceExtentSize OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Size of an extent. Extent is a block which is
+ used for synchronization. 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."
+ ::= { hastResourceEntry 6 }
+
+hastResourceKeepDirty OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Maximum number of dirty extents to keep dirty all
+ the time. Most recently used extents are kept
+ dirty to reduce number of metadata updates."
+ ::= { hastResourceEntry 7 }
+
+hastResourceRemoteAddr OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Address of the remote hastd(8) daemon for the resource."
+ ::= { hastResourceEntry 8 }
+
+hastResourceSourceAddr OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Local address the resource is bound to."
+ ::= { hastResourceEntry 9 }
+
+hastResourceReplication OBJECT-TYPE
+ SYNTAX INTEGER { fullsync(0), memsync(1), async(2) }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Resource replication mode."
+ ::= { hastResourceEntry 10 }
+
+hastResourceStatus OBJECT-TYPE
+ SYNTAX INTEGER { complete(0), degraded(1) }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Resource replication status."
+ ::= { hastResourceEntry 11 }
+
+hastResourceDirty OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Current number of dirty extents for the resource."
+ ::= { hastResourceEntry 12 }
+
+hastResourceReads OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Count of resource local read operations."
+ ::= { hastResourceEntry 13 }
+
+hastResourceWrites OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Count of resource local write operations."
+ ::= { hastResourceEntry 14 }
+
+hastResourceDeletes OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Count of resource local delete operations."
+ ::= { hastResourceEntry 15 }
+
+hastResourceFlushes OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Count of resource local flush operations."
+ ::= { hastResourceEntry 16 }
+
+hastResourceActivemapUpdates OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Count of resource local activemap updates."
+ ::= { hastResourceEntry 17 }
+
+hastResourceReadErrors OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Count of resource local read operations that failed."
+ ::= { hastResourceEntry 18 }
+
+hastResourceWriteErrors OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Count of resource local write operations that failed."
+ ::= { hastResourceEntry 19 }
+
+hastResourceDeleteErrors OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Count of resource local delete operations that failed."
+ ::= { hastResourceEntry 20 }
+
+hastResourceFlushErrors OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Count of resource local flush operations that failed."
+ ::= { hastResourceEntry 21 }
+
+hastResourceWorkerPid OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Worker process ID."
+ ::= { hastResourceEntry 22 }
+
+hastResourceLocalQueue OBJECT-TYPE
+ SYNTAX UNSIGNED32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of outstanding I/O requests to the local component."
+ ::= { hastResourceEntry 23 }
+
+hastResourceSendQueue OBJECT-TYPE
+ SYNTAX UNSIGNED32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of outstanding I/O requests to send to the remote
+ component."
+ ::= { hastResourceEntry 24 }
+
+hastResourceRecvQueue OBJECT-TYPE
+ SYNTAX UNSIGNED32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of outstanding I/O requests waiting for response
+ from the remote component."
+ ::= { hastResourceEntry 25 }
+
+hastResourceDoneQueue OBJECT-TYPE
+ SYNTAX UNSIGNED32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of processed I/O requests to return to the kernel."
+ ::= { hastResourceEntry 26 }
+
+hastResourceIdleQueue OBJECT-TYPE
+ SYNTAX UNSIGNED32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of request objects in the free bucket."
+ ::= { hastResourceEntry 27 }
+
+END
diff --git a/usr.sbin/bsnmpd/modules/snmp_hast/Makefile b/usr.sbin/bsnmpd/modules/snmp_hast/Makefile
new file mode 100644
index 0000000..d0c3a48
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hast/Makefile
@@ -0,0 +1,42 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+.PATH: ${.CURDIR}/../../../../sbin/hastd
+
+MOD= hast
+SRCS= ebuf.c
+SRCS+= hast_compression.c hast_proto.c hast_snmp.c
+SRCS+= lzf.c
+SRCS+= nv.c
+SRCS+= parse.y pjdlog.c
+SRCS+= proto.c proto_common.c proto_uds.c
+SRCS+= token.l
+SRCS+= y.tab.h
+MAN= snmp_hast.3
+
+NO_WFORMAT=
+NO_WCAST_ALIGN=
+NO_WMISSING_VARIABLE_DECLARATIONS=
+CFLAGS+=-I${.CURDIR}/../../../../sbin/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
+CFLAGS+= -DSNMPTREE_TYPES
+
+LIBADD= util
+
+XSYM= begemotHast
+DEFS= ${MOD}_tree.def
+BMIBS= BEGEMOT-HAST-MIB.txt
+
+YFLAGS+=-v
+
+CLEANFILES=y.tab.c y.tab.h y.output
+
+.include <bsd.snmpmod.mk>
diff --git a/usr.sbin/bsnmpd/modules/snmp_hast/Makefile.depend b/usr.sbin/bsnmpd/modules/snmp_hast/Makefile.depend
new file mode 100644
index 0000000..140fffb
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hast/Makefile.depend
@@ -0,0 +1,37 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbsnmp/libbsnmp \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+ usr.bin/yacc.host \
+ usr.sbin/bsnmpd/modules \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+hast_snmp.So: hast_oid.h
+hast_snmp.So: hast_tree.h
+hast_snmp.po: hast_oid.h
+hast_snmp.po: hast_tree.h
+hast_tree.So: hast_tree.c
+hast_tree.So: hast_tree.h
+hast_tree.po: hast_tree.c
+hast_tree.po: hast_tree.h
+parse.So: parse.c
+parse.po: parse.c
+token.So: token.c
+token.So: y.tab.h
+token.po: token.c
+token.po: y.tab.h
+.endif
diff --git a/usr.sbin/bsnmpd/modules/snmp_hast/hast_snmp.c b/usr.sbin/bsnmpd/modules/snmp_hast/hast_snmp.c
new file mode 100644
index 0000000..210db9e
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hast/hast_snmp.c
@@ -0,0 +1,541 @@
+/*-
+ * Copyright (c) 2013 Mikolaj Golub <trociny@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 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/queue.h>
+
+#include <bsnmp/snmpmod.h>
+
+#include <string.h>
+
+#include "hast.h"
+#include "hast_oid.h"
+#include "hast_proto.h"
+#include "hast_tree.h"
+#include "nv.h"
+#include "pjdlog.h"
+#include "proto.h"
+
+#define UPDATE_INTERVAL 500 /* update interval in ticks */
+
+static struct lmodule *module;
+
+static const struct asn_oid oid_hast = OIDX_begemotHast;
+
+/* the Object Resource registration index */
+static u_int hast_index = 0;
+
+/*
+ * Structure that describes single resource.
+ */
+struct hast_snmp_resource {
+ TAILQ_ENTRY(hast_snmp_resource) link;
+ int32_t index;
+ char name[NAME_MAX];
+ int error;
+ int role;
+ char provname[NAME_MAX];
+ char localpath[PATH_MAX];
+ int32_t extentsize;
+ int32_t keepdirty;
+ char remoteaddr[HAST_ADDRSIZE];
+ char sourceaddr[HAST_ADDRSIZE];
+ int replication;
+ int status;
+ uint64_t dirty;
+ uint64_t reads;
+ uint64_t writes;
+ uint64_t deletes;
+ uint64_t flushes;
+ uint64_t activemap_updates;
+ uint64_t read_errors;
+ uint64_t write_errors;
+ uint64_t delete_errors;
+ uint64_t flush_errors;
+ pid_t workerpid;
+ uint32_t local_queue;
+ uint32_t send_queue;
+ uint32_t recv_queue;
+ uint32_t done_queue;
+ uint32_t idle_queue;
+};
+
+static TAILQ_HEAD(, hast_snmp_resource) resources =
+ TAILQ_HEAD_INITIALIZER(resources);
+
+/* Path to configuration file. */
+static u_char *cfgpath;
+/* Ticks of the last hast resources update. */
+static uint64_t last_resources_update;
+
+static void free_resources(void);
+static int hastctl(struct nv *nvin, struct nv **nvout);
+static int hast_fini(void);
+static int hast_init(struct lmodule *mod, int argc, char *argv[]);
+static void hast_start(void);
+static int set_role(const char *resource, int role);
+static int str2role(const char *str);
+static int str2replication(const char *str);
+static int str2status(const char *str);
+static int update_resources(void);
+
+const struct snmp_module config = {
+ .comment = "This module implements the BEGEMOT MIB for HAST.",
+ .init = hast_init,
+ .start = hast_start,
+ .fini = hast_fini,
+ .tree = hast_ctree,
+ .tree_size = hast_CTREE_SIZE,
+};
+
+static int
+hast_init(struct lmodule *mod, int argc __unused, char *argv[] __unused)
+{
+
+ module = mod;
+
+ pjdlog_init(PJDLOG_MODE_SYSLOG);
+ pjdlog_debug_set(0);
+
+ cfgpath = malloc(sizeof(HAST_CONFIG));
+ if (cfgpath == NULL) {
+ pjdlog_error("Unable to allocate %zu bytes for cfgpath",
+ sizeof(HAST_CONFIG));
+ return (-1);
+ }
+ strcpy(cfgpath, HAST_CONFIG);
+ return(0);
+}
+
+static void
+hast_start(void)
+{
+ hast_index = or_register(&oid_hast,
+ "The MIB module for BEGEMOT-HAST-MIB.", module);
+}
+
+static int
+hast_fini(void)
+{
+
+ or_unregister(hast_index);
+ free_resources();
+ free(cfgpath);
+ return (0);
+}
+
+static void
+free_resources(void)
+{
+ struct hast_snmp_resource *res;
+
+ while ((res = TAILQ_FIRST(&resources)) != NULL) {
+ TAILQ_REMOVE(&resources, res, link);
+ free(res);
+ }
+}
+
+static int
+str2role(const char *str)
+{
+
+ if (strcmp(str, "init") == 0)
+ return (HAST_ROLE_INIT);
+ if (strcmp(str, "primary") == 0)
+ return (HAST_ROLE_PRIMARY);
+ if (strcmp(str, "secondary") == 0)
+ return (HAST_ROLE_SECONDARY);
+ return (HAST_ROLE_UNDEF);
+}
+
+static int
+str2replication(const char *str)
+{
+
+ if (strcmp(str, "fullsync") == 0)
+ return (HAST_REPLICATION_FULLSYNC);
+ if (strcmp(str, "memsync") == 0)
+ return (HAST_REPLICATION_MEMSYNC);
+ if (strcmp(str, "async") == 0)
+ return (HAST_REPLICATION_ASYNC);
+ return (-1);
+}
+
+static int
+str2status(const char *str)
+{
+
+ if (strcmp(str, "complete") == 0)
+ return (0);
+ if (strcmp(str, "degraded") == 0)
+ return (1);
+ return (-1);
+}
+
+static int
+hastctl(struct nv *nvin, struct nv **nvout)
+{
+ struct hastd_config *cfg;
+ struct proto_conn *conn;
+ struct nv *nv;
+ int error;
+
+ cfg = yy_config_parse(cfgpath, true);
+ if (cfg == NULL)
+ return (-1);
+
+ /* Setup control connection... */
+ if (proto_client(NULL, cfg->hc_controladdr, &conn) == -1) {
+ pjdlog_error("Unable to setup control connection to %s",
+ cfg->hc_controladdr);
+ return (-1);
+ }
+ /* ...and connect to hastd. */
+ if (proto_connect(conn, HAST_TIMEOUT) == -1) {
+ pjdlog_error("Unable to connect to hastd via %s",
+ cfg->hc_controladdr);
+ proto_close(conn);
+ return (-1);
+ }
+ /* Send the command to the server... */
+ if (hast_proto_send(NULL, conn, nvin, NULL, 0) == -1) {
+ pjdlog_error("Unable to send command to hastd via %s",
+ cfg->hc_controladdr);
+ proto_close(conn);
+ return (-1);
+ }
+ /* ...and receive reply. */
+ if (hast_proto_recv_hdr(conn, &nv) == -1) {
+ pjdlog_error("cannot receive reply from hastd via %s",
+ cfg->hc_controladdr);
+ proto_close(conn);
+ return (-1);
+ }
+ proto_close(conn);
+ error = nv_get_int16(nv, "error");
+ if (error != 0) {
+ pjdlog_error("Error %d received from hastd.", error);
+ nv_free(nv);
+ return (-1);
+ }
+ nv_set_error(nv, 0);
+ *nvout = nv;
+ return (0);
+}
+
+static int
+set_role(const char *resource, int role)
+{
+ struct nv *nvin, *nvout;
+ int error;
+
+ nvin = nv_alloc();
+ nv_add_string(nvin, resource, "resource%d", 0);
+ nv_add_uint8(nvin, HASTCTL_CMD_SETROLE, "cmd");
+ nv_add_uint8(nvin, role, "role");
+ error = hastctl(nvin, &nvout);
+ nv_free(nvin);
+ if (error != 0)
+ return (-1);
+ nv_free(nvout);
+ return (SNMP_ERR_NOERROR);
+}
+
+static int
+update_resources(void)
+{
+ struct hast_snmp_resource *res;
+ struct nv *nvin, *nvout;
+ static uint64_t now;
+ unsigned int i;
+ const char *str;
+ int error;
+
+ now = get_ticks();
+ if (now - last_resources_update < UPDATE_INTERVAL)
+ return (0);
+
+ last_resources_update = now;
+
+ free_resources();
+
+ nvin = nv_alloc();
+ nv_add_uint8(nvin, HASTCTL_CMD_STATUS, "cmd");
+ nv_add_string(nvin, "all", "resource%d", 0);
+ error = hastctl(nvin, &nvout);
+ nv_free(nvin);
+ if (error != 0)
+ return (-1);
+
+ for (i = 0; ; i++) {
+ str = nv_get_string(nvout, "resource%u", i);
+ if (str == NULL)
+ break;
+ res = calloc(1, sizeof(*res));
+ if (res == NULL) {
+ pjdlog_error("Unable to allocate %zu bytes for "
+ "resource", sizeof(*res));
+ return (-1);
+ }
+ res->index = i + 1;
+ strncpy(res->name, str, sizeof(res->name) - 1);
+ error = nv_get_int16(nvout, "error%u", i);
+ if (error != 0)
+ continue;
+ str = nv_get_string(nvout, "role%u", i);
+ res->role = str != NULL ? str2role(str) : HAST_ROLE_UNDEF;
+ str = nv_get_string(nvout, "provname%u", i);
+ if (str != NULL)
+ strncpy(res->provname, str, sizeof(res->provname) - 1);
+ str = nv_get_string(nvout, "localpath%u", i);
+ if (str != NULL) {
+ strncpy(res->localpath, str,
+ sizeof(res->localpath) - 1);
+ }
+ res->extentsize = nv_get_uint32(nvout, "extentsize%u", i);
+ res->keepdirty = nv_get_uint32(nvout, "keepdirty%u", i);
+ str = nv_get_string(nvout, "remoteaddr%u", i);
+ if (str != NULL) {
+ strncpy(res->remoteaddr, str,
+ sizeof(res->remoteaddr) - 1);
+ }
+ str = nv_get_string(nvout, "sourceaddr%u", i);
+ if (str != NULL) {
+ strncpy(res->sourceaddr, str,
+ sizeof(res->sourceaddr) - 1);
+ }
+ str = nv_get_string(nvout, "replication%u", i);
+ res->replication = str != NULL ? str2replication(str) : -1;
+ str = nv_get_string(nvout, "status%u", i);
+ res->status = str != NULL ? str2status(str) : -1;
+ res->dirty = nv_get_uint64(nvout, "dirty%u", i);
+ res->reads = nv_get_uint64(nvout, "stat_read%u", i);
+ res->writes = nv_get_uint64(nvout, "stat_write%u", i);
+ res->deletes = nv_get_uint64(nvout, "stat_delete%u", i);
+ res->flushes = nv_get_uint64(nvout, "stat_flush%u", i);
+ res->activemap_updates =
+ nv_get_uint64(nvout, "stat_activemap_update%u", i);
+ res->read_errors =
+ nv_get_uint64(nvout, "stat_read_error%u", i);
+ res->write_errors =
+ nv_get_uint64(nvout, "stat_write_error%u", i);
+ res->delete_errors =
+ nv_get_uint64(nvout, "stat_delete_error%u", i);
+ res->flush_errors =
+ nv_get_uint64(nvout, "stat_flush_error%u", i);
+ res->workerpid = nv_get_int32(nvout, "workerpid%u", i);
+ res->local_queue =
+ nv_get_uint64(nvout, "local_queue_size%u", i);
+ res->send_queue =
+ nv_get_uint64(nvout, "send_queue_size%u", i);
+ res->recv_queue =
+ nv_get_uint64(nvout, "recv_queue_size%u", i);
+ res->done_queue =
+ nv_get_uint64(nvout, "done_queue_size%u", i);
+ res->idle_queue =
+ nv_get_uint64(nvout, "idle_queue_size%u", i);
+ TAILQ_INSERT_TAIL(&resources, res, link);
+ }
+ nv_free(nvout);
+ return (0);
+}
+
+int
+op_hastConfig(struct snmp_context *context, struct snmp_value *value,
+ u_int sub, u_int iidx __unused, enum snmp_op op)
+{
+ asn_subid_t which;
+
+ which = value->var.subs[sub - 1];
+
+ switch (op) {
+ case SNMP_OP_GET:
+ switch (which) {
+ case LEAF_hastConfigFile:
+ return (string_get(value, cfgpath, -1));
+ default:
+ return (SNMP_ERR_RES_UNAVAIL);
+ }
+ case SNMP_OP_SET:
+ switch (which) {
+ case LEAF_hastConfigFile:
+ return (string_save(value, context, -1,
+ (u_char **)&cfgpath));
+ default:
+ return (SNMP_ERR_RES_UNAVAIL);
+ }
+ case SNMP_OP_GETNEXT:
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ return (SNMP_ERR_NOERROR);
+ default:
+ return (SNMP_ERR_RES_UNAVAIL);
+ }
+}
+
+int
+op_hastResourceTable(struct snmp_context *context __unused,
+ struct snmp_value *value, u_int sub, u_int iidx __unused, enum snmp_op op)
+{
+ struct hast_snmp_resource *res;
+ asn_subid_t which;
+ int ret;
+
+ if (update_resources() == -1)
+ return (SNMP_ERR_RES_UNAVAIL);
+
+ which = value->var.subs[sub - 1];
+
+ switch (op) {
+ case SNMP_OP_GETNEXT:
+ res = NEXT_OBJECT_INT(&resources, &value->var, sub);
+ if (res == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ value->var.len = sub + 1;
+ value->var.subs[sub] = res->index;
+ break;
+ case SNMP_OP_GET:
+ if (value->var.len - sub != 1)
+ return (SNMP_ERR_NOSUCHNAME);
+ res = FIND_OBJECT_INT(&resources, &value->var, sub);
+ if (res == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+ case SNMP_OP_SET:
+ res = FIND_OBJECT_INT(&resources, &value->var, sub);
+ if (res == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ switch (which) {
+ case LEAF_hastResourceRole:
+ ret = set_role(res->name, value->v.integer);
+ /* force update on next run */
+ last_resources_update = 0;
+ break;
+ default:
+ ret = SNMP_ERR_NOT_WRITEABLE;
+ break;
+ }
+ return ret;
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ return (SNMP_ERR_NOERROR);
+ default:
+ return (SNMP_ERR_RES_UNAVAIL);
+ }
+
+ ret = SNMP_ERR_NOERROR;
+
+ switch (which) {
+ case LEAF_hastResourceIndex:
+ value->v.integer = res->index;
+ break;
+ case LEAF_hastResourceName:
+ ret = string_get(value, res->name, -1);
+ break;
+ case LEAF_hastResourceRole:
+ value->v.integer = res->role;
+ break;
+ case LEAF_hastResourceProvName:
+ ret = string_get(value, res->provname, -1);
+ break;
+ case LEAF_hastResourceLocalPath:
+ ret = string_get(value, res->localpath, -1);
+ break;
+ case LEAF_hastResourceExtentSize:
+ value->v.integer = res->extentsize;
+ break;
+ case LEAF_hastResourceKeepDirty:
+ value->v.integer = res->keepdirty;
+ break;
+ case LEAF_hastResourceRemoteAddr:
+ ret = string_get(value, res->remoteaddr, -1);
+ break;
+ case LEAF_hastResourceSourceAddr:
+ ret = string_get(value, res->sourceaddr, -1);
+ break;
+ case LEAF_hastResourceReplication:
+ value->v.integer = res->replication;
+ break;
+ case LEAF_hastResourceStatus:
+ value->v.integer = res->status;
+ break;
+ case LEAF_hastResourceDirty:
+ value->v.counter64 = res->dirty;
+ break;
+ case LEAF_hastResourceReads:
+ value->v.counter64 = res->reads;
+ break;
+ case LEAF_hastResourceWrites:
+ value->v.counter64 = res->writes;
+ break;
+ case LEAF_hastResourceDeletes:
+ value->v.counter64 = res->deletes;
+ break;
+ case LEAF_hastResourceFlushes:
+ value->v.counter64 = res->flushes;
+ break;
+ case LEAF_hastResourceActivemapUpdates:
+ value->v.counter64 = res->activemap_updates;
+ break;
+ case LEAF_hastResourceReadErrors:
+ value->v.counter64 = res->read_errors;
+ break;
+ case LEAF_hastResourceWriteErrors:
+ value->v.counter64 = res->write_errors;
+ break;
+ case LEAF_hastResourceDeleteErrors:
+ value->v.counter64 = res->delete_errors;
+ break;
+ case LEAF_hastResourceFlushErrors:
+ value->v.counter64 = res->flush_errors;
+ break;
+ case LEAF_hastResourceWorkerPid:
+ value->v.integer = res->workerpid;
+ break;
+ case LEAF_hastResourceLocalQueue:
+ value->v.uint32 = res->local_queue;
+ break;
+ case LEAF_hastResourceSendQueue:
+ value->v.uint32 = res->send_queue;
+ break;
+ case LEAF_hastResourceRecvQueue:
+ value->v.uint32 = res->recv_queue;
+ break;
+ case LEAF_hastResourceDoneQueue:
+ value->v.uint32 = res->done_queue;
+ break;
+ case LEAF_hastResourceIdleQueue:
+ value->v.uint32 = res->idle_queue;
+ break;
+ default:
+ ret = SNMP_ERR_RES_UNAVAIL;
+ break;
+ }
+ return (ret);
+}
diff --git a/usr.sbin/bsnmpd/modules/snmp_hast/hast_tree.def b/usr.sbin/bsnmpd/modules/snmp_hast/hast_tree.def
new file mode 100644
index 0000000..980de92
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hast/hast_tree.def
@@ -0,0 +1,76 @@
+#-
+# Copyright (c) 2013 Mikolaj Golub <trociny@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 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$
+#
+
+(1 internet
+ (4 private
+ (1 enterprises
+ (12325 fokus
+ (1 begemot
+ (220 begemotHast
+ (1 begemotHastObjects
+ (1 hastConfig
+ (1 hastConfigFile OCTETSTRING op_hastConfig GET)
+ )
+ (2 hastResourceTable
+ (1 hastResourceEntry : OCTETSTRING op_hastResourceTable
+ (1 hastResourceIndex INTEGER32 GET)
+ (2 hastResourceName OCTETSTRING GET)
+ (3 hastResourceRole INTEGER GET SET)
+ (4 hastResourceProvName OCTETSTRING GET)
+ (5 hastResourceLocalPath OCTETSTRING GET)
+ (6 hastResourceExtentSize INTEGER32 GET)
+ (7 hastResourceKeepDirty INTEGER32 GET)
+ (8 hastResourceRemoteAddr OCTETSTRING GET)
+ (9 hastResourceSourceAddr OCTETSTRING GET)
+ (10 hastResourceReplication INTEGER GET)
+ (11 hastResourceStatus INTEGER GET)
+ (12 hastResourceDirty COUNTER64 GET)
+ (13 hastResourceReads COUNTER64 GET)
+ (14 hastResourceWrites COUNTER64 GET)
+ (15 hastResourceDeletes COUNTER64 GET)
+ (16 hastResourceFlushes COUNTER64 GET)
+ (17 hastResourceActivemapUpdates COUNTER64 GET)
+ (18 hastResourceReadErrors COUNTER64 GET)
+ (19 hastResourceWriteErrors COUNTER64 GET)
+ (20 hastResourceDeleteErrors COUNTER64 GET)
+ (21 hastResourceFlushErrors COUNTER64 GET)
+ (22 hastResourceWorkerPid INTEGER GET)
+ (23 hastResourceLocalQueue UNSIGNED32 GET)
+ (24 hastResourceSendQueue UNSIGNED32 GET)
+ (25 hastResourceRecvQueue UNSIGNED32 GET)
+ (26 hastResourceDoneQueue UNSIGNED32 GET)
+ (27 hastResourceIdleQueue UNSIGNED32 GET)
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+)
diff --git a/usr.sbin/bsnmpd/modules/snmp_hast/snmp_hast.3 b/usr.sbin/bsnmpd/modules/snmp_hast/snmp_hast.3
new file mode 100644
index 0000000..44a15905
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hast/snmp_hast.3
@@ -0,0 +1,70 @@
+.\"-
+.\" Copyright (c) 2013 Mikolaj Golub <trociny@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 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 May 31, 2014
+.Dt SNMP_HAST 3
+.Os
+.Sh NAME
+.Nm snmp_hast
+.Nd "HAST module for"
+.Xr bsnmpd 1
+.Sh LIBRARY
+.Pq begemotSnmpdModulePath."hast" = "/usr/lib/snmp_hast.so"
+.Sh DESCRIPTION
+The
+.Nm snmp_hast
+module implements a private BEGEMOT-HAST-MIB, which allows
+management of HAST resources.
+.Pp
+The module uses the
+.Xr hastd 8
+control socket to communicate with the daemon.
+The
+.Va hastConfigFile
+variable can be used to specify the location of the
+.Xr hast.conf 5
+file to find the address of the control connection.
+.Sh FILES
+.Bl -tag -width "XXXXXXXXX"
+.It Pa /usr/share/snmp/defs/hast_tree.def
+The description of the MIB tree implemented by
+.Nm .
+.It Pa /usr/share/snmp/mibs/BEGEMOT-HAST-MIB.txt
+The private BEGEMOT-HAST-MIB that is implemented by this module.
+.It Pa /etc/hast.conf
+The default
+.Xr hastd 8
+configuration file.
+.El
+.Sh SEE ALSO
+.Xr bsnmpd 1 ,
+.Xr gensnmptree 1 ,
+.Xr snmpmod 3 ,
+.Xr hastctl 8 ,
+.Xr hastd 8
+.Sh AUTHORS
+.An Mikolaj Golub Aq Mt trociny@FreeBSD.org
diff --git a/usr.sbin/bsnmpd/modules/snmp_hostres/BEGEMOT-HOSTRES-MIB.txt b/usr.sbin/bsnmpd/modules/snmp_hostres/BEGEMOT-HOSTRES-MIB.txt
new file mode 100644
index 0000000..ee8d284
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hostres/BEGEMOT-HOSTRES-MIB.txt
@@ -0,0 +1,125 @@
+--
+-- Copyright (c) 2005-2006
+-- Hartmut Brandt
+-- All rights reserved.
+--
+-- Author: Harti 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 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$
+--
+-- Additional stuff for the HOST-RESOURCES MIB.
+--
+BEGEMOT-HOSTRES-MIB DEFINITIONS ::= BEGIN
+
+IMPORTS
+ MODULE-IDENTITY, OBJECT-TYPE, TimeTicks
+ FROM SNMPv2-SMI
+ begemot
+ FROM BEGEMOT-MIB;
+
+begemotHostres MODULE-IDENTITY
+ LAST-UPDATED "200601030000Z"
+ ORGANIZATION "German Aerospace Center"
+ CONTACT-INFO
+ " Hartmut Brandt
+
+ Postal: German Aerospace Center
+ Oberpfaffenhofen
+ 82234 Wessling
+ Germany
+
+ Fax: +49 8153 28 2843
+
+ E-mail: harti@freebsd.org"
+ DESCRIPTION
+ "The MIB for additional HOST-RESOURCES data."
+ ::= { begemot 202 }
+
+begemotHostresObjects OBJECT IDENTIFIER ::= { begemotHostres 1 }
+
+begemotHrStorageUpdate OBJECT-TYPE
+ SYNTAX TimeTicks
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The maximum number of ticks the storage table is cached."
+ DEFVAL { 700 }
+ ::= { begemotHostresObjects 1 }
+
+begemotHrFSUpdate OBJECT-TYPE
+ SYNTAX TimeTicks
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The maximum number of ticks the FS table is cached."
+ DEFVAL { 700 }
+ ::= { begemotHostresObjects 2 }
+
+begemotHrDiskStorageUpdate OBJECT-TYPE
+ SYNTAX TimeTicks
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The maximum number of ticks the disk storage table is cached."
+ DEFVAL { 300 }
+ ::= { begemotHostresObjects 3 }
+
+begemotHrNetworkUpdate OBJECT-TYPE
+ SYNTAX TimeTicks
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The maximum number of ticks the network table is cached."
+ DEFVAL { 700 }
+ ::= { begemotHostresObjects 4 }
+
+begemotHrSWInstalledUpdate OBJECT-TYPE
+ SYNTAX TimeTicks
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The maximum number of ticks the hrSWInstalledTable is cached."
+ DEFVAL { 1200 }
+ ::= { begemotHostresObjects 5 }
+
+begemotHrSWRunUpdate OBJECT-TYPE
+ SYNTAX TimeTicks
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The maximum number of ticks the hrSWRunTable and
+ hrSWRunPerfTable are cached."
+ DEFVAL { 300 }
+ ::= { begemotHostresObjects 6 }
+
+begemotHrPkgDir OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The path to the package DB directory."
+ DEFVAL { "/var/db/pkg" }
+ ::= { begemotHostresObjects 7 }
+
+END
diff --git a/usr.sbin/bsnmpd/modules/snmp_hostres/Makefile b/usr.sbin/bsnmpd/modules/snmp_hostres/Makefile
new file mode 100644
index 0000000..57f3eab
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hostres/Makefile
@@ -0,0 +1,81 @@
+#
+# Copyright (c) 2005-2006 The FreeBSD Project
+# All rights reserved.
+# Author: Victor Cruceru <soc-victor@freebsd.org>
+#
+# Redistribution of this software and documentation and use in source and
+# binary forms, with or without modification, are permitted provided that
+# the following conditions are met:
+#
+# 1. Redistributions of source code or documentation must retain the above
+# copyright notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+
+LPRSRC= ${.CURDIR}/../../../lpr/common_source
+.PATH: ${LPRSRC}
+
+MOD= hostres
+SRCS= hostres_begemot.c \
+ hostres_device_tbl.c \
+ hostres_diskstorage_tbl.c \
+ hostres_fs_tbl.c \
+ hostres_network_tbl.c \
+ hostres_partition_tbl.c \
+ hostres_printer_tbl.c \
+ hostres_processor_tbl.c \
+ hostres_scalars.c \
+ hostres_snmp.c \
+ hostres_storage_tbl.c \
+ hostres_swinstalled_tbl.c \
+ hostres_swrun_tbl.c \
+ printcap.c
+
+#Not having NDEBUG defined will enable assertions and a lot of output on stderr
+CFLAGS+= -DNDEBUG -I${LPRSRC}
+XSYM= host hrStorageOther hrStorageRam hrStorageVirtualMemory \
+ hrStorageFixedDisk hrStorageRemovableDisk hrStorageFloppyDisk \
+ hrStorageCompactDisc hrStorageRamDisk hrStorageFlashMemory \
+ hrStorageNetworkDisk hrDeviceOther hrDeviceUnknown \
+ hrDeviceProcessor hrDeviceNetwork hrDevicePrinter \
+ hrDeviceDiskStorage hrDeviceVideo hrDeviceAudio \
+ hrDeviceCoprocessor hrDeviceKeyboard hrDeviceModem \
+ hrDeviceParallelPort hrDevicePointing \
+ hrDeviceSerialPort hrDeviceTape hrDeviceClock \
+ hrDeviceVolatileMemory hrDeviceNonVolatileMemory \
+ hrFSOther hrFSUnknown hrFSBerkeleyFFS hrFSSys5FS hrFSFat\
+ hrFSHPFS hrFSHFS hrFSMFS hrFSNTFS hrFSVNode hrFSJournaled \
+ hrFSiso9660 hrFSRockRidge hrFSNFS hrFSNetware hrFSAFS hrFSDFS \
+ hrFSAppleshare hrFSRFS hrFSDGCFS hrFSBFS hrFSFAT32 hrFSLinuxExt2
+
+MAN= snmp_hostres.3
+
+DEFS= ${MOD}_tree.def
+BMIBS= BEGEMOT-HOSTRES-MIB.txt
+
+LIBADD= kvm devinfo m geom memstat
+
+.include <bsd.snmpmod.mk>
+
+printcap.So: printcap.c
+ ${CC} ${PICFLAG} -DPIC ${CFLAGS:C/^-W.*//} -c ${.IMPSRC} -o ${.TARGET}
+
+smilint:
+ env SMIPATH=.:/usr/share/snmp/mibs:/usr/local/share/snmp/mibs \
+ smilint -c /dev/null -l6 -i group-membership BEGEMOT-HOSTRES-MIB
diff --git a/usr.sbin/bsnmpd/modules/snmp_hostres/Makefile.depend b/usr.sbin/bsnmpd/modules/snmp_hostres/Makefile.depend
new file mode 100644
index 0000000..a492080
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hostres/Makefile.depend
@@ -0,0 +1,82 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbsnmp/libbsnmp \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libdevinfo \
+ lib/libgeom \
+ lib/libkvm \
+ lib/libmemstat \
+ lib/msun \
+ usr.sbin/bsnmpd/modules \
+ usr.sbin/bsnmpd/modules/snmp_mibII \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+hostres_begemot.So: hostres_oid.h
+hostres_begemot.So: hostres_tree.h
+hostres_begemot.po: hostres_oid.h
+hostres_begemot.po: hostres_tree.h
+hostres_device_tbl.So: hostres_oid.h
+hostres_device_tbl.So: hostres_tree.h
+hostres_device_tbl.po: hostres_oid.h
+hostres_device_tbl.po: hostres_tree.h
+hostres_diskstorage_tbl.So: hostres_oid.h
+hostres_diskstorage_tbl.So: hostres_tree.h
+hostres_diskstorage_tbl.po: hostres_oid.h
+hostres_diskstorage_tbl.po: hostres_tree.h
+hostres_fs_tbl.So: hostres_oid.h
+hostres_fs_tbl.So: hostres_tree.h
+hostres_fs_tbl.po: hostres_oid.h
+hostres_fs_tbl.po: hostres_tree.h
+hostres_network_tbl.So: hostres_oid.h
+hostres_network_tbl.So: hostres_tree.h
+hostres_network_tbl.po: hostres_oid.h
+hostres_network_tbl.po: hostres_tree.h
+hostres_partition_tbl.So: hostres_oid.h
+hostres_partition_tbl.So: hostres_tree.h
+hostres_partition_tbl.po: hostres_oid.h
+hostres_partition_tbl.po: hostres_tree.h
+hostres_printer_tbl.So: hostres_oid.h
+hostres_printer_tbl.So: hostres_tree.h
+hostres_printer_tbl.po: hostres_oid.h
+hostres_printer_tbl.po: hostres_tree.h
+hostres_processor_tbl.So: hostres_oid.h
+hostres_processor_tbl.So: hostres_tree.h
+hostres_processor_tbl.po: hostres_oid.h
+hostres_processor_tbl.po: hostres_tree.h
+hostres_scalars.So: hostres_oid.h
+hostres_scalars.So: hostres_tree.h
+hostres_scalars.po: hostres_oid.h
+hostres_scalars.po: hostres_tree.h
+hostres_snmp.So: hostres_oid.h
+hostres_snmp.So: hostres_tree.h
+hostres_snmp.po: hostres_oid.h
+hostres_snmp.po: hostres_tree.h
+hostres_storage_tbl.So: hostres_oid.h
+hostres_storage_tbl.So: hostres_tree.h
+hostres_storage_tbl.po: hostres_oid.h
+hostres_storage_tbl.po: hostres_tree.h
+hostres_swinstalled_tbl.So: hostres_oid.h
+hostres_swinstalled_tbl.So: hostres_tree.h
+hostres_swinstalled_tbl.po: hostres_oid.h
+hostres_swinstalled_tbl.po: hostres_tree.h
+hostres_swrun_tbl.So: hostres_oid.h
+hostres_swrun_tbl.So: hostres_tree.h
+hostres_swrun_tbl.po: hostres_oid.h
+hostres_swrun_tbl.po: hostres_tree.h
+hostres_tree.So: hostres_tree.c
+hostres_tree.So: hostres_tree.h
+hostres_tree.po: hostres_tree.c
+hostres_tree.po: hostres_tree.h
+.endif
diff --git a/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_begemot.c b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_begemot.c
new file mode 100644
index 0000000..f1cc5e3
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_begemot.c
@@ -0,0 +1,171 @@
+/*-
+ * Copyright (c) 2005-2006.
+ * Hartmut Brandt.
+ * All rights reserved.
+ *
+ * Author: Hartmut Brandt <harti@freebsd.org>
+ *
+ * Redistribution of this software and documentation and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ *
+ * 1. Redistributions of source code or documentation must retain the above
+ * copyright notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <stdlib.h>
+
+#include "hostres_snmp.h"
+#include "hostres_oid.h"
+#include "hostres_tree.h"
+
+int
+op_begemot(struct snmp_context *ctx, struct snmp_value *value,
+ u_int sub, u_int iidx __unused, enum snmp_op op)
+{
+
+ switch (op) {
+
+ case SNMP_OP_GET:
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_begemotHrStorageUpdate:
+ value->v.uint32 = storage_tbl_refresh;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotHrFSUpdate:
+ value->v.uint32 = fs_tbl_refresh;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotHrDiskStorageUpdate:
+ value->v.uint32 = disk_storage_tbl_refresh;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotHrNetworkUpdate:
+ value->v.uint32 = network_tbl_refresh;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotHrSWInstalledUpdate:
+ value->v.uint32 = swins_tbl_refresh;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotHrSWRunUpdate:
+ value->v.uint32 = swrun_tbl_refresh;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotHrPkgDir:
+ return (string_get(value, pkg_dir, -1));
+ }
+ abort();
+
+ case SNMP_OP_GETNEXT:
+ abort();
+
+ case SNMP_OP_SET:
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_begemotHrStorageUpdate:
+ ctx->scratch->int1 = storage_tbl_refresh;
+ storage_tbl_refresh = value->v.uint32;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotHrFSUpdate:
+ ctx->scratch->int1 = fs_tbl_refresh;
+ fs_tbl_refresh = value->v.uint32;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotHrDiskStorageUpdate:
+ ctx->scratch->int1 = disk_storage_tbl_refresh;
+ disk_storage_tbl_refresh = value->v.uint32;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotHrNetworkUpdate:
+ ctx->scratch->int1 = network_tbl_refresh;
+ network_tbl_refresh = value->v.uint32;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotHrSWInstalledUpdate:
+ ctx->scratch->int1 = swins_tbl_refresh;
+ swins_tbl_refresh = value->v.uint32;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotHrSWRunUpdate:
+ ctx->scratch->int1 = swrun_tbl_refresh;
+ swrun_tbl_refresh = value->v.uint32;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotHrPkgDir:
+ return (string_save(value, ctx, -1, &pkg_dir));
+ }
+ abort();
+
+ case SNMP_OP_COMMIT:
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_begemotHrStorageUpdate:
+ case LEAF_begemotHrFSUpdate:
+ case LEAF_begemotHrDiskStorageUpdate:
+ case LEAF_begemotHrNetworkUpdate:
+ case LEAF_begemotHrSWInstalledUpdate:
+ case LEAF_begemotHrSWRunUpdate:
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotHrPkgDir:
+ string_commit(ctx);
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+
+ case SNMP_OP_ROLLBACK:
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_begemotHrStorageUpdate:
+ storage_tbl_refresh = ctx->scratch->int1;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotHrFSUpdate:
+ fs_tbl_refresh = ctx->scratch->int1;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotHrDiskStorageUpdate:
+ disk_storage_tbl_refresh = ctx->scratch->int1;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotHrNetworkUpdate:
+ network_tbl_refresh = ctx->scratch->int1;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotHrSWInstalledUpdate:
+ swins_tbl_refresh = ctx->scratch->int1;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotHrSWRunUpdate:
+ swrun_tbl_refresh = ctx->scratch->int1;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotHrPkgDir:
+ string_rollback(ctx, &pkg_dir);
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+ }
+
+ abort();
+}
diff --git a/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_device_tbl.c b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_device_tbl.c
new file mode 100644
index 0000000..1741502
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_device_tbl.c
@@ -0,0 +1,684 @@
+ /*-
+ * Copyright (c) 2005-2006 The FreeBSD Project
+ * All rights reserved.
+ *
+ * Author: Victor Cruceru <soc-victor@freebsd.org>
+ *
+ * Redistribution of this software and documentation and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ *
+ * 1. Redistributions of source code or documentation must retain the above
+ * copyright notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+/*
+ * Host Resources MIB: hrDeviceTable implementation for SNMPd.
+ */
+
+#include <sys/un.h>
+#include <sys/limits.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <sysexits.h>
+
+#include "hostres_snmp.h"
+#include "hostres_oid.h"
+#include "hostres_tree.h"
+
+#define FREE_DEV_STRUCT(entry_p) do { \
+ free(entry_p->name); \
+ free(entry_p->location); \
+ free(entry_p->descr); \
+ free(entry_p); \
+} while (0)
+
+/*
+ * Status of a device
+ */
+enum DeviceStatus {
+ DS_UNKNOWN = 1,
+ DS_RUNNING = 2,
+ DS_WARNING = 3,
+ DS_TESTING = 4,
+ DS_DOWN = 5
+};
+
+TAILQ_HEAD(device_tbl, device_entry);
+
+/* the head of the list with hrDeviceTable's entries */
+static struct device_tbl device_tbl = TAILQ_HEAD_INITIALIZER(device_tbl);
+
+/* Table used for consistent device table indexing. */
+struct device_map device_map = STAILQ_HEAD_INITIALIZER(device_map);
+
+/* next int available for indexing the hrDeviceTable */
+static uint32_t next_device_index = 1;
+
+/* last (agent) tick when hrDeviceTable was updated */
+static uint64_t device_tick = 0;
+
+/* maximum number of ticks between updates of device table */
+uint32_t device_tbl_refresh = 10 * 100;
+
+/* socket for /var/run/devd.pipe */
+static int devd_sock = -1;
+
+/* used to wait notifications from /var/run/devd.pipe */
+static void *devd_fd;
+
+/* some constants */
+static const struct asn_oid OIDX_hrDeviceProcessor_c = OIDX_hrDeviceProcessor;
+static const struct asn_oid OIDX_hrDeviceOther_c = OIDX_hrDeviceOther;
+
+/**
+ * Create a new entry out of thin air.
+ */
+struct device_entry *
+device_entry_create(const char *name, const char *location, const char *descr)
+{
+ struct device_entry *entry = NULL;
+ struct device_map_entry *map = NULL;
+ size_t name_len;
+ size_t location_len;
+
+ assert((name[0] != 0) || (location[0] != 0));
+
+ if (name[0] == 0 && location[0] == 0)
+ return (NULL);
+
+ STAILQ_FOREACH(map, &device_map, link) {
+ assert(map->name_key != NULL);
+ assert(map->location_key != NULL);
+
+ if (strcmp(map->name_key, name) == 0 &&
+ strcmp(map->location_key, location) == 0) {
+ break;
+ }
+ }
+
+ if (map == NULL) {
+ /* new object - get a new index */
+ if (next_device_index > INT_MAX) {
+ syslog(LOG_ERR,
+ "%s: hrDeviceTable index wrap", __func__);
+ /* There isn't much we can do here.
+ * If the next_swins_index is consumed
+ * then we can't add entries to this table
+ * So it is better to exit - if the table is sparsed
+ * at the next agent run we can fill it fully.
+ */
+ errx(EX_SOFTWARE, "hrDeviceTable index wrap");
+ /* not reachable */
+ }
+
+ if ((map = malloc(sizeof(*map))) == NULL) {
+ syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );
+ return (NULL);
+ }
+
+ map->entry_p = NULL;
+
+ name_len = strlen(name) + 1;
+ if (name_len > DEV_NAME_MLEN)
+ name_len = DEV_NAME_MLEN;
+
+ if ((map->name_key = malloc(name_len)) == NULL) {
+ syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );
+ free(map);
+ return (NULL);
+ }
+
+ location_len = strlen(location) + 1;
+ if (location_len > DEV_LOC_MLEN)
+ location_len = DEV_LOC_MLEN;
+
+ if ((map->location_key = malloc(location_len )) == NULL) {
+ syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );
+ free(map->name_key);
+ free(map);
+ return (NULL);
+ }
+
+ map->hrIndex = next_device_index++;
+
+ strlcpy(map->name_key, name, name_len);
+ strlcpy(map->location_key, location, location_len);
+
+ STAILQ_INSERT_TAIL(&device_map, map, link);
+ HRDBG("%s at %s added into hrDeviceMap at index=%d",
+ name, location, map->hrIndex);
+ } else {
+ HRDBG("%s at %s exists in hrDeviceMap index=%d",
+ name, location, map->hrIndex);
+ }
+
+ if ((entry = malloc(sizeof(*entry))) == NULL) {
+ syslog(LOG_WARNING, "hrDeviceTable: %s: %m", __func__);
+ return (NULL);
+ }
+ memset(entry, 0, sizeof(*entry));
+
+ entry->index = map->hrIndex;
+ map->entry_p = entry;
+
+ if ((entry->name = strdup(map->name_key)) == NULL) {
+ syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );
+ free(entry);
+ return (NULL);
+ }
+
+ if ((entry->location = strdup(map->location_key)) == NULL) {
+ syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );
+ free(entry->name);
+ free(entry);
+ return (NULL);
+ }
+
+ /*
+ * From here till the end of this function we reuse name_len
+ * for a different purpose - for device_entry::descr
+ */
+ if (name[0] != '\0')
+ name_len = strlen(name) + strlen(descr) +
+ strlen(": ") + 1;
+ else
+ name_len = strlen(location) + strlen(descr) +
+ strlen("unknown at : ") + 1;
+
+ if (name_len > DEV_DESCR_MLEN)
+ name_len = DEV_DESCR_MLEN;
+
+ if ((entry->descr = malloc(name_len )) == NULL) {
+ syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );
+ free(entry->name);
+ free(entry->location);
+ free(entry);
+ return (NULL);
+ }
+
+ memset(&entry->descr[0], '\0', name_len);
+
+ if (name[0] != '\0')
+ snprintf(entry->descr, name_len,
+ "%s: %s", name, descr);
+ else
+ snprintf(entry->descr, name_len,
+ "unknown at %s: %s", location, descr);
+
+ entry->id = &oid_zeroDotZero; /* unknown id - FIXME */
+ entry->status = (u_int)DS_UNKNOWN;
+ entry->errors = 0;
+ entry->type = &OIDX_hrDeviceOther_c;
+
+ INSERT_OBJECT_INT(entry, &device_tbl);
+
+ return (entry);
+}
+
+/**
+ * Create a new entry into the device table.
+ */
+static struct device_entry *
+device_entry_create_devinfo(const struct devinfo_dev *dev_p)
+{
+
+ assert(dev_p->dd_name != NULL);
+ assert(dev_p->dd_location != NULL);
+
+ return (device_entry_create(dev_p->dd_name, dev_p->dd_location,
+ dev_p->dd_desc));
+}
+
+/**
+ * Delete an entry from the device table.
+ */
+void
+device_entry_delete(struct device_entry *entry)
+{
+ struct device_map_entry *map;
+
+ assert(entry != NULL);
+
+ TAILQ_REMOVE(&device_tbl, entry, link);
+
+ STAILQ_FOREACH(map, &device_map, link)
+ if (map->entry_p == entry) {
+ map->entry_p = NULL;
+ break;
+ }
+
+ FREE_DEV_STRUCT(entry);
+}
+
+/**
+ * Find an entry given its name and location
+ */
+static struct device_entry *
+device_find_by_dev(const struct devinfo_dev *dev_p)
+{
+ struct device_map_entry *map;
+
+ assert(dev_p != NULL);
+
+ STAILQ_FOREACH(map, &device_map, link)
+ if (strcmp(map->name_key, dev_p->dd_name) == 0 &&
+ strcmp(map->location_key, dev_p->dd_location) == 0)
+ return (map->entry_p);
+ return (NULL);
+}
+
+/**
+ * Find an entry given its index.
+ */
+struct device_entry *
+device_find_by_index(int32_t idx)
+{
+ struct device_entry *entry;
+
+ TAILQ_FOREACH(entry, &device_tbl, link)
+ if (entry->index == idx)
+ return (entry);
+ return (NULL);
+}
+
+/**
+ * Find an device entry given its name.
+ */
+struct device_entry *
+device_find_by_name(const char *dev_name)
+{
+ struct device_map_entry *map;
+
+ assert(dev_name != NULL);
+
+ STAILQ_FOREACH(map, &device_map, link)
+ if (strcmp(map->name_key, dev_name) == 0)
+ return (map->entry_p);
+
+ return (NULL);
+}
+
+/**
+ * Find out the type of device. CPU only currently.
+ */
+static void
+device_get_type(struct devinfo_dev *dev_p, const struct asn_oid **out_type_p)
+{
+
+ assert(dev_p != NULL);
+ assert(out_type_p != NULL);
+
+ if (dev_p == NULL)
+ return;
+
+ if (strncmp(dev_p->dd_name, "cpu", strlen("cpu")) == 0 &&
+ strstr(dev_p->dd_location, ".CPU") != NULL) {
+ *out_type_p = &OIDX_hrDeviceProcessor_c;
+ return;
+ }
+}
+
+/**
+ * Get the status of a device
+ */
+static enum DeviceStatus
+device_get_status(struct devinfo_dev *dev)
+{
+
+ assert(dev != NULL);
+
+ switch (dev->dd_state) {
+ case DS_ALIVE: /* probe succeeded */
+ case DS_NOTPRESENT: /* not probed or probe failed */
+ return (DS_DOWN);
+ case DS_ATTACHED: /* attach method called */
+ case DS_BUSY: /* device is open */
+ return (DS_RUNNING);
+ default:
+ return (DS_UNKNOWN);
+ }
+}
+
+/**
+ * Get the info for the given device and then recursively process all
+ * child devices.
+ */
+static int
+device_collector(struct devinfo_dev *dev, void *arg)
+{
+ struct device_entry *entry;
+
+ HRDBG("%llu/%llu name='%s' desc='%s' drivername='%s' location='%s'",
+ (unsigned long long)dev->dd_handle,
+ (unsigned long long)dev->dd_parent, dev->dd_name, dev->dd_desc,
+ dev->dd_drivername, dev->dd_location);
+
+ if (dev->dd_name[0] != '\0' || dev->dd_location[0] != '\0') {
+ HRDBG("ANALYZING dev %s at %s",
+ dev->dd_name, dev->dd_location);
+
+ if ((entry = device_find_by_dev(dev)) != NULL) {
+ entry->flags |= HR_DEVICE_FOUND;
+ entry->status = (u_int)device_get_status(dev);
+ } else if ((entry = device_entry_create_devinfo(dev)) != NULL) {
+ device_get_type(dev, &entry->type);
+
+ entry->flags |= HR_DEVICE_FOUND;
+ entry->status = (u_int)device_get_status(dev);
+ }
+ } else {
+ HRDBG("SKIPPED unknown device at location '%s'",
+ dev->dd_location );
+ }
+
+ return (devinfo_foreach_device_child(dev, device_collector, arg));
+}
+
+/**
+ * Create the socket to the device daemon.
+ */
+static int
+create_devd_socket(void)
+{
+ int d_sock;
+ struct sockaddr_un devd_addr;
+
+ bzero(&devd_addr, sizeof(struct sockaddr_un));
+
+ if ((d_sock = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) {
+ syslog(LOG_ERR, "Failed to create the socket for %s: %m",
+ PATH_DEVD_PIPE);
+ return (-1);
+ }
+
+ devd_addr.sun_family = PF_LOCAL;
+ devd_addr.sun_len = sizeof(devd_addr);
+ strlcpy(devd_addr.sun_path, PATH_DEVD_PIPE,
+ sizeof(devd_addr.sun_path) - 1);
+
+ if (connect(d_sock, (struct sockaddr *)&devd_addr,
+ sizeof(devd_addr)) == -1) {
+ syslog(LOG_ERR,"Failed to connect socket for %s: %m",
+ PATH_DEVD_PIPE);
+ if (close(d_sock) < 0 )
+ syslog(LOG_ERR,"Failed to close socket for %s: %m",
+ PATH_DEVD_PIPE);
+ return (-1);
+ }
+
+ return (d_sock);
+}
+
+/*
+ * Event on the devd socket.
+ *
+ * We should probably directly process entries here. For simplicity just
+ * call the refresh routine with the force flag for now.
+ */
+static void
+devd_socket_callback(int fd, void *arg __unused)
+{
+ char buf[512];
+ int read_len = -1;
+
+ assert(fd == devd_sock);
+
+ HRDBG("called");
+
+again:
+ read_len = read(fd, buf, sizeof(buf));
+ if (read_len < 0) {
+ if (errno == EBADF) {
+ devd_sock = -1;
+ if (devd_fd != NULL) {
+ fd_deselect(devd_fd);
+ devd_fd = NULL;
+ }
+ syslog(LOG_ERR, "Closing devd_fd, revert to "
+ "devinfo polling");
+ }
+
+ } else if (read_len == 0) {
+ syslog(LOG_ERR, "zero bytes read from devd pipe... "
+ "closing socket!");
+
+ if (close(devd_sock) < 0 )
+ syslog(LOG_ERR, "Failed to close devd socket: %m");
+
+ devd_sock = -1;
+ if (devd_fd != NULL) {
+ fd_deselect(devd_fd);
+ devd_fd = NULL;
+ }
+ syslog(LOG_ERR, "Closing devd_fd, revert to devinfo polling");
+
+ } else {
+ if (read_len == sizeof(buf))
+ goto again;
+ refresh_device_tbl(1);
+ }
+}
+
+/**
+ * Initialize and populate the device table.
+ */
+void
+init_device_tbl(void)
+{
+
+ /* initially populate table for the other tables */
+ refresh_device_tbl(1);
+
+ /* no problem if that fails - just use polling mode */
+ devd_sock = create_devd_socket();
+}
+
+/**
+ * Start devd(8) monitoring.
+ */
+void
+start_device_tbl(struct lmodule *mod)
+{
+
+ if (devd_sock > 0) {
+ devd_fd = fd_select(devd_sock, devd_socket_callback, NULL, mod);
+ if (devd_fd == NULL)
+ syslog(LOG_ERR, "fd_select failed on devd socket: %m");
+ }
+}
+
+/**
+ * Finalization routine for hrDeviceTable
+ * It destroys the lists and frees any allocated heap memory
+ */
+void
+fini_device_tbl(void)
+{
+ struct device_map_entry *n1;
+
+ if (devd_fd != NULL)
+ fd_deselect(devd_fd);
+
+ if (devd_sock != -1)
+ (void)close(devd_sock);
+
+ devinfo_free();
+
+ while ((n1 = STAILQ_FIRST(&device_map)) != NULL) {
+ STAILQ_REMOVE_HEAD(&device_map, link);
+ if (n1->entry_p != NULL) {
+ TAILQ_REMOVE(&device_tbl, n1->entry_p, link);
+ FREE_DEV_STRUCT(n1->entry_p);
+ }
+ free(n1->name_key);
+ free(n1->location_key);
+ free(n1);
+ }
+ assert(TAILQ_EMPTY(&device_tbl));
+}
+
+/**
+ * Refresh routine for hrDeviceTable. We don't refresh here if the devd socket
+ * is open, because in this case we have the actual information always. We
+ * also don't refresh when the table is new enough (if we don't have a devd
+ * socket). In either case a refresh can be forced by passing a non-zero value.
+ */
+void
+refresh_device_tbl(int force)
+{
+ struct device_entry *entry, *entry_tmp;
+ struct devinfo_dev *dev_root;
+ static int act = 0;
+
+ if (!force && (devd_sock >= 0 ||
+ (device_tick != 0 && this_tick - device_tick < device_tbl_refresh))){
+ HRDBG("no refresh needed");
+ return;
+ }
+
+ if (act) {
+ syslog(LOG_ERR, "%s: recursive call", __func__);
+ return;
+ }
+
+ if (devinfo_init() != 0) {
+ syslog(LOG_ERR,"%s: devinfo_init failed: %m", __func__);
+ return;
+ }
+
+ act = 1;
+ if ((dev_root = devinfo_handle_to_device(DEVINFO_ROOT_DEVICE)) == NULL){
+ syslog(LOG_ERR, "%s: can't get the root device: %m", __func__);
+ goto out;
+ }
+
+ /* mark each entry as missing */
+ TAILQ_FOREACH(entry, &device_tbl, link)
+ entry->flags &= ~HR_DEVICE_FOUND;
+
+ if (devinfo_foreach_device_child(dev_root, device_collector, NULL))
+ syslog(LOG_ERR, "%s: devinfo_foreach_device_child failed",
+ __func__);
+
+ /*
+ * Purge items that disappeared
+ */
+ TAILQ_FOREACH_SAFE(entry, &device_tbl, link, entry_tmp) {
+ /*
+ * If HR_DEVICE_IMMUTABLE bit is set then this means that
+ * this entry was not detected by the above
+ * devinfo_foreach_device() call. So we are not deleting
+ * it there.
+ */
+ if (!(entry->flags & HR_DEVICE_FOUND) &&
+ !(entry->flags & HR_DEVICE_IMMUTABLE))
+ device_entry_delete(entry);
+ }
+
+ device_tick = this_tick;
+
+ /*
+ * Force a refresh for the hrDiskStorageTable
+ * XXX Why not the other dependen tables?
+ */
+ refresh_disk_storage_tbl(1);
+
+ out:
+ devinfo_free();
+ act = 0;
+}
+
+/**
+ * This is the implementation for a generated (by a SNMP tool)
+ * function prototype, see hostres_tree.h
+ * It handles the SNMP operations for hrDeviceTable
+ */
+int
+op_hrDeviceTable(struct snmp_context *ctx __unused, struct snmp_value *value,
+ u_int sub, u_int iidx __unused, enum snmp_op curr_op)
+{
+ struct device_entry *entry;
+
+ refresh_device_tbl(0);
+
+ switch (curr_op) {
+
+ case SNMP_OP_GETNEXT:
+ if ((entry = NEXT_OBJECT_INT(&device_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ value->var.len = sub + 1;
+ value->var.subs[sub] = entry->index;
+ goto get;
+
+ case SNMP_OP_GET:
+ if ((entry = FIND_OBJECT_INT(&device_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_SET:
+ if ((entry = FIND_OBJECT_INT(&device_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NO_CREATION);
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ abort();
+ }
+ abort();
+
+ get:
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_hrDeviceIndex:
+ value->v.integer = entry->index;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrDeviceType:
+ assert(entry->type != NULL);
+ value->v.oid = *(entry->type);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrDeviceDescr:
+ return (string_get(value, entry->descr, -1));
+
+ case LEAF_hrDeviceID:
+ value->v.oid = *(entry->id);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrDeviceStatus:
+ value->v.integer = entry->status;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrDeviceErrors:
+ value->v.uint32 = entry->errors;
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+}
diff --git a/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_diskstorage_tbl.c b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_diskstorage_tbl.c
new file mode 100644
index 0000000..5675350
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_diskstorage_tbl.c
@@ -0,0 +1,653 @@
+/*-
+ * Copyright (c) 2005-2006 The FreeBSD Project
+ * All rights reserved.
+ *
+ * Author: Victor Cruceru <soc-victor@freebsd.org>
+ *
+ * Redistribution of this software and documentation and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ *
+ * 1. Redistributions of source code or documentation must retain the above
+ * copyright notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+/*
+ * Host Resources MIB for SNMPd. Implementation for the hrDiskStorageTable
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/ata.h>
+#include <sys/disk.h>
+#include <sys/linker.h>
+#include <sys/mdioctl.h>
+#include <sys/module.h>
+#include <sys/sysctl.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <paths.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "hostres_snmp.h"
+#include "hostres_oid.h"
+#include "hostres_tree.h"
+
+enum hrDiskStrorageAccess {
+ DS_READ_WRITE = 1,
+ DS_READ_ONLY = 2
+};
+
+enum hrDiskStrorageMedia {
+ DSM_OTHER = 1,
+ DSM_UNKNOWN = 2,
+ DSM_HARDDISK = 3,
+ DSM_FLOPPYDISK = 4,
+ DSM_OPTICALDISKROM= 5,
+ DSM_OPTICALDISKWORM= 6,
+ DSM_OPTICALDISKRW= 7,
+ DSM_RAMDISK = 8
+};
+
+/*
+ * This structure is used to hold a SNMP table entry for HOST-RESOURCES-MIB's
+ * hrDiskStorageTable. Note that index is external being allocated and
+ * maintained by the hrDeviceTable code.
+ *
+ * NOTE: according to MIB removable means removable media, not the
+ * device itself (like a USB card reader)
+ */
+struct disk_entry {
+ int32_t index;
+ int32_t access; /* enum hrDiskStrorageAccess */
+ int32_t media; /* enum hrDiskStrorageMedia*/
+ int32_t removable; /* enum snmpTCTruthValue*/
+ int32_t capacity;
+ TAILQ_ENTRY(disk_entry) link;
+ /*
+ * next items are not from the SNMP mib table, only to be used
+ * internally
+ */
+#define HR_DISKSTORAGE_FOUND 0x001
+#define HR_DISKSTORAGE_ATA 0x002 /* belongs to the ATA subsystem */
+#define HR_DISKSTORAGE_MD 0x004 /* it is a MD (memory disk) */
+ uint32_t flags;
+ uint64_t r_tick;
+ u_char dev_name[32]; /* device name, i.e. "ad4" or "acd0" */
+};
+TAILQ_HEAD(disk_tbl, disk_entry);
+
+/* the head of the list with hrDiskStorageTable's entries */
+static struct disk_tbl disk_tbl =
+ TAILQ_HEAD_INITIALIZER(disk_tbl);
+
+/* last tick when hrFSTable was updated */
+static uint64_t disk_storage_tick;
+
+/* minimum number of ticks between refreshs */
+uint32_t disk_storage_tbl_refresh = HR_DISK_TBL_REFRESH * 100;
+
+/* fd for "/dev/mdctl"*/
+static int md_fd = -1;
+
+/* buffer for sysctl("kern.disks") */
+static char *disk_list;
+static size_t disk_list_len;
+
+/* some constants */
+static const struct asn_oid OIDX_hrDeviceDiskStorage_c =
+ OIDX_hrDeviceDiskStorage;
+
+/**
+ * Load the MD driver if it isn't loaded already.
+ */
+static void
+mdmaybeload(void)
+{
+ char name1[64], name2[64];
+
+ snprintf(name1, sizeof(name1), "g_%s", MD_NAME);
+ snprintf(name2, sizeof(name2), "geom_%s", MD_NAME);
+ if (modfind(name1) == -1) {
+ /* Not present in kernel, try loading it. */
+ if (kldload(name2) == -1 || modfind(name1) == -1) {
+ if (errno != EEXIST) {
+ errx(EXIT_FAILURE,
+ "%s module not available!", name2);
+ }
+ }
+ }
+}
+
+/**
+ * Create a new entry into the DiskStorageTable.
+ */
+static struct disk_entry *
+disk_entry_create(const struct device_entry *devEntry)
+{
+ struct disk_entry *entry;
+
+ assert(devEntry != NULL);
+ if (devEntry == NULL)
+ return NULL;
+
+ if ((entry = malloc(sizeof(*entry))) == NULL) {
+ syslog(LOG_WARNING, "hrDiskStorageTable: %s: %m", __func__);
+ return (NULL);
+ }
+
+ memset(entry, 0, sizeof(*entry));
+ entry->index = devEntry->index;
+ INSERT_OBJECT_INT(entry, &disk_tbl);
+
+ return (entry);
+}
+
+/**
+ * Delete a disk table entry.
+ */
+static void
+disk_entry_delete(struct disk_entry *entry)
+{
+ struct device_entry *devEntry;
+
+ assert(entry != NULL);
+ TAILQ_REMOVE(&disk_tbl, entry, link);
+
+ devEntry = device_find_by_index(entry->index);
+
+ free(entry);
+
+ /*
+ * Also delete the respective device entry -
+ * this is needed for disk devices that are not
+ * detected by libdevinfo
+ */
+ if (devEntry != NULL &&
+ (devEntry->flags & HR_DEVICE_IMMUTABLE) == HR_DEVICE_IMMUTABLE)
+ device_entry_delete(devEntry);
+}
+
+/**
+ * Find a disk storage entry given its index.
+ */
+static struct disk_entry *
+disk_find_by_index(int32_t idx)
+{
+ struct disk_entry *entry;
+
+ TAILQ_FOREACH(entry, &disk_tbl, link)
+ if (entry->index == idx)
+ return (entry);
+
+ return (NULL);
+}
+
+/**
+ * Get the disk parameters
+ */
+static void
+disk_query_disk(struct disk_entry *entry)
+{
+ char dev_path[128];
+ int fd;
+ off_t mediasize;
+
+ if (entry == NULL || entry->dev_name[0] == '\0')
+ return;
+
+ snprintf(dev_path, sizeof(dev_path),
+ "%s%s", _PATH_DEV, entry->dev_name);
+ entry->capacity = 0;
+
+ HRDBG("OPENING device %s", dev_path);
+ if ((fd = open(dev_path, O_RDONLY|O_NONBLOCK)) == -1) {
+ HRDBG("OPEN device %s failed: %s", dev_path, strerror(errno));
+ return;
+ }
+
+ if (ioctl(fd, DIOCGMEDIASIZE, &mediasize) < 0) {
+ HRDBG("DIOCGMEDIASIZE for device %s failed: %s",
+ dev_path, strerror(errno));
+ (void)close(fd);
+ return;
+ }
+
+ mediasize = mediasize / 1024;
+ entry->capacity = (mediasize > INT_MAX ? INT_MAX : mediasize);
+ partition_tbl_handle_disk(entry->index, entry->dev_name);
+
+ (void)close(fd);
+}
+
+/**
+ * Find all ATA disks in the device table.
+ */
+static void
+disk_OS_get_ATA_disks(void)
+{
+ struct device_map_entry *map;
+ struct device_entry *entry;
+ struct disk_entry *disk_entry;
+ const struct disk_entry *found;
+
+ /* Things we know are ata disks */
+ static const struct disk_entry lookup[] = {
+ {
+ .dev_name = "ad",
+ .media = DSM_HARDDISK,
+ .removable = SNMP_FALSE
+ },
+ {
+ .dev_name = "ar",
+ .media = DSM_OTHER,
+ .removable = SNMP_FALSE
+ },
+ {
+ .dev_name = "acd",
+ .media = DSM_OPTICALDISKROM,
+ .removable = SNMP_TRUE
+ },
+ {
+ .dev_name = "afd",
+ .media = DSM_FLOPPYDISK,
+ .removable = SNMP_TRUE
+ },
+ {
+ .dev_name = "ast",
+ .media = DSM_OTHER,
+ .removable = SNMP_TRUE
+ },
+
+ { .media = DSM_UNKNOWN }
+ };
+
+ /* Walk over the device table looking for ata disks */
+ STAILQ_FOREACH(map, &device_map, link) {
+ /* Skip deleted entries. */
+ if (map->entry_p == NULL)
+ continue;
+ for (found = lookup; found->media != DSM_UNKNOWN; found++) {
+ if (strncmp(map->name_key, found->dev_name,
+ strlen(found->dev_name)) != 0)
+ continue;
+
+ /*
+ * Avoid false disk devices. For example adw(4) and
+ * adv(4) - they are not disks!
+ */
+ if (strlen(map->name_key) > strlen(found->dev_name) &&
+ !isdigit(map->name_key[strlen(found->dev_name)]))
+ continue;
+
+ /* First get the entry from the hrDeviceTbl */
+ entry = map->entry_p;
+ entry->type = &OIDX_hrDeviceDiskStorage_c;
+
+ /* Then check hrDiskStorage table for this device */
+ disk_entry = disk_find_by_index(entry->index);
+ if (disk_entry == NULL) {
+ disk_entry = disk_entry_create(entry);
+ if (disk_entry == NULL)
+ continue;
+
+ disk_entry->access = DS_READ_WRITE;
+ strlcpy(disk_entry->dev_name, entry->name,
+ sizeof(disk_entry->dev_name));
+
+ disk_entry->media = found->media;
+ disk_entry->removable = found->removable;
+ }
+
+ disk_entry->flags |= HR_DISKSTORAGE_FOUND;
+ disk_entry->flags |= HR_DISKSTORAGE_ATA;
+
+ disk_query_disk(disk_entry);
+ disk_entry->r_tick = this_tick;
+ }
+ }
+}
+
+/**
+ * Find MD disks in the device table.
+ */
+static void
+disk_OS_get_MD_disks(void)
+{
+ struct device_map_entry *map;
+ struct device_entry *entry;
+ struct disk_entry *disk_entry;
+ struct md_ioctl mdio;
+ int unit;
+
+ if (md_fd <= 0)
+ return;
+
+ /* Look for md devices */
+ STAILQ_FOREACH(map, &device_map, link) {
+ /* Skip deleted entries. */
+ if (map->entry_p == NULL)
+ continue;
+ if (sscanf(map->name_key, "md%d", &unit) != 1)
+ continue;
+
+ /* First get the entry from the hrDeviceTbl */
+ entry = device_find_by_index(map->hrIndex);
+ entry->type = &OIDX_hrDeviceDiskStorage_c;
+
+ /* Then check hrDiskStorage table for this device */
+ disk_entry = disk_find_by_index(entry->index);
+ if (disk_entry == NULL) {
+ disk_entry = disk_entry_create(entry);
+ if (disk_entry == NULL)
+ continue;
+
+ memset(&mdio, 0, sizeof(mdio));
+ mdio.md_version = MDIOVERSION;
+ mdio.md_unit = unit;
+
+ if (ioctl(md_fd, MDIOCQUERY, &mdio) < 0) {
+ syslog(LOG_ERR,
+ "hrDiskStorageTable: Couldnt ioctl");
+ continue;
+ }
+
+ if ((mdio.md_options & MD_READONLY) == MD_READONLY)
+ disk_entry->access = DS_READ_ONLY;
+ else
+ disk_entry->access = DS_READ_WRITE;
+
+ strlcpy(disk_entry->dev_name, entry->name,
+ sizeof(disk_entry->dev_name));
+
+ disk_entry->media = DSM_RAMDISK;
+ disk_entry->removable = SNMP_FALSE;
+ }
+
+ disk_entry->flags |= HR_DISKSTORAGE_FOUND;
+ disk_entry->flags |= HR_DISKSTORAGE_MD;
+ disk_entry->r_tick = this_tick;
+ }
+}
+
+/**
+ * Find rest of disks
+ */
+static void
+disk_OS_get_disks(void)
+{
+ size_t disk_cnt = 0;
+ struct device_entry *entry;
+ struct disk_entry *disk_entry;
+
+ size_t need = 0;
+
+ if (sysctlbyname("kern.disks", NULL, &need, NULL, 0) == -1) {
+ syslog(LOG_ERR, "%s: sysctl_1 kern.disks failed: %m", __func__);
+ return;
+ }
+
+ if (need == 0)
+ return;
+
+ if (disk_list_len != need + 1 || disk_list == NULL) {
+ disk_list_len = need + 1;
+ disk_list = reallocf(disk_list, disk_list_len);
+ }
+
+ if (disk_list == NULL) {
+ syslog(LOG_ERR, "%s: reallocf failed", __func__);
+ disk_list_len = 0;
+ return;
+ }
+
+ memset(disk_list, 0, disk_list_len);
+
+ if (sysctlbyname("kern.disks", disk_list, &need, NULL, 0) == -1 ||
+ disk_list[0] == 0) {
+ syslog(LOG_ERR, "%s: sysctl_2 kern.disks failed: %m", __func__);
+ return;
+ }
+
+ for (disk_cnt = 0; disk_cnt < need; disk_cnt++) {
+ char *disk = NULL;
+ char disk_device[128] = "";
+
+ disk = strsep(&disk_list, " ");
+ if (disk == NULL)
+ break;
+
+ snprintf(disk_device, sizeof(disk_device),
+ "%s%s", _PATH_DEV, disk);
+
+ /* First check if the disk is in the hrDeviceTable. */
+ if ((entry = device_find_by_name(disk)) == NULL) {
+ /*
+ * not found there - insert it as immutable
+ */
+ syslog(LOG_WARNING, "%s: adding device '%s' to "
+ "device list", __func__, disk);
+
+ if ((entry = device_entry_create(disk, "", "")) == NULL)
+ continue;
+
+ entry->flags |= HR_DEVICE_IMMUTABLE;
+ }
+
+ entry->type = &OIDX_hrDeviceDiskStorage_c;
+
+ /* Then check hrDiskStorage table for this device */
+ disk_entry = disk_find_by_index(entry->index);
+ if (disk_entry == NULL) {
+ disk_entry = disk_entry_create(entry);
+ if (disk_entry == NULL)
+ continue;
+ }
+
+ disk_entry->flags |= HR_DISKSTORAGE_FOUND;
+
+ if ((disk_entry->flags & HR_DISKSTORAGE_ATA) ||
+ (disk_entry->flags & HR_DISKSTORAGE_MD)) {
+ /*
+ * ATA/MD detection is running before this one,
+ * so don't waste the time here
+ */
+ continue;
+ }
+
+ disk_entry->access = DS_READ_WRITE;
+ disk_entry->media = DSM_UNKNOWN;
+ disk_entry->removable = SNMP_FALSE;
+
+ if (strncmp(disk_entry->dev_name, "da", 2) == 0 ||
+ strncmp(disk_entry->dev_name, "ada", 3) == 0) {
+ disk_entry->media = DSM_HARDDISK;
+ disk_entry->removable = SNMP_FALSE;
+ } else if (strncmp(disk_entry->dev_name, "cd", 2) == 0) {
+ disk_entry->media = DSM_OPTICALDISKROM;
+ disk_entry->removable = SNMP_TRUE;
+ } else {
+ disk_entry->media = DSM_UNKNOWN;
+ disk_entry->removable = SNMP_FALSE;
+ }
+
+ strlcpy((char *)disk_entry->dev_name, disk,
+ sizeof(disk_entry->dev_name));
+
+ disk_query_disk(disk_entry);
+ disk_entry->r_tick = this_tick;
+ }
+}
+
+/**
+ * Refresh routine for hrDiskStorageTable
+ * Usable for polling the system for any changes.
+ */
+void
+refresh_disk_storage_tbl(int force)
+{
+ struct disk_entry *entry, *entry_tmp;
+
+ if (disk_storage_tick != 0 && !force &&
+ this_tick - disk_storage_tick < disk_storage_tbl_refresh) {
+ HRDBG("no refresh needed");
+ return;
+ }
+
+ partition_tbl_pre_refresh();
+
+ /* mark each entry as missing */
+ TAILQ_FOREACH(entry, &disk_tbl, link)
+ entry->flags &= ~HR_DISKSTORAGE_FOUND;
+
+ disk_OS_get_ATA_disks(); /* this must be called first ! */
+ disk_OS_get_MD_disks();
+ disk_OS_get_disks();
+
+ /*
+ * Purge items that disappeared
+ */
+ TAILQ_FOREACH_SAFE(entry, &disk_tbl, link, entry_tmp)
+ if (!(entry->flags & HR_DISKSTORAGE_FOUND))
+ /* XXX remove IMMUTABLE entries that have disappeared */
+ disk_entry_delete(entry);
+
+ disk_storage_tick = this_tick;
+
+ partition_tbl_post_refresh();
+
+ HRDBG("refresh DONE");
+}
+
+/*
+ * Init the things for both of hrDiskStorageTable
+ */
+int
+init_disk_storage_tbl(void)
+{
+ char mddev[32] = "";
+
+ /* Try to load md.ko if not loaded already */
+ mdmaybeload();
+
+ md_fd = -1;
+ snprintf(mddev, sizeof(mddev) - 1, "%s%s", _PATH_DEV, MDCTL_NAME);
+ if ((md_fd = open(mddev, O_RDWR)) == -1) {
+ syslog(LOG_ERR, "open %s failed - will not include md(4) "
+ "info: %m", mddev);
+ }
+
+ refresh_disk_storage_tbl(1);
+
+ return (0);
+}
+
+/*
+ * Finalization routine for hrDiskStorageTable
+ * It destroys the lists and frees any allocated heap memory
+ */
+void
+fini_disk_storage_tbl(void)
+{
+ struct disk_entry *n1;
+
+ while ((n1 = TAILQ_FIRST(&disk_tbl)) != NULL) {
+ TAILQ_REMOVE(&disk_tbl, n1, link);
+ free(n1);
+ }
+
+ free(disk_list);
+
+ if (md_fd > 0) {
+ if (close(md_fd) == -1)
+ syslog(LOG_ERR,"close (/dev/mdctl) failed: %m");
+ md_fd = -1;
+ }
+}
+
+/*
+ * This is the implementation for a generated (by our SNMP "compiler" tool)
+ * function prototype, see hostres_tree.h
+ * It handles the SNMP operations for hrDiskStorageTable
+ */
+int
+op_hrDiskStorageTable(struct snmp_context *ctx __unused,
+ struct snmp_value *value, u_int sub, u_int iidx __unused,
+ enum snmp_op curr_op)
+{
+ struct disk_entry *entry;
+
+ refresh_disk_storage_tbl(0);
+
+ switch (curr_op) {
+
+ case SNMP_OP_GETNEXT:
+ if ((entry = NEXT_OBJECT_INT(&disk_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ value->var.len = sub + 1;
+ value->var.subs[sub] = entry->index;
+ goto get;
+
+ case SNMP_OP_GET:
+ if ((entry = FIND_OBJECT_INT(&disk_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_SET:
+ if ((entry = FIND_OBJECT_INT(&disk_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NO_CREATION);
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ abort();
+ }
+ abort();
+
+ get:
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_hrDiskStorageAccess:
+ value->v.integer = entry->access;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrDiskStorageMedia:
+ value->v.integer = entry->media;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrDiskStorageRemovable:
+ value->v.integer = entry->removable;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrDiskStorageCapacity:
+ value->v.integer = entry->capacity;
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+}
diff --git a/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_fs_tbl.c b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_fs_tbl.c
new file mode 100644
index 0000000..91505d5
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_fs_tbl.c
@@ -0,0 +1,473 @@
+/*-
+ * Copyright (c) 2005-2006 The FreeBSD Project
+ * All rights reserved.
+ *
+ * Author: Victor Cruceru <soc-victor@freebsd.org>
+ *
+ * Redistribution of this software and documentation and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ *
+ * 1. Redistributions of source code or documentation must retain the above
+ * copyright notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+/*
+ * Host Resources MIB for SNMPd. Implementation for hrFSTable
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <sys/mount.h>
+
+#include <assert.h>
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <sysexits.h>
+
+#include "hostres_snmp.h"
+#include "hostres_oid.h"
+#include "hostres_tree.h"
+
+/*
+ * File system access enum
+ */
+enum hrFSAccess {
+ FS_READ_WRITE = 1,
+ FS_READ_ONLY = 2
+};
+
+/* maximum length (according to MIB) for fs_entry::mountPoint */
+#define FS_MP_MLEN (128 + 1)
+
+/* maximum length (according to MIB) for fs_entry::remoteMountPoint */
+#define FS_RMP_MLEN (128 + 1)
+
+/*
+ * This structure is used to hold a SNMP table entry
+ * for HOST-RESOURCES-MIB's hrFSTable
+ */
+struct fs_entry {
+ int32_t index;
+ u_char *mountPoint;
+ u_char *remoteMountPoint;
+ const struct asn_oid *type;
+ int32_t access; /* enum hrFSAccess, see above */
+ int32_t bootable; /* TruthValue */
+ int32_t storageIndex; /* hrStorageTblEntry::index */
+ u_char lastFullBackupDate[11];
+ u_char lastPartialBackupDate[11];
+#define HR_FS_FOUND 0x001
+ uint32_t flags; /* not in mib table, for internal use */
+ TAILQ_ENTRY(fs_entry) link;
+};
+TAILQ_HEAD(fs_tbl, fs_entry);
+
+/*
+ * Next structure is used to keep o list of mappings from a specific name
+ * (a_name) to an entry in the hrFSTblEntry. We are trying to keep the same
+ * index for a specific name at least for the duration of one SNMP agent run.
+ */
+struct fs_map_entry {
+ int32_t hrIndex; /* used for fs_entry::index */
+ u_char *a_name; /* map key same as fs_entry::mountPoint */
+
+ /* may be NULL if the respective hrFSTblEntry is (temporally) gone */
+ struct fs_entry *entry;
+ STAILQ_ENTRY(fs_map_entry) link;
+};
+STAILQ_HEAD(fs_map, fs_map_entry);
+
+/* head of the list with hrFSTable's entries */
+static struct fs_tbl fs_tbl = TAILQ_HEAD_INITIALIZER(fs_tbl);
+
+/* for consistent table indexing */
+static struct fs_map fs_map = STAILQ_HEAD_INITIALIZER(fs_map);
+
+/* next index available for hrFSTable */
+static uint32_t next_fs_index = 1;
+
+/* last tick when hrFSTable was updated */
+static uint64_t fs_tick;
+
+/* maximum number of ticks between refreshs */
+uint32_t fs_tbl_refresh = HR_FS_TBL_REFRESH * 100;
+
+/* some constants */
+static const struct asn_oid OIDX_hrFSBerkeleyFFS_c = OIDX_hrFSBerkeleyFFS;
+static const struct asn_oid OIDX_hrFSiso9660_c = OIDX_hrFSiso9660;
+static const struct asn_oid OIDX_hrFSNFS_c = OIDX_hrFSNFS;
+static const struct asn_oid OIDX_hrFSLinuxExt2_c = OIDX_hrFSLinuxExt2;
+static const struct asn_oid OIDX_hrFSOther_c = OIDX_hrFSOther;
+static const struct asn_oid OIDX_hrFSFAT32_c = OIDX_hrFSFAT32;
+static const struct asn_oid OIDX_hrFSNTFS_c = OIDX_hrFSNTFS;
+static const struct asn_oid OIDX_hrFSNetware_c = OIDX_hrFSNetware;
+static const struct asn_oid OIDX_hrFSHPFS_c = OIDX_hrFSHPFS;
+static const struct asn_oid OIDX_hrFSUnknown_c = OIDX_hrFSUnknown;
+
+/* file system type map */
+static const struct {
+ const char *str; /* the type string */
+ const struct asn_oid *oid; /* the OID to return */
+} fs_type_map[] = {
+ { "ufs", &OIDX_hrFSBerkeleyFFS_c },
+ { "zfs", &OIDX_hrFSOther_c },
+ { "cd9660", &OIDX_hrFSiso9660_c },
+ { "nfs", &OIDX_hrFSNFS_c },
+ { "ext2fs", &OIDX_hrFSLinuxExt2_c },
+ { "procfs", &OIDX_hrFSOther_c },
+ { "devfs", &OIDX_hrFSOther_c },
+ { "msdosfs", &OIDX_hrFSFAT32_c },
+ { "ntfs", &OIDX_hrFSNTFS_c },
+ { "nwfs", &OIDX_hrFSNetware_c },
+ { "hpfs", &OIDX_hrFSHPFS_c },
+ { "smbfs", &OIDX_hrFSOther_c },
+};
+#define N_FS_TYPE_MAP (sizeof(fs_type_map) / sizeof(fs_type_map[0]))
+
+/**
+ * Create an entry into the FS table and an entry in the map (if needed).
+ */
+static struct fs_entry *
+fs_entry_create(const char *name)
+{
+ struct fs_entry *entry;
+ struct fs_map_entry *map;
+
+ assert(name != NULL);
+ assert(strlen(name) > 0);
+
+ STAILQ_FOREACH(map, &fs_map, link)
+ if (strcmp(map->a_name, name) == 0)
+ break;
+
+ if (map == NULL) {
+ size_t mount_point_len;
+
+ /* new object - get a new index */
+ if (next_fs_index > INT_MAX) {
+ /* Unrecoverable error - die clean and quicly*/
+ syslog(LOG_ERR, "%s: hrFSTable index wrap", __func__);
+ errx(EX_SOFTWARE, "hrFSTable index wrap");
+ }
+
+ if ((map = malloc(sizeof(*map))) == NULL) {
+ syslog(LOG_ERR, "%s: %m", __func__);
+ return (NULL);
+ }
+
+ mount_point_len = strlen(name) + 1;
+ if (mount_point_len > FS_MP_MLEN)
+ mount_point_len = FS_MP_MLEN;
+
+ if ((map->a_name = malloc(mount_point_len)) == NULL) {
+ syslog(LOG_ERR, "%s: %m", __func__);
+ free(map);
+ return (NULL);
+ }
+
+ strlcpy(map->a_name, name, mount_point_len);
+
+ map->hrIndex = next_fs_index++;
+ map->entry = NULL;
+ STAILQ_INSERT_TAIL(&fs_map, map, link);
+
+ HRDBG("%s added into hrFSMap at index=%d", name, map->hrIndex);
+ } else {
+ HRDBG("%s exists in hrFSMap index=%d", name, map->hrIndex);
+ }
+
+ if ((entry = malloc(sizeof(*entry))) == NULL) {
+ syslog(LOG_WARNING, "%s: %m", __func__);
+ return (NULL);
+ }
+
+ if ((entry->mountPoint = strdup(name)) == NULL) {
+ syslog(LOG_ERR, "%s: %m", __func__);
+ free(entry);
+ return (NULL);
+ }
+
+ entry->index = map->hrIndex;
+ map->entry = entry;
+
+ INSERT_OBJECT_INT(entry, &fs_tbl);
+ return (entry);
+}
+
+/**
+ * Delete an entry in the FS table.
+ */
+static void
+fs_entry_delete(struct fs_entry* entry)
+{
+ struct fs_map_entry *map;
+
+ assert(entry != NULL);
+
+ TAILQ_REMOVE(&fs_tbl, entry, link);
+ STAILQ_FOREACH(map, &fs_map, link)
+ if (map->entry == entry) {
+ map->entry = NULL;
+ break;
+ }
+ free(entry->mountPoint);
+ free(entry->remoteMountPoint);
+ free(entry);
+}
+
+/**
+ * Find a table entry by its name
+ */
+static struct fs_entry *
+fs_find_by_name(const char *name)
+{
+ struct fs_entry *entry;
+
+ TAILQ_FOREACH(entry, &fs_tbl, link)
+ if (strcmp(entry->mountPoint, name) == 0)
+ return (entry);
+
+ return (NULL);
+}
+
+/**
+ * Get rid of all data
+ */
+void
+fini_fs_tbl(void)
+{
+ struct fs_map_entry *n1;
+
+ while ((n1 = STAILQ_FIRST(&fs_map)) != NULL) {
+ STAILQ_REMOVE_HEAD(&fs_map, link);
+ if (n1->entry != NULL) {
+ TAILQ_REMOVE(&fs_tbl, n1->entry, link);
+ free(n1->entry->mountPoint);
+ free(n1->entry->remoteMountPoint);
+ free(n1->entry);
+ }
+ free(n1->a_name);
+ free(n1);
+ }
+ assert(TAILQ_EMPTY(&fs_tbl));
+}
+
+/**
+ * Called before the refreshing is started from the storage table.
+ */
+void
+fs_tbl_pre_refresh(void)
+{
+ struct fs_entry *entry;
+
+ /* mark each entry as missisng */
+ TAILQ_FOREACH(entry, &fs_tbl, link)
+ entry->flags &= ~HR_FS_FOUND;
+}
+
+/**
+ * Called after refreshing from the storage table.
+ */
+void
+fs_tbl_post_refresh(void)
+{
+ struct fs_entry *entry, *entry_tmp;
+
+ /*
+ * Purge items that disappeared
+ */
+ TAILQ_FOREACH_SAFE(entry, &fs_tbl, link, entry_tmp)
+ if (!(entry->flags & HR_FS_FOUND))
+ fs_entry_delete(entry);
+
+ fs_tick = this_tick;
+}
+
+/*
+ * Refresh the FS table. This is done by forcing a refresh of the storage table.
+ */
+void
+refresh_fs_tbl(void)
+{
+
+ if (fs_tick == 0 || this_tick - fs_tick >= fs_tbl_refresh) {
+ refresh_storage_tbl(1);
+ HRDBG("refresh DONE");
+ }
+}
+
+/**
+ * Get the type OID for a given file system
+ */
+const struct asn_oid *
+fs_get_type(const struct statfs *fs_p)
+{
+ u_int t;
+
+ assert(fs_p != NULL);
+
+ for (t = 0; t < N_FS_TYPE_MAP; t++)
+ if (strcmp(fs_type_map[t].str, fs_p->f_fstypename) == 0)
+ return (fs_type_map[t].oid);
+
+ return (&OIDX_hrFSUnknown_c);
+}
+
+/*
+ * Given information returned from statfs(2) either create a new entry into
+ * the fs_tbl or refresh the entry if it is already there.
+ */
+void
+fs_tbl_process_statfs_entry(const struct statfs *fs_p, int32_t storage_idx)
+{
+ struct fs_entry *entry;
+
+ assert(fs_p != 0);
+
+ HRDBG("for hrStorageEntry::index %d", storage_idx);
+
+ if (fs_p == NULL)
+ return;
+
+ if ((entry = fs_find_by_name(fs_p->f_mntonname)) != NULL ||
+ (entry = fs_entry_create(fs_p->f_mntonname)) != NULL) {
+ entry->flags |= HR_FS_FOUND;
+
+ if (!(fs_p->f_flags & MNT_LOCAL)) {
+ /* this is a remote mount */
+ entry->remoteMountPoint = strdup(fs_p->f_mntfromname);
+ /* if strdup failed, let it be NULL */
+
+ } else {
+ entry->remoteMountPoint = strdup("");
+ /* if strdup failed, let it be NULL */
+ }
+
+ entry->type = fs_get_type(fs_p);
+
+ if ((fs_p->f_flags & MNT_RDONLY) == MNT_RDONLY)
+ entry->access = FS_READ_ONLY;
+ else
+ entry->access = FS_READ_WRITE;
+
+ /* FIXME - bootable fs ?! */
+ entry->bootable = TRUTH_MK((fs_p->f_flags & MNT_ROOTFS)
+ == MNT_ROOTFS);
+
+ entry->storageIndex = storage_idx;
+
+ /* Info not available */
+ memset(entry->lastFullBackupDate, 0,
+ sizeof(entry->lastFullBackupDate));
+
+ /* Info not available */
+ memset(entry->lastPartialBackupDate, 0,
+ sizeof(entry->lastPartialBackupDate));
+
+ handle_partition_fs_index(fs_p->f_mntfromname, entry->index);
+ }
+}
+
+/*
+ * This is the implementation for a generated (by our SNMP "compiler" tool)
+ * function prototype, see hostres_tree.h
+ * It handles the SNMP operations for hrFSTable
+ */
+int
+op_hrFSTable(struct snmp_context *ctx __unused, struct snmp_value *value,
+ u_int sub, u_int iidx __unused, enum snmp_op curr_op)
+{
+ struct fs_entry *entry;
+
+ refresh_fs_tbl();
+
+ switch (curr_op) {
+
+ case SNMP_OP_GETNEXT:
+ if ((entry = NEXT_OBJECT_INT(&fs_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ value->var.len = sub + 1;
+ value->var.subs[sub] = entry->index;
+ goto get;
+
+ case SNMP_OP_GET:
+ if ((entry = FIND_OBJECT_INT(&fs_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_SET:
+ if ((entry = FIND_OBJECT_INT(&fs_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NO_CREATION);
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ abort();
+ }
+ abort();
+ get:
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_hrFSIndex:
+ value->v.integer = entry->index;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrFSMountPoint:
+ return (string_get(value, entry->mountPoint, -1));
+
+ case LEAF_hrFSRemoteMountPoint:
+ if (entry->remoteMountPoint == NULL)
+ return (string_get(value, "", -1));
+ else
+ return (string_get(value, entry->remoteMountPoint, -1));
+ break;
+
+ case LEAF_hrFSType:
+ assert(entry->type != NULL);
+ value->v.oid = *(entry->type);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrFSAccess:
+ value->v.integer = entry->access;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrFSBootable:
+ value->v.integer = entry->bootable;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrFSStorageIndex:
+ value->v.integer = entry->storageIndex;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrFSLastFullBackupDate:
+ return (string_get(value, entry->lastFullBackupDate, 8));
+
+ case LEAF_hrFSLastPartialBackupDate:
+ return (string_get(value, entry->lastPartialBackupDate, 8));
+ }
+ abort();
+}
diff --git a/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_network_tbl.c b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_network_tbl.c
new file mode 100644
index 0000000..4329a1c
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_network_tbl.c
@@ -0,0 +1,302 @@
+/*-
+ * Copyright (c) 2005-2006 The FreeBSD Project
+ * All rights reserved.
+ *
+ * Author: Victor Cruceru <soc-victor@freebsd.org>
+ *
+ * Redistribution of this software and documentation and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ *
+ * 1. Redistributions of source code or documentation must retain the above
+ * copyright notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+/*
+ * Host Resources MIB implementation for SNMPd: instrumentation for
+ * hrNetworkTable
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <net/if.h>
+#include <net/if_mib.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <ifaddrs.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "hostres_snmp.h"
+#include "hostres_oid.h"
+#include "hostres_tree.h"
+
+#include <bsnmp/snmp_mibII.h>
+
+/*
+ * This structure is used to hold a SNMP table entry
+ * for HOST-RESOURCES-MIB's hrNetworkTable
+ */
+struct network_entry {
+ int32_t index;
+ int32_t ifIndex;
+ TAILQ_ENTRY(network_entry) link;
+#define HR_NETWORK_FOUND 0x001
+ uint32_t flags;
+
+};
+TAILQ_HEAD(network_tbl, network_entry);
+
+/* the head of the list with hrNetworkTable's entries */
+static struct network_tbl network_tbl = TAILQ_HEAD_INITIALIZER(network_tbl);
+
+/* last (agent) tick when hrNetworkTable was updated */
+static uint64_t network_tick;
+
+/* maximum number of ticks between updates of network table */
+uint32_t network_tbl_refresh = HR_NETWORK_TBL_REFRESH * 100;
+
+/* Constants */
+static const struct asn_oid OIDX_hrDeviceNetwork_c = OIDX_hrDeviceNetwork;
+
+/**
+ * Create a new entry into the network table
+ */
+static struct network_entry *
+network_entry_create(const struct device_entry *devEntry)
+{
+ struct network_entry *entry;
+
+ assert(devEntry != NULL);
+ if (devEntry == NULL)
+ return (NULL);
+
+ if ((entry = malloc(sizeof(*entry))) == NULL) {
+ syslog(LOG_WARNING, "%s: %m", __func__);
+ return (NULL);
+ }
+
+ memset(entry, 0, sizeof(*entry));
+ entry->index = devEntry->index;
+ INSERT_OBJECT_INT(entry, &network_tbl);
+
+ return (entry);
+}
+
+/**
+ * Delete an entry in the network table
+ */
+static void
+network_entry_delete(struct network_entry* entry)
+{
+
+ TAILQ_REMOVE(&network_tbl, entry, link);
+ free(entry);
+}
+
+/**
+ * Fetch the interfaces from the mibII module, get their real name from the
+ * kernel and try to find it in the device table.
+ */
+static void
+network_get_interfaces(void)
+{
+ struct device_entry *dev;
+ struct network_entry *net;
+ struct mibif *ifp;
+ int name[6];
+ size_t len;
+ char *dname;
+
+ name[0] = CTL_NET;
+ name[1] = PF_LINK;
+ name[2] = NETLINK_GENERIC;
+ name[3] = IFMIB_IFDATA;
+ name[5] = IFDATA_DRIVERNAME;
+
+ for (ifp = mib_first_if(); ifp != NULL; ifp = mib_next_if(ifp)) {
+ HRDBG("%s %s", ifp->name, ifp->descr);
+
+ name[4] = ifp->sysindex;
+
+ /* get the original name */
+ len = 0;
+ if (sysctl(name, 6, NULL, &len, 0, 0) < 0) {
+ syslog(LOG_ERR, "sysctl(net.link.ifdata.%d."
+ "drivername): %m", ifp->sysindex);
+ continue;
+ }
+ if ((dname = malloc(len)) == NULL) {
+ syslog(LOG_ERR, "malloc: %m");
+ continue;
+ }
+ if (sysctl(name, 6, dname, &len, 0, 0) < 0) {
+ syslog(LOG_ERR, "sysctl(net.link.ifdata.%d."
+ "drivername): %m", ifp->sysindex);
+ free(dname);
+ continue;
+ }
+
+ HRDBG("got device %s (%s)", ifp->name, dname);
+
+ if ((dev = device_find_by_name(dname)) == NULL) {
+ HRDBG("%s not in hrDeviceTable", dname);
+ free(dname);
+ continue;
+ }
+ HRDBG("%s found in hrDeviceTable", dname);
+
+ dev->type = &OIDX_hrDeviceNetwork_c;
+ dev->flags |= HR_DEVICE_IMMUTABLE;
+
+ free(dname);
+
+ /* Then check hrNetworkTable for this device */
+ TAILQ_FOREACH(net, &network_tbl, link)
+ if (net->index == dev->index)
+ break;
+
+ if (net == NULL && (net = network_entry_create(dev)) == NULL)
+ continue;
+
+ net->flags |= HR_NETWORK_FOUND;
+ net->ifIndex = ifp->index;
+ }
+
+ network_tick = this_tick;
+}
+
+/**
+ * Finalization routine for hrNetworkTable.
+ * It destroys the lists and frees any allocated heap memory.
+ */
+void
+fini_network_tbl(void)
+{
+ struct network_entry *n1;
+
+ while ((n1 = TAILQ_FIRST(&network_tbl)) != NULL) {
+ TAILQ_REMOVE(&network_tbl, n1, link);
+ free(n1);
+ }
+}
+
+/**
+ * Get the interface list from mibII only at this point to be sure that
+ * it is there already.
+ */
+void
+start_network_tbl(void)
+{
+
+ mib_refresh_iflist();
+ network_get_interfaces();
+}
+
+/**
+ * Refresh the table.
+ */
+static void
+refresh_network_tbl(void)
+{
+ struct network_entry *entry, *entry_tmp;
+
+ if (this_tick - network_tick < network_tbl_refresh) {
+ HRDBG("no refresh needed");
+ return;
+ }
+
+ /* mark each entry as missing */
+ TAILQ_FOREACH(entry, &network_tbl, link)
+ entry->flags &= ~HR_NETWORK_FOUND;
+
+ network_get_interfaces();
+
+ /*
+ * Purge items that disappeared
+ */
+ TAILQ_FOREACH_SAFE(entry, &network_tbl, link, entry_tmp) {
+ if (!(entry->flags & HR_NETWORK_FOUND))
+ network_entry_delete(entry);
+ }
+
+ HRDBG("refresh DONE");
+}
+
+/*
+ * This is the implementation for a generated (by our SNMP tool)
+ * function prototype, see hostres_tree.h
+ * It handles the SNMP operations for hrNetworkTable
+ */
+int
+op_hrNetworkTable(struct snmp_context *ctx __unused, struct snmp_value *value,
+ u_int sub, u_int iidx __unused, enum snmp_op curr_op)
+{
+ struct network_entry *entry;
+
+ refresh_network_tbl();
+
+ switch (curr_op) {
+
+ case SNMP_OP_GETNEXT:
+ if ((entry = NEXT_OBJECT_INT(&network_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ value->var.len = sub + 1;
+ value->var.subs[sub] = entry->index;
+ goto get;
+
+ case SNMP_OP_GET:
+ if ((entry = FIND_OBJECT_INT(&network_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_SET:
+ if ((entry = FIND_OBJECT_INT(&network_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NO_CREATION);
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ abort();
+ }
+ abort();
+
+ get:
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_hrNetworkIfIndex:
+ value->v.integer = entry->ifIndex;
+ return (SNMP_ERR_NOERROR);
+
+ }
+ abort();
+}
diff --git a/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_partition_tbl.c b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_partition_tbl.c
new file mode 100644
index 0000000..65c0012
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_partition_tbl.c
@@ -0,0 +1,630 @@
+/*-
+ * Copyright (c) 2005-2006 The FreeBSD Project
+ * All rights reserved.
+ *
+ * Author: Victor Cruceru <soc-victor@freebsd.org>
+ *
+ * Redistribution of this software and documentation and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ *
+ * 1. Redistributions of source code or documentation must retain the above
+ * copyright notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+/*
+ * Host Resources MIB: hrPartitionTable implementation for SNMPd.
+ */
+
+#include <sys/types.h>
+#include <sys/limits.h>
+
+#include <assert.h>
+#include <err.h>
+#include <inttypes.h>
+#include <libgeom.h>
+#include <paths.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <sysexits.h>
+
+#include "hostres_snmp.h"
+#include "hostres_oid.h"
+#include "hostres_tree.h"
+
+#ifdef PC98
+#define HR_FREEBSD_PART_TYPE 0xc494
+#else
+#define HR_FREEBSD_PART_TYPE 165
+#endif
+
+/* Maximum length for label and id including \0 */
+#define PART_STR_MLEN (128 + 1)
+
+/*
+ * One row in the hrPartitionTable
+ */
+struct partition_entry {
+ asn_subid_t index[2];
+ u_char *label; /* max allocated len will be PART_STR_MLEN */
+ u_char *id; /* max allocated len will be PART_STR_MLEN */
+ int32_t size;
+ int32_t fs_Index;
+ TAILQ_ENTRY(partition_entry) link;
+#define HR_PARTITION_FOUND 0x001
+ uint32_t flags;
+};
+TAILQ_HEAD(partition_tbl, partition_entry);
+
+/*
+ * This table is used to get a consistent indexing. It saves the name -> index
+ * mapping while we rebuild the partition table.
+ */
+struct partition_map_entry {
+ int32_t index; /* partition_entry::index */
+ u_char *id; /* max allocated len will be PART_STR_MLEN */
+
+ /*
+ * next may be NULL if the respective partition_entry
+ * is (temporally) gone.
+ */
+ struct partition_entry *entry;
+ STAILQ_ENTRY(partition_map_entry) link;
+};
+STAILQ_HEAD(partition_map, partition_map_entry);
+
+/* Mapping table for consistent indexing */
+static struct partition_map partition_map =
+ STAILQ_HEAD_INITIALIZER(partition_map);
+
+/* THE partition table. */
+static struct partition_tbl partition_tbl =
+ TAILQ_HEAD_INITIALIZER(partition_tbl);
+
+/* next int available for indexing the hrPartitionTable */
+static uint32_t next_partition_index = 1;
+
+/*
+ * Partition_entry_cmp is used for INSERT_OBJECT_FUNC_LINK
+ * macro.
+ */
+static int
+partition_entry_cmp(const struct partition_entry *a,
+ const struct partition_entry *b)
+{
+ assert(a != NULL);
+ assert(b != NULL);
+
+ if (a->index[0] < b->index[0])
+ return (-1);
+
+ if (a->index[0] > b->index[0])
+ return (+1);
+
+ if (a->index[1] < b->index[1])
+ return (-1);
+
+ if (a->index[1] > b->index[1])
+ return (+1);
+
+ return (0);
+}
+
+/*
+ * Partition_idx_cmp is used for NEXT_OBJECT_FUNC and FIND_OBJECT_FUNC
+ * macros
+ */
+static int
+partition_idx_cmp(const struct asn_oid *oid, u_int sub,
+ const struct partition_entry *entry)
+{
+ u_int i;
+
+ for (i = 0; i < 2 && i < oid->len - sub; i++) {
+ if (oid->subs[sub + i] < entry->index[i])
+ return (-1);
+ if (oid->subs[sub + i] > entry->index[i])
+ return (+1);
+ }
+ if (oid->len - sub < 2)
+ return (-1);
+ if (oid->len - sub > 2)
+ return (+1);
+
+ return (0);
+}
+
+/**
+ * Create a new partition table entry
+ */
+static struct partition_entry *
+partition_entry_create(int32_t ds_index, const char *chunk_name)
+{
+ struct partition_entry *entry;
+ struct partition_map_entry *map;
+ size_t id_len;
+
+ /* sanity checks */
+ assert(chunk_name != NULL);
+ if (chunk_name == NULL || chunk_name[0] == '\0')
+ return (NULL);
+
+ /* check whether we already have seen this partition */
+ STAILQ_FOREACH(map, &partition_map, link)
+ if (strcmp(map->id, chunk_name) == 0)
+ break;
+
+ if (map == NULL) {
+ /* new object - get a new index and create a map */
+
+ if (next_partition_index > INT_MAX) {
+ /* Unrecoverable error - die clean and quicly*/
+ syslog(LOG_ERR, "%s: hrPartitionTable index wrap",
+ __func__);
+ errx(EX_SOFTWARE, "hrPartitionTable index wrap");
+ }
+
+ if ((map = malloc(sizeof(*map))) == NULL) {
+ syslog(LOG_ERR, "hrPartitionTable: %s: %m", __func__);
+ return (NULL);
+ }
+
+ id_len = strlen(chunk_name) + 1;
+ if (id_len > PART_STR_MLEN)
+ id_len = PART_STR_MLEN;
+
+ if ((map->id = malloc(id_len)) == NULL) {
+ free(map);
+ return (NULL);
+ }
+
+ map->index = next_partition_index++;
+
+ strlcpy(map->id, chunk_name, id_len);
+
+ map->entry = NULL;
+
+ STAILQ_INSERT_TAIL(&partition_map, map, link);
+
+ HRDBG("%s added into hrPartitionMap at index=%d",
+ chunk_name, map->index);
+
+ } else {
+ HRDBG("%s exists in hrPartitionMap index=%d",
+ chunk_name, map->index);
+ }
+
+ if ((entry = malloc(sizeof(*entry))) == NULL) {
+ syslog(LOG_WARNING, "hrPartitionTable: %s: %m", __func__);
+ return (NULL);
+ }
+ memset(entry, 0, sizeof(*entry));
+
+ /* create the index */
+ entry->index[0] = ds_index;
+ entry->index[1] = map->index;
+
+ map->entry = entry;
+
+ if ((entry->id = strdup(map->id)) == NULL) {
+ free(entry);
+ return (NULL);
+ }
+
+ /*
+ * reuse id_len from here till the end of this function
+ * for partition_entry::label
+ */
+ id_len = strlen(_PATH_DEV) + strlen(chunk_name) + 1;
+
+ if (id_len > PART_STR_MLEN)
+ id_len = PART_STR_MLEN;
+
+ if ((entry->label = malloc(id_len )) == NULL) {
+ free(entry->id);
+ free(entry);
+ return (NULL);
+ }
+
+ snprintf(entry->label, id_len, "%s%s", _PATH_DEV, chunk_name);
+
+ INSERT_OBJECT_FUNC_LINK(entry, &partition_tbl, link,
+ partition_entry_cmp);
+
+ return (entry);
+}
+
+/**
+ * Delete a partition table entry but keep the map entry intact.
+ */
+static void
+partition_entry_delete(struct partition_entry *entry)
+{
+ struct partition_map_entry *map;
+
+ assert(entry != NULL);
+
+ TAILQ_REMOVE(&partition_tbl, entry, link);
+ STAILQ_FOREACH(map, &partition_map, link)
+ if (map->entry == entry) {
+ map->entry = NULL;
+ break;
+ }
+ free(entry->id);
+ free(entry->label);
+ free(entry);
+}
+
+/**
+ * Find a partition table entry by name. If none is found, return NULL.
+ */
+static struct partition_entry *
+partition_entry_find_by_name(const char *name)
+{
+ struct partition_entry *entry = NULL;
+
+ TAILQ_FOREACH(entry, &partition_tbl, link)
+ if (strcmp(entry->id, name) == 0)
+ return (entry);
+
+ return (NULL);
+}
+
+/**
+ * Find a partition table entry by label. If none is found, return NULL.
+ */
+static struct partition_entry *
+partition_entry_find_by_label(const char *name)
+{
+ struct partition_entry *entry = NULL;
+
+ TAILQ_FOREACH(entry, &partition_tbl, link)
+ if (strcmp(entry->label, name) == 0)
+ return (entry);
+
+ return (NULL);
+}
+
+/**
+ * Process a chunk from libgeom(4). A chunk is either a slice or a partition.
+ * If necessary create a new partition table entry for it. In any case
+ * set the size field of the entry and set the FOUND flag.
+ */
+static void
+handle_chunk(int32_t ds_index, const char *chunk_name, off_t chunk_size)
+{
+ struct partition_entry *entry;
+ daddr_t k_size;
+
+ assert(chunk_name != NULL);
+ assert(chunk_name[0] != '\0');
+ if (chunk_name == NULL || chunk_name == '\0')
+ return;
+
+ HRDBG("ANALYZE chunk %s", chunk_name);
+
+ if ((entry = partition_entry_find_by_name(chunk_name)) == NULL)
+ if ((entry = partition_entry_create(ds_index,
+ chunk_name)) == NULL)
+ return;
+
+ entry->flags |= HR_PARTITION_FOUND;
+
+ /* actual size may overflow the SNMP type */
+ k_size = chunk_size / 1024;
+ entry->size = (k_size > (off_t)INT_MAX ? INT_MAX : k_size);
+}
+
+/**
+ * Start refreshing the partition table. A call to this function will
+ * be followed by a call to handleDiskStorage() for every disk, followed
+ * by a single call to the post_refresh function.
+ */
+void
+partition_tbl_pre_refresh(void)
+{
+ struct partition_entry *entry;
+
+ /* mark each entry as missing */
+ TAILQ_FOREACH(entry, &partition_tbl, link)
+ entry->flags &= ~HR_PARTITION_FOUND;
+}
+
+/**
+ * Try to find a geom(4) class by its name. Returns a pointer to that
+ * class if found NULL otherways.
+ */
+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);
+}
+
+/**
+ * Process all MBR-type partitions from the given disk.
+ */
+static void
+get_mbr(struct gclass *classp, int32_t ds_index, const char *disk_dev_name)
+{
+ struct ggeom *gp;
+ struct gprovider *pp;
+ struct gconfig *conf;
+ long part_type;
+
+ LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
+ /* We are only interested in partitions from this disk */
+ if (strcmp(gp->lg_name, disk_dev_name) != 0)
+ continue;
+
+ /*
+ * Find all the non-BSD providers (these are handled in get_bsd)
+ */
+ LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
+ LIST_FOREACH(conf, &pp->lg_config, lg_config) {
+ if (conf->lg_name == NULL ||
+ conf->lg_val == NULL ||
+ strcmp(conf->lg_name, "type") != 0)
+ continue;
+
+ /*
+ * We are not interested in BSD partitions
+ * (ie ad0s1 is not interesting at this point).
+ * We'll take care of them in detail (slice
+ * by slice) in get_bsd.
+ */
+ part_type = strtol(conf->lg_val, NULL, 10);
+ if (part_type == HR_FREEBSD_PART_TYPE)
+ break;
+ HRDBG("-> MBR PROVIDER Name: %s", pp->lg_name);
+ HRDBG("Mediasize: %jd",
+ (intmax_t)pp->lg_mediasize / 1024);
+ HRDBG("Sectorsize: %u", pp->lg_sectorsize);
+ HRDBG("Mode: %s", pp->lg_mode);
+ HRDBG("CONFIG: %s: %s",
+ conf->lg_name, conf->lg_val);
+
+ handle_chunk(ds_index, pp->lg_name,
+ pp->lg_mediasize);
+ }
+ }
+ }
+}
+
+/**
+ * Process all BSD-type partitions from the given disk.
+ */
+static void
+get_bsd_sun(struct gclass *classp, int32_t ds_index, const char *disk_dev_name)
+{
+ struct ggeom *gp;
+ struct gprovider *pp;
+
+ LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
+ /*
+ * We are only interested in those geoms starting with
+ * the disk_dev_name passed as parameter to this function.
+ */
+ if (strncmp(gp->lg_name, disk_dev_name,
+ strlen(disk_dev_name)) != 0)
+ continue;
+
+ LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
+ if (pp->lg_name == NULL)
+ continue;
+ handle_chunk(ds_index, pp->lg_name, pp->lg_mediasize);
+ }
+ }
+}
+
+/**
+ * Called from the DiskStorage table for every row. Open the GEOM(4) framework
+ * and process all the partitions in it.
+ * ds_index is the index into the DiskStorage table.
+ * This is done in two steps: for non BSD partitions the geom class "MBR" is
+ * used, for our BSD slices the "BSD" geom class.
+ */
+void
+partition_tbl_handle_disk(int32_t ds_index, const char *disk_dev_name)
+{
+ struct gmesh mesh; /* GEOM userland tree */
+ struct gclass *classp;
+ int error;
+
+ assert(disk_dev_name != NULL);
+ assert(ds_index > 0);
+
+ HRDBG("===> getting partitions for %s <===", disk_dev_name);
+
+ /* try to construct the GEOM tree */
+ if ((error = geom_gettree(&mesh)) != 0) {
+ syslog(LOG_WARNING, "cannot get GEOM tree: %m");
+ return;
+ }
+
+ /*
+ * First try the GEOM "MBR" class.
+ * This is needed for non-BSD slices (aka partitions)
+ * on PC architectures.
+ */
+ if ((classp = find_class(&mesh, "MBR")) != NULL) {
+ get_mbr(classp, ds_index, disk_dev_name);
+ } else {
+ HRDBG("cannot find \"MBR\" geom class");
+ }
+
+ /*
+ * Get the "BSD" GEOM class.
+ * Here we'll find all the info needed about the BSD slices.
+ */
+ if ((classp = find_class(&mesh, "BSD")) != NULL) {
+ get_bsd_sun(classp, ds_index, disk_dev_name);
+ } else {
+ /* no problem on sparc64 */
+ HRDBG("cannot find \"BSD\" geom class");
+ }
+
+ /*
+ * Get the "SUN" GEOM class.
+ * Here we'll find all the info needed about the BSD slices.
+ */
+ if ((classp = find_class(&mesh, "SUN")) != NULL) {
+ get_bsd_sun(classp, ds_index, disk_dev_name);
+ } else {
+ /* no problem on i386 */
+ HRDBG("cannot find \"SUN\" geom class");
+ }
+
+ geom_deletetree(&mesh);
+}
+
+/**
+ * Finish refreshing the table.
+ */
+void
+partition_tbl_post_refresh(void)
+{
+ struct partition_entry *e, *etmp;
+
+ /*
+ * Purge items that disappeared
+ */
+ TAILQ_FOREACH_SAFE(e, &partition_tbl, link, etmp)
+ if (!(e->flags & HR_PARTITION_FOUND))
+ partition_entry_delete(e);
+}
+
+/*
+ * Finalization routine for hrPartitionTable
+ * It destroys the lists and frees any allocated heap memory
+ */
+void
+fini_partition_tbl(void)
+{
+ struct partition_map_entry *m;
+
+ while ((m = STAILQ_FIRST(&partition_map)) != NULL) {
+ STAILQ_REMOVE_HEAD(&partition_map, link);
+ if(m->entry != NULL) {
+ TAILQ_REMOVE(&partition_tbl, m->entry, link);
+ free(m->entry->id);
+ free(m->entry->label);
+ free(m->entry);
+ }
+ free(m->id);
+ free(m);
+ }
+ assert(TAILQ_EMPTY(&partition_tbl));
+}
+
+/**
+ * Called from the file system code to insert the file system table index
+ * into the partition table entry. Note, that an partition table entry exists
+ * only for local file systems.
+ */
+void
+handle_partition_fs_index(const char *name, int32_t fs_idx)
+{
+ struct partition_entry *entry;
+
+ if ((entry = partition_entry_find_by_label(name)) == NULL) {
+ HRDBG("%s IS MISSING from hrPartitionTable", name);
+ return;
+ }
+ HRDBG("%s [FS index = %d] IS in hrPartitionTable", name, fs_idx);
+ entry->fs_Index = fs_idx;
+}
+
+/*
+ * This is the implementation for a generated (by our SNMP tool)
+ * function prototype, see hostres_tree.h
+ * It handles the SNMP operations for hrPartitionTable
+ */
+int
+op_hrPartitionTable(struct snmp_context *ctx __unused, struct snmp_value *value,
+ u_int sub, u_int iidx __unused, enum snmp_op op)
+{
+ struct partition_entry *entry;
+
+ /*
+ * Refresh the disk storage table (which refreshes the partition
+ * table) if necessary.
+ */
+ refresh_disk_storage_tbl(0);
+
+ switch (op) {
+
+ case SNMP_OP_GETNEXT:
+ if ((entry = NEXT_OBJECT_FUNC(&partition_tbl,
+ &value->var, sub, partition_idx_cmp)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ value->var.len = sub + 2;
+ value->var.subs[sub] = entry->index[0];
+ value->var.subs[sub + 1] = entry->index[1];
+
+ goto get;
+
+ case SNMP_OP_GET:
+ if ((entry = FIND_OBJECT_FUNC(&partition_tbl,
+ &value->var, sub, partition_idx_cmp)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_SET:
+ if ((entry = FIND_OBJECT_FUNC(&partition_tbl,
+ &value->var, sub, partition_idx_cmp)) == NULL)
+ return (SNMP_ERR_NOT_WRITEABLE);
+ return (SNMP_ERR_NO_CREATION);
+
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ abort();
+ }
+ abort();
+
+ get:
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_hrPartitionIndex:
+ value->v.integer = entry->index[1];
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrPartitionLabel:
+ return (string_get(value, entry->label, -1));
+
+ case LEAF_hrPartitionID:
+ return(string_get(value, entry->id, -1));
+
+ case LEAF_hrPartitionSize:
+ value->v.integer = entry->size;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrPartitionFSIndex:
+ value->v.integer = entry->fs_Index;
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+}
diff --git a/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_printer_tbl.c b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_printer_tbl.c
new file mode 100644
index 0000000..33a54b5
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_printer_tbl.c
@@ -0,0 +1,398 @@
+/*-
+ * Copyright (c) 2005-2006 The FreeBSD Project
+ * All rights reserved.
+ *
+ * Author: Victor Cruceru <soc-victor@freebsd.org>
+ *
+ * Redistribution of this software and documentation and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ *
+ * 1. Redistributions of source code or documentation must retain the above
+ * copyright notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+/*
+ * Host Resources MIB implementation for SNMPd: instrumentation for
+ * hrPrinterTable
+ */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <paths.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "hostres_snmp.h"
+#include "hostres_oid.h"
+#include "hostres_tree.h"
+
+#include <sys/dirent.h>
+#include "lp.h"
+
+/* Constants */
+static const struct asn_oid OIDX_hrDevicePrinter_c = OIDX_hrDevicePrinter;
+
+enum PrinterStatus {
+ PS_OTHER = 1,
+ PS_UNKNOWN = 2,
+ PS_IDLE = 3,
+ PS_PRINTING = 4,
+ PS_WARMUP = 5
+};
+
+/*
+ * This structure is used to hold a SNMP table entry
+ * for HOST-RESOURCES-MIB's hrPrinterTable.
+ */
+struct printer_entry {
+ int32_t index;
+ int32_t status; /* values from PrinterStatus enum above */
+ u_char detectedErrorState[2];
+ TAILQ_ENTRY(printer_entry) link;
+#define HR_PRINTER_FOUND 0x001
+ uint32_t flags;
+
+};
+TAILQ_HEAD(printer_tbl, printer_entry);
+
+/* the hrPrinterTable */
+static struct printer_tbl printer_tbl = TAILQ_HEAD_INITIALIZER(printer_tbl);
+
+/* last (agent) tick when hrPrinterTable was updated */
+static uint64_t printer_tick;
+
+/**
+ * Create entry into the printer table.
+ */
+static struct printer_entry *
+printer_entry_create(const struct device_entry *devEntry)
+{
+ struct printer_entry *entry = NULL;
+
+ assert(devEntry != NULL);
+ if (devEntry == NULL)
+ return (NULL);
+
+ if ((entry = malloc(sizeof(*entry))) == NULL) {
+ syslog(LOG_WARNING, "hrPrinterTable: %s: %m", __func__);
+ return (NULL);
+ }
+ memset(entry, 0, sizeof(*entry));
+ entry->index = devEntry->index;
+ INSERT_OBJECT_INT(entry, &printer_tbl);
+ return (entry);
+}
+
+/**
+ * Delete entry from the printer table.
+ */
+static void
+printer_entry_delete(struct printer_entry *entry)
+{
+
+ assert(entry != NULL);
+ if (entry == NULL)
+ return;
+
+ TAILQ_REMOVE(&printer_tbl, entry, link);
+ free(entry);
+}
+
+/**
+ * Find a printer by its index
+ */
+static struct printer_entry *
+printer_find_by_index(int32_t idx)
+{
+ struct printer_entry *entry;
+
+ TAILQ_FOREACH(entry, &printer_tbl, link)
+ if (entry->index == idx)
+ return (entry);
+
+ return (NULL);
+}
+
+/**
+ * Get the status of a printer
+ */
+static enum PrinterStatus
+get_printer_status(const struct printer *pp)
+{
+ char statfile[MAXPATHLEN];
+ char lockfile[MAXPATHLEN];
+ char fline[128];
+ int fd;
+ FILE *f = NULL;
+ enum PrinterStatus ps = PS_UNKNOWN;
+
+ if (pp->lock_file[0] == '/')
+ strlcpy(lockfile, pp->lock_file, sizeof(lockfile));
+ else
+ snprintf(lockfile, sizeof(lockfile), "%s/%s",
+ pp->spool_dir, pp->lock_file);
+
+ fd = open(lockfile, O_RDONLY);
+ if (fd < 0 || flock(fd, LOCK_SH | LOCK_NB) == 0) {
+ ps = PS_IDLE;
+ goto LABEL_DONE;
+ }
+
+ if (pp->status_file[0] == '/')
+ strlcpy(statfile, pp->status_file, sizeof(statfile));
+ else
+ snprintf(statfile, sizeof(statfile), "%s/%s",
+ pp->spool_dir, pp->status_file);
+
+ f = fopen(statfile, "r");
+ if (f == NULL) {
+ syslog(LOG_ERR, "cannot open status file: %s", strerror(errno));
+ ps = PS_UNKNOWN;
+ goto LABEL_DONE;
+ }
+
+ memset(&fline[0], '\0', sizeof(fline));
+ if (fgets(fline, sizeof(fline) -1, f) == NULL) {
+ ps = PS_UNKNOWN;
+ goto LABEL_DONE;
+ }
+
+ if (strstr(fline, "is ready and printing") != NULL) {
+ ps = PS_PRINTING;
+ goto LABEL_DONE;
+ }
+
+ if (strstr(fline, "to become ready (offline?)") != NULL) {
+ ps = PS_OTHER;
+ goto LABEL_DONE;
+ }
+
+LABEL_DONE:
+ if (fd >= 0)
+ (void)close(fd); /* unlocks as well */
+
+ if (f != NULL)
+ (void)fclose(f);
+
+ return (ps);
+}
+
+/**
+ * Called for each printer found in /etc/printcap.
+ */
+static void
+handle_printer(struct printer *pp)
+{
+ struct device_entry *dev_entry;
+ struct printer_entry *printer_entry;
+ char dev_only[128];
+ struct stat sb;
+
+ if (pp->remote_host != NULL) {
+ HRDBG("skipped %s -- remote", pp->printer);
+ return;
+ }
+
+ if (strncmp(pp->lp, _PATH_DEV, strlen(_PATH_DEV)) != 0) {
+ HRDBG("skipped %s [device %s] -- remote", pp->printer, pp->lp);
+ return;
+ }
+
+ memset(dev_only, '\0', sizeof(dev_only));
+ snprintf(dev_only, sizeof(dev_only), "%s", pp->lp + strlen(_PATH_DEV));
+
+ HRDBG("printer %s has device %s", pp->printer, dev_only);
+
+ if (stat(pp->lp, &sb) < 0) {
+ if (errno == ENOENT) {
+ HRDBG("skipped %s -- device %s missing",
+ pp->printer, pp->lp);
+ return;
+ }
+ }
+
+ if ((dev_entry = device_find_by_name(dev_only)) == NULL) {
+ HRDBG("%s not in hrDeviceTable", pp->lp);
+ return;
+ }
+ HRDBG("%s found in hrDeviceTable", pp->lp);
+ dev_entry->type = &OIDX_hrDevicePrinter_c;
+
+ dev_entry->flags |= HR_DEVICE_IMMUTABLE;
+
+ /* Then check hrPrinterTable for this device */
+ if ((printer_entry = printer_find_by_index(dev_entry->index)) == NULL &&
+ (printer_entry = printer_entry_create(dev_entry)) == NULL)
+ return;
+
+ printer_entry->flags |= HR_PRINTER_FOUND;
+ printer_entry->status = get_printer_status(pp);
+ memset(printer_entry->detectedErrorState, 0,
+ sizeof(printer_entry->detectedErrorState));
+}
+
+static void
+hrPrinter_get_OS_entries(void)
+{
+ int status, more;
+ struct printer myprinter, *pp = &myprinter;
+
+ init_printer(pp);
+ HRDBG("---->Getting printers .....");
+ more = firstprinter(pp, &status);
+ if (status)
+ goto errloop;
+
+ while (more) {
+ do {
+ HRDBG("---->Got printer %s", pp->printer);
+
+ handle_printer(pp);
+ more = nextprinter(pp, &status);
+errloop:
+ if (status)
+ syslog(LOG_WARNING,
+ "hrPrinterTable: printcap entry for %s "
+ "has errors, skipping",
+ pp->printer ? pp->printer : "<noname?>");
+ } while (more && status);
+ }
+
+ lastprinter();
+ printer_tick = this_tick;
+}
+
+/**
+ * Init the things for hrPrinterTable
+ */
+void
+init_printer_tbl(void)
+{
+
+ hrPrinter_get_OS_entries();
+}
+
+/**
+ * Finalization routine for hrPrinterTable
+ * It destroys the lists and frees any allocated heap memory
+ */
+void
+fini_printer_tbl(void)
+{
+ struct printer_entry *n1;
+
+ while ((n1 = TAILQ_FIRST(&printer_tbl)) != NULL) {
+ TAILQ_REMOVE(&printer_tbl, n1, link);
+ free(n1);
+ }
+}
+
+/**
+ * Refresh the printer table if needed.
+ */
+void
+refresh_printer_tbl(void)
+{
+ struct printer_entry *entry;
+ struct printer_entry *entry_tmp;
+
+ if (this_tick <= printer_tick) {
+ HRDBG("no refresh needed");
+ return;
+ }
+
+ /* mark each entry as missing */
+ TAILQ_FOREACH(entry, &printer_tbl, link)
+ entry->flags &= ~HR_PRINTER_FOUND;
+
+ hrPrinter_get_OS_entries();
+
+ /*
+ * Purge items that disappeared
+ */
+ entry = TAILQ_FIRST(&printer_tbl);
+ while (entry != NULL) {
+ entry_tmp = TAILQ_NEXT(entry, link);
+ if (!(entry->flags & HR_PRINTER_FOUND))
+ printer_entry_delete(entry);
+ entry = entry_tmp;
+ }
+
+ printer_tick = this_tick;
+
+ HRDBG("refresh DONE ");
+}
+
+int
+op_hrPrinterTable(struct snmp_context *ctx __unused, struct snmp_value *value,
+ u_int sub, u_int iidx __unused, enum snmp_op curr_op)
+{
+ struct printer_entry *entry;
+
+ refresh_printer_tbl();
+
+ switch (curr_op) {
+
+ case SNMP_OP_GETNEXT:
+ if ((entry = NEXT_OBJECT_INT(&printer_tbl, &value->var,
+ sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ value->var.len = sub + 1;
+ value->var.subs[sub] = entry->index;
+ goto get;
+
+ case SNMP_OP_GET:
+ if ((entry = FIND_OBJECT_INT(&printer_tbl, &value->var,
+ sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_SET:
+ if ((entry = FIND_OBJECT_INT(&printer_tbl, &value->var,
+ sub)) == NULL)
+ return (SNMP_ERR_NO_CREATION);
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ abort();
+ }
+ abort();
+
+ get:
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_hrPrinterStatus:
+ value->v.integer = entry->status;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrPrinterDetectedErrorState:
+ return (string_get(value, entry->detectedErrorState,
+ sizeof(entry->detectedErrorState)));
+ }
+ abort();
+}
diff --git a/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_processor_tbl.c b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_processor_tbl.c
new file mode 100644
index 0000000..e75b55e
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_processor_tbl.c
@@ -0,0 +1,431 @@
+/*-
+ * Copyright (c) 2005-2006 The FreeBSD Project
+ * All rights reserved.
+ *
+ * Author: Victor Cruceru <soc-victor@freebsd.org>
+ *
+ * Redistribution of this software and documentation and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ *
+ * 1. Redistributions of source code or documentation must retain the above
+ * copyright notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+/*
+ * Host Resources MIB for SNMPd. Implementation for hrProcessorTable
+ */
+
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <sys/user.h>
+
+#include <assert.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "hostres_snmp.h"
+#include "hostres_oid.h"
+#include "hostres_tree.h"
+
+/*
+ * This structure is used to hold a SNMP table entry
+ * for HOST-RESOURCES-MIB's hrProcessorTable.
+ * Note that index is external being allocated & maintained
+ * by the hrDeviceTable code..
+ */
+struct processor_entry {
+ int32_t index;
+ const struct asn_oid *frwId;
+ int32_t load; /* average cpu usage */
+ int32_t sample_cnt; /* number of usage samples */
+ int32_t cur_sample_idx; /* current valid sample */
+ TAILQ_ENTRY(processor_entry) link;
+ u_char cpu_no; /* which cpu, counted from 0 */
+
+ /* the samples from the last minute, as required by MIB */
+ double samples[MAX_CPU_SAMPLES];
+ long states[MAX_CPU_SAMPLES][CPUSTATES];
+};
+TAILQ_HEAD(processor_tbl, processor_entry);
+
+/* the head of the list with hrDeviceTable's entries */
+static struct processor_tbl processor_tbl =
+ TAILQ_HEAD_INITIALIZER(processor_tbl);
+
+/* number of processors in dev tbl */
+static int32_t detected_processor_count;
+
+/* sysctlbyname(hw.ncpu) */
+static int hw_ncpu;
+
+/* sysctlbyname(kern.cp_times) */
+static int cpmib[2];
+static size_t cplen;
+
+/* periodic timer used to get cpu load stats */
+static void *cpus_load_timer;
+
+/**
+ * Returns the CPU usage of a given processor entry.
+ *
+ * It needs at least two cp_times "tick" samples to calculate a delta and
+ * thus, the usage over the sampling period.
+ */
+static int
+get_avg_load(struct processor_entry *e)
+{
+ u_int i, oldest;
+ long delta = 0;
+ double usage = 0.0;
+
+ assert(e != NULL);
+
+ /* Need two samples to perform delta calculation. */
+ if (e->sample_cnt <= 1)
+ return (0);
+
+ /* Oldest usable index, we wrap around. */
+ if (e->sample_cnt == MAX_CPU_SAMPLES)
+ oldest = (e->cur_sample_idx + 1) % MAX_CPU_SAMPLES;
+ else
+ oldest = 0;
+
+ /* Sum delta for all states. */
+ for (i = 0; i < CPUSTATES; i++) {
+ delta += e->states[e->cur_sample_idx][i];
+ delta -= e->states[oldest][i];
+ }
+ if (delta == 0)
+ return 0;
+
+ /* Take idle time from the last element and convert to
+ * percent usage by contrasting with total ticks delta. */
+ usage = (double)(e->states[e->cur_sample_idx][CPUSTATES-1] -
+ e->states[oldest][CPUSTATES-1]) / delta;
+ usage = 100 - (usage * 100);
+ HRDBG("CPU no. %d, delta ticks %ld, pct usage %.2f", e->cpu_no,
+ delta, usage);
+
+ return ((int)(usage));
+}
+
+/**
+ * Save a new sample to proc entry and get the average usage.
+ *
+ * Samples are stored in a ringbuffer from 0..(MAX_CPU_SAMPLES-1)
+ */
+static void
+save_sample(struct processor_entry *e, long *cp_times)
+{
+ int i;
+
+ e->cur_sample_idx = (e->cur_sample_idx + 1) % MAX_CPU_SAMPLES;
+ for (i = 0; cp_times != NULL && i < CPUSTATES; i++)
+ e->states[e->cur_sample_idx][i] = cp_times[i];
+
+ e->sample_cnt++;
+ if (e->sample_cnt > MAX_CPU_SAMPLES)
+ e->sample_cnt = MAX_CPU_SAMPLES;
+
+ HRDBG("sample count for CPU no. %d went to %d", e->cpu_no, e->sample_cnt);
+ e->load = get_avg_load(e);
+
+}
+
+/**
+ * Create a new entry into the processor table.
+ */
+static struct processor_entry *
+proc_create_entry(u_int cpu_no, struct device_map_entry *map)
+{
+ struct device_entry *dev;
+ struct processor_entry *entry;
+ char name[128];
+
+ /*
+ * If there is no map entry create one by creating a device table
+ * entry.
+ */
+ if (map == NULL) {
+ snprintf(name, sizeof(name), "cpu%u", cpu_no);
+ if ((dev = device_entry_create(name, "", "")) == NULL)
+ return (NULL);
+ dev->flags |= HR_DEVICE_IMMUTABLE;
+ STAILQ_FOREACH(map, &device_map, link)
+ if (strcmp(map->name_key, name) == 0)
+ break;
+ if (map == NULL)
+ abort();
+ }
+
+ if ((entry = malloc(sizeof(*entry))) == NULL) {
+ syslog(LOG_ERR, "hrProcessorTable: %s malloc "
+ "failed: %m", __func__);
+ return (NULL);
+ }
+ memset(entry, 0, sizeof(*entry));
+
+ entry->index = map->hrIndex;
+ entry->load = 0;
+ entry->sample_cnt = 0;
+ entry->cur_sample_idx = -1;
+ entry->cpu_no = (u_char)cpu_no;
+ entry->frwId = &oid_zeroDotZero; /* unknown id FIXME */
+
+ INSERT_OBJECT_INT(entry, &processor_tbl);
+
+ HRDBG("CPU %d added with SNMP index=%d",
+ entry->cpu_no, entry->index);
+
+ return (entry);
+}
+
+/**
+ * Scan the device map table for CPUs and create an entry into the
+ * processor table for each CPU.
+ *
+ * Make sure that the number of processors announced by the kernel hw.ncpu
+ * is equal to the number of processors we have found in the device table.
+ */
+static void
+create_proc_table(void)
+{
+ struct device_map_entry *map;
+ struct processor_entry *entry;
+ int cpu_no;
+ size_t len;
+
+ detected_processor_count = 0;
+
+ /*
+ * Because hrProcessorTable depends on hrDeviceTable,
+ * the device detection must be performed at this point.
+ * If not, no entries will be present in the hrProcessor Table.
+ *
+ * For non-ACPI system the processors are not in the device table,
+ * therefore insert them after checking hw.ncpu.
+ */
+ STAILQ_FOREACH(map, &device_map, link)
+ if (strncmp(map->name_key, "cpu", strlen("cpu")) == 0 &&
+ strstr(map->location_key, ".CPU") != NULL) {
+ if (sscanf(map->name_key,"cpu%d", &cpu_no) != 1) {
+ syslog(LOG_ERR, "hrProcessorTable: Failed to "
+ "get cpu no. from device named '%s'",
+ map->name_key);
+ continue;
+ }
+
+ if ((entry = proc_create_entry(cpu_no, map)) == NULL)
+ continue;
+
+ detected_processor_count++;
+ }
+
+ len = sizeof(hw_ncpu);
+ if (sysctlbyname("hw.ncpu", &hw_ncpu, &len, NULL, 0) == -1 ||
+ len != sizeof(hw_ncpu)) {
+ syslog(LOG_ERR, "hrProcessorTable: sysctl(hw.ncpu) failed");
+ hw_ncpu = 0;
+ }
+
+ HRDBG("%d CPUs detected via device table; hw.ncpu is %d",
+ detected_processor_count, hw_ncpu);
+
+ /* XXX Can happen on non-ACPI systems? Create entries by hand. */
+ for (; detected_processor_count < hw_ncpu; detected_processor_count++)
+ proc_create_entry(detected_processor_count, NULL);
+
+ len = 2;
+ if (sysctlnametomib("kern.cp_times", cpmib, &len)) {
+ syslog(LOG_ERR, "hrProcessorTable: sysctlnametomib(kern.cp_times) failed");
+ cpmib[0] = 0;
+ cpmib[1] = 0;
+ cplen = 0;
+ } else if (sysctl(cpmib, 2, NULL, &len, NULL, 0)) {
+ syslog(LOG_ERR, "hrProcessorTable: sysctl(kern.cp_times) length query failed");
+ cplen = 0;
+ } else {
+ cplen = len / sizeof(long);
+ }
+ HRDBG("%zu entries for kern.cp_times", cplen);
+
+}
+
+/**
+ * Free the processor table
+ */
+static void
+free_proc_table(void)
+{
+ struct processor_entry *n1;
+
+ while ((n1 = TAILQ_FIRST(&processor_tbl)) != NULL) {
+ TAILQ_REMOVE(&processor_tbl, n1, link);
+ free(n1);
+ detected_processor_count--;
+ }
+
+ assert(detected_processor_count == 0);
+ detected_processor_count = 0;
+}
+
+/**
+ * Refresh all values in the processor table. We call this once for
+ * every PDU that accesses the table.
+ */
+static void
+refresh_processor_tbl(void)
+{
+ struct processor_entry *entry;
+ size_t size;
+
+ long pcpu_cp_times[cplen];
+ memset(pcpu_cp_times, 0, sizeof(pcpu_cp_times));
+
+ size = cplen * sizeof(long);
+ if (sysctl(cpmib, 2, pcpu_cp_times, &size, NULL, 0) == -1 &&
+ !(errno == ENOMEM && size >= cplen * sizeof(long))) {
+ syslog(LOG_ERR, "hrProcessorTable: sysctl(kern.cp_times) failed");
+ return;
+ }
+
+ TAILQ_FOREACH(entry, &processor_tbl, link) {
+ assert(hr_kd != NULL);
+ save_sample(entry, &pcpu_cp_times[entry->cpu_no * CPUSTATES]);
+ }
+
+}
+
+/**
+ * This function is called MAX_CPU_SAMPLES times per minute to collect the
+ * CPU load.
+ */
+static void
+get_cpus_samples(void *arg __unused)
+{
+
+ HRDBG("[%llu] ENTER", (unsigned long long)get_ticks());
+ refresh_processor_tbl();
+ HRDBG("[%llu] EXIT", (unsigned long long)get_ticks());
+}
+
+/**
+ * Called to start this table. We need to start the periodic idle
+ * time collection.
+ */
+void
+start_processor_tbl(struct lmodule *mod)
+{
+
+ /*
+ * Start the cpu stats collector
+ * The semantics of timer_start parameters is in "SNMP ticks";
+ * we have 100 "SNMP ticks" per second, thus we are trying below
+ * to get MAX_CPU_SAMPLES per minute
+ */
+ cpus_load_timer = timer_start_repeat(100, 100 * 60 / MAX_CPU_SAMPLES,
+ get_cpus_samples, NULL, mod);
+}
+
+/**
+ * Init the things for hrProcessorTable.
+ * Scan the device table for processor entries.
+ */
+void
+init_processor_tbl(void)
+{
+
+ /* create the initial processor table */
+ create_proc_table();
+ /* and get first samples */
+ refresh_processor_tbl();
+}
+
+/**
+ * Finalization routine for hrProcessorTable.
+ * It destroys the lists and frees any allocated heap memory.
+ */
+void
+fini_processor_tbl(void)
+{
+
+ if (cpus_load_timer != NULL) {
+ timer_stop(cpus_load_timer);
+ cpus_load_timer = NULL;
+ }
+
+ free_proc_table();
+}
+
+/**
+ * Access routine for the processor table.
+ */
+int
+op_hrProcessorTable(struct snmp_context *ctx __unused,
+ struct snmp_value *value, u_int sub, u_int iidx __unused,
+ enum snmp_op curr_op)
+{
+ struct processor_entry *entry;
+
+ switch (curr_op) {
+
+ case SNMP_OP_GETNEXT:
+ if ((entry = NEXT_OBJECT_INT(&processor_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ value->var.len = sub + 1;
+ value->var.subs[sub] = entry->index;
+ goto get;
+
+ case SNMP_OP_GET:
+ if ((entry = FIND_OBJECT_INT(&processor_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_SET:
+ if ((entry = FIND_OBJECT_INT(&processor_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NO_CREATION);
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ abort();
+ }
+ abort();
+
+ get:
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_hrProcessorFrwID:
+ assert(entry->frwId != NULL);
+ value->v.oid = *entry->frwId;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrProcessorLoad:
+ value->v.integer = entry->load;
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+}
diff --git a/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_scalars.c b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_scalars.c
new file mode 100644
index 0000000..ee7d4b1
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_scalars.c
@@ -0,0 +1,492 @@
+/*-
+ * Copyright (c) 2005-2006 The FreeBSD Project
+ * All rights reserved.
+ *
+ * Author: Victor Cruceru <soc-victor@freebsd.org>
+ *
+ * Redistribution of this software and documentation and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ *
+ * 1. Redistributions of source code or documentation must retain the above
+ * copyright notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+/*
+ * Host Resources MIB scalars implementation for SNMPd.
+ */
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+
+#include <pwd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <syslog.h>
+#include <utmpx.h>
+
+#include "hostres_snmp.h"
+#include "hostres_oid.h"
+#include "hostres_tree.h"
+
+/* boot timestamp in centi-seconds */
+static uint64_t kernel_boot;
+
+/* physical memory size in Kb */
+static uint64_t phys_mem_size;
+
+/* boot line (malloced) */
+static u_char *boot_line;
+
+/* maximum number of processes */
+static uint32_t max_proc;
+
+/**
+ * Free all static data
+ */
+void
+fini_scalars(void)
+{
+
+ free(boot_line);
+}
+
+/**
+ * Get system uptime in hundredths of seconds since the epoch
+ * Returns 0 in case of an error
+ */
+static int
+OS_getSystemUptime(uint32_t *ut)
+{
+ struct timeval right_now;
+ uint64_t now;
+
+ if (kernel_boot == 0) {
+ /* first time, do the sysctl */
+ struct timeval kernel_boot_timestamp;
+ int mib[2] = { CTL_KERN, KERN_BOOTTIME };
+ size_t len = sizeof(kernel_boot_timestamp);
+
+ if (sysctl(mib, 2, &kernel_boot_timestamp,
+ &len, NULL, 0) == -1) {
+ syslog(LOG_ERR, "sysctl KERN_BOOTTIME failed: %m");
+ return (SNMP_ERR_GENERR);
+ }
+
+ HRDBG("boot timestamp from kernel: {%lld, %ld}",
+ (long long)kernel_boot_timestamp.tv_sec,
+ (long)kernel_boot_timestamp.tv_usec);
+
+ kernel_boot = ((uint64_t)kernel_boot_timestamp.tv_sec * 100) +
+ (kernel_boot_timestamp.tv_usec / 10000);
+ }
+
+ if (gettimeofday(&right_now, NULL) < 0) {
+ syslog(LOG_ERR, "gettimeofday failed: %m");
+ return (SNMP_ERR_GENERR);
+ }
+ now = ((uint64_t)right_now.tv_sec * 100) + (right_now.tv_usec / 10000);
+
+ if (now - kernel_boot > UINT32_MAX)
+ *ut = UINT32_MAX;
+ else
+ *ut = now - kernel_boot;
+
+ return (SNMP_ERR_NOERROR);
+}
+
+/**
+ * Get system local date and time in a foramt suitable for DateAndTime TC:
+ * field octets contents range
+ * ----- ------ -------- -----
+ * 1 1-2 year* 0..65536
+ * 2 3 month 1..12
+ * 3 4 day 1..31
+ * 4 5 hour 0..23
+ * 5 6 minutes 0..59
+ * 6 7 seconds 0..60
+ * (use 60 for leap-second)
+ * 7 8 deci-seconds 0..9
+ * 8 9 direction from UTC '+' / '-'
+ * 9 10 hours from UTC* 0..13
+ * 10 11 minutes from UTC 0..59
+ *
+ * * Notes:
+ * - the value of year is in network-byte order
+ * - daylight saving time in New Zealand is +13
+ *
+ * For example, Tuesday May 26, 1992 at 1:30:15 PM EDT would be
+ * displayed as:
+ *
+ * 1992-5-26,13:30:15.0,-4:0
+ *
+ * Returns -1 in case of an error or the length of the string (8 or 11)
+ * Actually returns always 11 on freebsd
+ */
+static int
+OS_getSystemDate(struct snmp_value *value)
+{
+ u_char s_date_time[11];
+ struct tm tloc_tm;
+ time_t tloc_time_t;
+ struct timeval right_now;
+ int string_len;
+
+ if (gettimeofday(&right_now, NULL) < 0) {
+ syslog(LOG_ERR, "gettimeofday failed: %m");
+ return (SNMP_ERR_GENERR);
+ }
+
+ tloc_time_t = right_now.tv_sec;
+
+ if (localtime_r(&tloc_time_t, &tloc_tm) == NULL) {
+ syslog(LOG_ERR, "localtime_r() failed: %m ");
+ return (SNMP_ERR_GENERR);
+ }
+
+ string_len = make_date_time(s_date_time, &tloc_tm,
+ right_now.tv_usec / 100000);
+
+ return (string_get(value, s_date_time, string_len));
+}
+
+/**
+ * Get kernel boot path. For FreeBSD it seems that no arguments are
+ * present. Returns NULL if an error occurred. The returned data is a
+ * pointer to a global storage.
+ */
+int
+OS_getSystemInitialLoadParameters(u_char **params)
+{
+
+ if (boot_line == NULL) {
+ int mib[2] = { CTL_KERN, KERN_BOOTFILE };
+ char *buf;
+ size_t buf_len = 0;
+
+ /* get the needed buffer len */
+ if (sysctl(mib, 2, NULL, &buf_len, NULL, 0) != 0) {
+ syslog(LOG_ERR,
+ "sysctl({CTL_KERN,KERN_BOOTFILE}) failed: %m");
+ return (SNMP_ERR_GENERR);
+ }
+
+ if ((buf = malloc(buf_len)) == NULL) {
+ syslog(LOG_ERR, "malloc failed");
+ return (SNMP_ERR_GENERR);
+ }
+ if (sysctl(mib, 2, buf, &buf_len, NULL, 0)) {
+ syslog(LOG_ERR,
+ "sysctl({CTL_KERN,KERN_BOOTFILE}) failed: %m");
+ free(buf);
+ return (SNMP_ERR_GENERR);
+ }
+
+ boot_line = buf;
+ HRDBG("kernel boot file: %s", boot_line);
+ }
+
+ *params = boot_line;
+ return (SNMP_ERR_NOERROR);
+}
+
+/**
+ * Get number of current users which are logged in
+ */
+static int
+OS_getSystemNumUsers(uint32_t *nu)
+{
+ struct utmpx *utmp;
+
+ setutxent();
+ *nu = 0;
+ while ((utmp = getutxent()) != NULL) {
+ if (utmp->ut_type == USER_PROCESS)
+ (*nu)++;
+ }
+ endutxent();
+
+ return (SNMP_ERR_NOERROR);
+}
+
+/**
+ * Get number of current processes existing into the system
+ */
+static int
+OS_getSystemProcesses(uint32_t *proc_count)
+{
+ int pc;
+
+ if (hr_kd == NULL)
+ return (SNMP_ERR_GENERR);
+
+ if (kvm_getprocs(hr_kd, KERN_PROC_PROC, 0, &pc) == NULL) {
+ syslog(LOG_ERR, "kvm_getprocs failed: %m");
+ return (SNMP_ERR_GENERR);
+ }
+
+ *proc_count = pc;
+ return (SNMP_ERR_NOERROR);
+}
+
+/**
+ * Get maximum number of processes allowed on this system
+ */
+static int
+OS_getSystemMaxProcesses(uint32_t *mproc)
+{
+
+ if (max_proc == 0) {
+ int mib[2] = { CTL_KERN, KERN_MAXPROC };
+ int mp;
+ size_t len = sizeof(mp);
+
+ if (sysctl(mib, 2, &mp, &len, NULL, 0) == -1) {
+ syslog(LOG_ERR, "sysctl KERN_MAXPROC failed: %m");
+ return (SNMP_ERR_GENERR);
+ }
+ max_proc = mp;
+ }
+
+ *mproc = max_proc;
+ return (SNMP_ERR_NOERROR);
+}
+
+/*
+ * Get the physical memeory size in Kbytes.
+ * Returns SNMP error code.
+ */
+static int
+OS_getMemorySize(uint32_t *ms)
+{
+
+ if (phys_mem_size == 0) {
+ int mib[2] = { CTL_HW, HW_PHYSMEM };
+ u_long physmem;
+ size_t len = sizeof(physmem);
+
+ if (sysctl(mib, 2, &physmem, &len, NULL, 0) == -1) {
+ syslog(LOG_ERR,
+ "sysctl({ CTL_HW, HW_PHYSMEM }) failed: %m");
+ return (SNMP_ERR_GENERR);
+ }
+
+ phys_mem_size = physmem / 1024;
+ }
+
+ if (phys_mem_size > UINT32_MAX)
+ *ms = UINT32_MAX;
+ else
+ *ms = phys_mem_size;
+ return (SNMP_ERR_NOERROR);
+}
+
+/*
+ * Try to use the s_date_time parameter as a DateAndTime TC to fill in
+ * the second parameter.
+ * Returns 0 on succes and -1 for an error.
+ * Bug: time zone info is not used
+ */
+static struct timeval *
+OS_checkSystemDateInput(const u_char *str, u_int len)
+{
+ struct tm tm_to_set;
+ time_t t;
+ struct timeval *tv;
+
+ if (len != 8 && len != 11)
+ return (NULL);
+
+ if (str[2] == 0 || str[2] > 12 ||
+ str[3] == 0 || str[3] > 31 ||
+ str[4] > 23 || str[5] > 59 || str[6] > 60 || str[7] > 9)
+ return (NULL);
+
+ tm_to_set.tm_year = ((str[0] << 8) + str[1]) - 1900;
+ tm_to_set.tm_mon = str[2] - 1;
+ tm_to_set.tm_mday = str[3];
+ tm_to_set.tm_hour = str[4];
+ tm_to_set.tm_min = str[5];
+ tm_to_set.tm_sec = str[6];
+ tm_to_set.tm_isdst = 0;
+
+ /* now make UTC from it */
+ if ((t = timegm(&tm_to_set)) == (time_t)-1)
+ return (NULL);
+
+ /* now apply timezone if specified */
+ if (len == 11) {
+ if (str[9] > 13 || str[10] > 59)
+ return (NULL);
+ if (str[8] == '+')
+ t += 3600 * str[9] + 60 * str[10];
+ else
+ t -= 3600 * str[9] + 60 * str[10];
+ }
+
+ if ((tv = malloc(sizeof(*tv))) == NULL)
+ return (NULL);
+
+ tv->tv_sec = t;
+ tv->tv_usec = (int32_t)str[7] * 100000;
+
+ return (tv);
+}
+
+/*
+ * Set system date and time. Timezone is not changed
+ */
+static int
+OS_setSystemDate(const struct timeval *timeval_to_set)
+{
+ if (settimeofday(timeval_to_set, NULL) == -1) {
+ syslog(LOG_ERR, "settimeofday failed: %m");
+ return (SNMP_ERR_GENERR);
+ }
+ return (SNMP_ERR_NOERROR);
+}
+
+/*
+ * prototype of this function was genrated by gensnmptree tool in header file
+ * hostres_tree.h
+ * Returns SNMP_ERR_NOERROR on success
+ */
+int
+op_hrSystem(struct snmp_context *ctx, struct snmp_value *value,
+ u_int sub, u_int iidx __unused, enum snmp_op curr_op)
+{
+ int err;
+ u_char *str;
+
+ switch (curr_op) {
+
+ case SNMP_OP_GET:
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_hrSystemUptime:
+ return (OS_getSystemUptime(&value->v.uint32));
+
+ case LEAF_hrSystemDate:
+ return (OS_getSystemDate(value));
+
+ case LEAF_hrSystemInitialLoadDevice:
+ value->v.uint32 = 0; /* FIXME */
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrSystemInitialLoadParameters:
+ if ((err = OS_getSystemInitialLoadParameters(&str)) !=
+ SNMP_ERR_NOERROR)
+ return (err);
+ return (string_get(value, str, -1));
+
+ case LEAF_hrSystemNumUsers:
+ return (OS_getSystemNumUsers(&value->v.uint32));
+
+ case LEAF_hrSystemProcesses:
+ return (OS_getSystemProcesses(&value->v.uint32));
+
+ case LEAF_hrSystemMaxProcesses:
+ return (OS_getSystemMaxProcesses(&value->v.uint32));
+ }
+ abort();
+
+ case SNMP_OP_SET:
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_hrSystemDate:
+ if ((ctx->scratch->ptr1 =
+ OS_checkSystemDateInput(value->v.octetstring.octets,
+ value->v.octetstring.len)) == NULL)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrSystemInitialLoadDevice:
+ case LEAF_hrSystemInitialLoadParameters:
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ }
+ abort();
+
+ case SNMP_OP_ROLLBACK:
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_hrSystemDate:
+ free(ctx->scratch->ptr1);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrSystemInitialLoadDevice:
+ case LEAF_hrSystemInitialLoadParameters:
+ abort();
+ }
+ abort();
+
+ case SNMP_OP_COMMIT:
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_hrSystemDate:
+ (void)OS_setSystemDate(ctx->scratch->ptr1);
+ free(ctx->scratch->ptr1);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrSystemInitialLoadDevice:
+ case LEAF_hrSystemInitialLoadParameters:
+ abort();
+ }
+ abort();
+
+ case SNMP_OP_GETNEXT:
+ abort();
+ }
+ abort();
+}
+
+/*
+ * prototype of this function was genrated by gensnmptree tool
+ * in the header file hostres_tree.h
+ * Returns SNMP_ERR_NOERROR on success
+ */
+int
+op_hrStorage(struct snmp_context *ctx __unused, struct snmp_value *value,
+ u_int sub, u_int iidx __unused, enum snmp_op curr_op)
+{
+
+ /* only GET is possible */
+ switch (curr_op) {
+
+ case SNMP_OP_GET:
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_hrMemorySize:
+ return (OS_getMemorySize(&value->v.uint32));
+ }
+ abort();
+
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ case SNMP_OP_GETNEXT:
+ abort();
+ }
+ abort();
+}
diff --git a/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_snmp.c b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_snmp.c
new file mode 100644
index 0000000..26bd919
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_snmp.c
@@ -0,0 +1,208 @@
+/*-
+ * Copyright (c) 2005-2006 The FreeBSD Project
+ * All rights reserved.
+ *
+ * Author: Victor Cruceru <soc-victor@freebsd.org>
+ *
+ * Redistribution of this software and documentation and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ *
+ * 1. Redistributions of source code or documentation must retain the above
+ * copyright notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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.
+ *
+ * This C file contains code developed by Poul-Henning Kamp under the
+ * following license:
+ *
+ * FreeBSD: src/sbin/mdconfig/mdconfig.c,v 1.33.2.1 2004/09/14 03:32:21 jmg Exp
+ * ----------------------------------------------------------------------------
+ * "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$
+ */
+
+/*
+ * Host Resources MIB implementation for bsnmpd.
+ */
+
+#include <paths.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "hostres_snmp.h"
+#include "hostres_oid.h"
+#include "hostres_tree.h"
+
+/* Internal id got after we'll register this module with the agent */
+static u_int host_registration_id = 0;
+
+/* This our hostres module */
+static struct lmodule *hostres_module;
+
+/* See the generated file hostres_oid.h */
+static const struct asn_oid oid_host = OIDX_host;
+
+/* descriptor to access kernel memory */
+kvm_t *hr_kd;
+
+/*
+ * HOST RESOURCES mib module finalization hook.
+ * Returns 0 on success, < 0 on error
+ */
+static int
+hostres_fini(void)
+{
+
+ if (hr_kd != NULL)
+ (void)kvm_close(hr_kd);
+
+ fini_storage_tbl();
+ fini_fs_tbl();
+ fini_processor_tbl();
+ fini_disk_storage_tbl();
+ fini_device_tbl();
+ fini_partition_tbl();
+ fini_network_tbl();
+ fini_printer_tbl();
+
+ fini_swrun_tbl();
+ fini_swins_tbl();
+
+ fini_scalars();
+
+ if (host_registration_id > 0)
+ or_unregister(host_registration_id);
+
+ HRDBG("done.");
+ return (0);
+}
+
+/*
+ * HOST RESOURCES mib module initialization hook.
+ * Returns 0 on success, < 0 on error
+ */
+static int
+hostres_init(struct lmodule *mod, int argc __unused, char *argv[] __unused)
+{
+
+ hostres_module = mod;
+
+ /*
+ * NOTE: order of these calls is important here!
+ */
+ if ((hr_kd = kvm_open(NULL, _PATH_DEVNULL, NULL, O_RDONLY,
+ "kvm_open")) == NULL) {
+ syslog(LOG_ERR, "kvm_open failed: %m ");
+ return (-1);
+ }
+
+ /*
+ * The order is relevant here, because some table depend on each other.
+ */
+ init_device_tbl();
+
+ /* populates partition table too */
+ if (init_disk_storage_tbl()) {
+ hostres_fini();
+ return (-1);
+ }
+ init_processor_tbl();
+ init_printer_tbl();
+
+ /*
+ * populate storage and FS tables. Must be done after device
+ * initialisation because the FS refresh code calls into the
+ * partition refresh code.
+ */
+ init_storage_tbl();
+
+
+ /* also the hrSWRunPerfTable's support is initialized here */
+ init_swrun_tbl();
+ init_swins_tbl();
+
+ HRDBG("done.");
+
+ return (0);
+}
+
+/*
+ * HOST RESOURCES mib module start operation
+ * returns nothing
+ */
+static void
+hostres_start(void)
+{
+
+ host_registration_id = or_register(&oid_host,
+ "The MIB module for Host Resource MIB (RFC 2790).",
+ hostres_module);
+
+ start_device_tbl(hostres_module);
+ start_processor_tbl(hostres_module);
+ start_network_tbl();
+
+ HRDBG("done.");
+}
+
+/* this identifies the HOST RESOURCES mib module */
+const struct snmp_module config = {
+ "This module implements the host resource mib (rfc 2790)",
+ hostres_init,
+ hostres_fini,
+ NULL, /* idle function, do not use it */
+ NULL,
+ NULL,
+ hostres_start,
+ NULL, /* proxy a PDU */
+ hostres_ctree, /* see the generated hostres_tree.h */
+ hostres_CTREE_SIZE, /* see the generated hostres_tree.h */
+ NULL
+};
+
+/**
+ * Make an SNMP DateAndTime from a struct tm. This should be in the library.
+ */
+int
+make_date_time(u_char *str, const struct tm *tm, u_int decisecs)
+{
+
+ str[0] = (u_char)((tm->tm_year + 1900) >> 8);
+ str[1] = (u_char)(tm->tm_year + 1900);
+ str[2] = tm->tm_mon + 1;
+ str[3] = tm->tm_mday;
+ str[4] = tm->tm_hour;
+ str[5] = tm->tm_min;
+ str[6] = tm->tm_sec;
+ str[7] = decisecs;
+ if (tm->tm_gmtoff < 0)
+ str[8] = '-';
+ else
+ str[8] = '+';
+
+ str[9] = (u_char)(labs(tm->tm_gmtoff) / 3600);
+ str[10] = (u_char)((labs(tm->tm_gmtoff) % 3600) / 60);
+
+ return (11);
+}
diff --git a/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_snmp.h b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_snmp.h
new file mode 100644
index 0000000..04928d2
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_snmp.h
@@ -0,0 +1,324 @@
+/*
+ * Copyright (c) 2005-2006 The FreeBSD Project
+ * All rights reserved.
+ *
+ * Author: Victor Cruceru <soc-victor@freebsd.org>
+ *
+ * Redistribution of this software and documentation and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ *
+ * 1. Redistributions of source code or documentation must retain the above
+ * copyright notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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.
+ *
+ * Host Resources MIB for SNMPd.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef HOSTRES_SNMP_H_1132245017
+#define HOSTRES_SNMP_H_1132245017
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <kvm.h>
+#include <devinfo.h>
+
+#include <bsnmp/asn1.h>
+#include <bsnmp/snmp.h>
+
+#include <bsnmp/snmpmod.h>
+
+/*
+ * Default package directory for hrSWInstalledTable. Can be overridden
+ * via SNMP or configuration file.
+ */
+#define PATH_PKGDIR "/var/db/pkg"
+
+/*
+ * These are the default maximum caching intervals for the various tables
+ * in seconds. They can be overridden from the configuration file.
+ */
+#define HR_STORAGE_TBL_REFRESH 7
+#define HR_FS_TBL_REFRESH 7
+#define HR_DISK_TBL_REFRESH 7
+#define HR_NETWORK_TBL_REFRESH 7
+#define HR_SWINS_TBL_REFRESH 120
+#define HR_SWRUN_TBL_REFRESH 3
+
+struct tm;
+struct statfs;
+
+/* a debug macro */
+#ifndef NDEBUG
+
+#define HRDBG(...) do { \
+ fprintf(stderr, "HRDEBUG: %s: ", __func__); \
+ fprintf(stderr, __VA_ARGS__); \
+ fprintf(stderr, "\n"); \
+ } while (0)
+
+#else
+
+#define HRDBG(...) do { } while (0)
+
+#endif /*NDEBUG*/
+
+/* path to devd(8) output pipe */
+#define PATH_DEVD_PIPE "/var/run/devd.pipe"
+
+#define IS_KERNPROC(kp) (((kp)->ki_flag & P_KTHREAD) == P_KTHREAD)
+
+enum snmpTCTruthValue {
+ SNMP_TRUE = 1,
+ SNMP_FALSE= 2
+};
+
+/* The number of CPU load samples per one minute, per each CPU */
+#define MAX_CPU_SAMPLES 4
+
+
+/*
+ * max len (including '\0'), for device_entry::descr field below,
+ * according to MIB
+ */
+#define DEV_DESCR_MLEN (64 + 1)
+
+/*
+ * max len (including '\0'), for device_entry::name and
+ * device_map_entry::name_key fields below, according to MIB
+ */
+#define DEV_NAME_MLEN (32 + 1)
+
+/*
+ * max len (including '\0'), for device_entry::location and
+ * device_map_entry::location_key fields below, according to MIB
+ */
+#define DEV_LOC_MLEN (128 + 1)
+
+/*
+ * This structure is used to hold a SNMP table entry
+ * for HOST-RESOURCES-MIB's hrDeviceTable
+ */
+struct device_entry {
+ int32_t index;
+ const struct asn_oid *type;
+ u_char *descr;
+ const struct asn_oid *id; /* only oid_zeroDotZero as (*id) value*/
+ int32_t status; /* enum DeviceStatus */
+ uint32_t errors;
+
+#define HR_DEVICE_FOUND 0x001
+ /* not dectected by libdevice, so don't try to refresh it*/
+#define HR_DEVICE_IMMUTABLE 0x002
+
+ /* next 3 are not from the SNMP mib table, only to be used internally */
+ uint32_t flags;
+
+ u_char *name;
+ u_char *location;
+ TAILQ_ENTRY(device_entry) link;
+};
+
+/*
+ * Next structure is used to keep o list of mappings from a specific
+ * name (a_name) to an entry in the hrFSTblEntry;
+ * We are trying to keep the same index for a specific name at least
+ * for the duration of one SNMP agent run.
+ */
+struct device_map_entry {
+ int32_t hrIndex; /* used for hrDeviceTblEntry::index */
+
+ /* map key is the pair (name_key, location_key) */
+ u_char *name_key; /* copy of device name */
+ u_char *location_key;
+
+ /*
+ * Next may be NULL if the respective hrDeviceTblEntry
+ * is (temporally) gone.
+ */
+ struct device_entry *entry_p;
+ STAILQ_ENTRY(device_map_entry) link;
+};
+STAILQ_HEAD(device_map, device_map_entry);
+
+/* descriptor to access kernel memory */
+extern kvm_t *hr_kd;
+
+/* Table used for consistent device table indexing. */
+extern struct device_map device_map;
+
+/* Maximum number of ticks between two updates for hrStorageTable */
+extern uint32_t storage_tbl_refresh;
+
+/* Maximum number of ticks between updated of FS table */
+extern uint32_t fs_tbl_refresh;
+
+/* maximum number of ticks between updates of SWRun and SWRunPerf table */
+extern uint32_t swrun_tbl_refresh;
+
+/* Maximum number of ticks between device table refreshs. */
+extern uint32_t device_tbl_refresh;
+
+/* maximum number of ticks between refreshs */
+extern uint32_t disk_storage_tbl_refresh;
+
+/* maximum number of ticks between updates of network table */
+extern uint32_t swins_tbl_refresh;
+
+/* maximum number of ticks between updates of network table */
+extern uint32_t network_tbl_refresh;
+
+/* package directory */
+extern u_char *pkg_dir;
+
+/* Initialize and populate storage table */
+void init_storage_tbl(void);
+
+/* Finalization routine for hrStorageTable. */
+void fini_storage_tbl(void);
+
+/* Refresh routine for hrStorageTable. */
+void refresh_storage_tbl(int);
+
+/*
+ * Get the type of filesystem referenced in a struct statfs * -
+ * used by FSTbl and StorageTbl functions.
+ */
+const struct asn_oid *fs_get_type(const struct statfs *);
+
+/*
+ * Because hrFSTable depends to hrStorageTable we are
+ * refreshing hrFSTable by refreshing hrStorageTable.
+ * When one entry "of type" fs from hrStorageTable is refreshed
+ * then the corresponding entry from hrFSTable is refreshed
+ * FS_tbl_pre_refresh_v() is called before refeshing fs part of hrStorageTable
+ */
+void fs_tbl_pre_refresh(void);
+void fs_tbl_process_statfs_entry(const struct statfs *, int32_t);
+
+/* Called after refreshing fs part of hrStorageTable */
+void fs_tbl_post_refresh(void);
+
+/* Refresh the FS table if necessary. */
+void refresh_fs_tbl(void);
+
+/* Finalization routine for hrFSTable. */
+void fini_fs_tbl(void);
+
+/* Init the things for both of hrSWRunTable and hrSWRunPerfTable */
+void init_swrun_tbl(void);
+
+/* Finalization routine for both of hrSWRunTable and hrSWRunPerfTable */
+void fini_swrun_tbl(void);
+
+/* Init and populate hrDeviceTable */
+void init_device_tbl(void);
+
+/* start devd monitoring */
+void start_device_tbl(struct lmodule *);
+
+/* Finalization routine for hrDeviceTable */
+void fini_device_tbl(void);
+
+/* Refresh routine for hrDeviceTable. */
+void refresh_device_tbl(int);
+
+/* Find an item in hrDeviceTbl by its entry->index. */
+struct device_entry *device_find_by_index(int32_t);
+
+/* Find an item in hrDeviceTbl by name. */
+struct device_entry *device_find_by_name(const char *);
+
+/* Create a new entry out of thin air. */
+struct device_entry *device_entry_create(const char *, const char *,
+ const char *);
+
+/* Delete an entry from hrDeviceTbl */
+void device_entry_delete(struct device_entry *entry);
+
+/* Init the things for hrProcessorTable. */
+void init_processor_tbl(void);
+
+/* Finalization routine for hrProcessorTable. */
+void fini_processor_tbl(void);
+
+/* Start the processor table CPU load collector. */
+void start_processor_tbl(struct lmodule *);
+
+/* Init the things for hrDiskStorageTable */
+int init_disk_storage_tbl(void);
+
+/* Finalization routine for hrDiskStorageTable. */
+void fini_disk_storage_tbl(void);
+
+/* Refresh routine for hrDiskStorageTable. */
+void refresh_disk_storage_tbl(int);
+
+/* Finalization routine for hrPartitionTable. */
+void fini_partition_tbl(void);
+
+/* Finalization routine for hrNetworkTable. */
+void fini_network_tbl(void);
+
+/* populate network table */
+void start_network_tbl(void);
+
+/* initialize installed software table */
+void init_swins_tbl(void);
+
+/* finalize installed software table */
+void fini_swins_tbl(void);
+
+/* refresh the hrSWInstalledTable if necessary */
+void refresh_swins_tbl(void);
+
+/* Init the things for hrPrinterTable */
+void init_printer_tbl(void);
+
+/* Finalization routine for hrPrinterTable. */
+void fini_printer_tbl(void);
+
+/* Refresh printer table */
+void refresh_printer_tbl(void);
+
+/* get boot command line */
+int OS_getSystemInitialLoadParameters(u_char **);
+
+/* Start refreshing the partition table */
+void partition_tbl_post_refresh(void);
+
+/* Handle refresh for the given disk */
+void partition_tbl_handle_disk(int32_t, const char *);
+
+/* Finish refreshing the partition table. */
+void partition_tbl_pre_refresh(void);
+
+/* Set the FS index in a partition table entry */
+void handle_partition_fs_index(const char *, int32_t);
+
+/* Make an SNMP DateAndTime from a struct tm. */
+int make_date_time(u_char *, const struct tm *, u_int);
+
+/* Free all static data */
+void fini_scalars(void);
+
+#endif /* HOSTRES_SNMP_H_1132245017 */
diff --git a/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_storage_tbl.c b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_storage_tbl.c
new file mode 100644
index 0000000..ced7e8d
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_storage_tbl.c
@@ -0,0 +1,662 @@
+/*-
+ * Copyright (c) 2005-2006 The FreeBSD Project
+ * All rights reserved.
+ *
+ * Author: Victor Cruceru <soc-victor@freebsd.org>
+ *
+ * Redistribution of this software and documentation and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ *
+ * 1. Redistributions of source code or documentation must retain the above
+ * copyright notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+/*
+ * Host Resources MIB for SNMPd. Implementation for hrStorageTable
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <sys/vmmeter.h>
+#include <sys/mount.h>
+
+#include <vm/vm_param.h>
+
+#include <assert.h>
+#include <err.h>
+#include <limits.h>
+#include <memstat.h>
+#include <paths.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h> /* for getpagesize() */
+#include <sysexits.h>
+
+#include "hostres_snmp.h"
+#include "hostres_oid.h"
+#include "hostres_tree.h"
+
+/* maximum length for descritpion string according to MIB */
+#define SE_DESC_MLEN (255 + 1)
+
+/*
+ * This structure is used to hold a SNMP table entry
+ * for HOST-RESOURCES-MIB's hrStorageTable
+ */
+struct storage_entry {
+ int32_t index;
+ const struct asn_oid *type;
+ u_char *descr;
+ int32_t allocationUnits;
+ int32_t size;
+ int32_t used;
+ uint32_t allocationFailures;
+#define HR_STORAGE_FOUND 0x001
+ uint32_t flags; /* to be used internally*/
+ TAILQ_ENTRY(storage_entry) link;
+};
+TAILQ_HEAD(storage_tbl, storage_entry);
+
+/*
+ * Next structure is used to keep o list of mappings from a specific name
+ * (a_name) to an entry in the hrStorageTblEntry. We are trying to keep the
+ * same index for a specific name at least for the duration of one SNMP agent
+ * run.
+ */
+struct storage_map_entry {
+ int32_t hrIndex; /* used for storage_entry::index */
+
+ /* map key, also used for storage_entry::descr */
+ u_char *a_name;
+
+ /*
+ * next may be NULL if the respective storage_entry
+ * is (temporally) gone
+ */
+ struct storage_entry *entry;
+ STAILQ_ENTRY(storage_map_entry) link;
+};
+STAILQ_HEAD(storage_map, storage_map_entry);
+
+/* the head of the list with table's entries */
+static struct storage_tbl storage_tbl = TAILQ_HEAD_INITIALIZER(storage_tbl);
+
+/*for consistent table indexing*/
+static struct storage_map storage_map =
+ STAILQ_HEAD_INITIALIZER(storage_map);
+
+/* last (agent) tick when hrStorageTable was updated */
+static uint64_t storage_tick;
+
+/* maximum number of ticks between two refreshs */
+uint32_t storage_tbl_refresh = HR_STORAGE_TBL_REFRESH * 100;
+
+/* for kvm_getswapinfo, malloc'd */
+static struct kvm_swap *swap_devs;
+static size_t swap_devs_len; /* item count for swap_devs */
+
+/* for getfsstat, malloc'd */
+static struct statfs *fs_buf;
+static size_t fs_buf_count; /* item count for fs_buf */
+
+static struct vmtotal mem_stats;
+
+/* next int available for indexing the hrStorageTable */
+static uint32_t next_storage_index = 1;
+
+/* start of list for memory detailed stats */
+static struct memory_type_list *mt_list;
+
+/* Constants */
+static const struct asn_oid OIDX_hrStorageRam_c = OIDX_hrStorageRam;
+static const struct asn_oid OIDX_hrStorageVirtualMemory_c =
+ OIDX_hrStorageVirtualMemory;
+
+/**
+ * Create a new entry into the storage table and, if necessary, an
+ * entry into the storage map.
+ */
+static struct storage_entry *
+storage_entry_create(const char *name)
+{
+ struct storage_entry *entry;
+ struct storage_map_entry *map;
+ size_t name_len;
+
+ assert(name != NULL);
+ assert(strlen(name) > 0);
+
+ STAILQ_FOREACH(map, &storage_map, link)
+ if (strcmp(map->a_name, name) == 0)
+ break;
+
+ if (map == NULL) {
+ /* new object - get a new index */
+ if (next_storage_index > INT_MAX) {
+ syslog(LOG_ERR,
+ "%s: hrStorageTable index wrap", __func__);
+ errx(EX_SOFTWARE, "hrStorageTable index wrap");
+ }
+
+ if ((map = malloc(sizeof(*map))) == NULL) {
+ syslog(LOG_ERR, "hrStorageTable: %s: %m", __func__ );
+ return (NULL);
+ }
+
+ name_len = strlen(name) + 1;
+ if (name_len > SE_DESC_MLEN)
+ name_len = SE_DESC_MLEN;
+
+ if ((map->a_name = malloc(name_len)) == NULL) {
+ free(map);
+ return (NULL);
+ }
+
+ strlcpy(map->a_name, name, name_len);
+ map->hrIndex = next_storage_index++;
+
+ STAILQ_INSERT_TAIL(&storage_map, map, link);
+
+ HRDBG("%s added into hrStorageMap at index=%d",
+ name, map->hrIndex);
+ } else {
+ HRDBG("%s exists in hrStorageMap index=%d\n",
+ name, map->hrIndex);
+ }
+
+ if ((entry = malloc(sizeof(*entry))) == NULL) {
+ syslog(LOG_WARNING, "%s: %m", __func__);
+ return (NULL);
+ }
+ memset(entry, 0, sizeof(*entry));
+
+ entry->index = map->hrIndex;
+
+ if ((entry->descr = strdup(map->a_name)) == NULL) {
+ free(entry);
+ return (NULL);
+ }
+
+ map->entry = entry;
+
+ INSERT_OBJECT_INT(entry, &storage_tbl);
+
+ return (entry);
+}
+
+/**
+ * Delete an entry from the storage table.
+ */
+static void
+storage_entry_delete(struct storage_entry *entry)
+{
+ struct storage_map_entry *map;
+
+ assert(entry != NULL);
+
+ TAILQ_REMOVE(&storage_tbl, entry, link);
+ STAILQ_FOREACH(map, &storage_map, link)
+ if (map->entry == entry) {
+ map->entry = NULL;
+ break;
+ }
+ free(entry->descr);
+ free(entry);
+}
+
+/**
+ * Find a table entry by its name.
+ */
+static struct storage_entry *
+storage_find_by_name(const char *name)
+{
+ struct storage_entry *entry;
+
+ TAILQ_FOREACH(entry, &storage_tbl, link)
+ if (strcmp(entry->descr, name) == 0)
+ return (entry);
+
+ return (NULL);
+}
+
+/*
+ * VM info.
+ */
+static void
+storage_OS_get_vm(void)
+{
+ int mib[2] = { CTL_VM, VM_TOTAL };
+ size_t len = sizeof(mem_stats);
+ int page_size_bytes;
+ struct storage_entry *entry;
+
+ if (sysctl(mib, 2, &mem_stats, &len, NULL, 0) < 0) {
+ syslog(LOG_ERR,
+ "hrStoragetable: %s: sysctl({CTL_VM, VM_METER}) "
+ "failed: %m", __func__);
+ assert(0);
+ return;
+ }
+
+ page_size_bytes = getpagesize();
+
+ /* Real Memory Metrics */
+ if ((entry = storage_find_by_name("Real Memory Metrics")) == NULL &&
+ (entry = storage_entry_create("Real Memory Metrics")) == NULL)
+ return; /* I'm out of luck now, maybe next time */
+
+ entry->flags |= HR_STORAGE_FOUND;
+ entry->type = &OIDX_hrStorageRam_c;
+ entry->allocationUnits = page_size_bytes;
+ entry->size = mem_stats.t_rm;
+ entry->used = mem_stats.t_arm; /* ACTIVE is not USED - FIXME */
+ entry->allocationFailures = 0;
+
+ /* Shared Real Memory Metrics */
+ if ((entry = storage_find_by_name("Shared Real Memory Metrics")) ==
+ NULL &&
+ (entry = storage_entry_create("Shared Real Memory Metrics")) ==
+ NULL)
+ return;
+
+ entry->flags |= HR_STORAGE_FOUND;
+ entry->type = &OIDX_hrStorageRam_c;
+ entry->allocationUnits = page_size_bytes;
+ entry->size = mem_stats.t_rmshr;
+ /* ACTIVE is not USED - FIXME */
+ entry->used = mem_stats.t_armshr;
+ entry->allocationFailures = 0;
+}
+
+static void
+storage_OS_get_memstat(void)
+{
+ struct memory_type *mt_item;
+ struct storage_entry *entry;
+
+ if (mt_list == NULL) {
+ if ((mt_list = memstat_mtl_alloc()) == NULL)
+ /* again? we have a serious problem */
+ return;
+ }
+
+ if (memstat_sysctl_all(mt_list, 0) < 0) {
+ syslog(LOG_ERR, "memstat_sysctl_all failed: %s",
+ memstat_strerror(memstat_mtl_geterror(mt_list)) );
+ return;
+ }
+
+ if ((mt_item = memstat_mtl_first(mt_list)) == NULL) {
+ /* usually this is not an error, no errno for this failure*/
+ HRDBG("memstat_mtl_first failed");
+ return;
+ }
+
+ do {
+ const char *memstat_name;
+ uint64_t tmp_size;
+ int allocator;
+ char alloc_descr[SE_DESC_MLEN];
+
+ memstat_name = memstat_get_name(mt_item);
+
+ if (memstat_name == NULL || strlen(memstat_name) == 0)
+ continue;
+
+ switch (allocator = memstat_get_allocator(mt_item)) {
+
+ case ALLOCATOR_MALLOC:
+ snprintf(alloc_descr, sizeof(alloc_descr),
+ "MALLOC: %s", memstat_name);
+ break;
+
+ case ALLOCATOR_UMA:
+ snprintf(alloc_descr, sizeof(alloc_descr),
+ "UMA: %s", memstat_name);
+ break;
+
+ default:
+ snprintf(alloc_descr, sizeof(alloc_descr),
+ "UNKNOWN%d: %s", allocator, memstat_name);
+ break;
+ }
+
+ if ((entry = storage_find_by_name(alloc_descr)) == NULL &&
+ (entry = storage_entry_create(alloc_descr)) == NULL)
+ return;
+
+ entry->flags |= HR_STORAGE_FOUND;
+ entry->type = &OIDX_hrStorageRam_c;
+
+ if ((tmp_size = memstat_get_size(mt_item)) == 0)
+ tmp_size = memstat_get_sizemask(mt_item);
+ entry->allocationUnits =
+ (tmp_size > INT_MAX ? INT_MAX : (int32_t)tmp_size);
+
+ tmp_size = memstat_get_countlimit(mt_item);
+ entry->size =
+ (tmp_size > INT_MAX ? INT_MAX : (int32_t)tmp_size);
+
+ tmp_size = memstat_get_count(mt_item);
+ entry->used =
+ (tmp_size > INT_MAX ? INT_MAX : (int32_t)tmp_size);
+
+ tmp_size = memstat_get_failures(mt_item);
+ entry->allocationFailures =
+ (tmp_size > INT_MAX ? INT_MAX : (int32_t)tmp_size);
+
+ } while((mt_item = memstat_mtl_next(mt_item)) != NULL);
+}
+
+/**
+ * Get swap info
+ */
+static void
+storage_OS_get_swap(void)
+{
+ int nswapdev = 0;
+ size_t len = sizeof(nswapdev);
+ struct storage_entry *entry;
+ char swap_w_prefix[SE_DESC_MLEN];
+
+ if (sysctlbyname("vm.nswapdev", &nswapdev, &len, NULL,0 ) < 0) {
+ syslog(LOG_ERR,
+ "hrStorageTable: sysctlbyname(\"vm.nswapdev\") "
+ "failed. %m");
+ assert(0);
+ return;
+ }
+
+ if (nswapdev <= 0) {
+ HRDBG("vm.nswapdev is %d", nswapdev);
+ return;
+ }
+
+ if (nswapdev + 1 != (int)swap_devs_len || swap_devs == NULL) {
+ swap_devs_len = nswapdev + 1;
+ swap_devs = reallocf(swap_devs,
+ swap_devs_len * sizeof(struct kvm_swap));
+
+ assert(swap_devs != NULL);
+ if (swap_devs == NULL) {
+ swap_devs_len = 0;
+ return;
+ }
+ }
+
+ nswapdev = kvm_getswapinfo(hr_kd, swap_devs, swap_devs_len, 0);
+ if (nswapdev < 0) {
+ syslog(LOG_ERR,
+ "hrStorageTable: kvm_getswapinfo failed. %m\n");
+ assert(0);
+ return;
+ }
+
+ for (len = 0; len < (size_t)nswapdev; len++) {
+ memset(&swap_w_prefix[0], '\0', sizeof(swap_w_prefix));
+ snprintf(swap_w_prefix, sizeof(swap_w_prefix) - 1,
+ "Swap:%s%s", _PATH_DEV, swap_devs[len].ksw_devname);
+
+ entry = storage_find_by_name(swap_w_prefix);
+ if (entry == NULL)
+ entry = storage_entry_create(swap_w_prefix);
+
+ assert (entry != NULL);
+ if (entry == NULL)
+ return; /* Out of luck */
+
+ entry->flags |= HR_STORAGE_FOUND;
+ entry->type = &OIDX_hrStorageVirtualMemory_c;
+ entry->allocationUnits = getpagesize();
+ entry->size = swap_devs[len].ksw_total;
+ entry->used = swap_devs[len].ksw_used;
+ entry->allocationFailures = 0;
+ }
+}
+
+/**
+ * Query the underlaying OS for the mounted file systems
+ * anf fill in the respective lists (for hrStorageTable and for hrFSTable)
+ */
+static void
+storage_OS_get_fs(void)
+{
+ struct storage_entry *entry;
+ uint64_t size, used;
+ int i, mounted_fs_count, units;
+ char fs_string[SE_DESC_MLEN];
+
+ if ((mounted_fs_count = getfsstat(NULL, 0, MNT_NOWAIT)) < 0) {
+ syslog(LOG_ERR, "hrStorageTable: getfsstat() failed: %m");
+ return; /* out of luck this time */
+ }
+
+ if (mounted_fs_count != (int)fs_buf_count || fs_buf == NULL) {
+ fs_buf_count = mounted_fs_count;
+ fs_buf = reallocf(fs_buf, fs_buf_count * sizeof(struct statfs));
+ if (fs_buf == NULL) {
+ fs_buf_count = 0;
+ assert(0);
+ return;
+ }
+ }
+
+ if ((mounted_fs_count = getfsstat(fs_buf,
+ fs_buf_count * sizeof(struct statfs), MNT_NOWAIT)) < 0) {
+ syslog(LOG_ERR, "hrStorageTable: getfsstat() failed: %m");
+ return; /* out of luck this time */
+ }
+
+ HRDBG("got %d mounted FS", mounted_fs_count);
+
+ fs_tbl_pre_refresh();
+
+ for (i = 0; i < mounted_fs_count; i++) {
+ snprintf(fs_string, sizeof(fs_string),
+ "%s, type: %s, dev: %s", fs_buf[i].f_mntonname,
+ fs_buf[i].f_fstypename, fs_buf[i].f_mntfromname);
+
+ entry = storage_find_by_name(fs_string);
+ if (entry == NULL)
+ entry = storage_entry_create(fs_string);
+
+ assert (entry != NULL);
+ if (entry == NULL)
+ return; /* Out of luck */
+
+ entry->flags |= HR_STORAGE_FOUND;
+ entry->type = fs_get_type(&fs_buf[i]); /*XXX - This is wrong*/
+
+ units = fs_buf[i].f_bsize;
+ size = fs_buf[i].f_blocks;
+ used = fs_buf[i].f_blocks - fs_buf[i].f_bfree;
+ while (size > INT_MAX) {
+ units <<= 1;
+ size >>= 1;
+ used >>= 1;
+ }
+ entry->allocationUnits = units;
+ entry->size = size;
+ entry->used = used;
+
+ entry->allocationFailures = 0;
+
+ /* take care of hrFSTable */
+ fs_tbl_process_statfs_entry(&fs_buf[i], entry->index);
+ }
+
+ fs_tbl_post_refresh();
+}
+
+/**
+ * Initialize storage table and populate it.
+ */
+void
+init_storage_tbl(void)
+{
+ if ((mt_list = memstat_mtl_alloc()) == NULL)
+ syslog(LOG_ERR,
+ "hrStorageTable: memstat_mtl_alloc() failed: %m");
+
+ refresh_storage_tbl(1);
+}
+
+void
+fini_storage_tbl(void)
+{
+ struct storage_map_entry *n1;
+
+ if (swap_devs != NULL) {
+ free(swap_devs);
+ swap_devs = NULL;
+ }
+ swap_devs_len = 0;
+
+ if (fs_buf != NULL) {
+ free(fs_buf);
+ fs_buf = NULL;
+ }
+ fs_buf_count = 0;
+
+ while ((n1 = STAILQ_FIRST(&storage_map)) != NULL) {
+ STAILQ_REMOVE_HEAD(&storage_map, link);
+ if (n1->entry != NULL) {
+ TAILQ_REMOVE(&storage_tbl, n1->entry, link);
+ free(n1->entry->descr);
+ free(n1->entry);
+ }
+ free(n1->a_name);
+ free(n1);
+ }
+ assert(TAILQ_EMPTY(&storage_tbl));
+}
+
+void
+refresh_storage_tbl(int force)
+{
+ struct storage_entry *entry, *entry_tmp;
+
+ if (!force && storage_tick != 0 &&
+ this_tick - storage_tick < storage_tbl_refresh) {
+ HRDBG("no refresh needed");
+ return;
+ }
+
+ /* mark each entry as missing */
+ TAILQ_FOREACH(entry, &storage_tbl, link)
+ entry->flags &= ~HR_STORAGE_FOUND;
+
+ storage_OS_get_vm();
+ storage_OS_get_swap();
+ storage_OS_get_fs();
+ storage_OS_get_memstat();
+
+ /*
+ * Purge items that disappeared
+ */
+ TAILQ_FOREACH_SAFE(entry, &storage_tbl, link, entry_tmp)
+ if (!(entry->flags & HR_STORAGE_FOUND))
+ storage_entry_delete(entry);
+
+ storage_tick = this_tick;
+
+ HRDBG("refresh DONE");
+}
+
+/*
+ * This is the implementation for a generated (by our SNMP tool)
+ * function prototype, see hostres_tree.h
+ * It handles the SNMP operations for hrStorageTable
+ */
+int
+op_hrStorageTable(struct snmp_context *ctx __unused, struct snmp_value *value,
+ u_int sub, u_int iidx __unused, enum snmp_op curr_op)
+{
+ struct storage_entry *entry;
+
+ refresh_storage_tbl(0);
+
+ switch (curr_op) {
+
+ case SNMP_OP_GETNEXT:
+ if ((entry = NEXT_OBJECT_INT(&storage_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ value->var.len = sub + 1;
+ value->var.subs[sub] = entry->index;
+ goto get;
+
+ case SNMP_OP_GET:
+ if ((entry = FIND_OBJECT_INT(&storage_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_SET:
+ if ((entry = FIND_OBJECT_INT(&storage_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NO_CREATION);
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ abort();
+ }
+ abort();
+
+ get:
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_hrStorageIndex:
+ value->v.integer = entry->index;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrStorageType:
+ assert(entry->type != NULL);
+ value->v.oid = *entry->type;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrStorageDescr:
+ assert(entry->descr != NULL);
+ return (string_get(value, entry->descr, -1));
+ break;
+
+ case LEAF_hrStorageAllocationUnits:
+ value->v.integer = entry->allocationUnits;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrStorageSize:
+ value->v.integer = entry->size;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrStorageUsed:
+ value->v.integer = entry->used;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrStorageAllocationFailures:
+ value->v.uint32 = entry->allocationFailures;
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+}
diff --git a/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_swinstalled_tbl.c b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_swinstalled_tbl.c
new file mode 100644
index 0000000..5fa5b4c
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_swinstalled_tbl.c
@@ -0,0 +1,555 @@
+/*
+ * Copyright (c) 2005-2006 The FreeBSD Project
+ * All rights reserved.
+ *
+ * Author: Victor Cruceru <soc-victor@freebsd.org>
+ *
+ * Redistribution of this software and documentation and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ *
+ * 1. Redistributions of source code or documentation must retain the above
+ * copyright notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ *
+ * Host Resources MIB implementation for SNMPd: instrumentation for
+ * hrSWInstalledTable
+ */
+
+#include <sys/limits.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/utsname.h>
+
+#include <assert.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <sysexits.h>
+
+#include "hostres_snmp.h"
+#include "hostres_oid.h"
+#include "hostres_tree.h"
+
+#define CONTENTS_FNAME "+CONTENTS"
+
+enum SWInstalledType {
+ SWI_UNKNOWN = 1,
+ SWI_OPERATING_SYSTEM = 2,
+ SWI_DEVICE_DRIVER = 3,
+ SWI_APPLICATION = 4
+};
+
+#define SW_NAME_MLEN (64 + 1)
+
+/*
+ * This structure is used to hold a SNMP table entry
+ * for HOST-RESOURCES-MIB's hrSWInstalledTable
+ */
+struct swins_entry {
+ int32_t index;
+ u_char *name; /* max len for this is SW_NAME_MLEN */
+ const struct asn_oid *id;
+ int32_t type; /* from enum SWInstalledType */
+ u_char date[11];
+ u_int date_len;
+
+#define HR_SWINSTALLED_FOUND 0x001
+#define HR_SWINSTALLED_IMMUTABLE 0x002
+ uint32_t flags;
+
+ TAILQ_ENTRY(swins_entry) link;
+};
+TAILQ_HEAD(swins_tbl, swins_entry);
+
+/*
+ * Table to keep a conistent mapping between software and indexes.
+ */
+struct swins_map_entry {
+ int32_t index; /* swins_entry::index */
+ u_char *name; /* map key,a copy of swins_entry::name*/
+
+ /*
+ * next may be NULL if the respective hrSWInstalledTblEntry
+ * is (temporally) gone
+ */
+ struct swins_entry *entry;
+
+ STAILQ_ENTRY(swins_map_entry) link;
+};
+STAILQ_HEAD(swins_map, swins_map_entry);
+
+/* map for consistent indexing */
+static struct swins_map swins_map = STAILQ_HEAD_INITIALIZER(swins_map);
+
+/* the head of the list with hrSWInstalledTable's entries */
+static struct swins_tbl swins_tbl = TAILQ_HEAD_INITIALIZER(swins_tbl);
+
+/* next int available for indexing the hrSWInstalledTable */
+static uint32_t next_swins_index = 1;
+
+/* last (agent) tick when hrSWInstalledTable was updated */
+static uint64_t swins_tick;
+
+/* maximum number of ticks between updates of network table */
+uint32_t swins_tbl_refresh = HR_SWINS_TBL_REFRESH * 100;
+
+/* package directory */
+u_char *pkg_dir;
+
+/* last change of package list */
+static time_t os_pkg_last_change;
+
+/**
+ * Create a new entry into the hrSWInstalledTable
+ */
+static struct swins_entry *
+swins_entry_create(const char *name)
+{
+ struct swins_entry *entry;
+ struct swins_map_entry *map;
+
+ STAILQ_FOREACH(map, &swins_map, link)
+ if (strcmp((const char *)map->name, name) == 0)
+ break;
+
+ if (map == NULL) {
+ size_t name_len;
+ /* new object - get a new index */
+ if (next_swins_index > INT_MAX) {
+ syslog(LOG_ERR, "%s: hrSWInstalledTable index wrap",
+ __func__ );
+ /* There isn't much we can do here.
+ * If the next_swins_index is consumed
+ * then we can't add entries to this table
+ * So it is better to exit - if the table is sparsed
+ * at the next agent run we can fill it fully.
+ */
+ errx(EX_SOFTWARE, "hrSWInstalledTable index wrap");
+ }
+
+ if ((map = malloc(sizeof(*map))) == NULL) {
+ syslog(LOG_ERR, "%s: %m", __func__ );
+ return (NULL);
+ }
+
+ name_len = strlen(name) + 1;
+ if (name_len > SW_NAME_MLEN)
+ name_len = SW_NAME_MLEN;
+
+ if ((map->name = malloc(name_len)) == NULL) {
+ syslog(LOG_WARNING, "%s: %m", __func__);
+ free(map);
+ return (NULL);
+ }
+
+ map->index = next_swins_index++;
+ strlcpy((char *)map->name, name, name_len);
+
+ STAILQ_INSERT_TAIL(&swins_map, map, link);
+
+ HRDBG("%s added into hrSWInstalled at %d", name, map->index);
+ }
+
+ if ((entry = malloc(sizeof(*entry))) == NULL) {
+ syslog(LOG_WARNING, "%s: %m", __func__);
+ return (NULL);
+ }
+ memset(entry, 0, sizeof(*entry));
+
+ if ((entry->name = strdup(map->name)) == NULL) {
+ syslog(LOG_WARNING, "%s: %m", __func__);
+ free(entry);
+ return (NULL);
+ }
+
+ entry->index = map->index;
+ map->entry = entry;
+
+ INSERT_OBJECT_INT(entry, &swins_tbl);
+
+ return (entry);
+}
+
+/**
+ * Delete an entry in the hrSWInstalledTable
+ */
+static void
+swins_entry_delete(struct swins_entry *entry)
+{
+ struct swins_map_entry *map;
+
+ assert(entry != NULL);
+
+ TAILQ_REMOVE(&swins_tbl, entry, link);
+
+ STAILQ_FOREACH(map, &swins_map, link)
+ if (map->entry == entry) {
+ map->entry = NULL;
+ break;
+ }
+
+ free(entry->name);
+ free(entry);
+}
+
+/**
+ * Find an entry given it's name
+ */
+static struct swins_entry *
+swins_find_by_name(const char *name)
+{
+ struct swins_entry *entry;
+
+ TAILQ_FOREACH(entry, &swins_tbl, link)
+ if (strcmp((const char*)entry->name, name) == 0)
+ return (entry);
+ return (NULL);
+}
+
+/**
+ * Finalize this table
+ */
+void
+fini_swins_tbl(void)
+{
+ struct swins_map_entry *n1;
+
+ while ((n1 = STAILQ_FIRST(&swins_map)) != NULL) {
+ STAILQ_REMOVE_HEAD(&swins_map, link);
+ if (n1->entry != NULL) {
+ TAILQ_REMOVE(&swins_tbl, n1->entry, link);
+ free(n1->entry->name);
+ free(n1->entry);
+ }
+ free(n1->name);
+ free(n1);
+ }
+ assert(TAILQ_EMPTY(&swins_tbl));
+}
+
+/**
+ * Get the *running* O/S identification
+ */
+static void
+swins_get_OS_ident(void)
+{
+ struct utsname os_id;
+ char os_string[SW_NAME_MLEN] = "";
+ struct swins_entry *entry;
+ u_char *boot;
+ struct stat sb;
+ struct tm k_ts;
+
+ if (uname(&os_id) == -1) {
+ syslog(LOG_WARNING, "%s: %m", __func__);
+ return;
+ }
+
+ snprintf(os_string, sizeof(os_string), "%s: %s",
+ os_id.sysname, os_id.version);
+
+ if ((entry = swins_find_by_name(os_string)) != NULL ||
+ (entry = swins_entry_create(os_string)) == NULL)
+ return;
+
+ entry->flags |= (HR_SWINSTALLED_FOUND | HR_SWINSTALLED_IMMUTABLE);
+ entry->id = &oid_zeroDotZero;
+ entry->type = (int32_t)SWI_OPERATING_SYSTEM;
+ memset(entry->date, 0, sizeof(entry->date));
+
+ if (OS_getSystemInitialLoadParameters(&boot) == SNMP_ERR_NOERROR &&
+ strlen(boot) > 0 && stat(boot, &sb) == 0 &&
+ localtime_r(&sb.st_ctime, &k_ts) != NULL)
+ entry->date_len = make_date_time(entry->date, &k_ts, 0);
+}
+
+/**
+ * Read the installed packages
+ */
+static int
+swins_get_packages(void)
+{
+ struct stat sb;
+ DIR *p_dir;
+ struct dirent *ent;
+ struct tm k_ts;
+ char *pkg_file;
+ struct swins_entry *entry;
+ int ret = 0;
+
+ if (pkg_dir == NULL)
+ /* initialisation may have failed */
+ return (-1);
+
+ if (stat(pkg_dir, &sb) != 0) {
+ syslog(LOG_ERR, "hrSWInstalledTable: stat(\"%s\") failed: %m",
+ pkg_dir);
+ return (-1);
+ }
+ if (!S_ISDIR(sb.st_mode)) {
+ syslog(LOG_ERR, "hrSWInstalledTable: \"%s\" is not a directory",
+ pkg_dir);
+ return (-1);
+ }
+ if (sb.st_ctime <= os_pkg_last_change) {
+ HRDBG("no need to rescan installed packages -- "
+ "directory time-stamp unmodified");
+
+ TAILQ_FOREACH(entry, &swins_tbl, link)
+ entry->flags |= HR_SWINSTALLED_FOUND;
+
+ return (0);
+ }
+
+ if ((p_dir = opendir(pkg_dir)) == NULL) {
+ syslog(LOG_ERR, "hrSWInstalledTable: opendir(\"%s\") failed: "
+ "%m", pkg_dir);
+ return (-1);
+ }
+
+ while (errno = 0, (ent = readdir(p_dir)) != NULL) {
+ HRDBG(" pkg file: %s", ent->d_name);
+
+ /* check that the contents file is a regular file */
+ if (asprintf(&pkg_file, "%s/%s/%s", pkg_dir, ent->d_name,
+ CONTENTS_FNAME) == -1)
+ continue;
+
+ if (stat(pkg_file, &sb) != 0 ) {
+ free(pkg_file);
+ continue;
+ }
+
+ if (!S_ISREG(sb.st_mode)) {
+ syslog(LOG_ERR, "hrSWInstalledTable: \"%s\" not a "
+ "regular file -- skipped", pkg_file);
+ free(pkg_file);
+ continue;
+ }
+ free(pkg_file);
+
+ /* read directory timestamp on package */
+ if (asprintf(&pkg_file, "%s/%s", pkg_dir, ent->d_name) == -1)
+ continue;
+
+ if (stat(pkg_file, &sb) == -1 ||
+ localtime_r(&sb.st_ctime, &k_ts) == NULL) {
+ free(pkg_file);
+ continue;
+ }
+ free(pkg_file);
+
+ /* update or create entry */
+ if ((entry = swins_find_by_name(ent->d_name)) == NULL &&
+ (entry = swins_entry_create(ent->d_name)) == NULL) {
+ ret = -1;
+ goto PKG_LOOP_END;
+ }
+
+ entry->flags |= HR_SWINSTALLED_FOUND;
+ entry->id = &oid_zeroDotZero;
+ entry->type = (int32_t)SWI_APPLICATION;
+
+ entry->date_len = make_date_time(entry->date, &k_ts, 0);
+ }
+
+ if (errno != 0) {
+ syslog(LOG_ERR, "hrSWInstalledTable: readdir_r(\"%s\") failed:"
+ " %m", pkg_dir);
+ ret = -1;
+ } else {
+ /*
+ * save the timestamp of directory
+ * to avoid any further scanning
+ */
+ os_pkg_last_change = sb.st_ctime;
+ }
+ PKG_LOOP_END:
+ (void)closedir(p_dir);
+ return (ret);
+}
+
+/**
+ * Refresh the installed software table.
+ */
+void
+refresh_swins_tbl(void)
+{
+ int ret;
+ struct swins_entry *entry, *entry_tmp;
+
+ if (this_tick - swins_tick < swins_tbl_refresh) {
+ HRDBG("no refresh needed");
+ return;
+ }
+
+ /* mark each entry as missing */
+ TAILQ_FOREACH(entry, &swins_tbl, link)
+ entry->flags &= ~HR_SWINSTALLED_FOUND;
+
+ ret = swins_get_packages();
+
+ TAILQ_FOREACH_SAFE(entry, &swins_tbl, link, entry_tmp)
+ if (!(entry->flags & HR_SWINSTALLED_FOUND) &&
+ !(entry->flags & HR_SWINSTALLED_IMMUTABLE))
+ swins_entry_delete(entry);
+
+ if (ret == 0)
+ swins_tick = this_tick;
+}
+
+/**
+ * Create and populate the package table
+ */
+void
+init_swins_tbl(void)
+{
+
+ if ((pkg_dir = malloc(sizeof(PATH_PKGDIR))) == NULL)
+ syslog(LOG_ERR, "%s: %m", __func__);
+ else
+ strcpy(pkg_dir, PATH_PKGDIR);
+
+ swins_get_OS_ident();
+ refresh_swins_tbl();
+
+ HRDBG("init done");
+}
+
+/**
+ * SNMP handler
+ */
+int
+op_hrSWInstalledTable(struct snmp_context *ctx __unused,
+ struct snmp_value *value, u_int sub, u_int iidx __unused,
+ enum snmp_op curr_op)
+{
+ struct swins_entry *entry;
+
+ refresh_swins_tbl();
+
+ switch (curr_op) {
+
+ case SNMP_OP_GETNEXT:
+ if ((entry = NEXT_OBJECT_INT(&swins_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ value->var.len = sub + 1;
+ value->var.subs[sub] = entry->index;
+ goto get;
+
+ case SNMP_OP_GET:
+ if ((entry = FIND_OBJECT_INT(&swins_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_SET:
+ if ((entry = FIND_OBJECT_INT(&swins_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NO_CREATION);
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ abort();
+ }
+ abort();
+
+ get:
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_hrSWInstalledIndex:
+ value->v.integer = entry->index;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrSWInstalledName:
+ return (string_get(value, entry->name, -1));
+ break;
+
+ case LEAF_hrSWInstalledID:
+ assert(entry->id != NULL);
+ value->v.oid = *entry->id;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrSWInstalledType:
+ value->v.integer = entry->type;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrSWInstalledDate:
+ return (string_get(value, entry->date, entry->date_len));
+ }
+ abort();
+}
+
+/**
+ * Scalars
+ */
+int
+op_hrSWInstalled(struct snmp_context *ctx __unused,
+ struct snmp_value *value __unused, u_int sub,
+ u_int iidx __unused, enum snmp_op curr_op)
+{
+
+ /* only SNMP GET is possible */
+ switch (curr_op) {
+
+ case SNMP_OP_GET:
+ goto get;
+
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ case SNMP_OP_GETNEXT:
+ abort();
+ }
+ abort();
+
+ get:
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_hrSWInstalledLastChange:
+ case LEAF_hrSWInstalledLastUpdateTime:
+ /*
+ * We always update the entire table so these two tick
+ * values should be equal.
+ */
+ refresh_swins_tbl();
+ if (swins_tick <= start_tick)
+ value->v.uint32 = 0;
+ else {
+ uint64_t lastChange = swins_tick - start_tick;
+
+ /* may overflow the SNMP type */
+ value->v.uint32 =
+ (lastChange > UINT_MAX ? UINT_MAX : lastChange);
+ }
+
+ return (SNMP_ERR_NOERROR);
+
+ default:
+ abort();
+ }
+}
diff --git a/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_swrun_tbl.c b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_swrun_tbl.c
new file mode 100644
index 0000000..f1a2f09
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_swrun_tbl.c
@@ -0,0 +1,792 @@
+/*
+ * Copyright (c) 2005-2006 The FreeBSD Project
+ * All rights reserved.
+ *
+ * Author: Victor Cruceru <soc-victor@freebsd.org>
+ *
+ * Redistribution of this software and documentation and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ *
+ * 1. Redistributions of source code or documentation must retain the above
+ * copyright notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ *
+ * Host Resources MIB for SNMPd. Implementation for hrSWRunTable
+ */
+
+#include <sys/param.h>
+#include <sys/proc.h>
+#include <sys/sysctl.h>
+#include <sys/user.h>
+#include <sys/linker.h>
+
+#include <assert.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "hostres_snmp.h"
+#include "hostres_oid.h"
+#include "hostres_tree.h"
+
+/*
+ * Ugly thing: PID_MAX, NO_PID defined only in kernel
+ */
+#define NO_PID 100000
+
+enum SWRunType {
+ SRT_UNKNOWN = 1,
+ SRT_OPERATING_SYSTEM = 2,
+ SRT_DEVICE_DRIVER = 3,
+ SRT_APPLICATION = 4
+
+};
+
+enum SWRunStatus {
+ SRS_RUNNING = 1,
+ SRS_RUNNABLE = 2,
+ SRS_NOT_RUNNABLE = 3,
+ SRS_INVALID = 4
+};
+
+/* Maximum lengths for the strings according to the MIB */
+#define SWR_NAME_MLEN (64 + 1)
+#define SWR_PATH_MLEN (128 + 1)
+#define SWR_PARAM_MLEN (128 + 1)
+
+/*
+ * This structure is used to hold a SNMP table entry
+ * for both hrSWRunTable and hrSWRunPerfTable because
+ * hrSWRunPerfTable AUGMENTS hrSWRunTable
+ */
+struct swrun_entry {
+ int32_t index;
+ u_char *name; /* it may be NULL */
+ const struct asn_oid *id;
+ u_char *path; /* it may be NULL */
+ u_char *parameters; /* it may be NULL */
+ int32_t type; /* enum SWRunType */
+ int32_t status; /* enum SWRunStatus */
+ int32_t perfCPU;
+ int32_t perfMemory;
+#define HR_SWRUN_FOUND 0x001
+ uint32_t flags;
+ uint64_t r_tick; /* tick when entry refreshed */
+ TAILQ_ENTRY(swrun_entry) link;
+};
+TAILQ_HEAD(swrun_tbl, swrun_entry);
+
+/* the head of the list with hrSWRunTable's entries */
+static struct swrun_tbl swrun_tbl = TAILQ_HEAD_INITIALIZER(swrun_tbl);
+
+/* last (agent) tick when hrSWRunTable and hrSWRunPerTable was updated */
+static uint64_t swrun_tick;
+
+/* maximum number of ticks between updates of SWRun and SWRunPerf table */
+uint32_t swrun_tbl_refresh = HR_SWRUN_TBL_REFRESH * 100;
+
+/* the value of the MIB object with the same name */
+static int32_t SWOSIndex;
+
+/**
+ * Malloc a new entry and add it to the list
+ * associated to this table. The item identified by
+ * the index parameter must not exist in this list.
+ */
+static struct swrun_entry *
+swrun_entry_create(int32_t idx)
+{
+ struct swrun_entry *entry;
+
+ if ((entry = malloc(sizeof(*entry))) == NULL) {
+ syslog(LOG_WARNING, "%s: %m", __func__);
+ return (NULL);
+ }
+ memset(entry, 0, sizeof(*entry));
+ entry->index = idx;
+
+ INSERT_OBJECT_INT(entry, &swrun_tbl);
+ return (entry);
+}
+
+/**
+ * Unlink the entry from the list and then free its heap memory
+ */
+static void
+swrun_entry_delete(struct swrun_entry *entry)
+{
+
+ assert(entry != NULL);
+
+ TAILQ_REMOVE(&swrun_tbl, entry, link);
+
+ free(entry->name);
+ free(entry->path);
+ free(entry->parameters);
+ free(entry);
+}
+
+/**
+ * Search one item by its index, return NULL if none found
+ */
+static struct swrun_entry *
+swrun_entry_find_by_index(int32_t idx)
+{
+ struct swrun_entry *entry;
+
+ TAILQ_FOREACH(entry, &swrun_tbl, link)
+ if (entry->index == idx)
+ return (entry);
+ return (NULL);
+}
+
+/**
+ * Translate the kernel's process status to SNMP.
+ */
+static enum SWRunStatus
+swrun_OS_get_proc_status(const struct kinfo_proc *kp)
+{
+
+ assert(kp != NULL);
+ if(kp == NULL) {
+ return (SRS_INVALID);
+ }
+
+ /*
+ * I'm using the old style flags - they look cleaner to me,
+ * at least for the purpose of this SNMP table
+ */
+ switch (kp->ki_stat) {
+
+ case SSTOP:
+ return (SRS_NOT_RUNNABLE);
+
+ case SWAIT:
+ case SLOCK:
+ case SSLEEP:
+ return (SRS_RUNNABLE);
+
+ case SZOMB:
+ return (SRS_INVALID);
+
+ case SIDL:
+ case SRUN:
+ return (SRS_RUNNING);
+
+ default:
+ syslog(LOG_ERR,"Unknown process state: %d", kp->ki_stat);
+ return (SRS_INVALID);
+ }
+}
+
+/**
+ * Make an SNMP table entry from a kernel one.
+ */
+static void
+kinfo_proc_to_swrun_entry(const struct kinfo_proc *kp,
+ struct swrun_entry *entry)
+{
+ char **argv = NULL;
+ uint64_t cpu_time = 0;
+ size_t pname_len;
+
+ pname_len = strlen(kp->ki_comm) + 1;
+ entry->name = reallocf(entry->name, pname_len);
+ if (entry->name != NULL)
+ strlcpy(entry->name, kp->ki_comm, pname_len);
+
+ entry->id = &oid_zeroDotZero; /* unknown id - FIXME */
+
+ assert(hr_kd != NULL);
+
+ argv = kvm_getargv(hr_kd, kp, SWR_PARAM_MLEN - 1);
+ if(argv != NULL){
+ u_char param[SWR_PARAM_MLEN];
+
+ memset(param, '\0', sizeof(param));
+
+ /*
+ * FIXME
+ * Path seems to not be available.
+ * Try to hack the info in argv[0];
+ * this argv is under control of the program so this info
+ * is not realiable
+ */
+ if(*argv != NULL && (*argv)[0] == '/') {
+ size_t path_len;
+
+ path_len = strlen(*argv) + 1;
+ if (path_len > SWR_PATH_MLEN)
+ path_len = SWR_PATH_MLEN;
+
+ entry->path = reallocf(entry->path, path_len);
+ if (entry->path != NULL) {
+ memset(entry->path, '\0', path_len);
+ strlcpy((char*)entry->path, *argv, path_len);
+ }
+ }
+
+ argv++; /* skip the first one which was used for path */
+
+ while (argv != NULL && *argv != NULL ) {
+ if (param[0] != 0) {
+ /*
+ * add a space between parameters,
+ * except before the first one
+ */
+ strlcat((char *)param, " ", sizeof(param));
+ }
+ strlcat((char *)param, *argv, sizeof(param));
+ argv++;
+ }
+ /* reuse pname_len */
+ pname_len = strlen(param) + 1;
+ if (pname_len > SWR_PARAM_MLEN)
+ pname_len = SWR_PARAM_MLEN;
+
+ entry->parameters = reallocf(entry->parameters, pname_len);
+ strlcpy(entry->parameters, param, pname_len);
+ }
+
+ entry->type = (int32_t)(IS_KERNPROC(kp) ? SRT_OPERATING_SYSTEM :
+ SRT_APPLICATION);
+
+ entry->status = (int32_t)swrun_OS_get_proc_status(kp);
+ cpu_time = kp->ki_runtime / 100000; /* centi-seconds */
+
+ /* may overflow the snmp type */
+ entry->perfCPU = (cpu_time > (uint64_t)INT_MAX ? INT_MAX : cpu_time);
+ entry->perfMemory = kp->ki_size / 1024; /* in kilo-bytes */
+ entry->r_tick = get_ticks();
+}
+
+/**
+ * Create a table entry for a KLD
+ */
+static void
+kld_file_stat_to_swrun(const struct kld_file_stat *kfs,
+ struct swrun_entry *entry)
+{
+ size_t name_len;
+
+ assert(kfs != NULL);
+ assert(entry != NULL);
+
+ name_len = strlen(kfs->name) + 1;
+ if (name_len > SWR_NAME_MLEN)
+ name_len = SWR_NAME_MLEN;
+
+ entry->name = reallocf(entry->name, name_len);
+ if (entry->name != NULL)
+ strlcpy((char *)entry->name, kfs->name, name_len);
+
+ /* FIXME: can we find the location where the module was loaded from? */
+ entry->path = NULL;
+
+ /* no parameters for kernel files (.ko) of for the kernel */
+ entry->parameters = NULL;
+
+ entry->id = &oid_zeroDotZero; /* unknown id - FIXME */
+
+ if (strcmp(kfs->name, "kernel") == 0) {
+ entry->type = (int32_t)SRT_OPERATING_SYSTEM;
+ SWOSIndex = entry->index;
+ } else {
+ entry->type = (int32_t)SRT_DEVICE_DRIVER; /* well, not really */
+ }
+ entry->status = (int32_t)SRS_RUNNING;
+ entry->perfCPU = 0; /* Info not available */
+ entry->perfMemory = kfs->size / 1024; /* in kilo-bytes */
+ entry->r_tick = get_ticks();
+}
+
+/**
+ * Get all visible processes including the kernel visible threads
+ */
+static void
+swrun_OS_get_procs(void)
+{
+ struct kinfo_proc *plist, *kp;
+ int i;
+ int nproc;
+ struct swrun_entry *entry;
+
+ plist = kvm_getprocs(hr_kd, KERN_PROC_ALL, 0, &nproc);
+ if (plist == NULL || nproc < 0) {
+ syslog(LOG_ERR, "kvm_getprocs() failed: %m");
+ return;
+ }
+ for (i = 0, kp = plist; i < nproc; i++, kp++) {
+ /*
+ * The SNMP table's index must begin from 1 (as specified by
+ * this table definition), the PIDs are starting from 0
+ * so we are translating the PIDs to +1
+ */
+ entry = swrun_entry_find_by_index((int32_t)kp->ki_pid + 1);
+ if (entry == NULL) {
+ /* new entry - get memory for it */
+ entry = swrun_entry_create((int32_t)kp->ki_pid + 1);
+ if (entry == NULL)
+ continue;
+ }
+ entry->flags |= HR_SWRUN_FOUND; /* mark it as found */
+
+ kinfo_proc_to_swrun_entry(kp, entry);
+ }
+}
+
+/*
+ * Get kernel items: first the kernel itself, then the loaded modules.
+ */
+static void
+swrun_OS_get_kinfo(void)
+{
+ int fileid;
+ struct swrun_entry *entry;
+ struct kld_file_stat stat;
+
+ for (fileid = kldnext(0); fileid > 0; fileid = kldnext(fileid)) {
+ stat.version = sizeof(struct kld_file_stat);
+ if (kldstat(fileid, &stat) < 0) {
+ syslog(LOG_ERR, "kldstat() failed: %m");
+ continue;
+ }
+
+ /*
+ * kernel and kernel files (*.ko) will be indexed starting with
+ * NO_PID + 1; NO_PID is PID_MAX + 1 thus it will be no risk to
+ * overlap with real PIDs which are in range of 1 .. NO_PID
+ */
+ entry = swrun_entry_find_by_index(NO_PID + 1 + stat.id);
+ if (entry == NULL) {
+ /* new entry - get memory for it */
+ entry = swrun_entry_create(NO_PID + 1 + stat.id);
+ if (entry == NULL)
+ continue;
+ }
+ entry->flags |= HR_SWRUN_FOUND; /* mark it as found */
+
+ kld_file_stat_to_swrun(&stat, entry);
+ }
+}
+
+/**
+ * Refresh the hrSWRun and hrSWRunPert tables.
+ */
+static void
+refresh_swrun_tbl(void)
+{
+
+ struct swrun_entry *entry, *entry_tmp;
+
+ if (this_tick - swrun_tick < swrun_tbl_refresh) {
+ HRDBG("no refresh needed ");
+ return;
+ }
+
+ /* mark each entry as missing */
+ TAILQ_FOREACH(entry, &swrun_tbl, link)
+ entry->flags &= ~HR_SWRUN_FOUND;
+
+ swrun_OS_get_procs();
+ swrun_OS_get_kinfo();
+
+ /*
+ * Purge items that disappeared
+ */
+ TAILQ_FOREACH_SAFE(entry, &swrun_tbl, link, entry_tmp)
+ if (!(entry->flags & HR_SWRUN_FOUND))
+ swrun_entry_delete(entry);
+
+ swrun_tick = this_tick;
+
+ HRDBG("refresh DONE");
+}
+
+/**
+ * Update the information in this entry
+ */
+static void
+fetch_swrun_entry(struct swrun_entry *entry)
+{
+ struct kinfo_proc *plist;
+ int nproc;
+ struct kld_file_stat stat;
+
+ assert(entry != NULL);
+
+ if (entry->index >= NO_PID + 1) {
+ /*
+ * kernel and kernel files (*.ko) will be indexed
+ * starting with NO_PID + 1; NO_PID is PID_MAX + 1
+ * thus it will be no risk to overlap with real PIDs
+ * which are in range of 1 .. NO_PID
+ */
+ stat.version = sizeof(stat);
+ if (kldstat(entry->index - NO_PID - 1, &stat) == -1) {
+ /*
+ * not found, it's gone. Mark it as invalid for now, it
+ * will be removed from the list at next global refersh
+ */
+ HRDBG("missing item with kid=%d",
+ entry->index - NO_PID - 1);
+ entry->status = (int32_t)SRS_INVALID;
+ } else
+ kld_file_stat_to_swrun(&stat, entry);
+
+ } else {
+ /* this is a process */
+ assert(hr_kd != NULL);
+ plist = kvm_getprocs(hr_kd, KERN_PROC_PID,
+ entry->index - 1, &nproc);
+ if (plist == NULL || nproc != 1) {
+ HRDBG("missing item with PID=%d", entry->index - 1);
+ entry->status = (int32_t)SRS_INVALID;
+ } else
+ kinfo_proc_to_swrun_entry(plist, entry);
+ }
+}
+
+/**
+ * Invalidate entry. For KLDs we try to unload it, for processes we SIGKILL it.
+ */
+static int
+invalidate_swrun_entry(struct swrun_entry *entry, int commit)
+{
+ struct kinfo_proc *plist;
+ int nproc;
+ struct kld_file_stat stat;
+
+ assert(entry != NULL);
+
+ if (entry->index >= NO_PID + 1) {
+ /* this is a kernel item */
+ HRDBG("atempt to unload KLD %d",
+ entry->index - NO_PID - 1);
+
+ if (entry->index == SWOSIndex) {
+ /* can't invalidate the kernel itself */
+ return (SNMP_ERR_NOT_WRITEABLE);
+ }
+
+ stat.version = sizeof(stat);
+ if (kldstat(entry->index - NO_PID - 1, &stat) == -1) {
+ /*
+ * not found, it's gone. Mark it as invalid for now, it
+ * will be removed from the list at next global
+ * refresh
+ */
+ HRDBG("missing item with kid=%d",
+ entry->index - NO_PID - 1);
+ entry->status = (int32_t)SRS_INVALID;
+ return (SNMP_ERR_NOERROR);
+ }
+ /*
+ * There is no way to try to unload a module. There seems
+ * also no way to find out whether it is busy without unloading
+ * it. We can assume that it is busy, if the reference count
+ * is larger than 2, but if it is 1 nothing helps.
+ */
+ if (!commit) {
+ if (stat.refs > 1)
+ return (SNMP_ERR_NOT_WRITEABLE);
+ return (SNMP_ERR_NOERROR);
+ }
+ if (kldunload(stat.id) == -1) {
+ syslog(LOG_ERR,"kldunload for %d/%s failed: %m",
+ stat.id, stat.name);
+ if (errno == EBUSY)
+ return (SNMP_ERR_NOT_WRITEABLE);
+ else
+ return (SNMP_ERR_RES_UNAVAIL);
+ }
+ } else {
+ /* this is a process */
+ assert(hr_kd != NULL);
+
+ plist = kvm_getprocs(hr_kd, KERN_PROC_PID,
+ entry->index - 1, &nproc);
+ if (plist == NULL || nproc != 1) {
+ HRDBG("missing item with PID=%d", entry->index - 1);
+ entry->status = (int32_t)SRS_INVALID;
+ return (SNMP_ERR_NOERROR);
+ }
+ if (IS_KERNPROC(plist)) {
+ /* you don't want to do this */
+ return (SNMP_ERR_NOT_WRITEABLE);
+ }
+ if (kill(entry->index - 1, commit ? SIGKILL : 0) < 0) {
+ syslog(LOG_ERR,"kill (%d, SIGKILL) failed: %m",
+ entry->index - 1);
+ if (errno == ESRCH) {
+ /* race: just gone */
+ entry->status = (int32_t)SRS_INVALID;
+ return (SNMP_ERR_NOERROR);
+ }
+ return (SNMP_ERR_GENERR);
+ }
+ }
+ return (SNMP_ERR_NOERROR);
+}
+
+/**
+ * Popuplate the hrSWRunTable.
+ */
+void
+init_swrun_tbl(void)
+{
+
+ refresh_swrun_tbl();
+ HRDBG("done");
+}
+
+/**
+ * Finalize the hrSWRunTable.
+ */
+void
+fini_swrun_tbl(void)
+{
+ struct swrun_entry *n1;
+
+ while ((n1 = TAILQ_FIRST(&swrun_tbl)) != NULL) {
+ TAILQ_REMOVE(&swrun_tbl, n1, link);
+ free(n1);
+ }
+}
+
+/*
+ * This is the implementation for a generated (by a SNMP tool)
+ * function prototype, see hostres_tree.h
+ * It hanldes the SNMP operations for hrSWRunTable
+ */
+int
+op_hrSWRunTable(struct snmp_context *ctx __unused, struct snmp_value *value,
+ u_int sub, u_int iidx __unused, enum snmp_op curr_op)
+{
+ struct swrun_entry *entry;
+ int ret;
+
+ refresh_swrun_tbl();
+
+ switch (curr_op) {
+
+ case SNMP_OP_GETNEXT:
+ if ((entry = NEXT_OBJECT_INT(&swrun_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ value->var.len = sub + 1;
+ value->var.subs[sub] = entry->index;
+ goto get;
+
+ case SNMP_OP_GET:
+ if ((entry = FIND_OBJECT_INT(&swrun_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_SET:
+ if ((entry = FIND_OBJECT_INT(&swrun_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NO_CREATION);
+
+ if (entry->r_tick < this_tick)
+ fetch_swrun_entry(entry);
+
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_hrSWRunStatus:
+ if (value->v.integer != (int32_t)SRS_INVALID)
+ return (SNMP_ERR_WRONG_VALUE);
+
+ if (entry->status == (int32_t)SRS_INVALID)
+ return (SNMP_ERR_NOERROR);
+
+ /*
+ * Here we have a problem with the entire SNMP
+ * model: if we kill now, we cannot rollback.
+ * If we kill in the commit code, we cannot
+ * return an error. Because things may change between
+ * SET and COMMIT this is impossible to handle
+ * correctly.
+ */
+ return (invalidate_swrun_entry(entry, 0));
+ }
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case SNMP_OP_ROLLBACK:
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_COMMIT:
+ if ((entry = FIND_OBJECT_INT(&swrun_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NOERROR);
+
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_hrSWRunStatus:
+ if (value->v.integer == (int32_t)SRS_INVALID &&
+ entry->status != (int32_t)SRS_INVALID)
+ (void)invalidate_swrun_entry(entry, 1);
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+ }
+ abort();
+
+ get:
+ ret = SNMP_ERR_NOERROR;
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_hrSWRunIndex:
+ value->v.integer = entry->index;
+ break;
+
+ case LEAF_hrSWRunName:
+ if (entry->name != NULL)
+ ret = string_get(value, entry->name, -1);
+ else
+ ret = string_get(value, "", -1);
+ break;
+
+ case LEAF_hrSWRunID:
+ assert(entry->id != NULL);
+ value->v.oid = *entry->id;
+ break;
+
+ case LEAF_hrSWRunPath:
+ if (entry->path != NULL)
+ ret = string_get(value, entry->path, -1);
+ else
+ ret = string_get(value, "", -1);
+ break;
+
+ case LEAF_hrSWRunParameters:
+ if (entry->parameters != NULL)
+ ret = string_get(value, entry->parameters, -1);
+ else
+ ret = string_get(value, "", -1);
+ break;
+
+ case LEAF_hrSWRunType:
+ value->v.integer = entry->type;
+ break;
+
+ case LEAF_hrSWRunStatus:
+ value->v.integer = entry->status;
+ break;
+
+ default:
+ abort();
+ }
+ return (ret);
+}
+
+/**
+ * Scalar(s) in the SWRun group
+ */
+int
+op_hrSWRun(struct snmp_context *ctx __unused, struct snmp_value *value,
+ u_int sub, u_int iidx __unused, enum snmp_op curr_op)
+{
+
+ /* only SNMP GET is possible */
+ switch (curr_op) {
+
+ case SNMP_OP_GET:
+ goto get;
+
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ case SNMP_OP_GETNEXT:
+ abort();
+ }
+ abort();
+
+ get:
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_hrSWOSIndex:
+ value->v.uint32 = SWOSIndex;
+ return (SNMP_ERR_NOERROR);
+
+ default:
+ abort();
+ }
+}
+
+/*
+ * This is the implementation for a generated (by a SNMP tool)
+ * function prototype, see hostres_tree.h
+ * It handles the SNMP operations for hrSWRunPerfTable
+ */
+int
+op_hrSWRunPerfTable(struct snmp_context *ctx __unused,
+ struct snmp_value *value, u_int sub, u_int iidx __unused,
+ enum snmp_op curr_op )
+{
+ struct swrun_entry *entry;
+
+ refresh_swrun_tbl();
+
+ switch (curr_op) {
+
+ case SNMP_OP_GETNEXT:
+ if ((entry = NEXT_OBJECT_INT(&swrun_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ value->var.len = sub + 1;
+ value->var.subs[sub] = entry->index;
+ goto get;
+
+ case SNMP_OP_GET:
+ if ((entry = FIND_OBJECT_INT(&swrun_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get;
+
+ case SNMP_OP_SET:
+ if ((entry = FIND_OBJECT_INT(&swrun_tbl,
+ &value->var, sub)) == NULL)
+ return (SNMP_ERR_NO_CREATION);
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ abort();
+ }
+ abort();
+
+ get:
+ switch (value->var.subs[sub - 1]) {
+
+ case LEAF_hrSWRunPerfCPU:
+ value->v.integer = entry->perfCPU;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_hrSWRunPerfMem:
+ value->v.integer = entry->perfMemory;
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+}
diff --git a/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_tree.def b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_tree.def
new file mode 100644
index 0000000..83c187b
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_tree.def
@@ -0,0 +1,292 @@
+#
+# Copyright (c) 2005-2006 The FreeBSD Project
+# All rights reserved.
+#
+# Author: Victor Cruceru <soc-victor@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$
+#
+# This is the .def file for both HOST-RESOURCES-MIB and HOST-RESOURCES-TYPES
+#
+
+(1 internet
+ (2 mgmt
+ (1 mib_2
+ (25 host
+ (1 hrSystem
+ (1 hrSystemUptime TIMETICKS op_hrSystem GET)
+ (2 hrSystemDate OCTETSTRING op_hrSystem GET SET)
+ (3 hrSystemInitialLoadDevice INTEGER op_hrSystem GET SET)
+ (4 hrSystemInitialLoadParameters OCTETSTRING op_hrSystem GET SET)
+ (5 hrSystemNumUsers GAUGE op_hrSystem GET)
+ (6 hrSystemProcesses GAUGE op_hrSystem GET)
+ (7 hrSystemMaxProcesses INTEGER op_hrSystem GET)
+ )
+ (2 hrStorage
+ (1 hrStorageTypes
+ (1 hrStorageOther
+ )
+ (2 hrStorageRam
+ )
+ (3 hrStorageVirtualMemory
+ )
+ (4 hrStorageFixedDisk
+ )
+ (5 hrStorageRemovableDisk
+ )
+ (6 hrStorageFloppyDisk
+ )
+ (7 hrStorageCompactDisc
+ )
+ (8 hrStorageRamDisk
+ )
+ (9 hrStorageFlashMemory
+ )
+ (10 hrStorageNetworkDisk
+ )
+ )
+ (2 hrMemorySize INTEGER op_hrStorage GET)
+ (3 hrStorageTable
+ (1 hrStorageEntry : INTEGER op_hrStorageTable
+ (1 hrStorageIndex INTEGER GET)
+ (2 hrStorageType OID GET)
+ (3 hrStorageDescr OCTETSTRING GET)
+ (4 hrStorageAllocationUnits INTEGER GET)
+ (5 hrStorageSize INTEGER GET SET)
+ (6 hrStorageUsed INTEGER GET)
+ (7 hrStorageAllocationFailures COUNTER GET)
+ )
+ )
+ )
+ (3 hrDevice
+ (1 hrDeviceTypes
+ (1 hrDeviceOther
+ )
+ (2 hrDeviceUnknown
+ )
+ (3 hrDeviceProcessor
+ )
+ (4 hrDeviceNetwork
+ )
+ (5 hrDevicePrinter
+ )
+ (6 hrDeviceDiskStorage
+ )
+ (10 hrDeviceVideo
+ )
+ (11 hrDeviceAudio
+ )
+ (12 hrDeviceCoprocessor
+ )
+ (13 hrDeviceKeyboard
+ )
+ (14 hrDeviceModem
+ )
+ (15 hrDeviceParallelPort
+ )
+ (16 hrDevicePointing
+ )
+ (17 hrDeviceSerialPort
+ )
+ (18 hrDeviceTape
+ )
+ (19 hrDeviceClock
+ )
+ (20 hrDeviceVolatileMemory
+ )
+ (21 hrDeviceNonVolatileMemory
+ )
+ )
+ (2 hrDeviceTable
+ (1 hrDeviceEntry : INTEGER op_hrDeviceTable
+ (1 hrDeviceIndex INTEGER GET)
+ (2 hrDeviceType OID GET)
+ (3 hrDeviceDescr OCTETSTRING GET)
+ (4 hrDeviceID OID GET)
+ (5 hrDeviceStatus INTEGER GET)
+ (6 hrDeviceErrors COUNTER GET)
+ )
+ )
+ (3 hrProcessorTable
+ (1 hrProcessorEntry : INTEGER op_hrProcessorTable
+ (1 hrProcessorFrwID OID GET)
+ (2 hrProcessorLoad INTEGER GET)
+ )
+ )
+ (4 hrNetworkTable
+ (1 hrNetworkEntry : INTEGER op_hrNetworkTable
+ (1 hrNetworkIfIndex INTEGER GET)
+ )
+ )
+ (5 hrPrinterTable
+ (1 hrPrinterEntry : INTEGER op_hrPrinterTable
+ (1 hrPrinterStatus INTEGER GET)
+ (2 hrPrinterDetectedErrorState OCTETSTRING GET)
+ )
+ )
+ (6 hrDiskStorageTable
+ (1 hrDiskStorageEntry : INTEGER op_hrDiskStorageTable
+ (1 hrDiskStorageAccess INTEGER GET)
+ (2 hrDiskStorageMedia INTEGER GET)
+ (3 hrDiskStorageRemovable INTEGER GET)
+ (4 hrDiskStorageCapacity INTEGER GET)
+ )
+ )
+ (7 hrPartitionTable
+ (1 hrPartitionEntry : INTEGER INTEGER op_hrPartitionTable
+ (1 hrPartitionIndex INTEGER GET)
+ (2 hrPartitionLabel OCTETSTRING GET)
+ (3 hrPartitionID OCTETSTRING GET)
+ (4 hrPartitionSize INTEGER GET)
+ (5 hrPartitionFSIndex INTEGER GET)
+ )
+ )
+ (8 hrFSTable
+ (1 hrFSEntry : INTEGER op_hrFSTable
+ (1 hrFSIndex INTEGER GET)
+ (2 hrFSMountPoint OCTETSTRING GET)
+ (3 hrFSRemoteMountPoint OCTETSTRING GET)
+ (4 hrFSType OID GET)
+ (5 hrFSAccess INTEGER GET)
+ (6 hrFSBootable INTEGER GET)
+ (7 hrFSStorageIndex INTEGER GET)
+ (8 hrFSLastFullBackupDate OCTETSTRING GET SET)
+ (9 hrFSLastPartialBackupDate OCTETSTRING GET SET)
+ )
+ )
+ (9 hrFSTypes
+ (1 hrFSOther
+ )
+ (2 hrFSUnknown
+ )
+ (3 hrFSBerkeleyFFS
+ )
+ (4 hrFSSys5FS
+ )
+ (5 hrFSFat
+ )
+ (6 hrFSHPFS
+ )
+ (7 hrFSHFS
+ )
+ (8 hrFSMFS
+ )
+ (9 hrFSNTFS
+ )
+ (10 hrFSVNode
+ )
+ (11 hrFSJournaled
+ )
+ (12 hrFSiso9660
+ )
+ (13 hrFSRockRidge
+ )
+ (14 hrFSNFS
+ )
+ (15 hrFSNetware
+ )
+ (16 hrFSAFS
+ )
+ (17 hrFSDFS
+ )
+ (18 hrFSAppleshare
+ )
+ (19 hrFSRFS
+ )
+ (20 hrFSDGCFS
+ )
+ (21 hrFSBFS
+ )
+ (22 hrFSFAT32
+ )
+ (23 hrFSLinuxExt2
+ )
+ )
+ )
+ (4 hrSWRun
+ (1 hrSWOSIndex INTEGER op_hrSWRun GET)
+ (2 hrSWRunTable
+ (1 hrSWRunEntry : INTEGER op_hrSWRunTable
+ (1 hrSWRunIndex INTEGER GET)
+ (2 hrSWRunName OCTETSTRING GET)
+ (3 hrSWRunID OID GET)
+ (4 hrSWRunPath OCTETSTRING GET)
+ (5 hrSWRunParameters OCTETSTRING GET)
+ (6 hrSWRunType INTEGER GET)
+ (7 hrSWRunStatus INTEGER GET SET)
+ )
+ )
+ )
+ (5 hrSWRunPerf
+ (1 hrSWRunPerfTable
+ (1 hrSWRunPerfEntry : INTEGER op_hrSWRunPerfTable
+ (1 hrSWRunPerfCPU INTEGER GET)
+ (2 hrSWRunPerfMem INTEGER GET)
+ )
+ )
+ )
+ (6 hrSWInstalled
+ (1 hrSWInstalledLastChange TIMETICKS op_hrSWInstalled GET)
+ (2 hrSWInstalledLastUpdateTime TIMETICKS op_hrSWInstalled GET)
+ (3 hrSWInstalledTable
+ (1 hrSWInstalledEntry : INTEGER op_hrSWInstalledTable
+ (1 hrSWInstalledIndex INTEGER GET)
+ (2 hrSWInstalledName OCTETSTRING GET)
+ (3 hrSWInstalledID OID GET)
+ (4 hrSWInstalledType INTEGER GET)
+ (5 hrSWInstalledDate OCTETSTRING GET)
+ )
+ )
+ )
+ (7 hrMIBAdminInfo
+ (1 hostResourcesMibModule
+ )
+ (2 hrMIBCompliances
+ )
+ (3 hrMIBGroups
+ )
+ )
+ )
+ )
+ )
+ (4 private
+ (1 enterprises
+ (12325 fokus
+ (1 begemot
+ (202 begemotHostres
+ (1 begemotHostresObjects
+ (1 begemotHrStorageUpdate TIMETICKS op_begemot GET SET)
+ (2 begemotHrFSUpdate TIMETICKS op_begemot GET SET)
+ (3 begemotHrDiskStorageUpdate TIMETICKS op_begemot GET SET)
+ (4 begemotHrNetworkUpdate TIMETICKS op_begemot GET SET)
+ (5 begemotHrSWInstalledUpdate TIMETICKS op_begemot GET SET)
+ (6 begemotHrSWRunUpdate TIMETICKS op_begemot GET SET)
+ (7 begemotHrPkgDir OCTETSTRING op_begemot GET SET)
+ )
+ )
+ )
+ )
+ )
+ )
+)
diff --git a/usr.sbin/bsnmpd/modules/snmp_hostres/snmp_hostres.3 b/usr.sbin/bsnmpd/modules/snmp_hostres/snmp_hostres.3
new file mode 100644
index 0000000..73036c0
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_hostres/snmp_hostres.3
@@ -0,0 +1,88 @@
+.\"
+.\" Copyright (C) 2005-2006
+.\" The FreeBSD Project.
+.\" All rights reserved.
+.\"
+.\" Author: Harti 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 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 January 3, 2006
+.Dt SNMP_HOSTRES 3
+.Os
+.Sh NAME
+.Nm snmp_hostres
+.Nd host resources module for
+.Xr bsnmpd 8
+.Sh LIBRARY
+.Pq begemotSnmpdModulePath."hostres" = "/usr/lib/snmp_hostres.so"
+.Sh DESCRIPTION
+The
+.Nm
+module implements the HOST-RESOURCES-MIB as standardized in RFC 2790.
+.Sh RESTRICTIONS
+Not all information in the MIB is meaningful in FreeBSD or is available.
+The following variables are not implemented or carry no information:
+.Bl -tag -width indent
+.It Va hrFSType
+There are several types of file systems for which no appropriate OID
+exists yet which are supported by
+.Fx .
+For smbfs, procfs and devfs ,
+.Va hrFSOther
+is returned.
+In all other cases,
+.Va hrFSUnknown .
+.It Va hrFSBootable
+It is questionable what bootable means here.
+Does it mean that the BIOS is available to start a boot on that file system
+or does it mean that there is something bootable?
+In either case this information is not available so this variable returns True
+for the root file system (which is not necessarily correct) and False for
+all others.
+.It Va hrFSLastFullBackupDate , hrFSLastPartialBackupDate
+This is not available and always returns an empty string.
+Theoretically, this could be retrieved from
+.Pa /etc/dumpdates ,
+which would
+hardly be correct given the different ways of doing backups.
+.It Va hrDiskStorageTable
+Floppy devices are currently not reported.
+Also the names of the disks are hard-coded in the module.
+.El
+.Sh FILES
+.Bl -tag -width indent
+.It Pa /usr/share/snmp/defs/hostres_tree.def
+The description of the MIB tree implemented by
+.Nm .
+.It Pa /usr/share/snmp/mibs/HOST-RESOURCES-TYPES.txt
+.It Pa /usr/share/snmp/mibs/HOST-RESOURCES-MIB.txt
+.It Pa /usr/share/snmp/mibs/BEGEMOT-HOSTRES-MIB.txt
+This is the MIB that is implemented by this module.
+.El
+.Sh SEE ALSO
+.Xr gensnmptree 1 ,
+.Xr snmpmod 3
+.Sh AUTHORS
+.An Victor Cruceru Aq Mt soc-victor@FreeBSD.org
diff --git a/usr.sbin/bsnmpd/modules/snmp_lm75/BEGEMOT-LM75-MIB.txt b/usr.sbin/bsnmpd/modules/snmp_lm75/BEGEMOT-LM75-MIB.txt
new file mode 100644
index 0000000..f8f52a6
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_lm75/BEGEMOT-LM75-MIB.txt
@@ -0,0 +1,160 @@
+--
+-- Copyright (c) 2014 Luiz Otavio O Souza <loos@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 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$
+--
+
+BEGEMOT-LM75-MIB DEFINITIONS ::= BEGIN
+
+IMPORTS
+ MODULE-IDENTITY, OBJECT-TYPE, NOTIFICATION-TYPE,
+ Counter64, Integer32
+ FROM SNMPv2-SMI
+ TEXTUAL-CONVENTION, RowStatus
+ FROM SNMPv2-TC
+ begemot
+ FROM BEGEMOT-MIB;
+
+begemotLoos MODULE-IDENTITY
+ LAST-UPDATED "201402240000Z"
+ ORGANIZATION "FreeBSD"
+ CONTACT-INFO
+ " Luiz Otavio O Souza
+
+ Postal: N/A
+
+ Fax: N/A
+
+ E-Mail: loos@FreeBSD.org"
+ DESCRIPTION
+ "The Begemot MIB for reading lm75 sensors data."
+ REVISION "201402240000Z"
+ DESCRIPTION
+ "Initial revision."
+ ::= { begemot 400 }
+
+begemotLm75Objects OBJECT IDENTIFIER ::= { begemotLm75 1 }
+
+-- ---------------------------------------------------------- --
+-- Configuration parameters
+-- ---------------------------------------------------------- --
+
+lm75Sensor OBJECT IDENTIFIER ::= { begemotlm75Objects 1 }
+
+lm75Sensors OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of LM75 sensors in the system."
+ ::= { lm75Sensors 1 }
+
+-- ---------------------------------------------------------- --
+-- TempSensor Table
+-- ---------------------------------------------------------- --
+lm75SensorTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF Lm75SensorEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table containing information about all temperature sensors."
+ ::= { begemotLm75Objects 2 }
+
+loosTempSensorEntry OBJECT-TYPE
+ SYNTAX Lm75SensorEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Table entry that describes one temperature sensor."
+ INDEX { lm75SensorIndex }
+ ::= { lm75SensorTable 1 }
+
+Lm75SensorEntry ::= SEQUENCE {
+ lm75SensorIndex Integer32,
+ lm75SensorSysctlIndex Integer32,
+ lm75SensorDesc OCTET STRING,
+ lm75SensorLocation OCTET STRING,
+ lm75SensorPnpInfo OCTET STRING,
+ lm75SensorParent OCTET STRING,
+ lm75SensorTemperature Integer32
+}
+
+lm75SensorIndex OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "LM75 Sensor index."
+ ::= { lm75SensorEntry 1 }
+
+lm75SensorSysctlIndex OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "LM75 Sensor sysctl index."
+ ::= { lm75SensorEntry 2 }
+
+lm75SensorDesc OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "LM75 Sensor description."
+ ::= { lm75SensorEntry 3 }
+
+lm75SensorLocation OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "LM75 Sensor location."
+ ::= { lm75SensorEntry 4 }
+
+lm75SensorPnpInfo OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "LM75 Sensor pnp information."
+ ::= { lm75SensorEntry 5 }
+
+lm75SensorParent OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "LM75 Sensor parent bus."
+ ::= { lm75SensorEntry 6 }
+
+lm75SensorTemperature OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "LM75 Sensor temperature."
+ ::= { lm75SensorEntry 7 }
+
+END
diff --git a/usr.sbin/bsnmpd/modules/snmp_lm75/Makefile b/usr.sbin/bsnmpd/modules/snmp_lm75/Makefile
new file mode 100644
index 0000000..f9ee77d
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_lm75/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+.include <bsd.own.mk>
+
+MOD= lm75
+SRCS= snmp_lm75.c
+XSYM= begemotLm75
+MAN= snmp_lm75.3
+
+BMIBS= BEGEMOT-LM75-MIB.txt
+DEFS= ${MOD}_tree.def
+
+.include <bsd.snmpmod.mk>
diff --git a/usr.sbin/bsnmpd/modules/snmp_lm75/Makefile.depend b/usr.sbin/bsnmpd/modules/snmp_lm75/Makefile.depend
new file mode 100644
index 0000000..e3d0506
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_lm75/Makefile.depend
@@ -0,0 +1,28 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbsnmp/libbsnmp \
+ lib/libc \
+ lib/libcompiler_rt \
+ usr.sbin/bsnmpd/modules \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+lm75_tree.So: lm75_tree.c
+lm75_tree.So: lm75_tree.h
+lm75_tree.po: lm75_tree.c
+lm75_tree.po: lm75_tree.h
+snmp_lm75.So: lm75_oid.h
+snmp_lm75.So: lm75_tree.h
+snmp_lm75.po: lm75_oid.h
+snmp_lm75.po: lm75_tree.h
+.endif
diff --git a/usr.sbin/bsnmpd/modules/snmp_lm75/lm75_tree.def b/usr.sbin/bsnmpd/modules/snmp_lm75/lm75_tree.def
new file mode 100644
index 0000000..3bfcd8d
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_lm75/lm75_tree.def
@@ -0,0 +1,56 @@
+#-
+# Copyright (c) 2014 Luiz Otavio O Souza <loos@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 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$
+#
+
+(1 internet
+ (4 private
+ (1 enterprises
+ (12325 fokus
+ (1 begemot
+ (400 begemotLm75
+ (1 begemotLm75Objects
+ (1 lm75Sensors
+ (1 lm75Sensors INTEGER32 op_lm75Sensors GET)
+ )
+ (2 lm75SensorTable
+ (1 lm75SensorEntry : OCTETSTRING op_lm75SensorTable
+ (1 lm75SensorIndex INTEGER32 GET)
+ (2 lm75SensorSysctlIndex INTEGER32 GET)
+ (3 lm75SensorDesc OCTETSTRING GET)
+ (4 lm75SensorLocation OCTETSTRING GET)
+ (5 lm75SensorPnpInfo OCTETSTRING GET)
+ (6 lm75SensorParent OCTETSTRING GET)
+ (7 lm75SensorTemperature INTEGER32 GET)
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+)
diff --git a/usr.sbin/bsnmpd/modules/snmp_lm75/snmp_lm75.3 b/usr.sbin/bsnmpd/modules/snmp_lm75/snmp_lm75.3
new file mode 100644
index 0000000..cd9d579
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_lm75/snmp_lm75.3
@@ -0,0 +1,60 @@
+.\"-
+.\" Copyright (c) 2014 Luiz Otavio O Souza <loos@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 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 June 2, 2014
+.Dt SNMP_LM75 3
+.Os
+.Sh NAME
+.Nm snmp_lm75
+.Nd "LM75 Sensor module for"
+.Xr bsnmpd 1
+.Sh LIBRARY
+.Pq begemotSnmpdModulePath."lm75" = "/usr/lib/snmp_lm75.so"
+.Sh DESCRIPTION
+The
+.Nm snmp_lm75
+module implements a private BEGEMOT-LM75-MIB, which allows
+reading the temperature of the LM75 sensors on the system.
+.Pp
+The module reads the sensor(s) temperature using the
+.Xr sysctl 8
+API.
+.Sh FILES
+.Bl -tag -width "XXXXXXXXX"
+.It Pa /usr/share/snmp/defs/lm75_tree.def
+The description of the MIB tree implemented by
+.Nm .
+.It Pa /usr/share/snmp/mibs/BEGEMOT-LM75-MIB.txt
+The private BEGEMOT-LM75-MIB that is implemented by this module.
+.El
+.Sh SEE ALSO
+.Xr bsnmpd 1 ,
+.Xr gensnmptree 1 ,
+.Xr snmpmod 3 ,
+.Xr lm75 4
+.Sh AUTHORS
+.An Luiz Otavio O Souza Aq Mt loos@FreeBSD.org
diff --git a/usr.sbin/bsnmpd/modules/snmp_lm75/snmp_lm75.c b/usr.sbin/bsnmpd/modules/snmp_lm75/snmp_lm75.c
new file mode 100644
index 0000000..1822f78
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_lm75/snmp_lm75.c
@@ -0,0 +1,435 @@
+/*-
+ * Copyright (c) 2014 Luiz Otavio O Souza <loos@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 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/queue.h>
+#include <sys/sysctl.h>
+
+#include <bsnmp/snmpmod.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "lm75_oid.h"
+#include "lm75_tree.h"
+
+#ifndef LM75BUF
+#define LM75BUF 64
+#endif
+#define TZ_ZEROC 2732
+#define UPDATE_INTERVAL 500 /* update interval in ticks */
+
+static struct lmodule *module;
+
+static const struct asn_oid oid_lm75 = OIDX_begemotLm75;
+
+/* the Object Resource registration index */
+static u_int lm75_index = 0;
+
+/* Number of available sensors in the system. */
+static int lm75_sensors;
+
+/*
+ * Structure that describes single sensor.
+ */
+struct lm75_snmp_sensor {
+ TAILQ_ENTRY(lm75_snmp_sensor) link;
+ int32_t index;
+ int32_t sysctlidx;
+ int32_t temp;
+ char desc[LM75BUF];
+ char location[LM75BUF];
+ char parent[LM75BUF];
+ char pnpinfo[LM75BUF];
+};
+
+static TAILQ_HEAD(, lm75_snmp_sensor) sensors =
+ TAILQ_HEAD_INITIALIZER(sensors);
+
+/* Ticks of the last sensors reading. */
+static uint64_t last_sensors_update;
+
+static void free_sensors(void);
+static int lm75_fini(void);
+static int lm75_init(struct lmodule *mod, int argc, char *argv[]);
+static void lm75_start(void);
+static int update_sensors(void);
+
+const struct snmp_module config = {
+ .comment =
+ "This module implements the BEGEMOT MIB for reading LM75 sensors data.",
+ .init = lm75_init,
+ .start = lm75_start,
+ .fini = lm75_fini,
+ .tree = lm75_ctree,
+ .tree_size = lm75_CTREE_SIZE,
+};
+
+static int
+lm75_init(struct lmodule *mod, int argc __unused, char *argv[] __unused)
+{
+
+ module = mod;
+
+ lm75_sensors = 0;
+ openlog("snmp_lm75", LOG_NDELAY | LOG_PID, LOG_DAEMON);
+
+ return(0);
+}
+
+static void
+lm75_start(void)
+{
+
+ lm75_index = or_register(&oid_lm75,
+ "The MIB module for reading lm75 sensors data.", module);
+}
+
+static int
+lm75_fini(void)
+{
+
+ or_unregister(lm75_index);
+ free_sensors();
+ closelog();
+
+ return (0);
+}
+
+static void
+free_sensors(void)
+{
+ struct lm75_snmp_sensor *sensor;
+
+ while ((sensor = TAILQ_FIRST(&sensors)) != NULL) {
+ TAILQ_REMOVE(&sensors, sensor, link);
+ free(sensor);
+ }
+}
+
+static int
+sysctlname(int *oid, int nlen, char *name, size_t len)
+{
+ int mib[12];
+
+ if (nlen > (int)(sizeof(mib) / sizeof(int) - 2))
+ return (-1);
+
+ mib[0] = 0;
+ mib[1] = 1;
+ memcpy(mib + 2, oid, nlen * sizeof(int));
+
+ if (sysctl(mib, nlen + 2, name, &len, 0, 0) == -1)
+ return (-1);
+
+ return (0);
+}
+
+static int
+sysctlgetnext(int *oid, int nlen, int *next, size_t *nextlen)
+{
+ int mib[12];
+
+ if (nlen > (int)(sizeof(mib) / sizeof(int) - 2))
+ return (-1);
+
+ mib[0] = 0;
+ mib[1] = 2;
+ memcpy(mib + 2, oid, nlen * sizeof(int));
+
+ if (sysctl(mib, nlen + 2, next, nextlen, 0, 0) == -1)
+ return (-1);
+
+ return (0);
+}
+
+static int
+update_sensor_sysctl(void *obuf, size_t *obuflen, int idx, const char *name)
+{
+ char buf[LM75BUF];
+ int mib[5];
+ size_t len;
+
+ /* Fill out the mib information. */
+ snprintf(buf, sizeof(buf) - 1, "dev.lm75.%d.%s", idx, name);
+ len = sizeof(mib) / sizeof(int);
+ if (sysctlnametomib(buf, mib, &len) == -1)
+ return (-1);
+
+ if (len != 4)
+ return (-1);
+
+ /* Read the sysctl data. */
+ if (sysctl(mib, len, obuf, obuflen, NULL, 0) == -1)
+ return (-1);
+
+ return (0);
+}
+
+static void
+update_sensor(struct lm75_snmp_sensor *sensor, int idx)
+{
+ size_t len;
+
+ len = sizeof(sensor->desc);
+ update_sensor_sysctl(sensor->desc, &len, idx, "%desc");
+
+ len = sizeof(sensor->location);
+ update_sensor_sysctl(sensor->location, &len, idx, "%location");
+
+ len = sizeof(sensor->pnpinfo);
+ update_sensor_sysctl(sensor->pnpinfo, &len, idx, "%pnpinfo");
+
+ len = sizeof(sensor->parent);
+ update_sensor_sysctl(sensor->parent, &len, idx, "%parent");
+}
+
+static int
+add_sensor(char *buf)
+{
+ int idx, temp;
+ size_t len;
+ struct lm75_snmp_sensor *sensor;
+
+ if (sscanf(buf, "dev.lm75.%d.temperature", &idx) != 1)
+ return (-1);
+
+ /* Read the sensor temperature. */
+ len = sizeof(temp);
+ if (update_sensor_sysctl(&temp, &len, idx, "temperature") != 0)
+ return (-1);
+
+ /* Add the sensor data to the table. */
+ sensor = calloc(1, sizeof(*sensor));
+ if (sensor == NULL) {
+ syslog(LOG_ERR, "Unable to allocate %zu bytes for resource",
+ sizeof(*sensor));
+ return (-1);
+ }
+ sensor->index = ++lm75_sensors;
+ sensor->sysctlidx = idx;
+ sensor->temp = (temp - TZ_ZEROC) / 10;
+ TAILQ_INSERT_TAIL(&sensors, sensor, link);
+
+ update_sensor(sensor, idx);
+
+ return (0);
+}
+
+static int
+update_sensors(void)
+{
+ char buf[LM75BUF];
+ int i, root[5], *next, *oid;
+ size_t len, nextlen, rootlen;
+ static uint64_t now;
+
+ now = get_ticks();
+ if (now - last_sensors_update < UPDATE_INTERVAL)
+ return (0);
+
+ last_sensors_update = now;
+
+ /* Reset the sensor data. */
+ free_sensors();
+ lm75_sensors = 0;
+
+ /* Start from the lm75 default root node. */
+ rootlen = 2;
+ if (sysctlnametomib("dev.lm75", root, &rootlen) == -1)
+ return (0);
+
+ oid = (int *)malloc(sizeof(int) * rootlen);
+ if (oid == NULL) {
+ perror("malloc");
+ return (-1);
+ }
+ memcpy(oid, root, rootlen * sizeof(int));
+ len = rootlen;
+
+ /* Traverse the sysctl(3) interface and find the active sensors. */
+ for (;;) {
+
+ /* Find the size of the next mib. */
+ nextlen = 0;
+ if (sysctlgetnext(oid, len, NULL, &nextlen) == -1) {
+ free(oid);
+ return (0);
+ }
+ /* Alocate and read the next mib. */
+ next = (int *)malloc(nextlen);
+ if (next == NULL) {
+ syslog(LOG_ERR,
+ "Unable to allocate %zu bytes for resource",
+ nextlen);
+ free(oid);
+ return (-1);
+ }
+ if (sysctlgetnext(oid, len, next, &nextlen) == -1) {
+ free(oid);
+ free(next);
+ return (0);
+ }
+ free(oid);
+ /* Check if we care about the next mib. */
+ for (i = 0; i < (int)rootlen; i++)
+ if (next[i] != root[i]) {
+ free(next);
+ return (0);
+ }
+ oid = (int *)malloc(nextlen);
+ if (oid == NULL) {
+ syslog(LOG_ERR,
+ "Unable to allocate %zu bytes for resource",
+ nextlen);
+ free(next);
+ return (-1);
+ }
+ memcpy(oid, next, nextlen);
+ free(next);
+ len = nextlen / sizeof(int);
+
+ /* Find the mib name. */
+ if (sysctlname(oid, len, buf, sizeof(buf)) != 0)
+ continue;
+
+ if (strstr(buf, "temperature"))
+ if (add_sensor(buf) != 0) {
+ free(oid);
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+int
+op_lm75Sensors(struct snmp_context *context __unused, struct snmp_value *value,
+ u_int sub, u_int iidx __unused, enum snmp_op op)
+{
+ asn_subid_t which;
+
+ if (update_sensors() == -1)
+ return (SNMP_ERR_RES_UNAVAIL);
+
+ which = value->var.subs[sub - 1];
+
+ switch (op) {
+ case SNMP_OP_GET:
+ switch (which) {
+ case LEAF_lm75Sensors:
+ value->v.integer = lm75_sensors;
+ break;
+ default:
+ return (SNMP_ERR_RES_UNAVAIL);
+ }
+ break;
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ case SNMP_OP_GETNEXT:
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ return (SNMP_ERR_NOERROR);
+ default:
+ return (SNMP_ERR_RES_UNAVAIL);
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+op_lm75SensorTable(struct snmp_context *context __unused,
+ struct snmp_value *value, u_int sub, u_int iidx __unused, enum snmp_op op)
+{
+ struct lm75_snmp_sensor *sensor;
+ asn_subid_t which;
+ int ret;
+
+ if (update_sensors() == -1)
+ return (SNMP_ERR_RES_UNAVAIL);
+
+ which = value->var.subs[sub - 1];
+
+ switch (op) {
+ case SNMP_OP_GETNEXT:
+ sensor = NEXT_OBJECT_INT(&sensors, &value->var, sub);
+ if (sensor == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ value->var.len = sub + 1;
+ value->var.subs[sub] = sensor->index;
+ break;
+ case SNMP_OP_GET:
+ if (value->var.len - sub != 1)
+ return (SNMP_ERR_NOSUCHNAME);
+ sensor = FIND_OBJECT_INT(&sensors, &value->var, sub);
+ if (sensor == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ return (SNMP_ERR_NOERROR);
+ default:
+ return (SNMP_ERR_RES_UNAVAIL);
+ }
+
+ ret = SNMP_ERR_NOERROR;
+
+ switch (which) {
+ case LEAF_lm75SensorIndex:
+ value->v.integer = sensor->index;
+ break;
+ case LEAF_lm75SensorSysctlIndex:
+ value->v.integer = sensor->sysctlidx;
+ break;
+ case LEAF_lm75SensorDesc:
+ ret = string_get(value, sensor->desc, -1);
+ break;
+ case LEAF_lm75SensorLocation:
+ ret = string_get(value, sensor->location, -1);
+ break;
+ case LEAF_lm75SensorPnpInfo:
+ ret = string_get(value, sensor->pnpinfo, -1);
+ break;
+ case LEAF_lm75SensorParent:
+ ret = string_get(value, sensor->parent, -1);
+ break;
+ case LEAF_lm75SensorTemperature:
+ value->v.integer = sensor->temp;
+ break;
+ default:
+ ret = SNMP_ERR_RES_UNAVAIL;
+ break;
+ }
+
+ return (ret);
+}
diff --git a/usr.sbin/bsnmpd/modules/snmp_mibII/Makefile b/usr.sbin/bsnmpd/modules/snmp_mibII/Makefile
new file mode 100644
index 0000000..32bca7d
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_mibII/Makefile
@@ -0,0 +1,29 @@
+# $FreeBSD$
+#
+# Author: Harti Brandt <harti@freebsd.org>
+
+CONTRIB= ${.CURDIR}/../../../../contrib/bsnmp
+.PATH: ${CONTRIB}/snmp_mibII
+
+MOD= mibII
+SRCS= mibII.c mibII_begemot.c mibII_ifmib.c mibII_ifstack.c \
+ mibII_interfaces.c mibII_ip.c mibII_ipaddr.c mibII_nettomedia.c \
+ mibII_rcvaddr.c mibII_route.c mibII_tcp.c mibII_udp.c
+XSYM= ipAddrTable ifTable ifRcvAddressEntry ifMIB ipMIB tcpMIB udpMIB \
+ ipForward ifIndex linkDown linkUp
+MAN= snmp_mibII.3
+
+CFLAGS+= -I${CONTRIB}/lib -I${CONTRIB}/snmpd
+CFLAGS+= -DHAVE_ERR_H -DHAVE_GETADDRINFO -DHAVE_STRLCPY -DHAVE_SYS_TREE_H
+# XXX Work around clang warning, until maintainer approves fix.
+NO_WERROR.clang=
+
+DEFS= ${MOD}_tree.def
+INCS= snmp_${MOD}.h
+BMIBS= BEGEMOT-IP-MIB.txt BEGEMOT-MIB2-MIB.txt
+
+.include <bsd.snmpmod.mk>
+
+smilint:
+ env SMIPATH=/usr/share/snmp/mibs:/usr/local/share/snmp/mibs \
+ smilint -c /dev/null -l6 -i group-membership ${BMIBS:C/^/${CONTRIB}\/snmp_mibII\//}
diff --git a/usr.sbin/bsnmpd/modules/snmp_mibII/Makefile.depend b/usr.sbin/bsnmpd/modules/snmp_mibII/Makefile.depend
new file mode 100644
index 0000000..32018d0
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_mibII/Makefile.depend
@@ -0,0 +1,70 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbsnmp/libbsnmp \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+mibII.So: mibII_oid.h
+mibII.So: mibII_tree.h
+mibII.po: mibII_oid.h
+mibII.po: mibII_tree.h
+mibII_begemot.So: mibII_oid.h
+mibII_begemot.So: mibII_tree.h
+mibII_begemot.po: mibII_oid.h
+mibII_begemot.po: mibII_tree.h
+mibII_ifmib.So: mibII_oid.h
+mibII_ifmib.So: mibII_tree.h
+mibII_ifmib.po: mibII_oid.h
+mibII_ifmib.po: mibII_tree.h
+mibII_ifstack.So: mibII_tree.h
+mibII_ifstack.po: mibII_tree.h
+mibII_interfaces.So: mibII_oid.h
+mibII_interfaces.So: mibII_tree.h
+mibII_interfaces.po: mibII_oid.h
+mibII_interfaces.po: mibII_tree.h
+mibII_ip.So: mibII_oid.h
+mibII_ip.So: mibII_tree.h
+mibII_ip.po: mibII_oid.h
+mibII_ip.po: mibII_tree.h
+mibII_ipaddr.So: mibII_oid.h
+mibII_ipaddr.So: mibII_tree.h
+mibII_ipaddr.po: mibII_oid.h
+mibII_ipaddr.po: mibII_tree.h
+mibII_nettomedia.So: mibII_oid.h
+mibII_nettomedia.So: mibII_tree.h
+mibII_nettomedia.po: mibII_oid.h
+mibII_nettomedia.po: mibII_tree.h
+mibII_rcvaddr.So: mibII_oid.h
+mibII_rcvaddr.So: mibII_tree.h
+mibII_rcvaddr.po: mibII_oid.h
+mibII_rcvaddr.po: mibII_tree.h
+mibII_route.So: mibII_oid.h
+mibII_route.So: mibII_tree.h
+mibII_route.po: mibII_oid.h
+mibII_route.po: mibII_tree.h
+mibII_tcp.So: mibII_oid.h
+mibII_tcp.So: mibII_tree.h
+mibII_tcp.po: mibII_oid.h
+mibII_tcp.po: mibII_tree.h
+mibII_tree.So: mibII_tree.c
+mibII_tree.So: mibII_tree.h
+mibII_tree.po: mibII_tree.c
+mibII_tree.po: mibII_tree.h
+mibII_udp.So: mibII_oid.h
+mibII_udp.So: mibII_tree.h
+mibII_udp.po: mibII_oid.h
+mibII_udp.po: mibII_tree.h
+.endif
diff --git a/usr.sbin/bsnmpd/modules/snmp_netgraph/BEGEMOT-NETGRAPH.txt b/usr.sbin/bsnmpd/modules/snmp_netgraph/BEGEMOT-NETGRAPH.txt
new file mode 100644
index 0000000..1896fe6
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_netgraph/BEGEMOT-NETGRAPH.txt
@@ -0,0 +1,398 @@
+--
+-- Copyright (c) 2001-2003
+-- Fraunhofer Institute for Open Communication Systems (FhG Fokus).
+-- All rights reserved.
+--
+-- Author: Harti Brandt <harti@freebsd.org>
+--
+-- Redistribution of this software and documentation and use in source and
+-- binary forms, with or without modification, are permitted provided that
+-- the following conditions are met:
+--
+-- 1. Redistributions of source code or documentation must retain the above
+-- copyright notice, this list of conditions and the following disclaimer.
+-- 2. Redistributions in binary form must 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 AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS
+-- AND ITS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+-- FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+-- FRAUNHOFER FOKUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+-- INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+-- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+-- OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+-- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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$
+--
+-- Private MIB for netgraph part of Begemot SNMP daemon.
+--
+BEGEMOT-NETGRAPH-MIB DEFINITIONS ::= BEGIN
+
+IMPORTS
+ MODULE-IDENTITY, OBJECT-TYPE, Counter32, Unsigned32
+ FROM SNMPv2-SMI
+ TEXTUAL-CONVENTION, TruthValue
+ FROM SNMPv2-TC
+ MODULE-COMPLIANCE, OBJECT-GROUP
+ FROM SNMPv2-CONF
+ begemot
+ FROM BEGEMOT-MIB;
+
+begemotNg MODULE-IDENTITY
+ LAST-UPDATED "200311140000Z"
+ ORGANIZATION "Fraunhofer FOKUS, CATS"
+ CONTACT-INFO
+ " Hartmut Brandt
+
+ Postal: Fraunhofer Institute for Open Communication Systems
+ Kaiserin-Augusta-Allee 31
+ 10589 Berlin
+ Germany
+
+ Fax: +49 30 3463 7352
+
+ E-mail: harti@freebsd.org"
+ DESCRIPTION
+ "The MIB for the NetGraph access module for SNMP."
+ ::= { begemot 2 }
+
+begemotNgObjects OBJECT IDENTIFIER ::= { begemotNg 1 }
+
+-- --------------------------------------------------------------------------
+
+NgTypeName ::= TEXTUAL-CONVENTION
+ DISPLAY-HINT "31a"
+ STATUS current
+ DESCRIPTION
+ "Name of a netgraph type."
+ SYNTAX OCTET STRING (SIZE(1..31))
+
+NgNodeName ::= TEXTUAL-CONVENTION
+ DISPLAY-HINT "31a"
+ STATUS current
+ DESCRIPTION
+ "Name of a netgraph node."
+ SYNTAX OCTET STRING (SIZE(1..31))
+
+NgNodeNameOrEmpty ::= TEXTUAL-CONVENTION
+ DISPLAY-HINT "31a"
+ STATUS current
+ DESCRIPTION
+ "Name of a netgraph node."
+ SYNTAX OCTET STRING (SIZE(0..31))
+
+NgHookName ::= TEXTUAL-CONVENTION
+ DISPLAY-HINT "31a"
+ STATUS current
+ DESCRIPTION
+ "Name of a netgraph hook."
+ SYNTAX OCTET STRING (SIZE(1..31))
+
+NgNodeId ::= TEXTUAL-CONVENTION
+ DISPLAY-HINT "x"
+ STATUS current
+ DESCRIPTION
+ "Node identifier."
+ SYNTAX Unsigned32 (1..4294967295)
+
+NgNodeIdOrZero ::= TEXTUAL-CONVENTION
+ DISPLAY-HINT "x"
+ STATUS current
+ DESCRIPTION
+ "Node identifier or 0 for 'no-node'."
+ SYNTAX Unsigned32 (0..4294967295)
+
+-- --------------------------------------------------------------------------
+--
+-- Configuration parameters
+--
+begemotNgConfig OBJECT IDENTIFIER ::= { begemotNgObjects 1 }
+
+begemotNgControlNodeName OBJECT-TYPE
+ SYNTAX NgNodeName
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The name of the netgraph node of this daemon. The name is
+ writeable during initialisation. If the name is set from
+ the empty string to the non-empty string, the netgraph socket
+ is created. Once set it cannot be changed."
+ ::= { begemotNgConfig 1 }
+
+begemotNgResBufSiz OBJECT-TYPE
+ SYNTAX INTEGER (1024..65536)
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The size of the receive buffers for netgraph messages."
+ DEFVAL { 20000 }
+ ::= { begemotNgConfig 2 }
+
+begemotNgTimeout OBJECT-TYPE
+ SYNTAX INTEGER (10..10000)
+ UNITS "milliseconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The maximum time to wait for a response to a netgraph message."
+ DEFVAL { 1000 }
+ ::= { begemotNgConfig 3 }
+
+begemotNgDebugLevel OBJECT-TYPE
+ SYNTAX Unsigned32
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The netgraph library debug level. This should be set only
+ if the daemon is run with a terminal attached."
+ DEFVAL { 0 }
+ ::= { begemotNgConfig 4 }
+
+-- --------------------------------------------------------------------------
+--
+-- The STATISTICS Group
+--
+begemotNgStats OBJECT IDENTIFIER ::= { begemotNgObjects 2 }
+
+begemotNgNoMems OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of times a memory allocation has failed for buffers
+ or the message queue."
+ ::= { begemotNgStats 1 }
+
+begemotNgMsgReadErrs OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of times reading a netgraph message has failed."
+ ::= { begemotNgStats 2 }
+
+begemotNgTooLargeMsgs OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of times a netgraph message was too large for
+ the buffer. Try increasing begemotNgResBufSiz if
+ this happens."
+ ::= { begemotNgStats 3 }
+
+begemotNgDataReadErrs OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of times reading a netgraph data message has failed."
+ ::= { begemotNgStats 4 }
+
+begemotNgTooLargeDatas OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of times a netgraph data message was too large.
+ You need to increase begemotNgResBufSiz."
+ ::= { begemotNgStats 5 }
+
+-- -----------------------------------------------------
+--
+-- The NODE table
+--
+begemotNgTypeTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF BegemotNgTypeEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table containing information about all netgraph node types."
+ ::= { begemotNgObjects 3 }
+
+begemotNgTypeEntry OBJECT-TYPE
+ SYNTAX BegemotNgTypeEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Table entry that describes one netgraph node."
+ INDEX { begemotNgTypeName }
+ ::= { begemotNgTypeTable 1 }
+
+BegemotNgTypeEntry ::= SEQUENCE {
+ begemotNgTypeName NgTypeName,
+ begemotNgTypeStatus INTEGER
+}
+
+begemotNgTypeName OBJECT-TYPE
+ SYNTAX NgTypeName
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "The name of the type. Used as index."
+ ::= { begemotNgTypeEntry 1 }
+
+begemotNgTypeStatus OBJECT-TYPE
+ SYNTAX INTEGER { loaded(1), unloaded(2) }
+ MAX-ACCESS read-create
+ STATUS current
+ DESCRIPTION
+ "If loaded then the node type is available. A type can be load
+ by setting this field to loaded. It is unload if the field is
+ set to unloaded. Note, that a type cannot be unloaded if it
+ is compiled into the kernel or has nodes of this type. The name
+ of the file containing the type implementation is constructed by
+ prepending ng_ to the type name."
+ ::= { begemotNgTypeEntry 2 }
+
+--
+-- Node table
+--
+begemotNgNodeTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF BegemotNgNodeEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table containing information about all netgraph nodes."
+ ::= { begemotNgObjects 4 }
+
+begemotNgNodeEntry OBJECT-TYPE
+ SYNTAX BegemotNgNodeEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Table entry that describes one netgraph node."
+ INDEX { begemotNgNodeId }
+ ::= { begemotNgNodeTable 1 }
+
+BegemotNgNodeEntry ::= SEQUENCE {
+ begemotNgNodeId NgNodeId,
+ begemotNgNodeStatus INTEGER,
+ begemotNgNodeName NgNodeNameOrEmpty,
+ begemotNgNodeType NgTypeName,
+ begemotNgNodeHooks Unsigned32
+}
+
+begemotNgNodeId OBJECT-TYPE
+ SYNTAX NgNodeId
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "The 32bit node id of this node. 0 is an illegal value."
+ ::= { begemotNgNodeEntry 1 }
+
+begemotNgNodeStatus OBJECT-TYPE
+ SYNTAX INTEGER { valid(1), invalid(2) }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Indicates whether the node exists or not."
+ ::= { begemotNgNodeEntry 2 }
+
+begemotNgNodeName OBJECT-TYPE
+ SYNTAX NgNodeNameOrEmpty
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Name of the node (if any)."
+ ::= { begemotNgNodeEntry 3 }
+
+begemotNgNodeType OBJECT-TYPE
+ SYNTAX NgTypeName
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Type name of the node."
+ ::= { begemotNgNodeEntry 4 }
+
+begemotNgNodeHooks OBJECT-TYPE
+ SYNTAX Unsigned32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of hooks on this node."
+ ::= { begemotNgNodeEntry 5 }
+
+--
+-- Hook table
+--
+begemotNgHookTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF BegemotNgHookEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table containing information about all netgraph hooks."
+ ::= { begemotNgObjects 5 }
+
+begemotNgHookEntry OBJECT-TYPE
+ SYNTAX BegemotNgHookEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Table entry that describes one netgraph node."
+ INDEX { begemotNgHookNodeId, begemotNgHookHook }
+ ::= { begemotNgHookTable 1 }
+
+BegemotNgHookEntry ::= SEQUENCE {
+ begemotNgHookNodeId NgNodeId,
+ begemotNgHookHook NgHookName,
+ begemotNgHookStatus INTEGER,
+ begemotNgHookPeerNodeId NgNodeId,
+ begemotNgHookPeerHook NgHookName,
+ begemotNgHookPeerType NgTypeName
+}
+
+begemotNgHookNodeId OBJECT-TYPE
+ SYNTAX NgNodeId
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "The 32bit node id of this node."
+ ::= { begemotNgHookEntry 1 }
+
+begemotNgHookHook OBJECT-TYPE
+ SYNTAX NgHookName
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Name of the hook."
+ ::= { begemotNgHookEntry 2 }
+
+begemotNgHookStatus OBJECT-TYPE
+ SYNTAX INTEGER { valid(1), invalid(2) }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Indicates whether the hook exists or not."
+ ::= { begemotNgHookEntry 3 }
+
+begemotNgHookPeerNodeId OBJECT-TYPE
+ SYNTAX NgNodeId
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The 32bit node id of the peer node of this hook."
+ ::= { begemotNgHookEntry 4 }
+
+begemotNgHookPeerHook OBJECT-TYPE
+ SYNTAX NgHookName
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Name of the peer hook."
+ ::= { begemotNgHookEntry 5 }
+
+begemotNgHookPeerType OBJECT-TYPE
+ SYNTAX NgTypeName
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Name of the peer type."
+ ::= { begemotNgHookEntry 6 }
+
+END
diff --git a/usr.sbin/bsnmpd/modules/snmp_netgraph/Makefile b/usr.sbin/bsnmpd/modules/snmp_netgraph/Makefile
new file mode 100644
index 0000000..85057c9
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_netgraph/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+#
+# Author: Harti Brandt <harti@freebsd.org>
+
+MOD= netgraph
+SRCS= snmp_netgraph.c
+XSYM= begemotNg
+MAN= snmp_netgraph.3
+
+BMIBS= BEGEMOT-NETGRAPH.txt
+DEFS= ${MOD}_tree.def
+INCS= snmp_${MOD}.h
+
+LIBADD= netgraph
+
+.include <bsd.snmpmod.mk>
diff --git a/usr.sbin/bsnmpd/modules/snmp_netgraph/Makefile.depend b/usr.sbin/bsnmpd/modules/snmp_netgraph/Makefile.depend
new file mode 100644
index 0000000..cad32c9
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_netgraph/Makefile.depend
@@ -0,0 +1,29 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbsnmp/libbsnmp \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libnetgraph \
+ usr.sbin/bsnmpd/modules \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+netgraph_tree.So: netgraph_tree.c
+netgraph_tree.So: netgraph_tree.h
+netgraph_tree.po: netgraph_tree.c
+netgraph_tree.po: netgraph_tree.h
+snmp_netgraph.So: netgraph_oid.h
+snmp_netgraph.So: netgraph_tree.h
+snmp_netgraph.po: netgraph_oid.h
+snmp_netgraph.po: netgraph_tree.h
+.endif
diff --git a/usr.sbin/bsnmpd/modules/snmp_netgraph/netgraph_tree.def b/usr.sbin/bsnmpd/modules/snmp_netgraph/netgraph_tree.def
new file mode 100644
index 0000000..eff59ff
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_netgraph/netgraph_tree.def
@@ -0,0 +1,78 @@
+#
+# Copyright (c) 2001-2003
+# Fraunhofer Institute for Open Communication Systems (FhG Fokus).
+# All rights reserved.
+#
+# Author: Harti Brandt <harti@freebsd.org>
+#
+# Redistribution of this software and documentation and use in source and
+# binary forms, with or without modification, are permitted provided that
+# the following conditions are met:
+#
+# 1. Redistributions of source code or documentation must retain the above
+# copyright notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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 AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS
+# AND ITS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+# FRAUNHOFER FOKUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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$
+#
+# Definition of the tree implemented by snmp_netgraph.
+#
+(1 internet
+ (4 private
+ (1 enterprises
+ (12325 fokus
+ (1 begemot
+ (2 begemotNg
+ (1 begemotNgObjects
+ (1 begemotNgConfig
+ (1 begemotNgControlNodeName OCTETSTRING op_ng_config GET SET)
+ (2 begemotNgResBufSiz INTEGER op_ng_config GET SET)
+ (3 begemotNgTimeout INTEGER op_ng_config GET SET)
+ (4 begemotNgDebugLevel UNSIGNED32 op_ng_config GET SET)
+ )
+# Change definition of stats array if you change StatsGroup
+ (2 begemotNgStats
+ (1 begemotNgNoMems COUNTER op_ng_stats GET)
+ (2 begemotNgMsgReadErrs COUNTER op_ng_stats GET)
+ (3 begemotNgTooLargeMsgs COUNTER op_ng_stats GET)
+ (4 begemotNgDataReadErrs COUNTER op_ng_stats GET)
+ (5 begemotNgTooLargeDatas COUNTER op_ng_stats GET)
+ )
+ (3 begemotNgTypeTable
+ (1 begemotNgTypeEntry : OCTETSTRING op_ng_type
+ (1 begemotNgTypeName OCTETSTRING)
+ (2 begemotNgTypeStatus INTEGER GET SET)
+ ))
+ (4 begemotNgNodeTable
+ (1 begemotNgNodeEntry : INTEGER op_ng_node
+ (1 begemotNgNodeId UNSIGNED32)
+ (2 begemotNgNodeStatus INTEGER GET)
+ (3 begemotNgNodeName OCTETSTRING GET)
+ (4 begemotNgNodeType OCTETSTRING GET)
+ (5 begemotNgNodeHooks UNSIGNED32 GET)
+ ))
+ (5 begemotNgHookTable
+ (1 begemotNgHookEntry : UNSIGNED32 OCTETSTRING op_ng_hook
+ (1 begemotNgHookNodeId UNSIGNED32)
+ (2 begemotNgHookHook OCTETSTRING)
+ (3 begemotNgHookStatus INTEGER GET)
+ (4 begemotNgHookPeerNodeId UNSIGNED32 GET)
+ (5 begemotNgHookPeerHook OCTETSTRING GET)
+ (6 begemotNgHookPeerType OCTETSTRING GET)
+ ))
+ ))
+)))))
diff --git a/usr.sbin/bsnmpd/modules/snmp_netgraph/snmp_netgraph.3 b/usr.sbin/bsnmpd/modules/snmp_netgraph/snmp_netgraph.3
new file mode 100644
index 0000000..d40aef5
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_netgraph/snmp_netgraph.3
@@ -0,0 +1,436 @@
+.\"
+.\" Copyright (c) 2001-2003
+.\" Fraunhofer Institute for Open Communication Systems (FhG Fokus).
+.\" All rights reserved.
+.\"
+.\" Author: Harti Brandt <harti@FreeBSD.org>
+.\"
+.\" Redistribution of this software and documentation and use in source and
+.\" binary forms, with or without modification, are permitted provided that
+.\" the following conditions are met:
+.\"
+.\" 1. Redistributions of source code or documentation must retain the above
+.\" copyright notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS
+.\" AND ITS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+.\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+.\" FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+.\" FRAUNHOFER FOKUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+.\" OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+.\" LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 November 14, 2003
+.Dt SNMP_NETGRAPH 3
+.Os
+.Sh NAME
+.Nm snmp_netgraph ,
+.Nm snmp_node ,
+.Nm snmp_nodename ,
+.Nm ng_cookie_f ,
+.Nm ng_hook_f ,
+.Nm ng_register_cookie ,
+.Nm ng_unregister_cookie ,
+.Nm ng_register_hook ,
+.Nm ng_unregister_hook ,
+.Nm ng_unregister_module ,
+.Nm ng_output ,
+.Nm ng_output_node ,
+.Nm ng_output_id ,
+.Nm ng_dialog ,
+.Nm ng_dialog_node ,
+.Nm ng_dialog_id ,
+.Nm ng_send_data ,
+.Nm ng_mkpeer_id ,
+.Nm ng_connect_node ,
+.Nm ng_connect_id ,
+.Nm ng_connect2_id ,
+.Nm ng_connect2_tee_id ,
+.Nm ng_rmhook ,
+.Nm ng_rmhook_id ,
+.Nm ng_rmhook_tee_id ,
+.Nm ng_shutdown_id ,
+.Nm ng_next_node_id ,
+.Nm ng_node_id ,
+.Nm ng_node_id_node ,
+.Nm ng_node_name ,
+.Nm ng_node_type ,
+.Nm ng_peer_hook_id
+.Nd "netgraph module for snmpd"
+.Sh LIBRARY
+.Pq begemotSnmpdModulePath."netgraph" = "/usr/lib/snmp_netgraph.so"
+.Sh SYNOPSIS
+.In bsnmp/snmpmod.h
+.In bsnmp/snmp_netgraph.h
+.Vt extern ng_ID_t snmp_node ;
+.Vt extern u_char *snmp_nodename ;
+.Ft typedef void
+.Fn ng_cookie_f "const struct ng_mesg *mesg" "const char *path" "ng_ID_t id" "void *uarg"
+.Ft typedef void
+.Fn ng_hook_f "const char *hook" "const u_char *mesg" "size_t len" "void *uarg"
+.Ft void *
+.Fn ng_register_cookie "const struct lmodule *mod" "uint32_t cookie" "ng_ID_t id" "ng_cookie_f *func" "void *uarg"
+.Ft void
+.Fn ng_unregister_cookie "void *reg"
+.Ft void *
+.Fn ng_register_hook "const struct lmodule *mod" "const char *hook" "ng_hook_f *func" "void *uarg"
+.Ft void
+.Fn ng_unregister_hook "void *reg"
+.Ft void
+.Fn ng_unregister_module "const struct lmodule *mod"
+.Ft int
+.Fn ng_output "const char *path" "u_int cookie" "u_int opcode" "const void *arg" "size_t arglen"
+.Ft int
+.Fn ng_output_node "const char *node" "u_int cookie" "u_int opcode" "const void *arg" "size_t arglen"
+.Ft int
+.Fn ng_output_id "ng_ID_t node" "u_int cookie" "u_int opcode" "const void *arg" "size_t arglen"
+.Ft struct ng_mesg *
+.Fn ng_dialog "const char *path" "u_int cookie" "u_int opcode" "const void *arg" "size_t arglen"
+.Ft struct ng_mesg *
+.Fn ng_dialog_node "const char *node" "u_int cookie" "u_int opcode" "const void *arg" "size_t arglen"
+.Ft struct ng_mesg *
+.Fn ng_dialog_id "ng_ID_t id" "u_int cookie" "u_int opcode" "const void *arg" "size_t arglen"
+.Ft int
+.Fn ng_send_data "const char *hook" "const void *sndbuf" "size_t sndlen"
+.Ft ng_ID_t
+.Fn ng_mkpeer_id "ng_ID_t id" "const char *name" "const char *type" "const char *hook" "const char *peerhook"
+.Ft int
+.Fn ng_connect_node "const char *node" "const char *ourhook" "const char *peerhook"
+.Ft int
+.Fn ng_connect_id "ng_ID_t id" "const char *ourhook" "const char *peerhook"
+.Ft int
+.Fn ng_connect2_id "ng_ID_t id" "ng_ID_t peer" "const char *ourhook" "const char *peerhook"
+.Ft int
+.Fn ng_connect2_tee_id "ng_ID_t id" "ng_ID_t peer" "const char *ourhook" "const char *peerhook"
+.Ft int
+.Fn ng_rmhook "const char *ourhook"
+.Ft int
+.Fn ng_rmhook_id "ng_ID_t id" "const char *hook"
+.Ft int
+.Fn ng_rmhook_tee_id "ng_ID_t id" "const char *hook"
+.Ft int
+.Fn ng_shutdown_id "ng_ID_t id"
+.Ft ng_ID_t
+.Fn ng_next_node_id "ng_ID_t node" "const char *type" "const char *hook"
+.Ft ng_ID_t
+.Fn ng_node_id "const char *path"
+.Ft ng_ID_t
+.Fn ng_node_id_node "const char *node"
+.Ft ng_ID_t
+.Fn ng_node_name "ng_ID_t id" "char *name"
+.Ft ng_ID_t
+.Fn ng_node_type "ng_ID_t id" "char *type"
+.Ft int
+.Fn ng_peer_hook_id "ng_ID_t id" "const char *hook" "char *peerhook"
+.Sh DESCRIPTION
+The
+.Nm snmp_netgraph
+module implements a number of tables and scalars that enable remote access to
+the netgraph subsystem.
+It also exports a number of global variables and
+functions, that allow other modules to easily use the netgraph system.
+.Pp
+If upon start up of the module the variable
+.Va begemotNgControlNodeName
+is not empty the module opens a netgraph socket and names that socket node.
+If the variable is empty, the socket is created, as soon as the variable is
+written with a non-empty name.
+The socket can be closed by writing an empty
+string to the variable.
+The socket itself and its name are available in
+.Va snmp_node
+and
+.Va snmp_nodename .
+.Ss SENDING AND RECEIVING MESSAGES AND DATA
+There are three functions for sending control message:
+.Bl -tag -width ".It Fn ng_output_node"
+.It Fn ng_output
+sends a control message along the given
+.Fa path .
+.It Fn ng_output_node
+sends a control message to the node with name
+.Fa node
+and
+.It Fn ng_output_id
+sends a control message to the node with node id
+.Fa id .
+.El
+.Pp
+Each of these functions takes the following arguments:
+.Bl -tag -width ".It Fa cookie"
+.It Fa cookie
+is the node specific command cookie,
+.It Fa opcode
+is the node specific code for the operation to perform,
+.It Fa arg
+is a pointer to the message itself.
+This message must start with a
+.Vt struct ng_mesg .
+.It Fa arglen
+is the overall length of the message (header plus arguments).
+.El
+The functions return the message id that can be used to match incoming responses
+or -1 if an error occurs.
+.Pp
+Another class of functions is used to send a control message and to wait for
+a matching response.
+Note, that this operation blocks the daemon, so use it
+only if you are sure that the response will happen.
+There is a maximum timeout
+that is configurable in the MIB variable
+.Va begemotNgTimeout .
+Other messages arriving while the functions are waiting for the response are
+queued and delivered on the next call to the module's idle function.
+.Bl -tag -width ".It Fn ng_output_node"
+.It Fn ng_dialog
+sends a control message along the given
+.Fa path
+and waits for a matching response.
+.It Fn ng_dialog_node
+sends a control message to the node with name
+.Fa node
+and waits for a matching response.
+.It Fn ng_dialog_id
+sends a control message to the node with id
+.Fa id
+and waits for a matching response.
+.El
+.Pp
+All three functions take the same arguments as the
+.Fn ng_output*
+functions.
+The functions return the response message in a buffer allocated by
+.Xr malloc 3
+or NULL in case of an error.
+The maximum size of the response buffer can be
+configured in the variable
+.Va begemotNgResBufSiz .
+.Pp
+A data message can be send with the function
+.Fn ng_send_data .
+This function takes the name of the
+.Va snmp_node Ns 's
+hook through which to send the data, a pointer to the message buffer and
+the size of the message.
+It returns -1 if an error happens.
+.Ss ASYNCHRONOUS CONTROL AND DATA MESSAGES
+A module can register functions to asynchronously receive control and data
+message.
+.Pp
+The function
+.Fn ng_register_cookie
+registers a control message receive function.
+If a control message is
+received, that is not consumed by the dialog functions, the list of registered
+control message receive functions is scanned.
+If the cookie in the received
+message is the same as the
+.Fa cookie
+argument to the
+.Fn ng_register_cookie
+call and the
+.Fa id
+argument to the
+.Fn ng_register_cookie
+call was either 0 or equals the node id which sent the control message, the
+handler function
+.Fa func
+is called with a pointer to the received message, the hook on which the
+message was received (or NULL if it was received not on a hook), the id
+of the sender's node and the
+.Fa uarg
+argument of the registration call.
+The handler function should not modify
+the contents of the message, because more than one function may be registered
+to the same cookie and node id.
+.Pp
+A control message registration can be undone by calling
+.Fn ng_unregister_cookie
+with the return value of the registration call.
+If an error occurs while registering,
+.Fn ng_register_cookie
+returns NULL.
+.Pp
+A module can call
+.Fn ng_register_hook
+to register a callback for data messages on one of the
+.Va snmp_node Ns 's
+hooks.
+If a data message is received on that hook, the callback function
+.Fa func
+is called with the hook name, a pointer to the data message, the size of
+the message and the argument
+.Fa uarg
+to the registration function.
+The message should be treated as read-only.
+A data message registration can be undone by calling
+.Fn ng_unregister_hook
+with the return value of the registration call.
+If an error occurs while registering,
+.Fn ng_register_hook
+returns NULL.
+.Pp
+The function
+.Fn ng_unregister_module
+removes all control and data registrations for that module.
+.Ss FINDING NODES AND NODE CHARACTERISTICS
+The function
+.Fn ng_node_id
+returns the id of the node addressed by
+.Fa path
+or 0 if the node does not exists.
+.Pp
+The function
+.Fn ng_node_id_node
+returns the id of the node with name
+.Fa node
+or 0 if the node does not exist.
+.Pp
+The function
+.Fn ng_node_node
+retrieves the name of the node with id
+.Fa id
+and writes it to the buffer pointed to by
+.Fa name .
+This buffer should be at least
+.Li NG_NODESIZ
+bytes long.
+The function returns the node id or 0 if the
+node is not found.
+.Pp
+The function
+.Fn ng_node_type
+retrieves the name of the node with id
+.Fa id
+and writes it to the buffer pointed to by
+.Fa type .
+This buffer should be at least
+.Li NG_TYPESIZ
+bytes long.
+The function returns the node id or 0 if the
+node is not found.
+.Pp
+The function
+.Fn ng_peer_hook_id
+writes the name of the peer hook of the hook
+.Fa hook
+on the node with
+.Fa id
+to the buffer pointed to by
+.Fa peer_hook .
+The buffer should be at least
+.Li NG_HOOKSIZ
+bytes long.
+The function returns 0 if the node and the hook is found, -1
+otherwise.
+The function skips intermediate tee nodes (see
+.Xr ng_tee 4 ) .
+.Pp
+The function
+.Fn ng_next_node_id
+returns the node id of the peer node that is on the other side of hook
+.Fa hook
+of node
+.Fa id .
+If
+.Fa type
+is not NULL, the function checks, that the peer node's type is
+.Fa type .
+The function skips intermediate tee nodes (see
+.Xr ng_tee 4 ) .
+It returns the node id of the peer node or 0 if an error occurs or the
+types do not match.
+.Ss CHANGING THE GRAPH
+A number of functions can be used to create or destroy nodes and hooks.
+.Pp
+The function
+.Fn ng_mkpeer_id
+creates a new node of type
+.Fa type
+whose hook
+.Fa peerhook
+will be connected to
+.Fa hook
+of node
+.Fa id .
+If
+.Fa name
+is not NULL the new node is named with this name.
+The function returns
+The node id of the new node or 0 if an error happens.
+.Pp
+The functions
+.Fn ng_connect_node
+and
+.Fn ng_connect_id
+make a new hook connecting
+.Fa ourhook
+of the modules socket node
+.Va snmp_node
+to
+.Fa peerhook
+of the node identified by id
+.Fa id
+or name
+.Fa node .
+The functions return 0 on success or -1 otherwise.
+.Pp
+The function
+.Fn ng_connect2_id
+connects hook
+.Fa ourhook
+of the node with id
+.Fa id
+to hook
+.Fa peerhook
+of the node with id
+.Fa peer .
+The functions return 0 on success or -1 otherwise.
+The function
+.Fn ng_connect2_tee_id
+does the same as
+.Fn ng_connect2_id
+except, that it puts an unnamed tee node between the two nodes.
+.Pp
+The function
+.Fn ng_rmhook
+removes hook
+.Fa hook
+on the module's
+.Va snmp_node .
+The function
+.Fn ng_rmhook_id
+removes hook
+.Fa hook
+on the node with id
+.Fa id .
+The function
+.Fn ng_rmhook_tee_id
+additionally shuts down all tee nodes between the node and the first non-tee
+peer.
+.Pp
+The function
+.Fn ng_shutdown_id
+destroys the given node.
+.Sh FILES
+.Bl -tag -width "XXXXXXXXX"
+.It Pa /usr/share/bsnmp/defs/netgraph_tree.def
+The description of the MIB tree implemented by
+.Nm .
+.It Pa /usr/share/bsnmp/mibs/BEGEMOT-NETGRAPH.txt
+This is the MIB that is implemented by this module.
+.El
+.Sh SEE ALSO
+.Xr gensnmptree 1 ,
+.Xr snmpmod 3
+.Sh AUTHORS
+.An Hartmut Brandt Aq Mt harti@FreeBSD.org
diff --git a/usr.sbin/bsnmpd/modules/snmp_netgraph/snmp_netgraph.c b/usr.sbin/bsnmpd/modules/snmp_netgraph/snmp_netgraph.c
new file mode 100644
index 0000000..d9d136a
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_netgraph/snmp_netgraph.c
@@ -0,0 +1,1690 @@
+/*
+ * Copyright (c) 2001-2003
+ * Fraunhofer Institute for Open Communication Systems (FhG Fokus).
+ * All rights reserved.
+ *
+ * Author: Harti Brandt <harti@freebsd.org>
+ *
+ * Redistribution of this software and documentation and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ *
+ * 1. Redistributions of source code or documentation must retain the above
+ * copyright notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS
+ * AND ITS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * FRAUNHOFER FOKUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+ * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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$
+ *
+ * Netgraph interface for SNMPd.
+ */
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/socket.h>
+#include <sys/syslog.h>
+#include <sys/queue.h>
+#include <sys/sysctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <netgraph.h>
+#include <bsnmp/snmpmod.h>
+#include "snmp_netgraph.h"
+#include "netgraph_tree.h"
+#include "netgraph_oid.h"
+
+/* maximum message size */
+#define RESBUFSIZ 20000
+
+/* default node name */
+#define NODENAME "NgSnmpd"
+
+/* my node Id */
+ng_ID_t snmp_node;
+u_char *snmp_nodename;
+
+/* the Object Resource registration index */
+static u_int reg_index;
+static const struct asn_oid oid_begemotNg = OIDX_begemotNg;
+
+/* configuration */
+/* this must be smaller than int32_t because the functions in libnetgraph
+ * falsely return an int */
+static size_t resbufsiz = RESBUFSIZ;
+static u_int timeout = 1000;
+static u_int debug_level;
+
+/* number of microseconds per clock tick */
+static struct clockinfo clockinfo;
+
+/* Csock buffers. Communication on the csock is asynchronuous. This means
+ * if we wait for a specific response, we may get other messages. Put these
+ * into a queue and execute them when we are idle. */
+struct csock_buf {
+ STAILQ_ENTRY(csock_buf) link;
+ struct ng_mesg *mesg;
+ char path[NG_PATHSIZ];
+};
+static STAILQ_HEAD(, csock_buf) csock_bufs =
+ STAILQ_HEAD_INITIALIZER(csock_bufs);
+
+/*
+ * We dispatch unsolicieted messages by node cookies and ids.
+ * So we must keep a list of hook names and dispatch functions.
+ */
+struct msgreg {
+ u_int32_t cookie;
+ ng_ID_t id;
+ ng_cookie_f *func;
+ void *arg;
+ const struct lmodule *mod;
+ SLIST_ENTRY(msgreg) link;
+};
+static SLIST_HEAD(, msgreg) msgreg_list =
+ SLIST_HEAD_INITIALIZER(msgreg_list);
+
+/*
+ * Data messages are dispatched by hook names.
+ */
+struct datareg {
+ char hook[NG_HOOKSIZ];
+ ng_hook_f *func;
+ void *arg;
+ const struct lmodule *mod;
+ SLIST_ENTRY(datareg) link;
+};
+static SLIST_HEAD(, datareg) datareg_list =
+ SLIST_HEAD_INITIALIZER(datareg_list);
+
+/* the netgraph sockets */
+static int csock, dsock;
+static void *csock_fd, *dsock_fd;
+
+/* our module handle */
+static struct lmodule *module;
+
+/* statistics */
+static u_int32_t stats[LEAF_begemotNgTooLargeDatas+1];
+
+/* netgraph type list */
+struct ngtype {
+ char name[NG_TYPESIZ];
+ struct asn_oid index;
+ TAILQ_ENTRY(ngtype) link;
+};
+TAILQ_HEAD(ngtype_list, ngtype);
+
+static struct ngtype_list ngtype_list;
+static uint64_t ngtype_tick;
+
+
+/*
+ * Register a function to receive unsolicited messages
+ */
+void *
+ng_register_cookie(const struct lmodule *mod, u_int32_t cookie, ng_ID_t id,
+ ng_cookie_f *func, void *arg)
+{
+ struct msgreg *d;
+
+ if ((d = malloc(sizeof(*d))) == NULL)
+ return (NULL);
+
+ d->cookie = cookie;
+ d->id = id;
+ d->func = func;
+ d->arg = arg;
+ d->mod = mod;
+
+ SLIST_INSERT_HEAD(&msgreg_list, d, link);
+
+ return (d);
+}
+
+/*
+ * Remove a registration.
+ */
+void
+ng_unregister_cookie(void *dd)
+{
+ struct msgreg *d = dd;
+
+ SLIST_REMOVE(&msgreg_list, d, msgreg, link);
+ free(d);
+}
+
+/*
+ * Register a function for hook data.
+ */
+void *
+ng_register_hook(const struct lmodule *mod, const char *hook,
+ ng_hook_f *func, void *arg)
+{
+ struct datareg *d;
+
+ if ((d = malloc(sizeof(*d))) == NULL)
+ return (NULL);
+
+ strcpy(d->hook, hook);
+ d->func = func;
+ d->arg = arg;
+ d->mod = mod;
+
+ SLIST_INSERT_HEAD(&datareg_list, d, link);
+
+ return (d);
+}
+
+/*
+ * Unregister a hook function
+ */
+void
+ng_unregister_hook(void *dd)
+{
+ struct datareg *d = dd;
+
+ SLIST_REMOVE(&datareg_list, d, datareg, link);
+ free(d);
+}
+
+/*
+ * Unregister all hooks and cookies for that module. Note: doesn't disconnect
+ * any hooks!
+ */
+void
+ng_unregister_module(const struct lmodule *mod)
+{
+ struct msgreg *m, *m1;
+ struct datareg *d, *d1;
+
+ m = SLIST_FIRST(&msgreg_list);
+ while (m != NULL) {
+ m1 = SLIST_NEXT(m, link);
+ if (m->mod == mod) {
+ SLIST_REMOVE(&msgreg_list, m, msgreg, link);
+ free(m);
+ }
+ m = m1;
+ }
+
+ d = SLIST_FIRST(&datareg_list);
+ while (d != NULL) {
+ d1 = SLIST_NEXT(d, link);
+ if (d->mod == mod) {
+ SLIST_REMOVE(&datareg_list, d, datareg, link);
+ free(d);
+ }
+ d = d1;
+ }
+}
+
+/*
+ * Dispatch a message to the correct module and delete it. More than one
+ * module can get a message.
+ */
+static void
+csock_handle(struct ng_mesg *mesg, const char *path)
+{
+ struct msgreg *d, *d1;
+ u_int id;
+ int len;
+
+ if (sscanf(path, "[%x]:%n", &id, &len) != 1 ||
+ (u_int)len != strlen(path)) {
+ syslog(LOG_ERR, "cannot parse message path '%s'", path);
+ id = 0;
+ }
+
+ d = SLIST_FIRST(&msgreg_list);
+ while (d != NULL) {
+ d1 = SLIST_NEXT(d, link);
+ if (d->cookie == mesg->header.typecookie &&
+ (d->id == 0 || d->id == id || id == 0))
+ (*d->func)(mesg, path, id, d->arg);
+ d = d1;
+ }
+ free(mesg);
+}
+
+/*
+ * Input from the control socket.
+ */
+static struct ng_mesg *
+csock_read(char *path)
+{
+ struct ng_mesg *mesg;
+ int ret, err;
+
+ if ((mesg = malloc(resbufsiz + 1)) == NULL) {
+ stats[LEAF_begemotNgNoMems]++;
+ syslog(LOG_CRIT, "out of memory");
+ errno = ENOMEM;
+ return (NULL);
+ }
+ if ((ret = NgRecvMsg(csock, mesg, resbufsiz + 1, path)) < 0) {
+ err = errno;
+ free(mesg);
+ if (errno == EWOULDBLOCK) {
+ errno = err;
+ return (NULL);
+ }
+ stats[LEAF_begemotNgMsgReadErrs]++;
+ syslog(LOG_WARNING, "read from csock: %m");
+ errno = err;
+ return (NULL);
+ }
+ if (ret == 0) {
+ syslog(LOG_DEBUG, "node closed -- exiting");
+ exit(0);
+ }
+ if ((size_t)ret > resbufsiz) {
+ stats[LEAF_begemotNgTooLargeMsgs]++;
+ syslog(LOG_WARNING, "ng message too large");
+ free(mesg);
+ errno = EFBIG;
+ return (NULL);
+ }
+ return (mesg);
+}
+
+static void
+csock_input(int fd __unused, void *udata __unused)
+{
+ struct ng_mesg *mesg;
+ char path[NG_PATHSIZ];
+
+ if ((mesg = csock_read(path)) == NULL)
+ return;
+
+ csock_handle(mesg, path);
+}
+
+/*
+ * Write a message to a node.
+ */
+int
+ng_output(const char *path, u_int cookie, u_int opcode,
+ const void *arg, size_t arglen)
+{
+ return (NgSendMsg(csock, path, (int)cookie, (int)opcode, arg, arglen));
+}
+int
+ng_output_node(const char *node, u_int cookie, u_int opcode,
+ const void *arg, size_t arglen)
+{
+ char path[NG_PATHSIZ];
+
+ sprintf(path, "%s:", node);
+ return (ng_output(path, cookie, opcode, arg, arglen));
+}
+int
+ng_output_id(ng_ID_t node, u_int cookie, u_int opcode,
+ const void *arg, size_t arglen)
+{
+ char path[NG_PATHSIZ];
+
+ sprintf(path, "[%x]:", node);
+ return (ng_output(path, cookie, opcode, arg, arglen));
+}
+
+
+
+/*
+ * Execute a synchronuous dialog with the csock. All message we receive, that
+ * do not match our request, are queue until the next call to the IDLE function.
+ */
+struct ng_mesg *
+ng_dialog(const char *path, u_int cookie, u_int opcode,
+ const void *arg, size_t arglen)
+{
+ int token, err;
+ struct ng_mesg *mesg;
+ char rpath[NG_PATHSIZ];
+ struct csock_buf *b;
+ struct timeval end, tv;
+
+ if ((token = ng_output(path, cookie, opcode, arg, arglen)) < 0)
+ return (NULL);
+
+ if (csock_fd)
+ fd_suspend(csock_fd);
+
+ gettimeofday(&end, NULL);
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = (timeout % 1000) * 1000;
+ timeradd(&end, &tv, &end);
+ for (;;) {
+ mesg = NULL;
+ gettimeofday(&tv, NULL);
+ if (timercmp(&tv, &end, >=)) {
+ block:
+ syslog(LOG_WARNING, "no response for request %u/%u",
+ cookie, opcode);
+ errno = EWOULDBLOCK;
+ break;
+ }
+ timersub(&end, &tv, &tv);
+ if (tv.tv_sec == 0 && tv.tv_usec < clockinfo.tick)
+ goto block;
+
+ if (setsockopt(csock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1)
+ syslog(LOG_WARNING, "setsockopt(SO_RCVTIMEO): %m");
+ if ((mesg = csock_read(rpath)) == NULL) {
+ if (errno == EWOULDBLOCK)
+ continue;
+ break;
+ }
+ if (mesg->header.token == (u_int)token)
+ break;
+ if ((b = malloc(sizeof(*b))) == NULL) {
+ stats[LEAF_begemotNgNoMems]++;
+ syslog(LOG_ERR, "out of memory");
+ free(mesg);
+ continue;
+ }
+ b->mesg = mesg;
+ strcpy(b->path, rpath);
+ STAILQ_INSERT_TAIL(&csock_bufs, b, link);
+ }
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ if (setsockopt(csock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1)
+ syslog(LOG_WARNING, "setsockopt(SO_RCVTIMEO,0): %m");
+
+ if (csock_fd) {
+ err = errno;
+ fd_resume(csock_fd);
+ errno = err;
+ }
+
+ return (mesg);
+}
+struct ng_mesg *
+ng_dialog_node(const char *node, u_int cookie, u_int opcode,
+ const void *arg, size_t arglen)
+{
+ char path[NG_PATHSIZ];
+
+ sprintf(path, "%s:", node);
+ return (ng_dialog(path, cookie, opcode, arg, arglen));
+}
+struct ng_mesg *
+ng_dialog_id(ng_ID_t id, u_int cookie, u_int opcode,
+ const void *arg, size_t arglen)
+{
+ char path[NG_PATHSIZ];
+
+ sprintf(path, "[%x]:", id);
+ return (ng_dialog(path, cookie, opcode, arg, arglen));
+}
+
+
+/*
+ * Send a data message to a given hook.
+ */
+int
+ng_send_data(const char *hook, const void *sndbuf, size_t sndlen)
+{
+ return (NgSendData(dsock, hook, sndbuf, sndlen));
+}
+
+/*
+ * Input from a data socket. Dispatch to the function for that hook.
+ */
+static void
+dsock_input(int fd __unused, void *udata __unused)
+{
+ u_char *resbuf, embuf[100];
+ ssize_t len;
+ char hook[NG_HOOKSIZ];
+ struct datareg *d, *d1;
+
+ if ((resbuf = malloc(resbufsiz + 1)) == NULL) {
+ stats[LEAF_begemotNgNoMems]++;
+ syslog(LOG_CRIT, "out of memory");
+ (void)NgRecvData(fd, embuf, sizeof(embuf), hook);
+ errno = ENOMEM;
+ return;
+ }
+ if ((len = NgRecvData(fd, resbuf, resbufsiz + 1, hook)) == -1) {
+ stats[LEAF_begemotNgDataReadErrs]++;
+ syslog(LOG_ERR, "reading message: %m");
+ free(resbuf);
+ return;
+ }
+ if (len == 0) {
+ free(resbuf);
+ return;
+ }
+ if ((size_t)len == resbufsiz + 1) {
+ stats[LEAF_begemotNgTooLargeDatas]++;
+ syslog(LOG_WARNING, "message too long");
+ free(resbuf);
+ return;
+ }
+
+ /*
+ * Dispatch message. Maybe dispatched to more than one function.
+ */
+ d = SLIST_FIRST(&datareg_list);
+ while (d != NULL) {
+ d1 = SLIST_NEXT(d, link);
+ if (strcmp(hook, d->hook) == 0)
+ (*d->func)(hook, resbuf, len, d->arg);
+ d = d1;
+ }
+
+ free(resbuf);
+}
+
+/*
+ * The SNMP daemon is about to wait for an event. Look whether we have
+ * netgraph messages waiting. If yes, drain the queue.
+ */
+static void
+ng_idle(void)
+{
+ struct csock_buf *b;
+
+ /* execute waiting csock_bufs */
+ while ((b = STAILQ_FIRST(&csock_bufs)) != NULL) {
+ STAILQ_REMOVE_HEAD(&csock_bufs, link);
+ csock_handle(b->mesg, b->path);
+ free(b);
+ }
+}
+
+/*
+ * Called when the module is loaded. Returning a non-zero value means,
+ * rejecting the initialisation.
+ *
+ * We make the netgraph socket.
+ */
+static int
+ng_init(struct lmodule *mod, int argc, char *argv[])
+{
+ int name[2];
+ size_t len;
+
+ module = mod;
+
+ if (argc == 0) {
+ if ((snmp_nodename = malloc(strlen(NODENAME) + 1)) == NULL)
+ return (ENOMEM);
+ strcpy(snmp_nodename, NODENAME);
+ } else {
+ if ((snmp_nodename = malloc(NG_NODESIZ)) == NULL)
+ return (ENOMEM);
+ strlcpy(snmp_nodename, argv[0], NG_NODESIZ);
+ }
+
+ /* fetch clockinfo (for the number of microseconds per tick) */
+ name[0] = CTL_KERN;
+ name[1] = KERN_CLOCKRATE;
+ len = sizeof(clockinfo);
+ if (sysctl(name, 2, &clockinfo, &len, NULL, 0) == -1)
+ return (errno);
+
+ TAILQ_INIT(&ngtype_list);
+
+ return (0);
+}
+
+/*
+ * Get the node Id/name/type of a node.
+ */
+ng_ID_t
+ng_node_id(const char *path)
+{
+ struct ng_mesg *resp;
+ ng_ID_t id;
+
+ if ((resp = ng_dialog(path, NGM_GENERIC_COOKIE, NGM_NODEINFO,
+ NULL, 0)) == NULL)
+ return (0);
+ id = ((struct nodeinfo *)(void *)resp->data)->id;
+ free(resp);
+ return (id);
+}
+ng_ID_t
+ng_node_id_node(const char *node)
+{
+ struct ng_mesg *resp;
+ ng_ID_t id;
+
+ if ((resp = ng_dialog_node(node, NGM_GENERIC_COOKIE, NGM_NODEINFO,
+ NULL, 0)) == NULL)
+ return (0);
+ id = ((struct nodeinfo *)(void *)resp->data)->id;
+ free(resp);
+ return (id);
+}
+ng_ID_t
+ng_node_name(ng_ID_t id, char *name)
+{
+ struct ng_mesg *resp;
+
+ if ((resp = ng_dialog_id(id, NGM_GENERIC_COOKIE, NGM_NODEINFO,
+ NULL, 0)) == NULL)
+ return (0);
+ strcpy(name, ((struct nodeinfo *)(void *)resp->data)->name);
+ free(resp);
+ return (id);
+
+}
+ng_ID_t
+ng_node_type(ng_ID_t id, char *type)
+{
+ struct ng_mesg *resp;
+
+ if ((resp = ng_dialog_id(id, NGM_GENERIC_COOKIE, NGM_NODEINFO,
+ NULL, 0)) == NULL)
+ return (0);
+ strcpy(type, ((struct nodeinfo *)(void *)resp->data)->type);
+ free(resp);
+ return (id);
+}
+
+/*
+ * Connect our node to some other node
+ */
+int
+ng_connect_node(const char *node, const char *ourhook, const char *peerhook)
+{
+ struct ngm_connect conn;
+
+ snprintf(conn.path, NG_PATHSIZ, "%s:", node);
+ strlcpy(conn.ourhook, ourhook, NG_HOOKSIZ);
+ strlcpy(conn.peerhook, peerhook, NG_HOOKSIZ);
+ return (NgSendMsg(csock, ".:",
+ NGM_GENERIC_COOKIE, NGM_CONNECT, &conn, sizeof(conn)));
+}
+int
+ng_connect_id(ng_ID_t id, const char *ourhook, const char *peerhook)
+{
+ struct ngm_connect conn;
+
+ snprintf(conn.path, NG_PATHSIZ, "[%x]:", id);
+ strlcpy(conn.ourhook, ourhook, NG_HOOKSIZ);
+ strlcpy(conn.peerhook, peerhook, NG_HOOKSIZ);
+ return (NgSendMsg(csock, ".:",
+ NGM_GENERIC_COOKIE, NGM_CONNECT, &conn, sizeof(conn)));
+}
+
+int
+ng_connect2_id(ng_ID_t id, ng_ID_t peer, const char *ourhook,
+ const char *peerhook)
+{
+ struct ngm_connect conn;
+ char path[NG_PATHSIZ];
+
+ snprintf(path, NG_PATHSIZ, "[%x]:", id);
+
+ snprintf(conn.path, NG_PATHSIZ, "[%x]:", peer);
+ strlcpy(conn.ourhook, ourhook, NG_HOOKSIZ);
+ strlcpy(conn.peerhook, peerhook, NG_HOOKSIZ);
+ return (NgSendMsg(csock, path,
+ NGM_GENERIC_COOKIE, NGM_CONNECT, &conn, sizeof(conn)));
+}
+
+int
+ng_connect2_tee_id(ng_ID_t id, ng_ID_t peer, const char *ourhook,
+ const char *peerhook)
+{
+ struct ngm_connect conn;
+ char path[NG_PATHSIZ];
+ ng_ID_t tee;
+
+ if ((tee = ng_mkpeer_id(id, NULL, "tee", ourhook, "left")) == 0)
+ return (-1);
+
+ snprintf(path, NG_PATHSIZ, "[%x]:", tee);
+
+ snprintf(conn.path, NG_PATHSIZ, "[%x]:", peer);
+ strlcpy(conn.ourhook, "right", NG_HOOKSIZ);
+ strlcpy(conn.peerhook, peerhook, NG_HOOKSIZ);
+ return (NgSendMsg(csock, path,
+ NGM_GENERIC_COOKIE, NGM_CONNECT, &conn, sizeof(conn)));
+}
+
+/*
+ * Ensure that a node of type 'type' is connected to 'hook' of 'node'
+ * and return its node id. tee nodes between node and the target node
+ * are skipped. If the type is wrong, or the hook is a dead-end return 0.
+ * If type is NULL, it is not checked.
+ */
+static ng_ID_t
+ng_next_node_id_internal(ng_ID_t node, const char *type, const char *hook,
+ int skip_tee)
+{
+ struct ng_mesg *resp;
+ struct hooklist *hooklist;
+ u_int i;
+
+ if ((resp = ng_dialog_id(node, NGM_GENERIC_COOKIE, NGM_LISTHOOKS,
+ NULL, 0)) == NULL) {
+ syslog(LOG_ERR, "get hook list: %m");
+ exit(1);
+ }
+ hooklist = (struct hooklist *)(void *)resp->data;
+
+ for (i = 0; i < hooklist->nodeinfo.hooks; i++)
+ if (strcmp(hooklist->link[i].ourhook, hook) == 0)
+ break;
+
+ if (i == hooklist->nodeinfo.hooks) {
+ free(resp);
+ return (0);
+ }
+
+ node = hooklist->link[i].nodeinfo.id;
+
+ if (skip_tee && strcmp(hooklist->link[i].nodeinfo.type, "tee") == 0) {
+ if (strcmp(hooklist->link[i].peerhook, "left") == 0)
+ node = ng_next_node_id(node, type, "right");
+ else if (strcmp(hooklist->link[i].peerhook, "right") == 0)
+ node = ng_next_node_id(node, type, "left");
+ else if (type != NULL &&
+ strcmp(hooklist->link[i].nodeinfo.type, type) != 0)
+ node = 0;
+
+ } else if (type != NULL &&
+ strcmp(hooklist->link[i].nodeinfo.type, type) != 0)
+ node = 0;
+
+ free(resp);
+
+ return (node);
+}
+
+/*
+ * Ensure that a node of type 'type' is connected to 'hook' of 'node'
+ * and return its node id. tee nodes between node and the target node
+ * are skipped. If the type is wrong, or the hook is a dead-end return 0.
+ * If type is NULL, it is not checked.
+ */
+ng_ID_t
+ng_next_node_id(ng_ID_t node, const char *type, const char *hook)
+{
+ return (ng_next_node_id_internal(node, type, hook, 1));
+}
+
+ng_ID_t
+ng_mkpeer_id(ng_ID_t id, const char *nodename, const char *type,
+ const char *hook, const char *peerhook)
+{
+ char path[NG_PATHSIZ];
+ struct ngm_mkpeer mkpeer;
+ struct ngm_name name;
+
+ strlcpy(mkpeer.type, type, NG_TYPESIZ);
+ strlcpy(mkpeer.ourhook, hook, NG_HOOKSIZ);
+ strlcpy(mkpeer.peerhook, peerhook, NG_HOOKSIZ);
+
+ sprintf(path, "[%x]:", id);
+ if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE, NGM_MKPEER,
+ &mkpeer, sizeof(mkpeer)) == -1)
+ return (0);
+
+ if ((id = ng_next_node_id_internal(id, NULL, hook, 0)) == 0)
+ return (0);
+
+ if (nodename != NULL) {
+ strcpy(name.name, nodename);
+ sprintf(path, "[%x]:", id);
+ if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE, NGM_NAME,
+ &name, sizeof(name)) == -1)
+ return (0);
+ }
+ return (id);
+}
+
+/*
+ * SHutdown node
+ */
+int
+ng_shutdown_id(ng_ID_t id)
+{
+ char path[NG_PATHSIZ];
+
+ snprintf(path, NG_PATHSIZ, "[%x]:", id);
+ return (NgSendMsg(csock, path, NGM_GENERIC_COOKIE,
+ NGM_SHUTDOWN, NULL, 0));
+}
+
+/*
+ * Disconnect one of our hooks
+ */
+int
+ng_rmhook(const char *ourhook)
+{
+ struct ngm_rmhook rmhook;
+
+ strlcpy(rmhook.ourhook, ourhook, NG_HOOKSIZ);
+ return (NgSendMsg(csock, ".:",
+ NGM_GENERIC_COOKIE, NGM_RMHOOK, &rmhook, sizeof(rmhook)));
+}
+
+/*
+ * Disconnect a hook of a node
+ */
+int
+ng_rmhook_id(ng_ID_t id, const char *hook)
+{
+ struct ngm_rmhook rmhook;
+ char path[NG_PATHSIZ];
+
+ strlcpy(rmhook.ourhook, hook, NG_HOOKSIZ);
+ snprintf(path, NG_PATHSIZ, "[%x]:", id);
+ return (NgSendMsg(csock, path,
+ NGM_GENERIC_COOKIE, NGM_RMHOOK, &rmhook, sizeof(rmhook)));
+}
+
+/*
+ * Disconnect a hook and shutdown all tee nodes that were connected to that
+ * hook.
+ */
+int
+ng_rmhook_tee_id(ng_ID_t node, const char *hook)
+{
+ struct ng_mesg *resp;
+ struct hooklist *hooklist;
+ u_int i;
+ int first = 1;
+ ng_ID_t next_node;
+ const char *next_hook;
+
+ again:
+ /* if we have just shutdown a tee node, which had no other hooks
+ * connected, the node id may already be wrong here. */
+ if ((resp = ng_dialog_id(node, NGM_GENERIC_COOKIE, NGM_LISTHOOKS,
+ NULL, 0)) == NULL)
+ return (0);
+
+ hooklist = (struct hooklist *)(void *)resp->data;
+
+ for (i = 0; i < hooklist->nodeinfo.hooks; i++)
+ if (strcmp(hooklist->link[i].ourhook, hook) == 0)
+ break;
+
+ if (i == hooklist->nodeinfo.hooks) {
+ free(resp);
+ return (0);
+ }
+
+ next_node = 0;
+ next_hook = NULL;
+ if (strcmp(hooklist->link[i].nodeinfo.type, "tee") == 0) {
+ if (strcmp(hooklist->link[i].peerhook, "left") == 0) {
+ next_node = hooklist->link[i].nodeinfo.id;
+ next_hook = "right";
+ } else if (strcmp(hooklist->link[i].peerhook, "right") == 0) {
+ next_node = hooklist->link[i].nodeinfo.id;
+ next_hook = "left";
+ }
+ }
+ free(resp);
+
+ if (first) {
+ ng_rmhook_id(node, hook);
+ first = 0;
+ } else {
+ ng_shutdown_id(node);
+ }
+ if ((node = next_node) == 0)
+ return (0);
+ hook = next_hook;
+
+ goto again;
+}
+
+/*
+ * Get the peer hook of a hook on a given node. Skip any tee nodes in between
+ */
+int
+ng_peer_hook_id(ng_ID_t node, const char *hook, char *peerhook)
+{
+ struct ng_mesg *resp;
+ struct hooklist *hooklist;
+ u_int i;
+ int ret;
+
+ if ((resp = ng_dialog_id(node, NGM_GENERIC_COOKIE, NGM_LISTHOOKS,
+ NULL, 0)) == NULL) {
+ syslog(LOG_ERR, "get hook list: %m");
+ exit(1);
+ }
+ hooklist = (struct hooklist *)(void *)resp->data;
+
+ for (i = 0; i < hooklist->nodeinfo.hooks; i++)
+ if (strcmp(hooklist->link[i].ourhook, hook) == 0)
+ break;
+
+ if (i == hooklist->nodeinfo.hooks) {
+ free(resp);
+ return (-1);
+ }
+
+ node = hooklist->link[i].nodeinfo.id;
+
+ ret = 0;
+ if (strcmp(hooklist->link[i].nodeinfo.type, "tee") == 0) {
+ if (strcmp(hooklist->link[i].peerhook, "left") == 0)
+ ret = ng_peer_hook_id(node, "right", peerhook);
+ else if (strcmp(hooklist->link[i].peerhook, "right") == 0)
+ ret = ng_peer_hook_id(node, "left", peerhook);
+ else
+ strcpy(peerhook, hooklist->link[i].peerhook);
+
+ } else
+ strcpy(peerhook, hooklist->link[i].peerhook);
+
+ free(resp);
+
+ return (ret);
+}
+
+
+/*
+ * Now the module is started. Select on the sockets, so that we can get
+ * unsolicited input.
+ */
+static void
+ng_start(void)
+{
+ if (snmp_node == 0) {
+ if (NgMkSockNode(snmp_nodename, &csock, &dsock) < 0) {
+ syslog(LOG_ERR, "NgMkSockNode: %m");
+ exit(1);
+ }
+ snmp_node = ng_node_id(".:");
+ }
+
+ if ((csock_fd = fd_select(csock, csock_input, NULL, module)) == NULL) {
+ syslog(LOG_ERR, "fd_select failed on csock: %m");
+ return;
+ }
+ if ((dsock_fd = fd_select(dsock, dsock_input, NULL, module)) == NULL) {
+ syslog(LOG_ERR, "fd_select failed on dsock: %m");
+ return;
+ }
+
+ reg_index = or_register(&oid_begemotNg,
+ "The MIB for the NetGraph access module for SNMP.", module);
+}
+
+/*
+ * Called, when the module is to be unloaded after it was successfully loaded
+ */
+static int
+ng_fini(void)
+{
+ struct ngtype *t;
+
+ while ((t = TAILQ_FIRST(&ngtype_list)) != NULL) {
+ TAILQ_REMOVE(&ngtype_list, t, link);
+ free(t);
+ }
+
+ if (csock_fd != NULL)
+ fd_deselect(csock_fd);
+ (void)close(csock);
+
+ if (dsock_fd != NULL)
+ fd_deselect(dsock_fd);
+ (void)close(dsock);
+
+ free(snmp_nodename);
+
+ or_unregister(reg_index);
+
+ return (0);
+}
+
+const struct snmp_module config = {
+ "This module implements access to the netgraph sub-system",
+ ng_init,
+ ng_fini,
+ ng_idle,
+ NULL,
+ NULL,
+ ng_start,
+ NULL,
+ netgraph_ctree,
+ netgraph_CTREE_SIZE,
+ NULL
+};
+
+int
+op_ng_config(struct snmp_context *ctx, struct snmp_value *value,
+ u_int sub, u_int iidx __unused, enum snmp_op op)
+{
+ asn_subid_t which = value->var.subs[sub - 1];
+ int ret;
+
+ switch (op) {
+
+ case SNMP_OP_GETNEXT:
+ abort();
+
+ case SNMP_OP_GET:
+ /*
+ * Come here for GET, GETNEXT and COMMIT
+ */
+ switch (which) {
+
+ case LEAF_begemotNgControlNodeName:
+ return (string_get(value, snmp_nodename, -1));
+
+ case LEAF_begemotNgResBufSiz:
+ value->v.integer = resbufsiz;
+ break;
+
+ case LEAF_begemotNgTimeout:
+ value->v.integer = timeout;
+ break;
+
+ case LEAF_begemotNgDebugLevel:
+ value->v.uint32 = debug_level;
+ break;
+
+ default:
+ abort();
+ }
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_SET:
+ switch (which) {
+
+ case LEAF_begemotNgControlNodeName:
+ /* only at initialisation */
+ if (community != COMM_INITIALIZE)
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ if (snmp_node != 0)
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ if ((ret = string_save(value, ctx, -1, &snmp_nodename))
+ != SNMP_ERR_NOERROR)
+ return (ret);
+
+ if (NgMkSockNode(snmp_nodename, &csock, &dsock) < 0) {
+ syslog(LOG_ERR, "NgMkSockNode: %m");
+ string_rollback(ctx, &snmp_nodename);
+ return (SNMP_ERR_GENERR);
+ }
+ snmp_node = ng_node_id(".:");
+
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotNgResBufSiz:
+ ctx->scratch->int1 = resbufsiz;
+ if (value->v.integer < 1024 ||
+ value->v.integer > 0x10000)
+ return (SNMP_ERR_WRONG_VALUE);
+ resbufsiz = value->v.integer;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotNgTimeout:
+ ctx->scratch->int1 = timeout;
+ if (value->v.integer < 10 ||
+ value->v.integer > 10000)
+ return (SNMP_ERR_WRONG_VALUE);
+ timeout = value->v.integer;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotNgDebugLevel:
+ ctx->scratch->int1 = debug_level;
+ debug_level = value->v.uint32;
+ NgSetDebug(debug_level);
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+
+ case SNMP_OP_ROLLBACK:
+ switch (which) {
+
+ case LEAF_begemotNgControlNodeName:
+ string_rollback(ctx, &snmp_nodename);
+ close(csock);
+ close(dsock);
+ snmp_node = 0;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotNgResBufSiz:
+ resbufsiz = ctx->scratch->int1;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotNgTimeout:
+ timeout = ctx->scratch->int1;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotNgDebugLevel:
+ debug_level = ctx->scratch->int1;
+ NgSetDebug(debug_level);
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+
+ case SNMP_OP_COMMIT:
+ switch (which) {
+
+ case LEAF_begemotNgControlNodeName:
+ string_commit(ctx);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_begemotNgResBufSiz:
+ case LEAF_begemotNgTimeout:
+ case LEAF_begemotNgDebugLevel:
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+ }
+ abort();
+}
+
+int
+op_ng_stats(struct snmp_context *ctx __unused, struct snmp_value *value,
+ u_int sub, u_int iidx __unused, enum snmp_op op)
+{
+ switch (op) {
+
+ case SNMP_OP_GETNEXT:
+ abort();
+
+ case SNMP_OP_GET:
+ value->v.uint32 = stats[value->var.subs[sub - 1] - 1];
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ abort();
+ }
+ abort();
+}
+
+/*
+ * Netgraph type table
+ */
+static int
+fetch_types(void)
+{
+ struct ngtype *t;
+ struct typelist *typelist;
+ struct ng_mesg *resp;
+ u_int u, i;
+
+ if (this_tick <= ngtype_tick)
+ return (0);
+
+ while ((t = TAILQ_FIRST(&ngtype_list)) != NULL) {
+ TAILQ_REMOVE(&ngtype_list, t, link);
+ free(t);
+ }
+
+ if ((resp = ng_dialog_id(snmp_node, NGM_GENERIC_COOKIE,
+ NGM_LISTTYPES, NULL, 0)) == NULL)
+ return (SNMP_ERR_GENERR);
+ typelist = (struct typelist *)(void *)resp->data;
+
+ for (u = 0; u < typelist->numtypes; u++) {
+ if ((t = malloc(sizeof(*t))) == NULL) {
+ free(resp);
+ return (SNMP_ERR_GENERR);
+ }
+ strcpy(t->name, typelist->typeinfo[u].type_name);
+ t->index.subs[0] = strlen(t->name);
+ t->index.len = t->index.subs[0] + 1;
+ for (i = 0; i < t->index.subs[0]; i++)
+ t->index.subs[i + 1] = t->name[i];
+
+ INSERT_OBJECT_OID(t, &ngtype_list);
+ }
+
+ ngtype_tick = this_tick;
+
+ free(resp);
+ return (0);
+}
+
+/*
+ * Try to load the netgraph type with the given name. We assume, that
+ * type 'type' is implemented in the kernel module 'ng_type'.
+ */
+static int
+ngtype_load(const u_char *name, size_t namelen)
+{
+ char *mod;
+ int ret;
+
+ if ((mod = malloc(namelen + 4)) == NULL)
+ return (-1);
+ strcpy(mod, "ng_");
+ strncpy(mod + 3, name, namelen);
+ mod[namelen + 3] = '\0';
+
+ ret = kldload(mod);
+ free(mod);
+ return (ret);
+}
+
+/*
+ * Unload a netgraph type.
+ */
+static int
+ngtype_unload(const u_char *name, size_t namelen)
+{
+ char *mod;
+ int id;
+
+ if ((mod = malloc(namelen + 4)) == NULL)
+ return (-1);
+ strcpy(mod, "ng_");
+ strncpy(mod + 3, name, namelen);
+ mod[namelen + 3] = '\0';
+
+ if ((id = kldfind(mod)) == -1) {
+ free(mod);
+ return (-1);
+ }
+ free(mod);
+ return (kldunload(id));
+}
+
+int
+op_ng_type(struct snmp_context *ctx, struct snmp_value *value,
+ u_int sub, u_int iidx, enum snmp_op op)
+{
+ asn_subid_t which = value->var.subs[sub - 1];
+ struct ngtype *t;
+ u_char *name;
+ size_t namelen;
+ int status = 1;
+ int ret;
+
+ switch (op) {
+
+ case SNMP_OP_GETNEXT:
+ if ((ret = fetch_types()) != 0)
+ return (ret);
+ if ((t = NEXT_OBJECT_OID(&ngtype_list, &value->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ index_append(&value->var, sub, &t->index);
+ break;
+
+ case SNMP_OP_GET:
+ if ((ret = fetch_types()) != 0)
+ return (ret);
+ if ((t = FIND_OBJECT_OID(&ngtype_list, &value->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+
+ case SNMP_OP_SET:
+ if (index_decode(&value->var, sub, iidx, &name, &namelen))
+ return (SNMP_ERR_NO_CREATION);
+ if (namelen == 0 || namelen >= NG_TYPESIZ) {
+ free(name);
+ return (SNMP_ERR_NO_CREATION);
+ }
+ if ((ret = fetch_types()) != 0) {
+ free(name);
+ return (ret);
+ }
+ t = FIND_OBJECT_OID(&ngtype_list, &value->var, sub);
+
+ if (which != LEAF_begemotNgTypeStatus) {
+ free(name);
+ if (t != NULL)
+ return (SNMP_ERR_NOT_WRITEABLE);
+ return (SNMP_ERR_NO_CREATION);
+ }
+ if (!TRUTH_OK(value->v.integer)) {
+ free(name);
+ return (SNMP_ERR_WRONG_VALUE);
+ }
+ ctx->scratch->int1 = TRUTH_GET(value->v.integer);
+ ctx->scratch->int1 |= (t != NULL) << 1;
+ ctx->scratch->ptr2 = name;
+ ctx->scratch->int2 = namelen;
+
+ if (t == NULL) {
+ /* type not loaded */
+ if (ctx->scratch->int1 & 1) {
+ /* request to load */
+ if (ngtype_load(name, namelen) == -1) {
+ free(name);
+ if (errno == ENOENT)
+ return (SNMP_ERR_INCONS_NAME);
+ else
+ return (SNMP_ERR_GENERR);
+ }
+ }
+ } else {
+ /* is type loaded */
+ if (!(ctx->scratch->int1 & 1)) {
+ /* request to unload */
+ if (ngtype_unload(name, namelen) == -1) {
+ free(name);
+ return (SNMP_ERR_GENERR);
+ }
+ }
+ }
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_ROLLBACK:
+ ret = SNMP_ERR_NOERROR;
+ if (!(ctx->scratch->int1 & 2)) {
+ /* did not exist */
+ if (ctx->scratch->int1 & 1) {
+ /* request to load - unload */
+ if (ngtype_unload(ctx->scratch->ptr2,
+ ctx->scratch->int2) == -1)
+ ret = SNMP_ERR_UNDO_FAILED;
+ }
+ } else {
+ /* did exist */
+ if (!(ctx->scratch->int1 & 1)) {
+ /* request to unload - reload */
+ if (ngtype_load(ctx->scratch->ptr2,
+ ctx->scratch->int2) == -1)
+ ret = SNMP_ERR_UNDO_FAILED;
+ }
+ }
+ free(ctx->scratch->ptr2);
+ return (ret);
+
+ case SNMP_OP_COMMIT:
+ free(ctx->scratch->ptr2);
+ return (SNMP_ERR_NOERROR);
+
+ default:
+ abort();
+ }
+
+ /*
+ * Come here for GET and COMMIT
+ */
+ switch (which) {
+
+ case LEAF_begemotNgTypeStatus:
+ value->v.integer = status;
+ break;
+
+ default:
+ abort();
+ }
+ return (SNMP_ERR_NOERROR);
+}
+
+/*
+ * Implement the node table
+ */
+static int
+find_node(const struct asn_oid *oid, u_int sub, struct nodeinfo *info)
+{
+ ng_ID_t id = oid->subs[sub];
+ struct ng_mesg *resp;
+
+ if ((resp = ng_dialog_id(id, NGM_GENERIC_COOKIE, NGM_NODEINFO,
+ NULL, 0)) == NULL)
+ return (-1);
+
+ *info = *(struct nodeinfo *)(void *)resp->data;
+ free(resp);
+ return (0);
+}
+
+static int
+ncmp(const void *p1, const void *p2)
+{
+ const struct nodeinfo *i1 = p1;
+ const struct nodeinfo *i2 = p2;
+
+ if (i1->id < i2->id)
+ return (-1);
+ if (i1->id > i2->id)
+ return (+1);
+ return (0);
+}
+
+static int
+find_node_next(const struct asn_oid *oid, u_int sub, struct nodeinfo *info)
+{
+ u_int idxlen = oid->len - sub;
+ struct ng_mesg *resp;
+ struct namelist *list;
+ ng_ID_t id;
+ u_int i;
+
+ if ((resp = ng_dialog_id(snmp_node, NGM_GENERIC_COOKIE, NGM_LISTNODES,
+ NULL, 0)) == NULL)
+ return (-1);
+ list = (struct namelist *)(void *)resp->data;
+
+ qsort(list->nodeinfo, list->numnames, sizeof(list->nodeinfo[0]), ncmp);
+
+ if (idxlen == 0) {
+ if (list->numnames == 0) {
+ free(resp);
+ return (-1);
+ }
+ *info = list->nodeinfo[0];
+ free(resp);
+ return (0);
+ }
+ id = oid->subs[sub];
+
+ for (i = 0; i < list->numnames; i++)
+ if (list->nodeinfo[i].id > id) {
+ *info = list->nodeinfo[i];
+ free(resp);
+ return (0);
+ }
+
+ free(resp);
+ return (-1);
+}
+
+int
+op_ng_node(struct snmp_context *ctx __unused, struct snmp_value *value,
+ u_int sub, u_int iidx __unused, enum snmp_op op)
+{
+ asn_subid_t which = value->var.subs[sub - 1];
+ u_int idxlen = value->var.len - sub;
+ struct nodeinfo nodeinfo;
+
+ switch (op) {
+
+ case SNMP_OP_GETNEXT:
+ if (find_node_next(&value->var, sub, &nodeinfo) == -1)
+ return (SNMP_ERR_NOSUCHNAME);
+ value->var.len = sub + 1;
+ value->var.subs[sub] = nodeinfo.id;
+ break;
+
+ case SNMP_OP_GET:
+ if (idxlen != 1)
+ return (SNMP_ERR_NOSUCHNAME);
+ if (find_node(&value->var, sub, &nodeinfo) == -1)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+
+ case SNMP_OP_SET:
+ if (idxlen != 1)
+ return (SNMP_ERR_NO_CREATION);
+ if (find_node(&value->var, sub, &nodeinfo) == -1)
+ return (SNMP_ERR_NO_CREATION);
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ default:
+ abort();
+ }
+
+ /*
+ * Come here for GET and COMMIT
+ */
+ switch (which) {
+
+ case LEAF_begemotNgNodeStatus:
+ value->v.integer = 1;
+ break;
+ case LEAF_begemotNgNodeName:
+ return (string_get(value, nodeinfo.name, -1));
+ case LEAF_begemotNgNodeType:
+ return (string_get(value, nodeinfo.type, -1));
+ case LEAF_begemotNgNodeHooks:
+ value->v.uint32 = nodeinfo.hooks;
+ break;
+
+ default:
+ abort();
+ }
+ return (SNMP_ERR_NOERROR);
+}
+
+/*
+ * Implement the hook table
+ */
+static int
+find_hook(int32_t id, const u_char *hook, size_t hooklen, struct linkinfo *info)
+{
+ struct ng_mesg *resp;
+ struct hooklist *list;
+ u_int i;
+
+ if ((resp = ng_dialog_id(id, NGM_GENERIC_COOKIE,
+ NGM_LISTHOOKS, NULL, 0)) == NULL)
+ return (-1);
+
+ list = (struct hooklist *)(void *)resp->data;
+
+ for (i = 0; i < list->nodeinfo.hooks; i++) {
+ if (strlen(list->link[i].ourhook) == hooklen &&
+ strncmp(list->link[i].ourhook, hook, hooklen) == 0) {
+ *info = list->link[i];
+ free(resp);
+ return (0);
+ }
+ }
+ free(resp);
+ return (-1);
+}
+
+static int
+hook_cmp(const void *p1, const void *p2)
+{
+ const struct linkinfo *i1 = p1;
+ const struct linkinfo *i2 = p2;
+
+ if (strlen(i1->ourhook) < strlen(i2->ourhook))
+ return (-1);
+ if (strlen(i1->ourhook) > strlen(i2->ourhook))
+ return (+1);
+ return (strcmp(i1->ourhook, i2->ourhook));
+}
+
+static int
+find_hook_next(const struct asn_oid *oid, u_int sub, struct nodeinfo *nodeinfo,
+ struct linkinfo *linkinfo)
+{
+ u_int idxlen = oid->len - sub;
+ struct namelist *list;
+ struct ng_mesg *resp;
+ struct hooklist *hooks;
+ struct ng_mesg *resp1;
+ u_int node_index;
+ struct asn_oid idx;
+ u_int i, j;
+
+ /*
+ * Get and sort Node list
+ */
+ if ((resp = ng_dialog_id(snmp_node, NGM_GENERIC_COOKIE, NGM_LISTNODES,
+ NULL, 0)) == NULL)
+ return (-1);
+ list = (struct namelist *)(void *)resp->data;
+
+ qsort(list->nodeinfo, list->numnames, sizeof(list->nodeinfo[0]), ncmp);
+
+ /*
+ * If we have no index, take the first node and return the
+ * first hook.
+ */
+ if (idxlen == 0) {
+ node_index = 0;
+ goto return_first_hook;
+ }
+
+ /*
+ * Locate node
+ */
+ for (node_index = 0; node_index < list->numnames; node_index++)
+ if (list->nodeinfo[node_index].id >= oid->subs[sub])
+ break;
+
+ /*
+ * If we have only the node part of the index take, or
+ * there is no node with that Id, take the first hook of that node.
+ */
+ if (idxlen == 1 || node_index >= list->numnames ||
+ list->nodeinfo[node_index].id > oid->subs[sub])
+ goto return_first_hook;
+
+ /*
+ * We had an exact match on the node id and have (at last part)
+ * of the hook name index. Loop through the hooks of the node
+ * and find the next one.
+ */
+ if ((resp1 = ng_dialog_id(list->nodeinfo[node_index].id,
+ NGM_GENERIC_COOKIE, NGM_LISTHOOKS, NULL, 0)) == NULL) {
+ free(resp);
+ return (-1);
+ }
+ hooks = (struct hooklist *)(void *)resp1->data;
+ if (hooks->nodeinfo.hooks > 0) {
+ qsort(hooks->link, hooks->nodeinfo.hooks,
+ sizeof(hooks->link[0]), hook_cmp);
+ for (i = 0; i < hooks->nodeinfo.hooks; i++) {
+ idx.len = strlen(hooks->link[i].ourhook) + 1;
+ idx.subs[0] = idx.len - 1;
+ for (j = 0; j < idx.len; j++)
+ idx.subs[j + 1] = hooks->link[i].ourhook[j];
+ if (index_compare(oid, sub + 1, &idx) < 0)
+ break;
+ }
+ if (i < hooks->nodeinfo.hooks) {
+ *nodeinfo = hooks->nodeinfo;
+ *linkinfo = hooks->link[i];
+
+ free(resp);
+ free(resp1);
+ return (0);
+ }
+ }
+
+ /* no hook found larger than the index on the index node - take
+ * first hook of next node */
+ free(resp1);
+ node_index++;
+
+ return_first_hook:
+ while (node_index < list->numnames) {
+ if ((resp1 = ng_dialog_id(list->nodeinfo[node_index].id,
+ NGM_GENERIC_COOKIE, NGM_LISTHOOKS, NULL, 0)) == NULL)
+ break;
+ hooks = (struct hooklist *)(void *)resp1->data;
+ if (hooks->nodeinfo.hooks > 0) {
+ qsort(hooks->link, hooks->nodeinfo.hooks,
+ sizeof(hooks->link[0]), hook_cmp);
+
+ *nodeinfo = hooks->nodeinfo;
+ *linkinfo = hooks->link[0];
+
+ free(resp);
+ free(resp1);
+ return (0);
+ }
+
+ /* if we don't have hooks, try next node */
+ free(resp1);
+ node_index++;
+ }
+
+ free(resp);
+ return (-1);
+}
+
+int
+op_ng_hook(struct snmp_context *ctx __unused, struct snmp_value *value,
+ u_int sub, u_int iidx, enum snmp_op op)
+{
+ asn_subid_t which = value->var.subs[sub - 1];
+ struct linkinfo linkinfo;
+ struct nodeinfo nodeinfo;
+ u_int32_t lid;
+ u_char *hook;
+ size_t hooklen;
+ u_int i;
+
+ switch (op) {
+
+ case SNMP_OP_GETNEXT:
+ if (find_hook_next(&value->var, sub, &nodeinfo, &linkinfo) == -1)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ value->var.len = sub + 1 + 1 + strlen(linkinfo.ourhook);
+ value->var.subs[sub] = nodeinfo.id;
+ value->var.subs[sub + 1] = strlen(linkinfo.ourhook);
+ for (i = 0; i < strlen(linkinfo.ourhook); i++)
+ value->var.subs[sub + i + 2] =
+ linkinfo.ourhook[i];
+ break;
+
+ case SNMP_OP_GET:
+ if (index_decode(&value->var, sub, iidx, &lid,
+ &hook, &hooklen))
+ return (SNMP_ERR_NOSUCHNAME);
+ if (find_hook(lid, hook, hooklen, &linkinfo) == -1) {
+ free(hook);
+ return (SNMP_ERR_NOSUCHNAME);
+ }
+ free(hook);
+ break;
+
+ case SNMP_OP_SET:
+ if (index_decode(&value->var, sub, iidx, &lid,
+ &hook, &hooklen))
+ return (SNMP_ERR_NO_CREATION);
+ if (find_hook(lid, hook, hooklen, &linkinfo) == -1) {
+ free(hook);
+ return (SNMP_ERR_NO_CREATION);
+ }
+ free(hook);
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case SNMP_OP_ROLLBACK:
+ case SNMP_OP_COMMIT:
+ default:
+ abort();
+
+ }
+
+ switch (which) {
+
+ case LEAF_begemotNgHookStatus:
+ value->v.integer = 1;
+ break;
+ case LEAF_begemotNgHookPeerNodeId:
+ value->v.uint32 = linkinfo.nodeinfo.id;
+ break;
+ case LEAF_begemotNgHookPeerHook:
+ return (string_get(value, linkinfo.peerhook, -1));
+ case LEAF_begemotNgHookPeerType:
+ return (string_get(value, linkinfo.nodeinfo.type, -1));
+ default:
+ abort();
+ }
+ return (SNMP_ERR_NOERROR);
+}
diff --git a/usr.sbin/bsnmpd/modules/snmp_netgraph/snmp_netgraph.h b/usr.sbin/bsnmpd/modules/snmp_netgraph/snmp_netgraph.h
new file mode 100644
index 0000000..21e553c
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_netgraph/snmp_netgraph.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2001-2003
+ * Fraunhofer Institute for Open Communication Systems (FhG Fokus).
+ * All rights reserved.
+ *
+ * Author: Harti Brandt <harti@freebsd.org>
+ *
+ * Redistribution of this software and documentation and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ *
+ * 1. Redistributions of source code or documentation must retain the above
+ * copyright notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS
+ * AND ITS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * FRAUNHOFER FOKUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+ * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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$
+ *
+ * Netgraph interface for SNMPd. Exported stuff.
+ */
+#ifndef SNMP_NETGRAPH_H_
+#define SNMP_NETGRAPH_H_
+
+#include <netgraph/ng_message.h>
+
+extern ng_ID_t snmp_node;
+extern u_char *snmp_nodename;
+
+typedef void ng_cookie_f(const struct ng_mesg *, const char *, ng_ID_t, void *);
+typedef void ng_hook_f(const char *, const u_char *, size_t, void *);
+
+void *ng_register_cookie(const struct lmodule *, u_int32_t cookie,
+ ng_ID_t, ng_cookie_f *, void *);
+void ng_unregister_cookie(void *reg);
+
+void *ng_register_hook(const struct lmodule *, const char *,
+ ng_hook_f *, void *);
+void ng_unregister_hook(void *reg);
+
+void ng_unregister_module(const struct lmodule *);
+
+int ng_output(const char *path, u_int cookie, u_int opcode,
+ const void *arg, size_t arglen);
+int ng_output_node(const char *node, u_int cookie, u_int opcode,
+ const void *arg, size_t arglen);
+int ng_output_id(ng_ID_t node, u_int cookie, u_int opcode,
+ const void *arg, size_t arglen);
+
+struct ng_mesg *ng_dialog(const char *path, u_int cookie, u_int opcode,
+ const void *arg, size_t arglen);
+struct ng_mesg *ng_dialog_node(const char *node, u_int cookie, u_int opcode,
+ const void *arg, size_t arglen);
+struct ng_mesg *ng_dialog_id(ng_ID_t id, u_int cookie, u_int opcode,
+ const void *arg, size_t arglen);
+
+int ng_send_data(const char *hook, const void *sndbuf, size_t sndlen);
+
+ng_ID_t ng_mkpeer_id(ng_ID_t, const char *name, const char *type,
+ const char *hook, const char *peerhook);
+int ng_connect_node(const char *node, const char *ourhook, const char *peerhook);
+int ng_connect_id(ng_ID_t id, const char *ourhook, const char *peerhook);
+int ng_connect2_id(ng_ID_t id, ng_ID_t peer, const char *ourhook,
+ const char *peerhook);
+int ng_connect2_tee_id(ng_ID_t id, ng_ID_t peer, const char *ourhook,
+ const char *peerhook);
+int ng_rmhook(const char *ourhook);
+int ng_rmhook_id(ng_ID_t, const char *);
+int ng_rmhook_tee_id(ng_ID_t, const char *);
+int ng_shutdown_id(ng_ID_t);
+
+ng_ID_t ng_next_node_id(ng_ID_t node, const char *type, const char *hook);
+ng_ID_t ng_node_id(const char *path);
+ng_ID_t ng_node_id_node(const char *node);
+ng_ID_t ng_node_name(ng_ID_t, char *);
+ng_ID_t ng_node_type(ng_ID_t, char *);
+int ng_peer_hook_id(ng_ID_t, const char *, char *);
+
+#endif
diff --git a/usr.sbin/bsnmpd/modules/snmp_pf/BEGEMOT-PF-MIB.txt b/usr.sbin/bsnmpd/modules/snmp_pf/BEGEMOT-PF-MIB.txt
new file mode 100644
index 0000000..d2b247f
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_pf/BEGEMOT-PF-MIB.txt
@@ -0,0 +1,1347 @@
+--
+-- ----------------------------------------------------------------------------
+-- "THE BEER-WARE LICENSE" (Revision 42):
+-- <philip@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. -Philip Paeps
+-- ----------------------------------------------------------------------------
+--
+-- $FreeBSD$
+--
+
+BEGEMOT-PF-MIB DEFINITIONS ::= BEGIN
+
+IMPORTS
+ MODULE-IDENTITY, OBJECT-TYPE, Counter64, Integer32,
+ TimeTicks, Unsigned32
+ FROM SNMPv2-SMI
+ TruthValue
+ FROM SNMPv2-TC
+ InetAddress, InetAddressType, InetAddressPrefixLength
+ FROM INET-ADDRESS-MIB
+ begemot
+ FROM BEGEMOT-MIB;
+
+begemotPf MODULE-IDENTITY
+ LAST-UPDATED "201003180000Z"
+ ORGANIZATION "NixSys BVBA"
+ CONTACT-INFO
+ " Philip Paeps
+
+ Postal: NixSys BVBA
+ Louizastraat 14
+ BE-2800 Mechelen
+ Belgium
+
+ E-Mail: philip@FreeBSD.org"
+ DESCRIPTION
+ "The Begemot MIB for the pf packet filter."
+ REVISION "201003180000Z"
+ DESCRIPTION
+ "Modified pfTablesAddrEntry to support IPv6
+ addresses - added pfTablesAddrNetType column
+ and modified type of pfTablesAddrNet to
+ InetAddress."
+ REVISION "200912050000Z"
+ DESCRIPTION
+ "Added support for retrieving counters of labeled
+ pf filter rules via pfLabelspfLabels subtree."
+ REVISION "200501240000Z"
+ DESCRIPTION
+ "Initial revision."
+
+ ::= { begemot 200 }
+
+begemotPfObjects OBJECT IDENTIFIER ::= { begemotPf 1 }
+
+-- --------------------------------------------------------------------------
+
+pfStatus OBJECT IDENTIFIER ::= { begemotPfObjects 1 }
+pfCounter OBJECT IDENTIFIER ::= { begemotPfObjects 2 }
+pfStateTable OBJECT IDENTIFIER ::= { begemotPfObjects 3 }
+pfSrcNodes OBJECT IDENTIFIER ::= { begemotPfObjects 4 }
+pfLimits OBJECT IDENTIFIER ::= { begemotPfObjects 5 }
+pfTimeouts OBJECT IDENTIFIER ::= { begemotPfObjects 6 }
+pfLogInterface OBJECT IDENTIFIER ::= { begemotPfObjects 7 }
+pfInterfaces OBJECT IDENTIFIER ::= { begemotPfObjects 8 }
+pfTables OBJECT IDENTIFIER ::= { begemotPfObjects 9 }
+pfAltq OBJECT IDENTIFIER ::= { begemotPfObjects 10 }
+pfLabels OBJECT IDENTIFIER ::= { begemotPfObjects 11 }
+
+-- --------------------------------------------------------------------------
+
+--
+-- status information
+--
+
+pfStatusRunning OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "True if pf is currently enabled."
+ ::= { pfStatus 1 }
+
+pfStatusRuntime OBJECT-TYPE
+ SYNTAX TimeTicks
+ UNITS "1/100th of a Second"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Indicates how long pf has been enabled. If pf is not currently
+ enabled, indicates how long it has been disabled. If pf has not
+ been enabled or disabled since the system was started, the value
+ will be 0."
+ ::= { pfStatus 2 }
+
+pfStatusDebug OBJECT-TYPE
+ SYNTAX INTEGER { none(0), urgent(1), misc(2), loud(3) }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Indicates the debug level at which pf is running."
+ ::= { pfStatus 3 }
+
+pfStatusHostId OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The (unique) host identifier of the machine running pf."
+ ::= { pfStatus 4 }
+
+-- --------------------------------------------------------------------------
+
+--
+-- counters
+--
+
+pfCounterMatch OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of packets that matched a filter rule."
+ ::= { pfCounter 1 }
+
+pfCounterBadOffset OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of packets with bad offset."
+ ::= { pfCounter 2 }
+
+pfCounterFragment OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of fragmented packets."
+ ::= { pfCounter 3 }
+
+pfCounterShort OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of short packets."
+ ::= { pfCounter 4 }
+
+pfCounterNormalize OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of normalized packets."
+ ::= { pfCounter 5 }
+
+pfCounterMemDrop OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of packets dropped due to memory limitations."
+ ::= { pfCounter 6 }
+
+-- --------------------------------------------------------------------------
+
+--
+-- state table
+--
+
+pfStateTableCount OBJECT-TYPE
+ SYNTAX Unsigned32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of entries in the state table."
+ ::= { pfStateTable 1 }
+
+pfStateTableSearches OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of searches against the state table."
+ ::= { pfStateTable 2 }
+
+pfStateTableInserts OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of entries inserted into the state table."
+ ::= { pfStateTable 3 }
+
+pfStateTableRemovals OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of entries removed from the state table."
+ ::= { pfStateTable 4 }
+
+-- --------------------------------------------------------------------------
+
+--
+-- source nodes
+--
+
+pfSrcNodesCount OBJECT-TYPE
+ SYNTAX Unsigned32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of entries in the source tracking table."
+ ::= { pfSrcNodes 1 }
+
+pfSrcNodesSearches OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of searches against the source tracking table."
+ ::= { pfSrcNodes 2 }
+
+pfSrcNodesInserts OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of entries inserted into the source tracking table."
+ ::= { pfSrcNodes 3 }
+
+pfSrcNodesRemovals OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of entries removed from the source tracking table."
+ ::= { pfSrcNodes 4 }
+
+-- --------------------------------------------------------------------------
+
+--
+-- limits
+--
+
+pfLimitsStates OBJECT-TYPE
+ SYNTAX Unsigned32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Maximum number of 'keep state' rules in the ruleset."
+ ::= { pfLimits 1 }
+
+pfLimitsSrcNodes OBJECT-TYPE
+ SYNTAX Unsigned32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Maximum number of 'sticky-address' or 'source-track' rules
+ in the ruleset."
+ ::= { pfLimits 2 }
+
+pfLimitsFrags OBJECT-TYPE
+ SYNTAX Unsigned32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Maximum number of 'scrub' rules in the ruleset."
+ ::= { pfLimits 3 }
+
+-- --------------------------------------------------------------------------
+
+--
+-- timeouts
+--
+
+pfTimeoutsTcpFirst OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "State after the first packet in a connection."
+ ::= { pfTimeouts 1 }
+
+pfTimeoutsTcpOpening OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "State before the destination host ever sends a packet."
+ ::= { pfTimeouts 2 }
+
+pfTimeoutsTcpEstablished OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The fully established state."
+ ::= { pfTimeouts 3 }
+
+pfTimeoutsTcpClosing OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "State after the first FIN has been sent."
+ ::= { pfTimeouts 4 }
+
+pfTimeoutsTcpFinWait OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "State after both FINs have been exchanged and the
+ connection is closed."
+ ::= { pfTimeouts 5 }
+
+pfTimeoutsTcpClosed OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "State after one endpoint sends an RST."
+ ::= { pfTimeouts 6 }
+
+pfTimeoutsUdpFirst OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "State after the first packet."
+ ::= { pfTimeouts 7 }
+
+pfTimeoutsUdpSingle OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "State if the source host sends more than one packet but
+ the destination host has never sent one back."
+ ::= { pfTimeouts 8 }
+
+pfTimeoutsUdpMultiple OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "State if both hosts have sent packets."
+ ::= { pfTimeouts 9 }
+
+pfTimeoutsIcmpFirst OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "State after the first packet."
+ ::= { pfTimeouts 10 }
+
+pfTimeoutsIcmpError OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "State after an ICMP error came back in response to an
+ ICMP packet."
+ ::= { pfTimeouts 11 }
+
+pfTimeoutsOtherFirst OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "State after the first packet."
+ ::= { pfTimeouts 12 }
+
+pfTimeoutsOtherSingle OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "State if the source host sends more than one packet but
+ the destination host has never sent one back."
+ ::= { pfTimeouts 13 }
+
+pfTimeoutsOtherMultiple OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "State if both hosts have sent packets."
+ ::= { pfTimeouts 14 }
+
+pfTimeoutsFragment OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Seconds before an unassembled fragment is expired."
+ ::= { pfTimeouts 15 }
+
+pfTimeoutsInterval OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Interval between purging expired states and fragments."
+ ::= { pfTimeouts 16 }
+
+pfTimeoutsAdaptiveStart OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "When the number of state entries exceeds this value,
+ adaptive scaling begins."
+ ::= { pfTimeouts 17 }
+
+pfTimeoutsAdaptiveEnd OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "When reaching this number of state entries, all timeout
+ values become zero, effectively purging all state entries
+ immediately."
+ ::= { pfTimeouts 18 }
+
+pfTimeoutsSrcNode OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Length of time to retain a source tracking entry after
+ the last state expires."
+ ::= { pfTimeouts 19 }
+
+-- --------------------------------------------------------------------------
+
+--
+-- log interface
+--
+
+pfLogInterfaceName OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The name of the interface configured with 'set loginterface'.
+ If no interface has been configured, the object will be empty."
+ ::= { pfLogInterface 1 }
+
+pfLogInterfaceIp4BytesIn OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of IPv4 bytes passed in on the loginterface."
+ ::= { pfLogInterface 2 }
+
+pfLogInterfaceIp4BytesOut OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of IPv4 bytes passed out on the loginterface."
+ ::= { pfLogInterface 3 }
+
+pfLogInterfaceIp4PktsInPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of IPv4 packets passed in on the loginterface."
+ ::= { pfLogInterface 4 }
+
+pfLogInterfaceIp4PktsInDrop OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of IPv4 packets dropped coming in on the loginterface."
+ ::= { pfLogInterface 5 }
+
+pfLogInterfaceIp4PktsOutPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of IPv4 packets passed out on the loginterface."
+ ::= { pfLogInterface 6 }
+
+pfLogInterfaceIp4PktsOutDrop OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of IPv4 packets dropped going out on the loginterface."
+ ::= { pfLogInterface 7 }
+
+pfLogInterfaceIp6BytesIn OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of IPv6 bytes passed in on the loginterface."
+ ::= { pfLogInterface 8 }
+
+pfLogInterfaceIp6BytesOut OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of IPv6 bytes passed out on the loginterface."
+ ::= { pfLogInterface 9 }
+
+pfLogInterfaceIp6PktsInPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of IPv6 packets passed in on the loginterface."
+ ::= { pfLogInterface 10 }
+
+pfLogInterfaceIp6PktsInDrop OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of IPv6 packets dropped coming in on the loginterface."
+ ::= { pfLogInterface 11 }
+
+pfLogInterfaceIp6PktsOutPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of IPv6 packets passed out on the loginterface."
+ ::= { pfLogInterface 12 }
+
+pfLogInterfaceIp6PktsOutDrop OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of IPv6 packets dropped going out on the loginterface."
+ ::= { pfLogInterface 13 }
+
+-- --------------------------------------------------------------------------
+
+--
+-- interfaces
+--
+
+pfInterfacesIfNumber OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of network interfaces on this system."
+ ::= { pfInterfaces 1 }
+
+pfInterfacesIfTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF PfInterfacesIfEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Table of network interfaces, indexed on pfInterfacesIfNumber."
+ ::= { pfInterfaces 2 }
+
+pfInterfacesIfEntry OBJECT-TYPE
+ SYNTAX PfInterfacesIfEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "An entry in the pfInterfacesIfTable containing information
+ about a particular network interface in the machine."
+ INDEX { pfInterfacesIfIndex }
+ ::= { pfInterfacesIfTable 1 }
+
+PfInterfacesIfEntry ::= SEQUENCE {
+ pfInterfacesIfIndex Integer32,
+ pfInterfacesIfDescr OCTET STRING,
+ pfInterfacesIfType INTEGER,
+ pfInterfacesIfTZero TimeTicks,
+ pfInterfacesIfRefsState Null,
+ pfInterfacesIfRefsRule Unsigned32,
+ pfInterfacesIf4BytesInPass Counter64,
+ pfInterfacesIf4BytesInBlock Counter64,
+ pfInterfacesIf4BytesOutPass Counter64,
+ pfInterfacesIf4BytesOutBlock Counter64,
+ pfInterfacesIf4PktsInPass Counter64,
+ pfInterfacesIf4PktsInBlock Counter64,
+ pfInterfacesIf4PktsOutPass Counter64,
+ pfInterfacesIf4PktsOutBlock Counter64,
+ pfInterfacesIf6BytesInPass Counter64,
+ pfInterfacesIf6BytesInBlock Counter64,
+ pfInterfacesIf6BytesOutPass Counter64,
+ pfInterfacesIf6BytesOutBlock Counter64,
+ pfInterfacesIf6PktsInPass Counter64,
+ pfInterfacesIf6PktsInBlock Counter64,
+ pfInterfacesIf6PktsOutPass Counter64,
+ pfInterfacesIf6PktsOutBlock Counter64
+}
+
+pfInterfacesIfIndex OBJECT-TYPE
+ SYNTAX Integer32 (1..2147483647)
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A unique value, greater than zero, for each interface."
+ ::= { pfInterfacesIfEntry 1 }
+
+pfInterfacesIfDescr OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The name of the interface."
+ ::= { pfInterfacesIfEntry 2 }
+
+pfInterfacesIfType OBJECT-TYPE
+ SYNTAX INTEGER { group(0), instance(1), detached(2) }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Indicates whether the interface is a group inteface, an
+ interface instance, or whether it has been removed or
+ destroyed."
+ ::= { pfInterfacesIfEntry 3 }
+
+pfInterfacesIfTZero OBJECT-TYPE
+ SYNTAX TimeTicks
+ UNITS "1/100th of a Second"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Time since statistics were last reset or since the
+ interface was loaded."
+ ::= { pfInterfacesIfEntry 4 }
+
+pfInterfacesIfRefsState OBJECT-TYPE
+ SYNTAX Unsigned32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of state and/or source track entries referencing
+ this interface."
+ ::= { pfInterfacesIfEntry 5 }
+
+pfInterfacesIfRefsRule OBJECT-TYPE
+ SYNTAX Unsigned32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of rules referencing this interface."
+ ::= { pfInterfacesIfEntry 6 }
+
+pfInterfacesIf4BytesInPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of IPv4 bytes passed coming in on this interface."
+ ::= { pfInterfacesIfEntry 7 }
+
+pfInterfacesIf4BytesInBlock OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of IPv4 bytes blocked coming in on this interface."
+ ::= { pfInterfacesIfEntry 8 }
+
+pfInterfacesIf4BytesOutPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of IPv4 bytes passed going out on this interface."
+ ::= { pfInterfacesIfEntry 9 }
+
+pfInterfacesIf4BytesOutBlock OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of IPv4 bytes blocked going out on this interface."
+ ::= { pfInterfacesIfEntry 10 }
+
+pfInterfacesIf4PktsInPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of IPv4 packets passed coming in on this interface."
+ ::= { pfInterfacesIfEntry 11 }
+
+pfInterfacesIf4PktsInBlock OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of IPv4 packets blocked coming in on this interface."
+ ::= { pfInterfacesIfEntry 12 }
+
+pfInterfacesIf4PktsOutPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of IPv4 packets passed going out on this interface."
+ ::= { pfInterfacesIfEntry 13 }
+
+pfInterfacesIf4PktsOutBlock OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of IPv4 packets blocked going out on this interface."
+ ::= { pfInterfacesIfEntry 14 }
+
+pfInterfacesIf6BytesInPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of IPv6 bytes passed coming in on this interface."
+ ::= { pfInterfacesIfEntry 15 }
+
+pfInterfacesIf6BytesInBlock OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of IPv6 bytes blocked coming in on this interface."
+ ::= { pfInterfacesIfEntry 16 }
+
+pfInterfacesIf6BytesOutPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of IPv6 bytes passed going out on this interface."
+ ::= { pfInterfacesIfEntry 17 }
+
+pfInterfacesIf6BytesOutBlock OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of IPv6 bytes blocked going out on this interface."
+ ::= { pfInterfacesIfEntry 18 }
+
+
+pfInterfacesIf6PktsInPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of IPv6 packets passed coming in on this interface."
+ ::= { pfInterfacesIfEntry 19 }
+
+pfInterfacesIf6PktsInBlock OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of IPv6 packets blocked coming in on this interface."
+ ::= { pfInterfacesIfEntry 20 }
+
+pfInterfacesIf6PktsOutPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of IPv6 packets passed going out on this interface."
+ ::= { pfInterfacesIfEntry 21 }
+
+pfInterfacesIf6PktsOutBlock OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of IPv6 packets blocked going out on this interface."
+ ::= { pfInterfacesIfEntry 22 }
+
+-- --------------------------------------------------------------------------
+
+--
+-- tables
+--
+
+pfTablesTblNumber OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of tables on this system."
+ ::= { pfTables 1 }
+
+pfTablesTblTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF PfTablesTblEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Table of tables, index on pfTablesTblIndex."
+ ::= { pfTables 2 }
+
+pfTablesTblEntry OBJECT-TYPE
+ SYNTAX PfTablesTblEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Any entry in the pfTablesTblTable containing information
+ about a particular table on the system."
+ INDEX { pfTablesTblIndex }
+ ::= { pfTablesTblTable 1 }
+
+PfTablesTblEntry ::= SEQUENCE {
+ pfTablesTblIndex Integer32,
+ pfTablesTblDescr OCTET STRING,
+ pfTablesTblCount Integer32,
+ pfTablesTblTZero TimeTicks,
+ pfTablesTblRefsAnchor Integer32,
+ pfTablesTblRefsRule Integer32,
+ pfTablesTblEvalMatch Counter64,
+ pfTablesTblEvalNoMatch Counter64,
+ pfTablesTblBytesInPass Counter64,
+ pfTablesTblBytesInBlock Counter64,
+ pfTablesTblBytesInXPass Counter64,
+ pfTablesTblBytesOutPass Counter64,
+ pfTablesTblBytesOutBlock Counter64,
+ pfTablesTblBytesOutXPass Counter64,
+ pfTablesTblPktsInPass Counter64,
+ pfTablesTblPktsInBlock Counter64,
+ pfTablesTblPktsInXPass Counter64,
+ pfTablesTblPktsOutPass Counter64,
+ pfTablesTblPktsOutBlock Counter64,
+ pfTablesTblPktsOutXPass Counter64
+}
+
+pfTablesTblIndex OBJECT-TYPE
+ SYNTAX Integer32 (1..2147483647)
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A unique value, greater than zero, for each table."
+ ::= { pfTablesTblEntry 1 }
+
+pfTablesTblDescr OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The name of the table."
+ ::= { pfTablesTblEntry 2 }
+
+pfTablesTblCount OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of addresses in the table."
+ ::= { pfTablesTblEntry 3 }
+
+pfTablesTblTZero OBJECT-TYPE
+ SYNTAX TimeTicks
+ UNITS "1/100th of a Second"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The time passed since the statistics of this table were last
+ cleared or the time since this table was loaded, whichever is
+ sooner."
+ ::= { pfTablesTblEntry 4 }
+
+pfTablesTblRefsAnchor OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of anchors referencing this table."
+ ::= { pfTablesTblEntry 5 }
+
+pfTablesTblRefsRule OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of rules referencing this table."
+ ::= { pfTablesTblEntry 6 }
+
+pfTablesTblEvalMatch OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of evaluations returning a match."
+ ::= { pfTablesTblEntry 7 }
+
+pfTablesTblEvalNoMatch OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of evaluations not returning a match."
+ ::= { pfTablesTblEntry 8 }
+
+pfTablesTblBytesInPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of bytes passed in matching the table."
+ ::= { pfTablesTblEntry 9 }
+
+pfTablesTblBytesInBlock OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of bytes blocked coming in matching the table."
+ ::= { pfTablesTblEntry 10 }
+
+pfTablesTblBytesInXPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of bytes statefully passed in where the state
+ entry refers to the table, but the table no longer contains
+ the address in question."
+ ::= { pfTablesTblEntry 11 }
+
+pfTablesTblBytesOutPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of bytes passed out matching the table."
+ ::= { pfTablesTblEntry 12 }
+
+pfTablesTblBytesOutBlock OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of bytes blocked going out matching the table."
+ ::= { pfTablesTblEntry 13 }
+
+pfTablesTblBytesOutXPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of bytes statefully passed out where the state
+ entry refers to the table, but the table no longer contains
+ the address in question."
+ ::= { pfTablesTblEntry 14 }
+
+pfTablesTblPktsInPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of packets passed in matching the table."
+ ::= { pfTablesTblEntry 15 }
+
+pfTablesTblPktsInBlock OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of packets blocked coming in matching the table."
+ ::= { pfTablesTblEntry 16 }
+
+pfTablesTblPktsInXPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of packets statefully passed in where the state
+ entry refers to the table, but the table no longer contains
+ the address in question."
+ ::= { pfTablesTblEntry 17 }
+
+pfTablesTblPktsOutPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of packets passed out matching the table."
+ ::= { pfTablesTblEntry 18 }
+
+pfTablesTblPktsOutBlock OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of packets blocked going out matching the table."
+ ::= { pfTablesTblEntry 19 }
+
+pfTablesTblPktsOutXPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of packets statefully passed out where the state
+ entry refers to the table, but the table no longer contains
+ the address in question."
+ ::= { pfTablesTblEntry 20 }
+
+pfTablesAddrTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF PfTablesAddrEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Table of addresses from every table on the system."
+ ::= { pfTables 3 }
+
+pfTablesAddrEntry OBJECT-TYPE
+ SYNTAX PfTablesAddrEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "An entry in the pfTablesAddrTable containing information
+ about a particular entry in a table."
+ INDEX { pfTablesAddrIndex }
+ ::= { pfTablesAddrTable 1 }
+
+PfTablesAddrEntry ::= SEQUENCE {
+ pfTablesAddrIndex Integer32,
+ pfTablesAddrNetType InetAddressType,
+ pfTablesAddrNet InetAddress,
+ pfTablesAddrPrefix InetAddressPrefixLength,
+ pfTablesAddrTZero TimeTicks,
+ pfTablesAddrBytesInPass Counter64,
+ pfTablesAddrBytesInBlock Counter64,
+ pfTablesAddrBytesOutPass Counter64,
+ pfTablesAddrBytesOutBlock Counter64,
+ pfTablesAddrPktsInPass Counter64,
+ pfTablesAddrPktsInBlock Counter64,
+ pfTablesAddrPktsOutPass Counter64,
+ pfTablesAddrPktsOutBlock Counter64
+}
+
+pfTablesAddrIndex OBJECT-TYPE
+ SYNTAX Integer32 (1..2147483647)
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A unique value, greater than zero, for each address."
+ ::= { pfTablesAddrEntry 1 }
+
+pfTablesAddrNetType OBJECT-TYPE
+ SYNTAX InetAddressType
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The type of address in the corresponding pfTablesAddrNet object."
+ ::= { pfTablesAddrEntry 2 }
+
+pfTablesAddrNet OBJECT-TYPE
+ SYNTAX InetAddress
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The IP address of this particular table entry."
+ ::= { pfTablesAddrEntry 3 }
+
+pfTablesAddrPrefix OBJECT-TYPE
+ SYNTAX InetAddressPrefixLength
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The CIDR netmask of this particular table entry."
+ ::= { pfTablesAddrEntry 4 }
+
+pfTablesAddrTZero OBJECT-TYPE
+ SYNTAX TimeTicks
+ UNITS "1/100th of a Second"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The time passed since this entry's statistics were last
+ cleared, or the time passed since this entry was loaded
+ into the table, whichever is sooner."
+ ::= { pfTablesAddrEntry 5 }
+
+pfTablesAddrBytesInPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of inbound bytes passed as a result of this entry."
+ ::= { pfTablesAddrEntry 6 }
+
+pfTablesAddrBytesInBlock OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of inbound bytes blocked as a result of this entry."
+ ::= { pfTablesAddrEntry 7 }
+
+pfTablesAddrBytesOutPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of outbound bytes passed as a result of this entry."
+ ::= { pfTablesAddrEntry 8 }
+
+pfTablesAddrBytesOutBlock OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of outbound bytes blocked as a result of this entry."
+ ::= { pfTablesAddrEntry 9 }
+
+pfTablesAddrPktsInPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of inbound packets passed as a result of this entry."
+ ::= { pfTablesAddrEntry 10 }
+
+pfTablesAddrPktsInBlock OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of inbound packets blocked as a result of this entry."
+ ::= { pfTablesAddrEntry 11 }
+
+pfTablesAddrPktsOutPass OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of outbound packets passed as a result of this entry."
+ ::= { pfTablesAddrEntry 12 }
+
+pfTablesAddrPktsOutBlock OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of outbound packets blocked as a result of this
+ entry."
+ ::= { pfTablesAddrEntry 13 }
+
+-- --------------------------------------------------------------------------
+
+--
+-- Altq information
+--
+
+pfAltqQueueNumber OBJECT-TYPE
+ SYNTAX Unsigned32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of queues in the active set."
+ ::= { pfAltq 1 }
+
+pfAltqQueueTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF PfAltqQueueEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Table containing the rules that are active on this system."
+ ::= { pfAltq 2 }
+
+pfAltqQueueEntry OBJECT-TYPE
+ SYNTAX PfAltqQueueEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "An entry in the pfAltqQueueTable table."
+ INDEX { pfAltqQueueIndex }
+ ::= { pfAltqQueueTable 1 }
+
+PfAltqQueueEntry ::= SEQUENCE {
+ pfAltqQueueIndex Integer32,
+ pfAltqQueueDescr OCTET STRING,
+ pfAltqQueueParent OCTET STRING,
+ pfAltqQueueScheduler INTEGER,
+ pfAltqQueueBandwidth Unsigned32,
+ pfAltqQueuePriority Integer32,
+ pfAltqQueueLimit Integer32
+}
+
+pfAltqQueueIndex OBJECT-TYPE
+ SYNTAX Integer32 (1..2147483647)
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A unique value, greater than zero, for each queue."
+ ::= { pfAltqQueueEntry 1 }
+
+pfAltqQueueDescr OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The name of the queue."
+ ::= { pfAltqQueueEntry 2 }
+
+pfAltqQueueParent OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Name of the queue's parent if it has one."
+ ::= { pfAltqQueueEntry 3 }
+
+pfAltqQueueScheduler OBJECT-TYPE
+ SYNTAX INTEGER { cbq(1), hfsc(8), priq(11) }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Scheduler algorithm implemented by this queue."
+ ::= { pfAltqQueueEntry 4 }
+
+pfAltqQueueBandwidth OBJECT-TYPE
+ SYNTAX Unsigned32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Bandwitch assigned to this queue."
+ ::= { pfAltqQueueEntry 5 }
+
+pfAltqQueuePriority OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Priority level of the queue."
+ ::= { pfAltqQueueEntry 6 }
+
+pfAltqQueueLimit OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Maximum number of packets in the queue."
+ ::= { pfAltqQueueEntry 7 }
+
+pfLabelsLblNumber OBJECT-TYPE
+ SYNTAX Integer32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of labeled filter rules on this system."
+ ::= { pfLabels 1 }
+
+pfLabelsLblTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF PfLabelsLblEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Table of filter rules, index on pfLabelsLblIndex."
+ ::= { pfLabels 2 }
+
+pfLabelsLblEntry OBJECT-TYPE
+ SYNTAX PfLabelsLblEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Any entry in the pfLabelsLblTable containing information
+ about a particular filter rule on the system."
+ INDEX { pfLabelsLblIndex }
+ ::= { pfLabelsLblTable 1 }
+
+PfLabelsLblEntry ::= SEQUENCE {
+ pfLabelsLblIndex Integer32,
+ pfLabelsLblName OCTET STRING,
+ pfLabelsLblEvals Counter64,
+ pfLabelsLblBytesIn Counter64,
+ pfLabelsLblBytesOut Counter64,
+ pfLabelsLblPktsIn Counter64,
+ pfLabelsLblPktsOut Counter64
+}
+
+pfLabelsLblIndex OBJECT-TYPE
+ SYNTAX Integer32 (1..2147483647)
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A unique value, greater than zero, for each label."
+ ::= { pfLabelsLblEntry 1 }
+
+pfLabelsLblName OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The name of the rule label."
+ ::= { pfLabelsLblEntry 2 }
+
+pfLabelsLblEvals OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of rule evaluations."
+ ::= { pfLabelsLblEntry 3 }
+
+pfLabelsLblBytesIn OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of incoming bytes matched by the rule."
+ ::= { pfLabelsLblEntry 4 }
+
+pfLabelsLblBytesOut OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of outgoing bytes matched by the rule."
+ ::= { pfLabelsLblEntry 5 }
+
+pfLabelsLblPktsIn OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of incoming packets matched by the rule."
+ ::= { pfLabelsLblEntry 6 }
+
+pfLabelsLblPktsOut OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of outgoing packets matched by the rule."
+ ::= { pfLabelsLblEntry 7 }
+
+END
diff --git a/usr.sbin/bsnmpd/modules/snmp_pf/Makefile b/usr.sbin/bsnmpd/modules/snmp_pf/Makefile
new file mode 100644
index 0000000..6218932
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_pf/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+#
+# Author: Philip Paeps <philip@freebsd.org>
+
+MOD= pf
+SRCS= pf_snmp.c
+CFLAGS+= -DSNMPTREE_TYPES
+
+XSYM= begemotPf
+DEFS= ${MOD}_tree.def
+BMIBS= BEGEMOT-PF-MIB.txt
+
+.include <bsd.snmpmod.mk>
diff --git a/usr.sbin/bsnmpd/modules/snmp_pf/Makefile.depend b/usr.sbin/bsnmpd/modules/snmp_pf/Makefile.depend
new file mode 100644
index 0000000..855779d
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_pf/Makefile.depend
@@ -0,0 +1,28 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbsnmp/libbsnmp \
+ lib/libc \
+ lib/libcompiler_rt \
+ usr.sbin/bsnmpd/modules \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+pf_snmp.So: pf_oid.h
+pf_snmp.So: pf_tree.h
+pf_snmp.po: pf_oid.h
+pf_snmp.po: pf_tree.h
+pf_tree.So: pf_tree.c
+pf_tree.So: pf_tree.h
+pf_tree.po: pf_tree.c
+pf_tree.po: pf_tree.h
+.endif
diff --git a/usr.sbin/bsnmpd/modules/snmp_pf/pf_snmp.c b/usr.sbin/bsnmpd/modules/snmp_pf/pf_snmp.c
new file mode 100644
index 0000000..1048ffe
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_pf/pf_snmp.c
@@ -0,0 +1,1797 @@
+/*-
+ * Copyright (c) 2005 Philip Paeps <philip@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/queue.h>
+#include <bsnmp/snmpmod.h>
+
+#include <net/pfvar.h>
+#include <sys/ioctl.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "pf_oid.h"
+#include "pf_tree.h"
+
+struct lmodule *module;
+
+static int dev = -1;
+static int started;
+static uint64_t pf_tick;
+
+static struct pf_status pfs;
+
+enum { IN, OUT };
+enum { IPV4, IPV6 };
+enum { PASS, BLOCK };
+
+#define PFI_IFTYPE_GROUP 0
+#define PFI_IFTYPE_INSTANCE 1
+#define PFI_IFTYPE_DETACHED 2
+
+struct pfi_entry {
+ struct pfi_kif pfi;
+ u_int index;
+ TAILQ_ENTRY(pfi_entry) link;
+};
+TAILQ_HEAD(pfi_table, pfi_entry);
+
+static struct pfi_table pfi_table;
+static time_t pfi_table_age;
+static int pfi_table_count;
+
+#define PFI_TABLE_MAXAGE 5
+
+struct pft_entry {
+ struct pfr_tstats pft;
+ u_int index;
+ TAILQ_ENTRY(pft_entry) link;
+};
+TAILQ_HEAD(pft_table, pft_entry);
+
+static struct pft_table pft_table;
+static time_t pft_table_age;
+static int pft_table_count;
+
+#define PFT_TABLE_MAXAGE 5
+
+struct pfa_entry {
+ struct pfr_astats pfas;
+ u_int index;
+ TAILQ_ENTRY(pfa_entry) link;
+};
+TAILQ_HEAD(pfa_table, pfa_entry);
+
+static struct pfa_table pfa_table;
+static time_t pfa_table_age;
+static int pfa_table_count;
+
+#define PFA_TABLE_MAXAGE 5
+
+struct pfq_entry {
+ struct pf_altq altq;
+ u_int index;
+ TAILQ_ENTRY(pfq_entry) link;
+};
+TAILQ_HEAD(pfq_table, pfq_entry);
+
+static struct pfq_table pfq_table;
+static time_t pfq_table_age;
+static int pfq_table_count;
+
+static int altq_enabled = 0;
+
+#define PFQ_TABLE_MAXAGE 5
+
+struct pfl_entry {
+ char name[MAXPATHLEN + PF_RULE_LABEL_SIZE];
+ u_int64_t evals;
+ u_int64_t bytes[2];
+ u_int64_t pkts[2];
+ u_int index;
+ TAILQ_ENTRY(pfl_entry) link;
+};
+TAILQ_HEAD(pfl_table, pfl_entry);
+
+static struct pfl_table pfl_table;
+static time_t pfl_table_age;
+static int pfl_table_count;
+
+#define PFL_TABLE_MAXAGE 5
+
+/* Forward declarations */
+static int pfi_refresh(void);
+static int pfq_refresh(void);
+static int pfs_refresh(void);
+static int pft_refresh(void);
+static int pfa_refresh(void);
+static int pfl_refresh(void);
+static struct pfi_entry * pfi_table_find(u_int idx);
+static struct pfq_entry * pfq_table_find(u_int idx);
+static struct pft_entry * pft_table_find(u_int idx);
+static struct pfa_entry * pfa_table_find(u_int idx);
+static struct pfl_entry * pfl_table_find(u_int idx);
+
+static int altq_is_enabled(int pfdevice);
+
+int
+pf_status(struct snmp_context __unused *ctx, struct snmp_value *val,
+ u_int sub, u_int __unused vindex, enum snmp_op op)
+{
+ asn_subid_t which = val->var.subs[sub - 1];
+ time_t runtime;
+ unsigned char str[128];
+
+ if (op == SNMP_OP_SET)
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ if (op == SNMP_OP_GET) {
+ if (pfs_refresh() == -1)
+ return (SNMP_ERR_GENERR);
+
+ switch (which) {
+ case LEAF_pfStatusRunning:
+ val->v.uint32 = pfs.running;
+ break;
+ case LEAF_pfStatusRuntime:
+ runtime = (pfs.since > 0) ?
+ time(NULL) - pfs.since : 0;
+ val->v.uint32 = runtime * 100;
+ break;
+ case LEAF_pfStatusDebug:
+ val->v.uint32 = pfs.debug;
+ break;
+ case LEAF_pfStatusHostId:
+ sprintf(str, "0x%08x", ntohl(pfs.hostid));
+ return (string_get(val, str, strlen(str)));
+
+ default:
+ return (SNMP_ERR_NOSUCHNAME);
+ }
+
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+int
+pf_counter(struct snmp_context __unused *ctx, struct snmp_value *val,
+ u_int sub, u_int __unused vindex, enum snmp_op op)
+{
+ asn_subid_t which = val->var.subs[sub - 1];
+
+ if (op == SNMP_OP_SET)
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ if (op == SNMP_OP_GET) {
+ if (pfs_refresh() == -1)
+ return (SNMP_ERR_GENERR);
+
+ switch (which) {
+ case LEAF_pfCounterMatch:
+ val->v.counter64 = pfs.counters[PFRES_MATCH];
+ break;
+ case LEAF_pfCounterBadOffset:
+ val->v.counter64 = pfs.counters[PFRES_BADOFF];
+ break;
+ case LEAF_pfCounterFragment:
+ val->v.counter64 = pfs.counters[PFRES_FRAG];
+ break;
+ case LEAF_pfCounterShort:
+ val->v.counter64 = pfs.counters[PFRES_SHORT];
+ break;
+ case LEAF_pfCounterNormalize:
+ val->v.counter64 = pfs.counters[PFRES_NORM];
+ break;
+ case LEAF_pfCounterMemDrop:
+ val->v.counter64 = pfs.counters[PFRES_MEMORY];
+ break;
+
+ default:
+ return (SNMP_ERR_NOSUCHNAME);
+ }
+
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+int
+pf_statetable(struct snmp_context __unused *ctx, struct snmp_value *val,
+ u_int sub, u_int __unused vindex, enum snmp_op op)
+{
+ asn_subid_t which = val->var.subs[sub - 1];
+
+ if (op == SNMP_OP_SET)
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ if (op == SNMP_OP_GET) {
+ if (pfs_refresh() == -1)
+ return (SNMP_ERR_GENERR);
+
+ switch (which) {
+ case LEAF_pfStateTableCount:
+ val->v.uint32 = pfs.states;
+ break;
+ case LEAF_pfStateTableSearches:
+ val->v.counter64 =
+ pfs.fcounters[FCNT_STATE_SEARCH];
+ break;
+ case LEAF_pfStateTableInserts:
+ val->v.counter64 =
+ pfs.fcounters[FCNT_STATE_INSERT];
+ break;
+ case LEAF_pfStateTableRemovals:
+ val->v.counter64 =
+ pfs.fcounters[FCNT_STATE_REMOVALS];
+ break;
+
+ default:
+ return (SNMP_ERR_NOSUCHNAME);
+ }
+
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+int
+pf_srcnodes(struct snmp_context __unused *ctx, struct snmp_value *val,
+ u_int sub, u_int __unused vindex, enum snmp_op op)
+{
+ asn_subid_t which = val->var.subs[sub - 1];
+
+ if (op == SNMP_OP_SET)
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ if (op == SNMP_OP_GET) {
+ if (pfs_refresh() == -1)
+ return (SNMP_ERR_GENERR);
+
+ switch (which) {
+ case LEAF_pfSrcNodesCount:
+ val->v.uint32 = pfs.src_nodes;
+ break;
+ case LEAF_pfSrcNodesSearches:
+ val->v.counter64 =
+ pfs.scounters[SCNT_SRC_NODE_SEARCH];
+ break;
+ case LEAF_pfSrcNodesInserts:
+ val->v.counter64 =
+ pfs.scounters[SCNT_SRC_NODE_INSERT];
+ break;
+ case LEAF_pfSrcNodesRemovals:
+ val->v.counter64 =
+ pfs.scounters[SCNT_SRC_NODE_REMOVALS];
+ break;
+
+ default:
+ return (SNMP_ERR_NOSUCHNAME);
+ }
+
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+int
+pf_limits(struct snmp_context __unused *ctx, struct snmp_value *val,
+ u_int sub, u_int __unused vindex, enum snmp_op op)
+{
+ asn_subid_t which = val->var.subs[sub - 1];
+ struct pfioc_limit pl;
+
+ if (op == SNMP_OP_SET)
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ if (op == SNMP_OP_GET) {
+ bzero(&pl, sizeof(struct pfioc_limit));
+
+ switch (which) {
+ case LEAF_pfLimitsStates:
+ pl.index = PF_LIMIT_STATES;
+ break;
+ case LEAF_pfLimitsSrcNodes:
+ pl.index = PF_LIMIT_SRC_NODES;
+ break;
+ case LEAF_pfLimitsFrags:
+ pl.index = PF_LIMIT_FRAGS;
+ break;
+
+ default:
+ return (SNMP_ERR_NOSUCHNAME);
+ }
+
+ if (ioctl(dev, DIOCGETLIMIT, &pl)) {
+ syslog(LOG_ERR, "pf_limits(): ioctl(): %s",
+ strerror(errno));
+ return (SNMP_ERR_GENERR);
+ }
+
+ val->v.uint32 = pl.limit;
+
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+int
+pf_timeouts(struct snmp_context __unused *ctx, struct snmp_value *val,
+ u_int sub, u_int __unused vindex, enum snmp_op op)
+{
+ asn_subid_t which = val->var.subs[sub - 1];
+ struct pfioc_tm pt;
+
+ if (op == SNMP_OP_SET)
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ if (op == SNMP_OP_GET) {
+ bzero(&pt, sizeof(struct pfioc_tm));
+
+ switch (which) {
+ case LEAF_pfTimeoutsTcpFirst:
+ pt.timeout = PFTM_TCP_FIRST_PACKET;
+ break;
+ case LEAF_pfTimeoutsTcpOpening:
+ pt.timeout = PFTM_TCP_OPENING;
+ break;
+ case LEAF_pfTimeoutsTcpEstablished:
+ pt.timeout = PFTM_TCP_ESTABLISHED;
+ break;
+ case LEAF_pfTimeoutsTcpClosing:
+ pt.timeout = PFTM_TCP_CLOSING;
+ break;
+ case LEAF_pfTimeoutsTcpFinWait:
+ pt.timeout = PFTM_TCP_FIN_WAIT;
+ break;
+ case LEAF_pfTimeoutsTcpClosed:
+ pt.timeout = PFTM_TCP_CLOSED;
+ break;
+ case LEAF_pfTimeoutsUdpFirst:
+ pt.timeout = PFTM_UDP_FIRST_PACKET;
+ break;
+ case LEAF_pfTimeoutsUdpSingle:
+ pt.timeout = PFTM_UDP_SINGLE;
+ break;
+ case LEAF_pfTimeoutsUdpMultiple:
+ pt.timeout = PFTM_UDP_MULTIPLE;
+ break;
+ case LEAF_pfTimeoutsIcmpFirst:
+ pt.timeout = PFTM_ICMP_FIRST_PACKET;
+ break;
+ case LEAF_pfTimeoutsIcmpError:
+ pt.timeout = PFTM_ICMP_ERROR_REPLY;
+ break;
+ case LEAF_pfTimeoutsOtherFirst:
+ pt.timeout = PFTM_OTHER_FIRST_PACKET;
+ break;
+ case LEAF_pfTimeoutsOtherSingle:
+ pt.timeout = PFTM_OTHER_SINGLE;
+ break;
+ case LEAF_pfTimeoutsOtherMultiple:
+ pt.timeout = PFTM_OTHER_MULTIPLE;
+ break;
+ case LEAF_pfTimeoutsFragment:
+ pt.timeout = PFTM_FRAG;
+ break;
+ case LEAF_pfTimeoutsInterval:
+ pt.timeout = PFTM_INTERVAL;
+ break;
+ case LEAF_pfTimeoutsAdaptiveStart:
+ pt.timeout = PFTM_ADAPTIVE_START;
+ break;
+ case LEAF_pfTimeoutsAdaptiveEnd:
+ pt.timeout = PFTM_ADAPTIVE_END;
+ break;
+ case LEAF_pfTimeoutsSrcNode:
+ pt.timeout = PFTM_SRC_NODE;
+ break;
+
+ default:
+ return (SNMP_ERR_NOSUCHNAME);
+ }
+
+ if (ioctl(dev, DIOCGETTIMEOUT, &pt)) {
+ syslog(LOG_ERR, "pf_timeouts(): ioctl(): %s",
+ strerror(errno));
+ return (SNMP_ERR_GENERR);
+ }
+
+ val->v.integer = pt.seconds;
+
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+int
+pf_logif(struct snmp_context __unused *ctx, struct snmp_value *val,
+ u_int sub, u_int __unused vindex, enum snmp_op op)
+{
+ asn_subid_t which = val->var.subs[sub - 1];
+ unsigned char str[IFNAMSIZ];
+
+ if (op == SNMP_OP_SET)
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ if (op == SNMP_OP_GET) {
+ if (pfs_refresh() == -1)
+ return (SNMP_ERR_GENERR);
+
+ switch (which) {
+ case LEAF_pfLogInterfaceName:
+ strlcpy(str, pfs.ifname, sizeof str);
+ return (string_get(val, str, strlen(str)));
+ case LEAF_pfLogInterfaceIp4BytesIn:
+ val->v.counter64 = pfs.bcounters[IPV4][IN];
+ break;
+ case LEAF_pfLogInterfaceIp4BytesOut:
+ val->v.counter64 = pfs.bcounters[IPV4][OUT];
+ break;
+ case LEAF_pfLogInterfaceIp4PktsInPass:
+ val->v.counter64 =
+ pfs.pcounters[IPV4][IN][PF_PASS];
+ break;
+ case LEAF_pfLogInterfaceIp4PktsInDrop:
+ val->v.counter64 =
+ pfs.pcounters[IPV4][IN][PF_DROP];
+ break;
+ case LEAF_pfLogInterfaceIp4PktsOutPass:
+ val->v.counter64 =
+ pfs.pcounters[IPV4][OUT][PF_PASS];
+ break;
+ case LEAF_pfLogInterfaceIp4PktsOutDrop:
+ val->v.counter64 =
+ pfs.pcounters[IPV4][OUT][PF_DROP];
+ break;
+ case LEAF_pfLogInterfaceIp6BytesIn:
+ val->v.counter64 = pfs.bcounters[IPV6][IN];
+ break;
+ case LEAF_pfLogInterfaceIp6BytesOut:
+ val->v.counter64 = pfs.bcounters[IPV6][OUT];
+ break;
+ case LEAF_pfLogInterfaceIp6PktsInPass:
+ val->v.counter64 =
+ pfs.pcounters[IPV6][IN][PF_PASS];
+ break;
+ case LEAF_pfLogInterfaceIp6PktsInDrop:
+ val->v.counter64 =
+ pfs.pcounters[IPV6][IN][PF_DROP];
+ break;
+ case LEAF_pfLogInterfaceIp6PktsOutPass:
+ val->v.counter64 =
+ pfs.pcounters[IPV6][OUT][PF_PASS];
+ break;
+ case LEAF_pfLogInterfaceIp6PktsOutDrop:
+ val->v.counter64 =
+ pfs.pcounters[IPV6][OUT][PF_DROP];
+ break;
+
+ default:
+ return (SNMP_ERR_NOSUCHNAME);
+ }
+
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+int
+pf_interfaces(struct snmp_context __unused *ctx, struct snmp_value *val,
+ u_int sub, u_int __unused vindex, enum snmp_op op)
+{
+ asn_subid_t which = val->var.subs[sub - 1];
+
+ if (op == SNMP_OP_SET)
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ if (op == SNMP_OP_GET) {
+ if ((time(NULL) - pfi_table_age) > PFI_TABLE_MAXAGE)
+ if (pfi_refresh() == -1)
+ return (SNMP_ERR_GENERR);
+
+ switch (which) {
+ case LEAF_pfInterfacesIfNumber:
+ val->v.uint32 = pfi_table_count;
+ break;
+
+ default:
+ return (SNMP_ERR_NOSUCHNAME);
+ }
+
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+int
+pf_iftable(struct snmp_context __unused *ctx, struct snmp_value *val,
+ u_int sub, u_int __unused vindex, enum snmp_op op)
+{
+ asn_subid_t which = val->var.subs[sub - 1];
+ struct pfi_entry *e = NULL;
+
+ if ((time(NULL) - pfi_table_age) > PFI_TABLE_MAXAGE)
+ pfi_refresh();
+
+ switch (op) {
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ case SNMP_OP_GETNEXT:
+ if ((e = NEXT_OBJECT_INT(&pfi_table,
+ &val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ val->var.len = sub + 1;
+ val->var.subs[sub] = e->index;
+ break;
+ case SNMP_OP_GET:
+ if (val->var.len - sub != 1)
+ return (SNMP_ERR_NOSUCHNAME);
+ if ((e = pfi_table_find(val->var.subs[sub])) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+
+ case SNMP_OP_COMMIT:
+ case SNMP_OP_ROLLBACK:
+ default:
+ abort();
+ }
+
+ switch (which) {
+ case LEAF_pfInterfacesIfDescr:
+ return (string_get(val, e->pfi.pfik_name, -1));
+ case LEAF_pfInterfacesIfType:
+ val->v.integer = PFI_IFTYPE_INSTANCE;
+ break;
+ case LEAF_pfInterfacesIfTZero:
+ val->v.uint32 =
+ (time(NULL) - e->pfi.pfik_tzero) * 100;
+ break;
+ case LEAF_pfInterfacesIfRefsRule:
+ val->v.uint32 = e->pfi.pfik_rulerefs;
+ break;
+ case LEAF_pfInterfacesIf4BytesInPass:
+ val->v.counter64 =
+ e->pfi.pfik_bytes[IPV4][IN][PASS];
+ break;
+ case LEAF_pfInterfacesIf4BytesInBlock:
+ val->v.counter64 =
+ e->pfi.pfik_bytes[IPV4][IN][BLOCK];
+ break;
+ case LEAF_pfInterfacesIf4BytesOutPass:
+ val->v.counter64 =
+ e->pfi.pfik_bytes[IPV4][OUT][PASS];
+ break;
+ case LEAF_pfInterfacesIf4BytesOutBlock:
+ val->v.counter64 =
+ e->pfi.pfik_bytes[IPV4][OUT][BLOCK];
+ break;
+ case LEAF_pfInterfacesIf4PktsInPass:
+ val->v.counter64 =
+ e->pfi.pfik_packets[IPV4][IN][PASS];
+ break;
+ case LEAF_pfInterfacesIf4PktsInBlock:
+ val->v.counter64 =
+ e->pfi.pfik_packets[IPV4][IN][BLOCK];
+ break;
+ case LEAF_pfInterfacesIf4PktsOutPass:
+ val->v.counter64 =
+ e->pfi.pfik_packets[IPV4][OUT][PASS];
+ break;
+ case LEAF_pfInterfacesIf4PktsOutBlock:
+ val->v.counter64 =
+ e->pfi.pfik_packets[IPV4][OUT][BLOCK];
+ break;
+ case LEAF_pfInterfacesIf6BytesInPass:
+ val->v.counter64 =
+ e->pfi.pfik_bytes[IPV6][IN][PASS];
+ break;
+ case LEAF_pfInterfacesIf6BytesInBlock:
+ val->v.counter64 =
+ e->pfi.pfik_bytes[IPV6][IN][BLOCK];
+ break;
+ case LEAF_pfInterfacesIf6BytesOutPass:
+ val->v.counter64 =
+ e->pfi.pfik_bytes[IPV6][OUT][PASS];
+ break;
+ case LEAF_pfInterfacesIf6BytesOutBlock:
+ val->v.counter64 =
+ e->pfi.pfik_bytes[IPV6][OUT][BLOCK];
+ break;
+ case LEAF_pfInterfacesIf6PktsInPass:
+ val->v.counter64 =
+ e->pfi.pfik_packets[IPV6][IN][PASS];
+ break;
+ case LEAF_pfInterfacesIf6PktsInBlock:
+ val->v.counter64 =
+ e->pfi.pfik_packets[IPV6][IN][BLOCK];
+ break;
+ case LEAF_pfInterfacesIf6PktsOutPass:
+ val->v.counter64 =
+ e->pfi.pfik_packets[IPV6][OUT][PASS];
+ break;
+ case LEAF_pfInterfacesIf6PktsOutBlock:
+ val->v.counter64 =
+ e->pfi.pfik_packets[IPV6][OUT][BLOCK];
+ break;
+
+ default:
+ return (SNMP_ERR_NOSUCHNAME);
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+pf_tables(struct snmp_context __unused *ctx, struct snmp_value *val,
+ u_int sub, u_int __unused vindex, enum snmp_op op)
+{
+ asn_subid_t which = val->var.subs[sub - 1];
+
+ if (op == SNMP_OP_SET)
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ if (op == SNMP_OP_GET) {
+ if ((time(NULL) - pft_table_age) > PFT_TABLE_MAXAGE)
+ if (pft_refresh() == -1)
+ return (SNMP_ERR_GENERR);
+
+ switch (which) {
+ case LEAF_pfTablesTblNumber:
+ val->v.uint32 = pft_table_count;
+ break;
+
+ default:
+ return (SNMP_ERR_NOSUCHNAME);
+ }
+
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+int
+pf_tbltable(struct snmp_context __unused *ctx, struct snmp_value *val,
+ u_int sub, u_int __unused vindex, enum snmp_op op)
+{
+ asn_subid_t which = val->var.subs[sub - 1];
+ struct pft_entry *e = NULL;
+
+ if ((time(NULL) - pft_table_age) > PFT_TABLE_MAXAGE)
+ pft_refresh();
+
+ switch (op) {
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ case SNMP_OP_GETNEXT:
+ if ((e = NEXT_OBJECT_INT(&pft_table,
+ &val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ val->var.len = sub + 1;
+ val->var.subs[sub] = e->index;
+ break;
+ case SNMP_OP_GET:
+ if (val->var.len - sub != 1)
+ return (SNMP_ERR_NOSUCHNAME);
+ if ((e = pft_table_find(val->var.subs[sub])) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+
+ case SNMP_OP_COMMIT:
+ case SNMP_OP_ROLLBACK:
+ default:
+ abort();
+ }
+
+ switch (which) {
+ case LEAF_pfTablesTblDescr:
+ return (string_get(val, e->pft.pfrts_name, -1));
+ case LEAF_pfTablesTblCount:
+ val->v.integer = e->pft.pfrts_cnt;
+ break;
+ case LEAF_pfTablesTblTZero:
+ val->v.uint32 =
+ (time(NULL) - e->pft.pfrts_tzero) * 100;
+ break;
+ case LEAF_pfTablesTblRefsAnchor:
+ val->v.integer =
+ e->pft.pfrts_refcnt[PFR_REFCNT_ANCHOR];
+ break;
+ case LEAF_pfTablesTblRefsRule:
+ val->v.integer =
+ e->pft.pfrts_refcnt[PFR_REFCNT_RULE];
+ break;
+ case LEAF_pfTablesTblEvalMatch:
+ val->v.counter64 = e->pft.pfrts_match;
+ break;
+ case LEAF_pfTablesTblEvalNoMatch:
+ val->v.counter64 = e->pft.pfrts_nomatch;
+ break;
+ case LEAF_pfTablesTblBytesInPass:
+ val->v.counter64 =
+ e->pft.pfrts_bytes[PFR_DIR_IN][PFR_OP_PASS];
+ break;
+ case LEAF_pfTablesTblBytesInBlock:
+ val->v.counter64 =
+ e->pft.pfrts_bytes[PFR_DIR_IN][PFR_OP_BLOCK];
+ break;
+ case LEAF_pfTablesTblBytesInXPass:
+ val->v.counter64 =
+ e->pft.pfrts_bytes[PFR_DIR_IN][PFR_OP_XPASS];
+ break;
+ case LEAF_pfTablesTblBytesOutPass:
+ val->v.counter64 =
+ e->pft.pfrts_bytes[PFR_DIR_OUT][PFR_OP_PASS];
+ break;
+ case LEAF_pfTablesTblBytesOutBlock:
+ val->v.counter64 =
+ e->pft.pfrts_bytes[PFR_DIR_OUT][PFR_OP_BLOCK];
+ break;
+ case LEAF_pfTablesTblBytesOutXPass:
+ val->v.counter64 =
+ e->pft.pfrts_bytes[PFR_DIR_OUT][PFR_OP_XPASS];
+ break;
+ case LEAF_pfTablesTblPktsInPass:
+ val->v.counter64 =
+ e->pft.pfrts_packets[PFR_DIR_IN][PFR_OP_PASS];
+ break;
+ case LEAF_pfTablesTblPktsInBlock:
+ val->v.counter64 =
+ e->pft.pfrts_packets[PFR_DIR_IN][PFR_OP_BLOCK];
+ break;
+ case LEAF_pfTablesTblPktsInXPass:
+ val->v.counter64 =
+ e->pft.pfrts_packets[PFR_DIR_IN][PFR_OP_XPASS];
+ break;
+ case LEAF_pfTablesTblPktsOutPass:
+ val->v.counter64 =
+ e->pft.pfrts_packets[PFR_DIR_OUT][PFR_OP_PASS];
+ break;
+ case LEAF_pfTablesTblPktsOutBlock:
+ val->v.counter64 =
+ e->pft.pfrts_packets[PFR_DIR_OUT][PFR_OP_BLOCK];
+ break;
+ case LEAF_pfTablesTblPktsOutXPass:
+ val->v.counter64 =
+ e->pft.pfrts_packets[PFR_DIR_OUT][PFR_OP_XPASS];
+ break;
+
+ default:
+ return (SNMP_ERR_NOSUCHNAME);
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+pf_tbladdr(struct snmp_context __unused *ctx, struct snmp_value __unused *val,
+ u_int __unused sub, u_int __unused vindex, enum snmp_op __unused op)
+{
+ asn_subid_t which = val->var.subs[sub - 1];
+ struct pfa_entry *e = NULL;
+
+ if ((time(NULL) - pfa_table_age) > PFA_TABLE_MAXAGE)
+ pfa_refresh();
+
+ switch (op) {
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ case SNMP_OP_GETNEXT:
+ if ((e = NEXT_OBJECT_INT(&pfa_table,
+ &val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ val->var.len = sub + 1;
+ val->var.subs[sub] = e->index;
+ break;
+ case SNMP_OP_GET:
+ if (val->var.len - sub != 1)
+ return (SNMP_ERR_NOSUCHNAME);
+ if ((e = pfa_table_find(val->var.subs[sub])) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+
+ case SNMP_OP_COMMIT:
+ case SNMP_OP_ROLLBACK:
+ default:
+ abort();
+ }
+
+ switch (which) {
+ case LEAF_pfTablesAddrNetType:
+ if (e->pfas.pfras_a.pfra_af == AF_INET)
+ val->v.integer = pfTablesAddrNetType_ipv4;
+ else if (e->pfas.pfras_a.pfra_af == AF_INET6)
+ val->v.integer = pfTablesAddrNetType_ipv6;
+ else
+ return (SNMP_ERR_GENERR);
+ break;
+ case LEAF_pfTablesAddrNet:
+ if (e->pfas.pfras_a.pfra_af == AF_INET) {
+ return (string_get(val,
+ (u_char *)&e->pfas.pfras_a.pfra_ip4addr, 4));
+ } else if (e->pfas.pfras_a.pfra_af == AF_INET6)
+ return (string_get(val,
+ (u_char *)&e->pfas.pfras_a.pfra_ip6addr, 16));
+ else
+ return (SNMP_ERR_GENERR);
+ break;
+ case LEAF_pfTablesAddrPrefix:
+ val->v.integer = (int32_t) e->pfas.pfras_a.pfra_net;
+ break;
+ case LEAF_pfTablesAddrTZero:
+ val->v.uint32 =
+ (time(NULL) - e->pfas.pfras_tzero) * 100;
+ break;
+ case LEAF_pfTablesAddrBytesInPass:
+ val->v.counter64 =
+ e->pfas.pfras_bytes[PFR_DIR_IN][PFR_OP_PASS];
+ break;
+ case LEAF_pfTablesAddrBytesInBlock:
+ val->v.counter64 =
+ e->pfas.pfras_bytes[PFR_DIR_IN][PFR_OP_BLOCK];
+ break;
+ case LEAF_pfTablesAddrBytesOutPass:
+ val->v.counter64 =
+ e->pfas.pfras_bytes[PFR_DIR_OUT][PFR_OP_PASS];
+ break;
+ case LEAF_pfTablesAddrBytesOutBlock:
+ val->v.counter64 =
+ e->pfas.pfras_bytes[PFR_DIR_OUT][PFR_OP_BLOCK];
+ break;
+ case LEAF_pfTablesAddrPktsInPass:
+ val->v.counter64 =
+ e->pfas.pfras_packets[PFR_DIR_IN][PFR_OP_PASS];
+ break;
+ case LEAF_pfTablesAddrPktsInBlock:
+ val->v.counter64 =
+ e->pfas.pfras_packets[PFR_DIR_IN][PFR_OP_BLOCK];
+ break;
+ case LEAF_pfTablesAddrPktsOutPass:
+ val->v.counter64 =
+ e->pfas.pfras_packets[PFR_DIR_OUT][PFR_OP_PASS];
+ break;
+ case LEAF_pfTablesAddrPktsOutBlock:
+ val->v.counter64 =
+ e->pfas.pfras_packets[PFR_DIR_OUT][PFR_OP_BLOCK];
+ break;
+ default:
+ return (SNMP_ERR_NOSUCHNAME);
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+pf_altq(struct snmp_context __unused *ctx, struct snmp_value *val,
+ u_int sub, u_int __unused vindex, enum snmp_op op)
+{
+ asn_subid_t which = val->var.subs[sub - 1];
+
+ if (!altq_enabled)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ if (op == SNMP_OP_SET)
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ if (op == SNMP_OP_GET) {
+ if ((time(NULL) - pfq_table_age) > PFQ_TABLE_MAXAGE)
+ if (pfq_refresh() == -1)
+ return (SNMP_ERR_GENERR);
+
+ switch (which) {
+ case LEAF_pfAltqQueueNumber:
+ val->v.uint32 = pfq_table_count;
+ break;
+
+ default:
+ return (SNMP_ERR_NOSUCHNAME);
+ }
+
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+ return (SNMP_ERR_GENERR);
+}
+
+int
+pf_altqq(struct snmp_context __unused *ctx, struct snmp_value *val,
+ u_int sub, u_int __unused vindex, enum snmp_op op)
+{
+ asn_subid_t which = val->var.subs[sub - 1];
+ struct pfq_entry *e = NULL;
+
+ if (!altq_enabled)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ if ((time(NULL) - pfq_table_age) > PFQ_TABLE_MAXAGE)
+ pfq_refresh();
+
+ switch (op) {
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ case SNMP_OP_GETNEXT:
+ if ((e = NEXT_OBJECT_INT(&pfq_table,
+ &val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ val->var.len = sub + 1;
+ val->var.subs[sub] = e->index;
+ break;
+ case SNMP_OP_GET:
+ if (val->var.len - sub != 1)
+ return (SNMP_ERR_NOSUCHNAME);
+ if ((e = pfq_table_find(val->var.subs[sub])) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+
+ case SNMP_OP_COMMIT:
+ case SNMP_OP_ROLLBACK:
+ default:
+ abort();
+ }
+
+ switch (which) {
+ case LEAF_pfAltqQueueDescr:
+ return (string_get(val, e->altq.qname, -1));
+ case LEAF_pfAltqQueueParent:
+ return (string_get(val, e->altq.parent, -1));
+ case LEAF_pfAltqQueueScheduler:
+ val->v.integer = e->altq.scheduler;
+ break;
+ case LEAF_pfAltqQueueBandwidth:
+ val->v.uint32 = e->altq.bandwidth;
+ break;
+ case LEAF_pfAltqQueuePriority:
+ val->v.integer = e->altq.priority;
+ break;
+ case LEAF_pfAltqQueueLimit:
+ val->v.integer = e->altq.qlimit;
+ break;
+
+ default:
+ return (SNMP_ERR_NOSUCHNAME);
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+pf_labels(struct snmp_context __unused *ctx, struct snmp_value *val,
+ u_int sub, u_int __unused vindex, enum snmp_op op)
+{
+ asn_subid_t which = val->var.subs[sub - 1];
+
+ if (op == SNMP_OP_SET)
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ if (op == SNMP_OP_GET) {
+ if ((time(NULL) - pfl_table_age) > PFL_TABLE_MAXAGE)
+ if (pfl_refresh() == -1)
+ return (SNMP_ERR_GENERR);
+
+ switch (which) {
+ case LEAF_pfLabelsLblNumber:
+ val->v.uint32 = pfl_table_count;
+ break;
+
+ default:
+ return (SNMP_ERR_NOSUCHNAME);
+ }
+
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+ return (SNMP_ERR_GENERR);
+}
+
+int
+pf_lbltable(struct snmp_context __unused *ctx, struct snmp_value *val,
+ u_int sub, u_int __unused vindex, enum snmp_op op)
+{
+ asn_subid_t which = val->var.subs[sub - 1];
+ struct pfl_entry *e = NULL;
+
+ if ((time(NULL) - pfl_table_age) > PFL_TABLE_MAXAGE)
+ pfl_refresh();
+
+ switch (op) {
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ case SNMP_OP_GETNEXT:
+ if ((e = NEXT_OBJECT_INT(&pfl_table,
+ &val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ val->var.len = sub + 1;
+ val->var.subs[sub] = e->index;
+ break;
+ case SNMP_OP_GET:
+ if (val->var.len - sub != 1)
+ return (SNMP_ERR_NOSUCHNAME);
+ if ((e = pfl_table_find(val->var.subs[sub])) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+
+ case SNMP_OP_COMMIT:
+ case SNMP_OP_ROLLBACK:
+ default:
+ abort();
+ }
+
+ switch (which) {
+ case LEAF_pfLabelsLblName:
+ return (string_get(val, e->name, -1));
+ case LEAF_pfLabelsLblEvals:
+ val->v.counter64 = e->evals;
+ break;
+ case LEAF_pfLabelsLblBytesIn:
+ val->v.counter64 = e->bytes[IN];
+ break;
+ case LEAF_pfLabelsLblBytesOut:
+ val->v.counter64 = e->bytes[OUT];
+ break;
+ case LEAF_pfLabelsLblPktsIn:
+ val->v.counter64 = e->pkts[IN];
+ break;
+ case LEAF_pfLabelsLblPktsOut:
+ val->v.counter64 = e->pkts[OUT];
+ break;
+ default:
+ return (SNMP_ERR_NOSUCHNAME);
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+static struct pfi_entry *
+pfi_table_find(u_int idx)
+{
+ struct pfi_entry *e;
+
+ TAILQ_FOREACH(e, &pfi_table, link)
+ if (e->index == idx)
+ return (e);
+ return (NULL);
+}
+
+static struct pfq_entry *
+pfq_table_find(u_int idx)
+{
+ struct pfq_entry *e;
+
+ TAILQ_FOREACH(e, &pfq_table, link)
+ if (e->index == idx)
+ return (e);
+ return (NULL);
+}
+
+static struct pft_entry *
+pft_table_find(u_int idx)
+{
+ struct pft_entry *e;
+
+ TAILQ_FOREACH(e, &pft_table, link)
+ if (e->index == idx)
+ return (e);
+ return (NULL);
+}
+
+static struct pfa_entry *
+pfa_table_find(u_int idx)
+{
+ struct pfa_entry *e;
+
+ TAILQ_FOREACH(e, &pfa_table, link)
+ if (e->index == idx)
+ return (e);
+ return (NULL);
+}
+
+static struct pfl_entry *
+pfl_table_find(u_int idx)
+{
+ struct pfl_entry *e;
+
+ TAILQ_FOREACH(e, &pfl_table, link)
+ if (e->index == idx)
+ return (e);
+
+ return (NULL);
+}
+
+static int
+pfi_refresh(void)
+{
+ struct pfioc_iface io;
+ struct pfi_kif *p = NULL;
+ struct pfi_entry *e;
+ int i, numifs = 1;
+
+ if (started && this_tick <= pf_tick)
+ return (0);
+
+ while (!TAILQ_EMPTY(&pfi_table)) {
+ e = TAILQ_FIRST(&pfi_table);
+ TAILQ_REMOVE(&pfi_table, e, link);
+ free(e);
+ }
+
+ bzero(&io, sizeof(io));
+ io.pfiio_esize = sizeof(struct pfi_kif);
+
+ for (;;) {
+ p = reallocf(p, numifs * sizeof(struct pfi_kif));
+ if (p == NULL) {
+ syslog(LOG_ERR, "pfi_refresh(): reallocf() numifs=%d: %s",
+ numifs, strerror(errno));
+ goto err2;
+ }
+ io.pfiio_size = numifs;
+ io.pfiio_buffer = p;
+
+ if (ioctl(dev, DIOCIGETIFACES, &io)) {
+ syslog(LOG_ERR, "pfi_refresh(): ioctl(): %s",
+ strerror(errno));
+ goto err2;
+ }
+
+ if (numifs >= io.pfiio_size)
+ break;
+
+ numifs = io.pfiio_size;
+ }
+
+ for (i = 0; i < numifs; i++) {
+ e = malloc(sizeof(struct pfi_entry));
+ if (e == NULL)
+ goto err1;
+ e->index = i + 1;
+ memcpy(&e->pfi, p+i, sizeof(struct pfi_kif));
+ TAILQ_INSERT_TAIL(&pfi_table, e, link);
+ }
+
+ pfi_table_age = time(NULL);
+ pfi_table_count = numifs;
+ pf_tick = this_tick;
+
+ free(p);
+ return (0);
+
+err1:
+ while (!TAILQ_EMPTY(&pfi_table)) {
+ e = TAILQ_FIRST(&pfi_table);
+ TAILQ_REMOVE(&pfi_table, e, link);
+ free(e);
+ }
+err2:
+ free(p);
+ return(-1);
+}
+
+static int
+pfq_refresh(void)
+{
+ struct pfioc_altq pa;
+ struct pfq_entry *e;
+ int i, numqs, ticket;
+
+ if (started && this_tick <= pf_tick)
+ return (0);
+
+ while (!TAILQ_EMPTY(&pfq_table)) {
+ e = TAILQ_FIRST(&pfq_table);
+ TAILQ_REMOVE(&pfq_table, e, link);
+ free(e);
+ }
+
+ bzero(&pa, sizeof(pa));
+
+ if (ioctl(dev, DIOCGETALTQS, &pa)) {
+ syslog(LOG_ERR, "pfq_refresh: ioctl(DIOCGETALTQS): %s",
+ strerror(errno));
+ return (-1);
+ }
+
+ numqs = pa.nr;
+ ticket = pa.ticket;
+
+ for (i = 0; i < numqs; i++) {
+ e = malloc(sizeof(struct pfq_entry));
+ if (e == NULL) {
+ syslog(LOG_ERR, "pfq_refresh(): "
+ "malloc(): %s",
+ strerror(errno));
+ goto err;
+ }
+ pa.ticket = ticket;
+ pa.nr = i;
+
+ if (ioctl(dev, DIOCGETALTQ, &pa)) {
+ syslog(LOG_ERR, "pfq_refresh(): "
+ "ioctl(DIOCGETALTQ): %s",
+ strerror(errno));
+ goto err;
+ }
+
+ if (pa.altq.qid > 0) {
+ memcpy(&e->altq, &pa.altq, sizeof(struct pf_altq));
+ e->index = pa.altq.qid;
+ pfq_table_count = i;
+ INSERT_OBJECT_INT_LINK_INDEX(e, &pfq_table, link, index);
+ }
+ }
+
+ pfq_table_age = time(NULL);
+ pf_tick = this_tick;
+
+ return (0);
+err:
+ free(e);
+ while (!TAILQ_EMPTY(&pfq_table)) {
+ e = TAILQ_FIRST(&pfq_table);
+ TAILQ_REMOVE(&pfq_table, e, link);
+ free(e);
+ }
+ return(-1);
+}
+
+static int
+pfs_refresh(void)
+{
+ if (started && this_tick <= pf_tick)
+ return (0);
+
+ bzero(&pfs, sizeof(struct pf_status));
+
+ if (ioctl(dev, DIOCGETSTATUS, &pfs)) {
+ syslog(LOG_ERR, "pfs_refresh(): ioctl(): %s",
+ strerror(errno));
+ return (-1);
+ }
+
+ pf_tick = this_tick;
+ return (0);
+}
+
+static int
+pft_refresh(void)
+{
+ struct pfioc_table io;
+ struct pfr_tstats *t = NULL;
+ struct pft_entry *e;
+ int i, numtbls = 1;
+
+ if (started && this_tick <= pf_tick)
+ return (0);
+
+ while (!TAILQ_EMPTY(&pft_table)) {
+ e = TAILQ_FIRST(&pft_table);
+ TAILQ_REMOVE(&pft_table, e, link);
+ free(e);
+ }
+
+ bzero(&io, sizeof(io));
+ io.pfrio_esize = sizeof(struct pfr_tstats);
+
+ for (;;) {
+ t = reallocf(t, numtbls * sizeof(struct pfr_tstats));
+ if (t == NULL) {
+ syslog(LOG_ERR, "pft_refresh(): reallocf() numtbls=%d: %s",
+ numtbls, strerror(errno));
+ goto err2;
+ }
+ io.pfrio_size = numtbls;
+ io.pfrio_buffer = t;
+
+ if (ioctl(dev, DIOCRGETTSTATS, &io)) {
+ syslog(LOG_ERR, "pft_refresh(): ioctl(): %s",
+ strerror(errno));
+ goto err2;
+ }
+
+ if (numtbls >= io.pfrio_size)
+ break;
+
+ numtbls = io.pfrio_size;
+ }
+
+ for (i = 0; i < numtbls; i++) {
+ e = malloc(sizeof(struct pft_entry));
+ if (e == NULL)
+ goto err1;
+ e->index = i + 1;
+ memcpy(&e->pft, t+i, sizeof(struct pfr_tstats));
+ TAILQ_INSERT_TAIL(&pft_table, e, link);
+ }
+
+ pft_table_age = time(NULL);
+ pft_table_count = numtbls;
+ pf_tick = this_tick;
+
+ free(t);
+ return (0);
+err1:
+ while (!TAILQ_EMPTY(&pft_table)) {
+ e = TAILQ_FIRST(&pft_table);
+ TAILQ_REMOVE(&pft_table, e, link);
+ free(e);
+ }
+err2:
+ free(t);
+ return(-1);
+}
+
+static int
+pfa_table_addrs(u_int sidx, struct pfr_table *pt)
+{
+ struct pfioc_table io;
+ struct pfr_astats *t = NULL;
+ struct pfa_entry *e;
+ int i, numaddrs = 1;
+
+ if (pt == NULL)
+ return (-1);
+
+ memset(&io, 0, sizeof(io));
+ strlcpy(io.pfrio_table.pfrt_name, pt->pfrt_name,
+ sizeof(io.pfrio_table.pfrt_name));
+
+ for (;;) {
+ t = reallocf(t, numaddrs * sizeof(struct pfr_astats));
+ if (t == NULL) {
+ syslog(LOG_ERR, "pfa_table_addrs(): reallocf(): %s",
+ strerror(errno));
+ numaddrs = -1;
+ goto error;
+ }
+
+ memset(t, 0, sizeof(*t));
+ io.pfrio_size = numaddrs;
+ io.pfrio_buffer = t;
+ io.pfrio_esize = sizeof(struct pfr_astats);
+
+ if (ioctl(dev, DIOCRGETASTATS, &io)) {
+ syslog(LOG_ERR, "pfa_table_addrs(): ioctl() on %s: %s",
+ pt->pfrt_name, strerror(errno));
+ numaddrs = -1;
+ break;
+ }
+
+ if (numaddrs >= io.pfrio_size)
+ break;
+
+ numaddrs = io.pfrio_size;
+ }
+
+ for (i = 0; i < numaddrs; i++) {
+ if ((t + i)->pfras_a.pfra_af != AF_INET &&
+ (t + i)->pfras_a.pfra_af != AF_INET6) {
+ numaddrs = i;
+ break;
+ }
+
+ e = (struct pfa_entry *)malloc(sizeof(struct pfa_entry));
+ if (e == NULL) {
+ syslog(LOG_ERR, "pfa_table_addrs(): malloc(): %s",
+ strerror(errno));
+ numaddrs = -1;
+ break;
+ }
+ e->index = sidx + i;
+ memcpy(&e->pfas, t + i, sizeof(struct pfr_astats));
+ TAILQ_INSERT_TAIL(&pfa_table, e, link);
+ }
+
+ free(t);
+error:
+ return (numaddrs);
+}
+
+static int
+pfa_refresh(void)
+{
+ struct pfioc_table io;
+ struct pfr_table *pt = NULL, *it = NULL;
+ struct pfa_entry *e;
+ int i, numtbls = 1, cidx, naddrs;
+
+ if (started && this_tick <= pf_tick)
+ return (0);
+
+ while (!TAILQ_EMPTY(&pfa_table)) {
+ e = TAILQ_FIRST(&pfa_table);
+ TAILQ_REMOVE(&pfa_table, e, link);
+ free(e);
+ }
+
+ memset(&io, 0, sizeof(io));
+ io.pfrio_esize = sizeof(struct pfr_table);
+
+ for (;;) {
+ pt = reallocf(pt, numtbls * sizeof(struct pfr_table));
+ if (pt == NULL) {
+ syslog(LOG_ERR, "pfa_refresh(): reallocf() %s",
+ strerror(errno));
+ return (-1);
+ }
+ memset(pt, 0, sizeof(*pt));
+ io.pfrio_size = numtbls;
+ io.pfrio_buffer = pt;
+
+ if (ioctl(dev, DIOCRGETTABLES, &io)) {
+ syslog(LOG_ERR, "pfa_refresh(): ioctl(): %s",
+ strerror(errno));
+ goto err2;
+ }
+
+ if (numtbls >= io.pfrio_size)
+ break;
+
+ numtbls = io.pfrio_size;
+ }
+
+ cidx = 1;
+
+ for (it = pt, i = 0; i < numtbls; it++, i++) {
+ /*
+ * Skip the table if not active - ioctl(DIOCRGETASTATS) will
+ * return ESRCH for this entry anyway.
+ */
+ if (!(it->pfrt_flags & PFR_TFLAG_ACTIVE))
+ continue;
+
+ if ((naddrs = pfa_table_addrs(cidx, it)) < 0)
+ goto err1;
+
+ cidx += naddrs;
+ }
+
+ pfa_table_age = time(NULL);
+ pfa_table_count = cidx;
+ pf_tick = this_tick;
+
+ free(pt);
+ return (0);
+err1:
+ while (!TAILQ_EMPTY(&pfa_table)) {
+ e = TAILQ_FIRST(&pfa_table);
+ TAILQ_REMOVE(&pfa_table, e, link);
+ free(e);
+ }
+
+err2:
+ free(pt);
+ return (-1);
+}
+
+static int
+pfl_scan_ruleset(const char *path)
+{
+ struct pfioc_rule pr;
+ struct pfl_entry *e;
+ u_int32_t nr, i;
+
+ bzero(&pr, sizeof(pr));
+ strlcpy(pr.anchor, path, sizeof(pr.anchor));
+ pr.rule.action = PF_PASS;
+ if (ioctl(dev, DIOCGETRULES, &pr)) {
+ syslog(LOG_ERR, "pfl_scan_ruleset: ioctl(DIOCGETRULES): %s",
+ strerror(errno));
+ goto err;
+ }
+
+ for (nr = pr.nr, i = 0; i < nr; i++) {
+ pr.nr = i;
+ if (ioctl(dev, DIOCGETRULE, &pr)) {
+ syslog(LOG_ERR, "pfl_scan_ruleset: ioctl(DIOCGETRULE):"
+ " %s", strerror(errno));
+ goto err;
+ }
+
+ if (pr.rule.label[0]) {
+ e = (struct pfl_entry *)malloc(sizeof(*e));
+ if (e == NULL)
+ goto err;
+
+ strlcpy(e->name, path, sizeof(e->name));
+ if (path[0])
+ strlcat(e->name, "/", sizeof(e->name));
+ strlcat(e->name, pr.rule.label, sizeof(e->name));
+
+ e->evals = pr.rule.evaluations;
+ e->bytes[IN] = pr.rule.bytes[IN];
+ e->bytes[OUT] = pr.rule.bytes[OUT];
+ e->pkts[IN] = pr.rule.packets[IN];
+ e->pkts[OUT] = pr.rule.packets[OUT];
+ e->index = ++pfl_table_count;
+
+ TAILQ_INSERT_TAIL(&pfl_table, e, link);
+ }
+ }
+
+ return (0);
+
+err:
+ return (-1);
+}
+
+static int
+pfl_walk_rulesets(const char *path)
+{
+ struct pfioc_ruleset prs;
+ char newpath[MAXPATHLEN];
+ u_int32_t nr, i;
+
+ if (pfl_scan_ruleset(path))
+ goto err;
+
+ bzero(&prs, sizeof(prs));
+ strlcpy(prs.path, path, sizeof(prs.path));
+ if (ioctl(dev, DIOCGETRULESETS, &prs)) {
+ syslog(LOG_ERR, "pfl_walk_rulesets: ioctl(DIOCGETRULESETS): %s",
+ strerror(errno));
+ goto err;
+ }
+
+ for (nr = prs.nr, i = 0; i < nr; i++) {
+ prs.nr = i;
+ if (ioctl(dev, DIOCGETRULESET, &prs)) {
+ syslog(LOG_ERR, "pfl_walk_rulesets: ioctl(DIOCGETRULESET):"
+ " %s", strerror(errno));
+ goto err;
+ }
+
+ if (strcmp(prs.name, PF_RESERVED_ANCHOR) == 0)
+ continue;
+
+ strlcpy(newpath, path, sizeof(newpath));
+ if (path[0])
+ strlcat(newpath, "/", sizeof(newpath));
+
+ strlcat(newpath, prs.name, sizeof(newpath));
+ if (pfl_walk_rulesets(newpath))
+ goto err;
+ }
+
+ return (0);
+
+err:
+ return (-1);
+}
+
+static int
+pfl_refresh(void)
+{
+ struct pfl_entry *e;
+
+ if (started && this_tick <= pf_tick)
+ return (0);
+
+ while (!TAILQ_EMPTY(&pfl_table)) {
+ e = TAILQ_FIRST(&pfl_table);
+ TAILQ_REMOVE(&pfl_table, e, link);
+ free(e);
+ }
+ pfl_table_count = 0;
+
+ if (pfl_walk_rulesets(""))
+ goto err;
+
+ pfl_table_age = time(NULL);
+ pf_tick = this_tick;
+
+ return (0);
+
+err:
+ while (!TAILQ_EMPTY(&pfl_table)) {
+ e = TAILQ_FIRST(&pfl_table);
+ TAILQ_REMOVE(&pfl_table, e, link);
+ free(e);
+ }
+ pfl_table_count = 0;
+
+ return (-1);
+}
+
+/*
+ * check whether altq support is enabled in kernel
+ */
+
+static int
+altq_is_enabled(int pfdev)
+{
+ struct pfioc_altq pa;
+
+ errno = 0;
+ if (ioctl(pfdev, DIOCGETALTQS, &pa)) {
+ if (errno == ENODEV) {
+ syslog(LOG_INFO, "No ALTQ support in kernel\n"
+ "ALTQ related functions disabled\n");
+ return (0);
+ } else
+ syslog(LOG_ERR, "DIOCGETALTQS returned an error: %s",
+ strerror(errno));
+ return (-1);
+ }
+ return (1);
+}
+
+/*
+ * Implement the bsnmpd module interface
+ */
+static int
+pf_init(struct lmodule *mod, int __unused argc, char __unused *argv[])
+{
+ module = mod;
+
+ if ((dev = open("/dev/pf", O_RDONLY)) == -1) {
+ syslog(LOG_ERR, "pf_init(): open(): %s\n",
+ strerror(errno));
+ return (-1);
+ }
+
+ if ((altq_enabled = altq_is_enabled(dev)) == -1) {
+ syslog(LOG_ERR, "pf_init(): altq test failed");
+ return (-1);
+ }
+
+ /* Prepare internal state */
+ TAILQ_INIT(&pfi_table);
+ TAILQ_INIT(&pfq_table);
+ TAILQ_INIT(&pft_table);
+ TAILQ_INIT(&pfa_table);
+ TAILQ_INIT(&pfl_table);
+
+ pfi_refresh();
+ if (altq_enabled) {
+ pfq_refresh();
+ }
+
+ pfs_refresh();
+ pft_refresh();
+ pfa_refresh();
+ pfl_refresh();
+
+ started = 1;
+
+ return (0);
+}
+
+static int
+pf_fini(void)
+{
+ struct pfi_entry *i1, *i2;
+ struct pfq_entry *q1, *q2;
+ struct pft_entry *t1, *t2;
+ struct pfa_entry *a1, *a2;
+ struct pfl_entry *l1, *l2;
+
+ /* Empty the list of interfaces */
+ i1 = TAILQ_FIRST(&pfi_table);
+ while (i1 != NULL) {
+ i2 = TAILQ_NEXT(i1, link);
+ free(i1);
+ i1 = i2;
+ }
+
+ /* List of queues */
+ q1 = TAILQ_FIRST(&pfq_table);
+ while (q1 != NULL) {
+ q2 = TAILQ_NEXT(q1, link);
+ free(q1);
+ q1 = q2;
+ }
+
+ /* List of tables */
+ t1 = TAILQ_FIRST(&pft_table);
+ while (t1 != NULL) {
+ t2 = TAILQ_NEXT(t1, link);
+ free(t1);
+ t1 = t2;
+ }
+
+ /* List of table addresses */
+ a1 = TAILQ_FIRST(&pfa_table);
+ while (a1 != NULL) {
+ a2 = TAILQ_NEXT(a1, link);
+ free(a1);
+ a1 = a2;
+ }
+
+ /* And the list of labeled filter rules */
+ l1 = TAILQ_FIRST(&pfl_table);
+ while (l1 != NULL) {
+ l2 = TAILQ_NEXT(l1, link);
+ free(l1);
+ l1 = l2;
+ }
+
+ close(dev);
+ return (0);
+}
+
+static void
+pf_dump(void)
+{
+ pfi_refresh();
+ if (altq_enabled) {
+ pfq_refresh();
+ }
+ pft_refresh();
+ pfa_refresh();
+ pfl_refresh();
+
+ syslog(LOG_ERR, "Dump: pfi_table_age = %jd",
+ (intmax_t)pfi_table_age);
+ syslog(LOG_ERR, "Dump: pfi_table_count = %d",
+ pfi_table_count);
+
+ syslog(LOG_ERR, "Dump: pfq_table_age = %jd",
+ (intmax_t)pfq_table_age);
+ syslog(LOG_ERR, "Dump: pfq_table_count = %d",
+ pfq_table_count);
+
+ syslog(LOG_ERR, "Dump: pft_table_age = %jd",
+ (intmax_t)pft_table_age);
+ syslog(LOG_ERR, "Dump: pft_table_count = %d",
+ pft_table_count);
+
+ syslog(LOG_ERR, "Dump: pfa_table_age = %jd",
+ (intmax_t)pfa_table_age);
+ syslog(LOG_ERR, "Dump: pfa_table_count = %d",
+ pfa_table_count);
+
+ syslog(LOG_ERR, "Dump: pfl_table_age = %jd",
+ (intmax_t)pfl_table_age);
+ syslog(LOG_ERR, "Dump: pfl_table_count = %d",
+ pfl_table_count);
+}
+
+const struct snmp_module config = {
+ .comment = "This module implements a MIB for the pf packet filter.",
+ .init = pf_init,
+ .fini = pf_fini,
+ .tree = pf_ctree,
+ .dump = pf_dump,
+ .tree_size = pf_CTREE_SIZE,
+};
diff --git a/usr.sbin/bsnmpd/modules/snmp_pf/pf_tree.def b/usr.sbin/bsnmpd/modules/snmp_pf/pf_tree.def
new file mode 100644
index 0000000..1dfa14c
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_pf/pf_tree.def
@@ -0,0 +1,210 @@
+#
+# Copyright (c) 2010 Philip Paeps <philip@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$
+#
+
+(1 internet
+ (4 private
+ (1 enterprises
+ (12325 fokus
+ (1 begemot
+ (200 begemotPf
+ (1 begemotPfObjects
+ (1 pfStatus
+ (1 pfStatusRunning ENUM ( 1 true 2 false ) pf_status GET)
+ (2 pfStatusRuntime TIMETICKS pf_status GET)
+ (3 pfStatusDebug ENUM ( 0 none 1 urgent 2 misc 3 loud ) pf_status GET)
+ (4 pfStatusHostId OCTETSTRING pf_status GET)
+ )
+ (2 pfCounter
+ (1 pfCounterMatch COUNTER64 pf_counter GET)
+ (2 pfCounterBadOffset COUNTER64 pf_counter GET)
+ (3 pfCounterFragment COUNTER64 pf_counter GET)
+ (4 pfCounterShort COUNTER64 pf_counter GET)
+ (5 pfCounterNormalize COUNTER64 pf_counter GET)
+ (6 pfCounterMemDrop COUNTER64 pf_counter GET)
+ )
+ (3 pfStateTable
+ (1 pfStateTableCount UNSIGNED32 pf_statetable GET)
+ (2 pfStateTableSearches COUNTER64 pf_statetable GET)
+ (3 pfStateTableInserts COUNTER64 pf_statetable GET)
+ (4 pfStateTableRemovals COUNTER64 pf_statetable GET)
+ )
+ (4 pfSrcNodes
+ (1 pfSrcNodesCount UNSIGNED32 pf_srcnodes GET)
+ (2 pfSrcNodesSearches COUNTER64 pf_srcnodes GET)
+ (3 pfSrcNodesInserts COUNTER64 pf_srcnodes GET)
+ (4 pfSrcNodesRemovals COUNTER64 pf_srcnodes GET)
+ )
+ (5 pfLimits
+ (1 pfLimitsStates UNSIGNED32 pf_limits GET)
+ (2 pfLimitsSrcNodes UNSIGNED32 pf_limits GET)
+ (3 pfLimitsFrags UNSIGNED32 pf_limits GET)
+ )
+ (6 pfTimeouts
+ (1 pfTimeoutsTcpFirst INTEGER32 pf_timeouts GET)
+ (2 pfTimeoutsTcpOpening INTEGER32 pf_timeouts GET)
+ (3 pfTimeoutsTcpEstablished INTEGER32 pf_timeouts GET)
+ (4 pfTimeoutsTcpClosing INTEGER32 pf_timeouts GET)
+ (5 pfTimeoutsTcpFinWait INTEGER32 pf_timeouts GET)
+ (6 pfTimeoutsTcpClosed INTEGER32 pf_timeouts GET)
+ (7 pfTimeoutsUdpFirst INTEGER32 pf_timeouts GET)
+ (8 pfTimeoutsUdpSingle INTEGER32 pf_timeouts GET)
+ (9 pfTimeoutsUdpMultiple INTEGER32 pf_timeouts GET)
+ (10 pfTimeoutsIcmpFirst INTEGER32 pf_timeouts GET)
+ (11 pfTimeoutsIcmpError INTEGER32 pf_timeouts GET)
+ (12 pfTimeoutsOtherFirst INTEGER32 pf_timeouts GET)
+ (13 pfTimeoutsOtherSingle INTEGER32 pf_timeouts GET)
+ (14 pfTimeoutsOtherMultiple INTEGER32 pf_timeouts GET)
+ (15 pfTimeoutsFragment INTEGER32 pf_timeouts GET)
+ (16 pfTimeoutsInterval INTEGER32 pf_timeouts GET)
+ (17 pfTimeoutsAdaptiveStart INTEGER32 pf_timeouts GET)
+ (18 pfTimeoutsAdaptiveEnd INTEGER32 pf_timeouts GET)
+ (19 pfTimeoutsSrcNode INTEGER32 pf_timeouts GET)
+ )
+ (7 pfLogInterface
+ (1 pfLogInterfaceName OCTETSTRING pf_logif GET)
+ (2 pfLogInterfaceIp4BytesIn COUNTER64 pf_logif GET)
+ (3 pfLogInterfaceIp4BytesOut COUNTER64 pf_logif GET)
+ (4 pfLogInterfaceIp4PktsInPass COUNTER64 pf_logif GET)
+ (5 pfLogInterfaceIp4PktsInDrop COUNTER64 pf_logif GET)
+ (6 pfLogInterfaceIp4PktsOutPass COUNTER64 pf_logif GET)
+ (7 pfLogInterfaceIp4PktsOutDrop COUNTER64 pf_logif GET)
+ (8 pfLogInterfaceIp6BytesIn COUNTER64 pf_logif GET)
+ (9 pfLogInterfaceIp6BytesOut COUNTER64 pf_logif GET)
+ (10 pfLogInterfaceIp6PktsInPass COUNTER64 pf_logif GET)
+ (11 pfLogInterfaceIp6PktsInDrop COUNTER64 pf_logif GET)
+ (12 pfLogInterfaceIp6PktsOutPass COUNTER64 pf_logif GET)
+ (13 pfLogInterfaceIp6PktsOutDrop COUNTER64 pf_logif GET)
+ )
+ (8 pfInterfaces
+ (1 pfInterfacesIfNumber INTEGER32 pf_interfaces GET)
+ (2 pfInterfacesIfTable
+ (1 pfInterfacesIfEntry : INTEGER32 pf_iftable
+ (1 pfInterfacesIfIndex INTEGER32)
+ (2 pfInterfacesIfDescr OCTETSTRING GET)
+ (3 pfInterfacesIfType ENUM ( 0 group 1 instance 2 detached ) GET)
+ (4 pfInterfacesIfTZero TIMETICKS GET)
+ (5 pfInterfacesIfRefsState NULL GET)
+ (6 pfInterfacesIfRefsRule UNSIGNED32 GET)
+ (7 pfInterfacesIf4BytesInPass COUNTER64 GET)
+ (8 pfInterfacesIf4BytesInBlock COUNTER64 GET)
+ (9 pfInterfacesIf4BytesOutPass COUNTER64 GET)
+ (10 pfInterfacesIf4BytesOutBlock COUNTER64 GET)
+ (11 pfInterfacesIf4PktsInPass COUNTER64 GET)
+ (12 pfInterfacesIf4PktsInBlock COUNTER64 GET)
+ (13 pfInterfacesIf4PktsOutPass COUNTER64 GET)
+ (14 pfInterfacesIf4PktsOutBlock COUNTER64 GET)
+ (15 pfInterfacesIf6BytesInPass COUNTER64 GET)
+ (16 pfInterfacesIf6BytesInBlock COUNTER64 GET)
+ (17 pfInterfacesIf6BytesOutPass COUNTER64 GET)
+ (18 pfInterfacesIf6BytesOutBlock COUNTER64 GET)
+ (19 pfInterfacesIf6PktsInPass COUNTER64 GET)
+ (20 pfInterfacesIf6PktsInBlock COUNTER64 GET)
+ (21 pfInterfacesIf6PktsOutPass COUNTER64 GET)
+ (22 pfInterfacesIf6PktsOutBlock COUNTER64 GET)
+ )
+ )
+ )
+ (9 pfTables
+ (1 pfTablesTblNumber INTEGER32 pf_tables GET)
+ (2 pfTablesTblTable
+ (1 pfTablesTblEntry : INTEGER32 pf_tbltable
+ (1 pfTablesTblIndex INTEGER32)
+ (2 pfTablesTblDescr OCTETSTRING GET)
+ (3 pfTablesTblCount INTEGER32 GET)
+ (4 pfTablesTblTZero TIMETICKS GET)
+ (5 pfTablesTblRefsAnchor INTEGER32 GET)
+ (6 pfTablesTblRefsRule INTEGER32 GET)
+ (7 pfTablesTblEvalMatch COUNTER64 GET)
+ (8 pfTablesTblEvalNoMatch COUNTER64 GET)
+ (9 pfTablesTblBytesInPass COUNTER64 GET)
+ (10 pfTablesTblBytesInBlock COUNTER64 GET)
+ (11 pfTablesTblBytesInXPass COUNTER64 GET)
+ (12 pfTablesTblBytesOutPass COUNTER64 GET)
+ (13 pfTablesTblBytesOutBlock COUNTER64 GET)
+ (14 pfTablesTblBytesOutXPass COUNTER64 GET)
+ (15 pfTablesTblPktsInPass COUNTER64 GET)
+ (16 pfTablesTblPktsInBlock COUNTER64 GET)
+ (17 pfTablesTblPktsInXPass COUNTER64 GET)
+ (18 pfTablesTblPktsOutPass COUNTER64 GET)
+ (19 pfTablesTblPktsOutBlock COUNTER64 GET)
+ (20 pfTablesTblPktsOutXPass COUNTER64 GET)
+ )
+ )
+ (3 pfTablesAddrTable
+ (1 pfTablesAddrEntry : INTEGER32 pf_tbladdr
+ (1 pfTablesAddrIndex INTEGER32)
+ (2 pfTablesAddrNetType ENUM ( 0 unknown 1 ipv4 2 ipv6) GET)
+ (3 pfTablesAddrNet OCTETSTRING | InetAddress GET)
+ (4 pfTablesAddrPrefix UNSIGNED32 GET)
+ (5 pfTablesAddrTZero TIMETICKS GET)
+ (6 pfTablesAddrBytesInPass COUNTER64 GET)
+ (7 pfTablesAddrBytesInBlock COUNTER64 GET)
+ (8 pfTablesAddrBytesOutPass COUNTER64 GET)
+ (9 pfTablesAddrBytesOutBlock COUNTER64 GET)
+ (10 pfTablesAddrPktsInPass COUNTER64 GET)
+ (11 pfTablesAddrPktsInBlock COUNTER64 GET)
+ (12 pfTablesAddrPktsOutPass COUNTER64 GET)
+ (13 pfTablesAddrPktsOutBlock COUNTER64 GET)
+ )
+ )
+ )
+ (10 pfAltq
+ (1 pfAltqQueueNumber INTEGER32 pf_altq GET)
+ (2 pfAltqQueueTable
+ (1 pfAltqQueueEntry : INTEGER32 pf_altqq
+ (1 pfAltqQueueIndex INTEGER32)
+ (2 pfAltqQueueDescr OCTETSTRING GET)
+ (3 pfAltqQueueParent OCTETSTRING GET)
+ (4 pfAltqQueueScheduler ENUM ( 1 cbq 8 hfsc 11 priq ) GET)
+ (5 pfAltqQueueBandwidth UNSIGNED32 GET)
+ (6 pfAltqQueuePriority INTEGER32 GET)
+ (7 pfAltqQueueLimit INTEGER32 GET)
+ )
+ )
+ )
+ (11 pfLabels
+ (1 pfLabelsLblNumber INTEGER32 pf_labels GET)
+ (2 pfLabelsLblTable
+ (1 pfLabelsLblEntry : INTEGER pf_lbltable
+ (1 pfLabelsLblIndex INTEGER)
+ (2 pfLabelsLblName OCTETSTRING GET)
+ (3 pfLabelsLblEvals COUNTER64 GET)
+ (4 pfLabelsLblBytesIn COUNTER64 GET)
+ (5 pfLabelsLblBytesOut COUNTER64 GET)
+ (6 pfLabelsLblPktsIn COUNTER64 GET)
+ (7 pfLabelsLblPktsOut COUNTER64 GET)
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+)
diff --git a/usr.sbin/bsnmpd/modules/snmp_target/Makefile b/usr.sbin/bsnmpd/modules/snmp_target/Makefile
new file mode 100644
index 0000000..10141e5
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_target/Makefile
@@ -0,0 +1,20 @@
+# $FreeBSD$
+#
+# Author: Shteryana Shopova <syrinx@freebsd.org>
+
+CONTRIB= ${.CURDIR}/../../../../contrib/bsnmp
+.PATH: ${CONTRIB}/snmp_target
+
+MOD= target
+SRCS= target_snmp.c
+XSYM= snmpTargetMIB snmpNotificationMIB snmpUDPDomain
+
+MAN= snmp_target.3
+
+CFLAGS+= -I${CONTRIB}/lib -I${CONTRIB}/snmpd -DSNMPTREE_TYPES
+CFLAGS+= -DHAVE_ERR_H -DHAVE_GETADDRINFO -DHAVE_STRLCPY -DHAVE_SYS_TREE_H
+
+DEFS= ${MOD}_tree.def
+BMIBS=
+
+.include <bsd.snmpmod.mk>
diff --git a/usr.sbin/bsnmpd/modules/snmp_target/Makefile.depend b/usr.sbin/bsnmpd/modules/snmp_target/Makefile.depend
new file mode 100644
index 0000000..66ec571
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_target/Makefile.depend
@@ -0,0 +1,27 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbsnmp/libbsnmp \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+target_snmp.So: target_oid.h
+target_snmp.So: target_tree.h
+target_snmp.po: target_oid.h
+target_snmp.po: target_tree.h
+target_tree.So: target_tree.c
+target_tree.So: target_tree.h
+target_tree.po: target_tree.c
+target_tree.po: target_tree.h
+.endif
diff --git a/usr.sbin/bsnmpd/modules/snmp_usm/Makefile b/usr.sbin/bsnmpd/modules/snmp_usm/Makefile
new file mode 100644
index 0000000..4ae818a
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_usm/Makefile
@@ -0,0 +1,22 @@
+# $FreeBSD$
+#
+# Author: Shteryana Shopova <syrinx@freebsd.org>
+
+CONTRIB= ${.CURDIR}/../../../../contrib/bsnmp
+.PATH: ${CONTRIB}/snmp_usm
+
+MOD= usm
+SRCS= usm_snmp.c
+XSYM= snmpUsmMIB usmNoAuthProtocol usmHMACMD5AuthProtocol \
+ usmHMACSHAAuthProtocol usmNoPrivProtocol usmDESPrivProtocol \
+ usmAesCfb128Protocol usmUserSecurityName
+
+MAN= snmp_usm.3
+
+CFLAGS+= -I${CONTRIB}/lib -I${CONTRIB}/snmpd -DSNMPTREE_TYPES
+CFLAGS+= -DHAVE_ERR_H -DHAVE_GETADDRINFO -DHAVE_STRLCPY -DHAVE_SYS_TREE_H
+
+DEFS= ${MOD}_tree.def
+BMIBS=
+
+.include <bsd.snmpmod.mk>
diff --git a/usr.sbin/bsnmpd/modules/snmp_usm/Makefile.depend b/usr.sbin/bsnmpd/modules/snmp_usm/Makefile.depend
new file mode 100644
index 0000000..a7180f9
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_usm/Makefile.depend
@@ -0,0 +1,27 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbsnmp/libbsnmp \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+usm_snmp.So: usm_oid.h
+usm_snmp.So: usm_tree.h
+usm_snmp.po: usm_oid.h
+usm_snmp.po: usm_tree.h
+usm_tree.So: usm_tree.c
+usm_tree.So: usm_tree.h
+usm_tree.po: usm_tree.c
+usm_tree.po: usm_tree.h
+.endif
diff --git a/usr.sbin/bsnmpd/modules/snmp_vacm/Makefile b/usr.sbin/bsnmpd/modules/snmp_vacm/Makefile
new file mode 100644
index 0000000..1be8d62
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_vacm/Makefile
@@ -0,0 +1,20 @@
+# $FreeBSD$
+#
+# Author: Shteryana Shopova <syrinx@freebsd.org>
+
+CONTRIB= ${.CURDIR}/../../../../contrib/bsnmp
+.PATH: ${CONTRIB}/snmp_vacm
+
+MOD= vacm
+SRCS= vacm_snmp.c
+XSYM= snmpVacmMIB
+
+MAN= snmp_vacm.3
+
+CFLAGS+= -I${CONTRIB}/lib -I${CONTRIB}/snmpd -DSNMPTREE_TYPES
+CFLAGS+= -DHAVE_ERR_H -DHAVE_GETADDRINFO -DHAVE_STRLCPY -DHAVE_SYS_TREE_H
+
+DEFS= ${MOD}_tree.def
+BMIBS=
+
+.include <bsd.snmpmod.mk>
diff --git a/usr.sbin/bsnmpd/modules/snmp_vacm/Makefile.depend b/usr.sbin/bsnmpd/modules/snmp_vacm/Makefile.depend
new file mode 100644
index 0000000..89b4f2b
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_vacm/Makefile.depend
@@ -0,0 +1,27 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbsnmp/libbsnmp \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+vacm_snmp.So: vacm_oid.h
+vacm_snmp.So: vacm_tree.h
+vacm_snmp.po: vacm_oid.h
+vacm_snmp.po: vacm_tree.h
+vacm_tree.So: vacm_tree.c
+vacm_tree.So: vacm_tree.h
+vacm_tree.po: vacm_tree.c
+vacm_tree.po: vacm_tree.h
+.endif
diff --git a/usr.sbin/bsnmpd/modules/snmp_wlan/BEGEMOT-WIRELESS-MIB.txt b/usr.sbin/bsnmpd/modules/snmp_wlan/BEGEMOT-WIRELESS-MIB.txt
new file mode 100644
index 0000000..27f5be7
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_wlan/BEGEMOT-WIRELESS-MIB.txt
@@ -0,0 +1,3898 @@
+--
+-- Copyright (C) 2010 The FreeBSD Foundation
+-- All rights reserved.
+--
+-- This documentation was written by Shteryana Sotirova Shopova 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 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$
+--
+
+BEGEMOT-WIRELESS-MIB DEFINITIONS ::= BEGIN
+
+IMPORTS
+ MODULE-IDENTITY, OBJECT-TYPE, NOTIFICATION-TYPE,
+ Counter32, Integer32, TimeTicks, Unsigned32, mib-2
+ FROM SNMPv2-SMI
+ TEXTUAL-CONVENTION, MacAddress, TruthValue, RowStatus,
+ DisplayString
+ FROM SNMPv2-TC
+ InterfaceIndex, ifIndex FROM IF-MIB
+ begemot
+ FROM BEGEMOT-MIB;
+
+begemotWlan MODULE-IDENTITY
+ LAST-UPDATED "201005170000Z"
+ ORGANIZATION "The FreeBSD Foundation"
+ CONTACT-INFO
+ " Shteryana Shopova
+
+ Postal: 12 Andrey Lyapchev Blvd.
+ block 2, ap.19
+ 1797 Sofia
+ Bulgaria
+
+ Fax: N/A
+
+ E-Mail: syrinx@FreeBSD.org"
+ DESCRIPTION
+ "The Begemot MIB for managing IEEE802.11 interfaces."
+ REVISION "201005170000Z"
+ DESCRIPTION
+ "Initial revision."
+ ::= { begemot 210 }
+
+-- ---------------------------------------------------------- --
+-- Textual conventions
+-- ---------------------------------------------------------- --
+WlanMgmtReasonCode ::= TEXTUAL-CONVENTION
+ STATUS current
+ DESCRIPTION
+ "Enumeration of reason and codes used in IEEE802.11
+ management frames to indicate why an action took place."
+ SYNTAX INTEGER {
+ unspecified(1),
+ authenticationExpire(2),
+ authenticationLeave(3),
+ associationExpire(4),
+ associationTooMany(5),
+ notAuthenticated(6),
+ notAssociated(7),
+ associationLeave(8),
+ associationNotAuthenticated(9),
+-- XXX: TODO - FIXME
+ dissassocPwrcapBad(10),
+ dissassocSuperchanBad(11),
+ ieInvalid(13),
+ micFailure(14),
+ fourWayHandshakeTimeout(15),
+ groupKeyUpdateTimeout(16),
+ ieIn4FourWayDiffers(17),
+ groupCipherInvalid(18),
+ pairwiseCiherInvalid(19),
+ akmpInvalid(20),
+ unsupportedRsnIeVersion(21),
+ invalidRsnIeCap(22),
+ dot1xAuthFailed(23),
+ cipherSuiteRejected(24),
+ unspeciffiedQos(32),
+ insufficientBw(33),
+ tooManyFrames(34),
+ outsideTxOp(35),
+ leavingQbss(36),
+ badMechanism(37),
+ setupNeeded(38),
+ timeout(39)
+ }
+
+WlanMgmtMeshReasonCode ::= TEXTUAL-CONVENTION
+ STATUS current
+ DESCRIPTION
+ "Enumeration of reason and codes used in IEEE802.11
+ mesh routing management frames to indicate why an
+ action took place."
+ SYNTAX INTEGER {
+-- XXX: TODO - FIXME
+ peerLinkCancelled(2),
+ maxPeers(3),
+ cpViolation(4),
+ closeRcvd(5),
+ maxRetries(6),
+ confirmTimeout(7),
+ invalidGtk(8),
+ inconsistentParams(9),
+ invalidSecurity(10),
+ perrUnspecified(11),
+ perrNoFI(12),
+ perrDestUnreach(13)
+ }
+
+WlanMgmtStatusCode ::= TEXTUAL-CONVENTION
+ STATUS current
+ DESCRIPTION
+ "Enumeration of reason and codes used in IEEE802.11
+ management frames to indicate what the result of an
+ operation is."
+ SYNTAX INTEGER {
+-- XXX: TODO - FIXME
+ success(0),
+ unspecified(1),
+ capabilitiesInfo(10),
+ notAssociated(11),
+ other(12),
+ algorithm(13),
+ sequence(14),
+ challenge(15),
+ timeout(16),
+ tooMany(17),
+ basicRate(18),
+ spRequired(19),
+ pbccRequired(20),
+ caRequired(21),
+ specMgmtRequired(22),
+ pwrcapRequire(23),
+ superchanRequired(24),
+ shortSlotRequired(25),
+ dssofdmRequired(26),
+ missingHTCaps(27),
+ invalidIE(40),
+ groupCipherInvalid(41),
+ pairwiseCipherInvalid(42),
+ akmpInvalid(43),
+ unsupportedRsnIEVersion(44),
+ invalidRsnIECap(45),
+ cipherSuiteRejected(46)
+ }
+
+WlanRegDomainCode ::= TEXTUAL-CONVENTION
+ STATUS current
+ DESCRIPTION
+ "Enumeration of regdomain codes."
+ SYNTAX INTEGER {
+ fcc(1),
+ ca(2),
+ etsi(3),
+ etsi2(4),
+ etsi3(5),
+ fcc3(6),
+ japan(7),
+ korea(8),
+ apac(9),
+ apac2(10),
+ apac3(11),
+ row(12),
+ none(13),
+ debug(14),
+ sr9(15),
+ xr9(16),
+ gz901(17)
+ }
+
+WlanIfaceDot11nPduType ::= TEXTUAL-CONVENTION
+ STATUS current
+ DESCRIPTION
+ "Enumeration of values for PDU transmit/receive enabled."
+ SYNTAX INTEGER {
+ disabled(0),
+ rxOnly(1),
+ txOnly(2),
+ txAndRx(3)
+ }
+
+WlanPeerCapabilityFlags ::= TEXTUAL-CONVENTION
+ STATUS current
+ DESCRIPTION
+ "A list of capability bits that may be advertised by a peer."
+ SYNTAX BITS {
+ ess(1),
+ ibss(2),
+ cfPollable(3),
+ cfPollRequest(4),
+ privacy(5),
+ shortPreamble(6),
+ pbcc(7),
+ channelAgility(8),
+ shortSlotTime(9),
+ rsn(10),
+ dsssofdm(11)
+ }
+
+WlanIfPhyMode ::= TEXTUAL-CONVENTION
+ STATUS current
+ DESCRIPTION
+ "A list of wireless PHY operating modes."
+ SYNTAX INTEGER {
+ auto(1),
+ dot11a(2),
+ dot11b(3),
+ dot11g(4),
+ fh(5),
+ turboA(6),
+ turboG(7),
+ sturboA(8),
+ dot11na(9),
+ dot11ng(10),
+ ofdmHalf(11),
+ ofdmQuarter(12)
+ }
+
+-- ---------------------------------------------------------- --
+-- Subtrees in the Begemot Wireless MIB
+-- ---------------------------------------------------------- --
+begemotWlanNotifications OBJECT IDENTIFIER ::= { begemotWlan 0 }
+
+begemotWlanInterface OBJECT IDENTIFIER ::= { begemotWlan 1 }
+
+begemotWlanScanning OBJECT IDENTIFIER ::= { begemotWlan 2 }
+
+begemotWlanStatistics OBJECT IDENTIFIER ::= { begemotWlan 3 }
+
+begemotWlanWep OBJECT IDENTIFIER ::= { begemotWlan 4 }
+
+begemotWlanMACAccessControl OBJECT IDENTIFIER ::= { begemotWlan 5 }
+
+begemotWlanMeshRouting OBJECT IDENTIFIER ::= { begemotWlan 6 }
+
+-- ---------------------------------------------------------- --
+-- begemotWlanMultimedia OBJECT IDENTIFIER ::= { begemotWlan 7 }
+-- ---------------------------------------------------------- --
+
+-- ---------------------------------------------------------- --
+-- Cloned wireless interfaces' database
+-- ---------------------------------------------------------- --
+wlanInterfaceTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF WlanInterfaceEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains the list of cloned wireless
+ interfaces created on the system."
+ ::= { begemotWlanInterface 1 }
+
+wlanInterfaceEntry OBJECT-TYPE
+ SYNTAX WlanInterfaceEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Information for a cloned wireless interface."
+ INDEX { wlanIfaceName }
+ ::= { wlanInterfaceTable 1 }
+
+WlanInterfaceEntry ::= SEQUENCE {
+ wlanIfaceIndex InterfaceIndex,
+ wlanIfaceName DisplayString,
+ wlanParentIfName DisplayString,
+ wlanIfaceOperatingMode INTEGER,
+ wlanIfaceFlags BITS,
+ wlanIfaceBssid MacAddress,
+ wlanIfaceLocalAddress MacAddress,
+ wlanIfaceStatus RowStatus,
+ wlanIfaceState INTEGER
+}
+
+wlanIfaceIndex OBJECT-TYPE
+ SYNTAX InterfaceIndex
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The ifIndex of this cloned wireless interface."
+ ::= { wlanInterfaceEntry 1 }
+
+wlanIfaceName OBJECT-TYPE
+ SYNTAX DisplayString (SIZE(1..32))
+ MAX-ACCESS read-create
+ STATUS current
+ DESCRIPTION
+ "The name of this cloned wireless interface."
+ ::= { wlanInterfaceEntry 2 }
+
+wlanParentIfName OBJECT-TYPE
+ SYNTAX DisplayString (SIZE(1..32))
+ MAX-ACCESS read-create
+ STATUS current
+ DESCRIPTION
+ "The name of this cloned wireless interface's parent hardware
+ interface."
+ ::= { wlanInterfaceEntry 3 }
+
+wlanIfaceOperatingMode OBJECT-TYPE
+ SYNTAX INTEGER {
+ ibss(0),
+ station(1),
+ wds(2),
+ adhocDemo(3),
+ hostAp(4),
+ monitor(5),
+ meshPoint(6),
+ tdma(7)
+ }
+ MAX-ACCESS read-create
+ STATUS current
+ DESCRIPTION
+ "The desired operating mode of the cloned wireless interface."
+ DEFVAL { station }
+ ::= { wlanInterfaceEntry 4 }
+
+wlanIfaceFlags OBJECT-TYPE
+ SYNTAX BITS {
+ uniqueBssid(1),
+ noBeacons(2),
+ wdsLegacy(3)
+ }
+ MAX-ACCESS read-create
+ STATUS current
+ DESCRIPTION
+ "Flags per cloned wireless interface used during creation."
+ ::= { wlanInterfaceEntry 5 }
+
+wlanIfaceBssid OBJECT-TYPE
+ SYNTAX MacAddress
+ MAX-ACCESS read-create
+ STATUS current
+ DESCRIPTION
+ "The BSSID assigned to a cloned wireless interface operating in
+ WDS mode."
+ ::= { wlanInterfaceEntry 6 }
+
+wlanIfaceLocalAddress OBJECT-TYPE
+ SYNTAX MacAddress
+ MAX-ACCESS read-create
+ STATUS current
+ DESCRIPTION
+ "The unique local MAC address assigned to the cloned wireless
+ interface during creation."
+ ::= { wlanInterfaceEntry 7 }
+
+wlanIfaceStatus OBJECT-TYPE
+ SYNTAX RowStatus
+ MAX-ACCESS read-create
+ STATUS current
+ DESCRIPTION
+ "This column allows creation or deletion of cloned wireless
+ interfaces."
+ ::= { wlanInterfaceEntry 8 }
+
+wlanIfaceState OBJECT-TYPE
+ SYNTAX INTEGER {
+ up(1),
+ down(2)
+ }
+ MAX-ACCESS read-create
+ STATUS current
+ DESCRIPTION
+ "The operating state of the interface."
+ ::= { wlanInterfaceEntry 9 }
+
+wlanIfParentTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF WlanIfParentEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains information about the parent hardware
+ interface of every cloned wireless interface in the system."
+ ::= { begemotWlanInterface 2 }
+
+wlanIfParentEntry OBJECT-TYPE
+ SYNTAX WlanIfParentEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Information for the parent hardware interface of a cloned
+ wireless interface."
+ AUGMENTS { wlanInterfaceEntry }
+ ::= { wlanIfParentTable 1 }
+
+WlanIfParentEntry ::= SEQUENCE {
+ wlanIfParentDriverCapabilities BITS,
+ wlanIfParentCryptoCapabilities BITS,
+ wlanIfParentHTCapabilities BITS
+}
+
+wlanIfParentDriverCapabilities OBJECT-TYPE
+ SYNTAX BITS {
+ station(1),
+ ieee8023encap(2),
+ athFastFrames(3),
+ athTurbo(4),
+ ibss(5),
+ pmgt(6),
+ hostAp(7),
+ ahDemo(8),
+ swRetry(9),
+ txPmgt(10),
+ shortSlot(11),
+ shortPreamble(12),
+ monitor(13),
+ dfs(14),
+ mbss(15),
+ wpa1(16),
+ wpa2(17),
+ burst(18),
+ wme(19),
+ wds(20),
+ bgScan(21),
+ txFrag(22),
+ tdma(23)
+ }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The driver capabilities of this cloned interface's parent."
+ ::= { wlanIfParentEntry 1 }
+
+wlanIfParentCryptoCapabilities OBJECT-TYPE
+ SYNTAX BITS {
+ wep(1),
+ tkip(2),
+ aes(3),
+ aesCcm(4),
+ tkipMic(5),
+ ckip(6)
+ }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The hardware cryptographic capabilities of this cloned
+ interface's parent."
+ ::= { wlanIfParentEntry 2 }
+
+wlanIfParentHTCapabilities OBJECT-TYPE
+ SYNTAX BITS {
+ ldpc(1),
+ chwidth40(2),
+ greenField(3),
+ shortGi20(4),
+ shortGi40(5),
+ txStbc(6),
+ delba(7),
+ amsdu7935(8),
+ dssscck40(9),
+ psmp(10),
+ fortyMHzIntolerant(11),
+ lsigTxOpProt(12),
+ htcAmpdu(13),
+ htcAmsdu(14),
+ htcHt(15),
+ htcSmps(16),
+ htcRifs(17)
+ }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The hardware High Throughput capabilities of this cloned
+ interface's parent."
+ ::= { wlanIfParentEntry 3 }
+
+wlanIfaceConfigTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF WlanIfaceConfigEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains a list of configuration parameters per
+ cloned wireless interface. Some of the parameters may not be
+ applicable depending on the underlying device's hardware
+ capabilities and operating mode of the virtual interface."
+ ::= { begemotWlanInterface 3 }
+
+wlanIfaceConfigEntry OBJECT-TYPE
+ SYNTAX WlanIfaceConfigEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A list of configuration parameters for a cloned wireless interface."
+ AUGMENTS { wlanInterfaceEntry }
+ ::= { wlanIfaceConfigTable 1 }
+
+WlanIfaceConfigEntry ::= SEQUENCE {
+ wlanIfacePacketBurst TruthValue,
+ wlanIfaceCountryCode OCTET STRING,
+ wlanIfaceRegDomain INTEGER,
+ wlanIfaceDesiredSsid OCTET STRING,
+ wlanIfaceDesiredChannel INTEGER,
+ wlanIfaceDynamicFreqSelection TruthValue,
+ wlanIfaceFastFrames TruthValue,
+ wlanIfaceDturbo TruthValue,
+ wlanIfaceTxPower INTEGER,
+ wlanIfaceFragmentThreshold INTEGER,
+ wlanIfaceRTSThreshold INTEGER,
+ wlanIfaceWlanPrivacySubscribe TruthValue,
+-- Parameters for station mode
+ wlanIfaceBgScan TruthValue,
+ wlanIfaceBgScanIdle INTEGER,
+ wlanIfaceBgScanInterval INTEGER,
+ wlanIfaceBeaconMissedThreshold INTEGER,
+ wlanIfaceDesiredBssid MacAddress,
+ wlanIfaceRoamingMode INTEGER,
+-- Additional parameters when operating in host-ap/ad-hoc mode
+ wlanIfaceDot11d TruthValue,
+ wlanIfaceDot11h TruthValue,
+ wlanIfaceDynamicWds TruthValue,
+ wlanIfacePowerSave TruthValue,
+ wlanIfaceApBridge TruthValue,
+ wlanIfaceBeaconInterval INTEGER,
+ wlanIfaceDtimPeriod INTEGER,
+ wlanIfaceHideSsid TruthValue,
+ wlanIfaceInactivityProccess TruthValue,
+ wlanIfaceDot11gProtMode INTEGER,
+ wlanIfaceDot11gPureMode TruthValue,
+ wlanIfaceDot11nPureMode TruthValue,
+ wlanIfaceDot11nAmpdu INTEGER,
+ wlanIfaceDot11nAmpduDensity INTEGER,
+ wlanIfaceDot11nAmpduLimit INTEGER,
+ wlanIfaceDot11nAmsdu INTEGER,
+ wlanIfaceDot11nAmsduLimit INTEGER,
+ wlanIfaceDot11nHighThroughput TruthValue,
+ wlanIfaceDot11nHTCompatible TruthValue,
+ wlanIfaceDot11nHTProtMode INTEGER,
+ wlanIfaceDot11nRIFS TruthValue,
+ wlanIfaceDot11nShortGI TruthValue,
+ wlanIfaceDot11nSMPSMode INTEGER,
+-- Parameters when operating in tdma mode
+ wlanIfaceTdmaSlot INTEGER,
+ wlanIfaceTdmaSlotCount INTEGER,
+ wlanIfaceTdmaSlotLength INTEGER,
+ wlanIfaceTdmaBeaconInterval INTEGER
+}
+
+wlanIfacePacketBurst OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object controls whether packet bursting is
+ enabled on the interface."
+ ::= { wlanIfaceConfigEntry 1 }
+
+wlanIfaceCountryCode OBJECT-TYPE
+ SYNTAX OCTET STRING (SIZE(3))
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object controls the country regulatory
+ constraints for operation of this wireless interface. The first
+ two octets of this string is the two character country code as
+ described in ISO/IEC 3166-1. The third octet shall contain one
+ of the following:
+
+ 1. an ASCII space character, if the regulations under which the
+ interface is operating include all environments in the specified
+ country.
+
+ 2. an ASCII 'O' character, if the country's regulations are for
+ Outdoor environment only.
+
+ 3. an ASCII 'I' character, if the country's regulations are for
+ Indoor environment only."
+ ::= { wlanIfaceConfigEntry 2 }
+
+wlanIfaceRegDomain OBJECT-TYPE
+ SYNTAX WlanRegDomainCode
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "This object specifies the regulatory domain to use when calculating
+ the regulatory constraints for operation of the interface."
+ ::= { wlanIfaceConfigEntry 3 }
+
+wlanIfaceDesiredSsid OBJECT-TYPE
+ SYNTAX OCTET STRING (SIZE(0..32))
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The desired SSID for the interface as an ASCII string.
+ Specifying an empty string shall remove the current configured
+ SSID."
+ ::= { wlanIfaceConfigEntry 4 }
+
+wlanIfaceDesiredChannel OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The desired operating channel for this interface. The value of this
+ column is the channel index (wlanIfaceChannelId) of the corresponding
+ entry from the wlanIfaceChannelTable. The interface status must be
+ down so that the current operating channel may be set properly."
+ ::= { wlanIfaceConfigEntry 5 }
+
+wlanIfaceDynamicFreqSelection OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies whether Dynamic Frequency
+ Selection (DFS) as specified in 802.11h is enabled on an
+ interface that supports 802.11h and DFS."
+ DEFVAL { false }
+ ::= { wlanIfaceConfigEntry 6 }
+
+wlanIfaceFastFrames OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object controls whether use of Atheros Fast
+ Frames is enabled when when communicating with another Fast
+ Frames-capable station. The value is only meaningful for
+ interfaces that support Atheros Fast Frames."
+ ::= { wlanIfaceConfigEntry 7 }
+
+wlanIfaceDturbo OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object controls whether use of Atheros Dynamic
+ Turbo mode is enabled when when communicating with another Dynamic
+ Turbo-capable station. The value is only meaningful for interfaces
+ that support Atheros Dynamic Turbo mode."
+ ::= { wlanIfaceConfigEntry 8 }
+
+wlanIfaceTxPower OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object controls the power used to transmit
+ frames. Accepted values are in units of one tenths of a dBm in
+ steps of .5 dBm, e.g setting the value of this object to 155
+ results in 15.5 dBm transmit power configured on the interface."
+ ::= { wlanIfaceConfigEntry 9 }
+
+wlanIfaceFragmentThreshold OBJECT-TYPE
+ SYNTAX INTEGER (256..2346)
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object controls the threshold for which
+ transmitted frames are broken into fragments. Setting the value
+ of this object to 2346 will disable transmit fragmentation."
+ DEFVAL { 2346 }
+ ::= { wlanIfaceConfigEntry 10 }
+
+wlanIfaceRTSThreshold OBJECT-TYPE
+ SYNTAX INTEGER (1..2346)
+ UNITS "bytes"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object controls the threshold for which
+ transmitted frames are preceded by transmission of an RTS
+ control frame. Setting the value of this object to 2346 will
+ disable transmission of RTS frames."
+ DEFVAL { 2346 }
+ ::= { wlanIfaceConfigEntry 11 }
+
+wlanIfaceWlanPrivacySubscribe OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies whether Wireless Privacy
+ Subscriber support is enabled on the interface."
+ ::= { wlanIfaceConfigEntry 12 }
+
+wlanIfaceBgScan OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies whether background scanning
+ is enabled for an interface operating in station mode."
+ ::= { wlanIfaceConfigEntry 13 }
+
+wlanIfaceBgScanIdle OBJECT-TYPE
+ SYNTAX INTEGER
+ UNITS "milliseconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies the minimum time a station must
+ be idle before a background scan is initiated on an interface
+ operating in station mode."
+ DEFVAL { 250 }
+ ::= { wlanIfaceConfigEntry 14 }
+
+wlanIfaceBgScanInterval OBJECT-TYPE
+ SYNTAX INTEGER
+ UNITS "seconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies the interval at which background
+ scanning is attempted when operating in station mode."
+ DEFVAL { 300 }
+ ::= { wlanIfaceConfigEntry 15 }
+
+wlanIfaceBeaconMissedThreshold OBJECT-TYPE
+ SYNTAX INTEGER (1..255)
+ UNITS "frames"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies the number of consecutive missed
+ beacons before an interface operating in station mode will attempt
+ to search for a new access point."
+ DEFVAL { 7 }
+ ::= { wlanIfaceConfigEntry 16 }
+
+wlanIfaceDesiredBssid OBJECT-TYPE
+ SYNTAX MacAddress
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies the MAC address of the desired
+ access point to use when an interface is operating as a station."
+ ::= { wlanIfaceConfigEntry 17 }
+
+wlanIfaceRoamingMode OBJECT-TYPE
+ SYNTAX INTEGER {
+ device(1),
+ auto(2),
+ manual(3)
+ }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies the desired system behavior
+ when the interface is operating as a station and the communication
+ with the current access point is broken."
+ DEFVAL { auto }
+ ::= { wlanIfaceConfigEntry 18 }
+
+wlanIfaceDot11d OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies whether 802.11d specification
+ support is enabled."
+ DEFVAL { false }
+ ::= { wlanIfaceConfigEntry 19 }
+
+wlanIfaceDot11h OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies whether 802.11h support
+ including spectrum management is enabled. The value is only
+ meaningfull for interfaces that support 802.11h specification."
+ DEFVAL { false }
+ ::= { wlanIfaceConfigEntry 20 }
+
+wlanIfaceDynamicWds OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies whether Dynamic WDS (DWDS)
+ support is enabled. The value is only meaningful for interfaces
+ that support Dynamic WDS."
+ ::= { wlanIfaceConfigEntry 21 }
+
+wlanIfacePowerSave OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies whether powersave operation
+ is enabled. The value is only meaningful for interfaces that
+ support powersave operation."
+ ::= { wlanIfaceConfigEntry 22 }
+
+wlanIfaceApBridge OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies whether packets between
+ wireless clients will be passed directly by an interface
+ operating in host ap mode. Disabling it may be useful in
+ situations when traffic between wireless clients needs to be
+ processed with packet filtering."
+ DEFVAL { true }
+ ::= { wlanIfaceConfigEntry 23 }
+
+wlanIfaceBeaconInterval OBJECT-TYPE
+ SYNTAX INTEGER (25..1000)
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies the interval at with beacon
+ frames are sent when an interface is operating in ad-hoc or ap
+ mode. The beacon interval is specified in TU's (1024 usecs)."
+ DEFVAL { 100 }
+ ::= { wlanIfaceConfigEntry 24 }
+
+wlanIfaceDtimPeriod OBJECT-TYPE
+ SYNTAX INTEGER (1..15)
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies the DTIM period at which
+ buffered multicast data frames are transmitted by an interface
+ operating in host ap mode. Its value indicates the number of
+ beacon intervals between DTIM."
+ DEFVAL { 1 }
+ ::= { wlanIfaceConfigEntry 25 }
+
+wlanIfaceHideSsid OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies whether broadcasting of the
+ SSID in beacon frames and responding to undirected probe request
+ frames is enabled for an interface operating in ap mode."
+ DEFVAL { false }
+ ::= { wlanIfaceConfigEntry 26 }
+
+wlanIfaceInactivityProccess OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies whether inactivity processing
+ for associated station on an interface operating in ap mode is
+ enabled."
+ DEFVAL { true }
+ ::= { wlanIfaceConfigEntry 27 }
+
+wlanIfaceDot11gProtMode OBJECT-TYPE
+ SYNTAX INTEGER {
+ off(1),
+ cts(2),
+ rtscts(3)
+ }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies the technique used for
+ protecting OFDM frames in a mixed 11b/11g network."
+ ::= { wlanIfaceConfigEntry 28 }
+
+wlanIfaceDot11gPureMode OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies whether only 802.11g-capable
+ stations will be allowed to associate to an interface operating
+ as access point in 802.11g mode."
+ ::= { wlanIfaceConfigEntry 29 }
+
+wlanIfaceDot11nPureMode OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies whether only HT-capable
+ stations will be allowed to associate to an interface operating
+ as access point in 802.11n mode."
+ ::= { wlanIfaceConfigEntry 30 }
+
+wlanIfaceDot11nAmpdu OBJECT-TYPE
+ SYNTAX WlanIfaceDot11nPduType
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies whether sending and
+ receiving of AMPDU frames is enabled on an interface
+ operating in 802.11n mode."
+ DEFVAL { txAndRx }
+ ::= { wlanIfaceConfigEntry 31 }
+
+wlanIfaceDot11nAmpduDensity OBJECT-TYPE
+ SYNTAX INTEGER (0|25|50|100|200|400|800|1600)
+ UNITS "1/100ths-of-microsecond"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies the AMPDU density parameter
+ for an interface operating in 802.11n mode."
+ ::= { wlanIfaceConfigEntry 32 }
+
+wlanIfaceDot11nAmpduLimit OBJECT-TYPE
+ SYNTAX INTEGER (8192|16384|32768|65536)
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies a limit on the AMPDU packet
+ size for receiving AMPDU frames for an interface operating in
+ 802.11n mode."
+ ::= { wlanIfaceConfigEntry 33 }
+
+wlanIfaceDot11nAmsdu OBJECT-TYPE
+ SYNTAX WlanIfaceDot11nPduType
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies whether sending and receiving
+ of AMSDU frames is enabled on an interface operating in 802.11n
+ mode."
+ DEFVAL { rxOnly }
+ ::= { wlanIfaceConfigEntry 34 }
+
+wlanIfaceDot11nAmsduLimit OBJECT-TYPE
+ SYNTAX INTEGER (3839|7935)
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies a limit on the AMSDU packet
+ size when sending and receiving AMSDU frames for an interface
+ operating in 802.11n mode."
+ ::= { wlanIfaceConfigEntry 35 }
+
+wlanIfaceDot11nHighThroughput OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies whether use of High Throughput
+ (HT) is enabled for an interface operating in 802.11n mode."
+ DEFVAL { true }
+ ::= { wlanIfaceConfigEntry 36 }
+
+wlanIfaceDot11nHTCompatible OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies whether use of compatibility
+ support for pre-802.11n devices is enabled for an interface
+ operating in 802.11n mode."
+ DEFVAL { true }
+ ::= { wlanIfaceConfigEntry 37 }
+
+wlanIfaceDot11nHTProtMode OBJECT-TYPE
+ SYNTAX INTEGER {
+ off(1),
+ rts(2)
+ }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies the technique used for
+ protecting HT frames in a mixed legacy/HT network for interfaces
+ operating in 802.11n mode."
+ DEFVAL { rts }
+ ::= { wlanIfaceConfigEntry 38 }
+
+wlanIfaceDot11nRIFS OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies whether Reduced InterFrame
+ Spacing (RIFS) is enabled for an interface operating in 802.11n
+ mode on an HT channel."
+ ::= { wlanIfaceConfigEntry 39 }
+
+wlanIfaceDot11nShortGI OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies whether use of Short Guard
+ Interval is enabled on an interface operating in 802.11n mode
+ on an HT channel."
+ ::= { wlanIfaceConfigEntry 40 }
+
+wlanIfaceDot11nSMPSMode OBJECT-TYPE
+ SYNTAX INTEGER {
+ disabled(1),
+ static(2),
+ dynamic(3)
+ }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies whether use of Spatial Multiplexing
+ Power Save (SMPS) is enabled on an interface operating in 802.11n mode
+ and whether SMPS mode is set to Static or Dynamic. The value is only
+ meaningfull for interfaces that support SMPS."
+ ::= { wlanIfaceConfigEntry 41 }
+
+wlanIfaceTdmaSlot OBJECT-TYPE
+ SYNTAX INTEGER (0..2)
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies the slot configuration to use
+ when operating in TDMA mode."
+ ::= { wlanIfaceConfigEntry 42 }
+
+wlanIfaceTdmaSlotCount OBJECT-TYPE
+ SYNTAX INTEGER (0..2)
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies the number of slots to use to
+ setup a BSS for an interface operating in TDMA mode."
+ ::= { wlanIfaceConfigEntry 43 }
+
+wlanIfaceTdmaSlotLength OBJECT-TYPE
+ SYNTAX INTEGER (150..65000)
+ UNITS "microseconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies a slot length that each station
+ has when a BSS is setup by an interface operating in TDMA mode."
+ DEFVAL { 10000 }
+ ::= { wlanIfaceConfigEntry 44 }
+
+wlanIfaceTdmaBeaconInterval OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies the number of superframes at
+ which a beacon frame is sent to synchronize the TDMA slot timing
+ for interfaces operating in TDMA mode."
+ DEFVAL { 5 }
+ ::= { wlanIfaceConfigEntry 45 }
+
+wlanIfacePeerTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF WlanIfacePeerEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains information about the associated stations
+ for an interface operating as an access point, or the stations
+ identified as neighbors in the IBSS for an interface operating in
+ adhoc mode."
+ ::= { begemotWlanInterface 4 }
+
+wlanIfacePeerEntry OBJECT-TYPE
+ SYNTAX WlanIfacePeerEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "An entry that contains information for the associated stations
+ for an interface operating as an access point, or the neighboring
+ stations of an interface in adhoc mode."
+ INDEX { wlanIfaceName, wlanIfacePeerAddress }
+ ::= { wlanIfacePeerTable 1 }
+
+WlanIfacePeerEntry ::= SEQUENCE {
+ wlanIfacePeerAddress MacAddress,
+ wlanIfacePeerAssociationId INTEGER,
+ wlanIfacePeerVlanTag INTEGER,
+ wlanIfacePeerFrequency INTEGER,
+ wlanIfacePeerCurrentTXRate INTEGER,
+ wlanIfacePeerRxSignalStrength INTEGER,
+ wlanIfacePeerIdleTimer INTEGER,
+ wlanIfacePeerTxSequenceNo INTEGER,
+ wlanIfacePeerRxSequenceNo INTEGER,
+ wlanIfacePeerTxPower INTEGER,
+ wlanIfacePeerCapabilities BITS,
+ wlanIfacePeerFlags BITS
+}
+
+wlanIfacePeerAddress OBJECT-TYPE
+ SYNTAX MacAddress
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The MAC address of this peer."
+ ::= { wlanIfacePeerEntry 1 }
+
+wlanIfacePeerAssociationId OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The ID of the associacition with this peer."
+ ::= { wlanIfacePeerEntry 2 }
+
+wlanIfacePeerVlanTag OBJECT-TYPE
+ SYNTAX INTEGER (0..4096)
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The Vlan Tag for traffic to/from this peer."
+ ::= { wlanIfacePeerEntry 3 }
+
+wlanIfacePeerFrequency OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The operating frequency for the link with this peer."
+ ::= { wlanIfacePeerEntry 4 }
+
+wlanIfacePeerCurrentTXRate OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The current transmit rate for this peer."
+ ::= { wlanIfacePeerEntry 5 }
+
+wlanIfacePeerRxSignalStrength OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The average receive signal strength for this peer."
+ ::= { wlanIfacePeerEntry 6 }
+
+wlanIfacePeerIdleTimer OBJECT-TYPE
+ SYNTAX INTEGER
+ UNITS "seconds"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The value of this peer's inactivity timer."
+ ::= { wlanIfacePeerEntry 7 }
+
+wlanIfacePeerTxSequenceNo OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The last sequence number transmitted to this peer."
+ ::= { wlanIfacePeerEntry 8 }
+
+wlanIfacePeerRxSequenceNo OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The last sequence number received from this peer."
+ ::= { wlanIfacePeerEntry 9 }
+
+wlanIfacePeerTxPower OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The transmit power limit for this peer."
+ ::= { wlanIfacePeerEntry 10 }
+
+wlanIfacePeerCapabilities OBJECT-TYPE
+ SYNTAX WlanPeerCapabilityFlags
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The capabilities advertised by this peer."
+ ::= { wlanIfacePeerEntry 11 }
+
+wlanIfacePeerFlags OBJECT-TYPE
+ SYNTAX BITS {
+ authorizedForData(1),
+ qosEnabled(2),
+ erpEnabled(3),
+ powerSaveMode(4),
+ authRefHeld(5),
+ htEnabled(6),
+ htCompat(7),
+ wpsAssoc(8),
+ tsnAssoc(9),
+ ampduRx(10),
+ ampduTx(11),
+ mimoPowerSave(12),
+ sendRts(13),
+ rifs(14),
+ shortGiHT20(15),
+ shortGiHT40(16),
+ amsduRx(17),
+ amsduTx(18)
+ }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The peer state flags."
+ ::= { wlanIfacePeerEntry 12 }
+
+wlanIfaceChannelTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF WlanIfaceChannelEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains information about the active channels for
+ the cloned wireless interfaces in the system."
+ ::= { begemotWlanInterface 5 }
+
+wlanIfaceChannelEntry OBJECT-TYPE
+ SYNTAX WlanIfaceChannelEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "An entry that contains active channel information for the wireless
+ interface."
+ INDEX { wlanIfaceName, wlanIfaceChannelId }
+ ::= { wlanIfaceChannelTable 1 }
+
+WlanIfaceChannelEntry ::= SEQUENCE {
+ wlanIfaceChannelId INTEGER,
+ wlanIfaceChannelIeeeId INTEGER,
+ wlanIfaceChannelType INTEGER,
+ wlanIfaceChannelFlags BITS,
+ wlanIfaceChannelFrequency INTEGER,
+ wlanIfaceChannelMaxRegPower INTEGER,
+ wlanIfaceChannelMaxTxPower INTEGER,
+ wlanIfaceChannelMinTxPower INTEGER,
+ wlanIfaceChannelState BITS,
+ wlanIfaceChannelHTExtension INTEGER,
+ wlanIfaceChannelMaxAntennaGain INTEGER
+}
+
+wlanIfaceChannelId OBJECT-TYPE
+ SYNTAX INTEGER (1..1536)
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "The index of this channel entry."
+ ::= { wlanIfaceChannelEntry 1 }
+
+wlanIfaceChannelIeeeId OBJECT-TYPE
+ SYNTAX INTEGER (1..256)
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The channel IEEE ID."
+ ::= { wlanIfaceChannelEntry 2 }
+
+wlanIfaceChannelType OBJECT-TYPE
+ SYNTAX INTEGER {
+ fhss(1),
+ dot11a(2),
+ dot11b(3),
+ dot11g(4),
+ tenMHz(5),
+ fiveMHz(6),
+ turbo(7),
+ ht(8)
+ }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The operating channel type for this entry."
+ ::= { wlanIfaceChannelEntry 3 }
+
+wlanIfaceChannelFlags OBJECT-TYPE
+ SYNTAX BITS {
+ turbo(1),
+ cck(2),
+ ofdm(3),
+ spectrum2Ghz(4),
+ spectrum5Ghz(5),
+ passiveScan(6),
+ dynamicCckOfdm(7),
+ gfsk(8),
+ spectrum900Mhz(9),
+ dot11aStaticTurbo(10),
+ halfRate(11),
+ quarterRate(12),
+ ht20(13),
+ ht40u(14),
+ ht40d(15),
+ dfs(16),
+ xmit4ms(17),
+ noAdhoc(18),
+ noHostAp(19),
+ dot11d(20)
+ }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The channel flags."
+ ::= { wlanIfaceChannelEntry 4 }
+
+wlanIfaceChannelFrequency OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The channel frequency setting in MHz."
+ ::= { wlanIfaceChannelEntry 5 }
+
+wlanIfaceChannelMaxRegPower OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The maximum regulatory tx power in dBm for this channel."
+ ::= { wlanIfaceChannelEntry 6 }
+
+wlanIfaceChannelMaxTxPower OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The maximum tx power in units of .5 dBm for this channel."
+ ::= { wlanIfaceChannelEntry 7 }
+
+wlanIfaceChannelMinTxPower OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The minimum tx power in units of .5 dBm for this channel."
+ ::= { wlanIfaceChannelEntry 8 }
+
+wlanIfaceChannelState OBJECT-TYPE
+ SYNTAX BITS {
+ radar(1),
+ cacDone(2),
+ interferenceDetected(3),
+ radarClear(4)
+ }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The channel dynamic state."
+ ::= { wlanIfaceChannelEntry 9 }
+
+wlanIfaceChannelHTExtension OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The HT40 extension channel number."
+ ::= { wlanIfaceChannelEntry 10 }
+
+wlanIfaceChannelMaxAntennaGain OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The maximum antenna gain in units of .5 dBm."
+ ::= { wlanIfaceChannelEntry 11 }
+
+-- ---------------------------------------------------------- --
+-- The Scan requests/results for cloned wireless interfaces
+-- ---------------------------------------------------------- --
+
+wlanScanConfigTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF WlanScanConfigEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains a configuration for channel scanning
+ initiated via SNMP."
+ ::= { begemotWlanScanning 1 }
+
+wlanScanConfigEntry OBJECT-TYPE
+ SYNTAX WlanScanConfigEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Per cloned wireless interface channel scan configuration data.
+ The entry will be empty if no scans were initiated via SNMP."
+ INDEX { wlanIfaceName }
+ ::= { wlanScanConfigTable 1 }
+
+WlanScanConfigEntry ::= SEQUENCE {
+ wlanScanFlags BITS,
+ wlanScanDuration INTEGER,
+ wlanScanMinChannelDwellTime INTEGER,
+ wlanScanMaxChannelDwellTime INTEGER,
+ wlanScanConfigStatus INTEGER
+}
+
+wlanScanFlags OBJECT-TYPE
+ SYNTAX BITS {
+ noSelection(1),
+ activeScan(2),
+ pickFirst(3),
+ backgroundScan(4),
+ once(5),
+ noBroadcast(6),
+ noAutoSequencing(7),
+ flushCashe(8),
+ chechCashe(9)
+ }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "Desired flags for the channel scan."
+ ::= { wlanScanConfigEntry 1 }
+
+wlanScanDuration OBJECT-TYPE
+ SYNTAX INTEGER (1..2147483647)
+ UNITS "milliseconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The desired duration of the scan. Setting the value of this object
+ to the highest allowed value will initiate an infinite scan."
+ ::= { wlanScanConfigEntry 2 }
+
+wlanScanMinChannelDwellTime OBJECT-TYPE
+ SYNTAX INTEGER
+ UNITS "milliseconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The minimum channel dwelltime for this scan."
+ ::= { wlanScanConfigEntry 3 }
+
+wlanScanMaxChannelDwellTime OBJECT-TYPE
+ SYNTAX INTEGER
+ UNITS "milliseconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The maximum channel dwelltime for this scan."
+ ::= { wlanScanConfigEntry 4 }
+
+wlanScanConfigStatus OBJECT-TYPE
+ SYNTAX INTEGER {
+ unknown(0),
+ notStarted(1),
+ running(2),
+ finished(3),
+ cancel(4)
+ }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "This object is used to initiate or cancel channel scanning on the cloned
+ interface via SNMP. Setting its value to running(2) will initiate channel
+ scanning on the cloned interface, while setting the value to cancel will
+ cancel the current ongoing scan. All other values should be returned in
+ GET operations only."
+ ::= { wlanScanConfigEntry 5 }
+
+wlanScanResultsTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF WlanScanResultsEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains scan results for a virtual wireless interface."
+ ::= { begemotWlanScanning 2 }
+
+wlanScanResultsEntry OBJECT-TYPE
+ SYNTAX WlanScanResultsEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Per virtual wireless interface channel scan results data."
+ INDEX { wlanIfaceName, wlanScanResultID, wlanScanResultBssid }
+ ::= { wlanScanResultsTable 1 }
+
+WlanScanResultsEntry ::= SEQUENCE {
+ wlanScanResultID OCTET STRING,
+ wlanScanResultBssid MacAddress,
+ wlanScanResultChannel INTEGER,
+ wlanScanResultRate INTEGER,
+ wlanScanResultNoise INTEGER,
+ wlanScanResultBeaconInterval INTEGER,
+ wlanScanResultCapabilities BITS
+}
+
+wlanScanResultID OBJECT-TYPE
+ SYNTAX OCTET STRING (SIZE(0..32))
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The SSID/MESH ID for this scan result entry."
+ ::= { wlanScanResultsEntry 1 }
+
+wlanScanResultBssid OBJECT-TYPE
+ SYNTAX MacAddress
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The BSSID for this scan result entry."
+ ::= { wlanScanResultsEntry 2 }
+
+wlanScanResultChannel OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The operating channel for this scan result entry."
+ ::= { wlanScanResultsEntry 3 }
+
+wlanScanResultRate OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The operating rate of this scan result entry."
+ ::= { wlanScanResultsEntry 4 }
+
+wlanScanResultNoise OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The channel noise for this scan result entry."
+ ::= { wlanScanResultsEntry 5 }
+
+wlanScanResultBeaconInterval OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The beacon interval reported for this scan result entry."
+ ::= { wlanScanResultsEntry 6 }
+
+wlanScanResultCapabilities OBJECT-TYPE
+ SYNTAX WlanPeerCapabilityFlags
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The capabilities advertised for this scan result entry."
+ ::= { wlanScanResultsEntry 7 }
+
+wlanIfRoamParamsTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF WlanIfRoamParamsEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains the parameters that govern the roaming
+ operation on a wireless interface."
+ ::= { begemotWlanInterface 6 }
+
+wlanIfRoamParamsEntry OBJECT-TYPE
+ SYNTAX WlanIfRoamParamsEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "An entry that contains the roaming parameters of a wireless
+ interface."
+ INDEX { wlanIfaceName, wlanIfRoamPhyMode }
+ ::= { wlanIfRoamParamsTable 1 }
+
+WlanIfRoamParamsEntry ::= SEQUENCE {
+ wlanIfRoamPhyMode INTEGER,
+ wlanIfRoamRxSignalStrength INTEGER,
+ wlanIfRoamTxRateThreshold INTEGER
+}
+
+wlanIfRoamPhyMode OBJECT-TYPE
+ SYNTAX WlanIfPhyMode
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "The PHY mode for this roaming parameters entry."
+ ::= { wlanIfRoamParamsEntry 1 }
+
+wlanIfRoamRxSignalStrength OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The Receive Signal Strength for this roaming parameters entry."
+ ::= { wlanIfRoamParamsEntry 2 }
+
+wlanIfRoamTxRateThreshold OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The transmit rate threshold value for this roaming parameters
+ entry in Mb/s or MCS."
+ ::= { wlanIfRoamParamsEntry 3 }
+
+wlanIfTxParamsTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF WlanIfTxParamsEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains the parameters that govern the transmit
+ operation on a wireless interface."
+ ::= { begemotWlanInterface 7 }
+
+wlanIfTxParamsEntry OBJECT-TYPE
+ SYNTAX WlanIfTxParamsEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "An entry that contains the transmit parameters of a wireless
+ interface."
+ INDEX { wlanIfaceName, wlanIfTxPhyMode }
+ ::= { wlanIfTxParamsTable 1 }
+
+WlanIfTxParamsEntry ::= SEQUENCE {
+ wlanIfTxPhyMode INTEGER,
+ wlanIfTxUnicastRate INTEGER,
+ wlanIfTxMcastRate INTEGER,
+ wlanIfTxMgmtRate INTEGER,
+ wlanIfTxMaxRetryCount INTEGER
+}
+
+wlanIfTxPhyMode OBJECT-TYPE
+ SYNTAX WlanIfPhyMode
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "The PHY mode for this entry."
+ ::= { wlanIfTxParamsEntry 1 }
+
+wlanIfTxUnicastRate OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies a fixed rate for transmitting
+ unicast frames in this phy mode."
+ ::= { wlanIfTxParamsEntry 2 }
+
+wlanIfTxMcastRate OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies a fixed rate for transmitting
+ broadcast and multicast frames in this phy mode."
+ ::= { wlanIfTxParamsEntry 3 }
+
+wlanIfTxMgmtRate OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies a fixed rate for transmitting
+ management and/or control frames in this phy mode."
+ ::= { wlanIfTxParamsEntry 4 }
+
+wlanIfTxMaxRetryCount OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The maximum number of tries to use when sending unicast frames
+ in this phy mode."
+ ::= { wlanIfTxParamsEntry 5 }
+
+-- ---------------------------------------------------------- --
+-- The Statistics Database for Wireless interfaces
+-- ---------------------------------------------------------- --
+
+wlanIfaceStatisticsTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF WlanIfaceStatisticsEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains summary statistics for each virtual wireless
+ interface on the managed device."
+ ::= { begemotWlanStatistics 1 }
+
+wlanIfaceStatisticsEntry OBJECT-TYPE
+ SYNTAX WlanIfaceStatisticsEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A list of statistics for each virtual wireless interface."
+ AUGMENTS { wlanInterfaceEntry }
+ ::= { wlanIfaceStatisticsTable 1 }
+
+WlanIfaceStatisticsEntry ::= SEQUENCE {
+ wlanStatsRxBadVersion Counter32,
+ wlanStatsRxTooShort Counter32,
+ wlanStatsRxWrongBssid Counter32,
+ wlanStatsRxDiscardedDups Counter32,
+ wlanStatsRxWrongDir Counter32,
+ wlanStatsRxDiscardMcastEcho Counter32,
+ wlanStatsRxDiscardNoAssoc Counter32,
+ wlanStatsRxWepNoPrivacy Counter32,
+ wlanStatsRxWepUnencrypted Counter32,
+ wlanStatsRxWepFailed Counter32,
+ wlanStatsRxDecapsulationFailed Counter32,
+ wlanStatsRxDiscardMgmt Counter32,
+ wlanStatsRxControl Counter32,
+ wlanStatsRxBeacon Counter32,
+ wlanStatsRxRateSetTooBig Counter32,
+ wlanStatsRxElemMissing Counter32,
+ wlanStatsRxElemTooBig Counter32,
+ wlanStatsRxElemTooSmall Counter32,
+ wlanStatsRxElemUnknown Counter32,
+ wlanStatsRxChannelMismatch Counter32,
+ wlanStatsRxDropped Counter32,
+ wlanStatsRxSsidMismatch Counter32,
+ wlanStatsRxAuthNotSupported Counter32,
+ wlanStatsRxAuthFailed Counter32,
+ wlanStatsRxAuthCM Counter32,
+ wlanStatsRxAssocWrongBssid Counter32,
+ wlanStatsRxAssocNoAuth Counter32,
+ wlanStatsRxAssocCapMismatch Counter32,
+ wlanStatsRxAssocNoRateMatch Counter32,
+ wlanStatsRxBadWpaIE Counter32,
+ wlanStatsRxDeauthenticate Counter32,
+ wlanStatsRxDisassociate Counter32,
+ wlanStatsRxUnknownSubtype Counter32,
+ wlanStatsRxFailedNoBuf Counter32,
+ wlanStatsRxBadAuthRequest Counter32,
+ wlanStatsRxUnAuthorized Counter32,
+ wlanStatsRxBadKeyId Counter32,
+ wlanStatsRxCCMPSeqViolation Counter32,
+ wlanStatsRxCCMPBadFormat Counter32,
+ wlanStatsRxCCMPFailedMIC Counter32,
+ wlanStatsRxTKIPSeqViolation Counter32,
+ wlanStatsRxTKIPBadFormat Counter32,
+ wlanStatsRxTKIPFailedMIC Counter32,
+ wlanStatsRxTKIPFailedICV Counter32,
+ wlanStatsRxDiscardACL Counter32,
+ wlanStatsTxFailedNoBuf Counter32,
+ wlanStatsTxFailedNoNode Counter32,
+ wlanStatsTxUnknownMgmt Counter32,
+ wlanStatsTxBadCipher Counter32,
+ wlanStatsTxNoDefKey Counter32,
+ wlanStatsTxFragmented Counter32,
+ wlanStatsTxFragmentsCreated Counter32,
+ wlanStatsActiveScans Counter32,
+ wlanStatsPassiveScans Counter32,
+ wlanStatsTimeoutInactivity Counter32,
+ wlanStatsCryptoNoMem Counter32,
+ wlanStatsSwCryptoTKIP Counter32,
+ wlanStatsSwCryptoTKIPEnMIC Counter32,
+ wlanStatsSwCryptoTKIPDeMIC Counter32,
+ wlanStatsCryptoTKIPCM Counter32,
+ wlanStatsSwCryptoCCMP Counter32,
+ wlanStatsSwCryptoWEP Counter32,
+ wlanStatsCryptoCipherKeyRejected Counter32,
+ wlanStatsCryptoNoKey Counter32,
+ wlanStatsCryptoDeleteKeyFailed Counter32,
+ wlanStatsCryptoUnknownCipher Counter32,
+ wlanStatsCryptoAttachFailed Counter32,
+ wlanStatsCryptoKeyFailed Counter32,
+ wlanStatsCryptoEnMICFailed Counter32,
+ wlanStatsIBSSCapMismatch Counter32,
+ wlanStatsUnassocStaPSPoll Counter32,
+ wlanStatsBadAidPSPoll Counter32,
+ wlanStatsEmptyPSPoll Counter32,
+ wlanStatsRxFFBadHdr Counter32,
+ wlanStatsRxFFTooShort Counter32,
+ wlanStatsRxFFSplitError Counter32,
+ wlanStatsRxFFDecap Counter32,
+ wlanStatsTxFFEncap Counter32,
+ wlanStatsRxBadBintval Counter32,
+ wlanStatsRxDemicFailed Counter32,
+ wlanStatsRxDefragFailed Counter32,
+ wlanStatsRxMgmt Counter32,
+ wlanStatsRxActionMgmt Counter32,
+ wlanStatsRxAMSDUTooShort Counter32,
+ wlanStatsRxAMSDUSplitError Counter32,
+ wlanStatsRxAMSDUDecap Counter32,
+ wlanStatsTxAMSDUEncap Counter32,
+ wlanStatsAMPDUBadBAR Counter32,
+ wlanStatsAMPDUOowBar Counter32,
+ wlanStatsAMPDUMovedBAR Counter32,
+ wlanStatsAMPDURxBAR Counter32,
+ wlanStatsAMPDURxOor Counter32,
+ wlanStatsAMPDURxCopied Counter32,
+ wlanStatsAMPDURxDropped Counter32,
+ wlanStatsTxDiscardBadState Counter32,
+ wlanStatsTxFailedNoAssoc Counter32,
+ wlanStatsTxClassifyFailed Counter32,
+ wlanStatsDwdsMcastDiscard Counter32,
+ wlanStatsHTAssocRejectNoHT Counter32,
+ wlanStatsHTAssocDowngrade Counter32,
+ wlanStatsHTAssocRateMismatch Counter32,
+ wlanStatsAMPDURxAge Counter32,
+ wlanStatsAMPDUMoved Counter32,
+ wlanStatsADDBADisabledReject Counter32,
+ wlanStatsADDBANoRequest Counter32,
+ wlanStatsADDBABadToken Counter32,
+ wlanStatsADDBABadPolicy Counter32,
+ wlanStatsAMPDUStopped Counter32,
+ wlanStatsAMPDUStopFailed Counter32,
+ wlanStatsAMPDURxReorder Counter32,
+ wlanStatsScansBackground Counter32,
+ wlanLastDeauthReason INTEGER,
+ wlanLastDissasocReason INTEGER,
+ wlanLastAuthFailReason INTEGER,
+ wlanStatsBeaconMissedEvents Counter32,
+ wlanStatsRxDiscardBadStates Counter32,
+ wlanStatsFFFlushed Counter32,
+ wlanStatsTxControlFrames Counter32,
+ wlanStatsAMPDURexmt Counter32,
+ wlanStatsAMPDURexmtFailed Counter32,
+ wlanStatsReset INTEGER
+}
+
+wlanStatsRxBadVersion OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface that had bad version."
+ ::= { wlanIfaceStatisticsEntry 1 }
+
+wlanStatsRxTooShort OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface that were too short."
+ ::= { wlanIfaceStatisticsEntry 2 }
+
+wlanStatsRxWrongBssid OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface with wrong BSSID."
+ ::= { wlanIfaceStatisticsEntry 3 }
+
+wlanStatsRxDiscardedDups OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of received discarded duplicate frames by this interface."
+ ::= { wlanIfaceStatisticsEntry 4 }
+
+wlanStatsRxWrongDir OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of received frames by this interface that were dropped
+ due to wrong direction."
+ ::= { wlanIfaceStatisticsEntry 5 }
+
+wlanStatsRxDiscardMcastEcho OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of received multicast echo frames discarded by this
+ interface."
+ ::= { wlanIfaceStatisticsEntry 6 }
+
+wlanStatsRxDiscardNoAssoc OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface that were dropped
+ since no association existed with the sending station."
+ ::= { wlanIfaceStatisticsEntry 7 }
+
+wlanStatsRxWepNoPrivacy OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface that were dropped
+ since they contained WEP information and WEP privacy was off."
+ ::= { wlanIfaceStatisticsEntry 8 }
+
+wlanStatsRxWepUnencrypted OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface that were dropped
+ since they contained no WEP information and WEP privacy was on."
+ ::= { wlanIfaceStatisticsEntry 9 }
+
+wlanStatsRxWepFailed OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface that were dropped
+ since processing of the WEP information contained in them failed."
+ ::= { wlanIfaceStatisticsEntry 10 }
+
+wlanStatsRxDecapsulationFailed OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of received frames that were discarded by this interface
+ due to decapsulation failure."
+ ::= { wlanIfaceStatisticsEntry 11 }
+
+wlanStatsRxDiscardMgmt OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of received management frames discarded by this interface."
+ ::= { wlanIfaceStatisticsEntry 12 }
+
+wlanStatsRxControl OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of control frames received by this interface."
+ ::= { wlanIfaceStatisticsEntry 13 }
+
+wlanStatsRxBeacon OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of beacon frames received by this interface."
+ ::= { wlanIfaceStatisticsEntry 14 }
+
+wlanStatsRxRateSetTooBig OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface with extended
+ supported rate element."
+ ::= { wlanIfaceStatisticsEntry 15 }
+
+wlanStatsRxElemMissing OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface that were missing
+ a required element."
+ ::= { wlanIfaceStatisticsEntry 16 }
+
+wlanStatsRxElemTooBig OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface that contained an
+ information element whose size was too big."
+ ::= { wlanIfaceStatisticsEntry 17 }
+
+wlanStatsRxElemTooSmall OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface that contained an
+ information element whose size was too small."
+ ::= { wlanIfaceStatisticsEntry 18 }
+
+wlanStatsRxElemUnknown OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface that contained an
+ unknown information element."
+ ::= { wlanIfaceStatisticsEntry 19 }
+
+wlanStatsRxChannelMismatch OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface, that were discarded
+ since they were received on a channel different from the one indicated
+ in the DS params element id."
+ ::= { wlanIfaceStatisticsEntry 20 }
+
+wlanStatsRxDropped OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface that were dropped due
+ to unknown reason."
+ ::= { wlanIfaceStatisticsEntry 21 }
+
+wlanStatsRxSsidMismatch OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface that had a bad SSID."
+ ::= { wlanIfaceStatisticsEntry 22 }
+
+wlanStatsRxAuthNotSupported OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface that contained an
+ unknown authentication algorithm."
+ ::= { wlanIfaceStatisticsEntry 23 }
+
+wlanStatsRxAuthFailed OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface for which the
+ authentication failed."
+ ::= { wlanIfaceStatisticsEntry 24 }
+
+wlanStatsRxAuthCM OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface for which the
+ authentication failed due to TKIP countermeasures enabled."
+ ::= { wlanIfaceStatisticsEntry 25 }
+
+wlanStatsRxAssocWrongBssid OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface with association
+ request that had a bad BSSID."
+ ::= { wlanIfaceStatisticsEntry 26 }
+
+wlanStatsRxAssocNoAuth OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface with association
+ request that came from unauthentication node."
+ ::= { wlanIfaceStatisticsEntry 27 }
+
+wlanStatsRxAssocCapMismatch OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface with association
+ request that had bad capabilities set."
+ ::= { wlanIfaceStatisticsEntry 28 }
+
+wlanStatsRxAssocNoRateMatch OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface with association
+ request that had unsupported rate set."
+ ::= { wlanIfaceStatisticsEntry 29 }
+
+wlanStatsRxBadWpaIE OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface with association
+ request that had no or invalid WPA information element."
+ ::= { wlanIfaceStatisticsEntry 30 }
+
+wlanStatsRxDeauthenticate OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of deauthentication requests received by this interface."
+ ::= { wlanIfaceStatisticsEntry 31 }
+
+wlanStatsRxDisassociate OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of deassociation requests received by this interface."
+ ::= { wlanIfaceStatisticsEntry 32 }
+
+wlanStatsRxUnknownSubtype OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface that had unknown
+ subtype."
+ ::= { wlanIfaceStatisticsEntry 33 }
+
+wlanStatsRxFailedNoBuf OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface that were dropped
+ due to lack of free buffers."
+ ::= { wlanIfaceStatisticsEntry 34 }
+
+wlanStatsRxBadAuthRequest OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface for which
+ authentication failed."
+ ::= { wlanIfaceStatisticsEntry 35 }
+
+wlanStatsRxUnAuthorized OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of non-PAE frames received by this interface prior to
+ authorization."
+ ::= { wlanIfaceStatisticsEntry 36 }
+
+wlanStatsRxBadKeyId OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface with bad key."
+ ::= { wlanIfaceStatisticsEntry 37 }
+
+wlanStatsRxCCMPSeqViolation OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface that caused CCMP
+ sequence violation."
+ ::= { wlanIfaceStatisticsEntry 38 }
+
+wlanStatsRxCCMPBadFormat OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface that had bad CCMP
+ format."
+ ::= { wlanIfaceStatisticsEntry 39 }
+
+wlanStatsRxCCMPFailedMIC OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames receivbed by this interface for which CCMP
+ decryption failed due to MIC mismatch."
+ ::= { wlanIfaceStatisticsEntry 40 }
+
+wlanStatsRxTKIPSeqViolation OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface that caused TKIP
+ sequence violation.."
+ ::= { wlanIfaceStatisticsEntry 41 }
+
+wlanStatsRxTKIPBadFormat OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface that were missing
+ TKIP ExtIV."
+ ::= { wlanIfaceStatisticsEntry 42 }
+
+wlanStatsRxTKIPFailedMIC OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface for which TKIP
+ decryption failed due to MIC mismatch."
+ ::= { wlanIfaceStatisticsEntry 43 }
+
+wlanStatsRxTKIPFailedICV OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface for which TKIP
+ decryption failed due to ICV mismatch."
+ ::= { wlanIfaceStatisticsEntry 44 }
+
+wlanStatsRxDiscardACL OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface that were
+ disallowed by ACL."
+ ::= { wlanIfaceStatisticsEntry 45 }
+
+wlanStatsTxFailedNoBuf OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames that were not transmitted by this interface
+ due to lack of free buffers."
+ ::= { wlanIfaceStatisticsEntry 46 }
+
+wlanStatsTxFailedNoNode OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames that were queued for transmit on this interface
+ but were not sent since appropriate node for sending was not found."
+ ::= { wlanIfaceStatisticsEntry 47 }
+
+wlanStatsTxUnknownMgmt OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of unknown management frames transmitted by this interface."
+ ::= { wlanIfaceStatisticsEntry 48 }
+
+wlanStatsTxBadCipher OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames that were queued for transmit on this interface
+ but were not send since the specified key was not setup."
+ ::= { wlanIfaceStatisticsEntry 49 }
+
+wlanStatsTxNoDefKey OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames that were queued for transmit on this interface
+ but were not send since an appropriate key was not found."
+ ::= { wlanIfaceStatisticsEntry 50 }
+
+wlanStatsTxFragmented OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of fragmented frames transmitted by this interface."
+ ::= { wlanIfaceStatisticsEntry 51 }
+
+wlanStatsTxFragmentsCreated OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of created fragments transmitted by this interface."
+ ::= { wlanIfaceStatisticsEntry 52 }
+
+wlanStatsActiveScans OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of active scans performed by this interface."
+ ::= { wlanIfaceStatisticsEntry 53 }
+
+wlanStatsPassiveScans OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of passive scans performed by this interface."
+ ::= { wlanIfaceStatisticsEntry 54 }
+
+wlanStatsTimeoutInactivity OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of times a station/node was dropped by this interface
+ due to inactivity timeout."
+ ::= { wlanIfaceStatisticsEntry 55 }
+
+wlanStatsCryptoNoMem OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number times attaching a crypto protocol to this interface
+ failed due to lack of memory."
+ ::= { wlanIfaceStatisticsEntry 56 }
+
+wlanStatsSwCryptoTKIP OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of times TKIP encryption/decryption was handled in
+ software for frames received/transmitted by this interface."
+ ::= { wlanIfaceStatisticsEntry 57 }
+
+wlanStatsSwCryptoTKIPEnMIC OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of times TKIP MIC was added in software to frames
+ transmitted by this interface."
+ ::= { wlanIfaceStatisticsEntry 58 }
+
+wlanStatsSwCryptoTKIPDeMIC OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of times TKIP MIC was stripped in software from frames
+ received by this interface."
+ ::= { wlanIfaceStatisticsEntry 59 }
+
+wlanStatsCryptoTKIPCM OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames discarded by this interface due to TKIP
+ counter measures."
+ ::= { wlanIfaceStatisticsEntry 60 }
+
+wlanStatsSwCryptoCCMP OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of times CCMP encryption/decryption was handled in
+ software for frames received/transmitted by this interface."
+ ::= { wlanIfaceStatisticsEntry 61 }
+
+wlanStatsSwCryptoWEP OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of times WEP encryption/decryption was handled in
+ software for frames received/transmitted by this interface."
+ ::= { wlanIfaceStatisticsEntry 62 }
+
+wlanStatsCryptoCipherKeyRejected OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of times a key was rejected for this interface."
+ ::= { wlanIfaceStatisticsEntry 63 }
+
+wlanStatsCryptoNoKey OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of times key setup for this interface failed."
+ ::= { wlanIfaceStatisticsEntry 64 }
+
+wlanStatsCryptoDeleteKeyFailed OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of times key deletion from driver for this interface
+ failed."
+ ::= { wlanIfaceStatisticsEntry 65 }
+
+wlanStatsCryptoUnknownCipher OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of times key setup for this interface failed due to
+ invalid cipher."
+ ::= { wlanIfaceStatisticsEntry 66 }
+
+wlanStatsCryptoAttachFailed OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of times attaching a cipher for this interface failed."
+ ::= { wlanIfaceStatisticsEntry 67 }
+
+wlanStatsCryptoKeyFailed OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of times setting a cipher in the driver for this
+ interface failed."
+ ::= { wlanIfaceStatisticsEntry 68 }
+
+wlanStatsCryptoEnMICFailed OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames that were discarded by by this interface
+ due to failed enmic."
+ ::= { wlanIfaceStatisticsEntry 69 }
+
+wlanStatsIBSSCapMismatch OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of times a BSSID change failed for an interface operating
+ in ad hoc mode due to capabilities mismatch."
+ ::= { wlanIfaceStatisticsEntry 70 }
+
+wlanStatsUnassocStaPSPoll OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of ps-poll frames from unassociated station received
+ by this interface."
+ ::= { wlanIfaceStatisticsEntry 71 }
+
+wlanStatsBadAidPSPoll OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of ps-poll frames with incorrect aid received by this
+ interface."
+ ::= { wlanIfaceStatisticsEntry 72 }
+
+wlanStatsEmptyPSPoll OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of empty ps-poll frames received by this interface."
+ ::= { wlanIfaceStatisticsEntry 73 }
+
+wlanStatsRxFFBadHdr OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of fast frames with bad header received by this interface."
+ ::= { wlanIfaceStatisticsEntry 74 }
+
+wlanStatsRxFFTooShort OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of fast frames received by this interface, for which
+ decapsulation failed."
+ ::= { wlanIfaceStatisticsEntry 75 }
+
+wlanStatsRxFFSplitError OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of fast frames received by this interface, for which
+ decapsulation failed during split."
+ ::= { wlanIfaceStatisticsEntry 76 }
+
+wlanStatsRxFFDecap OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of fast frames received by this interface, that were
+ successfully decapsulated."
+ ::= { wlanIfaceStatisticsEntry 77 }
+
+wlanStatsTxFFEncap OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of encapsulated fast frames transmitted by this interface."
+ ::= { wlanIfaceStatisticsEntry 78 }
+
+wlanStatsRxBadBintval OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames with bogus beacon interval received by this
+ interface."
+ ::= { wlanIfaceStatisticsEntry 79 }
+
+wlanStatsRxDemicFailed OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface for which
+ stripping of the MIC failed."
+ ::= { wlanIfaceStatisticsEntry 80 }
+
+wlanStatsRxDefragFailed OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received by this interface for which
+ defragmentation failed."
+ ::= { wlanIfaceStatisticsEntry 81 }
+
+wlanStatsRxMgmt OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of management frames received by this interface."
+ ::= { wlanIfaceStatisticsEntry 82 }
+
+wlanStatsRxActionMgmt OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of action management frames received by this interface."
+ ::= { wlanIfaceStatisticsEntry 83 }
+
+wlanStatsRxAMSDUTooShort OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of A-MSDU frames received by this interface for which
+ decapsulaiton failed."
+ ::= { wlanIfaceStatisticsEntry 84 }
+
+wlanStatsRxAMSDUSplitError OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of A-MSDU frames received by this interface for which
+ split failed."
+ ::= { wlanIfaceStatisticsEntry 85 }
+
+wlanStatsRxAMSDUDecap OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of A-MSDU frames received by this interface which
+ were successfully decapsulaited."
+ ::= { wlanIfaceStatisticsEntry 86 }
+
+wlanStatsTxAMSDUEncap OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of encapsulated A-MSDU frames transmitted by this
+ interface."
+ ::= { wlanIfaceStatisticsEntry 87 }
+
+wlanStatsAMPDUBadBAR OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of A-MPDU frames that were dropped by this interface
+ source BAR frame processing was disabled."
+ ::= { wlanIfaceStatisticsEntry 88 }
+
+wlanStatsAMPDUOowBar OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of A-MPDU BAR before ADDBA frames received by this
+ interface."
+ ::= { wlanIfaceStatisticsEntry 89 }
+
+wlanStatsAMPDUMovedBAR OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of times a BAR moved window occurred."
+ ::= { wlanIfaceStatisticsEntry 90 }
+
+wlanStatsAMPDURxBAR OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of A-MPDU BAR frames received by this interface."
+ ::= { wlanIfaceStatisticsEntry 91 }
+
+wlanStatsAMPDURxOor OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of out-of-order A-MPDU frames by received this interface."
+ ::= { wlanIfaceStatisticsEntry 92 }
+
+wlanStatsAMPDURxCopied OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of A-MPDU frames by copied down this interface."
+ ::= { wlanIfaceStatisticsEntry 93 }
+
+wlanStatsAMPDURxDropped OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of A-MPDU frames by dropped this interface."
+ ::= { wlanIfaceStatisticsEntry 94 }
+
+wlanStatsTxDiscardBadState OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames queued for transmit on this interface that
+ were discarded due to interface state not ready for transmit."
+ ::= { wlanIfaceStatisticsEntry 95 }
+
+wlanStatsTxFailedNoAssoc OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames queued for transmit on this interface that
+ were discarded since the receiving station was not associated."
+ ::= { wlanIfaceStatisticsEntry 96 }
+
+wlanStatsTxClassifyFailed OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames queued for transmit on this interface that
+ were discarded since their priority was not determined."
+ ::= { wlanIfaceStatisticsEntry 97 }
+
+wlanStatsDwdsMcastDiscard OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of multicast over DWDS frames discarded by this interface."
+ ::= { wlanIfaceStatisticsEntry 98 }
+
+wlanStatsHTAssocRejectNoHT OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of management frames received from a non-HT stations
+ that were rejected by this interface."
+ ::= { wlanIfaceStatisticsEntry 99 }
+
+wlanStatsHTAssocDowngrade OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of times HT was disallowed for an association on
+ this interface due to WEP or TKIP requested."
+ ::= { wlanIfaceStatisticsEntry 100 }
+
+wlanStatsHTAssocRateMismatch OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of times rate mismatch occurred during HT rate set
+ handling on this interface."
+ ::= { wlanIfaceStatisticsEntry 101 }
+
+wlanStatsAMPDURxAge OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of A-MPDU frames sent by this interface due to aging out."
+ ::= { wlanIfaceStatisticsEntry 102 }
+
+wlanStatsAMPDUMoved OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of time A-MPDU MSDU moved window occurred for this
+ interface."
+ ::= { wlanIfaceStatisticsEntry 103 }
+
+wlanStatsADDBADisabledReject OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of received ADDBA frames that were discarded by this
+ interface since ADDBA was disabled."
+ ::= { wlanIfaceStatisticsEntry 104 }
+
+wlanStatsADDBANoRequest OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of received ADDBA responses frames that were discarded
+ by this interface due to no pending ADDBA."
+ ::= { wlanIfaceStatisticsEntry 105 }
+
+wlanStatsADDBABadToken OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of received ADDBA response frames that were discarded
+ by this interface since ADDBA response caused dialogtoken mismatch."
+ ::= { wlanIfaceStatisticsEntry 106 }
+
+wlanStatsADDBABadPolicy OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of received ADDBA response frames that were discarded
+ by this interface since ADDBA response caused policy mismatch."
+ ::= { wlanIfaceStatisticsEntry 107 }
+
+wlanStatsAMPDUStopped OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of times a A-MPDU stream stopped on this interface."
+ ::= { wlanIfaceStatisticsEntry 108 }
+
+wlanStatsAMPDUStopFailed OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of times a A-MPDU stream stop failed on this interface."
+ ::= { wlanIfaceStatisticsEntry 109 }
+
+wlanStatsAMPDURxReorder OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of received reordered A-MPDU frames on this interface."
+ ::= { wlanIfaceStatisticsEntry 110 }
+
+wlanStatsScansBackground OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of background scans started on this interface."
+ ::= { wlanIfaceStatisticsEntry 111 }
+
+wlanLastDeauthReason OBJECT-TYPE
+ SYNTAX WlanMgmtReasonCode
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The last received deauthenticate reason on this interface."
+ ::= { wlanIfaceStatisticsEntry 112 }
+
+wlanLastDissasocReason OBJECT-TYPE
+ SYNTAX WlanMgmtReasonCode
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The last received disassociate reason on this interface."
+ ::= { wlanIfaceStatisticsEntry 113 }
+
+wlanLastAuthFailReason OBJECT-TYPE
+ SYNTAX WlanMgmtReasonCode
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The last received authentication failed reason on this interface."
+ ::= { wlanIfaceStatisticsEntry 114 }
+
+wlanStatsBeaconMissedEvents OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of beacon miss notification events on this interface."
+ ::= { wlanIfaceStatisticsEntry 115 }
+
+wlanStatsRxDiscardBadStates OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames received on this interface that were discarded
+ due to interface state not ready for receive."
+ ::= { wlanIfaceStatisticsEntry 116 }
+
+wlanStatsFFFlushed OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of fast frames flushed from the stage queue on this
+ interface."
+ ::= { wlanIfaceStatisticsEntry 117 }
+
+wlanStatsTxControlFrames OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of control frames transmitted by this interface."
+ ::= { wlanIfaceStatisticsEntry 118 }
+
+wlanStatsAMPDURexmt OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of A-MPDU frames successfully retransmitted by this
+ interface."
+ ::= { wlanIfaceStatisticsEntry 119 }
+
+wlanStatsAMPDURexmtFailed OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of A-MPDU frames for which retransmission failed on
+ this interface."
+ ::= { wlanIfaceStatisticsEntry 120 }
+
+wlanStatsReset OBJECT-TYPE
+ SYNTAX INTEGER {
+ no-op(1),
+ clear(2)
+ }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object is used to reset the statistics on this
+ interface."
+ ::= { wlanIfaceStatisticsEntry 121 }
+
+-- ---------------------------------------------------------- --
+-- The WEP Configuration Database for Wireless interfaces
+-- ---------------------------------------------------------- --
+
+wlanWepInterfaceTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF WlanWepInterfaceEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains WEP configuration for the wireless interfaces
+ on the managed system."
+ ::= { begemotWlanWep 1 }
+
+wlanWepInterfaceEntry OBJECT-TYPE
+ SYNTAX WlanWepInterfaceEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "WEP Configuration for wireless interface."
+ INDEX { wlanIfaceName}
+ ::= { wlanWepInterfaceTable 1 }
+
+WlanWepInterfaceEntry ::= SEQUENCE {
+ wlanWepMode INTEGER,
+ wlanWepDefTxKey INTEGER
+}
+
+wlanWepMode OBJECT-TYPE
+ SYNTAX INTEGER {
+ off(0),
+ on(1),
+ mixed(2)
+ }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The WEP mode set on the interface."
+ DEFVAL { off }
+ ::= { wlanWepInterfaceEntry 1 }
+
+wlanWepDefTxKey OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The index of the default WEP key for the interface."
+ ::= { wlanWepInterfaceEntry 2 }
+
+wlanWepKeyTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF WlanWepKeyEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains the configured WEP keys for a virtual
+ wireless interface."
+ ::= { begemotWlanWep 2 }
+
+wlanWepKeyEntry OBJECT-TYPE
+ SYNTAX WlanWepKeyEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A configured WEP Key entry."
+ INDEX { wlanIfaceName, wlanWepKeyID }
+ ::= { wlanWepKeyTable 1 }
+
+WlanWepKeyEntry ::= SEQUENCE {
+ wlanWepKeyID INTEGER,
+ wlanWepKeyLength INTEGER,
+ wlanWepKeySet OCTET STRING,
+ wlanWepKeyHash OCTET STRING,
+ wlanWepKeyStatus RowStatus
+}
+
+wlanWepKeyID OBJECT-TYPE
+ SYNTAX INTEGER (1..4)
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The WEP Key ID."
+ ::= { wlanWepKeyEntry 1 }
+
+wlanWepKeyLength OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The WEP Key length."
+ ::= { wlanWepKeyEntry 2 }
+
+wlanWepKeySet OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The WEP Key String to configure for this key. When GET is attempted
+ for this column, an empty Octet String is returned."
+ ::= { wlanWepKeyEntry 3 }
+
+wlanWepKeyHash OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The SHA256 hash produced of the WEP Key String."
+ ::= { wlanWepKeyEntry 4 }
+
+wlanWepKeyStatus OBJECT-TYPE
+ SYNTAX RowStatus
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "This object is used for creating/deleting WEP keys."
+ ::= { wlanWepKeyEntry 5 }
+
+-- ---------------------------------------------------------- --
+-- The MAC Access Control Database for Wireless interfaces
+-- ---------------------------------------------------------- --
+
+wlanMACAccessControlTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF WlanMACAccessControlEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains Access Control configuration for wireless
+ interfaces operating as an access point."
+ ::= { begemotWlanMACAccessControl 1 }
+
+wlanMACAccessControlEntry OBJECT-TYPE
+ SYNTAX WlanMACAccessControlEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "The MAC Access Control configuration for a wireless interface
+ operating as an access point."
+ INDEX { wlanIfaceName}
+ ::= { wlanMACAccessControlTable 1 }
+
+WlanMACAccessControlEntry ::= SEQUENCE {
+ wlanMACAccessControlPolicy INTEGER,
+ wlanMACAccessControlNacl Counter32,
+ wlanMACAccessControlFlush INTEGER
+}
+
+wlanMACAccessControlPolicy OBJECT-TYPE
+ SYNTAX INTEGER {
+ open(0),
+ allow(1),
+ deny(2),
+ radius(7)
+ }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies the MAC Access Control policy
+ for this Host AP interface."
+ DEFVAL { open }
+ ::= { wlanMACAccessControlEntry 1 }
+
+wlanMACAccessControlNacl OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of active MAC Access Control Entries in the Database
+ for this Host AP interface."
+ ::= { wlanMACAccessControlEntry 2 }
+
+wlanMACAccessControlFlush OBJECT-TYPE
+ SYNTAX INTEGER {
+ no-op(0),
+ flush(1)
+ }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "This object is used to flush all entries from the MAC Access
+ Control Database for the specified virtual wireless interface."
+ ::= { wlanMACAccessControlEntry 3 }
+
+wlanMACAccessControlMACTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF WlanMACAccessControlMACEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains Access Control MAC for virtual wireless
+ interfaces operating in Host AP mode."
+ ::= { begemotWlanMACAccessControl 2 }
+
+wlanMACAccessControlMACEntry OBJECT-TYPE
+ SYNTAX WlanMACAccessControlMACEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "The MAC Access Control configuration database with MAC addresses
+ for a virtual wireless interface."
+ INDEX { wlanIfaceName, wlanMACAccessControlMAC }
+ ::= { wlanMACAccessControlMACTable 1 }
+
+WlanMACAccessControlMACEntry ::= SEQUENCE {
+ wlanMACAccessControlMAC MacAddress,
+ wlanMACAccessControlMACStatus RowStatus
+}
+
+wlanMACAccessControlMAC OBJECT-TYPE
+ SYNTAX MacAddress
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The value of this object specifies the station's MAC to which
+ the Access Control policy will be applied."
+ ::= { wlanMACAccessControlMACEntry 1 }
+
+wlanMACAccessControlMACStatus OBJECT-TYPE
+ SYNTAX RowStatus
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The object is used to add or delete MAC entries from the Access
+ Control Database for this interface operating in Host AP mode.
+ To add an entry the value of this object should be set to createAndGo,
+ a value of destroy will remove an existing entry. A GET on this object
+ will always return value active."
+ ::= { wlanMACAccessControlMACEntry 2 }
+
+-- ---------------------------------------------------------- --
+-- The Mesh Routing Database for interfaces operating in mesh mode
+-- ---------------------------------------------------------- --
+
+wlanMeshRoutingConfig OBJECT IDENTIFIER ::= { begemotWlanMeshRouting 1 }
+
+wlanMeshInterface OBJECT IDENTIFIER ::= { begemotWlanMeshRouting 2 }
+
+wlanMeshRoute OBJECT IDENTIFIER ::= { begemotWlanMeshRouting 3 }
+
+wlanMeshStatistics OBJECT IDENTIFIER ::= { begemotWlanMeshRouting 4 }
+
+wlanMeshRouteProtocols OBJECT IDENTIFIER ::= { begemotWlanMeshRouting 5 }
+
+wlanMeshMaxRetries OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "Maximum retries during peer link establishment for wireless mesh
+ routing operation."
+ DEFVAL { 2 }
+ ::= { wlanMeshRoutingConfig 1 }
+
+wlanMeshConfirmTimeout OBJECT-TYPE
+ SYNTAX INTEGER
+ UNITS "milliseconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "Confirm state timeout for wireless mesh routing operation."
+ DEFVAL { 40 }
+ ::= { wlanMeshRoutingConfig 2 }
+
+wlanMeshHoldingTimeout OBJECT-TYPE
+ SYNTAX INTEGER
+ UNITS "milliseconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "Holding state timeout for wireless mesh routing operation."
+ DEFVAL { 40 }
+ ::= { wlanMeshRoutingConfig 3 }
+
+wlanMeshRetryTimeout OBJECT-TYPE
+ SYNTAX INTEGER
+ UNITS "milliseconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "Retry timeout for wireless mesh routing operation."
+ DEFVAL { 40 }
+ ::= { wlanMeshRoutingConfig 4 }
+
+wlanMeshInterfaceTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF WlanMeshInterfaceEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains information for wireless interfaces operating
+ as wireless mesh points."
+ ::= { wlanMeshInterface 1 }
+
+wlanMeshInterfaceEntry OBJECT-TYPE
+ SYNTAX WlanMeshInterfaceEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Wireless Mesh Routing information for an interface operating as
+ mesh point."
+ INDEX { wlanIfaceName }
+ ::= { wlanMeshInterfaceTable 1 }
+
+WlanMeshInterfaceEntry ::= SEQUENCE {
+ wlanMeshId OCTET STRING,
+ wlanMeshTTL INTEGER,
+ wlanMeshPeeringEnabled TruthValue,
+ wlanMeshForwardingEnabled TruthValue,
+ wlanMeshMetric INTEGER,
+ wlanMeshPath INTEGER,
+ wlanMeshRoutesFlush INTEGER
+}
+
+wlanMeshId OBJECT-TYPE
+ SYNTAX OCTET STRING (SIZE(1..32))
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The desired Mesh Identifier for the interface."
+ ::= { wlanMeshInterfaceEntry 1 }
+
+wlanMeshTTL OBJECT-TYPE
+ SYNTAX INTEGER
+ UNITS "hops"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The number of hops a packet may be forwarded before it is discarded."
+ DEFVAL { 31 }
+ ::= { wlanMeshInterfaceEntry 2 }
+
+wlanMeshPeeringEnabled OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "Enable or disable peering with neighbor mesh stations for this
+ interface."
+ DEFVAL { true }
+ ::= { wlanMeshInterfaceEntry 3 }
+
+wlanMeshForwardingEnabled OBJECT-TYPE
+ SYNTAX TruthValue
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "Enable or disable forwarding packets by this interface."
+ DEFVAL { true }
+ ::= { wlanMeshInterfaceEntry 4 }
+
+wlanMeshMetric OBJECT-TYPE
+ SYNTAX INTEGER {
+ unknown(0),
+ airtime(1)
+ }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The link metric protocol used by the interface."
+ DEFVAL { airtime }
+ ::= { wlanMeshInterfaceEntry 5 }
+
+wlanMeshPath OBJECT-TYPE
+ SYNTAX INTEGER {
+ unknown(0),
+ hwmp(1)
+ }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The path selection protocol used by the interface."
+ DEFVAL { hwmp }
+ ::= { wlanMeshInterfaceEntry 6 }
+
+wlanMeshRoutesFlush OBJECT-TYPE
+ SYNTAX INTEGER {
+ no-op(0),
+ flush(1)
+ }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "This object is used to flush all mesh route entries from the mesh
+ routing table for the specified interface."
+ ::= { wlanMeshInterfaceEntry 7 }
+
+wlanMeshNeighborTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF WlanMeshNeighborEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains information for the neighbors of wireless
+ interfaces operating in mesh mode."
+ ::= { wlanMeshInterface 2 }
+
+wlanMeshNeighborEntry OBJECT-TYPE
+ SYNTAX WlanMeshNeighborEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Information for all neighbors of a wireless interface operating as
+ a mesh point."
+ INDEX { wlanIfaceName, wlanMeshNeighborAddress }
+ ::= { wlanMeshNeighborTable 1 }
+
+WlanMeshNeighborEntry ::= SEQUENCE {
+ wlanMeshNeighborAddress MacAddress,
+ wlanMeshNeighborFrequency INTEGER,
+ wlanMeshNeighborLocalId INTEGER,
+ wlanMeshNeighborPeerId INTEGER,
+ wlanMeshNeighborPeerState INTEGER,
+ wlanMeshNeighborCurrentTXRate INTEGER,
+ wlanMeshNeighborRxSignalStrength INTEGER,
+ wlanMeshNeighborIdleTimer INTEGER,
+ wlanMeshNeighborTxSequenceNo INTEGER,
+ wlanMeshNeighborRxSequenceNo INTEGER
+}
+
+wlanMeshNeighborAddress OBJECT-TYPE
+ SYNTAX MacAddress
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The Ethernet address of this neighbor."
+ ::= { wlanMeshNeighborEntry 1 }
+
+wlanMeshNeighborFrequency OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The operating frequency for the link with this neighbor."
+ ::= { wlanMeshNeighborEntry 2 }
+
+wlanMeshNeighborLocalId OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The local mesh id for this neighbor."
+ ::= { wlanMeshNeighborEntry 3 }
+
+wlanMeshNeighborPeerId OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The mesh peer id of this neighbor."
+ ::= { wlanMeshNeighborEntry 4 }
+
+wlanMeshNeighborPeerState OBJECT-TYPE
+ SYNTAX INTEGER {
+ idle(0),
+ openTx(1),
+ openRx(2),
+ confirmRx(3),
+ established(4),
+ closing(5)
+ }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The current link state for this neighbor."
+ ::= { wlanMeshNeighborEntry 5 }
+
+wlanMeshNeighborCurrentTXRate OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The current transmit rate for this neighbor."
+ ::= { wlanMeshNeighborEntry 6 }
+
+wlanMeshNeighborRxSignalStrength OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The average receive signal strength for this neighbor."
+ ::= { wlanMeshNeighborEntry 7 }
+
+wlanMeshNeighborIdleTimer OBJECT-TYPE
+ SYNTAX INTEGER
+ UNITS "seconds"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The value of this neighbor's inactivity timer."
+ ::= { wlanMeshNeighborEntry 8 }
+
+wlanMeshNeighborTxSequenceNo OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The last sequence number transmitted to this neighbor."
+ ::= { wlanMeshNeighborEntry 9 }
+
+wlanMeshNeighborRxSequenceNo OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The last sequence number received from this neighbor."
+ ::= { wlanMeshNeighborEntry 10 }
+
+wlanMeshRouteTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF WlanMeshRouteEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains the mesh routing table for interfaces operating
+ as mesh points, used for forwarding packets on a mesh network."
+ ::= { wlanMeshRoute 1 }
+
+wlanMeshRouteEntry OBJECT-TYPE
+ SYNTAX WlanMeshRouteEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Wireless Mesh Routing Table entries for virtual wireless interfaces."
+ INDEX { wlanIfaceName, wlanMeshRouteDestination }
+ ::= { wlanMeshRouteTable 1 }
+
+WlanMeshRouteEntry ::= SEQUENCE {
+ wlanMeshRouteDestination MacAddress,
+ wlanMeshRouteNextHop MacAddress,
+ wlanMeshRouteHops INTEGER,
+ wlanMeshRouteMetric Unsigned32,
+ wlanMeshRouteLifeTime Unsigned32,
+ wlanMeshRouteLastMseq Unsigned32,
+ wlanMeshRouteFlags BITS,
+ wlanMeshRouteStatus RowStatus
+}
+
+wlanMeshRouteDestination OBJECT-TYPE
+ SYNTAX MacAddress
+ MAX-ACCESS read-create
+ STATUS current
+ DESCRIPTION
+ "The mesh route entry's destination address."
+ ::= { wlanMeshRouteEntry 1 }
+
+wlanMeshRouteNextHop OBJECT-TYPE
+ SYNTAX MacAddress
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The mesh route entry's next hop address."
+ ::= { wlanMeshRouteEntry 2 }
+
+wlanMeshRouteHops OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of hops for this mesh route entry."
+ ::= { wlanMeshRouteEntry 3 }
+
+wlanMeshRouteMetric OBJECT-TYPE
+ SYNTAX Unsigned32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The metric of this mesh route entry."
+ ::= { wlanMeshRouteEntry 4 }
+
+wlanMeshRouteLifeTime OBJECT-TYPE
+ SYNTAX Unsigned32
+ UNITS "seconds"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The life time of this mesh route entry."
+ ::= { wlanMeshRouteEntry 5 }
+
+wlanMeshRouteLastMseq OBJECT-TYPE
+ SYNTAX Unsigned32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The last sequence number seen from this destination."
+ ::= { wlanMeshRouteEntry 6 }
+
+wlanMeshRouteFlags OBJECT-TYPE
+ SYNTAX BITS {
+ valid(1),
+ proxy(2)
+ }
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The Mesh Route entry's flags."
+ ::= { wlanMeshRouteEntry 7 }
+
+wlanMeshRouteStatus OBJECT-TYPE
+ SYNTAX RowStatus
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The object is used to add or delete entries from the mesh routing
+ table for the virtual wireless interface."
+ ::= { wlanMeshRouteEntry 8 }
+
+wlanMeshStatsTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF WlanMeshStatsEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains summary statistics for each virtual wireless
+ interface operating as mesh point."
+ ::= { wlanMeshStatistics 1 }
+
+wlanMeshStatsEntry OBJECT-TYPE
+ SYNTAX WlanMeshStatsEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A list of statistics for each virtual wireless interface operating
+ as mesh point."
+ INDEX { wlanIfaceName }
+ ::= { wlanMeshStatsTable 1 }
+
+WlanMeshStatsEntry ::= SEQUENCE {
+ wlanMeshDroppedBadSta Counter32,
+ wlanMeshDroppedNoLink Counter32,
+ wlanMeshNoFwdTtl Counter32,
+ wlanMeshNoFwdBuf Counter32,
+ wlanMeshNoFwdTooShort Counter32,
+ wlanMeshNoFwdDisabled Counter32,
+ wlanMeshNoFwdPathUnknown Counter32,
+ wlanMeshDroppedBadAE Counter32,
+ wlanMeshRouteAddFailed Counter32,
+ wlanMeshDroppedNoProxy Counter32,
+ wlanMeshDroppedMisaligned Counter32
+}
+
+wlanMeshDroppedBadSta OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames dropped by this interface since they were
+ received from a non-mesh station."
+ ::= { wlanMeshStatsEntry 1 }
+
+wlanMeshDroppedNoLink OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames dropped by this interface since no link had
+ been established."
+ ::= { wlanMeshStatsEntry 2 }
+
+wlanMeshNoFwdTtl OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames that were not forwarded by this interface
+ because of a zero TTL."
+ ::= { wlanMeshStatsEntry 3 }
+
+wlanMeshNoFwdBuf OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames that were not forwarded by this interface
+ due to lack of free buffers."
+ ::= { wlanMeshStatsEntry 4 }
+
+wlanMeshNoFwdTooShort OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames that were not forwarded by this interface
+ due to missing headers."
+ ::= { wlanMeshStatsEntry 5 }
+
+wlanMeshNoFwdDisabled OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames that were not forwarded by this interface
+ since forwarding was disabled."
+ ::= { wlanMeshStatsEntry 6 }
+
+wlanMeshNoFwdPathUnknown OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames that were not forwarded by this interface
+ since the path was unknown."
+ ::= { wlanMeshStatsEntry 7 }
+
+wlanMeshDroppedBadAE OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames that were dropped by this interface since
+ the AE was invalid."
+ ::= { wlanMeshStatsEntry 8 }
+
+wlanMeshRouteAddFailed OBJECT-TYPE
+ SYNTAX Counter32
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of times an addition of a route to the mesh routing
+ table for this interface failed."
+ ::= { wlanMeshStatsEntry 9 }
+
+wlanMeshDroppedNoProxy OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames that were dropped by this interface since
+ proxying was not enabled on the interface."
+ ::= { wlanMeshStatsEntry 10 }
+
+wlanMeshDroppedMisaligned OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of frames that were dropped by this interface due to
+ bad alignment."
+ ::= { wlanMeshStatsEntry 11 }
+
+-- ---------------------------------------------------------- --
+-- Subtrees containing data for each supported mesh routing protocol.
+-- ---------------------------------------------------------- --
+
+wlanMeshProtoHWMP OBJECT IDENTIFIER ::= { wlanMeshRouteProtocols 1 }
+
+-- ---------------------------------------------------------- --
+-- Hybrid Wireless Mesh Protocol database.
+-- ---------------------------------------------------------- --
+wlanMeshHWMPConfig OBJECT IDENTIFIER ::= { wlanMeshProtoHWMP 1 }
+
+wlanMeshHWMPInterface OBJECT IDENTIFIER ::= { wlanMeshProtoHWMP 2 }
+
+wlanMeshHWMPStatistics OBJECT IDENTIFIER ::= { wlanMeshProtoHWMP 3 }
+
+wlanHWMPRouteInactiveTimeout OBJECT-TYPE
+ SYNTAX INTEGER
+ UNITS "milliseconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The HWMP Route inactivity timeout."
+ DEFVAL { 5000 }
+ ::= { wlanMeshHWMPConfig 1 }
+
+wlanHWMPRootAnnounceInterval OBJECT-TYPE
+ SYNTAX INTEGER
+ UNITS "milliseconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The HWMP Root Announcement interval."
+ DEFVAL { 1000 }
+ ::= { wlanMeshHWMPConfig 2 }
+
+wlanHWMPRootInterval OBJECT-TYPE
+ SYNTAX INTEGER
+ UNITS "milliseconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The HWMP Root interval."
+ DEFVAL { 2000 }
+ ::= { wlanMeshHWMPConfig 3 }
+
+wlanHWMPRootTimeout OBJECT-TYPE
+ SYNTAX INTEGER
+ UNITS "milliseconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The root PREQ timeout."
+ DEFVAL { 5000 }
+ ::= { wlanMeshHWMPConfig 4 }
+
+wlanHWMPPathLifetime OBJECT-TYPE
+ SYNTAX INTEGER
+ UNITS "milliseconds"
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The HWMP path entry lifetime."
+ DEFVAL { 500 }
+ ::= { wlanMeshHWMPConfig 5 }
+
+wlanHWMPReplyForwardBit OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "A non-zero value for this object specifies that RF bit shall be
+ set on generated PREQs."
+ DEFVAL { 1 }
+ ::= { wlanMeshHWMPConfig 6 }
+
+wlanHWMPTargetOnlyBit OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "A non-zero value for this object specifies that TO bit shall be
+ set on generated PREQs."
+ DEFVAL { 0 }
+ ::= { wlanMeshHWMPConfig 7 }
+
+wlanHWMPInterfaceTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF WlanHWMPInterfaceEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains information for wireless interfaces
+ operating in mesh mode."
+ ::= { wlanMeshHWMPInterface 1 }
+
+wlanHWMPInterfaceEntry OBJECT-TYPE
+ SYNTAX WlanHWMPInterfaceEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Wireless Mesh Routing HWMP information for a wireless interface."
+ INDEX { wlanIfaceName }
+ ::= { wlanHWMPInterfaceTable 1 }
+
+WlanHWMPInterfaceEntry ::= SEQUENCE {
+ wlanHWMPRootMode INTEGER,
+ wlanHWMPMaxHops INTEGER
+}
+
+wlanHWMPRootMode OBJECT-TYPE
+ SYNTAX INTEGER {
+ disabled(1),
+ normal(2),
+ proactive(3),
+ rann(4)
+ }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "This object is used to configure whether the interface will operate
+ as root node and specify root node mode."
+ DEFVAL { disabled }
+ ::= { wlanHWMPInterfaceEntry 1 }
+
+wlanHWMPMaxHops OBJECT-TYPE
+ SYNTAX INTEGER
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "The maximum number of hops allowed on an HMWP path for this interface."
+ DEFVAL { 31 }
+ ::= { wlanHWMPInterfaceEntry 2 }
+
+wlanMeshHWMPStatsTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF WlanMeshHWMPStatsEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A table that contains summary statistics for HWMP operation on an
+ interface operating as mesh point."
+ ::= { wlanMeshHWMPStatistics 1 }
+
+wlanMeshHWMPStatsEntry OBJECT-TYPE
+ SYNTAX WlanMeshHWMPStatsEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "A list of HWMP statistics for each wlan interface operating as HWMP
+ mesh point."
+ INDEX { wlanIfaceName }
+ ::= { wlanMeshHWMPStatsTable 1 }
+
+WlanMeshHWMPStatsEntry ::= SEQUENCE {
+ wlanMeshHWMPWrongSeqNo Counter32,
+ wlanMeshHWMPTxRootPREQ Counter32,
+ wlanMeshHWMPTxRootRANN Counter32,
+ wlanMeshHWMPProxy Counter32
+}
+
+wlanMeshHWMPWrongSeqNo OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of HWMP frames with wrong sequence number received by
+ this interface."
+ ::= { wlanMeshHWMPStatsEntry 1 }
+
+wlanMeshHWMPTxRootPREQ OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of HWMP Root PREQ frames sent by this interface."
+ ::= { wlanMeshHWMPStatsEntry 2 }
+
+wlanMeshHWMPTxRootRANN OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of HWMP Root RANN frames sent by this interface."
+ ::= { wlanMeshHWMPStatsEntry 3 }
+
+wlanMeshHWMPProxy OBJECT-TYPE
+ SYNTAX Counter32
+ UNITS "frames"
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "The number of HWMP PREP frames discarded by this interface due to
+ the HWMP route being marked as proxy."
+ ::= { wlanMeshHWMPStatsEntry 4 }
+
+END
diff --git a/usr.sbin/bsnmpd/modules/snmp_wlan/Makefile b/usr.sbin/bsnmpd/modules/snmp_wlan/Makefile
new file mode 100644
index 0000000..6a53d46
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_wlan/Makefile
@@ -0,0 +1,15 @@
+#
+# $FreeBSD$
+#
+
+MOD= wlan
+SRCS= wlan_snmp.c wlan_sys.c
+CFLAGS+= -DSNMPTREE_TYPES
+
+XSYM= begemotWlan
+
+BMIBS= BEGEMOT-WIRELESS-MIB.txt
+MAN= snmp_${MOD}.3
+DEFS= ${MOD}_tree.def
+
+.include <bsd.snmpmod.mk>
diff --git a/usr.sbin/bsnmpd/modules/snmp_wlan/Makefile.depend b/usr.sbin/bsnmpd/modules/snmp_wlan/Makefile.depend
new file mode 100644
index 0000000..329da3d
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_wlan/Makefile.depend
@@ -0,0 +1,31 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbsnmp/libbsnmp \
+ lib/libc \
+ lib/libcompiler_rt \
+ usr.sbin/bsnmpd/modules \
+ usr.sbin/bsnmpd/modules/snmp_mibII \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+wlan_snmp.So: wlan_oid.h
+wlan_snmp.So: wlan_tree.h
+wlan_snmp.po: wlan_oid.h
+wlan_snmp.po: wlan_tree.h
+wlan_sys.So: wlan_tree.h
+wlan_sys.po: wlan_tree.h
+wlan_tree.So: wlan_tree.c
+wlan_tree.So: wlan_tree.h
+wlan_tree.po: wlan_tree.c
+wlan_tree.po: wlan_tree.h
+.endif
diff --git a/usr.sbin/bsnmpd/modules/snmp_wlan/snmp_wlan.3 b/usr.sbin/bsnmpd/modules/snmp_wlan/snmp_wlan.3
new file mode 100644
index 0000000..d695eee
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_wlan/snmp_wlan.3
@@ -0,0 +1,160 @@
+.\"-
+.\" Copyright (C) 2010 The FreeBSD Foundation
+.\" All rights reserved.
+.\"
+.\" This documentation was written by Shteryana Sotirova Shopova 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 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 June 28, 2010
+.Dt SNMP_WLAN 3
+.Os
+.Sh NAME
+.Nm snmp_wlan
+.Nd "wireless networking module for"
+.Xr bsnmpd 1
+.Sh LIBRARY
+.Pq begemotSnmpdModulePath."wlan" = "/usr/lib/snmp_wlan.so"
+.Sh DESCRIPTION
+The
+.Nm snmp_wlan
+module implements a private BEGEMOT-WIRELESS-MIB, which allows
+management of virtual wireless interfaces. The MIB defines objects similar to the
+state data and configuration capabilities of
+.Xr ifconfig 8
+for configuring virtual wireless interfaces.
+Therefore one should consider adding write communities or loading the
+.Nm
+module on systems where security is crucial.
+.Sh IMPLEMENTATION NOTES
+A short description of the Tables and interesting objects in the MIB follows.
+.Bl -tag -width "XXXXXXXXX"
+.It Va wlanInterfaceTable
+The table is used for creation and deletion of virtual wireless interfaces. To
+add a new interface, a SET should be executed on the
+.Va wlanIfaceName
+column with
+value the desired name of the interface. Next the parent interface must be set
+via
+.Va wlanParentIfName
+column. Any optional parameters may be set
+via the
+.Va wlanIfaceOperatingMode ,
+.Va wlanIfaceFlags ,
+.Va wlanIfaceBssid
+and
+.Va wlanIfaceLocalAddress
+columns.
+To finally create the interface in the system, a SET with value of active(1) to
+.Va wlanIfaceStatus
+column should be executed.
+To destroy a wireless interface a SET with value of destroy(6) to the relevant
+.Va wlanIfaceStatus
+column should be executed.
+.It Va wlanIfParentTable
+The table contains information about the hardware capabilities of the parent of
+a wireless interface.
+.It Va wlanIfaceConfigTable
+The table is used to get or set various configuration parameters for a virtual
+wireless interface. Depending on the operating mode of the interface and the
+hardware capabilities of the underlying hardware interface, not all parameters
+and values may be supported.
+.It Va wlanIfacePeerTable
+The table contains information about the associated stations for interfaces
+operating as access points, or the stations identified as neighbors in the IBSS
+for interfaces operating in adhoc mode.
+.It Va wlanIfaceChannelTable
+Information about the active channels for the wireless interfaces in the system.
+.It Va wlanIfRoamParamsTable
+The parameters that govern the roaming operation on the wireless interfaces.
+.It Va wlanIfTxParamsTable
+The parameters that govern the transmit operation on the wireless interfaces.
+.It Va wlanScanConfigTable
+The table that contains a configuration for channel scanning initiated via SNMP.
+.It Va wlanScanResultsTable
+The table contains the scan results from the last scan for each wireless
+interface on the system.
+.It Va wlanIfaceStatisticsTable
+Summary statistics for each wireless interface on the system.
+.It Va wlanWepInterfaceTable
+WEP configuration for the wireless interfaces on the system.
+.It Va wlanMACAccessControlTable
+Access Control configuration for wireless interfaces operating as access points.
+.It Va wlanMACAccessControlMACTable
+The table with Access Control MAC entries for which the configured Access
+Control Policy on wireless interfaces operating in Host AP mode is applied.
+.Va wlanMACAccessControlMACStatus
+column is used to add or delete MAC ACL entries. A set with value createAndGo(4)
+will add new entry, while with value destroy(6) will delete an existing one.
+.It Va wlanMeshRoutingConfig
+The subtree contains system configuration related to Wireless Mesh Routing.
+.It Va wlanMeshInterfaceTable
+The table contains information for wireless interfaces operating as wireless
+mesh points.
+.It Va wlanMeshNeighborTable
+The table contains information for the neighbors of wireless interfaces
+operating in mesh mode.
+.It Va wlanMeshRouteTable
+The mesh routing table for interfaces operating as mesh points, used for
+forwarding packets on a mesh network.
+.Va wlanMeshRouteStatus
+column is used to add or delete entries in the mesh routing table for an
+interface. A set with value createAndGo(4) will add new entry, while with value
+destroy(6) will delete an existing one.
+.It Va wlanMeshStatsTable
+Summary statistics for each virtual wireless interface operating as mesh point.
+.It Va wlanMeshHWMPConfig
+The subtree contains system configuration related to Hybrid Wireless Mesh
+Protocol.
+.It Va wlanHWMPInterfaceTable
+The table contains HWMP information for wireless interfaces operating in mesh
+mode.
+.It Va wlanMeshHWMPStatsTable
+Summary statistics for HWMP operation on interfaces operating as mesh points.
+.El
+.Sh RESTRICTIONS
+Not all information or configuration in the MIBs is currently available in FreeBSD.
+The values of the following variables carry no information:
+.Bl -tag -width "XXXXXXXXX"
+.It Va wlanStatsReset
+.El
+.Sh FILES
+.Bl -tag -width "XXXXXXXXX"
+.It Pa /usr/share/snmp/defs/wlan_tree.def
+The description of the MIB tree implemented by
+.Nm .
+.It Pa /usr/share/snmp/mibs/BEGEMOT-WIRELESS-MIB.txt
+The private BEGEMOT-WIRELESS-MIB that is implemented by this module.
+.El
+.Sh SEE ALSO
+.Xr bsnmpd 1 ,
+.Xr gensnmptree 1 ,
+.Xr snmpmod 3 ,
+.Xr wlan 4 ,
+.Xr wlan_acl 4 ,
+.Xr wlan_wep 4 ,
+.Xr ifconfig 8
+.Sh AUTHORS
+.An Shteryana Shopova Aq Mt syrinx@FreeBSD.org
diff --git a/usr.sbin/bsnmpd/modules/snmp_wlan/wlan_snmp.c b/usr.sbin/bsnmpd/modules/snmp_wlan/wlan_snmp.c
new file mode 100644
index 0000000..ec94ac6
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_wlan/wlan_snmp.c
@@ -0,0 +1,4513 @@
+/*-
+ * Copyright (c) 2010 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Shteryana Sotirova Shopova 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/queue.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <net/if.h>
+#include <net/if_media.h>
+#include <net/if_mib.h>
+#include <net/if_types.h>
+#include <net80211/ieee80211.h>
+#include <net80211/ieee80211_ioctl.h>
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+
+#include <bsnmp/snmpmod.h>
+#include <bsnmp/snmp_mibII.h>
+
+#include "wlan_tree.h"
+#include "wlan_snmp.h"
+#include "wlan_oid.h"
+
+static struct lmodule *wlan_module;
+
+/* For the registration. */
+static const struct asn_oid oid_wlan = OIDX_begemotWlan;
+/* The registration. */
+static uint reg_wlan;
+
+/* Periodic timer for polling the module's data. */
+static void *wlan_data_timer;
+
+/*
+ * Poll data from kernel every 15 minutes unless explicitly requested by an
+ * SNMP client.
+ * XXX: make that configurable.
+ */
+static int wlan_poll_ticks = (15 * 60) * 100;
+
+/* The age of each table. */
+#define WLAN_LIST_MAXAGE 5
+
+static time_t wlan_iflist_age;
+static time_t wlan_peerlist_age;
+static time_t wlan_chanlist_age;
+static time_t wlan_roamlist_age;
+static time_t wlan_tx_paramlist_age;
+static time_t wlan_scanlist_age;
+static time_t wlan_maclist_age;
+static time_t wlan_mrlist_age;
+
+/*
+ * The list of all virtual wireless interfaces - sorted by name.
+ */
+SLIST_HEAD(wlan_ifaces, wlan_iface);
+static struct wlan_ifaces wlan_ifaces = SLIST_HEAD_INITIALIZER(wlan_ifaces);
+
+static struct wlan_config wlan_config;
+
+/* Forward declarations */
+static int bits_get(struct snmp_value *, const u_char *, ssize_t);
+
+static int wlan_add_wif(struct wlan_iface *);
+static void wlan_delete_wif(struct wlan_iface *);
+static int wlan_attach_newif(struct mibif *);
+static int wlan_iface_create(struct wlan_iface *);
+static int wlan_iface_destroy(struct wlan_iface *);
+static struct wlan_iface * wlan_new_wif(char *);
+
+static void wlan_free_interface(struct wlan_iface *);
+static void wlan_free_iflist(void);
+static void wlan_free_peerlist(struct wlan_iface *);
+static void wlan_scan_free_results(struct wlan_iface *);
+static void wlan_mac_free_maclist(struct wlan_iface *);
+static void wlan_mesh_free_routes(struct wlan_iface *);
+
+static int wlan_update_interface(struct wlan_iface *);
+static void wlan_update_interface_list(void);
+static void wlan_update_peers(void);
+static void wlan_update_channels(void);
+static void wlan_update_roam_params(void);
+static void wlan_update_tx_params(void);
+static void wlan_scan_update_results(void);
+static void wlan_mac_update_aclmacs(void);
+static void wlan_mesh_update_routes(void);
+
+static struct wlan_iface * wlan_find_interface(const char *);
+static struct wlan_peer * wlan_find_peer(struct wlan_iface *, uint8_t *);
+static struct ieee80211_channel* wlan_find_channel(struct wlan_iface *,
+ uint32_t);
+static struct wlan_scan_result * wlan_scan_find_result(struct wlan_iface *,
+ uint8_t *, uint8_t *);
+static struct wlan_mac_mac * wlan_mac_find_mac(struct wlan_iface *,
+ uint8_t *);
+static struct wlan_mesh_route * wlan_mesh_find_route(struct wlan_iface *,
+ uint8_t *);
+
+static struct wlan_iface * wlan_first_interface(void);
+static struct wlan_iface * wlan_next_interface(struct wlan_iface *);
+static struct wlan_iface * wlan_mesh_first_interface(void);
+static struct wlan_iface * wlan_mesh_next_interface(struct wlan_iface *);
+
+static struct wlan_iface * wlan_get_interface(const struct asn_oid *, uint);
+static struct wlan_iface * wlan_get_snmp_interface(const struct asn_oid *,
+ uint);
+static struct wlan_peer * wlan_get_peer(const struct asn_oid *, uint,
+ struct wlan_iface **);
+static struct ieee80211_channel *wlan_get_channel(const struct asn_oid *, uint,
+ struct wlan_iface **);
+static struct ieee80211_roamparam *wlan_get_roam_param(const struct asn_oid *,
+ uint, struct wlan_iface **);
+static struct ieee80211_txparam *wlan_get_tx_param(const struct asn_oid *,
+ uint, struct wlan_iface **, uint32_t *);
+static struct wlan_scan_result *wlan_get_scanr(const struct asn_oid *, uint,
+ struct wlan_iface **);
+static struct wlan_mac_mac * wlan_get_acl_mac(const struct asn_oid *,
+ uint, struct wlan_iface **);
+static struct wlan_iface * wlan_mesh_get_iface(const struct asn_oid *, uint);
+static struct wlan_peer * wlan_mesh_get_peer(const struct asn_oid *, uint,
+ struct wlan_iface **);
+static struct wlan_mesh_route * wlan_mesh_get_route(const struct asn_oid *,
+ uint, struct wlan_iface **);
+
+static struct wlan_iface * wlan_get_next_interface(const struct asn_oid *,
+ uint);
+static struct wlan_iface * wlan_get_next_snmp_interface(const struct
+ asn_oid *, uint);
+static struct wlan_peer * wlan_get_next_peer(const struct asn_oid *, uint,
+ struct wlan_iface **);
+static struct ieee80211_channel *wlan_get_next_channel(const struct asn_oid *,
+ uint, struct wlan_iface **);
+static struct ieee80211_roamparam *wlan_get_next_roam_param(const struct
+ asn_oid *, uint sub, struct wlan_iface **, uint32_t *);
+static struct ieee80211_txparam *wlan_get_next_tx_param(const struct asn_oid *,
+ uint, struct wlan_iface **, uint32_t *);
+static struct wlan_scan_result *wlan_get_next_scanr(const struct asn_oid *,
+ uint , struct wlan_iface **);
+static struct wlan_mac_mac * wlan_get_next_acl_mac(const struct asn_oid *,
+ uint, struct wlan_iface **);
+static struct wlan_iface * wlan_mesh_get_next_iface(const struct asn_oid *,
+ uint);
+static struct wlan_peer * wlan_mesh_get_next_peer(const struct asn_oid *,
+ uint, struct wlan_iface **);
+static struct wlan_mesh_route * wlan_mesh_get_next_route(const struct asn_oid *,
+ uint sub, struct wlan_iface **);
+
+static uint8_t *wlan_get_ifname(const struct asn_oid *, uint, uint8_t *);
+static int wlan_mac_index_decode(const struct asn_oid *, uint, char *,
+ uint8_t *);
+static int wlan_channel_index_decode(const struct asn_oid *, uint,
+ char *, uint32_t *);
+static int wlan_phy_index_decode(const struct asn_oid *, uint, char *,
+ uint32_t *);
+static int wlan_scanr_index_decode(const struct asn_oid *oid, uint sub,
+ char *wname, uint8_t *ssid, uint8_t *bssid);
+
+static void wlan_append_ifindex(struct asn_oid *, uint,
+ const struct wlan_iface *);
+static void wlan_append_mac_index(struct asn_oid *, uint, char *, uint8_t *);
+static void wlan_append_channel_index(struct asn_oid *, uint,
+ const struct wlan_iface *, const struct ieee80211_channel *);
+static void wlan_append_phy_index(struct asn_oid *, uint, char *, uint32_t);
+static void wlan_append_scanr_index(struct asn_oid *, uint, char *,
+ uint8_t *, uint8_t *);
+
+static int wlan_acl_mac_set_status(struct snmp_context *,
+ struct snmp_value *, uint);
+static int wlan_mesh_route_set_status(struct snmp_context *,
+ struct snmp_value *, uint);
+
+static int32_t wlan_get_channel_type(struct ieee80211_channel *);
+static int wlan_scan_compare_result(struct wlan_scan_result *,
+ struct wlan_scan_result *);
+static int wlan_mac_delete_mac(struct wlan_iface *, struct wlan_mac_mac *);
+static int wlan_mesh_delete_route(struct wlan_iface *,
+ struct wlan_mesh_route *);
+
+/*
+ * The module's GET/SET data hooks per each table or group of objects as
+ * required by bsnmpd(1).
+ */
+int
+op_wlan_iface(struct snmp_context *ctx, struct snmp_value *val, uint32_t sub,
+ uint32_t iidx __unused, enum snmp_op op)
+{
+ int rc;
+ char wname[IFNAMSIZ];
+ struct wlan_iface *wif;
+
+ wlan_update_interface_list();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((wif = wlan_get_snmp_interface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+
+ case SNMP_OP_GETNEXT:
+ if ((wif = wlan_get_next_snmp_interface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ wlan_append_ifindex(&val->var, sub, wif);
+ break;
+
+ case SNMP_OP_SET:
+ if ((wif = wlan_get_snmp_interface(&val->var, sub)) == NULL) {
+ if (val->var.subs[sub - 1] != LEAF_wlanIfaceName)
+ return (SNMP_ERR_NOSUCHNAME);
+ if (wlan_get_ifname(&val->var, sub, wname) == NULL)
+ return (SNMP_ERR_INCONS_VALUE);
+ if ((wif = wlan_new_wif(wname)) == NULL)
+ return (SNMP_ERR_GENERR);
+ wif->internal = 1;
+ }
+ if (wif->status == RowStatus_active &&
+ val->var.subs[sub - 1] != LEAF_wlanIfaceStatus &&
+ val->var.subs[sub - 1] != LEAF_wlanIfaceState)
+ return (SNMP_ERR_INCONS_VALUE);
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanIfaceIndex:
+ return (SNMP_ERR_NOT_WRITEABLE);
+
+ case LEAF_wlanIfaceName:
+ if (val->v.octetstring.len >= IFNAMSIZ)
+ return (SNMP_ERR_INCONS_VALUE);
+ if ((ctx->scratch->ptr1 = malloc(IFNAMSIZ)) == NULL)
+ return (SNMP_ERR_GENERR);
+ strlcpy(ctx->scratch->ptr1, wif->wname, IFNAMSIZ);
+ memcpy(wif->wname, val->v.octetstring.octets,
+ val->v.octetstring.len);
+ wif->wname[val->v.octetstring.len] = '\0';
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_wlanParentIfName:
+ if (val->v.octetstring.len >= IFNAMSIZ)
+ return (SNMP_ERR_INCONS_VALUE);
+ if ((ctx->scratch->ptr1 = malloc(IFNAMSIZ)) == NULL)
+ return (SNMP_ERR_GENERR);
+ strlcpy(ctx->scratch->ptr1, wif->pname, IFNAMSIZ);
+ memcpy(wif->pname, val->v.octetstring.octets,
+ val->v.octetstring.len);
+ wif->pname[val->v.octetstring.len] = '\0';
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_wlanIfaceOperatingMode:
+ ctx->scratch->int1 = wif->mode;
+ wif->mode = val->v.integer;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_wlanIfaceFlags:
+ if (val->v.octetstring.len > sizeof(wif->flags))
+ return (SNMP_ERR_INCONS_VALUE);
+ ctx->scratch->ptr1 = malloc(sizeof(wif->flags));
+ if (ctx->scratch->ptr1 == NULL)
+ return (SNMP_ERR_GENERR);
+ memcpy(ctx->scratch->ptr1, (uint8_t *)&wif->flags,
+ sizeof(wif->flags));
+ memcpy((uint8_t *)&wif->flags, val->v.octetstring.octets,
+ sizeof(wif->flags));
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_wlanIfaceBssid:
+ if (val->v.octetstring.len != IEEE80211_ADDR_LEN)
+ return (SNMP_ERR_INCONS_VALUE);
+ ctx->scratch->ptr1 = malloc(IEEE80211_ADDR_LEN);
+ if (ctx->scratch->ptr1 == NULL)
+ return (SNMP_ERR_GENERR);
+ memcpy(ctx->scratch->ptr1, wif->dbssid,
+ IEEE80211_ADDR_LEN);
+ memcpy(wif->dbssid, val->v.octetstring.octets,
+ IEEE80211_ADDR_LEN);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_wlanIfaceLocalAddress:
+ if (val->v.octetstring.len != IEEE80211_ADDR_LEN)
+ return (SNMP_ERR_INCONS_VALUE);
+ ctx->scratch->ptr1 = malloc(IEEE80211_ADDR_LEN);
+ if (ctx->scratch->ptr1 == NULL)
+ return (SNMP_ERR_GENERR);
+ memcpy(ctx->scratch->ptr1, wif->dlmac,
+ IEEE80211_ADDR_LEN);
+ memcpy(wif->dlmac, val->v.octetstring.octets,
+ IEEE80211_ADDR_LEN);
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_wlanIfaceStatus:
+ ctx->scratch->int1 = wif->status;
+ wif->status = val->v.integer;
+ if (wif->status == RowStatus_active) {
+ rc = wlan_iface_create(wif); /* XXX */
+ if (rc != SNMP_ERR_NOERROR) {
+ wif->status = ctx->scratch->int1;
+ return (rc);
+ }
+ } else if (wif->status == RowStatus_destroy)
+ return (wlan_iface_destroy(wif));
+ else
+ wif->status = RowStatus_notReady;
+ return (SNMP_ERR_NOERROR);
+
+ case LEAF_wlanIfaceState:
+ ctx->scratch->int1 = wif->state;
+ wif->state = val->v.integer;
+ if (wif->status == RowStatus_active)
+ if (wlan_config_state(wif, 1) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+ }
+ abort();
+
+ case SNMP_OP_ROLLBACK:
+ if ((wif = wlan_get_snmp_interface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanIfaceName:
+ strlcpy(wif->wname, ctx->scratch->ptr1, IFNAMSIZ);
+ free(ctx->scratch->ptr1);
+ break;
+
+ case LEAF_wlanParentIfName:
+ strlcpy(wif->pname, ctx->scratch->ptr1, IFNAMSIZ);
+ free(ctx->scratch->ptr1);
+ break;
+
+ case LEAF_wlanIfaceOperatingMode:
+ wif->mode = ctx->scratch->int1;
+ break;
+
+ case LEAF_wlanIfaceFlags:
+ memcpy((uint8_t *)&wif->flags, ctx->scratch->ptr1,
+ sizeof(wif->flags));
+ free(ctx->scratch->ptr1);
+ break;
+
+ case LEAF_wlanIfaceBssid:
+ memcpy(wif->dbssid, ctx->scratch->ptr1,
+ IEEE80211_ADDR_LEN);
+ free(ctx->scratch->ptr1);
+ break;
+
+ case LEAF_wlanIfaceLocalAddress:
+ memcpy(wif->dlmac, ctx->scratch->ptr1,
+ IEEE80211_ADDR_LEN);
+ free(ctx->scratch->ptr1);
+ break;
+
+ case LEAF_wlanIfaceStatus:
+ wif->status = ctx->scratch->int1;
+ if (ctx->scratch->int1 == RowStatus_active)
+ return (SNMP_ERR_GENERR); /* XXX: FIXME */
+ else if (wif->internal != 0)
+ return (wlan_iface_destroy(wif));
+ break;
+
+ case LEAF_wlanIfaceState:
+ wif->state = ctx->scratch->int1;
+ if (wif->status == RowStatus_active)
+ if (wlan_config_state(wif, 1) < 0)
+ return (SNMP_ERR_GENERR);
+ break;
+ }
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_COMMIT:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanIfaceName:
+ case LEAF_wlanParentIfName:
+ case LEAF_wlanIfaceFlags:
+ case LEAF_wlanIfaceBssid:
+ case LEAF_wlanIfaceLocalAddress:
+ free(ctx->scratch->ptr1);
+ /* FALLTHROUGH */
+ default:
+ return (SNMP_ERR_NOERROR);
+ }
+ default:
+ abort();
+ }
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanIfaceIndex:
+ val->v.integer = wif->index;
+ return (SNMP_ERR_NOERROR);
+ case LEAF_wlanIfaceName:
+ return (string_get(val, wif->wname, -1));
+ case LEAF_wlanParentIfName:
+ return (string_get(val, wif->pname, -1));
+ case LEAF_wlanIfaceOperatingMode:
+ val->v.integer = wif->mode;
+ return (SNMP_ERR_NOERROR);
+ case LEAF_wlanIfaceFlags:
+ return (bits_get(val, (uint8_t *)&wif->flags,
+ sizeof(wif->flags)));
+ case LEAF_wlanIfaceBssid:
+ return (string_get(val, wif->dbssid, IEEE80211_ADDR_LEN));
+ case LEAF_wlanIfaceLocalAddress:
+ return (string_get(val, wif->dlmac, IEEE80211_ADDR_LEN));
+ case LEAF_wlanIfaceStatus:
+ val->v.integer = wif->status;
+ return (SNMP_ERR_NOERROR);
+ case LEAF_wlanIfaceState:
+ val->v.integer = wif->state;
+ return (SNMP_ERR_NOERROR);
+ }
+
+ abort();
+}
+
+int
+op_wlan_if_parent(struct snmp_context *ctx __unused, struct snmp_value *val,
+ uint32_t sub, uint32_t iidx __unused, enum snmp_op op)
+{
+ struct wlan_iface *wif;
+
+ wlan_update_interface_list();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((wif = wlan_get_interface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+ case SNMP_OP_GETNEXT:
+ if ((wif = wlan_get_next_interface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ wlan_append_ifindex(&val->var, sub, wif);
+ break;
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ case SNMP_OP_COMMIT:
+ /* FALLTHROUGH */
+ case SNMP_OP_ROLLBACK:
+ /* FALLTHROUGH */
+ default:
+ abort();
+ }
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanIfParentDriverCapabilities:
+ return (bits_get(val, (uint8_t *)&wif->drivercaps,
+ sizeof(wif->drivercaps)));
+ case LEAF_wlanIfParentCryptoCapabilities:
+ return (bits_get(val, (uint8_t *)&wif->cryptocaps,
+ sizeof(wif->cryptocaps)));
+ case LEAF_wlanIfParentHTCapabilities:
+ return (bits_get(val, (uint8_t *)&wif->htcaps,
+ sizeof(wif->htcaps)));
+ }
+
+ abort();
+}
+
+int
+op_wlan_iface_config(struct snmp_context *ctx, struct snmp_value *val,
+ uint32_t sub, uint32_t iidx __unused, enum snmp_op op)
+{
+ int intval, vlen, rc;
+ char *strval;
+ struct wlan_iface *wif;
+
+ wlan_update_interface_list();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((wif = wlan_get_interface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get_config;
+
+ case SNMP_OP_GETNEXT:
+ if ((wif = wlan_get_next_interface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ wlan_append_ifindex(&val->var, sub, wif);
+ goto get_config;
+
+ case SNMP_OP_SET:
+ if ((wif = wlan_get_interface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+
+ intval = val->v.integer;
+ strval = NULL;
+ vlen = 0;
+
+ /* Simple sanity checks & save old data. */
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanIfaceCountryCode:
+ if (val->v.octetstring.len != WLAN_COUNTRY_CODE_SIZE)
+ return (SNMP_ERR_INCONS_VALUE);
+ break;
+ case LEAF_wlanIfaceDesiredSsid:
+ if (val->v.octetstring.len > IEEE80211_NWID_LEN)
+ return (SNMP_ERR_INCONS_VALUE);
+ break;
+ case LEAF_wlanIfaceDesiredBssid:
+ if (val->v.octetstring.len != IEEE80211_ADDR_LEN)
+ return (SNMP_ERR_INCONS_VALUE);
+ break;
+ case LEAF_wlanIfacePacketBurst:
+ ctx->scratch->int1 = wif->packet_burst;
+ break;
+ case LEAF_wlanIfaceRegDomain:
+ ctx->scratch->int1 = wif->reg_domain;
+ break;
+ case LEAF_wlanIfaceDesiredChannel:
+ ctx->scratch->int1 = wif->desired_channel;
+ break;
+ case LEAF_wlanIfaceDynamicFreqSelection:
+ ctx->scratch->int1 = wif->dyn_frequency;
+ break;
+ case LEAF_wlanIfaceFastFrames:
+ ctx->scratch->int1 = wif->fast_frames;
+ break;
+ case LEAF_wlanIfaceDturbo:
+ ctx->scratch->int1 = wif->dturbo;
+ break;
+ case LEAF_wlanIfaceTxPower:
+ ctx->scratch->int1 = wif->tx_power;
+ break;
+ case LEAF_wlanIfaceFragmentThreshold:
+ ctx->scratch->int1 = wif->frag_threshold;
+ break;
+ case LEAF_wlanIfaceRTSThreshold:
+ ctx->scratch->int1 = wif->rts_threshold;
+ break;
+ case LEAF_wlanIfaceWlanPrivacySubscribe:
+ ctx->scratch->int1 = wif->priv_subscribe;
+ break;
+ case LEAF_wlanIfaceBgScan:
+ ctx->scratch->int1 = wif->bg_scan;
+ break;
+ case LEAF_wlanIfaceBgScanIdle:
+ ctx->scratch->int1 = wif->bg_scan_idle;
+ break;
+ case LEAF_wlanIfaceBgScanInterval:
+ ctx->scratch->int1 = wif->bg_scan_interval;
+ break;
+ case LEAF_wlanIfaceBeaconMissedThreshold:
+ ctx->scratch->int1 = wif->beacons_missed;
+ break;
+ case LEAF_wlanIfaceRoamingMode:
+ ctx->scratch->int1 = wif->roam_mode;
+ break;
+ case LEAF_wlanIfaceDot11d:
+ ctx->scratch->int1 = wif->dot11d;
+ break;
+ case LEAF_wlanIfaceDot11h:
+ ctx->scratch->int1 = wif->dot11h;
+ break;
+ case LEAF_wlanIfaceDynamicWds:
+ ctx->scratch->int1 = wif->dynamic_wds;
+ break;
+ case LEAF_wlanIfacePowerSave:
+ ctx->scratch->int1 = wif->power_save;
+ break;
+ case LEAF_wlanIfaceApBridge:
+ ctx->scratch->int1 = wif->ap_bridge;
+ break;
+ case LEAF_wlanIfaceBeaconInterval:
+ ctx->scratch->int1 = wif->beacon_interval;
+ break;
+ case LEAF_wlanIfaceDtimPeriod:
+ ctx->scratch->int1 = wif->dtim_period;
+ break;
+ case LEAF_wlanIfaceHideSsid:
+ ctx->scratch->int1 = wif->hide_ssid;
+ break;
+ case LEAF_wlanIfaceInactivityProccess:
+ ctx->scratch->int1 = wif->inact_process;
+ break;
+ case LEAF_wlanIfaceDot11gProtMode:
+ ctx->scratch->int1 = wif->do11g_protect;
+ break;
+ case LEAF_wlanIfaceDot11gPureMode:
+ ctx->scratch->int1 = wif->dot11g_pure;
+ break;
+ case LEAF_wlanIfaceDot11nPureMode:
+ ctx->scratch->int1 = wif->dot11n_pure;
+ break;
+ case LEAF_wlanIfaceDot11nAmpdu:
+ ctx->scratch->int1 = wif->ampdu;
+ break;
+ case LEAF_wlanIfaceDot11nAmpduDensity:
+ ctx->scratch->int1 = wif->ampdu_density;
+ break;
+ case LEAF_wlanIfaceDot11nAmpduLimit:
+ ctx->scratch->int1 = wif->ampdu_limit;
+ break;
+ case LEAF_wlanIfaceDot11nAmsdu:
+ ctx->scratch->int1 = wif->amsdu;
+ break;
+ case LEAF_wlanIfaceDot11nAmsduLimit:
+ ctx->scratch->int1 = wif->amsdu_limit;
+ break;
+ case LEAF_wlanIfaceDot11nHighThroughput:
+ ctx->scratch->int1 = wif->ht_enabled;
+ break;
+ case LEAF_wlanIfaceDot11nHTCompatible:
+ ctx->scratch->int1 = wif->ht_compatible;
+ break;
+ case LEAF_wlanIfaceDot11nHTProtMode:
+ ctx->scratch->int1 = wif->ht_prot_mode;
+ break;
+ case LEAF_wlanIfaceDot11nRIFS:
+ ctx->scratch->int1 = wif->rifs;
+ break;
+ case LEAF_wlanIfaceDot11nShortGI:
+ ctx->scratch->int1 = wif->short_gi;
+ break;
+ case LEAF_wlanIfaceDot11nSMPSMode:
+ ctx->scratch->int1 = wif->smps_mode;
+ break;
+ case LEAF_wlanIfaceTdmaSlot:
+ ctx->scratch->int1 = wif->tdma_slot;
+ break;
+ case LEAF_wlanIfaceTdmaSlotCount:
+ ctx->scratch->int1 = wif->tdma_slot_count;
+ break;
+ case LEAF_wlanIfaceTdmaSlotLength:
+ ctx->scratch->int1 = wif->tdma_slot_length;
+ break;
+ case LEAF_wlanIfaceTdmaBeaconInterval:
+ ctx->scratch->int1 = wif->tdma_binterval;
+ break;
+ default:
+ abort();
+ }
+
+ if (val->syntax != SNMP_SYNTAX_OCTETSTRING)
+ goto set_config;
+
+ ctx->scratch->int1 = val->v.octetstring.len;
+ ctx->scratch->ptr1 = malloc(val->v.octetstring.len + 1);
+ if (ctx->scratch->ptr1 == NULL)
+ return (SNMP_ERR_GENERR); /* XXX */
+ if (val->var.subs[sub - 1] == LEAF_wlanIfaceDesiredSsid)
+ strlcpy(ctx->scratch->ptr1, val->v.octetstring.octets,
+ val->v.octetstring.len + 1);
+ else
+ memcpy(ctx->scratch->ptr1, val->v.octetstring.octets,
+ val->v.octetstring.len);
+ strval = val->v.octetstring.octets;
+ vlen = val->v.octetstring.len;
+ goto set_config;
+
+ case SNMP_OP_ROLLBACK:
+ intval = ctx->scratch->int1;
+ strval = NULL;
+ vlen = 0;
+
+ if ((wif = wlan_get_interface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanIfaceCountryCode:
+ case LEAF_wlanIfaceDesiredSsid:
+ case LEAF_wlanIfaceDesiredBssid:
+ strval = ctx->scratch->ptr1;
+ vlen = ctx->scratch->int1;
+ break;
+ default:
+ break;
+ }
+ goto set_config;
+
+ case SNMP_OP_COMMIT:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanIfaceCountryCode:
+ case LEAF_wlanIfaceDesiredSsid:
+ case LEAF_wlanIfaceDesiredBssid:
+ free(ctx->scratch->ptr1);
+ /* FALLTHROUGH */
+ default:
+ return (SNMP_ERR_NOERROR);
+ }
+ }
+ abort();
+
+get_config:
+
+ if (wlan_config_get_ioctl(wif, val->var.subs[sub - 1]) < 0)
+ return (SNMP_ERR_GENERR);
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanIfacePacketBurst:
+ val->v.integer = wif->packet_burst;
+ break;
+ case LEAF_wlanIfaceCountryCode:
+ return (string_get(val, wif->country_code,
+ WLAN_COUNTRY_CODE_SIZE));
+ case LEAF_wlanIfaceRegDomain:
+ val->v.integer = wif->reg_domain;
+ break;
+ case LEAF_wlanIfaceDesiredSsid:
+ return (string_get(val, wif->desired_ssid, -1));
+ case LEAF_wlanIfaceDesiredChannel:
+ val->v.integer = wif->desired_channel;
+ break;
+ case LEAF_wlanIfaceDynamicFreqSelection:
+ val->v.integer = wif->dyn_frequency;
+ break;
+ case LEAF_wlanIfaceFastFrames:
+ val->v.integer = wif->fast_frames;
+ break;
+ case LEAF_wlanIfaceDturbo:
+ val->v.integer = wif->dturbo;
+ break;
+ case LEAF_wlanIfaceTxPower:
+ val->v.integer = wif->tx_power;
+ break;
+ case LEAF_wlanIfaceFragmentThreshold:
+ val->v.integer = wif->frag_threshold;
+ break;
+ case LEAF_wlanIfaceRTSThreshold:
+ val->v.integer = wif->rts_threshold;
+ break;
+ case LEAF_wlanIfaceWlanPrivacySubscribe:
+ val->v.integer = wif->priv_subscribe;
+ break;
+ case LEAF_wlanIfaceBgScan:
+ val->v.integer = wif->bg_scan;
+ break;
+ case LEAF_wlanIfaceBgScanIdle:
+ val->v.integer = wif->bg_scan_idle;
+ break;
+ case LEAF_wlanIfaceBgScanInterval:
+ val->v.integer = wif->bg_scan_interval;
+ break;
+ case LEAF_wlanIfaceBeaconMissedThreshold:
+ val->v.integer = wif->beacons_missed;
+ break;
+ case LEAF_wlanIfaceDesiredBssid:
+ return (string_get(val, wif->desired_bssid,
+ IEEE80211_ADDR_LEN));
+ case LEAF_wlanIfaceRoamingMode:
+ val->v.integer = wif->roam_mode;
+ break;
+ case LEAF_wlanIfaceDot11d:
+ val->v.integer = wif->dot11d;
+ break;
+ case LEAF_wlanIfaceDot11h:
+ val->v.integer = wif->dot11h;
+ break;
+ case LEAF_wlanIfaceDynamicWds:
+ val->v.integer = wif->dynamic_wds;
+ break;
+ case LEAF_wlanIfacePowerSave:
+ val->v.integer = wif->power_save;
+ break;
+ case LEAF_wlanIfaceApBridge:
+ val->v.integer = wif->ap_bridge;
+ break;
+ case LEAF_wlanIfaceBeaconInterval:
+ val->v.integer = wif->beacon_interval;
+ break;
+ case LEAF_wlanIfaceDtimPeriod:
+ val->v.integer = wif->dtim_period;
+ break;
+ case LEAF_wlanIfaceHideSsid:
+ val->v.integer = wif->hide_ssid;
+ break;
+ case LEAF_wlanIfaceInactivityProccess:
+ val->v.integer = wif->inact_process;
+ break;
+ case LEAF_wlanIfaceDot11gProtMode:
+ val->v.integer = wif->do11g_protect;
+ break;
+ case LEAF_wlanIfaceDot11gPureMode:
+ val->v.integer = wif->dot11g_pure;
+ break;
+ case LEAF_wlanIfaceDot11nPureMode:
+ val->v.integer = wif->dot11n_pure;
+ break;
+ case LEAF_wlanIfaceDot11nAmpdu:
+ val->v.integer = wif->ampdu;
+ break;
+ case LEAF_wlanIfaceDot11nAmpduDensity:
+ val->v.integer = wif->ampdu_density;
+ break;
+ case LEAF_wlanIfaceDot11nAmpduLimit:
+ val->v.integer = wif->ampdu_limit;
+ break;
+ case LEAF_wlanIfaceDot11nAmsdu:
+ val->v.integer = wif->amsdu;
+ break;
+ case LEAF_wlanIfaceDot11nAmsduLimit:
+ val->v.integer = wif->amsdu_limit;
+ break;
+ case LEAF_wlanIfaceDot11nHighThroughput:
+ val->v.integer = wif->ht_enabled;
+ break;
+ case LEAF_wlanIfaceDot11nHTCompatible:
+ val->v.integer = wif->ht_compatible;
+ break;
+ case LEAF_wlanIfaceDot11nHTProtMode:
+ val->v.integer = wif->ht_prot_mode;
+ break;
+ case LEAF_wlanIfaceDot11nRIFS:
+ val->v.integer = wif->rifs;
+ break;
+ case LEAF_wlanIfaceDot11nShortGI:
+ val->v.integer = wif->short_gi;
+ break;
+ case LEAF_wlanIfaceDot11nSMPSMode:
+ val->v.integer = wif->smps_mode;
+ break;
+ case LEAF_wlanIfaceTdmaSlot:
+ val->v.integer = wif->tdma_slot;
+ break;
+ case LEAF_wlanIfaceTdmaSlotCount:
+ val->v.integer = wif->tdma_slot_count;
+ break;
+ case LEAF_wlanIfaceTdmaSlotLength:
+ val->v.integer = wif->tdma_slot_length;
+ break;
+ case LEAF_wlanIfaceTdmaBeaconInterval:
+ val->v.integer = wif->tdma_binterval;
+ break;
+ }
+
+ return (SNMP_ERR_NOERROR);
+
+set_config:
+ rc = wlan_config_set_ioctl(wif, val->var.subs[sub - 1], intval,
+ strval, vlen);
+
+ if (op == SNMP_OP_ROLLBACK) {
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanIfaceCountryCode:
+ case LEAF_wlanIfaceDesiredSsid:
+ case LEAF_wlanIfaceDesiredBssid:
+ free(ctx->scratch->ptr1);
+ /* FALLTHROUGH */
+ default:
+ break;
+ }
+ }
+
+ if (rc < 0)
+ return (SNMP_ERR_GENERR);
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+op_wlan_if_peer(struct snmp_context *ctx, struct snmp_value *val, uint32_t sub,
+ uint32_t iidx __unused, enum snmp_op op)
+{
+ struct wlan_peer *wip;
+ struct wlan_iface *wif;
+
+ wlan_update_interface_list();
+ wlan_update_peers();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((wip = wlan_get_peer(&val->var, sub, &wif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+ case SNMP_OP_GETNEXT:
+ if ((wip = wlan_get_next_peer(&val->var, sub, &wif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ wlan_append_mac_index(&val->var, sub, wif->wname, wip->pmac);
+ break;
+ case SNMP_OP_SET:
+ if ((wip = wlan_get_peer(&val->var, sub, &wif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ if (val->var.subs[sub - 1] != LEAF_wlanIfacePeerVlanTag)
+ return (SNMP_ERR_GENERR);
+ ctx->scratch->int1 = wip->vlan;
+ if (wlan_peer_set_vlan(wif, wip, val->v.integer) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+ case SNMP_OP_COMMIT:
+ return (SNMP_ERR_NOERROR);
+ case SNMP_OP_ROLLBACK:
+ if ((wip = wlan_get_peer(&val->var, sub, &wif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ if (val->var.subs[sub - 1] != LEAF_wlanIfacePeerVlanTag)
+ return (SNMP_ERR_GENERR);
+ if (wlan_peer_set_vlan(wif, wip, ctx->scratch->int1) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+ default:
+ abort();
+ }
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanIfacePeerAddress:
+ return (string_get(val, wip->pmac, IEEE80211_ADDR_LEN));
+ case LEAF_wlanIfacePeerAssociationId:
+ val->v.integer = wip->associd;
+ break;
+ case LEAF_wlanIfacePeerVlanTag:
+ val->v.integer = wip->vlan;
+ break;
+ case LEAF_wlanIfacePeerFrequency:
+ val->v.integer = wip->frequency;
+ break;
+ case LEAF_wlanIfacePeerCurrentTXRate:
+ val->v.integer = wip->txrate;
+ break;
+ case LEAF_wlanIfacePeerRxSignalStrength:
+ val->v.integer = wip->rssi;
+ break;
+ case LEAF_wlanIfacePeerIdleTimer:
+ val->v.integer = wip->idle;
+ break;
+ case LEAF_wlanIfacePeerTxSequenceNo:
+ val->v.integer = wip->txseqs;
+ break;
+ case LEAF_wlanIfacePeerRxSequenceNo:
+ val->v.integer = wip->rxseqs;
+ break;
+ case LEAF_wlanIfacePeerTxPower:
+ val->v.integer = wip->txpower;
+ break;
+ case LEAF_wlanIfacePeerCapabilities:
+ return (bits_get(val, (uint8_t *)&wip->capinfo,
+ sizeof(wip->capinfo)));
+ case LEAF_wlanIfacePeerFlags:
+ return (bits_get(val, (uint8_t *)&wip->state,
+ sizeof(wip->state)));
+ default:
+ abort();
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+op_wlan_channels(struct snmp_context *ctx __unused, struct snmp_value *val,
+ uint32_t sub, uint32_t iidx __unused, enum snmp_op op)
+{
+ int32_t bits;
+ struct ieee80211_channel *channel;
+ struct wlan_iface *wif;
+
+ wlan_update_interface_list();
+ wlan_update_channels();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((channel = wlan_get_channel(&val->var, sub, &wif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+ case SNMP_OP_GETNEXT:
+ channel = wlan_get_next_channel(&val->var, sub, &wif);
+ if (channel == NULL || wif == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ wlan_append_channel_index(&val->var, sub, wif, channel);
+ break;
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ case SNMP_OP_COMMIT:
+ /* FALLTHROUGH */
+ case SNMP_OP_ROLLBACK:
+ /* FALLTHROUGH */
+ default:
+ abort();
+ }
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanIfaceChannelIeeeId:
+ val->v.integer = channel->ic_ieee;
+ break;
+ case LEAF_wlanIfaceChannelType:
+ val->v.integer = wlan_get_channel_type(channel);
+ break;
+ case LEAF_wlanIfaceChannelFlags:
+ bits = wlan_channel_flags_to_snmp(channel->ic_flags);
+ return (bits_get(val, (uint8_t *)&bits, sizeof(bits)));
+ case LEAF_wlanIfaceChannelFrequency:
+ val->v.integer = channel->ic_freq;
+ break;
+ case LEAF_wlanIfaceChannelMaxRegPower:
+ val->v.integer = channel->ic_maxregpower;
+ break;
+ case LEAF_wlanIfaceChannelMaxTxPower:
+ val->v.integer = channel->ic_maxpower;
+ break;
+ case LEAF_wlanIfaceChannelMinTxPower:
+ val->v.integer = channel->ic_minpower;
+ break;
+ case LEAF_wlanIfaceChannelState:
+ bits = wlan_channel_state_to_snmp(channel->ic_state);
+ return (bits_get(val, (uint8_t *)&bits, sizeof(bits)));
+ case LEAF_wlanIfaceChannelHTExtension:
+ val->v.integer = channel->ic_extieee;
+ break;
+ case LEAF_wlanIfaceChannelMaxAntennaGain:
+ val->v.integer = channel->ic_maxantgain;
+ break;
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+op_wlan_roam_params(struct snmp_context *ctx __unused, struct snmp_value *val,
+ uint32_t sub, uint32_t iidx __unused, enum snmp_op op)
+{
+ uint32_t phy;
+ struct ieee80211_roamparam *rparam;
+ struct wlan_iface *wif;
+
+ wlan_update_interface_list();
+ wlan_update_roam_params();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ rparam = wlan_get_roam_param(&val->var, sub, &wif);
+ if (rparam == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+ case SNMP_OP_GETNEXT:
+ rparam = wlan_get_next_roam_param(&val->var, sub, &wif, &phy);
+ if (rparam == NULL || wif == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ wlan_append_phy_index(&val->var, sub, wif->wname, phy);
+ break;
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ case SNMP_OP_COMMIT:
+ /* FALLTHROUGH */
+ case SNMP_OP_ROLLBACK:
+ /* FALLTHROUGH */
+ default:
+ abort();
+ }
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanIfRoamRxSignalStrength:
+ val->v.integer = rparam->rssi/2;
+ break;
+ case LEAF_wlanIfRoamTxRateThreshold:
+ val->v.integer = rparam->rate/2;
+ break;
+ default:
+ abort();
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+op_wlan_tx_params(struct snmp_context *ctx, struct snmp_value *val,
+ uint32_t sub, uint32_t iidx __unused, enum snmp_op op)
+{
+ uint32_t phy;
+ struct ieee80211_txparam *txparam;
+ struct wlan_iface *wif;
+
+ wlan_update_interface_list();
+ wlan_update_tx_params();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ txparam = wlan_get_tx_param(&val->var, sub, &wif, &phy);
+ if (txparam == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ goto get_txparams;
+
+ case SNMP_OP_GETNEXT:
+ txparam = wlan_get_next_tx_param(&val->var, sub, &wif, &phy);
+ if (txparam == NULL || wif == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ wlan_append_phy_index(&val->var, sub, wif->wname, phy);
+ goto get_txparams;
+
+ case SNMP_OP_SET:
+ txparam = wlan_get_tx_param(&val->var, sub, &wif, &phy);
+ if (txparam == NULL || wif == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanIfTxUnicastRate:
+ ctx->scratch->int1 = txparam->ucastrate;
+ txparam->ucastrate = val->v.integer * 2;
+ break;
+ case LEAF_wlanIfTxMcastRate:
+ ctx->scratch->int1 = txparam->mcastrate;
+ txparam->mcastrate = val->v.integer * 2;
+ break;
+ case LEAF_wlanIfTxMgmtRate:
+ ctx->scratch->int1 = txparam->mgmtrate;
+ txparam->mgmtrate = val->v.integer * 2;
+ break;
+ case LEAF_wlanIfTxMaxRetryCount:
+ ctx->scratch->int1 = txparam->maxretry;
+ txparam->maxretry = val->v.integer;
+ break;
+ default:
+ abort();
+ }
+ if (wlan_set_tx_params(wif, phy) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_COMMIT:
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_ROLLBACK:
+ txparam = wlan_get_tx_param(&val->var, sub, &wif, &phy);
+ if (txparam == NULL || wif == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanIfTxUnicastRate:
+ txparam->ucastrate = ctx->scratch->int1;
+ break;
+ case LEAF_wlanIfTxMcastRate:
+ txparam->mcastrate = ctx->scratch->int1;
+ break;
+ case LEAF_wlanIfTxMgmtRate:
+ txparam->mgmtrate = ctx->scratch->int1;
+ break;
+ case LEAF_wlanIfTxMaxRetryCount:
+ txparam->maxretry = ctx->scratch->int1;
+ break;
+ default:
+ abort();
+ }
+ if (wlan_set_tx_params(wif, phy) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+ default:
+ abort();
+ }
+
+get_txparams:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanIfTxUnicastRate:
+ val->v.integer = txparam->ucastrate / 2;
+ break;
+ case LEAF_wlanIfTxMcastRate:
+ val->v.integer = txparam->mcastrate / 2;
+ break;
+ case LEAF_wlanIfTxMgmtRate:
+ val->v.integer = txparam->mgmtrate / 2;
+ break;
+ case LEAF_wlanIfTxMaxRetryCount:
+ val->v.integer = txparam->maxretry;
+ break;
+ default:
+ abort();
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+op_wlan_scan_config(struct snmp_context *ctx, struct snmp_value *val,
+ uint32_t sub, uint32_t iidx __unused, enum snmp_op op)
+{
+ struct wlan_iface *wif;
+
+ wlan_update_interface_list();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((wif = wlan_get_interface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+
+ case SNMP_OP_GETNEXT:
+ if ((wif = wlan_get_next_interface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ wlan_append_ifindex(&val->var, sub, wif);
+ break;
+
+ case SNMP_OP_SET:
+ if ((wif = wlan_get_interface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ if (wif->scan_status == wlanScanConfigStatus_running
+ && val->var.subs[sub - 1] != LEAF_wlanScanConfigStatus)
+ return (SNMP_ERR_INCONS_VALUE);
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanScanFlags:
+ ctx->scratch->int1 = wif->scan_flags;
+ wif->scan_flags = val->v.integer;
+ break;
+ case LEAF_wlanScanDuration:
+ ctx->scratch->int1 = wif->scan_duration;
+ wif->scan_duration = val->v.integer;
+ break;
+ case LEAF_wlanScanMinChannelDwellTime:
+ ctx->scratch->int1 = wif->scan_mindwell;
+ wif->scan_mindwell = val->v.integer;
+ break;
+ case LEAF_wlanScanMaxChannelDwellTime:
+ ctx->scratch->int1 = wif->scan_maxdwell;
+ wif->scan_maxdwell = val->v.integer;
+ break;
+ case LEAF_wlanScanConfigStatus:
+ if (val->v.integer == wlanScanConfigStatus_running ||
+ val->v.integer == wlanScanConfigStatus_cancel) {
+ ctx->scratch->int1 = wif->scan_status;
+ wif->scan_status = val->v.integer;
+ break;
+ }
+ return (SNMP_ERR_INCONS_VALUE);
+ }
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_COMMIT:
+ if ((wif = wlan_get_interface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ if (val->var.subs[sub - 1] == LEAF_wlanScanConfigStatus)
+ if (wif->scan_status == wlanScanConfigStatus_running)
+ (void)wlan_set_scan_config(wif); /* XXX */
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_ROLLBACK:
+ if ((wif = wlan_get_interface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanScanFlags:
+ wif->scan_flags = ctx->scratch->int1;
+ break;
+ case LEAF_wlanScanDuration:
+ wif->scan_duration = ctx->scratch->int1;
+ break;
+ case LEAF_wlanScanMinChannelDwellTime:
+ wif->scan_mindwell = ctx->scratch->int1;
+ break;
+ case LEAF_wlanScanMaxChannelDwellTime:
+ wif->scan_maxdwell = ctx->scratch->int1;
+ break;
+ case LEAF_wlanScanConfigStatus:
+ wif->scan_status = ctx->scratch->int1;
+ break;
+ }
+ return (SNMP_ERR_NOERROR);
+ default:
+ abort();
+ }
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanScanFlags:
+ val->v.integer = wif->scan_flags;
+ break;
+ case LEAF_wlanScanDuration:
+ val->v.integer = wif->scan_duration;
+ break;
+ case LEAF_wlanScanMinChannelDwellTime:
+ val->v.integer = wif->scan_mindwell;
+ break;
+ case LEAF_wlanScanMaxChannelDwellTime:
+ val->v.integer = wif->scan_maxdwell;
+ break;
+ case LEAF_wlanScanConfigStatus:
+ val->v.integer = wif->scan_status;
+ break;
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+op_wlan_scan_results(struct snmp_context *ctx __unused, struct snmp_value *val,
+ uint32_t sub, uint32_t iidx __unused, enum snmp_op op)
+{
+ struct wlan_scan_result *sr;
+ struct wlan_iface *wif;
+
+ wlan_update_interface_list();
+ wlan_scan_update_results();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((sr = wlan_get_scanr(&val->var, sub, &wif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+
+ case SNMP_OP_GETNEXT:
+ if ((sr = wlan_get_next_scanr(&val->var, sub, &wif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ wlan_append_scanr_index(&val->var, sub, wif->wname, sr->ssid,
+ sr->bssid);
+ break;
+
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ case SNMP_OP_COMMIT:
+ /* FALLTHROUGH */
+ case SNMP_OP_ROLLBACK:
+ /* FALLTHROUGH */
+ default:
+ abort();
+ }
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanScanResultID:
+ return (string_get(val, sr->ssid, -1));
+ case LEAF_wlanScanResultBssid:
+ return (string_get(val, sr->bssid, IEEE80211_ADDR_LEN));
+ case LEAF_wlanScanResultChannel:
+ val->v.integer = sr->opchannel; /* XXX */
+ break;
+ case LEAF_wlanScanResultRate:
+ val->v.integer = sr->rssi;
+ break;
+ case LEAF_wlanScanResultNoise:
+ val->v.integer = sr->noise;
+ break;
+ case LEAF_wlanScanResultBeaconInterval:
+ val->v.integer = sr->bintval;
+ break;
+ case LEAF_wlanScanResultCapabilities:
+ return (bits_get(val, &sr->capinfo, sizeof(sr->capinfo)));
+ default:
+ abort();
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+op_wlan_iface_stats(struct snmp_context *ctx __unused, struct snmp_value *val,
+ uint32_t sub, uint32_t iidx __unused, enum snmp_op op)
+{
+ struct wlan_iface *wif;
+
+ wlan_update_interface_list();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((wif = wlan_get_interface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+ case SNMP_OP_GETNEXT:
+ if ((wif = wlan_get_next_interface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ wlan_append_ifindex(&val->var, sub, wif);
+ break;
+ case SNMP_OP_SET:
+ /* XXX: LEAF_wlanStatsReset */
+ return (SNMP_ERR_NOT_WRITEABLE);
+ case SNMP_OP_COMMIT:
+ /* FALLTHROUGH */
+ case SNMP_OP_ROLLBACK:
+ /* FALLTHROUGH */
+ default:
+ abort();
+ }
+
+ if (wlan_get_stats(wif) < 0)
+ return (SNMP_ERR_GENERR);
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanStatsRxBadVersion:
+ val->v.uint32 = wif->stats.is_rx_badversion;
+ break;
+ case LEAF_wlanStatsRxTooShort:
+ val->v.uint32 = wif->stats.is_rx_tooshort;
+ break;
+ case LEAF_wlanStatsRxWrongBssid:
+ val->v.uint32 = wif->stats.is_rx_wrongbss;
+ break;
+ case LEAF_wlanStatsRxDiscardedDups:
+ val->v.uint32 = wif->stats.is_rx_dup;
+ break;
+ case LEAF_wlanStatsRxWrongDir:
+ val->v.uint32 = wif->stats.is_rx_wrongdir;
+ break;
+ case LEAF_wlanStatsRxDiscardMcastEcho:
+ val->v.uint32 = wif->stats.is_rx_mcastecho;
+ break;
+ case LEAF_wlanStatsRxDiscardNoAssoc:
+ val->v.uint32 = wif->stats.is_rx_notassoc;
+ break;
+ case LEAF_wlanStatsRxWepNoPrivacy:
+ val->v.uint32 = wif->stats.is_rx_noprivacy;
+ break;
+ case LEAF_wlanStatsRxWepUnencrypted:
+ val->v.uint32 = wif->stats.is_rx_unencrypted;
+ break;
+ case LEAF_wlanStatsRxWepFailed:
+ val->v.uint32 = wif->stats.is_rx_wepfail;
+ break;
+ case LEAF_wlanStatsRxDecapsulationFailed:
+ val->v.uint32 = wif->stats.is_rx_decap;
+ break;
+ case LEAF_wlanStatsRxDiscardMgmt:
+ val->v.uint32 = wif->stats.is_rx_mgtdiscard;
+ break;
+ case LEAF_wlanStatsRxControl:
+ val->v.uint32 = wif->stats.is_rx_ctl;
+ break;
+ case LEAF_wlanStatsRxBeacon:
+ val->v.uint32 = wif->stats.is_rx_beacon;
+ break;
+ case LEAF_wlanStatsRxRateSetTooBig:
+ val->v.uint32 = wif->stats.is_rx_rstoobig;
+ break;
+ case LEAF_wlanStatsRxElemMissing:
+ val->v.uint32 = wif->stats.is_rx_elem_missing;
+ break;
+ case LEAF_wlanStatsRxElemTooBig:
+ val->v.uint32 = wif->stats.is_rx_elem_toobig;
+ break;
+ case LEAF_wlanStatsRxElemTooSmall:
+ val->v.uint32 = wif->stats.is_rx_elem_toosmall;
+ break;
+ case LEAF_wlanStatsRxElemUnknown:
+ val->v.uint32 = wif->stats.is_rx_elem_unknown;
+ break;
+ case LEAF_wlanStatsRxChannelMismatch:
+ val->v.uint32 = wif->stats.is_rx_chanmismatch;
+ break;
+ case LEAF_wlanStatsRxDropped:
+ val->v.uint32 = wif->stats.is_rx_nodealloc;
+ break;
+ case LEAF_wlanStatsRxSsidMismatch:
+ val->v.uint32 = wif->stats.is_rx_ssidmismatch;
+ break;
+ case LEAF_wlanStatsRxAuthNotSupported:
+ val->v.uint32 = wif->stats.is_rx_auth_unsupported;
+ break;
+ case LEAF_wlanStatsRxAuthFailed:
+ val->v.uint32 = wif->stats.is_rx_auth_fail;
+ break;
+ case LEAF_wlanStatsRxAuthCM:
+ val->v.uint32 = wif->stats.is_rx_auth_countermeasures;
+ break;
+ case LEAF_wlanStatsRxAssocWrongBssid:
+ val->v.uint32 = wif->stats.is_rx_assoc_bss;
+ break;
+ case LEAF_wlanStatsRxAssocNoAuth:
+ val->v.uint32 = wif->stats.is_rx_assoc_notauth;
+ break;
+ case LEAF_wlanStatsRxAssocCapMismatch:
+ val->v.uint32 = wif->stats.is_rx_assoc_capmismatch;
+ break;
+ case LEAF_wlanStatsRxAssocNoRateMatch:
+ val->v.uint32 = wif->stats.is_rx_assoc_norate;
+ break;
+ case LEAF_wlanStatsRxBadWpaIE:
+ val->v.uint32 = wif->stats.is_rx_assoc_badwpaie;
+ break;
+ case LEAF_wlanStatsRxDeauthenticate:
+ val->v.uint32 = wif->stats.is_rx_deauth;
+ break;
+ case LEAF_wlanStatsRxDisassociate:
+ val->v.uint32 = wif->stats.is_rx_disassoc;
+ break;
+ case LEAF_wlanStatsRxUnknownSubtype:
+ val->v.uint32 = wif->stats.is_rx_badsubtype;
+ break;
+ case LEAF_wlanStatsRxFailedNoBuf:
+ val->v.uint32 = wif->stats.is_rx_nobuf;
+ break;
+ case LEAF_wlanStatsRxBadAuthRequest:
+ val->v.uint32 = wif->stats.is_rx_bad_auth;
+ break;
+ case LEAF_wlanStatsRxUnAuthorized:
+ val->v.uint32 = wif->stats.is_rx_unauth;
+ break;
+ case LEAF_wlanStatsRxBadKeyId:
+ val->v.uint32 = wif->stats.is_rx_badkeyid;
+ break;
+ case LEAF_wlanStatsRxCCMPSeqViolation:
+ val->v.uint32 = wif->stats.is_rx_ccmpreplay;
+ break;
+ case LEAF_wlanStatsRxCCMPBadFormat:
+ val->v.uint32 = wif->stats.is_rx_ccmpformat;
+ break;
+ case LEAF_wlanStatsRxCCMPFailedMIC:
+ val->v.uint32 = wif->stats.is_rx_ccmpmic;
+ break;
+ case LEAF_wlanStatsRxTKIPSeqViolation:
+ val->v.uint32 = wif->stats.is_rx_tkipreplay;
+ break;
+ case LEAF_wlanStatsRxTKIPBadFormat:
+ val->v.uint32 = wif->stats.is_rx_tkipformat;
+ break;
+ case LEAF_wlanStatsRxTKIPFailedMIC:
+ val->v.uint32 = wif->stats.is_rx_tkipmic;
+ break;
+ case LEAF_wlanStatsRxTKIPFailedICV:
+ val->v.uint32 = wif->stats.is_rx_tkipicv;
+ break;
+ case LEAF_wlanStatsRxDiscardACL:
+ val->v.uint32 = wif->stats.is_rx_acl;
+ break;
+ case LEAF_wlanStatsTxFailedNoBuf:
+ val->v.uint32 = wif->stats.is_tx_nobuf;
+ break;
+ case LEAF_wlanStatsTxFailedNoNode:
+ val->v.uint32 = wif->stats.is_tx_nonode;
+ break;
+ case LEAF_wlanStatsTxUnknownMgmt:
+ val->v.uint32 = wif->stats.is_tx_unknownmgt;
+ break;
+ case LEAF_wlanStatsTxBadCipher:
+ val->v.uint32 = wif->stats.is_tx_badcipher;
+ break;
+ case LEAF_wlanStatsTxNoDefKey:
+ val->v.uint32 = wif->stats.is_tx_nodefkey;
+ break;
+ case LEAF_wlanStatsTxFragmented:
+ val->v.uint32 = wif->stats.is_tx_fragframes;
+ break;
+ case LEAF_wlanStatsTxFragmentsCreated:
+ val->v.uint32 = wif->stats.is_tx_frags;
+ break;
+ case LEAF_wlanStatsActiveScans:
+ val->v.uint32 = wif->stats.is_scan_active;
+ break;
+ case LEAF_wlanStatsPassiveScans:
+ val->v.uint32 = wif->stats.is_scan_passive;
+ break;
+ case LEAF_wlanStatsTimeoutInactivity:
+ val->v.uint32 = wif->stats.is_node_timeout;
+ break;
+ case LEAF_wlanStatsCryptoNoMem:
+ val->v.uint32 = wif->stats.is_crypto_nomem;
+ break;
+ case LEAF_wlanStatsSwCryptoTKIP:
+ val->v.uint32 = wif->stats.is_crypto_tkip;
+ break;
+ case LEAF_wlanStatsSwCryptoTKIPEnMIC:
+ val->v.uint32 = wif->stats.is_crypto_tkipenmic;
+ break;
+ case LEAF_wlanStatsSwCryptoTKIPDeMIC:
+ val->v.uint32 = wif->stats.is_crypto_tkipdemic;
+ break;
+ case LEAF_wlanStatsCryptoTKIPCM:
+ val->v.uint32 = wif->stats.is_crypto_tkipcm;
+ break;
+ case LEAF_wlanStatsSwCryptoCCMP:
+ val->v.uint32 = wif->stats.is_crypto_ccmp;
+ break;
+ case LEAF_wlanStatsSwCryptoWEP:
+ val->v.uint32 = wif->stats.is_crypto_wep;
+ break;
+ case LEAF_wlanStatsCryptoCipherKeyRejected:
+ val->v.uint32 = wif->stats.is_crypto_setkey_cipher;
+ break;
+ case LEAF_wlanStatsCryptoNoKey:
+ val->v.uint32 = wif->stats.is_crypto_setkey_nokey;
+ break;
+ case LEAF_wlanStatsCryptoDeleteKeyFailed:
+ val->v.uint32 = wif->stats.is_crypto_delkey;
+ break;
+ case LEAF_wlanStatsCryptoUnknownCipher:
+ val->v.uint32 = wif->stats.is_crypto_badcipher;
+ break;
+ case LEAF_wlanStatsCryptoAttachFailed:
+ val->v.uint32 = wif->stats.is_crypto_attachfail;
+ break;
+ case LEAF_wlanStatsCryptoKeyFailed:
+ val->v.uint32 = wif->stats.is_crypto_keyfail;
+ break;
+ case LEAF_wlanStatsCryptoEnMICFailed:
+ val->v.uint32 = wif->stats.is_crypto_enmicfail;
+ break;
+ case LEAF_wlanStatsIBSSCapMismatch:
+ val->v.uint32 = wif->stats.is_ibss_capmismatch;
+ break;
+ case LEAF_wlanStatsUnassocStaPSPoll:
+ val->v.uint32 = wif->stats.is_ps_unassoc;
+ break;
+ case LEAF_wlanStatsBadAidPSPoll:
+ val->v.uint32 = wif->stats.is_ps_badaid;
+ break;
+ case LEAF_wlanStatsEmptyPSPoll:
+ val->v.uint32 = wif->stats.is_ps_qempty;
+ break;
+ case LEAF_wlanStatsRxFFBadHdr:
+ val->v.uint32 = wif->stats.is_ff_badhdr;
+ break;
+ case LEAF_wlanStatsRxFFTooShort:
+ val->v.uint32 = wif->stats.is_ff_tooshort;
+ break;
+ case LEAF_wlanStatsRxFFSplitError:
+ val->v.uint32 = wif->stats.is_ff_split;
+ break;
+ case LEAF_wlanStatsRxFFDecap:
+ val->v.uint32 = wif->stats.is_ff_decap;
+ break;
+ case LEAF_wlanStatsTxFFEncap:
+ val->v.uint32 = wif->stats.is_ff_encap;
+ break;
+ case LEAF_wlanStatsRxBadBintval:
+ val->v.uint32 = wif->stats.is_rx_badbintval;
+ break;
+ case LEAF_wlanStatsRxDemicFailed:
+ val->v.uint32 = wif->stats.is_rx_demicfail;
+ break;
+ case LEAF_wlanStatsRxDefragFailed:
+ val->v.uint32 = wif->stats.is_rx_defrag;
+ break;
+ case LEAF_wlanStatsRxMgmt:
+ val->v.uint32 = wif->stats.is_rx_mgmt;
+ break;
+ case LEAF_wlanStatsRxActionMgmt:
+ val->v.uint32 = wif->stats.is_rx_action;
+ break;
+ case LEAF_wlanStatsRxAMSDUTooShort:
+ val->v.uint32 = wif->stats.is_amsdu_tooshort;
+ break;
+ case LEAF_wlanStatsRxAMSDUSplitError:
+ val->v.uint32 = wif->stats.is_amsdu_split;
+ break;
+ case LEAF_wlanStatsRxAMSDUDecap:
+ val->v.uint32 = wif->stats.is_amsdu_decap;
+ break;
+ case LEAF_wlanStatsTxAMSDUEncap:
+ val->v.uint32 = wif->stats.is_amsdu_encap;
+ break;
+ case LEAF_wlanStatsAMPDUBadBAR:
+ val->v.uint32 = wif->stats.is_ampdu_bar_bad;
+ break;
+ case LEAF_wlanStatsAMPDUOowBar:
+ val->v.uint32 = wif->stats.is_ampdu_bar_oow;
+ break;
+ case LEAF_wlanStatsAMPDUMovedBAR:
+ val->v.uint32 = wif->stats.is_ampdu_bar_move;
+ break;
+ case LEAF_wlanStatsAMPDURxBAR:
+ val->v.uint32 = wif->stats.is_ampdu_bar_rx;
+ break;
+ case LEAF_wlanStatsAMPDURxOor:
+ val->v.uint32 = wif->stats.is_ampdu_rx_oor;
+ break;
+ case LEAF_wlanStatsAMPDURxCopied:
+ val->v.uint32 = wif->stats.is_ampdu_rx_copy;
+ break;
+ case LEAF_wlanStatsAMPDURxDropped:
+ val->v.uint32 = wif->stats.is_ampdu_rx_drop;
+ break;
+ case LEAF_wlanStatsTxDiscardBadState:
+ val->v.uint32 = wif->stats.is_tx_badstate;
+ break;
+ case LEAF_wlanStatsTxFailedNoAssoc:
+ val->v.uint32 = wif->stats.is_tx_notassoc;
+ break;
+ case LEAF_wlanStatsTxClassifyFailed:
+ val->v.uint32 = wif->stats.is_tx_classify;
+ break;
+ case LEAF_wlanStatsDwdsMcastDiscard:
+ val->v.uint32 = wif->stats.is_dwds_mcast;
+ break;
+ case LEAF_wlanStatsHTAssocRejectNoHT:
+ val->v.uint32 = wif->stats.is_ht_assoc_nohtcap;
+ break;
+ case LEAF_wlanStatsHTAssocDowngrade:
+ val->v.uint32 = wif->stats.is_ht_assoc_downgrade;
+ break;
+ case LEAF_wlanStatsHTAssocRateMismatch:
+ val->v.uint32 = wif->stats.is_ht_assoc_norate;
+ break;
+ case LEAF_wlanStatsAMPDURxAge:
+ val->v.uint32 = wif->stats.is_ampdu_rx_age;
+ break;
+ case LEAF_wlanStatsAMPDUMoved:
+ val->v.uint32 = wif->stats.is_ampdu_rx_move;
+ break;
+ case LEAF_wlanStatsADDBADisabledReject:
+ val->v.uint32 = wif->stats.is_addba_reject;
+ break;
+ case LEAF_wlanStatsADDBANoRequest:
+ val->v.uint32 = wif->stats.is_addba_norequest;
+ break;
+ case LEAF_wlanStatsADDBABadToken:
+ val->v.uint32 = wif->stats.is_addba_badtoken;
+ break;
+ case LEAF_wlanStatsADDBABadPolicy:
+ val->v.uint32 = wif->stats.is_addba_badpolicy;
+ break;
+ case LEAF_wlanStatsAMPDUStopped:
+ val->v.uint32 = wif->stats.is_ampdu_stop;
+ break;
+ case LEAF_wlanStatsAMPDUStopFailed:
+ val->v.uint32 = wif->stats.is_ampdu_stop_failed;
+ break;
+ case LEAF_wlanStatsAMPDURxReorder:
+ val->v.uint32 = wif->stats.is_ampdu_rx_reorder;
+ break;
+ case LEAF_wlanStatsScansBackground:
+ val->v.uint32 = wif->stats.is_scan_bg;
+ break;
+ case LEAF_wlanLastDeauthReason:
+ val->v.uint32 = wif->stats.is_rx_deauth_code;
+ break;
+ case LEAF_wlanLastDissasocReason:
+ val->v.uint32 = wif->stats.is_rx_disassoc_code;
+ break;
+ case LEAF_wlanLastAuthFailReason:
+ val->v.uint32 = wif->stats.is_rx_authfail_code;
+ break;
+ case LEAF_wlanStatsBeaconMissedEvents:
+ val->v.uint32 = wif->stats.is_beacon_miss;
+ break;
+ case LEAF_wlanStatsRxDiscardBadStates:
+ val->v.uint32 = wif->stats.is_rx_badstate;
+ break;
+ case LEAF_wlanStatsFFFlushed:
+ val->v.uint32 = wif->stats.is_ff_flush;
+ break;
+ case LEAF_wlanStatsTxControlFrames:
+ val->v.uint32 = wif->stats.is_tx_ctl;
+ break;
+ case LEAF_wlanStatsAMPDURexmt:
+ val->v.uint32 = wif->stats.is_ampdu_rexmt;
+ break;
+ case LEAF_wlanStatsAMPDURexmtFailed:
+ val->v.uint32 = wif->stats.is_ampdu_rexmt_fail;
+ break;
+ case LEAF_wlanStatsReset:
+ val->v.uint32 = wlanStatsReset_no_op;
+ break;
+ default:
+ abort();
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+op_wlan_wep_iface(struct snmp_context *ctx, struct snmp_value *val,
+ uint32_t sub, uint32_t iidx __unused, enum snmp_op op)
+{
+ struct wlan_iface *wif;
+
+ wlan_update_interface_list();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((wif = wlan_get_interface(&val->var, sub)) == NULL ||
+ !wif->wepsupported)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+
+ case SNMP_OP_GETNEXT:
+ /* XXX: filter wif->wepsupported */
+ if ((wif = wlan_get_next_interface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ wlan_append_ifindex(&val->var, sub, wif);
+ break;
+
+ case SNMP_OP_SET:
+ if ((wif = wlan_get_interface(&val->var, sub)) == NULL ||
+ !wif->wepsupported)
+ return (SNMP_ERR_NOSUCHNAME);
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanWepMode:
+ if (val->v.integer < wlanWepMode_off ||
+ val->v.integer > wlanWepMode_mixed)
+ return (SNMP_ERR_INCONS_VALUE);
+ ctx->scratch->int1 = wif->wepmode;
+ wif->wepmode = val->v.integer;
+ if (wlan_set_wepmode(wif) < 0) {
+ wif->wepmode = ctx->scratch->int1;
+ return (SNMP_ERR_GENERR);
+ }
+ break;
+ case LEAF_wlanWepDefTxKey:
+ if (val->v.integer < 0 ||
+ val->v.integer > IEEE80211_WEP_NKID)
+ return (SNMP_ERR_INCONS_VALUE);
+ ctx->scratch->int1 = wif->weptxkey;
+ wif->weptxkey = val->v.integer;
+ if (wlan_set_weptxkey(wif) < 0) {
+ wif->weptxkey = ctx->scratch->int1;
+ return (SNMP_ERR_GENERR);
+ }
+ break;
+ default:
+ abort();
+ }
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_COMMIT:
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_ROLLBACK:
+ if ((wif = wlan_get_interface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanWepMode:
+ wif->wepmode = ctx->scratch->int1;
+ if (wlan_set_wepmode(wif) < 0)
+ return (SNMP_ERR_GENERR);
+ break;
+ case LEAF_wlanWepDefTxKey:
+ wif->weptxkey = ctx->scratch->int1;
+ if (wlan_set_weptxkey(wif) < 0)
+ return (SNMP_ERR_GENERR);
+ break;
+ default:
+ abort();
+ }
+ return (SNMP_ERR_NOERROR);
+
+ default:
+ abort();
+ }
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanWepMode:
+ if (wlan_get_wepmode(wif) < 0)
+ return (SNMP_ERR_GENERR);
+ val->v.integer = wif->wepmode;
+ break;
+ case LEAF_wlanWepDefTxKey:
+ if (wlan_get_weptxkey(wif) < 0)
+ return (SNMP_ERR_GENERR);
+ val->v.integer = wif->weptxkey;
+ break;
+ default:
+ abort();
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+op_wlan_wep_key(struct snmp_context *ctx __unused,
+ struct snmp_value *val __unused, uint32_t sub __unused,
+ uint32_t iidx __unused, enum snmp_op op __unused)
+{
+ return (SNMP_ERR_NOSUCHNAME);
+}
+
+int
+op_wlan_mac_access_control(struct snmp_context *ctx, struct snmp_value *val,
+ uint32_t sub, uint32_t iidx __unused, enum snmp_op op)
+{
+ struct wlan_iface *wif;
+
+ wlan_update_interface_list();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((wif = wlan_get_interface(&val->var, sub)) == NULL ||
+ !wif->macsupported)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+
+ case SNMP_OP_GETNEXT:
+ /* XXX: filter wif->macsupported */
+ if ((wif = wlan_get_next_interface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ wlan_append_ifindex(&val->var, sub, wif);
+ break;
+
+ case SNMP_OP_SET:
+ if ((wif = wlan_get_interface(&val->var, sub)) == NULL ||
+ !wif->macsupported)
+ return (SNMP_ERR_NOSUCHNAME);
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanMACAccessControlPolicy:
+ ctx->scratch->int1 = wif->mac_policy;
+ wif->mac_policy = val->v.integer;
+ break;
+ case LEAF_wlanMACAccessControlNacl:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ case LEAF_wlanMACAccessControlFlush:
+ break;
+ default:
+ abort();
+ }
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_COMMIT:
+ if ((wif = wlan_get_interface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanMACAccessControlPolicy:
+ if (wlan_set_mac_policy(wif) < 0) {
+ wif->mac_policy = ctx->scratch->int1;
+ return (SNMP_ERR_GENERR);
+ }
+ break;
+ case LEAF_wlanMACAccessControlFlush:
+ if (wlan_flush_mac_mac(wif) < 0)
+ return (SNMP_ERR_GENERR);
+ break;
+ default:
+ abort();
+ }
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_ROLLBACK:
+ if ((wif = wlan_get_interface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ if (val->var.subs[sub - 1] == LEAF_wlanMACAccessControlPolicy)
+ wif->mac_policy = ctx->scratch->int1;
+ return (SNMP_ERR_NOERROR);
+
+ default:
+ abort();
+ }
+
+ if (wlan_get_mac_policy(wif) < 0)
+ return (SNMP_ERR_GENERR);
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanMACAccessControlPolicy:
+ val->v.integer = wif->mac_policy;
+ break;
+ case LEAF_wlanMACAccessControlNacl:
+ val->v.integer = wif->mac_nacls;
+ break;
+ case LEAF_wlanMACAccessControlFlush:
+ val->v.integer = wlanMACAccessControlFlush_no_op;
+ break;
+ default:
+ abort();
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+op_wlan_mac_acl_mac(struct snmp_context *ctx, struct snmp_value *val,
+ uint32_t sub, uint32_t iidx __unused, enum snmp_op op)
+{
+ struct wlan_iface *wif;
+ struct wlan_mac_mac *macl;
+
+ wlan_update_interface_list();
+ wlan_mac_update_aclmacs();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((macl = wlan_get_acl_mac(&val->var, sub, &wif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+
+ case SNMP_OP_GETNEXT:
+ if ((macl = wlan_get_next_acl_mac(&val->var, sub, &wif))
+ == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ wlan_append_mac_index(&val->var, sub, wif->wname, macl->mac);
+ break;
+
+ case SNMP_OP_SET:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanMACAccessControlMAC:
+ return (SNMP_ERR_INCONS_NAME);
+ case LEAF_wlanMACAccessControlMACStatus:
+ return(wlan_acl_mac_set_status(ctx, val, sub));
+ default:
+ abort();
+ }
+
+ case SNMP_OP_COMMIT:
+ if ((macl = wlan_get_acl_mac(&val->var, sub, &wif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ if (val->v.integer == RowStatus_destroy &&
+ wlan_mac_delete_mac(wif, macl) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_ROLLBACK:
+ if ((macl = wlan_get_acl_mac(&val->var, sub, &wif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ if (ctx->scratch->int1 == RowStatus_destroy &&
+ wlan_mac_delete_mac(wif, macl) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ default:
+ abort();
+ }
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanMACAccessControlMAC:
+ return (string_get(val, macl->mac, IEEE80211_ADDR_LEN));
+ case LEAF_wlanMACAccessControlMACStatus:
+ val->v.integer = macl->mac_status;
+ break;
+ default:
+ abort();
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+op_wlan_mesh_config(struct snmp_context *ctx, struct snmp_value *val,
+ uint32_t sub, uint32_t iidx __unused, enum snmp_op op)
+{
+ int which;
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanMeshMaxRetries:
+ which = WLAN_MESH_MAX_RETRIES;
+ break;
+ case LEAF_wlanMeshHoldingTimeout:
+ which = WLAN_MESH_HOLDING_TO;
+ break;
+ case LEAF_wlanMeshConfirmTimeout:
+ which = WLAN_MESH_CONFIRM_TO;
+ break;
+ case LEAF_wlanMeshRetryTimeout:
+ which = WLAN_MESH_RETRY_TO;
+ break;
+ default:
+ abort();
+ }
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if (wlan_do_sysctl(&wlan_config, which, 0) < 0)
+ return (SNMP_ERR_GENERR);
+ break;
+
+ case SNMP_OP_GETNEXT:
+ abort();
+
+ case SNMP_OP_SET:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanMeshRetryTimeout :
+ ctx->scratch->int1 = wlan_config.mesh_retryto;
+ wlan_config.mesh_retryto = val->v.integer;
+ break;
+ case LEAF_wlanMeshHoldingTimeout:
+ ctx->scratch->int1 = wlan_config.mesh_holdingto;
+ wlan_config.mesh_holdingto = val->v.integer;
+ break;
+ case LEAF_wlanMeshConfirmTimeout:
+ ctx->scratch->int1 = wlan_config.mesh_confirmto;
+ wlan_config.mesh_confirmto = val->v.integer;
+ break;
+ case LEAF_wlanMeshMaxRetries:
+ ctx->scratch->int1 = wlan_config.mesh_maxretries;
+ wlan_config.mesh_maxretries = val->v.integer;
+ break;
+ }
+ if (wlan_do_sysctl(&wlan_config, which, 1) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_COMMIT:
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_ROLLBACK:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanMeshRetryTimeout:
+ wlan_config.mesh_retryto = ctx->scratch->int1;
+ break;
+ case LEAF_wlanMeshConfirmTimeout:
+ wlan_config.mesh_confirmto = ctx->scratch->int1;
+ break;
+ case LEAF_wlanMeshHoldingTimeout:
+ wlan_config.mesh_holdingto= ctx->scratch->int1;
+ break;
+ case LEAF_wlanMeshMaxRetries:
+ wlan_config.mesh_maxretries = ctx->scratch->int1;
+ break;
+ }
+ if (wlan_do_sysctl(&wlan_config, which, 1) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ default:
+ abort();
+ }
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanMeshRetryTimeout:
+ val->v.integer = wlan_config.mesh_retryto;
+ break;
+ case LEAF_wlanMeshHoldingTimeout:
+ val->v.integer = wlan_config.mesh_holdingto;
+ break;
+ case LEAF_wlanMeshConfirmTimeout:
+ val->v.integer = wlan_config.mesh_confirmto;
+ break;
+ case LEAF_wlanMeshMaxRetries:
+ val->v.integer = wlan_config.mesh_maxretries;
+ break;
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+op_wlan_mesh_iface(struct snmp_context *ctx, struct snmp_value *val,
+ uint32_t sub, uint32_t iidx __unused, enum snmp_op op)
+{
+ int rc;
+ struct wlan_iface *wif;
+
+ wlan_update_interface_list();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((wif = wlan_mesh_get_iface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+
+ case SNMP_OP_GETNEXT:
+ if ((wif = wlan_mesh_get_next_iface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ wlan_append_ifindex(&val->var, sub, wif);
+ break;
+
+ case SNMP_OP_SET:
+ if ((wif = wlan_mesh_get_iface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanMeshId:
+ if (val->v.octetstring.len > IEEE80211_NWID_LEN)
+ return (SNMP_ERR_INCONS_VALUE);
+ ctx->scratch->ptr1 = malloc(val->v.octetstring.len + 1);
+ if (ctx->scratch->ptr1 == NULL)
+ return (SNMP_ERR_GENERR);
+ strlcpy(ctx->scratch->ptr1, wif->desired_ssid,
+ val->v.octetstring.len + 1);
+ ctx->scratch->int1 = strlen(wif->desired_ssid);
+ memcpy(wif->desired_ssid, val->v.octetstring.octets,
+ val->v.octetstring.len);
+ wif->desired_ssid[val->v.octetstring.len] = '\0';
+ break;
+ case LEAF_wlanMeshTTL:
+ ctx->scratch->int1 = wif->mesh_ttl;
+ wif->mesh_ttl = val->v.integer;
+ break;
+ case LEAF_wlanMeshPeeringEnabled:
+ ctx->scratch->int1 = wif->mesh_peering;
+ wif->mesh_peering = val->v.integer;
+ break;
+ case LEAF_wlanMeshForwardingEnabled:
+ ctx->scratch->int1 = wif->mesh_forwarding;
+ wif->mesh_forwarding = val->v.integer;
+ break;
+ case LEAF_wlanMeshMetric:
+ ctx->scratch->int1 = wif->mesh_metric;
+ wif->mesh_metric = val->v.integer;
+ break;
+ case LEAF_wlanMeshPath:
+ ctx->scratch->int1 = wif->mesh_path;
+ wif->mesh_path = val->v.integer;
+ break;
+ case LEAF_wlanMeshRoutesFlush:
+ if (val->v.integer != wlanMeshRoutesFlush_flush)
+ return (SNMP_ERR_INCONS_VALUE);
+ return (SNMP_ERR_NOERROR);
+ default:
+ abort();
+ }
+ if (val->var.subs[sub - 1] == LEAF_wlanMeshId)
+ rc = wlan_config_set_dssid(wif,
+ val->v.octetstring.octets, val->v.octetstring.len);
+ else
+ rc = wlan_mesh_config_set(wif, val->var.subs[sub - 1]);
+ if (rc < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_COMMIT:
+ if ((wif = wlan_mesh_get_iface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ if (val->var.subs[sub - 1] == LEAF_wlanMeshRoutesFlush &&
+ wlan_mesh_flush_routes(wif) < 0)
+ return (SNMP_ERR_GENERR);
+ if (val->var.subs[sub - 1] == LEAF_wlanMeshId)
+ free(ctx->scratch->ptr1);
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_ROLLBACK:
+ if ((wif = wlan_mesh_get_iface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanMeshId:
+ strlcpy(wif->desired_ssid, ctx->scratch->ptr1,
+ IEEE80211_NWID_LEN);
+ free(ctx->scratch->ptr1);
+ break;
+ case LEAF_wlanMeshTTL:
+ wif->mesh_ttl = ctx->scratch->int1;
+ break;
+ case LEAF_wlanMeshPeeringEnabled:
+ wif->mesh_peering = ctx->scratch->int1;
+ break;
+ case LEAF_wlanMeshForwardingEnabled:
+ wif->mesh_forwarding = ctx->scratch->int1;
+ break;
+ case LEAF_wlanMeshMetric:
+ wif->mesh_metric = ctx->scratch->int1;
+ break;
+ case LEAF_wlanMeshPath:
+ wif->mesh_path = ctx->scratch->int1;
+ break;
+ case LEAF_wlanMeshRoutesFlush:
+ return (SNMP_ERR_NOERROR);
+ default:
+ abort();
+ }
+ if (val->var.subs[sub - 1] == LEAF_wlanMeshId)
+ rc = wlan_config_set_dssid(wif, wif->desired_ssid,
+ strlen(wif->desired_ssid));
+ else
+ rc = wlan_mesh_config_set(wif, val->var.subs[sub - 1]);
+ if (rc < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ default:
+ abort();
+ }
+
+ if (val->var.subs[sub - 1] == LEAF_wlanMeshId)
+ rc = wlan_config_get_dssid(wif);
+ else
+ rc = wlan_mesh_config_get(wif, val->var.subs[sub - 1]);
+ if (rc < 0)
+ return (SNMP_ERR_GENERR);
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanMeshId:
+ return (string_get(val, wif->desired_ssid, -1));
+ case LEAF_wlanMeshTTL:
+ val->v.integer = wif->mesh_ttl;
+ break;
+ case LEAF_wlanMeshPeeringEnabled:
+ val->v.integer = wif->mesh_peering;
+ break;
+ case LEAF_wlanMeshForwardingEnabled:
+ val->v.integer = wif->mesh_forwarding;
+ break;
+ case LEAF_wlanMeshMetric:
+ val->v.integer = wif->mesh_metric;
+ break;
+ case LEAF_wlanMeshPath:
+ val->v.integer = wif->mesh_path;
+ break;
+ case LEAF_wlanMeshRoutesFlush:
+ val->v.integer = wlanMeshRoutesFlush_no_op;
+ break;
+ default:
+ abort();
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+op_wlan_mesh_neighbor(struct snmp_context *ctx __unused, struct snmp_value *val,
+ uint32_t sub, uint32_t iidx __unused, enum snmp_op op)
+{
+ struct wlan_peer *wip;
+ struct wlan_iface *wif;
+
+ wlan_update_interface_list();
+ wlan_update_peers();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((wip = wlan_mesh_get_peer(&val->var, sub, &wif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+ case SNMP_OP_GETNEXT:
+ wip = wlan_mesh_get_next_peer(&val->var, sub, &wif);
+ if (wip == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ wlan_append_mac_index(&val->var, sub, wif->wname,
+ wip->pmac);
+ break;
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ case SNMP_OP_COMMIT:
+ /* FALLTHROUGH */
+ case SNMP_OP_ROLLBACK:
+ /* FALLTHROUGH */
+ default:
+ abort();
+ }
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanMeshNeighborAddress:
+ return (string_get(val, wip->pmac, IEEE80211_ADDR_LEN));
+ case LEAF_wlanMeshNeighborFrequency:
+ val->v.integer = wip->frequency;
+ break;
+ case LEAF_wlanMeshNeighborLocalId:
+ val->v.integer = wip->local_id;
+ break;
+ case LEAF_wlanMeshNeighborPeerId:
+ val->v.integer = wip->peer_id;
+ break;
+ case LEAF_wlanMeshNeighborPeerState:
+ return (bits_get(val, (uint8_t *)&wip->state,
+ sizeof(wip->state)));
+ case LEAF_wlanMeshNeighborCurrentTXRate:
+ val->v.integer = wip->txrate;
+ break;
+ case LEAF_wlanMeshNeighborRxSignalStrength:
+ val->v.integer = wip->rssi;
+ break;
+ case LEAF_wlanMeshNeighborIdleTimer:
+ val->v.integer = wip->idle;
+ break;
+ case LEAF_wlanMeshNeighborTxSequenceNo:
+ val->v.integer = wip->txseqs;
+ break;
+ case LEAF_wlanMeshNeighborRxSequenceNo:
+ val->v.integer = wip->rxseqs;
+ break;
+ default:
+ abort();
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+op_wlan_mesh_route(struct snmp_context *ctx, struct snmp_value *val,
+ uint32_t sub, uint32_t iidx __unused, enum snmp_op op)
+{
+ struct wlan_mesh_route *wmr;
+ struct wlan_iface *wif;
+
+ wlan_update_interface_list();
+ wlan_mesh_update_routes();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((wmr = wlan_mesh_get_route(&val->var, sub, &wif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+
+ case SNMP_OP_GETNEXT:
+ wmr = wlan_mesh_get_next_route(&val->var, sub, &wif);
+ if (wmr == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ wlan_append_mac_index(&val->var, sub, wif->wname,
+ wmr->imroute.imr_dest);
+ break;
+
+ case SNMP_OP_SET:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanMeshRouteDestination:
+ return (SNMP_ERR_INCONS_NAME);
+ case LEAF_wlanMeshRouteStatus:
+ return(wlan_mesh_route_set_status(ctx, val, sub));
+ default:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ }
+ abort();
+
+ case SNMP_OP_COMMIT:
+ if ((wmr = wlan_mesh_get_route(&val->var, sub, &wif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ if (val->v.integer == RowStatus_destroy &&
+ wlan_mesh_delete_route(wif, wmr) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_ROLLBACK:
+ if ((wmr = wlan_mesh_get_route(&val->var, sub, &wif)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ if (ctx->scratch->int1 == RowStatus_destroy &&
+ wlan_mesh_delete_route(wif, wmr) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ default:
+ abort();
+ }
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanMeshRouteDestination:
+ return (string_get(val, wmr->imroute.imr_dest,
+ IEEE80211_ADDR_LEN));
+ case LEAF_wlanMeshRouteNextHop:
+ return (string_get(val, wmr->imroute.imr_nexthop,
+ IEEE80211_ADDR_LEN));
+ case LEAF_wlanMeshRouteHops:
+ val->v.integer = wmr->imroute.imr_nhops;
+ break;
+ case LEAF_wlanMeshRouteMetric:
+ val->v.integer = wmr->imroute.imr_metric;
+ break;
+ case LEAF_wlanMeshRouteLifeTime:
+ val->v.integer = wmr->imroute.imr_lifetime;
+ break;
+ case LEAF_wlanMeshRouteLastMseq:
+ val->v.integer = wmr->imroute.imr_lastmseq;
+ break;
+ case LEAF_wlanMeshRouteFlags:
+ val->v.integer = 0;
+ if ((wmr->imroute.imr_flags &
+ IEEE80211_MESHRT_FLAGS_VALID) != 0)
+ val->v.integer |= (0x1 << wlanMeshRouteFlags_valid);
+ if ((wmr->imroute.imr_flags &
+ IEEE80211_MESHRT_FLAGS_PROXY) != 0)
+ val->v.integer |= (0x1 << wlanMeshRouteFlags_proxy);
+ return (bits_get(val, (uint8_t *)&val->v.integer,
+ sizeof(val->v.integer)));
+ case LEAF_wlanMeshRouteStatus:
+ val->v.integer = wmr->mroute_status;
+ break;
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+op_wlan_mesh_stats(struct snmp_context *ctx __unused, struct snmp_value *val,
+ uint32_t sub, uint32_t iidx __unused, enum snmp_op op)
+{
+ struct wlan_iface *wif;
+
+ wlan_update_interface_list();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((wif = wlan_mesh_get_iface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+ case SNMP_OP_GETNEXT:
+ if ((wif = wlan_mesh_get_next_iface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ wlan_append_ifindex(&val->var, sub, wif);
+ break;
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ case SNMP_OP_COMMIT:
+ /* FALLTHROUGH */
+ case SNMP_OP_ROLLBACK:
+ /* FALLTHROUGH */
+ default:
+ abort();
+ }
+
+ if (wlan_get_stats(wif) < 0)
+ return (SNMP_ERR_GENERR);
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanMeshDroppedBadSta:
+ val->v.uint32 = wif->stats.is_mesh_wrongmesh;
+ break;
+ case LEAF_wlanMeshDroppedNoLink:
+ val->v.uint32 = wif->stats.is_mesh_nolink;
+ break;
+ case LEAF_wlanMeshNoFwdTtl:
+ val->v.uint32 = wif->stats.is_mesh_fwd_ttl;
+ break;
+ case LEAF_wlanMeshNoFwdBuf:
+ val->v.uint32 = wif->stats.is_mesh_fwd_nobuf;
+ break;
+ case LEAF_wlanMeshNoFwdTooShort:
+ val->v.uint32 = wif->stats.is_mesh_fwd_tooshort;
+ break;
+ case LEAF_wlanMeshNoFwdDisabled:
+ val->v.uint32 = wif->stats.is_mesh_fwd_disabled;
+ break;
+ case LEAF_wlanMeshNoFwdPathUnknown:
+ val->v.uint32 = wif->stats.is_mesh_fwd_nopath;
+ break;
+ case LEAF_wlanMeshDroppedBadAE:
+ val->v.uint32 = wif->stats.is_mesh_badae;
+ break;
+ case LEAF_wlanMeshRouteAddFailed:
+ val->v.uint32 = wif->stats.is_mesh_rtaddfailed;
+ break;
+ case LEAF_wlanMeshDroppedNoProxy:
+ val->v.uint32 = wif->stats.is_mesh_notproxy;
+ break;
+ case LEAF_wlanMeshDroppedMisaligned:
+ val->v.uint32 = wif->stats.is_rx_badalign;
+ break;
+ default:
+ abort();
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+op_wlan_hwmp_config(struct snmp_context *ctx, struct snmp_value *val,
+ uint32_t sub, uint32_t iidx __unused, enum snmp_op op)
+{
+ int which;
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanHWMPRouteInactiveTimeout:
+ which = WLAN_HWMP_INACTIVITY_TO;
+ break;
+ case LEAF_wlanHWMPRootAnnounceInterval:
+ which = WLAN_HWMP_RANN_INT;
+ break;
+ case LEAF_wlanHWMPRootInterval:
+ which = WLAN_HWMP_ROOT_INT;
+ break;
+ case LEAF_wlanHWMPRootTimeout:
+ which = WLAN_HWMP_ROOT_TO;
+ break;
+ case LEAF_wlanHWMPPathLifetime:
+ which = WLAN_HWMP_PATH_LIFETIME;
+ break;
+ case LEAF_wlanHWMPReplyForwardBit:
+ which = WLAN_HWMP_REPLY_FORWARD;
+ break;
+ case LEAF_wlanHWMPTargetOnlyBit:
+ which = WLAN_HWMP_TARGET_ONLY;
+ break;
+ default:
+ abort();
+ }
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if (wlan_do_sysctl(&wlan_config, which, 0) < 0)
+ return (SNMP_ERR_GENERR);
+ break;
+
+ case SNMP_OP_GETNEXT:
+ abort();
+
+ case SNMP_OP_SET:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanHWMPRouteInactiveTimeout:
+ ctx->scratch->int1 = wlan_config.hwmp_inact;
+ wlan_config.hwmp_inact = val->v.integer;
+ break;
+ case LEAF_wlanHWMPRootAnnounceInterval:
+ ctx->scratch->int1 = wlan_config.hwmp_rannint;
+ wlan_config.hwmp_rannint = val->v.integer;
+ break;
+ case LEAF_wlanHWMPRootInterval:
+ ctx->scratch->int1 = wlan_config.hwmp_rootint;
+ wlan_config.hwmp_rootint = val->v.integer;
+ break;
+ case LEAF_wlanHWMPRootTimeout:
+ ctx->scratch->int1 = wlan_config.hwmp_roottimeout;
+ wlan_config.hwmp_roottimeout = val->v.integer;
+ break;
+ case LEAF_wlanHWMPPathLifetime:
+ ctx->scratch->int1 = wlan_config.hwmp_pathlifetime;
+ wlan_config.hwmp_pathlifetime = val->v.integer;
+ break;
+ case LEAF_wlanHWMPReplyForwardBit:
+ ctx->scratch->int1 = wlan_config.hwmp_replyforward;
+ wlan_config.hwmp_replyforward = val->v.integer;
+ break;
+ case LEAF_wlanHWMPTargetOnlyBit:
+ ctx->scratch->int1 = wlan_config.hwmp_targetonly;
+ wlan_config.hwmp_targetonly = val->v.integer;
+ break;
+ }
+ if (wlan_do_sysctl(&wlan_config, which, 1) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_COMMIT:
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_ROLLBACK:
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanHWMPRouteInactiveTimeout:
+ wlan_config.hwmp_inact = ctx->scratch->int1;
+ break;
+ case LEAF_wlanHWMPRootAnnounceInterval:
+ wlan_config.hwmp_rannint = ctx->scratch->int1;
+ break;
+ case LEAF_wlanHWMPRootInterval:
+ wlan_config.hwmp_rootint = ctx->scratch->int1;
+ break;
+ case LEAF_wlanHWMPRootTimeout:
+ wlan_config.hwmp_roottimeout = ctx->scratch->int1;
+ break;
+ case LEAF_wlanHWMPPathLifetime:
+ wlan_config.hwmp_pathlifetime = ctx->scratch->int1;
+ break;
+ case LEAF_wlanHWMPReplyForwardBit:
+ wlan_config.hwmp_replyforward = ctx->scratch->int1;
+ break;
+ case LEAF_wlanHWMPTargetOnlyBit:
+ wlan_config.hwmp_targetonly = ctx->scratch->int1;
+ break;
+ }
+ if (wlan_do_sysctl(&wlan_config, which, 1) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ default:
+ abort();
+ }
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanHWMPRouteInactiveTimeout:
+ val->v.integer = wlan_config.hwmp_inact;
+ break;
+ case LEAF_wlanHWMPRootAnnounceInterval:
+ val->v.integer = wlan_config.hwmp_rannint;
+ break;
+ case LEAF_wlanHWMPRootInterval:
+ val->v.integer = wlan_config.hwmp_rootint;
+ break;
+ case LEAF_wlanHWMPRootTimeout:
+ val->v.integer = wlan_config.hwmp_roottimeout;
+ break;
+ case LEAF_wlanHWMPPathLifetime:
+ val->v.integer = wlan_config.hwmp_pathlifetime;
+ break;
+ case LEAF_wlanHWMPReplyForwardBit:
+ val->v.integer = wlan_config.hwmp_replyforward;
+ break;
+ case LEAF_wlanHWMPTargetOnlyBit:
+ val->v.integer = wlan_config.hwmp_targetonly;
+ break;
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+op_wlan_hwmp_iface(struct snmp_context *ctx, struct snmp_value *val,
+ uint32_t sub, uint32_t iidx __unused, enum snmp_op op)
+{
+ struct wlan_iface *wif;
+
+ wlan_update_interface_list();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((wif = wlan_mesh_get_iface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+
+ case SNMP_OP_GETNEXT:
+ if ((wif = wlan_mesh_get_next_iface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ wlan_append_ifindex(&val->var, sub, wif);
+ break;
+
+ case SNMP_OP_SET:
+ if ((wif = wlan_mesh_get_iface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanHWMPRootMode:
+ ctx->scratch->int1 = wif->hwmp_root_mode;
+ wif->hwmp_root_mode = val->v.integer;
+ break;
+ case LEAF_wlanHWMPMaxHops:
+ ctx->scratch->int1 = wif->hwmp_max_hops;
+ wif->hwmp_max_hops = val->v.integer;
+ break;
+ default:
+ abort();
+ }
+ if (wlan_hwmp_config_set(wif, val->var.subs[sub - 1]) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_COMMIT:
+ return (SNMP_ERR_NOERROR);
+
+ case SNMP_OP_ROLLBACK:
+ if ((wif = wlan_mesh_get_iface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanHWMPRootMode:
+ wif->hwmp_root_mode = ctx->scratch->int1;
+ break;
+ case LEAF_wlanHWMPMaxHops:
+ wif->hwmp_max_hops = ctx->scratch->int1;
+ break;
+ default:
+ abort();
+ }
+ if (wlan_hwmp_config_set(wif, val->var.subs[sub - 1]) < 0)
+ return (SNMP_ERR_GENERR);
+ return (SNMP_ERR_NOERROR);
+
+ default:
+ abort();
+ }
+
+ if (wlan_hwmp_config_get(wif, val->var.subs[sub - 1]) < 0)
+ return (SNMP_ERR_GENERR);
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanHWMPRootMode:
+ val->v.integer = wif->hwmp_root_mode;
+ break;
+ case LEAF_wlanHWMPMaxHops:
+ val->v.integer = wif->hwmp_max_hops;
+ break;
+ default:
+ abort();
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+op_wlan_hwmp_stats(struct snmp_context *ctx __unused, struct snmp_value *val,
+ uint32_t sub, uint32_t iidx __unused, enum snmp_op op)
+{
+ struct wlan_iface *wif;
+
+ wlan_update_interface_list();
+
+ switch (op) {
+ case SNMP_OP_GET:
+ if ((wif = wlan_mesh_get_iface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ break;
+ case SNMP_OP_GETNEXT:
+ if ((wif = wlan_mesh_get_next_iface(&val->var, sub)) == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ wlan_append_ifindex(&val->var, sub, wif);
+ break;
+ case SNMP_OP_SET:
+ return (SNMP_ERR_NOT_WRITEABLE);
+ case SNMP_OP_COMMIT:
+ /* FALLTHROUGH */
+ case SNMP_OP_ROLLBACK:
+ /* FALLTHROUGH */
+ default:
+ abort();
+ }
+
+ if (wlan_get_stats(wif) < 0)
+ return (SNMP_ERR_GENERR);
+
+ switch (val->var.subs[sub - 1]) {
+ case LEAF_wlanMeshHWMPWrongSeqNo:
+ val->v.uint32 = wif->stats.is_hwmp_wrongseq;
+ break;
+ case LEAF_wlanMeshHWMPTxRootPREQ:
+ val->v.uint32 = wif->stats.is_hwmp_rootreqs;
+ break;
+ case LEAF_wlanMeshHWMPTxRootRANN:
+ val->v.uint32 = wif->stats.is_hwmp_rootrann;
+ break;
+ case LEAF_wlanMeshHWMPProxy:
+ val->v.uint32 = wif->stats.is_hwmp_proxy;
+ break;
+ default:
+ abort();
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+/*
+ * Encode BITS type for a response packet - XXX: this belongs to the snmp lib.
+ */
+static int
+bits_get(struct snmp_value *value, const u_char *ptr, ssize_t len)
+{
+ int size;
+
+ if (ptr == NULL) {
+ value->v.octetstring.len = 0;
+ value->v.octetstring.octets = NULL;
+ return (SNMP_ERR_NOERROR);
+ }
+
+ /* Determine length - up to 8 octets supported so far. */
+ for (size = len; size > 0; size--)
+ if (ptr[size - 1] != 0)
+ break;
+ if (size == 0)
+ size = 1;
+
+ value->v.octetstring.len = (u_long)size;
+ if ((value->v.octetstring.octets = malloc((size_t)size)) == NULL)
+ return (SNMP_ERR_RES_UNAVAIL);
+ memcpy(value->v.octetstring.octets, ptr, (size_t)size);
+ return (SNMP_ERR_NOERROR);
+}
+
+/*
+ * Calls for adding/updating/freeing/etc of wireless interfaces.
+ */
+static void
+wlan_free_interface(struct wlan_iface *wif)
+{
+ wlan_free_peerlist(wif);
+ free(wif->chanlist);
+ wlan_scan_free_results(wif);
+ wlan_mac_free_maclist(wif);
+ wlan_mesh_free_routes(wif);
+ free(wif);
+}
+
+static void
+wlan_free_iflist(void)
+{
+ struct wlan_iface *w;
+
+ while ((w = SLIST_FIRST(&wlan_ifaces)) != NULL) {
+ SLIST_REMOVE_HEAD(&wlan_ifaces, w_if);
+ wlan_free_interface(w);
+ }
+}
+
+static struct wlan_iface *
+wlan_find_interface(const char *wname)
+{
+ struct wlan_iface *wif;
+
+ SLIST_FOREACH(wif, &wlan_ifaces, w_if)
+ if (strcmp(wif->wname, wname) == 0) {
+ if (wif->status != RowStatus_active)
+ return (NULL);
+ break;
+ }
+
+ return (wif);
+}
+
+static struct wlan_iface *
+wlan_first_interface(void)
+{
+ return (SLIST_FIRST(&wlan_ifaces));
+}
+
+static struct wlan_iface *
+wlan_next_interface(struct wlan_iface *wif)
+{
+ if (wif == NULL)
+ return (NULL);
+
+ return (SLIST_NEXT(wif, w_if));
+}
+
+/*
+ * Add a new interface to the list - sorted by name.
+ */
+static int
+wlan_add_wif(struct wlan_iface *wif)
+{
+ int cmp;
+ struct wlan_iface *temp, *prev;
+
+ if ((prev = SLIST_FIRST(&wlan_ifaces)) == NULL ||
+ strcmp(wif->wname, prev->wname) < 0) {
+ SLIST_INSERT_HEAD(&wlan_ifaces, wif, w_if);
+ return (0);
+ }
+
+ SLIST_FOREACH(temp, &wlan_ifaces, w_if) {
+ if ((cmp = strcmp(wif->wname, temp->wname)) <= 0)
+ break;
+ prev = temp;
+ }
+
+ if (temp == NULL)
+ SLIST_INSERT_AFTER(prev, wif, w_if);
+ else if (cmp > 0)
+ SLIST_INSERT_AFTER(temp, wif, w_if);
+ else {
+ syslog(LOG_ERR, "Wlan iface %s already in list", wif->wname);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static struct wlan_iface *
+wlan_new_wif(char *wname)
+{
+ struct wlan_iface *wif;
+
+ /* Make sure it's not in the list. */
+ for (wif = wlan_first_interface(); wif != NULL;
+ wif = wlan_next_interface(wif))
+ if (strcmp(wname, wif->wname) == 0) {
+ wif->internal = 0;
+ return (wif);
+ }
+
+ if ((wif = (struct wlan_iface *)malloc(sizeof(*wif))) == NULL)
+ return (NULL);
+
+ memset(wif, 0, sizeof(struct wlan_iface));
+ strlcpy(wif->wname, wname, IFNAMSIZ);
+ wif->status = RowStatus_notReady;
+ wif->state = wlanIfaceState_down;
+ wif->mode = WlanIfaceOperatingModeType_station;
+
+ if (wlan_add_wif(wif) < 0) {
+ free(wif);
+ return (NULL);
+ }
+
+ return (wif);
+}
+
+static void
+wlan_delete_wif(struct wlan_iface *wif)
+{
+ SLIST_REMOVE(&wlan_ifaces, wif, wlan_iface, w_if);
+ wlan_free_interface(wif);
+}
+
+static int
+wlan_attach_newif(struct mibif *mif)
+{
+ struct wlan_iface *wif;
+
+ if (mif->mib.ifmd_data.ifi_type != IFT_ETHER ||
+ wlan_check_media(mif->name) != IFM_IEEE80211)
+ return (0);
+
+ if ((wif = wlan_new_wif(mif->name)) == NULL)
+ return (-1);
+
+ (void)wlan_get_opmode(wif);
+ wif->index = mif->index;
+ wif->status = RowStatus_active;
+ (void)wlan_update_interface(wif);
+
+ return (0);
+}
+
+static int
+wlan_iface_create(struct wlan_iface *wif)
+{
+ int rc;
+
+ if ((rc = wlan_clone_create(wif)) == SNMP_ERR_NOERROR) {
+ /*
+ * The rest of the info will be updated once the
+ * snmp_mibII module notifies us of the interface.
+ */
+ wif->status = RowStatus_active;
+ if (wif->state == wlanIfaceState_up)
+ (void)wlan_config_state(wif, 1);
+ }
+
+ return (rc);
+}
+
+static int
+wlan_iface_destroy(struct wlan_iface *wif)
+{
+ int rc = SNMP_ERR_NOERROR;
+
+ if (wif->internal == 0)
+ rc = wlan_clone_destroy(wif);
+
+ if (rc == SNMP_ERR_NOERROR)
+ wlan_delete_wif(wif);
+
+ return (rc);
+}
+
+static int
+wlan_update_interface(struct wlan_iface *wif)
+{
+ int i;
+
+ (void)wlan_config_state(wif, 0);
+ (void)wlan_get_driver_caps(wif);
+ for (i = LEAF_wlanIfacePacketBurst;
+ i <= LEAF_wlanIfaceTdmaBeaconInterval; i++)
+ (void)wlan_config_get_ioctl(wif, i);
+ (void)wlan_get_stats(wif);
+ /*
+ * XXX: wlan_get_channel_list() not needed -
+ * fetched with wlan_get_driver_caps()
+ */
+ (void)wlan_get_channel_list(wif);
+ (void)wlan_get_roam_params(wif);
+ (void)wlan_get_tx_params(wif);
+ (void)wlan_get_scan_results(wif);
+ (void)wlan_get_wepmode(wif);
+ (void)wlan_get_weptxkey(wif);
+ (void)wlan_get_mac_policy(wif);
+ (void)wlan_get_mac_acl_macs(wif);
+ (void)wlan_get_peerinfo(wif);
+
+ if (wif->mode == WlanIfaceOperatingModeType_meshPoint) {
+ for (i = LEAF_wlanMeshTTL; i <= LEAF_wlanMeshPath; i++)
+ (void)wlan_mesh_config_get(wif, i);
+ (void)wlan_mesh_get_routelist(wif);
+ for (i = LEAF_wlanHWMPRootMode; i <= LEAF_wlanHWMPMaxHops; i++)
+ (void)wlan_hwmp_config_get(wif, i);
+ }
+
+ return (0);
+}
+
+static void
+wlan_update_interface_list(void)
+{
+ struct wlan_iface *wif, *twif;
+
+ if ((time(NULL) - wlan_iflist_age) <= WLAN_LIST_MAXAGE)
+ return;
+
+ /*
+ * The snmp_mibII module would have notified us for new interfaces,
+ * so only check if any have been deleted.
+ */
+ SLIST_FOREACH_SAFE(wif, &wlan_ifaces, w_if, twif)
+ if (wif->status == RowStatus_active && wlan_get_opmode(wif) < 0)
+ wlan_delete_wif(wif);
+
+ wlan_iflist_age = time(NULL);
+}
+
+static void
+wlan_append_ifindex(struct asn_oid *oid, uint sub, const struct wlan_iface *w)
+{
+ uint32_t i;
+
+ oid->len = sub + strlen(w->wname) + 1;
+ oid->subs[sub] = strlen(w->wname);
+ for (i = 1; i <= strlen(w->wname); i++)
+ oid->subs[sub + i] = w->wname[i - 1];
+}
+
+static uint8_t *
+wlan_get_ifname(const struct asn_oid *oid, uint sub, uint8_t *wname)
+{
+ uint32_t i;
+
+ memset(wname, 0, IFNAMSIZ);
+
+ if (oid->len - sub != oid->subs[sub] + 1 || oid->subs[sub] >= IFNAMSIZ)
+ return (NULL);
+
+ for (i = 0; i < oid->subs[sub]; i++)
+ wname[i] = oid->subs[sub + i + 1];
+ wname[i] = '\0';
+
+ return (wname);
+}
+
+static struct wlan_iface *
+wlan_get_interface(const struct asn_oid *oid, uint sub)
+{
+ uint8_t wname[IFNAMSIZ];
+
+ if (wlan_get_ifname(oid, sub, wname) == NULL)
+ return (NULL);
+
+ return (wlan_find_interface(wname));
+}
+
+static struct wlan_iface *
+wlan_get_next_interface(const struct asn_oid *oid, uint sub)
+{
+ uint32_t i;
+ uint8_t wname[IFNAMSIZ];
+ struct wlan_iface *wif;
+
+ if (oid->len - sub == 0) {
+ for (wif = wlan_first_interface(); wif != NULL;
+ wif = wlan_next_interface(wif))
+ if (wif->status == RowStatus_active)
+ break;
+ return (wif);
+ }
+
+ if (oid->len - sub != oid->subs[sub] + 1 || oid->subs[sub] >= IFNAMSIZ)
+ return (NULL);
+
+ memset(wname, 0, IFNAMSIZ);
+ for (i = 0; i < oid->subs[sub]; i++)
+ wname[i] = oid->subs[sub + i + 1];
+ wname[i] = '\0';
+ if ((wif = wlan_find_interface(wname)) == NULL)
+ return (NULL);
+
+ while ((wif = wlan_next_interface(wif)) != NULL)
+ if (wif->status == RowStatus_active)
+ break;
+
+ return (wif);
+}
+
+static struct wlan_iface *
+wlan_get_snmp_interface(const struct asn_oid *oid, uint sub)
+{
+ uint8_t wname[IFNAMSIZ];
+ struct wlan_iface *wif;
+
+ if (wlan_get_ifname(oid, sub, wname) == NULL)
+ return (NULL);
+
+ for (wif = wlan_first_interface(); wif != NULL;
+ wif = wlan_next_interface(wif))
+ if (strcmp(wif->wname, wname) == 0)
+ break;
+
+ return (wif);
+}
+
+static struct wlan_iface *
+wlan_get_next_snmp_interface(const struct asn_oid *oid, uint sub)
+{
+ uint32_t i;
+ uint8_t wname[IFNAMSIZ];
+ struct wlan_iface *wif;
+
+ if (oid->len - sub == 0)
+ return (wlan_first_interface());
+
+ if (oid->len - sub != oid->subs[sub] + 1 || oid->subs[sub] >= IFNAMSIZ)
+ return (NULL);
+
+ memset(wname, 0, IFNAMSIZ);
+ for (i = 0; i < oid->subs[sub]; i++)
+ wname[i] = oid->subs[sub + i + 1];
+ wname[i] = '\0';
+
+ for (wif = wlan_first_interface(); wif != NULL;
+ wif = wlan_next_interface(wif))
+ if (strcmp(wif->wname, wname) == 0)
+ break;
+
+ return (wlan_next_interface(wif));
+}
+
+/*
+ * Decode/Append an index for tables indexed by the wireless interface
+ * name and a MAC address - ACL MACs and Mesh Routes.
+ */
+static int
+wlan_mac_index_decode(const struct asn_oid *oid, uint sub,
+ char *wname, uint8_t *mac)
+{
+ uint32_t i;
+ int mac_off;
+
+ if (oid->len - sub != oid->subs[sub] + 2 + IEEE80211_ADDR_LEN
+ || oid->subs[sub] >= IFNAMSIZ)
+ return (-1);
+
+ for (i = 0; i < oid->subs[sub]; i++)
+ wname[i] = oid->subs[sub + i + 1];
+ wname[i] = '\0';
+
+ mac_off = sub + oid->subs[sub] + 1;
+ if (oid->subs[mac_off] != IEEE80211_ADDR_LEN)
+ return (-1);
+ for (i = 0; i < IEEE80211_ADDR_LEN; i++)
+ mac[i] = oid->subs[mac_off + i + 1];
+
+ return (0);
+}
+
+static void
+wlan_append_mac_index(struct asn_oid *oid, uint sub, char *wname, uint8_t *mac)
+{
+ uint32_t i;
+
+ oid->len = sub + strlen(wname) + IEEE80211_ADDR_LEN + 2;
+ oid->subs[sub] = strlen(wname);
+ for (i = 1; i <= strlen(wname); i++)
+ oid->subs[sub + i] = wname[i - 1];
+
+ sub += strlen(wname) + 1;
+ oid->subs[sub] = IEEE80211_ADDR_LEN;
+ for (i = 1; i <= IEEE80211_ADDR_LEN; i++)
+ oid->subs[sub + i] = mac[i - 1];
+}
+
+/*
+ * Decode/Append an index for tables indexed by the wireless interface
+ * name and the PHY mode - Roam and TX params.
+ */
+static int
+wlan_phy_index_decode(const struct asn_oid *oid, uint sub, char *wname,
+ uint32_t *phy)
+{
+ uint32_t i;
+
+ if (oid->len - sub != oid->subs[sub] + 2 || oid->subs[sub] >= IFNAMSIZ)
+ return (-1);
+
+ for (i = 0; i < oid->subs[sub]; i++)
+ wname[i] = oid->subs[sub + i + 1];
+ wname[i] = '\0';
+
+ *phy = oid->subs[sub + oid->subs[sub] + 1];
+ return (0);
+}
+
+static void
+wlan_append_phy_index(struct asn_oid *oid, uint sub, char *wname, uint32_t phy)
+{
+ uint32_t i;
+
+ oid->len = sub + strlen(wname) + 2;
+ oid->subs[sub] = strlen(wname);
+ for (i = 1; i <= strlen(wname); i++)
+ oid->subs[sub + i] = wname[i - 1];
+ oid->subs[sub + strlen(wname) + 1] = phy;
+}
+
+/*
+ * Calls for manipulating the peerlist of a wireless interface.
+ */
+static void
+wlan_free_peerlist(struct wlan_iface *wif)
+{
+ struct wlan_peer *wip;
+
+ while ((wip = SLIST_FIRST(&wif->peerlist)) != NULL) {
+ SLIST_REMOVE_HEAD(&wif->peerlist, wp);
+ free(wip);
+ }
+
+ SLIST_INIT(&wif->peerlist);
+}
+
+static struct wlan_peer *
+wlan_find_peer(struct wlan_iface *wif, uint8_t *peermac)
+{
+ struct wlan_peer *wip;
+
+ SLIST_FOREACH(wip, &wif->peerlist, wp)
+ if (memcmp(wip->pmac, peermac, IEEE80211_ADDR_LEN) == 0)
+ break;
+
+ return (wip);
+}
+
+struct wlan_peer *
+wlan_new_peer(const uint8_t *pmac)
+{
+ struct wlan_peer *wip;
+
+ if ((wip = (struct wlan_peer *)malloc(sizeof(*wip))) == NULL)
+ return (NULL);
+
+ memset(wip, 0, sizeof(struct wlan_peer));
+ memcpy(wip->pmac, pmac, IEEE80211_ADDR_LEN);
+
+ return (wip);
+}
+
+void
+wlan_free_peer(struct wlan_peer *wip)
+{
+ free(wip);
+}
+
+int
+wlan_add_peer(struct wlan_iface *wif, struct wlan_peer *wip)
+{
+ struct wlan_peer *temp, *prev;
+
+ SLIST_FOREACH(temp, &wif->peerlist, wp)
+ if (memcmp(temp->pmac, wip->pmac, IEEE80211_ADDR_LEN) == 0)
+ return (-1);
+
+ if ((prev = SLIST_FIRST(&wif->peerlist)) == NULL ||
+ memcmp(wip->pmac, prev->pmac, IEEE80211_ADDR_LEN) < 0) {
+ SLIST_INSERT_HEAD(&wif->peerlist, wip, wp);
+ return (0);
+ }
+
+ SLIST_FOREACH(temp, &wif->peerlist, wp) {
+ if (memcmp(wip->pmac, temp->pmac, IEEE80211_ADDR_LEN) < 0)
+ break;
+ prev = temp;
+ }
+
+ SLIST_INSERT_AFTER(prev, wip, wp);
+ return (0);
+}
+
+static void
+wlan_update_peers(void)
+{
+ struct wlan_iface *wif;
+
+ if ((time(NULL) - wlan_peerlist_age) <= WLAN_LIST_MAXAGE)
+ return;
+
+ for (wif = wlan_first_interface(); wif != NULL;
+ wif = wlan_next_interface(wif)) {
+ if (wif->status != RowStatus_active)
+ continue;
+ wlan_free_peerlist(wif);
+ (void)wlan_get_peerinfo(wif);
+ }
+ wlan_peerlist_age = time(NULL);
+}
+
+static struct wlan_peer *
+wlan_get_peer(const struct asn_oid *oid, uint sub, struct wlan_iface **wif)
+{
+ char wname[IFNAMSIZ];
+ uint8_t pmac[IEEE80211_ADDR_LEN];
+
+ if (wlan_mac_index_decode(oid, sub, wname, pmac) < 0)
+ return (NULL);
+
+ if ((*wif = wlan_find_interface(wname)) == NULL)
+ return (NULL);
+
+ return (wlan_find_peer(*wif, pmac));
+}
+
+static struct wlan_peer *
+wlan_get_next_peer(const struct asn_oid *oid, uint sub, struct wlan_iface **wif)
+{
+ char wname[IFNAMSIZ];
+ char pmac[IEEE80211_ADDR_LEN];
+ struct wlan_peer *wip;
+
+ if (oid->len - sub == 0) {
+ for (*wif = wlan_first_interface(); *wif != NULL;
+ *wif = wlan_next_interface(*wif)) {
+ if ((*wif)->mode ==
+ WlanIfaceOperatingModeType_meshPoint)
+ continue;
+ wip = SLIST_FIRST(&(*wif)->peerlist);
+ if (wip != NULL)
+ return (wip);
+ }
+ return (NULL);
+ }
+
+ if (wlan_mac_index_decode(oid, sub, wname, pmac) < 0 ||
+ (*wif = wlan_find_interface(wname)) == NULL ||
+ (wip = wlan_find_peer(*wif, pmac)) == NULL)
+ return (NULL);
+
+ if ((wip = SLIST_NEXT(wip, wp)) != NULL)
+ return (wip);
+
+ while ((*wif = wlan_next_interface(*wif)) != NULL) {
+ if ((*wif)->mode == WlanIfaceOperatingModeType_meshPoint)
+ continue;
+ if ((wip = SLIST_FIRST(&(*wif)->peerlist)) != NULL)
+ break;
+ }
+
+ return (wip);
+}
+
+/*
+ * Calls for manipulating the active channel list of a wireless interface.
+ */
+static void
+wlan_update_channels(void)
+{
+ struct wlan_iface *wif;
+
+ if ((time(NULL) - wlan_chanlist_age) <= WLAN_LIST_MAXAGE)
+ return;
+
+ for (wif = wlan_first_interface(); wif != NULL;
+ wif = wlan_next_interface(wif)) {
+ if (wif->status != RowStatus_active)
+ continue;
+ (void)wlan_get_channel_list(wif);
+ }
+ wlan_chanlist_age = time(NULL);
+}
+
+static int
+wlan_channel_index_decode(const struct asn_oid *oid, uint sub, char *wname,
+ uint32_t *cindex)
+{
+ uint32_t i;
+ if (oid->len - sub != oid->subs[sub] + 2 || oid->subs[sub] >= IFNAMSIZ)
+ return (-1);
+
+ for (i = 0; i < oid->subs[sub]; i++)
+ wname[i] = oid->subs[sub + i + 1];
+ wname[i] = '\0';
+
+ *cindex = oid->subs[sub + oid->subs[sub] + 1];
+
+ return (0);
+}
+
+static void
+wlan_append_channel_index(struct asn_oid *oid, uint sub,
+ const struct wlan_iface *wif, const struct ieee80211_channel *channel)
+{
+ uint32_t i;
+
+ oid->len = sub + strlen(wif->wname) + 2;
+ oid->subs[sub] = strlen(wif->wname);
+ for (i = 1; i <= strlen(wif->wname); i++)
+ oid->subs[sub + i] = wif->wname[i - 1];
+ oid->subs[sub + strlen(wif->wname) + 1] = (channel - wif->chanlist) + 1;
+}
+
+static int32_t
+wlan_get_channel_type(struct ieee80211_channel *c)
+{
+ if (IEEE80211_IS_CHAN_FHSS(c))
+ return (WlanChannelType_fhss);
+ if (IEEE80211_IS_CHAN_A(c))
+ return (WlanChannelType_dot11a);
+ if (IEEE80211_IS_CHAN_B(c))
+ return (WlanChannelType_dot11b);
+ if (IEEE80211_IS_CHAN_ANYG(c))
+ return (WlanChannelType_dot11g);
+ if (IEEE80211_IS_CHAN_HALF(c))
+ return (WlanChannelType_tenMHz);
+ if (IEEE80211_IS_CHAN_QUARTER(c))
+ return (WlanChannelType_fiveMHz);
+ if (IEEE80211_IS_CHAN_TURBO(c))
+ return (WlanChannelType_turbo);
+ if (IEEE80211_IS_CHAN_HT(c))
+ return (WlanChannelType_ht);
+
+ return (-1);
+}
+
+static struct ieee80211_channel *
+wlan_find_channel(struct wlan_iface *wif, uint32_t cindex)
+{
+ if (wif->chanlist == NULL || cindex > wif->nchannels)
+ return (NULL);
+
+ return (wif->chanlist + cindex - 1);
+}
+
+static struct ieee80211_channel *
+wlan_get_channel(const struct asn_oid *oid, uint sub, struct wlan_iface **wif)
+{
+ uint32_t cindex;
+ char wname[IFNAMSIZ];
+
+ if (wlan_channel_index_decode(oid, sub, wname, &cindex) < 0)
+ return (NULL);
+
+ if ((*wif = wlan_find_interface(wname)) == NULL)
+ return (NULL);
+
+ return (wlan_find_channel(*wif, cindex));
+}
+
+static struct ieee80211_channel *
+wlan_get_next_channel(const struct asn_oid *oid, uint sub,
+ struct wlan_iface **wif)
+{
+ uint32_t cindex;
+ char wname[IFNAMSIZ];
+
+ if (oid->len - sub == 0) {
+ for (*wif = wlan_first_interface(); *wif != NULL;
+ *wif = wlan_next_interface(*wif)) {
+ if ((*wif)->status != RowStatus_active)
+ continue;
+ if ((*wif)->nchannels != 0 && (*wif)->chanlist != NULL)
+ return ((*wif)->chanlist);
+ }
+ return (NULL);
+ }
+
+ if (wlan_channel_index_decode(oid, sub, wname, &cindex) < 0)
+ return (NULL);
+
+ if ((*wif = wlan_find_interface(wname)) == NULL)
+ return (NULL);
+
+ if (cindex < (*wif)->nchannels)
+ return ((*wif)->chanlist + cindex);
+
+ while ((*wif = wlan_next_interface(*wif)) != NULL)
+ if ((*wif)->status == RowStatus_active)
+ if ((*wif)->nchannels != 0 && (*wif)->chanlist != NULL)
+ return ((*wif)->chanlist);
+
+ return (NULL);
+}
+
+/*
+ * Calls for manipulating the roam params of a wireless interface.
+ */
+static void
+wlan_update_roam_params(void)
+{
+ struct wlan_iface *wif;
+
+ if ((time(NULL) - wlan_roamlist_age) <= WLAN_LIST_MAXAGE)
+ return;
+
+ for (wif = wlan_first_interface(); wif != NULL;
+ wif = wlan_next_interface(wif)) {
+ if (wif->status != RowStatus_active)
+ continue;
+ (void)wlan_get_roam_params(wif);
+ }
+ wlan_roamlist_age = time(NULL);
+}
+
+static struct ieee80211_roamparam *
+wlan_get_roam_param(const struct asn_oid *oid, uint sub, struct wlan_iface **wif)
+{
+ uint32_t phy;
+ char wname[IFNAMSIZ];
+
+ if (wlan_phy_index_decode(oid, sub, wname, &phy) < 0)
+ return (NULL);
+
+ if ((*wif = wlan_find_interface(wname)) == NULL)
+ return (NULL);
+
+ if (phy == 0 || phy > IEEE80211_MODE_MAX)
+ return (NULL);
+
+ return ((*wif)->roamparams.params + phy - 1);
+}
+
+static struct ieee80211_roamparam *
+wlan_get_next_roam_param(const struct asn_oid *oid, uint sub,
+ struct wlan_iface **wif, uint32_t *phy)
+{
+ char wname[IFNAMSIZ];
+
+ if (oid->len - sub == 0) {
+ for (*wif = wlan_first_interface(); *wif != NULL;
+ *wif = wlan_next_interface(*wif)) {
+ if ((*wif)->status != RowStatus_active)
+ continue;
+ *phy = 1;
+ return ((*wif)->roamparams.params);
+ }
+ return (NULL);
+ }
+
+ if (wlan_phy_index_decode(oid, sub, wname, phy) < 0)
+ return (NULL);
+
+ if (*phy == 0 || (*wif = wlan_find_interface(wname)) == NULL)
+ return (NULL);
+
+ if (++(*phy) <= IEEE80211_MODE_MAX)
+ return ((*wif)->roamparams.params + *phy - 1);
+
+ *phy = 1;
+ while ((*wif = wlan_next_interface(*wif)) != NULL)
+ if ((*wif)->status == RowStatus_active)
+ return ((*wif)->roamparams.params);
+
+ return (NULL);
+}
+
+/*
+ * Calls for manipulating the tx params of a wireless interface.
+ */
+static void
+wlan_update_tx_params(void)
+{
+ struct wlan_iface *wif;
+
+ if ((time(NULL) - wlan_tx_paramlist_age) <= WLAN_LIST_MAXAGE)
+ return;
+
+ for (wif = wlan_first_interface(); wif != NULL;
+ wif = wlan_next_interface(wif)) {
+ if (wif->status != RowStatus_active)
+ continue;
+ (void)wlan_get_tx_params(wif);
+ }
+
+ wlan_tx_paramlist_age = time(NULL);
+}
+
+static struct ieee80211_txparam *
+wlan_get_tx_param(const struct asn_oid *oid, uint sub, struct wlan_iface **wif,
+ uint32_t *phy)
+{
+ char wname[IFNAMSIZ];
+
+ if (wlan_phy_index_decode(oid, sub, wname, phy) < 0)
+ return (NULL);
+
+ if ((*wif = wlan_find_interface(wname)) == NULL)
+ return (NULL);
+
+ if (*phy == 0 || *phy > IEEE80211_MODE_MAX)
+ return (NULL);
+
+ return ((*wif)->txparams.params + *phy - 1);
+}
+
+static struct ieee80211_txparam *
+wlan_get_next_tx_param(const struct asn_oid *oid, uint sub,
+ struct wlan_iface **wif, uint32_t *phy)
+{
+ char wname[IFNAMSIZ];
+
+ if (oid->len - sub == 0) {
+ for (*wif = wlan_first_interface(); *wif != NULL;
+ *wif = wlan_next_interface(*wif)) {
+ if ((*wif)->status != RowStatus_active)
+ continue;
+ *phy = 1;
+ return ((*wif)->txparams.params);
+ }
+ return (NULL);
+ }
+
+ if (wlan_phy_index_decode(oid, sub, wname, phy) < 0)
+ return (NULL);
+
+ if (*phy == 0 || (*wif = wlan_find_interface(wname)) == NULL)
+ return (NULL);
+
+ if (++(*phy) <= IEEE80211_MODE_MAX)
+ return ((*wif)->txparams.params + *phy - 1);
+
+ *phy = 1;
+ while ((*wif = wlan_next_interface(*wif)) != NULL)
+ if ((*wif)->status == RowStatus_active)
+ return ((*wif)->txparams.params);
+
+ return (NULL);
+}
+
+/*
+ * Calls for manipulating the scan results for a wireless interface.
+ */
+static void
+wlan_scan_free_results(struct wlan_iface *wif)
+{
+ struct wlan_scan_result *sr;
+
+ while ((sr = SLIST_FIRST(&wif->scanlist)) != NULL) {
+ SLIST_REMOVE_HEAD(&wif->scanlist, wsr);
+ free(sr);
+ }
+
+ SLIST_INIT(&wif->scanlist);
+}
+
+static struct wlan_scan_result *
+wlan_scan_find_result(struct wlan_iface *wif, uint8_t *ssid, uint8_t *bssid)
+{
+ struct wlan_scan_result *sr;
+
+ SLIST_FOREACH(sr, &wif->scanlist, wsr)
+ if (strlen(ssid) == strlen(sr->ssid) &&
+ strcmp(sr->ssid, ssid) == 0 &&
+ memcmp(sr->bssid, bssid, IEEE80211_ADDR_LEN) == 0)
+ break;
+
+ return (sr);
+}
+
+struct wlan_scan_result *
+wlan_scan_new_result(const uint8_t *ssid, const uint8_t *bssid)
+{
+ struct wlan_scan_result *sr;
+
+ sr = (struct wlan_scan_result *)malloc(sizeof(*sr));
+ if (sr == NULL)
+ return (NULL);
+
+ memset(sr, 0, sizeof(*sr));
+ if (ssid[0] != '\0')
+ strlcpy(sr->ssid, ssid, IEEE80211_NWID_LEN + 1);
+ memcpy(sr->bssid, bssid, IEEE80211_ADDR_LEN);
+
+ return (sr);
+}
+
+void
+wlan_scan_free_result(struct wlan_scan_result *sr)
+{
+ free(sr);
+}
+
+static int
+wlan_scan_compare_result(struct wlan_scan_result *sr1,
+ struct wlan_scan_result *sr2)
+{
+ uint32_t i;
+
+ if (strlen(sr1->ssid) < strlen(sr2->ssid))
+ return (-1);
+ if (strlen(sr1->ssid) > strlen(sr2->ssid))
+ return (1);
+
+ for (i = 0; i < strlen(sr1->ssid) && i < strlen(sr2->ssid); i++) {
+ if (sr1->ssid[i] < sr2->ssid[i])
+ return (-1);
+ if (sr1->ssid[i] > sr2->ssid[i])
+ return (1);
+ }
+
+ for (i = 0; i < IEEE80211_ADDR_LEN; i++) {
+ if (sr1->bssid[i] < sr2->bssid[i])
+ return (-1);
+ if (sr1->bssid[i] > sr2->bssid[i])
+ return (1);
+ }
+
+ return (0);
+}
+
+int
+wlan_scan_add_result(struct wlan_iface *wif, struct wlan_scan_result *sr)
+{
+ struct wlan_scan_result *prev, *temp;
+
+ SLIST_FOREACH(temp, &wif->scanlist, wsr)
+ if (strlen(temp->ssid) == strlen(sr->ssid) &&
+ strcmp(sr->ssid, temp->ssid) == 0 &&
+ memcmp(sr->bssid, temp->bssid, IEEE80211_ADDR_LEN) == 0)
+ return (-1);
+
+ if ((prev = SLIST_FIRST(&wif->scanlist)) == NULL ||
+ wlan_scan_compare_result(sr, prev) < 0) {
+ SLIST_INSERT_HEAD(&wif->scanlist, sr, wsr);
+ return (0);
+ }
+
+ SLIST_FOREACH(temp, &wif->scanlist, wsr) {
+ if (wlan_scan_compare_result(sr, temp) < 0)
+ break;
+ prev = temp;
+ }
+
+ SLIST_INSERT_AFTER(prev, sr, wsr);
+ return (0);
+}
+
+static void
+wlan_scan_update_results(void)
+{
+ struct wlan_iface *wif;
+
+ if ((time(NULL) - wlan_scanlist_age) <= WLAN_LIST_MAXAGE)
+ return;
+
+ for (wif = wlan_first_interface(); wif != NULL;
+ wif = wlan_next_interface(wif)) {
+ if (wif->status != RowStatus_active)
+ continue;
+ wlan_scan_free_results(wif);
+ (void)wlan_get_scan_results(wif);
+ }
+ wlan_scanlist_age = time(NULL);
+}
+
+static int
+wlan_scanr_index_decode(const struct asn_oid *oid, uint sub,
+ char *wname, uint8_t *ssid, uint8_t *bssid)
+{
+ uint32_t i;
+ int offset;
+
+ if (oid->subs[sub] >= IFNAMSIZ)
+ return (-1);
+ for (i = 0; i < oid->subs[sub]; i++)
+ wname[i] = oid->subs[sub + i + 1];
+ wname[oid->subs[sub]] = '\0';
+
+ offset = sub + oid->subs[sub] + 1;
+ if (oid->subs[offset] > IEEE80211_NWID_LEN)
+ return (-1);
+ for (i = 0; i < oid->subs[offset]; i++)
+ ssid[i] = oid->subs[offset + i + 1];
+ ssid[i] = '\0';
+
+ offset = sub + oid->subs[sub] + oid->subs[offset] + 2;
+ if (oid->subs[offset] != IEEE80211_ADDR_LEN)
+ return (-1);
+ for (i = 0; i < IEEE80211_ADDR_LEN; i++)
+ bssid[i] = oid->subs[offset + i + 1];
+
+ return (0);
+}
+
+static void
+wlan_append_scanr_index(struct asn_oid *oid, uint sub, char *wname,
+ uint8_t *ssid, uint8_t *bssid)
+{
+ uint32_t i;
+
+ oid->len = sub + strlen(wname) + strlen(ssid) + IEEE80211_ADDR_LEN + 3;
+ oid->subs[sub] = strlen(wname);
+ for (i = 1; i <= strlen(wname); i++)
+ oid->subs[sub + i] = wname[i - 1];
+
+ sub += strlen(wname) + 1;
+ oid->subs[sub] = strlen(ssid);
+ for (i = 1; i <= strlen(ssid); i++)
+ oid->subs[sub + i] = ssid[i - 1];
+
+ sub += strlen(ssid) + 1;
+ oid->subs[sub] = IEEE80211_ADDR_LEN;
+ for (i = 1; i <= IEEE80211_ADDR_LEN; i++)
+ oid->subs[sub + i] = bssid[i - 1];
+}
+
+static struct wlan_scan_result *
+wlan_get_scanr(const struct asn_oid *oid, uint sub, struct wlan_iface **wif)
+{
+ char wname[IFNAMSIZ];
+ uint8_t ssid[IEEE80211_NWID_LEN + 1];
+ uint8_t bssid[IEEE80211_ADDR_LEN];
+
+ if (wlan_scanr_index_decode(oid, sub, wname, ssid, bssid) < 0)
+ return (NULL);
+
+ if ((*wif = wlan_find_interface(wname)) == NULL)
+ return (NULL);
+
+ return (wlan_scan_find_result(*wif, ssid, bssid));
+}
+
+static struct wlan_scan_result *
+wlan_get_next_scanr(const struct asn_oid *oid, uint sub,
+ struct wlan_iface **wif)
+{
+ char wname[IFNAMSIZ];
+ uint8_t ssid[IEEE80211_NWID_LEN + 1];
+ uint8_t bssid[IEEE80211_ADDR_LEN];
+ struct wlan_scan_result *sr;
+
+ if (oid->len - sub == 0) {
+ for (*wif = wlan_first_interface(); *wif != NULL;
+ *wif = wlan_next_interface(*wif)) {
+ sr = SLIST_FIRST(&(*wif)->scanlist);
+ if (sr != NULL)
+ return (sr);
+ }
+ return (NULL);
+ }
+
+ if (wlan_scanr_index_decode(oid, sub, wname, ssid, bssid) < 0 ||
+ (*wif = wlan_find_interface(wname)) == NULL ||
+ (sr = wlan_scan_find_result(*wif, ssid, bssid)) == NULL)
+ return (NULL);
+
+ if ((sr = SLIST_NEXT(sr, wsr)) != NULL)
+ return (sr);
+
+ while ((*wif = wlan_next_interface(*wif)) != NULL)
+ if ((sr = SLIST_FIRST(&(*wif)->scanlist)) != NULL)
+ break;
+
+ return (sr);
+}
+
+/*
+ * MAC Access Control.
+ */
+static void
+wlan_mac_free_maclist(struct wlan_iface *wif)
+{
+ struct wlan_mac_mac *wmm;
+
+ while ((wmm = SLIST_FIRST(&wif->mac_maclist)) != NULL) {
+ SLIST_REMOVE_HEAD(&wif->mac_maclist, wm);
+ free(wmm);
+ }
+
+ SLIST_INIT(&wif->mac_maclist);
+}
+
+static struct wlan_mac_mac *
+wlan_mac_find_mac(struct wlan_iface *wif, uint8_t *mac)
+{
+ struct wlan_mac_mac *wmm;
+
+ SLIST_FOREACH(wmm, &wif->mac_maclist, wm)
+ if (memcmp(wmm->mac, mac, IEEE80211_ADDR_LEN) == 0)
+ break;
+
+ return (wmm);
+}
+
+struct wlan_mac_mac *
+wlan_mac_new_mac(const uint8_t *mac)
+{
+ struct wlan_mac_mac *wmm;
+
+ if ((wmm = (struct wlan_mac_mac *)malloc(sizeof(*wmm))) == NULL)
+ return (NULL);
+
+ memset(wmm, 0, sizeof(*wmm));
+ memcpy(wmm->mac, mac, IEEE80211_ADDR_LEN);
+ wmm->mac_status = RowStatus_notReady;
+
+ return (wmm);
+}
+
+void
+wlan_mac_free_mac(struct wlan_mac_mac *wmm)
+{
+ free(wmm);
+}
+
+int
+wlan_mac_add_mac(struct wlan_iface *wif, struct wlan_mac_mac *wmm)
+{
+ struct wlan_mac_mac *temp, *prev;
+
+ SLIST_FOREACH(temp, &wif->mac_maclist, wm)
+ if (memcmp(temp->mac, wmm->mac, IEEE80211_ADDR_LEN) == 0)
+ return (-1);
+
+ if ((prev = SLIST_FIRST(&wif->mac_maclist)) == NULL ||
+ memcmp(wmm->mac, prev->mac,IEEE80211_ADDR_LEN) < 0) {
+ SLIST_INSERT_HEAD(&wif->mac_maclist, wmm, wm);
+ return (0);
+ }
+
+ SLIST_FOREACH(temp, &wif->mac_maclist, wm) {
+ if (memcmp(wmm->mac, temp->mac, IEEE80211_ADDR_LEN) < 0)
+ break;
+ prev = temp;
+ }
+
+ SLIST_INSERT_AFTER(prev, wmm, wm);
+ return (0);
+}
+
+static int
+wlan_mac_delete_mac(struct wlan_iface *wif, struct wlan_mac_mac *wmm)
+{
+ if (wmm->mac_status == RowStatus_active &&
+ wlan_del_mac_acl_mac(wif, wmm) < 0)
+ return (-1);
+
+ SLIST_REMOVE(&wif->mac_maclist, wmm, wlan_mac_mac, wm);
+ free(wmm);
+
+ return (0);
+}
+
+static void
+wlan_mac_update_aclmacs(void)
+{
+ struct wlan_iface *wif;
+ struct wlan_mac_mac *wmm, *twmm;
+
+ if ((time(NULL) - wlan_maclist_age) <= WLAN_LIST_MAXAGE)
+ return;
+
+ for (wif = wlan_first_interface(); wif != NULL;
+ wif = wlan_next_interface(wif)) {
+ if (wif->status != RowStatus_active)
+ continue;
+ /*
+ * Nuke old entries - XXX - they are likely not to
+ * change often - reconsider.
+ */
+ SLIST_FOREACH_SAFE(wmm, &wif->mac_maclist, wm, twmm)
+ if (wmm->mac_status == RowStatus_active) {
+ SLIST_REMOVE(&wif->mac_maclist, wmm,
+ wlan_mac_mac, wm);
+ wlan_mac_free_mac(wmm);
+ }
+ (void)wlan_get_mac_acl_macs(wif);
+ }
+ wlan_maclist_age = time(NULL);
+}
+
+static struct wlan_mac_mac *
+wlan_get_acl_mac(const struct asn_oid *oid, uint sub, struct wlan_iface **wif)
+{
+ char wname[IFNAMSIZ];
+ char mac[IEEE80211_ADDR_LEN];
+
+ if (wlan_mac_index_decode(oid, sub, wname, mac) < 0)
+ return (NULL);
+
+ if ((*wif = wlan_find_interface(wname)) == NULL)
+ return (NULL);
+
+ return (wlan_mac_find_mac(*wif, mac));
+}
+
+static struct wlan_mac_mac *
+wlan_get_next_acl_mac(const struct asn_oid *oid, uint sub,
+ struct wlan_iface **wif)
+{
+ char wname[IFNAMSIZ];
+ char mac[IEEE80211_ADDR_LEN];
+ struct wlan_mac_mac *wmm;
+
+ if (oid->len - sub == 0) {
+ for (*wif = wlan_first_interface(); *wif != NULL;
+ *wif = wlan_next_interface(*wif)) {
+ wmm = SLIST_FIRST(&(*wif)->mac_maclist);
+ if (wmm != NULL)
+ return (wmm);
+ }
+ return (NULL);
+ }
+
+ if (wlan_mac_index_decode(oid, sub, wname, mac) < 0 ||
+ (*wif = wlan_find_interface(wname)) == NULL ||
+ (wmm = wlan_mac_find_mac(*wif, mac)) == NULL)
+ return (NULL);
+
+ if ((wmm = SLIST_NEXT(wmm, wm)) != NULL)
+ return (wmm);
+
+ while ((*wif = wlan_next_interface(*wif)) != NULL)
+ if ((wmm = SLIST_FIRST(&(*wif)->mac_maclist)) != NULL)
+ break;
+
+ return (wmm);
+}
+
+static int
+wlan_acl_mac_set_status(struct snmp_context *ctx, struct snmp_value *val,
+ uint sub)
+{
+ char wname[IFNAMSIZ];
+ uint8_t mac[IEEE80211_ADDR_LEN];
+ struct wlan_iface *wif;
+ struct wlan_mac_mac *macl;
+
+ if (wlan_mac_index_decode(&val->var, sub, wname, mac) < 0)
+ return (SNMP_ERR_GENERR);
+ macl = wlan_get_acl_mac(&val->var, sub, &wif);
+
+ switch (val->v.integer) {
+ case RowStatus_createAndGo:
+ if (macl != NULL)
+ return (SNMP_ERR_INCONS_NAME);
+ break;
+ case RowStatus_destroy:
+ if (macl == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ ctx->scratch->int1 = RowStatus_active;
+ return (SNMP_ERR_NOERROR);
+ default:
+ return (SNMP_ERR_INCONS_VALUE);
+ }
+
+
+ if (wif == NULL || !wif->macsupported)
+ return (SNMP_ERR_INCONS_VALUE);
+
+ if ((macl = wlan_mac_new_mac((const uint8_t *)mac)) == NULL)
+ return (SNMP_ERR_GENERR);
+
+ ctx->scratch->int1 = RowStatus_destroy;
+
+ if (wlan_mac_add_mac(wif, macl) < 0) {
+ wlan_mac_free_mac(macl);
+ return (SNMP_ERR_GENERR);
+ }
+
+ ctx->scratch->int1 = RowStatus_destroy;
+ if (wlan_add_mac_acl_mac(wif, macl) < 0) {
+ (void)wlan_mac_delete_mac(wif, macl);
+ return (SNMP_ERR_GENERR);
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+/*
+ * Wireless interfaces operating as mesh points.
+ */
+static struct wlan_iface *
+wlan_mesh_first_interface(void)
+{
+ struct wlan_iface *wif;
+
+ SLIST_FOREACH(wif, &wlan_ifaces, w_if)
+ if (wif->mode == WlanIfaceOperatingModeType_meshPoint &&
+ wif->status == RowStatus_active)
+ break;
+
+ return (wif);
+}
+
+static struct wlan_iface *
+wlan_mesh_next_interface(struct wlan_iface *wif)
+{
+ struct wlan_iface *nwif;
+
+ while ((nwif = wlan_next_interface(wif)) != NULL) {
+ if (nwif->mode == WlanIfaceOperatingModeType_meshPoint &&
+ nwif->status == RowStatus_active)
+ break;
+ wif = nwif;
+ }
+
+ return (nwif);
+}
+
+static struct wlan_iface *
+wlan_mesh_get_iface(const struct asn_oid *oid, uint sub)
+{
+ struct wlan_iface *wif;
+
+ if ((wif = wlan_get_interface(oid, sub)) == NULL)
+ return (NULL);
+
+ if (wif->mode != WlanIfaceOperatingModeType_meshPoint)
+ return (NULL);
+
+ return (wif);
+}
+
+static struct wlan_iface *
+wlan_mesh_get_next_iface(const struct asn_oid *oid, uint sub)
+{
+ uint32_t i;
+ uint8_t wname[IFNAMSIZ];
+ struct wlan_iface *wif;
+
+ if (oid->len - sub == 0)
+ return (wlan_mesh_first_interface());
+
+ if (oid->len - sub != oid->subs[sub] + 1 || oid->subs[sub] >= IFNAMSIZ)
+ return (NULL);
+
+ memset(wname, 0, IFNAMSIZ);
+ for (i = 0; i < oid->subs[sub]; i++)
+ wname[i] = oid->subs[sub + i + 1];
+ wname[i] = '\0';
+
+ if ((wif = wlan_find_interface(wname)) == NULL)
+ return (NULL);
+
+ return (wlan_mesh_next_interface(wif));
+}
+
+/*
+ * The neighbors of wireless interfaces operating as mesh points.
+ */
+static struct wlan_peer *
+wlan_mesh_get_peer(const struct asn_oid *oid, uint sub, struct wlan_iface **wif)
+{
+ char wname[IFNAMSIZ];
+ uint8_t pmac[IEEE80211_ADDR_LEN];
+
+ if (wlan_mac_index_decode(oid, sub, wname, pmac) < 0)
+ return (NULL);
+
+ if ((*wif = wlan_find_interface(wname)) == NULL ||
+ (*wif)->mode != WlanIfaceOperatingModeType_meshPoint)
+ return (NULL);
+
+ return (wlan_find_peer(*wif, pmac));
+}
+
+static struct wlan_peer *
+wlan_mesh_get_next_peer(const struct asn_oid *oid, uint sub, struct wlan_iface **wif)
+{
+ char wname[IFNAMSIZ];
+ char pmac[IEEE80211_ADDR_LEN];
+ struct wlan_peer *wip;
+
+ if (oid->len - sub == 0) {
+ for (*wif = wlan_mesh_first_interface(); *wif != NULL;
+ *wif = wlan_mesh_next_interface(*wif)) {
+ wip = SLIST_FIRST(&(*wif)->peerlist);
+ if (wip != NULL)
+ return (wip);
+ }
+ return (NULL);
+ }
+
+ if (wlan_mac_index_decode(oid, sub, wname, pmac) < 0 ||
+ (*wif = wlan_find_interface(wname)) == NULL ||
+ (*wif)->mode != WlanIfaceOperatingModeType_meshPoint ||
+ (wip = wlan_find_peer(*wif, pmac)) == NULL)
+ return (NULL);
+
+ if ((wip = SLIST_NEXT(wip, wp)) != NULL)
+ return (wip);
+
+ while ((*wif = wlan_mesh_next_interface(*wif)) != NULL)
+ if ((wip = SLIST_FIRST(&(*wif)->peerlist)) != NULL)
+ break;
+
+ return (wip);
+}
+
+/*
+ * Mesh routing table.
+ */
+static void
+wlan_mesh_free_routes(struct wlan_iface *wif)
+{
+ struct wlan_mesh_route *wmr;
+
+ while ((wmr = SLIST_FIRST(&wif->mesh_routelist)) != NULL) {
+ SLIST_REMOVE_HEAD(&wif->mesh_routelist, wr);
+ free(wmr);
+ }
+
+ SLIST_INIT(&wif->mesh_routelist);
+}
+
+static struct wlan_mesh_route *
+wlan_mesh_find_route(struct wlan_iface *wif, uint8_t *dstmac)
+{
+ struct wlan_mesh_route *wmr;
+
+ if (wif->mode != WlanIfaceOperatingModeType_meshPoint)
+ return (NULL);
+
+ SLIST_FOREACH(wmr, &wif->mesh_routelist, wr)
+ if (memcmp(wmr->imroute.imr_dest, dstmac,
+ IEEE80211_ADDR_LEN) == 0)
+ break;
+
+ return (wmr);
+}
+
+struct wlan_mesh_route *
+wlan_mesh_new_route(const uint8_t *dstmac)
+{
+ struct wlan_mesh_route *wmr;
+
+ if ((wmr = (struct wlan_mesh_route *)malloc(sizeof(*wmr))) == NULL)
+ return (NULL);
+
+ memset(wmr, 0, sizeof(*wmr));
+ memcpy(wmr->imroute.imr_dest, dstmac, IEEE80211_ADDR_LEN);
+ wmr->mroute_status = RowStatus_notReady;
+
+ return (wmr);
+}
+
+void
+wlan_mesh_free_route(struct wlan_mesh_route *wmr)
+{
+ free(wmr);
+}
+
+int
+wlan_mesh_add_rtentry(struct wlan_iface *wif, struct wlan_mesh_route *wmr)
+{
+ struct wlan_mesh_route *temp, *prev;
+
+ SLIST_FOREACH(temp, &wif->mesh_routelist, wr)
+ if (memcmp(temp->imroute.imr_dest, wmr->imroute.imr_dest,
+ IEEE80211_ADDR_LEN) == 0)
+ return (-1);
+
+ if ((prev = SLIST_FIRST(&wif->mesh_routelist)) == NULL ||
+ memcmp(wmr->imroute.imr_dest, prev->imroute.imr_dest,
+ IEEE80211_ADDR_LEN) < 0) {
+ SLIST_INSERT_HEAD(&wif->mesh_routelist, wmr, wr);
+ return (0);
+ }
+
+ SLIST_FOREACH(temp, &wif->mesh_routelist, wr) {
+ if (memcmp(wmr->imroute.imr_dest, temp->imroute.imr_dest,
+ IEEE80211_ADDR_LEN) < 0)
+ break;
+ prev = temp;
+ }
+
+ SLIST_INSERT_AFTER(prev, wmr, wr);
+ return (0);
+}
+
+static int
+wlan_mesh_delete_route(struct wlan_iface *wif, struct wlan_mesh_route *wmr)
+{
+ if (wmr->mroute_status == RowStatus_active &&
+ wlan_mesh_del_route(wif, wmr) < 0)
+ return (-1);
+
+ SLIST_REMOVE(&wif->mesh_routelist, wmr, wlan_mesh_route, wr);
+ free(wmr);
+
+ return (0);
+}
+
+static void
+wlan_mesh_update_routes(void)
+{
+ struct wlan_iface *wif;
+ struct wlan_mesh_route *wmr, *twmr;
+
+ if ((time(NULL) - wlan_mrlist_age) <= WLAN_LIST_MAXAGE)
+ return;
+
+ for (wif = wlan_mesh_first_interface(); wif != NULL;
+ wif = wlan_mesh_next_interface(wif)) {
+ /*
+ * Nuke old entries - XXX - they are likely not to
+ * change often - reconsider.
+ */
+ SLIST_FOREACH_SAFE(wmr, &wif->mesh_routelist, wr, twmr)
+ if (wmr->mroute_status == RowStatus_active) {
+ SLIST_REMOVE(&wif->mesh_routelist, wmr,
+ wlan_mesh_route, wr);
+ wlan_mesh_free_route(wmr);
+ }
+ (void)wlan_mesh_get_routelist(wif);
+ }
+ wlan_mrlist_age = time(NULL);
+}
+
+static struct wlan_mesh_route *
+wlan_mesh_get_route(const struct asn_oid *oid, uint sub, struct wlan_iface **wif)
+{
+ char wname[IFNAMSIZ];
+ char dstmac[IEEE80211_ADDR_LEN];
+
+ if (wlan_mac_index_decode(oid, sub, wname, dstmac) < 0)
+ return (NULL);
+
+ if ((*wif = wlan_find_interface(wname)) == NULL)
+ return (NULL);
+
+ return (wlan_mesh_find_route(*wif, dstmac));
+}
+
+static struct wlan_mesh_route *
+wlan_mesh_get_next_route(const struct asn_oid *oid, uint sub,
+ struct wlan_iface **wif)
+{
+ char wname[IFNAMSIZ];
+ char dstmac[IEEE80211_ADDR_LEN];
+ struct wlan_mesh_route *wmr;
+
+ if (oid->len - sub == 0) {
+ for (*wif = wlan_mesh_first_interface(); *wif != NULL;
+ *wif = wlan_mesh_next_interface(*wif)) {
+ wmr = SLIST_FIRST(&(*wif)->mesh_routelist);
+ if (wmr != NULL)
+ return (wmr);
+ }
+ return (NULL);
+ }
+
+ if (wlan_mac_index_decode(oid, sub, wname, dstmac) < 0 ||
+ (*wif = wlan_find_interface(wname)) == NULL ||
+ (wmr = wlan_mesh_find_route(*wif, dstmac)) == NULL)
+ return (NULL);
+
+ if ((wmr = SLIST_NEXT(wmr, wr)) != NULL)
+ return (wmr);
+
+ while ((*wif = wlan_mesh_next_interface(*wif)) != NULL)
+ if ((wmr = SLIST_FIRST(&(*wif)->mesh_routelist)) != NULL)
+ break;
+
+ return (wmr);
+}
+
+static int
+wlan_mesh_route_set_status(struct snmp_context *ctx, struct snmp_value *val,
+ uint sub)
+{
+ char wname[IFNAMSIZ];
+ char mac[IEEE80211_ADDR_LEN];
+ struct wlan_mesh_route *wmr;
+ struct wlan_iface *wif;
+
+ if (wlan_mac_index_decode(&val->var, sub, wname, mac) < 0)
+ return (SNMP_ERR_GENERR);
+ wmr = wlan_mesh_get_route(&val->var, sub, &wif);
+
+ switch (val->v.integer) {
+ case RowStatus_createAndGo:
+ if (wmr != NULL)
+ return (SNMP_ERR_INCONS_NAME);
+ break;
+ case RowStatus_destroy:
+ if (wmr == NULL)
+ return (SNMP_ERR_NOSUCHNAME);
+ ctx->scratch->int1 = RowStatus_active;
+ return (SNMP_ERR_NOERROR);
+ default:
+ return (SNMP_ERR_INCONS_VALUE);
+ }
+
+ if ((wif = wlan_find_interface(wname)) == NULL)
+ return (SNMP_ERR_INCONS_NAME);
+
+ if ((wmr = wlan_mesh_new_route(mac)) == NULL)
+ return (SNMP_ERR_GENERR);
+
+ if (wlan_mesh_add_rtentry(wif, wmr) < 0) {
+ wlan_mesh_free_route(wmr);
+ return (SNMP_ERR_GENERR);
+ }
+
+ ctx->scratch->int1 = RowStatus_destroy;
+ if (wlan_mesh_add_route(wif, wmr) < 0) {
+ (void)wlan_mesh_delete_route(wif, wmr);
+ return (SNMP_ERR_GENERR);
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+/*
+ * Wlan snmp module initialization hook.
+ * Returns 0 on success, < 0 on error.
+ */
+static int
+wlan_init(struct lmodule * mod __unused, int argc __unused,
+ char *argv[] __unused)
+{
+ if (wlan_kmodules_load() < 0)
+ return (-1);
+
+ if (wlan_ioctl_init() < 0)
+ return (-1);
+
+ /* Register for new interface creation notifications. */
+ if (mib_register_newif(wlan_attach_newif, wlan_module)) {
+ syslog(LOG_ERR, "Cannot register newif function: %s",
+ strerror(errno));
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Wlan snmp module finalization hook.
+ */
+static int
+wlan_fini(void)
+{
+ mib_unregister_newif(wlan_module);
+ or_unregister(reg_wlan);
+
+ /* XXX: Cleanup! */
+ wlan_free_iflist();
+
+ return (0);
+}
+
+/*
+ * Refetch all available data from the kernel.
+ */
+static void
+wlan_update_data(void *arg __unused)
+{
+}
+
+/*
+ * Wlan snmp module start operation.
+ */
+static void
+wlan_start(void)
+{
+ struct mibif *ifp;
+
+ reg_wlan = or_register(&oid_wlan,
+ "The MIB module for managing wireless networking.", wlan_module);
+
+ /* Add the existing wlan interfaces. */
+ for (ifp = mib_first_if(); ifp != NULL; ifp = mib_next_if(ifp))
+ wlan_attach_newif(ifp);
+
+ wlan_data_timer = timer_start_repeat(wlan_poll_ticks,
+ wlan_poll_ticks, wlan_update_data, NULL, wlan_module);
+}
+
+/*
+ * Dump the Wlan snmp module data on SIGUSR1.
+ */
+static void
+wlan_dump(void)
+{
+ /* XXX: Print some debug info to syslog. */
+ struct wlan_iface *wif;
+
+ for (wif = wlan_first_interface(); wif != NULL;
+ wif = wlan_next_interface(wif))
+ syslog(LOG_ERR, "wlan iface %s", wif->wname);
+}
+
+const char wlan_comment[] = \
+"This module implements the BEGEMOT MIB for wireless networking.";
+
+const struct snmp_module config = {
+ .comment = wlan_comment,
+ .init = wlan_init,
+ .fini = wlan_fini,
+ .start = wlan_start,
+ .tree = wlan_ctree,
+ .dump = wlan_dump,
+ .tree_size = wlan_CTREE_SIZE,
+};
diff --git a/usr.sbin/bsnmpd/modules/snmp_wlan/wlan_snmp.h b/usr.sbin/bsnmpd/modules/snmp_wlan/wlan_snmp.h
new file mode 100644
index 0000000..9e184f5
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_wlan/wlan_snmp.h
@@ -0,0 +1,286 @@
+/*-
+ * Copyright (c) 2010 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Shteryana Sotirova Shopova 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$
+ */
+
+#define WLAN_IFMODE_MAX WlanIfaceOperatingModeType_tdma
+#define WLAN_COUNTRY_CODE_SIZE 3
+#define WLAN_BGSCAN_IDLE_MIN 100 /* XXX */
+#define WLAN_SCAN_VALID_MIN 10 /* XXX */
+#define WLAN_TDMA_MAXSLOTS 2 /* XXX */
+
+struct wlan_iface;
+
+struct wlan_peer {
+ uint8_t pmac[IEEE80211_ADDR_LEN]; /* key */
+ uint16_t associd;
+ uint16_t vlan;
+ uint16_t frequency;
+ uint32_t fflags;
+ uint8_t txrate;
+ int8_t rssi;
+ uint16_t idle;
+ uint16_t txseqs;
+ uint16_t rxseqs;
+ uint16_t txpower;
+ uint8_t capinfo;
+ uint32_t state;
+ uint16_t local_id;
+ uint16_t peer_id;
+ SLIST_ENTRY(wlan_peer) wp;
+};
+
+SLIST_HEAD(wlan_peerlist, wlan_peer);
+
+struct wlan_scan_result {
+ uint8_t ssid[IEEE80211_NWID_LEN + 1];
+ uint8_t bssid[IEEE80211_ADDR_LEN];
+ uint8_t opchannel;
+ int8_t rssi;
+ uint16_t frequency;
+ int8_t noise;
+ uint16_t bintval;
+ uint8_t capinfo;
+ struct wlan_iface *pwif;
+ SLIST_ENTRY(wlan_scan_result) wsr;
+};
+
+SLIST_HEAD(wlan_scanlist, wlan_scan_result);
+
+struct wlan_mac_mac {
+ uint8_t mac[IEEE80211_ADDR_LEN];
+ enum RowStatus mac_status;
+ SLIST_ENTRY(wlan_mac_mac) wm;
+};
+
+SLIST_HEAD(wlan_maclist, wlan_mac_mac);
+
+struct wlan_mesh_route {
+ struct ieee80211req_mesh_route imroute;
+ enum RowStatus mroute_status;
+ SLIST_ENTRY(wlan_mesh_route) wr;
+};
+
+SLIST_HEAD(wlan_mesh_routes, wlan_mesh_route);
+
+struct wlan_iface {
+ char wname[IFNAMSIZ];
+ uint32_t index;
+ char pname[IFNAMSIZ];
+ enum WlanIfaceOperatingModeType mode;
+ uint32_t flags;
+ uint8_t dbssid[IEEE80211_ADDR_LEN];
+ uint8_t dlmac[IEEE80211_ADDR_LEN];
+ enum RowStatus status;
+ enum wlanIfaceState state;
+ uint8_t internal;
+
+ uint32_t drivercaps;
+ uint32_t cryptocaps;
+ uint32_t htcaps;
+
+ uint32_t packet_burst;
+ uint8_t country_code[WLAN_COUNTRY_CODE_SIZE];
+ enum WlanRegDomainCode reg_domain;
+ uint8_t desired_ssid[IEEE80211_NWID_LEN + 1];
+ uint32_t desired_channel;
+ enum TruthValue dyn_frequency;
+ enum TruthValue fast_frames;
+ enum TruthValue dturbo;
+ int32_t tx_power;
+ int32_t frag_threshold;
+ int32_t rts_threshold;
+ enum TruthValue priv_subscribe;
+ enum TruthValue bg_scan;
+ int32_t bg_scan_idle;
+ int32_t bg_scan_interval;
+ int32_t beacons_missed;
+ uint8_t desired_bssid[IEEE80211_ADDR_LEN];
+ enum wlanIfaceRoamingMode roam_mode;
+ enum TruthValue dot11d;
+ enum TruthValue dot11h;
+ enum TruthValue dynamic_wds;
+ enum TruthValue power_save;
+ enum TruthValue ap_bridge;
+ int32_t beacon_interval;
+ int32_t dtim_period;
+ enum TruthValue hide_ssid;
+ enum TruthValue inact_process;
+ enum wlanIfaceDot11gProtMode do11g_protect;
+ enum TruthValue dot11g_pure;
+ enum TruthValue dot11n_pure;
+ enum WlanIfaceDot11nPduType ampdu;
+ int32_t ampdu_density;
+ int32_t ampdu_limit;
+ enum WlanIfaceDot11nPduType amsdu;
+ int32_t amsdu_limit;
+ enum TruthValue ht_enabled;
+ enum TruthValue ht_compatible;
+ enum wlanIfaceDot11nHTProtMode ht_prot_mode;
+ enum TruthValue rifs;
+ enum TruthValue short_gi;
+ enum wlanIfaceDot11nSMPSMode smps_mode;
+ int32_t tdma_slot;
+ int32_t tdma_slot_count;
+ int32_t tdma_slot_length;
+ int32_t tdma_binterval;
+
+ struct wlan_peerlist peerlist;
+ struct ieee80211_stats stats;
+ uint32_t nchannels;
+ struct ieee80211_channel *chanlist;
+ struct ieee80211_roamparams_req roamparams;
+ struct ieee80211_txparams_req txparams;
+
+ uint32_t scan_flags;
+ uint32_t scan_duration;
+ uint32_t scan_mindwell;
+ uint32_t scan_maxdwell;
+ enum wlanScanConfigStatus scan_status;
+ struct wlan_scanlist scanlist;
+
+ uint8_t wepsupported;
+ enum wlanWepMode wepmode;
+ int32_t weptxkey;
+
+ uint8_t macsupported;
+ enum wlanMACAccessControlPolicy mac_policy;
+ uint32_t mac_nacls;
+ struct wlan_maclist mac_maclist;
+
+ uint32_t mesh_ttl;
+ enum wlanMeshPeeringEnabled mesh_peering;
+ enum wlanMeshForwardingEnabled mesh_forwarding;
+ enum wlanMeshMetric mesh_metric;
+ enum wlanMeshPath mesh_path;
+ enum wlanHWMPRootMode hwmp_root_mode;
+ uint32_t hwmp_max_hops;
+ struct wlan_mesh_routes mesh_routelist;
+
+ SLIST_ENTRY(wlan_iface) w_if;
+};
+
+enum wlan_syscl {
+ WLAN_MESH_RETRY_TO = 0,
+ WLAN_MESH_HOLDING_TO,
+ WLAN_MESH_CONFIRM_TO,
+ WLAN_MESH_MAX_RETRIES,
+ WLAN_HWMP_TARGET_ONLY,
+ WLAN_HWMP_REPLY_FORWARD,
+ WLAN_HWMP_PATH_LIFETIME,
+ WLAN_HWMP_ROOT_TO,
+ WLAN_HWMP_ROOT_INT,
+ WLAN_HWMP_RANN_INT,
+ WLAN_HWMP_INACTIVITY_TO,
+ WLAN_SYSCTL_MAX
+};
+
+struct wlan_config {
+ int32_t mesh_retryto;
+ int32_t mesh_holdingto;
+ int32_t mesh_confirmto;
+ int32_t mesh_maxretries;
+ int32_t hwmp_targetonly;
+ int32_t hwmp_replyforward;
+ int32_t hwmp_pathlifetime;
+ int32_t hwmp_roottimeout;
+ int32_t hwmp_rootint;
+ int32_t hwmp_rannint;
+ int32_t hwmp_inact;
+};
+
+int wlan_ioctl_init(void);
+int wlan_kmodules_load(void);
+int wlan_check_media(char *);
+int wlan_config_state(struct wlan_iface *, uint8_t);
+int wlan_get_opmode(struct wlan_iface *wif);
+int wlan_get_local_addr(struct wlan_iface *wif);
+int wlan_get_parent(struct wlan_iface *wif);
+int wlan_get_driver_caps(struct wlan_iface *wif);
+uint8_t wlan_channel_state_to_snmp(uint8_t cstate);
+uint32_t wlan_channel_flags_to_snmp(uint32_t cflags);
+int wlan_get_channel_list(struct wlan_iface *wif);
+int wlan_get_roam_params(struct wlan_iface *wif);
+int wlan_get_tx_params(struct wlan_iface *wif);
+int wlan_set_tx_params(struct wlan_iface *wif, int32_t pmode);
+int wlan_clone_create(struct wlan_iface *);
+int wlan_clone_destroy(struct wlan_iface *wif);
+int wlan_config_get_dssid(struct wlan_iface *wif);
+int wlan_config_set_dssid(struct wlan_iface *wif, char *ssid, int slen);
+int wlan_config_get_ioctl(struct wlan_iface *wif, int which);
+int wlan_config_set_ioctl(struct wlan_iface *wif, int which, int val,
+ char *strval, int len);
+int wlan_set_scan_config(struct wlan_iface *wif);
+int wlan_get_scan_results(struct wlan_iface *wif);
+int wlan_get_stats(struct wlan_iface *wif);
+int wlan_get_wepmode(struct wlan_iface *wif);
+int wlan_set_wepmode(struct wlan_iface *wif);
+int wlan_get_weptxkey(struct wlan_iface *wif);
+int wlan_set_weptxkey(struct wlan_iface *wif);
+int wlan_get_wepkeys(struct wlan_iface *wif);
+int wlan_set_wepkeys(struct wlan_iface *wif);
+int wlan_get_mac_policy(struct wlan_iface *wif);
+int wlan_set_mac_policy(struct wlan_iface *wif);
+int wlan_flush_mac_mac(struct wlan_iface *wif);
+int wlan_get_mac_acl_macs(struct wlan_iface *wif);
+int wlan_add_mac_acl_mac(struct wlan_iface *wif, struct wlan_mac_mac *mmac);
+int wlan_del_mac_acl_mac(struct wlan_iface *wif, struct wlan_mac_mac *mmac);
+
+int32_t wlan_do_sysctl(struct wlan_config *cfg, enum wlan_syscl which, int set);
+int wlan_mesh_config_get(struct wlan_iface *wif, int which);
+int wlan_mesh_config_set(struct wlan_iface *wif, int which);
+int wlan_mesh_flush_routes(struct wlan_iface *wif);
+int wlan_mesh_add_route(struct wlan_iface *wif, struct wlan_mesh_route *wmr);
+int wlan_mesh_del_route(struct wlan_iface *wif, struct wlan_mesh_route *wmr);
+int wlan_mesh_get_routelist(struct wlan_iface *wif);
+int wlan_hwmp_config_get(struct wlan_iface *wif, int which);
+int wlan_hwmp_config_set(struct wlan_iface *wif, int which);
+
+/* XXX: static */
+
+int wlan_peer_set_vlan(struct wlan_iface *wif, struct wlan_peer *wip, int vlan);
+int wlan_get_peerinfo(struct wlan_iface *wif);
+
+/* XXX*/
+struct wlan_peer *wlan_new_peer(const uint8_t *pmac);
+void wlan_free_peer(struct wlan_peer *wip);
+int wlan_add_peer(struct wlan_iface *wif, struct wlan_peer *wip);
+
+struct wlan_scan_result * wlan_scan_new_result(const uint8_t *ssid,
+ const uint8_t *bssid);
+void wlan_scan_free_result(struct wlan_scan_result *sr);
+int wlan_scan_add_result(struct wlan_iface *wif, struct wlan_scan_result *sr);
+
+struct wlan_mac_mac *wlan_mac_new_mac(const uint8_t *mac);
+void wlan_mac_free_mac(struct wlan_mac_mac *wmm);
+int wlan_mac_add_mac(struct wlan_iface *wif, struct wlan_mac_mac *wmm);
+
+struct wlan_mesh_route *wlan_mesh_new_route(const uint8_t *dstmac);
+int wlan_mesh_add_rtentry(struct wlan_iface *wif, struct wlan_mesh_route *wmr);
+void wlan_mesh_free_route(struct wlan_mesh_route *wmr);
diff --git a/usr.sbin/bsnmpd/modules/snmp_wlan/wlan_sys.c b/usr.sbin/bsnmpd/modules/snmp_wlan/wlan_sys.c
new file mode 100644
index 0000000..739f11f
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_wlan/wlan_sys.c
@@ -0,0 +1,3145 @@
+/*-
+ * Copyright (c) 2010 The FreeBSD Foundation
+ * All rights reserved.
+
+ * This software was developed by Shteryana Sotirova Shopova 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/ioctl.h>
+#include <sys/param.h>
+#include <sys/module.h>
+#include <sys/linker.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_mib.h>
+#include <net/if_types.h>
+#include <net80211/ieee80211.h>
+#include <net80211/ieee80211_ioctl.h>
+#include <net80211/ieee80211_regdomain.h>
+
+#include <errno.h>
+#include <ifaddrs.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+
+#include <bsnmp/snmpmod.h>
+#include <bsnmp/snmp_mibII.h>
+
+#include "wlan_tree.h"
+#include "wlan_snmp.h"
+
+static int sock = -1;
+
+static int wlan_ioctl(char *, uint16_t, int *, void *, size_t *, int);
+static int wlan_kmod_load(const char *);
+static uint32_t wlan_drivercaps_to_snmp(uint32_t);
+static uint32_t wlan_cryptocaps_to_snmp(uint32_t);
+static uint32_t wlan_htcaps_to_snmp(uint32_t);
+static uint32_t wlan_peerstate_to_snmp(uint32_t);
+static uint32_t wlan_peercaps_to_snmp(uint32_t );
+static uint32_t wlan_channel_flags_to_snmp_phy(uint32_t);
+static uint32_t wlan_regdomain_to_snmp(int);
+static uint32_t wlan_snmp_to_scan_flags(int);
+static int wlan_config_snmp2ioctl(int);
+static int wlan_snmp_to_regdomain(enum WlanRegDomainCode);
+static int wlan_config_get_country(struct wlan_iface *);
+static int wlan_config_set_country(struct wlan_iface *, char *, int);
+static int wlan_config_get_dchannel(struct wlan_iface *wif);
+static int wlan_config_set_dchannel(struct wlan_iface *wif, uint32_t);
+static int wlan_config_get_bssid(struct wlan_iface *);
+static int wlan_config_set_bssid(struct wlan_iface *, uint8_t *);
+static void wlan_config_set_snmp_intval(struct wlan_iface *, int, int);
+static int wlan_config_snmp2value(int, int, int *);
+static int wlan_config_check(struct wlan_iface *, int);
+static int wlan_config_get_intval(struct wlan_iface *, int);
+static int wlan_config_set_intval(struct wlan_iface *, int, int);
+static int wlan_add_new_scan_result(struct wlan_iface *,
+ const struct ieee80211req_scan_result *, uint8_t *);
+static int wlan_add_mac_macinfo(struct wlan_iface *,
+ const struct ieee80211req_maclist *);
+static struct wlan_peer *wlan_add_peerinfo(const struct ieee80211req_sta_info *);
+
+int
+wlan_ioctl_init(void)
+{
+ if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
+ syslog(LOG_ERR, "cannot open socket : %s", strerror(errno));
+ return (-1);
+ }
+
+ return (0);
+}
+/*
+ * Load the needed modules in kernel if not already there.
+ */
+enum wlan_kmodules {
+ WLAN_KMOD = 0,
+ WLAN_KMOD_ACL,
+ WLAN_KMOD_WEP,
+ WLAN_KMODS_MAX
+};
+
+static const char *wmod_names[] = {
+ "wlan",
+ "wlan_wlan_acl",
+ "wlan_wep",
+ NULL
+};
+
+static int
+wlan_kmod_load(const char *modname)
+{
+ int fileid, modid;
+ struct module_stat mstat;
+
+ mstat.version = sizeof(struct module_stat);
+ for (fileid = kldnext(0); fileid > 0; fileid = kldnext(fileid)) {
+ for (modid = kldfirstmod(fileid); modid > 0;
+ modid = modfnext(modid)) {
+ if (modstat(modid, &mstat) < 0)
+ continue;
+ if (strcmp(modname, mstat.name) == 0)
+ return (0);
+ }
+ }
+
+ /* Not present - load it. */
+ if (kldload(modname) < 0) {
+ syslog(LOG_ERR, "failed to load %s kernel module - %s", modname,
+ strerror(errno));
+ return (-1);
+ }
+
+ return (1);
+}
+
+int
+wlan_kmodules_load(void)
+{
+ if (wlan_kmod_load(wmod_names[WLAN_KMOD]) < 0)
+ return (-1);
+
+ if (wlan_kmod_load(wmod_names[WLAN_KMOD_ACL]) > 0)
+ syslog(LOG_NOTICE, "SNMP wlan loaded %s module",
+ wmod_names[WLAN_KMOD_ACL]);
+
+ if (wlan_kmod_load(wmod_names[WLAN_KMOD_WEP]) > 0)
+ syslog(LOG_NOTICE, "SNMP wlan loaded %s module",
+ wmod_names[WLAN_KMOD_WEP]);
+
+ return (0);
+}
+
+/* XXX: FIXME */
+static int
+wlan_ioctl(char *wif_name, uint16_t req_type, int *val, void *arg,
+ size_t *argsize, int set)
+{
+ struct ieee80211req ireq;
+
+ memset(&ireq, 0, sizeof(struct ieee80211req));
+ strlcpy(ireq.i_name, wif_name, IFNAMSIZ);
+
+ ireq.i_type = req_type;
+ ireq.i_val = *val;
+ ireq.i_len = *argsize;
+ ireq.i_data = arg;
+
+ if (ioctl(sock, set ? SIOCS80211 : SIOCG80211, &ireq) < 0) {
+ syslog(LOG_ERR, "iface %s - %s param: ioctl(%d) "
+ "failed: %s", wif_name, set ? "set" : "get",
+ req_type, strerror(errno));
+ return (-1);
+ }
+
+ *argsize = ireq.i_len;
+ *val = ireq.i_val;
+
+ return (0);
+}
+
+int
+wlan_check_media(char *ifname)
+{
+ struct ifmediareq ifmr;
+
+ memset(&ifmr, 0, sizeof(struct ifmediareq));
+ strlcpy(ifmr.ifm_name, ifname, sizeof(ifmr.ifm_name));
+
+ if (ioctl(sock, SIOCGIFMEDIA, &ifmr) < 0 || ifmr.ifm_count == 0)
+ return (0); /* Interface doesn't support SIOCGIFMEDIA. */
+
+ if ((ifmr.ifm_status & IFM_AVALID) == 0)
+ return (0);
+
+ return (IFM_TYPE(ifmr.ifm_active));
+}
+
+int
+wlan_get_opmode(struct wlan_iface *wif)
+{
+ struct ifmediareq ifmr;
+
+ memset(&ifmr, 0, sizeof(struct ifmediareq));
+ strlcpy(ifmr.ifm_name, wif->wname, sizeof(ifmr.ifm_name));
+
+ if (ioctl(sock, SIOCGIFMEDIA, &ifmr) < 0) {
+ if (errno == ENXIO)
+ return (-1);
+ wif->mode = WlanIfaceOperatingModeType_station;
+ return (0);
+ }
+
+ if (ifmr.ifm_current & IFM_IEEE80211_ADHOC) {
+ if (ifmr.ifm_current & IFM_FLAG0)
+ wif->mode = WlanIfaceOperatingModeType_adhocDemo;
+ else
+ wif->mode = WlanIfaceOperatingModeType_ibss;
+ } else if (ifmr.ifm_current & IFM_IEEE80211_HOSTAP)
+ wif->mode = WlanIfaceOperatingModeType_hostAp;
+ else if (ifmr.ifm_current & IFM_IEEE80211_MONITOR)
+ wif->mode = WlanIfaceOperatingModeType_monitor;
+ else if (ifmr.ifm_current & IFM_IEEE80211_MBSS)
+ wif->mode = WlanIfaceOperatingModeType_meshPoint;
+ else if (ifmr.ifm_current & IFM_IEEE80211_WDS)
+ wif->mode = WlanIfaceOperatingModeType_wds;
+
+ return (0);
+}
+
+int
+wlan_config_state(struct wlan_iface *wif, uint8_t set)
+{
+ int flags;
+ struct ifreq ifr;
+
+ memset(&ifr, 0, sizeof(ifr));
+ strcpy(ifr.ifr_name, wif->wname);
+
+ if (ioctl(sock, SIOCGIFFLAGS, (caddr_t) &ifr) < 0) {
+ syslog(LOG_ERR, "set %s status: ioctl(SIOCGIFFLAGS) "
+ "failed: %s", wif->wname, strerror(errno));
+ return (-1);
+ }
+
+ if (set == 0) {
+ if ((ifr.ifr_flags & IFF_UP) != 0)
+ wif->state = wlanIfaceState_up;
+ else
+ wif->state = wlanIfaceState_down;
+ return (0);
+ }
+
+ flags = (ifr.ifr_flags & 0xffff) | (ifr.ifr_flagshigh << 16);
+
+ if (wif->state == wlanIfaceState_up)
+ flags |= IFF_UP;
+ else
+ flags &= ~IFF_UP;
+
+ ifr.ifr_flags = flags & 0xffff;
+ ifr.ifr_flagshigh = flags >> 16;
+ if (ioctl(sock, SIOCSIFFLAGS, (caddr_t) &ifr) < 0) {
+ syslog(LOG_ERR, "set %s %s: ioctl(SIOCSIFFLAGS) failed: %s",
+ wif->wname, wif->state == wlanIfaceState_up?"up":"down",
+ strerror(errno));
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+wlan_get_local_addr(struct wlan_iface *wif)
+{
+ int len;
+ char ifname[IFNAMSIZ];
+ struct ifaddrs *ifap, *ifa;
+ struct sockaddr_dl sdl;
+
+ if (getifaddrs(&ifap) != 0) {
+ syslog(LOG_ERR, "wlan get mac: getifaddrs() failed - %s",
+ strerror(errno));
+ return (-1);
+ }
+
+ for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr->sa_family != AF_LINK)
+ continue;
+ memcpy(&sdl, ifa->ifa_addr, sizeof(struct sockaddr_dl));
+ if (sdl.sdl_alen > IEEE80211_ADDR_LEN)
+ continue;
+ if ((len = sdl.sdl_nlen) >= IFNAMSIZ)
+ len = IFNAMSIZ - 1;
+ memcpy(ifname, sdl.sdl_data, len);
+ ifname[len] = '\0';
+ if (strcmp(wif->wname, ifname) == 0)
+ break;
+ }
+
+ freeifaddrs(ifap);
+ return (0);
+}
+
+int
+wlan_get_parent(struct wlan_iface *wif __unused)
+{
+ /* XXX: There's no way to fetch this from the kernel. */
+ return (0);
+}
+
+/* XXX */
+#define IEEE80211_C_STA 0x00000001 /* CAPABILITY: STA available */
+#define IEEE80211_C_8023ENCAP 0x00000002 /* CAPABILITY: 802.3 encap */
+#define IEEE80211_C_FF 0x00000040 /* CAPABILITY: ATH FF avail */
+#define IEEE80211_C_TURBOP 0x00000080 /* CAPABILITY: ATH Turbo avail*/
+#define IEEE80211_C_IBSS 0x00000100 /* CAPABILITY: IBSS available */
+#define IEEE80211_C_PMGT 0x00000200 /* CAPABILITY: Power mgmt */
+#define IEEE80211_C_HOSTAP 0x00000400 /* CAPABILITY: HOSTAP avail */
+#define IEEE80211_C_AHDEMO 0x00000800 /* CAPABILITY: Old Adhoc Demo */
+#define IEEE80211_C_SWRETRY 0x00001000 /* CAPABILITY: sw tx retry */
+#define IEEE80211_C_TXPMGT 0x00002000 /* CAPABILITY: tx power mgmt */
+#define IEEE80211_C_SHSLOT 0x00004000 /* CAPABILITY: short slottime */
+#define IEEE80211_C_SHPREAMBLE 0x00008000 /* CAPABILITY: short preamble */
+#define IEEE80211_C_MONITOR 0x00010000 /* CAPABILITY: monitor mode */
+#define IEEE80211_C_DFS 0x00020000 /* CAPABILITY: DFS/radar avail*/
+#define IEEE80211_C_MBSS 0x00040000 /* CAPABILITY: MBSS available */
+/* 0x7c0000 available */
+#define IEEE80211_C_WPA1 0x00800000 /* CAPABILITY: WPA1 avail */
+#define IEEE80211_C_WPA2 0x01000000 /* CAPABILITY: WPA2 avail */
+#define IEEE80211_C_WPA 0x01800000 /* CAPABILITY: WPA1+WPA2 avail*/
+#define IEEE80211_C_BURST 0x02000000 /* CAPABILITY: frame bursting */
+#define IEEE80211_C_WME 0x04000000 /* CAPABILITY: WME avail */
+#define IEEE80211_C_WDS 0x08000000 /* CAPABILITY: 4-addr support */
+/* 0x10000000 reserved */
+#define IEEE80211_C_BGSCAN 0x20000000 /* CAPABILITY: bg scanning */
+#define IEEE80211_C_TXFRAG 0x40000000 /* CAPABILITY: tx fragments */
+#define IEEE80211_C_TDMA 0x80000000 /* CAPABILITY: TDMA avail */
+
+static uint32_t
+wlan_drivercaps_to_snmp(uint32_t dcaps)
+{
+ uint32_t scaps = 0;
+
+ if ((dcaps & IEEE80211_C_STA) != 0)
+ scaps |= (0x1 << WlanDriverCaps_station);
+ if ((dcaps & IEEE80211_C_8023ENCAP) != 0)
+ scaps |= (0x1 << WlanDriverCaps_ieee8023encap);
+ if ((dcaps & IEEE80211_C_FF) != 0)
+ scaps |= (0x1 << WlanDriverCaps_athFastFrames);
+ if ((dcaps & IEEE80211_C_TURBOP) != 0)
+ scaps |= (0x1 << WlanDriverCaps_athTurbo);
+ if ((dcaps & IEEE80211_C_IBSS) != 0)
+ scaps |= (0x1 << WlanDriverCaps_ibss);
+ if ((dcaps & IEEE80211_C_PMGT) != 0)
+ scaps |= (0x1 << WlanDriverCaps_pmgt);
+ if ((dcaps & IEEE80211_C_HOSTAP) != 0)
+ scaps |= (0x1 << WlanDriverCaps_hostAp);
+ if ((dcaps & IEEE80211_C_AHDEMO) != 0)
+ scaps |= (0x1 << WlanDriverCaps_ahDemo);
+ if ((dcaps & IEEE80211_C_SWRETRY) != 0)
+ scaps |= (0x1 << WlanDriverCaps_swRetry);
+ if ((dcaps & IEEE80211_C_TXPMGT) != 0)
+ scaps |= (0x1 << WlanDriverCaps_txPmgt);
+ if ((dcaps & IEEE80211_C_SHSLOT) != 0)
+ scaps |= (0x1 << WlanDriverCaps_shortSlot);
+ if ((dcaps & IEEE80211_C_SHPREAMBLE) != 0)
+ scaps |= (0x1 << WlanDriverCaps_shortPreamble);
+ if ((dcaps & IEEE80211_C_MONITOR) != 0)
+ scaps |= (0x1 << WlanDriverCaps_monitor);
+ if ((dcaps & IEEE80211_C_DFS) != 0)
+ scaps |= (0x1 << WlanDriverCaps_dfs);
+ if ((dcaps & IEEE80211_C_MBSS) != 0)
+ scaps |= (0x1 << WlanDriverCaps_mbss);
+ if ((dcaps & IEEE80211_C_WPA1) != 0)
+ scaps |= (0x1 << WlanDriverCaps_wpa1);
+ if ((dcaps & IEEE80211_C_WPA2) != 0)
+ scaps |= (0x1 << WlanDriverCaps_wpa2);
+ if ((dcaps & IEEE80211_C_BURST) != 0)
+ scaps |= (0x1 << WlanDriverCaps_burst);
+ if ((dcaps & IEEE80211_C_WME) != 0)
+ scaps |= (0x1 << WlanDriverCaps_wme);
+ if ((dcaps & IEEE80211_C_WDS) != 0)
+ scaps |= (0x1 << WlanDriverCaps_wds);
+ if ((dcaps & IEEE80211_C_BGSCAN) != 0)
+ scaps |= (0x1 << WlanDriverCaps_bgScan);
+ if ((dcaps & IEEE80211_C_TXFRAG) != 0)
+ scaps |= (0x1 << WlanDriverCaps_txFrag);
+ if ((dcaps & IEEE80211_C_TDMA) != 0)
+ scaps |= (0x1 << WlanDriverCaps_tdma);
+
+ return (scaps);
+}
+
+static uint32_t
+wlan_cryptocaps_to_snmp(uint32_t ccaps)
+{
+ uint32_t scaps = 0;
+
+#if NOT_YET
+ if ((ccaps & IEEE80211_CRYPTO_WEP) != 0)
+ scaps |= (0x1 << wlanCryptoCaps_wep);
+ if ((ccaps & IEEE80211_CRYPTO_TKIP) != 0)
+ scaps |= (0x1 << wlanCryptoCaps_tkip);
+ if ((ccaps & IEEE80211_CRYPTO_AES_OCB) != 0)
+ scaps |= (0x1 << wlanCryptoCaps_aes);
+ if ((ccaps & IEEE80211_CRYPTO_AES_CCM) != 0)
+ scaps |= (0x1 << wlanCryptoCaps_aesCcm);
+ if ((ccaps & IEEE80211_CRYPTO_TKIPMIC) != 0)
+ scaps |= (0x1 << wlanCryptoCaps_tkipMic);
+ if ((ccaps & IEEE80211_CRYPTO_CKIP) != 0)
+ scaps |= (0x1 << wlanCryptoCaps_ckip);
+#else /* !NOT_YET */
+ scaps = ccaps;
+#endif
+ return (scaps);
+}
+
+#define IEEE80211_HTC_AMPDU 0x00010000 /* CAPABILITY: A-MPDU tx */
+#define IEEE80211_HTC_AMSDU 0x00020000 /* CAPABILITY: A-MSDU tx */
+/* NB: HT40 is implied by IEEE80211_HTCAP_CHWIDTH40 */
+#define IEEE80211_HTC_HT 0x00040000 /* CAPABILITY: HT operation */
+#define IEEE80211_HTC_SMPS 0x00080000 /* CAPABILITY: MIMO power save*/
+#define IEEE80211_HTC_RIFS 0x00100000 /* CAPABILITY: RIFS support */
+
+static uint32_t
+wlan_htcaps_to_snmp(uint32_t hcaps)
+{
+ uint32_t scaps = 0;
+
+ if ((hcaps & IEEE80211_HTCAP_LDPC) != 0)
+ scaps |= (0x1 << WlanHTCaps_ldpc);
+ if ((hcaps & IEEE80211_HTCAP_CHWIDTH40) != 0)
+ scaps |= (0x1 << WlanHTCaps_chwidth40);
+ if ((hcaps & IEEE80211_HTCAP_GREENFIELD) != 0)
+ scaps |= (0x1 << WlanHTCaps_greenField);
+ if ((hcaps & IEEE80211_HTCAP_SHORTGI20) != 0)
+ scaps |= (0x1 << WlanHTCaps_shortGi20);
+ if ((hcaps & IEEE80211_HTCAP_SHORTGI40) != 0)
+ scaps |= (0x1 << WlanHTCaps_shortGi40);
+ if ((hcaps & IEEE80211_HTCAP_TXSTBC) != 0)
+ scaps |= (0x1 << WlanHTCaps_txStbc);
+ if ((hcaps & IEEE80211_HTCAP_DELBA) != 0)
+ scaps |= (0x1 << WlanHTCaps_delba);
+ if ((hcaps & IEEE80211_HTCAP_MAXAMSDU_7935) != 0)
+ scaps |= (0x1 << WlanHTCaps_amsdu7935);
+ if ((hcaps & IEEE80211_HTCAP_DSSSCCK40) != 0)
+ scaps |= (0x1 << WlanHTCaps_dssscck40);
+ if ((hcaps & IEEE80211_HTCAP_PSMP) != 0)
+ scaps |= (0x1 << WlanHTCaps_psmp);
+ if ((hcaps & IEEE80211_HTCAP_40INTOLERANT) != 0)
+ scaps |= (0x1 << WlanHTCaps_fortyMHzIntolerant);
+ if ((hcaps & IEEE80211_HTCAP_LSIGTXOPPROT) != 0)
+ scaps |= (0x1 << WlanHTCaps_lsigTxOpProt);
+ if ((hcaps & IEEE80211_HTC_AMPDU) != 0)
+ scaps |= (0x1 << WlanHTCaps_htcAmpdu);
+ if ((hcaps & IEEE80211_HTC_AMSDU) != 0)
+ scaps |= (0x1 << WlanHTCaps_htcAmsdu);
+ if ((hcaps & IEEE80211_HTC_HT) != 0)
+ scaps |= (0x1 << WlanHTCaps_htcHt);
+ if ((hcaps & IEEE80211_HTC_SMPS) != 0)
+ scaps |= (0x1 << WlanHTCaps_htcSmps);
+ if ((hcaps & IEEE80211_HTC_RIFS) != 0)
+ scaps |= (0x1 << WlanHTCaps_htcRifs);
+
+ return (scaps);
+}
+
+/* XXX: Not here? */
+#define WLAN_SET_TDMA_OPMODE(w) do { \
+ if ((w)->mode == WlanIfaceOperatingModeType_adhocDemo && \
+ ((w)->drivercaps & WlanDriverCaps_tdma) != 0) \
+ (w)->mode = WlanIfaceOperatingModeType_tdma; \
+} while (0)
+int
+wlan_get_driver_caps(struct wlan_iface *wif)
+{
+ int val = 0;
+ size_t argsize;
+ struct ieee80211_devcaps_req dc;
+
+ memset(&dc, 0, sizeof(struct ieee80211_devcaps_req));
+ argsize = sizeof(struct ieee80211_devcaps_req);
+
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_DEVCAPS, &val, &dc,
+ &argsize, 0) < 0)
+ return (-1);
+
+ wif->drivercaps = wlan_drivercaps_to_snmp(dc.dc_drivercaps);
+ wif->cryptocaps = wlan_cryptocaps_to_snmp(dc.dc_cryptocaps);
+ wif->htcaps = wlan_htcaps_to_snmp(dc.dc_htcaps);
+
+ WLAN_SET_TDMA_OPMODE(wif);
+
+ argsize = dc.dc_chaninfo.ic_nchans * sizeof(struct ieee80211_channel);
+ wif->chanlist = (struct ieee80211_channel *)malloc(argsize);
+ if (wif->chanlist == NULL)
+ return (0);
+
+ memcpy(wif->chanlist, dc.dc_chaninfo.ic_chans, argsize);
+ wif->nchannels = dc.dc_chaninfo.ic_nchans;
+
+ return (0);
+}
+
+uint8_t
+wlan_channel_state_to_snmp(uint8_t cstate)
+{
+ uint8_t cs = 0;
+
+ if ((cstate & IEEE80211_CHANSTATE_RADAR) != 0)
+ cs |= (0x1 << WlanIfaceChannelStateType_radar);
+ if ((cstate & IEEE80211_CHANSTATE_CACDONE) != 0)
+ cs |= (0x1 << WlanIfaceChannelStateType_cacDone);
+ if ((cstate & IEEE80211_CHANSTATE_CWINT) != 0)
+ cs |= (0x1 << WlanIfaceChannelStateType_interferenceDetected);
+ if ((cstate & IEEE80211_CHANSTATE_NORADAR) != 0)
+ cs |= (0x1 << WlanIfaceChannelStateType_radarClear);
+
+ return (cs);
+}
+
+uint32_t
+wlan_channel_flags_to_snmp(uint32_t cflags)
+{
+ uint32_t cf = 0;
+
+ if ((cflags & IEEE80211_CHAN_TURBO) != 0)
+ cf |= (0x1 << WlanIfaceChannelFlagsType_turbo);
+ if ((cflags & IEEE80211_CHAN_CCK) != 0)
+ cf |= (0x1 << WlanIfaceChannelFlagsType_cck);
+ if ((cflags & IEEE80211_CHAN_OFDM) != 0)
+ cf |= (0x1 << WlanIfaceChannelFlagsType_ofdm);
+ if ((cflags & IEEE80211_CHAN_2GHZ) != 0)
+ cf |= (0x1 << WlanIfaceChannelFlagsType_spectrum2Ghz);
+ if ((cflags & IEEE80211_CHAN_5GHZ) != 0)
+ cf |= (0x1 << WlanIfaceChannelFlagsType_spectrum5Ghz);
+ if ((cflags & IEEE80211_CHAN_PASSIVE) != 0)
+ cf |= (0x1 << WlanIfaceChannelFlagsType_passiveScan);
+ if ((cflags & IEEE80211_CHAN_DYN) != 0)
+ cf |= (0x1 << WlanIfaceChannelFlagsType_dynamicCckOfdm);
+ if ((cflags & IEEE80211_CHAN_GFSK) != 0)
+ cf |= (0x1 << WlanIfaceChannelFlagsType_gfsk);
+ if ((cflags & IEEE80211_CHAN_GSM) != 0)
+ cf |= (0x1 << WlanIfaceChannelFlagsType_spectrum900Mhz);
+ if ((cflags & IEEE80211_CHAN_STURBO) != 0)
+ cf |= (0x1 << WlanIfaceChannelFlagsType_dot11aStaticTurbo);
+ if ((cflags & IEEE80211_CHAN_HALF) != 0)
+ cf |= (0x1 << WlanIfaceChannelFlagsType_halfRate);
+ if ((cflags & IEEE80211_CHAN_QUARTER) != 0)
+ cf |= (0x1 << WlanIfaceChannelFlagsType_quarterRate);
+ if ((cflags & IEEE80211_CHAN_HT20) != 0)
+ cf |= (0x1 << WlanIfaceChannelFlagsType_ht20);
+ if ((cflags & IEEE80211_CHAN_HT40U) != 0)
+ cf |= (0x1 << WlanIfaceChannelFlagsType_ht40u);
+ if ((cflags & IEEE80211_CHAN_HT40D) != 0)
+ cf |= (0x1 << WlanIfaceChannelFlagsType_ht40d);
+ if ((cflags & IEEE80211_CHAN_DFS) != 0)
+ cf |= (0x1 << WlanIfaceChannelFlagsType_dfs);
+ if ((cflags & IEEE80211_CHAN_4MSXMIT) != 0)
+ cf |= (0x1 << WlanIfaceChannelFlagsType_xmit4ms);
+ if ((cflags & IEEE80211_CHAN_NOADHOC) != 0)
+ cf |= (0x1 << WlanIfaceChannelFlagsType_noAdhoc);
+ if ((cflags & IEEE80211_CHAN_NOHOSTAP) != 0)
+ cf |= (0x1 << WlanIfaceChannelFlagsType_noHostAp);
+ if ((cflags & IEEE80211_CHAN_11D) != 0)
+ cf |= (0x1 << WlanIfaceChannelFlagsType_dot11d);
+
+ return (cf);
+}
+
+/* XXX: */
+#define WLAN_SNMP_MAX_CHANS 256
+int
+wlan_get_channel_list(struct wlan_iface *wif)
+{
+ int val = 0;
+ uint32_t i, nchans;
+ size_t argsize;
+ struct ieee80211req_chaninfo *chaninfo;
+ struct ieee80211req_chanlist active;
+ const struct ieee80211_channel *c;
+
+ argsize = sizeof(struct ieee80211req_chaninfo) +
+ sizeof(struct ieee80211_channel) * WLAN_SNMP_MAX_CHANS;
+ chaninfo = (struct ieee80211req_chaninfo *)malloc(argsize);
+ if (chaninfo == NULL)
+ return (-1);
+
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_CHANINFO, &val, chaninfo,
+ &argsize, 0) < 0)
+ return (-1);
+
+ argsize = sizeof(active);
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_CHANLIST, &val, &active,
+ &argsize, 0) < 0)
+ goto error;
+
+ for (i = 0, nchans = 0; i < chaninfo->ic_nchans; i++) {
+ c = &chaninfo->ic_chans[i];
+ if (!isset(active.ic_channels, c->ic_ieee))
+ continue;
+ nchans++;
+ }
+ wif->chanlist = (struct ieee80211_channel *)reallocf(wif->chanlist,
+ nchans * sizeof(*c));
+ if (wif->chanlist == NULL)
+ goto error;
+ wif->nchannels = nchans;
+ for (i = 0, nchans = 0; i < chaninfo->ic_nchans; i++) {
+ c = &chaninfo->ic_chans[i];
+ if (!isset(active.ic_channels, c->ic_ieee))
+ continue;
+ memcpy(wif->chanlist + nchans, c, sizeof (*c));
+ nchans++;
+ }
+
+ free(chaninfo);
+ return (0);
+error:
+ wif->nchannels = 0;
+ free(chaninfo);
+ return (-1);
+}
+
+static enum WlanIfPhyMode
+wlan_channel_flags_to_snmp_phy(uint32_t cflags)
+{
+ /* XXX: recheck */
+ if ((cflags & IEEE80211_CHAN_A) != 0)
+ return (WlanIfPhyMode_dot11a);
+ if ((cflags & IEEE80211_CHAN_B) != 0)
+ return (WlanIfPhyMode_dot11b);
+ if ((cflags & IEEE80211_CHAN_G) != 0 ||
+ (cflags & IEEE80211_CHAN_PUREG) != 0)
+ return (WlanIfPhyMode_dot11g);
+ if ((cflags & IEEE80211_CHAN_FHSS) != 0)
+ return (WlanIfPhyMode_fh);
+ if ((cflags & IEEE80211_CHAN_TURBO) != 0 &&
+ (cflags & IEEE80211_CHAN_A) != 0)
+ return (WlanIfPhyMode_turboA);
+ if ((cflags & IEEE80211_CHAN_TURBO) != 0 &&
+ (cflags & IEEE80211_CHAN_G) != 0)
+ return (WlanIfPhyMode_turboG);
+ if ((cflags & IEEE80211_CHAN_STURBO) != 0)
+ return (WlanIfPhyMode_sturboA);
+ if ((cflags & IEEE80211_CHAN_HALF) != 0)
+ return (WlanIfPhyMode_ofdmHalf);
+ if ((cflags & IEEE80211_CHAN_QUARTER) != 0)
+ return (WlanIfPhyMode_ofdmQuarter);
+
+ return (WlanIfPhyMode_auto);
+}
+
+int
+wlan_get_roam_params(struct wlan_iface *wif)
+{
+ int val = 0;
+ size_t argsize;
+
+ argsize = sizeof(struct ieee80211_roamparams_req);
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_ROAM, &val,
+ &wif->roamparams, &argsize, 0) < 0)
+ return (-1);
+
+ return (0);
+}
+
+int
+wlan_get_tx_params(struct wlan_iface *wif)
+{
+ int val = 0;
+ size_t argsize;
+
+ /*
+ * XXX: Reset IEEE80211_RATE_MCS bit on IEEE80211_MODE_11NA
+ * and IEEE80211_MODE_11NG modes.
+ */
+ argsize = sizeof(struct ieee80211_txparams_req);
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_TXPARAMS, &val,
+ &wif->txparams, &argsize, 0) < 0)
+ return (-1);
+
+ return (0);
+}
+
+int
+wlan_set_tx_params(struct wlan_iface *wif, int32_t pmode __unused)
+{
+ int val = 0;
+ size_t argsize;
+
+ /*
+ * XXX: Set IEEE80211_RATE_MCS bit on IEEE80211_MODE_11NA
+ * and IEEE80211_MODE_11NG modes.
+ */
+ argsize = sizeof(struct ieee80211_txparams_req);
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_TXPARAMS, &val,
+ &wif->txparams, &argsize, 1) < 0)
+ return (-1);
+
+ return (0);
+}
+
+int
+wlan_clone_create(struct wlan_iface *wif)
+{
+ struct ifreq ifr;
+ struct ieee80211_clone_params wcp;
+ static const uint8_t zerobssid[IEEE80211_ADDR_LEN];
+
+ memset(&wcp, 0, sizeof(wcp));
+ memset(&ifr, 0, sizeof(ifr));
+
+ /* Sanity checks. */
+ if (wif == NULL || wif->pname[0] == '\0' || wif->mode > WLAN_IFMODE_MAX)
+ return (SNMP_ERR_INCONS_VALUE);
+
+ if (wif->mode == WlanIfaceOperatingModeType_wds &&
+ memcmp(wif->dbssid, zerobssid, IEEE80211_ADDR_LEN) == 0)
+ return (SNMP_ERR_INCONS_VALUE);
+
+ strlcpy(wcp.icp_parent, wif->pname, IFNAMSIZ);
+ if ((wif->flags & WlanIfaceFlagsType_uniqueBssid) != 0)
+ wcp.icp_flags |= IEEE80211_CLONE_BSSID;
+ if ((wif->flags & WlanIfaceFlagsType_noBeacons) != 0)
+ wcp.icp_flags |= IEEE80211_CLONE_NOBEACONS;
+ if (wif->mode == WlanIfaceOperatingModeType_wds &&
+ (wif->flags & WlanIfaceFlagsType_wdsLegacy) != 0)
+ wcp.icp_flags |= IEEE80211_CLONE_WDSLEGACY;
+
+ switch (wif->mode) {
+ case WlanIfaceOperatingModeType_ibss:
+ wcp.icp_opmode = IEEE80211_M_IBSS;
+ break;
+ case WlanIfaceOperatingModeType_station:
+ wcp.icp_opmode = IEEE80211_M_STA;
+ break;
+ case WlanIfaceOperatingModeType_wds:
+ wcp.icp_opmode = IEEE80211_M_WDS;
+ break;
+ case WlanIfaceOperatingModeType_adhocDemo:
+ wcp.icp_opmode = IEEE80211_M_AHDEMO;
+ break;
+ case WlanIfaceOperatingModeType_hostAp:
+ wcp.icp_opmode = IEEE80211_M_HOSTAP;
+ break;
+ case WlanIfaceOperatingModeType_monitor:
+ wcp.icp_opmode = IEEE80211_M_MONITOR;
+ break;
+ case WlanIfaceOperatingModeType_meshPoint:
+ wcp.icp_opmode = IEEE80211_M_MBSS;
+ break;
+ case WlanIfaceOperatingModeType_tdma:
+ wcp.icp_opmode = IEEE80211_M_AHDEMO;
+ wcp.icp_flags |= IEEE80211_CLONE_TDMA;
+ break;
+ }
+
+ memcpy(wcp.icp_bssid, wif->dbssid, IEEE80211_ADDR_LEN);
+ if (memcmp(wif->dlmac, zerobssid, IEEE80211_ADDR_LEN) != 0) {
+ memcpy(wcp.icp_macaddr, wif->dlmac, IEEE80211_ADDR_LEN);
+ wcp.icp_flags |= IEEE80211_CLONE_MACADDR;
+ }
+
+ strlcpy(ifr.ifr_name, wif->wname, IFNAMSIZ);
+ ifr.ifr_data = (caddr_t) &wcp;
+
+ if (ioctl(sock, SIOCIFCREATE2, (caddr_t) &ifr) < 0) {
+ syslog(LOG_ERR, "wlan clone create: ioctl(SIOCIFCREATE2) "
+ "failed: %s", strerror(errno));
+ return (SNMP_ERR_GENERR);
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+int
+wlan_clone_destroy(struct wlan_iface *wif)
+{
+ struct ifreq ifr;
+
+ if (wif == NULL)
+ return (SNMP_ERR_INCONS_VALUE);
+
+ memset(&ifr, 0, sizeof(ifr));
+ strcpy(ifr.ifr_name, wif->wname);
+
+ if (ioctl(sock, SIOCIFDESTROY, &ifr) < 0) {
+ syslog(LOG_ERR, "wlan clone destroy: ioctl(SIOCIFDESTROY) "
+ "failed: %s", strerror(errno));
+ return (SNMP_ERR_GENERR);
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+static int
+wlan_config_snmp2ioctl(int which)
+{
+ int op;
+
+ switch (which) {
+ case LEAF_wlanIfacePacketBurst:
+ op = IEEE80211_IOC_BURST;
+ break;
+ case LEAF_wlanIfaceCountryCode:
+ op = IEEE80211_IOC_REGDOMAIN;
+ break;
+ case LEAF_wlanIfaceRegDomain:
+ op = IEEE80211_IOC_REGDOMAIN;
+ break;
+ case LEAF_wlanIfaceDesiredSsid:
+ op = IEEE80211_IOC_SSID;
+ break;
+ case LEAF_wlanIfaceDesiredChannel:
+ op = IEEE80211_IOC_CURCHAN;
+ break;
+ case LEAF_wlanIfaceDynamicFreqSelection:
+ op = IEEE80211_IOC_DFS;
+ break;
+ case LEAF_wlanIfaceFastFrames:
+ op = IEEE80211_IOC_FF;
+ break;
+ case LEAF_wlanIfaceDturbo:
+ op = IEEE80211_IOC_TURBOP;
+ break;
+ case LEAF_wlanIfaceTxPower:
+ op = IEEE80211_IOC_TXPOWER;
+ break;
+ case LEAF_wlanIfaceFragmentThreshold:
+ op = IEEE80211_IOC_FRAGTHRESHOLD;
+ break;
+ case LEAF_wlanIfaceRTSThreshold:
+ op = IEEE80211_IOC_RTSTHRESHOLD;
+ break;
+ case LEAF_wlanIfaceWlanPrivacySubscribe:
+ op = IEEE80211_IOC_WPS;
+ break;
+ case LEAF_wlanIfaceBgScan:
+ op = IEEE80211_IOC_BGSCAN;
+ break;
+ case LEAF_wlanIfaceBgScanIdle:
+ op = IEEE80211_IOC_BGSCAN_IDLE;
+ break;
+ case LEAF_wlanIfaceBgScanInterval:
+ op = IEEE80211_IOC_BGSCAN_INTERVAL;
+ break;
+ case LEAF_wlanIfaceBeaconMissedThreshold:
+ op = IEEE80211_IOC_BMISSTHRESHOLD;
+ break;
+ case LEAF_wlanIfaceDesiredBssid:
+ op = IEEE80211_IOC_BSSID;
+ break;
+ case LEAF_wlanIfaceRoamingMode:
+ op = IEEE80211_IOC_ROAMING;
+ break;
+ case LEAF_wlanIfaceDot11d:
+ op = IEEE80211_IOC_DOTD;
+ break;
+ case LEAF_wlanIfaceDot11h:
+ op = IEEE80211_IOC_DOTH;
+ break;
+ case LEAF_wlanIfaceDynamicWds:
+ op = IEEE80211_IOC_DWDS;
+ break;
+ case LEAF_wlanIfacePowerSave:
+ op = IEEE80211_IOC_POWERSAVE;
+ break;
+ case LEAF_wlanIfaceApBridge:
+ op = IEEE80211_IOC_APBRIDGE;
+ break;
+ case LEAF_wlanIfaceBeaconInterval:
+ op = IEEE80211_IOC_BEACON_INTERVAL;
+ break;
+ case LEAF_wlanIfaceDtimPeriod:
+ op = IEEE80211_IOC_DTIM_PERIOD;
+ break;
+ case LEAF_wlanIfaceHideSsid:
+ op = IEEE80211_IOC_HIDESSID;
+ break;
+ case LEAF_wlanIfaceInactivityProccess:
+ op = IEEE80211_IOC_INACTIVITY;
+ break;
+ case LEAF_wlanIfaceDot11gProtMode:
+ op = IEEE80211_IOC_PROTMODE;
+ break;
+ case LEAF_wlanIfaceDot11gPureMode:
+ op = IEEE80211_IOC_PUREG;
+ break;
+ case LEAF_wlanIfaceDot11nPureMode:
+ op = IEEE80211_IOC_PUREN;
+ break;
+ case LEAF_wlanIfaceDot11nAmpdu:
+ op = IEEE80211_IOC_AMPDU;
+ break;
+ case LEAF_wlanIfaceDot11nAmpduDensity:
+ op = IEEE80211_IOC_AMPDU_DENSITY;
+ break;
+ case LEAF_wlanIfaceDot11nAmpduLimit:
+ op = IEEE80211_IOC_AMPDU_LIMIT;
+ break;
+ case LEAF_wlanIfaceDot11nAmsdu:
+ op = IEEE80211_IOC_AMSDU;
+ break;
+ case LEAF_wlanIfaceDot11nAmsduLimit:
+ op = IEEE80211_IOC_AMSDU_LIMIT;
+ break;
+ case LEAF_wlanIfaceDot11nHighThroughput:
+ op = IEEE80211_IOC_HTCONF;
+ break;
+ case LEAF_wlanIfaceDot11nHTCompatible:
+ op = IEEE80211_IOC_HTCOMPAT;
+ break;
+ case LEAF_wlanIfaceDot11nHTProtMode:
+ op = IEEE80211_IOC_HTPROTMODE;
+ break;
+ case LEAF_wlanIfaceDot11nRIFS:
+ op = IEEE80211_IOC_RIFS;
+ break;
+ case LEAF_wlanIfaceDot11nShortGI:
+ op = IEEE80211_IOC_SHORTGI;
+ break;
+ case LEAF_wlanIfaceDot11nSMPSMode:
+ op = IEEE80211_IOC_SMPS;
+ break;
+ case LEAF_wlanIfaceTdmaSlot:
+ op = IEEE80211_IOC_TDMA_SLOT;
+ break;
+ case LEAF_wlanIfaceTdmaSlotCount:
+ op = IEEE80211_IOC_TDMA_SLOTCNT;
+ break;
+ case LEAF_wlanIfaceTdmaSlotLength:
+ op = IEEE80211_IOC_TDMA_SLOTLEN;
+ break;
+ case LEAF_wlanIfaceTdmaBeaconInterval:
+ op = IEEE80211_IOC_TDMA_BINTERVAL;
+ break;
+ default:
+ op = -1;
+ }
+
+ return (op);
+}
+
+static enum WlanRegDomainCode
+wlan_regdomain_to_snmp(int which)
+{
+ enum WlanRegDomainCode reg_domain;
+
+ switch (which) {
+ case SKU_FCC:
+ reg_domain = WlanRegDomainCode_fcc;
+ break;
+ case SKU_CA:
+ reg_domain = WlanRegDomainCode_ca;
+ break;
+ case SKU_ETSI:
+ reg_domain = WlanRegDomainCode_etsi;
+ break;
+ case SKU_ETSI2:
+ reg_domain = WlanRegDomainCode_etsi2;
+ break;
+ case SKU_ETSI3:
+ reg_domain = WlanRegDomainCode_etsi3;
+ break;
+ case SKU_FCC3:
+ reg_domain = WlanRegDomainCode_fcc3;
+ break;
+ case SKU_JAPAN:
+ reg_domain = WlanRegDomainCode_japan;
+ break;
+ case SKU_KOREA:
+ reg_domain = WlanRegDomainCode_korea;
+ break;
+ case SKU_APAC:
+ reg_domain = WlanRegDomainCode_apac;
+ break;
+ case SKU_APAC2:
+ reg_domain = WlanRegDomainCode_apac2;
+ break;
+ case SKU_APAC3:
+ reg_domain = WlanRegDomainCode_apac3;
+ break;
+ case SKU_ROW:
+ reg_domain = WlanRegDomainCode_row;
+ break;
+ case SKU_NONE:
+ reg_domain = WlanRegDomainCode_none;
+ break;
+ case SKU_DEBUG:
+ reg_domain = WlanRegDomainCode_debug;
+ break;
+ case SKU_SR9:
+ reg_domain = WlanRegDomainCode_sr9;
+ break;
+ case SKU_XR9:
+ reg_domain = WlanRegDomainCode_xr9;
+ break;
+ case SKU_GZ901:
+ reg_domain = WlanRegDomainCode_gz901;
+ break;
+ case 0:
+ reg_domain = WlanRegDomainCode_none;
+ break;
+ default:
+ syslog(LOG_ERR, "unknown regdomain (0x%x) ", which);
+ reg_domain = WlanRegDomainCode_none;
+ break;
+ }
+
+ return (reg_domain);
+}
+
+static int
+wlan_snmp_to_regdomain(enum WlanRegDomainCode regdomain)
+{
+ int which;
+
+ switch (regdomain) {
+ case WlanRegDomainCode_fcc:
+ which = SKU_FCC;
+ break;
+ case WlanRegDomainCode_ca:
+ which = SKU_CA;
+ break;
+ case WlanRegDomainCode_etsi:
+ which = SKU_ETSI;
+ break;
+ case WlanRegDomainCode_etsi2:
+ which = SKU_ETSI2;
+ break;
+ case WlanRegDomainCode_etsi3:
+ which = SKU_ETSI3;
+ break;
+ case WlanRegDomainCode_fcc3:
+ which = SKU_FCC3;
+ break;
+ case WlanRegDomainCode_japan:
+ which = SKU_JAPAN;
+ break;
+ case WlanRegDomainCode_korea:
+ which = SKU_KOREA;
+ break;
+ case WlanRegDomainCode_apac:
+ which = SKU_APAC;
+ break;
+ case WlanRegDomainCode_apac2:
+ which = SKU_APAC2;
+ break;
+ case WlanRegDomainCode_apac3:
+ which = SKU_APAC3;
+ break;
+ case WlanRegDomainCode_row:
+ which = SKU_ROW;
+ break;
+ case WlanRegDomainCode_none:
+ which = SKU_NONE;
+ break;
+ case WlanRegDomainCode_debug:
+ which = SKU_DEBUG;
+ break;
+ case WlanRegDomainCode_sr9:
+ which = SKU_SR9;
+ break;
+ case WlanRegDomainCode_xr9:
+ which = SKU_XR9;
+ break;
+ case WlanRegDomainCode_gz901:
+ which = SKU_GZ901;
+ break;
+ default:
+ syslog(LOG_ERR, "unknown snmp regdomain (0x%x) ", regdomain);
+ which = SKU_NONE;
+ break;
+ }
+
+ return (which);
+}
+
+static int
+wlan_config_get_country(struct wlan_iface *wif)
+{
+ int val = 0;
+ size_t argsize;
+ struct ieee80211_regdomain regdomain;
+
+ memset(&regdomain, 0, sizeof(regdomain));
+ argsize = sizeof(regdomain);
+
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_REGDOMAIN, &val, &regdomain,
+ &argsize, 0) < 0)
+ return (-1);
+
+ wif->reg_domain = wlan_regdomain_to_snmp(regdomain.regdomain);
+ wif->country_code[0] = regdomain.isocc[0];
+ wif->country_code[1] = regdomain.isocc[1];
+ wif->country_code[2] = regdomain.location;
+
+ return (0);
+}
+
+static int
+wlan_config_set_country(struct wlan_iface *wif, char *ccode, int rdomain)
+{
+ int val = 0, txpowermax;
+ uint32_t i;
+ size_t argsize = 0;
+ struct ieee80211_regdomain_req *regdomain;
+
+ if (wlan_get_channel_list(wif) < 0)
+ return (-1);
+
+ if (wif->nchannels == 0) {
+ syslog(LOG_ERR, "iface %s - set regdomain failed", wif->wname);
+ return (-1);
+ }
+
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_TXPOWMAX, &txpowermax, 0,
+ &argsize, 0) < 0)
+ return (-1);
+
+ regdomain = malloc(IEEE80211_REGDOMAIN_SIZE(wif->nchannels));
+ if (regdomain == NULL)
+ return (-1);
+ memset(regdomain, 0, IEEE80211_REGDOMAIN_SIZE(wif->nchannels));
+ argsize = IEEE80211_REGDOMAIN_SIZE(wif->nchannels);
+
+ /* XXX: recheck with how this is done by ifconfig(8) */
+ regdomain->rd.regdomain = wlan_snmp_to_regdomain(rdomain);
+ regdomain->rd.isocc[0] = ccode[0];
+ regdomain->rd.isocc[1] = ccode[1];
+ regdomain->rd.location = ccode[2];
+
+ /* XXX: fill the channel list properly */
+ regdomain->chaninfo.ic_nchans = wif->nchannels;
+ memcpy(regdomain->chaninfo.ic_chans, wif->chanlist,
+ wif->nchannels * sizeof(struct ieee80211_channel));
+ for (i = 0; i < wif->nchannels; i++)
+ regdomain->chaninfo.ic_chans[i].ic_maxregpower = txpowermax;
+
+ wif->state = wlanIfaceState_down;
+ if (wlan_config_state(wif, 1) < 0 ||
+ wlan_ioctl(wif->wname, IEEE80211_IOC_REGDOMAIN, &val, regdomain,
+ &argsize, 1) < 0) {
+ free(regdomain);
+ return (-1);
+ }
+
+ wif->state = wlanIfaceState_up;
+ (void)wlan_config_state(wif, 1);
+ wif->reg_domain = wlan_regdomain_to_snmp(regdomain->rd.regdomain);
+ wif->country_code[0] = regdomain->rd.isocc[0];
+ wif->country_code[1] = regdomain->rd.isocc[1];
+ wif->country_code[2] = regdomain->rd.location;
+ free(regdomain);
+
+ return (0);
+}
+
+int
+wlan_config_get_dssid(struct wlan_iface *wif)
+{
+ int val = -1;
+ size_t argsize = IEEE80211_NWID_LEN + 1;
+ char ssid[IEEE80211_NWID_LEN + 1];
+
+ memset(ssid, 0, IEEE80211_NWID_LEN + 1);
+
+ if (wlan_ioctl(wif->wname,
+ (wif->mode == WlanIfaceOperatingModeType_meshPoint) ?
+ IEEE80211_IOC_MESH_ID : IEEE80211_IOC_SSID, &val, ssid,
+ &argsize, 0) < 0)
+ return (-1);
+
+ if (argsize > IEEE80211_NWID_LEN)
+ argsize = IEEE80211_NWID_LEN;
+ memcpy(wif->desired_ssid, ssid, argsize);
+ wif->desired_ssid[argsize] = '\0';
+
+ return (0);
+}
+
+int
+wlan_config_set_dssid(struct wlan_iface *wif, char *ssid, int slen)
+{
+ int val = 0;
+ size_t argsize = slen;
+
+ if (wlan_ioctl(wif->wname,
+ (wif->mode == WlanIfaceOperatingModeType_meshPoint) ?
+ IEEE80211_IOC_MESH_ID : IEEE80211_IOC_SSID, &val, ssid,
+ &argsize, 1) < 0)
+ return (-1);
+
+ if (argsize > IEEE80211_NWID_LEN)
+ argsize = IEEE80211_NWID_LEN;
+ memcpy(wif->desired_ssid, ssid, argsize);
+ wif->desired_ssid[argsize] = '\0';
+
+ return (0);
+}
+
+static int
+wlan_config_get_dchannel(struct wlan_iface *wif)
+{
+ uint32_t i = 0;
+ int val = 0;
+ size_t argsize = sizeof(struct ieee80211_channel);
+ struct ieee80211_channel chan;
+
+ if (wlan_get_channel_list(wif) < 0)
+ return (-1);
+
+ memset(&chan, 0, sizeof(chan));
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_CURCHAN, &val, &chan,
+ &argsize, 0) < 0)
+ return (-1);
+
+ for (i = 0; i < wif->nchannels; i++)
+ if (chan.ic_ieee == wif->chanlist[i].ic_ieee &&
+ chan.ic_flags == wif->chanlist[i].ic_flags) {
+ wif->desired_channel = i + 1;
+ break;
+ }
+
+ return (0);
+}
+
+static int
+wlan_config_set_dchannel(struct wlan_iface *wif, uint32_t dchannel)
+{
+ int val = 0;
+ size_t argsize = sizeof(struct ieee80211_channel);
+ struct ieee80211_channel chan;
+
+ if (wlan_get_channel_list(wif) < 0)
+ return (-1);
+
+ if (dchannel > wif->nchannels)
+ return (-1);
+
+ memcpy(&chan, wif->chanlist + dchannel - 1, sizeof(chan));
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_CURCHAN, &val, &chan,
+ &argsize, 1) < 0)
+ return (-1);
+
+ wif->desired_channel = dchannel;
+
+ return (0);
+}
+
+static int
+wlan_config_get_bssid(struct wlan_iface *wif)
+{
+ int val = 0;
+ size_t argsize = IEEE80211_ADDR_LEN;
+ char bssid[IEEE80211_ADDR_LEN];
+
+ memset(bssid, 0, IEEE80211_ADDR_LEN);
+
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_BSSID, &val, bssid,
+ &argsize, 0) < 0 || argsize != IEEE80211_ADDR_LEN)
+ return (-1);
+
+ memcpy(wif->desired_bssid, bssid, IEEE80211_ADDR_LEN);
+
+ return (0);
+}
+
+static int
+wlan_config_set_bssid(struct wlan_iface *wif, uint8_t *bssid)
+{
+ int val = 0;
+ size_t argsize = IEEE80211_ADDR_LEN;
+
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_BSSID, &val, bssid,
+ &argsize, 1) < 0 || argsize != IEEE80211_ADDR_LEN)
+ return (-1);
+
+ memcpy(wif->desired_bssid, bssid, IEEE80211_ADDR_LEN);
+
+ return (0);
+}
+
+/*
+ * Convert the value returned by the kernel to the appropriate SNMP
+ * representation and set the corresponding interface member accordingly.
+ */
+static void
+wlan_config_set_snmp_intval(struct wlan_iface *wif, int op, int val)
+{
+ switch (op) {
+ case IEEE80211_IOC_BURST:
+ if (val == 0)
+ wif->packet_burst = TruthValue_false;
+ else
+ wif->packet_burst = TruthValue_true;
+ break;
+ case IEEE80211_IOC_DFS:
+ if (val == 0)
+ wif->dyn_frequency = TruthValue_false;
+ else
+ wif->dyn_frequency = TruthValue_true;
+ break;
+ case IEEE80211_IOC_FF:
+ if (val == 0)
+ wif->fast_frames = TruthValue_false;
+ else
+ wif->fast_frames = TruthValue_true;
+ break;
+ case IEEE80211_IOC_TURBOP:
+ if (val == 0)
+ wif->dturbo = TruthValue_false;
+ else
+ wif->dturbo = TruthValue_true;
+ break;
+ case IEEE80211_IOC_TXPOWER:
+ wif->tx_power = val / 2;
+ break;
+ case IEEE80211_IOC_FRAGTHRESHOLD:
+ wif->frag_threshold = val;
+ break;
+ case IEEE80211_IOC_RTSTHRESHOLD:
+ wif->rts_threshold = val;
+ break;
+ case IEEE80211_IOC_WPS:
+ if (val == 0)
+ wif->priv_subscribe = TruthValue_false;
+ else
+ wif->priv_subscribe = TruthValue_true;
+ break;
+ case IEEE80211_IOC_BGSCAN:
+ if (val == 0)
+ wif->bg_scan = TruthValue_false;
+ else
+ wif->bg_scan = TruthValue_true;
+ break;
+ case IEEE80211_IOC_BGSCAN_IDLE:
+ wif->bg_scan_idle = val;
+ break;
+ case IEEE80211_IOC_BGSCAN_INTERVAL:
+ wif->bg_scan_interval = val;
+ break;
+ case IEEE80211_IOC_BMISSTHRESHOLD:
+ wif->beacons_missed = val;
+ break;
+ case IEEE80211_IOC_ROAMING:
+ switch (val) {
+ case IEEE80211_ROAMING_DEVICE:
+ wif->roam_mode = wlanIfaceRoamingMode_device;
+ break;
+ case IEEE80211_ROAMING_MANUAL:
+ wif->roam_mode = wlanIfaceRoamingMode_manual;
+ break;
+ case IEEE80211_ROAMING_AUTO:
+ /* FALTHROUGH */
+ default:
+ wif->roam_mode = wlanIfaceRoamingMode_auto;
+ break;
+ }
+ break;
+ case IEEE80211_IOC_DOTD:
+ if (val == 0)
+ wif->dot11d = TruthValue_false;
+ else
+ wif->dot11d = TruthValue_true;
+ break;
+ case IEEE80211_IOC_DOTH:
+ if (val == 0)
+ wif->dot11h = TruthValue_false;
+ else
+ wif->dot11h = TruthValue_true;
+ break;
+ case IEEE80211_IOC_DWDS:
+ if (val == 0)
+ wif->dynamic_wds = TruthValue_false;
+ else
+ wif->dynamic_wds = TruthValue_true;
+ break;
+ case IEEE80211_IOC_POWERSAVE:
+ if (val == 0)
+ wif->power_save = TruthValue_false;
+ else
+ wif->power_save = TruthValue_true;
+ break;
+ case IEEE80211_IOC_APBRIDGE:
+ if (val == 0)
+ wif->ap_bridge = TruthValue_false;
+ else
+ wif->ap_bridge = TruthValue_true;
+ break;
+ case IEEE80211_IOC_BEACON_INTERVAL:
+ wif->beacon_interval = val;
+ break;
+ case IEEE80211_IOC_DTIM_PERIOD:
+ wif->dtim_period = val;
+ break;
+ case IEEE80211_IOC_HIDESSID:
+ if (val == 0)
+ wif->hide_ssid = TruthValue_false;
+ else
+ wif->hide_ssid = TruthValue_true;
+ break;
+ case IEEE80211_IOC_INACTIVITY:
+ if (val == 0)
+ wif->inact_process = TruthValue_false;
+ else
+ wif->inact_process = TruthValue_true;
+ break;
+ case IEEE80211_IOC_PROTMODE:
+ switch (val) {
+ case IEEE80211_PROTMODE_CTS:
+ wif->do11g_protect = wlanIfaceDot11gProtMode_cts;
+ break;
+ case IEEE80211_PROTMODE_RTSCTS:
+ wif->do11g_protect = wlanIfaceDot11gProtMode_rtscts;
+ break;
+ case IEEE80211_PROTMODE_OFF:
+ /* FALLTHROUGH */
+ default:
+ wif->do11g_protect = wlanIfaceDot11gProtMode_off;
+ break;
+ }
+ break;
+ case IEEE80211_IOC_PUREG:
+ if (val == 0)
+ wif->dot11g_pure = TruthValue_false;
+ else
+ wif->dot11g_pure = TruthValue_true;
+ break;
+ case IEEE80211_IOC_PUREN:
+ if (val == 0)
+ wif->dot11n_pure = TruthValue_false;
+ else
+ wif->dot11n_pure = TruthValue_true;
+ break;
+ case IEEE80211_IOC_AMPDU:
+ switch (val) {
+ case 0:
+ wif->ampdu = WlanIfaceDot11nPduType_disabled;
+ break;
+ case 1:
+ wif->ampdu = WlanIfaceDot11nPduType_txOnly;
+ break;
+ case 2:
+ wif->ampdu = WlanIfaceDot11nPduType_rxOnly;
+ break;
+ case 3:
+ /* FALLTHROUGH */
+ default:
+ wif->ampdu = WlanIfaceDot11nPduType_txAndRx;
+ break;
+ }
+ break;
+ case IEEE80211_IOC_AMPDU_DENSITY:
+ switch (val) {
+ case IEEE80211_HTCAP_MPDUDENSITY_025:
+ wif->ampdu_density = 25;
+ break;
+ case IEEE80211_HTCAP_MPDUDENSITY_05:
+ wif->ampdu_density = 50;
+ break;
+ case IEEE80211_HTCAP_MPDUDENSITY_1:
+ wif->ampdu_density = 100;
+ break;
+ case IEEE80211_HTCAP_MPDUDENSITY_2:
+ wif->ampdu_density = 200;
+ break;
+ case IEEE80211_HTCAP_MPDUDENSITY_4:
+ wif->ampdu_density = 400;
+ break;
+ case IEEE80211_HTCAP_MPDUDENSITY_8:
+ wif->ampdu_density = 800;
+ break;
+ case IEEE80211_HTCAP_MPDUDENSITY_16:
+ wif->ampdu_density = 1600;
+ break;
+ case IEEE80211_HTCAP_MPDUDENSITY_NA:
+ default:
+ wif->ampdu_density = 0;
+ break;
+ }
+ break;
+ case IEEE80211_IOC_AMPDU_LIMIT:
+ switch (val) {
+ case IEEE80211_HTCAP_MAXRXAMPDU_8K:
+ wif->ampdu_limit = 8192;
+ break;
+ case IEEE80211_HTCAP_MAXRXAMPDU_16K:
+ wif->ampdu_limit = 16384;
+ break;
+ case IEEE80211_HTCAP_MAXRXAMPDU_32K:
+ wif->ampdu_limit = 32768;
+ break;
+ case IEEE80211_HTCAP_MAXRXAMPDU_64K:
+ default:
+ wif->ampdu_limit = 65536;
+ break;
+ }
+ break;
+ case IEEE80211_IOC_AMSDU:
+ switch (val) {
+ case 0:
+ wif->amsdu = WlanIfaceDot11nPduType_disabled;
+ break;
+ case 1:
+ wif->amsdu = WlanIfaceDot11nPduType_txOnly;
+ break;
+ case 3:
+ wif->amsdu = WlanIfaceDot11nPduType_txAndRx;
+ break;
+ case 2:
+ default:
+ /* FALLTHROUGH */
+ wif->amsdu = WlanIfaceDot11nPduType_rxOnly;
+ break;
+ }
+ break;
+ case IEEE80211_IOC_AMSDU_LIMIT:
+ wif->amsdu_limit = val;
+ break;
+ case IEEE80211_IOC_HTCONF:
+ if (val == 0) /* XXX */
+ wif->ht_enabled = TruthValue_false;
+ else
+ wif->ht_enabled = TruthValue_true;
+ break;
+ case IEEE80211_IOC_HTCOMPAT:
+ if (val == 0)
+ wif->ht_compatible = TruthValue_false;
+ else
+ wif->ht_compatible = TruthValue_true;
+ break;
+ case IEEE80211_IOC_HTPROTMODE:
+ if (val == IEEE80211_PROTMODE_RTSCTS)
+ wif->ht_prot_mode = wlanIfaceDot11nHTProtMode_rts;
+ else
+ wif->ht_prot_mode = wlanIfaceDot11nHTProtMode_off;
+ break;
+ case IEEE80211_IOC_RIFS:
+ if (val == 0)
+ wif->rifs = TruthValue_false;
+ else
+ wif->rifs = TruthValue_true;
+ break;
+ case IEEE80211_IOC_SHORTGI:
+ if (val == 0)
+ wif->short_gi = TruthValue_false;
+ else
+ wif->short_gi = TruthValue_true;
+ break;
+ case IEEE80211_IOC_SMPS:
+ switch (val) {
+ case IEEE80211_HTCAP_SMPS_DYNAMIC:
+ wif->smps_mode = wlanIfaceDot11nSMPSMode_dynamic;
+ break;
+ case IEEE80211_HTCAP_SMPS_ENA:
+ wif->smps_mode = wlanIfaceDot11nSMPSMode_static;
+ break;
+ case IEEE80211_HTCAP_SMPS_OFF:
+ /* FALLTHROUGH */
+ default:
+ wif->smps_mode = wlanIfaceDot11nSMPSMode_disabled;
+ break;
+ }
+ break;
+ case IEEE80211_IOC_TDMA_SLOT:
+ wif->tdma_slot = val;
+ break;
+ case IEEE80211_IOC_TDMA_SLOTCNT:
+ wif->tdma_slot_count = val;
+ break;
+ case IEEE80211_IOC_TDMA_SLOTLEN:
+ wif->tdma_slot_length = val;
+ break;
+ case IEEE80211_IOC_TDMA_BINTERVAL:
+ wif->tdma_binterval = val;
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * Convert an SNMP value to the kernel equivalent and also do sanity check
+ * for each specific type.
+ */
+static int
+wlan_config_snmp2value(int which, int sval, int *value)
+{
+ *value = 0;
+
+ switch (which) {
+ case IEEE80211_IOC_BURST:
+ case IEEE80211_IOC_DFS:
+ case IEEE80211_IOC_FF:
+ case IEEE80211_IOC_TURBOP:
+ case IEEE80211_IOC_WPS:
+ case IEEE80211_IOC_BGSCAN:
+ case IEEE80211_IOC_DOTD:
+ case IEEE80211_IOC_DOTH:
+ case IEEE80211_IOC_DWDS:
+ case IEEE80211_IOC_POWERSAVE:
+ case IEEE80211_IOC_APBRIDGE:
+ case IEEE80211_IOC_HIDESSID:
+ case IEEE80211_IOC_INACTIVITY:
+ case IEEE80211_IOC_PUREG:
+ case IEEE80211_IOC_PUREN:
+ case IEEE80211_IOC_HTCONF:
+ case IEEE80211_IOC_HTCOMPAT:
+ case IEEE80211_IOC_RIFS:
+ if (sval == TruthValue_true)
+ *value = 1;
+ else if (sval != TruthValue_false)
+ return (SNMP_ERR_INCONS_VALUE);
+ break;
+ case IEEE80211_IOC_REGDOMAIN:
+ break;
+ case IEEE80211_IOC_SSID:
+ break;
+ case IEEE80211_IOC_CURCHAN:
+ break;
+ case IEEE80211_IOC_TXPOWER:
+ *value = sval * 2;
+ break;
+ case IEEE80211_IOC_FRAGTHRESHOLD:
+ if (sval < IEEE80211_FRAG_MIN || sval > IEEE80211_FRAG_MAX)
+ return (SNMP_ERR_INCONS_VALUE);
+ *value = sval;
+ break;
+ case IEEE80211_IOC_RTSTHRESHOLD:
+ if (sval < IEEE80211_RTS_MIN || sval > IEEE80211_RTS_MAX)
+ return (SNMP_ERR_INCONS_VALUE);
+ *value = sval;
+ break;
+ case IEEE80211_IOC_BGSCAN_IDLE:
+ if (sval < WLAN_BGSCAN_IDLE_MIN)
+ return (SNMP_ERR_INCONS_VALUE);
+ *value = sval;
+ break;
+ case IEEE80211_IOC_BGSCAN_INTERVAL:
+ if (sval < WLAN_SCAN_VALID_MIN)
+ return (SNMP_ERR_INCONS_VALUE);
+ *value = sval;
+ break;
+ case IEEE80211_IOC_BMISSTHRESHOLD:
+ if (sval < IEEE80211_HWBMISS_MIN || sval > IEEE80211_HWBMISS_MAX)
+ return (SNMP_ERR_INCONS_VALUE);
+ *value = sval;
+ break;
+ case IEEE80211_IOC_BSSID:
+ break;
+ case IEEE80211_IOC_ROAMING:
+ switch (sval) {
+ case wlanIfaceRoamingMode_device:
+ *value = IEEE80211_ROAMING_DEVICE;
+ break;
+ case wlanIfaceRoamingMode_manual:
+ *value = IEEE80211_ROAMING_MANUAL;
+ break;
+ case wlanIfaceRoamingMode_auto:
+ *value = IEEE80211_ROAMING_AUTO;
+ break;
+ default:
+ return (SNMP_ERR_INCONS_VALUE);
+ }
+ break;
+ case IEEE80211_IOC_BEACON_INTERVAL:
+ if (sval < IEEE80211_BINTVAL_MIN || sval > IEEE80211_BINTVAL_MAX)
+ return (SNMP_ERR_INCONS_VALUE);
+ *value = sval;
+ break;
+ case IEEE80211_IOC_DTIM_PERIOD:
+ if (sval < IEEE80211_DTIM_MIN || sval > IEEE80211_DTIM_MAX)
+ return (SNMP_ERR_INCONS_VALUE);
+ *value = sval;
+ break;
+ case IEEE80211_IOC_PROTMODE:
+ switch (sval) {
+ case wlanIfaceDot11gProtMode_cts:
+ *value = IEEE80211_PROTMODE_CTS;
+ break;
+ case wlanIfaceDot11gProtMode_rtscts:
+ *value = IEEE80211_PROTMODE_RTSCTS;
+ break;
+ case wlanIfaceDot11gProtMode_off:
+ *value = IEEE80211_PROTMODE_OFF;
+ break;
+ default:
+ return (SNMP_ERR_INCONS_VALUE);
+ }
+ break;
+ case IEEE80211_IOC_AMPDU:
+ switch (sval) {
+ case WlanIfaceDot11nPduType_disabled:
+ break;
+ case WlanIfaceDot11nPduType_txOnly:
+ *value = 1;
+ break;
+ case WlanIfaceDot11nPduType_rxOnly:
+ *value = 2;
+ break;
+ case WlanIfaceDot11nPduType_txAndRx:
+ *value = 3;
+ break;
+ default:
+ return (SNMP_ERR_INCONS_VALUE);
+ }
+ break;
+ case IEEE80211_IOC_AMPDU_DENSITY:
+ switch (sval) {
+ case 0:
+ *value = IEEE80211_HTCAP_MPDUDENSITY_NA;
+ break;
+ case 25:
+ *value = IEEE80211_HTCAP_MPDUDENSITY_025;
+ break;
+ case 50:
+ *value = IEEE80211_HTCAP_MPDUDENSITY_05;
+ break;
+ case 100:
+ *value = IEEE80211_HTCAP_MPDUDENSITY_1;
+ break;
+ case 200:
+ *value = IEEE80211_HTCAP_MPDUDENSITY_2;
+ break;
+ case 400:
+ *value = IEEE80211_HTCAP_MPDUDENSITY_4;
+ break;
+ case 800:
+ *value = IEEE80211_HTCAP_MPDUDENSITY_8;
+ break;
+ case 1600:
+ *value = IEEE80211_HTCAP_MPDUDENSITY_16;
+ break;
+ default:
+ return (SNMP_ERR_INCONS_VALUE);
+ }
+ break;
+ case IEEE80211_IOC_AMPDU_LIMIT:
+ switch (sval) {
+ case 8192:
+ *value = IEEE80211_HTCAP_MAXRXAMPDU_8K;
+ break;
+ case 16384:
+ *value = IEEE80211_HTCAP_MAXRXAMPDU_16K;
+ break;
+ case 32768:
+ *value = IEEE80211_HTCAP_MAXRXAMPDU_32K;
+ break;
+ case 65536:
+ *value = IEEE80211_HTCAP_MAXRXAMPDU_64K;
+ break;
+ default:
+ return (SNMP_ERR_INCONS_VALUE);
+ }
+ break;
+ case IEEE80211_IOC_AMSDU:
+ switch (sval) {
+ case WlanIfaceDot11nPduType_disabled:
+ break;
+ case WlanIfaceDot11nPduType_txOnly:
+ *value = 1;
+ break;
+ case WlanIfaceDot11nPduType_rxOnly:
+ *value = 2;
+ break;
+ case WlanIfaceDot11nPduType_txAndRx:
+ *value = 3;
+ break;
+ default:
+ return (SNMP_ERR_INCONS_VALUE);
+ }
+ break;
+ case IEEE80211_IOC_AMSDU_LIMIT:
+ if (sval == 3839 || sval == 0)
+ *value = IEEE80211_HTCAP_MAXAMSDU_3839;
+ else if (sval == 7935)
+ *value = IEEE80211_HTCAP_MAXAMSDU_7935;
+ else
+ return (SNMP_ERR_INCONS_VALUE);
+ break;
+ case IEEE80211_IOC_HTPROTMODE:
+ switch (sval) {
+ case wlanIfaceDot11nHTProtMode_rts:
+ *value = IEEE80211_PROTMODE_RTSCTS;
+ break;
+ case wlanIfaceDot11nHTProtMode_off:
+ break;
+ default:
+ return (SNMP_ERR_INCONS_VALUE);
+ }
+ break;
+ case IEEE80211_IOC_SHORTGI:
+ if (sval == TruthValue_true)
+ *value = IEEE80211_HTCAP_SHORTGI20 |
+ IEEE80211_HTCAP_SHORTGI40;
+ else if (sval != TruthValue_false)
+ return (SNMP_ERR_INCONS_VALUE);
+ break;
+ case IEEE80211_IOC_SMPS:
+ switch (sval) {
+ case wlanIfaceDot11nSMPSMode_disabled:
+ *value = IEEE80211_HTCAP_SMPS_OFF;
+ break;
+ case wlanIfaceDot11nSMPSMode_static:
+ *value = IEEE80211_HTCAP_SMPS_ENA;
+ break;
+ case wlanIfaceDot11nSMPSMode_dynamic:
+ *value = IEEE80211_HTCAP_SMPS_DYNAMIC;
+ break;
+ default:
+ return (SNMP_ERR_INCONS_VALUE);
+ }
+ break;
+ case IEEE80211_IOC_TDMA_SLOT:
+ if (sval < 0 || sval > WLAN_TDMA_MAXSLOTS) /* XXX */
+ return (SNMP_ERR_INCONS_VALUE);
+ *value = sval;
+ break;
+ case IEEE80211_IOC_TDMA_SLOTCNT:
+ if (sval < 0 || sval > WLAN_TDMA_MAXSLOTS) /* XXX */
+ return (SNMP_ERR_INCONS_VALUE);
+ *value = sval;
+ break;
+ case IEEE80211_IOC_TDMA_SLOTLEN:
+ if (sval < 2*100 || sval > 0xfffff) /* XXX */
+ return (SNMP_ERR_INCONS_VALUE);
+ *value = sval;
+ break;
+ case IEEE80211_IOC_TDMA_BINTERVAL:
+ if (sval < 1) /* XXX */
+ return (SNMP_ERR_INCONS_VALUE);
+ *value = sval;
+ break;
+ default:
+ return (SNMP_ERR_INCONS_VALUE);
+ }
+
+ return (SNMP_ERR_NOERROR);
+}
+
+/*
+ * Sanity checks for the wlanIfaceConfigTable.
+ */
+static int
+wlan_config_check(struct wlan_iface *wif, int op)
+{
+ switch (op) {
+ case IEEE80211_IOC_BURST:
+ if ((wif->drivercaps & (0x1 << WlanDriverCaps_burst)) == 0) {
+ wif->packet_burst = TruthValue_false;
+ return (-1);
+ }
+ break;
+ case IEEE80211_IOC_DFS:
+ if ((wif->drivercaps & (0x1 << WlanDriverCaps_dfs)) == 0) {
+ wif->dyn_frequency = TruthValue_false;
+ return (-1);
+ }
+ break;
+ case IEEE80211_IOC_FF:
+ if ((wif->drivercaps & (0x1 << WlanDriverCaps_athFastFrames))
+ == 0) {
+ wif->fast_frames = TruthValue_false;
+ return (-1);
+ }
+ break;
+ case IEEE80211_IOC_TURBOP:
+ if ((wif->drivercaps & (0x1 << WlanDriverCaps_athTurbo)) == 0) {
+ wif->dturbo = TruthValue_false;
+ return (-1);
+ }
+ break;
+ case IEEE80211_IOC_TXPOWER:
+ if ((wif->drivercaps & (0x1 << WlanDriverCaps_txPmgt)) == 0) {
+ wif->tx_power = 0;
+ return (-1);
+ }
+ break;
+ case IEEE80211_IOC_FRAGTHRESHOLD:
+ if ((wif->drivercaps & (0x1 << WlanDriverCaps_txFrag)) == 0) {
+ wif->frag_threshold = IEEE80211_FRAG_MAX;
+ return (-1);
+ }
+ break;
+ case IEEE80211_IOC_DWDS:
+ if ((wif->drivercaps & (0x1 << WlanDriverCaps_wds)) == 0) {
+ wif->dynamic_wds = TruthValue_false;
+ return (-1);
+ }
+ break;
+ case IEEE80211_IOC_POWERSAVE:
+ if ((wif->drivercaps & (0x1 << WlanDriverCaps_pmgt)) == 0) {
+ wif->power_save = TruthValue_false;
+ return (-1);
+ }
+ break;
+ case IEEE80211_IOC_BEACON_INTERVAL:
+ if (wif->mode != WlanIfaceOperatingModeType_hostAp &&
+ wif->mode != WlanIfaceOperatingModeType_meshPoint &&
+ wif->mode != WlanIfaceOperatingModeType_ibss) {
+ wif->beacon_interval = 100; /* XXX */
+ return (-1);
+ }
+ break;
+ case IEEE80211_IOC_DTIM_PERIOD:
+ if (wif->mode != WlanIfaceOperatingModeType_hostAp &&
+ wif->mode != WlanIfaceOperatingModeType_meshPoint &&
+ wif->mode != WlanIfaceOperatingModeType_ibss) {
+ wif->dtim_period = 1; /* XXX */
+ return (-1);
+ }
+ break;
+ case IEEE80211_IOC_PUREN:
+ if ((wif->htcaps & (0x1 << WlanHTCaps_htcHt)) == 0) {
+ wif->dot11n_pure = TruthValue_false;
+ return (-1);
+ }
+ break;
+ case IEEE80211_IOC_AMPDU:
+ if ((wif->htcaps & (0x1 << WlanHTCaps_htcAmpdu)) == 0) {
+ wif->ampdu = WlanIfaceDot11nPduType_disabled;
+ return (-1);
+ }
+ break;
+ case IEEE80211_IOC_AMSDU:
+ if ((wif->htcaps & (0x1 << WlanHTCaps_htcAmsdu)) == 0) {
+ wif->amsdu = WlanIfaceDot11nPduType_disabled;
+ return (-1);
+ }
+ break;
+ case IEEE80211_IOC_RIFS:
+ if ((wif->htcaps & (0x1 << WlanHTCaps_htcRifs)) == 0) {
+ wif->rifs = TruthValue_false;
+ return (-1);
+ }
+ break;
+ case IEEE80211_IOC_SHORTGI:
+ if ((wif->htcaps & (0x1 << WlanHTCaps_shortGi20 |
+ 0x1 << WlanHTCaps_shortGi40)) == 0) {
+ wif->short_gi = TruthValue_false;
+ return (-1);
+ }
+ break;
+ case IEEE80211_IOC_SMPS:
+ if ((wif->htcaps & (0x1 << WlanHTCaps_htcSmps)) == 0) {
+ wif->smps_mode = wlanIfaceDot11nSMPSMode_disabled;
+ return (-1);
+ }
+ break;
+ case IEEE80211_IOC_TDMA_SLOT:
+ if ((wif->drivercaps & (0x1 << WlanDriverCaps_tdma)) == 0) {
+ wif->tdma_slot = 0;
+ return (-1);
+ }
+ break;
+ case IEEE80211_IOC_TDMA_SLOTCNT:
+ if ((wif->drivercaps & (0x1 << WlanDriverCaps_tdma)) == 0) {
+ wif->tdma_slot_count = 0;
+ return (-1);
+ }
+ break;
+ case IEEE80211_IOC_TDMA_SLOTLEN:
+ if ((wif->drivercaps & (0x1 << WlanDriverCaps_tdma)) == 0) {
+ wif->tdma_slot_length = 0;
+ return (-1);
+ }
+ break;
+ case IEEE80211_IOC_TDMA_BINTERVAL:
+ if ((wif->drivercaps & (0x1 << WlanDriverCaps_tdma)) == 0) {
+ wif->tdma_binterval = 0;
+ return (-1);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return (0);
+}
+
+static int
+wlan_config_get_intval(struct wlan_iface *wif, int op)
+{
+ int val = 0;
+ size_t argsize = 0;
+
+ if (wlan_config_check(wif, op) < 0)
+ return (0);
+ if (wlan_ioctl(wif->wname, op, &val, NULL, &argsize, 0) < 0)
+ return (-1);
+ wlan_config_set_snmp_intval(wif, op, val);
+
+ return (0);
+}
+
+static int
+wlan_config_set_intval(struct wlan_iface *wif, int op, int sval)
+{
+ size_t argsize = 0;
+ int val;
+
+ if (wlan_config_check(wif, op) < 0)
+ return (-1);
+ if (wlan_config_snmp2value(op, sval, &val) != SNMP_ERR_NOERROR)
+ return (-1);
+ if (wlan_ioctl(wif->wname, op, &val, NULL, &argsize, 1) < 0)
+ return (-1);
+ wlan_config_set_snmp_intval(wif, op, val);
+
+ return (0);
+}
+
+int
+wlan_config_get_ioctl(struct wlan_iface *wif, int which)
+{
+ int op;
+
+ switch (which) {
+ case LEAF_wlanIfaceCountryCode:
+ /* FALLTHROUGH */
+ case LEAF_wlanIfaceRegDomain:
+ return (wlan_config_get_country(wif));
+ case LEAF_wlanIfaceDesiredSsid:
+ return (wlan_config_get_dssid(wif));
+ case LEAF_wlanIfaceDesiredChannel:
+ return (wlan_config_get_dchannel(wif));
+ case LEAF_wlanIfaceDesiredBssid:
+ return (wlan_config_get_bssid(wif));
+ default:
+ op = wlan_config_snmp2ioctl(which);
+ return (wlan_config_get_intval(wif, op));
+ }
+
+ return (-1);
+}
+
+int
+wlan_config_set_ioctl(struct wlan_iface *wif, int which, int val,
+ char *strval, int len)
+{
+ int op;
+
+ switch (which) {
+ case LEAF_wlanIfaceCountryCode:
+ return (wlan_config_set_country(wif, strval,
+ wif->reg_domain));
+ case LEAF_wlanIfaceRegDomain:
+ return (wlan_config_set_country(wif, wif->country_code,
+ val));
+ case LEAF_wlanIfaceDesiredSsid:
+ return (wlan_config_set_dssid(wif, strval, len));
+ case LEAF_wlanIfaceDesiredChannel:
+ return (wlan_config_set_dchannel(wif, val));
+ case LEAF_wlanIfaceDesiredBssid:
+ return (wlan_config_set_bssid(wif, strval));
+ default:
+ op = wlan_config_snmp2ioctl(which);
+ return (wlan_config_set_intval(wif, op, val));
+ }
+
+ return (-1);
+}
+
+static uint32_t
+wlan_snmp_to_scan_flags(int flags)
+{
+ int sr_flags = 0;
+
+ if ((flags & (0x1 << WlanScanFlagsType_noSelection)) != 0)
+ sr_flags |= IEEE80211_IOC_SCAN_NOPICK;
+ if ((flags & (0x1 << WlanScanFlagsType_activeScan)) != 0)
+ sr_flags |= IEEE80211_IOC_SCAN_ACTIVE;
+ if ((flags & (0x1 << WlanScanFlagsType_pickFirst)) != 0)
+ sr_flags |= IEEE80211_IOC_SCAN_PICK1ST;
+ if ((flags & (0x1 << WlanScanFlagsType_backgroundScan)) != 0)
+ sr_flags |= IEEE80211_IOC_SCAN_BGSCAN;
+ if ((flags & (0x1 << WlanScanFlagsType_once)) != 0)
+ sr_flags |= IEEE80211_IOC_SCAN_ONCE;
+ if ((flags & (0x1 << WlanScanFlagsType_noBroadcast)) != 0)
+ sr_flags |= IEEE80211_IOC_SCAN_NOBCAST;
+ if ((flags & (0x1 << WlanScanFlagsType_noAutoSequencing)) != 0)
+ sr_flags |= IEEE80211_IOC_SCAN_NOJOIN;
+ if ((flags & (0x1 << WlanScanFlagsType_flushCashe)) != 0)
+ sr_flags |= IEEE80211_IOC_SCAN_FLUSH;
+ if ((flags & (0x1 << WlanScanFlagsType_chechCashe)) != 0)
+ sr_flags |= IEEE80211_IOC_SCAN_CHECK;
+
+ return (sr_flags);
+}
+
+int
+wlan_set_scan_config(struct wlan_iface *wif)
+{
+ int val = 0;
+ size_t argsize;
+ struct ieee80211_scan_req sr;
+
+
+ memset(&sr, 0, sizeof(sr));
+ argsize = sizeof(struct ieee80211_scan_req);
+ sr.sr_flags = wlan_snmp_to_scan_flags(wif->scan_flags);
+ sr.sr_flags |= IEEE80211_IOC_SCAN_BGSCAN;
+ sr.sr_duration = wif->scan_duration;
+ sr.sr_mindwell = wif->scan_mindwell;
+ sr.sr_maxdwell = wif->scan_maxdwell;
+ sr.sr_nssid = 0;
+
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_SCAN_REQ,
+ &val, &sr, &argsize, 1) < 0)
+ return (-1);
+
+ wif->scan_status = wlanScanConfigStatus_running;
+ return (0);
+}
+
+static uint32_t
+wlan_peercaps_to_snmp(uint32_t pcaps)
+{
+ uint32_t scaps = 0;
+
+ if ((pcaps & IEEE80211_CAPINFO_ESS) != 0)
+ scaps |= (0x1 << WlanPeerCapabilityFlags_ess);
+ if ((pcaps & IEEE80211_CAPINFO_IBSS) != 0)
+ scaps |= (0x1 << WlanPeerCapabilityFlags_ibss);
+ if ((pcaps & IEEE80211_CAPINFO_CF_POLLABLE) != 0)
+ scaps |= (0x1 << WlanPeerCapabilityFlags_cfPollable);
+ if ((pcaps & IEEE80211_CAPINFO_CF_POLLREQ) != 0)
+ scaps |= (0x1 << WlanPeerCapabilityFlags_cfPollRequest);
+ if ((pcaps & IEEE80211_CAPINFO_PRIVACY) != 0)
+ scaps |= (0x1 << WlanPeerCapabilityFlags_privacy);
+ if ((pcaps & IEEE80211_CAPINFO_SHORT_PREAMBLE) != 0)
+ scaps |= (0x1 << WlanPeerCapabilityFlags_shortPreamble);
+ if ((pcaps & IEEE80211_CAPINFO_PBCC) != 0)
+ scaps |= (0x1 << WlanPeerCapabilityFlags_pbcc);
+ if ((pcaps & IEEE80211_CAPINFO_CHNL_AGILITY) != 0)
+ scaps |= (0x1 << WlanPeerCapabilityFlags_channelAgility);
+ if ((pcaps & IEEE80211_CAPINFO_SHORT_SLOTTIME) != 0)
+ scaps |= (0x1 << WlanPeerCapabilityFlags_shortSlotTime);
+ if ((pcaps & IEEE80211_CAPINFO_RSN) != 0)
+ scaps |= (0x1 << WlanPeerCapabilityFlags_rsn);
+ if ((pcaps & IEEE80211_CAPINFO_DSSSOFDM) != 0)
+ scaps |= (0x1 << WlanPeerCapabilityFlags_dsssofdm);
+
+ return (scaps);
+}
+
+static int
+wlan_add_new_scan_result(struct wlan_iface *wif,
+ const struct ieee80211req_scan_result *isr, uint8_t *ssid)
+{
+ struct wlan_scan_result *sr;
+
+ if ((sr = wlan_scan_new_result(ssid, isr->isr_bssid)) == NULL)
+ return (-1);
+
+ sr->opchannel = wlan_channel_flags_to_snmp_phy(isr->isr_flags);
+ sr->rssi = isr->isr_rssi;
+ sr->frequency = isr->isr_freq;
+ sr->noise = isr->isr_noise;
+ sr->bintval = isr->isr_intval;
+ sr->capinfo = wlan_peercaps_to_snmp(isr->isr_capinfo);
+
+ if (wlan_scan_add_result(wif, sr) < 0) {
+ wlan_scan_free_result(sr);
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+wlan_get_scan_results(struct wlan_iface *wif)
+{
+ int ssidlen, val = 0;
+ uint8_t buf[24 * 1024];
+ size_t argsize;
+ const uint8_t *cp, *idp;
+ uint8_t ssid[IEEE80211_NWID_LEN + 1];
+ struct ieee80211req_scan_result isr;
+
+ argsize = sizeof(buf);
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_SCAN_RESULTS, &val, &buf,
+ &argsize, 0) < 0)
+ return (-1);
+
+ if (argsize < sizeof(struct ieee80211req_scan_result))
+ return (0);
+
+ cp = buf;
+ do {
+ memcpy(&isr, cp, sizeof(struct ieee80211req_scan_result));
+ memset(ssid, 0, IEEE80211_NWID_LEN + 1);
+
+ if (isr.isr_meshid_len) {
+ idp = cp + isr.isr_ie_off + isr.isr_ssid_len;
+ ssidlen = isr.isr_meshid_len;
+ } else {
+ idp = cp + isr.isr_ie_off;
+ ssidlen = isr.isr_ssid_len;
+ }
+ if (ssidlen > IEEE80211_NWID_LEN)
+ ssidlen = IEEE80211_NWID_LEN;
+ memcpy(ssid, idp, ssidlen);
+ ssid[IEEE80211_NWID_LEN] = '\0';
+ (void)wlan_add_new_scan_result(wif, &isr, ssid);
+ cp += isr.isr_len;
+ argsize -= isr.isr_len;
+ } while (argsize >= sizeof(struct ieee80211req_scan_result));
+
+ return (0);
+}
+
+int
+wlan_get_stats(struct wlan_iface *wif)
+{
+ struct ifreq ifr;
+
+ memset(&ifr, 0, sizeof(struct ifreq));
+ strlcpy(ifr.ifr_name, wif->wname, IFNAMSIZ);
+
+ ifr.ifr_data = (caddr_t) &wif->stats;
+
+ if (ioctl(sock, SIOCG80211STATS, &ifr) < 0) {
+ syslog(LOG_ERR, "iface %s - ioctl(SIOCG80211STATS) failed: %s",
+ wif->wname, strerror(errno));
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+wlan_get_wepmode(struct wlan_iface *wif)
+{
+ int val = 0;
+ size_t argsize = 0;
+
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_WEP, &val, NULL,
+ &argsize, 0) < 0 || val == IEEE80211_WEP_NOSUP) {
+ wif->wepsupported = 0; /* XXX */
+ wif->wepmode = wlanWepMode_off;
+ wif->weptxkey = 0;
+ return (-1);
+ }
+
+ wif->wepsupported = 1;
+
+ switch (val) {
+ case IEEE80211_WEP_ON:
+ wif->wepmode = wlanWepMode_on;
+ break;
+ case IEEE80211_WEP_MIXED:
+ wif->wepmode = wlanWepMode_mixed;
+ break;
+ case IEEE80211_WEP_OFF:
+ /* FALLTHROUGH */
+ default:
+ wif->wepmode = wlanWepMode_off;
+ break;
+ }
+
+ return (0);
+}
+
+int
+wlan_set_wepmode(struct wlan_iface *wif)
+{
+ int val;
+ size_t argsize = 0;
+
+ if (!wif->wepsupported)
+ return (-1);
+
+ switch (wif->wepmode) {
+ case wlanWepMode_off:
+ val = IEEE80211_WEP_OFF;
+ break;
+ case wlanWepMode_on:
+ val = IEEE80211_WEP_ON;
+ break;
+ case wlanWepMode_mixed:
+ val = IEEE80211_WEP_MIXED;
+ break;
+ default:
+ return (-1);
+ }
+
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_WEP, &val, NULL,
+ &argsize, 1) < 0)
+ return (-1);
+
+ return (0);
+}
+
+int
+wlan_get_weptxkey(struct wlan_iface *wif)
+{
+ int val;
+ size_t argsize = 0;
+
+ if (!wif->wepsupported)
+ return (0);
+
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_WEPTXKEY, &val, NULL,
+ &argsize, 0) < 0)
+ return (-1);
+
+ if (val == IEEE80211_KEYIX_NONE)
+ wif->weptxkey = 0;
+ else
+ wif->weptxkey = val + 1;
+
+ return (0);
+}
+
+int
+wlan_set_weptxkey(struct wlan_iface *wif)
+{
+ int val;
+ size_t argsize = 0;
+
+ if (!wif->wepsupported)
+ return (0);
+
+ if (wif->weptxkey >= IEEE80211_WEP_NKID)
+ return (-1);
+
+ if (wif->weptxkey == 0)
+ val = IEEE80211_KEYIX_NONE;
+ else
+ val = wif->weptxkey - 1;
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_WEPTXKEY, &val, NULL,
+ &argsize, 1) < 0)
+ return (-1);
+
+ return (0);
+}
+
+int
+wlan_get_wepkeys(struct wlan_iface *wif __unused)
+{
+ /* XXX: should they be visible via SNMP */
+ return (0);
+}
+
+int
+wlan_set_wepkeys(struct wlan_iface *wif __unused)
+{
+ /* XXX: should they be configurable via SNMP */
+ return (0);
+}
+
+int
+wlan_get_mac_policy(struct wlan_iface *wif)
+{
+ int val = IEEE80211_MACCMD_POLICY;
+ size_t argsize = 0;
+ struct ieee80211req ireq;
+
+ memset(&ireq, 0, sizeof(struct ieee80211req));
+ strlcpy(ireq.i_name, wif->wname, IFNAMSIZ);
+ ireq.i_type = IEEE80211_IOC_MACCMD;
+ ireq.i_val = IEEE80211_MACCMD_POLICY;
+
+ if (ioctl(sock, SIOCG80211, &ireq) < 0) {
+ if (errno != EINVAL) {
+ syslog(LOG_ERR, "iface %s - get param: ioctl(%d) "
+ "failed: %s", wif->wname, ireq.i_type,
+ strerror(errno));
+ wif->macsupported = 0;
+ return (-1);
+ } else {
+ wif->macsupported = 1;
+ wif->mac_policy = wlanMACAccessControlPolicy_open;
+ return (0);
+ }
+
+ }
+
+ wif->macsupported = 1;
+
+ switch (val) {
+ case IEEE80211_MACCMD_POLICY_ALLOW:
+ wif->mac_policy = wlanMACAccessControlPolicy_allow;
+ break;
+ case IEEE80211_MACCMD_POLICY_DENY:
+ wif->mac_policy = wlanMACAccessControlPolicy_deny;
+ break;
+ case IEEE80211_MACCMD_POLICY_RADIUS:
+ wif->mac_policy = wlanMACAccessControlPolicy_radius;
+ break;
+ case IEEE80211_MACCMD_POLICY_OPEN:
+ /* FALLTHROUGH */
+ default:
+ wif->mac_policy = wlanMACAccessControlPolicy_open;
+ break;
+ }
+
+ argsize = 0;
+ val = IEEE80211_MACCMD_LIST;
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_MACCMD, &val, NULL,
+ &argsize, 0) < 0)
+ return (-1);
+
+ wif->mac_nacls = argsize / sizeof(struct ieee80211req_maclist *);
+ return (0);
+}
+
+int
+wlan_set_mac_policy(struct wlan_iface *wif)
+{
+ int val;
+ size_t argsize = 0;
+
+ if (!wif->macsupported)
+ return (-1);
+
+ switch (wif->mac_policy) {
+ case wlanMACAccessControlPolicy_allow:
+ val = IEEE80211_MACCMD_POLICY_ALLOW;
+ break;
+ case wlanMACAccessControlPolicy_deny:
+ val = IEEE80211_MACCMD_POLICY_DENY;
+ break;
+ case wlanMACAccessControlPolicy_radius:
+ val = IEEE80211_MACCMD_POLICY_RADIUS;
+ break;
+ case wlanMACAccessControlPolicy_open:
+ val = IEEE80211_MACCMD_POLICY_OPEN;
+ break;
+ default:
+ return (-1);
+ }
+
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_MACCMD, &val, NULL,
+ &argsize, 1) < 0)
+ return (-1);
+
+ return (0);
+}
+
+int
+wlan_flush_mac_mac(struct wlan_iface *wif)
+{
+ int val = IEEE80211_MACCMD_FLUSH;
+ size_t argsize = 0;
+
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_MACCMD, &val, NULL,
+ &argsize, 1) < 0)
+ return (-1);
+
+ return (0);
+}
+
+static int
+wlan_add_mac_macinfo(struct wlan_iface *wif,
+ const struct ieee80211req_maclist *ml)
+{
+ struct wlan_mac_mac *mmac;
+
+ if ((mmac = wlan_mac_new_mac(ml->ml_macaddr)) == NULL)
+ return (-1);
+
+ mmac->mac_status = RowStatus_active;
+ if (wlan_mac_add_mac(wif, mmac) < 0) {
+ wlan_mac_free_mac(mmac);
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+wlan_get_mac_acl_macs(struct wlan_iface *wif)
+{
+ int i, nacls, val = IEEE80211_MACCMD_LIST;
+ size_t argsize = 0;
+ uint8_t *data;
+ struct ieee80211req ireq;
+ const struct ieee80211req_maclist *acllist;
+
+ if (wif->mac_policy == wlanMACAccessControlPolicy_radius) {
+ wif->mac_nacls = 0;
+ return (0);
+ }
+
+ memset(&ireq, 0, sizeof(struct ieee80211req));
+ strlcpy(ireq.i_name, wif->wname, IFNAMSIZ);
+ ireq.i_type = IEEE80211_IOC_MACCMD;
+ ireq.i_val = IEEE80211_MACCMD_LIST;
+
+
+ if (ioctl(sock, SIOCG80211, &ireq) < 0) {
+ if (errno != EINVAL) {
+ syslog(LOG_ERR, "iface %s - get param: ioctl(%d) "
+ "failed: %s", wif->wname, ireq.i_type,
+ strerror(errno));
+ wif->macsupported = 0;
+ return (-1);
+ }
+ }
+
+ if (argsize == 0) {
+ wif->mac_nacls = 0;
+ return (0);
+ }
+
+ if ((data = (uint8_t *)malloc(argsize)) == NULL)
+ return (-1);
+
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_MACCMD, &val, data,
+ &argsize, 0) < 0)
+ return (-1);
+
+ nacls = argsize / sizeof(*acllist);
+ acllist = (struct ieee80211req_maclist *) data;
+ for (i = 0; i < nacls; i++)
+ (void)wlan_add_mac_macinfo(wif, acllist + i);
+
+ wif->mac_nacls = nacls;
+ return (0);
+}
+
+int
+wlan_add_mac_acl_mac(struct wlan_iface *wif, struct wlan_mac_mac *mmac)
+{
+ int val = 0;
+ size_t argsize = IEEE80211_ADDR_LEN;
+ struct ieee80211req_mlme mlme;
+
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_ADDMAC, &val,
+ mmac->mac, &argsize, 1) < 0)
+ return (-1);
+
+ mmac->mac_status = RowStatus_active;
+
+ /* If policy is deny, try to kick the station just in case. */
+ if (wif->mac_policy != wlanMACAccessControlPolicy_deny)
+ return (0);
+
+ memset(&mlme, 0, sizeof(mlme));
+ mlme.im_op = IEEE80211_MLME_DEAUTH;
+ mlme.im_reason = IEEE80211_REASON_AUTH_EXPIRE;
+ memcpy(mlme.im_macaddr, mmac->mac, IEEE80211_ADDR_LEN);
+ argsize = sizeof(struct ieee80211req_mlme);
+
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_MLME, &val, &mlme,
+ &argsize, 1) < 0 && errno != ENOENT)
+ return (-1);
+
+ return (0);
+}
+
+int
+wlan_del_mac_acl_mac(struct wlan_iface *wif, struct wlan_mac_mac *mmac)
+{
+ int val = 0;
+ size_t argsize = IEEE80211_ADDR_LEN;
+ struct ieee80211req_mlme mlme;
+
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_DELMAC, &val,
+ mmac->mac, &argsize, 1) < 0)
+ return (-1);
+
+ mmac->mac_status = RowStatus_active;
+
+ /* If policy is allow, try to kick the station just in case. */
+ if (wif->mac_policy != wlanMACAccessControlPolicy_allow)
+ return (0);
+
+ memset(&mlme, 0, sizeof(mlme));
+ mlme.im_op = IEEE80211_MLME_DEAUTH;
+ mlme.im_reason = IEEE80211_REASON_AUTH_EXPIRE;
+ memcpy(mlme.im_macaddr, mmac->mac, IEEE80211_ADDR_LEN);
+ argsize = sizeof(struct ieee80211req_mlme);
+
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_MLME, &val, &mlme,
+ &argsize, 1) < 0 && errno != ENOENT)
+ return (-1);
+
+ return (0);
+}
+
+int
+wlan_peer_set_vlan(struct wlan_iface *wif, struct wlan_peer *wip, int vlan)
+{
+ int val = 0;
+ size_t argsize;
+ struct ieee80211req_sta_vlan vreq;
+
+ memcpy(vreq.sv_macaddr, wip->pmac, IEEE80211_ADDR_LEN);
+ vreq.sv_vlan = vlan;
+ argsize = sizeof(struct ieee80211req_sta_vlan);
+
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_STA_VLAN,
+ &val, &vreq, &argsize, 1) < 0)
+ return (-1);
+
+ wip->vlan = vlan;
+
+ return (0);
+}
+
+/* XXX */
+#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
+
+static uint32_t
+wlan_peerstate_to_snmp(uint32_t pstate)
+{
+ uint32_t sstate = 0;
+
+ if ((pstate & IEEE80211_NODE_AUTH) != 0)
+ sstate |= (0x1 << WlanIfacePeerFlagsType_authorizedForData);
+ if ((pstate & IEEE80211_NODE_QOS) != 0)
+ sstate |= (0x1 << WlanIfacePeerFlagsType_qosEnabled);
+ if ((pstate & IEEE80211_NODE_ERP) != 0)
+ sstate |= (0x1 << WlanIfacePeerFlagsType_erpEnabled);
+ if ((pstate & IEEE80211_NODE_PWR_MGT) != 0)
+ sstate |= (0x1 << WlanIfacePeerFlagsType_powerSaveMode);
+ if ((pstate & IEEE80211_NODE_AREF) != 0)
+ sstate |= (0x1 << WlanIfacePeerFlagsType_authRefHeld);
+ if ((pstate & IEEE80211_NODE_HT) != 0)
+ sstate |= (0x1 << WlanIfacePeerFlagsType_htEnabled);
+ if ((pstate & IEEE80211_NODE_HTCOMPAT) != 0)
+ sstate |= (0x1 << WlanIfacePeerFlagsType_htCompat);
+ if ((pstate & IEEE80211_NODE_WPS) != 0)
+ sstate |= (0x1 << WlanIfacePeerFlagsType_wpsAssoc);
+ if ((pstate & IEEE80211_NODE_TSN) != 0)
+ sstate |= (0x1 << WlanIfacePeerFlagsType_tsnAssoc);
+ if ((pstate & IEEE80211_NODE_AMPDU_RX) != 0)
+ sstate |= (0x1 << WlanIfacePeerFlagsType_ampduRx);
+ if ((pstate & IEEE80211_NODE_AMPDU_TX) != 0)
+ sstate |= (0x1 << WlanIfacePeerFlagsType_ampduTx);
+ if ((pstate & IEEE80211_NODE_MIMO_PS) != 0)
+ sstate |= (0x1 << WlanIfacePeerFlagsType_mimoPowerSave);
+ if ((pstate & IEEE80211_NODE_MIMO_RTS) != 0)
+ sstate |= (0x1 << WlanIfacePeerFlagsType_sendRts);
+ if ((pstate & IEEE80211_NODE_RIFS) != 0)
+ sstate |= (0x1 << WlanIfacePeerFlagsType_rifs);
+ if ((pstate & IEEE80211_NODE_SGI20) != 0)
+ sstate |= (0x1 << WlanIfacePeerFlagsType_shortGiHT20);
+ if ((pstate & IEEE80211_NODE_SGI40) != 0)
+ sstate |= (0x1 << WlanIfacePeerFlagsType_shortGiHT40);
+ if ((pstate & IEEE80211_NODE_AMSDU_RX) != 0)
+ sstate |= (0x1 << WlanIfacePeerFlagsType_amsduRx);
+ if ((pstate & IEEE80211_NODE_AMSDU_TX) != 0)
+ sstate |= (0x1 << WlanIfacePeerFlagsType_amsduTx);
+
+ return (sstate);
+}
+
+static struct wlan_peer *
+wlan_add_peerinfo(const struct ieee80211req_sta_info *si)
+{
+ struct wlan_peer *wip;
+
+ if ((wip = wlan_new_peer(si->isi_macaddr))== NULL)
+ return (NULL);
+
+ wip->associd = IEEE80211_AID(si->isi_associd);
+ wip->vlan = si->isi_vlan;
+ wip->frequency = si->isi_freq;
+ wip->fflags = si->isi_flags;
+ wip->txrate = si->isi_txrate;
+ wip->rssi = si->isi_rssi;
+ wip->idle = si->isi_inact;
+ wip->txseqs = si->isi_txseqs[0]; /* XXX */
+ wip->rxseqs = si->isi_rxseqs[0]; /* XXX */
+ wip->txpower = si->isi_txpower;
+ wip->capinfo = wlan_peercaps_to_snmp(si->isi_capinfo);
+ wip->state = wlan_peerstate_to_snmp(si->isi_state);
+ wip->local_id = si->isi_localid;
+ wip->peer_id = si->isi_peerid;
+
+ return (wip);
+}
+
+int
+wlan_get_peerinfo(struct wlan_iface *wif)
+{
+ union {
+ struct ieee80211req_sta_req req;
+ uint8_t buf[24 * 1024];
+ } u;
+ const uint8_t *cp;
+ int val = 0;
+ size_t len;
+ struct ieee80211req_sta_info si;
+ struct wlan_peer *wip;
+
+ /* Get all stations - broadcast address */
+ (void) memset(u.req.is_u.macaddr, 0xff, IEEE80211_ADDR_LEN);
+ len = sizeof(u);
+
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_STA_INFO,
+ & val, &u, &len, 0) < 0)
+ return (-1);
+
+ if (len < sizeof(struct ieee80211req_sta_info))
+ return (-1);
+
+ cp = (const uint8_t *) u.req.info;
+ do {
+ memcpy(&si, cp, sizeof(struct ieee80211req_sta_info));
+ if ((wip = wlan_add_peerinfo(&si)) != NULL &&
+ wlan_add_peer(wif, wip) < 0)
+ wlan_free_peer(wip);
+ cp += si.isi_len, len -= si.isi_len;
+ } while (len >= sizeof(struct ieee80211req_sta_info));
+
+ return (0);
+}
+
+/************************************************************************
+ * Wireless MESH & HWMP sysctl config.
+ */
+const char wlan_sysctl_name[] = "net.wlan.";
+
+static const char *wlan_sysctl[] = {
+ "mesh.retrytimeout",
+ "mesh.holdingtimeout",
+ "mesh.confirmtimeout",
+ "mesh.maxretries",
+ "hwmp.targetonly",
+ "hwmp.replyforward",
+ "hwmp.pathlifetime",
+ "hwmp.roottimeout",
+ "hwmp.rootint",
+ "hwmp.rannint",
+ "hwmp.inact",
+};
+
+int32_t
+wlan_do_sysctl(struct wlan_config *cfg, enum wlan_syscl which, int set)
+{
+ char mib_name[100];
+ int val, sval;
+ size_t len, vlen;
+
+ if (set) {
+ vlen = sizeof(sval);
+ switch (which) {
+ case WLAN_MESH_RETRY_TO:
+ sval = cfg->mesh_retryto;
+ break;
+ case WLAN_MESH_HOLDING_TO:
+ sval = cfg->mesh_holdingto;
+ break;
+ case WLAN_MESH_CONFIRM_TO:
+ sval = cfg->mesh_confirmto;
+ break;
+ case WLAN_MESH_MAX_RETRIES:
+ sval = cfg->mesh_maxretries;
+ break;
+ case WLAN_HWMP_TARGET_ONLY:
+ sval = cfg->hwmp_targetonly;
+ break;
+ case WLAN_HWMP_REPLY_FORWARD:
+ sval = cfg->hwmp_replyforward;
+ break;
+ case WLAN_HWMP_PATH_LIFETIME:
+ sval = cfg->hwmp_pathlifetime;
+ break;
+ case WLAN_HWMP_ROOT_TO:
+ sval = cfg->hwmp_roottimeout;
+ break;
+ case WLAN_HWMP_ROOT_INT:
+ sval = cfg->hwmp_rootint;
+ break;
+ case WLAN_HWMP_RANN_INT:
+ sval = cfg->hwmp_rannint;
+ break;
+ case WLAN_HWMP_INACTIVITY_TO:
+ sval = cfg->hwmp_inact;
+ break;
+ default:
+ return (-1);
+ }
+ } else {
+ if (which >= WLAN_SYSCTL_MAX)
+ return (-1);
+ vlen = 0;
+ }
+
+ strlcpy(mib_name, wlan_sysctl_name, sizeof(mib_name));
+ strlcat(mib_name, wlan_sysctl[which], sizeof(mib_name));
+ len = sizeof (val);
+
+ if (sysctlbyname(mib_name, &val, &len, (set? &sval : NULL), vlen) < 0) {
+ syslog(LOG_ERR, "sysctl(%s) failed - %s", mib_name,
+ strerror(errno));
+ return (-1);
+ }
+
+ switch (which) {
+ case WLAN_MESH_RETRY_TO:
+ cfg->mesh_retryto = val;
+ break;
+ case WLAN_MESH_HOLDING_TO:
+ cfg->mesh_holdingto = val;
+ break;
+ case WLAN_MESH_CONFIRM_TO:
+ cfg->mesh_confirmto = val;
+ break;
+ case WLAN_MESH_MAX_RETRIES:
+ cfg->mesh_maxretries = val;
+ break;
+ case WLAN_HWMP_TARGET_ONLY:
+ cfg->hwmp_targetonly = val;
+ break;
+ case WLAN_HWMP_REPLY_FORWARD:
+ cfg->hwmp_replyforward = val;
+ break;
+ case WLAN_HWMP_PATH_LIFETIME:
+ cfg->hwmp_pathlifetime = val;
+ break;
+ case WLAN_HWMP_ROOT_TO:
+ cfg->hwmp_roottimeout = val;
+ break;
+ case WLAN_HWMP_ROOT_INT:
+ cfg->hwmp_rootint = val;
+ break;
+ case WLAN_HWMP_RANN_INT:
+ cfg->hwmp_rannint = val;
+ break;
+ case WLAN_HWMP_INACTIVITY_TO:
+ cfg->hwmp_inact = val;
+ break;
+ default:
+ /* NOTREACHED */
+ abort();
+ }
+
+ return (0);
+}
+
+int
+wlan_mesh_config_get(struct wlan_iface *wif, int which)
+{
+ int op, val = 0;
+ size_t argsize = 0;
+ uint8_t data[32], *pd = NULL;
+
+ switch (which) {
+ case LEAF_wlanMeshTTL:
+ op = IEEE80211_IOC_MESH_TTL;
+ break;
+ case LEAF_wlanMeshPeeringEnabled:
+ op = IEEE80211_IOC_MESH_AP;
+ break;
+ case LEAF_wlanMeshForwardingEnabled:
+ op = IEEE80211_IOC_MESH_FWRD;
+ break;
+ case LEAF_wlanMeshMetric:
+ op = IEEE80211_IOC_MESH_PR_METRIC;
+ pd = data;
+ argsize = sizeof(data);
+ break;
+ case LEAF_wlanMeshPath:
+ op = IEEE80211_IOC_MESH_PR_PATH;
+ pd = data;
+ argsize = sizeof(data);
+ break;
+ case LEAF_wlanMeshRoutesFlush:
+ return (0);
+ default:
+ return (-1);
+ }
+
+ if (wlan_ioctl(wif->wname, op, &val, pd, &argsize, 0) < 0)
+ return (-1);
+
+ switch (which) {
+ case LEAF_wlanMeshTTL:
+ wif->mesh_ttl = val;
+ break;
+ case LEAF_wlanMeshPeeringEnabled:
+ if (val)
+ wif->mesh_peering = wlanMeshPeeringEnabled_true;
+ else
+ wif->mesh_peering = wlanMeshPeeringEnabled_false;
+ break;
+ case LEAF_wlanMeshForwardingEnabled:
+ if (val)
+ wif->mesh_forwarding = wlanMeshForwardingEnabled_true;
+ else
+ wif->mesh_forwarding = wlanMeshForwardingEnabled_false;
+ break;
+ case LEAF_wlanMeshMetric:
+ data[argsize] = '\0';
+ if (strcmp(data, "AIRTIME") == 0)
+ wif->mesh_metric = wlanMeshMetric_airtime;
+ else
+ wif->mesh_metric = wlanMeshMetric_unknown;
+ break;
+ case LEAF_wlanMeshPath:
+ data[argsize] = '\0';
+ if (strcmp(data, "HWMP") == 0)
+ wif->mesh_path = wlanMeshPath_hwmp;
+ else
+ wif->mesh_path = wlanMeshPath_unknown;
+ }
+
+ return (0);
+}
+
+int
+wlan_mesh_config_set(struct wlan_iface *wif, int which)
+{
+ int op, val = 0;
+ size_t argsize = 0;
+ uint8_t data[32], *pd = NULL;
+
+ switch (which) {
+ case LEAF_wlanMeshTTL:
+ op = IEEE80211_IOC_MESH_TTL;
+ val = wif->mesh_ttl;
+ break;
+ case LEAF_wlanMeshPeeringEnabled:
+ op = IEEE80211_IOC_MESH_AP;
+ if (wif->mesh_peering == wlanMeshPeeringEnabled_true)
+ val = 1;
+ break;
+ case LEAF_wlanMeshForwardingEnabled:
+ if (wif->mesh_forwarding == wlanMeshForwardingEnabled_true)
+ val = 1;
+ op = IEEE80211_IOC_MESH_FWRD;
+ break;
+ case LEAF_wlanMeshMetric:
+ op = IEEE80211_IOC_MESH_PR_METRIC;
+ if (wif->mesh_metric == wlanMeshMetric_airtime)
+ strcpy(data, "AIRTIME");
+ else
+ return (-1);
+ pd = data;
+ argsize = sizeof(data);
+ break;
+ case LEAF_wlanMeshPath:
+ op = IEEE80211_IOC_MESH_PR_PATH;
+ if (wif->mesh_path == wlanMeshPath_hwmp)
+ strcpy(data, "HWMP");
+ else
+ return (-1);
+ pd = data;
+ argsize = sizeof(data);
+ break;
+ default:
+ return (-1);
+ }
+
+ if (wlan_ioctl(wif->wname, op, &val, pd, &argsize, 1) < 0)
+ return (-1);
+
+ return(0);
+}
+
+int
+wlan_mesh_flush_routes(struct wlan_iface *wif)
+{
+ int val = IEEE80211_MESH_RTCMD_FLUSH;
+ size_t argsize = 0;
+
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_MESH_RTCMD, &val, NULL,
+ &argsize, 1) < 0)
+ return (-1);
+
+ return (0);
+}
+
+int
+wlan_mesh_add_route(struct wlan_iface *wif, struct wlan_mesh_route *wmr)
+{
+ int val = IEEE80211_MESH_RTCMD_ADD;
+ size_t argsize = IEEE80211_ADDR_LEN;
+
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_MESH_RTCMD, &val,
+ wmr->imroute.imr_dest, &argsize, 1) < 0)
+ return (-1);
+
+ wmr->mroute_status = RowStatus_active;
+
+ return (0);
+}
+
+int
+wlan_mesh_del_route(struct wlan_iface *wif, struct wlan_mesh_route *wmr)
+{
+ int val = IEEE80211_MESH_RTCMD_DELETE;
+ size_t argsize = IEEE80211_ADDR_LEN;
+
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_MESH_RTCMD, &val,
+ wmr->imroute.imr_dest, &argsize, 1) < 0)
+ return (-1);
+
+ wmr->mroute_status = RowStatus_destroy;
+
+ return (0);
+}
+
+int
+wlan_mesh_get_routelist(struct wlan_iface *wif)
+{
+ int i, nroutes, val = IEEE80211_MESH_RTCMD_LIST;
+ size_t argsize;
+ struct ieee80211req_mesh_route routes[128];
+ struct ieee80211req_mesh_route *rt;
+ struct wlan_mesh_route *wmr;
+
+ argsize = sizeof(routes);
+ if (wlan_ioctl(wif->wname, IEEE80211_IOC_MESH_RTCMD, &val, routes,
+ &argsize, 0) < 0) /* XXX: ENOMEM? */
+ return (-1);
+
+ nroutes = argsize / sizeof(*rt);
+ for (i = 0; i < nroutes; i++) {
+ rt = routes + i;
+ if ((wmr = wlan_mesh_new_route(rt->imr_dest)) == NULL)
+ return (-1);
+ memcpy(&wmr->imroute, rt, sizeof(*rt));
+ wmr->mroute_status = RowStatus_active;
+ if (wlan_mesh_add_rtentry(wif, wmr) < 0)
+ wlan_mesh_free_route(wmr);
+ }
+
+ return (0);
+}
+
+int
+wlan_hwmp_config_get(struct wlan_iface *wif, int which)
+{
+ int op, val = 0;
+ size_t argsize = 0;
+
+ switch (which) {
+ case LEAF_wlanHWMPRootMode:
+ op = IEEE80211_IOC_HWMP_ROOTMODE;
+ break;
+ case LEAF_wlanHWMPMaxHops:
+ op = IEEE80211_IOC_HWMP_MAXHOPS;
+ break;
+ default:
+ return (-1);
+ }
+
+ if (wlan_ioctl(wif->wname, op, &val, NULL, &argsize, 0) < 0)
+ return (-1);
+
+ switch (which) {
+ case LEAF_wlanHWMPRootMode:
+ switch (val) {
+ case IEEE80211_HWMP_ROOTMODE_NORMAL:
+ wif->hwmp_root_mode = wlanHWMPRootMode_normal;
+ break;
+ case IEEE80211_HWMP_ROOTMODE_PROACTIVE:
+ wif->hwmp_root_mode = wlanHWMPRootMode_proactive;
+ break;
+ case IEEE80211_HWMP_ROOTMODE_RANN:
+ wif->hwmp_root_mode = wlanHWMPRootMode_rann;
+ break;
+ case IEEE80211_HWMP_ROOTMODE_DISABLED:
+ default:
+ wif->hwmp_root_mode = wlanHWMPRootMode_disabled;
+ break;
+ }
+ break;
+ case LEAF_wlanHWMPMaxHops:
+ wif->hwmp_max_hops = val;
+ break;
+ }
+
+ return (0);
+}
+
+int
+wlan_hwmp_config_set(struct wlan_iface *wif, int which)
+{
+ int op, val = 0;
+ size_t argsize = 0;
+
+ switch (which) {
+ case LEAF_wlanHWMPRootMode:
+ op = IEEE80211_IOC_HWMP_ROOTMODE;
+ switch (wif->hwmp_root_mode) {
+ case wlanHWMPRootMode_disabled:
+ val = IEEE80211_HWMP_ROOTMODE_DISABLED;
+ break;
+ case wlanHWMPRootMode_normal:
+ val = IEEE80211_HWMP_ROOTMODE_NORMAL;
+ break;
+ case wlanHWMPRootMode_proactive:
+ val = IEEE80211_HWMP_ROOTMODE_PROACTIVE;
+ break;
+ case wlanHWMPRootMode_rann:
+ val = IEEE80211_HWMP_ROOTMODE_RANN;
+ break;
+ default:
+ return (-1);
+ }
+ break;
+ case LEAF_wlanHWMPMaxHops:
+ op = IEEE80211_IOC_HWMP_MAXHOPS;
+ val = wif->hwmp_max_hops;
+ break;
+ default:
+ return (-1);
+ }
+
+ if (wlan_ioctl(wif->wname, op, &val, NULL, &argsize, 1) < 0)
+ return (-1);
+
+ return (0);
+}
diff --git a/usr.sbin/bsnmpd/modules/snmp_wlan/wlan_tree.def b/usr.sbin/bsnmpd/modules/snmp_wlan/wlan_tree.def
new file mode 100644
index 0000000..e0ae2a0
--- /dev/null
+++ b/usr.sbin/bsnmpd/modules/snmp_wlan/wlan_tree.def
@@ -0,0 +1,677 @@
+#-
+# Copyright (C) 2010 The FreeBSD Foundation
+# All rights reserved.
+#
+# This software was developed by Shteryana Sotirova Shopova 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 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 "tc.def"
+
+typedef RowStatus ENUM (
+ 1 active
+ 2 notInService
+ 3 notReady
+ 4 createAndGo
+ 5 createAndWait
+ 6 destroy
+)
+
+typedef TruthValue ENUM (
+ 1 true
+ 2 false
+)
+
+typedef WlanRegDomainCode ENUM (
+ 1 fcc
+ 2 ca
+ 3 etsi
+ 4 etsi2
+ 5 etsi3
+ 6 fcc3
+ 7 japan
+ 8 korea
+ 9 apac
+ 10 apac2
+ 11 apac3
+ 12 row
+ 13 none
+ 14 debug
+ 15 sr9
+ 16 xr9
+ 17 gz901
+)
+
+typedef WlanMgmtReasonCode ENUM (
+ 1 unspecified
+ 2 authenticationExpire
+ 3 authenticationLeave
+ 4 associationExpire
+ 5 associationTooMany
+ 6 notAuthenticated
+ 7 notAssociated
+ 8 associationLeave
+ 9 associationNotAuthenticated
+ 10 dissassocPwrcapBad
+ 11 dissassocSuperchanBad
+ 13 ieInvalid
+ 14 micFailure
+ 15 fourWayHandshakeTimeout
+ 16 groupKeyUpdateTimeout
+ 17 ieIn4FourWayDiffers
+ 18 groupCipherInvalid
+ 19 pairwiseCiherInvalid
+ 20 akmpInvalid
+ 21 unsupportedRsnIeVersion
+ 22 invalidRsnIeCap
+ 23 dot1xAuthFailed
+ 24 cipherSuiteRejected
+ 32 unspeciffiedQos
+ 33 insufficientBw
+ 34 tooManyFrames
+ 35 outsideTxOp
+ 36 leavingQbss
+ 37 badMechanism
+ 38 setupNeeded
+ 39 timeout
+)
+
+typedef WlanIfaceOperatingModeType ENUM (
+ 0 ibss
+ 1 station
+ 2 wds
+ 3 adhocDemo
+ 4 hostAp
+ 5 monitor
+ 6 meshPoint
+ 7 tdma
+)
+
+typedef WlanIfaceFlagsType BITS (
+ 1 uniqueBssid
+ 2 noBeacons
+ 3 wdsLegacy
+)
+
+typedef WlanDriverCaps BITS (
+ 1 station
+ 2 ieee8023encap
+ 3 athFastFrames
+ 4 athTurbo
+ 5 ibss
+ 6 pmgt
+ 7 hostAp
+ 8 ahDemo
+ 9 swRetry
+ 10 txPmgt
+ 11 shortSlot
+ 12 shortPreamble
+ 13 monitor
+ 14 dfs
+ 15 mbss
+ 16 wpa1
+ 17 wpa2
+ 18 burst
+ 19 wme
+ 20 wds
+ 21 bgScan
+ 22 txFrag
+ 23 tdma
+)
+
+typedef WlanCryptoCaps BITS (
+ 1 wep
+ 2 tkip
+ 3 aes
+ 4 aesCcm
+ 5 tkipMic
+ 6 ckip
+)
+
+typedef WlanHTCaps BITS (
+ 1 ldpc
+ 2 chwidth40
+ 3 greenField
+ 4 shortGi20
+ 5 shortGi40
+ 6 txStbc
+ 7 delba
+ 8 amsdu7935
+ 9 dssscck40
+ 10 psmp
+ 11 fortyMHzIntolerant
+ 12 lsigTxOpProt
+ 13 htcAmpdu
+ 14 htcAmsdu
+ 15 htcHt
+ 16 htcSmps
+ 17 htcRifs
+)
+
+typedef WlanIfaceDot11nPduType ENUM (
+ 0 disabled
+ 1 rxOnly
+ 2 txOnly
+ 3 txAndRx
+)
+
+typedef WlanPeerCapabilityFlags BITS (
+ 1 ess
+ 2 ibss
+ 3 cfPollable
+ 4 cfPollRequest
+ 5 privacy
+ 6 shortPreamble
+ 7 pbcc
+ 8 channelAgility
+ 9 shortSlotTime
+ 10 rsn
+ 11 dsssofdm
+)
+
+typedef WlanIfacePeerFlagsType BITS (
+ 1 authorizedForData
+ 2 qosEnabled
+ 3 erpEnabled
+ 4 powerSaveMode
+ 5 authRefHeld
+ 6 htEnabled
+ 7 htCompat
+ 8 wpsAssoc
+ 9 tsnAssoc
+ 10 ampduRx
+ 11 ampduTx
+ 12 mimoPowerSave
+ 13 sendRts
+ 14 rifs
+ 15 shortGiHT20
+ 16 shortGiHT40
+ 17 amsduRx
+ 18 amsduTx
+)
+
+typedef WlanIfaceChannelFlagsType BITS (
+ 1 turbo
+ 2 cck
+ 3 ofdm
+ 4 spectrum2Ghz
+ 5 spectrum5Ghz
+ 6 passiveScan
+ 7 dynamicCckOfdm
+ 8 gfsk
+ 9 spectrum900Mhz
+ 10 dot11aStaticTurbo
+ 11 halfRate
+ 12 quarterRate
+ 13 ht20
+ 14 ht40u
+ 15 ht40d
+ 16 dfs
+ 17 xmit4ms
+ 18 noAdhoc
+ 19 noHostAp
+ 20 dot11d
+)
+
+typedef WlanIfaceChannelStateType BITS (
+ 1 radar
+ 2 cacDone
+ 3 interferenceDetected
+ 4 radarClear
+)
+
+typedef WlanIfPhyMode ENUM (
+ 1 auto
+ 2 dot11a
+ 3 dot11b
+ 4 dot11g
+ 5 fh
+ 6 turboA
+ 7 turboG
+ 8 sturboA
+ 9 dot11na
+ 10 dot11ng
+ 11 ofdmHalf
+ 12 ofdmQuarter
+)
+
+typedef WlanChannelType ENUM (
+ 1 fhss
+ 2 dot11a
+ 3 dot11b
+ 4 dot11g
+ 5 tenMHz
+ 6 fiveMHz
+ 7 turbo
+ 8 ht
+)
+
+typedef WlanScanFlagsType BITS (
+ 1 noSelection
+ 2 activeScan
+ 3 pickFirst
+ 4 backgroundScan
+ 5 once
+ 6 noBroadcast
+ 7 noAutoSequencing
+ 8 flushCashe
+ 9 chechCashe
+)
+
+typedef WlanMeshNeighborPeerStateType ENUM (
+ 0 idle
+ 1 openTx
+ 2 openRx
+ 3 confirmRx
+ 4 established
+ 5 closing
+)
+
+(1 internet
+ (4 private
+ (1 enterprises
+ (12325 fokus
+ (1 begemot
+ (210 begemotWlan
+ (0 begemotWlanNotifications
+ )
+ (1 begemotWlanInterface
+ (1 wlanInterfaceTable
+ (1 wlanInterfaceEntry : OCTETSTRING op_wlan_iface
+ (1 wlanIfaceIndex INTEGER GET)
+ (2 wlanIfaceName OCTETSTRING GET SET)
+ (3 wlanParentIfName OCTETSTRING GET SET)
+ (4 wlanIfaceOperatingMode WlanIfaceOperatingModeType GET SET)
+ (5 wlanIfaceFlags WlanIfaceFlagsType GET SET)
+ (6 wlanIfaceBssid OCTETSTRING | MacAddress GET SET)
+ (7 wlanIfaceLocalAddress OCTETSTRING | MacAddress GET SET)
+ (8 wlanIfaceStatus RowStatus GET SET)
+ (9 wlanIfaceState ENUM ( 1 up 2 down ) GET SET)
+ ))
+ (2 wlanIfParentTable
+ (1 wlanIfParentEntry : OCTETSTRING op_wlan_if_parent
+ (1 wlanIfParentDriverCapabilities WlanDriverCaps GET)
+ (2 wlanIfParentCryptoCapabilities WlanCryptoCaps GET)
+ (3 wlanIfParentHTCapabilities WlanHTCaps GET)
+ ))
+ (3 wlanIfaceConfigTable
+ (1 wlanIfaceConfigEntry : OCTETSTRING op_wlan_iface_config
+ (1 wlanIfacePacketBurst ENUM ( 1 true 2 false ) GET SET)
+ (2 wlanIfaceCountryCode OCTETSTRING GET SET)
+ (3 wlanIfaceRegDomain WlanRegDomainCode GET SET)
+ (4 wlanIfaceDesiredSsid OCTETSTRING GET SET)
+ (5 wlanIfaceDesiredChannel INTEGER32 GET SET)
+ (6 wlanIfaceDynamicFreqSelection ENUM ( 1 true 2 false ) GET SET)
+ (7 wlanIfaceFastFrames ENUM ( 1 true 2 false ) GET SET)
+ (8 wlanIfaceDturbo ENUM ( 1 true 2 false ) GET SET)
+ (9 wlanIfaceTxPower INTEGER32 GET SET)
+ (10 wlanIfaceFragmentThreshold INTEGER GET SET)
+ (11 wlanIfaceRTSThreshold INTEGER GET SET)
+ (12 wlanIfaceWlanPrivacySubscribe ENUM ( 1 true 2 false ) GET SET)
+ (13 wlanIfaceBgScan ENUM ( 1 true 2 false ) GET SET)
+ (14 wlanIfaceBgScanIdle INTEGER32 GET SET)
+ (15 wlanIfaceBgScanInterval INTEGER32 GET SET)
+ (16 wlanIfaceBeaconMissedThreshold INTEGER GET SET)
+ (17 wlanIfaceDesiredBssid OCTETSTRING | MacAddress GET SET)
+ (18 wlanIfaceRoamingMode ENUM ( 1 device 2 auto 3 manual ) GET SET)
+ (19 wlanIfaceDot11d ENUM ( 1 true 2 false ) GET SET)
+ (20 wlanIfaceDot11h ENUM ( 1 true 2 false ) GET SET)
+ (21 wlanIfaceDynamicWds ENUM ( 1 true 2 false ) GET SET)
+ (22 wlanIfacePowerSave ENUM ( 1 true 2 false ) GET SET)
+ (23 wlanIfaceApBridge ENUM ( 1 true 2 false ) GET SET)
+ (24 wlanIfaceBeaconInterval INTEGER GET SET)
+ (25 wlanIfaceDtimPeriod INTEGER GET SET)
+ (26 wlanIfaceHideSsid ENUM ( 1 true 2 false ) GET SET)
+ (27 wlanIfaceInactivityProccess ENUM ( 1 true 2 false ) GET SET)
+ (28 wlanIfaceDot11gProtMode ENUM ( 1 off 2 cts 3 rtscts ) GET SET)
+ (29 wlanIfaceDot11gPureMode ENUM ( 1 true 2 false ) GET SET)
+ (30 wlanIfaceDot11nPureMode ENUM ( 1 true 2 false ) GET SET)
+ (31 wlanIfaceDot11nAmpdu WlanIfaceDot11nPduType GET SET)
+ (32 wlanIfaceDot11nAmpduDensity INTEGER GET SET)
+ (33 wlanIfaceDot11nAmpduLimit INTEGER GET SET)
+ (34 wlanIfaceDot11nAmsdu WlanIfaceDot11nPduType GET SET)
+ (35 wlanIfaceDot11nAmsduLimit INTEGER GET SET)
+ (36 wlanIfaceDot11nHighThroughput ENUM ( 1 true 2 false ) GET SET)
+ (37 wlanIfaceDot11nHTCompatible ENUM ( 1 true 2 false ) GET SET)
+ (38 wlanIfaceDot11nHTProtMode ENUM ( 1 off 2 rts ) GET SET)
+ (39 wlanIfaceDot11nRIFS ENUM ( 1 true 2 false ) GET SET)
+ (40 wlanIfaceDot11nShortGI ENUM ( 1 true 2 false ) GET SET)
+ (41 wlanIfaceDot11nSMPSMode ENUM ( 1 disabled 2 static 3 dynamic ) GET SET)
+ (42 wlanIfaceTdmaSlot INTEGER GET SET)
+ (43 wlanIfaceTdmaSlotCount INTEGER GET SET)
+ (44 wlanIfaceTdmaSlotLength INTEGER GET SET)
+ (45 wlanIfaceTdmaBeaconInterval INTEGER32 GET SET)
+ ))
+ (4 wlanIfacePeerTable
+ (1 wlanIfacePeerEntry : OCTETSTRING OCTETSTRING | MacAddress op_wlan_if_peer
+ (1 wlanIfacePeerAddress OCTETSTRING | MacAddress GET)
+ (2 wlanIfacePeerAssociationId INTEGER32 GET)
+ (3 wlanIfacePeerVlanTag INTEGER GET SET)
+ (4 wlanIfacePeerFrequency INTEGER32 GET)
+ (5 wlanIfacePeerCurrentTXRate INTEGER32 GET)
+ (6 wlanIfacePeerRxSignalStrength INTEGER32 GET)
+ (7 wlanIfacePeerIdleTimer INTEGER32 GET)
+ (8 wlanIfacePeerTxSequenceNo INTEGER32 GET)
+ (9 wlanIfacePeerRxSequenceNo INTEGER32 GET)
+ (10 wlanIfacePeerTxPower INTEGER32 GET)
+ (11 wlanIfacePeerCapabilities WlanPeerCapabilityFlags GET)
+ (12 wlanIfacePeerFlags WlanIfacePeerFlagsType GET)
+ ))
+ (5 wlanIfaceChannelTable
+ (1 wlanIfaceChannelEntry : OCTETSTRING INTEGER op_wlan_channels
+ (1 wlanIfaceChannelId INTEGER)
+ (2 wlanIfaceChannelIeeeId INTEGER GET)
+ (3 wlanIfaceChannelType WlanChannelType GET)
+ (4 wlanIfaceChannelFlags WlanIfaceChannelFlagsType GET)
+ (5 wlanIfaceChannelFrequency INTEGER32 GET)
+ (6 wlanIfaceChannelMaxRegPower INTEGER32 GET)
+ (7 wlanIfaceChannelMaxTxPower INTEGER32 GET)
+ (8 wlanIfaceChannelMinTxPower INTEGER32 GET)
+ (9 wlanIfaceChannelState WlanIfaceChannelStateType GET)
+ (10 wlanIfaceChannelHTExtension INTEGER32 GET)
+ (11 wlanIfaceChannelMaxAntennaGain INTEGER32 GET)
+ ))
+ (6 wlanIfRoamParamsTable
+ (1 wlanIfRoamParamsEntry : OCTETSTRING WlanIfPhyMode op_wlan_roam_params
+ (1 wlanIfRoamPhyMode WlanIfPhyMode)
+ (2 wlanIfRoamRxSignalStrength INTEGER32 GET)
+ (3 wlanIfRoamTxRateThreshold INTEGER32 GET)
+ ))
+ (7 wlanIfTxParamsTable
+ (1 wlanIfTxParamsEntry : OCTETSTRING WlanIfPhyMode op_wlan_tx_params
+ (1 wlanIfTxPhyMode WlanIfPhyMode)
+ (2 wlanIfTxUnicastRate INTEGER32 GET SET)
+ (3 wlanIfTxMcastRate INTEGER32 GET SET)
+ (4 wlanIfTxMgmtRate INTEGER32 GET SET)
+ (5 wlanIfTxMaxRetryCount INTEGER32 GET SET)
+ ))
+ )
+ (2 begemotWlanScanning
+ (1 wlanScanConfigTable
+ (1 wlanScanConfigEntry : OCTETSTRING op_wlan_scan_config
+ (1 wlanScanFlags WlanScanFlagsType GET SET)
+ (2 wlanScanDuration INTEGER GET SET)
+ (3 wlanScanMinChannelDwellTime INTEGER32 GET SET)
+ (4 wlanScanMaxChannelDwellTime INTEGER32 GET SET)
+ (5 wlanScanConfigStatus ENUM ( 0 unknown 1 notStarted 2 running 3 finished 4 cancel ) GET SET)
+ ))
+ (2 wlanScanResultsTable
+ (1 wlanScanResultsEntry : OCTETSTRING OCTETSTRING OCTETSTRING | MacAddress op_wlan_scan_results
+ (1 wlanScanResultID OCTETSTRING GET)
+ (2 wlanScanResultBssid OCTETSTRING | MacAddress GET)
+ (3 wlanScanResultChannel INTEGER32 GET)
+ (4 wlanScanResultRate INTEGER32 GET)
+ (5 wlanScanResultNoise INTEGER32 GET)
+ (6 wlanScanResultBeaconInterval INTEGER32 GET)
+ (7 wlanScanResultCapabilities WlanPeerCapabilityFlags GET)
+ ))
+ )
+ (3 begemotWlanStatistics
+ (1 wlanIfaceStatisticsTable
+ (1 wlanIfaceStatisticsEntry : OCTETSTRING op_wlan_iface_stats
+ (1 wlanStatsRxBadVersion COUNTER GET)
+ (2 wlanStatsRxTooShort COUNTER GET)
+ (3 wlanStatsRxWrongBssid COUNTER GET)
+ (4 wlanStatsRxDiscardedDups COUNTER GET)
+ (5 wlanStatsRxWrongDir COUNTER GET)
+ (6 wlanStatsRxDiscardMcastEcho COUNTER GET)
+ (7 wlanStatsRxDiscardNoAssoc COUNTER GET)
+ (8 wlanStatsRxWepNoPrivacy COUNTER GET)
+ (9 wlanStatsRxWepUnencrypted COUNTER GET)
+ (10 wlanStatsRxWepFailed COUNTER GET)
+ (11 wlanStatsRxDecapsulationFailed COUNTER GET)
+ (12 wlanStatsRxDiscardMgmt COUNTER GET)
+ (13 wlanStatsRxControl COUNTER GET)
+ (14 wlanStatsRxBeacon COUNTER GET)
+ (15 wlanStatsRxRateSetTooBig COUNTER GET)
+ (16 wlanStatsRxElemMissing COUNTER GET)
+ (17 wlanStatsRxElemTooBig COUNTER GET)
+ (18 wlanStatsRxElemTooSmall COUNTER GET)
+ (19 wlanStatsRxElemUnknown COUNTER GET)
+ (20 wlanStatsRxChannelMismatch COUNTER GET)
+ (21 wlanStatsRxDropped COUNTER GET)
+ (22 wlanStatsRxSsidMismatch COUNTER GET)
+ (23 wlanStatsRxAuthNotSupported COUNTER GET)
+ (24 wlanStatsRxAuthFailed COUNTER GET)
+ (25 wlanStatsRxAuthCM COUNTER GET)
+ (26 wlanStatsRxAssocWrongBssid COUNTER GET)
+ (27 wlanStatsRxAssocNoAuth COUNTER GET)
+ (28 wlanStatsRxAssocCapMismatch COUNTER GET)
+ (29 wlanStatsRxAssocNoRateMatch COUNTER GET)
+ (30 wlanStatsRxBadWpaIE COUNTER GET)
+ (31 wlanStatsRxDeauthenticate COUNTER GET)
+ (32 wlanStatsRxDisassociate COUNTER GET)
+ (33 wlanStatsRxUnknownSubtype COUNTER GET)
+ (34 wlanStatsRxFailedNoBuf COUNTER GET)
+ (35 wlanStatsRxBadAuthRequest COUNTER GET)
+ (36 wlanStatsRxUnAuthorized COUNTER GET)
+ (37 wlanStatsRxBadKeyId COUNTER GET)
+ (38 wlanStatsRxCCMPSeqViolation COUNTER GET)
+ (39 wlanStatsRxCCMPBadFormat COUNTER GET)
+ (40 wlanStatsRxCCMPFailedMIC COUNTER GET)
+ (41 wlanStatsRxTKIPSeqViolation COUNTER GET)
+ (42 wlanStatsRxTKIPBadFormat COUNTER GET)
+ (43 wlanStatsRxTKIPFailedMIC COUNTER GET)
+ (44 wlanStatsRxTKIPFailedICV COUNTER GET)
+ (45 wlanStatsRxDiscardACL COUNTER GET)
+ (46 wlanStatsTxFailedNoBuf COUNTER GET)
+ (47 wlanStatsTxFailedNoNode COUNTER GET)
+ (48 wlanStatsTxUnknownMgmt COUNTER GET)
+ (49 wlanStatsTxBadCipher COUNTER GET)
+ (50 wlanStatsTxNoDefKey COUNTER GET)
+ (51 wlanStatsTxFragmented COUNTER GET)
+ (52 wlanStatsTxFragmentsCreated COUNTER GET)
+ (53 wlanStatsActiveScans COUNTER GET)
+ (54 wlanStatsPassiveScans COUNTER GET)
+ (55 wlanStatsTimeoutInactivity COUNTER GET)
+ (56 wlanStatsCryptoNoMem COUNTER GET)
+ (57 wlanStatsSwCryptoTKIP COUNTER GET)
+ (58 wlanStatsSwCryptoTKIPEnMIC COUNTER GET)
+ (59 wlanStatsSwCryptoTKIPDeMIC COUNTER GET)
+ (60 wlanStatsCryptoTKIPCM COUNTER GET)
+ (61 wlanStatsSwCryptoCCMP COUNTER GET)
+ (62 wlanStatsSwCryptoWEP COUNTER GET)
+ (63 wlanStatsCryptoCipherKeyRejected COUNTER GET)
+ (64 wlanStatsCryptoNoKey COUNTER GET)
+ (65 wlanStatsCryptoDeleteKeyFailed COUNTER GET)
+ (66 wlanStatsCryptoUnknownCipher COUNTER GET)
+ (67 wlanStatsCryptoAttachFailed COUNTER GET)
+ (68 wlanStatsCryptoKeyFailed COUNTER GET)
+ (69 wlanStatsCryptoEnMICFailed COUNTER GET)
+ (70 wlanStatsIBSSCapMismatch COUNTER GET)
+ (71 wlanStatsUnassocStaPSPoll COUNTER GET)
+ (72 wlanStatsBadAidPSPoll COUNTER GET)
+ (73 wlanStatsEmptyPSPoll COUNTER GET)
+ (74 wlanStatsRxFFBadHdr COUNTER GET)
+ (75 wlanStatsRxFFTooShort COUNTER GET)
+ (76 wlanStatsRxFFSplitError COUNTER GET)
+ (77 wlanStatsRxFFDecap COUNTER GET)
+ (78 wlanStatsTxFFEncap COUNTER GET)
+ (79 wlanStatsRxBadBintval COUNTER GET)
+ (80 wlanStatsRxDemicFailed COUNTER GET)
+ (81 wlanStatsRxDefragFailed COUNTER GET)
+ (82 wlanStatsRxMgmt COUNTER GET)
+ (83 wlanStatsRxActionMgmt COUNTER GET)
+ (84 wlanStatsRxAMSDUTooShort COUNTER GET)
+ (85 wlanStatsRxAMSDUSplitError COUNTER GET)
+ (86 wlanStatsRxAMSDUDecap COUNTER GET)
+ (87 wlanStatsTxAMSDUEncap COUNTER GET)
+ (88 wlanStatsAMPDUBadBAR COUNTER GET)
+ (89 wlanStatsAMPDUOowBar COUNTER GET)
+ (90 wlanStatsAMPDUMovedBAR COUNTER GET)
+ (91 wlanStatsAMPDURxBAR COUNTER GET)
+ (92 wlanStatsAMPDURxOor COUNTER GET)
+ (93 wlanStatsAMPDURxCopied COUNTER GET)
+ (94 wlanStatsAMPDURxDropped COUNTER GET)
+ (95 wlanStatsTxDiscardBadState COUNTER GET)
+ (96 wlanStatsTxFailedNoAssoc COUNTER GET)
+ (97 wlanStatsTxClassifyFailed COUNTER GET)
+ (98 wlanStatsDwdsMcastDiscard COUNTER GET)
+ (99 wlanStatsHTAssocRejectNoHT COUNTER GET)
+ (100 wlanStatsHTAssocDowngrade COUNTER GET)
+ (101 wlanStatsHTAssocRateMismatch COUNTER GET)
+ (102 wlanStatsAMPDURxAge COUNTER GET)
+ (103 wlanStatsAMPDUMoved COUNTER GET)
+ (104 wlanStatsADDBADisabledReject COUNTER GET)
+ (105 wlanStatsADDBANoRequest COUNTER GET)
+ (106 wlanStatsADDBABadToken COUNTER GET)
+ (107 wlanStatsADDBABadPolicy COUNTER GET)
+ (108 wlanStatsAMPDUStopped COUNTER GET)
+ (109 wlanStatsAMPDUStopFailed COUNTER GET)
+ (110 wlanStatsAMPDURxReorder COUNTER GET)
+ (111 wlanStatsScansBackground COUNTER GET)
+ (112 wlanLastDeauthReason WlanMgmtReasonCode GET)
+ (113 wlanLastDissasocReason WlanMgmtReasonCode GET)
+ (114 wlanLastAuthFailReason WlanMgmtReasonCode GET)
+ (115 wlanStatsBeaconMissedEvents COUNTER GET)
+ (116 wlanStatsRxDiscardBadStates COUNTER GET)
+ (117 wlanStatsFFFlushed COUNTER GET)
+ (118 wlanStatsTxControlFrames COUNTER GET)
+ (119 wlanStatsAMPDURexmt COUNTER GET)
+ (120 wlanStatsAMPDURexmtFailed COUNTER GET)
+ (121 wlanStatsReset ENUM ( 1 no-op 2 clear ) GET SET)
+ ))
+ )
+ (4 begemotWlanWep
+ (1 wlanWepInterfaceTable
+ (1 wlanWepInterfaceEntry : OCTETSTRING op_wlan_wep_iface
+ (1 wlanWepMode ENUM ( 0 off 1 on 2 mixed ) GET SET)
+ (2 wlanWepDefTxKey INTEGER32 GET SET)
+ ))
+ (2 wlanWepKeyTable
+ (1 wlanWepKeyEntry : OCTETSTRING INTEGER op_wlan_wep_key
+ (1 wlanWepKeyID INTEGER GET SET)
+ (2 wlanWepKeyLength INTEGER32 GET)
+ (3 wlanWepKeySet OCTETSTRING | OctetString GET SET)
+ (4 wlanWepKeyHash OCTETSTRING | OctetString GET)
+ (5 wlanWepKeyStatus RowStatus GET SET)
+ ))
+ )
+ (5 begemotWlanMACAccessControl
+ (1 wlanMACAccessControlTable
+ (1 wlanMACAccessControlEntry : OCTETSTRING op_wlan_mac_access_control
+ (1 wlanMACAccessControlPolicy ENUM ( 0 open 1 allow 2 deny 7 radius ) GET SET)
+ (2 wlanMACAccessControlNacl COUNTER GET)
+ (3 wlanMACAccessControlFlush ENUM ( 0 no-op 1 flush ) GET SET)
+ ))
+ (2 wlanMACAccessControlMACTable
+ (1 wlanMACAccessControlMACEntry : OCTETSTRING OCTETSTRING | MacAddress op_wlan_mac_acl_mac
+ (1 wlanMACAccessControlMAC OCTETSTRING | MacAddress GET SET)
+ (2 wlanMACAccessControlMACStatus RowStatus GET SET)
+ ))
+ )
+ (6 begemotWlanMeshRouting
+ (1 wlanMeshRoutingConfig
+ (1 wlanMeshMaxRetries INTEGER32 op_wlan_mesh_config GET SET)
+ (2 wlanMeshConfirmTimeout INTEGER32 op_wlan_mesh_config GET SET)
+ (3 wlanMeshHoldingTimeout INTEGER32 op_wlan_mesh_config GET SET)
+ (4 wlanMeshRetryTimeout INTEGER32 op_wlan_mesh_config GET SET)
+ )
+ (2 wlanMeshInterface
+ (1 wlanMeshInterfaceTable
+ (1 wlanMeshInterfaceEntry : OCTETSTRING op_wlan_mesh_iface
+ (1 wlanMeshId OCTETSTRING GET SET)
+ (2 wlanMeshTTL INTEGER32 GET SET)
+ (3 wlanMeshPeeringEnabled ENUM ( 1 true 2 false ) GET SET)
+ (4 wlanMeshForwardingEnabled ENUM ( 1 true 2 false ) GET SET)
+ (5 wlanMeshMetric ENUM ( 0 unknown 1 airtime ) GET SET)
+ (6 wlanMeshPath ENUM ( 0 unknown 1 hwmp ) GET SET)
+ (7 wlanMeshRoutesFlush ENUM ( 0 no-op 1 flush ) GET SET)
+ ))
+ (2 wlanMeshNeighborTable
+ (1 wlanMeshNeighborEntry : OCTETSTRING OCTETSTRING | MacAddress op_wlan_mesh_neighbor
+ (1 wlanMeshNeighborAddress OCTETSTRING | MacAddress GET)
+ (2 wlanMeshNeighborFrequency INTEGER32 GET)
+ (3 wlanMeshNeighborLocalId INTEGER32 GET)
+ (4 wlanMeshNeighborPeerId INTEGER32 GET)
+ (5 wlanMeshNeighborPeerState WlanMeshNeighborPeerStateType GET)
+ (6 wlanMeshNeighborCurrentTXRate INTEGER32 GET)
+ (7 wlanMeshNeighborRxSignalStrength INTEGER32 GET)
+ (8 wlanMeshNeighborIdleTimer INTEGER32 GET)
+ (9 wlanMeshNeighborTxSequenceNo INTEGER32 GET)
+ (10 wlanMeshNeighborRxSequenceNo INTEGER32 GET)
+ ))
+ )
+ (3 wlanMeshRoute
+ (1 wlanMeshRouteTable
+ (1 wlanMeshRouteEntry : OCTETSTRING OCTETSTRING | MacAddress op_wlan_mesh_route
+ (1 wlanMeshRouteDestination OCTETSTRING | MacAddress GET SET)
+ (2 wlanMeshRouteNextHop OCTETSTRING | MacAddress GET)
+ (3 wlanMeshRouteHops INTEGER32 GET)
+ (4 wlanMeshRouteMetric UNSIGNED32 GET)
+ (5 wlanMeshRouteLifeTime UNSIGNED32 GET)
+ (6 wlanMeshRouteLastMseq UNSIGNED32 GET)
+ (7 wlanMeshRouteFlags BITS ( 1 valid 2 proxy ) GET)
+ (8 wlanMeshRouteStatus RowStatus GET SET)
+ ))
+ )
+ (4 wlanMeshStatistics
+ (1 wlanMeshStatsTable
+ (1 wlanMeshStatsEntry : OCTETSTRING op_wlan_mesh_stats
+ (1 wlanMeshDroppedBadSta COUNTER GET)
+ (2 wlanMeshDroppedNoLink COUNTER GET)
+ (3 wlanMeshNoFwdTtl COUNTER GET)
+ (4 wlanMeshNoFwdBuf COUNTER GET)
+ (5 wlanMeshNoFwdTooShort COUNTER GET)
+ (6 wlanMeshNoFwdDisabled COUNTER GET)
+ (7 wlanMeshNoFwdPathUnknown COUNTER GET)
+ (8 wlanMeshDroppedBadAE COUNTER GET)
+ (9 wlanMeshRouteAddFailed COUNTER GET)
+ (10 wlanMeshDroppedNoProxy COUNTER GET)
+ (11 wlanMeshDroppedMisaligned COUNTER GET)
+ ))
+ )
+ (5 wlanMeshRouteProtocols
+ (1 wlanMeshProtoHWMP
+ (1 wlanMeshHWMPConfig
+ (1 wlanHWMPRouteInactiveTimeout INTEGER32 op_wlan_hwmp_config GET SET)
+ (2 wlanHWMPRootAnnounceInterval INTEGER32 op_wlan_hwmp_config GET SET)
+ (3 wlanHWMPRootInterval INTEGER32 op_wlan_hwmp_config GET SET)
+ (4 wlanHWMPRootTimeout INTEGER32 op_wlan_hwmp_config GET SET)
+ (5 wlanHWMPPathLifetime INTEGER32 op_wlan_hwmp_config GET SET)
+ (6 wlanHWMPReplyForwardBit INTEGER32 op_wlan_hwmp_config GET SET)
+ (7 wlanHWMPTargetOnlyBit INTEGER32 op_wlan_hwmp_config GET SET)
+ )
+ (2 wlanMeshHWMPInterface
+ (1 wlanHWMPInterfaceTable
+ (1 wlanHWMPInterfaceEntry : OCTETSTRING op_wlan_hwmp_iface
+ (1 wlanHWMPRootMode ENUM ( 1 disabled 2 normal 3 proactive 4 rann ) GET SET)
+ (2 wlanHWMPMaxHops INTEGER32 GET SET)
+ ))
+ )
+ (3 wlanMeshHWMPStatistics
+ (1 wlanMeshHWMPStatsTable
+ (1 wlanMeshHWMPStatsEntry : OCTETSTRING op_wlan_hwmp_stats
+ (1 wlanMeshHWMPWrongSeqNo COUNTER GET)
+ (2 wlanMeshHWMPTxRootPREQ COUNTER GET)
+ (3 wlanMeshHWMPTxRootRANN COUNTER GET)
+ (4 wlanMeshHWMPProxy COUNTER GET)
+ ))
+ )
+ )
+ )
+ ))))))
+)
diff --git a/usr.sbin/bsnmpd/tools/Makefile b/usr.sbin/bsnmpd/tools/Makefile
new file mode 100644
index 0000000..3ffc01e
--- /dev/null
+++ b/usr.sbin/bsnmpd/tools/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+# Author: Shteryana Shopova <syrinx@FreeBSD.org>
+
+SUBDIR= libbsnmptools \
+ bsnmptools
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/bsnmpd/tools/Makefile.inc b/usr.sbin/bsnmpd/tools/Makefile.inc
new file mode 100644
index 0000000..e08fe26
--- /dev/null
+++ b/usr.sbin/bsnmpd/tools/Makefile.inc
@@ -0,0 +1,13 @@
+# $FreeBSD$
+# Author: Shteryana Shopova <syrinx@FreeBSD.org>
+
+BINDIR?= /usr/bin
+
+CFLAGS+= -I. -I${.CURDIR}
+
+.if exists(${.OBJDIR}/../libbsnmptools)
+LIBBSNMPTOOLSDIR= ${.OBJDIR}/../libbsnmptools
+.else
+LIBBSNMPTOOLSDIR= ${.CURDIR}/../libbsnmptools
+.endif
+LIBBSNMPTOOLS= ${LIBBSNMPTOOLSDIR}/libbsnmptools.a
diff --git a/usr.sbin/bsnmpd/tools/bsnmptools/Makefile b/usr.sbin/bsnmpd/tools/bsnmptools/Makefile
new file mode 100644
index 0000000..f63975b
--- /dev/null
+++ b/usr.sbin/bsnmpd/tools/bsnmptools/Makefile
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Author: Shteryana Shopova <syrinx@FreeBSD.org>
+
+.include <src.opts.mk>
+
+.PATH: ${.CURDIR}
+
+PROG= bsnmpget
+
+LIBADD= bsnmp bsnmptools
+CFLAGS+= -I${.CURDIR}/../libbsnmptools
+
+LINKS= ${BINDIR}/bsnmpget ${BINDIR}/bsnmpwalk
+LINKS+= ${BINDIR}/bsnmpget ${BINDIR}/bsnmpset
+
+MAN= bsnmpget.1
+
+MLINKS= bsnmpget.1 bsnmpwalk.1
+MLINKS+= bsnmpget.1 bsnmpset.1
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsnmpd/tools/bsnmptools/Makefile.depend b/usr.sbin/bsnmpd/tools/bsnmptools/Makefile.depend
new file mode 100644
index 0000000..0aeb983
--- /dev/null
+++ b/usr.sbin/bsnmpd/tools/bsnmptools/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbsnmp/libbsnmp \
+ lib/libc \
+ lib/libcompiler_rt \
+ secure/lib/libcrypto \
+ usr.sbin/bsnmpd/tools/libbsnmptools \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsnmpd/tools/bsnmptools/bsnmpget.1 b/usr.sbin/bsnmpd/tools/bsnmptools/bsnmpget.1
new file mode 100644
index 0000000..a2a1186
--- /dev/null
+++ b/usr.sbin/bsnmpd/tools/bsnmptools/bsnmpget.1
@@ -0,0 +1,433 @@
+.\"
+.\" Copyright (c) 2010 The FreeBSD Foundation
+.\" All rights reserved.
+.\"
+.\" Portions of this documentation were written by Shteryana Sotirova Shopova
+.\" under sponsorship from the FreeBSD Foundation.
+.\"
+.\" Copyright (c) 2005-2007 The FreeBSD Project.
+.\" All rights reserved.
+.\"
+.\" Author: Shteryana Shopova <syrinx@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 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 January 10, 2012
+.Dt BSNMPGET 1
+.Os
+.Sh NAME
+.Nm bsnmpget ,
+.Nm bsnmpwalk ,
+.Nm bsnmpset
+.Nd "simple tools for querying SNMP agents"
+.Sh SYNOPSIS
+.Nm
+.Op Fl aDdehnK
+.Op Fl A Ar options
+.Op Fl b Ar buffersize
+.Op Fl C Ar options
+.Op Fl I Ar options
+.Op Fl i Ar filelist
+.Op Fl l Ar filename
+.Op Fl M Ar max-repetitions
+.Op Fl N Ar non-repeaters
+.Op Fl o Ar output
+.Op Fl P Ar options
+.Op Fl p Ar pdu
+.Op Fl r Ar retries
+.Op Fl s Ar [trans::][community@][server][:port]
+.Op Fl t Ar timeout
+.Op Fl U Ar options
+.Op Fl v Ar version
+.Op Ar OID ...
+.Pp
+.Nm bsnmpwalk
+.Op Fl dhnK
+.Op Fl A Ar options
+.Op Fl b Ar buffersize
+.Op Fl C Ar options
+.Op Fl I Ar options
+.Op Fl i Ar filelist
+.Op Fl l Ar filename
+.Op Fl o Ar output
+.Op Fl P Ar options
+.Op Fl r Ar retries
+.Op Fl s Ar [trans::][community@][server][:port]
+.Op Fl t Ar timeout
+.Op Fl U Ar options
+.Op Fl v Ar version
+.Op Ar OID ...
+.Pp
+.Nm bsnmpset
+.Op Fl adehnK
+.Op Fl A Ar options
+.Op Fl b Ar buffersize
+.Op Fl C Ar options
+.Op Fl I Ar options
+.Op Fl i Ar filelist
+.Op Fl l Ar filename
+.Op Fl o Ar output
+.Op Fl P Ar options
+.Op Fl r Ar retries
+.Op Fl s Ar [trans::][community@][server][:port]
+.Op Fl t Ar timeout
+.Op Fl U Ar options
+.Op Fl v Ar version
+.Ar OID Ns = Ar syntax Ns : Ns Ar value
+.Op Ar OID Ns = Ar syntax Ns : Ns Ar value ...
+.Sh DESCRIPTION
+.Nm ,
+.Nm bsnmpwalk
+and
+.Nm bsnmpset
+are simple tools for retrieving management information from and setting
+management information to a Simple Network Management Protocol (SNMP) agent.
+.Pp
+Depending on the options
+.Nm bsnmpget
+constructs either a SNMP GetRequest, GetNextRequest
+or a GetBulkRequest packet, fills in the object identifiers (OIDs) of the
+objects whose values will be retrieved, waits for a response and prints it if
+received successfully.
+.Pp
+.Nm Bsnmpwalk
+queries an agent with ether SNMP GetNextRequest or GetBulkRequest packets,
+asking for values of OID instances that are a part of the object subtree
+rooted at the provided OIDs.
+.Pp
+.Nm Bsnmpset
+constructs a SNMP SetRequest packet, fills in the OIDs (object identifiers),
+syntaxes and values of the objects whose values are to be set and waits for a
+response from server.
+.Sh OPTIONS
+The options are as follows (not all apply to all three programs):
+.Bl -tag -width ".It Fl D Ar options"
+.It Fl A Ar options
+Authentication options to use with SNMPv3 PDUs
+.Bl -tag -width \&
+.It Cm proto=[md5|sha]
+The protocol to use when calculating the PDU message digest.
+.It Cm key=authkey
+A binary localized authentication key to use when calculating the PDU message
+digest.
+.El
+.Pp
+By default SNMPv3 PDUs are sent unauthenticated.
+.It Fl a
+Skip any sanity checks when adding OIDs to a Protocol Data Unit (PDU):
+ingore syntax/access type, allow adding of non-leaf objects for GetPdu and
+read-only objects to a SetPDU.
+.It Fl b Ar buffersize
+Tune the size of buffers used to send and receive packets.
+The default size is 10000 bytes which should be enough unless an agent sends
+a really large octetstring.
+The maximum allowed length is 65535 according to the Structure of Management
+Information (SMIv2).
+.It Fl C Ar options
+The context to query with SNMPv3 PDUs.
+.Bl -tag -width \&
+.It Cm context=name
+The context name. Default is "" (empty).
+.It Cm context-engine=engine-id
+The SNMP Engine ID of the context to query with SNMPv3 PDUs, represented as
+binary octet string.
+By default, this is set to the Engine ID of the SNMP agent.
+.El
+.It Fl D
+Perform SNMP USM Engine Discovery, rather than sending a request for the value
+of a specific object.
+.It Fl d
+Turn on debugging.
+This option will cause the packets sent and received to be dumped to the
+terminal.
+.It Fl e
+Retry on error.
+If an error is returned in the response PDU, resend the request removing the
+variable that caused the error until a valid response is received.
+This is only useful for a GetRequest- and a GetNextRequest-PDU.
+.It Fl h
+Print a short help text with default values for various options.
+.It Fl I Ar options
+Load each MIB description file from the given list to translate symbolic
+object names to their numerical representation and vice versa.
+Use the other options to obtain a non-default behaviour:
+.Bl -tag -width \&
+.It Cm cut=OID
+Specifies the initial OID that was cut by
+.Xr gensnmpdef 1
+when producing the MIB description file.
+The default value is .iso(1).org(3).dod(6) which is what should have been
+used for all the files installed under
+.Pa /usr/share/snmp/defs .
+Use this only if you generated your own files, providing a
+.Fl c
+option to
+.Xr gensnmpdef 1 .
+.It Cm path=filedir
+The directory where files in the list will be searched.
+The default is
+.Pa /usr/share/snmp/defs Ns .
+.It Cm file=filelist
+A comma separated list of files to which the two options above will apply.
+.El
+.Pp
+The file suboption has to come after the other suboptions so that their
+non-default values will be applied to the list of files.
+The order of the other suboptions before each file suboption can be random.
+Suboptions may be separated either by commas or by spaces.
+If using spaces make sure the entire option string is one argument, for
+example using quotes.
+.It Fl i Ar filelist
+List of MIB description files produced by
+.Xr gensnmpdef 1
+which
+.Nm bsnmpget ,
+.Nm bsnmpwalk
+or
+.Nm bsnmpset
+will search to translate numerical OIDs to their symbolic object names.
+Multiple files can be provided either giving this option multiple times
+or a comma separated list of file names.
+If a filename begins with a letter the default directory,
+.Pa /usr/share/snmp/defs ,
+will be searched.
+.It Fl K
+Calculate and display the localized authentication and privacy keys
+corresponding to a plain text password.
+The password is obtained via the environment.
+Additionally, if one or more OIDs are specified, the calculated
+keys are used when processing the SNMPv3 requests.
+.It Fl l Ar filename
+The path of the posix local (unix domain) socket if local
+transport is used.
+.It Fl M Ar max-repetitions
+The value for the max-repetitions field in a GetBulk PDU.
+Default is 10.
+.It Fl N Ar non-repeaters
+The value for the non-repeaters field in a GetBulk PDU.
+Default is 0.
+.It Fl n
+Only use numerical representations for input and output OIDs and do not
+try to resolve symbolic object names.
+Note that
+.Nm bsnmpget ,
+.Nm bsnmpwalk
+and
+.Nm bsnmpset
+will print numerical OIDs anyway if the corresponding string representation
+is not found in the MIB description files.
+.It Fl o Ar [quiet|short|verbose]
+The format used to print the received response.
+Quiet only prints values, short (default) prints an abbreviated OID
+representation and the value.
+In addition to the short output verbose prints the type before the value.
+.It Fl P Ar options
+Privacy options to use with SNMPv3 PDUs
+.Bl -tag -width \&
+.It Cm proto=[aes|des]
+The protocol to use when encrypting/decrypting SNMPv3 PDU data.
+.It Cm key=privkey
+A binary localized privacy key to use when encrypting/decrypting SNMPv3 PDU data.
+.El
+.Pp
+By default plain text SNMPv3 PDUs are sent.
+.It Fl p Ar [get|getnext|getbulk]
+The PDU type to send by
+.Nm bsmpget
+and
+.Nm bsnmpwalk .
+Default is get
+for
+.Nm bsmpget
+and getnext for
+.Nm bsnmpwalk .
+Getbulk allows executing the so called SNMP "bulkwalks" allowing the values of
+multiple columns to be retrieved in a single PDU by
+.Nm bsnmpwalk .
+.It Fl r Ar retries
+Number of resends of request packets before giving up if the agent does
+not respond after the first try.
+Default is 3.
+.It Fl s Ar [trans::] Ns Ar [community@] Ns Ar [server] Ns Ar [:port]
+Each of the server specification components is optional but at least one
+has to be provided if the
+.Ar s
+option is used.
+The server specification is constructed in the following manner:
+.Bl -tag -width \&
+.It Cm trans::
+Transport type may be one of udp, stream or dgram.
+If this option is not provided an UDP inet/inet6 socket will be used, which
+is the most common.
+Stream stands for a posix local stream socket and a posix local datagram
+socket will be used if dgram is specified.
+.It Cm community@
+Specify an SNMP community string to be used when sending packets.
+If the option is skipped the default "public" will be used for
+.Nm
+and
+.Nm bsnmpwalk
+and the default "private" community string will be used for
+.Nm bsnmpset .
+.It Cm server
+This might be either the IP address or the hostname where the agent is
+listening.
+The default is
+.Qq localhost .
+.It Cm port
+The destination port to send the requests to.
+This is useful if the SNMP agent listens on a non-default port.
+Default is given by the
+.Qq snmp
+entry in
+.Pa /etc/services ,
+port 161.
+.El
+.It Fl t Ar timeout
+Number of seconds before resending a request packet if the agent does
+not respond.
+The default value is 3 seconds.
+.It Fl U Ar options
+User credentials when sending SNMPv3 PDUs.
+.Bl -tag -width \&
+.It Cm engine=id
+The Engine ID of the SNMP agent represented as a binary octet string.
+.It Cm engine-boots=value
+The value of the snmpEngineBoots of the SNMP agent.
+.It Cm engine-time=value
+The value of the snmpEngineTime of the SNMP agent.
+.Pp
+If any of the above is not specified, SNMP USM Engine Discovery is attempted.
+This is also the default behavior.
+.It Cm name=username
+The USM user name to include in the SNMPv3 PDUs.
+By default, the user name is
+obtained via the environment.
+.El
+.It Fl v Ar version
+The SNMP protocol version to use when sending requests.
+SNMP versions 1, 2 and
+3 are supported.
+If no version option is provided
+.Nm bsnmpget ,
+.Nm bsnmpwalk
+and
+.Nm bsnmpset
+will use version 2.
+Note that GetBulkRequest-PDUs were introduced in SNMPv2 thus setting the
+version to 1 is incompatible with sending a GetBulk PDU.
+.It OID
+The object identifier whose value to retrieve.
+At least one OID should be provided for
+.Nm bsnmpget
+to be able to send a request.
+.Pp
+For
+.Nm bsnmpwalk
+this is the root object identifier of the subtree whose values are to be
+retrieved.
+If no OID is provided
+.Nm bsnmpwalk
+will walk the mib2 subtree rooted
+at .iso(1).org(3).dod(6).internet(1).mgmt(2).mib2(1) .
+.Pp
+Any of the formats used to print a single variable
+is valid as input OID:
+.Bl -tag -width \&
+.It 1.3.6.1.2.1.25.1.1.0
+.It sysDescr
+.It ifPhysAddress.1
+.It ifRcvAddressStatus.2.6.255.255.255.255.255.255
+.It ifRcvAddressType[2,ff:ff:ff:ff:ff:ff]
+.It ifRcvAddressStatus[Integer:1,OctetString:ff:ff:ff:ff:ff:ff]
+(requires the
+.Fl o Ar verbose
+option)
+.El
+.Pp
+Square brackets are used to denote an entry's indexes.
+When used in an input OID, the square brackets may have to be
+escaped or the OID has to be quoted to protect it from the shell.
+Note there is no difference between ifName.1 and "ifName[1]".
+.It OID Ns = Ns Ar [syntax Ns :] Ns Ar value
+The object identifier with its syntax type and value that is to be set.
+At least one such string OID=[syntax:]value should be provided to
+.Nm bsnmpset
+to be able to send a request.
+.Bl -tag -width \&
+.It Cm OID
+OID may be input as a string, a string followed by a random number of integers
+(suboids) separated by dots, a sequence of integers separated by dots - that is
+if the
+.Ar n
+option is used - and in such case a syntax is required for every value,
+or a string followed by square brackets (used to denote an entry's indexes) and
+corresponding indexes.
+Any of the formats used to print a single variable by
+.Nm bsnmpset
+is valid as input OID as well:
+.Bl -tag -width \&
+.It 1.3.6.1.2.1.25.1.1.0=TimeTicks:537615486
+.It sysLocation=OctetString:"@ Home" (with Fl o Ar verbose No option)
+.It sysLocation.0="@ Home"
+.It 1.3.6.1.2.1.2.2.1.6.1=OctetString:ffffffffffff
+.It ifPhysAddress.1="00:02:b3:1d:1c:a3"
+.It ifRcvAddressStatus.1.6.255.255.255.255.255.255=1
+.It "ifRcvAddressStatus[Integer:1,OctetString:ff:ff:ff:ff:ff:ff]=Integer:1"
+(with the
+.Fl o Ar verbose
+option)
+.El
+.It Cm syntax
+where the syntax string is one of:
+Integer, OctetString, OID, IpAddress, Counter32, Gauge, TimeTicks, Counter64.
+.It Cm value
+The value to be set - IP address in form of u.u.u.u - for example
+1.3.1.6.1.2.0=IpAddress:192.168.0.1, strings require inverted-commas if they
+contain any special characters or spaces, all other numeric types do not.
+.El
+.El
+.Sh ENVIRONMENT
+.Nm ,
+.Nm bsnmpwalk
+and
+.Nm bsnmpset
+use the following environment variables:
+.Bl -tag -width SNMPAUTH
+.It Ev SNMPAUTH
+Specifies a default SNMP USM authentication protocol.
+.It Ev SNMPPRIV
+Specifies a default SNMP USM privacy protocol.
+.It Ev SNMPUSER
+Specifies a default SNMP USM user name.
+.It Ev SNMPPASSWD
+Specifies the SNMP USM plain text password to use when calculating localized
+authentication and privacy keys.
+If this variable exists in the environment,
+SNMPv3 is the default version to use for outgoing requests.
+.El
+.Sh SEE ALSO
+.Xr gensnmpdef 1
+.Sh AUTHORS
+.An Shteryana Shopova Aq Mt syrinx@FreeBSD.org
diff --git a/usr.sbin/bsnmpd/tools/bsnmptools/bsnmpget.c b/usr.sbin/bsnmpd/tools/bsnmptools/bsnmpget.c
new file mode 100644
index 0000000..fb0c7e5
--- /dev/null
+++ b/usr.sbin/bsnmpd/tools/bsnmptools/bsnmpget.c
@@ -0,0 +1,1289 @@
+/*-
+ * Copyright (c) 2005-2006 The FreeBSD Project
+ * All rights reserved.
+ *
+ * Author: Shteryana Shopova <syrinx@FreeBSD.org>
+ *
+ * Redistribution of this software and documentation and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ *
+ * 1. Redistributions of source code or documentation must retain the above
+ * copyright notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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.
+ *
+ * Bsnmpget and bsnmpwalk are simple tools for querying SNMP agents,
+ * bsnmpset can be used to set MIB objects in an agent.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#include <sys/types.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <bsnmp/asn1.h>
+#include <bsnmp/snmp.h>
+#include <bsnmp/snmpclient.h>
+#include "bsnmptc.h"
+#include "bsnmptools.h"
+
+static const char *program_name = NULL;
+static enum program_e {
+ BSNMPGET,
+ BSNMPWALK,
+ BSNMPSET
+} program;
+
+/* *****************************************************************************
+ * Common bsnmptools functions.
+ */
+static void
+usage(void)
+{
+ fprintf(stderr,
+"Usage:\n"
+"%s %s [-A options] [-b buffersize] [-C options] [-I options]\n"
+"\t[-i filelist] [-l filename]%s [-o output] [-P options]\n"
+"\t%s[-r retries] [-s [trans::][community@][server][:port]]\n"
+"\t[-t timeout] [-U options] [-v version]%s\n",
+ program_name,
+ (program == BSNMPGET) ? "[-aDdehnK]" :
+ (program == BSNMPWALK) ? "[-dhnK]" :
+ (program == BSNMPSET) ? "[-adehnK]" :
+ "",
+ (program == BSNMPGET || program == BSNMPWALK) ?
+ " [-M max-repetitions] [-N non-repeaters]" : "",
+ (program == BSNMPGET || program == BSNMPWALK) ? "[-p pdu] " : "",
+ (program == BSNMPGET) ? " OID [OID ...]" :
+ (program == BSNMPWALK || program == BSNMPSET) ? " [OID ...]" :
+ ""
+ );
+}
+
+static int32_t
+parse_max_repetitions(struct snmp_toolinfo *snmptoolctx, char *opt_arg)
+{
+ uint32_t v;
+
+ assert(opt_arg != NULL);
+
+ v = strtoul(opt_arg, (void *) NULL, 10);
+
+ if (v > SNMP_MAX_BINDINGS) {
+ warnx("Max repetitions value greater than %d maximum allowed.",
+ SNMP_MAX_BINDINGS);
+ return (-1);
+ }
+
+ SET_MAXREP(snmptoolctx, v);
+ return (2);
+}
+
+static int32_t
+parse_non_repeaters(struct snmp_toolinfo *snmptoolctx, char *opt_arg)
+{
+ uint32_t v;
+
+ assert(opt_arg != NULL);
+
+ v = strtoul(opt_arg, (void *) NULL, 10);
+
+ if (v > SNMP_MAX_BINDINGS) {
+ warnx("Non repeaters value greater than %d maximum allowed.",
+ SNMP_MAX_BINDINGS);
+ return (-1);
+ }
+
+ SET_NONREP(snmptoolctx, v);
+ return (2);
+}
+
+static int32_t
+parse_pdu_type(struct snmp_toolinfo *snmptoolctx, char *opt_arg)
+{
+ assert(opt_arg != NULL);
+
+ if (strcasecmp(opt_arg, "getbulk") == 0)
+ SET_PDUTYPE(snmptoolctx, SNMP_PDU_GETBULK);
+ else if (strcasecmp(opt_arg, "getnext") == 0)
+ SET_PDUTYPE(snmptoolctx, SNMP_PDU_GETNEXT);
+ else if (strcasecmp(opt_arg, "get") == 0)
+ SET_PDUTYPE(snmptoolctx, SNMP_PDU_GET);
+ else {
+ warnx("PDU type '%s' not supported.", opt_arg);
+ return (-1);
+ }
+
+ return (2);
+}
+
+static int32_t
+snmptool_parse_options(struct snmp_toolinfo *snmptoolctx, int argc, char **argv)
+{
+ int32_t count, optnum = 0;
+ int ch;
+ const char *opts;
+
+ switch (program) {
+ case BSNMPWALK:
+ opts = "dhnKA:b:C:I:i:l:M:N:o:P:p:r:s:t:U:v:";
+ break;
+ case BSNMPGET:
+ opts = "aDdehnKA:b:C:I:i:l:M:N:o:P:p:r:s:t:U:v:";
+ break;
+ case BSNMPSET:
+ opts = "adehnKA:b:C:I:i:l:o:P:r:s:t:U:v:";
+ break;
+ default:
+ return (-1);
+ }
+
+ while ((ch = getopt(argc, argv, opts)) != EOF) {
+ switch (ch) {
+ case 'A':
+ count = parse_authentication(snmptoolctx, optarg);
+ break;
+ case 'a':
+ count = parse_skip_access(snmptoolctx);
+ break;
+ case 'b':
+ count = parse_buflen(optarg);
+ break;
+ case 'D':
+ count = parse_discovery(snmptoolctx);
+ break;
+ case 'd':
+ count = parse_debug();
+ break;
+ case 'e':
+ count = parse_errors(snmptoolctx);
+ break;
+ case 'h':
+ usage();
+ return (-2);
+ case 'C':
+ count = parse_context(snmptoolctx, optarg);
+ break;
+ case 'I':
+ count = parse_include(snmptoolctx, optarg);
+ break;
+ case 'i':
+ count = parse_file(snmptoolctx, optarg);
+ break;
+ case 'K':
+ count = parse_local_key(snmptoolctx);
+ break;
+ case 'l':
+ count = parse_local_path(optarg);
+ break;
+ case 'M':
+ count = parse_max_repetitions(snmptoolctx, optarg);
+ break;
+ case 'N':
+ count = parse_non_repeaters(snmptoolctx, optarg);
+ break;
+ case 'n':
+ count = parse_num_oids(snmptoolctx);
+ break;
+ case 'o':
+ count = parse_output(snmptoolctx, optarg);
+ break;
+ case 'P':
+ count = parse_privacy(snmptoolctx, optarg);
+ break;
+ case 'p':
+ count = parse_pdu_type(snmptoolctx, optarg);
+ break;
+ case 'r':
+ count = parse_retry(optarg);
+ break;
+ case 's':
+ count = parse_server(optarg);
+ break;
+ case 't':
+ count = parse_timeout(optarg);
+ break;
+ case 'U':
+ count = parse_user_security(snmptoolctx, optarg);
+ break;
+ case 'v':
+ count = parse_version(optarg);
+ break;
+ case '?':
+ default:
+ usage();
+ return (-1);
+ }
+ if (count < 0)
+ return (-1);
+ optnum += count;
+ }
+
+ return (optnum);
+}
+
+/*
+ * Read user input OID - one of following formats:
+ * 1) 1.2.1.1.2.1.0 - that is if option numeric was given;
+ * 2) string - in such case append .0 to the asn_oid subs;
+ * 3) string.1 - no additional processing required in such case.
+ */
+static char *
+snmptools_parse_stroid(struct snmp_toolinfo *snmptoolctx,
+ struct snmp_object *obj, char *argv)
+{
+ char string[MAXSTR], *str;
+ int32_t i = 0;
+ struct asn_oid in_oid;
+
+ str = argv;
+
+ if (*str == '.')
+ str++;
+
+ while (isalpha(*str) || *str == '_' || (i != 0 && isdigit(*str))) {
+ str++;
+ i++;
+ }
+
+ if (i <= 0 || i >= MAXSTR)
+ return (NULL);
+
+ memset(&in_oid, 0, sizeof(struct asn_oid));
+ if ((str = snmp_parse_suboid((argv + i), &in_oid)) == NULL) {
+ warnx("Invalid OID - %s", argv);
+ return (NULL);
+ }
+
+ strlcpy(string, argv, i + 1);
+ if (snmp_lookup_oidall(snmptoolctx, obj, string) < 0) {
+ warnx("No entry for %s in mapping lists", string);
+ return (NULL);
+ }
+
+ /* If OID given on command line append it. */
+ if (in_oid.len > 0)
+ asn_append_oid(&(obj->val.var), &in_oid);
+ else if (*str == '[') {
+ if ((str = snmp_parse_index(snmptoolctx, str + 1, obj)) == NULL)
+ return (NULL);
+ } else if (obj->val.syntax > 0 && GET_PDUTYPE(snmptoolctx) ==
+ SNMP_PDU_GET) {
+ if (snmp_suboid_append(&(obj->val.var), (asn_subid_t) 0) < 0)
+ return (NULL);
+ }
+
+ return (str);
+}
+
+static int32_t
+snmptools_parse_oid(struct snmp_toolinfo *snmptoolctx,
+ struct snmp_object *obj, char *argv)
+{
+ if (argv == NULL)
+ return (-1);
+
+ if (ISSET_NUMERIC(snmptoolctx)) {
+ if (snmp_parse_numoid(argv, &(obj->val.var)) < 0)
+ return (-1);
+ } else {
+ if (snmptools_parse_stroid(snmptoolctx, obj, argv) == NULL &&
+ snmp_parse_numoid(argv, &(obj->val.var)) < 0)
+ return (-1);
+ }
+
+ return (1);
+}
+
+static int32_t
+snmptool_add_vbind(struct snmp_pdu *pdu, struct snmp_object *obj)
+{
+ if (obj->error > 0)
+ return (0);
+
+ asn_append_oid(&(pdu->bindings[pdu->nbindings].var), &(obj->val.var));
+ pdu->nbindings++;
+
+ return (pdu->nbindings);
+}
+
+/* *****************************************************************************
+ * bsnmpget private functions.
+ */
+static int32_t
+snmpget_verify_vbind(struct snmp_toolinfo *snmptoolctx, struct snmp_pdu *pdu,
+ struct snmp_object *obj)
+{
+ if (pdu->version == SNMP_V1 && obj->val.syntax ==
+ SNMP_SYNTAX_COUNTER64) {
+ warnx("64-bit counters are not supported in SNMPv1 PDU");
+ return (-1);
+ }
+
+ if (ISSET_NUMERIC(snmptoolctx) || pdu->type == SNMP_PDU_GETNEXT ||
+ pdu->type == SNMP_PDU_GETBULK)
+ return (1);
+
+ if (pdu->type == SNMP_PDU_GET && obj->val.syntax == SNMP_SYNTAX_NULL) {
+ warnx("Only leaf object values can be added to GET PDU");
+ return (-1);
+ }
+
+ return (1);
+}
+
+/*
+ * In case of a getbulk PDU, the error_status and error_index fields are used by
+ * libbsnmp to hold the values of the non-repeaters and max-repetitions fields
+ * that are present only in the getbulk - so before sending the PDU make sure
+ * these have correct values as well.
+ */
+static void
+snmpget_fix_getbulk(struct snmp_pdu *pdu, uint32_t max_rep, uint32_t non_rep)
+{
+ assert(pdu != NULL);
+
+ if (pdu->nbindings < non_rep)
+ pdu->error_status = pdu->nbindings;
+ else
+ pdu->error_status = non_rep;
+
+ if (max_rep > 0)
+ pdu->error_index = max_rep;
+ else
+ pdu->error_index = 1;
+}
+
+static int
+snmptool_get(struct snmp_toolinfo *snmptoolctx)
+{
+ struct snmp_pdu req, resp;
+
+ snmp_pdu_create(&req, GET_PDUTYPE(snmptoolctx));
+
+ while ((snmp_pdu_add_bindings(snmptoolctx, snmpget_verify_vbind,
+ snmptool_add_vbind, &req, SNMP_MAX_BINDINGS)) > 0) {
+
+ if (GET_PDUTYPE(snmptoolctx) == SNMP_PDU_GETBULK)
+ snmpget_fix_getbulk(&req, GET_MAXREP(snmptoolctx),
+ GET_NONREP(snmptoolctx));
+
+ if (snmp_dialog(&req, &resp) == -1) {
+ warnx("Snmp dialog - %s", strerror(errno));
+ break;
+ }
+
+ if (snmp_parse_resp(&resp, &req) >= 0) {
+ snmp_output_resp(snmptoolctx, &resp, NULL);
+ break;
+ }
+
+ snmp_output_err_resp(snmptoolctx, &resp);
+ if (GET_PDUTYPE(snmptoolctx) == SNMP_PDU_GETBULK ||
+ !ISSET_RETRY(snmptoolctx))
+ break;
+
+ /*
+ * Loop through the object list and set object->error to the
+ * varbinding that caused the error.
+ */
+ if (snmp_object_seterror(snmptoolctx,
+ &(resp.bindings[resp.error_index - 1]),
+ resp.error_status) <= 0)
+ break;
+
+ fprintf(stderr, "Retrying...\n");
+ snmp_pdu_free(&resp);
+ snmp_pdu_create(&req, GET_PDUTYPE(snmptoolctx));
+ }
+
+ snmp_pdu_free(&resp);
+
+ return (0);
+}
+
+
+/* *****************************************************************************
+ * bsnmpwalk private functions.
+ */
+/* The default tree to walk. */
+static const struct asn_oid snmp_mibII_OID = {
+ 6 , { 1, 3, 6, 1, 2, 1 }
+};
+
+static int32_t
+snmpwalk_add_default(struct snmp_toolinfo *snmptoolctx __unused,
+ struct snmp_object *obj, char *string __unused)
+{
+ asn_append_oid(&(obj->val.var), &snmp_mibII_OID);
+ return (1);
+}
+
+/*
+ * Prepare the next GetNext/Get PDU to send.
+ */
+static void
+snmpwalk_nextpdu_create(uint32_t op, struct asn_oid *var, struct snmp_pdu *pdu)
+{
+ snmp_pdu_create(pdu, op);
+ asn_append_oid(&(pdu->bindings[0].var), var);
+ pdu->nbindings = 1;
+}
+
+static int
+snmptool_walk(struct snmp_toolinfo *snmptoolctx)
+{
+ struct snmp_pdu req, resp;
+ struct asn_oid root; /* Keep the initial oid. */
+ int32_t outputs, rc;
+ uint32_t op;
+
+ if (GET_PDUTYPE(snmptoolctx) == SNMP_PDU_GETBULK)
+ op = SNMP_PDU_GETBULK;
+ else
+ op = SNMP_PDU_GETNEXT;
+
+ snmp_pdu_create(&req, op);
+
+ while ((rc = snmp_pdu_add_bindings(snmptoolctx, NULL,
+ snmptool_add_vbind, &req, 1)) > 0) {
+
+ /* Remember the root where the walk started from. */
+ memset(&root, 0, sizeof(struct asn_oid));
+ asn_append_oid(&root, &(req.bindings[0].var));
+
+ if (op == SNMP_PDU_GETBULK)
+ snmpget_fix_getbulk(&req, GET_MAXREP(snmptoolctx),
+ GET_NONREP(snmptoolctx));
+
+ outputs = 0;
+ while (snmp_dialog(&req, &resp) >= 0) {
+ if ((snmp_parse_resp(&resp, &req)) < 0) {
+ snmp_output_err_resp(snmptoolctx, &resp);
+ snmp_pdu_free(&resp);
+ outputs = -1;
+ break;
+ }
+
+ rc = snmp_output_resp(snmptoolctx, &resp, &root);
+ if (rc < 0) {
+ snmp_pdu_free(&resp);
+ outputs = -1;
+ break;
+ }
+
+ outputs += rc;
+ snmp_pdu_free(&resp);
+
+ if (rc < resp.nbindings)
+ break;
+
+ snmpwalk_nextpdu_create(op,
+ &(resp.bindings[resp.nbindings - 1].var), &req);
+ if (op == SNMP_PDU_GETBULK)
+ snmpget_fix_getbulk(&req, GET_MAXREP(snmptoolctx),
+ GET_NONREP(snmptoolctx));
+ }
+
+ /* Just in case our root was a leaf. */
+ if (outputs == 0) {
+ snmpwalk_nextpdu_create(SNMP_PDU_GET, &root, &req);
+ if (snmp_dialog(&req, &resp) == SNMP_CODE_OK) {
+ if (snmp_parse_resp(&resp,&req) < 0)
+ snmp_output_err_resp(snmptoolctx, &resp);
+ else
+ snmp_output_resp(snmptoolctx, &(resp), NULL);
+
+ snmp_pdu_free(&resp);
+ } else
+ warnx("Snmp dialog - %s", strerror(errno));
+ }
+
+ if (snmp_object_remove(snmptoolctx, &root) < 0) {
+ warnx("snmp_object_remove");
+ break;
+ }
+
+ snmp_pdu_create(&req, op);
+ }
+
+ if (rc == 0)
+ return (0);
+ else
+ return (1);
+}
+
+/* *****************************************************************************
+ * bsnmpset private functions.
+ */
+
+static int32_t
+parse_oid_numeric(struct snmp_value *value, char *val)
+{
+ char *endptr;
+ int32_t saved_errno;
+ asn_subid_t suboid;
+
+ do {
+ saved_errno = errno;
+ errno = 0;
+ suboid = strtoul(val, &endptr, 10);
+ if (errno != 0) {
+ warnx("Value %s not supported - %s", val,
+ strerror(errno));
+ errno = saved_errno;
+ return (-1);
+ }
+ errno = saved_errno;
+ if ((asn_subid_t) suboid > ASN_MAXID) {
+ warnx("Suboid %u > ASN_MAXID", suboid);
+ return (-1);
+ }
+ if (snmp_suboid_append(&(value->v.oid), suboid) < 0)
+ return (-1);
+ val = endptr + 1;
+ } while (*endptr == '.');
+
+ if (*endptr != '\0')
+ warnx("OID value %s not supported", val);
+
+ value->syntax = SNMP_SYNTAX_OID;
+ return (0);
+}
+
+/*
+ * Allow OID leaf in both forms:
+ * 1) 1.3.6.1.2... -> in such case call directly the function reading raw OIDs;
+ * 2) begemotSnmpdAgentFreeBSD -> lookup the ASN OID corresponding to that.
+ */
+static int32_t
+parse_oid_string(struct snmp_toolinfo *snmptoolctx,
+ struct snmp_value *value, char *string)
+{
+ struct snmp_object obj;
+
+ if (isdigit(string[0]))
+ return (parse_oid_numeric(value, string));
+
+ memset(&obj, 0, sizeof(struct snmp_object));
+ if (snmp_lookup_enumoid(snmptoolctx, &obj, string) < 0) {
+ warnx("Unknown OID enum string - %s", string);
+ return (-1);
+ }
+
+ asn_append_oid(&(value->v.oid), &(obj.val.var));
+ return (1);
+}
+
+static int32_t
+parse_ip(struct snmp_value * value, char * val)
+{
+ uint32_t v;
+ int32_t i;
+ char *endptr, *str;
+
+ str = val;
+ for (i = 0; i < 4; i++) {
+ v = strtoul(str, &endptr, 10);
+ if (v > 0xff)
+ return (-1);
+ if (*endptr != '.' && *endptr != '\0' && i != 3)
+ break;
+ str = endptr + 1;
+ value->v.ipaddress[i] = (uint8_t) v;
+ }
+
+ value->syntax = SNMP_SYNTAX_IPADDRESS;
+ return (0);
+}
+
+static int32_t
+parse_int(struct snmp_value *value, char *val)
+{
+ char *endptr;
+ int32_t v, saved_errno;
+
+ saved_errno = errno;
+ errno = 0;
+
+ v = strtol(val, &endptr, 10);
+
+ if (errno != 0) {
+ warnx("Value %s not supported - %s", val, strerror(errno));
+ errno = saved_errno;
+ return (-1);
+ }
+
+ value->syntax = SNMP_SYNTAX_INTEGER;
+ value->v.integer = v;
+ errno = saved_errno;
+
+ return (0);
+}
+
+static int32_t
+parse_int_string(struct snmp_object *object, char *val)
+{
+ int32_t v;
+
+ if (isdigit(val[0]))
+ return ((parse_int(&(object->val), val)));
+
+ if (object->info == NULL) {
+ warnx("Unknown enumerated integer type - %s", val);
+ return (-1);
+ }
+ if ((v = enum_number_lookup(object->info->snmp_enum, val)) < 0)
+ warnx("Unknown enumerated integer type - %s", val);
+
+ object->val.v.integer = v;
+ return (1);
+}
+
+/*
+ * Here syntax may be one of SNMP_SYNTAX_COUNTER, SNMP_SYNTAX_GAUGE,
+ * SNMP_SYNTAX_TIMETICKS.
+ */
+static int32_t
+parse_uint(struct snmp_value *value, char *val)
+{
+ char *endptr;
+ uint32_t v = 0;
+ int32_t saved_errno;
+
+ saved_errno = errno;
+ errno = 0;
+
+ v = strtoul(val, &endptr, 10);
+
+ if (errno != 0) {
+ warnx("Value %s not supported - %s", val, strerror(errno));
+ errno = saved_errno;
+ return (-1);
+ }
+
+ value->v.uint32 = v;
+ errno = saved_errno;
+
+ return (0);
+}
+
+static int32_t
+parse_ticks(struct snmp_value *value, char *val)
+{
+ if (parse_uint(value, val) < 0)
+ return (-1);
+
+ value->syntax = SNMP_SYNTAX_TIMETICKS;
+ return (0);
+}
+
+static int32_t
+parse_gauge(struct snmp_value *value, char *val)
+{
+ if (parse_uint(value, val) < 0)
+ return (-1);
+
+ value->syntax = SNMP_SYNTAX_GAUGE;
+ return (0);
+}
+
+static int32_t
+parse_counter(struct snmp_value *value, char *val)
+{
+ if (parse_uint(value, val) < 0)
+ return (-1);
+
+ value->syntax = SNMP_SYNTAX_COUNTER;
+ return (0);
+}
+
+static int32_t
+parse_uint64(struct snmp_value *value, char *val)
+{
+ char *endptr;
+ int32_t saved_errno;
+ uint64_t v;
+
+ saved_errno = errno;
+ errno = 0;
+
+ v = strtoull(val, &endptr, 10);
+
+ if (errno != 0) {
+ warnx("Value %s not supported - %s", val, strerror(errno));
+ errno = saved_errno;
+ return (-1);
+ }
+
+ value->syntax = SNMP_SYNTAX_COUNTER64;
+ value->v.counter64 = v;
+ errno = saved_errno;
+
+ return (0);
+}
+
+static int32_t
+parse_syntax_val(struct snmp_value *value, enum snmp_syntax syntax, char *val)
+{
+ switch (syntax) {
+ case SNMP_SYNTAX_INTEGER:
+ return (parse_int(value, val));
+ case SNMP_SYNTAX_IPADDRESS:
+ return (parse_ip(value, val));
+ case SNMP_SYNTAX_COUNTER:
+ return (parse_counter(value, val));
+ case SNMP_SYNTAX_GAUGE:
+ return (parse_gauge(value, val));
+ case SNMP_SYNTAX_TIMETICKS:
+ return (parse_ticks(value, val));
+ case SNMP_SYNTAX_COUNTER64:
+ return (parse_uint64(value, val));
+ case SNMP_SYNTAX_OCTETSTRING:
+ return (snmp_tc2oct(SNMP_STRING, value, val));
+ case SNMP_SYNTAX_OID:
+ return (parse_oid_numeric(value, val));
+ default:
+ /* NOTREACHED */
+ break;
+ }
+
+ return (-1);
+}
+
+/*
+ * Parse a command line argument of type OID=syntax:value and fill in whatever
+ * fields can be derived from the input into snmp_value structure. Reads numeric
+ * OIDs.
+ */
+static int32_t
+parse_pair_numoid_val(char *str, struct snmp_value *snmp_val)
+{
+ int32_t cnt;
+ char *ptr;
+ enum snmp_syntax syntax;
+ char oid_str[ASN_OIDSTRLEN];
+
+ ptr = str;
+ for (cnt = 0; cnt < ASN_OIDSTRLEN; cnt++)
+ if (ptr[cnt] == '=')
+ break;
+
+ if (cnt >= ASN_OIDSTRLEN) {
+ warnx("OID too long - %s", str);
+ return (-1);
+ }
+ strlcpy(oid_str, ptr, (size_t) (cnt + 1));
+
+ ptr = str + cnt + 1;
+ for (cnt = 0; cnt < MAX_CMD_SYNTAX_LEN; cnt++)
+ if(ptr[cnt] == ':')
+ break;
+
+ if (cnt >= MAX_CMD_SYNTAX_LEN) {
+ warnx("Unknown syntax in OID - %s", str);
+ return (-1);
+ }
+
+ if ((syntax = parse_syntax(ptr)) <= SNMP_SYNTAX_NULL) {
+ warnx("Unknown syntax in OID - %s", ptr);
+ return (-1);
+ }
+
+ ptr = ptr + cnt + 1;
+ for (cnt = 0; cnt < MAX_OCTSTRING_LEN; cnt++)
+ if (ptr[cnt] == '\0')
+ break;
+
+ if (ptr[cnt] != '\0') {
+ warnx("Value string too long - %s",ptr);
+ return (-1);
+ }
+
+ /*
+ * Here try parsing the OIDs and syntaxes and then check values - have
+ * to know syntax to check value boundaries.
+ */
+ if (snmp_parse_numoid(oid_str, &(snmp_val->var)) < 0) {
+ warnx("Error parsing OID %s",oid_str);
+ return (-1);
+ }
+
+ if (parse_syntax_val(snmp_val, syntax, ptr) < 0)
+ return (-1);
+
+ return (1);
+}
+
+/* XXX-BZ aruments should be swapped. */
+static int32_t
+parse_syntax_strval(struct snmp_toolinfo *snmptoolctx, char *str,
+ struct snmp_object *object)
+{
+ uint32_t len;
+ enum snmp_syntax syn;
+
+ /*
+ * Syntax string here not required - still may be present.
+ */
+
+ if (GET_OUTPUT(snmptoolctx) == OUTPUT_VERBOSE) {
+ for (len = 0 ; *(str + len) != ':'; len++) {
+ if (*(str + len) == '\0') {
+ warnx("Syntax missing in value - %s", str);
+ return (-1);
+ }
+ }
+ if ((syn = parse_syntax(str)) <= SNMP_SYNTAX_NULL) {
+ warnx("Unknown syntax in - %s", str);
+ return (-1);
+ }
+ if (syn != object->val.syntax) {
+ if (!ISSET_ERRIGNORE(snmptoolctx)) {
+ warnx("Bad syntax in - %s", str);
+ return (-1);
+ } else
+ object->val.syntax = syn;
+ }
+ len++;
+ } else
+ len = 0;
+
+ switch (object->val.syntax) {
+ case SNMP_SYNTAX_INTEGER:
+ return (parse_int_string(object, str + len));
+ case SNMP_SYNTAX_IPADDRESS:
+ return (parse_ip(&(object->val), str + len));
+ case SNMP_SYNTAX_COUNTER:
+ return (parse_counter(&(object->val), str + len));
+ case SNMP_SYNTAX_GAUGE:
+ return (parse_gauge(&(object->val), str + len));
+ case SNMP_SYNTAX_TIMETICKS:
+ return (parse_ticks(&(object->val), str + len));
+ case SNMP_SYNTAX_COUNTER64:
+ return (parse_uint64(&(object->val), str + len));
+ case SNMP_SYNTAX_OCTETSTRING:
+ return (snmp_tc2oct(object->info->tc, &(object->val),
+ str + len));
+ case SNMP_SYNTAX_OID:
+ return (parse_oid_string(snmptoolctx, &(object->val),
+ str + len));
+ default:
+ /* NOTREACHED */
+ break;
+ }
+
+ return (-1);
+}
+
+static int32_t
+parse_pair_stroid_val(struct snmp_toolinfo *snmptoolctx,
+ struct snmp_object *obj, char *argv)
+{
+ char *ptr;
+
+ if ((ptr = snmptools_parse_stroid(snmptoolctx, obj, argv)) == NULL)
+ return (-1);
+
+ if (*ptr != '=') {
+ warnx("Value to set expected after OID");
+ return (-1);
+ }
+
+ if (parse_syntax_strval(snmptoolctx, ptr + 1, obj) < 0)
+ return (-1);
+
+ return (1);
+}
+
+
+static int32_t
+snmpset_parse_oid(struct snmp_toolinfo *snmptoolctx,
+ struct snmp_object *obj, char *argv)
+{
+ if (argv == NULL)
+ return (-1);
+
+ if (ISSET_NUMERIC(snmptoolctx)) {
+ if (parse_pair_numoid_val(argv, &(obj->val)) < 0)
+ return (-1);
+ } else {
+ if (parse_pair_stroid_val(snmptoolctx, obj, argv) < 0)
+ return (-1);
+ }
+
+ return (1);
+}
+
+static int32_t
+add_ip_syntax(struct snmp_value *dst, struct snmp_value *src)
+{
+ int8_t i;
+
+ dst->syntax = SNMP_SYNTAX_IPADDRESS;
+ for (i = 0; i < 4; i++)
+ dst->v.ipaddress[i] = src->v.ipaddress[i];
+
+ return (1);
+}
+
+static int32_t
+add_octstring_syntax(struct snmp_value *dst, struct snmp_value *src)
+{
+ if (src->v.octetstring.len > ASN_MAXOCTETSTRING) {
+ warnx("OctetString len too big - %u",src->v.octetstring.len);
+ return (-1);
+ }
+
+ if ((dst->v.octetstring.octets = malloc(src->v.octetstring.len)) ==
+ NULL) {
+ syslog(LOG_ERR, "malloc() failed - %s", strerror(errno));
+ return (-1);
+ }
+
+ memcpy(dst->v.octetstring.octets, src->v.octetstring.octets,
+ src->v.octetstring.len);
+ dst->syntax = SNMP_SYNTAX_OCTETSTRING;
+ dst->v.octetstring.len = src->v.octetstring.len;
+
+ return(0);
+}
+
+static int32_t
+add_oid_syntax(struct snmp_value *dst, struct snmp_value *src)
+{
+ asn_append_oid(&(dst->v.oid), &(src->v.oid));
+ dst->syntax = SNMP_SYNTAX_OID;
+ return (0);
+}
+
+/*
+ * Check syntax - if one of SNMP_SYNTAX_NULL, SNMP_SYNTAX_NOSUCHOBJECT,
+ * SNMP_SYNTAX_NOSUCHINSTANCE, SNMP_SYNTAX_ENDOFMIBVIEW or anything not known -
+ * return error.
+ */
+static int32_t
+snmpset_add_value(struct snmp_value *dst, struct snmp_value *src)
+{
+ if (dst == NULL || src == NULL)
+ return (-1);
+
+ switch (src->syntax) {
+ case SNMP_SYNTAX_INTEGER:
+ dst->v.integer = src->v.integer;
+ dst->syntax = SNMP_SYNTAX_INTEGER;
+ break;
+ case SNMP_SYNTAX_TIMETICKS:
+ dst->v.uint32 = src->v.uint32;
+ dst->syntax = SNMP_SYNTAX_TIMETICKS;
+ break;
+ case SNMP_SYNTAX_GAUGE:
+ dst->v.uint32 = src->v.uint32;
+ dst->syntax = SNMP_SYNTAX_GAUGE;
+ break;
+ case SNMP_SYNTAX_COUNTER:
+ dst->v.uint32 = src->v.uint32;
+ dst->syntax = SNMP_SYNTAX_COUNTER;
+ break;
+ case SNMP_SYNTAX_COUNTER64:
+ dst->v.counter64 = src->v.counter64;
+ dst->syntax = SNMP_SYNTAX_COUNTER64;
+ break;
+ case SNMP_SYNTAX_IPADDRESS:
+ add_ip_syntax(dst, src);
+ break;
+ case SNMP_SYNTAX_OCTETSTRING:
+ add_octstring_syntax(dst, src);
+ break;
+ case SNMP_SYNTAX_OID:
+ add_oid_syntax(dst, src);
+ break;
+ default:
+ warnx("Unknown syntax %d", src->syntax);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int32_t
+snmpset_verify_vbind(struct snmp_toolinfo *snmptoolctx, struct snmp_pdu *pdu,
+ struct snmp_object *obj)
+{
+ if (pdu->version == SNMP_V1 && obj->val.syntax ==
+ SNMP_SYNTAX_COUNTER64) {
+ warnx("64-bit counters are not supported in SNMPv1 PDU");
+ return (-1);
+ }
+
+ if (ISSET_NUMERIC(snmptoolctx) || ISSET_ERRIGNORE(snmptoolctx))
+ return (1);
+
+ if (obj->info->access < SNMP_ACCESS_SET) {
+ warnx("Object %s not accessible for set - try 'bsnmpset -a'",
+ obj->info->string);
+ return (-1);
+ }
+
+ return (1);
+}
+
+static int32_t
+snmpset_add_vbind(struct snmp_pdu *pdu, struct snmp_object *obj)
+{
+ if (pdu->nbindings > SNMP_MAX_BINDINGS) {
+ warnx("Too many OIDs for one PDU");
+ return (-1);
+ }
+
+ if (obj->error > 0)
+ return (0);
+
+ if (snmpset_add_value(&(pdu->bindings[pdu->nbindings]), &(obj->val))
+ < 0)
+ return (-1);
+
+ asn_append_oid(&(pdu->bindings[pdu->nbindings].var), &(obj->val.var));
+ pdu->nbindings++;
+
+ return (pdu->nbindings);
+}
+
+static int
+snmptool_set(struct snmp_toolinfo *snmptoolctx)
+{
+ struct snmp_pdu req, resp;
+
+ snmp_pdu_create(&req, SNMP_PDU_SET);
+
+ while ((snmp_pdu_add_bindings(snmptoolctx, snmpset_verify_vbind,
+ snmpset_add_vbind, &req, SNMP_MAX_BINDINGS)) > 0) {
+ if (snmp_dialog(&req, &resp)) {
+ warnx("Snmp dialog - %s", strerror(errno));
+ break;
+ }
+
+ if (snmp_pdu_check(&req, &resp) > 0) {
+ if (GET_OUTPUT(snmptoolctx) != OUTPUT_QUIET)
+ snmp_output_resp(snmptoolctx, &resp, NULL);
+ break;
+ }
+
+ snmp_output_err_resp(snmptoolctx, &resp);
+ if (!ISSET_RETRY(snmptoolctx))
+ break;
+
+ if (snmp_object_seterror(snmptoolctx,
+ &(resp.bindings[resp.error_index - 1]),
+ resp.error_status) <= 0)
+ break;
+
+ fprintf(stderr, "Retrying...\n");
+ snmp_pdu_free(&req);
+ snmp_pdu_free(&resp);
+ snmp_pdu_create(&req, SNMP_PDU_SET);
+ }
+
+ snmp_pdu_free(&resp);
+
+ return (0);
+}
+
+/* *****************************************************************************
+ * main
+ */
+/*
+ * According to command line options prepare SNMP Get | GetNext | GetBulk PDU.
+ * Wait for a response and print it.
+ */
+/*
+ * Do a 'snmp walk' - according to command line options request for values
+ * lexicographically subsequent and subrooted at a common node. Send a GetNext
+ * PDU requesting the value for each next variable and print the response. Stop
+ * when a Response PDU is received that contains the value of a variable not
+ * subrooted at the variable the walk started.
+ */
+int
+main(int argc, char ** argv)
+{
+ struct snmp_toolinfo snmptoolctx;
+ int32_t oid_cnt, last_oid, opt_num;
+ int rc = 0;
+
+ /* Make sure program_name is set and valid. */
+ if (*argv == NULL)
+ program_name = "snmptool";
+ else {
+ program_name = strrchr(*argv, '/');
+ if (program_name != NULL)
+ program_name++;
+ else
+ program_name = *argv;
+ }
+
+ if (program_name == NULL) {
+ fprintf(stderr, "Error: No program name?\n");
+ exit (1);
+ } else if (strcmp(program_name, "bsnmpget") == 0)
+ program = BSNMPGET;
+ else if (strcmp(program_name, "bsnmpwalk") == 0)
+ program = BSNMPWALK;
+ else if (strcmp(program_name, "bsnmpset") == 0)
+ program = BSNMPSET;
+ else {
+ fprintf(stderr, "Unknown snmp tool name '%s'.\n", program_name);
+ exit (1);
+ }
+
+ /* Initialize. */
+ if (snmptool_init(&snmptoolctx) < 0)
+ exit (1);
+
+ if ((opt_num = snmptool_parse_options(&snmptoolctx, argc, argv)) < 0) {
+ snmp_tool_freeall(&snmptoolctx);
+ /* On -h (help) exit without error. */
+ if (opt_num == -2)
+ exit(0);
+ else
+ exit(1);
+ }
+
+ oid_cnt = argc - opt_num - 1;
+ if (oid_cnt == 0) {
+ switch (program) {
+ case BSNMPGET:
+ if (!ISSET_EDISCOVER(&snmptoolctx) &&
+ !ISSET_LOCALKEY(&snmptoolctx)) {
+ fprintf(stderr, "No OID given.\n");
+ usage();
+ snmp_tool_freeall(&snmptoolctx);
+ exit(1);
+ }
+ break;
+
+ case BSNMPWALK:
+ if (snmp_object_add(&snmptoolctx, snmpwalk_add_default,
+ NULL) < 0) {
+ fprintf(stderr,
+ "Error setting default subtree.\n");
+ snmp_tool_freeall(&snmptoolctx);
+ exit(1);
+ }
+ break;
+
+ case BSNMPSET:
+ fprintf(stderr, "No OID given.\n");
+ usage();
+ snmp_tool_freeall(&snmptoolctx);
+ exit(1);
+ }
+ }
+
+ if (snmp_import_all(&snmptoolctx) < 0) {
+ snmp_tool_freeall(&snmptoolctx);
+ exit(1);
+ }
+
+ /* A simple sanity check - can not send GETBULK when using SNMPv1. */
+ if (program == BSNMPGET && snmp_client.version == SNMP_V1 &&
+ GET_PDUTYPE(&snmptoolctx) == SNMP_PDU_GETBULK) {
+ fprintf(stderr, "Cannot send GETBULK PDU with SNMPv1.\n");
+ snmp_tool_freeall(&snmptoolctx);
+ exit(1);
+ }
+
+ for (last_oid = argc - 1; oid_cnt > 0; last_oid--, oid_cnt--) {
+ if ((snmp_object_add(&snmptoolctx, (program == BSNMPSET) ?
+ snmpset_parse_oid : snmptools_parse_oid,
+ argv[last_oid])) < 0) {
+ fprintf(stderr, "Error parsing OID string '%s'.\n",
+ argv[last_oid]);
+ snmp_tool_freeall(&snmptoolctx);
+ exit(1);
+ }
+ }
+
+ if (snmp_open(NULL, NULL, NULL, NULL)) {
+ warnx("Failed to open snmp session: %s.", strerror(errno));
+ snmp_tool_freeall(&snmptoolctx);
+ exit(1);
+ }
+
+ if (snmp_client.version == SNMP_V3 && snmp_client.engine.engine_len == 0)
+ SET_EDISCOVER(&snmptoolctx);
+
+ if (ISSET_EDISCOVER(&snmptoolctx) &&
+ snmp_discover_engine(snmptoolctx.passwd) < 0) {
+ warnx("Unknown SNMP Engine ID: %s.", strerror(errno));
+ rc = 1;
+ goto cleanup;
+ }
+
+ if (GET_OUTPUT(&snmptoolctx) == OUTPUT_VERBOSE ||
+ ISSET_EDISCOVER(&snmptoolctx))
+ snmp_output_engine();
+
+ if (snmp_client.version == SNMP_V3 && ISSET_LOCALKEY(&snmptoolctx) &&
+ !ISSET_EDISCOVER(&snmptoolctx)) {
+ if (snmp_passwd_to_keys(&snmp_client.user,
+ snmptoolctx.passwd) != SNMP_CODE_OK ||
+ snmp_get_local_keys(&snmp_client.user,
+ snmp_client.engine.engine_id,
+ snmp_client.engine.engine_len) != SNMP_CODE_OK) {
+ warnx("Failed to get keys: %s.", strerror(errno));
+ rc = 1;
+ goto cleanup;
+ }
+ }
+
+ if (GET_OUTPUT(&snmptoolctx) == OUTPUT_VERBOSE ||
+ ISSET_EDISCOVER(&snmptoolctx))
+ snmp_output_keys();
+
+ if (ISSET_EDISCOVER(&snmptoolctx) && snmptoolctx.objects == 0)
+ goto cleanup;
+
+ switch (program) {
+ case BSNMPGET:
+ rc = snmptool_get(&snmptoolctx);
+ break;
+ case BSNMPWALK:
+ rc = snmptool_walk(&snmptoolctx);
+ break;
+ case BSNMPSET:
+ rc = snmptool_set(&snmptoolctx);
+ break;
+ }
+
+
+cleanup:
+ snmp_tool_freeall(&snmptoolctx);
+ snmp_close();
+
+ exit(rc);
+}
diff --git a/usr.sbin/bsnmpd/tools/libbsnmptools/Makefile b/usr.sbin/bsnmpd/tools/libbsnmptools/Makefile
new file mode 100644
index 0000000..3551464
--- /dev/null
+++ b/usr.sbin/bsnmpd/tools/libbsnmptools/Makefile
@@ -0,0 +1,13 @@
+#
+# $FreeBSD$
+#
+
+.PATH: ${.CURDIR}
+
+LIB= bsnmptools
+#INTERNALLIB=
+SRCS= bsnmpimport.c bsnmpmap.c bsnmptools.c bsnmptc.c
+
+SHLIB_MAJOR= 0
+
+.include <bsd.lib.mk>
diff --git a/usr.sbin/bsnmpd/tools/libbsnmptools/Makefile.depend b/usr.sbin/bsnmpd/tools/libbsnmptools/Makefile.depend
new file mode 100644
index 0000000..fc79573
--- /dev/null
+++ b/usr.sbin/bsnmpd/tools/libbsnmptools/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbsnmp/libbsnmp \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmpimport.c b/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmpimport.c
new file mode 100644
index 0000000..b92532f
--- /dev/null
+++ b/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmpimport.c
@@ -0,0 +1,971 @@
+/*-
+ * Copyright (c) 2006 The FreeBSD Project
+ * All rights reserved.
+ *
+ * Author: Shteryana Shopova <syrinx@FreeBSD.org>
+ *
+ * Redistribution of this software and documentation and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ *
+ * 1. Redistributions of source code or documentation must retain the above
+ * copyright notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+/*
+ * Read file containing table description - reuse magic from gensnmptree.c.
+ * Hopefully one day most of the code here will be part of libbsnmp and
+ * this duplication won't be necessary.
+ *
+ * Syntax is:
+ * ---------
+ * file := top | top file
+ *
+ * top := tree | typedef | include
+ *
+ * tree := head elements ')'
+ *
+ * entry := head ':' index STRING elements ')'
+ *
+ * leaf := head type STRING ACCESS ')'
+ *
+ * column := head type ACCESS ')'
+ *
+ * type := BASETYPE | BASETYPE '|' subtype | enum | bits
+ *
+ * subtype := STRING
+ *
+ * enum := ENUM '(' value ')'
+ *
+ * bits := BITS '(' value ')'
+ *
+ * value := INT STRING | INT STRING value
+ *
+ * head := '(' INT STRING
+ *
+ * elements := EMPTY | elements element
+ *
+ * element := tree | leaf | column
+ *
+ * index := type | index type
+ *
+ * typedef := 'typedef' STRING type
+ *
+ * include := 'include' filespec
+ *
+ * filespec := '"' STRING '"' | '<' STRING '>'
+ */
+
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <bsnmp/asn1.h>
+#include <bsnmp/snmp.h>
+#include <bsnmp/snmpagent.h> /* SNMP_INDEXES_MAX */
+#include "bsnmptc.h"
+#include "bsnmptools.h"
+
+enum snmp_tbl_entry {
+ ENTRY_NONE = 0,
+ ENTRY_INDEX,
+ ENTRY_DATA
+};
+
+enum {
+ FL_GET = 0x01,
+ FL_SET = 0x02,
+};
+
+/************************************************************
+ *
+ * Allocate memory and panic just in the case...
+ */
+static void *
+xalloc(size_t size)
+{
+ void *ptr;
+
+ if ((ptr = malloc(size)) == NULL)
+ err(1, "allocing %zu bytes", size);
+
+ return (ptr);
+}
+
+static char *
+savestr(const char *s)
+{
+ if (s == NULL)
+ return (NULL);
+
+ return (strcpy(xalloc(strlen(s) + 1), s));
+}
+
+/************************************************************
+ *
+ * Input stack
+ */
+struct input {
+ FILE *fp;
+ uint32_t lno;
+ char *fname;
+ char *path;
+ LIST_ENTRY(input) link;
+};
+
+LIST_HEAD(, input) inputs = LIST_HEAD_INITIALIZER(inputs);
+struct input *input = NULL;
+int32_t pbchar = -1;
+
+#define MAX_PATHS 100
+
+static const char *paths[MAX_PATHS + 1] = {
+ "/usr/share/snmp/defs",
+ "/usr/local/share/snmp/defs",
+ NULL
+};
+
+static void
+input_new(FILE *fp, const char *path, const char *fname)
+{
+ struct input *ip;
+
+ ip = xalloc(sizeof(*ip));
+ ip->fp = fp;
+ ip->lno = 1;
+ ip->fname = savestr(fname);
+ ip->path = savestr(path);
+ LIST_INSERT_HEAD(&inputs, ip, link);
+
+ input = ip;
+}
+
+static void
+input_close(void)
+{
+ if (input == NULL)
+ return;
+
+ fclose(input->fp);
+ free(input->fname);
+ free(input->path);
+ LIST_REMOVE(input, link);
+ free(input);
+
+ input = LIST_FIRST(&inputs);
+}
+
+static FILE *
+tryopen(const char *path, const char *fname)
+{
+ char *fn;
+ FILE *fp;
+
+ if (path == NULL)
+ fn = savestr(fname);
+ else {
+ fn = xalloc(strlen(path) + strlen(fname) + 2);
+ sprintf(fn, "%s/%s", path, fname);
+ }
+ fp = fopen(fn, "r");
+ free(fn);
+ return (fp);
+}
+
+static int32_t
+input_fopen(const char *fname)
+{
+ FILE *fp;
+ u_int p;
+
+ if (fname[0] == '/' || fname[0] == '.' || fname[0] == '~') {
+ if ((fp = tryopen(NULL, fname)) != NULL) {
+ input_new(fp, NULL, fname);
+ return (0);
+ }
+
+ } else {
+
+ for (p = 0; paths[p] != NULL; p++)
+ if ((fp = tryopen(paths[p], fname)) != NULL) {
+ input_new(fp, paths[p], fname);
+ return (0);
+ }
+ }
+
+ warnx("cannot open '%s'", fname);
+ return (-1);
+}
+
+static int32_t
+tgetc(void)
+{
+ int c;
+
+ if (pbchar != -1) {
+ c = pbchar;
+ pbchar = -1;
+ return (c);
+ }
+
+ for (;;) {
+ if (input == NULL)
+ return (EOF);
+
+ if ((c = getc(input->fp)) != EOF)
+ return (c);
+
+ input_close();
+ }
+}
+
+static int32_t
+tungetc(int c)
+{
+
+ if (pbchar != -1)
+ return (-1);
+
+ pbchar = c;
+ return (1);
+}
+
+/************************************************************
+ *
+ * Parsing input
+ */
+enum tok {
+ TOK_EOF = 0200, /* end-of-file seen */
+ TOK_NUM, /* number */
+ TOK_STR, /* string */
+ TOK_ACCESS, /* access operator */
+ TOK_TYPE, /* type operator */
+ TOK_ENUM, /* enum token (kind of a type) */
+ TOK_TYPEDEF, /* typedef directive */
+ TOK_DEFTYPE, /* defined type */
+ TOK_INCLUDE, /* include directive */
+ TOK_FILENAME, /* filename ("foo.bar" or <foo.bar>) */
+ TOK_BITS, /* bits token (kind of a type) */
+ TOK_ERR /* unexpected char - exit */
+};
+
+static const struct {
+ const char *str;
+ enum tok tok;
+ uint32_t val;
+} keywords[] = {
+ { "GET", TOK_ACCESS, FL_GET },
+ { "SET", TOK_ACCESS, FL_SET },
+ { "NULL", TOK_TYPE, SNMP_SYNTAX_NULL },
+ { "INTEGER", TOK_TYPE, SNMP_SYNTAX_INTEGER },
+ { "INTEGER32", TOK_TYPE, SNMP_SYNTAX_INTEGER },
+ { "UNSIGNED32", TOK_TYPE, SNMP_SYNTAX_GAUGE },
+ { "OCTETSTRING", TOK_TYPE, SNMP_SYNTAX_OCTETSTRING },
+ { "IPADDRESS", TOK_TYPE, SNMP_SYNTAX_IPADDRESS },
+ { "OID", TOK_TYPE, SNMP_SYNTAX_OID },
+ { "TIMETICKS", TOK_TYPE, SNMP_SYNTAX_TIMETICKS },
+ { "COUNTER", TOK_TYPE, SNMP_SYNTAX_COUNTER },
+ { "GAUGE", TOK_TYPE, SNMP_SYNTAX_GAUGE },
+ { "COUNTER64", TOK_TYPE, SNMP_SYNTAX_COUNTER64 },
+ { "ENUM", TOK_ENUM, SNMP_SYNTAX_INTEGER },
+ { "BITS", TOK_BITS, SNMP_SYNTAX_OCTETSTRING },
+ { "typedef", TOK_TYPEDEF, 0 },
+ { "include", TOK_INCLUDE, 0 },
+ { NULL, 0, 0 }
+};
+
+struct {
+ /* Current OID type, regarding table membership. */
+ enum snmp_tbl_entry tbl_type;
+ /* A pointer to a structure in table list to add to its members. */
+ struct snmp_index_entry *table_idx;
+} table_data;
+
+struct asn_oid current_oid;
+char nexttok[MAXSTR];
+u_long val; /* integer values */
+int32_t all_cond; /* all conditions are true */
+int32_t saved_token = -1;
+
+/* Prepare the global data before parsing a new file. */
+static void
+snmp_import_init(struct asn_oid *append)
+{
+ memset(&table_data, 0, sizeof(table_data));
+ memset(&current_oid, 0, sizeof(struct asn_oid));
+ memset(nexttok, 0, MAXSTR);
+
+ if (append != NULL)
+ asn_append_oid(&current_oid, append);
+
+ all_cond = 0;
+ val = 0;
+ saved_token = -1;
+}
+
+static int32_t
+gettoken(struct snmp_toolinfo *snmptoolctx)
+{
+ int c;
+ struct enum_type *t;
+
+ if (saved_token != -1) {
+ c = saved_token;
+ saved_token = -1;
+ return (c);
+ }
+
+ again:
+ /*
+ * Skip any whitespace before the next token.
+ */
+ while ((c = tgetc()) != EOF) {
+ if (c == '\n')
+ input->lno++;
+ if (!isspace(c))
+ break;
+ }
+ if (c == EOF)
+ return (TOK_EOF);
+
+ if (!isascii(c)) {
+ warnx("unexpected character %#2x", (u_int) c);
+ return (TOK_ERR);
+ }
+
+ /*
+ * Skip comments.
+ */
+ if (c == '#') {
+ while ((c = tgetc()) != EOF) {
+ if (c == '\n') {
+ input->lno++;
+ goto again;
+ }
+ }
+ warnx("unexpected EOF in comment");
+ return (TOK_ERR);
+ }
+
+ /*
+ * Single character tokens.
+ */
+ if (strchr("():|", c) != NULL)
+ return (c);
+
+ if (c == '"' || c == '<') {
+ int32_t end = c;
+ size_t n = 0;
+
+ val = 1;
+ if (c == '<') {
+ val = 0;
+ end = '>';
+ }
+
+ while ((c = tgetc()) != EOF) {
+ if (c == end)
+ break;
+ if (n == sizeof(nexttok) - 1) {
+ nexttok[n++] = '\0';
+ warnx("filename too long '%s...'", nexttok);
+ return (TOK_ERR);
+ }
+ nexttok[n++] = c;
+ }
+ nexttok[n++] = '\0';
+ return (TOK_FILENAME);
+ }
+
+ /*
+ * Sort out numbers.
+ */
+ if (isdigit(c)) {
+ size_t n = 0;
+ nexttok[n++] = c;
+ while ((c = tgetc()) != EOF) {
+ if (!isdigit(c)) {
+ if (tungetc(c) < 0)
+ return (TOK_ERR);
+ break;
+ }
+ if (n == sizeof(nexttok) - 1) {
+ nexttok[n++] = '\0';
+ warnx("number too long '%s...'", nexttok);
+ return (TOK_ERR);
+ }
+ nexttok[n++] = c;
+ }
+ nexttok[n++] = '\0';
+ sscanf(nexttok, "%lu", &val);
+ return (TOK_NUM);
+ }
+
+ /*
+ * So that has to be a string.
+ */
+ if (isalpha(c) || c == '_' || c == '-') {
+ size_t n = 0;
+ nexttok[n++] = c;
+ while ((c = tgetc()) != EOF) {
+ if (!isalnum(c) && c != '_' && c != '-') {
+ if (tungetc (c) < 0)
+ return (TOK_ERR);
+ break;
+ }
+ if (n == sizeof(nexttok) - 1) {
+ nexttok[n++] = '\0';
+ warnx("string too long '%s...'", nexttok);
+ return (TOK_ERR);
+ }
+ nexttok[n++] = c;
+ }
+ nexttok[n++] = '\0';
+
+ /*
+ * Keywords.
+ */
+ for (c = 0; keywords[c].str != NULL; c++)
+ if (strcmp(keywords[c].str, nexttok) == 0) {
+ val = keywords[c].val;
+ return (keywords[c].tok);
+ }
+
+ if ((t = snmp_enumtc_lookup(snmptoolctx, nexttok)) != NULL) {
+ val = t->syntax;
+ return (TOK_DEFTYPE);
+ }
+
+ return (TOK_STR);
+ }
+
+ if (isprint(c))
+ warnx("%u: unexpected character '%c'", input->lno, c);
+ else
+ warnx("%u: unexpected character 0x%02x", input->lno, (u_int) c);
+
+ return (TOK_ERR);
+}
+
+/*
+ * Update table information.
+ */
+static struct snmp_index_entry *
+snmp_import_update_table(enum snmp_tbl_entry te, struct snmp_index_entry *tbl)
+{
+ switch (te) {
+ case ENTRY_NONE:
+ if (table_data.tbl_type == ENTRY_NONE)
+ return (NULL);
+ if (table_data.tbl_type == ENTRY_INDEX)
+ table_data.table_idx = NULL;
+ table_data.tbl_type--;
+ return (NULL);
+
+ case ENTRY_INDEX:
+ if (tbl == NULL)
+ warnx("No table_index to add!!!");
+ table_data.table_idx = tbl;
+ table_data.tbl_type = ENTRY_INDEX;
+ return (tbl);
+
+ case ENTRY_DATA:
+ if (table_data.tbl_type == ENTRY_INDEX) {
+ table_data.tbl_type = ENTRY_DATA;
+ return (table_data.table_idx);
+ }
+ return (NULL);
+
+ default:
+ /* NOTREACHED */
+ warnx("Unknown table entry type!!!");
+ break;
+ }
+
+ return (NULL);
+}
+
+static int32_t
+parse_enum(struct snmp_toolinfo *snmptoolctx, enum tok *tok,
+ struct enum_pairs *enums)
+{
+ while ((*tok = gettoken(snmptoolctx)) == TOK_STR) {
+ if (enum_pair_insert(enums, val, nexttok) < 0)
+ return (-1);
+ if ((*tok = gettoken(snmptoolctx)) != TOK_NUM)
+ break;
+ }
+
+ if (*tok != ')') {
+ warnx("')' at end of enums");
+ return (-1);
+ }
+
+ return (1);
+}
+
+static int32_t
+parse_subtype(struct snmp_toolinfo *snmptoolctx, enum tok *tok,
+ enum snmp_tc *tc)
+{
+ if ((*tok = gettoken(snmptoolctx)) != TOK_STR) {
+ warnx("subtype expected after '|'");
+ return (-1);
+ }
+
+ *tc = snmp_get_tc(nexttok);
+ *tok = gettoken(snmptoolctx);
+
+ return (1);
+}
+
+static int32_t
+parse_type(struct snmp_toolinfo *snmptoolctx, enum tok *tok,
+ enum snmp_tc *tc, struct enum_pairs **snmp_enum)
+{
+ int32_t syntax, mem;
+
+ syntax = val;
+ *tc = 0;
+
+ if (*tok == TOK_ENUM || *tok == TOK_BITS) {
+ if (*snmp_enum == NULL) {
+ if ((*snmp_enum = enum_pairs_init()) == NULL)
+ return (-1);
+ mem = 1;
+ *tc = SNMP_TC_OWN;
+ } else
+ mem = 0;
+
+ if (gettoken(snmptoolctx) != '(') {
+ warnx("'(' expected after ENUM/BITS");
+ return (-1);
+ }
+
+ if ((*tok = gettoken(snmptoolctx)) != TOK_NUM) {
+ warnx("need value for ENUM//BITS");
+ if (mem == 1) {
+ free(*snmp_enum);
+ *snmp_enum = NULL;
+ }
+ return (-1);
+ }
+
+ if (parse_enum(snmptoolctx, tok, *snmp_enum) < 0) {
+ enum_pairs_free(*snmp_enum);
+ *snmp_enum = NULL;
+ return (-1);
+ }
+
+ *tok = gettoken(snmptoolctx);
+
+ } else if (*tok == TOK_DEFTYPE) {
+ struct enum_type *t;
+
+ *tc = 0;
+ t = snmp_enumtc_lookup(snmptoolctx, nexttok);
+ if (t != NULL)
+ *snmp_enum = t->snmp_enum;
+
+ *tok = gettoken(snmptoolctx);
+
+ } else {
+ if ((*tok = gettoken(snmptoolctx)) == '|') {
+ if (parse_subtype(snmptoolctx, tok, tc) < 0)
+ return (-1);
+ }
+ }
+
+ return (syntax);
+}
+
+static int32_t
+snmp_import_head(struct snmp_toolinfo *snmptoolctx)
+{
+ enum tok tok;
+
+ if ((tok = gettoken(snmptoolctx)) == '(')
+ tok = gettoken(snmptoolctx);
+
+ if (tok != TOK_NUM || val > ASN_MAXID ) {
+ warnx("Suboid expected - line %d", input->lno);
+ return (-1);
+ }
+
+ if (gettoken(snmptoolctx) != TOK_STR) {
+ warnx("Node name expected at line %d", input->lno);
+ return (-1);
+ }
+
+ return (1);
+}
+
+static int32_t
+snmp_import_table(struct snmp_toolinfo *snmptoolctx, struct snmp_oid2str *obj)
+{
+ int32_t i;
+ enum snmp_tc tc;
+ enum tok tok;
+ struct snmp_index_entry *entry;
+
+ if ((entry = malloc(sizeof(struct snmp_index_entry))) == NULL) {
+ syslog(LOG_ERR, "malloc() failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ memset(entry, 0, sizeof(struct snmp_index_entry));
+ STAILQ_INIT(&(entry->index_list));
+
+ for (i = 0, tok = gettoken(snmptoolctx); i < SNMP_INDEXES_MAX; i++) {
+ int32_t syntax;
+ struct enum_pairs *enums = NULL;
+
+ if (tok != TOK_TYPE && tok != TOK_DEFTYPE && tok != TOK_ENUM &&
+ tok != TOK_BITS)
+ break;
+
+ if ((syntax = parse_type(snmptoolctx, &tok, &tc, &enums)) < 0) {
+ enum_pairs_free(enums);
+ snmp_index_listfree(&(entry->index_list));
+ free(entry);
+ return (-1);
+ }
+
+ if (snmp_syntax_insert(&(entry->index_list), enums, syntax,
+ tc) < 0) {
+ snmp_index_listfree(&(entry->index_list));
+ enum_pairs_free(enums);
+ free(entry);
+ return (-1);
+ }
+ }
+
+ if (i == 0 || i > SNMP_INDEXES_MAX) {
+ warnx("Bad number of indexes at line %d", input->lno);
+ snmp_index_listfree(&(entry->index_list));
+ free(entry);
+ return (-1);
+ }
+
+ if (tok != TOK_STR) {
+ warnx("String expected after indexes at line %d", input->lno);
+ snmp_index_listfree(&(entry->index_list));
+ free(entry);
+ return (-1);
+ }
+
+ entry->string = obj->string;
+ entry->strlen = obj->strlen;
+ asn_append_oid(&(entry->var), &(obj->var));
+
+ if ((i = snmp_table_insert(snmptoolctx, entry)) < 0) {
+ snmp_index_listfree(&(entry->index_list));
+ free(entry);
+ return (-1);
+ } else if (i == 0) {
+ /* Same entry already present in lists. */
+ free(entry->string);
+ free(entry);
+ }
+
+ (void) snmp_import_update_table(ENTRY_INDEX, entry);
+
+ return (1);
+}
+
+/*
+ * Read everything after the syntax type that is certainly a leaf OID info.
+ */
+static int32_t
+snmp_import_leaf(struct snmp_toolinfo *snmptoolctx, enum tok *tok,
+ struct snmp_oid2str *oid2str)
+{
+ int32_t i, syntax;
+
+ if ((syntax = parse_type(snmptoolctx, tok, &(oid2str->tc), &(oid2str->snmp_enum)))
+ < 0)
+ return(-1);
+
+ oid2str->syntax = syntax;
+ /*
+ * That is the name of the function, corresponding to the entry.
+ * It is used by bsnmpd, but is not interesting for us.
+ */
+ if (*tok == TOK_STR)
+ *tok = gettoken(snmptoolctx);
+
+ for (i = 0; i < SNMP_ACCESS_GETSET && *tok == TOK_ACCESS; i++) {
+ oid2str->access |= (uint32_t) val;
+ *tok = gettoken(snmptoolctx);
+ }
+
+ if (*tok != ')') {
+ warnx("')' expected at end of line %d", input->lno);
+ return (-1);
+ }
+
+ oid2str->table_idx = snmp_import_update_table(ENTRY_DATA, NULL);
+
+ if ((i = snmp_leaf_insert(snmptoolctx, oid2str)) < 0) {
+ warnx("Error adding leaf %s to list", oid2str->string);
+ return (-1);
+ }
+
+ /*
+ * Same entry is already present in the mapping lists and
+ * the new one was not inserted.
+ */
+ if (i == 0) {
+ free(oid2str->string);
+ free(oid2str);
+ }
+
+ (void) snmp_import_update_table(ENTRY_NONE, NULL);
+
+ return (1);
+}
+
+static int32_t
+snmp_import_object(struct snmp_toolinfo *snmptoolctx)
+{
+ char *string;
+ int i;
+ enum tok tok;
+ struct snmp_oid2str *oid2str;
+
+ if (snmp_import_head(snmptoolctx) < 0)
+ return (-1);
+
+ if ((oid2str = malloc(sizeof(struct snmp_oid2str))) == NULL) {
+ syslog(LOG_ERR, "malloc() failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ if ((string = malloc(strlen(nexttok) + 1)) == NULL) {
+ syslog(LOG_ERR, "malloc() failed: %s", strerror(errno));
+ free(oid2str);
+ return (-1);
+ }
+
+ memset(oid2str, 0, sizeof(struct snmp_oid2str));
+ strlcpy(string, nexttok, strlen(nexttok) + 1);
+ oid2str->string = string;
+ oid2str->strlen = strlen(nexttok);
+
+ asn_append_oid(&(oid2str->var), &(current_oid));
+ if (snmp_suboid_append(&(oid2str->var), (asn_subid_t) val) < 0)
+ goto error;
+
+ /*
+ * Prepared the entry - now figure out where to insert it.
+ * After the object we have following options:
+ * 1) new line, blank, ) - then it is an enum oid -> snmp_enumlist;
+ * 2) new line , ( - nonleaf oid -> snmp_nodelist;
+ * 2) ':' - table entry - a variable length SYNTAX_TYPE (one or more)
+ * may follow and second string must end line -> snmp_tablelist;
+ * 3) OID , string ) - this is a trap entry or a leaf -> snmp_oidlist;
+ * 4) SYNTAX_TYPE, string (not always), get/set modifier - always last
+ * and )- this is definitely a leaf.
+ */
+
+ switch (tok = gettoken(snmptoolctx)) {
+ case ')':
+ if ((i = snmp_enum_insert(snmptoolctx, oid2str)) < 0)
+ goto error;
+ if (i == 0) {
+ free(oid2str->string);
+ free(oid2str);
+ }
+ return (1);
+
+ case '(':
+ if (snmp_suboid_append(&current_oid, (asn_subid_t) val) < 0)
+ goto error;
+
+ /*
+ * Ignore the error for nodes since the .def files currently
+ * contain different strings for 1.3.6.1.2.1 - mibII. Only make
+ * sure the memory is freed and don't complain.
+ */
+ if ((i = snmp_node_insert(snmptoolctx, oid2str)) <= 0) {
+ free(string);
+ free(oid2str);
+ }
+ return (snmp_import_object(snmptoolctx));
+
+ case ':':
+ if (snmp_suboid_append(&current_oid, (asn_subid_t) val) < 0)
+ goto error;
+ if (snmp_import_table(snmptoolctx, oid2str) < 0)
+ goto error;
+ /*
+ * A different table entry type was malloced and the data is
+ * contained there.
+ */
+ free(oid2str);
+ return (1);
+
+ case TOK_TYPE:
+ /* FALLTHROUGH */
+ case TOK_DEFTYPE:
+ /* FALLTHROUGH */
+ case TOK_ENUM:
+ /* FALLTHROUGH */
+ case TOK_BITS:
+ if (snmp_import_leaf(snmptoolctx, &tok, oid2str) < 0)
+ goto error;
+ return (1);
+
+ default:
+ warnx("Unexpected token at line %d - %s", input->lno,
+ input->fname);
+ break;
+ }
+
+error:
+ snmp_mapping_entryfree(oid2str);
+
+ return (-1);
+}
+
+static int32_t
+snmp_import_tree(struct snmp_toolinfo *snmptoolctx, enum tok *tok)
+{
+ while (*tok != TOK_EOF) {
+ switch (*tok) {
+ case TOK_ERR:
+ return (-1);
+ case '(':
+ if (snmp_import_object(snmptoolctx) < 0)
+ return (-1);
+ break;
+ case ')':
+ if (snmp_suboid_pop(&current_oid) < 0)
+ return (-1);
+ (void) snmp_import_update_table(ENTRY_NONE, NULL);
+ break;
+ default:
+ /* Anything else here would be illegal. */
+ return (-1);
+ }
+ *tok = gettoken(snmptoolctx);
+ }
+
+ return (0);
+}
+
+static int32_t
+snmp_import_top(struct snmp_toolinfo *snmptoolctx, enum tok *tok)
+{
+ enum snmp_tc tc;
+ struct enum_type *t;
+
+ if (*tok == '(')
+ return (snmp_import_tree(snmptoolctx, tok));
+
+ if (*tok == TOK_TYPEDEF) {
+ if ((*tok = gettoken(snmptoolctx)) != TOK_STR) {
+ warnx("type name expected after typedef - %s",
+ input->fname);
+ return (-1);
+ }
+
+ t = snmp_enumtc_init(nexttok);
+
+ *tok = gettoken(snmptoolctx);
+ t->is_enum = (*tok == TOK_ENUM);
+ t->is_bits = (*tok == TOK_BITS);
+ t->syntax = parse_type(snmptoolctx, tok, &tc, &(t->snmp_enum));
+ snmp_enumtc_insert(snmptoolctx, t);
+
+ return (1);
+ }
+
+ if (*tok == TOK_INCLUDE) {
+ int i;
+
+ *tok = gettoken(snmptoolctx);
+ if (*tok != TOK_FILENAME) {
+ warnx("filename expected in include directive - %s",
+ nexttok);
+ return (-1);
+ }
+
+ if (( i = add_filename(snmptoolctx, nexttok, NULL, 1)) == 0) {
+ *tok = gettoken(snmptoolctx);
+ return (1);
+ }
+
+ if (i == -1)
+ return (-1);
+
+ input_fopen(nexttok);
+ *tok = gettoken(snmptoolctx);
+ return (1);
+ }
+
+ warnx("'(' or 'typedef' expected - %s", nexttok);
+ return (-1);
+}
+
+static int32_t
+snmp_import(struct snmp_toolinfo *snmptoolctx)
+{
+ int i;
+ enum tok tok;
+
+ tok = gettoken(snmptoolctx);
+
+ do
+ i = snmp_import_top(snmptoolctx, &tok);
+ while (i > 0);
+
+ return (i);
+}
+
+/*
+ * Read a .def file and import oid<->string mapping.
+ * Mappings are inserted into a global structure containing list for each OID
+ * syntax type.
+ */
+int32_t
+snmp_import_file(struct snmp_toolinfo *snmptoolctx, struct fname *file)
+{
+ int idx;
+
+ snmp_import_init(&(file->cut));
+ input_fopen(file->name);
+ if ((idx = snmp_import(snmptoolctx)) < 0)
+ warnx("Failed to read mappings from file %s", file->name);
+
+ input_close();
+
+ return (idx);
+}
diff --git a/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmpmap.c b/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmpmap.c
new file mode 100644
index 0000000..4f71c58
--- /dev/null
+++ b/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmpmap.c
@@ -0,0 +1,1018 @@
+/*-
+ * Copyright (c) 2006 The FreeBSD Project
+ * All rights reserved.
+ *
+ * Author: Shteryana Shopova <syrinx@FreeBSD.org>
+ *
+ * Redistribution of this software and documentation and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ *
+ * 1. Redistributions of source code or documentation must retain the above
+ * copyright notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/queue.h>
+#include <sys/uio.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <bsnmp/asn1.h>
+#include <bsnmp/snmp.h>
+#include "bsnmptc.h"
+#include "bsnmptools.h"
+
+extern int _bsnmptools_debug;
+#define DEBUG if (_bsnmptools_debug) fprintf
+
+/* Allocate memory and initialize list. */
+struct snmp_mappings *
+snmp_mapping_init(void)
+{
+ struct snmp_mappings *m;
+
+ if ((m = malloc(sizeof(struct snmp_mappings))) == NULL) {
+ syslog(LOG_ERR, "malloc() failed: %s", strerror(errno));
+ return (NULL);
+ }
+
+ memset(m, 0, sizeof(struct snmp_mappings));
+ return (m);
+}
+
+#define snmp_nodelist mappings->nodelist
+#define snmp_intlist mappings->intlist
+#define snmp_octlist mappings->octlist
+#define snmp_oidlist mappings->oidlist
+#define snmp_iplist mappings->iplist
+#define snmp_ticklist mappings->ticklist
+#define snmp_cntlist mappings->cntlist
+#define snmp_gaugelist mappings->gaugelist
+#define snmp_cnt64list mappings->cnt64list
+#define snmp_enumlist mappings->enumlist
+#define snmp_tablelist mappings->tablelist
+#define snmp_tclist mappings->tclist
+
+void
+enum_pairs_free(struct enum_pairs *headp)
+{
+ struct enum_pair *e;
+
+ if (headp == NULL)
+ return;
+
+ while ((e = STAILQ_FIRST(headp)) != NULL) {
+ STAILQ_REMOVE_HEAD(headp, link);
+
+ if (e->enum_str)
+ free(e->enum_str);
+ free(e);
+ }
+
+ free(headp);
+}
+
+void
+snmp_mapping_entryfree(struct snmp_oid2str *entry)
+{
+ if (entry->string)
+ free(entry->string);
+
+ if (entry->tc == SNMP_TC_OWN)
+ enum_pairs_free(entry->snmp_enum);
+
+ free(entry);
+}
+
+static void
+snmp_mapping_listfree(struct snmp_mapping *headp)
+{
+ struct snmp_oid2str *p;
+
+ while ((p = SLIST_FIRST(headp)) != NULL) {
+ SLIST_REMOVE_HEAD(headp, link);
+
+ if (p->string)
+ free(p->string);
+
+ if (p->tc == SNMP_TC_OWN)
+ enum_pairs_free(p->snmp_enum);
+ free(p);
+ }
+
+ SLIST_INIT(headp);
+}
+
+void
+snmp_index_listfree(struct snmp_idxlist *headp)
+{
+ struct index *i;
+
+ while ((i = STAILQ_FIRST(headp)) != NULL) {
+ STAILQ_REMOVE_HEAD(headp, link);
+ if (i->tc == SNMP_TC_OWN)
+ enum_pairs_free(i->snmp_enum);
+ free(i);
+ }
+
+ STAILQ_INIT(headp);
+}
+
+static void
+snmp_mapping_table_listfree(struct snmp_table_index *headp)
+{
+ struct snmp_index_entry *t;
+
+ while ((t = SLIST_FIRST(headp)) != NULL) {
+ SLIST_REMOVE_HEAD(headp, link);
+
+ if (t->string)
+ free(t->string);
+
+ snmp_index_listfree(&(t->index_list));
+ free(t);
+ }
+}
+
+static void
+snmp_enumtc_listfree(struct snmp_enum_tc *headp)
+{
+ struct enum_type *t;
+
+ while ((t = SLIST_FIRST(headp)) != NULL) {
+ SLIST_REMOVE_HEAD(headp, link);
+
+ if (t->name)
+ free(t->name);
+ enum_pairs_free(t->snmp_enum);
+ free(t);
+ }
+}
+
+int
+snmp_mapping_free(struct snmp_toolinfo *snmptoolctx)
+{
+ if (snmptoolctx == NULL || snmptoolctx->mappings == NULL)
+ return (-1);
+
+ snmp_mapping_listfree(&snmptoolctx->snmp_nodelist);
+ snmp_mapping_listfree(&snmptoolctx->snmp_intlist);
+ snmp_mapping_listfree(&snmptoolctx->snmp_octlist);
+ snmp_mapping_listfree(&snmptoolctx->snmp_oidlist);
+ snmp_mapping_listfree(&snmptoolctx->snmp_iplist);
+ snmp_mapping_listfree(&snmptoolctx->snmp_ticklist);
+ snmp_mapping_listfree(&snmptoolctx->snmp_cntlist);
+ snmp_mapping_listfree(&snmptoolctx->snmp_gaugelist);
+ snmp_mapping_listfree(&snmptoolctx->snmp_cnt64list);
+ snmp_mapping_listfree(&snmptoolctx->snmp_enumlist);
+ snmp_mapping_table_listfree(&snmptoolctx->snmp_tablelist);
+ snmp_enumtc_listfree(&snmptoolctx->snmp_tclist);
+ free(snmptoolctx->mappings);
+
+ return (0);
+}
+
+static void
+snmp_dump_enumpairs(struct enum_pairs *headp)
+{
+ struct enum_pair *entry;
+
+ if (headp == NULL)
+ return;
+
+ fprintf(stderr,"enums: ");
+ STAILQ_FOREACH(entry, headp, link)
+ fprintf(stderr,"%d - %s, ", entry->enum_val,
+ (entry->enum_str == NULL)?"NULL":entry->enum_str);
+
+ fprintf(stderr,"; ");
+}
+
+void
+snmp_dump_oid2str(struct snmp_oid2str *entry)
+{
+ char buf[ASN_OIDSTRLEN];
+
+ if (entry != NULL) {
+ memset(buf, 0, sizeof(buf));
+ asn_oid2str_r(&(entry->var), buf);
+ DEBUG(stderr, "%s - %s - %d - %d - %d", buf, entry->string,
+ entry->syntax, entry->access, entry->strlen);
+ snmp_dump_enumpairs(entry->snmp_enum);
+ DEBUG(stderr,"%s \n", (entry->table_idx == NULL)?"No table":
+ entry->table_idx->string);
+ }
+}
+
+static void
+snmp_dump_indexlist(struct snmp_idxlist *headp)
+{
+ struct index *entry;
+
+ if (headp == NULL)
+ return;
+
+ STAILQ_FOREACH(entry, headp, link) {
+ fprintf(stderr,"%d, ", entry->syntax);
+ snmp_dump_enumpairs(entry->snmp_enum);
+ }
+
+ fprintf(stderr,"\n");
+}
+
+/* Initialize the enum pairs list of a oid2str entry. */
+struct enum_pairs *
+enum_pairs_init(void)
+{
+ struct enum_pairs *snmp_enum;
+
+ if ((snmp_enum = malloc(sizeof(struct enum_pairs))) == NULL) {
+ syslog(LOG_ERR, "malloc() failed: %s", strerror(errno));
+ return (NULL);
+ }
+
+ STAILQ_INIT(snmp_enum);
+ return (snmp_enum);
+}
+
+/*
+ * Given a number and string, allocate memory for a (int, string) pair and add
+ * it to the given oid2str mapping entry's enum pairs list.
+ */
+int32_t
+enum_pair_insert(struct enum_pairs *headp, int32_t enum_val, char *enum_str)
+{
+ struct enum_pair *e_new;
+
+ if ((e_new = malloc(sizeof(struct enum_pair))) == NULL) {
+ syslog(LOG_ERR, "malloc() failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ memset(e_new, 0, sizeof(struct enum_pair));
+
+ if ((e_new->enum_str = malloc(strlen(enum_str) + 1)) == NULL) {
+ syslog(LOG_ERR, "malloc() failed: %s", strerror(errno));
+ free(e_new);
+ return (-1);
+ }
+
+ e_new->enum_val = enum_val;
+ strlcpy(e_new->enum_str, enum_str, strlen(enum_str) + 1);
+ STAILQ_INSERT_TAIL(headp, e_new, link);
+
+ return (1);
+
+}
+
+/*
+ * Insert an entry in a list - entries are lexicographicaly order by asn_oid.
+ * Returns 1 on success, -1 if list is not initialized, 0 if a matching oid already
+ * exists. Error cheking is left to calling function.
+ */
+static int
+snmp_mapping_insert(struct snmp_mapping *headp, struct snmp_oid2str *entry)
+{
+ int32_t rc;
+ struct snmp_oid2str *temp, *prev;
+
+ if (entry == NULL)
+ return(-1);
+
+ if ((prev = SLIST_FIRST(headp)) == NULL ||
+ asn_compare_oid(&(entry->var), &(prev->var)) < 0) {
+ SLIST_INSERT_HEAD(headp, entry, link);
+ return (1);
+ } else
+ rc = -1; /* Make the compiler happy. */
+
+ SLIST_FOREACH(temp, headp, link) {
+ if ((rc = asn_compare_oid(&(entry->var), &(temp->var))) <= 0)
+ break;
+ prev = temp;
+ rc = -1;
+ }
+
+ switch (rc) {
+ case 0:
+ /* Ops, matching OIDs - hope the rest info also matches. */
+ if (strncmp(temp->string, entry->string, entry->strlen)) {
+ syslog(LOG_INFO, "Matching OIDs with different string "
+ "mappings: old - %s, new - %s", temp->string,
+ entry->string);
+ return (-1);
+ }
+ /*
+ * Ok, we have that already.
+ * As long as the strings match - don't complain.
+ */
+ return (0);
+
+ case 1:
+ SLIST_INSERT_AFTER(temp, entry, link);
+ break;
+
+ case -1:
+ SLIST_INSERT_AFTER(prev, entry, link);
+ break;
+
+ default:
+ /* NOTREACHED */
+ return (-1);
+ }
+
+ return (1);
+}
+
+int32_t
+snmp_node_insert(struct snmp_toolinfo *snmptoolctx, struct snmp_oid2str *entry)
+{
+ if (snmptoolctx != NULL && snmptoolctx->mappings)
+ return (snmp_mapping_insert(&snmptoolctx->snmp_nodelist,entry));
+
+ return (-1);
+}
+
+static int32_t
+snmp_int_insert(struct snmp_toolinfo *snmptoolctx, struct snmp_oid2str *entry)
+{
+ if (snmptoolctx != NULL && snmptoolctx->mappings)
+ return (snmp_mapping_insert(&snmptoolctx->snmp_intlist,entry));
+
+ return (-1);
+}
+
+static int32_t
+snmp_oct_insert(struct snmp_toolinfo *snmptoolctx, struct snmp_oid2str *entry)
+{
+ if (snmptoolctx != NULL && snmptoolctx->mappings)
+ return (snmp_mapping_insert(&snmptoolctx->snmp_octlist,entry));
+
+ return (-1);
+}
+
+static int32_t
+snmp_oid_insert(struct snmp_toolinfo *snmptoolctx, struct snmp_oid2str *entry)
+{
+ if (snmptoolctx != NULL && snmptoolctx->mappings)
+ return (snmp_mapping_insert(&snmptoolctx->snmp_oidlist,entry));
+
+ return (-1);
+}
+
+static int32_t
+snmp_ip_insert(struct snmp_toolinfo *snmptoolctx, struct snmp_oid2str *entry)
+{
+ if (snmptoolctx != NULL && snmptoolctx->mappings)
+ return (snmp_mapping_insert(&snmptoolctx->snmp_iplist,entry));
+
+ return (-1);
+}
+
+static int32_t
+snmp_tick_insert(struct snmp_toolinfo *snmptoolctx, struct snmp_oid2str *entry)
+{
+ if (snmptoolctx != NULL && snmptoolctx->mappings)
+ return (snmp_mapping_insert(&snmptoolctx->snmp_ticklist,entry));
+
+ return (-1);
+}
+
+static int32_t
+snmp_cnt_insert(struct snmp_toolinfo *snmptoolctx, struct snmp_oid2str *entry)
+{
+ if (snmptoolctx != NULL && snmptoolctx->mappings)
+ return (snmp_mapping_insert(&snmptoolctx->snmp_cntlist,entry));
+
+ return (-1);
+}
+
+static int32_t
+snmp_gauge_insert(struct snmp_toolinfo *snmptoolctx, struct snmp_oid2str *entry)
+{
+ if (snmptoolctx != NULL && snmptoolctx->mappings)
+ return (snmp_mapping_insert(&snmptoolctx->snmp_gaugelist,entry));
+
+ return (-1);
+}
+
+static int32_t
+snmp_cnt64_insert(struct snmp_toolinfo *snmptoolctx, struct snmp_oid2str *entry)
+{
+ if (snmptoolctx != NULL && snmptoolctx->mappings)
+ return (snmp_mapping_insert(&snmptoolctx->snmp_cnt64list,entry));
+
+ return (-1);
+}
+
+int32_t
+snmp_enum_insert(struct snmp_toolinfo *snmptoolctx, struct snmp_oid2str *entry)
+{
+ if (snmptoolctx != NULL && snmptoolctx->mappings)
+ return (snmp_mapping_insert(&snmptoolctx->snmp_enumlist,entry));
+
+ return (-1);
+}
+
+int32_t
+snmp_leaf_insert(struct snmp_toolinfo *snmptoolctx, struct snmp_oid2str *entry)
+{
+ switch (entry->syntax) {
+ case SNMP_SYNTAX_INTEGER:
+ return (snmp_int_insert(snmptoolctx, entry));
+ case SNMP_SYNTAX_OCTETSTRING:
+ return (snmp_oct_insert(snmptoolctx, entry));
+ case SNMP_SYNTAX_OID:
+ return (snmp_oid_insert(snmptoolctx, entry));
+ case SNMP_SYNTAX_IPADDRESS:
+ return (snmp_ip_insert(snmptoolctx, entry));
+ case SNMP_SYNTAX_COUNTER:
+ return (snmp_cnt_insert(snmptoolctx, entry));
+ case SNMP_SYNTAX_GAUGE:
+ return (snmp_gauge_insert(snmptoolctx, entry));
+ case SNMP_SYNTAX_TIMETICKS:
+ return (snmp_tick_insert(snmptoolctx, entry));
+ case SNMP_SYNTAX_COUNTER64:
+ return (snmp_cnt64_insert(snmptoolctx, entry));
+ default:
+ break;
+ }
+
+ return (-1);
+}
+
+static int32_t
+snmp_index_insert(struct snmp_idxlist *headp, struct index *idx)
+{
+ if (headp == NULL || idx == NULL)
+ return (-1);
+
+ STAILQ_INSERT_TAIL(headp, idx, link);
+ return (1);
+}
+
+int32_t
+snmp_syntax_insert(struct snmp_idxlist *headp, struct enum_pairs *enums,
+ enum snmp_syntax syntax, enum snmp_tc tc)
+{
+ struct index *idx;
+
+ if ((idx = malloc(sizeof(struct index))) == NULL) {
+ syslog(LOG_ERR, "malloc() failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ memset(idx, 0, sizeof(struct index));
+
+ if (snmp_index_insert(headp, idx) < 0) {
+ free(idx);
+ return (-1);
+ }
+
+ idx->syntax = syntax;
+ idx->snmp_enum = enums;
+ idx->tc = tc;
+
+ return (1);
+}
+
+int32_t
+snmp_table_insert(struct snmp_toolinfo *snmptoolctx,
+ struct snmp_index_entry *entry)
+{
+ int32_t rc;
+ struct snmp_index_entry *temp, *prev;
+
+ if (snmptoolctx == NULL || snmptoolctx->mappings == NULL ||
+ entry == NULL)
+ return(-1);
+
+ if ((prev = SLIST_FIRST(&snmptoolctx->snmp_tablelist)) == NULL ||
+ asn_compare_oid(&(entry->var), &(prev->var)) < 0) {
+ SLIST_INSERT_HEAD(&snmptoolctx->snmp_tablelist, entry, link);
+ return (1);
+ } else
+ rc = -1; /* Make the compiler happy. */
+
+ SLIST_FOREACH(temp, &snmptoolctx->snmp_tablelist, link) {
+ if ((rc = asn_compare_oid(&(entry->var), &(temp->var))) <= 0)
+ break;
+ prev = temp;
+ rc = -1;
+ }
+
+ switch (rc) {
+ case 0:
+ /* Ops, matching OIDs - hope the rest info also matches. */
+ if (strncmp(temp->string, entry->string, entry->strlen)) {
+ syslog(LOG_INFO, "Matching OIDs with different string "
+ "mapping - old - %s, new - %s", temp->string,
+ entry->string);
+ return (-1);
+ }
+ return(0);
+
+ case 1:
+ SLIST_INSERT_AFTER(temp, entry, link);
+ break;
+
+ case -1:
+ SLIST_INSERT_AFTER(prev, entry, link);
+ break;
+
+ default:
+ /* NOTREACHED */
+ return (-1);
+ }
+
+ return (1);
+}
+
+struct enum_type *
+snmp_enumtc_init(char *name)
+{
+ struct enum_type *enum_tc;
+
+ if ((enum_tc = malloc(sizeof(struct enum_type))) == NULL) {
+ syslog(LOG_ERR, "malloc() failed: %s", strerror(errno));
+ return (NULL);
+ }
+
+ memset(enum_tc, 0, sizeof(struct enum_type));
+ if ((enum_tc->name = malloc(strlen(name) + 1)) == NULL) {
+ syslog(LOG_ERR, "malloc() failed: %s", strerror(errno));
+ free(enum_tc);
+ return (NULL);
+ }
+ strlcpy(enum_tc->name, name, strlen(name) + 1);
+
+ return (enum_tc);
+}
+
+void
+snmp_enumtc_free(struct enum_type *tc)
+{
+ if (tc->name)
+ free(tc->name);
+ if (tc->snmp_enum)
+ enum_pairs_free(tc->snmp_enum);
+ free(tc);
+}
+
+void
+snmp_enumtc_insert(struct snmp_toolinfo *snmptoolctx, struct enum_type *entry)
+{
+ if (snmptoolctx == NULL || snmptoolctx->mappings == NULL)
+ return; /* XXX no error handling? */
+
+ SLIST_INSERT_HEAD(&snmptoolctx->snmp_tclist, entry, link);
+}
+
+struct enum_type *
+snmp_enumtc_lookup(struct snmp_toolinfo *snmptoolctx, char *name)
+{
+ struct enum_type *temp;
+
+ if (snmptoolctx == NULL || snmptoolctx->mappings == NULL)
+ return (NULL);
+
+ SLIST_FOREACH(temp, &snmptoolctx->snmp_tclist, link) {
+ if (strcmp(temp->name, name) == 0)
+ return (temp);
+ }
+ return (NULL);
+}
+
+static void
+snmp_mapping_dumplist(struct snmp_mapping *headp)
+{
+ char buf[ASN_OIDSTRLEN];
+ struct snmp_oid2str *entry;
+
+ if (headp == NULL)
+ return;
+
+ SLIST_FOREACH(entry,headp,link) {
+ memset(buf, 0, sizeof(buf));
+ asn_oid2str_r(&(entry->var), buf);
+ fprintf(stderr, "%s - %s - %d - %d - %d", buf, entry->string,
+ entry->syntax, entry->access ,entry->strlen);
+ fprintf(stderr," - %s \n", (entry->table_idx == NULL)?
+ "No table":entry->table_idx->string);
+ }
+}
+
+static void
+snmp_mapping_dumptable(struct snmp_table_index *headp)
+{
+ char buf[ASN_OIDSTRLEN];
+ struct snmp_index_entry *entry;
+
+ if (headp == NULL)
+ return;
+
+ SLIST_FOREACH(entry, headp, link) {
+ memset(buf, 0, sizeof(buf));
+ asn_oid2str_r(&(entry->var), buf);
+ fprintf(stderr,"%s - %s - %d - ", buf, entry->string,
+ entry->strlen);
+ snmp_dump_indexlist(&(entry->index_list));
+ }
+}
+
+void
+snmp_mapping_dump(struct snmp_toolinfo *snmptoolctx /* int bits */)
+{
+ if (!_bsnmptools_debug)
+ return;
+
+ if (snmptoolctx == NULL) {
+ fprintf(stderr,"No snmptool context!\n");
+ return;
+ }
+
+ if (snmptoolctx->mappings == NULL) {
+ fprintf(stderr,"No mappings!\n");
+ return;
+ }
+
+ fprintf(stderr,"snmp_nodelist:\n");
+ snmp_mapping_dumplist(&snmptoolctx->snmp_nodelist);
+
+ fprintf(stderr,"snmp_intlist:\n");
+ snmp_mapping_dumplist(&snmptoolctx->snmp_intlist);
+
+ fprintf(stderr,"snmp_octlist:\n");
+ snmp_mapping_dumplist(&snmptoolctx->snmp_octlist);
+
+ fprintf(stderr,"snmp_oidlist:\n");
+ snmp_mapping_dumplist(&snmptoolctx->snmp_oidlist);
+
+ fprintf(stderr,"snmp_iplist:\n");
+ snmp_mapping_dumplist(&snmptoolctx->snmp_iplist);
+
+ fprintf(stderr,"snmp_ticklist:\n");
+ snmp_mapping_dumplist(&snmptoolctx->snmp_ticklist);
+
+ fprintf(stderr,"snmp_cntlist:\n");
+ snmp_mapping_dumplist(&snmptoolctx->snmp_cntlist);
+
+ fprintf(stderr,"snmp_gaugelist:\n");
+ snmp_mapping_dumplist(&snmptoolctx->snmp_gaugelist);
+
+ fprintf(stderr,"snmp_cnt64list:\n");
+ snmp_mapping_dumplist(&snmptoolctx->snmp_cnt64list);
+
+ fprintf(stderr,"snmp_enumlist:\n");
+ snmp_mapping_dumplist(&snmptoolctx->snmp_enumlist);
+
+ fprintf(stderr,"snmp_tablelist:\n");
+ snmp_mapping_dumptable(&snmptoolctx->snmp_tablelist);
+}
+
+char *
+enum_string_lookup(struct enum_pairs *headp, int32_t enum_val)
+{
+ struct enum_pair *temp;
+
+ if (headp == NULL)
+ return (NULL);
+
+ STAILQ_FOREACH(temp, headp, link) {
+ if (temp->enum_val == enum_val)
+ return (temp->enum_str);
+ }
+
+ return (NULL);
+}
+
+int32_t
+enum_number_lookup(struct enum_pairs *headp, char *e_str)
+{
+ struct enum_pair *tmp;
+
+ if (headp == NULL)
+ return (-1);
+
+ STAILQ_FOREACH(tmp, headp, link)
+ if (strncmp(tmp->enum_str, e_str, strlen(tmp->enum_str)) == 0)
+ return (tmp->enum_val);
+
+ return (-1);
+}
+
+static int32_t
+snmp_lookuplist_string(struct snmp_mapping *headp, struct snmp_object *s)
+{
+ struct snmp_oid2str *temp;
+
+ if (headp == NULL)
+ return (-1);
+
+ SLIST_FOREACH(temp, headp, link)
+ if (asn_compare_oid(&(temp->var), &(s->val.var)) == 0)
+ break;
+
+ if ((s->info = temp) == NULL)
+ return (-1);
+
+ return (1);
+}
+
+/* provided an asn_oid find the corresponding string for it */
+static int32_t
+snmp_lookup_leaf(struct snmp_mapping *headp, struct snmp_object *s)
+{
+ struct snmp_oid2str *temp;
+
+ if (headp == NULL)
+ return (-1);
+
+ SLIST_FOREACH(temp,headp,link) {
+ if ((asn_compare_oid(&(temp->var), &(s->val.var)) == 0) ||
+ (asn_is_suboid(&(temp->var), &(s->val.var)))) {
+ s->info = temp;
+ return (1);
+ }
+ }
+
+ return (-1);
+}
+
+int32_t
+snmp_lookup_leafstring(struct snmp_toolinfo *snmptoolctx, struct snmp_object *s)
+{
+ if (snmptoolctx == NULL || snmptoolctx->mappings == NULL || s == NULL)
+ return (-1);
+
+ switch (s->val.syntax) {
+ case SNMP_SYNTAX_INTEGER:
+ return (snmp_lookup_leaf(&snmptoolctx->snmp_intlist, s));
+ case SNMP_SYNTAX_OCTETSTRING:
+ return (snmp_lookup_leaf(&snmptoolctx->snmp_octlist, s));
+ case SNMP_SYNTAX_OID:
+ return (snmp_lookup_leaf(&snmptoolctx->snmp_oidlist, s));
+ case SNMP_SYNTAX_IPADDRESS:
+ return (snmp_lookup_leaf(&snmptoolctx->snmp_iplist, s));
+ case SNMP_SYNTAX_COUNTER:
+ return (snmp_lookup_leaf(&snmptoolctx->snmp_cntlist, s));
+ case SNMP_SYNTAX_GAUGE:
+ return (snmp_lookup_leaf(
+ &snmptoolctx->snmp_gaugelist, s));
+ case SNMP_SYNTAX_TIMETICKS:
+ return (snmp_lookup_leaf(
+ &snmptoolctx->snmp_ticklist, s));
+ case SNMP_SYNTAX_COUNTER64:
+ return (snmp_lookup_leaf(
+ &snmptoolctx->snmp_cnt64list, s));
+ case SNMP_SYNTAX_NOSUCHOBJECT:
+ /* FALLTHROUGH */
+ case SNMP_SYNTAX_NOSUCHINSTANCE:
+ /* FALLTHROUGH */
+ case SNMP_SYNTAX_ENDOFMIBVIEW:
+ return (snmp_lookup_allstring(snmptoolctx, s));
+ default:
+ warnx("Unknown syntax - %d", s->val.syntax);
+ break;
+ }
+
+ return (-1);
+}
+
+int32_t
+snmp_lookup_enumstring(struct snmp_toolinfo *snmptoolctx, struct snmp_object *s)
+{
+ if (snmptoolctx == NULL || snmptoolctx->mappings == NULL || s == NULL)
+ return (-1);
+
+ return (snmp_lookuplist_string(&snmptoolctx->snmp_enumlist, s));
+}
+
+int32_t
+snmp_lookup_oidstring(struct snmp_toolinfo *snmptoolctx, struct snmp_object *s)
+{
+ if (snmptoolctx == NULL || snmptoolctx->mappings == NULL || s == NULL)
+ return (-1);
+
+ return (snmp_lookuplist_string(&snmptoolctx->snmp_oidlist, s));
+}
+
+int32_t
+snmp_lookup_nodestring(struct snmp_toolinfo *snmptoolctx, struct snmp_object *s)
+{
+ if (snmptoolctx == NULL || snmptoolctx->mappings == NULL || s == NULL)
+ return (-1);
+
+ return (snmp_lookuplist_string(&snmptoolctx->snmp_nodelist, s));
+}
+
+int32_t
+snmp_lookup_allstring(struct snmp_toolinfo *snmptoolctx, struct snmp_object *s)
+{
+ if (snmptoolctx == NULL || snmptoolctx->mappings == NULL)
+ return (-1);
+
+ if (snmp_lookup_leaf(&snmptoolctx->snmp_intlist, s) > 0)
+ return (1);
+ if (snmp_lookup_leaf(&snmptoolctx->snmp_octlist, s) > 0)
+ return (1);
+ if (snmp_lookup_leaf(&snmptoolctx->snmp_oidlist, s) > 0)
+ return (1);
+ if (snmp_lookup_leaf(&snmptoolctx->snmp_iplist, s) > 0)
+ return (1);
+ if (snmp_lookup_leaf(&snmptoolctx->snmp_cntlist, s) > 0)
+ return (1);
+ if (snmp_lookup_leaf(&snmptoolctx->snmp_gaugelist, s) > 0)
+ return (1);
+ if (snmp_lookup_leaf(&snmptoolctx->snmp_ticklist, s) > 0)
+ return (1);
+ if (snmp_lookup_leaf(&snmptoolctx->snmp_cnt64list, s) > 0)
+ return (1);
+ if (snmp_lookuplist_string(&snmptoolctx->snmp_enumlist, s) > 0)
+ return (1);
+ if (snmp_lookuplist_string(&snmptoolctx->snmp_nodelist, s) > 0)
+ return (1);
+
+ return (-1);
+}
+
+int32_t
+snmp_lookup_nonleaf_string(struct snmp_toolinfo *snmptoolctx,
+ struct snmp_object *s)
+{
+ if (snmptoolctx == NULL)
+ return (-1);
+
+ if (snmp_lookuplist_string(&snmptoolctx->snmp_nodelist, s) > 0)
+ return (1);
+ if (snmp_lookuplist_string(&snmptoolctx->snmp_enumlist, s) > 0)
+ return (1);
+
+ return (-1);
+}
+
+static int32_t
+snmp_lookup_oidlist(struct snmp_mapping *hp, struct snmp_object *s, char *oid)
+{
+ struct snmp_oid2str *temp;
+
+ if (hp == NULL)
+ return (-1);
+
+ SLIST_FOREACH(temp, hp, link) {
+ if (temp->strlen != strlen(oid))
+ continue;
+
+ if (strncmp(temp->string, oid, temp->strlen))
+ continue;
+
+ s->val.syntax = temp->syntax;
+ s->info = temp;
+ asn_append_oid(&(s->val.var), &(temp->var));
+ return (1);
+ }
+
+ return (-1);
+}
+
+static int32_t
+snmp_lookup_tablelist(struct snmp_toolinfo *snmptoolctx,
+ struct snmp_table_index *headp, struct snmp_object *s, char *oid)
+{
+ struct snmp_index_entry *temp;
+
+ if (snmptoolctx == NULL || headp == NULL)
+ return (-1);
+
+ SLIST_FOREACH(temp, headp, link) {
+ if (temp->strlen != strlen(oid))
+ continue;
+
+ if (strncmp(temp->string, oid, temp->strlen))
+ continue;
+
+ /*
+ * Another hack here - if we were given a table name
+ * return the corresponding pointer to it's entry.
+ * That should not change the reponce we'll get.
+ */
+ s->val.syntax = SNMP_SYNTAX_NULL;
+ asn_append_oid(&(s->val.var), &(temp->var));
+ if (snmp_lookup_leaf(&snmptoolctx->snmp_nodelist, s) > 0)
+ return (1);
+ else
+ return (-1);
+ }
+
+ return (-1);
+}
+
+int32_t
+snmp_lookup_oidall(struct snmp_toolinfo *snmptoolctx, struct snmp_object *s,
+ char *oid)
+{
+ if (snmptoolctx == NULL || s == NULL || oid == NULL)
+ return (-1);
+
+ if (snmp_lookup_oidlist(&snmptoolctx->snmp_intlist, s, oid) > 0)
+ return (1);
+ if (snmp_lookup_oidlist(&snmptoolctx->snmp_octlist, s, oid) > 0)
+ return (1);
+ if (snmp_lookup_oidlist(&snmptoolctx->snmp_oidlist, s, oid) > 0)
+ return (1);
+ if (snmp_lookup_oidlist(&snmptoolctx->snmp_iplist, s, oid) > 0)
+ return (1);
+ if (snmp_lookup_oidlist(&snmptoolctx->snmp_ticklist, s, oid) > 0)
+ return (1);
+ if (snmp_lookup_oidlist(&snmptoolctx->snmp_cntlist, s, oid) > 0)
+ return (1);
+ if (snmp_lookup_oidlist(&snmptoolctx->snmp_gaugelist, s, oid) > 0)
+ return (1);
+ if (snmp_lookup_oidlist(&snmptoolctx->snmp_cnt64list, s, oid) > 0)
+ return (1);
+ if (snmp_lookup_oidlist(&snmptoolctx->snmp_nodelist, s, oid) > 0)
+ return (1);
+ if (snmp_lookup_tablelist(snmptoolctx, &snmptoolctx->snmp_tablelist,
+ s, oid) > 0)
+ return (1);
+
+ return (-1);
+}
+
+int32_t
+snmp_lookup_enumoid(struct snmp_toolinfo *snmptoolctx, struct snmp_object *s,
+ char *oid)
+{
+ if (snmptoolctx == NULL || s == NULL)
+ return (-1);
+
+ return (snmp_lookup_oidlist(&snmptoolctx->snmp_enumlist, s, oid));
+}
+
+int32_t
+snmp_lookup_oid(struct snmp_toolinfo *snmptoolctx, struct snmp_object *s,
+ char *oid)
+{
+ if (snmptoolctx == NULL || s == NULL)
+ return (-1);
+
+ switch (s->val.syntax) {
+ case SNMP_SYNTAX_INTEGER:
+ return (snmp_lookup_oidlist(&snmptoolctx->snmp_intlist,
+ s, oid));
+ case SNMP_SYNTAX_OCTETSTRING:
+ return (snmp_lookup_oidlist(&snmptoolctx->snmp_octlist,
+ s, oid));
+ case SNMP_SYNTAX_OID:
+ return (snmp_lookup_oidlist(&snmptoolctx->snmp_oidlist,
+ s, oid));
+ case SNMP_SYNTAX_IPADDRESS:
+ return (snmp_lookup_oidlist(&snmptoolctx->snmp_iplist,
+ s, oid));
+ case SNMP_SYNTAX_COUNTER:
+ return (snmp_lookup_oidlist(&snmptoolctx->snmp_cntlist,
+ s, oid));
+ case SNMP_SYNTAX_GAUGE:
+ return (snmp_lookup_oidlist(&snmptoolctx->snmp_gaugelist,
+ s, oid));
+ case SNMP_SYNTAX_TIMETICKS:
+ return (snmp_lookup_oidlist(&snmptoolctx->snmp_ticklist,
+ s, oid));
+ case SNMP_SYNTAX_COUNTER64:
+ return (snmp_lookup_oidlist(&snmptoolctx->snmp_cnt64list,
+ s, oid));
+ case SNMP_SYNTAX_NULL:
+ return (snmp_lookup_oidlist(&snmptoolctx->snmp_nodelist,
+ s, oid));
+ default:
+ warnx("Unknown syntax - %d", s->val.syntax);
+ break;
+ }
+
+ return (-1);
+}
diff --git a/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptc.c b/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptc.c
new file mode 100644
index 0000000..dc22c69
--- /dev/null
+++ b/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptc.c
@@ -0,0 +1,1287 @@
+/*-
+ * Copyright (c) 2006 The FreeBSD Project
+ * All rights reserved.
+ *
+ * Author: Shteryana Shopova <syrinx@FreeBSD.org>
+ *
+ * Redistribution of this software and documentation and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ *
+ * 1. Redistributions of source code or documentation must retain the above
+ * copyright notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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.
+ *
+ * Textual conventions for OctetStrings
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <bsnmp/asn1.h>
+#include <bsnmp/snmp.h>
+#include "bsnmptc.h"
+#include "bsnmptools.h"
+
+/* OctetString, DisplayString */
+static char *snmp_oct2str(uint32_t, char *, char *);
+static char *snmp_str2asn_oid(char *, struct asn_oid *);
+static int parse_octetstring(struct snmp_value *, char *);
+
+/* DateAndTime */
+static char *snmp_octstr2date(uint32_t, char *, char *);
+static char *snmp_date2asn_oid(char * , struct asn_oid *);
+static int parse_dateandtime(struct snmp_value *, char *);
+
+/* PhysAddress */
+static char *snmp_oct2physAddr(uint32_t, char *, char *);
+static char *snmp_addr2asn_oid(char *, struct asn_oid *);
+static int parse_physaddress(struct snmp_value *, char *);
+
+/* NTPTimeStamp */
+static char *snmp_oct2ntp_ts(uint32_t, char *, char *);
+static char *snmp_ntp_ts2asn_oid(char *, struct asn_oid *);
+static int parse_ntp_ts(struct snmp_value *, char *);
+
+/* BridgeId */
+static char *snmp_oct2bridgeid(uint32_t, char *, char *);
+static char *snmp_bridgeid2oct(char *, struct asn_oid *);
+static int parse_bridge_id(struct snmp_value *, char *);
+
+/* BridgePortId */
+static char *snmp_oct2bport_id(uint32_t, char *, char *);
+static char *snmp_bport_id2oct(char *, struct asn_oid *);
+static int parse_bport_id(struct snmp_value *, char *);
+
+/* InetAddress */
+static char *snmp_oct2inetaddr(uint32_t len, char *octets, char *buf);
+static char *snmp_inetaddr2oct(char *str, struct asn_oid *oid);
+static int32_t parse_inetaddr(struct snmp_value *value, char *string);
+
+static char *snmp_oct2bits(uint32_t len, char *octets, char *buf);
+static char *snmp_bits2oct(char *str, struct asn_oid *oid);
+static int32_t parse_bits(struct snmp_value *value, char *string);
+
+struct snmp_text_conv {
+ enum snmp_tc tc;
+ const char *tc_str;
+ int32_t len;
+ snmp_oct2tc_f oct2tc;
+ snmp_tc2oid_f tc2oid;
+ snmp_tc2oct_f tc2oct;
+} text_convs[] = {
+ { SNMP_STRING, "OctetString", SNMP_VAR_STRSZ,
+ snmp_oct2str, snmp_str2asn_oid, parse_octetstring },
+
+ { SNMP_DISPLAYSTRING, "DisplayString" , SNMP_VAR_STRSZ,
+ snmp_oct2str, snmp_str2asn_oid, parse_octetstring },
+
+ { SNMP_DATEANDTIME, "DateAndTime", SNMP_DATETIME_STRSZ,
+ snmp_octstr2date, snmp_date2asn_oid, parse_dateandtime },
+
+ { SNMP_PHYSADDR, "PhysAddress", SNMP_PHYSADDR_STRSZ,
+ snmp_oct2physAddr, snmp_addr2asn_oid, parse_physaddress },
+
+ { SNMP_ATMESI, "AtmESI", SNMP_PHYSADDR_STRSZ,
+ snmp_oct2physAddr, snmp_addr2asn_oid, parse_physaddress },
+
+ { SNMP_NTP_TIMESTAMP, "NTPTimeStamp", SNMP_NTP_TS_STRSZ,
+ snmp_oct2ntp_ts, snmp_ntp_ts2asn_oid, parse_ntp_ts },
+
+ { SNMP_MACADDRESS, "MacAddress", SNMP_PHYSADDR_STRSZ,
+ snmp_oct2physAddr, snmp_addr2asn_oid, parse_physaddress },
+
+ { SNMP_BRIDGE_ID, "BridgeId", SNMP_BRIDGEID_STRSZ,
+ snmp_oct2bridgeid, snmp_bridgeid2oct, parse_bridge_id },
+
+ { SNMP_BPORT_ID, "BridgePortId", SNMP_BPORT_STRSZ,
+ snmp_oct2bport_id, snmp_bport_id2oct, parse_bport_id },
+
+ { SNMP_INETADDRESS, "InetAddress", SNMP_INADDRS_STRSZ,
+ snmp_oct2inetaddr, snmp_inetaddr2oct, parse_inetaddr },
+
+ { SNMP_TC_OWN, "BITS", SNMP_VAR_STRSZ,
+ snmp_oct2bits, snmp_bits2oct, parse_bits },
+
+ { SNMP_UNKNOWN, "Unknown", SNMP_VAR_STRSZ, snmp_oct2str,
+ snmp_str2asn_oid, parse_octetstring } /* keep last */
+};
+
+/* Common API */
+enum snmp_tc
+snmp_get_tc(char *str)
+{
+ int i;
+ for (i = 0; i < SNMP_UNKNOWN; i++) {
+ if (!strncmp(text_convs[i].tc_str, str,
+ strlen(text_convs[i].tc_str)))
+ return (text_convs[i].tc);
+ }
+
+ return (SNMP_STRING);
+}
+
+char *
+snmp_oct2tc(enum snmp_tc tc, uint32_t len, char *octets)
+{
+ uint32_t tc_len;
+ char * buf;
+
+ if (tc < 0 || tc > SNMP_UNKNOWN)
+ tc = SNMP_UNKNOWN;
+
+ if (text_convs[tc].len > 0)
+ tc_len = text_convs[tc].len;
+ else
+ tc_len = 2 * len + 3;
+
+ if ((buf = malloc(tc_len)) == NULL ) {
+ syslog(LOG_ERR, "malloc failed - %s", strerror(errno));
+ return (NULL);
+ }
+
+ memset(buf, 0, tc_len);
+ if (text_convs[tc].oct2tc(len, octets, buf) == NULL) {
+ free(buf);
+ return (NULL);
+ }
+
+ return (buf);
+}
+
+char *
+snmp_tc2oid(enum snmp_tc tc, char *str, struct asn_oid *oid)
+{
+ if (tc < 0 || tc > SNMP_UNKNOWN)
+ tc = SNMP_UNKNOWN;
+
+ return (text_convs[tc].tc2oid(str, oid));
+}
+
+int32_t
+snmp_tc2oct(enum snmp_tc tc, struct snmp_value *value, char *string)
+{
+ if (tc < 0 || tc > SNMP_UNKNOWN)
+ tc = SNMP_UNKNOWN;
+
+ return (text_convs[tc].tc2oct(value, string));
+}
+
+/*****************************************************
+* Basic OctetString type.
+*/
+static char *
+snmp_oct2str(uint32_t len, char *octets, char *buf)
+{
+ uint8_t binary = 0;
+ uint32_t i;
+ char *ptr;
+
+ if (len > MAX_OCTSTRING_LEN || octets == NULL || buf == NULL)
+ return (NULL);
+
+ for (ptr = buf, i = 0; i < len; i++)
+ if (!isprint(octets[i])) {
+ binary = 1;
+ buf += sprintf(buf, "0x");
+ break;
+ }
+
+ for (ptr = buf, i = 0; i < len; i++)
+ if (!binary)
+ ptr += sprintf(ptr, "%c", octets[i]);
+ else
+ ptr += sprintf(ptr, "%2.2x", (u_char)octets[i]);
+
+ return (buf);
+}
+
+static char *
+snmp_str2asn_oid(char *str, struct asn_oid *oid)
+{
+ uint32_t i, len = 0;
+
+ /*
+ * OctetStrings are allowed max length of ASN_MAXOCTETSTRING,
+ * but trying to index an entry with such a long OctetString
+ * will fail anyway.
+ */
+ for (len = 0; len < ASN_MAXOIDLEN; len++) {
+ if (strchr(",]", *(str + len)) != NULL)
+ break;
+ }
+
+ if (len >= ASN_MAXOIDLEN)
+ return (NULL);
+
+ if (snmp_suboid_append(oid, (asn_subid_t) len) < 0)
+ return (NULL);
+
+ for (i = 0; i < len; i++)
+ if (snmp_suboid_append(oid, (asn_subid_t) *(str + i)) < 0)
+ return (NULL);
+
+ return (str + len);
+}
+
+static int32_t
+parse_octetstring(struct snmp_value *value, char *val)
+{
+ size_t len;
+
+ if ((len = strlen(val)) >= MAX_OCTSTRING_LEN) {
+ warnx("Octetstring too long - %d is max allowed",
+ MAX_OCTSTRING_LEN - 1);
+ return (-1);
+ }
+
+ value->v.octetstring.len = len;
+
+ if((value->v.octetstring.octets = malloc(len)) == NULL) {
+ syslog(LOG_ERR,"malloc failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ memcpy(value->v.octetstring.octets, val, len);
+ value->syntax = SNMP_SYNTAX_OCTETSTRING;
+
+ return (0);
+}
+
+/*************************************************************
+ * DateAndTime
+ *************************************************************
+ * rfc 2579 specification:
+ * DateAndTime ::= TEXTUAL-CONVENTION
+ * DISPLAY-HINT "2d-1d-1d,1d:1d:1d.1d,1a1d:1d"
+ * STATUS current
+ * DESCRIPTION
+ * "A date-time specification.
+ *
+ * field octets contents range
+ * ----- ------ -------- -----
+ * 1 1-2 year* 0..65536
+ * 2 3 month 1..12
+ * 3 4 day 1..31
+ * 4 5 hour 0..23
+ * 5 6 minutes 0..59
+ * 6 7 seconds 0..60
+ * (use 60 for leap-second)
+ * 7 8 deci-seconds 0..9
+ * 8 9 direction from UTC '+' / '-'
+ * 9 10 hours from UTC* 0..13
+ * 10 11 minutes from UTC 0..59
+ *
+ * * Notes:
+ * - the value of year is in network-byte order
+ * - daylight saving time in New Zealand is +13
+ *
+ * For example, Tuesday May 26, 1992 at 1:30:15 PM EDT would be
+ * displayed as:
+ *
+ * 1992-5-26,13:30:15.0,-4:0
+ */
+static char *
+snmp_octstr2date(uint32_t len, char *octets, char *buf)
+{
+ int year;
+ char *ptr;
+
+ if (len != SNMP_DATETIME_OCTETS || octets == NULL || buf == NULL)
+ return (NULL);
+
+ buf[0]= '\0';
+ year = (octets[0] << 8);
+ year += (octets[1]);
+
+ ptr = buf;
+ ptr += sprintf(ptr, "%4.4d-%.2d-%.2d, ", year, octets[2],octets[3]);
+ ptr += sprintf(ptr, "%2.2d:%2.2d:%2.2d.%.2d, ", octets[4],octets[5],
+ octets[6],octets[7]);
+ ptr += sprintf(ptr, "%c%.2d:%.2d", octets[8],octets[9],octets[10]);
+
+ return (buf);
+}
+
+static char *
+snmp_date2asn_oid(char *str, struct asn_oid *oid)
+{
+ char *endptr, *ptr;
+ uint32_t v;
+ int32_t saved_errno;
+
+ if (snmp_suboid_append(oid, (asn_subid_t) SNMP_DATETIME_OCTETS) < 0)
+ return (NULL);
+
+ /* Read 'YYYY-' and write it in two subs. */
+ ptr = str;
+ saved_errno = errno;
+ errno = 0;
+ v = strtoul(ptr, &endptr, 10);
+ if (v > 0xffff)
+ goto error;
+ else
+ errno = saved_errno;
+ if (*endptr != '-')
+ goto error1;
+ if (snmp_suboid_append(oid, (asn_subid_t) ((v & 0xff00) >> 8)) < 0)
+ return (NULL);
+ if (snmp_suboid_append(oid, (asn_subid_t) (v & 0xff)) < 0)
+ return (NULL);
+
+ /* 'MM-' */
+ ptr = endptr + 1;
+ saved_errno = errno;
+ v = strtoul(ptr, &endptr, 10);
+ if (errno != 0)
+ goto error;
+ else
+ errno = saved_errno;
+ if (*endptr != '-')
+ goto error1;
+ if (snmp_suboid_append(oid, (asn_subid_t) v) < 0)
+ return (NULL);
+
+ /* 'DD,' */
+ ptr = endptr + 1;
+ saved_errno = errno;
+ v = strtoul(ptr, &endptr, 10);
+ if (errno != 0)
+ goto error;
+ else
+ errno = saved_errno;
+ if (*endptr != '-')
+ goto error1;
+ if (snmp_suboid_append(oid, (asn_subid_t) v) < 0)
+ return (NULL);
+
+ /* 'HH:' */
+ ptr = endptr + 1;
+ saved_errno = errno;
+ v = strtoul(ptr, &endptr, 10);
+ if (errno != 0)
+ goto error;
+ else
+ errno = saved_errno;
+ if (*endptr != ':')
+ goto error1;
+ if (snmp_suboid_append(oid, (asn_subid_t) v) < 0)
+ return (NULL);
+
+ /* 'MM:' */
+ ptr = endptr + 1;
+ saved_errno = errno;
+ v = strtoul(ptr, &endptr, 10);
+ if (errno != 0)
+ goto error;
+ else
+ errno = saved_errno;
+ if (*endptr != ':')
+ goto error1;
+ if (snmp_suboid_append(oid, (asn_subid_t) v) < 0)
+ return (NULL);
+
+ /* 'SS.' */
+ ptr = endptr + 1;
+ saved_errno = errno;
+ v = strtoul(ptr, &endptr, 10);
+ if (errno != 0)
+ goto error;
+ else
+ errno = saved_errno;
+ if (*endptr != '.')
+ goto error1;
+ if (snmp_suboid_append(oid, (asn_subid_t) v) < 0)
+ return (NULL);
+
+ /* 'M(mseconds),' */
+ ptr = endptr + 1;
+ saved_errno = errno;
+ v = strtoul(ptr, &endptr, 10);
+ if (errno != 0)
+ goto error;
+ else
+ errno = saved_errno;
+ if (*endptr != ',')
+ goto error1;
+ if (snmp_suboid_append(oid, (asn_subid_t) v) < 0)
+ return (NULL);
+
+ /* 'UTC' - optional */
+ ptr = endptr + 1;
+ if (*ptr == 'U' && *(ptr + 1) == 'T' && *(ptr + 1) == 'C')
+ ptr += 3;
+
+ /* '+/-' */
+ if (*ptr == '-' || *ptr == '+') {
+ if (snmp_suboid_append(oid, (asn_subid_t) (*ptr)) < 0)
+ return (NULL);
+ } else
+ goto error1;
+
+ /* 'HH:' */
+ ptr = endptr + 1;
+ saved_errno = errno;
+ v = strtoul(ptr, &endptr, 10);
+ if (errno != 0)
+ goto error;
+ else
+ errno = saved_errno;
+ if (*endptr != ':')
+ goto error1;
+ if (snmp_suboid_append(oid, (asn_subid_t) v) < 0)
+ return (NULL);
+
+ /* 'MM' - last one - ignore endptr here. */
+ ptr = endptr + 1;
+ saved_errno = errno;
+ v = strtoul(ptr, &endptr, 10);
+ if (errno != 0)
+ goto error;
+ else
+ errno = saved_errno;
+ if (snmp_suboid_append(oid, (asn_subid_t) v) < 0)
+ return (NULL);
+
+ return (endptr);
+
+ error:
+ errno = saved_errno;
+ error1:
+ warnx("Date value %s not supported", str);
+ return (NULL);
+}
+
+/* Read a DateAndTime string eg. 1992-5-26,13:30:15.0,-4:0. */
+static int32_t
+parse_dateandtime(struct snmp_value *sv, char *val)
+{
+ char *endptr;
+ uint32_t v;
+ uint8_t date[SNMP_DATETIME_OCTETS];
+
+ /* 'YYYY-' */
+ v = strtoul(val, &endptr, 10);
+ if (v > 0xffff || *endptr != '-')
+ goto error;
+ date[0] = ((v & 0xff00) >> 8);
+ date[1] = (v & 0xff);
+ val = endptr + 1;
+
+ /* 'MM-' */
+ v = strtoul(val, &endptr, 10);
+ if (v == 0 || v > 12 || *endptr != '-')
+ goto error;
+ date[2] = v;
+ val = endptr + 1;
+
+ /* 'DD,' */
+ v = strtoul(val, &endptr, 10);
+ if (v == 0 || v > 31 || *endptr != ',')
+ goto error;
+ date[3] = v;
+ val = endptr + 1;
+
+ /* 'HH:' */
+ v = strtoul(val, &endptr, 10);
+ if (v > 23 || *endptr != ':')
+ goto error;
+ date[4] = v;
+ val = endptr + 1;
+
+ /* 'MM:' */
+ v = strtoul(val, &endptr, 10);
+ if (v > 59 || *endptr != ':')
+ goto error;
+ date[5] = v;
+ val = endptr + 1;
+
+ /* 'SS.' */
+ v = strtoul(val, &endptr, 10);
+ if (v > 60 || *endptr != '.')
+ goto error;
+ date[6] = v;
+ val = endptr + 1;
+
+ /* '(deci-)s,' */
+ v = strtoul(val, &endptr, 10);
+ if (v > 9 || *endptr != ',')
+ goto error;
+ date[7] = v;
+ val = endptr + 1;
+
+ /* offset - '+/-' */
+ if (*val != '-' && *val != '+')
+ goto error;
+ date[8] = (uint8_t) *val;
+ val = endptr + 1;
+
+ /* 'HH:' - offset from UTC */
+ v = strtoul(val, &endptr, 10);
+ if (v > 13 || *endptr != ':')
+ goto error;
+ date[9] = v;
+ val = endptr + 1;
+
+ /* 'MM'\0'' offset from UTC */
+ v = strtoul(val, &endptr, 10);
+ if (v > 59 || *endptr != '\0')
+ goto error;
+ date[10] = v;
+
+ if ((sv->v.octetstring.octets = malloc(SNMP_DATETIME_OCTETS)) == NULL) {
+ warnx("malloc() failed - %s", strerror(errno));
+ return (-1);
+ }
+
+ sv->v.octetstring.len = SNMP_DATETIME_OCTETS;
+ memcpy(sv->v.octetstring.octets, date, SNMP_DATETIME_OCTETS);
+ sv->syntax = SNMP_SYNTAX_OCTETSTRING;
+ return (1);
+
+ error:
+ warnx("Date value %s not supported", val);
+ return (-1);
+}
+
+/**************************************************************
+ * PhysAddress
+ */
+static char *
+snmp_oct2physAddr(uint32_t len, char *octets, char *buf)
+{
+ char *ptr;
+ uint32_t i;
+
+ if (len != SNMP_PHYSADDR_OCTETS || octets == NULL || buf == NULL)
+ return (NULL);
+
+ buf[0]= '\0';
+
+ ptr = buf;
+ ptr += sprintf(ptr, "%2.2x", octets[0]);
+ for (i = 1; i < 6; i++)
+ ptr += sprintf(ptr, ":%2.2x", octets[i]);
+
+ return (buf);
+}
+
+static char *
+snmp_addr2asn_oid(char *str, struct asn_oid *oid)
+{
+ char *endptr, *ptr;
+ uint32_t v, i;
+ int saved_errno;
+
+ if (snmp_suboid_append(oid, (asn_subid_t) SNMP_PHYSADDR_OCTETS) < 0)
+ return (NULL);
+
+ ptr = str;
+ for (i = 0; i < 5; i++) {
+ saved_errno = errno;
+ v = strtoul(ptr, &endptr, 16);
+ errno = saved_errno;
+ if (v > 0xff) {
+ warnx("Integer value %s not supported", str);
+ return (NULL);
+ }
+ if (*endptr != ':') {
+ warnx("Failed adding oid - %s",str);
+ return (NULL);
+ }
+ if (snmp_suboid_append(oid, (asn_subid_t) v) < 0)
+ return (NULL);
+ ptr = endptr + 1;
+ }
+
+ /* The last one - don't check the ending char here. */
+ saved_errno = errno;
+ v = strtoul(ptr, &endptr, 16);
+ errno = saved_errno;
+ if (v > 0xff) {
+ warnx("Integer value %s not supported", str);
+ return (NULL);
+ }
+ if (snmp_suboid_append(oid, (asn_subid_t) v) < 0)
+ return (NULL);
+
+ return (endptr);
+}
+
+static int32_t
+parse_physaddress(struct snmp_value *sv, char *val)
+{
+ char *endptr;
+ int32_t i;
+ uint32_t v;
+ uint8_t phys_addr[SNMP_PHYSADDR_OCTETS];
+
+ for (i = 0; i < 5; i++) {
+ v = strtoul(val, &endptr, 16);
+ if (v > 0xff) {
+ warnx("Integer value %s not supported", val);
+ return (-1);
+ }
+ if(*endptr != ':') {
+ warnx("Failed reading octet - %s", val);
+ return (-1);
+ }
+ phys_addr[i] = v;
+ val = endptr + 1;
+ }
+
+ /* The last one - don't check the ending char here. */
+ v = strtoul(val, &endptr, 16);
+ if (v > 0xff) {
+ warnx("Integer value %s not supported", val);
+ return (-1);
+ }
+ phys_addr[5] = v;
+
+ if ((sv->v.octetstring.octets = malloc(SNMP_PHYSADDR_OCTETS)) == NULL) {
+ syslog(LOG_ERR,"malloc failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ sv->v.octetstring.len = SNMP_PHYSADDR_OCTETS;
+ memcpy(sv->v.octetstring.octets, phys_addr, SNMP_PHYSADDR_OCTETS);
+ sv->syntax = SNMP_SYNTAX_OCTETSTRING;
+ return (1);
+}
+
+/**************************************************************
+ * NTPTimeStamp
+ **************************************************************
+ * NTP MIB, Revision 0.2, 7/25/97:
+ * NTPTimeStamp ::= TEXTUAL-CONVENTION
+ * DISPLAY-HINT "4x.4x"
+ * STATUS current
+ * DESCRIPTION
+ * ""
+ * SYNTAX OCTET STRING (SIZE(8))
+ */
+static char *
+snmp_oct2ntp_ts(uint32_t len, char *octets, char *buf)
+{
+ char *ptr;
+ uint32_t i;
+
+ if (len != SNMP_NTP_TS_OCTETS || octets == NULL || buf == NULL)
+ return (NULL);
+
+ buf[0]= '\0';
+
+ ptr = buf;
+ i = octets[0] * 1000 + octets[1] * 100 + octets[2] * 10 + octets[3];
+ ptr += sprintf(ptr, "%4.4d", i);
+ i = octets[4] * 1000 + octets[5] * 100 + octets[6] * 10 + octets[7];
+ ptr += sprintf(ptr, ".%4.4d", i);
+
+ return (buf);
+}
+
+static char *
+snmp_ntp_ts2asn_oid(char *str, struct asn_oid *oid)
+{
+ char *endptr, *ptr;
+ uint32_t v, i, d;
+ struct asn_oid suboid;
+ int saved_errno;
+
+ if (snmp_suboid_append(oid, (asn_subid_t) SNMP_NTP_TS_OCTETS) < 0)
+ return (NULL);
+
+ ptr = str;
+ saved_errno = errno;
+ v = strtoul(ptr, &endptr, 10);
+ if (errno != 0 || (v / 1000) > 9) {
+ warnx("Integer value %s not supported", str);
+ errno = saved_errno;
+ return (NULL);
+ } else
+ errno = saved_errno;
+
+ if (*endptr != '.') {
+ warnx("Failed adding oid - %s",str);
+ return (NULL);
+ }
+
+ memset(&suboid, 0, sizeof(struct asn_oid));
+ suboid.len = SNMP_NTP_TS_OCTETS;
+
+ for (i = 0, d = 1000; i < 4; i++) {
+ suboid.subs[i] = v / d;
+ v = v % d;
+ d = d / 10;
+ }
+
+ ptr = endptr + 1;
+ saved_errno = errno;
+ v = strtoul(ptr, &endptr, 10);
+ if (errno != 0 || (v / 1000) > 9) {
+ warnx("Integer value %s not supported", str);
+ errno = saved_errno;
+ return (NULL);
+ } else
+ errno = saved_errno;
+
+ for (i = 0, d = 1000; i < 4; i++) {
+ suboid.subs[i + 4] = v / d;
+ v = v % d;
+ d = d / 10;
+ }
+
+ asn_append_oid(oid, &suboid);
+ return (endptr);
+}
+
+static int32_t
+parse_ntp_ts(struct snmp_value *sv, char *val)
+{
+ char *endptr;
+ int32_t i, d, saved_errno;
+ uint32_t v;
+ uint8_t ntp_ts[SNMP_NTP_TS_OCTETS];
+
+ saved_errno = errno;
+ v = strtoul(val, &endptr, 10);
+ if (errno != 0 || (v / 1000) > 9) {
+ saved_errno = errno;
+ warnx("Integer value %s not supported", val);
+ return (-1);
+ } else
+ saved_errno = errno;
+
+ if (*endptr != '.') {
+ warnx("Failed reading octet - %s", val);
+ return (-1);
+ }
+
+ for (i = 0, d = 1000; i < 4; i++) {
+ ntp_ts[i] = v / d;
+ v = v % d;
+ d = d / 10;
+ }
+ val = endptr + 1;
+
+ saved_errno = errno;
+ v = strtoul(val, &endptr, 10);
+ if (errno != 0 || (v / 1000) > 9) {
+ saved_errno = errno;
+ warnx("Integer value %s not supported", val);
+ return (-1);
+ } else
+ saved_errno = errno;
+
+ for (i = 0, d = 1000; i < 4; i++) {
+ ntp_ts[i + 4] = v / d;
+ v = v % d;
+ d = d / 10;
+ }
+
+ if ((sv->v.octetstring.octets = malloc(SNMP_NTP_TS_OCTETS)) == NULL) {
+ syslog(LOG_ERR,"malloc failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ sv->v.octetstring.len = SNMP_NTP_TS_OCTETS;
+ memcpy(sv->v.octetstring.octets, ntp_ts, SNMP_NTP_TS_OCTETS);
+ sv->syntax = SNMP_SYNTAX_OCTETSTRING;
+ return (1);
+}
+
+/**************************************************************
+ * BridgeId
+ **************************************************************
+ * BRIDGE-MIB, REVISION "200509190000Z"
+ * BridgeId ::= TEXTUAL-CONVENTION
+ * STATUS current
+ * DESCRIPTION
+ * "The Bridge-Identifier, as used in the Spanning Tree
+ * Protocol, to uniquely identify a bridge. Its first two
+ * octets (in network byte order) contain a priority value,
+ * and its last 6 octets contain the MAC address used to
+ * refer to a bridge in a unique fashion (typically, the
+ * numerically smallest MAC address of all ports on the
+ * bridge)."
+ * SYNTAX OCTET STRING (SIZE (8))
+ */
+static char *
+snmp_oct2bridgeid(uint32_t len, char *octets, char *buf)
+{
+ char *ptr;
+ uint32_t i, priority;
+
+ if (len != SNMP_BRIDGEID_OCTETS || octets == NULL || buf == NULL)
+ return (NULL);
+
+ buf[0]= '\0';
+ ptr = buf;
+
+ priority = octets[0] << 8;
+ priority += octets[1];
+ if (priority > SNMP_MAX_BRIDGE_PRIORITY) {
+ warnx("Invalid bridge priority %d", priority);
+ return (NULL);
+ } else
+ ptr += sprintf(ptr, "%d.", octets[0]);
+
+ ptr += sprintf(ptr, "%2.2x", octets[2]);
+
+ for (i = 1; i < 6; i++)
+ ptr += sprintf(ptr, ":%2.2x", octets[i + 2]);
+
+ return (buf);
+}
+
+static char *
+snmp_bridgeid2oct(char *str, struct asn_oid *oid)
+{
+ char *endptr, *ptr;
+ uint32_t v, i;
+ int32_t saved_errno;
+
+ if (snmp_suboid_append(oid, (asn_subid_t) SNMP_BRIDGEID_OCTETS) < 0)
+ return (NULL);
+
+ ptr = str;
+ /* Read the priority. */
+ saved_errno = errno;
+ v = strtoul(ptr, &endptr, 10);
+ errno = 0;
+
+ if (v > SNMP_MAX_BRIDGE_PRIORITY || errno != 0 || *endptr != '.') {
+ errno = saved_errno;
+ warnx("Bad bridge priority value %d", v);
+ return (NULL);
+ }
+
+ if (snmp_suboid_append(oid, (asn_subid_t) (v & 0xff00)) < 0)
+ return (NULL);
+
+ if (snmp_suboid_append(oid, (asn_subid_t) (v & 0xff)) < 0)
+ return (NULL);
+
+ ptr = endptr + 1;
+ for (i = 0; i < 5; i++) {
+ saved_errno = errno;
+ v = strtoul(ptr, &endptr, 16);
+ errno = saved_errno;
+ if (v > 0xff) {
+ warnx("Integer value %s not supported", str);
+ return (NULL);
+ }
+ if (*endptr != ':') {
+ warnx("Failed adding oid - %s",str);
+ return (NULL);
+ }
+ if (snmp_suboid_append(oid, (asn_subid_t) v) < 0)
+ return (NULL);
+ ptr = endptr + 1;
+ }
+
+ /* The last one - don't check the ending char here. */
+ saved_errno = errno;
+ v = strtoul(ptr, &endptr, 16);
+ errno = saved_errno;
+ if (v > 0xff) {
+ warnx("Integer value %s not supported", str);
+ return (NULL);
+ }
+ if (snmp_suboid_append(oid, (asn_subid_t) v) < 0)
+ return (NULL);
+
+ return (endptr);
+}
+
+static int32_t
+parse_bridge_id(struct snmp_value *sv, char *string)
+{
+ char *ptr, *endptr;
+ int32_t i, saved_errno;
+ uint32_t v;
+ uint8_t bridge_id[SNMP_BRIDGEID_OCTETS];
+
+ ptr = string;
+ /* Read the priority. */
+ saved_errno = errno;
+ errno = 0;
+ v = strtoul(string, &endptr, 10);
+ errno = saved_errno;
+
+ if (v > SNMP_MAX_BRIDGE_PRIORITY || errno != 0 || *endptr != '.') {
+ errno = saved_errno;
+ warnx("Bad bridge priority value %d", v);
+ return (-1);
+ }
+
+ bridge_id[0] = (v & 0xff00);
+ bridge_id[1] = (v & 0xff);
+
+ string = endptr + 1;
+
+ for (i = 0; i < 5; i++) {
+ v = strtoul(string, &endptr, 16);
+ if (v > 0xff) {
+ warnx("Integer value %s not supported", string);
+ return (-1);
+ }
+ if(*endptr != ':') {
+ warnx("Failed reading octet - %s", string);
+ return (-1);
+ }
+ bridge_id[i + 2] = v;
+ string = endptr + 1;
+ }
+
+ /* The last one - don't check the ending char here. */
+ v = strtoul(string, &endptr, 16);
+ if (v > 0xff) {
+ warnx("Integer value %s not supported", string);
+ return (-1);
+ }
+ bridge_id[7] = v;
+
+ if ((sv->v.octetstring.octets = malloc(SNMP_BRIDGEID_OCTETS)) == NULL) {
+ syslog(LOG_ERR,"malloc failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ sv->v.octetstring.len = SNMP_BRIDGEID_OCTETS;
+ memcpy(sv->v.octetstring.octets, bridge_id, SNMP_BRIDGEID_OCTETS);
+ sv->syntax = SNMP_SYNTAX_OCTETSTRING;
+ return (1);
+}
+
+/**************************************************************
+ * BridgePortId
+ **************************************************************
+ * BEGEMOT-BRIDGE-MIB, LAST-UPDATED "200608100000Z"
+ * BridgePortId ::= TEXTUAL-CONVENTION
+ * DISPLAY-HINT "1x.1x"
+ * STATUS current
+ * DESCRIPTION
+ * "A port identifier that contains a bridge port's STP priority
+ * in the first octet and the port number in the second octet."
+ * SYNTAX OCTET STRING (SIZE(2))
+ */
+static char *
+snmp_oct2bport_id(uint32_t len, char *octets, char *buf)
+{
+ char *ptr;
+
+ if (len != SNMP_BPORT_OCTETS || octets == NULL || buf == NULL)
+ return (NULL);
+
+ buf[0]= '\0';
+ ptr = buf;
+
+ ptr += sprintf(ptr, "%d.", octets[0]);
+ ptr += sprintf(ptr, "%d", octets[1]);
+
+ return (buf);
+}
+
+static char *
+snmp_bport_id2oct(char *str, struct asn_oid *oid)
+{
+ char *endptr, *ptr;
+ uint32_t v;
+ int saved_errno;
+
+ if (snmp_suboid_append(oid, (asn_subid_t) SNMP_BPORT_OCTETS) < 0)
+ return (NULL);
+
+ ptr = str;
+ /* Read the priority. */
+ saved_errno = errno;
+ v = strtoul(ptr, &endptr, 10);
+ errno = 0;
+
+ if (v > SNMP_MAX_BPORT_PRIORITY || errno != 0 || *endptr != '.') {
+ errno = saved_errno;
+ warnx("Bad bridge port priority value %d", v);
+ return (NULL);
+ }
+
+ if (snmp_suboid_append(oid, (asn_subid_t) v) < 0)
+ return (NULL);
+
+ saved_errno = errno;
+ v = strtoul(ptr, &endptr, 16);
+ errno = saved_errno;
+
+ if (v > 0xff) {
+ warnx("Bad port number - %d", v);
+ return (NULL);
+ }
+
+ if (snmp_suboid_append(oid, (asn_subid_t) v) < 0)
+ return (NULL);
+
+ return (endptr);
+}
+
+static int32_t
+parse_bport_id(struct snmp_value *value, char *string)
+{
+ char *ptr, *endptr;
+ int saved_errno;
+ uint32_t v;
+ uint8_t bport_id[SNMP_BPORT_OCTETS];
+
+ ptr = string;
+ /* Read the priority. */
+ saved_errno = errno;
+ errno = 0;
+ v = strtoul(string, &endptr, 10);
+ errno = saved_errno;
+
+ if (v > SNMP_MAX_BPORT_PRIORITY || errno != 0 || *endptr != '.') {
+ errno = saved_errno;
+ warnx("Bad bridge port priority value %d", v);
+ return (-1);
+ }
+
+ bport_id[0] = v;
+
+ string = endptr + 1;
+ v = strtoul(string, &endptr, 16);
+ if (v > 0xff) {
+ warnx("Bad port number - %d", v);
+ return (-1);
+ }
+
+ bport_id[1] = v;
+
+ if ((value->v.octetstring.octets = malloc(SNMP_BPORT_OCTETS)) == NULL) {
+ syslog(LOG_ERR,"malloc failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ value->v.octetstring.len = SNMP_BPORT_OCTETS;
+ memcpy(value->v.octetstring.octets, bport_id, SNMP_BPORT_OCTETS);
+ value->syntax = SNMP_SYNTAX_OCTETSTRING;
+ return (1);
+}
+/**************************************************************
+ * InetAddress
+ **************************************************************
+ * INET-ADDRESS-MIB, REVISION "200502040000Z"
+ * InetAddress ::= TEXTUAL-CONVENTION
+ * STATUS current
+ * DESCRIPTION
+ * "Denotes a generic Internet address.
+ *
+ * An InetAddress value is always interpreted within the context
+ * of an InetAddressType value. Every usage of the InetAddress
+ * textual convention is required to specify the InetAddressType
+ * object that provides the context. It is suggested that the
+ * InetAddressType object be logically registered before the
+ * object(s) that use the InetAddress textual convention, if
+ * they appear in the same logical row.
+ *
+ * The value of an InetAddress object must always be
+ * consistent with the value of the associated InetAddressType
+ * object. Attempts to set an InetAddress object to a value
+ * inconsistent with the associated InetAddressType
+ * must fail with an inconsistentValue error.
+ *
+ * When this textual convention is used as the syntax of an
+ * index object, there may be issues with the limit of 128
+ * sub-identifiers specified in SMIv2, STD 58. In this case,
+ * the object definition MUST include a 'SIZE' clause to
+ * limit the number of potential instance sub-identifiers;
+ * otherwise the applicable constraints MUST be stated in
+ * the appropriate conceptual row DESCRIPTION clauses, or
+ * in the surrounding documentation if there is no single
+ * DESCRIPTION clause that is appropriate."
+ * SYNTAX OCTET STRING (SIZE (0..255))
+ **************************************************************
+ * TODO: FIXME!!! syrinx: Since we do not support checking the
+ * consistency of a varbinding based on the value of a previous
+ * one, try to guess the type of address based on the
+ * OctetString SIZE - 4 for IPv4, 16 for IPv6, others currently
+ * not supported.
+ */
+static char *
+snmp_oct2inetaddr(uint32_t len, char *octets, char *buf)
+{
+ int af;
+ void *ip;
+ struct in_addr ipv4;
+ struct in6_addr ipv6;
+
+ if (len > MAX_OCTSTRING_LEN || octets == NULL || buf == NULL)
+ return (NULL);
+
+ switch (len) {
+ /* XXX: FIXME - IPv4*/
+ case 4:
+ memcpy(&ipv4.s_addr, octets, sizeof(ipv4.s_addr));
+ af = AF_INET;
+ ip = &ipv4;
+ break;
+
+ /* XXX: FIXME - IPv4*/
+ case 16:
+ memcpy(ipv6.s6_addr, octets, sizeof(ipv6.s6_addr));
+ af = AF_INET6;
+ ip = &ipv6;
+ break;
+
+ default:
+ return (NULL);
+ }
+
+ if (inet_ntop(af, ip, buf, SNMP_INADDRS_STRSZ) == NULL) {
+ warnx("inet_ntop failed - %s", strerror(errno));
+ return (NULL);
+ }
+
+ return (buf);
+}
+
+static char *
+snmp_inetaddr2oct(char *str, struct asn_oid *oid)
+{
+ return (NULL);
+}
+
+static int32_t
+parse_inetaddr(struct snmp_value *value, char *string)
+{
+ return (-1);
+}
+
+/**************************************************************
+ * SNMP BITS type - XXX: FIXME
+ **************************************************************/
+static char *
+snmp_oct2bits(uint32_t len, char *octets, char *buf)
+{
+ int i, bits;
+ uint64_t value;
+
+ if (len > sizeof(value) || octets == NULL || buf == NULL)
+ return (NULL);
+
+ for (i = len, value = 0, bits = 0; i > 0; i--, bits += 8)
+ value += octets[i] << bits;
+
+ buf[0]= '\0';
+ sprintf(buf, "0x%llx.",(long long unsigned) value);
+
+ return (buf);
+}
+
+static char *
+snmp_bits2oct(char *str, struct asn_oid *oid)
+{
+ char *endptr;
+ int i, size, bits, saved_errno;
+ uint64_t v, mask = 0xFF00000000000000;
+
+ saved_errno = errno;
+ errno = 0;
+
+ v = strtoull(str, &endptr, 16);
+ if (errno != 0) {
+ warnx("Bad BITS value %s - %s", str, strerror(errno));
+ errno = saved_errno;
+ return (NULL);
+ }
+
+ bits = 8;
+ /* Determine length - up to 8 octets supported so far. */
+ for (size = sizeof(v); size > 0; size--) {
+ if ((v & mask) != 0)
+ break;
+ mask = mask >> bits;
+ }
+
+ if (size == 0)
+ size = 1;
+
+ if (snmp_suboid_append(oid, (asn_subid_t) size) < 0)
+ return (NULL);
+
+ for (i = 0, bits = 0; i < size; i++, bits += 8)
+ if (snmp_suboid_append(oid,
+ (asn_subid_t)((v & mask) >> bits)) < 0)
+ return (NULL);
+
+ return (endptr);
+}
+
+static int32_t
+parse_bits(struct snmp_value *value, char *string)
+{
+ char *endptr;
+ int i, size, bits, saved_errno;
+ uint64_t v, mask = 0xFF00000000000000;
+
+ saved_errno = errno;
+ errno = 0;
+
+ v = strtoull(string, &endptr, 16);
+
+ if (errno != 0) {
+ warnx("Bad BITS value %s - %s", string, strerror(errno));
+ errno = saved_errno;
+ return (-1);
+ }
+
+ bits = 8;
+ /* Determine length - up to 8 octets supported so far. */
+ for (size = sizeof(v); size > 0; size--) {
+ if ((v & mask) != 0)
+ break;
+ mask = mask >> bits;
+ }
+
+ if (size == 0)
+ size = 1;
+
+ if ((value->v.octetstring.octets = malloc(size)) == NULL) {
+ syslog(LOG_ERR, "malloc failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ value->v.octetstring.len = size;
+ for (i = 0, bits = 0; i < size; i++, bits += 8)
+ value->v.octetstring.octets[i] = (v & mask) >> bits;
+ value->syntax = SNMP_SYNTAX_OCTETSTRING;
+ return (1);
+}
diff --git a/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptc.h b/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptc.h
new file mode 100644
index 0000000..fd06676
--- /dev/null
+++ b/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptc.h
@@ -0,0 +1,95 @@
+/*-
+ * Copyright (c) 2006 The FreeBSD Project
+ * All rights reserved.
+ *
+ * Author: Shteryana Shopova <syrinx@FreeBSD.org>
+ *
+ * Redistribution of this software and documentation and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ *
+ * 1. Redistributions of source code or documentation must retain the above
+ * copyright notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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.
+ *
+ * Textual conventions for snmp
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _BSNMP_TEXT_CONV_H_
+#define _BSNMP_TEXT_CONV_H_
+
+/* Variable display length string. */
+#define SNMP_VAR_STRSZ -1
+
+/*
+ * 11 bytes - octets that represent DateAndTime Textual convention
+ * and the size of string used to diplay that.
+ */
+#define SNMP_DATETIME_OCTETS 11
+#define SNMP_DATETIME_STRSZ 32
+
+/*
+ * 6 bytes - octets that represent PhysAddress Textual convention
+ * and the size of string used to diplay that.
+ */
+#define SNMP_PHYSADDR_OCTETS 6
+#define SNMP_PHYSADDR_STRSZ 19
+
+/* NTPTimeStamp. */
+#define SNMP_NTP_TS_OCTETS 8
+#define SNMP_NTP_TS_STRSZ 10
+
+/* BridgeId. */
+#define SNMP_BRIDGEID_OCTETS 8
+#define SNMP_BRIDGEID_STRSZ 25
+#define SNMP_MAX_BRIDGE_PRIORITY 65535
+
+/* BridgePortId. */
+#define SNMP_BPORT_OCTETS 2
+#define SNMP_BPORT_STRSZ 7
+#define SNMP_MAX_BPORT_PRIORITY 255
+
+/* InetAddress. */
+#define SNMP_INADDRS_STRSZ INET6_ADDRSTRLEN
+
+enum snmp_tc {
+ SNMP_STRING = 0,
+ SNMP_DISPLAYSTRING = 1,
+ SNMP_DATEANDTIME = 2,
+ SNMP_PHYSADDR = 3,
+ SNMP_ATMESI = 4,
+ SNMP_NTP_TIMESTAMP = 5,
+ SNMP_MACADDRESS = 6,
+ SNMP_BRIDGE_ID = 7,
+ SNMP_BPORT_ID = 8,
+ SNMP_INETADDRESS = 9,
+ SNMP_TC_OWN = 10,
+ SNMP_UNKNOWN, /* keep last */
+};
+
+typedef char * (*snmp_oct2tc_f) (uint32_t len, char *octs, char *buf);
+typedef char * (*snmp_tc2oid_f) (char *str, struct asn_oid *oid);
+typedef int32_t (*snmp_tc2oct_f) (struct snmp_value *value, char *string);
+
+enum snmp_tc snmp_get_tc(char *str);
+char *snmp_oct2tc(enum snmp_tc tc, uint32_t len, char *octets);
+char *snmp_tc2oid(enum snmp_tc tc, char *str, struct asn_oid *oid);
+int32_t snmp_tc2oct(enum snmp_tc tc, struct snmp_value *value, char *string);
+
+#endif /* _BSNMP_TEXT_CONV_H_ */
diff --git a/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptools.c b/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptools.c
new file mode 100644
index 0000000..52aa1a9
--- /dev/null
+++ b/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptools.c
@@ -0,0 +1,2132 @@
+/*-
+ * Copyright (c) 2005-2006 The FreeBSD Project
+ * All rights reserved.
+ *
+ * Author: Shteryana Shopova <syrinx@FreeBSD.org>
+ *
+ * Redistribution of this software and documentation and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ *
+ * 1. Redistributions of source code or documentation must retain the above
+ * copyright notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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.
+ *
+ * Helper functions for snmp client tools
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <bsnmp/asn1.h>
+#include <bsnmp/snmp.h>
+#include <bsnmp/snmpclient.h>
+#include "bsnmptc.h"
+#include "bsnmptools.h"
+
+/* Internal varibale to turn on library debugging for testing and to
+ * find bugs. It is not exported via the header file.
+ * XXX should we cover it by some #ifdef BSNMPTOOLS_DEBUG? */
+int _bsnmptools_debug = 0;
+
+/* Default files to import mapping from if none explicitly provided. */
+#define bsnmpd_defs "/usr/share/snmp/defs/tree.def"
+#define mibII_defs "/usr/share/snmp/defs/mibII_tree.def"
+
+/*
+ * The .iso.org.dod oid that has to be prepended to every OID when requesting
+ * a value.
+ */
+const struct asn_oid IsoOrgDod_OID = {
+ 3, { 1, 3, 6 }
+};
+
+
+#define SNMP_ERR_UNKNOWN 0
+
+/*
+ * An array of error strings corresponding to error definitions from libbsnmp.
+ */
+static const struct {
+ const char *str;
+ int32_t error;
+} error_strings[] = {
+ { "Unknown", SNMP_ERR_UNKNOWN },
+ { "Too big ", SNMP_ERR_TOOBIG },
+ { "No such Name", SNMP_ERR_NOSUCHNAME },
+ { "Bad Value", SNMP_ERR_BADVALUE },
+ { "Readonly", SNMP_ERR_READONLY },
+ { "General error", SNMP_ERR_GENERR },
+ { "No access", SNMP_ERR_NO_ACCESS },
+ { "Wrong type", SNMP_ERR_WRONG_TYPE },
+ { "Wrong length", SNMP_ERR_WRONG_LENGTH },
+ { "Wrong encoding", SNMP_ERR_WRONG_ENCODING },
+ { "Wrong value", SNMP_ERR_WRONG_VALUE },
+ { "No creation", SNMP_ERR_NO_CREATION },
+ { "Inconsistent value", SNMP_ERR_INCONS_VALUE },
+ { "Resource unavailable", SNMP_ERR_RES_UNAVAIL },
+ { "Commit failed", SNMP_ERR_COMMIT_FAILED },
+ { "Undo failed", SNMP_ERR_UNDO_FAILED },
+ { "Authorization error", SNMP_ERR_AUTH_ERR },
+ { "Not writable", SNMP_ERR_NOT_WRITEABLE },
+ { "Inconsistent name", SNMP_ERR_INCONS_NAME },
+ { NULL, 0 }
+};
+
+/* This one and any following are exceptions. */
+#define SNMP_SYNTAX_UNKNOWN SNMP_SYNTAX_NOSUCHOBJECT
+
+static const struct {
+ const char *str;
+ enum snmp_syntax stx;
+} syntax_strings[] = {
+ { "Null", SNMP_SYNTAX_NULL },
+ { "Integer", SNMP_SYNTAX_INTEGER },
+ { "OctetString", SNMP_SYNTAX_OCTETSTRING },
+ { "OID", SNMP_SYNTAX_OID },
+ { "IpAddress", SNMP_SYNTAX_IPADDRESS },
+ { "Counter32", SNMP_SYNTAX_COUNTER },
+ { "Gauge", SNMP_SYNTAX_GAUGE },
+ { "TimeTicks", SNMP_SYNTAX_TIMETICKS },
+ { "Counter64", SNMP_SYNTAX_COUNTER64 },
+ { "Unknown", SNMP_SYNTAX_UNKNOWN },
+};
+
+int
+snmptool_init(struct snmp_toolinfo *snmptoolctx)
+{
+ char *str;
+ size_t slen;
+
+ memset(snmptoolctx, 0, sizeof(struct snmp_toolinfo));
+ snmptoolctx->objects = 0;
+ snmptoolctx->mappings = NULL;
+ snmptoolctx->flags = SNMP_PDU_GET; /* XXX */
+ SLIST_INIT(&snmptoolctx->filelist);
+ snmp_client_init(&snmp_client);
+ SET_MAXREP(snmptoolctx, SNMP_MAX_REPETITIONS);
+
+ if (add_filename(snmptoolctx, bsnmpd_defs, &IsoOrgDod_OID, 0) < 0)
+ warnx("Error adding file %s to list", bsnmpd_defs);
+
+ if (add_filename(snmptoolctx, mibII_defs, &IsoOrgDod_OID, 0) < 0)
+ warnx("Error adding file %s to list", mibII_defs);
+
+ /* Read the environment */
+ if ((str = getenv("SNMPAUTH")) != NULL) {
+ slen = strlen(str);
+ if (slen == strlen("md5") && strcasecmp(str, "md5") == 0)
+ snmp_client.user.auth_proto = SNMP_AUTH_HMAC_MD5;
+ else if (slen == strlen("sha")&& strcasecmp(str, "sha") == 0)
+ snmp_client.user.auth_proto = SNMP_AUTH_HMAC_SHA;
+ else if (slen != 0)
+ warnx("Bad authentication type - %s in SNMPAUTH", str);
+ }
+
+ if ((str = getenv("SNMPPRIV")) != NULL) {
+ slen = strlen(str);
+ if (slen == strlen("des") && strcasecmp(str, "des") == 0)
+ snmp_client.user.priv_proto = SNMP_PRIV_DES;
+ else if (slen == strlen("aes")&& strcasecmp(str, "aes") == 0)
+ snmp_client.user.priv_proto = SNMP_PRIV_AES;
+ else if (slen != 0)
+ warnx("Bad privacy type - %s in SNMPPRIV", str);
+ }
+
+ if ((str = getenv("SNMPUSER")) != NULL) {
+ if ((slen = strlen(str)) > sizeof(snmp_client.user.sec_name)) {
+ warnx("Username too long - %s in SNMPUSER", str);
+ return (-1);
+ }
+ if (slen > 0) {
+ strlcpy(snmp_client.user.sec_name, str,
+ sizeof(snmp_client.user.sec_name));
+ snmp_client.version = SNMP_V3;
+ }
+ }
+
+ if ((str = getenv("SNMPPASSWD")) != NULL) {
+ if ((slen = strlen(str)) > MAXSTR)
+ slen = MAXSTR - 1;
+ if ((snmptoolctx->passwd = malloc(slen + 1)) == NULL) {
+ warnx("malloc() failed - %s", strerror(errno));
+ return (-1);
+ }
+ if (slen > 0)
+ strlcpy(snmptoolctx->passwd, str, slen + 1);
+ }
+
+ return (0);
+}
+
+#define OBJECT_IDX_LIST(o) o->info->table_idx->index_list
+
+/*
+ * Walk through the file list and import string<->oid mappings from each file.
+ */
+int32_t
+snmp_import_all(struct snmp_toolinfo *snmptoolctx)
+{
+ int32_t fc;
+ struct fname *tmp;
+
+ if (snmptoolctx == NULL)
+ return (-1);
+
+ if (ISSET_NUMERIC(snmptoolctx))
+ return (0);
+
+ if ((snmptoolctx->mappings = snmp_mapping_init()) == NULL)
+ return (-1);
+
+ fc = 0;
+ if (SLIST_EMPTY(&snmptoolctx->filelist)) {
+ warnx("No files to read OID <-> string conversions from");
+ return (-1);
+ } else {
+ SLIST_FOREACH(tmp, &snmptoolctx->filelist, link) {
+ if (tmp->done)
+ continue;
+ if (snmp_import_file(snmptoolctx, tmp) < 0) {
+ fc = -1;
+ break;
+ }
+ fc++;
+ }
+ }
+
+ snmp_mapping_dump(snmptoolctx);
+ return (fc);
+}
+
+/*
+ * Add a filename to the file list - the initial idea of keeping a list with all
+ * files to read OIDs from was that an application might want to have loaded in
+ * memory the OIDs from a single file only and when done with them read the OIDs
+ * from another file. This is not used yet but might be a good idea at some
+ * point. Size argument is number of bytes in string including trailing '\0',
+ * not string length.
+ */
+int32_t
+add_filename(struct snmp_toolinfo *snmptoolctx, const char *filename,
+ const struct asn_oid *cut, int32_t done)
+{
+ char *fstring;
+ struct fname *entry;
+
+ if (snmptoolctx == NULL)
+ return (-1);
+
+ /* Make sure file was not in list. */
+ SLIST_FOREACH(entry, &snmptoolctx->filelist, link) {
+ if (strncmp(entry->name, filename, strlen(entry->name)) == 0)
+ return (0);
+ }
+
+ if ((fstring = malloc(strlen(filename) + 1)) == NULL) {
+ warnx("malloc() failed - %s", strerror(errno));
+ return (-1);
+ }
+
+ if ((entry = malloc(sizeof(struct fname))) == NULL) {
+ warnx("malloc() failed - %s", strerror(errno));
+ free(fstring);
+ return (-1);
+ }
+
+ memset(entry, 0, sizeof(struct fname));
+
+ if (cut != NULL)
+ asn_append_oid(&(entry->cut), cut);
+ strlcpy(fstring, filename, strlen(filename) + 1);
+ entry->name = fstring;
+ entry->done = done;
+ SLIST_INSERT_HEAD(&snmptoolctx->filelist, entry, link);
+
+ return (1);
+}
+
+void
+free_filelist(struct snmp_toolinfo *snmptoolctx)
+{
+ struct fname *f;
+
+ if (snmptoolctx == NULL)
+ return; /* XXX error handling */
+
+ while ((f = SLIST_FIRST(&snmptoolctx->filelist)) != NULL) {
+ SLIST_REMOVE_HEAD(&snmptoolctx->filelist, link);
+ if (f->name)
+ free(f->name);
+ free(f);
+ }
+}
+
+static char
+isvalid_fchar(char c, int pos)
+{
+ if (isalpha(c)|| c == '/'|| c == '_' || c == '.' || c == '~' ||
+ (pos != 0 && isdigit(c))){
+ return (c);
+ }
+
+ if (c == '\0')
+ return (0);
+
+ if (!isascii(c) || !isprint(c))
+ warnx("Unexpected character %#2x", (u_int) c);
+ else
+ warnx("Illegal character '%c'", c);
+
+ return (-1);
+}
+
+/*
+ * Re-implement getsubopt from scratch, because the second argument is broken
+ * and will not compile with WARNS=5.
+ * Copied from src/contrib/bsnmp/snmpd/main.c.
+ */
+static int
+getsubopt1(char **arg, const char *const *options, char **valp, char **optp)
+{
+ static const char *const delim = ",\t ";
+ u_int i;
+ char *ptr;
+
+ *optp = NULL;
+
+ /* Skip leading junk. */
+ for (ptr = *arg; *ptr != '\0'; ptr++)
+ if (strchr(delim, *ptr) == NULL)
+ break;
+ if (*ptr == '\0') {
+ *arg = ptr;
+ return (-1);
+ }
+ *optp = ptr;
+
+ /* Find the end of the option. */
+ while (*++ptr != '\0')
+ if (strchr(delim, *ptr) != NULL || *ptr == '=')
+ break;
+
+ if (*ptr != '\0') {
+ if (*ptr == '=') {
+ *ptr++ = '\0';
+ *valp = ptr;
+ while (*ptr != '\0' && strchr(delim, *ptr) == NULL)
+ ptr++;
+ if (*ptr != '\0')
+ *ptr++ = '\0';
+ } else
+ *ptr++ = '\0';
+ }
+
+ *arg = ptr;
+
+ for (i = 0; *options != NULL; options++, i++)
+ if (strcmp(*optp, *options) == 0)
+ return (i);
+ return (-1);
+}
+
+static int32_t
+parse_path(char *value)
+{
+ int32_t i, len;
+
+ if (value == NULL)
+ return (-1);
+
+ for (len = 0; len < MAXPATHLEN; len++) {
+ i = isvalid_fchar(*(value + len), len) ;
+
+ if (i == 0)
+ break;
+ else if (i < 0)
+ return (-1);
+ }
+
+ if (len >= MAXPATHLEN || value[len] != '\0') {
+ warnx("Bad pathname - '%s'", value);
+ return (-1);
+ }
+
+ return (len);
+}
+
+static int32_t
+parse_flist(struct snmp_toolinfo *snmptoolctx, char *value, char *path,
+ const struct asn_oid *cut)
+{
+ int32_t namelen;
+ char filename[MAXPATHLEN + 1];
+
+ if (value == NULL)
+ return (-1);
+
+ do {
+ memset(filename, 0, MAXPATHLEN + 1);
+
+ if (isalpha(*value) && (path == NULL || path[0] == '\0')) {
+ strlcpy(filename, SNMP_DEFS_DIR, MAXPATHLEN + 1);
+ namelen = strlen(SNMP_DEFS_DIR);
+ } else if (path != NULL){
+ strlcpy(filename, path, MAXPATHLEN + 1);
+ namelen = strlen(path);
+ } else
+ namelen = 0;
+
+ for ( ; namelen < MAXPATHLEN; value++) {
+ if (isvalid_fchar(*value, namelen) > 0) {
+ filename[namelen++] = *value;
+ continue;
+ }
+
+ if (*value == ',' )
+ value++;
+ else if (*value == '\0')
+ ;
+ else {
+ if (!isascii(*value) || !isprint(*value))
+ warnx("Unexpected character %#2x in"
+ " filename", (u_int) *value);
+ else
+ warnx("Illegal character '%c' in"
+ " filename", *value);
+ return (-1);
+ }
+
+ filename[namelen]='\0';
+ break;
+ }
+
+ if ((namelen == MAXPATHLEN) && (filename[MAXPATHLEN] != '\0')) {
+ warnx("Filename %s too long", filename);
+ return (-1);
+ }
+
+ if (add_filename(snmptoolctx, filename, cut, 0) < 0) {
+ warnx("Error adding file %s to list", filename);
+ return (-1);
+ }
+ } while (*value != '\0');
+
+ return(1);
+}
+
+static int32_t
+parse_ascii(char *ascii, uint8_t *binstr, size_t binlen)
+{
+ int32_t alen, count, saved_errno, i;
+ uint32_t val;
+ char dptr[3];
+
+ /* Filter 0x at the beginning */
+ if ((alen = strlen(ascii)) > 2 && ascii[0] == '0' && ascii[1] == 'x')
+ i = 2;
+ else
+ i = 0;
+
+ saved_errno = errno;
+ errno = 0;
+ for (count = 0; i < alen; i += 2) {
+ /* XXX: consider strlen(ascii) % 2 != 0 */
+ dptr[0] = ascii[i];
+ dptr[1] = ascii[i + 1];
+ dptr[2] = '\0';
+ if ((val = strtoul(dptr, NULL, 16)) > 0xFF || errno != 0) {
+ errno = saved_errno;
+ return (-1);
+ }
+ binstr[count] = (uint8_t) val;
+ if (++count >= binlen) {
+ warnx("Key %s too long - truncating to %zu octets",
+ ascii, binlen);
+ break;
+ }
+ }
+
+ return (count);
+}
+
+/*
+ * Functions to parse common input options for client tools and fill in the
+ * snmp_client structure.
+ */
+int32_t
+parse_authentication(struct snmp_toolinfo *snmptoolctx, char *opt_arg)
+{
+ int32_t count, subopt;
+ char *val, *option;
+ const char *const subopts[] = {
+ "proto",
+ "key",
+ NULL
+ };
+
+ assert(opt_arg != NULL);
+ count = 1;
+ while ((subopt = getsubopt1(&opt_arg, subopts, &val, &option)) != EOF) {
+ switch (subopt) {
+ case 0:
+ if (val == NULL) {
+ warnx("Suboption 'proto' requires an argument");
+ return (-1);
+ }
+ if (strlen(val) != 3) {
+ warnx("Unknown auth protocol - %s", val);
+ return (-1);
+ }
+ if (strncasecmp("md5", val, strlen("md5")) == 0)
+ snmp_client.user.auth_proto =
+ SNMP_AUTH_HMAC_MD5;
+ else if (strncasecmp("sha", val, strlen("sha")) == 0)
+ snmp_client.user.auth_proto =
+ SNMP_AUTH_HMAC_SHA;
+ else {
+ warnx("Unknown auth protocol - %s", val);
+ return (-1);
+ }
+ break;
+ case 1:
+ if (val == NULL) {
+ warnx("Suboption 'key' requires an argument");
+ return (-1);
+ }
+ if (parse_ascii(val, snmp_client.user.auth_key,
+ SNMP_AUTH_KEY_SIZ) < 0) {
+ warnx("Bad authentication key- %s", val);
+ return (-1);
+ }
+ break;
+ default:
+ warnx("Unknown suboption - '%s'", suboptarg);
+ return (-1);
+ }
+ count += 1;
+ }
+ return (2/* count */);
+}
+
+int32_t
+parse_privacy(struct snmp_toolinfo *snmptoolctx, char *opt_arg)
+{
+ int32_t count, subopt;
+ char *val, *option;
+ const char *const subopts[] = {
+ "proto",
+ "key",
+ NULL
+ };
+
+ assert(opt_arg != NULL);
+ count = 1;
+ while ((subopt = getsubopt1(&opt_arg, subopts, &val, &option)) != EOF) {
+ switch (subopt) {
+ case 0:
+ if (val == NULL) {
+ warnx("Suboption 'proto' requires an argument");
+ return (-1);
+ }
+ if (strlen(val) != 3) {
+ warnx("Unknown privacy protocol - %s", val);
+ return (-1);
+ }
+ if (strncasecmp("aes", val, strlen("aes")) == 0)
+ snmp_client.user.priv_proto = SNMP_PRIV_AES;
+ else if (strncasecmp("des", val, strlen("des")) == 0)
+ snmp_client.user.priv_proto = SNMP_PRIV_DES;
+ else {
+ warnx("Unknown privacy protocol - %s", val);
+ return (-1);
+ }
+ break;
+ case 1:
+ if (val == NULL) {
+ warnx("Suboption 'key' requires an argument");
+ return (-1);
+ }
+ if (parse_ascii(val, snmp_client.user.priv_key,
+ SNMP_PRIV_KEY_SIZ) < 0) {
+ warnx("Bad privacy key- %s", val);
+ return (-1);
+ }
+ break;
+ default:
+ warnx("Unknown suboption - '%s'", suboptarg);
+ return (-1);
+ }
+ count += 1;
+ }
+ return (2/* count */);
+}
+
+int32_t
+parse_context(struct snmp_toolinfo *snmptoolctx, char *opt_arg)
+{
+ int32_t count, subopt;
+ char *val, *option;
+ const char *const subopts[] = {
+ "context",
+ "context-engine",
+ NULL
+ };
+
+ assert(opt_arg != NULL);
+ count = 1;
+ while ((subopt = getsubopt1(&opt_arg, subopts, &val, &option)) != EOF) {
+ switch (subopt) {
+ case 0:
+ if (val == NULL) {
+ warnx("Suboption 'context' - no argument");
+ return (-1);
+ }
+ strlcpy(snmp_client.cname, val, SNMP_CONTEXT_NAME_SIZ);
+ break;
+ case 1:
+ if (val == NULL) {
+ warnx("Suboption 'context-engine' - no argument");
+ return (-1);
+ }
+ if ((snmp_client.clen = parse_ascii(val,
+ snmp_client.cengine, SNMP_ENGINE_ID_SIZ)) < 0) {
+ warnx("Bad EngineID - %s", val);
+ return (-1);
+ }
+ break;
+ default:
+ warnx("Unknown suboption - '%s'", suboptarg);
+ return (-1);
+ }
+ count += 1;
+ }
+ return (2/* count */);
+}
+
+int32_t
+parse_user_security(struct snmp_toolinfo *snmptoolctx, char *opt_arg)
+{
+ int32_t count, subopt, saved_errno;
+ char *val, *option;
+ const char *const subopts[] = {
+ "engine",
+ "engine-boots",
+ "engine-time",
+ "name",
+ NULL
+ };
+
+ assert(opt_arg != NULL);
+ count = 1;
+ while ((subopt = getsubopt1(&opt_arg, subopts, &val, &option)) != EOF) {
+ switch (subopt) {
+ case 0:
+ if (val == NULL) {
+ warnx("Suboption 'engine' - no argument");
+ return (-1);
+ }
+ snmp_client.engine.engine_len = parse_ascii(val,
+ snmp_client.engine.engine_id, SNMP_ENGINE_ID_SIZ);
+ if (snmp_client.engine.engine_len < 0) {
+ warnx("Bad EngineID - %s", val);
+ return (-1);
+ }
+ break;
+ case 1:
+ if (val == NULL) {
+ warnx("Suboption 'engine-boots' - no argument");
+ return (-1);
+ }
+ saved_errno = errno;
+ errno = 0;
+ snmp_client.engine.engine_boots = strtoul(val, NULL, 10);
+ if (errno != 0) {
+ warnx("Bad 'engine-boots' value %s - %s", val,
+ strerror(errno));
+ errno = saved_errno;
+ return (-1);
+ }
+ errno = saved_errno;
+ break;
+ case 2:
+ if (val == NULL) {
+ warnx("Suboption 'engine-time' - no argument");
+ return (-1);
+ }
+ saved_errno = errno;
+ errno = 0;
+ snmp_client.engine.engine_time = strtoul(val, NULL, 10);
+ if (errno != 0) {
+ warnx("Bad 'engine-time' value %s - %s", val,
+ strerror(errno));
+ errno = saved_errno;
+ return (-1);
+ }
+ errno = saved_errno;
+ break;
+ case 3:
+ strlcpy(snmp_client.user.sec_name, val,
+ SNMP_ADM_STR32_SIZ);
+ break;
+ default:
+ warnx("Unknown suboption - '%s'", suboptarg);
+ return (-1);
+ }
+ count += 1;
+ }
+ return (2/* count */);
+}
+
+int32_t
+parse_file(struct snmp_toolinfo *snmptoolctx, char *opt_arg)
+{
+ assert(opt_arg != NULL);
+
+ if (parse_flist(snmptoolctx, opt_arg, NULL, &IsoOrgDod_OID) < 0)
+ return (-1);
+
+ return (2);
+}
+
+int32_t
+parse_include(struct snmp_toolinfo *snmptoolctx, char *opt_arg)
+{
+ char path[MAXPATHLEN + 1];
+ int32_t cut_dflt, len, subopt;
+ struct asn_oid cut;
+ char *val, *option;
+ const char *const subopts[] = {
+ "cut",
+ "path",
+ "file",
+ NULL
+ };
+
+#define INC_CUT 0
+#define INC_PATH 1
+#define INC_LIST 2
+
+ assert(opt_arg != NULL);
+
+ /* if (opt == 'i')
+ free_filelist(snmptoolctx, ); */
+ /*
+ * This function should be called only after getopt(3) - otherwise if
+ * no previous validation of opt_arg strlen() may not return what is
+ * expected.
+ */
+
+ path[0] = '\0';
+ memset(&cut, 0, sizeof(struct asn_oid));
+ cut_dflt = -1;
+
+ while ((subopt = getsubopt1(&opt_arg, subopts, &val, &option)) != EOF) {
+ switch (subopt) {
+ case INC_CUT:
+ if (val == NULL) {
+ warnx("Suboption 'cut' requires an argument");
+ return (-1);
+ } else {
+ if (snmp_parse_numoid(val, &cut) < 0)
+ return (-1);
+ }
+ cut_dflt = 1;
+ break;
+
+ case INC_PATH:
+ if ((len = parse_path(val)) < 0)
+ return (-1);
+ strlcpy(path, val, len + 1);
+ break;
+
+ case INC_LIST:
+ if (val == NULL)
+ return (-1);
+ if (cut_dflt == -1)
+ len = parse_flist(snmptoolctx, val, path, &IsoOrgDod_OID);
+ else
+ len = parse_flist(snmptoolctx, val, path, &cut);
+ if (len < 0)
+ return (-1);
+ break;
+
+ default:
+ warnx("Unknown suboption - '%s'", suboptarg);
+ return (-1);
+ }
+ }
+
+ /* XXX: Fix me - returning two is wrong here */
+ return (2);
+}
+
+int32_t
+parse_server(char *opt_arg)
+{
+ assert(opt_arg != NULL);
+
+ if (snmp_parse_server(&snmp_client, opt_arg) < 0)
+ return (-1);
+
+ if (snmp_client.trans > SNMP_TRANS_UDP && snmp_client.chost == NULL) {
+ if ((snmp_client.chost = malloc(strlen(SNMP_DEFAULT_LOCAL + 1)))
+ == NULL) {
+ syslog(LOG_ERR, "malloc() failed: %s", strerror(errno));
+ return (-1);
+ }
+ strcpy(snmp_client.chost, SNMP_DEFAULT_LOCAL);
+ }
+
+ return (2);
+}
+
+int32_t
+parse_timeout(char *opt_arg)
+{
+ int32_t v, saved_errno;
+
+ assert(opt_arg != NULL);
+
+ saved_errno = errno;
+ errno = 0;
+
+ v = strtol(opt_arg, NULL, 10);
+ if (errno != 0) {
+ warnx( "Error parsing timeout value - %s", strerror(errno));
+ errno = saved_errno;
+ return (-1);
+ }
+
+ snmp_client.timeout.tv_sec = v;
+ errno = saved_errno;
+ return (2);
+}
+
+int32_t
+parse_retry(char *opt_arg)
+{
+ uint32_t v;
+ int32_t saved_errno;
+
+ assert(opt_arg != NULL);
+
+ saved_errno = errno;
+ errno = 0;
+
+ v = strtoul(opt_arg, NULL, 10);
+ if (errno != 0) {
+ warnx("Error parsing retries count - %s", strerror(errno));
+ errno = saved_errno;
+ return (-1);
+ }
+
+ snmp_client.retries = v;
+ errno = saved_errno;
+ return (2);
+}
+
+int32_t
+parse_version(char *opt_arg)
+{
+ uint32_t v;
+ int32_t saved_errno;
+
+ assert(opt_arg != NULL);
+
+ saved_errno = errno;
+ errno = 0;
+
+ v = strtoul(opt_arg, NULL, 10);
+ if (errno != 0) {
+ warnx("Error parsing version - %s", strerror(errno));
+ errno = saved_errno;
+ return (-1);
+ }
+
+ switch (v) {
+ case 1:
+ snmp_client.version = SNMP_V1;
+ break;
+ case 2:
+ snmp_client.version = SNMP_V2c;
+ break;
+ case 3:
+ snmp_client.version = SNMP_V3;
+ break;
+ default:
+ warnx("Unsupported SNMP version - %u", v);
+ errno = saved_errno;
+ return (-1);
+ }
+
+ errno = saved_errno;
+ return (2);
+}
+
+int32_t
+parse_local_path(char *opt_arg)
+{
+ assert(opt_arg != NULL);
+
+ if (sizeof(opt_arg) > sizeof(SNMP_LOCAL_PATH)) {
+ warnx("Filename too long - %s", opt_arg);
+ return (-1);
+ }
+
+ strlcpy(snmp_client.local_path, opt_arg, sizeof(SNMP_LOCAL_PATH));
+ return (2);
+}
+
+int32_t
+parse_buflen(char *opt_arg)
+{
+ uint32_t size;
+ int32_t saved_errno;
+
+ assert(opt_arg != NULL);
+
+ saved_errno = errno;
+ errno = 0;
+
+ size = strtoul(opt_arg, NULL, 10);
+ if (errno != 0) {
+ warnx("Error parsing buffer size - %s", strerror(errno));
+ errno = saved_errno;
+ return (-1);
+ }
+
+ if (size > MAX_BUFF_SIZE) {
+ warnx("Buffer size too big - %d max allowed", MAX_BUFF_SIZE);
+ errno = saved_errno;
+ return (-1);
+ }
+
+ snmp_client.txbuflen = snmp_client.rxbuflen = size;
+ errno = saved_errno;
+ return (2);
+}
+
+int32_t
+parse_debug(void)
+{
+ snmp_client.dump_pdus = 1;
+ return (1);
+}
+
+int32_t
+parse_discovery(struct snmp_toolinfo *snmptoolctx)
+{
+ SET_EDISCOVER(snmptoolctx);
+ snmp_client.version = SNMP_V3;
+ return (1);
+}
+
+int32_t
+parse_local_key(struct snmp_toolinfo *snmptoolctx)
+{
+ SET_LOCALKEY(snmptoolctx);
+ snmp_client.version = SNMP_V3;
+ return (1);
+}
+
+int32_t
+parse_num_oids(struct snmp_toolinfo *snmptoolctx)
+{
+ SET_NUMERIC(snmptoolctx);
+ return (1);
+}
+
+int32_t
+parse_output(struct snmp_toolinfo *snmptoolctx, char *opt_arg)
+{
+ assert(opt_arg != NULL);
+
+ if (strlen(opt_arg) > strlen("verbose")) {
+ warnx( "Invalid output option - %s",opt_arg);
+ return (-1);
+ }
+
+ if (strncasecmp(opt_arg, "short", strlen(opt_arg)) == 0)
+ SET_OUTPUT(snmptoolctx, OUTPUT_SHORT);
+ else if (strncasecmp(opt_arg, "verbose", strlen(opt_arg)) == 0)
+ SET_OUTPUT(snmptoolctx, OUTPUT_VERBOSE);
+ else if (strncasecmp(opt_arg,"tabular", strlen(opt_arg)) == 0)
+ SET_OUTPUT(snmptoolctx, OUTPUT_TABULAR);
+ else if (strncasecmp(opt_arg, "quiet", strlen(opt_arg)) == 0)
+ SET_OUTPUT(snmptoolctx, OUTPUT_QUIET);
+ else {
+ warnx( "Invalid output option - %s", opt_arg);
+ return (-1);
+ }
+
+ return (2);
+}
+
+int32_t
+parse_errors(struct snmp_toolinfo *snmptoolctx)
+{
+ SET_RETRY(snmptoolctx);
+ return (1);
+}
+
+int32_t
+parse_skip_access(struct snmp_toolinfo *snmptoolctx)
+{
+ SET_ERRIGNORE(snmptoolctx);
+ return (1);
+}
+
+char *
+snmp_parse_suboid(char *str, struct asn_oid *oid)
+{
+ char *endptr;
+ asn_subid_t suboid;
+
+ if (*str == '.')
+ str++;
+
+ if (*str < '0' || *str > '9')
+ return (str);
+
+ do {
+ suboid = strtoul(str, &endptr, 10);
+ if ((asn_subid_t) suboid > ASN_MAXID) {
+ warnx("Suboid %u > ASN_MAXID", suboid);
+ return (NULL);
+ }
+ if (snmp_suboid_append(oid, suboid) < 0)
+ return (NULL);
+ str = endptr + 1;
+ } while (*endptr == '.');
+
+ return (endptr);
+}
+
+static char *
+snmp_int2asn_oid(char *str, struct asn_oid *oid)
+{
+ char *endptr;
+ int32_t v, saved_errno;
+
+ saved_errno = errno;
+ errno = 0;
+
+ v = strtol(str, &endptr, 10);
+ if (errno != 0) {
+ warnx("Integer value %s not supported - %s", str,
+ strerror(errno));
+ errno = saved_errno;
+ return (NULL);
+ }
+ errno = saved_errno;
+
+ if (snmp_suboid_append(oid, (asn_subid_t) v) < 0)
+ return (NULL);
+
+ return (endptr);
+}
+
+/* It is a bit weird to have a table indexed by OID but still... */
+static char *
+snmp_oid2asn_oid(struct snmp_toolinfo *snmptoolctx, char *str,
+ struct asn_oid *oid)
+{
+ int32_t i;
+ char string[MAXSTR], *endptr;
+ struct snmp_object obj;
+
+ for (i = 0; i < MAXSTR; i++)
+ if (isalpha (*(str + i)) == 0)
+ break;
+
+ endptr = str + i;
+ memset(&obj, 0, sizeof(struct snmp_object));
+ if (i == 0) {
+ if ((endptr = snmp_parse_suboid(str, &(obj.val.var))) == NULL)
+ return (NULL);
+ if (snmp_suboid_append(oid, (asn_subid_t) obj.val.var.len) < 0)
+ return (NULL);
+ } else {
+ strlcpy(string, str, i + 1);
+ string[i] = '\0';
+ if (snmp_lookup_enumoid(snmptoolctx, &obj, string) < 0) {
+ warnx("Unknown string - %s",string);
+ return (NULL);
+ }
+ free(string);
+ }
+
+ asn_append_oid(oid, &(obj.val.var));
+ return (endptr);
+}
+
+static char *
+snmp_ip2asn_oid(char *str, struct asn_oid *oid)
+{
+ uint32_t v;
+ int32_t i;
+ char *endptr, *ptr;
+
+ ptr = str;
+ for (i = 0; i < 4; i++) {
+ v = strtoul(ptr, &endptr, 10);
+ if (v > 0xff)
+ return (NULL);
+ if (*endptr != '.' && strchr("],\0", *endptr) == NULL && i != 3)
+ return (NULL);
+ if (snmp_suboid_append(oid, (asn_subid_t) v) < 0)
+ return (NULL);
+ ptr = endptr + 1;
+ }
+
+ return (endptr);
+}
+
+/* 32-bit counter, gauge, timeticks. */
+static char *
+snmp_uint2asn_oid(char *str, struct asn_oid *oid)
+{
+ char *endptr;
+ uint32_t v;
+ int32_t saved_errno;
+
+ saved_errno = errno;
+ errno = 0;
+
+ v = strtoul(str, &endptr, 10);
+ if (errno != 0) {
+ warnx("Integer value %s not supported - %s\n", str,
+ strerror(errno));
+ errno = saved_errno;
+ return (NULL);
+ }
+ errno = saved_errno;
+ if (snmp_suboid_append(oid, (asn_subid_t) v) < 0)
+ return (NULL);
+
+ return (endptr);
+}
+
+static char *
+snmp_cnt64_2asn_oid(char *str, struct asn_oid *oid)
+{
+ char *endptr;
+ uint64_t v;
+ int32_t saved_errno;
+
+ saved_errno = errno;
+ errno = 0;
+
+ v = strtoull(str, &endptr, 10);
+
+ if (errno != 0) {
+ warnx("Integer value %s not supported - %s", str,
+ strerror(errno));
+ errno = saved_errno;
+ return (NULL);
+ }
+ errno = saved_errno;
+ if (snmp_suboid_append(oid, (asn_subid_t) (v & 0xffffffff)) < 0)
+ return (NULL);
+
+ if (snmp_suboid_append(oid, (asn_subid_t) (v >> 32)) < 0)
+ return (NULL);
+
+ return (endptr);
+}
+
+enum snmp_syntax
+parse_syntax(char *str)
+{
+ int32_t i;
+
+ for (i = 0; i < SNMP_SYNTAX_UNKNOWN; i++) {
+ if (strncmp(syntax_strings[i].str, str,
+ strlen(syntax_strings[i].str)) == 0)
+ return (syntax_strings[i].stx);
+ }
+
+ return (SNMP_SYNTAX_NULL);
+}
+
+static char *
+snmp_parse_subindex(struct snmp_toolinfo *snmptoolctx, char *str,
+ struct index *idx, struct snmp_object *object)
+{
+ char *ptr;
+ int32_t i;
+ enum snmp_syntax stx;
+ char syntax[MAX_CMD_SYNTAX_LEN];
+
+ ptr = str;
+ if (GET_OUTPUT(snmptoolctx) == OUTPUT_VERBOSE) {
+ for (i = 0; i < MAX_CMD_SYNTAX_LEN ; i++) {
+ if (*(ptr + i) == ':')
+ break;
+ }
+
+ if (i >= MAX_CMD_SYNTAX_LEN) {
+ warnx("Unknown syntax in OID - %s", str);
+ return (NULL);
+ }
+ /* Expect a syntax string here. */
+ if ((stx = parse_syntax(str)) <= SNMP_SYNTAX_NULL) {
+ warnx("Invalid syntax - %s",syntax);
+ return (NULL);
+ }
+
+ if (stx != idx->syntax && !ISSET_ERRIGNORE(snmptoolctx)) {
+ warnx("Syntax mismatch - %d expected, %d given",
+ idx->syntax, stx);
+ return (NULL);
+ }
+ /*
+ * That is where the suboid started + the syntax length + one
+ * character for ':'.
+ */
+ ptr = str + i + 1;
+ } else
+ stx = idx->syntax;
+
+ switch (stx) {
+ case SNMP_SYNTAX_INTEGER:
+ return (snmp_int2asn_oid(ptr, &(object->val.var)));
+ case SNMP_SYNTAX_OID:
+ return (snmp_oid2asn_oid(snmptoolctx, ptr,
+ &(object->val.var)));
+ case SNMP_SYNTAX_IPADDRESS:
+ return (snmp_ip2asn_oid(ptr, &(object->val.var)));
+ case SNMP_SYNTAX_COUNTER:
+ /* FALLTHROUGH */
+ case SNMP_SYNTAX_GAUGE:
+ /* FALLTHROUGH */
+ case SNMP_SYNTAX_TIMETICKS:
+ return (snmp_uint2asn_oid(ptr, &(object->val.var)));
+ case SNMP_SYNTAX_COUNTER64:
+ return (snmp_cnt64_2asn_oid(ptr, &(object->val.var)));
+ case SNMP_SYNTAX_OCTETSTRING:
+ return (snmp_tc2oid(idx->tc, ptr, &(object->val.var)));
+ default:
+ /* NOTREACHED */
+ break;
+ }
+
+ return (NULL);
+}
+
+char *
+snmp_parse_index(struct snmp_toolinfo *snmptoolctx, char *str,
+ struct snmp_object *object)
+{
+ char *ptr;
+ struct index *temp;
+
+ if (object->info->table_idx == NULL)
+ return (NULL);
+
+ ptr = NULL;
+ STAILQ_FOREACH(temp, &(OBJECT_IDX_LIST(object)), link) {
+ if ((ptr = snmp_parse_subindex(snmptoolctx, str, temp, object))
+ == NULL)
+ return (NULL);
+
+ if (*ptr != ',' && *ptr != ']')
+ return (NULL);
+ str = ptr + 1;
+ }
+
+ if (ptr == NULL || *ptr != ']') {
+ warnx("Mismatching index - %s", str);
+ return (NULL);
+ }
+
+ return (ptr + 1);
+}
+
+/*
+ * Fill in the struct asn_oid member of snmp_value with suboids from input.
+ * If an error occurs - print message on stderr and return (-1).
+ * If all is ok - return the length of the oid.
+ */
+int32_t
+snmp_parse_numoid(char *argv, struct asn_oid *var)
+{
+ char *endptr, *str;
+ asn_subid_t suboid;
+
+ str = argv;
+
+ if (*str == '.')
+ str++;
+
+ do {
+ if (var->len == ASN_MAXOIDLEN) {
+ warnx("Oid too long - %u", var->len);
+ return (-1);
+ }
+
+ suboid = strtoul(str, &endptr, 10);
+ if (suboid > ASN_MAXID) {
+ warnx("Oid too long - %u", var->len);
+ return (-1);
+ }
+
+ var->subs[var->len++] = suboid;
+ str = endptr + 1;
+ } while ( *endptr == '.');
+
+ if (*endptr != '\0') {
+ warnx("Invalid oid string - %s", argv);
+ return (-1);
+ }
+
+ return (var->len);
+}
+
+/* Append a length 1 suboid to an asn_oid structure. */
+int32_t
+snmp_suboid_append(struct asn_oid *var, asn_subid_t suboid)
+{
+ if (var == NULL)
+ return (-1);
+
+ if (var->len >= ASN_MAXOIDLEN) {
+ warnx("Oid too long - %u", var->len);
+ return (-1);
+ }
+
+ var->subs[var->len++] = suboid;
+
+ return (1);
+}
+
+/* Pop the last suboid from an asn_oid structure. */
+int32_t
+snmp_suboid_pop(struct asn_oid *var)
+{
+ asn_subid_t suboid;
+
+ if (var == NULL)
+ return (-1);
+
+ if (var->len < 1)
+ return (-1);
+
+ suboid = var->subs[--(var->len)];
+ var->subs[var->len] = 0;
+
+ return (suboid);
+}
+
+/*
+ * Parse the command-line provided string into an OID - alocate memory for a new
+ * snmp object, fill in its fields and insert it in the object list. A
+ * (snmp_verify_inoid_f) function must be provided to validate the input string.
+ */
+int32_t
+snmp_object_add(struct snmp_toolinfo *snmptoolctx, snmp_verify_inoid_f func,
+ char *string)
+{
+ struct snmp_object *obj;
+
+ if (snmptoolctx == NULL)
+ return (-1);
+
+ /* XXX-BZ does that chack make sense? */
+ if (snmptoolctx->objects >= SNMP_MAX_BINDINGS) {
+ warnx("Too many bindings in PDU - %u", snmptoolctx->objects + 1);
+ return (-1);
+ }
+
+ if ((obj = malloc(sizeof(struct snmp_object))) == NULL) {
+ syslog(LOG_ERR, "malloc() failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ memset(obj, 0, sizeof(struct snmp_object));
+ if (func(snmptoolctx, obj, string) < 0) {
+ warnx("Invalid OID - %s", string);
+ free(obj);
+ return (-1);
+ }
+
+ snmptoolctx->objects++;
+ SLIST_INSERT_HEAD(&snmptoolctx->snmp_objectlist, obj, link);
+
+ return (1);
+}
+
+/* Given an OID, find it in the object list and remove it. */
+int32_t
+snmp_object_remove(struct snmp_toolinfo *snmptoolctx, struct asn_oid *oid)
+{
+ struct snmp_object *temp;
+
+ if (SLIST_EMPTY(&snmptoolctx->snmp_objectlist)) {
+ warnx("Object list already empty");
+ return (-1);
+ }
+
+
+ SLIST_FOREACH(temp, &snmptoolctx->snmp_objectlist, link)
+ if (asn_compare_oid(&(temp->val.var), oid) == 0)
+ break;
+
+ if (temp == NULL) {
+ warnx("No such object in list");
+ return (-1);
+ }
+
+ SLIST_REMOVE(&snmptoolctx->snmp_objectlist, temp, snmp_object, link);
+ if (temp->val.syntax == SNMP_SYNTAX_OCTETSTRING &&
+ temp->val.v.octetstring.octets != NULL)
+ free(temp->val.v.octetstring.octets);
+ free(temp);
+
+ return (1);
+}
+
+static void
+snmp_object_freeall(struct snmp_toolinfo *snmptoolctx)
+{
+ struct snmp_object *o;
+
+ while ((o = SLIST_FIRST(&snmptoolctx->snmp_objectlist)) != NULL) {
+ SLIST_REMOVE_HEAD(&snmptoolctx->snmp_objectlist, link);
+
+ if (o->val.syntax == SNMP_SYNTAX_OCTETSTRING &&
+ o->val.v.octetstring.octets != NULL)
+ free(o->val.v.octetstring.octets);
+ free(o);
+ }
+}
+
+/* Do all possible memory release before exit. */
+void
+snmp_tool_freeall(struct snmp_toolinfo *snmptoolctx)
+{
+ if (snmp_client.chost != NULL) {
+ free(snmp_client.chost);
+ snmp_client.chost = NULL;
+ }
+
+ if (snmp_client.cport != NULL) {
+ free(snmp_client.cport);
+ snmp_client.cport = NULL;
+ }
+
+ snmp_mapping_free(snmptoolctx);
+ free_filelist(snmptoolctx);
+ snmp_object_freeall(snmptoolctx);
+
+ if (snmptoolctx->passwd != NULL) {
+ free(snmptoolctx->passwd);
+ snmptoolctx->passwd = NULL;
+ }
+}
+
+/*
+ * Fill all variables from the object list into a PDU. (snmp_verify_vbind_f)
+ * function should check whether the variable is consistent in this PDU
+ * (e.g do not add non-leaf OIDs to a GET PDU, or OIDs with read access only to
+ * a SET PDU) - might be NULL though. (snmp_add_vbind_f) function is the
+ * function actually adds the variable to the PDU and must not be NULL.
+ */
+int32_t
+snmp_pdu_add_bindings(struct snmp_toolinfo *snmptoolctx,
+ snmp_verify_vbind_f vfunc, snmp_add_vbind_f afunc,
+ struct snmp_pdu *pdu, int32_t maxcount)
+{
+ int32_t nbindings, abind;
+ struct snmp_object *obj;
+
+ if (pdu == NULL || afunc == NULL)
+ return (-1);
+
+ /* Return 0 in case of no more work todo. */
+ if (SLIST_EMPTY(&snmptoolctx->snmp_objectlist))
+ return (0);
+
+ if (maxcount < 0 || maxcount > SNMP_MAX_BINDINGS) {
+ warnx("maxcount out of range: <0 || >SNMP_MAX_BINDINGS");
+ return (-1);
+ }
+
+ nbindings = 0;
+ SLIST_FOREACH(obj, &snmptoolctx->snmp_objectlist, link) {
+ if ((vfunc != NULL) && (vfunc(snmptoolctx, pdu, obj) < 0)) {
+ nbindings = -1;
+ break;
+ }
+ if ((abind = afunc(pdu, obj)) < 0) {
+ nbindings = -1;
+ break;
+ }
+
+ if (abind > 0) {
+ /* Do not put more varbindings than requested. */
+ if (++nbindings >= maxcount)
+ break;
+ }
+ }
+
+ return (nbindings);
+}
+
+/*
+ * Locate an object in the object list and set a corresponding error status.
+ */
+int32_t
+snmp_object_seterror(struct snmp_toolinfo *snmptoolctx,
+ struct snmp_value *err_value, int32_t error_status)
+{
+ struct snmp_object *obj;
+
+ if (SLIST_EMPTY(&snmptoolctx->snmp_objectlist) || err_value == NULL)
+ return (-1);
+
+ SLIST_FOREACH(obj, &snmptoolctx->snmp_objectlist, link)
+ if (asn_compare_oid(&(err_value->var), &(obj->val.var)) == 0) {
+ obj->error = error_status;
+ return (1);
+ }
+
+ return (0);
+}
+
+/*
+ * Check a PDU received in response to a SNMP_PDU_GET/SNMP_PDU_GETBULK request
+ * but don't compare syntaxes - when sending a request PDU they must be null.
+ * This is a (almost) complete copy of snmp_pdu_check() - with matching syntaxes
+ * checks and some other checks skipped.
+ */
+int32_t
+snmp_parse_get_resp(struct snmp_pdu *resp, struct snmp_pdu *req)
+{
+ uint32_t i;
+
+ for (i = 0; i < req->nbindings; i++) {
+ if (asn_compare_oid(&req->bindings[i].var,
+ &resp->bindings[i].var) != 0) {
+ warnx("Bad OID in response");
+ return (-1);
+ }
+
+ if (snmp_client.version != SNMP_V1 && (resp->bindings[i].syntax
+ == SNMP_SYNTAX_NOSUCHOBJECT || resp->bindings[i].syntax ==
+ SNMP_SYNTAX_NOSUCHINSTANCE))
+ return (0);
+ }
+
+ return (1);
+}
+
+int32_t
+snmp_parse_getbulk_resp(struct snmp_pdu *resp, struct snmp_pdu *req)
+{
+ int32_t N, R, M, r;
+
+ if (req->error_status > (int32_t) resp->nbindings) {
+ warnx("Bad number of bindings in response");
+ return (-1);
+ }
+
+ for (N = 0; N < req->error_status; N++) {
+ if (asn_is_suboid(&req->bindings[N].var,
+ &resp->bindings[N].var) == 0)
+ return (0);
+ if (resp->bindings[N].syntax == SNMP_SYNTAX_ENDOFMIBVIEW)
+ return (0);
+ }
+
+ for (R = N , r = N; R < (int32_t) req->nbindings; R++) {
+ for (M = 0; M < req->error_index && (r + M) <
+ (int32_t) resp->nbindings; M++) {
+ if (asn_is_suboid(&req->bindings[R].var,
+ &resp->bindings[r + M].var) == 0)
+ return (0);
+
+ if (resp->bindings[r + M].syntax ==
+ SNMP_SYNTAX_ENDOFMIBVIEW) {
+ M++;
+ break;
+ }
+ }
+ r += M;
+ }
+
+ return (0);
+}
+
+int32_t
+snmp_parse_getnext_resp(struct snmp_pdu *resp, struct snmp_pdu *req)
+{
+ uint32_t i;
+
+ for (i = 0; i < req->nbindings; i++) {
+ if (asn_is_suboid(&req->bindings[i].var, &resp->bindings[i].var)
+ == 0)
+ return (0);
+
+ if (resp->version != SNMP_V1 && resp->bindings[i].syntax ==
+ SNMP_SYNTAX_ENDOFMIBVIEW)
+ return (0);
+ }
+
+ return (1);
+}
+
+/*
+ * Should be called to check a response to get/getnext/getbulk.
+ */
+int32_t
+snmp_parse_resp(struct snmp_pdu *resp, struct snmp_pdu *req)
+{
+ if (resp == NULL || req == NULL)
+ return (-2);
+
+ if (resp->version != req->version) {
+ warnx("Response has wrong version");
+ return (-1);
+ }
+
+ if (resp->error_status == SNMP_ERR_NOSUCHNAME) {
+ warnx("Error - No Such Name");
+ return (0);
+ }
+
+ if (resp->error_status != SNMP_ERR_NOERROR) {
+ warnx("Error %d in response", resp->error_status);
+ return (-1);
+ }
+
+ if (resp->nbindings != req->nbindings && req->type != SNMP_PDU_GETBULK){
+ warnx("Bad number of bindings in response");
+ return (-1);
+ }
+
+ switch (req->type) {
+ case SNMP_PDU_GET:
+ return (snmp_parse_get_resp(resp,req));
+ case SNMP_PDU_GETBULK:
+ return (snmp_parse_getbulk_resp(resp,req));
+ case SNMP_PDU_GETNEXT:
+ return (snmp_parse_getnext_resp(resp,req));
+ default:
+ /* NOTREACHED */
+ break;
+ }
+
+ return (-2);
+}
+
+static void
+snmp_output_octetstring(struct snmp_toolinfo *snmptoolctx, enum snmp_tc tc,
+ uint32_t len, uint8_t *octets)
+{
+ char *buf;
+
+ if (len == 0 || octets == NULL)
+ return;
+
+ if (GET_OUTPUT(snmptoolctx) == OUTPUT_VERBOSE)
+ fprintf(stdout, "%s : ",
+ syntax_strings[SNMP_SYNTAX_OCTETSTRING].str);
+
+ if ((buf = snmp_oct2tc(tc, len, (char *) octets)) != NULL) {
+ fprintf(stdout, "%s", buf);
+ free(buf);
+ }
+}
+
+static void
+snmp_output_octetindex(struct snmp_toolinfo *snmptoolctx, enum snmp_tc tc,
+ struct asn_oid *oid)
+{
+ uint32_t i;
+ uint8_t *s;
+
+ if ((s = malloc(oid->subs[0] + 1)) == NULL)
+ syslog(LOG_ERR, "malloc failed - %s", strerror(errno));
+ else {
+ for (i = 0; i < oid->subs[0]; i++)
+ s[i] = (u_char) (oid->subs[i + 1]);
+
+ snmp_output_octetstring(snmptoolctx, tc, oid->subs[0], s);
+ free(s);
+ }
+}
+
+/*
+ * Check and output syntax type and value.
+ */
+static void
+snmp_output_oid_value(struct snmp_toolinfo *snmptoolctx, struct asn_oid *oid)
+{
+ char oid_string[ASN_OIDSTRLEN];
+ struct snmp_object obj;
+
+ if (GET_OUTPUT(snmptoolctx) == OUTPUT_VERBOSE)
+ fprintf(stdout, "%s : ", syntax_strings[SNMP_SYNTAX_OID].str);
+
+ if(!ISSET_NUMERIC(snmptoolctx)) {
+ memset(&obj, 0, sizeof(struct snmp_object));
+ asn_append_oid(&(obj.val.var), oid);
+
+ if (snmp_lookup_enumstring(snmptoolctx, &obj) > 0)
+ fprintf(stdout, "%s" , obj.info->string);
+ else if (snmp_lookup_oidstring(snmptoolctx, &obj) > 0)
+ fprintf(stdout, "%s" , obj.info->string);
+ else if (snmp_lookup_nodestring(snmptoolctx, &obj) > 0)
+ fprintf(stdout, "%s" , obj.info->string);
+ else {
+ (void) asn_oid2str_r(oid, oid_string);
+ fprintf(stdout, "%s", oid_string);
+ }
+ } else {
+ (void) asn_oid2str_r(oid, oid_string);
+ fprintf(stdout, "%s", oid_string);
+ }
+}
+
+static void
+snmp_output_int(struct snmp_toolinfo *snmptoolctx, struct enum_pairs *enums,
+ int32_t int_val)
+{
+ char *string;
+
+ if (GET_OUTPUT(snmptoolctx) == OUTPUT_VERBOSE)
+ fprintf(stdout, "%s : ",
+ syntax_strings[SNMP_SYNTAX_INTEGER].str);
+
+ if (enums != NULL && (string = enum_string_lookup(enums, int_val))
+ != NULL)
+ fprintf(stdout, "%s", string);
+ else
+ fprintf(stdout, "%d", int_val);
+}
+
+static void
+snmp_output_ipaddress(struct snmp_toolinfo *snmptoolctx, uint8_t *ip)
+{
+ if (GET_OUTPUT(snmptoolctx) == OUTPUT_VERBOSE)
+ fprintf(stdout, "%s : ",
+ syntax_strings[SNMP_SYNTAX_IPADDRESS].str);
+
+ fprintf(stdout, "%u.%u.%u.%u", ip[0], ip[1], ip[2], ip[3]);
+}
+
+static void
+snmp_output_counter(struct snmp_toolinfo *snmptoolctx, uint32_t counter)
+{
+ if (GET_OUTPUT(snmptoolctx) == OUTPUT_VERBOSE)
+ fprintf(stdout, "%s : ",
+ syntax_strings[SNMP_SYNTAX_COUNTER].str);
+
+ fprintf(stdout, "%u", counter);
+}
+
+static void
+snmp_output_gauge(struct snmp_toolinfo *snmptoolctx, uint32_t gauge)
+{
+ if (GET_OUTPUT(snmptoolctx) == OUTPUT_VERBOSE)
+ fprintf(stdout, "%s : ", syntax_strings[SNMP_SYNTAX_GAUGE].str);
+
+ fprintf(stdout, "%u", gauge);
+}
+
+static void
+snmp_output_ticks(struct snmp_toolinfo *snmptoolctx, uint32_t ticks)
+{
+ if (GET_OUTPUT(snmptoolctx) == OUTPUT_VERBOSE)
+ fprintf(stdout, "%s : ",
+ syntax_strings[SNMP_SYNTAX_TIMETICKS].str);
+
+ fprintf(stdout, "%u", ticks);
+}
+
+static void
+snmp_output_counter64(struct snmp_toolinfo *snmptoolctx, uint64_t counter64)
+{
+ if (GET_OUTPUT(snmptoolctx) == OUTPUT_VERBOSE)
+ fprintf(stdout, "%s : ",
+ syntax_strings[SNMP_SYNTAX_COUNTER64].str);
+
+ fprintf(stdout,"%ju", counter64);
+}
+
+int32_t
+snmp_output_numval(struct snmp_toolinfo *snmptoolctx, struct snmp_value *val,
+ struct snmp_oid2str *entry)
+{
+ if (val == NULL)
+ return (-1);
+
+ if (GET_OUTPUT(snmptoolctx) != OUTPUT_QUIET)
+ fprintf(stdout, " = ");
+
+ switch (val->syntax) {
+ case SNMP_SYNTAX_INTEGER:
+ if (entry != NULL)
+ snmp_output_int(snmptoolctx, entry->snmp_enum,
+ val->v.integer);
+ else
+ snmp_output_int(snmptoolctx, NULL, val->v.integer);
+ break;
+
+ case SNMP_SYNTAX_OCTETSTRING:
+ if (entry != NULL)
+ snmp_output_octetstring(snmptoolctx, entry->tc,
+ val->v.octetstring.len, val->v.octetstring.octets);
+ else
+ snmp_output_octetstring(snmptoolctx, SNMP_STRING,
+ val->v.octetstring.len, val->v.octetstring.octets);
+ break;
+
+ case SNMP_SYNTAX_OID:
+ snmp_output_oid_value(snmptoolctx, &(val->v.oid));
+ break;
+
+ case SNMP_SYNTAX_IPADDRESS:
+ snmp_output_ipaddress(snmptoolctx, val->v.ipaddress);
+ break;
+
+ case SNMP_SYNTAX_COUNTER:
+ snmp_output_counter(snmptoolctx, val->v.uint32);
+ break;
+
+ case SNMP_SYNTAX_GAUGE:
+ snmp_output_gauge(snmptoolctx, val->v.uint32);
+ break;
+
+ case SNMP_SYNTAX_TIMETICKS:
+ snmp_output_ticks(snmptoolctx, val->v.uint32);
+ break;
+
+ case SNMP_SYNTAX_COUNTER64:
+ snmp_output_counter64(snmptoolctx, val->v.counter64);
+ break;
+
+ case SNMP_SYNTAX_NOSUCHOBJECT:
+ fprintf(stdout, "No Such Object\n");
+ return (val->syntax);
+
+ case SNMP_SYNTAX_NOSUCHINSTANCE:
+ fprintf(stdout, "No Such Instance\n");
+ return (val->syntax);
+
+ case SNMP_SYNTAX_ENDOFMIBVIEW:
+ fprintf(stdout, "End of Mib View\n");
+ return (val->syntax);
+
+ case SNMP_SYNTAX_NULL:
+ /* NOTREACHED */
+ fprintf(stdout, "agent returned NULL Syntax\n");
+ return (val->syntax);
+
+ default:
+ /* NOTREACHED - If here - then all went completely wrong. */
+ fprintf(stdout, "agent returned unknown syntax\n");
+ return (-1);
+ }
+
+ fprintf(stdout, "\n");
+
+ return (0);
+}
+
+static int32_t
+snmp_fill_object(struct snmp_toolinfo *snmptoolctx, struct snmp_object *obj,
+ struct snmp_value *val)
+{
+ int32_t rc;
+ asn_subid_t suboid;
+
+ if (obj == NULL || val == NULL)
+ return (-1);
+
+ if ((suboid = snmp_suboid_pop(&(val->var))) > ASN_MAXID)
+ return (-1);
+
+ memset(obj, 0, sizeof(struct snmp_object));
+ asn_append_oid(&(obj->val.var), &(val->var));
+ obj->val.syntax = val->syntax;
+
+ if (obj->val.syntax > 0)
+ rc = snmp_lookup_leafstring(snmptoolctx, obj);
+ else
+ rc = snmp_lookup_nonleaf_string(snmptoolctx, obj);
+
+ (void) snmp_suboid_append(&(val->var), suboid);
+ (void) snmp_suboid_append(&(obj->val.var), suboid);
+
+ return (rc);
+}
+
+static int32_t
+snmp_output_index(struct snmp_toolinfo *snmptoolctx, struct index *stx,
+ struct asn_oid *oid)
+{
+ uint8_t ip[4];
+ uint32_t bytes = 1;
+ uint64_t cnt64;
+ struct asn_oid temp, out;
+
+ if (oid->len < bytes)
+ return (-1);
+
+ memset(&temp, 0, sizeof(struct asn_oid));
+ asn_append_oid(&temp, oid);
+
+ switch (stx->syntax) {
+ case SNMP_SYNTAX_INTEGER:
+ snmp_output_int(snmptoolctx, stx->snmp_enum, temp.subs[0]);
+ break;
+
+ case SNMP_SYNTAX_OCTETSTRING:
+ if ((temp.subs[0] > temp.len -1 ) || (temp.subs[0] >
+ ASN_MAXOCTETSTRING))
+ return (-1);
+ snmp_output_octetindex(snmptoolctx, stx->tc, &temp);
+ bytes += temp.subs[0];
+ break;
+
+ case SNMP_SYNTAX_OID:
+ if ((temp.subs[0] > temp.len -1) || (temp.subs[0] >
+ ASN_MAXOIDLEN))
+ return (-1);
+
+ bytes += temp.subs[0];
+ memset(&out, 0, sizeof(struct asn_oid));
+ asn_slice_oid(&out, &temp, 1, bytes);
+ snmp_output_oid_value(snmptoolctx, &out);
+ break;
+
+ case SNMP_SYNTAX_IPADDRESS:
+ if (temp.len < 4)
+ return (-1);
+ for (bytes = 0; bytes < 4; bytes++)
+ ip[bytes] = temp.subs[bytes];
+
+ snmp_output_ipaddress(snmptoolctx, ip);
+ bytes = 4;
+ break;
+
+ case SNMP_SYNTAX_COUNTER:
+ snmp_output_counter(snmptoolctx, temp.subs[0]);
+ break;
+
+ case SNMP_SYNTAX_GAUGE:
+ snmp_output_gauge(snmptoolctx, temp.subs[0]);
+ break;
+
+ case SNMP_SYNTAX_TIMETICKS:
+ snmp_output_ticks(snmptoolctx, temp.subs[0]);
+ break;
+
+ case SNMP_SYNTAX_COUNTER64:
+ if (oid->len < 2)
+ return (-1);
+ bytes = 2;
+ memcpy(&cnt64, temp.subs, bytes);
+ snmp_output_counter64(snmptoolctx, cnt64);
+ break;
+
+ default:
+ return (-1);
+ }
+
+ return (bytes);
+}
+
+static int32_t
+snmp_output_object(struct snmp_toolinfo *snmptoolctx, struct snmp_object *o)
+{
+ int32_t i, first, len;
+ struct asn_oid oid;
+ struct index *temp;
+
+ if (ISSET_NUMERIC(snmptoolctx))
+ return (-1);
+
+ if (o->info->table_idx == NULL) {
+ fprintf(stdout,"%s.%d", o->info->string,
+ o->val.var.subs[o->val.var.len - 1]);
+ return (1);
+ }
+
+ fprintf(stdout,"%s[", o->info->string);
+ memset(&oid, 0, sizeof(struct asn_oid));
+
+ len = 1;
+ asn_slice_oid(&oid, &(o->val.var), (o->info->table_idx->var.len + len),
+ o->val.var.len);
+
+ first = 1;
+ STAILQ_FOREACH(temp, &(OBJECT_IDX_LIST(o)), link) {
+ if(first)
+ first = 0;
+ else
+ fprintf(stdout, ", ");
+ if ((i = snmp_output_index(snmptoolctx, temp, &oid)) < 0)
+ break;
+ len += i;
+ memset(&oid, 0, sizeof(struct asn_oid));
+ asn_slice_oid(&oid, &(o->val.var),
+ (o->info->table_idx->var.len + len), o->val.var.len + 1);
+ }
+
+ fprintf(stdout,"]");
+ return (1);
+}
+
+void
+snmp_output_err_resp(struct snmp_toolinfo *snmptoolctx, struct snmp_pdu *pdu)
+{
+ char buf[ASN_OIDSTRLEN];
+ struct snmp_object object;
+
+ if (pdu == NULL || (pdu->error_index > (int32_t) pdu->nbindings)) {
+ fprintf(stdout,"Invalid error index in PDU\n");
+ return;
+ }
+
+ fprintf(stdout, "Agent %s:%s returned error \n", snmp_client.chost,
+ snmp_client.cport);
+
+ if (!ISSET_NUMERIC(snmptoolctx) && (snmp_fill_object(snmptoolctx, &object,
+ &(pdu->bindings[pdu->error_index - 1])) > 0))
+ snmp_output_object(snmptoolctx, &object);
+ else {
+ asn_oid2str_r(&(pdu->bindings[pdu->error_index - 1].var), buf);
+ fprintf(stdout,"%s", buf);
+ }
+
+ fprintf(stdout," caused error - ");
+ if ((pdu->error_status > 0) && (pdu->error_status <=
+ SNMP_ERR_INCONS_NAME))
+ fprintf(stdout, "%s\n", error_strings[pdu->error_status].str);
+ else
+ fprintf(stdout,"%s\n", error_strings[SNMP_ERR_UNKNOWN].str);
+}
+
+int32_t
+snmp_output_resp(struct snmp_toolinfo *snmptoolctx, struct snmp_pdu *pdu,
+ struct asn_oid *root)
+{
+ int32_t error;
+ char p[ASN_OIDSTRLEN];
+ uint32_t i;
+ struct snmp_object object;
+
+ i = error = 0;
+ while (i < pdu->nbindings) {
+ if (root != NULL && !(asn_is_suboid(root,
+ &(pdu->bindings[i].var))))
+ break;
+
+ if (GET_OUTPUT(snmptoolctx) != OUTPUT_QUIET) {
+ if (!ISSET_NUMERIC(snmptoolctx) &&
+ (snmp_fill_object(snmptoolctx, &object,
+ &(pdu->bindings[i])) > 0))
+ snmp_output_object(snmptoolctx, &object);
+ else {
+ asn_oid2str_r(&(pdu->bindings[i].var), p);
+ fprintf(stdout, "%s", p);
+ }
+ }
+ error |= snmp_output_numval(snmptoolctx, &(pdu->bindings[i]), object.info);
+ i++;
+ }
+
+ if (error)
+ return (-1);
+
+ return (i);
+}
+
+void
+snmp_output_engine(void)
+{
+ uint32_t i;
+ char *cptr, engine[2 * SNMP_ENGINE_ID_SIZ + 2];
+
+ cptr = engine;
+ for (i = 0; i < snmp_client.engine.engine_len; i++)
+ cptr += sprintf(cptr, "%.2x", snmp_client.engine.engine_id[i]);
+ *cptr++ = '\0';
+
+ fprintf(stdout, "Engine ID 0x%s\n", engine);
+ fprintf(stdout, "Boots : %u\t\tTime : %d\n",
+ snmp_client.engine.engine_boots,
+ snmp_client.engine.engine_time);
+}
+
+void
+snmp_output_keys(void)
+{
+ uint32_t i, keylen = 0;
+ char *cptr, extkey[2 * SNMP_AUTH_KEY_SIZ + 2];
+
+ fprintf(stdout, "Localized keys for %s\n", snmp_client.user.sec_name);
+ if (snmp_client.user.auth_proto == SNMP_AUTH_HMAC_MD5) {
+ fprintf(stdout, "MD5 : 0x");
+ keylen = SNMP_AUTH_HMACMD5_KEY_SIZ;
+ } else if (snmp_client.user.auth_proto == SNMP_AUTH_HMAC_SHA) {
+ fprintf(stdout, "SHA : 0x");
+ keylen = SNMP_AUTH_HMACSHA_KEY_SIZ;
+ }
+ if (snmp_client.user.auth_proto != SNMP_AUTH_NOAUTH) {
+ cptr = extkey;
+ for (i = 0; i < keylen; i++)
+ cptr += sprintf(cptr, "%.2x",
+ snmp_client.user.auth_key[i]);
+ *cptr++ = '\0';
+ fprintf(stdout, "%s\n", extkey);
+ }
+
+ if (snmp_client.user.priv_proto == SNMP_PRIV_DES) {
+ fprintf(stdout, "DES : 0x");
+ keylen = SNMP_PRIV_DES_KEY_SIZ;
+ } else if (snmp_client.user.priv_proto == SNMP_PRIV_AES) {
+ fprintf(stdout, "AES : 0x");
+ keylen = SNMP_PRIV_AES_KEY_SIZ;
+ }
+ if (snmp_client.user.priv_proto != SNMP_PRIV_NOPRIV) {
+ cptr = extkey;
+ for (i = 0; i < keylen; i++)
+ cptr += sprintf(cptr, "%.2x",
+ snmp_client.user.priv_key[i]);
+ *cptr++ = '\0';
+ fprintf(stdout, "%s\n", extkey);
+ }
+}
diff --git a/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptools.h b/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptools.h
new file mode 100644
index 0000000..c14fe52
--- /dev/null
+++ b/usr.sbin/bsnmpd/tools/libbsnmptools/bsnmptools.h
@@ -0,0 +1,333 @@
+/*-
+ * Copyright (c) 2005-2006 The FreeBSD Project
+ * All rights reserved.
+ *
+ * Author: Shteryana Shopova <syrinx@FreeBSD.org>
+ *
+ * Redistribution of this software and documentation and use in source and
+ * binary forms, with or without modification, are permitted provided that
+ * the following conditions are met:
+ *
+ * 1. Redistributions of source code or documentation must retain the above
+ * copyright notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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.
+ *
+ * Helper functions common for all tools.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _BSNMP_TOOLS_H_
+#define _BSNMP_TOOLS_H_
+
+/* From asn1.h + 1 byte for trailing zero. */
+#define MAX_OCTSTRING_LEN ASN_MAXOCTETSTRING + 1
+#define MAX_CMD_SYNTAX_LEN 12
+
+/* Arbitrary upper limit on node names and function names - gensnmptree.c. */
+#define MAXSTR 1000
+
+/* Should be enough to fetch the biggest allowed octet string. */
+#define MAX_BUFF_SIZE (ASN_MAXOCTETSTRING + 50)
+
+#define SNMP_DEFS_DIR "/usr/share/snmp/defs/"
+#define SNMP_DEFAULT_LOCAL "/var/run/snmpd.sock"
+
+#define SNMP_MAX_REPETITIONS 10
+
+enum snmp_access {
+ SNMP_ACCESS_NONE = 0,
+ SNMP_ACCESS_GET,
+ SNMP_ACCESS_SET,
+ SNMP_ACCESS_GETSET,
+};
+
+/* A structure for integer-string enumerations. */
+struct enum_pair {
+ int32_t enum_val;
+ char *enum_str;
+ STAILQ_ENTRY(enum_pair) link;
+};
+
+STAILQ_HEAD(enum_pairs, enum_pair);
+
+struct enum_type {
+ char *name;
+ uint32_t syntax;
+ int32_t is_enum;
+ int32_t is_bits;
+ struct enum_pairs *snmp_enum;
+ SLIST_ENTRY(enum_type) link;
+};
+
+SLIST_HEAD(snmp_enum_tc, enum_type);
+
+struct index {
+ enum snmp_tc tc;
+ enum snmp_syntax syntax;
+ struct enum_pairs *snmp_enum;
+ STAILQ_ENTRY(index) link;
+};
+
+STAILQ_HEAD(snmp_idxlist, index);
+
+struct snmp_index_entry {
+ char *string;
+ uint32_t strlen;
+ struct asn_oid var;
+ struct snmp_idxlist index_list;
+ SLIST_ENTRY(snmp_index_entry) link;
+};
+
+/* Information needed for oid to string conversion. */
+struct snmp_oid2str {
+ char *string;
+ uint32_t strlen;
+ enum snmp_tc tc;
+ enum snmp_syntax syntax;
+ enum snmp_access access;
+ struct asn_oid var;
+ /* A pointer to a entry from the table list - OK if NULL. */
+ struct snmp_index_entry *table_idx;
+ /*
+ * A singly-linked tail queue of all (int, string) pairs -
+ * for INTEGER syntax only.
+ */
+ struct enum_pairs *snmp_enum;
+ SLIST_ENTRY(snmp_oid2str) link;
+};
+
+/* A structure to hold each oid input by user. */
+struct snmp_object {
+ /* Flag - if set, the variable caused error in a previous request. */
+ int32_t error;
+ /*
+ * A pointer in the mapping lists - not used if OIDs are input as
+ * numericals.
+ */
+ struct snmp_oid2str *info;
+ /* A snmp value to hold the actual oid, syntax and value. */
+ struct snmp_value val;
+ SLIST_ENTRY(snmp_object) link;
+};
+
+struct fname {
+ char *name;
+ int32_t done;
+ struct asn_oid cut;
+ SLIST_ENTRY(fname) link;
+};
+
+SLIST_HEAD(snmp_mapping, snmp_oid2str);
+SLIST_HEAD(fname_list, fname);
+SLIST_HEAD(snmp_table_index, snmp_index_entry);
+
+/*
+ * Keep a list for every syntax type.
+ */
+struct snmp_mappings {
+ /* The list containing all non-leaf nodes. */
+ struct snmp_mapping nodelist;
+ /* INTEGER/INTEGER32 types. */
+ struct snmp_mapping intlist;
+ /* OCTETSTRING types. */
+ struct snmp_mapping octlist;
+ /* OID types. */
+ struct snmp_mapping oidlist;
+ /* IPADDRESS types. */
+ struct snmp_mapping iplist;
+ /* TIMETICKS types. */
+ struct snmp_mapping ticklist;
+ /* COUNTER types. */
+ struct snmp_mapping cntlist;
+ /* GAUGE types. */
+ struct snmp_mapping gaugelist;
+ /* COUNTER64 types. */
+ struct snmp_mapping cnt64list;
+ /* ENUM values for oid types. */
+ struct snmp_mapping enumlist;
+ /* Description of all table entry types. */
+ struct snmp_table_index tablelist;
+ /* Defined enumerated textual conventions. */
+ struct snmp_enum_tc tclist;
+};
+
+struct snmp_toolinfo {
+ uint32_t flags;
+ /* Number of initially input OIDs. */
+ int32_t objects;
+ /* List of all input OIDs. */
+ SLIST_HEAD(snmp_objectlist, snmp_object) snmp_objectlist;
+ /* All known OID to string mapping data. */
+ struct snmp_mappings *mappings;
+ /* A list of .defs filenames to search oid<->string mapping. */
+ struct fname_list filelist;
+ /* SNMPv3 USM user credentials */
+ char *passwd;
+};
+
+/* XXX we might want to get away with this and will need to touch
+ * XXX the MACROS then too */
+extern struct snmp_toolinfo snmptool;
+
+/* Definitions for some flags' bits. */
+#define OUTPUT_BITS 0x00000003 /* bits 0-1 for output type */
+#define NUMERIC_BIT 0x00000004 /* bit 2 for numeric oids */
+#define RETRY_BIT 0x00000008 /* bit 3 for retry on error response */
+#define ERRIGNORE_BIT 0x00000010 /* bit 4 for skip sanity checking */
+#define ERRIGNORE_BIT 0x00000010 /* bit 4 for skip sanity checking */
+#define EDISCOVER_BIT 0x00000020 /* bit 5 for SNMP Engine Discovery */
+#define LOCALKEY_BIT 0x00000040 /* bit 6 for using localized key */
+ /* 0x00000080 */ /* bit 7 reserved */
+#define PDUTYPE_BITS 0x00000f00 /* bits 8-11 for pdu type */
+ /* 0x0000f000 */ /* bit 12-15 reserved */
+#define MAXREP_BITS 0x00ff0000 /* bits 16-23 for max-repetit. value */
+#define NONREP_BITS 0xff000000 /* bits 24-31 for non-repeaters value */
+
+#define OUTPUT_SHORT 0x0
+#define OUTPUT_VERBOSE 0x1
+#define OUTPUT_TABULAR 0x2
+#define OUTPUT_QUIET 0x3
+
+/* Macros for playing with flags' bits. */
+#define SET_OUTPUT(ctx, type) ((ctx)->flags |= ((type) & OUTPUT_BITS))
+#define GET_OUTPUT(ctx) ((ctx)->flags & OUTPUT_BITS)
+
+#define SET_NUMERIC(ctx) ((ctx)->flags |= NUMERIC_BIT)
+#define ISSET_NUMERIC(ctx) ((ctx)->flags & NUMERIC_BIT)
+
+#define SET_RETRY(ctx) ((ctx)->flags |= RETRY_BIT)
+#define ISSET_RETRY(ctx) ((ctx)->flags & RETRY_BIT)
+
+#define SET_ERRIGNORE(ctx) ((ctx)->flags |= ERRIGNORE_BIT)
+#define ISSET_ERRIGNORE(ctx) ((ctx)->flags & ERRIGNORE_BIT)
+
+#define SET_EDISCOVER(ctx) ((ctx)->flags |= EDISCOVER_BIT)
+#define ISSET_EDISCOVER(ctx) ((ctx)->flags & EDISCOVER_BIT)
+
+#define SET_LOCALKEY(ctx) ((ctx)->flags |= LOCALKEY_BIT)
+#define ISSET_LOCALKEY(ctx) ((ctx)->flags & LOCALKEY_BIT)
+
+#define SET_PDUTYPE(ctx, type) (((ctx)->flags |= (((type) & 0xf) << 8)))
+#define GET_PDUTYPE(ctx) (((ctx)->flags & PDUTYPE_BITS) >> 8)
+
+#define SET_MAXREP(ctx, i) (((ctx)->flags |= (((i) & 0xff) << 16)))
+#define GET_MAXREP(ctx) (((ctx)->flags & MAXREP_BITS) >> 16)
+
+#define SET_NONREP(ctx, i) (((ctx)->flags |= (((i) & 0xff) << 24)))
+#define GET_NONREP(ctx) (((ctx)->flags & NONREP_BITS) >> 24)
+
+
+extern const struct asn_oid IsoOrgDod_OID;
+
+int snmptool_init(struct snmp_toolinfo *);
+int32_t snmp_import_file(struct snmp_toolinfo *, struct fname *);
+int32_t snmp_import_all(struct snmp_toolinfo *);
+int32_t add_filename(struct snmp_toolinfo *, const char *,
+ const struct asn_oid *, int32_t);
+void free_filelist(struct snmp_toolinfo *);
+void snmp_tool_freeall(struct snmp_toolinfo *);
+void snmp_import_dump(int);
+
+/* bsnmpmap.c */
+struct snmp_mappings *snmp_mapping_init(void);
+int32_t snmp_mapping_free(struct snmp_toolinfo *);
+void snmp_index_listfree(struct snmp_idxlist *);
+void snmp_dump_oid2str(struct snmp_oid2str *);
+int32_t snmp_node_insert(struct snmp_toolinfo *, struct snmp_oid2str *);
+int32_t snmp_leaf_insert(struct snmp_toolinfo *, struct snmp_oid2str *);
+int32_t snmp_enum_insert(struct snmp_toolinfo *, struct snmp_oid2str *);
+struct enum_pairs *enum_pairs_init(void);
+void enum_pairs_free(struct enum_pairs *);
+void snmp_mapping_entryfree(struct snmp_oid2str *);
+int32_t enum_pair_insert(struct enum_pairs *, int32_t, char *);
+char *enum_string_lookup(struct enum_pairs *, int32_t);
+int32_t enum_number_lookup(struct enum_pairs *, char *);
+int32_t snmp_syntax_insert(struct snmp_idxlist *, struct enum_pairs *,
+ enum snmp_syntax, enum snmp_tc);
+int32_t snmp_table_insert(struct snmp_toolinfo *, struct snmp_index_entry *);
+
+struct enum_type *snmp_enumtc_init(char *);
+void snmp_enumtc_free(struct enum_type *);
+void snmp_enumtc_insert(struct snmp_toolinfo *, struct enum_type *);
+struct enum_type *snmp_enumtc_lookup(struct snmp_toolinfo *, char *);
+
+void snmp_mapping_dump(struct snmp_toolinfo *);
+int32_t snmp_lookup_leafstring(struct snmp_toolinfo *, struct snmp_object *);
+int32_t snmp_lookup_enumstring(struct snmp_toolinfo *, struct snmp_object *);
+int32_t snmp_lookup_oidstring(struct snmp_toolinfo *, struct snmp_object *);
+int32_t snmp_lookup_nonleaf_string(struct snmp_toolinfo *, struct snmp_object *);
+int32_t snmp_lookup_allstring(struct snmp_toolinfo *, struct snmp_object *);
+int32_t snmp_lookup_nodestring(struct snmp_toolinfo *, struct snmp_object *);
+int32_t snmp_lookup_oidall(struct snmp_toolinfo *, struct snmp_object *, char *);
+int32_t snmp_lookup_enumoid(struct snmp_toolinfo *, struct snmp_object *, char *);
+int32_t snmp_lookup_oid(struct snmp_toolinfo *, struct snmp_object *, char *);
+
+/* Functions parsing common options for all tools. */
+int32_t parse_server(char *);
+int32_t parse_timeout(char *);
+int32_t parse_retry(char *);
+int32_t parse_version(char *);
+int32_t parse_local_path(char *);
+int32_t parse_buflen(char *);
+int32_t parse_debug(void);
+int32_t parse_discovery(struct snmp_toolinfo *);
+int32_t parse_local_key(struct snmp_toolinfo *);
+int32_t parse_num_oids(struct snmp_toolinfo *);
+int32_t parse_file(struct snmp_toolinfo *, char *);
+int32_t parse_include(struct snmp_toolinfo *, char *);
+int32_t parse_output(struct snmp_toolinfo *, char *);
+int32_t parse_errors(struct snmp_toolinfo *);
+int32_t parse_skip_access(struct snmp_toolinfo *);
+int32_t parse_authentication(struct snmp_toolinfo *, char *);
+int32_t parse_privacy(struct snmp_toolinfo *, char *);
+int32_t parse_context(struct snmp_toolinfo *, char *);
+int32_t parse_user_security(struct snmp_toolinfo *, char *);
+
+typedef int32_t (*snmp_verify_inoid_f) (struct snmp_toolinfo *,
+ struct snmp_object *, char *);
+int32_t snmp_object_add(struct snmp_toolinfo *, snmp_verify_inoid_f, char *);
+int32_t snmp_object_remove(struct snmp_toolinfo *, struct asn_oid *oid);
+int32_t snmp_object_seterror(struct snmp_toolinfo *, struct snmp_value *,
+ int32_t);
+
+enum snmp_syntax parse_syntax(char *);
+char *snmp_parse_suboid(char *, struct asn_oid *);
+char *snmp_parse_index(struct snmp_toolinfo *, char *, struct snmp_object *);
+int32_t snmp_parse_numoid(char *, struct asn_oid *);
+int32_t snmp_suboid_append(struct asn_oid *, asn_subid_t);
+int32_t snmp_suboid_pop(struct asn_oid *);
+
+typedef int32_t (*snmp_verify_vbind_f) (struct snmp_toolinfo *,
+ struct snmp_pdu *, struct snmp_object *);
+typedef int32_t (*snmp_add_vbind_f) (struct snmp_pdu *, struct snmp_object *);
+int32_t snmp_pdu_add_bindings(struct snmp_toolinfo *, snmp_verify_vbind_f,
+ snmp_add_vbind_f, struct snmp_pdu *, int32_t);
+
+int32_t snmp_parse_get_resp(struct snmp_pdu *, struct snmp_pdu *);
+int32_t snmp_parse_getbulk_resp(struct snmp_pdu *, struct snmp_pdu *);
+int32_t snmp_parse_getnext_resp(struct snmp_pdu *, struct snmp_pdu *);
+int32_t snmp_parse_resp(struct snmp_pdu *, struct snmp_pdu *);
+int32_t snmp_output_numval(struct snmp_toolinfo *, struct snmp_value *,
+ struct snmp_oid2str *);
+void snmp_output_val(struct snmp_value *);
+int32_t snmp_output_resp(struct snmp_toolinfo *, struct snmp_pdu *, struct asn_oid *);
+void snmp_output_err_resp(struct snmp_toolinfo *, struct snmp_pdu *);
+void snmp_output_engine(void);
+void snmp_output_keys(void);
+
+#endif /* _BSNMP_TOOLS_H_ */
diff --git a/usr.sbin/btxld/Makefile b/usr.sbin/btxld/Makefile
new file mode 100644
index 0000000..32cf99d
--- /dev/null
+++ b/usr.sbin/btxld/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= btxld
+MAN= btxld.8
+SRCS= btxld.c elfh.c
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/btxld/Makefile.depend b/usr.sbin/btxld/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/btxld/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/btxld/btx.h b/usr.sbin/btxld/btx.h
new file mode 100644
index 0000000..86f0ede
--- /dev/null
+++ b/usr.sbin/btxld/btx.h
@@ -0,0 +1,68 @@
+/*
+ * 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 AND CONTRIBUTORS``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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 _BTX_H_
+#define _BTX_H_
+
+#include <sys/types.h>
+
+#define BTX_PGSIZE 0x1000 /* Page size */
+#define BTX_PGBASE 0x5000 /* Start of page tables */
+#define BTX_MAXCWR 0x3bc /* Max. btx_pgctl adjustment */
+
+/*
+ * BTX image header.
+ */
+struct btx_hdr {
+ uint8_t btx_machid; /* Machine ID */
+ uint8_t btx_hdrsz; /* Header size */
+ uint8_t btx_magic[3]; /* Magic */
+ uint8_t btx_majver; /* Major version */
+ uint8_t btx_minver; /* Minor version */
+ uint8_t btx_flags; /* Flags */
+ uint16_t btx_pgctl; /* Paging control */
+ uint16_t btx_textsz; /* Text size */
+ uint32_t btx_entry; /* Client entry address */
+};
+
+/* btx_machid */
+#define BTX_I386 0xeb /* Intel i386 or compatible */
+
+/* btx_magic */
+#define BTX_MAG0 'B'
+#define BTX_MAG1 'T'
+#define BTX_MAG2 'X'
+
+/* btx_flags */
+#define BTX_MAPONE 0x80 /* Start mapping at page 1 */
+
+#define BTX_MAPPED(btx) (((btx).btx_pgctl | (BTX_PGSIZE / 4 - 1)) + 1)
+#define BTX_ORIGIN(btx) (BTX_PGBASE + BTX_MAPPED(btx) * 4)
+#define BTX_ENTRY(btx) (BTX_ORIGIN(btx) + 2 + (btx).btx_hdrsz)
+
+#endif /* !_BTX_H_ */
diff --git a/usr.sbin/btxld/btxld.8 b/usr.sbin/btxld/btxld.8
new file mode 100644
index 0000000..cfa4623
--- /dev/null
+++ b/usr.sbin/btxld/btxld.8
@@ -0,0 +1,98 @@
+.\" 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 AND CONTRIBUTORS``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED 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 12, 1998
+.Dt BTXLD 8
+.Os
+.Sh NAME
+.Nm btxld
+.Nd link editor for BTX clients
+.Sh SYNOPSIS
+.Nm
+.Op Fl qv
+.Op Fl b Ar file
+.Op Fl E Ar address
+.Op Fl e Ar address
+.Op Fl f Ar format
+.Op Fl l Ar file
+.Op Fl o Ar filename
+.Op Fl P Ar page
+.Op Fl W Ar page
+.Ar file
+.Sh DESCRIPTION
+The
+.Nm
+utility binds the specified client executable together with a BTX
+loader program and the BTX kernel, and creates a composite object file
+suitable for loading during the boot process.
+.Pp
+The options are:
+.Bl -tag -width indent
+.It Fl q
+Quiet: inhibit warnings.
+.It Fl v
+Verbose: display information about the files processed.
+.It Fl b Ar file
+Specify the BTX kernel to be bound with the client.
+.It Fl E Ar address
+Set the client entry point.
+.It Fl e Ar address
+Set the BTX loader entry point.
+.It Fl f Ar format
+Specify the output format, where
+.Ar format
+is one of
+.Sq bin ,
+.Sq aout ,
+or
+.Sq elf .
+.It Fl l Ar file
+Specify the BTX loader to be bound with the client.
+.It Fl o Ar filename
+Name the output file.
+The default is
+.Dq a.out .
+.It Fl P Ar page
+Specify the first page of the client's segment to be marked
+.Sq present ,
+where
+.Ar page
+may be 0 or 1.
+.It Fl W Ar page
+Specify the first page of the client's segment to be marked
+.Sq writable ,
+where
+.Ar page
+may be 0, and should not exceed the number of pages occupied by the
+combined .text and .data segments of the client image.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr ld 1 ,
+.Xr boot 8
+.Sh AUTHORS
+.An Robert Nordier Aq Mt rnordier@FreeBSD.org
diff --git a/usr.sbin/btxld/btxld.c b/usr.sbin/btxld/btxld.c
new file mode 100644
index 0000000..7984c4c
--- /dev/null
+++ b/usr.sbin/btxld/btxld.c
@@ -0,0 +1,575 @@
+/*
+ * 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 AND CONTRIBUTORS``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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/param.h>
+#include <sys/endian.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+/* XXX make this work as an i386/amd64 cross-tool */
+#include <machine/exec.h>
+#undef __LDPGSZ
+#define __LDPGSZ 4096
+
+#include <netinet/in.h>
+
+#include <a.out.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "btx.h"
+#include "elfh.h"
+
+#define BTX_PATH "/sys/boot/i386/btx"
+
+#define I_LDR 0 /* BTX loader */
+#define I_BTX 1 /* BTX kernel */
+#define I_CLNT 2 /* Client program */
+
+#define F_BIN 0 /* Binary */
+#define F_AOUT 1 /* ZMAGIC a.out */
+#define F_ELF 2 /* 32-bit ELF */
+#define F_CNT 3 /* Number of formats */
+
+#define IMPURE 1 /* Writable text */
+#define MAXU32 0xffffffff /* Maximum unsigned 32-bit quantity */
+
+#define align(x, y) (((x) + (y) - 1) & ~((y) - 1))
+
+struct hdr {
+ uint32_t fmt; /* Format */
+ uint32_t flags; /* Bit flags */
+ uint32_t size; /* Size of file */
+ uint32_t text; /* Size of text segment */
+ uint32_t data; /* Size of data segment */
+ uint32_t bss; /* Size of bss segment */
+ uint32_t org; /* Program origin */
+ uint32_t entry; /* Program entry point */
+};
+
+static const char *const fmtlist[] = {"bin", "aout", "elf"};
+
+static const char binfo[] =
+ "kernel: ver=%u.%02u size=%x load=%x entry=%x map=%uM "
+ "pgctl=%x:%x\n";
+static const char cinfo[] =
+ "client: fmt=%s size=%x text=%x data=%x bss=%x entry=%x\n";
+static const char oinfo[] =
+ "output: fmt=%s size=%x text=%x data=%x org=%x entry=%x\n";
+
+static const char *lname =
+ BTX_PATH "/btxldr/btxldr"; /* BTX loader */
+static const char *bname =
+ BTX_PATH "/btx/btx"; /* BTX kernel */
+static const char *oname =
+ "a.out"; /* Output filename */
+
+static int ppage = -1; /* First page present */
+static int wpage = -1; /* First page writable */
+
+static unsigned int format; /* Output format */
+
+static uint32_t centry; /* Client entry address */
+static uint32_t lentry; /* Loader entry address */
+
+static int Eflag; /* Client entry option */
+
+static int quiet; /* Inhibit warnings */
+static int verbose; /* Display information */
+
+static const char *tname; /* Temporary output file */
+static const char *fname; /* Current input file */
+
+static void cleanup(void);
+static void btxld(const char *);
+static void getbtx(int, struct btx_hdr *);
+static void gethdr(int, struct hdr *);
+static void puthdr(int, struct hdr *);
+static void copy(int, int, size_t, off_t);
+static size_t readx(int, void *, size_t, off_t);
+static void writex(int, const void *, size_t);
+static void seekx(int, off_t);
+static unsigned int optfmt(const char *);
+static uint32_t optaddr(const char *);
+static int optpage(const char *, int);
+static void Warn(const char *, const char *, ...);
+static void usage(void);
+
+/*
+ * A link editor for BTX clients.
+ */
+int
+main(int argc, char *argv[])
+{
+ int c;
+
+ while ((c = getopt(argc, argv, "qvb:E:e:f:l:o:P:W:")) != -1)
+ switch (c) {
+ case 'q':
+ quiet = 1;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ case 'b':
+ bname = optarg;
+ break;
+ case 'E':
+ centry = optaddr(optarg);
+ Eflag = 1;
+ break;
+ case 'e':
+ lentry = optaddr(optarg);
+ break;
+ case 'f':
+ format = optfmt(optarg);
+ break;
+ case 'l':
+ lname = optarg;
+ break;
+ case 'o':
+ oname = optarg;
+ break;
+ case 'P':
+ ppage = optpage(optarg, 1);
+ break;
+ case 'W':
+ wpage = optpage(optarg, BTX_MAXCWR);
+ break;
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 1)
+ usage();
+ atexit(cleanup);
+ btxld(*argv);
+ return 0;
+}
+
+/*
+ * Clean up after errors.
+ */
+static void
+cleanup(void)
+{
+ if (tname)
+ remove(tname);
+}
+
+/*
+ * Read the input files; write the output file; display information.
+ */
+static void
+btxld(const char *iname)
+{
+ char name[FILENAME_MAX];
+ struct btx_hdr btx, btxle;
+ struct hdr ihdr, ohdr;
+ unsigned int ldr_size, cwr;
+ int fdi[3], fdo, i;
+
+ ldr_size = 0;
+
+ for (i = I_LDR; i <= I_CLNT; i++) {
+ fname = i == I_LDR ? lname : i == I_BTX ? bname : iname;
+ if ((fdi[i] = open(fname, O_RDONLY)) == -1)
+ err(2, "%s", fname);
+ switch (i) {
+ case I_LDR:
+ gethdr(fdi[i], &ihdr);
+ if (ihdr.fmt != F_BIN)
+ Warn(fname, "Loader format is %s; processing as %s",
+ fmtlist[ihdr.fmt], fmtlist[F_BIN]);
+ ldr_size = ihdr.size;
+ break;
+ case I_BTX:
+ getbtx(fdi[i], &btx);
+ break;
+ case I_CLNT:
+ gethdr(fdi[i], &ihdr);
+ if (ihdr.org && ihdr.org != BTX_PGSIZE)
+ Warn(fname,
+ "Client origin is 0x%x; expecting 0 or 0x%x",
+ ihdr.org, BTX_PGSIZE);
+ }
+ }
+ memset(&ohdr, 0, sizeof(ohdr));
+ ohdr.fmt = format;
+ ohdr.text = ldr_size;
+ ohdr.data = btx.btx_textsz + ihdr.size;
+ ohdr.org = lentry;
+ ohdr.entry = lentry;
+ cwr = 0;
+ if (wpage > 0 || (wpage == -1 && !(ihdr.flags & IMPURE))) {
+ if (wpage > 0)
+ cwr = wpage;
+ else {
+ cwr = howmany(ihdr.text, BTX_PGSIZE);
+ if (cwr > BTX_MAXCWR)
+ cwr = BTX_MAXCWR;
+ }
+ }
+ if (ppage > 0 || (ppage && wpage && ihdr.org >= BTX_PGSIZE)) {
+ btx.btx_flags |= BTX_MAPONE;
+ if (!cwr)
+ cwr++;
+ }
+ btx.btx_pgctl -= cwr;
+ btx.btx_entry = Eflag ? centry : ihdr.entry;
+ if ((size_t)snprintf(name, sizeof(name), "%s.tmp", oname) >= sizeof(name))
+ errx(2, "%s: Filename too long", oname);
+ if ((fdo = open(name, O_CREAT | O_TRUNC | O_WRONLY, 0666)) == -1)
+ err(2, "%s", name);
+ if (!(tname = strdup(name)))
+ err(2, NULL);
+ puthdr(fdo, &ohdr);
+ for (i = I_LDR; i <= I_CLNT; i++) {
+ fname = i == I_LDR ? lname : i == I_BTX ? bname : iname;
+ switch (i) {
+ case I_LDR:
+ copy(fdi[i], fdo, ldr_size, 0);
+ seekx(fdo, ohdr.size += ohdr.text);
+ break;
+ case I_BTX:
+ btxle = btx;
+ btxle.btx_pgctl = htole16(btxle.btx_pgctl);
+ btxle.btx_textsz = htole16(btxle.btx_textsz);
+ btxle.btx_entry = htole32(btxle.btx_entry);
+ writex(fdo, &btxle, sizeof(btxle));
+ copy(fdi[i], fdo, btx.btx_textsz - sizeof(btx),
+ sizeof(btx));
+ break;
+ case I_CLNT:
+ copy(fdi[i], fdo, ihdr.size, 0);
+ if (ftruncate(fdo, ohdr.size += ohdr.data))
+ err(2, "%s", tname);
+ }
+ if (close(fdi[i]))
+ err(2, "%s", fname);
+ }
+ if (close(fdo))
+ err(2, "%s", tname);
+ if (rename(tname, oname))
+ err(2, "%s: Can't rename to %s", tname, oname);
+ tname = NULL;
+ if (verbose) {
+ printf(binfo, btx.btx_majver, btx.btx_minver, btx.btx_textsz,
+ BTX_ORIGIN(btx), BTX_ENTRY(btx), BTX_MAPPED(btx) *
+ BTX_PGSIZE / 0x100000, !!(btx.btx_flags & BTX_MAPONE),
+ BTX_MAPPED(btx) - btx.btx_pgctl - BTX_PGBASE /
+ BTX_PGSIZE - BTX_MAPPED(btx) * 4 / BTX_PGSIZE);
+ printf(cinfo, fmtlist[ihdr.fmt], ihdr.size, ihdr.text,
+ ihdr.data, ihdr.bss, ihdr.entry);
+ printf(oinfo, fmtlist[ohdr.fmt], ohdr.size, ohdr.text,
+ ohdr.data, ohdr.org, ohdr.entry);
+ }
+}
+
+/*
+ * Read BTX file header.
+ */
+static void
+getbtx(int fd, struct btx_hdr * btx)
+{
+ if (readx(fd, btx, sizeof(*btx), 0) != sizeof(*btx) ||
+ btx->btx_magic[0] != BTX_MAG0 ||
+ btx->btx_magic[1] != BTX_MAG1 ||
+ btx->btx_magic[2] != BTX_MAG2)
+ errx(1, "%s: Not a BTX kernel", fname);
+ btx->btx_pgctl = le16toh(btx->btx_pgctl);
+ btx->btx_textsz = le16toh(btx->btx_textsz);
+ btx->btx_entry = le32toh(btx->btx_entry);
+}
+
+/*
+ * Get file size and read a.out or ELF header.
+ */
+static void
+gethdr(int fd, struct hdr *hdr)
+{
+ struct stat sb;
+ const struct exec *ex;
+ const Elf32_Ehdr *ee;
+ const Elf32_Phdr *ep;
+ void *p;
+ unsigned int fmt, x, n, i;
+
+ memset(hdr, 0, sizeof(*hdr));
+ if (fstat(fd, &sb))
+ err(2, "%s", fname);
+ if (sb.st_size > MAXU32)
+ errx(1, "%s: Too big", fname);
+ hdr->size = sb.st_size;
+ if (!hdr->size)
+ return;
+ if ((p = mmap(NULL, hdr->size, PROT_READ, MAP_SHARED, fd,
+ 0)) == MAP_FAILED)
+ err(2, "%s", fname);
+ for (fmt = F_CNT - 1; !hdr->fmt && fmt; fmt--)
+ switch (fmt) {
+ case F_AOUT:
+ ex = p;
+ if (hdr->size >= sizeof(struct exec) && !N_BADMAG(*ex)) {
+ hdr->fmt = fmt;
+ x = N_GETMAGIC(*ex);
+ if (x == OMAGIC || x == NMAGIC) {
+ if (x == NMAGIC)
+ Warn(fname, "Treating %s NMAGIC as OMAGIC",
+ fmtlist[fmt]);
+ hdr->flags |= IMPURE;
+ }
+ hdr->text = le32toh(ex->a_text);
+ hdr->data = le32toh(ex->a_data);
+ hdr->bss = le32toh(ex->a_bss);
+ hdr->entry = le32toh(ex->a_entry);
+ if (le32toh(ex->a_entry) >= BTX_PGSIZE)
+ hdr->org = BTX_PGSIZE;
+ }
+ break;
+ case F_ELF:
+ ee = p;
+ if (hdr->size >= sizeof(Elf32_Ehdr) && IS_ELF(*ee)) {
+ hdr->fmt = fmt;
+ for (n = i = 0; i < le16toh(ee->e_phnum); i++) {
+ ep = (void *)((uint8_t *)p + le32toh(ee->e_phoff) +
+ le16toh(ee->e_phentsize) * i);
+ if (le32toh(ep->p_type) == PT_LOAD)
+ switch (n++) {
+ case 0:
+ hdr->text = le32toh(ep->p_filesz);
+ hdr->org = le32toh(ep->p_paddr);
+ if (le32toh(ep->p_flags) & PF_W)
+ hdr->flags |= IMPURE;
+ break;
+ case 1:
+ hdr->data = le32toh(ep->p_filesz);
+ hdr->bss = le32toh(ep->p_memsz) -
+ le32toh(ep->p_filesz);
+ break;
+ case 2:
+ Warn(fname,
+ "Ignoring extra %s PT_LOAD segments",
+ fmtlist[fmt]);
+ }
+ }
+ hdr->entry = le32toh(ee->e_entry);
+ }
+ }
+ if (munmap(p, hdr->size))
+ err(2, "%s", fname);
+}
+
+/*
+ * Write a.out or ELF header.
+ */
+static void
+puthdr(int fd, struct hdr *hdr)
+{
+ struct exec ex;
+ struct elfh eh;
+
+ switch (hdr->fmt) {
+ case F_AOUT:
+ memset(&ex, 0, sizeof(ex));
+ N_SETMAGIC(ex, ZMAGIC, MID_I386, 0);
+ hdr->text = N_ALIGN(ex, hdr->text);
+ ex.a_text = htole32(hdr->text);
+ hdr->data = N_ALIGN(ex, hdr->data);
+ ex.a_data = htole32(hdr->data);
+ ex.a_entry = htole32(hdr->entry);
+ writex(fd, &ex, sizeof(ex));
+ hdr->size = N_ALIGN(ex, sizeof(ex));
+ seekx(fd, hdr->size);
+ break;
+ case F_ELF:
+ eh = elfhdr;
+ eh.e.e_entry = htole32(hdr->entry);
+ eh.p[0].p_vaddr = eh.p[0].p_paddr = htole32(hdr->org);
+ eh.p[0].p_filesz = eh.p[0].p_memsz = htole32(hdr->text);
+ eh.p[1].p_offset = htole32(le32toh(eh.p[0].p_offset) +
+ le32toh(eh.p[0].p_filesz));
+ eh.p[1].p_vaddr = eh.p[1].p_paddr =
+ htole32(align(le32toh(eh.p[0].p_paddr) + le32toh(eh.p[0].p_memsz),
+ 4096));
+ eh.p[1].p_filesz = eh.p[1].p_memsz = htole32(hdr->data);
+ eh.sh[2].sh_addr = eh.p[0].p_vaddr;
+ eh.sh[2].sh_offset = eh.p[0].p_offset;
+ eh.sh[2].sh_size = eh.p[0].p_filesz;
+ eh.sh[3].sh_addr = eh.p[1].p_vaddr;
+ eh.sh[3].sh_offset = eh.p[1].p_offset;
+ eh.sh[3].sh_size = eh.p[1].p_filesz;
+ writex(fd, &eh, sizeof(eh));
+ hdr->size = sizeof(eh);
+ }
+}
+
+/*
+ * Safe copy from input file to output file.
+ */
+static void
+copy(int fdi, int fdo, size_t nbyte, off_t offset)
+{
+ char buf[8192];
+ size_t n;
+
+ while (nbyte) {
+ if ((n = sizeof(buf)) > nbyte)
+ n = nbyte;
+ if (readx(fdi, buf, n, offset) != n)
+ errx(2, "%s: Short read", fname);
+ writex(fdo, buf, n);
+ nbyte -= n;
+ offset = -1;
+ }
+}
+
+/*
+ * Safe read from input file.
+ */
+static size_t
+readx(int fd, void *buf, size_t nbyte, off_t offset)
+{
+ ssize_t n;
+
+ if (offset != -1 && lseek(fd, offset, SEEK_SET) != offset)
+ err(2, "%s", fname);
+ if ((n = read(fd, buf, nbyte)) == -1)
+ err(2, "%s", fname);
+ return n;
+}
+
+/*
+ * Safe write to output file.
+ */
+static void
+writex(int fd, const void *buf, size_t nbyte)
+{
+ ssize_t n;
+
+ if ((n = write(fd, buf, nbyte)) == -1)
+ err(2, "%s", tname);
+ if ((size_t)n != nbyte)
+ errx(2, "%s: Short write", tname);
+}
+
+/*
+ * Safe seek in output file.
+ */
+static void
+seekx(int fd, off_t offset)
+{
+ if (lseek(fd, offset, SEEK_SET) != offset)
+ err(2, "%s", tname);
+}
+
+/*
+ * Convert an option argument to a format code.
+ */
+static unsigned int
+optfmt(const char *arg)
+{
+ unsigned int i;
+
+ for (i = 0; i < F_CNT && strcmp(arg, fmtlist[i]); i++);
+ if (i == F_CNT)
+ errx(1, "%s: Unknown format", arg);
+ return i;
+}
+
+/*
+ * Convert an option argument to an address.
+ */
+static uint32_t
+optaddr(const char *arg)
+{
+ char *s;
+ unsigned long x;
+
+ errno = 0;
+ x = strtoul(arg, &s, 0);
+ if (errno || !*arg || *s || x > MAXU32)
+ errx(1, "%s: Illegal address", arg);
+ return x;
+}
+
+/*
+ * Convert an option argument to a page number.
+ */
+static int
+optpage(const char *arg, int hi)
+{
+ char *s;
+ long x;
+
+ errno = 0;
+ x = strtol(arg, &s, 0);
+ if (errno || !*arg || *s || x < 0 || x > hi)
+ errx(1, "%s: Illegal page number", arg);
+ return x;
+}
+
+/*
+ * Display a warning.
+ */
+static void
+Warn(const char *locus, const char *fmt, ...)
+{
+ va_list ap;
+ char *s;
+
+ if (!quiet) {
+ asprintf(&s, "%s: Warning: %s", locus, fmt);
+ va_start(ap, fmt);
+ vwarnx(s, ap);
+ va_end(ap);
+ free(s);
+ }
+}
+
+/*
+ * Display usage information.
+ */
+static void
+usage(void)
+{
+ fprintf(stderr, "%s\n%s\n",
+ "usage: btxld [-qv] [-b file] [-E address] [-e address] [-f format]",
+ " [-l file] [-o filename] [-P page] [-W page] file");
+ exit(1);
+}
diff --git a/usr.sbin/btxld/elfh.c b/usr.sbin/btxld/elfh.c
new file mode 100644
index 0000000..2790d5a
--- /dev/null
+++ b/usr.sbin/btxld/elfh.c
@@ -0,0 +1,125 @@
+/*
+ * 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 AND CONTRIBUTORS``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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/endian.h>
+
+#include <stddef.h>
+#include "elfh.h"
+
+#define SET_ME 0xeeeeeeee /* filled in by btxld */
+
+/*
+ * ELF header template.
+ */
+const struct elfh elfhdr = {
+ {
+ {
+ ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3, /* e_ident */
+ ELFCLASS32, ELFDATA2LSB, EV_CURRENT, 0,
+ 'F', 'r', 'e', 'e', 'B', 'S', 'D', 0
+ },
+ htole16(ET_EXEC), /* e_type */
+ htole16(EM_386), /* e_machine */
+ htole32(EV_CURRENT), /* e_version */
+ htole32(SET_ME), /* e_entry */
+ htole32(offsetof(struct elfh, p)), /* e_phoff */
+ htole32(offsetof(struct elfh, sh)), /* e_shoff */
+ 0, /* e_flags */
+ htole16(sizeof(elfhdr.e)), /* e_ehsize */
+ htole16(sizeof(elfhdr.p[0])), /* e_phentsize */
+ htole16(sizeof(elfhdr.p) / sizeof(elfhdr.p[0])), /* e_phnum */
+ htole16(sizeof(elfhdr.sh[0])), /* e_shentsize */
+ htole16(sizeof(elfhdr.sh) / sizeof(elfhdr.sh[0])), /* e_shnum */
+ htole16(1) /* e_shstrndx */
+ },
+ {
+ {
+ htole32(PT_LOAD), /* p_type */
+ htole32(sizeof(elfhdr)), /* p_offset */
+ htole32(SET_ME), /* p_vaddr */
+ htole32(SET_ME), /* p_paddr */
+ htole32(SET_ME), /* p_filesz */
+ htole32(SET_ME), /* p_memsz */
+ htole32(PF_R | PF_X), /* p_flags */
+ htole32(0x1000) /* p_align */
+ },
+ {
+ htole32(PT_LOAD), /* p_type */
+ htole32(SET_ME), /* p_offset */
+ htole32(SET_ME), /* p_vaddr */
+ htole32(SET_ME), /* p_paddr */
+ htole32(SET_ME), /* p_filesz */
+ htole32(SET_ME), /* p_memsz */
+ htole32(PF_R | PF_W), /* p_flags */
+ htole32(0x1000) /* p_align */
+ }
+ },
+ {
+ {
+ 0, htole32(SHT_NULL), 0, 0, 0, 0, htole32(SHN_UNDEF), 0, 0, 0
+ },
+ {
+ htole32(1), /* sh_name */
+ htole32(SHT_STRTAB), /* sh_type */
+ 0, /* sh_flags */
+ 0, /* sh_addr */
+ htole32(offsetof(struct elfh, shstrtab)), /* sh_offset */
+ htole32(sizeof(elfhdr.shstrtab)), /* sh_size */
+ htole32(SHN_UNDEF), /* sh_link */
+ 0, /* sh_info */
+ htole32(1), /* sh_addralign */
+ 0 /* sh_entsize */
+ },
+ {
+ htole32(0xb), /* sh_name */
+ htole32(SHT_PROGBITS), /* sh_type */
+ htole32(SHF_EXECINSTR | SHF_ALLOC), /* sh_flags */
+ htole32(SET_ME), /* sh_addr */
+ htole32(SET_ME), /* sh_offset */
+ htole32(SET_ME), /* sh_size */
+ htole32(SHN_UNDEF), /* sh_link */
+ 0, /* sh_info */
+ htole32(4), /* sh_addralign */
+ 0 /* sh_entsize */
+ },
+ {
+ htole32(0x11), /* sh_name */
+ htole32(SHT_PROGBITS), /* sh_type */
+ htole32(SHF_ALLOC | SHF_WRITE), /* sh_flags */
+ htole32(SET_ME), /* sh_addr */
+ htole32(SET_ME), /* sh_offset */
+ htole32(SET_ME), /* sh_size */
+ htole32(SHN_UNDEF), /* sh_link */
+ 0, /* sh_info */
+ htole32(4), /* sh_addralign */
+ 0 /* sh_entsize */
+ }
+ },
+ "\0.shstrtab\0.text\0.data" /* shstrtab */
+};
diff --git a/usr.sbin/btxld/elfh.h b/usr.sbin/btxld/elfh.h
new file mode 100644
index 0000000..a4ab134
--- /dev/null
+++ b/usr.sbin/btxld/elfh.h
@@ -0,0 +1,38 @@
+/*
+ * 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 AND CONTRIBUTORS``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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/elf32.h>
+
+struct elfh {
+ Elf32_Ehdr e; /* ELF header */
+ Elf32_Phdr p[2]; /* program header */
+ Elf32_Shdr sh[4]; /* section header */
+ char shstrtab[28]; /* section header string table */
+};
+
+extern const struct elfh elfhdr; /* ELF header template */
diff --git a/usr.sbin/camdd/Makefile b/usr.sbin/camdd/Makefile
new file mode 100644
index 0000000..64ee61b
--- /dev/null
+++ b/usr.sbin/camdd/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+PROG= camdd
+SRCS= camdd.c
+SDIR= ${.CURDIR}/../../sys
+LIBADD= cam mt util pthread
+NO_WTHREAD_SAFETY= 1
+MAN= camdd.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/camdd/Makefile.depend b/usr.sbin/camdd/Makefile.depend
new file mode 100644
index 0000000..17ca2bc
--- /dev/null
+++ b/usr.sbin/camdd/Makefile.depend
@@ -0,0 +1,25 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcam \
+ lib/libcompiler_rt \
+ lib/libexpat \
+ lib/libmt \
+ lib/libsbuf \
+ lib/libthr \
+ lib/libutil \
+ lib/libz \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/camdd/camdd.8 b/usr.sbin/camdd/camdd.8
new file mode 100644
index 0000000..af556bb
--- /dev/null
+++ b/usr.sbin/camdd/camdd.8
@@ -0,0 +1,283 @@
+.\"
+.\" Copyright (c) 2015 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)
+.\"
+.\" $FreeBSD$
+.\"
+.Dd November 11, 2015
+.Dt CAMDD 8
+.Os
+.Sh NAME
+.Nm camdd
+.Nd CAM data transfer utility
+.Sh SYNOPSIS
+.Nm
+.Aq Fl i|o Ar pass=pass_dev|file=filename,bs=blocksize,[...]
+.Op Fl C Ar retry_count
+.Op Fl E
+.Op Fl m Ar max_io
+.Op Fl t Ar timeout
+.Op Fl v
+.Op Fl h
+.Sh DESCRIPTION
+The
+.Nm
+utility is a sequential data transfer utility that offers standard
+.Xr read 2
+and
+.Xr write 2
+operation in addition to a mode that uses the asynchronous
+.Xr pass 4
+API.
+The asynchronous
+.Xr pass 4
+API allows multiple requests to be queued to a device simultaneously.
+.Pp
+.Nm
+collects performance information and will display it when the transfer
+completes, when
+.Nm
+is terminated or when it receives a SIGINFO signal.
+.Pp
+The following options are available:
+.Bl -tag -width 12n
+.It Fl i | o Ar args
+Specify the input and output device or file.
+Both
+.Fl i
+and
+.Fl o
+must be specified.
+There are a number of parameters that can be specified.
+One of the first two (file or pass) MUST be specified to indicate which I/O
+method to use on the device in question.
+.Bl -tag -width 9n
+.It pass=dev
+Specify a
+.Xr pass 4
+device to operate on.
+This requests that
+.Nm
+access the device in question be accessed via the asynchronous
+.Xr pass 4
+interface.
+.Pp
+The device name can be a
+.Xr pass 4
+name and unit number, for instance
+.Dq pass0 ,
+or a regular peripheral driver name and unit number, for instance
+.Dq da5 .
+It can also be the path of a
+.Xr pass 4
+or other disk device, like
+.Dq /dev/da5 .
+It may also be a bus:target:lun, for example:
+.Dq 0:5:0 .
+.Pp
+Only
+.Xr pass 4
+devices for
+.Tn SCSI
+disk-like devices are supported.
+.Tn ATA
+devices are not currently supported, but support could be added later.
+Specifically,
+.Tn SCSI
+Direct Access (type 0), WORM (type 4), CDROM (type 5), and RBC (Reduced
+Block Command, type 14) devices are supported.
+Tape drives, medium changers, enclosures etc. are not supported.
+.It file=path
+Specify a file or device to operate on.
+This requests that the file or device in question be accessed using the
+standard
+.Xr read 2
+and
+.Xr write 2
+system calls.
+The file interface does not support queueing multiple commands at a time.
+It does support probing disk sector size and capacity information, and tape
+blocksize and maximum transfer size information.
+The file interface supports standard files, disks, tape drives, special
+devices, pipes and standard input and output.
+If the file is specified as a
+.Dq - ,
+standard input or standard output are used.
+For tape devices, the specified blocksize will be the size that
+.Nm
+attempts to use to write to or read from the tape.
+When writing to a tape device, the blocksize is treated like a disk sector
+size.
+So, that means
+.Nm
+will not write anything smaller than the sector size.
+At the end of a transfer, if there isn't sufficient data from the reader
+to yield a full block,
+.Nm
+will add zeros on the end of the data from the reader to make up a full
+block.
+.It bs=N
+Specify the blocksize to use for transfers.
+.Nm
+will attempt to read or write using the requested blocksize.
+.Pp
+Note that the blocksize given only applies to either the input or the
+output path.
+To use the same blocksize for the input and output transfers, you must
+specify that blocksize with both the
+.Fl i
+and
+.Fl o
+arguments.
+.Pp
+The blocksize may be specified in bytes, or using any suffix (e.g. k, M, G)
+supported by
+.Xr expand_number 3 .
+.It offset=N
+Specify the starting offset for the input or output device or file.
+The offset may be specified in bytes, or by using any suffix (e.g. k, M, G)
+supported by
+.Xr expand_number 3 .
+.It depth=N
+Specify a desired queue depth for the input or output path.
+.Nm
+will attempt to keep the requested number of requests of the specified
+blocksize queued to the input or output device.
+Queue depths greater than 1 are only supported for the asynchronous
+.Xr pass 4
+output method.
+The queue depth is maintained on a best effort basis, and may not be
+possible to maintain for especially fast devices.
+For writes, maintaining the queue depth also depends on a sufficiently
+fast reading device.
+.It mcs=N
+Specify the minimum command size to use for
+.Xr pass 4
+devices.
+Some devices do not support 6 byte
+.Tn SCSI
+commands.
+The
+.Xr da 4
+device handles this restriction automatically, but the
+.Xr pass 4
+device allows the user to specify the
+.Tn SCSI
+command used.
+If a device does not accept 6 byte
+.Tn SCSI
+READ/WRITE commands (which is the default at lower LBAs), it will generally
+accept 10 byte
+.Tn SCSI
+commands instead.
+.It debug=N
+Specify the debug level for this device.
+There is currently only one debug level setting, so setting this to any
+non-zero value will turn on debugging.
+The debug facility may be expanded in the future.
+.El
+.It Fl C Ar count
+Specify the retry count for commands sent via the asynchronous
+.Xr pass 4
+interface.
+This does not apply to commands sent via the file interface.
+.It Fl E
+Enable kernel error recovery for the
+.Xr pass 4
+driver.
+If error recovery is not enabled, unit attention conditions and other
+transient failures may cause the transfer to fail.
+.It Fl m Ar size
+Specify the maximum amount of data to be transferred.
+This may be specified in bytes, or by using any suffix (e.g. K, M, G)
+supported by
+.Xr expand_number 3 .
+.It Fl t Ar timeout
+Specify the command timeout in seconds to use for commands sent via the
+.Xr pass 4
+driver.
+.It Fl v
+Enable verbose reporting of errors.
+This is recommended to aid in debugging any
+.Tn SCSI
+issues that come up.
+.It Fl h
+Display the
+.Nm
+usage message.
+.El
+.Pp
+If
+.Nm
+receives a SIGINFO signal, it will print the current input and output byte
+counts, elapsed runtime and average throughput.
+If
+.Nm
+receives a SIGINT signal, it will print the current input and output byte
+counts, elapsed runtime and average throughput and then exit.
+.Sh EXAMPLES
+.Dl camdd -i pass=da8,bs=512k,depth=4 -o pass=da3,bs=512k,depth=4
+.Pp
+Copy all data from da8 to da3 using a blocksize of 512k for both drives,
+and attempt to maintain a queue depth of 4 on both the input and output
+devices.
+The transfer will stop when the end of either device is reached.
+.Pp
+.Dl camdd -i file=/dev/zero,bs=1M -o pass=da5,bs=1M,depth=4 -m 100M
+.Pp
+Read 1MB blocks of zeros from /dev/zero, and write them to da5 with a
+desired queue depth of 4.
+Stop the transfer after 100MB has been written.
+.Pp
+.Dl camdd -i pass=da8,bs=1M,depth=3 -o file=disk.img
+.Pp
+Copy disk da8 using a 1MB blocksize and desired queue depth of 3 to the
+file disk.img.
+.Pp
+.Dl camdd -i file=/etc/rc -o file=-
+.Pp
+Read the file /etc/rc and write it to standard output.
+.Pp
+.Dl camdd -i pass=da10,bs=64k,depth=16 -o file=/dev/nsa0,bs=128k
+.Pp
+Copy 64K blocks from the disk da10 with a queue depth of 16, and write
+to the tape drive sa0 with a 128k blocksize.
+The copy will stop when either the end of the disk or tape is reached.
+.Sh SEE ALSO
+.Xr cam 3 ,
+.Xr cam 4 ,
+.Xr pass 4 ,
+.Xr camcontrol 8
+.Sh HISTORY
+.Nm
+first appeared in
+.Fx 10.2
+.Sh AUTHORS
+.An Kenneth Merry Aq Mt ken@FreeBSD.org
diff --git a/usr.sbin/camdd/camdd.c b/usr.sbin/camdd/camdd.c
new file mode 100644
index 0000000..9284eb5
--- /dev/null
+++ b/usr.sbin/camdd/camdd.c
@@ -0,0 +1,3423 @@
+/*-
+ * Copyright (c) 1997-2007 Kenneth D. Merry
+ * Copyright (c) 2013, 2014, 2015 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)
+ */
+
+/*
+ * This is eventually intended to be:
+ * - A basic data transfer/copy utility
+ * - A simple benchmark utility
+ * - An example of how to use the asynchronous pass(4) driver interface.
+ */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/ioctl.h>
+#include <sys/stdint.h>
+#include <sys/types.h>
+#include <sys/endian.h>
+#include <sys/param.h>
+#include <sys/sbuf.h>
+#include <sys/stat.h>
+#include <sys/event.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+#include <vm/vm.h>
+#include <machine/bus.h>
+#include <sys/bus.h>
+#include <sys/bus_dma.h>
+#include <sys/mtio.h>
+#include <sys/conf.h>
+#include <sys/disk.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <semaphore.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>
+#include <pthread.h>
+#include <assert.h>
+#include <bsdxml.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_da.h>
+#include <cam/scsi/scsi_pass.h>
+#include <cam/scsi/scsi_message.h>
+#include <cam/scsi/smp_all.h>
+#include <camlib.h>
+#include <mtlib.h>
+#include <zlib.h>
+
+typedef enum {
+ CAMDD_CMD_NONE = 0x00000000,
+ CAMDD_CMD_HELP = 0x00000001,
+ CAMDD_CMD_WRITE = 0x00000002,
+ CAMDD_CMD_READ = 0x00000003
+} camdd_cmdmask;
+
+typedef enum {
+ CAMDD_ARG_NONE = 0x00000000,
+ CAMDD_ARG_VERBOSE = 0x00000001,
+ CAMDD_ARG_DEVICE = 0x00000002,
+ CAMDD_ARG_BUS = 0x00000004,
+ CAMDD_ARG_TARGET = 0x00000008,
+ CAMDD_ARG_LUN = 0x00000010,
+ CAMDD_ARG_UNIT = 0x00000020,
+ CAMDD_ARG_TIMEOUT = 0x00000040,
+ CAMDD_ARG_ERR_RECOVER = 0x00000080,
+ CAMDD_ARG_RETRIES = 0x00000100
+} camdd_argmask;
+
+typedef enum {
+ CAMDD_DEV_NONE = 0x00,
+ CAMDD_DEV_PASS = 0x01,
+ CAMDD_DEV_FILE = 0x02
+} camdd_dev_type;
+
+struct camdd_io_opts {
+ camdd_dev_type dev_type;
+ char *dev_name;
+ uint64_t blocksize;
+ uint64_t queue_depth;
+ uint64_t offset;
+ int min_cmd_size;
+ int write_dev;
+ uint64_t debug;
+};
+
+typedef enum {
+ CAMDD_BUF_NONE,
+ CAMDD_BUF_DATA,
+ CAMDD_BUF_INDIRECT
+} camdd_buf_type;
+
+struct camdd_buf_indirect {
+ /*
+ * Pointer to the source buffer.
+ */
+ struct camdd_buf *src_buf;
+
+ /*
+ * Offset into the source buffer, in bytes.
+ */
+ uint64_t offset;
+ /*
+ * Pointer to the starting point in the source buffer.
+ */
+ uint8_t *start_ptr;
+
+ /*
+ * Length of this chunk in bytes.
+ */
+ size_t len;
+};
+
+struct camdd_buf_data {
+ /*
+ * Buffer allocated when we allocate this camdd_buf. This should
+ * be the size of the blocksize for this device.
+ */
+ uint8_t *buf;
+
+ /*
+ * The amount of backing store allocated in buf. Generally this
+ * will be the blocksize of the device.
+ */
+ uint32_t alloc_len;
+
+ /*
+ * The amount of data that was put into the buffer (on reads) or
+ * the amount of data we have put onto the src_list so far (on
+ * writes).
+ */
+ uint32_t fill_len;
+
+ /*
+ * The amount of data that was not transferred.
+ */
+ uint32_t resid;
+
+ /*
+ * Starting byte offset on the reader.
+ */
+ uint64_t src_start_offset;
+
+ /*
+ * CCB used for pass(4) device targets.
+ */
+ union ccb ccb;
+
+ /*
+ * Number of scatter/gather segments.
+ */
+ int sg_count;
+
+ /*
+ * Set if we had to tack on an extra buffer to round the transfer
+ * up to a sector size.
+ */
+ int extra_buf;
+
+ /*
+ * Scatter/gather list used generally when we're the writer for a
+ * pass(4) device.
+ */
+ bus_dma_segment_t *segs;
+
+ /*
+ * Scatter/gather list used generally when we're the writer for a
+ * file or block device;
+ */
+ struct iovec *iovec;
+};
+
+union camdd_buf_types {
+ struct camdd_buf_indirect indirect;
+ struct camdd_buf_data data;
+};
+
+typedef enum {
+ CAMDD_STATUS_NONE,
+ CAMDD_STATUS_OK,
+ CAMDD_STATUS_SHORT_IO,
+ CAMDD_STATUS_EOF,
+ CAMDD_STATUS_ERROR
+} camdd_buf_status;
+
+struct camdd_buf {
+ camdd_buf_type buf_type;
+ union camdd_buf_types buf_type_spec;
+
+ camdd_buf_status status;
+
+ uint64_t lba;
+ size_t len;
+
+ /*
+ * A reference count of how many indirect buffers point to this
+ * buffer.
+ */
+ int refcount;
+
+ /*
+ * A link back to our parent device.
+ */
+ struct camdd_dev *dev;
+ STAILQ_ENTRY(camdd_buf) links;
+ STAILQ_ENTRY(camdd_buf) work_links;
+
+ /*
+ * A count of the buffers on the src_list.
+ */
+ int src_count;
+
+ /*
+ * List of buffers from our partner thread that are the components
+ * of this buffer for the I/O. Uses src_links.
+ */
+ STAILQ_HEAD(,camdd_buf) src_list;
+ STAILQ_ENTRY(camdd_buf) src_links;
+};
+
+#define NUM_DEV_TYPES 2
+
+struct camdd_dev_pass {
+ int scsi_dev_type;
+ struct cam_device *dev;
+ uint64_t max_sector;
+ uint32_t block_len;
+ uint32_t cpi_maxio;
+};
+
+typedef enum {
+ CAMDD_FILE_NONE,
+ CAMDD_FILE_REG,
+ CAMDD_FILE_STD,
+ CAMDD_FILE_PIPE,
+ CAMDD_FILE_DISK,
+ CAMDD_FILE_TAPE,
+ CAMDD_FILE_TTY,
+ CAMDD_FILE_MEM
+} camdd_file_type;
+
+typedef enum {
+ CAMDD_FF_NONE = 0x00,
+ CAMDD_FF_CAN_SEEK = 0x01
+} camdd_file_flags;
+
+struct camdd_dev_file {
+ int fd;
+ struct stat sb;
+ char filename[MAXPATHLEN + 1];
+ camdd_file_type file_type;
+ camdd_file_flags file_flags;
+ uint8_t *tmp_buf;
+};
+
+struct camdd_dev_block {
+ int fd;
+ uint64_t size_bytes;
+ uint32_t block_len;
+};
+
+union camdd_dev_spec {
+ struct camdd_dev_pass pass;
+ struct camdd_dev_file file;
+ struct camdd_dev_block block;
+};
+
+typedef enum {
+ CAMDD_DEV_FLAG_NONE = 0x00,
+ CAMDD_DEV_FLAG_EOF = 0x01,
+ CAMDD_DEV_FLAG_PEER_EOF = 0x02,
+ CAMDD_DEV_FLAG_ACTIVE = 0x04,
+ CAMDD_DEV_FLAG_EOF_SENT = 0x08,
+ CAMDD_DEV_FLAG_EOF_QUEUED = 0x10
+} camdd_dev_flags;
+
+struct camdd_dev {
+ camdd_dev_type dev_type;
+ union camdd_dev_spec dev_spec;
+ camdd_dev_flags flags;
+ char device_name[MAXPATHLEN+1];
+ uint32_t blocksize;
+ uint32_t sector_size;
+ uint64_t max_sector;
+ uint64_t sector_io_limit;
+ int min_cmd_size;
+ int write_dev;
+ int retry_count;
+ int io_timeout;
+ int debug;
+ uint64_t start_offset_bytes;
+ uint64_t next_io_pos_bytes;
+ uint64_t next_peer_pos_bytes;
+ uint64_t next_completion_pos_bytes;
+ uint64_t peer_bytes_queued;
+ uint64_t bytes_transferred;
+ uint32_t target_queue_depth;
+ uint32_t cur_active_io;
+ uint8_t *extra_buf;
+ uint32_t extra_buf_len;
+ struct camdd_dev *peer_dev;
+ pthread_mutex_t mutex;
+ pthread_cond_t cond;
+ int kq;
+
+ int (*run)(struct camdd_dev *dev);
+ int (*fetch)(struct camdd_dev *dev);
+
+ /*
+ * Buffers that are available for I/O. Uses links.
+ */
+ STAILQ_HEAD(,camdd_buf) free_queue;
+
+ /*
+ * Free indirect buffers. These are used for breaking a large
+ * buffer into multiple pieces.
+ */
+ STAILQ_HEAD(,camdd_buf) free_indirect_queue;
+
+ /*
+ * Buffers that have been queued to the kernel. Uses links.
+ */
+ STAILQ_HEAD(,camdd_buf) active_queue;
+
+ /*
+ * Will generally contain one of our buffers that is waiting for enough
+ * I/O from our partner thread to be able to execute. This will
+ * generally happen when our per-I/O-size is larger than the
+ * partner thread's per-I/O-size. Uses links.
+ */
+ STAILQ_HEAD(,camdd_buf) pending_queue;
+
+ /*
+ * Number of buffers on the pending queue
+ */
+ int num_pending_queue;
+
+ /*
+ * Buffers that are filled and ready to execute. This is used when
+ * our partner (reader) thread sends us blocks that are larger than
+ * our blocksize, and so we have to split them into multiple pieces.
+ */
+ STAILQ_HEAD(,camdd_buf) run_queue;
+
+ /*
+ * Number of buffers on the run queue.
+ */
+ int num_run_queue;
+
+ STAILQ_HEAD(,camdd_buf) reorder_queue;
+
+ int num_reorder_queue;
+
+ /*
+ * Buffers that have been queued to us by our partner thread
+ * (generally the reader thread) to be written out. Uses
+ * work_links.
+ */
+ STAILQ_HEAD(,camdd_buf) work_queue;
+
+ /*
+ * Buffers that have been completed by our partner thread. Uses
+ * work_links.
+ */
+ STAILQ_HEAD(,camdd_buf) peer_done_queue;
+
+ /*
+ * Number of buffers on the peer done queue.
+ */
+ uint32_t num_peer_done_queue;
+
+ /*
+ * A list of buffers that we have queued to our peer thread. Uses
+ * links.
+ */
+ STAILQ_HEAD(,camdd_buf) peer_work_queue;
+
+ /*
+ * Number of buffers on the peer work queue.
+ */
+ uint32_t num_peer_work_queue;
+};
+
+static sem_t camdd_sem;
+static int need_exit = 0;
+static int error_exit = 0;
+static int need_status = 0;
+
+#ifndef min
+#define min(a, b) (a < b) ? a : b
+#endif
+
+/*
+ * XXX KDM private copy of timespecsub(). This is normally defined in
+ * sys/time.h, but is only enabled in the kernel. If that definition is
+ * enabled in userland, it breaks the build of libnetbsd.
+ */
+#ifndef timespecsub
+#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)
+#endif
+
+
+/* Generically usefull offsets into the peripheral private area */
+#define ppriv_ptr0 periph_priv.entries[0].ptr
+#define ppriv_ptr1 periph_priv.entries[1].ptr
+#define ppriv_field0 periph_priv.entries[0].field
+#define ppriv_field1 periph_priv.entries[1].field
+
+#define ccb_buf ppriv_ptr0
+
+#define CAMDD_FILE_DEFAULT_BLOCK 524288
+#define CAMDD_FILE_DEFAULT_DEPTH 1
+#define CAMDD_PASS_MAX_BLOCK 1048576
+#define CAMDD_PASS_DEFAULT_DEPTH 6
+#define CAMDD_PASS_RW_TIMEOUT 60 * 1000
+
+static int parse_btl(char *tstr, int *bus, int *target, int *lun,
+ camdd_argmask *arglst);
+void camdd_free_dev(struct camdd_dev *dev);
+struct camdd_dev *camdd_alloc_dev(camdd_dev_type dev_type,
+ struct kevent *new_ke, int num_ke,
+ int retry_count, int timeout);
+static struct camdd_buf *camdd_alloc_buf(struct camdd_dev *dev,
+ camdd_buf_type buf_type);
+void camdd_release_buf(struct camdd_buf *buf);
+struct camdd_buf *camdd_get_buf(struct camdd_dev *dev, camdd_buf_type buf_type);
+int camdd_buf_sg_create(struct camdd_buf *buf, int iovec,
+ uint32_t sector_size, uint32_t *num_sectors_used,
+ int *double_buf_needed);
+uint32_t camdd_buf_get_len(struct camdd_buf *buf);
+void camdd_buf_add_child(struct camdd_buf *buf, struct camdd_buf *child_buf);
+int camdd_probe_tape(int fd, char *filename, uint64_t *max_iosize,
+ uint64_t *max_blk, uint64_t *min_blk, uint64_t *blk_gran);
+struct camdd_dev *camdd_probe_file(int fd, struct camdd_io_opts *io_opts,
+ int retry_count, int timeout);
+struct camdd_dev *camdd_probe_pass(struct cam_device *cam_dev,
+ struct camdd_io_opts *io_opts,
+ camdd_argmask arglist, int probe_retry_count,
+ int probe_timeout, int io_retry_count,
+ int io_timeout);
+void *camdd_file_worker(void *arg);
+camdd_buf_status camdd_ccb_status(union ccb *ccb);
+int camdd_queue_peer_buf(struct camdd_dev *dev, struct camdd_buf *buf);
+int camdd_complete_peer_buf(struct camdd_dev *dev, struct camdd_buf *peer_buf);
+void camdd_peer_done(struct camdd_buf *buf);
+void camdd_complete_buf(struct camdd_dev *dev, struct camdd_buf *buf,
+ int *error_count);
+int camdd_pass_fetch(struct camdd_dev *dev);
+int camdd_file_run(struct camdd_dev *dev);
+int camdd_pass_run(struct camdd_dev *dev);
+int camdd_get_next_lba_len(struct camdd_dev *dev, uint64_t *lba, ssize_t *len);
+int camdd_queue(struct camdd_dev *dev, struct camdd_buf *read_buf);
+void camdd_get_depth(struct camdd_dev *dev, uint32_t *our_depth,
+ uint32_t *peer_depth, uint32_t *our_bytes,
+ uint32_t *peer_bytes);
+void *camdd_worker(void *arg);
+void camdd_sig_handler(int sig);
+void camdd_print_status(struct camdd_dev *camdd_dev,
+ struct camdd_dev *other_dev,
+ struct timespec *start_time);
+int camdd_rw(struct camdd_io_opts *io_opts, int num_io_opts,
+ uint64_t max_io, int retry_count, int timeout);
+int camdd_parse_io_opts(char *args, int is_write,
+ struct camdd_io_opts *io_opts);
+void usage(void);
+
+/*
+ * 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, int *bus, int *target, int *lun, camdd_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 |= CAMDD_ARG_BUS;
+ convs++;
+ tmpstr = (char *)strtok(NULL, ":");
+ if ((tmpstr != NULL) && (*tmpstr != '\0')) {
+ *target = strtol(tmpstr, NULL, 0);
+ *arglst |= CAMDD_ARG_TARGET;
+ convs++;
+ tmpstr = (char *)strtok(NULL, ":");
+ if ((tmpstr != NULL) && (*tmpstr != '\0')) {
+ *lun = strtol(tmpstr, NULL, 0);
+ *arglst |= CAMDD_ARG_LUN;
+ convs++;
+ }
+ }
+ }
+
+ return convs;
+}
+
+/*
+ * XXX KDM clean up and free all of the buffers on the queue!
+ */
+void
+camdd_free_dev(struct camdd_dev *dev)
+{
+ if (dev == NULL)
+ return;
+
+ switch (dev->dev_type) {
+ case CAMDD_DEV_FILE: {
+ struct camdd_dev_file *file_dev = &dev->dev_spec.file;
+
+ if (file_dev->fd != -1)
+ close(file_dev->fd);
+ free(file_dev->tmp_buf);
+ break;
+ }
+ case CAMDD_DEV_PASS: {
+ struct camdd_dev_pass *pass_dev = &dev->dev_spec.pass;
+
+ if (pass_dev->dev != NULL)
+ cam_close_device(pass_dev->dev);
+ break;
+ }
+ default:
+ break;
+ }
+
+ free(dev);
+}
+
+struct camdd_dev *
+camdd_alloc_dev(camdd_dev_type dev_type, struct kevent *new_ke, int num_ke,
+ int retry_count, int timeout)
+{
+ struct camdd_dev *dev = NULL;
+ struct kevent *ke;
+ size_t ke_size;
+ int retval = 0;
+
+ dev = malloc(sizeof(*dev));
+ if (dev == NULL) {
+ warn("%s: unable to malloc %zu bytes", __func__, sizeof(*dev));
+ goto bailout;
+ }
+
+ bzero(dev, sizeof(*dev));
+
+ dev->dev_type = dev_type;
+ dev->io_timeout = timeout;
+ dev->retry_count = retry_count;
+ STAILQ_INIT(&dev->free_queue);
+ STAILQ_INIT(&dev->free_indirect_queue);
+ STAILQ_INIT(&dev->active_queue);
+ STAILQ_INIT(&dev->pending_queue);
+ STAILQ_INIT(&dev->run_queue);
+ STAILQ_INIT(&dev->reorder_queue);
+ STAILQ_INIT(&dev->work_queue);
+ STAILQ_INIT(&dev->peer_done_queue);
+ STAILQ_INIT(&dev->peer_work_queue);
+ retval = pthread_mutex_init(&dev->mutex, NULL);
+ if (retval != 0) {
+ warnc(retval, "%s: failed to initialize mutex", __func__);
+ goto bailout;
+ }
+
+ retval = pthread_cond_init(&dev->cond, NULL);
+ if (retval != 0) {
+ warnc(retval, "%s: failed to initialize condition variable",
+ __func__);
+ goto bailout;
+ }
+
+ dev->kq = kqueue();
+ if (dev->kq == -1) {
+ warn("%s: Unable to create kqueue", __func__);
+ goto bailout;
+ }
+
+ ke_size = sizeof(struct kevent) * (num_ke + 4);
+ ke = malloc(ke_size);
+ if (ke == NULL) {
+ warn("%s: unable to malloc %zu bytes", __func__, ke_size);
+ goto bailout;
+ }
+ bzero(ke, ke_size);
+ if (num_ke > 0)
+ bcopy(new_ke, ke, num_ke * sizeof(struct kevent));
+
+ EV_SET(&ke[num_ke++], (uintptr_t)&dev->work_queue, EVFILT_USER,
+ EV_ADD|EV_ENABLE|EV_CLEAR, 0,0, 0);
+ EV_SET(&ke[num_ke++], (uintptr_t)&dev->peer_done_queue, EVFILT_USER,
+ EV_ADD|EV_ENABLE|EV_CLEAR, 0,0, 0);
+ EV_SET(&ke[num_ke++], SIGINFO, EVFILT_SIGNAL, EV_ADD|EV_ENABLE, 0,0,0);
+ EV_SET(&ke[num_ke++], SIGINT, EVFILT_SIGNAL, EV_ADD|EV_ENABLE, 0,0,0);
+
+ retval = kevent(dev->kq, ke, num_ke, NULL, 0, NULL);
+ if (retval == -1) {
+ warn("%s: Unable to register kevents", __func__);
+ goto bailout;
+ }
+
+
+ return (dev);
+
+bailout:
+ free(dev);
+
+ return (NULL);
+}
+
+static struct camdd_buf *
+camdd_alloc_buf(struct camdd_dev *dev, camdd_buf_type buf_type)
+{
+ struct camdd_buf *buf = NULL;
+ uint8_t *data_ptr = NULL;
+
+ /*
+ * We only need to allocate data space for data buffers.
+ */
+ switch (buf_type) {
+ case CAMDD_BUF_DATA:
+ data_ptr = malloc(dev->blocksize);
+ if (data_ptr == NULL) {
+ warn("unable to allocate %u bytes", dev->blocksize);
+ goto bailout_error;
+ }
+ break;
+ default:
+ break;
+ }
+
+ buf = malloc(sizeof(*buf));
+ if (buf == NULL) {
+ warn("unable to allocate %zu bytes", sizeof(*buf));
+ goto bailout_error;
+ }
+
+ bzero(buf, sizeof(*buf));
+ buf->buf_type = buf_type;
+ buf->dev = dev;
+ switch (buf_type) {
+ case CAMDD_BUF_DATA: {
+ struct camdd_buf_data *data;
+
+ data = &buf->buf_type_spec.data;
+
+ data->alloc_len = dev->blocksize;
+ data->buf = data_ptr;
+ break;
+ }
+ case CAMDD_BUF_INDIRECT:
+ break;
+ default:
+ break;
+ }
+ STAILQ_INIT(&buf->src_list);
+
+ return (buf);
+
+bailout_error:
+ if (data_ptr != NULL)
+ free(data_ptr);
+
+ if (buf != NULL)
+ free(buf);
+
+ return (NULL);
+}
+
+void
+camdd_release_buf(struct camdd_buf *buf)
+{
+ struct camdd_dev *dev;
+
+ dev = buf->dev;
+
+ switch (buf->buf_type) {
+ case CAMDD_BUF_DATA: {
+ struct camdd_buf_data *data;
+
+ data = &buf->buf_type_spec.data;
+
+ if (data->segs != NULL) {
+ if (data->extra_buf != 0) {
+ void *extra_buf;
+
+ extra_buf = (void *)
+ data->segs[data->sg_count - 1].ds_addr;
+ free(extra_buf);
+ data->extra_buf = 0;
+ }
+ free(data->segs);
+ data->segs = NULL;
+ data->sg_count = 0;
+ } else if (data->iovec != NULL) {
+ if (data->extra_buf != 0) {
+ free(data->iovec[data->sg_count - 1].iov_base);
+ data->extra_buf = 0;
+ }
+ free(data->iovec);
+ data->iovec = NULL;
+ data->sg_count = 0;
+ }
+ STAILQ_INSERT_TAIL(&dev->free_queue, buf, links);
+ break;
+ }
+ case CAMDD_BUF_INDIRECT:
+ STAILQ_INSERT_TAIL(&dev->free_indirect_queue, buf, links);
+ break;
+ default:
+ err(1, "%s: Invalid buffer type %d for released buffer",
+ __func__, buf->buf_type);
+ break;
+ }
+}
+
+struct camdd_buf *
+camdd_get_buf(struct camdd_dev *dev, camdd_buf_type buf_type)
+{
+ struct camdd_buf *buf = NULL;
+
+ switch (buf_type) {
+ case CAMDD_BUF_DATA:
+ buf = STAILQ_FIRST(&dev->free_queue);
+ if (buf != NULL) {
+ struct camdd_buf_data *data;
+ uint8_t *data_ptr;
+ uint32_t alloc_len;
+
+ STAILQ_REMOVE_HEAD(&dev->free_queue, links);
+ data = &buf->buf_type_spec.data;
+ data_ptr = data->buf;
+ alloc_len = data->alloc_len;
+ bzero(buf, sizeof(*buf));
+ data->buf = data_ptr;
+ data->alloc_len = alloc_len;
+ }
+ break;
+ case CAMDD_BUF_INDIRECT:
+ buf = STAILQ_FIRST(&dev->free_indirect_queue);
+ if (buf != NULL) {
+ STAILQ_REMOVE_HEAD(&dev->free_indirect_queue, links);
+
+ bzero(buf, sizeof(*buf));
+ }
+ break;
+ default:
+ warnx("Unknown buffer type %d requested", buf_type);
+ break;
+ }
+
+
+ if (buf == NULL)
+ return (camdd_alloc_buf(dev, buf_type));
+ else {
+ STAILQ_INIT(&buf->src_list);
+ buf->dev = dev;
+ buf->buf_type = buf_type;
+
+ return (buf);
+ }
+}
+
+int
+camdd_buf_sg_create(struct camdd_buf *buf, int iovec, uint32_t sector_size,
+ uint32_t *num_sectors_used, int *double_buf_needed)
+{
+ struct camdd_buf *tmp_buf;
+ struct camdd_buf_data *data;
+ uint8_t *extra_buf = NULL;
+ size_t extra_buf_len = 0;
+ int i, retval = 0;
+
+ data = &buf->buf_type_spec.data;
+
+ data->sg_count = buf->src_count;
+ /*
+ * Compose a scatter/gather list from all of the buffers in the list.
+ * If the length of the buffer isn't a multiple of the sector size,
+ * we'll have to add an extra buffer. This should only happen
+ * at the end of a transfer.
+ */
+ if ((data->fill_len % sector_size) != 0) {
+ extra_buf_len = sector_size - (data->fill_len % sector_size);
+ extra_buf = calloc(extra_buf_len, 1);
+ if (extra_buf == NULL) {
+ warn("%s: unable to allocate %zu bytes for extra "
+ "buffer space", __func__, extra_buf_len);
+ retval = 1;
+ goto bailout;
+ }
+ data->extra_buf = 1;
+ data->sg_count++;
+ }
+ if (iovec == 0) {
+ data->segs = calloc(data->sg_count, sizeof(bus_dma_segment_t));
+ if (data->segs == NULL) {
+ warn("%s: unable to allocate %zu bytes for S/G list",
+ __func__, sizeof(bus_dma_segment_t) *
+ data->sg_count);
+ retval = 1;
+ goto bailout;
+ }
+
+ } else {
+ data->iovec = calloc(data->sg_count, sizeof(struct iovec));
+ if (data->iovec == NULL) {
+ warn("%s: unable to allocate %zu bytes for S/G list",
+ __func__, sizeof(struct iovec) * data->sg_count);
+ retval = 1;
+ goto bailout;
+ }
+ }
+
+ for (i = 0, tmp_buf = STAILQ_FIRST(&buf->src_list);
+ i < buf->src_count && tmp_buf != NULL; i++,
+ tmp_buf = STAILQ_NEXT(tmp_buf, src_links)) {
+
+ if (tmp_buf->buf_type == CAMDD_BUF_DATA) {
+ struct camdd_buf_data *tmp_data;
+
+ tmp_data = &tmp_buf->buf_type_spec.data;
+ if (iovec == 0) {
+ data->segs[i].ds_addr =
+ (bus_addr_t) tmp_data->buf;
+ data->segs[i].ds_len = tmp_data->fill_len -
+ tmp_data->resid;
+ } else {
+ data->iovec[i].iov_base = tmp_data->buf;
+ data->iovec[i].iov_len = tmp_data->fill_len -
+ tmp_data->resid;
+ }
+ if (((tmp_data->fill_len - tmp_data->resid) %
+ sector_size) != 0)
+ *double_buf_needed = 1;
+ } else {
+ struct camdd_buf_indirect *tmp_ind;
+
+ tmp_ind = &tmp_buf->buf_type_spec.indirect;
+ if (iovec == 0) {
+ data->segs[i].ds_addr =
+ (bus_addr_t)tmp_ind->start_ptr;
+ data->segs[i].ds_len = tmp_ind->len;
+ } else {
+ data->iovec[i].iov_base = tmp_ind->start_ptr;
+ data->iovec[i].iov_len = tmp_ind->len;
+ }
+ if ((tmp_ind->len % sector_size) != 0)
+ *double_buf_needed = 1;
+ }
+ }
+
+ if (extra_buf != NULL) {
+ if (iovec == 0) {
+ data->segs[i].ds_addr = (bus_addr_t)extra_buf;
+ data->segs[i].ds_len = extra_buf_len;
+ } else {
+ data->iovec[i].iov_base = extra_buf;
+ data->iovec[i].iov_len = extra_buf_len;
+ }
+ i++;
+ }
+ if ((tmp_buf != NULL) || (i != data->sg_count)) {
+ warnx("buffer source count does not match "
+ "number of buffers in list!");
+ retval = 1;
+ goto bailout;
+ }
+
+bailout:
+ if (retval == 0) {
+ *num_sectors_used = (data->fill_len + extra_buf_len) /
+ sector_size;
+ }
+ return (retval);
+}
+
+uint32_t
+camdd_buf_get_len(struct camdd_buf *buf)
+{
+ uint32_t len = 0;
+
+ if (buf->buf_type != CAMDD_BUF_DATA) {
+ struct camdd_buf_indirect *indirect;
+
+ indirect = &buf->buf_type_spec.indirect;
+ len = indirect->len;
+ } else {
+ struct camdd_buf_data *data;
+
+ data = &buf->buf_type_spec.data;
+ len = data->fill_len;
+ }
+
+ return (len);
+}
+
+void
+camdd_buf_add_child(struct camdd_buf *buf, struct camdd_buf *child_buf)
+{
+ struct camdd_buf_data *data;
+
+ assert(buf->buf_type == CAMDD_BUF_DATA);
+
+ data = &buf->buf_type_spec.data;
+
+ STAILQ_INSERT_TAIL(&buf->src_list, child_buf, src_links);
+ buf->src_count++;
+
+ data->fill_len += camdd_buf_get_len(child_buf);
+}
+
+typedef enum {
+ CAMDD_TS_MAX_BLK,
+ CAMDD_TS_MIN_BLK,
+ CAMDD_TS_BLK_GRAN,
+ CAMDD_TS_EFF_IOSIZE
+} camdd_status_item_index;
+
+static struct camdd_status_items {
+ const char *name;
+ struct mt_status_entry *entry;
+} req_status_items[] = {
+ { "max_blk", NULL },
+ { "min_blk", NULL },
+ { "blk_gran", NULL },
+ { "max_effective_iosize", NULL }
+};
+
+int
+camdd_probe_tape(int fd, char *filename, uint64_t *max_iosize,
+ uint64_t *max_blk, uint64_t *min_blk, uint64_t *blk_gran)
+{
+ struct mt_status_data status_data;
+ char *xml_str = NULL;
+ unsigned int i;
+ int retval = 0;
+
+ retval = mt_get_xml_str(fd, MTIOCEXTGET, &xml_str);
+ if (retval != 0)
+ err(1, "Couldn't get XML string from %s", filename);
+
+ retval = mt_get_status(xml_str, &status_data);
+ if (retval != XML_STATUS_OK) {
+ warn("couldn't get status for %s", filename);
+ retval = 1;
+ goto bailout;
+ } else
+ retval = 0;
+
+ if (status_data.error != 0) {
+ warnx("%s", status_data.error_str);
+ retval = 1;
+ goto bailout;
+ }
+
+ for (i = 0; i < sizeof(req_status_items) /
+ sizeof(req_status_items[0]); i++) {
+ char *name;
+
+ name = __DECONST(char *, req_status_items[i].name);
+ req_status_items[i].entry = mt_status_entry_find(&status_data,
+ name);
+ if (req_status_items[i].entry == NULL) {
+ errx(1, "Cannot find status entry %s",
+ req_status_items[i].name);
+ }
+ }
+
+ *max_iosize = req_status_items[CAMDD_TS_EFF_IOSIZE].entry->value_unsigned;
+ *max_blk= req_status_items[CAMDD_TS_MAX_BLK].entry->value_unsigned;
+ *min_blk= req_status_items[CAMDD_TS_MIN_BLK].entry->value_unsigned;
+ *blk_gran = req_status_items[CAMDD_TS_BLK_GRAN].entry->value_unsigned;
+bailout:
+
+ free(xml_str);
+ mt_status_free(&status_data);
+
+ return (retval);
+}
+
+struct camdd_dev *
+camdd_probe_file(int fd, struct camdd_io_opts *io_opts, int retry_count,
+ int timeout)
+{
+ struct camdd_dev *dev = NULL;
+ struct camdd_dev_file *file_dev;
+ uint64_t blocksize = io_opts->blocksize;
+
+ dev = camdd_alloc_dev(CAMDD_DEV_FILE, NULL, 0, retry_count, timeout);
+ if (dev == NULL)
+ goto bailout;
+
+ file_dev = &dev->dev_spec.file;
+ file_dev->fd = fd;
+ strlcpy(file_dev->filename, io_opts->dev_name,
+ sizeof(file_dev->filename));
+ strlcpy(dev->device_name, io_opts->dev_name, sizeof(dev->device_name));
+ if (blocksize == 0)
+ dev->blocksize = CAMDD_FILE_DEFAULT_BLOCK;
+ else
+ dev->blocksize = blocksize;
+
+ if ((io_opts->queue_depth != 0)
+ && (io_opts->queue_depth != 1)) {
+ warnx("Queue depth %ju for %s ignored, only 1 outstanding "
+ "command supported", (uintmax_t)io_opts->queue_depth,
+ io_opts->dev_name);
+ }
+ dev->target_queue_depth = CAMDD_FILE_DEFAULT_DEPTH;
+ dev->run = camdd_file_run;
+ dev->fetch = NULL;
+
+ /*
+ * We can effectively access files on byte boundaries. We'll reset
+ * this for devices like disks that can be accessed on sector
+ * boundaries.
+ */
+ dev->sector_size = 1;
+
+ if ((fd != STDIN_FILENO)
+ && (fd != STDOUT_FILENO)) {
+ int retval;
+
+ retval = fstat(fd, &file_dev->sb);
+ if (retval != 0) {
+ warn("Cannot stat %s", dev->device_name);
+ goto bailout;
+ camdd_free_dev(dev);
+ dev = NULL;
+ }
+ if (S_ISREG(file_dev->sb.st_mode)) {
+ file_dev->file_type = CAMDD_FILE_REG;
+ } else if (S_ISCHR(file_dev->sb.st_mode)) {
+ int type;
+
+ if (ioctl(fd, FIODTYPE, &type) == -1)
+ err(1, "FIODTYPE ioctl failed on %s",
+ dev->device_name);
+ else {
+ if (type & D_TAPE)
+ file_dev->file_type = CAMDD_FILE_TAPE;
+ else if (type & D_DISK)
+ file_dev->file_type = CAMDD_FILE_DISK;
+ else if (type & D_MEM)
+ file_dev->file_type = CAMDD_FILE_MEM;
+ else if (type & D_TTY)
+ file_dev->file_type = CAMDD_FILE_TTY;
+ }
+ } else if (S_ISDIR(file_dev->sb.st_mode)) {
+ errx(1, "cannot operate on directory %s",
+ dev->device_name);
+ } else if (S_ISFIFO(file_dev->sb.st_mode)) {
+ file_dev->file_type = CAMDD_FILE_PIPE;
+ } else
+ errx(1, "Cannot determine file type for %s",
+ dev->device_name);
+
+ switch (file_dev->file_type) {
+ case CAMDD_FILE_REG:
+ if (file_dev->sb.st_size != 0)
+ dev->max_sector = file_dev->sb.st_size - 1;
+ else
+ dev->max_sector = 0;
+ file_dev->file_flags |= CAMDD_FF_CAN_SEEK;
+ break;
+ case CAMDD_FILE_TAPE: {
+ uint64_t max_iosize, max_blk, min_blk, blk_gran;
+ /*
+ * Check block limits and maximum effective iosize.
+ * Make sure the blocksize is within the block
+ * limits (and a multiple of the minimum blocksize)
+ * and that the blocksize is <= maximum effective
+ * iosize.
+ */
+ retval = camdd_probe_tape(fd, dev->device_name,
+ &max_iosize, &max_blk, &min_blk, &blk_gran);
+ if (retval != 0)
+ errx(1, "Unable to probe tape %s",
+ dev->device_name);
+
+ /*
+ * The blocksize needs to be <= the maximum
+ * effective I/O size of the tape device. Note
+ * that this also takes into account the maximum
+ * blocksize reported by READ BLOCK LIMITS.
+ */
+ if (dev->blocksize > max_iosize) {
+ warnx("Blocksize %u too big for %s, limiting "
+ "to %ju", dev->blocksize, dev->device_name,
+ max_iosize);
+ dev->blocksize = max_iosize;
+ }
+
+ /*
+ * The blocksize needs to be at least min_blk;
+ */
+ if (dev->blocksize < min_blk) {
+ warnx("Blocksize %u too small for %s, "
+ "increasing to %ju", dev->blocksize,
+ dev->device_name, min_blk);
+ dev->blocksize = min_blk;
+ }
+
+ /*
+ * And the blocksize needs to be a multiple of
+ * the block granularity.
+ */
+ if ((blk_gran != 0)
+ && (dev->blocksize % (1 << blk_gran))) {
+ warnx("Blocksize %u for %s not a multiple of "
+ "%d, adjusting to %d", dev->blocksize,
+ dev->device_name, (1 << blk_gran),
+ dev->blocksize & ~((1 << blk_gran) - 1));
+ dev->blocksize &= ~((1 << blk_gran) - 1);
+ }
+
+ if (dev->blocksize == 0) {
+ errx(1, "Unable to derive valid blocksize for "
+ "%s", dev->device_name);
+ }
+
+ /*
+ * For tape drives, set the sector size to the
+ * blocksize so that we make sure not to write
+ * less than the blocksize out to the drive.
+ */
+ dev->sector_size = dev->blocksize;
+ break;
+ }
+ case CAMDD_FILE_DISK: {
+ off_t media_size;
+ unsigned int sector_size;
+
+ file_dev->file_flags |= CAMDD_FF_CAN_SEEK;
+
+ if (ioctl(fd, DIOCGSECTORSIZE, &sector_size) == -1) {
+ err(1, "DIOCGSECTORSIZE ioctl failed on %s",
+ dev->device_name);
+ }
+
+ if (sector_size == 0) {
+ errx(1, "DIOCGSECTORSIZE ioctl returned "
+ "invalid sector size %u for %s",
+ sector_size, dev->device_name);
+ }
+
+ if (ioctl(fd, DIOCGMEDIASIZE, &media_size) == -1) {
+ err(1, "DIOCGMEDIASIZE ioctl failed on %s",
+ dev->device_name);
+ }
+
+ if (media_size == 0) {
+ errx(1, "DIOCGMEDIASIZE ioctl returned "
+ "invalid media size %ju for %s",
+ (uintmax_t)media_size, dev->device_name);
+ }
+
+ if (dev->blocksize % sector_size) {
+ errx(1, "%s blocksize %u not a multiple of "
+ "sector size %u", dev->device_name,
+ dev->blocksize, sector_size);
+ }
+
+ dev->sector_size = sector_size;
+ dev->max_sector = (media_size / sector_size) - 1;
+ break;
+ }
+ case CAMDD_FILE_MEM:
+ file_dev->file_flags |= CAMDD_FF_CAN_SEEK;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if ((io_opts->offset != 0)
+ && ((file_dev->file_flags & CAMDD_FF_CAN_SEEK) == 0)) {
+ warnx("Offset %ju specified for %s, but we cannot seek on %s",
+ io_opts->offset, io_opts->dev_name, io_opts->dev_name);
+ goto bailout_error;
+ }
+#if 0
+ else if ((io_opts->offset != 0)
+ && ((io_opts->offset % dev->sector_size) != 0)) {
+ warnx("Offset %ju for %s is not a multiple of the "
+ "sector size %u", io_opts->offset,
+ io_opts->dev_name, dev->sector_size);
+ goto bailout_error;
+ } else {
+ dev->start_offset_bytes = io_opts->offset;
+ }
+#endif
+
+bailout:
+ return (dev);
+
+bailout_error:
+ camdd_free_dev(dev);
+ return (NULL);
+}
+
+/*
+ * Need to implement this. Do a basic probe:
+ * - Check the inquiry data, make sure we're talking to a device that we
+ * can reasonably expect to talk to -- direct, RBC, CD, WORM.
+ * - Send a test unit ready, make sure the device is available.
+ * - Get the capacity and block size.
+ */
+struct camdd_dev *
+camdd_probe_pass(struct cam_device *cam_dev, struct camdd_io_opts *io_opts,
+ camdd_argmask arglist, int probe_retry_count,
+ int probe_timeout, int io_retry_count, int io_timeout)
+{
+ union ccb *ccb;
+ uint64_t maxsector;
+ uint32_t cpi_maxio, max_iosize, pass_numblocks;
+ uint32_t block_len;
+ struct scsi_read_capacity_data rcap;
+ struct scsi_read_capacity_data_long rcaplong;
+ struct camdd_dev *dev;
+ struct camdd_dev_pass *pass_dev;
+ struct kevent ke;
+ int scsi_dev_type;
+
+ dev = NULL;
+
+ scsi_dev_type = SID_TYPE(&cam_dev->inq_data);
+ maxsector = 0;
+ block_len = 0;
+
+ /*
+ * For devices that support READ CAPACITY, we'll attempt to get the
+ * capacity. Otherwise, we really don't support tape or other
+ * devices via SCSI passthrough, so just return an error in that case.
+ */
+ switch (scsi_dev_type) {
+ case T_DIRECT:
+ case T_WORM:
+ case T_CDROM:
+ case T_OPTICAL:
+ case T_RBC:
+ break;
+ default:
+ errx(1, "Unsupported SCSI device type %d", scsi_dev_type);
+ break; /*NOTREACHED*/
+ }
+
+ ccb = cam_getccb(cam_dev);
+
+ if (ccb == NULL) {
+ warnx("%s: error allocating ccb", __func__);
+ goto bailout;
+ }
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr));
+
+ scsi_read_capacity(&ccb->csio,
+ /*retries*/ probe_retry_count,
+ /*cbfcnp*/ NULL,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ &rcap,
+ SSD_FULL_SIZE,
+ /*timeout*/ probe_timeout ? probe_timeout : 5000);
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (arglist & CAMDD_ARG_ERR_RECOVER)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+ if (cam_send_ccb(cam_dev, ccb) < 0) {
+ warn("error sending READ CAPACITY command");
+
+ cam_error_print(cam_dev, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+
+ goto bailout;
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ cam_error_print(cam_dev, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr);
+ 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 rcap_done;
+
+ scsi_read_capacity_16(&ccb->csio,
+ /*retries*/ probe_retry_count,
+ /*cbfcnp*/ NULL,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*lba*/ 0,
+ /*reladdr*/ 0,
+ /*pmi*/ 0,
+ (uint8_t *)&rcaplong,
+ sizeof(rcaplong),
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ probe_timeout ? probe_timeout : 5000);
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (arglist & CAMDD_ARG_ERR_RECOVER)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+ if (cam_send_ccb(cam_dev, ccb) < 0) {
+ warn("error sending READ CAPACITY (16) command");
+ cam_error_print(cam_dev, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ goto bailout;
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ cam_error_print(cam_dev, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr);
+ goto bailout;
+ }
+
+ maxsector = scsi_8btou64(rcaplong.addr);
+ block_len = scsi_4btoul(rcaplong.length);
+
+rcap_done:
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr));
+
+ ccb->ccb_h.func_code = XPT_PATH_INQ;
+ ccb->ccb_h.flags = CAM_DIR_NONE;
+ ccb->ccb_h.retry_count = 1;
+
+ if (cam_send_ccb(cam_dev, ccb) < 0) {
+ warn("error sending XPT_PATH_INQ CCB");
+
+ cam_error_print(cam_dev, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ goto bailout;
+ }
+
+ EV_SET(&ke, cam_dev->fd, EVFILT_READ, EV_ADD|EV_ENABLE, 0, 0, 0);
+
+ dev = camdd_alloc_dev(CAMDD_DEV_PASS, &ke, 1, io_retry_count,
+ io_timeout);
+ if (dev == NULL)
+ goto bailout;
+
+ pass_dev = &dev->dev_spec.pass;
+ pass_dev->scsi_dev_type = scsi_dev_type;
+ pass_dev->dev = cam_dev;
+ pass_dev->max_sector = maxsector;
+ pass_dev->block_len = block_len;
+ pass_dev->cpi_maxio = ccb->cpi.maxio;
+ snprintf(dev->device_name, sizeof(dev->device_name), "%s%u",
+ pass_dev->dev->device_name, pass_dev->dev->dev_unit_num);
+ dev->sector_size = block_len;
+ dev->max_sector = maxsector;
+
+
+ /*
+ * Determine the optimal blocksize to use for this device.
+ */
+
+ /*
+ * If the controller has not specified a maximum I/O size,
+ * just go with 128K as a somewhat conservative value.
+ */
+ if (pass_dev->cpi_maxio == 0)
+ cpi_maxio = 131072;
+ else
+ cpi_maxio = pass_dev->cpi_maxio;
+
+ /*
+ * If the controller has a large maximum I/O size, limit it
+ * to something smaller so that the kernel doesn't have trouble
+ * allocating buffers to copy data in and out for us.
+ * XXX KDM this is until we have unmapped I/O support in the kernel.
+ */
+ max_iosize = min(cpi_maxio, CAMDD_PASS_MAX_BLOCK);
+
+ /*
+ * If we weren't able to get a block size for some reason,
+ * default to 512 bytes.
+ */
+ block_len = pass_dev->block_len;
+ if (block_len == 0)
+ block_len = 512;
+
+ /*
+ * Figure out how many blocksize chunks will fit in the
+ * maximum I/O size.
+ */
+ pass_numblocks = max_iosize / block_len;
+
+ /*
+ * And finally, multiple the number of blocks by the LBA
+ * length to get our maximum block size;
+ */
+ dev->blocksize = pass_numblocks * block_len;
+
+ if (io_opts->blocksize != 0) {
+ if ((io_opts->blocksize % dev->sector_size) != 0) {
+ warnx("Blocksize %ju for %s is not a multiple of "
+ "sector size %u", (uintmax_t)io_opts->blocksize,
+ dev->device_name, dev->sector_size);
+ goto bailout_error;
+ }
+ dev->blocksize = io_opts->blocksize;
+ }
+ dev->target_queue_depth = CAMDD_PASS_DEFAULT_DEPTH;
+ if (io_opts->queue_depth != 0)
+ dev->target_queue_depth = io_opts->queue_depth;
+
+ if (io_opts->offset != 0) {
+ if (io_opts->offset > (dev->max_sector * dev->sector_size)) {
+ warnx("Offset %ju is past the end of device %s",
+ io_opts->offset, dev->device_name);
+ goto bailout_error;
+ }
+#if 0
+ else if ((io_opts->offset % dev->sector_size) != 0) {
+ warnx("Offset %ju for %s is not a multiple of the "
+ "sector size %u", io_opts->offset,
+ dev->device_name, dev->sector_size);
+ goto bailout_error;
+ }
+ dev->start_offset_bytes = io_opts->offset;
+#endif
+ }
+
+ dev->min_cmd_size = io_opts->min_cmd_size;
+
+ dev->run = camdd_pass_run;
+ dev->fetch = camdd_pass_fetch;
+
+bailout:
+ cam_freeccb(ccb);
+
+ return (dev);
+
+bailout_error:
+ cam_freeccb(ccb);
+
+ camdd_free_dev(dev);
+
+ return (NULL);
+}
+
+void *
+camdd_worker(void *arg)
+{
+ struct camdd_dev *dev = arg;
+ struct camdd_buf *buf;
+ struct timespec ts, *kq_ts;
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+
+ pthread_mutex_lock(&dev->mutex);
+
+ dev->flags |= CAMDD_DEV_FLAG_ACTIVE;
+
+ for (;;) {
+ struct kevent ke;
+ int retval = 0;
+
+ /*
+ * XXX KDM check the reorder queue depth?
+ */
+ if (dev->write_dev == 0) {
+ uint32_t our_depth, peer_depth, peer_bytes, our_bytes;
+ uint32_t target_depth = dev->target_queue_depth;
+ uint32_t peer_target_depth =
+ dev->peer_dev->target_queue_depth;
+ uint32_t peer_blocksize = dev->peer_dev->blocksize;
+
+ camdd_get_depth(dev, &our_depth, &peer_depth,
+ &our_bytes, &peer_bytes);
+
+#if 0
+ while (((our_depth < target_depth)
+ && (peer_depth < peer_target_depth))
+ || ((peer_bytes + our_bytes) <
+ (peer_blocksize * 2))) {
+#endif
+ while (((our_depth + peer_depth) <
+ (target_depth + peer_target_depth))
+ || ((peer_bytes + our_bytes) <
+ (peer_blocksize * 3))) {
+
+ retval = camdd_queue(dev, NULL);
+ if (retval == 1)
+ break;
+ else if (retval != 0) {
+ error_exit = 1;
+ goto bailout;
+ }
+
+ camdd_get_depth(dev, &our_depth, &peer_depth,
+ &our_bytes, &peer_bytes);
+ }
+ }
+ /*
+ * See if we have any I/O that is ready to execute.
+ */
+ buf = STAILQ_FIRST(&dev->run_queue);
+ if (buf != NULL) {
+ while (dev->target_queue_depth > dev->cur_active_io) {
+ retval = dev->run(dev);
+ if (retval == -1) {
+ dev->flags |= CAMDD_DEV_FLAG_EOF;
+ error_exit = 1;
+ break;
+ } else if (retval != 0) {
+ break;
+ }
+ }
+ }
+
+ /*
+ * We've reached EOF, or our partner has reached EOF.
+ */
+ if ((dev->flags & CAMDD_DEV_FLAG_EOF)
+ || (dev->flags & CAMDD_DEV_FLAG_PEER_EOF)) {
+ if (dev->write_dev != 0) {
+ if ((STAILQ_EMPTY(&dev->work_queue))
+ && (dev->num_run_queue == 0)
+ && (dev->cur_active_io == 0)) {
+ goto bailout;
+ }
+ } else {
+ /*
+ * If we're the reader, and the writer
+ * got EOF, he is already done. If we got
+ * the EOF, then we need to wait until
+ * everything is flushed out for the writer.
+ */
+ if (dev->flags & CAMDD_DEV_FLAG_PEER_EOF) {
+ goto bailout;
+ } else if ((dev->num_peer_work_queue == 0)
+ && (dev->num_peer_done_queue == 0)
+ && (dev->cur_active_io == 0)
+ && (dev->num_run_queue == 0)) {
+ goto bailout;
+ }
+ }
+ /*
+ * XXX KDM need to do something about the pending
+ * queue and cleanup resources.
+ */
+ }
+
+ if ((dev->write_dev == 0)
+ && (dev->cur_active_io == 0)
+ && (dev->peer_bytes_queued < dev->peer_dev->blocksize))
+ kq_ts = &ts;
+ else
+ kq_ts = NULL;
+
+ /*
+ * Run kevent to see if there are events to process.
+ */
+ pthread_mutex_unlock(&dev->mutex);
+ retval = kevent(dev->kq, NULL, 0, &ke, 1, kq_ts);
+ pthread_mutex_lock(&dev->mutex);
+ if (retval == -1) {
+ warn("%s: error returned from kevent",__func__);
+ goto bailout;
+ } else if (retval != 0) {
+ switch (ke.filter) {
+ case EVFILT_READ:
+ if (dev->fetch != NULL) {
+ retval = dev->fetch(dev);
+ if (retval == -1) {
+ error_exit = 1;
+ goto bailout;
+ }
+ }
+ break;
+ case EVFILT_SIGNAL:
+ /*
+ * We register for this so we don't get
+ * an error as a result of a SIGINFO or a
+ * SIGINT. It will actually get handled
+ * by the signal handler. If we get a
+ * SIGINT, bail out without printing an
+ * error message. Any other signals
+ * will result in the error message above.
+ */
+ if (ke.ident == SIGINT)
+ goto bailout;
+ break;
+ case EVFILT_USER:
+ retval = 0;
+ /*
+ * Check to see if the other thread has
+ * queued any I/O for us to do. (In this
+ * case we're the writer.)
+ */
+ for (buf = STAILQ_FIRST(&dev->work_queue);
+ buf != NULL;
+ buf = STAILQ_FIRST(&dev->work_queue)) {
+ STAILQ_REMOVE_HEAD(&dev->work_queue,
+ work_links);
+ retval = camdd_queue(dev, buf);
+ /*
+ * We keep going unless we get an
+ * actual error. If we get EOF, we
+ * still want to remove the buffers
+ * from the queue and send the back
+ * to the reader thread.
+ */
+ if (retval == -1) {
+ error_exit = 1;
+ goto bailout;
+ } else
+ retval = 0;
+ }
+
+ /*
+ * Next check to see if the other thread has
+ * queued any completed buffers back to us.
+ * (In this case we're the reader.)
+ */
+ for (buf = STAILQ_FIRST(&dev->peer_done_queue);
+ buf != NULL;
+ buf = STAILQ_FIRST(&dev->peer_done_queue)){
+ STAILQ_REMOVE_HEAD(
+ &dev->peer_done_queue, work_links);
+ dev->num_peer_done_queue--;
+ camdd_peer_done(buf);
+ }
+ break;
+ default:
+ warnx("%s: unknown kevent filter %d",
+ __func__, ke.filter);
+ break;
+ }
+ }
+ }
+
+bailout:
+
+ dev->flags &= ~CAMDD_DEV_FLAG_ACTIVE;
+
+ /* XXX KDM cleanup resources here? */
+
+ pthread_mutex_unlock(&dev->mutex);
+
+ need_exit = 1;
+ sem_post(&camdd_sem);
+
+ return (NULL);
+}
+
+/*
+ * Simplistic translation of CCB status to our local status.
+ */
+camdd_buf_status
+camdd_ccb_status(union ccb *ccb)
+{
+ camdd_buf_status status = CAMDD_STATUS_NONE;
+ cam_status ccb_status;
+
+ ccb_status = ccb->ccb_h.status & CAM_STATUS_MASK;
+
+ switch (ccb_status) {
+ case CAM_REQ_CMP: {
+ if (ccb->csio.resid == 0) {
+ status = CAMDD_STATUS_OK;
+ } else if (ccb->csio.dxfer_len > ccb->csio.resid) {
+ status = CAMDD_STATUS_SHORT_IO;
+ } else {
+ status = CAMDD_STATUS_EOF;
+ }
+ break;
+ }
+ case CAM_SCSI_STATUS_ERROR: {
+ switch (ccb->csio.scsi_status) {
+ case SCSI_STATUS_OK:
+ case SCSI_STATUS_COND_MET:
+ case SCSI_STATUS_INTERMED:
+ case SCSI_STATUS_INTERMED_COND_MET:
+ status = CAMDD_STATUS_OK;
+ break;
+ case SCSI_STATUS_CMD_TERMINATED:
+ case SCSI_STATUS_CHECK_COND:
+ case SCSI_STATUS_QUEUE_FULL:
+ case SCSI_STATUS_BUSY:
+ case SCSI_STATUS_RESERV_CONFLICT:
+ default:
+ status = CAMDD_STATUS_ERROR;
+ break;
+ }
+ break;
+ }
+ default:
+ status = CAMDD_STATUS_ERROR;
+ break;
+ }
+
+ return (status);
+}
+
+/*
+ * Queue a buffer to our peer's work thread for writing.
+ *
+ * Returns 0 for success, -1 for failure, 1 if the other thread exited.
+ */
+int
+camdd_queue_peer_buf(struct camdd_dev *dev, struct camdd_buf *buf)
+{
+ struct kevent ke;
+ STAILQ_HEAD(, camdd_buf) local_queue;
+ struct camdd_buf *buf1, *buf2;
+ struct camdd_buf_data *data = NULL;
+ uint64_t peer_bytes_queued = 0;
+ int active = 1;
+ int retval = 0;
+
+ STAILQ_INIT(&local_queue);
+
+ /*
+ * Since we're the reader, we need to queue our I/O to the writer
+ * in sequential order in order to make sure it gets written out
+ * in sequential order.
+ *
+ * Check the next expected I/O starting offset. If this doesn't
+ * match, put it on the reorder queue.
+ */
+ if ((buf->lba * dev->sector_size) != dev->next_completion_pos_bytes) {
+
+ /*
+ * If there is nothing on the queue, there is no sorting
+ * needed.
+ */
+ if (STAILQ_EMPTY(&dev->reorder_queue)) {
+ STAILQ_INSERT_TAIL(&dev->reorder_queue, buf, links);
+ dev->num_reorder_queue++;
+ goto bailout;
+ }
+
+ /*
+ * Sort in ascending order by starting LBA. There should
+ * be no identical LBAs.
+ */
+ for (buf1 = STAILQ_FIRST(&dev->reorder_queue); buf1 != NULL;
+ buf1 = buf2) {
+ buf2 = STAILQ_NEXT(buf1, links);
+ if (buf->lba < buf1->lba) {
+ /*
+ * If we're less than the first one, then
+ * we insert at the head of the list
+ * because this has to be the first element
+ * on the list.
+ */
+ STAILQ_INSERT_HEAD(&dev->reorder_queue,
+ buf, links);
+ dev->num_reorder_queue++;
+ break;
+ } else if (buf->lba > buf1->lba) {
+ if (buf2 == NULL) {
+ STAILQ_INSERT_TAIL(&dev->reorder_queue,
+ buf, links);
+ dev->num_reorder_queue++;
+ break;
+ } else if (buf->lba < buf2->lba) {
+ STAILQ_INSERT_AFTER(&dev->reorder_queue,
+ buf1, buf, links);
+ dev->num_reorder_queue++;
+ break;
+ }
+ } else {
+ errx(1, "Found buffers with duplicate LBA %ju!",
+ buf->lba);
+ }
+ }
+ goto bailout;
+ } else {
+
+ /*
+ * We're the next expected I/O completion, so put ourselves
+ * on the local queue to be sent to the writer. We use
+ * work_links here so that we can queue this to the
+ * peer_work_queue before taking the buffer off of the
+ * local_queue.
+ */
+ dev->next_completion_pos_bytes += buf->len;
+ STAILQ_INSERT_TAIL(&local_queue, buf, work_links);
+
+ /*
+ * Go through the reorder queue looking for more sequential
+ * I/O and add it to the local queue.
+ */
+ for (buf1 = STAILQ_FIRST(&dev->reorder_queue); buf1 != NULL;
+ buf1 = STAILQ_FIRST(&dev->reorder_queue)) {
+ /*
+ * As soon as we see an I/O that is out of sequence,
+ * we're done.
+ */
+ if ((buf1->lba * dev->sector_size) !=
+ dev->next_completion_pos_bytes)
+ break;
+
+ STAILQ_REMOVE_HEAD(&dev->reorder_queue, links);
+ dev->num_reorder_queue--;
+ STAILQ_INSERT_TAIL(&local_queue, buf1, work_links);
+ dev->next_completion_pos_bytes += buf1->len;
+ }
+ }
+
+ /*
+ * Setup the event to let the other thread know that it has work
+ * pending.
+ */
+ EV_SET(&ke, (uintptr_t)&dev->peer_dev->work_queue, EVFILT_USER, 0,
+ NOTE_TRIGGER, 0, NULL);
+
+ /*
+ * Put this on our shadow queue so that we know what we've queued
+ * to the other thread.
+ */
+ STAILQ_FOREACH_SAFE(buf1, &local_queue, work_links, buf2) {
+ if (buf1->buf_type != CAMDD_BUF_DATA) {
+ errx(1, "%s: should have a data buffer, not an "
+ "indirect buffer", __func__);
+ }
+ data = &buf1->buf_type_spec.data;
+
+ /*
+ * We only need to send one EOF to the writer, and don't
+ * need to continue sending EOFs after that.
+ */
+ if (buf1->status == CAMDD_STATUS_EOF) {
+ if (dev->flags & CAMDD_DEV_FLAG_EOF_SENT) {
+ STAILQ_REMOVE(&local_queue, buf1, camdd_buf,
+ work_links);
+ camdd_release_buf(buf1);
+ retval = 1;
+ continue;
+ }
+ dev->flags |= CAMDD_DEV_FLAG_EOF_SENT;
+ }
+
+
+ STAILQ_INSERT_TAIL(&dev->peer_work_queue, buf1, links);
+ peer_bytes_queued += (data->fill_len - data->resid);
+ dev->peer_bytes_queued += (data->fill_len - data->resid);
+ dev->num_peer_work_queue++;
+ }
+
+ if (STAILQ_FIRST(&local_queue) == NULL)
+ goto bailout;
+
+ /*
+ * Drop our mutex and pick up the other thread's mutex. We need to
+ * do this to avoid deadlocks.
+ */
+ pthread_mutex_unlock(&dev->mutex);
+ pthread_mutex_lock(&dev->peer_dev->mutex);
+
+ if (dev->peer_dev->flags & CAMDD_DEV_FLAG_ACTIVE) {
+ /*
+ * Put the buffers on the other thread's incoming work queue.
+ */
+ for (buf1 = STAILQ_FIRST(&local_queue); buf1 != NULL;
+ buf1 = STAILQ_FIRST(&local_queue)) {
+ STAILQ_REMOVE_HEAD(&local_queue, work_links);
+ STAILQ_INSERT_TAIL(&dev->peer_dev->work_queue, buf1,
+ work_links);
+ }
+ /*
+ * Send an event to the other thread's kqueue to let it know
+ * that there is something on the work queue.
+ */
+ retval = kevent(dev->peer_dev->kq, &ke, 1, NULL, 0, NULL);
+ if (retval == -1)
+ warn("%s: unable to add peer work_queue kevent",
+ __func__);
+ else
+ retval = 0;
+ } else
+ active = 0;
+
+ pthread_mutex_unlock(&dev->peer_dev->mutex);
+ pthread_mutex_lock(&dev->mutex);
+
+ /*
+ * If the other side isn't active, run through the queue and
+ * release all of the buffers.
+ */
+ if (active == 0) {
+ for (buf1 = STAILQ_FIRST(&local_queue); buf1 != NULL;
+ buf1 = STAILQ_FIRST(&local_queue)) {
+ STAILQ_REMOVE_HEAD(&local_queue, work_links);
+ STAILQ_REMOVE(&dev->peer_work_queue, buf1, camdd_buf,
+ links);
+ dev->num_peer_work_queue--;
+ camdd_release_buf(buf1);
+ }
+ dev->peer_bytes_queued -= peer_bytes_queued;
+ retval = 1;
+ }
+
+bailout:
+ return (retval);
+}
+
+/*
+ * Return a buffer to the reader thread when we have completed writing it.
+ */
+int
+camdd_complete_peer_buf(struct camdd_dev *dev, struct camdd_buf *peer_buf)
+{
+ struct kevent ke;
+ int retval = 0;
+
+ /*
+ * Setup the event to let the other thread know that we have
+ * completed a buffer.
+ */
+ EV_SET(&ke, (uintptr_t)&dev->peer_dev->peer_done_queue, EVFILT_USER, 0,
+ NOTE_TRIGGER, 0, NULL);
+
+ /*
+ * Drop our lock and acquire the other thread's lock before
+ * manipulating
+ */
+ pthread_mutex_unlock(&dev->mutex);
+ pthread_mutex_lock(&dev->peer_dev->mutex);
+
+ /*
+ * Put the buffer on the reader thread's peer done queue now that
+ * we have completed it.
+ */
+ STAILQ_INSERT_TAIL(&dev->peer_dev->peer_done_queue, peer_buf,
+ work_links);
+ dev->peer_dev->num_peer_done_queue++;
+
+ /*
+ * Send an event to the peer thread to let it know that we've added
+ * something to its peer done queue.
+ */
+ retval = kevent(dev->peer_dev->kq, &ke, 1, NULL, 0, NULL);
+ if (retval == -1)
+ warn("%s: unable to add peer_done_queue kevent", __func__);
+ else
+ retval = 0;
+
+ /*
+ * Drop the other thread's lock and reacquire ours.
+ */
+ pthread_mutex_unlock(&dev->peer_dev->mutex);
+ pthread_mutex_lock(&dev->mutex);
+
+ return (retval);
+}
+
+/*
+ * Free a buffer that was written out by the writer thread and returned to
+ * the reader thread.
+ */
+void
+camdd_peer_done(struct camdd_buf *buf)
+{
+ struct camdd_dev *dev;
+ struct camdd_buf_data *data;
+
+ dev = buf->dev;
+ if (buf->buf_type != CAMDD_BUF_DATA) {
+ errx(1, "%s: should have a data buffer, not an "
+ "indirect buffer", __func__);
+ }
+
+ data = &buf->buf_type_spec.data;
+
+ STAILQ_REMOVE(&dev->peer_work_queue, buf, camdd_buf, links);
+ dev->num_peer_work_queue--;
+ dev->peer_bytes_queued -= (data->fill_len - data->resid);
+
+ if (buf->status == CAMDD_STATUS_EOF)
+ dev->flags |= CAMDD_DEV_FLAG_PEER_EOF;
+
+ STAILQ_INSERT_TAIL(&dev->free_queue, buf, links);
+}
+
+/*
+ * Assumes caller holds the lock for this device.
+ */
+void
+camdd_complete_buf(struct camdd_dev *dev, struct camdd_buf *buf,
+ int *error_count)
+{
+ int retval = 0;
+
+ /*
+ * If we're the reader, we need to send the completed I/O
+ * to the writer. If we're the writer, we need to just
+ * free up resources, or let the reader know if we've
+ * encountered an error.
+ */
+ if (dev->write_dev == 0) {
+ retval = camdd_queue_peer_buf(dev, buf);
+ if (retval != 0)
+ (*error_count)++;
+ } else {
+ struct camdd_buf *tmp_buf, *next_buf;
+
+ STAILQ_FOREACH_SAFE(tmp_buf, &buf->src_list, src_links,
+ next_buf) {
+ struct camdd_buf *src_buf;
+ struct camdd_buf_indirect *indirect;
+
+ STAILQ_REMOVE(&buf->src_list, tmp_buf,
+ camdd_buf, src_links);
+
+ tmp_buf->status = buf->status;
+
+ if (tmp_buf->buf_type == CAMDD_BUF_DATA) {
+ camdd_complete_peer_buf(dev, tmp_buf);
+ continue;
+ }
+
+ indirect = &tmp_buf->buf_type_spec.indirect;
+ src_buf = indirect->src_buf;
+ src_buf->refcount--;
+ /*
+ * XXX KDM we probably need to account for
+ * exactly how many bytes we were able to
+ * write. Allocate the residual to the
+ * first N buffers? Or just track the
+ * number of bytes written? Right now the reader
+ * doesn't do anything with a residual.
+ */
+ src_buf->status = buf->status;
+ if (src_buf->refcount <= 0)
+ camdd_complete_peer_buf(dev, src_buf);
+ STAILQ_INSERT_TAIL(&dev->free_indirect_queue,
+ tmp_buf, links);
+ }
+
+ STAILQ_INSERT_TAIL(&dev->free_queue, buf, links);
+ }
+}
+
+/*
+ * Fetch all completed commands from the pass(4) device.
+ *
+ * Returns the number of commands received, or -1 if any of the commands
+ * completed with an error. Returns 0 if no commands are available.
+ */
+int
+camdd_pass_fetch(struct camdd_dev *dev)
+{
+ struct camdd_dev_pass *pass_dev = &dev->dev_spec.pass;
+ union ccb ccb;
+ int retval = 0, num_fetched = 0, error_count = 0;
+
+ pthread_mutex_unlock(&dev->mutex);
+ /*
+ * XXX KDM we don't distinguish between EFAULT and ENOENT.
+ */
+ while ((retval = ioctl(pass_dev->dev->fd, CAMIOGET, &ccb)) != -1) {
+ struct camdd_buf *buf;
+ struct camdd_buf_data *data;
+ cam_status ccb_status;
+ union ccb *buf_ccb;
+
+ buf = ccb.ccb_h.ccb_buf;
+ data = &buf->buf_type_spec.data;
+ buf_ccb = &data->ccb;
+
+ num_fetched++;
+
+ /*
+ * Copy the CCB back out so we get status, sense data, etc.
+ */
+ bcopy(&ccb, buf_ccb, sizeof(ccb));
+
+ pthread_mutex_lock(&dev->mutex);
+
+ /*
+ * We're now done, so take this off the active queue.
+ */
+ STAILQ_REMOVE(&dev->active_queue, buf, camdd_buf, links);
+ dev->cur_active_io--;
+
+ ccb_status = ccb.ccb_h.status & CAM_STATUS_MASK;
+ if (ccb_status != CAM_REQ_CMP) {
+ cam_error_print(pass_dev->dev, &ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ }
+
+ data->resid = ccb.csio.resid;
+ dev->bytes_transferred += (ccb.csio.dxfer_len - ccb.csio.resid);
+
+ if (buf->status == CAMDD_STATUS_NONE)
+ buf->status = camdd_ccb_status(&ccb);
+ if (buf->status == CAMDD_STATUS_ERROR)
+ error_count++;
+ else if (buf->status == CAMDD_STATUS_EOF) {
+ /*
+ * Once we queue this buffer to our partner thread,
+ * he will know that we've hit EOF.
+ */
+ dev->flags |= CAMDD_DEV_FLAG_EOF;
+ }
+
+ camdd_complete_buf(dev, buf, &error_count);
+
+ /*
+ * Unlock in preparation for the ioctl call.
+ */
+ pthread_mutex_unlock(&dev->mutex);
+ }
+
+ pthread_mutex_lock(&dev->mutex);
+
+ if (error_count > 0)
+ return (-1);
+ else
+ return (num_fetched);
+}
+
+/*
+ * Returns -1 for error, 0 for success/continue, and 1 for resource
+ * shortage/stop processing.
+ */
+int
+camdd_file_run(struct camdd_dev *dev)
+{
+ struct camdd_dev_file *file_dev = &dev->dev_spec.file;
+ struct camdd_buf_data *data;
+ struct camdd_buf *buf;
+ off_t io_offset;
+ int retval = 0, write_dev = dev->write_dev;
+ int error_count = 0, no_resources = 0, double_buf_needed = 0;
+ uint32_t num_sectors = 0, db_len = 0;
+
+ buf = STAILQ_FIRST(&dev->run_queue);
+ if (buf == NULL) {
+ no_resources = 1;
+ goto bailout;
+ } else if ((dev->write_dev == 0)
+ && (dev->flags & (CAMDD_DEV_FLAG_EOF |
+ CAMDD_DEV_FLAG_EOF_SENT))) {
+ STAILQ_REMOVE(&dev->run_queue, buf, camdd_buf, links);
+ dev->num_run_queue--;
+ buf->status = CAMDD_STATUS_EOF;
+ error_count++;
+ goto bailout;
+ }
+
+ /*
+ * If we're writing, we need to go through the source buffer list
+ * and create an S/G list.
+ */
+ if (write_dev != 0) {
+ retval = camdd_buf_sg_create(buf, /*iovec*/ 1,
+ dev->sector_size, &num_sectors, &double_buf_needed);
+ if (retval != 0) {
+ no_resources = 1;
+ goto bailout;
+ }
+ }
+
+ STAILQ_REMOVE(&dev->run_queue, buf, camdd_buf, links);
+ dev->num_run_queue--;
+
+ data = &buf->buf_type_spec.data;
+
+ /*
+ * pread(2) and pwrite(2) offsets are byte offsets.
+ */
+ io_offset = buf->lba * dev->sector_size;
+
+ /*
+ * Unlock the mutex while we read or write.
+ */
+ pthread_mutex_unlock(&dev->mutex);
+
+ /*
+ * Note that we don't need to double buffer if we're the reader
+ * because in that case, we have allocated a single buffer of
+ * sufficient size to do the read. This copy is necessary on
+ * writes because if one of the components of the S/G list is not
+ * a sector size multiple, the kernel will reject the write. This
+ * is unfortunate but not surprising. So this will make sure that
+ * we're using a single buffer that is a multiple of the sector size.
+ */
+ if ((double_buf_needed != 0)
+ && (data->sg_count > 1)
+ && (write_dev != 0)) {
+ uint32_t cur_offset;
+ int i;
+
+ if (file_dev->tmp_buf == NULL)
+ file_dev->tmp_buf = calloc(dev->blocksize, 1);
+ if (file_dev->tmp_buf == NULL) {
+ buf->status = CAMDD_STATUS_ERROR;
+ error_count++;
+ goto bailout;
+ }
+ for (i = 0, cur_offset = 0; i < data->sg_count; i++) {
+ bcopy(data->iovec[i].iov_base,
+ &file_dev->tmp_buf[cur_offset],
+ data->iovec[i].iov_len);
+ cur_offset += data->iovec[i].iov_len;
+ }
+ db_len = cur_offset;
+ }
+
+ if (file_dev->file_flags & CAMDD_FF_CAN_SEEK) {
+ if (write_dev == 0) {
+ /*
+ * XXX KDM is there any way we would need a S/G
+ * list here?
+ */
+ retval = pread(file_dev->fd, data->buf,
+ buf->len, io_offset);
+ } else {
+ if (double_buf_needed != 0) {
+ retval = pwrite(file_dev->fd, file_dev->tmp_buf,
+ db_len, io_offset);
+ } else if (data->sg_count == 0) {
+ retval = pwrite(file_dev->fd, data->buf,
+ data->fill_len, io_offset);
+ } else {
+ retval = pwritev(file_dev->fd, data->iovec,
+ data->sg_count, io_offset);
+ }
+ }
+ } else {
+ if (write_dev == 0) {
+ /*
+ * XXX KDM is there any way we would need a S/G
+ * list here?
+ */
+ retval = read(file_dev->fd, data->buf, buf->len);
+ } else {
+ if (double_buf_needed != 0) {
+ retval = write(file_dev->fd, file_dev->tmp_buf,
+ db_len);
+ } else if (data->sg_count == 0) {
+ retval = write(file_dev->fd, data->buf,
+ data->fill_len);
+ } else {
+ retval = writev(file_dev->fd, data->iovec,
+ data->sg_count);
+ }
+ }
+ }
+
+ /* We're done, re-acquire the lock */
+ pthread_mutex_lock(&dev->mutex);
+
+ if (retval >= (ssize_t)data->fill_len) {
+ /*
+ * If the bytes transferred is more than the request size,
+ * that indicates an overrun, which should only happen at
+ * the end of a transfer if we have to round up to a sector
+ * boundary.
+ */
+ if (buf->status == CAMDD_STATUS_NONE)
+ buf->status = CAMDD_STATUS_OK;
+ data->resid = 0;
+ dev->bytes_transferred += retval;
+ } else if (retval == -1) {
+ warn("Error %s %s", (write_dev) ? "writing to" :
+ "reading from", file_dev->filename);
+
+ buf->status = CAMDD_STATUS_ERROR;
+ data->resid = data->fill_len;
+ error_count++;
+
+ if (dev->debug == 0)
+ goto bailout;
+
+ if ((double_buf_needed != 0)
+ && (write_dev != 0)) {
+ fprintf(stderr, "%s: fd %d, DB buf %p, len %u lba %ju "
+ "offset %ju\n", __func__, file_dev->fd,
+ file_dev->tmp_buf, db_len, (uintmax_t)buf->lba,
+ (uintmax_t)io_offset);
+ } else if (data->sg_count == 0) {
+ fprintf(stderr, "%s: fd %d, buf %p, len %u, lba %ju "
+ "offset %ju\n", __func__, file_dev->fd, data->buf,
+ data->fill_len, (uintmax_t)buf->lba,
+ (uintmax_t)io_offset);
+ } else {
+ int i;
+
+ fprintf(stderr, "%s: fd %d, len %u, lba %ju "
+ "offset %ju\n", __func__, file_dev->fd,
+ data->fill_len, (uintmax_t)buf->lba,
+ (uintmax_t)io_offset);
+
+ for (i = 0; i < data->sg_count; i++) {
+ fprintf(stderr, "index %d ptr %p len %zu\n",
+ i, data->iovec[i].iov_base,
+ data->iovec[i].iov_len);
+ }
+ }
+ } else if (retval == 0) {
+ buf->status = CAMDD_STATUS_EOF;
+ if (dev->debug != 0)
+ printf("%s: got EOF from %s!\n", __func__,
+ file_dev->filename);
+ data->resid = data->fill_len;
+ error_count++;
+ } else if (retval < (ssize_t)data->fill_len) {
+ if (buf->status == CAMDD_STATUS_NONE)
+ buf->status = CAMDD_STATUS_SHORT_IO;
+ data->resid = data->fill_len - retval;
+ dev->bytes_transferred += retval;
+ }
+
+bailout:
+ if (buf != NULL) {
+ if (buf->status == CAMDD_STATUS_EOF) {
+ struct camdd_buf *buf2;
+ dev->flags |= CAMDD_DEV_FLAG_EOF;
+ STAILQ_FOREACH(buf2, &dev->run_queue, links)
+ buf2->status = CAMDD_STATUS_EOF;
+ }
+
+ camdd_complete_buf(dev, buf, &error_count);
+ }
+
+ if (error_count != 0)
+ return (-1);
+ else if (no_resources != 0)
+ return (1);
+ else
+ return (0);
+}
+
+/*
+ * Execute one command from the run queue. Returns 0 for success, 1 for
+ * stop processing, and -1 for error.
+ */
+int
+camdd_pass_run(struct camdd_dev *dev)
+{
+ struct camdd_buf *buf = NULL;
+ struct camdd_dev_pass *pass_dev = &dev->dev_spec.pass;
+ struct camdd_buf_data *data;
+ uint32_t num_blocks, sectors_used = 0;
+ union ccb *ccb;
+ int retval = 0, is_write = dev->write_dev;
+ int double_buf_needed = 0;
+
+ buf = STAILQ_FIRST(&dev->run_queue);
+ if (buf == NULL) {
+ retval = 1;
+ goto bailout;
+ }
+
+ /*
+ * If we're writing, we need to go through the source buffer list
+ * and create an S/G list.
+ */
+ if (is_write != 0) {
+ retval = camdd_buf_sg_create(buf, /*iovec*/ 0,dev->sector_size,
+ &sectors_used, &double_buf_needed);
+ if (retval != 0) {
+ retval = -1;
+ goto bailout;
+ }
+ }
+
+ STAILQ_REMOVE(&dev->run_queue, buf, camdd_buf, links);
+ dev->num_run_queue--;
+
+ data = &buf->buf_type_spec.data;
+
+ ccb = &data->ccb;
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr));
+
+ /*
+ * In almost every case the number of blocks should be the device
+ * block size. The exception may be at the end of an I/O stream
+ * for a partial block or at the end of a device.
+ */
+ if (is_write != 0)
+ num_blocks = sectors_used;
+ else
+ num_blocks = data->fill_len / pass_dev->block_len;
+
+ scsi_read_write(&ccb->csio,
+ /*retries*/ dev->retry_count,
+ /*cbfcnp*/ NULL,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*readop*/ (dev->write_dev == 0) ? SCSI_RW_READ :
+ SCSI_RW_WRITE,
+ /*byte2*/ 0,
+ /*minimum_cmd_size*/ dev->min_cmd_size,
+ /*lba*/ buf->lba,
+ /*block_count*/ num_blocks,
+ /*data_ptr*/ (data->sg_count != 0) ?
+ (uint8_t *)data->segs : data->buf,
+ /*dxfer_len*/ (num_blocks * pass_dev->block_len),
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ dev->io_timeout);
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (dev->retry_count != 0)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+ if (data->sg_count != 0) {
+ ccb->csio.sglist_cnt = data->sg_count;
+ ccb->ccb_h.flags |= CAM_DATA_SG;
+ }
+
+ /*
+ * Store a pointer to the buffer in the CCB. The kernel will
+ * restore this when we get it back, and we'll use it to identify
+ * the buffer this CCB came from.
+ */
+ ccb->ccb_h.ccb_buf = buf;
+
+ /*
+ * Unlock our mutex in preparation for issuing the ioctl.
+ */
+ pthread_mutex_unlock(&dev->mutex);
+ /*
+ * Queue the CCB to the pass(4) driver.
+ */
+ if (ioctl(pass_dev->dev->fd, CAMIOQUEUE, ccb) == -1) {
+ pthread_mutex_lock(&dev->mutex);
+
+ warn("%s: error sending CAMIOQUEUE ioctl to %s%u", __func__,
+ pass_dev->dev->device_name, pass_dev->dev->dev_unit_num);
+ warn("%s: CCB address is %p", __func__, ccb);
+ retval = -1;
+
+ STAILQ_INSERT_TAIL(&dev->free_queue, buf, links);
+ } else {
+ pthread_mutex_lock(&dev->mutex);
+
+ dev->cur_active_io++;
+ STAILQ_INSERT_TAIL(&dev->active_queue, buf, links);
+ }
+
+bailout:
+ return (retval);
+}
+
+int
+camdd_get_next_lba_len(struct camdd_dev *dev, uint64_t *lba, ssize_t *len)
+{
+ struct camdd_dev_pass *pass_dev;
+ uint32_t num_blocks;
+ int retval = 0;
+
+ pass_dev = &dev->dev_spec.pass;
+
+ *lba = dev->next_io_pos_bytes / dev->sector_size;
+ *len = dev->blocksize;
+ num_blocks = *len / dev->sector_size;
+
+ /*
+ * If max_sector is 0, then we have no set limit. This can happen
+ * if we're writing to a file in a filesystem, or reading from
+ * something like /dev/zero.
+ */
+ if ((dev->max_sector != 0)
+ || (dev->sector_io_limit != 0)) {
+ uint64_t max_sector;
+
+ if ((dev->max_sector != 0)
+ && (dev->sector_io_limit != 0))
+ max_sector = min(dev->sector_io_limit, dev->max_sector);
+ else if (dev->max_sector != 0)
+ max_sector = dev->max_sector;
+ else
+ max_sector = dev->sector_io_limit;
+
+
+ /*
+ * Check to see whether we're starting off past the end of
+ * the device. If so, we need to just send an EOF
+ * notification to the writer.
+ */
+ if (*lba > max_sector) {
+ *len = 0;
+ retval = 1;
+ } else if (((*lba + num_blocks) > max_sector + 1)
+ || ((*lba + num_blocks) < *lba)) {
+ /*
+ * If we get here (but pass the first check), we
+ * can trim the request length down to go to the
+ * end of the device.
+ */
+ num_blocks = (max_sector + 1) - *lba;
+ *len = num_blocks * dev->sector_size;
+ retval = 1;
+ }
+ }
+
+ dev->next_io_pos_bytes += *len;
+
+ return (retval);
+}
+
+/*
+ * Returns 0 for success, 1 for EOF detected, and -1 for failure.
+ */
+int
+camdd_queue(struct camdd_dev *dev, struct camdd_buf *read_buf)
+{
+ struct camdd_buf *buf = NULL;
+ struct camdd_buf_data *data;
+ struct camdd_dev_pass *pass_dev;
+ size_t new_len;
+ struct camdd_buf_data *rb_data;
+ int is_write = dev->write_dev;
+ int eof_flush_needed = 0;
+ int retval = 0;
+ int error;
+
+ pass_dev = &dev->dev_spec.pass;
+
+ /*
+ * If we've gotten EOF or our partner has, we should not continue
+ * queueing I/O. If we're a writer, though, we should continue
+ * to write any buffers that don't have EOF status.
+ */
+ if ((dev->flags & CAMDD_DEV_FLAG_EOF)
+ || ((dev->flags & CAMDD_DEV_FLAG_PEER_EOF)
+ && (is_write == 0))) {
+ /*
+ * Tell the worker thread that we have seen EOF.
+ */
+ retval = 1;
+
+ /*
+ * If we're the writer, send the buffer back with EOF status.
+ */
+ if (is_write) {
+ read_buf->status = CAMDD_STATUS_EOF;
+
+ error = camdd_complete_peer_buf(dev, read_buf);
+ }
+ goto bailout;
+ }
+
+ if (is_write == 0) {
+ buf = camdd_get_buf(dev, CAMDD_BUF_DATA);
+ if (buf == NULL) {
+ retval = -1;
+ goto bailout;
+ }
+ data = &buf->buf_type_spec.data;
+
+ retval = camdd_get_next_lba_len(dev, &buf->lba, &buf->len);
+ if (retval != 0) {
+ buf->status = CAMDD_STATUS_EOF;
+
+ if ((buf->len == 0)
+ && ((dev->flags & (CAMDD_DEV_FLAG_EOF_SENT |
+ CAMDD_DEV_FLAG_EOF_QUEUED)) != 0)) {
+ camdd_release_buf(buf);
+ goto bailout;
+ }
+ dev->flags |= CAMDD_DEV_FLAG_EOF_QUEUED;
+ }
+
+ data->fill_len = buf->len;
+ data->src_start_offset = buf->lba * dev->sector_size;
+
+ /*
+ * Put this on the run queue.
+ */
+ STAILQ_INSERT_TAIL(&dev->run_queue, buf, links);
+ dev->num_run_queue++;
+
+ /* We're done. */
+ goto bailout;
+ }
+
+ /*
+ * Check for new EOF status from the reader.
+ */
+ if ((read_buf->status == CAMDD_STATUS_EOF)
+ || (read_buf->status == CAMDD_STATUS_ERROR)) {
+ dev->flags |= CAMDD_DEV_FLAG_PEER_EOF;
+ if ((STAILQ_FIRST(&dev->pending_queue) == NULL)
+ && (read_buf->len == 0)) {
+ camdd_complete_peer_buf(dev, read_buf);
+ retval = 1;
+ goto bailout;
+ } else
+ eof_flush_needed = 1;
+ }
+
+ /*
+ * See if we have a buffer we're composing with pieces from our
+ * partner thread.
+ */
+ buf = STAILQ_FIRST(&dev->pending_queue);
+ if (buf == NULL) {
+ uint64_t lba;
+ ssize_t len;
+
+ retval = camdd_get_next_lba_len(dev, &lba, &len);
+ if (retval != 0) {
+ read_buf->status = CAMDD_STATUS_EOF;
+
+ if (len == 0) {
+ dev->flags |= CAMDD_DEV_FLAG_EOF;
+ error = camdd_complete_peer_buf(dev, read_buf);
+ goto bailout;
+ }
+ }
+
+ /*
+ * If we don't have a pending buffer, we need to grab a new
+ * one from the free list or allocate another one.
+ */
+ buf = camdd_get_buf(dev, CAMDD_BUF_DATA);
+ if (buf == NULL) {
+ retval = 1;
+ goto bailout;
+ }
+
+ buf->lba = lba;
+ buf->len = len;
+
+ STAILQ_INSERT_TAIL(&dev->pending_queue, buf, links);
+ dev->num_pending_queue++;
+ }
+
+ data = &buf->buf_type_spec.data;
+
+ rb_data = &read_buf->buf_type_spec.data;
+
+ if ((rb_data->src_start_offset != dev->next_peer_pos_bytes)
+ && (dev->debug != 0)) {
+ printf("%s: WARNING: reader offset %#jx != expected offset "
+ "%#jx\n", __func__, (uintmax_t)rb_data->src_start_offset,
+ (uintmax_t)dev->next_peer_pos_bytes);
+ }
+ dev->next_peer_pos_bytes = rb_data->src_start_offset +
+ (rb_data->fill_len - rb_data->resid);
+
+ new_len = (rb_data->fill_len - rb_data->resid) + data->fill_len;
+ if (new_len < buf->len) {
+ /*
+ * There are three cases here:
+ * 1. We need more data to fill up a block, so we put
+ * this I/O on the queue and wait for more I/O.
+ * 2. We have a pending buffer in the queue that is
+ * smaller than our blocksize, but we got an EOF. So we
+ * need to go ahead and flush the write out.
+ * 3. We got an error.
+ */
+
+ /*
+ * Increment our fill length.
+ */
+ data->fill_len += (rb_data->fill_len - rb_data->resid);
+
+ /*
+ * Add the new read buffer to the list for writing.
+ */
+ STAILQ_INSERT_TAIL(&buf->src_list, read_buf, src_links);
+
+ /* Increment the count */
+ buf->src_count++;
+
+ if (eof_flush_needed == 0) {
+ /*
+ * We need to exit, because we don't have enough
+ * data yet.
+ */
+ goto bailout;
+ } else {
+ /*
+ * Take the buffer off of the pending queue.
+ */
+ STAILQ_REMOVE(&dev->pending_queue, buf, camdd_buf,
+ links);
+ dev->num_pending_queue--;
+
+ /*
+ * If we need an EOF flush, but there is no data
+ * to flush, go ahead and return this buffer.
+ */
+ if (data->fill_len == 0) {
+ camdd_complete_buf(dev, buf, /*error_count*/0);
+ retval = 1;
+ goto bailout;
+ }
+
+ /*
+ * Put this on the next queue for execution.
+ */
+ STAILQ_INSERT_TAIL(&dev->run_queue, buf, links);
+ dev->num_run_queue++;
+ }
+ } else if (new_len == buf->len) {
+ /*
+ * We have enough data to completey fill one block,
+ * so we're ready to issue the I/O.
+ */
+
+ /*
+ * Take the buffer off of the pending queue.
+ */
+ STAILQ_REMOVE(&dev->pending_queue, buf, camdd_buf, links);
+ dev->num_pending_queue--;
+
+ /*
+ * Add the new read buffer to the list for writing.
+ */
+ STAILQ_INSERT_TAIL(&buf->src_list, read_buf, src_links);
+
+ /* Increment the count */
+ buf->src_count++;
+
+ /*
+ * Increment our fill length.
+ */
+ data->fill_len += (rb_data->fill_len - rb_data->resid);
+
+ /*
+ * Put this on the next queue for execution.
+ */
+ STAILQ_INSERT_TAIL(&dev->run_queue, buf, links);
+ dev->num_run_queue++;
+ } else {
+ struct camdd_buf *idb;
+ struct camdd_buf_indirect *indirect;
+ uint32_t len_to_go, cur_offset;
+
+
+ idb = camdd_get_buf(dev, CAMDD_BUF_INDIRECT);
+ if (idb == NULL) {
+ retval = 1;
+ goto bailout;
+ }
+ indirect = &idb->buf_type_spec.indirect;
+ indirect->src_buf = read_buf;
+ read_buf->refcount++;
+ indirect->offset = 0;
+ indirect->start_ptr = rb_data->buf;
+ /*
+ * We've already established that there is more
+ * data in read_buf than we have room for in our
+ * current write request. So this particular chunk
+ * of the request should just be the remainder
+ * needed to fill up a block.
+ */
+ indirect->len = buf->len - (data->fill_len - data->resid);
+
+ camdd_buf_add_child(buf, idb);
+
+ /*
+ * This buffer is ready to execute, so we can take
+ * it off the pending queue and put it on the run
+ * queue.
+ */
+ STAILQ_REMOVE(&dev->pending_queue, buf, camdd_buf,
+ links);
+ dev->num_pending_queue--;
+ STAILQ_INSERT_TAIL(&dev->run_queue, buf, links);
+ dev->num_run_queue++;
+
+ cur_offset = indirect->offset + indirect->len;
+
+ /*
+ * The resulting I/O would be too large to fit in
+ * one block. We need to split this I/O into
+ * multiple pieces. Allocate as many buffers as needed.
+ */
+ for (len_to_go = rb_data->fill_len - rb_data->resid -
+ indirect->len; len_to_go > 0;) {
+ struct camdd_buf *new_buf;
+ struct camdd_buf_data *new_data;
+ uint64_t lba;
+ ssize_t len;
+
+ retval = camdd_get_next_lba_len(dev, &lba, &len);
+ if ((retval != 0)
+ && (len == 0)) {
+ /*
+ * The device has already been marked
+ * as EOF, and there is no space left.
+ */
+ goto bailout;
+ }
+
+ new_buf = camdd_get_buf(dev, CAMDD_BUF_DATA);
+ if (new_buf == NULL) {
+ retval = 1;
+ goto bailout;
+ }
+
+ new_buf->lba = lba;
+ new_buf->len = len;
+
+ idb = camdd_get_buf(dev, CAMDD_BUF_INDIRECT);
+ if (idb == NULL) {
+ retval = 1;
+ goto bailout;
+ }
+
+ indirect = &idb->buf_type_spec.indirect;
+
+ indirect->src_buf = read_buf;
+ read_buf->refcount++;
+ indirect->offset = cur_offset;
+ indirect->start_ptr = rb_data->buf + cur_offset;
+ indirect->len = min(len_to_go, new_buf->len);
+#if 0
+ if (((indirect->len % dev->sector_size) != 0)
+ || ((indirect->offset % dev->sector_size) != 0)) {
+ warnx("offset %ju len %ju not aligned with "
+ "sector size %u", indirect->offset,
+ (uintmax_t)indirect->len, dev->sector_size);
+ }
+#endif
+ cur_offset += indirect->len;
+ len_to_go -= indirect->len;
+
+ camdd_buf_add_child(new_buf, idb);
+
+ new_data = &new_buf->buf_type_spec.data;
+
+ if ((new_data->fill_len == new_buf->len)
+ || (eof_flush_needed != 0)) {
+ STAILQ_INSERT_TAIL(&dev->run_queue,
+ new_buf, links);
+ dev->num_run_queue++;
+ } else if (new_data->fill_len < buf->len) {
+ STAILQ_INSERT_TAIL(&dev->pending_queue,
+ new_buf, links);
+ dev->num_pending_queue++;
+ } else {
+ warnx("%s: too much data in new "
+ "buffer!", __func__);
+ retval = 1;
+ goto bailout;
+ }
+ }
+ }
+
+bailout:
+ return (retval);
+}
+
+void
+camdd_get_depth(struct camdd_dev *dev, uint32_t *our_depth,
+ uint32_t *peer_depth, uint32_t *our_bytes, uint32_t *peer_bytes)
+{
+ *our_depth = dev->cur_active_io + dev->num_run_queue;
+ if (dev->num_peer_work_queue >
+ dev->num_peer_done_queue)
+ *peer_depth = dev->num_peer_work_queue -
+ dev->num_peer_done_queue;
+ else
+ *peer_depth = 0;
+ *our_bytes = *our_depth * dev->blocksize;
+ *peer_bytes = dev->peer_bytes_queued;
+}
+
+void
+camdd_sig_handler(int sig)
+{
+ if (sig == SIGINFO)
+ need_status = 1;
+ else {
+ need_exit = 1;
+ error_exit = 1;
+ }
+
+ sem_post(&camdd_sem);
+}
+
+void
+camdd_print_status(struct camdd_dev *camdd_dev, struct camdd_dev *other_dev,
+ struct timespec *start_time)
+{
+ struct timespec done_time;
+ uint64_t total_ns;
+ long double mb_sec, total_sec;
+ int error = 0;
+
+ error = clock_gettime(CLOCK_MONOTONIC_PRECISE, &done_time);
+ if (error != 0) {
+ warn("Unable to get done time");
+ return;
+ }
+
+ timespecsub(&done_time, start_time);
+
+ total_ns = done_time.tv_nsec + (done_time.tv_sec * 1000000000);
+ total_sec = total_ns;
+ total_sec /= 1000000000;
+
+ fprintf(stderr, "%ju bytes %s %s\n%ju bytes %s %s\n"
+ "%.4Lf seconds elapsed\n",
+ (uintmax_t)camdd_dev->bytes_transferred,
+ (camdd_dev->write_dev == 0) ? "read from" : "written to",
+ camdd_dev->device_name,
+ (uintmax_t)other_dev->bytes_transferred,
+ (other_dev->write_dev == 0) ? "read from" : "written to",
+ other_dev->device_name, total_sec);
+
+ mb_sec = min(other_dev->bytes_transferred,camdd_dev->bytes_transferred);
+ mb_sec /= 1024 * 1024;
+ mb_sec *= 1000000000;
+ mb_sec /= total_ns;
+ fprintf(stderr, "%.2Lf MB/sec\n", mb_sec);
+}
+
+int
+camdd_rw(struct camdd_io_opts *io_opts, int num_io_opts, uint64_t max_io,
+ int retry_count, int timeout)
+{
+ char *device = NULL;
+ struct cam_device *new_cam_dev = NULL;
+ struct camdd_dev *devs[2];
+ struct timespec start_time;
+ pthread_t threads[2];
+ int unit = 0;
+ int error = 0;
+ int i;
+
+ if (num_io_opts != 2) {
+ warnx("Must have one input and one output path");
+ error = 1;
+ goto bailout;
+ }
+
+ bzero(devs, sizeof(devs));
+
+ for (i = 0; i < num_io_opts; i++) {
+ switch (io_opts[i].dev_type) {
+ case CAMDD_DEV_PASS: {
+ camdd_argmask new_arglist = CAMDD_ARG_NONE;
+ int bus = 0, target = 0, lun = 0;
+ char name[30];
+ int rv;
+
+ if (isdigit(io_opts[i].dev_name[0])) {
+ /* device specified as bus:target[:lun] */
+ rv = parse_btl(io_opts[i].dev_name, &bus,
+ &target, &lun, &new_arglist);
+ if (rv < 2) {
+ warnx("numeric device specification "
+ "must be either bus:target, or "
+ "bus:target:lun");
+ error = 1;
+ goto bailout;
+ }
+ /* default to 0 if lun was not specified */
+ if ((new_arglist & CAMDD_ARG_LUN) == 0) {
+ lun = 0;
+ new_arglist |= CAMDD_ARG_LUN;
+ }
+ } else {
+ if (cam_get_device(io_opts[i].dev_name, name,
+ sizeof name, &unit) == -1) {
+ warnx("%s", cam_errbuf);
+ error = 1;
+ goto bailout;
+ }
+ device = strdup(name);
+ new_arglist |= CAMDD_ARG_DEVICE |CAMDD_ARG_UNIT;
+ }
+
+ if (new_arglist & (CAMDD_ARG_BUS | CAMDD_ARG_TARGET))
+ new_cam_dev = cam_open_btl(bus, target, lun,
+ O_RDWR, NULL);
+ else
+ new_cam_dev = cam_open_spec_device(device, unit,
+ O_RDWR, NULL);
+ if (new_cam_dev == NULL) {
+ warnx("%s", cam_errbuf);
+ error = 1;
+ goto bailout;
+ }
+
+ devs[i] = camdd_probe_pass(new_cam_dev,
+ /*io_opts*/ &io_opts[i],
+ CAMDD_ARG_ERR_RECOVER,
+ /*probe_retry_count*/ 3,
+ /*probe_timeout*/ 5000,
+ /*io_retry_count*/ retry_count,
+ /*io_timeout*/ timeout);
+ if (devs[i] == NULL) {
+ warn("Unable to probe device %s%u",
+ new_cam_dev->device_name,
+ new_cam_dev->dev_unit_num);
+ error = 1;
+ goto bailout;
+ }
+ break;
+ }
+ case CAMDD_DEV_FILE: {
+ int fd = -1;
+
+ if (io_opts[i].dev_name[0] == '-') {
+ if (io_opts[i].write_dev != 0)
+ fd = STDOUT_FILENO;
+ else
+ fd = STDIN_FILENO;
+ } else {
+ if (io_opts[i].write_dev != 0) {
+ fd = open(io_opts[i].dev_name,
+ O_RDWR | O_CREAT, S_IWUSR |S_IRUSR);
+ } else {
+ fd = open(io_opts[i].dev_name,
+ O_RDONLY);
+ }
+ }
+ if (fd == -1) {
+ warn("error opening file %s",
+ io_opts[i].dev_name);
+ error = 1;
+ goto bailout;
+ }
+
+ devs[i] = camdd_probe_file(fd, &io_opts[i],
+ retry_count, timeout);
+ if (devs[i] == NULL) {
+ error = 1;
+ goto bailout;
+ }
+
+ break;
+ }
+ default:
+ warnx("Unknown device type %d (%s)",
+ io_opts[i].dev_type, io_opts[i].dev_name);
+ error = 1;
+ goto bailout;
+ break; /*NOTREACHED */
+ }
+
+ devs[i]->write_dev = io_opts[i].write_dev;
+
+ devs[i]->start_offset_bytes = io_opts[i].offset;
+
+ if (max_io != 0) {
+ devs[i]->sector_io_limit =
+ (devs[i]->start_offset_bytes /
+ devs[i]->sector_size) +
+ (max_io / devs[i]->sector_size) - 1;
+ devs[i]->sector_io_limit =
+ (devs[i]->start_offset_bytes /
+ devs[i]->sector_size) +
+ (max_io / devs[i]->sector_size) - 1;
+ }
+
+ devs[i]->next_io_pos_bytes = devs[i]->start_offset_bytes;
+ devs[i]->next_completion_pos_bytes =devs[i]->start_offset_bytes;
+ }
+
+ devs[0]->peer_dev = devs[1];
+ devs[1]->peer_dev = devs[0];
+ devs[0]->next_peer_pos_bytes = devs[0]->peer_dev->next_io_pos_bytes;
+ devs[1]->next_peer_pos_bytes = devs[1]->peer_dev->next_io_pos_bytes;
+
+ sem_init(&camdd_sem, /*pshared*/ 0, 0);
+
+ signal(SIGINFO, camdd_sig_handler);
+ signal(SIGINT, camdd_sig_handler);
+
+ error = clock_gettime(CLOCK_MONOTONIC_PRECISE, &start_time);
+ if (error != 0) {
+ warn("Unable to get start time");
+ goto bailout;
+ }
+
+ for (i = 0; i < num_io_opts; i++) {
+ error = pthread_create(&threads[i], NULL, camdd_worker,
+ (void *)devs[i]);
+ if (error != 0) {
+ warnc(error, "pthread_create() failed");
+ goto bailout;
+ }
+ }
+
+ for (;;) {
+ if ((sem_wait(&camdd_sem) == -1)
+ || (need_exit != 0)) {
+ struct kevent ke;
+
+ for (i = 0; i < num_io_opts; i++) {
+ EV_SET(&ke, (uintptr_t)&devs[i]->work_queue,
+ EVFILT_USER, 0, NOTE_TRIGGER, 0, NULL);
+
+ devs[i]->flags |= CAMDD_DEV_FLAG_EOF;
+
+ error = kevent(devs[i]->kq, &ke, 1, NULL, 0,
+ NULL);
+ if (error == -1)
+ warn("%s: unable to wake up thread",
+ __func__);
+ error = 0;
+ }
+ break;
+ } else if (need_status != 0) {
+ camdd_print_status(devs[0], devs[1], &start_time);
+ need_status = 0;
+ }
+ }
+ for (i = 0; i < num_io_opts; i++) {
+ pthread_join(threads[i], NULL);
+ }
+
+ camdd_print_status(devs[0], devs[1], &start_time);
+
+bailout:
+
+ for (i = 0; i < num_io_opts; i++)
+ camdd_free_dev(devs[i]);
+
+ return (error + error_exit);
+}
+
+void
+usage(void)
+{
+ fprintf(stderr,
+"usage: camdd <-i|-o pass=pass0,bs=1M,offset=1M,depth=4>\n"
+" <-i|-o file=/tmp/file,bs=512K,offset=1M>\n"
+" <-i|-o file=/dev/da0,bs=512K,offset=1M>\n"
+" <-i|-o file=/dev/nsa0,bs=512K>\n"
+" [-C retry_count][-E][-m max_io_amt][-t timeout_secs][-v][-h]\n"
+"Option description\n"
+"-i <arg=val> Specify input device/file and parameters\n"
+"-o <arg=val> Specify output device/file and parameters\n"
+"Input and Output parameters\n"
+"pass=name Specify a pass(4) device like pass0 or /dev/pass0\n"
+"file=name Specify a file or device, /tmp/foo, /dev/da0, /dev/null\n"
+" or - for stdin/stdout\n"
+"bs=blocksize Specify blocksize in bytes, or using K, M, G, etc. suffix\n"
+"offset=len Specify starting offset in bytes or using K, M, G suffix\n"
+" NOTE: offset cannot be specified on tapes, pipes, stdin/out\n"
+"depth=N Specify a numeric queue depth. This only applies to pass(4)\n"
+"mcs=N Specify a minimum cmd size for pass(4) read/write commands\n"
+"Optional arguments\n"
+"-C retry_cnt Specify a retry count for pass(4) devices\n"
+"-E Enable CAM error recovery for pass(4) devices\n"
+"-m max_io Specify the maximum amount to be transferred in bytes or\n"
+" using K, G, M, etc. suffixes\n"
+"-t timeout Specify the I/O timeout to use with pass(4) devices\n"
+"-v Enable verbose error recovery\n"
+"-h Print this message\n");
+}
+
+
+int
+camdd_parse_io_opts(char *args, int is_write, struct camdd_io_opts *io_opts)
+{
+ char *tmpstr, *tmpstr2;
+ char *orig_tmpstr = NULL;
+ int retval = 0;
+
+ io_opts->write_dev = is_write;
+
+ tmpstr = strdup(args);
+ if (tmpstr == NULL) {
+ warn("strdup failed");
+ retval = 1;
+ goto bailout;
+ }
+ orig_tmpstr = tmpstr;
+ while ((tmpstr2 = strsep(&tmpstr, ",")) != NULL) {
+ char *name, *value;
+
+ /*
+ * If the user creates an empty parameter by putting in two
+ * commas, skip over it and look for the next field.
+ */
+ if (*tmpstr2 == '\0')
+ continue;
+
+ name = strsep(&tmpstr2, "=");
+ if (*name == '\0') {
+ warnx("Got empty I/O parameter name");
+ retval = 1;
+ goto bailout;
+ }
+ value = strsep(&tmpstr2, "=");
+ if ((value == NULL)
+ || (*value == '\0')) {
+ warnx("Empty I/O parameter value for %s", name);
+ retval = 1;
+ goto bailout;
+ }
+ if (strncasecmp(name, "file", 4) == 0) {
+ io_opts->dev_type = CAMDD_DEV_FILE;
+ io_opts->dev_name = strdup(value);
+ if (io_opts->dev_name == NULL) {
+ warn("Error allocating memory");
+ retval = 1;
+ goto bailout;
+ }
+ } else if (strncasecmp(name, "pass", 4) == 0) {
+ io_opts->dev_type = CAMDD_DEV_PASS;
+ io_opts->dev_name = strdup(value);
+ if (io_opts->dev_name == NULL) {
+ warn("Error allocating memory");
+ retval = 1;
+ goto bailout;
+ }
+ } else if ((strncasecmp(name, "bs", 2) == 0)
+ || (strncasecmp(name, "blocksize", 9) == 0)) {
+ retval = expand_number(value, &io_opts->blocksize);
+ if (retval == -1) {
+ warn("expand_number(3) failed on %s=%s", name,
+ value);
+ retval = 1;
+ goto bailout;
+ }
+ } else if (strncasecmp(name, "depth", 5) == 0) {
+ char *endptr;
+
+ io_opts->queue_depth = strtoull(value, &endptr, 0);
+ if (*endptr != '\0') {
+ warnx("invalid queue depth %s", value);
+ retval = 1;
+ goto bailout;
+ }
+ } else if (strncasecmp(name, "mcs", 3) == 0) {
+ char *endptr;
+
+ io_opts->min_cmd_size = strtol(value, &endptr, 0);
+ if ((*endptr != '\0')
+ || ((io_opts->min_cmd_size > 16)
+ || (io_opts->min_cmd_size < 0))) {
+ warnx("invalid minimum cmd size %s", value);
+ retval = 1;
+ goto bailout;
+ }
+ } else if (strncasecmp(name, "offset", 6) == 0) {
+ retval = expand_number(value, &io_opts->offset);
+ if (retval == -1) {
+ warn("expand_number(3) failed on %s=%s", name,
+ value);
+ retval = 1;
+ goto bailout;
+ }
+ } else if (strncasecmp(name, "debug", 5) == 0) {
+ char *endptr;
+
+ io_opts->debug = strtoull(value, &endptr, 0);
+ if (*endptr != '\0') {
+ warnx("invalid debug level %s", value);
+ retval = 1;
+ goto bailout;
+ }
+ } else {
+ warnx("Unrecognized parameter %s=%s", name, value);
+ }
+ }
+bailout:
+ free(orig_tmpstr);
+
+ return (retval);
+}
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ camdd_argmask arglist = CAMDD_ARG_NONE;
+ int timeout = 0, retry_count = 1;
+ int error = 0;
+ uint64_t max_io = 0;
+ struct camdd_io_opts *opt_list = NULL;
+
+ if (argc == 1) {
+ usage();
+ exit(1);
+ }
+
+ opt_list = calloc(2, sizeof(struct camdd_io_opts));
+ if (opt_list == NULL) {
+ warn("Unable to allocate option list");
+ error = 1;
+ goto bailout;
+ }
+
+ while ((c = getopt(argc, argv, "C:Ehi:m:o:t:v")) != -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 |= CAMDD_ARG_RETRIES;
+ break;
+ case 'E':
+ arglist |= CAMDD_ARG_ERR_RECOVER;
+ break;
+ case 'i':
+ case 'o':
+ if (((c == 'i')
+ && (opt_list[0].dev_type != CAMDD_DEV_NONE))
+ || ((c == 'o')
+ && (opt_list[1].dev_type != CAMDD_DEV_NONE))) {
+ errx(1, "Only one input and output path "
+ "allowed");
+ }
+ error = camdd_parse_io_opts(optarg, (c == 'o') ? 1 : 0,
+ (c == 'o') ? &opt_list[1] : &opt_list[0]);
+ if (error != 0)
+ goto bailout;
+ break;
+ case 'm':
+ error = expand_number(optarg, &max_io);
+ if (error == -1) {
+ warn("invalid maximum I/O amount %s", optarg);
+ error = 1;
+ goto bailout;
+ }
+ 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 |= CAMDD_ARG_TIMEOUT;
+ break;
+ case 'v':
+ arglist |= CAMDD_ARG_VERBOSE;
+ break;
+ case 'h':
+ default:
+ usage();
+ exit(1);
+ break; /*NOTREACHED*/
+ }
+ }
+
+ if ((opt_list[0].dev_type == CAMDD_DEV_NONE)
+ || (opt_list[1].dev_type == CAMDD_DEV_NONE))
+ errx(1, "Must specify both -i and -o");
+
+ /*
+ * Set the timeout if the user hasn't specified one.
+ */
+ if (timeout == 0)
+ timeout = CAMDD_PASS_RW_TIMEOUT;
+
+ error = camdd_rw(opt_list, 2, max_io, retry_count, timeout);
+
+bailout:
+ free(opt_list);
+
+ exit(error);
+}
diff --git a/usr.sbin/cdcontrol/Makefile b/usr.sbin/cdcontrol/Makefile
new file mode 100644
index 0000000..de95606
--- /dev/null
+++ b/usr.sbin/cdcontrol/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= cdcontrol
+
+LIBADD= edit
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/cdcontrol/Makefile.depend b/usr.sbin/cdcontrol/Makefile.depend
new file mode 100644
index 0000000..d6c8db8
--- /dev/null
+++ b/usr.sbin/cdcontrol/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libedit \
+ lib/ncurses/ncursesw \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/cdcontrol/cdcontrol.1 b/usr.sbin/cdcontrol/cdcontrol.1
new file mode 100644
index 0000000..f1e969d
--- /dev/null
+++ b/usr.sbin/cdcontrol/cdcontrol.1
@@ -0,0 +1,222 @@
+.\" $FreeBSD$
+.\"
+.Dd June 27, 2008
+.Dt CDCONTROL 1
+.Os
+.Sh NAME
+.Nm cdcontrol
+.Nd compact disc control utility
+.Sh SYNOPSIS
+.Nm
+.Op Fl sv
+.Op Fl f Ar device
+.Op Ar command ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is a program to control audio features of a CD drive.
+The device is a name such
+as
+.Pa cd0
+or
+.Pa acd0 .
+.Pp
+If no
+.Ar command
+is given, then
+.Nm
+enters an interactive mode, reading commands from the standard input.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl s
+Silent mode.
+Do not print table headers and human readable comments.
+.It Fl v
+Verbose mode.
+Print as much information as possible.
+.It Fl f Ar device
+Specify a device, such as
+.Pa /dev/cd0
+or
+.Pa acd0 .
+Both absolute path and relative to
+.Pa /dev
+filename are possible.
+The
+.Fl f
+option overrides
+.Ev CDROM .
+If neither
+.Ev CDROM
+nor the
+.Fl f
+option is specified,
+.Nm
+tries opening first
+.Pa /dev/cdrom ,
+then
+.Pa /dev/cd0 ,
+and finally
+.Pa /dev/acd0 .
+.El
+.Pp
+The available commands are listed below.
+Only as many
+characters as are required to uniquely identify a command
+need be specified.
+The word
+.Ic play
+can be omitted or the characters
+.Ic +
+and
+.Ic -
+can be used in the
+place of
+.Ic next
+and
+.Ic prev .
+.Bl -tag -width indent
+.It Ic play Ar first_track Op Ar last_track
+Play from track
+.Ar first_track
+to track
+.Ar last_track .
+The first track has number 1.
+Can be omitted in all cases.
+.It Xo
+.Ic play
+.Ar start_m : Ns Ar start_s . Ns Ar start_f
+.Op Ar end_m : Ns Ar end_s . Ns Ar end_f
+.Xc
+Play from the absolute address
+(MSF) defined by
+.Ar start_m
+in minutes,
+.Ar start_s ,
+in seconds and
+.Ar start_f
+(frame number) to the absolute address defined by
+.Ar end_m
+in minutes,
+.Ar end_s ,
+in seconds and
+.Ar end_f
+(frame number).
+Minutes are in the range 0-99.
+Seconds are in the range 0-59.
+Frame numbers are in the range 0-74.
+.It Ic play Op # Ns Ar start_block Op Ar length
+Play starting from the logical block
+.Ar start_block
+using
+.Ar length
+logical blocks.
+.It Ic next Op Ar tracks
+Skip forward a number of tracks (default 1).
+.It Ic prev Op Ar tracks
+Skip backward a number of tracks (default 1).
+.It Ic pause
+Stop playing.
+Do not stop the disc.
+.It Ic resume
+Resume playing.
+Used after the
+.Ic pause
+command.
+.It Ic stop
+Stop the disc.
+.It Ic eject
+Eject the disc.
+.It Ic close
+Inject the disc.
+.It Ic volume
+Same as
+.Em status volume
+command.
+.It Ic volume Ar level
+Set the volume of both channels to
+.Ar level .
+Allowed values are in the range 0-255.
+.It Ic volume Ar left_channel right_channel
+Set the volume of left channel to
+.Ar left_channel
+and the volume of right channel to
+.Ar right_channel .
+Allowed values are in the range 0-255.
+.It Ic volume Cm mute
+Turn the sound off.
+.It Ic volume Cm mono
+Set the mono mode.
+.It Ic volume Cm stereo
+Set the stereo mode.
+.It Ic volume Cm left
+Play the left subtrack on both left and right channels.
+.It Ic volume Cm right
+Play the right subtrack on both left and right channels.
+.It Ic info
+Print the table of contents.
+.It Ic status Op Cm audio | media | volume
+Print the information about the disc:
+.Pp
+.Bl -tag -width ".Cm volume" -compact
+.It Cm audio
+the current playing status and position
+.It Cm media
+the current media catalog status
+.It Cm volume
+the current values of the volume for left and right channels.
+.El
+.It Ic cdid
+Display the serial number of the CD using the method used by the
+.Tn CDDB
+project
+.Pq Pa http://www.cddb.org/ .
+.It Ic help
+Print the list of available commands.
+.It Ic debug Cm on
+Enable the debugging mode of the CD device driver.
+.It Ic debug Cm off
+Disable the driver debugging mode.
+.It Ic reset
+Perform the hardware reset of the device.
+.It Ic set Cm msf
+Set minute-second-frame ioctl mode (default).
+.It Ic set Cm lba
+Set LBA ioctl mode.
+.It Ic speed Ar s
+Set the highest speed that the drive should use for reading data.
+The units are multiples of a single speed CDROM (150 KB/s).
+Specify
+.Dq Li max
+to use the drive's fastest speed.
+.It Ic quit
+Quit the program.
+.El
+.Sh ENVIRONMENT
+The following environment variables affect the execution of
+.Nm :
+.Bl -tag -width ".Ev CD_DRIVE"
+.It Ev CDROM
+The CD device to use, if one is not specified with the
+.Fl f
+option.
+.It Ev CDPLAY , CD_DRIVE , DISC , MUSIC_CD
+These variables have been deprecated in favour of
+.Ev CDROM .
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /dev/mcd0" -compact
+.It Pa /dev/cd0
+.It Pa /dev/mcd0
+.It Pa /dev/acd0
+.El
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Fx 2.1 .
+.Sh AUTHORS
+.An Jean-Marc Zucconi
+.An Andrey A. Chernov
+.An Serge V. Vakulenko
diff --git a/usr.sbin/cdcontrol/cdcontrol.c b/usr.sbin/cdcontrol/cdcontrol.c
new file mode 100644
index 0000000..70ef2f5
--- /dev/null
+++ b/usr.sbin/cdcontrol/cdcontrol.c
@@ -0,0 +1,1310 @@
+/*
+ * Compact Disc Control Utility by Serge V. Vakulenko <vak@cronyx.ru>.
+ * Based on the non-X based CD player by Jean-Marc Zucconi and
+ * Andrey A. Chernov.
+ *
+ * Fixed and further modified on 5-Sep-1995 by Jukka Ukkonen <jau@funet.fi>.
+ *
+ * 11-Sep-1995: Jukka A. Ukkonen <jau@funet.fi>
+ * A couple of further fixes to my own earlier "fixes".
+ *
+ * 18-Sep-1995: Jukka A. Ukkonen <jau@funet.fi>
+ * Added an ability to specify addresses relative to the
+ * beginning of a track. This is in fact a variation of
+ * doing the simple play_msf() call.
+ *
+ * 11-Oct-1995: Serge V.Vakulenko <vak@cronyx.ru>
+ * New eject algorithm.
+ * Some code style reformatting.
+ *
+ * 13-Dec-1999: Knut A. Syed <kas@kas.no>
+ * Volume-command modified. If used with only one
+ * parameter it now sets both channels. If used without
+ * parameters it will print volume-info.
+ * Version 2.0.1
+ *
+ * 27-Jun-2008 Pietro Cerutti <gahr@FreeBSD.org>
+ * Further enhancement to volume. Values not in range 0-255
+ * are now reduced to be in range. This prevents overflow in
+ * the uchar storing the volume (256 -> 0, -20 -> 236, ...).
+ * Version 2.0.2
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/cdio.h>
+#include <sys/cdrio.h>
+#include <sys/file.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <histedit.h>
+#include <limits.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <vis.h>
+
+#define VERSION "2.0.2"
+
+#define ASTS_INVALID 0x00 /* Audio status byte not valid */
+#define ASTS_PLAYING 0x11 /* Audio play operation in progress */
+#define ASTS_PAUSED 0x12 /* Audio play operation paused */
+#define ASTS_COMPLETED 0x13 /* Audio play operation successfully completed */
+#define ASTS_ERROR 0x14 /* Audio play operation stopped due to error */
+#define ASTS_VOID 0x15 /* No current audio status to return */
+
+#ifdef DEFAULT_CD_DRIVE
+# error "Setting DEFAULT_CD_DRIVE is no longer supported"
+#endif
+
+#define CMD_DEBUG 1
+#define CMD_EJECT 2
+#define CMD_HELP 3
+#define CMD_INFO 4
+#define CMD_PAUSE 5
+#define CMD_PLAY 6
+#define CMD_QUIT 7
+#define CMD_RESUME 8
+#define CMD_STOP 9
+#define CMD_VOLUME 10
+#define CMD_CLOSE 11
+#define CMD_RESET 12
+#define CMD_SET 13
+#define CMD_STATUS 14
+#define CMD_CDID 15
+#define CMD_NEXT 16
+#define CMD_PREVIOUS 17
+#define CMD_SPEED 18
+#define STATUS_AUDIO 0x1
+#define STATUS_MEDIA 0x2
+#define STATUS_VOLUME 0x4
+
+static struct cmdtab {
+ int command;
+ const char *name;
+ unsigned min;
+ const char *args;
+} cmdtab[] = {
+{ CMD_CLOSE, "close", 1, "" },
+{ CMD_DEBUG, "debug", 1, "on | off" },
+{ CMD_EJECT, "eject", 1, "" },
+{ CMD_HELP, "?", 1, 0 },
+{ CMD_HELP, "help", 1, "" },
+{ CMD_INFO, "info", 1, "" },
+{ CMD_NEXT, "next", 1, "" },
+{ CMD_PAUSE, "pause", 2, "" },
+{ CMD_PLAY, "play", 1, "min1:sec1[.fram1] [min2:sec2[.fram2]]" },
+{ CMD_PLAY, "play", 1, "track1[.index1] [track2[.index2]]" },
+{ CMD_PLAY, "play", 1, "tr1 m1:s1[.f1] [[tr2] [m2:s2[.f2]]]" },
+{ CMD_PLAY, "play", 1, "[#block [len]]" },
+{ CMD_PREVIOUS, "previous", 2, "" },
+{ CMD_QUIT, "quit", 1, "" },
+{ CMD_RESET, "reset", 4, "" },
+{ CMD_RESUME, "resume", 1, "" },
+{ CMD_SET, "set", 2, "msf | lba" },
+{ CMD_STATUS, "status", 1, "[audio | media | volume]" },
+{ CMD_STOP, "stop", 3, "" },
+{ CMD_VOLUME, "volume", 1,
+ "<l&r> <l> <r> | left | right | mute | mono | stereo" },
+{ CMD_CDID, "cdid", 2, "" },
+{ CMD_SPEED, "speed", 2, "speed" },
+{ 0, NULL, 0, NULL }
+};
+
+static struct cd_toc_entry toc_buffer[100];
+
+static const char *cdname;
+static int fd = -1;
+static int verbose = 1;
+static int msf = 1;
+
+static int setvol(int, int);
+static int read_toc_entrys(int);
+static int play_msf(int, int, int, int, int, int);
+static int play_track(int, int, int, int);
+static int status(int *, int *, int *, int *);
+static int open_cd(void);
+static int next_prev(char *arg, int);
+static int play(char *arg);
+static int info(char *arg);
+static int cdid(void);
+static int pstatus(char *arg);
+static char *input(int *);
+static void prtrack(struct cd_toc_entry *e, int lastflag);
+static void lba2msf(unsigned long lba, u_char *m, u_char *s, u_char *f);
+static unsigned int msf2lba(u_char m, u_char s, u_char f);
+static int play_blocks(int blk, int len);
+static int run(int cmd, char *arg);
+static char *parse(char *buf, int *cmd);
+static void help(void);
+static void usage(void);
+static char *use_cdrom_instead(const char *);
+static const char *strstatus(int);
+static u_int dbprog_discid(void);
+static const char *cdcontrol_prompt(void);
+
+static void
+help(void)
+{
+ struct cmdtab *c;
+ const char *s;
+ char n;
+ int i;
+
+ for (c=cmdtab; c->name; ++c) {
+ if (! c->args)
+ continue;
+ printf("\t");
+ for (i = c->min, s = c->name; *s; s++, i--) {
+ if (i > 0)
+ n = toupper(*s);
+ else
+ n = *s;
+ putchar(n);
+ }
+ if (*c->args)
+ printf (" %s", c->args);
+ printf ("\n");
+ }
+ printf ("\n\tThe word \"play\" is not required for the play commands.\n");
+ printf ("\tThe plain target address is taken as a synonym for play.\n");
+}
+
+static void
+usage(void)
+{
+ fprintf (stderr, "usage: cdcontrol [-sv] [-f device] [command ...]\n");
+ exit (1);
+}
+
+static char *
+use_cdrom_instead(const char *old_envvar)
+{
+ char *device;
+
+ device = getenv(old_envvar);
+ if (device)
+ warnx("%s environment variable deprecated, "
+ "please use CDROM in the future.", old_envvar);
+ return device;
+}
+
+
+int
+main(int argc, char **argv)
+{
+ int cmd;
+ char *arg;
+
+ for (;;) {
+ switch (getopt (argc, argv, "svhf:")) {
+ case -1:
+ break;
+ case 's':
+ verbose = 0;
+ continue;
+ case 'v':
+ verbose = 2;
+ continue;
+ case 'f':
+ cdname = optarg;
+ continue;
+ case 'h':
+ default:
+ usage ();
+ }
+ break;
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0 && ! strcasecmp (*argv, "help"))
+ usage ();
+
+ if (! cdname) {
+ cdname = getenv("CDROM");
+ }
+
+ if (! cdname)
+ cdname = use_cdrom_instead("MUSIC_CD");
+ if (! cdname)
+ cdname = use_cdrom_instead("CD_DRIVE");
+ if (! cdname)
+ cdname = use_cdrom_instead("DISC");
+ if (! cdname)
+ cdname = use_cdrom_instead("CDPLAY");
+
+ if (argc > 0) {
+ char buf[80], *p;
+ int len, rc;
+
+ for (p=buf; argc-->0; ++argv) {
+ len = strlen (*argv);
+
+ if (p + len >= buf + sizeof (buf) - 1)
+ usage ();
+
+ if (p > buf)
+ *p++ = ' ';
+
+ strcpy (p, *argv);
+ p += len;
+ }
+ *p = 0;
+ arg = parse (buf, &cmd);
+ rc = run (cmd, arg);
+ if (rc < 0 && verbose)
+ warn(NULL);
+
+ return (rc);
+ }
+
+ if (verbose == 1)
+ verbose = isatty (0);
+
+ if (verbose) {
+ printf ("Compact Disc Control utility, version %s\n", VERSION);
+ printf ("Type `?' for command list\n\n");
+ }
+
+ for (;;) {
+ arg = input (&cmd);
+ if (run (cmd, arg) < 0) {
+ if (verbose)
+ warn(NULL);
+ close (fd);
+ fd = -1;
+ }
+ fflush (stdout);
+ }
+}
+
+static int
+run(int cmd, char *arg)
+{
+ long speed;
+ int l, r, rc, count;
+
+ switch (cmd) {
+
+ case CMD_QUIT:
+ exit (0);
+
+ case CMD_INFO:
+ if (fd < 0 && ! open_cd ())
+ return (0);
+
+ return info (arg);
+
+ case CMD_CDID:
+ if (fd < 0 && ! open_cd ())
+ return (0);
+
+ return cdid ();
+
+ case CMD_STATUS:
+ if (fd < 0 && ! open_cd ())
+ return (0);
+
+ return pstatus (arg);
+
+ case CMD_NEXT:
+ case CMD_PREVIOUS:
+ if (fd < 0 && ! open_cd ())
+ return (0);
+
+ while (isspace (*arg))
+ arg++;
+
+ return next_prev (arg, cmd);
+
+ case CMD_PAUSE:
+ if (fd < 0 && ! open_cd ())
+ return (0);
+
+ return ioctl (fd, CDIOCPAUSE);
+
+ case CMD_RESUME:
+ if (fd < 0 && ! open_cd ())
+ return (0);
+
+ return ioctl (fd, CDIOCRESUME);
+
+ case CMD_STOP:
+ if (fd < 0 && ! open_cd ())
+ return (0);
+
+ rc = ioctl (fd, CDIOCSTOP);
+
+ (void) ioctl (fd, CDIOCALLOW);
+
+ return (rc);
+
+ case CMD_RESET:
+ if (fd < 0 && ! open_cd ())
+ return (0);
+
+ rc = ioctl (fd, CDIOCRESET);
+ if (rc < 0)
+ return rc;
+ close(fd);
+ fd = -1;
+ return (0);
+
+ case CMD_DEBUG:
+ if (fd < 0 && ! open_cd ())
+ return (0);
+
+ if (! strcasecmp (arg, "on"))
+ return ioctl (fd, CDIOCSETDEBUG);
+
+ if (! strcasecmp (arg, "off"))
+ return ioctl (fd, CDIOCCLRDEBUG);
+
+ warnx("invalid command arguments");
+
+ return (0);
+
+ case CMD_EJECT:
+ if (fd < 0 && ! open_cd ())
+ return (0);
+
+ (void) ioctl (fd, CDIOCALLOW);
+ rc = ioctl (fd, CDIOCEJECT);
+ if (rc < 0)
+ return (rc);
+ return (0);
+
+ case CMD_CLOSE:
+ if (fd < 0 && ! open_cd ())
+ return (0);
+
+ (void) ioctl (fd, CDIOCALLOW);
+ rc = ioctl (fd, CDIOCCLOSE);
+ if (rc < 0)
+ return (rc);
+ close(fd);
+ fd = -1;
+ return (0);
+
+ case CMD_PLAY:
+ if (fd < 0 && ! open_cd ())
+ return (0);
+
+ while (isspace (*arg))
+ arg++;
+
+ return play (arg);
+
+ case CMD_SET:
+ if (! strcasecmp (arg, "msf"))
+ msf = 1;
+ else if (! strcasecmp (arg, "lba"))
+ msf = 0;
+ else
+ warnx("invalid command arguments");
+ return (0);
+
+ case CMD_VOLUME:
+ if (fd < 0 && !open_cd ())
+ return (0);
+
+ if (! strlen (arg)) {
+ char volume[] = "volume";
+
+ return pstatus (volume);
+ }
+
+ if (! strncasecmp (arg, "left", strlen(arg)))
+ return ioctl (fd, CDIOCSETLEFT);
+
+ if (! strncasecmp (arg, "right", strlen(arg)))
+ return ioctl (fd, CDIOCSETRIGHT);
+
+ if (! strncasecmp (arg, "mono", strlen(arg)))
+ return ioctl (fd, CDIOCSETMONO);
+
+ if (! strncasecmp (arg, "stereo", strlen(arg)))
+ return ioctl (fd, CDIOCSETSTERIO);
+
+ if (! strncasecmp (arg, "mute", strlen(arg)))
+ return ioctl (fd, CDIOCSETMUTE);
+
+ count = sscanf (arg, "%d %d", &l, &r);
+ if (count == 1)
+ return setvol (l, l);
+ if (count == 2)
+ return setvol (l, r);
+ warnx("invalid command arguments");
+ return (0);
+
+ case CMD_SPEED:
+ if (fd < 0 && ! open_cd ())
+ return (0);
+
+ errno = 0;
+ if (strcasecmp("max", arg) == 0)
+ speed = CDR_MAX_SPEED;
+ else
+ speed = strtol(arg, NULL, 10) * 177;
+ if (speed <= 0 || speed > INT_MAX) {
+ warnx("invalid command arguments %s", arg);
+ return (0);
+ }
+ return ioctl(fd, CDRIOCREADSPEED, &speed);
+
+ default:
+ case CMD_HELP:
+ help ();
+ return (0);
+
+ }
+}
+
+static int
+play(char *arg)
+{
+ struct ioc_toc_header h;
+ unsigned int n;
+ int rc, start, end = 0, istart = 1, iend = 1;
+
+ rc = ioctl (fd, CDIOREADTOCHEADER, &h);
+
+ if (rc < 0)
+ return (rc);
+
+ n = h.ending_track - h.starting_track + 1;
+ rc = read_toc_entrys ((n + 1) * sizeof (struct cd_toc_entry));
+
+ if (rc < 0)
+ return (rc);
+
+ if (! arg || ! *arg) {
+ /* Play the whole disc */
+ if (msf)
+ return play_blocks (0, msf2lba (toc_buffer[n].addr.msf.minute,
+ toc_buffer[n].addr.msf.second,
+ toc_buffer[n].addr.msf.frame));
+ else
+ return play_blocks (0, ntohl(toc_buffer[n].addr.lba));
+ }
+
+ if (strchr (arg, '#')) {
+ /* Play block #blk [ len ] */
+ int blk, len = 0;
+
+ if (2 != sscanf (arg, "#%d%d", &blk, &len) &&
+ 1 != sscanf (arg, "#%d", &blk))
+ goto Clean_up;
+
+ if (len == 0) {
+ if (msf)
+ len = msf2lba (toc_buffer[n].addr.msf.minute,
+ toc_buffer[n].addr.msf.second,
+ toc_buffer[n].addr.msf.frame) - blk;
+ else
+ len = ntohl(toc_buffer[n].addr.lba) - blk;
+ }
+ return play_blocks (blk, len);
+ }
+
+ if (strchr (arg, ':')) {
+ /*
+ * Play MSF m1:s1 [ .f1 ] [ m2:s2 [ .f2 ] ]
+ *
+ * Will now also undestand timed addresses relative
+ * to the beginning of a track in the form...
+ *
+ * tr1 m1:s1[.f1] [[tr2] [m2:s2[.f2]]]
+ */
+ unsigned tr1, tr2;
+ unsigned m1, m2, s1, s2, f1, f2;
+ unsigned char tm, ts, tf;
+
+ tr2 = m2 = s2 = f2 = f1 = 0;
+ if (8 == sscanf (arg, "%d %d:%d.%d %d %d:%d.%d",
+ &tr1, &m1, &s1, &f1, &tr2, &m2, &s2, &f2))
+ goto Play_Relative_Addresses;
+
+ tr2 = m2 = s2 = f2 = f1 = 0;
+ if (7 == sscanf (arg, "%d %d:%d %d %d:%d.%d",
+ &tr1, &m1, &s1, &tr2, &m2, &s2, &f2))
+ goto Play_Relative_Addresses;
+
+ tr2 = m2 = s2 = f2 = f1 = 0;
+ if (7 == sscanf (arg, "%d %d:%d.%d %d %d:%d",
+ &tr1, &m1, &s1, &f1, &tr2, &m2, &s2))
+ goto Play_Relative_Addresses;
+
+ tr2 = m2 = s2 = f2 = f1 = 0;
+ if (7 == sscanf (arg, "%d %d:%d.%d %d:%d.%d",
+ &tr1, &m1, &s1, &f1, &m2, &s2, &f2))
+ goto Play_Relative_Addresses;
+
+ tr2 = m2 = s2 = f2 = f1 = 0;
+ if (6 == sscanf (arg, "%d %d:%d.%d %d:%d",
+ &tr1, &m1, &s1, &f1, &m2, &s2))
+ goto Play_Relative_Addresses;
+
+ tr2 = m2 = s2 = f2 = f1 = 0;
+ if (6 == sscanf (arg, "%d %d:%d %d:%d.%d",
+ &tr1, &m1, &s1, &m2, &s2, &f2))
+ goto Play_Relative_Addresses;
+
+ tr2 = m2 = s2 = f2 = f1 = 0;
+ if (6 == sscanf (arg, "%d %d:%d.%d %d %d",
+ &tr1, &m1, &s1, &f1, &tr2, &m2))
+ goto Play_Relative_Addresses;
+
+ tr2 = m2 = s2 = f2 = f1 = 0;
+ if (5 == sscanf (arg, "%d %d:%d %d:%d", &tr1, &m1, &s1, &m2, &s2))
+ goto Play_Relative_Addresses;
+
+ tr2 = m2 = s2 = f2 = f1 = 0;
+ if (5 == sscanf (arg, "%d %d:%d %d %d",
+ &tr1, &m1, &s1, &tr2, &m2))
+ goto Play_Relative_Addresses;
+
+ tr2 = m2 = s2 = f2 = f1 = 0;
+ if (5 == sscanf (arg, "%d %d:%d.%d %d",
+ &tr1, &m1, &s1, &f1, &tr2))
+ goto Play_Relative_Addresses;
+
+ tr2 = m2 = s2 = f2 = f1 = 0;
+ if (4 == sscanf (arg, "%d %d:%d %d", &tr1, &m1, &s1, &tr2))
+ goto Play_Relative_Addresses;
+
+ tr2 = m2 = s2 = f2 = f1 = 0;
+ if (4 == sscanf (arg, "%d %d:%d.%d", &tr1, &m1, &s1, &f1))
+ goto Play_Relative_Addresses;
+
+ tr2 = m2 = s2 = f2 = f1 = 0;
+ if (3 == sscanf (arg, "%d %d:%d", &tr1, &m1, &s1))
+ goto Play_Relative_Addresses;
+
+ tr2 = m2 = s2 = f2 = f1 = 0;
+ goto Try_Absolute_Timed_Addresses;
+
+Play_Relative_Addresses:
+ if (! tr1)
+ tr1 = 1;
+ else if (tr1 > n)
+ tr1 = n;
+
+ tr1--;
+
+ if (msf) {
+ tm = toc_buffer[tr1].addr.msf.minute;
+ ts = toc_buffer[tr1].addr.msf.second;
+ tf = toc_buffer[tr1].addr.msf.frame;
+ } else
+ lba2msf(ntohl(toc_buffer[tr1].addr.lba),
+ &tm, &ts, &tf);
+ if ((m1 > tm)
+ || ((m1 == tm)
+ && ((s1 > ts)
+ || ((s1 == ts)
+ && (f1 > tf))))) {
+ printf ("Track %d is not that long.\n", tr1);
+ return (0);
+ }
+
+ f1 += tf;
+ if (f1 >= 75) {
+ s1 += f1 / 75;
+ f1 %= 75;
+ }
+
+ s1 += ts;
+ if (s1 >= 60) {
+ m1 += s1 / 60;
+ s1 %= 60;
+ }
+
+ m1 += tm;
+
+ if (! tr2) {
+ if (m2 || s2 || f2) {
+ tr2 = tr1;
+ f2 += f1;
+ if (f2 >= 75) {
+ s2 += f2 / 75;
+ f2 %= 75;
+ }
+
+ s2 += s1;
+ if (s2 > 60) {
+ m2 += s2 / 60;
+ s2 %= 60;
+ }
+
+ m2 += m1;
+ } else {
+ tr2 = n;
+ if (msf) {
+ m2 = toc_buffer[n].addr.msf.minute;
+ s2 = toc_buffer[n].addr.msf.second;
+ f2 = toc_buffer[n].addr.msf.frame;
+ } else {
+ lba2msf(ntohl(toc_buffer[n].addr.lba),
+ &tm, &ts, &tf);
+ m2 = tm;
+ s2 = ts;
+ f2 = tf;
+ }
+ }
+ } else if (tr2 > n) {
+ tr2 = n;
+ m2 = s2 = f2 = 0;
+ } else {
+ if (m2 || s2 || f2)
+ tr2--;
+ if (msf) {
+ tm = toc_buffer[tr2].addr.msf.minute;
+ ts = toc_buffer[tr2].addr.msf.second;
+ tf = toc_buffer[tr2].addr.msf.frame;
+ } else
+ lba2msf(ntohl(toc_buffer[tr2].addr.lba),
+ &tm, &ts, &tf);
+ f2 += tf;
+ if (f2 >= 75) {
+ s2 += f2 / 75;
+ f2 %= 75;
+ }
+
+ s2 += ts;
+ if (s2 > 60) {
+ m2 += s2 / 60;
+ s2 %= 60;
+ }
+
+ m2 += tm;
+ }
+
+ if (msf) {
+ tm = toc_buffer[n].addr.msf.minute;
+ ts = toc_buffer[n].addr.msf.second;
+ tf = toc_buffer[n].addr.msf.frame;
+ } else
+ lba2msf(ntohl(toc_buffer[n].addr.lba),
+ &tm, &ts, &tf);
+ if ((tr2 < n)
+ && ((m2 > tm)
+ || ((m2 == tm)
+ && ((s2 > ts)
+ || ((s2 == ts)
+ && (f2 > tf)))))) {
+ printf ("The playing time of the disc is not that long.\n");
+ return (0);
+ }
+ return (play_msf (m1, s1, f1, m2, s2, f2));
+
+Try_Absolute_Timed_Addresses:
+ if (6 != sscanf (arg, "%d:%d.%d%d:%d.%d",
+ &m1, &s1, &f1, &m2, &s2, &f2) &&
+ 5 != sscanf (arg, "%d:%d.%d%d:%d", &m1, &s1, &f1, &m2, &s2) &&
+ 5 != sscanf (arg, "%d:%d%d:%d.%d", &m1, &s1, &m2, &s2, &f2) &&
+ 3 != sscanf (arg, "%d:%d.%d", &m1, &s1, &f1) &&
+ 4 != sscanf (arg, "%d:%d%d:%d", &m1, &s1, &m2, &s2) &&
+ 2 != sscanf (arg, "%d:%d", &m1, &s1))
+ goto Clean_up;
+
+ if (m2 == 0) {
+ if (msf) {
+ m2 = toc_buffer[n].addr.msf.minute;
+ s2 = toc_buffer[n].addr.msf.second;
+ f2 = toc_buffer[n].addr.msf.frame;
+ } else {
+ lba2msf(ntohl(toc_buffer[n].addr.lba),
+ &tm, &ts, &tf);
+ m2 = tm;
+ s2 = ts;
+ f2 = tf;
+ }
+ }
+ return play_msf (m1, s1, f1, m2, s2, f2);
+ }
+
+ /*
+ * Play track trk1 [ .idx1 ] [ trk2 [ .idx2 ] ]
+ */
+ if (4 != sscanf (arg, "%d.%d%d.%d", &start, &istart, &end, &iend) &&
+ 3 != sscanf (arg, "%d.%d%d", &start, &istart, &end) &&
+ 3 != sscanf (arg, "%d%d.%d", &start, &end, &iend) &&
+ 2 != sscanf (arg, "%d.%d", &start, &istart) &&
+ 2 != sscanf (arg, "%d%d", &start, &end) &&
+ 1 != sscanf (arg, "%d", &start))
+ goto Clean_up;
+
+ if (end == 0)
+ end = n;
+ return (play_track (start, istart, end, iend));
+
+Clean_up:
+ warnx("invalid command arguments");
+ return (0);
+}
+
+static int
+next_prev(char *arg, int cmd)
+{
+ struct ioc_toc_header h;
+ int dir, junk, n, off, rc, trk;
+
+ dir = (cmd == CMD_NEXT) ? 1 : -1;
+ rc = ioctl (fd, CDIOREADTOCHEADER, &h);
+ if (rc < 0)
+ return (rc);
+
+ n = h.ending_track - h.starting_track + 1;
+ rc = status (&trk, &junk, &junk, &junk);
+ if (rc < 0)
+ return (-1);
+
+ if (arg && *arg) {
+ if (sscanf (arg, "%u", &off) != 1) {
+ warnx("invalid command argument");
+ return (0);
+ } else
+ trk += off * dir;
+ } else
+ trk += dir;
+
+ if (trk > h.ending_track)
+ trk = 1;
+
+ return (play_track (trk, 1, n, 1));
+}
+
+static const char *
+strstatus(int sts)
+{
+ switch (sts) {
+ case ASTS_INVALID: return ("invalid");
+ case ASTS_PLAYING: return ("playing");
+ case ASTS_PAUSED: return ("paused");
+ case ASTS_COMPLETED: return ("completed");
+ case ASTS_ERROR: return ("error");
+ case ASTS_VOID: return ("void");
+ default: return ("??");
+ }
+}
+
+static int
+pstatus(char *arg)
+{
+ struct ioc_vol v;
+ struct ioc_read_subchannel ss;
+ struct cd_sub_channel_info data;
+ int rc, trk, m, s, f;
+ int what = 0;
+ char *p, vmcn[(4 * 15) + 1];
+
+ while ((p = strtok(arg, " \t"))) {
+ arg = 0;
+ if (!strncasecmp(p, "audio", strlen(p)))
+ what |= STATUS_AUDIO;
+ else if (!strncasecmp(p, "media", strlen(p)))
+ what |= STATUS_MEDIA;
+ else if (!strncasecmp(p, "volume", strlen(p)))
+ what |= STATUS_VOLUME;
+ else {
+ warnx("invalid command arguments");
+ return 0;
+ }
+ }
+ if (!what)
+ what = STATUS_AUDIO|STATUS_MEDIA|STATUS_VOLUME;
+ if (what & STATUS_AUDIO) {
+ rc = status (&trk, &m, &s, &f);
+ if (rc >= 0)
+ if (verbose)
+ printf ("Audio status = %d<%s>, current track = %d, current position = %d:%02d.%02d\n",
+ rc, strstatus (rc), trk, m, s, f);
+ else
+ printf ("%d %d %d:%02d.%02d\n", rc, trk, m, s, f);
+ else
+ printf ("No current status info available\n");
+ }
+ if (what & STATUS_MEDIA) {
+ bzero (&ss, sizeof (ss));
+ ss.data = &data;
+ ss.data_len = sizeof (data);
+ ss.address_format = msf ? CD_MSF_FORMAT : CD_LBA_FORMAT;
+ ss.data_format = CD_MEDIA_CATALOG;
+ rc = ioctl (fd, CDIOCREADSUBCHANNEL, (char *) &ss);
+ if (rc >= 0) {
+ printf("Media catalog is %sactive",
+ ss.data->what.media_catalog.mc_valid ? "": "in");
+ if (ss.data->what.media_catalog.mc_valid &&
+ ss.data->what.media_catalog.mc_number[0])
+ {
+ strvisx (vmcn, ss.data->what.media_catalog.mc_number,
+ (sizeof (vmcn) - 1) / 4, VIS_OCTAL | VIS_NL);
+ printf(", number \"%.*s\"", (int)sizeof (vmcn), vmcn);
+ }
+ putchar('\n');
+ } else
+ printf("No media catalog info available\n");
+ }
+ if (what & STATUS_VOLUME) {
+ rc = ioctl (fd, CDIOCGETVOL, &v);
+ if (rc >= 0)
+ if (verbose)
+ printf ("Left volume = %d, right volume = %d\n",
+ v.vol[0], v.vol[1]);
+ else
+ printf ("%d %d\n", v.vol[0], v.vol[1]);
+ else
+ printf ("No volume level info available\n");
+ }
+ return(0);
+}
+
+/*
+ * dbprog_sum
+ * Convert an integer to its text string representation, and
+ * compute its checksum. Used by dbprog_discid to derive the
+ * disc ID.
+ *
+ * Args:
+ * n - The integer value.
+ *
+ * Return:
+ * The integer checksum.
+ */
+static int
+dbprog_sum(int n)
+{
+ char buf[12],
+ *p;
+ int ret = 0;
+
+ /* For backward compatibility this algorithm must not change */
+ sprintf(buf, "%u", n);
+ for (p = buf; *p != '\0'; p++)
+ ret += (*p - '0');
+
+ return(ret);
+}
+
+
+/*
+ * dbprog_discid
+ * Compute a magic disc ID based on the number of tracks,
+ * the length of each track, and a checksum of the string
+ * that represents the offset of each track.
+ *
+ * Args:
+ * s - Pointer to the curstat_t structure.
+ *
+ * Return:
+ * The integer disc ID.
+ */
+static u_int
+dbprog_discid(void)
+{
+ struct ioc_toc_header h;
+ int rc;
+ int i, ntr,
+ t = 0,
+ n = 0;
+
+ rc = ioctl (fd, CDIOREADTOCHEADER, &h);
+ if (rc < 0)
+ return 0;
+ ntr = h.ending_track - h.starting_track + 1;
+ i = msf;
+ msf = 1;
+ rc = read_toc_entrys ((ntr + 1) * sizeof (struct cd_toc_entry));
+ msf = i;
+ if (rc < 0)
+ return 0;
+ /* For backward compatibility this algorithm must not change */
+ for (i = 0; i < ntr; i++) {
+#define TC_MM(a) toc_buffer[a].addr.msf.minute
+#define TC_SS(a) toc_buffer[a].addr.msf.second
+ n += dbprog_sum((TC_MM(i) * 60) + TC_SS(i));
+
+ t += ((TC_MM(i+1) * 60) + TC_SS(i+1)) -
+ ((TC_MM(i) * 60) + TC_SS(i));
+ }
+
+ return((n % 0xff) << 24 | t << 8 | ntr);
+}
+
+static int
+cdid(void)
+{
+ u_int id;
+
+ id = dbprog_discid();
+ if (id)
+ {
+ if (verbose)
+ printf ("CDID=");
+ printf ("%08x\n",id);
+ }
+ return id ? 0 : 1;
+}
+
+static int
+info(char *arg __unused)
+{
+ struct ioc_toc_header h;
+ int rc, i, n;
+
+ rc = ioctl (fd, CDIOREADTOCHEADER, &h);
+ if (rc >= 0) {
+ if (verbose)
+ printf ("Starting track = %d, ending track = %d, TOC size = %d bytes\n",
+ h.starting_track, h.ending_track, h.len);
+ else
+ printf ("%d %d %d\n", h.starting_track,
+ h.ending_track, h.len);
+ } else {
+ warn("getting toc header");
+ return (rc);
+ }
+
+ n = h.ending_track - h.starting_track + 1;
+ rc = read_toc_entrys ((n + 1) * sizeof (struct cd_toc_entry));
+ if (rc < 0)
+ return (rc);
+
+ if (verbose) {
+ printf ("track start duration block length type\n");
+ printf ("-------------------------------------------------\n");
+ }
+
+ for (i = 0; i < n; i++) {
+ printf ("%5d ", toc_buffer[i].track);
+ prtrack (toc_buffer + i, 0);
+ }
+ printf ("%5d ", toc_buffer[n].track);
+ prtrack (toc_buffer + n, 1);
+ return (0);
+}
+
+static void
+lba2msf(unsigned long lba, u_char *m, u_char *s, u_char *f)
+{
+ lba += 150; /* block start offset */
+ lba &= 0xffffff; /* negative lbas use only 24 bits */
+ *m = lba / (60 * 75);
+ lba %= (60 * 75);
+ *s = lba / 75;
+ *f = lba % 75;
+}
+
+static unsigned int
+msf2lba(u_char m, u_char s, u_char f)
+{
+ return (((m * 60) + s) * 75 + f) - 150;
+}
+
+static void
+prtrack(struct cd_toc_entry *e, int lastflag)
+{
+ int block, next, len;
+ u_char m, s, f;
+
+ if (msf) {
+ /* Print track start */
+ printf ("%2d:%02d.%02d ", e->addr.msf.minute,
+ e->addr.msf.second, e->addr.msf.frame);
+
+ block = msf2lba (e->addr.msf.minute, e->addr.msf.second,
+ e->addr.msf.frame);
+ } else {
+ block = ntohl(e->addr.lba);
+ lba2msf(block, &m, &s, &f);
+ /* Print track start */
+ printf ("%2d:%02d.%02d ", m, s, f);
+ }
+ if (lastflag) {
+ /* Last track -- print block */
+ printf (" - %6d - -\n", block);
+ return;
+ }
+
+ if (msf)
+ next = msf2lba (e[1].addr.msf.minute, e[1].addr.msf.second,
+ e[1].addr.msf.frame);
+ else
+ next = ntohl(e[1].addr.lba);
+ len = next - block;
+ /* Take into account a start offset time. */
+ lba2msf (len - 150, &m, &s, &f);
+
+ /* Print duration, block, length, type */
+ printf ("%2d:%02d.%02d %6d %6d %5s\n", m, s, f, block, len,
+ (e->control & 4) ? "data" : "audio");
+}
+
+static int
+play_track(int tstart, int istart, int tend, int iend)
+{
+ struct ioc_play_track t;
+
+ t.start_track = tstart;
+ t.start_index = istart;
+ t.end_track = tend;
+ t.end_index = iend;
+
+ return ioctl (fd, CDIOCPLAYTRACKS, &t);
+}
+
+static int
+play_blocks(int blk, int len)
+{
+ struct ioc_play_blocks t;
+
+ t.blk = blk;
+ t.len = len;
+
+ return ioctl (fd, CDIOCPLAYBLOCKS, &t);
+}
+
+static int
+setvol(int left, int right)
+{
+ struct ioc_vol v;
+
+ left = left < 0 ? 0 : left > 255 ? 255 : left;
+ right = right < 0 ? 0 : right > 255 ? 255 : right;
+
+ v.vol[0] = left;
+ v.vol[1] = right;
+ v.vol[2] = 0;
+ v.vol[3] = 0;
+
+ return ioctl (fd, CDIOCSETVOL, &v);
+}
+
+static int
+read_toc_entrys(int len)
+{
+ struct ioc_read_toc_entry t;
+
+ t.address_format = msf ? CD_MSF_FORMAT : CD_LBA_FORMAT;
+ t.starting_track = 0;
+ t.data_len = len;
+ t.data = toc_buffer;
+
+ return (ioctl (fd, CDIOREADTOCENTRYS, (char *) &t));
+}
+
+static int
+play_msf(int start_m, int start_s, int start_f,
+ int end_m, int end_s, int end_f)
+{
+ struct ioc_play_msf a;
+
+ a.start_m = start_m;
+ a.start_s = start_s;
+ a.start_f = start_f;
+ a.end_m = end_m;
+ a.end_s = end_s;
+ a.end_f = end_f;
+
+ return ioctl (fd, CDIOCPLAYMSF, (char *) &a);
+}
+
+static int
+status(int *trk, int *min, int *sec, int *frame)
+{
+ struct ioc_read_subchannel s;
+ struct cd_sub_channel_info data;
+ u_char mm, ss, ff;
+
+ bzero (&s, sizeof (s));
+ s.data = &data;
+ s.data_len = sizeof (data);
+ s.address_format = msf ? CD_MSF_FORMAT : CD_LBA_FORMAT;
+ s.data_format = CD_CURRENT_POSITION;
+
+ if (ioctl (fd, CDIOCREADSUBCHANNEL, (char *) &s) < 0)
+ return -1;
+
+ *trk = s.data->what.position.track_number;
+ if (msf) {
+ *min = s.data->what.position.reladdr.msf.minute;
+ *sec = s.data->what.position.reladdr.msf.second;
+ *frame = s.data->what.position.reladdr.msf.frame;
+ } else {
+ lba2msf(ntohl(s.data->what.position.reladdr.lba),
+ &mm, &ss, &ff);
+ *min = mm;
+ *sec = ss;
+ *frame = ff;
+ }
+
+ return s.data->header.audio_status;
+}
+
+static const char *
+cdcontrol_prompt(void)
+{
+ return ("cdcontrol> ");
+}
+
+static char *
+input(int *cmd)
+{
+#define MAXLINE 80
+ static EditLine *el = NULL;
+ static History *hist = NULL;
+ HistEvent he;
+ static char buf[MAXLINE];
+ int num = 0;
+ int len;
+ const char *bp = NULL;
+ char *p;
+
+ do {
+ if (verbose) {
+ if (!el) {
+ el = el_init("cdcontrol", stdin, stdout,
+ stderr);
+ hist = history_init();
+ history(hist, &he, H_SETSIZE, 100);
+ el_set(el, EL_HIST, history, hist);
+ el_set(el, EL_EDITOR, "emacs");
+ el_set(el, EL_PROMPT, cdcontrol_prompt);
+ el_set(el, EL_SIGNAL, 1);
+ el_source(el, NULL);
+ }
+ if ((bp = el_gets(el, &num)) == NULL || num == 0) {
+ *cmd = CMD_QUIT;
+ fprintf (stderr, "\r\n");
+ return (0);
+ }
+
+ len = (num > MAXLINE) ? MAXLINE : num;
+ memcpy(buf, bp, len);
+ buf[len] = 0;
+ history(hist, &he, H_ENTER, bp);
+#undef MAXLINE
+
+ } else {
+ if (! fgets (buf, sizeof (buf), stdin)) {
+ *cmd = CMD_QUIT;
+ fprintf (stderr, "\r\n");
+ return (0);
+ }
+ }
+ p = parse (buf, cmd);
+ } while (! p);
+ return (p);
+}
+
+static char *
+parse(char *buf, int *cmd)
+{
+ struct cmdtab *c;
+ char *p;
+ unsigned int len;
+
+ for (p=buf; isspace (*p); p++)
+ continue;
+
+ if (isdigit (*p) || (p[0] == '#' && isdigit (p[1]))) {
+ *cmd = CMD_PLAY;
+ return (p);
+ } else if (*p == '+') {
+ *cmd = CMD_NEXT;
+ return (p + 1);
+ } else if (*p == '-') {
+ *cmd = CMD_PREVIOUS;
+ return (p + 1);
+ }
+
+ for (buf = p; *p && ! isspace (*p); p++)
+ continue;
+
+ len = p - buf;
+ if (! len)
+ return (0);
+
+ if (*p) { /* It must be a spacing character! */
+ char *q;
+
+ *p++ = 0;
+ for (q=p; *q && *q != '\n' && *q != '\r'; q++)
+ continue;
+ *q = 0;
+ }
+
+ *cmd = -1;
+ for (c=cmdtab; c->name; ++c) {
+ /* Is it an exact match? */
+ if (! strcasecmp (buf, c->name)) {
+ *cmd = c->command;
+ break;
+ }
+
+ /* Try short hand forms then... */
+ if (len >= c->min && ! strncasecmp (buf, c->name, len)) {
+ if (*cmd != -1 && *cmd != c->command) {
+ warnx("ambiguous command");
+ return (0);
+ }
+ *cmd = c->command;
+ }
+ }
+
+ if (*cmd == -1) {
+ warnx("invalid command, enter ``help'' for commands");
+ return (0);
+ }
+
+ while (isspace (*p))
+ p++;
+ return p;
+}
+
+static int
+open_cd(void)
+{
+ char devbuf[MAXPATHLEN];
+ const char *dev;
+
+ if (fd > -1)
+ return (1);
+
+ if (cdname) {
+ if (*cdname == '/') {
+ snprintf (devbuf, MAXPATHLEN, "%s", cdname);
+ } else {
+ snprintf (devbuf, MAXPATHLEN, "%s%s", _PATH_DEV, cdname);
+ }
+ fd = open (dev = devbuf, O_RDONLY);
+ } else {
+ fd = open(dev = "/dev/cdrom", O_RDONLY);
+ if (fd < 0 && errno == ENOENT)
+ fd = open(dev = "/dev/cd0", O_RDONLY);
+ if (fd < 0 && errno == ENOENT)
+ fd = open(dev = "/dev/acd0", O_RDONLY);
+ }
+
+ if (fd < 0) {
+ if (errno == ENXIO) {
+ /* ENXIO has an overloaded meaning here.
+ * The original "Device not configured" should
+ * be interpreted as "No disc in drive %s". */
+ warnx("no disc in drive %s", dev);
+ return (0);
+ }
+ err(1, "%s", dev);
+ }
+ return (1);
+}
diff --git a/usr.sbin/chkgrp/Makefile b/usr.sbin/chkgrp/Makefile
new file mode 100644
index 0000000..a7bfc54
--- /dev/null
+++ b/usr.sbin/chkgrp/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= chkgrp
+MAN= chkgrp.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/chkgrp/Makefile.depend b/usr.sbin/chkgrp/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/chkgrp/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/chkgrp/chkgrp.8 b/usr.sbin/chkgrp/chkgrp.8
new file mode 100644
index 0000000..c0d7156
--- /dev/null
+++ b/usr.sbin/chkgrp/chkgrp.8
@@ -0,0 +1,93 @@
+.\" Copyright (c) 1998 Dag-Erling Coïdan Smørgrav
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer
+.\" in this position and unchanged.
+.\" 2. Redistributions in binary form must 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 May 26, 2005
+.Dt CHKGRP 8
+.Os
+.Sh NAME
+.Nm chkgrp
+.Nd check the syntax of the group file
+.Sh SYNOPSIS
+.Nm
+.Op Fl q
+.Op Ar groupfile
+.Sh DESCRIPTION
+The
+.Nm
+utility
+scans the given file or, failing that, the system-wide group file for
+errors.
+Specifically, it checks that every non-blank, non-comment
+entry is composed of four colon-separated fields, that none of them
+contains whitespace, and that the third field (the group ID) is
+numeric.
+It will also check for invalid characters in the group names
+and group members.
+The following options are available:
+.Bl -tag -width indent
+.It Fl q
+This option disables printing of text when the group format
+is correct.
+.El
+.Sh FILES
+.Bl -tag -width /etc/group -compact
+.It Pa /etc/group
+group database file
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility returns
+.Dv EX_DATAERR
+if errors were found in the group file,
+and
+.Dv EX_OK
+otherwise.
+.Sh DIAGNOSTICS
+For each error found,
+.Nm
+will print an error message containing the name of the file being
+scanned and the line number on which the error was found.
+.Sh SEE ALSO
+.Xr getgrent 3 ,
+.Xr group 5
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 3.0 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility and this manual page were written by
+.An Dag-Erling Sm\(/orgrav Aq Mt des@FreeBSD.org .
+Further functionality was added by
+.An Liam J. Foy Aq Mt liamfoy@dragonflybsd.org .
+.Sh BUGS
+Should check the range of the group ID.
diff --git a/usr.sbin/chkgrp/chkgrp.c b/usr.sbin/chkgrp/chkgrp.c
new file mode 100644
index 0000000..81cb83b
--- /dev/null
+++ b/usr.sbin/chkgrp/chkgrp.c
@@ -0,0 +1,193 @@
+/*-
+ * Copyright (c) 1998 Dag-Erling Coïdan Smørgrav
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <err.h>
+#include <errno.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sysexits.h>
+
+static void __dead2
+usage(void)
+{
+
+ fprintf(stderr, "usage: chkgrp [-q] [groupfile]\n");
+ exit(EX_USAGE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *gf;
+ unsigned long gid;
+ unsigned int i;
+ size_t len;
+ int opt, quiet;
+ int n = 0, k, e = 0;
+ const char *cp, *f[4], *gfn, *p;
+ char *line;
+
+ quiet = 0;
+ while ((opt = getopt(argc, argv, "q")) != -1) {
+ switch (opt) {
+ case 'q':
+ quiet = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0)
+ gfn = "/etc/group";
+ else if (argc == 1)
+ gfn = argv[0];
+ else
+ usage();
+
+ /* open group file */
+ if ((gf = fopen(gfn, "r")) == NULL)
+ err(EX_NOINPUT, "%s", gfn);
+
+ /* check line by line */
+ while (++n) {
+ if ((line = fgetln(gf, &len)) == NULL)
+ break;
+ if (len > 0 && line[len - 1] != '\n') {
+ warnx("%s: line %d: no newline character", gfn, n);
+ e = 1;
+ }
+ while (len && isspace(line[len-1]))
+ len--;
+
+ /* ignore blank lines and comments */
+ for (p = line; p < line + len; p++)
+ if (!isspace(*p)) break;
+ if (!len || *p == '#')
+ continue;
+
+ /*
+ * Hack: special case for + line
+ */
+ if (strncmp(line, "+:::", len) == 0 ||
+ strncmp(line, "+:*::", len) == 0)
+ continue;
+
+ /*
+ * A correct group entry has four colon-separated fields,
+ * the third of which must be entirely numeric and the
+ * fourth of which may be empty.
+ */
+ for (i = k = 0; k < 4; k++) {
+ for (f[k] = line + i; i < len && line[i] != ':'; i++)
+ /* nothing */ ;
+ if (k < 3 && line[i] != ':')
+ break;
+ line[i++] = 0;
+ }
+
+ if (k < 4) {
+ warnx("%s: line %d: missing field(s)", gfn, n);
+ while (k < 4)
+ f[k++] = "";
+ e = 1;
+ }
+
+ for (cp = f[0] ; *cp ; cp++) {
+ if (!isalnum(*cp) && *cp != '.' && *cp != '_' &&
+ *cp != '-' && (cp > f[0] || *cp != '+')) {
+ warnx("%s: line %d: '%c' invalid character",
+ gfn, n, *cp);
+ e = 1;
+ }
+ }
+
+ for (cp = f[3] ; *cp ; cp++) {
+ if (!isalnum(*cp) && *cp != '.' && *cp != '_' &&
+ *cp != '-' && *cp != ',') {
+ warnx("%s: line %d: '%c' invalid character",
+ gfn, n, *cp);
+ e = 1;
+ }
+ }
+
+ /* check if fourth field ended with a colon */
+ if (i < len) {
+ warnx("%s: line %d: too many fields", gfn, n);
+ e = 1;
+ }
+
+ /* check that none of the fields contain whitespace */
+ for (k = 0; k < 4; k++) {
+ if (strcspn(f[k], " \t") != strlen(f[k])) {
+ warnx("%s: line %d: field %d contains whitespace",
+ gfn, n, k+1);
+ e = 1;
+ }
+ }
+
+ /* check that the GID is numeric */
+ if (strspn(f[2], "0123456789") != strlen(f[2])) {
+ warnx("%s: line %d: group id is not numeric", gfn, n);
+ e = 1;
+ }
+
+ /* check the range of the group id */
+ errno = 0;
+ gid = strtoul(f[2], NULL, 10);
+ if (errno != 0) {
+ warnx("%s: line %d: strtoul failed", gfn, n);
+ } else if (gid > GID_MAX) {
+ warnx("%s: line %d: group id is too large (%ju > %ju)",
+ gfn, n, (uintmax_t)gid, (uintmax_t)GID_MAX);
+ e = 1;
+ }
+ }
+
+ /* check what broke the loop */
+ if (ferror(gf))
+ err(EX_IOERR, "%s: line %d", gfn, n);
+
+ /* done */
+ fclose(gf);
+ if (e == 0 && quiet == 0)
+ printf("%s is fine\n", gfn);
+ exit(e ? EX_DATAERR : EX_OK);
+}
diff --git a/usr.sbin/chown/Makefile b/usr.sbin/chown/Makefile
new file mode 100644
index 0000000..7bcb677
--- /dev/null
+++ b/usr.sbin/chown/Makefile
@@ -0,0 +1,14 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= chown
+LINKS= ${BINDIR}/chown /usr/bin/chgrp
+MAN= chgrp.1 chown.8
+
+.if ${MK_TESTS} != "no"
+SUBDIR+= tests
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/chown/Makefile.depend b/usr.sbin/chown/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/chown/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/chown/chgrp.1 b/usr.sbin/chown/chgrp.1
new file mode 100644
index 0000000..6fb0a31
--- /dev/null
+++ b/usr.sbin/chown/chgrp.1
@@ -0,0 +1,150 @@
+.\" Copyright (c) 1983, 1990, 1993, 1994
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, 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.
+.\"
+.\" @(#)chgrp.1 8.3 (Berkeley) 3/31/94
+.\" $FreeBSD$
+.\"
+.Dd April 20, 2015
+.Dt CHGRP 1
+.Os
+.Sh NAME
+.Nm chgrp
+.Nd change group
+.Sh SYNOPSIS
+.Nm
+.Op Fl fhvx
+.Oo
+.Fl R
+.Op Fl H | Fl L | Fl P
+.Oc
+.Ar group
+.Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility sets the group ID of the file named by each
+.Ar file
+operand to the
+.Ar group
+ID specified by the group operand.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl H
+If the
+.Fl R
+option is specified, symbolic links on the command line are followed
+and hence unaffected by the command.
+(Symbolic links encountered during traversal are not followed.)
+.It Fl L
+If the
+.Fl R
+option is specified, all symbolic links are followed.
+.It Fl P
+If the
+.Fl R
+option is specified, no symbolic links are followed.
+This is the default.
+.It Fl R
+Change the group ID of the file hierarchies rooted in the files,
+instead of just the files themselves.
+Beware of unintentionally matching the
+.Dq Pa ".."
+hard link to the parent directory when using wildcards like
+.Dq Li ".*" .
+.It Fl f
+The force option ignores errors, except for usage errors and does not
+query about strange modes (unless the user does not have proper permissions).
+.It Fl h
+If the file is a symbolic link, the group ID of the link itself is changed
+rather than the file that is pointed to.
+.It Fl v
+Cause
+.Nm
+to be verbose, showing files as the group is modified.
+If the
+.Fl v
+flag is specified more than once,
+.Nm
+will print the filename, followed by the old and new numeric group ID.
+.It Fl x
+File system mount points are not traversed.
+.El
+.Pp
+The
+.Fl H ,
+.Fl L
+and
+.Fl P
+options are ignored unless the
+.Fl R
+option is specified.
+In addition, these options override each other and the
+command's actions are determined by the last one specified.
+.Pp
+The
+.Ar group
+operand can be either a group name from the group database,
+or a numeric group ID.
+If a group name is also a numeric group ID, the operand is used as a
+group name.
+.Pp
+The user invoking
+.Nm
+must belong to the specified group and be the owner of the file,
+or be the super-user.
+.Sh FILES
+.Bl -tag -width /etc/group -compact
+.It Pa /etc/group
+group ID file
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh COMPATIBILITY
+In previous versions of this system, symbolic links did not have groups.
+.Pp
+The
+.Fl v
+and
+.Fl x
+options are non-standard and their use in scripts is not recommended.
+.Sh SEE ALSO
+.Xr chown 2 ,
+.Xr fts 3 ,
+.Xr group 5 ,
+.Xr passwd 5 ,
+.Xr symlink 7 ,
+.Xr chown 8
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2
+compatible.
diff --git a/usr.sbin/chown/chown.8 b/usr.sbin/chown/chown.8
new file mode 100644
index 0000000..6b82728
--- /dev/null
+++ b/usr.sbin/chown/chown.8
@@ -0,0 +1,171 @@
+.\" Copyright (c) 1990, 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.
+.\"
+.\" @(#)chown.8 8.3 (Berkeley) 3/31/94
+.\" $FreeBSD$
+.\"
+.Dd April 20, 2015
+.Dt CHOWN 8
+.Os
+.Sh NAME
+.Nm chown
+.Nd change file owner and group
+.Sh SYNOPSIS
+.Nm
+.Op Fl fhvx
+.Oo
+.Fl R
+.Op Fl H | Fl L | Fl P
+.Oc
+.Ar owner Ns Op : Ns Ar group
+.Ar
+.Nm
+.Op Fl fhvx
+.Oo
+.Fl R
+.Op Fl H | Fl L | Fl P
+.Oc
+.No : Ns Ar group
+.Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility changes the user ID and/or the group ID of the specified files.
+Symbolic links named by arguments are silently left unchanged unless
+.Fl h
+is used.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl H
+If the
+.Fl R
+option is specified, symbolic links on the command line are followed
+and hence unaffected by the command.
+(Symbolic links encountered during traversal are not followed.)
+.It Fl L
+If the
+.Fl R
+option is specified, all symbolic links are followed.
+.It Fl P
+If the
+.Fl R
+option is specified, no symbolic links are followed.
+This is the default.
+.It Fl R
+Change the user ID and/or the group ID of the file hierarchies rooted
+in the files, instead of just the files themselves.
+Beware of unintentionally matching the
+.Dq Pa ".."
+hard link to the parent directory when using wildcards like
+.Dq Li ".*" .
+.It Fl f
+Do not report any failure to change file owner or group, nor modify
+the exit status to reflect such failures.
+.It Fl h
+If the file is a symbolic link, change the user ID and/or the
+group ID of the link itself.
+.It Fl v
+Cause
+.Nm
+to be verbose, showing files as the owner is modified.
+If the
+.Fl v
+flag is specified more than once,
+.Nm
+will print the filename, followed by the old and new numeric user/group ID.
+.It Fl x
+File system mount points are not traversed.
+.El
+.Pp
+The
+.Fl H ,
+.Fl L
+and
+.Fl P
+options are ignored unless the
+.Fl R
+option is specified.
+In addition, these options override each other and the
+command's actions are determined by the last one specified.
+.Pp
+The
+.Ar owner
+and
+.Ar group
+operands are both optional, however, one must be specified.
+If the
+.Ar group
+operand is specified, it must be preceded by a colon (``:'') character.
+.Pp
+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.
+If a group name is also a numeric group ID, the operand is used as a
+group name.
+.Pp
+The ownership of a file may only be altered by a super-user for
+obvious security reasons.
+.Sh EXIT STATUS
+.Ex -std
+.Sh COMPATIBILITY
+Previous versions of the
+.Nm
+utility used the dot (``.'') character to distinguish the group name.
+This has been changed to be a colon (``:'') character so that user and
+group names may contain the dot character.
+.Pp
+On previous versions of this system, symbolic links did not have
+owners.
+.Pp
+The
+.Fl v
+and
+.Fl x
+options are non-standard and their use in scripts is not recommended.
+.Sh SEE ALSO
+.Xr chgrp 1 ,
+.Xr find 1 ,
+.Xr chown 2 ,
+.Xr fts 3 ,
+.Xr symlink 7
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2
+compliant.
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.At v1 .
diff --git a/usr.sbin/chown/chown.c b/usr.sbin/chown/chown.c
new file mode 100644
index 0000000..457068a
--- /dev/null
+++ b/usr.sbin/chown/chown.c
@@ -0,0 +1,317 @@
+/*
+ * Copyright (c) 1988, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 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, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)chown.c 8.8 (Berkeley) 4/4/94";
+#endif /* not lint */
+#endif
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fts.h>
+#include <grp.h>
+#include <libgen.h>
+#include <pwd.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static void a_gid(const char *);
+static void a_uid(const char *);
+static void chownerr(const char *);
+static uid_t id(const char *, const char *);
+static void usage(void);
+
+static uid_t uid;
+static gid_t gid;
+static int ischown;
+static const char *gname;
+
+int
+main(int argc, char **argv)
+{
+ FTS *ftsp;
+ FTSENT *p;
+ int Hflag, Lflag, Rflag, fflag, hflag, vflag, xflag;
+ int ch, fts_options, rval;
+ char *cp;
+
+ ischown = (strcmp(basename(argv[0]), "chown") == 0);
+
+ Hflag = Lflag = Rflag = fflag = hflag = vflag = xflag = 0;
+ while ((ch = getopt(argc, argv, "HLPRfhvx")) != -1)
+ switch (ch) {
+ case 'H':
+ Hflag = 1;
+ Lflag = 0;
+ break;
+ case 'L':
+ Lflag = 1;
+ Hflag = 0;
+ break;
+ case 'P':
+ Hflag = Lflag = 0;
+ break;
+ case 'R':
+ Rflag = 1;
+ break;
+ case 'f':
+ fflag = 1;
+ break;
+ case 'h':
+ hflag = 1;
+ break;
+ case 'v':
+ vflag++;
+ break;
+ case 'x':
+ xflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argv += optind;
+ argc -= optind;
+
+ if (argc < 2)
+ usage();
+
+ if (Rflag) {
+ if (hflag && (Hflag || Lflag))
+ errx(1, "the -R%c and -h options may not be "
+ "specified together", Hflag ? 'H' : 'L');
+ if (Lflag) {
+ fts_options = FTS_LOGICAL;
+ } else {
+ fts_options = FTS_PHYSICAL;
+
+ if (Hflag) {
+ fts_options |= FTS_COMFOLLOW;
+ }
+ }
+ } else if (hflag) {
+ fts_options = FTS_PHYSICAL;
+ } else {
+ fts_options = FTS_LOGICAL;
+ }
+
+ if (xflag)
+ fts_options |= FTS_XDEV;
+
+ uid = (uid_t)-1;
+ gid = (gid_t)-1;
+ if (ischown) {
+ if ((cp = strchr(*argv, ':')) != NULL) {
+ *cp++ = '\0';
+ a_gid(cp);
+ }
+#ifdef SUPPORT_DOT
+ else if ((cp = strchr(*argv, '.')) != NULL) {
+ warnx("separation of user and group with a period is deprecated");
+ *cp++ = '\0';
+ a_gid(cp);
+ }
+#endif
+ a_uid(*argv);
+ } else
+ a_gid(*argv);
+
+ if ((ftsp = fts_open(++argv, fts_options, 0)) == NULL)
+ err(1, NULL);
+
+ for (rval = 0; (p = fts_read(ftsp)) != NULL;) {
+ int atflag;
+
+ if ((fts_options & FTS_LOGICAL) ||
+ ((fts_options & FTS_COMFOLLOW) &&
+ p->fts_level == FTS_ROOTLEVEL))
+ atflag = 0;
+ else
+ atflag = AT_SYMLINK_NOFOLLOW;
+
+ switch (p->fts_info) {
+ case FTS_D: /* Change it at FTS_DP. */
+ if (!Rflag)
+ fts_set(ftsp, p, FTS_SKIP);
+ continue;
+ case FTS_DNR: /* Warn, chown. */
+ warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
+ rval = 1;
+ break;
+ case FTS_ERR: /* Warn, continue. */
+ case FTS_NS:
+ warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
+ rval = 1;
+ continue;
+ default:
+ break;
+ }
+ if ((uid == (uid_t)-1 || uid == p->fts_statp->st_uid) &&
+ (gid == (gid_t)-1 || gid == p->fts_statp->st_gid))
+ continue;
+ if (fchownat(AT_FDCWD, p->fts_accpath, uid, gid, atflag)
+ == -1 && !fflag) {
+ chownerr(p->fts_path);
+ rval = 1;
+ } else if (vflag) {
+ printf("%s", p->fts_path);
+ if (vflag > 1) {
+ if (ischown) {
+ printf(": %ju:%ju -> %ju:%ju",
+ (uintmax_t)
+ p->fts_statp->st_uid,
+ (uintmax_t)
+ p->fts_statp->st_gid,
+ (uid == (uid_t)-1) ?
+ (uintmax_t)
+ p->fts_statp->st_uid :
+ (uintmax_t)uid,
+ (gid == (gid_t)-1) ?
+ (uintmax_t)
+ p->fts_statp->st_gid :
+ (uintmax_t)gid);
+ } else {
+ printf(": %ju -> %ju",
+ (uintmax_t)
+ p->fts_statp->st_gid,
+ (gid == (gid_t)-1) ?
+ (uintmax_t)
+ p->fts_statp->st_gid :
+ (uintmax_t)gid);
+ }
+ }
+ printf("\n");
+ }
+ }
+ if (errno)
+ err(1, "fts_read");
+ exit(rval);
+}
+
+static void
+a_gid(const char *s)
+{
+ struct group *gr;
+
+ if (*s == '\0') /* Argument was "uid[:.]". */
+ return;
+ gname = s;
+ gid = ((gr = getgrnam(s)) != NULL) ? gr->gr_gid : id(s, "group");
+}
+
+static void
+a_uid(const char *s)
+{
+ struct passwd *pw;
+
+ if (*s == '\0') /* Argument was "[:.]gid". */
+ return;
+ uid = ((pw = getpwnam(s)) != NULL) ? pw->pw_uid : id(s, "user");
+}
+
+static uid_t
+id(const char *name, const char *type)
+{
+ uid_t 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 || *ep != '\0')
+ errx(1, "%s: illegal %s name", name, type);
+ return (val);
+}
+
+static void
+chownerr(const char *file)
+{
+ static uid_t euid = -1;
+ static int ngroups = -1;
+ static long ngroups_max;
+ gid_t *groups;
+
+ /* Check for chown without being root. */
+ if (errno != EPERM || (uid != (uid_t)-1 &&
+ euid == (uid_t)-1 && (euid = geteuid()) != 0)) {
+ warn("%s", file);
+ return;
+ }
+
+ /* Check group membership; kernel just returns EPERM. */
+ if (gid != (gid_t)-1 && ngroups == -1 &&
+ euid == (uid_t)-1 && (euid = geteuid()) != 0) {
+ ngroups_max = sysconf(_SC_NGROUPS_MAX) + 1;
+ if ((groups = malloc(sizeof(gid_t) * ngroups_max)) == NULL)
+ err(1, "malloc");
+ ngroups = getgroups(ngroups_max, groups);
+ while (--ngroups >= 0 && gid != groups[ngroups]);
+ free(groups);
+ if (ngroups < 0) {
+ warnx("you are not a member of group %s", gname);
+ return;
+ }
+ }
+ warn("%s", file);
+}
+
+static void
+usage(void)
+{
+
+ if (ischown)
+ (void)fprintf(stderr, "%s\n%s\n",
+ "usage: chown [-fhvx] [-R [-H | -L | -P]] owner[:group]"
+ " file ...",
+ " chown [-fhvx] [-R [-H | -L | -P]] :group file ...");
+ else
+ (void)fprintf(stderr, "%s\n",
+ "usage: chgrp [-fhvx] [-R [-H | -L | -P]] group file ...");
+ exit(1);
+}
diff --git a/usr.sbin/chown/tests/Makefile b/usr.sbin/chown/tests/Makefile
new file mode 100644
index 0000000..fb13f3f
--- /dev/null
+++ b/usr.sbin/chown/tests/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+.include <bsd.own.mk>
+
+TESTSDIR= ${TESTSBASE}/bin/chown
+
+TAP_TESTS_SH= chown-f_test
+
+.include <bsd.test.mk>
diff --git a/usr.sbin/chown/tests/chown-f_test.sh b/usr.sbin/chown/tests/chown-f_test.sh
new file mode 100755
index 0000000..c66b008
--- /dev/null
+++ b/usr.sbin/chown/tests/chown-f_test.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+# $FreeBSD$
+
+base=`basename $0`
+
+echo "1..1"
+
+name="chown -f root:wheel file"
+if [ `id -u` -eq 0 ]; then
+ echo "ok 1 - $name # skip Test must not be uid 0."
+else
+ touch file
+ output=$(chown -f root:wheel file 2>&1)
+ if [ $? -eq 0 -a -z "$output" ]
+ then
+ echo "ok 1 - $name"
+ else
+ echo "not ok 1 - $name"
+ fi
+ rm file
+fi
diff --git a/usr.sbin/chroot/Makefile b/usr.sbin/chroot/Makefile
new file mode 100644
index 0000000..652de79
--- /dev/null
+++ b/usr.sbin/chroot/Makefile
@@ -0,0 +1,7 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+PROG= chroot
+MAN= chroot.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/chroot/Makefile.depend b/usr.sbin/chroot/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/chroot/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/chroot/chroot.8 b/usr.sbin/chroot/chroot.8
new file mode 100644
index 0000000..e5f9f44
--- /dev/null
+++ b/usr.sbin/chroot/chroot.8
@@ -0,0 +1,94 @@
+.\" 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.
+.\"
+.\" @(#)chroot.8 8.1 (Berkeley) 6/9/93
+.\" $FreeBSD$
+.\"
+.Dd June 7, 2003
+.Dt CHROOT 8
+.Os
+.Sh NAME
+.Nm chroot
+.Nd change root directory
+.Sh SYNOPSIS
+.Nm
+.Op Fl u Ar user
+.Op Fl g Ar group
+.Op Fl G Ar group,group,...
+.Ar newroot
+.Op Ar command
+.Sh DESCRIPTION
+The
+.Nm
+utility changes its current and root directories to the supplied directory
+.Ar newroot
+and then exec's
+.Ar command ,
+if supplied,
+or an interactive copy of the user's login shell.
+.Pp
+If the
+.Fl u ,
+.Fl g
+or
+.Fl G
+options are given,
+the user,
+group and group list of the process are set to
+these values after the
+.Nm
+has taken place.
+.Sh ENVIRONMENT
+The following environment variable is referenced by
+.Nm :
+.Bl -tag -width ".Ev SHELL"
+.It Ev SHELL
+If set,
+the string specified by
+.Ev SHELL
+is interpreted as the name of
+the shell to exec.
+If the variable
+.Ev SHELL
+is not set,
+.Pa /bin/sh
+is used.
+.El
+.Sh SEE ALSO
+.Xr chdir 2 ,
+.Xr chroot 2 ,
+.Xr setgid 2 ,
+.Xr setgroups 2 ,
+.Xr setuid 2 ,
+.Xr getgrnam 3 ,
+.Xr environ 7 ,
+.Xr jail 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Bx 4.4 .
diff --git a/usr.sbin/chroot/chroot.c b/usr.sbin/chroot/chroot.c
new file mode 100644
index 0000000..9db0192
--- /dev/null
+++ b/usr.sbin/chroot/chroot.c
@@ -0,0 +1,182 @@
+/*
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1988, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)chroot.c 8.1 (Berkeley) 6/9/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <grp.h>
+#include <limits.h>
+#include <paths.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ struct group *gp;
+ struct passwd *pw;
+ char *endp, *p, *user, *group, *grouplist;
+ const char *shell;
+ gid_t gid, *gidlist;
+ uid_t uid;
+ int ch, gids;
+ long ngroups_max;
+
+ gid = 0;
+ uid = 0;
+ user = group = grouplist = NULL;
+ while ((ch = getopt(argc, argv, "G:g:u:")) != -1) {
+ switch(ch) {
+ case 'u':
+ user = optarg;
+ if (*user == '\0')
+ usage();
+ break;
+ case 'g':
+ group = optarg;
+ if (*group == '\0')
+ usage();
+ break;
+ case 'G':
+ grouplist = optarg;
+ if (*grouplist == '\0')
+ usage();
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ usage();
+
+ if (group != NULL) {
+ if (isdigit((unsigned char)*group)) {
+ gid = (gid_t)strtoul(group, &endp, 0);
+ if (*endp != '\0')
+ goto getgroup;
+ } else {
+ getgroup:
+ if ((gp = getgrnam(group)) != NULL)
+ gid = gp->gr_gid;
+ else
+ errx(1, "no such group `%s'", group);
+ }
+ }
+
+ ngroups_max = sysconf(_SC_NGROUPS_MAX) + 1;
+ if ((gidlist = malloc(sizeof(gid_t) * ngroups_max)) == NULL)
+ err(1, "malloc");
+ for (gids = 0;
+ (p = strsep(&grouplist, ",")) != NULL && gids < ngroups_max; ) {
+ if (*p == '\0')
+ continue;
+
+ if (isdigit((unsigned char)*p)) {
+ gidlist[gids] = (gid_t)strtoul(p, &endp, 0);
+ if (*endp != '\0')
+ goto getglist;
+ } else {
+ getglist:
+ if ((gp = getgrnam(p)) != NULL)
+ gidlist[gids] = gp->gr_gid;
+ else
+ errx(1, "no such group `%s'", p);
+ }
+ gids++;
+ }
+ if (p != NULL && gids == ngroups_max)
+ errx(1, "too many supplementary groups provided");
+
+ if (user != NULL) {
+ if (isdigit((unsigned char)*user)) {
+ uid = (uid_t)strtoul(user, &endp, 0);
+ if (*endp != '\0')
+ goto getuser;
+ } else {
+ getuser:
+ if ((pw = getpwnam(user)) != NULL)
+ uid = pw->pw_uid;
+ else
+ errx(1, "no such user `%s'", user);
+ }
+ }
+
+ if (chdir(argv[0]) == -1 || chroot(".") == -1)
+ err(1, "%s", argv[0]);
+
+ if (gids && setgroups(gids, gidlist) == -1)
+ err(1, "setgroups");
+ if (group && setgid(gid) == -1)
+ err(1, "setgid");
+ if (user && setuid(uid) == -1)
+ err(1, "setuid");
+
+ if (argv[1]) {
+ execvp(argv[1], &argv[1]);
+ err(1, "%s", argv[1]);
+ }
+
+ if (!(shell = getenv("SHELL")))
+ shell = _PATH_BSHELL;
+ execlp(shell, shell, "-i", (char *)NULL);
+ err(1, "%s", shell);
+ /* NOTREACHED */
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: chroot [-g group] [-G group,group,...] "
+ "[-u user] newroot [command]\n");
+ exit(1);
+}
diff --git a/usr.sbin/ckdist/Makefile b/usr.sbin/ckdist/Makefile
new file mode 100644
index 0000000..4d35763
--- /dev/null
+++ b/usr.sbin/ckdist/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../usr.bin/cksum
+
+PROG= ckdist
+SRCS= ckdist.c crc.c
+
+LIBADD= md
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ckdist/Makefile.depend b/usr.sbin/ckdist/Makefile.depend
new file mode 100644
index 0000000..064e492
--- /dev/null
+++ b/usr.sbin/ckdist/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libmd \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ckdist/ckdist.1 b/usr.sbin/ckdist/ckdist.1
new file mode 100644
index 0000000..86175c9
--- /dev/null
+++ b/usr.sbin/ckdist/ckdist.1
@@ -0,0 +1,132 @@
+.\" Copyright (c) 1997 Robert Nordier
+.\" All rights reserved.
+.\"
+.\" $FreeBSD$
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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.
+.\"
+.Dd January 20, 1997
+.Dt CKDIST 1
+.Os
+.Sh NAME
+.Nm ckdist
+.Nd check software distributions
+.Sh SYNOPSIS
+.Nm
+.Bq Fl airsx
+.Bq Fl d Ar dir
+.Bq Fl n Ar name
+.Bq Fl t Ar type
+.Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility reads
+.Dq checksum
+files (which are assumed to specify components
+of a software distribution) and verifies the integrity of the
+distribution by validating the checksum of each component file.
+Both MD5 (128-bit
+.Dq "message digest" )
+and
+.Pa .inf
+(32-bit CRC) checksum
+formats are supported.
+.Pp
+The
+.Ar file
+operands may refer to regular files or to directories.
+Regular files
+named
+.Pa md5 ,
+or which have an
+.Pa .md5
+or an
+.Pa .inf
+extension, are
+assumed to be of the implied type, otherwise format is determined from
+content.
+If a directory is specified, it is searched for
+appropriately-named files only.
+.Pp
+The options are as follows:
+.\"Bl -tag -width ".Fl n Ar name"
+.Bl -tag -width 8n -offset indent
+.It Fl a
+Report on all distribution components, not just those in respect of
+which errors are detected.
+.It Fl i
+Ignore missing distribution components.
+.It Fl r
+Search specified directories recursively.
+.It Fl s
+Suppress complaints about inaccessible checksum files and directories.
+.It Fl x
+Verify the existence of distribution components (and also check sizes,
+in the case of
+.Pa .inf
+files), but omit the more time-consuming step of
+actually computing and comparing checksums.
+.It Fl d Ar dir
+Look for distribution components in the directory
+.Ar dir .
+.It Fl n Ar name
+Access distribution components using the filename
+.Ar name .
+When accessing
+.Pa .inf
+file components, append the appropriate
+extension to the filename.
+.It Fl t Ar type
+Assume that all specified checksum files are of the format
+.Ar type ,
+and search directories only for files in this format (where
+.Ar type
+is either
+.Cm md5
+or
+.Cm inf ) .
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility exits with one of the following values:
+.Bl -tag -width indent
+.It 0
+No errors were detected.
+.It 1
+Errors were found in a distribution.
+.It 2
+Usage errors, inaccessible input files, or
+other system errors were encountered.
+.El
+.Sh SEE ALSO
+.Xr cksum 1 ,
+.Xr md5 1
+.Sh NOTES
+Both
+.Bx
+and
+.Tn DOS
+versions of
+.Nm
+are available.
diff --git a/usr.sbin/ckdist/ckdist.c b/usr.sbin/ckdist/ckdist.c
new file mode 100644
index 0000000..dcaa9f3
--- /dev/null
+++ b/usr.sbin/ckdist/ckdist.c
@@ -0,0 +1,445 @@
+/*
+ * Copyright (c) 1997 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/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fts.h>
+#include <md5.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+extern int crc(int fd, uint32_t *cval, off_t *clen);
+
+#define DISTMD5 1 /* MD5 format */
+#define DISTINF 2 /* .inf format */
+#define DISTTYPES 2 /* types supported */
+
+#define E_UNKNOWN 1 /* Unknown format */
+#define E_BADMD5 2 /* Invalid MD5 format */
+#define E_BADINF 3 /* Invalid .inf format */
+#define E_NAME 4 /* Can't derive component name */
+#define E_LENGTH 5 /* Length mismatch */
+#define E_CHKSUM 6 /* Checksum mismatch */
+#define E_ERRNO 7 /* sys_errlist[errno] */
+
+#define isfatal(err) ((err) && (err) <= E_NAME)
+
+#define NAMESIZE 256 /* filename buffer size */
+#define MDSUMLEN 32 /* length of MD5 message digest */
+
+#define isstdin(path) ((path)[0] == '-' && !(path)[1])
+
+static const char *opt_dir; /* where to look for components */
+static const char *opt_name; /* name for accessing components */
+static int opt_all; /* report on all components */
+static int opt_ignore; /* ignore missing components */
+static int opt_recurse; /* search directories recursively */
+static int opt_silent; /* silent about inaccessible files */
+static int opt_type; /* dist type: md5 or inf */
+static int opt_exist; /* just verify existence */
+
+static int ckdist(const char *path, int type);
+static int chkmd5(FILE * fp, const char *path);
+static int chkinf(FILE * fp, const char *path);
+static int report(const char *path, const char *name, int error);
+static const char *distname(const char *path, const char *name,
+ const char *ext);
+static const char *stripath(const char *path);
+static int distfile(const char *path);
+static int disttype(const char *name);
+static int fail(const char *path, const char *msg);
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ static char *arg[2];
+ struct stat sb;
+ FTS *ftsp;
+ FTSENT *f;
+ int rval, c, type;
+
+ while ((c = getopt(argc, argv, "ad:in:rst:x")) != -1)
+ switch (c) {
+ case 'a':
+ opt_all = 1;
+ break;
+ case 'd':
+ opt_dir = optarg;
+ break;
+ case 'i':
+ opt_ignore = 1;
+ break;
+ case 'n':
+ opt_name = optarg;
+ break;
+ case 'r':
+ opt_recurse = 1;
+ break;
+ case 's':
+ opt_silent = 1;
+ break;
+ case 't':
+ if ((opt_type = disttype(optarg)) == 0) {
+ warnx("illegal argument to -t option");
+ usage();
+ }
+ break;
+ case 'x':
+ opt_exist = 1;
+ break;
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc < 1)
+ usage();
+ if (opt_dir) {
+ if (stat(opt_dir, &sb))
+ err(2, "%s", opt_dir);
+ if (!S_ISDIR(sb.st_mode))
+ errx(2, "%s: not a directory", opt_dir);
+ }
+ rval = 0;
+ do {
+ if (isstdin(*argv))
+ rval |= ckdist(*argv, opt_type);
+ else if (stat(*argv, &sb))
+ rval |= fail(*argv, NULL);
+ else if (S_ISREG(sb.st_mode))
+ rval |= ckdist(*argv, opt_type);
+ else {
+ arg[0] = *argv;
+ if ((ftsp = fts_open(arg, FTS_LOGICAL, NULL)) == NULL)
+ err(2, "fts_open");
+ while ((f = fts_read(ftsp)) != NULL)
+ switch (f->fts_info) {
+ case FTS_DC:
+ rval = fail(f->fts_path, "Directory causes a cycle");
+ break;
+ case FTS_DNR:
+ case FTS_ERR:
+ case FTS_NS:
+ rval = fail(f->fts_path, sys_errlist[f->fts_errno]);
+ break;
+ case FTS_D:
+ if (!opt_recurse && f->fts_level > FTS_ROOTLEVEL &&
+ fts_set(ftsp, f, FTS_SKIP))
+ err(2, "fts_set");
+ break;
+ case FTS_F:
+ if ((type = distfile(f->fts_name)) != 0 &&
+ (!opt_type || type == opt_type))
+ rval |= ckdist(f->fts_path, type);
+ break;
+ default: ;
+ }
+ if (errno)
+ err(2, "fts_read");
+ if (fts_close(ftsp))
+ err(2, "fts_close");
+ }
+ } while (*++argv);
+ return rval;
+}
+
+static int
+ckdist(const char *path, int type)
+{
+ FILE *fp;
+ int rval, c;
+
+ if (isstdin(path)) {
+ path = "(stdin)";
+ fp = stdin;
+ } else if ((fp = fopen(path, "r")) == NULL)
+ return fail(path, NULL);
+ if (!type) {
+ if (fp != stdin)
+ type = distfile(path);
+ if (!type)
+ if ((c = fgetc(fp)) != EOF) {
+ type = c == 'M' ? DISTMD5 : c == 'P' ? DISTINF : 0;
+ (void)ungetc(c, fp);
+ }
+ }
+ switch (type) {
+ case DISTMD5:
+ rval = chkmd5(fp, path);
+ break;
+ case DISTINF:
+ rval = chkinf(fp, path);
+ break;
+ default:
+ rval = report(path, NULL, E_UNKNOWN);
+ }
+ if (ferror(fp))
+ warn("%s", path);
+ if (fp != stdin && fclose(fp))
+ err(2, "%s", path);
+ return rval;
+}
+
+static int
+chkmd5(FILE * fp, const char *path)
+{
+ char buf[298]; /* "MD5 (NAMESIZE = MDSUMLEN" */
+ char name[NAMESIZE + 1];
+ char sum[MDSUMLEN + 1], chk[MDSUMLEN + 1];
+ const char *dname;
+ char *s;
+ int rval, error, c, fd;
+ char ch;
+
+ rval = 0;
+ while (fgets(buf, sizeof(buf), fp)) {
+ dname = NULL;
+ error = 0;
+ if (((c = sscanf(buf, "MD5 (%256s = %32s%c", name, sum,
+ &ch)) != 3 && (!feof(fp) || c != 2)) ||
+ (c == 3 && ch != '\n') ||
+ (s = strrchr(name, ')')) == NULL ||
+ strlen(sum) != MDSUMLEN)
+ error = E_BADMD5;
+ else {
+ *s = 0;
+ if ((dname = distname(path, name, NULL)) == NULL)
+ error = E_NAME;
+ else if (opt_exist) {
+ if ((fd = open(dname, O_RDONLY)) == -1)
+ error = E_ERRNO;
+ else if (close(fd))
+ err(2, "%s", dname);
+ } else if (!MD5File(dname, chk))
+ error = E_ERRNO;
+ else if (strcmp(chk, sum))
+ error = E_CHKSUM;
+ }
+ if (opt_ignore && error == E_ERRNO && errno == ENOENT)
+ continue;
+ if (error || opt_all)
+ rval |= report(path, dname, error);
+ if (isfatal(error))
+ break;
+ }
+ return rval;
+}
+
+static int
+chkinf(FILE * fp, const char *path)
+{
+ char buf[30]; /* "cksum.2 = 10 6" */
+ char ext[3];
+ struct stat sb;
+ const char *dname;
+ off_t len;
+ u_long sum;
+ intmax_t sumlen;
+ uint32_t chk;
+ int rval, error, c, pieces, cnt, fd;
+ char ch;
+
+ rval = 0;
+ for (cnt = -1; fgets(buf, sizeof(buf), fp); cnt++) {
+ fd = -1;
+ dname = NULL;
+ error = 0;
+ if (cnt == -1) {
+ if ((c = sscanf(buf, "Pieces = %d%c", &pieces, &ch)) != 2 ||
+ ch != '\n' || pieces < 1)
+ error = E_BADINF;
+ } else if (((c = sscanf(buf, "cksum.%2s = %lu %jd%c", ext, &sum,
+ &sumlen, &ch)) != 4 &&
+ (!feof(fp) || c != 3)) || (c == 4 && ch != '\n') ||
+ ext[0] != 'a' + cnt / 26 || ext[1] != 'a' + cnt % 26)
+ error = E_BADINF;
+ else if ((dname = distname(fp == stdin ? NULL : path, NULL,
+ ext)) == NULL)
+ error = E_NAME;
+ else if ((fd = open(dname, O_RDONLY)) == -1)
+ error = E_ERRNO;
+ else if (fstat(fd, &sb))
+ error = E_ERRNO;
+ else if (sb.st_size != (off_t)sumlen)
+ error = E_LENGTH;
+ else if (!opt_exist) {
+ if (crc(fd, &chk, &len))
+ error = E_ERRNO;
+ else if (chk != sum)
+ error = E_CHKSUM;
+ }
+ if (fd != -1 && close(fd))
+ err(2, "%s", dname);
+ if (opt_ignore && error == E_ERRNO && errno == ENOENT)
+ continue;
+ if (error || (opt_all && cnt >= 0))
+ rval |= report(path, dname, error);
+ if (isfatal(error))
+ break;
+ }
+ return rval;
+}
+
+static int
+report(const char *path, const char *name, int error)
+{
+ if (name)
+ name = stripath(name);
+ switch (error) {
+ case E_UNKNOWN:
+ printf("%s: Unknown format\n", path);
+ break;
+ case E_BADMD5:
+ printf("%s: Invalid MD5 format\n", path);
+ break;
+ case E_BADINF:
+ printf("%s: Invalid .inf format\n", path);
+ break;
+ case E_NAME:
+ printf("%s: Can't derive component name\n", path);
+ break;
+ case E_LENGTH:
+ printf("%s: %s: Size mismatch\n", path, name);
+ break;
+ case E_CHKSUM:
+ printf("%s: %s: Checksum mismatch\n", path, name);
+ break;
+ case E_ERRNO:
+ printf("%s: %s: %s\n", path, name, sys_errlist[errno]);
+ break;
+ default:
+ printf("%s: %s: OK\n", path, name);
+ }
+ return error != 0;
+}
+
+static const char *
+distname(const char *path, const char *name, const char *ext)
+{
+ static char buf[NAMESIZE];
+ size_t plen, nlen;
+ char *s;
+
+ if (opt_name)
+ name = opt_name;
+ else if (!name) {
+ if (!path)
+ return NULL;
+ name = stripath(path);
+ }
+ nlen = strlen(name);
+ if (ext && nlen > 4 && name[nlen - 4] == '.' &&
+ disttype(name + nlen - 3) == DISTINF)
+ nlen -= 4;
+ if (opt_dir) {
+ path = opt_dir;
+ plen = strlen(path);
+ } else
+ plen = path && (s = strrchr(path, '/')) != NULL ?
+ (size_t)(s - path) : 0;
+ if (plen + (plen > 0) + nlen + (ext ? 3 : 0) >= sizeof(buf))
+ return NULL;
+ s = buf;
+ if (plen) {
+ memcpy(s, path, plen);
+ s += plen;
+ *s++ = '/';
+ }
+ memcpy(s, name, nlen);
+ s += nlen;
+ if (ext) {
+ *s++ = '.';
+ memcpy(s, ext, 2);
+ s += 2;
+ }
+ *s = 0;
+ return buf;
+}
+
+static const char *
+stripath(const char *path)
+{
+ const char *s;
+
+ return ((s = strrchr(path, '/')) != NULL && s[1] ?
+ s + 1 : path);
+}
+
+static int
+distfile(const char *path)
+{
+ const char *s;
+ int type;
+
+ if ((type = disttype(path)) == DISTMD5 ||
+ ((s = strrchr(path, '.')) != NULL && s > path &&
+ (type = disttype(s + 1)) != 0))
+ return type;
+ return 0;
+}
+
+static int
+disttype(const char *name)
+{
+ static const char dname[DISTTYPES][4] = {"md5", "inf"};
+ int i;
+
+ for (i = 0; i < DISTTYPES; i++)
+ if (!strcmp(dname[i], name))
+ return 1 + i;
+ return 0;
+}
+
+static int
+fail(const char *path, const char *msg)
+{
+ if (opt_silent)
+ return 0;
+ warnx("%s: %s", path, msg ? msg : sys_errlist[errno]);
+ return 2;
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: ckdist [-airsx] [-d dir] [-n name] [-t type] file ...\n");
+ exit(2);
+}
diff --git a/usr.sbin/clear_locks/Makefile b/usr.sbin/clear_locks/Makefile
new file mode 100644
index 0000000..6906bf0
--- /dev/null
+++ b/usr.sbin/clear_locks/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= clear_locks
+MAN= clear_locks.8
+LIBADD= rpcsvc
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/clear_locks/Makefile.depend b/usr.sbin/clear_locks/Makefile.depend
new file mode 100644
index 0000000..c8383bd
--- /dev/null
+++ b/usr.sbin/clear_locks/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/librpcsvc \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/clear_locks/clear_locks.8 b/usr.sbin/clear_locks/clear_locks.8
new file mode 100644
index 0000000..6b601ea
--- /dev/null
+++ b/usr.sbin/clear_locks/clear_locks.8
@@ -0,0 +1,51 @@
+.\" Copyright (c) 2008 Isilon Inc http://www.isilon.com/
+.\" Authors: Doug Rabson <dfr@rabson.org>
+.\" Developed with Red Inc: Alfred Perlstein <alfred@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$
+.\"
+.Dd March 19, 2008
+.Dt CLEAR_LOCKS 8
+.Os
+.Sh NAME
+.Nm clear_locks
+.Nd clear locks held on behalf of an NFS client
+.Sh SYNOPSIS
+.Nm
+.Ar hostname
+.Sh DESCRIPTION
+The
+.Nm
+command can be used to clear file locks held by an NFS client.
+This should only be used to handle problems caused by an NFS client
+crashing while holding locks and failing to clear them itself when it
+reboots.
+.Sh SEE ALSO
+.Xr rpc.lockd 8
+.Sh HISTORY
+A version of
+.Nm
+appeared in
+.Tn SunOS
+4.
diff --git a/usr.sbin/clear_locks/clear_locks.c b/usr.sbin/clear_locks/clear_locks.c
new file mode 100644
index 0000000..1249c12
--- /dev/null
+++ b/usr.sbin/clear_locks/clear_locks.c
@@ -0,0 +1,70 @@
+/*-
+ * Copyright (c) 2008 Isilon Inc http://www.isilon.com/
+ * Authors: Doug Rabson <dfr@rabson.org>
+ * Developed with Red Inc: Alfred Perlstein <alfred@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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <rpc/rpc.h>
+#include <rpcsvc/nlm_prot.h>
+
+int
+main(int argc, char **argv)
+{
+ enum clnt_stat stat;
+ char *hostname;
+ nlm4_notify notify;
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: clear_locks <hostname>\n");
+ exit(1);
+ }
+ hostname = argv[1];
+
+ if (geteuid() != 0) {
+ fprintf(stderr, "clear_locks: must be root\n");
+ exit(1);
+ }
+
+ notify.name = hostname;
+ notify.state = 0;
+ stat = rpc_call("localhost", NLM_PROG, NLM_VERS4, NLM4_FREE_ALL,
+ (xdrproc_t) xdr_nlm4_notify, (void *) &notify,
+ (xdrproc_t) xdr_void, NULL, NULL);
+
+ if (stat != RPC_SUCCESS) {
+ clnt_perrno(stat);
+ exit(1);
+ }
+ fprintf(stderr, "clear_locks: cleared locks for hostname %s\n",
+ hostname);
+
+ return (0);
+}
diff --git a/usr.sbin/config/Makefile b/usr.sbin/config/Makefile
new file mode 100644
index 0000000..76712d2
--- /dev/null
+++ b/usr.sbin/config/Makefile
@@ -0,0 +1,22 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+PROG= config
+MAN= config.5 config.8
+SRCS= config.y main.c lang.l mkmakefile.c mkheaders.c \
+ mkoptions.c y.tab.h kernconf.c
+
+kernconf.c: kernconf.tmpl
+ file2c 'char kernconfstr[] = {' ',0};' < ${.CURDIR}/kernconf.tmpl > kernconf.c
+
+CFLAGS+= -I. -I${.CURDIR}
+
+NO_WMISSING_VARIABLE_DECLARATIONS=
+
+LIBADD= l sbuf
+
+CLEANFILES+= kernconf.c
+
+mkmakefile.o: configvers.h
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/config/Makefile.depend b/usr.sbin/config/Makefile.depend
new file mode 100644
index 0000000..b443a43
--- /dev/null
+++ b/usr.sbin/config/Makefile.depend
@@ -0,0 +1,37 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libsbuf \
+ usr.bin/lex/lib \
+ usr.bin/yacc.host \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+config.o: config.c
+config.po: config.c
+kernconf.o: kernconf.c
+kernconf.po: kernconf.c
+lang.o: lang.c
+lang.o: y.tab.h
+lang.po: lang.c
+lang.po: y.tab.h
+main.o: y.tab.h
+main.po: y.tab.h
+mkheaders.o: y.tab.h
+mkheaders.po: y.tab.h
+mkmakefile.o: y.tab.h
+mkmakefile.po: y.tab.h
+mkoptions.o: y.tab.h
+mkoptions.po: y.tab.h
+.endif
diff --git a/usr.sbin/config/config.5 b/usr.sbin/config/config.5
new file mode 100644
index 0000000..dfc9fc8
--- /dev/null
+++ b/usr.sbin/config/config.5
@@ -0,0 +1,405 @@
+.\" Copyright (c) 2003 Joseph Koshy
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 3, 2005
+.Dt CONFIG 5
+.Os
+.Sh NAME
+.Nm config
+.Nd kernel configuration file format
+.Sh DESCRIPTION
+A kernel configuration file specifies the configuration of a
+.Fx
+kernel.
+It is processed by
+.Xr config 8
+to create a build environment where a kernel may be built using
+.Xr make 1 .
+.Ss Lexical Structure
+A kernel configuration file comprises a sequence of specification
+directives.
+.Pp
+A specification directive starts with a keyword at the beginning
+of the line and is followed by additional parameters.
+.Pp
+A specification directive may be terminated by a semicolon
+.Ql \&;
+or by a newline.
+Long input lines may be broken into shorter lines by starting the
+second and subsequent lines with a white space character.
+.Pp
+Case is significant,
+.Dq Li machine
+and
+.Dq Li MACHINE
+are different tokens.
+.Pp
+A double quote character
+.Ql \[dq]
+starts a quoted string.
+All characters up to the next quote character form the value
+of the quoted string.
+A
+.Ql \[dq]
+character may be inserted into a quoted string by
+using the sequence
+.Ql \e\[dq] .
+.Pp
+Numbers are specified using
+.Tn C Ns -style
+syntax.
+.Pp
+A
+.Ql #
+character starts a comment; all characters from the
+.Ql #
+character till the end of the current line are ignored.
+.Pp
+Whitespace between tokens is ignored, except inside quoted strings.
+Whitespace following a comment line is ignored.
+.Ss Configuration Directives
+Kernel configuration directives may appear in any order
+in a kernel configuration file.
+Directives are processed in order of appearance with subsequent
+directive lines overriding the effect of prior ones.
+.Pp
+The list of keywords and their meanings are as follows:
+.Pp
+.Bl -tag -width indent -compact
+.\" -------- CPU --------
+.It Ic cpu Ar cputype
+Specify the CPU this kernel will run on.
+There can be more than one
+.Ic cpu
+directive in a configuration file.
+The allowed list of CPU names is architecture specific and is
+defined in the file
+.Pa sys/conf/options. Ns Aq Ar arch .
+.\" -------- DEVICE --------
+.Pp
+.It Ic device Ar name Op , Ar name Op ...
+.It Ic devices Ar name Op , Ar name Op ...
+Configures the specified devices
+for inclusion into the kernel image.
+Devices that are common to all architectures are
+defined in the file
+.Pa sys/conf/files .
+Devices that are specific to architecture
+.Ar arch
+are defined in the file
+.Pa sys/conf/files. Ns Aq Ar arch .
+.\" -------- ENV --------
+.Pp
+.It Ic env Ar filename
+Specifies a filename containing a kernel environment definition.
+The kernel normally uses an environment prepared for it at boot time
+by
+.Xr loader 8 .
+This directive makes the kernel ignore the boot environment and use
+the compiled-in environment instead.
+.Pp
+This directive is useful for setting kernel tunables in
+embedded environments that do not start from
+.Xr loader 8 .
+.\" -------- FILES --------
+.Pp
+.It Ic files Ar filename
+Specifies a file containing a list of files specific to that kernel
+configuration file (a la
+.Pa files. Ns Aq Ar arch ) .
+.\" -------- HINTS --------
+.Pp
+.It Ic hints Ar filename
+Specifies a file to load a static device configuration specification
+from.
+From
+.Fx 5.0
+onwards, the kernel reads the system's device configuration at boot
+time (see
+.Xr device.hints 5 ) .
+This directive configures the kernel to use the static device configuration
+listed in
+.Ar filename .
+The file
+.Ar filename
+must conform to the syntax specified by
+.Xr device.hints 5 .
+Multiple hints lines are allowed.
+The resulting hints will be the files concatenated in the order of appearance.
+.\" -------- IDENT --------
+.Pp
+.It Ic ident Ar name
+Set the kernel name to
+.Ar name .
+At least one
+.Ic ident
+directive is required.
+.\" -------- INCLUDE --------
+.Pp
+.It Ic include Ar filename
+Read subsequent text from file
+.Ar filename
+and return to the current file after
+.Ar filename
+is successfully processed.
+.\" -------- MACHINE --------
+.Pp
+.It Ic machine Ar arch Op Ar cpuarch
+Specifies the architecture of the machine the kernel is being
+compiled for.
+Legal values for
+.Ar arch
+include:
+.Pp
+.Bl -tag -width ".Cm powerpc" -compact
+.It Cm alpha
+The DEC Alpha architecture.
+.It Cm arm
+The ARM architecture.
+.It Cm amd64
+The AMD x86-64 architecture.
+.It Cm i386
+The Intel x86 based PC architecture.
+.It Cm mips
+The MIPS architecture.
+.It Cm pc98
+The PC98 architecture.
+.It Cm powerpc
+The IBM PowerPC architecture.
+.It Cm sparc64
+The Sun Sparc64 architecture.
+.El
+.Pp
+If argument
+.Ar cpuarch
+is specified, it points
+.Xr config 8
+to the cpu architecture of the machine.
+Currently the
+.Cm pc98
+architecture requires its cpu architecture
+to be set to
+.Cm i386 .
+When
+.Ar cpuarch
+is not specified, it is assumed to be the same as
+.Ar arch .
+.Ar arch
+corresponds to MACHINE.
+.Ar cpuarch
+corresponds to MACHINE_ARCH.
+.Pp
+A kernel configuration file may have only one
+.Ic machine
+directive.
+.\" -------- MAKEOPTION --------
+.Pp
+.It Ic makeoption Ar options
+.It Ic makeoptions Ar options
+Add
+.Ar options
+to the generated makefile.
+.Pp
+The
+.Ar options
+argument is a comma separated list of one or more option
+specifications.
+Each option specification has the form
+.Pp
+.D1 Ar MakeVariableName Ns Op = Ns Ar Value
+.D1 Ar MakeVariableName Ns += Ns Ar Value
+.Pp
+and results in the appropriate
+.Xr make 1
+variable definition being inserted into the generated makefile.
+If only the name of the
+.Xr make 1
+variable is specified,
+.Ar value
+is assumed to be the empty string.
+.Pp
+Example:
+.Bd -literal -offset indent -compact
+makeoptions MYMAKEOPTION="foo"
+makeoptions MYMAKEOPTION+="bar"
+makeoptions MYNULLMAKEOPTION
+.Ed
+.\" -------- MAXUSERS --------
+.Pp
+.It Ic maxusers Ar number
+This optional directive is used to configure the size
+of some kernel data structures.
+The parameter
+.Ar number
+can be 0 (the default) or an integer greater than or equal to 2.
+A value of 0 indicates that the kernel should configure
+its data structures according to the size of available
+physical memory.
+If auto configuration is requested, the kernel will set
+this tunable to a value between 32 and 384.
+.Pp
+As explained in
+.Xr tuning 7 ,
+this tunable can also be set at boot time using
+.Xr loader 8 .
+.\" -------- NOCPU --------
+.Pp
+.It Ic nocpu Ar cputype
+Remove the specified CPU
+from the list of previously selected CPUs.
+This directive can be used to cancel the effect of
+.Ic cpu
+directives in files included using
+.Ic include .
+.\" -------- NODEVICE --------
+.Pp
+.It Ic nodevice Ar name Op , Ar name Op ...
+.It Ic nodevices Ar name Op , Ar name Op ...
+Remove the specified devices
+from the list of previously selected devices.
+This directive can be used to cancel the effects of
+.Ic device
+or
+.Ic devices
+directives in files included using
+.Ic include .
+.\" -------- NOMAKEOPTION --------
+.Pp
+.It Ic nomakeoption Ar name
+.It Ic nomakeoptions Ar name
+Removes previously defined
+.Xr make 1
+option
+.Ar name
+from the kernel build.
+This directive can be used to cancel the effects of
+.Ic makeoption
+directives in files included using
+.Ic include .
+.\" -------- NOOPTION --------
+.Pp
+.It Ic nooption Ar name Op , Ar name Op ...
+.It Ic nooptions Ar name Op , Ar name Op ...
+Remove the specified kernel options
+from the list of previously defined options.
+This directive can be used to cancel the effects of
+.Ic option
+or
+.Ic options
+directives in files included using
+.Ic include .
+.\" -------- OPTIONS --------
+.Pp
+.It Ic option Ar optionspec Op , Ar optionspec Op ...
+.It Ic options Ar optionspec Op , Ar optionspec Op ...
+Add compile time kernel options to the kernel build.
+Each option specification has the form
+.Pp
+.D1 Ar name Ns Op = Ns Ar value
+.Pp
+If
+.Ar value
+is not specified, it is assumed to be
+.Dv NULL .
+Options common to all architectures are specified in
+the file
+.Pa sys/conf/options .
+Options specific to architecture
+.Ar arch
+are specified in the file
+.Pa sys/conf/options. Ns Aq Ar arch .
+.\" -------- PROFILE --------
+.Pp
+.It Ic profile Ar number
+Enables kernel profiling if
+.Ar number
+is non-zero.
+If
+.Ar number
+is 2 or greater, the kernel is configured for
+high-resolution profiling.
+Kernels can also be built for profiling using the
+.Fl p
+option to
+.Xr config 8 .
+.El
+.Ss Obsolete Directives
+The following kernel configuration directives are obsolete.
+.Bl -tag -width indent
+.\" -------- CONFIG --------
+.It Ic config
+This directive was used to specify the device to be used for the root
+file system.
+From
+.Fx 4.0
+onwards, this information is passed to a booting kernel by
+.Xr loader 8 .
+.El
+.Sh FILES
+.Bl -tag -width ".Pa sys/conf/Makefile. Ns Ar arch" -compact
+.It Pa sys/compile/ Ns Ar NAME
+Compile directory created from a kernel configuration.
+.It Pa sys/conf/Makefile. Ns Ar arch
+.Pa Makefile
+fragments for architecture
+.Ar arch .
+.It Pa sys/conf/files
+Devices common to all architectures.
+.It Pa sys/conf/files. Ns Ar arch
+Devices for architecture
+.Ar arch .
+.It Pa sys/conf/options
+Options common to all architectures.
+.It Pa sys/conf/options. Ns Ar arch
+Options for architecture
+.Ar arch .
+.El
+.Sh SEE ALSO
+.Xr kenv 1 ,
+.Xr make 1 ,
+.Xr device.hints 5 ,
+.Xr loader.conf 5 ,
+.Xr config 8 ,
+.Xr kldload 8 ,
+.Xr loader 8
+.Rs
+.%T "Building 4.4BSD Kernels with Config"
+.%A "Samuel J. Leffler"
+.%A "Michael J. Karels"
+.Re
+.Sh HISTORY
+The
+.Xr config 8
+utility first appeared in
+.Bx 4.1 ,
+and was subsequently revised in
+.Bx 4.4 .
+.Pp
+The kernel configuration mechanism changed further in
+.Fx 4.0
+and
+.Fx 5.0 ,
+moving toward an architecture supporting dynamic kernel
+configuration.
diff --git a/usr.sbin/config/config.8 b/usr.sbin/config/config.8
new file mode 100644
index 0000000..dcfbc62
--- /dev/null
+++ b/usr.sbin/config/config.8
@@ -0,0 +1,283 @@
+.\" 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.
+.\"
+.\" @(#)config.8 8.2 (Berkeley) 4/19/94
+.\" $FreeBSD$
+.\"
+.Dd May 8, 2007
+.Dt CONFIG 8
+.Os
+.Sh NAME
+.Nm config
+.Nd build system configuration files
+.Sh SYNOPSIS
+.Nm
+.Op Fl CVgp
+.Op Fl I Ar path
+.Op Fl d Ar destdir
+.Op Fl s Ar srcdir
+.Ar SYSTEM_NAME
+.Nm
+.Op Fl x Ar kernel
+.Sh DESCRIPTION
+The
+.Nm
+utility builds a set of system configuration files from the file
+.Ar SYSTEM_NAME
+which describes
+the system to configure.
+A second file
+tells
+.Nm
+what files are needed to generate a system and
+can be augmented by configuration specific set of files
+that give alternate files for a specific machine
+(see the
+.Sx FILES
+section below).
+.Pp
+Available options and operands:
+.Bl -tag -width ".Ar SYSTEM_NAME"
+.It Fl V
+Print the
+.Nm
+version number.
+.It Fl C
+If the INCLUDE_CONFIG_FILE is present in a configuration file,
+kernel image will contain full configuration files included
+literally (preserving comments).
+This flag is kept for backward compatibility.
+.It Fl I Ar path
+Search in
+.Ar path
+for any file included by the
+.Ic include
+directive. This option may be specified more than once.
+.It Fl d Ar destdir
+Use
+.Ar destdir
+as the output directory, instead of the default one.
+Note that
+.Nm
+does not append
+.Ar SYSTEM_NAME
+to the directory given.
+.It Fl s Ar srcdir
+Use
+.Ar srcdir
+as the source directory, instead of the default one.
+.It Fl m
+Print the MACHINE and MACHINE_ARCH values for this
+kernel and exit.
+.It Fl g
+Configure a system for debugging.
+.It Fl x Ar kernel
+Print kernel configuration file embedded into a kernel
+file.
+This option makes sense only if
+.Cd "options INCLUDE_CONFIG_FILE"
+entry was present in your configuration file.
+.It Fl p
+Configure a system for profiling; for example,
+.Xr kgmon 8
+and
+.Xr gprof 1 .
+If two or more
+.Fl p
+options are supplied,
+.Nm
+configures a system for high resolution profiling.
+.It Ar SYSTEM_NAME
+Specify the name of the system configuration file
+containing device specifications, configuration options
+and other system parameters for one system configuration.
+.El
+.Pp
+The
+.Nm
+utility should be run from the
+.Pa conf
+subdirectory of the system source (usually
+.Pa /sys/ Ns Va ARCH Ns Pa /conf ) ,
+where
+.Va ARCH
+represents one of the architectures supported by
+.Fx .
+The
+.Nm
+utility creates the directory
+.Pa ../compile/ Ns Ar SYSTEM_NAME
+or the one given with the
+.Fl d
+option
+as necessary and places all output files there.
+The output of
+.Nm
+consists of a number of files; for the
+.Tn i386 ,
+they are:
+.Pa Makefile ,
+used by
+.Xr make 1
+in building the system;
+header files,
+definitions of
+the number of various devices that will be compiled into the system.
+.Pp
+The
+.Nm
+utility looks for kernel sources in the directory
+.Pa ../..
+or the one given with the
+.Fl s
+option.
+.Pp
+After running
+.Nm ,
+it is necessary to run
+.Dq Li make depend
+in the directory where the new makefile
+was created.
+The
+.Nm
+utility prints a reminder of this when it completes.
+.Pp
+If any other error messages are produced by
+.Nm ,
+the problems in the configuration file should be corrected and
+.Nm
+should be run again.
+Attempts to compile a system that had configuration errors
+are likely to fail.
+.Sh DEBUG KERNELS
+Traditional
+.Bx
+kernels are compiled without symbols due to the heavy load on the
+system when compiling a
+.Dq debug
+kernel.
+A debug kernel contains complete symbols for all the source files, and
+enables an experienced kernel programmer to analyse the cause of a problem.
+The
+debuggers available prior to
+.Bx 4.4 Lite
+were able to find some information
+from a normal kernel;
+.Xr gdb 1
+provides very little support for normal kernels, and a debug kernel is needed
+for any meaningful analysis.
+.Pp
+For reasons of history, time and space, building a debug kernel is not the
+default with
+.Fx :
+a debug kernel takes up to 30% longer to build and
+requires about 30 MB of disk storage in the build directory, compared to about 6
+MB for a non-debug kernel.
+A debug kernel is about 11 MB in size, compared to
+about 2 MB for a non-debug kernel.
+This space is used both in the root file
+system and at run time in memory.
+Use the
+.Fl g
+option to build a debug kernel.
+With this option,
+.Nm
+causes two kernel files to be built in the kernel build directory:
+.Bl -bullet
+.It
+.Pa kernel.debug
+is the complete debug kernel.
+.It
+.Pa kernel
+is a copy of the kernel with the debug symbols stripped off.
+This is equivalent
+to the normal non-debug kernel.
+.El
+.Pp
+There is currently little sense in installing and booting from a debug kernel,
+since the only tools available which use the symbols do not run on-line.
+There
+are therefore two options for installing a debug kernel:
+.Bl -bullet
+.It
+.Dq Li "make install"
+installs
+.Pa kernel
+in the root file system.
+.It
+.Dq Li "make install.debug"
+installs
+.Pa kernel.debug
+in the root file system.
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /sys/ Ns Va ARCH Ns Pa /compile/ Ns Ar SYSTEM_NAME" -compact
+.It Pa /sys/conf/files
+list of common files system is built from
+.It Pa /sys/conf/Makefile. Ns Va ARCH
+generic makefile for the
+.Va ARCH
+.It Pa /sys/conf/files. Ns Va ARCH
+list of
+.Va ARCH
+specific files
+.It Pa /sys/ Ns Va ARCH Ns Pa /compile/ Ns Ar SYSTEM_NAME
+default kernel build directory for system
+.Ar SYSTEM_NAME
+on
+.Va ARCH .
+.El
+.Sh SEE ALSO
+.Xr config 5
+.Pp
+The
+.Sx SYNOPSIS
+portion of each device in section 4.
+.Rs
+.%T "Building 4.3 BSD UNIX System with Config"
+.Re
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.1 .
+.Pp
+Before support for
+.Fl x
+was introduced,
+.Cd "options INCLUDE_CONFIG_FILE"
+included entire configuration file that used to be embedded in
+the new kernel.
+This meant that
+.Xr strings 1
+could be used to extract it from a kernel:
+to extract the configuration information, you had to use
+the command:
+.Pp
+.Dl "strings -n 3 kernel | sed -n 's/^___//p'"
+.Sh BUGS
+The line numbers reported in error messages are usually off by one.
diff --git a/usr.sbin/config/config.h b/usr.sbin/config/config.h
new file mode 100644
index 0000000..703d053
--- /dev/null
+++ b/usr.sbin/config/config.h
@@ -0,0 +1,211 @@
+/*
+ * 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.
+ *
+ * @(#)config.h 8.1 (Berkeley) 6/6/93
+ * $FreeBSD$
+ */
+
+/*
+ * Config.
+ */
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct cfgfile {
+ STAILQ_ENTRY(cfgfile) cfg_next;
+ char *cfg_path;
+};
+STAILQ_HEAD(, cfgfile) cfgfiles;
+
+struct file_list {
+ STAILQ_ENTRY(file_list) f_next;
+ char *f_fn; /* the name */
+ int f_type; /* type */
+ u_char f_flags; /* see below */
+ char *f_compilewith; /* special make rule if present */
+ char *f_depends; /* additional dependencies */
+ char *f_clean; /* File list to add to clean rule */
+ char *f_warn; /* warning message */
+ const char *f_objprefix; /* prefix string for object name */
+};
+
+struct files_name {
+ char *f_name;
+ STAILQ_ENTRY(files_name) f_next;
+};
+
+/*
+ * Types.
+ */
+#define NORMAL 1
+#define PROFILING 3
+#define NODEPEND 4
+#define LOCAL 5
+#define DEVDONE 0x80000000
+#define TYPEMASK 0x7fffffff
+
+/*
+ * Attributes (flags).
+ */
+#define NO_IMPLCT_RULE 1
+#define NO_OBJ 2
+#define BEFORE_DEPEND 4
+#define NOWERROR 16
+
+struct device {
+ int d_done; /* processed */
+ char *d_name; /* name of device (e.g. rk11) */
+#define UNKNOWN -2 /* -2 means not set yet */
+ STAILQ_ENTRY(device) d_next; /* Next one in list */
+};
+
+struct config {
+ char *s_sysname;
+};
+
+/*
+ * Config has a global notion of which machine type is
+ * being used. It uses the name of the machine in choosing
+ * files and directories. Thus if the name of the machine is ``i386'',
+ * it will build from ``Makefile.i386'' and use ``../i386/inline''
+ * in the makerules, etc. machinearch is the global notion of the
+ * MACHINE_ARCH for this MACHINE.
+ */
+char *machinename;
+char *machinearch;
+
+/*
+ * For each machine, a set of CPU's may be specified as supported.
+ * These and the options (below) are put in the C flags in the makefile.
+ */
+struct cputype {
+ char *cpu_name;
+ SLIST_ENTRY(cputype) cpu_next;
+};
+
+SLIST_HEAD(, cputype) cputype;
+
+/*
+ * A set of options may also be specified which are like CPU types,
+ * but which may also specify values for the options.
+ * A separate set of options may be defined for make-style options.
+ */
+struct opt {
+ char *op_name;
+ char *op_value;
+ int op_ownfile; /* true = own file, false = makefile */
+ SLIST_ENTRY(opt) op_next;
+ SLIST_ENTRY(opt) op_append;
+};
+
+SLIST_HEAD(opt_head, opt) opt, mkopt, rmopts;
+
+struct opt_list {
+ char *o_name;
+ char *o_file;
+ int o_flags;
+#define OL_ALIAS 1
+ SLIST_ENTRY(opt_list) o_next;
+};
+
+SLIST_HEAD(, opt_list) otab;
+
+struct hint {
+ char *hint_name;
+ STAILQ_ENTRY(hint) hint_next;
+};
+
+STAILQ_HEAD(hint_head, hint) hints;
+
+struct includepath {
+ char *path;
+ SLIST_ENTRY(includepath) path_next;
+};
+
+SLIST_HEAD(, includepath) includepath;
+
+/*
+ * Tag present in the kernelconf.tmlp template file. It's mandatory for those
+ * two strings to be the same. Otherwise you'll get into trouble.
+ */
+#define KERNCONFTAG "%%KERNCONFFILE%%"
+
+/*
+ * Faked option to note, that the configuration file has been taken from the
+ * kernel file and inclusion of DEFAULTS etc.. isn't nessesery, because we
+ * already have a list of all required devices.
+ */
+#define OPT_AUTOGEN "CONFIG_AUTOGENERATED"
+
+extern char *ident;
+extern char *env;
+extern char kernconfstr[];
+extern int do_trace;
+extern int envmode;
+extern int hintmode;
+extern int incignore;
+
+char *get_word(FILE *);
+char *get_quoted_word(FILE *);
+char *path(const char *);
+char *raisestr(char *);
+void remember(const char *);
+void moveifchanged(const char *, const char *);
+int yylex(void);
+void options(void);
+void makefile(void);
+void makeenv(void);
+void makehints(void);
+void headers(void);
+void cfgfile_add(const char *);
+void cfgfile_removeall(void);
+FILE *open_makefile_template(void);
+
+extern STAILQ_HEAD(device_head, device) dtab;
+
+extern char errbuf[80];
+extern int yyline;
+extern const char *yyfile;
+
+extern STAILQ_HEAD(file_list_head, file_list) ftab;
+
+extern STAILQ_HEAD(files_name_head, files_name) fntab;
+
+extern int profiling;
+extern int debugging;
+extern int found_defaults;
+
+extern int maxusers;
+
+extern char *PREFIX; /* Config file name - for error messages */
+extern char srcdir[]; /* root of the kernel source tree */
+
+#define eq(a,b) (!strcmp(a,b))
+#define ns(s) strdup(s)
diff --git a/usr.sbin/config/config.y b/usr.sbin/config/config.y
new file mode 100644
index 0000000..c7198a0
--- /dev/null
+++ b/usr.sbin/config/config.y
@@ -0,0 +1,469 @@
+%union {
+ char *str;
+ int val;
+ struct file_list *file;
+}
+
+%token ARCH
+%token COMMA
+%token CONFIG
+%token CPU
+%token NOCPU
+%token DEVICE
+%token NODEVICE
+%token ENV
+%token EQUALS
+%token PLUSEQUALS
+%token HINTS
+%token IDENT
+%token MAXUSERS
+%token PROFILE
+%token OPTIONS
+%token NOOPTION
+%token MAKEOPTIONS
+%token NOMAKEOPTION
+%token SEMICOLON
+%token INCLUDE
+%token FILES
+
+%token <str> ID
+%token <val> NUMBER
+
+%type <str> Save_id
+%type <str> Opt_value
+%type <str> Dev
+%token <str> PATH
+
+%{
+
+/*
+ * 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.
+ *
+ * @(#)config.y 8.1 (Berkeley) 6/6/93
+ * $FreeBSD$
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "config.h"
+
+struct device_head dtab;
+char *ident;
+char *env;
+int envmode;
+int hintmode;
+int yyline;
+const char *yyfile;
+struct file_list_head ftab;
+struct files_name_head fntab;
+char errbuf[80];
+int maxusers;
+
+#define ns(s) strdup(s)
+int include(const char *, int);
+void yyerror(const char *s);
+int yywrap(void);
+
+static void newdev(char *name);
+static void newfile(char *name);
+static void rmdev_schedule(struct device_head *dh, char *name);
+static void newopt(struct opt_head *list, char *name, char *value, int append);
+static void rmopt_schedule(struct opt_head *list, char *name);
+
+static char *
+devopt(char *dev)
+{
+ char *ret = malloc(strlen(dev) + 5);
+
+ sprintf(ret, "DEV_%s", dev);
+ raisestr(ret);
+ return ret;
+}
+
+%}
+%%
+Configuration:
+ Many_specs
+ ;
+
+Many_specs:
+ Many_specs Spec
+ |
+ /* lambda */
+ ;
+
+Spec:
+ Device_spec SEMICOLON
+ |
+ Config_spec SEMICOLON
+ |
+ INCLUDE PATH SEMICOLON {
+ if (incignore == 0)
+ include($2, 0);
+ };
+ |
+ INCLUDE ID SEMICOLON {
+ if (incignore == 0)
+ include($2, 0);
+ };
+ |
+ FILES ID SEMICOLON { newfile($2); };
+ |
+ SEMICOLON
+ |
+ error SEMICOLON
+ ;
+
+Config_spec:
+ ARCH Save_id {
+ if (machinename != NULL && !eq($2, machinename))
+ errx(1, "%s:%d: only one machine directive is allowed",
+ yyfile, yyline);
+ machinename = $2;
+ machinearch = $2;
+ } |
+ ARCH Save_id Save_id {
+ if (machinename != NULL &&
+ !(eq($2, machinename) && eq($3, machinearch)))
+ errx(1, "%s:%d: only one machine directive is allowed",
+ yyfile, yyline);
+ machinename = $2;
+ machinearch = $3;
+ } |
+ CPU Save_id {
+ struct cputype *cp =
+ (struct cputype *)calloc(1, sizeof (struct cputype));
+ if (cp == NULL)
+ err(EXIT_FAILURE, "calloc");
+ cp->cpu_name = $2;
+ SLIST_INSERT_HEAD(&cputype, cp, cpu_next);
+ } |
+ NOCPU Save_id {
+ struct cputype *cp, *cp2;
+ SLIST_FOREACH_SAFE(cp, &cputype, cpu_next, cp2) {
+ if (eq(cp->cpu_name, $2)) {
+ SLIST_REMOVE(&cputype, cp, cputype, cpu_next);
+ free(cp);
+ }
+ }
+ } |
+ OPTIONS Opt_list
+ |
+ NOOPTION NoOpt_list |
+ MAKEOPTIONS Mkopt_list
+ |
+ NOMAKEOPTION Save_id { rmopt_schedule(&mkopt, $2); } |
+ IDENT ID { ident = $2; } |
+ System_spec
+ |
+ MAXUSERS NUMBER { maxusers = $2; } |
+ PROFILE NUMBER { profiling = $2; } |
+ ENV ID {
+ env = $2;
+ envmode = 1;
+ } |
+ HINTS ID {
+ struct hint *hint;
+
+ hint = (struct hint *)calloc(1, sizeof (struct hint));
+ if (hint == NULL)
+ err(EXIT_FAILURE, "calloc");
+ hint->hint_name = $2;
+ STAILQ_INSERT_TAIL(&hints, hint, hint_next);
+ hintmode = 1;
+ }
+
+System_spec:
+ CONFIG System_id System_parameter_list {
+ errx(1, "%s:%d: root/dump/swap specifications obsolete",
+ yyfile, yyline);
+ }
+ |
+ CONFIG System_id
+ ;
+
+System_id:
+ Save_id { newopt(&mkopt, ns("KERNEL"), $1, 0); };
+
+System_parameter_list:
+ System_parameter_list ID
+ | ID
+ ;
+
+Opt_list:
+ Opt_list COMMA Option
+ |
+ Option
+ ;
+
+NoOpt_list:
+ NoOpt_list COMMA NoOption
+ |
+ NoOption
+ ;
+Option:
+ Save_id {
+ newopt(&opt, $1, NULL, 0);
+ if (strchr($1, '=') != NULL)
+ errx(1, "%s:%d: The `=' in options should not be "
+ "quoted", yyfile, yyline);
+ } |
+ Save_id EQUALS Opt_value {
+ newopt(&opt, $1, $3, 0);
+ } ;
+
+NoOption:
+ Save_id {
+ rmopt_schedule(&opt, $1);
+ };
+
+Opt_value:
+ ID { $$ = $1; } |
+ NUMBER {
+ char buf[80];
+
+ (void) snprintf(buf, sizeof(buf), "%d", $1);
+ $$ = ns(buf);
+ } ;
+
+Save_id:
+ ID { $$ = $1; }
+ ;
+
+Mkopt_list:
+ Mkopt_list COMMA Mkoption
+ |
+ Mkoption
+ ;
+
+Mkoption:
+ Save_id { newopt(&mkopt, $1, ns(""), 0); } |
+ Save_id EQUALS { newopt(&mkopt, $1, ns(""), 0); } |
+ Save_id EQUALS Opt_value { newopt(&mkopt, $1, $3, 0); } |
+ Save_id PLUSEQUALS Opt_value { newopt(&mkopt, $1, $3, 1); } ;
+
+Dev:
+ ID { $$ = $1; }
+ ;
+
+Device_spec:
+ DEVICE Dev_list
+ |
+ NODEVICE NoDev_list
+ ;
+
+Dev_list:
+ Dev_list COMMA Device
+ |
+ Device
+ ;
+
+NoDev_list:
+ NoDev_list COMMA NoDevice
+ |
+ NoDevice
+ ;
+
+Device:
+ Dev {
+ newopt(&opt, devopt($1), ns("1"), 0);
+ /* and the device part */
+ newdev($1);
+ }
+
+NoDevice:
+ Dev {
+ char *s = devopt($1);
+
+ rmopt_schedule(&opt, s);
+ free(s);
+ /* and the device part */
+ rmdev_schedule(&dtab, $1);
+ } ;
+
+%%
+
+void
+yyerror(const char *s)
+{
+
+ errx(1, "%s:%d: %s", yyfile, yyline + 1, s);
+}
+
+int
+yywrap(void)
+{
+ if (found_defaults) {
+ if (freopen(PREFIX, "r", stdin) == NULL)
+ err(2, "%s", PREFIX);
+ yyfile = PREFIX;
+ yyline = 0;
+ found_defaults = 0;
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * Add a new file to the list of files.
+ */
+static void
+newfile(char *name)
+{
+ struct files_name *nl;
+
+ nl = (struct files_name *) calloc(1, sizeof *nl);
+ if (nl == NULL)
+ err(EXIT_FAILURE, "calloc");
+ nl->f_name = name;
+ STAILQ_INSERT_TAIL(&fntab, nl, f_next);
+}
+
+/*
+ * Find a device in the list of devices.
+ */
+static struct device *
+finddev(struct device_head *dlist, char *name)
+{
+ struct device *dp;
+
+ STAILQ_FOREACH(dp, dlist, d_next)
+ if (eq(dp->d_name, name))
+ return (dp);
+
+ return (NULL);
+}
+
+/*
+ * Add a device to the list of devices.
+ */
+static void
+newdev(char *name)
+{
+ struct device *np;
+
+ if (finddev(&dtab, name)) {
+ fprintf(stderr,
+ "WARNING: duplicate device `%s' encountered.\n", name);
+ return;
+ }
+
+ np = (struct device *) calloc(1, sizeof *np);
+ if (np == NULL)
+ err(EXIT_FAILURE, "calloc");
+ np->d_name = name;
+ STAILQ_INSERT_TAIL(&dtab, np, d_next);
+}
+
+/*
+ * Schedule a device to removal.
+ */
+static void
+rmdev_schedule(struct device_head *dh, char *name)
+{
+ struct device *dp;
+
+ dp = finddev(dh, name);
+ if (dp != NULL) {
+ STAILQ_REMOVE(dh, dp, device, d_next);
+ free(dp->d_name);
+ free(dp);
+ }
+}
+
+/*
+ * Find an option in the list of options.
+ */
+static struct opt *
+findopt(struct opt_head *list, char *name)
+{
+ struct opt *op;
+
+ SLIST_FOREACH(op, list, op_next)
+ if (eq(op->op_name, name))
+ return (op);
+
+ return (NULL);
+}
+
+/*
+ * Add an option to the list of options.
+ */
+static void
+newopt(struct opt_head *list, char *name, char *value, int append)
+{
+ struct opt *op, *op2;
+
+ /*
+ * Ignore inclusions listed explicitly for configuration files.
+ */
+ if (eq(name, OPT_AUTOGEN)) {
+ incignore = 1;
+ return;
+ }
+
+ op2 = findopt(list, name);
+ if (op2 != NULL && !append) {
+ fprintf(stderr,
+ "WARNING: duplicate option `%s' encountered.\n", name);
+ return;
+ }
+
+ op = (struct opt *)calloc(1, sizeof (struct opt));
+ if (op == NULL)
+ err(EXIT_FAILURE, "calloc");
+ op->op_name = name;
+ op->op_ownfile = 0;
+ op->op_value = value;
+ if (op2 != NULL) {
+ while (SLIST_NEXT(op2, op_append) != NULL)
+ op2 = SLIST_NEXT(op2, op_append);
+ SLIST_NEXT(op2, op_append) = op;
+ } else
+ SLIST_INSERT_HEAD(list, op, op_next);
+}
+
+/*
+ * Remove an option from the list of options.
+ */
+static void
+rmopt_schedule(struct opt_head *list, char *name)
+{
+ struct opt *op;
+
+ op = findopt(list, name);
+ if (op != NULL) {
+ SLIST_REMOVE(list, op, opt, op_next);
+ free(op->op_name);
+ free(op);
+ }
+}
diff --git a/usr.sbin/config/configvers.h b/usr.sbin/config/configvers.h
new file mode 100644
index 0000000..5c29373
--- /dev/null
+++ b/usr.sbin/config/configvers.h
@@ -0,0 +1,53 @@
+/*-
+ * This file is in the public domain
+ *
+ * $FreeBSD$
+ */
+
+/*
+ * 6 digits of version. The most significant are branch indicators at the
+ * time when the last incompatible change was made (which is why it is
+ * presently 6 on 7-current). The least significant digits are incremented
+ * as described below. The format is similar to the __FreeBSD_version, but
+ * not tied to it.
+ *
+ * DO NOT CASUALLY BUMP THIS NUMBER! The rules are not the same as shared
+ * libs or param.h/osreldate.
+ *
+ * It is the version number of the protocol between config(8) and the
+ * sys/conf/ Makefiles (the kernel build system).
+ *
+ * It is now also used to trap certain problems that the syntax parser cannot
+ * detect.
+ *
+ * Unfortunately, there is no version number for user supplied config files.
+ *
+ * Once, config(8) used to silently report errors and continue anyway. This
+ * was a huge problem for 'make buildkernel' which was run with the installed
+ * /usr/sbin/config, not a cross built one. We started bumping the version
+ * number as a way to trap cases where the previous installworld was not
+ * compatible with the new buildkernel. The buildtools phase and much more
+ * comprehensive error code returns solved this original problem.
+ *
+ * Most end-users will use buildkernel and the build tools from buildworld.
+ * The people that are inconvenienced by gratuitous bumps are developers
+ * who run config by hand. However, developers shouldn't gratuitously be
+ * inconvenienced.
+ *
+ * One should bump the CONFIGVERS in the following ways:
+ *
+ * (1) If you change config such that it won't read old config files,
+ * then bump the major number. You shouldn't be doing this unless
+ * you are overhauling config. Do not casually bump this number
+ * and by implication do not make changes that would force a bump
+ * of this number casually. You should limit major bumps to once
+ * per branch.
+ * (2) For each new feature added, bump the minor version of this file.
+ * When a new feature is actually used by the build system, update the
+ * %VERSREQ field in the Makefile.$ARCH of all the affected makefiles
+ * (typically all of them).
+ *
+ * $FreeBSD$
+ */
+#define CONFIGVERS 600014
+#define MAJOR_VERS(x) ((x) / 100000)
diff --git a/usr.sbin/config/kernconf.tmpl b/usr.sbin/config/kernconf.tmpl
new file mode 100644
index 0000000..182614b
--- /dev/null
+++ b/usr.sbin/config/kernconf.tmpl
@@ -0,0 +1,17 @@
+/*
+ * This file acts as a template for config.c that will be generated in the
+ * kernel build directory after config(8) has been successfully run.
+ *
+ * $FreeBSD$
+ */
+#include "opt_config.h"
+#ifdef INCLUDE_CONFIG_FILE
+
+/*
+ * For !INCLUDE_CONFIG_FILE case, you should look at kern_mib.c. This is
+ * where kernconfstring is defined then.
+ */
+const char kernconfstring[] __attribute__ ((section("kern_conf"))) =
+"%%KERNCONFFILE%%";
+
+#endif /* INCLUDE_CONFIG_FILE */
diff --git a/usr.sbin/config/lang.l b/usr.sbin/config/lang.l
new file mode 100644
index 0000000..14fa7da
--- /dev/null
+++ b/usr.sbin/config/lang.l
@@ -0,0 +1,325 @@
+%{
+/*-
+ * 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.
+ *
+ * @(#)lang.l 8.1 (Berkeley) 6/6/93
+ * $FreeBSD$
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+#include "y.tab.h"
+#include "config.h"
+
+/*
+ * Data for returning to previous files from include files.
+ */
+struct incl {
+ struct incl *in_prev; /* previous includes in effect, if any */
+ YY_BUFFER_STATE in_buf; /* previous lex state */
+ const char *in_fname; /* previous file name */
+ int in_lineno; /* previous line number */
+ int in_ateof; /* token to insert at EOF */
+};
+static struct incl *inclp;
+static const char *lastfile;
+
+/*
+ * Key word table
+ */
+
+struct kt {
+ const char *kt_name;
+ int kt_val;
+} key_words[] = {
+ { "config", CONFIG },
+ { "cpu", CPU },
+ { "nocpu", NOCPU },
+ { "device", DEVICE },
+ { "devices", DEVICE },
+ { "nodevice", NODEVICE },
+ { "nodevices", NODEVICE },
+ { "env", ENV },
+ { "hints", HINTS },
+ { "ident", IDENT },
+ { "machine", ARCH }, /* MACHINE is defined in /sys/param.h */
+ { "makeoption", MAKEOPTIONS },
+ { "makeoptions", MAKEOPTIONS },
+ { "nomakeoption", NOMAKEOPTION },
+ { "nomakeoptions", NOMAKEOPTION },
+ { "maxusers", MAXUSERS },
+ { "profile", PROFILE },
+ { "option", OPTIONS },
+ { "options", OPTIONS },
+ { "nooption", NOOPTION },
+ { "nooptions", NOOPTION },
+ { "include", INCLUDE },
+ { "files", FILES },
+ { 0, 0 },
+};
+
+
+static int endinclude(void);
+int include(const char *, int);
+int kw_lookup(char *);
+unsigned int octal(const char *);
+unsigned int hex(const char *);
+int yyerror(const char *);
+
+#define YY_DECL int yylex(void)
+%}
+
+%option nounput
+%option noinput
+
+ID [A-Za-z_][-A-Za-z_0-9]*
+PATH [./][-/.%^A-Za-z_0-9]+
+%START TOEOL
+%%
+{ID} {
+ int i;
+
+ BEGIN 0;
+ if ((i = kw_lookup(yytext)) == -1)
+ {
+ yylval.str = strdup(yytext);
+ return ID;
+ }
+ return i;
+ }
+\\\"[^"]+\\\" {
+ BEGIN 0;
+ yytext[yyleng-2] = '"';
+ yytext[yyleng-1] = '\0';
+ yylval.str = strdup(yytext + 1);
+ return ID;
+ }
+\"[^"]+\" {
+ BEGIN 0;
+ yytext[yyleng-1] = '\0';
+ yylval.str = strdup(yytext + 1);
+ return ID;
+ }
+<TOEOL>[^# \t\n]* {
+ BEGIN 0;
+ yylval.str = strdup(yytext);
+ return ID;
+ }
+0[0-7]* {
+ yylval.val = octal(yytext);
+ return NUMBER;
+ }
+0x[0-9a-fA-F]+ {
+ yylval.val = hex(yytext);
+ return NUMBER;
+ }
+-?[1-9][0-9]* {
+ yylval.val = atoi(yytext);
+ return NUMBER;
+ }
+"?" {
+ yylval.val = -1;
+ return NUMBER;
+ }
+\n/[ \t] {
+ yyline++;
+ }
+\n {
+ yyline++;
+ return SEMICOLON;
+ }
+#.* { /* Ignored (comment) */; }
+[ \t\f]* { /* Ignored (white space) */; }
+";" { return SEMICOLON; }
+"," { return COMMA; }
+"=" { BEGIN TOEOL; return EQUALS; }
+"+=" { BEGIN TOEOL; return PLUSEQUALS; }
+<<EOF>> {
+ int tok;
+
+ if (inclp == NULL)
+ return YY_NULL;
+ tok = endinclude();
+ if (tok != 0)
+ return tok;
+ /* otherwise continue scanning */
+ }
+{PATH} {
+ BEGIN 0;
+ yylval.str = strdup(yytext);
+ return PATH;
+ }
+. { return yytext[0]; }
+
+%%
+/*
+ * kw_lookup
+ * Look up a string in the keyword table. Returns a -1 if the
+ * string is not a keyword otherwise it returns the keyword number
+ */
+
+int
+kw_lookup(char *word)
+{
+ struct kt *kp;
+
+ for (kp = key_words; kp->kt_name != 0; kp++)
+ if (eq(word, kp->kt_name))
+ return kp->kt_val;
+ return -1;
+}
+
+/*
+ * Number conversion routines
+ */
+
+unsigned int
+octal(const char *str)
+{
+ unsigned int num;
+
+ (void) sscanf(str, "%o", &num);
+ return num;
+}
+
+unsigned int
+hex(const char *str)
+{
+ unsigned int num;
+
+ (void) sscanf(str+2, "%x", &num);
+ return num;
+}
+
+void
+cfgfile_add(const char *fname)
+{
+ struct cfgfile *cf;
+
+ cf = calloc(1, sizeof(*cf));
+ if (cf == NULL)
+ err(EXIT_FAILURE, "calloc");
+ assert(cf != NULL);
+ asprintf(&cf->cfg_path, "%s", fname);
+ STAILQ_INSERT_TAIL(&cfgfiles, cf, cfg_next);
+}
+
+void
+cfgfile_removeall(void)
+{
+ struct cfgfile *cf;
+
+ while (!STAILQ_EMPTY(&cfgfiles)) {
+ cf = STAILQ_FIRST(&cfgfiles);
+ STAILQ_REMOVE_HEAD(&cfgfiles, cfg_next);
+ if (cf->cfg_path != NULL)
+ free(cf->cfg_path);
+ free(cf);
+ }
+}
+
+/*
+ * Open the named file for inclusion at the current point. Returns 0 on
+ * success (file opened and previous state pushed), nonzero on failure
+ * (fopen failed, complaint made). The `ateof' parameter controls the
+ * token to be inserted at the end of the include file. If ateof == 0,
+ * then nothing is inserted.
+ */
+int
+include(const char *fname, int ateof)
+{
+ FILE *fp;
+ struct incl *in;
+ struct includepath* ipath;
+ char *fnamebuf;
+
+ fnamebuf = NULL;
+ fp = fopen(fname, "r");
+ if (fp == NULL && fname[0] != '.' && fname[0] != '/') {
+ asprintf(&fnamebuf, "../../conf/%s", fname);
+ if (fnamebuf != NULL) {
+ fp = fopen(fnamebuf, "r");
+ free(fnamebuf);
+ }
+ }
+ if (fp == NULL) {
+ SLIST_FOREACH(ipath, &includepath, path_next) {
+ asprintf(&fnamebuf, "%s/%s", ipath->path, fname);
+ if (fnamebuf != NULL) {
+ fp = fopen(fnamebuf, "r");
+ free(fnamebuf);
+ }
+ if (fp != NULL)
+ break;
+ }
+ }
+ if (fp == NULL) {
+ yyerror("cannot open included file");
+ return (-1);
+ }
+ cfgfile_add(fnamebuf == NULL ? fname : fnamebuf);
+ in = malloc(sizeof(*in));
+ assert(in != NULL);
+ in->in_prev = inclp;
+ in->in_buf = YY_CURRENT_BUFFER;
+ in->in_fname = yyfile;
+ in->in_lineno = yyline;
+ in->in_ateof = ateof;
+ inclp = in;
+ yy_switch_to_buffer(yy_create_buffer(fp, YY_BUF_SIZE));
+ yyfile = fname;
+ yyline = 0;
+ return (0);
+}
+
+/*
+ * Terminate the most recent inclusion.
+ */
+static int
+endinclude(void)
+{
+ struct incl *in;
+ int ateof;
+
+ in = inclp;
+ assert(in != NULL);
+ inclp = in->in_prev;
+ lastfile = yyfile;
+ yy_delete_buffer(YY_CURRENT_BUFFER);
+ (void)fclose(yyin);
+ yy_switch_to_buffer(in->in_buf);
+ yyfile = in->in_fname;
+ yyline = in->in_lineno;
+ ateof = in->in_ateof;
+ free(in);
+
+ return (ateof);
+}
diff --git a/usr.sbin/config/main.c b/usr.sbin/config/main.c
new file mode 100644
index 0000000..c87f3bb
--- /dev/null
+++ b/usr.sbin/config/main.c
@@ -0,0 +1,778 @@
+/*
+ * 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
+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
+#if 0
+static char sccsid[] = "@(#)main.c 8.1 (Berkeley) 6/6/93";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/sbuf.h>
+#include <sys/file.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <stdio.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <dirent.h>
+#include "y.tab.h"
+#include "config.h"
+#include "configvers.h"
+
+#ifndef TRUE
+#define TRUE (1)
+#endif
+
+#ifndef FALSE
+#define FALSE (0)
+#endif
+
+#define CDIR "../compile/"
+
+char * PREFIX;
+char destdir[MAXPATHLEN];
+char srcdir[MAXPATHLEN];
+
+int debugging;
+int profiling;
+int found_defaults;
+int incignore;
+
+/*
+ * Preserve old behaviour in INCLUDE_CONFIG_FILE handling (files are included
+ * literally).
+ */
+int filebased = 0;
+
+static void configfile(void);
+static void get_srcdir(void);
+static void usage(void);
+static void cleanheaders(char *);
+static void kernconfdump(const char *);
+static void checkversion(void);
+extern int yyparse(void);
+
+struct hdr_list {
+ char *h_name;
+ struct hdr_list *h_next;
+} *htab;
+
+/*
+ * Config builds a set of files for building a UNIX
+ * system given a description of the desired system.
+ */
+int
+main(int argc, char **argv)
+{
+
+ struct stat buf;
+ int ch, len;
+ char *p;
+ char *kernfile;
+ struct includepath* ipath;
+ int printmachine;
+
+ printmachine = 0;
+ kernfile = NULL;
+ SLIST_INIT(&includepath);
+ while ((ch = getopt(argc, argv, "CI:d:gmpsVx:")) != -1)
+ switch (ch) {
+ case 'C':
+ filebased = 1;
+ break;
+ case 'I':
+ ipath = (struct includepath *) \
+ calloc(1, sizeof (struct includepath));
+ if (ipath == NULL)
+ err(EXIT_FAILURE, "calloc");
+ ipath->path = optarg;
+ SLIST_INSERT_HEAD(&includepath, ipath, path_next);
+ break;
+ case 'm':
+ printmachine = 1;
+ break;
+ case 'd':
+ if (*destdir == '\0')
+ strlcpy(destdir, optarg, sizeof(destdir));
+ else
+ errx(EXIT_FAILURE, "directory already set");
+ break;
+ case 'g':
+ debugging++;
+ break;
+ case 'p':
+ profiling++;
+ break;
+ case 's':
+ if (*srcdir == '\0')
+ strlcpy(srcdir, optarg, sizeof(srcdir));
+ else
+ errx(EXIT_FAILURE, "src directory already set");
+ break;
+ case 'V':
+ printf("%d\n", CONFIGVERS);
+ exit(0);
+ case 'x':
+ kernfile = optarg;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (kernfile != NULL) {
+ kernconfdump(kernfile);
+ exit(EXIT_SUCCESS);
+ }
+
+ if (argc != 1)
+ usage();
+
+ PREFIX = *argv;
+ if (stat(PREFIX, &buf) != 0 || !S_ISREG(buf.st_mode))
+ err(2, "%s", PREFIX);
+ if (freopen("DEFAULTS", "r", stdin) != NULL) {
+ found_defaults = 1;
+ yyfile = "DEFAULTS";
+ } else {
+ if (freopen(PREFIX, "r", stdin) == NULL)
+ err(2, "%s", PREFIX);
+ yyfile = PREFIX;
+ }
+ if (*destdir != '\0') {
+ len = strlen(destdir);
+ while (len > 1 && destdir[len - 1] == '/')
+ destdir[--len] = '\0';
+ if (*srcdir == '\0')
+ get_srcdir();
+ } else {
+ strlcpy(destdir, CDIR, sizeof(destdir));
+ strlcat(destdir, PREFIX, sizeof(destdir));
+ }
+
+ SLIST_INIT(&cputype);
+ SLIST_INIT(&mkopt);
+ SLIST_INIT(&opt);
+ SLIST_INIT(&rmopts);
+ STAILQ_INIT(&cfgfiles);
+ STAILQ_INIT(&dtab);
+ STAILQ_INIT(&fntab);
+ STAILQ_INIT(&ftab);
+ STAILQ_INIT(&hints);
+ if (yyparse())
+ exit(3);
+
+ /*
+ * Ensure that required elements (machine, cpu, ident) are present.
+ */
+ if (machinename == NULL) {
+ printf("Specify machine type, e.g. ``machine i386''\n");
+ exit(1);
+ }
+ if (ident == NULL) {
+ printf("no ident line specified\n");
+ exit(1);
+ }
+ if (SLIST_EMPTY(&cputype)) {
+ printf("cpu type must be specified\n");
+ exit(1);
+ }
+ checkversion();
+
+ if (printmachine) {
+ printf("%s\t%s\n",machinename,machinearch);
+ exit(0);
+ }
+
+ /* Make compile directory */
+ p = path((char *)NULL);
+ if (stat(p, &buf)) {
+ if (mkdir(p, 0777))
+ err(2, "%s", p);
+ } else if (!S_ISDIR(buf.st_mode))
+ errx(EXIT_FAILURE, "%s isn't a directory", p);
+
+ configfile(); /* put config file into kernel*/
+ options(); /* make options .h files */
+ makefile(); /* build Makefile */
+ makeenv(); /* build env.c */
+ makehints(); /* build hints.c */
+ headers(); /* make a lot of .h files */
+ cleanheaders(p);
+ printf("Kernel build directory is %s\n", p);
+ printf("Don't forget to do ``make cleandepend && make depend''\n");
+ exit(0);
+}
+
+/*
+ * get_srcdir
+ * determine the root of the kernel source tree
+ * and save that in srcdir.
+ */
+static void
+get_srcdir(void)
+{
+ struct stat lg, phy;
+ char *p, *pwd;
+ int i;
+
+ if (realpath("../..", srcdir) == NULL)
+ err(EXIT_FAILURE, "Unable to find root of source tree");
+ if ((pwd = getenv("PWD")) != NULL && *pwd == '/' &&
+ (pwd = strdup(pwd)) != NULL) {
+ /* Remove the last two path components. */
+ for (i = 0; i < 2; i++) {
+ if ((p = strrchr(pwd, '/')) == NULL) {
+ free(pwd);
+ return;
+ }
+ *p = '\0';
+ }
+ if (stat(pwd, &lg) != -1 && stat(srcdir, &phy) != -1 &&
+ lg.st_dev == phy.st_dev && lg.st_ino == phy.st_ino)
+ strlcpy(srcdir, pwd, MAXPATHLEN);
+ free(pwd);
+ }
+}
+
+static void
+usage(void)
+{
+
+ fprintf(stderr,
+ "usage: config [-CgmpV] [-d destdir] [-s srcdir] sysname\n");
+ fprintf(stderr, " config -x kernel\n");
+ exit(EX_USAGE);
+}
+
+/*
+ * get_word
+ * returns EOF on end of file
+ * NULL on end of line
+ * pointer to the word otherwise
+ */
+char *
+get_word(FILE *fp)
+{
+ static char line[80];
+ int ch;
+ char *cp;
+ int escaped_nl = 0;
+
+begin:
+ while ((ch = getc(fp)) != EOF)
+ if (ch != ' ' && ch != '\t')
+ break;
+ if (ch == EOF)
+ return ((char *)EOF);
+ if (ch == '\\'){
+ escaped_nl = 1;
+ goto begin;
+ }
+ if (ch == '\n') {
+ if (escaped_nl){
+ escaped_nl = 0;
+ goto begin;
+ }
+ else
+ return (NULL);
+ }
+ cp = line;
+ *cp++ = ch;
+ /* Negation operator is a word by itself. */
+ if (ch == '!') {
+ *cp = 0;
+ return (line);
+ }
+ while ((ch = getc(fp)) != EOF) {
+ if (isspace(ch))
+ break;
+ *cp++ = ch;
+ }
+ *cp = 0;
+ if (ch == EOF)
+ return ((char *)EOF);
+ (void) ungetc(ch, fp);
+ return (line);
+}
+
+/*
+ * get_quoted_word
+ * like get_word but will accept something in double or single quotes
+ * (to allow embedded spaces).
+ */
+char *
+get_quoted_word(FILE *fp)
+{
+ static char line[256];
+ int ch;
+ char *cp;
+ int escaped_nl = 0;
+
+begin:
+ while ((ch = getc(fp)) != EOF)
+ if (ch != ' ' && ch != '\t')
+ break;
+ if (ch == EOF)
+ return ((char *)EOF);
+ if (ch == '\\'){
+ escaped_nl = 1;
+ goto begin;
+ }
+ if (ch == '\n') {
+ if (escaped_nl){
+ escaped_nl = 0;
+ goto begin;
+ }
+ else
+ return (NULL);
+ }
+ cp = line;
+ if (ch == '"' || ch == '\'') {
+ int quote = ch;
+
+ escaped_nl = 0;
+ while ((ch = getc(fp)) != EOF) {
+ if (ch == quote && !escaped_nl)
+ break;
+ if (ch == '\n' && !escaped_nl) {
+ *cp = 0;
+ printf("config: missing quote reading `%s'\n",
+ line);
+ exit(2);
+ }
+ if (ch == '\\' && !escaped_nl) {
+ escaped_nl = 1;
+ continue;
+ }
+ if (ch != quote && escaped_nl)
+ *cp++ = '\\';
+ *cp++ = ch;
+ escaped_nl = 0;
+ }
+ } else {
+ *cp++ = ch;
+ while ((ch = getc(fp)) != EOF) {
+ if (isspace(ch))
+ break;
+ *cp++ = ch;
+ }
+ if (ch != EOF)
+ (void) ungetc(ch, fp);
+ }
+ *cp = 0;
+ if (ch == EOF)
+ return ((char *)EOF);
+ return (line);
+}
+
+/*
+ * prepend the path to a filename
+ */
+char *
+path(const char *file)
+{
+ char *cp = NULL;
+
+ if (file)
+ asprintf(&cp, "%s/%s", destdir, file);
+ else
+ cp = strdup(destdir);
+ return (cp);
+}
+
+/*
+ * Generate configuration file based on actual settings. With this mode, user
+ * will be able to obtain and build conifguration file with one command.
+ */
+static void
+configfile_dynamic(struct sbuf *sb)
+{
+ struct cputype *cput;
+ struct device *d;
+ struct opt *ol;
+ char *lend;
+ unsigned int i;
+
+ asprintf(&lend, "\\n\\\n");
+ assert(lend != NULL);
+ sbuf_printf(sb, "options\t%s%s", OPT_AUTOGEN, lend);
+ sbuf_printf(sb, "ident\t%s%s", ident, lend);
+ sbuf_printf(sb, "machine\t%s%s", machinename, lend);
+ SLIST_FOREACH(cput, &cputype, cpu_next)
+ sbuf_printf(sb, "cpu\t%s%s", cput->cpu_name, lend);
+ SLIST_FOREACH(ol, &mkopt, op_next)
+ sbuf_printf(sb, "makeoptions\t%s=%s%s", ol->op_name,
+ ol->op_value, lend);
+ SLIST_FOREACH(ol, &opt, op_next) {
+ if (strncmp(ol->op_name, "DEV_", 4) == 0)
+ continue;
+ sbuf_printf(sb, "options\t%s", ol->op_name);
+ if (ol->op_value != NULL) {
+ sbuf_putc(sb, '=');
+ for (i = 0; i < strlen(ol->op_value); i++) {
+ if (ol->op_value[i] == '"')
+ sbuf_printf(sb, "\\%c",
+ ol->op_value[i]);
+ else
+ sbuf_printf(sb, "%c",
+ ol->op_value[i]);
+ }
+ sbuf_printf(sb, "%s", lend);
+ } else {
+ sbuf_printf(sb, "%s", lend);
+ }
+ }
+ /*
+ * Mark this file as containing everything we need.
+ */
+ STAILQ_FOREACH(d, &dtab, d_next)
+ sbuf_printf(sb, "device\t%s%s", d->d_name, lend);
+ free(lend);
+}
+
+/*
+ * Generate file from the configuration files.
+ */
+static void
+configfile_filebased(struct sbuf *sb)
+{
+ FILE *cff;
+ struct cfgfile *cf;
+ int i;
+
+ /*
+ * Try to read all configuration files. Since those will be present as
+ * C string in the macro, we have to slash their ends then the line
+ * wraps.
+ */
+ STAILQ_FOREACH(cf, &cfgfiles, cfg_next) {
+ cff = fopen(cf->cfg_path, "r");
+ if (cff == NULL) {
+ warn("Couldn't open file %s", cf->cfg_path);
+ continue;
+ }
+ while ((i = getc(cff)) != EOF) {
+ if (i == '\n')
+ sbuf_printf(sb, "\\n\\\n");
+ else if (i == '"' || i == '\'')
+ sbuf_printf(sb, "\\%c", i);
+ else
+ sbuf_putc(sb, i);
+ }
+ fclose(cff);
+ }
+}
+
+static void
+configfile(void)
+{
+ FILE *fo;
+ struct sbuf *sb;
+ char *p;
+
+ /* Add main configuration file to the list of files to be included */
+ cfgfile_add(PREFIX);
+ p = path("config.c.new");
+ fo = fopen(p, "w");
+ if (!fo)
+ err(2, "%s", p);
+ sb = sbuf_new(NULL, NULL, 2048, SBUF_AUTOEXTEND);
+ assert(sb != NULL);
+ sbuf_clear(sb);
+ if (filebased) {
+ /* Is needed, can be used for backward compatibility. */
+ configfile_filebased(sb);
+ } else {
+ configfile_dynamic(sb);
+ }
+ sbuf_finish(sb);
+ /*
+ * We print first part of the template, replace our tag with
+ * configuration files content and later continue writing our
+ * template.
+ */
+ p = strstr(kernconfstr, KERNCONFTAG);
+ if (p == NULL)
+ errx(EXIT_FAILURE, "Something went terribly wrong!");
+ *p = '\0';
+ fprintf(fo, "%s", kernconfstr);
+ fprintf(fo, "%s", sbuf_data(sb));
+ p += strlen(KERNCONFTAG);
+ fprintf(fo, "%s", p);
+ sbuf_delete(sb);
+ fclose(fo);
+ moveifchanged(path("config.c.new"), path("config.c"));
+ cfgfile_removeall();
+}
+
+/*
+ * moveifchanged --
+ * compare two files; rename if changed.
+ */
+void
+moveifchanged(const char *from_name, const char *to_name)
+{
+ char *p, *q;
+ int changed;
+ size_t tsize;
+ struct stat from_sb, to_sb;
+ int from_fd, to_fd;
+
+ changed = 0;
+
+ if ((from_fd = open(from_name, O_RDONLY)) < 0)
+ err(EX_OSERR, "moveifchanged open(%s)", from_name);
+
+ if ((to_fd = open(to_name, O_RDONLY)) < 0)
+ changed++;
+
+ if (!changed && fstat(from_fd, &from_sb) < 0)
+ err(EX_OSERR, "moveifchanged fstat(%s)", from_name);
+
+ if (!changed && fstat(to_fd, &to_sb) < 0)
+ err(EX_OSERR, "moveifchanged fstat(%s)", to_name);
+
+ if (!changed && from_sb.st_size != to_sb.st_size)
+ changed++;
+
+ tsize = (size_t)from_sb.st_size;
+
+ if (!changed) {
+ p = mmap(NULL, tsize, PROT_READ, MAP_SHARED, from_fd, (off_t)0);
+ if (p == MAP_FAILED)
+ err(EX_OSERR, "mmap %s", from_name);
+ q = mmap(NULL, tsize, PROT_READ, MAP_SHARED, to_fd, (off_t)0);
+ if (q == MAP_FAILED)
+ err(EX_OSERR, "mmap %s", to_name);
+
+ changed = memcmp(p, q, tsize);
+ munmap(p, tsize);
+ munmap(q, tsize);
+ }
+ if (changed) {
+ if (rename(from_name, to_name) < 0)
+ err(EX_OSERR, "rename(%s, %s)", from_name, to_name);
+ } else {
+ if (unlink(from_name) < 0)
+ err(EX_OSERR, "unlink(%s)", from_name);
+ }
+}
+
+static void
+cleanheaders(char *p)
+{
+ DIR *dirp;
+ struct dirent *dp;
+ struct file_list *fl;
+ struct hdr_list *hl;
+ size_t len;
+
+ remember("y.tab.h");
+ remember("setdefs.h");
+ STAILQ_FOREACH(fl, &ftab, f_next)
+ remember(fl->f_fn);
+
+ /*
+ * Scan the build directory and clean out stuff that looks like
+ * it might have been a leftover NFOO header, etc.
+ */
+ if ((dirp = opendir(p)) == NULL)
+ err(EX_OSERR, "opendir %s", p);
+ while ((dp = readdir(dirp)) != NULL) {
+ len = strlen(dp->d_name);
+ /* Skip non-headers */
+ if (len < 2 || dp->d_name[len - 2] != '.' ||
+ dp->d_name[len - 1] != 'h')
+ continue;
+ /* Skip special stuff, eg: bus_if.h, but check opt_*.h */
+ if (strchr(dp->d_name, '_') &&
+ strncmp(dp->d_name, "opt_", 4) != 0)
+ continue;
+ /* Check if it is a target file */
+ for (hl = htab; hl != NULL; hl = hl->h_next) {
+ if (eq(dp->d_name, hl->h_name)) {
+ break;
+ }
+ }
+ if (hl)
+ continue;
+ printf("Removing stale header: %s\n", dp->d_name);
+ if (unlink(path(dp->d_name)) == -1)
+ warn("unlink %s", dp->d_name);
+ }
+ (void)closedir(dirp);
+}
+
+void
+remember(const char *file)
+{
+ char *s;
+ struct hdr_list *hl;
+
+ if ((s = strrchr(file, '/')) != NULL)
+ s = ns(s + 1);
+ else
+ s = ns(file);
+
+ if (strchr(s, '_') && strncmp(s, "opt_", 4) != 0) {
+ free(s);
+ return;
+ }
+ for (hl = htab; hl != NULL; hl = hl->h_next) {
+ if (eq(s, hl->h_name)) {
+ free(s);
+ return;
+ }
+ }
+ hl = calloc(1, sizeof(*hl));
+ if (hl == NULL)
+ err(EXIT_FAILURE, "calloc");
+ hl->h_name = s;
+ hl->h_next = htab;
+ htab = hl;
+}
+
+/*
+ * This one is quick hack. Will be probably moved to elf(3) interface.
+ * It takes kernel configuration file name, passes it as an argument to
+ * elfdump -a, which output is parsed by some UNIX tools...
+ */
+static void
+kernconfdump(const char *file)
+{
+ struct stat st;
+ FILE *fp, *pp;
+ int error, osz, r;
+ unsigned int i, off, size, t1, t2, align;
+ char *cmd, *o;
+
+ r = open(file, O_RDONLY);
+ if (r == -1)
+ err(EXIT_FAILURE, "Couldn't open file '%s'", file);
+ error = fstat(r, &st);
+ if (error == -1)
+ err(EXIT_FAILURE, "fstat() failed");
+ if (S_ISDIR(st.st_mode))
+ errx(EXIT_FAILURE, "'%s' is a directory", file);
+ fp = fdopen(r, "r");
+ if (fp == NULL)
+ err(EXIT_FAILURE, "fdopen() failed");
+ osz = 1024;
+ o = calloc(1, osz);
+ if (o == NULL)
+ err(EXIT_FAILURE, "Couldn't allocate memory");
+ /* ELF note section header. */
+ asprintf(&cmd, "/usr/bin/elfdump -c %s | grep -A 8 kern_conf"
+ "| tail -5 | cut -d ' ' -f 2 | paste - - - - -", file);
+ if (cmd == NULL)
+ errx(EXIT_FAILURE, "asprintf() failed");
+ pp = popen(cmd, "r");
+ if (pp == NULL)
+ errx(EXIT_FAILURE, "popen() failed");
+ free(cmd);
+ (void)fread(o, osz, 1, pp);
+ pclose(pp);
+ r = sscanf(o, "%d%d%d%d%d", &off, &size, &t1, &t2, &align);
+ free(o);
+ if (r != 5)
+ errx(EXIT_FAILURE, "File %s doesn't contain configuration "
+ "file. Either unsupported, or not compiled with "
+ "INCLUDE_CONFIG_FILE", file);
+ r = fseek(fp, off, SEEK_CUR);
+ if (r != 0)
+ err(EXIT_FAILURE, "fseek() failed");
+ for (i = 0; i < size; i++) {
+ r = fgetc(fp);
+ if (r == EOF)
+ break;
+ if (r == '\0') {
+ assert(i == size - 1 &&
+ ("\\0 found in the middle of a file"));
+ break;
+ }
+ fputc(r, stdout);
+ }
+ fclose(fp);
+}
+
+static void
+badversion(int versreq)
+{
+ fprintf(stderr, "ERROR: version of config(8) does not match kernel!\n");
+ fprintf(stderr, "config version = %d, ", CONFIGVERS);
+ fprintf(stderr, "version required = %d\n\n", versreq);
+ fprintf(stderr, "Make sure that /usr/src/usr.sbin/config is in sync\n");
+ fprintf(stderr, "with your /usr/src/sys and install a new config binary\n");
+ fprintf(stderr, "before trying this again.\n\n");
+ fprintf(stderr, "If running the new config fails check your config\n");
+ fprintf(stderr, "file against the GENERIC or LINT config files for\n");
+ fprintf(stderr, "changes in config syntax, or option/device naming\n");
+ fprintf(stderr, "conventions\n\n");
+ exit(1);
+}
+
+static void
+checkversion(void)
+{
+ FILE *ifp;
+ char line[BUFSIZ];
+ int versreq;
+
+ ifp = open_makefile_template();
+ while (fgets(line, BUFSIZ, ifp) != 0) {
+ if (*line != '%')
+ continue;
+ if (strncmp(line, "%VERSREQ=", 9) != 0)
+ continue;
+ versreq = atoi(line + 9);
+ if (MAJOR_VERS(versreq) == MAJOR_VERS(CONFIGVERS) &&
+ versreq <= CONFIGVERS)
+ continue;
+ badversion(versreq);
+ }
+ fclose(ifp);
+}
diff --git a/usr.sbin/config/mkheaders.c b/usr.sbin/config/mkheaders.c
new file mode 100644
index 0000000..fd7f1b3
--- /dev/null
+++ b/usr.sbin/config/mkheaders.c
@@ -0,0 +1,66 @@
+/*
+ * 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[] = "@(#)mkheaders.c 8.1 (Berkeley) 6/6/93";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+/*
+ * Make all the .h files for the optional entries
+ */
+
+#include <ctype.h>
+#include <err.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/param.h>
+#include "config.h"
+#include "y.tab.h"
+
+void
+headers(void)
+{
+ struct device *dp;
+ int errors;
+
+ errors = 0;
+ STAILQ_FOREACH(dp, &dtab, d_next) {
+ if (!(dp->d_done & DEVDONE)) {
+ warnx("Error: device \"%s\" is unknown",
+ dp->d_name);
+ errors++;
+ }
+ }
+ if (errors)
+ errx(1, "%d errors", errors);
+}
diff --git a/usr.sbin/config/mkmakefile.c b/usr.sbin/config/mkmakefile.c
new file mode 100644
index 0000000..877f934
--- /dev/null
+++ b/usr.sbin/config/mkmakefile.c
@@ -0,0 +1,779 @@
+/*
+ * Copyright (c) 1980, 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.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)mkmakefile.c 8.1 (Berkeley) 6/6/93";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+/*
+ * Build the makefile for the system, from
+ * the information in the files files and the
+ * additional files for the machine being compiled to.
+ */
+
+#include <ctype.h>
+#include <err.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/param.h>
+#include "y.tab.h"
+#include "config.h"
+#include "configvers.h"
+
+static char *tail(char *);
+static void do_clean(FILE *);
+static void do_rules(FILE *);
+static void do_xxfiles(char *, FILE *);
+static void do_objs(FILE *);
+static void do_before_depend(FILE *);
+static int opteq(const char *, const char *);
+static void read_files(void);
+
+static void errout(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ exit(1);
+}
+
+/*
+ * Lookup a file, by name.
+ */
+static struct file_list *
+fl_lookup(char *file)
+{
+ struct file_list *fp;
+
+ STAILQ_FOREACH(fp, &ftab, f_next) {
+ if (eq(fp->f_fn, file))
+ return (fp);
+ }
+ return (0);
+}
+
+/*
+ * Make a new file list entry
+ */
+static struct file_list *
+new_fent(void)
+{
+ struct file_list *fp;
+
+ fp = (struct file_list *) calloc(1, sizeof *fp);
+ if (fp == NULL)
+ err(EXIT_FAILURE, "calloc");
+ STAILQ_INSERT_TAIL(&ftab, fp, f_next);
+ return (fp);
+}
+
+/*
+ * Open the correct Makefile and return it, or error out.
+ */
+FILE *
+open_makefile_template(void)
+{
+ FILE *ifp;
+ char line[BUFSIZ];
+
+ snprintf(line, sizeof(line), "../../conf/Makefile.%s", machinename);
+ ifp = fopen(line, "r");
+ if (ifp == 0) {
+ snprintf(line, sizeof(line), "Makefile.%s", machinename);
+ ifp = fopen(line, "r");
+ }
+ if (ifp == 0)
+ err(1, "%s", line);
+ return (ifp);
+}
+
+/*
+ * Build the makefile from the skeleton
+ */
+void
+makefile(void)
+{
+ FILE *ifp, *ofp;
+ char line[BUFSIZ];
+ struct opt *op, *t;
+
+ read_files();
+ ifp = open_makefile_template();
+ ofp = fopen(path("Makefile.new"), "w");
+ if (ofp == 0)
+ err(1, "%s", path("Makefile.new"));
+ fprintf(ofp, "KERN_IDENT=%s\n", ident);
+ fprintf(ofp, "MACHINE=%s\n", machinename);
+ fprintf(ofp, "MACHINE_ARCH=%s\n", machinearch);
+ SLIST_FOREACH_SAFE(op, &mkopt, op_next, t) {
+ fprintf(ofp, "%s=%s", op->op_name, op->op_value);
+ while ((op = SLIST_NEXT(op, op_append)) != NULL)
+ fprintf(ofp, " %s", op->op_value);
+ fprintf(ofp, "\n");
+ }
+ if (debugging)
+ fprintf(ofp, "DEBUG=-g\n");
+ if (profiling)
+ fprintf(ofp, "PROFLEVEL=%d\n", profiling);
+ if (*srcdir != '\0')
+ fprintf(ofp,"S=%s\n", srcdir);
+ while (fgets(line, BUFSIZ, ifp) != NULL) {
+ if (*line != '%') {
+ fprintf(ofp, "%s", line);
+ continue;
+ }
+ if (eq(line, "%BEFORE_DEPEND\n"))
+ do_before_depend(ofp);
+ else if (eq(line, "%OBJS\n"))
+ do_objs(ofp);
+ else if (strncmp(line, "%FILES.", 7) == 0)
+ do_xxfiles(line, ofp);
+ else if (eq(line, "%RULES\n"))
+ do_rules(ofp);
+ else if (eq(line, "%CLEAN\n"))
+ do_clean(ofp);
+ else if (strncmp(line, "%VERSREQ=", 9) == 0)
+ line[0] = '\0'; /* handled elsewhere */
+ else
+ fprintf(stderr,
+ "Unknown %% construct in generic makefile: %s",
+ line);
+ }
+ (void) fclose(ifp);
+ (void) fclose(ofp);
+ moveifchanged(path("Makefile.new"), path("Makefile"));
+}
+
+/*
+ * Build hints.c from the skeleton
+ */
+void
+makehints(void)
+{
+ FILE *ifp, *ofp;
+ char line[BUFSIZ];
+ char *s;
+ struct hint *hint;
+
+ ofp = fopen(path("hints.c.new"), "w");
+ if (ofp == NULL)
+ err(1, "%s", path("hints.c.new"));
+ fprintf(ofp, "#include <sys/types.h>\n");
+ fprintf(ofp, "#include <sys/systm.h>\n");
+ fprintf(ofp, "\n");
+ fprintf(ofp, "int hintmode = %d;\n", hintmode);
+ fprintf(ofp, "char static_hints[] = {\n");
+ STAILQ_FOREACH(hint, &hints, hint_next) {
+ ifp = fopen(hint->hint_name, "r");
+ if (ifp == NULL)
+ err(1, "%s", hint->hint_name);
+ while (fgets(line, BUFSIZ, ifp) != NULL) {
+ /* zap trailing CR and/or LF */
+ while ((s = strrchr(line, '\n')) != NULL)
+ *s = '\0';
+ while ((s = strrchr(line, '\r')) != NULL)
+ *s = '\0';
+ /* remove # comments */
+ s = strchr(line, '#');
+ if (s)
+ *s = '\0';
+ /* remove any whitespace and " characters */
+ s = line;
+ while (*s) {
+ if (*s == ' ' || *s == '\t' || *s == '"') {
+ while (*s) {
+ s[0] = s[1];
+ s++;
+ }
+ /* start over */
+ s = line;
+ continue;
+ }
+ s++;
+ }
+ /* anything left? */
+ if (*line == '\0')
+ continue;
+ fprintf(ofp, "\"%s\\0\"\n", line);
+ }
+ fclose(ifp);
+ }
+ fprintf(ofp, "\"\\0\"\n};\n");
+ fclose(ofp);
+ moveifchanged(path("hints.c.new"), path("hints.c"));
+}
+
+/*
+ * Build env.c from the skeleton
+ */
+void
+makeenv(void)
+{
+ FILE *ifp, *ofp;
+ char line[BUFSIZ];
+ char *s;
+
+ if (env) {
+ ifp = fopen(env, "r");
+ if (ifp == NULL)
+ err(1, "%s", env);
+ } else {
+ ifp = NULL;
+ }
+ ofp = fopen(path("env.c.new"), "w");
+ if (ofp == NULL)
+ err(1, "%s", path("env.c.new"));
+ fprintf(ofp, "#include <sys/types.h>\n");
+ fprintf(ofp, "#include <sys/systm.h>\n");
+ fprintf(ofp, "\n");
+ fprintf(ofp, "int envmode = %d;\n", envmode);
+ fprintf(ofp, "char static_env[] = {\n");
+ if (ifp) {
+ while (fgets(line, BUFSIZ, ifp) != NULL) {
+ /* zap trailing CR and/or LF */
+ while ((s = strrchr(line, '\n')) != NULL)
+ *s = '\0';
+ while ((s = strrchr(line, '\r')) != NULL)
+ *s = '\0';
+ /* remove # comments */
+ s = strchr(line, '#');
+ if (s)
+ *s = '\0';
+ /* remove any whitespace and " characters */
+ s = line;
+ while (*s) {
+ if (*s == ' ' || *s == '\t' || *s == '"') {
+ while (*s) {
+ s[0] = s[1];
+ s++;
+ }
+ /* start over */
+ s = line;
+ continue;
+ }
+ s++;
+ }
+ /* anything left? */
+ if (*line == '\0')
+ continue;
+ fprintf(ofp, "\"%s\\0\"\n", line);
+ }
+ }
+ fprintf(ofp, "\"\\0\"\n};\n");
+ if (ifp)
+ fclose(ifp);
+ fclose(ofp);
+ moveifchanged(path("env.c.new"), path("env.c"));
+}
+
+static void
+read_file(char *fname)
+{
+ char ifname[MAXPATHLEN];
+ FILE *fp;
+ struct file_list *tp;
+ struct device *dp;
+ struct opt *op;
+ char *wd, *this, *compilewith, *depends, *clean, *warning;
+ const char *objprefix;
+ int compile, match, nreqs, std, filetype, not,
+ imp_rule, no_obj, before_depend, nowerror;
+
+ fp = fopen(fname, "r");
+ if (fp == 0)
+ err(1, "%s", fname);
+next:
+ /*
+ * include "filename"
+ * filename [ standard | optional ]
+ * [ dev* [ | dev* ... ] | profiling-routine ] [ no-obj ]
+ * [ compile-with "compile rule" [no-implicit-rule] ]
+ * [ dependency "dependency-list"] [ before-depend ]
+ * [ clean "file-list"] [ warning "text warning" ]
+ * [ obj-prefix "file prefix"]
+ */
+ wd = get_word(fp);
+ if (wd == (char *)EOF) {
+ (void) fclose(fp);
+ return;
+ }
+ if (wd == 0)
+ goto next;
+ if (wd[0] == '#')
+ {
+ while (((wd = get_word(fp)) != (char *)EOF) && wd)
+ ;
+ goto next;
+ }
+ if (eq(wd, "include")) {
+ wd = get_quoted_word(fp);
+ if (wd == (char *)EOF || wd == 0)
+ errout("%s: missing include filename.\n", fname);
+ (void) snprintf(ifname, sizeof(ifname), "../../%s", wd);
+ read_file(ifname);
+ while (((wd = get_word(fp)) != (char *)EOF) && wd)
+ ;
+ goto next;
+ }
+ this = ns(wd);
+ wd = get_word(fp);
+ if (wd == (char *)EOF)
+ return;
+ if (wd == 0)
+ errout("%s: No type for %s.\n", fname, this);
+ tp = fl_lookup(this);
+ compile = 0;
+ match = 1;
+ nreqs = 0;
+ compilewith = 0;
+ depends = 0;
+ clean = 0;
+ warning = 0;
+ std = 0;
+ imp_rule = 0;
+ no_obj = 0;
+ before_depend = 0;
+ nowerror = 0;
+ not = 0;
+ filetype = NORMAL;
+ objprefix = "";
+ if (eq(wd, "standard"))
+ std = 1;
+ else if (!eq(wd, "optional"))
+ errout("%s: \"%s\" %s must be optional or standard\n",
+ fname, wd, this);
+ for (wd = get_word(fp); wd; wd = get_word(fp)) {
+ if (wd == (char *)EOF)
+ return;
+ if (eq(wd, "!")) {
+ not = 1;
+ continue;
+ }
+ if (eq(wd, "|")) {
+ if (nreqs == 0)
+ errout("%s: syntax error describing %s\n",
+ fname, this);
+ compile += match;
+ match = 1;
+ nreqs = 0;
+ continue;
+ }
+ if (eq(wd, "no-obj")) {
+ no_obj++;
+ continue;
+ }
+ if (eq(wd, "no-implicit-rule")) {
+ if (compilewith == 0)
+ errout("%s: alternate rule required when "
+ "\"no-implicit-rule\" is specified for"
+ " %s.\n",
+ fname, this);
+ imp_rule++;
+ continue;
+ }
+ if (eq(wd, "before-depend")) {
+ before_depend++;
+ continue;
+ }
+ if (eq(wd, "dependency")) {
+ wd = get_quoted_word(fp);
+ if (wd == (char *)EOF || wd == 0)
+ errout("%s: %s missing dependency string.\n",
+ fname, this);
+ depends = ns(wd);
+ continue;
+ }
+ if (eq(wd, "clean")) {
+ wd = get_quoted_word(fp);
+ if (wd == (char *)EOF || wd == 0)
+ errout("%s: %s missing clean file list.\n",
+ fname, this);
+ clean = ns(wd);
+ continue;
+ }
+ if (eq(wd, "compile-with")) {
+ wd = get_quoted_word(fp);
+ if (wd == (char *)EOF || wd == 0)
+ errout("%s: %s missing compile command string.\n",
+ fname, this);
+ compilewith = ns(wd);
+ continue;
+ }
+ if (eq(wd, "warning")) {
+ wd = get_quoted_word(fp);
+ if (wd == (char *)EOF || wd == 0)
+ errout("%s: %s missing warning text string.\n",
+ fname, this);
+ warning = ns(wd);
+ continue;
+ }
+ if (eq(wd, "obj-prefix")) {
+ wd = get_quoted_word(fp);
+ if (wd == (char *)EOF || wd == 0)
+ errout("%s: %s missing object prefix string.\n",
+ fname, this);
+ objprefix = ns(wd);
+ continue;
+ }
+ if (eq(wd, "nowerror")) {
+ nowerror = 1;
+ continue;
+ }
+ if (eq(wd, "local")) {
+ filetype = LOCAL;
+ continue;
+ }
+ if (eq(wd, "no-depend")) {
+ filetype = NODEPEND;
+ continue;
+ }
+ nreqs++;
+ if (eq(wd, "profiling-routine")) {
+ filetype = PROFILING;
+ continue;
+ }
+ if (std)
+ errout("standard entry %s has optional inclusion specifier %s!\n",
+ this, wd);
+ STAILQ_FOREACH(dp, &dtab, d_next)
+ if (eq(dp->d_name, wd)) {
+ if (not)
+ match = 0;
+ else
+ dp->d_done |= DEVDONE;
+ goto nextparam;
+ }
+ SLIST_FOREACH(op, &opt, op_next)
+ if (op->op_value == 0 && opteq(op->op_name, wd)) {
+ if (not)
+ match = 0;
+ goto nextparam;
+ }
+ match &= not;
+nextparam:;
+ not = 0;
+ }
+ compile += match;
+ if (compile && tp == NULL) {
+ if (std == 0 && nreqs == 0)
+ errout("%s: what is %s optional on?\n",
+ fname, this);
+ if (filetype == PROFILING && profiling == 0)
+ goto next;
+ tp = new_fent();
+ tp->f_fn = this;
+ tp->f_type = filetype;
+ if (imp_rule)
+ tp->f_flags |= NO_IMPLCT_RULE;
+ if (no_obj)
+ tp->f_flags |= NO_OBJ;
+ if (before_depend)
+ tp->f_flags |= BEFORE_DEPEND;
+ if (nowerror)
+ tp->f_flags |= NOWERROR;
+ tp->f_compilewith = compilewith;
+ tp->f_depends = depends;
+ tp->f_clean = clean;
+ tp->f_warn = warning;
+ tp->f_objprefix = objprefix;
+ }
+ goto next;
+}
+
+/*
+ * Read in the information about files used in making the system.
+ * Store it in the ftab linked list.
+ */
+static void
+read_files(void)
+{
+ char fname[MAXPATHLEN];
+ struct files_name *nl, *tnl;
+
+ (void) snprintf(fname, sizeof(fname), "../../conf/files");
+ read_file(fname);
+ (void) snprintf(fname, sizeof(fname),
+ "../../conf/files.%s", machinename);
+ read_file(fname);
+ for (nl = STAILQ_FIRST(&fntab); nl != NULL; nl = tnl) {
+ read_file(nl->f_name);
+ tnl = STAILQ_NEXT(nl, f_next);
+ free(nl->f_name);
+ free(nl);
+ }
+}
+
+static int
+opteq(const char *cp, const char *dp)
+{
+ char c, d;
+
+ for (; ; cp++, dp++) {
+ if (*cp != *dp) {
+ c = isupper(*cp) ? tolower(*cp) : *cp;
+ d = isupper(*dp) ? tolower(*dp) : *dp;
+ if (c != d)
+ return (0);
+ }
+ if (*cp == 0)
+ return (1);
+ }
+}
+
+static void
+do_before_depend(FILE *fp)
+{
+ struct file_list *tp;
+ int lpos, len;
+
+ fputs("BEFORE_DEPEND=", fp);
+ lpos = 15;
+ STAILQ_FOREACH(tp, &ftab, f_next)
+ if (tp->f_flags & BEFORE_DEPEND) {
+ len = strlen(tp->f_fn);
+ if ((len = 3 + len) + lpos > 72) {
+ lpos = 8;
+ fputs("\\\n\t", fp);
+ }
+ if (tp->f_flags & NO_IMPLCT_RULE)
+ fprintf(fp, "%s ", tp->f_fn);
+ else
+ fprintf(fp, "$S/%s ", tp->f_fn);
+ lpos += len + 1;
+ }
+ if (lpos != 8)
+ putc('\n', fp);
+}
+
+static void
+do_objs(FILE *fp)
+{
+ struct file_list *tp;
+ int lpos, len;
+ char *cp, och, *sp;
+
+ fprintf(fp, "OBJS=");
+ lpos = 6;
+ STAILQ_FOREACH(tp, &ftab, f_next) {
+ if (tp->f_flags & NO_OBJ)
+ continue;
+ sp = tail(tp->f_fn);
+ cp = sp + (len = strlen(sp)) - 1;
+ och = *cp;
+ *cp = 'o';
+ len += strlen(tp->f_objprefix);
+ if (len + lpos > 72) {
+ lpos = 8;
+ fprintf(fp, "\\\n\t");
+ }
+ fprintf(fp, "%s%s ", tp->f_objprefix, sp);
+ lpos += len + 1;
+ *cp = och;
+ }
+ if (lpos != 8)
+ putc('\n', fp);
+}
+
+static void
+do_xxfiles(char *tag, FILE *fp)
+{
+ struct file_list *tp;
+ int lpos, len, slen;
+ char *suff, *SUFF;
+
+ if (tag[strlen(tag) - 1] == '\n')
+ tag[strlen(tag) - 1] = '\0';
+
+ suff = ns(tag + 7);
+ SUFF = ns(suff);
+ raisestr(SUFF);
+ slen = strlen(suff);
+
+ fprintf(fp, "%sFILES=", SUFF);
+ free(SUFF);
+ lpos = 8;
+ STAILQ_FOREACH(tp, &ftab, f_next)
+ if (tp->f_type != NODEPEND) {
+ len = strlen(tp->f_fn);
+ if (tp->f_fn[len - slen - 1] != '.')
+ continue;
+ if (strcasecmp(&tp->f_fn[len - slen], suff) != 0)
+ continue;
+ if ((len = 3 + len) + lpos > 72) {
+ lpos = 8;
+ fputs("\\\n\t", fp);
+ }
+ if (tp->f_type != LOCAL)
+ fprintf(fp, "$S/%s ", tp->f_fn);
+ else
+ fprintf(fp, "%s ", tp->f_fn);
+ lpos += len + 1;
+ }
+ free(suff);
+ if (lpos != 8)
+ putc('\n', fp);
+}
+
+static char *
+tail(char *fn)
+{
+ char *cp;
+
+ cp = strrchr(fn, '/');
+ if (cp == 0)
+ return (fn);
+ return (cp+1);
+}
+
+/*
+ * Create the makerules for each file
+ * which is part of the system.
+ */
+static void
+do_rules(FILE *f)
+{
+ char *cp, *np, och;
+ struct file_list *ftp;
+ char *compilewith;
+ char cmd[128];
+
+ STAILQ_FOREACH(ftp, &ftab, f_next) {
+ if (ftp->f_warn)
+ fprintf(stderr, "WARNING: %s\n", ftp->f_warn);
+ cp = (np = ftp->f_fn) + strlen(ftp->f_fn) - 1;
+ och = *cp;
+ if (ftp->f_flags & NO_IMPLCT_RULE) {
+ if (ftp->f_depends)
+ fprintf(f, "%s%s: %s\n",
+ ftp->f_objprefix, np, ftp->f_depends);
+ else
+ fprintf(f, "%s%s: \n", ftp->f_objprefix, np);
+ }
+ else {
+ *cp = '\0';
+ if (och == 'o') {
+ fprintf(f, "%s%so:\n\t-cp $S/%so .\n\n",
+ ftp->f_objprefix, tail(np), np);
+ continue;
+ }
+ if (ftp->f_depends) {
+ fprintf(f, "%s%sln: $S/%s%c %s\n",
+ ftp->f_objprefix, tail(np), np, och,
+ ftp->f_depends);
+ fprintf(f, "\t${NORMAL_LINT}\n\n");
+ fprintf(f, "%s%so: $S/%s%c %s\n",
+ ftp->f_objprefix, tail(np), np, och,
+ ftp->f_depends);
+ }
+ else {
+ fprintf(f, "%s%sln: $S/%s%c\n",
+ ftp->f_objprefix, tail(np), np, och);
+ fprintf(f, "\t${NORMAL_LINT}\n\n");
+ fprintf(f, "%s%so: $S/%s%c\n",
+ ftp->f_objprefix, tail(np), np, och);
+ }
+ }
+ compilewith = ftp->f_compilewith;
+ if (compilewith == 0) {
+ const char *ftype = NULL;
+
+ switch (ftp->f_type) {
+ case NORMAL:
+ ftype = "NORMAL";
+ break;
+ case PROFILING:
+ if (!profiling)
+ continue;
+ ftype = "PROFILE";
+ break;
+ default:
+ fprintf(stderr,
+ "config: don't know rules for %s\n", np);
+ break;
+ }
+ snprintf(cmd, sizeof(cmd),
+ "${%s_%c%s}", ftype,
+ toupper(och),
+ ftp->f_flags & NOWERROR ? "_NOWERROR" : "");
+ compilewith = cmd;
+ }
+ *cp = och;
+ if (strlen(ftp->f_objprefix))
+ fprintf(f, "\t%s $S/%s\n", compilewith, np);
+ else
+ fprintf(f, "\t%s\n", compilewith);
+
+ if (!(ftp->f_flags & NO_OBJ))
+ fprintf(f, "\t${NORMAL_CTFCONVERT}\n\n");
+ else
+ fprintf(f, "\n");
+ }
+}
+
+static void
+do_clean(FILE *fp)
+{
+ struct file_list *tp;
+ int lpos, len;
+
+ fputs("CLEAN=", fp);
+ lpos = 7;
+ STAILQ_FOREACH(tp, &ftab, f_next)
+ if (tp->f_clean) {
+ len = strlen(tp->f_clean);
+ if (len + lpos > 72) {
+ lpos = 8;
+ fputs("\\\n\t", fp);
+ }
+ fprintf(fp, "%s ", tp->f_clean);
+ lpos += len + 1;
+ }
+ if (lpos != 8)
+ putc('\n', fp);
+}
+
+char *
+raisestr(char *str)
+{
+ char *cp = str;
+
+ while (*str) {
+ if (islower(*str))
+ *str = toupper(*str);
+ str++;
+ }
+ return (cp);
+}
diff --git a/usr.sbin/config/mkoptions.c b/usr.sbin/config/mkoptions.c
new file mode 100644
index 0000000..b5d6e34
--- /dev/null
+++ b/usr.sbin/config/mkoptions.c
@@ -0,0 +1,435 @@
+/*
+ * Copyright (c) 1995 Peter Wemm
+ * 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[] = "@(#)mkheaders.c 8.1 (Berkeley) 6/6/93";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+/*
+ * Make all the .h files for the optional entries
+ */
+
+#include <ctype.h>
+#include <err.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/param.h>
+#include "config.h"
+#include "y.tab.h"
+
+static struct users {
+ int u_default;
+ int u_min;
+ int u_max;
+} users = { 8, 2, 512 };
+
+static char *lower(char *);
+static void read_options(void);
+static void do_option(char *);
+static char *tooption(char *);
+
+void
+options(void)
+{
+ char buf[40];
+ struct cputype *cp;
+ struct opt_list *ol;
+ struct opt *op;
+
+ /* Fake the cpu types as options. */
+ SLIST_FOREACH(cp, &cputype, cpu_next) {
+ op = (struct opt *)calloc(1, sizeof(*op));
+ if (op == NULL)
+ err(EXIT_FAILURE, "calloc");
+ op->op_name = ns(cp->cpu_name);
+ SLIST_INSERT_HEAD(&opt, op, op_next);
+ }
+
+ if (maxusers == 0) {
+ /* fprintf(stderr, "maxusers not specified; will auto-size\n"); */
+ } else if (maxusers < users.u_min) {
+ fprintf(stderr, "minimum of %d maxusers assumed\n",
+ users.u_min);
+ maxusers = users.u_min;
+ } else if (maxusers > users.u_max)
+ fprintf(stderr, "warning: maxusers > %d (%d)\n",
+ users.u_max, maxusers);
+
+ /* Fake MAXUSERS as an option. */
+ op = (struct opt *)calloc(1, sizeof(*op));
+ if (op == NULL)
+ err(EXIT_FAILURE, "calloc");
+ op->op_name = ns("MAXUSERS");
+ snprintf(buf, sizeof(buf), "%d", maxusers);
+ op->op_value = ns(buf);
+ SLIST_INSERT_HEAD(&opt, op, op_next);
+
+ read_options();
+
+ /* Fake the value of MACHINE_ARCH as an option if necessary */
+ SLIST_FOREACH(ol, &otab, o_next) {
+ if (strcasecmp(ol->o_name, machinearch) != 0)
+ continue;
+
+ op = (struct opt *)calloc(1, sizeof(*op));
+ if (op == NULL)
+ err(EXIT_FAILURE, "calloc");
+ op->op_name = ns(ol->o_name);
+ SLIST_INSERT_HEAD(&opt, op, op_next);
+ break;
+ }
+
+ SLIST_FOREACH(op, &opt, op_next) {
+ SLIST_FOREACH(ol, &otab, o_next) {
+ if (eq(op->op_name, ol->o_name) &&
+ (ol->o_flags & OL_ALIAS)) {
+ fprintf(stderr, "Mapping option %s to %s.\n",
+ op->op_name, ol->o_file);
+ op->op_name = ol->o_file;
+ break;
+ }
+ }
+ }
+ SLIST_FOREACH(ol, &otab, o_next)
+ do_option(ol->o_name);
+ SLIST_FOREACH(op, &opt, op_next) {
+ if (!op->op_ownfile && strncmp(op->op_name, "DEV_", 4)) {
+ fprintf(stderr, "%s: unknown option \"%s\"\n",
+ PREFIX, op->op_name);
+ exit(1);
+ }
+ }
+}
+
+/*
+ * Generate an <options>.h file
+ */
+
+static void
+do_option(char *name)
+{
+ char *file, *inw;
+ const char *basefile;
+ struct opt_list *ol;
+ struct opt *op;
+ struct opt_head op_head;
+ FILE *inf, *outf;
+ char *value;
+ char *oldvalue;
+ int seen;
+ int tidy;
+
+ file = tooption(name);
+ /*
+ * Check to see if the option was specified..
+ */
+ value = NULL;
+ SLIST_FOREACH(op, &opt, op_next) {
+ if (eq(name, op->op_name)) {
+ oldvalue = value;
+ value = op->op_value;
+ if (value == NULL)
+ value = ns("1");
+ if (oldvalue != NULL && !eq(value, oldvalue))
+ fprintf(stderr,
+ "%s: option \"%s\" redefined from %s to %s\n",
+ PREFIX, op->op_name, oldvalue,
+ value);
+ op->op_ownfile++;
+ }
+ }
+
+ remember(file);
+ inf = fopen(file, "r");
+ if (inf == 0) {
+ outf = fopen(file, "w");
+ if (outf == 0)
+ err(1, "%s", file);
+
+ /* was the option in the config file? */
+ if (value) {
+ fprintf(outf, "#define %s %s\n", name, value);
+ } /* else empty file */
+
+ (void)fclose(outf);
+ return;
+ }
+ basefile = "";
+ SLIST_FOREACH(ol, &otab, o_next)
+ if (eq(name, ol->o_name)) {
+ basefile = ol->o_file;
+ break;
+ }
+ oldvalue = NULL;
+ SLIST_INIT(&op_head);
+ seen = 0;
+ tidy = 0;
+ for (;;) {
+ char *cp;
+ char *invalue;
+
+ /* get the #define */
+ if ((inw = get_word(inf)) == 0 || inw == (char *)EOF)
+ break;
+ /* get the option name */
+ if ((inw = get_word(inf)) == 0 || inw == (char *)EOF)
+ break;
+ inw = ns(inw);
+ /* get the option value */
+ if ((cp = get_word(inf)) == 0 || cp == (char *)EOF)
+ break;
+ /* option value */
+ invalue = ns(cp); /* malloced */
+ if (eq(inw, name)) {
+ oldvalue = invalue;
+ invalue = value;
+ seen++;
+ }
+ SLIST_FOREACH(ol, &otab, o_next)
+ if (eq(inw, ol->o_name))
+ break;
+ if (!eq(inw, name) && !ol) {
+ fprintf(stderr,
+ "WARNING: unknown option `%s' removed from %s\n",
+ inw, file);
+ tidy++;
+ } else if (ol != NULL && !eq(basefile, ol->o_file)) {
+ fprintf(stderr,
+ "WARNING: option `%s' moved from %s to %s\n",
+ inw, basefile, ol->o_file);
+ tidy++;
+ } else {
+ op = (struct opt *) calloc(1, sizeof *op);
+ if (op == NULL)
+ err(EXIT_FAILURE, "calloc");
+ op->op_name = inw;
+ op->op_value = invalue;
+ SLIST_INSERT_HEAD(&op_head, op, op_next);
+ }
+
+ /* EOL? */
+ cp = get_word(inf);
+ if (cp == (char *)EOF)
+ break;
+ }
+ (void)fclose(inf);
+ if (!tidy && ((value == NULL && oldvalue == NULL) ||
+ (value && oldvalue && eq(value, oldvalue)))) {
+ while (!SLIST_EMPTY(&op_head)) {
+ op = SLIST_FIRST(&op_head);
+ SLIST_REMOVE_HEAD(&op_head, op_next);
+ free(op->op_name);
+ free(op->op_value);
+ free(op);
+ }
+ return;
+ }
+
+ if (value && !seen) {
+ /* New option appears */
+ op = (struct opt *) calloc(1, sizeof *op);
+ if (op == NULL)
+ err(EXIT_FAILURE, "calloc");
+ op->op_name = ns(name);
+ op->op_value = value ? ns(value) : NULL;
+ SLIST_INSERT_HEAD(&op_head, op, op_next);
+ }
+
+ outf = fopen(file, "w");
+ if (outf == 0)
+ err(1, "%s", file);
+ while (!SLIST_EMPTY(&op_head)) {
+ op = SLIST_FIRST(&op_head);
+ /* was the option in the config file? */
+ if (op->op_value) {
+ fprintf(outf, "#define %s %s\n",
+ op->op_name, op->op_value);
+ }
+ SLIST_REMOVE_HEAD(&op_head, op_next);
+ free(op->op_name);
+ free(op->op_value);
+ free(op);
+ }
+ (void)fclose(outf);
+}
+
+/*
+ * Find the filename to store the option spec into.
+ */
+static char *
+tooption(char *name)
+{
+ static char hbuf[MAXPATHLEN];
+ char nbuf[MAXPATHLEN];
+ struct opt_list *po;
+
+ /* "cannot happen"? the otab list should be complete.. */
+ (void)strlcpy(nbuf, "options.h", sizeof(nbuf));
+
+ SLIST_FOREACH(po, &otab, o_next) {
+ if (eq(po->o_name, name)) {
+ strlcpy(nbuf, po->o_file, sizeof(nbuf));
+ break;
+ }
+ }
+
+ (void)strlcpy(hbuf, path(nbuf), sizeof(hbuf));
+ return (hbuf);
+}
+
+
+static void
+check_duplicate(const char *fname, const char *this)
+{
+ struct opt_list *po;
+
+ SLIST_FOREACH(po, &otab, o_next) {
+ if (eq(po->o_name, this)) {
+ fprintf(stderr, "%s: Duplicate option %s.\n",
+ fname, this);
+ exit(1);
+ }
+ }
+}
+
+static void
+insert_option(const char *fname, char *this, char *val)
+{
+ struct opt_list *po;
+
+ check_duplicate(fname, this);
+ po = (struct opt_list *) calloc(1, sizeof *po);
+ if (po == NULL)
+ err(EXIT_FAILURE, "calloc");
+ po->o_name = this;
+ po->o_file = val;
+ po->o_flags = 0;
+ SLIST_INSERT_HEAD(&otab, po, o_next);
+}
+
+static void
+update_option(const char *this, char *val, int flags)
+{
+ struct opt_list *po;
+
+ SLIST_FOREACH(po, &otab, o_next) {
+ if (eq(po->o_name, this)) {
+ free(po->o_file);
+ po->o_file = val;
+ po->o_flags = flags;
+ return;
+ }
+ }
+ /*
+ * Option not found, but that's OK, we just ignore it since it
+ * may be for another arch.
+ */
+ return;
+}
+
+static int
+read_option_file(const char *fname, int flags)
+{
+ FILE *fp;
+ char *wd, *this, *val;
+ char genopt[MAXPATHLEN];
+
+ fp = fopen(fname, "r");
+ if (fp == 0)
+ return (0);
+ while ((wd = get_word(fp)) != (char *)EOF) {
+ if (wd == 0)
+ continue;
+ if (wd[0] == '#') {
+ while (((wd = get_word(fp)) != (char *)EOF) && wd)
+ continue;
+ continue;
+ }
+ this = ns(wd);
+ val = get_word(fp);
+ if (val == (char *)EOF)
+ return (1);
+ if (val == 0) {
+ if (flags) {
+ fprintf(stderr, "%s: compat file requires two"
+ " words per line at %s\n", fname, this);
+ exit(1);
+ }
+ char *s = ns(this);
+ (void)snprintf(genopt, sizeof(genopt), "opt_%s.h",
+ lower(s));
+ val = genopt;
+ free(s);
+ }
+ val = ns(val);
+ if (flags == 0)
+ insert_option(fname, this, val);
+ else
+ update_option(this, val, flags);
+ }
+ (void)fclose(fp);
+ return (1);
+}
+
+/*
+ * read the options and options.<machine> files
+ */
+static void
+read_options(void)
+{
+ char fname[MAXPATHLEN];
+
+ SLIST_INIT(&otab);
+ read_option_file("../../conf/options", 0);
+ (void)snprintf(fname, sizeof fname, "../../conf/options.%s",
+ machinename);
+ if (!read_option_file(fname, 0)) {
+ (void)snprintf(fname, sizeof fname, "options.%s", machinename);
+ read_option_file(fname, 0);
+ }
+ read_option_file("../../conf/options-compat", OL_ALIAS);
+}
+
+static char *
+lower(char *str)
+{
+ char *cp = str;
+
+ while (*str) {
+ if (isupper(*str))
+ *str = tolower(*str);
+ str++;
+ }
+ return (cp);
+}
diff --git a/usr.sbin/cpucontrol/Makefile b/usr.sbin/cpucontrol/Makefile
new file mode 100644
index 0000000..2d51429
--- /dev/null
+++ b/usr.sbin/cpucontrol/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PROG= cpucontrol
+MAN= cpucontrol.8
+SRCS= cpucontrol.c intel.c amd.c via.c
+
+NO_WCAST_ALIGN=
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/cpucontrol/Makefile.depend b/usr.sbin/cpucontrol/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/cpucontrol/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/cpucontrol/amd.c b/usr.sbin/cpucontrol/amd.c
new file mode 100644
index 0000000..3ba435c
--- /dev/null
+++ b/usr.sbin/cpucontrol/amd.c
@@ -0,0 +1,181 @@
+/*-
+ * Copyright (c) 2006, 2008 Stanislav Sedov <stas@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 ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED 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 <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <err.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <sys/ioccom.h>
+#include <sys/cpuctl.h>
+
+#include <machine/cpufunc.h>
+#include <machine/specialreg.h>
+
+#include "cpucontrol.h"
+#include "amd.h"
+
+int
+amd_probe(int fd)
+{
+ char vendor[13];
+ int error;
+ cpuctl_cpuid_args_t idargs = {
+ .level = 0,
+ };
+
+ error = ioctl(fd, CPUCTL_CPUID, &idargs);
+ if (error < 0) {
+ WARN(0, "ioctl()");
+ return (1);
+ }
+ ((uint32_t *)vendor)[0] = idargs.data[1];
+ ((uint32_t *)vendor)[1] = idargs.data[3];
+ ((uint32_t *)vendor)[2] = idargs.data[2];
+ vendor[12] = '\0';
+ if (strncmp(vendor, AMD_VENDOR_ID, sizeof(AMD_VENDOR_ID)) != 0)
+ return (1);
+ return (0);
+}
+
+void
+amd_update(const char *dev, const char *path)
+{
+ int fd, devfd;
+ unsigned int i;
+ struct stat st;
+ uint32_t *fw_image;
+ amd_fw_header_t *fw_header;
+ uint32_t sum;
+ uint32_t signature;
+ uint32_t *fw_data;
+ size_t fw_size;
+ cpuctl_cpuid_args_t idargs = {
+ .level = 1, /* Request signature. */
+ };
+ cpuctl_update_args_t args;
+ int error;
+
+ assert(path);
+ assert(dev);
+
+ fd = -1;
+ fw_image = MAP_FAILED;
+ devfd = open(dev, O_RDWR);
+ if (devfd < 0) {
+ WARN(0, "could not open %s for writing", dev);
+ return;
+ }
+ error = ioctl(devfd, CPUCTL_CPUID, &idargs);
+ if (error < 0) {
+ WARN(0, "ioctl()");
+ goto fail;
+ }
+ signature = idargs.data[0];
+ WARNX(2, "found cpu family %#x model %#x "
+ "stepping %#x extfamily %#x extmodel %#x.",
+ (signature >> 8) & 0x0f, (signature >> 4) & 0x0f,
+ (signature >> 0) & 0x0f, (signature >> 20) & 0xff,
+ (signature >> 16) & 0x0f);
+
+ /*
+ * Open the firmware file.
+ */
+ fd = open(path, O_RDONLY, 0);
+ if (fd < 0) {
+ WARN(0, "open(%s)", path);
+ goto fail;
+ }
+ error = fstat(fd, &st);
+ if (error != 0) {
+ WARN(0, "fstat(%s)", path);
+ goto fail;
+ }
+ if (st.st_size < 0 || (unsigned)st.st_size < sizeof(*fw_header)) {
+ WARNX(2, "file too short: %s", path);
+ goto fail;
+ }
+ /*
+ * mmap the whole image.
+ */
+ fw_image = (uint32_t *)mmap(NULL, st.st_size, PROT_READ,
+ MAP_PRIVATE, fd, 0);
+ if (fw_image == MAP_FAILED) {
+ WARN(0, "mmap(%s)", path);
+ goto fail;
+ }
+ fw_header = (amd_fw_header_t *)fw_image;
+ if ((fw_header->magic >> 8) != AMD_MAGIC) {
+ WARNX(2, "%s is not a valid amd firmware: version mismatch",
+ path);
+ goto fail;
+ }
+ fw_data = (uint32_t *)(fw_header + 1);
+ fw_size = (st.st_size - sizeof(*fw_header)) / sizeof(uint32_t);
+
+ /*
+ * Check the primary checksum.
+ */
+ sum = 0;
+ for (i = 0; i < fw_size; i++)
+ sum += fw_data[i];
+ if (sum != fw_header->checksum) {
+ WARNX(2, "%s: update data checksum invalid", path);
+ goto fail;
+ }
+ if (signature == fw_header->signature) {
+ fprintf(stderr, "%s: updating cpu %s... ", path, dev);
+
+ args.data = fw_image;
+ args.size = st.st_size;
+ error = ioctl(devfd, CPUCTL_UPDATE, &args);
+ if (error < 0) {
+ fprintf(stderr, "failed.\n");
+ warn("ioctl()");
+ goto fail;
+ }
+ fprintf(stderr, "done.\n");
+ }
+
+fail:
+ if (fd >= 0)
+ close(fd);
+ if (devfd >= 0)
+ close(devfd);
+ if (fw_image != MAP_FAILED)
+ if(munmap(fw_image, st.st_size) != 0)
+ warn("munmap(%s)", path);
+ return;
+}
diff --git a/usr.sbin/cpucontrol/amd.h b/usr.sbin/cpucontrol/amd.h
new file mode 100644
index 0000000..cf109c2
--- /dev/null
+++ b/usr.sbin/cpucontrol/amd.h
@@ -0,0 +1,49 @@
+/*-
+ * Copyright (c) 2006, 2008 Stanislav Sedov <stas@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 ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED 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 AMD_H
+#define AMD_H
+
+/*
+ * Prototypes.
+ */
+ucode_probe_t amd_probe;
+ucode_update_t amd_update;
+
+typedef struct amd_fw_header {
+ uint32_t date; /* Update creation date. */
+ uint32_t xz0[2];
+ uint32_t checksum; /* ucode checksum. */
+ uint32_t xz1[2];
+ uint32_t signature; /* Low byte of cpuid(0). */
+ uint32_t magic; /* 0x0Xaaaaaa */
+ uint32_t xz2[8];
+} amd_fw_header_t;
+
+#define AMD_MAGIC 0xaaaaaa
+
+#endif /* !AMD_H */
diff --git a/usr.sbin/cpucontrol/cpucontrol.8 b/usr.sbin/cpucontrol/cpucontrol.8
new file mode 100644
index 0000000..91946d3
--- /dev/null
+++ b/usr.sbin/cpucontrol/cpucontrol.8
@@ -0,0 +1,182 @@
+.\" Copyright (c) 2006, 2008 Stanislav Sedov <stas@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 June 30, 2009
+.Dt CPUCONTROL 8
+.Os
+.Sh NAME
+.Nm cpucontrol
+.Nd control utility for the
+.Xr cpuctl 4
+device
+.Sh SYNOPSIS
+.Nm
+.Op Fl vh
+.Fl m Ar msr
+.Bk
+.Ar device
+.Ek
+.Nm
+.Op Fl vh
+.Fl m Ar msr Ns = Ns Ar value
+.Bk
+.Ar device
+.Ek
+.Nm
+.Op Fl vh
+.Fl m Ar msr Ns &= Ns Ar mask
+.Bk
+.Ar device
+.Ek
+.Nm
+.Op Fl vh
+.Fl m Ar msr Ns |= Ns Ar mask
+.Bk
+.Ar device
+.Ek
+.Nm
+.Op Fl vh
+.Fl i Ar level
+.Bk
+.Ar device
+.Ek
+.Nm
+.Op Fl vh
+.Fl i Ar level,level_type
+.Bk
+.Ar device
+.Ek
+.Nm
+.Op Fl vh
+.Op Fl d Ar datadir
+.Fl u
+.Bk
+.Ar device
+.Ek
+.Sh DESCRIPTION
+The
+.Nm
+utility can be used to read and write arbitrary machine-specific
+CPU registers via the
+.Xr cpuctl 4
+special device.
+It can also be used to apply CPU firmware updates.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl d Ar datadir
+Where to look for microcode images.
+The option can be specified multiple times.
+.It Fl m Ar msr Ns Op = Ns Ar value
+Show value of the specified MSR.
+MSR register number should be given as a hexadecimal number.
+.It Fl m Ar msr Ns = Ns Ar value
+Store the
+.Ar value
+in the specified MSR register.
+The
+.Ar value
+argument can be prefixed with ~ operator.
+In this case the inverted value of argument will be stored in the register.
+.It Fl m Ar msr Ns &= Ns Ar mask
+Store the result of bitwise AND operation between
+.Ar mask
+and the current MSR value in the MSR register.
+The
+.Ar mask
+argument can be prefixed with ~ operator.
+In this case the inverted value of mask will be used.
+.It Fl m Ar msr Ns |= Ns Ar mask
+Store the result of bitwise OR operation between
+.Ar mask
+and the current MSR value in the MSR register.
+The
+.Ar mask
+argument can be prefixed with ~ operator.
+In this case the inverted value of mask will be used.
+.It Fl i Ar level
+Retrieve CPUID info.
+Level should be given as a hex number.
+.It Fl i Ar level,level_type
+Retrieve CPUID info.
+Level and level_type should be given as hex numbers.
+.It Fl u
+Apply CPU firmware updates.
+The
+.Nm
+utility will walk through the configured data directories
+and apply all firmware updates available for this CPU.
+.It Fl v
+Increase the verbosity level.
+.It Fl h
+Show help message.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+The command
+.Pp
+.Dq Li "cpucontrol -m 0x10 /dev/cpuctl0"
+.Pp
+will read the contents of TSC MSR from CPU 0.
+.Pp
+To set the CPU 0 TSC MSR register value to 0x1 issue
+.Pp
+.Dq Li "cpucontrol -m 0x10=0x1 /dev/cpuctl0" .
+.Pp
+The following command will clear the second bit of TSC register:
+.Pp
+.Dq Li "cpucontrol -m 0x10&=~0x02 /dev/cpuctl0" .
+.Pp
+The following command will set the forth and second bit of TSC register:
+.Pp
+.Dq Li "cpucontrol -m 0x10|=0x0a /dev/cpuctl0" .
+.Pp
+The command
+.Pp
+.Dq Li "cpucontrol -i 0x1 /dev/cpuctl1"
+.Pp
+will retrieve the CPUID level 0x1 from CPU 1.
+.Pp
+To perform firmware updates on CPU 0 from images located at
+.Pa /usr/local/share/cpuctl/
+use the following command:
+.Pp
+.Dq Li "cpucontrol -d /usr/local/share/cpuctl/ -u /dev/cpuctl0"
+.Sh SEE ALSO
+.Xr cpuctl 4
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 7.2 .
+.Sh AUTHORS
+The
+.Nm
+utility and this manual page was written by
+.An Stanislav Sedov Aq Mt stas@FreeBSD.org .
+.Sh BUGS
+Yes, probably, report if any.
diff --git a/usr.sbin/cpucontrol/cpucontrol.c b/usr.sbin/cpucontrol/cpucontrol.c
new file mode 100644
index 0000000..69fdf3a
--- /dev/null
+++ b/usr.sbin/cpucontrol/cpucontrol.c
@@ -0,0 +1,485 @@
+/*-
+ * Copyright (c) 2008-2011 Stanislav Sedov <stas@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 ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED 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 utility provides userland access to the cpuctl(4) pseudo-device
+ * features.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <err.h>
+#include <sysexits.h>
+#include <dirent.h>
+
+#include <sys/queue.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/cpuctl.h>
+
+#include "cpucontrol.h"
+#include "amd.h"
+#include "intel.h"
+#include "via.h"
+
+int verbosity_level = 0;
+
+#define DEFAULT_DATADIR "/usr/local/share/cpucontrol"
+
+#define FLAG_I 0x01
+#define FLAG_M 0x02
+#define FLAG_U 0x04
+
+#define OP_INVAL 0x00
+#define OP_READ 0x01
+#define OP_WRITE 0x02
+#define OP_OR 0x04
+#define OP_AND 0x08
+
+#define HIGH(val) (uint32_t)(((val) >> 32) & 0xffffffff)
+#define LOW(val) (uint32_t)((val) & 0xffffffff)
+
+/*
+ * Macros for freeing SLISTs, probably must be in /sys/queue.h
+ */
+#define SLIST_FREE(head, field, freef) do { \
+ typeof(SLIST_FIRST(head)) __elm0; \
+ typeof(SLIST_FIRST(head)) __elm; \
+ SLIST_FOREACH_SAFE(__elm, (head), field, __elm0) \
+ (void)(freef)(__elm); \
+} while(0);
+
+struct datadir {
+ const char *path;
+ SLIST_ENTRY(datadir) next;
+};
+static SLIST_HEAD(, datadir) datadirs = SLIST_HEAD_INITIALIZER(datadirs);
+
+static struct ucode_handler {
+ ucode_probe_t *probe;
+ ucode_update_t *update;
+} handlers[] = {
+ { intel_probe, intel_update },
+ { amd_probe, amd_update },
+ { via_probe, via_update },
+};
+#define NHANDLERS (sizeof(handlers) / sizeof(*handlers))
+
+static void usage(void);
+static int isdir(const char *path);
+static int do_cpuid(const char *cmdarg, const char *dev);
+static int do_cpuid_count(const char *cmdarg, const char *dev);
+static int do_msr(const char *cmdarg, const char *dev);
+static int do_update(const char *dev);
+static void datadir_add(const char *path);
+
+static void __dead2
+usage(void)
+{
+ const char *name;
+
+ name = getprogname();
+ if (name == NULL)
+ name = "cpuctl";
+ fprintf(stderr, "Usage: %s [-vh] [-d datadir] [-m msr[=value] | "
+ "-i level | -i level,level_type | -u] device\n", name);
+ exit(EX_USAGE);
+}
+
+static int
+isdir(const char *path)
+{
+ int error;
+ struct stat st;
+
+ error = stat(path, &st);
+ if (error < 0) {
+ WARN(0, "stat(%s)", path);
+ return (error);
+ }
+ return (st.st_mode & S_IFDIR);
+}
+
+static int
+do_cpuid(const char *cmdarg, const char *dev)
+{
+ unsigned int level;
+ cpuctl_cpuid_args_t args;
+ int fd, error;
+ char *endptr;
+
+ assert(cmdarg != NULL);
+ assert(dev != NULL);
+
+ level = strtoul(cmdarg, &endptr, 16);
+ if (*cmdarg == '\0' || *endptr != '\0') {
+ WARNX(0, "incorrect operand: %s", cmdarg);
+ usage();
+ /* NOTREACHED */
+ }
+
+ /*
+ * Fill ioctl argument structure.
+ */
+ args.level = level;
+ fd = open(dev, O_RDONLY);
+ if (fd < 0) {
+ WARN(0, "error opening %s for reading", dev);
+ return (1);
+ }
+ error = ioctl(fd, CPUCTL_CPUID, &args);
+ if (error < 0) {
+ WARN(0, "ioctl(%s, CPUCTL_CPUID)", dev);
+ close(fd);
+ return (error);
+ }
+ fprintf(stdout, "cpuid level 0x%x: 0x%.8x 0x%.8x 0x%.8x 0x%.8x\n",
+ level, args.data[0], args.data[1], args.data[2], args.data[3]);
+ close(fd);
+ return (0);
+}
+
+static int
+do_cpuid_count(const char *cmdarg, const char *dev)
+{
+ char *cmdarg1, *endptr, *endptr1;
+ unsigned int level, level_type;
+ cpuctl_cpuid_count_args_t args;
+ int fd, error;
+
+ assert(cmdarg != NULL);
+ assert(dev != NULL);
+
+ level = strtoul(cmdarg, &endptr, 16);
+ if (*cmdarg == '\0' || *endptr == '\0') {
+ WARNX(0, "incorrect or missing operand: %s", cmdarg);
+ usage();
+ /* NOTREACHED */
+ }
+ /* Locate the comma... */
+ cmdarg1 = strstr(endptr, ",");
+ /* ... and skip past it */
+ cmdarg1 += 1;
+ level_type = strtoul(cmdarg1, &endptr1, 16);
+ if (*cmdarg1 == '\0' || *endptr1 != '\0') {
+ WARNX(0, "incorrect or missing operand: %s", cmdarg);
+ usage();
+ /* NOTREACHED */
+ }
+
+ /*
+ * Fill ioctl argument structure.
+ */
+ args.level = level;
+ args.level_type = level_type;
+ fd = open(dev, O_RDONLY);
+ if (fd < 0) {
+ WARN(0, "error opening %s for reading", dev);
+ return (1);
+ }
+ error = ioctl(fd, CPUCTL_CPUID_COUNT, &args);
+ if (error < 0) {
+ WARN(0, "ioctl(%s, CPUCTL_CPUID_COUNT)", dev);
+ close(fd);
+ return (error);
+ }
+ fprintf(stdout, "cpuid level 0x%x, level_type 0x%x: 0x%.8x 0x%.8x "
+ "0x%.8x 0x%.8x\n", level, level_type, args.data[0], args.data[1],
+ args.data[2], args.data[3]);
+ close(fd);
+ return (0);
+}
+
+static int
+do_msr(const char *cmdarg, const char *dev)
+{
+ unsigned int msr;
+ cpuctl_msr_args_t args;
+ size_t len;
+ uint64_t data = 0;
+ unsigned long command;
+ int do_invert = 0, op;
+ int fd, error;
+ const char *command_name;
+ char *endptr;
+ char *p;
+
+ assert(cmdarg != NULL);
+ assert(dev != NULL);
+ len = strlen(cmdarg);
+ if (len == 0) {
+ WARNX(0, "MSR register expected");
+ usage();
+ /* NOTREACHED */
+ }
+
+ /*
+ * Parse command string.
+ */
+ msr = strtoul(cmdarg, &endptr, 16);
+ switch (*endptr) {
+ case '\0':
+ op = OP_READ;
+ break;
+ case '=':
+ op = OP_WRITE;
+ break;
+ case '&':
+ op = OP_AND;
+ endptr++;
+ break;
+ case '|':
+ op = OP_OR;
+ endptr++;
+ break;
+ default:
+ op = OP_INVAL;
+ }
+ if (op != OP_READ) { /* Complex operation. */
+ if (*endptr != '=')
+ op = OP_INVAL;
+ else {
+ p = ++endptr;
+ if (*p == '~') {
+ do_invert = 1;
+ p++;
+ }
+ data = strtoull(p, &endptr, 16);
+ if (*p == '\0' || *endptr != '\0') {
+ WARNX(0, "argument required: %s", cmdarg);
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ }
+ if (op == OP_INVAL) {
+ WARNX(0, "invalid operator: %s", cmdarg);
+ usage();
+ /* NOTREACHED */
+ }
+
+ /*
+ * Fill ioctl argument structure.
+ */
+ args.msr = msr;
+ if ((do_invert != 0) ^ (op == OP_AND))
+ args.data = ~data;
+ else
+ args.data = data;
+ switch (op) {
+ case OP_READ:
+ command = CPUCTL_RDMSR;
+ command_name = "RDMSR";
+ break;
+ case OP_WRITE:
+ command = CPUCTL_WRMSR;
+ command_name = "WRMSR";
+ break;
+ case OP_OR:
+ command = CPUCTL_MSRSBIT;
+ command_name = "MSRSBIT";
+ break;
+ case OP_AND:
+ command = CPUCTL_MSRCBIT;
+ command_name = "MSRCBIT";
+ break;
+ default:
+ abort();
+ }
+ fd = open(dev, op == OP_READ ? O_RDONLY : O_WRONLY);
+ if (fd < 0) {
+ WARN(0, "error opening %s for %s", dev,
+ op == OP_READ ? "reading" : "writing");
+ return (1);
+ }
+ error = ioctl(fd, command, &args);
+ if (error < 0) {
+ WARN(0, "ioctl(%s, CPUCTL_%s (%lu))", dev, command_name, command);
+ close(fd);
+ return (1);
+ }
+ if (op == OP_READ)
+ fprintf(stdout, "MSR 0x%x: 0x%.8x 0x%.8x\n", msr,
+ HIGH(args.data), LOW(args.data));
+ close(fd);
+ return (0);
+}
+
+static int
+do_update(const char *dev)
+{
+ int fd;
+ unsigned int i;
+ int error;
+ struct ucode_handler *handler;
+ struct datadir *dir;
+ DIR *dirp;
+ struct dirent *direntry;
+ char buf[MAXPATHLEN];
+
+ fd = open(dev, O_RDONLY);
+ if (fd < 0) {
+ WARN(0, "error opening %s for reading", dev);
+ return (1);
+ }
+
+ /*
+ * Find the appropriate handler for device.
+ */
+ for (i = 0; i < NHANDLERS; i++)
+ if (handlers[i].probe(fd) == 0)
+ break;
+ if (i < NHANDLERS)
+ handler = &handlers[i];
+ else {
+ WARNX(0, "cannot find the appropriate handler for device");
+ close(fd);
+ return (1);
+ }
+ close(fd);
+
+ /*
+ * Process every image in specified data directories.
+ */
+ SLIST_FOREACH(dir, &datadirs, next) {
+ dirp = opendir(dir->path);
+ if (dirp == NULL) {
+ WARNX(1, "skipping directory %s: not accessible", dir->path);
+ continue;
+ }
+ while ((direntry = readdir(dirp)) != NULL) {
+ if (direntry->d_namlen == 0)
+ continue;
+ error = snprintf(buf, sizeof(buf), "%s/%s", dir->path,
+ direntry->d_name);
+ if ((unsigned)error >= sizeof(buf))
+ WARNX(0, "skipping %s, buffer too short",
+ direntry->d_name);
+ if (isdir(buf) != 0) {
+ WARNX(2, "skipping %s: is a directory", buf);
+ continue;
+ }
+ handler->update(dev, buf);
+ }
+ error = closedir(dirp);
+ if (error != 0)
+ WARN(0, "closedir(%s)", dir->path);
+ }
+ return (0);
+}
+
+/*
+ * Add new data directory to the search list.
+ */
+static void
+datadir_add(const char *path)
+{
+ struct datadir *newdir;
+
+ newdir = (struct datadir *)malloc(sizeof(*newdir));
+ if (newdir == NULL)
+ err(EX_OSERR, "cannot allocate memory");
+ newdir->path = path;
+ SLIST_INSERT_HEAD(&datadirs, newdir, next);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int c, flags;
+ const char *cmdarg;
+ const char *dev;
+ int error;
+
+ flags = 0;
+ error = 0;
+ cmdarg = ""; /* To keep gcc3 happy. */
+
+ /*
+ * Add all default data dirs to the list first.
+ */
+ datadir_add(DEFAULT_DATADIR);
+ while ((c = getopt(argc, argv, "d:hi:m:uv")) != -1) {
+ switch (c) {
+ case 'd':
+ datadir_add(optarg);
+ break;
+ case 'i':
+ flags |= FLAG_I;
+ cmdarg = optarg;
+ break;
+ case 'm':
+ flags |= FLAG_M;
+ cmdarg = optarg;
+ break;
+ case 'u':
+ flags |= FLAG_U;
+ break;
+ case 'v':
+ verbosity_level++;
+ break;
+ case 'h':
+ /* FALLTHROUGH */
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc < 1) {
+ usage();
+ /* NOTREACHED */
+ }
+ dev = argv[0];
+ c = flags & (FLAG_I | FLAG_M | FLAG_U);
+ switch (c) {
+ case FLAG_I:
+ if (strstr(cmdarg, ",") != NULL)
+ error = do_cpuid_count(cmdarg, dev);
+ else
+ error = do_cpuid(cmdarg, dev);
+ break;
+ case FLAG_M:
+ error = do_msr(cmdarg, dev);
+ break;
+ case FLAG_U:
+ error = do_update(dev);
+ break;
+ default:
+ usage(); /* Only one command can be selected. */
+ }
+ SLIST_FREE(&datadirs, next, free);
+ return (error);
+}
diff --git a/usr.sbin/cpucontrol/cpucontrol.h b/usr.sbin/cpucontrol/cpucontrol.h
new file mode 100644
index 0000000..63d3995
--- /dev/null
+++ b/usr.sbin/cpucontrol/cpucontrol.h
@@ -0,0 +1,56 @@
+/*-
+ * Copyright (c) 2008 Stanislav Sedov <stas@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 ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED 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 CPUCONTROL_H
+#define CPUCONTROL_H
+
+typedef int ucode_probe_t(int fd);
+typedef void ucode_update_t(const char *dev, const char *image);
+
+extern int verbosity_level;
+
+#ifdef DEBUG
+# define WARNX(level, ...) \
+ if ((level) <= verbosity_level) { \
+ fprintf(stderr, "%s:%d ", __FILE__, __LINE__); \
+ warnx(__VA_ARGS__); \
+ }
+# define WARN(level, ...) \
+ if ((level) <= verbosity_level) { \
+ fprintf(stderr, "%s:%d ", __FILE__, __LINE__); \
+ warn(__VA_ARGS__); \
+ }
+#else
+# define WARNX(level, ...) \
+ if ((level) <= verbosity_level) \
+ warnx(__VA_ARGS__);
+# define WARN(level, ...) \
+ if ((level) <= verbosity_level) \
+ warn(__VA_ARGS__);
+#endif
+
+#endif /* !CPUCONTROL_H */
diff --git a/usr.sbin/cpucontrol/intel.c b/usr.sbin/cpucontrol/intel.c
new file mode 100644
index 0000000..96ab704
--- /dev/null
+++ b/usr.sbin/cpucontrol/intel.c
@@ -0,0 +1,287 @@
+/*-
+ * Copyright (c) 2006, 2008 Stanislav Sedov <stas@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 ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED 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 <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <err.h>
+#include <errno.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <sys/ioccom.h>
+#include <sys/cpuctl.h>
+
+#include <machine/cpufunc.h>
+#include <machine/specialreg.h>
+
+#include "cpucontrol.h"
+#include "intel.h"
+
+#define DEFAULT_UCODE_SIZE 2000 /* Size of update data if not specified. */
+
+int
+intel_probe(int fd)
+{
+ char vendor[13];
+ int error;
+ cpuctl_cpuid_args_t idargs = {
+ .level = 0,
+ };
+
+ error = ioctl(fd, CPUCTL_CPUID, &idargs);
+ if (error < 0) {
+ WARN(0, "ioctl()");
+ return (1);
+ }
+ ((uint32_t *)vendor)[0] = idargs.data[1];
+ ((uint32_t *)vendor)[1] = idargs.data[3];
+ ((uint32_t *)vendor)[2] = idargs.data[2];
+ vendor[12] = '\0';
+ if (strncmp(vendor, INTEL_VENDOR_ID, sizeof(INTEL_VENDOR_ID)) != 0)
+ return (1);
+ return (0);
+}
+
+void
+intel_update(const char *dev, const char *path)
+{
+ int fd, devfd;
+ struct stat st;
+ uint32_t *fw_image;
+ int have_ext_table;
+ uint32_t sum;
+ unsigned int i;
+ size_t payload_size;
+ intel_fw_header_t *fw_header;
+ intel_cpu_signature_t *ext_table;
+ intel_ext_header_t *ext_header;
+ uint32_t signature, flags;
+ int32_t revision;
+ ssize_t ext_size;
+ size_t ext_table_size;
+ void *fw_data;
+ size_t data_size, total_size;
+ cpuctl_msr_args_t msrargs = {
+ .msr = MSR_IA32_PLATFORM_ID,
+ };
+ cpuctl_cpuid_args_t idargs = {
+ .level = 1, /* Signature. */
+ };
+ cpuctl_update_args_t args;
+ int error;
+
+ assert(path);
+ assert(dev);
+
+ fd = -1;
+ fw_image = MAP_FAILED;
+ ext_table = NULL;
+ ext_header = NULL;
+ devfd = open(dev, O_RDWR);
+ if (devfd < 0) {
+ WARN(0, "could not open %s for writing", dev);
+ return;
+ }
+ error = ioctl(devfd, CPUCTL_CPUID, &idargs);
+ if (error < 0) {
+ WARN(0, "ioctl(%s)", dev);
+ goto fail;
+ }
+ signature = idargs.data[0];
+ error = ioctl(devfd, CPUCTL_RDMSR, &msrargs);
+ if (error < 0) {
+ WARN(0, "ioctl(%s)", dev);
+ goto fail;
+ }
+
+ /*
+ * MSR_IA32_PLATFORM_ID contains flag in BCD in bits 52-50.
+ */
+ flags = 1 << ((msrargs.data >> 50) & 7);
+ msrargs.msr = MSR_BIOS_SIGN;
+ error = ioctl(devfd, CPUCTL_RDMSR, &msrargs);
+ if (error < 0) {
+ WARN(0, "ioctl(%s)", dev);
+ goto fail;
+ }
+ revision = msrargs.data >> 32; /* Revision in the high dword. */
+ WARNX(2, "found cpu type %#x family %#x model %#x stepping %#x.",
+ (signature >> 12) & 0x03, (signature >> 8) & 0x0f,
+ (signature >> 4) & 0x0f, (signature >> 0) & 0x0f);
+ /*
+ * Open firmware image.
+ */
+ fd = open(path, O_RDONLY, 0);
+ if (fd < 0) {
+ WARN(0, "open(%s)", path);
+ return;
+ }
+ error = fstat(fd, &st);
+ if (error != 0) {
+ WARN(0, "fstat(%s)", path);
+ goto fail;
+ }
+ if (st.st_size < 0 || (unsigned)st.st_size < sizeof(*fw_header)) {
+ WARNX(2, "file too short: %s", path);
+ goto fail;
+ }
+
+ /*
+ * mmap the whole image.
+ */
+ fw_image = (uint32_t *)mmap(NULL, st.st_size, PROT_READ,
+ MAP_PRIVATE, fd, 0);
+ if (fw_image == MAP_FAILED) {
+ WARN(0, "mmap(%s)", path);
+ goto fail;
+ }
+ fw_header = (intel_fw_header_t *)fw_image;
+ if (fw_header->header_version != INTEL_HEADER_VERSION ||
+ fw_header->loader_revision != INTEL_LOADER_REVISION) {
+ WARNX(2, "%s is not a valid intel firmware: version mismatch",
+ path);
+ goto fail;
+ }
+ /*
+ * According to spec, if data_size == 0, then size of ucode = 2000.
+ */
+ if (fw_header->data_size == 0)
+ data_size = DEFAULT_UCODE_SIZE;
+ else
+ data_size = fw_header->data_size;
+ if (fw_header->total_size == 0)
+ total_size = data_size + sizeof(*fw_header);
+ else
+ total_size = fw_header->total_size;
+ if (total_size > (unsigned)st.st_size || st.st_size < 0) {
+ WARNX(2, "file too short: %s", path);
+ goto fail;
+ }
+ payload_size = data_size + sizeof(*fw_header);
+
+ /*
+ * Check the primary checksum.
+ */
+ sum = 0;
+ for (i = 0; i < (payload_size / sizeof(uint32_t)); i++)
+ sum += *((uint32_t *)fw_image + i);
+ if (sum != 0) {
+ WARNX(2, "%s: update data checksum invalid", path);
+ goto fail;
+ }
+
+ /*
+ * Check if there is an extended signature table.
+ */
+ ext_size = total_size - payload_size;
+ have_ext_table = 0;
+
+ if (ext_size > (signed)sizeof(*ext_header)) {
+ ext_header =
+ (intel_ext_header_t *)((char *)fw_image + payload_size);
+ ext_table = (intel_cpu_signature_t *)(ext_header + 1);
+
+ /*
+ * Check the extended table size.
+ */
+ ext_table_size = sizeof(*ext_header) +
+ ext_header->sig_count * sizeof(*ext_table);
+ if (ext_table_size + payload_size > total_size) {
+ WARNX(2, "%s: broken extended signature table", path);
+ goto no_table;
+ }
+
+ /*
+ * Check the extended table signature.
+ */
+ sum = 0;
+ for (i = 0; i < (ext_table_size / sizeof(uint32_t)); i++)
+ sum += *((uint32_t *)ext_header + i);
+ if (sum != 0) {
+ WARNX(2, "%s: extended signature table checksum invalid",
+ path);
+ goto no_table;
+ }
+ have_ext_table = 1;
+ }
+
+no_table:
+ fw_data = fw_header + 1; /* Pointer to the update data. */
+
+ /*
+ * Check if the given image is ok for this cpu.
+ */
+ if (signature == fw_header->cpu_signature &&
+ (flags & fw_header->cpu_flags) != 0)
+ goto matched;
+ else if (have_ext_table != 0) {
+ for (i = 0; i < ext_header->sig_count; i++) {
+ uint32_t sig = ext_table[i].cpu_signature;
+ if (signature == sig &&
+ (flags & ext_table[i].cpu_flags) != 0)
+ goto matched;
+ }
+ } else
+ goto fail;
+
+matched:
+ if (revision >= fw_header->revision) {
+ WARNX(1, "skipping %s of rev %#x: up to date",
+ path, fw_header->revision);
+ return;
+ }
+ fprintf(stderr, "%s: updating cpu %s from rev %#x to rev %#x... ",
+ path, dev, revision, fw_header->revision);
+ args.data = fw_data;
+ args.size = data_size;
+ error = ioctl(devfd, CPUCTL_UPDATE, &args);
+ if (error < 0) {
+ error = errno;
+ fprintf(stderr, "failed.\n");
+ errno = error;
+ WARN(0, "ioctl()");
+ goto fail;
+ }
+ fprintf(stderr, "done.\n");
+
+fail:
+ if (fw_image != MAP_FAILED)
+ if (munmap(fw_image, st.st_size) != 0)
+ warn("munmap(%s)", path);
+ if (devfd >= 0)
+ close(devfd);
+ if (fd >= 0)
+ close(fd);
+ return;
+}
diff --git a/usr.sbin/cpucontrol/intel.h b/usr.sbin/cpucontrol/intel.h
new file mode 100644
index 0000000..0303e69
--- /dev/null
+++ b/usr.sbin/cpucontrol/intel.h
@@ -0,0 +1,70 @@
+/*-
+ * Copyright (c) 2006, 2008 Stanislav Sedov <stas@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 ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED 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 INTEL_H
+#define INTEL_H
+
+/*
+ * Prototypes.
+ */
+ucode_probe_t intel_probe;
+ucode_update_t intel_update;
+
+typedef struct intel_fw_header {
+ uint32_t header_version; /* Version of the header. */
+ int32_t revision; /* Unique version number. */
+ uint32_t date; /* Date of creation in BCD. */
+ uint32_t cpu_signature; /* Extended family, extended
+ model, type, family, model
+ and stepping. */
+ uint32_t checksum; /* Sum of all DWORDS should
+ be 0. */
+ uint32_t loader_revision; /* Version of the loader
+ required to load update. */
+ uint32_t cpu_flags; /* Platform IDs encoded in
+ the lower 8 bits. */
+ uint32_t data_size;
+ uint32_t total_size;
+ uint8_t reserved[12];
+} intel_fw_header_t;
+
+typedef struct intel_cpu_signature {
+ uint32_t cpu_signature;
+ uint32_t cpu_flags;
+ uint32_t checksum;
+} intel_cpu_signature_t;
+
+typedef struct intel_ext_header {
+ uint32_t sig_count;
+ uint32_t checksum;
+ uint8_t reserved[12];
+} intel_ext_header_t;
+
+#define INTEL_HEADER_VERSION 0x00000001
+#define INTEL_LOADER_REVISION 0x00000001
+
+#endif /* !INTEL_H */
diff --git a/usr.sbin/cpucontrol/via.c b/usr.sbin/cpucontrol/via.c
new file mode 100644
index 0000000..d17e31f
--- /dev/null
+++ b/usr.sbin/cpucontrol/via.c
@@ -0,0 +1,224 @@
+/*-
+ * Copyright (c) 2011 Fabien Thomas <fabient@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 ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED 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 <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <err.h>
+#include <errno.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <sys/ioccom.h>
+#include <sys/cpuctl.h>
+
+#include <machine/cpufunc.h>
+#include <machine/specialreg.h>
+
+#include "cpucontrol.h"
+#include "via.h"
+
+int
+via_probe(int fd)
+{
+ char vendor[13];
+ int error;
+ cpuctl_cpuid_args_t idargs = {
+ .level = 0,
+ };
+
+ error = ioctl(fd, CPUCTL_CPUID, &idargs);
+ if (error < 0) {
+ WARN(0, "ioctl()");
+ return (1);
+ }
+ ((uint32_t *)vendor)[0] = idargs.data[1];
+ ((uint32_t *)vendor)[1] = idargs.data[3];
+ ((uint32_t *)vendor)[2] = idargs.data[2];
+ vendor[12] = '\0';
+ if (strncmp(vendor, CENTAUR_VENDOR_ID, sizeof(CENTAUR_VENDOR_ID)) != 0)
+ return (1);
+
+ /* TODO: detect Nano CPU. */
+ return (0);
+}
+
+void
+via_update(const char *dev, const char *path)
+{
+ int fd, devfd;
+ struct stat st;
+ uint32_t *fw_image;
+ uint32_t sum;
+ unsigned int i;
+ size_t payload_size;
+ via_fw_header_t *fw_header;
+ uint32_t signature;
+ int32_t revision;
+ void *fw_data;
+ size_t data_size, total_size;
+ cpuctl_msr_args_t msrargs = {
+ .msr = MSR_IA32_PLATFORM_ID,
+ };
+ cpuctl_cpuid_args_t idargs = {
+ .level = 1, /* Signature. */
+ };
+ cpuctl_update_args_t args;
+ int error;
+
+ assert(path);
+ assert(dev);
+
+ fd = -1;
+ devfd = -1;
+ fw_image = MAP_FAILED;
+ devfd = open(dev, O_RDWR);
+ if (devfd < 0) {
+ WARN(0, "could not open %s for writing", dev);
+ return;
+ }
+ error = ioctl(devfd, CPUCTL_CPUID, &idargs);
+ if (error < 0) {
+ WARN(0, "ioctl(%s)", dev);
+ goto fail;
+ }
+ signature = idargs.data[0];
+ error = ioctl(devfd, CPUCTL_RDMSR, &msrargs);
+ if (error < 0) {
+ WARN(0, "ioctl(%s)", dev);
+ goto fail;
+ }
+
+ /*
+ * MSR_IA32_PLATFORM_ID contains flag in BCD in bits 52-50.
+ */
+ msrargs.msr = MSR_BIOS_SIGN;
+ error = ioctl(devfd, CPUCTL_RDMSR, &msrargs);
+ if (error < 0) {
+ WARN(0, "ioctl(%s)", dev);
+ goto fail;
+ }
+ revision = msrargs.data >> 32; /* Revision in the high dword. */
+ WARNX(2, "found cpu type %#x family %#x model %#x stepping %#x.",
+ (signature >> 12) & 0x03, (signature >> 8) & 0x0f,
+ (signature >> 4) & 0x0f, (signature >> 0) & 0x0f);
+ /*
+ * Open firmware image.
+ */
+ fd = open(path, O_RDONLY, 0);
+ if (fd < 0) {
+ WARN(0, "open(%s)", path);
+ return;
+ }
+ error = fstat(fd, &st);
+ if (error != 0) {
+ WARN(0, "fstat(%s)", path);
+ goto fail;
+ }
+ if (st.st_size < 0 || (unsigned)st.st_size < sizeof(*fw_header)) {
+ WARNX(2, "file too short: %s", path);
+ goto fail;
+ }
+
+ /*
+ * mmap the whole image.
+ */
+ fw_image = (uint32_t *)mmap(NULL, st.st_size, PROT_READ,
+ MAP_PRIVATE, fd, 0);
+ if (fw_image == MAP_FAILED) {
+ WARN(0, "mmap(%s)", path);
+ goto fail;
+ }
+ fw_header = (via_fw_header_t *)fw_image;
+ if (fw_header->signature != VIA_HEADER_SIGNATURE ||
+ fw_header->loader_revision != VIA_LOADER_REVISION) {
+ WARNX(2, "%s is not a valid via firmware: version mismatch",
+ path);
+ goto fail;
+ }
+ data_size = fw_header->data_size;
+ total_size = fw_header->total_size;
+ if (total_size > (unsigned)st.st_size || st.st_size < 0) {
+ WARNX(2, "file too short: %s", path);
+ goto fail;
+ }
+ payload_size = data_size + sizeof(*fw_header);
+
+ /*
+ * Check the primary checksum.
+ */
+ sum = 0;
+ for (i = 0; i < (payload_size / sizeof(uint32_t)); i++)
+ sum += *((uint32_t *)fw_image + i);
+ if (sum != 0) {
+ WARNX(2, "%s: update data checksum invalid", path);
+ goto fail;
+ }
+
+ fw_data = fw_header + 1; /* Pointer to the update data. */
+
+ /*
+ * Check if the given image is ok for this cpu.
+ */
+ if (signature != fw_header->cpu_signature)
+ goto fail;
+
+ if (fw_header->revision != 0 && revision >= fw_header->revision) {
+ WARNX(1, "skipping %s of rev %#x: up to date",
+ path, fw_header->revision);
+ goto fail;
+ }
+ fprintf(stderr, "%s: updating cpu %s from rev %#x to rev %#x... ",
+ path, dev, revision, fw_header->revision);
+ args.data = fw_data;
+ args.size = data_size;
+ error = ioctl(devfd, CPUCTL_UPDATE, &args);
+ if (error < 0) {
+ error = errno;
+ fprintf(stderr, "failed.\n");
+ errno = error;
+ WARN(0, "ioctl()");
+ goto fail;
+ }
+ fprintf(stderr, "done.\n");
+
+fail:
+ if (fw_image != MAP_FAILED)
+ if (munmap(fw_image, st.st_size) != 0)
+ warn("munmap(%s)", path);
+ if (devfd >= 0)
+ close(devfd);
+ if (fd >= 0)
+ close(fd);
+ return;
+}
diff --git a/usr.sbin/cpucontrol/via.h b/usr.sbin/cpucontrol/via.h
new file mode 100644
index 0000000..ea99eac
--- /dev/null
+++ b/usr.sbin/cpucontrol/via.h
@@ -0,0 +1,63 @@
+/*-
+ * Copyright (c) 2011 Fabien Thomas <fabient@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 ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED 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 VIA_H
+#define VIA_H
+
+/*
+ * Prototypes.
+ */
+ucode_probe_t via_probe;
+ucode_update_t via_update;
+
+typedef struct via_fw_header {
+ uint32_t signature; /* Signature. */
+ int32_t revision; /* Unique version number. */
+ uint32_t date; /* Date of creation in BCD. */
+ uint32_t cpu_signature; /* Extended family, extended
+ model, type, family, model
+ and stepping. */
+ uint32_t checksum; /* Sum of all DWORDS should
+ be 0. */
+ uint32_t loader_revision; /* Version of the loader
+ required to load update. */
+ uint32_t reserverd1; /* Platform IDs encoded in
+ the lower 8 bits. */
+ uint32_t data_size;
+ uint32_t total_size;
+ uint8_t reserved2[12];
+} via_fw_header_t;
+
+typedef struct via_cpu_signature {
+ uint32_t cpu_signature;
+ uint32_t checksum;
+} via_cpu_signature_t;
+
+#define VIA_HEADER_SIGNATURE 0x53415252
+#define VIA_LOADER_REVISION 0x00000001
+
+#endif /* !VIA_H */
diff --git a/usr.sbin/crashinfo/Makefile b/usr.sbin/crashinfo/Makefile
new file mode 100644
index 0000000..fa2a19c
--- /dev/null
+++ b/usr.sbin/crashinfo/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+SCRIPTS= crashinfo.sh
+MAN= crashinfo.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/crashinfo/Makefile.depend b/usr.sbin/crashinfo/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/crashinfo/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/crashinfo/crashinfo.8 b/usr.sbin/crashinfo/crashinfo.8
new file mode 100644
index 0000000..897fc59
--- /dev/null
+++ b/usr.sbin/crashinfo/crashinfo.8
@@ -0,0 +1,109 @@
+.\" Copyright (c) 2008 Yahoo!, 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.
+.\" 3. 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 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 28, 2008
+.Dt CRASHINFO 8
+.Os
+.Sh NAME
+.Nm crashinfo
+.Nd "analyze a core dump of the operating system"
+.Sh SYNOPSIS
+.Nm
+.Op Fl d Ar crashdir
+.Op Fl n Ar dumpnr
+.Op Fl k Ar kernel
+.Op Ar core
+.Sh DESCRIPTION
+The
+.Nm
+utility analyzes a core dump saved by
+.Xr savecore 8 .
+It generates a text file containing the analysis in the same directory as
+the core dump.
+For a given core dump file named
+.Pa vmcore.XX
+the generated text file will be named
+.Pa core.txt.XX .
+.Pp
+By default,
+.Nm
+analyzes the most recent core dump in the core dump directory.
+A specific core dump may be specified via either the
+.Ar core
+or
+.Ar dumpnr
+arguments.
+Once
+.Nm
+has located a core dump,
+it analyzes the core dump to determine the exact version of the kernel
+that generated the core.
+It then looks for a matching kernel file under each of the subdirectories in
+.Pa /boot .
+The location of the kernel file can also be explicitly provided via the
+.Ar kernel
+argument.
+.Pp
+Once
+.Nm
+has located a core dump and kernel,
+it uses several utilities to analyze the core including
+.Xr dmesg 8 ,
+.Xr fstat 1 ,
+.Xr iostat 8 ,
+.Xr ipcs 1 ,
+.Xr kgdb 1 ,
+.Xr netstat 1 ,
+.Xr nfsstat 1 ,
+.Xr ps 1 ,
+.Xr pstat 8 ,
+and
+.Xr vmstat 8 .
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl d Ar crashdir
+Specify an alternate core dump directory.
+The default crash dump directory is
+.Pa /var/crash .
+.It Fl n Ar dumpnr
+Use the core dump saved in
+.Pa vmcore. Ns Ar dumpnr
+instead of the latest core in the core dump directory.
+.It Fl k Ar kernel
+Specify an explicit kernel file.
+.El
+.Sh SEE ALSO
+.Xr textdump 4 ,
+.Xr savecore 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 6.4 .
diff --git a/usr.sbin/crashinfo/crashinfo.sh b/usr.sbin/crashinfo/crashinfo.sh
new file mode 100755
index 0000000..3a55e5d
--- /dev/null
+++ b/usr.sbin/crashinfo/crashinfo.sh
@@ -0,0 +1,304 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Yahoo!, 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.
+# 3. 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 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$
+
+usage()
+{
+ echo "usage: crashinfo [-d crashdir] [-n dumpnr] [-k kernel] [core]"
+ exit 1
+}
+
+find_kernel()
+{
+ local ivers k kvers
+
+ ivers=$(awk '
+ /Version String/ {
+ print
+ nextline=1
+ next
+ }
+ nextline==1 {
+ if ($0 ~ "^ [A-Za-z ]+: ") {
+ nextline=0
+ } else {
+ print
+ }
+ }' $INFO)
+
+ # Look for a matching kernel version.
+ for k in `sysctl -n kern.bootfile` $(ls -t /boot/*/kernel); do
+ kvers=$(echo 'printf " Version String: %s", version' | \
+ gdb -x /dev/stdin -batch $k 2>/dev/null)
+ if [ "$ivers" = "$kvers" ]; then
+ KERNEL=$k
+ break
+ fi
+ done
+}
+
+CRASHDIR=/var/crash
+DUMPNR=
+KERNEL=
+
+while getopts "d:n:k:" opt; do
+ case "$opt" in
+ d)
+ CRASHDIR=$OPTARG
+ ;;
+ n)
+ DUMPNR=$OPTARG
+ ;;
+ k)
+ KERNEL=$OPTARG
+ ;;
+ \?)
+ usage
+ ;;
+ esac
+done
+
+shift $((OPTIND - 1))
+
+if [ $# -eq 1 ]; then
+ if [ -n "$DUMPNR" ]; then
+ echo "-n and an explicit vmcore are mutually exclusive"
+ usage
+ fi
+
+ # Figure out the crash directory and number from the vmcore name.
+ CRASHDIR=`dirname $1`
+ DUMPNR=$(expr $(basename $1) : 'vmcore\.\([0-9]*\)$')
+ if [ -z "$DUMPNR" ]; then
+ echo "Unable to determine dump number from vmcore file $1."
+ exit 1
+ fi
+elif [ $# -gt 1 ]; then
+ usage
+else
+ # If we don't have an explicit dump number, operate on the most
+ # recent dump.
+ if [ -z "$DUMPNR" ]; then
+ if ! [ -r $CRASHDIR/bounds ]; then
+ echo "No crash dumps in $CRASHDIR."
+ exit 1
+ fi
+ next=`cat $CRASHDIR/bounds`
+ if [ -z "$next" ] || [ "$next" -eq 0 ]; then
+ echo "No crash dumps in $CRASHDIR."
+ exit 1
+ fi
+ DUMPNR=$(($next - 1))
+ fi
+fi
+
+VMCORE=$CRASHDIR/vmcore.$DUMPNR
+INFO=$CRASHDIR/info.$DUMPNR
+FILE=$CRASHDIR/core.txt.$DUMPNR
+HOSTNAME=`hostname`
+
+if [ ! -e $VMCORE ]; then
+ echo "$VMCORE not found"
+ exit 1
+fi
+
+if [ ! -e $INFO ]; then
+ echo "$INFO not found"
+ exit 1
+fi
+
+# If the user didn't specify a kernel, then try to find one.
+if [ -z "$KERNEL" ]; then
+ find_kernel
+ if [ -z "$KERNEL" ]; then
+ echo "Unable to find matching kernel for $VMCORE"
+ exit 1
+ fi
+elif [ ! -e $KERNEL ]; then
+ echo "$KERNEL not found"
+ exit 1
+fi
+
+echo "Writing crash summary to $FILE."
+
+umask 077
+
+# Simulate uname
+ostype=$(echo -e printf '"%s", ostype' | gdb -x /dev/stdin -batch $KERNEL)
+osrelease=$(echo -e printf '"%s", osrelease' | gdb -x /dev/stdin -batch $KERNEL)
+version=$(echo -e printf '"%s", version' | gdb -x /dev/stdin -batch $KERNEL | \
+ tr '\t\n' ' ')
+machine=$(echo -e printf '"%s", machine' | gdb -x /dev/stdin -batch $KERNEL)
+
+exec > $FILE 2>&1
+
+echo "$HOSTNAME dumped core - see $VMCORE"
+echo
+date
+echo
+echo "$ostype $HOSTNAME $osrelease $version $machine"
+echo
+sed -ne '/^ Panic String: /{s//panic: /;p;}' $INFO
+echo
+
+# XXX: /bin/sh on 7.0+ is broken so we can't simply pipe the commands to
+# kgdb via stdin and have to use a temporary file instead.
+file=`mktemp /tmp/crashinfo.XXXXXX`
+if [ $? -eq 0 ]; then
+ echo "bt" >> $file
+ echo "quit" >> $file
+ kgdb $KERNEL $VMCORE < $file
+ rm -f $file
+ echo
+fi
+echo
+
+echo "------------------------------------------------------------------------"
+echo "ps -axlww"
+echo
+ps -M $VMCORE -N $KERNEL -axlww
+echo
+
+echo "------------------------------------------------------------------------"
+echo "vmstat -s"
+echo
+vmstat -M $VMCORE -N $KERNEL -s
+echo
+
+echo "------------------------------------------------------------------------"
+echo "vmstat -m"
+echo
+vmstat -M $VMCORE -N $KERNEL -m
+echo
+
+echo "------------------------------------------------------------------------"
+echo "vmstat -z"
+echo
+vmstat -M $VMCORE -N $KERNEL -z
+echo
+
+echo "------------------------------------------------------------------------"
+echo "vmstat -i"
+echo
+vmstat -M $VMCORE -N $KERNEL -i
+echo
+
+echo "------------------------------------------------------------------------"
+echo "pstat -T"
+echo
+pstat -M $VMCORE -N $KERNEL -T
+echo
+
+echo "------------------------------------------------------------------------"
+echo "pstat -s"
+echo
+pstat -M $VMCORE -N $KERNEL -s
+echo
+
+echo "------------------------------------------------------------------------"
+echo "iostat"
+echo
+iostat -M $VMCORE -N $KERNEL
+echo
+
+echo "------------------------------------------------------------------------"
+echo "ipcs -a"
+echo
+ipcs -C $VMCORE -N $KERNEL -a
+echo
+
+echo "------------------------------------------------------------------------"
+echo "ipcs -T"
+echo
+ipcs -C $VMCORE -N $KERNEL -T
+echo
+
+# XXX: This doesn't actually work in 5.x+
+if false; then
+echo "------------------------------------------------------------------------"
+echo "w -dn"
+echo
+w -M $VMCORE -N $KERNEL -dn
+echo
+fi
+
+echo "------------------------------------------------------------------------"
+echo "nfsstat"
+echo
+nfsstat -M $VMCORE -N $KERNEL
+echo
+
+echo "------------------------------------------------------------------------"
+echo "netstat -s"
+echo
+netstat -M $VMCORE -N $KERNEL -s
+echo
+
+echo "------------------------------------------------------------------------"
+echo "netstat -m"
+echo
+netstat -M $VMCORE -N $KERNEL -m
+echo
+
+echo "------------------------------------------------------------------------"
+echo "netstat -anA"
+echo
+netstat -M $VMCORE -N $KERNEL -anA
+echo
+
+echo "------------------------------------------------------------------------"
+echo "netstat -aL"
+echo
+netstat -M $VMCORE -N $KERNEL -aL
+echo
+
+echo "------------------------------------------------------------------------"
+echo "fstat"
+echo
+fstat -M $VMCORE -N $KERNEL
+echo
+
+echo "------------------------------------------------------------------------"
+echo "dmesg"
+echo
+dmesg -a -M $VMCORE -N $KERNEL
+echo
+
+echo "------------------------------------------------------------------------"
+echo "kernel config"
+echo
+config -x $KERNEL
+
+echo
+echo "------------------------------------------------------------------------"
+echo "ddb capture buffer"
+echo
+
+ddb capture -M $VMCORE -N $KERNEL print
diff --git a/usr.sbin/cron/Makefile b/usr.sbin/cron/Makefile
new file mode 100644
index 0000000..62f853c
--- /dev/null
+++ b/usr.sbin/cron/Makefile
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+SUBDIR= lib cron crontab
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/cron/Makefile.inc b/usr.sbin/cron/Makefile.inc
new file mode 100644
index 0000000..265f86d
--- /dev/null
+++ b/usr.sbin/cron/Makefile.inc
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+.include "../Makefile.inc"
diff --git a/usr.sbin/cron/cron/Makefile b/usr.sbin/cron/cron/Makefile
new file mode 100644
index 0000000..0aa84d8
--- /dev/null
+++ b/usr.sbin/cron/cron/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+PROG= cron
+MAN= cron.8
+SRCS= cron.c database.c do_command.c job.c user.c popen.c
+
+CFLAGS+= -DLOGIN_CAP -DPAM
+
+LIBADD= cron pam util
+
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/cron/cron/Makefile.depend b/usr.sbin/cron/cron/Makefile.depend
new file mode 100644
index 0000000..b21f78a
--- /dev/null
+++ b/usr.sbin/cron/cron/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libpam/libpam \
+ lib/libutil \
+ usr.sbin/cron/lib \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/cron/cron/compat.h b/usr.sbin/cron/cron/compat.h
new file mode 100644
index 0000000..905c3aa
--- /dev/null
+++ b/usr.sbin/cron/cron/compat.h
@@ -0,0 +1,140 @@
+/* Copyright 1993,1994 by Paul Vixie
+ * All rights reserved
+ *
+ * Distribute freely, except: don't remove my name from the source or
+ * documentation (don't take credit for my work), mark your changes (don't
+ * get me blamed for your possible bugs), don't alter or remove this
+ * notice. May be sold if buildable source is provided to buyer. No
+ * warrantee of any kind, express or implied, is included with this
+ * software; use at your own risk, responsibility for damages (if any) to
+ * anyone resulting from the use of this software rests entirely with the
+ * user.
+ *
+ * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+ * I'll try to keep a version up to date. I can be reached as follows:
+ * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+ */
+
+/*
+ * $FreeBSD$
+ */
+
+#ifndef __P
+# ifdef __STDC__
+# define __P(x) x
+# else
+# define __P(x) ()
+# define const
+# endif
+#endif
+
+#if defined(UNIXPC) || defined(unixpc)
+# define UNIXPC 1
+# define ATT 1
+#endif
+
+#if defined(hpux) || defined(_hpux) || defined(__hpux)
+# define HPUX 1
+# define seteuid(e) setresuid(-1,e,-1)
+# define setreuid(r,e) setresuid(r,e,-1)
+#endif
+
+#if defined(_IBMR2)
+# define AIX 1
+#endif
+
+#if defined(__convex__)
+# define CONVEX 1
+#endif
+
+#if defined(sgi) || defined(_sgi) || defined(__sgi)
+# define IRIX 1
+/* IRIX 4 hdrs are broken: one cannot #include both <stdio.h>
+ * and <stdlib.h> because they disagree on system(), perror().
+ * Therefore we must zap the "const" keyword BEFORE including
+ * either of them.
+ */
+# define const
+#endif
+
+#if defined(_UNICOS)
+# define UNICOS 1
+#endif
+
+#ifndef POSIX
+# if (BSD >= 199103) || defined(__linux) || defined(ultrix) || defined(AIX) ||\
+ defined(HPUX) || defined(CONVEX) || defined(IRIX)
+# define POSIX
+# endif
+#endif
+
+#ifndef BSD
+# if defined(ultrix)
+# define BSD 198902
+# endif
+#endif
+
+/*****************************************************************/
+
+#if !defined(BSD) && !defined(HPUX) && !defined(CONVEX) && !defined(__linux)
+# define NEED_VFORK
+#endif
+
+#if (!defined(BSD) || (BSD < 198902)) && !defined(__linux) && \
+ !defined(IRIX) && !defined(NeXT) && !defined(HPUX)
+# define NEED_STRCASECMP
+#endif
+
+#if (!defined(BSD) || (BSD < 198911)) && !defined(__linux) &&\
+ !defined(IRIX) && !defined(UNICOS) && !defined(HPUX)
+# define NEED_STRDUP
+#endif
+
+#if (!defined(BSD) || (BSD < 198911)) && !defined(POSIX) && !defined(NeXT)
+# define NEED_STRERROR
+#endif
+
+#if defined(HPUX) || defined(AIX) || defined(UNIXPC)
+# define NEED_FLOCK
+#endif
+
+#ifndef POSIX
+# define NEED_SETSID
+#endif
+
+#if (defined(POSIX) && !defined(BSD)) && !defined(__linux)
+# define NEED_GETDTABLESIZE
+#endif
+
+#ifdef POSIX
+#include <unistd.h>
+#ifdef _POSIX_SAVED_IDS
+# define HAVE_SAVED_UIDS
+#endif
+#endif
+
+#if !defined(ATT) && !defined(__linux) && !defined(IRIX) && !defined(UNICOS)
+# define USE_SIGCHLD
+#endif
+
+#if !defined(AIX) && !defined(UNICOS)
+# define SYS_TIME_H 1
+#else
+# define SYS_TIME_H 0
+#endif
+
+#if defined(BSD) && !defined(POSIX)
+# define USE_UTIMES
+#endif
+
+#if defined(AIX) || defined(HPUX) || defined(IRIX)
+# define NEED_SETENV
+#endif
+
+#if !defined(UNICOS) && !defined(UNIXPC)
+# define HAS_FCHOWN
+#endif
+
+#if !defined(UNICOS) && !defined(UNIXPC)
+# define HAS_FCHMOD
+#endif
diff --git a/usr.sbin/cron/cron/config.h b/usr.sbin/cron/cron/config.h
new file mode 100644
index 0000000..6d77da2
--- /dev/null
+++ b/usr.sbin/cron/cron/config.h
@@ -0,0 +1,87 @@
+/* Copyright 1988,1990,1993,1994 by Paul Vixie
+ * All rights reserved
+ *
+ * Distribute freely, except: don't remove my name from the source or
+ * documentation (don't take credit for my work), mark your changes (don't
+ * get me blamed for your possible bugs), don't alter or remove this
+ * notice. May be sold if buildable source is provided to buyer. No
+ * warrantee of any kind, express or implied, is included with this
+ * software; use at your own risk, responsibility for damages (if any) to
+ * anyone resulting from the use of this software rests entirely with the
+ * user.
+ *
+ * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+ * I'll try to keep a version up to date. I can be reached as follows:
+ * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+ */
+
+/* config.h - configurables for Vixie Cron
+ *
+ * $FreeBSD$
+ */
+
+#if !defined(_PATH_SENDMAIL)
+# define _PATH_SENDMAIL "/usr/lib/sendmail"
+#endif /*SENDMAIL*/
+
+/*
+ * these are site-dependent
+ */
+
+#ifndef DEBUGGING
+#define DEBUGGING 1 /* 1 or 0 -- do you want debugging code built in? */
+#endif
+
+ /*
+ * choose one of these MAILCMD commands. I use
+ * /bin/mail for speed; it makes biff bark but doesn't
+ * do aliasing. /usr/lib/sendmail does aliasing but is
+ * a hog for short messages. aliasing is not needed
+ * if you make use of the MAILTO= feature in crontabs.
+ * (hint: MAILTO= was added for this reason).
+ */
+
+#define MAILCMD _PATH_SENDMAIL /*-*/
+#define MAILARGS "%s -FCronDaemon -odi -oem -oi -t" /*-*/
+ /* -Fx = set full-name of sender
+ * -odi = Option Deliverymode Interactive
+ * -oem = Option Errors Mailedtosender
+ * -oi = Option dot message terminator
+ * -t = read recipients from header of message
+ */
+
+/* #define MAILCMD "/bin/mail" */ /*-*/
+/* #define MAILARGS "%s -d %s" */ /*-*/
+ /* -d = undocumented but common flag: deliver locally?
+ */
+
+/* #define MAILCMD "/usr/mmdf/bin/submit" */ /*-*/
+/* #define MAILARGS "%s -mlrxto %s" */ /*-*/
+
+/* #define MAIL_DATE */ /*-*/
+ /* should we include an ersatz Date: header in
+ * generated mail? if you are using sendmail
+ * for MAILCMD, it is better to let sendmail
+ * generate the Date: header.
+ */
+
+ /* if ALLOW_FILE and DENY_FILE are not defined or are
+ * defined but neither exists, should crontab(1) be
+ * usable only by root?
+ */
+/* #define ALLOW_ONLY_ROOT */ /*-*/
+
+ /* if you want to use syslog(3) instead of appending
+ * to CRONDIR/LOG_FILE (/var/cron/log, e.g.), define
+ * SYSLOG here. Note that quite a bit of logging
+ * info is written, and that you probably don't want
+ * to use this on 4.2bsd since everything goes in
+ * /usr/spool/mqueue/syslog. On 4.[34]bsd you can
+ * tell /etc/syslog.conf to send cron's logging to
+ * a separate file.
+ *
+ * Note that if this and LOG_FILE in "pathnames.h"
+ * are both defined, then logging will go to both
+ * places.
+ */
+#define SYSLOG /*-*/
diff --git a/usr.sbin/cron/cron/cron.8 b/usr.sbin/cron/cron/cron.8
new file mode 100644
index 0000000..697629f
--- /dev/null
+++ b/usr.sbin/cron/cron/cron.8
@@ -0,0 +1,221 @@
+.\"/* Copyright 1988,1990,1993 by Paul Vixie
+.\" * All rights reserved
+.\" *
+.\" * Distribute freely, except: don't remove my name from the source or
+.\" * documentation (don't take credit for my work), mark your changes (don't
+.\" * get me blamed for your possible bugs), don't alter or remove this
+.\" * notice. May be sold if buildable source is provided to buyer. No
+.\" * warrantee of any kind, express or implied, is included with this
+.\" * software; use at your own risk, responsibility for damages (if any) to
+.\" * anyone resulting from the use of this software rests entirely with the
+.\" * user.
+.\" *
+.\" * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+.\" * I'll try to keep a version up to date. I can be reached as follows:
+.\" * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+.\" */
+.\"
+.\" $FreeBSD$
+.\"
+.Dd June 29, 2008
+.Dt CRON 8
+.Os
+.Sh NAME
+.Nm cron
+.Nd daemon to execute scheduled commands (Vixie Cron)
+.Sh SYNOPSIS
+.Nm
+.Op Fl j Ar jitter
+.Op Fl J Ar rootjitter
+.Op Fl m Ar mailto
+.Op Fl s
+.Op Fl o
+.Op Fl x Ar debugflag Ns Op , Ns Ar ...
+.Sh DESCRIPTION
+The
+.Nm
+utility should be started from
+.Pa /etc/rc
+or
+.Pa /etc/rc.local .
+It will return immediately,
+so you do not need to start it with '&'.
+.Pp
+The
+.Nm
+utility searches
+.Pa /var/cron/tabs
+for crontab files which are named after accounts in
+.Pa /etc/passwd ;
+crontabs found are loaded into memory.
+The
+.Nm
+utility also searches for
+.Pa /etc/crontab
+which is in a different format (see
+.Xr crontab 5 ) .
+.Pp
+The
+.Nm
+utility
+then wakes up every minute, examining all stored crontabs, checking each
+command to see if it should be run in the current minute.
+Before running a command from a per-account crontab file,
+.Nm
+checks the status of the account with
+.Xr pam 3
+and skips the command if the account is unavailable,
+e.g., locked out or expired.
+Commands from
+.Pa /etc/crontab
+bypass this check.
+When executing
+commands, any output is mailed to the owner of the crontab (or to the user
+named in the
+.Ev MAILTO
+environment variable in the crontab, if such exists).
+.Pp
+Additionally,
+.Nm
+checks each minute to see if its spool directory's modification time (or
+the modification time on
+.Pa /etc/crontab )
+has changed, and if it has,
+.Nm
+will then examine the modification time on all crontabs and reload those
+which have changed.
+Thus
+.Nm
+need not be restarted whenever a crontab file is modified.
+Note that the
+.Xr crontab 1
+command updates the modification time of the spool directory whenever it
+changes a crontab.
+.Pp
+Available options:
+.Bl -tag -width indent
+.It Fl j Ar jitter
+Enable time jitter.
+Prior to executing commands,
+.Nm
+will sleep a random number of seconds in the range from 0 to
+.Ar jitter .
+This will not affect superuser jobs (see
+.Fl J ) .
+A value for
+.Ar jitter
+must be between 0 and 60 inclusive.
+Default is 0, which effectively disables time jitter.
+.Pp
+This option can help to smooth down system load spikes during
+moments when a lot of jobs are likely to start at once, e.g.,
+at the beginning of the first minute of each hour.
+.It Fl J Ar rootjitter
+Enable time jitter for superuser jobs.
+The same as
+.Fl j
+except that it will affect jobs run by the superuser only.
+.It Fl m Ar mailto
+Overrides the default recipient for
+.Nm
+mail.
+Each
+.Xr crontab 5
+without
+.Ev MAILTO
+explicitly set will send mail to the
+.Ar mailto
+mailbox.
+Sending mail will be disabled by default if
+.Ar mailto
+set to a null string, usually specified in a shell as
+.Li ''
+or
+.Li \*q\*q .
+.It Fl s
+Enable special handling of situations when the GMT offset of the local
+timezone changes, such as the switches between the standard time and
+daylight saving time.
+.Pp
+The jobs run during the GMT offset changes time as
+intuitively expected.
+If a job falls into a time interval that disappears
+(for example, during the switch from
+standard time) to daylight saving time or is
+duplicated (for example, during the reverse switch), then it is handled
+in one of two ways:
+.Pp
+The first case is for the jobs that run every at hour of a time interval
+overlapping with the disappearing or duplicated interval.
+In other words, if the job had run within one hour before the GMT offset change
+(and cron was not restarted nor the
+.Xr crontab 5
+changed after that)
+or would run after the change at the next hour.
+They work as always, skip the skipped time or run in the added
+time as usual.
+.Pp
+The second case is for the jobs that run less frequently.
+They are executed exactly once, they are not skipped nor
+executed twice (unless cron is restarted or the user's
+.Xr crontab 5
+is changed during such a time interval).
+If an interval disappears
+due to the GMT offset change, such jobs are
+executed at the same absolute point of time as they would be in the
+old time zone.
+For example, if exactly one hour disappears, this
+point would be during the next hour at the first minute that is
+specified for them in
+.Xr crontab 5 .
+.It Fl o
+Disable the special handling of situations when the GMT offset of the local
+timezone changes, to be compatible with the old (default) behavior.
+If both options
+.Fl o
+and
+.Fl s
+are specified, the option specified last wins.
+.It Fl x Ar debugflag Ns Op , Ns Ar ...
+Enable writing of debugging information to standard output.
+One or more of the following comma separated
+.Ar debugflag
+identifiers must be specified:
+.Pp
+.Bl -tag -width ".Cm proc" -compact
+.It Cm bit
+currently not used
+.It Cm ext
+make the other debug flags more verbose
+.It Cm load
+be verbose when loading crontab files
+.It Cm misc
+be verbose about miscellaneous one-off events
+.It Cm pars
+be verbose about parsing individual crontab lines
+.It Cm proc
+be verbose about the state of the process, including all of its offspring
+.It Cm sch
+be verbose when iterating through the scheduling algorithms
+.It Cm test
+trace through the execution, but do not perform any actions
+.El
+.El
+.Sh FILES
+.Bl -tag -width /etc/pam.d/cron -compact
+.It Pa /etc/crontab
+System crontab file
+.It Pa /etc/pam.d/cron
+.Xr pam.conf 5
+configuration file for
+.Nm
+.It Pa /var/cron/tabs
+Directory for personal crontab files
+.El
+.Sh SEE ALSO
+.Xr crontab 1 ,
+.Xr pam 3 ,
+.Xr crontab 5 ,
+.Xr pam.conf 5
+.Sh AUTHORS
+.An Paul Vixie Aq Mt paul@vix.com
diff --git a/usr.sbin/cron/cron/cron.c b/usr.sbin/cron/cron/cron.c
new file mode 100644
index 0000000..0cdb9e3
--- /dev/null
+++ b/usr.sbin/cron/cron/cron.c
@@ -0,0 +1,561 @@
+/* Copyright 1988,1990,1993,1994 by Paul Vixie
+ * All rights reserved
+ *
+ * Distribute freely, except: don't remove my name from the source or
+ * documentation (don't take credit for my work), mark your changes (don't
+ * get me blamed for your possible bugs), don't alter or remove this
+ * notice. May be sold if buildable source is provided to buyer. No
+ * warrantee of any kind, express or implied, is included with this
+ * software; use at your own risk, responsibility for damages (if any) to
+ * anyone resulting from the use of this software rests entirely with the
+ * user.
+ *
+ * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+ * I'll try to keep a version up to date. I can be reached as follows:
+ * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+ */
+
+#if !defined(lint) && !defined(LINT)
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif
+
+#define MAIN_PROGRAM
+
+
+#include "cron.h"
+#include <sys/mman.h>
+#include <sys/signal.h>
+#if SYS_TIME_H
+# include <sys/time.h>
+#else
+# include <time.h>
+#endif
+
+
+static void usage(void),
+ run_reboot_jobs(cron_db *),
+ cron_tick(cron_db *, int),
+ cron_sync(int),
+ cron_sleep(cron_db *, int),
+ cron_clean(cron_db *),
+#ifdef USE_SIGCHLD
+ sigchld_handler(int),
+#endif
+ sighup_handler(int),
+ parse_args(int c, char *v[]);
+
+static int run_at_secres(cron_db *);
+
+static time_t last_time = 0;
+static int dst_enabled = 0;
+struct pidfh *pfh;
+
+static void
+usage() {
+#if DEBUGGING
+ char **dflags;
+#endif
+
+ fprintf(stderr, "usage: cron [-j jitter] [-J rootjitter] "
+ "[-m mailto] [-s] [-o] [-x debugflag[,...]]\n");
+#if DEBUGGING
+ fprintf(stderr, "\ndebugflags: ");
+
+ for(dflags = DebugFlagNames; *dflags; dflags++) {
+ fprintf(stderr, "%s ", *dflags);
+ }
+ fprintf(stderr, "\n");
+#endif
+
+ exit(ERROR_EXIT);
+}
+
+static void
+open_pidfile(void)
+{
+ char pidfile[MAX_FNAME];
+ char buf[MAX_TEMPSTR];
+ int otherpid;
+
+ (void) snprintf(pidfile, sizeof(pidfile), PIDFILE, PIDDIR);
+ pfh = pidfile_open(pidfile, 0600, &otherpid);
+ if (pfh == NULL) {
+ if (errno == EEXIST) {
+ snprintf(buf, sizeof(buf),
+ "cron already running, pid: %d", otherpid);
+ } else {
+ snprintf(buf, sizeof(buf),
+ "can't open or create %s: %s", pidfile,
+ strerror(errno));
+ }
+ log_it("CRON", getpid(), "DEATH", buf);
+ errx(ERROR_EXIT, "%s", buf);
+ }
+}
+
+int
+main(argc, argv)
+ int argc;
+ char *argv[];
+{
+ cron_db database;
+ int runnum;
+ int secres1, secres2;
+ struct tm *tm;
+
+ ProgramName = argv[0];
+
+#if defined(BSD)
+ setlinebuf(stdout);
+ setlinebuf(stderr);
+#endif
+
+ parse_args(argc, argv);
+
+#ifdef USE_SIGCHLD
+ (void) signal(SIGCHLD, sigchld_handler);
+#else
+ (void) signal(SIGCLD, SIG_IGN);
+#endif
+ (void) signal(SIGHUP, sighup_handler);
+
+ open_pidfile();
+ set_cron_uid();
+ set_cron_cwd();
+
+#if defined(POSIX)
+ setenv("PATH", _PATH_DEFPATH, 1);
+#endif
+
+ /* if there are no debug flags turned on, fork as a daemon should.
+ */
+# if DEBUGGING
+ if (DebugFlags) {
+# else
+ if (0) {
+# endif
+ (void) fprintf(stderr, "[%d] cron started\n", getpid());
+ } else {
+ if (daemon(1, 0) == -1) {
+ pidfile_remove(pfh);
+ log_it("CRON",getpid(),"DEATH","can't become daemon");
+ exit(0);
+ }
+ }
+
+ if (madvise(NULL, 0, MADV_PROTECT) != 0)
+ log_it("CRON", getpid(), "WARNING", "madvise() failed");
+
+ pidfile_write(pfh);
+ database.head = NULL;
+ database.tail = NULL;
+ database.mtime = (time_t) 0;
+ load_database(&database);
+ secres1 = secres2 = run_at_secres(&database);
+ run_reboot_jobs(&database);
+ cron_sync(secres1);
+ runnum = 0;
+ while (TRUE) {
+# if DEBUGGING
+ /* if (!(DebugFlags & DTEST)) */
+# endif /*DEBUGGING*/
+ cron_sleep(&database, secres1);
+
+ if (secres1 == 0 || runnum % 60 == 0) {
+ load_database(&database);
+ secres2 = run_at_secres(&database);
+ if (secres2 != secres1) {
+ secres1 = secres2;
+ if (secres1 != 0) {
+ runnum = 0;
+ } else {
+ /*
+ * Going from 1 sec to 60 sec res. If we
+ * are already at minute's boundary, so
+ * let it run, otherwise schedule for the
+ * next minute.
+ */
+ tm = localtime(&TargetTime);
+ if (tm->tm_sec > 0) {
+ cron_sync(secres2);
+ continue;
+ }
+ }
+ }
+ }
+
+ /* do this iteration
+ */
+ cron_tick(&database, secres1);
+
+ /* sleep 1 or 60 seconds
+ */
+ TargetTime += (secres1 != 0) ? 1 : 60;
+ runnum += 1;
+ }
+}
+
+
+static void
+run_reboot_jobs(db)
+ cron_db *db;
+{
+ register user *u;
+ register entry *e;
+
+ for (u = db->head; u != NULL; u = u->next) {
+ for (e = u->crontab; e != NULL; e = e->next) {
+ if (e->flags & WHEN_REBOOT) {
+ job_add(e, u);
+ }
+ }
+ }
+ (void) job_runqueue();
+}
+
+
+static void
+cron_tick(cron_db *db, int secres)
+{
+ static struct tm lasttm;
+ static time_t diff = 0, /* time difference in seconds from the last offset change */
+ difflimit = 0; /* end point for the time zone correction */
+ struct tm otztm; /* time in the old time zone */
+ int otzsecond, otzminute, otzhour, otzdom, otzmonth, otzdow;
+ register struct tm *tm = localtime(&TargetTime);
+ register int second, minute, hour, dom, month, dow;
+ register user *u;
+ register entry *e;
+
+ /* make 0-based values out of these so we can use them as indicies
+ */
+ second = (secres == 0) ? 0 : tm->tm_sec -FIRST_SECOND;
+ minute = tm->tm_min -FIRST_MINUTE;
+ hour = tm->tm_hour -FIRST_HOUR;
+ dom = tm->tm_mday -FIRST_DOM;
+ month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
+ dow = tm->tm_wday -FIRST_DOW;
+
+ Debug(DSCH, ("[%d] tick(%d,%d,%d,%d,%d,%d)\n",
+ getpid(), second, minute, hour, dom, month, dow))
+
+ if (dst_enabled && last_time != 0
+ && TargetTime > last_time /* exclude stepping back */
+ && tm->tm_gmtoff != lasttm.tm_gmtoff ) {
+
+ diff = tm->tm_gmtoff - lasttm.tm_gmtoff;
+
+ if ( diff > 0 ) { /* ST->DST */
+ /* mark jobs for an earlier run */
+ difflimit = TargetTime + diff;
+ for (u = db->head; u != NULL; u = u->next) {
+ for (e = u->crontab; e != NULL; e = e->next) {
+ e->flags &= ~NOT_UNTIL;
+ if ( e->lastrun >= TargetTime )
+ e->lastrun = 0;
+ /* not include the ends of hourly ranges */
+ if ( e->lastrun < TargetTime - 3600 )
+ e->flags |= RUN_AT;
+ else
+ e->flags &= ~RUN_AT;
+ }
+ }
+ } else { /* diff < 0 : DST->ST */
+ /* mark jobs for skipping */
+ difflimit = TargetTime - diff;
+ for (u = db->head; u != NULL; u = u->next) {
+ for (e = u->crontab; e != NULL; e = e->next) {
+ e->flags |= NOT_UNTIL;
+ e->flags &= ~RUN_AT;
+ }
+ }
+ }
+ }
+
+ if (diff != 0) {
+ /* if the time was reset of the end of special zone is reached */
+ if (last_time == 0 || TargetTime >= difflimit) {
+ /* disable the TZ switch checks */
+ diff = 0;
+ difflimit = 0;
+ for (u = db->head; u != NULL; u = u->next) {
+ for (e = u->crontab; e != NULL; e = e->next) {
+ e->flags &= ~(RUN_AT|NOT_UNTIL);
+ }
+ }
+ } else {
+ /* get the time in the old time zone */
+ time_t difftime = TargetTime + tm->tm_gmtoff - diff;
+ gmtime_r(&difftime, &otztm);
+
+ /* make 0-based values out of these so we can use them as indicies
+ */
+ otzsecond = (secres == 0) ? 0 : otztm.tm_sec -FIRST_SECOND;
+ otzminute = otztm.tm_min -FIRST_MINUTE;
+ otzhour = otztm.tm_hour -FIRST_HOUR;
+ otzdom = otztm.tm_mday -FIRST_DOM;
+ otzmonth = otztm.tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
+ otzdow = otztm.tm_wday -FIRST_DOW;
+ }
+ }
+
+ /* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the
+ * first and fifteenth AND every Sunday; '* * * * Sun' will run *only*
+ * on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this
+ * is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre.
+ * like many bizarre things, it's the standard.
+ */
+ for (u = db->head; u != NULL; u = u->next) {
+ for (e = u->crontab; e != NULL; e = e->next) {
+ Debug(DSCH|DEXT, ("user [%s:%d:%d:...] cmd=\"%s\"\n",
+ env_get("LOGNAME", e->envp),
+ e->uid, e->gid, e->cmd))
+
+ if ( diff != 0 && (e->flags & (RUN_AT|NOT_UNTIL)) ) {
+ if (bit_test(e->second, otzsecond)
+ && bit_test(e->minute, otzminute)
+ && bit_test(e->hour, otzhour)
+ && bit_test(e->month, otzmonth)
+ && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
+ ? (bit_test(e->dow,otzdow) && bit_test(e->dom,otzdom))
+ : (bit_test(e->dow,otzdow) || bit_test(e->dom,otzdom))
+ )
+ ) {
+ if ( e->flags & RUN_AT ) {
+ e->flags &= ~RUN_AT;
+ e->lastrun = TargetTime;
+ job_add(e, u);
+ continue;
+ } else
+ e->flags &= ~NOT_UNTIL;
+ } else if ( e->flags & NOT_UNTIL )
+ continue;
+ }
+
+ if (bit_test(e->second, second)
+ && bit_test(e->minute, minute)
+ && bit_test(e->hour, hour)
+ && bit_test(e->month, month)
+ && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
+ ? (bit_test(e->dow,dow) && bit_test(e->dom,dom))
+ : (bit_test(e->dow,dow) || bit_test(e->dom,dom))
+ )
+ ) {
+ e->flags &= ~RUN_AT;
+ e->lastrun = TargetTime;
+ job_add(e, u);
+ }
+ }
+ }
+
+ last_time = TargetTime;
+ lasttm = *tm;
+}
+
+
+/* the task here is to figure out how long it's going to be until :00 of the
+ * following minute and initialize TargetTime to this value. TargetTime
+ * will subsequently slide 60 seconds at a time, with correction applied
+ * implicitly in cron_sleep(). it would be nice to let cron execute in
+ * the "current minute" before going to sleep, but by restarting cron you
+ * could then get it to execute a given minute's jobs more than once.
+ * instead we have the chance of missing a minute's jobs completely, but
+ * that's something sysadmin's know to expect what with crashing computers..
+ */
+static void
+cron_sync(int secres) {
+ struct tm *tm;
+
+ TargetTime = time((time_t*)0);
+ if (secres != 0) {
+ TargetTime += 1;
+ } else {
+ tm = localtime(&TargetTime);
+ TargetTime += (60 - tm->tm_sec);
+ }
+}
+
+static void
+timespec_subtract(struct timespec *result, struct timespec *x,
+ struct timespec *y)
+{
+ *result = *x;
+ result->tv_sec -= y->tv_sec;
+ result->tv_nsec -= y->tv_nsec;
+ if (result->tv_nsec < 0) {
+ result->tv_sec--;
+ result->tv_nsec += 1000000000;
+ }
+}
+
+static void
+cron_sleep(cron_db *db, int secres)
+{
+ int seconds_to_wait;
+ int rval;
+ struct timespec ctime, ttime, stime, remtime;
+
+ /*
+ * Loop until we reach the top of the next minute, sleep when possible.
+ */
+
+ for (;;) {
+ clock_gettime(CLOCK_REALTIME, &ctime);
+ ttime.tv_sec = TargetTime;
+ ttime.tv_nsec = 0;
+ timespec_subtract(&stime, &ttime, &ctime);
+
+ /*
+ * If the seconds_to_wait value is insane, jump the cron
+ */
+
+ if (stime.tv_sec < -600 || stime.tv_sec > 600) {
+ cron_clean(db);
+ cron_sync(secres);
+ continue;
+ }
+
+ seconds_to_wait = (stime.tv_nsec > 0) ? stime.tv_sec + 1 :
+ stime.tv_sec;
+
+ Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n",
+ getpid(), (long)TargetTime, seconds_to_wait))
+
+ /*
+ * If we've run out of wait time or there are no jobs left
+ * to run, break
+ */
+
+ if (stime.tv_sec < 0)
+ break;
+ if (job_runqueue() == 0) {
+ Debug(DSCH, ("[%d] sleeping for %d seconds\n",
+ getpid(), seconds_to_wait))
+
+ for (;;) {
+ rval = nanosleep(&stime, &remtime);
+ if (rval == 0 || errno != EINTR)
+ break;
+ stime.tv_sec = remtime.tv_sec;
+ stime.tv_nsec = remtime.tv_nsec;
+ }
+ }
+ }
+}
+
+
+/* if the time was changed abruptly, clear the flags related
+ * to the daylight time switch handling to avoid strange effects
+ */
+
+static void
+cron_clean(db)
+ cron_db *db;
+{
+ user *u;
+ entry *e;
+
+ last_time = 0;
+
+ for (u = db->head; u != NULL; u = u->next) {
+ for (e = u->crontab; e != NULL; e = e->next) {
+ e->flags &= ~(RUN_AT|NOT_UNTIL);
+ }
+ }
+}
+
+#ifdef USE_SIGCHLD
+static void
+sigchld_handler(int x)
+{
+ WAIT_T waiter;
+ PID_T pid;
+
+ for (;;) {
+#ifdef POSIX
+ pid = waitpid(-1, &waiter, WNOHANG);
+#else
+ pid = wait3(&waiter, WNOHANG, (struct rusage *)0);
+#endif
+ switch (pid) {
+ case -1:
+ Debug(DPROC,
+ ("[%d] sigchld...no children\n", getpid()))
+ return;
+ case 0:
+ Debug(DPROC,
+ ("[%d] sigchld...no dead kids\n", getpid()))
+ return;
+ default:
+ Debug(DPROC,
+ ("[%d] sigchld...pid #%d died, stat=%d\n",
+ getpid(), pid, WEXITSTATUS(waiter)))
+ }
+ }
+}
+#endif /*USE_SIGCHLD*/
+
+
+static void
+sighup_handler(int x)
+{
+ log_close();
+}
+
+
+static void
+parse_args(argc, argv)
+ int argc;
+ char *argv[];
+{
+ int argch;
+ char *endp;
+
+ while ((argch = getopt(argc, argv, "j:J:m:osx:")) != -1) {
+ switch (argch) {
+ case 'j':
+ Jitter = strtoul(optarg, &endp, 10);
+ if (*optarg == '\0' || *endp != '\0' || Jitter > 60)
+ errx(ERROR_EXIT,
+ "bad value for jitter: %s", optarg);
+ break;
+ case 'J':
+ RootJitter = strtoul(optarg, &endp, 10);
+ if (*optarg == '\0' || *endp != '\0' || RootJitter > 60)
+ errx(ERROR_EXIT,
+ "bad value for root jitter: %s", optarg);
+ break;
+ case 'm':
+ defmailto = optarg;
+ break;
+ case 'o':
+ dst_enabled = 0;
+ break;
+ case 's':
+ dst_enabled = 1;
+ break;
+ case 'x':
+ if (!set_debug_flags(optarg))
+ usage();
+ break;
+ default:
+ usage();
+ }
+ }
+}
+
+static int
+run_at_secres(cron_db *db)
+{
+ user *u;
+ entry *e;
+
+ for (u = db->head; u != NULL; u = u->next) {
+ for (e = u->crontab; e != NULL; e = e->next) {
+ if ((e->flags & SEC_RES) != 0)
+ return 1;
+ }
+ }
+ return 0;
+}
diff --git a/usr.sbin/cron/cron/cron.h b/usr.sbin/cron/cron/cron.h
new file mode 100644
index 0000000..1a814df
--- /dev/null
+++ b/usr.sbin/cron/cron/cron.h
@@ -0,0 +1,307 @@
+/* Copyright 1988,1990,1993,1994 by Paul Vixie
+ * All rights reserved
+ *
+ * Distribute freely, except: don't remove my name from the source or
+ * documentation (don't take credit for my work), mark your changes (don't
+ * get me blamed for your possible bugs), don't alter or remove this
+ * notice. May be sold if buildable source is provided to buyer. No
+ * warrantee of any kind, express or implied, is included with this
+ * software; use at your own risk, responsibility for damages (if any) to
+ * anyone resulting from the use of this software rests entirely with the
+ * user.
+ *
+ * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+ * I'll try to keep a version up to date. I can be reached as follows:
+ * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+ */
+
+/* cron.h - header for vixie's cron
+ *
+ * $FreeBSD$
+ *
+ * vix 14nov88 [rest of log is in RCS]
+ * vix 14jan87 [0 or 7 can be sunday; thanks, mwm@berkeley]
+ * vix 30dec86 [written]
+ */
+
+/* reorder these #include's at your peril */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include "compat.h"
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <libutil.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <time.h>
+#include <sys/wait.h>
+
+#include "pathnames.h"
+#include "config.h"
+#include "externs.h"
+
+ /* these are really immutable, and are
+ * defined for symbolic convenience only
+ * TRUE, FALSE, and ERR must be distinct
+ * ERR must be < OK.
+ */
+#define TRUE 1
+#define FALSE 0
+ /* system calls return this on success */
+#define OK 0
+ /* or this on error */
+#define ERR (-1)
+
+ /* turn this on to get '-x' code */
+#ifndef DEBUGGING
+#define DEBUGGING FALSE
+#endif
+
+#define READ_PIPE 0 /* which end of a pipe pair do you read? */
+#define WRITE_PIPE 1 /* or write to? */
+#define STDIN 0 /* what is stdin's file descriptor? */
+#define STDOUT 1 /* stdout's? */
+#define STDERR 2 /* stderr's? */
+#define ERROR_EXIT 1 /* exit() with this will scare the shell */
+#define OK_EXIT 0 /* exit() with this is considered 'normal' */
+#define MAX_FNAME 100 /* max length of internally generated fn */
+#define MAX_COMMAND 1000 /* max length of internally generated cmd */
+#define MAX_ENVSTR 1000 /* max length of envvar=value\0 strings */
+#define MAX_TEMPSTR 100 /* obvious */
+#define MAX_UNAME 20 /* max length of username, should be overkill */
+#define ROOT_UID 0 /* don't change this, it really must be root */
+#define ROOT_USER "root" /* ditto */
+#define SYS_NAME "*system*" /* magic owner name for system crontab */
+
+ /* NOTE: these correspond to DebugFlagNames,
+ * defined below.
+ */
+#define DEXT 0x0001 /* extend flag for other debug masks */
+#define DSCH 0x0002 /* scheduling debug mask */
+#define DPROC 0x0004 /* process control debug mask */
+#define DPARS 0x0008 /* parsing debug mask */
+#define DLOAD 0x0010 /* database loading debug mask */
+#define DMISC 0x0020 /* misc debug mask */
+#define DTEST 0x0040 /* test mode: don't execute any commands */
+#define DBIT 0x0080 /* bit twiddling shown (long) */
+
+#define CRON_TAB(u) "%s/%s", SPOOL_DIR, u
+#define REG register
+#define PPC_NULL ((char **)NULL)
+
+#ifndef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 256
+#endif
+
+#define Skip_Blanks(c, f) \
+ while (c == '\t' || c == ' ') \
+ c = get_char(f);
+
+#define Skip_Nonblanks(c, f) \
+ while (c!='\t' && c!=' ' && c!='\n' && c != EOF) \
+ c = get_char(f);
+
+#define Skip_Line(c, f) \
+ do {c = get_char(f);} while (c != '\n' && c != EOF);
+
+#if DEBUGGING
+# define Debug(mask, message) \
+ if ( (DebugFlags & (mask) ) == (mask) ) \
+ printf message;
+#else /* !DEBUGGING */
+# define Debug(mask, message) \
+ ;
+#endif /* DEBUGGING */
+
+#define MkLower(ch) (isupper(ch) ? tolower(ch) : ch)
+#define MkUpper(ch) (islower(ch) ? toupper(ch) : ch)
+#define Set_LineNum(ln) {Debug(DPARS|DEXT,("linenum=%d\n",ln)); \
+ LineNumber = ln; \
+ }
+
+#define FIRST_SECOND 0
+#define LAST_SECOND 59
+#define SECOND_COUNT (LAST_SECOND - FIRST_SECOND + 1)
+
+#define FIRST_MINUTE 0
+#define LAST_MINUTE 59
+#define MINUTE_COUNT (LAST_MINUTE - FIRST_MINUTE + 1)
+
+#define FIRST_HOUR 0
+#define LAST_HOUR 23
+#define HOUR_COUNT (LAST_HOUR - FIRST_HOUR + 1)
+
+#define FIRST_DOM 1
+#define LAST_DOM 31
+#define DOM_COUNT (LAST_DOM - FIRST_DOM + 1)
+
+#define FIRST_MONTH 1
+#define LAST_MONTH 12
+#define MONTH_COUNT (LAST_MONTH - FIRST_MONTH + 1)
+
+/* note on DOW: 0 and 7 are both Sunday, for compatibility reasons. */
+#define FIRST_DOW 0
+#define LAST_DOW 7
+#define DOW_COUNT (LAST_DOW - FIRST_DOW + 1)
+
+#ifdef LOGIN_CAP
+/* see init.c */
+#define RESOURCE_RC "daemon"
+#endif
+
+ /* each user's crontab will be held as a list of
+ * the following structure.
+ *
+ * These are the cron commands.
+ */
+
+typedef struct _entry {
+ struct _entry *next;
+ uid_t uid;
+ gid_t gid;
+#ifdef LOGIN_CAP
+ char *class;
+#endif
+ char **envp;
+ char *cmd;
+ bitstr_t bit_decl(second, SECOND_COUNT);
+ bitstr_t bit_decl(minute, MINUTE_COUNT);
+ bitstr_t bit_decl(hour, HOUR_COUNT);
+ bitstr_t bit_decl(dom, DOM_COUNT);
+ bitstr_t bit_decl(month, MONTH_COUNT);
+ bitstr_t bit_decl(dow, DOW_COUNT);
+ int flags;
+#define DOM_STAR 0x01
+#define DOW_STAR 0x02
+#define WHEN_REBOOT 0x04
+#define RUN_AT 0x08
+#define NOT_UNTIL 0x10
+#define SEC_RES 0x20
+ time_t lastrun;
+} entry;
+
+ /* the crontab database will be a list of the
+ * following structure, one element per user
+ * plus one for the system.
+ *
+ * These are the crontabs.
+ */
+
+typedef struct _user {
+ struct _user *next, *prev; /* links */
+ char *name;
+ time_t mtime; /* last modtime of crontab */
+ entry *crontab; /* this person's crontab */
+} user;
+
+typedef struct _cron_db {
+ user *head, *tail; /* links */
+ time_t mtime; /* last modtime on spooldir */
+} cron_db;
+
+
+void set_cron_uid(void),
+ set_cron_cwd(void),
+ load_database(cron_db *),
+ open_logfile(void),
+ sigpipe_func(void),
+ job_add(entry *, user *),
+ do_command(entry *, user *),
+ link_user(cron_db *, user *),
+ unlink_user(cron_db *, user *),
+ free_user(user *),
+ env_free(char **),
+ unget_char(int, FILE *),
+ free_entry(entry *),
+ skip_comments(FILE *),
+ log_it(char *, int, char *, char *),
+ log_close(void);
+
+int job_runqueue(void),
+ set_debug_flags(char *),
+ get_char(FILE *),
+ get_string(char *, int, FILE *, char *),
+ swap_uids(void),
+ swap_uids_back(void),
+ load_env(char *, FILE *),
+ cron_pclose(FILE *),
+ strcmp_until(char *, char *, int),
+ allowed(char *),
+ strdtb(char *);
+
+char *env_get(char *, char **),
+ *arpadate(time_t *),
+ *mkprints(unsigned char *, unsigned int),
+ *first_word(char *, char *),
+ **env_init(void),
+ **env_copy(char **),
+ **env_set(char **, char *);
+
+user *load_user(int, struct passwd *, char *),
+ *find_user(cron_db *, char *);
+
+entry *load_entry(FILE *, void (*)(char *),
+ struct passwd *, char **);
+
+FILE *cron_popen(char *, char *, entry *);
+
+
+ /* in the C tradition, we only create
+ * variables for the main program, just
+ * extern them elsewhere.
+ */
+
+#ifdef MAIN_PROGRAM
+# if !defined(LINT) && !defined(lint)
+char *copyright[] = {
+ "@(#) Copyright 1988,1989,1990,1993,1994 by Paul Vixie",
+ "@(#) All rights reserved"
+ };
+# endif
+
+char *MonthNames[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
+ NULL
+ };
+
+char *DowNames[] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
+ NULL
+ };
+
+char *ProgramName,
+ *defmailto;
+int LineNumber;
+unsigned Jitter,
+ RootJitter;
+time_t TargetTime;
+
+# if DEBUGGING
+int DebugFlags;
+char *DebugFlagNames[] = { /* sync with #defines */
+ "ext", "sch", "proc", "pars", "load", "misc", "test", "bit",
+ NULL /* NULL must be last element */
+ };
+# endif /* DEBUGGING */
+#else /*MAIN_PROGRAM*/
+extern char *copyright[],
+ *MonthNames[],
+ *DowNames[],
+ *ProgramName,
+ *defmailto;
+extern int LineNumber;
+extern unsigned Jitter,
+ RootJitter;
+extern time_t TargetTime;
+extern struct pidfh *pfh;
+# if DEBUGGING
+extern int DebugFlags;
+extern char *DebugFlagNames[];
+# endif /* DEBUGGING */
+#endif /*MAIN_PROGRAM*/
diff --git a/usr.sbin/cron/cron/database.c b/usr.sbin/cron/cron/database.c
new file mode 100644
index 0000000..7a44d14
--- /dev/null
+++ b/usr.sbin/cron/cron/database.c
@@ -0,0 +1,263 @@
+/* Copyright 1988,1990,1993,1994 by Paul Vixie
+ * All rights reserved
+ *
+ * Distribute freely, except: don't remove my name from the source or
+ * documentation (don't take credit for my work), mark your changes (don't
+ * get me blamed for your possible bugs), don't alter or remove this
+ * notice. May be sold if buildable source is provided to buyer. No
+ * warrantee of any kind, express or implied, is included with this
+ * software; use at your own risk, responsibility for damages (if any) to
+ * anyone resulting from the use of this software rests entirely with the
+ * user.
+ *
+ * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+ * I'll try to keep a version up to date. I can be reached as follows:
+ * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+ */
+
+#if !defined(lint) && !defined(LINT)
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif
+
+/* vix 26jan87 [RCS has the log]
+ */
+
+
+#include "cron.h"
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+
+
+#define TMAX(a,b) ((a)>(b)?(a):(b))
+
+
+static void process_crontab(char *, char *, char *,
+ struct stat *,
+ cron_db *, cron_db *);
+
+
+void
+load_database(old_db)
+ cron_db *old_db;
+{
+ DIR *dir;
+ struct stat statbuf;
+ struct stat syscron_stat;
+ DIR_T *dp;
+ cron_db new_db;
+ user *u, *nu;
+
+ Debug(DLOAD, ("[%d] load_database()\n", getpid()))
+
+ /* before we start loading any data, do a stat on SPOOL_DIR
+ * so that if anything changes as of this moment (i.e., before we've
+ * cached any of the database), we'll see the changes next time.
+ */
+ if (stat(SPOOL_DIR, &statbuf) < OK) {
+ log_it("CRON", getpid(), "STAT FAILED", SPOOL_DIR);
+ (void) exit(ERROR_EXIT);
+ }
+
+ /* track system crontab file
+ */
+ if (stat(SYSCRONTAB, &syscron_stat) < OK)
+ syscron_stat.st_mtime = 0;
+
+ /* if spooldir's mtime has not changed, we don't need to fiddle with
+ * the database.
+ *
+ * Note that old_db->mtime is initialized to 0 in main(), and
+ * so is guaranteed to be different than the stat() mtime the first
+ * time this function is called.
+ */
+ if (old_db->mtime == TMAX(statbuf.st_mtime, syscron_stat.st_mtime)) {
+ Debug(DLOAD, ("[%d] spool dir mtime unch, no load needed.\n",
+ getpid()))
+ return;
+ }
+
+ /* something's different. make a new database, moving unchanged
+ * elements from the old database, reloading elements that have
+ * actually changed. Whatever is left in the old database when
+ * we're done is chaff -- crontabs that disappeared.
+ */
+ new_db.mtime = TMAX(statbuf.st_mtime, syscron_stat.st_mtime);
+ new_db.head = new_db.tail = NULL;
+
+ if (syscron_stat.st_mtime) {
+ process_crontab("root", SYS_NAME,
+ SYSCRONTAB, &syscron_stat,
+ &new_db, old_db);
+ }
+
+ /* we used to keep this dir open all the time, for the sake of
+ * efficiency. however, we need to close it in every fork, and
+ * we fork a lot more often than the mtime of the dir changes.
+ */
+ if (!(dir = opendir(SPOOL_DIR))) {
+ log_it("CRON", getpid(), "OPENDIR FAILED", SPOOL_DIR);
+ (void) exit(ERROR_EXIT);
+ }
+
+ while (NULL != (dp = readdir(dir))) {
+ char fname[MAXNAMLEN+1],
+ tabname[MAXNAMLEN+1];
+
+ /* avoid file names beginning with ".". this is good
+ * because we would otherwise waste two guaranteed calls
+ * to getpwnam() for . and .., and also because user names
+ * starting with a period are just too nasty to consider.
+ */
+ if (dp->d_name[0] == '.')
+ continue;
+
+ (void) strncpy(fname, dp->d_name, sizeof(fname));
+ fname[sizeof(fname)-1] = '\0';
+ (void) snprintf(tabname, sizeof tabname, CRON_TAB(fname));
+
+ process_crontab(fname, fname, tabname,
+ &statbuf, &new_db, old_db);
+ }
+ closedir(dir);
+
+ /* if we don't do this, then when our children eventually call
+ * getpwnam() in do_command.c's child_process to verify MAILTO=,
+ * they will screw us up (and v-v).
+ */
+ endpwent();
+
+ /* whatever's left in the old database is now junk.
+ */
+ Debug(DLOAD, ("unlinking old database:\n"))
+ for (u = old_db->head; u != NULL; u = nu) {
+ Debug(DLOAD, ("\t%s\n", u->name))
+ nu = u->next;
+ unlink_user(old_db, u);
+ free_user(u);
+ }
+
+ /* overwrite the database control block with the new one.
+ */
+ *old_db = new_db;
+ Debug(DLOAD, ("load_database is done\n"))
+}
+
+
+void
+link_user(db, u)
+ cron_db *db;
+ user *u;
+{
+ if (db->head == NULL)
+ db->head = u;
+ if (db->tail)
+ db->tail->next = u;
+ u->prev = db->tail;
+ u->next = NULL;
+ db->tail = u;
+}
+
+
+void
+unlink_user(db, u)
+ cron_db *db;
+ user *u;
+{
+ if (u->prev == NULL)
+ db->head = u->next;
+ else
+ u->prev->next = u->next;
+
+ if (u->next == NULL)
+ db->tail = u->prev;
+ else
+ u->next->prev = u->prev;
+}
+
+
+user *
+find_user(db, name)
+ cron_db *db;
+ char *name;
+{
+ char *env_get();
+ user *u;
+
+ for (u = db->head; u != NULL; u = u->next)
+ if (!strcmp(u->name, name))
+ break;
+ return u;
+}
+
+
+static void
+process_crontab(uname, fname, tabname, statbuf, new_db, old_db)
+ char *uname;
+ char *fname;
+ char *tabname;
+ struct stat *statbuf;
+ cron_db *new_db;
+ cron_db *old_db;
+{
+ struct passwd *pw = NULL;
+ int crontab_fd = OK - 1;
+ user *u;
+
+ if (strcmp(fname, SYS_NAME) && !(pw = getpwnam(uname))) {
+ /* file doesn't have a user in passwd file.
+ */
+ log_it(fname, getpid(), "ORPHAN", "no passwd entry");
+ goto next_crontab;
+ }
+
+ if ((crontab_fd = open(tabname, O_RDONLY, 0)) < OK) {
+ /* crontab not accessible?
+ */
+ log_it(fname, getpid(), "CAN'T OPEN", tabname);
+ goto next_crontab;
+ }
+
+ if (fstat(crontab_fd, statbuf) < OK) {
+ log_it(fname, getpid(), "FSTAT FAILED", tabname);
+ goto next_crontab;
+ }
+
+ Debug(DLOAD, ("\t%s:", fname))
+ u = find_user(old_db, fname);
+ if (u != NULL) {
+ /* if crontab has not changed since we last read it
+ * in, then we can just use our existing entry.
+ */
+ if (u->mtime == statbuf->st_mtime) {
+ Debug(DLOAD, (" [no change, using old data]"))
+ unlink_user(old_db, u);
+ link_user(new_db, u);
+ goto next_crontab;
+ }
+
+ /* before we fall through to the code that will reload
+ * the user, let's deallocate and unlink the user in
+ * the old database. This is more a point of memory
+ * efficiency than anything else, since all leftover
+ * users will be deleted from the old database when
+ * we finish with the crontab...
+ */
+ Debug(DLOAD, (" [delete old data]"))
+ unlink_user(old_db, u);
+ free_user(u);
+ log_it(fname, getpid(), "RELOAD", tabname);
+ }
+ u = load_user(crontab_fd, pw, fname);
+ if (u != NULL) {
+ u->mtime = statbuf->st_mtime;
+ link_user(new_db, u);
+ }
+
+next_crontab:
+ if (crontab_fd >= OK) {
+ Debug(DLOAD, (" [done]\n"))
+ close(crontab_fd);
+ }
+}
diff --git a/usr.sbin/cron/cron/do_command.c b/usr.sbin/cron/cron/do_command.c
new file mode 100644
index 0000000..7a7ac57
--- /dev/null
+++ b/usr.sbin/cron/cron/do_command.c
@@ -0,0 +1,622 @@
+/* Copyright 1988,1990,1993,1994 by Paul Vixie
+ * All rights reserved
+ *
+ * Distribute freely, except: don't remove my name from the source or
+ * documentation (don't take credit for my work), mark your changes (don't
+ * get me blamed for your possible bugs), don't alter or remove this
+ * notice. May be sold if buildable source is provided to buyer. No
+ * warrantee of any kind, express or implied, is included with this
+ * software; use at your own risk, responsibility for damages (if any) to
+ * anyone resulting from the use of this software rests entirely with the
+ * user.
+ *
+ * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+ * I'll try to keep a version up to date. I can be reached as follows:
+ * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+ */
+
+#if !defined(lint) && !defined(LINT)
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif
+
+
+#include "cron.h"
+#include <sys/signal.h>
+#if defined(sequent)
+# include <sys/universe.h>
+#endif
+#if defined(SYSLOG)
+# include <syslog.h>
+#endif
+#if defined(LOGIN_CAP)
+# include <login_cap.h>
+#endif
+#ifdef PAM
+# include <security/pam_appl.h>
+# include <security/openpam.h>
+#endif
+
+
+static void child_process(entry *, user *),
+ do_univ(user *);
+
+
+void
+do_command(e, u)
+ entry *e;
+ user *u;
+{
+ Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n",
+ getpid(), e->cmd, u->name, e->uid, e->gid))
+
+ /* fork to become asynchronous -- parent process is done immediately,
+ * and continues to run the normal cron code, which means return to
+ * tick(). the child and grandchild don't leave this function, alive.
+ *
+ * vfork() is unsuitable, since we have much to do, and the parent
+ * needs to be able to run off and fork other processes.
+ */
+ switch (fork()) {
+ case -1:
+ log_it("CRON",getpid(),"error","can't fork");
+ break;
+ case 0:
+ /* child process */
+ pidfile_close(pfh);
+ child_process(e, u);
+ Debug(DPROC, ("[%d] child process done, exiting\n", getpid()))
+ _exit(OK_EXIT);
+ break;
+ default:
+ /* parent process */
+ break;
+ }
+ Debug(DPROC, ("[%d] main process returning to work\n", getpid()))
+}
+
+
+static void
+child_process(e, u)
+ entry *e;
+ user *u;
+{
+ int stdin_pipe[2], stdout_pipe[2];
+ register char *input_data;
+ char *usernm, *mailto;
+ int children = 0;
+# if defined(LOGIN_CAP)
+ struct passwd *pwd;
+ login_cap_t *lc;
+# endif
+
+ Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd))
+
+ /* mark ourselves as different to PS command watchers by upshifting
+ * our program name. This has no effect on some kernels.
+ */
+ setproctitle("running job");
+
+ /* discover some useful and important environment settings
+ */
+ usernm = env_get("LOGNAME", e->envp);
+ mailto = env_get("MAILTO", e->envp);
+
+#ifdef PAM
+ /* use PAM to see if the user's account is available,
+ * i.e., not locked or expired or whatever. skip this
+ * for system tasks from /etc/crontab -- they can run
+ * as any user.
+ */
+ if (strcmp(u->name, SYS_NAME)) { /* not equal */
+ pam_handle_t *pamh = NULL;
+ int pam_err;
+ struct pam_conv pamc = {
+ .conv = openpam_nullconv,
+ .appdata_ptr = NULL
+ };
+
+ Debug(DPROC, ("[%d] checking account with PAM\n", getpid()))
+
+ /* u->name keeps crontab owner name while LOGNAME is the name
+ * of user to run command on behalf of. they should be the
+ * same for a task from a per-user crontab.
+ */
+ if (strcmp(u->name, usernm)) {
+ log_it(usernm, getpid(), "username ambiguity", u->name);
+ exit(ERROR_EXIT);
+ }
+
+ pam_err = pam_start("cron", usernm, &pamc, &pamh);
+ if (pam_err != PAM_SUCCESS) {
+ log_it("CRON", getpid(), "error", "can't start PAM");
+ exit(ERROR_EXIT);
+ }
+
+ pam_err = pam_acct_mgmt(pamh, PAM_SILENT);
+ /* Expired password shouldn't prevent the job from running. */
+ if (pam_err != PAM_SUCCESS && pam_err != PAM_NEW_AUTHTOK_REQD) {
+ log_it(usernm, getpid(), "USER", "account unavailable");
+ exit(ERROR_EXIT);
+ }
+
+ pam_end(pamh, pam_err);
+ }
+#endif
+
+#ifdef USE_SIGCHLD
+ /* our parent is watching for our death by catching SIGCHLD. we
+ * do not care to watch for our children's deaths this way -- we
+ * use wait() explicitly. so we have to disable the signal (which
+ * was inherited from the parent).
+ */
+ (void) signal(SIGCHLD, SIG_DFL);
+#else
+ /* on system-V systems, we are ignoring SIGCLD. we have to stop
+ * ignoring it now or the wait() in cron_pclose() won't work.
+ * because of this, we have to wait() for our children here, as well.
+ */
+ (void) signal(SIGCLD, SIG_DFL);
+#endif /*BSD*/
+
+ /* create some pipes to talk to our future child
+ */
+ if (pipe(stdin_pipe) != 0 || pipe(stdout_pipe) != 0) {
+ log_it("CRON", getpid(), "error", "can't pipe");
+ exit(ERROR_EXIT);
+ }
+
+ /* since we are a forked process, we can diddle the command string
+ * we were passed -- nobody else is going to use it again, right?
+ *
+ * if a % is present in the command, previous characters are the
+ * command, and subsequent characters are the additional input to
+ * the command. Subsequent %'s will be transformed into newlines,
+ * but that happens later.
+ *
+ * If there are escaped %'s, remove the escape character.
+ */
+ /*local*/{
+ register int escaped = FALSE;
+ register int ch;
+ register char *p;
+
+ for (input_data = p = e->cmd; (ch = *input_data);
+ input_data++, p++) {
+ if (p != input_data)
+ *p = ch;
+ if (escaped) {
+ if (ch == '%' || ch == '\\')
+ *--p = ch;
+ escaped = FALSE;
+ continue;
+ }
+ if (ch == '\\') {
+ escaped = TRUE;
+ continue;
+ }
+ if (ch == '%') {
+ *input_data++ = '\0';
+ break;
+ }
+ }
+ *p = '\0';
+ }
+
+ /* fork again, this time so we can exec the user's command.
+ */
+ switch (vfork()) {
+ case -1:
+ log_it("CRON",getpid(),"error","can't vfork");
+ exit(ERROR_EXIT);
+ /*NOTREACHED*/
+ case 0:
+ Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n",
+ getpid()))
+
+ if (e->uid == ROOT_UID)
+ Jitter = RootJitter;
+ if (Jitter != 0) {
+ srandom(getpid());
+ sleep(random() % Jitter);
+ }
+
+ /* write a log message. we've waited this long to do it
+ * because it was not until now that we knew the PID that
+ * the actual user command shell was going to get and the
+ * PID is part of the log message.
+ */
+ /*local*/{
+ char *x = mkprints((u_char *)e->cmd, strlen(e->cmd));
+
+ log_it(usernm, getpid(), "CMD", x);
+ free(x);
+ }
+
+ /* that's the last thing we'll log. close the log files.
+ */
+#ifdef SYSLOG
+ closelog();
+#endif
+
+ /* get new pgrp, void tty, etc.
+ */
+ (void) setsid();
+
+ /* close the pipe ends that we won't use. this doesn't affect
+ * the parent, who has to read and write them; it keeps the
+ * kernel from recording us as a potential client TWICE --
+ * which would keep it from sending SIGPIPE in otherwise
+ * appropriate circumstances.
+ */
+ close(stdin_pipe[WRITE_PIPE]);
+ close(stdout_pipe[READ_PIPE]);
+
+ /* grandchild process. make std{in,out} be the ends of
+ * pipes opened by our daddy; make stderr go to stdout.
+ */
+ close(STDIN); dup2(stdin_pipe[READ_PIPE], STDIN);
+ close(STDOUT); dup2(stdout_pipe[WRITE_PIPE], STDOUT);
+ close(STDERR); dup2(STDOUT, STDERR);
+
+ /* close the pipes we just dup'ed. The resources will remain.
+ */
+ close(stdin_pipe[READ_PIPE]);
+ close(stdout_pipe[WRITE_PIPE]);
+
+ /* set our login universe. Do this in the grandchild
+ * so that the child can invoke /usr/lib/sendmail
+ * without surprises.
+ */
+ do_univ(u);
+
+# if defined(LOGIN_CAP)
+ /* Set user's entire context, but skip the environment
+ * as cron provides a separate interface for this
+ */
+ if ((pwd = getpwnam(usernm)) == NULL)
+ pwd = getpwuid(e->uid);
+ lc = NULL;
+ if (pwd != NULL) {
+ pwd->pw_gid = e->gid;
+ if (e->class != NULL)
+ lc = login_getclass(e->class);
+ }
+ if (pwd &&
+ setusercontext(lc, pwd, e->uid,
+ LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETENV)) == 0)
+ (void) endpwent();
+ else {
+ /* fall back to the old method */
+ (void) endpwent();
+# endif
+ /* set our directory, uid and gid. Set gid first,
+ * since once we set uid, we've lost root privileges.
+ */
+ if (setgid(e->gid) != 0) {
+ log_it(usernm, getpid(),
+ "error", "setgid failed");
+ exit(ERROR_EXIT);
+ }
+# if defined(BSD)
+ if (initgroups(usernm, e->gid) != 0) {
+ log_it(usernm, getpid(),
+ "error", "initgroups failed");
+ exit(ERROR_EXIT);
+ }
+# endif
+ if (setlogin(usernm) != 0) {
+ log_it(usernm, getpid(),
+ "error", "setlogin failed");
+ exit(ERROR_EXIT);
+ }
+ if (setuid(e->uid) != 0) {
+ log_it(usernm, getpid(),
+ "error", "setuid failed");
+ exit(ERROR_EXIT);
+ }
+ /* we aren't root after this..*/
+#if defined(LOGIN_CAP)
+ }
+ if (lc != NULL)
+ login_close(lc);
+#endif
+ chdir(env_get("HOME", e->envp));
+
+ /* exec the command.
+ */
+ {
+ char *shell = env_get("SHELL", e->envp);
+
+# if DEBUGGING
+ if (DebugFlags & DTEST) {
+ fprintf(stderr,
+ "debug DTEST is on, not exec'ing command.\n");
+ fprintf(stderr,
+ "\tcmd='%s' shell='%s'\n", e->cmd, shell);
+ _exit(OK_EXIT);
+ }
+# endif /*DEBUGGING*/
+ execle(shell, shell, "-c", e->cmd, (char *)0, e->envp);
+ warn("execl: couldn't exec `%s'", shell);
+ _exit(ERROR_EXIT);
+ }
+ break;
+ default:
+ /* parent process */
+ break;
+ }
+
+ children++;
+
+ /* middle process, child of original cron, parent of process running
+ * the user's command.
+ */
+
+ Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid()))
+
+ /* close the ends of the pipe that will only be referenced in the
+ * grandchild process...
+ */
+ close(stdin_pipe[READ_PIPE]);
+ close(stdout_pipe[WRITE_PIPE]);
+
+ /*
+ * write, to the pipe connected to child's stdin, any input specified
+ * after a % in the crontab entry. while we copy, convert any
+ * additional %'s to newlines. when done, if some characters were
+ * written and the last one wasn't a newline, write a newline.
+ *
+ * Note that if the input data won't fit into one pipe buffer (2K
+ * or 4K on most BSD systems), and the child doesn't read its stdin,
+ * we would block here. thus we must fork again.
+ */
+
+ if (*input_data && fork() == 0) {
+ register FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w");
+ register int need_newline = FALSE;
+ register int escaped = FALSE;
+ register int ch;
+
+ if (out == NULL) {
+ warn("fdopen failed in child2");
+ _exit(ERROR_EXIT);
+ }
+
+ Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid()))
+
+ /* close the pipe we don't use, since we inherited it and
+ * are part of its reference count now.
+ */
+ close(stdout_pipe[READ_PIPE]);
+
+ /* translation:
+ * \% -> %
+ * % -> \n
+ * \x -> \x for all x != %
+ */
+ while ((ch = *input_data++)) {
+ if (escaped) {
+ if (ch != '%')
+ putc('\\', out);
+ } else {
+ if (ch == '%')
+ ch = '\n';
+ }
+
+ if (!(escaped = (ch == '\\'))) {
+ putc(ch, out);
+ need_newline = (ch != '\n');
+ }
+ }
+ if (escaped)
+ putc('\\', out);
+ if (need_newline)
+ putc('\n', out);
+
+ /* close the pipe, causing an EOF condition. fclose causes
+ * stdin_pipe[WRITE_PIPE] to be closed, too.
+ */
+ fclose(out);
+
+ Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid()))
+ exit(0);
+ }
+
+ /* close the pipe to the grandkiddie's stdin, since its wicked uncle
+ * ernie back there has it open and will close it when he's done.
+ */
+ close(stdin_pipe[WRITE_PIPE]);
+
+ children++;
+
+ /*
+ * read output from the grandchild. it's stderr has been redirected to
+ * it's stdout, which has been redirected to our pipe. if there is any
+ * output, we'll be mailing it to the user whose crontab this is...
+ * when the grandchild exits, we'll get EOF.
+ */
+
+ Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid()))
+
+ /*local*/{
+ register FILE *in = fdopen(stdout_pipe[READ_PIPE], "r");
+ register int ch;
+
+ if (in == NULL) {
+ warn("fdopen failed in child");
+ _exit(ERROR_EXIT);
+ }
+
+ ch = getc(in);
+ if (ch != EOF) {
+ register FILE *mail;
+ register int bytes = 1;
+ int status = 0;
+
+ Debug(DPROC|DEXT,
+ ("[%d] got data (%x:%c) from grandchild\n",
+ getpid(), ch, ch))
+
+ /* get name of recipient. this is MAILTO if set to a
+ * valid local username; USER otherwise.
+ */
+ if (mailto == NULL) {
+ /* MAILTO not present, set to USER,
+ * unless globally overriden.
+ */
+ if (defmailto)
+ mailto = defmailto;
+ else
+ mailto = usernm;
+ }
+ if (mailto && *mailto == '\0')
+ mailto = NULL;
+
+ /* if we are supposed to be mailing, MAILTO will
+ * be non-NULL. only in this case should we set
+ * up the mail command and subjects and stuff...
+ */
+
+ if (mailto) {
+ register char **env;
+ auto char mailcmd[MAX_COMMAND];
+ auto char hostname[MAXHOSTNAMELEN];
+
+ if (gethostname(hostname, MAXHOSTNAMELEN) == -1)
+ hostname[0] = '\0';
+ hostname[sizeof(hostname) - 1] = '\0';
+ (void) snprintf(mailcmd, sizeof(mailcmd),
+ MAILARGS, MAILCMD);
+ if (!(mail = cron_popen(mailcmd, "w", e))) {
+ warn("%s", MAILCMD);
+ (void) _exit(ERROR_EXIT);
+ }
+ fprintf(mail, "From: Cron Daemon <%s@%s>\n",
+ usernm, hostname);
+ fprintf(mail, "To: %s\n", mailto);
+ fprintf(mail, "Subject: Cron <%s@%s> %s\n",
+ usernm, first_word(hostname, "."),
+ e->cmd);
+# if defined(MAIL_DATE)
+ fprintf(mail, "Date: %s\n",
+ arpadate(&TargetTime));
+# endif /* MAIL_DATE */
+ for (env = e->envp; *env; env++)
+ fprintf(mail, "X-Cron-Env: <%s>\n",
+ *env);
+ fprintf(mail, "\n");
+
+ /* this was the first char from the pipe
+ */
+ putc(ch, mail);
+ }
+
+ /* we have to read the input pipe no matter whether
+ * we mail or not, but obviously we only write to
+ * mail pipe if we ARE mailing.
+ */
+
+ while (EOF != (ch = getc(in))) {
+ bytes++;
+ if (mailto)
+ putc(ch, mail);
+ }
+
+ /* only close pipe if we opened it -- i.e., we're
+ * mailing...
+ */
+
+ if (mailto) {
+ Debug(DPROC, ("[%d] closing pipe to mail\n",
+ getpid()))
+ /* Note: the pclose will probably see
+ * the termination of the grandchild
+ * in addition to the mail process, since
+ * it (the grandchild) is likely to exit
+ * after closing its stdout.
+ */
+ status = cron_pclose(mail);
+ }
+
+ /* if there was output and we could not mail it,
+ * log the facts so the poor user can figure out
+ * what's going on.
+ */
+ if (mailto && status) {
+ char buf[MAX_TEMPSTR];
+
+ snprintf(buf, sizeof(buf),
+ "mailed %d byte%s of output but got status 0x%04x\n",
+ bytes, (bytes==1)?"":"s",
+ status);
+ log_it(usernm, getpid(), "MAIL", buf);
+ }
+
+ } /*if data from grandchild*/
+
+ Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
+
+ fclose(in); /* also closes stdout_pipe[READ_PIPE] */
+ }
+
+ /* wait for children to die.
+ */
+ for (; children > 0; children--)
+ {
+ WAIT_T waiter;
+ PID_T pid;
+
+ Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n",
+ getpid(), children))
+ pid = wait(&waiter);
+ if (pid < OK) {
+ Debug(DPROC, ("[%d] no more grandchildren--mail written?\n",
+ getpid()))
+ break;
+ }
+ Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x",
+ getpid(), pid, WEXITSTATUS(waiter)))
+ if (WIFSIGNALED(waiter) && WCOREDUMP(waiter))
+ Debug(DPROC, (", dumped core"))
+ Debug(DPROC, ("\n"))
+ }
+}
+
+
+static void
+do_univ(u)
+ user *u;
+{
+#if defined(sequent)
+/* Dynix (Sequent) hack to put the user associated with
+ * the passed user structure into the ATT universe if
+ * necessary. We have to dig the gecos info out of
+ * the user's password entry to see if the magic
+ * "universe(att)" string is present.
+ */
+
+ struct passwd *p;
+ char *s;
+ int i;
+
+ p = getpwuid(u->uid);
+ (void) endpwent();
+
+ if (p == NULL)
+ return;
+
+ s = p->pw_gecos;
+
+ for (i = 0; i < 4; i++)
+ {
+ if ((s = strchr(s, ',')) == NULL)
+ return;
+ s++;
+ }
+ if (strcmp(s, "universe(att)"))
+ return;
+
+ (void) universe(U_ATT);
+#endif
+}
diff --git a/usr.sbin/cron/cron/externs.h b/usr.sbin/cron/cron/externs.h
new file mode 100644
index 0000000..64bc8ac
--- /dev/null
+++ b/usr.sbin/cron/cron/externs.h
@@ -0,0 +1,147 @@
+/* $FreeBSD$ */
+
+/* Copyright 1993,1994 by Paul Vixie
+ * All rights reserved
+ *
+ * Distribute freely, except: don't remove my name from the source or
+ * documentation (don't take credit for my work), mark your changes (don't
+ * get me blamed for your possible bugs), don't alter or remove this
+ * notice. May be sold if buildable source is provided to buyer. No
+ * warrantee of any kind, express or implied, is included with this
+ * software; use at your own risk, responsibility for damages (if any) to
+ * anyone resulting from the use of this software rests entirely with the
+ * user.
+ *
+ * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+ * I'll try to keep a version up to date. I can be reached as follows:
+ * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+ */
+
+#if defined(POSIX) || defined(ATT)
+# include <stdlib.h>
+# include <unistd.h>
+# include <string.h>
+# include <dirent.h>
+# define DIR_T struct dirent
+# define WAIT_T int
+# define WAIT_IS_INT 1
+extern char *tzname[2];
+# define TZONE(tm) tzname[(tm).tm_isdst]
+#endif
+
+#if defined(UNIXPC)
+# undef WAIT_T
+# undef WAIT_IS_INT
+# define WAIT_T union wait
+#endif
+
+#if defined(POSIX)
+# define SIG_T sig_t
+# define TIME_T time_t
+# define PID_T pid_t
+#endif
+
+#if defined(ATT)
+# define SIG_T void
+# define TIME_T long
+# define PID_T int
+#endif
+
+#if !defined(POSIX) && !defined(ATT)
+/* classic BSD */
+extern time_t time();
+extern unsigned sleep();
+extern struct tm *localtime();
+extern struct passwd *getpwnam();
+extern int errno;
+extern void perror(), exit(), free();
+extern char *getenv(), *strcpy(), *strchr(), *strtok();
+extern void *malloc(), *realloc();
+# define SIG_T void
+# define TIME_T long
+# define PID_T int
+# define WAIT_T union wait
+# define DIR_T struct direct
+# include <sys/dir.h>
+# define TZONE(tm) (tm).tm_zone
+#endif
+
+/* getopt() isn't part of POSIX. some systems define it in <stdlib.h> anyway.
+ * of those that do, some complain that our definition is different and some
+ * do not. to add to the misery and confusion, some systems define getopt()
+ * in ways that we cannot predict or comprehend, yet do not define the adjunct
+ * external variables needed for the interface.
+ */
+#if (!defined(BSD) || (BSD < 198911)) && !defined(ATT) && !defined(UNICOS)
+int getopt(int, char * const *, const char *);
+#endif
+
+#if (!defined(BSD) || (BSD < 199103))
+extern char *optarg;
+extern int optind, opterr, optopt;
+#endif
+
+#if WAIT_IS_INT
+# ifndef WEXITSTATUS
+# define WEXITSTATUS(x) (((x) >> 8) & 0xff)
+# endif
+# ifndef WTERMSIG
+# define WTERMSIG(x) ((x) & 0x7f)
+# endif
+# ifndef WCOREDUMP
+# define WCOREDUMP(x) ((x) & 0x80)
+# endif
+#else /*WAIT_IS_INT*/
+# ifndef WEXITSTATUS
+# define WEXITSTATUS(x) ((x).w_retcode)
+# endif
+# ifndef WTERMSIG
+# define WTERMSIG(x) ((x).w_termsig)
+# endif
+# ifndef WCOREDUMP
+# define WCOREDUMP(x) ((x).w_coredump)
+# endif
+#endif /*WAIT_IS_INT*/
+
+#ifndef WIFSIGNALED
+#define WIFSIGNALED(x) (WTERMSIG(x) != 0)
+#endif
+#ifndef WIFEXITED
+#define WIFEXITED(x) (WTERMSIG(x) == 0)
+#endif
+
+#ifdef NEED_STRCASECMP
+extern int strcasecmp(char *, char *);
+#endif
+
+#ifdef NEED_STRDUP
+extern char *strdup(char *);
+#endif
+
+#ifdef NEED_STRERROR
+extern char *strerror(int);
+#endif
+
+#ifdef NEED_FLOCK
+extern int flock(int, int);
+# define LOCK_SH 1
+# define LOCK_EX 2
+# define LOCK_NB 4
+# define LOCK_UN 8
+#endif
+
+#ifdef NEED_SETSID
+extern int setsid(void);
+#endif
+
+#ifdef NEED_GETDTABLESIZE
+extern int getdtablesize(void);
+#endif
+
+#ifdef NEED_SETENV
+extern int setenv(char *, char *, int);
+#endif
+
+#ifdef NEED_VFORK
+extern PID_T vfork(void);
+#endif
diff --git a/usr.sbin/cron/cron/job.c b/usr.sbin/cron/cron/job.c
new file mode 100644
index 0000000..6d1b1af
--- /dev/null
+++ b/usr.sbin/cron/cron/job.c
@@ -0,0 +1,76 @@
+/* Copyright 1988,1990,1993,1994 by Paul Vixie
+ * All rights reserved
+ *
+ * Distribute freely, except: don't remove my name from the source or
+ * documentation (don't take credit for my work), mark your changes (don't
+ * get me blamed for your possible bugs), don't alter or remove this
+ * notice. May be sold if buildable source is provided to buyer. No
+ * warrantee of any kind, express or implied, is included with this
+ * software; use at your own risk, responsibility for damages (if any) to
+ * anyone resulting from the use of this software rests entirely with the
+ * user.
+ *
+ * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+ * I'll try to keep a version up to date. I can be reached as follows:
+ * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+ */
+
+#if !defined(lint) && !defined(LINT)
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif
+
+
+#include "cron.h"
+
+
+typedef struct _job {
+ struct _job *next;
+ entry *e;
+ user *u;
+} job;
+
+
+static job *jhead = NULL, *jtail = NULL;
+
+
+void
+job_add(e, u)
+ register entry *e;
+ register user *u;
+{
+ register job *j;
+
+ /* if already on queue, keep going */
+ for (j=jhead; j; j=j->next)
+ if (j->e == e && j->u == u) { return; }
+
+ /* build a job queue element */
+ if ((j = (job*)malloc(sizeof(job))) == NULL)
+ return;
+ j->next = (job*) NULL;
+ j->e = e;
+ j->u = u;
+
+ /* add it to the tail */
+ if (!jhead) { jhead=j; }
+ else { jtail->next=j; }
+ jtail = j;
+}
+
+
+int
+job_runqueue()
+{
+ register job *j, *jn;
+ register int run = 0;
+
+ for (j=jhead; j; j=jn) {
+ do_command(j->e, j->u);
+ jn = j->next;
+ free(j);
+ run++;
+ }
+ jhead = jtail = NULL;
+ return run;
+}
diff --git a/usr.sbin/cron/cron/pathnames.h b/usr.sbin/cron/cron/pathnames.h
new file mode 100644
index 0000000..ba91bfd
--- /dev/null
+++ b/usr.sbin/cron/cron/pathnames.h
@@ -0,0 +1,81 @@
+/* Copyright 1993,1994 by Paul Vixie
+ * All rights reserved
+ *
+ * Distribute freely, except: don't remove my name from the source or
+ * documentation (don't take credit for my work), mark your changes (don't
+ * get me blamed for your possible bugs), don't alter or remove this
+ * notice. May be sold if buildable source is provided to buyer. No
+ * warrantee of any kind, express or implied, is included with this
+ * software; use at your own risk, responsibility for damages (if any) to
+ * anyone resulting from the use of this software rests entirely with the
+ * user.
+ *
+ * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+ * I'll try to keep a version up to date. I can be reached as follows:
+ * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+ */
+
+/*
+ * $FreeBSD$
+ */
+
+#if (defined(BSD)) && (BSD >= 199103) || defined(__linux) || defined(AIX)
+# include <paths.h>
+#endif /*BSD*/
+
+#ifndef CRONDIR
+ /* CRONDIR is where crond(8) and crontab(1) both chdir
+ * to; SPOOL_DIR, ALLOW_FILE, DENY_FILE, and LOG_FILE
+ * are all relative to this directory.
+ */
+#define CRONDIR "/var/cron"
+#endif
+
+ /* SPOOLDIR is where the crontabs live.
+ * This directory will have its modtime updated
+ * whenever crontab(1) changes a crontab; this is
+ * the signal for crond(8) to look at each individual
+ * crontab file and reload those whose modtimes are
+ * newer than they were last time around (or which
+ * didn't exist last time around...)
+ */
+#define SPOOL_DIR "tabs"
+
+ /* undefining these turns off their features. note
+ * that ALLOW_FILE and DENY_FILE must both be defined
+ * in order to enable the allow/deny code. If neither
+ * LOG_FILE or SYSLOG is defined, we don't log. If
+ * both are defined, we log both ways.
+ */
+#define ALLOW_FILE "allow" /*-*/
+#define DENY_FILE "deny" /*-*/
+/*#define LOG_FILE "log"*/ /*-*/
+
+ /* where should the daemon stick its PID?
+ */
+#ifdef _PATH_VARRUN
+# define PIDDIR _PATH_VARRUN
+#else
+# define PIDDIR "/etc/"
+#endif
+#define PIDFILE "%scron.pid"
+
+ /* 4.3BSD-style crontab */
+#define SYSCRONTAB "/etc/crontab"
+
+ /* what editor to use if no EDITOR or VISUAL
+ * environment variable specified.
+ */
+#if defined(_PATH_VI)
+# define EDITOR _PATH_VI
+#else
+# define EDITOR "/usr/ucb/vi"
+#endif
+
+#ifndef _PATH_BSHELL
+# define _PATH_BSHELL "/bin/sh"
+#endif
+
+#ifndef _PATH_DEFPATH
+# define _PATH_DEFPATH "/usr/bin:/bin"
+#endif
diff --git a/usr.sbin/cron/cron/popen.c b/usr.sbin/cron/cron/popen.c
new file mode 100644
index 0000000..57b1f77
--- /dev/null
+++ b/usr.sbin/cron/cron/popen.c
@@ -0,0 +1,246 @@
+/*
+ * Copyright (c) 1988 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software written by Ken Arnold and
+ * published in UNIX Review, Vol. 6, No. 8.
+ *
+ * Redistribution and use in source and binary forms 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 University of California, Berkeley. The name of the
+ * University 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.
+ *
+ */
+
+/* this came out of the ftpd sources; it's been modified to avoid the
+ * globbing stuff since we don't need it. also execvp instead of execv.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)popen.c 5.7 (Berkeley) 2/14/89";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include "cron.h"
+#include <sys/signal.h>
+#include <fcntl.h>
+#include <paths.h>
+#if defined(SYSLOG)
+# include <syslog.h>
+#endif
+#if defined(LOGIN_CAP)
+# include <login_cap.h>
+#endif
+
+
+#define MAX_ARGS 100
+#define WANT_GLOBBING 0
+
+/*
+ * Special version of popen which avoids call to shell. This insures noone
+ * may create a pipe to a hidden program as a side effect of a list or dir
+ * command.
+ */
+static PID_T *pids;
+static int fds;
+
+FILE *
+cron_popen(program, type, e)
+ char *program, *type;
+ entry *e;
+{
+ register char *cp;
+ FILE *iop;
+ int argc, pdes[2];
+ PID_T pid;
+ char *usernm;
+ char *argv[MAX_ARGS + 1];
+# if defined(LOGIN_CAP)
+ struct passwd *pwd;
+ login_cap_t *lc;
+# endif
+#if WANT_GLOBBING
+ char **pop, *vv[2];
+ int gargc;
+ char *gargv[1000];
+ extern char **glob(), **copyblk();
+#endif
+
+ if ((*type != 'r' && *type != 'w') || type[1])
+ return(NULL);
+
+ if (!pids) {
+ if ((fds = getdtablesize()) <= 0)
+ return(NULL);
+ if (!(pids = calloc(fds, sizeof(PID_T))))
+ return(NULL);
+ }
+ if (pipe(pdes) < 0)
+ return(NULL);
+
+ /* break up string into pieces */
+ for (argc = 0, cp = program; argc < MAX_ARGS; cp = NULL)
+ if (!(argv[argc++] = strtok(cp, " \t\n")))
+ break;
+ argv[MAX_ARGS] = NULL;
+
+#if WANT_GLOBBING
+ /* glob each piece */
+ gargv[0] = argv[0];
+ for (gargc = argc = 1; argv[argc]; argc++) {
+ if (!(pop = glob(argv[argc]))) { /* globbing failed */
+ vv[0] = argv[argc];
+ vv[1] = NULL;
+ pop = copyblk(vv);
+ }
+ argv[argc] = (char *)pop; /* save to free later */
+ while (*pop && gargc < 1000)
+ gargv[gargc++] = *pop++;
+ }
+ gargv[gargc] = NULL;
+#endif
+
+ iop = NULL;
+ switch(pid = vfork()) {
+ case -1: /* error */
+ (void)close(pdes[0]);
+ (void)close(pdes[1]);
+ goto pfree;
+ /* NOTREACHED */
+ case 0: /* child */
+ if (e != NULL) {
+#ifdef SYSLOG
+ closelog();
+#endif
+
+ /* get new pgrp, void tty, etc.
+ */
+ (void) setsid();
+ }
+ if (*type == 'r') {
+ /* Do not share our parent's stdin */
+ (void)close(0);
+ (void)open(_PATH_DEVNULL, O_RDWR);
+ if (pdes[1] != 1) {
+ dup2(pdes[1], 1);
+ dup2(pdes[1], 2); /* stderr, too! */
+ (void)close(pdes[1]);
+ }
+ (void)close(pdes[0]);
+ } else {
+ if (pdes[0] != 0) {
+ dup2(pdes[0], 0);
+ (void)close(pdes[0]);
+ }
+ /* Hack: stdout gets revoked */
+ (void)close(1);
+ (void)open(_PATH_DEVNULL, O_RDWR);
+ (void)close(2);
+ (void)open(_PATH_DEVNULL, O_RDWR);
+ (void)close(pdes[1]);
+ }
+ if (e != NULL) {
+ /* Set user's entire context, but skip the environment
+ * as cron provides a separate interface for this
+ */
+ usernm = env_get("LOGNAME", e->envp);
+# if defined(LOGIN_CAP)
+ if ((pwd = getpwnam(usernm)) == NULL)
+ pwd = getpwuid(e->uid);
+ lc = NULL;
+ if (pwd != NULL) {
+ pwd->pw_gid = e->gid;
+ if (e->class != NULL)
+ lc = login_getclass(e->class);
+ }
+ if (pwd &&
+ setusercontext(lc, pwd, e->uid,
+ LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETENV)) == 0)
+ (void) endpwent();
+ else {
+ /* fall back to the old method */
+ (void) endpwent();
+# endif
+ /*
+ * Set our directory, uid and gid. Set gid
+ * first since once we set uid, we've lost
+ * root privileges.
+ */
+ if (setgid(e->gid) != 0)
+ _exit(ERROR_EXIT);
+# if defined(BSD)
+ if (initgroups(usernm, e->gid) != 0)
+ _exit(ERROR_EXIT);
+# endif
+ if (setlogin(usernm) != 0)
+ _exit(ERROR_EXIT);
+ if (setuid(e->uid) != 0)
+ _exit(ERROR_EXIT);
+ /* we aren't root after this..*/
+#if defined(LOGIN_CAP)
+ }
+ if (lc != NULL)
+ login_close(lc);
+#endif
+ chdir(env_get("HOME", e->envp));
+ }
+#if WANT_GLOBBING
+ execvp(gargv[0], gargv);
+#else
+ execvp(argv[0], argv);
+#endif
+ _exit(1);
+ }
+ /* parent; assume fdopen can't fail... */
+ if (*type == 'r') {
+ iop = fdopen(pdes[0], type);
+ (void)close(pdes[1]);
+ } else {
+ iop = fdopen(pdes[1], type);
+ (void)close(pdes[0]);
+ }
+ pids[fileno(iop)] = pid;
+
+pfree:
+#if WANT_GLOBBING
+ for (argc = 1; argv[argc] != NULL; argc++) {
+/* blkfree((char **)argv[argc]); */
+ free((char *)argv[argc]);
+ }
+#endif
+ return(iop);
+}
+
+int
+cron_pclose(iop)
+ FILE *iop;
+{
+ register int fdes;
+ int omask;
+ WAIT_T stat_loc;
+ PID_T pid;
+
+ /*
+ * pclose returns -1 if stream is not associated with a
+ * `popened' command, or, if already `pclosed'.
+ */
+ if (pids == 0 || pids[fdes = fileno(iop)] == 0)
+ return(-1);
+ (void)fclose(iop);
+ omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP));
+ while ((pid = wait(&stat_loc)) != pids[fdes] && pid != -1)
+ ;
+ (void)sigsetmask(omask);
+ pids[fdes] = 0;
+ return (pid == -1 ? -1 : WEXITSTATUS(stat_loc));
+}
diff --git a/usr.sbin/cron/cron/user.c b/usr.sbin/cron/cron/user.c
new file mode 100644
index 0000000..16fd617
--- /dev/null
+++ b/usr.sbin/cron/cron/user.c
@@ -0,0 +1,128 @@
+/* Copyright 1988,1990,1993,1994 by Paul Vixie
+ * All rights reserved
+ *
+ * Distribute freely, except: don't remove my name from the source or
+ * documentation (don't take credit for my work), mark your changes (don't
+ * get me blamed for your possible bugs), don't alter or remove this
+ * notice. May be sold if buildable source is provided to buyer. No
+ * warrantee of any kind, express or implied, is included with this
+ * software; use at your own risk, responsibility for damages (if any) to
+ * anyone resulting from the use of this software rests entirely with the
+ * user.
+ *
+ * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+ * I'll try to keep a version up to date. I can be reached as follows:
+ * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+ */
+
+#if !defined(lint) && !defined(LINT)
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif
+
+/* vix 26jan87 [log is in RCS file]
+ */
+
+
+#include "cron.h"
+
+static char *User_name;
+
+void
+free_user(u)
+ user *u;
+{
+ entry *e, *ne;
+
+ free(u->name);
+ for (e = u->crontab; e != NULL; e = ne) {
+ ne = e->next;
+ free_entry(e);
+ }
+ free(u);
+}
+
+static void
+log_error(msg)
+ char *msg;
+{
+ log_it(User_name, getpid(), "PARSE", msg);
+}
+
+user *
+load_user(crontab_fd, pw, name)
+ int crontab_fd;
+ struct passwd *pw; /* NULL implies syscrontab */
+ char *name;
+{
+ char envstr[MAX_ENVSTR];
+ FILE *file;
+ user *u;
+ entry *e;
+ int status;
+ char **envp, **tenvp;
+
+ if (!(file = fdopen(crontab_fd, "r"))) {
+ warn("fdopen on crontab_fd in load_user");
+ return NULL;
+ }
+
+ Debug(DPARS, ("load_user()\n"))
+
+ /* file is open. build user entry, then read the crontab file.
+ */
+ if ((u = (user *) malloc(sizeof(user))) == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ if ((u->name = strdup(name)) == NULL) {
+ free(u);
+ errno = ENOMEM;
+ return NULL;
+ }
+ u->crontab = NULL;
+
+ /*
+ * init environment. this will be copied/augmented for each entry.
+ */
+ if ((envp = env_init()) == NULL) {
+ free(u->name);
+ free(u);
+ return NULL;
+ }
+
+ /*
+ * load the crontab
+ */
+ while ((status = load_env(envstr, file)) >= OK) {
+ switch (status) {
+ case ERR:
+ free_user(u);
+ u = NULL;
+ goto done;
+ case FALSE:
+ User_name = u->name; /* for log_error */
+ e = load_entry(file, log_error, pw, envp);
+ if (e) {
+ e->next = u->crontab;
+ u->crontab = e;
+ }
+ break;
+ case TRUE:
+ if ((tenvp = env_set(envp, envstr))) {
+ envp = tenvp;
+ } else {
+ free_user(u);
+ u = NULL;
+ goto done;
+ }
+ break;
+ }
+ }
+
+ done:
+ env_free(envp);
+ fclose(file);
+ Debug(DPARS, ("...load_user() done\n"))
+ return u;
+}
diff --git a/usr.sbin/cron/crontab/Makefile b/usr.sbin/cron/crontab/Makefile
new file mode 100644
index 0000000..9f43112
--- /dev/null
+++ b/usr.sbin/cron/crontab/Makefile
@@ -0,0 +1,17 @@
+# $FreeBSD$
+
+BINDIR= /usr/bin
+
+PROG= crontab
+MAN= crontab.1 crontab.5
+BINOWN= root
+BINMODE=4555
+PRECIOUSPROG=
+
+WARNS?= 3
+
+CFLAGS+= -I${.CURDIR}/../cron
+
+LIBADD= cron md util
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/cron/crontab/Makefile.depend b/usr.sbin/cron/crontab/Makefile.depend
new file mode 100644
index 0000000..a1bc9b1
--- /dev/null
+++ b/usr.sbin/cron/crontab/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libmd \
+ lib/libutil \
+ usr.sbin/cron/lib \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/cron/crontab/crontab.1 b/usr.sbin/cron/crontab/crontab.1
new file mode 100644
index 0000000..0183c39
--- /dev/null
+++ b/usr.sbin/cron/crontab/crontab.1
@@ -0,0 +1,145 @@
+.\"/* Copyright 1988,1990,1993 by Paul Vixie
+.\" * All rights reserved
+.\" *
+.\" * Distribute freely, except: don't remove my name from the source or
+.\" * documentation (don't take credit for my work), mark your changes (don't
+.\" * get me blamed for your possible bugs), don't alter or remove this
+.\" * notice. May be sold if buildable source is provided to buyer. No
+.\" * warrantee of any kind, express or implied, is included with this
+.\" * software; use at your own risk, responsibility for damages (if any) to
+.\" * anyone resulting from the use of this software rests entirely with the
+.\" * user.
+.\" *
+.\" * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+.\" * I'll try to keep a version up to date. I can be reached as follows:
+.\" * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+.\" */
+.\"
+.\" $FreeBSD$
+.\"
+.Dd May 13, 2010
+.Dt CRONTAB 1
+.Os
+.Sh NAME
+.Nm crontab
+.Nd maintain crontab files for individual users (V3)
+.Sh SYNOPSIS
+.Nm
+.Op Fl u Ar user
+.Ar file
+.Nm
+.Op Fl u Ar user
+{
+.Fl l |
+.Fl r |
+.Fl e
+}
+.Sh DESCRIPTION
+The
+.Nm
+utility is the program used to install, deinstall or list the tables
+used to drive the
+.Xr cron 8
+daemon in Vixie Cron.
+Each user can have their own crontab, and though
+these are files in
+.Pa /var ,
+they are not intended to be edited directly.
+.Pp
+If the
+.Pa allow
+file exists, then you must be listed therein in order to be allowed to use
+this command.
+If the
+.Pa allow
+file does not exist but the
+.Pa deny
+file does exist, then you must
+.Em not
+be listed in the
+.Pa deny
+file in order to use this command.
+If neither of these files exists, then
+depending on site-dependent configuration parameters, only the super user
+will be allowed to use this command, or all users will be able to use this
+command.
+The format of these files is one username per line,
+with no leading or trailing whitespace.
+Lines of other formats will be ignored,
+and so can be used for comments.
+.Pp
+The first form of this command is used to install a new crontab from some
+named file or standard input if the pseudo-filename
+.Sq Fl
+is given.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl u
+Specify the name of the user whose crontab is to be
+tweaked.
+If this option is not given,
+.Nm
+examines
+.Dq your
+crontab, i.e., the crontab of the person executing the
+command.
+Note that
+.Xr su 1
+can confuse
+.Nm
+and that if you are running inside of
+.Xr su 1
+you should always use the
+.Fl u
+option for safety's sake.
+.It Fl l
+Display the current crontab on standard output.
+.It Fl r
+Remove the current crontab.
+.It Fl e
+Edit the current crontab using the editor specified by
+the
+.Ev VISUAL
+or
+.Ev EDITOR
+environment variables.
+The specified editor
+.Em must
+edit the file in place;
+any editor that unlinks the file and recreates it cannot be used.
+After you exit
+from the editor, the modified crontab will be installed automatically.
+.El
+.Sh FILES
+.Bl -tag -width /var/cron/allow -compact
+.It Pa /var/cron/allow
+List of users allowed to use crontab
+.It Pa /var/cron/deny
+List of users prohibited from using crontab
+.It Pa /var/cron/tabs
+Directory for personal crontab files
+.El
+.Sh DIAGNOSTICS
+A fairly informative usage message appears if you run it with a bad command
+line.
+.Sh SEE ALSO
+.Xr crontab 5 ,
+.Xr cron 8
+.Sh STANDARDS
+The
+.Nm
+command conforms to
+.St -p1003.2
+with the exception that the dangerous variant of calling
+.Nm
+without a file name in the first form of the command is not allowed by
+this implementation.
+The pseudo-filename
+.Sq Fl
+must be specified to read from standard input.
+The new command syntax
+differs from previous versions of Vixie Cron, as well as from the classic
+SVR3 syntax.
+.Sh AUTHORS
+.An Paul Vixie Aq Mt paul@vix.com
diff --git a/usr.sbin/cron/crontab/crontab.5 b/usr.sbin/cron/crontab/crontab.5
new file mode 100644
index 0000000..acd6cb5
--- /dev/null
+++ b/usr.sbin/cron/crontab/crontab.5
@@ -0,0 +1,323 @@
+.\"/* Copyright 1988,1990,1993,1994 by Paul Vixie
+.\" * All rights reserved
+.\" *
+.\" * Distribute freely, except: don't remove my name from the source or
+.\" * documentation (don't take credit for my work), mark your changes (don't
+.\" * get me blamed for your possible bugs), don't alter or remove this
+.\" * notice. May be sold if buildable source is provided to buyer. No
+.\" * warrantee of any kind, express or implied, is included with this
+.\" * software; use at your own risk, responsibility for damages (if any) to
+.\" * anyone resulting from the use of this software rests entirely with the
+.\" * user.
+.\" *
+.\" * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+.\" * I'll try to keep a version up to date. I can be reached as follows:
+.\" * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+.\" */
+.\"
+.\" $FreeBSD$
+.\"
+.Dd January 5, 2016
+.Dt CRONTAB 5
+.Os
+.Sh NAME
+.Nm crontab
+.Nd tables for driving cron
+.Sh DESCRIPTION
+A
+.Nm
+file contains instructions to the
+.Xr cron 8
+daemon of the general form: ``run this command at this time on this date''.
+Each user has their own crontab, and commands in any given crontab will be
+executed as the user who owns the crontab.
+Uucp and News will usually have
+their own crontabs, eliminating the need for explicitly running
+.Xr su 1
+as part of a cron command.
+.Pp
+Blank lines and leading spaces and tabs are ignored.
+Lines whose first
+non-space character is a pound-sign (#) are comments, and are ignored.
+Note that comments are not allowed on the same line as cron commands, since
+they will be taken to be part of the command.
+Similarly, comments are not
+allowed on the same line as environment variable settings.
+.Pp
+An active line in a crontab will be either an environment setting or a cron
+command.
+An environment setting is of the form,
+.Bd -literal
+ name = value
+.Ed
+.Pp
+where the spaces around the equal-sign (=) are optional, and any subsequent
+non-leading spaces in
+.Em value
+will be part of the value assigned to
+.Em name .
+The
+.Em value
+string may be placed in quotes (single or double, but matching) to preserve
+leading or trailing blanks.
+The
+.Em name
+string may also be placed in quote (single or double, but matching)
+to preserve leading, trailing or inner blanks.
+.Pp
+Several environment variables are set up
+automatically by the
+.Xr cron 8
+daemon.
+.Ev SHELL
+is set to
+.Pa /bin/sh ,
+.Ev PATH
+is set to
+.Pa /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin ,
+and
+.Ev LOGNAME
+and
+.Ev HOME
+are set from the
+.Pa /etc/passwd
+line of the crontab's owner.
+.Ev HOME ,
+.Ev PATH
+and
+.Ev SHELL
+may be overridden by settings in the crontab;
+.Ev LOGNAME
+may not.
+.Pp
+(Another note: the
+.Ev LOGNAME
+variable is sometimes called
+.Ev USER
+on
+.Bx
+systems...
+On these systems,
+.Ev USER
+will be set also).
+.Pp
+In addition to
+.Ev LOGNAME ,
+.Ev HOME ,
+.Ev PATH ,
+and
+.Ev SHELL ,
+.Xr cron 8
+will look at
+.Ev MAILTO
+if it has any reason to send mail as a result of running
+commands in ``this'' crontab.
+If
+.Ev MAILTO
+is defined (and non-empty), mail is
+sent to the user so named.
+.Ev MAILTO
+may also be used to direct mail to multiple recipients
+by separating recipient users with a comma.
+If
+.Ev MAILTO
+is defined but empty (MAILTO=""), no
+mail will be sent.
+Otherwise mail is sent to the owner of the crontab.
+This
+option is useful if you decide on
+.Pa /bin/mail
+instead of
+.Pa /usr/lib/sendmail
+as
+your mailer when you install cron --
+.Pa /bin/mail
+does not do aliasing, and UUCP
+usually does not read its mail.
+.Pp
+The format of a cron command is very much the V7 standard, with a number of
+upward-compatible extensions.
+Each line has five time and date fields,
+followed by a user name
+(with optional ``:<group>'' and ``/<login-class>'' suffixes)
+if this is the system crontab file,
+followed by a command.
+Commands are executed by
+.Xr cron 8
+when the minute, hour, and month of year fields match the current time,
+.Em and
+when at least one of the two day fields (day of month, or day of week)
+matches the current time (see ``Note'' below).
+.Xr cron 8
+examines cron entries once every minute.
+The time and date fields are:
+.Bd -literal -offset indent
+field allowed values
+----- --------------
+minute 0-59
+hour 0-23
+day of month 1-31
+month 1-12 (or names, see below)
+day of week 0-7 (0 or 7 is Sun, or use names)
+.Ed
+.Pp
+A field may be an asterisk (*), which always stands for ``first\-last''.
+.Pp
+Ranges of numbers are allowed.
+Ranges are two numbers separated
+with a hyphen.
+The specified range is inclusive.
+For example,
+8-11 for an ``hours'' entry specifies execution at hours 8, 9, 10
+and 11.
+.Pp
+Lists are allowed.
+A list is a set of numbers (or ranges)
+separated by commas.
+Examples: ``1,2,5,9'', ``0-4,8-12''.
+.Pp
+Step values can be used in conjunction with ranges.
+Following
+a range with ``/<number>'' specifies skips of the number's value
+through the range.
+For example, ``0-23/2'' can be used in the hours
+field to specify command execution every other hour (the alternative
+in the V7 standard is ``0,2,4,6,8,10,12,14,16,18,20,22'').
+Steps are
+also permitted after an asterisk, so if you want to say ``every two
+hours'', just use ``*/2''.
+.Pp
+Names can also be used for the ``month'' and ``day of week''
+fields.
+Use the first three letters of the particular
+day or month (case does not matter).
+Ranges or
+lists of names are not allowed.
+.Pp
+The ``sixth'' field (the rest of the line) specifies the command to be
+run.
+The entire command portion of the line, up to a newline or %
+character, will be executed by
+.Pa /bin/sh
+or by the shell
+specified in the
+.Ev SHELL
+variable of the cronfile.
+Percent-signs (%) in the command, unless escaped with backslash
+(\\), will be changed into newline characters, and all data
+after the first % will be sent to the command as standard
+input.
+.Pp
+Note: The day of a command's execution can be specified by two
+fields \(em day of month, and day of week.
+If both fields are
+restricted (ie, are not *), the command will be run when
+.Em either
+field matches the current time.
+For example,
+``30 4 1,15 * 5''
+would cause a command to be run at 4:30 am on the 1st and 15th of each
+month, plus every Friday.
+.Pp
+Instead of the first five fields,
+one of eight special strings may appear:
+.Bd -literal -offset indent
+string meaning
+------ -------
+@reboot Run once, at startup of cron.
+@yearly Run once a year, "0 0 1 1 *".
+@annually (same as @yearly)
+@monthly Run once a month, "0 0 1 * *".
+@weekly Run once a week, "0 0 * * 0".
+@daily Run once a day, "0 0 * * *".
+@midnight (same as @daily)
+@hourly Run once an hour, "0 * * * *".
+@every_minute Run once a minute, "*/1 * * * *".
+@every_second Run once a second.
+.Ed
+.Sh EXAMPLE CRON FILE
+.Bd -literal
+
+# use /bin/sh to run commands, overriding the default set by cron
+SHELL=/bin/sh
+# mail any output to `paul', no matter whose crontab this is
+MAILTO=paul
+#
+# run five minutes after midnight, every day
+5 0 * * * $HOME/bin/daily.job >> $HOME/tmp/out 2>&1
+# run at 2:15pm on the first of every month -- output mailed to paul
+15 14 1 * * $HOME/bin/monthly
+# run at 10 pm on weekdays, annoy Joe
+0 22 * * 1-5 mail -s "It's 10pm" joe%Joe,%%Where are your kids?%
+23 0-23/2 * * * echo "run 23 minutes after midn, 2am, 4am ..., everyday"
+5 4 * * sun echo "run at 5 after 4 every sunday"
+.Ed
+.Sh SEE ALSO
+.Xr crontab 1 ,
+.Xr cron 8
+.Sh EXTENSIONS
+When specifying day of week, both day 0 and day 7 will be considered Sunday.
+.Bx
+and
+.Tn ATT
+seem to disagree about this.
+.Pp
+Lists and ranges are allowed to co-exist in the same field.
+"1-3,7-9" would
+be rejected by
+.Tn ATT
+or
+.Bx
+cron -- they want to see "1-3" or "7,8,9" ONLY.
+.Pp
+Ranges can include "steps", so "1-9/2" is the same as "1,3,5,7,9".
+.Pp
+Names of months or days of the week can be specified by name.
+.Pp
+Environment variables can be set in the crontab.
+In
+.Bx
+or
+.Tn ATT ,
+the
+environment handed to child processes is basically the one from
+.Pa /etc/rc .
+.Pp
+Command output is mailed to the crontab owner
+.No ( Bx
+cannot do this), can be
+mailed to a person other than the crontab owner (SysV cannot do this), or the
+feature can be turned off and no mail will be sent at all (SysV cannot do this
+either).
+.Pp
+All of the
+.Sq @
+commands that can appear in place of the first five fields
+are extensions.
+.Sh AUTHORS
+.An Paul Vixie Aq Mt paul@vix.com
+.Sh BUGS
+If you are in one of the 70-odd countries that observe Daylight
+Savings Time, jobs scheduled during the rollback or advance may be
+affected if
+.Xr cron 8
+is not started with the
+.Fl s
+flag.
+In general, it is not a good idea to schedule jobs during
+this period if
+.Xr cron 8
+is not started with the
+.Fl s
+flag, which is enabled by default.
+See
+.Xr cron 8
+for more details.
+.Pp
+For US timezones (except parts of AZ and HI) the time shift occurs at
+2AM local time.
+For others, the output of the
+.Xr zdump 8
+program's verbose
+.Fl ( v )
+option can be used to determine the moment of time shift.
diff --git a/usr.sbin/cron/crontab/crontab.c b/usr.sbin/cron/crontab/crontab.c
new file mode 100644
index 0000000..f2e97a7
--- /dev/null
+++ b/usr.sbin/cron/crontab/crontab.c
@@ -0,0 +1,643 @@
+/* Copyright 1988,1990,1993,1994 by Paul Vixie
+ * All rights reserved
+ *
+ * Distribute freely, except: don't remove my name from the source or
+ * documentation (don't take credit for my work), mark your changes (don't
+ * get me blamed for your possible bugs), don't alter or remove this
+ * notice. May be sold if buildable source is provided to buyer. No
+ * warrantee of any kind, express or implied, is included with this
+ * software; use at your own risk, responsibility for damages (if any) to
+ * anyone resulting from the use of this software rests entirely with the
+ * user.
+ *
+ * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+ * I'll try to keep a version up to date. I can be reached as follows:
+ * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+ * From Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp
+ */
+
+#if !defined(lint) && !defined(LINT)
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif
+
+/* crontab - install and manage per-user crontab files
+ * vix 02may87 [RCS has the rest of the log]
+ * vix 26jan87 [original]
+ */
+
+#define MAIN_PROGRAM
+
+#include "cron.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <md5.h>
+#include <paths.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#ifdef USE_UTIMES
+# include <sys/time.h>
+#else
+# include <time.h>
+# include <utime.h>
+#endif
+#if defined(POSIX)
+# include <locale.h>
+#endif
+
+#define MD5_SIZE 33
+#define NHEADER_LINES 3
+
+
+enum opt_t { opt_unknown, opt_list, opt_delete, opt_edit, opt_replace };
+
+#if DEBUGGING
+static char *Options[] = { "???", "list", "delete", "edit", "replace" };
+#endif
+
+
+static PID_T Pid;
+static char User[MAX_UNAME], RealUser[MAX_UNAME];
+static char Filename[MAX_FNAME];
+static FILE *NewCrontab;
+static int CheckErrorCount;
+static enum opt_t Option;
+static struct passwd *pw;
+static void list_cmd(void),
+ delete_cmd(void),
+ edit_cmd(void),
+ poke_daemon(void),
+ check_error(char *),
+ parse_args(int c, char *v[]);
+static int replace_cmd(void);
+
+
+static void
+usage(char *msg)
+{
+ fprintf(stderr, "crontab: usage error: %s\n", msg);
+ fprintf(stderr, "%s\n%s\n",
+ "usage: crontab [-u user] file",
+ " crontab [-u user] { -e | -l | -r }");
+ exit(ERROR_EXIT);
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ int exitstatus;
+
+ Pid = getpid();
+ ProgramName = argv[0];
+
+#if defined(POSIX)
+ setlocale(LC_ALL, "");
+#endif
+
+#if defined(BSD)
+ setlinebuf(stderr);
+#endif
+ parse_args(argc, argv); /* sets many globals, opens a file */
+ set_cron_uid();
+ set_cron_cwd();
+ if (!allowed(User)) {
+ warnx("you (%s) are not allowed to use this program", User);
+ log_it(RealUser, Pid, "AUTH", "crontab command not allowed");
+ exit(ERROR_EXIT);
+ }
+ exitstatus = OK_EXIT;
+ switch (Option) {
+ case opt_list: list_cmd();
+ break;
+ case opt_delete: delete_cmd();
+ break;
+ case opt_edit: edit_cmd();
+ break;
+ case opt_replace: if (replace_cmd() < 0)
+ exitstatus = ERROR_EXIT;
+ break;
+ case opt_unknown:
+ break;
+ }
+ exit(exitstatus);
+ /*NOTREACHED*/
+}
+
+
+static void
+parse_args(argc, argv)
+ int argc;
+ char *argv[];
+{
+ int argch;
+ char resolved_path[PATH_MAX];
+
+ if (!(pw = getpwuid(getuid())))
+ errx(ERROR_EXIT, "your UID isn't in the passwd file, bailing out");
+ bzero(pw->pw_passwd, strlen(pw->pw_passwd));
+ (void) strncpy(User, pw->pw_name, (sizeof User)-1);
+ User[(sizeof User)-1] = '\0';
+ strcpy(RealUser, User);
+ Filename[0] = '\0';
+ Option = opt_unknown;
+ while ((argch = getopt(argc, argv, "u:lerx:")) != -1) {
+ switch (argch) {
+ case 'x':
+ if (!set_debug_flags(optarg))
+ usage("bad debug option");
+ break;
+ case 'u':
+ if (getuid() != ROOT_UID)
+ errx(ERROR_EXIT, "must be privileged to use -u");
+ if (!(pw = getpwnam(optarg)))
+ errx(ERROR_EXIT, "user `%s' unknown", optarg);
+ bzero(pw->pw_passwd, strlen(pw->pw_passwd));
+ (void) strncpy(User, pw->pw_name, (sizeof User)-1);
+ User[(sizeof User)-1] = '\0';
+ break;
+ case 'l':
+ if (Option != opt_unknown)
+ usage("only one operation permitted");
+ Option = opt_list;
+ break;
+ case 'r':
+ if (Option != opt_unknown)
+ usage("only one operation permitted");
+ Option = opt_delete;
+ break;
+ case 'e':
+ if (Option != opt_unknown)
+ usage("only one operation permitted");
+ Option = opt_edit;
+ break;
+ default:
+ usage("unrecognized option");
+ }
+ }
+
+ endpwent();
+
+ if (Option != opt_unknown) {
+ if (argv[optind] != NULL) {
+ usage("no arguments permitted after this option");
+ }
+ } else {
+ if (argv[optind] != NULL) {
+ Option = opt_replace;
+ (void) strncpy (Filename, argv[optind], (sizeof Filename)-1);
+ Filename[(sizeof Filename)-1] = '\0';
+
+ } else {
+ usage("file name must be specified for replace");
+ }
+ }
+
+ if (Option == opt_replace) {
+ /* relinquish the setuid status of the binary during
+ * the open, lest nonroot users read files they should
+ * not be able to read. we can't use access() here
+ * since there's a race condition. thanks go out to
+ * Arnt Gulbrandsen <agulbra@pvv.unit.no> for spotting
+ * the race.
+ */
+
+ if (swap_uids() < OK)
+ err(ERROR_EXIT, "swapping uids");
+
+ /* we have to open the file here because we're going to
+ * chdir(2) into /var/cron before we get around to
+ * reading the file.
+ */
+ if (!strcmp(Filename, "-")) {
+ NewCrontab = stdin;
+ } else if (realpath(Filename, resolved_path) != NULL &&
+ !strcmp(resolved_path, SYSCRONTAB)) {
+ err(ERROR_EXIT, SYSCRONTAB " must be edited manually");
+ } else {
+ if (!(NewCrontab = fopen(Filename, "r")))
+ err(ERROR_EXIT, "%s", Filename);
+ }
+ if (swap_uids_back() < OK)
+ err(ERROR_EXIT, "swapping uids back");
+ }
+
+ Debug(DMISC, ("user=%s, file=%s, option=%s\n",
+ User, Filename, Options[(int)Option]))
+}
+
+static void
+copy_file(FILE *in, FILE *out) {
+ int x, ch;
+
+ Set_LineNum(1)
+ /* ignore the top few comments since we probably put them there.
+ */
+ for (x = 0; x < NHEADER_LINES; x++) {
+ ch = get_char(in);
+ if (EOF == ch)
+ break;
+ if ('#' != ch) {
+ putc(ch, out);
+ break;
+ }
+ while (EOF != (ch = get_char(in)))
+ if (ch == '\n')
+ break;
+ if (EOF == ch)
+ break;
+ }
+
+ /* copy the rest of the crontab (if any) to the output file.
+ */
+ if (EOF != ch)
+ while (EOF != (ch = get_char(in)))
+ putc(ch, out);
+}
+
+static void
+list_cmd() {
+ char n[MAX_FNAME];
+ FILE *f;
+
+ log_it(RealUser, Pid, "LIST", User);
+ (void) snprintf(n, sizeof(n), CRON_TAB(User));
+ if (!(f = fopen(n, "r"))) {
+ if (errno == ENOENT)
+ errx(ERROR_EXIT, "no crontab for %s", User);
+ else
+ err(ERROR_EXIT, "%s", n);
+ }
+
+ /* file is open. copy to stdout, close.
+ */
+ copy_file(f, stdout);
+ fclose(f);
+}
+
+
+static void
+delete_cmd() {
+ char n[MAX_FNAME];
+ int ch, first;
+
+ if (isatty(STDIN_FILENO)) {
+ (void)fprintf(stderr, "remove crontab for %s? ", User);
+ first = ch = getchar();
+ while (ch != '\n' && ch != EOF)
+ ch = getchar();
+ if (first != 'y' && first != 'Y')
+ return;
+ }
+
+ log_it(RealUser, Pid, "DELETE", User);
+ (void) snprintf(n, sizeof(n), CRON_TAB(User));
+ if (unlink(n)) {
+ if (errno == ENOENT)
+ errx(ERROR_EXIT, "no crontab for %s", User);
+ else
+ err(ERROR_EXIT, "%s", n);
+ }
+ poke_daemon();
+}
+
+
+static void
+check_error(msg)
+ char *msg;
+{
+ CheckErrorCount++;
+ fprintf(stderr, "\"%s\":%d: %s\n", Filename, LineNumber-1, msg);
+}
+
+
+static void
+edit_cmd() {
+ char n[MAX_FNAME], q[MAX_TEMPSTR], *editor;
+ FILE *f;
+ int t;
+ struct stat statbuf, fsbuf;
+ WAIT_T waiter;
+ PID_T pid, xpid;
+ mode_t um;
+ int syntax_error = 0;
+ char orig_md5[MD5_SIZE];
+ char new_md5[MD5_SIZE];
+
+ log_it(RealUser, Pid, "BEGIN EDIT", User);
+ (void) snprintf(n, sizeof(n), CRON_TAB(User));
+ if (!(f = fopen(n, "r"))) {
+ if (errno != ENOENT)
+ err(ERROR_EXIT, "%s", n);
+ warnx("no crontab for %s - using an empty one", User);
+ if (!(f = fopen(_PATH_DEVNULL, "r")))
+ err(ERROR_EXIT, _PATH_DEVNULL);
+ }
+
+ um = umask(077);
+ (void) snprintf(Filename, sizeof(Filename), "/tmp/crontab.XXXXXXXXXX");
+ if ((t = mkstemp(Filename)) == -1) {
+ warn("%s", Filename);
+ (void) umask(um);
+ goto fatal;
+ }
+ (void) umask(um);
+#ifdef HAS_FCHOWN
+ if (fchown(t, getuid(), getgid()) < 0) {
+#else
+ if (chown(Filename, getuid(), getgid()) < 0) {
+#endif
+ warn("fchown");
+ goto fatal;
+ }
+ if (!(NewCrontab = fdopen(t, "r+"))) {
+ warn("fdopen");
+ goto fatal;
+ }
+
+ copy_file(f, NewCrontab);
+ fclose(f);
+ if (fflush(NewCrontab))
+ err(ERROR_EXIT, "%s", Filename);
+ if (fstat(t, &fsbuf) < 0) {
+ warn("unable to fstat temp file");
+ goto fatal;
+ }
+ again:
+ if (swap_uids() < OK)
+ err(ERROR_EXIT, "swapping uids");
+ if (stat(Filename, &statbuf) < 0) {
+ warn("stat");
+ fatal: unlink(Filename);
+ exit(ERROR_EXIT);
+ }
+ if (swap_uids_back() < OK)
+ err(ERROR_EXIT, "swapping uids back");
+ if (statbuf.st_dev != fsbuf.st_dev || statbuf.st_ino != fsbuf.st_ino)
+ errx(ERROR_EXIT, "temp file must be edited in place");
+ if (MD5File(Filename, orig_md5) == NULL) {
+ warn("MD5");
+ goto fatal;
+ }
+
+ if ((!(editor = getenv("VISUAL")))
+ && (!(editor = getenv("EDITOR")))
+ ) {
+ editor = EDITOR;
+ }
+
+ /* we still have the file open. editors will generally rewrite the
+ * original file rather than renaming/unlinking it and starting a
+ * new one; even backup files are supposed to be made by copying
+ * rather than by renaming. if some editor does not support this,
+ * then don't use it. the security problems are more severe if we
+ * close and reopen the file around the edit.
+ */
+
+ switch (pid = fork()) {
+ case -1:
+ warn("fork");
+ goto fatal;
+ case 0:
+ /* child */
+ if (setuid(getuid()) < 0)
+ err(ERROR_EXIT, "setuid(getuid())");
+ if (chdir("/tmp") < 0)
+ err(ERROR_EXIT, "chdir(/tmp)");
+ if (strlen(editor) + strlen(Filename) + 2 >= MAX_TEMPSTR)
+ errx(ERROR_EXIT, "editor or filename too long");
+ execlp(editor, editor, Filename, (char *)NULL);
+ err(ERROR_EXIT, "%s", editor);
+ /*NOTREACHED*/
+ default:
+ /* parent */
+ break;
+ }
+
+ /* parent */
+ {
+ void (*sig[3])(int signal);
+ sig[0] = signal(SIGHUP, SIG_IGN);
+ sig[1] = signal(SIGINT, SIG_IGN);
+ sig[2] = signal(SIGTERM, SIG_IGN);
+ xpid = wait(&waiter);
+ signal(SIGHUP, sig[0]);
+ signal(SIGINT, sig[1]);
+ signal(SIGTERM, sig[2]);
+ }
+ if (xpid != pid) {
+ warnx("wrong PID (%d != %d) from \"%s\"", xpid, pid, editor);
+ goto fatal;
+ }
+ if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) {
+ warnx("\"%s\" exited with status %d", editor, WEXITSTATUS(waiter));
+ goto fatal;
+ }
+ if (WIFSIGNALED(waiter)) {
+ warnx("\"%s\" killed; signal %d (%score dumped)",
+ editor, WTERMSIG(waiter), WCOREDUMP(waiter) ?"" :"no ");
+ goto fatal;
+ }
+ if (swap_uids() < OK)
+ err(ERROR_EXIT, "swapping uids");
+ if (stat(Filename, &statbuf) < 0) {
+ warn("stat");
+ goto fatal;
+ }
+ if (statbuf.st_dev != fsbuf.st_dev || statbuf.st_ino != fsbuf.st_ino)
+ errx(ERROR_EXIT, "temp file must be edited in place");
+ if (MD5File(Filename, new_md5) == NULL) {
+ warn("MD5");
+ goto fatal;
+ }
+ if (swap_uids_back() < OK)
+ err(ERROR_EXIT, "swapping uids back");
+ if (strcmp(orig_md5, new_md5) == 0 && !syntax_error) {
+ warnx("no changes made to crontab");
+ goto remove;
+ }
+ warnx("installing new crontab");
+ switch (replace_cmd()) {
+ case 0: /* Success */
+ break;
+ case -1: /* Syntax error */
+ for (;;) {
+ printf("Do you want to retry the same edit? ");
+ fflush(stdout);
+ q[0] = '\0';
+ (void) fgets(q, sizeof q, stdin);
+ switch (islower(q[0]) ? q[0] : tolower(q[0])) {
+ case 'y':
+ syntax_error = 1;
+ goto again;
+ case 'n':
+ goto abandon;
+ default:
+ fprintf(stderr, "Enter Y or N\n");
+ }
+ }
+ /*NOTREACHED*/
+ case -2: /* Install error */
+ abandon:
+ warnx("edits left in %s", Filename);
+ goto done;
+ default:
+ warnx("panic: bad switch() in replace_cmd()");
+ goto fatal;
+ }
+ remove:
+ unlink(Filename);
+ done:
+ log_it(RealUser, Pid, "END EDIT", User);
+}
+
+
+/* returns 0 on success
+ * -1 on syntax error
+ * -2 on install error
+ */
+static int
+replace_cmd() {
+ char n[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME];
+ FILE *tmp;
+ int ch, eof;
+ entry *e;
+ time_t now = time(NULL);
+ char **envp = env_init();
+
+ if (envp == NULL) {
+ warnx("cannot allocate memory");
+ return (-2);
+ }
+
+ (void) snprintf(n, sizeof(n), "tmp.%d", Pid);
+ (void) snprintf(tn, sizeof(tn), CRON_TAB(n));
+
+ if (!(tmp = fopen(tn, "w+"))) {
+ warn("%s", tn);
+ return (-2);
+ }
+
+ /* write a signature at the top of the file.
+ *
+ * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code.
+ */
+ fprintf(tmp, "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n");
+ fprintf(tmp, "# (%s installed on %-24.24s)\n", Filename, ctime(&now));
+ fprintf(tmp, "# (Cron version -- %s)\n", rcsid);
+
+ /* copy the crontab to the tmp
+ */
+ rewind(NewCrontab);
+ Set_LineNum(1)
+ while (EOF != (ch = get_char(NewCrontab)))
+ putc(ch, tmp);
+ ftruncate(fileno(tmp), ftell(tmp));
+ fflush(tmp); rewind(tmp);
+
+ if (ferror(tmp)) {
+ warnx("error while writing new crontab to %s", tn);
+ fclose(tmp); unlink(tn);
+ return (-2);
+ }
+
+ /* check the syntax of the file being installed.
+ */
+
+ /* BUG: was reporting errors after the EOF if there were any errors
+ * in the file proper -- kludged it by stopping after first error.
+ * vix 31mar87
+ */
+ Set_LineNum(1 - NHEADER_LINES)
+ CheckErrorCount = 0; eof = FALSE;
+ while (!CheckErrorCount && !eof) {
+ switch (load_env(envstr, tmp)) {
+ case ERR:
+ eof = TRUE;
+ break;
+ case FALSE:
+ e = load_entry(tmp, check_error, pw, envp);
+ if (e)
+ free_entry(e);
+ break;
+ case TRUE:
+ break;
+ }
+ }
+
+ if (CheckErrorCount != 0) {
+ warnx("errors in crontab file, can't install");
+ fclose(tmp); unlink(tn);
+ return (-1);
+ }
+
+#ifdef HAS_FCHOWN
+ if (fchown(fileno(tmp), ROOT_UID, -1) < OK)
+#else
+ if (chown(tn, ROOT_UID, -1) < OK)
+#endif
+ {
+ warn("chown");
+ fclose(tmp); unlink(tn);
+ return (-2);
+ }
+
+#ifdef HAS_FCHMOD
+ if (fchmod(fileno(tmp), 0600) < OK)
+#else
+ if (chmod(tn, 0600) < OK)
+#endif
+ {
+ warn("chown");
+ fclose(tmp); unlink(tn);
+ return (-2);
+ }
+
+ if (fclose(tmp) == EOF) {
+ warn("fclose");
+ unlink(tn);
+ return (-2);
+ }
+
+ (void) snprintf(n, sizeof(n), CRON_TAB(User));
+ if (rename(tn, n)) {
+ warn("error renaming %s to %s", tn, n);
+ unlink(tn);
+ return (-2);
+ }
+
+ log_it(RealUser, Pid, "REPLACE", User);
+
+ /*
+ * Creating the 'tn' temp file has already updated the
+ * modification time of the spool directory. Sleep for a
+ * second to ensure that poke_daemon() sets a later
+ * modification time. Otherwise, this can race with the cron
+ * daemon scanning for updated crontabs.
+ */
+ sleep(1);
+
+ poke_daemon();
+
+ return (0);
+}
+
+
+static void
+poke_daemon() {
+#ifdef USE_UTIMES
+ struct timeval tvs[2];
+
+ (void)gettimeofday(&tvs[0], NULL);
+ tvs[1] = tvs[0];
+ if (utimes(SPOOL_DIR, tvs) < OK) {
+ warn("can't update mtime on spooldir %s", SPOOL_DIR);
+ return;
+ }
+#else
+ if (utime(SPOOL_DIR, NULL) < OK) {
+ warn("can't update mtime on spooldir %s", SPOOL_DIR);
+ return;
+ }
+#endif /*USE_UTIMES*/
+}
diff --git a/usr.sbin/cron/doc/CHANGES b/usr.sbin/cron/doc/CHANGES
new file mode 100644
index 0000000..ed52a65
--- /dev/null
+++ b/usr.sbin/cron/doc/CHANGES
@@ -0,0 +1,158 @@
+$FreeBSD$
+--------
+
+Vixie Cron Changes from V2 to V3
+Paul Vixie
+29-Dec-1993
+
+The crontab command now conforms to POSIX 1003.2. This means that when you
+install it, if you have any "crontab" command lines floating around in shell
+scripts (such as /etc/rc or /etc/rc.local), you will need to change them.
+
+I have integrated several changes made by BSDi for their BSD/386 operating
+system; these were offerred to me before I started consulting for them, so
+it is safe to say that they were intended for publication. Most notably,
+the name of the cron daemon has changed from "crond" to "cron". This was
+done for compatibility with 4.3BSD. Another change made for the same reason
+is the ability to read in an /etc/crontab file which has an extra field in
+each entry, between the time fields and the command. This field is a user
+name, and it permits the /etc/crontab command to contain commands which are
+to be run by any user on the system. /etc/crontab is not "installed" via
+the crontab(1) command; it is automatically read at startup time and it will
+be reread whenever it changes.
+
+I also added a "-e" option to crontab(1). Nine people also sent me diffs
+to add this option, but I had already implemented it on my own. I actually
+released an interim version (V2.2, I think) for limited testing, and got a
+chance to fix a bad security bug in the "-e" option thanks to XXX.
+
+The daemon used to be extraordinarily sloppy in its use of file descriptors.
+A heck of a lot of them were left open in spawned jobs, which caused problems
+for the daemon and also caused problems with the spawned jobs if they were
+shell scripts since "sh" and "csh" have traditionally used hidden file
+descriptors to pass information to subshells, and cron was causing them to
+think they were subshells. If you had trouble with "sh" or "csh" scripts in
+V2, chances are good that V3 will fix your problems.
+
+About a dozen people have reminded me that I forgot to initialize
+"crontab_fd" in database.c. Keith Cantrell was the first, so he gets the
+point.
+
+Steve Simmons reminded me that once an account has been deleted from the
+system, "crontab -u USER -d" will not work. My solution is to suggest to
+all of you that before you delete a user's account, you first delete that
+user's crontab file if any. From cron's point of view, usernames can never
+be treated as arbitrary strings. Either they are valid user names, or they
+are not. I will not make an exception for the "-d" case, for security
+reasons that I consider reasonable. It is trivial for a root user to delete
+the entry by hand if necessary.
+
+Dan O'Neil reminded me that I forgot to reset "log_fd" in misc.c. A lot of
+others also reminded me of this, but Dan gets the point. I didn't fix it
+there, since the real bug was that it should have been open in the parent.
+
+Peter Kabal reminded me that I forgot to "#ifdef DEBUGGING" some code in
+misc.c. Hans Trompert actually told me first, but Peter sent the patch so
+he gets the point.
+
+Russell Nelson told me that I'd forgotten to "#include <syslog.h>" in misc.c,
+which explains why a lot of other people complained that it wasn't using
+syslog even when they configured it that way :-). Steve Simmons told me
+first, though, so he gets the point.
+
+An interim version of the daemon tried to "stat" every file before
+executing it; this turned out to be a horribly bad idea since finding the
+name of a file from a shell command is a hard job (that's why we have
+shells, right?) I removed this bogus code. Dave Burgess gets the point.
+
+Dennis R. Conley sent a suggestion for MMDF systems, which I've added to the
+comments in cron.h.
+
+Mike Heisler noted that I use comments in the CONVERSION file which are
+documented as illegal in the man pages. Thanks, Mike.
+
+Irving Wolfe sent me some very cheerful changes for a NeXT system, but I
+consider the system itself broken and I can't bring myself to #ifdef for
+something as screwed up as this system seems to be. However, various others
+did send me smaller patches which appear to have cause cron to build and run
+correctly on (the latest) NeXT machines, with or without the "-posix" CFLAG.
+Irving also asked for a per-job MAILTO, and this was finally added later when
+I integrated the BSD/386 changes contributed by BSDi, and generalized some of
+the parsing.
+
+Lots of folks complained that the autogenerated "Date:" header wasn't in
+ARPA format. I didn't understand this -- either folks will use Sendmail and
+not generate a Date: at all (since Sendmail will do it), or folks will use
+something other than Sendmail which won't care about Date: formats. But
+I've "fixed" it anyway...
+
+Several people suggested that "*" should be able to take a "/step". One person
+suggested that "N/step" ought to mean "N-last/step", but that's stretching things
+a bit far. "*/step" seems quite intuitive to me, so I've added it. Colin Plumb
+sent in the first and most polite request for this feature.
+
+As with every release of Cron, BIND, and seemingly everything else I do, one
+user stands out with the most critical but also the most useful analysis.
+Cron V3's high score belongs to Peter Holzer, who sent in the nicest looking
+patch for the "%" interpretation problem and also helped me understand a
+tricky bit of badness in the "log_fd" problem.
+
+agulbra@flode.nvg.unit.no wins the honors for being the first to point out the
+nasty security hole in "crontab -r". 'Nuff said.
+
+Several folks pointed out that log_it() needed to exist even if logging was
+disabled. Some day I will create a tool that will compile a subsystem with
+every possible combination and permutation of #ifdef options, but meanwhile
+thanks to everybody.
+
+job_runqueue() was using storage after freeing it, since Jordan told me back
+in 1983 that C let you do that, and I believed him in 1986 when I wrote all
+this junk. Linux was the first to die from this error, and the Linux people
+sent me the most amazing, um, collection of patches for this problem. Thanks
+for all the fish.
+
+Jeremy Bettis reminded me that popen() isn't safe. I grabbed Ken Arnold's
+version of popen/pclose from the ftpd and hacked it to taste. We're safe now,
+from this at least.
+
+Branko Lankester sent me a very timely and helpful fix for a looming security
+problem in my "crontab -e" implementation.
+
+--------
+
+Vixie Cron Changes from V1 to V2
+Paul Vixie
+8-Feb-1988
+
+Many changes were made in a rash of activity about six months ago, the exact
+list of which is no longer clear in my memory. I know that V1 used a file
+called POKECRON in /usr/spool/cron to tell it that it was time to re-read
+all the crontab files; V2 uses the modtime the crontab directory as a flag to
+check out the crontab files; those whose modtime has changed will be re-read,
+and the others left alone. Note that the crontab(1) command will do a utimes
+call to make sure the mtime of the dir changes, since the filename/inode will
+often remain the same after a replacement and the mtime wouldn't change in
+that case.
+
+8-Feb-88: made it possible to use much larger environment variable strings.
+ V1 allowed 100 characters; V2 allows 1000. This was needed for PATH
+ variables on some systems. Thanks to Toerless Eckert for this idea.
+ E-mail: UUCP: ...pyramid!fauern!faui10!eckert
+
+16-Feb-88: added allow/deny, moved /usr/spool/cron/crontabs to
+ /usr/lib/cron/tabs. allow and deny are /usr/lib/cron/{allow,deny},
+ since the sysv naming for this depends on 'at' using the same
+ dir, which would be stupid (hint: use /usr/{lib,spool}/at).
+
+22-Feb-88: made it read the spool directory for crontabs and look each one
+ up using getpwnam() rather than reading all passwds with getpwent()
+ and trying to open each crontab.
+
+9-Dec-88: made it sync to :00 after the minute, makes cron predictable.
+ added logging to /var/cron/log.
+
+14-Apr-90: (actually, changes since December 1989)
+ fixed a number of bugs reported from the net and from John Gilmore.
+ added syslog per Keith Bostic. security features including not
+ being willing to run a command owned or writable by other than
+ the owner of the crontab 9not working well yet)
diff --git a/usr.sbin/cron/doc/CONVERSION b/usr.sbin/cron/doc/CONVERSION
new file mode 100644
index 0000000..f30df7d
--- /dev/null
+++ b/usr.sbin/cron/doc/CONVERSION
@@ -0,0 +1,85 @@
+$FreeBSD$
+
+Conversion of BSD 4.[23] crontab files:
+
+Edit your current crontab (/usr/lib/crontab) into little pieces, with each
+users' commands in a different file. This is different on 4.2 and 4.3,
+but I'll get to that below. The biggest feature of this cron is that you
+can move 'news' and 'uucp' cron commands into files owned and maintainable
+by those two users. You also get to rip all the fancy 'su' footwork out
+of the cron commands. On 4.3, there's no need for the 'su' stuff since the
+user name appears on each command -- but I'd still rather have separate
+crontabs with separate environments and so on.
+
+Leave the original /usr/lib/crontab! This cron doesn't use it, so you may
+as well keep it around for a while in case something goes wakko with this
+fancy version.
+
+Most commands in most crontabs are run by root, have to run by root, and
+should continue to be run by root. They still have to be in their own file;
+I recommend /etc/crontab.src or /usr/adm/crontab.src.
+
+'uucp's commands need their own file; how about /usr/lib/uucp/crontab.src?
+'news' also, perhaps in /usr/lib/news/crontab.src...
+
+I say `how about' and `perhaps' because it really doesn't matter to anyone
+(except you) where you put the crontab source files. The `crontab' command
+COPIES them into a protected directory (CRONDIR/SPOOL_DIR in cron.h), named
+after the user whose crontab it is. If you want to examine, replace, or
+delete a crontab, the `crontab' command does all of those things. The
+various `crontab.src' (my suggested name for them) files are just source
+files---they have to be copied to SPOOLDIR using `crontab' before they'll be
+executed.
+
+On 4.2, your crontab might have a few lines like this:
+
+ 5 * * * * su uucp < /usr/lib/uucp/uudemon.hr
+ 10 4 * * * su uucp < /usr/lib/uucp/uudemon.day
+ 15 5 * * 0 su uucp < /usr/lib/uucp/uudemon.wk
+
+...or like this:
+
+ 5 * * * * echo /usr/lib/uucp/uudemon.hr | su uucp
+ 10 4 * * * echo /usr/lib/uucp/uudemon.day | su uucp
+ 15 5 * * 0 echo /usr/lib/uucp/uudemon.wk | su uucp
+
+On 4.3, they'd look a little bit better, but not much:
+
+ 5 * * * * uucp /usr/lib/uucp/uudemon.hr
+ 10 4 * * * uucp /usr/lib/uucp/uudemon.day
+ 15 5 * * 0 uucp /usr/lib/uucp/uudemon.wk
+
+For this cron, you'd create /usr/lib/uucp/crontab.src (or wherever you want
+to keep uucp's commands) which would look like this:
+
+ # /usr/lib/uucp/crontab.src - uucp's crontab
+ #
+ PATH=/usr/lib/uucp:/bin:/usr/bin
+ SHELL=/bin/sh
+ HOME=/usr/lib/uucp
+ #
+ 5 * * * * uudemon.hr
+ 10 4 * * * uudemon.day
+ 15 5 * * 0 uudemon.wk
+
+The application to the `news' cron commands (if any) is left for you to
+figure out. Likewise if there are any other cruddy-looking 'su' commands in
+your crontab commands, you don't need them anymore: just find a good place
+to put the `crontab.src' (or whatever you want to call it) file for that
+user, put the cron commands into it, and install it using the `crontab'
+command (probably with "-u USERNAME", but see the man page).
+
+If you run a 4.2-derived cron, you could of course just install your current
+crontab in toto as root's crontab. It would work exactly the way your
+current one does, barring the extra steps in installing or changing it.
+There would still be advantages to this cron, mostly that you get mail if
+there is any output from your cron commands.
+
+One note about getting mail from cron: you will probably find, after you
+install this version of cron, that your cron commands are generating a lot
+of irritating output. The work-around for this is to redirect all EXPECTED
+output to a per-execution log file, which you can examine if you want to
+see the output from the "last time" a command was executed; if you get any
+UNEXPECTED output, it will be mailed to you. This takes a while to get
+right, but it's amazingly convenient. Trust me.
+
diff --git a/usr.sbin/cron/doc/FEATURES b/usr.sbin/cron/doc/FEATURES
new file mode 100644
index 0000000..821d1f3
--- /dev/null
+++ b/usr.sbin/cron/doc/FEATURES
@@ -0,0 +1,84 @@
+$FreeBSD$
+
+Features of Vixie's cron relative to BSD 4.[23] and SysV crons:
+
+-- Environment variables can be set in each crontab. SHELL, USER,
+ LOGNAME, and HOME are set from the user's passwd entry; all except
+ USER can be changed in the crontab. PATH is especially useful to
+ set there. TZ can be set, but cron ignores it other than passing
+ it on through to the commands it runs. Format is
+
+ variable=value
+
+ Blanks surrounding the '=' will be eaten; other blanks in value are
+ okay. Leading or trailing blanks can be preserved by quoting, single
+ or double quotes are okay, just so they match.
+
+ PATH=.:/bin:/usr/bin
+ SHELL=/bin/sh
+ FOOBAR = this is a long blanky example
+
+ Above, FOOBAR would get "this is a long blanky example" as its value.
+
+ SHELL and HOME will be used when it's time to run a command; if
+ you don't set them, HOME defaults to your /etc/passwd entry
+ and SHELL defaults to /bin/sh.
+
+ MAILTO, if set to the login name of a user on your system, will be the
+ person that cron mails the output of commands in that crontab. This is
+ useful if you decide on BINMAIL when configuring cron.h, since binmail
+ doesn't know anything about aliasing.
+
+-- Weekdays can be specified by name. Case is not significant, but only
+ the first three letters should be specified.
+
+-- Months can likewise be specified by name. Three letters only.
+
+-- Ranges and lists can be mixed. Standard crons won't allow '1,3-5'.
+
+-- Ranges can specify 'step' values. '10-16/2' is like '10,12,14,16'.
+
+-- Sunday is both day 0 and day 7 -- apparently BSD and ATT disagree
+ about this.
+
+-- Each user gets their own crontab file. This is a win over BSD 4.2,
+ where only root has one, and over BSD 4.3, where they made the crontab
+ format incompatible and although the commands can be run by non-root
+ uid's, root is still the only one who can edit the crontab file. This
+ feature mimics the SysV cron.
+
+-- The 'crontab' command is loosely compatible with SysV, but has more
+ options which just generally make more sense. Running crontab with
+ no arguments will print a cute little summary of the command syntax.
+
+-- Comments and blank lines are allowed in the crontab file. Comments
+ must be on a line by themselves; leading whitespace is ignored, and
+ a '#' introduces the comment.
+
+-- (big win) If the `crontab' command changes anything in any crontab,
+ the 'cron' daemon will reload all the tables before running the
+ next iteration. In some crons, you have to kill and restart the
+ daemon whenever you change a crontab. In other crons, the crontab
+ file is reread and reparsed every minute even if it didn't change.
+
+-- In order to support the automatic reload, the crontab files are not
+ readable or writable except by 'crontab' or 'cron'. This is not a
+ problem, since 'crontab' will let you do pretty much whatever you
+ want to your own crontab, or if you are root, to anybody's crontab.
+
+-- If any output is generated by a command (on stdout OR stderr), it will
+ be mailed to the owner of the crontab that contained the command (or
+ MAILTO, see discussion of environment variables, above). The headers
+ of the mail message will include the command that was run, and a
+ complete list of the environment that was passed to it, which will
+ contain (at least) the USER (LOGNAME on SysV), HOME, and SHELL.
+
+-- the dom/dow situation is odd. '* * 1,15 * Sun' will run on the
+ first and fifteenth AND every Sunday; '* * * * Sun' will run *only*
+ on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this
+ is why we keep 'e->dow_star' and 'e->dom_star'. I didn't think up
+ this behaviour; it's how cron has always worked but the documentation
+ hasn't been very clear. I have been told that some AT&T crons do not
+ act this way and do the more reasonable thing, which is (IMHO) to "or"
+ the various field-matches together. In that sense this cron may not
+ be completely similar to some AT&T crons.
diff --git a/usr.sbin/cron/doc/INSTALL b/usr.sbin/cron/doc/INSTALL
new file mode 100644
index 0000000..7f4c997
--- /dev/null
+++ b/usr.sbin/cron/doc/INSTALL
@@ -0,0 +1,87 @@
+/* Copyright 1993,1994 by Paul Vixie
+ * All rights reserved
+ *
+ * Distribute freely, except: don't remove my name from the source or
+ * documentation (don't take credit for my work), mark your changes (don't
+ * get me blamed for your possible bugs), don't alter or remove this
+ * notice. May be sold if buildable source is provided to buyer. No
+ * warrantee of any kind, express or implied, is included with this
+ * software; use at your own risk, responsibility for damages (if any) to
+ * anyone resulting from the use of this software rests entirely with the
+ * user.
+ *
+ * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+ * I'll try to keep a version up to date. I can be reached as follows:
+ * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+ */
+
+$FreeBSD$
+
+Read the comments at the top of the Makefile, then edit the area marked
+'configurable stuff'.
+
+Edit config.h. The stuff I expect you to change is down a bit from the
+top of the file, but it's clearly marked. Also look at pathnames.h.
+
+You don't have to create the /var/cron or /var/cron/tabs directories, since
+both the daemon and the `crontab' program will do this the first time they
+run if they don't exist. You do need to have a /var, though -- just "mkdir
+/var" if you don't have one, or you can "mkdir /usr/var; ln -s /usr/var /var"
+if you expect your /var to have a lot of stuff in it.
+
+You will also need /usr/local/etc and /usr/local/bin directories unless you
+change the Makefile. These will have to be created by hand, but if you are
+a long-time Usenet user you probably have them already. /usr/local/man is
+where I keep my man pages, but I have the source for `man' and you probably
+do not. Therefore you may have to put the man pages into /usr/man/manl,
+which will be hard since there will be name collisions. (Note that the man
+command was originally written by Bill Joy before he left Berkeley, and it
+contains no AT&T code, so it is in UUNET's archive of freely-distributable
+BSD code.)
+
+LINUX note: /usr/include/paths.h on some linux systems shows _PATH_SENDMAIL
+ to be /usr/bin/sendmail even though sendmail is installed in /usr/lib.
+ you should check this out.
+
+say:
+ make all
+
+su and say:
+ make install
+
+Note that if I can get you to "su and say" something just by asking, you have
+a very serious security problem on your system and you should look into it.
+
+Edit your /usr/lib/crontab file into little pieces -- see the CONVERSION file
+for help on this.
+
+Use the `crontab' command to install all the little pieces you just created.
+Some examples (see below before trying any of these!)
+
+ crontab -u uucp -r /usr/lib/uucp/crontab.src
+ crontab -u news -r /usr/lib/news/crontab.src
+ crontab -u root -r /usr/adm/crontab.src
+
+Notes on above examples: (1) the .src files are copied at the time the
+command is issued; changing the source files later will have no effect until
+they are reinstalled with another `crontab -r' command. (2) The crontab
+command will affect the crontab of the person using the command unless `-u
+USER' is given; `-u' only works for root. When using most `su' commands
+under most BSD's, `crontab' will still think of you as yourself even though
+you may think of yourself as root -- so use `-u' liberally. (3) the `-r'
+option stands for `replace'; check the man page for crontab(1) for other
+possibilities.
+
+Kill your existing cron daemon -- do `ps aux' and look for /etc/cron.
+
+Edit your /etc/rc or /etc/rc.local, looking for the line that starts up
+/etc/cron. Comment it out and add a line to start the new cron daemon
+-- usually /usr/local/etc/cron, unless you changed it in the Makefile.
+
+Start up this cron daemon yourself as root. Just type /usr/local/etc/cron
+(or whatever); no '&' is needed since the daemon forks itself and the
+process you executed returns immediately.
+
+ATT notes: for those people unfortunate enough to be stuck on a AT&T UNIX,
+you will need the public-domain "libndir", found in the B News source and in
+any comp.sources.unix archive. You will also need to hack the code some.
diff --git a/usr.sbin/cron/doc/MAIL b/usr.sbin/cron/doc/MAIL
new file mode 100644
index 0000000..624f7c4
--- /dev/null
+++ b/usr.sbin/cron/doc/MAIL
@@ -0,0 +1,475 @@
+[ this is really old mail that came to me in response to my 1986 posting
+ to usenet asking for feature suggestions before releasing the first
+ version of cron. it is presented here for its entertainment value.
+ --vix ]
+
+$FreeBSD$
+
+From ptsfa!lll-crg!ames!acornrc!bob Wed Dec 31 10:07:08 1986
+Date: Wed, 31 Dec 86 08:59:31 pst
+From: lll-crg!ames!acornrc!bob (Bob Weissman)
+To: ptsfa!vixie!paul
+Status: RO
+
+Sure, here's a suggestion: I'd like to be able to run a program, say,
+every two hours. Current cron requires me to write
+0,2,4,6,8,10,12,14,16,18,20,22 in the hours field. How about a notation
+to handle this more elegantly?
+
+<< Okay, I've allowed 0-22/2 as a means of handling this.
+ The time specification for my cron is as follows:
+ specification = range {"," range}
+ range = (start "-" finish ["/" step]) | single-unit
+ This allows "1,3,5-7", which the current cron doesn't (it won't
+ do a range inside a list), and handles your specific need. >>
+
+From drw@mit-eddie Wed Dec 31 18:25:27 1986
+Date: Wed, 31 Dec 86 14:28:19 est
+From: drw@mit-eddie (Dale Worley)
+To: mit-eddie!vixie!paul
+Status: RO
+
+We have a lot of lines in our crontab of the form
+
+ 00 12 * * * su user < /usr/users/user/script.file
+
+This barfs (silently!) on our system (Dec Ultrix 1.2 == 4.2bsd) if
+user's shell is csh. This, I am told, is because csh requires that
+the environment be set up in certain ways, which cron doesn't do.
+(Actually, I believe, it is because /etc/rc, which runs cron, doesn't
+set up the environment enough for csh to run, and cron just inherits
+the situation.) Anyway, the point is that if you find out what csh
+really needs in its environment, you might want to set up cron to
+provide some reasonable defaults (if it isn't supplied by cron's
+parent). Also, could you tell me what csh needs, if you find out, so
+we can hack our /etc/rc?
+
+<< well, the environment IS a problem. processes that cron forks
+ will inherit the environment of the person who ran the cron
+ daemon... I plan to edit out such useless things as TERMCAP,
+ TERM, and the like; supply correct values for HOME, USER, CWD,
+ and whatever else comes to mind. I'll make sure csh works... >>
+From ptsfa!ames!seismo!dgis!generous Thu Jan 1 07:33:17 1987
+Date: Thu Jan 1 10:29:20 1987
+From: ames!seismo!dgis!generous (Curtis Generous)
+To: nike!ptsfa!vixie!paul
+Status: RO
+
+Paul:
+
+One of the limitations of the present versions of cron is the lack
+of the capability of specifying a way to execute a command every
+n units of time.
+
+Here is a good example:
+
+# Present method to start up uucico
+02,12,22,32,42,52 * * * * exec /usr/lib/uucp/uucico -r1
+
+# New method ?? (the ':' here is just one possibility for syntax)
+02:10 * * * * exec /usr/lib/uucp/uucico -r1
+
+This method would prove very helpful for those programs that get started
+every few minutes, making the entry long and not easily readable. The first
+number would specify the base time, and the second number the repetition
+interval.
+
+<< Good idea, but bob@acornrc beat you to it. I used '/' instead of
+ ':'. This is my personal preference, and seems intuitive when you
+ think of the divide operator in C... Does anyone have a preference? >>
+
+From ptsfa!lll-lcc!seismo!decuac!c3pe!c3engr!charles Thu Jan 1 17:04:24 1987
+From: lll-lcc!seismo!c3pe!c3engr!charles (Charles Green)
+To: c3pe!decuac!dolqci!vrdxhq!seismo!lll-lcc!ptsfa!vixie!paul
+Date: Thu Jan 1 19:22:47 1987
+Status: RO
+
+Well, this isn't a compatible extension, but I have in times past wondered
+about a facility to let you start a process at intervals of, say, 17 minutes,
+instead of particular minutes out of each hour.
+
+<< This was a popular request! >>
+
+From seismo!uwvax!astroatc!nicmad!norvax!mann Sun Jan 4 13:04:01 1987
+Date: Fri, 2 Jan 87 09:23:53 cst
+From: lll-lcc!seismo!uwvax!astroatc!nicmad!norvax!mann (Tom Mann)
+To: ptsfa!vixie!paul
+Status: RO
+
+I'm not sure if it is in cron (either SysV or BSD ... if it is, I haven't
+figured it out ) but a comment feature would SURE BE NICE!.
+There are times when I want to comment out an entry
+for a period of time; it might also make it a lot more legible.
+
+<< My cron allows blank lines and standard #-type comments. I know
+ that one BSD4.2 cron I've used had it. I don't know about SysV. >>
+
+From ptsfa!hoptoad!hugh Mon Jan 5 10:26:46 1987
+Date: Mon, 5 Jan 87 01:22:17 PST
+From: hoptoad!hugh (Hugh Daniel)
+To: ptsfa!vixie!paul
+Status: RO
+
+ Hi, I do have a BIG one that I would like. I want to log ALL output
+from command lines into a file for each line. Thus I might have a chance
+of finding out why my crontab entry did not work.
+ This would seem to work best if done by cron, as it is now I have a google
+of shell scripts laying about just to put the error output where I can see
+it.
+
+<< My cron (and the SysV cron) will send mail to the owner of the
+ particular crontab file if a command generates any output on stdout
+ or stderr. This can be irritating, but if you write a script such
+ that any output means a problem occurred, you can avoid most logfile
+ needs, and not generate mail except in unforeseen circumstances. >>
+
+From ptsfa!dual!ucbvax!ihnp4!anvil!es!Robert_Toxen Mon Jan 5 13:08:46 1987
+From: dual!ucbvax!ihnp4!anvil!es!Robert_Toxen
+Date: Fri, 2 Jan 87 14:25:29 EST
+To: anvil!ihnp4!ucbvax!dual!ptsfa!vixie!paul
+Status: RO
+
+Here are some suggestions:
+1. Run it through the C preprocessor via "/lib/<whatever>".
+
+<< hmmm. this seems of limited utility, and if you really wanted
+ to do it that way, you could do it yourself (since users can
+ write to their own crontab files). I'll add '-' (read stdin)
+ to the crontab installer program to facilitate this. >>
+
+2. Allow specifying every Nth day of week, i.e., every second Wednesday.
+ I did this to calendar by separating the day of week (Wed=4, which one
+ to start on and N with slashes). I took modulo the day of year as a
+ starting point so that someone with a desk calendar documenting such
+ things can easily determine the offset (second number). I did this
+ while at SGI; alas I don't have a copy of the code.
+
+<< I can see how this could be useful, but I'm not sure how I'd
+ implement it. Cron currently doesn't keep track of the last time
+ a given command was run; whether the current Wednesday is the first
+ or second since the command was last run would be pretty hard to
+ figure out. I'd have to keep a database of commands and their
+ execution around, and purge it when the crontab was overwritten.
+ This is too much work for me, but if someone adds it, let me know. >>
+
+From ptsfa!ames!seismo!cbmvax!devon!paul Tue Jan 6 05:50:17 1987
+From: ames!seismo!cbmvax!devon!paul
+To: cbmvax!seismo!nike!ptsfa!vixie!paul
+Date: Mon Jan 5 09:29:57 1987
+Status: RO
+
+One problem that has always plagued me with cron is the assumed ORing.
+I'd like to see some type of ANDing implemented. I guess I can best
+describe this by example. Say I have the following line in my crontab
+file:
+
+* * 4-31 * 1-6 /usr/bin/command
+
+What this does is run 'command' on the 4th thru 31st days of the
+month, AND on Monday thru Saturday; which probably means running it
+every day of the month (unless Sunday falls on days 1-3). This
+happens because cron runs the command if the day-of-month OR the
+day-of-week is true.
+
+What I'd like to happen with the above line is to run the command ONLY
+on Monday thru Saturday any time after the 3rd of the month, e.g. if
+the day-of-month AND the day-of-week are true.
+
+My proposal to you is to implement some special chars for the first
+five fields. Examples:
+
+* * !1-3 * 1-6 /usr/bin/command
+
+(run command Mon-Sat, but NOT [!] on the first 3 days of the month)
+
+* * &4-31 * &1-6 /usr/bin/command
+
+(run command if day-of-month AND day-of-week are true)
+
+Get the picture? This would be compatible with existing versions of
+cron (which wouldn't currently be using any special characters, so
+that old crontabs would be handled correctly).
+
+<< This message made me aware of the actual boolean expression involved
+ in a crontab entry. I'd assumed that it was
+ (minute && hour && DoM && month && DoW)
+ But it's really
+ (minute && hour && month && (DoM || DoW))
+
+ I can see some value in changing this, but with a fixed order of
+ fields, operators get to be kindof unary, which && and || really
+ aren't. If someone has an idea on a syntax that allows useful
+ variations to the standard (&& && && (||)) default, please suggest. >>
+
+From bobkat!pedz Tue Jan 6 20:02:10 1987
+From: pedz@bobkat.UUCP (Pedz Thing)
+Date: 2 Jan 87 17:34:44 GMT
+Status: RO
+
+Log files! It would be nice to be able to specify a log for cron
+itself and also a log for each program's stdout and stderr to go to.
+The latter can of course be done with > and 2> but it would be nice if
+there could be a single line with some sort of pattern like
+`> /usr/spool/log/%' and the command would be substituted for the %.
+Another thing which would be nice is to be able to specify which shell
+to call to give the command to.
+
+<< Log files are done with mail. The '%' idea could be useful if
+ a different character were used (% is special to cron, see man
+ page); a different directory would have to be chosen, since each
+ user has their own crontab file; and something intelligent would
+ have to be done in the file naming, since the first word of the
+ command might be ambiguous (with other commands). In short, it's
+ too much work. Sorry. >>
+
+From guy%gorodish@sun Tue Jan 6 20:03:13 1987
+From: guy%gorodish@sun (Guy Harris)
+Message-ID: <10944@sun.uucp>
+Date: 5 Jan 87 12:09:09 GMT
+References: <429@vixie.UUCP> <359@bobkat.UUCP>
+Sender: news@sun.uucp
+Status: RO
+
+> Another thing which would be nice is to be able to specify which shell
+> to call to give the command to.
+
+Well, the obvious choice would be the user's shell, but this wouldn't work
+for accounts like "uucico".
+
+<< I use the owning user's shell, and to handle "uucico" I check a
+ list of "acceptable shells" (currently compiled in, does anybody
+ mind?), substituting a default (compiled in) shell if the user's
+ shell isn't on the list.
+
+ BTW, "compiled in" means that it's in a .h file, easily changed
+ during installation, but requiring recompilation to modify. You
+ don't have to go digging through the code to find it... >>
+
+From qantel!hplabs!ucbvax!mwm@violet.berkeley.edu Tue Jan 6 21:24:48 1987
+To: hplabs!qantel!vixie!paul (Paul Vixie Esq)
+Date: 04 Jan 87 00:42:35 PST (Sun)
+From: Mike Meyer <mwm@violet.berkeley.edu>
+Status: RO
+
+<<[Discussion of RMS/FSF, and mwm's GNU Cron deleted]>>
+
+Oh, yeah - here are the extensions on my cron:
+
+1) Sunday is both day 0 and day 7, so it complies with both SysV and
+BSD cron.
+
+<< Good idea. I did it too, thanks for informing me. >>
+
+2) At is integrated into the cron. Instead of atrun to scan the
+/usr/spool/at directory, at files are put into the /usr/lib/cron
+directory along with users cron files, and cron fabricates a line from
+a crontab file to run them. This is considered a major win by all who
+use it.
+
+<< I don't use 'at', and my cron doesn't do anything with it. To run
+ 'at', I use 'atrun' the same way the current BSD cron does. My
+ crontab files are in /usr/spool/cron/crontabs, in the SysV
+ tradition -- not in /usr/lib/cron. This is a configuration
+ parameter, of course. >>
+
+There are two known restrictions:
+
+1) I don't support any of the SysV security hooks. I don't have a use
+for them, and RMS didn't like the idea at all :-).
+
+<< This means cron.allow and cron.deny. I plan to support them, as
+ they've been quite helpful at various HPUX sites I've administered. >>
+
+2) Cron expects to be able to create files with names longer than 14
+characters, which makes it hard to run on SysV. At least one person
+was working on a port, but I don't know how it's going. That might
+make for a good reason for releasing yours, right there.
+
+<< If someone has SysV (with the 14-character limit), they probably
+ won't want my cron, since it doesn't add much to the standard
+ version (which they may have support for). My cron is not currently
+ portable to non-BSD systems, since it relies on interval timers (I
+ needed to sleep for intervals more granular than seconds alone would
+ allow). The port would be trivial, and I will do it if a lot of
+ people ask for it... >>
+
+Oh, yeah - I'm going to see about getting this cron integrated into
+the next 4BSD release.
+
+<< How does one go about this? I have a few nifty gadgets I'd like
+ to contribute, this cron being one of them... >>
+
+<<[more FSF/GNU discussion deleted]>>
+
+From qantel!hplabs!ames!ut-sally!ut-ngp!melpad!bigtex!james Tue Jan 6 21:24:57 1987
+Posted-Date: Fri, 2 Jan 87 19:26:16 est
+Date: Fri, 2 Jan 87 19:26:16 est
+From: hplabs!ames!ut-sally!ut-ngp!bigtex!james
+To: vixie!paul
+Status: RO
+
+Yes!!! There are several critical failures in System V cron...
+
+1. Pass all variables in cron's environment into the environment of things
+ cron starts up, or at least into the crontab entries started up (at jobs
+ will inherit the environment of the user). If nothing else it is critically
+ important that the TZ variable be passed on. PATH should be passed on too.
+ Basically, passing environment values allows one to design a standard
+ environment with TZ and PATH and have that run by everything. If anyone
+ tells you this is no big deal, consider what happens when uucico is
+ started by cron in CA to make a long distance phone link... Unless the
+ administrator is really on his/her toes, calls scheduled at 5pm will really
+ go at two in the afternoon, needlessly incurring huge phone bills, all
+ because System V refuses to pass the TZ from its environment down. There
+ are work arounds, but only putting it in cron will really work. This is
+ not a security hole.
+
+<< delete TERM and TERMCAP; modify HOME, USER, and CWD; pass TZ and
+ PATH through undisturbed. any other requests out there?
+
+ BSD doesn't have this problem -- TZ is passed right on through if
+ you define it in the shell before starting my cron daemon. However,
+ the BSD I'm running this on doesn't need TZ to be defined anyway...
+ The default in the kernel has been just fine so far... But just the
+ same, if/when I port to SysV (I guess I really should), I'll make
+ sure this works right.
+
+ I guess I've been spoiled. HPUX is SysV-based, and I never had a
+ problem with cron and TZ when I used it. >>
+
+2. A way to avoid logging stuff in /usr/lib/cron/log. I have a cron entry
+ run uudemon.hr every 10 minutes. This is 144 times/day. Each run generates
+ three lines of text, for a total of 432 lines of text I don't want to see.
+ Obviously this should be optional, but it would be nice if there were a
+ way to flag an entry so that it wasn't logged at all unless there was an
+ error.
+
+<< I don't know nothin' 'bout no /usr/lib/cron/log. What is this file?
+ I don't see any reason to create log entries, given the mail-the-
+ output behaviour. Opinions, anyone? >>
+
+I will come up with other ideas no doubt, but I can always implement them
+myself.
+
+<< That's what I like about PD software. Please send me the diffs! >>
+
+The other problem you have is making sure you can run standard
+crontabs. I would suggest something like this: if the command part of the
+entry starts with an unescaped -, then flags and options follow immediately
+thereafter. As in:
+
+2,12,22,32,42,52 * * * * -q /usr/lib/uucp/uudemon.hr
+
+This could mean do not log the uudemon.hr run unless there is a problem of
+some kind. This is probably safe as not many filenames start with "-", and
+those that do are already a problem for people.
+
+<< Since I don't plan on supporting /usr/lib/cron/log in ANY form unless
+ many people request it, I won't be needing -q as you've defined it.
+ I could use something like this to avoid sending mail on errors, for
+ the occasional script that you don't want to bullet-proof.
+
+ The compatibility issue is CRITICAL. The 4.3BSD crontab format is
+ a crime against the whole philosophy of Unix(TM), in my opinion. >>
+
+One other minor thing to consider is the ulimit: can different users get
+different ulimits for their crontab entries?
+
+<< Boy I'm ignorant today. What's a ulimit, and what should I do with
+ it in a crontab? Suggestions, enlightenment, etc ?? >>
+
+From qantel!lll-crg!ames!uw-beaver!uw-nsr!john Tue Jan 6 23:32:44 1987
+Date: Thu, 1 Jan 87 10:53:05 pst
+From: lll-crg!ames!uw-beaver!uw-nsr!john (John Sambrook 5-7433)
+To: vixie!paul
+Status: RO
+
+How about not hardwiring the default environment that cron builds for its
+children in the cron program itself? Our cron does this and it's the pits
+because we are TZ=PST8PDT not TZ=EST5EDT !
+
+<< yeachk. I assure you, I will not hardwire the TZ! >>
+From ptsfa!well!dv Fri Jan 9 04:01:50 1987
+Date: Thu, 8 Jan 87 23:50:40 pst
+From: well!dv (David W. Vezie)
+To: ptsfa!vixie!paul
+Status: RO
+
+6, have a special notation called 'H' which would expand to weekends
+ and holidays (you'd have to keep a database somewhere of real
+ holidays), and also 'W' for workdays (neither weekend or holiday).
+
+<< Too much work. There should be a standard way to define and
+ detect holidays under Unix(TM); if there were, I'd use it. As
+ it is, I'll leave this for someone else to add.
+
+ I can see the usefulness; it just doesn't quite seem worth it. >>
+From qantel!gatech!akgua!blnt1!jat Wed Jan 14 20:00:40 1987
+Date: Tue, 13 Jan 87 16:39:38 EST
+From: gatech!akgua!blnt1!jat
+Status: RO
+
+1) Add some way to tell cron to reread the files, say kill -1 <pid>
+
+<< whenever the 'crontab' program is run and updates a crontab file,
+ a file /usr/spool/cron/POKECRON is created; next time the cron
+ daemon wakes up, it sees it, and re-reads the crontab files.
+
+ I thought of handling the signal; even implemented it. Then this
+ clever idea hit me and I ripped it all out and added a single
+ IF-statement to handle the POKECRON file. >>
+
+2) Have some kind of retry time so that if a command fails, cron will try to
+ execute it again after a certain period. This is useful if you have some
+ type of cleanup program that can run at the scheduled time for some reason
+ (such as locked device, unmounted filesystem, etc).
+
+<< Hmmm, sounds useful. I could do this by submitting an 'at' job...
+ I'll think about it. >>
+From ptsfa!dual!ucbvax!ihnp4!mtuxo!ender Sat Jan 3 16:54:00 1987
+From: dual!ucbvax!ihnp4!mtuxo!ender
+Date: Sat, 3 Jan 87 14:05:13 PST
+To: ucbvax!dual!ptsfa!vixie!paul
+Status: RO
+
+It would be nice if nonprivileged users can setup personal crontab files
+(~/.cronrc, say) and be able to run personal jobs at regular intervals.
+
+<< this is done, but in the SysV style: the 'crontab' program installs
+ a new crontab file for the executing user (can be overridden by root
+ for setup of uucp and news). the advantage of this is that (1) when
+ a crontab is changed, the daemon can be informed automatically; and
+ (2) the file can be syntax-checked before installation. >>
+From ptsfa!ames!seismo!ihnp4!lcc!richard Fri Jan 16 04:47:33 1987
+Date: Fri, 16 Jan 87 07:44:57 EST
+To: nike!ptsfa!vixie!paul
+Status: RO
+
+The System V cron is nice, but it has a few annoying features. One is that
+its mail files will say that the previous message is the output of "one of your
+cron commands." I wish it would say WHICH cron commmand.
+
+<< Done. Also which shell, which user (useful when the mail gets
+ forwarded), which home directory, and other useful crud. >>
+
+Another problem is with timezones. It is necessary to specify TZ=PST8PDT (or
+whatever) when you invoke cron (from inittab, or /etc/rc) and it is also
+necessary to add TZ=PST8PDT to each crontab line which might need it. Cron
+should automatically export its idea of the "TZ" to each invoked command, and
+it should be possible to put a line in the crontab file which overrides that
+for every command in the file (e.g., most users are on EST, so cron is run
+with TZ=EST5EDT; but one user is usually on PST and wants all of his cron
+commands to run with TZ=PST8PDT). This might be extended to allow any
+environment variable to be specified once for the whole crontab file (e.g.,
+PATH).
+
+<< Well, since I run the user's shell, you could put this into .cshrc.
+ generic environment-variable setting could be useful, though. Since
+ I have to modify the environment anyway, I'll consider this. >>
+
+A log file might be a nice idea, but the System V cron log is too verbose.
+I seem to remember that cron keeps it open, too; so you can't even have
+something go and periodically clean it out.
+
+<< I don't do /usr/lib/cron/log. I wasn't aware of this file until I
+ got all these suggestions. Do people want this file? Tell me! >>
diff --git a/usr.sbin/cron/doc/Makefile.vixie b/usr.sbin/cron/doc/Makefile.vixie
new file mode 100644
index 0000000..8df1301
--- /dev/null
+++ b/usr.sbin/cron/doc/Makefile.vixie
@@ -0,0 +1,128 @@
+#/* Copyright 1988,1990,1993,1994 by Paul Vixie
+# * All rights reserved
+# *
+# * Distribute freely, except: don't remove my name from the source or
+# * documentation (don't take credit for my work), mark your changes (don't
+# * get me blamed for your possible bugs), don't alter or remove this
+# * notice. May be sold if buildable source is provided to buyer. No
+# * warrantee of any kind, express or implied, is included with this
+# * software; use at your own risk, responsibility for damages (if any) to
+# * anyone resulting from the use of this software rests entirely with the
+# * user.
+# *
+# * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+# * I'll try to keep a version up to date. I can be reached as follows:
+# * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+# */
+
+# Makefile for vixie's cron
+#
+# $FreeBSD$
+#
+# vix 03mar88 [moved to RCS, rest of log is in there]
+# vix 30mar87 [goodbye, time.c; hello, getopt]
+# vix 12feb87 [cleanup for distribution]
+# vix 30dec86 [written]
+
+# NOTES:
+# 'make' can be done by anyone
+# 'make install' must be done by root
+#
+# this package needs getopt(3), bitstring(3), and BSD install(8).
+#
+# the configurable stuff in this makefile consists of compilation
+# options (use -O, cron runs forever) and destination directories.
+# SHELL is for the 'augumented make' systems where 'make' imports
+# SHELL from the environment and then uses it to run its commands.
+# if your environment SHELL variable is /bin/csh, make goes real
+# slow and sometimes does the wrong thing.
+#
+# this package needs the 'bitstring macros' library, which is
+# available from me or from the comp.sources.unix archive. if you
+# put 'bitstring.h' in a non-standard place (i.e., not intuited by
+# cc(1)), you will have to define INCLUDE to set the include
+# directory for cc. INCLUDE should be `-Isomethingorother'.
+#
+# there's more configuration info in config.h; edit that first!
+
+#################################### begin configurable stuff
+#<<DESTROOT is assumed to have ./etc, ./bin, and ./man subdirectories>>
+DESTROOT = $(DESTDIR)/usr
+DESTSBIN = $(DESTROOT)/sbin
+DESTBIN = $(DESTROOT)/bin
+DESTMAN = $(DESTROOT)/share/man
+#<<need bitstring.h>>
+INCLUDE = -I.
+#INCLUDE =
+#<<need getopt()>>
+LIBS =
+#<<optimize or debug?>>
+#OPTIM = -O
+OPTIM = -g
+#<<ATT or BSD or POSIX?>>
+# (ATT untested)
+#COMPAT = -DATT
+#(BSD is only needed if <sys/params.h> does not define it, as on ULTRIX)
+#COMPAT = -DBSD
+# (POSIX)
+#COMPAT = -DPOSIX
+#<<lint flags of choice?>>
+LINTFLAGS = -hbxa $(INCLUDE) $(COMPAT) $(DEBUGGING)
+#<<want to use a nonstandard CC?>>
+#CC = vcc
+#<<manifest defines>>
+DEFS =
+#(SGI IRIX systems need this)
+#DEFS = -D_BSD_SIGNALS -Dconst=
+#<<the name of the BSD-like install program>>
+#INSTALL = installbsd
+INSTALL = install
+#<<any special load flags>>
+LDFLAGS =
+#################################### end configurable stuff
+
+SHELL = /bin/sh
+CFLAGS = $(OPTIM) $(INCLUDE) $(COMPAT) $(DEFS)
+
+INFOS = README CHANGES FEATURES INSTALL CONVERSION THANKS MAIL
+MANPAGES = bitstring.3 crontab.5 crontab.1 cron.8 putman.sh
+HEADERS = bitstring.h cron.h config.h pathnames.h \
+ externs.h compat.h
+SOURCES = cron.c crontab.c database.c do_command.c entry.c \
+ env.c job.c user.c popen.c misc.c compat.c
+SHAR_SOURCE = $(INFOS) $(MANPAGES) Makefile $(HEADERS) $(SOURCES)
+LINT_CRON = cron.c database.c user.c entry.c compat.c \
+ misc.c job.c do_command.c env.c popen.c
+LINT_CRONTAB = crontab.c misc.c entry.c env.c compat.c
+CRON_OBJ = cron.o database.o user.o entry.o job.o do_command.o \
+ misc.o env.o popen.o compat.o
+CRONTAB_OBJ = crontab.o misc.o entry.o env.o compat.o
+
+all : cron crontab
+
+lint :
+ lint $(LINTFLAGS) $(LINT_CRON) $(LIBS) \
+ |grep -v "constant argument to NOT" 2>&1
+ lint $(LINTFLAGS) $(LINT_CRONTAB) $(LIBS) \
+ |grep -v "constant argument to NOT" 2>&1
+
+cron : $(CRON_OBJ)
+ $(CC) $(LDFLAGS) -o cron $(CRON_OBJ) $(LIBS)
+
+crontab : $(CRONTAB_OBJ)
+ $(CC) $(LDFLAGS) -o crontab $(CRONTAB_OBJ) $(LIBS)
+
+install : all
+ $(INSTALL) -c -m 111 -o root -s cron $(DESTSBIN)/
+ $(INSTALL) -c -m 4111 -o root -s crontab $(DESTBIN)/
+ sh putman.sh crontab.1 $(DESTMAN)
+ sh putman.sh cron.8 $(DESTMAN)
+ sh putman.sh crontab.5 $(DESTMAN)
+
+clean :; rm -f *.o cron crontab a.out core tags *~ #*
+
+kit : $(SHAR_SOURCE)
+ makekit -m -s99k $(SHAR_SOURCE)
+
+$(CRON_OBJ) : cron.h compat.h config.h externs.h pathnames.h Makefile
+$(CRONTAB_OBJ) : cron.h compat.h config.h externs.h pathnames.h Makefile
diff --git a/usr.sbin/cron/doc/README b/usr.sbin/cron/doc/README
new file mode 100644
index 0000000..eaced9e
--- /dev/null
+++ b/usr.sbin/cron/doc/README
@@ -0,0 +1,72 @@
+#/* Copyright 1988,1990,1993 by Paul Vixie
+# * All rights reserved
+# *
+# * Distribute freely, except: don't remove my name from the source or
+# * documentation (don't take credit for my work), mark your changes (don't
+# * get me blamed for your possible bugs), don't alter or remove this
+# * notice. May be sold if buildable source is provided to buyer. No
+# * warrantee of any kind, express or implied, is included with this
+# * software; use at your own risk, responsibility for damages (if any) to
+# * anyone resulting from the use of this software rests entirely with the
+# * user.
+# *
+# * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+# * I'll try to keep a version up to date. I can be reached as follows:
+# * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+# */
+
+Vixie Cron V3.0
+December 27, 1993
+[V2.2 was some time in 1992]
+[V2.1 was May 29, 1991]
+[V2.0 was July 5, 1990]
+[V2.0-beta was December 9, 1988]
+[V1.0 was May 6, 1987]
+Paul Vixie
+
+This is a version of 'cron' that is known to run on BSD 4.[23] systems. It
+is functionally based on the SysV cron, which means that each user can have
+their own crontab file (all crontab files are stored in a read-protected
+directory, usually /var/cron/tabs). No direct support is provided for
+'at'; you can continue to run 'atrun' from the crontab as you have been
+doing. If you don't have atrun (i.e., System V) you are in trouble.
+
+A messages is logged each time a command is executed; also, the files
+"allow" and "deny" in /var/cron can be used to control access to the
+"crontab" command (which installs crontabs). It hasn't been tested on
+SysV, although some effort has gone into making the port an easy one.
+
+This is more or less the copyright that USENET contributed software usually
+has. Since ATT couldn't use this version if they had to freely distribute
+source, and since I'd love to see them use it, I'll offer some rediculously
+low license fee just to have them take it. In the unlikely event that they
+do this, I will continue to support and distribute the pseudo-PD version, so
+please, don't flame me for wanting my work to see a wider distribution.
+
+To use this: Sorry, folks, there is no cutesy 'Configure' script. You'll
+have to go edit a couple of files... So, here's the checklist:
+
+ Read all the FEATURES, INSTALL, and CONVERSION files
+ Edit config.h
+ Edit Makefile
+ (both of these files have instructions inside; note that
+ some things in config.h are definable in Makefile and are
+ therefore surrounded by #ifndef...#endif)
+ 'make'
+ 'su' and 'make install'
+ (you may have to install the man pages by hand)
+ kill your existing cron process
+ (actually you can run your existing cron if you want, but why?)
+ build new crontabs using /usr/lib/{crontab,crontab.local}
+ (either put them all in "root"'s crontab, or divide it up
+ and rip out all the 'su' commands, collapse the lengthy
+ lists into ranges with steps -- basically, this step is
+ as much work as you want to make it)
+ start up the new cron
+ (must be done as root)
+ watch it. test it with 'crontab -r' and watch the daemon track your
+ changes.
+ if you like it, change your /etc/{rc,rc.local} to use it instead of
+ the old one.
+
+$FreeBSD$
diff --git a/usr.sbin/cron/doc/README.1ST b/usr.sbin/cron/doc/README.1ST
new file mode 100644
index 0000000..66cd0b2
--- /dev/null
+++ b/usr.sbin/cron/doc/README.1ST
@@ -0,0 +1,4 @@
+Sources substantially rearranged 26 Aug 94, Jordan Hubbard. Content
+remains faithful to the original 3.0 cron source from Paul Vixie, but
+any bugs introduced by this reorganization or the port to FreeBSD remain
+purely my own. - Jordan
diff --git a/usr.sbin/cron/doc/THANKS b/usr.sbin/cron/doc/THANKS
new file mode 100644
index 0000000..3787c29
--- /dev/null
+++ b/usr.sbin/cron/doc/THANKS
@@ -0,0 +1,29 @@
+15 January 1990
+Paul Vixie
+
+Many people have contributed to cron. Many more than I can remember, in fact.
+Rich Salz and Carl Gutekunst were each of enormous help to me in V1; Carl for
+helping me understand UNIX well enough to write it, and Rich for helping me
+get the features right.
+
+John Gilmore wrote me a wonderful review of V2, which took me a whole year to
+answer even though it made me clean up some really awful things in the code.
+(According to John the most awful things are still in here, of course.)
+
+Paul Close made a suggestion which led to /etc/crond.pid and the mutex locking
+on it. Kevin Braunsdorf of Purdue made a suggestion that led to @reboot and
+its brothers and sisters; he also sent some diffs that lead cron toward compil-
+ability with System V, though without at(1) capabilities, this cron isn't going
+to be that useful on System V. Bob Alverson fixed a silly bug in the line
+number counting. Brian Reid made suggestions which led to the run queue and
+the source-file labelling in installed crontabs.
+
+Scott Narveson ported V2 to a Sequent, and sent in the most useful single batch
+of diffs I got from anybody. Changes attributable to Scott are:
+ -> sendmail won't time out if the command is slow to generate output
+ -> day-of-week names aren't off by one anymore
+ -> crontab says the right thing if you do something you shouldn't do
+ -> crontab(5) man page is longer and more informative
+ -> misc changes related to the side effects of fclose()
+ -> Sequent "universe" support added (may also help on Pyramids)
+ -> null pw_shell is dealt with now; default is /bin/sh
diff --git a/usr.sbin/cron/lib/Makefile b/usr.sbin/cron/lib/Makefile
new file mode 100644
index 0000000..51316c1
--- /dev/null
+++ b/usr.sbin/cron/lib/Makefile
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+LIB= cron
+INTERNALLIB=
+SRCS= entry.c env.c misc.c
+
+WARNS?= 3
+
+CFLAGS+= -I${.CURDIR}/../cron
+CFLAGS+= -DLOGIN_CAP -DPAM
+
+.include <bsd.lib.mk>
diff --git a/usr.sbin/cron/lib/Makefile.depend b/usr.sbin/cron/lib/Makefile.depend
new file mode 100644
index 0000000..cf2961c
--- /dev/null
+++ b/usr.sbin/cron/lib/Makefile.depend
@@ -0,0 +1,14 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/cron/lib/compat.c b/usr.sbin/cron/lib/compat.c
new file mode 100644
index 0000000..9686012
--- /dev/null
+++ b/usr.sbin/cron/lib/compat.c
@@ -0,0 +1,237 @@
+/* Copyright 1988,1990,1993,1994 by Paul Vixie
+ * All rights reserved
+ *
+ * Distribute freely, except: don't remove my name from the source or
+ * documentation (don't take credit for my work), mark your changes (don't
+ * get me blamed for your possible bugs), don't alter or remove this
+ * notice. May be sold if buildable source is provided to buyer. No
+ * warrantee of any kind, express or implied, is included with this
+ * software; use at your own risk, responsibility for damages (if any) to
+ * anyone resulting from the use of this software rests entirely with the
+ * user.
+ *
+ * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+ * I'll try to keep a version up to date. I can be reached as follows:
+ * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+ */
+
+#if !defined(lint) && !defined(LINT)
+static char rcsid[] = "$FreeBSD$";
+#endif
+
+/* vix 30dec93 [broke this out of misc.c - see RCS log for history]
+ * vix 15jan87 [added TIOCNOTTY, thanks csg@pyramid]
+ */
+
+
+#include "cron.h"
+#ifdef NEED_GETDTABLESIZE
+# include <limits.h>
+#endif
+#if defined(NEED_SETSID) && defined(BSD)
+# include <sys/ioctl.h>
+#endif
+#include <errno.h>
+#include <paths.h>
+
+
+/* the code does not depend on any of vfork's
+ * side-effects; it just uses it as a quick
+ * fork-and-exec.
+ */
+#ifdef NEED_VFORK
+PID_T
+vfork() {
+ return (fork());
+}
+#endif
+
+
+#ifdef NEED_STRDUP
+char *
+strdup(str)
+ char *str;
+{
+ char *temp;
+
+ if ((temp = malloc(strlen(str) + 1)) == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ (void) strcpy(temp, str);
+ return temp;
+}
+#endif
+
+
+#ifdef NEED_STRERROR
+char *
+strerror(error)
+ int error;
+{
+ extern char *sys_errlist[];
+ extern int sys_nerr;
+ static char buf[32];
+
+ if ((error <= sys_nerr) && (error > 0)) {
+ return sys_errlist[error];
+ }
+
+ sprintf(buf, "Unknown error: %d", error);
+ return buf;
+}
+#endif
+
+
+#ifdef NEED_STRCASECMP
+int
+strcasecmp(left, right)
+ char *left;
+ char *right;
+{
+ while (*left && (MkLower(*left) == MkLower(*right))) {
+ left++;
+ right++;
+ }
+ return MkLower(*left) - MkLower(*right);
+}
+#endif
+
+
+#ifdef NEED_SETSID
+int
+setsid()
+{
+ int newpgrp;
+# if defined(BSD)
+ int fd;
+# if defined(POSIX)
+ newpgrp = setpgid((pid_t)0, getpid());
+# else
+ newpgrp = setpgrp(0, getpid());
+# endif
+ if ((fd = open(_PATH_TTY, 2)) >= 0)
+ {
+ (void) ioctl(fd, TIOCNOTTY, (char*)0);
+ (void) close(fd);
+ }
+# else /*BSD*/
+ newpgrp = setpgrp();
+
+ (void) close(STDIN); (void) open(_PATH_DEVNULL, 0);
+ (void) close(STDOUT); (void) open(_PATH_DEVNULL, 1);
+ (void) close(STDERR); (void) open(_PATH_DEVNULL, 2);
+# endif /*BSD*/
+ return newpgrp;
+}
+#endif /*NEED_SETSID*/
+
+
+#ifdef NEED_GETDTABLESIZE
+int
+getdtablesize() {
+#ifdef _SC_OPEN_MAX
+ return sysconf(_SC_OPEN_MAX);
+#else
+ return _POSIX_OPEN_MAX;
+#endif
+}
+#endif
+
+
+#ifdef NEED_FLOCK
+/* The following flock() emulation snarfed intact *) from the HP-UX
+ * "BSD to HP-UX porting tricks" maintained by
+ * system@alchemy.chem.utoronto.ca (System Admin (Mike Peterson))
+ * from the version "last updated: 11-Jan-1993"
+ * Snarfage done by Jarkko Hietaniemi <Jarkko.Hietaniemi@hut.fi>
+ * *) well, almost, had to K&R the function entry, HPUX "cc"
+ * does not grok ANSI function prototypes */
+
+/*
+ * flock (fd, operation)
+ *
+ * This routine performs some file locking like the BSD 'flock'
+ * on the object described by the int file descriptor 'fd',
+ * which must already be open.
+ *
+ * The operations that are available are:
+ *
+ * LOCK_SH - get a shared lock.
+ * LOCK_EX - get an exclusive lock.
+ * LOCK_NB - don't block (must be ORed with LOCK_SH or LOCK_EX).
+ * LOCK_UN - release a lock.
+ *
+ * Return value: 0 if lock successful, -1 if failed.
+ *
+ * Note that whether the locks are enforced or advisory is
+ * controlled by the presence or absence of the SETGID bit on
+ * the executable.
+ *
+ * Note that there is no difference between shared and exclusive
+ * locks, since the 'lockf' system call in SYSV doesn't make any
+ * distinction.
+ *
+ * The file "<sys/file.h>" should be modified to contain the definitions
+ * of the available operations, which must be added manually (see below
+ * for the values).
+ */
+
+/* this code has been reformatted by vixie */
+
+int
+flock(fd, operation)
+ int fd;
+ int operation;
+{
+ int i;
+
+ switch (operation) {
+ case LOCK_SH: /* get a shared lock */
+ case LOCK_EX: /* get an exclusive lock */
+ i = lockf (fd, F_LOCK, 0);
+ break;
+
+ case LOCK_SH|LOCK_NB: /* get a non-blocking shared lock */
+ case LOCK_EX|LOCK_NB: /* get a non-blocking exclusive lock */
+ i = lockf (fd, F_TLOCK, 0);
+ if (i == -1)
+ if ((errno == EAGAIN) || (errno == EACCES))
+ errno = EWOULDBLOCK;
+ break;
+
+ case LOCK_UN: /* unlock */
+ i = lockf (fd, F_ULOCK, 0);
+ break;
+
+ default: /* can't decipher operation */
+ i = -1;
+ errno = EINVAL;
+ break;
+ }
+
+ return (i);
+}
+#endif /*NEED_FLOCK*/
+
+
+#ifdef NEED_SETENV
+int
+setenv(name, value, overwrite)
+ char *name, *value;
+ int overwrite;
+{
+ char *tmp;
+
+ if (overwrite && getenv(name))
+ return -1;
+
+ if (!(tmp = malloc(strlen(name) + strlen(value) + 2))) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ sprintf(tmp, "%s=%s", name, value);
+ return putenv(tmp); /* intentionally orphan 'tmp' storage */
+}
+#endif
diff --git a/usr.sbin/cron/lib/entry.c b/usr.sbin/cron/lib/entry.c
new file mode 100644
index 0000000..57f7255
--- /dev/null
+++ b/usr.sbin/cron/lib/entry.c
@@ -0,0 +1,667 @@
+/* Copyright 1988,1990,1993,1994 by Paul Vixie
+ * All rights reserved
+ *
+ * Distribute freely, except: don't remove my name from the source or
+ * documentation (don't take credit for my work), mark your changes (don't
+ * get me blamed for your possible bugs), don't alter or remove this
+ * notice. May be sold if buildable source is provided to buyer. No
+ * warrantee of any kind, express or implied, is included with this
+ * software; use at your own risk, responsibility for damages (if any) to
+ * anyone resulting from the use of this software rests entirely with the
+ * user.
+ *
+ * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+ * I'll try to keep a version up to date. I can be reached as follows:
+ * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+ */
+
+#if !defined(lint) && !defined(LINT)
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif
+
+/* vix 26jan87 [RCS'd; rest of log is in RCS file]
+ * vix 01jan87 [added line-level error recovery]
+ * vix 31dec86 [added /step to the from-to range, per bob@acornrc]
+ * vix 30dec86 [written]
+ */
+
+
+#include "cron.h"
+#include <grp.h>
+#ifdef LOGIN_CAP
+#include <login_cap.h>
+#endif
+
+typedef enum ecode {
+ e_none, e_minute, e_hour, e_dom, e_month, e_dow,
+ e_cmd, e_timespec, e_username, e_group, e_mem
+#ifdef LOGIN_CAP
+ , e_class
+#endif
+} ecode_e;
+
+static char get_list(bitstr_t *, int, int, char *[], int, FILE *),
+ get_range(bitstr_t *, int, int, char *[], int, FILE *),
+ get_number(int *, int, char *[], int, FILE *);
+static int set_element(bitstr_t *, int, int, int);
+
+static char *ecodes[] =
+ {
+ "no error",
+ "bad minute",
+ "bad hour",
+ "bad day-of-month",
+ "bad month",
+ "bad day-of-week",
+ "bad command",
+ "bad time specifier",
+ "bad username",
+ "bad group name",
+ "out of memory",
+#ifdef LOGIN_CAP
+ "bad class name",
+#endif
+ };
+
+
+void
+free_entry(e)
+ entry *e;
+{
+#ifdef LOGIN_CAP
+ if (e->class != NULL)
+ free(e->class);
+#endif
+ if (e->cmd != NULL)
+ free(e->cmd);
+ if (e->envp != NULL)
+ env_free(e->envp);
+ free(e);
+}
+
+
+/* return NULL if eof or syntax error occurs;
+ * otherwise return a pointer to a new entry.
+ */
+entry *
+load_entry(file, error_func, pw, envp)
+ FILE *file;
+ void (*error_func)(char *);
+ struct passwd *pw;
+ char **envp;
+{
+ /* this function reads one crontab entry -- the next -- from a file.
+ * it skips any leading blank lines, ignores comments, and returns
+ * EOF if for any reason the entry can't be read and parsed.
+ *
+ * the entry is also parsed here.
+ *
+ * syntax:
+ * user crontab:
+ * minutes hours doms months dows cmd\n
+ * system crontab (/etc/crontab):
+ * minutes hours doms months dows USERNAME cmd\n
+ */
+
+ ecode_e ecode = e_none;
+ entry *e;
+ int ch;
+ char cmd[MAX_COMMAND];
+ char envstr[MAX_ENVSTR];
+ char **prev_env;
+
+ Debug(DPARS, ("load_entry()...about to eat comments\n"))
+
+ skip_comments(file);
+
+ ch = get_char(file);
+ if (ch == EOF)
+ return NULL;
+
+ /* ch is now the first useful character of a useful line.
+ * it may be an @special or it may be the first character
+ * of a list of minutes.
+ */
+
+ e = (entry *) calloc(sizeof(entry), sizeof(char));
+
+ if (e == NULL) {
+ warn("load_entry: calloc failed");
+ return NULL;
+ }
+
+ if (ch == '@') {
+ /* all of these should be flagged and load-limited; i.e.,
+ * instead of @hourly meaning "0 * * * *" it should mean
+ * "close to the front of every hour but not 'til the
+ * system load is low". Problems are: how do you know
+ * what "low" means? (save me from /etc/cron.conf!) and:
+ * how to guarantee low variance (how low is low?), which
+ * means how to we run roughly every hour -- seems like
+ * we need to keep a history or let the first hour set
+ * the schedule, which means we aren't load-limited
+ * anymore. too much for my overloaded brain. (vix, jan90)
+ * HINT
+ */
+ Debug(DPARS, ("load_entry()...about to test shortcuts\n"))
+ ch = get_string(cmd, MAX_COMMAND, file, " \t\n");
+ if (!strcmp("reboot", cmd)) {
+ Debug(DPARS, ("load_entry()...reboot shortcut\n"))
+ e->flags |= WHEN_REBOOT;
+ } else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){
+ Debug(DPARS, ("load_entry()...yearly shortcut\n"))
+ bit_set(e->second, 0);
+ bit_set(e->minute, 0);
+ bit_set(e->hour, 0);
+ bit_set(e->dom, 0);
+ bit_set(e->month, 0);
+ bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
+ e->flags |= DOW_STAR;
+ } else if (!strcmp("monthly", cmd)) {
+ Debug(DPARS, ("load_entry()...monthly shortcut\n"))
+ bit_set(e->second, 0);
+ bit_set(e->minute, 0);
+ bit_set(e->hour, 0);
+ bit_set(e->dom, 0);
+ bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
+ bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
+ e->flags |= DOW_STAR;
+ } else if (!strcmp("weekly", cmd)) {
+ Debug(DPARS, ("load_entry()...weekly shortcut\n"))
+ bit_set(e->second, 0);
+ bit_set(e->minute, 0);
+ bit_set(e->hour, 0);
+ bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
+ e->flags |= DOM_STAR;
+ bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
+ bit_set(e->dow, 0);
+ } else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) {
+ Debug(DPARS, ("load_entry()...daily shortcut\n"))
+ bit_set(e->second, 0);
+ bit_set(e->minute, 0);
+ bit_set(e->hour, 0);
+ bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
+ bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
+ bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
+ } else if (!strcmp("hourly", cmd)) {
+ Debug(DPARS, ("load_entry()...hourly shortcut\n"))
+ bit_set(e->second, 0);
+ bit_set(e->minute, 0);
+ bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1));
+ bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
+ bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
+ bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
+ } else if (!strcmp("every_minute", cmd)) {
+ Debug(DPARS, ("load_entry()...every_minute shortcut\n"))
+ bit_set(e->second, 0);
+ bit_nset(e->minute, 0, (LAST_MINUTE-FIRST_MINUTE+1));
+ bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1));
+ bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
+ bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
+ bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
+ } else if (!strcmp("every_second", cmd)) {
+ Debug(DPARS, ("load_entry()...every_second shortcut\n"))
+ e->flags |= SEC_RES;
+ bit_nset(e->second, 0, (LAST_SECOND-FIRST_SECOND+1));
+ bit_nset(e->minute, 0, (LAST_MINUTE-FIRST_MINUTE+1));
+ bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1));
+ bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
+ bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
+ bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
+ } else {
+ ecode = e_timespec;
+ goto eof;
+ }
+ /* Advance past whitespace between shortcut and
+ * username/command.
+ */
+ Skip_Blanks(ch, file);
+ if (ch == EOF) {
+ ecode = e_cmd;
+ goto eof;
+ }
+ } else {
+ Debug(DPARS, ("load_entry()...about to parse numerics\n"))
+ bit_set(e->second, 0);
+
+ ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE,
+ PPC_NULL, ch, file);
+ if (ch == EOF) {
+ ecode = e_minute;
+ goto eof;
+ }
+
+ /* hours
+ */
+
+ ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR,
+ PPC_NULL, ch, file);
+ if (ch == EOF) {
+ ecode = e_hour;
+ goto eof;
+ }
+
+ /* DOM (days of month)
+ */
+
+ if (ch == '*')
+ e->flags |= DOM_STAR;
+ ch = get_list(e->dom, FIRST_DOM, LAST_DOM,
+ PPC_NULL, ch, file);
+ if (ch == EOF) {
+ ecode = e_dom;
+ goto eof;
+ }
+
+ /* month
+ */
+
+ ch = get_list(e->month, FIRST_MONTH, LAST_MONTH,
+ MonthNames, ch, file);
+ if (ch == EOF) {
+ ecode = e_month;
+ goto eof;
+ }
+
+ /* DOW (days of week)
+ */
+
+ if (ch == '*')
+ e->flags |= DOW_STAR;
+ ch = get_list(e->dow, FIRST_DOW, LAST_DOW,
+ DowNames, ch, file);
+ if (ch == EOF) {
+ ecode = e_dow;
+ goto eof;
+ }
+ }
+
+ /* make sundays equivalent */
+ if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) {
+ bit_set(e->dow, 0);
+ bit_set(e->dow, 7);
+ }
+
+ /* ch is the first character of a command, or a username */
+ unget_char(ch, file);
+
+ if (!pw) {
+ char *username = cmd; /* temp buffer */
+ char *s;
+ struct group *grp;
+#ifdef LOGIN_CAP
+ login_cap_t *lc;
+#endif
+
+ Debug(DPARS, ("load_entry()...about to parse username\n"))
+ ch = get_string(username, MAX_COMMAND, file, " \t");
+
+ Debug(DPARS, ("load_entry()...got %s\n",username))
+ if (ch == EOF) {
+ ecode = e_cmd;
+ goto eof;
+ }
+
+#ifdef LOGIN_CAP
+ if ((s = strrchr(username, '/')) != NULL) {
+ *s = '\0';
+ e->class = strdup(s + 1);
+ if (e->class == NULL)
+ warn("strdup(\"%s\")", s + 1);
+ } else {
+ e->class = strdup(RESOURCE_RC);
+ if (e->class == NULL)
+ warn("strdup(\"%s\")", RESOURCE_RC);
+ }
+ if (e->class == NULL) {
+ ecode = e_mem;
+ goto eof;
+ }
+ if ((lc = login_getclass(e->class)) == NULL) {
+ ecode = e_class;
+ goto eof;
+ }
+ login_close(lc);
+#endif
+ grp = NULL;
+ if ((s = strrchr(username, ':')) != NULL) {
+ *s = '\0';
+ if ((grp = getgrnam(s + 1)) == NULL) {
+ ecode = e_group;
+ goto eof;
+ }
+ }
+
+ pw = getpwnam(username);
+ if (pw == NULL) {
+ ecode = e_username;
+ goto eof;
+ }
+ if (grp != NULL)
+ pw->pw_gid = grp->gr_gid;
+ Debug(DPARS, ("load_entry()...uid %d, gid %d\n",pw->pw_uid,pw->pw_gid))
+#ifdef LOGIN_CAP
+ Debug(DPARS, ("load_entry()...class %s\n",e->class))
+#endif
+ }
+
+#ifndef PAM /* PAM takes care of account expiration by itself */
+ if (pw->pw_expire && time(NULL) >= pw->pw_expire) {
+ ecode = e_username;
+ goto eof;
+ }
+#endif /* !PAM */
+
+ e->uid = pw->pw_uid;
+ e->gid = pw->pw_gid;
+
+ /* copy and fix up environment. some variables are just defaults and
+ * others are overrides.
+ */
+ e->envp = env_copy(envp);
+ if (e->envp == NULL) {
+ warn("env_copy");
+ ecode = e_mem;
+ goto eof;
+ }
+ if (!env_get("SHELL", e->envp)) {
+ prev_env = e->envp;
+ sprintf(envstr, "SHELL=%s", _PATH_BSHELL);
+ e->envp = env_set(e->envp, envstr);
+ if (e->envp == NULL) {
+ warn("env_set(%s)", envstr);
+ env_free(prev_env);
+ ecode = e_mem;
+ goto eof;
+ }
+ }
+ if (!env_get("HOME", e->envp)) {
+ prev_env = e->envp;
+ sprintf(envstr, "HOME=%s", pw->pw_dir);
+ e->envp = env_set(e->envp, envstr);
+ if (e->envp == NULL) {
+ warn("env_set(%s)", envstr);
+ env_free(prev_env);
+ ecode = e_mem;
+ goto eof;
+ }
+ }
+ if (!env_get("PATH", e->envp)) {
+ prev_env = e->envp;
+ sprintf(envstr, "PATH=%s", _PATH_DEFPATH);
+ e->envp = env_set(e->envp, envstr);
+ if (e->envp == NULL) {
+ warn("env_set(%s)", envstr);
+ env_free(prev_env);
+ ecode = e_mem;
+ goto eof;
+ }
+ }
+ prev_env = e->envp;
+ sprintf(envstr, "%s=%s", "LOGNAME", pw->pw_name);
+ e->envp = env_set(e->envp, envstr);
+ if (e->envp == NULL) {
+ warn("env_set(%s)", envstr);
+ env_free(prev_env);
+ ecode = e_mem;
+ goto eof;
+ }
+#if defined(BSD)
+ prev_env = e->envp;
+ sprintf(envstr, "%s=%s", "USER", pw->pw_name);
+ e->envp = env_set(e->envp, envstr);
+ if (e->envp == NULL) {
+ warn("env_set(%s)", envstr);
+ env_free(prev_env);
+ ecode = e_mem;
+ goto eof;
+ }
+#endif
+
+ Debug(DPARS, ("load_entry()...about to parse command\n"))
+
+ /* Everything up to the next \n or EOF is part of the command...
+ * too bad we don't know in advance how long it will be, since we
+ * need to malloc a string for it... so, we limit it to MAX_COMMAND.
+ * XXX - should use realloc().
+ */
+ ch = get_string(cmd, MAX_COMMAND, file, "\n");
+
+ /* a file without a \n before the EOF is rude, so we'll complain...
+ */
+ if (ch == EOF) {
+ ecode = e_cmd;
+ goto eof;
+ }
+
+ /* got the command in the 'cmd' string; save it in *e.
+ */
+ e->cmd = strdup(cmd);
+ if (e->cmd == NULL) {
+ warn("strdup(\"%s\")", cmd);
+ ecode = e_mem;
+ goto eof;
+ }
+ Debug(DPARS, ("load_entry()...returning successfully\n"))
+
+ /* success, fini, return pointer to the entry we just created...
+ */
+ return e;
+
+ eof:
+ free_entry(e);
+ if (ecode != e_none && error_func)
+ (*error_func)(ecodes[(int)ecode]);
+ while (ch != EOF && ch != '\n')
+ ch = get_char(file);
+ return NULL;
+}
+
+
+static char
+get_list(bits, low, high, names, ch, file)
+ bitstr_t *bits; /* one bit per flag, default=FALSE */
+ int low, high; /* bounds, impl. offset for bitstr */
+ char *names[]; /* NULL or *[] of names for these elements */
+ int ch; /* current character being processed */
+ FILE *file; /* file being read */
+{
+ register int done;
+
+ /* we know that we point to a non-blank character here;
+ * must do a Skip_Blanks before we exit, so that the
+ * next call (or the code that picks up the cmd) can
+ * assume the same thing.
+ */
+
+ Debug(DPARS|DEXT, ("get_list()...entered\n"))
+
+ /* list = range {"," range}
+ */
+
+ /* clear the bit string, since the default is 'off'.
+ */
+ bit_nclear(bits, 0, (high-low+1));
+
+ /* process all ranges
+ */
+ done = FALSE;
+ while (!done) {
+ ch = get_range(bits, low, high, names, ch, file);
+ if (ch == ',')
+ ch = get_char(file);
+ else
+ done = TRUE;
+ }
+
+ /* exiting. skip to some blanks, then skip over the blanks.
+ */
+ Skip_Nonblanks(ch, file)
+ Skip_Blanks(ch, file)
+
+ Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch))
+
+ return ch;
+}
+
+
+static char
+get_range(bits, low, high, names, ch, file)
+ bitstr_t *bits; /* one bit per flag, default=FALSE */
+ int low, high; /* bounds, impl. offset for bitstr */
+ char *names[]; /* NULL or names of elements */
+ int ch; /* current character being processed */
+ FILE *file; /* file being read */
+{
+ /* range = number | number "-" number [ "/" number ]
+ */
+
+ register int i;
+ auto int num1, num2, num3;
+
+ Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n"))
+
+ if (ch == '*') {
+ /* '*' means "first-last" but can still be modified by /step
+ */
+ num1 = low;
+ num2 = high;
+ ch = get_char(file);
+ if (ch == EOF)
+ return EOF;
+ } else {
+ if (EOF == (ch = get_number(&num1, low, names, ch, file)))
+ return EOF;
+
+ if (ch == '/')
+ num2 = high;
+ else if (ch != '-') {
+ /* not a range, it's a single number.
+ */
+ if (EOF == set_element(bits, low, high, num1))
+ return EOF;
+ return ch;
+ } else {
+ /* eat the dash
+ */
+ ch = get_char(file);
+ if (ch == EOF)
+ return EOF;
+
+ /* get the number following the dash
+ */
+ ch = get_number(&num2, low, names, ch, file);
+ if (ch == EOF)
+ return EOF;
+ }
+ }
+
+ /* check for step size
+ */
+ if (ch == '/') {
+ /* eat the slash
+ */
+ ch = get_char(file);
+ if (ch == EOF)
+ return EOF;
+
+ /* get the step size -- note: we don't pass the
+ * names here, because the number is not an
+ * element id, it's a step size. 'low' is
+ * sent as a 0 since there is no offset either.
+ */
+ ch = get_number(&num3, 0, PPC_NULL, ch, file);
+ if (ch == EOF || num3 == 0)
+ return EOF;
+ } else {
+ /* no step. default==1.
+ */
+ num3 = 1;
+ }
+
+ /* range. set all elements from num1 to num2, stepping
+ * by num3. (the step is a downward-compatible extension
+ * proposed conceptually by bob@acornrc, syntactically
+ * designed then implmented by paul vixie).
+ */
+ for (i = num1; i <= num2; i += num3)
+ if (EOF == set_element(bits, low, high, i))
+ return EOF;
+
+ return ch;
+}
+
+
+static char
+get_number(numptr, low, names, ch, file)
+ int *numptr; /* where does the result go? */
+ int low; /* offset applied to result if symbolic enum used */
+ char *names[]; /* symbolic names, if any, for enums */
+ int ch; /* current character */
+ FILE *file; /* source */
+{
+ char temp[MAX_TEMPSTR], *pc;
+ int len, i, all_digits;
+
+ /* collect alphanumerics into our fixed-size temp array
+ */
+ pc = temp;
+ len = 0;
+ all_digits = TRUE;
+ while (isalnum(ch)) {
+ if (++len >= MAX_TEMPSTR)
+ return EOF;
+
+ *pc++ = ch;
+
+ if (!isdigit(ch))
+ all_digits = FALSE;
+
+ ch = get_char(file);
+ }
+ *pc = '\0';
+ if (len == 0)
+ return (EOF);
+
+ /* try to find the name in the name list
+ */
+ if (names) {
+ for (i = 0; names[i] != NULL; i++) {
+ Debug(DPARS|DEXT,
+ ("get_num, compare(%s,%s)\n", names[i], temp))
+ if (!strcasecmp(names[i], temp)) {
+ *numptr = i+low;
+ return ch;
+ }
+ }
+ }
+
+ /* no name list specified, or there is one and our string isn't
+ * in it. either way: if it's all digits, use its magnitude.
+ * otherwise, it's an error.
+ */
+ if (all_digits) {
+ *numptr = atoi(temp);
+ return ch;
+ }
+
+ return EOF;
+}
+
+
+static int
+set_element(bits, low, high, number)
+ bitstr_t *bits; /* one bit per flag, default=FALSE */
+ int low;
+ int high;
+ int number;
+{
+ Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number))
+
+ if (number < low || number > high)
+ return EOF;
+
+ bit_set(bits, (number-low));
+ return OK;
+}
diff --git a/usr.sbin/cron/lib/env.c b/usr.sbin/cron/lib/env.c
new file mode 100644
index 0000000..fd35817
--- /dev/null
+++ b/usr.sbin/cron/lib/env.c
@@ -0,0 +1,269 @@
+/* Copyright 1988,1990,1993,1994 by Paul Vixie
+ * All rights reserved
+ *
+ * Distribute freely, except: don't remove my name from the source or
+ * documentation (don't take credit for my work), mark your changes (don't
+ * get me blamed for your possible bugs), don't alter or remove this
+ * notice. May be sold if buildable source is provided to buyer. No
+ * warrantee of any kind, express or implied, is included with this
+ * software; use at your own risk, responsibility for damages (if any) to
+ * anyone resulting from the use of this software rests entirely with the
+ * user.
+ *
+ * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+ * I'll try to keep a version up to date. I can be reached as follows:
+ * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+ */
+
+#if !defined(lint) && !defined(LINT)
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif
+
+
+#include "cron.h"
+
+
+char **
+env_init()
+{
+ register char **p = (char **) malloc(sizeof(char *));
+
+ if (p)
+ p[0] = NULL;
+ return (p);
+}
+
+
+void
+env_free(envp)
+ char **envp;
+{
+ char **p;
+
+ if ((p = envp))
+ for (; *p; p++)
+ free(*p);
+ free(envp);
+}
+
+
+char **
+env_copy(envp)
+ register char **envp;
+{
+ register int count, i;
+ register char **p;
+
+ for (count = 0; envp[count] != NULL; count++)
+ ;
+ p = (char **) malloc((count+1) * sizeof(char *)); /* 1 for the NULL */
+ if (p == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ for (i = 0; i < count; i++)
+ if ((p[i] = strdup(envp[i])) == NULL) {
+ while (--i >= 0)
+ (void) free(p[i]);
+ free(p);
+ errno = ENOMEM;
+ return NULL;
+ }
+ p[count] = NULL;
+ return (p);
+}
+
+
+char **
+env_set(envp, envstr)
+ char **envp;
+ char *envstr;
+{
+ register int count, found;
+ register char **p;
+ char *q;
+
+ /*
+ * count the number of elements, including the null pointer;
+ * also set 'found' to -1 or index of entry if already in here.
+ */
+ found = -1;
+ for (count = 0; envp[count] != NULL; count++) {
+ if (!strcmp_until(envp[count], envstr, '='))
+ found = count;
+ }
+ count++; /* for the NULL */
+
+ if (found != -1) {
+ /*
+ * it exists already, so just free the existing setting,
+ * save our new one there, and return the existing array.
+ */
+ q = envp[found];
+ if ((envp[found] = strdup(envstr)) == NULL) {
+ envp[found] = q;
+ /* XXX env_free(envp); */
+ errno = ENOMEM;
+ return NULL;
+ }
+ free(q);
+ return (envp);
+ }
+
+ /*
+ * it doesn't exist yet, so resize the array, move null pointer over
+ * one, save our string over the old null pointer, and return resized
+ * array.
+ */
+ p = (char **) realloc((void *) envp,
+ (unsigned) ((count+1) * sizeof(char *)));
+ if (p == NULL) {
+ /* XXX env_free(envp); */
+ errno = ENOMEM;
+ return NULL;
+ }
+ p[count] = p[count-1];
+ if ((p[count-1] = strdup(envstr)) == NULL) {
+ env_free(p);
+ errno = ENOMEM;
+ return NULL;
+ }
+ return (p);
+}
+
+
+/* return ERR = end of file
+ * FALSE = not an env setting (file was repositioned)
+ * TRUE = was an env setting
+ */
+int
+load_env(envstr, f)
+ char *envstr;
+ FILE *f;
+{
+ long filepos;
+ int fileline;
+ char name[MAX_ENVSTR], val[MAX_ENVSTR];
+ char quotechar, *c, *str;
+ int state;
+
+ /* The following states are traversed in order: */
+#define NAMEI 0 /* First char of NAME, may be quote */
+#define NAME 1 /* Subsequent chars of NAME */
+#define EQ1 2 /* After end of name, looking for '=' sign */
+#define EQ2 3 /* After '=', skipping whitespace */
+#define VALUEI 4 /* First char of VALUE, may be quote */
+#define VALUE 5 /* Subsequent chars of VALUE */
+#define FINI 6 /* All done, skipping trailing whitespace */
+#define ERROR 7 /* Error */
+
+ filepos = ftell(f);
+ fileline = LineNumber;
+ skip_comments(f);
+ if (EOF == get_string(envstr, MAX_ENVSTR, f, "\n"))
+ return (ERR);
+
+ Debug(DPARS, ("load_env, read <%s>\n", envstr));
+
+ bzero (name, sizeof name);
+ bzero (val, sizeof val);
+ str = name;
+ state = NAMEI;
+ quotechar = '\0';
+ c = envstr;
+ while (state != ERROR && *c) {
+ switch (state) {
+ case NAMEI:
+ case VALUEI:
+ if (*c == '\'' || *c == '"')
+ quotechar = *c++;
+ ++state;
+ /* FALLTHROUGH */
+ case NAME:
+ case VALUE:
+ if (quotechar) {
+ if (*c == quotechar) {
+ state++;
+ c++;
+ break;
+ }
+ if (state == NAME && *c == '=') {
+ state = ERROR;
+ break;
+ }
+ } else {
+ if (state == NAME) {
+ if (isspace (*c)) {
+ c++;
+ state++;
+ break;
+ }
+ if (*c == '=') {
+ state++;
+ break;
+ }
+ }
+ }
+ *str++ = *c++;
+ break;
+
+ case EQ1:
+ if (*c == '=') {
+ state++;
+ str = val;
+ quotechar = '\0';
+ } else {
+ if (!isspace (*c))
+ state = ERROR;
+ }
+ c++;
+ break;
+ case EQ2:
+ case FINI:
+ if (isspace (*c))
+ c++;
+ else
+ state++;
+ break;
+ }
+ }
+ if (state != FINI && !(state == VALUE && !quotechar)) {
+ Debug(DPARS, ("load_env, parse error, state = %d\n", state))
+ fseek(f, filepos, 0);
+ Set_LineNum(fileline);
+ return (FALSE);
+ }
+ if (state == VALUE) {
+ /* End of unquoted value: trim trailing whitespace */
+ c = val + strlen (val);
+ while (c > val && isspace (*(c - 1)))
+ *(--c) = '\0';
+ }
+
+ /* 2 fields from parser; looks like an env setting */
+
+ if (strlen(name) + 1 + strlen(val) >= MAX_ENVSTR-1)
+ return (FALSE);
+ (void) sprintf(envstr, "%s=%s", name, val);
+ Debug(DPARS, ("load_env, <%s> <%s> -> <%s>\n", name, val, envstr))
+ return (TRUE);
+}
+
+
+char *
+env_get(name, envp)
+ register char *name;
+ register char **envp;
+{
+ register int len = strlen(name);
+ register char *p, *q;
+
+ while ((p = *envp++)) {
+ if (!(q = strchr(p, '=')))
+ continue;
+ if ((q - p) == len && !strncmp(p, name, len))
+ return (q+1);
+ }
+ return (NULL);
+}
diff --git a/usr.sbin/cron/lib/misc.c b/usr.sbin/cron/lib/misc.c
new file mode 100644
index 0000000..afed07f
--- /dev/null
+++ b/usr.sbin/cron/lib/misc.c
@@ -0,0 +1,600 @@
+/* Copyright 1988,1990,1993,1994 by Paul Vixie
+ * All rights reserved
+ *
+ * Distribute freely, except: don't remove my name from the source or
+ * documentation (don't take credit for my work), mark your changes (don't
+ * get me blamed for your possible bugs), don't alter or remove this
+ * notice. May be sold if buildable source is provided to buyer. No
+ * warrantee of any kind, express or implied, is included with this
+ * software; use at your own risk, responsibility for damages (if any) to
+ * anyone resulting from the use of this software rests entirely with the
+ * user.
+ *
+ * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+ * I'll try to keep a version up to date. I can be reached as follows:
+ * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul
+ */
+
+#if !defined(lint) && !defined(LINT)
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif
+
+/* vix 26jan87 [RCS has the rest of the log]
+ * vix 30dec86 [written]
+ */
+
+
+#include "cron.h"
+#if SYS_TIME_H
+# include <sys/time.h>
+#else
+# include <time.h>
+#endif
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <err.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#if defined(SYSLOG)
+# include <syslog.h>
+#endif
+
+
+#if defined(LOG_DAEMON) && !defined(LOG_CRON)
+#define LOG_CRON LOG_DAEMON
+#endif
+
+
+static int LogFD = ERR;
+
+
+int
+strcmp_until(left, right, until)
+ char *left;
+ char *right;
+ int until;
+{
+ register int diff;
+
+ while (*left && *left != until && *left == *right) {
+ left++;
+ right++;
+ }
+
+ if ((*left=='\0' || *left == until) &&
+ (*right=='\0' || *right == until)) {
+ diff = 0;
+ } else {
+ diff = *left - *right;
+ }
+
+ return diff;
+}
+
+
+/* strdtb(s) - delete trailing blanks in string 's' and return new length
+ */
+int
+strdtb(s)
+ char *s;
+{
+ char *x = s;
+
+ /* scan forward to the null
+ */
+ while (*x)
+ x++;
+
+ /* scan backward to either the first character before the string,
+ * or the last non-blank in the string, whichever comes first.
+ */
+ do {x--;}
+ while (x >= s && isspace(*x));
+
+ /* one character beyond where we stopped above is where the null
+ * goes.
+ */
+ *++x = '\0';
+
+ /* the difference between the position of the null character and
+ * the position of the first character of the string is the length.
+ */
+ return x - s;
+}
+
+
+int
+set_debug_flags(flags)
+ char *flags;
+{
+ /* debug flags are of the form flag[,flag ...]
+ *
+ * if an error occurs, print a message to stdout and return FALSE.
+ * otherwise return TRUE after setting ERROR_FLAGS.
+ */
+
+#if !DEBUGGING
+
+ printf("this program was compiled without debugging enabled\n");
+ return FALSE;
+
+#else /* DEBUGGING */
+
+ char *pc = flags;
+
+ DebugFlags = 0;
+
+ while (*pc) {
+ char **test;
+ int mask;
+
+ /* try to find debug flag name in our list.
+ */
+ for ( test = DebugFlagNames, mask = 1;
+ *test && strcmp_until(*test, pc, ',');
+ test++, mask <<= 1
+ )
+ ;
+
+ if (!*test) {
+ fprintf(stderr,
+ "unrecognized debug flag <%s> <%s>\n",
+ flags, pc);
+ return FALSE;
+ }
+
+ DebugFlags |= mask;
+
+ /* skip to the next flag
+ */
+ while (*pc && *pc != ',')
+ pc++;
+ if (*pc == ',')
+ pc++;
+ }
+
+ if (DebugFlags) {
+ int flag;
+
+ fprintf(stderr, "debug flags enabled:");
+
+ for (flag = 0; DebugFlagNames[flag]; flag++)
+ if (DebugFlags & (1 << flag))
+ fprintf(stderr, " %s", DebugFlagNames[flag]);
+ fprintf(stderr, "\n");
+ }
+
+ return TRUE;
+
+#endif /* DEBUGGING */
+}
+
+
+void
+set_cron_uid()
+{
+#if defined(BSD) || defined(POSIX)
+ if (seteuid(ROOT_UID) < OK)
+ err(ERROR_EXIT, "seteuid");
+#else
+ if (setuid(ROOT_UID) < OK)
+ err(ERROR_EXIT, "setuid");
+#endif
+}
+
+
+void
+set_cron_cwd()
+{
+ struct stat sb;
+
+ /* first check for CRONDIR ("/var/cron" or some such)
+ */
+ if (stat(CRONDIR, &sb) < OK && errno == ENOENT) {
+ warn("%s", CRONDIR);
+ if (OK == mkdir(CRONDIR, 0700)) {
+ warnx("%s: created", CRONDIR);
+ stat(CRONDIR, &sb);
+ } else {
+ err(ERROR_EXIT, "%s: mkdir", CRONDIR);
+ }
+ }
+ if (!(sb.st_mode & S_IFDIR))
+ err(ERROR_EXIT, "'%s' is not a directory, bailing out", CRONDIR);
+ if (chdir(CRONDIR) < OK)
+ err(ERROR_EXIT, "cannot chdir(%s), bailing out", CRONDIR);
+
+ /* CRONDIR okay (now==CWD), now look at SPOOL_DIR ("tabs" or some such)
+ */
+ if (stat(SPOOL_DIR, &sb) < OK && errno == ENOENT) {
+ warn("%s", SPOOL_DIR);
+ if (OK == mkdir(SPOOL_DIR, 0700)) {
+ warnx("%s: created", SPOOL_DIR);
+ stat(SPOOL_DIR, &sb);
+ } else {
+ err(ERROR_EXIT, "%s: mkdir", SPOOL_DIR);
+ }
+ }
+ if (!(sb.st_mode & S_IFDIR))
+ err(ERROR_EXIT, "'%s' is not a directory, bailing out", SPOOL_DIR);
+}
+
+
+/* get_char(file) : like getc() but increment LineNumber on newlines
+ */
+int
+get_char(file)
+ FILE *file;
+{
+ int ch;
+
+ ch = getc(file);
+ if (ch == '\n')
+ Set_LineNum(LineNumber + 1)
+ return ch;
+}
+
+
+/* unget_char(ch, file) : like ungetc but do LineNumber processing
+ */
+void
+unget_char(ch, file)
+ int ch;
+ FILE *file;
+{
+ ungetc(ch, file);
+ if (ch == '\n')
+ Set_LineNum(LineNumber - 1)
+}
+
+
+/* get_string(str, max, file, termstr) : like fgets() but
+ * (1) has terminator string which should include \n
+ * (2) will always leave room for the null
+ * (3) uses get_char() so LineNumber will be accurate
+ * (4) returns EOF or terminating character, whichever
+ */
+int
+get_string(string, size, file, terms)
+ char *string;
+ int size;
+ FILE *file;
+ char *terms;
+{
+ int ch;
+
+ while (EOF != (ch = get_char(file)) && !strchr(terms, ch)) {
+ if (size > 1) {
+ *string++ = (char) ch;
+ size--;
+ }
+ }
+
+ if (size > 0)
+ *string = '\0';
+
+ return ch;
+}
+
+
+/* skip_comments(file) : read past comment (if any)
+ */
+void
+skip_comments(file)
+ FILE *file;
+{
+ int ch;
+
+ while (EOF != (ch = get_char(file))) {
+ /* ch is now the first character of a line.
+ */
+
+ while (ch == ' ' || ch == '\t')
+ ch = get_char(file);
+
+ if (ch == EOF)
+ break;
+
+ /* ch is now the first non-blank character of a line.
+ */
+
+ if (ch != '\n' && ch != '#')
+ break;
+
+ /* ch must be a newline or comment as first non-blank
+ * character on a line.
+ */
+
+ while (ch != '\n' && ch != EOF)
+ ch = get_char(file);
+
+ /* ch is now the newline of a line which we're going to
+ * ignore.
+ */
+ }
+ if (ch != EOF)
+ unget_char(ch, file);
+}
+
+
+/* int in_file(char *string, FILE *file)
+ * return TRUE if one of the lines in file matches string exactly,
+ * FALSE otherwise.
+ */
+static int
+in_file(char *string, FILE *file)
+{
+ char line[MAX_TEMPSTR];
+
+ rewind(file);
+ while (fgets(line, MAX_TEMPSTR, file)) {
+ if (line[0] != '\0')
+ if (line[strlen(line)-1] == '\n')
+ line[strlen(line)-1] = '\0';
+ if (0 == strcmp(line, string))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/* int allowed(char *username)
+ * returns TRUE if (ALLOW_FILE exists and user is listed)
+ * or (DENY_FILE exists and user is NOT listed)
+ * or (neither file exists but user=="root" so it's okay)
+ */
+int
+allowed(username)
+ char *username;
+{
+ FILE *allow, *deny;
+ int isallowed;
+
+ isallowed = FALSE;
+
+ deny = NULL;
+#if defined(ALLOW_FILE) && defined(DENY_FILE)
+ if ((allow = fopen(ALLOW_FILE, "r")) == NULL && errno != ENOENT)
+ goto out;
+ if ((deny = fopen(DENY_FILE, "r")) == NULL && errno != ENOENT)
+ goto out;
+ Debug(DMISC, ("allow/deny enabled, %d/%d\n", !!allow, !!deny))
+#else
+ allow = NULL;
+#endif
+
+ if (allow)
+ isallowed = in_file(username, allow);
+ else if (deny)
+ isallowed = !in_file(username, deny);
+ else {
+#if defined(ALLOW_ONLY_ROOT)
+ isallowed = (strcmp(username, ROOT_USER) == 0);
+#else
+ isallowed = TRUE;
+#endif
+ }
+out: if (allow)
+ fclose(allow);
+ if (deny)
+ fclose(deny);
+ return (isallowed);
+}
+
+
+void
+log_it(username, xpid, event, detail)
+ char *username;
+ int xpid;
+ char *event;
+ char *detail;
+{
+#if defined(LOG_FILE) || DEBUGGING
+ PID_T pid = xpid;
+#endif
+#if defined(LOG_FILE)
+ char *msg;
+ TIME_T now = time((TIME_T) 0);
+ register struct tm *t = localtime(&now);
+#endif /*LOG_FILE*/
+
+#if defined(SYSLOG)
+ static int syslog_open = 0;
+#endif
+
+#if defined(LOG_FILE)
+ /* we assume that MAX_TEMPSTR will hold the date, time, &punctuation.
+ */
+ msg = malloc(strlen(username)
+ + strlen(event)
+ + strlen(detail)
+ + MAX_TEMPSTR);
+
+ if (msg == NULL)
+ warnx("failed to allocate memory for log message");
+ else {
+ if (LogFD < OK) {
+ LogFD = open(LOG_FILE, O_WRONLY|O_APPEND|O_CREAT, 0600);
+ if (LogFD < OK) {
+ warn("can't open log file %s", LOG_FILE);
+ } else {
+ (void) fcntl(LogFD, F_SETFD, 1);
+ }
+ }
+
+ /* we have to sprintf() it because fprintf() doesn't always
+ * write everything out in one chunk and this has to be
+ * atomically appended to the log file.
+ */
+ sprintf(msg, "%s (%02d/%02d-%02d:%02d:%02d-%d) %s (%s)\n",
+ username,
+ t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min,
+ t->tm_sec, pid, event, detail);
+
+ /* we have to run strlen() because sprintf() returns (char*)
+ * on old BSD.
+ */
+ if (LogFD < OK || write(LogFD, msg, strlen(msg)) < OK) {
+ if (LogFD >= OK)
+ warn("%s", LOG_FILE);
+ warnx("can't write to log file");
+ write(STDERR, msg, strlen(msg));
+ }
+
+ free(msg);
+ }
+#endif /*LOG_FILE*/
+
+#if defined(SYSLOG)
+ if (!syslog_open) {
+ /* we don't use LOG_PID since the pid passed to us by
+ * our client may not be our own. therefore we want to
+ * print the pid ourselves.
+ */
+# ifdef LOG_DAEMON
+ openlog(ProgramName, LOG_PID, LOG_CRON);
+# else
+ openlog(ProgramName, LOG_PID);
+# endif
+ syslog_open = TRUE; /* assume openlog success */
+ }
+
+ syslog(LOG_INFO, "(%s) %s (%s)\n", username, event, detail);
+
+#endif /*SYSLOG*/
+
+#if DEBUGGING
+ if (DebugFlags) {
+ fprintf(stderr, "log_it: (%s %d) %s (%s)\n",
+ username, pid, event, detail);
+ }
+#endif
+}
+
+
+void
+log_close() {
+ if (LogFD != ERR) {
+ close(LogFD);
+ LogFD = ERR;
+ }
+}
+
+
+/* two warnings:
+ * (1) this routine is fairly slow
+ * (2) it returns a pointer to static storage
+ */
+char *
+first_word(s, t)
+ register char *s; /* string we want the first word of */
+ register char *t; /* terminators, implicitly including \0 */
+{
+ static char retbuf[2][MAX_TEMPSTR + 1]; /* sure wish C had GC */
+ static int retsel = 0;
+ register char *rb, *rp;
+
+ /* select a return buffer */
+ retsel = 1-retsel;
+ rb = &retbuf[retsel][0];
+ rp = rb;
+
+ /* skip any leading terminators */
+ while (*s && (NULL != strchr(t, *s))) {
+ s++;
+ }
+
+ /* copy until next terminator or full buffer */
+ while (*s && (NULL == strchr(t, *s)) && (rp < &rb[MAX_TEMPSTR])) {
+ *rp++ = *s++;
+ }
+
+ /* finish the return-string and return it */
+ *rp = '\0';
+ return rb;
+}
+
+
+/* warning:
+ * heavily ascii-dependent.
+ */
+static void
+mkprint(register char *dst, register unsigned char *src, register int len)
+{
+ while (len-- > 0)
+ {
+ register unsigned char ch = *src++;
+
+ if (ch < ' ') { /* control character */
+ *dst++ = '^';
+ *dst++ = ch + '@';
+ } else if (ch < 0177) { /* printable */
+ *dst++ = ch;
+ } else if (ch == 0177) { /* delete/rubout */
+ *dst++ = '^';
+ *dst++ = '?';
+ } else { /* parity character */
+ sprintf(dst, "\\%03o", ch);
+ dst += 4;
+ }
+ }
+ *dst = '\0';
+}
+
+
+/* warning:
+ * returns a pointer to malloc'd storage, you must call free yourself.
+ */
+char *
+mkprints(src, len)
+ register unsigned char *src;
+ register unsigned int len;
+{
+ register char *dst = malloc(len*4 + 1);
+
+ if (dst != NULL)
+ mkprint(dst, src, len);
+
+ return dst;
+}
+
+
+#ifdef MAIL_DATE
+/* Sat, 27 Feb 93 11:44:51 CST
+ * 123456789012345678901234567
+ */
+char *
+arpadate(clock)
+ time_t *clock;
+{
+ time_t t = clock ?*clock :time(0L);
+ struct tm *tm = localtime(&t);
+ static char ret[32]; /* zone name might be >3 chars */
+
+ if (tm->tm_year >= 100)
+ tm->tm_year += 1900;
+
+ (void) snprintf(ret, sizeof(ret), "%s, %2d %s %d %02d:%02d:%02d %s",
+ DowNames[tm->tm_wday],
+ tm->tm_mday,
+ MonthNames[tm->tm_mon],
+ tm->tm_year,
+ tm->tm_hour,
+ tm->tm_min,
+ tm->tm_sec,
+ TZONE(*tm));
+ return ret;
+}
+#endif /*MAIL_DATE*/
+
+
+#ifdef HAVE_SAVED_UIDS
+static int save_euid;
+int swap_uids() { save_euid = geteuid(); return seteuid(getuid()); }
+int swap_uids_back() { return seteuid(save_euid); }
+#else /*HAVE_SAVED_UIDS*/
+int swap_uids() { return setreuid(geteuid(), getuid()); }
+int swap_uids_back() { return swap_uids(); }
+#endif /*HAVE_SAVED_UIDS*/
diff --git a/usr.sbin/crunch/COPYRIGHT b/usr.sbin/crunch/COPYRIGHT
new file mode 100644
index 0000000..c7b4d2f
--- /dev/null
+++ b/usr.sbin/crunch/COPYRIGHT
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 1994 University of Maryland
+ * All Rights Reserved.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of U.M. not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. U.M. makes no representations about the
+ * suitability of this software for any purpose. It is provided "as is"
+ * without express or implied warranty.
+ *
+ * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
+ * BE LIABLE FOR ANY SPECIAL, 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.
+ *
+ * Author: James da Silva, Systems Design and Analysis Group
+ * Computer Science Department
+ * University of Maryland at College Park
+ */
diff --git a/usr.sbin/crunch/Makefile b/usr.sbin/crunch/Makefile
new file mode 100644
index 0000000..6bf600f
--- /dev/null
+++ b/usr.sbin/crunch/Makefile
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+SUBDIR= crunchgen crunchide
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/crunch/Makefile.inc b/usr.sbin/crunch/Makefile.inc
new file mode 100644
index 0000000..36175b7
--- /dev/null
+++ b/usr.sbin/crunch/Makefile.inc
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+# modify to taste
+BINDIR?= /usr/bin
+
+WARNS?= 2
diff --git a/usr.sbin/crunch/README b/usr.sbin/crunch/README
new file mode 100644
index 0000000..27c2d02
--- /dev/null
+++ b/usr.sbin/crunch/README
@@ -0,0 +1,88 @@
+
+CRUNCH 0.2 README 6/14/94
+
+Crunch is available via anonymous ftp to ftp.cs.umd.edu in
+ pub/bsd/crunch-0.2.tar.gz
+
+
+WHAT'S NEW IN 0.2
+
+* The prototype awk script has been replaced by a more capable and
+ hopefully more robust C program.
+* No fragile template makefiles or dependencies on the details of the
+ bsd build environment.
+* You can build crunched binaries even with no sources on-line, you
+ just need the .o files. Crunchgen still will try to figure out as
+ much as possible on its own, but you can override its guessing by
+ specifying the list of .o files explicitly.
+* Crunch itself has been bmake'd and some man pages written, so it
+ should be ready to install.
+
+
+INTRODUCTION
+
+Crunch is a little package that helps create "crunched" binaries for use
+on boot, install, and fixit floppies. A crunched binary in this case is
+one where many programs have been linked together into one a.out file.
+The different programs are run depending on the value of argv[0], so
+hard links to the crunched binary suffice to simulate a perfectly normal
+system.
+
+As an example, I have created an 980K crunched "fixit" binary containing
+the following programs in their entirety:
+
+ cat chmod cp date dd df echo ed expr hostname kill ln ls mkdir
+ mt mv pwd rcp rm rmdir sh sleep stty sync test [ badsect chown
+ clri disklabel dump rdump dmesg fdisk fsck halt ifconfig init
+ mknod mount newfs ping reboot restore rrestore swapon umount
+ ftp rsh sed telnet rlogin vi cpio gzip gunzip gzcat
+
+Note carefully: vi, cpio, gzip, ed, sed, dump/restore, some networking
+utilities, and the disk management utilities, all in a binary small
+enough to fit on a 1.2 MB root filesystem floppy (albeit with the kernel
+on its own boot floppy). A more reasonable subset can be made to fit
+easily with a kernel for a decent one-disk fixit filesystem.
+
+The linking together of different programs by hand is an old
+space-saving technique. Crunch automates the process by building the
+necessary stub files and makefile for you (via the crunchgen program),
+and by doctoring the symbol tables of the component .o files to allow
+them to link without "symbol multiply defined" conflicts (via the
+crunchide program).
+
+
+BUILDING CRUNCH
+
+Just type make, then make install.
+
+Crunch was written and tested under NetBSD/i386, but should work under
+other PC BSD systems that use GNU ld.
+
+The crunchgen(1) and crunchide(1) man pages have more details on using
+crunch, and the examples subdirectory contains some working .conf files
+and a sample Makefile.
+
+CREDITS
+
+Thanks to the NetBSD team for a consistently high quality effort in
+bringing together a solid, state of the art development environment.
+
+Thanks to the FreeBSD guys; Rod Grimes, Nate Williams and Jordan
+Hubbard; and to Bruce Evans, for immediate and detailed feedback on
+crunch 0.1, and for pressing me to make the prototype more useable.
+
+Crunch was written for the Maruti Hard Real-Time Operating System
+project at the University of Maryland, to help make for better install
+and recovery procedures for our NetBSD-based development environment. It
+is copyright (c) 1994 by the University of Maryland under a UCB-style
+freely- redistributable notice. See the file COPYRIGHT for details.
+
+Please let me know of any problems or of enhancements you make to this
+package. I'm particularly interested in the details of what you found
+was good to put on your fixit or install disks. Thanks!
+
+Share and Enjoy,
+Jaime
+............................................................................
+: Stand on my shoulders, : jds@cs.umd.edu : James da Silva
+: not on my toes. : uunet!mimsy!jds : http://www.cs.umd.edu/users/jds
diff --git a/usr.sbin/crunch/crunchgen/Makefile b/usr.sbin/crunch/crunchgen/Makefile
new file mode 100644
index 0000000..8d0a78b
--- /dev/null
+++ b/usr.sbin/crunch/crunchgen/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+PROG= crunchgen
+SRCS= crunchgen.c crunched_skel.c
+CLEANFILES+= crunched_skel.c
+
+crunched_skel.c: crunched_main.c
+ sh -e ${.CURDIR}/mkskel.sh ${.CURDIR}/crunched_main.c >crunched_skel.c
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/crunch/crunchgen/Makefile.depend b/usr.sbin/crunch/crunchgen/Makefile.depend
new file mode 100644
index 0000000..f384605
--- /dev/null
+++ b/usr.sbin/crunch/crunchgen/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+crunched_skel.o: crunched_skel.c
+crunched_skel.po: crunched_skel.c
+.endif
diff --git a/usr.sbin/crunch/crunchgen/crunched_main.c b/usr.sbin/crunch/crunchgen/crunched_main.c
new file mode 100644
index 0000000..f920c1f
--- /dev/null
+++ b/usr.sbin/crunch/crunchgen/crunched_main.c
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 1994 University of Maryland
+ * All Rights Reserved.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of U.M. not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. U.M. makes no representations about the
+ * suitability of this software for any purpose. It is provided "as is"
+ * without express or implied warranty.
+ *
+ * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
+ * BE LIABLE FOR ANY SPECIAL, 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.
+ *
+ * Author: James da Silva, Systems Design and Analysis Group
+ * Computer Science Department
+ * University of Maryland at College Park
+ */
+/*
+ * crunched_main.c - main program for crunched binaries, it branches to a
+ * particular subprogram based on the value of argv[0]. Also included
+ * is a little program invoked when the crunched binary is called via
+ * its EXECNAME. This one prints out the list of compiled-in binaries,
+ * or calls one of them based on argv[1]. This allows the testing of
+ * the crunched binary without creating all the links.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct stub {
+ char *name;
+ int (*f)();
+};
+
+extern char *__progname;
+extern struct stub entry_points[];
+
+int
+main(int argc, char **argv, char **envp)
+{
+ char *slash, *basename;
+ struct stub *ep;
+
+ if(argv[0] == NULL || *argv[0] == '\0')
+ crunched_usage();
+
+ slash = strrchr(argv[0], '/');
+ basename = slash? slash+1 : argv[0];
+
+ for(ep=entry_points; ep->name != NULL; ep++)
+ if(!strcmp(basename, ep->name)) break;
+
+ if(ep->name)
+ return ep->f(argc, argv, envp);
+ else {
+ fprintf(stderr, "%s: %s not compiled in\n", EXECNAME, basename);
+ crunched_usage();
+ }
+}
+
+
+int
+crunched_here(char *path)
+{
+ char *slash, *basename;
+ struct stub *ep;
+
+ slash = strrchr(path, '/');
+ basename = slash? slash+1 : path;
+
+ for(ep=entry_points; ep->name != NULL; ep++)
+ if(!strcmp(basename, ep->name))
+ return 1;
+ return 0;
+}
+
+
+int
+crunched_main(int argc, char **argv, char **envp)
+{
+ char *slash;
+ struct stub *ep;
+ int columns, len;
+
+ if(argc <= 1)
+ crunched_usage();
+
+ slash = strrchr(argv[1], '/');
+ __progname = slash? slash+1 : argv[1];
+
+ return main(--argc, ++argv, envp);
+}
+
+
+int
+crunched_usage()
+{
+ int columns, len;
+ struct stub *ep;
+
+ fprintf(stderr, "usage: %s <prog> <args> ..., where <prog> is one of:\n",
+ EXECNAME);
+ columns = 0;
+ for(ep=entry_points; ep->name != NULL; ep++) {
+ len = strlen(ep->name) + 1;
+ if(columns+len < 80)
+ columns += len;
+ else {
+ fprintf(stderr, "\n");
+ columns = len;
+ }
+ fprintf(stderr, " %s", ep->name);
+ }
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+/* end of crunched_main.c */
diff --git a/usr.sbin/crunch/crunchgen/crunchgen.1 b/usr.sbin/crunch/crunchgen/crunchgen.1
new file mode 100644
index 0000000..b9aef31
--- /dev/null
+++ b/usr.sbin/crunch/crunchgen/crunchgen.1
@@ -0,0 +1,475 @@
+.\"
+.\" Copyright (c) 1994 University of Maryland
+.\" All Rights Reserved.
+.\"
+.\" Permission to use, copy, modify, distribute, and sell this software and its
+.\" documentation for any purpose is hereby granted without fee, provided that
+.\" the above copyright notice appear in all copies and that both that
+.\" copyright notice and this permission notice appear in supporting
+.\" documentation, and that the name of U.M. not be used in advertising or
+.\" publicity pertaining to distribution of the software without specific,
+.\" written prior permission. U.M. makes no representations about the
+.\" suitability of this software for any purpose. It is provided "as is"
+.\" without express or implied warranty.
+.\"
+.\" U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
+.\" BE LIABLE FOR ANY SPECIAL, 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.
+.\"
+.\" Author: James da Silva, Systems Design and Analysis Group
+.\" Computer Science Department
+.\" University of Maryland at College Park
+.\" $FreeBSD$
+.\"
+.Dd December 23, 2005
+.Dt CRUNCHGEN 1
+.Os
+.Sh NAME
+.Nm crunchgen
+.Nd generates build environment for a crunched binary
+.Sh SYNOPSIS
+.Bk -words
+.Nm
+.Op Fl foql
+.Op Fl h Ar makefile-header-name
+.Op Fl m Ar makefile-name
+.Op Fl p Ar obj-prefix
+.Op Fl c Ar c-file-name
+.Op Fl e Ar exec-file-name
+.Op Ar conf-file
+.Ek
+.Sh DESCRIPTION
+A crunched binary is a program made up of many other programs linked
+together into a single executable.
+The crunched binary
+.Fn main
+function determines which component program to run by the contents of
+.Va argv[0] .
+The main reason to crunch programs together is for fitting
+as many programs as possible onto an installation or system recovery
+floppy.
+.Pp
+The
+.Nm
+utility reads in the specifications in
+.Ar conf-file
+for a crunched binary, and generates a
+.Pa Makefile
+and accompanying
+top-level C source file that when built creates the crunched executable
+file from the component programs.
+For each component program,
+.Nm
+can optionally attempt to determine the object (.o) files that make up
+the program from its source directory
+.Pa Makefile .
+This information is cached between runs.
+The
+.Nm
+utility uses the companion program
+.Xr crunchide 1
+to eliminate link-time conflicts between the component programs by
+hiding all unnecessary symbols.
+.Pp
+The
+.Nm
+utility places specific requirements on package
+.Pa Makefile Ns s
+which make it unsuitable for use with
+.No non- Ns Bx
+sources.
+In particular, the
+.Pa Makefile
+must contain the target
+.Ic depend ,
+and it must define all object files in the variable
+.Va OBJS .
+In some cases, you can use a fake
+.Pa Makefile :
+before looking for
+.Pa Makefile
+in the source directory
+.Pa foo ,
+.Nm
+looks for the file
+.Pa Makefile.foo
+in the current directory.
+.Pp
+After
+.Nm
+is run, the crunched binary can be built by running
+.Dq Li make -f <conf-name>.mk .
+The component programs' object files must already be built.
+An
+.Ic objs
+target, included in the output makefile, will
+run
+.Xr make 1
+in each component program's source dir to build the object
+files for the user.
+This is not done automatically since in release
+engineering circumstances it is generally not desirable to be
+modifying objects in other directories.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl c Ar c-file-name
+Set output C file name to
+.Ar c-file-name .
+The default name is
+.Pa <conf-name>.c .
+.It Fl e Ar exec-file-name
+Set crunched binary executable file name to
+.Ar exec-file-name .
+The default name is
+.Pa <conf-name> .
+.It Fl f
+Flush cache.
+Forces the recalculation of cached parameters.
+.It Fl l
+List names.
+Lists the names this binary will respond to.
+.It Fl h Ar makefile-header-name
+Set the name of a file to be included at the beginning of the
+.Pa Makefile Ns s
+generated by
+.Nm .
+This is useful to define some make variables such as
+.Va RELEASE_CRUNCH
+or similar, which might affect the behavior of
+.Xr make 1
+and are annoying to pass through environment variables.
+.It Fl m Ar makefile-name
+Set output
+.Pa Makefile
+name to
+.Ar makefile-name .
+The default name is
+.Pa <conf-name>.mk .
+.It Fl o
+Add
+.Dq Li make obj
+rules to each program make target.
+.It Fl p Ar obj-prefix
+Set the pathname to be prepended to the
+.Ic srcdir
+when computing the
+.Ic objdir .
+If this option is not present, then the prefix used
+is the content of the
+.Ev MAKEOBJDIRPREFIX
+environment variable, or
+.Pa /usr/obj .
+.It Fl q
+Quiet operation.
+Status messages are suppressed.
+.El
+.Sh CRUNCHGEN CONFIGURATION FILE COMMANDS
+The
+.Nm
+utility reads specifications from the
+.Ar conf-file
+that describe the components of the crunched binary.
+In its simplest
+use, the component program names are merely listed along with the
+top-level source directories in which their sources can be found.
+The
+.Nm
+utility then calculates (via the source makefiles) and caches the
+list of object files and their locations.
+For more specialized
+situations, the user can specify by hand all the parameters that
+.Nm
+needs.
+.Pp
+The
+.Ar conf-file
+commands are as follows:
+.Bl -tag -width indent
+.It Ic srcdirs Ar dirname ...
+A list of source trees in which the source directories of the
+component programs can be found.
+These dirs are searched using the
+.Bx
+.Dq Pa <source-dir>/<progname>/
+convention.
+Multiple
+.Ic srcdirs
+lines can be specified.
+The directories are searched in the order they are given.
+.It Ic progs Ar progname ...
+A list of programs that make up the crunched binary.
+Multiple
+.Ic progs
+lines can be specified.
+.It Ic libs Ar libspec ...
+A list of library specifications to be included in the crunched binary link.
+Multiple
+.Ic libs
+lines can be specified.
+.It Ic libs_so Ar libspec ...
+A list of library specifications to be dynamically linked in the
+crunched binary.
+These libraries will need to be made available via the run-time link-editor
+.Xr rtld 1
+when the component program that requires them is executed from
+the crunched binary.
+Multiple
+.Ic libs_so
+lines can be specified.
+The
+.Ic libs_so
+directive overrides a library specified gratuitously on a
+.Ic libs
+line.
+.It Ic buildopts Ar buildopts ...
+A list of build options to be added to every make target.
+.It Ic ln Ar progname linkname
+Causes the crunched binary to invoke
+.Ar progname
+whenever
+.Ar linkname
+appears in
+.Va argv[0] .
+This allows programs that change their behavior when
+run under different names to operate correctly.
+.El
+.Pp
+To handle specialized situations, such as when the source is not
+available or not built via a conventional
+.Pa Makefile ,
+the following
+.Ic special
+commands can be used to set
+.Nm
+parameters for a component program.
+.Bl -tag -width indent
+.It Ic special Ar progname Ic srcdir Ar pathname
+Set the source directory for
+.Ar progname .
+This is normally calculated by searching the specified
+.Ic srcdirs
+for a directory named
+.Ar progname .
+.It Ic special Ar progname Ic objdir Ar pathname
+Set the
+.Pa obj
+directory for
+.Ar progname .
+The
+.Pa obj
+directory is normally calculated by looking for a directory
+whose name is that of the source directory prepended by
+one of the following components, in order of priority:
+the
+.Fl p
+argument passed to the command line; or,
+the value of the
+.Ev MAKEOBJDIRPREFIX
+environment variable, or
+.Pa /usr/obj .
+If the directory is not found, the
+.Ic srcdir
+itself becomes the
+.Ic objdir .
+.It Ic special Ar progname Ic buildopts Ar buildopts
+Define a set of build options that should be added to
+.Xr make 1
+targets in addition to those specified using
+.Ic buildopts
+when processing
+.Ar progname .
+.It Ic special Ar progname Ic objs Ar object-file-name ...
+Set the list of object files for program
+.Ar progname .
+This is normally calculated by constructing a temporary makefile that includes
+.Dq Ic srcdir Ns / Ns Pa Makefile
+and outputs the value of
+.Va $(OBJS) .
+.It Ic special Ar progname Ic objpaths Ar full-pathname-to-object-file ...
+Sets the pathnames of the object files for program
+.Ar progname .
+This is normally calculated by prepending the
+.Ic objdir
+pathname to each file in the
+.Ic objs
+list.
+.It Ic special Ar progname Ic objvar Ar variable_name
+Sets the name of the
+.Xr make 1
+variable which holds the list of
+object files for program
+.Ar progname .
+This is normally
+.Va OBJS
+but some
+.Pa Makefile Ns s
+might like to use other conventions or
+prepend the program's name to the variable, e.g.\&
+.Va SSHD_OBJS .
+.It Ic special Ar progname Ic lib Ar library-name ...
+Specifies libraries to be linked with object files to produce
+.Ar progname Ns Pa .lo .
+This can be useful with libraries which redefine routines in
+the standard libraries, or poorly written libraries which
+reference symbols in the object files.
+.It Ic special Ar progname Ic keep Ar symbol-name ...
+Add specified list of symbols to the keep list for program
+.Ar progname .
+An underscore
+.Pq Ql _
+is prepended to each symbol and it becomes the argument to a
+.Fl k
+option for the
+.Xr crunchide 1
+phase.
+This option is to be used as a last resort as its use can cause a
+symbol conflict, however in certain instances it may be the only way to
+have a symbol resolve.
+.It Ic special Ar progname Ic ident Ar identifier
+Set the
+.Pa Makefile Ns / Ns Tn C
+identifier for
+.Ar progname .
+This is normally generated from a
+.Ar progname ,
+mapping
+.Ql -
+to
+.Ql _
+and ignoring all other non-identifier characters.
+This leads to programs named
+.Qq Li foo.bar
+and
+.Qq Li foobar
+to map to the same identifier.
+.El
+.Pp
+Only the
+.Ic objpaths
+parameter is actually needed by
+.Nm ,
+but it is calculated from
+.Ic objdir
+and
+.Ic objs ,
+which are in turn calculated from
+.Ic srcdir ,
+so is sometimes convenient to specify the earlier parameters and let
+.Nm
+calculate forward from there if it can.
+.Pp
+The makefile produced by
+.Nm
+contains an optional
+.Ic objs
+target that will build the object files for each component program by
+running
+.Xr make 1
+inside that program's source directory.
+For this to work the
+.Ic srcdir
+and
+.Ic objs
+parameters must also be valid.
+If they are not valid for a particular program, that
+program is skipped in the
+.Ic objs
+target.
+.Sh EXAMPLES
+Here is an example
+.Nm
+input conf file, named
+.Dq Pa kcopy.conf :
+.Bd -literal -offset indent
+srcdirs /usr/src/bin /usr/src/sbin
+
+progs test cp echo sh fsck halt init mount umount myinstall
+progs anotherprog
+ln test [ # test can be invoked via [
+ln sh -sh # init invokes the shell with "-sh" in argv[0]
+
+special myprog objpaths /homes/leroy/src/myinstall.o # no sources
+
+special anotherprog -DNO_FOO WITHOUT_BAR=YES
+
+libs -lutil -lcrypt
+.Ed
+.Pp
+This conf file specifies a small crunched binary consisting of some
+basic system utilities plus a homegrown install program
+.Dq Pa myinstall ,
+for which no source directory is specified, but its object file is
+specified directly with the
+.Ic special
+line.
+.Pp
+Additionally when
+.Dq Pa anotherprog
+is built the arguments
+.Pp
+.Dl -DNO_FOO WITHOUT_BAR=YES
+.Pp
+are added to all build targets.
+.Pp
+The crunched binary
+.Dq Pa kcopy
+can be built as follows:
+.Bd -literal -offset indent
+% crunchgen -m Makefile kcopy.conf # gen Makefile and kcopy.c
+% make objs # build the component programs' .o files
+% make # build the crunched binary kcopy
+% kcopy sh # test that this invokes a sh shell
+$ # it works!
+.Ed
+.Pp
+At this point the binary
+.Dq Pa kcopy
+can be copied onto an install floppy
+and hard-linked to the names of the component programs.
+.Pp
+Note that if the
+.Ic libs_so
+command had been used, copies of the libraries so named
+would also need to be copied to the install floppy.
+.Sh SEE ALSO
+.Xr crunchide 1 ,
+.Xr make 1 ,
+.Xr rtld 1
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility was written by
+.An James da Silva Aq Mt jds@cs.umd.edu .
+.Pp
+Copyright (c) 1994 University of Maryland.
+All Rights Reserved.
+.Pp
+The
+.Ic libs_so
+keyword was added in 2005 by
+.An Adrian Steinmann Aq Mt ast@marabu.ch
+and
+.An Ceri Davies Aq Mt ceri@FreeBSD.org .
+.Sh CAVEATS
+While
+.Nm
+takes care to eliminate link conflicts between the component programs
+of a crunched binary, conflicts are still possible between the
+libraries that are linked in.
+Some shuffling in the order of
+libraries may be required, and in some rare cases two libraries may
+have an unresolvable conflict and thus cannot be crunched together.
+.Pp
+Some versions of the
+.Bx
+build environment do not by default build the
+intermediate object file for single-source file programs.
+The
+.Dq Li make objs
+must then be used to get those object files built, or
+some other arrangements made.
diff --git a/usr.sbin/crunch/crunchgen/crunchgen.c b/usr.sbin/crunch/crunchgen/crunchgen.c
new file mode 100644
index 0000000..8ace15a
--- /dev/null
+++ b/usr.sbin/crunch/crunchgen/crunchgen.c
@@ -0,0 +1,1236 @@
+/*
+ * Copyright (c) 1994 University of Maryland
+ * All Rights Reserved.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of U.M. not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. U.M. makes no representations about the
+ * suitability of this software for any purpose. It is provided "as is"
+ * without express or implied warranty.
+ *
+ * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
+ * BE LIABLE FOR ANY SPECIAL, 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.
+ *
+ * Author: James da Silva, Systems Design and Analysis Group
+ * Computer Science Department
+ * University of Maryland at College Park
+ */
+/*
+ * ========================================================================
+ * crunchgen.c
+ *
+ * Generates a Makefile and main C file for a crunched executable,
+ * from specs given in a .conf file.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define CRUNCH_VERSION "0.2"
+
+#define MAXLINELEN 16384
+#define MAXFIELDS 2048
+
+
+/* internal representation of conf file: */
+
+/* simple lists of strings suffice for most parms */
+
+typedef struct strlst {
+ struct strlst *next;
+ char *str;
+} strlst_t;
+
+/* progs have structure, each field can be set with "special" or calculated */
+
+typedef struct prog {
+ struct prog *next; /* link field */
+ char *name; /* program name */
+ char *ident; /* C identifier for the program name */
+ char *srcdir;
+ char *realsrcdir;
+ char *objdir;
+ char *objvar; /* Makefile variable to replace OBJS */
+ strlst_t *objs, *objpaths;
+ strlst_t *buildopts;
+ strlst_t *keeplist;
+ strlst_t *links;
+ strlst_t *libs;
+ strlst_t *libs_so;
+ int goterror;
+} prog_t;
+
+
+/* global state */
+
+strlst_t *buildopts = NULL;
+strlst_t *srcdirs = NULL;
+strlst_t *libs = NULL;
+strlst_t *libs_so = NULL;
+prog_t *progs = NULL;
+
+char confname[MAXPATHLEN], infilename[MAXPATHLEN];
+char outmkname[MAXPATHLEN], outcfname[MAXPATHLEN], execfname[MAXPATHLEN];
+char tempfname[MAXPATHLEN], cachename[MAXPATHLEN], curfilename[MAXPATHLEN];
+char outhdrname[MAXPATHLEN] ; /* user-supplied header for *.mk */
+char *objprefix; /* where are the objects ? */
+char *path_make;
+int linenum = -1;
+int goterror = 0;
+
+int verbose, readcache; /* options */
+int reading_cache;
+int makeobj = 0; /* add 'make obj' rules to the makefile */
+
+int list_mode;
+
+/* general library routines */
+
+void status(const char *str);
+void out_of_memory(void);
+void add_string(strlst_t **listp, char *str);
+int is_dir(const char *pathname);
+int is_nonempty_file(const char *pathname);
+int subtract_strlst(strlst_t **lista, strlst_t **listb);
+int in_list(strlst_t **listp, char *str);
+
+/* helper routines for main() */
+
+void usage(void);
+void parse_conf_file(void);
+void gen_outputs(void);
+
+extern char *crunched_skel[];
+
+
+int
+main(int argc, char **argv)
+{
+ char *p;
+ int optc;
+
+ verbose = 1;
+ readcache = 1;
+ *outmkname = *outcfname = *execfname = '\0';
+
+ path_make = getenv("MAKE");
+ if (path_make == NULL || *path_make == '\0')
+ path_make = "make";
+
+ p = getenv("MAKEOBJDIRPREFIX");
+ if (p == NULL || *p == '\0')
+ objprefix = "/usr/obj"; /* default */
+ else
+ if ((objprefix = strdup(p)) == NULL)
+ out_of_memory();
+
+ while((optc = getopt(argc, argv, "lh:m:c:e:p:foq")) != -1) {
+ switch(optc) {
+ case 'f':
+ readcache = 0;
+ break;
+ case 'o':
+ makeobj = 1;
+ break;
+ case 'q':
+ verbose = 0;
+ break;
+
+ case 'm':
+ strlcpy(outmkname, optarg, sizeof(outmkname));
+ break;
+ case 'p':
+ if ((objprefix = strdup(optarg)) == NULL)
+ out_of_memory();
+ break;
+
+ case 'h':
+ strlcpy(outhdrname, optarg, sizeof(outhdrname));
+ break;
+ case 'c':
+ strlcpy(outcfname, optarg, sizeof(outcfname));
+ break;
+ case 'e':
+ strlcpy(execfname, optarg, sizeof(execfname));
+ break;
+
+ case 'l':
+ list_mode++;
+ verbose = 0;
+ break;
+
+ case '?':
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage();
+
+ /*
+ * generate filenames
+ */
+
+ strlcpy(infilename, argv[0], sizeof(infilename));
+
+ /* confname = `basename infilename .conf` */
+
+ if ((p=strrchr(infilename, '/')) != NULL)
+ strlcpy(confname, p + 1, sizeof(confname));
+ else
+ strlcpy(confname, infilename, sizeof(confname));
+
+ if ((p=strrchr(confname, '.')) != NULL && !strcmp(p, ".conf"))
+ *p = '\0';
+
+ if (!*outmkname)
+ snprintf(outmkname, sizeof(outmkname), "%s.mk", confname);
+ if (!*outcfname)
+ snprintf(outcfname, sizeof(outcfname), "%s.c", confname);
+ if (!*execfname)
+ snprintf(execfname, sizeof(execfname), "%s", confname);
+
+ snprintf(cachename, sizeof(cachename), "%s.cache", confname);
+ snprintf(tempfname, sizeof(tempfname), "%s/crunchgen_%sXXXXXX",
+ getenv("TMPDIR") ? getenv("TMPDIR") : _PATH_TMP, confname);
+
+ parse_conf_file();
+ if (list_mode)
+ exit(goterror);
+
+ gen_outputs();
+
+ exit(goterror);
+}
+
+
+void
+usage(void)
+{
+ fprintf(stderr, "%s%s\n\t%s%s\n", "usage: crunchgen [-foq] ",
+ "[-h <makefile-header-name>] [-m <makefile>]",
+ "[-p <obj-prefix>] [-c <c-file-name>] [-e <exec-file>] ",
+ "<conffile>");
+ exit(1);
+}
+
+
+/*
+ * ========================================================================
+ * parse_conf_file subsystem
+ *
+ */
+
+/* helper routines for parse_conf_file */
+
+void parse_one_file(char *filename);
+void parse_line(char *pline, int *fc, char **fv, int nf);
+void add_srcdirs(int argc, char **argv);
+void add_progs(int argc, char **argv);
+void add_link(int argc, char **argv);
+void add_libs(int argc, char **argv);
+void add_libs_so(int argc, char **argv);
+void add_buildopts(int argc, char **argv);
+void add_special(int argc, char **argv);
+
+prog_t *find_prog(char *str);
+void add_prog(char *progname);
+
+
+void
+parse_conf_file(void)
+{
+ if (!is_nonempty_file(infilename))
+ errx(1, "fatal: input file \"%s\" not found", infilename);
+
+ parse_one_file(infilename);
+ if (readcache && is_nonempty_file(cachename)) {
+ reading_cache = 1;
+ parse_one_file(cachename);
+ }
+}
+
+
+void
+parse_one_file(char *filename)
+{
+ char *fieldv[MAXFIELDS];
+ int fieldc;
+ void (*f)(int c, char **v);
+ FILE *cf;
+ char line[MAXLINELEN];
+
+ snprintf(line, sizeof(line), "reading %s", filename);
+ status(line);
+ strlcpy(curfilename, filename, sizeof(curfilename));
+
+ if ((cf = fopen(curfilename, "r")) == NULL) {
+ warn("%s", curfilename);
+ goterror = 1;
+ return;
+ }
+
+ linenum = 0;
+ while (fgets(line, MAXLINELEN, cf) != NULL) {
+ linenum++;
+ parse_line(line, &fieldc, fieldv, MAXFIELDS);
+
+ if (fieldc < 1)
+ continue;
+
+ if (!strcmp(fieldv[0], "srcdirs"))
+ f = add_srcdirs;
+ else if(!strcmp(fieldv[0], "progs"))
+ f = add_progs;
+ else if(!strcmp(fieldv[0], "ln"))
+ f = add_link;
+ else if(!strcmp(fieldv[0], "libs"))
+ f = add_libs;
+ else if(!strcmp(fieldv[0], "libs_so"))
+ f = add_libs_so;
+ else if(!strcmp(fieldv[0], "buildopts"))
+ f = add_buildopts;
+ else if(!strcmp(fieldv[0], "special"))
+ f = add_special;
+ else {
+ warnx("%s:%d: skipping unknown command `%s'",
+ curfilename, linenum, fieldv[0]);
+ goterror = 1;
+ continue;
+ }
+
+ if (fieldc < 2) {
+ warnx("%s:%d: %s %s",
+ curfilename, linenum, fieldv[0],
+ "command needs at least 1 argument, skipping");
+ goterror = 1;
+ continue;
+ }
+
+ f(fieldc, fieldv);
+ }
+
+ if (ferror(cf)) {
+ warn("%s", curfilename);
+ goterror = 1;
+ }
+ fclose(cf);
+}
+
+
+void
+parse_line(char *pline, int *fc, char **fv, int nf)
+{
+ char *p;
+
+ p = pline;
+ *fc = 0;
+
+ while (1) {
+ while (isspace((unsigned char)*p))
+ p++;
+
+ if (*p == '\0' || *p == '#')
+ break;
+
+ if (*fc < nf)
+ fv[(*fc)++] = p;
+
+ while (*p && !isspace((unsigned char)*p) && *p != '#')
+ p++;
+
+ if (*p == '\0' || *p == '#')
+ break;
+
+ *p++ = '\0';
+ }
+
+ if (*p)
+ *p = '\0'; /* needed for '#' case */
+}
+
+
+void
+add_srcdirs(int argc, char **argv)
+{
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ if (is_dir(argv[i]))
+ add_string(&srcdirs, argv[i]);
+ else {
+ warnx("%s:%d: `%s' is not a directory, skipping it",
+ curfilename, linenum, argv[i]);
+ goterror = 1;
+ }
+ }
+}
+
+
+void
+add_progs(int argc, char **argv)
+{
+ int i;
+
+ for (i = 1; i < argc; i++)
+ add_prog(argv[i]);
+}
+
+
+void
+add_prog(char *progname)
+{
+ prog_t *p1, *p2;
+
+ /* add to end, but be smart about dups */
+
+ for (p1 = NULL, p2 = progs; p2 != NULL; p1 = p2, p2 = p2->next)
+ if (!strcmp(p2->name, progname))
+ return;
+
+ p2 = malloc(sizeof(prog_t));
+ if(p2) {
+ memset(p2, 0, sizeof(prog_t));
+ p2->name = strdup(progname);
+ }
+ if (!p2 || !p2->name)
+ out_of_memory();
+
+ p2->next = NULL;
+ if (p1 == NULL)
+ progs = p2;
+ else
+ p1->next = p2;
+
+ p2->ident = NULL;
+ p2->srcdir = NULL;
+ p2->realsrcdir = NULL;
+ p2->objdir = NULL;
+ p2->links = NULL;
+ p2->libs = NULL;
+ p2->libs_so = NULL;
+ p2->objs = NULL;
+ p2->keeplist = NULL;
+ p2->buildopts = NULL;
+ p2->goterror = 0;
+
+ if (list_mode)
+ printf("%s\n",progname);
+}
+
+
+void
+add_link(int argc, char **argv)
+{
+ int i;
+ prog_t *p = find_prog(argv[1]);
+
+ if (p == NULL) {
+ warnx("%s:%d: no prog %s previously declared, skipping link",
+ curfilename, linenum, argv[1]);
+ goterror = 1;
+ return;
+ }
+
+ for (i = 2; i < argc; i++) {
+ if (list_mode)
+ printf("%s\n",argv[i]);
+
+ add_string(&p->links, argv[i]);
+ }
+}
+
+
+void
+add_libs(int argc, char **argv)
+{
+ int i;
+
+ for(i = 1; i < argc; i++) {
+ add_string(&libs, argv[i]);
+ if ( in_list(&libs_so, argv[i]) )
+ warnx("%s:%d: "
+ "library `%s' specified as dynamic earlier",
+ curfilename, linenum, argv[i]);
+ }
+}
+
+
+void
+add_libs_so(int argc, char **argv)
+{
+ int i;
+
+ for(i = 1; i < argc; i++) {
+ add_string(&libs_so, argv[i]);
+ if ( in_list(&libs, argv[i]) )
+ warnx("%s:%d: "
+ "library `%s' specified as static earlier",
+ curfilename, linenum, argv[i]);
+ }
+}
+
+
+void
+add_buildopts(int argc, char **argv)
+{
+ int i;
+
+ for (i = 1; i < argc; i++)
+ add_string(&buildopts, argv[i]);
+}
+
+
+void
+add_special(int argc, char **argv)
+{
+ int i;
+ prog_t *p = find_prog(argv[1]);
+
+ if (p == NULL) {
+ if (reading_cache)
+ return;
+
+ warnx("%s:%d: no prog %s previously declared, skipping special",
+ curfilename, linenum, argv[1]);
+ goterror = 1;
+ return;
+ }
+
+ if (!strcmp(argv[2], "ident")) {
+ if (argc != 4)
+ goto argcount;
+ if ((p->ident = strdup(argv[3])) == NULL)
+ out_of_memory();
+ } else if (!strcmp(argv[2], "srcdir")) {
+ if (argc != 4)
+ goto argcount;
+ if ((p->srcdir = strdup(argv[3])) == NULL)
+ out_of_memory();
+ } else if (!strcmp(argv[2], "objdir")) {
+ if(argc != 4)
+ goto argcount;
+ if((p->objdir = strdup(argv[3])) == NULL)
+ out_of_memory();
+ } else if (!strcmp(argv[2], "objs")) {
+ p->objs = NULL;
+ for (i = 3; i < argc; i++)
+ add_string(&p->objs, argv[i]);
+ } else if (!strcmp(argv[2], "objpaths")) {
+ p->objpaths = NULL;
+ for (i = 3; i < argc; i++)
+ add_string(&p->objpaths, argv[i]);
+ } else if (!strcmp(argv[2], "keep")) {
+ p->keeplist = NULL;
+ for(i = 3; i < argc; i++)
+ add_string(&p->keeplist, argv[i]);
+ } else if (!strcmp(argv[2], "objvar")) {
+ if(argc != 4)
+ goto argcount;
+ if ((p->objvar = strdup(argv[3])) == NULL)
+ out_of_memory();
+ } else if (!strcmp(argv[2], "buildopts")) {
+ p->buildopts = NULL;
+ for (i = 3; i < argc; i++)
+ add_string(&p->buildopts, argv[i]);
+ } else if (!strcmp(argv[2], "lib")) {
+ for (i = 3; i < argc; i++)
+ add_string(&p->libs, argv[i]);
+ } else {
+ warnx("%s:%d: bad parameter name `%s', skipping line",
+ curfilename, linenum, argv[2]);
+ goterror = 1;
+ }
+ return;
+
+ argcount:
+ warnx("%s:%d: too %s arguments, expected \"special %s %s <string>\"",
+ curfilename, linenum, argc < 4? "few" : "many", argv[1], argv[2]);
+ goterror = 1;
+}
+
+
+prog_t *find_prog(char *str)
+{
+ prog_t *p;
+
+ for (p = progs; p != NULL; p = p->next)
+ if (!strcmp(p->name, str))
+ return p;
+
+ return NULL;
+}
+
+
+/*
+ * ========================================================================
+ * gen_outputs subsystem
+ *
+ */
+
+/* helper subroutines */
+
+void remove_error_progs(void);
+void fillin_program(prog_t *p);
+void gen_specials_cache(void);
+void gen_output_makefile(void);
+void gen_output_cfile(void);
+
+void fillin_program_objs(prog_t *p, char *path);
+void top_makefile_rules(FILE *outmk);
+void prog_makefile_rules(FILE *outmk, prog_t *p);
+void output_strlst(FILE *outf, strlst_t *lst);
+char *genident(char *str);
+char *dir_search(char *progname);
+
+
+void
+gen_outputs(void)
+{
+ prog_t *p;
+
+ for (p = progs; p != NULL; p = p->next)
+ fillin_program(p);
+
+ remove_error_progs();
+ gen_specials_cache();
+ gen_output_cfile();
+ gen_output_makefile();
+ status("");
+ fprintf(stderr,
+ "Run \"%s -f %s\" to build crunched binary.\n",
+ path_make, outmkname);
+}
+
+/*
+ * run the makefile for the program to find which objects are necessary
+ */
+void
+fillin_program(prog_t *p)
+{
+ char path[MAXPATHLEN];
+ char line[MAXLINELEN];
+ FILE *f;
+
+ snprintf(line, MAXLINELEN, "filling in parms for %s", p->name);
+ status(line);
+
+ if (!p->ident)
+ p->ident = genident(p->name);
+
+ /* look for the source directory if one wasn't specified by a special */
+ if (!p->srcdir) {
+ p->srcdir = dir_search(p->name);
+ }
+
+ /* Determine the actual srcdir (maybe symlinked). */
+ if (p->srcdir) {
+ snprintf(line, MAXLINELEN, "cd %s && echo -n `/bin/pwd`",
+ p->srcdir);
+ f = popen(line,"r");
+ if (!f)
+ errx(1, "Can't execute: %s\n", line);
+
+ path[0] = '\0';
+ fgets(path, sizeof path, f);
+ if (pclose(f))
+ errx(1, "Can't execute: %s\n", line);
+
+ if (!*path)
+ errx(1, "Can't perform pwd on: %s\n", p->srcdir);
+
+ p->realsrcdir = strdup(path);
+ }
+
+ /* Unless the option to make object files was specified the
+ * the objects will be built in the source directory unless
+ * an object directory already exists.
+ */
+ if (!makeobj && !p->objdir && p->srcdir) {
+ snprintf(line, sizeof line, "%s/%s", objprefix, p->realsrcdir);
+ if (is_dir(line)) {
+ if ((p->objdir = strdup(line)) == NULL)
+ out_of_memory();
+ } else
+ p->objdir = p->realsrcdir;
+ }
+
+ /*
+ * XXX look for a Makefile.{name} in local directory first.
+ * This lets us override the original Makefile.
+ */
+ snprintf(path, sizeof(path), "Makefile.%s", p->name);
+ if (is_nonempty_file(path)) {
+ snprintf(line, MAXLINELEN, "Using %s for %s", path, p->name);
+ status(line);
+ } else
+ if (p->srcdir)
+ snprintf(path, sizeof(path), "%s/Makefile", p->srcdir);
+ if (!p->objs && p->srcdir && is_nonempty_file(path))
+ fillin_program_objs(p, path);
+
+ if (!p->srcdir && !p->objdir && verbose)
+ warnx("%s: %s: %s",
+ "warning: could not find source directory",
+ infilename, p->name);
+ if (!p->objs && verbose)
+ warnx("%s: %s: warning: could not find any .o files",
+ infilename, p->name);
+
+ if ((!p->srcdir || !p->objdir) && !p->objs)
+ p->goterror = 1;
+}
+
+void
+fillin_program_objs(prog_t *p, char *path)
+{
+ char *obj, *cp;
+ int fd, rc;
+ FILE *f;
+ char *objvar="OBJS";
+ strlst_t *s;
+ char line[MAXLINELEN];
+
+ /* discover the objs from the srcdir Makefile */
+
+ if ((fd = mkstemp(tempfname)) == -1) {
+ perror(tempfname);
+ exit(1);
+ }
+ if ((f = fdopen(fd, "w")) == NULL) {
+ warn("%s", tempfname);
+ goterror = 1;
+ return;
+ }
+ if (p->objvar)
+ objvar = p->objvar;
+
+ /*
+ * XXX include outhdrname (e.g. to contain Make variables)
+ */
+ if (outhdrname[0] != '\0')
+ fprintf(f, ".include \"%s\"\n", outhdrname);
+ fprintf(f, ".include \"%s\"\n", path);
+ fprintf(f, ".POSIX:\n");
+ if (buildopts) {
+ fprintf(f, "BUILDOPTS+=");
+ output_strlst(f, buildopts);
+ }
+ fprintf(f, ".if defined(PROG)\n");
+ fprintf(f, "%s?=${PROG}.o\n", objvar);
+ fprintf(f, ".endif\n");
+ fprintf(f, "loop:\n\t@echo 'OBJS= '${%s}\n", objvar);
+
+ fprintf(f, "crunchgen_objs:\n"
+ "\t@cd %s && %s -f %s $(BUILDOPTS) $(%s_OPTS)",
+ p->srcdir, path_make, tempfname, p->ident);
+ for (s = p->buildopts; s != NULL; s = s->next)
+ fprintf(f, " %s", s->str);
+ fprintf(f, " loop\n");
+
+ fclose(f);
+
+ snprintf(line, MAXLINELEN, "cd %s && %s -f %s -B crunchgen_objs",
+ p->srcdir, path_make, tempfname);
+ if ((f = popen(line, "r")) == NULL) {
+ warn("submake pipe");
+ goterror = 1;
+ return;
+ }
+
+ while(fgets(line, MAXLINELEN, f)) {
+ if (strncmp(line, "OBJS= ", 6)) {
+ warnx("make error: %s", line);
+ goterror = 1;
+ continue;
+ }
+
+ cp = line + 6;
+ while (isspace((unsigned char)*cp))
+ cp++;
+
+ while(*cp) {
+ obj = cp;
+ while (*cp && !isspace((unsigned char)*cp))
+ cp++;
+ if (*cp)
+ *cp++ = '\0';
+ add_string(&p->objs, obj);
+ while (isspace((unsigned char)*cp))
+ cp++;
+ }
+ }
+
+ if ((rc=pclose(f)) != 0) {
+ warnx("make error: make returned %d", rc);
+ goterror = 1;
+ }
+
+ unlink(tempfname);
+}
+
+void
+remove_error_progs(void)
+{
+ prog_t *p1, *p2;
+
+ p1 = NULL; p2 = progs;
+ while (p2 != NULL) {
+ if (!p2->goterror)
+ p1 = p2, p2 = p2->next;
+ else {
+ /* delete it from linked list */
+ warnx("%s: %s: ignoring program because of errors",
+ infilename, p2->name);
+ if (p1)
+ p1->next = p2->next;
+ else
+ progs = p2->next;
+ p2 = p2->next;
+ }
+ }
+}
+
+void
+gen_specials_cache(void)
+{
+ FILE *cachef;
+ prog_t *p;
+ char line[MAXLINELEN];
+
+ snprintf(line, MAXLINELEN, "generating %s", cachename);
+ status(line);
+
+ if ((cachef = fopen(cachename, "w")) == NULL) {
+ warn("%s", cachename);
+ goterror = 1;
+ return;
+ }
+
+ fprintf(cachef, "# %s - parm cache generated from %s by crunchgen "
+ " %s\n\n",
+ cachename, infilename, CRUNCH_VERSION);
+
+ for (p = progs; p != NULL; p = p->next) {
+ fprintf(cachef, "\n");
+ if (p->srcdir)
+ fprintf(cachef, "special %s srcdir %s\n",
+ p->name, p->srcdir);
+ if (p->objdir)
+ fprintf(cachef, "special %s objdir %s\n",
+ p->name, p->objdir);
+ if (p->objs) {
+ fprintf(cachef, "special %s objs", p->name);
+ output_strlst(cachef, p->objs);
+ }
+ if (p->objpaths) {
+ fprintf(cachef, "special %s objpaths", p->name);
+ output_strlst(cachef, p->objpaths);
+ }
+ }
+ fclose(cachef);
+}
+
+
+void
+gen_output_makefile(void)
+{
+ prog_t *p;
+ FILE *outmk;
+ char line[MAXLINELEN];
+
+ snprintf(line, MAXLINELEN, "generating %s", outmkname);
+ status(line);
+
+ if ((outmk = fopen(outmkname, "w")) == NULL) {
+ warn("%s", outmkname);
+ goterror = 1;
+ return;
+ }
+
+ fprintf(outmk, "# %s - generated from %s by crunchgen %s\n\n",
+ outmkname, infilename, CRUNCH_VERSION);
+
+ if (outhdrname[0] != '\0')
+ fprintf(outmk, ".include \"%s\"\n", outhdrname);
+
+ top_makefile_rules(outmk);
+ for (p = progs; p != NULL; p = p->next)
+ prog_makefile_rules(outmk, p);
+
+ fprintf(outmk, "\n# ========\n");
+ fclose(outmk);
+}
+
+
+void
+gen_output_cfile(void)
+{
+ char **cp;
+ FILE *outcf;
+ prog_t *p;
+ strlst_t *s;
+ char line[MAXLINELEN];
+
+ snprintf(line, MAXLINELEN, "generating %s", outcfname);
+ status(line);
+
+ if((outcf = fopen(outcfname, "w")) == NULL) {
+ warn("%s", outcfname);
+ goterror = 1;
+ return;
+ }
+
+ fprintf(outcf,
+ "/* %s - generated from %s by crunchgen %s */\n",
+ outcfname, infilename, CRUNCH_VERSION);
+
+ fprintf(outcf, "#define EXECNAME \"%s\"\n", execfname);
+ for (cp = crunched_skel; *cp != NULL; cp++)
+ fprintf(outcf, "%s\n", *cp);
+
+ for (p = progs; p != NULL; p = p->next)
+ fprintf(outcf, "extern int _crunched_%s_stub();\n", p->ident);
+
+ fprintf(outcf, "\nstruct stub entry_points[] = {\n");
+ for (p = progs; p != NULL; p = p->next) {
+ fprintf(outcf, "\t{ \"%s\", _crunched_%s_stub },\n",
+ p->name, p->ident);
+ for (s = p->links; s != NULL; s = s->next)
+ fprintf(outcf, "\t{ \"%s\", _crunched_%s_stub },\n",
+ s->str, p->ident);
+ }
+
+ fprintf(outcf, "\t{ EXECNAME, crunched_main },\n");
+ fprintf(outcf, "\t{ NULL, NULL }\n};\n");
+ fclose(outcf);
+}
+
+
+char *genident(char *str)
+{
+ char *n, *s, *d;
+
+ /*
+ * generates a Makefile/C identifier from a program name,
+ * mapping '-' to '_' and ignoring all other non-identifier
+ * characters. This leads to programs named "foo.bar" and
+ * "foobar" to map to the same identifier.
+ */
+
+ if ((n = strdup(str)) == NULL)
+ return NULL;
+ for (d = s = n; *s != '\0'; s++) {
+ if (*s == '-')
+ *d++ = '_';
+ else if (*s == '_' || isalnum((unsigned char)*s))
+ *d++ = *s;
+ }
+ *d = '\0';
+ return n;
+}
+
+
+char *dir_search(char *progname)
+{
+ char path[MAXPATHLEN];
+ strlst_t *dir;
+ char *srcdir;
+
+ for (dir = srcdirs; dir != NULL; dir = dir->next) {
+ snprintf(path, MAXPATHLEN, "%s/%s", dir->str, progname);
+ if (!is_dir(path))
+ continue;
+
+ if ((srcdir = strdup(path)) == NULL)
+ out_of_memory();
+
+ return srcdir;
+ }
+ return NULL;
+}
+
+
+void
+top_makefile_rules(FILE *outmk)
+{
+ prog_t *p;
+
+ fprintf(outmk, "LD?= ld\n");
+ fprintf(outmk, "STRIPBIN?= strip\n");
+ if ( subtract_strlst(&libs, &libs_so) )
+ fprintf(outmk, "# NOTE: Some LIBS declarations below overridden by LIBS_SO\n");
+
+ fprintf(outmk, "LIBS+=");
+ output_strlst(outmk, libs);
+
+ fprintf(outmk, "LIBS_SO+=");
+ output_strlst(outmk, libs_so);
+
+ if (makeobj) {
+ fprintf(outmk, "MAKEOBJDIRPREFIX?=%s\n", objprefix);
+ fprintf(outmk, "MAKEENV=env MAKEOBJDIRPREFIX=$(MAKEOBJDIRPREFIX)\n");
+ fprintf(outmk, "CRUNCHMAKE=$(MAKEENV) $(MAKE)\n");
+ } else {
+ fprintf(outmk, "CRUNCHMAKE=$(MAKE)\n");
+ }
+
+ if (buildopts) {
+ fprintf(outmk, "BUILDOPTS+=");
+ output_strlst(outmk, buildopts);
+ }
+
+ fprintf(outmk, "CRUNCHED_OBJS=");
+ for (p = progs; p != NULL; p = p->next)
+ fprintf(outmk, " %s.lo", p->name);
+ fprintf(outmk, "\n");
+
+ fprintf(outmk, "SUBMAKE_TARGETS=");
+ for (p = progs; p != NULL; p = p->next)
+ fprintf(outmk, " %s_make", p->ident);
+ fprintf(outmk, "\nSUBCLEAN_TARGETS=");
+ for (p = progs; p != NULL; p = p->next)
+ fprintf(outmk, " %s_clean", p->ident);
+ fprintf(outmk, "\n\n");
+
+ fprintf(outmk, "all: objs exe\nobjs: $(SUBMAKE_TARGETS)\n");
+ fprintf(outmk, "exe: %s\n", execfname);
+ fprintf(outmk, "%s: %s.o $(CRUNCHED_OBJS) $(SUBMAKE_TARGETS)\n", execfname, execfname);
+ fprintf(outmk, ".if defined(LIBS_SO) && !empty(LIBS_SO)\n");
+ fprintf(outmk, "\t$(CC) -o %s %s.o $(CRUNCHED_OBJS) \\\n",
+ execfname, execfname);
+ fprintf(outmk, "\t\t-Xlinker -Bstatic $(LIBS) \\\n");
+ fprintf(outmk, "\t\t-Xlinker -Bdynamic $(LIBS_SO)\n");
+ fprintf(outmk, ".else\n");
+ fprintf(outmk, "\t$(CC) -static -o %s %s.o $(CRUNCHED_OBJS) $(LIBS)\n",
+ execfname, execfname);
+ fprintf(outmk, ".endif\n");
+ fprintf(outmk, "\t$(STRIPBIN) %s\n", execfname);
+ fprintf(outmk, "realclean: clean subclean\n");
+ fprintf(outmk, "clean:\n\trm -f %s *.lo *.o *_stub.c\n", execfname);
+ fprintf(outmk, "subclean: $(SUBCLEAN_TARGETS)\n");
+}
+
+
+void
+prog_makefile_rules(FILE *outmk, prog_t *p)
+{
+ strlst_t *lst;
+
+ fprintf(outmk, "\n# -------- %s\n\n", p->name);
+
+ fprintf(outmk, "%s_OBJDIR=", p->ident);
+ if (p->objdir)
+ fprintf(outmk, "%s", p->objdir);
+ else
+ fprintf(outmk, "$(MAKEOBJDIRPREFIX)/$(%s_REALSRCDIR)\n",
+ p->ident);
+ fprintf(outmk, "\n");
+
+ fprintf(outmk, "%s_OBJPATHS=", p->ident);
+ if (p->objpaths)
+ output_strlst(outmk, p->objpaths);
+ else {
+ for (lst = p->objs; lst != NULL; lst = lst->next) {
+ fprintf(outmk, " $(%s_OBJDIR)/%s", p->ident, lst->str);
+ }
+ fprintf(outmk, "\n");
+ }
+
+ if (p->srcdir && p->objs) {
+ fprintf(outmk, "%s_SRCDIR=%s\n", p->ident, p->srcdir);
+ fprintf(outmk, "%s_REALSRCDIR=%s\n", p->ident, p->realsrcdir);
+
+ fprintf(outmk, "%s_OBJS=", p->ident);
+ output_strlst(outmk, p->objs);
+ if (p->buildopts != NULL) {
+ fprintf(outmk, "%s_OPTS+=", p->ident);
+ output_strlst(outmk, p->buildopts);
+ }
+#if 0
+ fprintf(outmk, "$(%s_OBJPATHS): %s_make\n\n", p->ident, p->ident);
+#endif
+ fprintf(outmk, "%s_make:\n", p->ident);
+ fprintf(outmk, "\t(cd $(%s_SRCDIR) && ", p->ident);
+ if (makeobj)
+ fprintf(outmk, "$(CRUNCHMAKE) obj && ");
+ fprintf(outmk, "\\\n");
+ fprintf(outmk, "\t\t$(CRUNCHMAKE) $(BUILDOPTS) $(%s_OPTS) depend &&",
+ p->ident);
+ fprintf(outmk, "\\\n");
+ fprintf(outmk, "\t\t$(CRUNCHMAKE) $(BUILDOPTS) $(%s_OPTS) "
+ "$(%s_OBJS))",
+ p->ident, p->ident);
+ fprintf(outmk, "\n");
+ fprintf(outmk, "%s_clean:\n", p->ident);
+ fprintf(outmk, "\t(cd $(%s_SRCDIR) && $(CRUNCHMAKE) $(BUILDOPTS) clean cleandepend)\n\n",
+ p->ident);
+ } else {
+ fprintf(outmk, "%s_make:\n", p->ident);
+ fprintf(outmk, "\t@echo \"** cannot make objs for %s\"\n\n",
+ p->name);
+ }
+
+ if (p->libs) {
+ fprintf(outmk, "%s_LIBS=", p->ident);
+ output_strlst(outmk, p->libs);
+ }
+
+ fprintf(outmk, "%s_stub.c:\n", p->name);
+ fprintf(outmk, "\techo \""
+ "int _crunched_%s_stub(int argc, char **argv, char **envp)"
+ "{return main(argc,argv,envp);}\" >%s_stub.c\n",
+ p->ident, p->name);
+ fprintf(outmk, "%s.lo: %s_stub.o $(%s_OBJPATHS)",
+ p->name, p->name, p->ident);
+ if (p->libs)
+ fprintf(outmk, " $(%s_LIBS)", p->ident);
+
+ fprintf(outmk, "\n");
+ fprintf(outmk, "\t$(CC) -nostdlib -Wl,-dc -r -o %s.lo %s_stub.o $(%s_OBJPATHS)",
+ p->name, p->name, p->ident);
+ if (p->libs)
+ fprintf(outmk, " $(%s_LIBS)", p->ident);
+ fprintf(outmk, "\n");
+ fprintf(outmk, "\tcrunchide -k _crunched_%s_stub ", p->ident);
+ for (lst = p->keeplist; lst != NULL; lst = lst->next)
+ fprintf(outmk, "-k _%s ", lst->str);
+ fprintf(outmk, "%s.lo\n", p->name);
+}
+
+void
+output_strlst(FILE *outf, strlst_t *lst)
+{
+ for (; lst != NULL; lst = lst->next)
+ if ( strlen(lst->str) )
+ fprintf(outf, " %s", lst->str);
+ fprintf(outf, "\n");
+}
+
+
+/*
+ * ========================================================================
+ * general library routines
+ *
+ */
+
+void
+status(const char *str)
+{
+ static int lastlen = 0;
+ int len, spaces;
+
+ if (!verbose)
+ return;
+
+ len = strlen(str);
+ spaces = lastlen - len;
+ if (spaces < 1)
+ spaces = 1;
+
+ fprintf(stderr, " [%s]%*.*s\r", str, spaces, spaces, " ");
+ fflush(stderr);
+ lastlen = len;
+}
+
+
+void
+out_of_memory(void)
+{
+ err(1, "%s: %d: out of memory, stopping", infilename, linenum);
+}
+
+
+void
+add_string(strlst_t **listp, char *str)
+{
+ strlst_t *p1, *p2;
+
+ /* add to end, but be smart about dups */
+
+ for (p1 = NULL, p2 = *listp; p2 != NULL; p1 = p2, p2 = p2->next)
+ if (!strcmp(p2->str, str))
+ return;
+
+ p2 = malloc(sizeof(strlst_t));
+ if (p2) {
+ p2->next = NULL;
+ p2->str = strdup(str);
+ }
+ if (!p2 || !p2->str)
+ out_of_memory();
+
+ if (p1 == NULL)
+ *listp = p2;
+ else
+ p1->next = p2;
+}
+
+int
+subtract_strlst(strlst_t **lista, strlst_t **listb)
+{
+ int subtract_count = 0;
+ strlst_t *p1;
+ for (p1 = *listb; p1 != NULL; p1 = p1->next)
+ if ( in_list(lista, p1->str) ) {
+ warnx("Will compile library `%s' dynamically", p1->str);
+ strcat(p1->str, "");
+ subtract_count++;
+ }
+ return subtract_count;
+}
+
+int
+in_list(strlst_t **listp, char *str)
+{
+ strlst_t *p1;
+ for (p1 = *listp; p1 != NULL; p1 = p1->next)
+ if (!strcmp(p1->str, str))
+ return 1;
+ return 0;
+}
+
+int
+is_dir(const char *pathname)
+{
+ struct stat buf;
+
+ if (stat(pathname, &buf) == -1)
+ return 0;
+
+ return S_ISDIR(buf.st_mode);
+}
+
+int
+is_nonempty_file(const char *pathname)
+{
+ struct stat buf;
+
+ if (stat(pathname, &buf) == -1)
+ return 0;
+
+ return S_ISREG(buf.st_mode) && buf.st_size > 0;
+}
diff --git a/usr.sbin/crunch/crunchgen/mkskel.sh b/usr.sbin/crunch/crunchgen/mkskel.sh
new file mode 100644
index 0000000..fd53d78
--- /dev/null
+++ b/usr.sbin/crunch/crunchgen/mkskel.sh
@@ -0,0 +1,15 @@
+#! /bin/sh
+# idea and sed lines taken straight from flex
+
+cat <<!EOF
+/* File created via mkskel.sh */
+
+char *crunched_skel[] = {
+!EOF
+
+sed 's/\\/&&/g' $* | sed 's/"/\\"/g' | sed 's/.*/ "&",/'
+
+cat <<!EOF
+ 0
+};
+!EOF
diff --git a/usr.sbin/crunch/crunchide/Makefile b/usr.sbin/crunch/crunchide/Makefile
new file mode 100644
index 0000000..467b0b6
--- /dev/null
+++ b/usr.sbin/crunch/crunchide/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PROG= crunchide
+SRCS= crunchide.c exec_elf32.c exec_elf64.c
+
+CFLAGS+=-DNLIST_ELF32 -DNLIST_ELF64
+exec_elf64.o: exec_elf32.c
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/crunch/crunchide/Makefile.depend b/usr.sbin/crunch/crunchide/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/crunch/crunchide/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/crunch/crunchide/crunchide.1 b/usr.sbin/crunch/crunchide/crunchide.1
new file mode 100644
index 0000000..4bdda5f
--- /dev/null
+++ b/usr.sbin/crunch/crunchide/crunchide.1
@@ -0,0 +1,92 @@
+.\"
+.\" Copyright (c) 1994 University of Maryland
+.\" All Rights Reserved.
+.\"
+.\" Permission to use, copy, modify, distribute, and sell this software and its
+.\" documentation for any purpose is hereby granted without fee, provided that
+.\" the above copyright notice appear in all copies and that both that
+.\" copyright notice and this permission notice appear in supporting
+.\" documentation, and that the name of U.M. not be used in advertising or
+.\" publicity pertaining to distribution of the software without specific,
+.\" written prior permission. U.M. makes no representations about the
+.\" suitability of this software for any purpose. It is provided "as is"
+.\" without express or implied warranty.
+.\"
+.\" U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
+.\" BE LIABLE FOR ANY SPECIAL, 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.
+.\"
+.\" Author: James da Silva, Systems Design and Analysis Group
+.\" Computer Science Department
+.\" University of Maryland at College Park
+.\" $FreeBSD$
+.\"
+.Dd June 14, 1994
+.Dt CRUNCHIDE 1
+.Os
+.Sh NAME
+.Nm crunchide
+.Nd hides symbol names from ld, for crunching programs together
+.Sh SYNOPSIS
+.Nm
+.Op Fl f Ar keep-list-file
+.Op Fl k Ar keep-symbol
+.Op Ar object-file ...
+.Sh DESCRIPTION
+The
+.Nm
+utility hides the global symbols of
+.Ar object-file
+such that they are ignored by subsequent runs of the linker,
+.Xr ld 1 .
+Some symbols may be left visible via the
+.Fl k Ar keep-symbol
+and
+.Fl f Ar keep-list-file
+options.
+The
+.Ar keep-list-file
+must contain a list of symbols to keep visible, one symbol per line.
+The names given by
+.Ar keep-symbol
+or in
+.Ar keep-list-file
+should be C names.
+For example,
+to keep the C function
+.Dq foo
+visible, the option
+.Dq -k foo
+should be used.
+.Pp
+The
+.Nm
+utility is designed as a companion program for
+.Xr crunchgen 1 ,
+which automates the process of creating crunched binaries from
+multiple component programs.
+.Sh SEE ALSO
+.Xr crunchgen 1 ,
+.Xr ld 1
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm crunch
+utility was written by
+.An James da Silva Aq Mt jds@cs.umd.edu .
+.Pp
+Copyright (c) 1994 University of Maryland.
+All Rights Reserved.
+.Pp
+.An Chris Demetriou Aq Mt cgd@netbsd.org
+reorganized
+.Nm
+so that it supported multiple object formats, and added
+ELF object support and ECOFF object recognition.
+.Pp
+Copyright (c) 1997
+.An Christopher G. Demetriou .
+All Rights Reserved.
diff --git a/usr.sbin/crunch/crunchide/crunchide.c b/usr.sbin/crunch/crunchide/crunchide.c
new file mode 100644
index 0000000..8665a14
--- /dev/null
+++ b/usr.sbin/crunch/crunchide/crunchide.c
@@ -0,0 +1,267 @@
+/* $NetBSD: crunchide.c,v 1.8 1997/11/01 06:51:45 lukem Exp $ */
+/*
+ * Copyright (c) 1997 Christopher G. Demetriou. All rights reserved.
+ * Copyright (c) 1994 University of Maryland
+ * All Rights Reserved.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of U.M. not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. U.M. makes no representations about the
+ * suitability of this software for any purpose. It is provided "as is"
+ * without express or implied warranty.
+ *
+ * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
+ * BE LIABLE FOR ANY SPECIAL, 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.
+ *
+ * Author: James da Silva, Systems Design and Analysis Group
+ * Computer Science Department
+ * University of Maryland at College Park
+ */
+/*
+ * crunchide.c - tiptoes through an a.out symbol table, hiding all defined
+ * global symbols. Allows the user to supply a "keep list" of symbols
+ * that are not to be hidden. This program relies on the use of the
+ * linker's -dc flag to actually put global bss data into the file's
+ * bss segment (rather than leaving it as undefined "common" data).
+ *
+ * The point of all this is to allow multiple programs to be linked
+ * together without getting multiple-defined errors.
+ *
+ * For example, consider a program "foo.c". It can be linked with a
+ * small stub routine, called "foostub.c", eg:
+ * int foo_main(int argc, char **argv){ return main(argc, argv); }
+ * like so:
+ * cc -c foo.c foostub.c
+ * ld -dc -r foo.o foostub.o -o foo.combined.o
+ * crunchide -k _foo_main foo.combined.o
+ * at this point, foo.combined.o can be linked with another program
+ * and invoked with "foo_main(argc, argv)". foo's main() and any
+ * other globals are hidden and will not conflict with other symbols.
+ *
+ * TODO:
+ * - resolve the theoretical hanging reloc problem (see check_reloc()
+ * below). I have yet to see this problem actually occur in any real
+ * program. In what cases will gcc/gas generate code that needs a
+ * relative reloc from a global symbol, other than PIC? The
+ * solution is to not hide the symbol from the linker in this case,
+ * but to generate some random name for it so that it doesn't link
+ * with anything but holds the place for the reloc.
+ * - arrange that all the BSS segments start at the same address, so
+ * that the final crunched binary BSS size is the max of all the
+ * component programs' BSS sizes, rather than their sum.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: crunchide.c,v 1.8 1997/11/01 06:51:45 lukem Exp $");
+#endif
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <a.out.h>
+
+#include "extern.h"
+
+char *pname = "crunchide";
+
+void usage(void);
+
+void add_to_keep_list(char *symbol);
+void add_file_to_keep_list(char *filename);
+
+int hide_syms(const char *filename);
+
+int verbose;
+
+int main(int, char *[]);
+
+int
+main(int argc, char **argv)
+{
+ int ch, errors;
+
+ if(argc > 0) pname = argv[0];
+
+ while ((ch = getopt(argc, argv, "k:f:v")) != -1)
+ switch(ch) {
+ case 'k':
+ add_to_keep_list(optarg);
+ break;
+ case 'f':
+ add_file_to_keep_list(optarg);
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ default:
+ usage();
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if(argc == 0) usage();
+
+ errors = 0;
+ while(argc) {
+ if (hide_syms(*argv))
+ errors = 1;
+ argc--, argv++;
+ }
+
+ return errors;
+}
+
+void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: %s [-k <symbol-name>] [-f <keep-list-file>] <files> ...\n",
+ pname);
+ exit(1);
+}
+
+/* ---------------------------- */
+
+struct keep {
+ struct keep *next;
+ char *sym;
+} *keep_list;
+
+void
+add_to_keep_list(char *symbol)
+{
+ struct keep *newp, *prevp, *curp;
+ int cmp;
+
+ cmp = 0;
+
+ for(curp = keep_list, prevp = NULL; curp; prevp = curp, curp = curp->next)
+ if((cmp = strcmp(symbol, curp->sym)) <= 0) break;
+
+ if(curp && cmp == 0)
+ return; /* already in table */
+
+ newp = (struct keep *) malloc(sizeof(struct keep));
+ if(newp) newp->sym = strdup(symbol);
+ if(newp == NULL || newp->sym == NULL) {
+ fprintf(stderr, "%s: out of memory for keep list\n", pname);
+ exit(1);
+ }
+
+ newp->next = curp;
+ if(prevp) prevp->next = newp;
+ else keep_list = newp;
+}
+
+int
+in_keep_list(const char *symbol)
+{
+ struct keep *curp;
+ int cmp;
+
+ cmp = 0;
+
+ for(curp = keep_list; curp; curp = curp->next)
+ if((cmp = strcmp(symbol, curp->sym)) <= 0) break;
+
+ return curp && cmp == 0;
+}
+
+void
+add_file_to_keep_list(char *filename)
+{
+ FILE *keepf;
+ char symbol[1024];
+ int len;
+
+ if((keepf = fopen(filename, "r")) == NULL) {
+ perror(filename);
+ usage();
+ }
+
+ while(fgets(symbol, sizeof(symbol), keepf)) {
+ len = strlen(symbol);
+ if(len && symbol[len-1] == '\n')
+ symbol[len-1] = '\0';
+
+ add_to_keep_list(symbol);
+ }
+ fclose(keepf);
+}
+
+/* ---------------------------- */
+
+struct {
+ const char *name;
+ int (*check)(int, const char *); /* 1 if match, zero if not */
+ int (*hide)(int, const char *); /* non-zero if error */
+} exec_formats[] = {
+#ifdef NLIST_ELF32
+ { "ELF32", check_elf32, hide_elf32, },
+#endif
+#ifdef NLIST_ELF64
+ { "ELF64", check_elf64, hide_elf64, },
+#endif
+};
+
+int
+hide_syms(const char *filename)
+{
+ int fd, i, n, rv;
+
+ fd = open(filename, O_RDWR, 0);
+ if (fd == -1) {
+ perror(filename);
+ return 1;
+ }
+
+ rv = 0;
+
+ n = sizeof exec_formats / sizeof exec_formats[0];
+ for (i = 0; i < n; i++) {
+ if (lseek(fd, 0, SEEK_SET) != 0) {
+ perror(filename);
+ goto err;
+ }
+ if ((*exec_formats[i].check)(fd, filename) != 0)
+ break;
+ }
+ if (i == n) {
+ fprintf(stderr, "%s: unknown executable format\n", filename);
+ goto err;
+ }
+
+ if (verbose)
+ fprintf(stderr, "%s is an %s binary\n", filename,
+ exec_formats[i].name);
+
+ if (lseek(fd, 0, SEEK_SET) != 0) {
+ perror(filename);
+ goto err;
+ }
+ rv = (*exec_formats[i].hide)(fd, filename);
+
+out:
+ close (fd);
+ return (rv);
+
+err:
+ rv = 1;
+ goto out;
+}
diff --git a/usr.sbin/crunch/crunchide/exec_elf32.c b/usr.sbin/crunch/crunchide/exec_elf32.c
new file mode 100644
index 0000000..6d94429
--- /dev/null
+++ b/usr.sbin/crunch/crunchide/exec_elf32.c
@@ -0,0 +1,491 @@
+/*
+ * Copyright (c) 1997 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
+ * for the NetBSD Project.
+ * 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>
+#ifndef lint
+#if 0
+__RCSID("$NetBSD: exec_elf32.c,v 1.6 1999/09/20 04:12:16 christos Exp $");
+#endif
+#endif
+__FBSDID("$FreeBSD$");
+
+#ifndef ELFSIZE
+#define ELFSIZE 32
+#endif
+
+#include <sys/types.h>
+#include <sys/endian.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+#if (defined(NLIST_ELF32) && (ELFSIZE == 32)) || \
+ (defined(NLIST_ELF64) && (ELFSIZE == 64))
+
+#define __ELF_WORD_SIZE ELFSIZE
+#if (ELFSIZE == 32)
+#include <sys/elf32.h>
+#define xewtoh(x) ((data == ELFDATA2MSB) ? be32toh(x) : le32toh(x))
+#define htoxew(x) ((data == ELFDATA2MSB) ? htobe32(x) : htole32(x))
+#define wewtoh(x) ((data == ELFDATA2MSB) ? be32toh(x) : le32toh(x))
+#define htowew(x) ((data == ELFDATA2MSB) ? htobe32(x) : htole32(x))
+#elif (ELFSIZE == 64)
+#include <sys/elf64.h>
+#define xewtoh(x) ((data == ELFDATA2MSB) ? be64toh(x) : le64toh(x))
+#define htoxew(x) ((data == ELFDATA2MSB) ? htobe64(x) : htole64(x))
+/* elf64 Elf64_Word are 32 bits */
+#define wewtoh(x) ((data == ELFDATA2MSB) ? be32toh(x) : le32toh(x))
+#define htowew(x) ((data == ELFDATA2MSB) ? htobe32(x) : htole32(x))
+#endif
+#include <sys/elf_generic.h>
+
+#define CONCAT(x,y) __CONCAT(x,y)
+#define ELFNAME(x) CONCAT(elf,CONCAT(ELFSIZE,CONCAT(_,x)))
+#define ELFNAME2(x,y) CONCAT(x,CONCAT(_elf,CONCAT(ELFSIZE,CONCAT(_,y))))
+#define ELFNAMEEND(x) CONCAT(x,CONCAT(_elf,ELFSIZE))
+#define ELFDEFNNAME(x) CONCAT(ELF,CONCAT(ELFSIZE,CONCAT(_,x)))
+#ifndef ELFCLASS
+#define ELFCLASS CONCAT(ELFCLASS,ELFSIZE)
+#endif
+
+#define xe16toh(x) ((data == ELFDATA2MSB) ? be16toh(x) : le16toh(x))
+#define xe32toh(x) ((data == ELFDATA2MSB) ? be32toh(x) : le32toh(x))
+#define htoxe32(x) ((data == ELFDATA2MSB) ? htobe32(x) : htole32(x))
+
+struct shlayout {
+ Elf_Shdr *shdr;
+ void *bufp;
+};
+
+static ssize_t
+xreadatoff(int fd, void *buf, off_t off, size_t size, const char *fn)
+{
+ ssize_t rv;
+
+ if (lseek(fd, off, SEEK_SET) != off) {
+ perror(fn);
+ return -1;
+ }
+ if ((size_t)(rv = read(fd, buf, size)) != size) {
+ fprintf(stderr, "%s: read error: %s\n", fn,
+ rv == -1 ? strerror(errno) : "short read");
+ return -1;
+ }
+ return size;
+}
+
+static ssize_t
+xwriteatoff(int fd, void *buf, off_t off, size_t size, const char *fn)
+{
+ ssize_t rv;
+
+ if (lseek(fd, off, SEEK_SET) != off) {
+ perror(fn);
+ return -1;
+ }
+ if ((size_t)(rv = write(fd, buf, size)) != size) {
+ fprintf(stderr, "%s: write error: %s\n", fn,
+ rv == -1 ? strerror(errno) : "short write");
+ return -1;
+ }
+ return size;
+}
+
+static void *
+xmalloc(size_t size, const char *fn, const char *use)
+{
+ void *rv;
+
+ rv = malloc(size);
+ if (rv == NULL)
+ fprintf(stderr, "%s: out of memory (allocating for %s)\n",
+ fn, use);
+ return (rv);
+}
+
+static void *
+xrealloc(void *ptr, size_t size, const char *fn, const char *use)
+{
+ void *rv;
+
+ rv = realloc(ptr, size);
+ if (rv == NULL) {
+ free(ptr);
+ fprintf(stderr, "%s: out of memory (reallocating for %s)\n",
+ fn, use);
+ }
+ return (rv);
+}
+
+int
+ELFNAMEEND(check)(int fd, const char *fn)
+{
+ Elf_Ehdr eh;
+ struct stat sb;
+ unsigned char data;
+
+ /*
+ * Check the header to maek sure it's an ELF file (of the
+ * appropriate size).
+ */
+ if (fstat(fd, &sb) == -1)
+ return 0;
+ if (sb.st_size < (off_t)(sizeof eh))
+ return 0;
+ if (read(fd, &eh, sizeof eh) != sizeof eh)
+ return 0;
+
+ if (IS_ELF(eh) == 0 || eh.e_ident[EI_CLASS] != ELFCLASS)
+ return 0;
+
+ data = eh.e_ident[EI_DATA];
+
+ switch (xe16toh(eh.e_machine)) {
+ case EM_386: break;
+ case EM_ALPHA: break;
+#ifndef EM_AARCH64
+#define EM_AARCH64 183
+#endif
+ case EM_AARCH64: break;
+ case EM_ARM: break;
+ case EM_MIPS: break;
+ case /* EM_MIPS_RS3_LE */ EM_MIPS_RS4_BE: break;
+ case EM_PPC: break;
+ case EM_PPC64: break;
+#ifndef EM_RISCV
+#define EM_RISCV 243
+#endif
+ case EM_RISCV: break;
+ case EM_SPARCV9: break;
+ case EM_X86_64: break;
+/* ELFDEFNNAME(MACHDEP_ID_CASES) */
+
+ default:
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * This function 'hides' (some of) ELF executable file's symbols.
+ * It hides them by renaming them to "_$$hide$$ <filename> <symbolname>".
+ * Symbols in the global keep list, or which are marked as being undefined,
+ * are left alone.
+ *
+ * An old version of this code shuffled various tables around, turning
+ * global symbols to be hidden into local symbols. That lost on the
+ * mips, because CALL16 relocs must reference global symbols, and, if
+ * those symbols were being hidden, they were no longer global.
+ *
+ * The new renaming behaviour doesn't take global symbols out of the
+ * namespace. However, it's ... unlikely that there will ever be
+ * any collisions in practice because of the new method.
+ */
+int
+ELFNAMEEND(hide)(int fd, const char *fn)
+{
+ Elf_Ehdr ehdr;
+ struct shlayout *layoutp = NULL;
+ Elf_Shdr *shdrp = NULL, *symtabshdr, *strtabshdr, *shstrtabshdr;
+ Elf_Shdr shdrshdr;
+ Elf_Sym *symtabp = NULL;
+ char *shstrtabp = NULL, *strtabp = NULL;
+ Elf_Size nsyms, ewi;
+ Elf_Off off;
+ ssize_t shdrsize;
+ int rv, i, weird, l, m, r, strtabidx;
+ size_t nstrtab_size, nstrtab_nextoff, fn_size, size;
+ char *nstrtabp = NULL;
+ unsigned char data;
+ const char *weirdreason = NULL;
+ void *buf;
+ Elf_Half shnum;
+
+ rv = 0;
+ if (xreadatoff(fd, &ehdr, 0, sizeof ehdr, fn) != sizeof ehdr)
+ goto bad;
+
+ data = ehdr.e_ident[EI_DATA];
+ shnum = xe16toh(ehdr.e_shnum);
+
+ shdrsize = shnum * xe16toh(ehdr.e_shentsize);
+ if ((shdrp = xmalloc(shdrsize, fn, "section header table")) == NULL)
+ goto bad;
+ if (xreadatoff(fd, shdrp, xewtoh(ehdr.e_shoff), shdrsize, fn) !=
+ shdrsize)
+ goto bad;
+
+ symtabshdr = strtabshdr = shstrtabshdr = NULL;
+ weird = 0;
+ for (i = 0; i < shnum; i++) {
+ switch (xe32toh(shdrp[i].sh_type)) {
+ case SHT_SYMTAB:
+ if (symtabshdr != NULL) {
+ weird = 1;
+ weirdreason = "multiple symbol tables";
+ }
+ symtabshdr = &shdrp[i];
+ strtabshdr = &shdrp[xe32toh(shdrp[i].sh_link)];
+ break;
+ case SHT_STRTAB:
+ if (i == xe16toh(ehdr.e_shstrndx))
+ shstrtabshdr = &shdrp[i];
+ break;
+ }
+ }
+ if (symtabshdr == NULL)
+ goto out;
+ if (strtabshdr == NULL) {
+ weird = 1;
+ weirdreason = "string table does not exist";
+ }
+ if (shstrtabshdr == NULL) {
+ weird = 1;
+ weirdreason = "section header string table does not exist";
+ }
+ if (weirdreason == NULL)
+ weirdreason = "unsupported";
+ if (weird) {
+ fprintf(stderr, "%s: weird executable (%s)\n", fn, weirdreason);
+ goto bad;
+ }
+
+ /*
+ * sort section layout table by offset
+ */
+ layoutp = xmalloc((shnum + 1) * sizeof(struct shlayout),
+ fn, "layout table");
+ if (layoutp == NULL)
+ goto bad;
+
+ /* add a pseudo entry to represent the section header table */
+ shdrshdr.sh_offset = ehdr.e_shoff;
+ shdrshdr.sh_size = htoxew(shdrsize);
+ shdrshdr.sh_addralign = htoxew(ELFSIZE / 8);
+ layoutp[shnum].shdr = &shdrshdr;
+
+ /* insert and sort normal section headers */
+ for (i = shnum; i-- != 0;) {
+ l = i + 1;
+ r = shnum;
+ while (l <= r) {
+ m = ( l + r) / 2;
+ if (xewtoh(shdrp[i].sh_offset) >
+ xewtoh(layoutp[m].shdr->sh_offset))
+ l = m + 1;
+ else
+ r = m - 1;
+ }
+
+ if (r != i) {
+ memmove(&layoutp[i], &layoutp[i + 1],
+ sizeof(struct shlayout) * (r - i));
+ }
+
+ layoutp[r].shdr = &shdrp[i];
+ layoutp[r].bufp = NULL;
+ }
+ ++shnum;
+
+ /*
+ * load up everything we need
+ */
+
+ /* load section string table for debug use */
+ if ((size = xewtoh(shstrtabshdr->sh_size)) == 0)
+ goto bad;
+ if ((shstrtabp = xmalloc(size, fn, "section string table")) == NULL)
+ goto bad;
+ if ((size_t)xreadatoff(fd, shstrtabp, xewtoh(shstrtabshdr->sh_offset),
+ size, fn) != size)
+ goto bad;
+ if (shstrtabp[size - 1] != '\0')
+ goto bad;
+
+ /* we need symtab, strtab, and everything behind strtab */
+ strtabidx = INT_MAX;
+ for (i = 0; i < shnum; i++) {
+ if (layoutp[i].shdr == &shdrshdr) {
+ /* not load section header again */
+ layoutp[i].bufp = shdrp;
+ continue;
+ }
+ if (layoutp[i].shdr == shstrtabshdr) {
+ /* not load section string table again */
+ layoutp[i].bufp = shstrtabp;
+ continue;
+ }
+
+ if (layoutp[i].shdr == strtabshdr)
+ strtabidx = i;
+ if (layoutp[i].shdr == symtabshdr || i >= strtabidx) {
+ off = xewtoh(layoutp[i].shdr->sh_offset);
+ if ((size = xewtoh(layoutp[i].shdr->sh_size)) == 0)
+ goto bad;
+ layoutp[i].bufp = xmalloc(size, fn,
+ shstrtabp + xewtoh(layoutp[i].shdr->sh_name));
+ if (layoutp[i].bufp == NULL)
+ goto bad;
+ if ((size_t)xreadatoff(fd, layoutp[i].bufp, off, size, fn) !=
+ size)
+ goto bad;
+
+ /* set symbol table and string table */
+ if (layoutp[i].shdr == symtabshdr) {
+ symtabp = layoutp[i].bufp;
+ } else if (layoutp[i].shdr == strtabshdr) {
+ strtabp = layoutp[i].bufp;
+ if (strtabp[size - 1] != '\0')
+ goto bad;
+ }
+ }
+ }
+
+ nstrtab_size = 256;
+ nstrtabp = xmalloc(nstrtab_size, fn, "new string table");
+ if (nstrtabp == NULL)
+ goto bad;
+ nstrtab_nextoff = 0;
+
+ fn_size = strlen(fn);
+
+ /* Prepare data structures for symbol movement. */
+ nsyms = xewtoh(symtabshdr->sh_size) / xewtoh(symtabshdr->sh_entsize);
+
+ /* move symbols, making them local */
+ for (ewi = 0; ewi < nsyms; ewi++) {
+ Elf_Sym *sp = &symtabp[ewi];
+ const char *symname = strtabp + xe32toh(sp->st_name);
+ size_t newent_len;
+ /*
+ * make sure there's size for the next entry, even if it's
+ * as large as it can be.
+ *
+ * "_$$hide$$ <filename> <symname><NUL>" ->
+ * 9 + 3 + sizes of fn and sym name
+ */
+ while ((nstrtab_size - nstrtab_nextoff) <
+ strlen(symname) + fn_size + 12) {
+ nstrtab_size *= 2;
+ nstrtabp = xrealloc(nstrtabp, nstrtab_size, fn,
+ "new string table");
+ if (nstrtabp == NULL)
+ goto bad;
+ }
+
+ sp->st_name = htowew(nstrtab_nextoff);
+
+ /* if it's a keeper or is undefined, don't rename it. */
+ if (in_keep_list(symname) ||
+ (xe16toh(sp->st_shndx) == SHN_UNDEF)) {
+ newent_len = sprintf(nstrtabp + nstrtab_nextoff,
+ "%s", symname) + 1;
+ } else {
+ newent_len = sprintf(nstrtabp + nstrtab_nextoff,
+ "_$$hide$$ %s %s", fn, symname) + 1;
+ }
+ nstrtab_nextoff += newent_len;
+ }
+ strtabshdr->sh_size = htoxew(nstrtab_nextoff);
+
+ /*
+ * update section header table in ascending order of offset
+ */
+ for (i = strtabidx + 1; i < shnum; i++) {
+ Elf_Off off, align;
+ off = xewtoh(layoutp[i - 1].shdr->sh_offset) +
+ xewtoh(layoutp[i - 1].shdr->sh_size);
+ align = xewtoh(layoutp[i].shdr->sh_addralign);
+ off = (off + (align - 1)) & ~(align - 1);
+ layoutp[i].shdr->sh_offset = htoxew(off);
+ }
+
+ /*
+ * write data to the file in descending order of offset
+ */
+ for (i = shnum; i-- != 0;) {
+ if (layoutp[i].shdr == strtabshdr) {
+ /* new string table */
+ buf = nstrtabp;
+ } else
+ buf = layoutp[i].bufp;
+
+ if (layoutp[i].shdr == &shdrshdr ||
+ layoutp[i].shdr == symtabshdr || i >= strtabidx) {
+ if (buf == NULL)
+ goto bad;
+
+ /*
+ * update the offset of section header table in elf
+ * header if needed.
+ */
+ if (layoutp[i].shdr == &shdrshdr &&
+ ehdr.e_shoff != shdrshdr.sh_offset) {
+ ehdr.e_shoff = shdrshdr.sh_offset;
+ off = offsetof(Elf_Ehdr, e_shoff);
+ size = sizeof(Elf_Off);
+ if ((size_t)xwriteatoff(fd, &ehdr.e_shoff, off, size,
+ fn) != size)
+ goto bad;
+ }
+
+ off = xewtoh(layoutp[i].shdr->sh_offset);
+ size = xewtoh(layoutp[i].shdr->sh_size);
+ if ((size_t)xwriteatoff(fd, buf, off, size, fn) != size)
+ goto bad;
+ }
+ }
+
+out:
+ if (layoutp != NULL) {
+ for (i = 0; i < shnum; i++) {
+ if (layoutp[i].bufp != NULL)
+ free(layoutp[i].bufp);
+ }
+ free(layoutp);
+ }
+ free(nstrtabp);
+ return (rv);
+
+bad:
+ rv = 1;
+ goto out;
+}
+
+#endif /* include this size of ELF */
diff --git a/usr.sbin/crunch/crunchide/exec_elf64.c b/usr.sbin/crunch/crunchide/exec_elf64.c
new file mode 100644
index 0000000..962cc07
--- /dev/null
+++ b/usr.sbin/crunch/crunchide/exec_elf64.c
@@ -0,0 +1,40 @@
+/* $NetBSD: exec_elf64.c,v 1.2 1997/08/02 21:30:19 perry Exp $ */
+
+/*
+ * Copyright (c) 1997 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
+ * for the NetBSD Project.
+ * 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>
+#ifndef lint
+__IDSTRING(elf64rcsid, "$NetBSD: exec_elf64.c,v 1.2 1997/08/02 21:30:19 perry Exp $");
+#endif
+
+#define ELFSIZE 64
+
+#include "exec_elf32.c"
diff --git a/usr.sbin/crunch/crunchide/extern.h b/usr.sbin/crunch/crunchide/extern.h
new file mode 100644
index 0000000..1e7809a
--- /dev/null
+++ b/usr.sbin/crunch/crunchide/extern.h
@@ -0,0 +1,43 @@
+/* $NetBSD: extern.h,v 1.5 1998/05/06 13:16:57 mycroft Exp $ */
+/* $FreeBSD$ */
+
+/*
+ * Copyright (c) 1997 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
+ * for the NetBSD Project.
+ * 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.
+ */
+
+#ifdef NLIST_ELF32
+int check_elf32(int, const char *);
+int hide_elf32(int, const char *);
+#endif
+#ifdef NLIST_ELF64
+int check_elf64(int, const char *);
+int hide_elf64(int, const char *);
+#endif
+
+int in_keep_list(const char *symbol);
diff --git a/usr.sbin/crunch/examples/Makefile b/usr.sbin/crunch/examples/Makefile
new file mode 100644
index 0000000..01b9242
--- /dev/null
+++ b/usr.sbin/crunch/examples/Makefile
@@ -0,0 +1,33 @@
+# $FreeBSD$
+
+CRUNCHED= fixit
+
+# below is boiler-plate to make $(CRUNCHED) from $(CRUNCHED).conf
+# I'd use PROG instead of CRUNCHED, but the system makefiles REALLY want
+# to build things in the normal way if you use PROG.
+
+CONF= $(CRUNCHED).conf
+
+OUTMK= $(CRUNCHED).mk
+OUTPUTS= $(OUTMK) $(CRUNCHED).c $(CRUNCHED).cache
+
+MAN=
+CLEANFILES+= $(CRUNCHED) *.o *.lo *.c *.mk *.cache
+CLEANDIRFILES+= $(OUTPUTS)
+
+all: $(CRUNCHED)
+exe: $(CRUNCHED)
+
+$(OUTPUTS): $(CONF)
+ MAKE=${MAKE} crunchgen ${.CURDIR}/$(CONF)
+
+$(CRUNCHED): $(OUTPUTS) submake
+
+submake:
+ ${MAKE} -f $(OUTMK)
+objs:
+ ${MAKE} -f $(OUTMK) objs
+cleandir:
+ rm -f $(CLEANDIRFILES)
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/crunch/examples/filesystem.conf b/usr.sbin/crunch/examples/filesystem.conf
new file mode 100644
index 0000000..1fb9385
--- /dev/null
+++ b/usr.sbin/crunch/examples/filesystem.conf
@@ -0,0 +1,31 @@
+# $FreeBSD$
+
+srcdirs /usr/src/bin /usr/src/sbin /usr/src/gnu/usr.bin /usr/src/usr.sbin
+srcdirs /usr/src/sbin/i386
+
+# /bin
+progs sh expr ls mkdir rm sync test
+ln test [
+
+# These are needed because of UN*X's idiotic way of indicating that something
+# is a login shell.
+ln sh -
+ln sh -sh
+
+# /sbin
+progs disklabel fdisk init mount newfs reboot umount
+ln reboot halt
+ln reboot fastboot
+ln reboot fasthalt
+
+
+# /usr/bin
+progs cpio gzip
+ln gzip gunzip
+ln gzip gzcat
+ln gzip zcat
+
+# /usr/sbin
+progs bad144
+
+libs -ll -ledit -ltermcap -lutil -lscrypt
diff --git a/usr.sbin/crunch/examples/fixit.conf b/usr.sbin/crunch/examples/fixit.conf
new file mode 100644
index 0000000..80a2346
--- /dev/null
+++ b/usr.sbin/crunch/examples/fixit.conf
@@ -0,0 +1,45 @@
+# fixit.conf - put in anything we think we might want on a fixit floppy
+
+# first, we list the source dirs that our programs reside in. These are
+# searched in order listed to find the dir containing each program.
+
+srcdirs /usr/src/bin /usr/src/sbin /usr/src/usr.bin /usr/src/usr.sbin
+srcdirs /usr/src/gnu/usr.bin /usr/src/usr.bin/vi
+srcdirs /usr/src/sbin/i386
+
+# second, we list all the programs we want to include in our crunched binary.
+# The order doesn't matter. Any program that needs hard links to it gets an
+# `ln' directive.
+
+# /bin stuff
+
+progs cat chmod cp date dd df echo ed expr hostname kill ln ls mkdir
+progs mt mv pwd rcp rm rmdir sh sleep stty sync test
+
+ln test [
+ln sh -sh # init invokes the shell this way
+
+# /sbin stuff
+
+progs badsect chown clri disklabel dump dmesg fdisk fsck ifconfig init
+progs mknod mount newfs ping reboot restore swapon umount
+ln dump rdump
+ln restore rrestore
+
+# /usr/bin stuff
+
+progs ftp rsh sed telnet rlogin common find
+ln common vi
+ln common view
+ln common ex
+
+# gnu stuff
+
+progs cpio gzip
+ln gzip gunzip
+ln gzip gzcat
+
+# finally, we specify the libraries to link in with our binary
+
+libs -lcrypt -ltelnet -lutil -ll
+libs -lcurses -ltermcap -ledit -lkvm
diff --git a/usr.sbin/crunch/examples/kcopy.conf b/usr.sbin/crunch/examples/kcopy.conf
new file mode 100644
index 0000000..3284e86
--- /dev/null
+++ b/usr.sbin/crunch/examples/kcopy.conf
@@ -0,0 +1,21 @@
+# $FreeBSD$
+
+srcdirs /usr/src/bin /usr/src/sbin
+
+# Programs from bin/
+progs sh cp echo test
+ln test [
+
+# These are needed because of UN*X's idiotic way of indicating that something
+# is a login shell.
+ln sh -
+ln sh -sh
+
+#
+# Programs from sbin/
+progs mount mount_cd9660 fsck init reboot umount
+ln reboot halt
+ln reboot fastboot
+ln reboot fasthalt
+
+libs -ll -ledit -ltermcap -lcompat -lutil -lscrypt
diff --git a/usr.sbin/crunch/examples/really-big.conf b/usr.sbin/crunch/examples/really-big.conf
new file mode 100644
index 0000000..922078a
--- /dev/null
+++ b/usr.sbin/crunch/examples/really-big.conf
@@ -0,0 +1,158 @@
+# really-big.conf - just about everything, just for testing.
+# This ends up having some good examples of the use of specials for
+# those hard-to-reach programs. I stopped when I got tired, but we
+# could probably get even more stuff (like libexec stuff) in here.
+#
+# This produces a 4608000 byte binary. Pretty sick and twisted, eh?
+#
+# $FreeBSD$
+#
+
+# =========================================================================
+
+srcdirs /usr/src/bin
+
+progs cat chmod cp csh date dd df domainname echo ed expr hostname kill
+progs ln ls mkdir mt mv ps pwd rcp rm rmail rmdir sh sleep stty sync test
+
+ln test [
+ln sh -sh
+
+
+# =========================================================================
+
+srcdirs /usr/src/sbin
+
+progs badsect bim clri disklabel dmesg dump dumpfs fdisk fsck halt
+progs ifconfig init mknod modload modunload mount mount_isofs
+progs mount_lofs mount_msdosfs mountd
+progs newfs nfsd nfsiod ping quotacheck reboot restore route routed savecore
+progs shutdown swapon ttyflags tunefs umount
+# shell scripts: fastboot
+
+ln dump rdump
+ln restore rrestore
+
+
+# =========================================================================
+
+srcdirs /usr/src/usr.bin
+
+progs apropos ar asa at basename biff cal calendar cap_mkdb checknr chpass
+progs cksum cmp col colcrt colrm column comm compress crontab ctags cut
+progs dirname du env error expand false file find finger fmt fold fpr from
+progs fsplit fstat ftp getconf getopt gprof head hexdump id indent ipcrm
+progs ipcs join kdump ktrace last lastcomm leave lex lock logger locate
+progs login logname look m4 machine mail make man mesg mkfifo
+progs mkstr modstat more msgs netstat newsyslog nfsstat nice nm nohup
+progs pagesize passwd paste patch pr printenv printf quota ranlib
+progs renice rev rlogin rpcgen rpcinfo rsh rup ruptime rusers rwall rwho
+progs script sed showmount size soelim split strings strip su tail talk
+progs tcopy tee telnet tftp time tip tn3270 touch tput tr true tset tsort
+progs tty ul uname unexpand unifdef uniq units unvis users uudecode uuencode
+progs vacation vgrind vi vis vmstat w wall wc what whatis whereis who
+progs whois window write xargs xinstall xstr yacc yes ypcat ypmatch ypwhich
+
+# shell scripts: lorder mkdep shar which
+# problems: rdist uses libcompat.a(regex.o), which conflicts with
+# libedit(readline.o) over regerror().
+
+# special requirements
+
+special locate srcdir /usr/src/usr.bin/locate/locate
+special tn3270 srcdir /usr/src/usr.bin/tn3270/tn3270
+
+
+# =========================================================================
+
+srcdirs /usr/src/usr.sbin
+
+progs ac accton amd arp bad144 catman chown chroot config config.new cron
+progs dev_mkdb diskpart edquota flcopy gettable grfinfo hilinfo htable inetd
+progs iostat iteconfig kvm_mkdb mtree named portmap pppd
+progs pstat pwd_mkdb quot quotaon rarpd rbootd repquota rmt rpc.bootparamd
+progs rwhod sa spray sysctl syslogd tcpdump
+progs traceroute trpt update vipw vnconfig ypbind yppoll ypset
+
+special amd srcdir /usr/src/usr.sbin/amd/amd
+special amd objs vers.amd.o afs_ops.o am_ops.o clock.o util.o xutil.o efs_ops.o mapc.o info_file.o info_hes.o info_ndbm.o info_passwd.o info_nis.o info_union.o map.o srvr_afs.o srvr_nfs.o mntfs.o misc_rpc.o mount_fs.o mtab.o mtab_bsd.o nfs_ops.o nfs_prot_svc.o nfs_start.o nfs_subr.o opts.o pfs_ops.o rpc_fwd.o sched.o sfs_ops.o amq_svc.o amq_subr.o umount_fs.o host_ops.o nfsx_ops.o ufs_ops.o ifs_ops.o amd.o get_args.o restart.o wire.o
+
+
+srcdirs /usr/src/usr.sbin/lpr # lpr subsystem
+progs lpr lpc lpq lprm pac lptest
+special lpr srcdir /usr/src/usr.sbin/lpr/lpr
+
+srcdirs /usr/src/usr.sbin/sendmail # sendmail subsystem
+progs mailstats makemap praliases sendmail
+special sendmail srcdir /usr/src/usr.sbin/sendmail/src
+ln sendmail newaliases
+ln sendmail mailq
+
+srcdirs /usr/src/usr.sbin/timed # timed & timedc
+progs timed timedc
+special timed srcdir /usr/src/usr.sbin/timed/timed
+
+srcdirs /usr/src/usr.sbin/xntpd # NTP subsystem
+# xntpd uses a gross hack to pass some information in the global
+# variable `progname' between the actual program (ntpdate in this
+# case), and the NTP library. Add `progname' to the keep list.
+progs ntpdate
+special ntpdate srcdir /usr/src/usr.sbin/xntpd/ntpdate
+special ntpdate keep progname
+libs -L/usr/src/usr.sbin/xntpd/lib -lntp
+
+srcdirs /usr/src/usr.sbin/yp # yp subsystem
+progs ypbind ypwhich ypcat ypmatch ypset yppoll
+
+
+# =========================================================================
+
+srcdirs /usr/src/gnu/usr.bin
+
+progs bc cpio diff diff3 gas gawk grep gzip sdiff sort tar
+# shell scripts: send-pr
+
+srcdirs /usr/src/gnu/usr.bin/ld # ldd and ldconfig
+progs ld ldd ldconfig
+
+# rcs stuff loses because there are cross dependencies between librcs.a and
+# the individual programs. The solution would be to specify the objpaths
+# directly for each one, and include the full path to librcs.a each the
+# objpaths.
+
+# srcdirs /usr/src/gnu/usr.bin/rcs # rcs subsystem
+# progs ci co ident merge rcs rcsclean rcsdiff rcsmerge rlog
+# # shell script: rcsfreeze
+# special rcs srcdir /usr/src/gnu/usr.bin/rcs/rcs
+# libs /usr/src/gnu/usr.bin/rcs/lib/obj/librcs.a
+
+# gdb loses too
+# progs gdb
+# special gdb srcdir /usr/src/gnu/usr.bin/gdb/gdb
+# libs /usr/src/gnu/usr.bin/gdb/bfd/obj/libbfd.a
+# libs /usr/src/gnu/usr.bin/gdb/readline/obj/libreadline.a
+# libs /usr/src/gnu/usr.bin/gdb/libiberty/obj/libiberty.a
+
+# groff has the same problem as rcs
+# srcdirs /usr/src/gnu/usr.bin/groff # groff subsystem
+# progs groff troff tbl pic eqn grops grotty grodvi refer lookbib
+# progs indxbib lkbib tfmtodit addftinfo pfbtops psbb
+# shell script: nroff
+# special groff srcdir /usr/src/gnu/usr.bin/groff/groff
+# libs /usr/src/gnu/usr.bin/groff/libgroff/obj/libgroff.a
+# libs /usr/src/gnu/usr.bin/groff/libbib/obj/libbib.a
+# libs /usr/src/gnu/usr.bin/groff/libdriver/obj/libdriver.a
+
+srcdirs /usr/src/gnu/usr.bin/gcc2 # gcc & friends
+progs cc cpp cc1
+
+# cc1 has the same problem as rcs and groff, but since there's only one program
+# I'll go ahead and solve it as an example.
+
+special cc1 objpaths /usr/src/gnu/usr.bin/gcc2/cc1/obj/c-parse.o /usr/src/gnu/usr.bin/gcc2/cc1/obj/c-lang.o /usr/src/gnu/usr.bin/gcc2/cc1/obj/c-lex.o /usr/src/gnu/usr.bin/gcc2/cc1/obj/c-pragma.o /usr/src/gnu/usr.bin/gcc2/cc1/obj/c-decl.o /usr/src/gnu/usr.bin/gcc2/cc1/obj/c-typeck.o /usr/src/gnu/usr.bin/gcc2/cc1/obj/c-convert.o /usr/src/gnu/usr.bin/gcc2/cc1/obj/c-aux-info.o /usr/src/gnu/usr.bin/gcc2/cc1/obj/c-iterate.o /usr/src/gnu/usr.bin/gcc2/common/obj/libcc1.a
+
+ln gzip gunzip
+ln gzip gzcat
+
+libs -ledit -lgnumalloc -lc -lcrypt -ltermcap -lcurses -ltelnet -lutil -lkvm
+libs -ll -ly -lm -lresolv -lrpcsvc -lcompat
diff --git a/usr.sbin/ctladm/Makefile b/usr.sbin/ctladm/Makefile
new file mode 100644
index 0000000..fd9e606
--- /dev/null
+++ b/usr.sbin/ctladm/Makefile
@@ -0,0 +1,20 @@
+# $FreeBSD$
+
+PROG= ctladm
+SRCS= ctladm.c util.c ctl_util.c ctl_scsi_all.c
+.PATH: ${.CURDIR}/../../sys/cam/ctl
+SDIR= ${.CURDIR}/../../sys
+CFLAGS+= -I${SDIR}
+# This is necessary because of these warnings:
+# warning: cast increases required alignment of target type
+# The solution is to either upgrade the compiler (preferred), or do void
+# pointer gymnastics to get around the warning. For now, disable the
+# warning instead of doing the void pointer workaround.
+.if ${MACHINE_CPUARCH} == "arm"
+WARNS?= 3
+.endif
+
+LIBADD= cam sbuf bsdxml util
+MAN= ctladm.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ctladm/Makefile.depend b/usr.sbin/ctladm/Makefile.depend
new file mode 100644
index 0000000..f96ac2f
--- /dev/null
+++ b/usr.sbin/ctladm/Makefile.depend
@@ -0,0 +1,22 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcam \
+ lib/libcompiler_rt \
+ lib/libexpat \
+ lib/libsbuf \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ctladm/ctladm.8 b/usr.sbin/ctladm/ctladm.8
new file mode 100644
index 0000000..0c30db3
--- /dev/null
+++ b/usr.sbin/ctladm/ctladm.8
@@ -0,0 +1,1026 @@
+.\"
+.\" Copyright (c) 2003 Silicon Graphics International Corp.
+.\" Copyright (c) 2015 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,
+.\" 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.
+.\"
+.\" ctladm utility man page.
+.\"
+.\" Author: Ken Merry <ken@FreeBSD.org>
+.\"
+.\" $Id: //depot/users/kenm/FreeBSD-test2/usr.sbin/ctladm/ctladm.8#3 $
+.\" $FreeBSD$
+.\"
+.Dd September 26, 2015
+.Dt CTLADM 8
+.Os
+.Sh NAME
+.Nm ctladm
+.Nd CAM Target Layer control utility
+.Sh SYNOPSIS
+.Nm
+.Aq Ar command
+.Op lun
+.Op generic args
+.Op command args
+.Nm
+.Ic tur
+.Aq lun
+.Op general options
+.Nm
+.Ic inquiry
+.Aq lun
+.Op general options
+.Nm
+.Ic reqsense
+.Aq lun
+.Op general options
+.Nm
+.Ic reportluns
+.Aq lun
+.Op general options
+.Nm
+.Ic read
+.Aq lun
+.Op general options
+.Aq Fl l Ar lba
+.Aq Fl d Ar datalen
+.Aq Fl f Ar file|-
+.Aq Fl b Ar blocksize_bytes
+.Op Fl c Ar cdbsize
+.Op Fl N
+.Nm
+.Ic write
+.Aq lun
+.Op general options
+.Aq Fl l Ar lba
+.Aq Fl d Ar datalen
+.Aq Fl f Ar file|-
+.Aq Fl b Ar blocksize_bytes
+.Op Fl c Ar cdbsize
+.Op Fl N
+.Nm
+.Ic readcap
+.Aq lun
+.Op general options
+.Op Fl c Ar cdbsize
+.Nm
+.Ic modesense
+.Aq lun
+.Aq Fl m Ar page | Fl l
+.Op Fl P Ar pc
+.Op Fl d
+.Op Fl S Ar subpage
+.Op Fl c Ar size
+.Nm
+.Ic start
+.Aq lun
+.Op general options
+.Op Fl i
+.Op Fl o
+.Nm
+.Ic stop
+.Aq lun
+.Op general options
+.Op Fl i
+.Op Fl o
+.Nm
+.Ic synccache
+.Aq lun
+.Op general options
+.Op Fl l Ar lba
+.Op Fl b Ar blockcount
+.Op Fl r
+.Op Fl i
+.Op Fl c Ar cdbsize
+.Nm
+.Ic lunlist
+.Nm
+.Ic delay
+.Aq lun
+.Aq Fl l Ar datamove|done
+.Aq Fl t Ar secs
+.Op Fl T Ar oneshot|cont
+.Nm
+.Ic inject
+.Aq Fl i Ar action
+.Aq Fl p Ar pattern
+.Op Fl r Ar lba,len
+.Op Fl s Ar len fmt Op Ar args
+.Op Fl c
+.Op Fl d Ar delete_id
+.Nm
+.Ic create
+.Aq Fl b Ar backend
+.Op Fl B Ar blocksize
+.Op Fl d Ar device_id
+.Op Fl l Ar lun_id
+.Op Fl o Ar name=value
+.Op Fl s Ar size_bytes
+.Op Fl S Ar serial_num
+.Op Fl t Ar device_type
+.Nm
+.Ic remove
+.Aq Fl b Ar backend
+.Aq Fl l Ar lun_id
+.Op Fl o Ar name=value
+.Nm
+.Ic modify
+.Aq Fl b Ar backend
+.Aq Fl l Ar lun_id
+.Op Fl o Ar name=value
+.Aq Fl s Ar size_bytes
+.Nm
+.Ic devlist
+.Op Fl b Ar backend
+.Op Fl v
+.Op Fl x
+.Nm
+.Ic port
+.Op Fl o Ar on|off
+.Op Fl w Ar wwpn
+.Op Fl W Ar wwnn
+.Op Fl p Ar targ_port
+.Op Fl t Ar fe_type
+.Nm
+.Ic portlist
+.Op Fl f Ar frontend
+.Op Fl i
+.Op Fl l
+.Op Fl p Ar targ_port
+.Op Fl q
+.Op Fl v
+.Op Fl x
+.Nm
+.Ic lunmap
+.Aq Fl p Ar targ_port
+.Op Fl l Ar pLUN
+.Op Fl L Ar cLUN
+.Nm
+.Ic dumpooa
+.Nm
+.Ic dumpstructs
+.Nm
+.Ic islist
+.Op Fl v
+.Op Fl x
+.Nm
+.Ic islogout
+.Aq Fl a | Fl c Ar connection-id | Fl i Ar name | Fl p Ar portal
+.Nm
+.Ic isterminate
+.Aq Fl a | Fl c Ar connection-id | Fl i Ar name | Fl p Ar portal
+.Nm
+.Ic help
+.Sh DESCRIPTION
+The
+.Nm
+utility is designed to provide a way to access and control the CAM Target
+Layer (CTL).
+It provides a way to send
+.Tn SCSI
+commands to the CTL layer, and also provides
+some meta-commands that utilize
+.Tn SCSI
+commands.
+(For instance, the
+.Ic lunlist
+command is implemented using the
+.Tn SCSI
+REPORT LUNS and INQUIRY commands.)
+.Pp
+The
+.Nm
+utility has a number of primary functions, many of which require a device
+identifier.
+The device identifier takes the following form:
+.Bl -tag -width 14n
+.It lun
+Specify the LUN number to operate on.
+.El
+Many of the primary functions of the
+.Nm
+utility take the following optional arguments:
+.Bl -tag -width 10n
+.It Fl C Ar retries
+Specify the number of times to retry a command in the event of failure.
+.It Fl D Ar device
+Specify the device to open. This allows opening a device other than the
+default device,
+.Pa /dev/cam/ctl ,
+to be opened for sending commands.
+.It Fl I Ar id
+Specify the initiator number to use.
+By default,
+.Nm
+will use 7 as the initiator number.
+.El
+.Pp
+Primary commands:
+.Bl -tag -width 11n
+.It Ic tur
+Send the
+.Tn SCSI
+TEST UNIT READY command to the device and report whether or not it is
+ready.
+.It Ic inquiry
+Send the
+.Tn SCSI
+INQUIRY command to the device and display some of the returned inquiry
+data.
+.It Ic reqsense
+Send the
+.Tn SCSI
+REQUEST SENSE command to the device and display the returned sense
+information.
+.It Ic reportluns
+Send the
+.Tn SCSI
+REPORT LUNS command to the device and display supported LUNs.
+.It Ic read
+Send a
+.Tn SCSI
+READ command to the device, and write the requested data to a file or
+stdout.
+.Bl -tag -width 12n
+.It Fl l Ar lba
+Specify the starting Logical Block Address for the READ. This can be
+specified in decimal, octal (starting with 0), hexadecimal (starting with
+0x) or any other base supported by
+.Xr strtoull 3 .
+.It Fl d Ar datalen
+Specify the length, in 512 byte blocks, of the READ request.
+.It Fl f Ar file
+Specify the destination for the data read by the READ command. Either a
+filename or
+.Sq -
+for stdout may be specified.
+.It Fl c Ar cdbsize
+Specify the minimum
+.Tn SCSI
+CDB (Command Data Block) size to be used for the READ request. Allowable
+values are 6, 10, 12 and 16. Depending upon the LBA and amount of data
+requested, a larger CDB size may be used to satisfy the request. (e.g.,
+for LBAs above 0xffffffff, READ(16) must be used to satisfy the request.)
+.It Fl b Ar blocksize
+Specify the blocksize of the underlying
+.Tn SCSI
+device, so the transfer length
+can be calculated accurately. The blocksize can be obtained via the
+.Tn SCSI
+READ CAPACITY command.
+.It Fl N
+Do not copy data to
+.Nm
+from the kernel when doing a read, just execute the command without copying
+data.
+This is to be used for performance testing.
+.El
+.It Ic write
+Read data from a file or stdin, and write the data to the device using the
+.Tn SCSI
+WRITE command.
+.Bl -tag -width 12n
+.It Fl l Ar lba
+Specify the starting Logical Block Address for the WRITE. This can be
+specified in decimal, octal (starting with 0), hexadecimal (starting with
+0x) or any other base supported by
+.Xr strtoull 3 .
+.It Fl d Ar atalen
+Specify the length, in 512 byte blocks, of the WRITE request.
+.It Fl f Ar file
+Specify the source for the data to be written by the WRITE command. Either a
+filename or
+.Sq -
+for stdin may be specified.
+.It Fl c Ar cdbsize
+Specify the minimum
+.Tn SCSI
+CDB (Command Data Block) size to be used for the READ request. Allowable
+values are 6, 10, 12 and 16. Depending upon the LBA and amount of data
+requested, a larger CDB size may be used to satisfy the request. (e.g.,
+for LBAs above 0xffffffff, READ(16) must be used to satisfy the request.)
+.It Fl b Ar blocksize
+Specify the blocksize of the underlying
+.Tn SCSI
+device, so the transfer length
+can be calculated accurately. The blocksize can be obtained via the
+.Tn SCSI
+READ CAPACITY command.
+.It Fl N
+Do not copy data to
+.Nm
+to the kernel when doing a write, just execute the command without copying
+data.
+This is to be used for performance testing.
+.El
+.It Ic readcap
+Send the
+.Tn SCSI
+READ CAPACITY command to the device and display the device size and device
+block size. By default, READ CAPACITY(10) is
+used. If the device returns a maximum LBA of 0xffffffff, however,
+.Nm
+will automatically issue a READ CAPACITY(16), which is implemented as a
+service action of the SERVICE ACTION IN(16) opcode. The user can specify
+the minimum CDB size with the
+.Fl c
+argument. Valid values for the
+.Fl c
+option are 10 and 16. If a 10 byte CDB is specified, the request will be
+automatically reissued with a 16 byte CDB if the maximum LBA returned is
+0xffffffff.
+.It Ic modesense
+Send a
+.Tn SCSI
+MODE SENSE command to the device, and display the requested mode page(s) or
+page list.
+.Bl -tag -width 10n
+.It Fl m Ar page
+Specify the mode page to display. This option and the
+.Fl l
+option are mutually exclusive. One of the two must be specified, though.
+Mode page numbers may be specified in decimal or hexadecimal.
+.It Fl l
+Request that the list of mode pages supported by the device be returned.
+This option and the
+.Fl m
+option are mutually exclusive. One of the two must be specified, though.
+.It Fl P Ar pc
+Specify the mode page control value. Possible values are:
+.Bl -tag -width 2n -compact
+.It 0
+Current values.
+.It 1
+Changeable value bitmask.
+.It 2
+Default values.
+.It 3
+Saved values.
+.El
+.It Fl d
+Disable block descriptors when sending the mode sense request.
+.It Fl S Ar subpage
+Specify the subpage used with the mode sense request.
+.It Fl c Ar cdbsize
+Specify the CDB size used for the mode sense request. Supported values are
+6 and 10.
+.El
+.It Ic start
+Send the
+.Tn SCSI
+START STOP UNIT command to the specified LUN with the start
+bit set.
+.Bl -tag -width 4n
+.It Fl i
+Set the immediate bit in the CDB. Note that CTL does not support the
+immediate bit, so this is primarily useful for making sure that CTL returns
+the proper error.
+.El
+.It Ic stop
+Send the
+.Tn SCSI
+START STOP UNIT command to the specified LUN with the start
+bit cleared. We use an ordered tag to stop the LUN, so we can guarantee
+that all pending I/O executes before it is stopped. (CTL guarantees this
+anyway, but
+.Nm
+sends an ordered tag for completeness.)
+.Bl -tag -width 4n
+.It Fl i
+Set the immediate bit in the CDB. Note that CTL does not support the
+immediate bit, so this is primarily useful for making sure that CTL returns
+the proper error.
+.El
+.It Ic synccache
+Send the
+.Tn SCSI
+SYNCHRONIZE CACHE command to the device. By default, SYNCHRONIZE
+CACHE(10) is used. If the specified starting LBA is greater than
+0xffffffff or the length is greater than 0xffff, though,
+SYNCHRONIZE CACHE(16) will be used. The 16 byte command will also be used
+if the user specifies a 16 byte CDB with the
+.Fl c
+argument.
+.Bl -tag -width 14n
+.It Fl l Ar lba
+Specify the starting LBA of the cache region to synchronize. This option is a
+no-op for CTL. If you send a SYNCHRONIZE CACHE command, it will sync the
+cache for the entire LUN.
+.It Fl b Ar blockcount
+Specify the length of the cache region to synchronize. This option is a
+no-op for CTL. If you send a SYNCHRONIZE CACHE command, it will sync the
+cache for the entire LUN.
+.It Fl r
+Specify relative addressing for the starting LBA. CTL does not support
+relative addressing, since it only works for linked commands, and CTL
+does not support linked commands.
+.It Fl i
+Tell the target to return status immediately after issuing the SYNCHRONIZE CACHE
+command rather than waiting for the cache to finish syncing. CTL does not
+support this bit.
+.It Fl c Ar cdbsize
+Specify the minimum CDB size. Valid values are 10 and 16 bytes.
+.El
+.It Ic lunlist
+List all LUNs registered with CTL.
+Because this command uses the ioctl port, it will only work when the FETDs
+(Front End Target Drivers) are enabled.
+This command is the equivalent of doing a REPORT LUNS on one LUN and then
+an INQUIRY on each LUN in the system.
+.It Ic delay
+Delay commands at the given location. There are two places where commands
+may be delayed currently: before data is transferred
+.Pq Dq datamove
+and just prior to sending status to the host
+.Pq Dq done .
+One of the two must be supplied as an argument to the
+.Fl l
+option. The
+.Fl t
+option must also be specified.
+.Bl -tag -width 12n
+.It Fl l Ar delayloc
+Delay command(s) at the specified location.
+This can either be at the data movement stage (datamove) or prior to
+command completion (done).
+.It Fl t Ar delaytime
+Delay command(s) for the specified number of seconds. This must be
+specified. If set to 0, it will clear out any previously set delay for
+this particular location (datamove or done).
+.It Fl T Ar delaytype
+Specify the delay type.
+By default, the
+.Ic delay
+option will delay the next command sent to the given LUN.
+With the
+.Fl T Ar cont
+option, every command will be delayed by the specified period of time.
+With the
+.Fl T Ar oneshot
+the next command sent to the given LUN will be delayed and all subsequent
+commands will be completed normally.
+This is the default.
+.El
+.It Ic inject
+Inject the specified type of error for the LUN specified, when a command
+that matches the given pattern is seen.
+The sense data returned is in either fixed or descriptor format, depending
+upon the status of the D_SENSE bit in the control mode page (page 0xa) for
+the LUN.
+.Pp
+Errors are only injected for commands that have not already failed for
+other reasons.
+By default, only the first command matching the pattern specified is
+returned with the supplied error.
+.Pp
+If the
+.Fl c
+flag is specified, all commands matching the pattern will be returned with
+the specified error until the error injection command is deleted with
+.Fl d
+flag.
+.Bl -tag -width 17n
+.It Fl i Ar action
+Specify the error to return:
+.Bl -tag -width 10n
+.It aborted
+Return the next matching command on the specified LUN with the sense key
+ABORTED COMMAND (0x0b), and the ASC/ASCQ 0x45,0x00 ("Select or reselect
+failure").
+.It mediumerr
+Return the next matching command on the specified LUN with the sense key
+MEDIUM ERROR (0x03) and the ASC/ASCQ 0x11,0x00 ("Unrecovered read error") for
+reads, or ASC/ASCQ 0x0c,0x02 ("Write error - auto reallocation failed")
+for write errors.
+.It ua
+Return the next matching command on the specified LUN with the sense key
+UNIT ATTENTION (0x06) and the ASC/ASCQ 0x29,0x00 ("POWER ON, RESET, OR BUS
+DEVICE RESET OCCURRED").
+.It custom
+Return the next matching command on the specified LUN with the supplied
+sense data.
+The
+.Fl s
+argument must be specified.
+.El
+.It Fl p Ar pattern
+Specify which commands should be returned with the given error.
+.Bl -tag -width 10n
+.It read
+The error should apply to READ(6), READ(10), READ(12), READ(16), etc.
+.It write
+The error should apply to WRITE(6), WRITE(10), WRITE(12), WRITE(16), WRITE
+AND VERIFY(10), etc.
+.It rw
+The error should apply to both read and write type commands.
+.It readcap
+The error should apply to READ CAPACITY(10) and READ CAPACITY(16) commands.
+.It tur
+The error should apply to TEST UNIT READY commands.
+.It any
+The error should apply to any command.
+.El
+.It Fl r Ar lba,len
+Specify the starting lba and length of the range of LBAs which should
+trigger an error.
+This option is only applies when read and/or write patterns are specified.
+If used with other command types, the error will never be triggered.
+.It Fl s Ar len fmt Op Ar args
+Specify the sense data that is to be returned for custom actions.
+If the format is
+.Sq - ,
+len bytes of sense data will be read from standard input and written to the
+sense buffer.
+If len is longer than 252 bytes (the maximum allowable
+.Tn SCSI
+sense data length), it will be truncated to that length.
+The sense data format is described in
+.Xr cam_cdparse 3 .
+.It Fl c
+The error injection should be persistent, instead of happening once.
+Persistent errors must be deleted with the
+.Fl d
+argument.
+.It Fl d Ar delete_id
+Delete the specified error injection serial number.
+The serial number is returned when the error is injected.
+.El
+.It Ic port
+Perform one of several CTL frontend port operations.
+Either get a list of frontend ports
+.Pq Fl l ,
+turn one or more frontends on
+or off
+.Pq Fl o Ar on|off ,
+or set the World Wide Node Name
+.Pq Fl w Ar wwnn
+or World Wide Port Name
+.Pq Fl W Ar wwpn
+for a given port.
+One of
+.Fl l ,
+.Fl o ,
+or
+.Fl w
+or
+.Fl W
+must be specified.
+The WWNN and WWPN may both be specified at the same time, but cannot be
+combined with enabling/disabling or listing ports.
+.Bl -tag -width 12n
+.It Fl o Ar on|off
+Turn the specified CTL frontend ports off or on.
+If no port number or port type is specified, all ports are turned on or
+off.
+.It Fl p Ar targ_port
+Specify the frontend port number.
+The port numbers can be found in the frontend port list.
+.It Fl t Ar fe_type
+Specify the frontend type.
+Currently defined port types are
+.Dq fc
+(Fibre Channel),
+.Dq scsi
+(Parallel SCSI),
+.Dq ioctl
+(CTL ioctl interface),
+and
+.Dq internal
+(CTL CAM SIM).
+.It Fl w Ar wwnn
+Set the World Wide Node Name for the given port.
+The
+.Fl n
+argument must be specified, since this is only possible to implement on a
+single port.
+As a general rule, the WWNN should be the same across all ports on the
+system.
+.It Fl W Ar wwpn
+Set the World Wide Port Name for the given port.
+The
+.Fl n
+argument must be specified, since this is only possible to implement on a
+single port.
+As a general rule, the WWPN must be different for every port in the system.
+.El
+.It Ic portlist
+List CTL frontend ports.
+.Bl -tag -width 12n
+.It Fl f Ar frontend
+Specify the frontend type.
+.It Fl i
+Report target and connected initiators addresses.
+.It Fl l
+Report LUN mapping.
+.It Fl p Ar targ_port
+Specify the frontend port number.
+.It Fl q
+Omit the header in the port list output.
+.It Fl v
+Enable verbose output (report all port options).
+.It Fl x
+Output the port list in XML format.
+.El
+.It Ic lunmap
+Change LUN mapping for specified port.
+If both
+.Ar pLUN
+and
+.Ar cLUN
+are specified -- LUN will be mapped.
+If
+.Ar pLUN
+is specified, but
+.Ar cLUN
+is not -- LUN will be unmapped.
+If neither
+.Ar pLUN
+nor
+.Ar cLUN
+are specified -- LUN mapping will be disabled, exposing all CTL LUNs.
+.Bl -tag -width 12n
+.It Fl p Ar targ_port
+Specify the frontend port number.
+.It Fl l Ar pLUN
+LUN number visible by specified port.
+.It Fl L Ar cLUN
+CTL LUN number.
+.El
+.It Ic dumpooa
+Dump the OOA (Order Of Arrival) queue for each LUN registered with CTL.
+.It Ic dumpstructs
+Dump the CTL structures to the console.
+.It Ic create
+Create a new LUN.
+The backend must be specified, and depending upon the backend requested,
+some of the other options may be required.
+If the LUN is created successfully, the LUN configuration will be
+displayed.
+If LUN creation fails, a message will be displayed describing the failure.
+.Bl -tag -width 14n
+.It Fl b Ar backend
+The
+.Fl b
+flag is required.
+This specifies the name backend to use when creating the LUN.
+Examples are
+.Dq ramdisk
+and
+.Dq block .
+.It Fl B Ar blocksize
+Specify the blocksize of the backend in bytes.
+.It Fl d Ar device_id
+Specify the LUN-associated string to use in the
+.Tn SCSI
+INQUIRY VPD page 0x83 data.
+.It Fl l Ar lun_id
+Request that a particular LUN number be assigned.
+If the requested LUN number is not available, the request will fail.
+.It Fl o Ar name=value
+Specify a backend-specific name/value pair.
+Multiple
+.Fl o
+arguments may be specified.
+Refer to the backend documentation for arguments that may be used.
+.It Fl s Ar size_bytes
+Specify the size of the LUN in bytes.
+Some backends may allow setting the size (e.g. the ramdisk backend) and for
+others the size may be implicit (e.g. the block backend).
+.It Fl S Ar serial_num
+Specify the serial number to be used in the
+.Tn SCSI
+INQUIRY VPD page 0x80 data.
+.It Fl t Ar device_type
+Specify the numeric SCSI device type to use when creating the LUN.
+If this flag is not used, the type of LUN created is backend-specific.
+Not all LUN types are supported.
+Currently CTL supports Direct Access (type 0), Processor (type 3)
+and CD/DVD (type 5) LUNs.
+The backend requested may or may not support all of the LUN types that CTL
+supports.
+.El
+.It Ic remove
+Remove a LUN.
+The backend must be specified, and the LUN number must also be specified.
+Backend-specific options may also be specified with the
+.Fl o
+flag.
+.Bl -tag -width 14n
+.It Fl b Ar backend
+Specify the backend that owns the LUN to be removed.
+Examples are
+.Dq ramdisk
+and
+.Dq block .
+.It Fl l Ar lun_id
+Specify the LUN number to remove.
+.It Fl o Ar name=value
+Specify a backend-specific name/value pair.
+Multiple
+.Fl o
+arguments may be specified.
+Refer to the backend documentation for arguments that may be used.
+.El
+.It Ic modify
+Modify a LUN size.
+The backend, the LUN number, and the size must be specified.
+.Bl -tag -width 14n
+.It Fl b Ar backend
+Specify the backend that owns the LUN to be removed.
+Examples are
+.Dq ramdisk
+and
+.Dq block .
+.It Fl l Ar lun_id
+Specify the LUN number to remove.
+.It Fl o Ar name=value
+Specify a backend-specific name/value pair.
+Multiple
+.Fl o
+arguments may be specified.
+Refer to the backend documentation for arguments that may be used.
+.It Fl s Ar size_bytes
+Specify the size of the LUN in bytes.
+For the
+.Dq block
+backend, an
+.Dq auto
+keyword may be passed instead; this will make CTL use the size of backing
+file or device.
+.El
+.It Ic devlist
+Get a list of all configured LUNs.
+This also includes the LUN size and blocksize, serial number and device ID.
+.Bl -tag -width 11n
+.It Fl b Ar backend
+Specify the backend.
+This restricts the LUN list to the named backend.
+Examples are
+.Dq ramdisk
+and
+.Dq block .
+.It Fl v
+Be verbose.
+This will also display any backend-specific LUN attributes in addition to
+the standard per-LUN information.
+.It Fl x
+Dump the raw XML.
+The LUN list information from the kernel comes in XML format, and this
+option allows the display of the raw XML data.
+This option and the
+.Fl v
+and
+.Fl b
+options are mutually exclusive.
+If you specify
+.Fl x ,
+the entire LUN database is displayed in XML format.
+.El
+.It Ic islist
+Get a list of currently running iSCSI sessions.
+This includes initiator and target names and the unique connection IDs.
+.Bl -tag -width 11n
+.It Fl v
+Verbose mode.
+.It Fl x
+Dump the raw XML.
+The sessions list information from the kernel comes in XML format, and this
+option allows the display of the raw XML data.
+.El
+.It Ic islogout
+Ask the initiator to log out iSCSI sessions matching criteria.
+.Bl -tag -width 11n
+.It Fl a
+Log out all sessions.
+.It Fl c
+Specify connection ID.
+.It Fl i
+Specify initiator name.
+.It Fl p
+Specify initiator portal (hostname or IP address).
+.El
+.It Ic isterminate
+Forcibly terminate iSCSI sessions matching criteria.
+.Bl -tag -width 11n
+.It Fl a
+Terminate all sessions.
+.It Fl c
+Specify connection ID.
+.It Fl i
+Specify initiator name.
+.It Fl p
+Specify initiator portal (hostname or IP address).
+.El
+.It Ic help
+Display
+.Nm
+usage information.
+.El
+.Sh OPTIONS
+Number of additional configuration options may be specified for LUNs.
+Some options are global, others are backend-specific.
+.Pp
+Global options:
+.Bl -tag -width 12n
+.It Va vendor
+Specifies LUN vendor string up to 8 chars.
+.It Va product
+Specifies LUN product string up to 16 chars.
+.It Va revision
+Specifies LUN revision string up to 4 chars.
+.It Va scsiname
+Specifies LUN SCSI name string.
+.It Va eui
+Specifies LUN EUI-64 identifier.
+.It Va naa
+Specifies LUN NAA identifier.
+Either EUI or NAA identifier should be set to UNIQUE value to allow
+EXTENDED COPY command access the LUN.
+Non-unique LUN identifiers may lead to data corruption.
+.It Va ha_role
+Setting to "primary" or "secondary" overrides default role of the node
+in HA cluster, set by kern.cam.ctl.ha_role sysctl.
+.It Va insecure_tpc
+Setting to "on" allows EXTENDED COPY command sent to this LUN access
+other LUNs on this host, not accessible otherwise.
+This allows to offload copying between different iSCSI targets residing
+on the same host in trusted environments.
+.It Va readcache
+Set to "off", disables read caching for the LUN, if supported by the backend.
+.It Va readonly
+Set to "on", blocks all media write operations to the LUN, reporting it
+as write protected.
+.It Va removable
+Set to "on", makes LUN removable.
+.It Va reordering
+Set to "unrestricted", allows target to process commands with SIMPLE task
+attribute in arbitrary order. Any data integrity exposures related to
+command sequence order shall be explicitly handled by the application
+client through the selection of appropriate commands and task attributes.
+The default value is "restricted". It improves data integrity, but may
+introduce some additional delays.
+.It Va serseq
+Set to "on" to serialize conseсutive reads/writes.
+Set to "read" to serialize conseсutive reads.
+Set to "off" to allow them be issued in parallel.
+Parallel issue of consecutive operations may confuse logic of the
+backing file system, hurting performance; but it may improve performance
+of backing stores without prefetch/write-back.
+.It Va pblocksize
+.It Va pblockoffset
+Specify physical block size and offset of the device.
+.It Va ublocksize
+.It Va ublockoffset
+Specify UNMAP block size and offset of the device.
+.It Va rpm
+Specifies medium rotation rate of the device: 0 -- not reported,
+1 -- non-rotating (SSD), >1024 -- value in revolutions per minute.
+.It Va formfactor
+Specifies nominal form factor of the device: 0 -- not reported, 1 -- 5.25",
+2 -- 3.5", 3 -- 2.5", 4 -- 1.8", 5 -- less then 1.8".
+.It Va unmap
+Set to "on", enables UNMAP support for the LUN, if supported by the backend.
+.It Va avail-threshold
+.It Va used-threshold
+.It Va pool-avail-threshold
+.It Va pool-used-threshold
+Set per-LUN/-pool thin provisioning soft thresholds.
+LUN will establish UNIT ATTENTION condition if its or pool available space
+get below configured avail values, or its or pool used space get above
+configured used values.
+Pool thresholds are working only for ZVOL-backed LUNs.
+.It Va writecache
+Set to "off", disables write caching for the LUN, if supported by the backend.
+.El
+.Pp
+Options specific for block backend:
+.Bl -tag -width 12n
+.It Va file
+Specifies file or device name to use for backing store.
+.It Va num_threads
+Specifies number of backend threads to use for this LUN.
+.El
+.Sh EXAMPLES
+.Dl ctladm tur 1
+.Pp
+Send a
+.Tn SCSI
+TEST UNIT READY command to LUN 1.
+.Pp
+.Dl ctladm modesense 1 -l
+.Pp
+Display the list of mode pages supported by LUN 1.
+.Pp
+.Dl ctladm modesense 0 -m 10 -P 3 -d -c 10
+.Pp
+Display the saved version of the Control mode page (page 10) on LUN 0.
+Disable fetching block descriptors, and use a 10 byte MODE SENSE command
+instead of the default 6 byte command.
+.Bd -literal
+ctladm read 2 -l 0 -d 1 -b 512 -f - > foo
+.Ed
+.Pp
+Read the first 512 byte block from LUN 2 and dump it to the file
+.Pa foo .
+.Bd -literal
+ctladm write 3 -l 0xff432140 -d 20 -b 512 -f /tmp/bar
+.Ed
+.Pp
+Read 10240 bytes from the file
+.Pa /tmp/bar
+and write it to LUN 3.
+starting at LBA 0xff432140.
+.Pp
+.Dl ctladm create -b ramdisk -s 10485760000000000
+.Pp
+Create a LUN with the
+.Dq fake
+ramdisk as a backing store.
+The LUN will claim to have a size of approximately 10 terabytes.
+.Pp
+.Dl ctladm create -b block -o file=src/usr.sbin/ctladm/ctladm.8
+.Pp
+Create a LUN using the block backend, and specify the file
+.Pa src/usr.sbin/ctladm/ctladm.8
+as the backing store.
+The size of the LUN will be derived from the size of the file.
+.Pp
+.Dl ctladm create -b block -o file=src/usr.sbin/ctladm/ctladm.8 -S MYSERIAL321 -d MYDEVID123
+.Pp
+Create a LUN using the block backend, specify the file
+.Pa src/usr.sbin/ctladm/ctladm.8
+as the backing store, and specify the
+.Tn SCSI
+VPD page 0x80 and 0x83 serial number
+.Fl ( S )
+and device ID
+.Fl ( d ) .
+.Pp
+.Dl ctladm remove -b block -l 12
+.Pp
+Remove LUN 12, which is handled by the block backend, from the system.
+.Pp
+.Dl ctladm devlist
+.Pp
+List configured LUNs in the system, along with their backend and serial
+number.
+This works when the Front End Target Drivers are enabled or disabled.
+.Pp
+.Dl ctladm lunlist
+.Pp
+List all LUNs in the system, along with their inquiry data and device type.
+This only works when the FETDs are enabled, since the commands go through the
+ioctl port.
+.Pp
+.Dl ctladm inject 6 -i mediumerr -p read -r 0,512 -c
+.Pp
+Inject a medium error on LUN 6 for every read that covers the first 512
+blocks of the LUN.
+.Bd -literal -offset indent
+ctladm inject 6 -i custom -p tur -s 18 "f0 0 02 s12 04 02"
+.Ed
+.Pp
+Inject a custom error on LUN 6 for the next TEST UNIT READY command only.
+This will result in a sense key of NOT READY (0x02), and an ASC/ASCQ of
+0x04,0x02 ("Logical unit not ready, initializing command required").
+.Sh SEE ALSO
+.Xr cam 3 ,
+.Xr cam_cdbparse 3 ,
+.Xr cam 4 ,
+.Xr ctl 4 ,
+.Xr xpt 4 ,
+.Xr camcontrol 8 ,
+.Xr ctld 8 ,
+.Xr ctlstat 8
+.Sh HISTORY
+The
+.Nm
+utility was originally written during the Winter/Spring of 2003 as an
+interface to CTL.
+.Sh AUTHORS
+.An Ken Merry Aq Mt ken@FreeBSD.org
diff --git a/usr.sbin/ctladm/ctladm.c b/usr.sbin/ctladm/ctladm.c
new file mode 100644
index 0000000..2e5817b
--- /dev/null
+++ b/usr.sbin/ctladm/ctladm.c
@@ -0,0 +1,4262 @@
+/*-
+ * Copyright (c) 2003, 2004 Silicon Graphics International Corp.
+ * Copyright (c) 1997-2007 Kenneth D. Merry
+ * 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,
+ * 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.
+ *
+ * $Id: //depot/users/kenm/FreeBSD-test2/usr.sbin/ctladm/ctladm.c#4 $
+ */
+/*
+ * CAM Target Layer exercise program.
+ *
+ * Author: Ken Merry <ken@FreeBSD.org>
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/queue.h>
+#include <sys/callout.h>
+#include <sys/sbuf.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <string.h>
+#include <errno.h>
+#include <err.h>
+#include <ctype.h>
+#include <bsdxml.h>
+#include <cam/scsi/scsi_all.h>
+#include <cam/scsi/scsi_message.h>
+#include <cam/ctl/ctl.h>
+#include <cam/ctl/ctl_io.h>
+#include <cam/ctl/ctl_backend.h>
+#include <cam/ctl/ctl_ioctl.h>
+#include <cam/ctl/ctl_util.h>
+#include <cam/ctl/ctl_scsi_all.h>
+#include <camlib.h>
+#include <libutil.h>
+#include "ctladm.h"
+
+#ifdef min
+#undef min
+#endif
+#define min(x,y) (x < y) ? x : y
+
+typedef enum {
+ CTLADM_CMD_TUR,
+ CTLADM_CMD_INQUIRY,
+ CTLADM_CMD_REQ_SENSE,
+ CTLADM_CMD_ARRAYLIST,
+ CTLADM_CMD_REPORT_LUNS,
+ CTLADM_CMD_HELP,
+ CTLADM_CMD_DEVLIST,
+ CTLADM_CMD_ADDDEV,
+ CTLADM_CMD_RM,
+ CTLADM_CMD_CREATE,
+ CTLADM_CMD_READ,
+ CTLADM_CMD_WRITE,
+ CTLADM_CMD_PORT,
+ CTLADM_CMD_PORTLIST,
+ CTLADM_CMD_READCAPACITY,
+ CTLADM_CMD_MODESENSE,
+ CTLADM_CMD_DUMPOOA,
+ CTLADM_CMD_DUMPSTRUCTS,
+ CTLADM_CMD_START,
+ CTLADM_CMD_STOP,
+ CTLADM_CMD_SYNC_CACHE,
+ CTLADM_CMD_LUNLIST,
+ CTLADM_CMD_DELAY,
+ CTLADM_CMD_ERR_INJECT,
+ CTLADM_CMD_PRES_IN,
+ CTLADM_CMD_PRES_OUT,
+ CTLADM_CMD_INQ_VPD_DEVID,
+ CTLADM_CMD_RTPG,
+ CTLADM_CMD_MODIFY,
+ CTLADM_CMD_ISLIST,
+ CTLADM_CMD_ISLOGOUT,
+ CTLADM_CMD_ISTERMINATE,
+ CTLADM_CMD_LUNMAP
+} ctladm_cmdfunction;
+
+typedef enum {
+ CTLADM_ARG_NONE = 0x0000000,
+ CTLADM_ARG_AUTOSENSE = 0x0000001,
+ CTLADM_ARG_DEVICE = 0x0000002,
+ CTLADM_ARG_ARRAYSIZE = 0x0000004,
+ CTLADM_ARG_BACKEND = 0x0000008,
+ CTLADM_ARG_CDBSIZE = 0x0000010,
+ CTLADM_ARG_DATALEN = 0x0000020,
+ CTLADM_ARG_FILENAME = 0x0000040,
+ CTLADM_ARG_LBA = 0x0000080,
+ CTLADM_ARG_PC = 0x0000100,
+ CTLADM_ARG_PAGE_CODE = 0x0000200,
+ CTLADM_ARG_PAGE_LIST = 0x0000400,
+ CTLADM_ARG_SUBPAGE = 0x0000800,
+ CTLADM_ARG_PAGELIST = 0x0001000,
+ CTLADM_ARG_DBD = 0x0002000,
+ CTLADM_ARG_TARG_LUN = 0x0004000,
+ CTLADM_ARG_BLOCKSIZE = 0x0008000,
+ CTLADM_ARG_IMMED = 0x0010000,
+ CTLADM_ARG_RELADR = 0x0020000,
+ CTLADM_ARG_RETRIES = 0x0040000,
+ CTLADM_ARG_ONOFFLINE = 0x0080000,
+ CTLADM_ARG_ONESHOT = 0x0100000,
+ CTLADM_ARG_TIMEOUT = 0x0200000,
+ CTLADM_ARG_INITIATOR = 0x0400000,
+ CTLADM_ARG_NOCOPY = 0x0800000,
+ CTLADM_ARG_NEED_TL = 0x1000000
+} ctladm_cmdargs;
+
+struct ctladm_opts {
+ const char *optname;
+ uint32_t cmdnum;
+ ctladm_cmdargs argnum;
+ const char *subopt;
+};
+
+typedef enum {
+ CC_OR_NOT_FOUND,
+ CC_OR_AMBIGUOUS,
+ CC_OR_FOUND
+} ctladm_optret;
+
+static const char rw_opts[] = "Nb:c:d:f:l:";
+static const char startstop_opts[] = "i";
+
+static struct ctladm_opts option_table[] = {
+ {"adddev", CTLADM_CMD_ADDDEV, CTLADM_ARG_NONE, NULL},
+ {"create", CTLADM_CMD_CREATE, CTLADM_ARG_NONE, "b:B:d:l:o:s:S:t:"},
+ {"delay", CTLADM_CMD_DELAY, CTLADM_ARG_NEED_TL, "T:l:t:"},
+ {"devid", CTLADM_CMD_INQ_VPD_DEVID, CTLADM_ARG_NEED_TL, NULL},
+ {"devlist", CTLADM_CMD_DEVLIST, CTLADM_ARG_NONE, "b:vx"},
+ {"dumpooa", CTLADM_CMD_DUMPOOA, CTLADM_ARG_NONE, NULL},
+ {"dumpstructs", CTLADM_CMD_DUMPSTRUCTS, CTLADM_ARG_NONE, NULL},
+ {"help", CTLADM_CMD_HELP, CTLADM_ARG_NONE, NULL},
+ {"inject", CTLADM_CMD_ERR_INJECT, CTLADM_ARG_NEED_TL, "cd:i:p:r:s:"},
+ {"inquiry", CTLADM_CMD_INQUIRY, CTLADM_ARG_NEED_TL, NULL},
+ {"islist", CTLADM_CMD_ISLIST, CTLADM_ARG_NONE, "vx"},
+ {"islogout", CTLADM_CMD_ISLOGOUT, CTLADM_ARG_NONE, "ac:i:p:"},
+ {"isterminate", CTLADM_CMD_ISTERMINATE, CTLADM_ARG_NONE, "ac:i:p:"},
+ {"lunlist", CTLADM_CMD_LUNLIST, CTLADM_ARG_NONE, NULL},
+ {"lunmap", CTLADM_CMD_LUNMAP, CTLADM_ARG_NONE, "p:l:L:"},
+ {"modesense", CTLADM_CMD_MODESENSE, CTLADM_ARG_NEED_TL, "P:S:dlm:c:"},
+ {"modify", CTLADM_CMD_MODIFY, CTLADM_ARG_NONE, "b:l:o:s:"},
+ {"port", CTLADM_CMD_PORT, CTLADM_ARG_NONE, "lo:p:qt:w:W:x"},
+ {"portlist", CTLADM_CMD_PORTLIST, CTLADM_ARG_NONE, "f:ilp:qvx"},
+ {"prin", CTLADM_CMD_PRES_IN, CTLADM_ARG_NEED_TL, "a:"},
+ {"prout", CTLADM_CMD_PRES_OUT, CTLADM_ARG_NEED_TL, "a:k:r:s:"},
+ {"read", CTLADM_CMD_READ, CTLADM_ARG_NEED_TL, rw_opts},
+ {"readcapacity", CTLADM_CMD_READCAPACITY, CTLADM_ARG_NEED_TL, "c:"},
+ {"remove", CTLADM_CMD_RM, CTLADM_ARG_NONE, "b:l:o:"},
+ {"reportluns", CTLADM_CMD_REPORT_LUNS, CTLADM_ARG_NEED_TL, NULL},
+ {"reqsense", CTLADM_CMD_REQ_SENSE, CTLADM_ARG_NEED_TL, NULL},
+ {"rtpg", CTLADM_CMD_RTPG, CTLADM_ARG_NEED_TL, NULL},
+ {"start", CTLADM_CMD_START, CTLADM_ARG_NEED_TL, startstop_opts},
+ {"stop", CTLADM_CMD_STOP, CTLADM_ARG_NEED_TL, startstop_opts},
+ {"synccache", CTLADM_CMD_SYNC_CACHE, CTLADM_ARG_NEED_TL, "b:c:il:r"},
+ {"tur", CTLADM_CMD_TUR, CTLADM_ARG_NEED_TL, NULL},
+ {"write", CTLADM_CMD_WRITE, CTLADM_ARG_NEED_TL, rw_opts},
+ {"-?", CTLADM_CMD_HELP, CTLADM_ARG_NONE, NULL},
+ {"-h", CTLADM_CMD_HELP, CTLADM_ARG_NONE, NULL},
+ {NULL, 0, 0, NULL}
+};
+
+
+ctladm_optret getoption(struct ctladm_opts *table, char *arg, uint32_t *cmdnum,
+ ctladm_cmdargs *argnum, const char **subopt);
+static int cctl_dump_ooa(int fd, int argc, char **argv);
+static int cctl_port(int fd, int argc, char **argv, char *combinedopt);
+static int cctl_do_io(int fd, int retries, union ctl_io *io, const char *func);
+static int cctl_delay(int fd, int lun, int argc, char **argv,
+ char *combinedopt);
+static int cctl_lunlist(int fd);
+static int cctl_sync_cache(int fd, int lun, int iid, int retries,
+ int argc, char **argv, char *combinedopt);
+static int cctl_start_stop(int fd, int lun, int iid, int retries,
+ int start, int argc, char **argv, char *combinedopt);
+static int cctl_mode_sense(int fd, int lun, int iid, int retries,
+ int argc, char **argv, char *combinedopt);
+static int cctl_read_capacity(int fd, int lun, int iid,
+ int retries, int argc, char **argv,
+ char *combinedopt);
+static int cctl_read_write(int fd, int lun, int iid, int retries,
+ int argc, char **argv, char *combinedopt,
+ ctladm_cmdfunction command);
+static int cctl_get_luns(int fd, int lun, int iid, int retries,
+ struct scsi_report_luns_data **lun_data,
+ uint32_t *num_luns);
+static int cctl_report_luns(int fd, int lun, int iid, int retries);
+static int cctl_tur(int fd, int lun, int iid, int retries);
+static int cctl_get_inquiry(int fd, int lun, int iid, int retries,
+ char *path_str, int path_len,
+ struct scsi_inquiry_data *inq_data);
+static int cctl_inquiry(int fd, int lun, int iid, int retries);
+static int cctl_req_sense(int fd, int lun, int iid, int retries);
+static int cctl_persistent_reserve_in(int fd, int lun,
+ int initiator, int argc, char **argv,
+ char *combinedopt, int retry_count);
+static int cctl_persistent_reserve_out(int fd, int lun,
+ int initiator, int argc, char **argv,
+ char *combinedopt, int retry_count);
+static int cctl_create_lun(int fd, int argc, char **argv, char *combinedopt);
+static int cctl_inquiry_vpd_devid(int fd, int lun, int initiator);
+static int cctl_report_target_port_group(int fd, int lun, int initiator);
+static int cctl_modify_lun(int fd, int argc, char **argv, char *combinedopt);
+static int cctl_portlist(int fd, int argc, char **argv, char *combinedopt);
+
+ctladm_optret
+getoption(struct ctladm_opts *table, char *arg, uint32_t *cmdnum,
+ ctladm_cmdargs *argnum, const char **subopt)
+{
+ struct ctladm_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 (strcmp(opts->optname, arg) == 0)
+ return (CC_OR_FOUND);
+
+ if (++num_matches > 1)
+ return(CC_OR_AMBIGUOUS);
+ }
+ }
+
+ if (num_matches > 0)
+ return(CC_OR_FOUND);
+ else
+ return(CC_OR_NOT_FOUND);
+}
+
+static int
+cctl_dump_ooa(int fd, int argc, char **argv)
+{
+ struct ctl_ooa ooa;
+ long double cmd_latency;
+ int num_entries, len, lun = -1, retval = 0;
+ unsigned int i;
+
+ num_entries = 104;
+
+ if ((argc > 2) && (isdigit(argv[2][0])))
+ lun = strtol(argv[2], NULL, 0);
+retry:
+
+ len = num_entries * sizeof(struct ctl_ooa_entry);
+ bzero(&ooa, sizeof(ooa));
+ ooa.entries = malloc(len);
+ if (ooa.entries == NULL) {
+ warn("%s: error mallocing %d bytes", __func__, len);
+ return (1);
+ }
+ if (lun >= 0) {
+ ooa.lun_num = lun;
+ } else
+ ooa.flags |= CTL_OOA_FLAG_ALL_LUNS;
+ ooa.alloc_len = len;
+ ooa.alloc_num = num_entries;
+ if (ioctl(fd, CTL_GET_OOA, &ooa) == -1) {
+ warn("%s: CTL_GET_OOA ioctl failed", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ if (ooa.status == CTL_OOA_NEED_MORE_SPACE) {
+ num_entries = num_entries * 2;
+ free(ooa.entries);
+ ooa.entries = NULL;
+ goto retry;
+ }
+
+ if (ooa.status != CTL_OOA_OK) {
+ warnx("%s: CTL_GET_OOA ioctl returned error %d", __func__,
+ ooa.status);
+ retval = 1;
+ goto bailout;
+ }
+
+ fprintf(stdout, "Dumping OOA queues\n");
+ for (i = 0; i < ooa.fill_num; i++) {
+ struct ctl_ooa_entry *entry;
+ char cdb_str[(SCSI_MAX_CDBLEN * 3) +1];
+ struct bintime delta_bt;
+ struct timespec ts;
+
+ entry = &ooa.entries[i];
+
+ delta_bt = ooa.cur_bt;
+ bintime_sub(&delta_bt, &entry->start_bt);
+ bintime2timespec(&delta_bt, &ts);
+ cmd_latency = ts.tv_sec * 1000;
+ if (ts.tv_nsec > 0)
+ cmd_latency += ts.tv_nsec / 1000000;
+
+ fprintf(stdout, "LUN %jd tag 0x%04x%s%s%s%s%s: %s. CDB: %s "
+ "(%0.0Lf ms)\n",
+ (intmax_t)entry->lun_num, entry->tag_num,
+ (entry->cmd_flags & CTL_OOACMD_FLAG_BLOCKED) ?
+ " BLOCKED" : "",
+ (entry->cmd_flags & CTL_OOACMD_FLAG_DMA) ? " DMA" : "",
+ (entry->cmd_flags & CTL_OOACMD_FLAG_DMA_QUEUED) ?
+ " DMAQUEUED" : "",
+ (entry->cmd_flags & CTL_OOACMD_FLAG_ABORT) ?
+ " ABORT" : "",
+ (entry->cmd_flags & CTL_OOACMD_FLAG_RTR) ? " RTR" :"",
+ scsi_op_desc(entry->cdb[0], NULL),
+ scsi_cdb_string(entry->cdb, cdb_str, sizeof(cdb_str)),
+ cmd_latency);
+ }
+ fprintf(stdout, "OOA queues dump done\n");
+
+bailout:
+ free(ooa.entries);
+ return (retval);
+}
+
+static int
+cctl_dump_structs(int fd, ctladm_cmdargs cmdargs __unused)
+{
+ if (ioctl(fd, CTL_DUMP_STRUCTS) == -1) {
+ warn(__func__);
+ return (1);
+ }
+ return (0);
+}
+
+typedef enum {
+ CCTL_PORT_MODE_NONE,
+ CCTL_PORT_MODE_LIST,
+ CCTL_PORT_MODE_SET,
+ CCTL_PORT_MODE_ON,
+ CCTL_PORT_MODE_OFF
+} cctl_port_mode;
+
+static struct ctladm_opts cctl_fe_table[] = {
+ {"fc", CTL_PORT_FC, CTLADM_ARG_NONE, NULL},
+ {"scsi", CTL_PORT_SCSI, CTLADM_ARG_NONE, NULL},
+ {"internal", CTL_PORT_INTERNAL, CTLADM_ARG_NONE, NULL},
+ {"iscsi", CTL_PORT_ISCSI, CTLADM_ARG_NONE, NULL},
+ {"sas", CTL_PORT_SAS, CTLADM_ARG_NONE, NULL},
+ {"all", CTL_PORT_ALL, CTLADM_ARG_NONE, NULL},
+ {NULL, 0, 0, NULL}
+};
+
+static int
+cctl_port(int fd, int argc, char **argv, char *combinedopt)
+{
+ int c;
+ int32_t targ_port = -1;
+ int retval = 0;
+ int wwnn_set = 0, wwpn_set = 0;
+ uint64_t wwnn = 0, wwpn = 0;
+ cctl_port_mode port_mode = CCTL_PORT_MODE_NONE;
+ struct ctl_port_entry entry;
+ ctl_port_type port_type = CTL_PORT_NONE;
+ int quiet = 0, xml = 0;
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'l':
+ if (port_mode != CCTL_PORT_MODE_NONE)
+ goto bailout_badarg;
+
+ port_mode = CCTL_PORT_MODE_LIST;
+ break;
+ case 'o':
+ if (port_mode != CCTL_PORT_MODE_NONE)
+ goto bailout_badarg;
+
+ if (strcasecmp(optarg, "on") == 0)
+ port_mode = CCTL_PORT_MODE_ON;
+ else if (strcasecmp(optarg, "off") == 0)
+ port_mode = CCTL_PORT_MODE_OFF;
+ else {
+ warnx("Invalid -o argument %s, \"on\" or "
+ "\"off\" are the only valid args",
+ optarg);
+ retval = 1;
+ goto bailout;
+ }
+ break;
+ case 'p':
+ targ_port = strtol(optarg, NULL, 0);
+ break;
+ case 'q':
+ quiet = 1;
+ break;
+ case 't': {
+ ctladm_optret optret;
+ ctladm_cmdargs argnum;
+ const char *subopt;
+ ctl_port_type tmp_port_type;
+
+ optret = getoption(cctl_fe_table, optarg, &tmp_port_type,
+ &argnum, &subopt);
+ if (optret == CC_OR_AMBIGUOUS) {
+ warnx("%s: ambiguous frontend type %s",
+ __func__, optarg);
+ retval = 1;
+ goto bailout;
+ } else if (optret == CC_OR_NOT_FOUND) {
+ warnx("%s: invalid frontend type %s",
+ __func__, optarg);
+ retval = 1;
+ goto bailout;
+ }
+
+ port_type |= tmp_port_type;
+ break;
+ }
+ case 'w':
+ if ((port_mode != CCTL_PORT_MODE_NONE)
+ && (port_mode != CCTL_PORT_MODE_SET))
+ goto bailout_badarg;
+
+ port_mode = CCTL_PORT_MODE_SET;
+
+ wwnn = strtoull(optarg, NULL, 0);
+ wwnn_set = 1;
+ break;
+ case 'W':
+ if ((port_mode != CCTL_PORT_MODE_NONE)
+ && (port_mode != CCTL_PORT_MODE_SET))
+ goto bailout_badarg;
+
+ port_mode = CCTL_PORT_MODE_SET;
+
+ wwpn = strtoull(optarg, NULL, 0);
+ wwpn_set = 1;
+ break;
+ case 'x':
+ xml = 1;
+ break;
+ }
+ }
+
+ /*
+ * The user can specify either one or more frontend types (-t), or
+ * a specific frontend, but not both.
+ *
+ * If the user didn't specify a frontend type or number, set it to
+ * all. This is primarily needed for the enable/disable ioctls.
+ * This will be a no-op for the listing code. For the set ioctl,
+ * we'll throw an error, since that only works on one port at a time.
+ */
+ if ((port_type != CTL_PORT_NONE) && (targ_port != -1)) {
+ warnx("%s: can only specify one of -t or -n", __func__);
+ retval = 1;
+ goto bailout;
+ } else if ((targ_port == -1) && (port_type == CTL_PORT_NONE))
+ port_type = CTL_PORT_ALL;
+
+ bzero(&entry, sizeof(entry));
+
+ /*
+ * These are needed for all but list/dump mode.
+ */
+ entry.port_type = port_type;
+ entry.targ_port = targ_port;
+
+ switch (port_mode) {
+ case CCTL_PORT_MODE_LIST: {
+ char opts[] = "xq";
+ char argx[] = "-x";
+ char argq[] = "-q";
+ char *argvx[2];
+ int argcx = 0;
+
+ optind = 0;
+ optreset = 1;
+ if (xml)
+ argvx[argcx++] = argx;
+ if (quiet)
+ argvx[argcx++] = argq;
+ cctl_portlist(fd, argcx, argvx, opts);
+ break;
+ }
+ case CCTL_PORT_MODE_SET:
+ if (targ_port == -1) {
+ warnx("%s: -w and -W require -n", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ if (wwnn_set) {
+ entry.flags |= CTL_PORT_WWNN_VALID;
+ entry.wwnn = wwnn;
+ }
+ if (wwpn_set) {
+ entry.flags |= CTL_PORT_WWPN_VALID;
+ entry.wwpn = wwpn;
+ }
+
+ if (ioctl(fd, CTL_SET_PORT_WWNS, &entry) == -1) {
+ warn("%s: CTL_SET_PORT_WWNS ioctl failed", __func__);
+ retval = 1;
+ goto bailout;
+ }
+ break;
+ case CCTL_PORT_MODE_ON:
+ if (ioctl(fd, CTL_ENABLE_PORT, &entry) == -1) {
+ warn("%s: CTL_ENABLE_PORT ioctl failed", __func__);
+ retval = 1;
+ goto bailout;
+ }
+ fprintf(stdout, "Front End Ports enabled\n");
+ break;
+ case CCTL_PORT_MODE_OFF:
+ if (ioctl(fd, CTL_DISABLE_PORT, &entry) == -1) {
+ warn("%s: CTL_DISABLE_PORT ioctl failed", __func__);
+ retval = 1;
+ goto bailout;
+ }
+ fprintf(stdout, "Front End Ports disabled\n");
+ break;
+ default:
+ warnx("%s: one of -l, -o or -w/-W must be specified", __func__);
+ retval = 1;
+ goto bailout;
+ break;
+ }
+
+bailout:
+
+ return (retval);
+
+bailout_badarg:
+ warnx("%s: only one of -l, -o or -w/-W may be specified", __func__);
+ return (1);
+}
+
+static int
+cctl_do_io(int fd, int retries, union ctl_io *io, const char *func)
+{
+ do {
+ if (ioctl(fd, CTL_IO, io) == -1) {
+ warn("%s: error sending CTL_IO ioctl", func);
+ return (-1);
+ }
+ } while (((io->io_hdr.status & CTL_STATUS_MASK) != CTL_SUCCESS)
+ && (retries-- > 0));
+
+ return (0);
+}
+
+static int
+cctl_delay(int fd, int lun, int argc, char **argv,
+ char *combinedopt)
+{
+ struct ctl_io_delay_info delay_info;
+ char *delayloc = NULL;
+ char *delaytype = NULL;
+ int delaytime = -1;
+ int retval;
+ int c;
+
+ retval = 0;
+
+ memset(&delay_info, 0, sizeof(delay_info));
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'T':
+ delaytype = strdup(optarg);
+ break;
+ case 'l':
+ delayloc = strdup(optarg);
+ break;
+ case 't':
+ delaytime = strtoul(optarg, NULL, 0);
+ break;
+ }
+ }
+
+ if (delaytime == -1) {
+ warnx("%s: you must specify the delaytime with -t", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ if (strcasecmp(delayloc, "datamove") == 0)
+ delay_info.delay_loc = CTL_DELAY_LOC_DATAMOVE;
+ else if (strcasecmp(delayloc, "done") == 0)
+ delay_info.delay_loc = CTL_DELAY_LOC_DONE;
+ else {
+ warnx("%s: invalid delay location %s", __func__, delayloc);
+ retval = 1;
+ goto bailout;
+ }
+
+ if ((delaytype == NULL)
+ || (strcmp(delaytype, "oneshot") == 0))
+ delay_info.delay_type = CTL_DELAY_TYPE_ONESHOT;
+ else if (strcmp(delaytype, "cont") == 0)
+ delay_info.delay_type = CTL_DELAY_TYPE_CONT;
+ else {
+ warnx("%s: invalid delay type %s", __func__, delaytype);
+ retval = 1;
+ goto bailout;
+ }
+
+ delay_info.lun_id = lun;
+ delay_info.delay_secs = delaytime;
+
+ if (ioctl(fd, CTL_DELAY_IO, &delay_info) == -1) {
+ warn("%s: CTL_DELAY_IO ioctl failed", __func__);
+ retval = 1;
+ goto bailout;
+ }
+ switch (delay_info.status) {
+ case CTL_DELAY_STATUS_NONE:
+ warnx("%s: no delay status??", __func__);
+ retval = 1;
+ break;
+ case CTL_DELAY_STATUS_OK:
+ break;
+ case CTL_DELAY_STATUS_INVALID_LUN:
+ warnx("%s: invalid lun %d", __func__, lun);
+ retval = 1;
+ break;
+ case CTL_DELAY_STATUS_INVALID_TYPE:
+ warnx("%s: invalid delay type %d", __func__,
+ delay_info.delay_type);
+ retval = 1;
+ break;
+ case CTL_DELAY_STATUS_INVALID_LOC:
+ warnx("%s: delay location %s not implemented?", __func__,
+ delayloc);
+ retval = 1;
+ break;
+ case CTL_DELAY_STATUS_NOT_IMPLEMENTED:
+ warnx("%s: delay not implemented in the kernel", __func__);
+ warnx("%s: recompile with the CTL_IO_DELAY flag set", __func__);
+ retval = 1;
+ break;
+ default:
+ warnx("%s: unknown delay return status %d", __func__,
+ delay_info.status);
+ retval = 1;
+ break;
+ }
+
+bailout:
+ free(delayloc);
+ free(delaytype);
+ return (retval);
+}
+
+static struct ctladm_opts cctl_err_types[] = {
+ {"aborted", CTL_LUN_INJ_ABORTED, CTLADM_ARG_NONE, NULL},
+ {"mediumerr", CTL_LUN_INJ_MEDIUM_ERR, CTLADM_ARG_NONE, NULL},
+ {"ua", CTL_LUN_INJ_UA, CTLADM_ARG_NONE, NULL},
+ {"custom", CTL_LUN_INJ_CUSTOM, CTLADM_ARG_NONE, NULL},
+ {NULL, 0, 0, NULL}
+
+};
+
+static struct ctladm_opts cctl_err_patterns[] = {
+ {"read", CTL_LUN_PAT_READ, CTLADM_ARG_NONE, NULL},
+ {"write", CTL_LUN_PAT_WRITE, CTLADM_ARG_NONE, NULL},
+ {"rw", CTL_LUN_PAT_READWRITE, CTLADM_ARG_NONE, NULL},
+ {"readwrite", CTL_LUN_PAT_READWRITE, CTLADM_ARG_NONE, NULL},
+ {"readcap", CTL_LUN_PAT_READCAP, CTLADM_ARG_NONE, NULL},
+ {"tur", CTL_LUN_PAT_TUR, CTLADM_ARG_NONE, NULL},
+ {"any", CTL_LUN_PAT_ANY, CTLADM_ARG_NONE, NULL},
+#if 0
+ {"cmd", CTL_LUN_PAT_CMD, CTLADM_ARG_NONE, NULL},
+#endif
+ {NULL, 0, 0, NULL}
+};
+
+static int
+cctl_error_inject(int fd, uint32_t lun, int argc, char **argv,
+ char *combinedopt)
+{
+ int retval = 0;
+ struct ctl_error_desc err_desc;
+ uint64_t lba = 0;
+ uint32_t len = 0;
+ uint64_t delete_id = 0;
+ int delete_id_set = 0;
+ int continuous = 0;
+ int sense_len = 0;
+ int fd_sense = 0;
+ int c;
+
+ bzero(&err_desc, sizeof(err_desc));
+ err_desc.lun_id = lun;
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'c':
+ continuous = 1;
+ break;
+ case 'd':
+ delete_id = strtoull(optarg, NULL, 0);
+ delete_id_set = 1;
+ break;
+ case 'i':
+ case 'p': {
+ ctladm_optret optret;
+ ctladm_cmdargs argnum;
+ const char *subopt;
+
+ if (c == 'i') {
+ ctl_lun_error err_type;
+
+ if (err_desc.lun_error != CTL_LUN_INJ_NONE) {
+ warnx("%s: can't specify multiple -i "
+ "arguments", __func__);
+ retval = 1;
+ goto bailout;
+ }
+ optret = getoption(cctl_err_types, optarg,
+ &err_type, &argnum, &subopt);
+ err_desc.lun_error = err_type;
+ } else {
+ ctl_lun_error_pattern pattern;
+
+ optret = getoption(cctl_err_patterns, optarg,
+ &pattern, &argnum, &subopt);
+ err_desc.error_pattern |= pattern;
+ }
+
+ if (optret == CC_OR_AMBIGUOUS) {
+ warnx("%s: ambiguous argument %s", __func__,
+ optarg);
+ retval = 1;
+ goto bailout;
+ } else if (optret == CC_OR_NOT_FOUND) {
+ warnx("%s: argument %s not found", __func__,
+ optarg);
+ retval = 1;
+ goto bailout;
+ }
+ break;
+ }
+ case 'r': {
+ char *tmpstr, *tmpstr2;
+
+ tmpstr = strdup(optarg);
+ if (tmpstr == NULL) {
+ warn("%s: error duplicating string %s",
+ __func__, optarg);
+ retval = 1;
+ goto bailout;
+ }
+
+ tmpstr2 = strsep(&tmpstr, ",");
+ if (tmpstr2 == NULL) {
+ warnx("%s: invalid -r argument %s", __func__,
+ optarg);
+ retval = 1;
+ free(tmpstr);
+ goto bailout;
+ }
+ lba = strtoull(tmpstr2, NULL, 0);
+ tmpstr2 = strsep(&tmpstr, ",");
+ if (tmpstr2 == NULL) {
+ warnx("%s: no len argument for -r lba,len, got"
+ " %s", __func__, optarg);
+ retval = 1;
+ free(tmpstr);
+ goto bailout;
+ }
+ len = strtoul(tmpstr2, NULL, 0);
+ free(tmpstr);
+ break;
+ }
+ case 's': {
+ struct get_hook hook;
+ char *sensestr;
+
+ sense_len = strtol(optarg, NULL, 0);
+ if (sense_len <= 0) {
+ warnx("invalid number of sense bytes %d",
+ sense_len);
+ retval = 1;
+ goto bailout;
+ }
+
+ sense_len = MIN(sense_len, SSD_FULL_SIZE);
+
+ hook.argc = argc - optind;
+ hook.argv = argv + optind;
+ hook.got = 0;
+
+ sensestr = cget(&hook, NULL);
+ if ((sensestr != NULL)
+ && (sensestr[0] == '-')) {
+ fd_sense = 1;
+ } else {
+ buff_encode_visit(
+ (uint8_t *)&err_desc.custom_sense,
+ sense_len, sensestr, iget, &hook);
+ }
+ optind += hook.got;
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ if (delete_id_set != 0) {
+ err_desc.serial = delete_id;
+ if (ioctl(fd, CTL_ERROR_INJECT_DELETE, &err_desc) == -1) {
+ warn("%s: error issuing CTL_ERROR_INJECT_DELETE ioctl",
+ __func__);
+ retval = 1;
+ }
+ goto bailout;
+ }
+
+ if (err_desc.lun_error == CTL_LUN_INJ_NONE) {
+ warnx("%s: error injection command (-i) needed",
+ __func__);
+ retval = 1;
+ goto bailout;
+ } else if ((err_desc.lun_error == CTL_LUN_INJ_CUSTOM)
+ && (sense_len == 0)) {
+ warnx("%s: custom error requires -s", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ if (continuous != 0)
+ err_desc.lun_error |= CTL_LUN_INJ_CONTINUOUS;
+
+ /*
+ * If fd_sense is set, we need to read the sense data the user
+ * wants returned from stdin.
+ */
+ if (fd_sense == 1) {
+ ssize_t amt_read;
+ int amt_to_read = sense_len;
+ u_int8_t *buf_ptr = (uint8_t *)&err_desc.custom_sense;
+
+ 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 sense data from stdin");
+ retval = 1;
+ goto bailout;
+ }
+ amt_to_read -= amt_read;
+ buf_ptr += amt_read;
+ }
+ }
+
+ if (err_desc.error_pattern == CTL_LUN_PAT_NONE) {
+ warnx("%s: command pattern (-p) needed", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ if (len != 0) {
+ err_desc.error_pattern |= CTL_LUN_PAT_RANGE;
+ /*
+ * We could check here to see whether it's a read/write
+ * command, but that will be pointless once we allow
+ * custom patterns. At that point, the user could specify
+ * a READ(6) CDB type, and we wouldn't have an easy way here
+ * to verify whether range checking is possible there. The
+ * user will just figure it out when his error never gets
+ * executed.
+ */
+#if 0
+ if ((err_desc.pattern & CTL_LUN_PAT_READWRITE) == 0) {
+ warnx("%s: need read and/or write pattern if range "
+ "is specified", __func__);
+ retval = 1;
+ goto bailout;
+ }
+#endif
+ err_desc.lba_range.lba = lba;
+ err_desc.lba_range.len = len;
+ }
+
+ if (ioctl(fd, CTL_ERROR_INJECT, &err_desc) == -1) {
+ warn("%s: error issuing CTL_ERROR_INJECT ioctl", __func__);
+ retval = 1;
+ } else {
+ printf("Error injection succeeded, serial number is %ju\n",
+ (uintmax_t)err_desc.serial);
+ }
+bailout:
+
+ return (retval);
+}
+
+static int
+cctl_lunlist(int fd)
+{
+ struct scsi_report_luns_data *lun_data;
+ struct scsi_inquiry_data *inq_data;
+ uint32_t num_luns;
+ int initid;
+ unsigned int i;
+ int retval;
+
+ inq_data = NULL;
+ initid = 7;
+
+ /*
+ * XXX KDM assuming LUN 0 is fine, but we may need to change this
+ * if we ever acquire the ability to have multiple targets.
+ */
+ if ((retval = cctl_get_luns(fd, /*lun*/ 0, initid,
+ /*retries*/ 2, &lun_data, &num_luns)) != 0)
+ goto bailout;
+
+ inq_data = malloc(sizeof(*inq_data));
+ if (inq_data == NULL) {
+ warn("%s: couldn't allocate memory for inquiry data\n",
+ __func__);
+ retval = 1;
+ goto bailout;
+ }
+ for (i = 0; i < num_luns; i++) {
+ char scsi_path[40];
+ int lun_val;
+
+ switch (lun_data->luns[i].lundata[0] & RPL_LUNDATA_ATYP_MASK) {
+ case RPL_LUNDATA_ATYP_PERIPH:
+ lun_val = lun_data->luns[i].lundata[1];
+ break;
+ case RPL_LUNDATA_ATYP_FLAT:
+ lun_val = (lun_data->luns[i].lundata[0] &
+ RPL_LUNDATA_FLAT_LUN_MASK) |
+ (lun_data->luns[i].lundata[1] <<
+ RPL_LUNDATA_FLAT_LUN_BITS);
+ break;
+ case RPL_LUNDATA_ATYP_LUN:
+ case RPL_LUNDATA_ATYP_EXTLUN:
+ default:
+ fprintf(stdout, "Unsupported LUN format %d\n",
+ lun_data->luns[i].lundata[0] &
+ RPL_LUNDATA_ATYP_MASK);
+ lun_val = -1;
+ break;
+ }
+ if (lun_val == -1)
+ continue;
+
+ if ((retval = cctl_get_inquiry(fd, lun_val, initid,
+ /*retries*/ 2, scsi_path,
+ sizeof(scsi_path),
+ inq_data)) != 0) {
+ goto bailout;
+ }
+ printf("%s", scsi_path);
+ scsi_print_inquiry(inq_data);
+ }
+bailout:
+
+ if (lun_data != NULL)
+ free(lun_data);
+
+ if (inq_data != NULL)
+ free(inq_data);
+
+ return (retval);
+}
+
+static int
+cctl_sync_cache(int fd, int lun, int iid, int retries,
+ int argc, char **argv, char *combinedopt)
+{
+ union ctl_io *io;
+ int cdb_size = -1;
+ int retval;
+ uint64_t our_lba = 0;
+ uint32_t our_block_count = 0;
+ int reladr = 0, immed = 0;
+ int c;
+
+ retval = 0;
+
+ io = ctl_scsi_alloc_io(iid);
+ if (io == NULL) {
+ warnx("%s: can't allocate memory", __func__);
+ return (1);
+ }
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'b':
+ our_block_count = strtoul(optarg, NULL, 0);
+ break;
+ case 'c':
+ cdb_size = strtol(optarg, NULL, 0);
+ break;
+ case 'i':
+ immed = 1;
+ break;
+ case 'l':
+ our_lba = strtoull(optarg, NULL, 0);
+ break;
+ case 'r':
+ reladr = 1;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (cdb_size != -1) {
+ switch (cdb_size) {
+ case 10:
+ case 16:
+ break;
+ default:
+ warnx("%s: invalid cdbsize %d, valid sizes are 10 "
+ "and 16", __func__, cdb_size);
+ retval = 1;
+ goto bailout;
+ break; /* NOTREACHED */
+ }
+ } else
+ cdb_size = 10;
+
+ ctl_scsi_sync_cache(/*io*/ io,
+ /*immed*/ immed,
+ /*reladr*/ reladr,
+ /*minimum_cdb_size*/ cdb_size,
+ /*starting_lba*/ our_lba,
+ /*block_count*/ our_block_count,
+ /*tag_type*/ CTL_TAG_SIMPLE,
+ /*control*/ 0);
+
+ io->io_hdr.nexus.targ_lun = lun;
+ io->io_hdr.nexus.initid = iid;
+
+ if (cctl_do_io(fd, retries, io, __func__) != 0) {
+ retval = 1;
+ goto bailout;
+ }
+
+ if ((io->io_hdr.status & CTL_STATUS_MASK) == CTL_SUCCESS) {
+ fprintf(stdout, "Cache synchronized successfully\n");
+ } else
+ ctl_io_error_print(io, NULL, stderr);
+bailout:
+ ctl_scsi_free_io(io);
+
+ return (retval);
+}
+
+static int
+cctl_start_stop(int fd, int lun, int iid, int retries, int start,
+ int argc, char **argv, char *combinedopt)
+{
+ union ctl_io *io;
+ char scsi_path[40];
+ int immed = 0;
+ int retval, c;
+
+ retval = 0;
+
+ io = ctl_scsi_alloc_io(iid);
+ if (io == NULL) {
+ warnx("%s: can't allocate memory", __func__);
+ return (1);
+ }
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'i':
+ immed = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ /*
+ * Use an ordered tag for the stop command, to guarantee that any
+ * pending I/O will finish before the stop command executes. This
+ * would normally be the case anyway, since CTL will basically
+ * treat the start/stop command as an ordered command with respect
+ * to any other command except an INQUIRY. (See ctl_ser_table.c.)
+ */
+ ctl_scsi_start_stop(/*io*/ io,
+ /*start*/ start,
+ /*load_eject*/ 0,
+ /*immediate*/ immed,
+ /*power_conditions*/ SSS_PC_START_VALID,
+ /*ctl_tag_type*/ start ? CTL_TAG_SIMPLE :
+ CTL_TAG_ORDERED,
+ /*control*/ 0);
+
+ io->io_hdr.nexus.targ_lun = lun;
+ io->io_hdr.nexus.initid = iid;
+
+ if (cctl_do_io(fd, retries, io, __func__) != 0) {
+ retval = 1;
+ goto bailout;
+ }
+
+ ctl_scsi_path_string(io, scsi_path, sizeof(scsi_path));
+ if ((io->io_hdr.status & CTL_STATUS_MASK) == CTL_SUCCESS) {
+ fprintf(stdout, "%s LUN %s successfully\n", scsi_path,
+ (start) ? "started" : "stopped");
+ } else
+ ctl_io_error_print(io, NULL, stderr);
+
+bailout:
+ ctl_scsi_free_io(io);
+
+ return (retval);
+}
+
+static int
+cctl_mode_sense(int fd, int lun, int iid, int retries,
+ int argc, char **argv, char *combinedopt)
+{
+ union ctl_io *io;
+ uint32_t datalen;
+ uint8_t *dataptr;
+ int pc = -1, cdbsize, retval, dbd = 0, subpage = -1;
+ int list = 0;
+ int page_code = -1;
+ int c;
+
+ cdbsize = 0;
+ retval = 0;
+ dataptr = NULL;
+
+ io = ctl_scsi_alloc_io(iid);
+ if (io == NULL) {
+ warn("%s: can't allocate memory", __func__);
+ return (1);
+ }
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'P':
+ pc = strtoul(optarg, NULL, 0);
+ break;
+ case 'S':
+ subpage = strtoul(optarg, NULL, 0);
+ break;
+ case 'd':
+ dbd = 1;
+ break;
+ case 'l':
+ list = 1;
+ break;
+ case 'm':
+ page_code = strtoul(optarg, NULL, 0);
+ break;
+ case 'c':
+ cdbsize = strtol(optarg, NULL, 0);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (((list == 0) && (page_code == -1))
+ || ((list != 0) && (page_code != -1))) {
+ warnx("%s: you must specify either a page code (-m) or -l",
+ __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ if ((page_code != -1)
+ && ((page_code > SMS_ALL_PAGES_PAGE)
+ || (page_code < 0))) {
+ warnx("%s: page code %d is out of range", __func__,
+ page_code);
+ retval = 1;
+ goto bailout;
+ }
+
+ if (list == 1) {
+ page_code = SMS_ALL_PAGES_PAGE;
+ if (pc != -1) {
+ warnx("%s: arg -P makes no sense with -l",
+ __func__);
+ retval = 1;
+ goto bailout;
+ }
+ if (subpage != -1) {
+ warnx("%s: arg -S makes no sense with -l", __func__);
+ retval = 1;
+ goto bailout;
+ }
+ }
+
+ if (pc == -1)
+ pc = SMS_PAGE_CTRL_CURRENT;
+ else {
+ if ((pc > 3)
+ || (pc < 0)) {
+ warnx("%s: page control value %d is out of range: 0-3",
+ __func__, pc);
+ retval = 1;
+ goto bailout;
+ }
+ }
+
+
+ if ((subpage != -1)
+ && ((subpage > 255)
+ || (subpage < 0))) {
+ warnx("%s: subpage code %d is out of range: 0-255", __func__,
+ subpage);
+ retval = 1;
+ goto bailout;
+ }
+ if (cdbsize != 0) {
+ switch (cdbsize) {
+ case 6:
+ case 10:
+ break;
+ default:
+ warnx("%s: invalid cdbsize %d, valid sizes are 6 "
+ "and 10", __func__, cdbsize);
+ retval = 1;
+ goto bailout;
+ break;
+ }
+ } else
+ cdbsize = 6;
+
+ if (subpage == -1)
+ subpage = 0;
+
+ if (cdbsize == 6)
+ datalen = 255;
+ else
+ datalen = 65535;
+
+ dataptr = (uint8_t *)malloc(datalen);
+ if (dataptr == NULL) {
+ warn("%s: can't allocate %d bytes", __func__, datalen);
+ retval = 1;
+ goto bailout;
+ }
+
+ memset(dataptr, 0, datalen);
+
+ ctl_scsi_mode_sense(io,
+ /*data_ptr*/ dataptr,
+ /*data_len*/ datalen,
+ /*dbd*/ dbd,
+ /*llbaa*/ 0,
+ /*page_code*/ page_code,
+ /*pc*/ pc << 6,
+ /*subpage*/ subpage,
+ /*minimum_cdb_size*/ cdbsize,
+ /*tag_type*/ CTL_TAG_SIMPLE,
+ /*control*/ 0);
+
+ io->io_hdr.nexus.targ_lun = lun;
+ io->io_hdr.nexus.initid = iid;
+
+ if (cctl_do_io(fd, retries, io, __func__) != 0) {
+ retval = 1;
+ goto bailout;
+ }
+
+ if ((io->io_hdr.status & CTL_STATUS_MASK) == CTL_SUCCESS) {
+ int pages_len, used_len;
+ uint32_t returned_len;
+ uint8_t *ndataptr;
+
+ if (io->scsiio.cdb[0] == MODE_SENSE_6) {
+ struct scsi_mode_hdr_6 *hdr6;
+ int bdlen;
+
+ hdr6 = (struct scsi_mode_hdr_6 *)dataptr;
+
+ returned_len = hdr6->datalen + 1;
+ bdlen = hdr6->block_descr_len;
+
+ ndataptr = (uint8_t *)((uint8_t *)&hdr6[1] + bdlen);
+ } else {
+ struct scsi_mode_hdr_10 *hdr10;
+ int bdlen;
+
+ hdr10 = (struct scsi_mode_hdr_10 *)dataptr;
+
+ returned_len = scsi_2btoul(hdr10->datalen) + 2;
+ bdlen = scsi_2btoul(hdr10->block_descr_len);
+
+ ndataptr = (uint8_t *)((uint8_t *)&hdr10[1] + bdlen);
+ }
+ /* just in case they can give us more than we allocated for */
+ returned_len = min(returned_len, datalen);
+ pages_len = returned_len - (ndataptr - dataptr);
+#if 0
+ fprintf(stdout, "returned_len = %d, pages_len = %d\n",
+ returned_len, pages_len);
+#endif
+ if (list == 1) {
+ fprintf(stdout, "Supported mode pages:\n");
+ for (used_len = 0; used_len < pages_len;) {
+ struct scsi_mode_page_header *header;
+
+ header = (struct scsi_mode_page_header *)
+ &ndataptr[used_len];
+ fprintf(stdout, "%d\n", header->page_code);
+ used_len += header->page_length + 2;
+ }
+ } else {
+ for (used_len = 0; used_len < pages_len; used_len++) {
+ fprintf(stdout, "0x%x ", ndataptr[used_len]);
+ if (((used_len+1) % 16) == 0)
+ fprintf(stdout, "\n");
+ }
+ fprintf(stdout, "\n");
+ }
+ } else
+ ctl_io_error_print(io, NULL, stderr);
+bailout:
+
+ ctl_scsi_free_io(io);
+
+ if (dataptr != NULL)
+ free(dataptr);
+
+ return (retval);
+}
+
+static int
+cctl_read_capacity(int fd, int lun, int iid, int retries,
+ int argc, char **argv, char *combinedopt)
+{
+ union ctl_io *io;
+ struct scsi_read_capacity_data *data;
+ struct scsi_read_capacity_data_long *longdata;
+ int cdbsize = -1, retval;
+ uint8_t *dataptr;
+ int c;
+
+ cdbsize = 10;
+ dataptr = NULL;
+ retval = 0;
+
+ io = ctl_scsi_alloc_io(iid);
+ if (io == NULL) {
+ warn("%s: can't allocate memory\n", __func__);
+ return (1);
+ }
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'c':
+ cdbsize = strtol(optarg, NULL, 0);
+ break;
+ default:
+ break;
+ }
+ }
+ if (cdbsize != -1) {
+ switch (cdbsize) {
+ case 10:
+ case 16:
+ break;
+ default:
+ warnx("%s: invalid cdbsize %d, valid sizes are 10 "
+ "and 16", __func__, cdbsize);
+ retval = 1;
+ goto bailout;
+ break; /* NOTREACHED */
+ }
+ } else
+ cdbsize = 10;
+
+ dataptr = (uint8_t *)malloc(sizeof(*longdata));
+ if (dataptr == NULL) {
+ warn("%s: can't allocate %zd bytes\n", __func__,
+ sizeof(*longdata));
+ retval = 1;
+ goto bailout;
+ }
+ memset(dataptr, 0, sizeof(*longdata));
+
+retry:
+
+ switch (cdbsize) {
+ case 10:
+ ctl_scsi_read_capacity(io,
+ /*data_ptr*/ dataptr,
+ /*data_len*/ sizeof(*longdata),
+ /*addr*/ 0,
+ /*reladr*/ 0,
+ /*pmi*/ 0,
+ /*tag_type*/ CTL_TAG_SIMPLE,
+ /*control*/ 0);
+ break;
+ case 16:
+ ctl_scsi_read_capacity_16(io,
+ /*data_ptr*/ dataptr,
+ /*data_len*/ sizeof(*longdata),
+ /*addr*/ 0,
+ /*reladr*/ 0,
+ /*pmi*/ 0,
+ /*tag_type*/ CTL_TAG_SIMPLE,
+ /*control*/ 0);
+ break;
+ }
+
+ io->io_hdr.nexus.initid = iid;
+ io->io_hdr.nexus.targ_lun = lun;
+
+ if (cctl_do_io(fd, retries, io, __func__) != 0) {
+ retval = 1;
+ goto bailout;
+ }
+
+ if ((io->io_hdr.status & CTL_STATUS_MASK) == CTL_SUCCESS) {
+ uint64_t maxlba;
+ uint32_t blocksize;
+
+ if (cdbsize == 10) {
+
+ data = (struct scsi_read_capacity_data *)dataptr;
+
+ maxlba = scsi_4btoul(data->addr);
+ blocksize = scsi_4btoul(data->length);
+
+ if (maxlba == 0xffffffff) {
+ cdbsize = 16;
+ goto retry;
+ }
+ } else {
+ longdata=(struct scsi_read_capacity_data_long *)dataptr;
+
+ maxlba = scsi_8btou64(longdata->addr);
+ blocksize = scsi_4btoul(longdata->length);
+ }
+
+ fprintf(stdout, "Disk Capacity: %ju, Blocksize: %d\n",
+ (uintmax_t)maxlba, blocksize);
+ } else {
+ ctl_io_error_print(io, NULL, stderr);
+ }
+bailout:
+ ctl_scsi_free_io(io);
+
+ if (dataptr != NULL)
+ free(dataptr);
+
+ return (retval);
+}
+
+static int
+cctl_read_write(int fd, int lun, int iid, int retries,
+ int argc, char **argv, char *combinedopt,
+ ctladm_cmdfunction command)
+{
+ union ctl_io *io;
+ int file_fd, do_stdio;
+ int cdbsize = -1, databytes;
+ uint8_t *dataptr;
+ char *filename = NULL;
+ int datalen = -1, blocksize = -1;
+ uint64_t lba = 0;
+ int lba_set = 0;
+ int retval;
+ int c;
+
+ retval = 0;
+ do_stdio = 0;
+ dataptr = NULL;
+ file_fd = -1;
+
+ io = ctl_scsi_alloc_io(iid);
+ if (io == NULL) {
+ warn("%s: can't allocate memory\n", __func__);
+ return (1);
+ }
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'N':
+ io->io_hdr.flags |= CTL_FLAG_NO_DATAMOVE;
+ break;
+ case 'b':
+ blocksize = strtoul(optarg, NULL, 0);
+ break;
+ case 'c':
+ cdbsize = strtoul(optarg, NULL, 0);
+ break;
+ case 'd':
+ datalen = strtoul(optarg, NULL, 0);
+ break;
+ case 'f':
+ filename = strdup(optarg);
+ break;
+ case 'l':
+ lba = strtoull(optarg, NULL, 0);
+ lba_set = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ if (filename == NULL) {
+ warnx("%s: you must supply a filename using -f", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ if (datalen == -1) {
+ warnx("%s: you must specify the data length with -d", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ if (lba_set == 0) {
+ warnx("%s: you must specify the LBA with -l", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ if (blocksize == -1) {
+ warnx("%s: you must specify the blocksize with -b", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ if (cdbsize != -1) {
+ switch (cdbsize) {
+ case 6:
+ case 10:
+ case 12:
+ case 16:
+ break;
+ default:
+ warnx("%s: invalid cdbsize %d, valid sizes are 6, "
+ "10, 12 or 16", __func__, cdbsize);
+ retval = 1;
+ goto bailout;
+ break; /* NOTREACHED */
+ }
+ } else
+ cdbsize = 6;
+
+ databytes = datalen * blocksize;
+ dataptr = (uint8_t *)malloc(databytes);
+
+ if (dataptr == NULL) {
+ warn("%s: can't allocate %d bytes\n", __func__, databytes);
+ retval = 1;
+ goto bailout;
+ }
+ if (strcmp(filename, "-") == 0) {
+ if (command == CTLADM_CMD_READ)
+ file_fd = STDOUT_FILENO;
+ else
+ file_fd = STDIN_FILENO;
+ do_stdio = 1;
+ } else {
+ file_fd = open(filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
+ if (file_fd == -1) {
+ warn("%s: can't open file %s", __func__, filename);
+ retval = 1;
+ goto bailout;
+ }
+ }
+
+ memset(dataptr, 0, databytes);
+
+ if (command == CTLADM_CMD_WRITE) {
+ int bytes_read;
+
+ bytes_read = read(file_fd, dataptr, databytes);
+ if (bytes_read == -1) {
+ warn("%s: error reading file %s", __func__, filename);
+ retval = 1;
+ goto bailout;
+ }
+ if (bytes_read != databytes) {
+ warnx("%s: only read %d bytes from file %s",
+ __func__, bytes_read, filename);
+ retval = 1;
+ goto bailout;
+ }
+ }
+ ctl_scsi_read_write(io,
+ /*data_ptr*/ dataptr,
+ /*data_len*/ databytes,
+ /*read_op*/ (command == CTLADM_CMD_READ) ? 1 : 0,
+ /*byte2*/ 0,
+ /*minimum_cdb_size*/ cdbsize,
+ /*lba*/ lba,
+ /*num_blocks*/ datalen,
+ /*tag_type*/ CTL_TAG_SIMPLE,
+ /*control*/ 0);
+
+ io->io_hdr.nexus.targ_lun = lun;
+ io->io_hdr.nexus.initid = iid;
+
+ if (cctl_do_io(fd, retries, io, __func__) != 0) {
+ retval = 1;
+ goto bailout;
+ }
+
+ if (((io->io_hdr.status & CTL_STATUS_MASK) == CTL_SUCCESS)
+ && (command == CTLADM_CMD_READ)) {
+ int bytes_written;
+
+ bytes_written = write(file_fd, dataptr, databytes);
+ if (bytes_written == -1) {
+ warn("%s: can't write to %s", __func__, filename);
+ goto bailout;
+ }
+ } else if ((io->io_hdr.status & CTL_STATUS_MASK) != CTL_SUCCESS)
+ ctl_io_error_print(io, NULL, stderr);
+
+
+bailout:
+
+ ctl_scsi_free_io(io);
+
+ if (dataptr != NULL)
+ free(dataptr);
+
+ if ((do_stdio == 0)
+ && (file_fd != -1))
+ close(file_fd);
+
+ return (retval);
+}
+
+static int
+cctl_get_luns(int fd, int lun, int iid, int retries, struct
+ scsi_report_luns_data **lun_data, uint32_t *num_luns)
+{
+ union ctl_io *io;
+ uint32_t nluns;
+ int lun_datalen;
+ int retval;
+
+ retval = 0;
+
+ io = ctl_scsi_alloc_io(iid);
+ if (io == NULL) {
+ warnx("%s: can't allocate memory", __func__);
+ return (1);
+ }
+
+ /*
+ * lun_data includes space for 1 lun, allocate space for 4 initially.
+ * If that isn't enough, we'll allocate more.
+ */
+ nluns = 4;
+retry:
+ lun_datalen = sizeof(*lun_data) +
+ (nluns * sizeof(struct scsi_report_luns_lundata));
+ *lun_data = malloc(lun_datalen);
+
+ if (*lun_data == NULL) {
+ warnx("%s: can't allocate memory", __func__);
+ ctl_scsi_free_io(io);
+ return (1);
+ }
+
+ ctl_scsi_report_luns(io,
+ /*data_ptr*/ (uint8_t *)*lun_data,
+ /*data_len*/ lun_datalen,
+ /*select_report*/ RPL_REPORT_ALL,
+ /*tag_type*/ CTL_TAG_SIMPLE,
+ /*control*/ 0);
+
+ io->io_hdr.nexus.initid = iid;
+ io->io_hdr.nexus.targ_lun = lun;
+
+ if (cctl_do_io(fd, retries, io, __func__) != 0) {
+ retval = 1;
+ goto bailout;
+ }
+
+ if ((io->io_hdr.status & CTL_STATUS_MASK) == CTL_SUCCESS) {
+ uint32_t returned_len, returned_luns;
+
+ returned_len = scsi_4btoul((*lun_data)->length);
+ returned_luns = returned_len / 8;
+ if (returned_luns > nluns) {
+ nluns = returned_luns;
+ free(*lun_data);
+ goto retry;
+ }
+ /* These should be the same */
+ *num_luns = MIN(returned_luns, nluns);
+ } else {
+ ctl_io_error_print(io, NULL, stderr);
+ retval = 1;
+ }
+bailout:
+ ctl_scsi_free_io(io);
+
+ return (retval);
+}
+
+static int
+cctl_report_luns(int fd, int lun, int iid, int retries)
+{
+ struct scsi_report_luns_data *lun_data;
+ uint32_t num_luns, i;
+ int retval;
+
+ lun_data = NULL;
+
+ if ((retval = cctl_get_luns(fd, lun, iid, retries, &lun_data,
+ &num_luns)) != 0)
+ goto bailout;
+
+ fprintf(stdout, "%u LUNs returned\n", num_luns);
+ for (i = 0; i < num_luns; i++) {
+ int lun_val;
+
+ /*
+ * XXX KDM figure out a way to share this code with
+ * cctl_lunlist()?
+ */
+ switch (lun_data->luns[i].lundata[0] & RPL_LUNDATA_ATYP_MASK) {
+ case RPL_LUNDATA_ATYP_PERIPH:
+ lun_val = lun_data->luns[i].lundata[1];
+ break;
+ case RPL_LUNDATA_ATYP_FLAT:
+ lun_val = (lun_data->luns[i].lundata[0] &
+ RPL_LUNDATA_FLAT_LUN_MASK) |
+ (lun_data->luns[i].lundata[1] <<
+ RPL_LUNDATA_FLAT_LUN_BITS);
+ break;
+ case RPL_LUNDATA_ATYP_LUN:
+ case RPL_LUNDATA_ATYP_EXTLUN:
+ default:
+ fprintf(stdout, "Unsupported LUN format %d\n",
+ lun_data->luns[i].lundata[0] &
+ RPL_LUNDATA_ATYP_MASK);
+ lun_val = -1;
+ break;
+ }
+ if (lun_val == -1)
+ continue;
+
+ fprintf(stdout, "%d\n", lun_val);
+ }
+
+bailout:
+ if (lun_data != NULL)
+ free(lun_data);
+
+ return (retval);
+}
+
+static int
+cctl_tur(int fd, int lun, int iid, int retries)
+{
+ union ctl_io *io;
+
+ io = ctl_scsi_alloc_io(iid);
+ if (io == NULL) {
+ fprintf(stderr, "can't allocate memory\n");
+ return (1);
+ }
+
+ ctl_scsi_tur(io,
+ /* tag_type */ CTL_TAG_SIMPLE,
+ /* control */ 0);
+
+ io->io_hdr.nexus.targ_lun = lun;
+ io->io_hdr.nexus.initid = iid;
+
+ if (cctl_do_io(fd, retries, io, __func__) != 0) {
+ ctl_scsi_free_io(io);
+ return (1);
+ }
+
+ if ((io->io_hdr.status & CTL_STATUS_MASK) == CTL_SUCCESS)
+ fprintf(stdout, "Unit is ready\n");
+ else
+ ctl_io_error_print(io, NULL, stderr);
+
+ return (0);
+}
+
+static int
+cctl_get_inquiry(int fd, int lun, int iid, int retries,
+ char *path_str, int path_len,
+ struct scsi_inquiry_data *inq_data)
+{
+ union ctl_io *io;
+ int retval;
+
+ retval = 0;
+
+ io = ctl_scsi_alloc_io(iid);
+ if (io == NULL) {
+ warnx("cctl_inquiry: can't allocate memory\n");
+ return (1);
+ }
+
+ ctl_scsi_inquiry(/*io*/ io,
+ /*data_ptr*/ (uint8_t *)inq_data,
+ /*data_len*/ sizeof(*inq_data),
+ /*byte2*/ 0,
+ /*page_code*/ 0,
+ /*tag_type*/ CTL_TAG_SIMPLE,
+ /*control*/ 0);
+
+ io->io_hdr.nexus.targ_lun = lun;
+ io->io_hdr.nexus.initid = iid;
+
+ if (cctl_do_io(fd, retries, io, __func__) != 0) {
+ retval = 1;
+ goto bailout;
+ }
+
+ if ((io->io_hdr.status & CTL_STATUS_MASK) != CTL_SUCCESS) {
+ retval = 1;
+ ctl_io_error_print(io, NULL, stderr);
+ } else if (path_str != NULL)
+ ctl_scsi_path_string(io, path_str, path_len);
+
+bailout:
+ ctl_scsi_free_io(io);
+
+ return (retval);
+}
+
+static int
+cctl_inquiry(int fd, int lun, int iid, int retries)
+{
+ struct scsi_inquiry_data *inq_data;
+ char scsi_path[40];
+ int retval;
+
+ inq_data = malloc(sizeof(*inq_data));
+ if (inq_data == NULL) {
+ warnx("%s: can't allocate inquiry data", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ if ((retval = cctl_get_inquiry(fd, lun, iid, retries, scsi_path,
+ sizeof(scsi_path), inq_data)) != 0)
+ goto bailout;
+
+ printf("%s", scsi_path);
+ scsi_print_inquiry(inq_data);
+
+bailout:
+ if (inq_data != NULL)
+ free(inq_data);
+
+ return (retval);
+}
+
+static int
+cctl_req_sense(int fd, int lun, int iid, int retries)
+{
+ union ctl_io *io;
+ struct scsi_sense_data *sense_data;
+ int retval;
+
+ retval = 0;
+
+ io = ctl_scsi_alloc_io(iid);
+ if (io == NULL) {
+ warnx("cctl_req_sense: can't allocate memory\n");
+ return (1);
+ }
+ sense_data = malloc(sizeof(*sense_data));
+ memset(sense_data, 0, sizeof(*sense_data));
+
+ ctl_scsi_request_sense(/*io*/ io,
+ /*data_ptr*/ (uint8_t *)sense_data,
+ /*data_len*/ sizeof(*sense_data),
+ /*byte2*/ 0,
+ /*tag_type*/ CTL_TAG_SIMPLE,
+ /*control*/ 0);
+
+ io->io_hdr.nexus.targ_lun = lun;
+ io->io_hdr.nexus.initid = iid;
+
+ if (cctl_do_io(fd, retries, io, __func__) != 0) {
+ retval = 1;
+ goto bailout;
+ }
+
+ if ((io->io_hdr.status & CTL_STATUS_MASK) == CTL_SUCCESS) {
+ bcopy(sense_data, &io->scsiio.sense_data, sizeof(*sense_data));
+ io->scsiio.sense_len = sizeof(*sense_data);
+ ctl_scsi_sense_print(&io->scsiio, NULL, stdout);
+ } else
+ ctl_io_error_print(io, NULL, stderr);
+
+bailout:
+
+ ctl_scsi_free_io(io);
+ free(sense_data);
+
+ return (retval);
+}
+
+static int
+cctl_report_target_port_group(int fd, int lun, int iid)
+{
+ union ctl_io *io;
+ uint32_t datalen;
+ uint8_t *dataptr;
+ int retval;
+
+ dataptr = NULL;
+ retval = 0;
+
+ io = ctl_scsi_alloc_io(iid);
+ if (io == NULL) {
+ warn("%s: can't allocate memory", __func__);
+ return (1);
+ }
+
+ datalen = 64;
+ dataptr = (uint8_t *)malloc(datalen);
+ if (dataptr == NULL) {
+ warn("%s: can't allocate %d bytes", __func__, datalen);
+ retval = 1;
+ goto bailout;
+ }
+
+ memset(dataptr, 0, datalen);
+
+ ctl_scsi_maintenance_in(/*io*/ io,
+ /*data_ptr*/ dataptr,
+ /*data_len*/ datalen,
+ /*action*/ SA_RPRT_TRGT_GRP,
+ /*tag_type*/ CTL_TAG_SIMPLE,
+ /*control*/ 0);
+
+ io->io_hdr.nexus.targ_lun = lun;
+ io->io_hdr.nexus.initid = iid;
+
+ if (cctl_do_io(fd, 0, io, __func__) != 0) {
+ retval = 1;
+ goto bailout;
+ }
+
+ if ((io->io_hdr.status & CTL_STATUS_MASK) == CTL_SUCCESS) {
+ int returned_len, used_len;
+
+ returned_len = scsi_4btoul(&dataptr[0]) + 4;
+
+ for (used_len = 0; used_len < returned_len; used_len++) {
+ fprintf(stdout, "0x%02x ", dataptr[used_len]);
+ if (((used_len+1) % 8) == 0)
+ fprintf(stdout, "\n");
+ }
+ fprintf(stdout, "\n");
+ } else
+ ctl_io_error_print(io, NULL, stderr);
+
+bailout:
+ ctl_scsi_free_io(io);
+
+ if (dataptr != NULL)
+ free(dataptr);
+
+ return (retval);
+}
+
+static int
+cctl_inquiry_vpd_devid(int fd, int lun, int iid)
+{
+ union ctl_io *io;
+ uint32_t datalen;
+ uint8_t *dataptr;
+ int retval;
+
+ retval = 0;
+ dataptr = NULL;
+
+ io = ctl_scsi_alloc_io(iid);
+ if (io == NULL) {
+ warn("%s: can't allocate memory", __func__);
+ return (1);
+ }
+
+ datalen = 256;
+ dataptr = (uint8_t *)malloc(datalen);
+ if (dataptr == NULL) {
+ warn("%s: can't allocate %d bytes", __func__, datalen);
+ retval = 1;
+ goto bailout;
+ }
+
+ memset(dataptr, 0, datalen);
+
+ ctl_scsi_inquiry(/*io*/ io,
+ /*data_ptr*/ dataptr,
+ /*data_len*/ datalen,
+ /*byte2*/ SI_EVPD,
+ /*page_code*/ SVPD_DEVICE_ID,
+ /*tag_type*/ CTL_TAG_SIMPLE,
+ /*control*/ 0);
+
+ io->io_hdr.nexus.targ_lun = lun;
+ io->io_hdr.nexus.initid = iid;
+
+ if (cctl_do_io(fd, 0, io, __func__) != 0) {
+ retval = 1;
+ goto bailout;
+ }
+
+ if ((io->io_hdr.status & CTL_STATUS_MASK) == CTL_SUCCESS) {
+ int returned_len, used_len;
+
+ returned_len = scsi_2btoul(&dataptr[2]) + 4;
+
+ for (used_len = 0; used_len < returned_len; used_len++) {
+ fprintf(stdout, "0x%02x ", dataptr[used_len]);
+ if (((used_len+1) % 8) == 0)
+ fprintf(stdout, "\n");
+ }
+ fprintf(stdout, "\n");
+ } else
+ ctl_io_error_print(io, NULL, stderr);
+
+bailout:
+ ctl_scsi_free_io(io);
+
+ if (dataptr != NULL)
+ free(dataptr);
+
+ return (retval);
+}
+
+static int
+cctl_persistent_reserve_in(int fd, int lun, int iid,
+ int argc, char **argv, char *combinedopt,
+ int retry_count)
+{
+ union ctl_io *io;
+ uint32_t datalen;
+ uint8_t *dataptr;
+ int action = -1;
+ int retval;
+ int c;
+
+ retval = 0;
+ dataptr = NULL;
+
+ io = ctl_scsi_alloc_io(iid);
+ if (io == NULL) {
+ warn("%s: can't allocate memory", __func__);
+ return (1);
+ }
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'a':
+ action = strtol(optarg, NULL, 0);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (action < 0 || action > 2) {
+ warn("action must be specified and in the range: 0-2");
+ retval = 1;
+ goto bailout;
+ }
+
+
+ datalen = 256;
+ dataptr = (uint8_t *)malloc(datalen);
+ if (dataptr == NULL) {
+ warn("%s: can't allocate %d bytes", __func__, datalen);
+ retval = 1;
+ goto bailout;
+ }
+
+ memset(dataptr, 0, datalen);
+
+ ctl_scsi_persistent_res_in(io,
+ /*data_ptr*/ dataptr,
+ /*data_len*/ datalen,
+ /*action*/ action,
+ /*tag_type*/ CTL_TAG_SIMPLE,
+ /*control*/ 0);
+
+ io->io_hdr.nexus.targ_lun = lun;
+ io->io_hdr.nexus.initid = iid;
+
+ if (cctl_do_io(fd, retry_count, io, __func__) != 0) {
+ retval = 1;
+ goto bailout;
+ }
+
+ if ((io->io_hdr.status & CTL_STATUS_MASK) == CTL_SUCCESS) {
+ int returned_len, used_len;
+
+ switch (action) {
+ case 0:
+ returned_len = scsi_4btoul(&dataptr[4]) + 8;
+ returned_len = min(returned_len, 256);
+ break;
+ case 1:
+ returned_len = scsi_4btoul(&dataptr[4]) + 8;
+ break;
+ case 2:
+ returned_len = 8;
+ break;
+ default:
+ warnx("%s: invalid action %d", __func__, action);
+ goto bailout;
+ break; /* NOTREACHED */
+ }
+
+ for (used_len = 0; used_len < returned_len; used_len++) {
+ fprintf(stdout, "0x%02x ", dataptr[used_len]);
+ if (((used_len+1) % 8) == 0)
+ fprintf(stdout, "\n");
+ }
+ fprintf(stdout, "\n");
+ } else
+ ctl_io_error_print(io, NULL, stderr);
+
+bailout:
+ ctl_scsi_free_io(io);
+
+ if (dataptr != NULL)
+ free(dataptr);
+
+ return (retval);
+}
+
+static int
+cctl_persistent_reserve_out(int fd, int lun, int iid,
+ int argc, char **argv, char *combinedopt,
+ int retry_count)
+{
+ union ctl_io *io;
+ uint32_t datalen;
+ uint64_t key = 0, sa_key = 0;
+ int action = -1, restype = -1;
+ uint8_t *dataptr;
+ int retval;
+ int c;
+
+ retval = 0;
+ dataptr = NULL;
+
+ io = ctl_scsi_alloc_io(iid);
+ if (io == NULL) {
+ warn("%s: can't allocate memory", __func__);
+ return (1);
+ }
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'a':
+ action = strtol(optarg, NULL, 0);
+ break;
+ case 'k':
+ key = strtoull(optarg, NULL, 0);
+ break;
+ case 'r':
+ restype = strtol(optarg, NULL, 0);
+ break;
+ case 's':
+ sa_key = strtoull(optarg, NULL, 0);
+ break;
+ default:
+ break;
+ }
+ }
+ if (action < 0 || action > 5) {
+ warn("action must be specified and in the range: 0-5");
+ retval = 1;
+ goto bailout;
+ }
+
+ if (restype < 0 || restype > 5) {
+ if (action != 0 && action != 5 && action != 3) {
+ warn("'restype' must specified and in the range: 0-5");
+ retval = 1;
+ goto bailout;
+ }
+ }
+
+ datalen = 24;
+ dataptr = (uint8_t *)malloc(datalen);
+ if (dataptr == NULL) {
+ warn("%s: can't allocate %d bytes", __func__, datalen);
+ retval = 1;
+ goto bailout;
+ }
+
+ memset(dataptr, 0, datalen);
+
+ ctl_scsi_persistent_res_out(io,
+ /*data_ptr*/ dataptr,
+ /*data_len*/ datalen,
+ /*action*/ action,
+ /*type*/ restype,
+ /*key*/ key,
+ /*sa key*/ sa_key,
+ /*tag_type*/ CTL_TAG_SIMPLE,
+ /*control*/ 0);
+
+ io->io_hdr.nexus.targ_lun = lun;
+ io->io_hdr.nexus.initid = iid;
+
+ if (cctl_do_io(fd, retry_count, io, __func__) != 0) {
+ retval = 1;
+ goto bailout;
+ }
+ if ((io->io_hdr.status & CTL_STATUS_MASK) == CTL_SUCCESS) {
+ char scsi_path[40];
+ ctl_scsi_path_string(io, scsi_path, sizeof(scsi_path));
+ fprintf( stdout, "%sPERSISTENT RESERVE OUT executed "
+ "successfully\n", scsi_path);
+ } else
+ ctl_io_error_print(io, NULL, stderr);
+
+bailout:
+ ctl_scsi_free_io(io);
+
+ if (dataptr != NULL)
+ free(dataptr);
+
+ return (retval);
+}
+
+struct cctl_req_option {
+ char *name;
+ int namelen;
+ char *value;
+ int vallen;
+ STAILQ_ENTRY(cctl_req_option) links;
+};
+
+static int
+cctl_create_lun(int fd, int argc, char **argv, char *combinedopt)
+{
+ struct ctl_lun_req req;
+ int device_type = -1;
+ uint64_t lun_size = 0;
+ uint32_t blocksize = 0, req_lun_id = 0;
+ char *serial_num = NULL;
+ char *device_id = NULL;
+ int lun_size_set = 0, blocksize_set = 0, lun_id_set = 0;
+ char *backend_name = NULL;
+ STAILQ_HEAD(, cctl_req_option) option_list;
+ int num_options = 0;
+ int retval = 0, c;
+
+ STAILQ_INIT(&option_list);
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'b':
+ backend_name = strdup(optarg);
+ break;
+ case 'B':
+ blocksize = strtoul(optarg, NULL, 0);
+ blocksize_set = 1;
+ break;
+ case 'd':
+ device_id = strdup(optarg);
+ break;
+ case 'l':
+ req_lun_id = strtoul(optarg, NULL, 0);
+ lun_id_set = 1;
+ break;
+ case 'o': {
+ struct cctl_req_option *option;
+ char *tmpstr;
+ char *name, *value;
+
+ tmpstr = strdup(optarg);
+ name = strsep(&tmpstr, "=");
+ if (name == NULL) {
+ warnx("%s: option -o takes \"name=value\""
+ "argument", __func__);
+ retval = 1;
+ goto bailout;
+ }
+ value = strsep(&tmpstr, "=");
+ if (value == NULL) {
+ warnx("%s: option -o takes \"name=value\""
+ "argument", __func__);
+ retval = 1;
+ goto bailout;
+ }
+ option = malloc(sizeof(*option));
+ if (option == NULL) {
+ warn("%s: error allocating %zd bytes",
+ __func__, sizeof(*option));
+ retval = 1;
+ goto bailout;
+ }
+ option->name = strdup(name);
+ option->namelen = strlen(name) + 1;
+ option->value = strdup(value);
+ option->vallen = strlen(value) + 1;
+ free(tmpstr);
+
+ STAILQ_INSERT_TAIL(&option_list, option, links);
+ num_options++;
+ break;
+ }
+ case 's':
+ if (strcasecmp(optarg, "auto") != 0) {
+ retval = expand_number(optarg, &lun_size);
+ if (retval != 0) {
+ warn("%s: invalid -s argument",
+ __func__);
+ retval = 1;
+ goto bailout;
+ }
+ }
+ lun_size_set = 1;
+ break;
+ case 'S':
+ serial_num = strdup(optarg);
+ break;
+ case 't':
+ device_type = strtoul(optarg, NULL, 0);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (backend_name == NULL) {
+ warnx("%s: backend name (-b) must be specified", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ bzero(&req, sizeof(req));
+
+ strlcpy(req.backend, backend_name, sizeof(req.backend));
+ req.reqtype = CTL_LUNREQ_CREATE;
+
+ if (blocksize_set != 0)
+ req.reqdata.create.blocksize_bytes = blocksize;
+
+ if (lun_size_set != 0)
+ req.reqdata.create.lun_size_bytes = lun_size;
+
+ if (lun_id_set != 0) {
+ req.reqdata.create.flags |= CTL_LUN_FLAG_ID_REQ;
+ req.reqdata.create.req_lun_id = req_lun_id;
+ }
+
+ req.reqdata.create.flags |= CTL_LUN_FLAG_DEV_TYPE;
+
+ if (device_type != -1)
+ req.reqdata.create.device_type = device_type;
+ else
+ req.reqdata.create.device_type = T_DIRECT;
+
+ if (serial_num != NULL) {
+ strlcpy(req.reqdata.create.serial_num, serial_num,
+ sizeof(req.reqdata.create.serial_num));
+ req.reqdata.create.flags |= CTL_LUN_FLAG_SERIAL_NUM;
+ }
+
+ if (device_id != NULL) {
+ strlcpy(req.reqdata.create.device_id, device_id,
+ sizeof(req.reqdata.create.device_id));
+ req.reqdata.create.flags |= CTL_LUN_FLAG_DEVID;
+ }
+
+ req.num_be_args = num_options;
+ if (num_options > 0) {
+ struct cctl_req_option *option, *next_option;
+ int i;
+
+ req.be_args = malloc(num_options * sizeof(*req.be_args));
+ if (req.be_args == NULL) {
+ warn("%s: error allocating %zd bytes", __func__,
+ num_options * sizeof(*req.be_args));
+ retval = 1;
+ goto bailout;
+ }
+
+ for (i = 0, option = STAILQ_FIRST(&option_list);
+ i < num_options; i++, option = next_option) {
+ next_option = STAILQ_NEXT(option, links);
+
+ req.be_args[i].namelen = option->namelen;
+ req.be_args[i].name = strdup(option->name);
+ req.be_args[i].vallen = option->vallen;
+ req.be_args[i].value = strdup(option->value);
+ /*
+ * XXX KDM do we want a way to specify a writeable
+ * flag of some sort? Do we want a way to specify
+ * binary data?
+ */
+ req.be_args[i].flags = CTL_BEARG_ASCII | CTL_BEARG_RD;
+
+ STAILQ_REMOVE(&option_list, option, cctl_req_option,
+ links);
+ free(option->name);
+ free(option->value);
+ free(option);
+ }
+ }
+
+ if (ioctl(fd, CTL_LUN_REQ, &req) == -1) {
+ warn("%s: error issuing CTL_LUN_REQ ioctl", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ switch (req.status) {
+ case CTL_LUN_ERROR:
+ warnx("LUN creation error: %s", req.error_str);
+ retval = 1;
+ goto bailout;
+ case CTL_LUN_WARNING:
+ warnx("LUN creation warning: %s", req.error_str);
+ break;
+ case CTL_LUN_OK:
+ break;
+ default:
+ warnx("unknown LUN creation status: %d", req.status);
+ retval = 1;
+ goto bailout;
+ }
+
+ fprintf(stdout, "LUN created successfully\n");
+ fprintf(stdout, "backend: %s\n", req.backend);
+ fprintf(stdout, "device type: %d\n",req.reqdata.create.device_type);
+ fprintf(stdout, "LUN size: %ju bytes\n",
+ (uintmax_t)req.reqdata.create.lun_size_bytes);
+ fprintf(stdout, "blocksize %u bytes\n",
+ req.reqdata.create.blocksize_bytes);
+ fprintf(stdout, "LUN ID: %d\n", req.reqdata.create.req_lun_id);
+ fprintf(stdout, "Serial Number: %s\n", req.reqdata.create.serial_num);
+ fprintf(stdout, "Device ID; %s\n", req.reqdata.create.device_id);
+
+bailout:
+ return (retval);
+}
+
+static int
+cctl_rm_lun(int fd, int argc, char **argv, char *combinedopt)
+{
+ struct ctl_lun_req req;
+ uint32_t lun_id = 0;
+ int lun_id_set = 0;
+ char *backend_name = NULL;
+ STAILQ_HEAD(, cctl_req_option) option_list;
+ int num_options = 0;
+ int retval = 0, c;
+
+ STAILQ_INIT(&option_list);
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'b':
+ backend_name = strdup(optarg);
+ break;
+ case 'l':
+ lun_id = strtoul(optarg, NULL, 0);
+ lun_id_set = 1;
+ break;
+ case 'o': {
+ struct cctl_req_option *option;
+ char *tmpstr;
+ char *name, *value;
+
+ tmpstr = strdup(optarg);
+ name = strsep(&tmpstr, "=");
+ if (name == NULL) {
+ warnx("%s: option -o takes \"name=value\""
+ "argument", __func__);
+ retval = 1;
+ goto bailout;
+ }
+ value = strsep(&tmpstr, "=");
+ if (value == NULL) {
+ warnx("%s: option -o takes \"name=value\""
+ "argument", __func__);
+ retval = 1;
+ goto bailout;
+ }
+ option = malloc(sizeof(*option));
+ if (option == NULL) {
+ warn("%s: error allocating %zd bytes",
+ __func__, sizeof(*option));
+ retval = 1;
+ goto bailout;
+ }
+ option->name = strdup(name);
+ option->namelen = strlen(name) + 1;
+ option->value = strdup(value);
+ option->vallen = strlen(value) + 1;
+ free(tmpstr);
+
+ STAILQ_INSERT_TAIL(&option_list, option, links);
+ num_options++;
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ if (backend_name == NULL)
+ errx(1, "%s: backend name (-b) must be specified", __func__);
+
+ if (lun_id_set == 0)
+ errx(1, "%s: LUN id (-l) must be specified", __func__);
+
+ bzero(&req, sizeof(req));
+
+ strlcpy(req.backend, backend_name, sizeof(req.backend));
+ req.reqtype = CTL_LUNREQ_RM;
+
+ req.reqdata.rm.lun_id = lun_id;
+
+ req.num_be_args = num_options;
+ if (num_options > 0) {
+ struct cctl_req_option *option, *next_option;
+ int i;
+
+ req.be_args = malloc(num_options * sizeof(*req.be_args));
+ if (req.be_args == NULL) {
+ warn("%s: error allocating %zd bytes", __func__,
+ num_options * sizeof(*req.be_args));
+ retval = 1;
+ goto bailout;
+ }
+
+ for (i = 0, option = STAILQ_FIRST(&option_list);
+ i < num_options; i++, option = next_option) {
+ next_option = STAILQ_NEXT(option, links);
+
+ req.be_args[i].namelen = option->namelen;
+ req.be_args[i].name = strdup(option->name);
+ req.be_args[i].vallen = option->vallen;
+ req.be_args[i].value = strdup(option->value);
+ /*
+ * XXX KDM do we want a way to specify a writeable
+ * flag of some sort? Do we want a way to specify
+ * binary data?
+ */
+ req.be_args[i].flags = CTL_BEARG_ASCII | CTL_BEARG_RD;
+
+ STAILQ_REMOVE(&option_list, option, cctl_req_option,
+ links);
+ free(option->name);
+ free(option->value);
+ free(option);
+ }
+ }
+
+ if (ioctl(fd, CTL_LUN_REQ, &req) == -1) {
+ warn("%s: error issuing CTL_LUN_REQ ioctl", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ switch (req.status) {
+ case CTL_LUN_ERROR:
+ warnx("LUN removal error: %s", req.error_str);
+ retval = 1;
+ goto bailout;
+ case CTL_LUN_WARNING:
+ warnx("LUN removal warning: %s", req.error_str);
+ break;
+ case CTL_LUN_OK:
+ break;
+ default:
+ warnx("unknown LUN removal status: %d", req.status);
+ retval = 1;
+ goto bailout;
+ }
+
+ printf("LUN %d removed successfully\n", lun_id);
+
+bailout:
+ return (retval);
+}
+
+static int
+cctl_modify_lun(int fd, int argc, char **argv, char *combinedopt)
+{
+ struct ctl_lun_req req;
+ uint64_t lun_size = 0;
+ uint32_t lun_id = 0;
+ int lun_id_set = 0, lun_size_set = 0;
+ char *backend_name = NULL;
+ STAILQ_HEAD(, cctl_req_option) option_list;
+ int num_options = 0;
+ int retval = 0, c;
+
+ STAILQ_INIT(&option_list);
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'b':
+ backend_name = strdup(optarg);
+ break;
+ case 'l':
+ lun_id = strtoul(optarg, NULL, 0);
+ lun_id_set = 1;
+ break;
+ case 'o': {
+ struct cctl_req_option *option;
+ char *tmpstr;
+ char *name, *value;
+
+ tmpstr = strdup(optarg);
+ name = strsep(&tmpstr, "=");
+ if (name == NULL) {
+ warnx("%s: option -o takes \"name=value\""
+ "argument", __func__);
+ retval = 1;
+ goto bailout;
+ }
+ value = strsep(&tmpstr, "=");
+ if (value == NULL) {
+ warnx("%s: option -o takes \"name=value\""
+ "argument", __func__);
+ retval = 1;
+ goto bailout;
+ }
+ option = malloc(sizeof(*option));
+ if (option == NULL) {
+ warn("%s: error allocating %zd bytes",
+ __func__, sizeof(*option));
+ retval = 1;
+ goto bailout;
+ }
+ option->name = strdup(name);
+ option->namelen = strlen(name) + 1;
+ option->value = strdup(value);
+ option->vallen = strlen(value) + 1;
+ free(tmpstr);
+
+ STAILQ_INSERT_TAIL(&option_list, option, links);
+ num_options++;
+ break;
+ }
+ case 's':
+ if (strcasecmp(optarg, "auto") != 0) {
+ retval = expand_number(optarg, &lun_size);
+ if (retval != 0) {
+ warn("%s: invalid -s argument",
+ __func__);
+ retval = 1;
+ goto bailout;
+ }
+ }
+ lun_size_set = 1;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (backend_name == NULL)
+ errx(1, "%s: backend name (-b) must be specified", __func__);
+
+ if (lun_id_set == 0)
+ errx(1, "%s: LUN id (-l) must be specified", __func__);
+
+ if (lun_size_set == 0 && num_options == 0)
+ errx(1, "%s: size (-s) or options (-o) must be specified",
+ __func__);
+
+ bzero(&req, sizeof(req));
+
+ strlcpy(req.backend, backend_name, sizeof(req.backend));
+ req.reqtype = CTL_LUNREQ_MODIFY;
+
+ req.reqdata.modify.lun_id = lun_id;
+ req.reqdata.modify.lun_size_bytes = lun_size;
+
+ req.num_be_args = num_options;
+ if (num_options > 0) {
+ struct cctl_req_option *option, *next_option;
+ int i;
+
+ req.be_args = malloc(num_options * sizeof(*req.be_args));
+ if (req.be_args == NULL) {
+ warn("%s: error allocating %zd bytes", __func__,
+ num_options * sizeof(*req.be_args));
+ retval = 1;
+ goto bailout;
+ }
+
+ for (i = 0, option = STAILQ_FIRST(&option_list);
+ i < num_options; i++, option = next_option) {
+ next_option = STAILQ_NEXT(option, links);
+
+ req.be_args[i].namelen = option->namelen;
+ req.be_args[i].name = strdup(option->name);
+ req.be_args[i].vallen = option->vallen;
+ req.be_args[i].value = strdup(option->value);
+ /*
+ * XXX KDM do we want a way to specify a writeable
+ * flag of some sort? Do we want a way to specify
+ * binary data?
+ */
+ req.be_args[i].flags = CTL_BEARG_ASCII | CTL_BEARG_RD;
+
+ STAILQ_REMOVE(&option_list, option, cctl_req_option,
+ links);
+ free(option->name);
+ free(option->value);
+ free(option);
+ }
+ }
+
+ if (ioctl(fd, CTL_LUN_REQ, &req) == -1) {
+ warn("%s: error issuing CTL_LUN_REQ ioctl", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ switch (req.status) {
+ case CTL_LUN_ERROR:
+ warnx("LUN modification error: %s", req.error_str);
+ retval = 1;
+ goto bailout;
+ case CTL_LUN_WARNING:
+ warnx("LUN modification warning: %s", req.error_str);
+ break;
+ case CTL_LUN_OK:
+ break;
+ default:
+ warnx("unknown LUN modification status: %d", req.status);
+ retval = 1;
+ goto bailout;
+ }
+
+ printf("LUN %d modified successfully\n", lun_id);
+
+bailout:
+ return (retval);
+}
+
+struct cctl_islist_conn {
+ int connection_id;
+ char *initiator;
+ char *initiator_addr;
+ char *initiator_alias;
+ char *target;
+ char *target_alias;
+ char *header_digest;
+ char *data_digest;
+ char *max_data_segment_length;;
+ char *offload;;
+ int immediate_data;
+ int iser;
+ STAILQ_ENTRY(cctl_islist_conn) links;
+};
+
+struct cctl_islist_data {
+ int num_conns;
+ STAILQ_HEAD(,cctl_islist_conn) conn_list;
+ struct cctl_islist_conn *cur_conn;
+ int level;
+ struct sbuf *cur_sb[32];
+};
+
+static void
+cctl_islist_start_element(void *user_data, const char *name, const char **attr)
+{
+ int i;
+ struct cctl_islist_data *islist;
+ struct cctl_islist_conn *cur_conn;
+
+ islist = (struct cctl_islist_data *)user_data;
+ cur_conn = islist->cur_conn;
+ islist->level++;
+ if ((u_int)islist->level >= (sizeof(islist->cur_sb) /
+ sizeof(islist->cur_sb[0])))
+ errx(1, "%s: too many nesting levels, %zd max", __func__,
+ sizeof(islist->cur_sb) / sizeof(islist->cur_sb[0]));
+
+ islist->cur_sb[islist->level] = sbuf_new_auto();
+ if (islist->cur_sb[islist->level] == NULL)
+ err(1, "%s: Unable to allocate sbuf", __func__);
+
+ if (strcmp(name, "connection") == 0) {
+ if (cur_conn != NULL)
+ errx(1, "%s: improper connection element nesting",
+ __func__);
+
+ cur_conn = calloc(1, sizeof(*cur_conn));
+ if (cur_conn == NULL)
+ err(1, "%s: cannot allocate %zd bytes", __func__,
+ sizeof(*cur_conn));
+
+ islist->num_conns++;
+ islist->cur_conn = cur_conn;
+
+ STAILQ_INSERT_TAIL(&islist->conn_list, cur_conn, links);
+
+ for (i = 0; attr[i] != NULL; i += 2) {
+ if (strcmp(attr[i], "id") == 0) {
+ cur_conn->connection_id =
+ strtoull(attr[i+1], NULL, 0);
+ } else {
+ errx(1,
+ "%s: invalid connection attribute %s = %s",
+ __func__, attr[i], attr[i+1]);
+ }
+ }
+ }
+}
+
+static void
+cctl_islist_end_element(void *user_data, const char *name)
+{
+ struct cctl_islist_data *islist;
+ struct cctl_islist_conn *cur_conn;
+ char *str;
+
+ islist = (struct cctl_islist_data *)user_data;
+ cur_conn = islist->cur_conn;
+
+ if ((cur_conn == NULL)
+ && (strcmp(name, "ctlislist") != 0))
+ errx(1, "%s: cur_conn == NULL! (name = %s)", __func__, name);
+
+ if (islist->cur_sb[islist->level] == NULL)
+ errx(1, "%s: no valid sbuf at level %d (name %s)", __func__,
+ islist->level, name);
+
+ sbuf_finish(islist->cur_sb[islist->level]);
+ str = strdup(sbuf_data(islist->cur_sb[islist->level]));
+ if (str == NULL)
+ err(1, "%s can't allocate %zd bytes for string", __func__,
+ sbuf_len(islist->cur_sb[islist->level]));
+
+ sbuf_delete(islist->cur_sb[islist->level]);
+ islist->cur_sb[islist->level] = NULL;
+ islist->level--;
+
+ if (strcmp(name, "initiator") == 0) {
+ cur_conn->initiator = str;
+ str = NULL;
+ } else if (strcmp(name, "initiator_addr") == 0) {
+ cur_conn->initiator_addr = str;
+ str = NULL;
+ } else if (strcmp(name, "initiator_alias") == 0) {
+ cur_conn->initiator_alias = str;
+ str = NULL;
+ } else if (strcmp(name, "target") == 0) {
+ cur_conn->target = str;
+ str = NULL;
+ } else if (strcmp(name, "target_alias") == 0) {
+ cur_conn->target_alias = str;
+ str = NULL;
+ } else if (strcmp(name, "target_portal_group_tag") == 0) {
+ } else if (strcmp(name, "header_digest") == 0) {
+ cur_conn->header_digest = str;
+ str = NULL;
+ } else if (strcmp(name, "data_digest") == 0) {
+ cur_conn->data_digest = str;
+ str = NULL;
+ } else if (strcmp(name, "max_data_segment_length") == 0) {
+ cur_conn->max_data_segment_length = str;
+ str = NULL;
+ } else if (strcmp(name, "offload") == 0) {
+ cur_conn->offload = str;
+ str = NULL;
+ } else if (strcmp(name, "immediate_data") == 0) {
+ cur_conn->immediate_data = atoi(str);
+ } else if (strcmp(name, "iser") == 0) {
+ cur_conn->iser = atoi(str);
+ } else if (strcmp(name, "connection") == 0) {
+ islist->cur_conn = NULL;
+ } else if (strcmp(name, "ctlislist") == 0) {
+ /* Nothing. */
+ } else {
+ /*
+ * Unknown element; ignore it for forward compatiblity.
+ */
+ }
+
+ free(str);
+}
+
+static void
+cctl_islist_char_handler(void *user_data, const XML_Char *str, int len)
+{
+ struct cctl_islist_data *islist;
+
+ islist = (struct cctl_islist_data *)user_data;
+
+ sbuf_bcat(islist->cur_sb[islist->level], str, len);
+}
+
+static int
+cctl_islist(int fd, int argc, char **argv, char *combinedopt)
+{
+ struct ctl_iscsi req;
+ struct cctl_islist_data islist;
+ struct cctl_islist_conn *conn;
+ XML_Parser parser;
+ char *conn_str;
+ int conn_len;
+ int dump_xml = 0;
+ int c, retval, verbose = 0;
+
+ retval = 0;
+ conn_len = 4096;
+
+ bzero(&islist, sizeof(islist));
+ STAILQ_INIT(&islist.conn_list);
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'v':
+ verbose = 1;
+ break;
+ case 'x':
+ dump_xml = 1;
+ break;
+ default:
+ break;
+ }
+ }
+
+retry:
+ conn_str = malloc(conn_len);
+
+ bzero(&req, sizeof(req));
+ req.type = CTL_ISCSI_LIST;
+ req.data.list.alloc_len = conn_len;
+ req.data.list.conn_xml = conn_str;
+
+ if (ioctl(fd, CTL_ISCSI, &req) == -1) {
+ warn("%s: error issuing CTL_ISCSI ioctl", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ if (req.status == CTL_ISCSI_ERROR) {
+ warnx("%s: error returned from CTL_ISCSI ioctl:\n%s",
+ __func__, req.error_str);
+ } else if (req.status == CTL_ISCSI_LIST_NEED_MORE_SPACE) {
+ conn_len = conn_len << 1;
+ goto retry;
+ }
+
+ if (dump_xml != 0) {
+ printf("%s", conn_str);
+ goto bailout;
+ }
+
+ parser = XML_ParserCreate(NULL);
+ if (parser == NULL) {
+ warn("%s: Unable to create XML parser", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ XML_SetUserData(parser, &islist);
+ XML_SetElementHandler(parser, cctl_islist_start_element,
+ cctl_islist_end_element);
+ XML_SetCharacterDataHandler(parser, cctl_islist_char_handler);
+
+ retval = XML_Parse(parser, conn_str, strlen(conn_str), 1);
+ if (retval != 1) {
+ warnx("%s: Unable to parse XML: Error %d", __func__,
+ XML_GetErrorCode(parser));
+ XML_ParserFree(parser);
+ retval = 1;
+ goto bailout;
+ }
+ retval = 0;
+ XML_ParserFree(parser);
+
+ if (verbose != 0) {
+ STAILQ_FOREACH(conn, &islist.conn_list, links) {
+ printf("Session ID: %d\n", conn->connection_id);
+ printf("Initiator name: %s\n", conn->initiator);
+ printf("Initiator portal: %s\n", conn->initiator_addr);
+ printf("Initiator alias: %s\n", conn->initiator_alias);
+ printf("Target name: %s\n", conn->target);
+ printf("Target alias: %s\n", conn->target_alias);
+ printf("Header digest: %s\n", conn->header_digest);
+ printf("Data digest: %s\n", conn->data_digest);
+ printf("DataSegmentLen: %s\n", conn->max_data_segment_length);
+ printf("ImmediateData: %s\n", conn->immediate_data ? "Yes" : "No");
+ printf("iSER (RDMA): %s\n", conn->iser ? "Yes" : "No");
+ printf("Offload driver: %s\n", conn->offload);
+ printf("\n");
+ }
+ } else {
+ printf("%4s %-16s %-36s %-36s\n", "ID", "Portal", "Initiator name",
+ "Target name");
+ STAILQ_FOREACH(conn, &islist.conn_list, links) {
+ printf("%4u %-16s %-36s %-36s\n",
+ conn->connection_id, conn->initiator_addr, conn->initiator,
+ conn->target);
+ }
+ }
+bailout:
+ free(conn_str);
+
+ return (retval);
+}
+
+static int
+cctl_islogout(int fd, int argc, char **argv, char *combinedopt)
+{
+ struct ctl_iscsi req;
+ int retval = 0, c;
+ int all = 0, connection_id = -1, nargs = 0;
+ char *initiator_name = NULL, *initiator_addr = NULL;
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'a':
+ all = 1;
+ nargs++;
+ break;
+ case 'c':
+ connection_id = strtoul(optarg, NULL, 0);
+ nargs++;
+ break;
+ case 'i':
+ initiator_name = strdup(optarg);
+ if (initiator_name == NULL)
+ err(1, "%s: strdup", __func__);
+ nargs++;
+ break;
+ case 'p':
+ initiator_addr = strdup(optarg);
+ if (initiator_addr == NULL)
+ err(1, "%s: strdup", __func__);
+ nargs++;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (nargs == 0)
+ errx(1, "%s: either -a, -c, -i, or -p must be specified",
+ __func__);
+ if (nargs > 1)
+ errx(1, "%s: only one of -a, -c, -i, or -p may be specified",
+ __func__);
+
+ bzero(&req, sizeof(req));
+ req.type = CTL_ISCSI_LOGOUT;
+ req.data.logout.connection_id = connection_id;
+ if (initiator_addr != NULL)
+ strlcpy(req.data.logout.initiator_addr,
+ initiator_addr, sizeof(req.data.logout.initiator_addr));
+ if (initiator_name != NULL)
+ strlcpy(req.data.logout.initiator_name,
+ initiator_name, sizeof(req.data.logout.initiator_name));
+ if (all != 0)
+ req.data.logout.all = 1;
+
+ if (ioctl(fd, CTL_ISCSI, &req) == -1) {
+ warn("%s: error issuing CTL_ISCSI ioctl", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ if (req.status != CTL_ISCSI_OK) {
+ warnx("%s: error returned from CTL iSCSI logout request:\n%s",
+ __func__, req.error_str);
+ retval = 1;
+ goto bailout;
+ }
+
+ printf("iSCSI logout requests submitted\n");
+
+bailout:
+ return (retval);
+}
+
+static int
+cctl_isterminate(int fd, int argc, char **argv, char *combinedopt)
+{
+ struct ctl_iscsi req;
+ int retval = 0, c;
+ int all = 0, connection_id = -1, nargs = 0;
+ char *initiator_name = NULL, *initiator_addr = NULL;
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'a':
+ all = 1;
+ nargs++;
+ break;
+ case 'c':
+ connection_id = strtoul(optarg, NULL, 0);
+ nargs++;
+ break;
+ case 'i':
+ initiator_name = strdup(optarg);
+ if (initiator_name == NULL)
+ err(1, "%s: strdup", __func__);
+ nargs++;
+ break;
+ case 'p':
+ initiator_addr = strdup(optarg);
+ if (initiator_addr == NULL)
+ err(1, "%s: strdup", __func__);
+ nargs++;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (nargs == 0)
+ errx(1, "%s: either -a, -c, -i, or -p must be specified",
+ __func__);
+ if (nargs > 1)
+ errx(1, "%s: only one of -a, -c, -i, or -p may be specified",
+ __func__);
+
+ bzero(&req, sizeof(req));
+ req.type = CTL_ISCSI_TERMINATE;
+ req.data.terminate.connection_id = connection_id;
+ if (initiator_addr != NULL)
+ strlcpy(req.data.terminate.initiator_addr,
+ initiator_addr, sizeof(req.data.terminate.initiator_addr));
+ if (initiator_name != NULL)
+ strlcpy(req.data.terminate.initiator_name,
+ initiator_name, sizeof(req.data.terminate.initiator_name));
+ if (all != 0)
+ req.data.terminate.all = 1;
+
+ if (ioctl(fd, CTL_ISCSI, &req) == -1) {
+ warn("%s: error issuing CTL_ISCSI ioctl", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ if (req.status != CTL_ISCSI_OK) {
+ warnx("%s: error returned from CTL iSCSI connection "
+ "termination request:\n%s", __func__, req.error_str);
+ retval = 1;
+ goto bailout;
+ }
+
+ printf("iSCSI connections terminated\n");
+
+bailout:
+ return (retval);
+}
+
+/*
+ * Name/value pair used for per-LUN attributes.
+ */
+struct cctl_lun_nv {
+ char *name;
+ char *value;
+ STAILQ_ENTRY(cctl_lun_nv) links;
+};
+
+/*
+ * Backend LUN information.
+ */
+struct cctl_lun {
+ uint64_t lun_id;
+ char *backend_type;
+ uint64_t size_blocks;
+ uint32_t blocksize;
+ char *serial_number;
+ char *device_id;
+ STAILQ_HEAD(,cctl_lun_nv) attr_list;
+ STAILQ_ENTRY(cctl_lun) links;
+};
+
+struct cctl_devlist_data {
+ int num_luns;
+ STAILQ_HEAD(,cctl_lun) lun_list;
+ struct cctl_lun *cur_lun;
+ int level;
+ struct sbuf *cur_sb[32];
+};
+
+static void
+cctl_start_element(void *user_data, const char *name, const char **attr)
+{
+ int i;
+ struct cctl_devlist_data *devlist;
+ struct cctl_lun *cur_lun;
+
+ devlist = (struct cctl_devlist_data *)user_data;
+ cur_lun = devlist->cur_lun;
+ devlist->level++;
+ if ((u_int)devlist->level >= (sizeof(devlist->cur_sb) /
+ sizeof(devlist->cur_sb[0])))
+ errx(1, "%s: too many nesting levels, %zd max", __func__,
+ sizeof(devlist->cur_sb) / sizeof(devlist->cur_sb[0]));
+
+ devlist->cur_sb[devlist->level] = sbuf_new_auto();
+ if (devlist->cur_sb[devlist->level] == NULL)
+ err(1, "%s: Unable to allocate sbuf", __func__);
+
+ if (strcmp(name, "lun") == 0) {
+ if (cur_lun != NULL)
+ errx(1, "%s: improper lun element nesting", __func__);
+
+ cur_lun = calloc(1, sizeof(*cur_lun));
+ if (cur_lun == NULL)
+ err(1, "%s: cannot allocate %zd bytes", __func__,
+ sizeof(*cur_lun));
+
+ devlist->num_luns++;
+ devlist->cur_lun = cur_lun;
+
+ STAILQ_INIT(&cur_lun->attr_list);
+ STAILQ_INSERT_TAIL(&devlist->lun_list, cur_lun, links);
+
+ for (i = 0; attr[i] != NULL; i += 2) {
+ if (strcmp(attr[i], "id") == 0) {
+ cur_lun->lun_id = strtoull(attr[i+1], NULL, 0);
+ } else {
+ errx(1, "%s: invalid LUN attribute %s = %s",
+ __func__, attr[i], attr[i+1]);
+ }
+ }
+ }
+}
+
+static void
+cctl_end_element(void *user_data, const char *name)
+{
+ struct cctl_devlist_data *devlist;
+ struct cctl_lun *cur_lun;
+ char *str;
+
+ devlist = (struct cctl_devlist_data *)user_data;
+ cur_lun = devlist->cur_lun;
+
+ if ((cur_lun == NULL)
+ && (strcmp(name, "ctllunlist") != 0))
+ errx(1, "%s: cur_lun == NULL! (name = %s)", __func__, name);
+
+ if (devlist->cur_sb[devlist->level] == NULL)
+ errx(1, "%s: no valid sbuf at level %d (name %s)", __func__,
+ devlist->level, name);
+
+ if (sbuf_finish(devlist->cur_sb[devlist->level]) != 0)
+ err(1, "%s: sbuf_finish", __func__);
+ str = strdup(sbuf_data(devlist->cur_sb[devlist->level]));
+ if (str == NULL)
+ err(1, "%s can't allocate %zd bytes for string", __func__,
+ sbuf_len(devlist->cur_sb[devlist->level]));
+
+ if (strlen(str) == 0) {
+ free(str);
+ str = NULL;
+ }
+
+ sbuf_delete(devlist->cur_sb[devlist->level]);
+ devlist->cur_sb[devlist->level] = NULL;
+ devlist->level--;
+
+ if (strcmp(name, "backend_type") == 0) {
+ cur_lun->backend_type = str;
+ str = NULL;
+ } else if (strcmp(name, "size") == 0) {
+ cur_lun->size_blocks = strtoull(str, NULL, 0);
+ } else if (strcmp(name, "blocksize") == 0) {
+ cur_lun->blocksize = strtoul(str, NULL, 0);
+ } else if (strcmp(name, "serial_number") == 0) {
+ cur_lun->serial_number = str;
+ str = NULL;
+ } else if (strcmp(name, "device_id") == 0) {
+ cur_lun->device_id = str;
+ str = NULL;
+ } else if (strcmp(name, "lun") == 0) {
+ devlist->cur_lun = NULL;
+ } else if (strcmp(name, "ctllunlist") == 0) {
+ /* Nothing. */
+ } else {
+ struct cctl_lun_nv *nv;
+
+ nv = calloc(1, sizeof(*nv));
+ if (nv == NULL)
+ err(1, "%s: can't allocate %zd bytes for nv pair",
+ __func__, sizeof(*nv));
+
+ nv->name = strdup(name);
+ if (nv->name == NULL)
+ err(1, "%s: can't allocated %zd bytes for string",
+ __func__, strlen(name));
+
+ nv->value = str;
+ str = NULL;
+ STAILQ_INSERT_TAIL(&cur_lun->attr_list, nv, links);
+ }
+
+ free(str);
+}
+
+static void
+cctl_char_handler(void *user_data, const XML_Char *str, int len)
+{
+ struct cctl_devlist_data *devlist;
+
+ devlist = (struct cctl_devlist_data *)user_data;
+
+ sbuf_bcat(devlist->cur_sb[devlist->level], str, len);
+}
+
+static int
+cctl_devlist(int fd, int argc, char **argv, char *combinedopt)
+{
+ struct ctl_lun_list list;
+ struct cctl_devlist_data devlist;
+ struct cctl_lun *lun;
+ XML_Parser parser;
+ char *lun_str;
+ int lun_len;
+ int dump_xml = 0;
+ int retval, c;
+ char *backend = NULL;
+ int verbose = 0;
+
+ retval = 0;
+ lun_len = 4096;
+
+ bzero(&devlist, sizeof(devlist));
+ STAILQ_INIT(&devlist.lun_list);
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'b':
+ backend = strdup(optarg);
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'x':
+ dump_xml = 1;
+ break;
+ default:
+ break;
+ }
+ }
+
+retry:
+ lun_str = malloc(lun_len);
+
+ bzero(&list, sizeof(list));
+ list.alloc_len = lun_len;
+ list.status = CTL_LUN_LIST_NONE;
+ list.lun_xml = lun_str;
+
+ if (ioctl(fd, CTL_LUN_LIST, &list) == -1) {
+ warn("%s: error issuing CTL_LUN_LIST ioctl", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ if (list.status == CTL_LUN_LIST_ERROR) {
+ warnx("%s: error returned from CTL_LUN_LIST ioctl:\n%s",
+ __func__, list.error_str);
+ } else if (list.status == CTL_LUN_LIST_NEED_MORE_SPACE) {
+ lun_len = lun_len << 1;
+ goto retry;
+ }
+
+ if (dump_xml != 0) {
+ printf("%s", lun_str);
+ goto bailout;
+ }
+
+ parser = XML_ParserCreate(NULL);
+ if (parser == NULL) {
+ warn("%s: Unable to create XML parser", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ XML_SetUserData(parser, &devlist);
+ XML_SetElementHandler(parser, cctl_start_element, cctl_end_element);
+ XML_SetCharacterDataHandler(parser, cctl_char_handler);
+
+ retval = XML_Parse(parser, lun_str, strlen(lun_str), 1);
+ if (retval != 1) {
+ warnx("%s: Unable to parse XML: Error %d", __func__,
+ XML_GetErrorCode(parser));
+ XML_ParserFree(parser);
+ retval = 1;
+ goto bailout;
+ }
+ retval = 0;
+ XML_ParserFree(parser);
+
+ printf("LUN Backend %18s %4s %-16s %-16s\n", "Size (Blocks)", "BS",
+ "Serial Number", "Device ID");
+ STAILQ_FOREACH(lun, &devlist.lun_list, links) {
+ struct cctl_lun_nv *nv;
+
+ if ((backend != NULL)
+ && (strcmp(lun->backend_type, backend) != 0))
+ continue;
+
+ printf("%3ju %-8s %18ju %4u %-16s %-16s\n",
+ (uintmax_t)lun->lun_id,
+ lun->backend_type, (uintmax_t)lun->size_blocks,
+ lun->blocksize, lun->serial_number, lun->device_id);
+
+ if (verbose == 0)
+ continue;
+
+ STAILQ_FOREACH(nv, &lun->attr_list, links) {
+ printf(" %s=%s\n", nv->name, nv->value);
+ }
+ }
+bailout:
+ free(lun_str);
+
+ return (retval);
+}
+
+/*
+ * Port information.
+ */
+struct cctl_port {
+ uint64_t port_id;
+ char *online;
+ char *frontend_type;
+ char *name;
+ int pp, vp;
+ char *target, *port, *lun_map;
+ STAILQ_HEAD(,cctl_lun_nv) init_list;
+ STAILQ_HEAD(,cctl_lun_nv) lun_list;
+ STAILQ_HEAD(,cctl_lun_nv) attr_list;
+ STAILQ_ENTRY(cctl_port) links;
+};
+
+struct cctl_portlist_data {
+ int num_ports;
+ STAILQ_HEAD(,cctl_port) port_list;
+ struct cctl_port *cur_port;
+ int level;
+ uint64_t cur_id;
+ struct sbuf *cur_sb[32];
+};
+
+static void
+cctl_start_pelement(void *user_data, const char *name, const char **attr)
+{
+ int i;
+ struct cctl_portlist_data *portlist;
+ struct cctl_port *cur_port;
+
+ portlist = (struct cctl_portlist_data *)user_data;
+ cur_port = portlist->cur_port;
+ portlist->level++;
+ if ((u_int)portlist->level >= (sizeof(portlist->cur_sb) /
+ sizeof(portlist->cur_sb[0])))
+ errx(1, "%s: too many nesting levels, %zd max", __func__,
+ sizeof(portlist->cur_sb) / sizeof(portlist->cur_sb[0]));
+
+ portlist->cur_sb[portlist->level] = sbuf_new_auto();
+ if (portlist->cur_sb[portlist->level] == NULL)
+ err(1, "%s: Unable to allocate sbuf", __func__);
+
+ portlist->cur_id = 0;
+ for (i = 0; attr[i] != NULL; i += 2) {
+ if (strcmp(attr[i], "id") == 0) {
+ portlist->cur_id = strtoull(attr[i+1], NULL, 0);
+ break;
+ }
+ }
+
+ if (strcmp(name, "targ_port") == 0) {
+ if (cur_port != NULL)
+ errx(1, "%s: improper port element nesting", __func__);
+
+ cur_port = calloc(1, sizeof(*cur_port));
+ if (cur_port == NULL)
+ err(1, "%s: cannot allocate %zd bytes", __func__,
+ sizeof(*cur_port));
+
+ portlist->num_ports++;
+ portlist->cur_port = cur_port;
+
+ STAILQ_INIT(&cur_port->init_list);
+ STAILQ_INIT(&cur_port->lun_list);
+ STAILQ_INIT(&cur_port->attr_list);
+ cur_port->port_id = portlist->cur_id;
+ STAILQ_INSERT_TAIL(&portlist->port_list, cur_port, links);
+ }
+}
+
+static void
+cctl_end_pelement(void *user_data, const char *name)
+{
+ struct cctl_portlist_data *portlist;
+ struct cctl_port *cur_port;
+ char *str;
+
+ portlist = (struct cctl_portlist_data *)user_data;
+ cur_port = portlist->cur_port;
+
+ if ((cur_port == NULL)
+ && (strcmp(name, "ctlportlist") != 0))
+ errx(1, "%s: cur_port == NULL! (name = %s)", __func__, name);
+
+ if (portlist->cur_sb[portlist->level] == NULL)
+ errx(1, "%s: no valid sbuf at level %d (name %s)", __func__,
+ portlist->level, name);
+
+ if (sbuf_finish(portlist->cur_sb[portlist->level]) != 0)
+ err(1, "%s: sbuf_finish", __func__);
+ str = strdup(sbuf_data(portlist->cur_sb[portlist->level]));
+ if (str == NULL)
+ err(1, "%s can't allocate %zd bytes for string", __func__,
+ sbuf_len(portlist->cur_sb[portlist->level]));
+
+ if (strlen(str) == 0) {
+ free(str);
+ str = NULL;
+ }
+
+ sbuf_delete(portlist->cur_sb[portlist->level]);
+ portlist->cur_sb[portlist->level] = NULL;
+ portlist->level--;
+
+ if (strcmp(name, "frontend_type") == 0) {
+ cur_port->frontend_type = str;
+ str = NULL;
+ } else if (strcmp(name, "port_name") == 0) {
+ cur_port->name = str;
+ str = NULL;
+ } else if (strcmp(name, "online") == 0) {
+ cur_port->online = str;
+ str = NULL;
+ } else if (strcmp(name, "physical_port") == 0) {
+ cur_port->pp = strtoull(str, NULL, 0);
+ } else if (strcmp(name, "virtual_port") == 0) {
+ cur_port->vp = strtoull(str, NULL, 0);
+ } else if (strcmp(name, "target") == 0) {
+ cur_port->target = str;
+ str = NULL;
+ } else if (strcmp(name, "port") == 0) {
+ cur_port->port = str;
+ str = NULL;
+ } else if (strcmp(name, "lun_map") == 0) {
+ cur_port->lun_map = str;
+ str = NULL;
+ } else if (strcmp(name, "targ_port") == 0) {
+ portlist->cur_port = NULL;
+ } else if (strcmp(name, "ctlportlist") == 0) {
+ /* Nothing. */
+ } else {
+ struct cctl_lun_nv *nv;
+
+ nv = calloc(1, sizeof(*nv));
+ if (nv == NULL)
+ err(1, "%s: can't allocate %zd bytes for nv pair",
+ __func__, sizeof(*nv));
+
+ if (strcmp(name, "initiator") == 0 ||
+ strcmp(name, "lun") == 0)
+ asprintf(&nv->name, "%ju", portlist->cur_id);
+ else
+ nv->name = strdup(name);
+ if (nv->name == NULL)
+ err(1, "%s: can't allocated %zd bytes for string",
+ __func__, strlen(name));
+
+ nv->value = str;
+ str = NULL;
+ if (strcmp(name, "initiator") == 0)
+ STAILQ_INSERT_TAIL(&cur_port->init_list, nv, links);
+ else if (strcmp(name, "lun") == 0)
+ STAILQ_INSERT_TAIL(&cur_port->lun_list, nv, links);
+ else
+ STAILQ_INSERT_TAIL(&cur_port->attr_list, nv, links);
+ }
+
+ free(str);
+}
+
+static void
+cctl_char_phandler(void *user_data, const XML_Char *str, int len)
+{
+ struct cctl_portlist_data *portlist;
+
+ portlist = (struct cctl_portlist_data *)user_data;
+
+ sbuf_bcat(portlist->cur_sb[portlist->level], str, len);
+}
+
+static int
+cctl_portlist(int fd, int argc, char **argv, char *combinedopt)
+{
+ struct ctl_lun_list list;
+ struct cctl_portlist_data portlist;
+ struct cctl_port *port;
+ XML_Parser parser;
+ char *port_str;
+ int port_len;
+ int dump_xml = 0;
+ int retval, c;
+ char *frontend = NULL;
+ uint64_t portarg = UINT64_MAX;
+ int verbose = 0, init = 0, lun = 0, quiet = 0;
+
+ retval = 0;
+ port_len = 4096;
+
+ bzero(&portlist, sizeof(portlist));
+ STAILQ_INIT(&portlist.port_list);
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'f':
+ frontend = strdup(optarg);
+ break;
+ case 'i':
+ init++;
+ break;
+ case 'l':
+ lun++;
+ break;
+ case 'p':
+ portarg = strtoll(optarg, NULL, 0);
+ break;
+ case 'q':
+ quiet++;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'x':
+ dump_xml = 1;
+ break;
+ default:
+ break;
+ }
+ }
+
+retry:
+ port_str = malloc(port_len);
+
+ bzero(&list, sizeof(list));
+ list.alloc_len = port_len;
+ list.status = CTL_LUN_LIST_NONE;
+ list.lun_xml = port_str;
+
+ if (ioctl(fd, CTL_PORT_LIST, &list) == -1) {
+ warn("%s: error issuing CTL_PORT_LIST ioctl", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ if (list.status == CTL_LUN_LIST_ERROR) {
+ warnx("%s: error returned from CTL_PORT_LIST ioctl:\n%s",
+ __func__, list.error_str);
+ } else if (list.status == CTL_LUN_LIST_NEED_MORE_SPACE) {
+ port_len = port_len << 1;
+ goto retry;
+ }
+
+ if (dump_xml != 0) {
+ printf("%s", port_str);
+ goto bailout;
+ }
+
+ parser = XML_ParserCreate(NULL);
+ if (parser == NULL) {
+ warn("%s: Unable to create XML parser", __func__);
+ retval = 1;
+ goto bailout;
+ }
+
+ XML_SetUserData(parser, &portlist);
+ XML_SetElementHandler(parser, cctl_start_pelement, cctl_end_pelement);
+ XML_SetCharacterDataHandler(parser, cctl_char_phandler);
+
+ retval = XML_Parse(parser, port_str, strlen(port_str), 1);
+ if (retval != 1) {
+ warnx("%s: Unable to parse XML: Error %d", __func__,
+ XML_GetErrorCode(parser));
+ XML_ParserFree(parser);
+ retval = 1;
+ goto bailout;
+ }
+ retval = 0;
+ XML_ParserFree(parser);
+
+ if (quiet == 0)
+ printf("Port Online Frontend Name pp vp\n");
+ STAILQ_FOREACH(port, &portlist.port_list, links) {
+ struct cctl_lun_nv *nv;
+
+ if ((frontend != NULL)
+ && (strcmp(port->frontend_type, frontend) != 0))
+ continue;
+
+ if ((portarg != UINT64_MAX) && (portarg != port->port_id))
+ continue;
+
+ printf("%-4ju %-6s %-8s %-8s %-2d %-2d %s\n",
+ (uintmax_t)port->port_id, port->online,
+ port->frontend_type, port->name, port->pp, port->vp,
+ port->port ? port->port : "");
+
+ if (init || verbose) {
+ if (port->target)
+ printf(" Target: %s\n", port->target);
+ STAILQ_FOREACH(nv, &port->init_list, links) {
+ printf(" Initiator %s: %s\n",
+ nv->name, nv->value);
+ }
+ }
+
+ if (lun || verbose) {
+ if (port->lun_map) {
+ STAILQ_FOREACH(nv, &port->lun_list, links)
+ printf(" LUN %s: %s\n",
+ nv->name, nv->value);
+ if (STAILQ_EMPTY(&port->lun_list))
+ printf(" No LUNs mapped\n");
+ } else
+ printf(" All LUNs mapped\n");
+ }
+
+ if (verbose) {
+ STAILQ_FOREACH(nv, &port->attr_list, links) {
+ printf(" %s=%s\n", nv->name, nv->value);
+ }
+ }
+ }
+bailout:
+ free(port_str);
+
+ return (retval);
+}
+
+static int
+cctl_lunmap(int fd, int argc, char **argv, char *combinedopt)
+{
+ struct ctl_lun_map lm;
+ int retval = 0, c;
+
+ retval = 0;
+ lm.port = UINT32_MAX;
+ lm.plun = UINT32_MAX;
+ lm.lun = UINT32_MAX;
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'p':
+ lm.port = strtoll(optarg, NULL, 0);
+ break;
+ case 'l':
+ lm.plun = strtoll(optarg, NULL, 0);
+ break;
+ case 'L':
+ lm.lun = strtoll(optarg, NULL, 0);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (ioctl(fd, CTL_LUN_MAP, &lm) == -1) {
+ warn("%s: error issuing CTL_LUN_MAP ioctl", __func__);
+ retval = 1;
+ }
+
+ return (retval);
+}
+
+void
+usage(int error)
+{
+ fprintf(error ? stderr : stdout,
+"Usage:\n"
+"Primary commands:\n"
+" ctladm tur [dev_id][general options]\n"
+" ctladm inquiry [dev_id][general options]\n"
+" ctladm devid [dev_id][general options]\n"
+" ctladm reqsense [dev_id][general options]\n"
+" ctladm reportluns [dev_id][general options]\n"
+" ctladm read [dev_id][general options] <-l lba> <-d len>\n"
+" <-f file|-> <-b blocksize> [-c cdbsize][-N]\n"
+" ctladm write [dev_id][general options] <-l lba> <-d len>\n"
+" <-f file|-> <-b blocksize> [-c cdbsize][-N]\n"
+" ctladm readcap [dev_id][general options] [-c cdbsize]\n"
+" ctladm modesense [dev_id][general options] <-m page|-l> [-P pc]\n"
+" [-d] [-S subpage] [-c cdbsize]\n"
+" ctladm prin [dev_id][general options] <-a action>\n"
+" ctladm prout [dev_id][general options] <-a action>\n"
+" <-r restype] [-k key] [-s sa_key]\n"
+" ctladm rtpg [dev_id][general options]\n"
+" ctladm start [dev_id][general options] [-i] [-o]\n"
+" ctladm stop [dev_id][general options] [-i] [-o]\n"
+" ctladm synccache [dev_id][general options] [-l lba]\n"
+" [-b blockcount] [-r] [-i] [-c cdbsize]\n"
+" ctladm create <-b backend> [-B blocksize] [-d device_id]\n"
+" [-l lun_id] [-o name=value] [-s size_bytes]\n"
+" [-S serial_num] [-t dev_type]\n"
+" ctladm remove <-b backend> <-l lun_id> [-o name=value]\n"
+" ctladm modify <-b backend> <-l lun_id> <-s size_bytes>\n"
+" ctladm devlist [-b backend] [-v] [-x]\n"
+" ctladm lunlist\n"
+" ctladm lunmap -p targ_port [-l pLUN] [-L cLUN]\n"
+" ctladm delay [dev_id] <-l datamove|done> [-T oneshot|cont]\n"
+" [-t secs]\n"
+" ctladm inject [dev_id] <-i action> <-p pattern> [-r lba,len]\n"
+" [-s len fmt [args]] [-c] [-d delete_id]\n"
+" ctladm port <-o <on|off> | [-w wwnn][-W wwpn]>\n"
+" [-p targ_port] [-t port_type]\n"
+" ctladm portlist [-f frontend] [-i] [-p targ_port] [-q] [-v] [-x]\n"
+" ctladm islist [-v | -x]\n"
+" ctladm islogout <-a | -c connection-id | -i name | -p portal>\n"
+" ctladm isterminate <-a | -c connection-id | -i name | -p portal>\n"
+" ctladm dumpooa\n"
+" ctladm dumpstructs\n"
+" ctladm help\n"
+"General Options:\n"
+"-I intiator_id : defaults to 7, used to change the initiator id\n"
+"-C retries : specify the number of times to retry this command\n"
+"-D devicename : specify the device to operate on\n"
+" : (default is %s)\n"
+"read/write options:\n"
+"-l lba : logical block address\n"
+"-d len : read/write length, in blocks\n"
+"-f file|- : write/read data to/from file or stdout/stdin\n"
+"-b blocksize : block size, in bytes\n"
+"-c cdbsize : specify minimum cdb size: 6, 10, 12 or 16\n"
+"-N : do not copy data to/from userland\n"
+"readcapacity options:\n"
+"-c cdbsize : specify minimum cdb size: 10 or 16\n"
+"modesense options:\n"
+"-m page : specify the mode page to view\n"
+"-l : request a list of supported pages\n"
+"-P pc : specify the page control value: 0-3 (current,\n"
+" changeable, default, saved, respectively)\n"
+"-d : disable block descriptors for mode sense\n"
+"-S subpage : specify a subpage\n"
+"-c cdbsize : specify minimum cdb size: 6 or 10\n"
+"persistent reserve in options:\n"
+"-a action : specify the action value: 0-2 (read key, read\n"
+" reservation, read capabilities, respectively)\n"
+"persistent reserve out options:\n"
+"-a action : specify the action value: 0-5 (register, reserve,\n"
+" release, clear, preempt, register and ignore)\n"
+"-k key : key value\n"
+"-s sa_key : service action value\n"
+"-r restype : specify the reservation type: 0-5(wr ex, ex ac,\n"
+" wr ex ro, ex ac ro, wr ex ar, ex ac ar)\n"
+"start/stop options:\n"
+"-i : set the immediate bit (CTL does not support this)\n"
+"-o : set the on/offline bit\n"
+"synccache options:\n"
+"-l lba : set the starting LBA\n"
+"-b blockcount : set the length to sync in blocks\n"
+"-r : set the relative addressing bit\n"
+"-i : set the immediate bit\n"
+"-c cdbsize : specify minimum cdb size: 10 or 16\n"
+"create options:\n"
+"-b backend : backend name (\"block\", \"ramdisk\", etc.)\n"
+"-B blocksize : LUN blocksize in bytes (some backends)\n"
+"-d device_id : SCSI VPD page 0x83 ID\n"
+"-l lun_id : requested LUN number\n"
+"-o name=value : backend-specific options, multiple allowed\n"
+"-s size_bytes : LUN size in bytes (some backends)\n"
+"-S serial_num : SCSI VPD page 0x80 serial number\n"
+"-t dev_type : SCSI device type (0=disk, 3=processor)\n"
+"remove options:\n"
+"-b backend : backend name (\"block\", \"ramdisk\", etc.)\n"
+"-l lun_id : LUN number to delete\n"
+"-o name=value : backend-specific options, multiple allowed\n"
+"devlist options:\n"
+"-b backend : list devices from specified backend only\n"
+"-v : be verbose, show backend attributes\n"
+"-x : dump raw XML\n"
+"delay options:\n"
+"-l datamove|done : delay command at datamove or done phase\n"
+"-T oneshot : delay one command, then resume normal completion\n"
+"-T cont : delay all commands\n"
+"-t secs : number of seconds to delay\n"
+"inject options:\n"
+"-i error_action : action to perform\n"
+"-p pattern : command pattern to look for\n"
+"-r lba,len : LBA range for pattern\n"
+"-s len fmt [args] : sense data for custom sense action\n"
+"-c : continuous operation\n"
+"-d delete_id : error id to delete\n"
+"port options:\n"
+"-l : list frontend ports\n"
+"-o on|off : turn frontend ports on or off\n"
+"-w wwnn : set WWNN for one frontend\n"
+"-W wwpn : set WWPN for one frontend\n"
+"-t port_type : specify fc, scsi, ioctl, internal frontend type\n"
+"-p targ_port : specify target port number\n"
+"-q : omit header in list output\n"
+"-x : output port list in XML format\n"
+"portlist options:\n"
+"-f fronetnd : specify frontend type\n"
+"-i : report target and initiators addresses\n"
+"-l : report LUN mapping\n"
+"-p targ_port : specify target port number\n"
+"-q : omit header in list output\n"
+"-v : verbose output (report all port options)\n"
+"-x : output port list in XML format\n"
+"lunmap options:\n"
+"-p targ_port : specify target port number\n"
+"-L pLUN : specify port-visible LUN\n"
+"-L cLUN : specify CTL LUN\n",
+CTL_DEFAULT_DEV);
+}
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ ctladm_cmdfunction command;
+ ctladm_cmdargs cmdargs;
+ ctladm_optret optreturn;
+ char *device;
+ const char *mainopt = "C:D:I:";
+ const char *subopt = NULL;
+ char combinedopt[256];
+ int lun;
+ int optstart = 2;
+ int retval, fd;
+ int retries;
+ int initid;
+ int saved_errno;
+
+ retval = 0;
+ cmdargs = CTLADM_ARG_NONE;
+ command = CTLADM_CMD_HELP;
+ device = NULL;
+ fd = -1;
+ retries = 0;
+ lun = 0;
+ initid = 7;
+
+ if (argc < 2) {
+ usage(1);
+ retval = 1;
+ goto bailout;
+ }
+
+ /*
+ * Get the base option.
+ */
+ optreturn = getoption(option_table,argv[1], &command, &cmdargs,&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);
+ }
+
+ if (cmdargs & CTLADM_ARG_NEED_TL) {
+ if ((argc < 3) || (!isdigit(argv[2][0]))) {
+ warnx("option %s requires a lun argument",
+ argv[1]);
+ usage(0);
+ exit(1);
+ }
+ lun = strtol(argv[2], NULL, 0);
+
+ cmdargs |= CTLADM_ARG_TARG_LUN;
+ optstart++;
+ }
+
+ /*
+ * 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);
+
+ /*
+ * 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':
+ cmdargs |= CTLADM_ARG_RETRIES;
+ retries = strtol(optarg, NULL, 0);
+ break;
+ case 'D':
+ device = strdup(optarg);
+ cmdargs |= CTLADM_ARG_DEVICE;
+ break;
+ case 'I':
+ cmdargs |= CTLADM_ARG_INITIATOR;
+ initid = strtol(optarg, NULL, 0);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if ((cmdargs & CTLADM_ARG_INITIATOR) == 0)
+ initid = 7;
+
+ optind = optstart;
+ optreset = 1;
+
+ /*
+ * Default to opening the CTL device for now.
+ */
+ if (((cmdargs & CTLADM_ARG_DEVICE) == 0)
+ && (command != CTLADM_CMD_HELP)) {
+ device = strdup(CTL_DEFAULT_DEV);
+ cmdargs |= CTLADM_ARG_DEVICE;
+ }
+
+ if ((cmdargs & CTLADM_ARG_DEVICE)
+ && (command != CTLADM_CMD_HELP)) {
+ fd = open(device, O_RDWR);
+ if (fd == -1 && errno == ENOENT) {
+ saved_errno = errno;
+ retval = kldload("ctl");
+ if (retval != -1)
+ fd = open(device, O_RDWR);
+ else
+ errno = saved_errno;
+ }
+ if (fd == -1) {
+ fprintf(stderr, "%s: error opening %s: %s\n",
+ argv[0], device, strerror(errno));
+ retval = 1;
+ goto bailout;
+ }
+ } else if ((command != CTLADM_CMD_HELP)
+ && ((cmdargs & CTLADM_ARG_DEVICE) == 0)) {
+ fprintf(stderr, "%s: you must specify a device with the "
+ "--device argument for this command\n", argv[0]);
+ command = CTLADM_CMD_HELP;
+ retval = 1;
+ }
+
+ switch (command) {
+ case CTLADM_CMD_TUR:
+ retval = cctl_tur(fd, lun, initid, retries);
+ break;
+ case CTLADM_CMD_INQUIRY:
+ retval = cctl_inquiry(fd, lun, initid, retries);
+ break;
+ case CTLADM_CMD_REQ_SENSE:
+ retval = cctl_req_sense(fd, lun, initid, retries);
+ break;
+ case CTLADM_CMD_REPORT_LUNS:
+ retval = cctl_report_luns(fd, lun, initid, retries);
+ break;
+ case CTLADM_CMD_CREATE:
+ retval = cctl_create_lun(fd, argc, argv, combinedopt);
+ break;
+ case CTLADM_CMD_RM:
+ retval = cctl_rm_lun(fd, argc, argv, combinedopt);
+ break;
+ case CTLADM_CMD_DEVLIST:
+ retval = cctl_devlist(fd, argc, argv, combinedopt);
+ break;
+ case CTLADM_CMD_READ:
+ case CTLADM_CMD_WRITE:
+ retval = cctl_read_write(fd, lun, initid, retries,
+ argc, argv, combinedopt, command);
+ break;
+ case CTLADM_CMD_PORT:
+ retval = cctl_port(fd, argc, argv, combinedopt);
+ break;
+ case CTLADM_CMD_PORTLIST:
+ retval = cctl_portlist(fd, argc, argv, combinedopt);
+ break;
+ case CTLADM_CMD_LUNMAP:
+ retval = cctl_lunmap(fd, argc, argv, combinedopt);
+ break;
+ case CTLADM_CMD_READCAPACITY:
+ retval = cctl_read_capacity(fd, lun, initid, retries,
+ argc, argv, combinedopt);
+ break;
+ case CTLADM_CMD_MODESENSE:
+ retval = cctl_mode_sense(fd, lun, initid, retries,
+ argc, argv, combinedopt);
+ break;
+ case CTLADM_CMD_START:
+ case CTLADM_CMD_STOP:
+ retval = cctl_start_stop(fd, lun, initid, retries,
+ (command == CTLADM_CMD_START) ? 1 : 0,
+ argc, argv, combinedopt);
+ break;
+ case CTLADM_CMD_SYNC_CACHE:
+ retval = cctl_sync_cache(fd, lun, initid, retries,
+ argc, argv, combinedopt);
+ break;
+ case CTLADM_CMD_LUNLIST:
+ retval = cctl_lunlist(fd);
+ break;
+ case CTLADM_CMD_DELAY:
+ retval = cctl_delay(fd, lun, argc, argv, combinedopt);
+ break;
+ case CTLADM_CMD_ERR_INJECT:
+ retval = cctl_error_inject(fd, lun, argc, argv,
+ combinedopt);
+ break;
+ case CTLADM_CMD_DUMPOOA:
+ retval = cctl_dump_ooa(fd, argc, argv);
+ break;
+ case CTLADM_CMD_DUMPSTRUCTS:
+ retval = cctl_dump_structs(fd, cmdargs);
+ break;
+ case CTLADM_CMD_PRES_IN:
+ retval = cctl_persistent_reserve_in(fd, lun, initid,
+ argc, argv, combinedopt,
+ retries);
+ break;
+ case CTLADM_CMD_PRES_OUT:
+ retval = cctl_persistent_reserve_out(fd, lun, initid,
+ argc, argv, combinedopt,
+ retries);
+ break;
+ case CTLADM_CMD_INQ_VPD_DEVID:
+ retval = cctl_inquiry_vpd_devid(fd, lun, initid);
+ break;
+ case CTLADM_CMD_RTPG:
+ retval = cctl_report_target_port_group(fd, lun, initid);
+ break;
+ case CTLADM_CMD_MODIFY:
+ retval = cctl_modify_lun(fd, argc, argv, combinedopt);
+ break;
+ case CTLADM_CMD_ISLIST:
+ retval = cctl_islist(fd, argc, argv, combinedopt);
+ break;
+ case CTLADM_CMD_ISLOGOUT:
+ retval = cctl_islogout(fd, argc, argv, combinedopt);
+ break;
+ case CTLADM_CMD_ISTERMINATE:
+ retval = cctl_isterminate(fd, argc, argv, combinedopt);
+ break;
+ case CTLADM_CMD_HELP:
+ default:
+ usage(retval);
+ break;
+ }
+bailout:
+
+ if (fd != -1)
+ close(fd);
+
+ exit (retval);
+}
+
+/*
+ * vim: ts=8
+ */
diff --git a/usr.sbin/ctladm/ctladm.h b/usr.sbin/ctladm/ctladm.h
new file mode 100644
index 0000000..b2e9f12
--- /dev/null
+++ b/usr.sbin/ctladm/ctladm.h
@@ -0,0 +1,50 @@
+/*
+ * 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$
+ * $Id: //depot/users/kenm/FreeBSD-test2/usr.sbin/ctladm/ctladm.h#1 $
+ */
+
+#ifndef _CTLADM_H
+#define _CTLADM_H
+
+/*
+ * get_hook: Structure for evaluating args in a callback.
+ */
+struct get_hook
+{
+ int argc;
+ char **argv;
+ int got;
+};
+
+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);
+void usage(int error);
+
+#endif /* _CTLADM_H */
diff --git a/usr.sbin/ctladm/util.c b/usr.sbin/ctladm/util.c
new file mode 100644
index 0000000..52d8d17
--- /dev/null
+++ b/usr.sbin/ctladm/util.c
@@ -0,0 +1,156 @@
+/*
+ * 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 "ctladm.h"
+
+static 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');
+}
diff --git a/usr.sbin/ctld/Makefile b/usr.sbin/ctld/Makefile
new file mode 100644
index 0000000..6169d30
--- /dev/null
+++ b/usr.sbin/ctld/Makefile
@@ -0,0 +1,21 @@
+# $FreeBSD$
+
+PROG= ctld
+SRCS= chap.c ctld.c discovery.c isns.c kernel.c keys.c log.c
+SRCS+= login.c parse.y pdu.c token.l y.tab.h
+CFLAGS+= -I${.CURDIR}
+CFLAGS+= -I${.CURDIR}/../../sys
+CFLAGS+= -I${.CURDIR}/../../sys/cam/ctl
+CFLAGS+= -I${.CURDIR}/../../sys/dev/iscsi
+#CFLAGS+= -DICL_KERNEL_PROXY
+MAN= ctld.8 ctl.conf.5
+
+LIBADD= bsdxml l md sbuf util
+
+YFLAGS+= -v
+CLEANFILES= y.tab.c y.tab.h y.output
+
+WARNS= 6
+NO_WMISSING_VARIABLE_DECLARATIONS=
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ctld/Makefile.depend b/usr.sbin/ctld/Makefile.depend
new file mode 100644
index 0000000..ece2325
--- /dev/null
+++ b/usr.sbin/ctld/Makefile.depend
@@ -0,0 +1,31 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libexpat \
+ lib/libmd \
+ lib/libsbuf \
+ lib/libutil \
+ usr.bin/lex/lib \
+ usr.bin/yacc.host \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+parse.o: parse.c
+parse.po: parse.c
+token.o: token.c
+token.o: y.tab.h
+token.po: token.c
+token.po: y.tab.h
+.endif
diff --git a/usr.sbin/ctld/chap.c b/usr.sbin/ctld/chap.c
new file mode 100644
index 0000000..2120350
--- /dev/null
+++ b/usr.sbin/ctld/chap.c
@@ -0,0 +1,422 @@
+/*-
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <resolv.h>
+#include <md5.h>
+
+#include "ctld.h"
+
+static void
+chap_compute_md5(const char id, const char *secret,
+ const void *challenge, size_t challenge_len, void *response,
+ size_t response_len)
+{
+ MD5_CTX ctx;
+
+ assert(response_len == CHAP_DIGEST_LEN);
+
+ MD5Init(&ctx);
+ MD5Update(&ctx, &id, sizeof(id));
+ MD5Update(&ctx, secret, strlen(secret));
+ MD5Update(&ctx, challenge, challenge_len);
+ MD5Final(response, &ctx);
+}
+
+static int
+chap_hex2int(const char hex)
+{
+ switch (hex) {
+ case '0':
+ return (0x00);
+ case '1':
+ return (0x01);
+ case '2':
+ return (0x02);
+ case '3':
+ return (0x03);
+ case '4':
+ return (0x04);
+ case '5':
+ return (0x05);
+ case '6':
+ return (0x06);
+ case '7':
+ return (0x07);
+ case '8':
+ return (0x08);
+ case '9':
+ return (0x09);
+ case 'a':
+ case 'A':
+ return (0x0a);
+ case 'b':
+ case 'B':
+ return (0x0b);
+ case 'c':
+ case 'C':
+ return (0x0c);
+ case 'd':
+ case 'D':
+ return (0x0d);
+ case 'e':
+ case 'E':
+ return (0x0e);
+ case 'f':
+ case 'F':
+ return (0x0f);
+ default:
+ return (-1);
+ }
+}
+
+static int
+chap_b642bin(const char *b64, void **binp, size_t *bin_lenp)
+{
+ char *bin;
+ int b64_len, bin_len;
+
+ b64_len = strlen(b64);
+ bin_len = (b64_len + 3) / 4 * 3;
+ bin = calloc(bin_len, 1);
+ if (bin == NULL)
+ log_err(1, "calloc");
+
+ bin_len = b64_pton(b64, bin, bin_len);
+ if (bin_len < 0) {
+ log_warnx("malformed base64 variable");
+ free(bin);
+ return (-1);
+ }
+ *binp = bin;
+ *bin_lenp = bin_len;
+ return (0);
+}
+
+/*
+ * XXX: Review this _carefully_.
+ */
+static int
+chap_hex2bin(const char *hex, void **binp, size_t *bin_lenp)
+{
+ int i, hex_len, nibble;
+ bool lo = true; /* As opposed to 'hi'. */
+ char *bin;
+ size_t bin_off, bin_len;
+
+ if (strncasecmp(hex, "0b", strlen("0b")) == 0)
+ return (chap_b642bin(hex + 2, binp, bin_lenp));
+
+ if (strncasecmp(hex, "0x", strlen("0x")) != 0) {
+ log_warnx("malformed variable, should start with \"0x\""
+ " or \"0b\"");
+ return (-1);
+ }
+
+ hex += strlen("0x");
+ hex_len = strlen(hex);
+ if (hex_len < 1) {
+ log_warnx("malformed variable; doesn't contain anything "
+ "but \"0x\"");
+ return (-1);
+ }
+
+ bin_len = hex_len / 2 + hex_len % 2;
+ bin = calloc(bin_len, 1);
+ if (bin == NULL)
+ log_err(1, "calloc");
+
+ bin_off = bin_len - 1;
+ for (i = hex_len - 1; i >= 0; i--) {
+ nibble = chap_hex2int(hex[i]);
+ if (nibble < 0) {
+ log_warnx("malformed variable, invalid char \"%c\"",
+ hex[i]);
+ free(bin);
+ return (-1);
+ }
+
+ assert(bin_off < bin_len);
+ if (lo) {
+ bin[bin_off] = nibble;
+ lo = false;
+ } else {
+ bin[bin_off] |= nibble << 4;
+ bin_off--;
+ lo = true;
+ }
+ }
+
+ *binp = bin;
+ *bin_lenp = bin_len;
+ return (0);
+}
+
+#ifdef USE_BASE64
+static char *
+chap_bin2hex(const char *bin, size_t bin_len)
+{
+ unsigned char *b64, *tmp;
+ size_t b64_len;
+
+ b64_len = (bin_len + 2) / 3 * 4 + 3; /* +2 for "0b", +1 for '\0'. */
+ b64 = malloc(b64_len);
+ if (b64 == NULL)
+ log_err(1, "malloc");
+
+ tmp = b64;
+ tmp += sprintf(tmp, "0b");
+ b64_ntop(bin, bin_len, tmp, b64_len - 2);
+
+ return (b64);
+}
+#else
+static char *
+chap_bin2hex(const char *bin, size_t bin_len)
+{
+ unsigned char *hex, *tmp, ch;
+ size_t hex_len;
+ size_t i;
+
+ hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */
+ hex = malloc(hex_len);
+ if (hex == NULL)
+ log_err(1, "malloc");
+
+ tmp = hex;
+ tmp += sprintf(tmp, "0x");
+ for (i = 0; i < bin_len; i++) {
+ ch = bin[i];
+ tmp += sprintf(tmp, "%02x", ch);
+ }
+
+ return (hex);
+}
+#endif /* !USE_BASE64 */
+
+struct chap *
+chap_new(void)
+{
+ struct chap *chap;
+
+ chap = calloc(sizeof(*chap), 1);
+ if (chap == NULL)
+ log_err(1, "calloc");
+
+ /*
+ * Generate the challenge.
+ */
+ arc4random_buf(chap->chap_challenge, sizeof(chap->chap_challenge));
+ arc4random_buf(&chap->chap_id, sizeof(chap->chap_id));
+
+ return (chap);
+}
+
+char *
+chap_get_id(const struct chap *chap)
+{
+ char *chap_i;
+ int ret;
+
+ ret = asprintf(&chap_i, "%d", chap->chap_id);
+ if (ret < 0)
+ log_err(1, "asprintf");
+
+ return (chap_i);
+}
+
+char *
+chap_get_challenge(const struct chap *chap)
+{
+ char *chap_c;
+
+ chap_c = chap_bin2hex(chap->chap_challenge,
+ sizeof(chap->chap_challenge));
+
+ return (chap_c);
+}
+
+static int
+chap_receive_bin(struct chap *chap, void *response, size_t response_len)
+{
+
+ if (response_len != sizeof(chap->chap_response)) {
+ log_debugx("got CHAP response with invalid length; "
+ "got %zd, should be %zd",
+ response_len, sizeof(chap->chap_response));
+ return (1);
+ }
+
+ memcpy(chap->chap_response, response, response_len);
+ return (0);
+}
+
+int
+chap_receive(struct chap *chap, const char *response)
+{
+ void *response_bin;
+ size_t response_bin_len;
+ int error;
+
+ error = chap_hex2bin(response, &response_bin, &response_bin_len);
+ if (error != 0) {
+ log_debugx("got incorrectly encoded CHAP response \"%s\"",
+ response);
+ return (1);
+ }
+
+ error = chap_receive_bin(chap, response_bin, response_bin_len);
+ free(response_bin);
+
+ return (error);
+}
+
+int
+chap_authenticate(struct chap *chap, const char *secret)
+{
+ char expected_response[CHAP_DIGEST_LEN];
+
+ chap_compute_md5(chap->chap_id, secret,
+ chap->chap_challenge, sizeof(chap->chap_challenge),
+ expected_response, sizeof(expected_response));
+
+ if (memcmp(chap->chap_response,
+ expected_response, sizeof(expected_response)) != 0) {
+ return (-1);
+ }
+
+ return (0);
+}
+
+void
+chap_delete(struct chap *chap)
+{
+
+ free(chap);
+}
+
+struct rchap *
+rchap_new(const char *secret)
+{
+ struct rchap *rchap;
+
+ rchap = calloc(sizeof(*rchap), 1);
+ if (rchap == NULL)
+ log_err(1, "calloc");
+
+ rchap->rchap_secret = checked_strdup(secret);
+
+ return (rchap);
+}
+
+static void
+rchap_receive_bin(struct rchap *rchap, const unsigned char id,
+ const void *challenge, size_t challenge_len)
+{
+
+ rchap->rchap_id = id;
+ rchap->rchap_challenge = calloc(challenge_len, 1);
+ if (rchap->rchap_challenge == NULL)
+ log_err(1, "calloc");
+ memcpy(rchap->rchap_challenge, challenge, challenge_len);
+ rchap->rchap_challenge_len = challenge_len;
+}
+
+int
+rchap_receive(struct rchap *rchap, const char *id, const char *challenge)
+{
+ unsigned char id_bin;
+ void *challenge_bin;
+ size_t challenge_bin_len;
+
+ int error;
+
+ id_bin = strtoul(id, NULL, 10);
+
+ error = chap_hex2bin(challenge, &challenge_bin, &challenge_bin_len);
+ if (error != 0) {
+ log_debugx("got incorrectly encoded CHAP challenge \"%s\"",
+ challenge);
+ return (1);
+ }
+
+ rchap_receive_bin(rchap, id_bin, challenge_bin, challenge_bin_len);
+ free(challenge_bin);
+
+ return (0);
+}
+
+static void
+rchap_get_response_bin(struct rchap *rchap,
+ void **responsep, size_t *response_lenp)
+{
+ void *response_bin;
+ size_t response_bin_len = CHAP_DIGEST_LEN;
+
+ response_bin = calloc(response_bin_len, 1);
+ if (response_bin == NULL)
+ log_err(1, "calloc");
+
+ chap_compute_md5(rchap->rchap_id, rchap->rchap_secret,
+ rchap->rchap_challenge, rchap->rchap_challenge_len,
+ response_bin, response_bin_len);
+
+ *responsep = response_bin;
+ *response_lenp = response_bin_len;
+}
+
+char *
+rchap_get_response(struct rchap *rchap)
+{
+ void *response;
+ size_t response_len;
+ char *chap_r;
+
+ rchap_get_response_bin(rchap, &response, &response_len);
+ chap_r = chap_bin2hex(response, response_len);
+ free(response);
+
+ return (chap_r);
+}
+
+void
+rchap_delete(struct rchap *rchap)
+{
+
+ free(rchap->rchap_secret);
+ free(rchap->rchap_challenge);
+ free(rchap);
+}
diff --git a/usr.sbin/ctld/ctl.conf.5 b/usr.sbin/ctld/ctl.conf.5
new file mode 100644
index 0000000..80b2f9e
--- /dev/null
+++ b/usr.sbin/ctld/ctl.conf.5
@@ -0,0 +1,494 @@
+.\" Copyright (c) 2012 The FreeBSD Foundation
+.\" Copyright (c) 2015 Alexander Motin <mav@FreeBSD.org>
+.\" All rights reserved.
+.\"
+.\" This software was 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 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 November 9, 2015
+.Dt CTL.CONF 5
+.Os
+.Sh NAME
+.Nm ctl.conf
+.Nd CAM Target Layer / iSCSI target daemon configuration file
+.Sh DESCRIPTION
+The
+.Nm
+configuration file is used by the
+.Xr ctld 8
+daemon.
+Lines starting with
+.Ql #
+are interpreted as comments.
+The general syntax of the
+.Nm
+file is:
+.Bd -literal -offset indent
+.No pidfile Ar path
+
+.No auth-group Ar name No {
+.Dl chap Ar user Ar secret
+.Dl ...
+}
+
+.No portal-group Ar name No {
+.Dl listen Ar address
+.\".Dl listen-iser Ar address
+.Dl discovery-auth-group Ar name
+.Dl ...
+}
+
+.No target Ar name {
+.Dl auth-group Ar name
+.Dl portal-group Ar name
+.Dl lun Ar number No {
+.Dl path Ar path
+.Dl }
+.Dl ...
+}
+.Ed
+.Ss Global Context
+.Bl -tag -width indent
+.It Ic auth-group Ar name
+Create an
+.Sy auth-group
+configuration context,
+defining a new auth-group,
+which can then be assigned to any number of targets.
+.It Ic debug Ar level
+The debug verbosity level.
+The default is 0.
+.It Ic maxproc Ar number
+The limit for concurrently running child processes handling
+incoming connections.
+The default is 30.
+A setting of 0 disables the limit.
+.It Ic pidfile Ar path
+The path to the pidfile.
+The default is
+.Pa /var/run/ctld.pid .
+.It Ic portal-group Ar name
+Create a
+.Sy portal-group
+configuration context,
+defining a new portal-group,
+which can then be assigned to any number of targets.
+.It Ic lun Ar name
+Create a
+.Sy lun
+configuration context, defining a LUN to be exported by any number of targets.
+.It Ic target Ar name
+Create a
+.Sy target
+configuration context, which can optionally contain one or more
+.Sy lun
+contexts.
+.It Ic timeout Ar seconds
+The timeout for login sessions, after which the connection
+will be forcibly terminated.
+The default is 60.
+A setting of 0 disables the timeout.
+.It Ic isns-server Ar address
+An IPv4 or IPv6 address and optionally port of iSNS server to register on.
+.It Ic isns-period Ar seconds
+iSNS registration period.
+Registered Network Entity not updated during this period will be unregistered.
+The default is 900.
+.It Ic isns-timeout Ar seconds
+Timeout for iSNS requests.
+The default is 5.
+.El
+.Ss auth-group Context
+.Bl -tag -width indent
+.It Ic auth-type Ar type
+Sets the authentication type.
+Type can be either
+.Qq Ar none ,
+.Qq Ar deny ,
+.Qq Ar chap ,
+or
+.Qq Ar chap-mutual .
+In most cases it is not necessary to set the type using this clause;
+it is usually used to disable authentication for a given
+.Sy auth-group .
+.It Ic chap Ar user Ar secret
+A set of CHAP authentication credentials.
+Note that for any
+.Sy auth-group ,
+the configuration may only contain either
+.Sy chap
+or
+.Sy chap-mutual
+entries; it is an error to mix them.
+.It Ic chap-mutual Ar user Ar secret Ar mutualuser Ar mutualsecret
+A set of mutual CHAP authentication credentials.
+Note that for any
+.Sy auth-group ,
+the configuration may only contain either
+.Sy chap
+or
+.Sy chap-mutual
+entries; it is an error to mix them.
+.It Ic initiator-name Ar initiator-name
+An iSCSI initiator name.
+Only initiators with a name matching one of the defined
+names will be allowed to connect.
+If not defined, there will be no restrictions based on initiator
+name.
+.It Ic initiator-portal Ar address Ns Op / Ns Ar prefixlen
+An iSCSI initiator portal: an IPv4 or IPv6 address, optionally
+followed by a literal slash and a prefix length.
+Only initiators with an address matching one of the defined
+addresses will be allowed to connect.
+If not defined, there will be no restrictions based on initiator
+address.
+.El
+.Ss portal-group Context
+.Bl -tag -width indent
+.It Ic discovery-auth-group Ar name
+Assign a previously defined authentication group to the portal group,
+to be used for target discovery.
+By default, portal groups are assigned predefined
+.Sy auth-group
+.Qq Ar default ,
+which denies discovery.
+Another predefined
+.Sy auth-group ,
+.Qq Ar no-authentication ,
+may be used
+to permit discovery without authentication.
+.It Ic discovery-filter Ar filter
+Determines which targets are returned during discovery.
+Filter can be either
+.Qq Ar none ,
+.Qq Ar portal ,
+.Qq Ar portal-name ,
+or
+.Qq Ar portal-name-auth .
+When set to
+.Qq Ar none ,
+discovery will return all targets assigned to that portal group.
+When set to
+.Qq Ar portal ,
+discovery will not return targets that cannot be accessed by the
+initiator because of their
+.Sy initiator-portal .
+When set to
+.Qq Ar portal-name ,
+the check will include both
+.Sy initiator-portal
+and
+.Sy initiator-name .
+When set to
+.Qq Ar portal-name-auth ,
+the check will include
+.Sy initiator-portal ,
+.Sy initiator-name ,
+and authentication credentials.
+The target is returned if it does not require CHAP authentication,
+or if the CHAP user and secret used during discovery match those
+used by the target.
+Note that when using
+.Qq Ar portal-name-auth ,
+targets that require CHAP authentication will only be returned if
+.Sy discovery-auth-group
+requires CHAP.
+The default is
+.Qq Ar none .
+.It Ic listen Ar address
+An IPv4 or IPv6 address and port to listen on for incoming connections.
+.\".It Ic listen-iser Ar address
+.\"An IPv4 or IPv6 address and port to listen on for incoming connections
+.\"using iSER (iSCSI over RDMA) protocol.
+.It Ic offload Ar driver
+Define iSCSI hardware offload driver to use for this
+.Sy portal-group .
+.It Ic option Ar name Ar value
+The CTL-specific port options passed to the kernel.
+.It Ic redirect Ar address
+IPv4 or IPv6 address to redirect initiators to.
+When configured, all initiators attempting to connect to portal
+belonging to this
+.Sy portal-group
+will get redirected using "Target moved temporarily" login response.
+Redirection happens before authentication and any
+.Sy initiator-name
+or
+.Sy initiator-portal
+checks are skipped.
+.It Ic tag Ar value
+Unique 16-bit tag value of this
+.Sy portal-group .
+If not specified, the value is generated automatically.
+.It Ic foreign
+Specifies that this
+.Sy portal-group
+is listened by some other host.
+This host will announce it on discovery stage, but won't listen.
+.El
+.Ss target Context
+.Bl -tag -width indent
+.It Ic alias Ar text
+Assign a human-readable description to the target.
+There is no default.
+.It Ic auth-group Ar name
+Assign a previously defined authentication group to the target.
+By default, targets that do not specify their own auth settings,
+using clauses such as
+.Sy chap
+or
+.Sy initiator-name ,
+are assigned
+predefined
+.Sy auth-group
+.Qq Ar default ,
+which denies all access.
+Another predefined
+.Sy auth-group ,
+.Qq Ar no-authentication ,
+may be used to permit access
+without authentication.
+Note that this clause can be overridden using the second argument
+to a
+.Sy portal-group
+clause.
+.It Ic auth-type Ar type
+Sets the authentication type.
+Type can be either
+.Qq Ar none ,
+.Qq Ar deny ,
+.Qq Ar chap ,
+or
+.Qq Ar chap-mutual .
+In most cases it is not necessary to set the type using this clause;
+it is usually used to disable authentication for a given
+.Sy target .
+This clause is mutually exclusive with
+.Sy auth-group ;
+one cannot use
+both in a single target.
+.It Ic chap Ar user Ar secret
+A set of CHAP authentication credentials.
+Note that targets must only use one of
+.Sy auth-group , chap , No or Sy chap-mutual ;
+it is a configuration error to mix multiple types in one target.
+.It Ic chap-mutual Ar user Ar secret Ar mutualuser Ar mutualsecret
+A set of mutual CHAP authentication credentials.
+Note that targets must only use one of
+.Sy auth-group , chap , No or Sy chap-mutual ;
+it is a configuration error to mix multiple types in one target.
+.It Ic initiator-name Ar initiator-name
+An iSCSI initiator name.
+Only initiators with a name matching one of the defined
+names will be allowed to connect.
+If not defined, there will be no restrictions based on initiator
+name.
+This clause is mutually exclusive with
+.Sy auth-group ;
+one cannot use
+both in a single target.
+.It Ic initiator-portal Ar address Ns Op / Ns Ar prefixlen
+An iSCSI initiator portal: an IPv4 or IPv6 address, optionally
+followed by a literal slash and a prefix length.
+Only initiators with an address matching one of the defined
+addresses will be allowed to connect.
+If not defined, there will be no restrictions based on initiator
+address.
+This clause is mutually exclusive with
+.Sy auth-group ;
+one cannot use
+both in a single target.
+.Pp
+The
+.Sy auth-type ,
+.Sy chap ,
+.Sy chap-mutual ,
+.Sy initiator-name ,
+and
+.Sy initiator-portal
+clauses in the target context provide an alternative to assigning an
+.Sy auth-group
+defined separately, useful in the common case of authentication settings
+specific to a single target.
+.It Ic portal-group Ar name Op Ar ag-name
+Assign a previously defined portal group to the target.
+The default portal group is
+.Qq Ar default ,
+which makes the target available
+on TCP port 3260 on all configured IPv4 and IPv6 addresses.
+Optional second argument specifies
+.Sy auth-group
+for connections to this specific portal group.
+If second argument is not specified, target
+.Sy auth-group
+is used.
+.It Ic port Ar name
+.It Ic port Ar name/pp
+.It Ic port Ar name/pp/vp
+Assign specified CTL port (such as "isp0" or "isp2/1") to the target.
+This is used to export the target through a specific physical - eg Fibre
+Channel - port, in addition to portal-groups configured for the target.
+Use
+.Cm "ctladm portlist"
+command to retrieve the list of available ports.
+On startup
+.Xr ctld 8
+configures LUN mapping and enables all assigned ports.
+Each port can be assigned to only one target.
+.It Ic redirect Ar address
+IPv4 or IPv6 address to redirect initiators to.
+When configured, all initiators attempting to connect to this target
+will get redirected using "Target moved temporarily" login response.
+Redirection happens after successful authentication.
+.It Ic lun Ar number Ar name
+Export previously defined
+.Sy lun
+by the parent target.
+.It Ic lun Ar number
+Create a
+.Sy lun
+configuration context, defining a LUN exported by the parent target.
+.Pp
+This is an alternative to defining the LUN separately, useful in the common
+case of a LUN being exported by a single target.
+.El
+.Ss lun Context
+.Bl -tag -width indent
+.It Ic backend Ar block No | Ar ramdisk
+The CTL backend to use for a given LUN.
+Valid choices are
+.Qq Ar block
+and
+.Qq Ar ramdisk ;
+block is used for LUNs backed
+by files or disk device nodes; ramdisk is a bitsink device, used mostly for
+testing.
+The default backend is block.
+.It Ic blocksize Ar size
+The blocksize visible to the initiator.
+The default blocksize is 512 for disks, and 2048 for CD/DVDs.
+.It Ic ctl-lun Ar lun_id
+Global numeric identifier to use for a given LUN inside CTL.
+By default CTL allocates those IDs dynamically, but explicit specification
+may be needed for consistency in HA configurations.
+.It Ic device-id Ar string
+The SCSI Device Identification string presented to the initiator.
+.It Ic device-type Ar type
+Specify the SCSI device type to use when creating the LUN.
+Currently CTL supports Direct Access (type 0), Processor (type 3)
+and CD/DVD (type 5) LUNs.
+.It Ic option Ar name Ar value
+The CTL-specific options passed to the kernel.
+All CTL-specific options are documented in the
+.Sx OPTIONS
+section of
+.Xr ctladm 8 .
+.It Ic path Ar path
+The path to the file, device node, or
+.Xr zfs 8
+volume used to back the LUN.
+For optimal performance, create the volume with the
+.Qq Ar volmode=dev
+property set.
+.It Ic serial Ar string
+The SCSI serial number presented to the initiator.
+.It Ic size Ar size
+The LUN size, in bytes.
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /etc/ctl.conf" -compact
+.It Pa /etc/ctl.conf
+The default location of the
+.Xr ctld 8
+configuration file.
+.El
+.Sh EXAMPLES
+.Bd -literal
+auth-group ag0 {
+ chap-mutual "user" "secret" "mutualuser" "mutualsecret"
+ chap-mutual "user2" "secret2" "mutualuser" "mutualsecret"
+ initiator-portal 192.168.1.1/16
+}
+
+auth-group ag1 {
+ auth-type none
+ initiator-name "iqn.2012-06.com.example:initiatorhost1"
+ initiator-name "iqn.2012-06.com.example:initiatorhost2"
+ initiator-portal 192.168.1.1/24
+ initiator-portal [2001:db8::de:ef]
+}
+
+portal-group pg0 {
+ discovery-auth-group no-authentication
+ listen 0.0.0.0:3260
+ listen [::]:3260
+ listen [fe80::be:ef]:3261
+}
+
+target iqn.2012-06.com.example:target0 {
+ alias "Example target"
+ auth-group no-authentication
+ lun 0 {
+ path /dev/zvol/tank/example_0
+ blocksize 4096
+ size 4G
+ }
+}
+
+lun example_1 {
+ path /dev/zvol/tank/example_1
+ option naa 0x50015178f369f093
+}
+
+target iqn.2012-06.com.example:target1 {
+ auth-group ag0
+ portal-group pg0
+ lun 0 example_1
+ lun 1 {
+ path /dev/zvol/tank/example_2
+ option vendor "FreeBSD"
+ }
+}
+
+target naa.50015178f369f092 {
+ port isp0
+ port isp1
+ lun 0 example_1
+}
+.Ed
+.Sh SEE ALSO
+.Xr ctl 4 ,
+.Xr ctladm 8 ,
+.Xr ctld 8 ,
+.Xr zfs 8
+.Sh AUTHORS
+The
+.Nm
+configuration file functionality for
+.Xr ctld 8
+was developed by
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+under sponsorship from the FreeBSD Foundation.
diff --git a/usr.sbin/ctld/ctld.8 b/usr.sbin/ctld/ctld.8
new file mode 100644
index 0000000..7ebb269
--- /dev/null
+++ b/usr.sbin/ctld/ctld.8
@@ -0,0 +1,119 @@
+.\" Copyright (c) 2012 The FreeBSD Foundation
+.\" All rights reserved.
+.\"
+.\" This software was 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 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 22, 2015
+.Dt CTLD 8
+.Os
+.Sh NAME
+.Nm ctld
+.Nd CAM Target Layer / iSCSI target daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl d
+.Op Fl f Ar config-file
+.Sh DESCRIPTION
+The
+.Nm
+daemon is responsible for managing the CAM Target Layer configuration,
+accepting incoming iSCSI connections, performing authentication and
+passing connections to the kernel part of the native iSCSI target.
+.Pp
+Upon startup, the
+.Nm
+daemon parses the configuration file and exits, if it encounters any errors.
+Then it compares the configuration with the kernel list of LUNs managed
+by previously running
+.Nm
+instances, removes LUNs no longer existing in the configuration file,
+and creates new LUNs as necessary.
+After that it listens for the incoming iSCSI connections, performs
+authentication, and, if successful, passes the connections to the kernel part
+of CTL iSCSI target, which handles it from that point.
+.Pp
+When it receives a SIGHUP signal, the
+.Nm
+reloads its configuration and applies the changes to the kernel.
+Changes are applied in a way that avoids unnecessary disruptions;
+for example removing one LUN does not affect other LUNs.
+.Pp
+When exiting gracefully, the
+.Nm
+daemon removes LUNs it managed and forcibly disconnects all the clients.
+Otherwise - for example, when killed with SIGKILL - LUNs stay configured
+and clients remain connected.
+.Pp
+To perform administrative actions that apply to already connected
+sessions, such as forcing termination, use
+.Xr ctladm 8 .
+.Pp
+The following options are available:
+.Bl -tag -width ".Fl P Ar pidfile"
+.It Fl f Ar config-file
+Specifies the name of the configuration file.
+The default is
+.Pa /etc/ctl.conf .
+.It Fl d
+Debug mode.
+The daemon sends verbose debug output to standard error, and does not
+put itself in the background.
+The daemon will also not fork and will exit after processing one connection.
+This option is only intended for debugging the target.
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /var/run/ctld.pid" -compact
+.It Pa /etc/ctl.conf
+The configuration file for
+.Nm .
+The file format and configuration options are described in
+.Xr ctl.conf 5 .
+.It Pa /var/run/ctld.pid
+The default location of the
+.Nm
+PID file.
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility exits 0 on success, and >0 if an error occurs.
+.Sh SEE ALSO
+.Xr ctl 4 ,
+.Xr ctl.conf 5 ,
+.Xr ctladm 8 ,
+.Xr ctlstat 8
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Fx 10.0 .
+.Sh AUTHORS
+The
+.Nm
+was developed by
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+under sponsorship from the FreeBSD Foundation.
diff --git a/usr.sbin/ctld/ctld.c b/usr.sbin/ctld/ctld.c
new file mode 100644
index 0000000..92fa553
--- /dev/null
+++ b/usr.sbin/ctld/ctld.c
@@ -0,0 +1,2619 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ctld.h"
+#include "isns.h"
+
+bool proxy_mode = false;
+
+static volatile bool sighup_received = false;
+static volatile bool sigterm_received = false;
+static volatile bool sigalrm_received = false;
+
+static int nchildren = 0;
+static uint16_t last_portal_group_tag = 0xff;
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: ctld [-d][-f config-file]\n");
+ exit(1);
+}
+
+char *
+checked_strdup(const char *s)
+{
+ char *c;
+
+ c = strdup(s);
+ if (c == NULL)
+ log_err(1, "strdup");
+ return (c);
+}
+
+struct conf *
+conf_new(void)
+{
+ struct conf *conf;
+
+ conf = calloc(1, sizeof(*conf));
+ if (conf == NULL)
+ log_err(1, "calloc");
+ TAILQ_INIT(&conf->conf_luns);
+ TAILQ_INIT(&conf->conf_targets);
+ TAILQ_INIT(&conf->conf_auth_groups);
+ TAILQ_INIT(&conf->conf_ports);
+ TAILQ_INIT(&conf->conf_portal_groups);
+ TAILQ_INIT(&conf->conf_pports);
+ TAILQ_INIT(&conf->conf_isns);
+
+ conf->conf_isns_period = 900;
+ conf->conf_isns_timeout = 5;
+ conf->conf_debug = 0;
+ conf->conf_timeout = 60;
+ conf->conf_maxproc = 30;
+
+ return (conf);
+}
+
+void
+conf_delete(struct conf *conf)
+{
+ struct lun *lun, *ltmp;
+ struct target *targ, *tmp;
+ struct auth_group *ag, *cagtmp;
+ struct portal_group *pg, *cpgtmp;
+ struct pport *pp, *pptmp;
+ struct isns *is, *istmp;
+
+ assert(conf->conf_pidfh == NULL);
+
+ TAILQ_FOREACH_SAFE(lun, &conf->conf_luns, l_next, ltmp)
+ lun_delete(lun);
+ TAILQ_FOREACH_SAFE(targ, &conf->conf_targets, t_next, tmp)
+ target_delete(targ);
+ TAILQ_FOREACH_SAFE(ag, &conf->conf_auth_groups, ag_next, cagtmp)
+ auth_group_delete(ag);
+ TAILQ_FOREACH_SAFE(pg, &conf->conf_portal_groups, pg_next, cpgtmp)
+ portal_group_delete(pg);
+ TAILQ_FOREACH_SAFE(pp, &conf->conf_pports, pp_next, pptmp)
+ pport_delete(pp);
+ TAILQ_FOREACH_SAFE(is, &conf->conf_isns, i_next, istmp)
+ isns_delete(is);
+ assert(TAILQ_EMPTY(&conf->conf_ports));
+ free(conf->conf_pidfile_path);
+ free(conf);
+}
+
+static struct auth *
+auth_new(struct auth_group *ag)
+{
+ struct auth *auth;
+
+ auth = calloc(1, sizeof(*auth));
+ if (auth == NULL)
+ log_err(1, "calloc");
+ auth->a_auth_group = ag;
+ TAILQ_INSERT_TAIL(&ag->ag_auths, auth, a_next);
+ return (auth);
+}
+
+static void
+auth_delete(struct auth *auth)
+{
+ TAILQ_REMOVE(&auth->a_auth_group->ag_auths, auth, a_next);
+
+ free(auth->a_user);
+ free(auth->a_secret);
+ free(auth->a_mutual_user);
+ free(auth->a_mutual_secret);
+ free(auth);
+}
+
+const struct auth *
+auth_find(const struct auth_group *ag, const char *user)
+{
+ const struct auth *auth;
+
+ TAILQ_FOREACH(auth, &ag->ag_auths, a_next) {
+ if (strcmp(auth->a_user, user) == 0)
+ return (auth);
+ }
+
+ return (NULL);
+}
+
+static void
+auth_check_secret_length(struct auth *auth)
+{
+ size_t len;
+
+ len = strlen(auth->a_secret);
+ if (len > 16) {
+ if (auth->a_auth_group->ag_name != NULL)
+ log_warnx("secret for user \"%s\", auth-group \"%s\", "
+ "is too long; it should be at most 16 characters "
+ "long", auth->a_user, auth->a_auth_group->ag_name);
+ else
+ log_warnx("secret for user \"%s\", target \"%s\", "
+ "is too long; it should be at most 16 characters "
+ "long", auth->a_user,
+ auth->a_auth_group->ag_target->t_name);
+ }
+ if (len < 12) {
+ if (auth->a_auth_group->ag_name != NULL)
+ log_warnx("secret for user \"%s\", auth-group \"%s\", "
+ "is too short; it should be at least 12 characters "
+ "long", auth->a_user,
+ auth->a_auth_group->ag_name);
+ else
+ log_warnx("secret for user \"%s\", target \"%s\", "
+ "is too short; it should be at least 16 characters "
+ "long", auth->a_user,
+ auth->a_auth_group->ag_target->t_name);
+ }
+
+ if (auth->a_mutual_secret != NULL) {
+ len = strlen(auth->a_mutual_secret);
+ if (len > 16) {
+ if (auth->a_auth_group->ag_name != NULL)
+ log_warnx("mutual secret for user \"%s\", "
+ "auth-group \"%s\", is too long; it should "
+ "be at most 16 characters long",
+ auth->a_user, auth->a_auth_group->ag_name);
+ else
+ log_warnx("mutual secret for user \"%s\", "
+ "target \"%s\", is too long; it should "
+ "be at most 16 characters long",
+ auth->a_user,
+ auth->a_auth_group->ag_target->t_name);
+ }
+ if (len < 12) {
+ if (auth->a_auth_group->ag_name != NULL)
+ log_warnx("mutual secret for user \"%s\", "
+ "auth-group \"%s\", is too short; it "
+ "should be at least 12 characters long",
+ auth->a_user, auth->a_auth_group->ag_name);
+ else
+ log_warnx("mutual secret for user \"%s\", "
+ "target \"%s\", is too short; it should be "
+ "at least 16 characters long",
+ auth->a_user,
+ auth->a_auth_group->ag_target->t_name);
+ }
+ }
+}
+
+const struct auth *
+auth_new_chap(struct auth_group *ag, const char *user,
+ const char *secret)
+{
+ struct auth *auth;
+
+ if (ag->ag_type == AG_TYPE_UNKNOWN)
+ ag->ag_type = AG_TYPE_CHAP;
+ if (ag->ag_type != AG_TYPE_CHAP) {
+ if (ag->ag_name != NULL)
+ log_warnx("cannot mix \"chap\" authentication with "
+ "other types for auth-group \"%s\"", ag->ag_name);
+ else
+ log_warnx("cannot mix \"chap\" authentication with "
+ "other types for target \"%s\"",
+ ag->ag_target->t_name);
+ return (NULL);
+ }
+
+ auth = auth_new(ag);
+ auth->a_user = checked_strdup(user);
+ auth->a_secret = checked_strdup(secret);
+
+ auth_check_secret_length(auth);
+
+ return (auth);
+}
+
+const struct auth *
+auth_new_chap_mutual(struct auth_group *ag, const char *user,
+ const char *secret, const char *user2, const char *secret2)
+{
+ struct auth *auth;
+
+ if (ag->ag_type == AG_TYPE_UNKNOWN)
+ ag->ag_type = AG_TYPE_CHAP_MUTUAL;
+ if (ag->ag_type != AG_TYPE_CHAP_MUTUAL) {
+ if (ag->ag_name != NULL)
+ log_warnx("cannot mix \"chap-mutual\" authentication "
+ "with other types for auth-group \"%s\"",
+ ag->ag_name);
+ else
+ log_warnx("cannot mix \"chap-mutual\" authentication "
+ "with other types for target \"%s\"",
+ ag->ag_target->t_name);
+ return (NULL);
+ }
+
+ auth = auth_new(ag);
+ auth->a_user = checked_strdup(user);
+ auth->a_secret = checked_strdup(secret);
+ auth->a_mutual_user = checked_strdup(user2);
+ auth->a_mutual_secret = checked_strdup(secret2);
+
+ auth_check_secret_length(auth);
+
+ return (auth);
+}
+
+const struct auth_name *
+auth_name_new(struct auth_group *ag, const char *name)
+{
+ struct auth_name *an;
+
+ an = calloc(1, sizeof(*an));
+ if (an == NULL)
+ log_err(1, "calloc");
+ an->an_auth_group = ag;
+ an->an_initator_name = checked_strdup(name);
+ TAILQ_INSERT_TAIL(&ag->ag_names, an, an_next);
+ return (an);
+}
+
+static void
+auth_name_delete(struct auth_name *an)
+{
+ TAILQ_REMOVE(&an->an_auth_group->ag_names, an, an_next);
+
+ free(an->an_initator_name);
+ free(an);
+}
+
+bool
+auth_name_defined(const struct auth_group *ag)
+{
+ if (TAILQ_EMPTY(&ag->ag_names))
+ return (false);
+ return (true);
+}
+
+const struct auth_name *
+auth_name_find(const struct auth_group *ag, const char *name)
+{
+ const struct auth_name *auth_name;
+
+ TAILQ_FOREACH(auth_name, &ag->ag_names, an_next) {
+ if (strcmp(auth_name->an_initator_name, name) == 0)
+ return (auth_name);
+ }
+
+ return (NULL);
+}
+
+int
+auth_name_check(const struct auth_group *ag, const char *initiator_name)
+{
+ if (!auth_name_defined(ag))
+ return (0);
+
+ if (auth_name_find(ag, initiator_name) == NULL)
+ return (1);
+
+ return (0);
+}
+
+const struct auth_portal *
+auth_portal_new(struct auth_group *ag, const char *portal)
+{
+ struct auth_portal *ap;
+ char *net, *mask, *str, *tmp;
+ int len, dm, m;
+
+ ap = calloc(1, sizeof(*ap));
+ if (ap == NULL)
+ log_err(1, "calloc");
+ ap->ap_auth_group = ag;
+ ap->ap_initator_portal = checked_strdup(portal);
+ mask = str = checked_strdup(portal);
+ net = strsep(&mask, "/");
+ if (net[0] == '[')
+ net++;
+ len = strlen(net);
+ if (len == 0)
+ goto error;
+ if (net[len - 1] == ']')
+ net[len - 1] = 0;
+ if (strchr(net, ':') != NULL) {
+ struct sockaddr_in6 *sin6 =
+ (struct sockaddr_in6 *)&ap->ap_sa;
+
+ sin6->sin6_len = sizeof(*sin6);
+ sin6->sin6_family = AF_INET6;
+ if (inet_pton(AF_INET6, net, &sin6->sin6_addr) <= 0)
+ goto error;
+ dm = 128;
+ } else {
+ struct sockaddr_in *sin =
+ (struct sockaddr_in *)&ap->ap_sa;
+
+ sin->sin_len = sizeof(*sin);
+ sin->sin_family = AF_INET;
+ if (inet_pton(AF_INET, net, &sin->sin_addr) <= 0)
+ goto error;
+ dm = 32;
+ }
+ if (mask != NULL) {
+ m = strtol(mask, &tmp, 0);
+ if (m < 0 || m > dm || tmp[0] != 0)
+ goto error;
+ } else
+ m = dm;
+ ap->ap_mask = m;
+ free(str);
+ TAILQ_INSERT_TAIL(&ag->ag_portals, ap, ap_next);
+ return (ap);
+
+error:
+ free(ap);
+ log_errx(1, "Incorrect initiator portal '%s'", portal);
+ return (NULL);
+}
+
+static void
+auth_portal_delete(struct auth_portal *ap)
+{
+ TAILQ_REMOVE(&ap->ap_auth_group->ag_portals, ap, ap_next);
+
+ free(ap->ap_initator_portal);
+ free(ap);
+}
+
+bool
+auth_portal_defined(const struct auth_group *ag)
+{
+ if (TAILQ_EMPTY(&ag->ag_portals))
+ return (false);
+ return (true);
+}
+
+const struct auth_portal *
+auth_portal_find(const struct auth_group *ag, const struct sockaddr_storage *ss)
+{
+ const struct auth_portal *ap;
+ const uint8_t *a, *b;
+ int i;
+ uint8_t bmask;
+
+ TAILQ_FOREACH(ap, &ag->ag_portals, ap_next) {
+ if (ap->ap_sa.ss_family != ss->ss_family)
+ continue;
+ if (ss->ss_family == AF_INET) {
+ a = (const uint8_t *)
+ &((const struct sockaddr_in *)ss)->sin_addr;
+ b = (const uint8_t *)
+ &((const struct sockaddr_in *)&ap->ap_sa)->sin_addr;
+ } else {
+ a = (const uint8_t *)
+ &((const struct sockaddr_in6 *)ss)->sin6_addr;
+ b = (const uint8_t *)
+ &((const struct sockaddr_in6 *)&ap->ap_sa)->sin6_addr;
+ }
+ for (i = 0; i < ap->ap_mask / 8; i++) {
+ if (a[i] != b[i])
+ goto next;
+ }
+ if (ap->ap_mask % 8) {
+ bmask = 0xff << (8 - (ap->ap_mask % 8));
+ if ((a[i] & bmask) != (b[i] & bmask))
+ goto next;
+ }
+ return (ap);
+next:
+ ;
+ }
+
+ return (NULL);
+}
+
+int
+auth_portal_check(const struct auth_group *ag, const struct sockaddr_storage *sa)
+{
+
+ if (!auth_portal_defined(ag))
+ return (0);
+
+ if (auth_portal_find(ag, sa) == NULL)
+ return (1);
+
+ return (0);
+}
+
+struct auth_group *
+auth_group_new(struct conf *conf, const char *name)
+{
+ struct auth_group *ag;
+
+ if (name != NULL) {
+ ag = auth_group_find(conf, name);
+ if (ag != NULL) {
+ log_warnx("duplicated auth-group \"%s\"", name);
+ return (NULL);
+ }
+ }
+
+ ag = calloc(1, sizeof(*ag));
+ if (ag == NULL)
+ log_err(1, "calloc");
+ if (name != NULL)
+ ag->ag_name = checked_strdup(name);
+ TAILQ_INIT(&ag->ag_auths);
+ TAILQ_INIT(&ag->ag_names);
+ TAILQ_INIT(&ag->ag_portals);
+ ag->ag_conf = conf;
+ TAILQ_INSERT_TAIL(&conf->conf_auth_groups, ag, ag_next);
+
+ return (ag);
+}
+
+void
+auth_group_delete(struct auth_group *ag)
+{
+ struct auth *auth, *auth_tmp;
+ struct auth_name *auth_name, *auth_name_tmp;
+ struct auth_portal *auth_portal, *auth_portal_tmp;
+
+ TAILQ_REMOVE(&ag->ag_conf->conf_auth_groups, ag, ag_next);
+
+ TAILQ_FOREACH_SAFE(auth, &ag->ag_auths, a_next, auth_tmp)
+ auth_delete(auth);
+ TAILQ_FOREACH_SAFE(auth_name, &ag->ag_names, an_next, auth_name_tmp)
+ auth_name_delete(auth_name);
+ TAILQ_FOREACH_SAFE(auth_portal, &ag->ag_portals, ap_next,
+ auth_portal_tmp)
+ auth_portal_delete(auth_portal);
+ free(ag->ag_name);
+ free(ag);
+}
+
+struct auth_group *
+auth_group_find(const struct conf *conf, const char *name)
+{
+ struct auth_group *ag;
+
+ TAILQ_FOREACH(ag, &conf->conf_auth_groups, ag_next) {
+ if (ag->ag_name != NULL && strcmp(ag->ag_name, name) == 0)
+ return (ag);
+ }
+
+ return (NULL);
+}
+
+int
+auth_group_set_type(struct auth_group *ag, const char *str)
+{
+ int type;
+
+ if (strcmp(str, "none") == 0) {
+ type = AG_TYPE_NO_AUTHENTICATION;
+ } else if (strcmp(str, "deny") == 0) {
+ type = AG_TYPE_DENY;
+ } else if (strcmp(str, "chap") == 0) {
+ type = AG_TYPE_CHAP;
+ } else if (strcmp(str, "chap-mutual") == 0) {
+ type = AG_TYPE_CHAP_MUTUAL;
+ } else {
+ if (ag->ag_name != NULL)
+ log_warnx("invalid auth-type \"%s\" for auth-group "
+ "\"%s\"", str, ag->ag_name);
+ else
+ log_warnx("invalid auth-type \"%s\" for target "
+ "\"%s\"", str, ag->ag_target->t_name);
+ return (1);
+ }
+
+ if (ag->ag_type != AG_TYPE_UNKNOWN && ag->ag_type != type) {
+ if (ag->ag_name != NULL) {
+ log_warnx("cannot set auth-type to \"%s\" for "
+ "auth-group \"%s\"; already has a different "
+ "type", str, ag->ag_name);
+ } else {
+ log_warnx("cannot set auth-type to \"%s\" for target "
+ "\"%s\"; already has a different type",
+ str, ag->ag_target->t_name);
+ }
+ return (1);
+ }
+
+ ag->ag_type = type;
+
+ return (0);
+}
+
+static struct portal *
+portal_new(struct portal_group *pg)
+{
+ struct portal *portal;
+
+ portal = calloc(1, sizeof(*portal));
+ if (portal == NULL)
+ log_err(1, "calloc");
+ TAILQ_INIT(&portal->p_targets);
+ portal->p_portal_group = pg;
+ TAILQ_INSERT_TAIL(&pg->pg_portals, portal, p_next);
+ return (portal);
+}
+
+static void
+portal_delete(struct portal *portal)
+{
+
+ TAILQ_REMOVE(&portal->p_portal_group->pg_portals, portal, p_next);
+ if (portal->p_ai != NULL)
+ freeaddrinfo(portal->p_ai);
+ free(portal->p_listen);
+ free(portal);
+}
+
+struct portal_group *
+portal_group_new(struct conf *conf, const char *name)
+{
+ struct portal_group *pg;
+
+ pg = portal_group_find(conf, name);
+ if (pg != NULL) {
+ log_warnx("duplicated portal-group \"%s\"", name);
+ return (NULL);
+ }
+
+ pg = calloc(1, sizeof(*pg));
+ if (pg == NULL)
+ log_err(1, "calloc");
+ pg->pg_name = checked_strdup(name);
+ TAILQ_INIT(&pg->pg_options);
+ TAILQ_INIT(&pg->pg_portals);
+ TAILQ_INIT(&pg->pg_ports);
+ pg->pg_conf = conf;
+ pg->pg_tag = 0; /* Assigned later in conf_apply(). */
+ TAILQ_INSERT_TAIL(&conf->conf_portal_groups, pg, pg_next);
+
+ return (pg);
+}
+
+void
+portal_group_delete(struct portal_group *pg)
+{
+ struct portal *portal, *tmp;
+ struct port *port, *tport;
+ struct option *o, *otmp;
+
+ TAILQ_FOREACH_SAFE(port, &pg->pg_ports, p_pgs, tport)
+ port_delete(port);
+ TAILQ_REMOVE(&pg->pg_conf->conf_portal_groups, pg, pg_next);
+
+ TAILQ_FOREACH_SAFE(portal, &pg->pg_portals, p_next, tmp)
+ portal_delete(portal);
+ TAILQ_FOREACH_SAFE(o, &pg->pg_options, o_next, otmp)
+ option_delete(&pg->pg_options, o);
+ free(pg->pg_name);
+ free(pg->pg_offload);
+ free(pg->pg_redirection);
+ free(pg);
+}
+
+struct portal_group *
+portal_group_find(const struct conf *conf, const char *name)
+{
+ struct portal_group *pg;
+
+ TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) {
+ if (strcmp(pg->pg_name, name) == 0)
+ return (pg);
+ }
+
+ return (NULL);
+}
+
+static int
+parse_addr_port(char *arg, const char *def_port, struct addrinfo **ai)
+{
+ struct addrinfo hints;
+ char *str, *addr, *ch;
+ const char *port;
+ int error, colons = 0;
+
+ str = arg = strdup(arg);
+ if (arg[0] == '[') {
+ /*
+ * IPv6 address in square brackets, perhaps with port.
+ */
+ arg++;
+ addr = strsep(&arg, "]");
+ if (arg == NULL)
+ return (1);
+ if (arg[0] == '\0') {
+ port = def_port;
+ } else if (arg[0] == ':') {
+ port = arg + 1;
+ } else {
+ free(str);
+ return (1);
+ }
+ } else {
+ /*
+ * Either IPv6 address without brackets - and without
+ * a port - or IPv4 address. Just count the colons.
+ */
+ for (ch = arg; *ch != '\0'; ch++) {
+ if (*ch == ':')
+ colons++;
+ }
+ if (colons > 1) {
+ addr = arg;
+ port = def_port;
+ } else {
+ addr = strsep(&arg, ":");
+ if (arg == NULL)
+ port = def_port;
+ else
+ port = arg;
+ }
+ }
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE;
+ error = getaddrinfo(addr, port, &hints, ai);
+ free(str);
+ return ((error != 0) ? 1 : 0);
+}
+
+int
+portal_group_add_listen(struct portal_group *pg, const char *value, bool iser)
+{
+ struct portal *portal;
+
+ portal = portal_new(pg);
+ portal->p_listen = checked_strdup(value);
+ portal->p_iser = iser;
+
+ if (parse_addr_port(portal->p_listen, "3260", &portal->p_ai)) {
+ log_warnx("invalid listen address %s", portal->p_listen);
+ portal_delete(portal);
+ return (1);
+ }
+
+ /*
+ * XXX: getaddrinfo(3) may return multiple addresses; we should turn
+ * those into multiple portals.
+ */
+
+ return (0);
+}
+
+int
+isns_new(struct conf *conf, const char *addr)
+{
+ struct isns *isns;
+
+ isns = calloc(1, sizeof(*isns));
+ if (isns == NULL)
+ log_err(1, "calloc");
+ isns->i_conf = conf;
+ TAILQ_INSERT_TAIL(&conf->conf_isns, isns, i_next);
+ isns->i_addr = checked_strdup(addr);
+
+ if (parse_addr_port(isns->i_addr, "3205", &isns->i_ai)) {
+ log_warnx("invalid iSNS address %s", isns->i_addr);
+ isns_delete(isns);
+ return (1);
+ }
+
+ /*
+ * XXX: getaddrinfo(3) may return multiple addresses; we should turn
+ * those into multiple servers.
+ */
+
+ return (0);
+}
+
+void
+isns_delete(struct isns *isns)
+{
+
+ TAILQ_REMOVE(&isns->i_conf->conf_isns, isns, i_next);
+ free(isns->i_addr);
+ if (isns->i_ai != NULL)
+ freeaddrinfo(isns->i_ai);
+ free(isns);
+}
+
+static int
+isns_do_connect(struct isns *isns)
+{
+ int s;
+
+ s = socket(isns->i_ai->ai_family, isns->i_ai->ai_socktype,
+ isns->i_ai->ai_protocol);
+ if (s < 0) {
+ log_warn("socket(2) failed for %s", isns->i_addr);
+ return (-1);
+ }
+ if (connect(s, isns->i_ai->ai_addr, isns->i_ai->ai_addrlen)) {
+ log_warn("connect(2) failed for %s", isns->i_addr);
+ close(s);
+ return (-1);
+ }
+ return(s);
+}
+
+static int
+isns_do_register(struct isns *isns, int s, const char *hostname)
+{
+ struct conf *conf = isns->i_conf;
+ struct target *target;
+ struct portal *portal;
+ struct portal_group *pg;
+ struct port *port;
+ struct isns_req *req;
+ int res = 0;
+ uint32_t error;
+
+ req = isns_req_create(ISNS_FUNC_DEVATTRREG, ISNS_FLAG_CLIENT);
+ isns_req_add_str(req, 32, TAILQ_FIRST(&conf->conf_targets)->t_name);
+ isns_req_add_delim(req);
+ isns_req_add_str(req, 1, hostname);
+ isns_req_add_32(req, 2, 2); /* 2 -- iSCSI */
+ isns_req_add_32(req, 6, conf->conf_isns_period);
+ TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) {
+ if (pg->pg_unassigned)
+ continue;
+ TAILQ_FOREACH(portal, &pg->pg_portals, p_next) {
+ isns_req_add_addr(req, 16, portal->p_ai);
+ isns_req_add_port(req, 17, portal->p_ai);
+ }
+ }
+ TAILQ_FOREACH(target, &conf->conf_targets, t_next) {
+ isns_req_add_str(req, 32, target->t_name);
+ isns_req_add_32(req, 33, 1); /* 1 -- Target*/
+ if (target->t_alias != NULL)
+ isns_req_add_str(req, 34, target->t_alias);
+ TAILQ_FOREACH(port, &target->t_ports, p_ts) {
+ if ((pg = port->p_portal_group) == NULL)
+ continue;
+ isns_req_add_32(req, 51, pg->pg_tag);
+ TAILQ_FOREACH(portal, &pg->pg_portals, p_next) {
+ isns_req_add_addr(req, 49, portal->p_ai);
+ isns_req_add_port(req, 50, portal->p_ai);
+ }
+ }
+ }
+ res = isns_req_send(s, req);
+ if (res < 0) {
+ log_warn("send(2) failed for %s", isns->i_addr);
+ goto quit;
+ }
+ res = isns_req_receive(s, req);
+ if (res < 0) {
+ log_warn("receive(2) failed for %s", isns->i_addr);
+ goto quit;
+ }
+ error = isns_req_get_status(req);
+ if (error != 0) {
+ log_warnx("iSNS register error %d for %s", error, isns->i_addr);
+ res = -1;
+ }
+quit:
+ isns_req_free(req);
+ return (res);
+}
+
+static int
+isns_do_check(struct isns *isns, int s, const char *hostname)
+{
+ struct conf *conf = isns->i_conf;
+ struct isns_req *req;
+ int res = 0;
+ uint32_t error;
+
+ req = isns_req_create(ISNS_FUNC_DEVATTRQRY, ISNS_FLAG_CLIENT);
+ isns_req_add_str(req, 32, TAILQ_FIRST(&conf->conf_targets)->t_name);
+ isns_req_add_str(req, 1, hostname);
+ isns_req_add_delim(req);
+ isns_req_add(req, 2, 0, NULL);
+ res = isns_req_send(s, req);
+ if (res < 0) {
+ log_warn("send(2) failed for %s", isns->i_addr);
+ goto quit;
+ }
+ res = isns_req_receive(s, req);
+ if (res < 0) {
+ log_warn("receive(2) failed for %s", isns->i_addr);
+ goto quit;
+ }
+ error = isns_req_get_status(req);
+ if (error != 0) {
+ log_warnx("iSNS check error %d for %s", error, isns->i_addr);
+ res = -1;
+ }
+quit:
+ isns_req_free(req);
+ return (res);
+}
+
+static int
+isns_do_deregister(struct isns *isns, int s, const char *hostname)
+{
+ struct conf *conf = isns->i_conf;
+ struct isns_req *req;
+ int res = 0;
+ uint32_t error;
+
+ req = isns_req_create(ISNS_FUNC_DEVDEREG, ISNS_FLAG_CLIENT);
+ isns_req_add_str(req, 32, TAILQ_FIRST(&conf->conf_targets)->t_name);
+ isns_req_add_delim(req);
+ isns_req_add_str(req, 1, hostname);
+ res = isns_req_send(s, req);
+ if (res < 0) {
+ log_warn("send(2) failed for %s", isns->i_addr);
+ goto quit;
+ }
+ res = isns_req_receive(s, req);
+ if (res < 0) {
+ log_warn("receive(2) failed for %s", isns->i_addr);
+ goto quit;
+ }
+ error = isns_req_get_status(req);
+ if (error != 0) {
+ log_warnx("iSNS deregister error %d for %s", error, isns->i_addr);
+ res = -1;
+ }
+quit:
+ isns_req_free(req);
+ return (res);
+}
+
+void
+isns_register(struct isns *isns, struct isns *oldisns)
+{
+ struct conf *conf = isns->i_conf;
+ int s;
+ char hostname[256];
+
+ if (TAILQ_EMPTY(&conf->conf_targets) ||
+ TAILQ_EMPTY(&conf->conf_portal_groups))
+ return;
+ set_timeout(conf->conf_isns_timeout, false);
+ s = isns_do_connect(isns);
+ if (s < 0) {
+ set_timeout(0, false);
+ return;
+ }
+ gethostname(hostname, sizeof(hostname));
+
+ if (oldisns == NULL || TAILQ_EMPTY(&oldisns->i_conf->conf_targets))
+ oldisns = isns;
+ isns_do_deregister(oldisns, s, hostname);
+ isns_do_register(isns, s, hostname);
+ close(s);
+ set_timeout(0, false);
+}
+
+void
+isns_check(struct isns *isns)
+{
+ struct conf *conf = isns->i_conf;
+ int s, res;
+ char hostname[256];
+
+ if (TAILQ_EMPTY(&conf->conf_targets) ||
+ TAILQ_EMPTY(&conf->conf_portal_groups))
+ return;
+ set_timeout(conf->conf_isns_timeout, false);
+ s = isns_do_connect(isns);
+ if (s < 0) {
+ set_timeout(0, false);
+ return;
+ }
+ gethostname(hostname, sizeof(hostname));
+
+ res = isns_do_check(isns, s, hostname);
+ if (res < 0) {
+ isns_do_deregister(isns, s, hostname);
+ isns_do_register(isns, s, hostname);
+ }
+ close(s);
+ set_timeout(0, false);
+}
+
+void
+isns_deregister(struct isns *isns)
+{
+ struct conf *conf = isns->i_conf;
+ int s;
+ char hostname[256];
+
+ if (TAILQ_EMPTY(&conf->conf_targets) ||
+ TAILQ_EMPTY(&conf->conf_portal_groups))
+ return;
+ set_timeout(conf->conf_isns_timeout, false);
+ s = isns_do_connect(isns);
+ if (s < 0)
+ return;
+ gethostname(hostname, sizeof(hostname));
+
+ isns_do_deregister(isns, s, hostname);
+ close(s);
+ set_timeout(0, false);
+}
+
+int
+portal_group_set_filter(struct portal_group *pg, const char *str)
+{
+ int filter;
+
+ if (strcmp(str, "none") == 0) {
+ filter = PG_FILTER_NONE;
+ } else if (strcmp(str, "portal") == 0) {
+ filter = PG_FILTER_PORTAL;
+ } else if (strcmp(str, "portal-name") == 0) {
+ filter = PG_FILTER_PORTAL_NAME;
+ } else if (strcmp(str, "portal-name-auth") == 0) {
+ filter = PG_FILTER_PORTAL_NAME_AUTH;
+ } else {
+ log_warnx("invalid discovery-filter \"%s\" for portal-group "
+ "\"%s\"; valid values are \"none\", \"portal\", "
+ "\"portal-name\", and \"portal-name-auth\"",
+ str, pg->pg_name);
+ return (1);
+ }
+
+ if (pg->pg_discovery_filter != PG_FILTER_UNKNOWN &&
+ pg->pg_discovery_filter != filter) {
+ log_warnx("cannot set discovery-filter to \"%s\" for "
+ "portal-group \"%s\"; already has a different "
+ "value", str, pg->pg_name);
+ return (1);
+ }
+
+ pg->pg_discovery_filter = filter;
+
+ return (0);
+}
+
+int
+portal_group_set_offload(struct portal_group *pg, const char *offload)
+{
+
+ if (pg->pg_offload != NULL) {
+ log_warnx("cannot set offload to \"%s\" for "
+ "portal-group \"%s\"; already defined",
+ offload, pg->pg_name);
+ return (1);
+ }
+
+ pg->pg_offload = checked_strdup(offload);
+
+ return (0);
+}
+
+int
+portal_group_set_redirection(struct portal_group *pg, const char *addr)
+{
+
+ if (pg->pg_redirection != NULL) {
+ log_warnx("cannot set redirection to \"%s\" for "
+ "portal-group \"%s\"; already defined",
+ addr, pg->pg_name);
+ return (1);
+ }
+
+ pg->pg_redirection = checked_strdup(addr);
+
+ return (0);
+}
+
+static bool
+valid_hex(const char ch)
+{
+ switch (ch) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case 'a':
+ case 'A':
+ case 'b':
+ case 'B':
+ case 'c':
+ case 'C':
+ case 'd':
+ case 'D':
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'F':
+ return (true);
+ default:
+ return (false);
+ }
+}
+
+bool
+valid_iscsi_name(const char *name)
+{
+ int i;
+
+ if (strlen(name) >= MAX_NAME_LEN) {
+ log_warnx("overlong name for target \"%s\"; max length allowed "
+ "by iSCSI specification is %d characters",
+ name, MAX_NAME_LEN);
+ return (false);
+ }
+
+ /*
+ * In the cases below, we don't return an error, just in case the admin
+ * was right, and we're wrong.
+ */
+ if (strncasecmp(name, "iqn.", strlen("iqn.")) == 0) {
+ for (i = strlen("iqn."); name[i] != '\0'; i++) {
+ /*
+ * XXX: We should verify UTF-8 normalisation, as defined
+ * by 3.2.6.2: iSCSI Name Encoding.
+ */
+ if (isalnum(name[i]))
+ continue;
+ if (name[i] == '-' || name[i] == '.' || name[i] == ':')
+ continue;
+ log_warnx("invalid character \"%c\" in target name "
+ "\"%s\"; allowed characters are letters, digits, "
+ "'-', '.', and ':'", name[i], name);
+ break;
+ }
+ /*
+ * XXX: Check more stuff: valid date and a valid reversed domain.
+ */
+ } else if (strncasecmp(name, "eui.", strlen("eui.")) == 0) {
+ if (strlen(name) != strlen("eui.") + 16)
+ log_warnx("invalid target name \"%s\"; the \"eui.\" "
+ "should be followed by exactly 16 hexadecimal "
+ "digits", name);
+ for (i = strlen("eui."); name[i] != '\0'; i++) {
+ if (!valid_hex(name[i])) {
+ log_warnx("invalid character \"%c\" in target "
+ "name \"%s\"; allowed characters are 1-9 "
+ "and A-F", name[i], name);
+ break;
+ }
+ }
+ } else if (strncasecmp(name, "naa.", strlen("naa.")) == 0) {
+ if (strlen(name) > strlen("naa.") + 32)
+ log_warnx("invalid target name \"%s\"; the \"naa.\" "
+ "should be followed by at most 32 hexadecimal "
+ "digits", name);
+ for (i = strlen("naa."); name[i] != '\0'; i++) {
+ if (!valid_hex(name[i])) {
+ log_warnx("invalid character \"%c\" in target "
+ "name \"%s\"; allowed characters are 1-9 "
+ "and A-F", name[i], name);
+ break;
+ }
+ }
+ } else {
+ log_warnx("invalid target name \"%s\"; should start with "
+ "either \"iqn.\", \"eui.\", or \"naa.\"",
+ name);
+ }
+ return (true);
+}
+
+struct pport *
+pport_new(struct conf *conf, const char *name, uint32_t ctl_port)
+{
+ struct pport *pp;
+
+ pp = calloc(1, sizeof(*pp));
+ if (pp == NULL)
+ log_err(1, "calloc");
+ pp->pp_conf = conf;
+ pp->pp_name = checked_strdup(name);
+ pp->pp_ctl_port = ctl_port;
+ TAILQ_INIT(&pp->pp_ports);
+ TAILQ_INSERT_TAIL(&conf->conf_pports, pp, pp_next);
+ return (pp);
+}
+
+struct pport *
+pport_find(const struct conf *conf, const char *name)
+{
+ struct pport *pp;
+
+ TAILQ_FOREACH(pp, &conf->conf_pports, pp_next) {
+ if (strcasecmp(pp->pp_name, name) == 0)
+ return (pp);
+ }
+ return (NULL);
+}
+
+struct pport *
+pport_copy(struct pport *pp, struct conf *conf)
+{
+ struct pport *ppnew;
+
+ ppnew = pport_new(conf, pp->pp_name, pp->pp_ctl_port);
+ return (ppnew);
+}
+
+void
+pport_delete(struct pport *pp)
+{
+ struct port *port, *tport;
+
+ TAILQ_FOREACH_SAFE(port, &pp->pp_ports, p_ts, tport)
+ port_delete(port);
+ TAILQ_REMOVE(&pp->pp_conf->conf_pports, pp, pp_next);
+ free(pp->pp_name);
+ free(pp);
+}
+
+struct port *
+port_new(struct conf *conf, struct target *target, struct portal_group *pg)
+{
+ struct port *port;
+ char *name;
+ int ret;
+
+ ret = asprintf(&name, "%s-%s", pg->pg_name, target->t_name);
+ if (ret <= 0)
+ log_err(1, "asprintf");
+ if (port_find(conf, name) != NULL) {
+ log_warnx("duplicate port \"%s\"", name);
+ free(name);
+ return (NULL);
+ }
+ port = calloc(1, sizeof(*port));
+ if (port == NULL)
+ log_err(1, "calloc");
+ port->p_conf = conf;
+ port->p_name = name;
+ TAILQ_INSERT_TAIL(&conf->conf_ports, port, p_next);
+ TAILQ_INSERT_TAIL(&target->t_ports, port, p_ts);
+ port->p_target = target;
+ TAILQ_INSERT_TAIL(&pg->pg_ports, port, p_pgs);
+ port->p_portal_group = pg;
+ port->p_foreign = pg->pg_foreign;
+ return (port);
+}
+
+struct port *
+port_new_pp(struct conf *conf, struct target *target, struct pport *pp)
+{
+ struct port *port;
+ char *name;
+ int ret;
+
+ ret = asprintf(&name, "%s-%s", pp->pp_name, target->t_name);
+ if (ret <= 0)
+ log_err(1, "asprintf");
+ if (port_find(conf, name) != NULL) {
+ log_warnx("duplicate port \"%s\"", name);
+ free(name);
+ return (NULL);
+ }
+ port = calloc(1, sizeof(*port));
+ if (port == NULL)
+ log_err(1, "calloc");
+ port->p_conf = conf;
+ port->p_name = name;
+ TAILQ_INSERT_TAIL(&conf->conf_ports, port, p_next);
+ TAILQ_INSERT_TAIL(&target->t_ports, port, p_ts);
+ port->p_target = target;
+ TAILQ_INSERT_TAIL(&pp->pp_ports, port, p_pps);
+ port->p_pport = pp;
+ return (port);
+}
+
+struct port *
+port_find(const struct conf *conf, const char *name)
+{
+ struct port *port;
+
+ TAILQ_FOREACH(port, &conf->conf_ports, p_next) {
+ if (strcasecmp(port->p_name, name) == 0)
+ return (port);
+ }
+
+ return (NULL);
+}
+
+struct port *
+port_find_in_pg(const struct portal_group *pg, const char *target)
+{
+ struct port *port;
+
+ TAILQ_FOREACH(port, &pg->pg_ports, p_pgs) {
+ if (strcasecmp(port->p_target->t_name, target) == 0)
+ return (port);
+ }
+
+ return (NULL);
+}
+
+void
+port_delete(struct port *port)
+{
+
+ if (port->p_portal_group)
+ TAILQ_REMOVE(&port->p_portal_group->pg_ports, port, p_pgs);
+ if (port->p_pport)
+ TAILQ_REMOVE(&port->p_pport->pp_ports, port, p_pps);
+ if (port->p_target)
+ TAILQ_REMOVE(&port->p_target->t_ports, port, p_ts);
+ TAILQ_REMOVE(&port->p_conf->conf_ports, port, p_next);
+ free(port->p_name);
+ free(port);
+}
+
+struct target *
+target_new(struct conf *conf, const char *name)
+{
+ struct target *targ;
+ int i, len;
+
+ targ = target_find(conf, name);
+ if (targ != NULL) {
+ log_warnx("duplicated target \"%s\"", name);
+ return (NULL);
+ }
+ if (valid_iscsi_name(name) == false) {
+ log_warnx("target name \"%s\" is invalid", name);
+ return (NULL);
+ }
+ targ = calloc(1, sizeof(*targ));
+ if (targ == NULL)
+ log_err(1, "calloc");
+ targ->t_name = checked_strdup(name);
+
+ /*
+ * RFC 3722 requires us to normalize the name to lowercase.
+ */
+ len = strlen(name);
+ for (i = 0; i < len; i++)
+ targ->t_name[i] = tolower(targ->t_name[i]);
+
+ targ->t_conf = conf;
+ TAILQ_INIT(&targ->t_ports);
+ TAILQ_INSERT_TAIL(&conf->conf_targets, targ, t_next);
+
+ return (targ);
+}
+
+void
+target_delete(struct target *targ)
+{
+ struct port *port, *tport;
+
+ TAILQ_FOREACH_SAFE(port, &targ->t_ports, p_ts, tport)
+ port_delete(port);
+ TAILQ_REMOVE(&targ->t_conf->conf_targets, targ, t_next);
+
+ free(targ->t_name);
+ free(targ->t_redirection);
+ free(targ);
+}
+
+struct target *
+target_find(struct conf *conf, const char *name)
+{
+ struct target *targ;
+
+ TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
+ if (strcasecmp(targ->t_name, name) == 0)
+ return (targ);
+ }
+
+ return (NULL);
+}
+
+int
+target_set_redirection(struct target *target, const char *addr)
+{
+
+ if (target->t_redirection != NULL) {
+ log_warnx("cannot set redirection to \"%s\" for "
+ "target \"%s\"; already defined",
+ addr, target->t_name);
+ return (1);
+ }
+
+ target->t_redirection = checked_strdup(addr);
+
+ return (0);
+}
+
+struct lun *
+lun_new(struct conf *conf, const char *name)
+{
+ struct lun *lun;
+
+ lun = lun_find(conf, name);
+ if (lun != NULL) {
+ log_warnx("duplicated lun \"%s\"", name);
+ return (NULL);
+ }
+
+ lun = calloc(1, sizeof(*lun));
+ if (lun == NULL)
+ log_err(1, "calloc");
+ lun->l_conf = conf;
+ lun->l_name = checked_strdup(name);
+ TAILQ_INIT(&lun->l_options);
+ TAILQ_INSERT_TAIL(&conf->conf_luns, lun, l_next);
+ lun->l_ctl_lun = -1;
+
+ return (lun);
+}
+
+void
+lun_delete(struct lun *lun)
+{
+ struct target *targ;
+ struct option *o, *tmp;
+ int i;
+
+ TAILQ_FOREACH(targ, &lun->l_conf->conf_targets, t_next) {
+ for (i = 0; i < MAX_LUNS; i++) {
+ if (targ->t_luns[i] == lun)
+ targ->t_luns[i] = NULL;
+ }
+ }
+ TAILQ_REMOVE(&lun->l_conf->conf_luns, lun, l_next);
+
+ TAILQ_FOREACH_SAFE(o, &lun->l_options, o_next, tmp)
+ option_delete(&lun->l_options, o);
+ free(lun->l_name);
+ free(lun->l_backend);
+ free(lun->l_device_id);
+ free(lun->l_path);
+ free(lun->l_scsiname);
+ free(lun->l_serial);
+ free(lun);
+}
+
+struct lun *
+lun_find(const struct conf *conf, const char *name)
+{
+ struct lun *lun;
+
+ TAILQ_FOREACH(lun, &conf->conf_luns, l_next) {
+ if (strcmp(lun->l_name, name) == 0)
+ return (lun);
+ }
+
+ return (NULL);
+}
+
+void
+lun_set_backend(struct lun *lun, const char *value)
+{
+ free(lun->l_backend);
+ lun->l_backend = checked_strdup(value);
+}
+
+void
+lun_set_blocksize(struct lun *lun, size_t value)
+{
+
+ lun->l_blocksize = value;
+}
+
+void
+lun_set_device_type(struct lun *lun, uint8_t value)
+{
+
+ lun->l_device_type = value;
+}
+
+void
+lun_set_device_id(struct lun *lun, const char *value)
+{
+ free(lun->l_device_id);
+ lun->l_device_id = checked_strdup(value);
+}
+
+void
+lun_set_path(struct lun *lun, const char *value)
+{
+ free(lun->l_path);
+ lun->l_path = checked_strdup(value);
+}
+
+void
+lun_set_scsiname(struct lun *lun, const char *value)
+{
+ free(lun->l_scsiname);
+ lun->l_scsiname = checked_strdup(value);
+}
+
+void
+lun_set_serial(struct lun *lun, const char *value)
+{
+ free(lun->l_serial);
+ lun->l_serial = checked_strdup(value);
+}
+
+void
+lun_set_size(struct lun *lun, size_t value)
+{
+
+ lun->l_size = value;
+}
+
+void
+lun_set_ctl_lun(struct lun *lun, uint32_t value)
+{
+
+ lun->l_ctl_lun = value;
+}
+
+struct option *
+option_new(struct options *options, const char *name, const char *value)
+{
+ struct option *o;
+
+ o = option_find(options, name);
+ if (o != NULL) {
+ log_warnx("duplicated option \"%s\"", name);
+ return (NULL);
+ }
+
+ o = calloc(1, sizeof(*o));
+ if (o == NULL)
+ log_err(1, "calloc");
+ o->o_name = checked_strdup(name);
+ o->o_value = checked_strdup(value);
+ TAILQ_INSERT_TAIL(options, o, o_next);
+
+ return (o);
+}
+
+void
+option_delete(struct options *options, struct option *o)
+{
+
+ TAILQ_REMOVE(options, o, o_next);
+ free(o->o_name);
+ free(o->o_value);
+ free(o);
+}
+
+struct option *
+option_find(const struct options *options, const char *name)
+{
+ struct option *o;
+
+ TAILQ_FOREACH(o, options, o_next) {
+ if (strcmp(o->o_name, name) == 0)
+ return (o);
+ }
+
+ return (NULL);
+}
+
+void
+option_set(struct option *o, const char *value)
+{
+
+ free(o->o_value);
+ o->o_value = checked_strdup(value);
+}
+
+static struct connection *
+connection_new(struct portal *portal, int fd, const char *host,
+ const struct sockaddr *client_sa)
+{
+ struct connection *conn;
+
+ conn = calloc(1, sizeof(*conn));
+ if (conn == NULL)
+ log_err(1, "calloc");
+ conn->conn_portal = portal;
+ conn->conn_socket = fd;
+ conn->conn_initiator_addr = checked_strdup(host);
+ memcpy(&conn->conn_initiator_sa, client_sa, client_sa->sa_len);
+
+ /*
+ * Default values, from RFC 3720, section 12.
+ */
+ conn->conn_max_data_segment_length = 8192;
+ conn->conn_max_burst_length = 262144;
+ conn->conn_immediate_data = true;
+
+ return (conn);
+}
+
+#if 0
+static void
+conf_print(struct conf *conf)
+{
+ struct auth_group *ag;
+ struct auth *auth;
+ struct auth_name *auth_name;
+ struct auth_portal *auth_portal;
+ struct portal_group *pg;
+ struct portal *portal;
+ struct target *targ;
+ struct lun *lun;
+ struct option *o;
+
+ TAILQ_FOREACH(ag, &conf->conf_auth_groups, ag_next) {
+ fprintf(stderr, "auth-group %s {\n", ag->ag_name);
+ TAILQ_FOREACH(auth, &ag->ag_auths, a_next)
+ fprintf(stderr, "\t chap-mutual %s %s %s %s\n",
+ auth->a_user, auth->a_secret,
+ auth->a_mutual_user, auth->a_mutual_secret);
+ TAILQ_FOREACH(auth_name, &ag->ag_names, an_next)
+ fprintf(stderr, "\t initiator-name %s\n",
+ auth_name->an_initator_name);
+ TAILQ_FOREACH(auth_portal, &ag->ag_portals, an_next)
+ fprintf(stderr, "\t initiator-portal %s\n",
+ auth_portal->an_initator_portal);
+ fprintf(stderr, "}\n");
+ }
+ TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) {
+ fprintf(stderr, "portal-group %s {\n", pg->pg_name);
+ TAILQ_FOREACH(portal, &pg->pg_portals, p_next)
+ fprintf(stderr, "\t listen %s\n", portal->p_listen);
+ fprintf(stderr, "}\n");
+ }
+ TAILQ_FOREACH(lun, &conf->conf_luns, l_next) {
+ fprintf(stderr, "\tlun %s {\n", lun->l_name);
+ fprintf(stderr, "\t\tpath %s\n", lun->l_path);
+ TAILQ_FOREACH(o, &lun->l_options, o_next)
+ fprintf(stderr, "\t\toption %s %s\n",
+ lo->o_name, lo->o_value);
+ fprintf(stderr, "\t}\n");
+ }
+ TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
+ fprintf(stderr, "target %s {\n", targ->t_name);
+ if (targ->t_alias != NULL)
+ fprintf(stderr, "\t alias %s\n", targ->t_alias);
+ fprintf(stderr, "}\n");
+ }
+}
+#endif
+
+static int
+conf_verify_lun(struct lun *lun)
+{
+ const struct lun *lun2;
+
+ if (lun->l_backend == NULL)
+ lun_set_backend(lun, "block");
+ if (strcmp(lun->l_backend, "block") == 0) {
+ if (lun->l_path == NULL) {
+ log_warnx("missing path for lun \"%s\"",
+ lun->l_name);
+ return (1);
+ }
+ } else if (strcmp(lun->l_backend, "ramdisk") == 0) {
+ if (lun->l_size == 0) {
+ log_warnx("missing size for ramdisk-backed lun \"%s\"",
+ lun->l_name);
+ return (1);
+ }
+ if (lun->l_path != NULL) {
+ log_warnx("path must not be specified "
+ "for ramdisk-backed lun \"%s\"",
+ lun->l_name);
+ return (1);
+ }
+ }
+ if (lun->l_blocksize == 0) {
+ if (lun->l_device_type == 5)
+ lun_set_blocksize(lun, DEFAULT_CD_BLOCKSIZE);
+ else
+ lun_set_blocksize(lun, DEFAULT_BLOCKSIZE);
+ } else if (lun->l_blocksize < 0) {
+ log_warnx("invalid blocksize for lun \"%s\"; "
+ "must be larger than 0", lun->l_name);
+ return (1);
+ }
+ if (lun->l_size != 0 && lun->l_size % lun->l_blocksize != 0) {
+ log_warnx("invalid size for lun \"%s\"; "
+ "must be multiple of blocksize", lun->l_name);
+ return (1);
+ }
+ TAILQ_FOREACH(lun2, &lun->l_conf->conf_luns, l_next) {
+ if (lun == lun2)
+ continue;
+ if (lun->l_path != NULL && lun2->l_path != NULL &&
+ strcmp(lun->l_path, lun2->l_path) == 0) {
+ log_debugx("WARNING: path \"%s\" duplicated "
+ "between lun \"%s\", and "
+ "lun \"%s\"", lun->l_path,
+ lun->l_name, lun2->l_name);
+ }
+ }
+
+ return (0);
+}
+
+int
+conf_verify(struct conf *conf)
+{
+ struct auth_group *ag;
+ struct portal_group *pg;
+ struct port *port;
+ struct target *targ;
+ struct lun *lun;
+ bool found;
+ int error, i;
+
+ if (conf->conf_pidfile_path == NULL)
+ conf->conf_pidfile_path = checked_strdup(DEFAULT_PIDFILE);
+
+ TAILQ_FOREACH(lun, &conf->conf_luns, l_next) {
+ error = conf_verify_lun(lun);
+ if (error != 0)
+ return (error);
+ }
+ TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
+ if (targ->t_auth_group == NULL) {
+ targ->t_auth_group = auth_group_find(conf,
+ "default");
+ assert(targ->t_auth_group != NULL);
+ }
+ if (TAILQ_EMPTY(&targ->t_ports)) {
+ pg = portal_group_find(conf, "default");
+ assert(pg != NULL);
+ port_new(conf, targ, pg);
+ }
+ found = false;
+ for (i = 0; i < MAX_LUNS; i++) {
+ if (targ->t_luns[i] != NULL)
+ found = true;
+ }
+ if (!found && targ->t_redirection == NULL) {
+ log_warnx("no LUNs defined for target \"%s\"",
+ targ->t_name);
+ }
+ if (found && targ->t_redirection != NULL) {
+ log_debugx("target \"%s\" contains luns, "
+ " but configured for redirection",
+ targ->t_name);
+ }
+ }
+ TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) {
+ assert(pg->pg_name != NULL);
+ if (pg->pg_discovery_auth_group == NULL) {
+ pg->pg_discovery_auth_group =
+ auth_group_find(conf, "default");
+ assert(pg->pg_discovery_auth_group != NULL);
+ }
+
+ if (pg->pg_discovery_filter == PG_FILTER_UNKNOWN)
+ pg->pg_discovery_filter = PG_FILTER_NONE;
+
+ if (pg->pg_redirection != NULL) {
+ if (!TAILQ_EMPTY(&pg->pg_ports)) {
+ log_debugx("portal-group \"%s\" assigned "
+ "to target, but configured "
+ "for redirection",
+ pg->pg_name);
+ }
+ pg->pg_unassigned = false;
+ } else if (!TAILQ_EMPTY(&pg->pg_ports)) {
+ pg->pg_unassigned = false;
+ } else {
+ if (strcmp(pg->pg_name, "default") != 0)
+ log_warnx("portal-group \"%s\" not assigned "
+ "to any target", pg->pg_name);
+ pg->pg_unassigned = true;
+ }
+ }
+ TAILQ_FOREACH(ag, &conf->conf_auth_groups, ag_next) {
+ if (ag->ag_name == NULL)
+ assert(ag->ag_target != NULL);
+ else
+ assert(ag->ag_target == NULL);
+
+ found = false;
+ TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
+ if (targ->t_auth_group == ag) {
+ found = true;
+ break;
+ }
+ }
+ TAILQ_FOREACH(port, &conf->conf_ports, p_next) {
+ if (port->p_auth_group == ag) {
+ found = true;
+ break;
+ }
+ }
+ TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) {
+ if (pg->pg_discovery_auth_group == ag) {
+ found = true;
+ break;
+ }
+ }
+ if (!found && ag->ag_name != NULL &&
+ strcmp(ag->ag_name, "default") != 0 &&
+ strcmp(ag->ag_name, "no-authentication") != 0 &&
+ strcmp(ag->ag_name, "no-access") != 0) {
+ log_warnx("auth-group \"%s\" not assigned "
+ "to any target", ag->ag_name);
+ }
+ }
+
+ return (0);
+}
+
+static int
+conf_apply(struct conf *oldconf, struct conf *newconf)
+{
+ struct lun *oldlun, *newlun, *tmplun;
+ struct portal_group *oldpg, *newpg;
+ struct portal *oldp, *newp;
+ struct port *oldport, *newport, *tmpport;
+ struct isns *oldns, *newns;
+ pid_t otherpid;
+ int changed, cumulated_error = 0, error, sockbuf;
+ int one = 1;
+
+ if (oldconf->conf_debug != newconf->conf_debug) {
+ log_debugx("changing debug level to %d", newconf->conf_debug);
+ log_init(newconf->conf_debug);
+ }
+
+ if (oldconf->conf_pidfh != NULL) {
+ assert(oldconf->conf_pidfile_path != NULL);
+ if (newconf->conf_pidfile_path != NULL &&
+ strcmp(oldconf->conf_pidfile_path,
+ newconf->conf_pidfile_path) == 0) {
+ newconf->conf_pidfh = oldconf->conf_pidfh;
+ oldconf->conf_pidfh = NULL;
+ } else {
+ log_debugx("removing pidfile %s",
+ oldconf->conf_pidfile_path);
+ pidfile_remove(oldconf->conf_pidfh);
+ oldconf->conf_pidfh = NULL;
+ }
+ }
+
+ if (newconf->conf_pidfh == NULL && newconf->conf_pidfile_path != NULL) {
+ log_debugx("opening pidfile %s", newconf->conf_pidfile_path);
+ newconf->conf_pidfh =
+ pidfile_open(newconf->conf_pidfile_path, 0600, &otherpid);
+ if (newconf->conf_pidfh == NULL) {
+ if (errno == EEXIST)
+ log_errx(1, "daemon already running, pid: %jd.",
+ (intmax_t)otherpid);
+ log_err(1, "cannot open or create pidfile \"%s\"",
+ newconf->conf_pidfile_path);
+ }
+ }
+
+ /*
+ * Go through the new portal groups, assigning tags or preserving old.
+ */
+ TAILQ_FOREACH(newpg, &newconf->conf_portal_groups, pg_next) {
+ if (newpg->pg_tag != 0)
+ continue;
+ oldpg = portal_group_find(oldconf, newpg->pg_name);
+ if (oldpg != NULL)
+ newpg->pg_tag = oldpg->pg_tag;
+ else
+ newpg->pg_tag = ++last_portal_group_tag;
+ }
+
+ /* Deregister on removed iSNS servers. */
+ TAILQ_FOREACH(oldns, &oldconf->conf_isns, i_next) {
+ TAILQ_FOREACH(newns, &newconf->conf_isns, i_next) {
+ if (strcmp(oldns->i_addr, newns->i_addr) == 0)
+ break;
+ }
+ if (newns == NULL)
+ isns_deregister(oldns);
+ }
+
+ /*
+ * XXX: If target or lun removal fails, we should somehow "move"
+ * the old lun or target into newconf, so that subsequent
+ * conf_apply() would try to remove them again. That would
+ * be somewhat hairy, though, and lun deletion failures don't
+ * really happen, so leave it as it is for now.
+ */
+ /*
+ * First, remove any ports present in the old configuration
+ * and missing in the new one.
+ */
+ TAILQ_FOREACH_SAFE(oldport, &oldconf->conf_ports, p_next, tmpport) {
+ if (oldport->p_foreign)
+ continue;
+ newport = port_find(newconf, oldport->p_name);
+ if (newport != NULL && !newport->p_foreign)
+ continue;
+ log_debugx("removing port \"%s\"", oldport->p_name);
+ error = kernel_port_remove(oldport);
+ if (error != 0) {
+ log_warnx("failed to remove port %s",
+ oldport->p_name);
+ /*
+ * XXX: Uncomment after fixing the root cause.
+ *
+ * cumulated_error++;
+ */
+ }
+ }
+
+ /*
+ * Second, remove any LUNs present in the old configuration
+ * and missing in the new one.
+ */
+ TAILQ_FOREACH_SAFE(oldlun, &oldconf->conf_luns, l_next, tmplun) {
+ newlun = lun_find(newconf, oldlun->l_name);
+ if (newlun == NULL) {
+ log_debugx("lun \"%s\", CTL lun %d "
+ "not found in new configuration; "
+ "removing", oldlun->l_name, oldlun->l_ctl_lun);
+ error = kernel_lun_remove(oldlun);
+ if (error != 0) {
+ log_warnx("failed to remove lun \"%s\", "
+ "CTL lun %d",
+ oldlun->l_name, oldlun->l_ctl_lun);
+ cumulated_error++;
+ }
+ continue;
+ }
+
+ /*
+ * Also remove the LUNs changed by more than size.
+ */
+ changed = 0;
+ assert(oldlun->l_backend != NULL);
+ assert(newlun->l_backend != NULL);
+ if (strcmp(newlun->l_backend, oldlun->l_backend) != 0) {
+ log_debugx("backend for lun \"%s\", "
+ "CTL lun %d changed; removing",
+ oldlun->l_name, oldlun->l_ctl_lun);
+ changed = 1;
+ }
+ if (oldlun->l_blocksize != newlun->l_blocksize) {
+ log_debugx("blocksize for lun \"%s\", "
+ "CTL lun %d changed; removing",
+ oldlun->l_name, oldlun->l_ctl_lun);
+ changed = 1;
+ }
+ if (newlun->l_device_id != NULL &&
+ (oldlun->l_device_id == NULL ||
+ strcmp(oldlun->l_device_id, newlun->l_device_id) !=
+ 0)) {
+ log_debugx("device-id for lun \"%s\", "
+ "CTL lun %d changed; removing",
+ oldlun->l_name, oldlun->l_ctl_lun);
+ changed = 1;
+ }
+ if (newlun->l_path != NULL &&
+ (oldlun->l_path == NULL ||
+ strcmp(oldlun->l_path, newlun->l_path) != 0)) {
+ log_debugx("path for lun \"%s\", "
+ "CTL lun %d, changed; removing",
+ oldlun->l_name, oldlun->l_ctl_lun);
+ changed = 1;
+ }
+ if (newlun->l_serial != NULL &&
+ (oldlun->l_serial == NULL ||
+ strcmp(oldlun->l_serial, newlun->l_serial) != 0)) {
+ log_debugx("serial for lun \"%s\", "
+ "CTL lun %d changed; removing",
+ oldlun->l_name, oldlun->l_ctl_lun);
+ changed = 1;
+ }
+ if (changed) {
+ error = kernel_lun_remove(oldlun);
+ if (error != 0) {
+ log_warnx("failed to remove lun \"%s\", "
+ "CTL lun %d",
+ oldlun->l_name, oldlun->l_ctl_lun);
+ cumulated_error++;
+ }
+ lun_delete(oldlun);
+ continue;
+ }
+
+ lun_set_ctl_lun(newlun, oldlun->l_ctl_lun);
+ }
+
+ TAILQ_FOREACH_SAFE(newlun, &newconf->conf_luns, l_next, tmplun) {
+ oldlun = lun_find(oldconf, newlun->l_name);
+ if (oldlun != NULL) {
+ log_debugx("modifying lun \"%s\", CTL lun %d",
+ newlun->l_name, newlun->l_ctl_lun);
+ error = kernel_lun_modify(newlun);
+ if (error != 0) {
+ log_warnx("failed to "
+ "modify lun \"%s\", CTL lun %d",
+ newlun->l_name, newlun->l_ctl_lun);
+ cumulated_error++;
+ }
+ continue;
+ }
+ log_debugx("adding lun \"%s\"", newlun->l_name);
+ error = kernel_lun_add(newlun);
+ if (error != 0) {
+ log_warnx("failed to add lun \"%s\"", newlun->l_name);
+ lun_delete(newlun);
+ cumulated_error++;
+ }
+ }
+
+ /*
+ * Now add new ports or modify existing ones.
+ */
+ TAILQ_FOREACH(newport, &newconf->conf_ports, p_next) {
+ if (newport->p_foreign)
+ continue;
+ oldport = port_find(oldconf, newport->p_name);
+
+ if (oldport == NULL || oldport->p_foreign) {
+ log_debugx("adding port \"%s\"", newport->p_name);
+ error = kernel_port_add(newport);
+ } else {
+ log_debugx("updating port \"%s\"", newport->p_name);
+ newport->p_ctl_port = oldport->p_ctl_port;
+ error = kernel_port_update(newport, oldport);
+ }
+ if (error != 0) {
+ log_warnx("failed to %s port %s",
+ (oldport == NULL) ? "add" : "update",
+ newport->p_name);
+ /*
+ * XXX: Uncomment after fixing the root cause.
+ *
+ * cumulated_error++;
+ */
+ }
+ }
+
+ /*
+ * Go through the new portals, opening the sockets as necessary.
+ */
+ TAILQ_FOREACH(newpg, &newconf->conf_portal_groups, pg_next) {
+ if (newpg->pg_foreign)
+ continue;
+ if (newpg->pg_unassigned) {
+ log_debugx("not listening on portal-group \"%s\", "
+ "not assigned to any target",
+ newpg->pg_name);
+ continue;
+ }
+ TAILQ_FOREACH(newp, &newpg->pg_portals, p_next) {
+ /*
+ * Try to find already open portal and reuse
+ * the listening socket. We don't care about
+ * what portal or portal group that was, what
+ * matters is the listening address.
+ */
+ TAILQ_FOREACH(oldpg, &oldconf->conf_portal_groups,
+ pg_next) {
+ TAILQ_FOREACH(oldp, &oldpg->pg_portals,
+ p_next) {
+ if (strcmp(newp->p_listen,
+ oldp->p_listen) == 0 &&
+ oldp->p_socket > 0) {
+ newp->p_socket =
+ oldp->p_socket;
+ oldp->p_socket = 0;
+ break;
+ }
+ }
+ }
+ if (newp->p_socket > 0) {
+ /*
+ * We're done with this portal.
+ */
+ continue;
+ }
+
+#ifdef ICL_KERNEL_PROXY
+ if (proxy_mode) {
+ newpg->pg_conf->conf_portal_id++;
+ newp->p_id = newpg->pg_conf->conf_portal_id;
+ log_debugx("listening on %s, portal-group "
+ "\"%s\", portal id %d, using ICL proxy",
+ newp->p_listen, newpg->pg_name, newp->p_id);
+ kernel_listen(newp->p_ai, newp->p_iser,
+ newp->p_id);
+ continue;
+ }
+#endif
+ assert(proxy_mode == false);
+ assert(newp->p_iser == false);
+
+ log_debugx("listening on %s, portal-group \"%s\"",
+ newp->p_listen, newpg->pg_name);
+ newp->p_socket = socket(newp->p_ai->ai_family,
+ newp->p_ai->ai_socktype,
+ newp->p_ai->ai_protocol);
+ if (newp->p_socket < 0) {
+ log_warn("socket(2) failed for %s",
+ newp->p_listen);
+ cumulated_error++;
+ continue;
+ }
+ sockbuf = SOCKBUF_SIZE;
+ if (setsockopt(newp->p_socket, SOL_SOCKET, SO_RCVBUF,
+ &sockbuf, sizeof(sockbuf)) == -1)
+ log_warn("setsockopt(SO_RCVBUF) failed "
+ "for %s", newp->p_listen);
+ sockbuf = SOCKBUF_SIZE;
+ if (setsockopt(newp->p_socket, SOL_SOCKET, SO_SNDBUF,
+ &sockbuf, sizeof(sockbuf)) == -1)
+ log_warn("setsockopt(SO_SNDBUF) failed "
+ "for %s", newp->p_listen);
+ error = setsockopt(newp->p_socket, SOL_SOCKET,
+ SO_REUSEADDR, &one, sizeof(one));
+ if (error != 0) {
+ log_warn("setsockopt(SO_REUSEADDR) failed "
+ "for %s", newp->p_listen);
+ close(newp->p_socket);
+ newp->p_socket = 0;
+ cumulated_error++;
+ continue;
+ }
+ error = bind(newp->p_socket, newp->p_ai->ai_addr,
+ newp->p_ai->ai_addrlen);
+ if (error != 0) {
+ log_warn("bind(2) failed for %s",
+ newp->p_listen);
+ close(newp->p_socket);
+ newp->p_socket = 0;
+ cumulated_error++;
+ continue;
+ }
+ error = listen(newp->p_socket, -1);
+ if (error != 0) {
+ log_warn("listen(2) failed for %s",
+ newp->p_listen);
+ close(newp->p_socket);
+ newp->p_socket = 0;
+ cumulated_error++;
+ continue;
+ }
+ }
+ }
+
+ /*
+ * Go through the no longer used sockets, closing them.
+ */
+ TAILQ_FOREACH(oldpg, &oldconf->conf_portal_groups, pg_next) {
+ TAILQ_FOREACH(oldp, &oldpg->pg_portals, p_next) {
+ if (oldp->p_socket <= 0)
+ continue;
+ log_debugx("closing socket for %s, portal-group \"%s\"",
+ oldp->p_listen, oldpg->pg_name);
+ close(oldp->p_socket);
+ oldp->p_socket = 0;
+ }
+ }
+
+ /* (Re-)Register on remaining/new iSNS servers. */
+ TAILQ_FOREACH(newns, &newconf->conf_isns, i_next) {
+ TAILQ_FOREACH(oldns, &oldconf->conf_isns, i_next) {
+ if (strcmp(oldns->i_addr, newns->i_addr) == 0)
+ break;
+ }
+ isns_register(newns, oldns);
+ }
+
+ /* Schedule iSNS update */
+ if (!TAILQ_EMPTY(&newconf->conf_isns))
+ set_timeout((newconf->conf_isns_period + 2) / 3, false);
+
+ return (cumulated_error);
+}
+
+bool
+timed_out(void)
+{
+
+ return (sigalrm_received);
+}
+
+static void
+sigalrm_handler_fatal(int dummy __unused)
+{
+ /*
+ * It would be easiest to just log an error and exit. We can't
+ * do this, though, because log_errx() is not signal safe, since
+ * it calls syslog(3). Instead, set a flag checked by pdu_send()
+ * and pdu_receive(), to call log_errx() there. Should they fail
+ * to notice, we'll exit here one second later.
+ */
+ if (sigalrm_received) {
+ /*
+ * Oh well. Just give up and quit.
+ */
+ _exit(2);
+ }
+
+ sigalrm_received = true;
+}
+
+static void
+sigalrm_handler(int dummy __unused)
+{
+
+ sigalrm_received = true;
+}
+
+void
+set_timeout(int timeout, int fatal)
+{
+ struct sigaction sa;
+ struct itimerval itv;
+ int error;
+
+ if (timeout <= 0) {
+ log_debugx("session timeout disabled");
+ bzero(&itv, sizeof(itv));
+ error = setitimer(ITIMER_REAL, &itv, NULL);
+ if (error != 0)
+ log_err(1, "setitimer");
+ sigalrm_received = false;
+ return;
+ }
+
+ sigalrm_received = false;
+ bzero(&sa, sizeof(sa));
+ if (fatal)
+ sa.sa_handler = sigalrm_handler_fatal;
+ else
+ sa.sa_handler = sigalrm_handler;
+ sigfillset(&sa.sa_mask);
+ error = sigaction(SIGALRM, &sa, NULL);
+ if (error != 0)
+ log_err(1, "sigaction");
+
+ /*
+ * First SIGALRM will arive after conf_timeout seconds.
+ * If we do nothing, another one will arrive a second later.
+ */
+ log_debugx("setting session timeout to %d seconds", timeout);
+ bzero(&itv, sizeof(itv));
+ itv.it_interval.tv_sec = 1;
+ itv.it_value.tv_sec = timeout;
+ error = setitimer(ITIMER_REAL, &itv, NULL);
+ if (error != 0)
+ log_err(1, "setitimer");
+}
+
+static int
+wait_for_children(bool block)
+{
+ pid_t pid;
+ int status;
+ int num = 0;
+
+ for (;;) {
+ /*
+ * If "block" is true, wait for at least one process.
+ */
+ if (block && num == 0)
+ pid = wait4(-1, &status, 0, NULL);
+ else
+ pid = wait4(-1, &status, WNOHANG, NULL);
+ if (pid <= 0)
+ break;
+ if (WIFSIGNALED(status)) {
+ log_warnx("child process %d terminated with signal %d",
+ pid, WTERMSIG(status));
+ } else if (WEXITSTATUS(status) != 0) {
+ log_warnx("child process %d terminated with exit status %d",
+ pid, WEXITSTATUS(status));
+ } else {
+ log_debugx("child process %d terminated gracefully", pid);
+ }
+ num++;
+ }
+
+ return (num);
+}
+
+static void
+handle_connection(struct portal *portal, int fd,
+ const struct sockaddr *client_sa, bool dont_fork)
+{
+ struct connection *conn;
+ int error;
+ pid_t pid;
+ char host[NI_MAXHOST + 1];
+ struct conf *conf;
+
+ conf = portal->p_portal_group->pg_conf;
+
+ if (dont_fork) {
+ log_debugx("incoming connection; not forking due to -d flag");
+ } else {
+ nchildren -= wait_for_children(false);
+ assert(nchildren >= 0);
+
+ while (conf->conf_maxproc > 0 && nchildren >= conf->conf_maxproc) {
+ log_debugx("maxproc limit of %d child processes hit; "
+ "waiting for child process to exit", conf->conf_maxproc);
+ nchildren -= wait_for_children(true);
+ assert(nchildren >= 0);
+ }
+ log_debugx("incoming connection; forking child process #%d",
+ nchildren);
+ nchildren++;
+ pid = fork();
+ if (pid < 0)
+ log_err(1, "fork");
+ if (pid > 0) {
+ close(fd);
+ return;
+ }
+ }
+ pidfile_close(conf->conf_pidfh);
+
+ error = getnameinfo(client_sa, client_sa->sa_len,
+ host, sizeof(host), NULL, 0, NI_NUMERICHOST);
+ if (error != 0)
+ log_errx(1, "getnameinfo: %s", gai_strerror(error));
+
+ log_debugx("accepted connection from %s; portal group \"%s\"",
+ host, portal->p_portal_group->pg_name);
+ log_set_peer_addr(host);
+ setproctitle("%s", host);
+
+ conn = connection_new(portal, fd, host, client_sa);
+ set_timeout(conf->conf_timeout, true);
+ kernel_capsicate();
+ login(conn);
+ if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) {
+ kernel_handoff(conn);
+ log_debugx("connection handed off to the kernel");
+ } else {
+ assert(conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY);
+ discovery(conn);
+ }
+ log_debugx("nothing more to do; exiting");
+ exit(0);
+}
+
+static int
+fd_add(int fd, fd_set *fdset, int nfds)
+{
+
+ /*
+ * Skip sockets which we failed to bind.
+ */
+ if (fd <= 0)
+ return (nfds);
+
+ FD_SET(fd, fdset);
+ if (fd > nfds)
+ nfds = fd;
+ return (nfds);
+}
+
+static void
+main_loop(struct conf *conf, bool dont_fork)
+{
+ struct portal_group *pg;
+ struct portal *portal;
+ struct sockaddr_storage client_sa;
+ socklen_t client_salen;
+#ifdef ICL_KERNEL_PROXY
+ int connection_id;
+ int portal_id;
+#endif
+ fd_set fdset;
+ int error, nfds, client_fd;
+
+ pidfile_write(conf->conf_pidfh);
+
+ for (;;) {
+ if (sighup_received || sigterm_received || timed_out())
+ return;
+
+#ifdef ICL_KERNEL_PROXY
+ if (proxy_mode) {
+ client_salen = sizeof(client_sa);
+ kernel_accept(&connection_id, &portal_id,
+ (struct sockaddr *)&client_sa, &client_salen);
+ assert(client_salen >= client_sa.ss_len);
+
+ log_debugx("incoming connection, id %d, portal id %d",
+ connection_id, portal_id);
+ TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) {
+ TAILQ_FOREACH(portal, &pg->pg_portals, p_next) {
+ if (portal->p_id == portal_id) {
+ goto found;
+ }
+ }
+ }
+
+ log_errx(1, "kernel returned invalid portal_id %d",
+ portal_id);
+
+found:
+ handle_connection(portal, connection_id,
+ (struct sockaddr *)&client_sa, dont_fork);
+ } else {
+#endif
+ assert(proxy_mode == false);
+
+ FD_ZERO(&fdset);
+ nfds = 0;
+ TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) {
+ TAILQ_FOREACH(portal, &pg->pg_portals, p_next)
+ nfds = fd_add(portal->p_socket, &fdset, nfds);
+ }
+ error = select(nfds + 1, &fdset, NULL, NULL, NULL);
+ if (error <= 0) {
+ if (errno == EINTR)
+ return;
+ log_err(1, "select");
+ }
+ TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) {
+ TAILQ_FOREACH(portal, &pg->pg_portals, p_next) {
+ if (!FD_ISSET(portal->p_socket, &fdset))
+ continue;
+ client_salen = sizeof(client_sa);
+ client_fd = accept(portal->p_socket,
+ (struct sockaddr *)&client_sa,
+ &client_salen);
+ if (client_fd < 0) {
+ if (errno == ECONNABORTED)
+ continue;
+ log_err(1, "accept");
+ }
+ assert(client_salen >= client_sa.ss_len);
+
+ handle_connection(portal, client_fd,
+ (struct sockaddr *)&client_sa,
+ dont_fork);
+ break;
+ }
+ }
+#ifdef ICL_KERNEL_PROXY
+ }
+#endif
+ }
+}
+
+static void
+sighup_handler(int dummy __unused)
+{
+
+ sighup_received = true;
+}
+
+static void
+sigterm_handler(int dummy __unused)
+{
+
+ sigterm_received = true;
+}
+
+static void
+sigchld_handler(int dummy __unused)
+{
+
+ /*
+ * The only purpose of this handler is to make SIGCHLD
+ * interrupt the ISCSIDWAIT ioctl(2), so we can call
+ * wait_for_children().
+ */
+}
+
+static void
+register_signals(void)
+{
+ struct sigaction sa;
+ int error;
+
+ bzero(&sa, sizeof(sa));
+ sa.sa_handler = sighup_handler;
+ sigfillset(&sa.sa_mask);
+ error = sigaction(SIGHUP, &sa, NULL);
+ if (error != 0)
+ log_err(1, "sigaction");
+
+ sa.sa_handler = sigterm_handler;
+ error = sigaction(SIGTERM, &sa, NULL);
+ if (error != 0)
+ log_err(1, "sigaction");
+
+ sa.sa_handler = sigterm_handler;
+ error = sigaction(SIGINT, &sa, NULL);
+ if (error != 0)
+ log_err(1, "sigaction");
+
+ sa.sa_handler = sigchld_handler;
+ error = sigaction(SIGCHLD, &sa, NULL);
+ if (error != 0)
+ log_err(1, "sigaction");
+}
+
+int
+main(int argc, char **argv)
+{
+ struct conf *oldconf, *newconf, *tmpconf;
+ struct isns *newns;
+ const char *config_path = DEFAULT_CONFIG_PATH;
+ int debug = 0, ch, error;
+ bool dont_daemonize = false;
+
+ while ((ch = getopt(argc, argv, "df:R")) != -1) {
+ switch (ch) {
+ case 'd':
+ dont_daemonize = true;
+ debug++;
+ break;
+ case 'f':
+ config_path = optarg;
+ break;
+ case 'R':
+#ifndef ICL_KERNEL_PROXY
+ log_errx(1, "ctld(8) compiled without ICL_KERNEL_PROXY "
+ "does not support iSER protocol");
+#endif
+ proxy_mode = true;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ if (argc != 0)
+ usage();
+
+ log_init(debug);
+ kernel_init();
+
+ oldconf = conf_new_from_kernel();
+ newconf = conf_new_from_file(config_path, oldconf);
+ if (newconf == NULL)
+ log_errx(1, "configuration error; exiting");
+ if (debug > 0) {
+ oldconf->conf_debug = debug;
+ newconf->conf_debug = debug;
+ }
+
+ error = conf_apply(oldconf, newconf);
+ if (error != 0)
+ log_errx(1, "failed to apply configuration; exiting");
+
+ conf_delete(oldconf);
+ oldconf = NULL;
+
+ register_signals();
+
+ if (dont_daemonize == false) {
+ log_debugx("daemonizing");
+ if (daemon(0, 0) == -1) {
+ log_warn("cannot daemonize");
+ pidfile_remove(newconf->conf_pidfh);
+ exit(1);
+ }
+ }
+
+ /* Schedule iSNS update */
+ if (!TAILQ_EMPTY(&newconf->conf_isns))
+ set_timeout((newconf->conf_isns_period + 2) / 3, false);
+
+ for (;;) {
+ main_loop(newconf, dont_daemonize);
+ if (sighup_received) {
+ sighup_received = false;
+ log_debugx("received SIGHUP, reloading configuration");
+ tmpconf = conf_new_from_file(config_path, newconf);
+ if (tmpconf == NULL) {
+ log_warnx("configuration error, "
+ "continuing with old configuration");
+ } else {
+ if (debug > 0)
+ tmpconf->conf_debug = debug;
+ oldconf = newconf;
+ newconf = tmpconf;
+ error = conf_apply(oldconf, newconf);
+ if (error != 0)
+ log_warnx("failed to reload "
+ "configuration");
+ conf_delete(oldconf);
+ oldconf = NULL;
+ }
+ } else if (sigterm_received) {
+ log_debugx("exiting on signal; "
+ "reloading empty configuration");
+
+ log_debugx("removing CTL iSCSI ports "
+ "and terminating all connections");
+
+ oldconf = newconf;
+ newconf = conf_new();
+ if (debug > 0)
+ newconf->conf_debug = debug;
+ error = conf_apply(oldconf, newconf);
+ if (error != 0)
+ log_warnx("failed to apply configuration");
+ conf_delete(oldconf);
+ oldconf = NULL;
+
+ log_warnx("exiting on signal");
+ exit(0);
+ } else {
+ nchildren -= wait_for_children(false);
+ assert(nchildren >= 0);
+ if (timed_out()) {
+ set_timeout(0, false);
+ TAILQ_FOREACH(newns, &newconf->conf_isns, i_next)
+ isns_check(newns);
+ /* Schedule iSNS update */
+ if (!TAILQ_EMPTY(&newconf->conf_isns)) {
+ set_timeout((newconf->conf_isns_period
+ + 2) / 3,
+ false);
+ }
+ }
+ }
+ }
+ /* NOTREACHED */
+}
diff --git a/usr.sbin/ctld/ctld.h b/usr.sbin/ctld/ctld.h
new file mode 100644
index 0000000..808b722
--- /dev/null
+++ b/usr.sbin/ctld/ctld.h
@@ -0,0 +1,455 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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$
+ */
+
+#ifndef CTLD_H
+#define CTLD_H
+
+#include <sys/queue.h>
+#ifdef ICL_KERNEL_PROXY
+#include <sys/types.h>
+#endif
+#include <sys/socket.h>
+#include <stdbool.h>
+#include <libutil.h>
+
+#define DEFAULT_CONFIG_PATH "/etc/ctl.conf"
+#define DEFAULT_PIDFILE "/var/run/ctld.pid"
+#define DEFAULT_BLOCKSIZE 512
+#define DEFAULT_CD_BLOCKSIZE 2048
+
+#define MAX_LUNS 1024
+#define MAX_NAME_LEN 223
+#define MAX_DATA_SEGMENT_LENGTH (128 * 1024)
+#define MAX_BURST_LENGTH 16776192
+#define SOCKBUF_SIZE 1048576
+
+struct auth {
+ TAILQ_ENTRY(auth) a_next;
+ struct auth_group *a_auth_group;
+ char *a_user;
+ char *a_secret;
+ char *a_mutual_user;
+ char *a_mutual_secret;
+};
+
+struct auth_name {
+ TAILQ_ENTRY(auth_name) an_next;
+ struct auth_group *an_auth_group;
+ char *an_initator_name;
+};
+
+struct auth_portal {
+ TAILQ_ENTRY(auth_portal) ap_next;
+ struct auth_group *ap_auth_group;
+ char *ap_initator_portal;
+ struct sockaddr_storage ap_sa;
+ int ap_mask;
+};
+
+#define AG_TYPE_UNKNOWN 0
+#define AG_TYPE_DENY 1
+#define AG_TYPE_NO_AUTHENTICATION 2
+#define AG_TYPE_CHAP 3
+#define AG_TYPE_CHAP_MUTUAL 4
+
+struct auth_group {
+ TAILQ_ENTRY(auth_group) ag_next;
+ struct conf *ag_conf;
+ char *ag_name;
+ struct target *ag_target;
+ int ag_type;
+ TAILQ_HEAD(, auth) ag_auths;
+ TAILQ_HEAD(, auth_name) ag_names;
+ TAILQ_HEAD(, auth_portal) ag_portals;
+};
+
+struct portal {
+ TAILQ_ENTRY(portal) p_next;
+ struct portal_group *p_portal_group;
+ bool p_iser;
+ char *p_listen;
+ struct addrinfo *p_ai;
+#ifdef ICL_KERNEL_PROXY
+ int p_id;
+#endif
+
+ TAILQ_HEAD(, target) p_targets;
+ int p_socket;
+};
+
+TAILQ_HEAD(options, option);
+
+#define PG_FILTER_UNKNOWN 0
+#define PG_FILTER_NONE 1
+#define PG_FILTER_PORTAL 2
+#define PG_FILTER_PORTAL_NAME 3
+#define PG_FILTER_PORTAL_NAME_AUTH 4
+
+struct portal_group {
+ TAILQ_ENTRY(portal_group) pg_next;
+ struct conf *pg_conf;
+ struct options pg_options;
+ char *pg_name;
+ struct auth_group *pg_discovery_auth_group;
+ int pg_discovery_filter;
+ int pg_foreign;
+ bool pg_unassigned;
+ TAILQ_HEAD(, portal) pg_portals;
+ TAILQ_HEAD(, port) pg_ports;
+ char *pg_offload;
+ char *pg_redirection;
+
+ uint16_t pg_tag;
+};
+
+struct pport {
+ TAILQ_ENTRY(pport) pp_next;
+ TAILQ_HEAD(, port) pp_ports;
+ struct conf *pp_conf;
+ char *pp_name;
+
+ uint32_t pp_ctl_port;
+};
+
+struct port {
+ TAILQ_ENTRY(port) p_next;
+ TAILQ_ENTRY(port) p_pgs;
+ TAILQ_ENTRY(port) p_pps;
+ TAILQ_ENTRY(port) p_ts;
+ struct conf *p_conf;
+ char *p_name;
+ struct auth_group *p_auth_group;
+ struct portal_group *p_portal_group;
+ struct pport *p_pport;
+ struct target *p_target;
+ int p_foreign;
+
+ uint32_t p_ctl_port;
+};
+
+struct option {
+ TAILQ_ENTRY(option) o_next;
+ char *o_name;
+ char *o_value;
+};
+
+struct lun {
+ TAILQ_ENTRY(lun) l_next;
+ struct conf *l_conf;
+ struct options l_options;
+ char *l_name;
+ char *l_backend;
+ uint8_t l_device_type;
+ int l_blocksize;
+ char *l_device_id;
+ char *l_path;
+ char *l_scsiname;
+ char *l_serial;
+ int64_t l_size;
+
+ int l_ctl_lun;
+};
+
+struct target {
+ TAILQ_ENTRY(target) t_next;
+ struct conf *t_conf;
+ struct lun *t_luns[MAX_LUNS];
+ struct auth_group *t_auth_group;
+ TAILQ_HEAD(, port) t_ports;
+ char *t_name;
+ char *t_alias;
+ char *t_redirection;
+};
+
+struct isns {
+ TAILQ_ENTRY(isns) i_next;
+ struct conf *i_conf;
+ char *i_addr;
+ struct addrinfo *i_ai;
+};
+
+struct conf {
+ char *conf_pidfile_path;
+ TAILQ_HEAD(, lun) conf_luns;
+ TAILQ_HEAD(, target) conf_targets;
+ TAILQ_HEAD(, auth_group) conf_auth_groups;
+ TAILQ_HEAD(, port) conf_ports;
+ TAILQ_HEAD(, portal_group) conf_portal_groups;
+ TAILQ_HEAD(, pport) conf_pports;
+ TAILQ_HEAD(, isns) conf_isns;
+ int conf_isns_period;
+ int conf_isns_timeout;
+ int conf_debug;
+ int conf_timeout;
+ int conf_maxproc;
+
+#ifdef ICL_KERNEL_PROXY
+ int conf_portal_id;
+#endif
+ struct pidfh *conf_pidfh;
+
+ bool conf_default_pg_defined;
+ bool conf_default_ag_defined;
+ bool conf_kernel_port_on;
+};
+
+#define CONN_SESSION_TYPE_NONE 0
+#define CONN_SESSION_TYPE_DISCOVERY 1
+#define CONN_SESSION_TYPE_NORMAL 2
+
+#define CONN_DIGEST_NONE 0
+#define CONN_DIGEST_CRC32C 1
+
+struct connection {
+ struct portal *conn_portal;
+ struct port *conn_port;
+ struct target *conn_target;
+ int conn_socket;
+ int conn_session_type;
+ char *conn_initiator_name;
+ char *conn_initiator_addr;
+ char *conn_initiator_alias;
+ uint8_t conn_initiator_isid[6];
+ struct sockaddr_storage conn_initiator_sa;
+ uint32_t conn_cmdsn;
+ uint32_t conn_statsn;
+ size_t conn_data_segment_limit;
+ size_t conn_max_data_segment_length;
+ size_t conn_max_burst_length;
+ int conn_immediate_data;
+ int conn_header_digest;
+ int conn_data_digest;
+ const char *conn_user;
+ struct chap *conn_chap;
+};
+
+struct pdu {
+ struct connection *pdu_connection;
+ struct iscsi_bhs *pdu_bhs;
+ char *pdu_data;
+ size_t pdu_data_len;
+};
+
+#define KEYS_MAX 1024
+
+struct keys {
+ char *keys_names[KEYS_MAX];
+ char *keys_values[KEYS_MAX];
+ char *keys_data;
+ size_t keys_data_len;
+};
+
+#define CHAP_CHALLENGE_LEN 1024
+#define CHAP_DIGEST_LEN 16 /* Equal to MD5 digest size. */
+
+struct chap {
+ unsigned char chap_id;
+ char chap_challenge[CHAP_CHALLENGE_LEN];
+ char chap_response[CHAP_DIGEST_LEN];
+};
+
+struct rchap {
+ char *rchap_secret;
+ unsigned char rchap_id;
+ void *rchap_challenge;
+ size_t rchap_challenge_len;
+};
+
+struct chap *chap_new(void);
+char *chap_get_id(const struct chap *chap);
+char *chap_get_challenge(const struct chap *chap);
+int chap_receive(struct chap *chap, const char *response);
+int chap_authenticate(struct chap *chap,
+ const char *secret);
+void chap_delete(struct chap *chap);
+
+struct rchap *rchap_new(const char *secret);
+int rchap_receive(struct rchap *rchap,
+ const char *id, const char *challenge);
+char *rchap_get_response(struct rchap *rchap);
+void rchap_delete(struct rchap *rchap);
+
+struct conf *conf_new(void);
+struct conf *conf_new_from_file(const char *path, struct conf *old);
+struct conf *conf_new_from_kernel(void);
+void conf_delete(struct conf *conf);
+int conf_verify(struct conf *conf);
+
+struct auth_group *auth_group_new(struct conf *conf, const char *name);
+void auth_group_delete(struct auth_group *ag);
+struct auth_group *auth_group_find(const struct conf *conf,
+ const char *name);
+int auth_group_set_type(struct auth_group *ag,
+ const char *type);
+
+const struct auth *auth_new_chap(struct auth_group *ag,
+ const char *user, const char *secret);
+const struct auth *auth_new_chap_mutual(struct auth_group *ag,
+ const char *user, const char *secret,
+ const char *user2, const char *secret2);
+const struct auth *auth_find(const struct auth_group *ag,
+ const char *user);
+
+const struct auth_name *auth_name_new(struct auth_group *ag,
+ const char *initiator_name);
+bool auth_name_defined(const struct auth_group *ag);
+const struct auth_name *auth_name_find(const struct auth_group *ag,
+ const char *initiator_name);
+int auth_name_check(const struct auth_group *ag,
+ const char *initiator_name);
+
+const struct auth_portal *auth_portal_new(struct auth_group *ag,
+ const char *initiator_portal);
+bool auth_portal_defined(const struct auth_group *ag);
+const struct auth_portal *auth_portal_find(const struct auth_group *ag,
+ const struct sockaddr_storage *sa);
+int auth_portal_check(const struct auth_group *ag,
+ const struct sockaddr_storage *sa);
+
+struct portal_group *portal_group_new(struct conf *conf, const char *name);
+void portal_group_delete(struct portal_group *pg);
+struct portal_group *portal_group_find(const struct conf *conf,
+ const char *name);
+int portal_group_add_listen(struct portal_group *pg,
+ const char *listen, bool iser);
+int portal_group_set_filter(struct portal_group *pg,
+ const char *filter);
+int portal_group_set_offload(struct portal_group *pg,
+ const char *offload);
+int portal_group_set_redirection(struct portal_group *pg,
+ const char *addr);
+
+int isns_new(struct conf *conf, const char *addr);
+void isns_delete(struct isns *is);
+void isns_register(struct isns *isns, struct isns *oldisns);
+void isns_check(struct isns *isns);
+void isns_deregister(struct isns *isns);
+
+struct pport *pport_new(struct conf *conf, const char *name,
+ uint32_t ctl_port);
+struct pport *pport_find(const struct conf *conf, const char *name);
+struct pport *pport_copy(struct pport *pport, struct conf *conf);
+void pport_delete(struct pport *pport);
+
+struct port *port_new(struct conf *conf, struct target *target,
+ struct portal_group *pg);
+struct port *port_new_pp(struct conf *conf, struct target *target,
+ struct pport *pp);
+struct port *port_find(const struct conf *conf, const char *name);
+struct port *port_find_in_pg(const struct portal_group *pg,
+ const char *target);
+void port_delete(struct port *port);
+
+struct target *target_new(struct conf *conf, const char *name);
+void target_delete(struct target *target);
+struct target *target_find(struct conf *conf,
+ const char *name);
+int target_set_redirection(struct target *target,
+ const char *addr);
+
+struct lun *lun_new(struct conf *conf, const char *name);
+void lun_delete(struct lun *lun);
+struct lun *lun_find(const struct conf *conf, const char *name);
+void lun_set_backend(struct lun *lun, const char *value);
+void lun_set_device_type(struct lun *lun, uint8_t value);
+void lun_set_blocksize(struct lun *lun, size_t value);
+void lun_set_device_id(struct lun *lun, const char *value);
+void lun_set_path(struct lun *lun, const char *value);
+void lun_set_scsiname(struct lun *lun, const char *value);
+void lun_set_serial(struct lun *lun, const char *value);
+void lun_set_size(struct lun *lun, size_t value);
+void lun_set_ctl_lun(struct lun *lun, uint32_t value);
+
+struct option *option_new(struct options *os,
+ const char *name, const char *value);
+void option_delete(struct options *os, struct option *co);
+struct option *option_find(const struct options *os, const char *name);
+void option_set(struct option *o, const char *value);
+
+void kernel_init(void);
+int kernel_lun_add(struct lun *lun);
+int kernel_lun_modify(struct lun *lun);
+int kernel_lun_remove(struct lun *lun);
+void kernel_handoff(struct connection *conn);
+void kernel_limits(const char *offload,
+ size_t *max_data_segment_length);
+int kernel_port_add(struct port *port);
+int kernel_port_update(struct port *port, struct port *old);
+int kernel_port_remove(struct port *port);
+void kernel_capsicate(void);
+
+#ifdef ICL_KERNEL_PROXY
+void kernel_listen(struct addrinfo *ai, bool iser,
+ int portal_id);
+void kernel_accept(int *connection_id, int *portal_id,
+ struct sockaddr *client_sa,
+ socklen_t *client_salen);
+void kernel_send(struct pdu *pdu);
+void kernel_receive(struct pdu *pdu);
+#endif
+
+struct keys *keys_new(void);
+void keys_delete(struct keys *keys);
+void keys_load(struct keys *keys, const struct pdu *pdu);
+void keys_save(struct keys *keys, struct pdu *pdu);
+const char *keys_find(struct keys *keys, const char *name);
+void keys_add(struct keys *keys,
+ const char *name, const char *value);
+void keys_add_int(struct keys *keys,
+ const char *name, int value);
+
+struct pdu *pdu_new(struct connection *conn);
+struct pdu *pdu_new_response(struct pdu *request);
+void pdu_delete(struct pdu *pdu);
+void pdu_receive(struct pdu *request);
+void pdu_send(struct pdu *response);
+
+void login(struct connection *conn);
+
+void discovery(struct connection *conn);
+
+void log_init(int level);
+void log_set_peer_name(const char *name);
+void log_set_peer_addr(const char *addr);
+void log_err(int, const char *, ...)
+ __dead2 __printflike(2, 3);
+void log_errx(int, const char *, ...)
+ __dead2 __printflike(2, 3);
+void log_warn(const char *, ...) __printflike(1, 2);
+void log_warnx(const char *, ...) __printflike(1, 2);
+void log_debugx(const char *, ...) __printflike(1, 2);
+
+char *checked_strdup(const char *);
+bool valid_iscsi_name(const char *name);
+void set_timeout(int timeout, int fatal);
+bool timed_out(void);
+
+#endif /* !CTLD_H */
diff --git a/usr.sbin/ctld/discovery.c b/usr.sbin/ctld/discovery.c
new file mode 100644
index 0000000..d7d843e
--- /dev/null
+++ b/usr.sbin/ctld/discovery.c
@@ -0,0 +1,336 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <sys/socket.h>
+
+#include "ctld.h"
+#include "iscsi_proto.h"
+
+static struct pdu *
+text_receive(struct connection *conn)
+{
+ struct pdu *request;
+ struct iscsi_bhs_text_request *bhstr;
+
+ request = pdu_new(conn);
+ pdu_receive(request);
+ if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) !=
+ ISCSI_BHS_OPCODE_TEXT_REQUEST)
+ log_errx(1, "protocol error: received invalid opcode 0x%x",
+ request->pdu_bhs->bhs_opcode);
+ bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
+#if 0
+ if ((bhstr->bhstr_flags & ISCSI_BHSTR_FLAGS_FINAL) == 0)
+ log_errx(1, "received Text PDU without the \"F\" flag");
+#endif
+ /*
+ * XXX: Implement the C flag some day.
+ */
+ if ((bhstr->bhstr_flags & BHSTR_FLAGS_CONTINUE) != 0)
+ log_errx(1, "received Text PDU with unsupported \"C\" flag");
+ if (ISCSI_SNLT(ntohl(bhstr->bhstr_cmdsn), conn->conn_cmdsn)) {
+ log_errx(1, "received Text PDU with decreasing CmdSN: "
+ "was %u, is %u", conn->conn_cmdsn, ntohl(bhstr->bhstr_cmdsn));
+ }
+ if (ntohl(bhstr->bhstr_expstatsn) != conn->conn_statsn) {
+ log_errx(1, "received Text PDU with wrong StatSN: "
+ "is %u, should be %u", ntohl(bhstr->bhstr_expstatsn),
+ conn->conn_statsn);
+ }
+ conn->conn_cmdsn = ntohl(bhstr->bhstr_cmdsn);
+ if ((bhstr->bhstr_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0)
+ conn->conn_cmdsn++;
+
+ return (request);
+}
+
+static struct pdu *
+text_new_response(struct pdu *request)
+{
+ struct pdu *response;
+ struct connection *conn;
+ struct iscsi_bhs_text_request *bhstr;
+ struct iscsi_bhs_text_response *bhstr2;
+
+ bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
+ conn = request->pdu_connection;
+
+ response = pdu_new_response(request);
+ bhstr2 = (struct iscsi_bhs_text_response *)response->pdu_bhs;
+ bhstr2->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_RESPONSE;
+ bhstr2->bhstr_flags = BHSTR_FLAGS_FINAL;
+ bhstr2->bhstr_lun = bhstr->bhstr_lun;
+ bhstr2->bhstr_initiator_task_tag = bhstr->bhstr_initiator_task_tag;
+ bhstr2->bhstr_target_transfer_tag = bhstr->bhstr_target_transfer_tag;
+ bhstr2->bhstr_statsn = htonl(conn->conn_statsn++);
+ bhstr2->bhstr_expcmdsn = htonl(conn->conn_cmdsn);
+ bhstr2->bhstr_maxcmdsn = htonl(conn->conn_cmdsn);
+
+ return (response);
+}
+
+static struct pdu *
+logout_receive(struct connection *conn)
+{
+ struct pdu *request;
+ struct iscsi_bhs_logout_request *bhslr;
+
+ request = pdu_new(conn);
+ pdu_receive(request);
+ if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) !=
+ ISCSI_BHS_OPCODE_LOGOUT_REQUEST)
+ log_errx(1, "protocol error: received invalid opcode 0x%x",
+ request->pdu_bhs->bhs_opcode);
+ bhslr = (struct iscsi_bhs_logout_request *)request->pdu_bhs;
+ if ((bhslr->bhslr_reason & 0x7f) != BHSLR_REASON_CLOSE_SESSION)
+ log_debugx("received Logout PDU with invalid reason 0x%x; "
+ "continuing anyway", bhslr->bhslr_reason & 0x7f);
+ if (ISCSI_SNLT(ntohl(bhslr->bhslr_cmdsn), conn->conn_cmdsn)) {
+ log_errx(1, "received Logout PDU with decreasing CmdSN: "
+ "was %u, is %u", conn->conn_cmdsn,
+ ntohl(bhslr->bhslr_cmdsn));
+ }
+ if (ntohl(bhslr->bhslr_expstatsn) != conn->conn_statsn) {
+ log_errx(1, "received Logout PDU with wrong StatSN: "
+ "is %u, should be %u", ntohl(bhslr->bhslr_expstatsn),
+ conn->conn_statsn);
+ }
+ conn->conn_cmdsn = ntohl(bhslr->bhslr_cmdsn);
+ if ((bhslr->bhslr_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0)
+ conn->conn_cmdsn++;
+
+ return (request);
+}
+
+static struct pdu *
+logout_new_response(struct pdu *request)
+{
+ struct pdu *response;
+ struct connection *conn;
+ struct iscsi_bhs_logout_request *bhslr;
+ struct iscsi_bhs_logout_response *bhslr2;
+
+ bhslr = (struct iscsi_bhs_logout_request *)request->pdu_bhs;
+ conn = request->pdu_connection;
+
+ response = pdu_new_response(request);
+ bhslr2 = (struct iscsi_bhs_logout_response *)response->pdu_bhs;
+ bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_RESPONSE;
+ bhslr2->bhslr_flags = 0x80;
+ bhslr2->bhslr_response = BHSLR_RESPONSE_CLOSED_SUCCESSFULLY;
+ bhslr2->bhslr_initiator_task_tag = bhslr->bhslr_initiator_task_tag;
+ bhslr2->bhslr_statsn = htonl(conn->conn_statsn++);
+ bhslr2->bhslr_expcmdsn = htonl(conn->conn_cmdsn);
+ bhslr2->bhslr_maxcmdsn = htonl(conn->conn_cmdsn);
+
+ return (response);
+}
+
+static void
+discovery_add_target(struct keys *response_keys, const struct target *targ)
+{
+ struct port *port;
+ struct portal *portal;
+ char *buf;
+ char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
+ struct addrinfo *ai;
+ int ret;
+
+ keys_add(response_keys, "TargetName", targ->t_name);
+ TAILQ_FOREACH(port, &targ->t_ports, p_ts) {
+ if (port->p_portal_group == NULL)
+ continue;
+ TAILQ_FOREACH(portal, &port->p_portal_group->pg_portals, p_next) {
+ ai = portal->p_ai;
+ ret = getnameinfo(ai->ai_addr, ai->ai_addrlen,
+ hbuf, sizeof(hbuf), sbuf, sizeof(sbuf),
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ if (ret != 0) {
+ log_warnx("getnameinfo: %s", gai_strerror(ret));
+ continue;
+ }
+ switch (ai->ai_addr->sa_family) {
+ case AF_INET:
+ if (strcmp(hbuf, "0.0.0.0") == 0)
+ continue;
+ ret = asprintf(&buf, "%s:%s,%d", hbuf, sbuf,
+ port->p_portal_group->pg_tag);
+ break;
+ case AF_INET6:
+ if (strcmp(hbuf, "::") == 0)
+ continue;
+ ret = asprintf(&buf, "[%s]:%s,%d", hbuf, sbuf,
+ port->p_portal_group->pg_tag);
+ break;
+ default:
+ continue;
+ }
+ if (ret <= 0)
+ log_err(1, "asprintf");
+ keys_add(response_keys, "TargetAddress", buf);
+ free(buf);
+ }
+ }
+}
+
+static bool
+discovery_target_filtered_out(const struct connection *conn,
+ const struct port *port)
+{
+ const struct auth_group *ag;
+ const struct portal_group *pg;
+ const struct target *targ;
+ const struct auth *auth;
+ int error;
+
+ targ = port->p_target;
+ ag = port->p_auth_group;
+ if (ag == NULL)
+ ag = targ->t_auth_group;
+ pg = conn->conn_portal->p_portal_group;
+
+ assert(pg->pg_discovery_auth_group != PG_FILTER_UNKNOWN);
+
+ if (pg->pg_discovery_filter >= PG_FILTER_PORTAL &&
+ auth_portal_check(ag, &conn->conn_initiator_sa) != 0) {
+ log_debugx("initiator does not match initiator portals "
+ "allowed for target \"%s\"; skipping", targ->t_name);
+ return (true);
+ }
+
+ if (pg->pg_discovery_filter >= PG_FILTER_PORTAL_NAME &&
+ auth_name_check(ag, conn->conn_initiator_name) != 0) {
+ log_debugx("initiator does not match initiator names "
+ "allowed for target \"%s\"; skipping", targ->t_name);
+ return (true);
+ }
+
+ if (pg->pg_discovery_filter >= PG_FILTER_PORTAL_NAME_AUTH &&
+ ag->ag_type != AG_TYPE_NO_AUTHENTICATION) {
+ if (conn->conn_chap == NULL) {
+ assert(pg->pg_discovery_auth_group->ag_type ==
+ AG_TYPE_NO_AUTHENTICATION);
+
+ log_debugx("initiator didn't authenticate, but target "
+ "\"%s\" requires CHAP; skipping", targ->t_name);
+ return (true);
+ }
+
+ assert(conn->conn_user != NULL);
+ auth = auth_find(ag, conn->conn_user);
+ if (auth == NULL) {
+ log_debugx("CHAP user \"%s\" doesn't match target "
+ "\"%s\"; skipping", conn->conn_user, targ->t_name);
+ return (true);
+ }
+
+ error = chap_authenticate(conn->conn_chap, auth->a_secret);
+ if (error != 0) {
+ log_debugx("password for CHAP user \"%s\" doesn't "
+ "match target \"%s\"; skipping",
+ conn->conn_user, targ->t_name);
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+void
+discovery(struct connection *conn)
+{
+ struct pdu *request, *response;
+ struct keys *request_keys, *response_keys;
+ const struct port *port;
+ const struct portal_group *pg;
+ const char *send_targets;
+
+ pg = conn->conn_portal->p_portal_group;
+
+ log_debugx("beginning discovery session; waiting for Text PDU");
+ request = text_receive(conn);
+ request_keys = keys_new();
+ keys_load(request_keys, request);
+
+ send_targets = keys_find(request_keys, "SendTargets");
+ if (send_targets == NULL)
+ log_errx(1, "received Text PDU without SendTargets");
+
+ response = text_new_response(request);
+ response_keys = keys_new();
+
+ if (strcmp(send_targets, "All") == 0) {
+ TAILQ_FOREACH(port, &pg->pg_ports, p_pgs) {
+ if (discovery_target_filtered_out(conn, port)) {
+ /* Ignore this target. */
+ continue;
+ }
+ discovery_add_target(response_keys, port->p_target);
+ }
+ } else {
+ port = port_find_in_pg(pg, send_targets);
+ if (port == NULL) {
+ log_debugx("initiator requested information on unknown "
+ "target \"%s\"; returning nothing", send_targets);
+ } else {
+ if (discovery_target_filtered_out(conn, port)) {
+ /* Ignore this target. */
+ } else {
+ discovery_add_target(response_keys, port->p_target);
+ }
+ }
+ }
+ keys_save(response_keys, response);
+
+ pdu_send(response);
+ pdu_delete(response);
+ keys_delete(response_keys);
+ pdu_delete(request);
+ keys_delete(request_keys);
+
+ log_debugx("done sending targets; waiting for Logout PDU");
+ request = logout_receive(conn);
+ response = logout_new_response(request);
+
+ pdu_send(response);
+ pdu_delete(response);
+ pdu_delete(request);
+
+ log_debugx("discovery session done");
+}
diff --git a/usr.sbin/ctld/isns.c b/usr.sbin/ctld/isns.c
new file mode 100644
index 0000000..f7381a1
--- /dev/null
+++ b/usr.sbin/ctld/isns.c
@@ -0,0 +1,252 @@
+/*-
+ * Copyright (c) 2014 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 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/time.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/endian.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ctld.h"
+#include "isns.h"
+
+struct isns_req *
+isns_req_alloc(void)
+{
+ struct isns_req *req;
+
+ req = calloc(sizeof(struct isns_req), 1);
+ if (req == NULL) {
+ log_err(1, "calloc");
+ return (NULL);
+ }
+ req->ir_buflen = sizeof(struct isns_hdr);
+ req->ir_usedlen = 0;
+ req->ir_buf = calloc(req->ir_buflen, 1);
+ if (req->ir_buf == NULL) {
+ free(req);
+ log_err(1, "calloc");
+ return (NULL);
+ }
+ return (req);
+}
+
+struct isns_req *
+isns_req_create(uint16_t func, uint16_t flags)
+{
+ struct isns_req *req;
+ struct isns_hdr *hdr;
+
+ req = isns_req_alloc();
+ req->ir_usedlen = sizeof(struct isns_hdr);
+ hdr = (struct isns_hdr *)req->ir_buf;
+ be16enc(hdr->ih_version, ISNS_VERSION);
+ be16enc(hdr->ih_function, func);
+ be16enc(hdr->ih_flags, flags);
+ return (req);
+}
+
+void
+isns_req_free(struct isns_req *req)
+{
+
+ free(req->ir_buf);
+ free(req);
+}
+
+static int
+isns_req_getspace(struct isns_req *req, uint32_t len)
+{
+ void *newbuf;
+ int newlen;
+
+ if (req->ir_usedlen + len <= req->ir_buflen)
+ return (0);
+ newlen = 1 << flsl(req->ir_usedlen + len);
+ newbuf = realloc(req->ir_buf, newlen);
+ if (newbuf == NULL) {
+ log_err(1, "realloc");
+ return (1);
+ }
+ req->ir_buf = newbuf;
+ req->ir_buflen = newlen;
+ return (0);
+}
+
+void
+isns_req_add(struct isns_req *req, uint32_t tag, uint32_t len,
+ const void *value)
+{
+ struct isns_tlv *tlv;
+ uint32_t vlen;
+
+ vlen = len + ((len & 3) ? (4 - (len & 3)) : 0);
+ isns_req_getspace(req, sizeof(*tlv) + vlen);
+ tlv = (struct isns_tlv *)&req->ir_buf[req->ir_usedlen];
+ be32enc(tlv->it_tag, tag);
+ be32enc(tlv->it_length, vlen);
+ memcpy(tlv->it_value, value, len);
+ if (vlen != len)
+ memset(&tlv->it_value[len], 0, vlen - len);
+ req->ir_usedlen += sizeof(*tlv) + vlen;
+}
+
+void
+isns_req_add_delim(struct isns_req *req)
+{
+
+ isns_req_add(req, 0, 0, NULL);
+}
+
+void
+isns_req_add_str(struct isns_req *req, uint32_t tag, const char *value)
+{
+
+ isns_req_add(req, tag, strlen(value) + 1, value);
+}
+
+void
+isns_req_add_32(struct isns_req *req, uint32_t tag, uint32_t value)
+{
+ uint32_t beval;
+
+ be32enc(&beval, value);
+ isns_req_add(req, tag, sizeof(value), &beval);
+}
+
+void
+isns_req_add_addr(struct isns_req *req, uint32_t tag, struct addrinfo *ai)
+{
+ struct sockaddr_in *in4;
+ struct sockaddr_in6 *in6;
+ uint8_t buf[16];
+
+ switch (ai->ai_addr->sa_family) {
+ case AF_INET:
+ in4 = (struct sockaddr_in *)(void *)ai->ai_addr;
+ memset(buf, 0, 10);
+ buf[10] = 0xff;
+ buf[11] = 0xff;
+ memcpy(&buf[12], &in4->sin_addr, sizeof(in4->sin_addr));
+ isns_req_add(req, tag, sizeof(buf), buf);
+ break;
+ case AF_INET6:
+ in6 = (struct sockaddr_in6 *)(void *)ai->ai_addr;
+ isns_req_add(req, tag, sizeof(in6->sin6_addr), &in6->sin6_addr);
+ break;
+ default:
+ log_errx(1, "Unsupported address family %d",
+ ai->ai_addr->sa_family);
+ }
+}
+
+void
+isns_req_add_port(struct isns_req *req, uint32_t tag, struct addrinfo *ai)
+{
+ struct sockaddr_in *in4;
+ struct sockaddr_in6 *in6;
+ uint32_t buf;
+
+ switch (ai->ai_addr->sa_family) {
+ case AF_INET:
+ in4 = (struct sockaddr_in *)(void *)ai->ai_addr;
+ be32enc(&buf, ntohs(in4->sin_port));
+ isns_req_add(req, tag, sizeof(buf), &buf);
+ break;
+ case AF_INET6:
+ in6 = (struct sockaddr_in6 *)(void *)ai->ai_addr;
+ be32enc(&buf, ntohs(in6->sin6_port));
+ isns_req_add(req, tag, sizeof(buf), &buf);
+ break;
+ default:
+ log_errx(1, "Unsupported address family %d",
+ ai->ai_addr->sa_family);
+ }
+}
+
+int
+isns_req_send(int s, struct isns_req *req)
+{
+ struct isns_hdr *hdr;
+ int res;
+
+ hdr = (struct isns_hdr *)req->ir_buf;
+ be16enc(hdr->ih_length, req->ir_usedlen - sizeof(*hdr));
+ be16enc(hdr->ih_flags, be16dec(hdr->ih_flags) |
+ ISNS_FLAG_LAST | ISNS_FLAG_FIRST);
+ be16enc(hdr->ih_transaction, 0);
+ be16enc(hdr->ih_sequence, 0);
+
+ res = write(s, req->ir_buf, req->ir_usedlen);
+ return ((res < 0) ? -1 : 0);
+}
+
+int
+isns_req_receive(int s, struct isns_req *req)
+{
+ struct isns_hdr *hdr;
+ ssize_t res, len;
+
+ req->ir_usedlen = 0;
+ isns_req_getspace(req, sizeof(*hdr));
+ res = read(s, req->ir_buf, sizeof(*hdr));
+ if (res < (ssize_t)sizeof(*hdr))
+ return (-1);
+ req->ir_usedlen = sizeof(*hdr);
+ hdr = (struct isns_hdr *)req->ir_buf;
+ if (be16dec(hdr->ih_version) != ISNS_VERSION)
+ return (-1);
+ if ((be16dec(hdr->ih_flags) & (ISNS_FLAG_LAST | ISNS_FLAG_FIRST)) !=
+ (ISNS_FLAG_LAST | ISNS_FLAG_FIRST))
+ return (-1);
+ len = be16dec(hdr->ih_length);
+ isns_req_getspace(req, len);
+ res = read(s, &req->ir_buf[req->ir_usedlen], len);
+ if (res < len)
+ return (-1);
+ req->ir_usedlen += len;
+ return (0);
+}
+
+uint32_t
+isns_req_get_status(struct isns_req *req)
+{
+
+ if (req->ir_usedlen < sizeof(struct isns_hdr) + 4)
+ return (-1);
+ return (be32dec(&req->ir_buf[sizeof(struct isns_hdr)]));
+}
diff --git a/usr.sbin/ctld/isns.h b/usr.sbin/ctld/isns.h
new file mode 100644
index 0000000..00e6b50
--- /dev/null
+++ b/usr.sbin/ctld/isns.h
@@ -0,0 +1,92 @@
+/*-
+ * Copyright (c) 2014 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 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 _ISNS_H
+#define _ISNS_H
+
+#define ISNS_VERSION 0x0001
+
+#define ISNS_FUNC_DEVATTRREG 0x0001
+#define ISNS_FUNC_DEVATTRQRY 0x0002
+#define ISNS_FUNC_DEVGETNEXT 0x0003
+#define ISNS_FUNC_DEVDEREG 0x0004
+#define ISNS_FUNC_SCNREG 0x0005
+#define ISNS_FUNC_SCNDEREG 0x0006
+#define ISNS_FUNC_SCNEVENT 0x0007
+#define ISNS_FUNC_SCN 0x0008
+#define ISNS_FUNC_DDREG 0x0009
+#define ISNS_FUNC_DDDEREG 0x000a
+#define ISNS_FUNC_DDSREG 0x000b
+#define ISNS_FUNC_DDSDEREG 0x000c
+#define ISNS_FUNC_ESI 0x000d
+#define ISNS_FUNC_HEARTBEAT 0x000e
+#define ISNS_FUNC_RESPONSE 0x8000
+
+#define ISNS_FLAG_CLIENT 0x8000
+#define ISNS_FLAG_SERVER 0x4000
+#define ISNS_FLAG_AUTH 0x2000
+#define ISNS_FLAG_REPLACE 0x1000
+#define ISNS_FLAG_LAST 0x0800
+#define ISNS_FLAG_FIRST 0x0400
+
+struct isns_hdr {
+ uint8_t ih_version[2];
+ uint8_t ih_function[2];
+ uint8_t ih_length[2];
+ uint8_t ih_flags[2];
+ uint8_t ih_transaction[2];
+ uint8_t ih_sequence[2];
+};
+
+struct isns_tlv {
+ uint8_t it_tag[4];
+ uint8_t it_length[4];
+ uint8_t it_value[];
+};
+
+struct isns_req {
+ u_int ir_buflen;
+ u_int ir_usedlen;
+ uint8_t *ir_buf;
+};
+
+struct isns_req * isns_req_alloc(void);
+struct isns_req * isns_req_create(uint16_t func, uint16_t flags);
+void isns_req_free(struct isns_req *req);
+void isns_req_add(struct isns_req *req, uint32_t tag, uint32_t len,
+ const void *value);
+void isns_req_add_delim(struct isns_req *req);
+void isns_req_add_str(struct isns_req *req, uint32_t tag, const char *value);
+void isns_req_add_32(struct isns_req *req, uint32_t tag, uint32_t value);
+void isns_req_add_addr(struct isns_req *req, uint32_t tag, struct addrinfo *ai);
+void isns_req_add_port(struct isns_req *req, uint32_t tag, struct addrinfo *ai);
+int isns_req_send(int s, struct isns_req *req);
+int isns_req_receive(int s, struct isns_req *req);
+uint32_t isns_req_get_status(struct isns_req *req);
+
+#endif /* _ISNS_H */
diff --git a/usr.sbin/ctld/kernel.c b/usr.sbin/ctld/kernel.c
new file mode 100644
index 0000000..8f1cefb
--- /dev/null
+++ b/usr.sbin/ctld/kernel.c
@@ -0,0 +1,1276 @@
+/*-
+ * Copyright (c) 2003, 2004 Silicon Graphics International Corp.
+ * Copyright (c) 1997-2007 Kenneth D. Merry
+ * 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,
+ * 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/queue.h>
+#include <sys/callout.h>
+#include <sys/sbuf.h>
+#include <sys/capsicum.h>
+#include <assert.h>
+#include <bsdxml.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <cam/scsi/scsi_all.h>
+#include <cam/scsi/scsi_message.h>
+#include <cam/ctl/ctl.h>
+#include <cam/ctl/ctl_io.h>
+#include <cam/ctl/ctl_backend.h>
+#include <cam/ctl/ctl_ioctl.h>
+#include <cam/ctl/ctl_util.h>
+#include <cam/ctl/ctl_scsi_all.h>
+
+#include "ctld.h"
+
+#ifdef ICL_KERNEL_PROXY
+#include <netdb.h>
+#endif
+
+extern bool proxy_mode;
+
+static int ctl_fd = 0;
+
+void
+kernel_init(void)
+{
+ int retval, saved_errno;
+
+ ctl_fd = open(CTL_DEFAULT_DEV, O_RDWR);
+ if (ctl_fd < 0 && errno == ENOENT) {
+ saved_errno = errno;
+ retval = kldload("ctl");
+ if (retval != -1)
+ ctl_fd = open(CTL_DEFAULT_DEV, O_RDWR);
+ else
+ errno = saved_errno;
+ }
+ if (ctl_fd < 0)
+ log_err(1, "failed to open %s", CTL_DEFAULT_DEV);
+}
+
+/*
+ * Name/value pair used for per-LUN attributes.
+ */
+struct cctl_lun_nv {
+ char *name;
+ char *value;
+ STAILQ_ENTRY(cctl_lun_nv) links;
+};
+
+/*
+ * Backend LUN information.
+ */
+struct cctl_lun {
+ uint64_t lun_id;
+ char *backend_type;
+ uint8_t device_type;
+ uint64_t size_blocks;
+ uint32_t blocksize;
+ char *serial_number;
+ char *device_id;
+ char *ctld_name;
+ STAILQ_HEAD(,cctl_lun_nv) attr_list;
+ STAILQ_ENTRY(cctl_lun) links;
+};
+
+struct cctl_port {
+ uint32_t port_id;
+ char *port_frontend;
+ char *port_name;
+ int pp;
+ int vp;
+ int cfiscsi_state;
+ char *cfiscsi_target;
+ uint16_t cfiscsi_portal_group_tag;
+ char *ctld_portal_group_name;
+ STAILQ_HEAD(,cctl_lun_nv) attr_list;
+ STAILQ_ENTRY(cctl_port) links;
+};
+
+struct cctl_devlist_data {
+ int num_luns;
+ STAILQ_HEAD(,cctl_lun) lun_list;
+ struct cctl_lun *cur_lun;
+ int num_ports;
+ STAILQ_HEAD(,cctl_port) port_list;
+ struct cctl_port *cur_port;
+ int level;
+ struct sbuf *cur_sb[32];
+};
+
+static void
+cctl_start_element(void *user_data, const char *name, const char **attr)
+{
+ int i;
+ struct cctl_devlist_data *devlist;
+ struct cctl_lun *cur_lun;
+
+ devlist = (struct cctl_devlist_data *)user_data;
+ cur_lun = devlist->cur_lun;
+ devlist->level++;
+ if ((u_int)devlist->level >= (sizeof(devlist->cur_sb) /
+ sizeof(devlist->cur_sb[0])))
+ log_errx(1, "%s: too many nesting levels, %zd max", __func__,
+ sizeof(devlist->cur_sb) / sizeof(devlist->cur_sb[0]));
+
+ devlist->cur_sb[devlist->level] = sbuf_new_auto();
+ if (devlist->cur_sb[devlist->level] == NULL)
+ log_err(1, "%s: unable to allocate sbuf", __func__);
+
+ if (strcmp(name, "lun") == 0) {
+ if (cur_lun != NULL)
+ log_errx(1, "%s: improper lun element nesting",
+ __func__);
+
+ cur_lun = calloc(1, sizeof(*cur_lun));
+ if (cur_lun == NULL)
+ log_err(1, "%s: cannot allocate %zd bytes", __func__,
+ sizeof(*cur_lun));
+
+ devlist->num_luns++;
+ devlist->cur_lun = cur_lun;
+
+ STAILQ_INIT(&cur_lun->attr_list);
+ STAILQ_INSERT_TAIL(&devlist->lun_list, cur_lun, links);
+
+ for (i = 0; attr[i] != NULL; i += 2) {
+ if (strcmp(attr[i], "id") == 0) {
+ cur_lun->lun_id = strtoull(attr[i+1], NULL, 0);
+ } else {
+ log_errx(1, "%s: invalid LUN attribute %s = %s",
+ __func__, attr[i], attr[i+1]);
+ }
+ }
+ }
+}
+
+static void
+cctl_end_element(void *user_data, const char *name)
+{
+ struct cctl_devlist_data *devlist;
+ struct cctl_lun *cur_lun;
+ char *str;
+
+ devlist = (struct cctl_devlist_data *)user_data;
+ cur_lun = devlist->cur_lun;
+
+ if ((cur_lun == NULL)
+ && (strcmp(name, "ctllunlist") != 0))
+ log_errx(1, "%s: cur_lun == NULL! (name = %s)", __func__, name);
+
+ if (devlist->cur_sb[devlist->level] == NULL)
+ log_errx(1, "%s: no valid sbuf at level %d (name %s)", __func__,
+ devlist->level, name);
+
+ sbuf_finish(devlist->cur_sb[devlist->level]);
+ str = checked_strdup(sbuf_data(devlist->cur_sb[devlist->level]));
+
+ if (strlen(str) == 0) {
+ free(str);
+ str = NULL;
+ }
+
+ sbuf_delete(devlist->cur_sb[devlist->level]);
+ devlist->cur_sb[devlist->level] = NULL;
+ devlist->level--;
+
+ if (strcmp(name, "backend_type") == 0) {
+ cur_lun->backend_type = str;
+ str = NULL;
+ } else if (strcmp(name, "lun_type") == 0) {
+ cur_lun->device_type = strtoull(str, NULL, 0);
+ } else if (strcmp(name, "size") == 0) {
+ cur_lun->size_blocks = strtoull(str, NULL, 0);
+ } else if (strcmp(name, "blocksize") == 0) {
+ cur_lun->blocksize = strtoul(str, NULL, 0);
+ } else if (strcmp(name, "serial_number") == 0) {
+ cur_lun->serial_number = str;
+ str = NULL;
+ } else if (strcmp(name, "device_id") == 0) {
+ cur_lun->device_id = str;
+ str = NULL;
+ } else if (strcmp(name, "ctld_name") == 0) {
+ cur_lun->ctld_name = str;
+ str = NULL;
+ } else if (strcmp(name, "lun") == 0) {
+ devlist->cur_lun = NULL;
+ } else if (strcmp(name, "ctllunlist") == 0) {
+ /* Nothing. */
+ } else {
+ struct cctl_lun_nv *nv;
+
+ nv = calloc(1, sizeof(*nv));
+ if (nv == NULL)
+ log_err(1, "%s: can't allocate %zd bytes for nv pair",
+ __func__, sizeof(*nv));
+
+ nv->name = checked_strdup(name);
+
+ nv->value = str;
+ str = NULL;
+ STAILQ_INSERT_TAIL(&cur_lun->attr_list, nv, links);
+ }
+
+ free(str);
+}
+
+static void
+cctl_start_pelement(void *user_data, const char *name, const char **attr)
+{
+ int i;
+ struct cctl_devlist_data *devlist;
+ struct cctl_port *cur_port;
+
+ devlist = (struct cctl_devlist_data *)user_data;
+ cur_port = devlist->cur_port;
+ devlist->level++;
+ if ((u_int)devlist->level >= (sizeof(devlist->cur_sb) /
+ sizeof(devlist->cur_sb[0])))
+ log_errx(1, "%s: too many nesting levels, %zd max", __func__,
+ sizeof(devlist->cur_sb) / sizeof(devlist->cur_sb[0]));
+
+ devlist->cur_sb[devlist->level] = sbuf_new_auto();
+ if (devlist->cur_sb[devlist->level] == NULL)
+ log_err(1, "%s: unable to allocate sbuf", __func__);
+
+ if (strcmp(name, "targ_port") == 0) {
+ if (cur_port != NULL)
+ log_errx(1, "%s: improper port element nesting (%s)",
+ __func__, name);
+
+ cur_port = calloc(1, sizeof(*cur_port));
+ if (cur_port == NULL)
+ log_err(1, "%s: cannot allocate %zd bytes", __func__,
+ sizeof(*cur_port));
+
+ devlist->num_ports++;
+ devlist->cur_port = cur_port;
+
+ STAILQ_INIT(&cur_port->attr_list);
+ STAILQ_INSERT_TAIL(&devlist->port_list, cur_port, links);
+
+ for (i = 0; attr[i] != NULL; i += 2) {
+ if (strcmp(attr[i], "id") == 0) {
+ cur_port->port_id = strtoul(attr[i+1], NULL, 0);
+ } else {
+ log_errx(1, "%s: invalid LUN attribute %s = %s",
+ __func__, attr[i], attr[i+1]);
+ }
+ }
+ }
+}
+
+static void
+cctl_end_pelement(void *user_data, const char *name)
+{
+ struct cctl_devlist_data *devlist;
+ struct cctl_port *cur_port;
+ char *str;
+
+ devlist = (struct cctl_devlist_data *)user_data;
+ cur_port = devlist->cur_port;
+
+ if ((cur_port == NULL)
+ && (strcmp(name, "ctlportlist") != 0))
+ log_errx(1, "%s: cur_port == NULL! (name = %s)", __func__, name);
+
+ if (devlist->cur_sb[devlist->level] == NULL)
+ log_errx(1, "%s: no valid sbuf at level %d (name %s)", __func__,
+ devlist->level, name);
+
+ sbuf_finish(devlist->cur_sb[devlist->level]);
+ str = checked_strdup(sbuf_data(devlist->cur_sb[devlist->level]));
+
+ if (strlen(str) == 0) {
+ free(str);
+ str = NULL;
+ }
+
+ sbuf_delete(devlist->cur_sb[devlist->level]);
+ devlist->cur_sb[devlist->level] = NULL;
+ devlist->level--;
+
+ if (strcmp(name, "frontend_type") == 0) {
+ cur_port->port_frontend = str;
+ str = NULL;
+ } else if (strcmp(name, "port_name") == 0) {
+ cur_port->port_name = str;
+ str = NULL;
+ } else if (strcmp(name, "physical_port") == 0) {
+ cur_port->pp = strtoul(str, NULL, 0);
+ } else if (strcmp(name, "virtual_port") == 0) {
+ cur_port->vp = strtoul(str, NULL, 0);
+ } else if (strcmp(name, "cfiscsi_target") == 0) {
+ cur_port->cfiscsi_target = str;
+ str = NULL;
+ } else if (strcmp(name, "cfiscsi_state") == 0) {
+ cur_port->cfiscsi_state = strtoul(str, NULL, 0);
+ } else if (strcmp(name, "cfiscsi_portal_group_tag") == 0) {
+ cur_port->cfiscsi_portal_group_tag = strtoul(str, NULL, 0);
+ } else if (strcmp(name, "ctld_portal_group_name") == 0) {
+ cur_port->ctld_portal_group_name = str;
+ str = NULL;
+ } else if (strcmp(name, "targ_port") == 0) {
+ devlist->cur_port = NULL;
+ } else if (strcmp(name, "ctlportlist") == 0) {
+ /* Nothing. */
+ } else {
+ struct cctl_lun_nv *nv;
+
+ nv = calloc(1, sizeof(*nv));
+ if (nv == NULL)
+ log_err(1, "%s: can't allocate %zd bytes for nv pair",
+ __func__, sizeof(*nv));
+
+ nv->name = checked_strdup(name);
+
+ nv->value = str;
+ str = NULL;
+ STAILQ_INSERT_TAIL(&cur_port->attr_list, nv, links);
+ }
+
+ free(str);
+}
+
+static void
+cctl_char_handler(void *user_data, const XML_Char *str, int len)
+{
+ struct cctl_devlist_data *devlist;
+
+ devlist = (struct cctl_devlist_data *)user_data;
+
+ sbuf_bcat(devlist->cur_sb[devlist->level], str, len);
+}
+
+struct conf *
+conf_new_from_kernel(void)
+{
+ struct conf *conf = NULL;
+ struct target *targ;
+ struct portal_group *pg;
+ struct pport *pp;
+ struct port *cp;
+ struct lun *cl;
+ struct option *o;
+ struct ctl_lun_list list;
+ struct cctl_devlist_data devlist;
+ struct cctl_lun *lun;
+ struct cctl_port *port;
+ XML_Parser parser;
+ char *str, *name;
+ int len, retval;
+
+ bzero(&devlist, sizeof(devlist));
+ STAILQ_INIT(&devlist.lun_list);
+ STAILQ_INIT(&devlist.port_list);
+
+ log_debugx("obtaining previously configured CTL luns from the kernel");
+
+ str = NULL;
+ len = 4096;
+retry:
+ str = realloc(str, len);
+ if (str == NULL)
+ log_err(1, "realloc");
+
+ bzero(&list, sizeof(list));
+ list.alloc_len = len;
+ list.status = CTL_LUN_LIST_NONE;
+ list.lun_xml = str;
+
+ if (ioctl(ctl_fd, CTL_LUN_LIST, &list) == -1) {
+ log_warn("error issuing CTL_LUN_LIST ioctl");
+ free(str);
+ return (NULL);
+ }
+
+ if (list.status == CTL_LUN_LIST_ERROR) {
+ log_warnx("error returned from CTL_LUN_LIST ioctl: %s",
+ list.error_str);
+ free(str);
+ return (NULL);
+ }
+
+ if (list.status == CTL_LUN_LIST_NEED_MORE_SPACE) {
+ len = len << 1;
+ goto retry;
+ }
+
+ parser = XML_ParserCreate(NULL);
+ if (parser == NULL) {
+ log_warnx("unable to create XML parser");
+ free(str);
+ return (NULL);
+ }
+
+ XML_SetUserData(parser, &devlist);
+ XML_SetElementHandler(parser, cctl_start_element, cctl_end_element);
+ XML_SetCharacterDataHandler(parser, cctl_char_handler);
+
+ retval = XML_Parse(parser, str, strlen(str), 1);
+ XML_ParserFree(parser);
+ free(str);
+ if (retval != 1) {
+ log_warnx("XML_Parse failed");
+ return (NULL);
+ }
+
+ str = NULL;
+ len = 4096;
+retry_port:
+ str = realloc(str, len);
+ if (str == NULL)
+ log_err(1, "realloc");
+
+ bzero(&list, sizeof(list));
+ list.alloc_len = len;
+ list.status = CTL_LUN_LIST_NONE;
+ list.lun_xml = str;
+
+ if (ioctl(ctl_fd, CTL_PORT_LIST, &list) == -1) {
+ log_warn("error issuing CTL_PORT_LIST ioctl");
+ free(str);
+ return (NULL);
+ }
+
+ if (list.status == CTL_LUN_LIST_ERROR) {
+ log_warnx("error returned from CTL_PORT_LIST ioctl: %s",
+ list.error_str);
+ free(str);
+ return (NULL);
+ }
+
+ if (list.status == CTL_LUN_LIST_NEED_MORE_SPACE) {
+ len = len << 1;
+ goto retry_port;
+ }
+
+ parser = XML_ParserCreate(NULL);
+ if (parser == NULL) {
+ log_warnx("unable to create XML parser");
+ free(str);
+ return (NULL);
+ }
+
+ XML_SetUserData(parser, &devlist);
+ XML_SetElementHandler(parser, cctl_start_pelement, cctl_end_pelement);
+ XML_SetCharacterDataHandler(parser, cctl_char_handler);
+
+ retval = XML_Parse(parser, str, strlen(str), 1);
+ XML_ParserFree(parser);
+ free(str);
+ if (retval != 1) {
+ log_warnx("XML_Parse failed");
+ return (NULL);
+ }
+
+ conf = conf_new();
+
+ name = NULL;
+ STAILQ_FOREACH(port, &devlist.port_list, links) {
+ if (strcmp(port->port_frontend, "ha") == 0)
+ continue;
+ free(name);
+ if (port->pp == 0 && port->vp == 0) {
+ name = checked_strdup(port->port_name);
+ } else if (port->vp == 0) {
+ retval = asprintf(&name, "%s/%d",
+ port->port_name, port->pp);
+ if (retval <= 0)
+ log_err(1, "asprintf");
+ } else {
+ retval = asprintf(&name, "%s/%d/%d",
+ port->port_name, port->pp, port->vp);
+ if (retval <= 0)
+ log_err(1, "asprintf");
+ }
+
+ if (port->cfiscsi_target == NULL) {
+ log_debugx("CTL port %u \"%s\" wasn't managed by ctld; ",
+ port->port_id, name);
+ pp = pport_find(conf, name);
+ if (pp == NULL) {
+#if 0
+ log_debugx("found new kernel port %u \"%s\"",
+ port->port_id, name);
+#endif
+ pp = pport_new(conf, name, port->port_id);
+ if (pp == NULL) {
+ log_warnx("pport_new failed");
+ continue;
+ }
+ }
+ continue;
+ }
+ if (port->cfiscsi_state != 1) {
+ log_debugx("CTL port %ju is not active (%d); ignoring",
+ (uintmax_t)port->port_id, port->cfiscsi_state);
+ continue;
+ }
+
+ targ = target_find(conf, port->cfiscsi_target);
+ if (targ == NULL) {
+#if 0
+ log_debugx("found new kernel target %s for CTL port %ld",
+ port->cfiscsi_target, port->port_id);
+#endif
+ targ = target_new(conf, port->cfiscsi_target);
+ if (targ == NULL) {
+ log_warnx("target_new failed");
+ continue;
+ }
+ }
+
+ if (port->ctld_portal_group_name == NULL)
+ continue;
+ pg = portal_group_find(conf, port->ctld_portal_group_name);
+ if (pg == NULL) {
+#if 0
+ log_debugx("found new kernel portal group %s for CTL port %ld",
+ port->ctld_portal_group_name, port->port_id);
+#endif
+ pg = portal_group_new(conf, port->ctld_portal_group_name);
+ if (pg == NULL) {
+ log_warnx("portal_group_new failed");
+ continue;
+ }
+ }
+ pg->pg_tag = port->cfiscsi_portal_group_tag;
+ cp = port_new(conf, targ, pg);
+ if (cp == NULL) {
+ log_warnx("port_new failed");
+ continue;
+ }
+ cp->p_ctl_port = port->port_id;
+ }
+ free(name);
+
+ STAILQ_FOREACH(lun, &devlist.lun_list, links) {
+ struct cctl_lun_nv *nv;
+
+ if (lun->ctld_name == NULL) {
+ log_debugx("CTL lun %ju wasn't managed by ctld; "
+ "ignoring", (uintmax_t)lun->lun_id);
+ continue;
+ }
+
+ cl = lun_find(conf, lun->ctld_name);
+ if (cl != NULL) {
+ log_warnx("found CTL lun %ju \"%s\", "
+ "also backed by CTL lun %d; ignoring",
+ (uintmax_t)lun->lun_id, lun->ctld_name,
+ cl->l_ctl_lun);
+ continue;
+ }
+
+ log_debugx("found CTL lun %ju \"%s\"",
+ (uintmax_t)lun->lun_id, lun->ctld_name);
+
+ cl = lun_new(conf, lun->ctld_name);
+ if (cl == NULL) {
+ log_warnx("lun_new failed");
+ continue;
+ }
+ lun_set_backend(cl, lun->backend_type);
+ lun_set_device_type(cl, lun->device_type);
+ lun_set_blocksize(cl, lun->blocksize);
+ lun_set_device_id(cl, lun->device_id);
+ lun_set_serial(cl, lun->serial_number);
+ lun_set_size(cl, lun->size_blocks * cl->l_blocksize);
+ lun_set_ctl_lun(cl, lun->lun_id);
+
+ STAILQ_FOREACH(nv, &lun->attr_list, links) {
+ if (strcmp(nv->name, "file") == 0 ||
+ strcmp(nv->name, "dev") == 0) {
+ lun_set_path(cl, nv->value);
+ continue;
+ }
+ o = option_new(&cl->l_options, nv->name, nv->value);
+ if (o == NULL)
+ log_warnx("unable to add CTL lun option %s "
+ "for CTL lun %ju \"%s\"",
+ nv->name, (uintmax_t) lun->lun_id,
+ cl->l_name);
+ }
+ }
+
+ return (conf);
+}
+
+static void
+str_arg(struct ctl_be_arg *arg, const char *name, const char *value)
+{
+
+ arg->namelen = strlen(name) + 1;
+ arg->name = __DECONST(char *, name);
+ arg->vallen = strlen(value) + 1;
+ arg->value = __DECONST(char *, value);
+ arg->flags = CTL_BEARG_ASCII | CTL_BEARG_RD;
+}
+
+int
+kernel_lun_add(struct lun *lun)
+{
+ struct option *o;
+ struct ctl_lun_req req;
+ int error, i, num_options;
+
+ bzero(&req, sizeof(req));
+
+ strlcpy(req.backend, lun->l_backend, sizeof(req.backend));
+ req.reqtype = CTL_LUNREQ_CREATE;
+
+ req.reqdata.create.blocksize_bytes = lun->l_blocksize;
+
+ if (lun->l_size != 0)
+ req.reqdata.create.lun_size_bytes = lun->l_size;
+
+ if (lun->l_ctl_lun >= 0) {
+ req.reqdata.create.req_lun_id = lun->l_ctl_lun;
+ req.reqdata.create.flags |= CTL_LUN_FLAG_ID_REQ;
+ }
+
+ req.reqdata.create.flags |= CTL_LUN_FLAG_DEV_TYPE;
+ req.reqdata.create.device_type = lun->l_device_type;
+
+ if (lun->l_serial != NULL) {
+ strncpy(req.reqdata.create.serial_num, lun->l_serial,
+ sizeof(req.reqdata.create.serial_num));
+ req.reqdata.create.flags |= CTL_LUN_FLAG_SERIAL_NUM;
+ }
+
+ if (lun->l_device_id != NULL) {
+ strncpy(req.reqdata.create.device_id, lun->l_device_id,
+ sizeof(req.reqdata.create.device_id));
+ req.reqdata.create.flags |= CTL_LUN_FLAG_DEVID;
+ }
+
+ if (lun->l_path != NULL) {
+ o = option_find(&lun->l_options, "file");
+ if (o != NULL) {
+ option_set(o, lun->l_path);
+ } else {
+ o = option_new(&lun->l_options, "file", lun->l_path);
+ assert(o != NULL);
+ }
+ }
+
+ o = option_find(&lun->l_options, "ctld_name");
+ if (o != NULL) {
+ option_set(o, lun->l_name);
+ } else {
+ o = option_new(&lun->l_options, "ctld_name", lun->l_name);
+ assert(o != NULL);
+ }
+
+ o = option_find(&lun->l_options, "scsiname");
+ if (o == NULL && lun->l_scsiname != NULL) {
+ o = option_new(&lun->l_options, "scsiname", lun->l_scsiname);
+ assert(o != NULL);
+ }
+
+ num_options = 0;
+ TAILQ_FOREACH(o, &lun->l_options, o_next)
+ num_options++;
+
+ req.num_be_args = num_options;
+ if (num_options > 0) {
+ req.be_args = malloc(num_options * sizeof(*req.be_args));
+ if (req.be_args == NULL) {
+ log_warn("error allocating %zd bytes",
+ num_options * sizeof(*req.be_args));
+ return (1);
+ }
+
+ i = 0;
+ TAILQ_FOREACH(o, &lun->l_options, o_next) {
+ str_arg(&req.be_args[i], o->o_name, o->o_value);
+ i++;
+ }
+ assert(i == num_options);
+ }
+
+ error = ioctl(ctl_fd, CTL_LUN_REQ, &req);
+ free(req.be_args);
+ if (error != 0) {
+ log_warn("error issuing CTL_LUN_REQ ioctl");
+ return (1);
+ }
+
+ switch (req.status) {
+ case CTL_LUN_ERROR:
+ log_warnx("LUN creation error: %s", req.error_str);
+ return (1);
+ case CTL_LUN_WARNING:
+ log_warnx("LUN creation warning: %s", req.error_str);
+ break;
+ case CTL_LUN_OK:
+ break;
+ default:
+ log_warnx("unknown LUN creation status: %d",
+ req.status);
+ return (1);
+ }
+
+ lun_set_ctl_lun(lun, req.reqdata.create.req_lun_id);
+ return (0);
+}
+
+int
+kernel_lun_modify(struct lun *lun)
+{
+ struct option *o;
+ struct ctl_lun_req req;
+ int error, i, num_options;
+
+ bzero(&req, sizeof(req));
+
+ strlcpy(req.backend, lun->l_backend, sizeof(req.backend));
+ req.reqtype = CTL_LUNREQ_MODIFY;
+
+ req.reqdata.modify.lun_id = lun->l_ctl_lun;
+ req.reqdata.modify.lun_size_bytes = lun->l_size;
+
+ num_options = 0;
+ TAILQ_FOREACH(o, &lun->l_options, o_next)
+ num_options++;
+
+ req.num_be_args = num_options;
+ if (num_options > 0) {
+ req.be_args = malloc(num_options * sizeof(*req.be_args));
+ if (req.be_args == NULL) {
+ log_warn("error allocating %zd bytes",
+ num_options * sizeof(*req.be_args));
+ return (1);
+ }
+
+ i = 0;
+ TAILQ_FOREACH(o, &lun->l_options, o_next) {
+ str_arg(&req.be_args[i], o->o_name, o->o_value);
+ i++;
+ }
+ assert(i == num_options);
+ }
+
+ error = ioctl(ctl_fd, CTL_LUN_REQ, &req);
+ free(req.be_args);
+ if (error != 0) {
+ log_warn("error issuing CTL_LUN_REQ ioctl");
+ return (1);
+ }
+
+ switch (req.status) {
+ case CTL_LUN_ERROR:
+ log_warnx("LUN modification error: %s", req.error_str);
+ return (1);
+ case CTL_LUN_WARNING:
+ log_warnx("LUN modification warning: %s", req.error_str);
+ break;
+ case CTL_LUN_OK:
+ break;
+ default:
+ log_warnx("unknown LUN modification status: %d",
+ req.status);
+ return (1);
+ }
+
+ return (0);
+}
+
+int
+kernel_lun_remove(struct lun *lun)
+{
+ struct ctl_lun_req req;
+
+ bzero(&req, sizeof(req));
+
+ strlcpy(req.backend, lun->l_backend, sizeof(req.backend));
+ req.reqtype = CTL_LUNREQ_RM;
+
+ req.reqdata.rm.lun_id = lun->l_ctl_lun;
+
+ if (ioctl(ctl_fd, CTL_LUN_REQ, &req) == -1) {
+ log_warn("error issuing CTL_LUN_REQ ioctl");
+ return (1);
+ }
+
+ switch (req.status) {
+ case CTL_LUN_ERROR:
+ log_warnx("LUN removal error: %s", req.error_str);
+ return (1);
+ case CTL_LUN_WARNING:
+ log_warnx("LUN removal warning: %s", req.error_str);
+ break;
+ case CTL_LUN_OK:
+ break;
+ default:
+ log_warnx("unknown LUN removal status: %d", req.status);
+ return (1);
+ }
+
+ return (0);
+}
+
+void
+kernel_handoff(struct connection *conn)
+{
+ struct ctl_iscsi req;
+
+ bzero(&req, sizeof(req));
+
+ req.type = CTL_ISCSI_HANDOFF;
+ strlcpy(req.data.handoff.initiator_name,
+ conn->conn_initiator_name, sizeof(req.data.handoff.initiator_name));
+ strlcpy(req.data.handoff.initiator_addr,
+ conn->conn_initiator_addr, sizeof(req.data.handoff.initiator_addr));
+ if (conn->conn_initiator_alias != NULL) {
+ strlcpy(req.data.handoff.initiator_alias,
+ conn->conn_initiator_alias, sizeof(req.data.handoff.initiator_alias));
+ }
+ memcpy(req.data.handoff.initiator_isid, conn->conn_initiator_isid,
+ sizeof(req.data.handoff.initiator_isid));
+ strlcpy(req.data.handoff.target_name,
+ conn->conn_target->t_name, sizeof(req.data.handoff.target_name));
+ if (conn->conn_portal->p_portal_group->pg_offload != NULL) {
+ strlcpy(req.data.handoff.offload,
+ conn->conn_portal->p_portal_group->pg_offload,
+ sizeof(req.data.handoff.offload));
+ }
+#ifdef ICL_KERNEL_PROXY
+ if (proxy_mode)
+ req.data.handoff.connection_id = conn->conn_socket;
+ else
+ req.data.handoff.socket = conn->conn_socket;
+#else
+ req.data.handoff.socket = conn->conn_socket;
+#endif
+ req.data.handoff.portal_group_tag =
+ conn->conn_portal->p_portal_group->pg_tag;
+ if (conn->conn_header_digest == CONN_DIGEST_CRC32C)
+ req.data.handoff.header_digest = CTL_ISCSI_DIGEST_CRC32C;
+ if (conn->conn_data_digest == CONN_DIGEST_CRC32C)
+ req.data.handoff.data_digest = CTL_ISCSI_DIGEST_CRC32C;
+ req.data.handoff.cmdsn = conn->conn_cmdsn;
+ req.data.handoff.statsn = conn->conn_statsn;
+ req.data.handoff.max_recv_data_segment_length =
+ conn->conn_max_data_segment_length;
+ req.data.handoff.max_burst_length = conn->conn_max_burst_length;
+ req.data.handoff.immediate_data = conn->conn_immediate_data;
+
+ if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) {
+ log_err(1, "error issuing CTL_ISCSI ioctl; "
+ "dropping connection");
+ }
+
+ if (req.status != CTL_ISCSI_OK) {
+ log_errx(1, "error returned from CTL iSCSI handoff request: "
+ "%s; dropping connection", req.error_str);
+ }
+}
+
+void
+kernel_limits(const char *offload, size_t *max_data_segment_length)
+{
+ struct ctl_iscsi req;
+
+ bzero(&req, sizeof(req));
+
+ req.type = CTL_ISCSI_LIMITS;
+ if (offload != NULL) {
+ strlcpy(req.data.limits.offload, offload,
+ sizeof(req.data.limits.offload));
+ }
+
+ if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) {
+ log_err(1, "error issuing CTL_ISCSI ioctl; "
+ "dropping connection");
+ }
+
+ if (req.status != CTL_ISCSI_OK) {
+ log_errx(1, "error returned from CTL iSCSI limits request: "
+ "%s; dropping connection", req.error_str);
+ }
+
+ *max_data_segment_length = req.data.limits.data_segment_limit;
+ if (offload != NULL) {
+ log_debugx("MaxRecvDataSegment kernel limit for offload "
+ "\"%s\" is %zd", offload, *max_data_segment_length);
+ } else {
+ log_debugx("MaxRecvDataSegment kernel limit is %zd",
+ *max_data_segment_length);
+ }
+}
+
+int
+kernel_port_add(struct port *port)
+{
+ struct option *o;
+ struct ctl_port_entry entry;
+ struct ctl_req req;
+ struct ctl_lun_map lm;
+ struct target *targ = port->p_target;
+ struct portal_group *pg = port->p_portal_group;
+ char tagstr[16];
+ int error, i, n;
+
+ /* Create iSCSI port. */
+ if (port->p_portal_group) {
+ bzero(&req, sizeof(req));
+ strlcpy(req.driver, "iscsi", sizeof(req.driver));
+ req.reqtype = CTL_REQ_CREATE;
+ req.num_args = 5;
+ TAILQ_FOREACH(o, &pg->pg_options, o_next)
+ req.num_args++;
+ req.args = malloc(req.num_args * sizeof(*req.args));
+ if (req.args == NULL)
+ log_err(1, "malloc");
+ n = 0;
+ req.args[n].namelen = sizeof("port_id");
+ req.args[n].name = __DECONST(char *, "port_id");
+ req.args[n].vallen = sizeof(port->p_ctl_port);
+ req.args[n].value = &port->p_ctl_port;
+ req.args[n++].flags = CTL_BEARG_WR;
+ str_arg(&req.args[n++], "cfiscsi_target", targ->t_name);
+ snprintf(tagstr, sizeof(tagstr), "%d", pg->pg_tag);
+ str_arg(&req.args[n++], "cfiscsi_portal_group_tag", tagstr);
+ if (targ->t_alias)
+ str_arg(&req.args[n++], "cfiscsi_target_alias", targ->t_alias);
+ str_arg(&req.args[n++], "ctld_portal_group_name", pg->pg_name);
+ TAILQ_FOREACH(o, &pg->pg_options, o_next)
+ str_arg(&req.args[n++], o->o_name, o->o_value);
+ req.num_args = n;
+ error = ioctl(ctl_fd, CTL_PORT_REQ, &req);
+ free(req.args);
+ if (error != 0) {
+ log_warn("error issuing CTL_PORT_REQ ioctl");
+ return (1);
+ }
+ if (req.status == CTL_LUN_ERROR) {
+ log_warnx("error returned from port creation request: %s",
+ req.error_str);
+ return (1);
+ }
+ if (req.status != CTL_LUN_OK) {
+ log_warnx("unknown port creation request status %d",
+ req.status);
+ return (1);
+ }
+ } else if (port->p_pport) {
+ port->p_ctl_port = port->p_pport->pp_ctl_port;
+
+ if (strncmp(targ->t_name, "naa.", 4) == 0 &&
+ strlen(targ->t_name) == 20) {
+ bzero(&entry, sizeof(entry));
+ entry.port_type = CTL_PORT_NONE;
+ entry.targ_port = port->p_ctl_port;
+ entry.flags |= CTL_PORT_WWNN_VALID;
+ entry.wwnn = strtoull(targ->t_name + 4, NULL, 16);
+ if (ioctl(ctl_fd, CTL_SET_PORT_WWNS, &entry) == -1)
+ log_warn("CTL_SET_PORT_WWNS ioctl failed");
+ }
+ }
+
+ /* Explicitly enable mapping to block any access except allowed. */
+ lm.port = port->p_ctl_port;
+ lm.plun = UINT32_MAX;
+ lm.lun = 0;
+ error = ioctl(ctl_fd, CTL_LUN_MAP, &lm);
+ if (error != 0)
+ log_warn("CTL_LUN_MAP ioctl failed");
+
+ /* Map configured LUNs */
+ for (i = 0; i < MAX_LUNS; i++) {
+ if (targ->t_luns[i] == NULL)
+ continue;
+ lm.port = port->p_ctl_port;
+ lm.plun = i;
+ lm.lun = targ->t_luns[i]->l_ctl_lun;
+ error = ioctl(ctl_fd, CTL_LUN_MAP, &lm);
+ if (error != 0)
+ log_warn("CTL_LUN_MAP ioctl failed");
+ }
+
+ /* Enable port */
+ bzero(&entry, sizeof(entry));
+ entry.targ_port = port->p_ctl_port;
+ error = ioctl(ctl_fd, CTL_ENABLE_PORT, &entry);
+ if (error != 0) {
+ log_warn("CTL_ENABLE_PORT ioctl failed");
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+kernel_port_update(struct port *port, struct port *oport)
+{
+ struct ctl_lun_map lm;
+ struct target *targ = port->p_target;
+ struct target *otarg = oport->p_target;
+ int error, i;
+ uint32_t olun;
+
+ /* Map configured LUNs and unmap others */
+ for (i = 0; i < MAX_LUNS; i++) {
+ lm.port = port->p_ctl_port;
+ lm.plun = i;
+ if (targ->t_luns[i] == NULL)
+ lm.lun = UINT32_MAX;
+ else
+ lm.lun = targ->t_luns[i]->l_ctl_lun;
+ if (otarg->t_luns[i] == NULL)
+ olun = UINT32_MAX;
+ else
+ olun = otarg->t_luns[i]->l_ctl_lun;
+ if (lm.lun == olun)
+ continue;
+ error = ioctl(ctl_fd, CTL_LUN_MAP, &lm);
+ if (error != 0)
+ log_warn("CTL_LUN_MAP ioctl failed");
+ }
+ return (0);
+}
+
+int
+kernel_port_remove(struct port *port)
+{
+ struct ctl_port_entry entry;
+ struct ctl_lun_map lm;
+ struct ctl_req req;
+ char tagstr[16];
+ struct target *targ = port->p_target;
+ struct portal_group *pg = port->p_portal_group;
+ int error;
+
+ /* Disable port */
+ bzero(&entry, sizeof(entry));
+ entry.targ_port = port->p_ctl_port;
+ error = ioctl(ctl_fd, CTL_DISABLE_PORT, &entry);
+ if (error != 0) {
+ log_warn("CTL_DISABLE_PORT ioctl failed");
+ return (-1);
+ }
+
+ /* Remove iSCSI port. */
+ if (port->p_portal_group) {
+ bzero(&req, sizeof(req));
+ strlcpy(req.driver, "iscsi", sizeof(req.driver));
+ req.reqtype = CTL_REQ_REMOVE;
+ req.num_args = 2;
+ req.args = malloc(req.num_args * sizeof(*req.args));
+ if (req.args == NULL)
+ log_err(1, "malloc");
+ str_arg(&req.args[0], "cfiscsi_target", targ->t_name);
+ snprintf(tagstr, sizeof(tagstr), "%d", pg->pg_tag);
+ str_arg(&req.args[1], "cfiscsi_portal_group_tag", tagstr);
+ error = ioctl(ctl_fd, CTL_PORT_REQ, &req);
+ free(req.args);
+ if (error != 0) {
+ log_warn("error issuing CTL_PORT_REQ ioctl");
+ return (1);
+ }
+ if (req.status == CTL_LUN_ERROR) {
+ log_warnx("error returned from port removal request: %s",
+ req.error_str);
+ return (1);
+ }
+ if (req.status != CTL_LUN_OK) {
+ log_warnx("unknown port removal request status %d",
+ req.status);
+ return (1);
+ }
+ } else {
+ /* Disable LUN mapping. */
+ lm.port = port->p_ctl_port;
+ lm.plun = UINT32_MAX;
+ lm.lun = UINT32_MAX;
+ error = ioctl(ctl_fd, CTL_LUN_MAP, &lm);
+ if (error != 0)
+ log_warn("CTL_LUN_MAP ioctl failed");
+ }
+ return (0);
+}
+
+#ifdef ICL_KERNEL_PROXY
+void
+kernel_listen(struct addrinfo *ai, bool iser, int portal_id)
+{
+ struct ctl_iscsi req;
+
+ bzero(&req, sizeof(req));
+
+ req.type = CTL_ISCSI_LISTEN;
+ req.data.listen.iser = iser;
+ req.data.listen.domain = ai->ai_family;
+ req.data.listen.socktype = ai->ai_socktype;
+ req.data.listen.protocol = ai->ai_protocol;
+ req.data.listen.addr = ai->ai_addr;
+ req.data.listen.addrlen = ai->ai_addrlen;
+ req.data.listen.portal_id = portal_id;
+
+ if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1)
+ log_err(1, "error issuing CTL_ISCSI ioctl");
+
+ if (req.status != CTL_ISCSI_OK) {
+ log_errx(1, "error returned from CTL iSCSI listen: %s",
+ req.error_str);
+ }
+}
+
+void
+kernel_accept(int *connection_id, int *portal_id,
+ struct sockaddr *client_sa, socklen_t *client_salen)
+{
+ struct ctl_iscsi req;
+ struct sockaddr_storage ss;
+
+ bzero(&req, sizeof(req));
+
+ req.type = CTL_ISCSI_ACCEPT;
+ req.data.accept.initiator_addr = (struct sockaddr *)&ss;
+
+ if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1)
+ log_err(1, "error issuing CTL_ISCSI ioctl");
+
+ if (req.status != CTL_ISCSI_OK) {
+ log_errx(1, "error returned from CTL iSCSI accept: %s",
+ req.error_str);
+ }
+
+ *connection_id = req.data.accept.connection_id;
+ *portal_id = req.data.accept.portal_id;
+ *client_salen = req.data.accept.initiator_addrlen;
+ memcpy(client_sa, &ss, *client_salen);
+}
+
+void
+kernel_send(struct pdu *pdu)
+{
+ struct ctl_iscsi req;
+
+ bzero(&req, sizeof(req));
+
+ req.type = CTL_ISCSI_SEND;
+ req.data.send.connection_id = pdu->pdu_connection->conn_socket;
+ req.data.send.bhs = pdu->pdu_bhs;
+ req.data.send.data_segment_len = pdu->pdu_data_len;
+ req.data.send.data_segment = pdu->pdu_data;
+
+ if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) {
+ log_err(1, "error issuing CTL_ISCSI ioctl; "
+ "dropping connection");
+ }
+
+ if (req.status != CTL_ISCSI_OK) {
+ log_errx(1, "error returned from CTL iSCSI send: "
+ "%s; dropping connection", req.error_str);
+ }
+}
+
+void
+kernel_receive(struct pdu *pdu)
+{
+ struct ctl_iscsi req;
+
+ pdu->pdu_data = malloc(MAX_DATA_SEGMENT_LENGTH);
+ if (pdu->pdu_data == NULL)
+ log_err(1, "malloc");
+
+ bzero(&req, sizeof(req));
+
+ req.type = CTL_ISCSI_RECEIVE;
+ req.data.receive.connection_id = pdu->pdu_connection->conn_socket;
+ req.data.receive.bhs = pdu->pdu_bhs;
+ req.data.receive.data_segment_len = MAX_DATA_SEGMENT_LENGTH;
+ req.data.receive.data_segment = pdu->pdu_data;
+
+ if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) {
+ log_err(1, "error issuing CTL_ISCSI ioctl; "
+ "dropping connection");
+ }
+
+ if (req.status != CTL_ISCSI_OK) {
+ log_errx(1, "error returned from CTL iSCSI receive: "
+ "%s; dropping connection", req.error_str);
+ }
+
+}
+
+#endif /* ICL_KERNEL_PROXY */
+
+/*
+ * XXX: I CANT INTO LATIN
+ */
+void
+kernel_capsicate(void)
+{
+ int error;
+ cap_rights_t rights;
+ const unsigned long cmds[] = { CTL_ISCSI };
+
+ cap_rights_init(&rights, CAP_IOCTL);
+ error = cap_rights_limit(ctl_fd, &rights);
+ if (error != 0 && errno != ENOSYS)
+ log_err(1, "cap_rights_limit");
+
+ error = cap_ioctls_limit(ctl_fd, cmds,
+ sizeof(cmds) / sizeof(cmds[0]));
+ if (error != 0 && errno != ENOSYS)
+ log_err(1, "cap_ioctls_limit");
+
+ error = cap_enter();
+ if (error != 0 && errno != ENOSYS)
+ log_err(1, "cap_enter");
+
+ if (cap_sandboxed())
+ log_debugx("Capsicum capability mode enabled");
+ else
+ log_warnx("Capsicum capability mode not supported");
+}
+
diff --git a/usr.sbin/ctld/keys.c b/usr.sbin/ctld/keys.c
new file mode 100644
index 0000000..c2f1604
--- /dev/null
+++ b/usr.sbin/ctld/keys.c
@@ -0,0 +1,198 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ctld.h"
+
+struct keys *
+keys_new(void)
+{
+ struct keys *keys;
+
+ keys = calloc(sizeof(*keys), 1);
+ if (keys == NULL)
+ log_err(1, "calloc");
+
+ return (keys);
+}
+
+void
+keys_delete(struct keys *keys)
+{
+
+ free(keys->keys_data);
+ free(keys);
+}
+
+void
+keys_load(struct keys *keys, const struct pdu *pdu)
+{
+ int i;
+ char *pair;
+ size_t pair_len;
+
+ if (pdu->pdu_data_len == 0)
+ return;
+
+ if (pdu->pdu_data[pdu->pdu_data_len - 1] != '\0')
+ log_errx(1, "protocol error: key not NULL-terminated\n");
+
+ assert(keys->keys_data == NULL);
+ keys->keys_data_len = pdu->pdu_data_len;
+ keys->keys_data = malloc(keys->keys_data_len);
+ if (keys->keys_data == NULL)
+ log_err(1, "malloc");
+ memcpy(keys->keys_data, pdu->pdu_data, keys->keys_data_len);
+
+ /*
+ * XXX: Review this carefully.
+ */
+ pair = keys->keys_data;
+ for (i = 0;; i++) {
+ if (i >= KEYS_MAX)
+ log_errx(1, "too many keys received");
+
+ pair_len = strlen(pair);
+
+ keys->keys_values[i] = pair;
+ keys->keys_names[i] = strsep(&keys->keys_values[i], "=");
+ if (keys->keys_names[i] == NULL || keys->keys_values[i] == NULL)
+ log_errx(1, "malformed keys");
+ log_debugx("key received: \"%s=%s\"",
+ keys->keys_names[i], keys->keys_values[i]);
+
+ pair += pair_len + 1; /* +1 to skip the terminating '\0'. */
+ if (pair == keys->keys_data + keys->keys_data_len)
+ break;
+ assert(pair < keys->keys_data + keys->keys_data_len);
+ }
+}
+
+void
+keys_save(struct keys *keys, struct pdu *pdu)
+{
+ char *data;
+ size_t len;
+ int i;
+
+ /*
+ * XXX: Not particularly efficient.
+ */
+ len = 0;
+ for (i = 0; i < KEYS_MAX; i++) {
+ if (keys->keys_names[i] == NULL)
+ break;
+ /*
+ * +1 for '=', +1 for '\0'.
+ */
+ len += strlen(keys->keys_names[i]) +
+ strlen(keys->keys_values[i]) + 2;
+ }
+
+ if (len == 0)
+ return;
+
+ data = malloc(len);
+ if (data == NULL)
+ log_err(1, "malloc");
+
+ pdu->pdu_data = data;
+ pdu->pdu_data_len = len;
+
+ for (i = 0; i < KEYS_MAX; i++) {
+ if (keys->keys_names[i] == NULL)
+ break;
+ data += sprintf(data, "%s=%s",
+ keys->keys_names[i], keys->keys_values[i]);
+ data += 1; /* for '\0'. */
+ }
+}
+
+const char *
+keys_find(struct keys *keys, const char *name)
+{
+ int i;
+
+ /*
+ * Note that we don't handle duplicated key names here,
+ * as they are not supposed to happen in requests, and if they do,
+ * it's an initiator error.
+ */
+ for (i = 0; i < KEYS_MAX; i++) {
+ if (keys->keys_names[i] == NULL)
+ return (NULL);
+ if (strcmp(keys->keys_names[i], name) == 0)
+ return (keys->keys_values[i]);
+ }
+ return (NULL);
+}
+
+void
+keys_add(struct keys *keys, const char *name, const char *value)
+{
+ int i;
+
+ log_debugx("key to send: \"%s=%s\"", name, value);
+
+ /*
+ * Note that we don't check for duplicates here, as they are perfectly
+ * fine in responses, e.g. the "TargetName" keys in discovery sesion
+ * response.
+ */
+ for (i = 0; i < KEYS_MAX; i++) {
+ if (keys->keys_names[i] == NULL) {
+ keys->keys_names[i] = checked_strdup(name);
+ keys->keys_values[i] = checked_strdup(value);
+ return;
+ }
+ }
+ log_errx(1, "too many keys");
+}
+
+void
+keys_add_int(struct keys *keys, const char *name, int value)
+{
+ char *str;
+ int ret;
+
+ ret = asprintf(&str, "%d", value);
+ if (ret <= 0)
+ log_err(1, "asprintf");
+
+ keys_add(keys, name, str);
+ free(str);
+}
diff --git a/usr.sbin/ctld/log.c b/usr.sbin/ctld/log.c
new file mode 100644
index 0000000..ac838f3
--- /dev/null
+++ b/usr.sbin/ctld/log.c
@@ -0,0 +1,198 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <vis.h>
+
+#include "ctld.h"
+
+static int log_level = 0;
+static char *peer_name = NULL;
+static char *peer_addr = NULL;
+
+#define MSGBUF_LEN 1024
+
+void
+log_init(int level)
+{
+
+ log_level = level;
+ openlog(getprogname(), LOG_NDELAY | LOG_PID, LOG_DAEMON);
+}
+
+void
+log_set_peer_name(const char *name)
+{
+
+ /*
+ * XXX: Turn it into assertion?
+ */
+ if (peer_name != NULL)
+ log_errx(1, "%s called twice", __func__);
+ if (peer_addr == NULL)
+ log_errx(1, "%s called before log_set_peer_addr", __func__);
+
+ peer_name = checked_strdup(name);
+}
+
+void
+log_set_peer_addr(const char *addr)
+{
+
+ /*
+ * XXX: Turn it into assertion?
+ */
+ if (peer_addr != NULL)
+ log_errx(1, "%s called twice", __func__);
+
+ peer_addr = checked_strdup(addr);
+}
+
+static void
+log_common(int priority, int log_errno, const char *fmt, va_list ap)
+{
+ static char msgbuf[MSGBUF_LEN];
+ static char msgbuf_strvised[MSGBUF_LEN * 4 + 1];
+ int ret;
+
+ ret = vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
+ if (ret < 0) {
+ fprintf(stderr, "%s: snprintf failed", getprogname());
+ syslog(LOG_CRIT, "snprintf failed");
+ exit(1);
+ }
+
+ ret = strnvis(msgbuf_strvised, sizeof(msgbuf_strvised), msgbuf, VIS_NL);
+ if (ret < 0) {
+ fprintf(stderr, "%s: strnvis failed", getprogname());
+ syslog(LOG_CRIT, "strnvis failed");
+ exit(1);
+ }
+
+ if (log_errno == -1) {
+ if (peer_name != NULL) {
+ fprintf(stderr, "%s: %s (%s): %s\n", getprogname(),
+ peer_addr, peer_name, msgbuf_strvised);
+ syslog(priority, "%s (%s): %s",
+ peer_addr, peer_name, msgbuf_strvised);
+ } else if (peer_addr != NULL) {
+ fprintf(stderr, "%s: %s: %s\n", getprogname(),
+ peer_addr, msgbuf_strvised);
+ syslog(priority, "%s: %s",
+ peer_addr, msgbuf_strvised);
+ } else {
+ fprintf(stderr, "%s: %s\n", getprogname(), msgbuf_strvised);
+ syslog(priority, "%s", msgbuf_strvised);
+ }
+
+ } else {
+ if (peer_name != NULL) {
+ fprintf(stderr, "%s: %s (%s): %s: %s\n", getprogname(),
+ peer_addr, peer_name, msgbuf_strvised, strerror(errno));
+ syslog(priority, "%s (%s): %s: %s",
+ peer_addr, peer_name, msgbuf_strvised, strerror(errno));
+ } else if (peer_addr != NULL) {
+ fprintf(stderr, "%s: %s: %s: %s\n", getprogname(),
+ peer_addr, msgbuf_strvised, strerror(errno));
+ syslog(priority, "%s: %s: %s",
+ peer_addr, msgbuf_strvised, strerror(errno));
+ } else {
+ fprintf(stderr, "%s: %s: %s\n", getprogname(),
+ msgbuf_strvised, strerror(errno));
+ syslog(priority, "%s: %s",
+ msgbuf_strvised, strerror(errno));
+ }
+ }
+}
+
+void
+log_err(int eval, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ log_common(LOG_CRIT, errno, fmt, ap);
+ va_end(ap);
+
+ exit(eval);
+}
+
+void
+log_errx(int eval, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ log_common(LOG_CRIT, -1, fmt, ap);
+ va_end(ap);
+
+ exit(eval);
+}
+
+void
+log_warn(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ log_common(LOG_WARNING, errno, fmt, ap);
+ va_end(ap);
+}
+
+void
+log_warnx(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ log_common(LOG_WARNING, -1, fmt, ap);
+ va_end(ap);
+}
+
+void
+log_debugx(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (log_level == 0)
+ return;
+
+ va_start(ap, fmt);
+ log_common(LOG_DEBUG, -1, fmt, ap);
+ va_end(ap);
+}
diff --git a/usr.sbin/ctld/login.c b/usr.sbin/ctld/login.c
new file mode 100644
index 0000000..72c23ce
--- /dev/null
+++ b/usr.sbin/ctld/login.c
@@ -0,0 +1,1004 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <netinet/in.h>
+
+#include "ctld.h"
+#include "iscsi_proto.h"
+
+static void login_send_error(struct pdu *request,
+ char class, char detail);
+
+static void
+login_set_nsg(struct pdu *response, int nsg)
+{
+ struct iscsi_bhs_login_response *bhslr;
+
+ assert(nsg == BHSLR_STAGE_SECURITY_NEGOTIATION ||
+ nsg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION ||
+ nsg == BHSLR_STAGE_FULL_FEATURE_PHASE);
+
+ bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs;
+
+ bhslr->bhslr_flags &= 0xFC;
+ bhslr->bhslr_flags |= nsg;
+ bhslr->bhslr_flags |= BHSLR_FLAGS_TRANSIT;
+}
+
+static int
+login_csg(const struct pdu *request)
+{
+ struct iscsi_bhs_login_request *bhslr;
+
+ bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
+
+ return ((bhslr->bhslr_flags & 0x0C) >> 2);
+}
+
+static void
+login_set_csg(struct pdu *response, int csg)
+{
+ struct iscsi_bhs_login_response *bhslr;
+
+ assert(csg == BHSLR_STAGE_SECURITY_NEGOTIATION ||
+ csg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION ||
+ csg == BHSLR_STAGE_FULL_FEATURE_PHASE);
+
+ bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs;
+
+ bhslr->bhslr_flags &= 0xF3;
+ bhslr->bhslr_flags |= csg << 2;
+}
+
+static struct pdu *
+login_receive(struct connection *conn, bool initial)
+{
+ struct pdu *request;
+ struct iscsi_bhs_login_request *bhslr;
+
+ request = pdu_new(conn);
+ pdu_receive(request);
+ if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) !=
+ ISCSI_BHS_OPCODE_LOGIN_REQUEST) {
+ /*
+ * The first PDU in session is special - if we receive any PDU
+ * different than login request, we have to drop the connection
+ * without sending response ("A target receiving any PDU
+ * except a Login request before the Login Phase is started MUST
+ * immediately terminate the connection on which the PDU
+ * was received.")
+ */
+ if (initial == false)
+ login_send_error(request, 0x02, 0x0b);
+ log_errx(1, "protocol error: received invalid opcode 0x%x",
+ request->pdu_bhs->bhs_opcode);
+ }
+ bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
+ /*
+ * XXX: Implement the C flag some day.
+ */
+ if ((bhslr->bhslr_flags & BHSLR_FLAGS_CONTINUE) != 0) {
+ login_send_error(request, 0x03, 0x00);
+ log_errx(1, "received Login PDU with unsupported \"C\" flag");
+ }
+ if (bhslr->bhslr_version_max != 0x00) {
+ login_send_error(request, 0x02, 0x05);
+ log_errx(1, "received Login PDU with unsupported "
+ "Version-max 0x%x", bhslr->bhslr_version_max);
+ }
+ if (bhslr->bhslr_version_min != 0x00) {
+ login_send_error(request, 0x02, 0x05);
+ log_errx(1, "received Login PDU with unsupported "
+ "Version-min 0x%x", bhslr->bhslr_version_min);
+ }
+ if (ISCSI_SNLT(ntohl(bhslr->bhslr_cmdsn), conn->conn_cmdsn)) {
+ login_send_error(request, 0x02, 0x05);
+ log_errx(1, "received Login PDU with decreasing CmdSN: "
+ "was %u, is %u", conn->conn_cmdsn,
+ ntohl(bhslr->bhslr_cmdsn));
+ }
+ if (initial == false &&
+ ntohl(bhslr->bhslr_expstatsn) != conn->conn_statsn) {
+ login_send_error(request, 0x02, 0x05);
+ log_errx(1, "received Login PDU with wrong ExpStatSN: "
+ "is %u, should be %u", ntohl(bhslr->bhslr_expstatsn),
+ conn->conn_statsn);
+ }
+ conn->conn_cmdsn = ntohl(bhslr->bhslr_cmdsn);
+
+ return (request);
+}
+
+static struct pdu *
+login_new_response(struct pdu *request)
+{
+ struct pdu *response;
+ struct connection *conn;
+ struct iscsi_bhs_login_request *bhslr;
+ struct iscsi_bhs_login_response *bhslr2;
+
+ bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
+ conn = request->pdu_connection;
+
+ response = pdu_new_response(request);
+ bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs;
+ bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGIN_RESPONSE;
+ login_set_csg(response, BHSLR_STAGE_SECURITY_NEGOTIATION);
+ memcpy(bhslr2->bhslr_isid,
+ bhslr->bhslr_isid, sizeof(bhslr2->bhslr_isid));
+ bhslr2->bhslr_initiator_task_tag = bhslr->bhslr_initiator_task_tag;
+ bhslr2->bhslr_statsn = htonl(conn->conn_statsn++);
+ bhslr2->bhslr_expcmdsn = htonl(conn->conn_cmdsn);
+ bhslr2->bhslr_maxcmdsn = htonl(conn->conn_cmdsn);
+
+ return (response);
+}
+
+static void
+login_send_error(struct pdu *request, char class, char detail)
+{
+ struct pdu *response;
+ struct iscsi_bhs_login_response *bhslr2;
+
+ log_debugx("sending Login Response PDU with failure class 0x%x/0x%x; "
+ "see next line for reason", class, detail);
+ response = login_new_response(request);
+ bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs;
+ bhslr2->bhslr_status_class = class;
+ bhslr2->bhslr_status_detail = detail;
+
+ pdu_send(response);
+ pdu_delete(response);
+}
+
+static int
+login_list_contains(const char *list, const char *what)
+{
+ char *tofree, *str, *token;
+
+ tofree = str = checked_strdup(list);
+
+ while ((token = strsep(&str, ",")) != NULL) {
+ if (strcmp(token, what) == 0) {
+ free(tofree);
+ return (1);
+ }
+ }
+ free(tofree);
+ return (0);
+}
+
+static int
+login_list_prefers(const char *list,
+ const char *choice1, const char *choice2)
+{
+ char *tofree, *str, *token;
+
+ tofree = str = checked_strdup(list);
+
+ while ((token = strsep(&str, ",")) != NULL) {
+ if (strcmp(token, choice1) == 0) {
+ free(tofree);
+ return (1);
+ }
+ if (strcmp(token, choice2) == 0) {
+ free(tofree);
+ return (2);
+ }
+ }
+ free(tofree);
+ return (-1);
+}
+
+static struct pdu *
+login_receive_chap_a(struct connection *conn)
+{
+ struct pdu *request;
+ struct keys *request_keys;
+ const char *chap_a;
+
+ request = login_receive(conn, false);
+ request_keys = keys_new();
+ keys_load(request_keys, request);
+
+ chap_a = keys_find(request_keys, "CHAP_A");
+ if (chap_a == NULL) {
+ login_send_error(request, 0x02, 0x07);
+ log_errx(1, "received CHAP Login PDU without CHAP_A");
+ }
+ if (login_list_contains(chap_a, "5") == 0) {
+ login_send_error(request, 0x02, 0x01);
+ log_errx(1, "received CHAP Login PDU with unsupported CHAP_A "
+ "\"%s\"", chap_a);
+ }
+ keys_delete(request_keys);
+
+ return (request);
+}
+
+static void
+login_send_chap_c(struct pdu *request, struct chap *chap)
+{
+ struct pdu *response;
+ struct keys *response_keys;
+ char *chap_c, *chap_i;
+
+ chap_c = chap_get_challenge(chap);
+ chap_i = chap_get_id(chap);
+
+ response = login_new_response(request);
+ response_keys = keys_new();
+ keys_add(response_keys, "CHAP_A", "5");
+ keys_add(response_keys, "CHAP_I", chap_i);
+ keys_add(response_keys, "CHAP_C", chap_c);
+ free(chap_i);
+ free(chap_c);
+ keys_save(response_keys, response);
+ pdu_send(response);
+ pdu_delete(response);
+ keys_delete(response_keys);
+}
+
+static struct pdu *
+login_receive_chap_r(struct connection *conn, struct auth_group *ag,
+ struct chap *chap, const struct auth **authp)
+{
+ struct pdu *request;
+ struct keys *request_keys;
+ const char *chap_n, *chap_r;
+ const struct auth *auth;
+ int error;
+
+ request = login_receive(conn, false);
+ request_keys = keys_new();
+ keys_load(request_keys, request);
+
+ chap_n = keys_find(request_keys, "CHAP_N");
+ if (chap_n == NULL) {
+ login_send_error(request, 0x02, 0x07);
+ log_errx(1, "received CHAP Login PDU without CHAP_N");
+ }
+ chap_r = keys_find(request_keys, "CHAP_R");
+ if (chap_r == NULL) {
+ login_send_error(request, 0x02, 0x07);
+ log_errx(1, "received CHAP Login PDU without CHAP_R");
+ }
+ error = chap_receive(chap, chap_r);
+ if (error != 0) {
+ login_send_error(request, 0x02, 0x07);
+ log_errx(1, "received CHAP Login PDU with malformed CHAP_R");
+ }
+
+ /*
+ * Verify the response.
+ */
+ assert(ag->ag_type == AG_TYPE_CHAP ||
+ ag->ag_type == AG_TYPE_CHAP_MUTUAL);
+ auth = auth_find(ag, chap_n);
+ if (auth == NULL) {
+ login_send_error(request, 0x02, 0x01);
+ log_errx(1, "received CHAP Login with invalid user \"%s\"",
+ chap_n);
+ }
+
+ assert(auth->a_secret != NULL);
+ assert(strlen(auth->a_secret) > 0);
+
+ error = chap_authenticate(chap, auth->a_secret);
+ if (error != 0) {
+ login_send_error(request, 0x02, 0x01);
+ log_errx(1, "CHAP authentication failed for user \"%s\"",
+ auth->a_user);
+ }
+
+ keys_delete(request_keys);
+
+ *authp = auth;
+ return (request);
+}
+
+static void
+login_send_chap_success(struct pdu *request,
+ const struct auth *auth)
+{
+ struct pdu *response;
+ struct keys *request_keys, *response_keys;
+ struct rchap *rchap;
+ const char *chap_i, *chap_c;
+ char *chap_r;
+ int error;
+
+ response = login_new_response(request);
+ login_set_nsg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION);
+
+ /*
+ * Actually, one more thing: mutual authentication.
+ */
+ request_keys = keys_new();
+ keys_load(request_keys, request);
+ chap_i = keys_find(request_keys, "CHAP_I");
+ chap_c = keys_find(request_keys, "CHAP_C");
+ if (chap_i != NULL || chap_c != NULL) {
+ if (chap_i == NULL) {
+ login_send_error(request, 0x02, 0x07);
+ log_errx(1, "initiator requested target "
+ "authentication, but didn't send CHAP_I");
+ }
+ if (chap_c == NULL) {
+ login_send_error(request, 0x02, 0x07);
+ log_errx(1, "initiator requested target "
+ "authentication, but didn't send CHAP_C");
+ }
+ if (auth->a_auth_group->ag_type != AG_TYPE_CHAP_MUTUAL) {
+ login_send_error(request, 0x02, 0x01);
+ log_errx(1, "initiator requests target authentication "
+ "for user \"%s\", but mutual user/secret "
+ "is not set", auth->a_user);
+ }
+
+ log_debugx("performing mutual authentication as user \"%s\"",
+ auth->a_mutual_user);
+
+ rchap = rchap_new(auth->a_mutual_secret);
+ error = rchap_receive(rchap, chap_i, chap_c);
+ if (error != 0) {
+ login_send_error(request, 0x02, 0x07);
+ log_errx(1, "received CHAP Login PDU with malformed "
+ "CHAP_I or CHAP_C");
+ }
+ chap_r = rchap_get_response(rchap);
+ rchap_delete(rchap);
+ response_keys = keys_new();
+ keys_add(response_keys, "CHAP_N", auth->a_mutual_user);
+ keys_add(response_keys, "CHAP_R", chap_r);
+ free(chap_r);
+ keys_save(response_keys, response);
+ keys_delete(response_keys);
+ } else {
+ log_debugx("initiator did not request target authentication");
+ }
+
+ keys_delete(request_keys);
+ pdu_send(response);
+ pdu_delete(response);
+}
+
+static void
+login_chap(struct connection *conn, struct auth_group *ag)
+{
+ const struct auth *auth;
+ struct chap *chap;
+ struct pdu *request;
+
+ /*
+ * Receive CHAP_A PDU.
+ */
+ log_debugx("beginning CHAP authentication; waiting for CHAP_A");
+ request = login_receive_chap_a(conn);
+
+ /*
+ * Generate the challenge.
+ */
+ chap = chap_new();
+
+ /*
+ * Send the challenge.
+ */
+ log_debugx("sending CHAP_C, binary challenge size is %zd bytes",
+ sizeof(chap->chap_challenge));
+ login_send_chap_c(request, chap);
+ pdu_delete(request);
+
+ /*
+ * Receive CHAP_N/CHAP_R PDU and authenticate.
+ */
+ log_debugx("waiting for CHAP_N/CHAP_R");
+ request = login_receive_chap_r(conn, ag, chap, &auth);
+
+ /*
+ * Yay, authentication succeeded!
+ */
+ log_debugx("authentication succeeded for user \"%s\"; "
+ "transitioning to Negotiation Phase", auth->a_user);
+ login_send_chap_success(request, auth);
+ pdu_delete(request);
+
+ /*
+ * Leave username and CHAP information for discovery().
+ */
+ conn->conn_user = auth->a_user;
+ conn->conn_chap = chap;
+}
+
+static void
+login_negotiate_key(struct pdu *request, const char *name,
+ const char *value, bool skipped_security, struct keys *response_keys)
+{
+ int which;
+ size_t tmp;
+ struct connection *conn;
+
+ conn = request->pdu_connection;
+
+ if (strcmp(name, "InitiatorName") == 0) {
+ if (!skipped_security)
+ log_errx(1, "initiator resent InitiatorName");
+ } else if (strcmp(name, "SessionType") == 0) {
+ if (!skipped_security)
+ log_errx(1, "initiator resent SessionType");
+ } else if (strcmp(name, "TargetName") == 0) {
+ if (!skipped_security)
+ log_errx(1, "initiator resent TargetName");
+ } else if (strcmp(name, "InitiatorAlias") == 0) {
+ if (conn->conn_initiator_alias != NULL)
+ free(conn->conn_initiator_alias);
+ conn->conn_initiator_alias = checked_strdup(value);
+ } else if (strcmp(value, "Irrelevant") == 0) {
+ /* Ignore. */
+ } else if (strcmp(name, "HeaderDigest") == 0) {
+ /*
+ * We don't handle digests for discovery sessions.
+ */
+ if (conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY) {
+ log_debugx("discovery session; digests disabled");
+ keys_add(response_keys, name, "None");
+ return;
+ }
+
+ which = login_list_prefers(value, "CRC32C", "None");
+ switch (which) {
+ case 1:
+ log_debugx("initiator prefers CRC32C "
+ "for header digest; we'll use it");
+ conn->conn_header_digest = CONN_DIGEST_CRC32C;
+ keys_add(response_keys, name, "CRC32C");
+ break;
+ case 2:
+ log_debugx("initiator prefers not to do "
+ "header digest; we'll comply");
+ keys_add(response_keys, name, "None");
+ break;
+ default:
+ log_warnx("initiator sent unrecognized "
+ "HeaderDigest value \"%s\"; will use None", value);
+ keys_add(response_keys, name, "None");
+ break;
+ }
+ } else if (strcmp(name, "DataDigest") == 0) {
+ if (conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY) {
+ log_debugx("discovery session; digests disabled");
+ keys_add(response_keys, name, "None");
+ return;
+ }
+
+ which = login_list_prefers(value, "CRC32C", "None");
+ switch (which) {
+ case 1:
+ log_debugx("initiator prefers CRC32C "
+ "for data digest; we'll use it");
+ conn->conn_data_digest = CONN_DIGEST_CRC32C;
+ keys_add(response_keys, name, "CRC32C");
+ break;
+ case 2:
+ log_debugx("initiator prefers not to do "
+ "data digest; we'll comply");
+ keys_add(response_keys, name, "None");
+ break;
+ default:
+ log_warnx("initiator sent unrecognized "
+ "DataDigest value \"%s\"; will use None", value);
+ keys_add(response_keys, name, "None");
+ break;
+ }
+ } else if (strcmp(name, "MaxConnections") == 0) {
+ keys_add(response_keys, name, "1");
+ } else if (strcmp(name, "InitialR2T") == 0) {
+ keys_add(response_keys, name, "Yes");
+ } else if (strcmp(name, "ImmediateData") == 0) {
+ if (conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY) {
+ log_debugx("discovery session; ImmediateData irrelevant");
+ keys_add(response_keys, name, "Irrelevant");
+ } else {
+ if (strcmp(value, "Yes") == 0) {
+ conn->conn_immediate_data = true;
+ keys_add(response_keys, name, "Yes");
+ } else {
+ conn->conn_immediate_data = false;
+ keys_add(response_keys, name, "No");
+ }
+ }
+ } else if (strcmp(name, "MaxRecvDataSegmentLength") == 0) {
+ tmp = strtoul(value, NULL, 10);
+ if (tmp <= 0) {
+ login_send_error(request, 0x02, 0x00);
+ log_errx(1, "received invalid "
+ "MaxRecvDataSegmentLength");
+ }
+ if (tmp > conn->conn_data_segment_limit) {
+ log_debugx("capping MaxRecvDataSegmentLength "
+ "from %zd to %zd", tmp, conn->conn_data_segment_limit);
+ tmp = conn->conn_data_segment_limit;
+ }
+ conn->conn_max_data_segment_length = tmp;
+ keys_add_int(response_keys, name, conn->conn_data_segment_limit);
+ } else if (strcmp(name, "MaxBurstLength") == 0) {
+ tmp = strtoul(value, NULL, 10);
+ if (tmp <= 0) {
+ login_send_error(request, 0x02, 0x00);
+ log_errx(1, "received invalid MaxBurstLength");
+ }
+ if (tmp > MAX_BURST_LENGTH) {
+ log_debugx("capping MaxBurstLength from %zd to %d",
+ tmp, MAX_BURST_LENGTH);
+ tmp = MAX_BURST_LENGTH;
+ }
+ conn->conn_max_burst_length = tmp;
+ keys_add(response_keys, name, value);
+ } else if (strcmp(name, "FirstBurstLength") == 0) {
+ tmp = strtoul(value, NULL, 10);
+ if (tmp <= 0) {
+ login_send_error(request, 0x02, 0x00);
+ log_errx(1, "received invalid "
+ "FirstBurstLength");
+ }
+ if (tmp > conn->conn_data_segment_limit) {
+ log_debugx("capping FirstBurstLength from %zd to %zd",
+ tmp, conn->conn_data_segment_limit);
+ tmp = conn->conn_data_segment_limit;
+ }
+ /*
+ * We don't pass the value to the kernel; it only enforces
+ * hardcoded limit anyway.
+ */
+ keys_add_int(response_keys, name, tmp);
+ } else if (strcmp(name, "DefaultTime2Wait") == 0) {
+ keys_add(response_keys, name, value);
+ } else if (strcmp(name, "DefaultTime2Retain") == 0) {
+ keys_add(response_keys, name, "0");
+ } else if (strcmp(name, "MaxOutstandingR2T") == 0) {
+ keys_add(response_keys, name, "1");
+ } else if (strcmp(name, "DataPDUInOrder") == 0) {
+ keys_add(response_keys, name, "Yes");
+ } else if (strcmp(name, "DataSequenceInOrder") == 0) {
+ keys_add(response_keys, name, "Yes");
+ } else if (strcmp(name, "ErrorRecoveryLevel") == 0) {
+ keys_add(response_keys, name, "0");
+ } else if (strcmp(name, "OFMarker") == 0) {
+ keys_add(response_keys, name, "No");
+ } else if (strcmp(name, "IFMarker") == 0) {
+ keys_add(response_keys, name, "No");
+ } else if (strcmp(name, "iSCSIProtocolLevel") == 0) {
+ tmp = strtoul(value, NULL, 10);
+ if (tmp > 2)
+ tmp = 2;
+ keys_add_int(response_keys, name, tmp);
+ } else {
+ log_debugx("unknown key \"%s\"; responding "
+ "with NotUnderstood", name);
+ keys_add(response_keys, name, "NotUnderstood");
+ }
+}
+
+static void
+login_redirect(struct pdu *request, const char *target_address)
+{
+ struct pdu *response;
+ struct iscsi_bhs_login_response *bhslr2;
+ struct keys *response_keys;
+
+ response = login_new_response(request);
+ login_set_csg(response, login_csg(request));
+ bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs;
+ bhslr2->bhslr_status_class = 0x01;
+ bhslr2->bhslr_status_detail = 0x01;
+
+ response_keys = keys_new();
+ keys_add(response_keys, "TargetAddress", target_address);
+
+ keys_save(response_keys, response);
+ pdu_send(response);
+ pdu_delete(response);
+ keys_delete(response_keys);
+}
+
+static bool
+login_portal_redirect(struct connection *conn, struct pdu *request)
+{
+ const struct portal_group *pg;
+
+ pg = conn->conn_portal->p_portal_group;
+ if (pg->pg_redirection == NULL)
+ return (false);
+
+ log_debugx("portal-group \"%s\" configured to redirect to %s",
+ pg->pg_name, pg->pg_redirection);
+ login_redirect(request, pg->pg_redirection);
+
+ return (true);
+}
+
+static bool
+login_target_redirect(struct connection *conn, struct pdu *request)
+{
+ const char *target_address;
+
+ assert(conn->conn_portal->p_portal_group->pg_redirection == NULL);
+
+ if (conn->conn_target == NULL)
+ return (false);
+
+ target_address = conn->conn_target->t_redirection;
+ if (target_address == NULL)
+ return (false);
+
+ log_debugx("target \"%s\" configured to redirect to %s",
+ conn->conn_target->t_name, target_address);
+ login_redirect(request, target_address);
+
+ return (true);
+}
+
+static void
+login_negotiate(struct connection *conn, struct pdu *request)
+{
+ struct pdu *response;
+ struct iscsi_bhs_login_response *bhslr2;
+ struct keys *request_keys, *response_keys;
+ int i;
+ bool redirected, skipped_security;
+
+ if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) {
+ /*
+ * Query the kernel for MaxDataSegmentLength it can handle.
+ * In case of offload, it depends on hardware capabilities.
+ */
+ assert(conn->conn_target != NULL);
+ kernel_limits(conn->conn_portal->p_portal_group->pg_offload,
+ &conn->conn_data_segment_limit);
+ } else {
+ conn->conn_data_segment_limit = MAX_DATA_SEGMENT_LENGTH;
+ }
+
+ if (request == NULL) {
+ log_debugx("beginning operational parameter negotiation; "
+ "waiting for Login PDU");
+ request = login_receive(conn, false);
+ skipped_security = false;
+ } else
+ skipped_security = true;
+
+ /*
+ * RFC 3720, 10.13.5. Status-Class and Status-Detail, says
+ * the redirection SHOULD be accepted by the initiator before
+ * authentication, but MUST be be accepted afterwards; that's
+ * why we're doing it here and not earlier.
+ */
+ redirected = login_target_redirect(conn, request);
+ if (redirected) {
+ log_debugx("initiator redirected; exiting");
+ exit(0);
+ }
+
+ request_keys = keys_new();
+ keys_load(request_keys, request);
+
+ response = login_new_response(request);
+ bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs;
+ bhslr2->bhslr_tsih = htons(0xbadd);
+ login_set_csg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION);
+ login_set_nsg(response, BHSLR_STAGE_FULL_FEATURE_PHASE);
+ response_keys = keys_new();
+
+ if (skipped_security &&
+ conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) {
+ if (conn->conn_target->t_alias != NULL)
+ keys_add(response_keys,
+ "TargetAlias", conn->conn_target->t_alias);
+ keys_add_int(response_keys, "TargetPortalGroupTag",
+ conn->conn_portal->p_portal_group->pg_tag);
+ }
+
+ for (i = 0; i < KEYS_MAX; i++) {
+ if (request_keys->keys_names[i] == NULL)
+ break;
+
+ login_negotiate_key(request, request_keys->keys_names[i],
+ request_keys->keys_values[i], skipped_security,
+ response_keys);
+ }
+
+ log_debugx("operational parameter negotiation done; "
+ "transitioning to Full Feature Phase");
+
+ keys_save(response_keys, response);
+ pdu_send(response);
+ pdu_delete(response);
+ keys_delete(response_keys);
+ pdu_delete(request);
+ keys_delete(request_keys);
+}
+
+static void
+login_wait_transition(struct connection *conn)
+{
+ struct pdu *request, *response;
+ struct iscsi_bhs_login_request *bhslr;
+
+ log_debugx("waiting for state transition request");
+ request = login_receive(conn, false);
+ bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
+ if ((bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) == 0) {
+ login_send_error(request, 0x02, 0x00);
+ log_errx(1, "got no \"T\" flag after answering AuthMethod");
+ }
+ pdu_delete(request);
+
+ log_debugx("got state transition request");
+ response = login_new_response(request);
+ login_set_nsg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION);
+ pdu_send(response);
+ pdu_delete(response);
+
+ login_negotiate(conn, NULL);
+}
+
+void
+login(struct connection *conn)
+{
+ struct pdu *request, *response;
+ struct iscsi_bhs_login_request *bhslr;
+ struct keys *request_keys, *response_keys;
+ struct auth_group *ag;
+ struct portal_group *pg;
+ const char *initiator_name, *initiator_alias, *session_type,
+ *target_name, *auth_method;
+ bool redirected, fail, trans;
+
+ /*
+ * Handle the initial Login Request - figure out required authentication
+ * method and either transition to the next phase, if no authentication
+ * is required, or call appropriate authentication code.
+ */
+ log_debugx("beginning Login Phase; waiting for Login PDU");
+ request = login_receive(conn, true);
+ bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
+ if (bhslr->bhslr_tsih != 0) {
+ login_send_error(request, 0x02, 0x0a);
+ log_errx(1, "received Login PDU with non-zero TSIH");
+ }
+
+ pg = conn->conn_portal->p_portal_group;
+
+ memcpy(conn->conn_initiator_isid, bhslr->bhslr_isid,
+ sizeof(conn->conn_initiator_isid));
+
+ /*
+ * XXX: Implement the C flag some day.
+ */
+ request_keys = keys_new();
+ keys_load(request_keys, request);
+
+ assert(conn->conn_initiator_name == NULL);
+ initiator_name = keys_find(request_keys, "InitiatorName");
+ if (initiator_name == NULL) {
+ login_send_error(request, 0x02, 0x07);
+ log_errx(1, "received Login PDU without InitiatorName");
+ }
+ if (valid_iscsi_name(initiator_name) == false) {
+ login_send_error(request, 0x02, 0x00);
+ log_errx(1, "received Login PDU with invalid InitiatorName");
+ }
+ conn->conn_initiator_name = checked_strdup(initiator_name);
+ log_set_peer_name(conn->conn_initiator_name);
+ setproctitle("%s (%s)", conn->conn_initiator_addr, conn->conn_initiator_name);
+
+ redirected = login_portal_redirect(conn, request);
+ if (redirected) {
+ log_debugx("initiator redirected; exiting");
+ exit(0);
+ }
+
+ initiator_alias = keys_find(request_keys, "InitiatorAlias");
+ if (initiator_alias != NULL)
+ conn->conn_initiator_alias = checked_strdup(initiator_alias);
+
+ assert(conn->conn_session_type == CONN_SESSION_TYPE_NONE);
+ session_type = keys_find(request_keys, "SessionType");
+ if (session_type != NULL) {
+ if (strcmp(session_type, "Normal") == 0) {
+ conn->conn_session_type = CONN_SESSION_TYPE_NORMAL;
+ } else if (strcmp(session_type, "Discovery") == 0) {
+ conn->conn_session_type = CONN_SESSION_TYPE_DISCOVERY;
+ } else {
+ login_send_error(request, 0x02, 0x00);
+ log_errx(1, "received Login PDU with invalid "
+ "SessionType \"%s\"", session_type);
+ }
+ } else
+ conn->conn_session_type = CONN_SESSION_TYPE_NORMAL;
+
+ assert(conn->conn_target == NULL);
+ if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) {
+ target_name = keys_find(request_keys, "TargetName");
+ if (target_name == NULL) {
+ login_send_error(request, 0x02, 0x07);
+ log_errx(1, "received Login PDU without TargetName");
+ }
+
+ conn->conn_port = port_find_in_pg(pg, target_name);
+ if (conn->conn_port == NULL) {
+ login_send_error(request, 0x02, 0x03);
+ log_errx(1, "requested target \"%s\" not found",
+ target_name);
+ }
+ conn->conn_target = conn->conn_port->p_target;
+ }
+
+ /*
+ * At this point we know what kind of authentication we need.
+ */
+ if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) {
+ ag = conn->conn_port->p_auth_group;
+ if (ag == NULL)
+ ag = conn->conn_target->t_auth_group;
+ if (ag->ag_name != NULL) {
+ log_debugx("initiator requests to connect "
+ "to target \"%s\"; auth-group \"%s\"",
+ conn->conn_target->t_name,
+ ag->ag_name);
+ } else {
+ log_debugx("initiator requests to connect "
+ "to target \"%s\"", conn->conn_target->t_name);
+ }
+ } else {
+ assert(conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY);
+ ag = pg->pg_discovery_auth_group;
+ if (ag->ag_name != NULL) {
+ log_debugx("initiator requests "
+ "discovery session; auth-group \"%s\"", ag->ag_name);
+ } else {
+ log_debugx("initiator requests discovery session");
+ }
+ }
+
+ if (ag->ag_type == AG_TYPE_DENY) {
+ login_send_error(request, 0x02, 0x01);
+ log_errx(1, "auth-type is \"deny\"");
+ }
+
+ if (ag->ag_type == AG_TYPE_UNKNOWN) {
+ /*
+ * This can happen with empty auth-group.
+ */
+ login_send_error(request, 0x02, 0x01);
+ log_errx(1, "auth-type not set, denying access");
+ }
+
+ /*
+ * Enforce initiator-name and initiator-portal.
+ */
+ if (auth_name_check(ag, initiator_name) != 0) {
+ login_send_error(request, 0x02, 0x02);
+ log_errx(1, "initiator does not match allowed initiator names");
+ }
+
+ if (auth_portal_check(ag, &conn->conn_initiator_sa) != 0) {
+ login_send_error(request, 0x02, 0x02);
+ log_errx(1, "initiator does not match allowed "
+ "initiator portals");
+ }
+
+ /*
+ * Let's see if the initiator intends to do any kind of authentication
+ * at all.
+ */
+ if (login_csg(request) == BHSLR_STAGE_OPERATIONAL_NEGOTIATION) {
+ if (ag->ag_type != AG_TYPE_NO_AUTHENTICATION) {
+ login_send_error(request, 0x02, 0x01);
+ log_errx(1, "initiator skipped the authentication, "
+ "but authentication is required");
+ }
+
+ keys_delete(request_keys);
+
+ log_debugx("initiator skipped the authentication, "
+ "and we don't need it; proceeding with negotiation");
+ login_negotiate(conn, request);
+ return;
+ }
+
+ fail = false;
+ response = login_new_response(request);
+ response_keys = keys_new();
+ trans = (bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) != 0;
+ auth_method = keys_find(request_keys, "AuthMethod");
+ if (ag->ag_type == AG_TYPE_NO_AUTHENTICATION) {
+ log_debugx("authentication not required");
+ if (auth_method == NULL ||
+ login_list_contains(auth_method, "None")) {
+ keys_add(response_keys, "AuthMethod", "None");
+ } else {
+ log_warnx("initiator requests "
+ "AuthMethod \"%s\" instead of \"None\"",
+ auth_method);
+ keys_add(response_keys, "AuthMethod", "Reject");
+ }
+ if (trans)
+ login_set_nsg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION);
+ } else {
+ log_debugx("CHAP authentication required");
+ if (auth_method == NULL ||
+ login_list_contains(auth_method, "CHAP")) {
+ keys_add(response_keys, "AuthMethod", "CHAP");
+ } else {
+ log_warnx("initiator requests unsupported "
+ "AuthMethod \"%s\" instead of \"CHAP\"",
+ auth_method);
+ keys_add(response_keys, "AuthMethod", "Reject");
+ fail = true;
+ }
+ }
+ if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) {
+ if (conn->conn_target->t_alias != NULL)
+ keys_add(response_keys,
+ "TargetAlias", conn->conn_target->t_alias);
+ keys_add_int(response_keys,
+ "TargetPortalGroupTag", pg->pg_tag);
+ }
+ keys_save(response_keys, response);
+
+ pdu_send(response);
+ pdu_delete(response);
+ keys_delete(response_keys);
+ pdu_delete(request);
+ keys_delete(request_keys);
+
+ if (fail) {
+ log_debugx("sent reject for AuthMethod; exiting");
+ exit(1);
+ }
+
+ if (ag->ag_type != AG_TYPE_NO_AUTHENTICATION) {
+ login_chap(conn, ag);
+ login_negotiate(conn, NULL);
+ } else if (trans) {
+ login_negotiate(conn, NULL);
+ } else {
+ login_wait_transition(conn);
+ }
+}
diff --git a/usr.sbin/ctld/parse.y b/usr.sbin/ctld/parse.y
new file mode 100644
index 0000000..afbf315
--- /dev/null
+++ b/usr.sbin/ctld/parse.y
@@ -0,0 +1,1150 @@
+%{
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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/queue.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ctld.h"
+
+extern FILE *yyin;
+extern char *yytext;
+extern int lineno;
+
+static struct conf *conf = NULL;
+static struct auth_group *auth_group = NULL;
+static struct portal_group *portal_group = NULL;
+static struct target *target = NULL;
+static struct lun *lun = NULL;
+
+extern void yyerror(const char *);
+extern int yylex(void);
+extern void yyrestart(FILE *);
+
+%}
+
+%token ALIAS AUTH_GROUP AUTH_TYPE BACKEND BLOCKSIZE CHAP CHAP_MUTUAL
+%token CLOSING_BRACKET CTL_LUN DEBUG DEVICE_ID DEVICE_TYPE
+%token DISCOVERY_AUTH_GROUP DISCOVERY_FILTER FOREIGN
+%token INITIATOR_NAME INITIATOR_PORTAL ISNS_SERVER ISNS_PERIOD ISNS_TIMEOUT
+%token LISTEN LISTEN_ISER LUN MAXPROC OFFLOAD OPENING_BRACKET OPTION
+%token PATH PIDFILE PORT PORTAL_GROUP REDIRECT SEMICOLON SERIAL SIZE STR
+%token TAG TARGET TIMEOUT
+
+%union
+{
+ char *str;
+}
+
+%token <str> STR
+
+%%
+
+statements:
+ |
+ statements statement
+ |
+ statements statement SEMICOLON
+ ;
+
+statement:
+ debug
+ |
+ timeout
+ |
+ maxproc
+ |
+ pidfile
+ |
+ isns_server
+ |
+ isns_period
+ |
+ isns_timeout
+ |
+ auth_group
+ |
+ portal_group
+ |
+ lun
+ |
+ target
+ ;
+
+debug: DEBUG STR
+ {
+ uint64_t tmp;
+
+ if (expand_number($2, &tmp) != 0) {
+ yyerror("invalid numeric value");
+ free($2);
+ return (1);
+ }
+
+ conf->conf_debug = tmp;
+ }
+ ;
+
+timeout: TIMEOUT STR
+ {
+ uint64_t tmp;
+
+ if (expand_number($2, &tmp) != 0) {
+ yyerror("invalid numeric value");
+ free($2);
+ return (1);
+ }
+
+ conf->conf_timeout = tmp;
+ }
+ ;
+
+maxproc: MAXPROC STR
+ {
+ uint64_t tmp;
+
+ if (expand_number($2, &tmp) != 0) {
+ yyerror("invalid numeric value");
+ free($2);
+ return (1);
+ }
+
+ conf->conf_maxproc = tmp;
+ }
+ ;
+
+pidfile: PIDFILE STR
+ {
+ if (conf->conf_pidfile_path != NULL) {
+ log_warnx("pidfile specified more than once");
+ free($2);
+ return (1);
+ }
+ conf->conf_pidfile_path = $2;
+ }
+ ;
+
+isns_server: ISNS_SERVER STR
+ {
+ int error;
+
+ error = isns_new(conf, $2);
+ free($2);
+ if (error != 0)
+ return (1);
+ }
+ ;
+
+isns_period: ISNS_PERIOD STR
+ {
+ uint64_t tmp;
+
+ if (expand_number($2, &tmp) != 0) {
+ yyerror("invalid numeric value");
+ free($2);
+ return (1);
+ }
+
+ conf->conf_isns_period = tmp;
+ }
+ ;
+
+isns_timeout: ISNS_TIMEOUT STR
+ {
+ uint64_t tmp;
+
+ if (expand_number($2, &tmp) != 0) {
+ yyerror("invalid numeric value");
+ free($2);
+ return (1);
+ }
+
+ conf->conf_isns_timeout = tmp;
+ }
+ ;
+
+auth_group: AUTH_GROUP auth_group_name
+ OPENING_BRACKET auth_group_entries CLOSING_BRACKET
+ {
+ auth_group = NULL;
+ }
+ ;
+
+auth_group_name: STR
+ {
+ /*
+ * Make it possible to redefine default
+ * auth-group. but only once.
+ */
+ if (strcmp($1, "default") == 0 &&
+ conf->conf_default_ag_defined == false) {
+ auth_group = auth_group_find(conf, $1);
+ conf->conf_default_ag_defined = true;
+ } else {
+ auth_group = auth_group_new(conf, $1);
+ }
+ free($1);
+ if (auth_group == NULL)
+ return (1);
+ }
+ ;
+
+auth_group_entries:
+ |
+ auth_group_entries auth_group_entry
+ |
+ auth_group_entries auth_group_entry SEMICOLON
+ ;
+
+auth_group_entry:
+ auth_group_auth_type
+ |
+ auth_group_chap
+ |
+ auth_group_chap_mutual
+ |
+ auth_group_initiator_name
+ |
+ auth_group_initiator_portal
+ ;
+
+auth_group_auth_type: AUTH_TYPE STR
+ {
+ int error;
+
+ error = auth_group_set_type(auth_group, $2);
+ free($2);
+ if (error != 0)
+ return (1);
+ }
+ ;
+
+auth_group_chap: CHAP STR STR
+ {
+ const struct auth *ca;
+
+ ca = auth_new_chap(auth_group, $2, $3);
+ free($2);
+ free($3);
+ if (ca == NULL)
+ return (1);
+ }
+ ;
+
+auth_group_chap_mutual: CHAP_MUTUAL STR STR STR STR
+ {
+ const struct auth *ca;
+
+ ca = auth_new_chap_mutual(auth_group, $2, $3, $4, $5);
+ free($2);
+ free($3);
+ free($4);
+ free($5);
+ if (ca == NULL)
+ return (1);
+ }
+ ;
+
+auth_group_initiator_name: INITIATOR_NAME STR
+ {
+ const struct auth_name *an;
+
+ an = auth_name_new(auth_group, $2);
+ free($2);
+ if (an == NULL)
+ return (1);
+ }
+ ;
+
+auth_group_initiator_portal: INITIATOR_PORTAL STR
+ {
+ const struct auth_portal *ap;
+
+ ap = auth_portal_new(auth_group, $2);
+ free($2);
+ if (ap == NULL)
+ return (1);
+ }
+ ;
+
+portal_group: PORTAL_GROUP portal_group_name
+ OPENING_BRACKET portal_group_entries CLOSING_BRACKET
+ {
+ portal_group = NULL;
+ }
+ ;
+
+portal_group_name: STR
+ {
+ /*
+ * Make it possible to redefine default
+ * portal-group. but only once.
+ */
+ if (strcmp($1, "default") == 0 &&
+ conf->conf_default_pg_defined == false) {
+ portal_group = portal_group_find(conf, $1);
+ conf->conf_default_pg_defined = true;
+ } else {
+ portal_group = portal_group_new(conf, $1);
+ }
+ free($1);
+ if (portal_group == NULL)
+ return (1);
+ }
+ ;
+
+portal_group_entries:
+ |
+ portal_group_entries portal_group_entry
+ |
+ portal_group_entries portal_group_entry SEMICOLON
+ ;
+
+portal_group_entry:
+ portal_group_discovery_auth_group
+ |
+ portal_group_discovery_filter
+ |
+ portal_group_foreign
+ |
+ portal_group_listen
+ |
+ portal_group_listen_iser
+ |
+ portal_group_offload
+ |
+ portal_group_option
+ |
+ portal_group_redirect
+ |
+ portal_group_tag
+ ;
+
+portal_group_discovery_auth_group: DISCOVERY_AUTH_GROUP STR
+ {
+ if (portal_group->pg_discovery_auth_group != NULL) {
+ log_warnx("discovery-auth-group for portal-group "
+ "\"%s\" specified more than once",
+ portal_group->pg_name);
+ return (1);
+ }
+ portal_group->pg_discovery_auth_group =
+ auth_group_find(conf, $2);
+ if (portal_group->pg_discovery_auth_group == NULL) {
+ log_warnx("unknown discovery-auth-group \"%s\" "
+ "for portal-group \"%s\"",
+ $2, portal_group->pg_name);
+ return (1);
+ }
+ free($2);
+ }
+ ;
+
+portal_group_discovery_filter: DISCOVERY_FILTER STR
+ {
+ int error;
+
+ error = portal_group_set_filter(portal_group, $2);
+ free($2);
+ if (error != 0)
+ return (1);
+ }
+ ;
+
+portal_group_foreign: FOREIGN
+ {
+
+ portal_group->pg_foreign = 1;
+ }
+ ;
+
+portal_group_listen: LISTEN STR
+ {
+ int error;
+
+ error = portal_group_add_listen(portal_group, $2, false);
+ free($2);
+ if (error != 0)
+ return (1);
+ }
+ ;
+
+portal_group_listen_iser: LISTEN_ISER STR
+ {
+ int error;
+
+ error = portal_group_add_listen(portal_group, $2, true);
+ free($2);
+ if (error != 0)
+ return (1);
+ }
+ ;
+
+portal_group_offload: OFFLOAD STR
+ {
+ int error;
+
+ error = portal_group_set_offload(portal_group, $2);
+ free($2);
+ if (error != 0)
+ return (1);
+ }
+ ;
+
+portal_group_option: OPTION STR STR
+ {
+ struct option *o;
+
+ o = option_new(&portal_group->pg_options, $2, $3);
+ free($2);
+ free($3);
+ if (o == NULL)
+ return (1);
+ }
+ ;
+
+portal_group_redirect: REDIRECT STR
+ {
+ int error;
+
+ error = portal_group_set_redirection(portal_group, $2);
+ free($2);
+ if (error != 0)
+ return (1);
+ }
+ ;
+
+portal_group_tag: TAG STR
+ {
+ uint64_t tmp;
+
+ if (expand_number($2, &tmp) != 0) {
+ yyerror("invalid numeric value");
+ free($2);
+ return (1);
+ }
+
+ portal_group->pg_tag = tmp;
+ }
+ ;
+
+lun: LUN lun_name
+ OPENING_BRACKET lun_entries CLOSING_BRACKET
+ {
+ lun = NULL;
+ }
+ ;
+
+lun_name: STR
+ {
+ lun = lun_new(conf, $1);
+ free($1);
+ if (lun == NULL)
+ return (1);
+ }
+ ;
+
+target: TARGET target_name
+ OPENING_BRACKET target_entries CLOSING_BRACKET
+ {
+ target = NULL;
+ }
+ ;
+
+target_name: STR
+ {
+ target = target_new(conf, $1);
+ free($1);
+ if (target == NULL)
+ return (1);
+ }
+ ;
+
+target_entries:
+ |
+ target_entries target_entry
+ |
+ target_entries target_entry SEMICOLON
+ ;
+
+target_entry:
+ target_alias
+ |
+ target_auth_group
+ |
+ target_auth_type
+ |
+ target_chap
+ |
+ target_chap_mutual
+ |
+ target_initiator_name
+ |
+ target_initiator_portal
+ |
+ target_portal_group
+ |
+ target_port
+ |
+ target_redirect
+ |
+ target_lun
+ |
+ target_lun_ref
+ ;
+
+target_alias: ALIAS STR
+ {
+ if (target->t_alias != NULL) {
+ log_warnx("alias for target \"%s\" "
+ "specified more than once", target->t_name);
+ return (1);
+ }
+ target->t_alias = $2;
+ }
+ ;
+
+target_auth_group: AUTH_GROUP STR
+ {
+ if (target->t_auth_group != NULL) {
+ if (target->t_auth_group->ag_name != NULL)
+ log_warnx("auth-group for target \"%s\" "
+ "specified more than once", target->t_name);
+ else
+ log_warnx("cannot use both auth-group and explicit "
+ "authorisations for target \"%s\"",
+ target->t_name);
+ return (1);
+ }
+ target->t_auth_group = auth_group_find(conf, $2);
+ if (target->t_auth_group == NULL) {
+ log_warnx("unknown auth-group \"%s\" for target "
+ "\"%s\"", $2, target->t_name);
+ return (1);
+ }
+ free($2);
+ }
+ ;
+
+target_auth_type: AUTH_TYPE STR
+ {
+ int error;
+
+ if (target->t_auth_group != NULL) {
+ if (target->t_auth_group->ag_name != NULL) {
+ log_warnx("cannot use both auth-group and "
+ "auth-type for target \"%s\"",
+ target->t_name);
+ return (1);
+ }
+ } else {
+ target->t_auth_group = auth_group_new(conf, NULL);
+ if (target->t_auth_group == NULL) {
+ free($2);
+ return (1);
+ }
+ target->t_auth_group->ag_target = target;
+ }
+ error = auth_group_set_type(target->t_auth_group, $2);
+ free($2);
+ if (error != 0)
+ return (1);
+ }
+ ;
+
+target_chap: CHAP STR STR
+ {
+ const struct auth *ca;
+
+ if (target->t_auth_group != NULL) {
+ if (target->t_auth_group->ag_name != NULL) {
+ log_warnx("cannot use both auth-group and "
+ "chap for target \"%s\"",
+ target->t_name);
+ free($2);
+ free($3);
+ return (1);
+ }
+ } else {
+ target->t_auth_group = auth_group_new(conf, NULL);
+ if (target->t_auth_group == NULL) {
+ free($2);
+ free($3);
+ return (1);
+ }
+ target->t_auth_group->ag_target = target;
+ }
+ ca = auth_new_chap(target->t_auth_group, $2, $3);
+ free($2);
+ free($3);
+ if (ca == NULL)
+ return (1);
+ }
+ ;
+
+target_chap_mutual: CHAP_MUTUAL STR STR STR STR
+ {
+ const struct auth *ca;
+
+ if (target->t_auth_group != NULL) {
+ if (target->t_auth_group->ag_name != NULL) {
+ log_warnx("cannot use both auth-group and "
+ "chap-mutual for target \"%s\"",
+ target->t_name);
+ free($2);
+ free($3);
+ free($4);
+ free($5);
+ return (1);
+ }
+ } else {
+ target->t_auth_group = auth_group_new(conf, NULL);
+ if (target->t_auth_group == NULL) {
+ free($2);
+ free($3);
+ free($4);
+ free($5);
+ return (1);
+ }
+ target->t_auth_group->ag_target = target;
+ }
+ ca = auth_new_chap_mutual(target->t_auth_group,
+ $2, $3, $4, $5);
+ free($2);
+ free($3);
+ free($4);
+ free($5);
+ if (ca == NULL)
+ return (1);
+ }
+ ;
+
+target_initiator_name: INITIATOR_NAME STR
+ {
+ const struct auth_name *an;
+
+ if (target->t_auth_group != NULL) {
+ if (target->t_auth_group->ag_name != NULL) {
+ log_warnx("cannot use both auth-group and "
+ "initiator-name for target \"%s\"",
+ target->t_name);
+ free($2);
+ return (1);
+ }
+ } else {
+ target->t_auth_group = auth_group_new(conf, NULL);
+ if (target->t_auth_group == NULL) {
+ free($2);
+ return (1);
+ }
+ target->t_auth_group->ag_target = target;
+ }
+ an = auth_name_new(target->t_auth_group, $2);
+ free($2);
+ if (an == NULL)
+ return (1);
+ }
+ ;
+
+target_initiator_portal: INITIATOR_PORTAL STR
+ {
+ const struct auth_portal *ap;
+
+ if (target->t_auth_group != NULL) {
+ if (target->t_auth_group->ag_name != NULL) {
+ log_warnx("cannot use both auth-group and "
+ "initiator-portal for target \"%s\"",
+ target->t_name);
+ free($2);
+ return (1);
+ }
+ } else {
+ target->t_auth_group = auth_group_new(conf, NULL);
+ if (target->t_auth_group == NULL) {
+ free($2);
+ return (1);
+ }
+ target->t_auth_group->ag_target = target;
+ }
+ ap = auth_portal_new(target->t_auth_group, $2);
+ free($2);
+ if (ap == NULL)
+ return (1);
+ }
+ ;
+
+target_portal_group: PORTAL_GROUP STR STR
+ {
+ struct portal_group *tpg;
+ struct auth_group *tag;
+ struct port *tp;
+
+ tpg = portal_group_find(conf, $2);
+ if (tpg == NULL) {
+ log_warnx("unknown portal-group \"%s\" for target "
+ "\"%s\"", $2, target->t_name);
+ free($2);
+ free($3);
+ return (1);
+ }
+ tag = auth_group_find(conf, $3);
+ if (tag == NULL) {
+ log_warnx("unknown auth-group \"%s\" for target "
+ "\"%s\"", $3, target->t_name);
+ free($2);
+ free($3);
+ return (1);
+ }
+ tp = port_new(conf, target, tpg);
+ if (tp == NULL) {
+ log_warnx("can't link portal-group \"%s\" to target "
+ "\"%s\"", $2, target->t_name);
+ free($2);
+ return (1);
+ }
+ tp->p_auth_group = tag;
+ free($2);
+ free($3);
+ }
+ | PORTAL_GROUP STR
+ {
+ struct portal_group *tpg;
+ struct port *tp;
+
+ tpg = portal_group_find(conf, $2);
+ if (tpg == NULL) {
+ log_warnx("unknown portal-group \"%s\" for target "
+ "\"%s\"", $2, target->t_name);
+ free($2);
+ return (1);
+ }
+ tp = port_new(conf, target, tpg);
+ if (tp == NULL) {
+ log_warnx("can't link portal-group \"%s\" to target "
+ "\"%s\"", $2, target->t_name);
+ free($2);
+ return (1);
+ }
+ free($2);
+ }
+ ;
+
+target_port: PORT STR
+ {
+ struct pport *pp;
+ struct port *tp;
+
+ pp = pport_find(conf, $2);
+ if (pp == NULL) {
+ log_warnx("unknown port \"%s\" for target \"%s\"",
+ $2, target->t_name);
+ free($2);
+ return (1);
+ }
+ if (!TAILQ_EMPTY(&pp->pp_ports)) {
+ log_warnx("can't link port \"%s\" to target \"%s\", "
+ "port already linked to some target",
+ $2, target->t_name);
+ free($2);
+ return (1);
+ }
+ tp = port_new_pp(conf, target, pp);
+ if (tp == NULL) {
+ log_warnx("can't link port \"%s\" to target \"%s\"",
+ $2, target->t_name);
+ free($2);
+ return (1);
+ }
+ free($2);
+ }
+ ;
+
+target_redirect: REDIRECT STR
+ {
+ int error;
+
+ error = target_set_redirection(target, $2);
+ free($2);
+ if (error != 0)
+ return (1);
+ }
+ ;
+
+target_lun: LUN lun_number
+ OPENING_BRACKET lun_entries CLOSING_BRACKET
+ {
+ lun = NULL;
+ }
+ ;
+
+lun_number: STR
+ {
+ uint64_t tmp;
+ int ret;
+ char *name;
+
+ if (expand_number($1, &tmp) != 0) {
+ yyerror("invalid numeric value");
+ free($1);
+ return (1);
+ }
+
+ ret = asprintf(&name, "%s,lun,%ju", target->t_name, tmp);
+ if (ret <= 0)
+ log_err(1, "asprintf");
+ lun = lun_new(conf, name);
+ if (lun == NULL)
+ return (1);
+
+ lun_set_scsiname(lun, name);
+ target->t_luns[tmp] = lun;
+ }
+ ;
+
+target_lun_ref: LUN STR STR
+ {
+ uint64_t tmp;
+
+ if (expand_number($2, &tmp) != 0) {
+ yyerror("invalid numeric value");
+ free($2);
+ free($3);
+ return (1);
+ }
+ free($2);
+
+ lun = lun_find(conf, $3);
+ free($3);
+ if (lun == NULL)
+ return (1);
+
+ target->t_luns[tmp] = lun;
+ }
+ ;
+
+lun_entries:
+ |
+ lun_entries lun_entry
+ |
+ lun_entries lun_entry SEMICOLON
+ ;
+
+lun_entry:
+ lun_backend
+ |
+ lun_blocksize
+ |
+ lun_device_id
+ |
+ lun_device_type
+ |
+ lun_ctl_lun
+ |
+ lun_option
+ |
+ lun_path
+ |
+ lun_serial
+ |
+ lun_size
+ ;
+
+lun_backend: BACKEND STR
+ {
+ if (lun->l_backend != NULL) {
+ log_warnx("backend for lun \"%s\" "
+ "specified more than once",
+ lun->l_name);
+ free($2);
+ return (1);
+ }
+ lun_set_backend(lun, $2);
+ free($2);
+ }
+ ;
+
+lun_blocksize: BLOCKSIZE STR
+ {
+ uint64_t tmp;
+
+ if (expand_number($2, &tmp) != 0) {
+ yyerror("invalid numeric value");
+ free($2);
+ return (1);
+ }
+
+ if (lun->l_blocksize != 0) {
+ log_warnx("blocksize for lun \"%s\" "
+ "specified more than once",
+ lun->l_name);
+ return (1);
+ }
+ lun_set_blocksize(lun, tmp);
+ }
+ ;
+
+lun_device_id: DEVICE_ID STR
+ {
+ if (lun->l_device_id != NULL) {
+ log_warnx("device_id for lun \"%s\" "
+ "specified more than once",
+ lun->l_name);
+ free($2);
+ return (1);
+ }
+ lun_set_device_id(lun, $2);
+ free($2);
+ }
+ ;
+
+lun_device_type: DEVICE_TYPE STR
+ {
+ uint64_t tmp;
+
+ if (strcasecmp($2, "disk") == 0 ||
+ strcasecmp($2, "direct") == 0)
+ tmp = 0;
+ else if (strcasecmp($2, "processor") == 0)
+ tmp = 3;
+ else if (strcasecmp($2, "cd") == 0 ||
+ strcasecmp($2, "cdrom") == 0 ||
+ strcasecmp($2, "dvd") == 0 ||
+ strcasecmp($2, "dvdrom") == 0)
+ tmp = 5;
+ else if (expand_number($2, &tmp) != 0 ||
+ tmp > 15) {
+ yyerror("invalid numeric value");
+ free($2);
+ return (1);
+ }
+
+ lun_set_device_type(lun, tmp);
+ }
+ ;
+
+lun_ctl_lun: CTL_LUN STR
+ {
+ uint64_t tmp;
+
+ if (expand_number($2, &tmp) != 0) {
+ yyerror("invalid numeric value");
+ free($2);
+ return (1);
+ }
+
+ if (lun->l_ctl_lun >= 0) {
+ log_warnx("ctl_lun for lun \"%s\" "
+ "specified more than once",
+ lun->l_name);
+ return (1);
+ }
+ lun_set_ctl_lun(lun, tmp);
+ }
+ ;
+
+lun_option: OPTION STR STR
+ {
+ struct option *o;
+
+ o = option_new(&lun->l_options, $2, $3);
+ free($2);
+ free($3);
+ if (o == NULL)
+ return (1);
+ }
+ ;
+
+lun_path: PATH STR
+ {
+ if (lun->l_path != NULL) {
+ log_warnx("path for lun \"%s\" "
+ "specified more than once",
+ lun->l_name);
+ free($2);
+ return (1);
+ }
+ lun_set_path(lun, $2);
+ free($2);
+ }
+ ;
+
+lun_serial: SERIAL STR
+ {
+ if (lun->l_serial != NULL) {
+ log_warnx("serial for lun \"%s\" "
+ "specified more than once",
+ lun->l_name);
+ free($2);
+ return (1);
+ }
+ lun_set_serial(lun, $2);
+ free($2);
+ }
+ ;
+
+lun_size: SIZE STR
+ {
+ uint64_t tmp;
+
+ if (expand_number($2, &tmp) != 0) {
+ yyerror("invalid numeric value");
+ free($2);
+ return (1);
+ }
+
+ if (lun->l_size != 0) {
+ log_warnx("size for lun \"%s\" "
+ "specified more than once",
+ lun->l_name);
+ return (1);
+ }
+ lun_set_size(lun, tmp);
+ }
+ ;
+%%
+
+void
+yyerror(const char *str)
+{
+
+ log_warnx("error in configuration file at line %d near '%s': %s",
+ lineno, yytext, str);
+}
+
+static void
+check_perms(const char *path)
+{
+ struct stat sb;
+ int error;
+
+ error = stat(path, &sb);
+ if (error != 0) {
+ log_warn("stat");
+ return;
+ }
+ if (sb.st_mode & S_IWOTH) {
+ log_warnx("%s is world-writable", path);
+ } else if (sb.st_mode & S_IROTH) {
+ log_warnx("%s is world-readable", path);
+ } else if (sb.st_mode & S_IXOTH) {
+ /*
+ * Ok, this one doesn't matter, but still do it,
+ * just for consistency.
+ */
+ log_warnx("%s is world-executable", path);
+ }
+
+ /*
+ * XXX: Should we also check for owner != 0?
+ */
+}
+
+struct conf *
+conf_new_from_file(const char *path, struct conf *oldconf)
+{
+ struct auth_group *ag;
+ struct portal_group *pg;
+ struct pport *pp;
+ int error;
+
+ log_debugx("obtaining configuration from %s", path);
+
+ conf = conf_new();
+
+ TAILQ_FOREACH(pp, &oldconf->conf_pports, pp_next)
+ pport_copy(pp, conf);
+
+ ag = auth_group_new(conf, "default");
+ assert(ag != NULL);
+
+ ag = auth_group_new(conf, "no-authentication");
+ assert(ag != NULL);
+ ag->ag_type = AG_TYPE_NO_AUTHENTICATION;
+
+ ag = auth_group_new(conf, "no-access");
+ assert(ag != NULL);
+ ag->ag_type = AG_TYPE_DENY;
+
+ pg = portal_group_new(conf, "default");
+ assert(pg != NULL);
+
+ yyin = fopen(path, "r");
+ if (yyin == NULL) {
+ log_warn("unable to open configuration file %s", path);
+ conf_delete(conf);
+ return (NULL);
+ }
+ check_perms(path);
+ lineno = 1;
+ yyrestart(yyin);
+ error = yyparse();
+ auth_group = NULL;
+ portal_group = NULL;
+ target = NULL;
+ lun = NULL;
+ fclose(yyin);
+ if (error != 0) {
+ conf_delete(conf);
+ return (NULL);
+ }
+
+ if (conf->conf_default_ag_defined == false) {
+ log_debugx("auth-group \"default\" not defined; "
+ "going with defaults");
+ ag = auth_group_find(conf, "default");
+ assert(ag != NULL);
+ ag->ag_type = AG_TYPE_DENY;
+ }
+
+ if (conf->conf_default_pg_defined == false) {
+ log_debugx("portal-group \"default\" not defined; "
+ "going with defaults");
+ pg = portal_group_find(conf, "default");
+ assert(pg != NULL);
+ portal_group_add_listen(pg, "0.0.0.0:3260", false);
+ portal_group_add_listen(pg, "[::]:3260", false);
+ }
+
+ conf->conf_kernel_port_on = true;
+
+ error = conf_verify(conf);
+ if (error != 0) {
+ conf_delete(conf);
+ return (NULL);
+ }
+
+ return (conf);
+}
diff --git a/usr.sbin/ctld/pdu.c b/usr.sbin/ctld/pdu.c
new file mode 100644
index 0000000..be3598e
--- /dev/null
+++ b/usr.sbin/ctld/pdu.c
@@ -0,0 +1,264 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "ctld.h"
+#include "iscsi_proto.h"
+
+#ifdef ICL_KERNEL_PROXY
+#include <sys/ioctl.h>
+#endif
+
+extern bool proxy_mode;
+
+static int
+pdu_ahs_length(const struct pdu *pdu)
+{
+
+ return (pdu->pdu_bhs->bhs_total_ahs_len * 4);
+}
+
+static int
+pdu_data_segment_length(const struct pdu *pdu)
+{
+ uint32_t len = 0;
+
+ len += pdu->pdu_bhs->bhs_data_segment_len[0];
+ len <<= 8;
+ len += pdu->pdu_bhs->bhs_data_segment_len[1];
+ len <<= 8;
+ len += pdu->pdu_bhs->bhs_data_segment_len[2];
+
+ return (len);
+}
+
+static void
+pdu_set_data_segment_length(struct pdu *pdu, uint32_t len)
+{
+
+ pdu->pdu_bhs->bhs_data_segment_len[2] = len;
+ pdu->pdu_bhs->bhs_data_segment_len[1] = len >> 8;
+ pdu->pdu_bhs->bhs_data_segment_len[0] = len >> 16;
+}
+
+struct pdu *
+pdu_new(struct connection *conn)
+{
+ struct pdu *pdu;
+
+ pdu = calloc(sizeof(*pdu), 1);
+ if (pdu == NULL)
+ log_err(1, "calloc");
+
+ pdu->pdu_bhs = calloc(sizeof(*pdu->pdu_bhs), 1);
+ if (pdu->pdu_bhs == NULL)
+ log_err(1, "calloc");
+
+ pdu->pdu_connection = conn;
+
+ return (pdu);
+}
+
+struct pdu *
+pdu_new_response(struct pdu *request)
+{
+
+ return (pdu_new(request->pdu_connection));
+}
+
+#ifdef ICL_KERNEL_PROXY
+
+static void
+pdu_receive_proxy(struct pdu *pdu)
+{
+ size_t len;
+
+ assert(proxy_mode);
+
+ kernel_receive(pdu);
+
+ len = pdu_ahs_length(pdu);
+ if (len > 0)
+ log_errx(1, "protocol error: non-empty AHS");
+
+ len = pdu_data_segment_length(pdu);
+ assert(len <= MAX_DATA_SEGMENT_LENGTH);
+ pdu->pdu_data_len = len;
+}
+
+static void
+pdu_send_proxy(struct pdu *pdu)
+{
+
+ assert(proxy_mode);
+
+ pdu_set_data_segment_length(pdu, pdu->pdu_data_len);
+ kernel_send(pdu);
+}
+
+#endif /* ICL_KERNEL_PROXY */
+
+static size_t
+pdu_padding(const struct pdu *pdu)
+{
+
+ if ((pdu->pdu_data_len % 4) != 0)
+ return (4 - (pdu->pdu_data_len % 4));
+
+ return (0);
+}
+
+static void
+pdu_read(int fd, char *data, size_t len)
+{
+ ssize_t ret;
+
+ while (len > 0) {
+ ret = read(fd, data, len);
+ if (ret < 0) {
+ if (timed_out())
+ log_errx(1, "exiting due to timeout");
+ log_err(1, "read");
+ } else if (ret == 0)
+ log_errx(1, "read: connection lost");
+ len -= ret;
+ data += ret;
+ }
+}
+
+void
+pdu_receive(struct pdu *pdu)
+{
+ size_t len, padding;
+ char dummy[4];
+
+#ifdef ICL_KERNEL_PROXY
+ if (proxy_mode)
+ return (pdu_receive_proxy(pdu));
+#endif
+
+ assert(proxy_mode == false);
+
+ pdu_read(pdu->pdu_connection->conn_socket,
+ (char *)pdu->pdu_bhs, sizeof(*pdu->pdu_bhs));
+
+ len = pdu_ahs_length(pdu);
+ if (len > 0)
+ log_errx(1, "protocol error: non-empty AHS");
+
+ len = pdu_data_segment_length(pdu);
+ if (len > 0) {
+ if (len > MAX_DATA_SEGMENT_LENGTH) {
+ log_errx(1, "protocol error: received PDU "
+ "with DataSegmentLength exceeding %d",
+ MAX_DATA_SEGMENT_LENGTH);
+ }
+
+ pdu->pdu_data_len = len;
+ pdu->pdu_data = malloc(len);
+ if (pdu->pdu_data == NULL)
+ log_err(1, "malloc");
+
+ pdu_read(pdu->pdu_connection->conn_socket,
+ (char *)pdu->pdu_data, pdu->pdu_data_len);
+
+ padding = pdu_padding(pdu);
+ if (padding != 0) {
+ assert(padding < sizeof(dummy));
+ pdu_read(pdu->pdu_connection->conn_socket,
+ (char *)dummy, padding);
+ }
+ }
+}
+
+void
+pdu_send(struct pdu *pdu)
+{
+ ssize_t ret, total_len;
+ size_t padding;
+ uint32_t zero = 0;
+ struct iovec iov[3];
+ int iovcnt;
+
+#ifdef ICL_KERNEL_PROXY
+ if (proxy_mode)
+ return (pdu_send_proxy(pdu));
+#endif
+
+ assert(proxy_mode == false);
+
+ pdu_set_data_segment_length(pdu, pdu->pdu_data_len);
+ iov[0].iov_base = pdu->pdu_bhs;
+ iov[0].iov_len = sizeof(*pdu->pdu_bhs);
+ total_len = iov[0].iov_len;
+ iovcnt = 1;
+
+ if (pdu->pdu_data_len > 0) {
+ iov[1].iov_base = pdu->pdu_data;
+ iov[1].iov_len = pdu->pdu_data_len;
+ total_len += iov[1].iov_len;
+ iovcnt = 2;
+
+ padding = pdu_padding(pdu);
+ if (padding > 0) {
+ assert(padding < sizeof(zero));
+ iov[2].iov_base = &zero;
+ iov[2].iov_len = padding;
+ total_len += iov[2].iov_len;
+ iovcnt = 3;
+ }
+ }
+
+ ret = writev(pdu->pdu_connection->conn_socket, iov, iovcnt);
+ if (ret < 0) {
+ if (timed_out())
+ log_errx(1, "exiting due to timeout");
+ log_err(1, "writev");
+ }
+ if (ret != total_len)
+ log_errx(1, "short write");
+}
+
+void
+pdu_delete(struct pdu *pdu)
+{
+
+ free(pdu->pdu_data);
+ free(pdu->pdu_bhs);
+ free(pdu);
+}
diff --git a/usr.sbin/ctld/token.l b/usr.sbin/ctld/token.l
new file mode 100644
index 0000000..e8cbf3b
--- /dev/null
+++ b/usr.sbin/ctld/token.l
@@ -0,0 +1,96 @@
+%{
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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 <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "y.tab.h"
+
+int lineno;
+
+#define YY_DECL int yylex(void)
+extern int yylex(void);
+
+%}
+
+%option noinput
+%option nounput
+
+%%
+alias { return ALIAS; }
+auth-group { return AUTH_GROUP; }
+auth-type { return AUTH_TYPE; }
+backend { return BACKEND; }
+blocksize { return BLOCKSIZE; }
+chap { return CHAP; }
+chap-mutual { return CHAP_MUTUAL; }
+ctl-lun { return CTL_LUN; }
+debug { return DEBUG; }
+device-id { return DEVICE_ID; }
+device-type { return DEVICE_TYPE; }
+discovery-auth-group { return DISCOVERY_AUTH_GROUP; }
+discovery-filter { return DISCOVERY_FILTER; }
+foreign { return FOREIGN; }
+initiator-name { return INITIATOR_NAME; }
+initiator-portal { return INITIATOR_PORTAL; }
+listen { return LISTEN; }
+listen-iser { return LISTEN_ISER; }
+lun { return LUN; }
+maxproc { return MAXPROC; }
+offload { return OFFLOAD; }
+option { return OPTION; }
+path { return PATH; }
+pidfile { return PIDFILE; }
+isns-server { return ISNS_SERVER; }
+isns-period { return ISNS_PERIOD; }
+isns-timeout { return ISNS_TIMEOUT; }
+port { return PORT; }
+portal-group { return PORTAL_GROUP; }
+redirect { return REDIRECT; }
+serial { return SERIAL; }
+size { return SIZE; }
+tag { return TAG; }
+target { return TARGET; }
+timeout { return TIMEOUT; }
+\"[^"]+\" { yylval.str = strndup(yytext + 1,
+ strlen(yytext) - 2); return STR; }
+[a-zA-Z0-9\.\-@_/\:\[\]]+ { yylval.str = strdup(yytext); return STR; }
+\{ { return OPENING_BRACKET; }
+\} { return CLOSING_BRACKET; }
+#.*$ /* ignore comments */;
+\r\n { lineno++; }
+\n { lineno++; }
+; { return SEMICOLON; }
+[ \t]+ /* ignore whitespace */;
+. { yylval.str = strdup(yytext); return STR; }
+%%
diff --git a/usr.sbin/ctm/Makefile b/usr.sbin/ctm/Makefile
new file mode 100644
index 0000000..630aab0
--- /dev/null
+++ b/usr.sbin/ctm/Makefile
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+SUBDIR= ctm ctm_rmail ctm_smail ctm_dequeue
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/ctm/Makefile.inc b/usr.sbin/ctm/Makefile.inc
new file mode 100644
index 0000000..c6c2c5a
--- /dev/null
+++ b/usr.sbin/ctm/Makefile.inc
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+.if exists(${.CURDIR}/../../Makefile.inc)
+.include "${.CURDIR}/../../Makefile.inc"
+.endif
diff --git a/usr.sbin/ctm/README b/usr.sbin/ctm/README
new file mode 100644
index 0000000..94c779a
--- /dev/null
+++ b/usr.sbin/ctm/README
@@ -0,0 +1,85 @@
+# ----------------------------------------------------------------------------
+# "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$
+#
+
+What will I not find in this file ?
+-----------------------------------
+Instructions on how to obtain FreeBSD via CTM.
+Contact <CTM@FreeBSD.org> for that.
+
+What is CTM ?
+-------------
+CTM was originally "CVS Through eMail", but has since changed scope to be
+much more general.
+CTM is now meant to be the definitive way to make and apply a delta between
+two versions of a directory tree.
+There are two parts to this, making the delta and applying it. These are two
+entirely different things. CTM concentrates the computation-burden on the
+generation of the deltas, as a delta very often is applied more times than
+it is made. Second CTM tries to make the minimal size delta.
+
+Why not use diff/patch ?
+------------------------
+Good question. Primarily because diff and patch doesn't do their job very
+well. They don't deal with binary files (in this case files with '\0' or
+'\0377' characters in them or files that doesn't end in '\n') which isn't
+a big surprise: they were made to deal with text-files only. As a second
+gripe, with patch you send the entire file to delete it. Not particular
+efficient.
+
+So what does CTM do exactly ?
+-----------------------------
+CTM will produce a file, (a delta) containing the instructions and data needed
+to take another copy of the tree from the old to the new status. CTM means to
+do this in the exact sense, and therefore the delta contains MD5 checksums to
+verify that the tree it is applied to is indeed in the state CTM expects.
+
+This means that if you have modified the tree locally, CTM might not be able
+to upgrade your copy.
+
+How do I make a CTM-delta ?
+---------------------------
+
+Read the source, and be prepared to have 2 copies of the tree; One is
+the reference ("From") tree, and the other is the delta ("To") tree.
+The mkCTM script will create the CTM diff of the differences between
+the reference tree and the delta tree. A lot of scratch space is
+required, and your machine will work hard.
+
+How do I apply a CTM-delta ?
+----------------------------
+You pass it to the 'ctm' command. You can pass a CTM-delta on stdin, or
+you can give the filename as an argument. If you do the latter, you make
+life a lot easier for your self, since the program can accept gzip'ed files
+and since it will not have to make a temporary copy of your file. You can
+specify multiple deltas at one time, they will be processed one at a time.
+
+The ctm command runs in a number of passes. It will process the entire
+input file in each pass, before commencing with the next pass.
+
+Pass 1 will validate that the input file is OK. The syntax, the data and
+the global MD5 checksum will be checked. If any of these fail, ctm will
+never be able to do anything with the file, so it will simply reject it.
+
+Pass 2 will validate that the directory tree is in the state expected by
+the CTM-delta. This is done by looking for files and directories which
+should/should not exists and by checking the MD5 checksums of files.
+
+Pass 3 will actually apply the delta.
+
+Should I delete the delta when I have applied it ?
+--------------------------------------------------
+No. You might want to selectively reconstruct a file latter on.
+
+Why is CTM not being maintained?
+--------------------------------
+Because CVSUP has improved on the concept quite a bit, and is now
+the method of choice.
+
+Poul-Henning
diff --git a/usr.sbin/ctm/ctm/Makefile b/usr.sbin/ctm/ctm/Makefile
new file mode 100644
index 0000000..dd27ed2
--- /dev/null
+++ b/usr.sbin/ctm/ctm/Makefile
@@ -0,0 +1,24 @@
+# ----------------------------------------------------------------------------
+# "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$
+
+PROG= ctm
+MAN= ctm.1 ctm.5
+SRCS= ctm.c ctm_input.c ctm_pass1.c ctm_pass2.c ctm_pass3.c \
+ ctm_passb.c ctm_syntax.c ctm_ed.c
+
+NOTYET= ctm_ed.c
+
+LIBADD= md
+
+WARNS?= 2
+
+.if exists(${.CURDIR}/../../Makefile.inc)
+.include "${.CURDIR}/../../Makefile.inc"
+.endif
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ctm/ctm/Makefile.depend b/usr.sbin/ctm/ctm/Makefile.depend
new file mode 100644
index 0000000..064e492
--- /dev/null
+++ b/usr.sbin/ctm/ctm/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libmd \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ctm/ctm/ctm.1 b/usr.sbin/ctm/ctm/ctm.1
new file mode 100644
index 0000000..6ba35c9
--- /dev/null
+++ b/usr.sbin/ctm/ctm/ctm.1
@@ -0,0 +1,335 @@
+.\" ----------------------------------------------------------------------------
+.\" "THE BEER-WARE LICENSE" (Revision 42):
+.\" <joerg@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. Joerg Wunsch
+.\" ----------------------------------------------------------------------------
+.\"
+.\" This manual page is partially obtained from Poul-Hennings CTM README
+.\" file.
+.\"
+.\" CTM and ctm(1) by <phk@FreeBSD.org>
+.\"
+.\" $FreeBSD$
+.\"
+.Dd December 14, 2015
+.Dt CTM 1
+.Os
+.Sh NAME
+.Nm ctm
+.Nd source code mirror program
+.Sh SYNOPSIS
+.Nm
+.Op Fl cFklquv
+.Op Fl b Ar basedir
+.Op Fl B Ar backup-file
+.Op Fl e Ar include-regex
+.Op Fl t Ar tar-command
+.Op Fl T Ar tmpdir
+.Op Fl V Ar level
+.Op Fl x Ar exclude-regex
+.Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility was originally
+.Dq Cvs Through eMail ,
+but now instead it seems more fitting to call it
+.Dq Current Through eMail .
+.Pp
+The
+.Nm
+utility is now meant to be the definitive way to make and apply a delta between
+two versions of a directory tree.
+.Pp
+There are two parts to this, making the delta and applying it.
+These are two
+entirely different things.
+.Ss Usage
+To apply a CTM delta, you pass it to the
+.Nm
+command.
+You can pass a CTM delta on stdin, or you can give the
+filename as an argument.
+If you do the latter, you make life a lot
+easier for your self, since the program can accept gzip'ed files and
+since it will not have to make a temporary copy of your file.
+You can
+specify multiple deltas at one time, they will be processed one at a
+time.
+Deltas that are already applied will be ignored.
+.Pp
+The
+.Nm
+command runs in a number of passes.
+It will process the entire
+input file in each pass, before commencing with the next pass.
+.Pp
+Before working on a file
+.Ar name
+.Nm
+first checks for the existence of the file
+.Ar name.ctm .
+If this file exists,
+.Nm
+works on it instead.
+.Pp
+Pass 1 will verify that the input file is OK.
+The syntax, the data
+and the global MD5 checksum will be checked.
+If any of these fail,
+.Nm
+will simply reject the input file.
+.Pp
+Pass 2 will validate that the directory tree is in the state expected by
+the CTM delta.
+This is done by looking for files and directories which
+should/should not exist and by checking the MD5 checksums of files.
+.Pp
+If a
+.Ar backup-file
+had been specified using the
+.Fl B
+option, all files that would be modified by this
+.Nm
+invocation are backed up
+to this file using the archiver command specified by the
+.Fl t
+option.
+The default archiver command is
+.Nm "tar -rf %s -T -" .
+.Pp
+Pass 3 will actually apply the delta.
+.Pp
+The list of files that would be modified by
+.Nm
+is subject to filtering regular expressions specified
+using the
+.Fl e
+and
+.Fl x
+options.
+The
+.Fl e
+and
+.Fl x
+options are applied in order of appearance on the command line.
+The last
+filter that matched a given file name determines whether the file would be
+operated on or left alone by
+.Nm .
+.Pp
+The
+.Nm
+utility
+will extract the file hierarchy below its working directory.
+Absolute
+filenames or filenames containing references through
+.Sq Pa .\&
+and
+.Sq Pa ..\&
+are explicitly prohibited as a security measure.
+.Ss Options
+.Bl -tag -width indent
+.It Fl b Ar basedir
+Prepend the path
+.Ar basedir
+to every filename.
+.It Fl B Ar backup-file
+Backup all files that would be modified by this CTM run to
+.Ar backup-file .
+If any filters are specified using the
+.Fl e
+and
+.Fl x
+options, then the final set of files backed up are those that would be
+modified by CTM after the filters are applied.
+.It Fl c
+Check it out, do not do anything.
+.It Fl e Ar regular_expression
+Match each name in the CTM file against
+.Ar regular_expression ,
+and if it matches process the file, otherwise leave it alone.
+There may be
+any number of these options.
+Use of this option disables the
+.Pa .ctm_status
+sequence number checks.
+For example, the expression
+.Ic ^usr.sbin/ctm
+for example, will select the
+.Pa usr.sbin/ctm
+source directory and all pathnames under it.
+.Pp
+Pathnames can be disabled from being considered by CTM using the
+.Fl x
+option.
+.It Fl F
+Force.
+.It Fl k
+Keep files and directories and do not remove them even if the CTM file
+specifies they are to be removed.
+If the
+.Fl B
+option is specified, these files and directories will not be backed up.
+.It Fl l
+List files that would be modified by this invocation of CTM and the
+actions that would be performed on them.
+Use of the
+.Fl l
+option disables the
+.Pa .ctm_status
+checks and integrity checks on the source tree being operated on.
+The
+.Fl l
+option can be combined with the
+.Fl e
+and
+.Fl x
+options to determine which files would be modified by the given set of
+command line options.
+.It Fl q
+Tell us less.
+.It Fl t Ar tar-command
+Use
+.Ar tar-command
+instead of the default archiver
+.Nm tar .
+This option takes effect only if a backup file had been specified using the
+.Fl B
+option.
+A %s in the tar command will be replaced by the name of the backup
+file.
+.It Fl T Ar tmpdir
+Put temporary files under
+.Ar tmpdir .
+.It Fl u
+Set modification time of created and modified files to the CTM delta
+creation time.
+.It Fl v
+Tell us more.
+.It Fl V Ar level
+Tell us more.
+.Ar Level
+is the level of verbosity.
+.It Fl x Ar regular_expression
+Match each name in the CTM file against
+.Ar regular_expression
+and if it matches, leave the file alone.
+There may be any number of these
+options.
+Use of this option disables the
+.Pa .ctm_status
+sequence number checks.
+.Pp
+Pathnames can be selected for CTM's consideration using the
+.Fl e
+option.
+.El
+.Sh SECURITY
+On its own, CTM is an insecure protocol
+- there is no authentication performed that the
+changes applied to the source code were sent by a
+trusted party, and so care should be taken if the
+CTM deltas are obtained via an unauthenticated
+medium such as regular email.
+It is a relatively simple matter for an attacker
+to forge a CTM delta to replace or precede the
+legitimate one and insert malicious code into your
+source tree.
+If the legitimate delta is somehow prevented from
+arriving, this will go unnoticed until a later
+delta attempts to touch the same file, at which
+point the MD5 checksum will fail.
+.Pp
+To remedy this insecurity, CTM pieces generated by
+FreeBSD.org are cryptographically signed in a
+format compatible with the GNU Privacy Guard
+utility, available in /usr/ports/security/gpg, and
+the Pretty Good Privacy v5 utility,
+/usr/ports/security/pgp5.
+The relevant public key can be obtained by
+fingering ctm@FreeBSD.org.
+.Pp
+CTM deltas which are thus signed cannot be
+undetectably altered by an attacker.
+Therefore it is recommended that you make use of
+GPG or PGP5 to verify the signatures if you
+receive your CTM deltas via email.
+.Sh ENVIRONMENT
+.Ev TMPDIR ,
+if set to a pathname, will cause ctm to use that pathname
+as the location of temporary file.
+See
+.Xr tempnam 3 ,
+for more details on this.
+The same effect may be achieved with the
+.Fl T
+flag.
+.Sh FILES
+.Pa .ctm_status
+contains the sequence number of the last CTM delta applied.
+Changing
+or removing this file will greatly confuse
+.Nm .
+.Pp
+Using the
+.Fl e
+and
+.Fl x
+options can update a partial subset of the source tree and causes sources
+to be in an inconsistent state.
+It is assumed that you know what you are
+doing when you use these options.
+.Sh EXAMPLES
+.Bd -literal
+cd ~cvs
+/usr/sbin/ctm ~ctm/cvs-*
+.Ed
+.Pp
+To extract and patch all sources under `lib'
+.Bd -literal
+cd ~/lib-srcs
+/usr/sbin/ctm -e '^lib' ~ctm/src-cur*
+.Ed
+.Sh DIAGNOSTICS
+Numerous messages, hopefully self-explanatory.
+The
+.Dq noise level
+can be adjusted with the
+.Fl q ,
+.Fl v
+and
+.Fl V
+options.
+.Sh SEE ALSO
+.Xr ctm_dequeue 1 ,
+.Xr ctm_rmail 1 ,
+.Xr ctm_smail 1 ,
+.Xr ctm 5
+.Rs
+.%B "The FreeBSD Handbook"
+.%T "Using CTM"
+.%U http://www.FreeBSD.org/doc/en_US.ISO8859-1/books/handbook/ctm.html
+.Re
+.Rs
+.%T "Miscellaneous CTM on FreeBSD Resources"
+.%U http://ctm.berklix.org
+.Re
+.Sh HISTORY
+Initial trials were run during the work on
+.Fx 1.1.5 ,
+and many bugs and
+methods were hashed out.
+.Pp
+The
+.Nm
+command appeared in
+.Fx 2.1 .
+.Sh AUTHORS
+.An -nosplit
+The CTM system has been designed and implemented by
+.An Poul-Henning Kamp Aq Mt phk@FreeBSD.org .
+.Pp
+.An Joerg Wunsch Aq Mt joerg@FreeBSD.org
+wrote this man-page.
diff --git a/usr.sbin/ctm/ctm/ctm.5 b/usr.sbin/ctm/ctm/ctm.5
new file mode 100644
index 0000000..e48b09f
--- /dev/null
+++ b/usr.sbin/ctm/ctm/ctm.5
@@ -0,0 +1,182 @@
+.\" ----------------------------------------------------------------------------
+.\" "THE BEER-WARE LICENSE" (Revision 42):
+.\" <joerg@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. Joerg Wunsch
+.\" ----------------------------------------------------------------------------
+.\"
+.\" This manual page is partially obtained from Poul-Hennings CTM README
+.\" file.
+.\"
+.\" CTM and ctm(1) by <phk@FreeBSD.org>
+.\"
+.\" $FreeBSD$
+.\"
+.Dd March 25, 1995
+.Dt CTM 5
+.Os
+.Sh NAME
+.Nm ctm
+.Nd source code mirror system
+.Sh DESCRIPTION
+The
+.Nm
+transfers data in a specific file format, called a CTM delta.
+.Pp
+CTM deltas consist of control lines and data chunks.
+Each control
+line starts with the letters
+.Dq CTM ,
+followed by a CTM statement and control data, and ends with a '\en'
+character.
+.Pp
+Data chunks always belong to the preceding control line, and the
+last field on that control line is the number of bytes in the data
+chunk.
+A trailing newline '\en' character follows each data chunk, this
+newline is not part of the chunk and is not included in the count.
+.Pp
+The CTM statements are as follows.
+.Bl -tag -width indent
+.It _BEGIN Ar version name number timestamp prefix
+This is the overall begin of a CTM delta file.
+The
+.Ar version
+field must match the program version
+(currently 2.0).
+.Ar Name
+is the name and
+.Ar number
+the sequence number of the CTM service, it is matched against the file
+.Pa .ctm_status
+to see if the delta has already been applied.
+.Ar Timestamp
+contains the year, month, day, hour, minute, and second of the
+time of delta creation for reference
+(followed by the letter
+.Sq Z
+meaning this is a UTC timestamp).
+The
+.Ar prefix
+field is currently not implemented.
+.It _END Ar md5
+This statement ends the CTM delta, the global
+.Ar md5
+checksum is matched against the MD5 checksum of the entire delta, up to
+and including the space (0x20) character following ``_END''.
+.It \&FM Ar name uid gid mode md5 count
+Make the file
+.Ar name ,
+the original file had the uid
+.Ar uid
+(numerical, decimal),
+the gid
+.Ar gid
+(numerical, decimal),
+mode
+.Ar mode
+(numerical, octal),
+and the MD5 checksum
+.Ar md5 .
+.Pp
+The following
+.Ar count
+bytes data are the contents of the new file.
+.It \&FS Ar name uid gid mode md5before md5after count
+Substitute the contents of file
+.Ar name ,
+the original file had the new uid
+.Ar uid
+(numerical, decimal),
+the new gid
+.Ar gid
+(numerical, decimal),
+new mode
+.Ar mode
+(numerical, octal),
+the old MD5 checksum
+.Ar md5before ,
+and the new MD5 checksum
+.Ar md5after .
+.Pp
+The following
+.Ar count
+bytes data are the contents of the new file.
+.Pp
+File substitution is used if the commands to edit a file would exceed
+the total file length, so substituting it is more efficient.
+.It \&FN Ar name uid gid mode md5before md5after count
+Edit the file
+.Ar name .
+The arguments are as above, but the data sections contains an
+.Xr diff 1
+-n script which should be applied to the file in question.
+.It \&FR Ar name md5
+Remove the file
+.Ar name ,
+which must match the MD5 checksum
+.Ar md5 .
+.It \&AS Ar name uid gid mode
+The original file
+.Ar name
+changed its owner to
+.Ar uid ,
+its group to
+.Ar gid ,
+and/or its mode to
+.Ar mode .
+.It \&DM Ar name uid gid mode
+The directory
+.Ar name
+is to be created, it had originally the owner
+.Ar uid ,
+group
+.Ar gid ,
+and mode
+.Ar mode .
+.It \&DR Ar name
+The directory
+.Ar name
+is to be removed.
+.El
+.Sh EXAMPLES
+In the following example, long lines have been folded to make them
+printable
+(marked by backslashes).
+.Bd -literal
+CTM_BEGIN 2.0 cvs-cur 485 19950324214652Z .
+CTMFR src/sys/gnu/i386/isa/scd.c,v 5225f13aa3c7e458f9dd0d4bb637b18d
+CTMFR src/sys/gnu/i386/isa/scdreg.h,v e5af42b8a06f2c8030b93a7d71afb223
+CTMDM src/sys/gnu/i386/isa/Attic 0 552 775
+CTMFS .ctm_status 545 552 664 d9ccd2a84a9dbb8db56ba85663adebf0 \\
+e2a10c6f66428981782a0a18a789ee2e 12
+cvs-cur 485
+
+CTMFN CVSROOT/commitlogs/gnu 545 552 664 \\
+5d7bc3549140d860bd9641b5782c002d 7fb04ed84b48160c9b8eea84b4c0b6e3 394
+a6936 21
+ache 95/03/24 09:59:50
+
+ Modified: gnu/lib/libdialog kernel.c prgbox.c
+ Log:
+[...]
+CTM_END 74ddd298d76215ae45a077a4b6a74e9c
+.Ed
+.Sh SEE ALSO
+.Xr ctm 1 ,
+.Xr ctm_rmail 1 ,
+.Xr ed 1
+.Sh HISTORY
+Initial trials ran during the
+.Fx 1.1.5 ,
+and many bugs and
+methods were hashed out.
+The CTM system has been made publicly available in
+.Fx 2.1 .
+.Sh AUTHORS
+.An -nosplit
+The CTM system has been designed and implemented by
+.An Poul-Henning Kamp Aq Mt phk@FreeBSD.org .
+.Pp
+.An Joerg Wunsch Aq Mt joerg@FreeBSD.org
+wrote this man-page.
diff --git a/usr.sbin/ctm/ctm/ctm.c b/usr.sbin/ctm/ctm/ctm.c
new file mode 100644
index 0000000..0ea4895
--- /dev/null
+++ b/usr.sbin/ctm/ctm/ctm.c
@@ -0,0 +1,331 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "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$
+ *
+ * This is the client program of 'CTM'. It will apply a CTM-patch to a
+ * collection of files.
+ *
+ * Options we'd like to see:
+ *
+ * -a Attempt best effort.
+ * -d <int> Debug TBD.
+ * -m <mail-addr> Email me instead.
+ * -r <name> Reconstruct file.
+ * -R <file> Read list of files to reconstruct.
+ *
+ * Options we have:
+ * -b <dir> Base-dir
+ * -B <file> Backup to tar-file.
+ * -t Tar command (default as in TARCMD).
+ * -c Check it out, don't do anything.
+ * -F Force
+ * -q Tell us less.
+ * -T <tmpdir>. Temporary files.
+ * -u Set all file modification times to the timestamp
+ * -v Tell us more.
+ * -V <level> Tell us more level = number of -v
+ * -k Keep files and directories that would have been removed.
+ * -l List actions.
+ *
+ * Options we don't actually use:
+ * -p Less paranoid.
+ * -P Paranoid.
+ */
+
+#define EXTERN /* */
+#include <paths.h>
+#include "ctm.h"
+
+#define CTM_STATUS ".ctm_status"
+
+extern int Proc(char *, unsigned applied);
+
+int
+main(int argc, char **argv)
+{
+ int stat=0, err=0;
+ int c;
+ unsigned applied = 0;
+ FILE *statfile;
+ struct CTM_Filter *nfilter = NULL; /* new filter */
+ u_char * basedir;
+
+ basedir = NULL;
+ Verbose = 1;
+ Paranoid = 1;
+ SetTime = 0;
+ KeepIt = 0;
+ ListIt = 0;
+ BackupFile = NULL;
+ TarCmd = TARCMD;
+ LastFilter = FilterList = NULL;
+ TmpDir = getenv("TMPDIR");
+ if (TmpDir == NULL)
+ TmpDir = strdup(_PATH_TMP);
+ setbuf(stderr,0);
+ setbuf(stdout,0);
+
+ while((c=getopt(argc,argv,"ab:B:cd:e:Fklm:pPqr:R:t:T:uV:vx:")) != -1) {
+ switch (c) {
+ case 'b': basedir = optarg; break; /* Base Directory */
+ case 'B': BackupFile = optarg; break;
+ case 'c': CheckIt++; break; /* Only check it */
+ case 'F': Force = 1; break;
+ case 'k': KeepIt++; break; /* Don't do removes */
+ case 'l': ListIt++; break; /* Only list actions and files */
+ case 'p': Paranoid--; break; /* Less Paranoid */
+ case 'P': Paranoid++; break; /* More Paranoid */
+ case 'q': Verbose--; break; /* Quiet */
+ case 't': TarCmd = optarg; break; /* archiver command */
+ case 'T': TmpDir = optarg; break; /* set temporary directory */
+ case 'u': SetTime++; break; /* Set timestamp on files */
+ case 'v': Verbose++; break; /* Verbose */
+ case 'V': sscanf(optarg,"%d", &c); /* Verbose */
+ Verbose += c;
+ break;
+ case 'e': /* filter expressions */
+ case 'x':
+ if (NULL == (nfilter = Malloc(sizeof(struct CTM_Filter)))) {
+ warnx("out of memory for expressions: \"%s\"", optarg);
+ stat++;
+ break;
+ }
+
+ (void) memset(nfilter, 0, sizeof(struct CTM_Filter));
+
+ if (0 != (err =
+ regcomp(&nfilter->CompiledRegex, optarg, REG_NOSUB))) {
+
+ char errmsg[128];
+
+ regerror(err, &nfilter->CompiledRegex, errmsg,
+ sizeof(errmsg));
+ warnx("regular expression: \"%s\"", errmsg);
+ stat++;
+ break;
+ }
+
+ /* note whether the filter enables or disables on match */
+ nfilter->Action =
+ (('e' == c) ? CTM_FILTER_ENABLE : CTM_FILTER_DISABLE);
+
+ /* link in the expression into the list */
+ nfilter->Next = NULL;
+ if (NULL == FilterList) {
+ LastFilter = FilterList = nfilter; /* init head and tail */
+ } else { /* place at tail */
+ LastFilter->Next = nfilter;
+ LastFilter = nfilter;
+ }
+ break;
+ case ':':
+ warnx("option '%c' requires an argument",optopt);
+ stat++;
+ break;
+ case '?':
+ warnx("option '%c' not supported",optopt);
+ stat++;
+ break;
+ default:
+ warnx("option '%c' not yet implemented",optopt);
+ break;
+ }
+ }
+
+ if(stat) {
+ warnx("%d errors during option processing",stat);
+ return Exit_Pilot;
+ }
+ stat = Exit_Done;
+ argc -= optind;
+ argv += optind;
+
+ if (basedir == NULL) {
+ Buffer = (u_char *)Malloc(BUFSIZ + strlen(SUBSUFF) +1);
+ CatPtr = Buffer;
+ *Buffer = '\0';
+ } else {
+ Buffer = (u_char *)Malloc(strlen(basedir)+ BUFSIZ + strlen(SUBSUFF) +1);
+ strcpy(Buffer, basedir);
+ CatPtr = Buffer + strlen(basedir);
+ if (CatPtr[-1] != '/') {
+ strcat(Buffer, "/");
+ CatPtr++;
+ }
+ }
+ strcat(Buffer, CTM_STATUS);
+
+ if(ListIt)
+ applied = 0;
+ else
+ if((statfile = fopen(Buffer, "r")) == NULL) {
+ if (Verbose > 0)
+ warnx("warning: %s not found", Buffer);
+ } else {
+ fscanf(statfile, "%*s %u", &applied);
+ fclose(statfile);
+ }
+
+ if(!argc)
+ stat |= Proc("-", applied);
+
+ while(argc-- && stat == Exit_Done) {
+ stat |= Proc(*argv++, applied);
+ stat &= ~(Exit_Version | Exit_NoMatch);
+ }
+
+ if(stat == Exit_Done)
+ stat = Exit_OK;
+
+ if(Verbose > 0)
+ warnx("exit(%d)",stat);
+
+ if (FilterList)
+ for (nfilter = FilterList; nfilter; ) {
+ struct CTM_Filter *tmp = nfilter->Next;
+ Free(nfilter);
+ nfilter = tmp;
+ }
+ return stat;
+}
+
+int
+Proc(char *filename, unsigned applied)
+{
+ FILE *f;
+ int i;
+ char *p = strrchr(filename,'.');
+
+ if(!strcmp(filename,"-")) {
+ p = 0;
+ f = stdin;
+ } else if(p && (!strcmp(p,".gz") || !strcmp(p,".Z"))) {
+ p = alloca(20 + strlen(filename));
+ strcpy(p,"gunzip < ");
+ strcat(p,filename);
+ f = popen(p,"r");
+ if(!f) { warn("%s", p); return Exit_Garbage; }
+ } else {
+ p = 0;
+ f = fopen(filename,"r");
+ }
+ if(!f) {
+ warn("%s", filename);
+ return Exit_Garbage;
+ }
+
+ if(Verbose > 1)
+ fprintf(stderr,"Working on <%s>\n",filename);
+
+ Delete(FileName);
+ FileName = String(filename);
+
+ /* If we cannot seek, we're doomed, so copy to a tmp-file in that case */
+ if(!p && -1 == fseek(f,0,SEEK_END)) {
+ char *fn;
+ FILE *f2;
+ int fd;
+
+ if (asprintf(&fn, "%s/CTMclient.XXXXXXXXXX", TmpDir) == -1) {
+ fprintf(stderr, "Cannot allocate memory\n");
+ fclose(f);
+ return Exit_Broke;
+ }
+ if ((fd = mkstemp(fn)) == -1 || (f2 = fdopen(fd, "w+")) == NULL) {
+ warn("%s", fn);
+ free(fn);
+ if (fd != -1)
+ close(fd);
+ fclose(f);
+ return Exit_Broke;
+ }
+ unlink(fn);
+ if (Verbose > 0)
+ fprintf(stderr,"Writing tmp-file \"%s\"\n",fn);
+ free(fn);
+ while(EOF != (i=getc(f)))
+ if(EOF == putc(i,f2)) {
+ fclose(f2);
+ return Exit_Broke;
+ }
+ fclose(f);
+ f = f2;
+ }
+
+ if(!p)
+ rewind(f);
+
+ if((i=Pass1(f, applied)))
+ goto exit_and_close;
+
+ if(ListIt) {
+ i = Exit_Done;
+ goto exit_and_close;
+ }
+
+ if(!p) {
+ rewind(f);
+ } else {
+ pclose(f);
+ f = popen(p,"r");
+ if(!f) { warn("%s", p); return Exit_Broke; }
+ }
+
+ i=Pass2(f);
+
+ if(!p) {
+ rewind(f);
+ } else {
+ pclose(f);
+ f = popen(p,"r");
+ if(!f) { warn("%s", p); return Exit_Broke; }
+ }
+
+ if(i) {
+ if((!Force) || (i & ~Exit_Forcible))
+ goto exit_and_close;
+ }
+
+ if(CheckIt) {
+ if (Verbose > 0)
+ fprintf(stderr,"All checks out ok.\n");
+ i = Exit_Done;
+ goto exit_and_close;
+ }
+
+ /* backup files if requested */
+ if(BackupFile) {
+
+ i = PassB(f);
+
+ if(!p) {
+ rewind(f);
+ } else {
+ pclose(f);
+ f = popen(p,"r");
+ if(!f) { warn("%s", p); return Exit_Broke; }
+ }
+ }
+
+ i=Pass3(f);
+
+exit_and_close:
+ if(!p)
+ fclose(f);
+ else
+ pclose(f);
+
+ if(i)
+ return i;
+
+ if (Verbose > 0)
+ fprintf(stderr,"All done ok\n");
+
+ return Exit_Done;
+}
diff --git a/usr.sbin/ctm/ctm/ctm.h b/usr.sbin/ctm/ctm/ctm.h
new file mode 100644
index 0000000..b733539
--- /dev/null
+++ b/usr.sbin/ctm/ctm/ctm.h
@@ -0,0 +1,163 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "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 <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <md5.h>
+#include <regex.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/time.h>
+
+#define VERSION "2.0"
+
+#define SUBSUFF ".ctm"
+#define TMPSUFF ".ctmtmp"
+#define TARCMD "tar -rf %s -T -"
+
+/* The fields... */
+#define CTM_F_MASK 0xff
+#define CTM_F_Name 0x01
+#define CTM_F_Uid 0x02
+#define CTM_F_Gid 0x03
+#define CTM_F_Mode 0x04
+#define CTM_F_MD5 0x05
+#define CTM_F_Count 0x06
+#define CTM_F_Bytes 0x07
+
+/* The qualifiers... */
+#define CTM_Q_MASK 0xff00
+#define CTM_Q_Name_File 0x0100
+#define CTM_Q_Name_Dir 0x0200
+#define CTM_Q_Name_New 0x0400
+#define CTM_Q_Name_Subst 0x0800
+#define CTM_Q_MD5_After 0x0100
+#define CTM_Q_MD5_Before 0x0200
+#define CTM_Q_MD5_Chunk 0x0400
+#define CTM_Q_MD5_Force 0x0800
+
+struct CTM_Syntax {
+ char *Key; /* CTM key for operation */
+ int *List; /* List of operations */
+ };
+
+extern struct CTM_Syntax Syntax[];
+
+struct CTM_Filter {
+ struct CTM_Filter *Next; /* next filter in the list */
+ int Action; /* enable or disable */
+ regex_t CompiledRegex; /* compiled regex */
+};
+
+#define CTM_FILTER_DISABLE 0
+#define CTM_FILTER_ENABLE 1
+
+#define Malloc malloc
+#define Free free
+#define Delete(foo) if (!foo) ; else {Free(foo); foo = 0; }
+#define String(foo) strdup(foo)
+
+#ifndef EXTERN
+# define EXTERN extern
+#endif
+EXTERN u_char *Version;
+EXTERN u_char *Name;
+EXTERN u_char *Nbr;
+EXTERN u_char *TimeStamp;
+EXTERN u_char *Prefix;
+EXTERN u_char *FileName;
+EXTERN u_char *TmpDir;
+EXTERN u_char *CatPtr;
+EXTERN u_char *Buffer;
+EXTERN u_char *BackupFile;
+EXTERN u_char *TarCmd;
+
+/*
+ * Paranoid -- Just in case they should be after us...
+ * 0 not at all.
+ * 1 normal.
+ * 2 somewhat.
+ * 3 you bet!.
+ *
+ * Verbose -- What to tell mom...
+ * 0 Nothing which wouldn't surprise.
+ * 1 Normal.
+ * 2 Show progress '.'.
+ * 3 Show progress names, and actions.
+ * 4 even more...
+ * and so on
+ *
+ * ExitCode -- our Epitaph
+ * 0 Perfect, all input digested, no problems
+ * 1 Bad input, no point in retrying.
+ * 2 Pilot error, commandline problem &c
+ * 4 Out of resources.
+ * 8 Destination-tree not correct.
+ * 16 Destination-tree not correct, can force.
+ * 32 Internal problems.
+ *
+ */
+
+EXTERN int Paranoid;
+EXTERN int Verbose;
+EXTERN int Exit;
+EXTERN int Force;
+EXTERN int CheckIt;
+EXTERN int KeepIt;
+EXTERN int ListIt;
+EXTERN int SetTime;
+EXTERN struct timeval Times[2];
+EXTERN struct CTM_Filter *FilterList;
+EXTERN struct CTM_Filter *LastFilter;
+
+#define Exit_OK 0
+#define Exit_Garbage 1
+#define Exit_Pilot 2
+#define Exit_Broke 4
+#define Exit_NotOK 8
+#define Exit_Forcible 16
+#define Exit_Mess 32
+#define Exit_Done 64
+#define Exit_Version 128
+#define Exit_NoMatch 256
+
+void Fatal_(int ln, char *fn, char *kind);
+#define Fatal(foo) Fatal_(__LINE__,__FILE__,foo)
+#define Assert() Fatal_(__LINE__,__FILE__,"Assert failed.")
+#define WRONG {Assert(); return Exit_Mess;}
+
+u_char * Ffield(FILE *fd, MD5_CTX *ctx,u_char term);
+u_char * Fname(FILE *fd, MD5_CTX *ctx,u_char term,int qual, int verbose);
+
+int Fbytecnt(FILE *fd, MD5_CTX *ctx, u_char term);
+
+u_char * Fdata(FILE *fd, int u_chars, MD5_CTX *ctx);
+
+#define GETFIELD(p,q) if(!((p)=Ffield(fd,&ctx,(q)))) return BADREAD
+#define GETFIELDCOPY(p,q) if(!((p)=Ffield(fd,&ctx,(q)))) return BADREAD; else p=String(p)
+#define GETBYTECNT(p,q) if(0 >((p)= Fbytecnt(fd,&ctx,(q)))) return BADREAD
+#define GETDATA(p,q) if(!((p) = Fdata(fd,(q),&ctx))) return BADREAD
+#define GETNAMECOPY(p,q,r,v) if(!((p)=Fname(fd,&ctx,(q),(r),(v)))) return BADREAD; else p=String(p)
+
+int Pass1(FILE *fd, unsigned applied);
+int Pass2(FILE *fd);
+int PassB(FILE *fd);
+int Pass3(FILE *fd);
+
+int ctm_edit(u_char *script, int length, char *filein, char *fileout);
diff --git a/usr.sbin/ctm/ctm/ctm_ed.c b/usr.sbin/ctm/ctm/ctm_ed.c
new file mode 100644
index 0000000..e3ec464
--- /dev/null
+++ b/usr.sbin/ctm/ctm/ctm_ed.c
@@ -0,0 +1,114 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "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 "ctm.h"
+
+int
+ctm_edit(u_char *script, int length, char *filein, char *fileout)
+{
+ u_char *ep, cmd;
+ int ln, ln2, iln, ret=0, c;
+ FILE *fi=0,*fo=0;
+
+ fi = fopen(filein,"r");
+ if(!fi) {
+ warn("%s", filein);
+ return 8;
+ }
+
+ fo = fopen(fileout,"w");
+ if(!fo) {
+ warn("%s", fileout);
+ fclose(fi);
+ return 4;
+ }
+ iln = 1;
+ for(ep=script;ep < script+length;) {
+ cmd = *ep++;
+ if(cmd != 'a' && cmd != 'd') { ret = 1; goto bye; }
+ ln = 0;
+ while(isdigit(*ep)) {
+ ln *= 10;
+ ln += (*ep++ - '0');
+ }
+ if(*ep++ != ' ') { ret = 1; goto bye; }
+ ln2 = 0;
+ while(isdigit(*ep)) {
+ ln2 *= 10;
+ ln2 += (*ep++ - '0');
+ }
+ if(*ep++ != '\n') { ret = 1; goto bye; }
+
+
+ if(cmd == 'd') {
+ while(iln < ln) {
+ c = getc(fi);
+ if(c == EOF) { ret = 1; goto bye; }
+ putc(c,fo);
+ if(c == '\n')
+ iln++;
+ }
+ while(ln2) {
+ c = getc(fi);
+ if(c == EOF) { ret = 1; goto bye; }
+ if(c != '\n')
+ continue;
+ ln2--;
+ iln++;
+ }
+ continue;
+ }
+ if(cmd == 'a') {
+ while(iln <= ln) {
+ c = getc(fi);
+ if(c == EOF) { ret = 1; goto bye; }
+ putc(c,fo);
+ if(c == '\n')
+ iln++;
+ }
+ while(ln2) {
+ c = *ep++;
+ putc(c,fo);
+ if(c != '\n')
+ continue;
+ ln2--;
+ }
+ continue;
+ }
+ ret = 1;
+ goto bye;
+ }
+ while(1) {
+ c = getc(fi);
+ if(c == EOF) break;
+ putc(c,fo);
+ }
+ ret = 0;
+bye:
+ if(fi) {
+ if(fclose(fi) != 0) {
+ warn("%s", filein);
+ ret = 1;
+ }
+ }
+ if(fo) {
+ if(fflush(fo) != 0) {
+ warn("%s", fileout);
+ ret = 1;
+ }
+ if(fclose(fo) != 0) {
+ warn("%s", fileout);
+ ret = 1;
+ }
+ }
+ return ret;
+}
diff --git a/usr.sbin/ctm/ctm/ctm_input.c b/usr.sbin/ctm/ctm/ctm_input.c
new file mode 100644
index 0000000..32166d3
--- /dev/null
+++ b/usr.sbin/ctm/ctm/ctm_input.c
@@ -0,0 +1,134 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "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 "ctm.h"
+
+/*---------------------------------------------------------------------------*/
+void
+Fatal_(int ln, char *fn, char *kind)
+{
+ if(Verbose > 2)
+ fprintf(stderr,"Fatal error. (%s:%d)\n",fn,ln);
+ fprintf(stderr,"%s Fatal error: %s\n",FileName, kind);
+}
+#define Fatal(foo) Fatal_(__LINE__,__FILE__,foo)
+#define Assert() Fatal_(__LINE__,__FILE__,"Assert failed.")
+
+/*---------------------------------------------------------------------------*/
+/* get next field, check that the terminating whitespace is what we expect */
+u_char *
+Ffield(FILE *fd, MD5_CTX *ctx,u_char term)
+{
+ static u_char buf[BUFSIZ];
+ int i,l;
+
+ for(l=0;;) {
+ if((i=getc(fd)) == EOF) {
+ Fatal("Truncated patch.");
+ return 0;
+ }
+ buf[l++] = i;
+ if(isspace(i))
+ break;
+ if(l >= sizeof buf) {
+ Fatal("Corrupt patch.");
+ printf("Token is too long.\n");
+ return 0;
+ }
+ }
+ buf[l] = '\0';
+ MD5Update(ctx,buf,l);
+ if(buf[l-1] != term) {
+ Fatal("Corrupt patch.");
+ fprintf(stderr,"Expected \"%s\" but didn't find it {%02x}.\n",
+ term == '\n' ? "\\n" : " ",buf[l-1]);
+ if(Verbose > 4)
+ fprintf(stderr,"{%s}\n",buf);
+ return 0;
+ }
+ buf[--l] = '\0';
+ if(Verbose > 4)
+ fprintf(stderr,"<%s>\n",buf);
+ return buf;
+}
+
+int
+Fbytecnt(FILE *fd, MD5_CTX *ctx, u_char term)
+{
+ u_char *p,*q;
+ int u_chars=0;
+
+ p = Ffield(fd,ctx,term);
+ if(!p) return -1;
+ for(q=p;*q;q++) {
+ if(!isdigit(*q)) {
+ Fatal("Bytecount contains non-digit.");
+ return -1;
+ }
+ u_chars *= 10;
+ u_chars += (*q - '0');
+ }
+ return u_chars;
+}
+
+u_char *
+Fdata(FILE *fd, int u_chars, MD5_CTX *ctx)
+{
+ u_char *p = Malloc(u_chars+1);
+
+ if(u_chars+1 != fread(p,1,u_chars+1,fd)) {
+ Fatal("Truncated patch.");
+ return 0;
+ }
+ MD5Update(ctx,p,u_chars+1);
+ if(p[u_chars] != '\n') {
+ if(Verbose > 3)
+ printf("FileData wasn't followed by a newline.\n");
+ Fatal("Corrupt patch.");
+ return 0;
+ }
+ p[u_chars] = '\0';
+ return p;
+}
+
+/*---------------------------------------------------------------------------*/
+/* get the filename in the next field, prepend BaseDir and give back the result
+ strings. The sustitute filename is return (the one with the suffix SUBSUFF)
+ if it exists and the qualifier contains CTM_Q_Name_Subst
+ NOTA: Buffer is already initialize with BaseDir, CatPtr is the insertion
+ point on this buffer + the length test in Ffield() is enough for Fname() */
+
+u_char *
+Fname(FILE *fd, MD5_CTX *ctx,u_char term,int qual, int verbose)
+{
+ u_char * p;
+ struct stat st;
+
+ if ((p = Ffield(fd,ctx,term)) == NULL) return(NULL);
+
+ strcpy(CatPtr, p);
+
+ if (!(qual & CTM_Q_Name_Subst)) return(Buffer);
+
+ p = Buffer + strlen(Buffer);
+
+ strcat(Buffer, SUBSUFF);
+
+ if ( -1 == stat(Buffer, &st) ) {
+ *p = '\0';
+ } else {
+ if(verbose > 2)
+ fprintf(stderr,"Using %s as substitute file\n", Buffer);
+ }
+
+ return (Buffer);
+}
diff --git a/usr.sbin/ctm/ctm/ctm_pass1.c b/usr.sbin/ctm/ctm/ctm_pass1.c
new file mode 100644
index 0000000..026a320
--- /dev/null
+++ b/usr.sbin/ctm/ctm/ctm_pass1.c
@@ -0,0 +1,250 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "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 "ctm.h"
+#define BADREAD 1
+
+/*---------------------------------------------------------------------------*/
+/* Pass1 -- Validate the incoming CTM-file.
+ */
+
+int
+Pass1(FILE *fd, unsigned applied)
+{
+ u_char *p,*q;
+ MD5_CTX ctx;
+ int i,j,sep,cnt;
+ u_char *md5=0,*name=0,*trash=0;
+ struct CTM_Syntax *sp;
+ int slashwarn=0, match=0, total_matches=0;
+ unsigned current;
+ char md5_1[33];
+
+ if(Verbose>3)
+ printf("Pass1 -- Checking integrity of incoming CTM-patch\n");
+ MD5Init (&ctx);
+
+ GETFIELD(p,' '); /* CTM_BEGIN */
+ if(strcmp(p,"CTM_BEGIN")) {
+ Fatal("Probably not a CTM-patch at all.");
+ if(Verbose>3)
+ fprintf(stderr,"Expected \"CTM_BEGIN\" got \"%s\".\n",p);
+ return 1;
+ }
+
+ GETFIELDCOPY(Version,' '); /* <Version> */
+ if(strcmp(Version,VERSION)) {
+ Fatal("CTM-patch is wrong version.");
+ if(Verbose>3)
+ fprintf(stderr,"Expected \"%s\" got \"%s\".\n",VERSION,p);
+ return 1;
+ }
+
+ GETFIELDCOPY(Name,' '); /* <Name> */
+ GETFIELDCOPY(Nbr,' '); /* <Nbr> */
+ GETFIELDCOPY(TimeStamp,' '); /* <TimeStamp> */
+ GETFIELDCOPY(Prefix,'\n'); /* <Prefix> */
+
+ sscanf(Nbr, "%u", &current);
+ if (FilterList || ListIt)
+ current = 0; /* ignore if -l or if filters are present */
+ if(current && current <= applied) {
+ if(Verbose > 0)
+ fprintf(stderr,"Delta number %u is already applied; ignoring.\n",
+ current);
+ return Exit_Version;
+ }
+
+ for(;;) {
+ Delete(md5);
+ Delete(name);
+ Delete(trash);
+ cnt = -1;
+ /* if a filter list is defined we assume that all pathnames require
+ an action opposite to that requested by the first filter in the
+ list.
+ If no filter is defined, all pathnames are assumed to match. */
+ match = (FilterList ? !(FilterList->Action) : CTM_FILTER_ENABLE);
+
+ GETFIELD(p,' '); /* CTM_something */
+
+ if (p[0] != 'C' || p[1] != 'T' || p[2] != 'M') {
+ Fatal("Expected CTM keyword.");
+ fprintf(stderr,"Got [%s]\n",p);
+ return 1;
+ }
+
+ if(!strcmp(p+3,"_END"))
+ break;
+
+ for(sp=Syntax;sp->Key;sp++)
+ if(!strcmp(p+3,sp->Key))
+ goto found;
+ Fatal("Expected CTM keyword.");
+ fprintf(stderr,"Got [%s]\n",p);
+ return 1;
+ found:
+ if(Verbose > 5)
+ fprintf(stderr,"%s ",sp->Key);
+ for(i=0;(j = sp->List[i]);i++) {
+ if (sp->List[i+1] && (sp->List[i+1] & CTM_F_MASK) != CTM_F_Bytes)
+ sep = ' ';
+ else
+ sep = '\n';
+
+ if(Verbose > 5)
+ fprintf(stderr," %x(%d)",sp->List[i],sep);
+
+ switch (j & CTM_F_MASK) {
+ case CTM_F_Name: /* XXX check for garbage and .. */
+ GETFIELDCOPY(name,sep);
+ j = strlen(name);
+ if(name[j-1] == '/' && !slashwarn) {
+ fprintf(stderr,"Warning: contains trailing slash\n");
+ slashwarn++;
+ }
+ if (name[0] == '/') {
+ Fatal("Absolute paths are illegal.");
+ return Exit_Mess;
+ }
+ q = name;
+ for (;;) {
+ if (q[0] == '.' && q[1] == '.')
+ if (q[2] == '/' || q[2] == '\0') {
+ Fatal("Paths containing '..' are illegal.");
+ return Exit_Mess;
+ }
+ if ((q = strchr(q, '/')) == NULL)
+ break;
+ q++;
+ }
+
+ /* if we have been asked to `keep' files then skip
+ removes; i.e. we don't match these entries at
+ all. */
+ if (KeepIt &&
+ (!strcmp(sp->Key,"DR") || !strcmp(sp->Key,"FR"))) {
+ match = CTM_FILTER_DISABLE;
+ break;
+ }
+
+ /* If filter expression have been defined, match the
+ path name against the expression list. */
+
+ if (FilterList) {
+ struct CTM_Filter *filter;
+
+ for (filter = FilterList; filter;
+ filter = filter->Next) {
+ if (0 == regexec(&filter->CompiledRegex, name,
+ 0, 0, 0))
+ /* if the name matches, adopt the
+ action */
+ match = filter->Action;
+ }
+ }
+
+ /* Add up the total number of matches */
+ total_matches += match;
+ break;
+ case CTM_F_Uid:
+ GETFIELD(p,sep);
+ while(*p) {
+ if(!isdigit(*p)) {
+ Fatal("Non-digit in uid.");
+ return 32;
+ }
+ p++;
+ }
+ break;
+ case CTM_F_Gid:
+ GETFIELD(p,sep);
+ while(*p) {
+ if(!isdigit(*p)) {
+ Fatal("Non-digit in gid.");
+ return 32;
+ }
+ p++;
+ }
+ break;
+ case CTM_F_Mode:
+ GETFIELD(p,sep);
+ while(*p) {
+ if(!isdigit(*p)) {
+ Fatal("Non-digit in mode.");
+ return 32;
+ }
+ p++;
+ }
+ break;
+ case CTM_F_MD5:
+ if(j & CTM_Q_MD5_Chunk) {
+ GETFIELDCOPY(md5,sep); /* XXX check for garbage */
+ } else if(j & CTM_Q_MD5_Before) {
+ GETFIELD(p,sep); /* XXX check for garbage */
+ } else if(j & CTM_Q_MD5_After) {
+ GETFIELD(p,sep); /* XXX check for garbage */
+ } else {
+ fprintf(stderr,"List = 0x%x\n",j);
+ Fatal("Unqualified MD5.");
+ return 32;
+ }
+ break;
+ case CTM_F_Count:
+ GETBYTECNT(cnt,sep);
+ break;
+ case CTM_F_Bytes:
+ if(cnt < 0) WRONG
+ GETDATA(trash,cnt);
+ p = MD5Data(trash,cnt,md5_1);
+ if(md5 && strcmp(md5,p)) {
+ Fatal("Internal MD5 failed.");
+ return Exit_Garbage;
+ default:
+ fprintf(stderr,"List = 0x%x\n",j);
+ Fatal("List had garbage.");
+ return Exit_Garbage;
+ }
+ }
+ }
+ if(Verbose > 5)
+ putc('\n',stderr);
+ if(ListIt && match)
+ printf("> %s %s\n", sp->Key, name);
+ }
+
+ Delete(md5);
+ Delete(name);
+ Delete(trash);
+
+ q = MD5End (&ctx,md5_1);
+ if(Verbose > 2)
+ printf("Expecting Global MD5 <%s>\n",q);
+ GETFIELD(p,'\n'); /* <MD5> */
+ if(Verbose > 2)
+ printf("Reference Global MD5 <%s>\n",p);
+ if(strcmp(q,p)) {
+ Fatal("MD5 sum doesn't match.");
+ fprintf(stderr,"\tI have:<%s>\n",q);
+ fprintf(stderr,"\tShould have been:<%s>\n",p);
+ return Exit_Garbage;
+ }
+ if (-1 != getc(fd)) {
+ if(!Force) {
+ Fatal("Trailing junk in CTM-file. Can Force with -F.");
+ return 16;
+ }
+ }
+ if ((Verbose > 1) && (0 == total_matches))
+ printf("No matches in \"%s\"\n", FileName);
+ return (total_matches ? Exit_OK : Exit_NoMatch);
+}
diff --git a/usr.sbin/ctm/ctm/ctm_pass2.c b/usr.sbin/ctm/ctm/ctm_pass2.c
new file mode 100644
index 0000000..a0b857a
--- /dev/null
+++ b/usr.sbin/ctm/ctm/ctm_pass2.c
@@ -0,0 +1,301 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "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 "ctm.h"
+#define BADREAD 32
+
+/*---------------------------------------------------------------------------*/
+/* Pass2 -- Validate the incoming CTM-file.
+ */
+
+int
+Pass2(FILE *fd)
+{
+ u_char *p,*q,*md5=0;
+ MD5_CTX ctx;
+ int i,j,sep,cnt,fdesc;
+ u_char *trash=0,*name=0;
+ struct CTM_Syntax *sp;
+ struct stat st;
+ int ret = 0;
+ int match = 0;
+ char md5_1[33];
+ struct CTM_Filter *filter;
+ FILE *ed = NULL;
+ static char *template = NULL;
+
+ if(Verbose>3)
+ printf("Pass2 -- Checking if CTM-patch will apply\n");
+ MD5Init (&ctx);
+
+ GETFIELD(p,' '); if(strcmp("CTM_BEGIN",p)) WRONG
+ GETFIELD(p,' '); if(strcmp(Version,p)) WRONG
+ GETFIELD(p,' '); if(strcmp(Name,p)) WRONG
+ /* XXX Lookup name in /etc/ctm,conf, read stuff */
+ GETFIELD(p,' '); if(strcmp(Nbr,p)) WRONG
+ /* XXX Verify that this is the next patch to apply */
+ GETFIELD(p,' '); if(strcmp(TimeStamp,p)) WRONG
+ GETFIELD(p,'\n'); if(strcmp(Prefix,p)) WRONG
+ /* XXX drop or use ? */
+
+ for(;;) {
+ Delete(trash);
+ Delete(name);
+ Delete(md5);
+ cnt = -1;
+
+ /* if a filter list was specified, check file name against
+ the filters specified
+ if no filter was given operate on all files. */
+ match = (FilterList ?
+ !(FilterList->Action) : CTM_FILTER_ENABLE);
+
+ GETFIELD(p,' ');
+
+ if (p[0] != 'C' || p[1] != 'T' || p[2] != 'M') WRONG
+
+ if(!strcmp(p+3,"_END"))
+ break;
+
+ for(sp=Syntax;sp->Key;sp++)
+ if(!strcmp(p+3,sp->Key))
+ goto found;
+ WRONG
+ found:
+ for(i=0;(j = sp->List[i]);i++) {
+ if (sp->List[i+1] && (sp->List[i+1] & CTM_F_MASK) != CTM_F_Bytes)
+ sep = ' ';
+ else
+ sep = '\n';
+
+ switch (j & CTM_F_MASK) {
+ case CTM_F_Name:
+ GETNAMECOPY(name,sep,j,0);
+ /* If `keep' was specified, we won't remove any files,
+ so don't check if the file exists */
+ if (KeepIt &&
+ (!strcmp(sp->Key,"FR") || !strcmp(sp->Key,"DR"))) {
+ match = CTM_FILTER_DISABLE;
+ break;
+ }
+
+ for (filter = FilterList; filter; filter = filter->Next) if (0 == regexec(&filter->CompiledRegex, name,
+ 0, 0, 0)) {
+ match = filter->Action;
+ }
+
+ if (CTM_FILTER_DISABLE == match)
+ break; /* should ignore this file */
+
+ /* XXX Check DR DM rec's for parent-dir */
+ if(j & CTM_Q_Name_New) {
+ /* XXX Check DR FR rec's for item */
+ if(-1 != stat(name,&st)) {
+ fprintf(stderr," %s: %s exists.\n",
+ sp->Key,name);
+ ret |= Exit_Forcible;
+ }
+ break;
+ }
+ if(-1 == stat(name,&st)) {
+ fprintf(stderr," %s: %s doesn't exist.\n",
+ sp->Key,name);
+ if (sp->Key[1] == 'R')
+ ret |= Exit_Forcible;
+ else
+ ret |= Exit_NotOK;
+ break;
+ }
+ if (SetTime && getuid() && (getuid() != st.st_uid)) {
+ fprintf(stderr,
+ " %s: %s not mine, cannot set time.\n",
+ sp->Key,name);
+ ret |= Exit_NotOK;
+ }
+ if (j & CTM_Q_Name_Dir) {
+ if((st.st_mode & S_IFMT) != S_IFDIR) {
+ fprintf(stderr,
+ " %s: %s exist, but isn't dir.\n",
+ sp->Key,name);
+ ret |= Exit_NotOK;
+ }
+ break;
+ }
+ if (j & CTM_Q_Name_File) {
+ if((st.st_mode & S_IFMT) != S_IFREG) {
+ fprintf(stderr,
+ " %s: %s exist, but isn't file.\n",
+ sp->Key,name);
+ ret |= Exit_NotOK;
+ }
+ break;
+ }
+ break;
+ case CTM_F_Uid:
+ case CTM_F_Gid:
+ case CTM_F_Mode:
+ GETFIELD(p,sep);
+ break;
+ case CTM_F_MD5:
+ if(!name) WRONG
+ if(j & CTM_Q_MD5_Before) {
+ char *tmp;
+ GETFIELD(p,sep);
+ if(match && (st.st_mode & S_IFMT) == S_IFREG &&
+ (tmp = MD5File(name,md5_1)) != NULL &&
+ strcmp(tmp,p)) {
+ fprintf(stderr," %s: %s md5 mismatch.\n",
+ sp->Key,name);
+ GETFIELDCOPY(md5,sep);
+ if(md5 != NULL && strcmp(tmp,md5) == 0) {
+ fprintf(stderr," %s: %s already applied.\n",
+ sp->Key,name);
+ match = CTM_FILTER_DISABLE;
+ } else if(j & CTM_Q_MD5_Force) {
+ if(Force)
+ fprintf(stderr," Can and will force.\n");
+ else
+ fprintf(stderr," Could have forced.\n");
+ ret |= Exit_Forcible;
+ } else {
+ ret |= Exit_NotOK;
+ }
+ }
+ break;
+ } else if(j & CTM_Q_MD5_After) {
+ if(md5 == NULL) {
+ GETFIELDCOPY(md5,sep);
+ }
+ break;
+ }
+ /* Unqualified MD5 */
+ WRONG
+ break;
+ case CTM_F_Count:
+ GETBYTECNT(cnt,sep);
+ break;
+ case CTM_F_Bytes:
+ if(cnt < 0) WRONG
+ GETDATA(trash,cnt);
+ if (!match)
+ break;
+ if (!template) {
+ if (asprintf(&template, "%s/CTMclientXXXXXX",
+ TmpDir) == -1) {
+ fprintf(stderr, " %s: malloc failed.\n",
+ sp->Key);
+ ret |= Exit_Mess;
+ return ret;
+ }
+ }
+ if(!strcmp(sp->Key,"FN")) {
+ if ((p = strdup(template)) == NULL) {
+ fprintf(stderr, " %s: malloc failed.\n",
+ sp->Key);
+ ret |= Exit_Mess;
+ return ret;
+ }
+ if ((fdesc = mkstemp(p)) == -1) {
+ fprintf(stderr, " %s: mkstemp failed.\n",
+ sp->Key);
+ ret |= Exit_Mess;
+ Free(p);
+ return ret;
+ }
+ if (close(fdesc) == -1) {
+ fprintf(stderr, " %s: close failed.\n",
+ sp->Key);
+ ret |= Exit_Mess;
+ unlink(p);
+ Free(p);
+ return ret;
+ }
+ j = ctm_edit(trash,cnt,name,p);
+ if(j) {
+ fprintf(stderr," %s: %s edit returned %d.\n",
+ sp->Key,name,j);
+ ret |= j;
+ unlink(p);
+ Free(p);
+ return ret;
+ } else if(strcmp(md5,MD5File(p,md5_1))) {
+ fprintf(stderr," %s: %s edit fails.\n",
+ sp->Key,name);
+ ret |= Exit_Mess;
+ unlink(p);
+ Free(p);
+ return ret;
+ }
+ unlink(p);
+ Free(p);
+ } else if (!strcmp(sp->Key,"FE")) {
+ if ((p = strdup(template)) == NULL) {
+ fprintf(stderr, " %s: malloc failed.\n",
+ sp->Key);
+ ret |= Exit_Mess;
+ return ret;
+ }
+ if ((fdesc = mkstemp(p)) == -1) {
+ fprintf(stderr, " %s: mkstemp failed.\n",
+ sp->Key);
+ ret |= Exit_Mess;
+ Free(p);
+ return ret;
+ }
+ if (close(fdesc) == -1) {
+ fprintf(stderr, " %s: close failed.\n",
+ sp->Key);
+ ret |= Exit_Mess;
+ unlink(p);
+ Free(p);
+ return ret;
+ }
+ ed = popen("ed","w");
+ if (!ed) {
+ WRONG
+ }
+ fprintf(ed,"e %s\n", name);
+ if (cnt != fwrite(trash,1,cnt,ed)) {
+ warn("%s", name);
+ pclose(ed);
+ WRONG
+ }
+ fprintf(ed,"w %s\n",p);
+ if (pclose(ed)) {
+ warn("%s", p);
+ WRONG
+ }
+ if(strcmp(md5,MD5File(p,md5_1))) {
+ fprintf(stderr,"%s %s MD5 didn't come out right\n",
+ sp->Key, name);
+ WRONG
+ }
+ unlink(p);
+ Free(p);
+ }
+
+ break;
+ default: WRONG
+ }
+ }
+ }
+
+ Delete(trash);
+ Delete(name);
+ Delete(md5);
+
+ q = MD5End (&ctx,md5_1);
+ GETFIELD(p,'\n'); /* <MD5> */
+ if(strcmp(q,p)) WRONG
+ if (-1 != getc(fd)) WRONG
+ return ret;
+}
diff --git a/usr.sbin/ctm/ctm/ctm_pass3.c b/usr.sbin/ctm/ctm/ctm_pass3.c
new file mode 100644
index 0000000..34c6d87c1d
--- /dev/null
+++ b/usr.sbin/ctm/ctm/ctm_pass3.c
@@ -0,0 +1,295 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "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 "ctm.h"
+#define BADREAD 32
+
+/*---------------------------------------------------------------------------*/
+/* Pass3 -- Validate the incoming CTM-file.
+ */
+
+int
+settime(const char *name, const struct timeval *times)
+{
+ if (SetTime)
+ if (utimes(name,times)) {
+ warn("utimes(): %s", name);
+ return -1;
+ }
+ return 0;
+}
+
+int
+Pass3(FILE *fd)
+{
+ u_char *p,*q,buf[BUFSIZ];
+ MD5_CTX ctx;
+ int i,j,sep,cnt;
+ u_char *md5=0,*md5before=0,*trash=0,*name=0,*uid=0,*gid=0,*mode=0;
+ struct CTM_Syntax *sp;
+ FILE *ed=0;
+ struct stat st;
+ char md5_1[33];
+ int match=0;
+ struct timeval times[2];
+ struct CTM_Filter *filter = NULL;
+ if(Verbose>3)
+ printf("Pass3 -- Applying the CTM-patch\n");
+ MD5Init (&ctx);
+
+ GETFIELD(p,' '); if(strcmp("CTM_BEGIN",p)) WRONG
+ GETFIELD(p,' '); if(strcmp(Version,p)) WRONG
+ GETFIELD(p,' '); if(strcmp(Name,p)) WRONG
+ GETFIELD(p,' '); if(strcmp(Nbr,p)) WRONG
+ GETFIELD(p,' '); if(strcmp(TimeStamp,p)) WRONG
+ GETFIELD(p,'\n'); if(strcmp(Prefix,p)) WRONG
+
+ /*
+ * This would be cleaner if mktime() worked in UTC rather than
+ * local time.
+ */
+ if (SetTime) {
+ struct tm tm;
+ char *tz;
+ char buf[5];
+ int i;
+
+#define SUBSTR(off,len) strncpy(buf, &TimeStamp[off], len), buf[len] = '\0'
+#define WRONGDATE { fprintf(stderr, " %s failed date validation\n",\
+ TimeStamp); WRONG}
+
+ if (strlen(TimeStamp) != 15 || TimeStamp[14] != 'Z') WRONGDATE
+ for (i = 0; i < 14; i++)
+ if (!isdigit(TimeStamp[i])) WRONGDATE
+
+ tz = getenv("TZ");
+ if (setenv("TZ", "UTC", 1) < 0) WRONG
+ tzset();
+
+ tm.tm_isdst = tm.tm_gmtoff = 0;
+
+ SUBSTR(0, 4);
+ tm.tm_year = atoi(buf) - 1900;
+ SUBSTR(4, 2);
+ tm.tm_mon = atoi(buf) - 1;
+ if (tm.tm_mon < 0 || tm.tm_mon > 11) WRONGDATE
+ SUBSTR(6, 2);
+ tm.tm_mday = atoi(buf);
+ if (tm.tm_mday < 1 || tm.tm_mday > 31) WRONG;
+ SUBSTR(8, 2);
+ tm.tm_hour = atoi(buf);
+ if (tm.tm_hour > 24) WRONGDATE
+ SUBSTR(10, 2);
+ tm.tm_min = atoi(buf);
+ if (tm.tm_min > 59) WRONGDATE
+ SUBSTR(12, 2);
+ tm.tm_sec = atoi(buf);
+ if (tm.tm_min > 62) WRONGDATE /* allow leap seconds */
+
+ times[0].tv_sec = times[1].tv_sec = mktime(&tm);
+ if (times[0].tv_sec == -1) WRONGDATE
+ times[0].tv_usec = times[1].tv_usec = 0;
+
+ if (tz) {
+ if (setenv("TZ", tz, 1) < 0) WRONGDATE
+ } else {
+ unsetenv("TZ");
+ }
+ }
+
+ for(;;) {
+ Delete(md5);
+ Delete(uid);
+ Delete(gid);
+ Delete(mode);
+ Delete(md5before);
+ Delete(trash);
+ Delete(name);
+ cnt = -1;
+
+ GETFIELD(p,' ');
+
+ if (p[0] != 'C' || p[1] != 'T' || p[2] != 'M') WRONG
+
+ if(!strcmp(p+3,"_END"))
+ break;
+
+ for(sp=Syntax;sp->Key;sp++)
+ if(!strcmp(p+3,sp->Key))
+ goto found;
+ WRONG
+ found:
+ for(i=0;(j = sp->List[i]);i++) {
+ if (sp->List[i+1] && (sp->List[i+1] & CTM_F_MASK) != CTM_F_Bytes)
+ sep = ' ';
+ else
+ sep = '\n';
+
+ switch (j & CTM_F_MASK) {
+ case CTM_F_Name: GETNAMECOPY(name,sep,j, Verbose); break;
+ case CTM_F_Uid: GETFIELDCOPY(uid,sep); break;
+ case CTM_F_Gid: GETFIELDCOPY(gid,sep); break;
+ case CTM_F_Mode: GETFIELDCOPY(mode,sep); break;
+ case CTM_F_MD5:
+ if(j & CTM_Q_MD5_Before)
+ GETFIELDCOPY(md5before,sep);
+ else
+ GETFIELDCOPY(md5,sep);
+ break;
+ case CTM_F_Count: GETBYTECNT(cnt,sep); break;
+ case CTM_F_Bytes: GETDATA(trash,cnt); break;
+ default: WRONG
+ }
+ }
+ /* XXX This should go away. Disallow trailing '/' */
+ j = strlen(name)-1;
+ if(name[j] == '/') name[j] = '\0';
+
+ /*
+ * If a filter list is specified, run thru the filter list and
+ * match `name' against filters. If the name matches, set the
+ * required action to that specified in the filter.
+ * The default action if no filterlist is given is to match
+ * everything.
+ */
+
+ match = (FilterList ? !(FilterList->Action) : CTM_FILTER_ENABLE);
+ for (filter = FilterList; filter; filter = filter->Next) {
+ if (0 == regexec(&filter->CompiledRegex, name,
+ 0, 0, 0)) {
+ match = filter->Action;
+ }
+ }
+
+ if (CTM_FILTER_DISABLE == match) /* skip file if disabled */
+ continue;
+
+ if (Verbose > 0)
+ fprintf(stderr,"> %s %s\n",sp->Key,name);
+ if(!strcmp(sp->Key,"FM") || !strcmp(sp->Key, "FS")) {
+ i = open(name,O_WRONLY|O_CREAT|O_TRUNC,0666);
+ if(i < 0) {
+ warn("%s", name);
+ WRONG
+ }
+ if(cnt != write(i,trash,cnt)) {
+ warn("%s", name);
+ WRONG
+ }
+ close(i);
+ if(strcmp(md5,MD5File(name,md5_1))) {
+ fprintf(stderr," %s %s MD5 didn't come out right\n",
+ sp->Key,name);
+ WRONG
+ }
+ if (settime(name,times)) WRONG
+ continue;
+ }
+ if(!strcmp(sp->Key,"FE")) {
+ ed = popen("ed","w");
+ if(!ed) {
+ WRONG
+ }
+ fprintf(ed,"e %s\n",name);
+ if(cnt != fwrite(trash,1,cnt,ed)) {
+ warn("%s", name);
+ pclose(ed);
+ WRONG
+ }
+ fprintf(ed,"w %s\n",name);
+ if(pclose(ed)) {
+ warn("ed");
+ WRONG
+ }
+ if(strcmp(md5,MD5File(name,md5_1))) {
+ fprintf(stderr," %s %s MD5 didn't come out right\n",
+ sp->Key,name);
+ WRONG
+ }
+ if (settime(name,times)) WRONG
+ continue;
+ }
+ if(!strcmp(sp->Key,"FN")) {
+ strcpy(buf,name);
+ strcat(buf,TMPSUFF);
+ i = ctm_edit(trash,cnt,name,buf);
+ if(i) {
+ fprintf(stderr," %s %s Edit failed with code %d.\n",
+ sp->Key,name,i);
+ WRONG
+ }
+ if(strcmp(md5,MD5File(buf,md5_1))) {
+ fprintf(stderr," %s %s Edit failed MD5 check.\n",
+ sp->Key,name);
+ WRONG
+ }
+ if (rename(buf,name) == -1)
+ WRONG
+ if (settime(name,times)) WRONG
+ continue;
+ }
+ if(!strcmp(sp->Key,"DM")) {
+ if(0 > mkdir(name,0777)) {
+ sprintf(buf,"mkdir -p %s",name);
+ system(buf);
+ }
+ if(0 > stat(name,&st) || ((st.st_mode & S_IFMT) != S_IFDIR)) {
+ fprintf(stderr,"<%s> mkdir failed\n",name);
+ WRONG
+ }
+ if (settime(name,times)) WRONG
+ continue;
+ }
+ if(!strcmp(sp->Key,"FR")) {
+ if (KeepIt) {
+ if (Verbose > 1)
+ printf("<%s> not removed\n", name);
+ }
+ else if (0 != unlink(name)) {
+ fprintf(stderr,"<%s> unlink failed\n",name);
+ if (!Force)
+ WRONG
+ }
+ continue;
+ }
+ if(!strcmp(sp->Key,"DR")) {
+ /*
+ * We cannot use rmdir() because we do not get the directories
+ * in '-depth' order (cvs-cur.0018.gz for examples)
+ */
+ if (KeepIt) {
+ if (Verbose > 1) {
+ printf("<%s> not removed\n", name);
+ }
+ } else {
+ sprintf(buf,"rm -rf %s",name);
+ system(buf);
+ }
+ continue;
+ }
+ WRONG
+ }
+
+ Delete(md5);
+ Delete(uid);
+ Delete(gid);
+ Delete(mode);
+ Delete(md5before);
+ Delete(trash);
+ Delete(name);
+
+ q = MD5End (&ctx,md5_1);
+ GETFIELD(p,'\n');
+ if(strcmp(q,p)) WRONG
+ if (-1 != getc(fd)) WRONG
+ return 0;
+}
diff --git a/usr.sbin/ctm/ctm/ctm_passb.c b/usr.sbin/ctm/ctm/ctm_passb.c
new file mode 100644
index 0000000..ee3a69c5f
--- /dev/null
+++ b/usr.sbin/ctm/ctm/ctm_passb.c
@@ -0,0 +1,142 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <koshy@india.hp.com> 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. Joseph Koshy
+ * ----------------------------------------------------------------------------
+ *
+ * $FreeBSD$
+ *
+ */
+
+#include "ctm.h"
+#define BADREAD 32
+
+/*---------------------------------------------------------------------------*/
+/* PassB -- Backup modified files.
+ */
+
+int
+PassB(FILE *fd)
+{
+ u_char *p,*q;
+ MD5_CTX ctx;
+ int i,j,sep,cnt;
+ u_char *md5=0,*md5before=0,*trash=0,*name=0,*uid=0,*gid=0,*mode=0;
+ struct CTM_Syntax *sp;
+ FILE *b = 0; /* backup command */
+ u_char buf[BUFSIZ];
+ char md5_1[33];
+ int ret = 0;
+ int match = 0;
+ struct CTM_Filter *filter = NULL;
+
+ if(Verbose>3)
+ printf("PassB -- Backing up files which would be changed.\n");
+
+ MD5Init (&ctx);
+ snprintf(buf, sizeof(buf), fmtcheck(TarCmd, TARCMD), BackupFile);
+ b=popen(buf, "w");
+ if(!b) { warn("%s", buf); return Exit_Garbage; }
+
+ GETFIELD(p,' '); if(strcmp("CTM_BEGIN",p)) WRONG
+ GETFIELD(p,' '); if(strcmp(Version,p)) WRONG
+ GETFIELD(p,' '); if(strcmp(Name,p)) WRONG
+ GETFIELD(p,' '); if(strcmp(Nbr,p)) WRONG
+ GETFIELD(p,' '); if(strcmp(TimeStamp,p)) WRONG
+ GETFIELD(p,'\n'); if(strcmp(Prefix,p)) WRONG
+
+ for(;;) {
+ Delete(md5);
+ Delete(uid);
+ Delete(gid);
+ Delete(mode);
+ Delete(md5before);
+ Delete(trash);
+ Delete(name);
+ cnt = -1;
+
+ GETFIELD(p,' ');
+
+ if (p[0] != 'C' || p[1] != 'T' || p[2] != 'M') WRONG
+
+ if(!strcmp(p+3,"_END"))
+ break;
+
+ for(sp=Syntax;sp->Key;sp++)
+ if(!strcmp(p+3,sp->Key))
+ goto found;
+ WRONG
+ found:
+ for(i=0;(j = sp->List[i]);i++) {
+ if (sp->List[i+1] && (sp->List[i+1] & CTM_F_MASK) != CTM_F_Bytes)
+ sep = ' ';
+ else
+ sep = '\n';
+
+ switch (j & CTM_F_MASK) {
+ case CTM_F_Name: GETNAMECOPY(name,sep,j, Verbose); break;
+ case CTM_F_Uid: GETFIELDCOPY(uid,sep); break;
+ case CTM_F_Gid: GETFIELDCOPY(gid,sep); break;
+ case CTM_F_Mode: GETFIELDCOPY(mode,sep); break;
+ case CTM_F_MD5:
+ if(j & CTM_Q_MD5_Before)
+ GETFIELDCOPY(md5before,sep);
+ else
+ GETFIELDCOPY(md5,sep);
+ break;
+ case CTM_F_Count: GETBYTECNT(cnt,sep); break;
+ case CTM_F_Bytes: GETDATA(trash,cnt); break;
+ default: WRONG
+ }
+ }
+ /* XXX This should go away. Disallow trailing '/' */
+ j = strlen(name)-1;
+ if(name[j] == '/') name[j] = '\0';
+
+ if (KeepIt &&
+ (!strcmp(sp->Key,"DR") || !strcmp(sp->Key,"FR")))
+ continue;
+
+ /* match the name against the elements of the filter list. The
+ action associated with the last matched filter determines whether
+ this file should be ignored or backed up. */
+ match = (FilterList ? !(FilterList->Action) : CTM_FILTER_ENABLE);
+ for (filter = FilterList; filter; filter = filter->Next) {
+ if (0 == regexec(&filter->CompiledRegex, name, 0, 0, 0))
+ match = filter->Action;
+ }
+
+ if (CTM_FILTER_DISABLE == match)
+ continue;
+
+ if (!strcmp(sp->Key,"FS") || !strcmp(sp->Key,"FN") ||
+ !strcmp(sp->Key,"AS") || !strcmp(sp->Key,"DR") ||
+ !strcmp(sp->Key,"FR")) {
+ /* send name to the archiver for a backup */
+ cnt = strlen(name);
+ if (cnt != fwrite(name,1,cnt,b) || EOF == fputc('\n',b)) {
+ warn("%s", name);
+ pclose(b);
+ WRONG;
+ }
+ }
+ }
+
+ ret = pclose(b);
+
+ Delete(md5);
+ Delete(uid);
+ Delete(gid);
+ Delete(mode);
+ Delete(md5before);
+ Delete(trash);
+ Delete(name);
+
+ q = MD5End (&ctx,md5_1);
+ GETFIELD(p,'\n'); /* <MD5> */
+ if(strcmp(q,p)) WRONG
+ if (-1 != getc(fd)) WRONG
+ return ret;
+}
diff --git a/usr.sbin/ctm/ctm/ctm_syntax.c b/usr.sbin/ctm/ctm/ctm_syntax.c
new file mode 100644
index 0000000..6638943
--- /dev/null
+++ b/usr.sbin/ctm/ctm/ctm_syntax.c
@@ -0,0 +1,67 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "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 "ctm.h"
+
+/* The fields... */
+#define Name CTM_F_Name
+#define Uid CTM_F_Uid
+#define Gid CTM_F_Gid
+#define Mode CTM_F_Mode
+#define MD5 CTM_F_MD5
+#define Count CTM_F_Count
+#define Bytes CTM_F_Bytes
+
+/* The qualifiers... */
+#define File CTM_Q_Name_File
+#define Dir CTM_Q_Name_Dir
+#define New CTM_Q_Name_New
+#define Subst CTM_Q_Name_Subst
+#define After CTM_Q_MD5_After
+#define Before CTM_Q_MD5_Before
+#define Chunk CTM_Q_MD5_Chunk
+#define Force CTM_Q_MD5_Force
+
+static int ctmFM[] = /* File Make */
+ { Name|File|New|Subst, Uid, Gid, Mode,
+ MD5|After|Chunk, Count, Bytes,0 };
+
+static int ctmFS[] = /* File Substitute */
+ { Name|File|Subst, Uid, Gid, Mode,
+ MD5|Before|Force, MD5|After|Chunk, Count, Bytes,0 };
+
+static int ctmFE[] = /* File Edit */
+ { Name|File|Subst, Uid, Gid, Mode,
+ MD5|Before, MD5|After, Count, Bytes,0 };
+
+static int ctmFR[] = /* File Remove */
+ { Name|File|Subst, MD5|Before, 0 };
+
+static int ctmAS[] = /* Attribute Substitute */
+ { Name|Subst, Uid, Gid, Mode, 0 };
+
+static int ctmDM[] = /* Directory Make */
+ { Name|Dir|New , Uid, Gid, Mode, 0 };
+
+static int ctmDR[] = /* Directory Remove */
+ { Name|Dir, 0 };
+
+struct CTM_Syntax Syntax[] = {
+ { "FM", ctmFM },
+ { "FS", ctmFS },
+ { "FE", ctmFE },
+ { "FN", ctmFE },
+ { "FR", ctmFR },
+ { "AS", ctmAS },
+ { "DM", ctmDM },
+ { "DR", ctmDR },
+ { 0, 0} };
diff --git a/usr.sbin/ctm/ctm_dequeue/Makefile b/usr.sbin/ctm/ctm_dequeue/Makefile
new file mode 100644
index 0000000..d2f5648
--- /dev/null
+++ b/usr.sbin/ctm/ctm_dequeue/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../ctm_rmail
+
+PROG= ctm_dequeue
+MAN=
+SRCS= ctm_dequeue.c error.c
+
+CFLAGS+= -I${.CURDIR}/../ctm_rmail
+
+WARNS?= 1
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ctm/ctm_dequeue/Makefile.depend b/usr.sbin/ctm/ctm_dequeue/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/ctm/ctm_dequeue/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ctm/ctm_dequeue/ctm_dequeue.c b/usr.sbin/ctm/ctm_dequeue/ctm_dequeue.c
new file mode 100644
index 0000000..2dd6e86
--- /dev/null
+++ b/usr.sbin/ctm/ctm_dequeue/ctm_dequeue.c
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 1996, Gary J. Palmer
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer,
+ * verbatim and that no modifications are made prior to this
+ * point in 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 AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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$
+ */
+
+/*
+ * ctm_dequeue: Dequeue queued delta pieces and mail them.
+ *
+ * The pieces have already been packaged up as mail messages by ctm_smail,
+ * and will be simply passed to sendmail in alphabetical order.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <fts.h>
+#include <limits.h>
+#include <errno.h>
+#include <paths.h>
+#include "error.h"
+#include "options.h"
+
+#define DEFAULT_NUM 1 /* Default number of pieces mailed per run. */
+
+int fts_sort(const FTSENT * const *, const FTSENT * const *);
+int run_sendmail(int ifd);
+
+int
+main(int argc, char **argv)
+{
+ char *log_file = NULL;
+ char *queue_dir = NULL;
+ char *list[2];
+ int num_to_send = DEFAULT_NUM, chunk;
+ int fd;
+ FTS *fts;
+ FTSENT *ftsent;
+ int piece, npieces;
+ char filename[PATH_MAX];
+
+ err_prog_name(argv[0]);
+
+ OPTIONS("[-l log] [-n num] queuedir")
+ NUMBER('n', num_to_send)
+ STRING('l', log_file)
+ ENDOPTS
+
+ if (argc != 2)
+ usage();
+
+ if (log_file)
+ err_set_log(log_file);
+
+ queue_dir = argv[1];
+ list[0] = queue_dir;
+ list[1] = NULL;
+
+ fts = fts_open(list, FTS_PHYSICAL|FTS_COMFOLLOW, fts_sort);
+ if (fts == NULL)
+ {
+ err("fts failed on `%s'", queue_dir);
+ exit(1);
+ }
+
+ ftsent = fts_read(fts);
+ if (ftsent == NULL || ftsent->fts_info != FTS_D)
+ {
+ err("not a directory: %s", queue_dir);
+ exit(1);
+ }
+
+ ftsent = fts_children(fts, 0);
+ if (ftsent == NULL && errno)
+ {
+ err("*ftschildren failed");
+ exit(1);
+ }
+
+ for (chunk = 1; ftsent != NULL; ftsent = ftsent->fts_link)
+ {
+ /*
+ * Skip non-files and ctm_smail tmp files (ones starting with `.')
+ */
+ if (ftsent->fts_info != FTS_F || ftsent->fts_name[0] == '.')
+ continue;
+
+ sprintf(filename, "%s/%s", queue_dir, ftsent->fts_name);
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ {
+ err("*open: %s", filename);
+ exit(1);
+ }
+
+ if (run_sendmail(fd))
+ exit(1);
+
+ close(fd);
+
+ if (unlink(filename) < 0)
+ {
+ err("*unlink: %s", filename);
+ exit(1);
+ }
+
+ /*
+ * Deduce the delta, piece number, and number of pieces from
+ * the name of the file in the queue. Ideally, we should be
+ * able to get the mail alias name too.
+ *
+ * NOTE: This depends intimately on the queue name used in ctm_smail.
+ */
+ npieces = atoi(&ftsent->fts_name[ftsent->fts_namelen-3]);
+ piece = atoi(&ftsent->fts_name[ftsent->fts_namelen-7]);
+ err("%.*s %d/%d sent", (int)(ftsent->fts_namelen-8), ftsent->fts_name,
+ piece, npieces);
+
+ if (chunk++ == num_to_send)
+ break;
+ }
+
+ fts_close(fts);
+
+ return(0);
+}
+
+int
+fts_sort(const FTSENT * const * a, const FTSENT * const * b)
+{
+ if ((*a)->fts_info != FTS_F)
+ return(0);
+ if ((*a)->fts_info != FTS_F)
+ return(0);
+
+ return (strcmp((*a)->fts_name, (*b)->fts_name));
+}
+
+/*
+ * Run sendmail with the given file descriptor as standard input.
+ * Sendmail will decode the destination from the message contents.
+ * Returns 0 on success, 1 on failure.
+ */
+int
+run_sendmail(int ifd)
+{
+ pid_t child, pid;
+ int status;
+
+ switch (child = fork())
+ {
+ case -1:
+ err("*fork");
+ return(1);
+
+ case 0: /* Child */
+ dup2(ifd, 0);
+ execl(_PATH_SENDMAIL, _PATH_SENDMAIL, "-odq", "-t", (char *)NULL);
+ err("*exec: %s", _PATH_SENDMAIL);
+ _exit(1);
+
+ default: /* Parent */
+ while ((pid = wait(&status)) != child)
+ {
+ if (pid == -1 && errno != EINTR)
+ {
+ err("*wait");
+ return(1);
+ }
+ }
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
+ {
+ err("sendmail failed");
+ return(1);
+ }
+ }
+
+ return(0);
+}
diff --git a/usr.sbin/ctm/ctm_rmail/Makefile b/usr.sbin/ctm/ctm_rmail/Makefile
new file mode 100644
index 0000000..1278ab4
--- /dev/null
+++ b/usr.sbin/ctm/ctm_rmail/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+PROG= ctm_rmail
+MLINKS= ctm_rmail.1 ctm_smail.1 \
+ ctm_rmail.1 ctm_dequeue.1
+SRCS= ctm_rmail.c error.c
+
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ctm/ctm_rmail/Makefile.depend b/usr.sbin/ctm/ctm_rmail/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/ctm/ctm_rmail/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ctm/ctm_rmail/ctm_rmail.1 b/usr.sbin/ctm/ctm_rmail/ctm_rmail.1
new file mode 100644
index 0000000..232f646
--- /dev/null
+++ b/usr.sbin/ctm/ctm_rmail/ctm_rmail.1
@@ -0,0 +1,510 @@
+.\" NOTICE: This is free documentation. I hope you get some use from these
+.\" words. In return you should think about all the nice people who sweat
+.\" blood to document their free software. Maybe you should write some
+.\" documentation and give it away. Maybe with a free program attached!
+.\"
+.\" Author: Stephen McKay
+.\"
+.\" $FreeBSD$
+.\"
+.Dd January 24, 1995
+.Dt CTM_MAIL 1
+.Os
+.Sh NAME
+.Nm ctm_smail ,
+.Nm ctm_dequeue ,
+.Nm ctm_rmail
+.Nd send and receive
+.Xr ctm 1
+deltas via mail
+.Sh SYNOPSIS
+.Nm ctm_smail
+.Op Fl l Ar log
+.Op Fl m Ar maxmsgsize
+.Op Fl c Ar maxctmsize
+.Op Fl q Ar queue-dir
+.Ar ctm-delta
+.Ar mail-alias
+.Nm ctm_dequeue
+.Op Fl l Ar log
+.Op Fl n Ar numchunks
+.Ar queue-dir
+.Nm ctm_rmail
+.Op Fl Dfuv
+.Op Fl l Ar log
+.Op Fl p Ar piecedir
+.Op Fl d Ar deltadir
+.Op Fl b Ar basedir
+.Op Ar
+.Sh DESCRIPTION
+In conjunction with the
+.Xr ctm 1
+command,
+.Nm ctm_smail ,
+.Nm ctm_dequeue
+and
+.Nm ctm_rmail
+are used to distribute changes to a source tree via email.
+The
+.Nm ctm_smail
+utility is given a compressed
+.Xr ctm
+delta, and a mailing list to send it to.
+It splits the delta into manageable
+pieces, encodes them as mail messages and sends them to the mailing list
+(optionally queued to spread the mail load).
+Each recipient uses
+.Nm ctm_rmail
+(either manually or automatically) to decode and reassemble the delta, and
+optionally call
+.Xr ctm
+to apply it to the source tree.
+At the moment,
+several source trees are distributed, and by several sites.
+These include
+the
+.Fx Ns -current
+source and CVS trees, distributed by
+.Li freefall.FreeBSD.org .
+.Pp
+Command line arguments for
+.Nm ctm_smail :
+.Bl -tag -width indent
+.It Fl l Ar log
+Instead of appearing on
+.Em stderr ,
+error diagnostics and informational messages (other than command line errors)
+are time stamped and written to the file
+.Em log .
+.It Fl m Ar maxmsgsize
+Limit the maximum size mail message that
+.Nm ctm_smail
+is allowed to send.
+It is approximate since mail headers and other niceties
+are not counted in this limit.
+If not specified, it will default to 64000
+bytes, leaving room for 1535 bytes of headers before the rumoured 64k mail
+limit.
+.It Fl c Ar maxctmsize
+Limit the maximum size delta that will be sent.
+Deltas bigger that this
+limit will cause an apology mail message to be sent to the mailing list.
+This is to prevent massive changes overwhelming users' mail boxes.
+Note that
+this is the size before encoding.
+Encoding causes a 4/3 size increase before
+mail headers are added.
+If not specified, there is no limit.
+.It Fl q Ar queue-dir
+Instead of mailing the delta pieces now, store them in the given directory
+to be mailed later using
+.Nm ctm_dequeue .
+This feature allows the mailing of large deltas to be spread out over
+hours or even days to limit the impact on recipients with limited network
+bandwidth or small mail spool areas.
+.El
+.Pp
+.Ar ctm-delta
+is the delta to be sent, and
+.Ar mail-alias
+is the mailing list to send the delta to.
+The mail messages are sent using
+.Xr sendmail 8 .
+.Pp
+Command line arguments for
+.Nm ctm_dequeue :
+.Bl -tag -width indent
+.It Fl l Ar log
+Instead of appearing on
+.Em stderr ,
+error diagnostics and informational messages (other than command line errors)
+are time stamped and written to the file
+.Em log .
+.It Fl n Ar numchunks
+Limit the number of mail messages that
+.Nm ctm_dequeue
+will send per run.
+By default,
+.Nm ctm_dequeue
+will send one mail message per run.
+.El
+.Pp
+.Ar queuedir
+is the directory containing the mail messages stored by
+.Nm ctm_smail .
+Up to
+.Ar numchunks
+mail messages will be sent in each run.
+The recipient mailing list is already
+encoded in the queued files.
+.Pp
+It is safe to run
+.Nm ctm_dequeue
+while
+.Nm ctm_smail
+is adding entries to the queue, or even to run
+.Nm ctm_smail
+multiple times concurrently, but a separate queue directory should be used
+for each tree being distributed.
+This is because entries are served in
+alphabetical order, and one tree will be unfairly serviced before any others,
+based on the delta names, not delta creation times.
+.Pp
+Command line arguments for
+.Nm ctm_rmail :
+.Bl -tag -width indent
+.It Fl l Ar log
+Instead of appearing on
+.Em stderr ,
+error diagnostics and informational messages (other than command line errors)
+are time stamped and written to the file
+.Em log .
+.It Fl p Ar piecedir
+Collect pieces of deltas in this directory.
+Each piece corresponds to a
+single mail message.
+Pieces are removed when complete deltas are built.
+If this flag is not given, no input files will be read, but completed
+deltas may still be applied with
+.Xr ctm
+if the
+.Fl b
+flag is given.
+.It Fl d Ar deltadir
+Collect completed deltas in this directory.
+Deltas are built from one or
+more pieces when all pieces are present.
+.It Fl b Ar basedir
+Apply any completed deltas to this source tree.
+If this flag is not given,
+deltas will be stored, but not applied.
+The user may then apply the deltas
+manually, or by using
+.Nm ctm_rmail
+without the
+.Fl p
+flag.
+Deltas will not be applied if they do not match the
+.Li .ctm_status
+file in
+.Ar basedir
+(or if
+.Li .ctm_status
+does not exist).
+.It Fl D
+Delete deltas after successful application by
+.Xr ctm .
+It is probably a good idea to avoid this flag (and keep all the deltas) as
+.Xr ctm
+has the ability to recover small groups of files from a full set of deltas.
+.It Fl f
+Fork and execute in the background while applying deltas with
+.Xr ctm .
+This is useful when automatically invoking
+.Nm ctm_rmail
+from
+.Xr sendmail
+because
+.Xr ctm
+can take a very long time to complete, causing other people's mail to
+be delayed, and can in theory cause spurious
+mail retransmission due to the remote
+.Xr sendmail
+timing out, or even termination of
+.Nm ctm_rmail
+by mail filters such as
+.Xr "MH's"
+.Xr slocal .
+Do not worry about zillions of background
+.Xr ctm
+processes loading your machine, since locking is used to prevent more than one
+.Xr ctm
+invocation at a time.
+.It Fl u
+Pass the
+.Fl u
+flag to the
+.Xr ctm
+command when applying the complete deltas, causing it to set the modification
+time of created and modified files to the CTM delta creation time.
+.It Fl v
+Pass the
+.Fl v
+flag to the
+.Xr ctm
+command when applying the complete deltas, causing a more informative
+output.
+All
+.Xr ctm
+output appears in the
+.Nm ctm_rmail
+log file.
+.El
+.Pp
+The file arguments (or
+.Em stdin ,
+if there are none) are scanned for delta pieces.
+Multiple delta pieces
+can be read from a single file, so an entire maildrop can be scanned
+and processed with a single command.
+.Pp
+It is safe to invoke
+.Nm ctm_rmail
+multiple times concurrently (with different input files),
+as might happen when
+.Xr sendmail
+is delivering mail asynchronously.
+This is because locking is used to
+keep things orderly.
+.Sh FILE FORMAT
+Following are the important parts of an actual (very small) delta piece:
+.Bd -literal
+From: owner-src-cur
+To: src-cur
+Subject: ctm-mail src-cur.0003.gz 1/4
+
+CTM_MAIL BEGIN src-cur.0003.gz 1 4
+H4sIAAAAAAACA3VU72/bNhD9bP0VByQoEiyRSZEUSQP9kKTeYCR2gDTdsGFAwB/HRogtG5K8NCj6
+v4+UZSdtUQh6Rz0eee/xaF/dzx8up3/MFlDkBNrGnbttAwyo1pxoRgoiBNX/QJ5d3c9/X8DcPGGo
+lggkPiXngE4W1gUjKPJCYyk5MZRbIqmNW/ASglIFcdwIzTUxaAqhnCPcBqloKEkJVNDMF0Azk+Bo
+dDzzk0Ods/+A5gXv9YyJHjMCtJwQNeESNma7hOmXDRxn
+CTM_MAIL END 61065
+.Ed
+.Pp
+The subject of the message always begins with
+.Dq ctm-mail
+followed by the name of the delta, which piece this is, and how many total
+pieces there are.
+The data are bracketed by
+.Dq CTM_MAIL BEGIN
+and
+.Dq CTM_MAIL END
+lines, duplicating the information in the subject line, plus a simple checksum.
+.Pp
+If the delta exceeds
+.Ar maxctmsize ,
+then a message like this will be received instead:
+.Bd -literal
+From: owner-src-cur
+To: src-cur
+Subject: ctm-notice src-cur.0999.gz
+
+src-cur.0999.gz is 792843 bytes. The limit is 300000 bytes.
+
+You can retrieve this delta via ftp.
+.Ed
+.Pp
+You are then on your own!
+.Sh ENVIRONMENT
+If deltas are to be applied then
+.Xr ctm 1
+and
+.Xr gunzip 1
+must be in your
+.Ev PATH .
+.Sh FILES
+.Bl -tag -width indent
+.It Pa QUEUEDIR/*
+Pieces of deltas encoded as mail messages waiting to be sent to the
+mailing list.
+.It Pa PIECEDIR/*
+Pieces of deltas waiting for the rest to arrive.
+.It Pa DELTADIR/*
+Completed deltas.
+.It Pa BASEDIR/.ctm_status
+File containing the name and number of the next delta to be applied to this
+source tree.
+.El
+.Sh EXIT STATUS
+The
+.Nm ctm_smail ,
+.Nm ctm_dequeue
+and
+.Nm ctm_rmail
+utilities return exit status 0 for success, and 1 for various failures.
+The
+.Nm ctm_rmail
+utility is expected to be called from a mail transfer program, and thus signals
+failure only when the input mail message should be bounced (preferably into
+your regular maildrop, not back to the sender).
+In short, failure to
+apply a completed delta with
+.Xr ctm
+is not considered an error important enough to bounce the mail, and
+.Nm ctm_rmail
+returns an exit status of 0.
+.Sh EXAMPLES
+To send delta 32 of
+.Em src-cur
+to a group of wonderful code hackers known to
+.Xr sendmail
+as
+.Em src-guys ,
+limiting the mail size to roughly 60000 bytes, you could use:
+.Bd -literal -offset indent
+ctm_smail -m 60000 /wherever/it/is/src-cur.0032.gz src-guys
+.Ed
+.Pp
+To decode every
+.Nm ctm-mail
+message in your mailbox, assemble them into complete deltas, then apply
+any deltas built or lying around, you could use:
+.Bd -literal -offset indent
+ctm_rmail -p ~/pieces -d ~/deltas -b /usr/ctm-src-cur $MAIL
+.Ed
+.Pp
+(Note that no messages are deleted by
+.Nm ctm_rmail .
+Any mail reader could be used for that purpose.)
+.Pp
+To create a mail alias called
+.Em receiver-dude
+that will automatically decode and assemble deltas, but not apply them,
+you could put the following lines in your
+.Pa /etc/mail/aliases
+file (assuming the
+.Pa /ctm/tmp
+and
+.Pa /ctm/deltas
+directories and
+.Pa /ctm/log
+file are writable by user
+.Em daemon
+or group
+.Em wheel ) :
+.Bd -literal -offset indent
+receiver-dude: "|ctm_rmail -p /ctm/tmp -d /ctm/deltas -l /ctm/log"
+owner-receiver-dude: real_dude@wherever.you.like
+.Ed
+.Pp
+The second line will catch failures and drop them into your regular mailbox,
+or wherever else you like.
+.Pp
+To apply all the deltas collected, and delete those applied, you could use:
+.Bd -literal -offset indent
+ctm_rmail -D -d /ctm/deltas -b /ctm/src-cur -l /ctm/apply.log
+.Ed
+.Pp
+For maximum flexibility, consider this excerpt from a
+.Xr procmail
+script:
+.Bd -literal -offset indent
+PATH=$HOME/bin:$PATH
+
+:0 w
+* ^Subject: ctm-mail cvs-cur
+| ctm_incoming
+.Ed
+.Pp
+together with the
+shell script
+.Pa ~/bin/ctm_incoming :
+.Bd -literal -offset indent
+#! /bin/sh
+PATH="$HOME/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin"
+export PATH
+
+cd $HOME/ctm && ctm_rmail -f -p pieces -d deltas -l log -b /ctm
+.Ed
+.Pp
+which will deposit all
+.Xr ctm
+deltas in
+.Pa ~/ctm/deltas ,
+apply them to the tree in
+.Pa /ctm ,
+and drop any failures into your regular mail box.
+Note the
+.Ev PATH
+manipulation in
+.Pa ctm_incoming
+which allows
+.Nm ctm_rmail
+to execute
+.Xr ctm 1
+on the
+.Pq non- Ns Fx
+machine that this example was taken from.
+.Sh SECURITY
+On its own, CTM is an insecure protocol
+- there is no authentication performed that the
+changes applied to the source code were sent by a
+trusted party, and so care should be taken if the
+CTM deltas are obtained via an unauthenticated
+medium such as regular email.
+It is a relatively simple matter for an attacker
+to forge a CTM delta to replace or precede the
+legitimate one and insert malicious code into your
+source tree.
+If the legitimate delta is somehow prevented from
+arriving, this will go unnoticed until a later
+delta attempts to touch the same file, at which
+point the MD5 checksum will fail.
+.Pp
+To remedy this insecurity, CTM delta pieces generated by
+FreeBSD.org are cryptographically signed in a
+format compatible with the GNU Privacy Guard
+utility, available in /usr/ports/security/gpg, and
+the Pretty Good Privacy v5 utility,
+/usr/ports/security/pgp5.
+The relevant public key can be obtained by
+fingering ctm@FreeBSD.org.
+.Pp
+CTM deltas which are thus signed cannot be
+undetectably altered by an attacker.
+Therefore it is recommended that you make use of
+GPG or PGP5 to verify the signatures if you
+receive your CTM deltas via email.
+.Sh DIAGNOSTICS
+In normal operation,
+.Nm ctm_smail
+will report messages like:
+.Bd -literal -offset indent
+ctm_smail: src-cur.0250.gz 1/2 sent to src-guys
+.Ed
+.Pp
+or, if queueing,
+.Bd -literal -offset indent
+ctm_smail: src-cur.0250.gz 1/2 queued for src-guys
+.Ed
+.Pp
+The
+.Nm ctm_dequeue
+utility will report messages like:
+.Bd -literal -offset indent
+ctm_dequeue: src-cur.0250.gz 1/2 sent
+.Ed
+.Pp
+The
+.Nm ctm_rmail
+utility will report messages like:
+.Bd -literal -offset indent
+ctm_rmail: src-cur.0250.gz 1/2 stored
+ctm_rmail: src-cur.0250.gz 2/2 stored
+ctm_rmail: src-cur.0250.gz complete
+.Ed
+.Pp
+If any of the input files do not contain a valid delta piece,
+.Nm ctm_rmail
+will report:
+.Bd -literal -offset indent
+ctm_rmail: message contains no delta
+.Ed
+.Pp
+and return an exit status of 1.
+You can use this to redirect wayward messages
+back into your real mailbox if your mail filter goes wonky.
+.Pp
+These messages go to
+.Em stderr
+or to the log file.
+Messages from
+.Xr ctm 1
+turn up here too.
+Error messages should be self explanatory.
+.Sh SEE ALSO
+.Xr ctm 1 ,
+.Xr ctm 5
+.\" .Sh HISTORY
+.Sh AUTHORS
+.An Stephen McKay Aq Mt mckay@FreeBSD.org
diff --git a/usr.sbin/ctm/ctm_rmail/ctm_rmail.c b/usr.sbin/ctm/ctm_rmail/ctm_rmail.c
new file mode 100644
index 0000000..a46a58aa
--- /dev/null
+++ b/usr.sbin/ctm/ctm_rmail/ctm_rmail.c
@@ -0,0 +1,672 @@
+/*
+ * Accept one (or more) ASCII encoded chunks that together make a compressed
+ * CTM delta. Decode them and reconstruct the deltas. Any completed
+ * deltas may be passed to ctm for unpacking.
+ *
+ * Author: Stephen McKay
+ *
+ * NOTICE: This is free software. I hope you get some use from this program.
+ * In return you should think about all the nice people who give away software.
+ * Maybe you should write some free software too.
+ *
+ * $FreeBSD$
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <limits.h>
+#include "error.h"
+#include "options.h"
+
+#define CTM_STATUS ".ctm_status"
+
+char *piece_dir = NULL; /* Where to store pieces of deltas. */
+char *delta_dir = NULL; /* Where to store completed deltas. */
+char *base_dir = NULL; /* The tree to apply deltas to. */
+int delete_after = 0; /* Delete deltas after ctm applies them. */
+int apply_verbose = 0; /* Run with '-v' */
+int set_time = 0; /* Set the time of the files that is changed. */
+int mask = 0; /* The current umask */
+
+void apply_complete(void);
+int read_piece(char *input_file);
+int combine_if_complete(char *delta, int pce, int npieces);
+int combine(char *delta, int npieces, char *dname, char *pname, char *tname);
+int decode_line(char *line, char *out_buf);
+int lock_file(char *name);
+
+/*
+ * If given a '-p' flag, read encoded delta pieces from stdin or file
+ * arguments, decode them and assemble any completed deltas. If given
+ * a '-b' flag, pass any completed deltas to 'ctm' for application to
+ * the source tree. The '-d' flag is mandatory, but either of '-p' or
+ * '-b' can be omitted. If given the '-l' flag, notes and errors will
+ * be timestamped and written to the given file.
+ *
+ * Exit status is 0 for success or 1 for indigestible input. That is,
+ * 0 means the encode input pieces were decoded and stored, and 1 means
+ * some input was discarded. If a delta fails to apply, this won't be
+ * reflected in the exit status. In this case, the delta is left in
+ * 'deltadir'.
+ */
+int
+main(int argc, char **argv)
+ {
+ char *log_file = NULL;
+ int status = 0;
+ int fork_ctm = 0;
+
+ mask = umask(0);
+ umask(mask);
+
+ err_prog_name(argv[0]);
+
+ OPTIONS("[-Dfuv] [-p piecedir] [-d deltadir] [-b basedir] [-l log] [file ...]")
+ FLAG('D', delete_after)
+ FLAG('f', fork_ctm)
+ FLAG('u', set_time)
+ FLAG('v', apply_verbose)
+ STRING('p', piece_dir)
+ STRING('d', delta_dir)
+ STRING('b', base_dir)
+ STRING('l', log_file)
+ ENDOPTS
+
+ if (delta_dir == NULL)
+ usage();
+
+ if (piece_dir == NULL && (base_dir == NULL || argc > 1))
+ usage();
+
+ if (log_file != NULL)
+ err_set_log(log_file);
+
+ /*
+ * Digest each file in turn, or just stdin if no files were given.
+ */
+ if (argc <= 1)
+ {
+ if (piece_dir != NULL)
+ status = read_piece(NULL);
+ }
+ else
+ {
+ while (*++argv != NULL)
+ status |= read_piece(*argv);
+ }
+
+ /*
+ * Maybe it's time to look for and apply completed deltas with ctm.
+ *
+ * Shall we report back to sendmail immediately, and let a child do
+ * the work? Sendmail will be waiting for us to complete, delaying
+ * other mail, and possibly some intermediate process (like MH slocal)
+ * will terminate us if we take too long!
+ *
+ * If fork() fails, it's unlikely we'll be able to run ctm, so give up.
+ * Also, the child exit status is unimportant.
+ */
+ if (base_dir != NULL)
+ if (!fork_ctm || fork() == 0)
+ apply_complete();
+
+ return status;
+ }
+
+
+/*
+ * Construct the file name of a piece of a delta.
+ */
+#define mk_piece_name(fn,d,p,n) \
+ sprintf((fn), "%s/%s+%03d-%03d", piece_dir, (d), (p), (n))
+
+/*
+ * Construct the file name of an assembled delta.
+ */
+#define mk_delta_name(fn,d) \
+ sprintf((fn), "%s/%s", delta_dir, (d))
+
+/*
+ * If the next required delta is now present, let ctm lunch on it and any
+ * contiguous deltas.
+ */
+void
+apply_complete()
+ {
+ int i, dn;
+ int lfd;
+ FILE *fp, *ctm;
+ struct stat sb;
+ char class[20];
+ char delta[30];
+ char junk[2];
+ char fname[PATH_MAX];
+ char here[PATH_MAX];
+ char buf[PATH_MAX*2];
+
+ /*
+ * Grab a lock on the ctm mutex file so that we can be sure we are
+ * working alone, not fighting another ctm_rmail!
+ */
+ strcpy(fname, delta_dir);
+ strcat(fname, "/.mutex_apply");
+ if ((lfd = lock_file(fname)) < 0)
+ return;
+
+ /*
+ * Find out which delta ctm needs next.
+ */
+ sprintf(fname, "%s/%s", base_dir, CTM_STATUS);
+ if ((fp = fopen(fname, "r")) == NULL)
+ {
+ close(lfd);
+ return;
+ }
+
+ i = fscanf(fp, "%19s %d %c", class, &dn, junk);
+ fclose(fp);
+ if (i != 2)
+ {
+ close(lfd);
+ return;
+ }
+
+ /*
+ * We might need to convert the delta filename to an absolute pathname.
+ */
+ here[0] = '\0';
+ if (delta_dir[0] != '/')
+ {
+ getcwd(here, sizeof(here)-1);
+ i = strlen(here) - 1;
+ if (i >= 0 && here[i] != '/')
+ {
+ here[++i] = '/';
+ here[++i] = '\0';
+ }
+ }
+
+ /*
+ * Keep applying deltas until we run out or something bad happens.
+ */
+ for (;;)
+ {
+ sprintf(delta, "%s.%04d.gz", class, ++dn);
+ mk_delta_name(fname, delta);
+
+ if (stat(fname, &sb) < 0)
+ break;
+
+ sprintf(buf, "(cd %s && ctm %s%s%s%s) 2>&1", base_dir,
+ set_time ? "-u " : "",
+ apply_verbose ? "-v " : "", here, fname);
+ if ((ctm = popen(buf, "r")) == NULL)
+ {
+ err("ctm failed to apply %s", delta);
+ break;
+ }
+
+ while (fgets(buf, sizeof(buf), ctm) != NULL)
+ {
+ i = strlen(buf) - 1;
+ if (i >= 0 && buf[i] == '\n')
+ buf[i] = '\0';
+ err("ctm: %s", buf);
+ }
+
+ if (pclose(ctm) != 0)
+ {
+ err("ctm failed to apply %s", delta);
+ break;
+ }
+
+ if (delete_after)
+ unlink(fname);
+
+ err("%s applied%s", delta, delete_after ? " and deleted" : "");
+ }
+
+ /*
+ * Closing the lock file clears the lock.
+ */
+ close(lfd);
+ }
+
+
+/*
+ * This cheap plastic checksum effectively rotates our checksum-so-far
+ * left one, then adds the character. We only want 16 bits of it, and
+ * don't care what happens to the rest. It ain't much, but it's small.
+ */
+#define add_ck(sum,x) \
+ ((sum) += ((x)&0xff) + (sum) + (((sum)&0x8000) ? 1 : 0))
+
+
+/*
+ * Decode the data between BEGIN and END, and stash it in the staging area.
+ * Multiple pieces can be present in a single file, bracketed by BEGIN/END.
+ * If we have all pieces of a delta, combine them. Returns 0 on success,
+ * and 1 for any sort of failure.
+ */
+int
+read_piece(char *input_file)
+ {
+ int status = 0;
+ FILE *ifp, *ofp = 0;
+ int decoding = 0;
+ int got_one = 0;
+ int line_no = 0;
+ int i, n;
+ int pce, npieces;
+ unsigned claimed_cksum;
+ unsigned short cksum = 0;
+ char out_buf[200];
+ char line[200];
+ char delta[30];
+ char pname[PATH_MAX];
+ char tname[PATH_MAX];
+ char junk[2];
+
+ ifp = stdin;
+ if (input_file != NULL && (ifp = fopen(input_file, "r")) == NULL)
+ {
+ err("cannot open '%s' for reading", input_file);
+ return 1;
+ }
+
+ while (fgets(line, sizeof(line), ifp) != NULL)
+ {
+ line_no++;
+
+ /*
+ * Remove all trailing white space.
+ */
+ i = strlen(line) - 1;
+ while (i > 0 && isspace(line[i]))
+ line[i--] = '\0';
+
+ /*
+ * Look for the beginning of an encoded piece.
+ */
+ if (!decoding)
+ {
+ char *s;
+ int fd = -1;
+
+ if (sscanf(line, "CTM_MAIL BEGIN %29s %d %d %c",
+ delta, &pce, &npieces, junk) != 3)
+ continue;
+
+ while ((s = strchr(delta, '/')) != NULL)
+ *s = '_';
+
+ got_one++;
+ strcpy(tname, piece_dir);
+ strcat(tname, "/p.XXXXXXXXXX");
+ if ((fd = mkstemp(tname)) == -1 ||
+ (ofp = fdopen(fd, "w")) == NULL)
+ {
+ if (fd != -1) {
+ err("cannot open '%s' for writing", tname);
+ close(fd);
+ }
+ else
+ err("*mkstemp: '%s'", tname);
+ status++;
+ continue;
+ }
+
+ cksum = 0xffff;
+ decoding++;
+ continue;
+ }
+
+ /*
+ * We are decoding. Stop if we see the end flag.
+ */
+ if (sscanf(line, "CTM_MAIL END %d %c", &claimed_cksum, junk) == 1)
+ {
+ int e;
+
+ decoding = 0;
+
+ fflush(ofp);
+ e = ferror(ofp);
+ fclose(ofp);
+
+ if (e)
+ err("error writing %s", tname);
+
+ if (cksum != claimed_cksum)
+ err("checksum: read %d, calculated %d", claimed_cksum, cksum);
+
+ if (e || cksum != claimed_cksum)
+ {
+ err("%s %d/%d discarded", delta, pce, npieces);
+ unlink(tname);
+ status++;
+ continue;
+ }
+
+ mk_piece_name(pname, delta, pce, npieces);
+ if (rename(tname, pname) < 0)
+ {
+ err("*rename: '%s' to '%s'", tname, pname);
+ err("%s %d/%d lost!", delta, pce, npieces);
+ unlink(tname);
+ status++;
+ continue;
+ }
+
+ err("%s %d/%d stored", delta, pce, npieces);
+
+ if (!combine_if_complete(delta, pce, npieces))
+ status++;
+ continue;
+ }
+
+ /*
+ * Must be a line of encoded data. Decode it, sum it, and save it.
+ */
+ n = decode_line(line, out_buf);
+ if (n <= 0)
+ {
+ err("line %d: illegal character: '%c'", line_no, line[-n]);
+ err("%s %d/%d discarded", delta, pce, npieces);
+
+ fclose(ofp);
+ unlink(tname);
+
+ status++;
+ decoding = 0;
+ continue;
+ }
+
+ for (i = 0; i < n; i++)
+ add_ck(cksum, out_buf[i]);
+
+ fwrite(out_buf, sizeof(char), n, ofp);
+ }
+
+ if (decoding)
+ {
+ err("truncated file");
+ err("%s %d/%d discarded", delta, pce, npieces);
+
+ fclose(ofp);
+ unlink(tname);
+
+ status++;
+ }
+
+ if (ferror(ifp))
+ {
+ err("error reading %s", input_file == NULL ? "stdin" : input_file);
+ status++;
+ }
+
+ if (input_file != NULL)
+ fclose(ifp);
+
+ if (!got_one)
+ {
+ err("message contains no delta");
+ status++;
+ }
+
+ return (status != 0);
+ }
+
+
+/*
+ * Put the pieces together to form a delta, if they are all present.
+ * Returns 1 on success (even if we didn't do anything), and 0 on failure.
+ */
+int
+combine_if_complete(char *delta, int pce, int npieces)
+ {
+ int i, e;
+ int lfd;
+ struct stat sb;
+ char pname[PATH_MAX];
+ char dname[PATH_MAX];
+ char tname[PATH_MAX];
+
+ /*
+ * We can probably just rename() it into place if it is a small delta.
+ */
+ if (npieces == 1)
+ {
+ mk_delta_name(dname, delta);
+ mk_piece_name(pname, delta, 1, 1);
+ if (rename(pname, dname) == 0)
+ {
+ chmod(dname, 0666 & ~mask);
+ err("%s complete", delta);
+ return 1;
+ }
+ }
+
+ /*
+ * Grab a lock on the reassembly mutex file so that we can be sure we are
+ * working alone, not fighting another ctm_rmail!
+ */
+ strcpy(tname, delta_dir);
+ strcat(tname, "/.mutex_build");
+ if ((lfd = lock_file(tname)) < 0)
+ return 0;
+
+ /*
+ * Are all of the pieces present? Of course the current one is,
+ * unless all pieces are missing because another ctm_rmail has
+ * processed them already.
+ */
+ for (i = 1; i <= npieces; i++)
+ {
+ if (i == pce)
+ continue;
+ mk_piece_name(pname, delta, i, npieces);
+ if (stat(pname, &sb) < 0)
+ {
+ close(lfd);
+ return 1;
+ }
+ }
+
+ /*
+ * Stick them together. Let combine() use our file name buffers, since
+ * we're such good buddies. :-)
+ */
+ e = combine(delta, npieces, dname, pname, tname);
+ close(lfd);
+ return e;
+ }
+
+
+/*
+ * Put the pieces together to form a delta.
+ * Returns 1 on success, and 0 on failure.
+ * Note: dname, pname, and tname are room for some file names that just
+ * happened to by lying around in the calling routine. Waste not, want not!
+ */
+int
+combine(char *delta, int npieces, char *dname, char *pname, char *tname)
+ {
+ FILE *dfp, *pfp;
+ int i, n, e;
+ char buf[BUFSIZ];
+ int fd = -1;
+
+ strcpy(tname, delta_dir);
+ strcat(tname, "/d.XXXXXXXXXX");
+ if ((fd = mkstemp(tname)) == -1 ||
+ (dfp = fdopen(fd, "w")) == NULL)
+ {
+ if (fd != -1) {
+ close(fd);
+ err("cannot open '%s' for writing", tname);
+ }
+ else
+ err("*mkstemp: '%s'", tname);
+ return 0;
+ }
+
+ /*
+ * Reconstruct the delta by reading each piece in order.
+ */
+ for (i = 1; i <= npieces; i++)
+ {
+ mk_piece_name(pname, delta, i, npieces);
+ if ((pfp = fopen(pname, "r")) == NULL)
+ {
+ err("cannot open '%s' for reading", pname);
+ fclose(dfp);
+ unlink(tname);
+ return 0;
+ }
+ while ((n = fread(buf, sizeof(char), sizeof(buf), pfp)) != 0)
+ fwrite(buf, sizeof(char), n, dfp);
+ e = ferror(pfp);
+ fclose(pfp);
+ if (e)
+ {
+ err("error reading '%s'", pname);
+ fclose(dfp);
+ unlink(tname);
+ return 0;
+ }
+ }
+ fflush(dfp);
+ e = ferror(dfp);
+ fclose(dfp);
+ if (e)
+ {
+ err("error writing '%s'", tname);
+ unlink(tname);
+ return 0;
+ }
+
+ mk_delta_name(dname, delta);
+ if (rename(tname, dname) < 0)
+ {
+ err("*rename: '%s' to '%s'", tname, dname);
+ unlink(tname);
+ return 0;
+ }
+ chmod(dname, 0666 & ~mask);
+
+ /*
+ * Throw the pieces away.
+ */
+ for (i = 1; i <= npieces; i++)
+ {
+ mk_piece_name(pname, delta, i, npieces);
+ if (unlink(pname) < 0)
+ err("*unlink: '%s'", pname);
+ }
+
+ err("%s complete", delta);
+ return 1;
+ }
+
+
+/*
+ * MIME BASE64 decode table.
+ */
+static unsigned char from_b64[0x80] =
+ {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
+ 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
+ 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+ 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+ 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
+ 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
+ 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
+ 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
+ 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
+ };
+
+
+/*
+ * Decode a line of ASCII into binary. Returns the number of bytes in
+ * the output buffer, or < 0 on indigestable input. Error output is
+ * the negative of the index of the inedible character.
+ */
+int
+decode_line(char *line, char *out_buf)
+ {
+ unsigned char *ip = (unsigned char *)line;
+ unsigned char *op = (unsigned char *)out_buf;
+ unsigned long bits;
+ unsigned x;
+
+ for (;;)
+ {
+ if (*ip >= 0x80 || (x = from_b64[*ip]) >= 0x40)
+ break;
+ bits = x << 18;
+ ip++;
+ if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
+ {
+ bits |= x << 12;
+ *op++ = bits >> 16;
+ ip++;
+ if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
+ {
+ bits |= x << 6;
+ *op++ = bits >> 8;
+ ip++;
+ if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
+ {
+ bits |= x;
+ *op++ = bits;
+ ip++;
+ }
+ }
+ }
+ }
+
+ if (*ip == '\0' || *ip == '\n')
+ return op - (unsigned char *)out_buf;
+ else
+ return -(ip - (unsigned char *)line);
+ }
+
+
+/*
+ * Create and lock the given file.
+ *
+ * Clearing the lock is as simple as closing the file descriptor we return.
+ */
+int
+lock_file(char *name)
+ {
+ int lfd;
+
+ if ((lfd = open(name, O_WRONLY|O_CREAT, 0600)) < 0)
+ {
+ err("*open: '%s'", name);
+ return -1;
+ }
+ if (flock(lfd, LOCK_EX) < 0)
+ {
+ close(lfd);
+ err("*flock: '%s'", name);
+ return -1;
+ }
+ return lfd;
+ }
diff --git a/usr.sbin/ctm/ctm_rmail/error.c b/usr.sbin/ctm/ctm_rmail/error.c
new file mode 100644
index 0000000..56d3dc6
--- /dev/null
+++ b/usr.sbin/ctm/ctm_rmail/error.c
@@ -0,0 +1,102 @@
+/*
+ * Routines for logging error messages or other informative messages.
+ *
+ * Log messages can easily contain the program name, a time stamp, system
+ * error messages, and arbitrary printf-style strings, and can be directed
+ * to stderr or a log file.
+ *
+ * Author: Stephen McKay
+ *
+ * NOTICE: This is free software. I hope you get some use from this program.
+ * In return you should think about all the nice people who give away software.
+ * Maybe you should write some free software too.
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <time.h>
+#include <errno.h>
+#include "error.h"
+
+static FILE *error_fp = NULL;
+static char *prog = NULL;
+
+
+/*
+ * Log errors to the given file.
+ */
+void
+err_set_log(char *log_file)
+ {
+ FILE *fp;
+
+ if ((fp = fopen(log_file, "a")) == NULL)
+ err("cannot log to '%s'", log_file);
+ else
+ error_fp = fp;
+ }
+
+
+/*
+ * Set the error prefix if not logging to a file.
+ */
+void
+err_prog_name(char *name)
+ {
+ if ((prog = strrchr(name, '/')) == NULL)
+ prog = name;
+ else
+ prog++;
+ }
+
+
+/*
+ * Log an error.
+ *
+ * A leading '*' in the message format means we want the system errno
+ * decoded and appended.
+ */
+void
+err(const char *fmt, ...)
+ {
+ va_list ap;
+ time_t now;
+ struct tm *tm;
+ FILE *fp;
+ int x = errno;
+ int want_errno;
+
+ if ((fp = error_fp) == NULL)
+ {
+ fp = stderr;
+ if (prog != NULL)
+ fprintf(fp, "%s: ", prog);
+ }
+ else
+ {
+ time(&now);
+ tm = localtime(&now);
+ fprintf(fp, "%04d-%02d-%02d %02d:%02d ", tm->tm_year+1900,
+ tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min);
+ }
+
+ want_errno = 0;
+ if (*fmt == '*')
+ want_errno++, fmt++;
+
+ va_start(ap, fmt);
+ vfprintf(fp, fmt, ap);
+ va_end(ap);
+
+ if (want_errno)
+ fprintf(fp, ": %s", strerror(x));
+
+ fprintf(fp, "\n");
+ fflush(fp);
+ }
diff --git a/usr.sbin/ctm/ctm_rmail/error.h b/usr.sbin/ctm/ctm_rmail/error.h
new file mode 100644
index 0000000..c631b67
--- /dev/null
+++ b/usr.sbin/ctm/ctm_rmail/error.h
@@ -0,0 +1,5 @@
+/* $FreeBSD$ */
+
+extern void err_set_log(char *log_file);
+extern void err_prog_name(char *name);
+extern void err(const char *fmt, ...) __printflike(1, 2);
diff --git a/usr.sbin/ctm/ctm_rmail/options.h b/usr.sbin/ctm/ctm_rmail/options.h
new file mode 100644
index 0000000..86c1247
--- /dev/null
+++ b/usr.sbin/ctm/ctm_rmail/options.h
@@ -0,0 +1,139 @@
+/*
+ * Macros for processing command arguments.
+ *
+ * Conforms closely to the command option requirements of intro(1) in System V
+ * and intro(C) in Xenix.
+ *
+ * A command consists of: cmdname [ options ] [ cmdarguments ]
+ *
+ * Options consist of a leading dash '-' and a flag letter. An argument may
+ * follow optionally preceded by white space.
+ * Options without arguments may be grouped behind a single dash.
+ * A dash on its own is interpreted as the end of the options and is retained
+ * as a command argument.
+ * A double dash '--' is interpreted as the end of the options and is discarded.
+ *
+ * For example:
+ * zap -xz -f flame -q34 -- -x
+ *
+ * where zap.c contains the following in main():
+ *
+ * OPTIONS("[-xz] [-q queue-id] [-f dump-file] user")
+ * FLAG('x', xecute)
+ * FLAG('z', zot)
+ * STRING('f', file)
+ * fp = fopen(file, "w");
+ * NUMBER('q', queue)
+ * ENDOPTS
+ *
+ * Results in:
+ * xecute = 1
+ * zot = 1
+ * file = "flame"
+ * fp = fopen("flame", "w")
+ * queue = 34
+ * argc = 2
+ * argv[0] = "zap"
+ * argv[1] = "-x"
+ *
+ * Should the user enter unknown flags or leave out required arguments,
+ * the message:
+ *
+ * Usage: zap [-xz] [-q queue-id] [-f dump-file] user
+ *
+ * will be printed. This message can be printed by calling pusage(), or
+ * usage(). usage() will also cause program termination with exit code 1.
+ *
+ * Author: Stephen McKay, February 1991
+ *
+ * Based on recollection of the original options.h produced at the University
+ * of Queensland by Ross Patterson (and possibly others).
+ *
+ * $FreeBSD$
+ */
+
+static char *O_usage;
+static char *O_name;
+extern long atol();
+
+void
+pusage()
+ {
+ /*
+ * Avoid gratuitously loading stdio.
+ */
+ write(STDERR_FILENO, "usage: ", 7);
+ write(STDERR_FILENO, O_name, strlen(O_name));
+ write(STDERR_FILENO, " ", 1);
+ write(STDERR_FILENO, O_usage, strlen(O_usage));
+ write(STDERR_FILENO, "\n", 1);
+ }
+
+#define usage() (pusage(), exit(1))
+
+#define OPTIONS(usage_msg) \
+ { \
+ char O_cont; \
+ O_usage = (usage_msg); \
+ O_name = argv[0]; \
+ while (*++argv && **argv == '-') \
+ { \
+ if ((*argv)[1] == '\0') \
+ break; \
+ argc--; \
+ if ((*argv)[1] == '-' && (*argv)[2] == '\0') \
+ { \
+ argv++; \
+ break; \
+ } \
+ O_cont = 1; \
+ while (O_cont) \
+ switch (*++*argv) \
+ { \
+ default: \
+ case '-': \
+ usage(); \
+ case '\0': \
+ O_cont = 0;
+
+#define FLAG(x,flag) \
+ break; \
+ case (x): \
+ (flag) = 1;
+
+#define CHAR(x,ch) \
+ break; \
+ case (x): \
+ O_cont = 0; \
+ if (*++*argv == '\0' && (--argc, *++argv == 0)) \
+ usage(); \
+ (ch) = **argv;
+
+#define NUMBER(x,n) \
+ break; \
+ case (x): \
+ O_cont = 0; \
+ if (*++*argv == '\0' && (--argc, *++argv == 0)) \
+ usage(); \
+ (n) = atol(*argv);
+
+#define STRING(x,str) \
+ break; \
+ case (x): \
+ O_cont = 0; \
+ if (*++*argv == '\0' && (--argc, *++argv == 0)) \
+ usage(); \
+ (str) = *argv;
+
+#define SUFFIX(x,str) \
+ break; \
+ case (x): \
+ (str) = ++*argv; \
+ O_cont = 0;
+
+#define ENDOPTS \
+ break; \
+ } \
+ } \
+ *--argv = O_name; \
+ }
diff --git a/usr.sbin/ctm/ctm_smail/Makefile b/usr.sbin/ctm/ctm_smail/Makefile
new file mode 100644
index 0000000..4c98d31
--- /dev/null
+++ b/usr.sbin/ctm/ctm_smail/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../ctm_rmail
+
+PROG= ctm_smail
+MAN=
+SRCS= ctm_smail.c error.c
+
+CFLAGS+= -I${.CURDIR}/../ctm_rmail
+
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ctm/ctm_smail/Makefile.depend b/usr.sbin/ctm/ctm_smail/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/ctm/ctm_smail/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ctm/ctm_smail/ctm_smail.c b/usr.sbin/ctm/ctm_smail/ctm_smail.c
new file mode 100644
index 0000000..c90fa2f
--- /dev/null
+++ b/usr.sbin/ctm/ctm_smail/ctm_smail.c
@@ -0,0 +1,497 @@
+/*
+ * Send a compressed CTM delta to a recipient mailing list by encoding it
+ * in safe ASCII characters, in mailer-friendly chunks, and passing them
+ * to sendmail. Optionally, the chunks can be queued to be sent later by
+ * ctm_dequeue in controlled bursts. The encoding is almost the same as
+ * MIME BASE64, and is protected by a simple checksum.
+ *
+ * Author: Stephen McKay
+ *
+ * NOTICE: This is free software. I hope you get some use from this program.
+ * In return you should think about all the nice people who give away software.
+ * Maybe you should write some free software too.
+ *
+ * $FreeBSD$
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <paths.h>
+#include <limits.h>
+#include "error.h"
+#include "options.h"
+
+#define DEF_MAX_MSG 64000 /* Default maximum mail msg minus headers. */
+
+#define LINE_LENGTH 72 /* Chars per encoded line. Divisible by 4. */
+
+int chop_and_send_or_queue(FILE *dfp, char *delta, off_t ctm_size,
+ long max_msg_size, char *mail_alias, char *queue_dir);
+int chop_and_send(FILE *dfp, char *delta, long msg_size, int npieces,
+ char *mail_alias);
+int chop_and_queue(FILE *dfp, char *delta, long msg_size, int npieces,
+ char *mail_alias, char *queue_dir);
+void clean_up_queue(char *queue_dir);
+int encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size, unsigned *sum);
+void write_header(FILE *sfp, char *mail_alias, char *delta, int pce,
+ int npieces);
+void write_trailer(FILE *sfp, unsigned sum);
+int apologise(char *delta, off_t ctm_size, long max_ctm_size,
+ char *mail_alias, char *queue_dir);
+FILE *open_sendmail(void);
+int close_sendmail(FILE *fp);
+
+int
+main(int argc, char **argv)
+ {
+ int status = 0;
+ char *delta_file;
+ char *mail_alias;
+ long max_msg_size = DEF_MAX_MSG;
+ long max_ctm_size = 0;
+ char *log_file = NULL;
+ char *queue_dir = NULL;
+ char *delta;
+ FILE *dfp;
+ struct stat sb;
+
+ err_prog_name(argv[0]);
+
+ OPTIONS("[-l log] [-m maxmsgsize] [-c maxctmsize] [-q queuedir] ctm-delta mail-alias")
+ NUMBER('m', max_msg_size)
+ NUMBER('c', max_ctm_size)
+ STRING('l', log_file)
+ STRING('q', queue_dir)
+ ENDOPTS
+
+ if (argc != 3)
+ usage();
+
+ if (log_file != NULL)
+ err_set_log(log_file);
+
+ delta_file = argv[1];
+ mail_alias = argv[2];
+
+ if ((delta = strrchr(delta_file, '/')) == NULL)
+ delta = delta_file;
+ else
+ delta++;
+
+ if ((dfp = fopen(delta_file, "r")) == NULL || fstat(fileno(dfp), &sb) < 0)
+ {
+ err("*%s", delta_file);
+ exit(1);
+ }
+
+ if (max_ctm_size != 0 && sb.st_size > max_ctm_size)
+ status = apologise(delta, sb.st_size, max_ctm_size, mail_alias,
+ queue_dir);
+ else
+ status = chop_and_send_or_queue(dfp, delta, sb.st_size, max_msg_size,
+ mail_alias, queue_dir);
+
+ fclose(dfp);
+
+ return status;
+ }
+
+
+/*
+ * Carve our CTM delta into pieces, encode them, and send or queue them.
+ * Returns 0 on success, and 1 on failure.
+ */
+int
+chop_and_send_or_queue(FILE *dfp, char *delta, off_t ctm_size,
+ long max_msg_size, char *mail_alias, char *queue_dir)
+ {
+ int npieces;
+ long msg_size;
+ long exp_size;
+ int status;
+
+#undef howmany
+#define howmany(x,y) (((x)+((y)-1)) / (y))
+
+ /*
+ * Work out how many pieces we need, bearing in mind that each piece
+ * grows by 4/3 when encoded. We count the newlines too, but ignore
+ * all mail headers and piece headers. They are a "small" (almost
+ * constant) per message overhead that we make the user worry about. :-)
+ */
+ exp_size = ctm_size * 4 / 3;
+ exp_size += howmany(exp_size, LINE_LENGTH);
+ npieces = howmany(exp_size, max_msg_size);
+ msg_size = howmany(ctm_size, npieces);
+
+#undef howmany
+
+ if (queue_dir == NULL)
+ status = chop_and_send(dfp, delta, msg_size, npieces, mail_alias);
+ else
+ {
+ status = chop_and_queue(dfp, delta, msg_size, npieces, mail_alias,
+ queue_dir);
+ if (status)
+ clean_up_queue(queue_dir);
+ }
+
+ return status;
+ }
+
+
+/*
+ * Carve our CTM delta into pieces, encode them, and send them.
+ * Returns 0 on success, and 1 on failure.
+ */
+int
+chop_and_send(FILE *dfp, char *delta, long msg_size, int npieces,
+ char *mail_alias)
+ {
+ int pce;
+ FILE *sfp;
+ unsigned sum;
+
+ /*
+ * Send each chunk directly to sendmail as it is generated.
+ * No temporary files necessary. If things turn ugly, we just
+ * have to live with the fact the we have sent only part of
+ * the delta.
+ */
+ for (pce = 1; pce <= npieces; pce++)
+ {
+ int read_error;
+
+ if ((sfp = open_sendmail()) == NULL)
+ return 1;
+
+ write_header(sfp, mail_alias, delta, pce, npieces);
+ read_error = encode_body(sfp, dfp, msg_size, &sum);
+ if (!read_error)
+ write_trailer(sfp, sum);
+
+ if (!close_sendmail(sfp) || read_error)
+ return 1;
+
+ err("%s %d/%d sent to %s", delta, pce, npieces, mail_alias);
+ }
+
+ return 0;
+ }
+
+
+/*
+ * Construct the tmp queue file name of a delta piece.
+ */
+#define mk_tmp_name(fn,qd,p) \
+ sprintf((fn), "%s/.%08ld.%03d", (qd), (long)getpid(), (p))
+
+/*
+ * Construct the final queue file name of a delta piece.
+ */
+#define mk_queue_name(fn,qd,d,p,n) \
+ sprintf((fn), "%s/%s+%03d-%03d", (qd), (d), (p), (n))
+
+/*
+ * Carve our CTM delta into pieces, encode them, and queue them.
+ * Returns 0 on success, and 1 on failure.
+ */
+int
+chop_and_queue(FILE *dfp, char *delta, long msg_size, int npieces,
+ char *mail_alias, char *queue_dir)
+ {
+ int pce;
+ FILE *qfp;
+ unsigned sum;
+ char tname[PATH_MAX];
+ char qname[PATH_MAX];
+
+ /*
+ * Store each piece in the queue directory, but under temporary names,
+ * so that they can be deleted without unpleasant consequences if
+ * anything goes wrong. We could easily fill up a disk, for example.
+ */
+ for (pce = 1; pce <= npieces; pce++)
+ {
+ int write_error;
+
+ mk_tmp_name(tname, queue_dir, pce);
+ if ((qfp = fopen(tname, "w")) == NULL)
+ {
+ err("cannot open '%s' for writing", tname);
+ return 1;
+ }
+
+ write_header(qfp, mail_alias, delta, pce, npieces);
+ if (encode_body(qfp, dfp, msg_size, &sum))
+ return 1;
+ write_trailer(qfp, sum);
+
+ fflush(qfp);
+ write_error = ferror(qfp);
+ fclose(qfp);
+ if (write_error)
+ {
+ err("error writing '%s'", tname);
+ return 1;
+ }
+
+ /*
+ * Give the warm success message now, instead of all in a rush
+ * during the rename phase.
+ */
+ err("%s %d/%d queued for %s", delta, pce, npieces, mail_alias);
+ }
+
+ /*
+ * Rename the pieces into place. If an error occurs now, we are
+ * stuffed, but there is no neat way to back out. rename() should
+ * only fail now under extreme circumstances.
+ */
+ for (pce = 1; pce <= npieces; pce++)
+ {
+ mk_tmp_name(tname, queue_dir, pce);
+ mk_queue_name(qname, queue_dir, delta, pce, npieces);
+ if (rename(tname, qname) < 0)
+ {
+ err("*rename: '%s' to '%s'", tname, qname);
+ unlink(tname);
+ }
+ }
+
+ return 0;
+ }
+
+
+/*
+ * There may be temporary files cluttering up the queue directory.
+ */
+void
+clean_up_queue(char *queue_dir)
+ {
+ int pce;
+ char tname[PATH_MAX];
+
+ err("discarding queued delta pieces");
+ for (pce = 1; ; pce++)
+ {
+ mk_tmp_name(tname, queue_dir, pce);
+ if (unlink(tname) < 0)
+ break;
+ }
+ }
+
+
+/*
+ * MIME BASE64 encode table.
+ */
+static char to_b64[0x40] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/*
+ * This cheap plastic checksum effectively rotates our checksum-so-far
+ * left one, then adds the character. We only want 16 bits of it, and
+ * don't care what happens to the rest. It ain't much, but it's small.
+ */
+#define add_ck(sum,x) \
+ ((sum) += ((x)&0xff) + (sum) + (((sum)&0x8000) ? 1 : 0))
+
+/*
+ * Encode the body. Use an encoding almost the same as MIME BASE64.
+ *
+ * Characters are read from delta_fp and encoded characters are written
+ * to sm_fp. At most 'msg_size' characters should be read from delta_fp.
+ *
+ * The body consists of lines of up to LINE_LENGTH characters. Each group
+ * of 4 characters encodes 3 input characters. Each output character encodes
+ * 6 bits. Thus 64 different characters are needed in this representation.
+ */
+int
+encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size, unsigned *sum)
+ {
+ unsigned short cksum = 0xffff;
+ unsigned char *ip;
+ char *op;
+ int want, n, i;
+ unsigned char inbuf[LINE_LENGTH*3/4];
+ char outbuf[LINE_LENGTH+1];
+
+ /*
+ * Round up to the nearest line boundary, for the tiniest of gains,
+ * and lots of neatness. :-)
+ */
+ msg_size += (LINE_LENGTH*3/4) - 1;
+ msg_size -= msg_size % (LINE_LENGTH*3/4);
+
+ while (msg_size > 0)
+ {
+ want = (msg_size < sizeof(inbuf)) ? msg_size : sizeof(inbuf);
+ if ((n = fread(inbuf, sizeof(char), want, delta_fp)) == 0)
+ break;
+ msg_size -= n;
+
+ for (i = 0; i < n; i++)
+ add_ck(cksum, inbuf[i]);
+
+ /*
+ * Produce a line of encoded data. Every line length will be a
+ * multiple of 4, except for, perhaps, the last line.
+ */
+ ip = inbuf;
+ op = outbuf;
+ while (n >= 3)
+ {
+ *op++ = to_b64[ip[0] >> 2];
+ *op++ = to_b64[(ip[0] << 4 & 0x3f) | ip[1] >> 4];
+ *op++ = to_b64[(ip[1] << 2 & 0x3f) | ip[2] >> 6];
+ *op++ = to_b64[ip[2] & 0x3f];
+ ip += 3;
+ n -= 3;
+ }
+ if (n > 0)
+ {
+ *op++ = to_b64[ip[0] >> 2];
+ *op++ = to_b64[(ip[0] << 4 & 0x3f) | ip[1] >> 4];
+ if (n >= 2)
+ *op++ = to_b64[ip[1] << 2 & 0x3f];
+ }
+ *op++ = '\n';
+ fwrite(outbuf, sizeof(char), op - outbuf, sm_fp);
+ }
+
+ if (ferror(delta_fp))
+ {
+ err("error reading input file.");
+ return 1;
+ }
+
+ *sum = cksum;
+
+ return 0;
+ }
+
+
+/*
+ * Write the mail header and data header.
+ */
+void
+write_header(FILE *sfp, char *mail_alias, char *delta, int pce, int npieces)
+ {
+ fprintf(sfp, "From: owner-%s\n", mail_alias);
+ fprintf(sfp, "To: %s\n", mail_alias);
+ fprintf(sfp, "Subject: ctm-mail %s %d/%d\n\n", delta, pce, npieces);
+
+ fprintf(sfp, "CTM_MAIL BEGIN %s %d %d\n", delta, pce, npieces);
+ }
+
+
+/*
+ * Write the data trailer.
+ */
+void
+write_trailer(FILE *sfp, unsigned sum)
+ {
+ fprintf(sfp, "CTM_MAIL END %ld\n", (long)sum);
+ }
+
+
+/*
+ * We're terribly sorry, but the delta is too big to send.
+ * Returns 0 on success, 1 on failure.
+ */
+int
+apologise(char *delta, off_t ctm_size, long max_ctm_size, char *mail_alias,
+ char *queue_dir)
+ {
+ FILE *sfp;
+ char qname[PATH_MAX];
+
+ if (queue_dir == NULL)
+ {
+ sfp = open_sendmail();
+ if (sfp == NULL)
+ return 1;
+ }
+ else
+ {
+ mk_queue_name(qname, queue_dir, delta, 1, 1);
+ sfp = fopen(qname, "w");
+ if (sfp == NULL)
+ {
+ err("cannot open '%s' for writing", qname);
+ return 1;
+ }
+ }
+
+
+ fprintf(sfp, "From: owner-%s\n", mail_alias);
+ fprintf(sfp, "To: %s\n", mail_alias);
+ fprintf(sfp, "Subject: ctm-notice %s\n\n", delta);
+
+ fprintf(sfp, "%s is %ld bytes. The limit is %ld bytes.\n\n", delta,
+ (long)ctm_size, max_ctm_size);
+ fprintf(sfp, "You can retrieve this delta via ftp.\n");
+
+ if (queue_dir == NULL)
+ {
+ if (!close_sendmail(sfp))
+ return 1;
+ }
+ else
+ {
+ if (fclose(sfp)!=0)
+ {
+ err("error writing '%s'", qname);
+ unlink(qname);
+ return 1;
+ }
+ }
+
+ return 0;
+ }
+
+
+/*
+ * Start a pipe to sendmail. Sendmail will decode the destination
+ * from the message contents.
+ */
+FILE *
+open_sendmail()
+ {
+ FILE *fp;
+ char buf[100];
+
+ sprintf(buf, "%s -odq -t", _PATH_SENDMAIL);
+ if ((fp = popen(buf, "w")) == NULL)
+ err("cannot start sendmail");
+ return fp;
+ }
+
+
+/*
+ * Close a pipe to sendmail. Sendmail will then do its bit.
+ * Return 1 on success, 0 on failure.
+ */
+int
+close_sendmail(FILE *fp)
+ {
+ int status;
+
+ fflush(fp);
+ if (ferror(fp))
+ {
+ err("error writing to sendmail");
+ return 0;
+ }
+
+ if ((status = pclose(fp)) != 0)
+ err("sendmail failed with status %d", status);
+
+ return (status == 0);
+ }
diff --git a/usr.sbin/ctm/mkCTM/Makefile b/usr.sbin/ctm/mkCTM/Makefile
new file mode 100644
index 0000000..8194dd7
--- /dev/null
+++ b/usr.sbin/ctm/mkCTM/Makefile
@@ -0,0 +1,25 @@
+# $FreeBSD$
+
+PROG= mkctm
+MAN=
+
+LIBADD= md
+
+test: mkctm
+ rm -f tst.out*
+ time ./mkctm -v -v /3c/210src /a/r1/usr/src \
+ 2>a | md5 -p > /a/tst.out
+ ls -l /a/tst.out
+ gzip -9 -v /a/tst.out
+ ls -l /a/tst.out.gz
+ # cd /usr/src/release && ctm -c -v -v ${.CURDIR}/tst.out
+
+test1: mkctm
+ rm -f tst.out*
+ time ./mkctm -v -v /3c/210src /home/ncvs/src \
+ 2> b | md5 -p > /a/tst2.out
+ ls -l /a/tst2.out
+ gzip -9 -v /a/tst2.out
+ ls -l /a/tst2.out.gz
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ctm/mkCTM/ctm_conf.cvs-cur b/usr.sbin/ctm/mkCTM/ctm_conf.cvs-cur
new file mode 100644
index 0000000..fbb5bf2
--- /dev/null
+++ b/usr.sbin/ctm/mkCTM/ctm_conf.cvs-cur
@@ -0,0 +1,9 @@
+#!/usr/local/bin/tclsh
+
+set CTMname cvs-cur
+set CTMref /home/ncvs
+set CTMignore {^src/secure|^src/eBones|^src/kerberosIV|^CVSROOT/val-tags$|CVSROOT/\\.#}
+set CTMbogus {\\.core$|/#cvs|/\\.#}
+set CTMmail ctm-cvs-cur-fast@freebsd.org
+set CTMqueuemail ctm-cvs-cur@freebsd.org
+set CTMqueue /home/ctm/queue/ctm-cvs-cur
diff --git a/usr.sbin/ctm/mkCTM/ctm_conf.ports-cur b/usr.sbin/ctm/mkCTM/ctm_conf.ports-cur
new file mode 100644
index 0000000..b9d3d13
--- /dev/null
+++ b/usr.sbin/ctm/mkCTM/ctm_conf.ports-cur
@@ -0,0 +1,7 @@
+#!/usr/local/bin/tclsh
+
+set CTMname ports-cur
+set CTMref /usr/ports
+set CTMignore {/CVS$|/CVS/|^distfiles}
+set CTMbogus {\\.core$|/#cvs|/\\.#}
+set CTMmail ctm-ports-cur@freebsd.org
diff --git a/usr.sbin/ctm/mkCTM/ctm_conf.smp-cur b/usr.sbin/ctm/mkCTM/ctm_conf.smp-cur
new file mode 100644
index 0000000..5c3e1d1
--- /dev/null
+++ b/usr.sbin/ctm/mkCTM/ctm_conf.smp-cur
@@ -0,0 +1,7 @@
+#!/usr/local/bin/tclsh
+
+set CTMname smp-cur
+set CTMref /home/smp
+set CTMignore {^CVSROOT/history.*$|^CVSROOT/val-tags$|^CVSROOT/\\.#}
+set CTMbogus {\\.core$|/#cvs|/\\.#}
+set CTMmail smp-cvs-cur@freebsd.org
diff --git a/usr.sbin/ctm/mkCTM/ctm_conf.src-cur b/usr.sbin/ctm/mkCTM/ctm_conf.src-cur
new file mode 100644
index 0000000..8589d04
--- /dev/null
+++ b/usr.sbin/ctm/mkCTM/ctm_conf.src-cur
@@ -0,0 +1,9 @@
+#!/usr/local/bin/tclsh
+
+set CTMname src-cur
+set CTMref /c/src
+set CTMignore {/CVS$|/CVS/|^/secure|^/eBones}
+set CTMbogus {\\.core$|/#cvs|/\\.#}
+set CTMmail ctm-src-cur-fast@freebsd.org
+set CTMqueue /home/ctm/queue/ctm-src-cur
+set CTMqueuemail ctm-src-cur@freebsd.org
diff --git a/usr.sbin/ctm/mkCTM/ctm_conf.src-special b/usr.sbin/ctm/mkCTM/ctm_conf.src-special
new file mode 100644
index 0000000..2a8ca70
--- /dev/null
+++ b/usr.sbin/ctm/mkCTM/ctm_conf.src-special
@@ -0,0 +1,9 @@
+#!/usr/local/bin/tclsh
+
+set CTMname src-cur
+set CTMref $CTMSW/../$CTMname
+set CTMcopy /c/phk/20R/usr/src
+set CTMdont {\.core$|/CVS$|/CVS/|^/secure|^/eBones|/#cvs|/\.#}
+set CTMtest 1
+set CTMspecial 1
+set CTMsuff R20
diff --git a/usr.sbin/ctm/mkCTM/dequeue b/usr.sbin/ctm/mkCTM/dequeue
new file mode 100755
index 0000000..697ec0b
--- /dev/null
+++ b/usr.sbin/ctm/mkCTM/dequeue
@@ -0,0 +1,6 @@
+#! /bin/sh
+# $FreeBSD$
+
+L=/home/ctm/log.dequeue
+/usr/sbin/ctm_dequeue -n 1 -l $L /home/ctm/queue/ctm-cvs-cur
+/usr/sbin/ctm_dequeue -n 1 -l $L /home/ctm/queue/ctm-src-cur
diff --git a/usr.sbin/ctm/mkCTM/mkCTM b/usr.sbin/ctm/mkCTM/mkCTM
new file mode 100644
index 0000000..02b1544
--- /dev/null
+++ b/usr.sbin/ctm/mkCTM/mkCTM
@@ -0,0 +1,188 @@
+#!/usr/local/bin/tclsh7.4
+#
+# $FreeBSD$
+
+#############################################################################
+### Do we already have this delta ?
+#############################################################################
+
+proc find_delta {nbr} {
+ global CTMname CTMdest
+ if {[file exists [format "%s/$CTMname.%04d" $CTMdest $nbr]]} { return 1 }
+ if {[file exists [format "%s/$CTMname.%04d.gz" $CTMdest $nbr]]} { return 1 }
+ return 0
+}
+
+#############################################################################
+### The top level code...
+#############################################################################
+
+set CTMSW /home/ctm/SW
+
+cd $CTMSW
+
+# Defaults...
+set CTMapply 1
+set CTMignore {^///}
+set CTMbogus {\.core$}
+set CTMmail {}
+set CTMqueue {}
+set CTMqueuemail {}
+set CTMmaxctm 10000000
+set CTMmaxmsg 100000
+set CTMsuff {}
+set CTMdate [exec date -u +%Y%m%d%H%M%SZ]
+set CTMtmp {}
+set CTMcopy {}
+set CTMdest {}
+set CTMprefix .
+set CTMtest 0
+set CTMspecial 0
+set CTMscan .
+set CTMfirst 0
+set max_damage 100
+
+set damage 0
+set changes 0
+
+source $argv
+exec sh -c "date -u '+%Y%m%d%H%M%S $argv'" >> ${CTMSW}/log
+
+if {$CTMtmp == ""} {
+ set CTMtmp $CTMSW/../tmp/${CTMname}_${CTMsuff}
+}
+if {$CTMcopy == ""} {
+ set CTMcopy $CTMSW/../$CTMname
+}
+if {$CTMdest == ""} {
+ set CTMdest $CTMSW/../CTM-pub/$CTMname
+}
+
+# Make sure we only run one at a time...
+
+set CTMlock Lck.${CTMname}.${CTMdate}.[pid]
+exec rm -f ${CTMlock}
+exec echo starting > ${CTMlock}
+if {[catch "exec ln $CTMlock LCK.$CTMname" a]} {
+ puts "Not going, lock exists..."
+ exec rm -f $CTMlock
+ exit 1
+}
+exec rm -f $CTMlock
+set CTMlock LCK.$CTMname
+
+set CTMscratch ${CTMtmp}.tmp
+
+while 1 {
+ if { ! $CTMspecial} {
+ if {$CTMfirst} {
+ set CTMnbr 0
+ } else {
+ set CTMnbr [lindex [exec cat $CTMcopy/.ctm_status] 1]
+ }
+
+ if {$CTMnbr > 0 && ![find_delta $CTMnbr]} {
+ puts "$CTMname delta $CTMnbr doesn't exist..."
+ exec rm -f $CTMlock
+ exit 1
+ }
+
+ incr CTMnbr
+
+ if {[find_delta $CTMnbr]} {
+ puts "$CTMname delta $CTMnbr does already exist..."
+ exec rm -f $CTMlock
+ exit 1
+ }
+
+ set fo [open $CTMref/.ctm_status w]
+ puts $fo "$CTMname $CTMnbr"
+ close $fo
+ incr changes -1
+
+ } else {
+ set CTMnbr [lindex [exec cat $CTMref/.ctm_status] 1]
+ }
+
+ puts "Doing CTMname $CTMname CTMnbr $CTMnbr$CTMsuff CTMdate $CTMdate"
+ flush stdout
+ exec sh -c "rm -f ${CTMtmp}.* ${CTMtmp}:*" >&@ stdout
+
+ set nm [format "%s.%04d%s" $CTMname $CTMnbr $CTMsuff]
+
+ set x1 $CTMcopy
+ if {$x1 == ""} {
+ exec mkdir ${CTMtmp}.dir
+ set x1 ${CTMtmp}.dir
+ }
+ set r1 [catch "exec ${CTMSW}/mkctm -I ${CTMignore} -B ${CTMbogus} -l ${CTMtmp}.log -D $max_damage $CTMname $CTMnbr $CTMdate . $x1 $CTMref | md5 -p | gzip -9 > ${CTMtmp}:${nm}.gz 2>@ stderr" r2]
+
+ if {$r1} {
+ if {[lindex $errorCode 2] == 4} {
+ puts "No changes, stopping."
+ exec rm -f $CTMlock
+ exit 0
+ }
+ puts "problems, stopping now."
+ puts "errorCode $errorCode"
+ puts "$r2"
+ exec rm -f $CTMlock
+ exit 1
+ }
+
+ puts "mkctm done"
+
+ if {$CTMtest} {
+ puts "testing, stopping now."
+ exec rm -f $CTMlock
+ exit 0
+ }
+ if {$CTMapply} {
+ puts "Applying delta"
+ flush stdout
+ exec echo now applying > $CTMlock
+ exec sh -e -c "cd $CTMcopy ; $CTMSW/ctm -v -v -v ${CTMtmp}:${nm}.gz" >& ${CTMtmp}.apply
+ exec echo did apply > $CTMlock
+ }
+ puts "Moving delta"
+ flush stdout
+ exec mv ${CTMtmp}:${nm}.gz $CTMdest/.CTMtmp_${nm}.gz >&@ stdout
+ exec mv $CTMdest/.CTMtmp_${nm}.gz $CTMdest/${nm}.gz >&@ stdout
+ exec echo moved > $CTMlock
+
+ exec sh -c "rm -rf ${CTMtmp}.*" >&@ stdout
+
+ if {$CTMmail != ""} {
+ puts "Mailing delta"
+ flush stdout
+ exec $CTMSW/ctm_smail -m $CTMmaxmsg -c $CTMmaxctm $CTMdest/${nm}.gz $CTMmail >&@ stdout
+ if {$CTMqueue != "" && $CTMqueuemail != ""} {
+ puts "Queueing delta"
+ flush stdout
+ exec $CTMSW/ctm_smail -m $CTMmaxmsg -c $CTMmaxctm -q $CTMqueue $CTMdest/${nm}.gz $CTMqueuemail >&@ stdout
+ puts "Sending initial two deltas"
+ flush stdout
+ exec $CTMSW/ctm_dequeue -n 2 $CTMqueue >&@ stdout
+ }
+ }
+ exec echo mailed > $CTMlock
+
+ # If we did an absolute delta: stop.
+ if {$CTMsuff != ""} break
+
+ # Make an absolute delta (!) every 100 deltas
+ if {$CTMnbr == 0 || ($CTMnbr % 100)} break
+
+ # Make an absolute delta too...
+ set CTMref $CTMcopy
+ set CTMsuff A
+ set CTMcopy ""
+ set CTMmail ""
+ set CTMqueue ""
+ set CTMqueuemail ""
+ set CTMapply 0
+ set CTMspecial 1
+ exec rm -f $CTMlock
+}
+puts "done."
+exec rm -f $CTMlock
diff --git a/usr.sbin/ctm/mkCTM/mkctm.c b/usr.sbin/ctm/mkCTM/mkctm.c
new file mode 100644
index 0000000..d2c73e2
--- /dev/null
+++ b/usr.sbin/ctm/mkCTM/mkctm.c
@@ -0,0 +1,597 @@
+/* $FreeBSD$ */
+
+/* Still missing:
+ *
+ * mkctm
+ * -B regex Bogus
+ * -I regex Ignore
+ * -D int Damage
+ * -q decrease verbosity
+ * -v increase verbosity
+ * -l file logfile
+ * name cvs-cur
+ * prefix src/secure
+ * dir1 "Soll"
+ * dir2 "Ist"
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <dirent.h>
+#include <regex.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <md5.h>
+#include <err.h>
+#include <paths.h>
+#include <signal.h>
+
+#define DEFAULT_IGNORE "/CVS$|/\\.#|00_TRANS\\.TBL$"
+#define DEFAULT_BOGUS "\\.core$|\\.orig$|\\.rej$|\\.o$"
+regex_t reg_ignore, reg_bogus;
+int flag_ignore, flag_bogus;
+
+int verbose;
+int damage, damage_limit;
+int change;
+
+FILE *logf;
+
+u_long s1_ignored, s2_ignored;
+u_long s1_bogus, s2_bogus;
+u_long s1_wrong, s2_wrong;
+u_long s_new_dirs, s_new_files, s_new_bytes;
+u_long s_del_dirs, s_del_files, s_del_bytes;
+u_long s_files_chg, s_bytes_add, s_bytes_del;
+u_long s_same_dirs, s_same_files, s_same_bytes;
+u_long s_edit_files, s_edit_bytes, s_edit_saves;
+u_long s_sub_files, s_sub_bytes;
+
+void
+Usage(void)
+{
+ fprintf(stderr,
+ "usage: mkctm [-options] name number timestamp prefix dir1 dir2\n");
+ fprintf(stderr, "options:\n");
+ fprintf(stderr, "\t\t-B bogus_regexp\n");
+ fprintf(stderr, "\t\t-D damage_limit\n");
+ fprintf(stderr, "\t\t-I ignore_regexp\n");
+ fprintf(stderr, "\t\t-q\n");
+ fprintf(stderr, "\t\t-v\n");
+}
+
+void
+print_stat(FILE *fd, char *pre)
+{
+ fprintf(fd, "%sNames:\n", pre);
+ fprintf(fd, "%s ignore: %5lu ref %5lu target\n",
+ pre, s1_ignored, s2_ignored);
+ fprintf(fd, "%s bogus: %5lu ref %5lu target\n",
+ pre, s1_bogus, s2_bogus);
+ fprintf(fd, "%s wrong: %5lu ref %5lu target\n",
+ pre, s1_wrong, s2_wrong);
+ fprintf(fd, "%sDelta:\n", pre);
+ fprintf(fd, "%s new: %5lu dirs %5lu files %9lu plus\n",
+ pre, s_new_dirs, s_new_files, s_new_bytes);
+ fprintf(fd, "%s del: %5lu dirs %5lu files %9lu minus\n",
+ pre, s_del_dirs, s_del_files, s_del_bytes);
+ fprintf(fd, "%s chg: %5lu files %9lu plus %9lu minus\n",
+ pre, s_files_chg, s_bytes_add, s_bytes_del);
+ fprintf(fd, "%s same: %5lu dirs %5lu files %9lu bytes\n",
+ pre, s_same_dirs, s_same_files, s_same_bytes);
+ fprintf(fd, "%sMethod:\n", pre);
+ fprintf(fd, "%s edit: %5lu files %9lu bytes %9lu saved\n",
+ pre, s_edit_files, s_edit_bytes, s_edit_saves);
+ fprintf(fd, "%s sub: %5lu files %9lu bytes\n",
+ pre, s_sub_files, s_sub_bytes);
+
+}
+
+void
+stat_info(int foo)
+{
+ signal(SIGINFO, stat_info);
+ print_stat(stderr, "INFO: ");
+}
+
+void DoDir(const char *dir1, const char *dir2, const char *name);
+
+static struct stat st;
+static __inline struct stat *
+StatFile(char *name)
+{
+ if (lstat(name, &st) < 0)
+ err(1, "couldn't stat %s", name);
+ return &st;
+}
+
+int
+dirselect(struct dirent *de)
+{
+ if (!strcmp(de->d_name, ".")) return 0;
+ if (!strcmp(de->d_name, "..")) return 0;
+ return 1;
+}
+
+void
+name_stat(const char *pfx, const char *dir, const char *name, struct dirent *de)
+{
+ char *buf = alloca(strlen(dir) + strlen(name) +
+ strlen(de->d_name) + 3);
+ struct stat *st;
+
+ strcpy(buf, dir);
+ strcat(buf, "/"); strcat(buf, name);
+ strcat(buf, "/"); strcat(buf, de->d_name);
+ st = StatFile(buf);
+ printf("%s %s%s %u %u %o",
+ pfx, name, de->d_name,
+ st->st_uid, st->st_gid, st->st_mode & ~S_IFMT);
+ fprintf(logf, "%s %s%s\n", pfx, name, de->d_name);
+ if (verbose > 1) {
+ fprintf(stderr, "%s %s%s\n", pfx, name, de->d_name);
+ }
+}
+
+void
+Equ(const char *dir1, const char *dir2, const char *name, struct dirent *de)
+{
+ if (de->d_type == DT_DIR) {
+ char *p = alloca(strlen(name)+strlen(de->d_name)+2);
+
+ strcpy(p, name); strcat(p, de->d_name); strcat(p, "/");
+ DoDir(dir1, dir2, p);
+ s_same_dirs++;
+ } else {
+ char *buf1 = alloca(strlen(dir1) + strlen(name) +
+ strlen(de->d_name) + 3);
+ char *buf2 = alloca(strlen(dir2) + strlen(name) +
+ strlen(de->d_name) + 3);
+ char *m1, md5_1[33], *m2, md5_2[33];
+ u_char *p1, *p2;
+ int fd1, fd2;
+ struct stat s1, s2;
+
+ strcpy(buf1, dir1);
+ strcat(buf1, "/"); strcat(buf1, name);
+ strcat(buf1, "/"); strcat(buf1, de->d_name);
+ fd1 = open(buf1, O_RDONLY);
+ if(fd1 < 0) { err(3, "%s", buf1); }
+ fstat(fd1, &s1);
+ strcpy(buf2, dir2);
+ strcat(buf2, "/"); strcat(buf2, name);
+ strcat(buf2, "/"); strcat(buf2, de->d_name);
+ fd2 = open(buf2, O_RDONLY);
+ if(fd2 < 0) { err(3, "%s", buf2); }
+ fstat(fd2, &s2);
+#if 0
+ /* XXX if we could just trust the size to change... */
+ if (s1.st_size == s2.st_size) {
+ s_same_files++;
+ s_same_bytes += s1.st_size;
+ close(fd1);
+ close(fd2);
+ goto finish;
+ }
+#endif
+ p1=mmap(0, s1.st_size, PROT_READ, MAP_PRIVATE, fd1, 0);
+ if (p1 == (u_char *)MAP_FAILED) { err(3, "%s", buf1); }
+ close(fd1);
+
+ p2=mmap(0, s2.st_size, PROT_READ, MAP_PRIVATE, fd2, 0);
+ if (p2 == (u_char *)MAP_FAILED) { err(3, "%s", buf2); }
+ close(fd2);
+
+ /* If identical, we're done. */
+ if((s1.st_size == s2.st_size) && !memcmp(p1, p2, s1.st_size)) {
+ s_same_files++;
+ s_same_bytes += s1.st_size;
+ goto finish;
+ }
+
+ s_files_chg++;
+ change++;
+ if (s1.st_size > s2.st_size)
+ s_bytes_del += (s1.st_size - s2.st_size);
+ else
+ s_bytes_add += (s2.st_size - s1.st_size);
+
+ m1 = MD5Data(p1, s1.st_size, md5_1);
+ m2 = MD5Data(p2, s2.st_size, md5_2);
+
+ /* Just a curiosity... */
+ if(!strcmp(m1, m2)) {
+ if (s1.st_size != s2.st_size)
+ fprintf(stderr,
+ "Notice: MD5 same for files of diffent size:\n\t%s\n\t%s\n",
+ buf1, buf2);
+ goto finish;
+ }
+
+ {
+ u_long l = s2.st_size + 2;
+ u_char *cmd = alloca(strlen(buf1)+strlen(buf2)+100);
+ u_char *ob = malloc(l), *p;
+ int j;
+ FILE *F;
+
+ if (s1.st_size && p1[s1.st_size-1] != '\n') {
+ if (verbose > 0)
+ fprintf(stderr,
+ "last char != \\n in %s\n",
+ buf1);
+ goto subst;
+ }
+
+ if (s2.st_size && p2[s2.st_size-1] != '\n') {
+ if (verbose > 0)
+ fprintf(stderr,
+ "last char != \\n in %s\n",
+ buf2);
+ goto subst;
+ }
+
+ for (p=p1; p<p1+s1.st_size; p++)
+ if (!*p) {
+ if (verbose > 0)
+ fprintf(stderr,
+ "NULL char in %s\n",
+ buf1);
+ goto subst;
+ }
+
+ for (p=p2; p<p2+s2.st_size; p++)
+ if (!*p) {
+ if (verbose > 0)
+ fprintf(stderr,
+ "NULL char in %s\n",
+ buf2);
+ goto subst;
+ }
+
+ strcpy(cmd, "diff -n ");
+ strcat(cmd, buf1);
+ strcat(cmd, " ");
+ strcat(cmd, buf2);
+ F = popen(cmd, "r");
+ for (j = 1, l = 0; l < s2.st_size; ) {
+ j = fread(ob+l, 1, s2.st_size - l, F);
+ if (j < 1)
+ break;
+ l += j;
+ continue;
+ }
+ if (j) {
+ l = 0;
+ while (EOF != fgetc(F))
+ continue;
+ }
+ pclose(F);
+
+ if (l && l < s2.st_size) {
+ name_stat("CTMFN", dir2, name, de);
+ printf(" %s %s %d\n", m1, m2, (unsigned)l);
+ fwrite(ob, 1, l, stdout);
+ putchar('\n');
+ s_edit_files++;
+ s_edit_bytes += l;
+ s_edit_saves += (s2.st_size - l);
+ } else {
+ subst:
+ name_stat("CTMFS", dir2, name, de);
+ printf(" %s %s %u\n", m1, m2, (unsigned)s2.st_size);
+ fwrite(p2, 1, s2.st_size, stdout);
+ putchar('\n');
+ s_sub_files++;
+ s_sub_bytes += s2.st_size;
+ }
+ free(ob);
+ }
+ finish:
+ munmap(p1, s1.st_size);
+ munmap(p2, s2.st_size);
+ }
+}
+
+void
+Add(const char *dir1, const char *dir2, const char *name, struct dirent *de)
+{
+ change++;
+ if (de->d_type == DT_DIR) {
+ char *p = alloca(strlen(name)+strlen(de->d_name)+2);
+ strcpy(p, name); strcat(p, de->d_name); strcat(p, "/");
+ name_stat("CTMDM", dir2, name, de);
+ putchar('\n');
+ s_new_dirs++;
+ DoDir(dir1, dir2, p);
+ } else if (de->d_type == DT_REG) {
+ char *buf2 = alloca(strlen(dir2) + strlen(name) +
+ strlen(de->d_name) + 3);
+ char *m2, md5_2[33];
+ u_char *p1;
+ struct stat st;
+ int fd1;
+
+ strcpy(buf2, dir2);
+ strcat(buf2, "/"); strcat(buf2, name);
+ strcat(buf2, "/"); strcat(buf2, de->d_name);
+ fd1 = open(buf2, O_RDONLY);
+ if (fd1 < 0) { err(3, "%s", buf2); }
+ fstat(fd1, &st);
+ p1=mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd1, 0);
+ if (p1 == (u_char *)MAP_FAILED) { err(3, "%s", buf2); }
+ close(fd1);
+ m2 = MD5Data(p1, st.st_size, md5_2);
+ name_stat("CTMFM", dir2, name, de);
+ printf(" %s %u\n", m2, (unsigned)st.st_size);
+ fwrite(p1, 1, st.st_size, stdout);
+ putchar('\n');
+ munmap(p1, st.st_size);
+ s_new_files++;
+ s_new_bytes += st.st_size;
+ }
+}
+
+void
+Del (const char *dir1, const char *dir2, const char *name, struct dirent *de)
+{
+ damage++;
+ change++;
+ if (de->d_type == DT_DIR) {
+ char *p = alloca(strlen(name)+strlen(de->d_name)+2);
+ strcpy(p, name); strcat(p, de->d_name); strcat(p, "/");
+ DoDir(dir1, dir2, p);
+ printf("CTMDR %s%s\n", name, de->d_name);
+ fprintf(logf, "CTMDR %s%s\n", name, de->d_name);
+ if (verbose > 1) {
+ fprintf(stderr, "CTMDR %s%s\n", name, de->d_name);
+ }
+ s_del_dirs++;
+ } else if (de->d_type == DT_REG) {
+ char *buf1 = alloca(strlen(dir1) + strlen(name) +
+ strlen(de->d_name) + 3);
+ char *m1, md5_1[33];
+ strcpy(buf1, dir1);
+ strcat(buf1, "/"); strcat(buf1, name);
+ strcat(buf1, "/"); strcat(buf1, de->d_name);
+ m1 = MD5File(buf1, md5_1);
+ printf("CTMFR %s%s %s\n", name, de->d_name, m1);
+ fprintf(logf, "CTMFR %s%s %s\n", name, de->d_name, m1);
+ if (verbose > 1) {
+ fprintf(stderr, "CTMFR %s%s\n", name, de->d_name);
+ }
+ s_del_files++;
+ s_del_bytes += StatFile(buf1)->st_size;
+ }
+}
+
+void
+GetNext(int *i, int *n, struct dirent **nl, const char *dir, const char *name, u_long *ignored, u_long *bogus, u_long *wrong)
+{
+ char buf[BUFSIZ];
+ char buf1[BUFSIZ];
+
+ for (;;) {
+ for (;;) {
+ (*i)++;
+ if (*i >= *n)
+ return;
+ strcpy(buf1, name);
+ if (buf1[strlen(buf1)-1] != '/')
+ strcat(buf1, "/");
+ strcat(buf1, nl[*i]->d_name);
+ if (flag_ignore &&
+ !regexec(&reg_ignore, buf1, 0, 0, 0)) {
+ (*ignored)++;
+ fprintf(logf, "Ignore %s\n", buf1);
+ if (verbose > 2) {
+ fprintf(stderr, "Ignore %s\n", buf1);
+ }
+ } else if (flag_bogus &&
+ !regexec(&reg_bogus, buf1, 0, 0, 0)) {
+ (*bogus)++;
+ fprintf(logf, "Bogus %s\n", buf1);
+ fprintf(stderr, "Bogus %s\n", buf1);
+ damage++;
+ } else {
+ *buf = 0;
+ if (*dir != '/')
+ strcat(buf, "/");
+ strcat(buf, dir);
+ if (buf[strlen(buf)-1] != '/')
+ strcat(buf, "/");
+ strcat(buf, buf1);
+ break;
+ }
+ free(nl[*i]); nl[*i] = 0;
+ }
+ /* If the filesystem didn't tell us, find type */
+ if (nl[*i]->d_type == DT_UNKNOWN)
+ nl[*i]->d_type = IFTODT(StatFile(buf)->st_mode);
+ if (nl[*i]->d_type == DT_REG || nl[*i]->d_type == DT_DIR)
+ break;
+ (*wrong)++;
+ if (verbose > 0)
+ fprintf(stderr, "Wrong %s\n", buf);
+ free(nl[*i]); nl[*i] = 0;
+ }
+}
+
+void
+DoDir(const char *dir1, const char *dir2, const char *name)
+{
+ int i1, i2, n1, n2, i;
+ struct dirent **nl1, **nl2;
+ char *buf1 = alloca(strlen(dir1) + strlen(name) + 4);
+ char *buf2 = alloca(strlen(dir2) + strlen(name) + 4);
+
+ strcpy(buf1, dir1); strcat(buf1, "/"); strcat(buf1, name);
+ strcpy(buf2, dir2); strcat(buf2, "/"); strcat(buf2, name);
+ n1 = scandir(buf1, &nl1, dirselect, alphasort);
+ n2 = scandir(buf2, &nl2, dirselect, alphasort);
+ i1 = i2 = -1;
+ GetNext(&i1, &n1, nl1, dir1, name, &s1_ignored, &s1_bogus, &s1_wrong);
+ GetNext(&i2, &n2, nl2, dir2, name, &s2_ignored, &s2_bogus, &s2_wrong);
+ for (;i1 < n1 || i2 < n2;) {
+
+ if (damage_limit && damage > damage_limit)
+ break;
+
+ /* Get next item from list 1 */
+ if (i1 < n1 && !nl1[i1])
+ GetNext(&i1, &n1, nl1, dir1, name,
+ &s1_ignored, &s1_bogus, &s1_wrong);
+
+ /* Get next item from list 2 */
+ if (i2 < n2 && !nl2[i2])
+ GetNext(&i2, &n2, nl2, dir2, name,
+ &s2_ignored, &s2_bogus, &s2_wrong);
+
+ if (i1 >= n1 && i2 >= n2) {
+ /* Done */
+ break;
+ } else if (i1 >= n1 && i2 < n2) {
+ /* end of list 1, add anything left on list 2 */
+ Add(dir1, dir2, name, nl2[i2]);
+ free(nl2[i2]); nl2[i2] = 0;
+ } else if (i1 < n1 && i2 >= n2) {
+ /* end of list 2, delete anything left on list 1 */
+ Del(dir1, dir2, name, nl1[i1]);
+ free(nl1[i1]); nl1[i1] = 0;
+ } else if (!(i = strcmp(nl1[i1]->d_name, nl2[i2]->d_name))) {
+ /* Identical names */
+ if (nl1[i1]->d_type == nl2[i2]->d_type) {
+ /* same type */
+ Equ(dir1, dir2, name, nl1[i1]);
+ } else {
+ /* different types */
+ Del(dir1, dir2, name, nl1[i1]);
+ Add(dir1, dir2, name, nl2[i2]);
+ }
+ free(nl1[i1]); nl1[i1] = 0;
+ free(nl2[i2]); nl2[i2] = 0;
+ } else if (i < 0) {
+ /* Something extra in list 1, delete it */
+ Del(dir1, dir2, name, nl1[i1]);
+ free(nl1[i1]); nl1[i1] = 0;
+ } else {
+ /* Something extra in list 2, add it */
+ Add(dir1, dir2, name, nl2[i2]);
+ free(nl2[i2]); nl2[i2] = 0;
+ }
+ }
+ if (n1 >= 0)
+ free(nl1);
+ if (n2 >= 0)
+ free(nl2);
+}
+
+int
+main(int argc, char **argv)
+{
+ int i;
+
+ setbuf(stderr, NULL);
+
+#if 0
+ if (regcomp(&reg_bogus, DEFAULT_BOGUS, REG_EXTENDED | REG_NEWLINE))
+ /* XXX use regerror to explain it */
+ errx(1, "default regular expression argument to -B is botched");
+ flag_bogus = 1;
+
+ if (regcomp(&reg_ignore, DEFAULT_IGNORE, REG_EXTENDED | REG_NEWLINE))
+ /* XXX use regerror to explain it */
+ errx(1, "default regular expression argument to -I is botched");
+ flag_ignore = 1;
+#endif
+
+ while ((i = getopt(argc, argv, "D:I:B:l:qv")) != -1)
+ switch (i) {
+ case 'D':
+ damage_limit = strtol(optarg, 0, 0);
+ if (damage_limit < 0)
+ errx(1, "damage limit must be positive");
+ break;
+ case 'I':
+ if (flag_ignore)
+ regfree(&reg_ignore);
+ flag_ignore = 0;
+ if (!*optarg)
+ break;
+ if (regcomp(&reg_ignore, optarg,
+ REG_EXTENDED | REG_NEWLINE))
+ /* XXX use regerror to explain it */
+ errx(1, "regular expression argument to -I is botched");
+ flag_ignore = 1;
+ break;
+ case 'B':
+ if (flag_bogus)
+ regfree(&reg_bogus);
+ flag_bogus = 0;
+ if (!*optarg)
+ break;
+ if (regcomp(&reg_bogus, optarg,
+ REG_EXTENDED | REG_NEWLINE))
+ /* XXX use regerror to explain it */
+ errx(1, "regular expression argument to -B is botched");
+ flag_bogus = 1;
+ break;
+ case 'l':
+ logf = fopen(optarg, "w");
+ if (!logf)
+ err(1, "%s", optarg);
+ setlinebuf(logf);
+ break;
+ case 'q':
+ verbose--;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case '?':
+ default:
+ Usage();
+ return (1);
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!logf)
+ logf = fopen(_PATH_DEVNULL, "w");
+
+ setbuf(stdout, 0);
+
+ if (argc != 6) {
+ Usage();
+ return (1);
+ }
+
+ signal(SIGINFO, stat_info);
+
+ fprintf(stderr, "CTM_BEGIN 2.0 %s %s %s %s\n",
+ argv[0], argv[1], argv[2], argv[3]);
+ fprintf(logf, "CTM_BEGIN 2.0 %s %s %s %s\n",
+ argv[0], argv[1], argv[2], argv[3]);
+ printf("CTM_BEGIN 2.0 %s %s %s %s\n",
+ argv[0], argv[1], argv[2], argv[3]);
+ DoDir(argv[4], argv[5], "");
+ if (damage_limit && damage > damage_limit) {
+ print_stat(stderr, "DAMAGE: ");
+ errx(1, "damage of %d would exceed %d files",
+ damage, damage_limit);
+ } else if (change < 2) {
+ errx(4, "no changes");
+ } else {
+ printf("CTM_END ");
+ fprintf(logf, "CTM_END\n");
+ print_stat(stderr, "END: ");
+ }
+ exit(0);
+}
diff --git a/usr.sbin/daemon/Makefile b/usr.sbin/daemon/Makefile
new file mode 100644
index 0000000..eb0d502
--- /dev/null
+++ b/usr.sbin/daemon/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= daemon
+MAN= daemon.8
+
+LIBADD= util
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/daemon/Makefile.depend b/usr.sbin/daemon/Makefile.depend
new file mode 100644
index 0000000..0f77a15
--- /dev/null
+++ b/usr.sbin/daemon/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/daemon/daemon.8 b/usr.sbin/daemon/daemon.8
new file mode 100644
index 0000000..87cfd98
--- /dev/null
+++ b/usr.sbin/daemon/daemon.8
@@ -0,0 +1,163 @@
+.\" Copyright (c) 1999 Berkeley Software Design, 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.
+.\" 3. Berkeley Software Design Inc's name may not be used to endorse or
+.\" promote products derived from this software without specific prior
+.\" written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY BERKELEY SOFTWARE DESIGN 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 BERKELEY SOFTWARE DESIGN 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd September 13, 2013
+.Dt DAEMON 8
+.Os
+.Sh NAME
+.Nm daemon
+.Nd run detached from the controlling terminal
+.Sh SYNOPSIS
+.Nm
+.Op Fl cfr
+.Op Fl p Ar child_pidfile
+.Op Fl P Ar supervisor_pidfile
+.Op Fl u Ar user
+.Ar command arguments ...
+.Sh DESCRIPTION
+The
+.Nm
+utility detaches itself from the controlling terminal and
+executes the program specified by its arguments.
+Privileges may be lowered to the specified user.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl c
+Change the current working directory to the root
+.Pq Dq Pa / .
+.It Fl f
+Redirect standard input, standard output and standard error to
+.Pa /dev/null .
+.It Fl p Ar child_pidfile
+Write the ID of the created process into the
+.Ar child_pidfile
+using the
+.Xr pidfile 3
+functionality.
+The program is executed in a spawned child process while the
+.Nm
+waits until it terminates to keep the
+.Ar child_pidfile
+locked and removes it after the process exits.
+The
+.Ar child_pidfile
+owner is the user who runs the
+.Nm
+regardless of whether the
+.Fl u
+option is used or not.
+.It Fl P Ar supervisor_pidfile
+Write the ID of the
+.Nm
+process into the
+.Ar supervisor_pidfile
+using the
+.Xr pidfile 3
+functionality.
+The program is executed in a spawned child process while the
+.Nm
+waits until it terminates to keep the
+.Ar supervisor_pidfile
+locked and removes it after the process exits.
+The
+.Ar supervisor_pidfile
+owner is the user who runs the
+.Nm
+regardless of whether the
+.Fl u
+option is used or not.
+.It Fl r
+Supervise and restart the program if it has been terminated.
+.It Fl u Ar user
+Login name of the user to execute the program under.
+Requires adequate superuser privileges.
+.El
+.Pp
+If the
+.Fl p ,
+.Fl P
+or
+.Fl r
+option is specified the program is executed in a spawned child process.
+The
+.Nm
+waits until it terminates to keep the pid file(s) locked and removes them
+after the process exits or restarts the program.
+In this case if the monitoring
+.Nm
+receives software termination signal (SIGTERM) it forwards it to the
+spawned process.
+Normally it will cause the child to exit, remove the pidfile(s)
+and then terminate.
+.Pp
+The
+.Fl P
+option is useful combined with the
+.Fl r
+option as
+.Ar supervisor_pidfile
+contains the ID of the supervisor
+not the child. This is especially important if you use
+.Fl r
+in an rc script as the
+.Fl p
+option will give you the child's ID to signal when you attempt to
+stop the service, causing
+.Nm
+to restart the child.
+.Sh EXIT STATUS
+The
+.Nm
+utility exits 1 if an error is returned by the
+.Xr daemon 3
+library routine, 2 if
+.Ar child_pidfile
+or
+.Ar supervisor_pidfile
+is requested, but cannot be opened, 3 if process is already running (pidfile
+exists and is locked),
+otherwise 0.
+.Sh DIAGNOSTICS
+If the command cannot be executed, an error message is displayed on
+standard error unless the
+.Fl f
+flag is specified.
+.Sh SEE ALSO
+.Xr setregid 2 ,
+.Xr setreuid 2 ,
+.Xr daemon 3 ,
+.Xr exec 3 ,
+.Xr pidfile 3 ,
+.Xr termios 4 ,
+.Xr tty 4
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 4.7 .
diff --git a/usr.sbin/daemon/daemon.c b/usr.sbin/daemon/daemon.c
new file mode 100644
index 0000000..7bdd2a6
--- /dev/null
+++ b/usr.sbin/daemon/daemon.c
@@ -0,0 +1,276 @@
+/*-
+ * Copyright (c) 1999 Berkeley Software Design, 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.
+ * 3. Berkeley Software Design Inc's name may not be used to endorse or
+ * promote products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY BERKELEY SOFTWARE DESIGN 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 BERKELEY SOFTWARE DESIGN 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.
+ *
+ * From BSDI: daemon.c,v 1.2 1996/08/15 01:11:09 jch Exp
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+
+#include <err.h>
+#include <errno.h>
+#include <libutil.h>
+#include <login_cap.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static void dummy_sighandler(int);
+static void restrict_process(const char *);
+static int wait_child(pid_t pid, sigset_t *mask);
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ struct pidfh *ppfh, *pfh;
+ sigset_t mask, oldmask;
+ int ch, nochdir, noclose, restart, serrno;
+ const char *pidfile, *ppidfile, *user;
+ pid_t otherpid, pid;
+
+ nochdir = noclose = 1;
+ restart = 0;
+ ppidfile = pidfile = user = NULL;
+ while ((ch = getopt(argc, argv, "cfp:P:ru:")) != -1) {
+ switch (ch) {
+ case 'c':
+ nochdir = 0;
+ break;
+ case 'f':
+ noclose = 0;
+ break;
+ case 'p':
+ pidfile = optarg;
+ break;
+ case 'P':
+ ppidfile = optarg;
+ break;
+ case 'r':
+ restart = 1;
+ break;
+ case 'u':
+ user = optarg;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0)
+ usage();
+
+ ppfh = pfh = NULL;
+ /*
+ * Try to open the pidfile before calling daemon(3),
+ * to be able to report the error intelligently
+ */
+ if (pidfile != NULL) {
+ pfh = pidfile_open(pidfile, 0600, &otherpid);
+ if (pfh == NULL) {
+ if (errno == EEXIST) {
+ errx(3, "process already running, pid: %d",
+ otherpid);
+ }
+ err(2, "pidfile ``%s''", pidfile);
+ }
+ }
+ /* Do the same for actual daemon process. */
+ if (ppidfile != NULL) {
+ ppfh = pidfile_open(ppidfile, 0600, &otherpid);
+ if (ppfh == NULL) {
+ serrno = errno;
+ pidfile_remove(pfh);
+ errno = serrno;
+ if (errno == EEXIST) {
+ errx(3, "process already running, pid: %d",
+ otherpid);
+ }
+ err(2, "ppidfile ``%s''", ppidfile);
+ }
+ }
+
+ if (daemon(nochdir, noclose) == -1) {
+ warn("daemon");
+ goto exit;
+ }
+ /* Write out parent pidfile if needed. */
+ pidfile_write(ppfh);
+
+ /*
+ * If the pidfile or restart option is specified the daemon
+ * executes the command in a forked process and wait on child
+ * exit to remove the pidfile or restart the command. Normally
+ * we don't want the monitoring daemon to be terminated
+ * leaving the running process and the stale pidfile, so we
+ * catch SIGTERM and forward it to the children expecting to
+ * get SIGCHLD eventually.
+ */
+ pid = -1;
+ if (pidfile != NULL || ppidfile != NULL || restart) {
+ /*
+ * Restore default action for SIGTERM in case the
+ * parent process decided to ignore it.
+ */
+ if (signal(SIGTERM, SIG_DFL) == SIG_ERR) {
+ warn("signal");
+ goto exit;
+ }
+ /*
+ * Because SIGCHLD is ignored by default, setup dummy handler
+ * for it, so we can mask it.
+ */
+ if (signal(SIGCHLD, dummy_sighandler) == SIG_ERR) {
+ warn("signal");
+ goto exit;
+ }
+ /*
+ * Block interesting signals.
+ */
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGTERM);
+ sigaddset(&mask, SIGCHLD);
+ if (sigprocmask(SIG_SETMASK, &mask, &oldmask) == -1) {
+ warn("sigprocmask");
+ goto exit;
+ }
+ /*
+ * Try to protect against pageout kill. Ignore the
+ * error, madvise(2) will fail only if a process does
+ * not have superuser privileges.
+ */
+ (void)madvise(NULL, 0, MADV_PROTECT);
+restart:
+ /*
+ * Spawn a child to exec the command, so in the parent
+ * we could wait for it to exit and remove pidfile.
+ */
+ pid = fork();
+ if (pid == -1) {
+ warn("fork");
+ goto exit;
+ }
+ }
+ if (pid <= 0) {
+ if (pid == 0) {
+ /* Restore old sigmask in the child. */
+ if (sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1)
+ err(1, "sigprocmask");
+ }
+ /* Now that we are the child, write out the pid. */
+ pidfile_write(pfh);
+
+ if (user != NULL)
+ restrict_process(user);
+
+ execvp(argv[0], argv);
+
+ /*
+ * execvp() failed -- report the error. The child is
+ * now running, so the exit status doesn't matter.
+ */
+ err(1, "%s", argv[0]);
+ }
+
+ setproctitle("%s[%d]", argv[0], pid);
+ if (wait_child(pid, &mask) == 0 && restart) {
+ sleep(1);
+ goto restart;
+ }
+exit:
+ pidfile_remove(pfh);
+ pidfile_remove(ppfh);
+ exit(1); /* If daemon(3) succeeded exit status does not matter. */
+}
+
+static void
+dummy_sighandler(int sig __unused)
+{
+ /* Nothing to do. */
+}
+
+static void
+restrict_process(const char *user)
+{
+ struct passwd *pw = NULL;
+
+ pw = getpwnam(user);
+ if (pw == NULL)
+ errx(1, "unknown user: %s", user);
+
+ if (setusercontext(NULL, pw, pw->pw_uid, LOGIN_SETALL) != 0)
+ errx(1, "failed to set user environment");
+}
+
+static int
+wait_child(pid_t pid, sigset_t *mask)
+{
+ int terminate, signo;
+
+ terminate = 0;
+ for (;;) {
+ if (sigwait(mask, &signo) == -1) {
+ warn("sigwaitinfo");
+ return (-1);
+ }
+ switch (signo) {
+ case SIGCHLD:
+ if (waitpid(pid, NULL, WNOHANG) == -1) {
+ warn("waitpid");
+ return (-1);
+ }
+ return (terminate);
+ case SIGTERM:
+ terminate = 1;
+ if (kill(pid, signo) == -1) {
+ warn("kill");
+ return (-1);
+ }
+ continue;
+ default:
+ warnx("sigwaitinfo: invalid signal: %d", signo);
+ return (-1);
+ }
+ }
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "usage: daemon [-cfr] [-p child_pidfile] [-P supervisor_pidfile] "
+ "[-u user]\n command arguments ...\n");
+ exit(1);
+}
diff --git a/usr.sbin/dconschat/Makefile b/usr.sbin/dconschat/Makefile
new file mode 100644
index 0000000..198c5cc
--- /dev/null
+++ b/usr.sbin/dconschat/Makefile
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+PROG= dconschat
+MAN= dconschat.8
+
+CFLAGS+= -I${.CURDIR}/../../sys
+
+LIBADD= kvm
+
+WARNS?= 1
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/dconschat/Makefile.depend b/usr.sbin/dconschat/Makefile.depend
new file mode 100644
index 0000000..442e3ca8
--- /dev/null
+++ b/usr.sbin/dconschat/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libelf \
+ lib/libkvm \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/dconschat/dconschat.8 b/usr.sbin/dconschat/dconschat.8
new file mode 100644
index 0000000..330f44c
--- /dev/null
+++ b/usr.sbin/dconschat/dconschat.8
@@ -0,0 +1,329 @@
+.\" Copyright (c) 2003 Hidetoshi Shimokawa
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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$
+.\"
+.\"
+.Dd February 11, 2003
+.Dt DCONSCHAT 8
+.Os
+.Sh NAME
+.Nm dconschat
+.Nd user interface to
+.Xr dcons 4
+.Sh SYNOPSIS
+.Nm
+.Op Fl brvwRT1
+.Op Fl e Ar escape-char
+.Op Fl h Ar hz
+.Op Fl C Ar console_port
+.Op Fl G Ar gdb_port
+.Op Fl M Ar core
+.Op Fl N Ar system
+.Nm
+.Op Fl brvwR1
+.Op Fl h Ar hz
+.Op Fl C Ar console_port
+.Op Fl G Ar gdb_port
+.Op Fl a Ar address
+.Op Fl u Ar bus_num
+.Fl t Ar target_eui64
+.Sh DESCRIPTION
+The
+.Nm
+utility is designed to provide a way for users to access
+.Xr dcons 4
+(dumb console device) on a local or remote system.
+The
+.Nm
+utility interacts with
+.Xr dcons 4
+using
+.Xr kvm 3
+or
+.Xr firewire 4 ,
+and interacts with the user over TTY or TCP/IP.
+To access remote
+.Xr dcons 4
+using
+.Xr firewire 4 ,
+you have to specify target EUI64 address using the
+.Fl t
+option. Physical DMA should be enabled on the target machine for access
+via FireWire.
+.Pp
+The
+.Nm
+utility and the
+.Xr dcons 4
+driver communicate using 2 ports, one for the console port and another
+for remote
+.Xr gdb 1
+port.
+Users are supposed to access
+.Nm
+using TTY,
+.Xr telnet 1
+and
+.Xr gdb 1 .
+You can specify listen ports for console and
+.Xr gdb 1
+port using the
+.Fl C
+and
+.Fl G
+options respectively.
+The port number 0 has special meaning that
+current TTY (stdin/stdout) is used instead of TCP/IP.
+A negative port number will disable the port.
+By analogy with
+.Xr pty 4
+device, the
+.Xr dcons 4
+acts as a slave device and
+.Nm
+acts as a master device with
+.Xr telnetd 8 .
+.Pp
+Typed characters are normally transmitted directly to
+.Xr dcons 4 .
+A escape character (the default is
+.Ql ~
+) appearing as the first character of a line is an escape signal; the
+following are recognized:
+.Bl -tag -width ident
+.It Ic ~.
+Drop the connection and exit.
+.It Ic ~^G
+Invoke kgdb on the terminal on which dconschat is running.
+.It Ic ~^R
+Reset the target over FireWire if a reset address is registered in Configuration ROM.
+.It Ic ~^Z
+Suspend the dconschat process.
+.El
+.Pp
+The following options are supported.
+.Bl -tag -width indent
+.It Fl b
+Translate Ctrl-C to ALT_BREAK (CR +
+.Ql ~
++ Ctrl-B) on
+.Xr gdb 1
+port.
+.It Fl r
+Replay old buffer on connection.
+.It Fl v
+Verbose debug output.
+Multiple
+.Fl v
+options increase verbosity.
+.It Fl w
+Listen on a wildcard address rather than localhost.
+.It Fl R
+Read-only.
+Do not write anything to the
+.Xr dcons 4
+buffer.
+.It Fl T
+Enable ad-hoc workaround for the TELNET protocol to
+remove unnecessary byte sequences.
+It should be set when you access
+.Nm
+using
+.Xr telnet 1 .
+.It Fl 1
+One-shot.
+Read available buffer, then exit.
+This implies the
+.Fl r
+option.
+.It Fl e Ar escape-char
+Specify escape character.
+The default is '~'.
+.It Fl h Ar hz
+Specify polling rate.
+The default value is 100.
+.It Fl C Ar console_port
+Specify the console port.
+The default value is 0 (stdin/stdout).
+.It Fl G Ar gdb_port
+Specify
+.Xr gdb 1
+port.
+The default value is \-1 (disabled).
+.It Fl M Ar core
+Specify core file.
+.It Fl N Ar system
+Specify system file such as
+.Pa /boot/kernel/kernel .
+.It Fl t Ar target_eui64
+Specify the 64-bit extended unique identifier of the target,
+and use FireWire to access remote
+.Xr dcons 4 .
+.It Fl a Ar address
+Specify the physical I/O address of the
+.Xr dcons 4
+buffer.
+See
+.Xr dcons 4
+for details.
+If this option is not specified,
+.Nm
+tries to get the address from the Configuration ROM on the target.
+You are supposed to enable
+.Xr dcons_crom 4
+on the target to omit this option.
+.It Fl u Ar bus_num
+Specify FireWire bus number.
+The default is 0.
+.El
+.Sh FILES
+.Bl -tag -width indent -compact
+.It Pa /dev/fwmem0.0
+.It Pa /dev/mem
+.It Pa /dev/kmem
+.El
+.Sh EXAMPLES
+To use
+.Nm
+with FireWire for remote
+.Xr dcons 4 ,
+you have to specify the EUI64 of the target.
+You can obtain EUI64 by running
+.Xr fwcontrol 8
+without options.
+The first EUI64 is of the host running
+.Xr fwcontrol 8
+and others on the bus follow.
+.Bd -literal -offset indent
+# fwcontrol
+2 devices (info_len=2)
+node EUI64 status
+ 1 77-66-55-44-33-22-11-00 0
+ 0 00-11-22-33-44-55-66-77 1
+.Ed
+.Pp
+The EUI64 does not change unless you change the hardware
+as the ethernet address.
+.Pp
+Now we can run
+.Nm .
+.Bd -literal -offset indent
+# dconschat -br -G 12345 -t 00-11-22-33-44-55-66-77
+.Ed
+.Pp
+You will get console output of the target and login prompt if a
+.Xr getty 8
+is running on
+.Xr dcons 4 .
+You can break to DDB with ALT_BREAK (CR +
+.Ql ~
++ Ctrl-B)
+if
+.Dv DDB
+and
+.Dv ALT_BREAK_TO_DEBUGGER
+are enabled in the target kernel.
+To quit the session, type CR +
+.Ql ~
++
+.Ql \&.
+in the console port.
+.Pp
+Using
+.Xr gdb 1
+port is almost the same as remote
+.Xr gdb 1
+over serial line except
+using TCP/IP instead of
+.Pa /dev/cu* .
+See
+.Sx "On-line Kernel Debugging Using Remote GDB"
+section of
+.%T "The FreeBSD Developers Handbook"
+and
+.Xr gdb 4
+for details.
+.Bd -literal -offset indent
+% gdb -k kernel.debug
+(kgdb) target remote :12345
+.Ed
+.Pp
+Once
+.Xr gdb 1
+is attached and you specified the
+.Fl b
+option to
+.Nm ,
+typing Ctrl-C in
+.Xr gdb 1
+causes a break to debugger.
+.Pp
+The following command gets the console log from the crash dump:
+.Bd -literal -offset indent
+# dconschat -1 -M vmcore.0 -N kernel.0
+.Ed
+.Pp
+If you want access to the console using
+.Xr telnet 1 ,
+try the following:
+.Bd -literal -offset indent
+# dconschat -rTC 5555 &
+# telnet localhost 5555
+.Ed
+.Pp
+You may want to keep logging console output of several machines.
+.Nm conserver-com
+in the Ports collection may help you.
+Insert the following lines in
+.Pa conserver.cf :
+.Bd -literal -offset indent
+console local {
+ master localhost;
+ type exec;
+ exec /usr/sbin/dconschat -rh 25;
+}
+console remote {
+ master localhost;
+ type exec;
+ exec /usr/sbin/dconschat -rh 25 -t 00-11-22-33-44-55-66-77;
+}
+.Ed
+.Sh SEE ALSO
+.Xr gdb 1 ,
+.Xr telnet 1 ,
+.Xr kvm 3 ,
+.Xr dcons 4 ,
+.Xr dcons_crom 4 ,
+.Xr ddb 4 ,
+.Xr firewire 4 ,
+.Xr fwohci 4 ,
+.Xr gdb 4 ,
+.Xr eui64 5 ,
+.Xr fwcontrol 8
+.Sh AUTHORS
+.An Hidetoshi Shimokawa Aq Mt simokawa@FreeBSD.org
+.Sh BUGS
+This utility is
+.Ud
diff --git a/usr.sbin/dconschat/dconschat.c b/usr.sbin/dconschat/dconschat.c
new file mode 100644
index 0000000..dba568a
--- /dev/null
+++ b/usr.sbin/dconschat/dconschat.c
@@ -0,0 +1,1157 @@
+/*
+ * Copyright (C) 2003
+ * Hidetoshi Shimokawa. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 Hidetoshi Shimokawa.
+ *
+ * 4. Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $Id: dconschat.c,v 1.76 2003/10/23 06:21:13 simokawa Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <termios.h>
+#include <dev/dcons/dcons.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <err.h>
+#include <string.h>
+#include <sys/eui64.h>
+#include <sys/event.h>
+#include <sys/time.h>
+#include <arpa/telnet.h>
+
+#include <sys/ioccom.h>
+#include <dev/firewire/firewire.h>
+#include <dev/firewire/iec13213.h>
+
+#include <kvm.h>
+#include <nlist.h>
+
+#include <sys/errno.h>
+
+#define DCONS_POLL_HZ 100
+#define DCONS_POLL_OFFLINE 2 /* sec */
+
+#define RETRY 3
+
+#ifdef CSRVAL_VENDOR_PRIVATE
+#define USE_CROM 1
+#else
+#define USE_CROM 0
+#endif
+
+int verbose = 0;
+int tc_set = 0;
+int poll_hz = DCONS_POLL_HZ;
+static u_char abreak[3] = {13 /* CR */, 126 /* ~ */, 2 /* ^B */};
+
+#define IS_CONSOLE(p) ((p)->port == DCONS_CON)
+#define IS_GDB(p) ((p)->port == DCONS_GDB)
+
+static struct dcons_state {
+ int fd;
+ kvm_t *kd;
+ int kq;
+ off_t paddr;
+ off_t reset;
+#define F_READY (1 << 1)
+#define F_RD_ONLY (1 << 2)
+#define F_ALT_BREAK (1 << 3)
+#define F_TELNET (1 << 4)
+#define F_USE_CROM (1 << 5)
+#define F_ONE_SHOT (1 << 6)
+#define F_REPLAY (1 << 7)
+ int flags;
+ enum {
+ TYPE_KVM,
+ TYPE_FW
+ } type;
+ int escape_state;
+ struct dcons_port {
+ int port;
+ int sport;
+ struct dcons_ch o;
+ struct dcons_ch i;
+ u_int32_t optr;
+ u_int32_t iptr;
+ int s;
+ int infd;
+ int outfd;
+ struct addrinfo *res;
+ int skip_read;
+ } port[DCONS_NPORT];
+ struct timespec to;
+ struct timespec zero;
+ struct termios tsave;
+ struct termios traw;
+ char escape;
+} sc;
+
+static int dconschat_write_dcons(struct dcons_state *, int, char *, int);
+
+static int
+dread(struct dcons_state *dc, void *buf, size_t n, off_t offset)
+{
+ switch (dc->type) {
+ case TYPE_FW:
+ return (pread(dc->fd, buf, n, offset));
+ case TYPE_KVM:
+ return (kvm_read(dc->kd, offset, buf, n));
+ }
+ return (-1);
+}
+
+static int
+dwrite(struct dcons_state *dc, void *buf, size_t n, off_t offset)
+{
+ if ((dc->flags & F_RD_ONLY) != 0)
+ return (n);
+
+ switch (dc->type) {
+ case TYPE_FW:
+ return (pwrite(dc->fd, buf, n, offset));
+ case TYPE_KVM:
+ return (kvm_write(dc->kd, offset, buf, n));
+ }
+ return (-1);
+}
+
+static void
+dconschat_reset_target(struct dcons_state *dc, struct dcons_port *p)
+{
+ char buf[PAGE_SIZE];
+ if (dc->reset == 0)
+ return;
+
+ snprintf(buf, PAGE_SIZE,
+ "\r\n[dconschat reset target(addr=0x%jx)...]\r\n",
+ (intmax_t)dc->reset);
+ write(p->outfd, buf, strlen(buf));
+ bzero(&buf[0], PAGE_SIZE);
+ dwrite(dc, (void *)buf, PAGE_SIZE, dc->reset);
+}
+
+
+static void
+dconschat_suspend(struct dcons_state *dc, struct dcons_port *p)
+{
+ if (p->sport != 0)
+ return;
+
+ if (tc_set)
+ tcsetattr(STDIN_FILENO, TCSADRAIN, &dc->tsave);
+
+ printf("\n[dconschat suspend]\n");
+ kill(getpid(), SIGTSTP);
+
+ if (tc_set)
+ tcsetattr(STDIN_FILENO, TCSADRAIN, &dc->traw);
+}
+
+static void
+dconschat_sigchld(int s)
+{
+ struct kevent kev;
+ struct dcons_port *p;
+ char buf[256];
+
+ p = &sc.port[DCONS_CON];
+
+ snprintf(buf, 256, "\r\n[child exit]\r\n");
+ write(p->outfd, buf, strlen(buf));
+
+ if (tc_set)
+ tcsetattr(STDIN_FILENO, TCSADRAIN, &sc.traw);
+
+ EV_SET(&kev, p->infd, EVFILT_READ, EV_ADD, NOTE_LOWAT, 1, (void *)p);
+ kevent(sc.kq, &kev, 1, NULL, 0, &sc.zero);
+}
+
+static void
+dconschat_fork_gdb(struct dcons_state *dc, struct dcons_port *p)
+{
+ pid_t pid;
+ char buf[256], com[256];
+ struct kevent kev;
+
+ pid = fork();
+ if (pid < 0) {
+ snprintf(buf, 256, "\r\n[%s: fork failed]\r\n", __FUNCTION__);
+ write(p->outfd, buf, strlen(buf));
+ }
+
+
+ if (pid == 0) {
+ /* child */
+ if (tc_set)
+ tcsetattr(STDIN_FILENO, TCSADRAIN, &dc->tsave);
+
+ snprintf(com, sizeof(buf), "kgdb -r :%d kernel",
+ dc->port[DCONS_GDB].sport);
+ snprintf(buf, 256, "\n[fork %s]\n", com);
+ write(p->outfd, buf, strlen(buf));
+
+ execl("/bin/sh", "/bin/sh", "-c", com, NULL);
+
+ snprintf(buf, 256, "\n[fork failed]\n");
+ write(p->outfd, buf, strlen(buf));
+
+ if (tc_set)
+ tcsetattr(STDIN_FILENO, TCSADRAIN, &dc->traw);
+
+ exit(0);
+ } else {
+ signal(SIGCHLD, dconschat_sigchld);
+ EV_SET(&kev, p->infd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
+ kevent(sc.kq, &kev, 1, NULL, 0, &sc.zero);
+ }
+}
+
+
+static void
+dconschat_cleanup(int sig)
+{
+ struct dcons_state *dc;
+ int status;
+
+ dc = &sc;
+ if (tc_set != 0)
+ tcsetattr(STDIN_FILENO, TCSADRAIN, &dc->tsave);
+
+ if (sig > 0)
+ printf("\n[dconschat exiting with signal %d ...]\n", sig);
+ else
+ printf("\n[dconschat exiting...]\n");
+ wait(&status);
+ exit(0);
+}
+
+#if USE_CROM
+static int
+dconschat_get_crom(struct dcons_state *dc)
+{
+ off_t addr;
+ int i, state = 0;
+ u_int32_t buf, hi = 0, lo = 0, reset_hi = 0, reset_lo = 0;
+ struct csrreg *reg;
+
+ reg = (struct csrreg *)&buf;
+ addr = 0xffff;
+ addr = (addr << 32) | 0xf0000400;
+ for (i = 20; i < 0x400; i += 4) {
+ if (dread(dc, &buf, 4, addr + i) < 0) {
+ if (verbose)
+ warn("crom read faild");
+ goto out;
+ }
+ buf = ntohl(buf);
+ if (verbose)
+ printf("%d %02x %06x\n", state, reg->key, reg->val);
+ switch (state) {
+ case 0:
+ if (reg->key == CSRKEY_SPEC &&
+ reg->val == CSRVAL_VENDOR_PRIVATE)
+ state = 1;
+ break;
+ case 1:
+ if (reg->key == CSRKEY_VER &&
+ reg->val == DCONS_CSR_VAL_VER)
+ state = 2;
+ break;
+ case 2:
+ switch (reg->key) {
+ case DCONS_CSR_KEY_HI:
+ hi = reg->val;
+ break;
+ case DCONS_CSR_KEY_LO:
+ lo = reg->val;
+ break;
+ case DCONS_CSR_KEY_RESET_HI:
+ reset_hi = reg->val;
+ break;
+ case DCONS_CSR_KEY_RESET_LO:
+ reset_lo = reg->val;
+ goto out;
+ break;
+ case 0x81:
+ break;
+ default:
+ state = 0;
+ }
+ break;
+ }
+ }
+out:
+ if (verbose)
+ printf("addr: %06x %06x\n", hi, lo);
+ dc->paddr = ((off_t)hi << 24) | lo;
+ dc->reset = ((off_t)reset_hi << 24) | reset_lo;
+ if (dc->paddr == 0)
+ return (-1);
+ return (0);
+}
+#endif
+
+static void
+dconschat_ready(struct dcons_state *dc, int ready, char *reason)
+{
+ static char oldreason[64] = "";
+ int old;
+
+ old = (dc->flags & F_READY) ? 1 : 0;
+
+ if (ready) {
+ dc->flags |= F_READY;
+ if (ready != old)
+ printf("[dcons connected]\r\n");
+ oldreason[0] = 0;
+ } else {
+ dc->flags &= ~F_READY;
+ if (strncmp(oldreason, reason, sizeof(oldreason)) != 0) {
+ printf("[dcons disconnected (%s)]\r\n", reason);
+ strlcpy(oldreason, reason, sizeof(oldreason));
+ }
+ }
+}
+
+static int
+dconschat_fetch_header(struct dcons_state *dc)
+{
+ char ebuf[64];
+ struct dcons_buf dbuf;
+ int j;
+
+#if USE_CROM
+ if (dc->paddr == 0 && (dc->flags & F_USE_CROM) != 0) {
+ if (dconschat_get_crom(dc)) {
+ dconschat_ready(dc, 0, "get crom failed");
+ return (-1);
+ }
+ }
+#endif
+
+ if (dread(dc, &dbuf, DCONS_HEADER_SIZE, dc->paddr) < 0) {
+ dconschat_ready(dc, 0, "read header failed");
+ return (-1);
+ }
+ if (dbuf.magic != htonl(DCONS_MAGIC)) {
+ if ((dc->flags & F_USE_CROM) !=0)
+ dc->paddr = 0;
+ snprintf(ebuf, sizeof(ebuf), "wrong magic 0x%08x", dbuf.magic);
+ dconschat_ready(dc, 0, ebuf);
+ return (-1);
+ }
+ if (ntohl(dbuf.version) != DCONS_VERSION) {
+ snprintf(ebuf, sizeof(ebuf),
+#if __FreeBSD_version < 500000
+ "wrong version %ld,%d",
+#else
+ "wrong version %d,%d",
+#endif
+ ntohl(dbuf.version), DCONS_VERSION);
+ /* XXX exit? */
+ dconschat_ready(dc, 0, ebuf);
+ return (-1);
+ }
+
+ for (j = 0; j < DCONS_NPORT; j++) {
+ struct dcons_ch *o, *i;
+ off_t newbuf;
+ int new = 0;
+
+ o = &dc->port[j].o;
+ newbuf = dc->paddr + ntohl(dbuf.ooffset[j]);
+ o->size = ntohl(dbuf.osize[j]);
+
+ if (newbuf != o->buf) {
+ /* buffer address has changes */
+ new = 1;
+ o->gen = ntohl(dbuf.optr[j]) >> DCONS_GEN_SHIFT;
+ o->pos = ntohl(dbuf.optr[j]) & DCONS_POS_MASK;
+ o->buf = newbuf;
+ }
+
+ i = &dc->port[j].i;
+ i->size = ntohl(dbuf.isize[j]);
+ i->gen = ntohl(dbuf.iptr[j]) >> DCONS_GEN_SHIFT;
+ i->pos = ntohl(dbuf.iptr[j]) & DCONS_POS_MASK;
+ i->buf = dc->paddr + ntohl(dbuf.ioffset[j]);
+
+ if (verbose) {
+ printf("port %d size offset gen pos\n", j);
+#if __FreeBSD_version < 500000
+ printf("output: %5d %6ld %5d %5d\n"
+ "input : %5d %6ld %5d %5d\n",
+#else
+ printf("output: %5d %6d %5d %5d\n"
+ "input : %5d %6d %5d %5d\n",
+#endif
+ o->size, ntohl(dbuf.ooffset[j]), o->gen, o->pos,
+ i->size, ntohl(dbuf.ioffset[j]), i->gen, i->pos);
+ }
+
+ if (IS_CONSOLE(&dc->port[j]) && new &&
+ (dc->flags & F_REPLAY) !=0) {
+ if (o->gen > 0)
+ o->gen --;
+ else
+ o->pos = 0;
+ }
+ }
+ dconschat_ready(dc, 1, NULL);
+ return(0);
+}
+
+static int
+dconschat_get_ptr (struct dcons_state *dc) {
+ int dlen, i;
+ u_int32_t ptr[DCONS_NPORT*2+1];
+ static int retry = RETRY;
+ char ebuf[64];
+
+again:
+ dlen = dread(dc, &ptr, sizeof(ptr),
+ dc->paddr + __offsetof(struct dcons_buf, magic));
+
+ if (dlen < 0) {
+ if (errno == ETIMEDOUT)
+ if (retry -- > 0)
+ goto again;
+ dconschat_ready(dc, 0, "get ptr failed");
+ return(-1);
+ }
+ if (ptr[0] != htonl(DCONS_MAGIC)) {
+ if ((dc->flags & F_USE_CROM) !=0)
+ dc->paddr = 0;
+ snprintf(ebuf, sizeof(ebuf), "wrong magic 0x%08x", ptr[0]);
+ dconschat_ready(dc, 0, ebuf);
+ return(-1);
+ }
+ retry = RETRY;
+ for (i = 0; i < DCONS_NPORT; i ++) {
+ dc->port[i].optr = ntohl(ptr[i + 1]);
+ dc->port[i].iptr = ntohl(ptr[DCONS_NPORT + i + 1]);
+ }
+ return(0);
+}
+
+#define MAX_XFER 2048
+static int
+dconschat_read_dcons(struct dcons_state *dc, int port, char *buf, int len)
+{
+ struct dcons_ch *ch;
+ u_int32_t ptr, pos, gen, next_gen;
+ int rlen, dlen, lost;
+ int retry = RETRY;
+
+ ch = &dc->port[port].o;
+ ptr = dc->port[port].optr;
+ gen = ptr >> DCONS_GEN_SHIFT;
+ pos = ptr & DCONS_POS_MASK;
+ if (gen == ch->gen && pos == ch->pos)
+ return (-1);
+
+ next_gen = DCONS_NEXT_GEN(ch->gen);
+ /* XXX sanity check */
+ if (gen == ch->gen) {
+ if (pos > ch->pos)
+ goto ok;
+ lost = ch->size * DCONS_GEN_MASK - ch->pos;
+ ch->pos = 0;
+ } else if (gen == next_gen) {
+ if (pos <= ch->pos)
+ goto ok;
+ lost = pos - ch->pos;
+ ch->pos = pos;
+ } else {
+ lost = gen - ch->gen;
+ if (lost < 0)
+ lost += DCONS_GEN_MASK;
+ if (verbose)
+ printf("[genskip %d]", lost);
+ lost = lost * ch->size - ch->pos;
+ ch->pos = 0;
+ ch->gen = gen;
+ }
+ /* generation skipped !! */
+ /* XXX discard */
+ if (verbose)
+ printf("[lost %d]", lost);
+ok:
+ if (gen == ch->gen)
+ rlen = pos - ch->pos;
+ else
+ rlen = ch->size - ch->pos;
+
+ if (rlen > MAX_XFER)
+ rlen = MAX_XFER;
+ if (rlen > len)
+ rlen = len;
+
+#if 1
+ if (verbose == 1)
+ printf("[%d]", rlen); fflush(stdout);
+#endif
+
+again:
+ dlen = dread(dc, buf, rlen, ch->buf + ch->pos);
+ if (dlen < 0) {
+ if (errno == ETIMEDOUT)
+ if (retry -- > 0)
+ goto again;
+ dconschat_ready(dc, 0, "read buffer failed");
+ return(-1);
+ }
+ if (dlen != rlen)
+ warnx("dlen(%d) != rlen(%d)\n", dlen, rlen);
+ ch->pos += dlen;
+ if (ch->pos >= ch->size) {
+ ch->gen = next_gen;
+ ch->pos = 0;
+ if (verbose)
+ printf("read_dcons: gen=%d", ch->gen);
+ }
+ return (dlen);
+}
+
+static int
+dconschat_write_dcons(struct dcons_state *dc, int port, char *buf, int blen)
+{
+ struct dcons_ch *ch;
+ u_int32_t ptr;
+ int len, wlen;
+ int retry = RETRY;
+
+ ch = &dc->port[port].i;
+ ptr = dc->port[port].iptr;
+
+ /* the others may advance the pointer sync with it */
+ ch->gen = ptr >> DCONS_GEN_SHIFT;
+ ch->pos = ptr & DCONS_POS_MASK;
+
+ while(blen > 0) {
+ wlen = MIN(blen, ch->size - ch->pos);
+ wlen = MIN(wlen, MAX_XFER);
+ len = dwrite(dc, buf, wlen, ch->buf + ch->pos);
+ if (len < 0) {
+ if (errno == ETIMEDOUT)
+ if (retry -- > 0)
+ continue; /* try again */
+ dconschat_ready(dc, 0, "write buffer failed");
+ return(-1);
+ }
+ ch->pos += len;
+ buf += len;
+ blen -= len;
+ if (ch->pos >= ch->size) {
+ ch->gen = DCONS_NEXT_GEN(ch->gen);
+ ch->pos = 0;
+ if (verbose)
+ printf("write_dcons: gen=%d", ch->gen);
+
+ }
+ }
+
+ ptr = DCONS_MAKE_PTR(ch);
+ dc->port[port].iptr = ptr;
+
+ if (verbose > 2)
+ printf("(iptr: 0x%x)", ptr);
+again:
+ len = dwrite(dc, &ptr, sizeof(u_int32_t),
+ dc->paddr + __offsetof(struct dcons_buf, iptr[port]));
+ if (len < 0) {
+ if (errno == ETIMEDOUT)
+ if (retry -- > 0)
+ goto again;
+ dconschat_ready(dc, 0, "write ptr failed");
+ return(-1);
+ }
+ return(0);
+}
+
+
+static int
+dconschat_write_socket(int fd, char *buf, int len)
+{
+ write(fd, buf, len);
+ if (verbose > 1) {
+ buf[len] = 0;
+ printf("<- %s\n", buf);
+ }
+ return (0);
+}
+
+static void
+dconschat_init_socket(struct dcons_state *dc, int port, char *host, int sport)
+{
+ struct addrinfo hints, *res;
+ int on = 1, error;
+ char service[10];
+ struct kevent kev;
+ struct dcons_port *p;
+
+ p = &dc->port[port];
+ p->port = port;
+ p->sport = sport;
+ p->infd = p->outfd = -1;
+
+ if (sport < 0)
+ return;
+
+ if (sport == 0) {
+
+ /* Use stdin and stdout */
+ p->infd = STDIN_FILENO;
+ p->outfd = STDOUT_FILENO;
+ p->s = -1;
+ if (tc_set == 0 &&
+ tcgetattr(STDIN_FILENO, &dc->tsave) == 0) {
+ dc->traw = dc->tsave;
+ cfmakeraw(&dc->traw);
+ tcsetattr(STDIN_FILENO, TCSADRAIN, &dc->traw);
+ tc_set = 1;
+ }
+ EV_SET(&kev, p->infd, EVFILT_READ, EV_ADD, NOTE_LOWAT, 1,
+ (void *)p);
+ kevent(dc->kq, &kev, 1, NULL, 0, &dc->zero);
+ return;
+ }
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_PASSIVE;
+#if 1 /* gdb can talk v4 only */
+ hints.ai_family = PF_INET;
+#else
+ hints.ai_family = PF_UNSPEC;
+#endif
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = 0;
+
+ if (verbose)
+ printf("%s:%d for port %d\n",
+ host == NULL ? "*" : host, sport, port);
+ snprintf(service, sizeof(service), "%d", sport);
+ error = getaddrinfo(host, service, &hints, &res);
+ if (error)
+ errx(1, "tcp/%s: %s\n", service, gai_strerror(error));
+ p->res = res;
+ p->s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (p->s < 0)
+ err(1, "socket");
+ setsockopt(p->s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+ if (bind(p->s, p->res->ai_addr, p->res->ai_addrlen) < 0) {
+ err(1, "bind");
+ }
+ if (listen(p->s, 1) < 0)
+ err(1, "listen");
+ EV_SET(&kev, p->s, EVFILT_READ, EV_ADD | EV_ONESHOT, 0, 0, (void *)p);
+ error = kevent(dc->kq, &kev, 1, NULL, 0, &dc->to);
+ if (error < 0)
+ err(1, "kevent");
+ return;
+}
+
+static int
+dconschat_accept_socket(struct dcons_state *dc, struct dcons_port *p)
+{
+ socklen_t addrlen;
+ int ns, flags;
+ struct kevent kev;
+
+ /* accept connection */
+ addrlen = p->res->ai_addrlen;
+ ns = accept(p->s, p->res->ai_addr, &addrlen);
+ if (ns < 0)
+ err(1, "accept");
+ if (verbose)
+ printf("port%d accepted\n", p->port);
+
+ flags = fcntl(ns, F_GETFL, 0);
+ flags |= O_NDELAY;
+ fcntl(ns, F_SETFL, flags);
+#if 1
+ if (IS_CONSOLE(p) && (dc->flags & F_TELNET) != 0) {
+ char sga[] = {IAC, WILL, TELOPT_SGA};
+ char linemode[] = {IAC, DONT, TELOPT_LINEMODE};
+ char echo[] = {IAC, WILL, TELOPT_ECHO};
+ char bin[] = {IAC, DO, TELOPT_BINARY};
+
+ write(ns, sga, sizeof(sga));
+ write(ns, linemode, sizeof(linemode));
+ write(ns, echo, sizeof(echo));
+ write(ns, bin, sizeof(bin));
+ p->skip_read = 0;
+ }
+#endif
+ /* discard backlog on GDB port */
+ if (IS_GDB(p)) {
+ char buf[2048];
+ int len;
+
+ while ((len = dconschat_read_dcons(dc, DCONS_GDB, &buf[0],
+ 2048)) > 0)
+ if (verbose)
+ printf("discard %d chars on GDB port\n", len);
+ }
+
+ p->infd = p->outfd = ns;
+ EV_SET(&kev, ns, EVFILT_READ, EV_ADD, NOTE_LOWAT, 1, (void *)p);
+ kevent(dc->kq, &kev, 1, NULL, 0, &dc->zero);
+ return(0);
+}
+
+static int
+dconschat_read_filter(struct dcons_state *dc, struct dcons_port *p,
+ u_char *sp, int slen, u_char *dp, int *dlen)
+{
+ int skip;
+ char *buf;
+
+ while (slen > 0) {
+ skip = 0;
+ if (IS_CONSOLE(p)) {
+ if ((dc->flags & F_TELNET) != 0) {
+ /* XXX Telnet workarounds */
+ if (p->skip_read -- > 0) {
+ sp ++;
+ slen --;
+ continue;
+ }
+ if (*sp == IAC) {
+ if (verbose)
+ printf("(IAC)");
+ p->skip_read = 2;
+ sp ++;
+ slen --;
+ continue;
+ }
+ if (*sp == 0) {
+ if (verbose)
+ printf("(0 stripped)");
+ sp ++;
+ slen --;
+ continue;
+ }
+ }
+ switch (dc->escape_state) {
+ case STATE1:
+ if (*sp == dc->escape) {
+ skip = 1;
+ dc->escape_state = STATE2;
+ } else
+ dc->escape_state = STATE0;
+ break;
+ case STATE2:
+ dc->escape_state = STATE0;
+ skip = 1;
+ if (*sp == '.')
+ dconschat_cleanup(0);
+ else if (*sp == CTRL('B')) {
+ bcopy(abreak, dp, 3);
+ dp += 3;
+ *dlen += 3;
+ }
+ else if (*sp == CTRL('G'))
+ dconschat_fork_gdb(dc, p);
+ else if ((*sp == CTRL('R'))
+ && (dc->reset != 0)) {
+ dc->escape_state = STATE3;
+ buf = "\r\n[Are you sure to reset target? (y/N)]";
+ write(p->outfd, buf, strlen(buf));
+ } else if (*sp == CTRL('Z'))
+ dconschat_suspend(dc, p);
+ else {
+ skip = 0;
+ *dp++ = dc->escape;
+ (*dlen) ++;
+ }
+ break;
+ case STATE3:
+ dc->escape_state = STATE0;
+ skip = 1;
+ if (*sp == 'y')
+ dconschat_reset_target(dc, p);
+ else {
+ write(p->outfd, sp, 1);
+ write(p->outfd, "\r\n", 2);
+ }
+ break;
+ }
+ if (*sp == KEY_CR)
+ dc->escape_state = STATE1;
+ } else if (IS_GDB(p)) {
+ /* GDB: ^C -> CR+~+^B */
+ if (*sp == CTRL('C') && (dc->flags & F_ALT_BREAK) != 0) {
+ bcopy(abreak, dp, 3);
+ dp += 3;
+ sp ++;
+ *dlen += 3;
+ /* discard rest of the packet */
+ slen = 0;
+ break;
+ }
+ }
+ if (!skip) {
+ *dp++ = *sp;
+ (*dlen) ++;
+ }
+ sp ++;
+ slen --;
+ }
+ return (*dlen);
+
+}
+
+static int
+dconschat_read_socket(struct dcons_state *dc, struct dcons_port *p)
+{
+ struct kevent kev;
+ int len, wlen;
+ char rbuf[MAX_XFER], wbuf[MAX_XFER+2];
+
+ if ((len = read(p->infd, rbuf, sizeof(rbuf))) > 0) {
+ wlen = 0;
+ dconschat_read_filter(dc, p, rbuf, len, wbuf, &wlen);
+ /* XXX discard if not ready*/
+ if (wlen > 0 && (dc->flags & F_READY) != 0) {
+ dconschat_write_dcons(dc, p->port, wbuf, wlen);
+ if (verbose > 1) {
+ wbuf[wlen] = 0;
+ printf("-> %s\n", wbuf);
+ } else if (verbose == 1) {
+ printf("(%d)", wlen);
+ fflush(stdout);
+ }
+ }
+ } else {
+ if (verbose) {
+ if (len == 0)
+ warnx("port%d: closed", p->port);
+ else
+ warn("port%d: read", p->port);
+ }
+ EV_SET(&kev, p->infd, EVFILT_READ,
+ EV_DELETE, 0, 0, NULL);
+ kevent(dc->kq, &kev, 1, NULL, 0, &dc->zero);
+ close(p->infd);
+ close(p->outfd);
+ /* XXX exit for pipe case XXX */
+ EV_SET(&kev, p->s, EVFILT_READ,
+ EV_ADD | EV_ONESHOT, 0, 0, (void *) p);
+ kevent(dc->kq, &kev, 1, NULL, 0, &dc->zero);
+ p->infd = p->outfd = -1;
+ }
+ return(0);
+}
+#define NEVENT 5
+static int
+dconschat_proc_socket(struct dcons_state *dc)
+{
+ struct kevent elist[NEVENT], *e;
+ int i, n;
+ struct dcons_port *p;
+
+ n = kevent(dc->kq, NULL, 0, elist, NEVENT, &dc->to);
+ for (i = 0; i < n; i ++) {
+ e = &elist[i];
+ p = (struct dcons_port *)e->udata;
+ if (e->ident == p->s) {
+ dconschat_accept_socket(dc, p);
+ } else {
+ dconschat_read_socket(dc, p);
+ }
+ }
+ return(0);
+}
+
+static int
+dconschat_proc_dcons(struct dcons_state *dc)
+{
+ int port, len, err;
+ char buf[MAX_XFER];
+ struct dcons_port *p;
+
+ err = dconschat_get_ptr(dc);
+ if (err) {
+ /* XXX we should stop write operation too. */
+ return err;
+ }
+ for (port = 0; port < DCONS_NPORT; port ++) {
+ p = &dc->port[port];
+ if (p->infd < 0)
+ continue;
+ while ((len = dconschat_read_dcons(dc, port, buf,
+ sizeof(buf))) > 0) {
+ dconschat_write_socket(p->outfd, buf, len);
+ if ((err = dconschat_get_ptr(dc)))
+ return (err);
+ }
+ if ((dc->flags & F_ONE_SHOT) != 0 && len <= 0)
+ dconschat_cleanup(0);
+ }
+ return 0;
+}
+
+static int
+dconschat_start_session(struct dcons_state *dc)
+{
+ int counter = 0;
+ int retry = 0;
+ int retry_unit_init = MAX(1, poll_hz / 10);
+ int retry_unit_offline = poll_hz * DCONS_POLL_OFFLINE;
+ int retry_unit = retry_unit_init;
+ int retry_max = retry_unit_offline / retry_unit;
+
+ while (1) {
+ if (((dc->flags & F_READY) == 0) && ++counter > retry_unit) {
+ counter = 0;
+ retry ++;
+ if (retry > retry_max)
+ retry_unit = retry_unit_offline;
+ if (verbose) {
+ printf("%d/%d ", retry, retry_max);
+ fflush(stdout);
+ }
+ dconschat_fetch_header(dc);
+ }
+ if ((dc->flags & F_READY) != 0) {
+ counter = 0;
+ retry = 0;
+ retry_unit = retry_unit_init;
+ dconschat_proc_dcons(dc);
+ }
+ dconschat_proc_socket(dc);
+ }
+ return (0);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: dconschat [-brvwRT1] [-h hz] [-C port] [-G port]\n"
+ "\t\t\t[-M core] [-N system]\n"
+ "\t\t\t[-u unit] [-a address] [-t target_eui64]\n"
+ "\t-b translate ctrl-C to CR+~+ctrl-B on gdb port\n"
+ "\t-v verbose\n"
+ "\t-w listen on wildcard address rather than localhost\n"
+ "\t-r replay old buffer on connection\n"
+ "\t-R read-only\n"
+ "\t-T enable Telnet protocol workaround on console port\n"
+ "\t-1 one shot: read buffer and exit\n"
+ "\t-h polling rate\n"
+ "\t-C port number for console port\n"
+ "\t-G port number for gdb port\n"
+ "\t(for KVM)\n"
+ "\t-M core file\n"
+ "\t-N system file\n"
+ "\t(for FireWire)\n"
+ "\t-u specify unit number of the bus\n"
+ "\t-t EUI64 of target host (must be specified)\n"
+ "\t-a physical address of dcons buffer on target host\n"
+ );
+ exit(0);
+}
+int
+main(int argc, char **argv)
+{
+ struct dcons_state *dc;
+ struct fw_eui64 eui;
+ struct eui64 target;
+ char devname[256], *core = NULL, *system = NULL;
+ int i, ch, error;
+ int unit=0, wildcard=0;
+ int port[DCONS_NPORT];
+
+ bzero(&sc, sizeof(sc));
+ dc = &sc;
+ dc->flags |= USE_CROM ? F_USE_CROM : 0;
+
+ /* default ports */
+ port[0] = 0; /* stdin/out for console */
+ port[1] = -1; /* disable gdb port */
+
+ /* default escape char */
+ dc->escape = KEY_TILDE;
+
+ while ((ch = getopt(argc, argv, "a:be:h:rt:u:vwC:G:M:N:RT1")) != -1) {
+ switch(ch) {
+ case 'a':
+ dc->paddr = strtoull(optarg, NULL, 0);
+ dc->flags &= ~F_USE_CROM;
+ break;
+ case 'b':
+ dc->flags |= F_ALT_BREAK;
+ break;
+ case 'e':
+ dc->escape = optarg[0];
+ break;
+ case 'h':
+ poll_hz = strtoul(optarg, NULL, 0);
+ if (poll_hz == 0)
+ poll_hz = DCONS_POLL_HZ;
+ break;
+ case 'r':
+ dc->flags |= F_REPLAY;
+ break;
+ case 't':
+ if (eui64_hostton(optarg, &target) != 0 &&
+ eui64_aton(optarg, &target) != 0)
+ errx(1, "invalid target: %s", optarg);
+ eui.hi = ntohl(*(u_int32_t*)&(target.octet[0]));
+ eui.lo = ntohl(*(u_int32_t*)&(target.octet[4]));
+ dc->type = TYPE_FW;
+ break;
+ case 'u':
+ unit = strtol(optarg, NULL, 0);
+ break;
+ case 'v':
+ verbose ++;
+ break;
+ case 'w':
+ wildcard = 1;
+ break;
+ case 'C':
+ port[0] = strtol(optarg, NULL, 0);
+ break;
+ case 'G':
+ port[1] = strtol(optarg, NULL, 0);
+ break;
+ case 'M':
+ core = optarg;
+ break;
+ case 'N':
+ system = optarg;
+ break;
+ case 'R':
+ dc->flags |= F_RD_ONLY;
+ break;
+ case 'T':
+ dc->flags |= F_TELNET;
+ break;
+ case '1':
+ dc->flags |= F_ONE_SHOT | F_REPLAY;
+ break;
+ default:
+ usage();
+ }
+ }
+ if (dc->paddr == 0 && (dc->flags & F_USE_CROM) == 0) {
+ warnx("no address specified");
+ usage();
+ }
+
+ if (port[0] < 0 && port[1] < 0) {
+ warnx("no port specified");
+ usage();
+ }
+
+ /* set signal handler */
+ signal(SIGHUP, dconschat_cleanup);
+ signal(SIGINT, dconschat_cleanup);
+ signal(SIGPIPE, dconschat_cleanup);
+ signal(SIGTERM, dconschat_cleanup);
+
+ /* init firewire */
+ switch (dc->type) {
+ case TYPE_FW:
+#define MAXDEV 10
+ for (i = 0; i < MAXDEV; i ++) {
+ snprintf(devname, sizeof(devname),
+ "/dev/fwmem%d.%d", unit, i);
+ dc->fd = open(devname, O_RDWR);
+ if (dc->fd >= 0)
+ goto found;
+ }
+ err(1, "open");
+found:
+ error = ioctl(dc->fd, FW_SDEUI64, &eui);
+ if (error)
+ err(1, "ioctl");
+ break;
+ case TYPE_KVM:
+ {
+ struct nlist nl[] = {{"dcons_buf"}, {""}};
+ void *dcons_buf;
+
+ dc->kd = kvm_open(system, core, NULL,
+ (dc->flags & F_RD_ONLY) ? O_RDONLY : O_RDWR, "dconschat");
+ if (dc->kd == NULL)
+ errx(1, "kvm_open");
+
+ if (kvm_nlist(dc->kd, nl) < 0)
+ errx(1, "kvm_nlist: %s", kvm_geterr(dc->kd));
+
+ if (kvm_read(dc->kd, nl[0].n_value, &dcons_buf,
+ sizeof(void *)) < 0)
+ errx(1, "kvm_read: %s", kvm_geterr(dc->kd));
+ dc->paddr = (uintptr_t)dcons_buf;
+ if (verbose)
+ printf("dcons_buf: 0x%x\n", (uint)dc->paddr);
+ break;
+ }
+ }
+ dconschat_fetch_header(dc);
+
+ /* init sockets */
+ dc->kq = kqueue();
+ if (poll_hz == 1) {
+ dc->to.tv_sec = 1;
+ dc->to.tv_nsec = 0;
+ } else {
+ dc->to.tv_sec = 0;
+ dc->to.tv_nsec = 1000 * 1000 * 1000 / poll_hz;
+ }
+ dc->zero.tv_sec = 0;
+ dc->zero.tv_nsec = 0;
+ for (i = 0; i < DCONS_NPORT; i++)
+ dconschat_init_socket(dc, i,
+ wildcard ? NULL : "localhost", port[i]);
+
+ dconschat_start_session(dc);
+
+ for (i = 0; i < DCONS_NPORT; i++) {
+ freeaddrinfo(dc->port[i].res);
+ }
+ return (0);
+}
diff --git a/usr.sbin/devctl/Makefile b/usr.sbin/devctl/Makefile
new file mode 100644
index 0000000..a7deb37
--- /dev/null
+++ b/usr.sbin/devctl/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= devctl
+MAN= devctl.8
+
+LIBADD= devctl
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/devctl/Makefile.depend b/usr.sbin/devctl/Makefile.depend
new file mode 100644
index 0000000..34e1a91
--- /dev/null
+++ b/usr.sbin/devctl/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libdevctl \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/devctl/devctl.8 b/usr.sbin/devctl/devctl.8
new file mode 100644
index 0000000..fef42be
--- /dev/null
+++ b/usr.sbin/devctl/devctl.8
@@ -0,0 +1,137 @@
+.\"
+.\" Copyright (c) 2015 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$
+.\"
+.Dd February 5, 2015
+.Dt DEVCTL 8
+.Os
+.Sh NAME
+.Nm devctl
+.Nd device control utility
+.Sh SYNOPSIS
+.Nm
+.Cm attach
+.Ar device
+.Nm
+.Cm detach
+.Op Fl f
+.Ar device
+.Nm
+.Cm disable
+.Op Fl f
+.Ar device
+.Nm
+.Cm enable
+.Ar device
+.Nm
+.Cm suspend
+.Ar device
+.Nm
+.Cm resume
+.Ar device
+.Nm
+.Cm set driver
+.Op Fl f
+.Ar device driver
+.Sh DESCRIPTION
+The
+.Nm
+utility adjusts the state of individual devices in the kernel's
+internal device hierarchy.
+Each invocation of
+.Nm
+consists of a single command followed by command-specific arguments.
+Each command operates on a single device specified via the
+.Ar device
+argument.
+The
+.Ar device
+may be specified either as the name of an existing device or as a
+bus-specific address.
+More details on supported address formats can be found in
+.Xr devctl 3 .
+.Pp
+The following commands are supported:
+.Bl -tag -width indent
+.It Cm attach Ar device
+Force the kernel to re-probe the device.
+If a suitable driver is found,
+it is attached to the device.
+.It Xo Cm detach
+.Op Fl f
+.Ar device
+.Xc
+Detach the device from its current device driver.
+If the
+.Fl f
+flag is specified,
+the device driver will be detached even if the device is busy.
+.It Xo Cm disable
+.Op Fl f
+.Ar device
+.Xc
+Disable a device.
+If the device is currently attached to a device driver,
+the device driver will be detached from the device,
+but the device will retain its current name.
+If the
+.Fl f
+flag is specified,
+the device driver will be detached even if the device is busy.
+.It Cm enable Ar device
+Enable a device.
+The device will probe and attach if a suitable device driver is found.
+Note that this can re-enable a device disabled at boot time via a
+loader tunable.
+.It Cm suspend Ar device
+Suspend a device.
+This may include placing the device in a reduced power state.
+.It Cm resume Ar device
+Resume a suspended device to a fully working state.
+.It Xo Cm set driver
+.Op Fl f
+.Ar device driver
+.Xc
+Force the device to use a device driver named
+.Ar driver .
+If the device is already attached to a device driver and the
+.Fl f
+flag is specified,
+the device will be detached from its current device driver before it is
+attached to the new device driver.
+If the device is already attached to a device driver and the
+.Fl f
+flag is not specified,
+the device will not be changed.
+.El
+.Sh SEE ALSO
+.Xr devctl 3 ,
+.Xr devinfo 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 11.0 .
diff --git a/usr.sbin/devctl/devctl.c b/usr.sbin/devctl/devctl.c
new file mode 100644
index 0000000..076c650
--- /dev/null
+++ b/usr.sbin/devctl/devctl.c
@@ -0,0 +1,282 @@
+/*-
+ * Copyright (c) 2014 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/linker_set.h>
+#include <devctl.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+struct devctl_command {
+ const char *name;
+ int (*handler)(int ac, char **av);
+};
+
+#define DEVCTL_DATASET(name) devctl_ ## name ## _table
+
+#define DEVCTL_COMMAND(set, name, function) \
+ static struct devctl_command function ## _devctl_command = \
+ { #name, function }; \
+ DATA_SET(DEVCTL_DATASET(set), function ## _devctl_command)
+
+#define DEVCTL_TABLE(set, name) \
+ SET_DECLARE(DEVCTL_DATASET(name), struct devctl_command); \
+ \
+ static int \
+ devctl_ ## name ## _table_handler(int ac, char **av) \
+ { \
+ return (devctl_table_handler(SET_BEGIN(DEVCTL_DATASET(name)), \
+ SET_LIMIT(DEVCTL_DATASET(name)), ac, av)); \
+ } \
+ DEVCTL_COMMAND(set, name, devctl_ ## name ## _table_handler)
+
+static int devctl_table_handler(struct devctl_command **start,
+ struct devctl_command **end, int ac, char **av);
+
+SET_DECLARE(DEVCTL_DATASET(top), struct devctl_command);
+
+DEVCTL_TABLE(top, set);
+
+static void
+usage(void)
+{
+ fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
+ "usage: devctl attach device",
+ " devctl detach [-f] device",
+ " devctl disable [-f] device",
+ " devctl enable device",
+ " devctl suspend device",
+ " devctl resume device",
+ " devctl set driver [-f] device driver");
+ exit(1);
+}
+
+static int
+devctl_table_handler(struct devctl_command **start,
+ struct devctl_command **end, int ac, char **av)
+{
+ struct devctl_command **cmd;
+
+ if (ac < 2) {
+ warnx("The %s command requires a sub-command.", av[0]);
+ return (EINVAL);
+ }
+ for (cmd = start; cmd < end; cmd++) {
+ if (strcmp((*cmd)->name, av[1]) == 0)
+ return ((*cmd)->handler(ac - 1, av + 1));
+ }
+
+ warnx("%s is not a valid sub-command of %s.", av[1], av[0]);
+ return (ENOENT);
+}
+
+static int
+help(int ac __unused, char **av __unused)
+{
+
+ usage();
+ return (0);
+}
+DEVCTL_COMMAND(top, help, help);
+
+static int
+attach(int ac, char **av)
+{
+
+ if (ac != 2)
+ usage();
+ if (devctl_attach(av[1]) < 0)
+ err(1, "Failed to attach %s", av[1]);
+ return (0);
+}
+DEVCTL_COMMAND(top, attach, attach);
+
+static void
+detach_usage(void)
+{
+
+ fprintf(stderr, "usage: devctl detach [-f] device\n");
+ exit(1);
+}
+
+static int
+detach(int ac, char **av)
+{
+ bool force;
+ int ch;
+
+ force = false;
+ while ((ch = getopt(ac, av, "f")) != -1)
+ switch (ch) {
+ case 'f':
+ force = true;
+ break;
+ default:
+ detach_usage();
+ }
+ ac -= optind;
+ av += optind;
+
+ if (ac != 1)
+ detach_usage();
+ if (devctl_detach(av[0], force) < 0)
+ err(1, "Failed to detach %s", av[0]);
+ return (0);
+}
+DEVCTL_COMMAND(top, detach, detach);
+
+static void
+disable_usage(void)
+{
+
+ fprintf(stderr, "usage: devctl disable [-f] device\n");
+ exit(1);
+}
+
+static int
+disable(int ac, char **av)
+{
+ bool force;
+ int ch;
+
+ force = false;
+ while ((ch = getopt(ac, av, "f")) != -1)
+ switch (ch) {
+ case 'f':
+ force = true;
+ break;
+ default:
+ disable_usage();
+ }
+ ac -= optind;
+ av += optind;
+
+ if (ac != 1)
+ disable_usage();
+ if (devctl_disable(av[0], force) < 0)
+ err(1, "Failed to disable %s", av[0]);
+ return (0);
+}
+DEVCTL_COMMAND(top, disable, disable);
+
+static int
+enable(int ac, char **av)
+{
+
+ if (ac != 2)
+ usage();
+ if (devctl_enable(av[1]) < 0)
+ err(1, "Failed to enable %s", av[1]);
+ return (0);
+}
+DEVCTL_COMMAND(top, enable, enable);
+
+static int
+suspend(int ac, char **av)
+{
+
+ if (ac != 2)
+ usage();
+ if (devctl_suspend(av[1]) < 0)
+ err(1, "Failed to suspend %s", av[1]);
+ return (0);
+}
+DEVCTL_COMMAND(top, suspend, suspend);
+
+static int
+resume(int ac, char **av)
+{
+
+ if (ac != 2)
+ usage();
+ if (devctl_resume(av[1]) < 0)
+ err(1, "Failed to resume %s", av[1]);
+ return (0);
+}
+DEVCTL_COMMAND(top, resume, resume);
+
+static void
+set_driver_usage(void)
+{
+
+ fprintf(stderr, "usage: devctl set driver [-f] device driver\n");
+ exit(1);
+}
+
+static int
+set_driver(int ac, char **av)
+{
+ bool force;
+ int ch;
+
+ force = false;
+ while ((ch = getopt(ac, av, "f")) != -1)
+ switch (ch) {
+ case 'f':
+ force = true;
+ break;
+ default:
+ set_driver_usage();
+ }
+ ac -= optind;
+ av += optind;
+
+ if (ac != 2)
+ set_driver_usage();
+ if (devctl_set_driver(av[0], av[1], force) < 0)
+ err(1, "Failed to set %s driver to %s", av[0], av[1]);
+ return (0);
+}
+DEVCTL_COMMAND(set, driver, set_driver);
+
+int
+main(int ac, char *av[])
+{
+ struct devctl_command **cmd;
+
+ if (ac == 1)
+ usage();
+ ac--;
+ av++;
+
+ SET_FOREACH(cmd, DEVCTL_DATASET(top)) {
+ if (strcmp((*cmd)->name, av[0]) == 0) {
+ if ((*cmd)->handler(ac, av) != 0)
+ return (1);
+ else
+ return (0);
+ }
+ }
+ warnx("Unknown command %s.", av[0]);
+ return (1);
+}
diff --git a/usr.sbin/devinfo/Makefile b/usr.sbin/devinfo/Makefile
new file mode 100644
index 0000000..681c819
--- /dev/null
+++ b/usr.sbin/devinfo/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= devinfo
+MAN= devinfo.8
+
+LIBADD= devinfo
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/devinfo/Makefile.depend b/usr.sbin/devinfo/Makefile.depend
new file mode 100644
index 0000000..a6afa47
--- /dev/null
+++ b/usr.sbin/devinfo/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libdevinfo \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/devinfo/devinfo.8 b/usr.sbin/devinfo/devinfo.8
new file mode 100644
index 0000000..a1743ff
--- /dev/null
+++ b/usr.sbin/devinfo/devinfo.8
@@ -0,0 +1,76 @@
+.\" -*- nroff -*-
+.\"
+.\" Copyright (c) 2002 Hiten Pandya
+.\" Copyright (c) 2002 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 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 November 28, 2005
+.Dt DEVINFO 8
+.Os
+.Sh NAME
+.Nm devinfo
+.Nd print information about system device configuration
+.Sh SYNOPSIS
+.Nm
+.Op Fl rv
+.Nm
+.Fl u
+.Sh DESCRIPTION
+The
+.Nm
+utility, without any arguments, shows the hierarchy of devices available
+in the system, starting from the
+.Dq nexus
+device.
+.Pp
+The following options are accepted.
+.Bl -tag -width indent
+.It Fl r
+Causes hardware resource information (such as IRQ, I/O ports, I/O memory
+addresses) to be also listed, under each device that has reserved those resources.
+.It Fl u
+Displays the same information as with
+.Fl r
+but sorts by resource type rather than by device, allowing to review the
+set of system resources by usage and available resources.
+I.e., it lists all
+the IRQ consumers together.
+.It Fl v
+Display all devices in the driver tree, not just those that are attached or
+busy.
+Without this flag, only those devices that have attached are reported.
+.El
+.Sh SEE ALSO
+.Xr systat 1 ,
+.Xr devinfo 3 ,
+.Xr iostat 8 ,
+.Xr pciconf 8 ,
+.Xr pnpinfo 8 ,
+.Xr vmstat 8 ,
+.Xr devclass 9 ,
+.Xr device 9
+.Sh AUTHORS
+.An Mike Smith Aq Mt msmith@FreeBSD.org
diff --git a/usr.sbin/devinfo/devinfo.c b/usr.sbin/devinfo/devinfo.c
new file mode 100644
index 0000000..40f2b0b
--- /dev/null
+++ b/usr.sbin/devinfo/devinfo.c
@@ -0,0 +1,237 @@
+/*-
+ * Copyright (c) 2000, 2001 Michael Smith
+ * Copyright (c) 2000 BSDi
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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.
+ */
+
+/*
+ * Print information about system device configuration.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "devinfo.h"
+
+static int rflag;
+static int vflag;
+
+static void print_resource(struct devinfo_res *);
+static int print_device_matching_resource(struct devinfo_res *, void *);
+static int print_device_rman_resources(struct devinfo_rman *, void *);
+static int print_device(struct devinfo_dev *, void *);
+static int print_rman_resource(struct devinfo_res *, void *);
+static int print_rman(struct devinfo_rman *, void *);
+
+struct indent_arg
+{
+ int indent;
+ void *arg;
+};
+
+/*
+ * Print a resource.
+ */
+void
+print_resource(struct devinfo_res *res)
+{
+ struct devinfo_rman *rman;
+ int hexmode;
+
+ rman = devinfo_handle_to_rman(res->dr_rman);
+ hexmode = (rman->dm_size > 1000) || (rman->dm_size == 0);
+ printf(hexmode ? "0x%lx" : "%lu", res->dr_start);
+ if (res->dr_size > 1)
+ printf(hexmode ? "-0x%lx" : "-%lu",
+ res->dr_start + res->dr_size - 1);
+}
+
+/*
+ * Print resource information if this resource matches the
+ * given device.
+ *
+ * If the given indent is 0, return an indicator that a matching
+ * resource exists.
+ */
+int
+print_device_matching_resource(struct devinfo_res *res, void *arg)
+{
+ struct indent_arg *ia = (struct indent_arg *)arg;
+ struct devinfo_dev *dev = (struct devinfo_dev *)ia->arg;
+ int i;
+
+ if (devinfo_handle_to_device(res->dr_device) == dev) {
+ /* in 'detect' mode, found a match */
+ if (ia->indent == 0)
+ return(1);
+ for (i = 0; i < ia->indent; i++)
+ printf(" ");
+ print_resource(res);
+ printf("\n");
+ }
+ return(0);
+}
+
+/*
+ * Print resource information for this device and resource manager.
+ */
+int
+print_device_rman_resources(struct devinfo_rman *rman, void *arg)
+{
+ struct indent_arg *ia = (struct indent_arg *)arg;
+ int indent, i;
+
+ indent = ia->indent;
+
+ /* check whether there are any resources matching this device */
+ ia->indent = 0;
+ if (devinfo_foreach_rman_resource(rman,
+ print_device_matching_resource, ia) != 0) {
+
+ /* there are, print header */
+ for (i = 0; i < indent; i++)
+ printf(" ");
+ printf("%s:\n", rman->dm_desc);
+
+ /* print resources */
+ ia->indent = indent + 4;
+ devinfo_foreach_rman_resource(rman,
+ print_device_matching_resource, ia);
+ }
+ ia->indent = indent;
+ return(0);
+}
+
+/*
+ * Print information about a device.
+ */
+int
+print_device(struct devinfo_dev *dev, void *arg)
+{
+ struct indent_arg ia;
+ int i, indent;
+
+ if (vflag || (dev->dd_name[0] != 0 && dev->dd_state >= DS_ATTACHED)) {
+ indent = (int)(intptr_t)arg;
+ for (i = 0; i < indent; i++)
+ printf(" ");
+ printf("%s", dev->dd_name[0] ? dev->dd_name : "unknown");
+ if (vflag && *dev->dd_pnpinfo)
+ printf(" pnpinfo %s", dev->dd_pnpinfo);
+ if (vflag && *dev->dd_location)
+ printf(" at %s", dev->dd_location);
+ if (!(dev->dd_flags & DF_ENABLED))
+ printf(" (disabled)");
+ else if (dev->dd_flags & DF_SUSPENDED)
+ printf(" (suspended)");
+ printf("\n");
+ if (rflag) {
+ ia.indent = indent + 4;
+ ia.arg = dev;
+ devinfo_foreach_rman(print_device_rman_resources,
+ (void *)&ia);
+ }
+ }
+
+ return(devinfo_foreach_device_child(dev, print_device,
+ (void *)((char *)arg + 2)));
+}
+
+/*
+ * Print information about a resource under a resource manager.
+ */
+int
+print_rman_resource(struct devinfo_res *res, void *arg __unused)
+{
+ struct devinfo_dev *dev;
+
+ printf(" ");
+ print_resource(res);
+ dev = devinfo_handle_to_device(res->dr_device);
+ if ((dev != NULL) && (dev->dd_name[0] != 0)) {
+ printf(" (%s)", dev->dd_name);
+ } else {
+ printf(" ----");
+ }
+ printf("\n");
+ return(0);
+}
+
+/*
+ * Print information about a resource manager.
+ */
+int
+print_rman(struct devinfo_rman *rman, void *arg __unused)
+{
+ printf("%s:\n", rman->dm_desc);
+ devinfo_foreach_rman_resource(rman, print_rman_resource, 0);
+ return(0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct devinfo_dev *root;
+ int c, uflag;
+
+ uflag = 0;
+ while ((c = getopt(argc, argv, "ruv")) != -1) {
+ switch(c) {
+ case 'r':
+ rflag++;
+ break;
+ case 'u':
+ uflag++;
+ break;
+ case 'v':
+ vflag++;
+ break;
+ default:
+ fprintf(stderr, "%s\n%s\n",
+ "usage: devinfo [-rv]",
+ " devinfo -u");
+ exit(1);
+ }
+ }
+
+ if (devinfo_init())
+ err(1, "devinfo_init");
+
+ if ((root = devinfo_handle_to_device(DEVINFO_ROOT_DEVICE)) == NULL)
+ errx(1, "can't find root device");
+
+ /* print resource usage? */
+ if (uflag) {
+ devinfo_foreach_rman(print_rman, NULL);
+ } else {
+ /* print device hierarchy */
+ devinfo_foreach_device_child(root, print_device, (void *)0);
+ }
+ return(0);
+}
diff --git a/usr.sbin/digictl/Makefile b/usr.sbin/digictl/Makefile
new file mode 100644
index 0000000..cb3745b
--- /dev/null
+++ b/usr.sbin/digictl/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= digictl
+MAN= digictl.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/digictl/Makefile.depend b/usr.sbin/digictl/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/digictl/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/digictl/digictl.8 b/usr.sbin/digictl/digictl.8
new file mode 100644
index 0000000..fefefe4
--- /dev/null
+++ b/usr.sbin/digictl/digictl.8
@@ -0,0 +1,118 @@
+.\" $FreeBSD$
+.Dd June 20, 2001
+.Dt DIGICTL 8
+.Os
+.Sh NAME
+.Nm digictl
+.Nd control
+.Tn Digiboard
+devices
+.Sh SYNOPSIS
+.Nm
+.Fl a
+.Cm disable | enable | query
+.Ar device ...
+.Nm
+.Op Fl d Ar debug
+.Op Fl ir
+.Ar ctrl-device ...
+.Sh DESCRIPTION
+The
+.Nm
+utility provides control of the
+.Tn Digiboard
+installed with the given
+.Ar ctrl-device
+name and provides control of individual digiboard
+.Ar devices .
+A digiboard
+.Ar ctrl-device
+is usually of the form
+.Sm off
+.Pa /dev/digi Ar N Pa .ctl
+.Sm on
+where
+.Ar N
+is the card number and starts at
+.Dq 0
+for the first attached card.
+A digiboard
+.Ar device
+is usually of the form
+.Sm off
+.Pa /dev/cua Oo Pa il Oc Pa D Ar N Pa \&. Ar P
+.Sm on
+or
+.Sm off
+.Pa /dev/tty Oo Pa il Oc Pa D Ar N Pa \&. Ar P
+.Sm on
+where
+.Ar N
+is the card number and
+.Ar P
+is the port number.
+.Pp
+The following flags are recognized:
+.Bl -tag -width 10n
+.It Fl a Cm disable | enable | query
+Disable, enable or query the
+.Em ALTPIN
+settings for the given port(s).
+.Pp
+When ALTPIN is enabled, the CD and DSR lines are logically reversed.
+This is useful when wiring serial ports to an 8 way RJ45 cable (full
+10 way RJ45 cables are quite rare).
+.Pp
+A single ALTPIN setting applies to both of the callout and callin devices.
+.It Fl d Ar debug
+If the driver has been compiled with
+.Dv DEBUG
+defined, the following bits from the
+.Ar debug
+variable are used to enable diagnostics in the digiboard driver:
+.Bl -tag -width ".No 1024 ( Em MODEM )"
+.It 1 ( Em INIT )
+Diagnostics during card attach, detach and initialization.
+.It 2 ( Em OPEN )
+Diagnostics when opening a port.
+.It 4 ( Em CLOSE )
+Diagnostics when closing a port.
+.It 8 ( Em SET )
+Diagnostics when setting tty device flags.
+.It 16 ( Em INT )
+Diagnostics when processing card events.
+.It 32 ( Em READ )
+Reports return values from port reads.
+.It 64 ( Em WRITE )
+Reports return values from port writes.
+.It 128 ( Em RX )
+Reports receive queue flow control.
+.It 256 ( Em TX )
+Reports transmit queue flow control.
+.It 512 ( Em IRQ )
+Diagnostics during interrupts (enable these with care).
+.It 1024 ( Em MODEM )
+Diagnostics when setting modem status flags.
+.It 2048 ( Em RI )
+Reports when a RING is received.
+.El
+.It Fl i
+Display the card identification string and type ID.
+.It Fl r
+Reinitialize the card.
+For boards with external port modules, it is possible to add or remove
+modules and dynamically reprobe the number of ports using this switch.
+All ports on the card must be closed in order to reinitialize the card.
+.Pp
+It is preferable to reinitialize the card rather than reload the entire
+digi module as reinitialization only affects the specified board rather
+than affecting all attached boards.
+.El
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 5.0 .
+.Sh BUGS
+It should be possible to reinitialize a board without closing all of the
+existing ports.
diff --git a/usr.sbin/digictl/digictl.c b/usr.sbin/digictl/digictl.c
new file mode 100644
index 0000000..0aa5f68
--- /dev/null
+++ b/usr.sbin/digictl/digictl.c
@@ -0,0 +1,171 @@
+/*-
+ * Copyright (c) 2001 Brian Somers <brian@Awfulhak.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 <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <sys/digiio.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+enum aflag { NO_AFLAG, ALTPIN_DISABLE, ALTPIN_ENABLE, ALTPIN_QUERY };
+
+static int
+usage(const char *prog)
+{
+ fprintf(stderr, "usage: %s -a disable|enable|query device\n", prog);
+ fprintf(stderr, " %s [-d debug] [-ir] ctrl-device...\n", prog);
+ return (EX_USAGE);
+}
+
+int
+main(int argc, char **argv)
+{
+ char namedata[256], *name = namedata;
+ const char *prog;
+ enum digi_model model;
+ int altpin, ch, debug, fd, i, res;
+ int dflag, iflag, rflag;
+ enum aflag aflag;
+
+ if ((prog = strrchr(argv[0], '/')) == NULL)
+ prog = argv[0];
+ else
+ prog++;
+
+ aflag = NO_AFLAG;
+ dflag = iflag = rflag = 0;
+ while ((ch = getopt(argc, argv, "a:d:ir")) != -1)
+ switch (ch) {
+ case 'a':
+ if (strcasecmp(optarg, "disable") == 0)
+ aflag = ALTPIN_DISABLE;
+ else if (strcasecmp(optarg, "enable") == 0)
+ aflag = ALTPIN_ENABLE;
+ else if (strcasecmp(optarg, "query") == 0)
+ aflag = ALTPIN_QUERY;
+ else
+ return (usage(prog));
+ break;
+
+ case 'd':
+ dflag = 1;
+ debug = atoi(optarg);
+ break;
+
+ case 'i':
+ iflag = 1;
+ break;
+
+ case 'r':
+ rflag = 1;
+ break;
+
+ default:
+ return (usage(prog));
+ }
+
+ if ((dflag || iflag || rflag) && aflag != NO_AFLAG)
+ return (usage(prog));
+
+ if (argc <= optind)
+ return (usage(prog));
+
+ altpin = (aflag == ALTPIN_ENABLE);
+ res = 0;
+
+ for (i = optind; i < argc; i++) {
+ if ((fd = open(argv[i], O_RDONLY)) == -1) {
+ fprintf(stderr, "%s: %s: open: %s\n", prog, argv[i],
+ strerror(errno));
+ res++;
+ continue;
+ }
+
+ switch (aflag) {
+ case NO_AFLAG:
+ break;
+
+ case ALTPIN_ENABLE:
+ case ALTPIN_DISABLE:
+ if (ioctl(fd, DIGIIO_SETALTPIN, &altpin) != 0) {
+ fprintf(stderr, "%s: %s: set altpin: %s\n",
+ prog, argv[i], strerror(errno));
+ res++;
+ }
+ break;
+
+ case ALTPIN_QUERY:
+ if (ioctl(fd, DIGIIO_GETALTPIN, &altpin) != 0) {
+ fprintf(stderr, "%s: %s: get altpin: %s\n",
+ prog, argv[i], strerror(errno));
+ res++;
+ } else {
+ if (argc > optind + 1)
+ printf("%s: ", argv[i]);
+ puts(altpin ? "enabled" : "disabled");
+ }
+ break;
+ }
+
+ if (dflag && ioctl(fd, DIGIIO_DEBUG, &debug) != 0) {
+ fprintf(stderr, "%s: %s: debug: %s\n",
+ prog, argv[i], strerror(errno));
+ res++;
+ }
+
+ if (iflag) {
+ if (ioctl(fd, DIGIIO_MODEL, &model) != 0) {
+ fprintf(stderr, "%s: %s: model: %s\n",
+ prog, argv[i], strerror(errno));
+ res++;
+ } else if (ioctl(fd, DIGIIO_IDENT, &name) != 0) {
+ fprintf(stderr, "%s: %s: ident: %s\n",
+ prog, argv[i], strerror(errno));
+ res++;
+ } else {
+ if (argc > optind + 1)
+ printf("%s: ", argv[i]);
+ printf("%s (type %d)\n", name, (int)model);
+ }
+ }
+
+ if (rflag && ioctl(fd, DIGIIO_REINIT) != 0) {
+ fprintf(stderr, "%s: %s: reinit: %s\n",
+ prog, argv[i], strerror(errno));
+ res++;
+ }
+
+ close(fd);
+ }
+
+ return (res);
+}
diff --git a/usr.sbin/diskinfo/Makefile b/usr.sbin/diskinfo/Makefile
new file mode 100644
index 0000000..41c52da
--- /dev/null
+++ b/usr.sbin/diskinfo/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+PROG= diskinfo
+MAN= diskinfo.8
+
+LIBADD= util
+
+.include <bsd.prog.mk>
+
+test: ${PROG}
+ ./${PROG} /dev/ad4 md50
+ ./${PROG} -v /dev/ad4 md50
+ ./${PROG} -t /dev/ad4
diff --git a/usr.sbin/diskinfo/Makefile.depend b/usr.sbin/diskinfo/Makefile.depend
new file mode 100644
index 0000000..58f9a33
--- /dev/null
+++ b/usr.sbin/diskinfo/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/diskinfo/diskinfo.8 b/usr.sbin/diskinfo/diskinfo.8
new file mode 100644
index 0000000..f68d426
--- /dev/null
+++ b/usr.sbin/diskinfo/diskinfo.8
@@ -0,0 +1,74 @@
+.\"
+.\" Copyright (c) 2003 Poul-Henning Kamp
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 November 9, 2004
+.Dt DISKINFO 8
+.Os
+.Sh NAME
+.Nm diskinfo
+.Nd get information about disk device
+.Sh SYNOPSIS
+.Nm
+.Op Fl ctv
+.Ar disk ...
+.Sh DESCRIPTION
+The
+.Nm
+utility prints out information about a disk device,
+and optionally runs a naive performance test on the device.
+.Pp
+If given no arguments, the output will be a single line per specified device
+with the following fields: device name, sectorsize, media size in bytes,
+media size in sectors, stripe size, stripe offset, firmware cylinders,
+firmware heads, and firmware sectors.
+The last three fields are only present if the information is available.
+.Pp
+If given the
+.Fl v
+option, the fields will be printed one per line with a descriptive comment.
+.Pp
+The
+.Fl c
+option triggers a simple measurement of the I/O read command overhead.
+.Pp
+The
+.Fl t
+option triggers a simple and rather naive benchmark of the disks seek
+and transfer performance.
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Fx 5.1 .
+.Sh AUTHORS
+.An Poul-Henning Kamp
+.Sh BUGS
+There are in order of increasing severity: lies,
+damn lies, statistics, and computer benchmarks.
diff --git a/usr.sbin/diskinfo/diskinfo.c b/usr.sbin/diskinfo/diskinfo.c
new file mode 100644
index 0000000..90beba2
--- /dev/null
+++ b/usr.sbin/diskinfo/diskinfo.c
@@ -0,0 +1,388 @@
+/*-
+ * Copyright (c) 2003 Poul-Henning Kamp
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libutil.h>
+#include <paths.h>
+#include <err.h>
+#include <sys/disk.h>
+#include <sys/param.h>
+#include <sys/time.h>
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: diskinfo [-ctv] disk ...\n");
+ exit (1);
+}
+
+static int opt_c, opt_t, opt_v;
+
+static void speeddisk(int fd, off_t mediasize, u_int sectorsize);
+static void commandtime(int fd, off_t mediasize, u_int sectorsize);
+
+int
+main(int argc, char **argv)
+{
+ int i, ch, fd, error, exitval = 0;
+ char buf[BUFSIZ], ident[DISK_IDENT_SIZE], physpath[MAXPATHLEN];
+ off_t mediasize, stripesize, stripeoffset;
+ u_int sectorsize, fwsectors, fwheads;
+
+ while ((ch = getopt(argc, argv, "ctv")) != -1) {
+ switch (ch) {
+ case 'c':
+ opt_c = 1;
+ opt_v = 1;
+ break;
+ case 't':
+ opt_t = 1;
+ opt_v = 1;
+ break;
+ case 'v':
+ opt_v = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ usage();
+
+ for (i = 0; i < argc; i++) {
+ fd = open(argv[i], O_RDONLY);
+ if (fd < 0 && errno == ENOENT && *argv[i] != '/') {
+ sprintf(buf, "%s%s", _PATH_DEV, argv[i]);
+ fd = open(buf, O_RDONLY);
+ }
+ if (fd < 0) {
+ warn("%s", argv[i]);
+ exitval = 1;
+ goto out;
+ }
+ error = ioctl(fd, DIOCGMEDIASIZE, &mediasize);
+ if (error) {
+ warnx("%s: ioctl(DIOCGMEDIASIZE) failed, probably not a disk.", argv[i]);
+ exitval = 1;
+ goto out;
+ }
+ error = ioctl(fd, DIOCGSECTORSIZE, &sectorsize);
+ if (error) {
+ warnx("%s: ioctl(DIOCGSECTORSIZE) failed, probably not a disk.", argv[i]);
+ exitval = 1;
+ goto out;
+ }
+ error = ioctl(fd, DIOCGFWSECTORS, &fwsectors);
+ if (error)
+ fwsectors = 0;
+ error = ioctl(fd, DIOCGFWHEADS, &fwheads);
+ if (error)
+ fwheads = 0;
+ error = ioctl(fd, DIOCGSTRIPESIZE, &stripesize);
+ if (error)
+ stripesize = 0;
+ error = ioctl(fd, DIOCGSTRIPEOFFSET, &stripeoffset);
+ if (error)
+ stripeoffset = 0;
+ if (!opt_v) {
+ printf("%s", argv[i]);
+ printf("\t%u", sectorsize);
+ printf("\t%jd", (intmax_t)mediasize);
+ printf("\t%jd", (intmax_t)mediasize/sectorsize);
+ printf("\t%jd", (intmax_t)stripesize);
+ printf("\t%jd", (intmax_t)stripeoffset);
+ if (fwsectors != 0 && fwheads != 0) {
+ printf("\t%jd", (intmax_t)mediasize /
+ (fwsectors * fwheads * sectorsize));
+ printf("\t%u", fwheads);
+ printf("\t%u", fwsectors);
+ }
+ } else {
+ humanize_number(buf, 5, (int64_t)mediasize, "",
+ HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
+ printf("%s\n", argv[i]);
+ printf("\t%-12u\t# sectorsize\n", sectorsize);
+ printf("\t%-12jd\t# mediasize in bytes (%s)\n",
+ (intmax_t)mediasize, buf);
+ printf("\t%-12jd\t# mediasize in sectors\n",
+ (intmax_t)mediasize/sectorsize);
+ printf("\t%-12jd\t# stripesize\n", stripesize);
+ printf("\t%-12jd\t# stripeoffset\n", stripeoffset);
+ if (fwsectors != 0 && fwheads != 0) {
+ printf("\t%-12jd\t# Cylinders according to firmware.\n", (intmax_t)mediasize /
+ (fwsectors * fwheads * sectorsize));
+ printf("\t%-12u\t# Heads according to firmware.\n", fwheads);
+ printf("\t%-12u\t# Sectors according to firmware.\n", fwsectors);
+ }
+ if (ioctl(fd, DIOCGIDENT, ident) == 0)
+ printf("\t%-12s\t# Disk ident.\n", ident);
+ if (ioctl(fd, DIOCGPHYSPATH, physpath) == 0)
+ printf("\t%-12s\t# Physical path\n", physpath);
+ }
+ printf("\n");
+ if (opt_c)
+ commandtime(fd, mediasize, sectorsize);
+ if (opt_t)
+ speeddisk(fd, mediasize, sectorsize);
+out:
+ close(fd);
+ }
+ exit (exitval);
+}
+
+
+static char sector[65536];
+static char mega[1024 * 1024];
+
+static void
+rdsect(int fd, off_t blockno, u_int sectorsize)
+{
+ int error;
+
+ lseek(fd, (off_t)blockno * sectorsize, SEEK_SET);
+ error = read(fd, sector, sectorsize);
+ if (error == -1)
+ err(1, "read");
+ if (error != (int)sectorsize)
+ errx(1, "disk too small for test.");
+}
+
+static void
+rdmega(int fd)
+{
+ int error;
+
+ error = read(fd, mega, sizeof(mega));
+ if (error == -1)
+ err(1, "read");
+ if (error != sizeof(mega))
+ errx(1, "disk too small for test.");
+}
+
+static struct timeval tv1, tv2;
+
+static void
+T0(void)
+{
+
+ fflush(stdout);
+ sync();
+ sleep(1);
+ sync();
+ sync();
+ gettimeofday(&tv1, NULL);
+}
+
+static void
+TN(int count)
+{
+ double dt;
+
+ gettimeofday(&tv2, NULL);
+ dt = (tv2.tv_usec - tv1.tv_usec) / 1e6;
+ dt += (tv2.tv_sec - tv1.tv_sec);
+ printf("%5d iter in %10.6f sec = %8.3f msec\n",
+ count, dt, dt * 1000.0 / count);
+}
+
+static void
+TR(double count)
+{
+ double dt;
+
+ gettimeofday(&tv2, NULL);
+ dt = (tv2.tv_usec - tv1.tv_usec) / 1e6;
+ dt += (tv2.tv_sec - tv1.tv_sec);
+ printf("%8.0f kbytes in %10.6f sec = %8.0f kbytes/sec\n",
+ count, dt, count / dt);
+}
+
+static void
+speeddisk(int fd, off_t mediasize, u_int sectorsize)
+{
+ int bulk, i;
+ off_t b0, b1, sectorcount, step;
+
+ sectorcount = mediasize / sectorsize;
+ step = 1ULL << (flsll(sectorcount / (4 * 200)) - 1);
+ if (step > 16384)
+ step = 16384;
+ bulk = mediasize / (1024 * 1024);
+ if (bulk > 100)
+ bulk = 100;
+
+ printf("Seek times:\n");
+ printf("\tFull stroke:\t");
+ b0 = 0;
+ b1 = sectorcount - step;
+ T0();
+ for (i = 0; i < 125; i++) {
+ rdsect(fd, b0, sectorsize);
+ b0 += step;
+ rdsect(fd, b1, sectorsize);
+ b1 -= step;
+ }
+ TN(250);
+
+ printf("\tHalf stroke:\t");
+ b0 = sectorcount / 4;
+ b1 = b0 + sectorcount / 2;
+ T0();
+ for (i = 0; i < 125; i++) {
+ rdsect(fd, b0, sectorsize);
+ b0 += step;
+ rdsect(fd, b1, sectorsize);
+ b1 += step;
+ }
+ TN(250);
+ printf("\tQuarter stroke:\t");
+ b0 = sectorcount / 4;
+ b1 = b0 + sectorcount / 4;
+ T0();
+ for (i = 0; i < 250; i++) {
+ rdsect(fd, b0, sectorsize);
+ b0 += step;
+ rdsect(fd, b1, sectorsize);
+ b1 += step;
+ }
+ TN(500);
+
+ printf("\tShort forward:\t");
+ b0 = sectorcount / 2;
+ T0();
+ for (i = 0; i < 400; i++) {
+ rdsect(fd, b0, sectorsize);
+ b0 += step;
+ }
+ TN(400);
+
+ printf("\tShort backward:\t");
+ b0 = sectorcount / 2;
+ T0();
+ for (i = 0; i < 400; i++) {
+ rdsect(fd, b0, sectorsize);
+ b0 -= step;
+ }
+ TN(400);
+
+ printf("\tSeq outer:\t");
+ b0 = 0;
+ T0();
+ for (i = 0; i < 2048; i++) {
+ rdsect(fd, b0, sectorsize);
+ b0++;
+ }
+ TN(2048);
+
+ printf("\tSeq inner:\t");
+ b0 = sectorcount - 2048;
+ T0();
+ for (i = 0; i < 2048; i++) {
+ rdsect(fd, b0, sectorsize);
+ b0++;
+ }
+ TN(2048);
+
+ printf("Transfer rates:\n");
+ printf("\toutside: ");
+ rdsect(fd, 0, sectorsize);
+ T0();
+ for (i = 0; i < bulk; i++) {
+ rdmega(fd);
+ }
+ TR(bulk * 1024);
+
+ printf("\tmiddle: ");
+ b0 = sectorcount / 2 - bulk * (1024*1024 / sectorsize) / 2 - 1;
+ rdsect(fd, b0, sectorsize);
+ T0();
+ for (i = 0; i < bulk; i++) {
+ rdmega(fd);
+ }
+ TR(bulk * 1024);
+
+ printf("\tinside: ");
+ b0 = sectorcount - bulk * (1024*1024 / sectorsize) - 1;
+ rdsect(fd, b0, sectorsize);
+ T0();
+ for (i = 0; i < bulk; i++) {
+ rdmega(fd);
+ }
+ TR(bulk * 1024);
+
+ printf("\n");
+ return;
+}
+
+static void
+commandtime(int fd, off_t mediasize, u_int sectorsize)
+{
+ double dtmega, dtsector;
+ int i;
+
+ printf("I/O command overhead:\n");
+ i = mediasize;
+ rdsect(fd, 0, sectorsize);
+ T0();
+ for (i = 0; i < 10; i++)
+ rdmega(fd);
+ gettimeofday(&tv2, NULL);
+ dtmega = (tv2.tv_usec - tv1.tv_usec) / 1e6;
+ dtmega += (tv2.tv_sec - tv1.tv_sec);
+
+ printf("\ttime to read 10MB block %10.6f sec\t= %8.3f msec/sector\n",
+ dtmega, dtmega*100/2048);
+
+ rdsect(fd, 0, sectorsize);
+ T0();
+ for (i = 0; i < 20480; i++)
+ rdsect(fd, 0, sectorsize);
+ gettimeofday(&tv2, NULL);
+ dtsector = (tv2.tv_usec - tv1.tv_usec) / 1e6;
+ dtsector += (tv2.tv_sec - tv1.tv_sec);
+
+ printf("\ttime to read 20480 sectors %10.6f sec\t= %8.3f msec/sector\n",
+ dtsector, dtsector*100/2048);
+ printf("\tcalculated command overhead\t\t\t= %8.3f msec/sector\n",
+ (dtsector - dtmega)*100/2048);
+
+ printf("\n");
+ return;
+}
diff --git a/usr.sbin/dumpcis/Makefile b/usr.sbin/dumpcis/Makefile
new file mode 100644
index 0000000..7a5a590
--- /dev/null
+++ b/usr.sbin/dumpcis/Makefile
@@ -0,0 +1,9 @@
+# pccardc Makefile
+#
+# $FreeBSD$
+
+PROG= dumpcis
+MAN= dumpcis.8
+SRCS= main.c readcis.c printcis.c
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/dumpcis/Makefile.depend b/usr.sbin/dumpcis/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/dumpcis/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/dumpcis/cardinfo.h b/usr.sbin/dumpcis/cardinfo.h
new file mode 100644
index 0000000..9489067
--- /dev/null
+++ b/usr.sbin/dumpcis/cardinfo.h
@@ -0,0 +1,205 @@
+/*
+ * Include file for PCMCIA user process interface
+ *
+ *-------------------------------------------------------------------------
+ */
+/*-
+ * Copyright (c) 1995 Andrew McRae. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$ */
+
+#ifndef _PCCARD_CARDINFO_H_
+#define _PCCARD_CARDINFO_H_
+
+#ifndef _KERNEL
+#include <sys/types.h>
+#endif
+#include <sys/ioccom.h>
+
+#define PIOCGSTATE _IOR('P', 1, struct slotstate) /* Get slot state */
+#define PIOCGMEM _IOWR('P', 2, struct mem_desc) /* Get memory map */
+#define PIOCSMEM _IOW('P', 3, struct mem_desc) /* Set memory map */
+#define PIOCGIO _IOWR('P', 4, struct io_desc) /* Get I/O map */
+#define PIOCSIO _IOW('P', 5, struct io_desc) /* Set I/O map */
+#define PIOCSDRV _IOWR('P', 6, struct dev_desc) /* Set driver */
+#define PIOCRWFLAG _IOW('P', 7, int) /* Set flags for drv use */
+#define PIOCRWMEM _IOWR('P', 8, unsigned long) /* Set mem for drv use */
+#define PIOCSPOW _IOW('P', 9, struct power) /* Set power structure */
+#define PIOCSVIR _IOW('P', 10, int) /* Virtual insert/remove */
+#define PIOCSBEEP _IOW('P', 11, int) /* Select Beep */
+#define PIOCSRESOURCE _IOWR('P', 12, struct pccard_resource) /* get resource info */
+/*
+ * Debug codes.
+ */
+#define PIOCGREG _IOWR('P',100, struct pcic_reg) /* get reg */
+#define PIOCSREG _IOW('P', 101, struct pcic_reg) /* Set reg */
+
+/*
+ * Slot states for PIOCGSTATE
+ *
+ * Here's a state diagram of all the possible states:
+ *
+ * power x 1
+ * -------------------
+ * / \
+ * / v
+ * resume +----------+ power x 0 +----------+
+ * ------->| inactive |<--------------| filled |
+ * / +----------+ +----------+
+ * / / \ ^ |
+ * nil <--------- \ insert or | | suspend or
+ * suspend \ power x 1 | | eject
+ * \ | v
+ * \ +----------+
+ * ----------->| empty |
+ * eject +----------+
+ *
+ * Note, the above diagram is for the state. On suspend, the laststate
+ * gets set to suspend to tell pccardd what happened. Also the nil state
+ * means that when the no state change has happened. Note: if you eject
+ * while suspended in the inactive state, you will return to the
+ * empty state if you do not insert a new card and to the inactive state
+ * if you do insert a new card.
+ *
+ * Some might argue that inactive should be sticky forever and
+ * eject/insert shouldn't take it out of that state. They might be
+ * right. On the other hand, some would argue that eject resets all
+ * state. They might be right. They both can't be right. The above
+ * represents a reasonable compromise between the two.
+ *
+ * Some bridges allow one to query to see if the card was changed while
+ * we were suspended. Others do not. We make no use of this functionality
+ * at this time.
+ */
+enum cardstate { noslot, empty, suspend, filled, inactive };
+
+/*
+ * Descriptor structure for memory map.
+ */
+struct mem_desc {
+ int window; /* Memory map window number (0-4) */
+ int flags; /* Flags - see below */
+ caddr_t start; /* System memory start */
+ int size; /* Size of memory area */
+ unsigned long card; /* Card memory address */
+};
+
+#define MDF_16BITS 0x01 /* Memory is 16 bits wide */
+#define MDF_ZEROWS 0x02 /* Set no wait states for memory */
+#define MDF_WS0 0x04 /* Wait state flags */
+#define MDF_WS1 0x08
+#define MDF_ATTR 0x10 /* Memory is attribute memory */
+#define MDF_WP 0x20 /* Write protect memory */
+#define MDF_ACTIVE 0x40 /* Context active (read-only) */
+
+/*
+ * Descriptor structure for I/O map
+ */
+struct io_desc {
+ int window; /* I/O map number (0-1) */
+ int flags; /* Flags - see below */
+ int start; /* I/O port start */
+ int size; /* Number of port addresses */
+};
+
+#define IODF_WS 0x01 /* Set wait states for 16 bit I/O access */
+#define IODF_16BIT 0x02 /* I/O access are 16 bit */
+#define IODF_CS16 0x04 /* Allow card selection of 16 bit access */
+#define IODF_ZEROWS 0x08 /* No wait states for 8 bit I/O */
+#define IODF_ACTIVE 0x10 /* Context active (read-only) */
+
+/*
+ * Device descriptor for allocation of driver.
+ */
+#define DEV_MISC_LEN 36
+#define DEV_MAX_CIS_LEN 40
+struct dev_desc {
+ char name[16]; /* Driver name */
+ int unit; /* Driver unit number */
+ unsigned long mem; /* Memory address of driver */
+ int memsize; /* Memory size (if used) */
+ int iobase; /* base of I/O ports */
+ int iosize; /* Length of I/O ports */
+ int irqmask; /* Interrupt number(s) to allocate */
+ int flags; /* Device flags */
+ uint8_t misc[DEV_MISC_LEN]; /* For any random info */
+ uint8_t manufstr[DEV_MAX_CIS_LEN];
+ uint8_t versstr[DEV_MAX_CIS_LEN];
+ uint8_t cis3str[DEV_MAX_CIS_LEN];
+ uint8_t cis4str[DEV_MAX_CIS_LEN];
+ uint32_t manufacturer; /* Manufacturer ID */
+ uint32_t product; /* Product ID */
+ uint32_t prodext; /* Product ID (extended) */
+};
+#define DEV_DESC_HAS_SIZE 1
+
+struct pcic_reg {
+ unsigned char reg;
+ unsigned char value;
+};
+
+/*
+ * Slot information. Used to read current status of slot.
+ */
+struct slotstate {
+ enum cardstate state; /* Current state of slot */
+ enum cardstate laststate; /* Previous state of slot */
+ int maxmem; /* Max allowed memory windows */
+ int maxio; /* Max allowed I/O windows */
+ int irqs; /* Bitmap of IRQs allowed */
+ int flags; /* Capability flags */
+};
+
+/*
+ * The power values are in volts * 10, e.g. 5V is 50, 3.3V is 33.
+ */
+struct power {
+ int vcc;
+ int vpp;
+};
+
+/*
+ * The PC-Card resource IOC_GET_RESOURCE_RANGE
+ */
+struct pccard_resource {
+ int type;
+ u_long size;
+ u_long min;
+ u_long max;
+ u_long resource_addr;
+};
+
+
+/*
+ * Other system limits
+ */
+#define MAXSLOT 16
+#define NUM_MEM_WINDOWS 10
+#define NUM_IO_WINDOWS 6
+#define CARD_DEVICE "/dev/card%d" /* String for snprintf */
+#define PCCARD_MEMSIZE (4*1024)
+
+#endif /* !_PCCARD_CARDINFO_H_ */
diff --git a/usr.sbin/dumpcis/cis.h b/usr.sbin/dumpcis/cis.h
new file mode 100644
index 0000000..6cc935b
--- /dev/null
+++ b/usr.sbin/dumpcis/cis.h
@@ -0,0 +1,279 @@
+/*
+ * PCMCIA card structures and defines.
+ * These defines relate to the user level
+ * structures and card information, not
+ * driver/process communication.
+ *-------------------------------------------------------------------------
+ */
+/*-
+ * Copyright (c) 1995 Andrew McRae. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ *
+ */
+
+/*
+ * Card Information Structure tuples definitions
+ * The structure of a tuple is basically:
+ *
+ * Tuple_code
+ * Tuple_data_length
+ * Tuple_data ...
+ *
+ * Tuples are contiguous in attribute memory, and
+ * are terminated with a 0xFF for the tuple code or
+ * the tuple length.
+ */
+#ifndef _PCCARD_CIS_H
+#define _PCCARD_CIS_H
+
+#define CIS_NULL 0 /* Empty tuple */
+#define CIS_MEM_COMMON 0x01 /* Device descriptor, common memory */
+#define CIS_LONGLINK_CB 0x02 /* Long link to next chain for CardBus */
+#define CIS_INDIRECT 0x03 /* Indirect access */
+#define CIS_CONF_MAP_CB 0x04 /* Card Configuration map for CardBus */
+#define CIS_CONFIG_CB 0x05 /* Card Configuration entry for CardBus */
+#define CIS_LONGLINK_MFC 0x06 /* Long link to next chain for Multi function card */
+#define CIS_BAR 0x07 /* Base address register for CardBus */
+#define CIS_CHECKSUM 0x10 /* Checksum */
+#define CIS_LONGLINK_A 0x11 /* Link to Attribute memory */
+#define CIS_LONGLINK_C 0x12 /* Link to Common memory */
+#define CIS_LINKTARGET 0x13 /* Linked tuple must start with this. */
+#define CIS_NOLINK 0x14 /* Assume no common memory link tuple. */
+#define CIS_INFO_V1 0x15 /* Card info data, version 1 */
+#define CIS_ALTSTR 0x16 /* Alternate language string tuple. */
+#define CIS_MEM_ATTR 0x17 /* Device descriptor, Attribute memory */
+#define CIS_JEDEC_C 0x18 /* JEDEC descr for common memory */
+#define CIS_JEDEC_A 0x19 /* JEDEC descr for Attribute memory */
+#define CIS_CONF_MAP 0x1A /* Card Configuration map */
+#define CIS_CONFIG 0x1B /* Card Configuration entry */
+#define CIS_DEVICE_OC 0x1C /* Other conditions info - common memory */
+#define CIS_DEVICE_OA 0x1D /* Other conditions info - attribute memory */
+#define CIS_DEVICEGEO 0x1E /* Geometry info for common memory */
+#define CIS_DEVICEGEO_A 0x1F /* Geometry info for attribute memory */
+#define CIS_MANUF_ID 0x20 /* Card manufacturer's ID */
+#define CIS_FUNC_ID 0x21 /* Function of card */
+#define CIS_FUNC_EXT 0x22 /* Functional extension */
+/*
+ * Data recording format tuples.
+ */
+#define CIS_SW_INTERLV 0x23 /* Software interleave */
+#define CIS_VERS_2 0x40 /* Card info data, version 2 */
+#define CIS_FORMAT 0x41 /* Memory card format */
+#define CIS_GEOMETRY 0x42 /* Disk sector layout */
+#define CIS_BYTEORDER 0x43 /* Byte order of memory data */
+#define CIS_DATE 0x44 /* Format data/time */
+#define CIS_BATTERY 0x45 /* Battery replacement date */
+#define CIS_ORG 0x46 /* Organization of data on card */
+#define CIS_END 0xFF /* Termination code */
+
+/*
+ * Internal tuple definitions.
+ *
+ * Device descriptor for memory (CIS_MEM_ATTR, CIS_MEM_COMMON)
+ *
+ * Byte 1:
+ * 0xF0 - Device type
+ * 0x08 - Write protect switch
+ * 0x07 - Speed index (7 = extended speed)
+ * Byte 2: Extended speed (bit 7 = another follows)
+ * Byte 3: (ignored if 0xFF)
+ * 0xF8 - Addressable units (0's numbered)
+ * 0x07 - Unit size
+ * The three byte sequence is repeated until byte 1 == 0xFF
+ */
+
+/*
+ * CIS_INFO_V1 - Version one card information.
+ *
+ * Byte 1: Major version number (should be 4)
+ * Byte 2: Minor version number (should be 1)
+ * Byte 3-x: Null terminated Manufacturer name
+ * Byte x-x: Null terminated product name
+ * Byte x-x: Null terminated additional info 1
+ * Byte x-x: Null terminated additional info 2
+ * Byte x: final byte must be 0xFF
+ */
+#define CIS_MAJOR_VERSION 4
+#define CIS_MINOR_VERSION 1
+
+/*
+ * CIS_CONF_MAP - Provides an address map for the card
+ * configuration register(s), and a max value
+ * identifying the last configuration tuple.
+ *
+ * Byte 1:
+ * 0x3C - Register mask size (0's numbered)
+ * 0x03 - Register address size (0's numbered)
+ * Byte 2:
+ * 0x3F - ID of last configuration.
+ * Byte 3-n: Card register address (size is determined by
+ * the value in byte 1).
+ * Byte x-x: Card register masks (size determined by the
+ * value in byte 1)
+ */
+
+/*
+ * CIS_CONFIG - Card configuration entry. Multiple tuples may
+ * exist of this type, each one describing a different
+ * memory/I-O map that can be used to address this card.
+ * The first one usually has extra config data about the
+ * card features. The final configuration tuple number
+ * is stored in the CIS_CONF_MAP tuple so that the complete
+ * list can be scanned.
+ *
+ * Byte 1:
+ * 0x3F - Configuration ID number.
+ * 0x40 - Indicates this is the default configuration
+ * 0x80 - Interface byte exists
+ * Byte 2: (exists only if bit 0x80 set in byte 1)
+ * 0x0F - Interface type value
+ * 0x10 - Battery voltage detect
+ * 0x20 - Write protect active
+ * 0x40 - RdyBsy active bit
+ * 0x80 - Wait signal required
+ * Byte 3: (features byte)
+ * 0x03 - Power sub-tuple(s) exists
+ * 0x04 - Timing sub-tuple exists
+ * 0x08 - I/O space sub-tuple exists
+ * 0x10 - IRQ sub-tuple exists
+ * 0x60 - Memory space sub-tuple(s) exists
+ * 0x80 - Miscellaneous sub-tuple exists
+ */
+#define CIS_FEAT_POWER(x) ((x) & 0x3)
+#define CIS_FEAT_TIMING 0x4
+#define CIS_FEAT_I_O 0x8
+#define CIS_FEAT_IRQ 0x10
+#define CIS_FEAT_MEMORY(x) (((x) >> 5) & 0x3)
+#define CIS_FEAT_MISC 0x80
+/*
+ * Depending on whether the "features" byte has the corresponding
+ * bit set, a number of sub-tuples follow. Some features have
+ * more than one sub-tuple, depending on the count within the
+ * features byte (e.g power feature bits allows up to 3 sub-tuples).
+ *
+ * Power structure sub-tuple:
+ * Byte 1: parameter exists - Each bit (starting from 0x01) indicates
+ * that a parameter block exists - up to 8 parameter blocks
+ * are therefore allowed).
+ * Byte 2:
+ * 0x7F - Parameter data
+ * 0x80 - More bytes follow (0 = last byte)
+ *
+ * Timing sub-tuple
+ * Byte 1:
+ * 0x03 - Wait scale
+ * 0x1C - Ready scale
+ * 0xE0 - Reserved scale
+ * Byte 2: extended wait scale if wait scale != 3
+ * Byte 3: extended ready scale if ready scale != 7
+ * Byte 4: extended reserved scale if reserved scale != 7
+ */
+#define CIS_WAIT_SCALE(x) ((x) & 0x3)
+#define CIS_READY_SCALE(x) (((x)>>2) & 0x7)
+#define CIS_RESERVED_SCALE(x) (((x)>>5) & 0x7)
+/*
+ * I/O mapping sub-tuple:
+ * Byte 1:
+ * 0x1F - I/O address lines
+ * 0x20 - 8 bit I/O
+ * 0x40 - 16 bit I/O
+ * 0x80 - I/O range??
+ * Byte 2:
+ * 0x0F - 0's numbered count of I/O block subtuples following.
+ * 0x30 - Size of I/O address value within subtuple. Values
+ * can be 1 (8 bits), 2 (16 bits) or 3 (32 bits).
+ * 0xC0 - Size of I/O port block size value within subtuple.
+ * I/O block sub-tuples, count from previous block:
+ * Byte 1-n: I/O start address
+ * Byte x-x: Size of I/O port block.
+ */
+#define CIS_IO_ADDR(x) ((x) & 0x1F)
+#define CIS_IO_8BIT 0x20
+#define CIS_IO_16BIT 0x40
+#define CIS_IO_RANGE 0x80
+#define CIS_IO_BLKS(x) ((x) & 0xF)
+#define CIS_IO_ADSZ(x) (((x)>>4) & 3)
+#define CIS_IO_BLKSZ(x) (((x)>>6) & 3)
+/*
+ * IRQ sub-tuple.
+ * Byte 1:
+ * 0x0F - Irq number or mask bits
+ * 0x10 - IRQ mask values exist
+ * 0x20 - Level triggered interrupts
+ * 0x40 - Pulse triggered requests
+ * 0x80 - Interrupt sharing.
+ * Byte 2-3: Interrupt req mask (if 0x10 of byte 1 set).
+ */
+#define CIS_IRQ_IRQN(x) ((x) & 0xF)
+#define CIS_IRQ_MASK 0x10
+#define CIS_IRQ_LEVEL 0x20
+#define CIS_IRQ_PULSE 0x40
+#define CIS_IRQ_SHARING 0x80
+/*
+ * Memory block subtuple. Depending on the features bits, the
+ * following subtuples are used:
+ * mem features == 1
+ * Byte 1-2: upper 16 bits of 24 bit memory length.
+ * mem features == 2
+ * Byte 1-2: upper 16 bits of 24 bit memory length.
+ * Byte 3-4: upper 16 bits of 24 bit memory address.
+ * mem_features == 3
+ * Byte 1:
+ * 0x07 - 0's numbered count of memory sub-tuples
+ * 0x18 - Memory length size (1's numbered)
+ * 0x60 - Memory address size (1's numbered)
+ * 0x80 - Host address value exists
+ * Memory sub-tuples follow:
+ * Byte 1-n: Memory length value (<< 8)
+ * Byte n-n: Memory card address value (<< 8)
+ * Byte n-n: Memory host address value (<< 8)
+ */
+#define CIS_FEAT_MEM_NONE 0 /* No memory config */
+#define CIS_FEAT_MEM_LEN 1 /* Just length */
+#define CIS_FEAT_MEM_ADDR 2 /* Card address & length */
+#define CIS_FEAT_MEM_WIN 3 /* Multiple windows */
+
+#define CIS_MEM_WINS(x) (((x) & 0x7)+1)
+#define CIS_MEM_LENSZ(x) (((x) >> 3) & 0x3)
+#define CIS_MEM_ADDRSZ(x) (((x) >> 5) & 0x3)
+#define CIS_MEM_HOST 0x80
+/*
+ * Misc sub-tuple.
+ * Byte 1:
+ * Byte 2:
+ * 0x0c - DMA Request Signal
+ * 00 - not support DMA
+ * 01 - use SPKR# line
+ * 10 - use IOIS16# line
+ * 11 - use INPACK# line
+ * 0x10 - DMA Width
+ * 0 - 8 bit DMA
+ * 1 - 16 bit DMA
+ */
+#define CIS_MISC_DMA_WIDTH(x) (((x) & 0x10) >> 4)
+#define CIS_MISC_DMA_REQ(x) (((x) >> 2) & 0x3)
+
+#endif /* _PCCARD_CIS_H */
diff --git a/usr.sbin/dumpcis/dumpcis.8 b/usr.sbin/dumpcis/dumpcis.8
new file mode 100644
index 0000000..86949df
--- /dev/null
+++ b/usr.sbin/dumpcis/dumpcis.8
@@ -0,0 +1,48 @@
+.\"
+.\" Copyright (c) 2006 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.
+.\" 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 18, 2006
+.Dt DUMPCIS 8
+.Os
+.Sh NAME
+.Nm dumpcis
+.Nd PC Card and Cardbus (PCMCIA) CIS display tool
+.Sh SYNOPSIS
+.Nm
+.Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility translates a raw CIS stream into human readable form.
+.Sh SEE ALSO
+.Xr cardbus 4 ,
+.Xr cbb 4 ,
+.Xr pccard 4
+.Sh AUTHORS
+The original version was written by
+.An Warner Losh Aq Mt imp@FreeBSD.org .
diff --git a/usr.sbin/dumpcis/main.c b/usr.sbin/dumpcis/main.c
new file mode 100644
index 0000000..2e66506
--- /dev/null
+++ b/usr.sbin/dumpcis/main.c
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2006 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 ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED 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 <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+#include "readcis.h"
+
+static void
+scanfile(char *name)
+{
+ int fd;
+ struct tuple_list *tl;
+
+ fd = open(name, O_RDONLY);
+ if (fd < 0)
+ return;
+ tl = readcis(fd);
+ if (tl) {
+ printf("Configuration data for file %s\n",
+ name);
+ dumpcis(tl);
+ freecis(tl);
+ }
+ close(fd);
+}
+
+int
+main(int argc, char **argv)
+{
+ for (argc--, argv++; argc; argc--, argv++)
+ scanfile(*argv);
+ return 0;
+}
diff --git a/usr.sbin/dumpcis/printcis.c b/usr.sbin/dumpcis/printcis.c
new file mode 100644
index 0000000..ff2ac90
--- /dev/null
+++ b/usr.sbin/dumpcis/printcis.c
@@ -0,0 +1,1105 @@
+/*
+ * Copyright (c) 1995 Andrew McRae. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 */
+
+/*
+ * Code cleanup, bug-fix and extension
+ * by Tatsumi Hosokawa <hosokawa@mt.cs.keio.ac.jp>
+ */
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+
+#include "cis.h"
+#include "readcis.h"
+
+static void dump_config_map(struct tuple *tp);
+static void dump_cis_config(struct tuple *tp);
+static void dump_other_cond(u_char *p, int len);
+static void dump_device_desc(u_char *p, int len, const char *type);
+static void dump_info_v1(u_char *p, int len);
+static void dump_longlink_mfc(u_char *p, int len);
+static void dump_bar(u_char *p, int len);
+static void dump_device_geo(u_char *p, int len);
+static void dump_func_id(u_char *p);
+static void dump_serial_ext(u_char *p, int len);
+static void dump_disk_ext(u_char *p, int len);
+static void dump_network_ext(u_char *p, int len);
+static void dump_info_v2(u_char *p, int len);
+static void dump_org(u_char *p, int len);
+
+void
+dumpcis(struct tuple_list *tlist)
+{
+ struct tuple *tp;
+ struct tuple_list *tl;
+ int count = 0, sz, ad, i;
+ u_char *p;
+ int func = 0;
+
+ for (tl = tlist; tl; tl = tl->next)
+ for (tp = tl->tuples; tp; tp = tp->next) {
+ printf("Tuple #%d, code = 0x%x (%s), length = %d\n",
+ ++count, tp->code, tuple_name(tp->code), tp->length);
+ p = tp->data;
+ sz = tp->length;
+ ad = 0;
+ while (sz > 0) {
+ printf(" %03x: ", ad);
+ for (i = 0; i < ((sz < 16) ? sz : 16); i++)
+ printf(" %02x", p[i]);
+ printf("\n");
+ sz -= 16;
+ p += 16;
+ ad += 16;
+ }
+ switch (tp->code) {
+ default:
+ break;
+ case CIS_MEM_COMMON: /* 0x01 */
+ dump_device_desc(tp->data, tp->length, "Common");
+ break;
+ case CIS_CONF_MAP_CB: /* 0x04 */
+ dump_config_map(tp);
+ break;
+ case CIS_CONFIG_CB: /* 0x05 */
+ dump_cis_config(tp);
+ break;
+ case CIS_LONGLINK_MFC: /* 0x06 */
+ dump_longlink_mfc(tp->data, tp->length);
+ break;
+ case CIS_BAR: /* 0x07 */
+ dump_bar(tp->data, tp->length);
+ break;
+ case CIS_CHECKSUM: /* 0x10 */
+ printf("\tChecksum from offset %d, length %d, value is 0x%x\n",
+ tpl16(tp->data),
+ tpl16(tp->data + 2),
+ tp->data[4]);
+ break;
+ case CIS_LONGLINK_A: /* 0x11 */
+ printf("\tLong link to attribute memory, address 0x%x\n",
+ tpl32(tp->data));
+ break;
+ case CIS_LONGLINK_C: /* 0x12 */
+ printf("\tLong link to common memory, address 0x%x\n",
+ tpl32(tp->data));
+ break;
+ case CIS_INFO_V1: /* 0x15 */
+ dump_info_v1(tp->data, tp->length);
+ break;
+ case CIS_ALTSTR: /* 0x16 */
+ break;
+ case CIS_MEM_ATTR: /* 0x17 */
+ dump_device_desc(tp->data, tp->length, "Attribute");
+ break;
+ case CIS_JEDEC_C: /* 0x18 */
+ case CIS_JEDEC_A: /* 0x19 */
+ break;
+ case CIS_CONF_MAP: /* 0x1A */
+ dump_config_map(tp);
+ break;
+ case CIS_CONFIG: /* 0x1B */
+ dump_cis_config(tp);
+ break;
+ case CIS_DEVICE_OC: /* 0x1C */
+ case CIS_DEVICE_OA: /* 0x1D */
+ dump_other_cond(tp->data, tp->length);
+ break;
+ case CIS_DEVICEGEO: /* 0x1E */
+ case CIS_DEVICEGEO_A: /* 0x1F */
+ dump_device_geo(tp->data, tp->length);
+ break;
+ case CIS_MANUF_ID: /* 0x20 */
+ printf("\tPCMCIA ID = 0x%x, OEM ID = 0x%x\n",
+ tpl16(tp->data),
+ tpl16(tp->data + 2));
+ break;
+ case CIS_FUNC_ID: /* 0x21 */
+ func = tp->data[0];
+ dump_func_id(tp->data);
+ break;
+ case CIS_FUNC_EXT: /* 0x22 */
+ switch (func) {
+ case 2:
+ dump_serial_ext(tp->data, tp->length);
+ break;
+ case 4:
+ dump_disk_ext(tp->data, tp->length);
+ break;
+ case 6:
+ dump_network_ext(tp->data, tp->length);
+ break;
+ }
+ break;
+ case CIS_VERS_2: /* 0x40 */
+ dump_info_v2(tp->data, tp->length);
+ break;
+ case CIS_ORG: /* 0x46 */
+ dump_org(tp->data, tp->length);
+ break;
+ }
+ }
+}
+
+/*
+ * CIS_CONF_MAP : Dump configuration map tuple.
+ * CIS_CONF_MAP_CB: Dump configuration map for CardBus
+ */
+static void
+dump_config_map(struct tuple *tp)
+{
+ u_char *p = tp->data, x;
+ int rlen, mlen = 0;
+ int i;
+
+ rlen = (p[0] & 3) + 1;
+ if (tp->code == CIS_CONF_MAP)
+ mlen = ((p[0] >> 2) & 3) + 1;
+ if (tp->length < rlen + mlen + 2) {
+ printf("\tWrong length for configuration map tuple\n");
+ return;
+ }
+ printf("\tReg len = %d, config register addr = 0x%x, last config = 0x%x\n",
+ rlen, parse_num(rlen | 0x10, p + 2, &p, 0), p[1]);
+ if (mlen) {
+ printf("\tRegisters: ");
+ for (i = 0; i < mlen; i++, p++) {
+ for (x = 0x1; x; x <<= 1)
+ printf("%c", x & *p ? 'X' : '-');
+ putchar(' ');
+ }
+ }
+ i = tp->length - (rlen + mlen + 2);
+ if (i) {
+ if (!mlen)
+ putchar('\t');
+ printf("%d bytes in subtuples", i);
+ }
+ if (mlen || i)
+ putchar('\n');
+}
+
+/*
+ * Dump power descriptor.
+ * call from dump_cis_config()
+ */
+static int
+print_pwr_desc(u_char *p)
+{
+ int len = 1, i;
+ u_char mask;
+ const char **expp;
+ static const char *pname[] =
+ {"Nominal operating supply voltage",
+ "Minimum operating supply voltage",
+ "Maximum operating supply voltage",
+ "Continuous supply current",
+ "Max current average over 1 second",
+ "Max current average over 10 ms",
+ "Power down supply current",
+ "Reserved"
+ };
+ static const char *vexp[] =
+ {"10uV", "100uV", "1mV", "10mV", "100mV", "1V", "10V", "100V"};
+ static const char *cexp[] =
+ {"10nA", "1uA", "10uA", "100uA", "1mA", "10mA", "100mA", "1A"};
+ static const char *mant[] =
+ {"1", "1.2", "1.3", "1.5", "2", "2.5", "3", "3.5", "4", "4.5",
+ "5", "5.5", "6", "7", "8", "9"};
+
+ mask = *p++;
+ expp = vexp;
+ for (i = 0; i < 8; i++)
+ if (mask & (1 << i)) {
+ len++;
+ if (i >= 3)
+ expp = cexp;
+ printf("\t\t%s: ", pname[i]);
+ printf("%s x %s",
+ mant[(*p >> 3) & 0xF],
+ expp[*p & 7]);
+ while (*p & 0x80) {
+ len++;
+ p++;
+ printf(", ext = 0x%x", *p);
+ }
+ printf("\n");
+ p++;
+ }
+ return (len);
+}
+
+/*
+ * print_ext_speed - Print extended speed.
+ * call from dump_cis_config(), dump_device_desc()
+ */
+static void
+print_ext_speed(u_char x, int scale)
+{
+ static const char *mant[] =
+ {"Reserved", "1.0", "1.2", "1.3", "1.5", "2.0", "2.5", "3.0",
+ "3.5", "4.0", "4.5", "5.0", "5.5", "6.0", "7.0", "8.0"};
+ static const char *exp[] =
+ {"1 ns", "10 ns", "100 ns", "1 us", "10 us", "100 us",
+ "1 ms", "10 ms"};
+ static const char *scale_name[] =
+ {"None", "10", "100", "1,000", "10,000", "100,000",
+ "1,000,000", "10,000,000"};
+
+ printf("Speed = %s x %s", mant[(x >> 3) & 0xF], exp[x & 7]);
+ if (scale)
+ printf(", scaled by %s", scale_name[scale & 7]);
+}
+
+/*
+ * Print variable length value.
+ * call from print_io_map(), print_mem_map()
+ */
+static int
+print_num(int sz, const char *fmt, u_char *p, int ofs)
+{
+ switch (sz) {
+ case 0:
+ case 0x10:
+ return 0;
+ case 1:
+ case 0x11:
+ printf(fmt, *p + ofs);
+ return 1;
+ case 2:
+ case 0x12:
+ printf(fmt, tpl16(p) + ofs);
+ return 2;
+ case 0x13:
+ printf(fmt, tpl24(p) + ofs);
+ return 3;
+ case 3:
+ case 0x14:
+ printf(fmt, tpl32(p) + ofs);
+ return 4;
+ }
+ errx(1, "print_num(0x%x): Illegal arguments", sz);
+/*NOTREACHED*/
+}
+
+/*
+ * Print I/O mapping sub-tuple.
+ * call from dump_cis_config()
+ */
+static u_char *
+print_io_map(u_char *p, u_char *q)
+{
+ int i, j;
+ u_char c;
+
+ if (q <= p)
+ goto err;
+ if (CIS_IO_ADDR(*p)) /* I/O address line */
+ printf("\tCard decodes %d address lines",
+ CIS_IO_ADDR(*p));
+ else
+ printf("\tCard provides address decode");
+
+ /* 8/16 bit I/O */
+ switch (*p & (CIS_IO_8BIT | CIS_IO_16BIT)) {
+ case CIS_IO_8BIT:
+ printf(", 8 Bit I/O only");
+ break;
+ case CIS_IO_16BIT:
+ printf(", limited 8/16 Bit I/O");
+ break;
+ case (CIS_IO_8BIT | CIS_IO_16BIT):
+ printf(", full 8/16 Bit I/O");
+ break;
+ }
+ putchar('\n');
+
+ /* I/O block sub-tuple exist */
+ if (*p++ & CIS_IO_RANGE) {
+ if (q <= p)
+ goto err;
+ c = *p++;
+ /* calculate byte length */
+ j = CIS_IO_ADSZ(c) + CIS_IO_BLKSZ(c);
+ if (CIS_IO_ADSZ(c) == 3)
+ j++;
+ if (CIS_IO_BLKSZ(c) == 3)
+ j++;
+ /* number of I/O block sub-tuples */
+ for (i = 0; i <= CIS_IO_BLKS(c); i++) {
+ if (q - p < j)
+ goto err;
+ printf("\t\tI/O address # %d: ", i + 1);
+ /* start block address */
+ p += print_num(CIS_IO_ADSZ(c),
+ "block start = 0x%x", p, 0);
+ /* block size */
+ p += print_num(CIS_IO_BLKSZ(c),
+ " block length = 0x%x", p, 1);
+ putchar('\n');
+ }
+ }
+ return p;
+
+ err: /* warning */
+ printf("\tWrong length for I/O mapping sub-tuple\n");
+ return p;
+}
+
+/*
+ * Print IRQ sub-tuple.
+ * call from dump_cis_config()
+ */
+static u_char *
+print_irq_map(u_char *p, u_char *q)
+{
+ int i, j;
+ u_char c;
+
+ if (q <= p)
+ goto err;
+ printf("\t\tIRQ modes:");
+ c = ' ';
+ if (*p & CIS_IRQ_LEVEL) { /* Level triggered interrupts */
+ printf(" Level");
+ c = ',';
+ }
+ if (*p & CIS_IRQ_PULSE) { /* Pulse triggered requests */
+ printf("%c Pulse", c);
+ c = ',';
+ }
+ if (*p & CIS_IRQ_SHARING) /* Interrupt sharing */
+ printf("%c Shared", c);
+ putchar('\n');
+
+ /* IRQ mask values exist */
+ if (*p & CIS_IRQ_MASK) {
+ if (q - p < 3)
+ goto err;
+ i = tpl16(p + 1); /* IRQ mask */
+ printf("\t\tIRQs: ");
+ if (*p & 1)
+ printf(" NMI");
+ if (*p & 0x2)
+ printf(" IOCK");
+ if (*p & 0x4)
+ printf(" BERR");
+ if (*p & 0x8)
+ printf(" VEND");
+ for (j = 0; j < 16; j++)
+ if (i & (1 << j))
+ printf(" %d", j);
+ putchar('\n');
+ p += 3;
+ } else {
+ printf("\t\tIRQ level = %d\n", CIS_IRQ_IRQN(*p));
+ p++;
+ }
+ return p;
+
+ err: /* warning */
+ printf("\tWrong length for IRQ sub-tuple\n");
+ return p;
+}
+
+/*
+ * Print memory map sub-tuple.
+ * call from dump_cis_config()
+ */
+static u_char *
+print_mem_map(u_char feat, u_char *p, u_char *q)
+{
+ int i, j;
+ u_char c;
+
+ switch (CIS_FEAT_MEMORY(feat)) {
+
+ case CIS_FEAT_MEM_NONE: /* No memory block */
+ break;
+ case CIS_FEAT_MEM_LEN: /* Specify memory length */
+ if (q - p < 2)
+ goto err;
+ printf("\tMemory space length = 0x%x\n", tpl16(p));
+ p += 2;
+ break;
+ case CIS_FEAT_MEM_ADDR: /* Memory address and length */
+ if (q - p < 4)
+ goto err;
+ printf("\tMemory space address = 0x%x, length = 0x%x\n",
+ tpl16(p + 2), tpl16(p));
+ p += 4;
+ break;
+ case CIS_FEAT_MEM_WIN: /* Memory descriptors. */
+ if (q <= p)
+ goto err;
+ c = *p++;
+ /* calculate byte length */
+ j = CIS_MEM_LENSZ(c) + CIS_MEM_ADDRSZ(c);
+ if (c & CIS_MEM_HOST)
+ j += CIS_MEM_ADDRSZ(c);
+ /* number of memory block */
+ for (i = 0; i < CIS_MEM_WINS(c); i++) {
+ if (q - p < j)
+ goto err;
+ printf("\tMemory descriptor %d\n\t\t", i + 1);
+ /* memory length */
+ p += print_num(CIS_MEM_LENSZ(c) | 0x10,
+ " blk length = 0x%x00", p, 0);
+ /* card address */
+ p += print_num(CIS_MEM_ADDRSZ(c) | 0x10,
+ " card addr = 0x%x00", p, 0);
+ if (c & CIS_MEM_HOST) /* Host address value exist */
+ p += print_num(CIS_MEM_ADDRSZ(c) | 0x10,
+ " host addr = 0x%x00", p, 0);
+ putchar('\n');
+ }
+ break;
+ }
+ return p;
+
+ err: /* warning */
+ printf("\tWrong length for memory mapping sub-tuple\n");
+ return p;
+}
+
+/*
+ * CIS_CONFIG : Dump a config entry.
+ * CIS_CONFIG_CB: Dump a configuration entry for CardBus
+ */
+static void
+dump_cis_config(struct tuple *tp)
+{
+ u_char *p, *q, feat;
+ int i, j;
+ char c;
+
+ p = tp->data;
+ q = p + tp->length;
+ printf("\tConfig index = 0x%x%s\n", *p & 0x3F,
+ *p & 0x40 ? "(default)" : "");
+
+ /* Interface byte exists */
+ if (tp->code == CIS_CONFIG && (*p & 0x80)) {
+ p++;
+ printf("\tInterface byte = 0x%x ", *p);
+ switch (*p & 0xF) { /* Interface type */
+ default:
+ printf("(reserved)");
+ break;
+ case 0:
+ printf("(memory)");
+ break;
+ case 1:
+ printf("(I/O)");
+ break;
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ printf("(custom)");
+ break;
+ }
+ c = ' ';
+ if (*p & 0x10) { /* Battery voltage detect */
+ printf(" BVD1/2 active");
+ c = ',';
+ }
+ if (*p & 0x20) { /* Write protect active */
+ printf("%c card WP active", c); /* Write protect */
+ c = ',';
+ }
+ if (*p & 0x40) { /* RdyBsy active bit */
+ printf("%c +RDY/-BSY active", c);
+ c = ',';
+ }
+ if (*p & 0x80) /* Wait signal required */
+ printf("%c wait signal supported", c);
+ printf("\n");
+ }
+
+ /* features byte */
+ p++;
+ feat = *p++;
+
+ /* Power structure sub-tuple */
+ switch (CIS_FEAT_POWER(feat)) { /* Power sub-tuple(s) exists */
+ case 0:
+ break;
+ case 1:
+ printf("\tVcc pwr:\n");
+ p += print_pwr_desc(p);
+ break;
+ case 2:
+ printf("\tVcc pwr:\n");
+ p += print_pwr_desc(p);
+ printf("\tVpp pwr:\n");
+ p += print_pwr_desc(p);
+ break;
+ case 3:
+ printf("\tVcc pwr:\n");
+ p += print_pwr_desc(p);
+ printf("\tVpp1 pwr:\n");
+ p += print_pwr_desc(p);
+ printf("\tVpp2 pwr:\n");
+ p += print_pwr_desc(p);
+ break;
+ }
+
+ /* Timing sub-tuple */
+ if (tp->code == CIS_CONFIG &&
+ (feat & CIS_FEAT_TIMING)) { /* Timing sub-tuple exists */
+ i = *p++;
+ j = CIS_WAIT_SCALE(i);
+ if (j != 3) {
+ printf("\tWait scale ");
+ print_ext_speed(*p++, j);
+ printf("\n");
+ }
+ j = CIS_READY_SCALE(i);
+ if (j != 7) {
+ printf("\tRDY/BSY scale ");
+ print_ext_speed(*p++, j);
+ printf("\n");
+ }
+ j = CIS_RESERVED_SCALE(i);
+ if (j != 7) {
+ printf("\tExternal scale ");
+ print_ext_speed(*p++, j);
+ printf("\n");
+ }
+ }
+
+ /* I/O mapping sub-tuple */
+ if (feat & CIS_FEAT_I_O) { /* I/O space sub-tuple exists */
+ if (tp->code == CIS_CONFIG)
+ p = print_io_map(p, q);
+ else { /* CIS_CONFIG_CB */
+ printf("\tI/O base:");
+ for (i = 0; i < 8; i++)
+ if (*p & (1 << i))
+ printf(" %d", i);
+ putchar('\n');
+ p++;
+ }
+ }
+
+ /* IRQ descriptor sub-tuple */
+ if (feat & CIS_FEAT_IRQ) /* IRQ sub-tuple exists */
+ p = print_irq_map(p, q);
+
+ /* Memory map sub-tuple */
+ if (CIS_FEAT_MEMORY(feat)) { /* Memory space sub-tuple(s) exists */
+ if (tp->code == CIS_CONFIG)
+ p = print_mem_map(feat, p, q);
+ else { /* CIS_CONFIG_CB */
+ printf("\tMemory base:");
+ for (i = 0; i < 8; i++)
+ if (*p & (1 << i))
+ printf(" %d", i);
+ putchar('\n');
+ p++;
+ }
+ }
+
+ /* Misc sub-tuple */
+ if (feat & CIS_FEAT_MISC) { /* Miscellaneous sub-tuple exists */
+ if (tp->code == CIS_CONFIG) {
+ printf("\tMax twin cards = %d\n", *p & 7);
+ printf("\tMisc attr:%s%s%s",
+ (*p & 8) ? " (Audio-BVD2)" : "",
+ (*p & 0x10) ? " (Read-only)" : "",
+ (*p & 0x20) ? " (Power down supported)" : "");
+ if (*p++ & 0x80) {
+ printf(" (Ext byte = 0x%x)", *p);
+ p++;
+ }
+ putchar('\n');
+ }
+ else { /* CIS_CONFIG_CB */
+ printf("\tMisc attr:");
+ printf("%s%s%s%s%s%s%s",
+ (*p & 1) ? " (Master)" : "",
+ (*p & 2) ? " (Invalidate)" : "",
+ (*p & 4) ? " (VGA palette)" : "",
+ (*p & 8) ? " (Parity)" : "",
+ (*p & 0x10) ? " (Wait)" : "",
+ (*p & 0x20) ? " (Serr)" : "",
+ (*p & 0x40) ? " (Fast back)" : "");
+ if (*p++ & 0x80) {
+ printf("%s%s",
+ (*p & 1) ? " (Binary audio)" : "",
+ (*p & 2) ? " (pwm audio)" : "");
+ p++;
+ }
+ putchar('\n');
+ }
+ }
+}
+
+/*
+ * CIS_DEVICE_OC, CIS_DEVICE_OA:
+ * Dump other conditions for common/attribute memory
+ */
+static void
+dump_other_cond(u_char *p, int len)
+{
+ if (p[0] && len > 0) {
+ printf("\t");
+ if (p[0] & 1)
+ printf("(MWAIT)");
+ if (p[0] & 2)
+ printf(" (3V card)");
+ if (p[0] & 0x80)
+ printf(" (Extension bytes follow)");
+ printf("\n");
+ }
+}
+
+/*
+ * CIS_MEM_COMMON, CIS_MEM_ATTR:
+ * Common / Attribute memory descripter
+ */
+static void
+dump_device_desc(u_char *p, int len, const char *type)
+{
+ static const char *un_name[] =
+ {"512b", "2Kb", "8Kb", "32Kb", "128Kb", "512Kb", "2Mb", "reserved"};
+ static const char *speed[] =
+ {"No speed", "250nS", "200nS", "150nS",
+ "100nS", "Reserved", "Reserved"};
+ static const char *dev[] =
+ {"No device", "Mask ROM", "OTPROM", "UV EPROM",
+ "EEPROM", "FLASH EEPROM", "SRAM", "DRAM",
+ "Reserved", "Reserved", "Reserved", "Reserved",
+ "Reserved", "Function specific", "Extended",
+ "Reserved"};
+ int count = 0;
+
+ while (*p != 0xFF && len > 0) {
+ u_char x;
+
+ x = *p++;
+ len -= 2;
+ if (count++ == 0)
+ printf("\t%s memory device information:\n", type);
+ printf("\t\tDevice number %d, type %s, WPS = %s\n",
+ count, dev[x >> 4], (x & 0x8) ? "ON" : "OFF");
+ if ((x & 7) == 7) {
+ len--;
+ if (*p) {
+ printf("\t\t");
+ print_ext_speed(*p, 0);
+ while (*p & 0x80) {
+ p++;
+ len--;
+ }
+ }
+ p++;
+ } else
+ printf("\t\tSpeed = %s", speed[x & 7]);
+ printf(", Memory block size = %s, %d units\n",
+ un_name[*p & 7], (*p >> 3) + 1);
+ p++;
+ }
+}
+
+/*
+ * CIS_INFO_V1: Print version-1 info
+ */
+static void
+dump_info_v1(u_char *p, int len)
+{
+ if (len < 2) {
+ printf("\tWrong length for version-1 info tuple\n");
+ return;
+ }
+ printf("\tVersion = %d.%d", p[0], p[1]);
+ p += 2;
+ len -= 2;
+ if (len > 1 && *p != 0xff) {
+ printf(", Manuf = [%s]", p);
+ while (*p++ && --len > 0);
+ }
+ if (len > 1 && *p != 0xff) {
+ printf(", card vers = [%s]", p);
+ while (*p++ && --len > 0);
+ } else {
+ printf("\n\tWrong length for version-1 info tuple\n");
+ return;
+ }
+ putchar('\n');
+ if (len > 1 && *p != 0xff) {
+ printf("\tAddit. info = [%.*s]", len, p);
+ while (*p++ && --len > 0);
+ if (len > 1 && *p != 0xff)
+ printf(",[%.*s]", len, p);
+ putchar('\n');
+ }
+}
+
+/*
+ * CIS_FUNC_ID: Functional ID
+ */
+static void
+dump_func_id(u_char *p)
+{
+ static const char *id[] = {
+ "Multifunction card",
+ "Memory card",
+ "Serial port/modem",
+ "Parallel port",
+ "Fixed disk card",
+ "Video adapter",
+ "Network/LAN adapter",
+ "AIMS",
+ "SCSI card",
+ "Security"
+ };
+
+ printf("\t%s%s%s\n",
+ (*p <= 9) ? id[*p] : "Unknown function",
+ (p[1] & 1) ? " - POST initialize" : "",
+ (p[1] & 2) ? " - Card has ROM" : "");
+}
+
+/*
+ * CIS_FUNC_EXT: Dump functional extension tuple.
+ * (Serial port/modem)
+ */
+static void
+dump_serial_ext(u_char *p, int len)
+{
+ static const char *type[] = {
+ "", "Modem", "Data", "Fax", "Voice", "Data modem",
+ "Fax/modem", "Voice", " (Data)", " (Fax)", " (Voice)"
+ };
+
+ if (len < 1)
+ return;
+ switch (p[0]) {
+ case 0: /* Serial */
+ case 8: /* Data */
+ case 9: /* Fax */
+ case 10: /* Voice */
+ printf("\tSerial interface extension:%s\n", type[*p]);
+ if (len < 4)
+ goto err;
+ switch (p[1] & 0x1F) {
+ default:
+ printf("\t\tUnknown device");
+ break;
+ case 0:
+ printf("\t\t8250 UART");
+ break;
+ case 1:
+ printf("\t\t16450 UART");
+ break;
+ case 2:
+ printf("\t\t16550 UART");
+ break;
+ }
+ printf(", Parity - %s%s%s%s\n",
+ (p[2] & 1) ? "Space," : "",
+ (p[2] & 2) ? "Mark," : "",
+ (p[2] & 4) ? "Odd," : "",
+ (p[2] & 8) ? "Even" : "");
+ printf("\t\tData bit - %s%s%s%s Stop bit - %s%s%s\n",
+ (p[3] & 1) ? "5bit," : "",
+ (p[3] & 2) ? "6bit," : "",
+ (p[3] & 4) ? "7bit," : "",
+ (p[3] & 8) ? "8bit," : "",
+ (p[3] & 0x10) ? "1bit," : "",
+ (p[3] & 0x20) ? "1.5bit," : "",
+ (p[3] & 0x40) ? "2bit" : "");
+ break;
+ case 1: /* Serial */
+ case 5: /* Data */
+ case 6: /* Fax */
+ case 7: /* Voice */
+ printf("\t%s interface capabilities:\n", type[*p]);
+ if (len < 9)
+ goto err;
+ break;
+ case 2: /* Data */
+ printf("\tData modem services available:\n");
+ break;
+ case 0x13: /* Fax1 */
+ case 0x23: /* Fax2 */
+ case 0x33: /* Fax3 */
+ printf("\tFax%d/modem services available:\n", *p >> 4);
+ break;
+ case 0x84: /* Voice */
+ printf("\tVoice services available:\n");
+ break;
+ err: /* warning */
+ printf("\tWrong length for serial extension tuple\n");
+ return;
+ }
+}
+
+/*
+ * CIS_FUNC_EXT: Dump functional extension tuple.
+ * (Fixed disk card)
+ */
+static void
+dump_disk_ext(u_char *p, int len)
+{
+ if (len < 1)
+ return;
+ switch (p[0]) {
+ case 1: /* IDE interface */
+ if (len < 2)
+ goto err;
+ printf("\tDisk interface: %s\n",
+ (p[1] & 1) ? "IDE" : "Undefined");
+ break;
+ case 2: /* Master */
+ case 3: /* Slave */
+ if (len < 3)
+ goto err;
+ printf("\tDisk features: %s, %s%s\n",
+ (p[1] & 0x04) ? "Silicon" : "Rotating",
+ (p[1] & 0x08) ? "Unique, " : "",
+ (p[1] & 0x10) ? "Dual" : "Single");
+ if (p[2] & 0x7f)
+ printf("\t\t%s%s%s%s%s%s%s\n",
+ (p[2] & 0x01) ? "Sleep, " : "",
+ (p[2] & 0x02) ? "Standby, " : "",
+ (p[2] & 0x04) ? "Idle, " : "",
+ (p[2] & 0x08) ? "Low power, " : "",
+ (p[2] & 0x10) ? "Reg inhibit, " : "",
+ (p[2] & 0x20) ? "Index, " : "",
+ (p[2] & 0x40) ? "Iois16" : "");
+ break;
+ err: /* warning */
+ printf("\tWrong length for fixed disk extension tuple\n");
+ return;
+ }
+}
+
+static void
+print_speed(u_int i)
+{
+ if (i < 1000)
+ printf("%u bits/sec", i);
+ else if (i < 1000000)
+ printf("%u kb/sec", i / 1000);
+ else
+ printf("%u Mb/sec", i / 1000000);
+}
+
+/*
+ * CIS_FUNC_EXT: Dump functional extension tuple.
+ * (Network/LAN adapter)
+ */
+static void
+dump_network_ext(u_char *p, int len)
+{
+ static const char *tech[] = {
+ "Undefined", "ARCnet", "Ethernet", "Token ring",
+ "Localtalk", "FDDI/CDDI", "ATM", "Wireless"
+ };
+ static const char *media[] = {
+ "Undefined", "UTP", "STP", "Thin coax",
+ "THICK coax", "Fiber", "900 MHz", "2.4 GHz",
+ "5.4 GHz", "Diffuse Infrared", "Point to point Infrared"
+ };
+ u_int i = 0;
+
+ if (len < 1)
+ return;
+ switch (p[0]) {
+ case 1: /* Network technology */
+ if (len < 2)
+ goto err;
+ printf("\tNetwork technology: %s\n", tech[p[1] & 7]);
+ break;
+ case 2: /* Network speed */
+ if (len < 5)
+ goto err;
+ printf("\tNetwork speed: ");
+ print_speed(tpl32(p + 1));
+ putchar('\n');
+ break;
+ case 3: /* Network media */
+ if (len < 2)
+ goto err;
+ if (p[1] <= 10)
+ i = p[1];
+ printf("\tNetwork media: %s\n", media[i]);
+ break;
+ case 4: /* Node ID */
+ if (len <= 2 || len < p[1] + 2)
+ goto err;
+ printf("\tNetwork node ID:");
+ for (i = 0; i < p[1]; i++)
+ printf(" %02x", p[i + 2]);
+ putchar('\n');
+ break;
+ case 5: /* Connector type */
+ if (len < 2)
+ goto err;
+ printf("\tNetwork connector: %s connector standard\n",
+ (p[1] == 0) ? "open" : "closed");
+ break;
+ err: /* warning */
+ printf("\tWrong length for network extension tuple\n");
+ return;
+ }
+}
+
+/*
+ * CIS_LONGLINK_MFC: Long link to next chain for Multi function card
+ */
+static void
+dump_longlink_mfc(u_char *p, int len)
+{
+ u_int i, n = *p++;
+
+ --len;
+ for (i = 0; i < n; i++) {
+ if (len < 5) {
+ printf("\tWrong length for long link MFC tuple\n");
+ return;
+ }
+ printf("\tFunction %d: %s memory, address 0x%x\n",
+ i, (*p ? "common" : "attribute"), tpl32(p + 1));
+ p += 5;
+ len -= 5;
+ }
+}
+
+/*
+ * CIS_DEVICEGEO, CIS_DEVICEGEO_A:
+ * Geometry info for common/attribute memory
+ */
+static void
+dump_device_geo(u_char *p, int len)
+{
+ while (len >= 6) {
+ printf("\twidth = %d, erase = 0x%x, read = 0x%x, write = 0x%x\n"
+ "\t\tpartition = 0x%x, interleave = 0x%x\n",
+ p[0], 1 << (p[1] - 1),
+ 1 << (p[2] - 1), 1 << (p[3] - 1),
+ 1 << (p[4] - 1), 1 << (p[5] - 1));
+ len -= 6;
+ }
+}
+
+/*
+ * CIS_INFO_V2: Print version-2 info
+ */
+static void
+dump_info_v2(u_char *p, int len)
+{
+ if (len < 9) {
+ printf("\tWrong length for version-2 info tuple\n");
+ return;
+ }
+ printf("\tVersion = 0x%x, compliance = 0x%x, dindex = 0x%x\n",
+ p[0], p[1], tpl16(p + 2));
+ printf("\tVspec8 = 0x%x, vspec9 = 0x%x, nhdr = %d\n",
+ p[6], p[7], p[8]);
+ p += 9;
+ len -= 9;
+ if (len <= 1 || *p == 0xff)
+ return;
+ printf("\tVendor = [%.*s]", len, p);
+ while (*p++ && --len > 0);
+ if (len > 1 && *p != 0xff)
+ printf(", info = [%.*s]", len, p);
+ putchar('\n');
+}
+
+/*
+ * CIS_ORG: Organization
+ */
+static void
+dump_org(u_char *p, int len)
+{
+ if (len < 1) {
+ printf("\tWrong length for organization tuple\n");
+ return;
+ }
+ switch (*p) {
+ case 0:
+ printf("\tFilesystem");
+ break;
+ case 1:
+ printf("\tApp specific");
+ break;
+ case 2:
+ printf("\tCode");
+ break;
+ default:
+ if (*p < 0x80)
+ printf("\tReserved");
+ else
+ printf("\tVendor specific");
+ break;
+ }
+ printf(" [%.*s]\n", len - 1, p + 1);
+}
+
+static void
+print_size(u_int i)
+{
+ if (i < 1024)
+ printf("%ubits", i);
+ else if (i < 1024*1024)
+ printf("%ukb", i / 1024);
+ else
+ printf("%uMb", i / (1024*1024));
+}
+
+/*
+ * CIS_BAR: Base address register for CardBus
+ */
+static void
+dump_bar(u_char *p, int len)
+{
+ if (len < 6) {
+ printf("\tWrong length for BAR tuple\n");
+ return;
+ }
+ printf("\tBAR %d: size = ", *p & 7);
+ print_size(tpl32(p + 2));
+ printf(", %s%s%s%s\n",
+ (*p & 0x10) ? "I/O" : "Memory",
+ (*p & 0x20) ? ", Prefetch" : "",
+ (*p & 0x40) ? ", Cacheable" : "",
+ (*p & 0x80) ? ", <1Mb" : "");
+}
diff --git a/usr.sbin/dumpcis/readcis.c b/usr.sbin/dumpcis/readcis.c
new file mode 100644
index 0000000..25bd396
--- /dev/null
+++ b/usr.sbin/dumpcis/readcis.c
@@ -0,0 +1,377 @@
+/*
+ * Copyright (c) 1995 Andrew McRae. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 */
+
+/*
+ * Code cleanup, bug-fix and extension
+ * by Tatsumi Hosokawa <hosokawa@mt.cs.keio.ac.jp>
+ */
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "cardinfo.h"
+#include "cis.h"
+#include "readcis.h"
+
+static int ck_linktarget(int, off_t, int);
+static struct tuple_list *read_one_tuplelist(int, int, off_t);
+static struct tuple_list *read_tuples(int);
+static struct tuple *find_tuple_in_list(struct tuple_list *, unsigned char);
+static struct tuple_info *get_tuple_info(unsigned char);
+
+static struct tuple_info tuple_info[] = {
+ {"Null tuple", CIS_NULL, 0},
+ {"Common memory descriptor", CIS_MEM_COMMON, 255},
+ {"Long link to next chain for CardBus", CIS_LONGLINK_CB, 255},
+ {"Indirect access", CIS_INDIRECT, 255},
+ {"Configuration map for CardBus", CIS_CONF_MAP_CB, 255},
+ {"Configuration entry for CardBus", CIS_CONFIG_CB, 255},
+ {"Long link to next chain for MFC", CIS_LONGLINK_MFC, 255},
+ {"Base address register for CardBus", CIS_BAR, 6},
+ {"Checksum", CIS_CHECKSUM, 5},
+ {"Long link to attribute memory", CIS_LONGLINK_A, 4},
+ {"Long link to common memory", CIS_LONGLINK_C, 4},
+ {"Link target", CIS_LINKTARGET, 3},
+ {"No link", CIS_NOLINK, 0},
+ {"Version 1 info", CIS_INFO_V1, 255},
+ {"Alternate language string", CIS_ALTSTR, 255},
+ {"Attribute memory descriptor", CIS_MEM_ATTR, 255},
+ {"JEDEC descr for common memory", CIS_JEDEC_C, 255},
+ {"JEDEC descr for attribute memory", CIS_JEDEC_A, 255},
+ {"Configuration map", CIS_CONF_MAP, 255},
+ {"Configuration entry", CIS_CONFIG, 255},
+ {"Other conditions for common memory", CIS_DEVICE_OC, 255},
+ {"Other conditions for attribute memory", CIS_DEVICE_OA, 255},
+ {"Geometry info for common memory", CIS_DEVICEGEO, 255},
+ {"Geometry info for attribute memory", CIS_DEVICEGEO_A, 255},
+ {"Manufacturer ID", CIS_MANUF_ID, 4},
+ {"Functional ID", CIS_FUNC_ID, 2},
+ {"Functional EXT", CIS_FUNC_EXT, 255},
+ {"Software interleave", CIS_SW_INTERLV, 2},
+ {"Version 2 Info", CIS_VERS_2, 255},
+ {"Data format", CIS_FORMAT, 255},
+ {"Geometry", CIS_GEOMETRY, 4},
+ {"Byte order", CIS_BYTEORDER, 2},
+ {"Card init date", CIS_DATE, 4},
+ {"Battery replacement", CIS_BATTERY, 4},
+ {"Organization", CIS_ORG, 255},
+ {"Terminator", CIS_END, 0},
+ {0, 0, 0}
+};
+
+static void *
+xmalloc(int sz)
+{
+ void *p;
+
+ sz = (sz + 7) & ~7;
+ p = malloc(sz);
+ if (p)
+ bzero(p, sz);
+ else
+ errx(1, "malloc");
+ return (p);
+}
+
+/*
+ * After reading the tuples, decode the relevant ones.
+ */
+struct tuple_list *
+readcis(int fd)
+{
+
+ return (read_tuples(fd));
+}
+
+/*
+ * free_cis - delete cis entry.
+ */
+void
+freecis(struct tuple_list *tlist)
+{
+ struct tuple_list *tl;
+ struct tuple *tp;
+
+ while ((tl = tlist) != 0) {
+ tlist = tl->next;
+ while ((tp = tl->tuples) != 0) {
+ tl->tuples = tp->next;
+ free(tp->data);
+ free(tp);
+ }
+ free(tl);
+ }
+}
+
+/*
+ * Parse variable length value.
+ */
+u_int
+parse_num(int sz, u_char *p, u_char **q, int ofs)
+{
+ u_int num = 0;
+
+ switch (sz) {
+ case 0:
+ case 0x10:
+ break;
+ case 1:
+ case 0x11:
+ num = (*p++) + ofs;
+ break;
+ case 2:
+ case 0x12:
+ num = tpl16(p) + ofs;
+ p += 2;
+ break;
+ case 0x13:
+ num = tpl24(p) + ofs;
+ p += 3;
+ break;
+ case 3:
+ case 0x14:
+ num = tpl32(p) + ofs;
+ p += 4;
+ break;
+ }
+ if (q)
+ *q = p;
+ return num;
+}
+
+/*
+ * Read the tuples from the card.
+ * The processing of tuples is as follows:
+ * - Read tuples at attribute memory, offset 0.
+ * - If a CIS_END is the first tuple, look for
+ * a tuple list at common memory offset 0; this list
+ * must start with a LINKTARGET.
+ * - If a long link tuple was encountered, execute the long
+ * link.
+ * - If a no-link tuple was seen, terminate processing.
+ * - If no no-link tuple exists, and no long link tuple
+ * exists while processing the primary tuple list,
+ * then look for a LINKTARGET tuple in common memory.
+ * - If a long link tuple is found in any list, then process
+ * it. Only one link is allowed per list.
+ */
+static struct tuple_list *tlist;
+
+static struct tuple_list *
+read_tuples(int fd)
+{
+ struct tuple_list *tl = 0, *last_tl;
+ struct tuple *tp;
+ int flag;
+ off_t offs;
+
+ tlist = 0;
+ last_tl = tlist = read_one_tuplelist(fd, MDF_ATTR, (off_t) 0);
+
+ /* Now start processing the links (if any). */
+ do {
+ flag = MDF_ATTR;
+ tp = find_tuple_in_list(last_tl, CIS_LONGLINK_A);
+ if (tp == 0) {
+ flag = 0;
+ tp = find_tuple_in_list(last_tl, CIS_LONGLINK_C);
+ }
+ if (tp && tp->length == 4) {
+ offs = tpl32(tp->data);
+#ifdef DEBUG
+ printf("Checking long link at %zd (%s memory)\n",
+ offs, flag ? "Attribute" : "Common");
+#endif
+ /* If a link was found, read the tuple list from it. */
+ if (ck_linktarget(fd, offs, flag)) {
+ tl = read_one_tuplelist(fd, flag, offs);
+ last_tl->next = tl;
+ last_tl = tl;
+ }
+ } else
+ tl = 0;
+ } while (tl);
+
+ /*
+ * If the primary list had no NOLINK tuple, and no LINKTARGET,
+ * then try to read a tuple list at common memory (offset 0).
+ */
+ if (find_tuple_in_list(tlist, CIS_NOLINK) == 0 &&
+ find_tuple_in_list(tlist, CIS_LINKTARGET) == 0 &&
+ ck_linktarget(fd, (off_t) 0, 0)) {
+ offs = 0;
+#ifdef DEBUG
+ printf("Reading long link at %zd (%s memory)\n",
+ offs, flag ? "Attribute" : "Common");
+#endif
+ tlist->next = read_one_tuplelist(fd, 0, offs);
+ }
+ return (tlist);
+}
+
+/*
+ * Read one tuple list from the card.
+ */
+static struct tuple_list *
+read_one_tuplelist(int fd, int flags, off_t offs)
+{
+ struct tuple *tp, *last_tp = 0;
+ struct tuple_list *tl;
+ struct tuple_info *tinfo;
+ int total = 0;
+ unsigned char code, length;
+
+ /* Check to see if this memory has already been scanned. */
+ for (tl = tlist; tl; tl = tl->next)
+ if (tl->offs == offs && tl->flags == (flags & MDF_ATTR))
+ return (0);
+ tl = xmalloc(sizeof(*tl));
+ tl->offs = offs;
+ tl->flags = flags & MDF_ATTR;
+ ioctl(fd, PIOCRWFLAG, &flags);
+ lseek(fd, offs, SEEK_SET);
+ do {
+ if (read(fd, &code, 1) != 1) {
+ warn("CIS code read");
+ break;
+ }
+ total++;
+ if (code == CIS_NULL)
+ continue;
+ tp = xmalloc(sizeof(*tp));
+ tp->code = code;
+ if (code == CIS_END)
+ length = 0;
+ else {
+ if (read(fd, &length, 1) != 1) {
+ warn("CIS len read");
+ break;
+ }
+ total++;
+ }
+ tp->length = length;
+#ifdef DEBUG
+ printf("Tuple code = 0x%x, len = %d\n", code, length);
+#endif
+ if (length == 0xFF) {
+ length = tp->length = 0;
+ code = CIS_END;
+ }
+ if (length != 0) {
+ total += length;
+ tp->data = xmalloc(length);
+ if (read(fd, tp->data, length) != length) {
+ warn("CIS read");
+ break;
+ }
+ }
+
+ /*
+ * Check the tuple, and ignore it if it isn't in the table
+ * or the length is illegal.
+ */
+ tinfo = get_tuple_info(code);
+ if (tinfo != NULL && (tinfo->length != 255 && tinfo->length > length)) {
+ printf("code %s (%d) ignored\n", tuple_name(code), code);
+ tp->code = CIS_NULL;
+ }
+ if (tl->tuples == NULL)
+ tl->tuples = tp;
+ else
+ last_tp->next = tp;
+ last_tp = tp;
+ } while (code != CIS_END && total < 1024);
+ return (tl);
+}
+
+/*
+ * return true if the offset points to a LINKTARGET tuple.
+ */
+static int
+ck_linktarget(int fd, off_t offs, int flag)
+{
+ char blk[5];
+
+ ioctl(fd, PIOCRWFLAG, &flag);
+ lseek(fd, offs, SEEK_SET);
+ if (read(fd, blk, 5) != 5)
+ return (0);
+ if (blk[0] == CIS_LINKTARGET &&
+ blk[1] == 0x3 &&
+ blk[2] == 'C' &&
+ blk[3] == 'I' &&
+ blk[4] == 'S')
+ return (1);
+ return (0);
+}
+
+/*
+ * find_tuple_in_list - find a tuple within a
+ * single tuple list.
+ */
+static struct tuple *
+find_tuple_in_list(struct tuple_list *tl, unsigned char code)
+{
+ struct tuple *tp;
+
+ for (tp = tl->tuples; tp; tp = tp->next)
+ if (tp->code == code)
+ break;
+ return (tp);
+}
+
+/*
+ * return table entry for code.
+ */
+static struct tuple_info *
+get_tuple_info(unsigned char code)
+{
+ struct tuple_info *tp;
+
+ for (tp = tuple_info; tp->name; tp++)
+ if (tp->code == code)
+ return (tp);
+ return (0);
+}
+
+const char *
+tuple_name(unsigned char code)
+{
+ struct tuple_info *tp;
+
+ tp = get_tuple_info(code);
+ if (tp)
+ return (tp->name);
+ return ("Unknown");
+}
diff --git a/usr.sbin/dumpcis/readcis.h b/usr.sbin/dumpcis/readcis.h
new file mode 100644
index 0000000..8fc2e2d
--- /dev/null
+++ b/usr.sbin/dumpcis/readcis.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 1995 Andrew McRae. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+struct tuple {
+ struct tuple *next;
+ unsigned char code;
+ int length;
+ unsigned char *data;
+};
+
+struct tuple_list {
+ struct tuple_list *next;
+ struct tuple *tuples;
+ off_t offs;
+ int flags;
+};
+
+struct tuple_info {
+ const char *name;
+ unsigned char code;
+ unsigned char length; /* 255 means variable length */
+};
+
+#define tpl32(tp) ((*((tp) + 3) << 24) | \
+ (*((tp) + 2) << 16) | \
+ (*((tp) + 1) << 8) | *(tp))
+#define tpl24(tp) ((*((tp) + 2) << 16) | \
+ (*((tp) + 1) << 8) | *(tp))
+#define tpl16(tp) ((*((tp) + 1) << 8) | *(tp))
+
+void dumpcis(struct tuple_list *);
+void freecis(struct tuple_list *);
+struct tuple_list *readcis(int);
+
+const char *tuple_name(unsigned char);
+u_int parse_num(int, u_char *, u_char **, int);
diff --git a/usr.sbin/editmap/Makefile b/usr.sbin/editmap/Makefile
new file mode 100644
index 0000000..23e967f
--- /dev/null
+++ b/usr.sbin/editmap/Makefile
@@ -0,0 +1,29 @@
+# $FreeBSD$
+
+SENDMAIL_DIR=${.CURDIR}/../../contrib/sendmail
+.PATH: ${SENDMAIL_DIR}/editmap
+
+PROG= editmap
+SRCS= editmap.c
+MAN= editmap.8
+
+CFLAGS+= -I${SENDMAIL_DIR}/src -I${SENDMAIL_DIR}/include -I.
+CFLAGS+= -DNEWDB -DNOT_SENDMAIL
+
+WARNS?= 2
+
+LIBADD= smdb smutil sm
+
+SRCS+= sm_os.h
+CLEANFILES+=sm_os.h
+
+# User customizations to the sendmail build environment
+CFLAGS+=${SENDMAIL_CFLAGS}
+DPADD+=${SENDMAIL_DPADD}
+LDADD+=${SENDMAIL_LDADD}
+LDFLAGS+=${SENDMAIL_LDFLAGS}
+
+sm_os.h: ${SENDMAIL_DIR}/include/sm/os/sm_os_freebsd.h .NOMETA
+ ln -sf ${.ALLSRC} ${.TARGET}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/editmap/Makefile.depend b/usr.sbin/editmap/Makefile.depend
new file mode 100644
index 0000000..bc24fb7
--- /dev/null
+++ b/usr.sbin/editmap/Makefile.depend
@@ -0,0 +1,23 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libsm \
+ lib/libsmdb \
+ lib/libsmutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+editmap.o: sm_os.h
+editmap.po: sm_os.h
+.endif
diff --git a/usr.sbin/edquota/Makefile b/usr.sbin/edquota/Makefile
new file mode 100644
index 0000000..83f06d0
--- /dev/null
+++ b/usr.sbin/edquota/Makefile
@@ -0,0 +1,12 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+PROG= edquota
+MAN= edquota.8
+
+CSTD= gnu99
+WARNS?= 4
+
+LIBADD= util
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/edquota/Makefile.depend b/usr.sbin/edquota/Makefile.depend
new file mode 100644
index 0000000..58f9a33
--- /dev/null
+++ b/usr.sbin/edquota/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/edquota/edquota.8 b/usr.sbin/edquota/edquota.8
new file mode 100644
index 0000000..f616fa0
--- /dev/null
+++ b/usr.sbin/edquota/edquota.8
@@ -0,0 +1,264 @@
+.\" Copyright (c) 1983, 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.
+.\"
+.\" @(#)edquota.8 8.1 (Berkeley) 6/6/93
+.\" $FreeBSD$
+.\"
+.Dd June 6, 1993
+.Dt EDQUOTA 8
+.Os
+.Sh NAME
+.Nm edquota
+.Nd edit user quotas
+.Sh SYNOPSIS
+.Nm
+.Op Fl uh
+.Op Fl f Ar fspath
+.Op Fl p Ar proto-username
+.Ar username ...
+.Nm
+.Op Fl u
+.Fl e
+.Sm off
+.Ar fspath Op : Ar bslim Op : Ar bhlim Op : Ar islim Op : Ar ihlim
+.Sm on
+.Op Fl e Ar ...
+.Ar username ...
+.Nm
+.Fl g
+.Op Fl h
+.Op Fl f Ar fspath
+.Op Fl p Ar proto-groupname
+.Ar groupname ...
+.Nm
+.Fl g
+.Fl e
+.Sm off
+.Ar fspath Op : Ar bslim Op : Ar bhlim Op : Ar islim Op : Ar ihlim
+.Sm on
+.Op Fl e Ar ...
+.Ar groupname ...
+.Nm
+.Fl t
+.Op Fl u
+.Op Fl f Ar fspath
+.Nm
+.Fl t
+.Fl g
+.Op Fl f Ar fspath
+.Sh DESCRIPTION
+The
+.Nm
+utility is a quota editor.
+By default, or if the
+.Fl u
+flag is specified,
+one or more users may be specified on the command line.
+For each user a temporary file is created
+with an
+.Tn ASCII
+representation of the current
+disk quotas for that user.
+The list of file systems with user quotas is determined from
+.Pa /etc/fstab .
+An editor is invoked on the
+.Tn ASCII
+file.
+The editor invoked is
+.Xr vi 1
+unless the environment variable
+.Ev EDITOR
+specifies otherwise.
+.Pp
+The quotas may then be modified, new quotas added, etc.
+Block quotas can be specified in bytes (B), kilobytes (K),
+megabytes (M), terabytes (T), petabytes (P), or exabytes (E).
+If no units are specified, kilobytes are assumed.
+Inode quotas can be specified in kiloinodes (K),
+megainodes (M), terainodes (T), petainodes (P), or exainodes (E).
+If no units are specified, the number of inodes specified are used.
+If the
+.Fl h
+flag is specified, the editor will always display the
+block usage and limits in a more human readable format
+rather than displaying them in the historic kilobyte format.
+Setting a quota to zero indicates that no quota should be imposed.
+Setting a hard limit to one indicates that no allocations should
+be permitted.
+Setting a soft limit to one with a hard limit of zero
+indicates that allocations should be permitted only on
+a temporary basis (see
+.Fl t
+below).
+The current usage information in the file is for informational purposes;
+only the hard and soft limits can be changed.
+.Pp
+On leaving the editor,
+.Nm
+reads the temporary file and modifies the binary
+quota files to reflect the changes made.
+.Pp
+If the
+.Fl p
+option is specified,
+.Nm
+will duplicate the quotas of the prototypical user
+specified for each user specified.
+This is the normal mechanism used to
+initialize quotas for groups of users.
+If the user given to assign quotas to is a numerical uid
+range (e.g.\& 1000-2000), then
+.Nm
+will duplicate the quotas of the prototypical user
+for each uid in the range specified.
+This allows
+for easy setup of default quotas for a group of users.
+The uids in question do not have to be currently assigned in
+.Pa /etc/passwd .
+.Pp
+If one or more
+.Fl e
+.Sm off
+.Ar fspath Op : Ar bslim Op : Ar bhlim Op : Ar islim Op : Ar ihlim
+.Sm on
+options are specified,
+.Nm
+will non-interactively set quotas defined by
+.Ar bslim , bhlim , islim ,
+and
+.Ar ihlim
+on each particular file system referenced by
+.Ar fspath .
+Here
+.Ar bslim
+is the soft limit on the number of blocks,
+.Ar bhlim
+is the hard limit on the number of blocks,
+.Ar islim
+is the soft limit on the number of files, and
+.Ar ihlim
+is the hard limit on the number of files.
+If any of the
+.Ar bslim , bhlim , islim ,
+and
+.Ar ihlim
+values is omitted, it is assumed to be zero, therefore
+indicating that no particular quota should be imposed.
+Block quotas can be specified in bytes (B), kilobytes (K),
+megabytes (M), terabytes (T), petabytes (P), or exabytes (E).
+If no units are specified, kilobytes are assumed.
+Inode quotas can be specified in kiloinodes (K),
+megainodes (M), terainodes (T), petainodes (P), or exainodes (E).
+If no units are specified, the number of inodes specified are used.
+.Pp
+If invoked with the
+.Fl f
+option,
+.Nm
+will read and modify quotas on the file system specified by
+.Ar fspath
+only.
+The
+.Ar fspath
+argument may be either a special device
+or a file system mount point.
+The primary purpose of this option is to set the scope for the
+.Fl p
+option, which would overwrite quota records on every
+file system with quotas otherwise.
+.Pp
+If the
+.Fl g
+flag is specified,
+.Nm
+is invoked to edit the quotas of
+one or more groups specified on the command line.
+The
+.Fl p
+flag can be specified in conjunction with
+the
+.Fl g
+flag to specify a prototypical group
+to be duplicated among the listed set of groups.
+Similarly,
+.Fl e
+flag can be specified in conjunction with
+the
+.Fl g
+flag to non-interactively set-up quotas on the listed set
+of groups.
+.Pp
+Users are permitted to exceed their soft limits
+for a grace period that may be specified per file system.
+Once the grace period has expired,
+the soft limit is enforced as a hard limit.
+The default grace period for a file system is specified in
+.In ufs/ufs/quota.h .
+The
+.Fl t
+flag can be used to change the grace period.
+By default, or when invoked with the
+.Fl u
+flag,
+the grace period is set for all the file systems with user
+quotas specified in
+.Pa /etc/fstab .
+When invoked with the
+.Fl g
+flag the grace period is
+set for all the file systems with group quotas specified in
+.Pa /etc/fstab .
+The grace period may be specified in days, hours, minutes, or seconds.
+Setting a grace period to zero indicates that the default
+grace period should be imposed.
+Setting a grace period to one second indicates that no
+grace period should be granted.
+Quotas must be turned off for the file system and
+then turned back on for the new grace period to take effect.
+.Pp
+Only the super-user may edit quotas.
+.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
+to find file system names and locations
+.El
+.Sh DIAGNOSTICS
+Various messages about inaccessible files; self-explanatory.
+.Sh SEE ALSO
+.Xr quota 1 ,
+.Xr quotactl 2 ,
+.Xr fstab 5 ,
+.Xr quotacheck 8 ,
+.Xr quotaon 8 ,
+.Xr repquota 8
diff --git a/usr.sbin/edquota/edquota.c b/usr.sbin/edquota/edquota.c
new file mode 100644
index 0000000..0cd117f
--- /dev/null
+++ b/usr.sbin/edquota/edquota.c
@@ -0,0 +1,959 @@
+/*
+ * 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[] = "@(#)edquota.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Disk quota editor.
+ */
+
+#include <sys/file.h>
+#include <sys/mount.h>
+#include <sys/wait.h>
+#include <ufs/ufs/quota.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fstab.h>
+#include <grp.h>
+#include <inttypes.h>
+#include <libutil.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "pathnames.h"
+
+/* Let's be paranoid about block size */
+#if 10 > DEV_BSHIFT
+#define dbtokb(db) \
+ ((off_t)(db) >> (10-DEV_BSHIFT))
+#elif 10 < DEV_BSHIFT
+#define dbtokb(db) \
+ ((off_t)(db) << (DEV_BSHIFT-10))
+#else
+#define dbtokb(db) (db)
+#endif
+
+const char *qfextension[] = INITQFNAMES;
+char tmpfil[] = _PATH_TMP;
+int hflag;
+
+struct quotause {
+ struct quotause *next;
+ struct quotafile *qf;
+ struct dqblk dqblk;
+ int flags;
+ char fsname[MAXPATHLEN + 1];
+};
+#define FOUND 0x01
+
+int alldigits(const char *s);
+int cvtatos(uint64_t, char *, uint64_t *);
+char *cvtstoa(uint64_t);
+uint64_t cvtblkval(uint64_t, char, const char *);
+uint64_t cvtinoval(uint64_t, char, const char *);
+int editit(char *);
+char *fmthumanvalblks(int64_t);
+char *fmthumanvalinos(int64_t);
+void freeprivs(struct quotause *);
+int getentry(const char *, int);
+struct quotause *getprivs(long, int, char *);
+void putprivs(long, struct quotause *);
+int readprivs(struct quotause *, char *);
+int readtimes(struct quotause *, char *);
+static void usage(void);
+int writetimes(struct quotause *, int, int);
+int writeprivs(struct quotause *, int, char *, int);
+
+int
+main(int argc, char *argv[])
+{
+ struct quotause *qup, *protoprivs, *curprivs;
+ long id, protoid;
+ int i, quotatype, range, tmpfd;
+ uid_t startuid, enduid;
+ uint64_t lim;
+ char *protoname, *cp, *endpt, *oldoptarg;
+ int eflag = 0, tflag = 0, pflag = 0, ch;
+ char *fspath = NULL;
+ char buf[MAXLOGNAME];
+
+ if (argc < 2)
+ usage();
+ if (getuid())
+ errx(1, "permission denied");
+ quotatype = USRQUOTA;
+ protoprivs = NULL;
+ curprivs = NULL;
+ protoname = NULL;
+ while ((ch = getopt(argc, argv, "ughtf:p:e:")) != -1) {
+ switch(ch) {
+ case 'f':
+ fspath = optarg;
+ break;
+ case 'p':
+ if (eflag) {
+ warnx("cannot specify both -e and -p");
+ usage();
+ /* not reached */
+ }
+ protoname = optarg;
+ pflag++;
+ break;
+ case 'g':
+ quotatype = GRPQUOTA;
+ break;
+ case 'h':
+ hflag++;
+ break;
+ case 'u':
+ quotatype = USRQUOTA;
+ break;
+ case 't':
+ tflag++;
+ break;
+ case 'e':
+ if (pflag) {
+ warnx("cannot specify both -e and -p");
+ usage();
+ /* not reached */
+ }
+ if ((qup = calloc(1, sizeof(*qup))) == NULL)
+ errx(2, "out of memory");
+ oldoptarg = optarg;
+ for (i = 0, cp = optarg;
+ (cp = strsep(&optarg, ":")) != NULL; i++) {
+ if (cp != oldoptarg)
+ *(cp - 1) = ':';
+ if (i > 0 && !isdigit(*cp)) {
+ warnx("incorrect quota specification: "
+ "%s", oldoptarg);
+ usage();
+ /* Not Reached */
+ }
+ switch (i) {
+ case 0:
+ strlcpy(qup->fsname, cp,
+ sizeof(qup->fsname));
+ break;
+ case 1:
+ lim = strtoll(cp, &endpt, 10);
+ qup->dqblk.dqb_bsoftlimit =
+ cvtblkval(lim, *endpt,
+ "block soft limit");
+ continue;
+ case 2:
+ lim = strtoll(cp, &endpt, 10);
+ qup->dqblk.dqb_bhardlimit =
+ cvtblkval(lim, *endpt,
+ "block hard limit");
+ continue;
+ case 3:
+ lim = strtoll(cp, &endpt, 10);
+ qup->dqblk.dqb_isoftlimit =
+ cvtinoval(lim, *endpt,
+ "inode soft limit");
+ continue;
+ case 4:
+ lim = strtoll(cp, &endpt, 10);
+ qup->dqblk.dqb_ihardlimit =
+ cvtinoval(lim, *endpt,
+ "inode hard limit");
+ continue;
+ default:
+ warnx("incorrect quota specification: "
+ "%s", oldoptarg);
+ usage();
+ /* Not Reached */
+ }
+ }
+ if (protoprivs == NULL) {
+ protoprivs = curprivs = qup;
+ } else {
+ curprivs->next = qup;
+ curprivs = qup;
+ }
+ eflag++;
+ break;
+ default:
+ usage();
+ /* Not Reached */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (pflag || eflag) {
+ if (pflag) {
+ if ((protoid = getentry(protoname, quotatype)) == -1)
+ exit(1);
+ protoprivs = getprivs(protoid, quotatype, fspath);
+ if (protoprivs == NULL)
+ exit(0);
+ for (qup = protoprivs; qup; qup = qup->next) {
+ qup->dqblk.dqb_btime = 0;
+ qup->dqblk.dqb_itime = 0;
+ }
+ }
+ for (; argc-- > 0; argv++) {
+ if (strspn(*argv, "0123456789-") == strlen(*argv) &&
+ (cp = strchr(*argv, '-')) != NULL) {
+ *cp++ = '\0';
+ startuid = atoi(*argv);
+ enduid = atoi(cp);
+ if (enduid < startuid)
+ errx(1,
+ "ending uid (%d) must be >= starting uid (%d) when using uid ranges",
+ enduid, startuid);
+ range = 1;
+ } else {
+ startuid = enduid = 0;
+ range = 0;
+ }
+ for ( ; startuid <= enduid; startuid++) {
+ if (range)
+ snprintf(buf, sizeof(buf), "%d",
+ startuid);
+ else
+ snprintf(buf, sizeof(buf), "%s",
+ *argv);
+ if ((id = getentry(buf, quotatype)) < 0)
+ continue;
+ if (pflag) {
+ putprivs(id, protoprivs);
+ continue;
+ }
+ for (qup = protoprivs; qup; qup = qup->next) {
+ curprivs = getprivs(id, quotatype,
+ qup->fsname);
+ if (curprivs == NULL)
+ continue;
+ curprivs->dqblk = qup->dqblk;
+ putprivs(id, curprivs);
+ freeprivs(curprivs);
+ }
+ }
+ }
+ if (pflag)
+ freeprivs(protoprivs);
+ exit(0);
+ }
+ tmpfd = mkostemp(tmpfil, O_CLOEXEC);
+ fchown(tmpfd, getuid(), getgid());
+ if (tflag) {
+ if ((protoprivs = getprivs(0, quotatype, fspath)) != NULL) {
+ if (writetimes(protoprivs, tmpfd, quotatype) != 0 &&
+ editit(tmpfil) && readtimes(protoprivs, tmpfil))
+ putprivs(0L, protoprivs);
+ freeprivs(protoprivs);
+ }
+ close(tmpfd);
+ unlink(tmpfil);
+ exit(0);
+ }
+ for ( ; argc > 0; argc--, argv++) {
+ if ((id = getentry(*argv, quotatype)) == -1)
+ continue;
+ if ((curprivs = getprivs(id, quotatype, fspath)) == NULL)
+ exit(1);
+ if (writeprivs(curprivs, tmpfd, *argv, quotatype) == 0)
+ continue;
+ if (editit(tmpfil) && readprivs(curprivs, tmpfil))
+ putprivs(id, curprivs);
+ freeprivs(curprivs);
+ }
+ close(tmpfd);
+ unlink(tmpfil);
+ exit(0);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
+ "usage: edquota [-uh] [-f fspath] [-p username] username ...",
+ " edquota [-u] -e fspath[:bslim[:bhlim[:islim[:ihlim]]]] [-e ...]",
+ " username ...",
+ " edquota -g [-h] [-f fspath] [-p groupname] groupname ...",
+ " edquota -g -e fspath[:bslim[:bhlim[:islim[:ihlim]]]] [-e ...]",
+ " groupname ...",
+ " edquota [-u] -t [-f fspath]",
+ " edquota -g -t [-f fspath]");
+ exit(1);
+}
+
+/*
+ * This routine converts a name for a particular quota type to
+ * an identifier. This routine must agree with the kernel routine
+ * getinoquota as to the interpretation of quota types.
+ */
+int
+getentry(const char *name, int quotatype)
+{
+ struct passwd *pw;
+ struct group *gr;
+
+ if (alldigits(name))
+ return (atoi(name));
+ switch(quotatype) {
+ case USRQUOTA:
+ if ((pw = getpwnam(name)))
+ return (pw->pw_uid);
+ warnx("%s: no such user", name);
+ sleep(3);
+ break;
+ case GRPQUOTA:
+ if ((gr = getgrnam(name)))
+ return (gr->gr_gid);
+ warnx("%s: no such group", name);
+ sleep(3);
+ break;
+ default:
+ warnx("%d: unknown quota type", quotatype);
+ sleep(3);
+ break;
+ }
+ sleep(1);
+ return (-1);
+}
+
+/*
+ * Collect the requested quota information.
+ */
+struct quotause *
+getprivs(long id, int quotatype, char *fspath)
+{
+ struct quotafile *qf;
+ struct fstab *fs;
+ struct quotause *qup, *quptail;
+ struct quotause *quphead;
+
+ setfsent();
+ quphead = quptail = NULL;
+ while ((fs = getfsent())) {
+ if (fspath && *fspath && strcmp(fspath, fs->fs_spec) &&
+ strcmp(fspath, fs->fs_file))
+ continue;
+ if (strcmp(fs->fs_vfstype, "ufs"))
+ continue;
+ if ((qf = quota_open(fs, quotatype, O_CREAT|O_RDWR)) == NULL) {
+ if (errno != EOPNOTSUPP)
+ warn("cannot open quotas on %s", fs->fs_file);
+ continue;
+ }
+ if ((qup = (struct quotause *)calloc(1, sizeof(*qup))) == NULL)
+ errx(2, "out of memory");
+ qup->qf = qf;
+ strncpy(qup->fsname, fs->fs_file, sizeof(qup->fsname));
+ if (quota_read(qf, &qup->dqblk, id) == -1) {
+ warn("cannot read quotas on %s", fs->fs_file);
+ freeprivs(qup);
+ continue;
+ }
+ if (quphead == NULL)
+ quphead = qup;
+ else
+ quptail->next = qup;
+ quptail = qup;
+ qup->next = 0;
+ }
+ if (quphead == NULL) {
+ warnx("No quotas on %s", fspath ? fspath : "any filesystems");
+ }
+ endfsent();
+ return (quphead);
+}
+
+/*
+ * Store the requested quota information.
+ */
+void
+putprivs(long id, struct quotause *quplist)
+{
+ struct quotause *qup;
+
+ for (qup = quplist; qup; qup = qup->next)
+ if (quota_write_limits(qup->qf, &qup->dqblk, id) == -1)
+ warn("%s", qup->fsname);
+}
+
+/*
+ * Take a list of privileges and get it edited.
+ */
+int
+editit(char *tmpf)
+{
+ long omask;
+ int pid, status;
+
+ omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP));
+ top:
+ if ((pid = fork()) < 0) {
+
+ if (errno == EPROCLIM) {
+ warnx("you have too many processes");
+ return(0);
+ }
+ if (errno == EAGAIN) {
+ sleep(1);
+ goto top;
+ }
+ warn("fork");
+ return (0);
+ }
+ if (pid == 0) {
+ const char *ed;
+
+ sigsetmask(omask);
+ if (setgid(getgid()) != 0)
+ err(1, "setgid failed");
+ if (setuid(getuid()) != 0)
+ err(1, "setuid failed");
+ if ((ed = getenv("EDITOR")) == (char *)0)
+ ed = _PATH_VI;
+ execlp(ed, ed, tmpf, (char *)0);
+ err(1, "%s", ed);
+ }
+ waitpid(pid, &status, 0);
+ sigsetmask(omask);
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
+ return (0);
+ return (1);
+}
+
+/*
+ * Convert a quotause list to an ASCII file.
+ */
+int
+writeprivs(struct quotause *quplist, int outfd, char *name, int quotatype)
+{
+ struct quotause *qup;
+ FILE *fd;
+
+ ftruncate(outfd, 0);
+ lseek(outfd, 0, L_SET);
+ if ((fd = fdopen(dup(outfd), "w")) == NULL)
+ err(1, "%s", tmpfil);
+ fprintf(fd, "Quotas for %s %s:\n", qfextension[quotatype], name);
+ for (qup = quplist; qup; qup = qup->next) {
+ fprintf(fd, "%s: in use: %s, ", qup->fsname,
+ fmthumanvalblks(qup->dqblk.dqb_curblocks));
+ fprintf(fd, "limits (soft = %s, ",
+ fmthumanvalblks(qup->dqblk.dqb_bsoftlimit));
+ fprintf(fd, "hard = %s)\n",
+ fmthumanvalblks(qup->dqblk.dqb_bhardlimit));
+ fprintf(fd, "\tinodes in use: %s, ",
+ fmthumanvalinos(qup->dqblk.dqb_curinodes));
+ fprintf(fd, "limits (soft = %s, ",
+ fmthumanvalinos(qup->dqblk.dqb_isoftlimit));
+ fprintf(fd, "hard = %s)\n",
+ fmthumanvalinos(qup->dqblk.dqb_ihardlimit));
+ }
+ fclose(fd);
+ return (1);
+}
+
+char *
+fmthumanvalblks(int64_t blocks)
+{
+ static char numbuf[20];
+
+ if (hflag) {
+ humanize_number(numbuf, blocks < 0 ? 7 : 6,
+ dbtob(blocks), "", HN_AUTOSCALE, HN_NOSPACE);
+ return (numbuf);
+ }
+ snprintf(numbuf, sizeof(numbuf), "%juk", (uintmax_t)dbtokb(blocks));
+ return(numbuf);
+}
+
+char *
+fmthumanvalinos(int64_t inos)
+{
+ static char numbuf[20];
+
+ if (hflag) {
+ humanize_number(numbuf, inos < 0 ? 7 : 6,
+ inos, "", HN_AUTOSCALE, HN_NOSPACE | HN_DIVISOR_1000);
+ return (numbuf);
+ }
+ snprintf(numbuf, sizeof(numbuf), "%ju", (uintmax_t)inos);
+ return(numbuf);
+}
+
+/*
+ * Merge changes to an ASCII file into a quotause list.
+ */
+int
+readprivs(struct quotause *quplist, char *inname)
+{
+ struct quotause *qup;
+ FILE *fd;
+ uintmax_t hardlimit, softlimit, curitems;
+ char hardunits, softunits, curitemunits;
+ int cnt;
+ char *cp;
+ struct dqblk dqblk;
+ char *fsp, line1[BUFSIZ], line2[BUFSIZ];
+
+ fd = fopen(inname, "r");
+ if (fd == NULL) {
+ warnx("can't re-read temp file!!");
+ return (0);
+ }
+ /*
+ * Discard title line, then read pairs of lines to process.
+ */
+ (void) fgets(line1, sizeof (line1), fd);
+ while (fgets(line1, sizeof (line1), fd) != NULL &&
+ fgets(line2, sizeof (line2), fd) != NULL) {
+ if ((fsp = strtok(line1, " \t:")) == NULL) {
+ warnx("%s: bad format", line1);
+ return (0);
+ }
+ if ((cp = strtok((char *)0, "\n")) == NULL) {
+ warnx("%s: %s: bad format", fsp, &fsp[strlen(fsp) + 1]);
+ return (0);
+ }
+ cnt = sscanf(cp,
+ " in use: %ju%c, limits (soft = %ju%c, hard = %ju%c)",
+ &curitems, &curitemunits, &softlimit, &softunits,
+ &hardlimit, &hardunits);
+ /*
+ * The next three check for old-style input formats.
+ */
+ if (cnt != 6)
+ cnt = sscanf(cp,
+ " in use: %ju%c, limits (soft = %ju%c hard = %ju%c",
+ &curitems, &curitemunits, &softlimit,
+ &softunits, &hardlimit, &hardunits);
+ if (cnt != 6)
+ cnt = sscanf(cp,
+ " in use: %ju%c, limits (soft = %ju%c hard = %ju%c)",
+ &curitems, &curitemunits, &softlimit,
+ &softunits, &hardlimit, &hardunits);
+ if (cnt != 6)
+ cnt = sscanf(cp,
+ " in use: %ju%c, limits (soft = %ju%c, hard = %ju%c",
+ &curitems, &curitemunits, &softlimit,
+ &softunits, &hardlimit, &hardunits);
+ if (cnt != 6) {
+ warnx("%s:%s: bad format", fsp, cp);
+ return (0);
+ }
+ dqblk.dqb_curblocks = cvtblkval(curitems, curitemunits,
+ "current block count");
+ dqblk.dqb_bsoftlimit = cvtblkval(softlimit, softunits,
+ "block soft limit");
+ dqblk.dqb_bhardlimit = cvtblkval(hardlimit, hardunits,
+ "block hard limit");
+ if ((cp = strtok(line2, "\n")) == NULL) {
+ warnx("%s: %s: bad format", fsp, line2);
+ return (0);
+ }
+ cnt = sscanf(&cp[7],
+ " in use: %ju%c limits (soft = %ju%c, hard = %ju%c)",
+ &curitems, &curitemunits, &softlimit,
+ &softunits, &hardlimit, &hardunits);
+ /*
+ * The next three check for old-style input formats.
+ */
+ if (cnt != 6)
+ cnt = sscanf(&cp[7],
+ " in use: %ju%c limits (soft = %ju%c hard = %ju%c",
+ &curitems, &curitemunits, &softlimit,
+ &softunits, &hardlimit, &hardunits);
+ if (cnt != 6)
+ cnt = sscanf(&cp[7],
+ " in use: %ju%c limits (soft = %ju%c hard = %ju%c)",
+ &curitems, &curitemunits, &softlimit,
+ &softunits, &hardlimit, &hardunits);
+ if (cnt != 6)
+ cnt = sscanf(&cp[7],
+ " in use: %ju%c limits (soft = %ju%c, hard = %ju%c",
+ &curitems, &curitemunits, &softlimit,
+ &softunits, &hardlimit, &hardunits);
+ if (cnt != 6) {
+ warnx("%s: %s: bad format cnt %d", fsp, &cp[7], cnt);
+ return (0);
+ }
+ dqblk.dqb_curinodes = cvtinoval(curitems, curitemunits,
+ "current inode count");
+ dqblk.dqb_isoftlimit = cvtinoval(softlimit, softunits,
+ "inode soft limit");
+ dqblk.dqb_ihardlimit = cvtinoval(hardlimit, hardunits,
+ "inode hard limit");
+ for (qup = quplist; qup; qup = qup->next) {
+ if (strcmp(fsp, qup->fsname))
+ continue;
+ /*
+ * Cause time limit to be reset when the quota
+ * is next used if previously had no soft limit
+ * or were under it, but now have a soft limit
+ * and are over it.
+ */
+ if (dqblk.dqb_bsoftlimit &&
+ qup->dqblk.dqb_curblocks >= dqblk.dqb_bsoftlimit &&
+ (qup->dqblk.dqb_bsoftlimit == 0 ||
+ qup->dqblk.dqb_curblocks <
+ qup->dqblk.dqb_bsoftlimit))
+ qup->dqblk.dqb_btime = 0;
+ if (dqblk.dqb_isoftlimit &&
+ qup->dqblk.dqb_curinodes >= dqblk.dqb_isoftlimit &&
+ (qup->dqblk.dqb_isoftlimit == 0 ||
+ qup->dqblk.dqb_curinodes <
+ qup->dqblk.dqb_isoftlimit))
+ qup->dqblk.dqb_itime = 0;
+ qup->dqblk.dqb_bsoftlimit = dqblk.dqb_bsoftlimit;
+ qup->dqblk.dqb_bhardlimit = dqblk.dqb_bhardlimit;
+ qup->dqblk.dqb_isoftlimit = dqblk.dqb_isoftlimit;
+ qup->dqblk.dqb_ihardlimit = dqblk.dqb_ihardlimit;
+ qup->flags |= FOUND;
+ /* Humanized input returns only approximate counts */
+ if (hflag ||
+ (dqblk.dqb_curblocks == qup->dqblk.dqb_curblocks &&
+ dqblk.dqb_curinodes == qup->dqblk.dqb_curinodes))
+ break;
+ warnx("%s: cannot change current allocation", fsp);
+ break;
+ }
+ }
+ fclose(fd);
+ /*
+ * Disable quotas for any filesystems that have not been found.
+ */
+ for (qup = quplist; qup; qup = qup->next) {
+ if (qup->flags & FOUND) {
+ qup->flags &= ~FOUND;
+ continue;
+ }
+ qup->dqblk.dqb_bsoftlimit = 0;
+ qup->dqblk.dqb_bhardlimit = 0;
+ qup->dqblk.dqb_isoftlimit = 0;
+ qup->dqblk.dqb_ihardlimit = 0;
+ }
+ return (1);
+}
+
+/*
+ * Convert a quotause list to an ASCII file of grace times.
+ */
+int
+writetimes(struct quotause *quplist, int outfd, int quotatype)
+{
+ struct quotause *qup;
+ FILE *fd;
+
+ ftruncate(outfd, 0);
+ lseek(outfd, 0, L_SET);
+ if ((fd = fdopen(dup(outfd), "w")) == NULL)
+ err(1, "%s", tmpfil);
+ fprintf(fd, "Time units may be: days, hours, minutes, or seconds\n");
+ fprintf(fd, "Grace period before enforcing soft limits for %ss:\n",
+ qfextension[quotatype]);
+ for (qup = quplist; qup; qup = qup->next) {
+ fprintf(fd, "%s: block grace period: %s, ",
+ qup->fsname, cvtstoa(qup->dqblk.dqb_btime));
+ fprintf(fd, "file grace period: %s\n",
+ cvtstoa(qup->dqblk.dqb_itime));
+ }
+ fclose(fd);
+ return (1);
+}
+
+/*
+ * Merge changes of grace times in an ASCII file into a quotause list.
+ */
+int
+readtimes(struct quotause *quplist, char *inname)
+{
+ struct quotause *qup;
+ FILE *fd;
+ int cnt;
+ char *cp;
+ uintmax_t itime, btime, iseconds, bseconds;
+ char *fsp, bunits[10], iunits[10], line1[BUFSIZ];
+
+ fd = fopen(inname, "r");
+ if (fd == NULL) {
+ warnx("can't re-read temp file!!");
+ return (0);
+ }
+ /*
+ * Discard two title lines, then read lines to process.
+ */
+ (void) fgets(line1, sizeof (line1), fd);
+ (void) fgets(line1, sizeof (line1), fd);
+ while (fgets(line1, sizeof (line1), fd) != NULL) {
+ if ((fsp = strtok(line1, " \t:")) == NULL) {
+ warnx("%s: bad format", line1);
+ return (0);
+ }
+ if ((cp = strtok((char *)0, "\n")) == NULL) {
+ warnx("%s: %s: bad format", fsp, &fsp[strlen(fsp) + 1]);
+ return (0);
+ }
+ cnt = sscanf(cp,
+ " block grace period: %ju %s file grace period: %ju %s",
+ &btime, bunits, &itime, iunits);
+ if (cnt != 4) {
+ warnx("%s:%s: bad format", fsp, cp);
+ return (0);
+ }
+ if (cvtatos(btime, bunits, &bseconds) == 0)
+ return (0);
+ if (cvtatos(itime, iunits, &iseconds) == 0)
+ return (0);
+ for (qup = quplist; qup; qup = qup->next) {
+ if (strcmp(fsp, qup->fsname))
+ continue;
+ qup->dqblk.dqb_btime = bseconds;
+ qup->dqblk.dqb_itime = iseconds;
+ qup->flags |= FOUND;
+ break;
+ }
+ }
+ fclose(fd);
+ /*
+ * reset default grace periods for any filesystems
+ * that have not been found.
+ */
+ for (qup = quplist; qup; qup = qup->next) {
+ if (qup->flags & FOUND) {
+ qup->flags &= ~FOUND;
+ continue;
+ }
+ qup->dqblk.dqb_btime = 0;
+ qup->dqblk.dqb_itime = 0;
+ }
+ return (1);
+}
+
+/*
+ * Convert seconds to ASCII times.
+ */
+char *
+cvtstoa(uint64_t secs)
+{
+ static char buf[20];
+
+ if (secs % (24 * 60 * 60) == 0) {
+ secs /= 24 * 60 * 60;
+ sprintf(buf, "%ju day%s", (uintmax_t)secs,
+ secs == 1 ? "" : "s");
+ } else if (secs % (60 * 60) == 0) {
+ secs /= 60 * 60;
+ sprintf(buf, "%ju hour%s", (uintmax_t)secs,
+ secs == 1 ? "" : "s");
+ } else if (secs % 60 == 0) {
+ secs /= 60;
+ sprintf(buf, "%ju minute%s", (uintmax_t)secs,
+ secs == 1 ? "" : "s");
+ } else
+ sprintf(buf, "%ju second%s", (uintmax_t)secs,
+ secs == 1 ? "" : "s");
+ return (buf);
+}
+
+/*
+ * Convert ASCII input times to seconds.
+ */
+int
+cvtatos(uint64_t period, char *units, uint64_t *seconds)
+{
+
+ if (bcmp(units, "second", 6) == 0)
+ *seconds = period;
+ else if (bcmp(units, "minute", 6) == 0)
+ *seconds = period * 60;
+ else if (bcmp(units, "hour", 4) == 0)
+ *seconds = period * 60 * 60;
+ else if (bcmp(units, "day", 3) == 0)
+ *seconds = period * 24 * 60 * 60;
+ else {
+ warnx("%s: bad units, specify %s\n", units,
+ "days, hours, minutes, or seconds");
+ return (0);
+ }
+ return (1);
+}
+
+/*
+ * Convert a limit to number of disk blocks.
+ */
+uint64_t
+cvtblkval(uint64_t limit, char units, const char *itemname)
+{
+
+ switch(units) {
+ case 'B':
+ case 'b':
+ limit = btodb(limit);
+ break;
+ case '\0': /* historic behavior */
+ case ',': /* historic behavior */
+ case ')': /* historic behavior */
+ case 'K':
+ case 'k':
+ limit *= btodb(1024);
+ break;
+ case 'M':
+ case 'm':
+ limit *= btodb(1048576);
+ break;
+ case 'G':
+ case 'g':
+ limit *= btodb(1073741824);
+ break;
+ case 'T':
+ case 't':
+ limit *= btodb(1099511627776);
+ break;
+ case 'P':
+ case 'p':
+ limit *= btodb(1125899906842624);
+ break;
+ case 'E':
+ case 'e':
+ limit *= btodb(1152921504606846976);
+ break;
+ case ' ':
+ errx(2, "No space permitted between value and units for %s\n",
+ itemname);
+ break;
+ default:
+ errx(2, "%ju%c: unknown units for %s, specify "
+ "none, K, M, G, T, P, or E\n",
+ (uintmax_t)limit, units, itemname);
+ break;
+ }
+ return (limit);
+}
+
+/*
+ * Convert a limit to number of inodes.
+ */
+uint64_t
+cvtinoval(uint64_t limit, char units, const char *itemname)
+{
+
+ switch(units) {
+ case 'B':
+ case 'b':
+ case '\0': /* historic behavior */
+ case ',': /* historic behavior */
+ case ')': /* historic behavior */
+ break;
+ case 'K':
+ case 'k':
+ limit *= 1000;
+ break;
+ case 'M':
+ case 'm':
+ limit *= 1000000;
+ break;
+ case 'G':
+ case 'g':
+ limit *= 1000000000;
+ break;
+ case 'T':
+ case 't':
+ limit *= 1000000000000;
+ break;
+ case 'P':
+ case 'p':
+ limit *= 1000000000000000;
+ break;
+ case 'E':
+ case 'e':
+ limit *= 1000000000000000000;
+ break;
+ case ' ':
+ errx(2, "No space permitted between value and units for %s\n",
+ itemname);
+ break;
+ default:
+ errx(2, "%ju%c: unknown units for %s, specify "
+ "none, K, M, G, T, P, or E\n",
+ (uintmax_t)limit, units, itemname);
+ break;
+ }
+ return (limit);
+}
+
+/*
+ * Free a list of quotause structures.
+ */
+void
+freeprivs(struct quotause *quplist)
+{
+ struct quotause *qup, *nextqup;
+
+ for (qup = quplist; qup; qup = nextqup) {
+ quota_close(qup->qf);
+ nextqup = qup->next;
+ free(qup);
+ }
+}
+
+/*
+ * Check whether a string is completely composed of digits.
+ */
+int
+alldigits(const char *s)
+{
+ int c;
+
+ c = *s++;
+ do {
+ if (!isdigit(c))
+ return (0);
+ } while ((c = *s++));
+ return (1);
+}
diff --git a/usr.sbin/edquota/pathnames.h b/usr.sbin/edquota/pathnames.h
new file mode 100644
index 0000000..53bfbfe
--- /dev/null
+++ b/usr.sbin/edquota/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/6/93
+ * $FreeBSD$
+ */
+
+#include <paths.h>
+
+#undef _PATH_TMP
+#define _PATH_TMP "/tmp/EdP.aXXXXX"
diff --git a/usr.sbin/eeprom/Makefile b/usr.sbin/eeprom/Makefile
new file mode 100644
index 0000000..61b48a2
--- /dev/null
+++ b/usr.sbin/eeprom/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../ofwdump
+
+PROG= eeprom
+MAN= eeprom.8
+MANSUBDIR= /sparc64
+SRCS= eeprom.c ofw_options.c ofw_util.c
+CFLAGS+= -I${.CURDIR}/../ofwdump
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/eeprom/Makefile.depend b/usr.sbin/eeprom/Makefile.depend
new file mode 100644
index 0000000..79eb58b
--- /dev/null
+++ b/usr.sbin/eeprom/Makefile.depend
@@ -0,0 +1,16 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/eeprom/eeprom.8 b/usr.sbin/eeprom/eeprom.8
new file mode 100644
index 0000000..b5b08bb
--- /dev/null
+++ b/usr.sbin/eeprom/eeprom.8
@@ -0,0 +1,700 @@
+.\" Copyright (c) 1996 The NetBSD Foundation, Inc.
+.\" Copyright (c) 2004 Marius Strobl
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Jason R. Thorpe.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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.
+.\"
+.\" from: NetBSD: eeprom.8,v 1.11 2003/03/31 01:31:39 perry Exp
+.\" $FreeBSD$
+.\"
+.Dd September 1, 2006
+.Dt EEPROM 8 sparc64
+.Os
+.Sh NAME
+.Nm eeprom
+.Nd "display or modify contents of the EEPROM or NVRAM"
+.Sh SYNOPSIS
+.Nm
+.Fl a
+.Nm
+.Op Fl
+.Ar name Ns Op = Ns Ar value
+.Ar ...
+.Sh DESCRIPTION
+The
+.Nm
+utility provides an interface for displaying and changing the system's
+configuration variables contained in EEPROM or NVRAM.
+In the first synopsis form, all available configuration variables and their
+current values are printed.
+In the second form, only the variable selected by
+.Ar name
+and its value is printed or changed if
+.Ar name
+is followed by
+.Ql =
+and a
+.Ar value .
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl
+Commands for displaying or changing variables are taken from stdin, allowing
+one
+.Ar name
+or one
+.Ar name
+and
+.Ar value
+pair per line.
+The output is printed on stdout.
+.It Fl a
+Print all available configuration variables and their current values.
+.El
+.Sh VARIABLES AND VALUES
+Below are variables and values that one is likely to find on a system equipped
+with OpenBoot 3.x and Open Firmware respectively.
+.Pp
+Note: the attempt to set a variable to an illegal value results in the
+Open Firmware setting it to some legal value instead.
+The
+.Nm
+utility will detect this, try to recover the previous value of the variable
+and issue a warning telling that the requested value could not be set.
+.Bl -tag -width ".Va last-hardware-update"
+.It Va auto-boot?
+If
+.Dq Li true ,
+the system will try to boot automatically from the devices listed in
+.Va boot-device
+and
+.Va diag-device
+respectively, using the command specified in
+.Va boot-command
+at power-up.
+Default:
+.Dq Li true .
+.It Va auto-boot-retry?
+If set to
+.Dq Li true
+and
+.Va auto-boot?
+is also set to
+.Dq Li true ,
+the system will try to boot from the specified boot devices forever.
+Default:
+.Dq Li false .
+.It Va ansi-terminal?
+If
+.Dq Li false ,
+.Tn ANSI
+escape sequences are not interpreted by the terminal emulator.
+Default:
+.Dq Li true .
+.It Va boot-command
+Command executed when
+.Va auto-boot?
+is set to
+.Dq Li true .
+Default:
+.Dq Li boot .
+.It Va boot-device
+Default device to boot from if
+.Va diag-switch?
+is set to
+.Dq Li false .
+Takes one or more device aliases or device paths.
+The boot devices are sequentially tried to boot from, beginning with the first
+one specified.
+Default:
+.Dq Li "net disk" .
+.It Va cpci-probe-list
+Digits in the format
+.Dq Li 0,1,2
+specifying in which order to probe the devices on the CompactPCI bus at
+power-up.
+Default: system-dependent.
+.It Va boot-file
+Default arguments for boot when
+.Va diag-switch?
+is set to
+.Dq Li false .
+When empty, the secondary boot loader will choose the file to boot.
+Default: empty string.
+.It Va diag-device
+Like
+.Va boot-device .
+Used when
+.Va diag-switch?
+is set to
+.Dq Li true .
+Default:
+.Dq Li net .
+.It Va diag-file
+Like
+.Va boot-file .
+Used when
+.Va diag-switch?
+is set to
+.Dq Li true .
+Default: empty string.
+.It Va diag-level
+Level of diagnostics to run when
+.Va diag-switch?
+is set to
+.Dq Li true .
+Possible values are
+.Dq Li max ,
+.Dq Li menus ,
+.Dq Li min
+and
+.Dq Li off
+(depending on the system model).
+When set to
+.Dq Li off ,
+the Power-On Self Test (POST) is not run.
+The other values are interpreted by the POST.
+Default:
+.Dq Li min
+or
+.Dq Li max
+(system-dependent).
+.It Va diag-switch?
+If
+.Dq Li true ,
+the system will boot and run in diagnostic mode.
+Default:
+.Dq Li false
+or
+.Dq Li true
+(system-dependent).
+.It Va env-monitor
+Enables or disables the Advanced System Monitoring (ASM).
+Possible values are
+.Dq Li enabled
+and
+.Dq Li disabled .
+Default:
+.Dq Li enabled .
+.It Va fcode-debug?
+Used for debugging FCode programs.
+If set to
+.Dq Li true ,
+names of additional FCodes are registered in the Forth dictionary.
+Default:
+.Dq Li false .
+.It Va hardware-revision
+A string describing the system hardware version.
+Default: system-dependent.
+.It Va input-device
+One of the strings
+.Dq Li keyboard ,
+.Dq Li ttya ,
+or
+.Dq Li ttyb ,
+specifying the default console input device.
+Default:
+.Dq Li keyboard
+or
+.Dq Li ttya
+(system-dependent).
+.It Va keyboard-click?
+If set to
+.Dq Li true ,
+the keys click annoyingly.
+Default:
+.Dq Li false .
+.It Va keymap
+Keymap for a custom keyboard.
+Default: empty string.
+.It Va last-hardware-update
+Similar to
+.Va hardware-revision ,
+describing when the hardware was last updated.
+Default: system-dependent.
+.It Va last-poweroff-cause
+Cause of the last power-off.
+Used internally by the OpenBoot PROM.
+Default:
+.Dq Li 0 .
+.It Va load-base
+Default address where client programs are loaded to.
+It is unlikely that this value should ever be changed.
+Default:
+.Dq Li 16384 .
+.It Va local-mac-address?
+If set to
+.Dq Li false ,
+all Ethernet devices with FCode will use the system default MAC address.
+If set to
+.Dq Li true ,
+Ethernet devices with FCode that contains a unique MAC address will use it
+rather than the system's default MAC address.
+Default:
+.Dq Li false .
+.Pp
+Ethernet devices with FCode include those supported by
+.Xr dc 4 ,
+.Xr gem 4
+and
+.Xr hme 4 .
+Please see the respective manual page for further information.
+.It Va mfg-mode
+Manufacture test mode interpreted by the POST.
+Possible values are
+.Dq Li chamber
+and
+.Dq Li off .
+Default:
+.Dq Li off .
+.It Va mfg-switch?
+If set to
+.Dq Li true ,
+manufacturing tests are repeated until stopped by pressing STOP-A.
+Default:
+.Dq Li off .
+.It Va net-timeout
+If set to
+.Dq Li 0 ,
+the system will try to boot forever when the boot device used is a network
+device.
+Any non-zero value is interpreted as minutes to try a network boot.
+Default:
+.Dq Li 0 .
+.It Va nvramrc
+Contents of the NVRAMRC.
+Default: empty string.
+.Pp
+While
+.Va nvramrc
+can be set using
+.Nm ,
+it is preferred to use
+.Ic nvedit
+in the boot monitor instead.
+.It Va oem-banner
+A string displayed at power-up, rather than the default banner.
+Used when
+.Va oem-banner?
+is set to
+.Dq Li true .
+Default: system-dependent.
+.It Va oem-banner?
+If set to
+.Dq Li true ,
+the string stored in
+.Va oem-banner
+is displayed at power-up rather than the default banner.
+Default: system-dependent.
+.It Va oem-logo
+A logo displayed at power-up when
+.Va oem-logo?
+is set to
+.Dq Li true ,
+rather than the default logo.
+The logo has to be 512 bytes in size, containing a 64x64-bit monochrome image
+in Sun Raster format without the leading 32-byte header.
+Default: system-dependent.
+.Pp
+To set the logo with
+.Nm ,
+give the pathname of the file containing the image as the
+.Ar value .
+Using an empty
+.Ar value
+will remove the image.
+.It Va oem-logo?
+If set to
+.Dq Li true ,
+the logo stored in
+.Va oem-logo
+is displayed at power-up rather than the default logo.
+.It Va output-device
+One of the strings
+.Dq Li screen ,
+.Dq Li ttya ,
+or
+.Dq Li ttyb ,
+specifying the default console output device.
+Default:
+.Dq Li screen
+or
+.Dq Li ttya
+(system-dependent).
+.It Va pcia-probe-list
+Digits in the format
+.Dq Li 1,2,3
+specifying in which order to probe the devices on the PCI bus A.
+Default: system-dependent.
+.It Va pcib-probe-list
+Like
+.Va pcia-probe-list ,
+but for PCI bus B.
+Default: system-dependent.
+.It Va #power-cycles
+Number of power-cycles.
+Automatically incremented on each power-cycle.
+Default: system-dependent.
+.It Va sbus-probe-list
+Digits in the format
+.Dq Li 0123
+specifying in which order to probe the SBus slots at power-up.
+Default: system-dependent.
+.It Va screen-#columns
+An integer specifying the screen width in characters per line.
+Default:
+.Dq Li 80 .
+.It Va screen-#rows
+An integer specifying the screen height in lines.
+Default:
+.Dq Li 34 .
+.It Va scsi-initiator-id
+The SCSI ID of SCSI controllers in the range of [0-7] or [0-f] (depending
+on the controller).
+A SCSI controller may or may not adhere to this setting, depending on its
+FCode and device driver.
+Default:
+.Dq Li 7 .
+.It Va security-#badlogins
+Number of incorrect password attempts when
+.Va security-mode
+is set to
+.Dq Li command
+or
+.Dq Li full .
+Default:
+.Dq Li 0 .
+.It Va security-mode
+Boot monitor security level.
+One of the three possible values
+.Dq Li full ,
+.Dq Li command ,
+or
+.Dq Li none .
+When set to
+.Dq Li full ,
+all boot monitor commands except for
+.Ic go
+require the password.
+When set to
+.Dq Li command ,
+all boot monitor commands except for
+.Ic boot
+and
+.Ic go
+require the password.
+When set to
+.Dq Li none ,
+no password is required.
+Default:
+.Dq Li none .
+.Pp
+When
+.Nm
+is used to set
+.Va security-mode
+to
+.Dq Li full
+or
+.Dq Li command ,
+you will be prompted for the password.
+When
+.Va security-mode
+is set to
+.Dq Li none ,
+.Nm
+will clear the password.
+.It Va security-password
+The password used when
+.Va security-mode
+is set to
+.Dq Li full
+or
+.Dq Li command .
+The maximum length for this password is 8 characters.
+All characters exceeding this length will be ignored.
+The value displayed for
+.Va security-password
+is always an empty string, even when a password is set.
+Default: empty string.
+.Pp
+When
+.Va security-mode
+is set to
+.Dq Li full
+or
+.Dq Li command ,
+.Nm
+can be used to enter a new password using any
+.Ar value
+for
+.Va security-password
+on the command line.
+You will be prompted by
+.Nm
+to type in the new password in this case.
+Trying to set
+.Va security-password
+when
+.Va security-mode
+is set to
+.Dq Li none
+using
+.Nm
+has no effect.
+.It Va selftest-#megs
+An integer specifying the number of megabytes of memory to test upon
+power-up when
+.Va diag-switch?
+is set to
+.Dq Li false .
+Default:
+.Dq Li 1 .
+.It Va shutdown-temperature
+Temperature at which the ASM issues an over-temperature shutdown.
+Default: system-dependent.
+.It Va silent-mode
+If set to
+.Dq Li true ,
+memory test messages will not be displayed at power-up.
+Default:
+.Dq Li false .
+.It Va sunmon-compat?
+If set to
+.Dq Li true ,
+the old bootROM interface will be used while in the boot monitor,
+rather than the OpenBoot PROM interface.
+Default:
+.Dq Li false .
+.It Va system-board-date
+Manufacturing date of the system board.
+Default: system-dependent.
+.It Va system-board-serial#
+Serial number of the system board.
+Default: system-dependent.
+.It Va tpe-link-test?
+Enable link test on 10baseT and 100baseTX Ethernet devices.
+Default:
+.Dq Li true .
+.It Va ttya-mode
+A string of five comma separated fields in the format
+.Dq Li 9600,8,n,1,- .
+The first field is the baud rate.
+The second field is the number of data bits.
+The third field is the parity; acceptable values for parity are
+.Ql n
+(none),
+.Ql e
+(even),
+.Ql o
+(odd),
+.Ql m
+(mark), and
+.Ql s
+(space).
+The fourth field is the number of stop bits.
+The fifth field is the
+.Dq handshake
+field; acceptable values are
+.Ql -
+(none),
+.Ql h
+(RTS/CTS), and
+.Ql s
+(Xon/Xoff).
+Default:
+.Dq Li 9600,8,n,1,- .
+.It Va ttya-ignore-cd
+If set to
+.Dq Li true ,
+the system will ignore carrier detect.
+Default:
+.Dq Li true .
+.It Va ttya-rts-dtr-off
+If set to
+.Dq Li true ,
+the system will ignore RTS/DTR.
+Default:
+.Dq Li false .
+.It Va ttyb-mode
+Like
+.Va ttya-mode ,
+but for ttyb.
+Default:
+.Dq Li 9600,8,n,1,- .
+.It Va ttyb-ignore-cd
+Like
+.Va ttya-ignore-cd ,
+but for ttyb.
+Default:
+.Dq Li true .
+.It Va ttyb-rts-dtr-off
+Like
+.Va ttya-rts-dtr-off ,
+but for ttyb.
+Default:
+.Dq Li false .
+.It Va use-boot-table?
+Use boot table defined by the OEM.
+Default: system-dependent.
+.It Va use-nvramrc?
+If set to
+.Dq Li true ,
+the script stored in
+.Va nvramrc
+will be executed during start-up.
+Default:
+.Dq Li false .
+.It Va warning-temperature
+Temperature at which the ASM issues an over-temperature warning.
+Default: system-dependent.
+.It Va watchdog-enable?
+Enables or disables the system watchdog timer.
+Default:
+.Dq Li false .
+.It Va watchdog-reboot?
+If set to
+.Dq Li true ,
+the system will reboot upon terminal count of the system watchdog timer.
+If set to
+.Dq Li false ,
+the system will fall into the boot monitor.
+Default:
+.Dq Li false .
+.It Va watchdog-timeout
+Expiry limit for the system watchdog timer.
+Range and unit depend on the system model.
+Default: system-dependent.
+.El
+.Sh EXAMPLES
+Print all available configuration variables and their current values:
+.Pp
+.Dl "eeprom -a"
+.Pp
+Print the current value of the
+.Va local-mac-address?
+variable:
+.Pp
+.Dl "eeprom local-mac-address\e?"
+.Pp
+Set the value of the
+.Va local-mac-address?
+variable to
+.Dq Li true :
+.Pp
+.Dl "eeprom local-mac-address\e?=true"
+.Pp
+Note that the
+.Ql \e
+in the above examples is used to keep the shell from interpreting the
+.Ql \&? .
+.Pp
+Write an image to the
+.Va oem-logo
+variable:
+.Pp
+.Dl "eeprom oem-logo=/path/to/image.raw"
+.Pp
+Remove the image from the
+.Va oem-logo
+variable again:
+.Pp
+.Dl "eeprom oem-logo="
+.Pp
+Set the value of the
+.Va security-mode
+variable to
+.Dq Li full ,
+and set the password:
+.Bd -literal -offset indent
+eeprom security-mode=full
+New password:
+Retype new password:
+.Ed
+.Pp
+Remember that the maximum length for the password is 8 characters.
+All characters exceeding this length will be ignored.
+.Pp
+Set a new password when the
+.Va security-mode
+variable is set to
+.Dq Li command
+or
+.Dq Li full :
+.Bd -literal -offset indent
+eeprom security-password=
+New password:
+Retype new password:
+.Ed
+.Sh SEE ALSO
+.Xr dc 4 ,
+.Xr gem 4 ,
+.Xr hme 4 ,
+.Xr ofwdump 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Bx 4.4 .
+It was adopted from there by
+.Fx 2.0 .
+The
+.Nm
+utility was removed from
+.Fx
+again after
+.Fx 2.1.7
+because the utility was unused at that time.
+The present implementation of the
+.Nm
+utility first appeared in
+.Fx 5.3 .
+It is inspired by the
+.Nx
+.Xr eeprom 8
+and SunOS/Solaris
+.Xr eeprom 1M
+utilities.
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility uses base code from the
+.Nx
+version written by
+.An Jason R. Thorpe .
+The handlers for the Open Firmware
+.Pa /options
+node were written by
+.An Marius Strobl Aq Mt marius@FreeBSD.org .
+The code for accessing the Open Firmware device tree is shared with the
+.Xr ofwdump 8
+utility written by
+.An Thomas Moestl Aq Mt tmm@FreeBSD.org .
+.Sh BUGS
+Currently,
+.Nm
+only supports systems equipped with Open Firmware and is only tested on Sun
+Microsystems sun4u machines.
diff --git a/usr.sbin/eeprom/eeprom.c b/usr.sbin/eeprom/eeprom.c
new file mode 100644
index 0000000..30c1a93
--- /dev/null
+++ b/usr.sbin/eeprom/eeprom.c
@@ -0,0 +1,146 @@
+/*-
+ * Copyright (c) 1996 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Jason R. Thorpe.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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.
+ *
+ * from: NetBSD: main.c,v 1.15 2001/02/19 23:22:42 cgd Exp
+ */
+
+#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 "ofw_options.h"
+
+static int action(char *);
+static void dump_config(void);
+static void usage(void);
+
+static void
+usage(void)
+{
+
+ fprintf(stderr,
+ "usage: eeprom -a\n"
+ " eeprom [-] name[=value] ...\n");
+ exit(EX_USAGE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int do_stdin, opt;
+ int aflag, rv;
+ char *cp;
+ char line[BUFSIZ];
+
+ aflag = do_stdin = 0;
+ rv = EX_OK;
+ while ((opt = getopt(argc, argv, "-a")) != -1) {
+ switch (opt) {
+ case '-':
+ if (aflag)
+ usage();
+ do_stdin = 1;
+ break;
+ case 'a':
+ if (do_stdin)
+ usage();
+ aflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (aflag) {
+ if (argc != 0)
+ usage();
+ dump_config();
+ } else {
+ if (do_stdin) {
+ while (fgets(line, BUFSIZ, stdin) != NULL &&
+ rv == EX_OK) {
+ if (line[0] == '\n')
+ continue;
+ if ((cp = strrchr(line, '\n')) != NULL)
+ *cp = '\0';
+ rv = action(line);
+ }
+ if (ferror(stdin))
+ err(EX_NOINPUT, "stdin");
+ } else {
+ if (argc == 0)
+ usage();
+ while (argc && rv == EX_OK) {
+ rv = action(*argv);
+ ++argv;
+ --argc;
+ }
+ }
+ }
+ return (rv);
+}
+
+static int
+action(char *line)
+{
+ int rv;
+ char *keyword, *arg;
+
+ keyword = strdup(line);
+ if (keyword == NULL)
+ err(EX_OSERR, "malloc() failed");
+ if ((arg = strrchr(keyword, '=')) != NULL)
+ *arg++ = '\0';
+ switch (rv = ofwo_action(keyword, arg)) {
+ case EX_UNAVAILABLE:
+ warnx("nothing available for '%s'.", keyword);
+ break;
+ case EX_DATAERR:
+ warnx("invalid value '%s' for '%s'.", arg, keyword);
+ break;
+ }
+ free(keyword);
+ return(rv);
+}
+
+static void
+dump_config(void)
+{
+
+ ofwo_dump();
+}
diff --git a/usr.sbin/eeprom/ofw_options.c b/usr.sbin/eeprom/ofw_options.c
new file mode 100644
index 0000000..6e6aa85
--- /dev/null
+++ b/usr.sbin/eeprom/ofw_options.c
@@ -0,0 +1,310 @@
+/*-
+ * Copyright (c) 2004 Marius Strobl
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (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$");
+
+/*
+ * Handlers for Open Firmware /options node.
+ */
+
+#include <sys/types.h>
+
+#include <dev/ofw/openfirm.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <readpassphrase.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "ofw_options.h"
+#include "ofw_util.h"
+
+#define OFWO_LOGO 512
+#define OFWO_MAXPROP 31
+#define OFWO_MAXPWD 8
+
+struct ofwo_extabent {
+ const char *ex_prop;
+ int (*ex_handler)(const struct ofwo_extabent *, int,
+ const void *, int, const char *);
+};
+
+static int ofwo_oemlogo(const struct ofwo_extabent *, int, const void *,
+ int, const char *);
+static int ofwo_secmode(const struct ofwo_extabent *, int, const void *,
+ int, const char *);
+static int ofwo_secpwd(const struct ofwo_extabent *, int, const void *,
+ int, const char *);
+
+static const struct ofwo_extabent ofwo_extab[] = {
+ { "oem-logo", ofwo_oemlogo },
+ { "security-mode", ofwo_secmode },
+ { "security-password", ofwo_secpwd },
+ { NULL, NULL }
+};
+
+static int ofwo_setpass(int);
+static int ofwo_setstr(int, const void *, int, const char *, const char *);
+
+static __inline void
+ofwo_printprop(const char *prop, const char* buf, int buflen)
+{
+
+ printf("%s: %.*s\n", prop, buflen, buf);
+}
+
+static int
+ofwo_oemlogo(const struct ofwo_extabent *exent, int fd, const void *buf,
+ int buflen, const char *val)
+{
+ int lfd;
+ char logo[OFWO_LOGO + 1];
+
+ if (val) {
+ if (val[0] == '\0')
+ ofw_setprop(fd, ofw_optnode(fd), exent->ex_prop, "", 1);
+ else {
+ if ((lfd = open(val, O_RDONLY)) == -1) {
+ warn("could not open '%s'", val);
+ return (EX_USAGE);
+ }
+ if (read(lfd, logo, OFWO_LOGO) != OFWO_LOGO ||
+ lseek(lfd, 0, SEEK_END) != OFWO_LOGO) {
+ close(lfd);
+ warnx("logo '%s' has wrong size.", val);
+ return (EX_USAGE);
+ }
+ close(lfd);
+ logo[OFWO_LOGO] = '\0';
+ if (ofw_setprop(fd, ofw_optnode(fd), exent->ex_prop,
+ logo, OFWO_LOGO + 1) != OFWO_LOGO)
+ errx(EX_IOERR, "writing logo failed.");
+ }
+ } else
+ if (buflen != 0)
+ printf("%s: <logo data>\n", exent->ex_prop);
+ else
+ ofwo_printprop(exent->ex_prop, (const char *)buf,
+ buflen);
+ return (EX_OK);
+}
+
+static int
+ofwo_secmode(const struct ofwo_extabent *exent, int fd, const void *buf,
+ int buflen, const char *val)
+{
+ int res;
+
+ if (val) {
+ if (strcmp(val, "full") == 0 || strcmp(val, "command") == 0) {
+ if ((res = ofwo_setpass(fd)) != EX_OK)
+ return (res);
+ if ((res = ofwo_setstr(fd, buf, buflen, exent->ex_prop,
+ val)) != EX_OK)
+ ofw_setprop(fd, ofw_optnode(fd),
+ "security-password", "", 1);
+ return (res);
+ }
+ if (strcmp(val, "none") == 0) {
+ ofw_setprop(fd, ofw_optnode(fd), "security-password",
+ "", 1);
+ return (ofwo_setstr(fd, buf, buflen, exent->ex_prop,
+ val));
+ }
+ return (EX_DATAERR);
+ } else
+ ofwo_printprop(exent->ex_prop, (const char *)buf, buflen);
+ return (EX_OK);
+}
+
+static int
+ofwo_secpwd(const struct ofwo_extabent *exent, int fd, const void *buf,
+ int buflen, const char *val)
+{
+ void *pbuf;
+ int len, pblen, rv;
+
+ pblen = 0;
+ rv = EX_OK;
+ pbuf = NULL;
+ if (val) {
+ len = ofw_getprop_alloc(fd, ofw_optnode(fd), "security-mode",
+ &pbuf, &pblen, 1);
+ if (len <= 0 || strncmp("none", (char *)pbuf, len) == 0) {
+ rv = EX_CONFIG;
+ warnx("no security mode set.");
+ } else if (strncmp("command", (char *)pbuf, len) == 0 ||
+ strncmp("full", (char *)pbuf, len) == 0) {
+ rv = ofwo_setpass(fd);
+ } else {
+ rv = EX_CONFIG;
+ warnx("invalid security mode.");
+ }
+ } else
+ ofwo_printprop(exent->ex_prop, (const char *)buf, buflen);
+ if (pbuf != NULL)
+ free(pbuf);
+ return (rv);
+}
+
+static int
+ofwo_setpass(int fd)
+{
+ char pwd1[OFWO_MAXPWD + 1], pwd2[OFWO_MAXPWD + 1];
+
+ if (readpassphrase("New password:", pwd1, sizeof(pwd1),
+ RPP_ECHO_OFF | RPP_REQUIRE_TTY) == NULL ||
+ readpassphrase("Retype new password:", pwd2, sizeof(pwd2),
+ RPP_ECHO_OFF | RPP_REQUIRE_TTY) == NULL)
+ errx(EX_USAGE, "failed to get password.");
+ if (strlen(pwd1) == 0) {
+ printf("Password unchanged.\n");
+ return (EX_OK);
+ }
+ if (strcmp(pwd1, pwd2) != 0) {
+ printf("Mismatch - password unchanged.\n");
+ return (EX_USAGE);
+ }
+ ofw_setprop(fd, ofw_optnode(fd), "security-password", pwd1,
+ strlen(pwd1) + 1);
+ return (EX_OK);
+}
+
+static int
+ofwo_setstr(int fd, const void *buf, int buflen, const char *prop,
+ const char *val)
+{
+ void *pbuf;
+ int len, pblen, rv;
+ phandle_t optnode;
+ char *oval;
+
+ pblen = 0;
+ rv = EX_OK;
+ pbuf = NULL;
+ optnode = ofw_optnode(fd);
+ ofw_setprop(fd, optnode, prop, val, strlen(val) + 1);
+ len = ofw_getprop_alloc(fd, optnode, prop, &pbuf, &pblen, 1);
+ if (len < 0 || strncmp(val, (char *)pbuf, len) != 0) {
+ /*
+ * The value is too long for this property and the OFW has
+ * truncated it to fit or the value is illegal and a legal
+ * one has been written instead (e.g. attempted to write
+ * "foobar" to a "true"/"false"-property) - try to recover
+ * the old value.
+ */
+ rv = EX_DATAERR;
+ if ((oval = malloc(buflen + 1)) == NULL)
+ err(EX_OSERR, "malloc() failed.");
+ strncpy(oval, buf, buflen);
+ oval[buflen] = '\0';
+ len = ofw_setprop(fd, optnode, prop, oval, buflen + 1);
+ if (len != buflen)
+ errx(EX_IOERR, "recovery of old value failed.");
+ free(oval);
+ goto out;
+ }
+ printf("%s: %.*s%s->%s%.*s\n", prop, buflen, (const char *)buf,
+ buflen > 0 ? " " : "", len > 0 ? " " : "", len, (char *)pbuf);
+out:
+ if (pbuf != NULL)
+ free(pbuf);
+ return (rv);
+}
+
+void
+ofwo_dump(void)
+{
+ void *pbuf;
+ int fd, len, nlen, pblen;
+ phandle_t optnode;
+ char prop[OFWO_MAXPROP + 1];
+ const struct ofwo_extabent *ex;
+
+ pblen = 0;
+ pbuf = NULL;
+ fd = ofw_open(O_RDONLY);
+ optnode = ofw_optnode(fd);
+ for (nlen = ofw_firstprop(fd, optnode, prop, sizeof(prop)); nlen != 0;
+ nlen = ofw_nextprop(fd, optnode, prop, prop, sizeof(prop))) {
+ len = ofw_getprop_alloc(fd, optnode, prop, &pbuf, &pblen, 1);
+ if (len < 0)
+ continue;
+ if (strcmp(prop, "name") == 0)
+ continue;
+ for (ex = ofwo_extab; ex->ex_prop != NULL; ++ex)
+ if (strcmp(ex->ex_prop, prop) == 0)
+ break;
+ if (ex->ex_prop != NULL)
+ (*ex->ex_handler)(ex, fd, pbuf, len, NULL);
+ else
+ ofwo_printprop(prop, (char *)pbuf, len);
+ }
+ if (pbuf != NULL)
+ free(pbuf);
+ ofw_close(fd);
+}
+
+int
+ofwo_action(const char *prop, const char *val)
+{
+ void *pbuf;
+ int fd, len, pblen, rv;
+ const struct ofwo_extabent *ex;
+
+ pblen = 0;
+ rv = EX_OK;
+ pbuf = NULL;
+ if (strcmp(prop, "name") == 0)
+ return (EX_UNAVAILABLE);
+ if (val)
+ fd = ofw_open(O_RDWR);
+ else
+ fd = ofw_open(O_RDONLY);
+ len = ofw_getprop_alloc(fd, ofw_optnode(fd), prop, &pbuf, &pblen, 1);
+ if (len < 0) {
+ rv = EX_UNAVAILABLE;
+ goto out;
+ }
+ for (ex = ofwo_extab; ex->ex_prop != NULL; ++ex)
+ if (strcmp(ex->ex_prop, prop) == 0)
+ break;
+ if (ex->ex_prop != NULL)
+ rv = (*ex->ex_handler)(ex, fd, pbuf, len, val);
+ else if (val)
+ rv = ofwo_setstr(fd, pbuf, len, prop, val);
+ else
+ ofwo_printprop(prop, (char *)pbuf, len);
+out:
+ if (pbuf != NULL)
+ free(pbuf);
+ ofw_close(fd);
+ return (rv);
+}
diff --git a/usr.sbin/eeprom/ofw_options.h b/usr.sbin/eeprom/ofw_options.h
new file mode 100644
index 0000000..5bc6656
--- /dev/null
+++ b/usr.sbin/eeprom/ofw_options.h
@@ -0,0 +1,34 @@
+/*-
+ * Copyright (c) 2004 Marius Strobl
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (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 OFW_OPTIONS_H
+#define OFW_OPTIONS_H
+
+void ofwo_dump(void);
+int ofwo_action(const char *prop, const char *val);
+
+#endif /* OFW_OPTIONS_H */
diff --git a/usr.sbin/etcupdate/Makefile b/usr.sbin/etcupdate/Makefile
new file mode 100644
index 0000000..155151d
--- /dev/null
+++ b/usr.sbin/etcupdate/Makefile
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+SCRIPTS=etcupdate.sh
+MAN= etcupdate.8
+
+.if ${MK_TESTS} != "no"
+SUBDIR+= tests
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/etcupdate/Makefile.depend b/usr.sbin/etcupdate/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/etcupdate/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/etcupdate/etcupdate.8 b/usr.sbin/etcupdate/etcupdate.8
new file mode 100644
index 0000000..381d4ab
--- /dev/null
+++ b/usr.sbin/etcupdate/etcupdate.8
@@ -0,0 +1,900 @@
+.\" Copyright (c) 2010-2013 Hudson River Trading LLC
+.\" Written by: John H. 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$
+.\"
+.Dd September 29, 2015
+.Dt ETCUPDATE 8
+.Os
+.Sh NAME
+.Nm etcupdate
+.Nd "manage updates to system files not updated by installworld"
+.Sh SYNOPSIS
+.Nm
+.Op Fl npBF
+.Op Fl d Ar workdir
+.Op Fl r | Fl s Ar source | Fl t Ar tarball
+.Op Fl A Ar patterns
+.Op Fl D Ar destdir
+.Op Fl I Ar patterns
+.Op Fl L Ar logfile
+.Op Fl M Ar options
+.Nm
+.Cm build
+.Op Fl B
+.Op Fl d Ar workdir
+.Op Fl s Ar source
+.Op Fl L Ar logfile
+.Op Fl M Ar options
+.Ar tarball
+.Nm
+.Cm diff
+.Op Fl d Ar workdir
+.Op Fl D Ar destdir
+.Op Fl I Ar patterns
+.Op Fl L Ar logfile
+.Nm
+.Cm extract
+.Op Fl B
+.Op Fl d Ar workdir
+.Op Fl s Ar source | Fl t Ar tarball
+.Op Fl L Ar logfile
+.Op Fl M Ar options
+.Nm
+.Cm resolve
+.Op Fl p
+.Op Fl d Ar workdir
+.Op Fl D Ar destdir
+.Op Fl L Ar logfile
+.Nm
+.Cm status
+.Op Fl d Ar workdir
+.Op Fl D Ar destdir
+.Sh DESCRIPTION
+The
+.Nm
+utility is a tool for managing updates to files that are not updated as
+part of
+.Sq make installworld
+such as files in
+.Pa /etc .
+It manages updates by doing a three-way merge of changes made to these
+files against the local versions.
+It is also designed to minimize the amount of user intervention with
+the goal of simplifying upgrades for clusters of machines.
+.Pp
+To perform a three-way merge,
+.Nm
+keeps copies of the current and previous versions of files that it manages.
+These copies are stored in two trees known as the
+.Dq current
+and
+.Dq previous
+trees.
+During a merge,
+.Nm
+compares the
+.Dq current
+and
+.Dq previous
+copies of each file to determine which changes need to be merged into the
+local version of each file.
+If a file can be updated without generating a conflict,
+.Nm
+will update the file automatically.
+If the local changes to a file conflict with the changes made to a file in
+the source tree,
+then a merge conflict is generated.
+The conflict must be resolved after the merge has finished.
+The
+.Nm
+utility will not perform a new merge until all conflicts from an earlier
+merge are resolved.
+.Sh MODES
+The
+.Nm
+utility supports several modes of operation.
+The mode is specified via an optional command argument.
+If present, the command must be the first argument on the command line.
+If a command is not specified, the default mode is used.
+.Ss Default Mode
+The default mode merges changes from the source tree to the destination
+directory.
+First,
+it updates the
+.Dq current
+and
+.Dq previous
+trees.
+Next,
+it compares the two trees merging changes into the destination directory.
+Finally,
+it displays warnings for any conditions it could not handle automatically.
+.Pp
+If the
+.Fl r
+option is not specified,
+then the first step taken is to update the
+.Dq current
+and
+.Dq previous
+trees.
+If a
+.Dq current
+tree already exists,
+then that tree is saved as the
+.Dq previous
+tree.
+An older
+.Dq previous
+tree is removed if it exists.
+By default the new
+.Dq current
+tree is built from a source tree.
+However,
+if a tarball is specified via the
+.Fl t
+option,
+then the tree is extracted from that tarball instead.
+.Pp
+Next,
+.Nm
+compares the files in the
+.Dq current
+and
+.Dq previous
+trees.
+If a file was removed from the
+.Dq current
+tree,
+then it will be removed from the destination directory only if it
+does not have any local modifications.
+If a file was added to the
+.Dq current
+tree,
+then it will be copied to the destination directory only if it
+would not clobber an existing file.
+If a file is changed in the
+.Dq current
+tree,
+then
+.Nm
+will attempt to merge the changes into the version of the file in the
+destination directory.
+If the merge encounters conflicts,
+then a version of the file with conflict markers will be saved for
+future resolution.
+If the merge does not encounter conflicts,
+then the merged version of the file will be saved in the destination
+directory.
+If
+.Nm
+is not able to safely merge in changes to a file other than a merge conflict,
+it will generate a warning.
+.Pp
+For each file that is updated a line will be output with a leading character
+to indicate the action taken.
+The possible actions follow:
+.Pp
+.Bl -tag -width "A" -compact -offset indent
+.It A
+Added
+.It C
+Conflict
+.It D
+Deleted
+.It M
+Merged
+.It U
+Updated
+.El
+.Pp
+Finally,
+if any warnings were encountered they are displayed after the merge has
+completed.
+.Pp
+Note that for certain files
+.Nm
+will perform post-install actions any time that the file is updated.
+Specifically,
+.Xr pwd_mkdb 8
+is invoked if
+.Pa /etc/master.passwd
+is changed,
+.Xr cap_mkdb 1
+is invoked to update
+.Pa /etc/login.conf.db
+if
+.Pa /etc/login.conf
+is changed,
+.Xr newaliases 1
+is invoked if
+.Pa /etc/mail/aliases
+is changed,
+.Xr services_mkdb 8
+is invoked if
+.Pa /etc/services
+is changed,
+.Xr tzsetup 8
+is invoked if
+.Pa /etc/localtime
+is changed and if
+.Fa /var/db/zoneinfo
+exists,
+and
+.Pa /etc/rc.d/motd
+is invoked if
+.Pa /etc/motd
+is changed.
+One exception is that if
+.Pa /etc/mail/aliases
+is changed and the destination directory is not the default,
+then a warning will be issued instead.
+This is due to a limitation of the
+.Xr newaliases 1
+command.
+Similarly,
+if
+.Pa /etc/motd
+is changed and the destination directory is not the default,
+then
+.Pa /etc/rc.d/motd
+will not be executed due to a limitation of that script.
+In this case no warning is issued as the result of
+.Pa /etc/rc.d/motd
+is merely cosmetic and will be corrected on the next reboot.
+.Ss Build Mode
+The
+.Cm build
+mode is used to build a tarball that contains a snapshot of a
+.Dq current
+tree.
+This tarball can be used by the default and extract modes.
+Using a tarball can allow
+.Nm
+to perform a merge without requiring a source tree that matches the
+currently installed world.
+The
+.Fa tarball
+argument specifies the name of the file to create.
+The file will be a
+.Xr tar 5
+file compressed with
+.Xr bzip2 1 .
+.Ss Diff Mode
+The
+.Cm diff
+mode compares the versions of files in the destination directory to the
+.Dq current
+tree and generates a unified format diff of the changes.
+This can be used to determine which files have been locally modified and how.
+Note that
+.Nm
+does not manage files that are not maintained in the source tree such as
+.Pa /etc/fstab
+and
+.Pa /etc/rc.conf .
+.Ss Extract Mode
+The
+.Cm extract
+mode generates a new
+.Dq current
+tree.
+Unlike the default mode,
+it does not save any existing
+.Dq current
+tree and does not modify any existing
+.Dq previous
+tree.
+The new
+.Dq current
+tree can either be built from a source tree or extracted from a tarball.
+.Ss Resolve Mode
+The
+.Cm resolve
+mode is used to resolve any conflicts encountered during a merge.
+In this mode,
+.Nm
+iterates over any existing conflicts prompting the user for actions to take
+on each conflicted file.
+For each file, the following actions are available:
+.Pp
+.Bl -tag -width "(tf) theirs-full" -compact
+.It (p) postpone
+Ignore this conflict for now.
+.It (df) diff-full
+Show all changes made to the merged file as a unified diff.
+.It (e) edit
+Change the merged file in an editor.
+.It (r) resolved
+Install the merged version of the file into the destination directory.
+.It (mf) mine-full
+Use the version of the file in the destination directory and ignore any
+changes made to the file in the
+.Dq current
+tree.
+.It (tf) theirs-full
+Use the version of the file from the
+.Dq current
+tree and discard any local changes made to the file.
+.It (h) help
+Display the list of commands.
+.El
+.Ss Status Mode
+The
+.Cm status
+mode shows a summary of the results of the most recent merge.
+First it lists any files for which there are unresolved conflicts.
+Next it lists any warnings generated during the last merge.
+If the last merge did not generate any conflicts or warnings,
+then nothing will be output.
+.Sh OPTIONS
+The following options are available.
+Note that most options do not apply to all modes.
+.Bl -tag -width ".Fl A Ar patterns"
+.It Fl A Ar patterns
+Always install the new version of any files that match any of the patterns
+listed in
+.Ar patterns .
+Each pattern is evaluated as an
+.Xr sh 1
+shell pattern.
+This option may be specified multiple times to specify multiple patterns.
+Multiple space-separated patterns may also be specified in a single
+option.
+Note that ignored files specified via the
+.Ev IGNORE_FILES
+variable or the
+.Fl I
+option will not be installed.
+.It Fl B
+Do not build generated files in a private object tree.
+Instead,
+reuse the generated files from a previously built object tree that matches
+the source tree.
+This can be useful to avoid gratuitous conflicts in
+.Xr sendmail 8
+configuration
+files when bootstrapping.
+It can also be useful for building a tarball that matches a specific
+world build.
+.It Fl D Ar destdir
+Specify an alternate destination directory as the target of a merge.
+This is analogous to the
+.Dv DESTDIR
+variable used with
+.Sq make installworld .
+The default destination directory is an empty string which results in
+merges updating
+.Pa /etc
+on the local machine.
+.It Fl d Ar workdir
+Specify an alternate directory to use as the work directory.
+The work directory is used to store the
+.Dq current
+and
+.Dq previous
+trees as well as unresolved conflicts.
+The default work directory is
+.Pa <destdir>/var/db/etcupdate .
+.It Fl F
+Ignore changes in the FreeBSD ID string when comparing files in the
+destination directory to files in either of the
+.Dq current
+or
+.Dq previous
+trees.
+In
+.Cm diff
+mode,
+this reduces noise due to FreeBSD ID string changes in the output.
+During an update this can simplify handling for harmless conflicts caused
+by FreeBSD ID string changes.
+.Pp
+Specifically,
+if a file in the destination directory is identical to the same file in the
+.Dq previous
+tree modulo the FreeBSD ID string,
+then the file is treated as if it was unmodified and the
+.Dq current
+version of the file will be installed.
+Similarly,
+if a file in the destination directory is identical to the same file in the
+.Dq current
+tree modulo the FreeBSD ID string,
+then the
+.Dq current
+version of the file will be installed to update the ID string.
+If the
+.Dq previous
+and
+.Dq current
+versions of the file are identical,
+then
+.Nm
+will not change the file in the destination directory.
+.Pp
+Due to limitations in the
+.Xr diff 1
+command,
+this option may not have an effect if there are other changes in a file that
+are close to the FreeBSD ID string.
+.It Fl I Ar patterns
+Ignore any files that match any of the patterns listed in
+.Ar patterns .
+No warnings or other messages will be generated for those files during a
+merge.
+Each pattern is evaluated as an
+.Xr sh 1
+shell pattern.
+This option may be specified multiple times to specify multiple patterns.
+Multiple space-separated patterns may also be specified in a single
+option.
+.It Fl L Ar logfile
+Specify an alternate path for the log file.
+The
+.Nm
+utility logs each command that it invokes along with the standard output
+and standard error to this file.
+By default the log file is stored in a file named
+.Pa log
+in the work directory.
+.It Fl M Ar options
+Pass
+.Ar options
+as additional parameters to
+.Xr make 1
+when building a
+.Dq current
+tree.
+This can be used for to set the
+.Dv TARGET
+or
+.Dv TARGET_ARCH
+variables for a cross-build.
+.It Fl n
+Enable
+.Dq dry-run
+mode.
+Do not merge any changes to the destination directory.
+Instead,
+report what actions would be taken during a merge.
+Note that the existing
+.Dq current
+and
+.Dq previous
+trees will not be changed.
+If the
+.Fl r
+option is not specified,
+then a temporary
+.Dq current
+tree will be extracted to perform the comparison.
+.It Fl p
+Enable
+.Dq pre-world
+mode.
+Only merge changes to files that are necessary to successfully run
+.Sq make installworld
+or
+.Sq make installkernel .
+When this flag is enabled,
+the existing
+.Dq current
+and
+.Dq previous
+trees are left alone.
+Instead,
+a temporary tree is populated with the necessary files.
+This temporary tree is compared against the
+.Dq current
+tree.
+This allows a normal update to be run after
+.Sq make installworld
+has completed.
+Any conflicts generated during a
+.Dq pre-world
+update should be resolved by a
+.Dq pre-world
+.Cm resolve .
+.It Fl r
+Do not update the
+.Dq current
+and
+.Dq previous
+trees during a merge.
+This can be used to
+.Dq re-run
+a previous merge operation.
+.It Fl s Ar source
+Specify an alternate source tree to use when building or extracting a
+.Dq current
+tree.
+The default source tree is
+.Pa /usr/src .
+.It Fl t Ar tarball
+Extract a new
+.Dq current
+tree from a tarball previously generated by the
+.Cm build
+command rather than building the tree from a source tree.
+.El
+.Sh CONFIG FILE
+The
+.Nm
+utility can also be configured by setting variables in an optional
+configuration file named
+.Pa /etc/etcupdate.conf .
+Note that command line options override settings in the configuration file.
+The configuration file is executed by
+.Xr sh 1 ,
+so it uses that syntax to set configuration variables.
+The following variables can be set:
+.Bl -tag -width ".Ev ALWAYS_INSTALL"
+.It Ev ALWAYS_INSTALL
+Always install files that match any of the patterns listed in this variable
+similar to the
+.Fl A
+option.
+.It Ev DESTDIR
+Specify an alternate destination directory similar to the
+.Fl D
+option.
+.It Ev EDITOR
+Specify a program to edit merge conflicts.
+.It Ev FREEBSD_ID
+Ignore changes in the FreeBSD ID string similar to the
+.Fl F
+option.
+This is enabled by setting the variable to a non-empty value.
+.It Ev IGNORE_FILES
+Ignore files that match any of the patterns listed in this variable
+similar to the
+.Fl I
+option.
+.It Ev LOGFILE
+Specify an alternate path for the log file similar to the
+.Fl L
+option.
+.It Ev MAKE_OPTIONS
+Pass additional options to
+.Xr make 1
+when building a
+.Dq current
+tree similar to the
+.Fl M
+option.
+.It Ev SRCDIR
+Specify an alternate source tree similar to the
+.Fl s
+option.
+.It Ev WORKDIR
+Specify an alternate work directory similar to the
+.Fl d
+option.
+.El
+.Sh ENVIRONMENT
+The
+.Nm
+utility uses the program identified in the
+.Ev EDITOR
+environment variable to edit merge conflicts.
+If
+.Ev EDITOR
+is not set,
+.Xr vi 1
+is used as the default editor.
+.Sh FILES
+.Bl -tag -width ".Pa /var/db/etcupdate/log" -compact
+.It Pa /etc/etcupdate.conf
+Optional config file.
+.It Pa /var/db/etcupdate
+Default work directory used to store trees and other data.
+.It Pa /var/db/etcupdate/log
+Default log file.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+To compare the files in
+.Pa /etc
+with the stock versions:
+.Pp
+.Dl "etcupdate diff"
+.Pp
+To merge changes after an upgrade via the buildworld and installworld process:
+.Pp
+.Dl "etcupdate"
+.Pp
+To resolve any conflicts generated during a merge:
+.Pp
+.Dl "etcupdate resolve"
+.Ss Bootstrapping
+The
+.Nm
+utility may need to be bootstrapped before it can be used.
+The
+.Cm diff
+command will fail with an error about a missing reference tree if
+bootstrapping is needed.
+.Pp
+Bootstrapping
+.Nm
+requires a source tree that matches the currently installed world.
+The easiest way to ensure this is to bootstrap
+.Nm
+before updating the source tree to start the next world upgrade cycle.
+First,
+generate a reference tree:
+.Pp
+.Dl "etcupdate extract"
+.Pp
+Second,
+use the
+.Cm diff
+command to compare the reference tree to your current files in
+.Pa /etc .
+Undesired differences should be removed using an editor,
+.Xr patch 1 ,
+or by copying files from the reference tree
+.Po
+located at
+.Pa /var/db/etcupdate/current
+by default
+.Pc
+.
+.Pp
+If the tree at
+.Pa /usr/src
+is already newer than the currently installed world,
+a new tree matching the currently installed world can be checked out to
+a temporary location.
+The reference tree for
+.Nm
+can then be generated via:
+.Pp
+.Dl "etcupdate extract -s /path/to/tree"
+.Pp
+The
+.Cm diff
+command can be used as above to remove undesired differences.
+Afterwards,
+the changes in the tree at
+.Pa /usr/src
+can be merged via a regular merge.
+.Sh DIAGNOSTICS
+The following warning messages may be generated during a merge.
+Note that several of these warnings cover obscure cases that should occur
+rarely if at all in practice.
+For example,
+if a file changes from a file to a directory in the
+.Dq current
+tree
+and the file was modified in the destination directory,
+then a warning will be triggered.
+In general,
+when a warning references a pathname,
+the corresponding file in the destination directory is not changed by a
+merge operation.
+.Bl -diag
+.It "Directory mismatch: <path> (<type>)"
+An attempt was made to create a directory at
+.Pa path
+but an existing file of type
+.Dq type
+already exists for that path name.
+.It "Modified link changed: <file> (<old> became <new>)"
+The target of a symbolic link named
+.Pa file
+was changed from
+.Dq old
+to
+.Dq new
+in the
+.Dq current
+tree.
+The symbolic link has been modified to point to a target that is neither
+.Dq old
+nor
+.Dq new
+in the destination directory.
+.It "Modified mismatch: <file> (<new> vs <dest>)"
+A file named
+.Pa file
+of type
+.Dq new
+was modified in the
+.Dq current
+tree,
+but the file exists as a different type
+.Dq dest
+in the destination directory.
+.It "Modified <type> changed: <file> (<old> became <new>)"
+A file named
+.Pa file
+changed type from
+.Dq old
+in the
+.Dq previous
+tree to type
+.Dq new
+in the
+.Dq current
+tree.
+The file in the destination directory of type
+.Dq type
+has been modified,
+so it could not be merged automatically.
+.It "Modified <type> remains: <file>"
+The file of type
+.Dq type
+named
+.Pa file
+has been removed from the
+.Dq current
+tree,
+but it has been locally modified.
+The modified version of the file remains in the destination directory.
+.It "Needs update: /etc/localtime (required manual update via tzsetup(1))"
+The
+.Fa /var/db/zoneinfo
+file does not exist,
+so
+.Nm
+was not able to refresh
+.Fa /etc/localtime
+from its source file in
+.Fa /usr/share/zoneinfo .
+Running
+.Xr tzsetup 1
+will both refresh
+.Fa /etc/localtime
+and generate
+.Fa /var/db/zoneinfo
+permitting future updates to refresh
+.Fa /etc/localtime
+automatically.
+.It "Needs update: /etc/mail/aliases.db (required manual update via newaliases(1))"
+The file
+.Pa /etc/mail/aliases
+was updated during a merge with a non-empty destination directory.
+Due to a limitation of the
+.Xr newaliases 1
+command,
+.Nm
+was not able to automatically update the corresponding aliases database.
+.It "New file mismatch: <file> (<new> vs <dest>)"
+A new file named
+.Pa file
+of type
+.Dq new
+has been added to the
+.Dq current
+tree.
+A file of that name already exists in the destination directory,
+but it is of a different type
+.Dq dest .
+.It "New link conflict: <file> (<new> vs <dest>)"
+A symbolic link named
+.Pa file
+has been added to the
+.Dq current
+tree that links to
+.Dq new .
+A symbolic link of the same name already exists in the destination
+directory,
+but it links to a different target
+.Dq dest .
+.It "Non-empty directory remains: <file>"
+The directory
+.Pa file
+was removed from the
+.Dq current
+tree,
+but it contains additional files in the destination directory.
+These additional files as well as the directory remain.
+.It "Remove mismatch: <file> (<old> became <new>)"
+A file named
+.Pa file
+changed from type
+.Dq old
+in the
+.Dq previous
+tree to type
+.Dq new
+in the
+.Dq current
+tree,
+but it has been removed in the destination directory.
+.It "Removed file changed: <file>"
+A file named
+.Pa file
+was modified in the
+.Dq current
+tree,
+but it has been removed in the destination directory.
+.It "Removed link changed: <file> (<old> became <new>)"
+The target of a symbolic link named
+.Pa file
+was changed from
+.Dq old
+to
+.Dq new
+in the
+.Dq current
+tree,
+but it has been removed in the destination directory.
+.El
+.Sh SEE ALSO
+.Xr cap_mkdb 1 ,
+.Xr diff 1 ,
+.Xr make 1 ,
+.Xr newaliases 1 ,
+.Xr sh 1 ,
+.Xr pwd_mkdb 8 ,
+.Xr services_mkdb 8 ,
+.Xr tzsetup 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 10.0 .
+.Sh AUTHORS
+The
+.Nm
+utility was written by
+.An John Baldwin Aq Mt jhb@FreeBSD.org .
+.Sh BUGS
+Rerunning a merge does not automatically delete conflicts left over from a
+previous merge.
+Any conflicts must be resolved before the merge can be rerun.
+It it is not clear if this is a feature or a bug.
+.Pp
+There is no way to easily automate conflict resolution for specific files.
+For example, one can imagine a syntax along the lines of
+.Pp
+.Dl "etcupdate resolve tf /some/file"
+.Pp
+to resolve a specific conflict in an automated fashion.
+.Pp
+It might be nice to have something like a
+.Sq revert
+command to replace a locally modified version of a file with the stock
+version of the file.
+For example:
+.Pp
+.Dl "etcupdate revert /etc/mail/freebsd.cf"
+.Pp
+Bootstrapping
+.Nm
+often results in gratuitous diffs in
+.Pa /etc/mail/*.cf
+that cause conflicts in the first merge.
+If an object tree that matches the source tree is present when bootstrapping,
+then passing the
+.Fl B
+flag to the
+.Cm extract
+command can work around this.
diff --git a/usr.sbin/etcupdate/etcupdate.sh b/usr.sbin/etcupdate/etcupdate.sh
new file mode 100755
index 0000000..6be57b6
--- /dev/null
+++ b/usr.sbin/etcupdate/etcupdate.sh
@@ -0,0 +1,1795 @@
+#!/bin/sh
+#
+# Copyright (c) 2010-2013 Hudson River Trading LLC
+# Written by: John H. 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$
+
+# This is a tool to manage updating files that are not updated as part
+# of 'make installworld' such as files in /etc. Unlike other tools,
+# this one is specifically tailored to assisting with mass upgrades.
+# To that end it does not require user intervention while running.
+#
+# Theory of operation:
+#
+# The most reliable way to update changes to files that have local
+# modifications is to perform a three-way merge between the original
+# unmodified file, the new version of the file, and the modified file.
+# This requires having all three versions of the file available when
+# performing an update.
+#
+# To that end, etcupdate uses a strategy where the current unmodified
+# tree is kept in WORKDIR/current and the previous unmodified tree is
+# kept in WORKDIR/old. When performing a merge, a new tree is built
+# if needed and then the changes are merged into DESTDIR. Any files
+# with unresolved conflicts after the merge are left in a tree rooted
+# at WORKDIR/conflicts.
+#
+# To provide extra flexibility, etcupdate can also build tarballs of
+# root trees that can later be used. It can also use a tarball as the
+# source of a new tree instead of building it from /usr/src.
+
+# Global settings. These can be adjusted by config files and in some
+# cases by command line options.
+
+# TODO:
+# - automatable conflict resolution
+# - a 'revert' command to make a file "stock"
+
+usage()
+{
+ cat <<EOF
+usage: etcupdate [-npBF] [-d workdir] [-r | -s source | -t tarball]
+ [-A patterns] [-D destdir] [-I patterns] [-L logfile]
+ [-M options]
+ etcupdate build [-B] [-d workdir] [-s source] [-L logfile] [-M options]
+ <tarball>
+ etcupdate diff [-d workdir] [-D destdir] [-I patterns] [-L logfile]
+ etcupdate extract [-B] [-d workdir] [-s source | -t tarball] [-L logfile]
+ [-M options]
+ etcupdate resolve [-p] [-d workdir] [-D destdir] [-L logfile]
+ etcupdate status [-d workdir] [-D destdir]
+EOF
+ exit 1
+}
+
+# Used to write a message prepended with '>>>' to the logfile.
+log()
+{
+ echo ">>>" "$@" >&3
+}
+
+# Used for assertion conditions that should never happen.
+panic()
+{
+ echo "PANIC:" "$@"
+ exit 10
+}
+
+# Used to write a warning message. These are saved to the WARNINGS
+# file with " " prepended.
+warn()
+{
+ echo -n " " >> $WARNINGS
+ echo "$@" >> $WARNINGS
+}
+
+# Output a horizontal rule using the passed-in character. Matches the
+# length used for Index lines in CVS and SVN diffs.
+#
+# $1 - character
+rule()
+{
+ jot -b "$1" -s "" 67
+}
+
+# Output a text description of a specified file's type.
+#
+# $1 - file pathname.
+file_type()
+{
+ stat -f "%HT" $1 | tr "[:upper:]" "[:lower:]"
+}
+
+# Returns true (0) if a file exists
+#
+# $1 - file pathname.
+exists()
+{
+ [ -e $1 -o -L $1 ]
+}
+
+# Returns true (0) if a file should be ignored, false otherwise.
+#
+# $1 - file pathname
+ignore()
+{
+ local pattern -
+
+ set -o noglob
+ for pattern in $IGNORE_FILES; do
+ set +o noglob
+ case $1 in
+ $pattern)
+ return 0
+ ;;
+ esac
+ set -o noglob
+ done
+
+ # Ignore /.cshrc and /.profile if they are hardlinked to the
+ # same file in /root. This ensures we only compare those
+ # files once in that case.
+ case $1 in
+ /.cshrc|/.profile)
+ if [ ${DESTDIR}$1 -ef ${DESTDIR}/root$1 ]; then
+ return 0
+ fi
+ ;;
+ *)
+ ;;
+ esac
+
+ return 1
+}
+
+# Returns true (0) if the new version of a file should always be
+# installed rather than attempting to do a merge.
+#
+# $1 - file pathname
+always_install()
+{
+ local pattern -
+
+ set -o noglob
+ for pattern in $ALWAYS_INSTALL; do
+ set +o noglob
+ case $1 in
+ $pattern)
+ return 0
+ ;;
+ esac
+ set -o noglob
+ done
+
+ return 1
+}
+
+# Build a new tree
+#
+# $1 - directory to store new tree in
+build_tree()
+{
+ local destdir dir file make
+
+ make="make $MAKE_OPTIONS"
+
+ log "Building tree at $1 with $make"
+ mkdir -p $1/usr/obj >&3 2>&1
+ destdir=`realpath $1`
+
+ if [ -n "$preworld" ]; then
+ # Build a limited tree that only contains files that are
+ # crucial to installworld.
+ for file in $PREWORLD_FILES; do
+ dir=`dirname /$file`
+ mkdir -p $1/$dir >&3 2>&1 || return 1
+ cp -p $SRCDIR/$file $1/$file || return 1
+ done
+ elif ! [ -n "$nobuild" ]; then
+ (cd $SRCDIR; $make DESTDIR=$destdir distrib-dirs &&
+ MAKEOBJDIRPREFIX=$destdir/usr/obj $make _obj SUBDIR_OVERRIDE=etc &&
+ MAKEOBJDIRPREFIX=$destdir/usr/obj $make everything SUBDIR_OVERRIDE=etc &&
+ MAKEOBJDIRPREFIX=$destdir/usr/obj $make DESTDIR=$destdir distribution) \
+ >&3 2>&1 || return 1
+ else
+ (cd $SRCDIR; $make DESTDIR=$destdir distrib-dirs &&
+ $make DESTDIR=$destdir distribution) >&3 2>&1 || return 1
+ fi
+ chflags -R noschg $1 >&3 2>&1 || return 1
+ rm -rf $1/usr/obj >&3 2>&1 || return 1
+
+ # Purge auto-generated files. Only the source files need to
+ # be updated after which these files are regenerated.
+ rm -f $1/etc/*.db $1/etc/passwd $1/var/db/services.db >&3 2>&1 || \
+ return 1
+
+ # Remove empty files. These just clutter the output of 'diff'.
+ find $1 -type f -size 0 -delete >&3 2>&1 || return 1
+
+ # Trim empty directories.
+ find -d $1 -type d -empty -delete >&3 2>&1 || return 1
+ return 0
+}
+
+# Generate a new NEWTREE tree. If tarball is set, then the tree is
+# extracted from the tarball. Otherwise the tree is built from a
+# source tree.
+extract_tree()
+{
+ local files
+
+ # If we have a tarball, extract that into the new directory.
+ if [ -n "$tarball" ]; then
+ files=
+ if [ -n "$preworld" ]; then
+ files="$PREWORLD_FILES"
+ fi
+ if ! (mkdir -p $NEWTREE && tar xf $tarball -C $NEWTREE $files) \
+ >&3 2>&1; then
+ echo "Failed to extract new tree."
+ remove_tree $NEWTREE
+ exit 1
+ fi
+ else
+ if ! build_tree $NEWTREE; then
+ echo "Failed to build new tree."
+ remove_tree $NEWTREE
+ exit 1
+ fi
+ fi
+}
+
+# Forcefully remove a tree. Returns true (0) if the operation succeeds.
+#
+# $1 - path to tree
+remove_tree()
+{
+
+ rm -rf $1 >&3 2>&1
+ if [ -e $1 ]; then
+ chflags -R noschg $1 >&3 2>&1
+ rm -rf $1 >&3 2>&1
+ fi
+ [ ! -e $1 ]
+}
+
+# Return values for compare()
+COMPARE_EQUAL=0
+COMPARE_ONLYFIRST=1
+COMPARE_ONLYSECOND=2
+COMPARE_DIFFTYPE=3
+COMPARE_DIFFLINKS=4
+COMPARE_DIFFFILES=5
+
+# Compare two files/directories/symlinks. Note that this does not
+# recurse into subdirectories. Instead, if two nodes are both
+# directories, they are assumed to be equivalent.
+#
+# Returns true (0) if the nodes are identical. If only one of the two
+# nodes are present, return one of the COMPARE_ONLY* constants. If
+# the nodes are different, return one of the COMPARE_DIFF* constants
+# to indicate the type of difference.
+#
+# $1 - first node
+# $2 - second node
+compare()
+{
+ local first second
+
+ # If the first node doesn't exist, then check for the second
+ # node. Note that -e will fail for a symbolic link that
+ # points to a missing target.
+ if ! exists $1; then
+ if exists $2; then
+ return $COMPARE_ONLYSECOND
+ else
+ return $COMPARE_EQUAL
+ fi
+ elif ! exists $2; then
+ return $COMPARE_ONLYFIRST
+ fi
+
+ # If the two nodes are different file types fail.
+ first=`stat -f "%Hp" $1`
+ second=`stat -f "%Hp" $2`
+ if [ "$first" != "$second" ]; then
+ return $COMPARE_DIFFTYPE
+ fi
+
+ # If both are symlinks, compare the link values.
+ if [ -L $1 ]; then
+ first=`readlink $1`
+ second=`readlink $2`
+ if [ "$first" = "$second" ]; then
+ return $COMPARE_EQUAL
+ else
+ return $COMPARE_DIFFLINKS
+ fi
+ fi
+
+ # If both are files, compare the file contents.
+ if [ -f $1 ]; then
+ if cmp -s $1 $2; then
+ return $COMPARE_EQUAL
+ else
+ return $COMPARE_DIFFFILES
+ fi
+ fi
+
+ # As long as the two nodes are the same type of file, consider
+ # them equivalent.
+ return $COMPARE_EQUAL
+}
+
+# Returns true (0) if the only difference between two regular files is a
+# change in the FreeBSD ID string.
+#
+# $1 - path of first file
+# $2 - path of second file
+fbsdid_only()
+{
+
+ diff -qI '\$FreeBSD.*\$' $1 $2 >/dev/null 2>&1
+}
+
+# This is a wrapper around compare that will return COMPARE_EQUAL if
+# the only difference between two regular files is a change in the
+# FreeBSD ID string. It only makes this adjustment if the -F flag has
+# been specified.
+#
+# $1 - first node
+# $2 - second node
+compare_fbsdid()
+{
+ local cmp
+
+ compare $1 $2
+ cmp=$?
+
+ if [ -n "$FREEBSD_ID" -a "$cmp" -eq $COMPARE_DIFFFILES ] && \
+ fbsdid_only $1 $2; then
+ return $COMPARE_EQUAL
+ fi
+
+ return $cmp
+}
+
+# Returns true (0) if a directory is empty.
+#
+# $1 - pathname of the directory to check
+empty_dir()
+{
+ local contents
+
+ contents=`ls -A $1`
+ [ -z "$contents" ]
+}
+
+# Returns true (0) if one directories contents are a subset of the
+# other. This will recurse to handle subdirectories and compares
+# individual files in the trees. Its purpose is to quiet spurious
+# directory warnings for dryrun invocations.
+#
+# $1 - first directory (sub)
+# $2 - second directory (super)
+dir_subset()
+{
+ local contents file
+
+ if ! [ -d $1 -a -d $2 ]; then
+ return 1
+ fi
+
+ # Ignore files that are present in the second directory but not
+ # in the first.
+ contents=`ls -A $1`
+ for file in $contents; do
+ if ! compare $1/$file $2/$file; then
+ return 1
+ fi
+
+ if [ -d $1/$file ]; then
+ if ! dir_subset $1/$file $2/$file; then
+ return 1
+ fi
+ fi
+ done
+ return 0
+}
+
+# Returns true (0) if a directory in the destination tree is empty.
+# If this is a dryrun, then this returns true as long as the contents
+# of the directory are a subset of the contents in the old tree
+# (meaning that the directory would be empty in a non-dryrun when this
+# was invoked) to quiet spurious warnings.
+#
+# $1 - pathname of the directory to check relative to DESTDIR.
+empty_destdir()
+{
+
+ if [ -n "$dryrun" ]; then
+ dir_subset $DESTDIR/$1 $OLDTREE/$1
+ return
+ fi
+
+ empty_dir $DESTDIR/$1
+}
+
+# Output a diff of two directory entries with the same relative name
+# in different trees. Note that as with compare(), this does not
+# recurse into subdirectories. If the nodes are identical, nothing is
+# output.
+#
+# $1 - first tree
+# $2 - second tree
+# $3 - node name
+# $4 - label for first tree
+# $5 - label for second tree
+diffnode()
+{
+ local first second file old new diffargs
+
+ if [ -n "$FREEBSD_ID" ]; then
+ diffargs="-I \\\$FreeBSD.*\\\$"
+ else
+ diffargs=""
+ fi
+
+ compare_fbsdid $1/$3 $2/$3
+ case $? in
+ $COMPARE_EQUAL)
+ ;;
+ $COMPARE_ONLYFIRST)
+ echo
+ echo "Removed: $3"
+ echo
+ ;;
+ $COMPARE_ONLYSECOND)
+ echo
+ echo "Added: $3"
+ echo
+ ;;
+ $COMPARE_DIFFTYPE)
+ first=`file_type $1/$3`
+ second=`file_type $2/$3`
+ echo
+ echo "Node changed from a $first to a $second: $3"
+ echo
+ ;;
+ $COMPARE_DIFFLINKS)
+ first=`readlink $1/$file`
+ second=`readlink $2/$file`
+ echo
+ echo "Link changed: $file"
+ rule "="
+ echo "-$first"
+ echo "+$second"
+ echo
+ ;;
+ $COMPARE_DIFFFILES)
+ echo "Index: $3"
+ rule "="
+ diff -u $diffargs -L "$3 ($4)" $1/$3 -L "$3 ($5)" $2/$3
+ ;;
+ esac
+}
+
+# Run one-off commands after an update has completed. These commands
+# are not tied to a specific file, so they cannot be handled by
+# post_install_file().
+post_update()
+{
+ local args
+
+ # None of these commands should be run for a pre-world update.
+ if [ -n "$preworld" ]; then
+ return
+ fi
+
+ # If /etc/localtime exists and is not a symlink and /var/db/zoneinfo
+ # exists, run tzsetup -r to refresh /etc/localtime.
+ if [ -f ${DESTDIR}/etc/localtime -a \
+ ! -L ${DESTDIR}/etc/localtime ]; then
+ if [ -f ${DESTDIR}/var/db/zoneinfo ]; then
+ if [ -n "${DESTDIR}" ]; then
+ args="-C ${DESTDIR}"
+ else
+ args=""
+ fi
+ log "tzsetup -r ${args}"
+ if [ -z "$dryrun" ]; then
+ tzsetup -r ${args} >&3 2>&1
+ fi
+ else
+ warn "Needs update: /etc/localtime (required" \
+ "manual update via tzsetup(1))"
+ fi
+ fi
+}
+
+# Create missing parent directories of a node in a target tree
+# preserving the owner, group, and permissions from a specified
+# template tree.
+#
+# $1 - template tree
+# $2 - target tree
+# $3 - pathname of the node (relative to both trees)
+install_dirs()
+{
+ local args dir
+
+ dir=`dirname $3`
+
+ # Nothing to do if the parent directory exists. This also
+ # catches the degenerate cases when the path is just a simple
+ # filename.
+ if [ -d ${2}$dir ]; then
+ return 0
+ fi
+
+ # If non-directory file exists with the desired directory
+ # name, then fail.
+ if exists ${2}$dir; then
+ # If this is a dryrun and we are installing the
+ # directory in the DESTDIR and the file in the DESTDIR
+ # matches the file in the old tree, then fake success
+ # to quiet spurious warnings.
+ if [ -n "$dryrun" -a "$2" = "$DESTDIR" ]; then
+ if compare $OLDTREE/$dir $DESTDIR/$dir; then
+ return 0
+ fi
+ fi
+
+ args=`file_type ${2}$dir`
+ warn "Directory mismatch: ${2}$dir ($args)"
+ return 1
+ fi
+
+ # Ensure the parent directory of the directory is present
+ # first.
+ if ! install_dirs $1 "$2" $dir; then
+ return 1
+ fi
+
+ # Format attributes from template directory as install(1)
+ # arguments.
+ args=`stat -f "-o %Su -g %Sg -m %0Mp%0Lp" $1/$dir`
+
+ log "install -d $args ${2}$dir"
+ if [ -z "$dryrun" ]; then
+ install -d $args ${2}$dir >&3 2>&1
+ fi
+ return 0
+}
+
+# Perform post-install fixups for a file. This largely consists of
+# regenerating any files that depend on the newly installed file.
+#
+# $1 - pathname of the updated file (relative to DESTDIR)
+post_install_file()
+{
+ case $1 in
+ /etc/mail/aliases)
+ # Grr, newaliases only works for an empty DESTDIR.
+ if [ -z "$DESTDIR" ]; then
+ log "newaliases"
+ if [ -z "$dryrun" ]; then
+ newaliases >&3 2>&1
+ fi
+ else
+ NEWALIAS_WARN=yes
+ fi
+ ;;
+ /etc/login.conf)
+ log "cap_mkdb ${DESTDIR}$1"
+ if [ -z "$dryrun" ]; then
+ cap_mkdb ${DESTDIR}$1 >&3 2>&1
+ fi
+ ;;
+ /etc/master.passwd)
+ log "pwd_mkdb -p -d $DESTDIR/etc ${DESTDIR}$1"
+ if [ -z "$dryrun" ]; then
+ pwd_mkdb -p -d $DESTDIR/etc ${DESTDIR}$1 \
+ >&3 2>&1
+ fi
+ ;;
+ /etc/motd)
+ # /etc/rc.d/motd hardcodes the /etc/motd path.
+ # Don't warn about non-empty DESTDIR's since this
+ # change is only cosmetic anyway.
+ if [ -z "$DESTDIR" ]; then
+ log "sh /etc/rc.d/motd start"
+ if [ -z "$dryrun" ]; then
+ sh /etc/rc.d/motd start >&3 2>&1
+ fi
+ fi
+ ;;
+ /etc/services)
+ log "services_mkdb -q -o $DESTDIR/var/db/services.db" \
+ "${DESTDIR}$1"
+ if [ -z "$dryrun" ]; then
+ services_mkdb -q -o $DESTDIR/var/db/services.db \
+ ${DESTDIR}$1 >&3 2>&1
+ fi
+ ;;
+ esac
+}
+
+# Install the "new" version of a file. Returns true if it succeeds
+# and false otherwise.
+#
+# $1 - pathname of the file to install (relative to DESTDIR)
+install_new()
+{
+
+ if ! install_dirs $NEWTREE "$DESTDIR" $1; then
+ return 1
+ fi
+ log "cp -Rp ${NEWTREE}$1 ${DESTDIR}$1"
+ if [ -z "$dryrun" ]; then
+ cp -Rp ${NEWTREE}$1 ${DESTDIR}$1 >&3 2>&1
+ fi
+ post_install_file $1
+ return 0
+}
+
+# Install the "resolved" version of a file. Returns true if it succeeds
+# and false otherwise.
+#
+# $1 - pathname of the file to install (relative to DESTDIR)
+install_resolved()
+{
+
+ # This should always be present since the file is already
+ # there (it caused a conflict). However, it doesn't hurt to
+ # just be safe.
+ if ! install_dirs $NEWTREE "$DESTDIR" $1; then
+ return 1
+ fi
+
+ log "cp -Rp ${CONFLICTS}$1 ${DESTDIR}$1"
+ cp -Rp ${CONFLICTS}$1 ${DESTDIR}$1 >&3 2>&1
+ post_install_file $1
+ return 0
+}
+
+# Generate a conflict file when a "new" file conflicts with an
+# existing file in DESTDIR.
+#
+# $1 - pathname of the file that conflicts (relative to DESTDIR)
+new_conflict()
+{
+
+ if [ -n "$dryrun" ]; then
+ return
+ fi
+
+ install_dirs $NEWTREE $CONFLICTS $1
+ diff --changed-group-format='<<<<<<< (local)
+%<=======
+%>>>>>>>> (stock)
+' $DESTDIR/$1 $NEWTREE/$1 > $CONFLICTS/$1
+}
+
+# Remove the "old" version of a file.
+#
+# $1 - pathname of the old file to remove (relative to DESTDIR)
+remove_old()
+{
+ log "rm -f ${DESTDIR}$1"
+ if [ -z "$dryrun" ]; then
+ rm -f ${DESTDIR}$1 >&3 2>&1
+ fi
+ echo " D $1"
+}
+
+# Update a file that has no local modifications.
+#
+# $1 - pathname of the file to update (relative to DESTDIR)
+update_unmodified()
+{
+ local new old
+
+ # If the old file is a directory, then remove it with rmdir
+ # (this should only happen if the file has changed its type
+ # from a directory to a non-directory). If the directory
+ # isn't empty, then fail. This will be reported as a warning
+ # later.
+ if [ -d $DESTDIR/$1 ]; then
+ if empty_destdir $1; then
+ log "rmdir ${DESTDIR}$1"
+ if [ -z "$dryrun" ]; then
+ rmdir ${DESTDIR}$1 >&3 2>&1
+ fi
+ else
+ return 1
+ fi
+
+ # If both the old and new files are regular files, leave the
+ # existing file. This avoids breaking hard links for /.cshrc
+ # and /.profile. Otherwise, explicitly remove the old file.
+ elif ! [ -f ${DESTDIR}$1 -a -f ${NEWTREE}$1 ]; then
+ log "rm -f ${DESTDIR}$1"
+ if [ -z "$dryrun" ]; then
+ rm -f ${DESTDIR}$1 >&3 2>&1
+ fi
+ fi
+
+ # If the new file is a directory, note that the old file has
+ # been removed, but don't do anything else for now. The
+ # directory will be installed if needed when new files within
+ # that directory are installed.
+ if [ -d $NEWTREE/$1 ]; then
+ if empty_dir $NEWTREE/$1; then
+ echo " D $file"
+ else
+ echo " U $file"
+ fi
+ elif install_new $1; then
+ echo " U $file"
+ fi
+ return 0
+}
+
+# Update the FreeBSD ID string in a locally modified file to match the
+# FreeBSD ID string from the "new" version of the file.
+#
+# $1 - pathname of the file to update (relative to DESTDIR)
+update_freebsdid()
+{
+ local new dest file
+
+ # If the FreeBSD ID string is removed from the local file,
+ # there is nothing to do. In this case, treat the file as
+ # updated. Otherwise, if either file has more than one
+ # FreeBSD ID string, just punt and let the user handle the
+ # conflict manually.
+ new=`grep -c '\$FreeBSD.*\$' ${NEWTREE}$1`
+ dest=`grep -c '\$FreeBSD.*\$' ${DESTDIR}$1`
+ if [ "$dest" -eq 0 ]; then
+ return 0
+ fi
+ if [ "$dest" -ne 1 -o "$dest" -ne 1 ]; then
+ return 1
+ fi
+
+ # If the FreeBSD ID string in the new file matches the FreeBSD ID
+ # string in the local file, there is nothing to do.
+ new=`grep '\$FreeBSD.*\$' ${NEWTREE}$1`
+ dest=`grep '\$FreeBSD.*\$' ${DESTDIR}$1`
+ if [ "$new" = "$dest" ]; then
+ return 0
+ fi
+
+ # Build the new file in three passes. First, copy all the
+ # lines preceding the FreeBSD ID string from the local version
+ # of the file. Second, append the FreeBSD ID string line from
+ # the new version. Finally, append all the lines after the
+ # FreeBSD ID string from the local version of the file.
+ file=`mktemp $WORKDIR/etcupdate-XXXXXXX`
+ awk '/\$FreeBSD.*\$/ { exit } { print }' ${DESTDIR}$1 >> $file
+ awk '/\$FreeBSD.*\$/ { print }' ${NEWTREE}$1 >> $file
+ awk '/\$FreeBSD.*\$/ { ok = 1; next } { if (ok) print }' \
+ ${DESTDIR}$1 >> $file
+
+ # As an extra sanity check, fail the attempt if the updated
+ # version of the file has any differences aside from the
+ # FreeBSD ID string.
+ if ! fbsdid_only ${DESTDIR}$1 $file; then
+ rm -f $file
+ return 1
+ fi
+
+ log "cp $file ${DESTDIR}$1"
+ if [ -z "$dryrun" ]; then
+ cp $file ${DESTDIR}$1 >&3 2>&1
+ fi
+ rm -f $file
+ post_install_file $1
+ echo " M $1"
+ return 0
+}
+
+# Attempt to update a file that has local modifications. This routine
+# only handles regular files. If the 3-way merge succeeds without
+# conflicts, the updated file is installed. If the merge fails, the
+# merged version with conflict markers is left in the CONFLICTS tree.
+#
+# $1 - pathname of the file to merge (relative to DESTDIR)
+merge_file()
+{
+ local res
+
+ # Try the merge to see if there is a conflict.
+ merge -q -p ${DESTDIR}$1 ${OLDTREE}$1 ${NEWTREE}$1 >/dev/null 2>&3
+ res=$?
+ case $res in
+ 0)
+ # No conflicts, so just redo the merge to the
+ # real file.
+ log "merge ${DESTDIR}$1 ${OLDTREE}$1 ${NEWTREE}$1"
+ if [ -z "$dryrun" ]; then
+ merge ${DESTDIR}$1 ${OLDTREE}$1 ${NEWTREE}$1
+ fi
+ post_install_file $1
+ echo " M $1"
+ ;;
+ 1)
+ # Conflicts, save a version with conflict markers in
+ # the conflicts directory.
+ if [ -z "$dryrun" ]; then
+ install_dirs $NEWTREE $CONFLICTS $1
+ log "cp -Rp ${DESTDIR}$1 ${CONFLICTS}$1"
+ cp -Rp ${DESTDIR}$1 ${CONFLICTS}$1 >&3 2>&1
+ merge -A -q -L "yours" -L "original" -L "new" \
+ ${CONFLICTS}$1 ${OLDTREE}$1 ${NEWTREE}$1
+ fi
+ echo " C $1"
+ ;;
+ *)
+ panic "merge failed with status $res"
+ ;;
+ esac
+}
+
+# Returns true if a file contains conflict markers from a merge conflict.
+#
+# $1 - pathname of the file to resolve (relative to DESTDIR)
+has_conflicts()
+{
+
+ egrep -q '^(<{7}|\|{7}|={7}|>{7}) ' $CONFLICTS/$1
+}
+
+# Attempt to resolve a conflict. The user is prompted to choose an
+# action for each conflict. If the user edits the file, they are
+# prompted again for an action. The process is very similar to
+# resolving conflicts after an update or merge with Perforce or
+# Subversion. The prompts are modelled on a subset of the available
+# commands for resolving conflicts with Subversion.
+#
+# $1 - pathname of the file to resolve (relative to DESTDIR)
+resolve_conflict()
+{
+ local command junk
+
+ echo "Resolving conflict in '$1':"
+ edit=
+ while true; do
+ # Only display the resolved command if the file
+ # doesn't contain any conflicts.
+ echo -n "Select: (p) postpone, (df) diff-full, (e) edit,"
+ if ! has_conflicts $1; then
+ echo -n " (r) resolved,"
+ fi
+ echo
+ echo -n " (h) help for more options: "
+ read command
+ case $command in
+ df)
+ diff -u ${DESTDIR}$1 ${CONFLICTS}$1
+ ;;
+ e)
+ $EDITOR ${CONFLICTS}$1
+ ;;
+ h)
+ cat <<EOF
+ (p) postpone - ignore this conflict for now
+ (df) diff-full - show all changes made to merged file
+ (e) edit - change merged file in an editor
+ (r) resolved - accept merged version of file
+ (mf) mine-full - accept local version of entire file (ignore new changes)
+ (tf) theirs-full - accept new version of entire file (lose local changes)
+ (h) help - show this list
+EOF
+ ;;
+ mf)
+ # For mine-full, just delete the
+ # merged file and leave the local
+ # version of the file as-is.
+ rm ${CONFLICTS}$1
+ return
+ ;;
+ p)
+ return
+ ;;
+ r)
+ # If the merged file has conflict
+ # markers, require confirmation.
+ if has_conflicts $1; then
+ echo "File '$1' still has conflicts," \
+ "are you sure? (y/n) "
+ read junk
+ if [ "$junk" != "y" ]; then
+ continue
+ fi
+ fi
+
+ if ! install_resolved $1; then
+ panic "Unable to install merged" \
+ "version of $1"
+ fi
+ rm ${CONFLICTS}$1
+ return
+ ;;
+ tf)
+ # For theirs-full, install the new
+ # version of the file over top of the
+ # existing file.
+ if ! install_new $1; then
+ panic "Unable to install new" \
+ "version of $1"
+ fi
+ rm ${CONFLICTS}$1
+ return
+ ;;
+ *)
+ echo "Invalid command."
+ ;;
+ esac
+ done
+}
+
+# Handle a file that has been removed from the new tree. If the file
+# does not exist in DESTDIR, then there is nothing to do. If the file
+# exists in DESTDIR and is identical to the old version, remove it
+# from DESTDIR. Otherwise, whine about the conflict but leave the
+# file in DESTDIR. To handle directories, this uses two passes. The
+# first pass handles all non-directory files. The second pass handles
+# just directories and removes them if they are empty.
+#
+# If -F is specified, and the only difference in the file in DESTDIR
+# is a change in the FreeBSD ID string, then remove the file.
+#
+# $1 - pathname of the file (relative to DESTDIR)
+handle_removed_file()
+{
+ local dest file
+
+ file=$1
+ if ignore $file; then
+ log "IGNORE: removed file $file"
+ return
+ fi
+
+ compare_fbsdid $DESTDIR/$file $OLDTREE/$file
+ case $? in
+ $COMPARE_EQUAL)
+ if ! [ -d $DESTDIR/$file ]; then
+ remove_old $file
+ fi
+ ;;
+ $COMPARE_ONLYFIRST)
+ panic "Removed file now missing"
+ ;;
+ $COMPARE_ONLYSECOND)
+ # Already removed, nothing to do.
+ ;;
+ $COMPARE_DIFFTYPE|$COMPARE_DIFFLINKS|$COMPARE_DIFFFILES)
+ dest=`file_type $DESTDIR/$file`
+ warn "Modified $dest remains: $file"
+ ;;
+ esac
+}
+
+# Handle a directory that has been removed from the new tree. Only
+# remove the directory if it is empty.
+#
+# $1 - pathname of the directory (relative to DESTDIR)
+handle_removed_directory()
+{
+ local dir
+
+ dir=$1
+ if ignore $dir; then
+ log "IGNORE: removed dir $dir"
+ return
+ fi
+
+ if [ -d $DESTDIR/$dir -a -d $OLDTREE/$dir ]; then
+ if empty_destdir $dir; then
+ log "rmdir ${DESTDIR}$dir"
+ if [ -z "$dryrun" ]; then
+ rmdir ${DESTDIR}$dir >/dev/null 2>&1
+ fi
+ echo " D $dir"
+ else
+ warn "Non-empty directory remains: $dir"
+ fi
+ fi
+}
+
+# Handle a file that exists in both the old and new trees. If the
+# file has not changed in the old and new trees, there is nothing to
+# do. If the file in the destination directory matches the new file,
+# there is nothing to do. If the file in the destination directory
+# matches the old file, then the new file should be installed.
+# Everything else becomes some sort of conflict with more detailed
+# handling.
+#
+# $1 - pathname of the file (relative to DESTDIR)
+handle_modified_file()
+{
+ local cmp dest file new newdestcmp old
+
+ file=$1
+ if ignore $file; then
+ log "IGNORE: modified file $file"
+ return
+ fi
+
+ compare $OLDTREE/$file $NEWTREE/$file
+ cmp=$?
+ if [ $cmp -eq $COMPARE_EQUAL ]; then
+ return
+ fi
+
+ if [ $cmp -eq $COMPARE_ONLYFIRST -o $cmp -eq $COMPARE_ONLYSECOND ]; then
+ panic "Changed file now missing"
+ fi
+
+ compare $NEWTREE/$file $DESTDIR/$file
+ newdestcmp=$?
+ if [ $newdestcmp -eq $COMPARE_EQUAL ]; then
+ return
+ fi
+
+ # If the only change in the new file versus the destination
+ # file is a change in the FreeBSD ID string and -F is
+ # specified, just install the new file.
+ if [ -n "$FREEBSD_ID" -a $newdestcmp -eq $COMPARE_DIFFFILES ] && \
+ fbsdid_only $NEWTREE/$file $DESTDIR/$file; then
+ if update_unmodified $file; then
+ return
+ else
+ panic "Updating FreeBSD ID string failed"
+ fi
+ fi
+
+ # If the local file is the same as the old file, install the
+ # new file. If -F is specified and the only local change is
+ # in the FreeBSD ID string, then install the new file as well.
+ if compare_fbsdid $OLDTREE/$file $DESTDIR/$file; then
+ if update_unmodified $file; then
+ return
+ fi
+ fi
+
+ # If the file was removed from the dest tree, just whine.
+ if [ $newdestcmp -eq $COMPARE_ONLYFIRST ]; then
+ # If the removed file matches an ALWAYS_INSTALL glob,
+ # then just install the new version of the file.
+ if always_install $file; then
+ log "ALWAYS: adding $file"
+ if ! [ -d $NEWTREE/$file ]; then
+ if install_new $file; then
+ echo " A $file"
+ fi
+ fi
+ return
+ fi
+
+ # If the only change in the new file versus the old
+ # file is a change in the FreeBSD ID string and -F is
+ # specified, don't warn.
+ if [ -n "$FREEBSD_ID" -a $cmp -eq $COMPARE_DIFFFILES ] && \
+ fbsdid_only $OLDTREE/$file $NEWTREE/$file; then
+ return
+ fi
+
+ case $cmp in
+ $COMPARE_DIFFTYPE)
+ old=`file_type $OLDTREE/$file`
+ new=`file_type $NEWTREE/$file`
+ warn "Remove mismatch: $file ($old became $new)"
+ ;;
+ $COMPARE_DIFFLINKS)
+ old=`readlink $OLDTREE/$file`
+ new=`readlink $NEWTREE/$file`
+ warn \
+ "Removed link changed: $file (\"$old\" became \"$new\")"
+ ;;
+ $COMPARE_DIFFFILES)
+ warn "Removed file changed: $file"
+ ;;
+ esac
+ return
+ fi
+
+ # Treat the file as unmodified and force install of the new
+ # file if it matches an ALWAYS_INSTALL glob. If the update
+ # attempt fails, then fall through to the normal case so a
+ # warning is generated.
+ if always_install $file; then
+ log "ALWAYS: updating $file"
+ if update_unmodified $file; then
+ return
+ fi
+ fi
+
+ # If the only change in the new file versus the old file is a
+ # change in the FreeBSD ID string and -F is specified, just
+ # update the FreeBSD ID string in the local file.
+ if [ -n "$FREEBSD_ID" -a $cmp -eq $COMPARE_DIFFFILES ] && \
+ fbsdid_only $OLDTREE/$file $NEWTREE/$file; then
+ if update_freebsdid $file; then
+ continue
+ fi
+ fi
+
+ # If the file changed types between the old and new trees but
+ # the files in the new and dest tree are both of the same
+ # type, treat it like an added file just comparing the new and
+ # dest files.
+ if [ $cmp -eq $COMPARE_DIFFTYPE ]; then
+ case $newdestcmp in
+ $COMPARE_DIFFLINKS)
+ new=`readlink $NEWTREE/$file`
+ dest=`readlink $DESTDIR/$file`
+ warn \
+ "New link conflict: $file (\"$new\" vs \"$dest\")"
+ return
+ ;;
+ $COMPARE_DIFFFILES)
+ new_conflict $file
+ echo " C $file"
+ return
+ ;;
+ esac
+ else
+ # If the file has not changed types between the old
+ # and new trees, but it is a different type in
+ # DESTDIR, then just warn.
+ if [ $newdestcmp -eq $COMPARE_DIFFTYPE ]; then
+ new=`file_type $NEWTREE/$file`
+ dest=`file_type $DESTDIR/$file`
+ warn "Modified mismatch: $file ($new vs $dest)"
+ return
+ fi
+ fi
+
+ case $cmp in
+ $COMPARE_DIFFTYPE)
+ old=`file_type $OLDTREE/$file`
+ new=`file_type $NEWTREE/$file`
+ dest=`file_type $DESTDIR/$file`
+ warn "Modified $dest changed: $file ($old became $new)"
+ ;;
+ $COMPARE_DIFFLINKS)
+ old=`readlink $OLDTREE/$file`
+ new=`readlink $NEWTREE/$file`
+ warn \
+ "Modified link changed: $file (\"$old\" became \"$new\")"
+ ;;
+ $COMPARE_DIFFFILES)
+ merge_file $file
+ ;;
+ esac
+}
+
+# Handle a file that has been added in the new tree. If the file does
+# not exist in DESTDIR, simply copy the file into DESTDIR. If the
+# file exists in the DESTDIR and is identical to the new version, do
+# nothing. Otherwise, generate a diff of the two versions of the file
+# and mark it as a conflict.
+#
+# $1 - pathname of the file (relative to DESTDIR)
+handle_added_file()
+{
+ local cmp dest file new
+
+ file=$1
+ if ignore $file; then
+ log "IGNORE: added file $file"
+ return
+ fi
+
+ compare $DESTDIR/$file $NEWTREE/$file
+ cmp=$?
+ case $cmp in
+ $COMPARE_EQUAL)
+ return
+ ;;
+ $COMPARE_ONLYFIRST)
+ panic "Added file now missing"
+ ;;
+ $COMPARE_ONLYSECOND)
+ # Ignore new directories. They will be
+ # created as needed when non-directory nodes
+ # are installed.
+ if ! [ -d $NEWTREE/$file ]; then
+ if install_new $file; then
+ echo " A $file"
+ fi
+ fi
+ return
+ ;;
+ esac
+
+
+ # Treat the file as unmodified and force install of the new
+ # file if it matches an ALWAYS_INSTALL glob. If the update
+ # attempt fails, then fall through to the normal case so a
+ # warning is generated.
+ if always_install $file; then
+ log "ALWAYS: updating $file"
+ if update_unmodified $file; then
+ return
+ fi
+ fi
+
+ case $cmp in
+ $COMPARE_DIFFTYPE)
+ new=`file_type $NEWTREE/$file`
+ dest=`file_type $DESTDIR/$file`
+ warn "New file mismatch: $file ($new vs $dest)"
+ ;;
+ $COMPARE_DIFFLINKS)
+ new=`readlink $NEWTREE/$file`
+ dest=`readlink $DESTDIR/$file`
+ warn "New link conflict: $file (\"$new\" vs \"$dest\")"
+ ;;
+ $COMPARE_DIFFFILES)
+ # If the only change in the new file versus
+ # the destination file is a change in the
+ # FreeBSD ID string and -F is specified, just
+ # install the new file.
+ if [ -n "$FREEBSD_ID" ] && \
+ fbsdid_only $NEWTREE/$file $DESTDIR/$file; then
+ if update_unmodified $file; then
+ return
+ else
+ panic \
+ "Updating FreeBSD ID string failed"
+ fi
+ fi
+
+ new_conflict $file
+ echo " C $file"
+ ;;
+ esac
+}
+
+# Main routines for each command
+
+# Build a new tree and save it in a tarball.
+build_cmd()
+{
+ local dir
+
+ if [ $# -ne 1 ]; then
+ echo "Missing required tarball."
+ echo
+ usage
+ fi
+
+ log "build command: $1"
+
+ # Create a temporary directory to hold the tree
+ dir=`mktemp -d $WORKDIR/etcupdate-XXXXXXX`
+ if [ $? -ne 0 ]; then
+ echo "Unable to create temporary directory."
+ exit 1
+ fi
+ if ! build_tree $dir; then
+ echo "Failed to build tree."
+ remove_tree $dir
+ exit 1
+ fi
+ if ! tar cfj $1 -C $dir . >&3 2>&1; then
+ echo "Failed to create tarball."
+ remove_tree $dir
+ exit 1
+ fi
+ remove_tree $dir
+}
+
+# Output a diff comparing the tree at DESTDIR to the current
+# unmodified tree. Note that this diff does not include files that
+# are present in DESTDIR but not in the unmodified tree.
+diff_cmd()
+{
+ local file
+
+ if [ $# -ne 0 ]; then
+ usage
+ fi
+
+ # Requires an unmodified tree to diff against.
+ if ! [ -d $NEWTREE ]; then
+ echo "Reference tree to diff against unavailable."
+ exit 1
+ fi
+
+ # Unfortunately, diff alone does not quite provide the right
+ # level of options that we want, so improvise.
+ for file in `(cd $NEWTREE; find .) | sed -e 's/^\.//'`; do
+ if ignore $file; then
+ continue
+ fi
+
+ diffnode $NEWTREE "$DESTDIR" $file "stock" "local"
+ done
+}
+
+# Just extract a new tree into NEWTREE either by building a tree or
+# extracting a tarball. This can be used to bootstrap updates by
+# initializing the current "stock" tree to match the currently
+# installed system.
+#
+# Unlike 'update', this command does not rotate or preserve an
+# existing NEWTREE, it just replaces any existing tree.
+extract_cmd()
+{
+
+ if [ $# -ne 0 ]; then
+ usage
+ fi
+
+ log "extract command: tarball=$tarball"
+
+ if [ -d $NEWTREE ]; then
+ if ! remove_tree $NEWTREE; then
+ echo "Unable to remove current tree."
+ exit 1
+ fi
+ fi
+
+ extract_tree
+}
+
+# Resolve conflicts left from an earlier merge.
+resolve_cmd()
+{
+ local conflicts
+
+ if [ $# -ne 0 ]; then
+ usage
+ fi
+
+ if ! [ -d $CONFLICTS ]; then
+ return
+ fi
+
+ if ! [ -d $NEWTREE ]; then
+ echo "The current tree is not present to resolve conflicts."
+ exit 1
+ fi
+
+ conflicts=`(cd $CONFLICTS; find . ! -type d) | sed -e 's/^\.//'`
+ for file in $conflicts; do
+ resolve_conflict $file
+ done
+
+ if [ -n "$NEWALIAS_WARN" ]; then
+ warn "Needs update: /etc/mail/aliases.db" \
+ "(requires manual update via newaliases(1))"
+ echo
+ echo "Warnings:"
+ echo " Needs update: /etc/mail/aliases.db" \
+ "(requires manual update via newaliases(1))"
+ fi
+}
+
+# Report a summary of the previous merge. Specifically, list any
+# remaining conflicts followed by any warnings from the previous
+# update.
+status_cmd()
+{
+
+ if [ $# -ne 0 ]; then
+ usage
+ fi
+
+ if [ -d $CONFLICTS ]; then
+ (cd $CONFLICTS; find . ! -type d) | sed -e 's/^\./ C /'
+ fi
+ if [ -s $WARNINGS ]; then
+ echo "Warnings:"
+ cat $WARNINGS
+ fi
+}
+
+# Perform an actual merge. The new tree can either already exist (if
+# rerunning a merge), be extracted from a tarball, or generated from a
+# source tree.
+update_cmd()
+{
+ local dir
+
+ if [ $# -ne 0 ]; then
+ usage
+ fi
+
+ log "update command: rerun=$rerun tarball=$tarball preworld=$preworld"
+
+ if [ `id -u` -ne 0 ]; then
+ echo "Must be root to update a tree."
+ exit 1
+ fi
+
+ # Enforce a sane umask
+ umask 022
+
+ # XXX: Should existing conflicts be ignored and removed during
+ # a rerun?
+
+ # Trim the conflicts tree. Whine if there is anything left.
+ if [ -e $CONFLICTS ]; then
+ find -d $CONFLICTS -type d -empty -delete >&3 2>&1
+ rmdir $CONFLICTS >&3 2>&1
+ fi
+ if [ -d $CONFLICTS ]; then
+ echo "Conflicts remain from previous update, aborting."
+ exit 1
+ fi
+
+ if [ -z "$rerun" ]; then
+ # For a dryrun that is not a rerun, do not rotate the existing
+ # stock tree. Instead, extract a tree to a temporary directory
+ # and use that for the comparison.
+ if [ -n "$dryrun" ]; then
+ dir=`mktemp -d $WORKDIR/etcupdate-XXXXXXX`
+ if [ $? -ne 0 ]; then
+ echo "Unable to create temporary directory."
+ exit 1
+ fi
+
+ # A pre-world dryrun has already set OLDTREE to
+ # point to the current stock tree.
+ if [ -z "$preworld" ]; then
+ OLDTREE=$NEWTREE
+ fi
+ NEWTREE=$dir
+
+ # For a pre-world update, blow away any pre-existing
+ # NEWTREE.
+ elif [ -n "$preworld" ]; then
+ if ! remove_tree $NEWTREE; then
+ echo "Unable to remove pre-world tree."
+ exit 1
+ fi
+
+ # Rotate the existing stock tree to the old tree.
+ elif [ -d $NEWTREE ]; then
+ # First, delete the previous old tree if it exists.
+ if ! remove_tree $OLDTREE; then
+ echo "Unable to remove old tree."
+ exit 1
+ fi
+
+ # Move the current stock tree.
+ if ! mv $NEWTREE $OLDTREE >&3 2>&1; then
+ echo "Unable to rename current stock tree."
+ exit 1
+ fi
+ fi
+
+ if ! [ -d $OLDTREE ]; then
+ cat <<EOF
+No previous tree to compare against, a sane comparison is not possible.
+EOF
+ log "No previous tree to compare against."
+ if [ -n "$dir" ]; then
+ rmdir $dir
+ fi
+ exit 1
+ fi
+
+ # Populate the new tree.
+ extract_tree
+ fi
+
+ # Build lists of nodes in the old and new trees.
+ (cd $OLDTREE; find .) | sed -e 's/^\.//' | sort > $WORKDIR/old.files
+ (cd $NEWTREE; find .) | sed -e 's/^\.//' | sort > $WORKDIR/new.files
+
+ # Split the files up into three groups using comm.
+ comm -23 $WORKDIR/old.files $WORKDIR/new.files > $WORKDIR/removed.files
+ comm -13 $WORKDIR/old.files $WORKDIR/new.files > $WORKDIR/added.files
+ comm -12 $WORKDIR/old.files $WORKDIR/new.files > $WORKDIR/both.files
+
+ # Initialize conflicts and warnings handling.
+ rm -f $WARNINGS
+ mkdir -p $CONFLICTS
+
+ # Ignore removed files for the pre-world case. A pre-world
+ # update uses a stripped-down tree.
+ if [ -n "$preworld" ]; then
+ > $WORKDIR/removed.files
+ fi
+
+ # The order for the following sections is important. In the
+ # odd case that a directory is converted into a file, the
+ # existing subfiles need to be removed if possible before the
+ # file is converted. Similarly, in the case that a file is
+ # converted into a directory, the file needs to be converted
+ # into a directory if possible before the new files are added.
+
+ # First, handle removed files.
+ for file in `cat $WORKDIR/removed.files`; do
+ handle_removed_file $file
+ done
+
+ # For the directory pass, reverse sort the list to effect a
+ # depth-first traversal. This is needed to ensure that if a
+ # directory with subdirectories is removed, the entire
+ # directory is removed if there are no local modifications.
+ for file in `sort -r $WORKDIR/removed.files`; do
+ handle_removed_directory $file
+ done
+
+ # Second, handle files that exist in both the old and new
+ # trees.
+ for file in `cat $WORKDIR/both.files`; do
+ handle_modified_file $file
+ done
+
+ # Finally, handle newly added files.
+ for file in `cat $WORKDIR/added.files`; do
+ handle_added_file $file
+ done
+
+ if [ -n "$NEWALIAS_WARN" ]; then
+ warn "Needs update: /etc/mail/aliases.db" \
+ "(requires manual update via newaliases(1))"
+ fi
+
+ # Run any special one-off commands after an update has completed.
+ post_update
+
+ if [ -s $WARNINGS ]; then
+ echo "Warnings:"
+ cat $WARNINGS
+ fi
+
+ if [ -n "$dir" ]; then
+ if [ -z "$dryrun" -o -n "$rerun" ]; then
+ panic "Should not have a temporary directory"
+ fi
+
+ remove_tree $dir
+ fi
+}
+
+# Determine which command we are executing. A command may be
+# specified as the first word. If one is not specified then 'update'
+# is assumed as the default command.
+command="update"
+if [ $# -gt 0 ]; then
+ case "$1" in
+ build|diff|extract|status|resolve)
+ command="$1"
+ shift
+ ;;
+ -*)
+ # If first arg is an option, assume the
+ # default command.
+ ;;
+ *)
+ usage
+ ;;
+ esac
+fi
+
+# Set default variable values.
+
+# The path to the source tree used to build trees.
+SRCDIR=/usr/src
+
+# The destination directory where the modified files live.
+DESTDIR=
+
+# Ignore changes in the FreeBSD ID string.
+FREEBSD_ID=
+
+# Files that should always have the new version of the file installed.
+ALWAYS_INSTALL=
+
+# Files to ignore and never update during a merge.
+IGNORE_FILES=
+
+# Flags to pass to 'make' when building a tree.
+MAKE_OPTIONS=
+
+# Include a config file if it exists. Note that command line options
+# override any settings in the config file. More details are in the
+# manual, but in general the following variables can be set:
+# - ALWAYS_INSTALL
+# - DESTDIR
+# - EDITOR
+# - FREEBSD_ID
+# - IGNORE_FILES
+# - LOGFILE
+# - MAKE_OPTIONS
+# - SRCDIR
+# - WORKDIR
+if [ -r /etc/etcupdate.conf ]; then
+ . /etc/etcupdate.conf
+fi
+
+# Parse command line options
+tarball=
+rerun=
+always=
+dryrun=
+ignore=
+nobuild=
+preworld=
+while getopts "d:nprs:t:A:BD:FI:L:M:" option; do
+ case "$option" in
+ d)
+ WORKDIR=$OPTARG
+ ;;
+ n)
+ dryrun=YES
+ ;;
+ p)
+ preworld=YES
+ ;;
+ r)
+ rerun=YES
+ ;;
+ s)
+ SRCDIR=$OPTARG
+ ;;
+ t)
+ tarball=$OPTARG
+ ;;
+ A)
+ # To allow this option to be specified
+ # multiple times, accumulate command-line
+ # specified patterns in an 'always' variable
+ # and use that to overwrite ALWAYS_INSTALL
+ # after parsing all options. Need to be
+ # careful here with globbing expansion.
+ set -o noglob
+ always="$always $OPTARG"
+ set +o noglob
+ ;;
+ B)
+ nobuild=YES
+ ;;
+ D)
+ DESTDIR=$OPTARG
+ ;;
+ F)
+ FREEBSD_ID=YES
+ ;;
+ I)
+ # To allow this option to be specified
+ # multiple times, accumulate command-line
+ # specified patterns in an 'ignore' variable
+ # and use that to overwrite IGNORE_FILES after
+ # parsing all options. Need to be careful
+ # here with globbing expansion.
+ set -o noglob
+ ignore="$ignore $OPTARG"
+ set +o noglob
+ ;;
+ L)
+ LOGFILE=$OPTARG
+ ;;
+ M)
+ MAKE_OPTIONS="$OPTARG"
+ ;;
+ *)
+ echo
+ usage
+ ;;
+ esac
+done
+shift $((OPTIND - 1))
+
+# Allow -A command line options to override ALWAYS_INSTALL set from
+# the config file.
+set -o noglob
+if [ -n "$always" ]; then
+ ALWAYS_INSTALL="$always"
+fi
+
+# Allow -I command line options to override IGNORE_FILES set from the
+# config file.
+if [ -n "$ignore" ]; then
+ IGNORE_FILES="$ignore"
+fi
+set +o noglob
+
+# Where the "old" and "new" trees are stored.
+WORKDIR=${WORKDIR:-$DESTDIR/var/db/etcupdate}
+
+# Log file for verbose output from program that are run. The log file
+# is opened on fd '3'.
+LOGFILE=${LOGFILE:-$WORKDIR/log}
+
+# The path of the "old" tree
+OLDTREE=$WORKDIR/old
+
+# The path of the "new" tree
+NEWTREE=$WORKDIR/current
+
+# The path of the "conflicts" tree where files with merge conflicts are saved.
+CONFLICTS=$WORKDIR/conflicts
+
+# The path of the "warnings" file that accumulates warning notes from an update.
+WARNINGS=$WORKDIR/warnings
+
+# Use $EDITOR for resolving conflicts. If it is not set, default to vi.
+EDITOR=${EDITOR:-/usr/bin/vi}
+
+# Files that need to be updated before installworld.
+PREWORLD_FILES="etc/master.passwd etc/group"
+
+# Handle command-specific argument processing such as complaining
+# about unsupported options. Since the configuration file is always
+# included, do not complain about extra command line arguments that
+# may have been set via the config file rather than the command line.
+case $command in
+ update)
+ if [ -n "$rerun" -a -n "$tarball" ]; then
+ echo "Only one of -r or -t can be specified."
+ echo
+ usage
+ fi
+ if [ -n "$rerun" -a -n "$preworld" ]; then
+ echo "Only one of -p or -r can be specified."
+ echo
+ usage
+ fi
+ ;;
+ build|diff|status)
+ if [ -n "$dryrun" -o -n "$rerun" -o -n "$tarball" -o \
+ -n "$preworld" ]; then
+ usage
+ fi
+ ;;
+ resolve)
+ if [ -n "$dryrun" -o -n "$rerun" -o -n "$tarball" ]; then
+ usage
+ fi
+ ;;
+ extract)
+ if [ -n "$dryrun" -o -n "$rerun" -o -n "$preworld" ]; then
+ usage
+ fi
+ ;;
+esac
+
+# Pre-world mode uses a different set of trees. It leaves the current
+# tree as-is so it is still present for a full etcupdate run after the
+# world install is complete. Instead, it installs a few critical files
+# into a separate tree.
+if [ -n "$preworld" ]; then
+ OLDTREE=$NEWTREE
+ NEWTREE=$WORKDIR/preworld
+fi
+
+# Open the log file. Don't truncate it if doing a minor operation so
+# that a minor operation doesn't lose log info from a major operation.
+if ! mkdir -p $WORKDIR 2>/dev/null; then
+ echo "Failed to create work directory $WORKDIR"
+fi
+
+case $command in
+ diff|resolve|status)
+ exec 3>>$LOGFILE
+ ;;
+ *)
+ exec 3>$LOGFILE
+ ;;
+esac
+
+${command}_cmd "$@"
diff --git a/usr.sbin/etcupdate/tests/Makefile b/usr.sbin/etcupdate/tests/Makefile
new file mode 100644
index 0000000..45b12b7
--- /dev/null
+++ b/usr.sbin/etcupdate/tests/Makefile
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+PLAIN_TESTS_SH=
+.for test in always_test \
+ conflicts_test \
+ fbsdid_test \
+ ignore_test \
+ preworld_test \
+ tests_test \
+ tzsetup_test
+PLAIN_TESTS_SH+= ${test}
+TEST_METADATA.${test}+= required_user="root"
+.endfor
+
+.include <bsd.test.mk>
diff --git a/usr.sbin/etcupdate/tests/always_test.sh b/usr.sbin/etcupdate/tests/always_test.sh
new file mode 100644
index 0000000..2055bb6
--- /dev/null
+++ b/usr.sbin/etcupdate/tests/always_test.sh
@@ -0,0 +1,630 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Hudson River Trading LLC
+# Written by: John H. 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$
+
+# Various regression tests to test the -A flag to the 'update' command.
+
+FAILED=no
+WORKDIR=work
+
+usage()
+{
+ echo "Usage: always.sh [-s script] [-w workdir]"
+ exit 1
+}
+
+# Allow the user to specify an alternate work directory or script.
+COMMAND=etcupdate
+while getopts "s:w:" option; do
+ case $option in
+ s)
+ COMMAND="sh $OPTARG"
+ ;;
+ w)
+ WORKDIR=$OPTARG
+ ;;
+ *)
+ echo
+ usage
+ ;;
+ esac
+done
+shift $((OPTIND - 1))
+if [ $# -ne 0 ]; then
+ usage
+fi
+
+CONFLICTS=$WORKDIR/conflicts
+OLD=$WORKDIR/old
+NEW=$WORKDIR/current
+TEST=$WORKDIR/test
+
+# The various states of the comparison of a file between two trees.
+states="equal first second difftype difflinks difffiles"
+
+# These tests deal with ignoring certain patterns of files. We run
+# the test multiple times forcing the install of different patterns.
+build_trees()
+{
+ local i
+
+ rm -rf $OLD $NEW $TEST $CONFLICTS
+
+ for i in $states; do
+ for j in $states; do
+ for k in $states; do
+ mkdir -p $OLD/$i/$j/$k $NEW/$i/$j/$k \
+ $TEST/$i/$j/$k
+ done
+ done
+ done
+
+ # What follows are the various warning/conflict cases from the
+ # larger regression tests. These results of many of these
+ # tests should be changed when installation is forced. The
+ # cases when these updates should still fail even when forced
+ # are: 1) it should not force the removal of a modified file
+ # and 2) it should not remove a subdirectory that contains a
+ # modified or added file.
+
+ # /first/difftype/second: File with different local type
+ # removed. Should generate a warning.
+ mkfifo $OLD/first/difftype/second/fifo
+ mkdir $TEST/first/difftype/second/fifo
+
+ # /first/difflinks/second: Modified link removed. Should
+ # generate a warning.
+ ln -s "old link" $OLD/first/difflinks/second/link
+ ln -s "test link" $TEST/first/difflinks/second/link
+
+ # /first/difffiles/second: Modified file removed. Should
+ # generate a warning.
+ echo "foo" > $OLD/first/difffiles/second/file
+ echo "bar" > $TEST/first/difffiles/second/file
+
+ # /second/second/difftype: Newly added file conflicts with
+ # existing file in test tree of a different type. Should
+ # generate a warning.
+ mkdir $NEW/second/second/difftype/dir
+ mkfifo $TEST/second/second/difftype/dir
+
+ # /second/second/difflinks: Newly added link conflicts with
+ # existing link in test tree. Should generate a warning.
+ ln -s "new link" $NEW/second/second/difflinks/link
+ ln -s "test link" $TEST/second/second/difflinks/link
+
+ # /second/second/difffiles: Newly added file conflicts with
+ # existing file in test tree. Should generate a warning.
+ echo "new" > $NEW/second/second/difffiles/file
+ echo "test" > $TEST/second/second/difffiles/file
+
+ # /difftype/first/first: A removed file has changed type.
+ # This should generate a warning.
+ mkfifo $OLD/difftype/first/first/fifo
+ mkdir $NEW/difftype/first/first/fifo
+
+ # /difftype/difftype/difftype: All three files (old, new, and
+ # test) are different types from each other. This should
+ # generate a warning.
+ mkfifo $OLD/difftype/difftype/difftype/one
+ mkdir $NEW/difftype/difftype/difftype/one
+ echo "foo" > $TEST/difftype/difftype/difftype/one
+ mkdir $OLD/difftype/difftype/difftype/two
+ echo "baz" > $NEW/difftype/difftype/difftype/two
+ ln -s "bar" $TEST/difftype/difftype/difftype/two
+
+ # /difftype/difftype/difflinks: A file has changed from a
+ # non-link to a link in both the new and test trees, but the
+ # target of the new and test links differ. This should
+ # generate a new link conflict.
+ mkfifo $OLD/difftype/difftype/difflinks/link
+ ln -s "new" $NEW/difftype/difftype/difflinks/link
+ ln -s "test" $TEST/difftype/difftype/difflinks/link
+
+ # /difftype/difftype/difffile: A file has changed from a
+ # non-regular file to a regular file in both the new and test
+ # trees, but the contents in the new and test files differ.
+ # This should generate a new file conflict.
+ ln -s "old" $OLD/difftype/difftype/difffiles/file
+ echo "foo" > $NEW/difftype/difftype/difffiles/file
+ echo "bar" > $TEST/difftype/difftype/difffiles/file
+
+ # /difflinks/first/first: A modified link is missing in the
+ # test tree. This should generate a warning.
+ ln -s "old" $OLD/difflinks/first/first/link
+ ln -s "new" $NEW/difflinks/first/first/link
+
+ # /difflinks/difftype/difftype: An updated link has been
+ # changed to a different file type in the test tree. This
+ # should generate a warning.
+ ln -s "old" $OLD/difflinks/difftype/difftype/link
+ ln -s "new" $NEW/difflinks/difftype/difftype/link
+ echo "test" > $TEST/difflinks/difftype/difftype/link
+
+ # /difflinks/difflinks/difflinks: An updated link has been
+ # modified in the test tree and doesn't match either the old
+ # or new links. This should generate a warning.
+ ln -s "old" $OLD/difflinks/difflinks/difflinks/link
+ ln -s "new" $NEW/difflinks/difflinks/difflinks/link
+ ln -s "test" $TEST/difflinks/difflinks/difflinks/link
+
+ # /difffiles/first/first: A removed file has been changed in
+ # the new tree. This should generate a warning.
+ echo "foo" > $OLD/difffiles/first/first/file
+ echo "bar" > $NEW/difffiles/first/first/file
+
+ # /difffiles/difftype/difftype: An updated regular file has
+ # been changed to a different file type in the test tree.
+ # This should generate a warning.
+ echo "old" > $OLD/difffiles/difftype/difftype/file
+ echo "new" > $NEW/difffiles/difftype/difftype/file
+ mkfifo $TEST/difffiles/difftype/difftype/file
+
+ # /difffiles/difffiles/difffiles: A modified regular file was
+ # updated in the new tree. The changes should be merged into
+ # to the new file if possible. If the merge fails, a conflict
+ # should be generated. For this test we just include the
+ # conflict case.
+ cat > $OLD/difffiles/difffiles/difffiles/conflict <<EOF
+this is an old file
+EOF
+ cat > $NEW/difffiles/difffiles/difffiles/conflict <<EOF
+this is a new file
+EOF
+ cat > $TEST/difffiles/difffiles/difffiles/conflict <<EOF
+this is a test file
+EOF
+
+ ## Tests for adding directories
+ mkdir -p $OLD/adddir $NEW/adddir $TEST/adddir
+
+ # /adddir/conflict: Add a new file in a directory that already
+ # exists as a file. This should generate two warnings.
+ mkdir $NEW/adddir/conflict
+ touch $NEW/adddir/conflict/newfile
+ touch $TEST/adddir/conflict
+
+ ## Tests for removing directories
+ mkdir -p $OLD/rmdir $NEW/rmdir $TEST/rmdir
+
+ # /rmdir/extra: Do not remove a directory with an extra local file.
+ # This should generate a warning.
+ for i in $OLD $TEST; do
+ mkdir $i/rmdir/extra
+ done
+ echo "foo" > $TEST/rmdir/extra/localfile.txt
+
+ # /rmdir/conflict: Do not remove a directory with a conflicted
+ # remove file. This should generate a warning.
+ for i in $OLD $TEST; do
+ mkdir $i/rmdir/conflict
+ done
+ mkfifo $OLD/rmdir/conflict/difftype
+ mkdir $TEST/rmdir/conflict/difftype
+
+ ## Tests for converting files to directories and vice versa
+ for i in $OLD $NEW $TEST; do
+ for j in already old fromdir todir; do
+ mkdir -p $i/dirchange/$j
+ done
+ done
+
+ # /dirchange/fromdir/extradir: Convert a directory tree to a
+ # file. The test tree includes an extra file in the directory
+ # that is not present in the old tree. This should generate a
+ # warning.
+ for i in $OLD $TEST; do
+ mkdir $i/dirchange/fromdir/extradir
+ echo "foo" > $i/dirchange/fromdir/extradir/file
+ done
+ mkfifo $TEST/dirchange/fromdir/extradir/fifo
+ ln -s "bar" $NEW/dirchange/fromdir/extradir
+
+ # /dirchange/fromdir/conflict: Convert a directory tree to a
+ # file. The test tree includes a local change that generates
+ # a warning and prevents the removal of the directory.
+ for i in $OLD $TEST; do
+ mkdir $i/dirchange/fromdir/conflict
+ done
+ echo "foo" > $OLD/dirchange/fromdir/conflict/somefile
+ echo "bar" > $TEST/dirchange/fromdir/conflict/somefile
+ mkfifo $NEW/dirchange/fromdir/conflict
+
+ # /dirchange/todir/difffile: Convert a file to a directory
+ # tree. The test tree has a locally modified version of the
+ # file so that the conversion fails with a warning.
+ echo "foo" > $OLD/dirchange/todir/difffile
+ mkdir $NEW/dirchange/todir/difffile
+ echo "baz" > $NEW/dirchange/todir/difffile/file
+ echo "bar" > $TEST/dirchange/todir/difffile
+
+ # /dirchange/todir/difftype: Similar to the previous test, but
+ # the conflict is due to a change in the file type.
+ echo "foo" > $OLD/dirchange/todir/difftype
+ mkdir $NEW/dirchange/todir/difftype
+ echo "baz" > $NEW/dirchange/todir/difftype/file
+ mkfifo $TEST/dirchange/todir/difftype
+}
+
+# $1 - relative path to file that should be missing from TEST
+missing()
+{
+ if [ -e $TEST/$1 -o -L $TEST/$1 ]; then
+ echo "File $1 should be missing"
+ FAILED=yes
+ fi
+}
+
+# $1 - relative path to file that should be present in TEST
+present()
+{
+ if ! [ -e $TEST/$1 -o -L $TEST/$1 ]; then
+ echo "File $1 should be present"
+ FAILED=yes
+ fi
+}
+
+# $1 - relative path to file that should be a fifo in TEST
+fifo()
+{
+ if ! [ -p $TEST/$1 ]; then
+ echo "File $1 should be a FIFO"
+ FAILED=yes
+ fi
+}
+
+# $1 - relative path to file that should be a directory in TEST
+dir()
+{
+ if ! [ -d $TEST/$1 ]; then
+ echo "File $1 should be a directory"
+ FAILED=yes
+ fi
+}
+
+# $1 - relative path to file that should be a symlink in TEST
+# $2 - optional value of the link
+link()
+{
+ local val
+
+ if ! [ -L $TEST/$1 ]; then
+ echo "File $1 should be a link"
+ FAILED=yes
+ elif [ $# -gt 1 ]; then
+ val=`readlink $TEST/$1`
+ if [ "$val" != "$2" ]; then
+ echo "Link $1 should link to \"$2\""
+ FAILED=yes
+ fi
+ fi
+}
+
+# $1 - relative path to regular file that should be present in TEST
+# $2 - optional string that should match file contents
+# $3 - optional MD5 of the flie contents, overrides $2 if present
+file()
+{
+ local contents sum
+
+ if ! [ -f $TEST/$1 ]; then
+ echo "File $1 should be a regular file"
+ FAILED=yes
+ elif [ $# -eq 2 ]; then
+ contents=`cat $TEST/$1`
+ if [ "$contents" != "$2" ]; then
+ echo "File $1 has wrong contents"
+ FAILED=yes
+ fi
+ elif [ $# -eq 3 ]; then
+ sum=`md5 -q $TEST/$1`
+ if [ "$sum" != "$3" ]; then
+ echo "File $1 has wrong contents"
+ FAILED=yes
+ fi
+ fi
+}
+
+# $1 - relative path to a regular file that should have a conflict
+# $2 - optional MD5 of the conflict file contents
+conflict()
+{
+ local sum
+
+ if ! [ -f $CONFLICTS/$1 ]; then
+ echo "File $1 missing conflict"
+ FAILED=yes
+ elif [ $# -gt 1 ]; then
+ sum=`md5 -q $CONFLICTS/$1`
+ if [ "$sum" != "$2" ]; then
+ echo "Conflict $1 has wrong contents"
+ FAILED=yes
+ fi
+ fi
+}
+
+# $1 - relative path to a regular file that should not have a conflict
+noconflict()
+{
+ if [ -f $CONFLICTS/$1 ]; then
+ echo "File $1 should not have a conflict"
+ FAILED=yes
+ fi
+}
+
+if [ `id -u` -ne 0 ]; then
+ echo "must be root"
+ exit 0
+fi
+
+if [ -r /etc/etcupdate.conf ]; then
+ echo "WARNING: /etc/etcupdate.conf settings may break some tests."
+fi
+
+# First run the test ignoring no patterns.
+
+build_trees
+
+$COMMAND -r -d $WORKDIR -D $TEST > $WORKDIR/test.out
+
+cat > $WORKDIR/correct.out <<EOF
+ D /dirchange/fromdir/extradir/file
+ C /difffiles/difffiles/difffiles/conflict
+ C /difftype/difftype/difffiles/file
+ C /second/second/difffiles/file
+Warnings:
+ Modified regular file remains: /dirchange/fromdir/conflict/somefile
+ Modified regular file remains: /first/difffiles/second/file
+ Modified symbolic link remains: /first/difflinks/second/link
+ Modified directory remains: /first/difftype/second/fifo
+ Modified directory remains: /rmdir/conflict/difftype
+ Non-empty directory remains: /rmdir/extra
+ Non-empty directory remains: /rmdir/conflict
+ Modified mismatch: /difffiles/difftype/difftype/file (regular file vs fifo file)
+ Removed file changed: /difffiles/first/first/file
+ Modified link changed: /difflinks/difflinks/difflinks/link ("old" became "new")
+ Modified mismatch: /difflinks/difftype/difftype/link (symbolic link vs regular file)
+ Removed link changed: /difflinks/first/first/link ("old" became "new")
+ New link conflict: /difftype/difftype/difflinks/link ("new" vs "test")
+ Modified regular file changed: /difftype/difftype/difftype/one (fifo file became directory)
+ Modified symbolic link changed: /difftype/difftype/difftype/two (directory became regular file)
+ Remove mismatch: /difftype/first/first/fifo (fifo file became directory)
+ Modified directory changed: /dirchange/fromdir/conflict (directory became fifo file)
+ Modified directory changed: /dirchange/fromdir/extradir (directory became symbolic link)
+ Modified regular file changed: /dirchange/todir/difffile (regular file became directory)
+ Modified fifo file changed: /dirchange/todir/difftype (regular file became directory)
+ New file mismatch: /adddir/conflict (directory vs regular file)
+ Directory mismatch: $TEST/adddir/conflict (regular file)
+ Directory mismatch: $TEST/dirchange/todir/difffile (regular file)
+ Directory mismatch: $TEST/dirchange/todir/difftype (fifo file)
+ New link conflict: /second/second/difflinks/link ("new link" vs "test link")
+ New file mismatch: /second/second/difftype/dir (directory vs fifo file)
+EOF
+
+echo "Differences for regular:"
+diff -u -L "correct" $WORKDIR/correct.out -L "test" $WORKDIR/test.out \
+ || FAILED=yes
+
+## /first/difftype/second:
+present /first/difftype/second/fifo
+
+## /first/difflinks/second:
+link /first/difflinks/second/link "test link"
+
+## /first/difffiles/second:
+file /first/difffiles/second/file "bar"
+
+## /second/second/difftype:
+fifo /second/second/difftype/dir
+
+## /second/second/difflinks:
+link /second/second/difflinks/link "test link"
+
+## /second/second/difffiles:
+file /second/second/difffiles/file "test"
+conflict /second/second/difffiles/file 4f2ee8620a251fd53f06bb6112eb6ffa
+
+## /difftype/first/first:
+missing /difftype/first/first/fifo
+
+## /difftype/difftype/difftype:
+file /difftype/difftype/difftype/one "foo"
+link /difftype/difftype/difftype/two "bar"
+
+## /difftype/difftype/difflinks:
+link /difftype/difftype/difflinks/link "test"
+
+## /difftype/difftype/difffile:
+conflict /difftype/difftype/difffiles/file 117f2bcd1f6491f6044e79e5a57a9229
+
+## /difflinks/first/first:
+missing /difflinks/first/first/link
+
+## /difflinks/difftype/difftype:
+file /difflinks/difftype/difftype/link "test"
+
+## /difflinks/difflinks/difflinks:
+link /difflinks/difflinks/difflinks/link "test"
+
+## /difffiles/first/first:
+missing /difffiles/first/first/file
+
+## /difffiles/difftype/difftype:
+fifo /difffiles/difftype/difftype/file
+
+## /difffiles/difffiles/difffiles:
+file /difffiles/difffiles/difffiles/conflict "this is a test file"
+conflict /difffiles/difffiles/difffiles/conflict \
+ 8261cfdd89280c4a6c26e4ac86541fe9
+
+## /adddir/conflict:
+file /adddir/conflict
+
+## /rmdir/extra:
+dir /rmdir/extra
+file /rmdir/extra/localfile.txt "foo"
+
+## /rmdir/conflict:
+dir /rmdir/conflict/difftype
+present /rmdir/conflict
+
+## /dirchange/fromdir/extradir:
+missing /dirchange/fromdir/extradir/file
+fifo /dirchange/fromdir/extradir/fifo
+
+## /dirchange/fromdir/conflict:
+file /dirchange/fromdir/conflict/somefile "bar"
+
+## /dirchange/todir/difffile:
+file /dirchange/todir/difffile "bar"
+
+## /dirchange/todir/difftype:
+fifo /dirchange/todir/difftype
+
+# Now test with -A '/first*' -A '/second* /*di*'. This should remove
+# most of the warnings and conflicts.
+
+build_trees
+
+$COMMAND -r -A '/first*' -A '/second* /*di*' -d $WORKDIR -D $TEST > \
+ $WORKDIR/test1.out
+
+cat > $WORKDIR/correct1.out <<EOF
+ D /dirchange/fromdir/extradir/file
+ U /difffiles/difffiles/difffiles/conflict
+ U /difffiles/difftype/difftype/file
+ A /difffiles/first/first/file
+ U /difflinks/difflinks/difflinks/link
+ U /difflinks/difftype/difftype/link
+ A /difflinks/first/first/link
+ U /difftype/difftype/difffiles/file
+ U /difftype/difftype/difflinks/link
+ D /difftype/difftype/difftype/one
+ U /difftype/difftype/difftype/two
+ U /dirchange/todir/difffile
+ U /dirchange/todir/difftype
+ U /adddir/conflict
+ A /adddir/conflict/newfile
+ A /dirchange/todir/difffile/file
+ A /dirchange/todir/difftype/file
+ U /second/second/difffiles/file
+ U /second/second/difflinks/link
+ D /second/second/difftype/dir
+Warnings:
+ Modified regular file remains: /dirchange/fromdir/conflict/somefile
+ Modified regular file remains: /first/difffiles/second/file
+ Modified symbolic link remains: /first/difflinks/second/link
+ Modified directory remains: /first/difftype/second/fifo
+ Modified directory remains: /rmdir/conflict/difftype
+ Non-empty directory remains: /rmdir/extra
+ Non-empty directory remains: /rmdir/conflict
+ Modified directory changed: /dirchange/fromdir/conflict (directory became fifo file)
+ Modified directory changed: /dirchange/fromdir/extradir (directory became symbolic link)
+EOF
+
+echo "Differences for -A '/first*' -A '/second* /*di*':"
+diff -u -L "correct" $WORKDIR/correct1.out -L "test" $WORKDIR/test1.out \
+ || FAILED=yes
+
+## /first/difftype/second:
+present /first/difftype/second/fifo
+
+## /first/difflinks/second:
+link /first/difflinks/second/link "test link"
+
+## /first/difffiles/second:
+file /first/difffiles/second/file "bar"
+
+## /second/second/difftype:
+missing /second/second/difftype/dir
+
+## /second/second/difflinks:
+link /second/second/difflinks/link "new link"
+
+## /second/second/difffiles:
+file /second/second/difffiles/file "new"
+noconflict /second/second/difffiles/file
+
+## /difftype/first/first:
+missing /difftype/first/first/fifo
+
+## /difftype/difftype/difftype:
+missing /difftype/difftype/difftype/one
+file /difftype/difftype/difftype/two "baz"
+
+## /difftype/difftype/difflinks:
+link /difftype/difftype/difflinks/link "new"
+
+## /difftype/difftype/difffile:
+noconflict /difftype/difftype/difffiles/file
+file /difftype/difftype/difffiles/file "foo"
+
+## /difflinks/first/first:
+link /difflinks/first/first/link "new"
+
+## /difflinks/difftype/difftype:
+link /difflinks/difftype/difftype/link "new"
+
+## /difflinks/difflinks/difflinks:
+link /difflinks/difflinks/difflinks/link "new"
+
+## /difffiles/first/first:
+file /difffiles/first/first/file "bar"
+
+## /difffiles/difftype/difftype:
+file /difffiles/difftype/difftype/file "new"
+
+## /difffiles/difffiles/difffiles:
+noconflict /difffiles/difffiles/difffiles/conflict
+file /difffiles/difffiles/difffiles/conflict "this is a new file"
+
+## /adddir/conflict:
+file /adddir/conflict/newfile
+
+## /rmdir/extra:
+dir /rmdir/extra
+file /rmdir/extra/localfile.txt "foo"
+
+## /rmdir/conflict:
+dir /rmdir/conflict/difftype
+present /rmdir/conflict
+
+## /dirchange/fromdir/extradir:
+missing /dirchange/fromdir/extradir/file
+fifo /dirchange/fromdir/extradir/fifo
+
+## /dirchange/fromdir/conflict:
+file /dirchange/fromdir/conflict/somefile "bar"
+
+## /dirchange/todir/difffile:
+file /dirchange/todir/difffile/file "baz"
+
+## /dirchange/todir/difftype:
+file /dirchange/todir/difftype/file "baz"
+
+[ "${FAILED}" = no ]
diff --git a/usr.sbin/etcupdate/tests/conflicts_test.sh b/usr.sbin/etcupdate/tests/conflicts_test.sh
new file mode 100644
index 0000000..71f16fa
--- /dev/null
+++ b/usr.sbin/etcupdate/tests/conflicts_test.sh
@@ -0,0 +1,294 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Hudson River Trading LLC
+# Written by: John H. 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$
+
+# Various regression tests to run for the 'resolve' command.
+
+FAILED=no
+WORKDIR=work
+
+usage()
+{
+ echo "Usage: conflicts.sh [-s script] [-w workdir]"
+ exit 1
+}
+
+# Allow the user to specify an alternate work directory or script.
+COMMAND=etcupdate
+while getopts "s:w:" option; do
+ case $option in
+ s)
+ COMMAND="sh $OPTARG"
+ ;;
+ w)
+ WORKDIR=$OPTARG
+ ;;
+ *)
+ echo
+ usage
+ ;;
+ esac
+done
+shift $((OPTIND - 1))
+if [ $# -ne 0 ]; then
+ usage
+fi
+
+CONFLICTS=$WORKDIR/conflicts
+OLD=$WORKDIR/old
+NEW=$WORKDIR/current
+TEST=$WORKDIR/test
+
+# These tests deal with conflicts to a single file. For each test, we
+# generate a conflict in /etc/login.conf. Each resolve option is tested
+# to ensure it DTRT.
+build_login_conflict()
+{
+
+ rm -rf $OLD $NEW $TEST $CONFLICTS
+ mkdir -p $OLD/etc $NEW/etc $TEST/etc
+
+ # Generate a conflict in /etc/login.conf.
+ cat > $OLD/etc/login.conf <<EOF
+default:\\
+ :passwd_format=md5:
+EOF
+ cat > $NEW/etc/login.conf <<EOF
+default:\\
+ :passwd_format=md5:\\
+ :copyright=/etc/COPYRIGHT
+EOF
+ cat > $TEST/etc/login.conf <<EOF
+default:\\
+ :passwd_format=md5:\\
+ :welcome=/etc/motd:
+EOF
+
+ $COMMAND -r -d $WORKDIR -D $TEST >/dev/null
+}
+
+# This is used to verify special handling for /etc/mail/aliases and
+# the newaliases warning.
+build_aliases_conflict()
+{
+
+ rm -rf $OLD $NEW $TEST $CONFLICTS
+ mkdir -p $OLD/etc/mail $NEW/etc/mail $TEST/etc/mail
+
+ # Generate a conflict in /etc/mail/aliases
+ cat > $OLD/etc/mail/aliases <<EOF
+# root: me@my.domain
+
+# Basic system aliases -- these MUST be present
+MAILER-DAEMON: postmaster
+postmaster: root
+EOF
+ cat > $NEW/etc/mail/aliases <<EOF
+# root: me@my.domain
+
+# Basic system aliases -- these MUST be present
+MAILER-DAEMON: postmaster
+postmaster: root
+
+# General redirections for pseudo accounts
+_dhcp: root
+_pflogd: root
+EOF
+ cat > $TEST/etc/mail/aliases <<EOF
+root: someone@example.com
+
+# Basic system aliases -- these MUST be present
+MAILER-DAEMON: postmaster
+postmaster: foo
+EOF
+
+ $COMMAND -r -d $WORKDIR -D $TEST >/dev/null
+}
+
+# $1 - relative path to file that should be missing from TEST
+missing()
+{
+ if [ -e $TEST/$1 -o -L $TEST/$1 ]; then
+ echo "File $1 should be missing"
+ FAILED=yes
+ fi
+}
+
+# $1 - relative path to file that should be present in TEST
+present()
+{
+ if ! [ -e $TEST/$1 -o -L $TEST/$1 ]; then
+ echo "File $1 should be present"
+ FAILED=yes
+ fi
+}
+
+# $1 - relative path to regular file that should be present in TEST
+# $2 - optional string that should match file contents
+# $3 - optional MD5 of the flie contents, overrides $2 if present
+file()
+{
+ local contents sum
+
+ if ! [ -f $TEST/$1 ]; then
+ echo "File $1 should be a regular file"
+ FAILED=yes
+ elif [ $# -eq 2 ]; then
+ contents=`cat $TEST/$1`
+ if [ "$contents" != "$2" ]; then
+ echo "File $1 has wrong contents"
+ FAILED=yes
+ fi
+ elif [ $# -eq 3 ]; then
+ sum=`md5 -q $TEST/$1`
+ if [ "$sum" != "$3" ]; then
+ echo "File $1 has wrong contents"
+ FAILED=yes
+ fi
+ fi
+}
+
+# $1 - relative path to a regular file that should have a conflict
+# $2 - optional MD5 of the conflict file contents
+conflict()
+{
+ local sum
+
+ if ! [ -f $CONFLICTS/$1 ]; then
+ echo "File $1 missing conflict"
+ FAILED=yes
+ elif [ $# -gt 1 ]; then
+ sum=`md5 -q $CONFLICTS/$1`
+ if [ "$sum" != "$2" ]; then
+ echo "Conflict $1 has wrong contents"
+ FAILED=yes
+ fi
+ fi
+}
+
+# $1 - relative path to a regular file that should no longer have a conflict
+resolved()
+{
+ if [ -f $CONFLICTS/$1 ]; then
+ echo "Conflict $1 should be resolved"
+ FAILED=yes
+ fi
+}
+
+if [ `id -u` -ne 0 ]; then
+ echo "must be root"
+ exit 0
+fi
+
+if [ -r /etc/etcupdate.conf ]; then
+ echo "WARNING: /etc/etcupdate.conf settings may break some tests."
+fi
+
+# Test each of the following resolve options: 'p', 'mf', 'tf', 'r'.
+
+build_login_conflict
+
+# Verify that 'p' doesn't do anything.
+echo "Checking 'p':"
+echo 'p' | $COMMAND resolve -d $WORKDIR -D $TEST >/dev/null
+
+file /etc/login.conf "" 95de92ea3f1bb1bf4f612a8b5908cddd
+missing /etc/login.conf.db
+conflict /etc/login.conf
+
+# Verify that 'mf' removes the conflict, but does nothing else.
+echo "Checking 'mf':"
+echo 'mf' | $COMMAND resolve -d $WORKDIR -D $TEST >/dev/null
+
+file /etc/login.conf "" 95de92ea3f1bb1bf4f612a8b5908cddd
+missing /etc/login.conf.db
+resolved /etc/login.conf
+
+build_login_conflict
+
+# Verify that 'tf' installs the new version of the file.
+echo "Checking 'tf':"
+echo 'tf' | $COMMAND resolve -d $WORKDIR -D $TEST >/dev/null
+
+file /etc/login.conf "" 7774a0f9a3a372c7c109c32fd31c4b6b
+file /etc/login.conf.db
+resolved /etc/login.conf
+
+build_login_conflict
+
+# Verify that 'r' installs the resolved version of the file. To
+# simulate this, manually edit the merged file so that it doesn't
+# contain conflict markers.
+echo "Checking 'r':"
+cat > $CONFLICTS/etc/login.conf <<EOF
+default:\\
+ :passwd_format=md5:\\
+ :copyright=/etc/COPYRIGHT\\
+ :welcome=/etc/motd:
+EOF
+
+echo 'r' | $COMMAND resolve -d $WORKDIR -D $TEST >/dev/null
+
+file /etc/login.conf "" 966e25984b9b63da8eaac8479dcb0d4d
+file /etc/login.conf.db
+resolved /etc/login.conf
+
+build_aliases_conflict
+
+# Verify that 'p' and 'mf' do not generate the newaliases warning.
+echo "Checking newalias warning for 'p'":
+echo 'p' | $COMMAND resolve -d $WORKDIR -D $TEST | grep -q newalias
+if [ $? -eq 0 ]; then
+ echo "+ Extra warning"
+ FAILED=yes
+fi
+echo "Checking newalias warning for 'mf'":
+echo 'mf' | $COMMAND resolve -d $WORKDIR -D $TEST | grep -q newalias
+if [ $? -eq 0 ]; then
+ echo "+ Extra warning"
+ FAILED=yes
+fi
+
+# Verify that 'tf' and 'r' do generate the newaliases warning.
+build_aliases_conflict
+echo "Checking newalias warning for 'tf'":
+echo 'tf' | $COMMAND resolve -d $WORKDIR -D $TEST | grep -q newalias
+if [ $? -ne 0 ]; then
+ echo "- Missing warning"
+ FAILED=yes
+fi
+
+build_aliases_conflict
+cp $TEST/etc/mail/aliases $CONFLICTS/etc/mail/aliases
+echo 'r' | $COMMAND resolve -d $WORKDIR -D $TEST | grep -q newalias
+if [ $? -ne 0 ]; then
+ echo "- Missing warning"
+ FAILED=yes
+fi
+
+[ "${FAILED}" = no ]
diff --git a/usr.sbin/etcupdate/tests/fbsdid_test.sh b/usr.sbin/etcupdate/tests/fbsdid_test.sh
new file mode 100644
index 0000000..d8d5cce
--- /dev/null
+++ b/usr.sbin/etcupdate/tests/fbsdid_test.sh
@@ -0,0 +1,394 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Hudson River Trading LLC
+# Written by: John H. 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$
+
+# Various regression tests to test the -F flag to the 'update' command.
+
+FAILED=no
+WORKDIR=work
+
+usage()
+{
+ echo "Usage: fbsdid.sh [-s script] [-w workdir]"
+ exit 1
+}
+
+# Allow the user to specify an alternate work directory or script.
+COMMAND=etcupdate
+while getopts "s:w:" option; do
+ case $option in
+ s)
+ COMMAND="sh $OPTARG"
+ ;;
+ w)
+ WORKDIR=$OPTARG
+ ;;
+ *)
+ echo
+ usage
+ ;;
+ esac
+done
+shift $((OPTIND - 1))
+if [ $# -ne 0 ]; then
+ usage
+fi
+
+CONFLICTS=$WORKDIR/conflicts
+OLD=$WORKDIR/old
+NEW=$WORKDIR/current
+TEST=$WORKDIR/test
+
+# Store a FreeBSD ID string in a specified file. The first argument
+# is the file, the remaining arguments are the comment to use.
+store_id()
+{
+ local file
+
+ file=$1
+ shift
+
+ echo -n '# $FreeBSD' >> $file
+ echo -n "$@" >> $file
+ echo '$' >> $file
+}
+
+# These tests deal with FreeBSD ID string conflicts. We run the test
+# twice, once without -F and once with -F.
+build_trees()
+{
+ local i
+
+ rm -rf $OLD $NEW $TEST $CONFLICTS
+ mkdir -p $OLD $NEW $TEST
+
+ # remove: Remove a file where the only local difference is a
+ # change in the FreeBSD ID string.
+ store_id $OLD/remove
+ store_id $TEST/remove ": head/remove 12345 jhb "
+
+ # old: Modify a file where the only local difference between
+ # the old and test files is a change in the FreeBSD ID string.
+ store_id $OLD/old ": src/old,v 1.1 jhb Exp "
+ store_id $NEW/old ": head/old 12345 jhb "
+ store_id $TEST/old ": head/old 12000 jhb "
+ for i in $OLD $TEST; do
+ cat >> $i/old <<EOF
+
+an old file
+EOF
+ done
+ cat >> $NEW/old <<EOF
+
+a new file
+EOF
+
+ # already: Modify a file where the local file already matches
+ # the new file except for a change in the FreeBSD ID string.
+ store_id $OLD/already ": src/already,v 1.1 jhb Exp "
+ store_id $NEW/already ": head/already 12345 jhb "
+ store_id $TEST/already ": src/already,v 1.2 jhb Exp "
+ cat >> $OLD/already <<EOF
+
+another old file
+EOF
+ for i in $NEW $TEST; do
+ cat >> $i/already <<EOF
+
+another new file
+EOF
+ done
+
+ # add: Add a file that already exists where the only local
+ # difference is a change in the FreeBSD ID string.
+ store_id $NEW/add ": head/add 12345 jhb "
+ store_id $TEST/add ""
+
+ # conflict: Modify a file where the local file has a different
+ # FreeBSD ID string. This should still generate a conflict
+ # even in the -F case.
+ store_id $OLD/conflict ": head/conflict 12000 jhb "
+ store_id $NEW/conflict ": head/conflict 12345 jhb "
+ store_id $TEST/conflict ""
+ cat >> $OLD/conflict <<EOF
+
+this is the old file
+EOF
+ cat >> $NEW/conflict <<EOF
+
+this is the new file
+EOF
+ cat >> $TEST/conflict <<EOF
+
+this is the local file
+EOF
+
+ # local: A file with local modifications has a different
+ # FreeBSD ID string and the only differences between the old
+ # and new versions are a change in the FreeBSD ID string.
+ # This will just update the FreeBSD ID string in the -F case.
+ for i in $OLD $NEW $TEST; do
+ cat >> $i/local <<EOF
+# Some leading text
+#
+EOF
+ done
+
+ store_id $OLD/local ": head/local 12000 jhb "
+ store_id $NEW/local ": head/local 12345 jhb "
+ store_id $TEST/local ": src/local,v 1.5 jhb Exp "
+
+ for i in $OLD $NEW $TEST; do
+ cat >> $i/local <<EOF
+
+this is a file
+EOF
+ done
+
+ cat >> $TEST/local <<EOF
+
+these are some local mods to the file
+EOF
+
+ # local-already: A file with local modifications has the same
+ # FreeBSD ID string as the new version of the file and the
+ # only differences between the old and new versions are a
+ # change in the FreeBSD ID string. Nothing should happen in
+ # the -F case.
+ store_id $OLD/local-already ": head/local 12000 jhb "
+ for i in $NEW $TEST; do
+ store_id $i/local-already ": head/local 12345 jhb "
+ done
+
+ for i in $OLD $NEW $TEST; do
+ cat >> $i/local-already <<EOF
+
+this is a file
+EOF
+ done
+
+ cat >> $TEST/local-already <<EOF
+
+these are some local mods to the file
+EOF
+
+ # local-remove: A file removed locally changed it's FreeBSD ID
+ # but nothing else
+ store_id $OLD/local-remove ": head/local-remove 12000 jhb "
+ store_id $NEW/local-remove ": head/local-remove 12345 jhb "
+ for i in $OLD $NEW; do
+ cat >> $i/local-remove <<EOF
+
+this is a file
+EOF
+ done
+}
+
+# $1 - relative path to file that should be missing from TEST
+missing()
+{
+ if [ -e $TEST/$1 -o -L $TEST/$1 ]; then
+ echo "File $1 should be missing"
+ FAILED=yes
+ fi
+}
+
+# $1 - relative path to file that should be present in TEST
+present()
+{
+ if ! [ -e $TEST/$1 -o -L $TEST/$1 ]; then
+ echo "File $1 should be present"
+ FAILED=yes
+ fi
+}
+
+# $1 - relative path to regular file that should be present in TEST
+# $2 - optional string that should match file contents
+# $3 - optional MD5 of the flie contents, overrides $2 if present
+file()
+{
+ local contents sum
+
+ if ! [ -f $TEST/$1 ]; then
+ echo "File $1 should be a regular file"
+ FAILED=yes
+ elif [ $# -eq 2 ]; then
+ contents=`cat $TEST/$1`
+ if [ "$contents" != "$2" ]; then
+ echo "File $1 has wrong contents"
+ FAILED=yes
+ fi
+ elif [ $# -eq 3 ]; then
+ sum=`md5 -q $TEST/$1`
+ if [ "$sum" != "$3" ]; then
+ echo "File $1 has wrong contents"
+ FAILED=yes
+ fi
+ fi
+}
+
+# $1 - relative path to a regular file that should have a conflict
+# $2 - optional MD5 of the conflict file contents
+conflict()
+{
+ local sum
+
+ if ! [ -f $CONFLICTS/$1 ]; then
+ echo "File $1 missing conflict"
+ FAILED=yes
+ elif [ $# -gt 1 ]; then
+ sum=`md5 -q $CONFLICTS/$1`
+ if [ "$sum" != "$2" ]; then
+ echo "Conflict $1 has wrong contents"
+ FAILED=yes
+ fi
+ fi
+}
+
+# $1 - relative path to a regular file that should not have a conflict
+noconflict()
+{
+ if [ -f $CONFLICTS/$1 ]; then
+ echo "File $1 should not have a conflict"
+ FAILED=yes
+ fi
+}
+
+if [ `id -u` -ne 0 ]; then
+ echo "must be root"
+ exit 0
+fi
+
+if [ -r /etc/etcupdate.conf ]; then
+ echo "WARNING: /etc/etcupdate.conf settings may break some tests."
+fi
+
+# First run the test without -F.
+
+build_trees
+
+$COMMAND -r -d $WORKDIR -D $TEST > $WORKDIR/test.out
+
+cat > $WORKDIR/correct.out <<EOF
+ C /already
+ C /conflict
+ C /local
+ M /local-already
+ C /old
+ C /add
+Warnings:
+ Modified regular file remains: /remove
+ Removed file changed: /local-remove
+EOF
+
+echo "Differences for regular:"
+diff -u -L "correct" $WORKDIR/correct.out -L "test" $WORKDIR/test.out \
+ || FAILED=yes
+
+file /remove "" 1bb4776213af107077be78fead8a351c
+file /old "" 2f799a7addc4132563ef9b44adc66157
+conflict /old 8441be64a1540f2ff584015279682425
+file /already "" aa53bd506f65d01d766e7ba028585e1d
+conflict /already f44105abb1fa3293e95c5d77e428d418
+file /add "" 1dc8c617e541d1fd1b4c70212f71d8ae
+conflict /add f99081e0da9a07f3cfebb430c0414941
+file /conflict "" dc27978df125b0daeb7d9b93265f03fd
+conflict /conflict 868452f666fea1c60ffb918ad9ad9607
+file /local "" aa33e614b5e749449f230e2a2b0072eb
+conflict /local 3df93e64043c8e348fc625b93ea220f4
+file /local-already "" 0298b958a603049f45ae6a109c4f7fea
+missing /local-remove
+
+# Now test with -F.
+
+build_trees
+
+$COMMAND -rF -d $WORKDIR -D $TEST > $WORKDIR/testF.out
+
+cat > $WORKDIR/correctF.out <<EOF
+ D /remove
+ U /already
+ C /conflict
+ M /local
+ U /old
+ U /add
+EOF
+
+echo "Differences for -F:"
+diff -u -L "correct" $WORKDIR/correctF.out -L "test" $WORKDIR/testF.out \
+ || FAILED=yes
+
+missing /remove
+file /old "" 6a9f34f109d94406a4de3bc5d72de259
+noconflict /old
+file /already "" 21f4eca3aacc702c49878c8da7afd3d0
+noconflict /already
+file /add "" 0208bd647111fedf6318511712ab9e97
+noconflict /add
+file /conflict "" dc27978df125b0daeb7d9b93265f03fd
+conflict /conflict 868452f666fea1c60ffb918ad9ad9607
+file /local "" 3ed5a35e380c8a93fb5f599d4c052713
+file /local-already "" 0298b958a603049f45ae6a109c4f7fea
+missing /local-remove
+
+# Now test with -F and -A forcing all installs. (-A should have
+# precedence over -F)
+
+build_trees
+
+$COMMAND -A '/*' -rF -d $WORKDIR -D $TEST > $WORKDIR/testAF.out
+
+cat > $WORKDIR/correctAF.out <<EOF
+ D /remove
+ U /already
+ U /conflict
+ U /local
+ U /local-already
+ A /local-remove
+ U /old
+ U /add
+EOF
+
+echo "Differences for -A '/*' -F:"
+diff -u -L "correct" $WORKDIR/correctAF.out -L "test" $WORKDIR/testAF.out \
+ || FAILED=yes
+
+missing /remove
+file /old "" 6a9f34f109d94406a4de3bc5d72de259
+noconflict /old
+file /already "" 21f4eca3aacc702c49878c8da7afd3d0
+noconflict /already
+file /add "" 0208bd647111fedf6318511712ab9e97
+noconflict /add
+file /conflict "" 75ee141c4136beaf14e39de92efa84e4
+noconflict /conflict
+file /local "" 6a8fc5c2755b7a49015089f5e1dbe092
+file /local-already "" 49045f8b51542dd634655301cd296f66
+file /local-remove "" 5c38322efed4014797d7127f5c652d9d
+
+[ "${FAILED}" = no ]
diff --git a/usr.sbin/etcupdate/tests/ignore_test.sh b/usr.sbin/etcupdate/tests/ignore_test.sh
new file mode 100644
index 0000000..571dd24
--- /dev/null
+++ b/usr.sbin/etcupdate/tests/ignore_test.sh
@@ -0,0 +1,276 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Hudson River Trading LLC
+# Written by: John H. 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$
+
+# Various regression tests to test the -I flag to the 'update' command.
+
+FAILED=no
+WORKDIR=work
+
+usage()
+{
+ echo "Usage: ignore.sh [-s script] [-w workdir]"
+ exit 1
+}
+
+# Allow the user to specify an alternate work directory or script.
+COMMAND=etcupdate
+while getopts "s:w:" option; do
+ case $option in
+ s)
+ COMMAND="sh $OPTARG"
+ ;;
+ w)
+ WORKDIR=$OPTARG
+ ;;
+ *)
+ echo
+ usage
+ ;;
+ esac
+done
+shift $((OPTIND - 1))
+if [ $# -ne 0 ]; then
+ usage
+fi
+
+CONFLICTS=$WORKDIR/conflicts
+OLD=$WORKDIR/old
+NEW=$WORKDIR/current
+TEST=$WORKDIR/test
+
+# These tests deal with ignoring certain patterns of files. We run the
+# test multiple times ignoring different patterns.
+build_trees()
+{
+ local i
+
+ rm -rf $OLD $NEW $TEST $CONFLICTS
+ mkdir -p $OLD $NEW $TEST
+
+ for i in $OLD $NEW $TEST; do
+ mkdir -p $i/tree
+ done
+
+ # tree: Test three different cases (add, modify, remove) that all
+ # match the tree/* glob.
+ echo "foo" > $NEW/tree/add
+ for i in $OLD $TEST; do
+ echo "old" > $i/tree/modify
+ done
+ echo "new" > $NEW/tree/modify
+ for i in $OLD $TEST; do
+ echo "old" > $i/tree/remove
+ done
+
+ # rmdir: Remove a whole tree.
+ for i in $OLD $TEST; do
+ mkdir $i/rmdir
+ echo "foo" > $i/rmdir/file
+ done
+}
+
+# $1 - relative path to file that should be missing from TEST
+missing()
+{
+ if [ -e $TEST/$1 -o -L $TEST/$1 ]; then
+ echo "File $1 should be missing"
+ FAILED=yes
+ fi
+}
+
+# $1 - relative path to file that should be present in TEST
+present()
+{
+ if ! [ -e $TEST/$1 -o -L $TEST/$1 ]; then
+ echo "File $1 should be present"
+ FAILED=yes
+ fi
+}
+
+# $1 - relative path to file that should be a directory in TEST
+dir()
+{
+ if ! [ -d $TEST/$1 ]; then
+ echo "File $1 should be a directory"
+ FAILED=yes
+ fi
+}
+
+# $1 - relative path to regular file that should be present in TEST
+# $2 - optional string that should match file contents
+# $3 - optional MD5 of the flie contents, overrides $2 if present
+file()
+{
+ local contents sum
+
+ if ! [ -f $TEST/$1 ]; then
+ echo "File $1 should be a regular file"
+ FAILED=yes
+ elif [ $# -eq 2 ]; then
+ contents=`cat $TEST/$1`
+ if [ "$contents" != "$2" ]; then
+ echo "File $1 has wrong contents"
+ FAILED=yes
+ fi
+ elif [ $# -eq 3 ]; then
+ sum=`md5 -q $TEST/$1`
+ if [ "$sum" != "$3" ]; then
+ echo "File $1 has wrong contents"
+ FAILED=yes
+ fi
+ fi
+}
+
+# $1 - relative path to a regular file that should have a conflict
+# $2 - optional MD5 of the conflict file contents
+conflict()
+{
+ local sum
+
+ if ! [ -f $CONFLICTS/$1 ]; then
+ echo "File $1 missing conflict"
+ FAILED=yes
+ elif [ $# -gt 1 ]; then
+ sum=`md5 -q $CONFLICTS/$1`
+ if [ "$sum" != "$2" ]; then
+ echo "Conflict $1 has wrong contents"
+ FAILED=yes
+ fi
+ fi
+}
+
+# $1 - relative path to a regular file that should not have a conflict
+noconflict()
+{
+ if [ -f $CONFLICTS/$1 ]; then
+ echo "File $1 should not have a conflict"
+ FAILED=yes
+ fi
+}
+
+if [ `id -u` -ne 0 ]; then
+ echo "must be root"
+ exit 0
+fi
+
+if [ -r /etc/etcupdate.conf ]; then
+ echo "WARNING: /etc/etcupdate.conf settings may break some tests."
+fi
+
+# First run the test ignoring no patterns.
+
+build_trees
+
+$COMMAND -r -d $WORKDIR -D $TEST > $WORKDIR/test.out
+
+cat > $WORKDIR/correct.out <<EOF
+ D /rmdir/file
+ D /tree/remove
+ D /rmdir
+ U /tree/modify
+ A /tree/add
+EOF
+
+echo "Differences for regular:"
+diff -u -L "correct" $WORKDIR/correct.out -L "test" $WORKDIR/test.out \
+ || FAILED=yes
+
+missing /tree/remove
+file /tree/modify "new"
+file /tree/add "foo"
+missing /rmdir/file
+missing /rmdir
+
+# Now test with -I '/tree/*'. This should preserve the /tree files.
+
+build_trees
+
+$COMMAND -r -I '/tree/*' -d $WORKDIR -D $TEST > $WORKDIR/test1.out
+
+cat > $WORKDIR/correct1.out <<EOF
+ D /rmdir/file
+ D /rmdir
+EOF
+
+echo "Differences for -I '/tree/*':"
+diff -u -L "correct" $WORKDIR/correct1.out -L "test" $WORKDIR/test1.out \
+ || FAILED=yes
+
+file /tree/remove "old"
+file /tree/modify "old"
+missing /tree/add
+missing /rmdir/file
+missing /rmdir
+
+# Now test with two patterns. This should preserve everything.
+
+build_trees
+
+$COMMAND -r -I '/tree/*' -I '/rmdir*' -d $WORKDIR -D $TEST > \
+ $WORKDIR/test2.out
+
+cat > $WORKDIR/correct2.out <<EOF
+EOF
+
+echo "Differences for -I '/tree/*' -I '/rmdir*':"
+
+diff -u -L "correct" $WORKDIR/correct2.out -L "test" $WORKDIR/test2.out \
+ || FAILED=yes
+
+file /tree/remove "old"
+file /tree/modify "old"
+missing /tree/add
+file /rmdir/file "foo"
+
+# Now test with a pattern that should cause a warning on /rmdir by
+# only ignoring the files under that directory. Note that this also
+# tests putting two patterns into a single -I argument.
+
+build_trees
+
+$COMMAND -r -I '/tree/* /rmdir/*' -d $WORKDIR -D $TEST > \
+ $WORKDIR/test3.out
+
+cat > $WORKDIR/correct3.out <<EOF
+Warnings:
+ Non-empty directory remains: /rmdir
+EOF
+
+echo "Differences for -I '/tree/* /rmdir/*':"
+
+diff -u -L "correct" $WORKDIR/correct3.out -L "test" $WORKDIR/test3.out \
+ || FAILED=yes
+
+file /tree/remove "old"
+file /tree/modify "old"
+missing /tree/add
+file /rmdir/file "foo"
+dir /rmdir
+
+[ "${FAILED}" = no ]
diff --git a/usr.sbin/etcupdate/tests/preworld_test.sh b/usr.sbin/etcupdate/tests/preworld_test.sh
new file mode 100644
index 0000000..b724154
--- /dev/null
+++ b/usr.sbin/etcupdate/tests/preworld_test.sh
@@ -0,0 +1,252 @@
+#!/bin/sh
+#
+# Copyright (c) 2013 Hudson River Trading LLC
+# Written by: John H. 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$
+
+# Regression tests for the pre-world (-p) mode
+
+FAILED=no
+WORKDIR=work
+
+usage()
+{
+ echo "Usage: preworld.sh [-s script] [-w workdir]"
+ exit 1
+}
+
+# Allow the user to specify an alternate work directory or script.
+COMMAND=etcupdate
+while getopts "s:w:" option; do
+ case $option in
+ s)
+ COMMAND="sh $OPTARG"
+ ;;
+ w)
+ WORKDIR=$OPTARG
+ ;;
+ *)
+ echo
+ usage
+ ;;
+ esac
+done
+shift $((OPTIND - 1))
+if [ $# -ne 0 ]; then
+ usage
+fi
+
+CONFLICTS=$WORKDIR/conflicts
+SRC=$WORKDIR/src
+OLD=$WORKDIR/current
+TEST=$WORKDIR/test
+
+build_trees()
+{
+
+ # Populate trees with pre-world files and additional files
+ # that should not be touched.
+
+ rm -rf $SRC $OLD $TEST $CONFLICTS
+
+ # Create the "old" source tree as the starting point
+ mkdir -p $OLD/etc
+ cat >> $OLD/etc/master.passwd <<EOF
+#
+root::0:0::0:0:Charlie &:/root:/bin/csh
+toor:*:0:0::0:0:Bourne-again Superuser:/root:
+daemon:*:1:1::0:0:Owner of many system processes:/root:/usr/sbin/nologin
+operator:*:2:5::0:0:System &:/:/usr/sbin/nologin
+_dhcp:*:65:65::0:0:dhcp programs:/var/empty:/usr/sbin/nologin
+uucp:*:66:66::0:0:UUCP pseudo-user:/var/spool/uucppublic:/usr/local/libexec/uucp/uucico
+pop:*:68:6::0:0:Post Office Owner:/nonexistent:/usr/sbin/nologin
+www:*:80:80::0:0:World Wide Web Owner:/nonexistent:/usr/sbin/nologin
+hast:*:845:845::0:0:HAST unprivileged user:/var/empty:/usr/sbin/nologin
+nobody:*:65534:65534::0:0:Unprivileged user:/nonexistent:/usr/sbin/nologin
+EOF
+ cat >> $OLD/etc/group <<EOF
+#
+wheel:*:0:root
+daemon:*:1:
+kmem:*:2:
+sys:*:3:
+tty:*:4:
+operator:*:5:root
+_dhcp:*:65:
+uucp:*:66:
+dialer:*:68:
+network:*:69:
+www:*:80:
+hast:*:845:
+nogroup:*:65533:
+nobody:*:65534:
+EOF
+ cat >> $OLD/etc/inetd.conf <<EOF
+# Yet another file
+EOF
+
+ # Copy the "old" source tree to the test tree and make local
+ # modifications.
+ cp -R $OLD $TEST
+ sed -I "" -e 's/root::/root:<rpass>:/' $TEST/etc/master.passwd
+ cat >> $TEST/etc/master.passwd <<EOF
+john:<password>:1001:1001::0:0:John Baldwin:/home/john:/bin/tcsh
+messagebus:*:556:556::0:0:D-BUS Daemon User:/nonexistent:/usr/sbin/nologin
+polkit:*:562:562::0:0:PolicyKit User:/nonexistent:/usr/sbin/nologin
+haldaemon:*:560:560::0:0:HAL Daemon User:/nonexistent:/usr/sbin/nologin
+EOF
+ awk '/wheel/ { printf "%s,john\n", $0; next } // { print }' \
+ $OLD/etc/group > $TEST/etc/group
+ cat >> $TEST/etc/group <<EOF
+john:*:1001:
+messagebus:*:556:
+polkit:*:562:
+haldaemon:*:560:
+EOF
+ rm $TEST/etc/inetd.conf
+ touch $TEST/etc/localtime
+
+ # Copy the "old" source tree to the new source tree and
+ # make upstream modifications.
+ cp -R $OLD $SRC
+ sed -I "" -e '/:80:/i\
+auditdistd:*:78:77::0:0:Auditdistd unprivileged user:/var/empty:/usr/sbin/nologin' \
+ $SRC/etc/master.passwd
+ sed -I "" -e '/:80:/i\
+audit:*:77:' \
+ $SRC/etc/group
+ cat >> $SRC/etc/inetd.conf <<EOF
+# Making this larger
+EOF
+}
+
+# $1 - relative path to file that should be missing from TEST
+missing()
+{
+ if [ -e $TEST/$1 -o -L $TEST/$1 ]; then
+ echo "File $1 should be missing"
+ FAILED=yes
+ fi
+}
+
+# $1 - relative path to file that should be present in TEST
+present()
+{
+ if ! [ -e $TEST/$1 -o -L $TEST/$1 ]; then
+ echo "File $1 should be present"
+ FAILED=yes
+ fi
+}
+
+# $1 - relative path to regular file that should be present in TEST
+# $2 - optional string that should match file contents
+# $3 - optional MD5 of the flie contents, overrides $2 if present
+file()
+{
+ local contents sum
+
+ if ! [ -f $TEST/$1 ]; then
+ echo "File $1 should be a regular file"
+ FAILED=yes
+ elif [ $# -eq 2 ]; then
+ contents=`cat $TEST/$1`
+ if [ "$contents" != "$2" ]; then
+ echo "File $1 has wrong contents"
+ FAILED=yes
+ fi
+ elif [ $# -eq 3 ]; then
+ sum=`md5 -q $TEST/$1`
+ if [ "$sum" != "$3" ]; then
+ echo "File $1 has wrong contents"
+ FAILED=yes
+ fi
+ fi
+}
+
+# $1 - relative path to a regular file that should have a conflict
+# $2 - optional MD5 of the conflict file contents
+conflict()
+{
+ local sum
+
+ if ! [ -f $CONFLICTS/$1 ]; then
+ echo "File $1 missing conflict"
+ FAILED=yes
+ elif [ $# -gt 1 ]; then
+ sum=`md5 -q $CONFLICTS/$1`
+ if [ "$sum" != "$2" ]; then
+ echo "Conflict $1 has wrong contents"
+ FAILED=yes
+ fi
+ fi
+}
+
+check_trees()
+{
+
+ echo "Checking tree for correct results:"
+
+ file /etc/master.passwd "" 1385366e8b424d33d59b7d8a2bdb15d3
+ file /etc/group "" 21273f845f6ec0cda9188c4ddac9ed47
+ missing /etc/inetd.conf
+
+ # These should be auto-generated by pwd_mkdb
+ file /etc/passwd "" 9831537874bdc99adccaa2b0293248a1
+ file /etc/pwd.db
+ file /etc/spwd.db
+}
+
+if [ `id -u` -ne 0 ]; then
+ echo "must be root"
+ exit 0
+fi
+
+if [ -r /etc/etcupdate.conf ]; then
+ echo "WARNING: /etc/etcupdate.conf settings may break some tests."
+fi
+
+build_trees
+
+$COMMAND -np -s $SRC -d $WORKDIR -D $TEST > $WORKDIR/testn.out
+
+cat > $WORKDIR/correct.out <<EOF
+ M /etc/group
+ M /etc/master.passwd
+EOF
+
+echo "Differences for -n:"
+diff -u -L "correct" $WORKDIR/correct.out -L "test" $WORKDIR/testn.out \
+ || FAILED=yes
+
+$COMMAND -p -s $SRC -d $WORKDIR -D $TEST > $WORKDIR/test.out
+
+echo "Differences for real:"
+diff -u -L "correct" $WORKDIR/correct.out -L "test" $WORKDIR/test.out \
+ || FAILED=yes
+
+check_trees
+
+[ "${FAILED}" = no ]
diff --git a/usr.sbin/etcupdate/tests/tests_test.sh b/usr.sbin/etcupdate/tests/tests_test.sh
new file mode 100644
index 0000000..5382de3
--- /dev/null
+++ b/usr.sbin/etcupdate/tests/tests_test.sh
@@ -0,0 +1,1021 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Hudson River Trading LLC
+# Written by: John H. 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$
+
+# Various regression tests to run for the 'update' command.
+
+FAILED=no
+WORKDIR=work
+
+usage()
+{
+ echo "Usage: tests.sh [-s script] [-w workdir]"
+ exit 1
+}
+
+# Allow the user to specify an alternate work directory or script.
+COMMAND=etcupdate
+while getopts "s:w:" option; do
+ case $option in
+ s)
+ COMMAND="sh $OPTARG"
+ ;;
+ w)
+ WORKDIR=$OPTARG
+ ;;
+ *)
+ echo
+ usage
+ ;;
+ esac
+done
+shift $((OPTIND - 1))
+if [ $# -ne 0 ]; then
+ usage
+fi
+
+CONFLICTS=$WORKDIR/conflicts
+OLD=$WORKDIR/old
+NEW=$WORKDIR/current
+TEST=$WORKDIR/test
+
+# The various states of the comparison of a file between two trees.
+states="equal first second difftype difflinks difffiles"
+
+build_trees()
+{
+ local i j k
+
+ rm -rf $OLD $NEW $TEST $CONFLICTS
+ mkdir -p $OLD/etc $NEW/etc $TEST/etc
+
+ # For an given file, there are three different pair-wise
+ # relations between the three threes (old, new, and test): old
+ # vs new, old vs test, and new vs test. Each of these
+ # relations takes on one of six different states from the
+ # 'compare()' function in etcupdate: equal, onlyfirst,
+ # onlysecond, difftype, difflinks, difffiles. In addition,
+ # there are special considerations for considering cases such
+ # as a file merge that results in conflicts versus one that
+ # does not, special treatment of directories, etc. The tests
+ # below attempt to enumerate the three dimensional test matrix
+ # by having the path name use the three different tree states
+ # for the parent directories.
+ #
+ # Note that if the old and new files are identical (so first
+ # compare is "equal"), then the second and third comparisons
+ # will be the same.
+ #
+ # Note also that etcupdate only cares about files that are
+ # present in at least one of the old or new trees. Thus, none
+ # of the '*/second/second' cases are relevant.
+
+ for i in $states; do
+ for j in $states; do
+ for k in $states; do
+ mkdir -p $OLD/$i/$j/$k $NEW/$i/$j/$k \
+ $TEST/$i/$j/$k
+ done
+ done
+ done
+
+ # /equal/equal/equal: Everything is equal. Nothing should happen.
+ for i in $OLD $NEW $TEST; do
+ mkfifo $i/equal/equal/equal/fifo
+ echo "foo" > $i/equal/equal/equal/file
+ mkdir $i/equal/equal/equal/dir
+ ln -s "bar" $i/equal/equal/equal/link
+ done
+
+ # /equal/first/first: The file is missing from the test
+ # directory. Nothing should happen.
+ for i in $OLD $NEW; do
+ mkfifo $i/equal/first/first/fifo
+ echo "foo" > $i/equal/first/first/file
+ mkdir $i/equal/first/first/dir
+ ln -s "bar" $i/equal/first/first/link
+ done
+
+ # /equal/difftype/difftype: The local file is a different
+ # type. Nothing should happen.
+ for i in $OLD $NEW; do
+ mkfifo $i/equal/difftype/difftype/fifo
+ mkdir $i/equal/difftype/difftype/fromdir
+ done
+ echo "bar" > $TEST/equal/difftype/difftype/fifo
+ ln -s "test" $TEST/equal/difftype/difftype/fromdir
+
+ # /equal/difflinks/difflinks: The local file is a modified
+ # link. Nothing should happen.
+ for i in $OLD $NEW; do
+ ln -s "foo" $i/equal/difflinks/difflinks/link
+ done
+ ln -s "bar" $TEST/equal/difflinks/difflinks/link
+
+ # /equal/difffiles/difffiles: The local file is a modified
+ # file. Nothing should happen.
+ for i in $OLD $NEW; do
+ echo "foo" > $i/equal/difffiles/difffiles/file
+ done
+ echo "bar" > $TEST/equal/difffiles/difffiles/file
+
+ # /first/equal/second: Remove unmodified files. The files
+ # should all be removed.
+ for i in $OLD $TEST; do
+ mkfifo $i/first/equal/second/fifo
+ echo "foo" > $i/first/equal/second/file
+ mkdir $i/first/equal/second/emptydir
+ ln -s "bar" $i/first/equal/second/link
+ mkdir $i/first/equal/second/fulldir
+ echo "foo" > $i/first/equal/second/fulldir/file
+ done
+
+ # /first/equal/*: Cannot occur. If the file is missing from
+ # new, then new vs test will always be 'second'.
+
+ # /first/first/equal: Removed files are already removed.
+ # Nothing should happen.
+ mkfifo $OLD/first/first/equal/fifo
+ echo "foo" > $OLD/first/first/equal/file
+ mkdir $OLD/first/first/equal/dir
+ ln -s "bar" $OLD/first/first/equal/link
+
+ # /first/first/*: Cannot occur. The files are missing from
+ # both new and test.
+
+ # /first/second/*: Cannot happen, if the file is in old for
+ # old vs new, it cannot be missing for old vs test.
+
+ # /first/difftype/second: File with different local type
+ # removed. Should generate a warning.
+ mkfifo $OLD/first/difftype/second/fifo
+ mkdir $TEST/first/difftype/second/fifo
+
+ # /first/difftype/*: Cannot happen since the file is missing
+ # from new but present in test.
+
+ # /first/difflinks/second: Modified link removed. Should
+ # generate a warning.
+ ln -s "old link" $OLD/first/difflinks/second/link
+ ln -s "test link" $TEST/first/difflinks/second/link
+
+ # /first/difflinks/*: Cannot happen since the file is missing
+ # from new but present in test.
+
+ # /first/difffiles/second: Modified file removed. Should
+ # generate a warning.
+ echo "foo" > $OLD/first/difffiles/second/file
+ echo "bar" > $TEST/first/difffiles/second/file
+
+ # /first/difffiles/*: Cannot happen since the file is missing
+ # from new but present in test.
+
+ # /second/equal/first: Added a new file that isn't present in
+ # test. The empty directory should be ignored.
+ echo "bar" > $NEW/second/equal/first/file
+ mkfifo $NEW/second/equal/first/fifo
+ ln -s "new" $NEW/second/equal/first/link
+ mkdir $NEW/second/equal/first/emptydir
+ mkdir $NEW/second/equal/first/fulldir
+ echo "foo" > $NEW/second/equal/first/fulldir/file
+
+ # /second/equal/*: Cannot happen since the file is missing
+ # from test but present in new.
+
+ # /second/first/*: Cannot happen since the file is missing
+ # from old.
+
+ # /second/second/equal: Newly added file is already present in
+ # the test directory and identical to the new file. Nothing
+ # should happen.
+ for i in $NEW $TEST; do
+ mkfifo $i/second/second/equal/fifo
+ echo "foo" > $i/second/second/equal/file
+ mkdir $i/second/second/equal/dir
+ ln -s "bar" $i/second/second/equal/link
+ done
+
+ # /second/second/first: Cannot happen. The file is in dest in
+ # the second test, so it can't be missing from the third test.
+
+ # /second/second/second: Cannot happen. The file is in new in
+ # the first test, so it can't be missing from the third test.
+
+ # /second/second/difftype: Newly added file conflicts with
+ # existing file in test tree of a different type. Should
+ # generate a warning.
+ mkdir $NEW/second/second/difftype/dir
+ mkfifo $TEST/second/second/difftype/dir
+
+ # /second/second/difflinks: Newly added link conflicts with
+ # existing link in test tree. Should generate a warning.
+ ln -s "new link" $NEW/second/second/difflinks/link
+ ln -s "test link" $TEST/second/second/difflinks/link
+
+ # /second/second/difffiles: Newly added file conflicts with
+ # existing file in test tree. Should generate a warning.
+ echo "new" > $NEW/second/second/difffiles/file
+ echo "test" > $TEST/second/second/difffiles/file
+
+ # /second/difftype/*: Cannot happen since the file is missing
+ # from old.
+
+ # /second/difflinks/*: Cannot happen since the file is missing
+ # from old.
+
+ # /second/difffiles/*: Cannot happen since the file is missing
+ # from old.
+
+ # /difftype/equal/difftype: Unmodified file has changed type.
+ # File should be updated to the new file. In the 'todir' case
+ # the directory won't actually be created because it is empty.
+ for i in $OLD $TEST; do
+ echo "foo" > $i/difftype/equal/difftype/file
+ mkdir $i/difftype/equal/difftype/fromdir
+ ln -s "old" $i/difftype/equal/difftype/todir
+ done
+ ln -s "test" $NEW/difftype/equal/difftype/file
+ mkfifo $NEW/difftype/equal/difftype/fromdir
+ mkdir $NEW/difftype/equal/difftype/todir
+
+ # /difftype/equal/*: Cannot happen. Since the old file is a
+ # difftype from the new file and the test file is identical to
+ # the old file, the test file must be a difftype from the new
+ # file.
+
+ # /difftype/first/first: A removed file has changed type.
+ # This should generate a warning.
+ mkfifo $OLD/difftype/first/first/fifo
+ mkdir $NEW/difftype/first/first/fifo
+
+ # /difftype/first/*: Cannot happen. Since the new file exists
+ # and the dest file is missing, the last test must be 'first'.
+
+ # /difftype/second/*: Cannot happen. The old file exists in
+ # the first test, so it cannot be missing in the second test.
+
+ # /difftype/difftype/equal: A file has changed type, but the
+ # file in the test directory already matches the new file. Do
+ # nothing.
+ echo "foo" > $OLD/difftype/difftype/equal/fifo
+ mkfifo $OLD/difftype/difftype/equal/file
+ for i in $NEW $TEST; do
+ mkfifo $i/difftype/difftype/equal/fifo
+ echo "bar" > $i/difftype/difftype/equal/file
+ done
+
+ # /difftype/difftype/first: Cannot happen. The dest file
+ # exists in the second test.
+
+ # /difftype/difftype/second: Cannot happen. The new file
+ # exists in the first test.
+
+ # /difftype/difftype/difftype: All three files (old, new, and
+ # test) are different types from each other. This should
+ # generate a warning.
+ mkfifo $OLD/difftype/difftype/difftype/one
+ mkdir $NEW/difftype/difftype/difftype/one
+ echo "foo" > $TEST/difftype/difftype/difftype/one
+ mkdir $OLD/difftype/difftype/difftype/two
+ echo "baz" > $NEW/difftype/difftype/difftype/two
+ ln -s "bar" $TEST/difftype/difftype/difftype/two
+
+ # /difftype/difftype/difflinks: A file has changed from a
+ # non-link to a link in both the new and test trees, but the
+ # target of the new and test links differ. This should
+ # generate a new link conflict.
+ mkfifo $OLD/difftype/difftype/difflinks/link
+ ln -s "new" $NEW/difftype/difftype/difflinks/link
+ ln -s "test" $TEST/difftype/difftype/difflinks/link
+
+ # /difftype/difftype/difffile: A file has changed from a
+ # non-regular file to a regular file in both the new and test
+ # trees, but the contents in the new and test files differ.
+ # This should generate a new file conflict.
+ ln -s "old" $OLD/difftype/difftype/difffiles/file
+ echo "foo" > $NEW/difftype/difftype/difffiles/file
+ echo "bar" > $TEST/difftype/difftype/difffiles/file
+
+ # /difflinks/equal/difflinks: An unmodified symlink has
+ # changed. The link should be updated.
+ for i in $OLD $TEST; do
+ ln -s "old" $i/difflinks/equal/difflinks/link
+ done
+ ln -s "new" $NEW/difflinks/equal/difflinks/link
+
+ # /difflinks/equal/*: Cannot happen. Since old is identical
+ # to test, the third test must be 'difflinks'.
+
+ # /difflinks/first/first: A modified link is missing in the
+ # test tree. This should generate a warning.
+ ln -s "old" $OLD/difflinks/first/first/link
+ ln -s "new" $NEW/difflinks/first/first/link
+
+ # /difflinks/first/*: Cannot happen. Since the test file is
+ # missing in the second test, it must be missing in the third
+ # test.
+
+ # /difflinks/second/*: Cannot happen. The old link is present
+ # in the first test, so it cannot be missing in the second
+ # test.
+
+ # /difflinks/difftype/difftype: An updated link has been
+ # changed to a different file type in the test tree. This
+ # should generate a warning.
+ ln -s "old" $OLD/difflinks/difftype/difftype/link
+ ln -s "new" $NEW/difflinks/difftype/difftype/link
+ echo "test" > $TEST/difflinks/difftype/difftype/link
+
+ # /difflinks/difftype/*: Cannot happen. The old and new files
+ # are both links and the test file is not a link, so the third
+ # test must be 'difftype'.
+
+ # /difflinks/difflinks/equal: An updated link has already been
+ # updated to the new target in the test tree. Nothing should
+ # happen.
+ ln -s "old" $OLD/difflinks/difflinks/equal/link
+ for i in $NEW $TEST; do
+ ln -s "new" $i/difflinks/difflinks/equal/link
+ done
+
+ # /difflinks/difflinks/difflinks: An updated link has been
+ # modified in the test tree and doesn't match either the old
+ # or new links. This should generate a warning.
+ ln -s "old" $OLD/difflinks/difflinks/difflinks/link
+ ln -s "new" $NEW/difflinks/difflinks/difflinks/link
+ ln -s "test" $TEST/difflinks/difflinks/difflinks/link
+
+ # /difflinks/difflinks/*: Cannot happen. All three files are
+ # links from the first two tests, so the third test can only
+ # be 'equal' or 'difflink'.
+
+ # /difflinks/difffiles/*: Cannot happen. The old file is a
+ # link in the first test, so it cannot be a regular file in
+ # the second.
+
+ # /difffiles/equal/difffiles: An unmodified file has been
+ # changed in new tree. The file should be updated to the new
+ # version.
+ for i in $OLD $TEST; do
+ echo "foo" > $i/difffiles/equal/difffiles/file
+ done
+ echo "bar" > $NEW/difffiles/equal/difffiles/file
+
+ # /difffiles/equal/*: Cannot happen. Since the old file is
+ # identical to the test file, the third test must be
+ # 'difffiles'.
+
+ # /difffiles/first/first: A removed file has been changed in
+ # the new tree. This should generate a warning.
+ echo "foo" > $OLD/difffiles/first/first/file
+ echo "bar" > $NEW/difffiles/first/first/file
+
+ # /difffiles/first/*: Cannot happen. The new file is a
+ # regular file from the first test and the test file is
+ # missing in the second test, so the third test must be
+ # 'first'.
+
+ # /difffiles/second/*: Cannot happen. The old file is present
+ # in the first test, so it must be present in the second test.
+
+ # /difffiles/difftype/difftype: An updated regular file has
+ # been changed to a different file type in the test tree.
+ # This should generate a warning.
+ echo "old" > $OLD/difffiles/difftype/difftype/file
+ echo "new" > $NEW/difffiles/difftype/difftype/file
+ mkfifo $TEST/difffiles/difftype/difftype/file
+
+ # /difffiles/difftype/*: Cannot happen. The new file is known
+ # to be a regular file from the first test, and the test file
+ # is known to exist as a different file type from the second
+ # test. The third test must be 'difftype'.
+
+ # /difffiles/difflink/*: Cannot happen. The old file is known
+ # to be a regular file from the first test, so it cannot be a
+ # link in the second test.
+
+ # /difffiles/difffiles/equal: An updated regular file has
+ # already been updated to match the new file in the test tree.
+ # Nothing should happen.
+ echo "foo" > $OLD/difffiles/difffiles/equal/file
+ for i in $NEW $TEST; do
+ echo "bar" > $i/difffiles/difffiles/equal/file
+ done
+
+ # /difffiles/difffiles/difffiles: A modified regular file was
+ # updated in the new tree. The changes should be merged into
+ # to the new file if possible. If the merge fails, a conflict
+ # should be generated.
+ cat > $OLD/difffiles/difffiles/difffiles/simple <<EOF
+this is an old line
+
+EOF
+ cat > $NEW/difffiles/difffiles/difffiles/simple <<EOF
+this is a new line
+
+EOF
+ cat > $TEST/difffiles/difffiles/difffiles/simple <<EOF
+this is an old line
+
+this is a local line
+EOF
+ cat > $OLD/difffiles/difffiles/difffiles/conflict <<EOF
+this is an old file
+EOF
+ cat > $NEW/difffiles/difffiles/difffiles/conflict <<EOF
+this is a new file
+EOF
+ cat > $TEST/difffiles/difffiles/difffiles/conflict <<EOF
+this is a test file
+EOF
+
+ # /difffiles/difffiles/*: Cannot happen. From the first three
+ # tests, all three files are regular files. The test file can
+ # either be identical to the new file ('equal') or not
+ # ('difffiles').
+
+ ## Tests for adding directories
+ mkdir -p $OLD/adddir $NEW/adddir $TEST/adddir
+
+ # /adddir/conflict: Add a new file in a directory that already
+ # exists as a file. This should generate two warnings.
+ mkdir $NEW/adddir/conflict
+ touch $NEW/adddir/conflict/newfile
+ touch $TEST/adddir/conflict
+
+ # /adddir/partial: Add a new file in a directory. The
+ # directory already exists in the test tree and contains a
+ # different local file. The new file from the new tree should
+ # be added.
+ for i in $NEW $TEST; do
+ mkdir $i/adddir/partial
+ done
+ echo "foo" > $NEW/adddir/partial/file
+ mkfifo $TEST/adddir/partial/fifo
+
+ ## Tests for removing directories
+ mkdir -p $OLD/rmdir $NEW/rmdir $TEST/rmdir
+
+ # /rmdir/extra: Do not remove a directory with an extra local file.
+ # This should generate a warning.
+ for i in $OLD $TEST; do
+ mkdir $i/rmdir/extra
+ done
+ echo "foo" > $TEST/rmdir/extra/localfile.txt
+
+ # /rmdir/conflict: Do not remove a directory with a conflicted
+ # remove file. This should generate a warning.
+ for i in $OLD $TEST; do
+ mkdir $i/rmdir/conflict
+ done
+ mkfifo $OLD/rmdir/conflict/difftype
+ mkdir $TEST/rmdir/conflict/difftype
+
+ # /rmdir/partial: Remove a complete hierarchy when part of the
+ # tree has already been removed locally.
+ for i in $OLD $TEST; do
+ mkdir -p $i/rmdir/partial/subdir
+ mkfifo $i/rmdir/partial/subdir/fifo
+ done
+ echo "foo" > $OLD/rmdir/partial/subdir/file
+
+ ## Tests for converting files to directories and vice versa
+ for i in $OLD $NEW $TEST; do
+ for j in already old fromdir todir; do
+ mkdir -p $i/dirchange/$j
+ done
+ done
+
+ # /dirchange/already/fromdir: Convert a directory tree to a
+ # file without conflicts where the test tree already has the
+ # new file. Nothing should happen.
+ mkdir $OLD/dirchange/already/fromdir
+ echo "blah" > $OLD/dirchange/already/fromdir/somefile
+ for i in $NEW $TEST; do
+ echo "bar" > $i/dirchange/already/fromdir
+ done
+
+ # /dirchange/already/todir: Convert an unmodified file to a
+ # directory tree where the test tree already has the new
+ # tree. Nothing should happen.
+ echo "baz" > $OLD/dirchange/already/todir
+ for i in $NEW $TEST; do
+ mkdir $i/dirchange/already/todir
+ echo "blah" > $i/dirchange/already/todir/somefile
+ done
+
+ # /dirchange/old/fromdir: Convert a directory tree to a file.
+ # The old files are unmodified and should be changed to the new tree.
+ for i in $OLD $TEST; do
+ mkdir $i/dirchange/old/fromdir
+ echo "blah" > $i/dirchange/old/fromdir/somefile
+ done
+ echo "bar" > $NEW/dirchange/old/fromdir
+
+ # /dirchange/old/todir: Convert a file to a directory tree.
+ # The old file is unmodified and should be changed to the new
+ # tree.
+ for i in $OLD $TEST; do
+ echo "foo" > $i/dirchange/old/todir
+ done
+ mkdir $NEW/dirchange/old/todir
+ echo "bar" > $NEW/dirchange/old/todir/file
+
+ # /dirchange/fromdir/extradir: Convert a directory tree to a
+ # file. The test tree includes an extra file in the directory
+ # that is not present in the old tree. This should generate a
+ # warning.
+ for i in $OLD $TEST; do
+ mkdir $i/dirchange/fromdir/extradir
+ echo "foo" > $i/dirchange/fromdir/extradir/file
+ done
+ mkfifo $TEST/dirchange/fromdir/extradir/fifo
+ ln -s "bar" $NEW/dirchange/fromdir/extradir
+
+ # /dirchange/fromdir/conflict: Convert a directory tree to a
+ # file. The test tree includes a local change that generates
+ # a warning and prevents the removal of the directory.
+ for i in $OLD $TEST; do
+ mkdir $i/dirchange/fromdir/conflict
+ done
+ echo "foo" > $OLD/dirchange/fromdir/conflict/somefile
+ echo "bar" > $TEST/dirchange/fromdir/conflict/somefile
+ mkfifo $NEW/dirchange/fromdir/conflict
+
+ # /dirchange/todir/difffile: Convert a file to a directory
+ # tree. The test tree has a locally modified version of the
+ # file so that the conversion fails with a warning.
+ echo "foo" > $OLD/dirchange/todir/difffile
+ mkdir $NEW/dirchange/todir/difffile
+ echo "baz" > $NEW/dirchange/todir/difffile/file
+ echo "bar" > $TEST/dirchange/todir/difffile
+
+ # /dirchange/todir/difftype: Similar to the previous test, but
+ # the conflict is due to a change in the file type.
+ echo "foo" > $OLD/dirchange/todir/difftype
+ mkdir $NEW/dirchange/todir/difftype
+ echo "baz" > $NEW/dirchange/todir/difftype/file
+ mkfifo $TEST/dirchange/todir/difftype
+
+ ## Tests for post-install actions
+
+ # - Adding /etc/master.passwd should cause pwd_mkdb to be run
+ echo "foo:*:16000:100::0:0:& user:/home/foo:/bin/tcsh" > \
+ $NEW/etc/master.passwd
+
+ # - Verify that updating an unmodified /etc/login.conf builds
+ # /etc/login.conf.db.
+ cat > $OLD/etc/login.conf <<EOF
+default:\\
+ :passwd_format=md5:
+EOF
+ cat > $NEW/etc/login.conf <<EOF
+default:\\
+ :passwd_format=md5:\\
+ :copyright=/etc/COPYRIGHT
+EOF
+ cp $OLD/etc/login.conf $TEST/etc/login.conf
+
+ # - Verify that a merge without conflicts to /etc/mail/aliases
+ # will trigger a newaliases run request.
+ mkdir -p $OLD/etc/mail $NEW/etc/mail $TEST/etc/mail
+ cat > $OLD/etc/mail/aliases <<EOF
+# root: me@my.domain
+
+# Basic system aliases -- these MUST be present
+MAILER-DAEMON: postmaster
+postmaster: root
+EOF
+ cat > $NEW/etc/mail/aliases <<EOF
+# root: me@my.domain
+
+# Basic system aliases -- these MUST be present
+MAILER-DAEMON: postmaster
+postmaster: root
+
+# General redirections for pseudo accounts
+_dhcp: root
+_pflogd: root
+EOF
+ cat > $TEST/etc/mail/aliases <<EOF
+root: someone@example.com
+
+# Basic system aliases -- these MUST be present
+MAILER-DAEMON: postmaster
+postmaster: root
+EOF
+
+ # - Verify that updating an unmodified /etc/services builds
+ # /var/db/services.db.
+ cat > $OLD/etc/services <<EOF
+rtmp 1/ddp #Routing Table Maintenance Protocol
+tcpmux 1/tcp #TCP Port Service Multiplexer
+tcpmux 1/udp #TCP Port Service Multiplexer
+EOF
+ cat > $NEW/etc/services <<EOF
+rtmp 1/ddp #Routing Table Maintenance Protocol
+tcpmux 1/tcp #TCP Port Service Multiplexer
+tcpmux 1/udp #TCP Port Service Multiplexer
+nbp 2/ddp #Name Binding Protocol
+compressnet 2/tcp #Management Utility
+compressnet 2/udp #Management Utility
+EOF
+ cp $OLD/etc/services $TEST/etc/services
+ mkdir -p $TEST/var/db
+}
+
+# $1 - relative path to file that should be missing from TEST
+missing()
+{
+ if [ -e $TEST/$1 -o -L $TEST/$1 ]; then
+ echo "File $1 should be missing"
+ FAILED=yes
+ fi
+}
+
+# $1 - relative path to file that should be present in TEST
+present()
+{
+ if ! [ -e $TEST/$1 -o -L $TEST/$1 ]; then
+ echo "File $1 should be present"
+ FAILED=yes
+ fi
+}
+
+# $1 - relative path to file that should be a fifo in TEST
+fifo()
+{
+ if ! [ -p $TEST/$1 ]; then
+ echo "File $1 should be a FIFO"
+ FAILED=yes
+ fi
+}
+
+# $1 - relative path to file that should be a directory in TEST
+dir()
+{
+ if ! [ -d $TEST/$1 ]; then
+ echo "File $1 should be a directory"
+ FAILED=yes
+ fi
+}
+
+# $1 - relative path to file that should be a symlink in TEST
+# $2 - optional value of the link
+link()
+{
+ local val
+
+ if ! [ -L $TEST/$1 ]; then
+ echo "File $1 should be a link"
+ FAILED=yes
+ elif [ $# -gt 1 ]; then
+ val=`readlink $TEST/$1`
+ if [ "$val" != "$2" ]; then
+ echo "Link $1 should link to \"$2\""
+ FAILED=yes
+ fi
+ fi
+}
+
+# $1 - relative path to regular file that should be present in TEST
+# $2 - optional string that should match file contents
+# $3 - optional MD5 of the flie contents, overrides $2 if present
+file()
+{
+ local contents sum
+
+ if ! [ -f $TEST/$1 ]; then
+ echo "File $1 should be a regular file"
+ FAILED=yes
+ elif [ $# -eq 2 ]; then
+ contents=`cat $TEST/$1`
+ if [ "$contents" != "$2" ]; then
+ echo "File $1 has wrong contents"
+ FAILED=yes
+ fi
+ elif [ $# -eq 3 ]; then
+ sum=`md5 -q $TEST/$1`
+ if [ "$sum" != "$3" ]; then
+ echo "File $1 has wrong contents"
+ FAILED=yes
+ fi
+ fi
+}
+
+# $1 - relative path to a regular file that should have a conflict
+# $2 - optional MD5 of the conflict file contents
+conflict()
+{
+ local sum
+
+ if ! [ -f $CONFLICTS/$1 ]; then
+ echo "File $1 missing conflict"
+ FAILED=yes
+ elif [ $# -gt 1 ]; then
+ sum=`md5 -q $CONFLICTS/$1`
+ if [ "$sum" != "$2" ]; then
+ echo "Conflict $1 has wrong contents"
+ FAILED=yes
+ fi
+ fi
+}
+
+check_trees()
+{
+
+ echo "Checking tree for correct results:"
+
+ ## /equal/equal/equal:
+ fifo /equal/equal/equal/fifo
+ file /equal/equal/equal/file "foo"
+ dir /equal/equal/equal/dir
+ link /equal/equal/equal/link "bar"
+
+ ## /equal/first/first:
+ missing /equal/first/first/fifo
+ missing /equal/first/first/file
+ missing /equal/first/first/dir
+ missing /equal/first/first/link
+
+ ## /equal/difftype/difftype:
+ file /equal/difftype/difftype/fifo "bar"
+ link /equal/difftype/difftype/fromdir "test"
+
+ ## /equal/difflinks/difflinks:
+ link /equal/difflinks/difflinks/link "bar"
+
+ ## /equal/difffiles/difffiles:
+ file /equal/difffiles/difffiles/file "bar"
+
+ ## /first/equal/second:
+ missing /first/equal/second/fifo
+ missing /first/equal/second/file
+ missing /first/equal/second/emptydir
+ missing /first/equal/second/link
+ missing /first/equal/second/fulldir
+
+ ## /first/first/equal:
+ missing /first/first/equal/fifo
+ missing /first/first/equal/file
+ missing /first/first/equal/dir
+ missing /first/first/equal/link
+
+ ## /first/difftype/second:
+ present /first/difftype/second/fifo
+
+ ## /first/difflinks/second:
+ link /first/difflinks/second/link "test link"
+
+ ## /first/difffiles/second:
+ file /first/difffiles/second/file "bar"
+
+ ## /second/equal/first:
+ file /second/equal/first/file "bar"
+ fifo /second/equal/first/fifo
+ link /second/equal/first/link "new"
+ missing /second/equal/first/emptydir
+ file /second/equal/first/fulldir/file "foo"
+
+ ## /second/second/equal:
+ fifo /second/second/equal/fifo
+ file /second/second/equal/file "foo"
+ dir /second/second/equal/dir
+ link /second/second/equal/link "bar"
+
+ ## /second/second/difftype:
+ fifo /second/second/difftype/dir
+
+ ## /second/second/difflinks:
+ link /second/second/difflinks/link "test link"
+
+ ## /second/second/difffiles:
+ file /second/second/difffiles/file "test"
+ conflict /second/second/difffiles/file 4f2ee8620a251fd53f06bb6112eb6ffa
+
+ ## /difftype/equal/difftype:
+ link /difftype/equal/difftype/file "test"
+ fifo /difftype/equal/difftype/fromdir
+ missing /difftype/equal/difftype/todir
+
+ ## /difftype/first/first:
+ missing /difftype/first/first/fifo
+
+ ## /difftype/difftype/equal:
+ fifo /difftype/difftype/equal/fifo
+ file /difftype/difftype/equal/file "bar"
+
+ ## /difftype/difftype/difftype:
+ file /difftype/difftype/difftype/one "foo"
+ link /difftype/difftype/difftype/two "bar"
+
+ ## /difftype/difftype/difflinks:
+ link /difftype/difftype/difflinks/link "test"
+
+ ## /difftype/difftype/difffile:
+ conflict /difftype/difftype/difffiles/file \
+ 117f2bcd1f6491f6044e79e5a57a9229
+
+ ## /difflinks/equal/difflinks:
+ link /difflinks/equal/difflinks/link "new"
+
+ ## /difflinks/first/first:
+ missing /difflinks/first/first/link
+
+ ## /difflinks/difftype/difftype:
+ file /difflinks/difftype/difftype/link "test"
+
+ ## /difflinks/difflinks/equal:
+ link /difflinks/difflinks/equal/link "new"
+
+ ## /difflinks/difflinks/difflinks:
+ link /difflinks/difflinks/difflinks/link "test"
+
+ ## /difffiles/equal/difffiles:
+ file /difffiles/equal/difffiles/file "bar"
+
+ ## /difffiles/first/first:
+ missing /difffiles/first/first/file
+
+ ## /difffiles/difftype/difftype:
+ fifo /difffiles/difftype/difftype/file
+
+ ## /difffiles/difffiles/equal:
+ file /difffiles/difffiles/equal/file "bar"
+
+ ## /difffiles/difffiles/difffiles:
+ file /difffiles/difffiles/difffiles/simple "" \
+ cabc7e5e80b0946d79edd555e9648486
+ file /difffiles/difffiles/difffiles/conflict "this is a test file"
+ conflict /difffiles/difffiles/difffiles/conflict \
+ 8261cfdd89280c4a6c26e4ac86541fe9
+
+ ## /adddir/conflict:
+ file /adddir/conflict
+
+ ## /adddir/partial:
+ file /adddir/partial/file "foo"
+ fifo /adddir/partial/fifo
+
+ ## /rmdir/extra:
+ dir /rmdir/extra
+ file /rmdir/extra/localfile.txt "foo"
+
+ ## /rmdir/conflict:
+ dir /rmdir/conflict/difftype
+ present /rmdir/conflict
+
+ ## /rmdir/partial:
+ missing /rmdir/partial
+
+ ## /dirchange/already/fromdir:
+ file /dirchange/already/fromdir "bar"
+
+ ## /dirchange/already/todir:
+ file /dirchange/already/todir/somefile "blah"
+
+ ## /dirchange/old/fromdir:
+ file /dirchange/old/fromdir "bar"
+
+ ## /dirchange/old/todir
+ file /dirchange/old/todir/file "bar"
+
+ ## /dirchange/fromdir/extradir:
+ missing /dirchange/fromdir/extradir/file
+ fifo /dirchange/fromdir/extradir/fifo
+
+ ## /dirchange/fromdir/conflict:
+ file /dirchange/fromdir/conflict/somefile "bar"
+
+ ## /dirchange/todir/difffile:
+ file /dirchange/todir/difffile "bar"
+
+ ## /dirchange/todir/difftype:
+ fifo /dirchange/todir/difftype
+
+ ## Tests for post-install actions
+ file /etc/master.passwd
+ file /etc/passwd
+ file /etc/pwd.db
+ file /etc/spwd.db
+ file /etc/login.conf "" 7774a0f9a3a372c7c109c32fd31c4b6b
+ file /etc/login.conf.db
+ file /etc/mail/aliases "" 7d598f89ec040ab56af54011bdb83337
+ file /etc/services "" 37fb6a8d1273f3b78329d431f21d9c7d
+ file /var/db/services.db
+}
+
+if [ `id -u` -ne 0 ]; then
+ echo "must be root"
+ exit 0
+fi
+
+if [ -r /etc/etcupdate.conf ]; then
+ echo "WARNING: /etc/etcupdate.conf settings may break some tests."
+fi
+
+build_trees
+
+$COMMAND -nr -d $WORKDIR -D $TEST > $WORKDIR/testn.out
+
+cat > $WORKDIR/correct.out <<EOF
+ D /dirchange/fromdir/extradir/file
+ D /dirchange/old/fromdir/somefile
+ D /first/equal/second/fifo
+ D /first/equal/second/file
+ D /first/equal/second/fulldir/file
+ D /first/equal/second/link
+ D /rmdir/partial/subdir/fifo
+ D /rmdir/partial/subdir
+ D /rmdir/partial
+ D /first/equal/second/fulldir
+ D /first/equal/second/emptydir
+ C /difffiles/difffiles/difffiles/conflict
+ M /difffiles/difffiles/difffiles/simple
+ U /difffiles/equal/difffiles/file
+ U /difflinks/equal/difflinks/link
+ C /difftype/difftype/difffiles/file
+ U /difftype/equal/difftype/file
+ U /difftype/equal/difftype/fromdir
+ D /difftype/equal/difftype/todir
+ U /dirchange/old/fromdir
+ U /dirchange/old/todir
+ U /etc/login.conf
+ M /etc/mail/aliases
+ U /etc/services
+ A /adddir/partial/file
+ A /dirchange/old/todir/file
+ A /etc/master.passwd
+ A /second/equal/first/fifo
+ A /second/equal/first/file
+ A /second/equal/first/fulldir/file
+ A /second/equal/first/link
+ C /second/second/difffiles/file
+Warnings:
+ Modified regular file remains: /dirchange/fromdir/conflict/somefile
+ Modified regular file remains: /first/difffiles/second/file
+ Modified symbolic link remains: /first/difflinks/second/link
+ Modified directory remains: /first/difftype/second/fifo
+ Modified directory remains: /rmdir/conflict/difftype
+ Non-empty directory remains: /rmdir/extra
+ Non-empty directory remains: /rmdir/conflict
+ Modified mismatch: /difffiles/difftype/difftype/file (regular file vs fifo file)
+ Removed file changed: /difffiles/first/first/file
+ Modified link changed: /difflinks/difflinks/difflinks/link ("old" became "new")
+ Modified mismatch: /difflinks/difftype/difftype/link (symbolic link vs regular file)
+ Removed link changed: /difflinks/first/first/link ("old" became "new")
+ New link conflict: /difftype/difftype/difflinks/link ("new" vs "test")
+ Modified regular file changed: /difftype/difftype/difftype/one (fifo file became directory)
+ Modified symbolic link changed: /difftype/difftype/difftype/two (directory became regular file)
+ Remove mismatch: /difftype/first/first/fifo (fifo file became directory)
+ Modified directory changed: /dirchange/fromdir/conflict (directory became fifo file)
+ Modified directory changed: /dirchange/fromdir/extradir (directory became symbolic link)
+ Modified regular file changed: /dirchange/todir/difffile (regular file became directory)
+ Modified fifo file changed: /dirchange/todir/difftype (regular file became directory)
+ New file mismatch: /adddir/conflict (directory vs regular file)
+ Directory mismatch: $TEST/adddir/conflict (regular file)
+ Directory mismatch: $TEST/dirchange/todir/difffile (regular file)
+ Directory mismatch: $TEST/dirchange/todir/difftype (fifo file)
+ New link conflict: /second/second/difflinks/link ("new link" vs "test link")
+ New file mismatch: /second/second/difftype/dir (directory vs fifo file)
+ Needs update: /etc/mail/aliases.db (requires manual update via newaliases(1))
+EOF
+
+echo "Differences for -n:"
+diff -u -L "correct" $WORKDIR/correct.out -L "test" $WORKDIR/testn.out \
+ || failed=YES
+
+$COMMAND -r -d $WORKDIR -D $TEST > $WORKDIR/test.out
+
+echo "Differences for real:"
+diff -u -L "correct" $WORKDIR/correct.out -L "test" $WORKDIR/test.out \
+ || failed=YES
+
+check_trees
+
+[ "${FAILED}" = no ]
diff --git a/usr.sbin/etcupdate/tests/tzsetup_test.sh b/usr.sbin/etcupdate/tests/tzsetup_test.sh
new file mode 100644
index 0000000..dbdcc0e
--- /dev/null
+++ b/usr.sbin/etcupdate/tests/tzsetup_test.sh
@@ -0,0 +1,239 @@
+#!/bin/sh
+#
+# Copyright (c) 2013 Hudson River Trading LLC
+# Written by: John H. 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$
+
+# Various regression tests for the tzsetup handling in the 'update' command.
+
+FAILED=no
+WORKDIR=work
+
+usage()
+{
+ echo "Usage: tzsetup.sh [-s script] [-w workdir]"
+ exit 1
+}
+
+# Allow the user to specify an alternate work directory or script.
+COMMAND=etcupdate
+while getopts "s:w:" option; do
+ case $option in
+ s)
+ COMMAND="sh $OPTARG"
+ ;;
+ w)
+ WORKDIR=$OPTARG
+ ;;
+ *)
+ echo
+ usage
+ ;;
+ esac
+done
+shift $((OPTIND - 1))
+if [ $# -ne 0 ]; then
+ usage
+fi
+
+CONFLICTS=$WORKDIR/conflicts
+OLD=$WORKDIR/old
+NEW=$WORKDIR/current
+TEST=$WORKDIR/test
+
+build_trees()
+{
+
+ # Build the base tree, but not /etc/localtime itself
+ local i j k
+
+ rm -rf $OLD $NEW $TEST $CONFLICTS
+ mkdir -p $OLD $NEW $TEST
+ mkdir -p $TEST/etc
+ mkdir -p $TEST/var/db
+ mkdir -p $TEST/usr/share/zoneinfo
+
+ # Create a dummy timezone file
+ echo "foo" > $TEST/usr/share/zoneinfo/foo
+
+}
+
+# $1 - relative path to file that should be missing from TEST
+missing()
+{
+ if [ -e $TEST/$1 -o -L $TEST/$1 ]; then
+ echo "File $1 should be missing"
+ FAILED=yes
+ fi
+}
+
+# $1 - relative path to file that should be a symlink in TEST
+# $2 - optional value of the link
+link()
+{
+ local val
+
+ if ! [ -L $TEST/$1 ]; then
+ echo "File $1 should be a link"
+ FAILED=yes
+ elif [ $# -gt 1 ]; then
+ val=`readlink $TEST/$1`
+ if [ "$val" != "$2" ]; then
+ echo "Link $1 should link to \"$2\""
+ FAILED=yes
+ fi
+ fi
+}
+
+# $1 - relative path to regular file that should be present in TEST
+# $2 - optional string that should match file contents
+# $3 - optional MD5 of the flie contents, overrides $2 if present
+file()
+{
+ local contents sum
+
+ if ! [ -f $TEST/$1 ]; then
+ echo "File $1 should be a regular file"
+ FAILED=yes
+ elif [ $# -eq 2 ]; then
+ contents=`cat $TEST/$1`
+ if [ "$contents" != "$2" ]; then
+ echo "File $1 has wrong contents"
+ FAILED=yes
+ fi
+ elif [ $# -eq 3 ]; then
+ sum=`md5 -q $TEST/$1`
+ if [ "$sum" != "$3" ]; then
+ echo "File $1 has wrong contents"
+ FAILED=yes
+ fi
+ fi
+}
+
+if [ `id -u` -ne 0 ]; then
+ echo "must be root"
+ exit 0
+fi
+
+if [ -r /etc/etcupdate.conf ]; then
+ echo "WARNING: /etc/etcupdate.conf settings may break some tests."
+fi
+
+# First, test for /etc/localtime not existing
+
+build_trees
+
+$COMMAND -nr -d $WORKDIR -D $TEST > $WORKDIR/testn.out
+
+cat > $WORKDIR/correct.out <<EOF
+EOF
+
+echo "Differences for no /etc/localtime with -n:"
+diff -u -L "correct" $WORKDIR/correct.out -L "test" $WORKDIR/testn.out \
+ || FAILED=yes
+
+$COMMAND -r -d $WORKDIR -D $TEST > $WORKDIR/test.out
+
+echo "Differences for no /etc/localtime:"
+diff -u -L "correct" $WORKDIR/correct.out -L "test" $WORKDIR/test.out \
+ || FAILED=yes
+
+missing /etc/localtime
+missing /var/db/zoneinfo
+
+# Second, test for /etc/localtime being a symlink
+
+build_trees
+ln -s /dev/null $TEST/etc/localtime
+
+$COMMAND -nr -d $WORKDIR -D $TEST > $WORKDIR/testn.out
+
+cat > $WORKDIR/correct.out <<EOF
+EOF
+
+echo "Differences for symlinked /etc/localtime with -n:"
+diff -u -L "correct" $WORKDIR/correct.out -L "test" $WORKDIR/testn.out \
+ || FAILED=yes
+
+$COMMAND -r -d $WORKDIR -D $TEST > $WORKDIR/test.out
+
+echo "Differences for symlinked /etc/localtime:"
+diff -u -L "correct" $WORKDIR/correct.out -L "test" $WORKDIR/test.out \
+ || FAILED=yes
+
+link /etc/localtime "/dev/null"
+missing /var/db/zoneinfo
+
+# Third, test for /etc/localtime as a file and a missing /var/db/zoneinfo
+
+build_trees
+echo "bar" > $TEST/etc/localtime
+
+$COMMAND -nr -d $WORKDIR -D $TEST > $WORKDIR/testn.out
+
+cat > $WORKDIR/correct.out <<EOF
+Warnings:
+ Needs update: /etc/localtime (required manual update via tzsetup(1))
+EOF
+
+echo "Differences for missing /var/db/zoneinfo with -n:"
+diff -u -L "correct" $WORKDIR/correct.out -L "test" $WORKDIR/testn.out \
+ || FAILED=yes
+
+$COMMAND -r -d $WORKDIR -D $TEST > $WORKDIR/test.out
+
+echo "Differences for missing /var/db/zoneinfo:"
+diff -u -L "correct" $WORKDIR/correct.out -L "test" $WORKDIR/test.out \
+ || FAILED=yes
+
+file /etc/localtime "bar"
+missing /var/db/zoneinfo
+
+# Finally, test the case where it should update /etc/localtime
+
+build_trees
+echo "bar" > $TEST/etc/localtime
+echo "foo" > $TEST/var/db/zoneinfo
+
+$COMMAND -nr -d $WORKDIR -D $TEST > $WORKDIR/testn.out
+
+cat > $WORKDIR/correct.out <<EOF
+EOF
+
+echo "Differences for real update with -n:"
+diff -u -L "correct" $WORKDIR/correct.out -L "test" $WORKDIR/testn.out \
+ || FAILED=yes
+
+$COMMAND -r -d $WORKDIR -D $TEST > $WORKDIR/test.out
+
+echo "Differences for real update:"
+diff -u -L "correct" $WORKDIR/correct.out -L "test" $WORKDIR/test.out \
+ || FAILED=yes
+
+file /etc/localtime "foo"
+file /var/db/zoneinfo "foo"
+
+[ "${FAILED}" = no ]
diff --git a/usr.sbin/extattr/Makefile b/usr.sbin/extattr/Makefile
new file mode 100644
index 0000000..8e2b5f7
--- /dev/null
+++ b/usr.sbin/extattr/Makefile
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+PROG= rmextattr
+MAN= rmextattr.8
+
+LINKS+= ${BINDIR}/rmextattr ${BINDIR}/getextattr
+LINKS+= ${BINDIR}/rmextattr ${BINDIR}/setextattr
+LINKS+= ${BINDIR}/rmextattr ${BINDIR}/lsextattr
+
+MLINKS+= rmextattr.8 setextattr.8
+MLINKS+= rmextattr.8 getextattr.8
+MLINKS+= rmextattr.8 lsextattr.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/extattr/Makefile.depend b/usr.sbin/extattr/Makefile.depend
new file mode 100644
index 0000000..58f9a33
--- /dev/null
+++ b/usr.sbin/extattr/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/extattr/rmextattr.8 b/usr.sbin/extattr/rmextattr.8
new file mode 100644
index 0000000..c51fa6d
--- /dev/null
+++ b/usr.sbin/extattr/rmextattr.8
@@ -0,0 +1,135 @@
+.\"-
+.\" Copyright (c) 2000, 2001 Robert N. M. Watson
+.\" Copyright (c) 2002 Networks Associates Technology, Inc.
+.\" All rights reserved.
+.\"
+.\" This software was developed for the FreeBSD Project by Poul-Henning
+.\" Kamp 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd August 30, 2000
+.Dt RMEXTATTR 8
+.Os
+.Sh NAME
+.Nm getextattr ,
+.Nm lsextattr ,
+.Nm rmextattr ,
+.Nm setextattr
+.Nd manipulate extended attributes
+.Sh SYNOPSIS
+.Nm getextattr
+.Op Fl fhqsx
+.Ar attrnamespace
+.Ar attrname
+.Ar filename ...
+.Nm lsextattr
+.Op Fl fhq
+.Ar attrnamespace
+.Ar filename ...
+.Nm rmextattr
+.Op Fl fhq
+.Ar attrnamespace
+.Ar attrname
+.Ar filename ...
+.Nm setextattr
+.Op Fl fhnq
+.Ar attrnamespace
+.Ar attrname
+.Ar attrvalue
+.Ar filename ...
+.Sh DESCRIPTION
+These
+utilities
+are user tools to manipulate the named extended attributes on files and
+directories.
+The
+.Ar attrnamespace
+argument should be the namespace of the attribute to retrieve: legal
+values are
+.Cm user
+and
+.Cm system .
+The
+.Ar attrname
+argument should be the name of the attribute,
+.Ar filename
+the name of the target file or directory,
+.Ar attrvalue
+a string to store in the attribute.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl f
+(Force.)
+Ignore errors on individual filenames and continue with
+the remaining arguments.
+.It Fl h
+(No follow.)
+If the file is a symbolic link, perform the operation on the
+link itself rather than the file that the link points to.
+.It Fl n
+.Dv ( NUL Ns
+-terminate.)
+.Dv NUL Ns
+-terminate the extent content written out.
+.It Fl q
+(Quiet.)
+Do not print out the pathname and suppress error messages.
+.It Fl s
+(Stringify.)
+Escape nonprinting characters and put quotes around the output.
+.It Fl x
+(Hex.)
+Print the output in hexadecimal.
+.El
+.Sh EXAMPLES
+.Bd -literal
+setextattr system md5 `md5 -q /boot/kernel/kernel` /boot/kernel/kernel
+getextattr system md5 /boot/kernel/kernel
+lsextattr system /boot/kernel/kernel
+rmextattr system md5 /boot/kernel/kernel
+.Ed
+.Sh SEE ALSO
+.Xr extattr 2 ,
+.Xr extattr 3 ,
+.Xr extattrctl 8 ,
+.Xr extattr 9
+.Sh HISTORY
+Extended attribute support was developed as part of the
+.Tn TrustedBSD
+Project,
+and introduced in
+.Fx 5.0 .
+It was developed to support security extensions requiring additional labels
+to be associated with each file or directory.
+.Sh AUTHORS
+.An Robert N M Watson
+.An Poul-Henning Kamp
+.Sh BUGS
+The
+.Nm setextattr
+utility can only be used to set attributes to strings.
diff --git a/usr.sbin/extattr/rmextattr.c b/usr.sbin/extattr/rmextattr.c
new file mode 100644
index 0000000..c061943
--- /dev/null
+++ b/usr.sbin/extattr/rmextattr.c
@@ -0,0 +1,296 @@
+/*-
+ * Copyright (c) 2002, 2003 Networks Associates Technology, Inc.
+ * Copyright (c) 2002 Poul-Henning Kamp.
+ * Copyright (c) 1999, 2000, 2001, 2002 Robert N. M. Watson
+ * All rights reserved.
+ *
+ * This software was developed for the FreeBSD Project by Poul-Henning
+ * Kamp 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/types.h>
+#include <sys/uio.h>
+#include <sys/extattr.h>
+
+#include <libgen.h>
+#include <libutil.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <vis.h>
+#include <err.h>
+#include <errno.h>
+
+static enum { EADUNNO, EAGET, EASET, EARM, EALS } what = EADUNNO;
+
+static void __dead2
+usage(void)
+{
+
+ switch (what) {
+ case EAGET:
+ fprintf(stderr, "usage: getextattr [-fhqsx] attrnamespace");
+ fprintf(stderr, " attrname filename ...\n");
+ exit(-1);
+ case EASET:
+ fprintf(stderr, "usage: setextattr [-fhnq] attrnamespace");
+ fprintf(stderr, " attrname attrvalue filename ...\n");
+ exit(-1);
+ case EARM:
+ fprintf(stderr, "usage: rmextattr [-fhq] attrnamespace");
+ fprintf(stderr, " attrname filename ...\n");
+ exit(-1);
+ case EALS:
+ fprintf(stderr, "usage: lsextattr [-fhq] attrnamespace");
+ fprintf(stderr, " filename ...\n");
+ exit(-1);
+ case EADUNNO:
+ default:
+ fprintf(stderr, "usage: (getextattr|lsextattr|rmextattr");
+ fprintf(stderr, "|setextattr)\n");
+ exit (-1);
+ }
+}
+
+static void
+mkbuf(char **buf, int *oldlen, int newlen)
+{
+
+ if (*oldlen >= newlen)
+ return;
+ if (*buf != NULL)
+ free(*buf);
+ *buf = malloc(newlen);
+ if (*buf == NULL)
+ err(1, "malloc");
+ *oldlen = newlen;
+ return;
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *buf, *visbuf, *p;
+
+ const char *options, *attrname;
+ size_t len;
+ ssize_t ret;
+ int buflen, visbuflen, ch, error, i, arg_counter, attrnamespace,
+ minargc;
+
+ int flag_force = 0;
+ int flag_nofollow = 0;
+ int flag_null = 0;
+ int flag_quiet = 0;
+ int flag_string = 0;
+ int flag_hex = 0;
+
+ visbuflen = buflen = 0;
+ visbuf = buf = NULL;
+
+ p = basename(argv[0]);
+ if (p == NULL)
+ p = argv[0];
+ if (!strcmp(p, "getextattr")) {
+ what = EAGET;
+ options = "fhqsx";
+ minargc = 3;
+ } else if (!strcmp(p, "setextattr")) {
+ what = EASET;
+ options = "fhnq";
+ minargc = 4;
+ } else if (!strcmp(p, "rmextattr")) {
+ what = EARM;
+ options = "fhq";
+ minargc = 3;
+ } else if (!strcmp(p, "lsextattr")) {
+ what = EALS;
+ options = "fhq";
+ minargc = 2;
+ } else {
+ usage();
+ }
+
+ while ((ch = getopt(argc, argv, options)) != -1) {
+ switch (ch) {
+ case 'f':
+ flag_force = 1;
+ break;
+ case 'h':
+ flag_nofollow = 1;
+ break;
+ case 'n':
+ flag_null = 1;
+ break;
+ case 'q':
+ flag_quiet = 1;
+ break;
+ case 's':
+ flag_string = 1;
+ break;
+ case 'x':
+ flag_hex = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc < minargc)
+ usage();
+
+ error = extattr_string_to_namespace(argv[0], &attrnamespace);
+ if (error)
+ err(-1, "%s", argv[0]);
+ argc--; argv++;
+
+ if (what != EALS) {
+ attrname = argv[0];
+ argc--; argv++;
+ } else
+ attrname = NULL;
+
+ if (what == EASET) {
+ mkbuf(&buf, &buflen, strlen(argv[0]) + 1);
+ strcpy(buf, argv[0]);
+ argc--; argv++;
+ }
+
+ for (arg_counter = 0; arg_counter < argc; arg_counter++) {
+ switch (what) {
+ case EARM:
+ if (flag_nofollow)
+ error = extattr_delete_link(argv[arg_counter],
+ attrnamespace, attrname);
+ else
+ error = extattr_delete_file(argv[arg_counter],
+ attrnamespace, attrname);
+ if (error >= 0)
+ continue;
+ break;
+ case EASET:
+ len = strlen(buf) + flag_null;
+ if (flag_nofollow)
+ ret = extattr_set_link(argv[arg_counter],
+ attrnamespace, attrname, buf, len);
+ else
+ ret = extattr_set_file(argv[arg_counter],
+ attrnamespace, attrname, buf, len);
+ if (ret >= 0) {
+ if ((size_t)ret != len && !flag_quiet) {
+ warnx("Set %zd bytes of %zu for %s",
+ ret, len, attrname);
+ }
+ continue;
+ }
+ break;
+ case EALS:
+ if (flag_nofollow)
+ ret = extattr_list_link(argv[arg_counter],
+ attrnamespace, NULL, 0);
+ else
+ ret = extattr_list_file(argv[arg_counter],
+ attrnamespace, NULL, 0);
+ if (ret < 0)
+ break;
+ mkbuf(&buf, &buflen, ret);
+ if (flag_nofollow)
+ ret = extattr_list_link(argv[arg_counter],
+ attrnamespace, buf, buflen);
+ else
+ ret = extattr_list_file(argv[arg_counter],
+ attrnamespace, buf, buflen);
+ if (ret < 0)
+ break;
+ if (!flag_quiet)
+ printf("%s\t", argv[arg_counter]);
+ for (i = 0; i < ret; i += ch + 1) {
+ /* The attribute name length is unsigned. */
+ ch = (unsigned char)buf[i];
+ printf("%s%*.*s", i ? "\t" : "",
+ ch, ch, buf + i + 1);
+ }
+ if (!flag_quiet || ret > 0)
+ printf("\n");
+ continue;
+ case EAGET:
+ if (flag_nofollow)
+ ret = extattr_get_link(argv[arg_counter],
+ attrnamespace, attrname, NULL, 0);
+ else
+ ret = extattr_get_file(argv[arg_counter],
+ attrnamespace, attrname, NULL, 0);
+ if (ret < 0)
+ break;
+ mkbuf(&buf, &buflen, ret);
+ if (flag_nofollow)
+ ret = extattr_get_link(argv[arg_counter],
+ attrnamespace, attrname, buf, buflen);
+ else
+ ret = extattr_get_file(argv[arg_counter],
+ attrnamespace, attrname, buf, buflen);
+ if (ret < 0)
+ break;
+ if (!flag_quiet)
+ printf("%s\t", argv[arg_counter]);
+ if (flag_string) {
+ mkbuf(&visbuf, &visbuflen, ret * 4 + 1);
+ strvisx(visbuf, buf, ret,
+ VIS_SAFE | VIS_WHITE);
+ printf("\"%s\"\n", visbuf);
+ continue;
+ } else if (flag_hex) {
+ for (i = 0; i < ret; i++)
+ printf("%s%02x", i ? " " : "",
+ buf[i]);
+ printf("\n");
+ continue;
+ } else {
+ fwrite(buf, ret, 1, stdout);
+ printf("\n");
+ continue;
+ }
+ default:
+ break;
+ }
+ if (!flag_quiet)
+ warn("%s: failed", argv[arg_counter]);
+ if (flag_force)
+ continue;
+ return(1);
+ }
+ return (0);
+}
diff --git a/usr.sbin/extattrctl/Makefile b/usr.sbin/extattrctl/Makefile
new file mode 100644
index 0000000..496a8aa
--- /dev/null
+++ b/usr.sbin/extattrctl/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= extattrctl
+MAN= extattrctl.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/extattrctl/Makefile.depend b/usr.sbin/extattrctl/Makefile.depend
new file mode 100644
index 0000000..58f9a33
--- /dev/null
+++ b/usr.sbin/extattrctl/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/extattrctl/extattrctl.8 b/usr.sbin/extattrctl/extattrctl.8
new file mode 100644
index 0000000..c5850ab4
--- /dev/null
+++ b/usr.sbin/extattrctl/extattrctl.8
@@ -0,0 +1,181 @@
+.\"-
+.\" Copyright (c) 2000-2001 Robert N. M. Watson
+.\" All rights reserved.
+.\"
+.\" This software was developed by Robert Watson for the TrustedBSD 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.
+.\"
+.\" 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$
+.\"
+.\" Developed by the TrustedBSD Project.
+.\" Support for file system extended attribute.
+.\"
+.Dd March 30, 2000
+.Dt EXTATTRCTL 8
+.Os
+.Sh NAME
+.Nm extattrctl
+.Nd manage UFS1 extended attributes
+.Sh SYNOPSIS
+.Nm
+.Cm start
+.Ar path
+.Nm
+.Cm stop
+.Ar path
+.Nm
+.Cm initattr
+.Op Fl f
+.Op Fl p Ar path
+.Ar attrsize
+.Ar attrfile
+.Nm
+.Cm showattr
+.Ar attrfile
+.Nm
+.Cm enable
+.Ar path
+.Ar attrnamespace
+.Ar attrname
+.Ar attrfile
+.Nm
+.Cm disable
+.Ar path
+.Ar attrnamespace
+.Ar attrname
+.Sh DESCRIPTION
+The
+.Nm
+utility
+is the management utility for extended attributes over the UFS1 file system.
+It allows the starting and stopping of extended attributes on a file system,
+as well as initialization of attribute backing files, and enabling and
+disabling of specific extended attributes on a file system.
+.Pp
+The first argument on the command line indicates the operation to be
+performed.
+Operation must be one of the following:
+.Bl -tag -width indent
+.It Cm start Ar path
+Start extended attribute support on the file system named using
+.Ar path .
+The file system must be an UFS1 file system, and the UFS_EXTATTR kernel
+option must have been enabled.
+.It Cm stop Ar path
+Stop extended attribute support on the file system named using
+.Ar path .
+Extended attribute support must previously have been started.
+.It Xo
+.Cm initattr
+.Op Fl f
+.Op Fl p Ar path
+.Ar attrsize attrfile
+.Xc
+Create and initialize a file to use as an attribute backing file.
+You must specify a maximum per-inode size for the attribute in bytes in
+.Ar attrsize ,
+as well as the file where the attribute will be stored, using
+.Ar attrfile .
+.Pp
+The
+.Fl f
+argument may be used to indicate that it is alright to overwrite an
+existing attribute backing file; otherwise, if the target file exists,
+an error will be returned.
+.Pp
+The
+.Fl p Ar path
+argument may be used to preallocate space for all attributes rather than
+relying on sparse files to conserve space.
+This has the advantage of guaranteeing that space will be available
+for attributes when they are written, preventing low disk space conditions
+from denying attribute service.
+.Pp
+This file should not exist before running
+.Cm initattr .
+.It Cm showattr Ar attrfile
+Show the attribute header values in the attribute file named by
+.Ar attrfile .
+.It Cm enable Ar path attrnamespace attrname attrfile
+Enable an attribute named
+.Ar attrname
+in the namespace
+.Ar attrnamespace
+on the file system identified using
+.Ar path ,
+and backed by initialized attribute file
+.Ar attrfile .
+Available namespaces are "user" and "system".
+The backing file must have been initialized using
+.Cm initattr
+before its first use.
+Attributes must have been started on the file system prior to the
+enabling of any attributes.
+.It Cm disable Ar path attrnamespace attrname
+Disable the attributed named
+.Ar attrname
+in namespace
+.Ar attrnamespace
+on the file system identified by
+.Ar path .
+Available namespaces are "user" and "system".
+The file system must have attributes started on it, and the attribute
+most have been enabled using
+.Cm enable .
+.El
+.Sh EXAMPLES
+.Dl extattrctl start /
+.Pp
+Start extended attributes on the root file system.
+.Pp
+.Dl extattrctl initattr 17 /.attribute/system/md5
+.Pp
+Create an attribute backing file in /.attribute/system/md5, and set the maximum
+size of each attribute to 17 bytes, with a sparse file used for storing
+the attributes.
+.Pp
+.Dl extattrctl enable / system md5 /.attribute/system/md5
+.Pp
+Enable an attribute named md5 on the root file system, backed from the file
+/.attribute/system/md5.
+.Pp
+.Dl extattrctl disable / md5
+.Pp
+Disable the attribute named md5 on the root file system.
+.Pp
+.Dl extattrctl stop /
+.Pp
+Stop extended attributes on the root file system.
+.Sh SEE ALSO
+.Xr ffs 7 ,
+.Xr getextattr 8 ,
+.Xr setextattr 8 ,
+.Xr extattr 9
+.Sh HISTORY
+Extended attribute support was developed as part of the TrustedBSD Project,
+and introduced in
+.Fx 5.0 .
+It was developed to support security extensions requiring additional labels
+to be associated with each file or directory.
+.Sh AUTHORS
+Robert N M Watson
diff --git a/usr.sbin/extattrctl/extattrctl.c b/usr.sbin/extattrctl/extattrctl.c
new file mode 100644
index 0000000..377d3ba
--- /dev/null
+++ b/usr.sbin/extattrctl/extattrctl.c
@@ -0,0 +1,265 @@
+/*-
+ * Copyright (c) 1999-2002 Robert N. M. Watson
+ * All rights reserved.
+ *
+ * This software was developed by Robert Watson for the TrustedBSD 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.
+ *
+ * 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$
+ */
+/*
+ * Developed by the TrustedBSD Project.
+ * Support for file system extended attribute.
+ */
+
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/extattr.h>
+#include <sys/param.h>
+#include <sys/mount.h>
+
+#include <ufs/ufs/extattr.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <libutil.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+int initattr(int argc, char *argv[]);
+int showattr(int argc, char *argv[]);
+long num_inodes_by_path(char *path);
+void usage(void);
+
+void
+usage(void)
+{
+
+ fprintf(stderr,
+ "usage:\n"
+ " extattrctl start path\n"
+ " extattrctl stop path\n"
+ " extattrctl initattr [-f] [-p path] attrsize attrfile\n"
+ " extattrctl showattr attrfile\n"
+ " extattrctl enable path attrnamespace attrname attrfile\n"
+ " extattrctl disable path attrnamespace attrname\n");
+ exit(-1);
+}
+
+long
+num_inodes_by_path(char *path)
+{
+ struct statfs buf;
+ int error;
+
+ error = statfs(path, &buf);
+ if (error) {
+ perror("statfs");
+ return (-1);
+ }
+
+ return (buf.f_files);
+}
+
+static const char zero_buf[8192];
+
+int
+initattr(int argc, char *argv[])
+{
+ struct ufs_extattr_fileheader uef;
+ char *fs_path = NULL;
+ int ch, i, error, flags;
+ ssize_t wlen;
+ size_t easize;
+
+ flags = O_CREAT | O_WRONLY | O_TRUNC | O_EXCL;
+ optind = 0;
+ while ((ch = getopt(argc, argv, "fp:r:w:")) != -1)
+ switch (ch) {
+ case 'f':
+ flags &= ~O_EXCL;
+ break;
+ case 'p':
+ fs_path = optarg;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 2)
+ usage();
+
+ error = 0;
+ if ((i = open(argv[1], flags, 0600)) == -1) {
+ /* unable to open file */
+ perror(argv[1]);
+ return (-1);
+ }
+ uef.uef_magic = UFS_EXTATTR_MAGIC;
+ uef.uef_version = UFS_EXTATTR_VERSION;
+ uef.uef_size = atoi(argv[0]);
+ if (write(i, &uef, sizeof(uef)) == -1)
+ error = -1;
+ else if (fs_path != NULL) {
+ easize = (sizeof uef + uef.uef_size) *
+ num_inodes_by_path(fs_path);
+ while (easize > 0) {
+ if (easize > sizeof zero_buf)
+ wlen = write(i, zero_buf, sizeof zero_buf);
+ else
+ wlen = write(i, zero_buf, easize);
+ if (wlen == -1) {
+ error = -1;
+ break;
+ }
+ easize -= wlen;
+ }
+ }
+ if (error == -1) {
+ perror(argv[1]);
+ unlink(argv[1]);
+ close(i);
+ return (-1);
+ }
+
+ close(i);
+ return (0);
+}
+
+int
+showattr(int argc, char *argv[])
+{
+ struct ufs_extattr_fileheader uef;
+ int i, fd;
+
+ if (argc != 1)
+ usage();
+
+ fd = open(argv[0], O_RDONLY);
+ if (fd == -1) {
+ perror(argv[0]);
+ return (-1);
+ }
+
+ i = read(fd, &uef, sizeof(uef));
+ if (i == -1) {
+ perror(argv[0]);
+ close(fd);
+ return (-1);
+ }
+ if (i != sizeof(uef)) {
+ fprintf(stderr, "%s: invalid file header\n", argv[0]);
+ close(fd);
+ return (-1);
+ }
+
+ if (uef.uef_magic != UFS_EXTATTR_MAGIC) {
+ fprintf(stderr, "%s: bad magic\n", argv[0]);
+ close(fd);
+ return (-1);
+ }
+
+ printf("%s: version %d, size %d\n", argv[0], uef.uef_version,
+ uef.uef_size);
+
+ close(fd);
+ return (0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int error = 0, attrnamespace;
+
+ if (argc < 2)
+ usage();
+
+ if (!strcmp(argv[1], "start")) {
+ if (argc != 3)
+ usage();
+ error = extattrctl(argv[2], UFS_EXTATTR_CMD_START, NULL, 0,
+ NULL);
+ if (error) {
+ perror("extattrctl start");
+ return (-1);
+ }
+ } else if (!strcmp(argv[1], "stop")) {
+ if (argc != 3)
+ usage();
+ error = extattrctl(argv[2], UFS_EXTATTR_CMD_STOP, NULL, 0,
+ NULL);
+ if (error) {
+ perror("extattrctl stop");
+ return (-1);
+ }
+ } else if (!strcmp(argv[1], "enable")) {
+ if (argc != 6)
+ usage();
+ error = extattr_string_to_namespace(argv[3], &attrnamespace);
+ if (error) {
+ perror("extattrctl enable");
+ return (-1);
+ }
+ error = extattrctl(argv[2], UFS_EXTATTR_CMD_ENABLE, argv[5],
+ attrnamespace, argv[4]);
+ if (error) {
+ perror("extattrctl enable");
+ return (-1);
+ }
+ } else if (!strcmp(argv[1], "disable")) {
+ if (argc != 5)
+ usage();
+ error = extattr_string_to_namespace(argv[3], &attrnamespace);
+ if (error) {
+ perror("extattrctl disable");
+ return (-1);
+ }
+ error = extattrctl(argv[2], UFS_EXTATTR_CMD_DISABLE, NULL,
+ attrnamespace, argv[4]);
+ if (error) {
+ perror("extattrctl disable");
+ return (-1);
+ }
+ } else if (!strcmp(argv[1], "initattr")) {
+ argc -= 2;
+ argv += 2;
+ error = initattr(argc, argv);
+ if (error)
+ return (-1);
+ } else if (!strcmp(argv[1], "showattr")) {
+ argc -= 2;
+ argv += 2;
+ error = showattr(argc, argv);
+ if (error)
+ return (-1);
+ } else
+ usage();
+
+ return (0);
+}
diff --git a/usr.sbin/fdcontrol/Makefile b/usr.sbin/fdcontrol/Makefile
new file mode 100644
index 0000000..5f1426b
--- /dev/null
+++ b/usr.sbin/fdcontrol/Makefile
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../fdread
+
+PROG= fdcontrol
+SRCS= fdcontrol.c fdutil.c
+CFLAGS+= -I${.CURDIR}/../fdread
+MAN= fdcontrol.8
+
+.if ${MACHINE} == "pc98"
+CFLAGS+= -DPC98
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/fdcontrol/Makefile.depend b/usr.sbin/fdcontrol/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/fdcontrol/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/fdcontrol/fdcontrol.8 b/usr.sbin/fdcontrol/fdcontrol.8
new file mode 100644
index 0000000..8e2ab12
--- /dev/null
+++ b/usr.sbin/fdcontrol/fdcontrol.8
@@ -0,0 +1,334 @@
+.\" Copyright (C) 1994, 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 25, 2001
+.Dt FDCONTROL 8
+.Os
+.Sh NAME
+.Nm fdcontrol
+.Nd display and modify floppy disk parameters
+.Sh SYNOPSIS
+.Nm
+.Op Fl F
+.Op Fl d Ar dbg
+.Op Fl f Ar fmt
+.Op Fl s Ar fmtstr
+.Op Fl v
+.Ar device
+.Sh DESCRIPTION
+The
+.Nm
+utility allows the modification of the run-time behavior of the
+.Xr fdc 4
+driver for the device specified by
+.Ar device .
+.Pp
+Commands are implemented to query the current device density settings
+as well as the underlying device hardware as registered with the
+driver, to manipulate debugging levels, and to adjust the device
+density settings.
+All the operations that manipulate the kernel
+settings are restricted to the superuser (by the device driver), while
+all inquiry requests only require read access to
+.Ar device .
+.Pp
+The
+.Ar device
+argument should always be given as a full path name, e.g.\&
+.Pa /dev/fd0 .
+.Ss Inquiry Commands
+Running the
+.Nm
+utility without any of the optional flags will report the drive type
+that is registered with the device driver.
+In the shortest form, a single string describing the drive type will
+be returned.
+Possible values are:
+.Dq Li 360K ,
+.Dq Li 1.2M ,
+.Dq Li 720K ,
+.Dq Li 1.44M ,
+.Dq Li 2.88M ,
+or
+.Dq Li unknown .
+This information is primarily intended to be easily parsable by
+scripts.
+.Pp
+In order to add some descriptive text that makes the output better
+human readable, the flag
+.Fl v
+can be added.
+.Pp
+Specifying flag
+.Fl F
+will report the device's density settings in a form that is suitable
+as input to the
+.Fl s Ar fmtstr
+option (see below).
+Again, together with
+.Fl v ,
+some more text will be returned, including the total capacity of the
+density settings in kilobytes.
+.Ss Debug Control
+If the
+.Xr fdc 4
+driver was configured with the
+.Dv FDC_DEBUG
+option, by default, device debugging information is still disabled
+since it could produce huge amounts of kernel messages.
+It needs to
+be turned on using
+.Nm
+together with
+.Dq Fl d Li 1 ,
+usually immediately before starting an operation on the respective
+device the debug information is wanted for, and later turned off again
+using
+.Dq Fl d Li 0 .
+Note that debugging levels are a driver's global option that will
+affect any drives and controllers using the
+.Xr fdc 4
+driver, regardless which
+.Ar device
+was specified on the
+.Nm
+command line.
+.Ss Density Control
+The
+.Xr fdc 4
+control utilities support two different options how to specify device
+density settings.
+The first form uses
+.Fl f Ar fmt
+to specify the format of the medium in kilobytes.
+Depending on the
+underlying drive type, the value is compared against a table of known
+commonly used device density settings for that drive, and if a match
+is found, those settings will be used.
+Currently, the following
+values for the respective drive types are acceptable:
+.Bl -item
+.It
+2.88M and 1.44M drives:
+.Bd -ragged -offset indent -compact
+.TS
+lB lB lB lB lB lB lB
+r l l l l l l.
+KB sectrac secsize ncyls speed heads flags
+1721 21 2 (512) 82 500 2 MFM
+1476 18 2 (512) 82 500 2 MFM
+1440 18 2 (512) 80 500 2 MFM
+1200 15 2 (512) 80 500 2 MFM
+820 10 2 (512) 82 250 2 MFM
+800 10 2 (512) 80 250 2 MFM
+720 9 2 (512) 80 250 2 MFM
+.TE
+.Ed
+.It
+1.2M drives:
+.Bd -ragged -offset indent -compact
+.TS
+lB lB lB lB lB lB lB
+r l l l l l l.
+KB sectrac secsize ncyls speed heads flags
+1200 15 2 (512) 80 500 2 MFM
+1232 8 3 (1024) 77 500 2 MFM
+1476 18 2 (512) 82 500 2 MFM
+1440 18 2 (512) 80 500 2 MFM
+1200 15 2 (512) 80 500 2 MFM
+820 10 2 (512) 82 300 2 MFM
+800 10 2 (512) 80 300 2 MFM
+720 9 2 (512) 80 300 2 MFM
+360 9 2 (512) 40 300 2 MFM,2STEP
+640 8 2 (512) 80 300 2 MFM
+.TE
+.Ed
+.It
+720K drives:
+.Bd -ragged -offset indent -compact
+.TS
+lB lB lB lB lB lB lB
+r l l l l l l.
+KB sectrac secsize ncyls speed heads flags
+720 9 2 (512) 80 250 2 MFM
+.TE
+.Ed
+.It
+360K drives:
+.Bd -ragged -offset indent -compact
+.TS
+lB lB lB lB lB lB lB
+r l l l l l l.
+KB sectrac secsize ncyls speed heads flags
+360 9 2 (512) 40 250 2 MFM
+.TE
+.Ed
+.El
+.Pp
+The second form to specify a device density uses
+.Fl s Ar fmtstr
+to explicitly specify each parameter in detail.
+The argument
+.Ar fmtstr
+is a comma-separated list of values of the form:
+.Pp
+.Sm off
+.Ar sectrac , secsize , datalen , gap , ncyls , speed ,
+.Ar heads , f_gap , f_inter , offs2 , flags
+.Sm on
+.Pp
+The meaning of the parameters is:
+.Bl -tag -width ".Ar secsize"
+.It Ar sectrac
+The number of sectors per track.
+.It Ar secsize
+The sector size code, 0 = 128 bytes (or less), 1 = 256 bytes, 2 = 512
+bytes, 3 = 1024 bytes.
+.It Ar datalen
+The actual sector size if the size code is 0, or the (ignored) value
+0xFF for larger size codes.
+.It Ar gap
+The length of the gap 3 parameter for read/write operations.
+.It Ar ncyls
+The number of cylinders.
+.It Ar speed
+The transfer speed in kilobytes per second.
+Can be 250, 300, 500, or
+1000, but each drive type only supports a subset of these values.
+.It Ar heads
+The number of heads.
+.It Ar f_gap
+The length of the gap 3 when formatting media.
+.It Ar f_inter
+The sector interleave to be applied when formatting.
+0 means no
+interleave, 1 means 1:1 etc.
+.It Ar offs2
+The offset of the sector numbers on side 2 (i.e., head number 1).
+Normally, sector numbering on both sides starts with 1.
+.It Ar flags
+A list from one of the following flag values:
+.Pp
+.Bl -tag -width ".Cm +perpend" -compact
+.It Cm +mfm
+Use MFM encoding.
+.It Cm -mfm
+Use FM (single-density) encoding.
+.It Cm +2step
+Use 2 steps per each cylinder (for accessing 40-cylinder media in
+80-cylinder drives).
+.It Cm -2step
+Do not use 2 steps per cylinder, i.e., access each physical cylinder
+of the drive.
+.It Cm +perpend
+Use perpendicular recording (for 2.88 MB media, currently not
+supported).
+.It Cm -perpend
+Use longitudinal recording.
+.El
+.El
+.Pp
+For any missing parameter, the current value will be used, so only
+actual changes need to be specified.
+Thus to turn off a flag bit
+(like
+.Cm +mfm
+which is the default for all drive types), the form with a leading
+minus sign must explicitly be used.
+.Sh EXAMPLES
+A simple inquiry about the drive type:
+.Bd -literal -offset indent
+$ fdcontrol /dev/fd0
+1.44M
+.Ed
+.Pp
+Same as above, but with verbose output.
+Note that the result is about
+the
+.Em "drive type" ,
+as opposed to a
+.Em "device density" ,
+so it is independent from the actual subdevice being used for
+.Ar device .
+.Bd -literal -offset indent
+$ fdcontrol -v /dev/fd0
+/dev/fd0: 1.44M drive (3.5" high-density)
+.Ed
+.Pp
+Inquiry about the density settings:
+.Bd -literal -offset indent
+$ fdcontrol -F /dev/fd0
+18,512,0xff,0x1b,80,500,2,0x6c,1,0,+mfm
+.Ed
+.Pp
+The verbose flag makes this human readable:
+.Bd -literal -offset indent
+/dev/fd0: 1440 KB media type
+ Format: 18,512,0xff,0x1b,80,500,2,0x6c,1,0,+mfm
+ Sector size: 512
+ Sectors/track: 18
+ Heads/cylinder: 2
+ Cylinders/disk: 80
+ Transfer rate: 500 kbps
+ Sector gap: 27
+ Format gap: 108
+ Interleave: 1
+ Side offset: 0
+ Flags <MFM>
+.Ed
+.Pp
+As indicated, trailing commas in the parameter list may be omitted.
+.Pp
+In order to access archaic 160 KB single-density (FM encoded) 5.25
+media in a modern 1.2M drive, something like the following definition
+would be needed.
+(Note that not all controller hardware is actually
+capable of handling FM encoding at all.)
+.Bd -literal
+# fdcontrol -s 16,128,0x80,0x2,40,300,,0x10,,,-mfm,+2step /dev/fd1.1
+.Ed
+.Pp
+It is still possible to hook up 8" drives to most modern floppy
+controllers, given the right cable magic.
+(On PC hardware, tell the BIOS that it is a 5.25" drive.)
+The classical 128/26/2/77 format can be read with this entry
+.Bd -literal -offset indent
+fdcontrol -s 26,128,0x80,0x2,77,500,2,0x10,,,-mfm /dev/fd0
+.Ed
+.Sh SEE ALSO
+.Xr fdc 4
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 2.0 ,
+and was vastly overhauled in
+.Fx 5.0 .
+.Sh AUTHORS
+The program and this man page was contributed by
+.An J\(:org Wunsch ,
+Dresden.
diff --git a/usr.sbin/fdcontrol/fdcontrol.c b/usr.sbin/fdcontrol/fdcontrol.c
new file mode 100644
index 0000000..91877e6
--- /dev/null
+++ b/usr.sbin/fdcontrol/fdcontrol.c
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 1994, 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/fdcio.h>
+#include <sys/file.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "fdutil.h"
+
+
+static int format, verbose, show = 1, showfmt;
+static char *fmtstring;
+
+static void showdev(enum fd_drivetype, const char *);
+static void usage(void);
+
+static void
+usage(void)
+{
+ errx(EX_USAGE,
+ "usage: fdcontrol [-F] [-d dbg] [-f fmt] [-s fmtstr] [-v] device");
+}
+
+void
+showdev(enum fd_drivetype type, const char *fname)
+{
+ const char *name, *descr;
+
+ getname(type, &name, &descr);
+ if (verbose)
+ printf("%s: %s drive (%s)\n", fname, name, descr);
+ else
+ printf("%s\n", name);
+}
+
+int
+main(int argc, char **argv)
+{
+ enum fd_drivetype type;
+ struct fd_type ft, newft, *fdtp;
+ const char *name, *descr;
+ int fd, i, autofmt;
+
+ autofmt = 0;
+ while((i = getopt(argc, argv, "aFf:s:v")) != -1)
+ switch(i) {
+
+ case 'a':
+ autofmt = 1;
+ case 'F':
+ showfmt = 1;
+ show = 0;
+ break;
+
+ case 'f':
+ if (!strcmp(optarg, "auto")) {
+ format = -1;
+ } else if (getnum(optarg, &format)) {
+ fprintf(stderr,
+ "Bad argument %s to -f option; must be numeric\n",
+ optarg);
+ usage();
+ }
+ show = 0;
+ break;
+
+ case 's':
+ fmtstring = optarg;
+ show = 0;
+ break;
+
+ case 'v':
+ verbose++;
+ break;
+
+ default:
+ usage();
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if(argc != 1)
+ usage();
+
+ if((fd = open(argv[0], O_RDONLY | O_NONBLOCK)) < 0)
+ err(EX_UNAVAILABLE, "open(%s)", argv[0]);
+
+ if (ioctl(fd, FD_GDTYPE, &type) == -1)
+ err(EX_OSERR, "ioctl(FD_GDTYPE)");
+ if (ioctl(fd, FD_GTYPE, &ft) == -1)
+ err(EX_OSERR, "ioctl(FD_GTYPE)");
+
+ if (show) {
+ showdev(type, argv[0]);
+ return (0);
+ }
+
+ if (autofmt) {
+ memset(&newft, 0, sizeof newft);
+ ft = newft;
+ }
+
+ if (format) {
+ getname(type, &name, &descr);
+ fdtp = get_fmt(format, type);
+ if (fdtp == 0)
+ errx(EX_USAGE,
+ "unknown format %d KB for drive type %s",
+ format, name);
+ ft = *fdtp;
+ }
+
+ if (fmtstring) {
+ parse_fmt(fmtstring, type, ft, &newft);
+ ft = newft;
+ }
+
+ if (showfmt) {
+ if (verbose) {
+ const char *s;
+
+ printf("%s: %d KB media type\n", argv[0],
+ (128 << ft.secsize) * ft.size / 1024);
+ printf("\tFormat:\t\t");
+ print_fmt(ft);
+ if (ft.datalen != 0xff &&
+ ft.datalen != (128 << ft.secsize))
+ printf("\tData length:\t%d\n", ft.datalen);
+ printf("\tSector size:\t%d\n", 128 << ft.secsize);
+ printf("\tSectors/track:\t%d\n", ft.sectrac);
+ printf("\tHeads/cylinder:\t%d\n", ft.heads);
+ printf("\tCylinders/disk:\t%d\n", ft.tracks);
+ switch (ft.trans) {
+ case 0: printf("\tTransfer rate:\t500 kbps\n"); break;
+ case 1: printf("\tTransfer rate:\t300 kbps\n"); break;
+ case 2: printf("\tTransfer rate:\t250 kbps\n"); break;
+ case 3: printf("\tTransfer rate:\t1 Mbps\n"); break;
+ }
+ printf("\tSector gap:\t%d\n", ft.gap);
+ printf("\tFormat gap:\t%d\n", ft.f_gap);
+ printf("\tInterleave:\t%d\n", ft.f_inter);
+ printf("\tSide offset:\t%d\n", ft.offset_side2);
+ printf("\tFlags\t\t<");
+ s = "";
+ if (ft.flags & FL_MFM) {
+ printf("%sMFM", s);
+ s = ",";
+ }
+ if (ft.flags & FL_2STEP) {
+ printf("%s2STEP", s);
+ s = ",";
+ }
+ if (ft.flags & FL_PERPND) {
+ printf("%sPERPENDICULAR", s);
+ s = ",";
+ }
+ if (ft.flags & FL_AUTO) {
+ printf("%sAUTO", s);
+ s = ",";
+ }
+ printf(">\n");
+ } else {
+ print_fmt(ft);
+ }
+ return (0);
+ }
+
+ if (format || fmtstring) {
+ if (ioctl(fd, FD_STYPE, &ft) == -1)
+ err(EX_OSERR, "ioctl(FD_STYPE)");
+ return (0);
+ }
+
+ return 0;
+}
diff --git a/usr.sbin/fdformat/Makefile b/usr.sbin/fdformat/Makefile
new file mode 100644
index 0000000..59cd124
--- /dev/null
+++ b/usr.sbin/fdformat/Makefile
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../fdread
+
+PROG= fdformat
+SRCS= fdformat.c fdutil.c
+
+CFLAGS+= -I${.CURDIR}/../fdread
+
+.if ${MACHINE} == "pc98"
+CFLAGS+= -DPC98
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/fdformat/Makefile.depend b/usr.sbin/fdformat/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/fdformat/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/fdformat/fdformat.1 b/usr.sbin/fdformat/fdformat.1
new file mode 100644
index 0000000..596b09b
--- /dev/null
+++ b/usr.sbin/fdformat/fdformat.1
@@ -0,0 +1,180 @@
+.\" Copyright (C) 1993, 1994, 1995, 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 25, 2001
+.Dt FDFORMAT 1
+.Os
+.Sh NAME
+.Nm fdformat
+.Nd format floppy disks
+.Sh SYNOPSIS
+.Nm
+.Op Fl F Ar fill
+.Op Fl f Ar fmt
+.Op Fl s Ar fmtstr
+.Op Fl nqvy
+.Ar device
+.Sh DESCRIPTION
+The
+.Nm
+utility formats a floppy disk at
+.Ar device ,
+where
+.Ar device
+may either be given as a full path
+name of a device node for a floppy disk drive
+(e.g.\&
+.Pa /dev/fd0 ) ,
+or using an abbreviated name that will be looked up
+under
+.Pa /dev
+(e.g.\&
+.Dq Li fd0 ) .
+.Pp
+The options are as follows:
+.Bl -tag -width ".Fl s Ar fmtstr"
+.It Fl F Ar fill
+Use
+.Ar fill
+as the fill byte for newly formatted sectors.
+The
+.Ar fill
+argument
+must be a number in the range 0 through 255 using common C
+language notation.
+The default value is
+.Dq Li 0xf6 .
+.It Fl f Ar fmt
+Specify the density settings for a
+.Ar fmt
+kilobyte format, as described in
+.Xr fdcontrol 8 .
+.It Fl s Ar fmtstr
+Specify the density settings using explicit parameters, as
+described in
+.Xr fdcontrol 8 .
+.It Fl n
+Do not verify floppy after formatting.
+.It Fl q
+Suppress any normal output from the command, and do not ask the
+user for a confirmation whether to format the floppy disk at
+.Ar device .
+.It Fl v
+Do not format, verify only.
+.It Fl y
+Do not ask for confirmation whether to format the floppy disk but
+still report formatting status.
+.El
+.Pp
+For non-autoselecting subdevices, neither
+.Fl f Ar fmt
+nor
+.Fl s Ar fmtstr
+may be specified, since the preconfigured media density settings
+from the kernel driver will always be used.
+However, if
+.Ar device
+is a device with automatic media density selection (see
+.Xr fdc 4 ) ,
+both methods can be used to override the density settings for the
+newly formatted medium (without permanently changing the density
+settings of
+.Ar device ) .
+.Pp
+If the
+.Fl q
+flag has not been specified, the user is asked for a confirmation
+of the intended formatting process.
+In order to continue, an answer
+of
+.Ql y
+must be given.
+.Pp
+Note that
+.Nm
+does only perform low-level formatting.
+In order to create
+a file system on the medium, see the commands
+.Xr newfs 8
+for a
+.Tn UFS
+file system, or
+.Xr newfs_msdos 8
+for an
+.Tn MS-DOS
+(FAT)
+file system.
+.Sh EXIT STATUS
+An exit status of 0 is returned upon successful operation.
+Exit status
+1 is returned on any errors during floppy formatting, and an exit status
+of 2 reflects invalid arguments given to the program (along with an
+appropriate information written to diagnostic output).
+.Sh DIAGNOSTICS
+Unless
+.Fl q
+has been specified, a single letter is printed to standard output
+to inform the user about the progress of work.
+First, an
+.Ql F
+is printed when the track is being formatted, then a
+.Ql V
+while it is being verified, and if an error has been detected, it
+will finally change to
+.Ql E .
+Detailed status information (cylinder, head and sector number, and the
+exact cause of the error) will be printed for up to 10 errors after the
+entire formatting process has completed.
+.Sh SEE ALSO
+.Xr fdc 4 ,
+.Xr fdcontrol 8 ,
+.Xr newfs 8 ,
+.Xr newfs_msdos 8
+.Sh HISTORY
+The
+.Nm
+utility
+has been developed for
+.Bx 386 0.1
+and upgraded to the new
+.Xr fdc 4
+floppy disk driver.
+It later became part of the
+.Fx 1.1
+system.
+Starting with
+.Fx 5.0 ,
+it uses the unified density specifications as described in
+.Xr fdcontrol 8 .
+.Sh AUTHORS
+.An -nosplit
+The program has been contributed by
+.An J\(:org Wunsch ,
+Dresden, with changes by
+.An Serge Vakulenko
+and
+.An Andrey A. Chernov ,
+Moscow.
diff --git a/usr.sbin/fdformat/fdformat.c b/usr.sbin/fdformat/fdformat.c
new file mode 100644
index 0000000..341a161
--- /dev/null
+++ b/usr.sbin/fdformat/fdformat.c
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 1992-1994,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$
+ */
+
+#include <sys/types.h>
+#include <sys/fdcio.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "fdutil.h"
+
+static void
+format_track(int fd, int cyl, int secs, int head, int rate,
+ int gaplen, int secsize, int fill, int interleave,
+ int offset)
+{
+ struct fd_formb f;
+ int i, j, il[FD_MAX_NSEC + 1];
+
+ memset(il, 0, sizeof il);
+ for(j = 0, i = 1 + offset; i <= secs + offset; i++) {
+ while(il[(j % secs) + 1])
+ j++;
+ il[(j % secs) + 1] = i;
+ j += interleave;
+ }
+
+ f.format_version = FD_FORMAT_VERSION;
+ f.head = head;
+ f.cyl = cyl;
+ f.transfer_rate = rate;
+
+ f.fd_formb_secshift = secsize;
+ f.fd_formb_nsecs = secs;
+ f.fd_formb_gaplen = gaplen;
+ f.fd_formb_fillbyte = fill;
+ for(i = 0; i < secs; i++) {
+ f.fd_formb_cylno(i) = cyl;
+ f.fd_formb_headno(i) = head;
+ f.fd_formb_secno(i) = il[i+1];
+ f.fd_formb_secsize(i) = secsize;
+ }
+ (void)ioctl(fd, FD_FORM, (caddr_t)&f);
+}
+
+static int
+verify_track(int fd, int track, int tracksize)
+{
+ static char *buf;
+ static int bufsz;
+ int fdopts = -1, ofdopts, rv = 0;
+
+ if (ioctl(fd, FD_GOPTS, &fdopts) < 0)
+ warn("warning: ioctl(FD_GOPTS)");
+ else {
+ ofdopts = fdopts;
+ fdopts |= FDOPT_NORETRY;
+ (void)ioctl(fd, FD_SOPTS, &fdopts);
+ }
+
+ if (bufsz < tracksize)
+ buf = realloc(buf, bufsz = tracksize);
+ if (buf == 0)
+ errx(EX_UNAVAILABLE, "out of memory");
+ if (lseek (fd, (long) track * tracksize, 0) < 0)
+ rv = -1;
+ /* try twice reading it, without using the normal retrier */
+ else if (read (fd, buf, tracksize) != tracksize
+ && read (fd, buf, tracksize) != tracksize)
+ rv = -1;
+ if (fdopts != -1)
+ (void)ioctl(fd, FD_SOPTS, &ofdopts);
+ return (rv);
+}
+
+static void
+usage (void)
+{
+ errx(EX_USAGE,
+ "usage: fdformat [-F fill] [-f fmt] [-s fmtstr] [-nqvy] device");
+}
+
+static int
+yes (void)
+{
+ char reply[256], *p;
+
+ reply[sizeof(reply) - 1] = 0;
+ for (;;) {
+ fflush(stdout);
+ if (!fgets (reply, sizeof(reply) - 1, stdin))
+ return (0);
+ for (p=reply; *p==' ' || *p=='\t'; ++p)
+ continue;
+ if (*p=='y' || *p=='Y')
+ return (1);
+ if (*p=='n' || *p=='N' || *p=='\n' || *p=='\r')
+ return (0);
+ printf("Answer `yes' or `no': ");
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ enum fd_drivetype type;
+ struct fd_type fdt, newft, *fdtp;
+ struct stat sb;
+#define MAXPRINTERRS 10
+ struct fdc_status fdcs[MAXPRINTERRS];
+ int format, fill, quiet, verify, verify_only, confirm;
+ int fd, c, i, track, error, tracks_per_dot, bytes_per_track, errs;
+ int flags;
+ char *fmtstring, *device;
+ const char *name, *descr;
+
+ format = quiet = verify_only = confirm = 0;
+ verify = 1;
+ fill = 0xf6;
+ fmtstring = 0;
+
+ while((c = getopt(argc, argv, "F:f:nqs:vy")) != -1)
+ switch(c) {
+ case 'F': /* fill byte */
+ if (getnum(optarg, &fill)) {
+ fprintf(stderr,
+ "Bad argument %s to -F option; must be numeric\n",
+ optarg);
+ usage();
+ }
+ break;
+
+ case 'f': /* format in kilobytes */
+ if (getnum(optarg, &format)) {
+ fprintf(stderr,
+ "Bad argument %s to -f option; must be numeric\n",
+ optarg);
+ usage();
+ }
+ break;
+
+ case 'n': /* don't verify */
+ verify = 0;
+ break;
+
+ case 'q': /* quiet */
+ quiet = 1;
+ break;
+
+ case 's': /* format string with detailed options */
+ fmtstring = optarg;
+ break;
+
+ case 'v': /* verify only */
+ verify = 1;
+ verify_only = 1;
+ break;
+
+ case 'y': /* confirm */
+ confirm = 1;
+ break;
+
+ default:
+ usage();
+ }
+
+ if(optind != argc - 1)
+ usage();
+
+ if (stat(argv[optind], &sb) == -1 && errno == ENOENT) {
+ /* try prepending _PATH_DEV */
+ device = malloc(strlen(argv[optind]) + sizeof(_PATH_DEV) + 1);
+ if (device == 0)
+ errx(EX_UNAVAILABLE, "out of memory");
+ strcpy(device, _PATH_DEV);
+ strcat(device, argv[optind]);
+ if (stat(device, &sb) == -1) {
+ free(device);
+ device = argv[optind]; /* let it fail below */
+ }
+ } else {
+ device = argv[optind];
+ }
+
+ if ((fd = open(device, O_RDWR | O_NONBLOCK)) < 0)
+ err(EX_OSERR, "open(%s)", device);
+
+ /*
+ * Device initialization.
+ *
+ * First, get the device type descriptor. This tells us about
+ * the media geometry data we need to format a medium. It also
+ * lets us know quickly whether the device name actually points
+ * to a floppy disk drive.
+ *
+ * Then, obtain any drive options. We're mainly interested to
+ * see whether we're currently working on a device with media
+ * density autoselection (FDOPT_AUTOSEL). Then, we add the
+ * device option to tell the kernel not to log media errors,
+ * since we can handle them ourselves. If the device does
+ * media density autoselection, we then need to set the device
+ * type appropriately, since by opening with O_NONBLOCK we
+ * told the driver to bypass media autoselection (otherwise we
+ * wouldn't stand a chance to format an unformatted or damaged
+ * medium). We do not attempt to set the media type on any
+ * other devices since this is a privileged operation. For the
+ * same reason, specifying -f and -s options is only possible
+ * for autoselecting devices.
+ *
+ * Finally, we are ready to turn off O_NONBLOCK, and start to
+ * actually format something.
+ */
+ if(ioctl(fd, FD_GTYPE, &fdt) < 0)
+ errx(EX_OSERR, "not a floppy disk: %s", device);
+ if (ioctl(fd, FD_GDTYPE, &type) == -1)
+ err(EX_OSERR, "ioctl(FD_GDTYPE)");
+ if (format) {
+ getname(type, &name, &descr);
+ fdtp = get_fmt(format, type);
+ if (fdtp == 0)
+ errx(EX_USAGE,
+ "unknown format %d KB for drive type %s",
+ format, name);
+ fdt = *fdtp;
+ }
+ if (fmtstring) {
+ parse_fmt(fmtstring, type, fdt, &newft);
+ fdt = newft;
+ }
+ if (ioctl(fd, FD_STYPE, &fdt) < 0)
+ err(EX_OSERR, "ioctl(FD_STYPE)");
+ if ((flags = fcntl(fd, F_GETFL, 0)) == -1)
+ err(EX_OSERR, "fcntl(F_GETFL)");
+ flags &= ~O_NONBLOCK;
+ if (fcntl(fd, F_SETFL, flags) == -1)
+ err(EX_OSERR, "fcntl(F_SETFL)");
+
+ bytes_per_track = fdt.sectrac * (128 << fdt.secsize);
+
+ /* XXX 20/40 = 0.5 */
+ tracks_per_dot = (fdt.tracks * fdt.heads + 20) / 40;
+
+ if (verify_only) {
+ if(!quiet)
+ printf("Verify %dK floppy `%s'.\n",
+ fdt.tracks * fdt.heads * bytes_per_track / 1024,
+ device);
+ }
+ else if(!quiet && !confirm) {
+ printf("Format %dK floppy `%s'? (y/n): ",
+ fdt.tracks * fdt.heads * bytes_per_track / 1024,
+ device);
+ if(!yes()) {
+ printf("Not confirmed.\n");
+ return (EX_UNAVAILABLE);
+ }
+ }
+
+ /*
+ * Formatting.
+ */
+ if(!quiet) {
+ printf("Processing ");
+ for (i = 0; i < (fdt.tracks * fdt.heads) / tracks_per_dot; i++)
+ putchar('-');
+ printf("\rProcessing ");
+ fflush(stdout);
+ }
+
+ error = errs = 0;
+
+ for (track = 0; track < fdt.tracks * fdt.heads; track++) {
+ if (!verify_only) {
+ format_track(fd, track / fdt.heads, fdt.sectrac,
+ track % fdt.heads, fdt.trans, fdt.f_gap,
+ fdt.secsize, fill, fdt.f_inter,
+ track % fdt.heads? fdt.offset_side2: 0);
+ if(!quiet && !((track + 1) % tracks_per_dot)) {
+ putchar('F');
+ fflush(stdout);
+ }
+ }
+ if (verify) {
+ if (verify_track(fd, track, bytes_per_track) < 0) {
+ error = 1;
+ if (errs < MAXPRINTERRS && errno == EIO) {
+ if (ioctl(fd, FD_GSTAT, fdcs + errs) ==
+ -1)
+ errx(EX_IOERR,
+ "floppy IO error, but no FDC status");
+ errs++;
+ }
+ }
+ if(!quiet && !((track + 1) % tracks_per_dot)) {
+ if (!verify_only)
+ putchar('\b');
+ if (error) {
+ putchar('E');
+ error = 0;
+ }
+ else
+ putchar('V');
+ fflush(stdout);
+ }
+ }
+ }
+ if(!quiet)
+ printf(" done.\n");
+
+ if (!quiet && errs) {
+ fflush(stdout);
+ fprintf(stderr, "Errors encountered:\nCyl Head Sect Error\n");
+ for (i = 0; i < errs && i < MAXPRINTERRS; i++) {
+ fprintf(stderr, " %2d %2d %2d ",
+ fdcs[i].status[3], fdcs[i].status[4],
+ fdcs[i].status[5]);
+ printstatus(fdcs + i, 1);
+ putc('\n', stderr);
+ }
+ if (errs >= MAXPRINTERRS)
+ fprintf(stderr, "(Further errors not printed.)\n");
+ }
+
+ return errs != 0;
+}
diff --git a/usr.sbin/fdread/Makefile b/usr.sbin/fdread/Makefile
new file mode 100644
index 0000000..e99c620
--- /dev/null
+++ b/usr.sbin/fdread/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+PROG= fdread
+SRCS= fdread.c fdutil.c
+
+.if ${MACHINE} == "pc98"
+CFLAGS+= -DPC98
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/fdread/Makefile.depend b/usr.sbin/fdread/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/fdread/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/fdread/fdread.1 b/usr.sbin/fdread/fdread.1
new file mode 100644
index 0000000..d29a4a4
--- /dev/null
+++ b/usr.sbin/fdread/fdread.1
@@ -0,0 +1,233 @@
+.\"
+.\" Copyright (c) 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.
+.\"
+.\" $FreeBSD$
+.\"
+.\"
+.Dd May 14, 2001
+.Dt FDREAD 1
+.Os
+.Sh NAME
+.Nm fdread
+.Nd read floppy disks
+.Sh SYNOPSIS
+.Nm
+.Op Fl qr
+.Op Fl d Ar device
+.Op Fl f Ar fillbyte
+.Op Fl o Ar file
+.Nm
+.Op Fl d Ar device
+.Fl I Ar numsects
+.Op Fl t Ar trackno
+.Sh DESCRIPTION
+The
+.Nm
+utility reads floppy disks.
+Effective read blocking based on the track
+size is performed, and floppy-specific error recovery of otherwise
+bad blocks can be enabled.
+.Pp
+The
+.Nm
+utility
+will always read an entire floppy medium, and write its contents to
+the respective output file.
+Unlike other tools like
+.Xr dd 1 ,
+.Nm
+automatically uses a read block size that is more efficient than
+reading single blocks (usually one track of data at a time), but
+falls back to reading single floppy sectors in case of an input/output
+error occurred, in order to obtain as much valid data as possible.
+While
+.Nm
+is working, kernel error reporting for floppy errors is turned off, so
+the console and/or syslog are not flooded with kernel error messages.
+.Pp
+The
+.Nm
+utility accepts the following options:
+.Bl -tag -width indent
+.It Fl q
+Turn on quiet mode.
+By default, the medium parameters of the device
+are being written to standard error output, progress will be indicated
+by the approximate number of kilobytes read so far, and errors will be
+printed out in detail, including the information about the location of
+recovered data in the output.
+In quiet mode, none of these messages
+will be generated.
+.It Fl r
+Enable error recovery.
+By default,
+.Nm
+stops after the first unrecovered read error, much like
+.Xr dd 1
+does.
+In recovery mode, however, one of two recovery actions will be
+taken:
+.Bl -bullet
+.It
+If the error was a CRC error in the data field, the
+kernel is told to ignore the error, and data are transferred to the
+output file anyway.
+.Bf -emphasis
+Note that this will cause the erroneous data
+to be included in the output file!
+.Ef
+Still, this is the best recovery action that can be taken at all.
+.It
+All other errors are really fatal (usually, the FDC did not find the
+sector ID fields), thus a dummy block with fill
+bytes will be included in the output file.
+.El
+.Pp
+Unless operating in quiet mode, the action taken and the location of
+the error in the output file will be displayed.
+.It Fl d Ar device
+Specify the input floppy device, defaulting to
+.Pa /dev/fd0 .
+The parameter
+.Ar device
+must be a valid floppy disk device.
+.It Fl f Ar fillbyte
+Value of the fill byte used for dummy blocks in the output file in
+recovery mode.
+Defaults to
+.Ql 0xf0 .
+(Mnemonic:
+.Dq foo . )
+The value can be specified using the usual C language notation of
+the number base.
+.It Fl o Ar file
+Specify the output file to be
+.Ar file .
+By default, the data will be written to standard output.
+.It Fl I Ar numsects
+Read
+.Ar numsects
+sector ID fields, and write out their contents to standard output.
+Each sector ID field contains recorded values for the cylinder number
+.Pq Ql C ,
+the head number
+.Pq Ql H ,
+the record number (sector number starting with 1)
+.Pq Ql R ,
+and the
+.Em sector shift value
+(0 = 128 bytes, 1 = 256 bytes, 2 = 512 bytes, 3 = 1024 bytes)
+.Pq Ql N .
+The
+.Fl I
+option is mutually exclusive with all other options except
+.Fl d Ar device
+and
+.Fl t Ar trackno .
+.It Fl t Ar trackno
+Specify the track number (cylinder number * number of heads + head
+number) to read the sector ID fields from; only allowed together with
+the
+.Fl I Ar numsects
+option.
+.El
+.Sh FILES
+.Bl -tag -width /dev/fd0
+.It Pa /dev/fd0
+Default device to read from.
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility sets the exit value according to
+.Xr sysexits 3 .
+In recovery mode, the exit value will be set to
+.Dv EX_IOERR
+if any error occurred during processing (even in quiet mode).
+.Sh DIAGNOSTICS
+Unless running in quiet mode, upon encountering an error, the status
+of the floppy disc controller (FDC) will be printed out, both in
+hexadecimal form, followed by a textual description that translates
+those values into a human-readable form for the most common error
+cases that can happen in a PC environment.
+.Pp
+The FDC error status includes the three FDC status registers
+.Ql ST0 ,
+.Ql ST1 ,
+and
+.Ql ST2 ,
+as well as the location of the error (physical cylinder, head, and sector
+number, plus the
+.Dq sector shift value ,
+respectively).
+See the manual for the NE765 or compatible for details
+about the status register contents.
+.Pp
+The FDC's status is then examined to determine whether the error is
+deemed to be recoverable.
+If error recovery was requested, the
+location of the bad block in the output file is indicated by its
+(hexadecimal) bounds.
+Also, a summary line indicating the total number
+of transfer errors will be printed before exiting.
+.Sh SEE ALSO
+.Xr dd 1 ,
+.Xr fdwrite 1 ,
+.Xr sysexits 3 ,
+.Xr fdc 4 ,
+.Xr fdcontrol 8
+.Sh HISTORY
+The
+.Nm
+utility was written mainly to provide a means of recovering at least some of
+the data on bad media, and to obviate the need to invoke
+.Xr dd 1
+with too many hard to memorize options that might be useful to handle
+a floppy.
+.Pp
+The command appeared in
+.Fx 5.0 .
+.Sh AUTHORS
+Program and man page by
+.An J\(:org Wunsch .
+.Sh BUGS
+Concurrent traffic on the second floppy drive located at the same FDC
+will make error recovery attempts pointless, since the FDC status
+obtained after a read error occurred cannot be guaranteed to actually
+belong to the erroneous transfer.
+Thus using option
+.Fl r
+is only reliable if
+.Ar device
+is the only active drive on that controller.
+.Pp
+No attempt beyond the floppy error retry mechanism of
+.Xr fdc 4
+is made in order to see whether bad sectors could still be read
+without errors by trying multiple times.
+.Pp
+Bits that are (no longer) available on the floppy medium cannot be
+guessed by
+.Nm .
diff --git a/usr.sbin/fdread/fdread.c b/usr.sbin/fdread/fdread.c
new file mode 100644
index 0000000..770f92d
--- /dev/null
+++ b/usr.sbin/fdread/fdread.c
@@ -0,0 +1,330 @@
+/*
+ * Copyright (c) 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/fdcio.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include <dev/ic/nec765.h>
+
+#include "fdutil.h"
+
+static int quiet, recover;
+static unsigned char fillbyte = 0xf0; /* "foo" */
+
+static int doread(int fd, FILE *of, const char *_devname);
+static int doreadid(int fd, unsigned int numids, unsigned int trackno);
+static void usage(void);
+
+static void
+usage(void)
+{
+
+ errx(EX_USAGE,
+ "usage: fdread [-qr] [-d device] [-f fillbyte]\n"
+ " fdread [-d device] -I numids [-t trackno]");
+}
+
+
+int
+main(int argc, char **argv)
+{
+ int c, errs = 0;
+ unsigned int numids = 0, trackno = 0;
+ const char *fname = 0, *_devname = "/dev/fd0";
+ char *cp;
+ FILE *of = stdout;
+ int fd;
+ unsigned long ul;
+
+ while ((c = getopt(argc, argv, "d:f:I:o:qrt:")) != -1)
+ switch (c) {
+ case 'd':
+ _devname = optarg;
+ break;
+
+ case 'f':
+ ul = strtoul(optarg, &cp, 0);
+ if (*cp != '\0') {
+ fprintf(stderr,
+ "Bad argument %s to -f option; must be numeric\n",
+ optarg);
+ usage();
+ }
+ if (ul > 0xff)
+ warnx(
+ "Warning: fillbyte %#lx too large, truncating\n",
+ ul);
+ fillbyte = ul & 0xff;
+ break;
+
+ case 'I':
+ ul = strtoul(optarg, &cp, 0);
+ if (*cp != '\0') {
+ fprintf(stderr,
+ "Bad argument %s to -I option; must be numeric\n",
+ optarg);
+ usage();
+ }
+ numids = ul;
+ break;
+
+ case 'o':
+ fname = optarg;
+ break;
+
+ case 'q':
+ quiet++;
+ break;
+
+ case 'r':
+ recover++;
+ break;
+
+ case 't':
+ ul = strtoul(optarg, &cp, 0);
+ if (*cp != '\0') {
+ fprintf(stderr,
+ "Bad argument %s to -t option; must be numeric\n",
+ optarg);
+ usage();
+ }
+ trackno = ul;
+ break;
+
+ default:
+ errs++;
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 0 || errs)
+ usage();
+ /* check for mutually exclusive options */
+ if (numids) {
+ if (fname || quiet || recover)
+ usage();
+ } else {
+ if (trackno)
+ usage();
+ }
+
+ if (fname) {
+ if ((of = fopen(fname, "w")) == NULL)
+ err(EX_OSERR, "cannot create output file %s", fname);
+ }
+
+ if ((fd = open(_devname, O_RDONLY)) == -1)
+ err(EX_OSERR, "cannot open device %s", _devname);
+
+ return (numids? doreadid(fd, numids, trackno): doread(fd, of, _devname));
+}
+
+static int
+doread(int fd, FILE *of, const char *_devname)
+{
+ char *trackbuf;
+ int rv, fdopts, recoverable, nerrs = 0;
+ unsigned int nbytes, tracksize, mediasize, secsize, n;
+ struct fdc_status fdcs;
+ struct fd_type fdt;
+
+ if (ioctl(fd, FD_GTYPE, &fdt) == -1)
+ err(EX_OSERR, "ioctl(FD_GTYPE) failed -- not a floppy?");
+
+ secsize = 128 << fdt.secsize;
+ tracksize = fdt.sectrac * secsize;
+ mediasize = tracksize * fdt.tracks * fdt.heads;
+ if ((trackbuf = malloc(tracksize)) == 0)
+ errx(EX_TEMPFAIL, "out of memory");
+
+ if (!quiet)
+ fprintf(stderr, "Reading %d * %d * %d * %d medium at %s\n",
+ fdt.tracks, fdt.heads, fdt.sectrac, secsize, _devname);
+
+ for (nbytes = 0; nbytes < mediasize;) {
+ if (lseek(fd, nbytes, SEEK_SET) != nbytes)
+ err(EX_OSERR, "cannot lseek()");
+ rv = read(fd, trackbuf, tracksize);
+ if (rv == 0) {
+ /* EOF? */
+ warnx("premature EOF after %u bytes", nbytes);
+ return (EX_OK);
+ }
+ if ((unsigned)rv == tracksize) {
+ nbytes += rv;
+ if (!quiet)
+ fprintf(stderr, "%5d KB\r", nbytes / 1024);
+ fwrite(trackbuf, sizeof(unsigned char), rv, of);
+ fflush(of);
+ continue;
+ }
+ if (rv == -1) {
+ /* fall back reading one sector at a time */
+ for (n = 0; n < tracksize; n += secsize) {
+ if (lseek(fd, nbytes, SEEK_SET) != nbytes)
+ err(EX_OSERR, "cannot lseek()");
+ rv = read(fd, trackbuf, secsize);
+ if ((unsigned) rv == secsize) {
+ nbytes += rv;
+ if (!quiet)
+ fprintf(stderr, "%5d KB\r",
+ nbytes / 1024);
+ fwrite(trackbuf, sizeof(unsigned char),
+ rv, of);
+ fflush(of);
+ continue;
+ }
+ if (rv == -1) {
+ if (errno != EIO) {
+ if (!quiet)
+ putc('\n', stderr);
+ perror("non-IO error");
+ return (EX_OSERR);
+ }
+ if (ioctl(fd, FD_GSTAT, &fdcs) == -1)
+ errx(EX_IOERR,
+ "floppy IO error, but no FDC status");
+ nerrs++;
+ recoverable = fdcs.status[2] &
+ NE7_ST2_DD;
+ if (!quiet) {
+ printstatus(&fdcs, 0);
+ fputs(" (", stderr);
+ if (!recoverable)
+ fputs("not ", stderr);
+ fputs("recoverable)", stderr);
+ }
+ if (!recover) {
+ if (!quiet)
+ putc('\n', stderr);
+ return (EX_IOERR);
+ }
+ memset(trackbuf, fillbyte, secsize);
+ if (recoverable) {
+ fdopts |= FDOPT_NOERROR;
+ if (ioctl(fd, FD_SOPTS,
+ &fdopts) == -1)
+ err(EX_OSERR,
+ "ioctl(fd, FD_SOPTS, FDOPT_NOERROR)");
+ rv = read(fd, trackbuf,
+ secsize);
+ if ((unsigned)rv != secsize)
+ err(EX_IOERR,
+ "read() with FDOPT_NOERROR still fails");
+ fdopts &= ~FDOPT_NOERROR;
+ (void)ioctl(fd, FD_SOPTS,
+ &fdopts);
+ }
+ if (!quiet) {
+ if (recoverable)
+ fprintf(stderr,
+ ": recovered");
+ else
+ fprintf(stderr,
+ ": dummy");
+ fprintf(stderr,
+ " data @ %#x ... %#x\n",
+ nbytes,
+ nbytes + secsize - 1);
+ }
+ nbytes += secsize;
+ fwrite(trackbuf, sizeof(unsigned char),
+ secsize, of);
+ fflush(of);
+ continue;
+ }
+ errx(EX_OSERR, "unexpected read() result: %d",
+ rv);
+ }
+ }
+ if ((unsigned)rv < tracksize) {
+ /* should not happen */
+ nbytes += rv;
+ if (!quiet)
+ fprintf(stderr, "\nshort after %5d KB\r",
+ nbytes / 1024);
+ fwrite(trackbuf, sizeof(unsigned char), rv, of);
+ fflush(of);
+ continue;
+ }
+ }
+ if (!quiet) {
+ putc('\n', stderr);
+ if (nerrs)
+ fprintf(stderr, "%d error%s\n",
+ nerrs, nerrs > 1? "s": "");
+ }
+
+ return (nerrs? EX_IOERR: EX_OK);
+}
+
+static int
+doreadid(int fd, unsigned int numids, unsigned int trackno)
+{
+ int rv = 0;
+ unsigned int i;
+ struct fdc_readid info;
+ struct fdc_status fdcs;
+ struct fd_type fdt;
+
+ if (ioctl(fd, FD_GTYPE, &fdt) == -1)
+ err(EX_OSERR, "ioctl(FD_GTYPE) failed -- not a floppy?");
+
+ for (i = 0; i < numids; i++) {
+ info.cyl = trackno / fdt.heads;
+ info.head = fdt.heads > 1? trackno % fdt.heads: 0;
+ if (ioctl(fd, FD_READID, &info) == 0) {
+ printf("C = %d, H = %d, R = %d, N = %d\n",
+ info.cyl, info.head, info.sec, info.secshift);
+ } else {
+ if (errno != EIO) {
+ perror("non-IO error");
+ return (EX_OSERR);
+ }
+ if (ioctl(fd, FD_GSTAT, &fdcs) == -1)
+ errx(EX_IOERR,
+ "floppy IO error, but no FDC status");
+ printstatus(&fdcs, 0);
+ putc('\n', stderr);
+ rv = EX_IOERR;
+ }
+ }
+
+ return (rv);
+}
diff --git a/usr.sbin/fdread/fdutil.c b/usr.sbin/fdread/fdutil.c
new file mode 100644
index 0000000..c66b0c1
--- /dev/null
+++ b/usr.sbin/fdread/fdutil.c
@@ -0,0 +1,509 @@
+/*
+ * Copyright (c) 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <dev/ic/nec765.h>
+
+#include <sys/fdcio.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+#include "fdutil.h"
+
+/*
+ * Decode the FDC status pointed to by `fdcsp', and print a textual
+ * translation to stderr. If `terse' is false, the numerical FDC
+ * register status is printed, too.
+ */
+void
+printstatus(struct fdc_status *fdcsp, int terse)
+{
+ char msgbuf[100];
+
+ if (!terse)
+ fprintf(stderr,
+ "\nFDC status ST0=%#x ST1=%#x ST2=%#x C=%u H=%u R=%u N=%u:\n",
+ fdcsp->status[0] & 0xff,
+ fdcsp->status[1] & 0xff,
+ fdcsp->status[2] & 0xff,
+ fdcsp->status[3] & 0xff,
+ fdcsp->status[4] & 0xff,
+ fdcsp->status[5] & 0xff,
+ fdcsp->status[6] & 0xff);
+
+ if ((fdcsp->status[0] & NE7_ST0_IC_RC) == 0) {
+ sprintf(msgbuf, "timeout");
+ } else if ((fdcsp->status[0] & NE7_ST0_IC_RC) != NE7_ST0_IC_AT) {
+ sprintf(msgbuf, "unexcpted interrupt code %#x",
+ fdcsp->status[0] & NE7_ST0_IC_RC);
+ } else {
+ strcpy(msgbuf, "unexpected error code in ST1/ST2");
+
+ if (fdcsp->status[1] & NE7_ST1_EN)
+ strcpy(msgbuf, "end of cylinder (wrong format)");
+ else if (fdcsp->status[1] & NE7_ST1_DE) {
+ if (fdcsp->status[2] & NE7_ST2_DD)
+ strcpy(msgbuf, "CRC error in data field");
+ else
+ strcpy(msgbuf, "CRC error in ID field");
+ } else if (fdcsp->status[1] & NE7_ST1_MA) {
+ if (fdcsp->status[2] & NE7_ST2_MD)
+ strcpy(msgbuf, "no address mark in data field");
+ else
+ strcpy(msgbuf, "no address mark in ID field");
+ } else if (fdcsp->status[2] & NE7_ST2_WC)
+ strcpy(msgbuf, "wrong cylinder (format mismatch)");
+ else if (fdcsp->status[1] & NE7_ST1_ND)
+ strcpy(msgbuf, "no data (sector not found)");
+ }
+ fputs(msgbuf, stderr);
+}
+
+static struct fd_type fd_types_auto[1] =
+ { { 0,0,0,0,0,0,0,0,0,0,0,FL_AUTO } };
+
+
+static struct fd_type fd_types_288m[] = {
+#ifndef PC98
+#if 0
+ { FDF_3_2880 },
+#endif
+ { FDF_3_1722 },
+ { FDF_3_1476 },
+ { FDF_3_1440 },
+ { FDF_3_1200 },
+ { FDF_3_820 },
+ { FDF_3_800 },
+ { FDF_3_720 },
+#endif /* !PC98 */
+ { 0,0,0,0,0,0,0,0,0,0,0,0 }
+};
+
+static struct fd_type fd_types_144m[] = {
+#ifdef PC98
+ { FDF_3_1440 },
+ { FDF_3_1200 },
+ { FDF_3_720 },
+ { FDF_3_360 },
+ { FDF_3_640 },
+ { FDF_3_1230 },
+ { 0,0,0,0,0,0,0,0,0,0,0,0 }
+#else
+ { FDF_3_1722 },
+ { FDF_3_1476 },
+ { FDF_3_1440 },
+ { FDF_3_1200 },
+ { FDF_3_820 },
+ { FDF_3_800 },
+ { FDF_3_720 },
+ { 0,0,0,0,0,0,0,0,0,0,0,0 }
+#endif
+};
+
+static struct fd_type fd_types_12m[] = {
+#ifdef PC98
+ { FDF_5_1200 },
+ { FDF_5_720 },
+ { FDF_5_360 },
+ { FDF_5_640 },
+ { FDF_5_1230 },
+ { 0,0,0,0,0,0,0,0,0,0,0,0 }
+#else
+ { FDF_5_1200 },
+ { FDF_5_1230 },
+ { FDF_5_1480 },
+ { FDF_5_1440 },
+ { FDF_5_820 },
+ { FDF_5_800 },
+ { FDF_5_720 },
+ { FDF_5_360 | FL_2STEP },
+ { FDF_5_640 },
+ { 0,0,0,0,0,0,0,0,0,0,0,0 }
+#endif
+};
+
+static struct fd_type fd_types_720k[] =
+{
+#ifndef PC98
+ { FDF_3_720 },
+#endif
+ { 0,0,0,0,0,0,0,0,0,0,0,0 }
+};
+
+static struct fd_type fd_types_360k[] =
+{
+#ifndef PC98
+ { FDF_5_360 },
+#endif
+ { 0,0,0,0,0,0,0,0,0,0,0,0 }
+};
+
+
+/*
+ * Parse a format string, and fill in the parameter pointed to by `out'.
+ *
+ * sectrac,secsize,datalen,gap,ncyls,speed,heads,f_gap,f_inter,offs2,flags[...]
+ *
+ * sectrac = sectors per track
+ * secsize = sector size in bytes
+ * datalen = length of sector if secsize == 128
+ * gap = gap length when reading
+ * ncyls = number of cylinders
+ * speed = transfer speed 250/300/500/1000 KB/s
+ * heads = number of heads
+ * f_gap = gap length when formatting
+ * f_inter = sector interleave when formatting
+ * offs2 = offset of sectors on side 2
+ * flags = +/-mfm | +/-2step | +/-perpend
+ * mfm - use MFM recording
+ * 2step - use 2 steps between cylinders
+ * perpend - user perpendicular (vertical) recording
+ *
+ * Any omitted value will be passed on from parameter `in'.
+ */
+void
+parse_fmt(const char *s, enum fd_drivetype type,
+ struct fd_type in, struct fd_type *out)
+{
+ int i, j;
+ const char *cp;
+ char *s1;
+
+ *out = in;
+
+ for (i = 0;; i++) {
+ if (s == 0)
+ break;
+
+ if ((cp = strchr(s, ',')) == 0) {
+ s1 = strdup(s);
+ if (s1 == NULL)
+ abort();
+ s = 0;
+ } else {
+ s1 = malloc(cp - s + 1);
+ if (s1 == NULL)
+ abort();
+ memcpy(s1, s, cp - s);
+ s1[cp - s] = 0;
+
+ s = cp + 1;
+ }
+ if (strlen(s1) == 0) {
+ free(s1);
+ continue;
+ }
+
+ switch (i) {
+ case 0: /* sectrac */
+ if (getnum(s1, &out->sectrac))
+ errx(EX_USAGE,
+ "bad numeric value for sectrac: %s", s1);
+ break;
+
+ case 1: /* secsize */
+ if (getnum(s1, &j))
+ errx(EX_USAGE,
+ "bad numeric value for secsize: %s", s1);
+ if (j == 128) out->secsize = 0;
+ else if (j == 256) out->secsize = 1;
+ else if (j == 512) out->secsize = 2;
+ else if (j == 1024) out->secsize = 3;
+ else
+ errx(EX_USAGE, "bad sector size %d", j);
+ break;
+
+ case 2: /* datalen */
+ if (getnum(s1, &j))
+ errx(EX_USAGE,
+ "bad numeric value for datalen: %s", s1);
+ if (j >= 256)
+ errx(EX_USAGE, "bad datalen %d", j);
+ out->datalen = j;
+ break;
+
+ case 3: /* gap */
+ if (getnum(s1, &out->gap))
+ errx(EX_USAGE,
+ "bad numeric value for gap: %s", s1);
+ break;
+
+ case 4: /* ncyls */
+ if (getnum(s1, &j))
+ errx(EX_USAGE,
+ "bad numeric value for ncyls: %s", s1);
+ if (j > 85)
+ errx(EX_USAGE, "bad # of cylinders %d", j);
+ out->tracks = j;
+ break;
+
+ case 5: /* speed */
+ if (getnum(s1, &j))
+ errx(EX_USAGE,
+ "bad numeric value for speed: %s", s1);
+ switch (type) {
+ default:
+ abort(); /* paranoia */
+
+ case FDT_360K:
+ case FDT_720K:
+ if (j == 250)
+ out->trans = FDC_250KBPS;
+ else
+ errx(EX_USAGE, "bad speed %d", j);
+ break;
+
+ case FDT_12M:
+ if (j == 300)
+ out->trans = FDC_300KBPS;
+ else if (j == 250)
+ out->trans = FDC_250KBPS;
+ else if (j == 500)
+ out->trans = FDC_500KBPS;
+ else
+ errx(EX_USAGE, "bad speed %d", j);
+ break;
+
+ case FDT_288M:
+ if (j == 1000)
+ out->trans = FDC_1MBPS;
+ /* FALLTHROUGH */
+ case FDT_144M:
+ if (j == 250)
+ out->trans = FDC_250KBPS;
+ else if (j == 500)
+ out->trans = FDC_500KBPS;
+ else
+ errx(EX_USAGE, "bad speed %d", j);
+ break;
+ }
+ break;
+
+ case 6: /* heads */
+ if (getnum(s1, &j))
+ errx(EX_USAGE,
+ "bad numeric value for heads: %s", s1);
+ if (j == 1 || j == 2)
+ out->heads = j;
+ else
+ errx(EX_USAGE, "bad # of heads %d", j);
+ break;
+
+ case 7: /* f_gap */
+ if (getnum(s1, &out->f_gap))
+ errx(EX_USAGE,
+ "bad numeric value for f_gap: %s", s1);
+ break;
+
+ case 8: /* f_inter */
+ if (getnum(s1, &out->f_inter))
+ errx(EX_USAGE,
+ "bad numeric value for f_inter: %s", s1);
+ break;
+
+ case 9: /* offs2 */
+ if (getnum(s1, &out->offset_side2))
+ errx(EX_USAGE,
+ "bad numeric value for offs2: %s", s1);
+ break;
+
+ default:
+ if (strcmp(s1, "+mfm") == 0)
+ out->flags |= FL_MFM;
+ else if (strcmp(s1, "-mfm") == 0)
+ out->flags &= ~FL_MFM;
+ else if (strcmp(s1, "+auto") == 0)
+ out->flags |= FL_AUTO;
+ else if (strcmp(s1, "-auto") == 0)
+ out->flags &= ~FL_AUTO;
+ else if (strcmp(s1, "+2step") == 0)
+ out->flags |= FL_2STEP;
+ else if (strcmp(s1, "-2step") == 0)
+ out->flags &= ~FL_2STEP;
+ else if (strcmp(s1, "+perpnd") == 0)
+ out->flags |= FL_PERPND;
+ else if (strcmp(s1, "-perpnd") == 0)
+ out->flags &= ~FL_PERPND;
+ else
+ errx(EX_USAGE, "bad flag: %s", s1);
+ break;
+ }
+ free(s1);
+ }
+
+ out->size = out->tracks * out->heads * out->sectrac;
+}
+
+/*
+ * Print a textual translation of the drive (density) type described
+ * by `in' to stdout. The string uses the same form that is parseable
+ * by parse_fmt().
+ */
+void
+print_fmt(struct fd_type in)
+{
+ int secsize, speed;
+
+ secsize = 128 << in.secsize;
+ switch (in.trans) {
+ case FDC_250KBPS: speed = 250; break;
+ case FDC_300KBPS: speed = 300; break;
+ case FDC_500KBPS: speed = 500; break;
+ case FDC_1MBPS: speed = 1000; break;
+ default: speed = 1; break;
+ }
+
+ printf("%d,%d,%#x,%#x,%d,%d,%d,%#x,%d,%d",
+ in.sectrac, secsize, in.datalen, in.gap, in.tracks,
+ speed, in.heads, in.f_gap, in.f_inter, in.offset_side2);
+ if (in.flags & FL_MFM)
+ printf(",+mfm");
+ if (in.flags & FL_2STEP)
+ printf(",+2step");
+ if (in.flags & FL_PERPND)
+ printf(",+perpnd");
+ if (in.flags & FL_AUTO)
+ printf(",+auto");
+ putc('\n', stdout);
+}
+
+/*
+ * Based on `size' (in kilobytes), walk through the table of known
+ * densities for drive type `type' and see if we can find one. If
+ * found, return it (as a pointer to static storage), otherwise return
+ * NULL.
+ */
+struct fd_type *
+get_fmt(int size, enum fd_drivetype type)
+{
+ int i, n;
+ struct fd_type *fdtp;
+
+ switch (type) {
+ default:
+ return (0);
+
+ case FDT_360K:
+ fdtp = fd_types_360k;
+ n = sizeof fd_types_360k / sizeof(struct fd_type);
+ break;
+
+ case FDT_720K:
+ fdtp = fd_types_720k;
+ n = sizeof fd_types_720k / sizeof(struct fd_type);
+ break;
+
+ case FDT_12M:
+ fdtp = fd_types_12m;
+ n = sizeof fd_types_12m / sizeof(struct fd_type);
+ break;
+
+ case FDT_144M:
+ fdtp = fd_types_144m;
+ n = sizeof fd_types_144m / sizeof(struct fd_type);
+ break;
+
+ case FDT_288M:
+ fdtp = fd_types_288m;
+ n = sizeof fd_types_288m / sizeof(struct fd_type);
+ break;
+ }
+
+ if (size == -1)
+ return fd_types_auto;
+
+ for (i = 0; i < n; i++, fdtp++) {
+ fdtp->size = fdtp->sectrac * fdtp->heads * fdtp->tracks;
+ if (((128 << fdtp->secsize) * fdtp->size / 1024) == size)
+ return (fdtp);
+ }
+ return (0);
+}
+
+/*
+ * Parse a number from `s'. If the string cannot be converted into a
+ * number completely, return -1, otherwise 0. The result is returned
+ * in `*res'.
+ */
+int
+getnum(const char *s, int *res)
+{
+ unsigned long ul;
+ char *cp;
+
+ ul = strtoul(s, &cp, 0);
+ if (*cp != '\0')
+ return (-1);
+
+ *res = (int)ul;
+ return (0);
+}
+
+/*
+ * Return a short name and a verbose description for the drive
+ * described by `t'.
+ */
+void
+getname(enum fd_drivetype t, const char **name, const char **descr)
+{
+
+ switch (t) {
+ default:
+ *name = "unknown";
+ *descr = "unknown drive type";
+ break;
+
+ case FDT_360K:
+ *name = "360K";
+ *descr = "5.25\" double-density";
+ break;
+
+ case FDT_12M:
+ *name = "1.2M";
+ *descr = "5.25\" high-density";
+ break;
+
+ case FDT_720K:
+ *name = "720K";
+ *descr = "3.5\" double-density";
+ break;
+
+ case FDT_144M:
+ *name = "1.44M";
+ *descr = "3.5\" high-density";
+ break;
+
+ case FDT_288M:
+ *name = "2.88M";
+ *descr = "3.5\" extra-density";
+ break;
+ }
+}
diff --git a/usr.sbin/fdread/fdutil.h b/usr.sbin/fdread/fdutil.h
new file mode 100644
index 0000000..a093228
--- /dev/null
+++ b/usr.sbin/fdread/fdutil.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 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.
+ *
+ * $FreeBSD$
+ */
+
+
+void printstatus(struct fdc_status *fdcsp, int terse);
+void parse_fmt(const char *, enum fd_drivetype,
+ struct fd_type, struct fd_type *);
+struct fd_type *get_fmt(int, enum fd_drivetype);
+void print_fmt(struct fd_type);
+int getnum(const char *, int *);
+void getname(enum fd_drivetype, const char **, const char **);
+
diff --git a/usr.sbin/fdwrite/Makefile b/usr.sbin/fdwrite/Makefile
new file mode 100644
index 0000000..b97bdd4
--- /dev/null
+++ b/usr.sbin/fdwrite/Makefile
@@ -0,0 +1,12 @@
+# ----------------------------------------------------------------------------
+# "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$
+
+PROG= fdwrite
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/fdwrite/Makefile.depend b/usr.sbin/fdwrite/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/fdwrite/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/fdwrite/fdwrite.1 b/usr.sbin/fdwrite/fdwrite.1
new file mode 100644
index 0000000..9f1270f
--- /dev/null
+++ b/usr.sbin/fdwrite/fdwrite.1
@@ -0,0 +1,129 @@
+.\"
+.\" ----------------------------------------------------------------------------
+.\" "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 September 16, 1993
+.Dt FDWRITE 1
+.Os
+.Sh NAME
+.Nm fdwrite
+.Nd format and write floppy disks
+.Sh SYNOPSIS
+.Nm
+.Op Fl v
+.Op Fl y
+.Op Fl f Ar inputfile
+.Op Fl d Ar device
+.Sh DESCRIPTION
+The
+.Nm
+utility formats and writes one and more floppy disks.
+Any floppy disk device capable of formatting can be used.
+.Pp
+The
+.Nm
+utility will ask the user
+(on
+.Pa /dev/tty )
+to insert a new floppy and press return.
+The device will then be opened, and queried for its parameters,
+then each track will be formatted, written with data from the
+.Ar inputfile ,
+read back and compared.
+When the floppy disk is filled, the process is repeated, with the next disk.
+This continues until the program is interrupted or EOF is encountered on the
+.Ar inputfile .
+.Pp
+The options are as follows:
+.Bl -tag -width 10n -offset indent
+.It Fl v
+Toggle verbosity on stdout.
+Default is ``on''.
+After
+.Ar device
+is opened first time the format will be printed.
+During operation progress will be reported with the number of tracks
+remaining on the current floppy disk, and the letters I, Z, F, W,
+R and C, which indicates completion of Input, Zero-fill, Format
+Write, Read and Compare of current track respectively.
+.It Fl y
+Do not ask for presence of a floppy disk in the drive.
+This non-interactive flag
+is useful for shell scripts.
+.It Fl f Ar inputfile
+Input file to read.
+If none is given, stdin is assumed.
+.It Fl d Ar device
+The name of the floppy device to write to.
+Default is
+.Pa /dev/fd0 .
+.El
+.Pp
+The
+.Nm
+utility actually closes the
+.Ar device
+while it waits for the user to press return,
+it is thus quite possible to use the drive for other purposes at this
+time and later resume writing with the next floppy.
+.Pp
+The parameters returned from
+.Ar device
+are used for formatting.
+If custom formatting is needed, please use
+.Xr fdformat 1
+instead.
+.Sh EXAMPLES
+The
+.Nm
+utility
+was planned as a tool to make life easier when writing a set of floppies,
+one such use could be to write a tar-archive:
+.Pp
+.Dl tar cf - . | gzip -9 | fdwrite -d /dev/fd0.1720 -v
+.Pp
+The main difference from using
+.Xr tar 1 Ns 's
+multivolume facility is of course the formatting of the floppies, which
+here is done on the fly,
+thus reducing the amount of work for the floppy-jockey.
+.Sh SEE ALSO
+.Xr fdformat 1
+.Sh HISTORY
+The
+.Nm
+utility was written while waiting for ``make world'' to complete.
+Some of the code was taken from
+.Xr fdformat 1 .
+.Sh AUTHORS
+The program has been contributed by
+.An Poul-Henning Kamp Aq Mt phk@FreeBSD.org .
+.Sh BUGS
+Diagnostics are less than complete at present.
+.Pp
+If a floppy is sick, and the
+.Ar inputfile
+is seekable, it should ask the user to frisbee the disk, insert
+another, and rewind to the right spot and continue.
+.Pp
+This concept could be extended to cover non-seekable input also
+by employing a temporary file.
+.Pp
+An option (defaulting to zero) should allow the user to ask for
+retries in case of failure.
+.Pp
+At present a suitable tool for reading back a multivolume set
+of floppies is missing.
+Programs like
+.Xr tar 1
+for instance, will do the job, if the data has not been compressed.
+One can always trust
+.Xr dd 1
+to help out in this situation of course.
diff --git a/usr.sbin/fdwrite/fdwrite.c b/usr.sbin/fdwrite/fdwrite.c
new file mode 100644
index 0000000..8c953a6
--- /dev/null
+++ b/usr.sbin/fdwrite/fdwrite.c
@@ -0,0 +1,196 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "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 <ctype.h>
+#include <err.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/fdcio.h>
+
+static int
+format_track(int fd, int cyl, int secs, int head, int rate,
+ int gaplen, int secsize, int fill, int interleave)
+{
+ struct fd_formb f;
+ register int i,j;
+ int il[100];
+
+ memset(il,0,sizeof il);
+ for(j = 0, i = 1; i <= secs; i++) {
+ while(il[(j%secs)+1]) j++;
+ il[(j%secs)+1] = i;
+ j += interleave;
+ }
+
+ f.format_version = FD_FORMAT_VERSION;
+ f.head = head;
+ f.cyl = cyl;
+ f.transfer_rate = rate;
+
+ f.fd_formb_secshift = secsize;
+ f.fd_formb_nsecs = secs;
+ f.fd_formb_gaplen = gaplen;
+ f.fd_formb_fillbyte = fill;
+ for(i = 0; i < secs; i++) {
+ f.fd_formb_cylno(i) = cyl;
+ f.fd_formb_headno(i) = head;
+ f.fd_formb_secno(i) = il[i+1];
+ f.fd_formb_secsize(i) = secsize;
+ }
+ return ioctl(fd, FD_FORM, (caddr_t)&f);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: fdwrite [-v] [-y] [-f inputfile] [-d device]\n");
+ exit(2);
+}
+
+int
+main(int argc, char **argv)
+{
+ int inputfd = -1, c, fdn = 0, i,j,fd;
+ int bpt, verbose=1, nbytes=0, track;
+ int interactive = 1;
+ const char *device= "/dev/fd0";
+ char *trackbuf = 0,*vrfybuf = 0;
+ struct fd_type fdt;
+ FILE *tty;
+
+ setbuf(stdout,0);
+ while((c = getopt(argc, argv, "d:f:vy")) != -1)
+ switch(c) {
+ case 'd': /* Which drive */
+ device = optarg;
+ break;
+
+ case 'f': /* input file */
+ if (inputfd >= 0)
+ close(inputfd);
+ inputfd = open(optarg,O_RDONLY);
+ if (inputfd < 0)
+ err(1, "%s", optarg);
+ break;
+
+ case 'v': /* Toggle verbosity */
+ verbose = !verbose;
+ break;
+
+ case 'y': /* Don't confirm? */
+ interactive = 0;
+ break;
+
+ case '?': default:
+ usage();
+ }
+
+ if (inputfd < 0)
+ inputfd = 0;
+
+ if (!isatty(1))
+ interactive = 0;
+
+ if(optind < argc)
+ usage();
+
+ tty = fopen(_PATH_TTY,"r+");
+ if(!tty)
+ err(1, _PATH_TTY);
+ setbuf(tty,0);
+
+ for(j=1;j > 0;) {
+ fdn++;
+ if (interactive) {
+ fprintf(tty,
+ "Please insert floppy #%d in drive %s and press return >",
+ fdn,device);
+ while(1) {
+ i = getc(tty);
+ if(i == '\n') break;
+ }
+ }
+
+ if((fd = open(device, O_RDWR)) < 0)
+ err(1, "%s", device);
+
+ if(ioctl(fd, FD_GTYPE, &fdt) < 0)
+ errx(1, "not a floppy disk: %s", device);
+
+ bpt = fdt.sectrac * (1<<fdt.secsize) * 128;
+ if(!trackbuf) {
+ trackbuf = malloc(bpt);
+ if(!trackbuf) errx(1, "malloc");
+ }
+ if(!vrfybuf) {
+ vrfybuf = malloc(bpt);
+ if(!vrfybuf) errx(1, "malloc");
+ }
+
+ if(fdn == 1) {
+ if(verbose) {
+ printf("Format: %d cylinders, %d heads, %d sectors, %d bytes = %dkb\n",
+ fdt.tracks,fdt.heads,fdt.sectrac,(1<<fdt.secsize) * 128,
+ fdt.tracks*bpt*fdt.heads/1024);
+
+ }
+ memset(trackbuf,0,bpt);
+ for(j=0;inputfd >= 0 && j<bpt;j+=i) {
+ if(!(i = read(inputfd,trackbuf+j,bpt-j))) {
+ close(inputfd);
+ inputfd = -1;
+ break;
+ }
+ nbytes += i;
+ }
+ }
+ for (track = 0; track < fdt.tracks * fdt.heads; track++) {
+ if(verbose) printf("\r%3d ",fdt.tracks * fdt.heads-track);
+ if(verbose) putc((j ? 'I':'Z'),stdout);
+ format_track(fd, track / fdt.heads, fdt.sectrac, track % fdt.heads,
+ fdt.trans, fdt.f_gap, fdt.secsize, 0xe6,
+ fdt.f_inter);
+ if(verbose) putc('F',stdout);
+
+ if (lseek (fd, (long) track*bpt, 0) < 0) err(1, "lseek");
+ if (write (fd, trackbuf, bpt) != bpt) err(1, "write");
+ if(verbose) putc('W',stdout);
+
+ if (lseek (fd, (long) track*bpt, 0) < 0) err(1, "lseek");
+ if (read (fd, vrfybuf, bpt) != bpt) err(1, "read");
+ if(verbose) putc('R',stdout);
+
+ if (memcmp(trackbuf,vrfybuf,bpt)) err(1, "compare");
+ if(verbose) putc('C',stdout);
+
+ memset(trackbuf,0,bpt);
+ for(j=0;inputfd >= 0 && j<bpt;j+=i) {
+ if(!(i = read(inputfd,trackbuf+j,bpt-j))) {
+ close(inputfd);
+ inputfd = -1;
+ break;
+ }
+ nbytes += i;
+ }
+ }
+ close(fd);
+ putc('\r',stdout);
+ }
+ if(verbose)
+ printf("%d bytes on %d flopp%s\n",nbytes,fdn,fdn==1?"y":"ies");
+ exit(0);
+}
diff --git a/usr.sbin/fifolog/Makefile b/usr.sbin/fifolog/Makefile
new file mode 100644
index 0000000..fbaaa89
--- /dev/null
+++ b/usr.sbin/fifolog/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+SUBDIR= lib .WAIT \
+ fifolog_create fifolog_writer fifolog_reader
+SUBDIR_PARALLEL=
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/fifolog/Makefile.inc b/usr.sbin/fifolog/Makefile.inc
new file mode 100644
index 0000000..265f86d
--- /dev/null
+++ b/usr.sbin/fifolog/Makefile.inc
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+.include "../Makefile.inc"
diff --git a/usr.sbin/fifolog/fifolog_create/Makefile b/usr.sbin/fifolog/fifolog_create/Makefile
new file mode 100644
index 0000000..4a83b5b
--- /dev/null
+++ b/usr.sbin/fifolog/fifolog_create/Makefile
@@ -0,0 +1,21 @@
+# $FreeBSD$
+
+PROG= fifolog_create
+
+CFLAGS+= -I${.CURDIR}/../lib
+
+LIBADD= util fifolog
+
+MAN= fifolog.1
+MLINKS= fifolog.1 fifolog_create.1 \
+ fifolog.1 fifolog_reader.1 \
+ fifolog.1 fifolog_writer.1
+
+regress:
+ rm -f /tmp/fifolog.?
+ ./${PROG} /tmp/fifolog.0
+ ./${PROG} -s 10m /tmp/fifolog.1
+ ./${PROG} -l 1k /tmp/fifolog.2
+ ./${PROG} -r 1k /tmp/fifolog.3
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/fifolog/fifolog_create/Makefile.depend b/usr.sbin/fifolog/fifolog_create/Makefile.depend
new file mode 100644
index 0000000..2a0e9d7
--- /dev/null
+++ b/usr.sbin/fifolog/fifolog_create/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+ lib/libz \
+ usr.sbin/fifolog/lib \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/fifolog/fifolog_create/fifolog.1 b/usr.sbin/fifolog/fifolog_create/fifolog.1
new file mode 100644
index 0000000..69e8fbe
--- /dev/null
+++ b/usr.sbin/fifolog/fifolog_create/fifolog.1
@@ -0,0 +1,218 @@
+.\" Copyright (c) 2008 Poul-Henning Kamp
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 9, 2008
+.Dt FIFOLOG 1
+.Os
+.Sh NAME
+.Nm fifolog_create , fifolog_writer , fifolog_reader
+.Nd "initialize, write, seek and extract data from a fifolog"
+.Sh SYNOPSIS
+.Nm fifolog_create
+.Op Fl l Ar record-size
+.Op Fl r Ar record-count
+.Op Fl s Ar size
+.Ar file
+.Nm fifolog_reader
+.Op Fl t
+.Op Fl b Ar tstart
+.Op Fl B Ar Tstart
+.Op Fl e Ar tend
+.Op Fl E Ar Tend
+.Op Fl o Ar ofile
+.Op Fl R Ar regexp
+.Op Fl T Ar timefmt
+.Ar file
+.Nm fifolog_writer
+.Op Fl w Ar write-rate
+.Op Fl s Ar sync-rate
+.Op Fl z Ar compression
+.Ar file
+.Sh DESCRIPTION
+Fifologs provide a compact round-robin circular storage for
+recording text and binary information to permanent storage in a bounded
+and predictable fashion, time and space wise.
+.Pp
+A fifolog can be stored either directly on a disk partition or in a
+regular file.
+.Pp
+The input data stream is encoded, compressed and marked up with
+timestamps before it is written to storage, such that it is possible
+to seek out a particular time interval in the stored data, without
+having to decompress the entire logfile.
+.Pp
+The
+.Nm fifolog_create
+utility
+is used to initialize the first sector of a disk device
+or file system file to make it a fifolog and should be called only
+once.
+.Pp
+Running
+.Nm fifolog_create
+on an existing fifolog will reset it so that
+.Nm fifolog_reader
+and
+.Nm fifolog_writer
+will not see the previous contents.
+(The previous contents are not physically erased, and with a bit
+of hand-work all but the first record can be easily recovered.)
+.Pp
+If the
+.Ar file
+does not already exist,
+.Nm fifolog_create
+will attempt to create and
+.Xr ftruncate 2
+it to the specified size, defaulting to 86400 records of 512 bytes
+if the
+.Fl r , l
+or
+.Fl s
+options do not specify otherwise.
+.Pp
+The
+.Nm fifolog_writer
+utility
+will read standard input and write it to the end of the fifolog
+according to the parameters given.
+.Pp
+Writes happen whenever the output buffer is filled with compressed
+data or when either of two timers expire, forcing a partially filled
+buffer to be written.
+.Pp
+The first and faster timer,
+.Fl w Ar write-rate ,
+forces available data to be written
+but does not flush and reset the compression dictionary.
+This timer is intended to minimize the amount of logdata lost in RAM
+in case of a crash and by default it fires 10 seconds after
+the previous write.
+.Pp
+The second and slower timer,
+.Fl s Ar sync-rate ,
+forces a full flush and reset of the compression
+engine and causes the next record written to be a synchronization
+point with an uncompressed timestamp, making it possible to start
+reading the logfile from that record.
+By default this timer fires a minute after the previous sync.
+.Pp
+The
+.Fl z Ar compression
+option controls the
+.Xr zlib 3
+compression level; legal values are zero to nine which is the default.
+.Pp
+The
+.Nm fifolog_reader
+utility
+will retrieve records from the fifolog according to the specified
+parameters and write them either to standard output or the file specified
+with
+.Fl o .
+.Pp
+It is possible to specify a start and end time to limit the amount
+of data
+.Nm fifolog_reader
+will report.
+The lower-case variants
+.Fl b
+and
+.Fl e
+take a
+.Vt time_t
+value, whereas the upper-case variants
+.Fl B
+and
+.Fl E
+take human-readable specifications such as
+.Dq Li "1 hour ago" .
+.Pp
+The
+.Fl t
+option forces timestamps to be formatted as
+.Dq Li "YYYYMMDDhhmmss"
+instead of as
+.Vt time_t ,
+and
+.Fl T
+allows the specification of an
+.Xr strftime 3
+formatting string.
+.Pp
+Finally, records can be filtered such that only records matching the
+.Pq Dv REG_BASIC
+regular expression specified with
+.Fl R
+are output.
+.Sh IMPLEMENTATION NOTES
+The data stored in the fifolog consists of three layers, an outer
+layer that allows searches to synchronization points based on timestamps
+without having to decompress and decode the actual contents, a
+compression layer implemented with
+.Xr zlib 3 ,
+and an inner serialization and timestamping layer.
+.Pp
+The exact encoding is described in the
+.Pa fifolog.h
+file.
+.Pp
+Fifolog is particularly well suited for use on Flash based media, where
+it results in much lower write-wear, than a file system with regular
+log files rotated with
+.Xr newsyslog 8
+etc.
+.Sh EXAMPLES
+Create a fifolog with 1024*1024 records of 512 bytes:
+.Pp
+.Dl "fifolog_create -r 10m /tmp/fifolog"
+.Pp
+Write a single record to this file:
+.Pp
+.Dl "date | fifolog_writer /tmp/fifolog"
+.Pp
+Read it back with human readable timestamps:
+.Pp
+.Dl "fifolog_reader -t /tmp/fifolog"
+.Pp
+One particular useful use of
+.Nm fifolog_writer
+is with
+.Xr syslogd 8
+using a line such as this in
+.Xr syslog.conf 5 :
+.Pp
+.Dl "*.* |fifolog_writer /var/log/syslog_fifolog"
+.Sh HISTORY
+The fifolog tools have been liberated from an open source
+.Tn SCADA
+applications called
+.Dq measured ,
+which monitors and controls remote radio navigation
+transmitters for the Danish Air Traffic Control system.
+.Sh AUTHORS
+The fifolog tools were written by
+.An Poul-Henning Kamp .
diff --git a/usr.sbin/fifolog/fifolog_create/fifolog_create.c b/usr.sbin/fifolog/fifolog_create/fifolog_create.c
new file mode 100644
index 0000000..470c64c
--- /dev/null
+++ b/usr.sbin/fifolog/fifolog_create/fifolog_create.c
@@ -0,0 +1,115 @@
+/*-
+ * Copyright (c) 2005-2008 Poul-Henning Kamp
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <stdio.h>
+#include <stdlib.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <err.h>
+#include <libutil.h>
+
+#include "libfifolog.h"
+
+#define DEF_RECSIZE 512
+#define DEF_RECCNT (24 * 60 * 60)
+
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: fifolog_create [-l record-size] "
+ "[-r record-count] [-s size] file\n");
+ exit(EX_USAGE);
+}
+
+int
+main(int argc, char * const *argv)
+{
+ int ch;
+ int64_t size;
+ int64_t recsize;
+ int64_t reccnt;
+ const char *s;
+
+ recsize = 0;
+ size = 0;
+ reccnt = 0;
+ while((ch = getopt(argc, argv, "l:r:s:")) != -1) {
+ switch (ch) {
+ case 'l':
+ if (expand_number(optarg, &recsize))
+ err(1, "Couldn't parse -l argument");
+ break;
+ case 'r':
+ if (expand_number(optarg, &reccnt))
+ err(1, "Couldn't parse -r argument");
+ break;
+ case 's':
+ if (expand_number(optarg, &size))
+ err(1, "Couldn't parse -s argument");
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 1)
+ usage();
+
+ if (size != 0 && reccnt != 0 && recsize != 0) { /* N N N */
+ if (size != reccnt * recsize)
+ errx(1, "Inconsistent -l, -r and -s values");
+ } else if (size != 0 && reccnt != 0 && recsize == 0) { /* N N Z */
+ if (size % reccnt)
+ errx(1,
+ "Inconsistent -r and -s values (gives remainder)");
+ recsize = size / reccnt;
+ } else if (size != 0 && reccnt == 0 && recsize != 0) { /* N Z N */
+ if (size % recsize)
+ errx(1, "-s arg not divisible by -l arg");
+ } else if (size != 0 && reccnt == 0 && recsize == 0) { /* N Z Z */
+ recsize = DEF_RECSIZE;
+ if (size % recsize)
+ errx(1, "-s arg not divisible by %jd", recsize);
+ } else if (size == 0 && reccnt != 0 && recsize != 0) { /* Z N N */
+ size = reccnt * recsize;
+ } else if (size == 0 && reccnt != 0 && recsize == 0) { /* Z N Z */
+ recsize = DEF_RECSIZE;
+ size = reccnt * recsize;
+ } else if (size == 0 && reccnt == 0 && recsize != 0) { /* Z Z N */
+ size = DEF_RECCNT * recsize;
+ } else if (size == 0 && reccnt == 0 && recsize == 0) { /* Z Z Z */
+ recsize = DEF_RECSIZE;
+ size = DEF_RECCNT * recsize;
+ }
+
+ s = fifolog_create(argv[0], size, recsize);
+ if (s == NULL)
+ return (0);
+ err(1, "%s", s);
+}
diff --git a/usr.sbin/fifolog/fifolog_reader/Makefile b/usr.sbin/fifolog/fifolog_reader/Makefile
new file mode 100644
index 0000000..ae5f9e7
--- /dev/null
+++ b/usr.sbin/fifolog/fifolog_reader/Makefile
@@ -0,0 +1,19 @@
+# $FreeBSD$
+
+PROG= fifolog_reader
+
+CFLAGS+= -I${.CURDIR}/../lib
+
+MAN=
+
+LIBADD= fifolog
+
+regress:
+ ./${PROG} /tmp/fifolog.0
+ ./${PROG} -t /tmp/fifolog.0
+ ./${PROG} /tmp/fifolog.1
+ ./${PROG} -B "00:00" /tmp/fifolog.1
+ ./${PROG} -T "%y%m%d-%H%M%S" /tmp/fifolog.1
+ ./${PROG} -T "" /tmp/fifolog.1
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/fifolog/fifolog_reader/Makefile.depend b/usr.sbin/fifolog/fifolog_reader/Makefile.depend
new file mode 100644
index 0000000..50fc7bd
--- /dev/null
+++ b/usr.sbin/fifolog/fifolog_reader/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libz \
+ usr.sbin/fifolog/lib \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/fifolog/fifolog_reader/fifolog_reader.c b/usr.sbin/fifolog/fifolog_reader/fifolog_reader.c
new file mode 100644
index 0000000..73fc50d
--- /dev/null
+++ b/usr.sbin/fifolog/fifolog_reader/fifolog_reader.c
@@ -0,0 +1,171 @@
+/*-
+ * Copyright (c) 2005-2008 Poul-Henning Kamp
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <stdio.h>
+#include <assert.h>
+#include <unistd.h>
+#include <err.h>
+#include <time.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sysexits.h>
+#include <regex.h>
+
+#include "libfifolog.h"
+
+static time_t opt_B;
+static time_t opt_E;
+static const char *opt_T;
+static const char *opt_o;
+static const char *opt_R;
+static regex_t R;
+
+static FILE *fo;
+
+static void
+Render(void *priv __unused, time_t now, unsigned flag __unused, const unsigned char *p, unsigned l __unused)
+{
+ static struct tm utc;
+ char buf[128];
+ int i;
+
+ if (now < opt_B || now > opt_E)
+ return;
+
+ if (opt_R != NULL && regexec(&R, (const char *)p, 0, NULL, 0))
+ return;
+
+ if (opt_T != NULL && *opt_T == '\0') {
+ fprintf(fo, "%s\n", p);
+ } else if (opt_T != NULL) {
+ (void)gmtime_r(&now, &utc);
+ i = strftime(buf, sizeof buf, opt_T, &utc);
+ assert(i > 0);
+ fprintf(fo, "%s %s\n", buf, p);
+ } else {
+ fprintf(fo, "%12ld %s\n", (long)now, p);
+ }
+}
+
+/*--------------------------------------------------------------------*/
+
+static void
+Usage(void)
+{
+ fprintf(stderr,
+ "Usage: fiforead [options] fifofile\n"
+ "\t-b <start time integer>\n"
+ "\t-B <start time>\n"
+ "\t-e <end time integer>\n"
+ "\t-E <end time>\n"
+ "\t-o <output file>\n"
+ "\t-R <regexp> # match regexp\n"
+ "\t-t # format timestamps as %%Y%%m%%d%%H%%M%%S\n"
+ "\t-T <timestamp format>\n"
+ );
+ exit (EX_USAGE);
+}
+
+int
+main(int argc, char * const *argv)
+{
+ int ch, i;
+ off_t o;
+ struct fifolog_reader *fl;
+
+ time(&opt_E);
+ opt_o = "-";
+ while ((ch = getopt(argc, argv, "b:B:e:E:o:R:tT:")) != -1) {
+ switch (ch) {
+ case 'b':
+ opt_B = strtoul(optarg, NULL, 0);
+ break;
+ case 'B':
+ opt_B = get_date(optarg);
+ if (opt_B == -1)
+ errx(1, "Didn't understand \"%s\"", optarg);
+ break;
+ case 'e':
+ opt_E = strtoul(optarg, NULL, 0);
+ break;
+ case 'E':
+ opt_E = get_date(optarg);
+ if (opt_E == -1)
+ errx(1, "Didn't understand \"%s\"", optarg);
+ break;
+ case 'o':
+ opt_o = optarg;
+ break;
+ case 'R':
+ opt_R = optarg;
+ break;
+ case 't':
+ opt_T = "%Y%m%d%H%M%S";
+ break;
+ case 'T':
+ opt_T = optarg;
+ break;
+ default:
+ Usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (opt_R != NULL) {
+ i = regcomp(&R, opt_R, REG_NOSUB);
+ if (i != 0) {
+ char buf[BUFSIZ];
+ (void)regerror(i, &R, buf, sizeof buf);
+ fprintf(stderr, "-R argument: %s\n", buf);
+ exit (1);
+ }
+ }
+
+ if (argv[0] == NULL)
+ Usage();
+
+ fprintf(stderr, "From\t%jd %s", (intmax_t)opt_B, ctime(&opt_B));
+ fprintf(stderr, "To\t%jd %s", (intmax_t)opt_E, ctime(&opt_E));
+ if (opt_B >= opt_E)
+ errx(1, "Begin time not before End time");
+
+ fl = fifolog_reader_open(argv[0]);
+
+ if (!strcmp(opt_o, "-"))
+ fo = stdout;
+ else {
+ fo = fopen(opt_o, "w");
+ if (fo == NULL)
+ err(1, "Cannot open: %s", argv[1]);
+ }
+
+ o = fifolog_reader_seek(fl, opt_B);
+ fifolog_reader_process(fl, o, Render, NULL, opt_E);
+ return (0);
+}
diff --git a/usr.sbin/fifolog/fifolog_writer/Makefile b/usr.sbin/fifolog/fifolog_writer/Makefile
new file mode 100644
index 0000000..7a9316d
--- /dev/null
+++ b/usr.sbin/fifolog/fifolog_writer/Makefile
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+PROG= fifolog_writer
+
+CFLAGS+= -I${.CURDIR}/../lib
+
+MAN=
+
+LIBADD= fifolog
+
+regress:
+ date | ./${PROG} -z 0 /tmp/fifolog.0
+ lptest 65 | ./${PROG} -z 9 /tmp/fifolog.1
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/fifolog/fifolog_writer/Makefile.depend b/usr.sbin/fifolog/fifolog_writer/Makefile.depend
new file mode 100644
index 0000000..50fc7bd
--- /dev/null
+++ b/usr.sbin/fifolog/fifolog_writer/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libz \
+ usr.sbin/fifolog/lib \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/fifolog/fifolog_writer/fifolog_writer.c b/usr.sbin/fifolog/fifolog_writer/fifolog_writer.c
new file mode 100644
index 0000000..77776b8
--- /dev/null
+++ b/usr.sbin/fifolog/fifolog_writer/fifolog_writer.c
@@ -0,0 +1,115 @@
+/*-
+ * Copyright (c) 2005-2008 Poul-Henning Kamp
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <stdio.h>
+#include <stdlib.h>
+#include <sysexits.h>
+#include <err.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <assert.h>
+#include <poll.h>
+#include <string.h>
+#include <zlib.h>
+
+#include "libfifolog.h"
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "Usage: fifolog_writer [-w write-rate] [-s sync-rate] "
+ "[-z compression] file\n");
+ exit(EX_USAGE);
+}
+
+int
+main(int argc, char * const *argv)
+{
+ struct fifolog_writer *f;
+ const char *es;
+ struct pollfd pfd[1];
+ char buf[BUFSIZ], *p;
+ int i, c;
+ unsigned w_opt = 10;
+ unsigned s_opt = 60;
+ unsigned z_opt = Z_BEST_COMPRESSION;
+
+ while ((c = getopt(argc, argv, "w:s:z:")) != -1) {
+ switch(c) {
+ case 'w':
+ w_opt = strtoul(optarg, NULL, 0);
+ break;
+ case 's':
+ s_opt = strtoul(optarg, NULL, 0);
+ break;
+ case 'z':
+ z_opt = strtoul(optarg, NULL, 0);
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 1)
+ usage();
+
+ if (z_opt > 9)
+ usage();
+
+ if (w_opt > s_opt)
+ usage();
+
+ f = fifolog_write_new();
+ assert(f != NULL);
+
+ es = fifolog_write_open(f, argv[0], w_opt, s_opt, z_opt);
+ if (es)
+ err(1, "Error: %s", es);
+
+ while (1) {
+ pfd[0].fd = 0;
+ pfd[0].events = POLLIN;
+ i = poll(pfd, 1, 1000);
+ if (i == 1) {
+ if (fgets(buf, sizeof buf, stdin) == NULL)
+ break;
+ p = strchr(buf, '\0');
+ assert(p != NULL);
+ while (p > buf && isspace(p[-1]))
+ p--;
+ *p = '\0';
+ if (*buf != '\0')
+ fifolog_write_record_poll(f, 0, 0, buf, 0);
+ } else if (i == 0)
+ fifolog_write_poll(f, 0);
+ }
+ fifolog_write_close(f);
+ return (0);
+}
diff --git a/usr.sbin/fifolog/flint.lnt b/usr.sbin/fifolog/flint.lnt
new file mode 100644
index 0000000..87e0c86
--- /dev/null
+++ b/usr.sbin/fifolog/flint.lnt
@@ -0,0 +1,55 @@
+// $FreeBSD$
+// FlexeLint file for fifolog tools
+//
+//
+//-passes=3
+//-ffc
+//
+//// GCC
+//-cgnu
+//+d__FreeBSD__=7
+//+d__GNUC__=4
+//+d__GNUC_MINOR__=2
+//+d__FreeBSD_cc_version=700003
+//+d__attribute__()=
+//-d__builtin_va_list=void* // used by stdarg.h
+//// -d__builtin_stdarg_start()=_to_semi // ditto
+//// -d__builtin_va_start(a,b)=((void)(b),(a)=0) // ditto
+//// -d__builtin_va_end()=_to_semi // ditto
+//+rw(__inline) // enable the (non-standard) __inline keyword
+//+rw(__inline__) // enable the (non-standard) __inline__ keyword
+//
+//+d"__unused=/*lint -e{715} -e{818} */"
+//
+//-e537 // Repeated include file
+//-elib(652) // #define of symbol '...' declared previously
+//-function(exit,__assert)
+//-function(exit,err)
+//-function(exit,errx)
+//-e716 // while(1) ...
+//-e717 // do ... while(0)
+//
+//// Ignore return values
+-esym(534, memset)
+-esym(534, memcpy)
+-esym(534, strcpy)
+//-esym(534, printf)
+-esym(534, time)
+-esym(534, fprintf)
+//-esym(534, vfprintf)
+//
+//+libh(fifolog.h)
+//+libh(miniobj.h)
+//+libh(libfifolog.h)
+//
+//-e713 // loss of precision sign/unsigned
+//-e732 // loss of sign
+//-e734 // loss of precision assignment
+//-e737 // loss of sign in promotion int->unsigned
+//-e573 // sign/unsign mix in divide
+
+-e712 // Loss of precision (___) (___ to ___)
+-e713 // Loss of precision (___) (___ to ___)
+-e716 // while(1) ...
+-e732 // Loss of sign (___) (___ to ___)
+-e747 // Significant prototype coercion (___) ___ to ___
diff --git a/usr.sbin/fifolog/lib/Makefile b/usr.sbin/fifolog/lib/Makefile
new file mode 100644
index 0000000..f60529f
--- /dev/null
+++ b/usr.sbin/fifolog/lib/Makefile
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+LIB= fifolog
+INTERNALLIB= # API not published or supported.
+
+SRCS= fifolog_int.c fifolog_create.c fifolog_write_poll.c fifolog_reader.c
+SRCS+= getdate.y
+
+CFLAGS+= -I${.CURDIR}
+LIBADD= z
+
+NO_WMISSING_VARIABLE_DECLARATIONS=
+
+.include <bsd.lib.mk>
diff --git a/usr.sbin/fifolog/lib/Makefile.depend b/usr.sbin/fifolog/lib/Makefile.depend
new file mode 100644
index 0000000..f91d860
--- /dev/null
+++ b/usr.sbin/fifolog/lib/Makefile.depend
@@ -0,0 +1,17 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/libz \
+ usr.bin/yacc.host \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+getdate.o: getdate.c
+getdate.po: getdate.c
+.endif
diff --git a/usr.sbin/fifolog/lib/fifolog.h b/usr.sbin/fifolog/lib/fifolog.h
new file mode 100644
index 0000000..0575a46
--- /dev/null
+++ b/usr.sbin/fifolog/lib/fifolog.h
@@ -0,0 +1,138 @@
+/*-
+ * Copyright (c) 2005-2008 Poul-Henning Kamp
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 __LOCAL_FIFOLOG_H_
+#define __LOCAL_FIFOLOG_H_
+
+/*
+ * Definitions for fifolog "protocol": the on-media layout.
+ *
+ * The fifolog on-media record has three layers:
+ * The outer timestamping and synchronization layer.
+ * The zlib implemented data compression.
+ * The inner sequencing and identification layer.
+ *
+ * All three layers are synchronized at a subset of the outer layer
+ * record boundaries, from where reading can be initiated.
+ *
+ *
+ * The outer layer:
+ * -----------------
+ * The first record in a fifolog contains a magic string and version
+ * information along with a 32be encoded recordsize for all records
+ * in the fifolog, including the first.
+ * The recordsize is explicit to avoid ambiguities when a media is
+ * moved from one machine to another.
+ *
+ * Each record in the fifolog has the following contents:
+ * offset type contents
+ * --------------------------------------------------------------
+ * 0 32be sequence_number
+ * The sequence number is randomly chosen for the
+ * fifolog and increments once for each record written.
+ * It's presence allow quick identification of the next
+ * record to be written using a binary search for the
+ * first place where a discontinuity in the sequence
+ * numbers occur.
+ * 4 8 flags (FIFOLOG_FLG_*)
+ *
+ * If (flags & (FIFOLOG_FLG_SYNC)) the record is a synchronization point
+ * at which the inner layers are aligned so that reading can be started
+ * at this point.
+ * To enable seeks into the file based on timestamps, a third field is
+ * present in these records as well:
+ * 5 32be time_t containing POSIX's understanding of UTC.
+ *
+ * These fields are immediately followed by the inner layer payload as
+ * described below, which has variable length.
+ *
+ * If the inner layer payload is shorter than the available space in
+ * the record, it is padded with zero bytes, and the number of unused
+ * bytes, including the encoded length thereof is recorded at the end
+ * of the record as follows:
+ *
+ * If (flags & FIFOLOG_FLG_1BYTE)
+ * n-1 8 number of unused bytes
+ * else if (flags & FIFOLOG_FLG_4BYTE)
+ * n-4 32be number of unused bytes
+ *
+ *
+ * The gzip layer
+ * --------------
+ * Is just output from zlib, nothing special here. FIFOLOG_FLG_SYNC
+ * corresponds to Z_FINISH flags to zlib.
+ * In most cases, the timer will expire before zlib has filled an entire
+ * record in which case Z_SYNC_FLUSH will be used to force as much as
+ * possible into the buffer before it is written. This is not marked
+ * in outer layer (apart from a natural correlation with padding) since
+ * zlibs data stream handles this without help.
+ *
+ *
+ * The inner layer:
+ * ----------------
+ * The inner layer contains data identification and to the second
+ * timestamping (the timestamp in the outer layer only marks the
+ * first possible timestamp for content in the SYNC record).
+ *
+ * offset type contents
+ * --------------------------------------------------------------
+ * 0 32be ident
+ *
+ * The bottom 30 bits of the identification word are application defined,
+ * presently unused in the stand-alone fifolog tools, but used in the
+ * original "measured" application that originated the fifolog format.
+ * Should for instance syslogd(8) grow native support for fifolog format,
+ * it could store the message priority here.
+ *
+ * If (ident & FIFOLOG_TIMESTAMP) the record is prefixed by:
+ * 4 32be time_t containing POSIX's understanding of UTC.
+ *
+ * Then follows the content, either as a NUL terminated string or as
+ * a length encoded binary sequence:
+ *
+ * If (ident & FIFOLOG_LENGTH) the record is prefixed by:
+ * {0|4} 8 length of binary data
+ *
+ */
+
+/* Magic identification string */
+#define FIFOLOG_FMT_MAGIC "Measured FIFOLOG Ver 1.01\n"
+
+/* Offset of the 32be encoded recordsize in the first sector */
+#define FIFOLOG_OFF_BS 0x20
+
+#define FIFOLOG_FLG_1BYTE 0x01
+#define FIFOLOG_FLG_4BYTE 0x02
+#define FIFOLOG_FLG_RESTART 0x40
+#define FIFOLOG_FLG_SYNC 0x80
+
+#define FIFOLOG_TIMESTAMP 0x80000000
+#define FIFOLOG_LENGTH 0x40000000
+#define FIFOLOG_IDENT 0x3fffffff
+
+#endif /* __LOCAL_FIFOLOG_H_ */
diff --git a/usr.sbin/fifolog/lib/fifolog_create.c b/usr.sbin/fifolog/lib/fifolog_create.c
new file mode 100644
index 0000000..4a7c333
--- /dev/null
+++ b/usr.sbin/fifolog/lib/fifolog_create.c
@@ -0,0 +1,122 @@
+/*-
+ * Copyright (c) 2005-2008 Poul-Henning Kamp
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/endian.h>
+#include <sys/stat.h>
+#include <sys/disk.h>
+
+#include "fifolog.h"
+#include "libfifolog.h"
+
+const char *
+fifolog_create(const char *fn, off_t size, ssize_t recsize)
+{
+ int i, fd;
+ ssize_t u;
+ off_t ms;
+ struct stat st;
+ char *buf;
+ int created;
+
+ fd = open(fn, O_WRONLY | O_TRUNC | O_EXCL | O_CREAT, 0644);
+ if (fd < 0) {
+ created = 0;
+ fd = open(fn, O_WRONLY);
+ if (fd < 0)
+ return ("Could not open");
+ } else
+ created = 1;
+
+ /* Default sectorsize is 512 */
+ if (recsize == 0)
+ recsize = 512;
+
+ /* See what we got... */
+ i = fstat(fd, &st);
+ assert(i == 0);
+ if (!S_ISBLK(st.st_mode) &&
+ !S_ISCHR(st.st_mode) &&
+ !S_ISREG(st.st_mode)) {
+ assert(!close (fd));
+ return ("Wrong file type");
+ }
+
+ if(!created && S_ISREG(st.st_mode)) {
+ assert(!close (fd));
+ return ("Wrong file type");
+ }
+
+ /* For raw disk with larger sectors: use 1 sector */
+ i = ioctl(fd, DIOCGSECTORSIZE, &u);
+ if (i == 0 && (u > recsize || (recsize % u) != 0))
+ recsize = u;
+
+ /* If no configured size, or too large for disk, use device size */
+ i = ioctl(fd, DIOCGMEDIASIZE, &ms);
+ if (i == 0 && (size == 0 || size > ms))
+ size = ms;
+
+ if (size == 0 && S_ISREG(st.st_mode))
+ size = st.st_size;
+
+ if (size == 0)
+ size = recsize * (off_t)(24*60*60);
+
+ if (S_ISREG(st.st_mode) && ftruncate(fd, size) < 0)
+ return ("Could not ftrunc");
+
+ buf = calloc(recsize, 1);
+ if (buf == NULL)
+ return ("Could not malloc");
+
+ strcpy(buf, FIFOLOG_FMT_MAGIC); /*lint !e64 */
+ be32enc(buf + FIFOLOG_OFF_BS, recsize);
+ if (recsize != pwrite(fd, buf, recsize, 0)) {
+ i = errno;
+ free(buf);
+ errno = i;
+ return ("Could not write first sector");
+ }
+ memset(buf, 0, recsize);
+ if ((int)recsize != pwrite(fd, buf, recsize, recsize)) {
+ i = errno;
+ free(buf);
+ errno = i;
+ return ("Could not write second sector");
+ }
+ free(buf);
+ assert(0 == close(fd));
+ return (NULL);
+}
diff --git a/usr.sbin/fifolog/lib/fifolog_int.c b/usr.sbin/fifolog/lib/fifolog_int.c
new file mode 100644
index 0000000..9d3b437
--- /dev/null
+++ b/usr.sbin/fifolog/lib/fifolog_int.c
@@ -0,0 +1,256 @@
+/*-
+ * Copyright (c) 2005-2008 Poul-Henning Kamp
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <zlib.h>
+
+#include <sys/disk.h>
+#include <sys/endian.h>
+#include <sys/stat.h>
+
+#include "miniobj.h"
+#include "fifolog.h"
+#include "libfifolog_int.h"
+
+/*
+ * Open a fifolog file or partition for reading or writing.
+ *
+ * Return value is NULL for success or a error description string to
+ * be augmented by errno if non-zero.
+ *
+ * The second function is just an error-handling wrapper around the
+ * first which, does the actual work.
+ */
+
+static const char *
+fifolog_int_open_i(struct fifolog_file *f, const char *fname, int mode)
+{
+ struct stat st;
+ ssize_t u;
+ int i;
+
+ f->fd = open(fname, mode ? O_RDWR : O_RDONLY);
+ if (f->fd < 0)
+ return ("Cannot open");
+
+ /* Determine initial record size guesstimate */
+ i = ioctl(f->fd, DIOCGSECTORSIZE, &f->recsize);
+ if (i != 0 && errno != ENOTTY)
+ return ("ioctl(DIOCGSECTORSIZE) failed");
+
+ if (i != 0) {
+ i = fstat(f->fd, &st);
+ assert(i == 0);
+ if (!S_ISREG(st.st_mode))
+ return ("Neither disk nor regular file");
+ f->recsize = 512;
+ f->logsize = st.st_size;
+ } else if (f->recsize < 64) {
+ return ("Disk device sectorsize smaller than 64");
+ } else {
+ i = ioctl(f->fd, DIOCGMEDIASIZE, &f->logsize);
+ if (i < 0 && errno != ENOTTY)
+ return ("ioctl(DIOCGMEDIASIZE) failed");
+ }
+
+ /* Allocate a record buffer */
+ f->recbuf = malloc(f->recsize);
+ if (f->recbuf == NULL)
+ return ("Cannot malloc");
+
+ /* Read and validate the label sector */
+ i = pread(f->fd, f->recbuf, f->recsize, 0);
+ if (i < 0 || i < (int)f->recsize)
+ return ("Read error, first sector");
+
+ errno = 0;
+ if (memcmp(f->recbuf, FIFOLOG_FMT_MAGIC, strlen(FIFOLOG_FMT_MAGIC) + 1))
+ return ("Wrong or missing magic string");
+
+ u = be32dec(f->recbuf + FIFOLOG_OFF_BS);
+ if (u < 64)
+ return ("Wrong record size in header (<64)");
+
+ if ((off_t)u >= f->logsize)
+ return ("Record size in header bigger than fifolog");
+
+ f->recsize = u;
+
+ /* Reallocate the buffer to correct size if necessary */
+ if (u != f->recsize) {
+ free(f->recbuf);
+ f->recbuf = NULL;
+ f->recsize = u;
+ f->recbuf = malloc(f->recsize);
+ if (f->recbuf == NULL)
+ return ("Cannot malloc");
+ }
+
+ /* Calculate number of records in fifolog */
+ f->logsize /= u;
+ if (f->logsize < 10)
+ return ("less than 10 records in fifolog");
+
+ f->logsize--; /* the label record */
+
+ /* Initialize zlib handling */
+
+ f->zs = calloc(sizeof *f->zs, 1);
+ if (f->zs == NULL)
+ return ("cannot malloc");
+
+ return (NULL);
+}
+
+const char *
+fifolog_int_open(struct fifolog_file **ff, const char *fname, int mode)
+{
+ struct fifolog_file fs, *f;
+ const char *retval;
+ int e;
+
+ f = &fs;
+ memset(f, 0, sizeof *f);
+ f->fd = -1;
+ retval = fifolog_int_open_i(f, fname, mode);
+ e = errno;
+ if (retval == NULL) {
+ *ff = malloc(sizeof *f);
+ if (*ff != NULL) {
+ memcpy(*ff, f, sizeof *f);
+ (*ff)->magic = FIFOLOG_FILE_MAGIC;
+ return (retval);
+ }
+ }
+ fifolog_int_close(&f);
+ errno = e;
+ return (retval);
+}
+
+void
+fifolog_int_close(struct fifolog_file **ff)
+{
+ struct fifolog_file *f;
+
+ f = *ff;
+ *ff = NULL;
+ if (f == NULL)
+ return;
+
+ if (f->fd >= 0)
+ (void)close(f->fd);
+ if (f->zs != NULL)
+ free(f->zs);
+ if (f->recbuf != NULL)
+ free(f->recbuf);
+}
+
+static void
+fifolog_int_file_assert(const struct fifolog_file *ff)
+{
+
+ CHECK_OBJ_NOTNULL(ff, FIFOLOG_FILE_MAGIC);
+ assert(ff->fd >= 0);
+ assert(ff->recbuf != NULL);
+}
+
+
+/*
+ * Read a record.
+ *
+ * Return zero on success
+ */
+
+int
+fifolog_int_read(const struct fifolog_file *ff, off_t recno)
+{
+ int i;
+
+ fifolog_int_file_assert(ff);
+ if (recno >= ff->logsize)
+ return (-1);
+ recno++; /* label sector */
+ i = pread(ff->fd, ff->recbuf, ff->recsize, recno * ff->recsize);
+ if (i < 0)
+ return (-2);
+ if (i != (int)ff->recsize)
+ return (-3);
+ return (0);
+}
+
+/*
+ * Find the last written record in the fifolog.
+ *
+ * Return is error string or NULL on success
+ */
+
+const char *
+fifolog_int_findend(const struct fifolog_file *ff, off_t *last)
+{
+ off_t o, s;
+ int e;
+ unsigned seq0, seq;
+
+ fifolog_int_file_assert(ff);
+
+ o = 0;
+ e = fifolog_int_read(ff, o);
+ if (e)
+ return("Read error, first record");
+
+ seq0 = be32dec(ff->recbuf);
+
+ /* If the first records sequence is zero, the fifolog is empty */
+ if (seq0 == 0) {
+ *last = o;
+ return (NULL);
+ }
+
+ /* Do a binary search for a discontinuity in the sequence numbers */
+ s = ff->logsize / 2;
+ do {
+ e = fifolog_int_read(ff, o + s);
+ if (e)
+ return ("Read error while searching");
+ seq = be32dec(ff->recbuf);
+ if (seq == seq0 + s) {
+ o += s;
+ seq0 = seq;
+ }
+ s /= 2;
+ assert(o < ff->logsize);
+ } while (s > 0);
+
+ *last = o;
+ return (NULL);
+}
diff --git a/usr.sbin/fifolog/lib/fifolog_reader.c b/usr.sbin/fifolog/lib/fifolog_reader.c
new file mode 100644
index 0000000..5843a81
--- /dev/null
+++ b/usr.sbin/fifolog/lib/fifolog_reader.c
@@ -0,0 +1,322 @@
+/*-
+ * Copyright (c) 2005-2008 Poul-Henning Kamp
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <stdio.h>
+#include <unistd.h>
+#include <assert.h>
+#include <err.h>
+#include <time.h>
+#include <string.h>
+#include <stdlib.h>
+#include <zlib.h>
+#include <sys/endian.h>
+
+#include "fifolog.h"
+#include "libfifolog.h"
+#include "libfifolog_int.h"
+#include "miniobj.h"
+
+/*--------------------------------------------------------------------*/
+
+struct fifolog_reader {
+ unsigned magic;
+#define FIFOLOG_READER_MAGIC 0x1036d139
+ struct fifolog_file *ff;
+ unsigned olen;
+ unsigned char *obuf;
+ time_t now;
+};
+
+struct fifolog_reader *
+fifolog_reader_open(const char *fname)
+{
+ const char *retval;
+ struct fifolog_reader *fr;
+ int i;
+
+ fr = calloc(sizeof *fr, 1);
+ if (fr == NULL)
+ err(1, "Cannot malloc");
+
+ retval = fifolog_int_open(&fr->ff, fname, 0);
+ if (retval != NULL)
+ err(1, "%s", retval);
+
+ fr->olen = fr->ff->recsize * 16;
+ fr->obuf = calloc(fr->olen, 1);
+ if (fr->obuf == NULL)
+ err(1, "Cannot malloc");
+
+ i = inflateInit(fr->ff->zs);
+ assert(i == Z_OK);
+
+ fr->magic = FIFOLOG_READER_MAGIC;
+ return (fr);
+}
+
+/*
+ * Find the next SYNC block
+ *
+ * Return:
+ * 0 - empty fifolog
+ * 1 - found sync block
+ * 2 - would have wrapped around
+ * 3 - End of written log.
+ */
+
+static int
+fifolog_reader_findsync(const struct fifolog_file *ff, off_t *o)
+{
+ int e;
+ unsigned seq, seqs;
+
+ assert(*o < ff->logsize);
+ e = fifolog_int_read(ff, *o);
+ if (e)
+ err(1, "Read error (%d) while looking for SYNC", e);
+ seq = be32dec(ff->recbuf);
+ if (*o == 0 && seq == 0)
+ return (0);
+
+ if (ff->recbuf[4] & FIFOLOG_FLG_SYNC)
+ return (1); /* That was easy... */
+ while(1) {
+ assert(*o < ff->logsize);
+ (*o)++;
+ seq++;
+ if (*o == ff->logsize)
+ return (2); /* wraparound */
+ e = fifolog_int_read(ff, *o);
+ if (e)
+ err(1, "Read error (%d) while looking for SYNC", e);
+ seqs = be32dec(ff->recbuf);
+ if (seqs != seq)
+ return (3); /* End of log */
+ if (ff->recbuf[4] & FIFOLOG_FLG_SYNC)
+ return (1); /* Bingo! */
+ }
+}
+
+/*
+ * Seek out a given timestamp
+ */
+
+off_t
+fifolog_reader_seek(const struct fifolog_reader *fr, time_t t0)
+{
+ off_t o, s, st;
+ time_t t, tt;
+ unsigned seq, seqs;
+ const char *retval;
+ int e;
+
+ CHECK_OBJ_NOTNULL(fr, FIFOLOG_READER_MAGIC);
+
+ /*
+ * First, find the first SYNC block
+ */
+ o = 0;
+ e = fifolog_reader_findsync(fr->ff, &o);
+ if (e == 0)
+ return (0); /* empty fifolog */
+ assert(e == 1);
+
+ assert(fr->ff->recbuf[4] & FIFOLOG_FLG_SYNC);
+ seq = be32dec(fr->ff->recbuf);
+ t = be32dec(fr->ff->recbuf + 5);
+
+ if (t > t0) {
+ /* Check if there is a second older part we can use */
+ retval = fifolog_int_findend(fr->ff, &s);
+ if (retval != NULL)
+ err(1, "%s", retval);
+ s++;
+ e = fifolog_reader_findsync(fr->ff, &s);
+ if (e == 0)
+ return (0); /* empty fifolog */
+ if (e == 1) {
+ o = s;
+ seq = be32dec(fr->ff->recbuf);
+ t = be32dec(fr->ff->recbuf + 5);
+ }
+ }
+
+ /* Now do a binary search to find the sync block right before t0 */
+ s = st = (fr->ff->logsize - o) / 2;
+ while (s > 1) {
+ /* We know we shouldn't wrap */
+ if (o + st > fr->ff->logsize + 1) {
+ s = st = s / 2;
+ continue;
+ }
+ e = fifolog_int_read(fr->ff, o + st);
+ if (e) {
+ s = st = s / 2;
+ continue;
+ }
+ /* If not in same part, sequence won't match */
+ seqs = be32dec(fr->ff->recbuf);
+ if (seqs != seq + st) {
+ s = st = s / 2;
+ continue;
+ }
+ /* If not sync block, try next */
+ if (!(fr->ff->recbuf[4] & FIFOLOG_FLG_SYNC)) {
+ st++;
+ continue;
+ }
+ /* Check timestamp */
+ tt = be32dec(fr->ff->recbuf + 5);
+ if (tt >= t0) {
+ s = st = s / 2;
+ continue;
+ }
+ o += st;
+ seq = seqs;
+ }
+ fprintf(stderr, "Read from %jx\n", o * fr->ff->recsize);
+ return (o);
+}
+
+static unsigned char *
+fifolog_reader_chop(struct fifolog_reader *fr, fifolog_reader_render_t *func, void *priv)
+{
+ u_char *p, *q;
+ uint32_t v, w, u;
+
+ p = fr->obuf;
+ q = fr->obuf + (fr->olen - fr->ff->zs->avail_out);
+
+ while (1) {
+ /* Make sure we have a complete header */
+ if (p + 5 >= q)
+ return (p);
+ w = 4;
+ u = be32dec(p);
+ if (u & FIFOLOG_TIMESTAMP) {
+ fr->now = be32dec(p + 4);
+ w += 4;
+ }
+ if (u & FIFOLOG_LENGTH) {
+ v = p[w];
+ w++;
+ if (p + w + v >= q)
+ return (p);
+ } else {
+ for (v = 0; p + v + w < q && p[v + w] != '\0'; v++)
+ continue;
+ if (p + v + w >= q)
+ return (p);
+ v++;
+ }
+ func(priv, fr->now, u, p + w, v);
+ p += w + v;
+ }
+}
+
+/*
+ * Process fifolog until end of written log or provided timestamp
+ */
+
+void
+fifolog_reader_process(struct fifolog_reader *fr, off_t from, fifolog_reader_render_t *func, void *priv, time_t end)
+{
+ uint32_t seq, lseq;
+ off_t o = from;
+ int i, e;
+ time_t t;
+ u_char *p, *q;
+ z_stream *zs;
+
+ CHECK_OBJ_NOTNULL(fr, FIFOLOG_READER_MAGIC);
+ zs = fr->ff->zs;
+ lseq = 0;
+ while (1) {
+ e = fifolog_int_read(fr->ff, o);
+ if (e)
+ err(1, "Read error (%d)", e);
+ if (++o >= fr->ff->logsize)
+ o = 0;
+ seq = be32dec(fr->ff->recbuf);
+ if (lseq != 0 && seq != lseq + 1)
+ break;
+ lseq = seq;
+ zs->avail_in = fr->ff->recsize - 5;
+ zs->next_in = fr->ff->recbuf + 5;
+ if (fr->ff->recbuf[4] & FIFOLOG_FLG_1BYTE)
+ zs->avail_in -= fr->ff->recbuf[fr->ff->recsize - 1];
+ if (fr->ff->recbuf[4] & FIFOLOG_FLG_4BYTE)
+ zs->avail_in -=
+ be32dec(fr->ff->recbuf + fr->ff->recsize - 4);
+ if (fr->ff->recbuf[4] & FIFOLOG_FLG_SYNC) {
+ i = inflateReset(zs);
+ assert(i == Z_OK);
+ zs->next_out = fr->obuf;
+ zs->avail_out = fr->olen;
+ t = be32dec(fr->ff->recbuf + 5);
+ if (t > end)
+ break;
+ zs->next_in += 4;
+ zs->avail_in -= 4;
+ }
+
+ while(zs->avail_in > 0) {
+ i = inflate(zs, 0);
+ if (i == Z_BUF_ERROR) {
+#if 1
+ fprintf(stderr,
+ "Z_BUF_ERROR [%d,%d] [%d,%d,%d]\n",
+ (int)(zs->next_in - fr->ff->recbuf),
+ zs->avail_in,
+ (int)(zs->next_out - fr->obuf),
+ zs->avail_out, fr->olen);
+ exit (250);
+#else
+
+ i = Z_OK;
+#endif
+ }
+ if (i == Z_STREAM_END) {
+ i = inflateReset(zs);
+ }
+ if (i != Z_OK) {
+ fprintf(stderr, "inflate = %d\n", i);
+ exit (250);
+ }
+ assert(i == Z_OK);
+ if (zs->avail_out != fr->olen) {
+ q = fr->obuf + (fr->olen - zs->avail_out);
+ p = fifolog_reader_chop(fr, func, priv);
+ if (p < q)
+ (void)memmove(fr->obuf, p, q - p);
+ zs->avail_out = fr->olen - (q - p);
+ zs->next_out = fr->obuf + (q - p);
+ }
+ }
+ }
+}
diff --git a/usr.sbin/fifolog/lib/fifolog_write.h b/usr.sbin/fifolog/lib/fifolog_write.h
new file mode 100644
index 0000000..957ced8
--- /dev/null
+++ b/usr.sbin/fifolog/lib/fifolog_write.h
@@ -0,0 +1,75 @@
+/*-
+ * Copyright (c) 2005-2008 Poul-Henning Kamp
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+#define FIFOLOG_PT_BYTES_PRE 0
+#define FIFOLOG_PT_BYTES_POST 1
+#define FIFOLOG_PT_WRITES 2
+#define FIFOLOG_PT_FLUSH 3
+#define FIFOLOG_PT_SYNC 4
+#define FIFOLOG_PT_RUNTIME 5
+#define FIFOLOG_NPOINT 6
+
+struct fifolog_writer {
+ unsigned magic;
+#define FIFOLOG_WRITER_MAGIC 0xf1f0706
+
+ struct fifolog_file *ff;
+
+ unsigned writerate;
+ unsigned syncrate;
+ unsigned compression;
+
+ int cleanup;
+
+ intmax_t cnt[FIFOLOG_NPOINT];
+
+ uint32_t seq;
+ off_t recno;
+ uint8_t flag;
+ time_t last;
+
+ ssize_t obufsize;
+ u_char *obuf;
+
+ ssize_t ibufsize;
+ ssize_t ibufptr;
+ u_char *ibuf;
+
+ time_t starttime;
+ time_t lastwrite;
+ time_t lastsync;
+};
+
+struct fifolog_writer *fifolog_write_new(void);
+const char *fifolog_write_open(struct fifolog_writer *f, const char *fn, unsigned writerate, unsigned syncrate, unsigned compression);
+int fifolog_write_record(struct fifolog_writer *f, uint32_t id, time_t now, const void *ptr, ssize_t len);
+int fifolog_write_poll(struct fifolog_writer *f, time_t now);
+int fifolog_write_record_poll(struct fifolog_writer *f, uint32_t id, time_t now, const void *ptr, ssize_t len);
+void fifolog_write_close(struct fifolog_writer *f);
+void fifolog_write_destroy(struct fifolog_writer *f);
+extern const char *fifolog_write_statnames[];
diff --git a/usr.sbin/fifolog/lib/fifolog_write_poll.c b/usr.sbin/fifolog/lib/fifolog_write_poll.c
new file mode 100644
index 0000000..9ebd5be
--- /dev/null
+++ b/usr.sbin/fifolog/lib/fifolog_write_poll.c
@@ -0,0 +1,412 @@
+/*-
+ * Copyright (c) 2005-2008 Poul-Henning Kamp
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <time.h>
+#include <sys/endian.h>
+
+#include <zlib.h>
+
+#include "fifolog.h"
+#include "libfifolog_int.h"
+#include "fifolog_write.h"
+#include "miniobj.h"
+
+static int fifolog_write_gzip(struct fifolog_writer *f, time_t now);
+
+#define ALLOC(ptr, size) do { \
+ (*(ptr)) = calloc(size, 1); \
+ assert(*(ptr) != NULL); \
+} while (0)
+
+
+const char *fifolog_write_statnames[] = {
+ [FIFOLOG_PT_BYTES_PRE] = "Bytes before compression",
+ [FIFOLOG_PT_BYTES_POST] = "Bytes after compression",
+ [FIFOLOG_PT_WRITES] = "Writes",
+ [FIFOLOG_PT_FLUSH] = "Flushes",
+ [FIFOLOG_PT_SYNC] = "Syncs",
+ [FIFOLOG_PT_RUNTIME] = "Runtime"
+};
+
+/**********************************************************************
+ * Check that everything is all right
+ */
+static void
+fifolog_write_assert(const struct fifolog_writer *f)
+{
+
+ CHECK_OBJ_NOTNULL(f, FIFOLOG_WRITER_MAGIC);
+ assert(f->ff->zs->next_out + f->ff->zs->avail_out == \
+ f->obuf + f->obufsize);
+}
+
+/**********************************************************************
+ * Allocate/Destroy a new fifolog writer instance
+ */
+
+struct fifolog_writer *
+fifolog_write_new(void)
+{
+ struct fifolog_writer *f;
+
+ ALLOC_OBJ(f, FIFOLOG_WRITER_MAGIC);
+ assert(f != NULL);
+ return (f);
+}
+
+void
+fifolog_write_destroy(struct fifolog_writer *f)
+{
+
+ free(f->obuf);
+ free(f->ibuf);
+ FREE_OBJ(f);
+}
+
+/**********************************************************************
+ * Open/Close the fifolog
+ */
+
+void
+fifolog_write_close(struct fifolog_writer *f)
+{
+ time_t now;
+
+ CHECK_OBJ_NOTNULL(f, FIFOLOG_WRITER_MAGIC);
+ fifolog_write_assert(f);
+
+ f->cleanup = 1;
+ time(&now);
+ fifolog_write_gzip(f, now);
+ fifolog_write_assert(f);
+ fifolog_int_close(&f->ff);
+ free(f->ff);
+}
+
+const char *
+fifolog_write_open(struct fifolog_writer *f, const char *fn,
+ unsigned writerate, unsigned syncrate, unsigned compression)
+{
+ const char *es;
+ int i;
+ time_t now;
+ off_t o;
+
+ CHECK_OBJ_NOTNULL(f, FIFOLOG_WRITER_MAGIC);
+
+ /* Check for legal compression value */
+ if (compression > Z_BEST_COMPRESSION)
+ return ("Illegal compression value");
+
+ f->writerate = writerate;
+ f->syncrate = syncrate;
+ f->compression = compression;
+
+ /* Reset statistics */
+ memset(f->cnt, 0, sizeof f->cnt);
+
+ es = fifolog_int_open(&f->ff, fn, 1);
+ if (es != NULL)
+ return (es);
+ es = fifolog_int_findend(f->ff, &o);
+ if (es != NULL)
+ return (es);
+ i = fifolog_int_read(f->ff, o);
+ if (i)
+ return ("Read error, looking for seq");
+ f->seq = be32dec(f->ff->recbuf);
+ if (f->seq == 0) {
+ /* Empty fifolog */
+ f->seq = random();
+ } else {
+ f->recno = o + 1;
+ f->seq++;
+ }
+
+ f->obufsize = f->ff->recsize;
+ ALLOC(&f->obuf, f->obufsize);
+
+ f->ibufsize = f->obufsize * 10;
+ ALLOC(&f->ibuf, f->ibufsize);
+ f->ibufptr = 0;
+
+ i = deflateInit(f->ff->zs, (int)f->compression);
+ assert(i == Z_OK);
+
+ f->flag |= FIFOLOG_FLG_RESTART;
+ f->flag |= FIFOLOG_FLG_SYNC;
+ f->ff->zs->next_out = f->obuf + 9;
+ f->ff->zs->avail_out = f->obufsize - 9;
+
+ time(&now);
+ f->starttime = now;
+ f->lastsync = now;
+ f->lastwrite = now;
+
+ fifolog_write_assert(f);
+ return (NULL);
+}
+
+/**********************************************************************
+ * Write an output record
+ * Returns -1 if there are trouble writing data
+ */
+
+static int
+fifolog_write_output(struct fifolog_writer *f, int fl, time_t now)
+{
+ long h, l = f->ff->zs->next_out - f->obuf;
+ ssize_t i, w;
+ int retval = 0;
+
+ h = 4; /* seq */
+ be32enc(f->obuf, f->seq);
+ f->obuf[h] = f->flag;
+ h += 1; /* flag */
+ if (f->flag & FIFOLOG_FLG_SYNC) {
+ be32enc(f->obuf + h, now);
+ h += 4; /* timestamp */
+ }
+
+ assert(l <= (long)f->ff->recsize); /* NB: l includes h */
+ assert(l >= h);
+
+ /* We will never write an entirely empty buffer */
+ if (l == h)
+ return (0);
+
+ if (l < (long)f->ff->recsize && fl == Z_NO_FLUSH)
+ return (0);
+
+ w = f->ff->recsize - l;
+ if (w > 255) {
+ be32enc(f->obuf + f->ff->recsize - 4, w);
+ f->obuf[4] |= FIFOLOG_FLG_4BYTE;
+ } else if (w > 0) {
+ f->obuf[f->ff->recsize - 1] = (uint8_t)w;
+ f->obuf[4] |= FIFOLOG_FLG_1BYTE;
+ }
+
+ f->cnt[FIFOLOG_PT_BYTES_POST] += l - h;
+
+ i = pwrite(f->ff->fd, f->obuf, f->ff->recsize,
+ (f->recno + 1) * f->ff->recsize);
+ if (i != f->ff->recsize)
+ retval = -1;
+ else
+ retval = 1;
+
+ f->cnt[FIFOLOG_PT_WRITES]++;
+ f->cnt[FIFOLOG_PT_RUNTIME] = now - f->starttime;
+
+ f->lastwrite = now;
+ /*
+ * We increment these even on error, so as to properly skip bad,
+ * sectors or other light trouble.
+ */
+ f->seq++;
+ f->recno++;
+ f->flag = 0;
+
+ memset(f->obuf, 0, f->obufsize);
+ f->ff->zs->next_out = f->obuf + 5;
+ f->ff->zs->avail_out = f->obufsize - 5;
+ return (retval);
+}
+
+/**********************************************************************
+ * Run the compression engine
+ * Returns -1 if there are trouble writing data
+ */
+
+static int
+fifolog_write_gzip(struct fifolog_writer *f, time_t now)
+{
+ int i, fl, retval = 0;
+
+ assert(now != 0);
+ if (f->cleanup || now >= (int)(f->lastsync + f->syncrate)) {
+ f->cleanup = 0;
+ fl = Z_FINISH;
+ f->cnt[FIFOLOG_PT_SYNC]++;
+ } else if (now >= (int)(f->lastwrite + f->writerate)) {
+ fl = Z_SYNC_FLUSH;
+ f->cnt[FIFOLOG_PT_FLUSH]++;
+ } else if (f->ibufptr == 0)
+ return (0);
+ else
+ fl = Z_NO_FLUSH;
+
+ f->ff->zs->avail_in = f->ibufptr;
+ f->ff->zs->next_in = f->ibuf;
+
+ while (1) {
+ i = deflate(f->ff->zs, fl);
+ assert(i == Z_OK || i == Z_BUF_ERROR || i == Z_STREAM_END);
+
+ i = fifolog_write_output(f, fl, now);
+ if (i == 0)
+ break;
+ if (i < 0)
+ retval = -1;
+ }
+ assert(f->ff->zs->avail_in == 0);
+ f->ibufptr = 0;
+ if (fl == Z_FINISH) {
+ f->flag |= FIFOLOG_FLG_SYNC;
+ f->ff->zs->next_out = f->obuf + 9;
+ f->ff->zs->avail_out = f->obufsize - 9;
+ f->lastsync = now;
+ assert(Z_OK == deflateReset(f->ff->zs));
+ }
+ return (retval);
+}
+
+/**********************************************************************
+ * Poll to see if we need to flush out a record
+ * Returns -1 if there are trouble writing data
+ */
+
+int
+fifolog_write_poll(struct fifolog_writer *f, time_t now)
+{
+
+ if (now == 0)
+ time(&now);
+ return (fifolog_write_gzip(f, now));
+}
+
+/**********************************************************************
+ * Attempt to write an entry into the ibuf.
+ * Return zero if there is no space, one otherwise
+ */
+
+int
+fifolog_write_record(struct fifolog_writer *f, uint32_t id, time_t now,
+ const void *ptr, ssize_t len)
+{
+ const unsigned char *p;
+ uint8_t buf[9];
+ ssize_t bufl;
+
+ fifolog_write_assert(f);
+ assert(!(id & (FIFOLOG_TIMESTAMP|FIFOLOG_LENGTH)));
+ assert(ptr != NULL);
+
+ p = ptr;
+ if (len == 0) {
+ len = strlen(ptr);
+ len++;
+ } else {
+ assert(len <= 255);
+ id |= FIFOLOG_LENGTH;
+ }
+ assert (len > 0);
+
+ /* Do a timestamp, if needed */
+ if (now == 0)
+ time(&now);
+
+ if (now != f->last)
+ id |= FIFOLOG_TIMESTAMP;
+
+ /* Emit instance+flag */
+ be32enc(buf, id);
+ bufl = 4;
+
+ if (id & FIFOLOG_TIMESTAMP) {
+ be32enc(buf + bufl, (uint32_t)now);
+ bufl += 4;
+ }
+ if (id & FIFOLOG_LENGTH)
+ buf[bufl++] = (u_char)len;
+
+ if (bufl + len + f->ibufptr > f->ibufsize)
+ return (0);
+
+ memcpy(f->ibuf + f->ibufptr, buf, bufl);
+ f->ibufptr += bufl;
+ memcpy(f->ibuf + f->ibufptr, p, len);
+ f->ibufptr += len;
+ f->cnt[FIFOLOG_PT_BYTES_PRE] += bufl + len;
+
+ if (id & FIFOLOG_TIMESTAMP)
+ f->last = now;
+ return (1);
+}
+
+/**********************************************************************
+ * Write an entry, polling the gzip/writer until success.
+ * Long binary entries are broken into 255 byte chunks.
+ * Returns -1 if there are problems writing data
+ */
+
+int
+fifolog_write_record_poll(struct fifolog_writer *f, uint32_t id, time_t now,
+ const void *ptr, ssize_t len)
+{
+ u_int l;
+ const unsigned char *p;
+ int retval = 0;
+
+ if (now == 0)
+ time(&now);
+ fifolog_write_assert(f);
+
+ assert(!(id & (FIFOLOG_TIMESTAMP|FIFOLOG_LENGTH)));
+ assert(ptr != NULL);
+
+ if (len == 0) {
+ if (!fifolog_write_record(f, id, now, ptr, len)) {
+ if (fifolog_write_gzip(f, now) < 0)
+ retval = -1;
+ /* The string could be too long for the ibuf, so... */
+ if (!fifolog_write_record(f, id, now, ptr, len))
+ retval = -1;
+ }
+ } else {
+ for (p = ptr; len > 0; len -= l, p += l) {
+ l = len;
+ if (l > 255)
+ l = 255;
+ while (!fifolog_write_record(f, id, now, p, l))
+ if (fifolog_write_gzip(f, now) < 0)
+ retval = -1;
+ }
+ }
+ if (fifolog_write_gzip(f, now) < 0)
+ retval = -1;
+ fifolog_write_assert(f);
+ return (retval);
+}
diff --git a/usr.sbin/fifolog/lib/getdate.y b/usr.sbin/fifolog/lib/getdate.y
new file mode 100644
index 0000000..53a515c
--- /dev/null
+++ b/usr.sbin/fifolog/lib/getdate.y
@@ -0,0 +1,887 @@
+%{
+/*
+** Originally written by Steven M. Bellovin <smb@research.att.com> while
+** at the University of North Carolina at Chapel Hill. Later tweaked by
+** a couple of people on Usenet. Completely overhauled by Rich $alz
+** <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990;
+**
+** This grammar has 10 shift/reduce conflicts.
+**
+** This code is in the public domain and has no copyright.
+**
+** Picked up from CVS and slightly cleaned up by to WARNS=5 level by
+** Poul-Henning Kamp <phk@FreeBSD.org>
+**
+** $FreeBSD$
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/time.h>
+
+#include "libfifolog.h"
+
+#define yylex getdate_yylex
+#define yyerror getdate_yyerror
+
+static int yylex(void);
+static int yyerror(const char *);
+
+#define EPOCH 1970
+#define HOUR(x) ((time_t)(x) * 60)
+#define SECSPERDAY (24L * 60L * 60L)
+
+
+/*
+** An entry in the lexical lookup table.
+*/
+typedef struct _TABLE {
+ const char *name;
+ int type;
+ time_t value;
+} TABLE;
+
+
+/*
+** Daylight-savings mode: on, off, or not yet known.
+*/
+typedef enum _DSTMODE {
+ DSToff, DSTon, DSTmaybe
+} DSTMODE;
+
+/*
+** Meridian: am, pm, or 24-hour style.
+*/
+typedef enum _MERIDIAN {
+ MERam, MERpm, MER24
+} MERIDIAN;
+
+
+/*
+** Global variables. We could get rid of most of these by using a good
+** union as the yacc stack. (This routine was originally written before
+** yacc had the %union construct.) Maybe someday; right now we only use
+** the %union very rarely.
+*/
+static char *yyInput;
+static DSTMODE yyDSTmode;
+static time_t yyDayOrdinal;
+static time_t yyDayNumber;
+static int yyHaveDate;
+static int yyHaveDay;
+static int yyHaveRel;
+static int yyHaveTime;
+static int yyHaveZone;
+static time_t yyTimezone;
+static time_t yyDay;
+static time_t yyHour;
+static time_t yyMinutes;
+static time_t yyMonth;
+static time_t yySeconds;
+static time_t yyYear;
+static MERIDIAN yyMeridian;
+static time_t yyRelMonth;
+static time_t yyRelSeconds;
+
+%}
+
+%union {
+ time_t Number;
+ enum _MERIDIAN Meridian;
+}
+
+%token tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
+%token tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST
+
+%type <Number> tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
+%type <Number> tSEC_UNIT tSNUMBER tUNUMBER tZONE
+%type <Meridian> tMERIDIAN o_merid
+
+%%
+
+spec : /* NULL */
+ | spec item
+ ;
+
+item : time {
+ yyHaveTime++;
+ }
+ | zone {
+ yyHaveZone++;
+ }
+ | date {
+ yyHaveDate++;
+ }
+ | day {
+ yyHaveDay++;
+ }
+ | rel {
+ yyHaveRel++;
+ }
+ | cvsstamp {
+ yyHaveTime++;
+ yyHaveDate++;
+ yyHaveZone++;
+ }
+ | number
+ ;
+
+cvsstamp: tUNUMBER '.' tUNUMBER '.' tUNUMBER '.' tUNUMBER '.' tUNUMBER '.' tUNUMBER {
+ yyYear = $1;
+ if (yyYear < 100) yyYear += 1900;
+ yyMonth = $3;
+ yyDay = $5;
+ yyHour = $7;
+ yyMinutes = $9;
+ yySeconds = $11;
+ yyDSTmode = DSToff;
+ yyTimezone = 0;
+ }
+ ;
+
+time : tUNUMBER tMERIDIAN {
+ yyHour = $1;
+ yyMinutes = 0;
+ yySeconds = 0;
+ yyMeridian = $2;
+ }
+ | tUNUMBER ':' tUNUMBER o_merid {
+ yyHour = $1;
+ yyMinutes = $3;
+ yySeconds = 0;
+ yyMeridian = $4;
+ }
+ | tUNUMBER ':' tUNUMBER tSNUMBER {
+ yyHour = $1;
+ yyMinutes = $3;
+ yyMeridian = MER24;
+ yyDSTmode = DSToff;
+ yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
+ }
+ | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
+ yyHour = $1;
+ yyMinutes = $3;
+ yySeconds = $5;
+ yyMeridian = $6;
+ }
+ | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
+ yyHour = $1;
+ yyMinutes = $3;
+ yySeconds = $5;
+ yyMeridian = MER24;
+ yyDSTmode = DSToff;
+ yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
+ }
+ ;
+
+zone : tZONE {
+ yyTimezone = $1;
+ yyDSTmode = DSToff;
+ }
+ | tDAYZONE {
+ yyTimezone = $1;
+ yyDSTmode = DSTon;
+ }
+ |
+ tZONE tDST {
+ yyTimezone = $1;
+ yyDSTmode = DSTon;
+ }
+ ;
+
+day : tDAY {
+ yyDayOrdinal = 1;
+ yyDayNumber = $1;
+ }
+ | tDAY ',' {
+ yyDayOrdinal = 1;
+ yyDayNumber = $1;
+ }
+ | tUNUMBER tDAY {
+ yyDayOrdinal = $1;
+ yyDayNumber = $2;
+ }
+ ;
+
+date : tUNUMBER '/' tUNUMBER {
+ yyMonth = $1;
+ yyDay = $3;
+ }
+ | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
+ if ($1 >= 100) {
+ yyYear = $1;
+ yyMonth = $3;
+ yyDay = $5;
+ } else {
+ yyMonth = $1;
+ yyDay = $3;
+ yyYear = $5;
+ }
+ }
+ | tUNUMBER tSNUMBER tSNUMBER {
+ /* ISO 8601 format. yyyy-mm-dd. */
+ yyYear = $1;
+ yyMonth = -$2;
+ yyDay = -$3;
+ }
+ | tUNUMBER tMONTH tSNUMBER {
+ /* e.g. 17-JUN-1992. */
+ yyDay = $1;
+ yyMonth = $2;
+ yyYear = -$3;
+ }
+ | tMONTH tUNUMBER {
+ yyMonth = $1;
+ yyDay = $2;
+ }
+ | tMONTH tUNUMBER ',' tUNUMBER {
+ yyMonth = $1;
+ yyDay = $2;
+ yyYear = $4;
+ }
+ | tUNUMBER tMONTH {
+ yyMonth = $2;
+ yyDay = $1;
+ }
+ | tUNUMBER tMONTH tUNUMBER {
+ yyMonth = $2;
+ yyDay = $1;
+ yyYear = $3;
+ }
+ ;
+
+rel : relunit tAGO {
+ yyRelSeconds = -yyRelSeconds;
+ yyRelMonth = -yyRelMonth;
+ }
+ | relunit
+ ;
+
+relunit : tUNUMBER tMINUTE_UNIT {
+ yyRelSeconds += $1 * $2 * 60L;
+ }
+ | tSNUMBER tMINUTE_UNIT {
+ yyRelSeconds += $1 * $2 * 60L;
+ }
+ | tMINUTE_UNIT {
+ yyRelSeconds += $1 * 60L;
+ }
+ | tSNUMBER tSEC_UNIT {
+ yyRelSeconds += $1;
+ }
+ | tUNUMBER tSEC_UNIT {
+ yyRelSeconds += $1;
+ }
+ | tSEC_UNIT {
+ yyRelSeconds++;
+ }
+ | tSNUMBER tMONTH_UNIT {
+ yyRelMonth += $1 * $2;
+ }
+ | tUNUMBER tMONTH_UNIT {
+ yyRelMonth += $1 * $2;
+ }
+ | tMONTH_UNIT {
+ yyRelMonth += $1;
+ }
+ ;
+
+number : tUNUMBER {
+ if (yyHaveTime && yyHaveDate && !yyHaveRel)
+ yyYear = $1;
+ else {
+ if($1>10000) {
+ yyHaveDate++;
+ yyDay= ($1)%100;
+ yyMonth= ($1/100)%100;
+ yyYear = $1/10000;
+ }
+ else {
+ yyHaveTime++;
+ if ($1 < 100) {
+ yyHour = $1;
+ yyMinutes = 0;
+ }
+ else {
+ yyHour = $1 / 100;
+ yyMinutes = $1 % 100;
+ }
+ yySeconds = 0;
+ yyMeridian = MER24;
+ }
+ }
+ }
+ ;
+
+o_merid : /* NULL */ {
+ $$ = MER24;
+ }
+ | tMERIDIAN {
+ $$ = $1;
+ }
+ ;
+
+%%
+
+/* Month and day table. */
+static TABLE const MonthDayTable[] = {
+ { "january", tMONTH, 1 },
+ { "february", tMONTH, 2 },
+ { "march", tMONTH, 3 },
+ { "april", tMONTH, 4 },
+ { "may", tMONTH, 5 },
+ { "june", tMONTH, 6 },
+ { "july", tMONTH, 7 },
+ { "august", tMONTH, 8 },
+ { "september", tMONTH, 9 },
+ { "sept", tMONTH, 9 },
+ { "october", tMONTH, 10 },
+ { "november", tMONTH, 11 },
+ { "december", tMONTH, 12 },
+ { "sunday", tDAY, 0 },
+ { "monday", tDAY, 1 },
+ { "tuesday", tDAY, 2 },
+ { "tues", tDAY, 2 },
+ { "wednesday", tDAY, 3 },
+ { "wednes", tDAY, 3 },
+ { "thursday", tDAY, 4 },
+ { "thur", tDAY, 4 },
+ { "thurs", tDAY, 4 },
+ { "friday", tDAY, 5 },
+ { "saturday", tDAY, 6 },
+ { NULL, 0, 0 }
+};
+
+/* Time units table. */
+static TABLE const UnitsTable[] = {
+ { "year", tMONTH_UNIT, 12 },
+ { "month", tMONTH_UNIT, 1 },
+ { "fortnight", tMINUTE_UNIT, 14 * 24 * 60 },
+ { "week", tMINUTE_UNIT, 7 * 24 * 60 },
+ { "day", tMINUTE_UNIT, 1 * 24 * 60 },
+ { "hour", tMINUTE_UNIT, 60 },
+ { "minute", tMINUTE_UNIT, 1 },
+ { "min", tMINUTE_UNIT, 1 },
+ { "second", tSEC_UNIT, 1 },
+ { "sec", tSEC_UNIT, 1 },
+ { NULL, 0, 0 }
+};
+
+/* Assorted relative-time words. */
+static TABLE const OtherTable[] = {
+ { "tomorrow", tMINUTE_UNIT, 1 * 24 * 60 },
+ { "yesterday", tMINUTE_UNIT, -1 * 24 * 60 },
+ { "today", tMINUTE_UNIT, 0 },
+ { "now", tMINUTE_UNIT, 0 },
+ { "last", tUNUMBER, -1 },
+ { "this", tMINUTE_UNIT, 0 },
+ { "next", tUNUMBER, 2 },
+ { "first", tUNUMBER, 1 },
+/* { "second", tUNUMBER, 2 }, */
+ { "third", tUNUMBER, 3 },
+ { "fourth", tUNUMBER, 4 },
+ { "fifth", tUNUMBER, 5 },
+ { "sixth", tUNUMBER, 6 },
+ { "seventh", tUNUMBER, 7 },
+ { "eighth", tUNUMBER, 8 },
+ { "ninth", tUNUMBER, 9 },
+ { "tenth", tUNUMBER, 10 },
+ { "eleventh", tUNUMBER, 11 },
+ { "twelfth", tUNUMBER, 12 },
+ { "ago", tAGO, 1 },
+ { NULL, 0, 0 }
+};
+
+/* The timezone table. */
+/* Some of these are commented out because a time_t can't store a float. */
+static TABLE const TimezoneTable[] = {
+ { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */
+ { "ut", tZONE, HOUR( 0) }, /* Universal (Coordinated) */
+ { "utc", tZONE, HOUR( 0) },
+ { "wet", tZONE, HOUR( 0) }, /* Western European */
+ { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */
+ { "wat", tZONE, HOUR( 1) }, /* West Africa */
+ { "at", tZONE, HOUR( 2) }, /* Azores */
+#if 0
+ /* For completeness. BST is also British Summer, and GST is
+ * also Guam Standard. */
+ { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */
+ { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */
+#endif
+#if 0
+ { "nft", tZONE, HOUR(3.5) }, /* Newfoundland */
+ { "nst", tZONE, HOUR(3.5) }, /* Newfoundland Standard */
+ { "ndt", tDAYZONE, HOUR(3.5) }, /* Newfoundland Daylight */
+#endif
+ { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */
+ { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */
+ { "est", tZONE, HOUR( 5) }, /* Eastern Standard */
+ { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */
+ { "cst", tZONE, HOUR( 6) }, /* Central Standard */
+ { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */
+ { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */
+ { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */
+ { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */
+ { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */
+ { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */
+ { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */
+ { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */
+ { "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */
+ { "cat", tZONE, HOUR(10) }, /* Central Alaska */
+ { "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */
+ { "nt", tZONE, HOUR(11) }, /* Nome */
+ { "idlw", tZONE, HOUR(12) }, /* International Date Line West */
+ { "cet", tZONE, -HOUR(1) }, /* Central European */
+ { "met", tZONE, -HOUR(1) }, /* Middle European */
+ { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */
+ { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
+ { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */
+ { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */
+ { "fwt", tZONE, -HOUR(1) }, /* French Winter */
+ { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */
+ { "eet", tZONE, -HOUR(2) }, /* Eastern Europe, USSR Zone 1 */
+ { "bt", tZONE, -HOUR(3) }, /* Baghdad, USSR Zone 2 */
+#if 0
+ { "it", tZONE, -HOUR(3.5) },/* Iran */
+#endif
+ { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */
+ { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */
+#if 0
+ { "ist", tZONE, -HOUR(5.5) },/* Indian Standard */
+#endif
+ { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */
+#if 0
+ /* For completeness. NST is also Newfoundland Stanard, and SST is
+ * also Swedish Summer. */
+ { "nst", tZONE, -HOUR(6.5) },/* North Sumatra */
+ { "sst", tZONE, -HOUR(7) }, /* South Sumatra, USSR Zone 6 */
+#endif /* 0 */
+ { "wast", tZONE, -HOUR(7) }, /* West Australian Standard */
+ { "wadt", tDAYZONE, -HOUR(7) }, /* West Australian Daylight */
+#if 0
+ { "jt", tZONE, -HOUR(7.5) },/* Java (3pm in Cronusland!) */
+#endif
+ { "cct", tZONE, -HOUR(8) }, /* China Coast, USSR Zone 7 */
+ { "jst", tZONE, -HOUR(9) }, /* Japan Standard, USSR Zone 8 */
+#if 0
+ { "cast", tZONE, -HOUR(9.5) },/* Central Australian Standard */
+ { "cadt", tDAYZONE, -HOUR(9.5) },/* Central Australian Daylight */
+#endif
+ { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */
+ { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */
+ { "gst", tZONE, -HOUR(10) }, /* Guam Standard, USSR Zone 9 */
+ { "nzt", tZONE, -HOUR(12) }, /* New Zealand */
+ { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */
+ { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */
+ { "idle", tZONE, -HOUR(12) }, /* International Date Line East */
+ { NULL, 0, 0 }
+};
+
+/* Military timezone table. */
+static TABLE const MilitaryTable[] = {
+ { "a", tZONE, HOUR( 1) },
+ { "b", tZONE, HOUR( 2) },
+ { "c", tZONE, HOUR( 3) },
+ { "d", tZONE, HOUR( 4) },
+ { "e", tZONE, HOUR( 5) },
+ { "f", tZONE, HOUR( 6) },
+ { "g", tZONE, HOUR( 7) },
+ { "h", tZONE, HOUR( 8) },
+ { "i", tZONE, HOUR( 9) },
+ { "k", tZONE, HOUR( 10) },
+ { "l", tZONE, HOUR( 11) },
+ { "m", tZONE, HOUR( 12) },
+ { "n", tZONE, HOUR(- 1) },
+ { "o", tZONE, HOUR(- 2) },
+ { "p", tZONE, HOUR(- 3) },
+ { "q", tZONE, HOUR(- 4) },
+ { "r", tZONE, HOUR(- 5) },
+ { "s", tZONE, HOUR(- 6) },
+ { "t", tZONE, HOUR(- 7) },
+ { "u", tZONE, HOUR(- 8) },
+ { "v", tZONE, HOUR(- 9) },
+ { "w", tZONE, HOUR(-10) },
+ { "x", tZONE, HOUR(-11) },
+ { "y", tZONE, HOUR(-12) },
+ { "z", tZONE, HOUR( 0) },
+ { NULL, 0, 0 }
+};
+
+
+
+
+/* ARGSUSED */
+static int
+yyerror(const char *s __unused)
+{
+ return 0;
+}
+
+
+static time_t
+ToSeconds(time_t Hours, time_t Minutes, time_t Seconds, MERIDIAN Meridian)
+{
+ if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
+ return -1;
+ switch (Meridian) {
+ case MER24:
+ if (Hours < 0 || Hours > 23)
+ return -1;
+ return (Hours * 60L + Minutes) * 60L + Seconds;
+ case MERam:
+ if (Hours < 1 || Hours > 12)
+ return -1;
+ if (Hours == 12)
+ Hours = 0;
+ return (Hours * 60L + Minutes) * 60L + Seconds;
+ case MERpm:
+ if (Hours < 1 || Hours > 12)
+ return -1;
+ if (Hours == 12)
+ Hours = 0;
+ return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
+ default:
+ abort ();
+ }
+ /* NOTREACHED */
+}
+
+
+/* Year is either
+ * A negative number, which means to use its absolute value (why?)
+ * A number from 0 to 99, which means a year from 1900 to 1999, or
+ * The actual year (>=100). */
+static time_t
+Convert(time_t Month, time_t Day, time_t Year,
+ time_t Hours, time_t Minutes, time_t Seconds,
+ MERIDIAN Meridian, DSTMODE DSTmode)
+{
+ static int DaysInMonth[12] = {
+ 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+ };
+ time_t tod;
+ time_t Julian;
+ int i;
+ struct tm *ltm;
+
+ if (Year < 0)
+ Year = -Year;
+ if (Year < 69)
+ Year += 2000;
+ else if (Year < 100)
+ Year += 1900;
+ DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
+ ? 29 : 28;
+ /* Checking for 2038 bogusly assumes that time_t is 32 bits. But
+ I'm too lazy to try to check for time_t overflow in another way. */
+ if (Year < EPOCH || Year > 2038
+ || Month < 1 || Month > 12
+ /* Lint fluff: "conversion from long may lose accuracy" */
+ || Day < 1 || Day > DaysInMonth[(int)--Month])
+ /* FIXME:
+ * It would be nice to set a global error string here.
+ * "February 30 is not a valid date" is much more informative than
+ * "Can't parse date/time: 100 months" when the user input was
+ * "100 months" and addition resolved that to February 30, for
+ * example. See rcs2-7 in src/sanity.sh for more. */
+ return -1;
+
+ for (Julian = Day - 1, i = 0; i < Month; i++)
+ Julian += DaysInMonth[i];
+ for (i = EPOCH; i < Year; i++)
+ Julian += 365 + (i % 4 == 0);
+ Julian *= SECSPERDAY;
+ Julian += yyTimezone * 60L;
+ if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
+ return -1;
+ Julian += tod;
+ ltm = localtime(&Julian);
+fprintf(stderr, "DST %d TZ %s %d\n", DSTmode, ltm->tm_zone, ltm->tm_isdst);
+ if (DSTmode == DSTon
+ || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst))
+ Julian -= 60 * 60;
+ return Julian;
+}
+
+
+static time_t
+DSTcorrect(time_t Start, time_t Future)
+{
+ time_t StartDay;
+ time_t FutureDay;
+
+ StartDay = (localtime(&Start)->tm_hour + 1) % 24;
+ FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
+ return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
+}
+
+
+static time_t
+RelativeDate(time_t Start, time_t DayOrdinal, time_t DayNumber)
+{
+ struct tm *tm;
+ time_t now;
+
+ now = Start;
+ tm = localtime(&now);
+ now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
+ now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
+ return DSTcorrect(Start, now);
+}
+
+
+static time_t
+RelativeMonth(time_t Start, time_t RelMonth)
+{
+ struct tm *tm;
+ time_t Month;
+ time_t Year;
+
+ if (RelMonth == 0)
+ return 0;
+ tm = localtime(&Start);
+ Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth;
+ Year = Month / 12;
+ Month = Month % 12 + 1;
+ return DSTcorrect(Start,
+ Convert(Month, (time_t)tm->tm_mday, Year,
+ (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
+ MER24, DSTmaybe));
+}
+
+
+static int
+LookupWord(char *buff)
+{
+ char *p;
+ char *q;
+ const TABLE *tp;
+ int i;
+ int abbrev;
+
+ /* Make it lowercase. */
+ for (p = buff; *p; p++)
+ if (isupper(*p))
+ *p = tolower(*p);
+
+ if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
+ yylval.Meridian = MERam;
+ return tMERIDIAN;
+ }
+ if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
+ yylval.Meridian = MERpm;
+ return tMERIDIAN;
+ }
+
+ /* See if we have an abbreviation for a month. */
+ if (strlen(buff) == 3)
+ abbrev = 1;
+ else if (strlen(buff) == 4 && buff[3] == '.') {
+ abbrev = 1;
+ buff[3] = '\0';
+ }
+ else
+ abbrev = 0;
+
+ for (tp = MonthDayTable; tp->name; tp++) {
+ if (abbrev) {
+ if (strncmp(buff, tp->name, 3) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+ else if (strcmp(buff, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+
+ for (tp = TimezoneTable; tp->name; tp++)
+ if (strcmp(buff, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ if (strcmp(buff, "dst") == 0)
+ return tDST;
+
+ for (tp = UnitsTable; tp->name; tp++)
+ if (strcmp(buff, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ /* Strip off any plural and try the units table again. */
+ i = strlen(buff) - 1;
+ if (buff[i] == 's') {
+ buff[i] = '\0';
+ for (tp = UnitsTable; tp->name; tp++)
+ if (strcmp(buff, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ buff[i] = 's'; /* Put back for "this" in OtherTable. */
+ }
+
+ for (tp = OtherTable; tp->name; tp++)
+ if (strcmp(buff, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ /* Military timezones. */
+ if (buff[1] == '\0' && isalpha(*buff)) {
+ for (tp = MilitaryTable; tp->name; tp++)
+ if (strcmp(buff, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+
+ /* Drop out any periods and try the timezone table again. */
+ for (i = 0, p = q = buff; *q; q++)
+ if (*q != '.')
+ *p++ = *q;
+ else
+ i++;
+ *p = '\0';
+ if (i)
+ for (tp = TimezoneTable; tp->name; tp++)
+ if (strcmp(buff, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ return tID;
+}
+
+
+static int
+yylex(void)
+{
+ char c;
+ char *p;
+ char buff[20];
+ int Count;
+ int sign;
+
+ for ( ; ; ) {
+ while (isspace(*yyInput))
+ yyInput++;
+
+ if (isdigit(c = *yyInput) || c == '-' || c == '+') {
+ if (c == '-' || c == '+') {
+ sign = c == '-' ? -1 : 1;
+ if (!isdigit(*++yyInput))
+ /* skip the '-' sign */
+ continue;
+ }
+ else
+ sign = 0;
+ for (yylval.Number = 0; isdigit(c = *yyInput++); )
+ yylval.Number = 10 * yylval.Number + c - '0';
+ yyInput--;
+ if (sign < 0)
+ yylval.Number = -yylval.Number;
+ return sign ? tSNUMBER : tUNUMBER;
+ }
+ if (isalpha(c)) {
+ for (p = buff; isalpha(c = *yyInput++) || c == '.'; )
+ if (p < &buff[sizeof buff - 1])
+ *p++ = c;
+ *p = '\0';
+ yyInput--;
+ return LookupWord(buff);
+ }
+ if (c != '(')
+ return *yyInput++;
+ Count = 0;
+ do {
+ c = *yyInput++;
+ if (c == '\0')
+ return c;
+ if (c == '(')
+ Count++;
+ else if (c == ')')
+ Count--;
+ } while (Count > 0);
+ }
+}
+
+#define TM_YEAR_ORIGIN 1900
+
+time_t
+get_date(char *p)
+{
+ struct tm *tm, gmt;
+ time_t Start;
+ time_t tod;
+ time_t nowtime;
+ struct tm *gmt_ptr;
+
+ yyInput = p;
+
+ (void)time (&nowtime);
+
+ gmt_ptr = gmtime (&nowtime);
+ if (gmt_ptr != NULL)
+ {
+ /* Make a copy, in case localtime modifies *tm (I think
+ that comment now applies to *gmt_ptr, but I am too
+ lazy to dig into how gmtime and locatime allocate the
+ structures they return pointers to). */
+ gmt = *gmt_ptr;
+ }
+
+ if (! (tm = localtime (&nowtime)))
+ return -1;
+
+ tm = localtime(&nowtime);
+ yyYear = tm->tm_year + 1900;
+ yyMonth = tm->tm_mon + 1;
+ yyDay = tm->tm_mday;
+ yyTimezone = tm->tm_gmtoff;
+ yyDSTmode = DSTmaybe;
+ yyHour = 0;
+ yyMinutes = 0;
+ yySeconds = 0;
+ yyMeridian = MER24;
+ yyRelSeconds = 0;
+ yyRelMonth = 0;
+ yyHaveDate = 0;
+ yyHaveDay = 0;
+ yyHaveRel = 0;
+ yyHaveTime = 0;
+ yyHaveZone = 0;
+
+ if (yyparse()
+ || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
+ return -1;
+
+ if (yyHaveDate || yyHaveTime || yyHaveDay) {
+ Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
+ yyMeridian, yyDSTmode);
+ if (Start < 0)
+ return -1;
+ }
+ else {
+ Start = nowtime;
+ if (!yyHaveRel)
+ Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
+ }
+
+ Start += yyRelSeconds;
+ Start += RelativeMonth(Start, yyRelMonth);
+
+ if (yyHaveDay && !yyHaveDate) {
+ tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber);
+ Start += tod;
+ }
+
+ /* Have to do *something* with a legitimate -1 so it's distinguishable
+ * from the error return value. (Alternately could set errno on error.) */
+ return Start == -1 ? 0 : Start;
+}
diff --git a/usr.sbin/fifolog/lib/libfifolog.h b/usr.sbin/fifolog/lib/libfifolog.h
new file mode 100644
index 0000000..eb5cc58
--- /dev/null
+++ b/usr.sbin/fifolog/lib/libfifolog.h
@@ -0,0 +1,47 @@
+/*-
+ * Copyright (c) 2005-2008 Poul-Henning Kamp
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/stdint.h>
+
+/* CREATORS */
+const char *fifolog_create(const char *fn, off_t size, ssize_t recsize);
+
+/* WRITERS */
+
+#include "fifolog_write.h"
+
+/* READERS */
+
+typedef void fifolog_reader_render_t(void *priv, time_t when, unsigned flag, const unsigned char *p, unsigned l);
+struct fifolog_reader;
+struct fifolog_reader *fifolog_reader_open(const char *fname);
+off_t fifolog_reader_seek(const struct fifolog_reader *fl, time_t t0);
+void fifolog_reader_process(struct fifolog_reader *fl, off_t from, fifolog_reader_render_t *func, void *priv, time_t end);
+
+/* UTILS */
+time_t get_date(char *p);
diff --git a/usr.sbin/fifolog/lib/libfifolog_int.h b/usr.sbin/fifolog/lib/libfifolog_int.h
new file mode 100644
index 0000000..2b82241
--- /dev/null
+++ b/usr.sbin/fifolog/lib/libfifolog_int.h
@@ -0,0 +1,45 @@
+/*-
+ * Copyright (c) 2005-2008
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+struct fifolog_file {
+ unsigned magic;
+#define FIFOLOG_FILE_MAGIC 0x307ea50d
+
+ ssize_t recsize;
+ off_t logsize;
+ int fd;
+
+ z_stream *zs;
+
+ unsigned char *recbuf;
+};
+
+const char *fifolog_int_open(struct fifolog_file **ff, const char *fname, int mode);
+void fifolog_int_close(struct fifolog_file **ff);
+int fifolog_int_read(const struct fifolog_file *ff, off_t recno);
+const char *fifolog_int_findend(const struct fifolog_file *ff, off_t *last);
diff --git a/usr.sbin/fifolog/lib/miniobj.h b/usr.sbin/fifolog/lib/miniobj.h
new file mode 100644
index 0000000..dd79be1
--- /dev/null
+++ b/usr.sbin/fifolog/lib/miniobj.h
@@ -0,0 +1,74 @@
+/*-
+ * Copyright (c) 2005-2008 Poul-Henning Kamp
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+#define ALLOC_OBJ(to, type_magic) \
+ do { \
+ (to) = calloc(sizeof *(to), 1); \
+ if ((to) != NULL) \
+ (to)->magic = (type_magic); \
+ } while (0)
+
+#define FREE_OBJ(to) \
+ do { \
+ (to)->magic = (0); \
+ free(to); \
+ } while (0)
+
+#define VALID_OBJ(ptr, type_magic) \
+ ((ptr) != NULL && (ptr)->magic == (type_magic))
+
+#define CHECK_OBJ(ptr, type_magic) \
+ do { \
+ assert((ptr)->magic == type_magic); \
+ } while (0)
+
+#define CHECK_OBJ_NOTNULL(ptr, type_magic) \
+ do { \
+ assert((ptr) != NULL); \
+ assert((ptr)->magic == type_magic); \
+ } while (0)
+
+#define CHECK_OBJ_ORNULL(ptr, type_magic) \
+ do { \
+ if ((ptr) != NULL) \
+ assert((ptr)->magic == type_magic); \
+ } while (0)
+
+#define CAST_OBJ(to, from, type_magic) \
+ do { \
+ (to) = (from); \
+ if ((to) != NULL) \
+ CHECK_OBJ((to), (type_magic)); \
+ } while (0)
+
+#define CAST_OBJ_NOTNULL(to, from, type_magic) \
+ do { \
+ (to) = (from); \
+ assert((to) != NULL); \
+ CHECK_OBJ((to), (type_magic)); \
+ } while (0)
diff --git a/usr.sbin/flowctl/Makefile b/usr.sbin/flowctl/Makefile
new file mode 100644
index 0000000..8bd6389
--- /dev/null
+++ b/usr.sbin/flowctl/Makefile
@@ -0,0 +1,20 @@
+#
+# $FreeBSD$
+#
+
+.include <src.opts.mk>
+
+PROG= flowctl
+MAN= flowctl.8
+
+WARNS?= 2
+LIBADD= netgraph
+
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+= -DINET6
+.endif
+.if ${MK_INET_SUPPORT} != "no"
+CFLAGS+= -DINET
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/flowctl/Makefile.depend b/usr.sbin/flowctl/Makefile.depend
new file mode 100644
index 0000000..4fa00ad
--- /dev/null
+++ b/usr.sbin/flowctl/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libnetgraph \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/flowctl/flowctl.8 b/usr.sbin/flowctl/flowctl.8
new file mode 100644
index 0000000..a8a1ce1
--- /dev/null
+++ b/usr.sbin/flowctl/flowctl.8
@@ -0,0 +1,90 @@
+.\" Copyright (c) 2004-2005 Gleb Smirnoff <glebius@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 June 8, 2012
+.Dt FLOWCTL 8
+.Os
+.Sh NAME
+.Nm flowctl
+.Nd
+.Xr ng_netflow 4
+control utility
+.Sh SYNOPSIS
+.Nm
+.Op Fl d Ar level
+.Ar path command
+.Sh DESCRIPTION
+The
+.Nm
+utility is intended to control the
+.Xr ng_netflow 4
+nodes.
+It has a single option:
+.Bl -tag -width ".Fl d Ar level"
+.It Fl d Ar level
+Set the
+.Xr netgraph 3
+debugging level to
+.Ar level .
+.El
+.Sh COMMANDS
+Currently,
+.Nm
+supports only one command.
+.Bl -tag -width ".Cm show"
+.It Cm show Oo Cm ipv4|ipv6 Oc Op Cm human|verbose
+This command is the analog of the
+.Dq "show ip cache flow"
+command of a Cisco router.
+It dumps the contents of the flow cache in Cisco-like format.
+Specifying either
+.Cm ipv4
+or
+.Cm ipv6
+would extract only IPv4 or IPv6 flows respectively.
+It has optional parameter
+.Cm verbose ,
+which is analog of the
+.Dq "show ip cache verbose flow"
+command. Additionally,
+.Cm human
+parameter can be specify to show selected flows in human-readable format.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr netgraph 3 ,
+.Xr ng_netflow 4
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility was written by
+.An Gleb Smirnoff Aq Mt glebius@FreeBSD.org ,
+based on
+.Nm ipacctctl
+written by
+.An Roman V. Palagin Aq Mt romanp@unshadow.net .
diff --git a/usr.sbin/flowctl/flowctl.c b/usr.sbin/flowctl/flowctl.c
new file mode 100644
index 0000000..0c7539a
--- /dev/null
+++ b/usr.sbin/flowctl/flowctl.c
@@ -0,0 +1,426 @@
+/*-
+ * Copyright (c) 2004-2005 Gleb Smirnoff <glebius@FreeBSD.org>
+ * Copyright (c) 2001-2003 Roman V. Palagin <romanp@unshadow.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 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.
+ *
+ * $SourceForge: flowctl.c,v 1.15 2004/08/31 20:24:58 glebius Exp $
+ */
+
+#ifndef lint
+static const char rcs_id[] =
+ "@(#) $FreeBSD$";
+#endif
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include <netgraph.h>
+#include <netgraph/netflow/ng_netflow.h>
+
+#define CISCO_SH_FLOW_HEADER "SrcIf SrcIPaddress " \
+"DstIf DstIPaddress Pr SrcP DstP Pkts\n"
+#define CISCO_SH_FLOW "%-13s %-15s %-13s %-15s %2u %4.4x %4.4x %6lu\n"
+
+/* human-readable IPv4 header */
+#define CISCO_SH_FLOW_HHEADER "SrcIf SrcIPaddress " \
+"DstIf DstIPaddress Proto SrcPort DstPort Pkts\n"
+#define CISCO_SH_FLOW_H "%-13s %-15s %-13s %-15s %5u %8d %8d %8lu\n"
+
+#define CISCO_SH_FLOW6_HEADER "SrcIf SrcIPaddress " \
+"DstIf DstIPaddress Pr SrcP DstP Pkts\n"
+#define CISCO_SH_FLOW6 "%-13s %-30s %-13s %-30s %2u %4.4x %4.4x %6lu\n"
+
+/* Human-readable IPv6 headers */
+#define CISCO_SH_FLOW6_HHEADER "SrcIf SrcIPaddress " \
+"DstIf DstIPaddress Proto SrcPort DstPort Pkts\n"
+#define CISCO_SH_FLOW6_H "%-13s %-36s %-13s %-36s %5u %8d %8d %8lu\n"
+
+#define CISCO_SH_VERB_FLOW_HEADER "SrcIf SrcIPaddress " \
+"DstIf DstIPaddress Pr TOS Flgs Pkts\n" \
+"Port Msk AS Port Msk AS NextHop B/Pk Active\n"
+
+#define CISCO_SH_VERB_FLOW "%-14s %-15s %-14s %-15s %2u %3x %4x %6lu\n" \
+ "%4.4x /%-2u %-5u %4.4x /%-2u %-5u %-15s %9u %8u\n\n"
+
+#define CISCO_SH_VERB_FLOW6_HEADER "SrcIf SrcIPaddress " \
+"DstIf DstIPaddress Pr TOS Flgs Pkts\n" \
+"Port Msk AS Port Msk AS NextHop B/Pk Active\n"
+
+#define CISCO_SH_VERB_FLOW6 "%-14s %-30s %-14s %-30s %2u %3x %4x %6lu\n" \
+ "%4.4x /%-2u %-5u %4.4x /%-2u %-5u %-30s %9u %8u\n\n"
+#ifdef INET
+static void flow_cache_print(struct ngnf_show_header *resp);
+static void flow_cache_print_verbose(struct ngnf_show_header *resp);
+#endif
+#ifdef INET6
+static void flow_cache_print6(struct ngnf_show_header *resp);
+static void flow_cache_print6_verbose(struct ngnf_show_header *resp);
+#endif
+static void ctl_show(int, char **);
+#if defined(INET) || defined(INET6)
+static void do_show(int, void (*func)(struct ngnf_show_header *));
+#endif
+static void help(void);
+static void execute_command(int, char **);
+
+struct ip_ctl_cmd {
+ char *cmd_name;
+ void (*cmd_func)(int argc, char **argv);
+};
+
+struct ip_ctl_cmd cmds[] = {
+ {"show", ctl_show},
+ {NULL, NULL},
+};
+
+int cs, human = 0;
+char *ng_path;
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ char sname[NG_NODESIZ];
+ int rcvbuf = SORCVBUF_SIZE;
+
+ /* parse options */
+ while ((c = getopt(argc, argv, "d:")) != -1) {
+ switch (c) {
+ case 'd': /* set libnetgraph debug level. */
+ NgSetDebug(atoi(optarg));
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+ ng_path = argv[0];
+ if (ng_path == NULL || (strlen(ng_path) > NG_PATHSIZ))
+ help();
+ argc--;
+ argv++;
+
+ /* create control socket. */
+ snprintf(sname, sizeof(sname), "flowctl%i", getpid());
+
+ if (NgMkSockNode(sname, &cs, NULL) == -1)
+ err(1, "NgMkSockNode");
+
+ /* set receive buffer size */
+ if (setsockopt(cs, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(int)) == -1)
+ err(1, "setsockopt(SOL_SOCKET, SO_RCVBUF)");
+
+ /* parse and execute command */
+ execute_command(argc, argv);
+
+ close(cs);
+
+ exit(0);
+}
+
+static void
+execute_command(int argc, char **argv)
+{
+ int cindex = -1;
+ int i;
+
+ if (!argc)
+ help();
+ for (i = 0; cmds[i].cmd_name != NULL; i++)
+ if (!strncmp(argv[0], cmds[i].cmd_name, strlen(argv[0]))) {
+ if (cindex != -1)
+ errx(1, "ambiguous command: %s", argv[0]);
+ cindex = i;
+ }
+ if (cindex == -1)
+ errx(1, "bad command: %s", argv[0]);
+ argc--;
+ argv++;
+ (*cmds[cindex].cmd_func)(argc, argv);
+}
+
+static void
+ctl_show(int argc, char **argv)
+{
+ int ipv4, ipv6, verbose = 0;
+
+ ipv4 = feature_present("inet");
+ ipv6 = feature_present("inet6");
+
+ if (argc > 0 && !strncmp(argv[0], "ipv4", 4)) {
+ ipv6 = 0;
+ argc--;
+ argv++;
+ }
+ if (argc > 0 && !strncmp(argv[0], "ipv6", 4)) {
+ ipv4 = 0;
+ argc--;
+ argv++;
+ }
+
+ if (argc > 0 && !strncmp(argv[0], "verbose", strlen(argv[0])))
+ verbose = 1;
+
+ if (argc > 0 && !strncmp(argv[0], "human", strlen(argv[0])))
+ human = 1;
+
+#ifdef INET
+ if (ipv4) {
+ if (verbose)
+ do_show(4, &flow_cache_print_verbose);
+ else
+ do_show(4, &flow_cache_print);
+ }
+#endif
+
+#ifdef INET6
+ if (ipv6) {
+ if (verbose)
+ do_show(6, &flow_cache_print6_verbose);
+ else
+ do_show(6, &flow_cache_print6);
+ }
+#endif
+}
+
+#if defined(INET) || defined(INET6)
+static void
+do_show(int version, void (*func)(struct ngnf_show_header *))
+{
+ char buf[SORCVBUF_SIZE];
+ struct ng_mesg *ng_mesg;
+ struct ngnf_show_header req, *resp;
+ int token, nread;
+
+ ng_mesg = (struct ng_mesg *)buf;
+ req.version = version;
+ req.hash_id = req.list_id = 0;
+
+ for (;;) {
+ /* request set of accounting records */
+ token = NgSendMsg(cs, ng_path, NGM_NETFLOW_COOKIE,
+ NGM_NETFLOW_SHOW, (void *)&req, sizeof(req));
+ if (token == -1)
+ err(1, "NgSendMsg(NGM_NETFLOW_SHOW)");
+
+ /* read reply */
+ nread = NgRecvMsg(cs, ng_mesg, SORCVBUF_SIZE, NULL);
+ if (nread == -1)
+ err(1, "NgRecvMsg() failed");
+
+ if (ng_mesg->header.token != token)
+ err(1, "NgRecvMsg(NGM_NETFLOW_SHOW): token mismatch");
+
+ resp = (struct ngnf_show_header *)ng_mesg->data;
+ if ((ng_mesg->header.arglen < (sizeof(*resp))) ||
+ (ng_mesg->header.arglen < (sizeof(*resp) +
+ (resp->nentries * sizeof(struct flow_entry_data)))))
+ err(1, "NgRecvMsg(NGM_NETFLOW_SHOW): arglen too small");
+
+ (*func)(resp);
+
+ if (resp->hash_id != 0)
+ req.hash_id = resp->hash_id;
+ else
+ break;
+ req.list_id = resp->list_id;
+ }
+}
+#endif
+
+#ifdef INET
+static void
+flow_cache_print(struct ngnf_show_header *resp)
+{
+ struct flow_entry_data *fle;
+ char src[INET_ADDRSTRLEN], dst[INET_ADDRSTRLEN];
+ char src_if[IFNAMSIZ], dst_if[IFNAMSIZ];
+ int i;
+
+ if (resp->version != 4)
+ errx(EX_SOFTWARE, "%s: version mismatch: %u",
+ __func__, resp->version);
+
+ if (resp->nentries > 0)
+ printf(human ? CISCO_SH_FLOW_HHEADER : CISCO_SH_FLOW_HEADER);
+
+ fle = (struct flow_entry_data *)(resp + 1);
+ for (i = 0; i < resp->nentries; i++, fle++) {
+ inet_ntop(AF_INET, &fle->r.r_src, src, sizeof(src));
+ inet_ntop(AF_INET, &fle->r.r_dst, dst, sizeof(dst));
+ printf(human ? CISCO_SH_FLOW_H : CISCO_SH_FLOW,
+ if_indextoname(fle->fle_i_ifx, src_if),
+ src,
+ if_indextoname(fle->fle_o_ifx, dst_if),
+ dst,
+ fle->r.r_ip_p,
+ ntohs(fle->r.r_sport),
+ ntohs(fle->r.r_dport),
+ fle->packets);
+
+ }
+}
+#endif
+
+#ifdef INET6
+static void
+flow_cache_print6(struct ngnf_show_header *resp)
+{
+ struct flow6_entry_data *fle6;
+ char src6[INET6_ADDRSTRLEN], dst6[INET6_ADDRSTRLEN];
+ char src_if[IFNAMSIZ], dst_if[IFNAMSIZ];
+ int i;
+
+ if (resp->version != 6)
+ errx(EX_SOFTWARE, "%s: version mismatch: %u",
+ __func__, resp->version);
+
+ if (resp->nentries > 0)
+ printf(human ? CISCO_SH_FLOW6_HHEADER : CISCO_SH_FLOW6_HEADER);
+
+ fle6 = (struct flow6_entry_data *)(resp + 1);
+ for (i = 0; i < resp->nentries; i++, fle6++) {
+ inet_ntop(AF_INET6, &fle6->r.src.r_src6, src6, sizeof(src6));
+ inet_ntop(AF_INET6, &fle6->r.dst.r_dst6, dst6, sizeof(dst6));
+ printf(human ? CISCO_SH_FLOW6_H : CISCO_SH_FLOW6,
+ if_indextoname(fle6->fle_i_ifx, src_if),
+ src6,
+ if_indextoname(fle6->fle_o_ifx, dst_if),
+ dst6,
+ fle6->r.r_ip_p,
+ ntohs(fle6->r.r_sport),
+ ntohs(fle6->r.r_dport),
+ fle6->packets);
+
+ }
+}
+#endif
+
+#ifdef INET
+static void
+flow_cache_print_verbose(struct ngnf_show_header *resp)
+{
+ struct flow_entry_data *fle;
+ char src[INET_ADDRSTRLEN], dst[INET_ADDRSTRLEN], next[INET_ADDRSTRLEN];
+ char src_if[IFNAMSIZ], dst_if[IFNAMSIZ];
+ int i;
+
+ if (resp->version != 4)
+ errx(EX_SOFTWARE, "%s: version mismatch: %u",
+ __func__, resp->version);
+
+ printf(CISCO_SH_VERB_FLOW_HEADER);
+
+ fle = (struct flow_entry_data *)(resp + 1);
+ for (i = 0; i < resp->nentries; i++, fle++) {
+ inet_ntop(AF_INET, &fle->r.r_src, src, sizeof(src));
+ inet_ntop(AF_INET, &fle->r.r_dst, dst, sizeof(dst));
+ inet_ntop(AF_INET, &fle->next_hop, next, sizeof(next));
+ printf(CISCO_SH_VERB_FLOW,
+ if_indextoname(fle->fle_i_ifx, src_if),
+ src,
+ if_indextoname(fle->fle_o_ifx, dst_if),
+ dst,
+ fle->r.r_ip_p,
+ fle->r.r_tos,
+ fle->tcp_flags,
+ fle->packets,
+ ntohs(fle->r.r_sport),
+ fle->src_mask,
+ 0,
+ ntohs(fle->r.r_dport),
+ fle->dst_mask,
+ 0,
+ next,
+ (u_int)(fle->bytes / fle->packets),
+ 0);
+
+ }
+}
+#endif
+
+#ifdef INET6
+static void
+flow_cache_print6_verbose(struct ngnf_show_header *resp)
+{
+ struct flow6_entry_data *fle6;
+ char src6[INET6_ADDRSTRLEN], dst6[INET6_ADDRSTRLEN], next6[INET6_ADDRSTRLEN];
+ char src_if[IFNAMSIZ], dst_if[IFNAMSIZ];
+ int i;
+
+ if (resp->version != 6)
+ errx(EX_SOFTWARE, "%s: version mismatch: %u",
+ __func__, resp->version);
+
+ printf(CISCO_SH_VERB_FLOW6_HEADER);
+
+ fle6 = (struct flow6_entry_data *)(resp + 1);
+ for (i = 0; i < resp->nentries; i++, fle6++) {
+ inet_ntop(AF_INET6, &fle6->r.src.r_src6, src6, sizeof(src6));
+ inet_ntop(AF_INET6, &fle6->r.dst.r_dst6, dst6, sizeof(dst6));
+ inet_ntop(AF_INET6, &fle6->n.next_hop6, next6, sizeof(next6));
+ printf(CISCO_SH_VERB_FLOW6,
+ if_indextoname(fle6->fle_i_ifx, src_if),
+ src6,
+ if_indextoname(fle6->fle_o_ifx, dst_if),
+ dst6,
+ fle6->r.r_ip_p,
+ fle6->r.r_tos,
+ fle6->tcp_flags,
+ fle6->packets,
+ ntohs(fle6->r.r_sport),
+ fle6->src_mask,
+ 0,
+ ntohs(fle6->r.r_dport),
+ fle6->dst_mask,
+ 0,
+ next6,
+ (u_int)(fle6->bytes / fle6->packets),
+ 0);
+ }
+}
+#endif
+
+static void
+help(void)
+{
+ extern char *__progname;
+
+ fprintf(stderr, "usage: %s [-d level] nodename command\n", __progname);
+ exit (0);
+}
diff --git a/usr.sbin/fmtree/Makefile b/usr.sbin/fmtree/Makefile
new file mode 100644
index 0000000..6d060e2
--- /dev/null
+++ b/usr.sbin/fmtree/Makefile
@@ -0,0 +1,21 @@
+# From: @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+.include <bsd.own.mk>
+
+.PATH: ${.CURDIR}/../../usr.bin/cksum
+
+PROG= fmtree
+MAN= fmtree.8
+SRCS= compare.c crc.c create.c excludes.c misc.c mtree.c spec.c verify.c
+SRCS+= specspec.c
+
+CFLAGS+= -DMD5 -DSHA1 -DRMD160 -DSHA256
+LIBADD= md
+
+CLEANFILES+= fmtree.8
+
+fmtree.8: mtree.8
+ ${CP} ${.ALLSRC} ${.TARGET}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/fmtree/Makefile.depend b/usr.sbin/fmtree/Makefile.depend
new file mode 100644
index 0000000..064e492
--- /dev/null
+++ b/usr.sbin/fmtree/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libmd \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/fmtree/compare.c b/usr.sbin/fmtree/compare.c
new file mode 100644
index 0000000..fdd3767
--- /dev/null
+++ b/usr.sbin/fmtree/compare.c
@@ -0,0 +1,388 @@
+/*-
+ * 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.
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)compare.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fts.h>
+#ifdef MD5
+#include <md5.h>
+#endif
+#ifdef RMD160
+#include <ripemd.h>
+#endif
+#ifdef SHA1
+#include <sha.h>
+#endif
+#ifdef SHA256
+#include <sha256.h>
+#endif
+#include <stdint.h>
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+#include <vis.h>
+
+#include "mtree.h"
+#include "extern.h"
+
+#define INDENTNAMELEN 8
+#define LABEL \
+ if (!label++) { \
+ len = printf("%s changed\n", RP(p)); \
+ tab = "\t"; \
+ }
+
+int
+compare(char *name __unused, NODE *s, FTSENT *p)
+{
+ struct timeval tv[2];
+ uint32_t val;
+ int fd, label;
+ off_t len;
+ char *cp;
+ const char *tab = "";
+ char *fflags;
+
+ label = 0;
+ switch(s->type) {
+ case F_BLOCK:
+ if (!S_ISBLK(p->fts_statp->st_mode))
+ goto typeerr;
+ break;
+ case F_CHAR:
+ if (!S_ISCHR(p->fts_statp->st_mode))
+ goto typeerr;
+ break;
+ case F_DIR:
+ if (!S_ISDIR(p->fts_statp->st_mode))
+ goto typeerr;
+ break;
+ case F_FIFO:
+ if (!S_ISFIFO(p->fts_statp->st_mode))
+ goto typeerr;
+ break;
+ case F_FILE:
+ if (!S_ISREG(p->fts_statp->st_mode))
+ goto typeerr;
+ break;
+ case F_LINK:
+ if (!S_ISLNK(p->fts_statp->st_mode))
+ goto typeerr;
+ break;
+ case F_SOCK:
+ if (!S_ISSOCK(p->fts_statp->st_mode)) {
+typeerr: LABEL;
+ (void)printf("\ttype expected %s found %s\n",
+ ftype(s->type), inotype(p->fts_statp->st_mode));
+ return (label);
+ }
+ break;
+ }
+ /* Set the uid/gid first, then set the mode. */
+ if (s->flags & (F_UID | F_UNAME) && s->st_uid != p->fts_statp->st_uid) {
+ LABEL;
+ (void)printf("%suser expected %lu found %lu",
+ tab, (u_long)s->st_uid, (u_long)p->fts_statp->st_uid);
+ if (uflag)
+ if (chown(p->fts_accpath, s->st_uid, -1))
+ (void)printf(" not modified: %s\n",
+ strerror(errno));
+ else
+ (void)printf(" modified\n");
+ else
+ (void)printf("\n");
+ tab = "\t";
+ }
+ if (s->flags & (F_GID | F_GNAME) && s->st_gid != p->fts_statp->st_gid) {
+ LABEL;
+ (void)printf("%sgid expected %lu found %lu",
+ tab, (u_long)s->st_gid, (u_long)p->fts_statp->st_gid);
+ if (uflag)
+ if (chown(p->fts_accpath, -1, s->st_gid))
+ (void)printf(" not modified: %s\n",
+ strerror(errno));
+ else
+ (void)printf(" modified\n");
+ else
+ (void)printf("\n");
+ tab = "\t";
+ }
+ if (s->flags & F_MODE &&
+ !S_ISLNK(p->fts_statp->st_mode) &&
+ s->st_mode != (p->fts_statp->st_mode & MBITS)) {
+ LABEL;
+ (void)printf("%spermissions expected %#o found %#o",
+ tab, s->st_mode, p->fts_statp->st_mode & MBITS);
+ if (uflag)
+ if (chmod(p->fts_accpath, s->st_mode))
+ (void)printf(" not modified: %s\n",
+ strerror(errno));
+ else
+ (void)printf(" modified\n");
+ else
+ (void)printf("\n");
+ tab = "\t";
+ }
+ if (s->flags & F_NLINK && s->type != F_DIR &&
+ s->st_nlink != p->fts_statp->st_nlink) {
+ LABEL;
+ (void)printf("%slink_count expected %u found %u\n",
+ tab, s->st_nlink, p->fts_statp->st_nlink);
+ tab = "\t";
+ }
+ if (s->flags & F_SIZE && s->st_size != p->fts_statp->st_size &&
+ !S_ISDIR(p->fts_statp->st_mode)) {
+ LABEL;
+ (void)printf("%ssize expected %jd found %jd\n", tab,
+ (intmax_t)s->st_size, (intmax_t)p->fts_statp->st_size);
+ tab = "\t";
+ }
+ /*
+ * XXX
+ * Catches nano-second differences, but doesn't display them.
+ */
+ if ((s->flags & F_TIME) &&
+ ((s->st_mtimespec.tv_sec != p->fts_statp->st_mtim.tv_sec) ||
+ (s->st_mtimespec.tv_nsec != p->fts_statp->st_mtim.tv_nsec))) {
+ LABEL;
+ (void)printf("%smodification time expected %.24s ",
+ tab, ctime(&s->st_mtimespec.tv_sec));
+ (void)printf("found %.24s",
+ ctime(&p->fts_statp->st_mtim.tv_sec));
+ if (uflag) {
+ tv[0].tv_sec = s->st_mtimespec.tv_sec;
+ tv[0].tv_usec = s->st_mtimespec.tv_nsec / 1000;
+ tv[1] = tv[0];
+ if (utimes(p->fts_accpath, tv))
+ (void)printf(" not modified: %s\n",
+ strerror(errno));
+ else
+ (void)printf(" modified\n");
+ } else
+ (void)printf("\n");
+ tab = "\t";
+ }
+ if (s->flags & F_CKSUM) {
+ if ((fd = open(p->fts_accpath, O_RDONLY, 0)) < 0) {
+ LABEL;
+ (void)printf("%scksum: %s: %s\n",
+ tab, p->fts_accpath, strerror(errno));
+ tab = "\t";
+ } else if (crc(fd, &val, &len)) {
+ (void)close(fd);
+ LABEL;
+ (void)printf("%scksum: %s: %s\n",
+ tab, p->fts_accpath, strerror(errno));
+ tab = "\t";
+ } else {
+ (void)close(fd);
+ if (s->cksum != val) {
+ LABEL;
+ (void)printf("%scksum expected %lu found %lu\n",
+ tab, s->cksum, (unsigned long)val);
+ tab = "\t";
+ }
+ }
+ }
+ if ((s->flags & F_FLAGS) && s->st_flags != p->fts_statp->st_flags) {
+ LABEL;
+ fflags = flags_to_string(s->st_flags);
+ (void)printf("%sflags expected \"%s\"", tab, fflags);
+ free(fflags);
+
+ fflags = flags_to_string(p->fts_statp->st_flags);
+ (void)printf(" found \"%s\"", fflags);
+ free(fflags);
+
+ if (uflag)
+ if (chflags(p->fts_accpath, s->st_flags))
+ (void)printf(" not modified: %s\n",
+ strerror(errno));
+ else
+ (void)printf(" modified\n");
+ else
+ (void)printf("\n");
+ tab = "\t";
+ }
+#ifdef MD5
+ if (s->flags & F_MD5) {
+ char *new_digest, buf[33];
+
+ new_digest = MD5File(p->fts_accpath, buf);
+ if (!new_digest) {
+ LABEL;
+ printf("%sMD5: %s: %s\n", tab, p->fts_accpath,
+ strerror(errno));
+ tab = "\t";
+ } else if (strcmp(new_digest, s->md5digest)) {
+ LABEL;
+ printf("%sMD5 expected %s found %s\n", tab, s->md5digest,
+ new_digest);
+ tab = "\t";
+ }
+ }
+#endif /* MD5 */
+#ifdef SHA1
+ if (s->flags & F_SHA1) {
+ char *new_digest, buf[41];
+
+ new_digest = SHA1_File(p->fts_accpath, buf);
+ if (!new_digest) {
+ LABEL;
+ printf("%sSHA-1: %s: %s\n", tab, p->fts_accpath,
+ strerror(errno));
+ tab = "\t";
+ } else if (strcmp(new_digest, s->sha1digest)) {
+ LABEL;
+ printf("%sSHA-1 expected %s found %s\n",
+ tab, s->sha1digest, new_digest);
+ tab = "\t";
+ }
+ }
+#endif /* SHA1 */
+#ifdef RMD160
+ if (s->flags & F_RMD160) {
+ char *new_digest, buf[41];
+
+ new_digest = RIPEMD160_File(p->fts_accpath, buf);
+ if (!new_digest) {
+ LABEL;
+ printf("%sRIPEMD160: %s: %s\n", tab,
+ p->fts_accpath, strerror(errno));
+ tab = "\t";
+ } else if (strcmp(new_digest, s->rmd160digest)) {
+ LABEL;
+ printf("%sRIPEMD160 expected %s found %s\n",
+ tab, s->rmd160digest, new_digest);
+ tab = "\t";
+ }
+ }
+#endif /* RMD160 */
+#ifdef SHA256
+ if (s->flags & F_SHA256) {
+ char *new_digest, buf[65];
+
+ new_digest = SHA256_File(p->fts_accpath, buf);
+ if (!new_digest) {
+ LABEL;
+ printf("%sSHA-256: %s: %s\n", tab, p->fts_accpath,
+ strerror(errno));
+ tab = "\t";
+ } else if (strcmp(new_digest, s->sha256digest)) {
+ LABEL;
+ printf("%sSHA-256 expected %s found %s\n",
+ tab, s->sha256digest, new_digest);
+ tab = "\t";
+ }
+ }
+#endif /* SHA256 */
+
+ if (s->flags & F_SLINK &&
+ strcmp(cp = rlink(p->fts_accpath), s->slink)) {
+ LABEL;
+ (void)printf("%slink_ref expected %s found %s\n",
+ tab, s->slink, cp);
+ }
+ return (label);
+}
+
+const char *
+inotype(u_int type)
+{
+ switch(type & S_IFMT) {
+ case S_IFBLK:
+ return ("block");
+ case S_IFCHR:
+ return ("char");
+ case S_IFDIR:
+ return ("dir");
+ case S_IFIFO:
+ return ("fifo");
+ case S_IFREG:
+ return ("file");
+ case S_IFLNK:
+ return ("link");
+ case S_IFSOCK:
+ return ("socket");
+ default:
+ return ("unknown");
+ }
+ /* NOTREACHED */
+}
+
+const char *
+ftype(u_int type)
+{
+ switch(type) {
+ case F_BLOCK:
+ return ("block");
+ case F_CHAR:
+ return ("char");
+ case F_DIR:
+ return ("dir");
+ case F_FIFO:
+ return ("fifo");
+ case F_FILE:
+ return ("file");
+ case F_LINK:
+ return ("link");
+ case F_SOCK:
+ return ("socket");
+ default:
+ return ("unknown");
+ }
+ /* NOTREACHED */
+}
+
+char *
+rlink(char *name)
+{
+ static char lbuf[MAXPATHLEN * 4];
+ int len;
+ char tbuf[MAXPATHLEN];
+
+ if ((len = readlink(name, tbuf, sizeof(tbuf) - 1)) == -1)
+ err(1, "line %d: %s", lineno, name);
+ tbuf[len] = '\0';
+ strvis(lbuf, tbuf, VIS_WHITE | VIS_OCTAL);
+ return (lbuf);
+}
diff --git a/usr.sbin/fmtree/create.c b/usr.sbin/fmtree/create.c
new file mode 100644
index 0000000..8be9b02
--- /dev/null
+++ b/usr.sbin/fmtree/create.c
@@ -0,0 +1,428 @@
+/*-
+ * 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.
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)create.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fts.h>
+#include <grp.h>
+#ifdef MD5
+#include <md5.h>
+#endif
+#ifdef SHA1
+#include <sha.h>
+#endif
+#ifdef RMD160
+#include <ripemd.h>
+#endif
+#ifdef SHA256
+#include <sha256.h>
+#endif
+#include <pwd.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+#include <vis.h>
+#include "mtree.h"
+#include "extern.h"
+
+#define INDENTNAMELEN 15
+#define MAXLINELEN 80
+
+static gid_t gid;
+static uid_t uid;
+static mode_t mode;
+static u_long flags = 0xffffffff;
+
+static int dsort(const FTSENT * const *, const FTSENT * const *);
+static void output(int, int *, const char *, ...) __printflike(3, 4);
+static int statd(FTS *, FTSENT *, uid_t *, gid_t *, mode_t *, u_long *);
+static void statf(int, FTSENT *);
+
+void
+cwalk(void)
+{
+ FTS *t;
+ FTSENT *p;
+ time_t cl;
+ char *argv[2], host[MAXHOSTNAMELEN];
+ char dot[] = ".";
+ int indent = 0;
+
+ if (!nflag) {
+ (void)time(&cl);
+ (void)gethostname(host, sizeof(host));
+ (void)printf(
+ "#\t user: %s\n#\tmachine: %s\n",
+ getlogin(), host);
+ (void)printf(
+ "#\t tree: %s\n#\t date: %s",
+ fullpath, ctime(&cl));
+ }
+
+ argv[0] = dot;
+ argv[1] = NULL;
+ if ((t = fts_open(argv, ftsoptions, dsort)) == NULL)
+ err(1, "fts_open()");
+ while ((p = fts_read(t))) {
+ if (iflag)
+ indent = p->fts_level * 4;
+ if (check_excludes(p->fts_name, p->fts_path)) {
+ fts_set(t, p, FTS_SKIP);
+ continue;
+ }
+ switch(p->fts_info) {
+ case FTS_D:
+ if (!dflag)
+ (void)printf("\n");
+ if (!nflag)
+ (void)printf("# %s\n", p->fts_path);
+ statd(t, p, &uid, &gid, &mode, &flags);
+ statf(indent, p);
+ break;
+ case FTS_DP:
+ if (!nflag && (p->fts_level > 0))
+ (void)printf("%*s# %s\n", indent, "", p->fts_path);
+ (void)printf("%*s..\n", indent, "");
+ if (!dflag)
+ (void)printf("\n");
+ break;
+ case FTS_DNR:
+ case FTS_ERR:
+ case FTS_NS:
+ warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
+ break;
+ default:
+ if (!dflag)
+ statf(indent, p);
+ break;
+
+ }
+ }
+ (void)fts_close(t);
+ if (sflag && keys & F_CKSUM)
+ warnx("%s checksum: %lu", fullpath, (unsigned long)crc_total);
+}
+
+static void
+statf(int indent, FTSENT *p)
+{
+ struct group *gr;
+ struct passwd *pw;
+ uint32_t val;
+ off_t len;
+ int fd, offset;
+ char *fflags;
+ char *escaped_name;
+
+ escaped_name = calloc(1, p->fts_namelen * 4 + 1);
+ if (escaped_name == NULL)
+ errx(1, "statf(): calloc() failed");
+ strvis(escaped_name, p->fts_name, VIS_WHITE | VIS_OCTAL | VIS_GLOB);
+
+ if (iflag || S_ISDIR(p->fts_statp->st_mode))
+ offset = printf("%*s%s", indent, "", escaped_name);
+ else
+ offset = printf("%*s %s", indent, "", escaped_name);
+
+ free(escaped_name);
+
+ if (offset > (INDENTNAMELEN + indent))
+ offset = MAXLINELEN;
+ else
+ offset += printf("%*s", (INDENTNAMELEN + indent) - offset, "");
+
+ if (!S_ISREG(p->fts_statp->st_mode) && !dflag)
+ output(indent, &offset, "type=%s", inotype(p->fts_statp->st_mode));
+ if (p->fts_statp->st_uid != uid) {
+ if (keys & F_UNAME) {
+ pw = getpwuid(p->fts_statp->st_uid);
+ if (pw != NULL)
+ output(indent, &offset, "uname=%s", pw->pw_name);
+ else if (wflag)
+ warnx("Could not get uname for uid=%u",
+ p->fts_statp->st_uid);
+ else
+ errx(1,
+ "Could not get uname for uid=%u",
+ p->fts_statp->st_uid);
+ }
+ if (keys & F_UID)
+ output(indent, &offset, "uid=%u", p->fts_statp->st_uid);
+ }
+ if (p->fts_statp->st_gid != gid) {
+ if (keys & F_GNAME) {
+ gr = getgrgid(p->fts_statp->st_gid);
+ if (gr != NULL)
+ output(indent, &offset, "gname=%s", gr->gr_name);
+ else if (wflag)
+ warnx("Could not get gname for gid=%u",
+ p->fts_statp->st_gid);
+ else
+ errx(1,
+ "Could not get gname for gid=%u",
+ p->fts_statp->st_gid);
+ }
+ if (keys & F_GID)
+ output(indent, &offset, "gid=%u", p->fts_statp->st_gid);
+ }
+ if (keys & F_MODE && (p->fts_statp->st_mode & MBITS) != mode)
+ output(indent, &offset, "mode=%#o", p->fts_statp->st_mode & MBITS);
+ if (keys & F_NLINK && p->fts_statp->st_nlink != 1)
+ output(indent, &offset, "nlink=%u", p->fts_statp->st_nlink);
+ if (keys & F_SIZE && S_ISREG(p->fts_statp->st_mode))
+ output(indent, &offset, "size=%jd",
+ (intmax_t)p->fts_statp->st_size);
+ if (keys & F_TIME)
+ output(indent, &offset, "time=%ld.%09ld",
+ (long)p->fts_statp->st_mtim.tv_sec,
+ p->fts_statp->st_mtim.tv_nsec);
+ if (keys & F_CKSUM && S_ISREG(p->fts_statp->st_mode)) {
+ if ((fd = open(p->fts_accpath, O_RDONLY, 0)) < 0 ||
+ crc(fd, &val, &len))
+ err(1, "%s", p->fts_accpath);
+ (void)close(fd);
+ output(indent, &offset, "cksum=%lu", (unsigned long)val);
+ }
+#ifdef MD5
+ if (keys & F_MD5 && S_ISREG(p->fts_statp->st_mode)) {
+ char *digest, buf[33];
+
+ digest = MD5File(p->fts_accpath, buf);
+ if (!digest)
+ err(1, "%s", p->fts_accpath);
+ output(indent, &offset, "md5digest=%s", digest);
+ }
+#endif /* MD5 */
+#ifdef SHA1
+ if (keys & F_SHA1 && S_ISREG(p->fts_statp->st_mode)) {
+ char *digest, buf[41];
+
+ digest = SHA1_File(p->fts_accpath, buf);
+ if (!digest)
+ err(1, "%s", p->fts_accpath);
+ output(indent, &offset, "sha1digest=%s", digest);
+ }
+#endif /* SHA1 */
+#ifdef RMD160
+ if (keys & F_RMD160 && S_ISREG(p->fts_statp->st_mode)) {
+ char *digest, buf[41];
+
+ digest = RIPEMD160_File(p->fts_accpath, buf);
+ if (!digest)
+ err(1, "%s", p->fts_accpath);
+ output(indent, &offset, "ripemd160digest=%s", digest);
+ }
+#endif /* RMD160 */
+#ifdef SHA256
+ if (keys & F_SHA256 && S_ISREG(p->fts_statp->st_mode)) {
+ char *digest, buf[65];
+
+ digest = SHA256_File(p->fts_accpath, buf);
+ if (!digest)
+ err(1, "%s", p->fts_accpath);
+ output(indent, &offset, "sha256digest=%s", digest);
+ }
+#endif /* SHA256 */
+ if (keys & F_SLINK &&
+ (p->fts_info == FTS_SL || p->fts_info == FTS_SLNONE))
+ output(indent, &offset, "link=%s", rlink(p->fts_accpath));
+ if (keys & F_FLAGS && p->fts_statp->st_flags != flags) {
+ fflags = flags_to_string(p->fts_statp->st_flags);
+ output(indent, &offset, "flags=%s", fflags);
+ free(fflags);
+ }
+ (void)putchar('\n');
+}
+
+#define MAXGID 5000
+#define MAXUID 5000
+#define MAXMODE MBITS + 1
+#define MAXFLAGS 256
+#define MAXS 16
+
+static int
+statd(FTS *t, FTSENT *parent, uid_t *puid, gid_t *pgid, mode_t *pmode, u_long *pflags)
+{
+ FTSENT *p;
+ gid_t sgid;
+ uid_t suid;
+ mode_t smode;
+ u_long sflags;
+ struct group *gr;
+ struct passwd *pw;
+ gid_t savegid = *pgid;
+ uid_t saveuid = *puid;
+ mode_t savemode = *pmode;
+ u_long saveflags = *pflags;
+ u_short maxgid, maxuid, maxmode, maxflags;
+ u_short g[MAXGID], u[MAXUID], m[MAXMODE], f[MAXFLAGS];
+ char *fflags;
+ static int first = 1;
+
+ if ((p = fts_children(t, 0)) == NULL) {
+ if (errno)
+ err(1, "%s", RP(parent));
+ return (1);
+ }
+
+ bzero(g, sizeof(g));
+ bzero(u, sizeof(u));
+ bzero(m, sizeof(m));
+ bzero(f, sizeof(f));
+
+ maxuid = maxgid = maxmode = maxflags = 0;
+ for (; p; p = p->fts_link) {
+ if (!dflag || (dflag && S_ISDIR(p->fts_statp->st_mode))) {
+ smode = p->fts_statp->st_mode & MBITS;
+ if (smode < MAXMODE && ++m[smode] > maxmode) {
+ savemode = smode;
+ maxmode = m[smode];
+ }
+ sgid = p->fts_statp->st_gid;
+ if (sgid < MAXGID && ++g[sgid] > maxgid) {
+ savegid = sgid;
+ maxgid = g[sgid];
+ }
+ suid = p->fts_statp->st_uid;
+ if (suid < MAXUID && ++u[suid] > maxuid) {
+ saveuid = suid;
+ maxuid = u[suid];
+ }
+
+ /*
+ * XXX
+ * note that the below will break when file flags
+ * are extended beyond the first 4 bytes of each
+ * half word of the flags
+ */
+#define FLAGS2IDX(f) ((f & 0xf) | ((f >> 12) & 0xf0))
+ sflags = p->fts_statp->st_flags;
+ if (FLAGS2IDX(sflags) < MAXFLAGS &&
+ ++f[FLAGS2IDX(sflags)] > maxflags) {
+ saveflags = sflags;
+ maxflags = f[FLAGS2IDX(sflags)];
+ }
+ }
+ }
+ /*
+ * If the /set record is the same as the last one we do not need to output
+ * a new one. So first we check to see if anything changed. Note that we
+ * always output a /set record for the first directory.
+ */
+ if ((((keys & F_UNAME) | (keys & F_UID)) && (*puid != saveuid)) ||
+ (((keys & F_GNAME) | (keys & F_GID)) && (*pgid != savegid)) ||
+ ((keys & F_MODE) && (*pmode != savemode)) ||
+ ((keys & F_FLAGS) && (*pflags != saveflags)) ||
+ (first)) {
+ first = 0;
+ if (dflag)
+ (void)printf("/set type=dir");
+ else
+ (void)printf("/set type=file");
+ if (keys & F_UNAME) {
+ pw = getpwuid(saveuid);
+ if (pw != NULL)
+ (void)printf(" uname=%s", pw->pw_name);
+ else if (wflag)
+ warnx( "Could not get uname for uid=%u", saveuid);
+ else
+ errx(1, "Could not get uname for uid=%u", saveuid);
+ }
+ if (keys & F_UID)
+ (void)printf(" uid=%lu", (u_long)saveuid);
+ if (keys & F_GNAME) {
+ gr = getgrgid(savegid);
+ if (gr != NULL)
+ (void)printf(" gname=%s", gr->gr_name);
+ else if (wflag)
+ warnx("Could not get gname for gid=%u", savegid);
+ else
+ errx(1, "Could not get gname for gid=%u", savegid);
+ }
+ if (keys & F_GID)
+ (void)printf(" gid=%lu", (u_long)savegid);
+ if (keys & F_MODE)
+ (void)printf(" mode=%#o", savemode);
+ if (keys & F_NLINK)
+ (void)printf(" nlink=1");
+ if (keys & F_FLAGS) {
+ fflags = flags_to_string(saveflags);
+ (void)printf(" flags=%s", fflags);
+ free(fflags);
+ }
+ (void)printf("\n");
+ *puid = saveuid;
+ *pgid = savegid;
+ *pmode = savemode;
+ *pflags = saveflags;
+ }
+ return (0);
+}
+
+static int
+dsort(const FTSENT * const *a, const FTSENT * const *b)
+{
+ if (S_ISDIR((*a)->fts_statp->st_mode)) {
+ if (!S_ISDIR((*b)->fts_statp->st_mode))
+ return (1);
+ } else if (S_ISDIR((*b)->fts_statp->st_mode))
+ return (-1);
+ return (strcmp((*a)->fts_name, (*b)->fts_name));
+}
+
+#include <stdarg.h>
+
+void
+output(int indent, int *offset, const char *fmt, ...)
+{
+ va_list ap;
+ char buf[1024];
+ va_start(ap, fmt);
+ (void)vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ if (*offset + strlen(buf) > MAXLINELEN - 3) {
+ (void)printf(" \\\n%*s", INDENTNAMELEN + indent, "");
+ *offset = INDENTNAMELEN + indent;
+ }
+ *offset += printf(" %s", buf) + 1;
+}
diff --git a/usr.sbin/fmtree/excludes.c b/usr.sbin/fmtree/excludes.c
new file mode 100644
index 0000000..21a49b0
--- /dev/null
+++ b/usr.sbin/fmtree/excludes.c
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2000 Massachusetts Institute of Technology
+ *
+ * Permission to use, copy, modify, and distribute this software and
+ * its documentation for any purpose and without fee is hereby
+ * granted, provided that both the above copyright notice and this
+ * permission notice appear in all copies, that both the above
+ * copyright notice and this permission notice appear in all
+ * supporting documentation, and that the name of M.I.T. not be used
+ * in advertising or publicity pertaining to distribution of the
+ * software without specific, written prior permission. M.I.T. makes
+ * no representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS
+ * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
+ * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (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> /* XXX for mtree.h */
+#include <sys/queue.h>
+
+#include <err.h>
+#include <fnmatch.h>
+#include <fts.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "mtree.h" /* XXX for extern.h */
+#include "extern.h"
+
+/*
+ * We're assuming that there won't be a whole lot of excludes,
+ * so it's OK to use a stupid algorithm.
+ */
+struct exclude {
+ LIST_ENTRY(exclude) link;
+ const char *glob;
+ int pathname;
+};
+static LIST_HEAD(, exclude) excludes;
+
+void
+init_excludes(void)
+{
+ LIST_INIT(&excludes);
+}
+
+void
+read_excludes_file(const char *name)
+{
+ FILE *fp;
+ char *line, *str;
+ struct exclude *e;
+ size_t len;
+
+ fp = fopen(name, "r");
+ if (fp == 0)
+ err(1, "%s", name);
+
+ while ((line = fgetln(fp, &len)) != 0) {
+ if (line[len - 1] == '\n')
+ len--;
+ if (len == 0)
+ continue;
+
+ str = malloc(len + 1);
+ e = malloc(sizeof *e);
+ if (str == 0 || e == 0)
+ errx(1, "memory allocation error");
+ e->glob = str;
+ memcpy(str, line, len);
+ str[len] = '\0';
+ if (strchr(str, '/'))
+ e->pathname = 1;
+ else
+ e->pathname = 0;
+ LIST_INSERT_HEAD(&excludes, e, link);
+ }
+ fclose(fp);
+}
+
+int
+check_excludes(const char *fname, const char *path)
+{
+ struct exclude *e;
+
+ /* fnmatch(3) has a funny return value convention... */
+#define MATCH(g, n) (fnmatch((g), (n), FNM_PATHNAME) == 0)
+
+ LIST_FOREACH(e, &excludes, link) {
+ if ((e->pathname && MATCH(e->glob, path))
+ || MATCH(e->glob, fname))
+ return 1;
+ }
+ return 0;
+}
diff --git a/usr.sbin/fmtree/extern.h b/usr.sbin/fmtree/extern.h
new file mode 100644
index 0000000..4b6fb3c
--- /dev/null
+++ b/usr.sbin/fmtree/extern.h
@@ -0,0 +1,59 @@
+/*-
+ * 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.
+ * 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.
+ *
+ * @(#)extern.h 8.1 (Berkeley) 6/6/93
+ * $FreeBSD$
+ */
+extern uint32_t crc_total;
+
+#ifdef _FTS_H_
+int compare(char *, NODE *, FTSENT *);
+#endif
+int crc(int, uint32_t *, off_t *);
+void cwalk(void);
+char *flags_to_string(u_long);
+
+const char *inotype(u_int);
+u_int parsekey(char *, int *);
+char *rlink(char *);
+NODE *mtree_readspec(FILE *fi);
+int mtree_verifyspec(FILE *fi);
+int mtree_specspec(FILE *fi, FILE *fj);
+
+int check_excludes(const char *, const char *);
+void init_excludes(void);
+void read_excludes_file(const char *);
+const char * ftype(u_int type);
+
+extern int ftsoptions;
+extern u_int keys;
+extern int lineno;
+extern int dflag, eflag, iflag, nflag, qflag, rflag, sflag, uflag, wflag;
+#ifdef MAXPATHLEN
+extern char fullpath[MAXPATHLEN];
+#endif
diff --git a/usr.sbin/fmtree/misc.c b/usr.sbin/fmtree/misc.c
new file mode 100644
index 0000000..65667d6
--- /dev/null
+++ b/usr.sbin/fmtree/misc.c
@@ -0,0 +1,124 @@
+/*-
+ * 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.
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)misc.c 8.1 (Berkeley) 6/6/93";
+#endif /*not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <err.h>
+#include <fts.h>
+#include <stdio.h>
+#include <unistd.h>
+#include "mtree.h"
+#include "extern.h"
+
+typedef struct _key {
+ const char *name; /* key name */
+ u_int val; /* value */
+
+#define NEEDVALUE 0x01
+ u_int flags;
+} KEY;
+
+/* NB: the following table must be sorted lexically. */
+static KEY keylist[] = {
+ {"cksum", F_CKSUM, NEEDVALUE},
+ {"flags", F_FLAGS, NEEDVALUE},
+ {"gid", F_GID, NEEDVALUE},
+ {"gname", F_GNAME, NEEDVALUE},
+ {"ignore", F_IGN, 0},
+ {"link", F_SLINK, NEEDVALUE},
+#ifdef MD5
+ {"md5digest", F_MD5, NEEDVALUE},
+#endif
+ {"mode", F_MODE, NEEDVALUE},
+ {"nlink", F_NLINK, NEEDVALUE},
+ {"nochange", F_NOCHANGE, 0},
+ {"optional", F_OPT, 0},
+#ifdef RMD160
+ {"ripemd160digest", F_RMD160, NEEDVALUE},
+#endif
+#ifdef SHA1
+ {"sha1digest", F_SHA1, NEEDVALUE},
+#endif
+#ifdef SHA256
+ {"sha256digest", F_SHA256, NEEDVALUE},
+#endif
+ {"size", F_SIZE, NEEDVALUE},
+ {"time", F_TIME, NEEDVALUE},
+ {"type", F_TYPE, NEEDVALUE},
+ {"uid", F_UID, NEEDVALUE},
+ {"uname", F_UNAME, NEEDVALUE},
+};
+
+int keycompare(const void *, const void *);
+
+u_int
+parsekey(char *name, int *needvaluep)
+{
+ KEY *k, tmp;
+
+ tmp.name = name;
+ k = (KEY *)bsearch(&tmp, keylist, sizeof(keylist) / sizeof(KEY),
+ sizeof(KEY), keycompare);
+ if (k == NULL)
+ errx(1, "line %d: unknown keyword %s", lineno, name);
+
+ if (needvaluep)
+ *needvaluep = k->flags & NEEDVALUE ? 1 : 0;
+ return (k->val);
+}
+
+int
+keycompare(const void *a, const void *b)
+{
+ return (strcmp(((const KEY *)a)->name, ((const KEY *)b)->name));
+}
+
+char *
+flags_to_string(u_long fflags)
+{
+ char *string;
+
+ string = fflagstostr(fflags);
+ if (string != NULL && *string == '\0') {
+ free(string);
+ string = strdup("none");
+ }
+ if (string == NULL)
+ err(1, NULL);
+
+ return string;
+}
diff --git a/usr.sbin/fmtree/mtree.8 b/usr.sbin/fmtree/mtree.8
new file mode 100644
index 0000000..fd073b9
--- /dev/null
+++ b/usr.sbin/fmtree/mtree.8
@@ -0,0 +1,401 @@
+.\" Copyright (c) 1989, 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.
+.\"
+.\" From: @(#)mtree.8 8.2 (Berkeley) 12/11/93
+.\" $FreeBSD$
+.\"
+.Dd June 16, 2007
+.Dt MTREE 8
+.Os
+.Sh NAME
+.Nm mtree
+.Nd map a directory hierarchy
+.Sh SYNOPSIS
+.Nm
+.Op Fl LPUcdeinqruxw
+.Bk -words
+.Op Fl f Ar spec
+.Ek
+.Bk -words
+.Op Fl K Ar keywords
+.Ek
+.Bk -words
+.Op Fl k Ar keywords
+.Ek
+.Bk -words
+.Op Fl p Ar path
+.Ek
+.Bk -words
+.Op Fl s Ar seed
+.Ek
+.Bk -words
+.Op Fl X Ar exclude-list
+.Ek
+.Sh DESCRIPTION
+The
+.Nm
+utility compares the file hierarchy rooted in the current directory against a
+specification read from the standard input.
+Messages are written to the standard output for any files whose
+characteristics do not match the specifications, or which are
+missing from either the file hierarchy or the specification.
+.Pp
+The options are as follows:
+.Bl -tag -width flag
+.It Fl L
+Follow all symbolic links in the file hierarchy.
+.It Fl P
+Do not follow symbolic links in the file hierarchy, instead consider
+the symbolic link itself in any comparisons.
+This is the default.
+.It Fl U
+Modify the owner, group, permissions, and modification time of existing
+files to match the specification and create any missing directories or
+symbolic links.
+User, group and permissions must all be specified for missing directories
+to be created.
+Corrected mismatches are not considered errors.
+.It Fl c
+Print a specification for the file hierarchy to the standard output.
+.It Fl d
+Ignore everything except directory type files.
+.It Fl e
+Do not complain about files that are in the file hierarchy, but not in the
+specification.
+.It Fl i
+Indent the output 4 spaces each time a directory level is descended when
+creating a specification with the
+.Fl c
+option.
+This does not affect either the /set statements or the comment before each
+directory.
+It does however affect the comment before the close of each directory.
+.It Fl n
+Do not emit pathname comments when creating a specification.
+Normally
+a comment is emitted before each directory and before the close of that
+directory when using the
+.Fl c
+option.
+.It Fl q
+Quiet mode.
+Do not complain when a
+.Dq missing
+directory cannot be created because it already exists.
+This occurs when the directory is a symbolic link.
+.It Fl r
+Remove any files in the file hierarchy that are not described in the
+specification.
+.It Fl u
+Same as
+.Fl U
+except a status of 2 is returned if the file hierarchy did not match
+the specification.
+.It Fl w
+Make some errors non-fatal warnings.
+.It Fl x
+Do not descend below mount points in the file hierarchy.
+.It Fl f Ar file
+Read the specification from
+.Ar file ,
+instead of from the standard input.
+.Pp
+If this option is specified twice, the two specifications are compared
+to each other rather than to the file hierarchy.
+The specifications will be sorted like output generated using
+.Fl c .
+The output format in this case is somewhat remniscent of
+.Xr comm 1 ,
+having "in first spec only", "in second spec only", and "different"
+columns, prefixed by zero, one and two TAB characters respectively.
+Each entry in the "different" column occupies two lines, one from each specification.
+.It Fl K Ar keywords
+Add the specified (whitespace or comma separated)
+.Ar keywords
+to the current set of keywords.
+.It Fl k Ar keywords
+Use the ``type'' keyword plus the specified (whitespace or comma separated)
+.Ar keywords
+instead of the current set of keywords.
+.It Fl p Ar path
+Use the file hierarchy rooted in
+.Ar path ,
+instead of the current directory.
+.It Fl s Ar seed
+Display a single checksum to the standard error output that represents all
+of the files for which the keyword
+.Cm cksum
+was specified.
+The checksum is seeded with the specified value.
+.It Fl X Ar exclude-list
+The specified file contains
+.Xr fnmatch 3
+patterns matching files to be excluded from
+the specification, one to a line.
+If the pattern contains a
+.Ql \&/
+character, it will be matched against entire pathnames (relative to
+the starting directory); otherwise,
+it will be matched against basenames only.
+No comments are allowed in
+the
+.Ar exclude-list
+file.
+.El
+.Pp
+Specifications are mostly composed of ``keywords'', i.e., strings
+that specify values relating to files.
+No keywords have default values, and if a keyword has no value set, no
+checks based on it are performed.
+.Pp
+Currently supported keywords are as follows:
+.Bl -tag -width Cm
+.It Cm cksum
+The checksum of the file using the default algorithm specified by
+the
+.Xr cksum 1
+utility.
+.It Cm flags
+The file flags as a symbolic name.
+See
+.Xr chflags 1
+for information on these names.
+If no flags are to be set the string
+.Dq none
+may be used to override the current default.
+.It Cm ignore
+Ignore any file hierarchy below this file.
+.It Cm gid
+The file group as a numeric value.
+.It Cm gname
+The file group as a symbolic name.
+.It Cm md5digest
+The MD5 message digest of the file.
+.It Cm sha1digest
+The
+.Tn FIPS
+160-1
+.Pq Dq Tn SHA-1
+message digest of the file.
+.It Cm sha256digest
+The
+.Tn FIPS
+180-2
+.Pq Dq Tn SHA-256
+message digest of the file.
+.It Cm ripemd160digest
+The
+.Tn RIPEMD160
+message digest of the file.
+.It Cm mode
+The current file's permissions as a numeric (octal) or symbolic
+value.
+.It Cm nlink
+The number of hard links the file is expected to have.
+.It Cm nochange
+Make sure this file or directory exists but otherwise ignore all attributes.
+.It Cm optional
+The file is optional; do not complain about the file if it is
+not in the file hierarchy.
+.It Cm uid
+The file owner as a numeric value.
+.It Cm uname
+The file owner as a symbolic name.
+.It Cm size
+The size, in bytes, of the file.
+.It Cm link
+The file the symbolic link is expected to reference.
+.It Cm time
+The last modification time of the file, in seconds and nanoseconds.
+The value should include a period character and exactly nine digits
+after the period.
+.It Cm type
+The type of the file; may be set to any one of the following:
+.Pp
+.Bl -tag -width Cm -compact
+.It Cm block
+block special device
+.It Cm char
+character special device
+.It Cm dir
+directory
+.It Cm fifo
+fifo
+.It Cm file
+regular file
+.It Cm link
+symbolic link
+.It Cm socket
+socket
+.El
+.El
+.Pp
+The default set of keywords are
+.Cm flags ,
+.Cm gid ,
+.Cm link ,
+.Cm mode ,
+.Cm nlink ,
+.Cm size ,
+.Cm time ,
+and
+.Cm uid .
+.Pp
+There are four types of lines in a specification.
+.Pp
+The first type of line sets a global value for a keyword, and consists of
+the string ``/set'' followed by whitespace, followed by sets of keyword/value
+pairs, separated by whitespace.
+Keyword/value pairs consist of a keyword, followed by an equals sign
+(``=''), followed by a value, without whitespace characters.
+Once a keyword has been set, its value remains unchanged until either
+reset or unset.
+.Pp
+The second type of line unsets keywords and consists of the string
+``/unset'', followed by whitespace, followed by one or more keywords,
+separated by whitespace.
+.Pp
+The third type of line is a file specification and consists of a file
+name, followed by whitespace, followed by zero or more whitespace
+separated keyword/value pairs.
+The file name may be preceded by whitespace characters.
+The file name may contain any of the standard file name matching
+characters (``['', ``]'', ``?'' or ``*''), in which case files
+in the hierarchy will be associated with the first pattern that
+they match.
+.Pp
+Each of the keyword/value pairs consist of a keyword, followed by an
+equals sign (``=''), followed by the keyword's value, without
+whitespace characters.
+These values override, without changing, the global value of the
+corresponding keyword.
+.Pp
+All paths are relative.
+Specifying a directory will cause subsequent files to be searched
+for in that directory hierarchy.
+Which brings us to the last type of line in a specification: a line
+containing only the string
+.Dq Pa ..\&
+causes the current directory
+path to ascend one level.
+.Pp
+Empty lines and lines whose first non-whitespace character is a hash
+mark (``#'') are ignored.
+.Pp
+The
+.Nm
+utility exits with a status of 0 on success, 1 if any error occurred,
+and 2 if the file hierarchy did not match the specification.
+A status of 2 is converted to a status of 0 if the
+.Fl U
+option is used.
+.Sh FILES
+.Bl -tag -width /etc/mtree -compact
+.It Pa /etc/mtree
+system specification directory
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+To detect system binaries that have been ``trojan horsed'', it is recommended
+that
+.Nm
+.Fl K
+.Cm sha256digest
+be run on the file systems, and a copy of the results stored on a different
+machine, or, at least, in encrypted form.
+The output file itself should be digested using the
+.Xr sha256 1
+utility.
+Then, periodically,
+.Nm
+and
+.Xr sha256 1
+should be run against the on-line specifications.
+While it is possible for the bad guys to change the on-line specifications
+to conform to their modified binaries, it is believed to be
+impractical for them to create a modified specification which has
+the same SHA-256 digest as the original.
+.Pp
+The
+.Fl d
+and
+.Fl u
+options can be used in combination to create directory hierarchies
+for distributions and other such things; the files in
+.Pa /etc/mtree
+were used to create almost all directories in this
+.Fx
+distribution.
+.Pp
+To create an
+.Pa /etc/mtree
+style BSD.*.dist file, use
+.Nm
+.Fl c
+.Fl d
+.Fl i
+.Fl n
+.Fl k
+.Cm uname,gname,mode,nochange.
+.Sh SEE ALSO
+.Xr chflags 1 ,
+.Xr chgrp 1 ,
+.Xr chmod 1 ,
+.Xr cksum 1 ,
+.Xr md5 1 ,
+.Xr stat 2 ,
+.Xr fts 3 ,
+.Xr md5 3 ,
+.Xr chown 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.3 Reno .
+The
+.Tn MD5
+digest capability was added in
+.Fx 2.1 ,
+in response to the widespread use of programs which can spoof
+.Xr cksum 1 .
+The
+.Tn SHA-1
+and
+.Tn RIPEMD160
+digests were added in
+.Fx 4.0 ,
+as new attacks have demonstrated weaknesses in
+.Tn MD5 .
+The
+.Tn SHA-256
+digest was added in
+.Fx 6.0 .
+Support for file flags was added in
+.Fx 4.0 ,
+and mostly comes from
+.Nx .
diff --git a/usr.sbin/fmtree/mtree.c b/usr.sbin/fmtree/mtree.c
new file mode 100644
index 0000000..e90a5bb
--- /dev/null
+++ b/usr.sbin/fmtree/mtree.c
@@ -0,0 +1,191 @@
+/*-
+ * Copyright (c) 1989, 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.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1989, 1990, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)mtree.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <err.h>
+#include <errno.h>
+#include <fts.h>
+#include <stdio.h>
+#include <unistd.h>
+#include "mtree.h"
+#include "extern.h"
+
+int ftsoptions = FTS_PHYSICAL;
+int dflag, eflag, iflag, nflag, qflag, rflag, sflag, uflag, wflag;
+static int cflag, Uflag;
+u_int keys;
+char fullpath[MAXPATHLEN];
+
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ char *dir, *p;
+ int status;
+ FILE *spec1, *spec2;
+
+ dir = NULL;
+ keys = KEYDEFAULT;
+ init_excludes();
+ spec1 = stdin;
+ spec2 = NULL;
+
+ while ((ch = getopt(argc, argv, "cdef:iK:k:LnPp:qrs:UuwxX:")) != -1)
+ switch((char)ch) {
+ case 'c':
+ cflag = 1;
+ break;
+ case 'd':
+ dflag = 1;
+ break;
+ case 'e':
+ eflag = 1;
+ break;
+ case 'f':
+ if (spec1 == stdin) {
+ spec1 = fopen(optarg, "r");
+ if (spec1 == NULL)
+ err(1, "%s", optarg);
+ } else if (spec2 == NULL) {
+ spec2 = fopen(optarg, "r");
+ if (spec2 == NULL)
+ err(1, "%s", optarg);
+ } else
+ usage();
+ break;
+ case 'i':
+ iflag = 1;
+ break;
+ case 'K':
+ while ((p = strsep(&optarg, " \t,")) != NULL)
+ if (*p != '\0')
+ keys |= parsekey(p, NULL);
+ break;
+ case 'k':
+ keys = F_TYPE;
+ while ((p = strsep(&optarg, " \t,")) != NULL)
+ if (*p != '\0')
+ keys |= parsekey(p, NULL);
+ break;
+ case 'L':
+ ftsoptions &= ~FTS_PHYSICAL;
+ ftsoptions |= FTS_LOGICAL;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'P':
+ ftsoptions &= ~FTS_LOGICAL;
+ ftsoptions |= FTS_PHYSICAL;
+ break;
+ case 'p':
+ dir = optarg;
+ break;
+ case 'q':
+ qflag = 1;
+ break;
+ case 'r':
+ rflag = 1;
+ break;
+ case 's':
+ sflag = 1;
+ crc_total = ~strtoul(optarg, &p, 0);
+ if (*p)
+ errx(1, "illegal seed value -- %s", optarg);
+ break;
+ case 'U':
+ Uflag = 1;
+ uflag = 1;
+ break;
+ case 'u':
+ uflag = 1;
+ break;
+ case 'w':
+ wflag = 1;
+ break;
+ case 'x':
+ ftsoptions |= FTS_XDEV;
+ break;
+ case 'X':
+ read_excludes_file(optarg);
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc)
+ usage();
+
+ if (dir && chdir(dir))
+ err(1, "%s", dir);
+
+ if ((cflag || sflag) && !getcwd(fullpath, sizeof(fullpath)))
+ errx(1, "%s", fullpath);
+
+ if (cflag) {
+ cwalk();
+ exit(0);
+ }
+ if (spec2 != NULL)
+ status = mtree_specspec(spec1, spec2);
+ else
+ status = mtree_verifyspec(spec1);
+ if (Uflag & (status == MISMATCHEXIT))
+ status = 0;
+ exit(status);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+"usage: mtree [-LPUcdeinqruxw] [-f spec] [-f spec] [-K key] [-k key] [-p path] [-s seed]\n"
+"\t[-X excludes]\n");
+ exit(1);
+}
diff --git a/usr.sbin/fmtree/mtree.h b/usr.sbin/fmtree/mtree.h
new file mode 100644
index 0000000..fb22f0d
--- /dev/null
+++ b/usr.sbin/fmtree/mtree.h
@@ -0,0 +1,98 @@
+/*-
+ * 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.
+ *
+ * @(#)mtree.h 8.1 (Berkeley) 6/6/93
+ * $FreeBSD$
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#define KEYDEFAULT \
+ (F_GID | F_MODE | F_NLINK | F_SIZE | F_SLINK | F_TIME | F_UID | F_FLAGS)
+
+#define MISMATCHEXIT 2
+
+typedef struct _node {
+ struct _node *parent, *child; /* up, down */
+ struct _node *prev, *next; /* left, right */
+ off_t st_size; /* size */
+ struct timespec st_mtimespec; /* last modification time */
+ u_long cksum; /* check sum */
+ char *md5digest; /* MD5 digest */
+ char *sha1digest; /* SHA-1 digest */
+ char *sha256digest; /* SHA-256 digest */
+ char *rmd160digest; /* RIPEMD160 digest */
+ char *slink; /* symbolic link reference */
+ uid_t st_uid; /* uid */
+ gid_t st_gid; /* gid */
+#define MBITS (S_ISUID|S_ISGID|S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO)
+ mode_t st_mode; /* mode */
+ u_long st_flags; /* flags */
+ nlink_t st_nlink; /* link count */
+
+#define F_CKSUM 0x0001 /* check sum */
+#define F_DONE 0x0002 /* directory done */
+#define F_GID 0x0004 /* gid */
+#define F_GNAME 0x0008 /* group name */
+#define F_IGN 0x0010 /* ignore */
+#define F_MAGIC 0x0020 /* name has magic chars */
+#define F_MODE 0x0040 /* mode */
+#define F_NLINK 0x0080 /* number of links */
+#define F_SIZE 0x0100 /* size */
+#define F_SLINK 0x0200 /* link count */
+#define F_TIME 0x0400 /* modification time */
+#define F_TYPE 0x0800 /* file type */
+#define F_UID 0x1000 /* uid */
+#define F_UNAME 0x2000 /* user name */
+#define F_VISIT 0x4000 /* file visited */
+#define F_MD5 0x8000 /* MD5 digest */
+#define F_NOCHANGE 0x10000 /* If owner/mode "wrong", do */
+ /* not change */
+#define F_SHA1 0x20000 /* SHA-1 digest */
+#define F_RMD160 0x40000 /* RIPEMD160 digest */
+#define F_FLAGS 0x80000 /* file flags */
+#define F_SHA256 0x100000 /* SHA-256 digest */
+#define F_OPT 0x200000 /* existence optional */
+ u_int flags; /* items set */
+
+#define F_BLOCK 0x001 /* block special */
+#define F_CHAR 0x002 /* char special */
+#define F_DIR 0x004 /* directory */
+#define F_FIFO 0x008 /* fifo */
+#define F_FILE 0x010 /* regular file */
+#define F_LINK 0x020 /* symbolic link */
+#define F_SOCK 0x040 /* socket */
+ u_char type; /* file type */
+
+ char name[1]; /* file name (must be last) */
+} NODE;
+
+#define RP(p) \
+ ((p)->fts_path[0] == '.' && (p)->fts_path[1] == '/' ? \
+ (p)->fts_path + 2 : (p)->fts_path)
diff --git a/usr.sbin/fmtree/spec.c b/usr.sbin/fmtree/spec.c
new file mode 100644
index 0000000..417932d
--- /dev/null
+++ b/usr.sbin/fmtree/spec.c
@@ -0,0 +1,323 @@
+/*-
+ * 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.
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)spec.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fts.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <vis.h>
+#include "mtree.h"
+#include "extern.h"
+
+int lineno; /* Current spec line number. */
+
+static void set(char *, NODE *);
+static void unset(char *, NODE *);
+
+NODE *
+mtree_readspec(FILE *fi)
+{
+ NODE *centry, *last;
+ char *p;
+ NODE ginfo, *root;
+ int c_cur, c_next;
+ char buf[2048];
+
+ centry = last = root = NULL;
+ bzero(&ginfo, sizeof(ginfo));
+ c_cur = c_next = 0;
+ for (lineno = 1; fgets(buf, sizeof(buf), fi);
+ ++lineno, c_cur = c_next, c_next = 0) {
+ /* Skip empty lines. */
+ if (buf[0] == '\n')
+ continue;
+
+ /* Find end of line. */
+ if ((p = strchr(buf, '\n')) == NULL)
+ errx(1, "line %d too long", lineno);
+
+ /* See if next line is continuation line. */
+ if (p[-1] == '\\') {
+ --p;
+ c_next = 1;
+ }
+
+ /* Null-terminate the line. */
+ *p = '\0';
+
+ /* Skip leading whitespace. */
+ for (p = buf; *p && isspace(*p); ++p);
+
+ /* If nothing but whitespace or comment char, continue. */
+ if (!*p || *p == '#')
+ continue;
+
+#ifdef DEBUG
+ (void)fprintf(stderr, "line %d: {%s}\n", lineno, p);
+#endif
+ if (c_cur) {
+ set(p, centry);
+ continue;
+ }
+
+ /* Grab file name, "$", "set", or "unset". */
+ if ((p = strtok(p, "\n\t ")) == NULL)
+ errx(1, "line %d: missing field", lineno);
+
+ if (p[0] == '/')
+ switch(p[1]) {
+ case 's':
+ if (strcmp(p + 1, "set"))
+ break;
+ set(NULL, &ginfo);
+ continue;
+ case 'u':
+ if (strcmp(p + 1, "unset"))
+ break;
+ unset(NULL, &ginfo);
+ continue;
+ }
+
+ if (strchr(p, '/'))
+ errx(1, "line %d: slash character in file name",
+ lineno);
+
+ if (!strcmp(p, "..")) {
+ /* Don't go up, if haven't gone down. */
+ if (!root)
+ goto noparent;
+ if (last->type != F_DIR || last->flags & F_DONE) {
+ if (last == root)
+ goto noparent;
+ last = last->parent;
+ }
+ last->flags |= F_DONE;
+ continue;
+
+noparent: errx(1, "line %d: no parent node", lineno);
+ }
+
+ if ((centry = calloc(1, sizeof(NODE) + strlen(p))) == NULL)
+ errx(1, "calloc");
+ *centry = ginfo;
+#define MAGIC "?*["
+ if (strpbrk(p, MAGIC))
+ centry->flags |= F_MAGIC;
+ if (strunvis(centry->name, p) == -1)
+ errx(1, "filename %s is ill-encoded", p);
+ set(NULL, centry);
+
+ if (!root) {
+ last = root = centry;
+ root->parent = root;
+ } else if (last->type == F_DIR && !(last->flags & F_DONE)) {
+ centry->parent = last;
+ last = last->child = centry;
+ } else {
+ centry->parent = last->parent;
+ centry->prev = last;
+ last = last->next = centry;
+ }
+ }
+ return (root);
+}
+
+static void
+set(char *t, NODE *ip)
+{
+ int type;
+ char *kw, *val = NULL;
+ struct group *gr;
+ struct passwd *pw;
+ mode_t *m;
+ int value;
+ char *ep;
+
+ for (; (kw = strtok(t, "= \t\n")); t = NULL) {
+ ip->flags |= type = parsekey(kw, &value);
+ if (value && (val = strtok(NULL, " \t\n")) == NULL)
+ errx(1, "line %d: missing value", lineno);
+ switch(type) {
+ case F_CKSUM:
+ ip->cksum = strtoul(val, &ep, 10);
+ if (*ep)
+ errx(1, "line %d: invalid checksum %s",
+ lineno, val);
+ break;
+ case F_MD5:
+ ip->md5digest = strdup(val);
+ if(!ip->md5digest)
+ errx(1, "strdup");
+ break;
+ case F_SHA1:
+ ip->sha1digest = strdup(val);
+ if(!ip->sha1digest)
+ errx(1, "strdup");
+ break;
+ case F_SHA256:
+ ip->sha256digest = strdup(val);
+ if(!ip->sha256digest)
+ errx(1, "strdup");
+ break;
+ case F_RMD160:
+ ip->rmd160digest = strdup(val);
+ if(!ip->rmd160digest)
+ errx(1, "strdup");
+ break;
+ case F_FLAGS:
+ if (strcmp("none", val) == 0)
+ ip->st_flags = 0;
+ else if (strtofflags(&val, &ip->st_flags, NULL) != 0)
+ errx(1, "line %d: invalid flag %s",lineno, val);
+ break;
+ case F_GID:
+ ip->st_gid = strtoul(val, &ep, 10);
+ if (*ep)
+ errx(1, "line %d: invalid gid %s", lineno, val);
+ break;
+ case F_GNAME:
+ if ((gr = getgrnam(val)) == NULL)
+ errx(1, "line %d: unknown group %s", lineno, val);
+ ip->st_gid = gr->gr_gid;
+ break;
+ case F_IGN:
+ /* just set flag bit */
+ break;
+ case F_MODE:
+ if ((m = setmode(val)) == NULL)
+ errx(1, "line %d: invalid file mode %s",
+ lineno, val);
+ ip->st_mode = getmode(m, 0);
+ free(m);
+ break;
+ case F_NLINK:
+ ip->st_nlink = strtoul(val, &ep, 10);
+ if (*ep)
+ errx(1, "line %d: invalid link count %s",
+ lineno, val);
+ break;
+ case F_OPT:
+ /* just set flag bit */
+ break;
+ case F_SIZE:
+ ip->st_size = strtoq(val, &ep, 10);
+ if (*ep)
+ errx(1, "line %d: invalid size %s",
+ lineno, val);
+ break;
+ case F_SLINK:
+ ip->slink = malloc(strlen(val) + 1);
+ if (ip->slink == NULL)
+ errx(1, "malloc");
+ if (strunvis(ip->slink, val) == -1)
+ errx(1, "symlink %s is ill-encoded", val);
+ break;
+ case F_TIME:
+ ip->st_mtimespec.tv_sec = strtoul(val, &ep, 10);
+ if (*ep == '.') {
+ /* Note: we require exactly nine
+ * digits after the decimal point. */
+ val = ep + 1;
+ ip->st_mtimespec.tv_nsec
+ = strtoul(val, &ep, 10);
+ } else
+ ip->st_mtimespec.tv_nsec = 0;
+ if (*ep)
+ errx(1, "line %d: invalid time %s",
+ lineno, val);
+ break;
+ case F_TYPE:
+ switch(*val) {
+ case 'b':
+ if (!strcmp(val, "block"))
+ ip->type = F_BLOCK;
+ break;
+ case 'c':
+ if (!strcmp(val, "char"))
+ ip->type = F_CHAR;
+ break;
+ case 'd':
+ if (!strcmp(val, "dir"))
+ ip->type = F_DIR;
+ break;
+ case 'f':
+ if (!strcmp(val, "file"))
+ ip->type = F_FILE;
+ if (!strcmp(val, "fifo"))
+ ip->type = F_FIFO;
+ break;
+ case 'l':
+ if (!strcmp(val, "link"))
+ ip->type = F_LINK;
+ break;
+ case 's':
+ if (!strcmp(val, "socket"))
+ ip->type = F_SOCK;
+ break;
+ default:
+ errx(1, "line %d: unknown file type %s",
+ lineno, val);
+ }
+ break;
+ case F_UID:
+ ip->st_uid = strtoul(val, &ep, 10);
+ if (*ep)
+ errx(1, "line %d: invalid uid %s", lineno, val);
+ break;
+ case F_UNAME:
+ if ((pw = getpwnam(val)) == NULL)
+ errx(1, "line %d: unknown user %s", lineno, val);
+ ip->st_uid = pw->pw_uid;
+ break;
+ }
+ }
+}
+
+static void
+unset(char *t, NODE *ip)
+{
+ char *p;
+
+ while ((p = strtok(t, "\n\t ")))
+ ip->flags &= ~parsekey(p, NULL);
+}
diff --git a/usr.sbin/fmtree/specspec.c b/usr.sbin/fmtree/specspec.c
new file mode 100644
index 0000000..f85882e
--- /dev/null
+++ b/usr.sbin/fmtree/specspec.c
@@ -0,0 +1,256 @@
+/*-
+ * Copyright (c) 2003 Poul-Henning Kamp
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include "mtree.h"
+#include "extern.h"
+
+#define FF(a, b, c, d) \
+ (((a)->flags & (c)) && ((b)->flags & (c)) && ((a)->d) != ((b)->d))
+#define FS(a, b, c, d) \
+ (((a)->flags & (c)) && ((b)->flags & (c)) && strcmp((a)->d,(b)->d))
+#define FM(a, b, c, d) \
+ (((a)->flags & (c)) && ((b)->flags & (c)) && memcmp(&(a)->d,&(b)->d, sizeof (a)->d))
+
+static void
+shownode(NODE *n, int f, char const *path)
+{
+ struct group *gr;
+ struct passwd *pw;
+
+ printf("%s%s %s", path, n->name, ftype(n->type));
+ if (f & F_CKSUM)
+ printf(" cksum=%lu", n->cksum);
+ if (f & F_GID)
+ printf(" gid=%d", n->st_gid);
+ if (f & F_GNAME) {
+ gr = getgrgid(n->st_gid);
+ if (gr == NULL)
+ printf(" gid=%d", n->st_gid);
+ else
+ printf(" gname=%s", gr->gr_name);
+ }
+ if (f & F_MODE)
+ printf(" mode=%o", n->st_mode);
+ if (f & F_NLINK)
+ printf(" nlink=%d", n->st_nlink);
+ if (f & F_SIZE)
+ printf(" size=%jd", (intmax_t)n->st_size);
+ if (f & F_UID)
+ printf(" uid=%d", n->st_uid);
+ if (f & F_UNAME) {
+ pw = getpwuid(n->st_uid);
+ if (pw == NULL)
+ printf(" uid=%d", n->st_uid);
+ else
+ printf(" uname=%s", pw->pw_name);
+ }
+ if (f & F_MD5)
+ printf(" md5digest=%s", n->md5digest);
+ if (f & F_SHA1)
+ printf(" sha1digest=%s", n->sha1digest);
+ if (f & F_RMD160)
+ printf(" rmd160digest=%s", n->rmd160digest);
+ if (f & F_SHA256)
+ printf(" sha256digest=%s", n->sha256digest);
+ if (f & F_FLAGS)
+ printf(" flags=%s", flags_to_string(n->st_flags));
+ printf("\n");
+}
+
+static int
+mismatch(NODE *n1, NODE *n2, int differ, char const *path)
+{
+
+ if (n2 == NULL) {
+ shownode(n1, differ, path);
+ return (1);
+ }
+ if (n1 == NULL) {
+ printf("\t");
+ shownode(n2, differ, path);
+ return (1);
+ }
+ if (!(differ & keys))
+ return(0);
+ printf("\t\t");
+ shownode(n1, differ, path);
+ printf("\t\t");
+ shownode(n2, differ, path);
+ return (1);
+}
+
+static int
+compare_nodes(NODE *n1, NODE *n2, char const *path)
+{
+ int differs;
+
+ if (n1 != NULL && n1->type == F_LINK)
+ n1->flags &= ~F_MODE;
+ if (n2 != NULL && n2->type == F_LINK)
+ n2->flags &= ~F_MODE;
+ differs = 0;
+ if (n1 == NULL && n2 != NULL) {
+ differs = n2->flags;
+ mismatch(n1, n2, differs, path);
+ return (1);
+ }
+ if (n1 != NULL && n2 == NULL) {
+ differs = n1->flags;
+ mismatch(n1, n2, differs, path);
+ return (1);
+ }
+ if (n1->type != n2->type) {
+ differs = 0;
+ mismatch(n1, n2, differs, path);
+ return (1);
+ }
+ if (FF(n1, n2, F_CKSUM, cksum))
+ differs |= F_CKSUM;
+ if (FF(n1, n2, F_GID, st_gid))
+ differs |= F_GID;
+ if (FF(n1, n2, F_GNAME, st_gid))
+ differs |= F_GNAME;
+ if (FF(n1, n2, F_MODE, st_mode))
+ differs |= F_MODE;
+ if (FF(n1, n2, F_NLINK, st_nlink))
+ differs |= F_NLINK;
+ if (FF(n1, n2, F_SIZE, st_size))
+ differs |= F_SIZE;
+ if (FS(n1, n2, F_SLINK, slink))
+ differs |= F_SLINK;
+ if (FM(n1, n2, F_TIME, st_mtimespec))
+ differs |= F_TIME;
+ if (FF(n1, n2, F_UID, st_uid))
+ differs |= F_UID;
+ if (FF(n1, n2, F_UNAME, st_uid))
+ differs |= F_UNAME;
+ if (FS(n1, n2, F_MD5, md5digest))
+ differs |= F_MD5;
+ if (FS(n1, n2, F_SHA1, sha1digest))
+ differs |= F_SHA1;
+ if (FS(n1, n2, F_RMD160, rmd160digest))
+ differs |= F_RMD160;
+ if (FS(n1, n2, F_SHA256, sha256digest))
+ differs |= F_SHA256;
+ if (FF(n1, n2, F_FLAGS, st_flags))
+ differs |= F_FLAGS;
+ if (differs) {
+ mismatch(n1, n2, differs, path);
+ return (1);
+ }
+ return (0);
+}
+static int
+walk_in_the_forest(NODE *t1, NODE *t2, char const *path)
+{
+ int r, i;
+ NODE *c1, *c2, *n1, *n2;
+ char *np;
+
+ r = 0;
+
+ if (t1 != NULL)
+ c1 = t1->child;
+ else
+ c1 = NULL;
+ if (t2 != NULL)
+ c2 = t2->child;
+ else
+ c2 = NULL;
+ while (c1 != NULL || c2 != NULL) {
+ n1 = n2 = NULL;
+ if (c1 != NULL)
+ n1 = c1->next;
+ if (c2 != NULL)
+ n2 = c2->next;
+ if (c1 != NULL && c2 != NULL) {
+ if (c1->type != F_DIR && c2->type == F_DIR) {
+ n2 = c2;
+ c2 = NULL;
+ } else if (c1->type == F_DIR && c2->type != F_DIR) {
+ n1 = c1;
+ c1 = NULL;
+ } else {
+ i = strcmp(c1->name, c2->name);
+ if (i > 0) {
+ n1 = c1;
+ c1 = NULL;
+ } else if (i < 0) {
+ n2 = c2;
+ c2 = NULL;
+ }
+ }
+ }
+ if (c1 == NULL && c2->type == F_DIR) {
+ asprintf(&np, "%s%s/", path, c2->name);
+ i = walk_in_the_forest(c1, c2, np);
+ free(np);
+ i += compare_nodes(c1, c2, path);
+ } else if (c2 == NULL && c1->type == F_DIR) {
+ asprintf(&np, "%s%s/", path, c1->name);
+ i = walk_in_the_forest(c1, c2, np);
+ free(np);
+ i += compare_nodes(c1, c2, path);
+ } else if (c1 == NULL || c2 == NULL) {
+ i = compare_nodes(c1, c2, path);
+ } else if (c1->type == F_DIR && c2->type == F_DIR) {
+ asprintf(&np, "%s%s/", path, c1->name);
+ i = walk_in_the_forest(c1, c2, np);
+ free(np);
+ i += compare_nodes(c1, c2, path);
+ } else {
+ i = compare_nodes(c1, c2, path);
+ }
+ r += i;
+ c1 = n1;
+ c2 = n2;
+ }
+ return (r);
+}
+
+int
+mtree_specspec(FILE *fi, FILE *fj)
+{
+ int rval;
+ NODE *root1, *root2;
+
+ root1 = mtree_readspec(fi);
+ root2 = mtree_readspec(fj);
+ rval = walk_in_the_forest(root1, root2, "");
+ rval += compare_nodes(root1, root2, "");
+ if (rval > 0)
+ return (MISMATCHEXIT);
+ return (0);
+}
diff --git a/usr.sbin/fmtree/test/test00.sh b/usr.sbin/fmtree/test/test00.sh
new file mode 100644
index 0000000..fce801b
--- /dev/null
+++ b/usr.sbin/fmtree/test/test00.sh
@@ -0,0 +1,67 @@
+#!/bin/sh
+#
+# Copyright (c) 2003 Poul-Henning Kamp
+# All rights reserved.
+#
+# Please see src/share/examples/etc/bsd-style-copyright.
+#
+# $FreeBSD$
+#
+
+set -e
+
+TMP=/tmp/mtree.$$
+
+rm -rf ${TMP}
+mkdir -p ${TMP} ${TMP}/mr ${TMP}/mt
+
+
+mkdir ${TMP}/mt/foo
+mkdir ${TMP}/mr/\*
+mtree -c -p ${TMP}/mr | mtree -U -r -p ${TMP}/mt > /dev/null 2>&1
+if [ -d ${TMP}/mt/foo ] ; then
+ echo "ERROR Mtree create fell for filename with '*' char" 1>&2
+ rm -rf ${TMP}
+ exit 1
+fi
+rmdir ${TMP}/mr/\*
+
+mkdir -p ${TMP}/mt/foo
+mkdir ${TMP}/mr/\[f\]oo
+mtree -c -p ${TMP}/mr | mtree -U -r -p ${TMP}/mt > /dev/null 2>&1
+if [ -d ${TMP}/mt/foo ] ; then
+ echo "ERROR Mtree create fell for filename with '[' char" 1>&2
+ rm -rf ${TMP}
+ exit 1
+fi
+rmdir ${TMP}/mr/\[f\]oo
+
+mkdir -p ${TMP}/mt/foo
+mkdir ${TMP}/mr/\?oo
+mtree -c -p ${TMP}/mr | mtree -U -r -p ${TMP}/mt > /dev/null 2>&1
+if [ -d ${TMP}/mt/foo ] ; then
+ echo "ERROR Mtree create fell for filename with '?' char" 1>&2
+ rm -rf ${TMP}
+ exit 1
+fi
+rmdir ${TMP}/mr/\?oo
+
+mkdir ${TMP}/mr/\#
+mtree -c -p ${TMP}/mr > ${TMP}/_
+if mtree -U -r -p ${TMP}/mt < ${TMP}/_ > /dev/null 2>&1 ; then
+ true
+else
+ echo "ERROR Mtree create fell for filename with '#' char" 1>&2
+ rm -rf ${TMP}
+ exit 1
+fi
+
+if [ ! -d ${TMP}/mt/\# ] ; then
+ echo "ERROR Mtree update failed to create name with '#' char" 1>&2
+ rm -rf ${TMP}
+ exit 1
+fi
+rmdir ${TMP}/mr/\#
+
+rm -rf ${TMP}
+exit 0
diff --git a/usr.sbin/fmtree/test/test01.sh b/usr.sbin/fmtree/test/test01.sh
new file mode 100644
index 0000000..e4d3fc3
--- /dev/null
+++ b/usr.sbin/fmtree/test/test01.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+#
+# Copyright (c) 2003 Poul-Henning Kamp
+# All rights reserved.
+#
+# Please see src/share/examples/etc/bsd-style-copyright.
+#
+# $FreeBSD$
+#
+
+set -e
+
+TMP=/tmp/mtree.$$
+
+rm -rf ${TMP}
+mkdir -p ${TMP} ${TMP}/mr ${TMP}/mt
+
+
+ln -s "xx this=is=wrong" ${TMP}/mr/foo
+mtree -c -p ${TMP}/mr > ${TMP}/_
+
+if mtree -U -r -p ${TMP}/mt < ${TMP}/_ > /dev/null 2>&1 ; then
+ true
+else
+ echo "ERROR Mtree failed on symlink with space char" 1>&2
+ rm -rf ${TMP}
+ exit 1
+fi
+
+x=x`(cd ${TMP}/mr ; ls -l foo 2>&1) || true`
+y=x`(cd ${TMP}/mt ; ls -l foo 2>&1) || true`
+
+if [ "$x" != "$y" ] ; then
+ echo "ERROR Recreation of spaced symlink failed" 1>&2
+ rm -rf ${TMP}
+ exit 1
+fi
+
+rm -rf ${TMP}
+exit 0
diff --git a/usr.sbin/fmtree/test/test02.sh b/usr.sbin/fmtree/test/test02.sh
new file mode 100644
index 0000000..a7aa916
--- /dev/null
+++ b/usr.sbin/fmtree/test/test02.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# Copyright (c) 2003 Dan Nelson
+# All rights reserved.
+#
+# Please see src/share/examples/etc/bsd-style-copyright.
+#
+# $FreeBSD$
+#
+
+set -e
+
+TMP=/tmp/mtree.$$
+
+rm -rf ${TMP}
+mkdir -p ${TMP} ${TMP}/mr ${TMP}/mt
+
+touch -t 199901020304 ${TMP}/mr/oldfile
+touch ${TMP}/mt/oldfile
+
+mtree -c -p ${TMP}/mr > ${TMP}/_
+
+mtree -U -r -p ${TMP}/mt < ${TMP}/_ > /dev/null
+
+x=x`(cd ${TMP}/mr ; ls -l 2>&1) || true`
+y=x`(cd ${TMP}/mt ; ls -l 2>&1) || true`
+
+if [ "$x" != "$y" ] ; then
+ echo "ERROR Update of mtime failed" 1>&2
+ rm -rf ${TMP}
+ exit 1
+fi
+
+rm -rf ${TMP}
+exit 0
+
diff --git a/usr.sbin/fmtree/test/test03.sh b/usr.sbin/fmtree/test/test03.sh
new file mode 100644
index 0000000..bb3a5b5
--- /dev/null
+++ b/usr.sbin/fmtree/test/test03.sh
@@ -0,0 +1,60 @@
+#!/bin/sh
+#
+# Copyright (c) 2003 Poul-Henning Kamp
+# All rights reserved.
+#
+# Please see src/share/examples/etc/bsd-style-copyright.
+#
+# $FreeBSD$
+#
+
+set -e
+
+TMP=/tmp/mtree.$$
+
+rm -rf ${TMP}
+mkdir -p ${TMP}
+
+K=uid,uname,gid,gname,flags,md5digest,size,ripemd160digest,sha1digest,sha256digest,cksum
+
+rm -rf _FOO
+mkdir _FOO
+touch _FOO/_uid
+touch _FOO/_size
+touch _FOO/zztype
+
+touch _FOO/_bar
+mtree -c -K $K -p .. > ${TMP}/_r
+mtree -c -K $K -p .. > ${TMP}/_r2
+rm -rf _FOO/_bar
+
+rm -rf _FOO/zztype
+mkdir _FOO/zztype
+
+date > _FOO/_size
+
+chown nobody _FOO/_uid
+
+touch _FOO/_foo
+mtree -c -K $K -p .. > ${TMP}/_t
+
+rm -fr _FOO
+
+if mtree -f ${TMP}/_r -f ${TMP}/_r2 ; then
+ true
+else
+ echo "ERROR Compare identical failed" 1>&2
+ exit 1
+fi
+
+if mtree -f ${TMP}/_r -f ${TMP}/_t > ${TMP}/_ ; then
+ echo "ERROR Compare different succeeded" 1>&2
+ exit 1
+fi
+
+if [ `wc -l < ${TMP}/_` -ne 10 ] ; then
+ echo "ERROR wrong number of lines: `wc -l ${TMP}/_`" 1>&2
+ exit 1
+fi
+
+exit 0
diff --git a/usr.sbin/fmtree/test/test04.sh b/usr.sbin/fmtree/test/test04.sh
new file mode 100644
index 0000000..38acda2
--- /dev/null
+++ b/usr.sbin/fmtree/test/test04.sh
@@ -0,0 +1,51 @@
+#!/bin/sh
+#
+# Copyright (c) 2003 Dan Nelson
+# All rights reserved.
+#
+# Please see src/share/examples/etc/bsd-style-copyright.
+#
+# $FreeBSD$
+#
+
+set -e
+
+TMP=/tmp/mtree.$$
+
+rm -rf ${TMP}
+mkdir -p ${TMP} ${TMP}/mr ${TMP}/mt
+
+mkdir ${TMP}/mr/a
+mkdir ${TMP}/mr/b
+mkdir ${TMP}/mt/a
+mkdir ${TMP}/mt/b
+touch ${TMP}/mt/z
+
+mtree -c -p ${TMP}/mr > ${TMP}/_r
+mtree -c -p ${TMP}/mt > ${TMP}/_t
+
+if mtree -f ${TMP}/_r -f ${TMP}/_t > ${TMP}/_ ; then
+ echo "ERROR wrong exit on difference" 1>&2
+ exit 1
+fi
+
+if [ `wc -l < ${TMP}/_` -ne 1 ] ; then
+ echo "ERROR spec/spec compare generated wrong output" 1>&2
+ rm -rf ${TMP}
+ exit 1
+fi
+
+if mtree -f ${TMP}/_t -f ${TMP}/_r > ${TMP}/_ ; then
+ echo "ERROR wrong exit on difference" 1>&2
+ exit 1
+fi
+
+if [ `wc -l < ${TMP}/_` -ne 1 ] ; then
+ echo "ERROR spec/spec compare generated wrong output" 1>&2
+ rm -rf ${TMP}
+ exit 1
+fi
+
+rm -rf ${TMP}
+exit 0
+
diff --git a/usr.sbin/fmtree/test/test05.sh b/usr.sbin/fmtree/test/test05.sh
new file mode 100644
index 0000000..eda544f
--- /dev/null
+++ b/usr.sbin/fmtree/test/test05.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# $FreeBSD$
+#
+# Test for 'optional' keyword.
+#
+
+TMP=`mktemp -d /tmp/mtree.XXXXXX`
+mkdir -p ${TMP}/mr ${TMP}/mr/optional-dir ${TMP}/mr/some-dir
+touch ${TMP}/mr/optional-file ${TMP}/mr/some-file
+
+mtree -c -p ${TMP}/mr > ${TMP}/_
+rm -rf ${TMP}/mr/optional-file ${TMP}/mr/optional-dir
+mtree -p ${TMP}/mr -K optional < ${TMP}/_ > /dev/null
+
+res=$?
+
+if [ $res -ne 0 ] ; then
+ echo "ERROR 'optional' keyword failed" 1>&2
+ rm -rf ${TMP}
+ exit 1
+fi
+
+rm -rf ${TMP}
+exit 0
diff --git a/usr.sbin/fmtree/verify.c b/usr.sbin/fmtree/verify.c
new file mode 100644
index 0000000..c9291c0
--- /dev/null
+++ b/usr.sbin/fmtree/verify.c
@@ -0,0 +1,259 @@
+/*-
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)verify.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fts.h>
+#include <fnmatch.h>
+#include <stdio.h>
+#include <unistd.h>
+#include "mtree.h"
+#include "extern.h"
+
+static NODE *root;
+static char path[MAXPATHLEN];
+
+static void miss(NODE *, char *);
+static int vwalk(void);
+
+int
+mtree_verifyspec(FILE *fi)
+{
+ int rval;
+
+ root = mtree_readspec(fi);
+ rval = vwalk();
+ miss(root, path);
+ return (rval);
+}
+
+static int
+nsort(const FTSENT * const *a, const FTSENT * const *b)
+{
+ return (strcmp((*a)->fts_name, (*b)->fts_name));
+}
+
+static int
+vwalk(void)
+{
+ FTS *t;
+ FTSENT *p;
+ NODE *ep, *level;
+ int specdepth, rval;
+ char *argv[2];
+ char dot[] = ".";
+
+ argv[0] = dot;
+ argv[1] = NULL;
+ if ((t = fts_open(argv, ftsoptions, nsort)) == NULL)
+ err(1, "line %d: fts_open", lineno);
+ level = root;
+ specdepth = rval = 0;
+ while ((p = fts_read(t))) {
+ if (check_excludes(p->fts_name, p->fts_path)) {
+ fts_set(t, p, FTS_SKIP);
+ continue;
+ }
+ switch(p->fts_info) {
+ case FTS_D:
+ case FTS_SL:
+ break;
+ case FTS_DP:
+ if (specdepth > p->fts_level) {
+ for (level = level->parent; level->prev;
+ level = level->prev);
+ --specdepth;
+ }
+ continue;
+ case FTS_DNR:
+ case FTS_ERR:
+ case FTS_NS:
+ warnx("%s: %s", RP(p), strerror(p->fts_errno));
+ continue;
+ default:
+ if (dflag)
+ continue;
+ }
+
+ if (specdepth != p->fts_level)
+ goto extra;
+ for (ep = level; ep; ep = ep->next)
+ if ((ep->flags & F_MAGIC &&
+ !fnmatch(ep->name, p->fts_name, FNM_PATHNAME)) ||
+ !strcmp(ep->name, p->fts_name)) {
+ ep->flags |= F_VISIT;
+ if ((ep->flags & F_NOCHANGE) == 0 &&
+ compare(ep->name, ep, p))
+ rval = MISMATCHEXIT;
+ if (ep->flags & F_IGN)
+ (void)fts_set(t, p, FTS_SKIP);
+ else if (ep->child && ep->type == F_DIR &&
+ p->fts_info == FTS_D) {
+ level = ep->child;
+ ++specdepth;
+ }
+ break;
+ }
+
+ if (ep)
+ continue;
+extra:
+ if (!eflag) {
+ (void)printf("%s extra", RP(p));
+ if (rflag) {
+ if ((S_ISDIR(p->fts_statp->st_mode)
+ ? rmdir : unlink)(p->fts_accpath)) {
+ (void)printf(", not removed: %s",
+ strerror(errno));
+ } else
+ (void)printf(", removed");
+ }
+ (void)putchar('\n');
+ }
+ (void)fts_set(t, p, FTS_SKIP);
+ }
+ (void)fts_close(t);
+ if (sflag)
+ warnx("%s checksum: %lu", fullpath, (unsigned long)crc_total);
+ return (rval);
+}
+
+static void
+miss(NODE *p, char *tail)
+{
+ int create;
+ char *tp;
+ const char *type, *what;
+ int serr;
+
+ for (; p; p = p->next) {
+ if (p->flags & F_OPT && !(p->flags & F_VISIT))
+ continue;
+ if (p->type != F_DIR && (dflag || p->flags & F_VISIT))
+ continue;
+ (void)strcpy(tail, p->name);
+ if (!(p->flags & F_VISIT)) {
+ /* Don't print missing message if file exists as a
+ symbolic link and the -q flag is set. */
+ struct stat statbuf;
+
+ if (qflag && stat(path, &statbuf) == 0)
+ p->flags |= F_VISIT;
+ else
+ (void)printf("%s missing", path);
+ }
+ if (p->type != F_DIR && p->type != F_LINK) {
+ putchar('\n');
+ continue;
+ }
+
+ create = 0;
+ if (p->type == F_LINK)
+ type = "symlink";
+ else
+ type = "directory";
+ if (!(p->flags & F_VISIT) && uflag) {
+ if (!(p->flags & (F_UID | F_UNAME)))
+ (void)printf(" (%s not created: user not specified)", type);
+ else if (!(p->flags & (F_GID | F_GNAME)))
+ (void)printf(" (%s not created: group not specified)", type);
+ else if (p->type == F_LINK) {
+ if (symlink(p->slink, path))
+ (void)printf(" (symlink not created: %s)\n",
+ strerror(errno));
+ else
+ (void)printf(" (created)\n");
+ if (lchown(path, p->st_uid, p->st_gid) == -1) {
+ serr = errno;
+ if (p->st_uid == (uid_t)-1)
+ what = "group";
+ else if (lchown(path, (uid_t)-1,
+ p->st_gid) == -1)
+ what = "user & group";
+ else {
+ what = "user";
+ errno = serr;
+ }
+ (void)printf("%s: %s not modified: %s"
+ "\n", path, what, strerror(errno));
+ }
+ continue;
+ } else if (!(p->flags & F_MODE))
+ (void)printf(" (directory not created: mode not specified)");
+ else if (mkdir(path, S_IRWXU))
+ (void)printf(" (directory not created: %s)",
+ strerror(errno));
+ else {
+ create = 1;
+ (void)printf(" (created)");
+ }
+ }
+ if (!(p->flags & F_VISIT))
+ (void)putchar('\n');
+
+ for (tp = tail; *tp; ++tp);
+ *tp = '/';
+ miss(p->child, tp + 1);
+ *tp = '\0';
+
+ if (!create && !uflag)
+ continue;
+ if (chown(path, p->st_uid, p->st_gid) == -1) {
+ serr = errno;
+ if (p->st_uid == (uid_t)-1)
+ what = "group";
+ else if (chown(path, (uid_t)-1, p->st_gid) == -1)
+ what = "user & group";
+ else {
+ what = "user";
+ errno = serr;
+ }
+ (void)printf("%s: %s not modified: %s\n",
+ path, what, strerror(errno));
+ }
+ if (chmod(path, p->st_mode))
+ (void)printf("%s: permissions not set: %s\n",
+ path, strerror(errno));
+ if ((p->flags & F_FLAGS) && p->st_flags &&
+ chflags(path, p->st_flags))
+ (void)printf("%s: file flags not set: %s\n",
+ path, strerror(errno));
+ }
+}
diff --git a/usr.sbin/freebsd-update/Makefile b/usr.sbin/freebsd-update/Makefile
new file mode 100644
index 0000000..c5d4b00
--- /dev/null
+++ b/usr.sbin/freebsd-update/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+SCRIPTS=freebsd-update.sh
+MAN= freebsd-update.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/freebsd-update/Makefile.depend b/usr.sbin/freebsd-update/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/freebsd-update/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/freebsd-update/freebsd-update.8 b/usr.sbin/freebsd-update/freebsd-update.8
new file mode 100644
index 0000000..c0c9ae0
--- /dev/null
+++ b/usr.sbin/freebsd-update/freebsd-update.8
@@ -0,0 +1,197 @@
+.\"-
+.\" Copyright 2006, 2007 Colin Percival
+.\" All rights reserved
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted providing that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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$
+.\"
+.Dd March 2, 2015
+.Dt FREEBSD-UPDATE 8
+.Os FreeBSD
+.Sh NAME
+.Nm freebsd-update
+.Nd fetch and install binary updates to FreeBSD
+.Sh SYNOPSIS
+.Nm
+.Op Fl b Ar basedir
+.Op Fl d Ar workdir
+.Op Fl f Ar conffile
+.Op Fl F
+.Op Fl k Ar KEY
+.Op Fl r Ar newrelease
+.Op Fl s Ar server
+.Op Fl t Ar address
+.Op Fl -not-running-from-cron
+.Cm command ...
+.Sh DESCRIPTION
+The
+.Nm
+tool is used to fetch, install, and rollback binary
+updates to the FreeBSD base system.
+Note that updates are only available if they are being built for the
+FreeBSD release and architecture being used; in particular, the
+.Fx
+Security Team only builds updates for releases shipped in binary form
+by the
+.Fx
+Release Engineering Team, e.g.,
+.Fx
+9.3-RELEASE and
+.Fx
+10.1-RELEASE, but not
+.Fx
+9.3-STABLE or
+.Fx
+11-CURRENT.
+.Sh OPTIONS
+The following options are supported:
+.Bl -tag -width "-r newrelease"
+.It Fl b Ar basedir
+Operate on a system mounted at
+.Ar basedir .
+(default:
+.Pa / ,
+or as given in the configuration file.)
+.It Fl d Ar workdir
+Store working files in
+.Ar workdir .
+(default:
+.Pa /var/db/freebsd-update/ ,
+or as given in the configuration file.)
+.It Fl f Ar conffile
+Read configuration options from
+.Ar conffile .
+(default:
+.Pa /etc/freebsd-update.conf )
+.It Fl F
+Force
+.Nm Cm fetch
+to proceed where it normally would not, such as an unfinished upgrade
+.It Fl k Ar KEY
+Trust an RSA key with SHA256 of
+.Ar KEY .
+(default: read value from configuration file.)
+.It Fl r Ar newrelease
+Specify the new release to which
+.Nm
+should upgrade (upgrade command only).
+.It Fl s Ar server
+Fetch files from the specified server or server pool.
+(default: read value from configuration file.)
+.It Fl t Ar address
+Mail output of
+.Cm cron
+command, if any, to
+.Ar address .
+(default: root, or as given in the configuration file.)
+.It Fl -not-running-from-cron
+Force
+.Nm Cm fetch
+to proceed when there is no controlling tty.
+This is for use by automated scripts and orchestration tools.
+Please do not run
+.Nm Cm fetch
+from crontab or similar using this flag, see:
+.Nm Cm cron
+.It Fl -currently-running Ar release
+Don't detect the currently-running release; instead, assume that the
+system is running the specified
+.Ar release .
+This is most likely to be useful when upgrading jails.
+.El
+.Sh COMMANDS
+The
+.Cm command
+can be any one of the following:
+.Bl -tag -width "rollback"
+.It Cm fetch
+Based on the currently installed world and the configuration
+options set, fetch all available binary updates.
+.It Cm cron
+Sleep a random amount of time between 1 and 3600 seconds,
+then download updates as if the
+.Cm fetch
+command was used.
+If updates are downloaded, an email will be sent
+(to root or a different address if specified via the
+.Fl t
+option or in the configuration file).
+As the name suggests, this command is designed for running
+from
+.Xr cron 8 ;
+the random delay serves to minimize the probability that
+a large number of machines will simultaneously attempt to
+fetch updates.
+.It Cm upgrade
+Fetch files necessary for upgrading to a new release.
+Before using this command, make sure that you read the
+announcement and release notes for the new release in
+case there are any special steps needed for upgrading.
+Note that this command may require up to 500 MB of space in
+.Ar workdir
+depending on which components of the
+.Fx
+base system are installed.
+.It Cm install
+Install the most recently fetched updates or upgrade.
+.It Cm rollback
+Uninstall the most recently installed updates.
+.It Cm IDS
+Compare the system against a "known good" index of the
+installed release.
+.El
+.Sh TIPS
+.Bl -bullet
+.It
+If your clock is set to local time, adding the line
+.Pp
+.Dl 0 3 * * * root /usr/sbin/freebsd-update cron
+.Pp
+to /etc/crontab will check for updates every night.
+If your clock is set to UTC, please pick a random time
+other than 3AM, to avoid overly imposing an uneven load
+on the server(s) hosting the updates.
+.It
+In spite of its name,
+.Nm
+IDS should not be relied upon as an "Intrusion Detection
+System", since if the system has been tampered with
+it cannot be trusted to operate correctly.
+If you intend to use this command for intrusion-detection
+purposes, make sure you boot from a secure disk (e.g., a CD).
+.El
+.Sh FILES
+.Bl -tag -width "/etc/freebsd-update.conf"
+.It Pa /etc/freebsd-update.conf
+Default location of the
+.Nm
+configuration file.
+.It Pa /var/db/freebsd-update/
+Default location where
+.Nm
+stores temporary files and downloaded updates.
+.El
+.Sh SEE ALSO
+.Xr freebsd-update.conf 5
+.Sh AUTHORS
+.An Colin Percival Aq Mt cperciva@FreeBSD.org
diff --git a/usr.sbin/freebsd-update/freebsd-update.sh b/usr.sbin/freebsd-update/freebsd-update.sh
new file mode 100644
index 0000000..cce2af5
--- /dev/null
+++ b/usr.sbin/freebsd-update/freebsd-update.sh
@@ -0,0 +1,3304 @@
+#!/bin/sh
+
+#-
+# Copyright 2004-2007 Colin Percival
+# All rights reserved
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted providing that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+#### Usage function -- called from command-line handling code.
+
+# Usage instructions. Options not listed:
+# --debug -- don't filter output from utilities
+# --no-stats -- don't show progress statistics while fetching files
+usage () {
+ cat <<EOF
+usage: `basename $0` [options] command ... [path]
+
+Options:
+ -b basedir -- Operate on a system mounted at basedir
+ (default: /)
+ -d workdir -- Store working files in workdir
+ (default: /var/db/freebsd-update/)
+ -f conffile -- Read configuration options from conffile
+ (default: /etc/freebsd-update.conf)
+ -F -- Force a fetch operation to proceed
+ -k KEY -- Trust an RSA key with SHA256 hash of KEY
+ -r release -- Target for upgrade (e.g., 6.2-RELEASE)
+ -s server -- Server from which to fetch updates
+ (default: update.FreeBSD.org)
+ -t address -- Mail output of cron command, if any, to address
+ (default: root)
+ --not-running-from-cron
+ -- Run without a tty, for use by automated tools
+ --currently-running release
+ -- Update as if currently running this release
+Commands:
+ fetch -- Fetch updates from server
+ cron -- Sleep rand(3600) seconds, fetch updates, and send an
+ email if updates were found
+ upgrade -- Fetch upgrades to FreeBSD version specified via -r option
+ install -- Install downloaded updates or upgrades
+ rollback -- Uninstall most recently installed updates
+ IDS -- Compare the system against an index of "known good" files.
+EOF
+ exit 0
+}
+
+#### Configuration processing functions
+
+#-
+# Configuration options are set in the following order of priority:
+# 1. Command line options
+# 2. Configuration file options
+# 3. Default options
+# In addition, certain options (e.g., IgnorePaths) can be specified multiple
+# times and (as long as these are all in the same place, e.g., inside the
+# configuration file) they will accumulate. Finally, because the path to the
+# configuration file can be specified at the command line, the entire command
+# line must be processed before we start reading the configuration file.
+#
+# Sound like a mess? It is. Here's how we handle this:
+# 1. Initialize CONFFILE and all the options to "".
+# 2. Process the command line. Throw an error if a non-accumulating option
+# is specified twice.
+# 3. If CONFFILE is "", set CONFFILE to /etc/freebsd-update.conf .
+# 4. For all the configuration options X, set X_saved to X.
+# 5. Initialize all the options to "".
+# 6. Read CONFFILE line by line, parsing options.
+# 7. For each configuration option X, set X to X_saved iff X_saved is not "".
+# 8. Repeat steps 4-7, except setting options to their default values at (6).
+
+CONFIGOPTIONS="KEYPRINT WORKDIR SERVERNAME MAILTO ALLOWADD ALLOWDELETE
+ KEEPMODIFIEDMETADATA COMPONENTS IGNOREPATHS UPDATEIFUNMODIFIED
+ BASEDIR VERBOSELEVEL TARGETRELEASE STRICTCOMPONENTS MERGECHANGES
+ IDSIGNOREPATHS BACKUPKERNEL BACKUPKERNELDIR BACKUPKERNELSYMBOLFILES"
+
+# Set all the configuration options to "".
+nullconfig () {
+ for X in ${CONFIGOPTIONS}; do
+ eval ${X}=""
+ done
+}
+
+# For each configuration option X, set X_saved to X.
+saveconfig () {
+ for X in ${CONFIGOPTIONS}; do
+ eval ${X}_saved=\$${X}
+ done
+}
+
+# For each configuration option X, set X to X_saved if X_saved is not "".
+mergeconfig () {
+ for X in ${CONFIGOPTIONS}; do
+ eval _=\$${X}_saved
+ if ! [ -z "${_}" ]; then
+ eval ${X}=\$${X}_saved
+ fi
+ done
+}
+
+# Set the trusted keyprint.
+config_KeyPrint () {
+ if [ -z ${KEYPRINT} ]; then
+ KEYPRINT=$1
+ else
+ return 1
+ fi
+}
+
+# Set the working directory.
+config_WorkDir () {
+ if [ -z ${WORKDIR} ]; then
+ WORKDIR=$1
+ else
+ return 1
+ fi
+}
+
+# Set the name of the server (pool) from which to fetch updates
+config_ServerName () {
+ if [ -z ${SERVERNAME} ]; then
+ SERVERNAME=$1
+ else
+ return 1
+ fi
+}
+
+# Set the address to which 'cron' output will be mailed.
+config_MailTo () {
+ if [ -z ${MAILTO} ]; then
+ MAILTO=$1
+ else
+ return 1
+ fi
+}
+
+# Set whether FreeBSD Update is allowed to add files (or directories, or
+# symlinks) which did not previously exist.
+config_AllowAdd () {
+ if [ -z ${ALLOWADD} ]; then
+ case $1 in
+ [Yy][Ee][Ss])
+ ALLOWADD=yes
+ ;;
+ [Nn][Oo])
+ ALLOWADD=no
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+ else
+ return 1
+ fi
+}
+
+# Set whether FreeBSD Update is allowed to remove files/directories/symlinks.
+config_AllowDelete () {
+ if [ -z ${ALLOWDELETE} ]; then
+ case $1 in
+ [Yy][Ee][Ss])
+ ALLOWDELETE=yes
+ ;;
+ [Nn][Oo])
+ ALLOWDELETE=no
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+ else
+ return 1
+ fi
+}
+
+# Set whether FreeBSD Update should keep existing inode ownership,
+# permissions, and flags, in the event that they have been modified locally
+# after the release.
+config_KeepModifiedMetadata () {
+ if [ -z ${KEEPMODIFIEDMETADATA} ]; then
+ case $1 in
+ [Yy][Ee][Ss])
+ KEEPMODIFIEDMETADATA=yes
+ ;;
+ [Nn][Oo])
+ KEEPMODIFIEDMETADATA=no
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+ else
+ return 1
+ fi
+}
+
+# Add to the list of components which should be kept updated.
+config_Components () {
+ for C in $@; do
+ if [ "$C" = "src" ]; then
+ if [ -e /usr/src/COPYRIGHT ]; then
+ COMPONENTS="${COMPONENTS} ${C}"
+ else
+ echo "src component not installed, skipped"
+ fi
+ else
+ COMPONENTS="${COMPONENTS} ${C}"
+ fi
+ done
+}
+
+# Add to the list of paths under which updates will be ignored.
+config_IgnorePaths () {
+ for C in $@; do
+ IGNOREPATHS="${IGNOREPATHS} ${C}"
+ done
+}
+
+# Add to the list of paths which IDS should ignore.
+config_IDSIgnorePaths () {
+ for C in $@; do
+ IDSIGNOREPATHS="${IDSIGNOREPATHS} ${C}"
+ done
+}
+
+# Add to the list of paths within which updates will be performed only if the
+# file on disk has not been modified locally.
+config_UpdateIfUnmodified () {
+ for C in $@; do
+ UPDATEIFUNMODIFIED="${UPDATEIFUNMODIFIED} ${C}"
+ done
+}
+
+# Add to the list of paths within which updates to text files will be merged
+# instead of overwritten.
+config_MergeChanges () {
+ for C in $@; do
+ MERGECHANGES="${MERGECHANGES} ${C}"
+ done
+}
+
+# Work on a FreeBSD installation mounted under $1
+config_BaseDir () {
+ if [ -z ${BASEDIR} ]; then
+ BASEDIR=$1
+ else
+ return 1
+ fi
+}
+
+# When fetching upgrades, should we assume the user wants exactly the
+# components listed in COMPONENTS, rather than trying to guess based on
+# what's currently installed?
+config_StrictComponents () {
+ if [ -z ${STRICTCOMPONENTS} ]; then
+ case $1 in
+ [Yy][Ee][Ss])
+ STRICTCOMPONENTS=yes
+ ;;
+ [Nn][Oo])
+ STRICTCOMPONENTS=no
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+ else
+ return 1
+ fi
+}
+
+# Upgrade to FreeBSD $1
+config_TargetRelease () {
+ if [ -z ${TARGETRELEASE} ]; then
+ TARGETRELEASE=$1
+ else
+ return 1
+ fi
+ if echo ${TARGETRELEASE} | grep -qE '^[0-9.]+$'; then
+ TARGETRELEASE="${TARGETRELEASE}-RELEASE"
+ fi
+}
+
+# Define what happens to output of utilities
+config_VerboseLevel () {
+ if [ -z ${VERBOSELEVEL} ]; then
+ case $1 in
+ [Dd][Ee][Bb][Uu][Gg])
+ VERBOSELEVEL=debug
+ ;;
+ [Nn][Oo][Ss][Tt][Aa][Tt][Ss])
+ VERBOSELEVEL=nostats
+ ;;
+ [Ss][Tt][Aa][Tt][Ss])
+ VERBOSELEVEL=stats
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+ else
+ return 1
+ fi
+}
+
+config_BackupKernel () {
+ if [ -z ${BACKUPKERNEL} ]; then
+ case $1 in
+ [Yy][Ee][Ss])
+ BACKUPKERNEL=yes
+ ;;
+ [Nn][Oo])
+ BACKUPKERNEL=no
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+ else
+ return 1
+ fi
+}
+
+config_BackupKernelDir () {
+ if [ -z ${BACKUPKERNELDIR} ]; then
+ if [ -z "$1" ]; then
+ echo "BackupKernelDir set to empty dir"
+ return 1
+ fi
+
+ # We check for some paths which would be extremely odd
+ # to use, but which could cause a lot of problems if
+ # used.
+ case $1 in
+ /|/bin|/boot|/etc|/lib|/libexec|/sbin|/usr|/var)
+ echo "BackupKernelDir set to invalid path $1"
+ return 1
+ ;;
+ /*)
+ BACKUPKERNELDIR=$1
+ ;;
+ *)
+ echo "BackupKernelDir ($1) is not an absolute path"
+ return 1
+ ;;
+ esac
+ else
+ return 1
+ fi
+}
+
+config_BackupKernelSymbolFiles () {
+ if [ -z ${BACKUPKERNELSYMBOLFILES} ]; then
+ case $1 in
+ [Yy][Ee][Ss])
+ BACKUPKERNELSYMBOLFILES=yes
+ ;;
+ [Nn][Oo])
+ BACKUPKERNELSYMBOLFILES=no
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+ else
+ return 1
+ fi
+}
+
+# Handle one line of configuration
+configline () {
+ if [ $# -eq 0 ]; then
+ return
+ fi
+
+ OPT=$1
+ shift
+ config_${OPT} $@
+}
+
+#### Parameter handling functions.
+
+# Initialize parameters to null, just in case they're
+# set in the environment.
+init_params () {
+ # Configration settings
+ nullconfig
+
+ # No configuration file set yet
+ CONFFILE=""
+
+ # No commands specified yet
+ COMMANDS=""
+
+ # Force fetch to proceed
+ FORCEFETCH=0
+
+ # Run without a TTY
+ NOTTYOK=0
+}
+
+# Parse the command line
+parse_cmdline () {
+ while [ $# -gt 0 ]; do
+ case "$1" in
+ # Location of configuration file
+ -f)
+ if [ $# -eq 1 ]; then usage; fi
+ if [ ! -z "${CONFFILE}" ]; then usage; fi
+ shift; CONFFILE="$1"
+ ;;
+ -F)
+ FORCEFETCH=1
+ ;;
+ --not-running-from-cron)
+ NOTTYOK=1
+ ;;
+ --currently-running)
+ shift; export UNAME_r="$1"
+ ;;
+
+ # Configuration file equivalents
+ -b)
+ if [ $# -eq 1 ]; then usage; fi; shift
+ config_BaseDir $1 || usage
+ ;;
+ -d)
+ if [ $# -eq 1 ]; then usage; fi; shift
+ config_WorkDir $1 || usage
+ ;;
+ -k)
+ if [ $# -eq 1 ]; then usage; fi; shift
+ config_KeyPrint $1 || usage
+ ;;
+ -s)
+ if [ $# -eq 1 ]; then usage; fi; shift
+ config_ServerName $1 || usage
+ ;;
+ -r)
+ if [ $# -eq 1 ]; then usage; fi; shift
+ config_TargetRelease $1 || usage
+ ;;
+ -t)
+ if [ $# -eq 1 ]; then usage; fi; shift
+ config_MailTo $1 || usage
+ ;;
+ -v)
+ if [ $# -eq 1 ]; then usage; fi; shift
+ config_VerboseLevel $1 || usage
+ ;;
+
+ # Aliases for "-v debug" and "-v nostats"
+ --debug)
+ config_VerboseLevel debug || usage
+ ;;
+ --no-stats)
+ config_VerboseLevel nostats || usage
+ ;;
+
+ # Commands
+ cron | fetch | upgrade | install | rollback | IDS)
+ COMMANDS="${COMMANDS} $1"
+ ;;
+
+ # Anything else is an error
+ *)
+ usage
+ ;;
+ esac
+ shift
+ done
+
+ # Make sure we have at least one command
+ if [ -z "${COMMANDS}" ]; then
+ usage
+ fi
+}
+
+# Parse the configuration file
+parse_conffile () {
+ # If a configuration file was specified on the command line, check
+ # that it exists and is readable.
+ if [ ! -z "${CONFFILE}" ] && [ ! -r "${CONFFILE}" ]; then
+ echo -n "File does not exist "
+ echo -n "or is not readable: "
+ echo ${CONFFILE}
+ exit 1
+ fi
+
+ # If a configuration file was not specified on the command line,
+ # use the default configuration file path. If that default does
+ # not exist, give up looking for any configuration.
+ if [ -z "${CONFFILE}" ]; then
+ CONFFILE="/etc/freebsd-update.conf"
+ if [ ! -r "${CONFFILE}" ]; then
+ return
+ fi
+ fi
+
+ # Save the configuration options specified on the command line, and
+ # clear all the options in preparation for reading the config file.
+ saveconfig
+ nullconfig
+
+ # Read the configuration file. Anything after the first '#' is
+ # ignored, and any blank lines are ignored.
+ L=0
+ while read LINE; do
+ L=$(($L + 1))
+ LINEX=`echo "${LINE}" | cut -f 1 -d '#'`
+ if ! configline ${LINEX}; then
+ echo "Error processing configuration file, line $L:"
+ echo "==> ${LINE}"
+ exit 1
+ fi
+ done < ${CONFFILE}
+
+ # Merge the settings read from the configuration file with those
+ # provided at the command line.
+ mergeconfig
+}
+
+# Provide some default parameters
+default_params () {
+ # Save any parameters already configured, and clear the slate
+ saveconfig
+ nullconfig
+
+ # Default configurations
+ config_WorkDir /var/db/freebsd-update
+ config_MailTo root
+ config_AllowAdd yes
+ config_AllowDelete yes
+ config_KeepModifiedMetadata yes
+ config_BaseDir /
+ config_VerboseLevel stats
+ config_StrictComponents no
+ config_BackupKernel yes
+ config_BackupKernelDir /boot/kernel.old
+ config_BackupKernelSymbolFiles no
+
+ # Merge these defaults into the earlier-configured settings
+ mergeconfig
+}
+
+# Set utility output filtering options, based on ${VERBOSELEVEL}
+fetch_setup_verboselevel () {
+ case ${VERBOSELEVEL} in
+ debug)
+ QUIETREDIR="/dev/stderr"
+ QUIETFLAG=" "
+ STATSREDIR="/dev/stderr"
+ DDSTATS=".."
+ XARGST="-t"
+ NDEBUG=" "
+ ;;
+ nostats)
+ QUIETREDIR=""
+ QUIETFLAG=""
+ STATSREDIR="/dev/null"
+ DDSTATS=".."
+ XARGST=""
+ NDEBUG=""
+ ;;
+ stats)
+ QUIETREDIR="/dev/null"
+ QUIETFLAG="-q"
+ STATSREDIR="/dev/stdout"
+ DDSTATS=""
+ XARGST=""
+ NDEBUG="-n"
+ ;;
+ esac
+}
+
+# Perform sanity checks and set some final parameters
+# in preparation for fetching files. Figure out which
+# set of updates should be downloaded: If the user is
+# running *-p[0-9]+, strip off the last part; if the
+# user is running -SECURITY, call it -RELEASE. Chdir
+# into the working directory.
+fetchupgrade_check_params () {
+ export HTTP_USER_AGENT="freebsd-update (${COMMAND}, `uname -r`)"
+
+ _SERVERNAME_z=\
+"SERVERNAME must be given via command line or configuration file."
+ _KEYPRINT_z="Key must be given via -k option or configuration file."
+ _KEYPRINT_bad="Invalid key fingerprint: "
+ _WORKDIR_bad="Directory does not exist or is not writable: "
+ _WORKDIR_bad2="Directory is not on a persistent filesystem: "
+
+ if [ -z "${SERVERNAME}" ]; then
+ echo -n "`basename $0`: "
+ echo "${_SERVERNAME_z}"
+ exit 1
+ fi
+ if [ -z "${KEYPRINT}" ]; then
+ echo -n "`basename $0`: "
+ echo "${_KEYPRINT_z}"
+ exit 1
+ fi
+ if ! echo "${KEYPRINT}" | grep -qE "^[0-9a-f]{64}$"; then
+ echo -n "`basename $0`: "
+ echo -n "${_KEYPRINT_bad}"
+ echo ${KEYPRINT}
+ exit 1
+ fi
+ if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
+ echo -n "`basename $0`: "
+ echo -n "${_WORKDIR_bad}"
+ echo ${WORKDIR}
+ exit 1
+ fi
+ case `df -T ${WORKDIR}` in */dev/md[0-9]* | *tmpfs*)
+ echo -n "`basename $0`: "
+ echo -n "${_WORKDIR_bad2}"
+ echo ${WORKDIR}
+ exit 1
+ ;;
+ esac
+ chmod 700 ${WORKDIR}
+ cd ${WORKDIR} || exit 1
+
+ # Generate release number. The s/SECURITY/RELEASE/ bit exists
+ # to provide an upgrade path for FreeBSD Update 1.x users, since
+ # the kernels provided by FreeBSD Update 1.x are always labelled
+ # as X.Y-SECURITY.
+ RELNUM=`uname -r |
+ sed -E 's,-p[0-9]+,,' |
+ sed -E 's,-SECURITY,-RELEASE,'`
+ ARCH=`uname -m`
+ FETCHDIR=${RELNUM}/${ARCH}
+ PATCHDIR=${RELNUM}/${ARCH}/bp
+
+ # Figure out what directory contains the running kernel
+ BOOTFILE=`sysctl -n kern.bootfile`
+ KERNELDIR=${BOOTFILE%/kernel}
+ if ! [ -d ${KERNELDIR} ]; then
+ echo "Cannot identify running kernel"
+ exit 1
+ fi
+
+ # Figure out what kernel configuration is running. We start with
+ # the output of `uname -i`, and then make the following adjustments:
+ # 1. Replace "SMP-GENERIC" with "SMP". Why the SMP kernel config
+ # file says "ident SMP-GENERIC", I don't know...
+ # 2. If the kernel claims to be GENERIC _and_ ${ARCH} is "amd64"
+ # _and_ `sysctl kern.version` contains a line which ends "/SMP", then
+ # we're running an SMP kernel. This mis-identification is a bug
+ # which was fixed in 6.2-STABLE.
+ KERNCONF=`uname -i`
+ if [ ${KERNCONF} = "SMP-GENERIC" ]; then
+ KERNCONF=SMP
+ fi
+ if [ ${KERNCONF} = "GENERIC" ] && [ ${ARCH} = "amd64" ]; then
+ if sysctl kern.version | grep -qE '/SMP$'; then
+ KERNCONF=SMP
+ fi
+ fi
+
+ # Define some paths
+ BSPATCH=/usr/bin/bspatch
+ SHA256=/sbin/sha256
+ PHTTPGET=/usr/libexec/phttpget
+
+ # Set up variables relating to VERBOSELEVEL
+ fetch_setup_verboselevel
+
+ # Construct a unique name from ${BASEDIR}
+ BDHASH=`echo ${BASEDIR} | sha256 -q`
+}
+
+# Perform sanity checks etc. before fetching updates.
+fetch_check_params () {
+ fetchupgrade_check_params
+
+ if ! [ -z "${TARGETRELEASE}" ]; then
+ echo -n "`basename $0`: "
+ echo -n "-r option is meaningless with 'fetch' command. "
+ echo "(Did you mean 'upgrade' instead?)"
+ exit 1
+ fi
+
+ # Check that we have updates ready to install
+ if [ -f ${BDHASH}-install/kerneldone -a $FORCEFETCH -eq 0 ]; then
+ echo "You have a partially completed upgrade pending"
+ echo "Run '$0 install' first."
+ echo "Run '$0 fetch -F' to proceed anyway."
+ exit 1
+ fi
+}
+
+# Perform sanity checks etc. before fetching upgrades.
+upgrade_check_params () {
+ fetchupgrade_check_params
+
+ # Unless set otherwise, we're upgrading to the same kernel config.
+ NKERNCONF=${KERNCONF}
+
+ # We need TARGETRELEASE set
+ _TARGETRELEASE_z="Release target must be specified via -r option."
+ if [ -z "${TARGETRELEASE}" ]; then
+ echo -n "`basename $0`: "
+ echo "${_TARGETRELEASE_z}"
+ exit 1
+ fi
+
+ # The target release should be != the current release.
+ if [ "${TARGETRELEASE}" = "${RELNUM}" ]; then
+ echo -n "`basename $0`: "
+ echo "Cannot upgrade from ${RELNUM} to itself"
+ exit 1
+ fi
+
+ # Turning off AllowAdd or AllowDelete is a bad idea for upgrades.
+ if [ "${ALLOWADD}" = "no" ]; then
+ echo -n "`basename $0`: "
+ echo -n "WARNING: \"AllowAdd no\" is a bad idea "
+ echo "when upgrading between releases."
+ echo
+ fi
+ if [ "${ALLOWDELETE}" = "no" ]; then
+ echo -n "`basename $0`: "
+ echo -n "WARNING: \"AllowDelete no\" is a bad idea "
+ echo "when upgrading between releases."
+ echo
+ fi
+
+ # Set EDITOR to /usr/bin/vi if it isn't already set
+ : ${EDITOR:='/usr/bin/vi'}
+}
+
+# Perform sanity checks and set some final parameters in
+# preparation for installing updates.
+install_check_params () {
+ # Check that we are root. All sorts of things won't work otherwise.
+ if [ `id -u` != 0 ]; then
+ echo "You must be root to run this."
+ exit 1
+ fi
+
+ # Check that securelevel <= 0. Otherwise we can't update schg files.
+ if [ `sysctl -n kern.securelevel` -gt 0 ]; then
+ echo "Updates cannot be installed when the system securelevel"
+ echo "is greater than zero."
+ exit 1
+ fi
+
+ # Check that we have a working directory
+ _WORKDIR_bad="Directory does not exist or is not writable: "
+ if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
+ echo -n "`basename $0`: "
+ echo -n "${_WORKDIR_bad}"
+ echo ${WORKDIR}
+ exit 1
+ fi
+ cd ${WORKDIR} || exit 1
+
+ # Construct a unique name from ${BASEDIR}
+ BDHASH=`echo ${BASEDIR} | sha256 -q`
+
+ # Check that we have updates ready to install
+ if ! [ -L ${BDHASH}-install ]; then
+ echo "No updates are available to install."
+ echo "Run '$0 fetch' first."
+ exit 1
+ fi
+ if ! [ -f ${BDHASH}-install/INDEX-OLD ] ||
+ ! [ -f ${BDHASH}-install/INDEX-NEW ]; then
+ echo "Update manifest is corrupt -- this should never happen."
+ echo "Re-run '$0 fetch'."
+ exit 1
+ fi
+
+ # Figure out what directory contains the running kernel
+ BOOTFILE=`sysctl -n kern.bootfile`
+ KERNELDIR=${BOOTFILE%/kernel}
+ if ! [ -d ${KERNELDIR} ]; then
+ echo "Cannot identify running kernel"
+ exit 1
+ fi
+}
+
+# Perform sanity checks and set some final parameters in
+# preparation for UNinstalling updates.
+rollback_check_params () {
+ # Check that we are root. All sorts of things won't work otherwise.
+ if [ `id -u` != 0 ]; then
+ echo "You must be root to run this."
+ exit 1
+ fi
+
+ # Check that we have a working directory
+ _WORKDIR_bad="Directory does not exist or is not writable: "
+ if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
+ echo -n "`basename $0`: "
+ echo -n "${_WORKDIR_bad}"
+ echo ${WORKDIR}
+ exit 1
+ fi
+ cd ${WORKDIR} || exit 1
+
+ # Construct a unique name from ${BASEDIR}
+ BDHASH=`echo ${BASEDIR} | sha256 -q`
+
+ # Check that we have updates ready to rollback
+ if ! [ -L ${BDHASH}-rollback ]; then
+ echo "No rollback directory found."
+ exit 1
+ fi
+ if ! [ -f ${BDHASH}-rollback/INDEX-OLD ] ||
+ ! [ -f ${BDHASH}-rollback/INDEX-NEW ]; then
+ echo "Update manifest is corrupt -- this should never happen."
+ exit 1
+ fi
+}
+
+# Perform sanity checks and set some final parameters
+# in preparation for comparing the system against the
+# published index. Figure out which index we should
+# compare against: If the user is running *-p[0-9]+,
+# strip off the last part; if the user is running
+# -SECURITY, call it -RELEASE. Chdir into the working
+# directory.
+IDS_check_params () {
+ export HTTP_USER_AGENT="freebsd-update (${COMMAND}, `uname -r`)"
+
+ _SERVERNAME_z=\
+"SERVERNAME must be given via command line or configuration file."
+ _KEYPRINT_z="Key must be given via -k option or configuration file."
+ _KEYPRINT_bad="Invalid key fingerprint: "
+ _WORKDIR_bad="Directory does not exist or is not writable: "
+
+ if [ -z "${SERVERNAME}" ]; then
+ echo -n "`basename $0`: "
+ echo "${_SERVERNAME_z}"
+ exit 1
+ fi
+ if [ -z "${KEYPRINT}" ]; then
+ echo -n "`basename $0`: "
+ echo "${_KEYPRINT_z}"
+ exit 1
+ fi
+ if ! echo "${KEYPRINT}" | grep -qE "^[0-9a-f]{64}$"; then
+ echo -n "`basename $0`: "
+ echo -n "${_KEYPRINT_bad}"
+ echo ${KEYPRINT}
+ exit 1
+ fi
+ if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
+ echo -n "`basename $0`: "
+ echo -n "${_WORKDIR_bad}"
+ echo ${WORKDIR}
+ exit 1
+ fi
+ cd ${WORKDIR} || exit 1
+
+ # Generate release number. The s/SECURITY/RELEASE/ bit exists
+ # to provide an upgrade path for FreeBSD Update 1.x users, since
+ # the kernels provided by FreeBSD Update 1.x are always labelled
+ # as X.Y-SECURITY.
+ RELNUM=`uname -r |
+ sed -E 's,-p[0-9]+,,' |
+ sed -E 's,-SECURITY,-RELEASE,'`
+ ARCH=`uname -m`
+ FETCHDIR=${RELNUM}/${ARCH}
+ PATCHDIR=${RELNUM}/${ARCH}/bp
+
+ # Figure out what directory contains the running kernel
+ BOOTFILE=`sysctl -n kern.bootfile`
+ KERNELDIR=${BOOTFILE%/kernel}
+ if ! [ -d ${KERNELDIR} ]; then
+ echo "Cannot identify running kernel"
+ exit 1
+ fi
+
+ # Figure out what kernel configuration is running. We start with
+ # the output of `uname -i`, and then make the following adjustments:
+ # 1. Replace "SMP-GENERIC" with "SMP". Why the SMP kernel config
+ # file says "ident SMP-GENERIC", I don't know...
+ # 2. If the kernel claims to be GENERIC _and_ ${ARCH} is "amd64"
+ # _and_ `sysctl kern.version` contains a line which ends "/SMP", then
+ # we're running an SMP kernel. This mis-identification is a bug
+ # which was fixed in 6.2-STABLE.
+ KERNCONF=`uname -i`
+ if [ ${KERNCONF} = "SMP-GENERIC" ]; then
+ KERNCONF=SMP
+ fi
+ if [ ${KERNCONF} = "GENERIC" ] && [ ${ARCH} = "amd64" ]; then
+ if sysctl kern.version | grep -qE '/SMP$'; then
+ KERNCONF=SMP
+ fi
+ fi
+
+ # Define some paths
+ SHA256=/sbin/sha256
+ PHTTPGET=/usr/libexec/phttpget
+
+ # Set up variables relating to VERBOSELEVEL
+ fetch_setup_verboselevel
+}
+
+#### Core functionality -- the actual work gets done here
+
+# Use an SRV query to pick a server. If the SRV query doesn't provide
+# a useful answer, use the server name specified by the user.
+# Put another way... look up _http._tcp.${SERVERNAME} and pick a server
+# from that; or if no servers are returned, use ${SERVERNAME}.
+# This allows a user to specify "portsnap.freebsd.org" (in which case
+# portsnap will select one of the mirrors) or "portsnap5.tld.freebsd.org"
+# (in which case portsnap will use that particular server, since there
+# won't be an SRV entry for that name).
+#
+# We ignore the Port field, since we are always going to use port 80.
+
+# Fetch the mirror list, but do not pick a mirror yet. Returns 1 if
+# no mirrors are available for any reason.
+fetch_pick_server_init () {
+ : > serverlist_tried
+
+# Check that host(1) exists (i.e., that the system wasn't built with the
+# WITHOUT_BIND set) and don't try to find a mirror if it doesn't exist.
+ if ! which -s host; then
+ : > serverlist_full
+ return 1
+ fi
+
+ echo -n "Looking up ${SERVERNAME} mirrors... "
+
+# Issue the SRV query and pull out the Priority, Weight, and Target fields.
+# BIND 9 prints "$name has SRV record ..." while BIND 8 prints
+# "$name server selection ..."; we allow either format.
+ MLIST="_http._tcp.${SERVERNAME}"
+ host -t srv "${MLIST}" |
+ sed -nE "s/${MLIST} (has SRV record|server selection) //p" |
+ cut -f 1,2,4 -d ' ' |
+ sed -e 's/\.$//' |
+ sort > serverlist_full
+
+# If no records, give up -- we'll just use the server name we were given.
+ if [ `wc -l < serverlist_full` -eq 0 ]; then
+ echo "none found."
+ return 1
+ fi
+
+# Report how many mirrors we found.
+ echo `wc -l < serverlist_full` "mirrors found."
+
+# Generate a random seed for use in picking mirrors. If HTTP_PROXY
+# is set, this will be used to generate the seed; otherwise, the seed
+# will be random.
+ if [ -n "${HTTP_PROXY}${http_proxy}" ]; then
+ RANDVALUE=`sha256 -qs "${HTTP_PROXY}${http_proxy}" |
+ tr -d 'a-f' |
+ cut -c 1-9`
+ else
+ RANDVALUE=`jot -r 1 0 999999999`
+ fi
+}
+
+# Pick a mirror. Returns 1 if we have run out of mirrors to try.
+fetch_pick_server () {
+# Generate a list of not-yet-tried mirrors
+ sort serverlist_tried |
+ comm -23 serverlist_full - > serverlist
+
+# Have we run out of mirrors?
+ if [ `wc -l < serverlist` -eq 0 ]; then
+ echo "No mirrors remaining, giving up."
+ return 1
+ fi
+
+# Find the highest priority level (lowest numeric value).
+ SRV_PRIORITY=`cut -f 1 -d ' ' serverlist | sort -n | head -1`
+
+# Add up the weights of the response lines at that priority level.
+ SRV_WSUM=0;
+ while read X; do
+ case "$X" in
+ ${SRV_PRIORITY}\ *)
+ SRV_W=`echo $X | cut -f 2 -d ' '`
+ SRV_WSUM=$(($SRV_WSUM + $SRV_W))
+ ;;
+ esac
+ done < serverlist
+
+# If all the weights are 0, pretend that they are all 1 instead.
+ if [ ${SRV_WSUM} -eq 0 ]; then
+ SRV_WSUM=`grep -E "^${SRV_PRIORITY} " serverlist | wc -l`
+ SRV_W_ADD=1
+ else
+ SRV_W_ADD=0
+ fi
+
+# Pick a value between 0 and the sum of the weights - 1
+ SRV_RND=`expr ${RANDVALUE} % ${SRV_WSUM}`
+
+# Read through the list of mirrors and set SERVERNAME. Write the line
+# corresponding to the mirror we selected into serverlist_tried so that
+# we won't try it again.
+ while read X; do
+ case "$X" in
+ ${SRV_PRIORITY}\ *)
+ SRV_W=`echo $X | cut -f 2 -d ' '`
+ SRV_W=$(($SRV_W + $SRV_W_ADD))
+ if [ $SRV_RND -lt $SRV_W ]; then
+ SERVERNAME=`echo $X | cut -f 3 -d ' '`
+ echo "$X" >> serverlist_tried
+ break
+ else
+ SRV_RND=$(($SRV_RND - $SRV_W))
+ fi
+ ;;
+ esac
+ done < serverlist
+}
+
+# Take a list of ${oldhash}|${newhash} and output a list of needed patches,
+# i.e., those for which we have ${oldhash} and don't have ${newhash}.
+fetch_make_patchlist () {
+ grep -vE "^([0-9a-f]{64})\|\1$" |
+ tr '|' ' ' |
+ while read X Y; do
+ if [ -f "files/${Y}.gz" ] ||
+ [ ! -f "files/${X}.gz" ]; then
+ continue
+ fi
+ echo "${X}|${Y}"
+ done | uniq
+}
+
+# Print user-friendly progress statistics
+fetch_progress () {
+ LNC=0
+ while read x; do
+ LNC=$(($LNC + 1))
+ if [ $(($LNC % 10)) = 0 ]; then
+ echo -n $LNC
+ elif [ $(($LNC % 2)) = 0 ]; then
+ echo -n .
+ fi
+ done
+ echo -n " "
+}
+
+# Function for asking the user if everything is ok
+continuep () {
+ while read -p "Does this look reasonable (y/n)? " CONTINUE; do
+ case "${CONTINUE}" in
+ y*)
+ return 0
+ ;;
+ n*)
+ return 1
+ ;;
+ esac
+ done
+}
+
+# Initialize the working directory
+workdir_init () {
+ mkdir -p files
+ touch tINDEX.present
+}
+
+# Check that we have a public key with an appropriate hash, or
+# fetch the key if it doesn't exist. Returns 1 if the key has
+# not yet been fetched.
+fetch_key () {
+ if [ -r pub.ssl ] && [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then
+ return 0
+ fi
+
+ echo -n "Fetching public key from ${SERVERNAME}... "
+ rm -f pub.ssl
+ fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/pub.ssl \
+ 2>${QUIETREDIR} || true
+ if ! [ -r pub.ssl ]; then
+ echo "failed."
+ return 1
+ fi
+ if ! [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then
+ echo "key has incorrect hash."
+ rm -f pub.ssl
+ return 1
+ fi
+ echo "done."
+}
+
+# Fetch metadata signature, aka "tag".
+fetch_tag () {
+ echo -n "Fetching metadata signature "
+ echo ${NDEBUG} "for ${RELNUM} from ${SERVERNAME}... "
+ rm -f latest.ssl
+ fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/latest.ssl \
+ 2>${QUIETREDIR} || true
+ if ! [ -r latest.ssl ]; then
+ echo "failed."
+ return 1
+ fi
+
+ openssl rsautl -pubin -inkey pub.ssl -verify \
+ < latest.ssl > tag.new 2>${QUIETREDIR} || true
+ rm latest.ssl
+
+ if ! [ `wc -l < tag.new` = 1 ] ||
+ ! grep -qE \
+ "^freebsd-update\|${ARCH}\|${RELNUM}\|[0-9]+\|[0-9a-f]{64}\|[0-9]{10}" \
+ tag.new; then
+ echo "invalid signature."
+ return 1
+ fi
+
+ echo "done."
+
+ RELPATCHNUM=`cut -f 4 -d '|' < tag.new`
+ TINDEXHASH=`cut -f 5 -d '|' < tag.new`
+ EOLTIME=`cut -f 6 -d '|' < tag.new`
+}
+
+# Sanity-check the patch number in a tag, to make sure that we're not
+# going to "update" backwards and to prevent replay attacks.
+fetch_tagsanity () {
+ # Check that we're not going to move from -pX to -pY with Y < X.
+ RELPX=`uname -r | sed -E 's,.*-,,'`
+ if echo ${RELPX} | grep -qE '^p[0-9]+$'; then
+ RELPX=`echo ${RELPX} | cut -c 2-`
+ else
+ RELPX=0
+ fi
+ if [ "${RELPATCHNUM}" -lt "${RELPX}" ]; then
+ echo
+ echo -n "Files on mirror (${RELNUM}-p${RELPATCHNUM})"
+ echo " appear older than what"
+ echo "we are currently running (`uname -r`)!"
+ echo "Cowardly refusing to proceed any further."
+ return 1
+ fi
+
+ # If "tag" exists and corresponds to ${RELNUM}, make sure that
+ # it contains a patch number <= RELPATCHNUM, in order to protect
+ # against rollback (replay) attacks.
+ if [ -f tag ] &&
+ grep -qE \
+ "^freebsd-update\|${ARCH}\|${RELNUM}\|[0-9]+\|[0-9a-f]{64}\|[0-9]{10}" \
+ tag; then
+ LASTRELPATCHNUM=`cut -f 4 -d '|' < tag`
+
+ if [ "${RELPATCHNUM}" -lt "${LASTRELPATCHNUM}" ]; then
+ echo
+ echo -n "Files on mirror (${RELNUM}-p${RELPATCHNUM})"
+ echo " are older than the"
+ echo -n "most recently seen updates"
+ echo " (${RELNUM}-p${LASTRELPATCHNUM})."
+ echo "Cowardly refusing to proceed any further."
+ return 1
+ fi
+ fi
+}
+
+# Fetch metadata index file
+fetch_metadata_index () {
+ echo ${NDEBUG} "Fetching metadata index... "
+ rm -f ${TINDEXHASH}
+ fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/t/${TINDEXHASH}
+ 2>${QUIETREDIR}
+ if ! [ -f ${TINDEXHASH} ]; then
+ echo "failed."
+ return 1
+ fi
+ if [ `${SHA256} -q ${TINDEXHASH}` != ${TINDEXHASH} ]; then
+ echo "update metadata index corrupt."
+ return 1
+ fi
+ echo "done."
+}
+
+# Print an error message about signed metadata being bogus.
+fetch_metadata_bogus () {
+ echo
+ echo "The update metadata$1 is correctly signed, but"
+ echo "failed an integrity check."
+ echo "Cowardly refusing to proceed any further."
+ return 1
+}
+
+# Construct tINDEX.new by merging the lines named in $1 from ${TINDEXHASH}
+# with the lines not named in $@ from tINDEX.present (if that file exists).
+fetch_metadata_index_merge () {
+ for METAFILE in $@; do
+ if [ `grep -E "^${METAFILE}\|" ${TINDEXHASH} | wc -l` \
+ -ne 1 ]; then
+ fetch_metadata_bogus " index"
+ return 1
+ fi
+
+ grep -E "${METAFILE}\|" ${TINDEXHASH}
+ done |
+ sort > tINDEX.wanted
+
+ if [ -f tINDEX.present ]; then
+ join -t '|' -v 2 tINDEX.wanted tINDEX.present |
+ sort -m - tINDEX.wanted > tINDEX.new
+ rm tINDEX.wanted
+ else
+ mv tINDEX.wanted tINDEX.new
+ fi
+}
+
+# Sanity check all the lines of tINDEX.new. Even if more metadata lines
+# are added by future versions of the server, this won't cause problems,
+# since the only lines which appear in tINDEX.new are the ones which we
+# specifically grepped out of ${TINDEXHASH}.
+fetch_metadata_index_sanity () {
+ if grep -qvE '^[0-9A-Z.-]+\|[0-9a-f]{64}$' tINDEX.new; then
+ fetch_metadata_bogus " index"
+ return 1
+ fi
+}
+
+# Sanity check the metadata file $1.
+fetch_metadata_sanity () {
+ # Some aliases to save space later: ${P} is a character which can
+ # appear in a path; ${M} is the four numeric metadata fields; and
+ # ${H} is a sha256 hash.
+ P="[-+./:=,%@_[~[:alnum:]]"
+ M="[0-9]+\|[0-9]+\|[0-9]+\|[0-9]+"
+ H="[0-9a-f]{64}"
+
+ # Check that the first four fields make sense.
+ if gunzip -c < files/$1.gz |
+ grep -qvE "^[a-z]+\|[0-9a-z]+\|${P}+\|[fdL-]\|"; then
+ fetch_metadata_bogus ""
+ return 1
+ fi
+
+ # Remove the first three fields.
+ gunzip -c < files/$1.gz |
+ cut -f 4- -d '|' > sanitycheck.tmp
+
+ # Sanity check entries with type 'f'
+ if grep -E '^f' sanitycheck.tmp |
+ grep -qvE "^f\|${M}\|${H}\|${P}*\$"; then
+ fetch_metadata_bogus ""
+ return 1
+ fi
+
+ # Sanity check entries with type 'd'
+ if grep -E '^d' sanitycheck.tmp |
+ grep -qvE "^d\|${M}\|\|\$"; then
+ fetch_metadata_bogus ""
+ return 1
+ fi
+
+ # Sanity check entries with type 'L'
+ if grep -E '^L' sanitycheck.tmp |
+ grep -qvE "^L\|${M}\|${P}*\|\$"; then
+ fetch_metadata_bogus ""
+ return 1
+ fi
+
+ # Sanity check entries with type '-'
+ if grep -E '^-' sanitycheck.tmp |
+ grep -qvE "^-\|\|\|\|\|\|"; then
+ fetch_metadata_bogus ""
+ return 1
+ fi
+
+ # Clean up
+ rm sanitycheck.tmp
+}
+
+# Fetch the metadata index and metadata files listed in $@,
+# taking advantage of metadata patches where possible.
+fetch_metadata () {
+ fetch_metadata_index || return 1
+ fetch_metadata_index_merge $@ || return 1
+ fetch_metadata_index_sanity || return 1
+
+ # Generate a list of wanted metadata patches
+ join -t '|' -o 1.2,2.2 tINDEX.present tINDEX.new |
+ fetch_make_patchlist > patchlist
+
+ if [ -s patchlist ]; then
+ # Attempt to fetch metadata patches
+ echo -n "Fetching `wc -l < patchlist | tr -d ' '` "
+ echo ${NDEBUG} "metadata patches.${DDSTATS}"
+ tr '|' '-' < patchlist |
+ lam -s "${FETCHDIR}/tp/" - -s ".gz" |
+ xargs ${XARGST} ${PHTTPGET} ${SERVERNAME} \
+ 2>${STATSREDIR} | fetch_progress
+ echo "done."
+
+ # Attempt to apply metadata patches
+ echo -n "Applying metadata patches... "
+ tr '|' ' ' < patchlist |
+ while read X Y; do
+ if [ ! -f "${X}-${Y}.gz" ]; then continue; fi
+ gunzip -c < ${X}-${Y}.gz > diff
+ gunzip -c < files/${X}.gz > diff-OLD
+
+ # Figure out which lines are being added and removed
+ grep -E '^-' diff |
+ cut -c 2- |
+ while read PREFIX; do
+ look "${PREFIX}" diff-OLD
+ done |
+ sort > diff-rm
+ grep -E '^\+' diff |
+ cut -c 2- > diff-add
+
+ # Generate the new file
+ comm -23 diff-OLD diff-rm |
+ sort - diff-add > diff-NEW
+
+ if [ `${SHA256} -q diff-NEW` = ${Y} ]; then
+ mv diff-NEW files/${Y}
+ gzip -n files/${Y}
+ else
+ mv diff-NEW ${Y}.bad
+ fi
+ rm -f ${X}-${Y}.gz diff
+ rm -f diff-OLD diff-NEW diff-add diff-rm
+ done 2>${QUIETREDIR}
+ echo "done."
+ fi
+
+ # Update metadata without patches
+ cut -f 2 -d '|' < tINDEX.new |
+ while read Y; do
+ if [ ! -f "files/${Y}.gz" ]; then
+ echo ${Y};
+ fi
+ done |
+ sort -u > filelist
+
+ if [ -s filelist ]; then
+ echo -n "Fetching `wc -l < filelist | tr -d ' '` "
+ echo ${NDEBUG} "metadata files... "
+ lam -s "${FETCHDIR}/m/" - -s ".gz" < filelist |
+ xargs ${XARGST} ${PHTTPGET} ${SERVERNAME} \
+ 2>${QUIETREDIR}
+
+ while read Y; do
+ if ! [ -f ${Y}.gz ]; then
+ echo "failed."
+ return 1
+ fi
+ if [ `gunzip -c < ${Y}.gz |
+ ${SHA256} -q` = ${Y} ]; then
+ mv ${Y}.gz files/${Y}.gz
+ else
+ echo "metadata is corrupt."
+ return 1
+ fi
+ done < filelist
+ echo "done."
+ fi
+
+# Sanity-check the metadata files.
+ cut -f 2 -d '|' tINDEX.new > filelist
+ while read X; do
+ fetch_metadata_sanity ${X} || return 1
+ done < filelist
+
+# Remove files which are no longer needed
+ cut -f 2 -d '|' tINDEX.present |
+ sort > oldfiles
+ cut -f 2 -d '|' tINDEX.new |
+ sort |
+ comm -13 - oldfiles |
+ lam -s "files/" - -s ".gz" |
+ xargs rm -f
+ rm patchlist filelist oldfiles
+ rm ${TINDEXHASH}
+
+# We're done!
+ mv tINDEX.new tINDEX.present
+ mv tag.new tag
+
+ return 0
+}
+
+# Extract a subset of a downloaded metadata file containing only the parts
+# which are listed in COMPONENTS.
+fetch_filter_metadata_components () {
+ METAHASH=`look "$1|" tINDEX.present | cut -f 2 -d '|'`
+ gunzip -c < files/${METAHASH}.gz > $1.all
+
+ # Fish out the lines belonging to components we care about.
+ for C in ${COMPONENTS}; do
+ look "`echo ${C} | tr '/' '|'`|" $1.all
+ done > $1
+
+ # Remove temporary file.
+ rm $1.all
+}
+
+# Generate a filtered version of the metadata file $1 from the downloaded
+# file, by fishing out the lines corresponding to components we're trying
+# to keep updated, and then removing lines corresponding to paths we want
+# to ignore.
+fetch_filter_metadata () {
+ # Fish out the lines belonging to components we care about.
+ fetch_filter_metadata_components $1
+
+ # Canonicalize directory names by removing any trailing / in
+ # order to avoid listing directories multiple times if they
+ # belong to multiple components. Turning "/" into "" doesn't
+ # matter, since we add a leading "/" when we use paths later.
+ cut -f 3- -d '|' $1 |
+ sed -e 's,/|d|,|d|,' |
+ sed -e 's,/|-|,|-|,' |
+ sort -u > $1.tmp
+
+ # Figure out which lines to ignore and remove them.
+ for X in ${IGNOREPATHS}; do
+ grep -E "^${X}" $1.tmp
+ done |
+ sort -u |
+ comm -13 - $1.tmp > $1
+
+ # Remove temporary files.
+ rm $1.tmp
+}
+
+# Filter the metadata file $1 by adding lines with "/boot/$2"
+# replaced by ${KERNELDIR} (which is `sysctl -n kern.bootfile` minus the
+# trailing "/kernel"); and if "/boot/$2" does not exist, remove
+# the original lines which start with that.
+# Put another way: Deal with the fact that the FOO kernel is sometimes
+# installed in /boot/FOO/ and is sometimes installed elsewhere.
+fetch_filter_kernel_names () {
+ grep ^/boot/$2 $1 |
+ sed -e "s,/boot/$2,${KERNELDIR},g" |
+ sort - $1 > $1.tmp
+ mv $1.tmp $1
+
+ if ! [ -d /boot/$2 ]; then
+ grep -v ^/boot/$2 $1 > $1.tmp
+ mv $1.tmp $1
+ fi
+}
+
+# For all paths appearing in $1 or $3, inspect the system
+# and generate $2 describing what is currently installed.
+fetch_inspect_system () {
+ # No errors yet...
+ rm -f .err
+
+ # Tell the user why his disk is suddenly making lots of noise
+ echo -n "Inspecting system... "
+
+ # Generate list of files to inspect
+ cat $1 $3 |
+ cut -f 1 -d '|' |
+ sort -u > filelist
+
+ # Examine each file and output lines of the form
+ # /path/to/file|type|device-inum|user|group|perm|flags|value
+ # sorted by device and inode number.
+ while read F; do
+ # If the symlink/file/directory does not exist, record this.
+ if ! [ -e ${BASEDIR}/${F} ]; then
+ echo "${F}|-||||||"
+ continue
+ fi
+ if ! [ -r ${BASEDIR}/${F} ]; then
+ echo "Cannot read file: ${BASEDIR}/${F}" \
+ >/dev/stderr
+ touch .err
+ return 1
+ fi
+
+ # Otherwise, output an index line.
+ if [ -L ${BASEDIR}/${F} ]; then
+ echo -n "${F}|L|"
+ stat -n -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F};
+ readlink ${BASEDIR}/${F};
+ elif [ -f ${BASEDIR}/${F} ]; then
+ echo -n "${F}|f|"
+ stat -n -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F};
+ sha256 -q ${BASEDIR}/${F};
+ elif [ -d ${BASEDIR}/${F} ]; then
+ echo -n "${F}|d|"
+ stat -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F};
+ else
+ echo "Unknown file type: ${BASEDIR}/${F}" \
+ >/dev/stderr
+ touch .err
+ return 1
+ fi
+ done < filelist |
+ sort -k 3,3 -t '|' > $2.tmp
+ rm filelist
+
+ # Check if an error occurred during system inspection
+ if [ -f .err ]; then
+ return 1
+ fi
+
+ # Convert to the form
+ # /path/to/file|type|user|group|perm|flags|value|hlink
+ # by resolving identical device and inode numbers into hard links.
+ cut -f 1,3 -d '|' $2.tmp |
+ sort -k 1,1 -t '|' |
+ sort -s -u -k 2,2 -t '|' |
+ join -1 2 -2 3 -t '|' - $2.tmp |
+ awk -F \| -v OFS=\| \
+ '{
+ if (($2 == $3) || ($4 == "-"))
+ print $3,$4,$5,$6,$7,$8,$9,""
+ else
+ print $3,$4,$5,$6,$7,$8,$9,$2
+ }' |
+ sort > $2
+ rm $2.tmp
+
+ # We're finished looking around
+ echo "done."
+}
+
+# For any paths matching ${MERGECHANGES}, compare $1 and $2 and find any
+# files which differ; generate $3 containing these paths and the old hashes.
+fetch_filter_mergechanges () {
+ # Pull out the paths and hashes of the files matching ${MERGECHANGES}.
+ for F in $1 $2; do
+ for X in ${MERGECHANGES}; do
+ grep -E "^${X}" ${F}
+ done |
+ cut -f 1,2,7 -d '|' |
+ sort > ${F}-values
+ done
+
+ # Any line in $2-values which doesn't appear in $1-values and is a
+ # file means that we should list the path in $3.
+ comm -13 $1-values $2-values |
+ fgrep '|f|' |
+ cut -f 1 -d '|' > $2-paths
+
+ # For each path, pull out one (and only one!) entry from $1-values.
+ # Note that we cannot distinguish which "old" version the user made
+ # changes to; but hopefully any changes which occur due to security
+ # updates will exist in both the "new" version and the version which
+ # the user has installed, so the merging will still work.
+ while read X; do
+ look "${X}|" $1-values |
+ head -1
+ done < $2-paths > $3
+
+ # Clean up
+ rm $1-values $2-values $2-paths
+}
+
+# For any paths matching ${UPDATEIFUNMODIFIED}, remove lines from $[123]
+# which correspond to lines in $2 with hashes not matching $1 or $3, unless
+# the paths are listed in $4. For entries in $2 marked "not present"
+# (aka. type -), remove lines from $[123] unless there is a corresponding
+# entry in $1.
+fetch_filter_unmodified_notpresent () {
+ # Figure out which lines of $1 and $3 correspond to bits which
+ # should only be updated if they haven't changed, and fish out
+ # the (path, type, value) tuples.
+ # NOTE: We don't consider a file to be "modified" if it matches
+ # the hash from $3.
+ for X in ${UPDATEIFUNMODIFIED}; do
+ grep -E "^${X}" $1
+ grep -E "^${X}" $3
+ done |
+ cut -f 1,2,7 -d '|' |
+ sort > $1-values
+
+ # Do the same for $2.
+ for X in ${UPDATEIFUNMODIFIED}; do
+ grep -E "^${X}" $2
+ done |
+ cut -f 1,2,7 -d '|' |
+ sort > $2-values
+
+ # Any entry in $2-values which is not in $1-values corresponds to
+ # a path which we need to remove from $1, $2, and $3, unless it
+ # that path appears in $4.
+ comm -13 $1-values $2-values |
+ sort -t '|' -k 1,1 > mlines.tmp
+ cut -f 1 -d '|' $4 |
+ sort |
+ join -v 2 -t '|' - mlines.tmp |
+ sort > mlines
+ rm $1-values $2-values mlines.tmp
+
+ # Any lines in $2 which are not in $1 AND are "not present" lines
+ # also belong in mlines.
+ comm -13 $1 $2 |
+ cut -f 1,2,7 -d '|' |
+ fgrep '|-|' >> mlines
+
+ # Remove lines from $1, $2, and $3
+ for X in $1 $2 $3; do
+ sort -t '|' -k 1,1 ${X} > ${X}.tmp
+ cut -f 1 -d '|' < mlines |
+ sort |
+ join -v 2 -t '|' - ${X}.tmp |
+ sort > ${X}
+ rm ${X}.tmp
+ done
+
+ # Store a list of the modified files, for future reference
+ fgrep -v '|-|' mlines |
+ cut -f 1 -d '|' > modifiedfiles
+ rm mlines
+}
+
+# For each entry in $1 of type -, remove any corresponding
+# entry from $2 if ${ALLOWADD} != "yes". Remove all entries
+# of type - from $1.
+fetch_filter_allowadd () {
+ cut -f 1,2 -d '|' < $1 |
+ fgrep '|-' |
+ cut -f 1 -d '|' > filesnotpresent
+
+ if [ ${ALLOWADD} != "yes" ]; then
+ sort < $2 |
+ join -v 1 -t '|' - filesnotpresent |
+ sort > $2.tmp
+ mv $2.tmp $2
+ fi
+
+ sort < $1 |
+ join -v 1 -t '|' - filesnotpresent |
+ sort > $1.tmp
+ mv $1.tmp $1
+ rm filesnotpresent
+}
+
+# If ${ALLOWDELETE} != "yes", then remove any entries from $1
+# which don't correspond to entries in $2.
+fetch_filter_allowdelete () {
+ # Produce a lists ${PATH}|${TYPE}
+ for X in $1 $2; do
+ cut -f 1-2 -d '|' < ${X} |
+ sort -u > ${X}.nodes
+ done
+
+ # Figure out which lines need to be removed from $1.
+ if [ ${ALLOWDELETE} != "yes" ]; then
+ comm -23 $1.nodes $2.nodes > $1.badnodes
+ else
+ : > $1.badnodes
+ fi
+
+ # Remove the relevant lines from $1
+ while read X; do
+ look "${X}|" $1
+ done < $1.badnodes |
+ comm -13 - $1 > $1.tmp
+ mv $1.tmp $1
+
+ rm $1.badnodes $1.nodes $2.nodes
+}
+
+# If ${KEEPMODIFIEDMETADATA} == "yes", then for each entry in $2
+# with metadata not matching any entry in $1, replace the corresponding
+# line of $3 with one having the same metadata as the entry in $2.
+fetch_filter_modified_metadata () {
+ # Fish out the metadata from $1 and $2
+ for X in $1 $2; do
+ cut -f 1-6 -d '|' < ${X} > ${X}.metadata
+ done
+
+ # Find the metadata we need to keep
+ if [ ${KEEPMODIFIEDMETADATA} = "yes" ]; then
+ comm -13 $1.metadata $2.metadata > keepmeta
+ else
+ : > keepmeta
+ fi
+
+ # Extract the lines which we need to remove from $3, and
+ # construct the lines which we need to add to $3.
+ : > $3.remove
+ : > $3.add
+ while read LINE; do
+ NODE=`echo "${LINE}" | cut -f 1-2 -d '|'`
+ look "${NODE}|" $3 >> $3.remove
+ look "${NODE}|" $3 |
+ cut -f 7- -d '|' |
+ lam -s "${LINE}|" - >> $3.add
+ done < keepmeta
+
+ # Remove the specified lines and add the new lines.
+ sort $3.remove |
+ comm -13 - $3 |
+ sort -u - $3.add > $3.tmp
+ mv $3.tmp $3
+
+ rm keepmeta $1.metadata $2.metadata $3.add $3.remove
+}
+
+# Remove lines from $1 and $2 which are identical;
+# no need to update a file if it isn't changing.
+fetch_filter_uptodate () {
+ comm -23 $1 $2 > $1.tmp
+ comm -13 $1 $2 > $2.tmp
+
+ mv $1.tmp $1
+ mv $2.tmp $2
+}
+
+# Fetch any "clean" old versions of files we need for merging changes.
+fetch_files_premerge () {
+ # We only need to do anything if $1 is non-empty.
+ if [ -s $1 ]; then
+ # Tell the user what we're doing
+ echo -n "Fetching files from ${OLDRELNUM} for merging... "
+
+ # List of files wanted
+ fgrep '|f|' < $1 |
+ cut -f 3 -d '|' |
+ sort -u > files.wanted
+
+ # Only fetch the files we don't already have
+ while read Y; do
+ if [ ! -f "files/${Y}.gz" ]; then
+ echo ${Y};
+ fi
+ done < files.wanted > filelist
+
+ # Actually fetch them
+ lam -s "${OLDFETCHDIR}/f/" - -s ".gz" < filelist |
+ xargs ${XARGST} ${PHTTPGET} ${SERVERNAME} \
+ 2>${QUIETREDIR}
+
+ # Make sure we got them all, and move them into /files/
+ while read Y; do
+ if ! [ -f ${Y}.gz ]; then
+ echo "failed."
+ return 1
+ fi
+ if [ `gunzip -c < ${Y}.gz |
+ ${SHA256} -q` = ${Y} ]; then
+ mv ${Y}.gz files/${Y}.gz
+ else
+ echo "${Y} has incorrect hash."
+ return 1
+ fi
+ done < filelist
+ echo "done."
+
+ # Clean up
+ rm filelist files.wanted
+ fi
+}
+
+# Prepare to fetch files: Generate a list of the files we need,
+# copy the unmodified files we have into /files/, and generate
+# a list of patches to download.
+fetch_files_prepare () {
+ # Tell the user why his disk is suddenly making lots of noise
+ echo -n "Preparing to download files... "
+
+ # Reduce indices to ${PATH}|${HASH} pairs
+ for X in $1 $2 $3; do
+ cut -f 1,2,7 -d '|' < ${X} |
+ fgrep '|f|' |
+ cut -f 1,3 -d '|' |
+ sort > ${X}.hashes
+ done
+
+ # List of files wanted
+ cut -f 2 -d '|' < $3.hashes |
+ sort -u |
+ while read HASH; do
+ if ! [ -f files/${HASH}.gz ]; then
+ echo ${HASH}
+ fi
+ done > files.wanted
+
+ # Generate a list of unmodified files
+ comm -12 $1.hashes $2.hashes |
+ sort -k 1,1 -t '|' > unmodified.files
+
+ # Copy all files into /files/. We only need the unmodified files
+ # for use in patching; but we'll want all of them if the user asks
+ # to rollback the updates later.
+ while read LINE; do
+ F=`echo "${LINE}" | cut -f 1 -d '|'`
+ HASH=`echo "${LINE}" | cut -f 2 -d '|'`
+
+ # Skip files we already have.
+ if [ -f files/${HASH}.gz ]; then
+ continue
+ fi
+
+ # Make sure the file hasn't changed.
+ cp "${BASEDIR}/${F}" tmpfile
+ if [ `sha256 -q tmpfile` != ${HASH} ]; then
+ echo
+ echo "File changed while FreeBSD Update running: ${F}"
+ return 1
+ fi
+
+ # Place the file into storage.
+ gzip -c < tmpfile > files/${HASH}.gz
+ rm tmpfile
+ done < $2.hashes
+
+ # Produce a list of patches to download
+ sort -k 1,1 -t '|' $3.hashes |
+ join -t '|' -o 2.2,1.2 - unmodified.files |
+ fetch_make_patchlist > patchlist
+
+ # Garbage collect
+ rm unmodified.files $1.hashes $2.hashes $3.hashes
+
+ # We don't need the list of possible old files any more.
+ rm $1
+
+ # We're finished making noise
+ echo "done."
+}
+
+# Fetch files.
+fetch_files () {
+ # Attempt to fetch patches
+ if [ -s patchlist ]; then
+ echo -n "Fetching `wc -l < patchlist | tr -d ' '` "
+ echo ${NDEBUG} "patches.${DDSTATS}"
+ tr '|' '-' < patchlist |
+ lam -s "${PATCHDIR}/" - |
+ xargs ${XARGST} ${PHTTPGET} ${SERVERNAME} \
+ 2>${STATSREDIR} | fetch_progress
+ echo "done."
+
+ # Attempt to apply patches
+ echo -n "Applying patches... "
+ tr '|' ' ' < patchlist |
+ while read X Y; do
+ if [ ! -f "${X}-${Y}" ]; then continue; fi
+ gunzip -c < files/${X}.gz > OLD
+
+ bspatch OLD NEW ${X}-${Y}
+
+ if [ `${SHA256} -q NEW` = ${Y} ]; then
+ mv NEW files/${Y}
+ gzip -n files/${Y}
+ fi
+ rm -f diff OLD NEW ${X}-${Y}
+ done 2>${QUIETREDIR}
+ echo "done."
+ fi
+
+ # Download files which couldn't be generate via patching
+ while read Y; do
+ if [ ! -f "files/${Y}.gz" ]; then
+ echo ${Y};
+ fi
+ done < files.wanted > filelist
+
+ if [ -s filelist ]; then
+ echo -n "Fetching `wc -l < filelist | tr -d ' '` "
+ echo ${NDEBUG} "files... "
+ lam -s "${FETCHDIR}/f/" - -s ".gz" < filelist |
+ xargs ${XARGST} ${PHTTPGET} ${SERVERNAME} \
+ 2>${QUIETREDIR}
+
+ while read Y; do
+ if ! [ -f ${Y}.gz ]; then
+ echo "failed."
+ return 1
+ fi
+ if [ `gunzip -c < ${Y}.gz |
+ ${SHA256} -q` = ${Y} ]; then
+ mv ${Y}.gz files/${Y}.gz
+ else
+ echo "${Y} has incorrect hash."
+ return 1
+ fi
+ done < filelist
+ echo "done."
+ fi
+
+ # Clean up
+ rm files.wanted filelist patchlist
+}
+
+# Create and populate install manifest directory; and report what updates
+# are available.
+fetch_create_manifest () {
+ # If we have an existing install manifest, nuke it.
+ if [ -L "${BDHASH}-install" ]; then
+ rm -r ${BDHASH}-install/
+ rm ${BDHASH}-install
+ fi
+
+ # Report to the user if any updates were avoided due to local changes
+ if [ -s modifiedfiles ]; then
+ echo
+ echo -n "The following files are affected by updates, "
+ echo "but no changes have"
+ echo -n "been downloaded because the files have been "
+ echo "modified locally:"
+ cat modifiedfiles
+ fi | $PAGER
+ rm modifiedfiles
+
+ # If no files will be updated, tell the user and exit
+ if ! [ -s INDEX-PRESENT ] &&
+ ! [ -s INDEX-NEW ]; then
+ rm INDEX-PRESENT INDEX-NEW
+ echo
+ echo -n "No updates needed to update system to "
+ echo "${RELNUM}-p${RELPATCHNUM}."
+ return
+ fi
+
+ # Divide files into (a) removed files, (b) added files, and
+ # (c) updated files.
+ cut -f 1 -d '|' < INDEX-PRESENT |
+ sort > INDEX-PRESENT.flist
+ cut -f 1 -d '|' < INDEX-NEW |
+ sort > INDEX-NEW.flist
+ comm -23 INDEX-PRESENT.flist INDEX-NEW.flist > files.removed
+ comm -13 INDEX-PRESENT.flist INDEX-NEW.flist > files.added
+ comm -12 INDEX-PRESENT.flist INDEX-NEW.flist > files.updated
+ rm INDEX-PRESENT.flist INDEX-NEW.flist
+
+ # Report removed files, if any
+ if [ -s files.removed ]; then
+ echo
+ echo -n "The following files will be removed "
+ echo "as part of updating to ${RELNUM}-p${RELPATCHNUM}:"
+ cat files.removed
+ fi | $PAGER
+ rm files.removed
+
+ # Report added files, if any
+ if [ -s files.added ]; then
+ echo
+ echo -n "The following files will be added "
+ echo "as part of updating to ${RELNUM}-p${RELPATCHNUM}:"
+ cat files.added
+ fi | $PAGER
+ rm files.added
+
+ # Report updated files, if any
+ if [ -s files.updated ]; then
+ echo
+ echo -n "The following files will be updated "
+ echo "as part of updating to ${RELNUM}-p${RELPATCHNUM}:"
+
+ cat files.updated
+ fi | $PAGER
+ rm files.updated
+
+ # Create a directory for the install manifest.
+ MDIR=`mktemp -d install.XXXXXX` || return 1
+
+ # Populate it
+ mv INDEX-PRESENT ${MDIR}/INDEX-OLD
+ mv INDEX-NEW ${MDIR}/INDEX-NEW
+
+ # Link it into place
+ ln -s ${MDIR} ${BDHASH}-install
+}
+
+# Warn about any upcoming EoL
+fetch_warn_eol () {
+ # What's the current time?
+ NOWTIME=`date "+%s"`
+
+ # When did we last warn about the EoL date?
+ if [ -f lasteolwarn ]; then
+ LASTWARN=`cat lasteolwarn`
+ else
+ LASTWARN=`expr ${NOWTIME} - 63072000`
+ fi
+
+ # If the EoL time is past, warn.
+ if [ ${EOLTIME} -lt ${NOWTIME} ]; then
+ echo
+ cat <<-EOF
+ WARNING: `uname -sr` HAS PASSED ITS END-OF-LIFE DATE.
+ Any security issues discovered after `date -r ${EOLTIME}`
+ will not have been corrected.
+ EOF
+ return 1
+ fi
+
+ # Figure out how long it has been since we last warned about the
+ # upcoming EoL, and how much longer we have left.
+ SINCEWARN=`expr ${NOWTIME} - ${LASTWARN}`
+ TIMELEFT=`expr ${EOLTIME} - ${NOWTIME}`
+
+ # Don't warn if the EoL is more than 3 months away
+ if [ ${TIMELEFT} -gt 7884000 ]; then
+ return 0
+ fi
+
+ # Don't warn if the time remaining is more than 3 times the time
+ # since the last warning.
+ if [ ${TIMELEFT} -gt `expr ${SINCEWARN} \* 3` ]; then
+ return 0
+ fi
+
+ # Figure out what time units to use.
+ if [ ${TIMELEFT} -lt 604800 ]; then
+ UNIT="day"
+ SIZE=86400
+ elif [ ${TIMELEFT} -lt 2678400 ]; then
+ UNIT="week"
+ SIZE=604800
+ else
+ UNIT="month"
+ SIZE=2678400
+ fi
+
+ # Compute the right number of units
+ NUM=`expr ${TIMELEFT} / ${SIZE}`
+ if [ ${NUM} != 1 ]; then
+ UNIT="${UNIT}s"
+ fi
+
+ # Print the warning
+ echo
+ cat <<-EOF
+ WARNING: `uname -sr` is approaching its End-of-Life date.
+ It is strongly recommended that you upgrade to a newer
+ release within the next ${NUM} ${UNIT}.
+ EOF
+
+ # Update the stored time of last warning
+ echo ${NOWTIME} > lasteolwarn
+}
+
+# Do the actual work involved in "fetch" / "cron".
+fetch_run () {
+ workdir_init || return 1
+
+ # Prepare the mirror list.
+ fetch_pick_server_init && fetch_pick_server
+
+ # Try to fetch the public key until we run out of servers.
+ while ! fetch_key; do
+ fetch_pick_server || return 1
+ done
+
+ # Try to fetch the metadata index signature ("tag") until we run
+ # out of available servers; and sanity check the downloaded tag.
+ while ! fetch_tag; do
+ fetch_pick_server || return 1
+ done
+ fetch_tagsanity || return 1
+
+ # Fetch the latest INDEX-NEW and INDEX-OLD files.
+ fetch_metadata INDEX-NEW INDEX-OLD || return 1
+
+ # Generate filtered INDEX-NEW and INDEX-OLD files containing only
+ # the lines which (a) belong to components we care about, and (b)
+ # don't correspond to paths we're explicitly ignoring.
+ fetch_filter_metadata INDEX-NEW || return 1
+ fetch_filter_metadata INDEX-OLD || return 1
+
+ # Translate /boot/${KERNCONF} into ${KERNELDIR}
+ fetch_filter_kernel_names INDEX-NEW ${KERNCONF}
+ fetch_filter_kernel_names INDEX-OLD ${KERNCONF}
+
+ # For all paths appearing in INDEX-OLD or INDEX-NEW, inspect the
+ # system and generate an INDEX-PRESENT file.
+ fetch_inspect_system INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1
+
+ # Based on ${UPDATEIFUNMODIFIED}, remove lines from INDEX-* which
+ # correspond to lines in INDEX-PRESENT with hashes not appearing
+ # in INDEX-OLD or INDEX-NEW. Also remove lines where the entry in
+ # INDEX-PRESENT has type - and there isn't a corresponding entry in
+ # INDEX-OLD with type -.
+ fetch_filter_unmodified_notpresent \
+ INDEX-OLD INDEX-PRESENT INDEX-NEW /dev/null
+
+ # For each entry in INDEX-PRESENT of type -, remove any corresponding
+ # entry from INDEX-NEW if ${ALLOWADD} != "yes". Remove all entries
+ # of type - from INDEX-PRESENT.
+ fetch_filter_allowadd INDEX-PRESENT INDEX-NEW
+
+ # If ${ALLOWDELETE} != "yes", then remove any entries from
+ # INDEX-PRESENT which don't correspond to entries in INDEX-NEW.
+ fetch_filter_allowdelete INDEX-PRESENT INDEX-NEW
+
+ # If ${KEEPMODIFIEDMETADATA} == "yes", then for each entry in
+ # INDEX-PRESENT with metadata not matching any entry in INDEX-OLD,
+ # replace the corresponding line of INDEX-NEW with one having the
+ # same metadata as the entry in INDEX-PRESENT.
+ fetch_filter_modified_metadata INDEX-OLD INDEX-PRESENT INDEX-NEW
+
+ # Remove lines from INDEX-PRESENT and INDEX-NEW which are identical;
+ # no need to update a file if it isn't changing.
+ fetch_filter_uptodate INDEX-PRESENT INDEX-NEW
+
+ # Prepare to fetch files: Generate a list of the files we need,
+ # copy the unmodified files we have into /files/, and generate
+ # a list of patches to download.
+ fetch_files_prepare INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1
+
+ # Fetch files.
+ fetch_files || return 1
+
+ # Create and populate install manifest directory; and report what
+ # updates are available.
+ fetch_create_manifest || return 1
+
+ # Warn about any upcoming EoL
+ fetch_warn_eol || return 1
+}
+
+# If StrictComponents is not "yes", generate a new components list
+# with only the components which appear to be installed.
+upgrade_guess_components () {
+ if [ "${STRICTCOMPONENTS}" = "no" ]; then
+ # Generate filtered INDEX-ALL with only the components listed
+ # in COMPONENTS.
+ fetch_filter_metadata_components $1 || return 1
+
+ # Tell the user why his disk is suddenly making lots of noise
+ echo -n "Inspecting system... "
+
+ # Look at the files on disk, and assume that a component is
+ # supposed to be present if it is more than half-present.
+ cut -f 1-3 -d '|' < INDEX-ALL |
+ tr '|' ' ' |
+ while read C S F; do
+ if [ -e ${BASEDIR}/${F} ]; then
+ echo "+ ${C}|${S}"
+ fi
+ echo "= ${C}|${S}"
+ done |
+ sort |
+ uniq -c |
+ sed -E 's,^ +,,' > compfreq
+ grep ' = ' compfreq |
+ cut -f 1,3 -d ' ' |
+ sort -k 2,2 -t ' ' > compfreq.total
+ grep ' + ' compfreq |
+ cut -f 1,3 -d ' ' |
+ sort -k 2,2 -t ' ' > compfreq.present
+ join -t ' ' -1 2 -2 2 compfreq.present compfreq.total |
+ while read S P T; do
+ if [ ${P} -gt `expr ${T} / 2` ]; then
+ echo ${S}
+ fi
+ done > comp.present
+ cut -f 2 -d ' ' < compfreq.total > comp.total
+ rm INDEX-ALL compfreq compfreq.total compfreq.present
+
+ # We're done making noise.
+ echo "done."
+
+ # Sometimes the kernel isn't installed where INDEX-ALL
+ # thinks that it should be: In particular, it is often in
+ # /boot/kernel instead of /boot/GENERIC or /boot/SMP. To
+ # deal with this, if "kernel|X" is listed in comp.total
+ # (i.e., is a component which would be upgraded if it is
+ # found to be present) we will add it to comp.present.
+ # If "kernel|<anything>" is in comp.total but "kernel|X" is
+ # not, we print a warning -- the user is running a kernel
+ # which isn't part of the release.
+ KCOMP=`echo ${KERNCONF} | tr 'A-Z' 'a-z'`
+ grep -E "^kernel\|${KCOMP}\$" comp.total >> comp.present
+
+ if grep -qE "^kernel\|" comp.total &&
+ ! grep -qE "^kernel\|${KCOMP}\$" comp.total; then
+ cat <<-EOF
+
+WARNING: This system is running a "${KCOMP}" kernel, which is not a
+kernel configuration distributed as part of FreeBSD ${RELNUM}.
+This kernel will not be updated: you MUST update the kernel manually
+before running "$0 install".
+ EOF
+ fi
+
+ # Re-sort the list of installed components and generate
+ # the list of non-installed components.
+ sort -u < comp.present > comp.present.tmp
+ mv comp.present.tmp comp.present
+ comm -13 comp.present comp.total > comp.absent
+
+ # Ask the user to confirm that what we have is correct. To
+ # reduce user confusion, translate "X|Y" back to "X/Y" (as
+ # subcomponents must be listed in the configuration file).
+ echo
+ echo -n "The following components of FreeBSD "
+ echo "seem to be installed:"
+ tr '|' '/' < comp.present |
+ fmt -72
+ echo
+ echo -n "The following components of FreeBSD "
+ echo "do not seem to be installed:"
+ tr '|' '/' < comp.absent |
+ fmt -72
+ echo
+ continuep || return 1
+ echo
+
+ # Suck the generated list of components into ${COMPONENTS}.
+ # Note that comp.present.tmp is used due to issues with
+ # pipelines and setting variables.
+ COMPONENTS=""
+ tr '|' '/' < comp.present > comp.present.tmp
+ while read C; do
+ COMPONENTS="${COMPONENTS} ${C}"
+ done < comp.present.tmp
+
+ # Delete temporary files
+ rm comp.present comp.present.tmp comp.absent comp.total
+ fi
+}
+
+# If StrictComponents is not "yes", COMPONENTS contains an entry
+# corresponding to the currently running kernel, and said kernel
+# does not exist in the new release, add "kernel/generic" to the
+# list of components.
+upgrade_guess_new_kernel () {
+ if [ "${STRICTCOMPONENTS}" = "no" ]; then
+ # Grab the unfiltered metadata file.
+ METAHASH=`look "$1|" tINDEX.present | cut -f 2 -d '|'`
+ gunzip -c < files/${METAHASH}.gz > $1.all
+
+ # If "kernel/${KCOMP}" is in ${COMPONENTS} and that component
+ # isn't in $1.all, we need to add kernel/generic.
+ for C in ${COMPONENTS}; do
+ if [ ${C} = "kernel/${KCOMP}" ] &&
+ ! grep -qE "^kernel\|${KCOMP}\|" $1.all; then
+ COMPONENTS="${COMPONENTS} kernel/generic"
+ NKERNCONF="GENERIC"
+ cat <<-EOF
+
+WARNING: This system is running a "${KCOMP}" kernel, which is not a
+kernel configuration distributed as part of FreeBSD ${RELNUM}.
+As part of upgrading to FreeBSD ${RELNUM}, this kernel will be
+replaced with a "generic" kernel.
+ EOF
+ continuep || return 1
+ fi
+ done
+
+ # Don't need this any more...
+ rm $1.all
+ fi
+}
+
+# Convert INDEX-OLD (last release) and INDEX-ALL (new release) into
+# INDEX-OLD and INDEX-NEW files (in the sense of normal upgrades).
+upgrade_oldall_to_oldnew () {
+ # For each ${F}|... which appears in INDEX-ALL but does not appear
+ # in INDEX-OLD, add ${F}|-|||||| to INDEX-OLD.
+ cut -f 1 -d '|' < $1 |
+ sort -u > $1.paths
+ cut -f 1 -d '|' < $2 |
+ sort -u |
+ comm -13 $1.paths - |
+ lam - -s "|-||||||" |
+ sort - $1 > $1.tmp
+ mv $1.tmp $1
+
+ # Remove lines from INDEX-OLD which also appear in INDEX-ALL
+ comm -23 $1 $2 > $1.tmp
+ mv $1.tmp $1
+
+ # Remove lines from INDEX-ALL which have a file name not appearing
+ # anywhere in INDEX-OLD (since these must be files which haven't
+ # changed -- if they were new, there would be an entry of type "-").
+ cut -f 1 -d '|' < $1 |
+ sort -u > $1.paths
+ sort -k 1,1 -t '|' < $2 |
+ join -t '|' - $1.paths |
+ sort > $2.tmp
+ rm $1.paths
+ mv $2.tmp $2
+
+ # Rename INDEX-ALL to INDEX-NEW.
+ mv $2 $3
+}
+
+# Helper for upgrade_merge: Return zero true iff the two files differ only
+# in the contents of their RCS tags.
+samef () {
+ X=`sed -E 's/\\$FreeBSD.*\\$/\$FreeBSD\$/' < $1 | ${SHA256}`
+ Y=`sed -E 's/\\$FreeBSD.*\\$/\$FreeBSD\$/' < $2 | ${SHA256}`
+
+ if [ $X = $Y ]; then
+ return 0;
+ else
+ return 1;
+ fi
+}
+
+# From the list of "old" files in $1, merge changes in $2 with those in $3,
+# and update $3 to reflect the hashes of merged files.
+upgrade_merge () {
+ # We only need to do anything if $1 is non-empty.
+ if [ -s $1 ]; then
+ cut -f 1 -d '|' $1 |
+ sort > $1-paths
+
+ # Create staging area for merging files
+ rm -rf merge/
+ while read F; do
+ D=`dirname ${F}`
+ mkdir -p merge/old/${D}
+ mkdir -p merge/${OLDRELNUM}/${D}
+ mkdir -p merge/${RELNUM}/${D}
+ mkdir -p merge/new/${D}
+ done < $1-paths
+
+ # Copy in files
+ while read F; do
+ # Currently installed file
+ V=`look "${F}|" $2 | cut -f 7 -d '|'`
+ gunzip < files/${V}.gz > merge/old/${F}
+
+ # Old release
+ if look "${F}|" $1 | fgrep -q "|f|"; then
+ V=`look "${F}|" $1 | cut -f 3 -d '|'`
+ gunzip < files/${V}.gz \
+ > merge/${OLDRELNUM}/${F}
+ fi
+
+ # New release
+ if look "${F}|" $3 | cut -f 1,2,7 -d '|' |
+ fgrep -q "|f|"; then
+ V=`look "${F}|" $3 | cut -f 7 -d '|'`
+ gunzip < files/${V}.gz \
+ > merge/${RELNUM}/${F}
+ fi
+ done < $1-paths
+
+ # Attempt to automatically merge changes
+ echo -n "Attempting to automatically merge "
+ echo -n "changes in files..."
+ : > failed.merges
+ while read F; do
+ # If the file doesn't exist in the new release,
+ # the result of "merging changes" is having the file
+ # not exist.
+ if ! [ -f merge/${RELNUM}/${F} ]; then
+ continue
+ fi
+
+ # If the file didn't exist in the old release, we're
+ # going to throw away the existing file and hope that
+ # the version from the new release is what we want.
+ if ! [ -f merge/${OLDRELNUM}/${F} ]; then
+ cp merge/${RELNUM}/${F} merge/new/${F}
+ continue
+ fi
+
+ # Some files need special treatment.
+ case ${F} in
+ /etc/spwd.db | /etc/pwd.db | /etc/login.conf.db)
+ # Don't merge these -- we're rebuild them
+ # after updates are installed.
+ cp merge/old/${F} merge/new/${F}
+ ;;
+ *)
+ if ! merge -p -L "current version" \
+ -L "${OLDRELNUM}" -L "${RELNUM}" \
+ merge/old/${F} \
+ merge/${OLDRELNUM}/${F} \
+ merge/${RELNUM}/${F} \
+ > merge/new/${F} 2>/dev/null; then
+ echo ${F} >> failed.merges
+ fi
+ ;;
+ esac
+ done < $1-paths
+ echo " done."
+
+ # Ask the user to handle any files which didn't merge.
+ while read F; do
+ # If the installed file differs from the version in
+ # the old release only due to RCS tag expansion
+ # then just use the version in the new release.
+ if samef merge/old/${F} merge/${OLDRELNUM}/${F}; then
+ cp merge/${RELNUM}/${F} merge/new/${F}
+ continue
+ fi
+
+ cat <<-EOF
+
+The following file could not be merged automatically: ${F}
+Press Enter to edit this file in ${EDITOR} and resolve the conflicts
+manually...
+ EOF
+ read dummy </dev/tty
+ ${EDITOR} `pwd`/merge/new/${F} < /dev/tty
+ done < failed.merges
+ rm failed.merges
+
+ # Ask the user to confirm that he likes how the result
+ # of merging files.
+ while read F; do
+ # Skip files which haven't changed except possibly
+ # in their RCS tags.
+ if [ -f merge/old/${F} ] && [ -f merge/new/${F} ] &&
+ samef merge/old/${F} merge/new/${F}; then
+ continue
+ fi
+
+ # Skip files where the installed file differs from
+ # the old file only due to RCS tags.
+ if [ -f merge/old/${F} ] &&
+ [ -f merge/${OLDRELNUM}/${F} ] &&
+ samef merge/old/${F} merge/${OLDRELNUM}/${F}; then
+ continue
+ fi
+
+ # Warn about files which are ceasing to exist.
+ if ! [ -f merge/new/${F} ]; then
+ cat <<-EOF
+
+The following file will be removed, as it no longer exists in
+FreeBSD ${RELNUM}: ${F}
+ EOF
+ continuep < /dev/tty || return 1
+ continue
+ fi
+
+ # Print changes for the user's approval.
+ cat <<-EOF
+
+The following changes, which occurred between FreeBSD ${OLDRELNUM} and
+FreeBSD ${RELNUM} have been merged into ${F}:
+EOF
+ diff -U 5 -L "current version" -L "new version" \
+ merge/old/${F} merge/new/${F} || true
+ continuep < /dev/tty || return 1
+ done < $1-paths
+
+ # Store merged files.
+ while read F; do
+ if [ -f merge/new/${F} ]; then
+ V=`${SHA256} -q merge/new/${F}`
+
+ gzip -c < merge/new/${F} > files/${V}.gz
+ echo "${F}|${V}"
+ fi
+ done < $1-paths > newhashes
+
+ # Pull lines out from $3 which need to be updated to
+ # reflect merged files.
+ while read F; do
+ look "${F}|" $3
+ done < $1-paths > $3-oldlines
+
+ # Update lines to reflect merged files
+ join -t '|' -o 1.1,1.2,1.3,1.4,1.5,1.6,2.2,1.8 \
+ $3-oldlines newhashes > $3-newlines
+
+ # Remove old lines from $3 and add new lines.
+ sort $3-oldlines |
+ comm -13 - $3 |
+ sort - $3-newlines > $3.tmp
+ mv $3.tmp $3
+
+ # Clean up
+ rm $1-paths newhashes $3-oldlines $3-newlines
+ rm -rf merge/
+ fi
+
+ # We're done with merging files.
+ rm $1
+}
+
+# Do the work involved in fetching upgrades to a new release
+upgrade_run () {
+ workdir_init || return 1
+
+ # Prepare the mirror list.
+ fetch_pick_server_init && fetch_pick_server
+
+ # Try to fetch the public key until we run out of servers.
+ while ! fetch_key; do
+ fetch_pick_server || return 1
+ done
+
+ # Try to fetch the metadata index signature ("tag") until we run
+ # out of available servers; and sanity check the downloaded tag.
+ while ! fetch_tag; do
+ fetch_pick_server || return 1
+ done
+ fetch_tagsanity || return 1
+
+ # Fetch the INDEX-OLD and INDEX-ALL.
+ fetch_metadata INDEX-OLD INDEX-ALL || return 1
+
+ # If StrictComponents is not "yes", generate a new components list
+ # with only the components which appear to be installed.
+ upgrade_guess_components INDEX-ALL || return 1
+
+ # Generate filtered INDEX-OLD and INDEX-ALL files containing only
+ # the components we want and without anything marked as "Ignore".
+ fetch_filter_metadata INDEX-OLD || return 1
+ fetch_filter_metadata INDEX-ALL || return 1
+
+ # Merge the INDEX-OLD and INDEX-ALL files into INDEX-OLD.
+ sort INDEX-OLD INDEX-ALL > INDEX-OLD.tmp
+ mv INDEX-OLD.tmp INDEX-OLD
+ rm INDEX-ALL
+
+ # Adjust variables for fetching files from the new release.
+ OLDRELNUM=${RELNUM}
+ RELNUM=${TARGETRELEASE}
+ OLDFETCHDIR=${FETCHDIR}
+ FETCHDIR=${RELNUM}/${ARCH}
+
+ # Try to fetch the NEW metadata index signature ("tag") until we run
+ # out of available servers; and sanity check the downloaded tag.
+ while ! fetch_tag; do
+ fetch_pick_server || return 1
+ done
+
+ # Fetch the new INDEX-ALL.
+ fetch_metadata INDEX-ALL || return 1
+
+ # If StrictComponents is not "yes", COMPONENTS contains an entry
+ # corresponding to the currently running kernel, and said kernel
+ # does not exist in the new release, add "kernel/generic" to the
+ # list of components.
+ upgrade_guess_new_kernel INDEX-ALL || return 1
+
+ # Filter INDEX-ALL to contain only the components we want and without
+ # anything marked as "Ignore".
+ fetch_filter_metadata INDEX-ALL || return 1
+
+ # Convert INDEX-OLD (last release) and INDEX-ALL (new release) into
+ # INDEX-OLD and INDEX-NEW files (in the sense of normal upgrades).
+ upgrade_oldall_to_oldnew INDEX-OLD INDEX-ALL INDEX-NEW
+
+ # Translate /boot/${KERNCONF} or /boot/${NKERNCONF} into ${KERNELDIR}
+ fetch_filter_kernel_names INDEX-NEW ${NKERNCONF}
+ fetch_filter_kernel_names INDEX-OLD ${KERNCONF}
+
+ # For all paths appearing in INDEX-OLD or INDEX-NEW, inspect the
+ # system and generate an INDEX-PRESENT file.
+ fetch_inspect_system INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1
+
+ # Based on ${MERGECHANGES}, generate a file tomerge-old with the
+ # paths and hashes of old versions of files to merge.
+ fetch_filter_mergechanges INDEX-OLD INDEX-PRESENT tomerge-old
+
+ # Based on ${UPDATEIFUNMODIFIED}, remove lines from INDEX-* which
+ # correspond to lines in INDEX-PRESENT with hashes not appearing
+ # in INDEX-OLD or INDEX-NEW. Also remove lines where the entry in
+ # INDEX-PRESENT has type - and there isn't a corresponding entry in
+ # INDEX-OLD with type -.
+ fetch_filter_unmodified_notpresent \
+ INDEX-OLD INDEX-PRESENT INDEX-NEW tomerge-old
+
+ # For each entry in INDEX-PRESENT of type -, remove any corresponding
+ # entry from INDEX-NEW if ${ALLOWADD} != "yes". Remove all entries
+ # of type - from INDEX-PRESENT.
+ fetch_filter_allowadd INDEX-PRESENT INDEX-NEW
+
+ # If ${ALLOWDELETE} != "yes", then remove any entries from
+ # INDEX-PRESENT which don't correspond to entries in INDEX-NEW.
+ fetch_filter_allowdelete INDEX-PRESENT INDEX-NEW
+
+ # If ${KEEPMODIFIEDMETADATA} == "yes", then for each entry in
+ # INDEX-PRESENT with metadata not matching any entry in INDEX-OLD,
+ # replace the corresponding line of INDEX-NEW with one having the
+ # same metadata as the entry in INDEX-PRESENT.
+ fetch_filter_modified_metadata INDEX-OLD INDEX-PRESENT INDEX-NEW
+
+ # Remove lines from INDEX-PRESENT and INDEX-NEW which are identical;
+ # no need to update a file if it isn't changing.
+ fetch_filter_uptodate INDEX-PRESENT INDEX-NEW
+
+ # Fetch "clean" files from the old release for merging changes.
+ fetch_files_premerge tomerge-old
+
+ # Prepare to fetch files: Generate a list of the files we need,
+ # copy the unmodified files we have into /files/, and generate
+ # a list of patches to download.
+ fetch_files_prepare INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1
+
+ # Fetch patches from to-${RELNUM}/${ARCH}/bp/
+ PATCHDIR=to-${RELNUM}/${ARCH}/bp
+ fetch_files || return 1
+
+ # Merge configuration file changes.
+ upgrade_merge tomerge-old INDEX-PRESENT INDEX-NEW || return 1
+
+ # Create and populate install manifest directory; and report what
+ # updates are available.
+ fetch_create_manifest || return 1
+
+ # Leave a note behind to tell the "install" command that the kernel
+ # needs to be installed before the world.
+ touch ${BDHASH}-install/kernelfirst
+
+ # Remind the user that they need to run "freebsd-update install"
+ # to install the downloaded bits, in case they didn't RTFM.
+ echo "To install the downloaded upgrades, run \"$0 install\"."
+}
+
+# Make sure that all the file hashes mentioned in $@ have corresponding
+# gzipped files stored in /files/.
+install_verify () {
+ # Generate a list of hashes
+ cat $@ |
+ cut -f 2,7 -d '|' |
+ grep -E '^f' |
+ cut -f 2 -d '|' |
+ sort -u > filelist
+
+ # Make sure all the hashes exist
+ while read HASH; do
+ if ! [ -f files/${HASH}.gz ]; then
+ echo -n "Update files missing -- "
+ echo "this should never happen."
+ echo "Re-run '$0 fetch'."
+ return 1
+ fi
+ done < filelist
+
+ # Clean up
+ rm filelist
+}
+
+# Remove the system immutable flag from files
+install_unschg () {
+ # Generate file list
+ cat $@ |
+ cut -f 1 -d '|' > filelist
+
+ # Remove flags
+ while read F; do
+ if ! [ -e ${BASEDIR}/${F} ]; then
+ continue
+ else
+ echo ${BASEDIR}/${F}
+ fi
+ done < filelist | xargs chflags noschg || return 1
+
+ # Clean up
+ rm filelist
+}
+
+# Decide which directory name to use for kernel backups.
+backup_kernel_finddir () {
+ CNT=0
+ while true ; do
+ # Pathname does not exist, so it is OK use that name
+ # for backup directory.
+ if [ ! -e $BASEDIR/$BACKUPKERNELDIR ]; then
+ return 0
+ fi
+
+ # If directory do exist, we only use if it has our
+ # marker file.
+ if [ -d $BASEDIR/$BACKUPKERNELDIR -a \
+ -e $BASEDIR/$BACKUPKERNELDIR/.freebsd-update ]; then
+ return 0
+ fi
+
+ # We could not use current directory name, so add counter to
+ # the end and try again.
+ CNT=$((CNT + 1))
+ if [ $CNT -gt 9 ]; then
+ echo "Could not find valid backup dir ($BASEDIR/$BACKUPKERNELDIR)"
+ exit 1
+ fi
+ BACKUPKERNELDIR="`echo $BACKUPKERNELDIR | sed -Ee 's/[0-9]\$//'`"
+ BACKUPKERNELDIR="${BACKUPKERNELDIR}${CNT}"
+ done
+}
+
+# Backup the current kernel using hardlinks, if not disabled by user.
+# Since we delete all files in the directory used for previous backups
+# we create a marker file called ".freebsd-update" in the directory so
+# we can determine on the next run that the directory was created by
+# freebsd-update and we then do not accidentally remove user files in
+# the unlikely case that the user has created a directory with a
+# conflicting name.
+backup_kernel () {
+ # Only make kernel backup is so configured.
+ if [ $BACKUPKERNEL != yes ]; then
+ return 0
+ fi
+
+ # Decide which directory name to use for kernel backups.
+ backup_kernel_finddir
+
+ # Remove old kernel backup files. If $BACKUPKERNELDIR was
+ # "not ours", backup_kernel_finddir would have exited, so
+ # deleting the directory content is as safe as we can make it.
+ if [ -d $BASEDIR/$BACKUPKERNELDIR ]; then
+ rm -fr $BASEDIR/$BACKUPKERNELDIR
+ fi
+
+ # Create directories for backup.
+ mkdir -p $BASEDIR/$BACKUPKERNELDIR
+ mtree -cdn -p "${BASEDIR}/${KERNELDIR}" | \
+ mtree -Ue -p "${BASEDIR}/${BACKUPKERNELDIR}" > /dev/null
+
+ # Mark the directory as having been created by freebsd-update.
+ touch $BASEDIR/$BACKUPKERNELDIR/.freebsd-update
+ if [ $? -ne 0 ]; then
+ echo "Could not create kernel backup directory"
+ exit 1
+ fi
+
+ # Disable pathname expansion to be sure *.symbols is not
+ # expanded.
+ set -f
+
+ # Use find to ignore symbol files, unless disabled by user.
+ if [ $BACKUPKERNELSYMBOLFILES = yes ]; then
+ FINDFILTER=""
+ else
+ FINDFILTER="-a ! -name *.debug -a ! -name *.symbols"
+ fi
+
+ # Backup all the kernel files using hardlinks.
+ (cd ${BASEDIR}/${KERNELDIR} && find . -type f $FINDFILTER -exec \
+ cp -pl '{}' ${BASEDIR}/${BACKUPKERNELDIR}/'{}' \;)
+
+ # Re-enable patchname expansion.
+ set +f
+}
+
+# Install new files
+install_from_index () {
+ # First pass: Do everything apart from setting file flags. We
+ # can't set flags yet, because schg inhibits hard linking.
+ sort -k 1,1 -t '|' $1 |
+ tr '|' ' ' |
+ while read FPATH TYPE OWNER GROUP PERM FLAGS HASH LINK; do
+ case ${TYPE} in
+ d)
+ # Create a directory
+ install -d -o ${OWNER} -g ${GROUP} \
+ -m ${PERM} ${BASEDIR}/${FPATH}
+ ;;
+ f)
+ if [ -z "${LINK}" ]; then
+ # Create a file, without setting flags.
+ gunzip < files/${HASH}.gz > ${HASH}
+ install -S -o ${OWNER} -g ${GROUP} \
+ -m ${PERM} ${HASH} ${BASEDIR}/${FPATH}
+ rm ${HASH}
+ else
+ # Create a hard link.
+ ln -f ${BASEDIR}/${LINK} ${BASEDIR}/${FPATH}
+ fi
+ ;;
+ L)
+ # Create a symlink
+ ln -sfh ${HASH} ${BASEDIR}/${FPATH}
+ ;;
+ esac
+ done
+
+ # Perform a second pass, adding file flags.
+ tr '|' ' ' < $1 |
+ while read FPATH TYPE OWNER GROUP PERM FLAGS HASH LINK; do
+ if [ ${TYPE} = "f" ] &&
+ ! [ ${FLAGS} = "0" ]; then
+ chflags ${FLAGS} ${BASEDIR}/${FPATH}
+ fi
+ done
+}
+
+# Remove files which we want to delete
+install_delete () {
+ # Generate list of new files
+ cut -f 1 -d '|' < $2 |
+ sort > newfiles
+
+ # Generate subindex of old files we want to nuke
+ sort -k 1,1 -t '|' $1 |
+ join -t '|' -v 1 - newfiles |
+ sort -r -k 1,1 -t '|' |
+ cut -f 1,2 -d '|' |
+ tr '|' ' ' > killfiles
+
+ # Remove the offending bits
+ while read FPATH TYPE; do
+ case ${TYPE} in
+ d)
+ rmdir ${BASEDIR}/${FPATH}
+ ;;
+ f)
+ rm ${BASEDIR}/${FPATH}
+ ;;
+ L)
+ rm ${BASEDIR}/${FPATH}
+ ;;
+ esac
+ done < killfiles
+
+ # Clean up
+ rm newfiles killfiles
+}
+
+# Install new files, delete old files, and update linker.hints
+install_files () {
+ # If we haven't already dealt with the kernel, deal with it.
+ if ! [ -f $1/kerneldone ]; then
+ grep -E '^/boot/' $1/INDEX-OLD > INDEX-OLD
+ grep -E '^/boot/' $1/INDEX-NEW > INDEX-NEW
+
+ # Backup current kernel before installing a new one
+ backup_kernel || return 1
+
+ # Install new files
+ install_from_index INDEX-NEW || return 1
+
+ # Remove files which need to be deleted
+ install_delete INDEX-OLD INDEX-NEW || return 1
+
+ # Update linker.hints if necessary
+ if [ -s INDEX-OLD -o -s INDEX-NEW ]; then
+ kldxref -R ${BASEDIR}/boot/ 2>/dev/null
+ fi
+
+ # We've finished updating the kernel.
+ touch $1/kerneldone
+
+ # Do we need to ask for a reboot now?
+ if [ -f $1/kernelfirst ] &&
+ [ -s INDEX-OLD -o -s INDEX-NEW ]; then
+ cat <<-EOF
+
+Kernel updates have been installed. Please reboot and run
+"$0 install" again to finish installing updates.
+ EOF
+ exit 0
+ fi
+ fi
+
+ # If we haven't already dealt with the world, deal with it.
+ if ! [ -f $1/worlddone ]; then
+ # Create any necessary directories first
+ grep -vE '^/boot/' $1/INDEX-NEW |
+ grep -E '^[^|]+\|d\|' > INDEX-NEW
+ install_from_index INDEX-NEW || return 1
+
+ # Install new runtime linker
+ grep -vE '^/boot/' $1/INDEX-NEW |
+ grep -vE '^[^|]+\|d\|' |
+ grep -E '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' > INDEX-NEW
+ install_from_index INDEX-NEW || return 1
+
+ # Install new shared libraries next
+ grep -vE '^/boot/' $1/INDEX-NEW |
+ grep -vE '^[^|]+\|d\|' |
+ grep -vE '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' |
+ grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-NEW
+ install_from_index INDEX-NEW || return 1
+
+ # Deal with everything else
+ grep -vE '^/boot/' $1/INDEX-OLD |
+ grep -vE '^[^|]+\|d\|' |
+ grep -vE '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' |
+ grep -vE '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-OLD
+ grep -vE '^/boot/' $1/INDEX-NEW |
+ grep -vE '^[^|]+\|d\|' |
+ grep -vE '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' |
+ grep -vE '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-NEW
+ install_from_index INDEX-NEW || return 1
+ install_delete INDEX-OLD INDEX-NEW || return 1
+
+ # Rebuild /etc/spwd.db and /etc/pwd.db if necessary.
+ if [ ${BASEDIR}/etc/master.passwd -nt ${BASEDIR}/etc/spwd.db ] ||
+ [ ${BASEDIR}/etc/master.passwd -nt ${BASEDIR}/etc/pwd.db ]; then
+ pwd_mkdb -d ${BASEDIR}/etc ${BASEDIR}/etc/master.passwd
+ fi
+
+ # Rebuild /etc/login.conf.db if necessary.
+ if [ ${BASEDIR}/etc/login.conf -nt ${BASEDIR}/etc/login.conf.db ]; then
+ cap_mkdb ${BASEDIR}/etc/login.conf
+ fi
+
+ # We've finished installing the world and deleting old files
+ # which are not shared libraries.
+ touch $1/worlddone
+
+ # Do we need to ask the user to portupgrade now?
+ grep -vE '^/boot/' $1/INDEX-NEW |
+ grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' |
+ cut -f 1 -d '|' |
+ sort > newfiles
+ if grep -vE '^/boot/' $1/INDEX-OLD |
+ grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' |
+ cut -f 1 -d '|' |
+ sort |
+ join -v 1 - newfiles |
+ grep -q .; then
+ cat <<-EOF
+
+Completing this upgrade requires removing old shared object files.
+Please rebuild all installed 3rd party software (e.g., programs
+installed from the ports tree) and then run "$0 install"
+again to finish installing updates.
+ EOF
+ rm newfiles
+ exit 0
+ fi
+ rm newfiles
+ fi
+
+ # Remove old shared libraries
+ grep -vE '^/boot/' $1/INDEX-NEW |
+ grep -vE '^[^|]+\|d\|' |
+ grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-NEW
+ grep -vE '^/boot/' $1/INDEX-OLD |
+ grep -vE '^[^|]+\|d\|' |
+ grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-OLD
+ install_delete INDEX-OLD INDEX-NEW || return 1
+
+ # Remove old directories
+ grep -vE '^/boot/' $1/INDEX-NEW |
+ grep -E '^[^|]+\|d\|' > INDEX-NEW
+ grep -vE '^/boot/' $1/INDEX-OLD |
+ grep -E '^[^|]+\|d\|' > INDEX-OLD
+ install_delete INDEX-OLD INDEX-NEW || return 1
+
+ # Remove temporary files
+ rm INDEX-OLD INDEX-NEW
+}
+
+# Rearrange bits to allow the installed updates to be rolled back
+install_setup_rollback () {
+ # Remove the "reboot after installing kernel", "kernel updated", and
+ # "finished installing the world" flags if present -- they are
+ # irrelevant when rolling back updates.
+ if [ -f ${BDHASH}-install/kernelfirst ]; then
+ rm ${BDHASH}-install/kernelfirst
+ rm ${BDHASH}-install/kerneldone
+ fi
+ if [ -f ${BDHASH}-install/worlddone ]; then
+ rm ${BDHASH}-install/worlddone
+ fi
+
+ if [ -L ${BDHASH}-rollback ]; then
+ mv ${BDHASH}-rollback ${BDHASH}-install/rollback
+ fi
+
+ mv ${BDHASH}-install ${BDHASH}-rollback
+}
+
+# Actually install updates
+install_run () {
+ echo -n "Installing updates..."
+
+ # Make sure we have all the files we should have
+ install_verify ${BDHASH}-install/INDEX-OLD \
+ ${BDHASH}-install/INDEX-NEW || return 1
+
+ # Remove system immutable flag from files
+ install_unschg ${BDHASH}-install/INDEX-OLD \
+ ${BDHASH}-install/INDEX-NEW || return 1
+
+ # Install new files, delete old files, and update linker.hints
+ install_files ${BDHASH}-install || return 1
+
+ # Rearrange bits to allow the installed updates to be rolled back
+ install_setup_rollback
+
+ echo " done."
+}
+
+# Rearrange bits to allow the previous set of updates to be rolled back next.
+rollback_setup_rollback () {
+ if [ -L ${BDHASH}-rollback/rollback ]; then
+ mv ${BDHASH}-rollback/rollback rollback-tmp
+ rm -r ${BDHASH}-rollback/
+ rm ${BDHASH}-rollback
+ mv rollback-tmp ${BDHASH}-rollback
+ else
+ rm -r ${BDHASH}-rollback/
+ rm ${BDHASH}-rollback
+ fi
+}
+
+# Install old files, delete new files, and update linker.hints
+rollback_files () {
+ # Install old shared library files which don't have the same path as
+ # a new shared library file.
+ grep -vE '^/boot/' $1/INDEX-NEW |
+ grep -E '/lib/.*\.so\.[0-9]+\|' |
+ cut -f 1 -d '|' |
+ sort > INDEX-NEW.libs.flist
+ grep -vE '^/boot/' $1/INDEX-OLD |
+ grep -E '/lib/.*\.so\.[0-9]+\|' |
+ sort -k 1,1 -t '|' - |
+ join -t '|' -v 1 - INDEX-NEW.libs.flist > INDEX-OLD
+ install_from_index INDEX-OLD || return 1
+
+ # Deal with files which are neither kernel nor shared library
+ grep -vE '^/boot/' $1/INDEX-OLD |
+ grep -vE '/lib/.*\.so\.[0-9]+\|' > INDEX-OLD
+ grep -vE '^/boot/' $1/INDEX-NEW |
+ grep -vE '/lib/.*\.so\.[0-9]+\|' > INDEX-NEW
+ install_from_index INDEX-OLD || return 1
+ install_delete INDEX-NEW INDEX-OLD || return 1
+
+ # Install any old shared library files which we didn't install above.
+ grep -vE '^/boot/' $1/INDEX-OLD |
+ grep -E '/lib/.*\.so\.[0-9]+\|' |
+ sort -k 1,1 -t '|' - |
+ join -t '|' - INDEX-NEW.libs.flist > INDEX-OLD
+ install_from_index INDEX-OLD || return 1
+
+ # Delete unneeded shared library files
+ grep -vE '^/boot/' $1/INDEX-OLD |
+ grep -E '/lib/.*\.so\.[0-9]+\|' > INDEX-OLD
+ grep -vE '^/boot/' $1/INDEX-NEW |
+ grep -E '/lib/.*\.so\.[0-9]+\|' > INDEX-NEW
+ install_delete INDEX-NEW INDEX-OLD || return 1
+
+ # Deal with kernel files
+ grep -E '^/boot/' $1/INDEX-OLD > INDEX-OLD
+ grep -E '^/boot/' $1/INDEX-NEW > INDEX-NEW
+ install_from_index INDEX-OLD || return 1
+ install_delete INDEX-NEW INDEX-OLD || return 1
+ if [ -s INDEX-OLD -o -s INDEX-NEW ]; then
+ kldxref -R /boot/ 2>/dev/null
+ fi
+
+ # Remove temporary files
+ rm INDEX-OLD INDEX-NEW INDEX-NEW.libs.flist
+}
+
+# Actually rollback updates
+rollback_run () {
+ echo -n "Uninstalling updates..."
+
+ # If there are updates waiting to be installed, remove them; we
+ # want the user to re-run 'fetch' after rolling back updates.
+ if [ -L ${BDHASH}-install ]; then
+ rm -r ${BDHASH}-install/
+ rm ${BDHASH}-install
+ fi
+
+ # Make sure we have all the files we should have
+ install_verify ${BDHASH}-rollback/INDEX-NEW \
+ ${BDHASH}-rollback/INDEX-OLD || return 1
+
+ # Remove system immutable flag from files
+ install_unschg ${BDHASH}-rollback/INDEX-NEW \
+ ${BDHASH}-rollback/INDEX-OLD || return 1
+
+ # Install old files, delete new files, and update linker.hints
+ rollback_files ${BDHASH}-rollback || return 1
+
+ # Remove the rollback directory and the symlink pointing to it; and
+ # rearrange bits to allow the previous set of updates to be rolled
+ # back next.
+ rollback_setup_rollback
+
+ echo " done."
+}
+
+# Compare INDEX-ALL and INDEX-PRESENT and print warnings about differences.
+IDS_compare () {
+ # Get all the lines which mismatch in something other than file
+ # flags. We ignore file flags because sysinstall doesn't seem to
+ # set them when it installs FreeBSD; warning about these adds a
+ # very large amount of noise.
+ cut -f 1-5,7-8 -d '|' $1 > $1.noflags
+ sort -k 1,1 -t '|' $1.noflags > $1.sorted
+ cut -f 1-5,7-8 -d '|' $2 |
+ comm -13 $1.noflags - |
+ fgrep -v '|-|||||' |
+ sort -k 1,1 -t '|' |
+ join -t '|' $1.sorted - > INDEX-NOTMATCHING
+
+ # Ignore files which match IDSIGNOREPATHS.
+ for X in ${IDSIGNOREPATHS}; do
+ grep -E "^${X}" INDEX-NOTMATCHING
+ done |
+ sort -u |
+ comm -13 - INDEX-NOTMATCHING > INDEX-NOTMATCHING.tmp
+ mv INDEX-NOTMATCHING.tmp INDEX-NOTMATCHING
+
+ # Go through the lines and print warnings.
+ local IFS='|'
+ while read FPATH TYPE OWNER GROUP PERM HASH LINK P_TYPE P_OWNER P_GROUP P_PERM P_HASH P_LINK; do
+ # Warn about different object types.
+ if ! [ "${TYPE}" = "${P_TYPE}" ]; then
+ echo -n "${FPATH} is a "
+ case "${P_TYPE}" in
+ f) echo -n "regular file, "
+ ;;
+ d) echo -n "directory, "
+ ;;
+ L) echo -n "symlink, "
+ ;;
+ esac
+ echo -n "but should be a "
+ case "${TYPE}" in
+ f) echo -n "regular file."
+ ;;
+ d) echo -n "directory."
+ ;;
+ L) echo -n "symlink."
+ ;;
+ esac
+ echo
+
+ # Skip other tests, since they don't make sense if
+ # we're comparing different object types.
+ continue
+ fi
+
+ # Warn about different owners.
+ if ! [ "${OWNER}" = "${P_OWNER}" ]; then
+ echo -n "${FPATH} is owned by user id ${P_OWNER}, "
+ echo "but should be owned by user id ${OWNER}."
+ fi
+
+ # Warn about different groups.
+ if ! [ "${GROUP}" = "${P_GROUP}" ]; then
+ echo -n "${FPATH} is owned by group id ${P_GROUP}, "
+ echo "but should be owned by group id ${GROUP}."
+ fi
+
+ # Warn about different permissions. We do not warn about
+ # different permissions on symlinks, since some archivers
+ # don't extract symlink permissions correctly and they are
+ # ignored anyway.
+ if ! [ "${PERM}" = "${P_PERM}" ] &&
+ ! [ "${TYPE}" = "L" ]; then
+ echo -n "${FPATH} has ${P_PERM} permissions, "
+ echo "but should have ${PERM} permissions."
+ fi
+
+ # Warn about different file hashes / symlink destinations.
+ if ! [ "${HASH}" = "${P_HASH}" ]; then
+ if [ "${TYPE}" = "L" ]; then
+ echo -n "${FPATH} is a symlink to ${P_HASH}, "
+ echo "but should be a symlink to ${HASH}."
+ fi
+ if [ "${TYPE}" = "f" ]; then
+ echo -n "${FPATH} has SHA256 hash ${P_HASH}, "
+ echo "but should have SHA256 hash ${HASH}."
+ fi
+ fi
+
+ # We don't warn about different hard links, since some
+ # some archivers break hard links, and as long as the
+ # underlying data is correct they really don't matter.
+ done < INDEX-NOTMATCHING
+
+ # Clean up
+ rm $1 $1.noflags $1.sorted $2 INDEX-NOTMATCHING
+}
+
+# Do the work involved in comparing the system to a "known good" index
+IDS_run () {
+ workdir_init || return 1
+
+ # Prepare the mirror list.
+ fetch_pick_server_init && fetch_pick_server
+
+ # Try to fetch the public key until we run out of servers.
+ while ! fetch_key; do
+ fetch_pick_server || return 1
+ done
+
+ # Try to fetch the metadata index signature ("tag") until we run
+ # out of available servers; and sanity check the downloaded tag.
+ while ! fetch_tag; do
+ fetch_pick_server || return 1
+ done
+ fetch_tagsanity || return 1
+
+ # Fetch INDEX-OLD and INDEX-ALL.
+ fetch_metadata INDEX-OLD INDEX-ALL || return 1
+
+ # Generate filtered INDEX-OLD and INDEX-ALL files containing only
+ # the components we want and without anything marked as "Ignore".
+ fetch_filter_metadata INDEX-OLD || return 1
+ fetch_filter_metadata INDEX-ALL || return 1
+
+ # Merge the INDEX-OLD and INDEX-ALL files into INDEX-ALL.
+ sort INDEX-OLD INDEX-ALL > INDEX-ALL.tmp
+ mv INDEX-ALL.tmp INDEX-ALL
+ rm INDEX-OLD
+
+ # Translate /boot/${KERNCONF} to ${KERNELDIR}
+ fetch_filter_kernel_names INDEX-ALL ${KERNCONF}
+
+ # Inspect the system and generate an INDEX-PRESENT file.
+ fetch_inspect_system INDEX-ALL INDEX-PRESENT /dev/null || return 1
+
+ # Compare INDEX-ALL and INDEX-PRESENT and print warnings about any
+ # differences.
+ IDS_compare INDEX-ALL INDEX-PRESENT
+}
+
+#### Main functions -- call parameter-handling and core functions
+
+# Using the command line, configuration file, and defaults,
+# set all the parameters which are needed later.
+get_params () {
+ init_params
+ parse_cmdline $@
+ parse_conffile
+ default_params
+}
+
+# Fetch command. Make sure that we're being called
+# interactively, then run fetch_check_params and fetch_run
+cmd_fetch () {
+ if [ ! -t 0 -a $NOTTYOK -eq 0 ]; then
+ echo -n "`basename $0` fetch should not "
+ echo "be run non-interactively."
+ echo "Run `basename $0` cron instead."
+ exit 1
+ fi
+ fetch_check_params
+ fetch_run || exit 1
+}
+
+# Cron command. Make sure the parameters are sensible; wait
+# rand(3600) seconds; then fetch updates. While fetching updates,
+# send output to a temporary file; only print that file if the
+# fetching failed.
+cmd_cron () {
+ fetch_check_params
+ sleep `jot -r 1 0 3600`
+
+ TMPFILE=`mktemp /tmp/freebsd-update.XXXXXX` || exit 1
+ if ! fetch_run >> ${TMPFILE} ||
+ ! grep -q "No updates needed" ${TMPFILE} ||
+ [ ${VERBOSELEVEL} = "debug" ]; then
+ mail -s "`hostname` security updates" ${MAILTO} < ${TMPFILE}
+ fi
+
+ rm ${TMPFILE}
+}
+
+# Fetch files for upgrading to a new release.
+cmd_upgrade () {
+ upgrade_check_params
+ upgrade_run || exit 1
+}
+
+# Install downloaded updates.
+cmd_install () {
+ install_check_params
+ install_run || exit 1
+}
+
+# Rollback most recently installed updates.
+cmd_rollback () {
+ rollback_check_params
+ rollback_run || exit 1
+}
+
+# Compare system against a "known good" index.
+cmd_IDS () {
+ IDS_check_params
+ IDS_run || exit 1
+}
+
+#### Entry point
+
+# Make sure we find utilities from the base system
+export PATH=/sbin:/bin:/usr/sbin:/usr/bin:${PATH}
+
+# Set a pager if the user doesn't
+if [ -z "$PAGER" ]; then
+ PAGER=/usr/bin/more
+fi
+
+# Set LC_ALL in order to avoid problems with character ranges like [A-Z].
+export LC_ALL=C
+
+get_params $@
+for COMMAND in ${COMMANDS}; do
+ cmd_${COMMAND}
+done
diff --git a/usr.sbin/fstyp/Makefile b/usr.sbin/fstyp/Makefile
new file mode 100644
index 0000000..5eba12b
--- /dev/null
+++ b/usr.sbin/fstyp/Makefile
@@ -0,0 +1,45 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= fstyp
+SRCS= cd9660.c ext2fs.c fstyp.c geli.c msdosfs.c ntfs.c ufs.c
+
+.if ${MK_ZFS} != "no"
+SRCS += zfs.c
+.endif
+
+MAN= fstyp.8
+
+WARNS?= 2
+
+.include <src.opts.mk>
+
+.if ${MK_TESTS} != "no"
+SUBDIR+= tests
+.endif
+
+CFLAGS+=-I${.CURDIR}/../../sys
+
+.if ${MK_ZFS} != "no"
+IGNORE_PRAGMA= YES
+
+CFLAGS+= -DNEED_SOLARIS_BOOLEAN -DHAVE_ZFS
+CFLAGS+= -I${.CURDIR}/../../sys/cddl/compat/opensolaris
+CFLAGS+= -I${.CURDIR}/../../cddl/compat/opensolaris/include
+CFLAGS+= -I${.CURDIR}/../../cddl/compat/opensolaris/lib/libumem
+CFLAGS+= -I${.CURDIR}/../../cddl/contrib/opensolaris/lib/libnvpair
+CFLAGS+= -I${.CURDIR}/../../cddl/contrib/opensolaris/lib/libzpool/common
+CFLAGS+= -I${.CURDIR}/../../sys/cddl/contrib/opensolaris/uts/common/fs/zfs
+CFLAGS+= -I${.CURDIR}/../../sys/cddl/contrib/opensolaris/uts/common
+CFLAGS+= -I${.CURDIR}/../../sys/cddl/contrib/opensolaris/uts/common/sys
+CFLAGS+= -I${.CURDIR}/../../cddl/contrib/opensolaris/head
+.endif
+
+LIBADD= geom md
+
+.if ${MK_ZFS} != "no"
+LIBADD+=nvpair zfs
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/fstyp/Makefile.depend b/usr.sbin/fstyp/Makefile.depend
new file mode 100644
index 0000000..b1a2eff
--- /dev/null
+++ b/usr.sbin/fstyp/Makefile.depend
@@ -0,0 +1,32 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ cddl/lib/libavl \
+ cddl/lib/libnvpair \
+ cddl/lib/libumem \
+ cddl/lib/libuutil \
+ cddl/lib/libzfs \
+ cddl/lib/libzfs_core \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libexpat \
+ lib/libgeom \
+ lib/libmd \
+ lib/libsbuf \
+ lib/libthr \
+ lib/libutil \
+ lib/libz \
+ lib/msun \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/fstyp/cd9660.c b/usr.sbin/fstyp/cd9660.c
new file mode 100644
index 0000000..658af33
--- /dev/null
+++ b/usr.sbin/fstyp/cd9660.c
@@ -0,0 +1,62 @@
+/*-
+ * Copyright (c) 2004 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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 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 <stdlib.h>
+#include <string.h>
+
+#include "fstyp.h"
+
+#define ISO9660_MAGIC "\x01" "CD001" "\x01\x00"
+#define ISO9660_OFFSET 0x8000
+#define VOLUME_LEN 32
+
+int
+fstyp_cd9660(FILE *fp, char *label, size_t size)
+{
+ char *sector, *volume;
+
+ sector = read_buf(fp, ISO9660_OFFSET, 512);
+ if (sector == NULL)
+ return (1);
+ if (bcmp(sector, ISO9660_MAGIC, sizeof(ISO9660_MAGIC) - 1) != 0) {
+ free(sector);
+ return (1);
+ }
+ volume = sector + 0x28;
+ bzero(label, size);
+ strlcpy(label, volume, MIN(size, VOLUME_LEN));
+ free(sector);
+ rtrim(label, size);
+ return (0);
+}
diff --git a/usr.sbin/fstyp/ext2fs.c b/usr.sbin/fstyp/ext2fs.c
new file mode 100644
index 0000000..79a4e3a
--- /dev/null
+++ b/usr.sbin/fstyp/ext2fs.c
@@ -0,0 +1,85 @@
+/*-
+ * Copyright (c) 2005 Stanislav Sedov
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "fstyp.h"
+
+#define EXT2FS_SB_OFFSET 1024
+#define EXT2_SUPER_MAGIC 0xef53
+#define EXT2_DYNAMIC_REV 1
+
+typedef struct e2sb {
+ uint8_t fake1[56];
+ uint16_t s_magic;
+ uint8_t fake2[18];
+ uint32_t s_rev_level;
+ uint8_t fake3[40];
+ char s_volume_name[16];
+} e2sb_t;
+
+int
+fstyp_ext2fs(FILE *fp, char *label, size_t size)
+{
+ e2sb_t *fs;
+ char *s_volume_name;
+
+ fs = (e2sb_t *)read_buf(fp, EXT2FS_SB_OFFSET, 512);
+ if (fs == NULL)
+ return (1);
+
+ /* Check for magic and versio n*/
+ if (fs->s_magic == EXT2_SUPER_MAGIC &&
+ fs->s_rev_level == EXT2_DYNAMIC_REV) {
+ //G_LABEL_DEBUG(1, "ext2fs file system detected on %s.",
+ // pp->name);
+ } else {
+ free(fs);
+ return (1);
+ }
+
+ s_volume_name = fs->s_volume_name;
+ /* Terminate label */
+ s_volume_name[sizeof(fs->s_volume_name) - 1] = '\0';
+
+ if (s_volume_name[0] == '/')
+ s_volume_name += 1;
+
+ strlcpy(label, s_volume_name, size);
+ free(fs);
+
+ return (0);
+}
diff --git a/usr.sbin/fstyp/fstyp.8 b/usr.sbin/fstyp/fstyp.8
new file mode 100644
index 0000000..835feff
--- /dev/null
+++ b/usr.sbin/fstyp/fstyp.8
@@ -0,0 +1,127 @@
+.\" Copyright (c) 2014 The FreeBSD Foundation
+.\" All rights reserved.
+.\"
+.\" This software was 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 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 11, 2015
+.Dt FSTYP 8
+.Os
+.Sh NAME
+.Nm fstyp
+.Nd determine filesystem type
+.Sh SYNOPSIS
+.Nm
+.Op Fl l
+.Op Fl s
+.Op Fl u
+.Ar special
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to determine the filesystem type on a given device.
+It can recognize ISO-9660, Ext2, FAT, NTFS, and UFS filesystems.
+When the
+.Fl u
+flag is specified,
+.Nm
+also recognizes certain additional metadata formats that cannot be
+handled using
+.Xr mount 8 ,
+such as ZFS pools and
+.Xr geli 8
+providers.
+.Pp
+The filesystem name is printed to the standard output
+as, respectively:
+.Bl -item -offset indent -compact
+.It
+cd9660
+.It
+ext2fs
+.It
+geli
+.It
+msdosfs
+.It
+ntfs
+.It
+ufs
+.It
+zfs
+.El
+.Pp
+Because
+.Nm
+is built specifically to detect filesystem types, it differs from
+.Xr file 1
+in several ways.
+The output is machine-parsable, filesystem labels are supported,
+the utility runs sandboxed using
+.Xr capsicum 4 ,
+and does not try to recognize any file format other than filesystems.
+.Pp
+These options are available:
+.Bl -tag -width ".Fl l"
+.It Fl l
+In addition to filesystem type, print filesystem label if available.
+.It Fl s
+Ignore file type.
+By default,
+.Nm
+only works on regular files and disk-like device nodes.
+Trying to read other file types might have unexpected consequences or hang
+indefinitely.
+.It Fl u
+Include filesystems and devices that cannot be mounted directly by
+.Xr mount 8 .
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility exits 0 on success, and >0 if an error occurs or the filesystem
+type is not recognized.
+.Sh SEE ALSO
+.Xr file 1 ,
+.Xr capsicum 4 ,
+.Xr autofs 8 ,
+.Xr geli 8 ,
+.Xr glabel 8 ,
+.Xr mount 8 ,
+.Xr zpool 8
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Fx 10.2 .
+.Sh AUTHORS
+The
+.Nm
+utility was developed by
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+under sponsorship from the FreeBSD Foundation.
+ZFS and GELI support was added by
+.An Allan Jude Aq Mt allanjude@FreeBSD.org
diff --git a/usr.sbin/fstyp/fstyp.c b/usr.sbin/fstyp/fstyp.c
new file mode 100644
index 0000000..cde179f
--- /dev/null
+++ b/usr.sbin/fstyp/fstyp.c
@@ -0,0 +1,235 @@
+/*-
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/capsicum.h>
+#include <sys/disk.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <err.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <vis.h>
+
+#include "fstyp.h"
+
+#define LABEL_LEN 256
+
+typedef int (*fstyp_function)(FILE *, char *, size_t);
+
+static struct {
+ const char *name;
+ fstyp_function function;
+ bool unmountable;
+} fstypes[] = {
+ { "cd9660", &fstyp_cd9660, false },
+ { "ext2fs", &fstyp_ext2fs, false },
+ { "geli", &fstyp_geli, true },
+ { "msdosfs", &fstyp_msdosfs, false },
+ { "ntfs", &fstyp_ntfs, false },
+ { "ufs", &fstyp_ufs, false },
+#ifdef HAVE_ZFS
+ { "zfs", &fstyp_zfs, true },
+#endif
+ { NULL, NULL, NULL }
+};
+
+void *
+read_buf(FILE *fp, off_t off, size_t len)
+{
+ int error;
+ size_t nread;
+ void *buf;
+
+ error = fseek(fp, off, SEEK_SET);
+ if (error != 0) {
+ warn("cannot seek to %jd", (uintmax_t)off);
+ return (NULL);
+ }
+
+ buf = malloc(len);
+ if (buf == 0) {
+ warn("cannot malloc %zd bytes of memory", len);
+ return (NULL);
+ }
+
+ nread = fread(buf, len, 1, fp);
+ if (nread != 1) {
+ free(buf);
+ if (feof(fp) == 0)
+ warn("fread");
+ return (NULL);
+ }
+
+ return (buf);
+}
+
+char *
+checked_strdup(const char *s)
+{
+ char *c;
+
+ c = strdup(s);
+ if (c == NULL)
+ err(1, "strdup");
+ return (c);
+}
+
+void
+rtrim(char *label, size_t size)
+{
+ ptrdiff_t i;
+
+ for (i = size - 1; i >= 0; i--) {
+ if (label[i] == '\0')
+ continue;
+ else if (label[i] == ' ')
+ label[i] = '\0';
+ else
+ break;
+ }
+}
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: fstyp [-l] [-s] [-u] special\n");
+ exit(1);
+}
+
+static void
+type_check(const char *path, FILE *fp)
+{
+ int error, fd;
+ off_t mediasize;
+ struct stat sb;
+
+ fd = fileno(fp);
+
+ error = fstat(fd, &sb);
+ if (error != 0)
+ err(1, "%s: fstat", path);
+
+ if (S_ISREG(sb.st_mode))
+ return;
+
+ error = ioctl(fd, DIOCGMEDIASIZE, &mediasize);
+ if (error != 0)
+ errx(1, "%s: not a disk", path);
+}
+
+int
+main(int argc, char **argv)
+{
+ int ch, error, i, nbytes;
+ bool ignore_type = false, show_label = false, show_unmountable = false;
+ char label[LABEL_LEN + 1], strvised[LABEL_LEN * 4 + 1];
+ char *path;
+ FILE *fp;
+ fstyp_function fstyp_f;
+
+ while ((ch = getopt(argc, argv, "lsu")) != -1) {
+ switch (ch) {
+ case 'l':
+ show_label = true;
+ break;
+ case 's':
+ ignore_type = true;
+ break;
+ case 'u':
+ show_unmountable = true;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+ if (argc != 1)
+ usage();
+
+ path = argv[0];
+
+ fp = fopen(path, "r");
+ if (fp == NULL)
+ err(1, "%s", path);
+
+ error = cap_enter();
+ if (error != 0 && errno != ENOSYS)
+ err(1, "cap_enter");
+
+ if (ignore_type == false)
+ type_check(path, fp);
+
+ memset(label, '\0', sizeof(label));
+
+ for (i = 0;; i++) {
+ if (show_unmountable == false && fstypes[i].unmountable == true)
+ continue;
+ fstyp_f = fstypes[i].function;
+ if (fstyp_f == NULL)
+ break;
+
+ error = fstyp_f(fp, label, sizeof(label));
+ if (error == 0)
+ break;
+ }
+
+ if (fstypes[i].name == NULL) {
+ warnx("%s: filesystem not recognized", path);
+ return (1);
+ }
+
+ if (show_label && label[0] != '\0') {
+ /*
+ * XXX: I'd prefer VIS_HTTPSTYLE, but it unconditionally
+ * encodes spaces.
+ */
+ nbytes = strsnvis(strvised, sizeof(strvised), label,
+ VIS_GLOB | VIS_NL, "\"'$");
+ if (nbytes == -1)
+ err(1, "strsnvis");
+
+ printf("%s %s\n", fstypes[i].name, strvised);
+ } else {
+ printf("%s\n", fstypes[i].name);
+ }
+
+ return (0);
+}
diff --git a/usr.sbin/fstyp/fstyp.h b/usr.sbin/fstyp/fstyp.h
new file mode 100644
index 0000000..8deba5e
--- /dev/null
+++ b/usr.sbin/fstyp/fstyp.h
@@ -0,0 +1,51 @@
+/*-
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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$
+ */
+
+#ifndef FSTYP_H
+#define FSTYP_H
+
+#define MIN(a,b) (((a)<(b))?(a):(b))
+
+void *read_buf(FILE *fp, off_t off, size_t len);
+char *checked_strdup(const char *s);
+void rtrim(char *label, size_t size);
+
+int fstyp_cd9660(FILE *fp, char *label, size_t size);
+int fstyp_ext2fs(FILE *fp, char *label, size_t size);
+int fstyp_geli(FILE *fp, char *label, size_t size);
+int fstyp_msdosfs(FILE *fp, char *label, size_t size);
+int fstyp_ntfs(FILE *fp, char *label, size_t size);
+int fstyp_ufs(FILE *fp, char *label, size_t size);
+#ifdef HAVE_ZFS
+int fstyp_zfs(FILE *fp, char *label, size_t size);
+#endif
+
+#endif /* !FSTYP_H */
diff --git a/usr.sbin/fstyp/geli.c b/usr.sbin/fstyp/geli.c
new file mode 100644
index 0000000..59e8653
--- /dev/null
+++ b/usr.sbin/fstyp/geli.c
@@ -0,0 +1,71 @@
+/*-
+ * Copyright (c) 2015 Allan Jude <allanjude@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/disk.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <geom/eli/g_eli.h>
+
+#include "fstyp.h"
+
+int
+fstyp_geli(FILE *fp, char *label __unused, size_t labelsize __unused)
+{
+ int error;
+ off_t mediasize;
+ u_int sectorsize;
+ struct g_eli_metadata md;
+ u_char *buf;
+
+ error = ioctl(fileno(fp), DIOCGMEDIASIZE, &mediasize);
+ if (error != 0)
+ return (1);
+ error = ioctl(fileno(fp), DIOCGSECTORSIZE, &sectorsize);
+ if (error != 0)
+ return (1);
+ buf = (u_char *)read_buf(fp, mediasize - sectorsize, sectorsize);
+ if (buf == NULL)
+ goto gelierr;
+ error = eli_metadata_decode(buf, &md);
+ if (error)
+ goto gelierr;
+
+ if (strcmp(md.md_magic, G_ELI_MAGIC) == 0) {
+ free(buf);
+ return (0);
+ }
+
+gelierr:
+ free(buf);
+
+ return (1);
+}
diff --git a/usr.sbin/fstyp/msdosfs.c b/usr.sbin/fstyp/msdosfs.c
new file mode 100644
index 0000000..3d86802
--- /dev/null
+++ b/usr.sbin/fstyp/msdosfs.c
@@ -0,0 +1,175 @@
+/*-
+ * Copyright (c) 2004 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * Copyright (c) 2006 Tobias Reifenberger
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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 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 <stdlib.h>
+#include <string.h>
+
+#include "fstyp.h"
+#include "msdosfs.h"
+
+#define LABEL_NO_NAME "NO NAME "
+
+int
+fstyp_msdosfs(FILE *fp, char *label, size_t size)
+{
+ FAT_BSBPB *pfat_bsbpb;
+ FAT32_BSBPB *pfat32_bsbpb;
+ FAT_DES *pfat_entry;
+ uint8_t *sector0, *sector;
+
+ sector0 = NULL;
+ sector = NULL;
+
+ /* Load 1st sector with boot sector and boot parameter block. */
+ sector0 = (uint8_t *)read_buf(fp, 0, 512);
+ if (sector0 == NULL)
+ return (1);
+
+ /* Check for the FAT boot sector signature. */
+ if (sector0[510] != 0x55 || sector0[511] != 0xaa) {
+ goto error;
+ }
+
+ /*
+ * Test if this is really a FAT volume and determine the FAT type.
+ */
+
+ pfat_bsbpb = (FAT_BSBPB *)sector0;
+ pfat32_bsbpb = (FAT32_BSBPB *)sector0;
+
+ if (UINT16BYTES(pfat_bsbpb->BPB_FATSz16) != 0) {
+ /*
+ * If the BPB_FATSz16 field is not zero and the string "FAT" is
+ * at the right place, this should be a FAT12 or FAT16 volume.
+ */
+ if (strncmp(pfat_bsbpb->BS_FilSysType, "FAT", 3) != 0) {
+ goto error;
+ }
+
+ /* A volume with no name should have "NO NAME " as label. */
+ if (strncmp(pfat_bsbpb->BS_VolLab, LABEL_NO_NAME,
+ sizeof(pfat_bsbpb->BS_VolLab)) == 0) {
+ goto endofchecks;
+ }
+ strlcpy(label, pfat_bsbpb->BS_VolLab,
+ MIN(size, sizeof(pfat_bsbpb->BS_VolLab) + 1));
+ } else if (UINT32BYTES(pfat32_bsbpb->BPB_FATSz32) != 0) {
+ uint32_t fat_FirstDataSector, fat_BytesPerSector, offset;
+
+ /*
+ * If the BPB_FATSz32 field is not zero and the string "FAT" is
+ * at the right place, this should be a FAT32 volume.
+ */
+ if (strncmp(pfat32_bsbpb->BS_FilSysType, "FAT", 3) != 0) {
+ goto error;
+ }
+
+ /*
+ * If the volume label is not "NO NAME " we're done.
+ */
+ if (strncmp(pfat32_bsbpb->BS_VolLab, LABEL_NO_NAME,
+ sizeof(pfat32_bsbpb->BS_VolLab)) != 0) {
+ strlcpy(label, pfat32_bsbpb->BS_VolLab,
+ MIN(size, sizeof(pfat32_bsbpb->BS_VolLab) + 1));
+ goto endofchecks;
+ }
+
+ /*
+ * If the volume label "NO NAME " is in the boot sector, the
+ * label of FAT32 volumes may be stored as a special entry in
+ * the root directory.
+ */
+ fat_FirstDataSector =
+ UINT16BYTES(pfat32_bsbpb->BPB_RsvdSecCnt) +
+ (pfat32_bsbpb->BPB_NumFATs *
+ UINT32BYTES(pfat32_bsbpb->BPB_FATSz32));
+ fat_BytesPerSector = UINT16BYTES(pfat32_bsbpb->BPB_BytsPerSec);
+
+ // fat_FirstDataSector, fat_BytesPerSector);
+
+ for (offset = fat_BytesPerSector * fat_FirstDataSector;;
+ offset += fat_BytesPerSector) {
+ sector = (uint8_t *)read_buf(fp, offset, fat_BytesPerSector);
+ if (sector == NULL)
+ goto error;
+
+ pfat_entry = (FAT_DES *)sector;
+ do {
+ /* No more entries available. */
+ if (pfat_entry->DIR_Name[0] == 0) {
+ goto endofchecks;
+ }
+
+ /* Skip empty or long name entries. */
+ if (pfat_entry->DIR_Name[0] == 0xe5 ||
+ (pfat_entry->DIR_Attr &
+ FAT_DES_ATTR_LONG_NAME) ==
+ FAT_DES_ATTR_LONG_NAME) {
+ continue;
+ }
+
+ /*
+ * The name of the entry is the volume label if
+ * ATTR_VOLUME_ID is set.
+ */
+ if (pfat_entry->DIR_Attr &
+ FAT_DES_ATTR_VOLUME_ID) {
+ strlcpy(label, pfat_entry->DIR_Name,
+ MIN(size,
+ sizeof(pfat_entry->DIR_Name) + 1));
+ goto endofchecks;
+ }
+ } while((uint8_t *)(++pfat_entry) <
+ (uint8_t *)(sector + fat_BytesPerSector));
+ free(sector);
+ }
+ } else {
+ goto error;
+ }
+
+endofchecks:
+ rtrim(label, size);
+
+ free(sector0);
+ free(sector);
+
+ return (0);
+
+error:
+ free(sector0);
+ free(sector);
+
+ return (1);
+}
diff --git a/usr.sbin/fstyp/msdosfs.h b/usr.sbin/fstyp/msdosfs.h
new file mode 100644
index 0000000..a04b87f
--- /dev/null
+++ b/usr.sbin/fstyp/msdosfs.h
@@ -0,0 +1,140 @@
+/*-
+ * Copyright (c) 2006 Tobias Reifenberger
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/types.h>
+
+/*
+ * Conversion macros for little endian encoded unsigned integers
+ * in byte streams to the local unsigned integer format.
+ */
+#define UINT16BYTES(p) ((uint32_t)((p)[0] + (256*(p)[1])))
+#define UINT32BYTES(p) ((uint32_t)((p)[0] + (256*(p)[1]) + \
+ (65536*(p)[2]) + (16777216*(p)[3])))
+
+/*
+ * All following structures are according to:
+ *
+ * Microsoft Extensible Firmware Initiative FAT32 File System Specification
+ * FAT: General Overview of On-Disk Format
+ * Version 1.03, December 6, 2000
+ * Microsoft Corporation
+ */
+
+/*
+ * FAT boot sector and boot parameter block for
+ * FAT12 and FAT16 volumes
+ */
+typedef struct fat_bsbpb {
+ /* common fields */
+ uint8_t BS_jmpBoot[3];
+ uint8_t BS_OEMName[8];
+ uint8_t BPB_BytsPerSec[2];
+ uint8_t BPB_SecPerClus;
+ uint8_t BPB_RsvdSecCnt[2];
+ uint8_t BPB_NumFATs;
+ uint8_t BPB_RootEntCnt[2];
+ uint8_t BPB_TotSec16[2];
+ uint8_t BPB_Media;
+ uint8_t BPB_FATSz16[2];
+ uint8_t BPB_SecPerTrack[2];
+ uint8_t BPB_NumHeads[2];
+ uint8_t BPB_HiddSec[4];
+ uint8_t BPB_TotSec32[4];
+ /* FAT12/FAT16 only fields */
+ uint8_t BS_DrvNum;
+ uint8_t BS_Reserved1;
+ uint8_t BS_BootSig;
+ uint8_t BS_VolID[4];
+ uint8_t BS_VolLab[11];
+ uint8_t BS_FilSysType[8];
+} FAT_BSBPB; /* 62 bytes */
+
+/*
+ * FAT boot sector and boot parameter block for
+ * FAT32 volumes
+ */
+typedef struct fat32_bsbpb {
+ /* common fields */
+ uint8_t BS_jmpBoot[3];
+ uint8_t BS_OEMName[8];
+ uint8_t BPB_BytsPerSec[2];
+ uint8_t BPB_SecPerClus;
+ uint8_t BPB_RsvdSecCnt[2];
+ uint8_t BPB_NumFATs;
+ uint8_t BPB_RootEntCnt[2];
+ uint8_t BPB_TotSec16[2];
+ uint8_t BPB_Media;
+ uint8_t BPB_FATSz16[2];
+ uint8_t BPB_SecPerTrack[2];
+ uint8_t BPB_NumHeads[2];
+ uint8_t BPB_HiddSec[4];
+ uint8_t BPB_TotSec32[4];
+ /* FAT32 only fields */
+ uint8_t BPB_FATSz32[4];
+ uint8_t BPB_ExtFlags[2];
+ uint8_t BPB_FSVer[2];
+ uint8_t BPB_RootClus[4];
+ uint8_t BPB_FSInfo[2];
+ uint8_t BPB_BkBootSec[2];
+ uint8_t BPB_Reserved[12];
+ uint8_t BS_DrvNum;
+ uint8_t BS_Reserved1;
+ uint8_t BS_BootSig;
+ uint8_t BS_VolID[4];
+ uint8_t BS_VolLab[11];
+ uint8_t BS_FilSysType[8];
+} FAT32_BSBPB; /* 90 bytes */
+
+/*
+ * FAT directory entry structure
+ */
+#define FAT_DES_ATTR_READ_ONLY 0x01
+#define FAT_DES_ATTR_HIDDEN 0x02
+#define FAT_DES_ATTR_SYSTEM 0x04
+#define FAT_DES_ATTR_VOLUME_ID 0x08
+#define FAT_DES_ATTR_DIRECTORY 0x10
+#define FAT_DES_ATTR_ARCHIVE 0x20
+#define FAT_DES_ATTR_LONG_NAME (FAT_DES_ATTR_READ_ONLY | \
+ FAT_DES_ATTR_HIDDEN | \
+ FAT_DES_ATTR_SYSTEM | \
+ FAT_DES_ATTR_VOLUME_ID)
+
+typedef struct fat_des {
+ uint8_t DIR_Name[11];
+ uint8_t DIR_Attr;
+ uint8_t DIR_NTRes;
+ uint8_t DIR_CrtTimeTenth;
+ uint8_t DIR_CrtTime[2];
+ uint8_t DIR_CrtDate[2];
+ uint8_t DIR_LstAccDate[2];
+ uint8_t DIR_FstClusHI[2];
+ uint8_t DIR_WrtTime[2];
+ uint8_t DIR_WrtDate[2];
+ uint8_t DIR_FstClusLO[2];
+ uint8_t DIR_FileSize[4];
+} FAT_DES;
diff --git a/usr.sbin/fstyp/ntfs.c b/usr.sbin/fstyp/ntfs.c
new file mode 100644
index 0000000..9058410
--- /dev/null
+++ b/usr.sbin/fstyp/ntfs.c
@@ -0,0 +1,163 @@
+/*-
+ * Copyright (c) 2005 Takanori Watanabe
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "fstyp.h"
+
+#define NTFS_A_VOLUMENAME 0x60
+#define NTFS_FILEMAGIC ((uint32_t)(0x454C4946))
+#define NTFS_VOLUMEINO 3
+
+struct ntfs_attr {
+ uint32_t a_type;
+ uint32_t reclen;
+ uint8_t a_flag;
+ uint8_t a_namelen;
+ uint8_t a_nameoff;
+ uint8_t reserved1;
+ uint8_t a_compression;
+ uint8_t reserved2;
+ uint16_t a_index;
+ uint16_t a_datalen;
+ uint16_t reserved3;
+ uint16_t a_dataoff;
+ uint16_t a_indexed;
+} __packed;
+
+struct ntfs_filerec {
+ uint32_t fr_hdrmagic;
+ uint16_t fr_hdrfoff;
+ uint16_t fr_hdrfnum;
+ uint8_t reserved[8];
+ uint16_t fr_seqnum;
+ uint16_t fr_nlink;
+ uint16_t fr_attroff;
+ uint16_t fr_flags;
+ uint32_t fr_size;
+ uint32_t fr_allocated;
+ uint64_t fr_mainrec;
+ uint16_t fr_attrnum;
+} __packed;
+
+struct ntfs_bootfile {
+ uint8_t reserved1[3];
+ uint8_t bf_sysid[8];
+ uint16_t bf_bps;
+ uint8_t bf_spc;
+ uint8_t reserved2[7];
+ uint8_t bf_media;
+ uint8_t reserved3[2];
+ uint16_t bf_spt;
+ uint16_t bf_heads;
+ uint8_t reserver4[12];
+ uint64_t bf_spv;
+ uint64_t bf_mftcn;
+ uint64_t bf_mftmirrcn;
+ int8_t bf_mftrecsz;
+ uint32_t bf_ibsz;
+ uint32_t bf_volsn;
+} __packed;
+
+int
+fstyp_ntfs(FILE *fp, char *label, size_t size)
+{
+ struct ntfs_bootfile *bf;
+ struct ntfs_filerec *fr;
+ struct ntfs_attr *atr;
+ off_t voloff;
+ char *filerecp, *ap;
+ int8_t mftrecsz;
+ char vnchar;
+ int recsize, j;
+
+ filerecp = NULL;
+
+ bf = (struct ntfs_bootfile *)read_buf(fp, 0, 512);
+ if (bf == NULL || strncmp(bf->bf_sysid, "NTFS ", 8) != 0)
+ goto fail;
+
+ mftrecsz = bf->bf_mftrecsz;
+ recsize = (mftrecsz > 0) ? (mftrecsz * bf->bf_bps * bf->bf_spc) : (1 << -mftrecsz);
+
+ voloff = bf->bf_mftcn * bf->bf_spc * bf->bf_bps +
+ recsize * NTFS_VOLUMEINO;
+
+ filerecp = read_buf(fp, voloff, recsize);
+ if (filerecp == NULL)
+ goto fail;
+ fr = (struct ntfs_filerec *)filerecp;
+
+ if (fr->fr_hdrmagic != NTFS_FILEMAGIC)
+ goto fail;
+
+ for (ap = filerecp + fr->fr_attroff;
+ atr = (struct ntfs_attr *)ap, (int)atr->a_type != -1;
+ ap += atr->reclen) {
+ if (atr->a_type == NTFS_A_VOLUMENAME) {
+ if(atr->a_datalen >= size *2){
+ goto fail;
+ }
+ /*
+ *UNICODE to ASCII.
+ * Should we need to use iconv(9)?
+ */
+ for (j = 0; j < atr->a_datalen; j++) {
+ vnchar = *(ap + atr->a_dataoff + j);
+ if (j & 1) {
+ if (vnchar) {
+ goto fail;
+ }
+ } else {
+ label[j / 2] = vnchar;
+ }
+ }
+ label[j / 2] = 0;
+ break;
+ }
+ }
+
+ free(bf);
+ free(filerecp);
+
+ return (0);
+
+fail:
+ free(bf);
+ free(filerecp);
+
+ return (1);
+}
diff --git a/usr.sbin/fstyp/tests/Makefile b/usr.sbin/fstyp/tests/Makefile
new file mode 100644
index 0000000..01c9869
--- /dev/null
+++ b/usr.sbin/fstyp/tests/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+ATF_TESTS_SH= fstyp_test
+
+FILES= ext2.img.bz2
+FILES+= ext3.img.bz2
+FILES+= ext4.img.bz2
+FILES+= ext4_with_label.img.bz2
+FILES+= ntfs.img.bz2
+FILES+= ntfs_with_label.img.bz2
+FILESDIR= ${TESTSDIR}
+
+.include <bsd.test.mk>
diff --git a/usr.sbin/fstyp/tests/ext2.img.bz2 b/usr.sbin/fstyp/tests/ext2.img.bz2
new file mode 100644
index 0000000..b07389e
--- /dev/null
+++ b/usr.sbin/fstyp/tests/ext2.img.bz2
Binary files differ
diff --git a/usr.sbin/fstyp/tests/ext3.img.bz2 b/usr.sbin/fstyp/tests/ext3.img.bz2
new file mode 100644
index 0000000..7d8b9e2
--- /dev/null
+++ b/usr.sbin/fstyp/tests/ext3.img.bz2
Binary files differ
diff --git a/usr.sbin/fstyp/tests/ext4.img.bz2 b/usr.sbin/fstyp/tests/ext4.img.bz2
new file mode 100644
index 0000000..d24e0f8
--- /dev/null
+++ b/usr.sbin/fstyp/tests/ext4.img.bz2
Binary files differ
diff --git a/usr.sbin/fstyp/tests/ext4_with_label.img.bz2 b/usr.sbin/fstyp/tests/ext4_with_label.img.bz2
new file mode 100644
index 0000000..4b77619
--- /dev/null
+++ b/usr.sbin/fstyp/tests/ext4_with_label.img.bz2
Binary files differ
diff --git a/usr.sbin/fstyp/tests/fstyp_test.sh b/usr.sbin/fstyp/tests/fstyp_test.sh
new file mode 100755
index 0000000..8a1ea72
--- /dev/null
+++ b/usr.sbin/fstyp/tests/fstyp_test.sh
@@ -0,0 +1,234 @@
+# $FreeBSD$
+
+atf_test_case cd9660
+cd9660_head() {
+ atf_set "descr" "fstyp(8) should detect cd9660 filesystems"
+}
+cd9660_body() {
+ atf_check -s exit:0 mkdir -p dir/emptydir # makefs requires a nonempty directory
+ atf_check -s exit:0 -o ignore makefs -t cd9660 -Z -s 64m cd9660.img dir
+ atf_check -s exit:0 -o inline:"cd9660\n" fstyp cd9660.img
+ atf_check -s exit:0 -o inline:"cd9660\n" fstyp -l cd9660.img
+}
+
+atf_test_case cd9660_label
+cd9660_label_head() {
+ atf_set "descr" "fstyp(8) can read the label on a cd9660 filesystem"
+}
+cd9660_label_body() {
+ atf_check -s exit:0 mkdir -p dir/emptydir # makefs requires a nonempty directory
+ atf_check -s exit:0 -o ignore makefs -t cd9660 -o label=Foo -Z -s 64m cd9660.img dir
+ atf_check -s exit:0 -o inline:"cd9660\n" fstyp cd9660.img
+ # Note: cd9660 labels are always upper case
+ atf_check -s exit:0 -o inline:"cd9660 FOO\n" fstyp -l cd9660.img
+}
+
+atf_test_case dir
+dir_head() {
+ atf_set "descr" "fstyp(8) should fail on a directory"
+}
+dir_body() {
+ atf_check -s exit:0 mkdir dir
+ atf_check -s exit:1 -e match:"not a disk" fstyp dir
+}
+
+atf_test_case empty
+empty_head() {
+ atf_set "descr" "fstyp(8) should fail on an empty file"
+}
+empty_body() {
+ atf_check -s exit:0 touch empty
+ atf_check -s exit:1 -e match:"filesystem not recognized" fstyp empty
+}
+
+atf_test_case ext2
+ext2_head() {
+ atf_set "descr" "fstyp(8) can detect ext2 filesystems"
+}
+ext2_body() {
+ bzcat $(atf_get_srcdir)/ext2.img.bz2 > ext2.img
+ atf_check -s exit:0 -o inline:"ext2fs\n" fstyp ext2.img
+ atf_check -s exit:0 -o inline:"ext2fs\n" fstyp -l ext2.img
+}
+
+atf_test_case ext3
+ext3_head() {
+ atf_set "descr" "fstyp(8) can detect ext3 filesystems"
+}
+ext3_body() {
+ bzcat $(atf_get_srcdir)/ext3.img.bz2 > ext3.img
+ atf_check -s exit:0 -o inline:"ext2fs\n" fstyp ext3.img
+ atf_check -s exit:0 -o inline:"ext2fs\n" fstyp -l ext3.img
+}
+
+atf_test_case ext4
+ext4_head() {
+ atf_set "descr" "fstyp(8) can detect ext4 filesystems"
+}
+ext4_body() {
+ bzcat $(atf_get_srcdir)/ext4.img.bz2 > ext4.img
+ atf_check -s exit:0 -o inline:"ext2fs\n" fstyp ext4.img
+ atf_check -s exit:0 -o inline:"ext2fs\n" fstyp -l ext4.img
+}
+
+atf_test_case ext4_label
+ext4_label_head() {
+ atf_set "descr" "fstyp(8) can read the label on an ext4 filesystem"
+}
+ext4_label_body() {
+ bzcat $(atf_get_srcdir)/ext4_with_label.img.bz2 > ext4_with_label.img
+ atf_check -s exit:0 -o inline:"ext2fs foo\n" fstyp -l ext4_with_label.img
+}
+
+atf_test_case fat12
+fat12_head() {
+ atf_set "descr" "fstyp(8) can detect FAT12 filesystems"
+}
+fat12_body() {
+ atf_check -s exit:0 truncate -s 64m msdos.img
+ atf_check -s exit:0 -o ignore -e ignore newfs_msdos -F 12 ./msdos.img
+ atf_check -s exit:0 -o inline:"msdosfs\n" fstyp msdos.img
+ atf_check -s exit:0 -o inline:"msdosfs\n" fstyp -l msdos.img
+}
+
+atf_test_case fat16
+fat16_head() {
+ atf_set "descr" "fstyp(8) can detect FAT16 filesystems"
+}
+fat16_body() {
+ atf_check -s exit:0 truncate -s 64m msdos.img
+ atf_check -s exit:0 -o ignore -e ignore newfs_msdos -F 16 ./msdos.img
+ atf_check -s exit:0 -o inline:"msdosfs\n" fstyp msdos.img
+ atf_check -s exit:0 -o inline:"msdosfs\n" fstyp -l msdos.img
+}
+
+atf_test_case fat32
+fat32_head() {
+ atf_set "descr" "fstyp(8) can detect FAT32 filesystems"
+}
+fat32_body() {
+ atf_check -s exit:0 truncate -s 64m msdos.img
+ atf_check -s exit:0 -o ignore -e ignore newfs_msdos -F 32 -c 1 \
+ ./msdos.img
+ atf_check -s exit:0 -o inline:"msdosfs\n" fstyp msdos.img
+ atf_check -s exit:0 -o inline:"msdosfs\n" fstyp -l msdos.img
+}
+
+atf_test_case fat32_label
+fat32_label_head() {
+ atf_set "descr" "fstyp(8) can read the label on an msdos filesystem"
+}
+fat32_label_body() {
+ atf_check -s exit:0 truncate -s 64m msdos.img
+ atf_check -s exit:0 -o ignore -e ignore newfs_msdos -F 32 -L Foo -c 1 \
+ ./msdos.img
+ atf_check -s exit:0 -o inline:"msdosfs\n" fstyp msdos.img
+ # Note: msdos labels are always upper case
+ atf_check -s exit:0 -o inline:"msdosfs FOO\n" fstyp -l msdos.img
+}
+
+atf_test_case ntfs
+ntfs_head() {
+ atf_set "descr" "fstyp(8) can detect ntfs filesystems"
+}
+ntfs_body() {
+ bzcat $(atf_get_srcdir)/ntfs.img.bz2 > ntfs.img
+ atf_check -s exit:0 -o inline:"ntfs\n" fstyp ntfs.img
+ atf_check -s exit:0 -o inline:"ntfs\n" fstyp -l ntfs.img
+}
+
+atf_test_case ntfs_with_label
+ntfs_with_label_head() {
+ atf_set "descr" "fstyp(8) can read labels on ntfs filesystems"
+}
+ntfs_with_label_body() {
+ bzcat $(atf_get_srcdir)/ntfs_with_label.img.bz2 > ntfs_with_label.img
+ atf_check -s exit:0 -o inline:"ntfs\n" fstyp ntfs_with_label.img
+ atf_check -s exit:0 -o inline:"ntfs Foo\n" fstyp -l ntfs_with_label.img
+}
+
+atf_test_case ufs1
+ufs1_head() {
+ atf_set "descr" "fstyp(8) should detect UFS version 1 filesystems"
+}
+ufs1_body() {
+ atf_check -s exit:0 mkdir dir
+ atf_check -s exit:0 -o ignore makefs -Z -s 64m ufs.img dir
+ atf_check -s exit:0 -o inline:"ufs\n" fstyp ufs.img
+ atf_check -s exit:0 -o inline:"ufs\n" fstyp -l ufs.img
+}
+
+atf_test_case ufs2
+ufs2_head() {
+ atf_set "descr" "fstyp(8) should detect UFS version 2 filesystems"
+}
+ufs2_body() {
+ atf_check -s exit:0 mkdir dir
+ atf_check -s exit:0 -o ignore makefs -o version=2 -Z -s 64m ufs.img dir
+ atf_check -s exit:0 -o inline:"ufs\n" fstyp ufs.img
+ atf_check -s exit:0 -o inline:"ufs\n" fstyp -l ufs.img
+}
+
+atf_test_case ufs2_label
+ufs2_label_head() {
+ atf_set "descr" "fstyp(8) can read the label on a UFS v2 filesystem"
+}
+ufs2_label_body() {
+ atf_check -s exit:0 mkdir dir
+ atf_check -s exit:0 -o ignore makefs -o version=2,label="foo" -Z -s 64m ufs.img dir
+ atf_check -s exit:0 -o inline:"ufs foo\n" fstyp -l ufs.img
+}
+
+atf_test_case ufs_on_device cleanup
+ufs_on_device_head() {
+ atf_set "descr" "fstyp(8) should work on device nodes"
+ atf_set "require.user" "root"
+}
+ufs_on_device_body() {
+ mdconfig -a -t swap -s 64m > mdname
+ md=$(cat mdname)
+ if [ -z "$md" ]; then
+ atf_fail "Failed to create md(4) device"
+ fi
+ atf_check -s exit:0 -o ignore newfs -L foo /dev/$md
+ atf_check -s exit:0 -o inline:"ufs\n" fstyp /dev/$md
+ atf_check -s exit:0 -o inline:"ufs foo\n" fstyp -l /dev/$md
+}
+ufs_on_device_cleanup() {
+ md=$(cat mdname)
+ if [ -n "$md" ]; then
+ mdconfig -d -u "$md"
+ fi
+}
+
+atf_test_case zeros
+zeros_head() {
+ atf_set "descr" "fstyp(8) should fail on a zero-filled file"
+}
+zeros_body() {
+ atf_check -s exit:0 truncate -s 256m zeros
+ atf_check -s exit:1 -e match:"filesystem not recognized" fstyp zeros
+}
+
+
+atf_init_test_cases() {
+ atf_add_test_case cd9660
+ atf_add_test_case cd9660_label
+ atf_add_test_case dir
+ atf_add_test_case empty
+ atf_add_test_case ext2
+ atf_add_test_case ext3
+ atf_add_test_case ext4
+ atf_add_test_case ext4_label
+ atf_add_test_case fat12
+ atf_add_test_case fat16
+ atf_add_test_case fat32
+ atf_add_test_case fat32_label
+ atf_add_test_case ntfs
+ atf_add_test_case ntfs_with_label
+ atf_add_test_case ufs1
+ atf_add_test_case ufs2
+ atf_add_test_case ufs2_label
+ atf_add_test_case ufs_on_device
+ atf_add_test_case zeros
+}
diff --git a/usr.sbin/fstyp/tests/ntfs.img.bz2 b/usr.sbin/fstyp/tests/ntfs.img.bz2
new file mode 100644
index 0000000..5843e0b
--- /dev/null
+++ b/usr.sbin/fstyp/tests/ntfs.img.bz2
Binary files differ
diff --git a/usr.sbin/fstyp/tests/ntfs_with_label.img.bz2 b/usr.sbin/fstyp/tests/ntfs_with_label.img.bz2
new file mode 100644
index 0000000..9e2c4cf
--- /dev/null
+++ b/usr.sbin/fstyp/tests/ntfs_with_label.img.bz2
Binary files differ
diff --git a/usr.sbin/fstyp/ufs.c b/usr.sbin/fstyp/ufs.c
new file mode 100644
index 0000000..8b27ca0
--- /dev/null
+++ b/usr.sbin/fstyp/ufs.c
@@ -0,0 +1,109 @@
+/*-
+ * Copyright (c) 2002, 2003 Gordon Tetlow
+ * Copyright (c) 2006 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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 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 <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include "fstyp.h"
+
+static const int superblocks[] = SBLOCKSEARCH;
+
+int
+fstyp_ufs(FILE *fp, char *label, size_t labelsize)
+{
+ int sb, superblock;
+ struct fs *fs;
+
+ /*
+ * Walk through the standard places that superblocks hide and look
+ * for UFS magic. If we find magic, then check that the size in the
+ * superblock corresponds to the size of the underlying provider.
+ * Finally, look for a volume label and create an appropriate
+ * provider based on that.
+ */
+ for (sb = 0; (superblock = superblocks[sb]) != -1; sb++) {
+ fs = (struct fs *)read_buf(fp, superblock, SBLOCKSIZE);
+ if (fs == NULL)
+ continue;
+ /*
+ * Check for magic. We also need to check if file system size is equal
+ * to providers size, because sysinstall(8) used to bogusly put first
+ * partition at offset 0 instead of 16, and glabel/ufs would find file
+ * system on slice instead of partition.
+ */
+#ifdef notyet
+ if (fs->fs_magic == FS_UFS1_MAGIC && fs->fs_fsize > 0 &&
+ ((pp->mediasize / fs->fs_fsize == fs->fs_old_size) ||
+ (pp->mediasize / fs->fs_fsize == fs->fs_providersize))) {
+ /* Valid UFS1. */
+ } else if (fs->fs_magic == FS_UFS2_MAGIC && fs->fs_fsize > 0 &&
+ ((pp->mediasize / fs->fs_fsize == fs->fs_size) ||
+ (pp->mediasize / fs->fs_fsize == fs->fs_providersize))) {
+ /* Valid UFS2. */
+ } else {
+ g_free(fs);
+ continue;
+ }
+#else
+ if (fs->fs_magic == FS_UFS1_MAGIC && fs->fs_fsize > 0) {
+ /* Valid UFS1. */
+ } else if (fs->fs_magic == FS_UFS2_MAGIC && fs->fs_fsize > 0) {
+ /* Valid UFS2. */
+ } else {
+ free(fs);
+ continue;
+ }
+#endif
+
+ if (fs->fs_sblockloc != superblock || fs->fs_ncg < 1 ||
+ fs->fs_bsize < MINBSIZE ||
+ (size_t)fs->fs_bsize < sizeof(struct fs)) {
+ free(fs);
+ continue;
+ }
+
+ strlcpy(label, fs->fs_volname, labelsize);
+
+ free(fs);
+ return (0);
+ }
+
+ return (1);
+}
diff --git a/usr.sbin/fstyp/zfs.c b/usr.sbin/fstyp/zfs.c
new file mode 100644
index 0000000..c37a5db
--- /dev/null
+++ b/usr.sbin/fstyp/zfs.c
@@ -0,0 +1,78 @@
+/*-
+ * Copyright (c) 2015 Allan Jude <allanjude@FreeBSD.org>
+ * Copyright (c) 2015 Xin LI <delphij@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 <cddl/compat/opensolaris/sys/types.h>
+#include <sys/time.h>
+#include <cddl/compat/opensolaris/sys/time.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libnvpair.h>
+#include <sys/vdev_impl.h>
+
+#include "fstyp.h"
+
+int
+fstyp_zfs(FILE *fp, char *label, size_t labelsize)
+{
+ vdev_label_t *vdev_label = NULL;
+ vdev_phys_t *vdev_phys;
+ char *zpool_name = NULL;
+ nvlist_t *config = NULL;
+ int err = 0;
+
+ /*
+ * Read in the first ZFS vdev label ("L0"), located at the beginning
+ * of the vdev and extract the pool name from it.
+ *
+ * TODO: the checksum of label should be validated.
+ */
+ vdev_label = (vdev_label_t *)read_buf(fp, 0, sizeof(*vdev_label));
+ if (vdev_label == NULL)
+ return (1);
+
+ vdev_phys = &(vdev_label->vl_vdev_phys);
+
+ if ((nvlist_unpack(vdev_phys->vp_nvlist, sizeof(vdev_phys->vp_nvlist),
+ &config, 0)) == 0 &&
+ (nvlist_lookup_string(config, "name", &zpool_name) == 0)) {
+ strlcpy(label, zpool_name, labelsize);
+ } else
+ err = 1;
+
+ nvlist_free(config);
+ free(vdev_label);
+
+ return (err);
+}
diff --git a/usr.sbin/ftp-proxy/Makefile b/usr.sbin/ftp-proxy/Makefile
new file mode 100644
index 0000000..103ea86
--- /dev/null
+++ b/usr.sbin/ftp-proxy/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../contrib/pf/ftp-proxy
+
+PROG= ftp-proxy
+MAN= ftp-proxy.8
+
+SRCS= ftp-proxy.c filter.c
+
+CFLAGS+=-I${.CURDIR}/../../contrib/pf/libevent
+
+LIBADD= event
+
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ftp-proxy/Makefile.depend b/usr.sbin/ftp-proxy/Makefile.depend
new file mode 100644
index 0000000..c02eb2d
--- /dev/null
+++ b/usr.sbin/ftp-proxy/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libevent \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/fwcontrol/Makefile b/usr.sbin/fwcontrol/Makefile
new file mode 100644
index 0000000..10320d2
--- /dev/null
+++ b/usr.sbin/fwcontrol/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+PROG= fwcontrol
+SRCS= fwcontrol.c fwcrom.c fwdv.c fwmpegts.c
+MAN= fwcontrol.8
+WARNS?= 3
+
+.PATH: ${.CURDIR}/../../sys/dev/firewire
+
+SDIR= ${.CURDIR}/../../sys
+CFLAGS+=-I${.CURDIR} -I${SDIR}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/fwcontrol/Makefile.depend b/usr.sbin/fwcontrol/Makefile.depend
new file mode 100644
index 0000000..54c1f6f
--- /dev/null
+++ b/usr.sbin/fwcontrol/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/fwcontrol/fwcontrol.8 b/usr.sbin/fwcontrol/fwcontrol.8
new file mode 100644
index 0000000..4e9b61b
--- /dev/null
+++ b/usr.sbin/fwcontrol/fwcontrol.8
@@ -0,0 +1,220 @@
+.\" Copyright (c) 2002 Hidetoshi Shimokawa
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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$
+.\"
+.Dd September 12, 2008
+.Dt FWCONTROL 8
+.Os
+.Sh NAME
+.Nm fwcontrol
+.Nd FireWire control utility
+.Sh SYNOPSIS
+.Nm
+.Op Fl u Ar bus_num
+.Op Fl prt
+.Op Fl c Ar node
+.Op Fl d Ar node
+.Op Fl o Ar node
+.Op Fl s Ar node
+.Op Fl l Ar file
+.Op Fl f Ar node
+.Op Fl g Ar gap_count
+.Op Fl b Ar pri_req
+.Op Fl M Ar mode
+.Op Fl R Ar filename
+.Op Fl S Ar filename
+.Op Fl m Ar EUI64 | hostname
+.Sh DESCRIPTION
+The
+.Nm
+utility is designed to provide a way for users to access and control the
+.Fx
+FireWire subsystem.
+Without options,
+.Nm
+will output a list of devices that are/were connected to the bus.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl u Ar bus_num
+Specify the FireWire bus number to be operated on.
+The default is bus 0.
+.It Fl r
+Initiate bus reset.
+.It Fl t
+Show the topology map.
+.It Fl p
+Dump PHY registers.
+.It Fl c Ar node
+Show the configuration ROM on the node.
+.It Fl d Ar node
+Hex dump of the configuration ROM.
+.It Fl o Ar node
+Send a link-on PHY packet to the node.
+.It Fl s Ar node
+Write to the
+.Dv RESET_START
+register on the node.
+.It Fl l Ar file
+Load hex dump file of the configuration ROM and parse it.
+.It Fl f Ar node
+Force specified
+.Ar node
+to be the root node on the next bus reset by sending a PHY config packet.
+Valid values are 0 - 63.
+.It Fl g Ar gap_count
+Broadcast new
+.Ar gap_count
+by sending a PHY_config packet.
+By default this value is 63 on all nodes.
+Valid values are 0 - 63.
+.It Fl i Ar pri_req
+Set the
+.Dv PRIORITY_BUDGET
+register on all supported nodes.
+.It Fl M Ar mode
+Explicitly specify either
+.Ar dv
+or
+.Ar mpeg
+mode for the incoming stream.
+Only meaningful in case of and must precede the
+.Fl R
+option.
+If not specified, the program will try to guess.
+In case of
+.Dq format 0x20
+error, try to force the
+.Dq mpeg
+mode.
+.It Fl R Ar filename
+Receive DV or MPEG TS stream and dump it to a file.
+Use ^C to stop the receiving.
+Some DV cameras seem not to send the stream if a bus manager exists.
+If it is impossible to get the stream, try the following commands:
+.Bd -literal -offset indent
+sysctl hw.firewire.try_bmr=0
+fwcontrol -r
+.Ed
+.Pp
+The resulting file contains raw DV data excluding isochronous header
+and CIP header.
+It can be handled by
+.Nm libdv
+in the
+.Fx
+Ports Collection.
+Resulting MPEG TS stream can be played and sent over a
+network using the VideoLAN
+.Nm vlc
+tool in the
+.Fx
+Ports Collection.
+The stream can be piped directly to
+.Nm vlc,
+see
+.Sx EXAMPLES .
+.It Fl S Ar filename
+Send a DV file as isochronous stream.
+.It Fl m Ar EUI64 | hostname
+Set default fwmem target.
+Hostname will be converted to EUI64 using
+.Xr eui64 5 .
+.El
+.Sh FILES
+.Bl -tag -width "Pa /dev/fw0.0"
+.It Pa /dev/fw0.0
+.El
+.Sh EXAMPLES
+Each DV frame has a fixed size and it is easy to edit the frame order.
+.Pp
+.Dl "fwcontrol -R original.dv"
+.Pp
+Receive a DV stream with DV camera attached.
+.Pp
+.Dl "dd if=original.dv of=first.dv bs=120000 count=30"
+.Pp
+Get first 30 frames(NTSC).
+.Pp
+.Dl "dd if=original.dv of=second.dv bs=120000 skip=30 count=30"
+.Pp
+Get second 30 frames(NTSC).
+.Pp
+.Dl "cat second.dv first.dv | fwcontrol -S /dev/stdin"
+.Pp
+Swap first and second 30 frames and send them to DV recorder.
+.Pp
+For PAL, replace
+.Dq Li bs=120000
+with
+.Dq Li bs=144000 .
+.Pp
+.Dl "fwcontrol -R file.m2t"
+.Pp
+Receive an MPEG TS stream from a camera producing MPEG transport stream.
+This has been tested with SONY HDR-FX1E camera that produces HD MPEG-2
+stream at 25 Mbps bandwidth.
+.Pp
+To send the stream from the camera over the network using TCP (which
+surprisingly works better with vlc), you can use
+.Dl "fwcontrol -R - | nc 192.168.10.11 9000"
+with
+.Nm netcat
+from ports and to receive the stream, use
+.Dl nc -l -p 9000 | vlc -
+.Pp
+To netcast via UDP, you need to use
+.Nm buffer
+program from ports, since vlc is not fast enough to read UDP packets from
+buffers and thus it experiences dropouts when run directly.
+The sending side can use
+.Dl "fwcontrol -R - | nc 192.168.10.11 9000"
+and to receive the stream, use
+.Dl nc -l -u -p 9000 | buffer -s 10k -b 1000 -m 20m -p 5 | vlc -
+.Pp
+For more information on how to work with
+.Nm vlc
+see its docs.
+.Sh SEE ALSO
+.Xr mplayer 1 ,
+.Xr vlc 1 ,
+.Xr firewire 4 ,
+.Xr fwe 4 ,
+.Xr fwip 4 ,
+.Xr fwohci 4 ,
+.Xr sbp 4
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 5.0 .
+.Sh AUTHORS
+.An Hidetoshi Shimokawa Aq Mt simokawa@FreeBSD.org
+.An Petr Holub Aq Mt hopet@ics.muni.cz
+(MPEG TS mode)
+.Sh BUGS
+This utility is still under development and provided for debugging purposes.
+Especially MPEG TS reception support is very rudimental and supports only
+high-bandwidth MPEG-2 streams (fn field in CIP header equals 3).
diff --git a/usr.sbin/fwcontrol/fwcontrol.c b/usr.sbin/fwcontrol/fwcontrol.c
new file mode 100644
index 0000000..36efea1
--- /dev/null
+++ b/usr.sbin/fwcontrol/fwcontrol.c
@@ -0,0 +1,1089 @@
+/*
+ * Copyright (C) 2002
+ * Hidetoshi Shimokawa. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 Hidetoshi Shimokawa.
+ *
+ * 4. Neither the name of the author 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 defined(__FreeBSD__)
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+#endif
+
+#include <sys/param.h>
+#include <sys/malloc.h>
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/errno.h>
+#if defined(__FreeBSD__)
+#include <sys/eui64.h>
+#include <dev/firewire/firewire.h>
+#include <dev/firewire/iec13213.h>
+#include <dev/firewire/fwphyreg.h>
+#include <dev/firewire/iec68113.h>
+#elif defined(__NetBSD__)
+#include "eui64.h"
+#include <dev/ieee1394/firewire.h>
+#include <dev/ieee1394/iec13213.h>
+#include <dev/ieee1394/fwphyreg.h>
+#include <dev/ieee1394/iec68113.h>
+#else
+#warning "You need to add support for your OS"
+#endif
+
+
+#include <netinet/in.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include "fwmethods.h"
+
+static void sysctl_set_int(const char *, int);
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "%s [-u bus_num] [-prt] [-c node] [-d node] [-o node] [-s node]\n"
+ "\t [-l file] [-g gap_count] [-f force_root ] [-b pri_req]\n"
+ "\t [-M mode] [-R filename] [-S filename] [-m EUI64 | hostname]\n"
+ "\t-u: specify bus number\n"
+ "\t-p: Display current PHY register settings\n"
+ "\t-r: bus reset\n"
+ "\t-t: read topology map\n"
+ "\t-c: read configuration ROM\n"
+ "\t-d: hex dump of configuration ROM\n"
+ "\t-o: send link-on packet to the node\n"
+ "\t-s: write RESET_START register on the node\n"
+ "\t-l: load and parse hex dump file of configuration ROM\n"
+ "\t-g: set gap count\n"
+ "\t-f: force root node\n"
+ "\t-b: set PRIORITY_BUDGET register on all supported nodes\n"
+ "\t-M: specify dv or mpeg\n"
+ "\t-R: Receive DV or MPEG TS stream\n"
+ "\t-S: Send DV stream\n"
+ "\t-m: set fwmem target\n"
+ , getprogname() );
+ fprintf(stderr, "\n");
+}
+
+static void
+fweui2eui64(const struct fw_eui64 *fweui, struct eui64 *eui)
+{
+ *(u_int32_t*)&(eui->octet[0]) = htonl(fweui->hi);
+ *(u_int32_t*)&(eui->octet[4]) = htonl(fweui->lo);
+}
+
+static void
+get_dev(int fd, struct fw_devlstreq *data)
+{
+ if (data == NULL)
+ err(EX_SOFTWARE, "%s: data malloc", __func__);
+ if( ioctl(fd, FW_GDEVLST, data) < 0) {
+ err(EX_IOERR, "%s: ioctl", __func__);
+ }
+}
+
+static int
+str2node(int fd, const char *nodestr)
+{
+ struct eui64 eui, tmpeui;
+ struct fw_devlstreq *data;
+ char *endptr;
+ int i, node;
+
+ if (nodestr == '\0')
+ return (-1);
+
+ /*
+ * Deal with classic node specifications.
+ */
+ node = strtol(nodestr, &endptr, 0);
+ if (*endptr == '\0')
+ goto gotnode;
+
+ /*
+ * Try to get an eui and match it against available nodes.
+ */
+ if (eui64_hostton(nodestr, &eui) != 0 && eui64_aton(nodestr, &eui) != 0)
+ return (-1);
+
+ data = (struct fw_devlstreq *)malloc(sizeof(struct fw_devlstreq));
+ if (data == NULL)
+ err(EX_SOFTWARE, "%s: data malloc", __func__);
+ get_dev(fd,data);
+
+ for (i = 0; i < data->info_len; i++) {
+ fweui2eui64(&data->dev[i].eui, &tmpeui);
+ if (memcmp(&eui, &tmpeui, sizeof(struct eui64)) == 0) {
+ node = data->dev[i].dst;
+ free(data);
+ goto gotnode;
+ }
+ }
+ if (i >= data->info_len) {
+ if (data != NULL)
+ free(data);
+ return (-1);
+ }
+
+gotnode:
+ if (node < 0 || node > 63)
+ return (-1);
+ else
+ return (node);
+}
+
+static void
+list_dev(int fd)
+{
+ struct fw_devlstreq *data;
+ struct fw_devinfo *devinfo;
+ struct eui64 eui;
+ char addr[EUI64_SIZ], hostname[40];
+ int i;
+
+ data = (struct fw_devlstreq *)malloc(sizeof(struct fw_devlstreq));
+ if (data == NULL)
+ err(EX_SOFTWARE, "%s:data malloc", __func__);
+ get_dev(fd, data);
+ printf("%d devices (info_len=%d)\n", data->n, data->info_len);
+ printf("node EUI64 status hostname\n");
+ for (i = 0; i < data->info_len; i++) {
+ devinfo = &data->dev[i];
+ fweui2eui64(&devinfo->eui, &eui);
+ eui64_ntoa(&eui, addr, sizeof(addr));
+ if (eui64_ntohost(hostname, sizeof(hostname), &eui))
+ hostname[0] = 0;
+ printf("%4d %s %6d %s\n",
+ (devinfo->status || i == 0) ? devinfo->dst : -1,
+ addr,
+ devinfo->status,
+ hostname
+ );
+ }
+ free((void *)data);
+}
+
+static u_int32_t
+read_write_quad(int fd, struct fw_eui64 eui, u_int32_t addr_lo, int readmode, u_int32_t data)
+{
+ struct fw_asyreq *asyreq;
+ u_int32_t *qld, res;
+
+ asyreq = (struct fw_asyreq *)malloc(sizeof(struct fw_asyreq_t) + 16);
+ if (asyreq == NULL)
+ err(EX_SOFTWARE, "%s:asyreq malloc", __func__);
+ asyreq->req.len = 16;
+#if 0
+ asyreq->req.type = FWASREQNODE;
+ asyreq->pkt.mode.rreqq.dst = FWLOCALBUS | node;
+#else
+ asyreq->req.type = FWASREQEUI;
+ asyreq->req.dst.eui = eui;
+#endif
+ asyreq->pkt.mode.rreqq.tlrt = 0;
+ if (readmode)
+ asyreq->pkt.mode.rreqq.tcode = FWTCODE_RREQQ;
+ else
+ asyreq->pkt.mode.rreqq.tcode = FWTCODE_WREQQ;
+
+ asyreq->pkt.mode.rreqq.dest_hi = 0xffff;
+ asyreq->pkt.mode.rreqq.dest_lo = addr_lo;
+
+ qld = (u_int32_t *)&asyreq->pkt;
+ if (!readmode)
+ asyreq->pkt.mode.wreqq.data = htonl(data);
+
+ if (ioctl(fd, FW_ASYREQ, asyreq) < 0) {
+ err(EX_IOERR, "%s: ioctl", __func__);
+ }
+ res = qld[3];
+ free(asyreq);
+ if (readmode)
+ return ntohl(res);
+ else
+ return 0;
+}
+
+/*
+ * Send a PHY Config Packet
+ * ieee 1394a-2005 4.3.4.3
+ *
+ * Message ID Root ID R T Gap Count
+ * 00(2 bits) (6 bits) 1 1 (6 bits)
+ *
+ * if "R" is set, then Root ID will be the next
+ * root node upon the next bus reset.
+ * if "T" is set, then Gap Count will be the
+ * value that all nodes use for their Gap Count
+ * if "R" and "T" are not set, then this message
+ * is either ignored or interpreted as an extended
+ * PHY config Packet as per 1394a-2005 4.3.4.4
+ */
+static void
+send_phy_config(int fd, int root_node, int gap_count)
+{
+ struct fw_asyreq *asyreq;
+
+ asyreq = (struct fw_asyreq *)malloc(sizeof(struct fw_asyreq_t) + 12);
+ if (asyreq == NULL)
+ err(EX_SOFTWARE, "%s:asyreq malloc", __func__);
+ asyreq->req.len = 12;
+ asyreq->req.type = FWASREQNODE;
+ asyreq->pkt.mode.ld[0] = 0;
+ asyreq->pkt.mode.ld[1] = 0;
+ asyreq->pkt.mode.common.tcode = FWTCODE_PHY;
+ if (root_node >= 0)
+ asyreq->pkt.mode.ld[1] |= ((root_node << 24) | (1 << 23));
+ if (gap_count >= 0)
+ asyreq->pkt.mode.ld[1] |= ((1 << 22) | (gap_count << 16));
+ asyreq->pkt.mode.ld[2] = ~asyreq->pkt.mode.ld[1];
+
+ printf("send phy_config root_node=%d gap_count=%d\n",
+ root_node, gap_count);
+
+ if (ioctl(fd, FW_ASYREQ, asyreq) < 0)
+ err(EX_IOERR, "%s: ioctl", __func__);
+ free(asyreq);
+}
+
+static void
+link_on(int fd, int node)
+{
+ struct fw_asyreq *asyreq;
+
+ asyreq = (struct fw_asyreq *)malloc(sizeof(struct fw_asyreq_t) + 12);
+ if (asyreq == NULL)
+ err(EX_SOFTWARE, "%s:asyreq malloc", __func__);
+ asyreq->req.len = 12;
+ asyreq->req.type = FWASREQNODE;
+ asyreq->pkt.mode.common.tcode = FWTCODE_PHY;
+ asyreq->pkt.mode.ld[1] |= (1 << 30) | ((node & 0x3f) << 24);
+ asyreq->pkt.mode.ld[2] = ~asyreq->pkt.mode.ld[1];
+
+ if (ioctl(fd, FW_ASYREQ, asyreq) < 0)
+ err(EX_IOERR, "%s: ioctl", __func__);
+ free(asyreq);
+}
+
+static void
+reset_start(int fd, int node)
+{
+ struct fw_asyreq *asyreq;
+
+ asyreq = (struct fw_asyreq *)malloc(sizeof(struct fw_asyreq_t) + 16);
+ if (asyreq == NULL)
+ err(EX_SOFTWARE, "%s:asyreq malloc", __func__);
+ asyreq->req.len = 16;
+ asyreq->req.type = FWASREQNODE;
+ asyreq->pkt.mode.wreqq.dst = FWLOCALBUS | (node & 0x3f);
+ asyreq->pkt.mode.wreqq.tlrt = 0;
+ asyreq->pkt.mode.wreqq.tcode = FWTCODE_WREQQ;
+
+ asyreq->pkt.mode.wreqq.dest_hi = 0xffff;
+ asyreq->pkt.mode.wreqq.dest_lo = 0xf0000000 | RESET_START;
+
+ asyreq->pkt.mode.wreqq.data = htonl(0x1);
+
+ if (ioctl(fd, FW_ASYREQ, asyreq) < 0)
+ err(EX_IOERR, "%s: ioctl", __func__);
+ free(asyreq);
+}
+
+static void
+set_pri_req(int fd, u_int32_t pri_req)
+{
+ struct fw_devlstreq *data;
+ struct fw_devinfo *devinfo;
+ struct eui64 eui;
+ char addr[EUI64_SIZ];
+ u_int32_t max, reg, old;
+ int i;
+
+ data = (struct fw_devlstreq *)malloc(sizeof(struct fw_devlstreq));
+ if (data == NULL)
+ err(EX_SOFTWARE, "%s:data malloc", __func__);
+ get_dev(fd, data);
+#define BUGET_REG 0xf0000218
+ for (i = 0; i < data->info_len; i++) {
+ devinfo = &data->dev[i];
+ if (!devinfo->status)
+ continue;
+ reg = read_write_quad(fd, devinfo->eui, BUGET_REG, 1, 0);
+ fweui2eui64(&devinfo->eui, &eui);
+ eui64_ntoa(&eui, addr, sizeof(addr));
+ printf("%d %s, %08x",
+ devinfo->dst, addr, reg);
+ if (reg > 0) {
+ old = (reg & 0x3f);
+ max = (reg & 0x3f00) >> 8;
+ if (pri_req > max)
+ pri_req = max;
+ printf(" 0x%x -> 0x%x\n", old, pri_req);
+ read_write_quad(fd, devinfo->eui, BUGET_REG, 0, pri_req);
+ } else {
+ printf("\n");
+ }
+ }
+ free((void *)data);
+}
+
+static void
+parse_bus_info_block(u_int32_t *p)
+{
+ char addr[EUI64_SIZ];
+ struct bus_info *bi;
+ struct eui64 eui;
+
+ bi = (struct bus_info *)p;
+ fweui2eui64(&bi->eui64, &eui);
+ eui64_ntoa(&eui, addr, sizeof(addr));
+ printf("bus_name: 0x%04x\n"
+ "irmc:%d cmc:%d isc:%d bmc:%d pmc:%d\n"
+ "cyc_clk_acc:%d max_rec:%d max_rom:%d\n"
+ "generation:%d link_spd:%d\n"
+ "EUI64: %s\n",
+ bi->bus_name,
+ bi->irmc, bi->cmc, bi->isc, bi->bmc, bi->pmc,
+ bi->cyc_clk_acc, bi->max_rec, bi->max_rom,
+ bi->generation, bi->link_spd,
+ addr);
+}
+
+static int
+get_crom(int fd, int node, void *crom_buf, int len)
+{
+ struct fw_crom_buf buf;
+ int i, error;
+ struct fw_devlstreq *data;
+
+ data = (struct fw_devlstreq *)malloc(sizeof(struct fw_devlstreq));
+ if (data == NULL)
+ err(EX_SOFTWARE, "%s:data malloc", __func__);
+ get_dev(fd, data);
+
+ for (i = 0; i < data->info_len; i++) {
+ if (data->dev[i].dst == node && data->dev[i].eui.lo != 0)
+ break;
+ }
+ if (i == data->info_len)
+ errx(1, "no such node %d.", node);
+ else
+ buf.eui = data->dev[i].eui;
+ free((void *)data);
+
+ buf.len = len;
+ buf.ptr = crom_buf;
+ bzero(crom_buf, len);
+ if ((error = ioctl(fd, FW_GCROM, &buf)) < 0) {
+ err(EX_IOERR, "%s: ioctl", __func__);
+ }
+
+ return error;
+}
+
+static void
+show_crom(u_int32_t *crom_buf)
+{
+ int i;
+ struct crom_context cc;
+ char *desc, info[256];
+ static const char *key_types = "ICLD";
+ struct csrreg *reg;
+ struct csrdirectory *dir;
+ struct csrhdr *hdr;
+ u_int16_t crc;
+
+ printf("first quad: 0x%08x ", *crom_buf);
+ if (crom_buf[0] == 0) {
+ printf("(Invalid Configuration ROM)\n");
+ return;
+ }
+ hdr = (struct csrhdr *)crom_buf;
+ if (hdr->info_len == 1) {
+ /* minimum ROM */
+ reg = (struct csrreg *)hdr;
+ printf("verndor ID: 0x%06x\n", reg->val);
+ return;
+ }
+ printf("info_len=%d crc_len=%d crc=0x%04x",
+ hdr->info_len, hdr->crc_len, hdr->crc);
+ crc = crom_crc(crom_buf+1, hdr->crc_len);
+ if (crc == hdr->crc)
+ printf("(OK)\n");
+ else
+ printf("(NG)\n");
+ parse_bus_info_block(crom_buf+1);
+
+ crom_init_context(&cc, crom_buf);
+ dir = cc.stack[0].dir;
+ if (!dir) {
+ printf("no root directory - giving up\n");
+ return;
+ }
+ printf("root_directory: len=0x%04x(%d) crc=0x%04x",
+ dir->crc_len, dir->crc_len, dir->crc);
+ crc = crom_crc((u_int32_t *)&dir->entry[0], dir->crc_len);
+ if (crc == dir->crc)
+ printf("(OK)\n");
+ else
+ printf("(NG)\n");
+ if (dir->crc_len < 1)
+ return;
+ while (cc.depth >= 0) {
+ desc = crom_desc(&cc, info, sizeof(info));
+ reg = crom_get(&cc);
+ for (i = 0; i < cc.depth; i++)
+ printf("\t");
+ printf("%02x(%c:%02x) %06x %s: %s\n",
+ reg->key,
+ key_types[(reg->key & CSRTYPE_MASK)>>6],
+ reg->key & CSRKEY_MASK, reg->val,
+ desc, info);
+ crom_next(&cc);
+ }
+}
+
+#define DUMP_FORMAT "%08x %08x %08x %08x %08x %08x %08x %08x\n"
+
+static void
+dump_crom(u_int32_t *p)
+{
+ int len=1024, i;
+
+ for (i = 0; i < len/(4*8); i ++) {
+ printf(DUMP_FORMAT,
+ p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
+ p += 8;
+ }
+}
+
+static void
+load_crom(char *filename, u_int32_t *p)
+{
+ FILE *file;
+ int len=1024, i;
+
+ if ((file = fopen(filename, "r")) == NULL)
+ err(1, "load_crom %s", filename);
+ for (i = 0; i < len/(4*8); i ++) {
+ fscanf(file, DUMP_FORMAT,
+ p, p+1, p+2, p+3, p+4, p+5, p+6, p+7);
+ p += 8;
+ }
+ fclose(file);
+}
+
+static void
+show_topology_map(int fd)
+{
+ struct fw_topology_map *tmap;
+ union fw_self_id sid;
+ int i;
+ static const char *port_status[] = {" ", "-", "P", "C"};
+ static const char *pwr_class[] = {" 0W", "15W", "30W", "45W",
+ "-1W", "-2W", "-5W", "-9W"};
+ static const char *speed[] = {"S100", "S200", "S400", "S800"};
+ tmap = malloc(sizeof(struct fw_topology_map));
+ if (tmap == NULL)
+ err(EX_SOFTWARE, "%s:tmap malloc", __func__);
+ if (ioctl(fd, FW_GTPMAP, tmap) < 0) {
+ err(EX_IOERR, "%s: ioctl", __func__);
+ }
+ printf("crc_len: %d generation:%d node_count:%d sid_count:%d\n",
+ tmap->crc_len, tmap->generation,
+ tmap->node_count, tmap->self_id_count);
+ printf("id link gap_cnt speed delay cIRM power port0 port1 port2"
+ " ini more\n");
+ for (i = 0; i < tmap->crc_len - 2; i++) {
+ sid = tmap->self_id[i];
+ if (sid.p0.sequel) {
+ printf("%02d sequel packet\n", sid.p0.phy_id);
+ continue;
+ }
+ printf("%02d %2d %2d %4s %d %3s"
+ " %s %s %s %d %d\n",
+ sid.p0.phy_id,
+ sid.p0.link_active,
+ sid.p0.gap_count,
+ speed[sid.p0.phy_speed],
+ sid.p0.contender,
+ pwr_class[sid.p0.power_class],
+ port_status[sid.p0.port0],
+ port_status[sid.p0.port1],
+ port_status[sid.p0.port2],
+ sid.p0.initiated_reset,
+ sid.p0.more_packets
+ );
+ }
+ free(tmap);
+}
+
+static void
+read_phy_registers(int fd, u_int8_t *buf, int offset, int len)
+{
+ struct fw_reg_req_t reg;
+ int i;
+
+ for (i = 0; i < len; i++) {
+ reg.addr = offset + i;
+ if (ioctl(fd, FWOHCI_RDPHYREG, &reg) < 0)
+ err(EX_IOERR, "%s: ioctl", __func__);
+ buf[i] = (u_int8_t) reg.data;
+ printf("0x%02x ", reg.data);
+ }
+ printf("\n");
+}
+
+static void
+read_phy_page(int fd, u_int8_t *buf, int page, int port)
+{
+ struct fw_reg_req_t reg;
+
+ reg.addr = 0x7;
+ reg.data = ((page & 7) << 5) | (port & 0xf);
+ if (ioctl(fd, FWOHCI_WRPHYREG, &reg) < 0)
+ err(EX_IOERR, "%s: ioctl", __func__);
+ read_phy_registers(fd, buf, 8, 8);
+}
+
+static void
+dump_phy_registers(int fd)
+{
+ struct phyreg_base b;
+ struct phyreg_page0 p;
+ struct phyreg_page1 v;
+ int i;
+
+ printf("=== base register ===\n");
+ read_phy_registers(fd, (u_int8_t *)&b, 0, 8);
+ printf(
+ "Physical_ID:%d R:%d CPS:%d\n"
+ "RHB:%d IBR:%d Gap_Count:%d\n"
+ "Extended:%d Num_Ports:%d\n"
+ "PHY_Speed:%d Delay:%d\n"
+ "LCtrl:%d C:%d Jitter:%d Pwr_Class:%d\n"
+ "WDIE:%d ISBR:%d CTOI:%d CPSI:%d STOI:%d PEI:%d EAA:%d EMC:%d\n"
+ "Max_Legacy_SPD:%d BLINK:%d Bridge:%d\n"
+ "Page_Select:%d Port_Select%d\n",
+ b.phy_id, b.r, b.cps,
+ b.rhb, b.ibr, b.gap_count,
+ b.extended, b.num_ports,
+ b.phy_speed, b.delay,
+ b.lctrl, b.c, b.jitter, b.pwr_class,
+ b.wdie, b.isbr, b.ctoi, b.cpsi, b.stoi, b.pei, b.eaa, b.emc,
+ b.legacy_spd, b.blink, b.bridge,
+ b.page_select, b.port_select
+ );
+
+ for (i = 0; i < b.num_ports; i ++) {
+ printf("\n=== page 0 port %d ===\n", i);
+ read_phy_page(fd, (u_int8_t *)&p, 0, i);
+ printf(
+ "Astat:%d BStat:%d Ch:%d Con:%d RXOK:%d Dis:%d\n"
+ "Negotiated_speed:%d PIE:%d Fault:%d Stanby_fault:%d Disscrm:%d B_Only:%d\n"
+ "DC_connected:%d Max_port_speed:%d LPP:%d Cable_speed:%d\n"
+ "Connection_unreliable:%d Beta_mode:%d\n"
+ "Port_error:0x%x\n"
+ "Loop_disable:%d In_standby:%d Hard_disable:%d\n",
+ p.astat, p.bstat, p.ch, p.con, p.rxok, p.dis,
+ p.negotiated_speed, p.pie, p.fault, p.stanby_fault, p.disscrm, p.b_only,
+ p.dc_connected, p.max_port_speed, p.lpp, p.cable_speed,
+ p.connection_unreliable, p.beta_mode,
+ p.port_error,
+ p.loop_disable, p.in_standby, p.hard_disable
+ );
+ }
+ printf("\n=== page 1 ===\n");
+ read_phy_page(fd, (u_int8_t *)&v, 1, 0);
+ printf(
+ "Compliance:%d\n"
+ "Vendor_ID:0x%06x\n"
+ "Product_ID:0x%06x\n",
+ v.compliance,
+ (v.vendor_id[0] << 16) | (v.vendor_id[1] << 8) | v.vendor_id[2],
+ (v.product_id[0] << 16) | (v.product_id[1] << 8) | v.product_id[2]
+ );
+}
+
+static int
+open_dev(int *fd, char *devname)
+{
+ if (*fd < 0) {
+ *fd = open(devname, O_RDWR);
+ if (*fd < 0)
+ return(-1);
+
+ }
+ return(0);
+}
+
+static void
+sysctl_set_int(const char *name, int val)
+{
+ if (sysctlbyname(name, NULL, NULL, &val, sizeof(int)) < 0)
+ err(1, "sysctl %s failed.", name);
+}
+
+static fwmethod *
+detect_recv_fn(int fd, char ich)
+{
+ char *buf;
+ struct fw_isochreq isoreq;
+ struct fw_isobufreq bufreq;
+ int len;
+ u_int32_t *ptr;
+ struct ciphdr *ciph;
+ fwmethod *retfn;
+#define RECV_NUM_PACKET 16
+#define RECV_PACKET_SZ 1024
+
+ bufreq.rx.nchunk = 8;
+ bufreq.rx.npacket = RECV_NUM_PACKET;
+ bufreq.rx.psize = RECV_PACKET_SZ;
+ bufreq.tx.nchunk = 0;
+ bufreq.tx.npacket = 0;
+ bufreq.tx.psize = 0;
+
+ if (ioctl(fd, FW_SSTBUF, &bufreq) < 0)
+ err(EX_IOERR, "%s: ioctl FW_SSTBUF", __func__);
+
+ isoreq.ch = ich & 0x3f;
+ isoreq.tag = (ich >> 6) & 3;
+
+ if (ioctl(fd, FW_SRSTREAM, &isoreq) < 0)
+ err(EX_IOERR, "%s: ioctl FW_SRSTREAM", __func__);
+
+ buf = (char *)malloc(RECV_NUM_PACKET * RECV_PACKET_SZ);
+ if (buf == NULL)
+ err(EX_SOFTWARE, "%s:buf malloc", __func__);
+ /*
+ * fwdev.c seems to return EIO on error and
+ * the return value of the last uiomove
+ * on success. For now, checking that the
+ * return is not less than zero should be
+ * sufficient. fwdev.c::fw_read() should
+ * return the total length read, not the value
+ * of the last uiomove().
+ */
+ len = read(fd, buf, RECV_NUM_PACKET * RECV_PACKET_SZ);
+ if (len < 0)
+ err(EX_IOERR, "%s: error reading from device", __func__);
+ ptr = (u_int32_t *) buf;
+ ciph = (struct ciphdr *)(ptr + 1);
+
+ switch(ciph->fmt) {
+ case CIP_FMT_DVCR:
+ fprintf(stderr, "Detected DV format on input.\n");
+ retfn = dvrecv;
+ break;
+ case CIP_FMT_MPEG:
+ fprintf(stderr, "Detected MPEG TS format on input.\n");
+ retfn = mpegtsrecv;
+ break;
+ default:
+ errx(1, "Unsupported format for receiving: fmt=0x%x", ciph->fmt);
+ }
+ free(buf);
+ return retfn;
+}
+
+int
+main(int argc, char **argv)
+{
+#define MAX_BOARDS 10
+ u_int32_t crom_buf[1024/4];
+ u_int32_t crom_buf_hex[1024/4];
+ char devbase[64];
+ const char *device_string = "/dev/fw";
+ int fd = -1, ch, len=1024;
+ int32_t current_board = 0;
+ /*
+ * If !command_set, then -u will display the nodes for the board.
+ * This emulates the previous behavior when -u is passed by itself
+ */
+ bool command_set = false;
+ bool open_needed = false;
+ long tmp;
+ struct fw_eui64 eui;
+ struct eui64 target;
+ fwmethod *recvfn = NULL;
+/*
+ * Holders for which functions
+ * to iterate through
+ */
+ bool display_board_only = false;
+ bool display_crom = false;
+ bool send_bus_reset = false;
+ bool display_crom_hex = false;
+ bool load_crom_from_file = false;
+ bool set_fwmem_target = false;
+ bool dump_topology = false;
+ bool dump_phy_reg = false;
+
+ int32_t priority_budget = -1;
+ int32_t set_root_node = -1;
+ int32_t set_gap_count = -1;
+ int32_t send_link_on = -1;
+ int32_t send_reset_start = -1;
+
+ char *crom_string = NULL;
+ char *crom_string_hex = NULL;
+ char *recv_data = NULL;
+ char *send_data = NULL;
+
+ if (argc < 2) {
+ for (current_board = 0; current_board < MAX_BOARDS; current_board++) {
+ snprintf(devbase, sizeof(devbase), "%s%d.0", device_string, current_board);
+ if (open_dev(&fd, devbase) < 0) {
+ if (current_board == 0) {
+ usage();
+ err(EX_IOERR, "%s: Error opening firewire controller #%d %s",
+ __func__, current_board, devbase);
+ }
+ return(EIO);
+ }
+ list_dev(fd);
+ close(fd);
+ fd = -1;
+ }
+ }
+ /*
+ * Parse all command line options, then execute requested operations.
+ */
+ while ((ch = getopt(argc, argv, "M:f:g:m:o:s:b:prtc:d:l:u:R:S:")) != -1) {
+ switch(ch) {
+ case 'b':
+ priority_budget = strtol(optarg, NULL, 0);
+ if (priority_budget < 0 || priority_budget > INT32_MAX)
+ errx(EX_USAGE, "%s: priority_budget out of range: %s", __func__, optarg);
+ command_set = true;
+ open_needed = true;
+ display_board_only = false;
+ break;
+ case 'c':
+ crom_string = malloc(strlen(optarg)+1);
+ if (crom_string == NULL)
+ err(EX_SOFTWARE, "%s:crom_string malloc", __func__);
+ if ( (strtol(crom_string, NULL, 0) < 0) || strtol(crom_string, NULL, 0) > MAX_BOARDS)
+ errx(EX_USAGE, "%s:Invalid value for node", __func__);
+ strcpy(crom_string, optarg);
+ display_crom = 1;
+ open_needed = true;
+ command_set = true;
+ display_board_only = false;
+ break;
+ case 'd':
+ crom_string_hex = malloc(strlen(optarg)+1);
+ if (crom_string_hex == NULL)
+ err(EX_SOFTWARE, "%s:crom_string_hex malloc", __func__);
+ strcpy(crom_string_hex, optarg);
+ display_crom_hex = 1;
+ open_needed = true;
+ command_set = true;
+ display_board_only = false;
+ break;
+ case 'f':
+#define MAX_PHY_CONFIG 0x3f
+ set_root_node = strtol(optarg, NULL, 0);
+ if ( (set_root_node < 0) || (set_root_node > MAX_PHY_CONFIG) )
+ errx(EX_USAGE, "%s:set_root_node out of range", __func__);
+ open_needed = true;
+ command_set = true;
+ display_board_only = false;
+ break;
+ case 'g':
+ set_gap_count = strtol(optarg, NULL, 0);
+ if ( (set_gap_count < 0) || (set_gap_count > MAX_PHY_CONFIG) )
+ errx(EX_USAGE, "%s:set_gap_count out of range", __func__);
+ open_needed = true;
+ command_set = true;
+ display_board_only = false;
+ break;
+ case 'l':
+ load_crom_from_file = 1;
+ load_crom(optarg, crom_buf);
+ command_set = true;
+ display_board_only = false;
+ break;
+ case 'm':
+ set_fwmem_target = 1;
+ open_needed = 0;
+ command_set = true;
+ display_board_only = false;
+ if (eui64_hostton(optarg, &target) != 0 &&
+ eui64_aton(optarg, &target) != 0)
+ errx(EX_USAGE, "%s: invalid target: %s", __func__, optarg);
+ break;
+ case 'o':
+ send_link_on = str2node(fd, optarg);
+ if ( (send_link_on < 0) || (send_link_on > MAX_PHY_CONFIG) )
+ errx(EX_USAGE, "%s: node out of range: %s\n",__func__, optarg);
+ open_needed = true;
+ command_set = true;
+ display_board_only = false;
+ break;
+ case 'p':
+ dump_phy_reg = 1;
+ open_needed = true;
+ command_set = true;
+ display_board_only = false;
+ break;
+ case 'r':
+ send_bus_reset = 1;
+ open_needed = true;
+ command_set = true;
+ display_board_only = false;
+ break;
+ case 's':
+ send_reset_start = str2node(fd, optarg);
+ if ( (send_reset_start < 0) || (send_reset_start > MAX_PHY_CONFIG) )
+ errx(EX_USAGE, "%s: node out of range: %s\n", __func__, optarg);
+ open_needed = true;
+ command_set = true;
+ display_board_only = false;
+ break;
+ case 't':
+ dump_topology = 1;
+ open_needed = true;
+ command_set = true;
+ display_board_only = false;
+ break;
+ case 'u':
+ if(!command_set)
+ display_board_only = true;
+ current_board = strtol(optarg, NULL, 0);
+ open_needed = true;
+ break;
+ case 'M':
+ switch (optarg[0]) {
+ case 'm':
+ recvfn = mpegtsrecv;
+ break;
+ case 'd':
+ recvfn = dvrecv;
+ break;
+ default:
+ errx(EX_USAGE, "unrecognized method: %s",
+ optarg);
+ }
+ command_set = true;
+ display_board_only = false;
+ break;
+ case 'R':
+ recv_data = malloc(strlen(optarg)+1);
+ if (recv_data == NULL)
+ err(EX_SOFTWARE, "%s:recv_data malloc", __func__);
+ strcpy(recv_data, optarg);
+ open_needed = false;
+ command_set = true;
+ display_board_only = false;
+ break;
+ case 'S':
+ send_data = malloc(strlen(optarg)+1);
+ if (send_data == NULL)
+ err(EX_SOFTWARE, "%s:send_data malloc", __func__);
+ strcpy(send_data, optarg);
+ open_needed = true;
+ command_set = true;
+ display_board_only = false;
+ break;
+ case '?':
+ default:
+ usage();
+ warnc(EINVAL, "%s: Unknown command line arguments", __func__);
+ return 0;
+ }
+ } /* end while */
+
+ /*
+ * Catch the error case when the user
+ * executes the command with non ''-''
+ * delimited arguments.
+ * Generate the usage() display and exit.
+ */
+ if (!command_set && !display_board_only) {
+ usage();
+ warnc(EINVAL, "%s: Unknown command line arguments", __func__);
+ return 0;
+ }
+
+ /*
+ * If -u <bus_number> is passed, execute
+ * command for that card only.
+ *
+ * If -u <bus_number> is not passed, execute
+ * command for card 0 only.
+ *
+ */
+ if(open_needed){
+ snprintf(devbase, sizeof(devbase), "%s%d.0", device_string, current_board);
+ if (open_dev(&fd, devbase) < 0) {
+ err(EX_IOERR, "%s: Error opening firewire controller #%d %s", __func__, current_board, devbase);
+ }
+ }
+ /*
+ * display the nodes on this board "-u"
+ * only
+ */
+ if (display_board_only)
+ list_dev(fd);
+
+ /*
+ * dump_phy_reg "-p"
+ */
+ if (dump_phy_reg)
+ dump_phy_registers(fd);
+
+ /*
+ * send a BUS_RESET Event "-r"
+ */
+ if (send_bus_reset) {
+ if(ioctl(fd, FW_IBUSRST, &tmp) < 0)
+ err(EX_IOERR, "%s: Ioctl of bus reset failed for %s", __func__, devbase);
+ }
+ /*
+ * Print out the CROM for this node "-c"
+ */
+ if (display_crom) {
+ tmp = str2node(fd, crom_string);
+ get_crom(fd, tmp, crom_buf, len);
+ show_crom(crom_buf);
+ free(crom_string);
+ }
+ /*
+ * Hex Dump the CROM for this node "-d"
+ */
+ if (display_crom_hex) {
+ tmp = str2node(fd, crom_string_hex);
+ get_crom(fd, tmp, crom_buf_hex, len);
+ dump_crom(crom_buf_hex);
+ free(crom_string_hex);
+ }
+ /*
+ * Set Priority Budget to value for this node "-b"
+ */
+ if (priority_budget >= 0)
+ set_pri_req(fd, priority_budget);
+
+ /*
+ * Explicitly set the root node of this bus to value "-f"
+ */
+ if (set_root_node >= 0)
+ send_phy_config(fd, set_root_node, -1);
+
+ /*
+ * Set the gap count for this card/bus "-g"
+ */
+ if (set_gap_count >= 0)
+ send_phy_config(fd, -1, set_gap_count);
+
+ /*
+ * Load a CROM from a file "-l"
+ */
+ if (load_crom_from_file)
+ show_crom(crom_buf);
+ /*
+ * Set the fwmem target for a node to argument "-m"
+ */
+ if (set_fwmem_target) {
+ eui.hi = ntohl(*(u_int32_t*)&(target.octet[0]));
+ eui.lo = ntohl(*(u_int32_t*)&(target.octet[4]));
+#if defined(__FreeBSD__)
+ sysctl_set_int("hw.firewire.fwmem.eui64_hi", eui.hi);
+ sysctl_set_int("hw.firewire.fwmem.eui64_lo", eui.lo);
+#elif defined(__NetBSD__)
+ sysctl_set_int("hw.fwmem.eui64_hi", eui.hi);
+ sysctl_set_int("hw.fwmem.eui64_lo", eui.lo);
+#else
+#warning "You need to add support for your OS"
+#endif
+
+ }
+
+ /*
+ * Send a link on to this board/bus "-o"
+ */
+ if (send_link_on >= 0)
+ link_on(fd, send_link_on);
+
+ /*
+ * Send a reset start to this board/bus "-s"
+ */
+ if (send_reset_start >= 0)
+ reset_start(fd, send_reset_start);
+
+ /*
+ * Dump the node topology for this board/bus "-t"
+ */
+ if (dump_topology)
+ show_topology_map(fd);
+
+ /*
+ * Receive data file from node "-R"
+ */
+#define TAG (1<<6)
+#define CHANNEL 63
+ if (recv_data != NULL){
+ if (recvfn == NULL) { /* guess... */
+ recvfn = detect_recv_fn(fd, TAG | CHANNEL);
+ close(fd);
+ fd = -1;
+ }
+ snprintf(devbase, sizeof(devbase), "%s%d.0", device_string, current_board);
+ if (open_dev(&fd, devbase) < 0)
+ err(EX_IOERR, "%s: Error opening firewire controller #%d %s in recv_data\n", __func__, current_board, devbase);
+ (*recvfn)(fd, recv_data, TAG | CHANNEL, -1);
+ free(recv_data);
+ }
+
+ /*
+ * Send data file to node "-S"
+ */
+ if (send_data != NULL){
+ dvsend(fd, send_data, TAG | CHANNEL, -1);
+ free(send_data);
+ }
+
+ if (fd > 0) {
+ close(fd);
+ fd = -1;
+ }
+ return 0;
+}
diff --git a/usr.sbin/fwcontrol/fwdv.c b/usr.sbin/fwcontrol/fwdv.c
new file mode 100644
index 0000000..afb3ada
--- /dev/null
+++ b/usr.sbin/fwcontrol/fwdv.c
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2003
+ * Hidetoshi Shimokawa. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 Hidetoshi Shimokawa.
+ *
+ * 4. Neither the name of the author 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/param.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+
+#if __FreeBSD_version >= 500000
+#include <arpa/inet.h>
+#endif
+
+#include <err.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+#include <dev/firewire/firewire.h>
+#include <dev/firewire/iec68113.h>
+
+#include "fwmethods.h"
+
+#define DEBUG 0
+#define FIX_FRAME 1
+
+struct frac {
+ int n,d;
+};
+
+struct frac frame_cycle[2] = {
+ {8000*100, 2997}, /* NTSC 8000 cycle / 29.97 Hz */
+ {320, 1}, /* PAL 8000 cycle / 25 Hz */
+};
+int npackets[] = {
+ 250 /* NTSC */,
+ 300 /* PAL */
+};
+struct frac pad_rate[2] = {
+ {203, 2997}, /* = (8000 - 29.97 * 250)/(29.97 * 250) */
+ {1, 15}, /* = (8000 - 25 * 300)/(25 * 300) */
+};
+char *system_name[] = {"NTSC", "PAL"};
+int frame_rate[] = {30, 25};
+
+#define PSIZE 512
+#define DSIZE 480
+#define NCHUNK 64
+
+#define NPACKET_R 256
+#define NPACKET_T 255
+#define TNBUF 100 /* XXX too large value causes block noise */
+#define NEMPTY 10 /* depends on TNBUF */
+#define RBUFSIZE (PSIZE * NPACKET_R)
+#define MAXBLOCKS (300)
+#define CYCLE_FRAC 0xc00
+
+void
+dvrecv(int d, const char *filename, char ich, int count)
+{
+ struct fw_isochreq isoreq;
+ struct fw_isobufreq bufreq;
+ struct dvdbc *dv;
+ struct ciphdr *ciph;
+ struct fw_pkt *pkt;
+ char *pad, *buf;
+ u_int32_t *ptr;
+ int len, tlen, npad, fd, k, m, vec, system = -1, nb;
+ int nblocks[] = {250 /* NTSC */, 300 /* PAL */};
+ struct iovec wbuf[NPACKET_R];
+
+ if(strcmp(filename, "-") == 0) {
+ fd = STDOUT_FILENO;
+ } else {
+ fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0660);
+ if (fd == -1)
+ err(EX_NOINPUT, "%s", filename);
+ }
+ buf = malloc(RBUFSIZE);
+ pad = malloc(DSIZE*MAXBLOCKS);
+ memset(pad, 0xff, DSIZE*MAXBLOCKS);
+ bzero(wbuf, sizeof(wbuf));
+
+ bufreq.rx.nchunk = NCHUNK;
+ bufreq.rx.npacket = NPACKET_R;
+ bufreq.rx.psize = PSIZE;
+ bufreq.tx.nchunk = 0;
+ bufreq.tx.npacket = 0;
+ bufreq.tx.psize = 0;
+ if (ioctl(d, FW_SSTBUF, &bufreq) < 0)
+ err(1, "ioctl FW_SSTBUF");
+
+ isoreq.ch = ich & 0x3f;
+ isoreq.tag = (ich >> 6) & 3;
+
+ if (ioctl(d, FW_SRSTREAM, &isoreq) < 0)
+ err(1, "ioctl");
+
+ k = m = 0;
+ while (count <= 0 || k <= count) {
+#if 0
+ tlen = 0;
+ while ((len = read(d, buf + tlen, PSIZE
+ /* RBUFSIZE - tlen */)) > 0) {
+ if (len < 0) {
+ if (errno == EAGAIN) {
+ fprintf(stderr, "(EAGAIN)\n");
+ fflush(stderr);
+ if (len <= 0)
+ continue;
+ } else
+ err(1, "read failed");
+ }
+ tlen += len;
+ if ((RBUFSIZE - tlen) < PSIZE)
+ break;
+ };
+#else
+ tlen = len = read(d, buf, RBUFSIZE);
+ if (len < 0) {
+ if (errno == EAGAIN) {
+ fprintf(stderr, "(EAGAIN) - push 'Play'?\n");
+ fflush(stderr);
+ if (len <= 0)
+ continue;
+ } else
+ err(1, "read failed");
+ }
+#endif
+ vec = 0;
+ ptr = (u_int32_t *) buf;
+again:
+ pkt = (struct fw_pkt *) ptr;
+#if DEBUG
+ fprintf(stderr, "%08x %08x %08x %08x\n",
+ htonl(ptr[0]), htonl(ptr[1]),
+ htonl(ptr[2]), htonl(ptr[3]));
+#endif
+ ciph = (struct ciphdr *)(ptr + 1); /* skip iso header */
+ if (ciph->fmt != CIP_FMT_DVCR)
+ errx(1, "unknown format 0x%x", ciph->fmt);
+ ptr = (u_int32_t *) (ciph + 1); /* skip cip header */
+#if DEBUG
+ if (ciph->fdf.dv.cyc != 0xffff && k == 0) {
+ fprintf(stderr, "0x%04x\n", ntohs(ciph->fdf.dv.cyc));
+ }
+#endif
+ if (pkt->mode.stream.len <= sizeof(struct ciphdr))
+ /* no payload */
+ goto next;
+ for (dv = (struct dvdbc *)ptr;
+ (char *)dv < (char *)(ptr + ciph->len);
+ dv+=6) {
+
+#if DEBUG
+ fprintf(stderr, "(%d,%d) ", dv->sct, dv->dseq);
+#endif
+ if (dv->sct == DV_SCT_HEADER && dv->dseq == 0) {
+ if (system < 0) {
+ system = ciph->fdf.dv.fs;
+ fprintf(stderr, "%s\n", system_name[system]);
+ }
+
+ /* Fix DSF bit */
+ if (system == 1 &&
+ (dv->payload[0] & DV_DSF_12) == 0)
+ dv->payload[0] |= DV_DSF_12;
+ nb = nblocks[system];
+ fprintf(stderr, "%d:%02d:%02d %d\r",
+ k / (3600 * frame_rate[system]),
+ (k / (60 * frame_rate[system])) % 60,
+ (k / frame_rate[system]) % 60,
+ k % frame_rate[system]);
+
+#if FIX_FRAME
+ if (m > 0 && m != nb) {
+ /* padding bad frame */
+ npad = ((nb - m) % nb);
+ if (npad < 0)
+ npad += nb;
+ fprintf(stderr, "\n%d blocks padded\n",
+ npad);
+ npad *= DSIZE;
+ wbuf[vec].iov_base = pad;
+ wbuf[vec++].iov_len = npad;
+ if (vec >= NPACKET_R) {
+ writev(fd, wbuf, vec);
+ vec = 0;
+ }
+ }
+#endif
+ k++;
+ fflush(stderr);
+ m = 0;
+ }
+ if (k == 0 || (count > 0 && k > count))
+ continue;
+ m++;
+ wbuf[vec].iov_base = (char *) dv;
+ wbuf[vec++].iov_len = DSIZE;
+ if (vec >= NPACKET_R) {
+ writev(fd, wbuf, vec);
+ vec = 0;
+ }
+ }
+ ptr = (u_int32_t *)dv;
+next:
+ if ((char *)ptr < buf + tlen)
+ goto again;
+ if (vec > 0)
+ writev(fd, wbuf, vec);
+ }
+ if (fd != STDOUT_FILENO)
+ close(fd);
+ fprintf(stderr, "\n");
+}
+
+
+void
+dvsend(int d, const char *filename, char ich, int count)
+{
+ struct fw_isochreq isoreq;
+ struct fw_isobufreq bufreq;
+ struct dvdbc *dv;
+ struct fw_pkt *pkt;
+ int len, tlen, header, fd, frames, packets, vec, offset, nhdr, i;
+ int system=-1, pad_acc, cycle_acc, cycle, f_cycle, f_frac;
+ struct iovec wbuf[TNBUF*2 + NEMPTY];
+ char *pbuf;
+ u_int32_t iso_data, iso_empty, hdr[TNBUF + NEMPTY][3];
+ struct ciphdr *ciph;
+ struct timeval start, end;
+ double rtime;
+
+ fd = open(filename, O_RDONLY);
+ if (fd == -1)
+ err(EX_NOINPUT, "%s", filename);
+
+ pbuf = malloc(DSIZE * TNBUF);
+ bzero(wbuf, sizeof(wbuf));
+
+ bufreq.rx.nchunk = 0;
+ bufreq.rx.npacket = 0;
+ bufreq.rx.psize = 0;
+ bufreq.tx.nchunk = NCHUNK;
+ bufreq.tx.npacket = NPACKET_T;
+ bufreq.tx.psize = PSIZE;
+ if (ioctl(d, FW_SSTBUF, &bufreq) < 0)
+ err(1, "ioctl FW_SSTBUF");
+
+ isoreq.ch = ich & 0x3f;
+ isoreq.tag = (ich >> 6) & 3;
+
+ if (ioctl(d, FW_STSTREAM, &isoreq) < 0)
+ err(1, "ioctl FW_STSTREAM");
+
+ iso_data = 0;
+ pkt = (struct fw_pkt *) &iso_data;
+ pkt->mode.stream.len = DSIZE + sizeof(struct ciphdr);
+ pkt->mode.stream.sy = 0;
+ pkt->mode.stream.tcode = FWTCODE_STREAM;
+ pkt->mode.stream.chtag = ich;
+ iso_empty = iso_data;
+ pkt = (struct fw_pkt *) &iso_empty;
+ pkt->mode.stream.len = sizeof(struct ciphdr);
+
+ bzero(hdr[0], sizeof(hdr[0]));
+ hdr[0][0] = iso_data;
+ ciph = (struct ciphdr *)&hdr[0][1];
+ ciph->src = 0; /* XXX */
+ ciph->len = 120;
+ ciph->dbc = 0;
+ ciph->eoh1 = 1;
+ ciph->fdf.dv.cyc = 0xffff;
+
+ for (i = 1; i < TNBUF; i++)
+ bcopy(hdr[0], hdr[i], sizeof(hdr[0]));
+
+ gettimeofday(&start, NULL);
+#if DEBUG
+ fprintf(stderr, "%08x %08x %08x\n",
+ htonl(hdr[0]), htonl(hdr[1]), htonl(hdr[2]));
+#endif
+ frames = 0;
+ packets = 0;
+ pad_acc = 0;
+ while (1) {
+ tlen = 0;
+ while (tlen < DSIZE * TNBUF) {
+ len = read(fd, pbuf + tlen, DSIZE * TNBUF - tlen);
+ if (len <= 0) {
+ if (tlen > 0)
+ break;
+ if (len < 0)
+ warn("read");
+ else
+ fprintf(stderr, "\nend of file\n");
+ goto send_end;
+ }
+ tlen += len;
+ }
+ vec = 0;
+ offset = 0;
+ nhdr = 0;
+next:
+ dv = (struct dvdbc *)(pbuf + offset * DSIZE);
+#if 0
+ header = (dv->sct == 0 && dv->dseq == 0);
+#else
+ header = (packets == 0 || packets % npackets[system] == 0);
+#endif
+
+ ciph = (struct ciphdr *)&hdr[nhdr][1];
+ if (header) {
+ if (system < 0) {
+ system = ((dv->payload[0] & DV_DSF_12) != 0);
+ printf("%s\n", system_name[system]);
+ cycle = 1;
+ cycle_acc = frame_cycle[system].d * cycle;
+ }
+ fprintf(stderr, "%d", frames % 10);
+ frames ++;
+ if (count > 0 && frames > count)
+ break;
+ if (frames % frame_rate[system] == 0)
+ fprintf(stderr, "\n");
+ fflush(stderr);
+ f_cycle = (cycle_acc / frame_cycle[system].d) & 0xf;
+ f_frac = (cycle_acc % frame_cycle[system].d
+ * CYCLE_FRAC) / frame_cycle[system].d;
+#if 0
+ ciph->fdf.dv.cyc = htons(f_cycle << 12 | f_frac);
+#else
+ ciph->fdf.dv.cyc = htons(cycle << 12 | f_frac);
+#endif
+ cycle_acc += frame_cycle[system].n;
+ cycle_acc %= frame_cycle[system].d * 0x10;
+
+ } else {
+ ciph->fdf.dv.cyc = 0xffff;
+ }
+ ciph->dbc = packets++ % 256;
+ pad_acc += pad_rate[system].n;
+ if (pad_acc >= pad_rate[system].d) {
+ pad_acc -= pad_rate[system].d;
+ bcopy(hdr[nhdr], hdr[nhdr+1], sizeof(hdr[0]));
+ hdr[nhdr][0] = iso_empty;
+ wbuf[vec].iov_base = (char *)hdr[nhdr];
+ wbuf[vec++].iov_len = sizeof(hdr[0]);
+ nhdr ++;
+ cycle ++;
+ }
+ hdr[nhdr][0] = iso_data;
+ wbuf[vec].iov_base = (char *)hdr[nhdr];
+ wbuf[vec++].iov_len = sizeof(hdr[0]);
+ wbuf[vec].iov_base = (char *)dv;
+ wbuf[vec++].iov_len = DSIZE;
+ nhdr ++;
+ cycle ++;
+ offset ++;
+ if (offset * DSIZE < tlen)
+ goto next;
+
+again:
+ len = writev(d, wbuf, vec);
+ if (len < 0) {
+ if (errno == EAGAIN) {
+ fprintf(stderr, "(EAGAIN) - push 'Play'?\n");
+ goto again;
+ }
+ err(1, "write failed");
+ }
+ }
+ close(fd);
+ fprintf(stderr, "\n");
+send_end:
+ gettimeofday(&end, NULL);
+ rtime = end.tv_sec - start.tv_sec
+ + (end.tv_usec - start.tv_usec) * 1e-6;
+ fprintf(stderr, "%d frames, %.2f secs, %.2f frames/sec\n",
+ frames, rtime, frames/rtime);
+}
diff --git a/usr.sbin/fwcontrol/fwmethods.h b/usr.sbin/fwcontrol/fwmethods.h
new file mode 100644
index 0000000..9ac82e6
--- /dev/null
+++ b/usr.sbin/fwcontrol/fwmethods.h
@@ -0,0 +1,10 @@
+/*-
+ * This file is in the public domain.
+ *
+ * $FreeBSD$
+ */
+
+typedef void (fwmethod)(int dev_fd, const char *filename, char ich, int count);
+extern fwmethod dvrecv;
+extern fwmethod dvsend;
+extern fwmethod mpegtsrecv;
diff --git a/usr.sbin/fwcontrol/fwmpegts.c b/usr.sbin/fwcontrol/fwmpegts.c
new file mode 100644
index 0000000..ae9a52b
--- /dev/null
+++ b/usr.sbin/fwcontrol/fwmpegts.c
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2005
+ * Petr Holub, Hidetoshi Shimokawa. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 Hidetoshi Shimokawa.
+ *
+ * 4. Neither the name of the author 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/param.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+
+#if __FreeBSD_version >= 500000
+#include <arpa/inet.h>
+#endif
+
+#include <err.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+#if defined(__FreeBSD__)
+#include <dev/firewire/firewire.h>
+#include <dev/firewire/iec68113.h>
+#elif defined(__NetBSD__)
+#include <dev/ieee1394/firewire.h>
+#include <dev/ieee1394/iec68113.h>
+#else
+#warning "You need to add support for your OS"
+#endif
+
+
+#include "fwmethods.h"
+
+#define DEBUG 0
+
+/*****************************************************************************
+
+MPEG-2 Transport Stream (MPEG TS) packet format according to IEC 61883:
+
+31 15 0
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ --------
+| len |tag| channel | tcode | sy |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 1394
+| header_CRC |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ --------
+|0|0| sid | dbs |fn | qpc |S|RSV| dbc |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ CIP
+|1|0| fmt | fdf | fdf/syt |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ --------
+| reserved | cycle_count | cycle_offset |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+| | N x
+. . MPEG
+. MPEG TS payload 188 bytes .
+. .
+| |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ --------
+| data_CRC |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+N.b. that CRCs are removed by firewire layer!
+
+The following fields are fixed for IEEE-1394:
+tag = 01b
+tcode = 1010b
+The length is payload length, i.e. includes CIP header and data size.
+
+The following fields are constant for MPEG TS:
+sph = 1 (denoted as S in CIP header above)
+dbs = 6
+fmt = (1<<5)
+fdf = reserved
+In the supported streams we also require
+qpc = 0
+fn = 3
+and thus the payload is divided in 8 blocks as follows:
+
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+ | db0 | db1 | db2 | db3 | db4 | db5 | db6 | db7 |
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+
+We have several cases of payload distribution based on stream
+bandwidth (R):
+1) R < 1.5 Mbps: any of db0..db7 may be payload,
+2) 1.5 < R < 3 Mbps: db0/db1 or db2/db3 or db4/db5 or db6/db7 is payload,
+3) 3 < R < 6 Mbps: db0/db1/db2/db3 or db4/db5/db6/db7 is payload,
+4) R > 6 Mbps: all db0..db7 contain the payload.
+Currently, only case (4) is supported in fwmpegts.c
+
+Each packet may contain N MPEG TS data blocks with timestamp header,
+which are (4+188)B long. Experimentally, the N ranges from 0 through 3.
+
+*****************************************************************************/
+
+
+typedef uint8_t mpeg_ts_pld[188];
+
+struct mpeg_pldt {
+#if BYTE_ORDER == BIG_ENDIAN
+ uint32_t :7,
+ c_count:13,
+ c_offset:12;
+#else /* BYTE_ORDER != BIG_ENDIAN */
+ uint32_t c_offset:12,
+ c_count:13,
+ :7;
+#endif /* BYTE_ORDER == BIG_ENDIAN */
+ mpeg_ts_pld payload;
+};
+
+
+#define NCHUNK 8
+#define PSIZE 596
+#define NPACKET_R 4096
+#define RBUFSIZE (PSIZE * NPACKET_R)
+
+void
+mpegtsrecv(int d, const char *filename, char ich, int count)
+{
+ struct ciphdr *ciph;
+ struct fw_isochreq isoreq;
+ struct fw_isobufreq bufreq;
+ struct fw_pkt *pkt;
+ struct mpeg_pldt *pld;
+ uint32_t *ptr;
+ int fd, k, len, m, pkt_size, startwr, tlen;
+ char *buf;
+
+ startwr = 0;
+
+ if (strcmp(filename, "-") == 0)
+ fd = STDOUT_FILENO;
+ else {
+ fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0660);
+ if (fd == -1)
+ err(EX_NOINPUT, "%s", filename);
+ }
+ buf = malloc(RBUFSIZE);
+
+ bufreq.rx.nchunk = NCHUNK;
+ bufreq.rx.npacket = NPACKET_R;
+ bufreq.rx.psize = PSIZE;
+ bufreq.tx.nchunk = 0;
+ bufreq.tx.npacket = 0;
+ bufreq.tx.psize = 0;
+ if (ioctl(d, FW_SSTBUF, &bufreq) < 0)
+ err(1, "ioctl");
+
+ isoreq.ch = ich & 0x3f;
+ isoreq.tag = (ich >> 6) & 3;
+
+ if (ioctl(d, FW_SRSTREAM, &isoreq) < 0)
+ err(1, "ioctl");
+
+ k = m = 0;
+ while (count <= 0 || k <= count) {
+ len = tlen = read(d, buf, RBUFSIZE);
+#if DEBUG
+ fprintf(stderr, "Read %d bytes.\n", len);
+#endif /* DEBUG */
+ if (len < 0) {
+ if (errno == EAGAIN) {
+ fprintf(stderr, "(EAGAIN) - push 'Play'?\n");
+ continue;
+ }
+ err(1, "read failed");
+ }
+ ptr = (uint32_t *) buf;
+
+ do {
+ pkt = (struct fw_pkt *) ptr;
+#if DEBUG
+ fprintf(stderr, "\nReading new packet.\n");
+ fprintf(stderr, "%08x %08x %08x %08x\n",
+ htonl(ptr[0]), htonl(ptr[1]),
+ htonl(ptr[2]), htonl(ptr[3]));
+#endif /* DEBUG */
+ /* there is no CRC in the 1394 header */
+ ciph = (struct ciphdr *)(ptr + 1); /* skip iso header */
+ if (ciph->fmt != CIP_FMT_MPEG)
+ errx(1, "unknown format 0x%x", ciph->fmt);
+ if (ciph->fn != 3) {
+ errx(1,
+ "unsupported MPEG TS stream, fn=%d (only fn=3 is supported)",
+ ciph->fn);
+ }
+ ptr = (uint32_t *) (ciph + 1); /* skip cip header */
+
+ if (pkt->mode.stream.len <= sizeof(struct ciphdr)) {
+ /* no payload */
+ /* tlen needs to be decremented before end of the loop */
+ goto next;
+ }
+#if DEBUG
+ else {
+ fprintf(stderr,
+ "Packet net payload length (IEEE1394 header): %d\n",
+ pkt->mode.stream.len - sizeof(struct ciphdr));
+ fprintf(stderr, "Data block size (CIP header): %d [q], %d [B]\n",
+ ciph->len, ciph->len * 4);
+ fprintf(stderr,
+ "Data fraction number (CIP header): %d => DBC increments with %d\n",
+ ciph->fn, (1<<ciph->fn) );
+ fprintf(stderr, "QCP (CIP header): %d\n", ciph->qpc );
+ fprintf(stderr, "DBC counter (CIP header): %d\n", ciph->dbc );
+ fprintf(stderr, "MPEG payload type size: %d\n",
+ sizeof(struct mpeg_pldt));
+ }
+#endif /* DEBUG */
+
+ /* This is a condition that needs to be satisfied to start
+ writing the data */
+ if (ciph->dbc % (1<<ciph->fn) == 0)
+ startwr = 1;
+ /* Read out all the MPEG TS data blocks from current packet */
+ for (pld = (struct mpeg_pldt *)ptr;
+ (intptr_t)pld < (intptr_t)((char *)ptr +
+ pkt->mode.stream.len - sizeof(struct ciphdr));
+ pld++) {
+ if (startwr == 1)
+ write(fd, pld->payload,
+ sizeof(pld->payload));
+ }
+
+next:
+ /* CRCs are removed from both header and trailer
+ so that only 4 bytes of 1394 header remains */
+ pkt_size = pkt->mode.stream.len + 4;
+ ptr = (uint32_t *)((intptr_t)pkt + pkt_size);
+ tlen -= pkt_size;
+ } while (tlen > 0);
+#if DEBUG
+ fprintf(stderr, "\nReading a data from firewire.\n");
+#endif /* DEBUG */
+
+ }
+ if (fd != STDOUT_FILENO)
+ close(fd);
+ fprintf(stderr, "\n");
+}
diff --git a/usr.sbin/getfmac/Makefile b/usr.sbin/getfmac/Makefile
new file mode 100644
index 0000000..f9fa1b2
--- /dev/null
+++ b/usr.sbin/getfmac/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= getfmac
+MAN= getfmac.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/getfmac/Makefile.depend b/usr.sbin/getfmac/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/getfmac/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/getfmac/getfmac.8 b/usr.sbin/getfmac/getfmac.8
new file mode 100644
index 0000000..f8ebb31
--- /dev/null
+++ b/usr.sbin/getfmac/getfmac.8
@@ -0,0 +1,57 @@
+.\" Copyright (c) 2002 Networks Associates Technology, Inc.
+.\" All rights reserved.
+.\"
+.\" This software was developed for the FreeBSD Project by Chris
+.\" Costello at Safeport Network Services 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 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 27, 2002
+.Dt GETFMAC 8
+.Os
+.Sh NAME
+.Nm getfmac
+.Nd print MAC label for a file system object
+.Sh SYNOPSIS
+.Nm
+.Op Fl h
+.Op Fl l Ar list,of,labels
+.Op Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility prints the text representation of the MAC label associated with the
+specified files.
+.Sh SEE ALSO
+.Xr mac 3 ,
+.Xr mac_get_file 3 ,
+.Xr mac 4 ,
+.Xr setfmac 8 ,
+.Xr mac 9
diff --git a/usr.sbin/getfmac/getfmac.c b/usr.sbin/getfmac/getfmac.c
new file mode 100644
index 0000000..7c03d59
--- /dev/null
+++ b/usr.sbin/getfmac/getfmac.c
@@ -0,0 +1,116 @@
+/*-
+ * Copyright (c) 2002 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 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/types.h>
+#include <sys/mac.h>
+
+#include <err.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#define MAXELEMENTS 32
+
+static void
+usage(void)
+{
+
+ fprintf(stderr,
+ "getfmac [-h] [-l list,of,labels] [file1] [file2 ...]\n");
+ exit (EX_USAGE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *labellist, *string;
+ mac_t label;
+ int ch, hflag;
+ int error, i;
+
+ labellist = NULL;
+ hflag = 0;
+ while ((ch = getopt(argc, argv, "hl:")) != -1) {
+ switch (ch) {
+ case 'h':
+ hflag = 1;
+ break;
+ case 'l':
+ if (labellist != NULL)
+ usage();
+ labellist = argv[optind - 1];
+ break;
+ default:
+ usage();
+ }
+
+ }
+
+ for (i = optind; i < argc; i++) {
+ if (labellist != NULL)
+ error = mac_prepare(&label, labellist);
+ else
+ error = mac_prepare_file_label(&label);
+
+ if (error != 0) {
+ perror("mac_prepare");
+ return (-1);
+ }
+
+ if (hflag)
+ error = mac_get_link(argv[i], label);
+ else
+ error = mac_get_file(argv[i], label);
+ if (error) {
+ perror(argv[i]);
+ mac_free(label);
+ continue;
+ }
+
+ error = mac_to_text(label, &string);
+ if (error != 0)
+ perror("mac_to_text");
+ else {
+ printf("%s: %s\n", argv[i], string);
+ free(string);
+ }
+ mac_free(label);
+ }
+
+ exit(EX_OK);
+}
diff --git a/usr.sbin/getpmac/Makefile b/usr.sbin/getpmac/Makefile
new file mode 100644
index 0000000..5974728
--- /dev/null
+++ b/usr.sbin/getpmac/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= getpmac
+MAN= getpmac.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/getpmac/Makefile.depend b/usr.sbin/getpmac/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/getpmac/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/getpmac/getpmac.8 b/usr.sbin/getpmac/getpmac.8
new file mode 100644
index 0000000..307c962
--- /dev/null
+++ b/usr.sbin/getpmac/getpmac.8
@@ -0,0 +1,59 @@
+.\" Copyright (c) 2003 Networks Associates Technology, Inc.
+.\" All rights reserved.
+.\"
+.\" This software was developed for the FreeBSD Project by Chris Costello
+.\" at Safeport Network Services and Network Associates 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 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 14, 2003
+.Dt GETPMAC 8
+.Os
+.Sh NAME
+.Nm getpmac
+.Nd print process-related MAC labels
+.Sh SYNOPSIS
+.Nm
+.Op Fl l Ar list,of,labels
+.Op Fl p Ar pid
+.Sh DESCRIPTION
+The
+.Nm
+utility prints the text representation of the MAC label associated with the
+specified process.
+If no process is specified, it prints the label associated with its own
+process.
+.Sh SEE ALSO
+.Xr mac 3 ,
+.Xr mac_get_pid 3 ,
+.Xr mac_get_proc 3 ,
+.Xr mac 4 ,
+.Xr maclabel 7 ,
+.Xr getfmac 8 ,
+.Xr setfmac 8 ,
+.Xr setpmac 8 ,
+.Xr mac 9
diff --git a/usr.sbin/getpmac/getpmac.c b/usr.sbin/getpmac/getpmac.c
new file mode 100644
index 0000000..2abe87f
--- /dev/null
+++ b/usr.sbin/getpmac/getpmac.c
@@ -0,0 +1,127 @@
+/*-
+ * Copyright (c) 2002 Networks Associates Technology, Inc.
+ * All rights reserved.
+ *
+ * This software was developed for the FreeBSD Project by 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/types.h>
+#include <sys/mac.h>
+
+#include <err.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#define MAXELEMENTS 32
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "getpmac [-l list,of,labels] [-p pid]\n");
+ exit (EX_USAGE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *labellist, *string;
+ mac_t label;
+ pid_t pid;
+ int ch, error, pid_set;
+
+ pid_set = 0;
+ pid = 0;
+ labellist = NULL;
+ while ((ch = getopt(argc, argv, "l:p:")) != -1) {
+ switch (ch) {
+ case 'l':
+ if (labellist != NULL)
+ usage();
+ labellist = argv[optind - 1];
+ break;
+ case 'p':
+ if (pid_set)
+ usage();
+ pid = atoi(argv[optind - 1]);
+ pid_set = 1;
+ break;
+ default:
+ usage();
+ }
+
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 0)
+ usage();
+
+ if (labellist != NULL)
+ error = mac_prepare(&label, labellist);
+ else
+ error = mac_prepare_process_label(&label);
+ if (error != 0) {
+ perror("mac_prepare");
+ return (-1);
+ }
+
+ if (pid_set) {
+ error = mac_get_pid(pid, label);
+ if (error)
+ perror("mac_get_pid");
+ } else {
+ error = mac_get_proc(label);
+ if (error)
+ perror("mac_get_proc");
+ }
+ if (error) {
+ mac_free(label);
+ exit (-1);
+ }
+ error = mac_to_text(label, &string);
+ if (error != 0) {
+ perror("mac_to_text");
+ exit(EX_DATAERR);
+ }
+
+ if (strlen(string) > 0)
+ printf("%s\n", string);
+
+ mac_free(label);
+ free(string);
+ exit(EX_OK);
+}
diff --git a/usr.sbin/gpioctl/Makefile b/usr.sbin/gpioctl/Makefile
new file mode 100644
index 0000000..c0a907b
--- /dev/null
+++ b/usr.sbin/gpioctl/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+PROG= gpioctl
+MAN= gpioctl.8
+
+CFLAGS+= -I${.CURDIR}/../../lib/libgpio
+
+LIBADD= gpio
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/gpioctl/Makefile.depend b/usr.sbin/gpioctl/Makefile.depend
new file mode 100644
index 0000000..712b56e
--- /dev/null
+++ b/usr.sbin/gpioctl/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libgpio \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/gpioctl/gpioctl.8 b/usr.sbin/gpioctl/gpioctl.8
new file mode 100644
index 0000000..dc2a554
--- /dev/null
+++ b/usr.sbin/gpioctl/gpioctl.8
@@ -0,0 +1,132 @@
+.\" 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd March 8, 2015
+.Dt GPIOCTL 1
+.Os
+.Sh NAME
+.Nm gpioctl
+.Nd GPIO control utility
+.Sh SYNOPSIS
+.Nm
+.Op Fl f Ar ctldev
+.Cm -l
+.Op Fl v
+.Nm
+.Op Fl f Ar ctldev
+.Cm -t
+.Ar pin
+.Nm
+.Op Fl f Ar ctldev
+.Cm -c
+.Ar pin
+.Ar flag
+.Op flag ...
+.Nm
+.Op Fl f Ar ctldev
+.Cm -n
+.Ar pin
+.Ar pin-name
+.Nm
+.Op Cm -f Ar ctldev
+.Ar pin
+.Ar [0|1]
+.Sh DESCRIPTION
+The
+.Nm
+utility could be used to manage GPIO pins from userland and list available pins.
+.Pp
+The options are as follows:
+.Bl -tag -width ".Fl f Ar ctldev"
+.It Fl c Ar pin Ar flag Op flag ...
+Configure pin by setting provided flags.
+The following flags are currently defined:
+.Bl -tag -offset indent -width ".Cm PULSE"
+.It Cm IN
+Input pin
+.It Cm OUT
+Output pin
+.It Cm OD
+Open drain pin
+.It Cm PP
+Push pull pin
+.It Cm TS
+Tristate pin
+.It Cm PU
+Pull-up pin
+.It Cm PD
+Pull-down pin
+.It Cm II
+Inverted input pin
+.It Cm IO
+Inverted output pin
+.El
+.It Fl f Ar ctldev
+GPIO controller device to use
+If not specified, defaults to
+.Pa /dev/gpioc0
+.It Fl l
+list available pins
+.It Fl n Ar pin Ar pin-name
+set the name used to describe the pin
+.It Fl t Ar pin
+toggle value of provided pin number
+.It Fl v
+be verbose: for each listed pin print current configuration
+.El
+.Sh EXAMPLES
+.Bl -bullet
+.It
+List pins available on GPIO controller defined by device /dev/gpioc0
+.Pp
+gpioctl -f /dev/gpioc0 -l
+.It
+Set the value of pin 12 to 1
+.Pp
+gpioctl -f /dev/gpioc0 12 1
+.It
+Configure pin 12 to be input pin
+.Pp
+gpioctl -f /dev/gpioc0 -c 12 IN
+.El
+.Sh SEE ALSO
+.Xr gpio 4 ,
+.Xr gpioiic 4 ,
+.Xr gpioled 4
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 9.0 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility and this manual page were written by
+.An Oleksandr Tymoshenko Aq Mt gonzo@freebsd.org .
diff --git a/usr.sbin/gpioctl/gpioctl.c b/usr.sbin/gpioctl/gpioctl.c
new file mode 100644
index 0000000..38e53e7
--- /dev/null
+++ b/usr.sbin/gpioctl/gpioctl.c
@@ -0,0 +1,327 @@
+/*-
+ * Copyright (c) 2009, Oleksandr Tymoshenko <gonzo@FreeBSD.org>
+ * Copyright (c) 2014, Rui Paulo <rpaulo@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 AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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 <fcntl.h>
+#include <getopt.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libgpio.h>
+
+struct flag_desc {
+ const char *name;
+ uint32_t flag;
+};
+
+static struct flag_desc gpio_flags[] = {
+ { "IN", GPIO_PIN_INPUT },
+ { "OUT", GPIO_PIN_OUTPUT },
+ { "OD", GPIO_PIN_OPENDRAIN },
+ { "PP", GPIO_PIN_PUSHPULL },
+ { "TS", GPIO_PIN_TRISTATE },
+ { "PU", GPIO_PIN_PULLUP },
+ { "PD", GPIO_PIN_PULLDOWN },
+ { "II", GPIO_PIN_INVIN },
+ { "IO", GPIO_PIN_INVOUT },
+ { "PULSE", GPIO_PIN_PULSATE },
+ { NULL, 0 },
+};
+
+int str2cap(const char *str);
+
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage:\n");
+ fprintf(stderr, "\tgpioctl [-f ctldev] -l [-v]\n");
+ fprintf(stderr, "\tgpioctl [-f ctldev] -t pin\n");
+ fprintf(stderr, "\tgpioctl [-f ctldev] -c pin flag ...\n");
+ fprintf(stderr, "\tgpioctl [-f ctldev] -n pin pin-name\n");
+ fprintf(stderr, "\tgpioctl [-f ctldev] pin [0|1]\n");
+ exit(1);
+}
+
+static const char *
+cap2str(uint32_t cap)
+{
+ struct flag_desc * pdesc = gpio_flags;
+ while (pdesc->name) {
+ if (pdesc->flag == cap)
+ return pdesc->name;
+ pdesc++;
+ }
+
+ return "UNKNOWN";
+}
+
+int
+str2cap(const char *str)
+{
+ struct flag_desc * pdesc = gpio_flags;
+ while (pdesc->name) {
+ if (strcasecmp(str, pdesc->name) == 0)
+ return pdesc->flag;
+ pdesc++;
+ }
+
+ return (-1);
+}
+
+/*
+ * Our handmade function for converting string to number
+ */
+static int
+str2int(const char *s, int *ok)
+{
+ char *endptr;
+ int res = strtod(s, &endptr);
+ if (endptr != s + strlen(s) )
+ *ok = 0;
+ else
+ *ok = 1;
+
+ return res;
+}
+
+static void
+print_caps(int caps)
+{
+ int i, need_coma;
+
+ need_coma = 0;
+ printf("<");
+ for (i = 0; i < 32; i++) {
+ if (caps & (1 << i)) {
+ if (need_coma)
+ printf(",");
+ printf("%s", cap2str(1 << i));
+ need_coma = 1;
+ }
+ }
+ printf(">");
+}
+
+static void
+dump_pins(gpio_handle_t handle, int verbose)
+{
+ int i, maxpin, pinv;
+ gpio_config_t *cfgs;
+ gpio_config_t *pin;
+
+ maxpin = gpio_pin_list(handle, &cfgs);
+ if (maxpin < 0) {
+ perror("gpio_pin_list");
+ exit(1);
+ }
+
+ for (i = 0; i <= maxpin; i++) {
+ pin = cfgs + i;
+ pinv = gpio_pin_get(handle, pin->g_pin);
+ printf("pin %02d:\t%d\t%s", pin->g_pin, pinv,
+ pin->g_name);
+
+ print_caps(pin->g_flags);
+
+ if (verbose) {
+ printf(", caps:");
+ print_caps(pin->g_caps);
+ }
+ printf("\n");
+ }
+ free(cfgs);
+}
+
+static void
+fail(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ int i;
+ gpio_config_t pin;
+ gpio_handle_t handle;
+ char *ctlfile = NULL;
+ int pinn, pinv, ch;
+ int flags, flag, ok;
+ int config, list, name, toggle, verbose;
+
+ config = toggle = verbose = list = name = pinn = 0;
+
+ while ((ch = getopt(argc, argv, "c:f:ln:t:v")) != -1) {
+ switch (ch) {
+ case 'c':
+ config = 1;
+ pinn = str2int(optarg, &ok);
+ if (!ok)
+ fail("Invalid pin number: %s\n", optarg);
+ break;
+ case 'f':
+ ctlfile = optarg;
+ break;
+ case 'l':
+ list = 1;
+ break;
+ case 'n':
+ name = 1;
+ pinn = str2int(optarg, &ok);
+ if (!ok)
+ fail("Invalid pin number: %s\n", optarg);
+ break;
+ case 't':
+ toggle = 1;
+ pinn = str2int(optarg, &ok);
+ if (!ok)
+ fail("Invalid pin number: %s\n", optarg);
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ default:
+ usage();
+ break;
+ }
+ }
+ argv += optind;
+ argc -= optind;
+ if (ctlfile == NULL)
+ handle = gpio_open(0);
+ else
+ handle = gpio_open_device(ctlfile);
+ if (handle == GPIO_INVALID_HANDLE) {
+ perror("gpio_open");
+ exit(1);
+ }
+
+ /* Set the pin name. */
+ if (name) {
+ if (argc == 0) {
+ usage();
+ exit(1);
+ }
+ if (gpio_pin_set_name(handle, pinn, argv[0]) < 0) {
+ perror("gpio_pin_set_name");
+ exit(1);
+ }
+ exit(0);
+ }
+
+ if (list) {
+ dump_pins(handle, verbose);
+ gpio_close(handle);
+ exit(0);
+ }
+
+ if (toggle) {
+ /*
+ * -t pin assumes no additional arguments
+ */
+ if (argc > 0) {
+ usage();
+ exit(1);
+ }
+ if (gpio_pin_toggle(handle, pinn) < 0) {
+ perror("gpio_pin_toggle");
+ exit(1);
+ }
+ gpio_close(handle);
+ exit(0);
+ }
+
+ if (config) {
+ flags = 0;
+ for (i = 0; i < argc; i++) {
+ flag = str2cap(argv[i]);
+ if (flag < 0)
+ fail("Invalid flag: %s\n", argv[i]);
+ flags |= flag;
+ }
+ pin.g_pin = pinn;
+ pin.g_flags = flags;
+ if (gpio_pin_set_flags(handle, &pin) < 0) {
+ perror("gpio_pin_set_flags");
+ exit(1);
+ }
+ exit(0);
+ }
+
+ /*
+ * Last two cases - set value or print value
+ */
+ if ((argc == 0) || (argc > 2)) {
+ usage();
+ exit(1);
+ }
+
+ pinn = str2int(argv[0], &ok);
+ if (!ok)
+ fail("Invalid pin number: %s\n", argv[0]);
+
+ /*
+ * Read pin value
+ */
+ if (argc == 1) {
+ pinv = gpio_pin_get(handle, pinn);
+ if (pinv < 0) {
+ perror("gpio_pin_get");
+ exit(1);
+ }
+ printf("%d\n", pinv);
+ exit(0);
+ }
+
+ /* Is it valid number (0 or 1) ? */
+ pinv = str2int(argv[1], &ok);
+ if (!ok || ((pinv != 0) && (pinv != 1)))
+ fail("Invalid pin value: %s\n", argv[1]);
+
+ /*
+ * Set pin value
+ */
+ if (gpio_pin_set(handle, pinn, pinv) < 0) {
+ perror("gpio_pin_set");
+ exit(1);
+ }
+
+ gpio_close(handle);
+ exit(0);
+}
diff --git a/usr.sbin/gssd/Makefile b/usr.sbin/gssd/Makefile
new file mode 100644
index 0000000..4faaf2d
--- /dev/null
+++ b/usr.sbin/gssd/Makefile
@@ -0,0 +1,35 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= gssd
+MAN= gssd.8
+SRCS= gssd.c gssd.h gssd_svc.c gssd_xdr.c gssd_prot.c
+
+CFLAGS+= -I.
+WARNS?= 1
+
+LIBADD= gssapi
+.if ${MK_KERBEROS_SUPPORT} != "no"
+LIBADD+= krb5 roken
+.else
+CFLAGS+= -DWITHOUT_KERBEROS
+.endif
+
+CLEANFILES= gssd_svc.c gssd_xdr.c gssd.h
+
+RPCSRC= ${.CURDIR}/../../sys/kgssapi/gssd.x
+RPCGEN= RPCGEN_CPP=${CPP:Q} rpcgen -L -C -M
+
+gssd_svc.c: ${RPCSRC} gssd.h
+ ${RPCGEN} -m -o ${.TARGET} ${RPCSRC}
+
+gssd_xdr.c: ${RPCSRC} gssd.h
+ ${RPCGEN} -c -o ${.TARGET} ${RPCSRC}
+
+gssd.h: ${RPCSRC}
+ ${RPCGEN} -h -o ${.TARGET} ${RPCSRC}
+
+.PATH: ${.CURDIR}/../../sys/kgssapi
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/gssd/Makefile.depend b/usr.sbin/gssd/Makefile.depend
new file mode 100644
index 0000000..63f755c
--- /dev/null
+++ b/usr.sbin/gssd/Makefile.depend
@@ -0,0 +1,44 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/gssapi \
+ include/rpc \
+ include/xlocale \
+ kerberos5/lib/libasn1 \
+ kerberos5/lib/libheimbase \
+ kerberos5/lib/libheimipcc \
+ kerberos5/lib/libhx509 \
+ kerberos5/lib/libkrb5 \
+ kerberos5/lib/libroken \
+ kerberos5/lib/libwind \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcom_err \
+ lib/libcompiler_rt \
+ lib/libcrypt \
+ lib/libgssapi \
+ lib/libthr \
+ secure/lib/libcrypto \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+gssd.o: gssd.h
+gssd.po: gssd.h
+gssd_prot.o: gssd.h
+gssd_prot.po: gssd.h
+gssd_svc.o: gssd.h
+gssd_svc.o: gssd_svc.c
+gssd_svc.po: gssd.h
+gssd_svc.po: gssd_svc.c
+gssd_xdr.o: gssd.h
+gssd_xdr.o: gssd_xdr.c
+gssd_xdr.po: gssd.h
+gssd_xdr.po: gssd_xdr.c
+.endif
diff --git a/usr.sbin/gssd/gssd.8 b/usr.sbin/gssd/gssd.8
new file mode 100644
index 0000000..7eaf11a
--- /dev/null
+++ b/usr.sbin/gssd/gssd.8
@@ -0,0 +1,119 @@
+.\" Copyright (c) 2008 Isilon Inc http://www.isilon.com/
+.\" Authors: Doug Rabson <dfr@rabson.org>
+.\" Developed with Red Inc: Alfred Perlstein <alfred@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$
+.\"
+.Dd July 7, 2013
+.Dt GSSD 8
+.Os
+.Sh NAME
+.Nm gssd
+.Nd "Generic Security Services Daemon"
+.Sh SYNOPSIS
+.Nm
+.Op Fl d
+.Op Fl h
+.Op Fl o
+.Op Fl v
+.Op Fl s Ar dir-list
+.Op Fl c Ar file-substring
+.Op Fl r Ar preferred-realm
+.Sh DESCRIPTION
+The
+.Nm
+program provides support for the kernel GSS-API implementation.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl d
+Run in debug mode.
+In this mode,
+.Nm
+will not fork when it starts.
+.It Fl h
+Enable support for host-based initiator credentials.
+This permits a kerberized NFS mount to use a service principal in
+the default Kerberos 5 keytab file for access.
+Such access is enabled via the
+gssname
+option for the
+.Xr mount_nfs 8
+command.
+.It Fl o
+Force use of DES and the associated old style GSS-API initialization token.
+This may be required to make kerberized NFS mounts work against some
+non-FreeBSD NFS servers.
+.It Fl v
+Run in verbose mode.
+In this mode,
+.Nm
+will log activity messages to syslog using LOG_INFO | LOG_DAEMON or to
+stderr, if the
+.Fl d
+option has also been specified.
+The minor status is logged as a decimal number, since it is actually a
+Kerberos return status, which is signed.
+.It Fl s Ar dir-list
+Look for an appropriate credential cache file in this list of directories.
+The list should be full pathnames from root, separated by ':' characters.
+Usually this list will simply be "/tmp".
+Without this option,
+.Nm
+assumes that the credential cache file is called /tmp/krb5cc_<uid>,
+where <uid> is the effective uid for the RPC caller.
+.It Fl c Ar file-substring
+Set a file-substring for the credential cache file names.
+Only files with this substring embedded in their names will be
+selected as candidates when
+.Fl s
+has been specified.
+If not specified, it defaults to "krb5cc_".
+.It Fl r Ar preferred-realm
+Use Kerberos credentials for this realm when searching for
+credentials in directories specified with
+.Fl s .
+If not specified, the default Kerberos realm will be used.
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /etc/krb5.keytab" -compact
+.It Pa /etc/krb5.keytab
+Contains Kerberos service principals which may be used as credentials
+by kernel GSS-API services.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr gssapi 3 ,
+.Xr syslog 3 ,
+.Xr mount_nfs 8
+.Sh HISTORY
+The
+.Nm
+manual page first appeared in
+.Fx 8.0 .
+.Sh AUTHORS
+This
+manual page was written by
+.An Doug Rabson Aq Mt dfr@FreeBSD.org .
diff --git a/usr.sbin/gssd/gssd.c b/usr.sbin/gssd/gssd.c
new file mode 100644
index 0000000..f3e5ce9
--- /dev/null
+++ b/usr.sbin/gssd/gssd.c
@@ -0,0 +1,1292 @@
+/*-
+ * Copyright (c) 2008 Isilon Inc http://www.isilon.com/
+ * Authors: Doug Rabson <dfr@rabson.org>
+ * Developed with Red Inc: Alfred Perlstein <alfred@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 <sys/param.h>
+#include <sys/stat.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/queue.h>
+#include <sys/syslog.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#ifndef WITHOUT_KERBEROS
+#include <krb5.h>
+#endif
+#include <pwd.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <gssapi/gssapi.h>
+#include <rpc/rpc.h>
+#include <rpc/rpc_com.h>
+
+#include "gssd.h"
+
+#ifndef _PATH_GSS_MECH
+#define _PATH_GSS_MECH "/etc/gss/mech"
+#endif
+#ifndef _PATH_GSSDSOCK
+#define _PATH_GSSDSOCK "/var/run/gssd.sock"
+#endif
+#define GSSD_CREDENTIAL_CACHE_FILE "/tmp/krb5cc_gssd"
+
+struct gss_resource {
+ LIST_ENTRY(gss_resource) gr_link;
+ uint64_t gr_id; /* indentifier exported to kernel */
+ void* gr_res; /* GSS-API resource pointer */
+};
+LIST_HEAD(gss_resource_list, gss_resource) gss_resources;
+int gss_resource_count;
+uint32_t gss_next_id;
+uint32_t gss_start_time;
+int debug_level;
+static char ccfile_dirlist[PATH_MAX + 1], ccfile_substring[NAME_MAX + 1];
+static char pref_realm[1024];
+static int verbose;
+static int use_old_des;
+static int hostbased_initiator_cred;
+#ifndef WITHOUT_KERBEROS
+/* 1.2.752.43.13.14 */
+static gss_OID_desc gss_krb5_set_allowable_enctypes_x_desc =
+{6, (void *) "\x2a\x85\x70\x2b\x0d\x0e"};
+static gss_OID GSS_KRB5_SET_ALLOWABLE_ENCTYPES_X =
+ &gss_krb5_set_allowable_enctypes_x_desc;
+static gss_OID_desc gss_krb5_mech_oid_x_desc =
+{9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
+static gss_OID GSS_KRB5_MECH_OID_X =
+ &gss_krb5_mech_oid_x_desc;
+#endif
+
+static void gssd_load_mech(void);
+static int find_ccache_file(const char *, uid_t, char *);
+static int is_a_valid_tgt_cache(const char *, uid_t, int *, time_t *);
+static void gssd_verbose_out(const char *, ...);
+#ifndef WITHOUT_KERBEROS
+static krb5_error_code gssd_get_cc_from_keytab(const char *);
+static OM_uint32 gssd_get_user_cred(OM_uint32 *, uid_t, gss_cred_id_t *);
+#endif
+void gssd_terminate(int);
+
+extern void gssd_1(struct svc_req *rqstp, SVCXPRT *transp);
+extern int gssd_syscall(char *path);
+
+int
+main(int argc, char **argv)
+{
+ /*
+ * We provide an RPC service on a local-domain socket. The
+ * kernel's GSS-API code will pass what it can't handle
+ * directly to us.
+ */
+ struct sockaddr_un sun;
+ int fd, oldmask, ch, debug;
+ SVCXPRT *xprt;
+
+ /*
+ * Initialize the credential cache file name substring and the
+ * search directory list.
+ */
+ strlcpy(ccfile_substring, "krb5cc_", sizeof(ccfile_substring));
+ ccfile_dirlist[0] = '\0';
+ pref_realm[0] = '\0';
+ debug = 0;
+ verbose = 0;
+ while ((ch = getopt(argc, argv, "dhovs:c:r:")) != -1) {
+ switch (ch) {
+ case 'd':
+ debug_level++;
+ break;
+ case 'h':
+#ifndef WITHOUT_KERBEROS
+ /*
+ * Enable use of a host based initiator credential
+ * in the default keytab file.
+ */
+ hostbased_initiator_cred = 1;
+#else
+ errx(1, "This option not available when built"
+ " without MK_KERBEROS\n");
+#endif
+ break;
+ case 'o':
+#ifndef WITHOUT_KERBEROS
+ /*
+ * Force use of DES and the old type of GSSAPI token.
+ */
+ use_old_des = 1;
+#else
+ errx(1, "This option not available when built"
+ " without MK_KERBEROS\n");
+#endif
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ case 's':
+#ifndef WITHOUT_KERBEROS
+ /*
+ * Set the directory search list. This enables use of
+ * find_ccache_file() to search the directories for a
+ * suitable credentials cache file.
+ */
+ strlcpy(ccfile_dirlist, optarg, sizeof(ccfile_dirlist));
+#else
+ errx(1, "This option not available when built"
+ " without MK_KERBEROS\n");
+#endif
+ break;
+ case 'c':
+ /*
+ * Specify a non-default credential cache file
+ * substring.
+ */
+ strlcpy(ccfile_substring, optarg,
+ sizeof(ccfile_substring));
+ break;
+ case 'r':
+ /*
+ * Set the preferred realm for the credential cache tgt.
+ */
+ strlcpy(pref_realm, optarg, sizeof(pref_realm));
+ break;
+ default:
+ fprintf(stderr,
+ "usage: %s [-d] [-s dir-list] [-c file-substring]"
+ " [-r preferred-realm]\n", argv[0]);
+ exit(1);
+ break;
+ }
+ }
+
+ gssd_load_mech();
+
+ if (!debug_level) {
+ if (daemon(0, 0) != 0)
+ err(1, "Can't daemonize");
+ signal(SIGINT, SIG_IGN);
+ signal(SIGQUIT, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+ }
+ signal(SIGTERM, gssd_terminate);
+
+ memset(&sun, 0, sizeof sun);
+ sun.sun_family = AF_LOCAL;
+ unlink(_PATH_GSSDSOCK);
+ strcpy(sun.sun_path, _PATH_GSSDSOCK);
+ sun.sun_len = SUN_LEN(&sun);
+ fd = socket(AF_LOCAL, SOCK_STREAM, 0);
+ if (fd < 0) {
+ if (debug_level == 0) {
+ syslog(LOG_ERR, "Can't create local gssd socket");
+ exit(1);
+ }
+ err(1, "Can't create local gssd socket");
+ }
+ oldmask = umask(S_IXUSR|S_IRWXG|S_IRWXO);
+ if (bind(fd, (struct sockaddr *) &sun, sun.sun_len) < 0) {
+ if (debug_level == 0) {
+ syslog(LOG_ERR, "Can't bind local gssd socket");
+ exit(1);
+ }
+ err(1, "Can't bind local gssd socket");
+ }
+ umask(oldmask);
+ if (listen(fd, SOMAXCONN) < 0) {
+ if (debug_level == 0) {
+ syslog(LOG_ERR, "Can't listen on local gssd socket");
+ exit(1);
+ }
+ err(1, "Can't listen on local gssd socket");
+ }
+ xprt = svc_vc_create(fd, RPC_MAXDATASIZE, RPC_MAXDATASIZE);
+ if (!xprt) {
+ if (debug_level == 0) {
+ syslog(LOG_ERR,
+ "Can't create transport for local gssd socket");
+ exit(1);
+ }
+ err(1, "Can't create transport for local gssd socket");
+ }
+ if (!svc_reg(xprt, GSSD, GSSDVERS, gssd_1, NULL)) {
+ if (debug_level == 0) {
+ syslog(LOG_ERR,
+ "Can't register service for local gssd socket");
+ exit(1);
+ }
+ err(1, "Can't register service for local gssd socket");
+ }
+
+ LIST_INIT(&gss_resources);
+ gss_next_id = 1;
+ gss_start_time = time(0);
+
+ gssd_syscall(_PATH_GSSDSOCK);
+ svc_run();
+ gssd_syscall("");
+
+ return (0);
+}
+
+static void
+gssd_load_mech(void)
+{
+ FILE *fp;
+ char buf[256];
+ char *p;
+ char *name, *oid, *lib, *kobj;
+
+ fp = fopen(_PATH_GSS_MECH, "r");
+ if (!fp)
+ return;
+
+ while (fgets(buf, sizeof(buf), fp)) {
+ if (*buf == '#')
+ continue;
+ p = buf;
+ name = strsep(&p, "\t\n ");
+ if (p) while (isspace(*p)) p++;
+ oid = strsep(&p, "\t\n ");
+ if (p) while (isspace(*p)) p++;
+ lib = strsep(&p, "\t\n ");
+ if (p) while (isspace(*p)) p++;
+ kobj = strsep(&p, "\t\n ");
+ if (!name || !oid || !lib || !kobj)
+ continue;
+
+ if (strcmp(kobj, "-")) {
+ /*
+ * Attempt to load the kernel module if its
+ * not already present.
+ */
+ if (modfind(kobj) < 0) {
+ if (kldload(kobj) < 0) {
+ fprintf(stderr,
+ "%s: can't find or load kernel module %s for %s\n",
+ getprogname(), kobj, name);
+ }
+ }
+ }
+ }
+ fclose(fp);
+}
+
+static void *
+gssd_find_resource(uint64_t id)
+{
+ struct gss_resource *gr;
+
+ if (!id)
+ return (NULL);
+
+ LIST_FOREACH(gr, &gss_resources, gr_link)
+ if (gr->gr_id == id)
+ return (gr->gr_res);
+
+ return (NULL);
+}
+
+static uint64_t
+gssd_make_resource(void *res)
+{
+ struct gss_resource *gr;
+
+ if (!res)
+ return (0);
+
+ gr = malloc(sizeof(struct gss_resource));
+ if (!gr)
+ return (0);
+ gr->gr_id = (gss_next_id++) + ((uint64_t) gss_start_time << 32);
+ gr->gr_res = res;
+ LIST_INSERT_HEAD(&gss_resources, gr, gr_link);
+ gss_resource_count++;
+ if (debug_level > 1)
+ printf("%d resources allocated\n", gss_resource_count);
+
+ return (gr->gr_id);
+}
+
+static void
+gssd_delete_resource(uint64_t id)
+{
+ struct gss_resource *gr;
+
+ LIST_FOREACH(gr, &gss_resources, gr_link) {
+ if (gr->gr_id == id) {
+ LIST_REMOVE(gr, gr_link);
+ free(gr);
+ gss_resource_count--;
+ if (debug_level > 1)
+ printf("%d resources allocated\n",
+ gss_resource_count);
+ return;
+ }
+ }
+}
+
+static void
+gssd_verbose_out(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (verbose != 0) {
+ va_start(ap, fmt);
+ if (debug_level == 0)
+ vsyslog(LOG_INFO | LOG_DAEMON, fmt, ap);
+ else
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ }
+}
+
+bool_t
+gssd_null_1_svc(void *argp, void *result, struct svc_req *rqstp)
+{
+
+ gssd_verbose_out("gssd_null: done\n");
+ return (TRUE);
+}
+
+bool_t
+gssd_init_sec_context_1_svc(init_sec_context_args *argp, init_sec_context_res *result, struct svc_req *rqstp)
+{
+ gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
+ gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
+ gss_name_t name = GSS_C_NO_NAME;
+ char ccname[PATH_MAX + 5 + 1], *cp, *cp2;
+ int gotone, gotcred;
+ OM_uint32 min_stat;
+#ifndef WITHOUT_KERBEROS
+ gss_buffer_desc principal_desc;
+ char enctype[sizeof(uint32_t)];
+ int key_enctype;
+ OM_uint32 maj_stat;
+#endif
+
+ memset(result, 0, sizeof(*result));
+ if (hostbased_initiator_cred != 0 && argp->cred != 0 &&
+ argp->uid == 0) {
+ /*
+ * These credentials are for a host based initiator name
+ * in a keytab file, which should now have credentials
+ * in /tmp/krb5cc_gssd, because gss_acquire_cred() did
+ * the equivalent of "kinit -k".
+ */
+ snprintf(ccname, sizeof(ccname), "FILE:%s",
+ GSSD_CREDENTIAL_CACHE_FILE);
+ } else if (ccfile_dirlist[0] != '\0' && argp->cred == 0) {
+ /*
+ * For the "-s" case and no credentials provided as an
+ * argument, search the directory list for an appropriate
+ * credential cache file. If the search fails, return failure.
+ */
+ gotone = 0;
+ cp = ccfile_dirlist;
+ do {
+ cp2 = strchr(cp, ':');
+ if (cp2 != NULL)
+ *cp2 = '\0';
+ gotone = find_ccache_file(cp, argp->uid, ccname);
+ if (gotone != 0)
+ break;
+ if (cp2 != NULL)
+ *cp2++ = ':';
+ cp = cp2;
+ } while (cp != NULL && *cp != '\0');
+ if (gotone == 0) {
+ result->major_status = GSS_S_CREDENTIALS_EXPIRED;
+ gssd_verbose_out("gssd_init_sec_context: -s no"
+ " credential cache file found for uid=%d\n",
+ (int)argp->uid);
+ return (TRUE);
+ }
+ } else {
+ /*
+ * If there wasn't a "-s" option or the credentials have
+ * been provided as an argument, do it the old way.
+ * When credentials are provided, the uid should be root.
+ */
+ if (argp->cred != 0 && argp->uid != 0) {
+ if (debug_level == 0)
+ syslog(LOG_ERR, "gss_init_sec_context:"
+ " cred for non-root");
+ else
+ fprintf(stderr, "gss_init_sec_context:"
+ " cred for non-root\n");
+ }
+ snprintf(ccname, sizeof(ccname), "FILE:/tmp/krb5cc_%d",
+ (int) argp->uid);
+ }
+ setenv("KRB5CCNAME", ccname, TRUE);
+
+ if (argp->cred) {
+ cred = gssd_find_resource(argp->cred);
+ if (!cred) {
+ result->major_status = GSS_S_CREDENTIALS_EXPIRED;
+ gssd_verbose_out("gssd_init_sec_context: cred"
+ " resource not found\n");
+ return (TRUE);
+ }
+ }
+ if (argp->ctx) {
+ ctx = gssd_find_resource(argp->ctx);
+ if (!ctx) {
+ result->major_status = GSS_S_CONTEXT_EXPIRED;
+ gssd_verbose_out("gssd_init_sec_context: context"
+ " resource not found\n");
+ return (TRUE);
+ }
+ }
+ if (argp->name) {
+ name = gssd_find_resource(argp->name);
+ if (!name) {
+ result->major_status = GSS_S_BAD_NAME;
+ gssd_verbose_out("gssd_init_sec_context: name"
+ " resource not found\n");
+ return (TRUE);
+ }
+ }
+ gotcred = 0;
+
+#ifndef WITHOUT_KERBEROS
+ if (use_old_des != 0) {
+ if (cred == GSS_C_NO_CREDENTIAL) {
+ /* Acquire a credential for the uid. */
+ maj_stat = gssd_get_user_cred(&min_stat, argp->uid,
+ &cred);
+ if (maj_stat == GSS_S_COMPLETE)
+ gotcred = 1;
+ else
+ gssd_verbose_out("gssd_init_sec_context: "
+ "get user cred failed uid=%d major=0x%x "
+ "minor=%d\n", (int)argp->uid,
+ (unsigned int)maj_stat, (int)min_stat);
+ }
+ if (cred != GSS_C_NO_CREDENTIAL) {
+ key_enctype = ETYPE_DES_CBC_CRC;
+ enctype[0] = (key_enctype >> 24) & 0xff;
+ enctype[1] = (key_enctype >> 16) & 0xff;
+ enctype[2] = (key_enctype >> 8) & 0xff;
+ enctype[3] = key_enctype & 0xff;
+ principal_desc.length = sizeof(enctype);
+ principal_desc.value = enctype;
+ result->major_status = gss_set_cred_option(
+ &result->minor_status, &cred,
+ GSS_KRB5_SET_ALLOWABLE_ENCTYPES_X,
+ &principal_desc);
+ gssd_verbose_out("gssd_init_sec_context: set allowable "
+ "enctype major=0x%x minor=%d\n",
+ (unsigned int)result->major_status,
+ (int)result->minor_status);
+ if (result->major_status != GSS_S_COMPLETE) {
+ if (gotcred != 0)
+ gss_release_cred(&min_stat, &cred);
+ return (TRUE);
+ }
+ }
+ }
+#endif
+ result->major_status = gss_init_sec_context(&result->minor_status,
+ cred, &ctx, name, argp->mech_type,
+ argp->req_flags, argp->time_req, argp->input_chan_bindings,
+ &argp->input_token, &result->actual_mech_type,
+ &result->output_token, &result->ret_flags, &result->time_rec);
+ gssd_verbose_out("gssd_init_sec_context: done major=0x%x minor=%d"
+ " uid=%d\n", (unsigned int)result->major_status,
+ (int)result->minor_status, (int)argp->uid);
+ if (gotcred != 0)
+ gss_release_cred(&min_stat, &cred);
+
+ if (result->major_status == GSS_S_COMPLETE
+ || result->major_status == GSS_S_CONTINUE_NEEDED) {
+ if (argp->ctx)
+ result->ctx = argp->ctx;
+ else
+ result->ctx = gssd_make_resource(ctx);
+ }
+
+ return (TRUE);
+}
+
+bool_t
+gssd_accept_sec_context_1_svc(accept_sec_context_args *argp, accept_sec_context_res *result, struct svc_req *rqstp)
+{
+ gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
+ gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
+ gss_name_t src_name;
+ gss_cred_id_t delegated_cred_handle;
+
+ memset(result, 0, sizeof(*result));
+ if (argp->ctx) {
+ ctx = gssd_find_resource(argp->ctx);
+ if (!ctx) {
+ result->major_status = GSS_S_CONTEXT_EXPIRED;
+ gssd_verbose_out("gssd_accept_sec_context: ctx"
+ " resource not found\n");
+ return (TRUE);
+ }
+ }
+ if (argp->cred) {
+ cred = gssd_find_resource(argp->cred);
+ if (!cred) {
+ result->major_status = GSS_S_CREDENTIALS_EXPIRED;
+ gssd_verbose_out("gssd_accept_sec_context: cred"
+ " resource not found\n");
+ return (TRUE);
+ }
+ }
+
+ memset(result, 0, sizeof(*result));
+ result->major_status = gss_accept_sec_context(&result->minor_status,
+ &ctx, cred, &argp->input_token, argp->input_chan_bindings,
+ &src_name, &result->mech_type, &result->output_token,
+ &result->ret_flags, &result->time_rec,
+ &delegated_cred_handle);
+ gssd_verbose_out("gssd_accept_sec_context: done major=0x%x minor=%d\n",
+ (unsigned int)result->major_status, (int)result->minor_status);
+
+ if (result->major_status == GSS_S_COMPLETE
+ || result->major_status == GSS_S_CONTINUE_NEEDED) {
+ if (argp->ctx)
+ result->ctx = argp->ctx;
+ else
+ result->ctx = gssd_make_resource(ctx);
+ result->src_name = gssd_make_resource(src_name);
+ result->delegated_cred_handle =
+ gssd_make_resource(delegated_cred_handle);
+ }
+
+ return (TRUE);
+}
+
+bool_t
+gssd_delete_sec_context_1_svc(delete_sec_context_args *argp, delete_sec_context_res *result, struct svc_req *rqstp)
+{
+ gss_ctx_id_t ctx = gssd_find_resource(argp->ctx);
+
+ if (ctx) {
+ result->major_status = gss_delete_sec_context(
+ &result->minor_status, &ctx, &result->output_token);
+ gssd_delete_resource(argp->ctx);
+ } else {
+ result->major_status = GSS_S_COMPLETE;
+ result->minor_status = 0;
+ }
+ gssd_verbose_out("gssd_delete_sec_context: done major=0x%x minor=%d\n",
+ (unsigned int)result->major_status, (int)result->minor_status);
+
+ return (TRUE);
+}
+
+bool_t
+gssd_export_sec_context_1_svc(export_sec_context_args *argp, export_sec_context_res *result, struct svc_req *rqstp)
+{
+ gss_ctx_id_t ctx = gssd_find_resource(argp->ctx);
+
+ if (ctx) {
+ result->major_status = gss_export_sec_context(
+ &result->minor_status, &ctx,
+ &result->interprocess_token);
+ result->format = KGSS_HEIMDAL_1_1;
+ gssd_delete_resource(argp->ctx);
+ } else {
+ result->major_status = GSS_S_FAILURE;
+ result->minor_status = 0;
+ result->interprocess_token.length = 0;
+ result->interprocess_token.value = NULL;
+ }
+ gssd_verbose_out("gssd_export_sec_context: done major=0x%x minor=%d\n",
+ (unsigned int)result->major_status, (int)result->minor_status);
+
+ return (TRUE);
+}
+
+bool_t
+gssd_import_name_1_svc(import_name_args *argp, import_name_res *result, struct svc_req *rqstp)
+{
+ gss_name_t name;
+
+ result->major_status = gss_import_name(&result->minor_status,
+ &argp->input_name_buffer, argp->input_name_type, &name);
+ gssd_verbose_out("gssd_import_name: done major=0x%x minor=%d\n",
+ (unsigned int)result->major_status, (int)result->minor_status);
+
+ if (result->major_status == GSS_S_COMPLETE)
+ result->output_name = gssd_make_resource(name);
+ else
+ result->output_name = 0;
+
+ return (TRUE);
+}
+
+bool_t
+gssd_canonicalize_name_1_svc(canonicalize_name_args *argp, canonicalize_name_res *result, struct svc_req *rqstp)
+{
+ gss_name_t name = gssd_find_resource(argp->input_name);
+ gss_name_t output_name;
+
+ memset(result, 0, sizeof(*result));
+ if (!name) {
+ result->major_status = GSS_S_BAD_NAME;
+ return (TRUE);
+ }
+
+ result->major_status = gss_canonicalize_name(&result->minor_status,
+ name, argp->mech_type, &output_name);
+ gssd_verbose_out("gssd_canonicalize_name: done major=0x%x minor=%d\n",
+ (unsigned int)result->major_status, (int)result->minor_status);
+
+ if (result->major_status == GSS_S_COMPLETE)
+ result->output_name = gssd_make_resource(output_name);
+ else
+ result->output_name = 0;
+
+ return (TRUE);
+}
+
+bool_t
+gssd_export_name_1_svc(export_name_args *argp, export_name_res *result, struct svc_req *rqstp)
+{
+ gss_name_t name = gssd_find_resource(argp->input_name);
+
+ memset(result, 0, sizeof(*result));
+ if (!name) {
+ result->major_status = GSS_S_BAD_NAME;
+ gssd_verbose_out("gssd_export_name: name resource not found\n");
+ return (TRUE);
+ }
+
+ result->major_status = gss_export_name(&result->minor_status,
+ name, &result->exported_name);
+ gssd_verbose_out("gssd_export_name: done major=0x%x minor=%d\n",
+ (unsigned int)result->major_status, (int)result->minor_status);
+
+ return (TRUE);
+}
+
+bool_t
+gssd_release_name_1_svc(release_name_args *argp, release_name_res *result, struct svc_req *rqstp)
+{
+ gss_name_t name = gssd_find_resource(argp->input_name);
+
+ if (name) {
+ result->major_status = gss_release_name(&result->minor_status,
+ &name);
+ gssd_delete_resource(argp->input_name);
+ } else {
+ result->major_status = GSS_S_COMPLETE;
+ result->minor_status = 0;
+ }
+ gssd_verbose_out("gssd_release_name: done major=0x%x minor=%d\n",
+ (unsigned int)result->major_status, (int)result->minor_status);
+
+ return (TRUE);
+}
+
+bool_t
+gssd_pname_to_uid_1_svc(pname_to_uid_args *argp, pname_to_uid_res *result, struct svc_req *rqstp)
+{
+ gss_name_t name = gssd_find_resource(argp->pname);
+ uid_t uid;
+ char buf[1024], *bufp;
+ struct passwd pwd, *pw;
+ size_t buflen;
+ int error;
+ static size_t buflen_hint = 1024;
+
+ memset(result, 0, sizeof(*result));
+ if (name) {
+ result->major_status =
+ gss_pname_to_uid(&result->minor_status,
+ name, argp->mech, &uid);
+ if (result->major_status == GSS_S_COMPLETE) {
+ result->uid = uid;
+ buflen = buflen_hint;
+ for (;;) {
+ pw = NULL;
+ bufp = buf;
+ if (buflen > sizeof(buf))
+ bufp = malloc(buflen);
+ if (bufp == NULL)
+ break;
+ error = getpwuid_r(uid, &pwd, bufp, buflen,
+ &pw);
+ if (error != ERANGE)
+ break;
+ if (buflen > sizeof(buf))
+ free(bufp);
+ buflen += 1024;
+ if (buflen > buflen_hint)
+ buflen_hint = buflen;
+ }
+ if (pw) {
+ int len = NGROUPS;
+ int groups[NGROUPS];
+ result->gid = pw->pw_gid;
+ getgrouplist(pw->pw_name, pw->pw_gid,
+ groups, &len);
+ result->gidlist.gidlist_len = len;
+ result->gidlist.gidlist_val =
+ mem_alloc(len * sizeof(int));
+ memcpy(result->gidlist.gidlist_val, groups,
+ len * sizeof(int));
+ gssd_verbose_out("gssd_pname_to_uid: mapped"
+ " to uid=%d, gid=%d\n", (int)result->uid,
+ (int)result->gid);
+ } else {
+ result->gid = 65534;
+ result->gidlist.gidlist_len = 0;
+ result->gidlist.gidlist_val = NULL;
+ gssd_verbose_out("gssd_pname_to_uid: mapped"
+ " to uid=%d, but no groups\n",
+ (int)result->uid);
+ }
+ if (bufp != NULL && buflen > sizeof(buf))
+ free(bufp);
+ } else
+ gssd_verbose_out("gssd_pname_to_uid: failed major=0x%x"
+ " minor=%d\n", (unsigned int)result->major_status,
+ (int)result->minor_status);
+ } else {
+ result->major_status = GSS_S_BAD_NAME;
+ result->minor_status = 0;
+ gssd_verbose_out("gssd_pname_to_uid: no name\n");
+ }
+
+ return (TRUE);
+}
+
+bool_t
+gssd_acquire_cred_1_svc(acquire_cred_args *argp, acquire_cred_res *result, struct svc_req *rqstp)
+{
+ gss_name_t desired_name = GSS_C_NO_NAME;
+ gss_cred_id_t cred;
+ char ccname[PATH_MAX + 5 + 1], *cp, *cp2;
+ int gotone;
+#ifndef WITHOUT_KERBEROS
+ gss_buffer_desc namebuf;
+ uint32_t minstat;
+ krb5_error_code kret;
+#endif
+
+ memset(result, 0, sizeof(*result));
+ if (argp->desired_name) {
+ desired_name = gssd_find_resource(argp->desired_name);
+ if (!desired_name) {
+ result->major_status = GSS_S_BAD_NAME;
+ gssd_verbose_out("gssd_acquire_cred: no desired name"
+ " found\n");
+ return (TRUE);
+ }
+ }
+
+#ifndef WITHOUT_KERBEROS
+ if (hostbased_initiator_cred != 0 && argp->desired_name != 0 &&
+ argp->uid == 0 && argp->cred_usage == GSS_C_INITIATE) {
+ /* This is a host based initiator name in the keytab file. */
+ snprintf(ccname, sizeof(ccname), "FILE:%s",
+ GSSD_CREDENTIAL_CACHE_FILE);
+ setenv("KRB5CCNAME", ccname, TRUE);
+ result->major_status = gss_display_name(&result->minor_status,
+ desired_name, &namebuf, NULL);
+ gssd_verbose_out("gssd_acquire_cred: desired name for host "
+ "based initiator cred major=0x%x minor=%d\n",
+ (unsigned int)result->major_status,
+ (int)result->minor_status);
+ if (result->major_status != GSS_S_COMPLETE)
+ return (TRUE);
+ if (namebuf.length > PATH_MAX + 5) {
+ result->minor_status = 0;
+ result->major_status = GSS_S_FAILURE;
+ return (TRUE);
+ }
+ memcpy(ccname, namebuf.value, namebuf.length);
+ ccname[namebuf.length] = '\0';
+ if ((cp = strchr(ccname, '@')) != NULL)
+ *cp = '/';
+ kret = gssd_get_cc_from_keytab(ccname);
+ gssd_verbose_out("gssd_acquire_cred: using keytab entry for "
+ "%s, kerberos ret=%d\n", ccname, (int)kret);
+ gss_release_buffer(&minstat, &namebuf);
+ if (kret != 0) {
+ result->minor_status = kret;
+ result->major_status = GSS_S_FAILURE;
+ return (TRUE);
+ }
+ } else
+#endif /* !WITHOUT_KERBEROS */
+ if (ccfile_dirlist[0] != '\0' && argp->desired_name == 0) {
+ /*
+ * For the "-s" case and no name provided as an
+ * argument, search the directory list for an appropriate
+ * credential cache file. If the search fails, return failure.
+ */
+ gotone = 0;
+ cp = ccfile_dirlist;
+ do {
+ cp2 = strchr(cp, ':');
+ if (cp2 != NULL)
+ *cp2 = '\0';
+ gotone = find_ccache_file(cp, argp->uid, ccname);
+ if (gotone != 0)
+ break;
+ if (cp2 != NULL)
+ *cp2++ = ':';
+ cp = cp2;
+ } while (cp != NULL && *cp != '\0');
+ if (gotone == 0) {
+ result->major_status = GSS_S_CREDENTIALS_EXPIRED;
+ gssd_verbose_out("gssd_acquire_cred: no cred cache"
+ " file found\n");
+ return (TRUE);
+ }
+ setenv("KRB5CCNAME", ccname, TRUE);
+ } else {
+ /*
+ * If there wasn't a "-s" option or the name has
+ * been provided as an argument, do it the old way.
+ * When a name is provided, it will normally exist in the
+ * default keytab file and the uid will be root.
+ */
+ if (argp->desired_name != 0 && argp->uid != 0) {
+ if (debug_level == 0)
+ syslog(LOG_ERR, "gss_acquire_cred:"
+ " principal_name for non-root");
+ else
+ fprintf(stderr, "gss_acquire_cred:"
+ " principal_name for non-root\n");
+ }
+ snprintf(ccname, sizeof(ccname), "FILE:/tmp/krb5cc_%d",
+ (int) argp->uid);
+ setenv("KRB5CCNAME", ccname, TRUE);
+ }
+
+ result->major_status = gss_acquire_cred(&result->minor_status,
+ desired_name, argp->time_req, argp->desired_mechs,
+ argp->cred_usage, &cred, &result->actual_mechs, &result->time_rec);
+ gssd_verbose_out("gssd_acquire_cred: done major=0x%x minor=%d\n",
+ (unsigned int)result->major_status, (int)result->minor_status);
+
+ if (result->major_status == GSS_S_COMPLETE)
+ result->output_cred = gssd_make_resource(cred);
+ else
+ result->output_cred = 0;
+
+ return (TRUE);
+}
+
+bool_t
+gssd_set_cred_option_1_svc(set_cred_option_args *argp, set_cred_option_res *result, struct svc_req *rqstp)
+{
+ gss_cred_id_t cred = gssd_find_resource(argp->cred);
+
+ memset(result, 0, sizeof(*result));
+ if (!cred) {
+ result->major_status = GSS_S_CREDENTIALS_EXPIRED;
+ gssd_verbose_out("gssd_set_cred: no credentials\n");
+ return (TRUE);
+ }
+
+ result->major_status = gss_set_cred_option(&result->minor_status,
+ &cred, argp->option_name, &argp->option_value);
+ gssd_verbose_out("gssd_set_cred: done major=0x%x minor=%d\n",
+ (unsigned int)result->major_status, (int)result->minor_status);
+
+ return (TRUE);
+}
+
+bool_t
+gssd_release_cred_1_svc(release_cred_args *argp, release_cred_res *result, struct svc_req *rqstp)
+{
+ gss_cred_id_t cred = gssd_find_resource(argp->cred);
+
+ if (cred) {
+ result->major_status = gss_release_cred(&result->minor_status,
+ &cred);
+ gssd_delete_resource(argp->cred);
+ } else {
+ result->major_status = GSS_S_COMPLETE;
+ result->minor_status = 0;
+ }
+ gssd_verbose_out("gssd_release_cred: done major=0x%x minor=%d\n",
+ (unsigned int)result->major_status, (int)result->minor_status);
+
+ return (TRUE);
+}
+
+bool_t
+gssd_display_status_1_svc(display_status_args *argp, display_status_res *result, struct svc_req *rqstp)
+{
+
+ result->message_context = argp->message_context;
+ result->major_status = gss_display_status(&result->minor_status,
+ argp->status_value, argp->status_type, argp->mech_type,
+ &result->message_context, &result->status_string);
+ gssd_verbose_out("gssd_display_status: done major=0x%x minor=%d\n",
+ (unsigned int)result->major_status, (int)result->minor_status);
+
+ return (TRUE);
+}
+
+int
+gssd_1_freeresult(SVCXPRT *transp, xdrproc_t xdr_result, caddr_t result)
+{
+ /*
+ * We don't use XDR to free the results - anything which was
+ * allocated came from GSS-API. We use xdr_result to figure
+ * out what to do.
+ */
+ OM_uint32 junk;
+
+ if (xdr_result == (xdrproc_t) xdr_init_sec_context_res) {
+ init_sec_context_res *p = (init_sec_context_res *) result;
+ gss_release_buffer(&junk, &p->output_token);
+ } else if (xdr_result == (xdrproc_t) xdr_accept_sec_context_res) {
+ accept_sec_context_res *p = (accept_sec_context_res *) result;
+ gss_release_buffer(&junk, &p->output_token);
+ } else if (xdr_result == (xdrproc_t) xdr_delete_sec_context_res) {
+ delete_sec_context_res *p = (delete_sec_context_res *) result;
+ gss_release_buffer(&junk, &p->output_token);
+ } else if (xdr_result == (xdrproc_t) xdr_export_sec_context_res) {
+ export_sec_context_res *p = (export_sec_context_res *) result;
+ if (p->interprocess_token.length)
+ memset(p->interprocess_token.value, 0,
+ p->interprocess_token.length);
+ gss_release_buffer(&junk, &p->interprocess_token);
+ } else if (xdr_result == (xdrproc_t) xdr_export_name_res) {
+ export_name_res *p = (export_name_res *) result;
+ gss_release_buffer(&junk, &p->exported_name);
+ } else if (xdr_result == (xdrproc_t) xdr_acquire_cred_res) {
+ acquire_cred_res *p = (acquire_cred_res *) result;
+ gss_release_oid_set(&junk, &p->actual_mechs);
+ } else if (xdr_result == (xdrproc_t) xdr_pname_to_uid_res) {
+ pname_to_uid_res *p = (pname_to_uid_res *) result;
+ if (p->gidlist.gidlist_val)
+ free(p->gidlist.gidlist_val);
+ } else if (xdr_result == (xdrproc_t) xdr_display_status_res) {
+ display_status_res *p = (display_status_res *) result;
+ gss_release_buffer(&junk, &p->status_string);
+ }
+
+ return (TRUE);
+}
+
+/*
+ * Search a directory for the most likely candidate to be used as the
+ * credential cache for a uid. If successful, return 1 and fill the
+ * file's path id into "rpath". Otherwise, return 0.
+ */
+static int
+find_ccache_file(const char *dirpath, uid_t uid, char *rpath)
+{
+ DIR *dirp;
+ struct dirent *dp;
+ struct stat sb;
+ time_t exptime, oexptime;
+ int gotone, len, rating, orating;
+ char namepath[PATH_MAX + 5 + 1];
+ char retpath[PATH_MAX + 5 + 1];
+
+ dirp = opendir(dirpath);
+ if (dirp == NULL)
+ return (0);
+ gotone = 0;
+ orating = 0;
+ oexptime = 0;
+ while ((dp = readdir(dirp)) != NULL) {
+ len = snprintf(namepath, sizeof(namepath), "%s/%s", dirpath,
+ dp->d_name);
+ if (len < sizeof(namepath) &&
+ (hostbased_initiator_cred == 0 || strcmp(namepath,
+ GSSD_CREDENTIAL_CACHE_FILE) != 0) &&
+ strstr(dp->d_name, ccfile_substring) != NULL &&
+ lstat(namepath, &sb) >= 0 &&
+ sb.st_uid == uid &&
+ S_ISREG(sb.st_mode)) {
+ len = snprintf(namepath, sizeof(namepath), "FILE:%s/%s",
+ dirpath, dp->d_name);
+ if (len < sizeof(namepath) &&
+ is_a_valid_tgt_cache(namepath, uid, &rating,
+ &exptime) != 0) {
+ if (gotone == 0 || rating > orating ||
+ (rating == orating && exptime > oexptime)) {
+ orating = rating;
+ oexptime = exptime;
+ strcpy(retpath, namepath);
+ gotone = 1;
+ }
+ }
+ }
+ }
+ closedir(dirp);
+ if (gotone != 0) {
+ strcpy(rpath, retpath);
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * Try to determine if the file is a valid tgt cache file.
+ * Check that the file has a valid tgt for a principal.
+ * If it does, return 1, otherwise return 0.
+ * It also returns a "rating" and the expiry time for the TGT, when found.
+ * This "rating" is higher based on heuristics that make it more
+ * likely to be the correct credential cache file to use. It can
+ * be used by the caller, along with expiry time, to select from
+ * multiple credential cache files.
+ */
+static int
+is_a_valid_tgt_cache(const char *filepath, uid_t uid, int *retrating,
+ time_t *retexptime)
+{
+#ifndef WITHOUT_KERBEROS
+ krb5_context context;
+ krb5_principal princ;
+ krb5_ccache ccache;
+ krb5_error_code retval;
+ krb5_cc_cursor curse;
+ krb5_creds krbcred;
+ int gotone, orating, rating, ret;
+ struct passwd *pw;
+ char *cp, *cp2, *pname;
+ time_t exptime;
+
+ /* Find a likely name for the uid principal. */
+ pw = getpwuid(uid);
+
+ /*
+ * Do a bunch of krb5 library stuff to try and determine if
+ * this file is a credentials cache with an appropriate TGT
+ * in it.
+ */
+ retval = krb5_init_context(&context);
+ if (retval != 0)
+ return (0);
+ retval = krb5_cc_resolve(context, filepath, &ccache);
+ if (retval != 0) {
+ krb5_free_context(context);
+ return (0);
+ }
+ ret = 0;
+ orating = 0;
+ exptime = 0;
+ retval = krb5_cc_start_seq_get(context, ccache, &curse);
+ if (retval == 0) {
+ while ((retval = krb5_cc_next_cred(context, ccache, &curse,
+ &krbcred)) == 0) {
+ gotone = 0;
+ rating = 0;
+ retval = krb5_unparse_name(context, krbcred.server,
+ &pname);
+ if (retval == 0) {
+ cp = strchr(pname, '/');
+ if (cp != NULL) {
+ *cp++ = '\0';
+ if (strcmp(pname, "krbtgt") == 0 &&
+ krbcred.times.endtime > time(NULL)
+ ) {
+ gotone = 1;
+ /*
+ * Test to see if this is a
+ * tgt for cross-realm auth.
+ * Rate it higher, if it is not.
+ */
+ cp2 = strchr(cp, '@');
+ if (cp2 != NULL) {
+ *cp2++ = '\0';
+ if (strcmp(cp, cp2) ==
+ 0)
+ rating++;
+ }
+ }
+ }
+ free(pname);
+ }
+ if (gotone != 0) {
+ retval = krb5_unparse_name(context,
+ krbcred.client, &pname);
+ if (retval == 0) {
+ cp = strchr(pname, '@');
+ if (cp != NULL) {
+ *cp++ = '\0';
+ if (pw != NULL && strcmp(pname,
+ pw->pw_name) == 0)
+ rating++;
+ if (strchr(pname, '/') == NULL)
+ rating++;
+ if (pref_realm[0] != '\0' &&
+ strcmp(cp, pref_realm) == 0)
+ rating++;
+ }
+ }
+ free(pname);
+ if (rating > orating) {
+ orating = rating;
+ exptime = krbcred.times.endtime;
+ } else if (rating == orating &&
+ krbcred.times.endtime > exptime)
+ exptime = krbcred.times.endtime;
+ ret = 1;
+ }
+ krb5_free_cred_contents(context, &krbcred);
+ }
+ krb5_cc_end_seq_get(context, ccache, &curse);
+ }
+ krb5_cc_close(context, ccache);
+ krb5_free_context(context);
+ if (ret != 0) {
+ *retrating = orating;
+ *retexptime = exptime;
+ }
+ return (ret);
+#else /* WITHOUT_KERBEROS */
+ return (0);
+#endif /* !WITHOUT_KERBEROS */
+}
+
+#ifndef WITHOUT_KERBEROS
+/*
+ * This function attempts to do essentially a "kinit -k" for the principal
+ * name provided as the argument, so that there will be a TGT in the
+ * credential cache.
+ */
+static krb5_error_code
+gssd_get_cc_from_keytab(const char *name)
+{
+ krb5_error_code ret, opt_ret, princ_ret, cc_ret, kt_ret, cred_ret;
+ krb5_context context;
+ krb5_principal principal;
+ krb5_keytab kt;
+ krb5_creds cred;
+ krb5_get_init_creds_opt *opt;
+ krb5_deltat start_time = 0;
+ krb5_ccache ccache;
+
+ ret = krb5_init_context(&context);
+ if (ret != 0)
+ return (ret);
+ opt_ret = cc_ret = kt_ret = cred_ret = 1; /* anything non-zero */
+ princ_ret = ret = krb5_parse_name(context, name, &principal);
+ if (ret == 0)
+ opt_ret = ret = krb5_get_init_creds_opt_alloc(context, &opt);
+ if (ret == 0)
+ cc_ret = ret = krb5_cc_default(context, &ccache);
+ if (ret == 0)
+ ret = krb5_cc_initialize(context, ccache, principal);
+ if (ret == 0) {
+ krb5_get_init_creds_opt_set_default_flags(context, "gssd",
+ krb5_principal_get_realm(context, principal), opt);
+ kt_ret = ret = krb5_kt_default(context, &kt);
+ }
+ if (ret == 0)
+ cred_ret = ret = krb5_get_init_creds_keytab(context, &cred,
+ principal, kt, start_time, NULL, opt);
+ if (ret == 0)
+ ret = krb5_cc_store_cred(context, ccache, &cred);
+ if (kt_ret == 0)
+ krb5_kt_close(context, kt);
+ if (cc_ret == 0)
+ krb5_cc_close(context, ccache);
+ if (opt_ret == 0)
+ krb5_get_init_creds_opt_free(context, opt);
+ if (princ_ret == 0)
+ krb5_free_principal(context, principal);
+ if (cred_ret == 0)
+ krb5_free_cred_contents(context, &cred);
+ krb5_free_context(context);
+ return (ret);
+}
+
+/*
+ * Acquire a gss credential for a uid.
+ */
+static OM_uint32
+gssd_get_user_cred(OM_uint32 *min_statp, uid_t uid, gss_cred_id_t *credp)
+{
+ gss_buffer_desc principal_desc;
+ gss_name_t name;
+ OM_uint32 maj_stat, min_stat;
+ gss_OID_set mechlist;
+ struct passwd *pw;
+
+ pw = getpwuid(uid);
+ if (pw == NULL) {
+ *min_statp = 0;
+ return (GSS_S_FAILURE);
+ }
+
+ /*
+ * The mechanism must be set to KerberosV for acquisition
+ * of credentials to work reliably.
+ */
+ maj_stat = gss_create_empty_oid_set(min_statp, &mechlist);
+ if (maj_stat != GSS_S_COMPLETE)
+ return (maj_stat);
+ maj_stat = gss_add_oid_set_member(min_statp, GSS_KRB5_MECH_OID_X,
+ &mechlist);
+ if (maj_stat != GSS_S_COMPLETE) {
+ gss_release_oid_set(&min_stat, &mechlist);
+ return (maj_stat);
+ }
+
+ principal_desc.value = (void *)pw->pw_name;
+ principal_desc.length = strlen(pw->pw_name);
+ maj_stat = gss_import_name(min_statp, &principal_desc,
+ GSS_C_NT_USER_NAME, &name);
+ if (maj_stat != GSS_S_COMPLETE) {
+ gss_release_oid_set(&min_stat, &mechlist);
+ return (maj_stat);
+ }
+ /* Acquire the credentials. */
+ maj_stat = gss_acquire_cred(min_statp, name, 0, mechlist,
+ GSS_C_INITIATE, credp, NULL, NULL);
+ gss_release_name(&min_stat, &name);
+ gss_release_oid_set(&min_stat, &mechlist);
+ return (maj_stat);
+}
+#endif /* !WITHOUT_KERBEROS */
+
+void gssd_terminate(int sig __unused)
+{
+
+#ifndef WITHOUT_KERBEROS
+ if (hostbased_initiator_cred != 0)
+ unlink(GSSD_CREDENTIAL_CACHE_FILE);
+#endif
+ gssd_syscall("");
+ exit(0);
+}
+
diff --git a/usr.sbin/gstat/Makefile b/usr.sbin/gstat/Makefile
new file mode 100644
index 0000000..1c71f22
--- /dev/null
+++ b/usr.sbin/gstat/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= gstat
+MAN= gstat.8
+LIBADD= devstat geom edit ncursesw
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/gstat/Makefile.depend b/usr.sbin/gstat/Makefile.depend
new file mode 100644
index 0000000..c8fd534
--- /dev/null
+++ b/usr.sbin/gstat/Makefile.depend
@@ -0,0 +1,26 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libdevstat \
+ lib/libedit \
+ lib/libelf \
+ lib/libexpat \
+ lib/libgeom \
+ lib/libkvm \
+ lib/libsbuf \
+ lib/ncurses/ncursesw \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/gstat/gstat.8 b/usr.sbin/gstat/gstat.8
new file mode 100644
index 0000000..6c0430e
--- /dev/null
+++ b/usr.sbin/gstat/gstat.8
@@ -0,0 +1,101 @@
+.\" Copyright (c) 2003 Giorgos Keramidas
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 3, 2014
+.Dt GSTAT 8
+.Os
+.Sh NAME
+.Nm gstat
+.Nd print statistics about GEOM disks
+.Sh SYNOPSIS
+.Nm
+.Op Fl abcdop
+.Op Fl f Ar filter
+.Op Fl I Ar interval
+.Sh DESCRIPTION
+The
+.Nm
+utility can be used to monitor I/O transactions of
+.Xr geom 4
+devices.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl a
+Only display providers that are at least 0.1% busy.
+.It Fl b
+Batch mode.
+Collect numbers, print and exit.
+Default if stdout is not a tty.
+.It Fl c
+Enable display of
+.Xr geom 4
+consumers too.
+The default is to show statistics only for
+.Xr geom 4
+producers.
+.It Fl d
+Enable display of statistics for delete
+.Pq Dv BIO_DELETE
+operations.
+.It Fl f Ar filter
+A regular expression that can be used to only show statistics for some
+devices.
+Only devices with the names matching
+.Ar filter
+will be displayed.
+The format of the regular expression is described in
+.Xr re_format 7 .
+.It Fl o
+Enable display of statistics for other operations
+.Pq Dv BIO_FLUSH .
+.It Fl I Ar interval
+Refresh the
+.Nm
+display every
+.Ar interval
+microseconds.
+Adding a suffix of
+.Cm s , ms ,
+or
+.Cm us
+(the default) indicates that the update interval is specified in
+seconds, milliseconds, or microseconds, respectively.
+.It Fl p
+Only display physical providers (those with rank of 1).
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr systat 1 ,
+.Xr geom 4 ,
+.Xr iostat 8 ,
+.Xr vmstat 8
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.Fx 5.0 .
diff --git a/usr.sbin/gstat/gstat.c b/usr.sbin/gstat/gstat.c
new file mode 100644
index 0000000..8be3775
--- /dev/null
+++ b/usr.sbin/gstat/gstat.c
@@ -0,0 +1,455 @@
+/*-
+ * Copyright (c) 2003 Poul-Henning Kamp
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/devicestat.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+
+#include <curses.h>
+#include <devstat.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <histedit.h>
+#include <libgeom.h>
+#include <paths.h>
+#include <regex.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+static int flag_a, flag_b, flag_c, flag_d, flag_o, flag_p;
+static int flag_I = 1000000;
+
+#define PRINTMSG(...) do { \
+ if (flag_b && !loop) \
+ printf(__VA_ARGS__); \
+ else if (!flag_b) \
+ printw(__VA_ARGS__); \
+ } while(0)
+
+static void usage(void);
+
+static const char*
+el_prompt(void)
+{
+
+ return ("Filter: ");
+}
+
+int
+main(int argc, char **argv)
+{
+ int error, i, quit;
+ int curx, cury, maxx, maxy, line_len, loop, max_flen;
+ struct devstat *gsp, *gsq;
+ void *sp, *sq;
+ double dt;
+ struct timespec tp, tq;
+ struct gmesh gmp;
+ struct gprovider *pp;
+ struct gconsumer *cp;
+ struct gident *gid;
+ regex_t f_re, tmp_f_re;
+ short cf, cb;
+ char *p;
+ char f_s[100], pf_s[100], tmp_f_s[100];
+ const char *line;
+ long double ld[13];
+ uint64_t u64;
+ EditLine *el;
+ History *hist;
+ HistEvent hist_ev;
+
+ hist = NULL;
+ el = NULL;
+ maxx = -1;
+ curx = -1;
+ loop = 1;
+ /* Turn on batch mode if output is not tty. */
+ if (!isatty(fileno(stdout)))
+ flag_b = 1;
+
+ f_s[0] = '\0';
+ while ((i = getopt(argc, argv, "abdcf:I:op")) != -1) {
+ switch (i) {
+ case 'a':
+ flag_a = 1;
+ break;
+ case 'b':
+ flag_b = 1;
+ break;
+ case 'c':
+ flag_c = 1;
+ break;
+ case 'd':
+ flag_d = 1;
+ break;
+ case 'f':
+ if (strlen(optarg) > sizeof(f_s) - 1)
+ errx(EX_USAGE, "Filter string too long");
+ if (regcomp(&f_re, optarg, REG_EXTENDED) != 0)
+ errx(EX_USAGE,
+ "Invalid filter - see re_format(7)");
+ strlcpy(f_s, optarg, sizeof(f_s));
+ break;
+ case 'o':
+ flag_o = 1;
+ break;
+ case 'I':
+ p = NULL;
+ i = strtoul(optarg, &p, 0);
+ if (p == optarg || errno == EINVAL ||
+ errno == ERANGE) {
+ errx(1, "Invalid argument to -I");
+ } else if (!strcmp(p, "s"))
+ i *= 1000000;
+ else if (!strcmp(p, "ms"))
+ i *= 1000;
+ else if (!strcmp(p, "us"))
+ i *= 1;
+ flag_I = i;
+ break;
+ case 'p':
+ flag_p = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 0)
+ usage();
+
+ i = geom_gettree(&gmp);
+ if (i != 0)
+ err(1, "geom_gettree = %d", i);
+ error = geom_stats_open();
+ if (error)
+ err(1, "geom_stats_open()");
+ sq = NULL;
+ sq = geom_stats_snapshot_get();
+ if (sq == NULL)
+ err(1, "geom_stats_snapshot()");
+ if (!flag_b) {
+ /* Setup curses */
+ initscr();
+ start_color();
+ use_default_colors();
+ pair_content(0, &cf, &cb);
+ init_pair(1, COLOR_GREEN, cb);
+ init_pair(2, COLOR_MAGENTA, cb);
+ init_pair(3, COLOR_RED, cb);
+ cbreak();
+ noecho();
+ nonl();
+ nodelay(stdscr, 1);
+ intrflush(stdscr, FALSE);
+ keypad(stdscr, TRUE);
+ /* Setup libedit */
+ hist = history_init();
+ if (hist == NULL)
+ err(EX_SOFTWARE, "history_init()");
+ history(hist, &hist_ev, H_SETSIZE, 100);
+ el = el_init("gstat", stdin, stdout, stderr);
+ if (el == NULL)
+ err(EX_SOFTWARE, "el_init");
+ el_set(el, EL_EDITOR, "emacs");
+ el_set(el, EL_SIGNAL, 1);
+ el_set(el, EL_HIST, history, hist);
+ el_set(el, EL_PROMPT, el_prompt);
+ if (f_s[0] != '\0')
+ history(hist, &hist_ev, H_ENTER, f_s);
+ }
+ geom_stats_snapshot_timestamp(sq, &tq);
+ for (quit = 0; !quit;) {
+ sp = geom_stats_snapshot_get();
+ if (sp == NULL)
+ err(1, "geom_stats_snapshot()");
+ geom_stats_snapshot_timestamp(sp, &tp);
+ dt = tp.tv_sec - tq.tv_sec;
+ dt += (tp.tv_nsec - tq.tv_nsec) * 1e-9;
+ tq = tp;
+
+ geom_stats_snapshot_reset(sp);
+ geom_stats_snapshot_reset(sq);
+ move(0,0);
+ PRINTMSG("dT: %5.3fs w: %.3fs", dt, (float)flag_I / 1000000);
+ if (f_s[0] != '\0') {
+ PRINTMSG(" filter: ");
+ if (!flag_b) {
+ getyx(stdscr, cury, curx);
+ getmaxyx(stdscr, maxy, maxx);
+ }
+ strlcpy(pf_s, f_s, sizeof(pf_s));
+ max_flen = maxx - curx - 1;
+ if ((int)strlen(f_s) > max_flen && max_flen >= 0) {
+ if (max_flen > 3)
+ pf_s[max_flen - 3] = '.';
+ if (max_flen > 2)
+ pf_s[max_flen - 2] = '.';
+ if (max_flen > 1)
+ pf_s[max_flen - 1] = '.';
+ pf_s[max_flen] = '\0';
+ }
+ PRINTMSG("%s", pf_s);
+ }
+ PRINTMSG("\n");
+ PRINTMSG(" L(q) ops/s ");
+ PRINTMSG(" r/s kBps ms/r ");
+ PRINTMSG(" w/s kBps ms/w ");
+ if (flag_d)
+ PRINTMSG(" d/s kBps ms/d ");
+ if (flag_o)
+ PRINTMSG(" o/s ms/o ");
+ PRINTMSG("%%busy Name\n");
+ for (;;) {
+ gsp = geom_stats_snapshot_next(sp);
+ gsq = geom_stats_snapshot_next(sq);
+ if (gsp == NULL || gsq == NULL)
+ break;
+ if (gsp->id == NULL)
+ continue;
+ gid = geom_lookupid(&gmp, gsp->id);
+ if (gid == NULL) {
+ geom_deletetree(&gmp);
+ i = geom_gettree(&gmp);
+ if (i != 0)
+ err(1, "geom_gettree = %d", i);
+ gid = geom_lookupid(&gmp, gsp->id);
+ }
+ if (gid == NULL)
+ continue;
+ if (gid->lg_what == ISCONSUMER && !flag_c)
+ continue;
+ if (flag_p && gid->lg_what == ISPROVIDER &&
+ ((struct gprovider *)(gid->lg_ptr))->lg_geom->lg_rank != 1)
+ continue;
+ /* Do not print past end of window */
+ if (!flag_b) {
+ getyx(stdscr, cury, curx);
+ if (curx > 0)
+ continue;
+ }
+ if ((gid->lg_what == ISPROVIDER
+ || gid->lg_what == ISCONSUMER) && f_s[0] != '\0') {
+ pp = gid->lg_ptr;
+ if ((regexec(&f_re, pp->lg_name, 0, NULL, 0)
+ != 0))
+ continue;
+ }
+ if (gsp->sequence0 != gsp->sequence1) {
+ PRINTMSG("*\n");
+ continue;
+ }
+ devstat_compute_statistics(gsp, gsq, dt,
+ DSM_QUEUE_LENGTH, &u64,
+ DSM_TRANSFERS_PER_SECOND, &ld[0],
+
+ DSM_TRANSFERS_PER_SECOND_READ, &ld[1],
+ DSM_MB_PER_SECOND_READ, &ld[2],
+ DSM_MS_PER_TRANSACTION_READ, &ld[3],
+
+ DSM_TRANSFERS_PER_SECOND_WRITE, &ld[4],
+ DSM_MB_PER_SECOND_WRITE, &ld[5],
+ DSM_MS_PER_TRANSACTION_WRITE, &ld[6],
+
+ DSM_BUSY_PCT, &ld[7],
+
+ DSM_TRANSFERS_PER_SECOND_FREE, &ld[8],
+ DSM_MB_PER_SECOND_FREE, &ld[9],
+ DSM_MS_PER_TRANSACTION_FREE, &ld[10],
+
+ DSM_TRANSFERS_PER_SECOND_OTHER, &ld[11],
+ DSM_MS_PER_TRANSACTION_OTHER, &ld[12],
+
+ DSM_NONE);
+
+ if (flag_a && ld[7] < 0.1) {
+ *gsq = *gsp;
+ continue;
+ }
+
+ PRINTMSG(" %4ju", (uintmax_t)u64);
+ PRINTMSG(" %6.0f", (double)ld[0]);
+ PRINTMSG(" %6.0f", (double)ld[1]);
+ PRINTMSG(" %6.0f", (double)ld[2] * 1024);
+ if (ld[3] > 1e3)
+ PRINTMSG(" %6.0f", (double)ld[3]);
+ else
+ PRINTMSG(" %6.1f", (double)ld[3]);
+ PRINTMSG(" %6.0f", (double)ld[4]);
+ PRINTMSG(" %6.0f", (double)ld[5] * 1024);
+ if (ld[6] > 1e3)
+ PRINTMSG(" %6.0f", (double)ld[6]);
+ else
+ PRINTMSG(" %6.1f", (double)ld[6]);
+
+ if (flag_d) {
+ PRINTMSG(" %6.0f", (double)ld[8]);
+ PRINTMSG(" %6.0f", (double)ld[9] * 1024);
+ if (ld[10] > 1e3)
+ PRINTMSG(" %6.0f", (double)ld[10]);
+ else
+ PRINTMSG(" %6.1f", (double)ld[10]);
+ }
+
+ if (flag_o) {
+ PRINTMSG(" %6.0f", (double)ld[11]);
+ if (ld[12] > 1e3)
+ PRINTMSG(" %6.0f", (double)ld[12]);
+ else
+ PRINTMSG(" %6.1f", (double)ld[12]);
+ }
+
+ if (ld[7] > 80)
+ i = 3;
+ else if (ld[7] > 50)
+ i = 2;
+ else
+ i = 1;
+ if (!flag_b)
+ attron(COLOR_PAIR(i));
+ PRINTMSG(" %6.1lf", (double)ld[7]);
+ if (!flag_b) {
+ attroff(COLOR_PAIR(i));
+ PRINTMSG("|");
+ } else
+ PRINTMSG(" ");
+ if (gid == NULL) {
+ PRINTMSG(" ??");
+ } else if (gid->lg_what == ISPROVIDER) {
+ pp = gid->lg_ptr;
+ PRINTMSG(" %s", pp->lg_name);
+ } else if (gid->lg_what == ISCONSUMER) {
+ cp = gid->lg_ptr;
+ PRINTMSG(" %s/%s/%s",
+ cp->lg_geom->lg_class->lg_name,
+ cp->lg_geom->lg_name,
+ cp->lg_provider->lg_name);
+ }
+ if (!flag_b)
+ clrtoeol();
+ PRINTMSG("\n");
+ *gsq = *gsp;
+ }
+ geom_stats_snapshot_free(sp);
+ if (flag_b) {
+ /* We loop extra to make sure we get the information. */
+ if (!loop)
+ break;
+ loop = 0;
+ usleep(flag_I);
+ continue;
+ }
+ getyx(stdscr, cury, curx);
+ getmaxyx(stdscr, maxy, maxx);
+ clrtobot();
+ if (maxy - 1 <= cury)
+ move(maxy - 1, 0);
+ refresh();
+ usleep(flag_I);
+ while((i = getch()) != ERR) {
+ switch (i) {
+ case '>':
+ flag_I *= 2;
+ break;
+ case '<':
+ flag_I /= 2;
+ if (flag_I < 1000)
+ flag_I = 1000;
+ break;
+ case 'c':
+ flag_c = !flag_c;
+ break;
+ case 'f':
+ move(0,0);
+ clrtoeol();
+ refresh();
+ line = el_gets(el, &line_len);
+ if (line == NULL)
+ err(1, "el_gets");
+ if (line_len > 1)
+ history(hist, &hist_ev, H_ENTER, line);
+ strlcpy(tmp_f_s, line, sizeof(f_s));
+ if ((p = strchr(tmp_f_s, '\n')) != NULL)
+ *p = '\0';
+ /*
+ * We have to clear since we messed up
+ * curses idea of the screen by using
+ * libedit.
+ */
+ clear();
+ refresh();
+ if (regcomp(&tmp_f_re, tmp_f_s, REG_EXTENDED)
+ != 0) {
+ move(0, 0);
+ printw("Invalid filter");
+ refresh();
+ sleep(1);
+ } else {
+ strlcpy(f_s, tmp_f_s, sizeof(f_s));
+ f_re = tmp_f_re;
+ }
+ break;
+ case 'F':
+ f_s[0] = '\0';
+ break;
+ case 'q':
+ quit = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (!flag_b) {
+ endwin();
+ el_end(el);
+ }
+ exit(EX_OK);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: gstat [-abcdp] [-f filter] [-I interval]\n");
+ exit(EX_USAGE);
+ /* NOTREACHED */
+}
diff --git a/usr.sbin/hyperv/Makefile b/usr.sbin/hyperv/Makefile
new file mode 100644
index 0000000..c11b341
--- /dev/null
+++ b/usr.sbin/hyperv/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+.include <bsd.own.mk>
+
+SUBDIR = tools
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/hyperv/Makefile.inc b/usr.sbin/hyperv/Makefile.inc
new file mode 100644
index 0000000..edb0129
--- /dev/null
+++ b/usr.sbin/hyperv/Makefile.inc
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+CFLAGS.gcc+= -Wno-uninitialized
+.include "../Makefile.inc"
diff --git a/usr.sbin/hyperv/tools/Makefile b/usr.sbin/hyperv/tools/Makefile
new file mode 100644
index 0000000..3cfc001
--- /dev/null
+++ b/usr.sbin/hyperv/tools/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+.include <bsd.own.mk>
+
+HV_KVP_DAEMON_DISTDIR?= ${.CURDIR}/../../../contrib/hyperv/tools
+.PATH: ${HV_KVP_DAEMON_DISTDIR}
+
+PROG= hv_kvp_daemon
+MAN= hv_kvp_daemon.8
+
+CFLAGS+= -I${.CURDIR}/../../../sys/dev/hyperv/utilities
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/hyperv/tools/Makefile.depend b/usr.sbin/hyperv/tools/Makefile.depend
new file mode 100644
index 0000000..54c1f6f
--- /dev/null
+++ b/usr.sbin/hyperv/tools/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/i2c/Makefile b/usr.sbin/i2c/Makefile
new file mode 100644
index 0000000..9f377e6
--- /dev/null
+++ b/usr.sbin/i2c/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= i2c
+MAN= i2c.8
+
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/i2c/Makefile.depend b/usr.sbin/i2c/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/i2c/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/i2c/i2c.8 b/usr.sbin/i2c/i2c.8
new file mode 100644
index 0000000..27f7621
--- /dev/null
+++ b/usr.sbin/i2c/i2c.8
@@ -0,0 +1,164 @@
+.\"
+.\" Copyright (C) 2008-2009 Semihalf, Michal Hajduk and Bartlomiej Sieka
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 January 23, 2009
+.Dt I2C 8
+.Os
+.Sh NAME
+.Nm i2c
+.Nd test I2C bus and slave devices
+.Sh SYNOPSIS
+.Nm
+.Cm -a Ar address
+.Op Fl f Ar device
+.Op Fl d Ar r|w
+.Op Fl w Ar 0|8|16
+.Op Fl o Ar offset
+.Op Fl c Ar count
+.Op Fl m Ar ss|rs|no
+.Op Fl b
+.Op Fl v
+.Nm
+.Cm -s
+.Op Fl f Ar device
+.Op Fl n Ar skip_addr
+.Op Fl v
+.Nm
+.Cm -r
+.Op Fl f Ar device
+.Op Fl v
+.Sh DESCRIPTION
+The
+.Nm
+utility can be used to perform raw data transfers (read or write) with devices
+on the I2C bus. It can also scan the bus for available devices and reset the
+I2C controller.
+.Pp
+The options are as follows:
+.Bl -tag -width ".Fl d Ar direction"
+.It Fl a Ar address
+7-bit address on the I2C device to operate on (hex).
+.It Fl b
+binary mode - when performing a read operation, the data read from the device
+is output in binary format on stdout; when doing a write, the binary data to
+be written to the device is read from stdin.
+.It Fl c Ar count
+number of bytes to transfer (dec).
+.It Fl d Ar r|w
+transfer direction: r - read, w - write.
+.It Fl f Ar device
+I2C bus to use (default is /dev/iic0).
+.It Fl m Ar ss|rs|no
+addressing mode, i.e., I2C bus operations performed after the offset for the
+transfer has been written to the device and before the actual read/write
+operation. rs - repeated start; ss - stop start; no - none.
+.It Fl n Ar skip_addr
+skip address - address(es) to be skipped during bus scan.
+There are two ways to specify addresses to ignore: by range 'a..b' or
+using selected addresses 'a:b:c'. This option is available only when "-s" is
+used.
+.It Fl o Ar offset
+offset within the device for data transfer (hex).
+.It Fl r
+reset the controller.
+.It Fl s
+scan the bus for devices.
+.It Fl v
+be verbose.
+.It Fl w Ar 0|8|16
+device addressing width (in bits).
+.El
+.Sh WARNINGS
+Great care must be taken when manipulating slave I2C devices with the
+.Nm
+utility. Often times important configuration data for the system is kept in
+non-volatile but write enabled memories located on the I2C bus, for example
+Ethernet hardware addresses, RAM module parameters (SPD), processor reset
+configuration word etc.
+.Pp
+It is very easy to render the whole system unusable when such configuration
+data is deleted or altered, so use the
+.Dq -d w
+(write) command only if you know exactly what you are doing.
+.Pp
+Also avoid ungraceful interrupting of an ongoing transaction on the I2C bus,
+as it can lead to potentially dangerous effects. Consider the following
+scenario: when the host CPU is reset (for whatever reason) in the middle of a
+started I2C transaction, the I2C slave device could be left in write mode
+waiting for data or offset to arrive. When the CPU reinitializes itself and
+talks to this I2C slave device again, the commands and other control info it
+sends are treated by the slave device as data or offset it was waiting for,
+and there's great potential for corruption if such a write is performed.
+.Sh EXAMPLES
+.Bl -bullet
+.It
+Scan the default bus (/dev/iic0) for devices:
+.Pp
+i2c -s
+.It
+Scan the default bus (/dev/iic0) for devices and skip addresses 0x56 and
+0x45.
+.Pp
+i2c -s -n 0x56:0x45
+.It
+Scan the default bus (/dev/iic0) for devices and skip address range
+0x34 to 0x56.
+.Pp
+i2c -s -n 0x34..0x56
+.It
+Read 8 bytes of data from device at address 0x56 (e.g., an EEPROM):
+.Pp
+i2c -a 0x56 -d r -c 8
+.It
+Write 16 bytes of data from file data.bin to device 0x56 at offset 0x10:
+.Pp
+i2c -a 0x56 -d w -c 16 -o 0x10 -b < data.bin
+.It
+Copy 4 bytes between two EEPROMs (0x56 on /dev/iic1 to 0x57 on /dev/iic0):
+.Pp
+i2c -a 0x56 -f /dev/iic1 -d r -c 0x4 -b | i2c -a 0x57 -f /dev/iic0 -d w -c 4 -b
+.It
+Reset the controller:
+.Pp
+i2c -f /dev/iic1 -r
+.El
+.Sh SEE ALSO
+.Xr iic 4 ,
+.Xr iicbus 4
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 8.0 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility and this manual page were written by
+.An Bartlomiej Sieka Aq Mt tur@semihalf.com
+and
+.An Michal Hajduk Aq Mt mih@semihalf.com .
diff --git a/usr.sbin/i2c/i2c.c b/usr.sbin/i2c/i2c.c
new file mode 100644
index 0000000..3021b86
--- /dev/null
+++ b/usr.sbin/i2c/i2c.c
@@ -0,0 +1,689 @@
+/*-
+ * Copyright (C) 2008-2009 Semihalf, Michal Hajduk and Bartlomiej Sieka
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <err.h>
+#include <errno.h>
+#include <sysexits.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+
+#include <dev/iicbus/iic.h>
+
+#define I2C_DEV "/dev/iic0"
+#define I2C_MODE_NOTSET 0
+#define I2C_MODE_NONE 1
+#define I2C_MODE_STOP_START 2
+#define I2C_MODE_REPEATED_START 3
+
+struct options {
+ int width;
+ int count;
+ int verbose;
+ int addr_set;
+ int binary;
+ int scan;
+ int skip;
+ int reset;
+ int mode;
+ char dir;
+ uint32_t addr;
+ uint32_t off;
+};
+
+struct skip_range {
+ int start;
+ int end;
+};
+
+__dead2 static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: %s -a addr [-f device] [-d [r|w]] [-o offset] "
+ "[-w [0|8|16]] [-c count] [-m [ss|rs|no]] [-b] [-v]\n",
+ getprogname());
+ fprintf(stderr, " %s -s [-f device] [-n skip_addr] -v\n",
+ getprogname());
+ fprintf(stderr, " %s -r [-f device] -v\n", getprogname());
+ exit(EX_USAGE);
+}
+
+static struct skip_range
+skip_get_range(char *skip_addr)
+{
+ struct skip_range addr_range;
+ char *token;
+
+ addr_range.start = 0;
+ addr_range.end = 0;
+
+ token = strsep(&skip_addr, "..");
+ if (token) {
+ addr_range.start = strtoul(token, 0, 16);
+ token = strsep(&skip_addr, "..");
+ if ((token != NULL) && !atoi(token)) {
+ token = strsep(&skip_addr, "..");
+ if (token)
+ addr_range.end = strtoul(token, 0, 16);
+ }
+ }
+
+ return (addr_range);
+}
+
+/* Parse the string to get hex 7 bits addresses */
+static int
+skip_get_tokens(char *skip_addr, int *sk_addr, int max_index)
+{
+ char *token;
+ int i;
+
+ for (i = 0; i < max_index; i++) {
+ token = strsep(&skip_addr, ":");
+ if (token == NULL)
+ break;
+ sk_addr[i] = strtoul(token, 0, 16);
+ }
+ return (i);
+}
+
+static int
+scan_bus(struct iiccmd cmd, char *dev, int skip, char *skip_addr)
+{
+ struct skip_range addr_range = { 0, 0 };
+ int *tokens, fd, error, i, index, j;
+ int len = 0, do_skip = 0, no_range = 1;
+
+ fd = open(dev, O_RDWR);
+ if (fd == -1) {
+ fprintf(stderr, "Error opening I2C controller (%s) for "
+ "scanning: %s\n", dev, strerror(errno));
+ return (EX_NOINPUT);
+ }
+
+ if (skip) {
+ len = strlen(skip_addr);
+ if (strstr(skip_addr, "..") != NULL) {
+ addr_range = skip_get_range(skip_addr);
+ no_range = 0;
+ } else {
+ tokens = (int *)malloc((len / 2 + 1) * sizeof(int));
+ if (tokens == NULL) {
+ fprintf(stderr, "Error allocating tokens "
+ "buffer\n");
+ error = -1;
+ goto out;
+ }
+ index = skip_get_tokens(skip_addr, tokens,
+ len / 2 + 1);
+ }
+
+ if (!no_range && (addr_range.start > addr_range.end)) {
+ fprintf(stderr, "Skip address out of range\n");
+ error = -1;
+ goto out;
+ }
+ }
+
+ printf("Scanning I2C devices on %s: ", dev);
+ for (i = 1; i < 127; i++) {
+
+ if (skip && ( addr_range.start < addr_range.end)) {
+ if (i >= addr_range.start && i <= addr_range.end)
+ continue;
+
+ } else if (skip && no_range)
+ for (j = 0; j < index; j++) {
+ if (tokens[j] == i) {
+ do_skip = 1;
+ break;
+ }
+ }
+
+ if (do_skip) {
+ do_skip = 0;
+ continue;
+ }
+
+ cmd.slave = i << 1;
+ cmd.last = 1;
+ cmd.count = 0;
+ error = ioctl(fd, I2CRSTCARD, &cmd);
+ if (error)
+ goto out;
+
+ cmd.slave = i << 1;
+ cmd.last = 1;
+ error = ioctl(fd, I2CSTART, &cmd);
+ if (!error)
+ printf("%x ", i);
+ cmd.slave = i << 1;
+ cmd.last = 1;
+ error = ioctl(fd, I2CSTOP, &cmd);
+ }
+ printf("\n");
+
+ error = ioctl(fd, I2CRSTCARD, &cmd);
+out:
+ close(fd);
+ if (skip && no_range)
+ free(tokens);
+
+ if (error) {
+ fprintf(stderr, "Error scanning I2C controller (%s): %s\n",
+ dev, strerror(errno));
+ return (EX_NOINPUT);
+ } else
+ return (EX_OK);
+}
+
+static int
+reset_bus(struct iiccmd cmd, char *dev)
+{
+ int fd, error;
+
+ fd = open(dev, O_RDWR);
+ if (fd == -1) {
+ fprintf(stderr, "Error opening I2C controller (%s) for "
+ "resetting: %s\n", dev, strerror(errno));
+ return (EX_NOINPUT);
+ }
+
+ printf("Resetting I2C controller on %s: ", dev);
+ error = ioctl(fd, I2CRSTCARD, &cmd);
+ close (fd);
+
+ if (error) {
+ printf("error: %s\n", strerror(errno));
+ return (EX_IOERR);
+ } else {
+ printf("OK\n");
+ return (EX_OK);
+ }
+}
+
+static char *
+prepare_buf(int size, uint32_t off)
+{
+ char *buf;
+
+ buf = malloc(size);
+ if (buf == NULL)
+ return (buf);
+
+ if (size == 1)
+ buf[0] = off & 0xff;
+ else if (size == 2) {
+ buf[0] = (off >> 8) & 0xff;
+ buf[1] = off & 0xff;
+ }
+
+ return (buf);
+}
+
+static int
+i2c_write(char *dev, struct options i2c_opt, char *i2c_buf)
+{
+ struct iiccmd cmd;
+ int ch, i, error, fd, bufsize;
+ char *err_msg, *buf;
+
+ /*
+ * Read data to be written to the chip from stdin
+ */
+ if (i2c_opt.verbose && !i2c_opt.binary)
+ fprintf(stderr, "Enter %u bytes of data: ", i2c_opt.count);
+
+ for (i = 0; i < i2c_opt.count; i++) {
+ ch = getchar();
+ if (ch == EOF) {
+ free(i2c_buf);
+ err(1, "not enough data, exiting\n");
+ }
+ i2c_buf[i] = ch;
+ }
+
+ fd = open(dev, O_RDWR);
+ if (fd == -1) {
+ free(i2c_buf);
+ err(1, "open failed");
+ }
+
+ cmd.slave = i2c_opt.addr;
+ error = ioctl(fd, I2CSTART, &cmd);
+ if (error == -1) {
+ err_msg = "ioctl: error sending start condition";
+ goto err1;
+ }
+
+ if (i2c_opt.width) {
+ bufsize = i2c_opt.width / 8;
+ buf = prepare_buf(bufsize, i2c_opt.off);
+ if (buf == NULL) {
+ err_msg = "error: offset malloc";
+ goto err1;
+ }
+ }
+
+ switch(i2c_opt.mode) {
+ case I2C_MODE_STOP_START:
+ /*
+ * Write offset where the data will go
+ */
+ if (i2c_opt.width) {
+ cmd.count = bufsize;
+ cmd.buf = buf;
+ error = ioctl(fd, I2CWRITE, &cmd);
+ free(buf);
+ if (error == -1) {
+ err_msg = "ioctl: error when write offset";
+ goto err1;
+ }
+ }
+
+ error = ioctl(fd, I2CSTOP, &cmd);
+ if (error == -1) {
+ err_msg = "ioctl: error sending stop condition";
+ goto err2;
+ }
+ cmd.slave = i2c_opt.addr;
+ error = ioctl(fd, I2CSTART, &cmd);
+ if (error == -1) {
+ err_msg = "ioctl: error sending start condition";
+ goto err1;
+ }
+
+ /*
+ * Write the data
+ */
+ cmd.count = i2c_opt.count;
+ cmd.buf = i2c_buf;
+ cmd.last = 0;
+ error = ioctl(fd, I2CWRITE, &cmd);
+ if (error == -1) {
+ err_msg = "ioctl: error when write";
+ goto err1;
+ }
+ break;
+
+ case I2C_MODE_REPEATED_START:
+ /*
+ * Write offset where the data will go
+ */
+ if (i2c_opt.width) {
+ cmd.count = bufsize;
+ cmd.buf = buf;
+ error = ioctl(fd, I2CWRITE, &cmd);
+ free(buf);
+ if (error == -1) {
+ err_msg = "ioctl: error when write offset";
+ goto err1;
+ }
+ }
+
+ cmd.slave = i2c_opt.addr;
+ error = ioctl(fd, I2CRPTSTART, &cmd);
+ if (error == -1) {
+ err_msg = "ioctl: error sending repeated start "
+ "condition";
+ goto err1;
+ }
+
+ /*
+ * Write the data
+ */
+ cmd.count = i2c_opt.count;
+ cmd.buf = i2c_buf;
+ cmd.last = 0;
+ error = ioctl(fd, I2CWRITE, &cmd);
+ if (error == -1) {
+ err_msg = "ioctl: error when write";
+ goto err1;
+ }
+ break;
+
+ case I2C_MODE_NONE: /* fall through */
+ default:
+ buf = realloc(buf, bufsize + i2c_opt.count);
+ if (buf == NULL) {
+ err_msg = "error: data malloc";
+ goto err1;
+ }
+
+ memcpy(buf + bufsize, i2c_buf, i2c_opt.count);
+ /*
+ * Write offset and data
+ */
+ cmd.count = bufsize + i2c_opt.count;
+ cmd.buf = buf;
+ cmd.last = 0;
+ error = ioctl(fd, I2CWRITE, &cmd);
+ free(buf);
+ if (error == -1) {
+ err_msg = "ioctl: error when write";
+ goto err1;
+ }
+ break;
+ }
+ cmd.slave = i2c_opt.addr;
+ error = ioctl(fd, I2CSTOP, &cmd);
+ if (error == -1) {
+ err_msg = "ioctl: error sending stop condition";
+ goto err2;
+ }
+
+ close(fd);
+ return (0);
+
+err1:
+ cmd.slave = i2c_opt.addr;
+ error = ioctl(fd, I2CSTOP, &cmd);
+ if (error == -1)
+ fprintf(stderr, "error sending stop condtion\n");
+err2:
+ if (err_msg)
+ fprintf(stderr, "%s", err_msg);
+
+ close(fd);
+ return (1);
+}
+
+static int
+i2c_read(char *dev, struct options i2c_opt, char *i2c_buf)
+{
+ struct iiccmd cmd;
+ int i, fd, error, bufsize;
+ char *err_msg, data = 0, *buf;
+
+ fd = open(dev, O_RDWR);
+ if (fd == -1)
+ err(1, "open failed");
+
+ bzero(&cmd, sizeof(cmd));
+
+ if (i2c_opt.width) {
+ cmd.slave = i2c_opt.addr;
+ cmd.count = 1;
+ cmd.last = 0;
+ cmd.buf = &data;
+ error = ioctl(fd, I2CSTART, &cmd);
+ if (error == -1) {
+ err_msg = "ioctl: error sending start condition";
+ goto err1;
+ }
+ bufsize = i2c_opt.width / 8;
+ buf = prepare_buf(bufsize, i2c_opt.off);
+ if (buf == NULL) {
+ err_msg = "error: offset malloc";
+ goto err1;
+ }
+
+ cmd.count = bufsize;
+ cmd.buf = buf;
+ cmd.last = 0;
+ error = ioctl(fd, I2CWRITE, &cmd);
+ free(buf);
+ if (error == -1) {
+ err_msg = "ioctl: error when write offset";
+ goto err1;
+ }
+
+ if (i2c_opt.mode == I2C_MODE_STOP_START) {
+ cmd.slave = i2c_opt.addr;
+ error = ioctl(fd, I2CSTOP, &cmd);
+ if (error == -1) {
+ err_msg = "error sending stop condtion\n";
+ goto err2;
+ }
+ }
+ }
+ cmd.slave = i2c_opt.addr;
+ cmd.count = 1;
+ cmd.last = 0;
+ cmd.buf = &data;
+ if (i2c_opt.mode == I2C_MODE_STOP_START) {
+ error = ioctl(fd, I2CSTART, &cmd);
+ if (error == -1) {
+ err_msg = "ioctl: error sending start condition";
+ goto err1;
+ }
+ } else if (i2c_opt.mode == I2C_MODE_REPEATED_START) {
+ error = ioctl(fd, I2CRPTSTART, &cmd);
+ if (error == -1) {
+ err_msg = "ioctl: error sending repeated start "
+ "condition";
+ goto err1;
+ }
+ }
+ error = ioctl(fd, I2CSTOP, &cmd);
+ if (error == -1) {
+ err_msg = "error sending stop condtion\n";
+ goto err2;
+ }
+
+ for (i = 0; i < i2c_opt.count; i++) {
+ error = read(fd, &i2c_buf[i], 1);
+ if (error == -1) {
+ err_msg = "ioctl: error while reading";
+ goto err1;
+ }
+ }
+
+ close(fd);
+ return (0);
+
+err1:
+ cmd.slave = i2c_opt.addr;
+ error = ioctl(fd, I2CSTOP, &cmd);
+ if (error == -1)
+ fprintf(stderr, "error sending stop condtion\n");
+err2:
+ if (err_msg)
+ fprintf(stderr, "%s", err_msg);
+
+ close(fd);
+ return (1);
+}
+
+int
+main(int argc, char** argv)
+{
+ struct iiccmd cmd;
+ struct options i2c_opt;
+ char *dev, *skip_addr, *i2c_buf;
+ int error, chunk_size, i, j, ch;
+
+ errno = 0;
+ error = 0;
+
+ /* Line-break the output every chunk_size bytes */
+ chunk_size = 16;
+
+ dev = I2C_DEV;
+
+ /* Default values */
+ i2c_opt.addr_set = 0;
+ i2c_opt.off = 0;
+ i2c_opt.verbose = 0;
+ i2c_opt.dir = 'r'; /* direction = read */
+ i2c_opt.width = 8;
+ i2c_opt.count = 1;
+ i2c_opt.binary = 0; /* ASCII text output */
+ i2c_opt.scan = 0; /* no bus scan */
+ i2c_opt.skip = 0; /* scan all addresses */
+ i2c_opt.reset = 0; /* no bus reset */
+ i2c_opt.mode = I2C_MODE_NOTSET;
+
+ while ((ch = getopt(argc, argv, "a:f:d:o:w:c:m:n:sbvrh")) != -1) {
+ switch(ch) {
+ case 'a':
+ i2c_opt.addr = (strtoul(optarg, 0, 16) << 1);
+ if (i2c_opt.addr == 0 && errno == EINVAL)
+ i2c_opt.addr_set = 0;
+ else
+ i2c_opt.addr_set = 1;
+ break;
+ case 'f':
+ dev = optarg;
+ break;
+ case 'd':
+ i2c_opt.dir = optarg[0];
+ break;
+ case 'o':
+ i2c_opt.off = strtoul(optarg, 0, 16);
+ if (i2c_opt.off == 0 && errno == EINVAL)
+ error = 1;
+ break;
+ case 'w':
+ i2c_opt.width = atoi(optarg);
+ break;
+ case 'c':
+ i2c_opt.count = atoi(optarg);
+ break;
+ case 'm':
+ if (!strcmp(optarg, "no"))
+ i2c_opt.mode = I2C_MODE_NONE;
+ else if (!strcmp(optarg, "ss"))
+ i2c_opt.mode = I2C_MODE_STOP_START;
+ else if (!strcmp(optarg, "rs"))
+ i2c_opt.mode = I2C_MODE_REPEATED_START;
+ else
+ usage();
+ break;
+ case 'n':
+ i2c_opt.skip = 1;
+ skip_addr = optarg;
+ break;
+ case 's':
+ i2c_opt.scan = 1;
+ break;
+ case 'b':
+ i2c_opt.binary = 1;
+ break;
+ case 'v':
+ i2c_opt.verbose = 1;
+ break;
+ case 'r':
+ i2c_opt.reset = 1;
+ break;
+ case 'h':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Set default mode if option -m is not specified */
+ if (i2c_opt.mode == I2C_MODE_NOTSET) {
+ if (i2c_opt.dir == 'r')
+ i2c_opt.mode = I2C_MODE_STOP_START;
+ else if (i2c_opt.dir == 'w')
+ i2c_opt.mode = I2C_MODE_NONE;
+ }
+
+ /* Basic sanity check of command line arguments */
+ if (i2c_opt.scan) {
+ if (i2c_opt.addr_set)
+ usage();
+ } else if (i2c_opt.reset) {
+ if (i2c_opt.addr_set)
+ usage();
+ } else if (error) {
+ usage();
+ } else if ((i2c_opt.dir == 'r' || i2c_opt.dir == 'w')) {
+ if ((i2c_opt.addr_set == 0) ||
+ !(i2c_opt.width == 0 || i2c_opt.width == 8 ||
+ i2c_opt.width == 16))
+ usage();
+ }
+
+ if (i2c_opt.verbose)
+ fprintf(stderr, "dev: %s, addr: 0x%x, r/w: %c, "
+ "offset: 0x%02x, width: %u, count: %u\n", dev,
+ i2c_opt.addr >> 1, i2c_opt.dir, i2c_opt.off,
+ i2c_opt.width, i2c_opt.count);
+
+ if (i2c_opt.scan)
+ exit(scan_bus(cmd, dev, i2c_opt.skip, skip_addr));
+
+ if (i2c_opt.reset)
+ exit(reset_bus(cmd, dev));
+
+ i2c_buf = malloc(i2c_opt.count);
+ if (i2c_buf == NULL)
+ err(1, "data malloc");
+
+ if (i2c_opt.dir == 'w') {
+ error = i2c_write(dev, i2c_opt, i2c_buf);
+ if (error) {
+ free(i2c_buf);
+ return (1);
+ }
+ }
+ if (i2c_opt.dir == 'r') {
+ error = i2c_read(dev, i2c_opt, i2c_buf);
+ if (error) {
+ free(i2c_buf);
+ return (1);
+ }
+ }
+
+ if (i2c_opt.verbose)
+ fprintf(stderr, "\nData %s (hex):\n", i2c_opt.dir == 'r' ?
+ "read" : "written");
+
+ i = 0;
+ j = 0;
+ while (i < i2c_opt.count) {
+ if (i2c_opt.verbose || (i2c_opt.dir == 'r' &&
+ !i2c_opt.binary))
+ fprintf (stderr, "%02hhx ", i2c_buf[i++]);
+
+ if (i2c_opt.dir == 'r' && i2c_opt.binary) {
+ fprintf(stdout, "%c", i2c_buf[j++]);
+ if(!i2c_opt.verbose)
+ i++;
+ }
+ if (!i2c_opt.verbose && (i2c_opt.dir == 'w'))
+ break;
+ if ((i % chunk_size) == 0)
+ fprintf(stderr, "\n");
+ }
+ if ((i % chunk_size) != 0)
+ fprintf(stderr, "\n");
+
+ free(i2c_buf);
+ return (0);
+}
diff --git a/usr.sbin/ifmcstat/Makefile b/usr.sbin/ifmcstat/Makefile
new file mode 100644
index 0000000..adf1304
--- /dev/null
+++ b/usr.sbin/ifmcstat/Makefile
@@ -0,0 +1,18 @@
+# @(#)Makefile 8.1 (Berkeley) 6/5/93
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= ifmcstat
+SRCS= ifmcstat.c printb.c
+
+MAN= ifmcstat.8
+BINMODE= 555
+
+WARNS?= 2
+
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+=-DINET6
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ifmcstat/Makefile.depend b/usr.sbin/ifmcstat/Makefile.depend
new file mode 100644
index 0000000..54c1f6f
--- /dev/null
+++ b/usr.sbin/ifmcstat/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ifmcstat/ifmcstat.8 b/usr.sbin/ifmcstat/ifmcstat.8
new file mode 100644
index 0000000..70b9791
--- /dev/null
+++ b/usr.sbin/ifmcstat/ifmcstat.8
@@ -0,0 +1,129 @@
+.\" $KAME: ifmcstat.8,v 1.6 2002/10/31 04:23:43 suz Exp $
+.\"
+.\" Copyright (c) 2007-2009 Bruce Simpson.
+.\" 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 May 27, 2009
+.Dt IFMCSTAT 8
+.Os
+.Sh NAME
+.Nm ifmcstat
+.Nd dump multicast group management statistics per interface
+.Sh SYNOPSIS
+.Nm
+.Op Fl i Ar interface
+.Op Fl f Ar address-family
+.Op Fl v
+.Op Fl K
+.Op Fl M Ar core
+.Op Fl N Ar system
+.\"
+.Sh DESCRIPTION
+The
+.Nm
+command dumps multicast group information from the kernel.
+.Pp
+The following options are supported:
+.Bl -tag -width Fl
+.It Fl i Ar interface
+specifies the interface to be displayed.
+.It Fl f Ar address-family
+specifies the address family to be displayed;
+.Ar inet ,
+.Ar inet6
+and
+.Ar link
+are supported.
+.It Fl v
+specifies that link-layer memberships should be printed;
+they are suppressed by default.
+It may not be specified for
+.Fl f Ar link .
+Source lists for each group will also be printed.
+.Pp
+If specified twice, and
+.Xr kvm 3
+is in use, the control plane timers for each interface
+and the source list counters for each group
+will also be printed.
+.El
+.Pp
+The following options are only available if
+.Nm
+has been built with support for
+.Xr kvm 3 :
+.Bl -tag -width Fl
+.It Fl K
+attempts to use
+.Xr kvm 3
+to retrieve the multicast group information.
+.It Fl M Ar core
+extracts values associated with the name list from the specified core,
+instead of the default
+.Pa /dev/kmem .
+.It Fl N Ar system
+extracts the name list from the specified kernel instead of the
+default, which is the kernel image the system has booted from.
+.El
+.Sh IMPLEMENTATION NOTES
+.Nm
+will always print the embedded scope IDs of IPv6 multicast group
+memberships.
+This is because memberships are always scoped to an interface.
+.Pp
+When run with the
+.Fl v
+option,
+.Nm
+may print multicast MAC addresses twice if they are
+referenced by a layer 3 protocol.
+.Pp
+When run with
+.Xr kvm 3
+support,
+the names of all interfaces configured in the system will be
+printed in the first column of output, even if no multicast
+group memberships are present on those interfaces.
+The output may also be slightly different, as the kernel
+data structures are being traversed with minimal post-processing
+of the output.
+.Pp
+When built without
+.Xr kvm 3
+support, the information displayed by
+.Nm
+is more limited.
+This support is recommended for debugging purposes.
+It requires super-user privilege if used to inspect a running kernel.
+.Sh SEE ALSO
+.Xr netstat 1 ,
+.Xr getifaddrs 3 ,
+.Xr getifmaddrs 3 ,
+.Xr kvm 3
diff --git a/usr.sbin/ifmcstat/ifmcstat.c b/usr.sbin/ifmcstat/ifmcstat.c
new file mode 100644
index 0000000..4f3f444
--- /dev/null
+++ b/usr.sbin/ifmcstat/ifmcstat.c
@@ -0,0 +1,1246 @@
+/* $KAME: ifmcstat.c,v 1.48 2006/11/15 05:13:59 itojun Exp $ */
+
+/*
+ * Copyright (c) 2007-2009 Bruce Simpson.
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+
+#include <net/if.h>
+#include <net/if_types.h>
+#include <net/if_dl.h>
+#include <net/route.h>
+
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/igmp.h>
+#include <netinet/if_ether.h>
+#include <netinet/igmp_var.h>
+
+#ifdef INET6
+#include <netinet/icmp6.h>
+#include <netinet6/mld6_var.h>
+#endif /* INET6 */
+
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <stddef.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <ifaddrs.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#ifdef KVM
+/*
+ * Currently the KVM build is broken. To be fixed it requires uncovering
+ * large amount of _KERNEL code in include files, and it is also very
+ * tentative to internal kernel ABI changes. If anyone wishes to restore
+ * it, please move it out of src/usr.sbin to src/tools/tools.
+ */
+#include <kvm.h>
+#include <nlist.h>
+#endif
+
+/* XXX: This file currently assumes INET support in the base system. */
+#ifndef INET
+#define INET
+#endif
+
+extern void printb(const char *, unsigned int, const char *);
+
+union sockunion {
+ struct sockaddr_storage ss;
+ struct sockaddr sa;
+ struct sockaddr_dl sdl;
+#ifdef INET
+ struct sockaddr_in sin;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 sin6;
+#endif
+};
+typedef union sockunion sockunion_t;
+
+uint32_t ifindex = 0;
+int af = AF_UNSPEC;
+#ifdef WITH_KVM
+int Kflag = 0;
+#endif
+int vflag = 0;
+
+#define sa_dl_equal(a1, a2) \
+ ((((struct sockaddr_dl *)(a1))->sdl_len == \
+ ((struct sockaddr_dl *)(a2))->sdl_len) && \
+ (bcmp(LLADDR((struct sockaddr_dl *)(a1)), \
+ LLADDR((struct sockaddr_dl *)(a2)), \
+ ((struct sockaddr_dl *)(a1))->sdl_alen) == 0))
+
+/*
+ * Most of the code in this utility is to support the use of KVM for
+ * post-mortem debugging of the multicast code.
+ */
+#ifdef WITH_KVM
+
+#ifdef INET
+static void if_addrlist(struct ifaddr *);
+static struct in_multi *
+ in_multientry(struct in_multi *);
+#endif /* INET */
+
+#ifdef INET6
+static void if6_addrlist(struct ifaddr *);
+static struct in6_multi *
+ in6_multientry(struct in6_multi *);
+#endif /* INET6 */
+
+static void kread(u_long, void *, int);
+static void ll_addrlist(struct ifaddr *);
+
+static int ifmcstat_kvm(const char *kernel, const char *core);
+
+#define KREAD(addr, buf, type) \
+ kread((u_long)addr, (void *)buf, sizeof(type))
+
+kvm_t *kvmd;
+struct nlist nl[] = {
+ { "_ifnet", 0, 0, 0, 0, },
+ { "", 0, 0, 0, 0, },
+};
+#define N_IFNET 0
+
+#endif /* WITH_KVM */
+
+static int ifmcstat_getifmaddrs(void);
+#ifdef INET
+static void in_ifinfo(struct igmp_ifinfo *);
+static const char * inm_mode(u_int mode);
+#endif
+#ifdef INET6
+static void in6_ifinfo(struct mld_ifinfo *);
+static const char * inet6_n2a(struct in6_addr *, uint32_t);
+#endif
+int main(int, char **);
+
+static void
+usage()
+{
+
+ fprintf(stderr,
+ "usage: ifmcstat [-i interface] [-f address family]"
+ " [-v]"
+#ifdef WITH_KVM
+ " [-K] [-M core] [-N system]"
+#endif
+ "\n");
+ exit(EX_USAGE);
+}
+
+static const char *options = "i:f:vM:N:"
+#ifdef WITH_KVM
+ "K"
+#endif
+ ;
+
+int
+main(int argc, char **argv)
+{
+ int c, error;
+#ifdef WITH_KVM
+ const char *kernel = NULL;
+ const char *core = NULL;
+#endif
+
+ while ((c = getopt(argc, argv, options)) != -1) {
+ switch (c) {
+ case 'i':
+ if ((ifindex = if_nametoindex(optarg)) == 0) {
+ fprintf(stderr, "%s: unknown interface\n",
+ optarg);
+ exit(EX_NOHOST);
+ }
+ break;
+
+ case 'f':
+#ifdef INET
+ if (strcmp(optarg, "inet") == 0) {
+ af = AF_INET;
+ break;
+ }
+#endif
+#ifdef INET6
+ if (strcmp(optarg, "inet6") == 0) {
+ af = AF_INET6;
+ break;
+ }
+#endif
+ if (strcmp(optarg, "link") == 0) {
+ af = AF_LINK;
+ break;
+ }
+ fprintf(stderr, "%s: unknown address family\n", optarg);
+ exit(EX_USAGE);
+ /*NOTREACHED*/
+ break;
+
+#ifdef WITH_KVM
+ case 'K':
+ ++Kflag;
+ break;
+#endif
+
+ case 'v':
+ ++vflag;
+ break;
+
+#ifdef WITH_KVM
+ case 'M':
+ core = strdup(optarg);
+ break;
+
+ case 'N':
+ kernel = strdup(optarg);
+ break;
+#endif
+
+ default:
+ usage();
+ break;
+ /*NOTREACHED*/
+ }
+ }
+
+ if (af == AF_LINK && vflag)
+ usage();
+
+#ifdef WITH_KVM
+ if (Kflag)
+ error = ifmcstat_kvm(kernel, core);
+ /*
+ * If KVM failed, and user did not explicitly specify a core file,
+ * or force KVM backend to be disabled, try the sysctl backend.
+ */
+ if (!Kflag || (error != 0 && (core == NULL && kernel == NULL)))
+#endif
+ error = ifmcstat_getifmaddrs();
+ if (error != 0)
+ exit(EX_OSERR);
+
+ exit(EX_OK);
+ /*NOTREACHED*/
+}
+
+#ifdef INET
+
+static void
+in_ifinfo(struct igmp_ifinfo *igi)
+{
+
+ printf("\t");
+ switch (igi->igi_version) {
+ case IGMP_VERSION_1:
+ case IGMP_VERSION_2:
+ case IGMP_VERSION_3:
+ printf("igmpv%d", igi->igi_version);
+ break;
+ default:
+ printf("igmpv?(%d)", igi->igi_version);
+ break;
+ }
+ if (igi->igi_flags)
+ printb(" flags", igi->igi_flags, "\020\1SILENT\2LOOPBACK");
+ if (igi->igi_version == IGMP_VERSION_3) {
+ printf(" rv %u qi %u qri %u uri %u",
+ igi->igi_rv, igi->igi_qi, igi->igi_qri, igi->igi_uri);
+ }
+ if (vflag >= 2) {
+ printf(" v1timer %u v2timer %u v3timer %u",
+ igi->igi_v1_timer, igi->igi_v2_timer, igi->igi_v3_timer);
+ }
+ printf("\n");
+}
+
+static const char *inm_modes[] = {
+ "undefined",
+ "include",
+ "exclude",
+};
+
+static const char *
+inm_mode(u_int mode)
+{
+
+ if (mode >= MCAST_UNDEFINED && mode <= MCAST_EXCLUDE)
+ return (inm_modes[mode]);
+ return (NULL);
+}
+
+#endif /* INET */
+
+#ifdef WITH_KVM
+
+static int
+ifmcstat_kvm(const char *kernel, const char *core)
+{
+ char buf[_POSIX2_LINE_MAX], ifname[IFNAMSIZ];
+ struct ifnet *ifp, *nifp, ifnet;
+
+ if ((kvmd = kvm_openfiles(kernel, core, NULL, O_RDONLY, buf)) ==
+ NULL) {
+ perror("kvm_openfiles");
+ return (-1);
+ }
+ if (kvm_nlist(kvmd, nl) < 0) {
+ perror("kvm_nlist");
+ return (-1);
+ }
+ if (nl[N_IFNET].n_value == 0) {
+ printf("symbol %s not found\n", nl[N_IFNET].n_name);
+ return (-1);
+ }
+ KREAD(nl[N_IFNET].n_value, &ifp, struct ifnet *);
+ while (ifp) {
+ KREAD(ifp, &ifnet, struct ifnet);
+ nifp = ifnet.if_link.tqe_next;
+ if (ifindex && ifindex != ifnet.if_index)
+ goto next;
+
+ printf("%s:\n", if_indextoname(ifnet.if_index, ifname));
+#ifdef INET
+ if_addrlist(TAILQ_FIRST(&ifnet.if_addrhead));
+#endif
+#ifdef INET6
+ if6_addrlist(TAILQ_FIRST(&ifnet.if_addrhead));
+#endif
+ if (vflag)
+ ll_addrlist(TAILQ_FIRST(&ifnet.if_addrhead));
+ next:
+ ifp = nifp;
+ }
+
+ return (0);
+}
+
+static void
+kread(u_long addr, void *buf, int len)
+{
+
+ if (kvm_read(kvmd, addr, buf, len) != len) {
+ perror("kvm_read");
+ exit(EX_OSERR);
+ }
+}
+
+static void
+ll_addrlist(struct ifaddr *ifap)
+{
+ char addrbuf[NI_MAXHOST];
+ struct ifaddr ifa;
+ struct sockaddr sa;
+ struct sockaddr_dl sdl;
+ struct ifaddr *ifap0;
+
+ if (af && af != AF_LINK)
+ return;
+
+ ifap0 = ifap;
+ while (ifap) {
+ KREAD(ifap, &ifa, struct ifaddr);
+ if (ifa.ifa_addr == NULL)
+ goto nextifap;
+ KREAD(ifa.ifa_addr, &sa, struct sockaddr);
+ if (sa.sa_family != PF_LINK)
+ goto nextifap;
+ KREAD(ifa.ifa_addr, &sdl, struct sockaddr_dl);
+ if (sdl.sdl_alen == 0)
+ goto nextifap;
+ addrbuf[0] = '\0';
+ getnameinfo((struct sockaddr *)&sdl, sdl.sdl_len,
+ addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST);
+ printf("\tlink %s\n", addrbuf);
+ nextifap:
+ ifap = ifa.ifa_link.tqe_next;
+ }
+ if (ifap0) {
+ struct ifnet ifnet;
+ struct ifmultiaddr ifm, *ifmp = 0;
+
+ KREAD(ifap0, &ifa, struct ifaddr);
+ KREAD(ifa.ifa_ifp, &ifnet, struct ifnet);
+ if (TAILQ_FIRST(&ifnet.if_multiaddrs))
+ ifmp = TAILQ_FIRST(&ifnet.if_multiaddrs);
+ while (ifmp) {
+ KREAD(ifmp, &ifm, struct ifmultiaddr);
+ if (ifm.ifma_addr == NULL)
+ goto nextmulti;
+ KREAD(ifm.ifma_addr, &sa, struct sockaddr);
+ if (sa.sa_family != AF_LINK)
+ goto nextmulti;
+ KREAD(ifm.ifma_addr, &sdl, struct sockaddr_dl);
+ addrbuf[0] = '\0';
+ getnameinfo((struct sockaddr *)&sdl,
+ sdl.sdl_len, addrbuf, sizeof(addrbuf),
+ NULL, 0, NI_NUMERICHOST);
+ printf("\t\tgroup %s refcnt %d\n",
+ addrbuf, ifm.ifma_refcount);
+ nextmulti:
+ ifmp = TAILQ_NEXT(&ifm, ifma_link);
+ }
+ }
+}
+
+#ifdef INET6
+
+static void
+if6_addrlist(struct ifaddr *ifap)
+{
+ struct ifnet ifnet;
+ struct ifaddr ifa;
+ struct sockaddr sa;
+ struct in6_ifaddr if6a;
+ struct ifaddr *ifap0;
+
+ if (af && af != AF_INET6)
+ return;
+ ifap0 = ifap;
+ while (ifap) {
+ KREAD(ifap, &ifa, struct ifaddr);
+ if (ifa.ifa_addr == NULL)
+ goto nextifap;
+ KREAD(ifa.ifa_addr, &sa, struct sockaddr);
+ if (sa.sa_family != PF_INET6)
+ goto nextifap;
+ KREAD(ifap, &if6a, struct in6_ifaddr);
+ printf("\tinet6 %s\n", inet6_n2a(&if6a.ia_addr.sin6_addr,
+ if6a.ia_addr.sin6_scope_id));
+ /*
+ * Print per-link MLD information, if available.
+ */
+ if (ifa.ifa_ifp != NULL) {
+ struct in6_ifextra ie;
+ struct mld_ifinfo mli;
+
+ KREAD(ifa.ifa_ifp, &ifnet, struct ifnet);
+ KREAD(ifnet.if_afdata[AF_INET6], &ie,
+ struct in6_ifextra);
+ if (ie.mld_ifinfo != NULL) {
+ KREAD(ie.mld_ifinfo, &mli, struct mld_ifinfo);
+ in6_ifinfo(&mli);
+ }
+ }
+ nextifap:
+ ifap = ifa.ifa_link.tqe_next;
+ }
+ if (ifap0) {
+ struct ifnet ifnet;
+ struct ifmultiaddr ifm, *ifmp = 0;
+ struct sockaddr_dl sdl;
+
+ KREAD(ifap0, &ifa, struct ifaddr);
+ KREAD(ifa.ifa_ifp, &ifnet, struct ifnet);
+ if (TAILQ_FIRST(&ifnet.if_multiaddrs))
+ ifmp = TAILQ_FIRST(&ifnet.if_multiaddrs);
+ while (ifmp) {
+ KREAD(ifmp, &ifm, struct ifmultiaddr);
+ if (ifm.ifma_addr == NULL)
+ goto nextmulti;
+ KREAD(ifm.ifma_addr, &sa, struct sockaddr);
+ if (sa.sa_family != AF_INET6)
+ goto nextmulti;
+ (void)in6_multientry((struct in6_multi *)
+ ifm.ifma_protospec);
+ if (ifm.ifma_lladdr == 0)
+ goto nextmulti;
+ KREAD(ifm.ifma_lladdr, &sdl, struct sockaddr_dl);
+ printf("\t\t\tmcast-macaddr %s refcnt %d\n",
+ ether_ntoa((struct ether_addr *)LLADDR(&sdl)),
+ ifm.ifma_refcount);
+ nextmulti:
+ ifmp = TAILQ_NEXT(&ifm, ifma_link);
+ }
+ }
+}
+
+static struct in6_multi *
+in6_multientry(struct in6_multi *mc)
+{
+ struct in6_multi multi;
+
+ KREAD(mc, &multi, struct in6_multi);
+ printf("\t\tgroup %s", inet6_n2a(&multi.in6m_addr, 0));
+ printf(" refcnt %u\n", multi.in6m_refcount);
+
+ return (multi.in6m_entry.le_next);
+}
+
+#endif /* INET6 */
+
+#ifdef INET
+
+static void
+if_addrlist(struct ifaddr *ifap)
+{
+ struct ifaddr ifa;
+ struct ifnet ifnet;
+ struct sockaddr sa;
+ struct in_ifaddr ia;
+ struct ifaddr *ifap0;
+
+ if (af && af != AF_INET)
+ return;
+ ifap0 = ifap;
+ while (ifap) {
+ KREAD(ifap, &ifa, struct ifaddr);
+ if (ifa.ifa_addr == NULL)
+ goto nextifap;
+ KREAD(ifa.ifa_addr, &sa, struct sockaddr);
+ if (sa.sa_family != PF_INET)
+ goto nextifap;
+ KREAD(ifap, &ia, struct in_ifaddr);
+ printf("\tinet %s\n", inet_ntoa(ia.ia_addr.sin_addr));
+ /*
+ * Print per-link IGMP information, if available.
+ */
+ if (ifa.ifa_ifp != NULL) {
+ struct in_ifinfo ii;
+ struct igmp_ifinfo igi;
+
+ KREAD(ifa.ifa_ifp, &ifnet, struct ifnet);
+ KREAD(ifnet.if_afdata[AF_INET], &ii, struct in_ifinfo);
+ if (ii.ii_igmp != NULL) {
+ KREAD(ii.ii_igmp, &igi, struct igmp_ifinfo);
+ in_ifinfo(&igi);
+ }
+ }
+ nextifap:
+ ifap = ifa.ifa_link.tqe_next;
+ }
+ if (ifap0) {
+ struct ifmultiaddr ifm, *ifmp = 0;
+ struct sockaddr_dl sdl;
+
+ KREAD(ifap0, &ifa, struct ifaddr);
+ KREAD(ifa.ifa_ifp, &ifnet, struct ifnet);
+ if (TAILQ_FIRST(&ifnet.if_multiaddrs))
+ ifmp = TAILQ_FIRST(&ifnet.if_multiaddrs);
+ while (ifmp) {
+ KREAD(ifmp, &ifm, struct ifmultiaddr);
+ if (ifm.ifma_addr == NULL)
+ goto nextmulti;
+ KREAD(ifm.ifma_addr, &sa, struct sockaddr);
+ if (sa.sa_family != AF_INET)
+ goto nextmulti;
+ (void)in_multientry((struct in_multi *)
+ ifm.ifma_protospec);
+ if (ifm.ifma_lladdr == 0)
+ goto nextmulti;
+ KREAD(ifm.ifma_lladdr, &sdl, struct sockaddr_dl);
+ printf("\t\t\tmcast-macaddr %s refcnt %d\n",
+ ether_ntoa((struct ether_addr *)LLADDR(&sdl)),
+ ifm.ifma_refcount);
+ nextmulti:
+ ifmp = TAILQ_NEXT(&ifm, ifma_link);
+ }
+ }
+}
+
+static const char *inm_states[] = {
+ "not-member",
+ "silent",
+ "idle",
+ "lazy",
+ "sleeping",
+ "awakening",
+ "query-pending",
+ "sg-query-pending",
+ "leaving"
+};
+
+static const char *
+inm_state(u_int state)
+{
+
+ if (state >= IGMP_NOT_MEMBER && state <= IGMP_LEAVING_MEMBER)
+ return (inm_states[state]);
+ return (NULL);
+}
+
+#if 0
+static struct ip_msource *
+ims_min_kvm(struct in_multi *pinm)
+{
+ struct ip_msource ims0;
+ struct ip_msource *tmp, *parent;
+
+ parent = NULL;
+ tmp = RB_ROOT(&pinm->inm_srcs);
+ while (tmp) {
+ parent = tmp;
+ KREAD(tmp, &ims0, struct ip_msource);
+ tmp = RB_LEFT(&ims0, ims_link);
+ }
+ return (parent); /* kva */
+}
+
+/* XXX This routine is buggy. See RB_NEXT in sys/tree.h. */
+static struct ip_msource *
+ims_next_kvm(struct ip_msource *ims)
+{
+ struct ip_msource ims0, ims1;
+ struct ip_msource *tmp;
+
+ KREAD(ims, &ims0, struct ip_msource);
+ if (RB_RIGHT(&ims0, ims_link)) {
+ ims = RB_RIGHT(&ims0, ims_link);
+ KREAD(ims, &ims1, struct ip_msource);
+ while ((tmp = RB_LEFT(&ims1, ims_link))) {
+ KREAD(tmp, &ims0, struct ip_msource);
+ ims = RB_LEFT(&ims0, ims_link);
+ }
+ } else {
+ tmp = RB_PARENT(&ims0, ims_link);
+ if (tmp) {
+ KREAD(tmp, &ims1, struct ip_msource);
+ if (ims == RB_LEFT(&ims1, ims_link))
+ ims = tmp;
+ } else {
+ while ((tmp = RB_PARENT(&ims0, ims_link))) {
+ KREAD(tmp, &ims1, struct ip_msource);
+ if (ims == RB_RIGHT(&ims1, ims_link)) {
+ ims = tmp;
+ KREAD(ims, &ims0, struct ip_msource);
+ } else
+ break;
+ }
+ ims = RB_PARENT(&ims0, ims_link);
+ }
+ }
+ return (ims); /* kva */
+}
+
+static void
+inm_print_sources_kvm(struct in_multi *pinm)
+{
+ struct ip_msource ims0;
+ struct ip_msource *ims;
+ struct in_addr src;
+ int cnt;
+ uint8_t fmode;
+
+ cnt = 0;
+ fmode = pinm->inm_st[1].iss_fmode;
+ if (fmode == MCAST_UNDEFINED)
+ return;
+ for (ims = ims_min_kvm(pinm); ims != NULL; ims = ims_next_kvm(ims)) {
+ if (cnt == 0)
+ printf(" srcs ");
+ KREAD(ims, &ims0, struct ip_msource);
+ /* Only print sources in-mode at t1. */
+ if (fmode != ims_get_mode(pinm, ims, 1))
+ continue;
+ src.s_addr = htonl(ims0.ims_haddr);
+ printf("%s%s", (cnt++ == 0 ? "" : ","), inet_ntoa(src));
+ }
+}
+#endif
+
+static struct in_multi *
+in_multientry(struct in_multi *pinm)
+{
+ struct in_multi inm;
+ const char *state, *mode;
+
+ KREAD(pinm, &inm, struct in_multi);
+ printf("\t\tgroup %s", inet_ntoa(inm.inm_addr));
+ printf(" refcnt %u", inm.inm_refcount);
+
+ state = inm_state(inm.inm_state);
+ if (state)
+ printf(" state %s", state);
+ else
+ printf(" state (%d)", inm.inm_state);
+
+ mode = inm_mode(inm.inm_st[1].iss_fmode);
+ if (mode)
+ printf(" mode %s", mode);
+ else
+ printf(" mode (%d)", inm.inm_st[1].iss_fmode);
+
+ if (vflag >= 2) {
+ printf(" asm %u ex %u in %u rec %u",
+ (u_int)inm.inm_st[1].iss_asm,
+ (u_int)inm.inm_st[1].iss_ex,
+ (u_int)inm.inm_st[1].iss_in,
+ (u_int)inm.inm_st[1].iss_rec);
+ }
+
+#if 0
+ /* Buggy. */
+ if (vflag)
+ inm_print_sources_kvm(&inm);
+#endif
+
+ printf("\n");
+ return (NULL);
+}
+
+#endif /* INET */
+
+#endif /* WITH_KVM */
+
+#ifdef INET6
+
+static void
+in6_ifinfo(struct mld_ifinfo *mli)
+{
+
+ printf("\t");
+ switch (mli->mli_version) {
+ case MLD_VERSION_1:
+ case MLD_VERSION_2:
+ printf("mldv%d", mli->mli_version);
+ break;
+ default:
+ printf("mldv?(%d)", mli->mli_version);
+ break;
+ }
+ if (mli->mli_flags)
+ printb(" flags", mli->mli_flags, "\020\1SILENT\2USEALLOW");
+ if (mli->mli_version == MLD_VERSION_2) {
+ printf(" rv %u qi %u qri %u uri %u",
+ mli->mli_rv, mli->mli_qi, mli->mli_qri, mli->mli_uri);
+ }
+ if (vflag >= 2) {
+ printf(" v1timer %u v2timer %u", mli->mli_v1_timer,
+ mli->mli_v2_timer);
+ }
+ printf("\n");
+}
+
+static const char *
+inet6_n2a(struct in6_addr *p, uint32_t scope_id)
+{
+ static char buf[NI_MAXHOST];
+ struct sockaddr_in6 sin6;
+ const int niflags = NI_NUMERICHOST;
+
+ memset(&sin6, 0, sizeof(sin6));
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_len = sizeof(struct sockaddr_in6);
+ sin6.sin6_addr = *p;
+ sin6.sin6_scope_id = scope_id;
+ if (getnameinfo((struct sockaddr *)&sin6, sin6.sin6_len,
+ buf, sizeof(buf), NULL, 0, niflags) == 0) {
+ return (buf);
+ } else {
+ return ("(invalid)");
+ }
+}
+#endif /* INET6 */
+
+#ifdef INET
+/*
+ * Retrieve per-group source filter mode and lists via sysctl.
+ */
+static void
+inm_print_sources_sysctl(uint32_t ifindex, struct in_addr gina)
+{
+#define MAX_SYSCTL_TRY 5
+ int mib[7];
+ int ntry = 0;
+ size_t mibsize;
+ size_t len;
+ size_t needed;
+ size_t cnt;
+ int i;
+ char *buf;
+ struct in_addr *pina;
+ uint32_t *p;
+ uint32_t fmode;
+ const char *modestr;
+
+ mibsize = sizeof(mib) / sizeof(mib[0]);
+ if (sysctlnametomib("net.inet.ip.mcast.filters", mib, &mibsize) == -1) {
+ perror("sysctlnametomib");
+ return;
+ }
+
+ needed = 0;
+ mib[5] = ifindex;
+ mib[6] = gina.s_addr; /* 32 bits wide */
+ mibsize = sizeof(mib) / sizeof(mib[0]);
+ do {
+ if (sysctl(mib, mibsize, NULL, &needed, NULL, 0) == -1) {
+ perror("sysctl net.inet.ip.mcast.filters");
+ return;
+ }
+ if ((buf = malloc(needed)) == NULL) {
+ perror("malloc");
+ return;
+ }
+ if (sysctl(mib, mibsize, buf, &needed, NULL, 0) == -1) {
+ if (errno != ENOMEM || ++ntry >= MAX_SYSCTL_TRY) {
+ perror("sysctl");
+ goto out_free;
+ }
+ free(buf);
+ buf = NULL;
+ }
+ } while (buf == NULL);
+
+ len = needed;
+ if (len < sizeof(uint32_t)) {
+ perror("sysctl");
+ goto out_free;
+ }
+
+ p = (uint32_t *)buf;
+ fmode = *p++;
+ len -= sizeof(uint32_t);
+
+ modestr = inm_mode(fmode);
+ if (modestr)
+ printf(" mode %s", modestr);
+ else
+ printf(" mode (%u)", fmode);
+
+ if (vflag == 0)
+ goto out_free;
+
+ cnt = len / sizeof(struct in_addr);
+ pina = (struct in_addr *)p;
+
+ for (i = 0; i < cnt; i++) {
+ if (i == 0)
+ printf(" srcs ");
+ fprintf(stdout, "%s%s", (i == 0 ? "" : ","),
+ inet_ntoa(*pina++));
+ len -= sizeof(struct in_addr);
+ }
+ if (len > 0) {
+ fprintf(stderr, "warning: %u trailing bytes from %s\n",
+ (unsigned int)len, "net.inet.ip.mcast.filters");
+ }
+
+out_free:
+ free(buf);
+#undef MAX_SYSCTL_TRY
+}
+
+#endif /* INET */
+
+#ifdef INET6
+/*
+ * Retrieve MLD per-group source filter mode and lists via sysctl.
+ *
+ * Note: The 128-bit IPv6 group address needs to be segmented into
+ * 32-bit pieces for marshaling to sysctl. So the MIB name ends
+ * up looking like this:
+ * a.b.c.d.e.ifindex.g[0].g[1].g[2].g[3]
+ * Assumes that pgroup originated from the kernel, so its components
+ * are already in network-byte order.
+ */
+static void
+in6m_print_sources_sysctl(uint32_t ifindex, struct in6_addr *pgroup)
+{
+#define MAX_SYSCTL_TRY 5
+ char addrbuf[INET6_ADDRSTRLEN];
+ int mib[10];
+ int ntry = 0;
+ int *pi;
+ size_t mibsize;
+ size_t len;
+ size_t needed;
+ size_t cnt;
+ int i;
+ char *buf;
+ struct in6_addr *pina;
+ uint32_t *p;
+ uint32_t fmode;
+ const char *modestr;
+
+ mibsize = sizeof(mib) / sizeof(mib[0]);
+ if (sysctlnametomib("net.inet6.ip6.mcast.filters", mib,
+ &mibsize) == -1) {
+ perror("sysctlnametomib");
+ return;
+ }
+
+ needed = 0;
+ mib[5] = ifindex;
+ pi = (int *)pgroup;
+ for (i = 0; i < 4; i++)
+ mib[6 + i] = *pi++;
+
+ mibsize = sizeof(mib) / sizeof(mib[0]);
+ do {
+ if (sysctl(mib, mibsize, NULL, &needed, NULL, 0) == -1) {
+ perror("sysctl net.inet6.ip6.mcast.filters");
+ return;
+ }
+ if ((buf = malloc(needed)) == NULL) {
+ perror("malloc");
+ return;
+ }
+ if (sysctl(mib, mibsize, buf, &needed, NULL, 0) == -1) {
+ if (errno != ENOMEM || ++ntry >= MAX_SYSCTL_TRY) {
+ perror("sysctl");
+ goto out_free;
+ }
+ free(buf);
+ buf = NULL;
+ }
+ } while (buf == NULL);
+
+ len = needed;
+ if (len < sizeof(uint32_t)) {
+ perror("sysctl");
+ goto out_free;
+ }
+
+ p = (uint32_t *)buf;
+ fmode = *p++;
+ len -= sizeof(uint32_t);
+
+ modestr = inm_mode(fmode);
+ if (modestr)
+ printf(" mode %s", modestr);
+ else
+ printf(" mode (%u)", fmode);
+
+ if (vflag == 0)
+ goto out_free;
+
+ cnt = len / sizeof(struct in6_addr);
+ pina = (struct in6_addr *)p;
+
+ for (i = 0; i < cnt; i++) {
+ if (i == 0)
+ printf(" srcs ");
+ inet_ntop(AF_INET6, (const char *)pina++, addrbuf,
+ INET6_ADDRSTRLEN);
+ fprintf(stdout, "%s%s", (i == 0 ? "" : ","), addrbuf);
+ len -= sizeof(struct in6_addr);
+ }
+ if (len > 0) {
+ fprintf(stderr, "warning: %u trailing bytes from %s\n",
+ (unsigned int)len, "net.inet6.ip6.mcast.filters");
+ }
+
+out_free:
+ free(buf);
+#undef MAX_SYSCTL_TRY
+}
+#endif /* INET6 */
+
+static int
+ifmcstat_getifmaddrs(void)
+{
+ char thisifname[IFNAMSIZ];
+ char addrbuf[NI_MAXHOST];
+ struct ifaddrs *ifap, *ifa;
+ struct ifmaddrs *ifmap, *ifma;
+ sockunion_t lastifasa;
+ sockunion_t *psa, *pgsa, *pllsa, *pifasa;
+ char *pcolon;
+ char *pafname;
+ uint32_t lastifindex, thisifindex;
+ int error;
+
+ error = 0;
+ ifap = NULL;
+ ifmap = NULL;
+ lastifindex = 0;
+ thisifindex = 0;
+ lastifasa.ss.ss_family = AF_UNSPEC;
+
+ if (getifaddrs(&ifap) != 0) {
+ warn("getifmaddrs");
+ return (-1);
+ }
+
+ if (getifmaddrs(&ifmap) != 0) {
+ warn("getifmaddrs");
+ error = -1;
+ goto out;
+ }
+
+ for (ifma = ifmap; ifma; ifma = ifma->ifma_next) {
+ error = 0;
+ if (ifma->ifma_name == NULL || ifma->ifma_addr == NULL)
+ continue;
+
+ psa = (sockunion_t *)ifma->ifma_name;
+ if (psa->sa.sa_family != AF_LINK) {
+ fprintf(stderr,
+ "WARNING: Kernel returned invalid data.\n");
+ error = -1;
+ break;
+ }
+
+ /* Filter on interface name. */
+ thisifindex = psa->sdl.sdl_index;
+ if (ifindex != 0 && thisifindex != ifindex)
+ continue;
+
+ /* Filter on address family. */
+ pgsa = (sockunion_t *)ifma->ifma_addr;
+ if (af != 0 && pgsa->sa.sa_family != af)
+ continue;
+
+ strlcpy(thisifname, link_ntoa(&psa->sdl), IFNAMSIZ);
+ pcolon = strchr(thisifname, ':');
+ if (pcolon)
+ *pcolon = '\0';
+
+ /* Only print the banner for the first ifmaddrs entry. */
+ if (lastifindex == 0 || lastifindex != thisifindex) {
+ lastifindex = thisifindex;
+ fprintf(stdout, "%s:\n", thisifname);
+ }
+
+ /*
+ * Currently, multicast joins only take place on the
+ * primary IPv4 address, and only on the link-local IPv6
+ * address, as per IGMPv2/3 and MLDv1/2 semantics.
+ * Therefore, we only look up the primary address on
+ * the first pass.
+ */
+ pifasa = NULL;
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ if ((strcmp(ifa->ifa_name, thisifname) != 0) ||
+ (ifa->ifa_addr == NULL) ||
+ (ifa->ifa_addr->sa_family != pgsa->sa.sa_family))
+ continue;
+ /*
+ * For AF_INET6 only the link-local address should
+ * be returned. If built without IPv6 support,
+ * skip this address entirely.
+ */
+ pifasa = (sockunion_t *)ifa->ifa_addr;
+ if (pifasa->sa.sa_family == AF_INET6
+#ifdef INET6
+ && !IN6_IS_ADDR_LINKLOCAL(&pifasa->sin6.sin6_addr)
+#endif
+ ) {
+ pifasa = NULL;
+ continue;
+ }
+ break;
+ }
+ if (pifasa == NULL)
+ continue; /* primary address not found */
+
+ if (!vflag && pifasa->sa.sa_family == AF_LINK)
+ continue;
+
+ /* Parse and print primary address, if not already printed. */
+ if (lastifasa.ss.ss_family == AF_UNSPEC ||
+ ((lastifasa.ss.ss_family == AF_LINK &&
+ !sa_dl_equal(&lastifasa.sa, &pifasa->sa)) ||
+ !sa_equal(&lastifasa.sa, &pifasa->sa))) {
+
+ switch (pifasa->sa.sa_family) {
+ case AF_INET:
+ pafname = "inet";
+ break;
+ case AF_INET6:
+ pafname = "inet6";
+ break;
+ case AF_LINK:
+ pafname = "link";
+ break;
+ default:
+ pafname = "unknown";
+ break;
+ }
+
+ switch (pifasa->sa.sa_family) {
+ case AF_INET6:
+#ifdef INET6
+ {
+ const char *p =
+ inet6_n2a(&pifasa->sin6.sin6_addr,
+ pifasa->sin6.sin6_scope_id);
+ strlcpy(addrbuf, p, sizeof(addrbuf));
+ break;
+ }
+#else
+ /* FALLTHROUGH */
+#endif
+ case AF_INET:
+ case AF_LINK:
+ error = getnameinfo(&pifasa->sa,
+ pifasa->sa.sa_len,
+ addrbuf, sizeof(addrbuf), NULL, 0,
+ NI_NUMERICHOST);
+ if (error)
+ perror("getnameinfo");
+ break;
+ default:
+ addrbuf[0] = '\0';
+ break;
+ }
+
+ fprintf(stdout, "\t%s %s", pafname, addrbuf);
+#ifdef INET6
+ if (pifasa->sa.sa_family == AF_INET6 &&
+ pifasa->sin6.sin6_scope_id)
+ fprintf(stdout, " scopeid 0x%x",
+ pifasa->sin6.sin6_scope_id);
+#endif
+ fprintf(stdout, "\n");
+#ifdef INET
+ /*
+ * Print per-link IGMP information, if available.
+ */
+ if (pifasa->sa.sa_family == AF_INET) {
+ struct igmp_ifinfo igi;
+ size_t mibsize, len;
+ int mib[5];
+
+ mibsize = sizeof(mib) / sizeof(mib[0]);
+ if (sysctlnametomib("net.inet.igmp.ifinfo",
+ mib, &mibsize) == -1) {
+ perror("sysctlnametomib");
+ goto next_ifnet;
+ }
+ mib[mibsize] = thisifindex;
+ len = sizeof(struct igmp_ifinfo);
+ if (sysctl(mib, mibsize + 1, &igi, &len, NULL,
+ 0) == -1) {
+ perror("sysctl net.inet.igmp.ifinfo");
+ goto next_ifnet;
+ }
+ in_ifinfo(&igi);
+ }
+#endif /* INET */
+#ifdef INET6
+ /*
+ * Print per-link MLD information, if available.
+ */
+ if (pifasa->sa.sa_family == AF_INET6) {
+ struct mld_ifinfo mli;
+ size_t mibsize, len;
+ int mib[5];
+
+ mibsize = sizeof(mib) / sizeof(mib[0]);
+ if (sysctlnametomib("net.inet6.mld.ifinfo",
+ mib, &mibsize) == -1) {
+ perror("sysctlnametomib");
+ goto next_ifnet;
+ }
+ mib[mibsize] = thisifindex;
+ len = sizeof(struct mld_ifinfo);
+ if (sysctl(mib, mibsize + 1, &mli, &len, NULL,
+ 0) == -1) {
+ perror("sysctl net.inet6.mld.ifinfo");
+ goto next_ifnet;
+ }
+ in6_ifinfo(&mli);
+ }
+#endif /* INET6 */
+#if defined(INET) || defined(INET6)
+next_ifnet:
+#endif
+ lastifasa = *pifasa;
+ }
+
+ /* Print this group address. */
+#ifdef INET6
+ if (pgsa->sa.sa_family == AF_INET6) {
+ const char *p = inet6_n2a(&pgsa->sin6.sin6_addr,
+ pgsa->sin6.sin6_scope_id);
+ strlcpy(addrbuf, p, sizeof(addrbuf));
+ } else
+#endif
+ {
+ error = getnameinfo(&pgsa->sa, pgsa->sa.sa_len,
+ addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST);
+ if (error)
+ perror("getnameinfo");
+ }
+
+ fprintf(stdout, "\t\tgroup %s", addrbuf);
+#ifdef INET6
+ if (pgsa->sa.sa_family == AF_INET6 &&
+ pgsa->sin6.sin6_scope_id)
+ fprintf(stdout, " scopeid 0x%x",
+ pgsa->sin6.sin6_scope_id);
+#endif
+#ifdef INET
+ if (pgsa->sa.sa_family == AF_INET) {
+ inm_print_sources_sysctl(thisifindex,
+ pgsa->sin.sin_addr);
+ }
+#endif
+#ifdef INET6
+ if (pgsa->sa.sa_family == AF_INET6) {
+ in6m_print_sources_sysctl(thisifindex,
+ &pgsa->sin6.sin6_addr);
+ }
+#endif
+ fprintf(stdout, "\n");
+
+ /* Link-layer mapping, if present. */
+ pllsa = (sockunion_t *)ifma->ifma_lladdr;
+ if (pllsa != NULL) {
+ error = getnameinfo(&pllsa->sa, pllsa->sa.sa_len,
+ addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST);
+ fprintf(stdout, "\t\t\tmcast-macaddr %s\n", addrbuf);
+ }
+ }
+out:
+ if (ifmap != NULL)
+ freeifmaddrs(ifmap);
+ if (ifap != NULL)
+ freeifaddrs(ifap);
+
+ return (error);
+}
diff --git a/usr.sbin/ifmcstat/printb.c b/usr.sbin/ifmcstat/printb.c
new file mode 100644
index 0000000..a60d4f0
--- /dev/null
+++ b/usr.sbin/ifmcstat/printb.c
@@ -0,0 +1,64 @@
+/*
+ * 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/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <stdio.h>
+
+/*
+ * Print a value a la the %b format of the kernel's printf
+ */
+void
+printb(const char *s, unsigned int 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('>');
+ }
+}
diff --git a/usr.sbin/inetd/Makefile b/usr.sbin/inetd/Makefile
new file mode 100644
index 0000000..6174592
--- /dev/null
+++ b/usr.sbin/inetd/Makefile
@@ -0,0 +1,27 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= inetd
+MAN= inetd.8
+MLINKS= inetd.8 inetd.conf.5
+SRCS= inetd.c builtins.c
+
+WARNS?= 3
+CFLAGS+= -DLOGIN_CAP
+#CFLAGS+= -DSANITY_CHECK
+
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+= -DINET6
+.endif
+
+LIBADD= util wrap
+
+# XXX for src/release/picobsd
+.if !defined(RELEASE_CRUNCH)
+CFLAGS+= -DIPSEC
+LIBADD+= ipsec
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/inetd/Makefile.depend b/usr.sbin/inetd/Makefile.depend
new file mode 100644
index 0000000..cc94252
--- /dev/null
+++ b/usr.sbin/inetd/Makefile.depend
@@ -0,0 +1,23 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libipsec \
+ lib/libutil \
+ lib/libwrap \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/inetd/builtins.c b/usr.sbin/inetd/builtins.c
new file mode 100644
index 0000000..3ccdab0
--- /dev/null
+++ b/usr.sbin/inetd/builtins.c
@@ -0,0 +1,814 @@
+/*-
+ * Copyright (c) 1983, 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.
+ *
+ * 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/filio.h>
+#include <sys/ioccom.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/ucred.h>
+#include <sys/uio.h>
+#include <sys/utsname.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "inetd.h"
+
+void chargen_dg(int, struct servtab *);
+void chargen_stream(int, struct servtab *);
+void daytime_dg(int, struct servtab *);
+void daytime_stream(int, struct servtab *);
+void discard_dg(int, struct servtab *);
+void discard_stream(int, struct servtab *);
+void echo_dg(int, struct servtab *);
+void echo_stream(int, struct servtab *);
+static int getline(int, char *, int);
+void iderror(int, int, int, const char *);
+void ident_stream(int, struct servtab *);
+void initring(void);
+uint32_t machtime(void);
+void machtime_dg(int, struct servtab *);
+void machtime_stream(int, struct servtab *);
+
+char ring[128];
+char *endring;
+
+
+struct biltin biltins[] = {
+ /* Echo received data */
+ { "echo", SOCK_STREAM, 1, -1, echo_stream },
+ { "echo", SOCK_DGRAM, 0, 1, echo_dg },
+
+ /* Internet /dev/null */
+ { "discard", SOCK_STREAM, 1, -1, discard_stream },
+ { "discard", SOCK_DGRAM, 0, 1, discard_dg },
+
+ /* Return 32 bit time since 1900 */
+ { "time", SOCK_STREAM, 0, -1, machtime_stream },
+ { "time", SOCK_DGRAM, 0, 1, machtime_dg },
+
+ /* Return human-readable time */
+ { "daytime", SOCK_STREAM, 0, -1, daytime_stream },
+ { "daytime", SOCK_DGRAM, 0, 1, daytime_dg },
+
+ /* Familiar character generator */
+ { "chargen", SOCK_STREAM, 1, -1, chargen_stream },
+ { "chargen", SOCK_DGRAM, 0, 1, chargen_dg },
+
+ { "tcpmux", SOCK_STREAM, 1, -1, (bi_fn_t *)tcpmux },
+
+ { "auth", SOCK_STREAM, 1, -1, ident_stream },
+
+ { NULL, 0, 0, 0, NULL }
+};
+
+/*
+ * RFC864 Character Generator Protocol. Generates character data without
+ * any regard for input.
+ */
+
+void
+initring(void)
+{
+ int i;
+
+ endring = ring;
+
+ for (i = 0; i <= 128; ++i)
+ if (isprint(i))
+ *endring++ = i;
+}
+
+/* Character generator
+ * The RFC says that we should send back a random number of
+ * characters chosen from the range 0 to 512. We send LINESIZ+2.
+ */
+/* ARGSUSED */
+void
+chargen_dg(int s, struct servtab *sep)
+{
+ struct sockaddr_storage ss;
+ static char *rs;
+ int len;
+ socklen_t size;
+ char text[LINESIZ+2];
+
+ if (endring == 0) {
+ initring();
+ rs = ring;
+ }
+
+ size = sizeof(ss);
+ if (recvfrom(s, text, sizeof(text), 0,
+ (struct sockaddr *)&ss, &size) < 0)
+ return;
+
+ if (check_loop((struct sockaddr *)&ss, sep))
+ return;
+
+ if ((len = endring - rs) >= LINESIZ)
+ memmove(text, rs, LINESIZ);
+ else {
+ memmove(text, rs, len);
+ memmove(text + len, ring, LINESIZ - len);
+ }
+ if (++rs == endring)
+ rs = ring;
+ text[LINESIZ] = '\r';
+ text[LINESIZ + 1] = '\n';
+ (void) sendto(s, text, sizeof(text), 0, (struct sockaddr *)&ss, size);
+}
+
+/* Character generator */
+/* ARGSUSED */
+void
+chargen_stream(int s, struct servtab *sep)
+{
+ int len;
+ char *rs, text[LINESIZ+2];
+
+ inetd_setproctitle(sep->se_service, s);
+
+ if (!endring) {
+ initring();
+ rs = ring;
+ }
+
+ text[LINESIZ] = '\r';
+ text[LINESIZ + 1] = '\n';
+ for (rs = ring;;) {
+ if ((len = endring - rs) >= LINESIZ)
+ memmove(text, rs, LINESIZ);
+ else {
+ memmove(text, rs, len);
+ memmove(text + len, ring, LINESIZ - len);
+ }
+ if (++rs == endring)
+ rs = ring;
+ if (write(s, text, sizeof(text)) != sizeof(text))
+ break;
+ }
+ exit(0);
+}
+
+/*
+ * RFC867 Daytime Protocol. Sends the current date and time as an ascii
+ * character string without any regard for input.
+ */
+
+/* Return human-readable time of day */
+/* ARGSUSED */
+void
+daytime_dg(int s, struct servtab *sep)
+{
+ char buffer[256];
+ time_t now;
+ struct sockaddr_storage ss;
+ socklen_t size;
+
+ now = time((time_t *) 0);
+
+ size = sizeof(ss);
+ if (recvfrom(s, buffer, sizeof(buffer), 0,
+ (struct sockaddr *)&ss, &size) < 0)
+ return;
+
+ if (check_loop((struct sockaddr *)&ss, sep))
+ return;
+
+ (void) sprintf(buffer, "%.24s\r\n", ctime(&now));
+ (void) sendto(s, buffer, strlen(buffer), 0,
+ (struct sockaddr *)&ss, size);
+}
+
+/* Return human-readable time of day */
+/* ARGSUSED */
+void
+daytime_stream(int s, struct servtab *sep __unused)
+{
+ char buffer[256];
+ time_t now;
+
+ now = time((time_t *) 0);
+
+ (void) sprintf(buffer, "%.24s\r\n", ctime(&now));
+ (void) send(s, buffer, strlen(buffer), MSG_EOF);
+}
+
+/*
+ * RFC863 Discard Protocol. Any data received is thrown away and no response
+ * is sent.
+ */
+
+/* Discard service -- ignore data */
+/* ARGSUSED */
+void
+discard_dg(int s, struct servtab *sep __unused)
+{
+ char buffer[BUFSIZE];
+
+ (void) read(s, buffer, sizeof(buffer));
+}
+
+/* Discard service -- ignore data */
+/* ARGSUSED */
+void
+discard_stream(int s, struct servtab *sep)
+{
+ int ret;
+ char buffer[BUFSIZE];
+
+ inetd_setproctitle(sep->se_service, s);
+ while (1) {
+ while ((ret = read(s, buffer, sizeof(buffer))) > 0)
+ ;
+ if (ret == 0 || errno != EINTR)
+ break;
+ }
+ exit(0);
+}
+
+/*
+ * RFC862 Echo Protocol. Any data received is sent back to the sender as
+ * received.
+ */
+
+/* Echo service -- echo data back */
+/* ARGSUSED */
+void
+echo_dg(int s, struct servtab *sep)
+{
+ char buffer[65536]; /* Should be sizeof(max datagram). */
+ int i;
+ socklen_t size;
+ struct sockaddr_storage ss;
+
+ size = sizeof(ss);
+ if ((i = recvfrom(s, buffer, sizeof(buffer), 0,
+ (struct sockaddr *)&ss, &size)) < 0)
+ return;
+
+ if (check_loop((struct sockaddr *)&ss, sep))
+ return;
+
+ (void) sendto(s, buffer, i, 0, (struct sockaddr *)&ss, size);
+}
+
+/* Echo service -- echo data back */
+/* ARGSUSED */
+void
+echo_stream(int s, struct servtab *sep)
+{
+ char buffer[BUFSIZE];
+ int i;
+
+ inetd_setproctitle(sep->se_service, s);
+ while ((i = read(s, buffer, sizeof(buffer))) > 0 &&
+ write(s, buffer, i) > 0)
+ ;
+ exit(0);
+}
+
+/*
+ * RFC1413 Identification Protocol. Given a TCP port number pair, return a
+ * character string which identifies the owner of that connection on the
+ * server's system. Extended to allow for ~/.fakeid support and ~/.noident
+ * support.
+ */
+
+/* RFC 1413 says the following are the only errors you can return. */
+#define ID_INVALID "INVALID-PORT" /* Port number improperly specified. */
+#define ID_NOUSER "NO-USER" /* Port not in use/not identifable. */
+#define ID_HIDDEN "HIDDEN-USER" /* Hiden at user's request. */
+#define ID_UNKNOWN "UNKNOWN-ERROR" /* Everything else. */
+
+/* Generic ident_stream error-sending func */
+/* ARGSUSED */
+void
+iderror(int lport, int fport, int s, const char *er)
+{
+ char *p;
+
+ asprintf(&p, "%d , %d : ERROR : %s\r\n", lport, fport, er);
+ if (p == NULL) {
+ syslog(LOG_ERR, "asprintf: %m");
+ exit(EX_OSERR);
+ }
+ send(s, p, strlen(p), MSG_EOF);
+ free(p);
+
+ exit(0);
+}
+
+/* Ident service (AKA "auth") */
+/* ARGSUSED */
+void
+ident_stream(int s, struct servtab *sep)
+{
+ struct utsname un;
+ struct stat sb;
+ struct sockaddr_in sin4[2];
+#ifdef INET6
+ struct sockaddr_in6 sin6[2];
+#endif
+ struct sockaddr_storage ss[2];
+ struct xucred uc;
+ struct timeval tv = {
+ 10,
+ 0
+ }, to;
+ struct passwd *pw = NULL;
+ fd_set fdset;
+ char buf[BUFSIZE], *p, **av, *osname = NULL, e;
+ char idbuf[MAXLOGNAME] = ""; /* Big enough to hold uid in decimal. */
+ socklen_t socklen;
+ ssize_t ssize;
+ size_t size, bufsiz;
+ int c, fflag = 0, nflag = 0, rflag = 0, argc = 0;
+ int gflag = 0, iflag = 0, Fflag = 0, getcredfail = 0, onreadlen;
+ u_short lport, fport;
+
+ inetd_setproctitle(sep->se_service, s);
+ /*
+ * Reset getopt() since we are a fork() but not an exec() from
+ * a parent which used getopt() already.
+ */
+ optind = 1;
+ optreset = 1;
+ /*
+ * Take the internal argument vector and count it out to make an
+ * argument count for getopt. This can be used for any internal
+ * service to read arguments and use getopt() easily.
+ */
+ for (av = sep->se_argv; *av; av++)
+ argc++;
+ if (argc) {
+ int sec, usec;
+ size_t i;
+ u_int32_t rnd32;
+
+ while ((c = getopt(argc, sep->se_argv, "d:fFgino:rt:")) != -1)
+ switch (c) {
+ case 'd':
+ if (!gflag)
+ strlcpy(idbuf, optarg, sizeof(idbuf));
+ break;
+ case 'f':
+ fflag = 1;
+ break;
+ case 'F':
+ fflag = 1;
+ Fflag=1;
+ break;
+ case 'g':
+ gflag = 1;
+ rnd32 = 0; /* Shush, compiler. */
+ /*
+ * The number of bits in "rnd32" divided
+ * by the number of bits needed per iteration
+ * gives a more optimal way to reload the
+ * random number only when necessary.
+ *
+ * 32 bits from arc4random corresponds to
+ * about 6 base-36 digits, so we reseed evey 6.
+ */
+ for (i = 0; i < sizeof(idbuf) - 1; i++) {
+ static const char *const base36 =
+ "0123456789"
+ "abcdefghijklmnopqrstuvwxyz";
+ if (i % 6 == 0)
+ rnd32 = arc4random();
+ idbuf[i] = base36[rnd32 % 36];
+ rnd32 /= 36;
+ }
+ idbuf[i] = '\0';
+ break;
+ case 'i':
+ iflag = 1;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'o':
+ osname = optarg;
+ break;
+ case 'r':
+ rflag = 1;
+ break;
+ case 't':
+ switch (sscanf(optarg, "%d.%d", &sec, &usec)) {
+ case 2:
+ tv.tv_usec = usec;
+ /* FALLTHROUGH */
+ case 1:
+ tv.tv_sec = sec;
+ break;
+ default:
+ if (debug)
+ warnx("bad -t argument");
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if (osname == NULL) {
+ if (uname(&un) == -1)
+ iderror(0, 0, s, ID_UNKNOWN);
+ osname = un.sysname;
+ }
+
+ /*
+ * We're going to prepare for and execute reception of a
+ * packet of data from the user. The data is in the format
+ * "local_port , foreign_port\r\n" (with local being the
+ * server's port and foreign being the client's.)
+ */
+ gettimeofday(&to, NULL);
+ to.tv_sec += tv.tv_sec;
+ to.tv_usec += tv.tv_usec;
+ if (to.tv_usec >= 1000000) {
+ to.tv_usec -= 1000000;
+ to.tv_sec++;
+ }
+
+ size = 0;
+ bufsiz = sizeof(buf) - 1;
+ FD_ZERO(&fdset);
+ while (bufsiz > 0) {
+ gettimeofday(&tv, NULL);
+ tv.tv_sec = to.tv_sec - tv.tv_sec;
+ tv.tv_usec = to.tv_usec - tv.tv_usec;
+ if (tv.tv_usec < 0) {
+ tv.tv_usec += 1000000;
+ tv.tv_sec--;
+ }
+ if (tv.tv_sec < 0)
+ break;
+ FD_SET(s, &fdset);
+ if (select(s + 1, &fdset, NULL, NULL, &tv) == -1)
+ iderror(0, 0, s, ID_UNKNOWN);
+ if (ioctl(s, FIONREAD, &onreadlen) == -1)
+ iderror(0, 0, s, ID_UNKNOWN);
+ if ((size_t)onreadlen > bufsiz)
+ onreadlen = bufsiz;
+ ssize = read(s, &buf[size], (size_t)onreadlen);
+ if (ssize == -1)
+ iderror(0, 0, s, ID_UNKNOWN);
+ else if (ssize == 0)
+ break;
+ bufsiz -= ssize;
+ size += ssize;
+ if (memchr(&buf[size - ssize], '\n', ssize) != NULL)
+ break;
+ }
+ buf[size] = '\0';
+ /* Read two characters, and check for a delimiting character */
+ if (sscanf(buf, "%hu , %hu%c", &lport, &fport, &e) != 3 || isdigit(e))
+ iderror(0, 0, s, ID_INVALID);
+
+ /* Send garbage? */
+ if (gflag)
+ goto printit;
+
+ /*
+ * If not "real" (-r), send a HIDDEN-USER error for everything.
+ * If -d is used to set a fallback username, this is used to
+ * override it, and the fallback is returned instead.
+ */
+ if (!rflag) {
+ if (*idbuf == '\0')
+ iderror(lport, fport, s, ID_HIDDEN);
+ goto printit;
+ }
+
+ /*
+ * We take the input and construct an array of two sockaddr_ins
+ * which contain the local address information and foreign
+ * address information, respectively, used to look up the
+ * credentials for the socket (which are returned by the
+ * sysctl "net.inet.tcp.getcred" when we call it.)
+ */
+ socklen = sizeof(ss[0]);
+ if (getsockname(s, (struct sockaddr *)&ss[0], &socklen) == -1)
+ iderror(lport, fport, s, ID_UNKNOWN);
+ socklen = sizeof(ss[1]);
+ if (getpeername(s, (struct sockaddr *)&ss[1], &socklen) == -1)
+ iderror(lport, fport, s, ID_UNKNOWN);
+ if (ss[0].ss_family != ss[1].ss_family)
+ iderror(lport, fport, s, ID_UNKNOWN);
+ size = sizeof(uc);
+ switch (ss[0].ss_family) {
+ case AF_INET:
+ sin4[0] = *(struct sockaddr_in *)&ss[0];
+ sin4[0].sin_port = htons(lport);
+ sin4[1] = *(struct sockaddr_in *)&ss[1];
+ sin4[1].sin_port = htons(fport);
+ if (sysctlbyname("net.inet.tcp.getcred", &uc, &size, sin4,
+ sizeof(sin4)) == -1)
+ getcredfail = errno;
+ break;
+#ifdef INET6
+ case AF_INET6:
+ sin6[0] = *(struct sockaddr_in6 *)&ss[0];
+ sin6[0].sin6_port = htons(lport);
+ sin6[1] = *(struct sockaddr_in6 *)&ss[1];
+ sin6[1].sin6_port = htons(fport);
+ if (sysctlbyname("net.inet6.tcp6.getcred", &uc, &size, sin6,
+ sizeof(sin6)) == -1)
+ getcredfail = errno;
+ break;
+#endif
+ default: /* should not reach here */
+ getcredfail = EAFNOSUPPORT;
+ break;
+ }
+ if (getcredfail != 0 || uc.cr_version != XUCRED_VERSION) {
+ if (*idbuf == '\0')
+ iderror(lport, fport, s,
+ getcredfail == ENOENT ? ID_NOUSER : ID_UNKNOWN);
+ goto printit;
+ }
+
+ /* Look up the pw to get the username and home directory*/
+ errno = 0;
+ pw = getpwuid(uc.cr_uid);
+ if (pw == NULL)
+ iderror(lport, fport, s, errno == 0 ? ID_NOUSER : ID_UNKNOWN);
+
+ if (iflag)
+ snprintf(idbuf, sizeof(idbuf), "%u", (unsigned)pw->pw_uid);
+ else
+ strlcpy(idbuf, pw->pw_name, sizeof(idbuf));
+
+ /*
+ * If enabled, we check for a file named ".noident" in the user's
+ * home directory. If found, we return HIDDEN-USER.
+ */
+ if (nflag) {
+ if (asprintf(&p, "%s/.noident", pw->pw_dir) == -1)
+ iderror(lport, fport, s, ID_UNKNOWN);
+ if (lstat(p, &sb) == 0) {
+ free(p);
+ iderror(lport, fport, s, ID_HIDDEN);
+ }
+ free(p);
+ }
+
+ /*
+ * Here, if enabled, we read a user's ".fakeid" file in their
+ * home directory. It consists of a line containing the name
+ * they want.
+ */
+ if (fflag) {
+ int fakeid_fd;
+
+ /*
+ * Here we set ourself to effectively be the user, so we don't
+ * open any files we have no permission to open, especially
+ * symbolic links to sensitive root-owned files or devices.
+ */
+ if (initgroups(pw->pw_name, pw->pw_gid) == -1)
+ iderror(lport, fport, s, ID_UNKNOWN);
+ if (seteuid(pw->pw_uid) == -1)
+ iderror(lport, fport, s, ID_UNKNOWN);
+ /*
+ * We can't stat() here since that would be a race
+ * condition.
+ * Therefore, we open the file we have permissions to open
+ * and if it's not a regular file, we close it and end up
+ * returning the user's real username.
+ */
+ if (asprintf(&p, "%s/.fakeid", pw->pw_dir) == -1)
+ iderror(lport, fport, s, ID_UNKNOWN);
+ fakeid_fd = open(p, O_RDONLY | O_NONBLOCK);
+ free(p);
+ if (fakeid_fd == -1 || fstat(fakeid_fd, &sb) == -1 ||
+ !S_ISREG(sb.st_mode))
+ goto fakeid_fail;
+
+ if ((ssize = read(fakeid_fd, buf, sizeof(buf) - 1)) < 0)
+ goto fakeid_fail;
+ buf[ssize] = '\0';
+
+ /*
+ * Usually, the file will have the desired identity
+ * in the form "identity\n". Allow for leading white
+ * space and trailing white space/end of line.
+ */
+ p = buf;
+ p += strspn(p, " \t");
+ p[strcspn(p, " \t\r\n")] = '\0';
+ if (strlen(p) > MAXLOGNAME - 1) /* Too long (including nul)? */
+ p[MAXLOGNAME - 1] = '\0';
+
+ /*
+ * If the name is a zero-length string or matches it
+ * the id or name of another user (unless permitted by -F)
+ * then it is invalid.
+ */
+ if (*p == '\0')
+ goto fakeid_fail;
+ if (!Fflag) {
+ if (iflag) {
+ if (p[strspn(p, "0123456789")] == '\0' &&
+ getpwuid(atoi(p)) != NULL)
+ goto fakeid_fail;
+ } else {
+ if (getpwnam(p) != NULL)
+ goto fakeid_fail;
+ }
+ }
+
+ strlcpy(idbuf, p, sizeof(idbuf));
+
+fakeid_fail:
+ if (fakeid_fd != -1)
+ close(fakeid_fd);
+ }
+
+printit:
+ /* Finally, we make and send the reply. */
+ if (asprintf(&p, "%d , %d : USERID : %s : %s\r\n", lport, fport, osname,
+ idbuf) == -1) {
+ syslog(LOG_ERR, "asprintf: %m");
+ exit(EX_OSERR);
+ }
+ send(s, p, strlen(p), MSG_EOF);
+ free(p);
+
+ exit(0);
+}
+
+/*
+ * RFC738/868 Time Server.
+ * Return a machine readable date and time, in the form of the
+ * number of seconds since midnight, Jan 1, 1900. Since gettimeofday
+ * returns the number of seconds since midnight, Jan 1, 1970,
+ * we must add 2208988800 seconds to this figure to make up for
+ * some seventy years Bell Labs was asleep.
+ */
+
+uint32_t
+machtime(void)
+{
+
+#define OFFSET ((uint32_t)25567 * 24*60*60)
+ return (htonl((uint32_t)(time(NULL) + OFFSET)));
+#undef OFFSET
+}
+
+/* ARGSUSED */
+void
+machtime_dg(int s, struct servtab *sep)
+{
+ uint32_t result;
+ struct sockaddr_storage ss;
+ socklen_t size;
+
+ size = sizeof(ss);
+ if (recvfrom(s, (char *)&result, sizeof(result), 0,
+ (struct sockaddr *)&ss, &size) < 0)
+ return;
+
+ if (check_loop((struct sockaddr *)&ss, sep))
+ return;
+
+ result = machtime();
+ (void) sendto(s, (char *) &result, sizeof(result), 0,
+ (struct sockaddr *)&ss, size);
+}
+
+/* ARGSUSED */
+void
+machtime_stream(int s, struct servtab *sep __unused)
+{
+ uint32_t result;
+
+ result = machtime();
+ (void) send(s, (char *) &result, sizeof(result), MSG_EOF);
+}
+
+/*
+ * RFC1078 TCP Port Service Multiplexer (TCPMUX). Service connections to
+ * services based on the service name sent.
+ *
+ * Based on TCPMUX.C by Mark K. Lottor November 1988
+ * sri-nic::ps:<mkl>tcpmux.c
+ */
+
+#define MAX_SERV_LEN (256+2) /* 2 bytes for \r\n */
+#define strwrite(fd, buf) (void) write(fd, buf, sizeof(buf)-1)
+
+static int /* # of characters up to \r,\n or \0 */
+getline(int fd, char *buf, int len)
+{
+ int count = 0, n;
+ struct sigaction sa;
+
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = SIG_DFL;
+ sigaction(SIGALRM, &sa, (struct sigaction *)0);
+ do {
+ alarm(10);
+ n = read(fd, buf, len-count);
+ alarm(0);
+ if (n == 0)
+ return (count);
+ if (n < 0)
+ return (-1);
+ while (--n >= 0) {
+ if (*buf == '\r' || *buf == '\n' || *buf == '\0')
+ return (count);
+ count++;
+ buf++;
+ }
+ } while (count < len);
+ return (count);
+}
+
+struct servtab *
+tcpmux(int s)
+{
+ struct servtab *sep;
+ char service[MAX_SERV_LEN+1];
+ int len;
+
+ /* Get requested service name */
+ if ((len = getline(s, service, MAX_SERV_LEN)) < 0) {
+ strwrite(s, "-Error reading service name\r\n");
+ return (NULL);
+ }
+ service[len] = '\0';
+
+ if (debug)
+ warnx("tcpmux: someone wants %s", service);
+
+ /*
+ * Help is a required command, and lists available services,
+ * one per line.
+ */
+ if (!strcasecmp(service, "help")) {
+ for (sep = servtab; sep; sep = sep->se_next) {
+ if (!ISMUX(sep))
+ continue;
+ (void)write(s,sep->se_service,strlen(sep->se_service));
+ strwrite(s, "\r\n");
+ }
+ return (NULL);
+ }
+
+ /* Try matching a service in inetd.conf with the request */
+ for (sep = servtab; sep; sep = sep->se_next) {
+ if (!ISMUX(sep))
+ continue;
+ if (!strcasecmp(service, sep->se_service)) {
+ if (ISMUXPLUS(sep)) {
+ strwrite(s, "+Go\r\n");
+ }
+ return (sep);
+ }
+ }
+ strwrite(s, "-Service not available\r\n");
+ return (NULL);
+}
diff --git a/usr.sbin/inetd/inetd.8 b/usr.sbin/inetd/inetd.8
new file mode 100644
index 0000000..80cfbd2
--- /dev/null
+++ b/usr.sbin/inetd/inetd.8
@@ -0,0 +1,955 @@
+.\" Copyright (c) 1985, 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.
+.\"
+.\" from: @(#)inetd.8 8.3 (Berkeley) 4/13/94
+.\" $FreeBSD$
+.\"
+.Dd January 12, 2008
+.Dt INETD 8
+.Os
+.Sh NAME
+.Nm inetd
+.Nd internet
+.Dq super-server
+.Sh SYNOPSIS
+.Nm
+.Op Fl d
+.Op Fl l
+.Op Fl w
+.Op Fl W
+.Op Fl c Ar maximum
+.Op Fl C Ar rate
+.Op Fl a Ar address | hostname
+.Op Fl p Ar filename
+.Op Fl R Ar rate
+.Op Fl s Ar maximum
+.Op Ar configuration file
+.Sh DESCRIPTION
+The
+.Nm
+utility should be run at boot time by
+.Pa /etc/rc
+(see
+.Xr rc 8 ) .
+It then listens for connections on certain
+internet sockets.
+When a connection is found on one
+of its sockets, it decides what service the socket
+corresponds to, and invokes a program to service the request.
+The server program is invoked with the service socket
+as its standard input, output and error descriptors.
+After the program is
+finished,
+.Nm
+continues to listen on the socket (except in some cases which
+will be described below).
+Essentially,
+.Nm
+allows running one daemon to invoke several others,
+reducing load on the system.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl d
+Turn on debugging.
+.It Fl l
+Turn on logging of successful connections.
+.It Fl w
+Turn on TCP Wrapping for external services.
+See the
+.Sx "IMPLEMENTATION NOTES"
+section for more information on TCP Wrappers support.
+.It Fl W
+Turn on TCP Wrapping for internal services which are built in to
+.Nm .
+.It Fl c Ar maximum
+Specify the default maximum number of
+simultaneous invocations of each service;
+the default is unlimited.
+May be overridden on a per-service basis with the "max-child"
+parameter.
+.It Fl C Ar rate
+Specify the default maximum number of times a service can be invoked
+from a single IP address in one minute; the default is unlimited.
+May be overridden on a per-service basis with the
+"max-connections-per-ip-per-minute" parameter.
+.It Fl R Ar rate
+Specify the maximum number of times a service can be invoked
+in one minute; the default is 256.
+A rate of 0 allows an unlimited number of invocations.
+.It Fl s Ar maximum
+Specify the default maximum number of
+simultaneous invocations of each service from a single IP address;
+the default is unlimited.
+May be overridden on a per-service basis with the "max-child-per-ip"
+parameter.
+.It Fl a
+Specify one specific IP address to bind to.
+Alternatively, a hostname can be specified,
+in which case the IPv4 or IPv6 address
+which corresponds to that hostname is used.
+Usually a hostname is specified when
+.Nm
+is run inside a
+.Xr jail 8 ,
+in which case the hostname corresponds to that of the
+.Xr jail 8
+environment.
+.Pp
+When the hostname specification is used
+and both IPv4 and IPv6 bindings are desired,
+one entry with the appropriate
+.Em protocol
+type for each binding
+is required for each service in
+.Pa /etc/inetd.conf .
+For example,
+a TCP-based service would need two entries,
+one using
+.Dq tcp4
+for the
+.Em protocol
+and the other using
+.Dq tcp6 .
+See the explanation of the
+.Pa /etc/inetd.conf
+.Em protocol
+field below.
+.It Fl p
+Specify an alternate file in which to store the process ID.
+.El
+.Pp
+Upon execution,
+.Nm
+reads its configuration information from a configuration
+file which, by default, is
+.Pa /etc/inetd.conf .
+There must be an entry for each field of the configuration
+file, with entries for each field separated by a tab or
+a space.
+Comments are denoted by a
+.Dq #
+at the beginning
+of a line.
+There must be an entry for each field.
+The
+fields of the configuration file are as follows:
+.Pp
+.Bd -unfilled -offset indent -compact
+service-name
+socket-type
+protocol
+{wait|nowait}[/max-child[/max-connections-per-ip-per-minute[/max-child-per-ip]]]
+user[:group][/login-class]
+server-program
+server-program-arguments
+.Ed
+.Pp
+To specify an
+.Tn "ONC RPC" Ns -based
+service, the entry would contain these fields:
+.Pp
+.Bd -unfilled -offset indent -compact
+service-name/version
+socket-type
+rpc/protocol
+{wait|nowait}[/max-child[/max-connections-per-ip-per-minute[/max-child-per-ip]]]
+user[:group][/login-class]
+server-program
+server-program-arguments
+.Ed
+.Pp
+There are two types of services that
+.Nm
+can start: standard and TCPMUX.
+A standard service has a well-known port assigned to it;
+it may be a service that implements an official Internet standard or is a
+.Bx Ns -specific
+service.
+As described in
+.Tn RFC 1078 ,
+TCPMUX services are nonstandard services that do not have a
+well-known port assigned to them.
+They are invoked from
+.Nm
+when a program connects to the
+.Dq tcpmux
+well-known port and specifies
+the service name.
+This feature is useful for adding locally-developed servers.
+TCPMUX requests are only accepted when the multiplexor service itself
+is enabled, above and beyond and specific TCPMUX-based servers; see the
+discussion of internal services below.
+.Pp
+The
+.Em service-name
+entry is the name of a valid service in
+the file
+.Pa /etc/services ,
+or the specification of a
+.Ux
+domain socket (see below).
+For
+.Dq internal
+services (discussed below), the service
+name
+should
+be the official name of the service (that is, the first entry in
+.Pa /etc/services ) .
+When used to specify an
+.Tn "ONC RPC" Ns -based
+service, this field is a valid RPC service name listed in
+the file
+.Pa /etc/rpc .
+The part on the right of the
+.Dq /
+is the RPC version number.
+This
+can simply be a single numeric argument or a range of versions.
+A range is bounded by the low version to the high version -
+.Dq rusers/1-3 .
+For TCPMUX services, the value of the
+.Em service-name
+field consists of the string
+.Dq tcpmux
+followed by a slash and the
+locally-chosen service name.
+The service names listed in
+.Pa /etc/services
+and the name
+.Dq help
+are reserved.
+Try to choose unique names for your TCPMUX services by prefixing them with
+your organization's name and suffixing them with a version number.
+.Pp
+The
+.Em socket-type
+should be one of
+.Dq stream ,
+.Dq dgram ,
+.Dq raw ,
+.Dq rdm ,
+or
+.Dq seqpacket ,
+depending on whether the socket is a stream, datagram, raw,
+reliably delivered message, or sequenced packet socket.
+TCPMUX services must use
+.Dq stream .
+.Pp
+The
+.Em protocol
+must be a valid protocol or
+.Dq unix .
+Examples are
+.Dq tcp
+or
+.Dq udp ,
+both of which imply IPv4 for backward compatibility.
+The names
+.Dq tcp4
+and
+.Dq udp4
+specify IPv4 only.
+The names
+.Dq tcp6
+and
+.Dq udp6
+specify IPv6 only.
+The names
+.Dq tcp46
+and
+.Dq udp46
+specify that the entry accepts both IPv4 and IPv6 connections
+via a wildcard
+.Dv AF_INET6
+socket.
+Rpc based services
+are specified with the
+.Dq rpc/tcp
+or
+.Dq rpc/udp
+service type.
+One can use specify IPv4 and/or IPv6 with the 4, 6 or 46 suffix, for example
+.Dq rpc/tcp6
+or
+.Dq rpc/udp46 .
+TCPMUX services must use
+.Dq tcp ,
+.Dq tcp4 ,
+.Dq tcp6
+or
+.Dq tcp46 .
+.Pp
+The
+.Em wait/nowait
+entry specifies whether the server that is invoked by
+.Nm
+will take over
+the socket associated with the service access point, and thus whether
+.Nm
+should wait for the server to exit before listening for new service
+requests.
+Datagram servers must use
+.Dq wait ,
+as they are always invoked with the original datagram socket bound
+to the specified service address.
+These servers must read at least one datagram from the socket
+before exiting.
+If a datagram server connects
+to its peer, freeing the socket so
+.Nm
+can receive further messages on the socket, it is said to be
+a
+.Dq multi-threaded
+server;
+it should read one datagram from the socket and create a new socket
+connected to the peer.
+It should fork, and the parent should then exit
+to allow
+.Nm
+to check for new service requests to spawn new servers.
+Datagram servers which process all incoming datagrams
+on a socket and eventually time out are said to be
+.Dq single-threaded .
+The
+.Xr comsat 8
+and
+.Xr talkd 8
+utilities are examples of the latter type of
+datagram server.
+The
+.Xr tftpd 8
+utility is an example of a multi-threaded datagram server.
+.Pp
+Servers using stream sockets generally are multi-threaded and
+use the
+.Dq nowait
+entry.
+Connection requests for these services are accepted by
+.Nm ,
+and the server is given only the newly-accepted socket connected
+to a client of the service.
+Most stream-based services operate in this manner.
+Stream-based servers that use
+.Dq wait
+are started with the listening service socket, and must accept
+at least one connection request before exiting.
+Such a server would normally accept and process incoming connection
+requests until a timeout.
+TCPMUX services must use
+.Dq nowait .
+.Pp
+The maximum number of outstanding child processes (or
+.Dq threads )
+for a
+.Dq nowait
+service may be explicitly specified by appending a
+.Dq /
+followed by the number to the
+.Dq nowait
+keyword.
+Normally
+(or if a value of zero is specified) there is no maximum.
+Otherwise,
+once the maximum is reached, further connection attempts will be
+queued up until an existing child process exits.
+This also works
+in the case of
+.Dq wait
+mode, although a value other than one (the
+default) might not make sense in some cases.
+You can also specify the maximum number of connections per minute
+for a given IP address by appending
+a
+.Dq /
+followed by the number to the maximum number of
+outstanding child processes.
+Once the maximum is reached, further
+connections from this IP address will be dropped until the end of the
+minute.
+In addition, you can specify the maximum number of simultaneous
+invocations of each service from a single IP address by appending a
+.Dq /
+followed by the number to the maximum number of outstanding child
+processes.
+Once the maximum is reached, further connections from this
+IP address will be dropped.
+.Pp
+The
+.Em user
+entry should contain the user name of the user as whom the server
+should run.
+This allows for servers to be given less permission
+than root.
+The optional
+.Em group
+part separated by
+.Dq \&:
+allows a group name other
+than the default group for this user to be specified.
+The optional
+.Em login-class
+part separated by
+.Dq /
+allows specification of a login class other
+than the default
+.Dq daemon
+login class.
+.Pp
+The
+.Em server-program
+entry should contain the pathname of the program which is to be
+executed by
+.Nm
+when a request is found on its socket.
+If
+.Nm
+provides this service internally, this entry should
+be
+.Dq internal .
+.Pp
+The
+.Em server-program-arguments
+entry lists the arguments to be passed to the
+.Em server-program ,
+starting with argv[0], which usually is the name of
+the program.
+If the service is provided internally, the
+.Em service-name
+of the service (and any arguments to it) or the word
+.Dq internal
+should take the place of this entry.
+.Pp
+Currently, the only internal service to take arguments is
+.Dq auth .
+Without options, the service will always return
+.Dq ERROR\ : HIDDEN-USER .
+The available arguments to this service that alter its behavior are:
+.Bl -tag -width indent
+.It Fl d Ar fallback
+Provide a
+.Ar fallback
+username.
+If the real
+.Dq auth
+service is enabled
+(with the
+.Fl r
+option discussed below),
+return this username instead of an error
+when lookups fail
+for either socket credentials or the username.
+If the real
+.Dq auth
+service is disabled,
+return this username for every request.
+This is primarily useful when running this service on a NAT machine.
+.It Fl g
+Instead of returning
+the user's name to the ident requester,
+report a
+username made up of random alphanumeric characters,
+e.g.\&
+.Dq c0c993 .
+The
+.Fl g
+flag overrides not only the user names,
+but also any fallback name,
+.Pa .fakeid
+or
+.Pa .noident
+files.
+.It Fl t Xo
+.Ar sec Ns Op . Ns Ar usec
+.Xc
+Specify a timeout for the service.
+The default timeout is 10.0 seconds.
+.It Fl r
+Offer a real
+.Dq auth
+service, as per RFC 1413.
+All the remaining flags apply only in this case.
+.It Fl i
+Return numeric user IDs instead of usernames.
+.It Fl f
+If the file
+.Pa .fakeid
+exists in the home directory of the identified user, report the username
+found in that file instead of the real username.
+If the username found in
+.Pa .fakeid
+is that of an existing user,
+then the real username is reported.
+If the
+.Fl i
+flag is also given then the username in
+.Pa .fakeid
+is checked against existing user IDs instead.
+.It Fl F
+same as
+.Fl f
+but without the restriction that the username in
+.Pa .fakeid
+must not match an existing user.
+.It Fl n
+If the file
+.Pa .noident
+exists in the home directory of the identified user, return
+.Dq ERROR\ : HIDDEN-USER .
+This overrides any
+.Pa fakeid
+file which might exist.
+.It Fl o Ar osname
+Use
+.Ar osname
+instead of the name of the system as reported by
+.Xr uname 3 .
+.El
+.Pp
+The
+.Nm
+utility also provides several other
+.Dq trivial
+services internally by use of
+routines within itself.
+These services are
+.Dq echo ,
+.Dq discard ,
+.Dq chargen
+(character generator),
+.Dq daytime
+(human readable time), and
+.Dq time
+(machine readable time, in the form of the number of seconds since
+midnight, January 1, 1900).
+All of these services are available in
+both TCP and UDP versions; the UDP versions will refuse service if the
+request specifies a reply port corresponding to any internal service.
+(This is done as a defense against looping attacks; the remote IP address
+is logged.)
+For details of these services, consult the
+appropriate
+.Tn RFC
+document.
+.Pp
+The TCPMUX-demultiplexing service is also implemented as an internal service.
+For any TCPMUX-based service to function, the following line must be included
+in
+.Pa inetd.conf :
+.Bd -literal -offset indent
+tcpmux stream tcp nowait root internal
+.Ed
+.Pp
+When given the
+.Fl l
+option
+.Nm
+will log an entry to syslog each time a connection is accepted, noting the
+service selected and the IP-number of the remote requester if available.
+Unless otherwise specified in the configuration file,
+and in the absence of the
+.Fl W
+and
+.Fl w
+options,
+.Nm
+will log to the
+.Dq daemon
+facility.
+.Pp
+The
+.Nm
+utility rereads its configuration file when it receives a hangup signal,
+.Dv SIGHUP .
+Services may be added, deleted or modified when the configuration file
+is reread.
+Except when started in debugging mode,
+or configured otherwise with the
+.Fl p
+option,
+.Nm
+records its process ID in the file
+.Pa /var/run/inetd.pid
+to assist in reconfiguration.
+.Sh IMPLEMENTATION NOTES
+.Ss TCP Wrappers
+When given the
+.Fl w
+option,
+.Nm
+will wrap all services specified as
+.Dq stream nowait
+or
+.Dq dgram
+except for
+.Dq internal
+services.
+If the
+.Fl W
+option is given, such
+.Dq internal
+services will be wrapped.
+If both options are given, wrapping for both
+internal and external services will be enabled.
+Either wrapping option
+will cause failed connections to be logged to the
+.Dq auth
+syslog facility.
+Adding the
+.Fl l
+flag to the wrapping options will include successful connections in the
+logging to the
+.Dq auth
+facility.
+.Pp
+Note that
+.Nm
+only wraps requests for a
+.Dq wait
+service while no servers are available to service requests.
+Once a
+connection to such a service has been allowed,
+.Nm
+has no control
+over subsequent connections to the service until no more servers
+are left listening for connection requests.
+.Pp
+When wrapping is enabled, the
+.Pa tcpd
+daemon is not required, as that functionality is builtin.
+For more information on TCP Wrappers, see the relevant documentation
+.Pq Xr hosts_access 5 .
+When reading that document, keep in mind that
+.Dq internal
+services have no associated daemon name.
+Therefore, the service name
+as specified in
+.Pa inetd.conf
+should be used as the daemon name for
+.Dq internal
+services.
+.Ss TCPMUX
+.Tn RFC 1078
+describes the TCPMUX protocol:
+``A TCP client connects to a foreign host on TCP port 1.
+It sends the
+service name followed by a carriage-return line-feed <CRLF>.
+The
+service name is never case sensitive.
+The server replies with a
+single character indicating positive (+) or negative (\-)
+acknowledgment, immediately followed by an optional message of
+explanation, terminated with a <CRLF>.
+If the reply was positive,
+the selected protocol begins; otherwise the connection is closed.''
+The program is passed the TCP connection as file descriptors 0 and 1.
+.Pp
+If the TCPMUX service name begins with a
+.Dq + ,
+.Nm
+returns the positive reply for the program.
+This allows you to invoke programs that use stdin/stdout
+without putting any special server code in them.
+.Pp
+The special service name
+.Dq help
+causes
+.Nm
+to list the TCPMUX services which are enabled in
+.Pa inetd.conf .
+.Ss IPsec
+The implementation includes a tiny hack
+to support IPsec policy settings for each socket.
+A special form of comment line, starting with
+.Dq Li #@ ,
+is interpreted as a policy specifier.
+Everything after the
+.Dq Li #@
+will be used as an IPsec policy string,
+as described in
+.Xr ipsec_set_policy 3 .
+Each
+policy specifier is applied to all the following lines in
+.Pa inetd.conf
+until the next policy specifier.
+An empty policy specifier resets the IPsec policy.
+.Pp
+If an invalid IPsec policy specifier appears in
+.Pa inetd.conf ,
+.Nm
+will provide an error message via the
+.Xr syslog 3
+interface and abort execution.
+.Ss Ux Domain Sockets
+In addition to running services on IP sockets,
+.Nm
+can also manage
+.Ux
+domain sockets.
+To do this you specify a
+.Em protocol
+of
+.Dq unix
+and specify the
+.Ux
+domain socket as the
+.Em service-name .
+The
+.Em service-type
+may be
+.Dq stream
+or
+.Dq dgram .
+The specification of the socket must be
+an absolute path name,
+optionally prefixed by an owner and mode
+of the form
+.Em :user:group:mode: .
+The specification:
+.Pp
+.Dl ":news:daemon:220:/var/run/sock"
+.Pp
+creates a socket owned
+by user
+.Dq news
+in group
+.Dq daemon
+with permissions allowing only that user and group to connect.
+The default owner is the user that
+.Nm
+is running as.
+The default mode only allows the socket's owner to connect.
+.Pp
+.Sy WARNING :
+while creating a
+.Ux
+domain socket,
+.Nm
+must change the ownership and permissions on the socket.
+This can only be done securely if
+the directory in which the socket is created
+is writable only by root.
+Do
+.Em NOT
+use
+.Nm
+to create sockets in world writable directories
+such as
+.Pa /tmp ;
+use
+.Pa /var/run
+or a similar directory instead.
+.Pp
+Internal services may be run on
+.Ux
+domain sockets, in the usual way.
+In this case
+the name of the internal service
+is determined using
+the last component of the socket's pathname.
+For example, specifying a socket named
+.Pa /var/run/chargen
+would invoke the
+.Dq chargen
+service when a connection is received on that socket.
+.Sh "FILES"
+.Bl -tag -width /var/run/inetd.pid -compact
+.It Pa /etc/inetd.conf
+configuration file
+.It Pa /etc/netconfig
+network configuration data base
+.It Pa /etc/rpc
+translation of service names to RPC program numbers
+.It Pa /etc/services
+translation of service names to port numbers
+.It Pa /var/run/inetd.pid
+the pid of the currently running
+.Nm
+.El
+.Sh "EXAMPLES"
+Here are several example service entries for the various types of services:
+.Bd -literal
+ftp stream tcp nowait root /usr/libexec/ftpd ftpd -l
+ntalk dgram udp wait root /usr/libexec/ntalkd ntalkd
+telnet stream tcp6 nowait root /usr/libexec/telnetd telnetd
+shell stream tcp46 nowait root /usr/libexec/rshd rshd
+tcpmux/+date stream tcp nowait guest /bin/date date
+tcpmux/phonebook stream tcp nowait guest /usr/local/bin/phonebook phonebook
+rstatd/1-3 dgram rpc/udp wait root /usr/libexec/rpc.rstatd rpc.rstatd
+/var/run/echo stream unix nowait root internal
+#@ ipsec ah/require
+chargen stream tcp nowait root internal
+#@
+.Ed
+.Sh "ERROR MESSAGES"
+The
+.Nm
+server
+logs error messages using
+.Xr syslog 3 .
+Important error messages and their explanations are:
+.Pp
+.Bl -ohang -compact
+.It Xo
+.Ar service Ns / Ns Ar protocol
+.No "server failing (looping), service terminated."
+.Xc
+The number of requests for the specified service in the past minute
+exceeded the limit.
+The limit exists to prevent a broken program
+or a malicious user from swamping the system.
+This message may occur for several reasons:
+.Bl -enum -offset indent
+.It
+There are many hosts requesting the service within a short time period.
+.It
+A broken client program is requesting the service too frequently.
+.It
+A malicious user is running a program to invoke the service in
+a denial-of-service attack.
+.It
+The invoked service program has an error that causes clients
+to retry quickly.
+.El
+.Pp
+Use the
+.Fl R Ar rate
+option,
+as described above, to change the rate limit.
+Once the limit is reached, the service will be
+reenabled automatically in 10 minutes.
+.Pp
+.It Xo
+.Ar service Ns / Ns Ar protocol :
+.No \&No such user
+.Ar user ,
+.No service ignored
+.Xc
+.It Xo
+.Ar service Ns / Ns Ar protocol :
+.No getpwnam :
+.Ar user :
+.No \&No such user
+.Xc
+No entry for
+.Ar user
+exists in the
+.Xr passwd 5
+database.
+The first message
+occurs when
+.Nm
+(re)reads the configuration file.
+The second message occurs when the
+service is invoked.
+.Pp
+.It Xo
+.Ar service :
+.No can't set uid
+.Ar uid
+.Xc
+.It Xo
+.Ar service :
+.No can't set gid
+.Ar gid
+.Xc
+The user or group ID for the entry's
+.Ar user
+field is invalid.
+.Pp
+.It "setsockopt(SO_PRIVSTATE): Operation not supported"
+The
+.Nm
+utility attempted to renounce the privileged state associated with a
+socket but was unable to.
+.Pp
+.It Xo unknown
+.Ar rpc/udp
+or
+.Ar rpc/tcp
+.Xc
+No entry was found for either
+.Ar udp
+or
+.Ar tcp
+in the
+.Xr netconfig 5
+database.
+.Pp
+.It Xo unknown
+.Ar rpc/udp6
+or
+.Ar rpc/tcp6
+.Xc
+No entry was found for either
+.Ar udp6
+or
+.Ar tcp6
+in the
+.Xr netconfig 5
+database.
+.El
+.Sh SEE ALSO
+.Xr ipsec_set_policy 3 ,
+.Xr hosts_access 5 ,
+.Xr hosts_options 5 ,
+.Xr login.conf 5 ,
+.Xr netconfig 5 ,
+.Xr passwd 5 ,
+.Xr rpc 5 ,
+.Xr services 5 ,
+.Xr comsat 8 ,
+.Xr fingerd 8 ,
+.Xr ftpd 8 ,
+.Xr rlogind 8 ,
+.Xr rpcbind 8 ,
+.Xr rshd 8 ,
+.Xr talkd 8 ,
+.Xr telnetd 8 ,
+.Xr tftpd 8
+.Rs
+.%A Michael C. St. Johns
+.%T Identification Protocol
+.%O RFC1413
+.Re
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.3 .
+TCPMUX is based on code and documentation by Mark Lottor.
+Support for
+.Tn "ONC RPC"
+based services is modeled after that
+provided by
+.Tn SunOS
+4.1.
+The IPsec hack was contributed by the KAME project in 1999.
+The
+.Fx
+TCP Wrappers support first appeared in
+.Fx 3.2 .
diff --git a/usr.sbin/inetd/inetd.c b/usr.sbin/inetd/inetd.c
new file mode 100644
index 0000000..09690ab
--- /dev/null
+++ b/usr.sbin/inetd/inetd.c
@@ -0,0 +1,2565 @@
+/*
+ * Copyright (c) 1983, 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) 1983, 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[] = "@(#)from: inetd.c 8.4 (Berkeley) 4/13/94";
+#endif
+#endif /* not lint */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Inetd - Internet super-server
+ *
+ * This program invokes all internet services as needed. Connection-oriented
+ * services are invoked each time a connection is made, by creating a process.
+ * This process is passed the connection as file descriptor 0 and is expected
+ * to do a getpeername to find out the source host and port.
+ *
+ * Datagram oriented services are invoked when a datagram
+ * arrives; a process is created and passed a pending message
+ * on file descriptor 0. Datagram servers may either connect
+ * to their peer, freeing up the original socket for inetd
+ * to receive further messages on, or ``take over the socket'',
+ * processing all arriving datagrams and, eventually, timing
+ * out. The first type of server is said to be ``multi-threaded'';
+ * the second type of server ``single-threaded''.
+ *
+ * Inetd uses a configuration file which is read at startup
+ * and, possibly, at some later time in response to a hangup signal.
+ * The configuration file is ``free format'' with fields given in the
+ * order shown below. Continuation lines for an entry must begin with
+ * a space or tab. All fields must be present in each entry.
+ *
+ * service name must be in /etc/services
+ * or name a tcpmux service
+ * or specify a unix domain socket
+ * socket type stream/dgram/raw/rdm/seqpacket
+ * protocol tcp[4][6], udp[4][6], unix
+ * wait/nowait single-threaded/multi-threaded
+ * user[:group][/login-class] user/group/login-class to run daemon as
+ * server program full path name
+ * server program arguments maximum of MAXARGS (20)
+ *
+ * TCP services without official port numbers are handled with the
+ * RFC1078-based tcpmux internal service. Tcpmux listens on port 1 for
+ * requests. When a connection is made from a foreign host, the service
+ * requested is passed to tcpmux, which looks it up in the servtab list
+ * and returns the proper entry for the service. Tcpmux returns a
+ * negative reply if the service doesn't exist, otherwise the invoked
+ * server is expected to return the positive reply if the service type in
+ * inetd.conf file has the prefix "tcpmux/". If the service type has the
+ * prefix "tcpmux/+", tcpmux will return the positive reply for the
+ * process; this is for compatibility with older server code, and also
+ * allows you to invoke programs that use stdin/stdout without putting any
+ * special server code in them. Services that use tcpmux are "nowait"
+ * because they do not have a well-known port and hence cannot listen
+ * for new requests.
+ *
+ * For RPC services
+ * service name/version must be in /etc/rpc
+ * socket type stream/dgram/raw/rdm/seqpacket
+ * protocol rpc/tcp[4][6], rpc/udp[4][6]
+ * wait/nowait single-threaded/multi-threaded
+ * user[:group][/login-class] user/group/login-class to run daemon as
+ * server program full path name
+ * server program arguments maximum of MAXARGS
+ *
+ * Comment lines are indicated by a `#' in column 1.
+ *
+ * #ifdef IPSEC
+ * Comment lines that start with "#@" denote IPsec policy string, as described
+ * in ipsec_set_policy(3). This will affect all the following items in
+ * inetd.conf(8). To reset the policy, just use "#@" line. By default,
+ * there's no IPsec policy.
+ * #endif
+ */
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <rpc/rpc.h>
+#include <rpc/pmap_clnt.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <err.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <libutil.h>
+#include <limits.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <syslog.h>
+#include <tcpd.h>
+#include <unistd.h>
+
+#include "inetd.h"
+#include "pathnames.h"
+
+#ifdef IPSEC
+#include <netipsec/ipsec.h>
+#ifndef IPSEC_POLICY_IPSEC /* no ipsec support on old ipsec */
+#undef IPSEC
+#endif
+#endif
+
+#ifndef LIBWRAP_ALLOW_FACILITY
+# define LIBWRAP_ALLOW_FACILITY LOG_AUTH
+#endif
+#ifndef LIBWRAP_ALLOW_SEVERITY
+# define LIBWRAP_ALLOW_SEVERITY LOG_INFO
+#endif
+#ifndef LIBWRAP_DENY_FACILITY
+# define LIBWRAP_DENY_FACILITY LOG_AUTH
+#endif
+#ifndef LIBWRAP_DENY_SEVERITY
+# define LIBWRAP_DENY_SEVERITY LOG_WARNING
+#endif
+
+#define ISWRAP(sep) \
+ ( ((wrap_ex && !(sep)->se_bi) || (wrap_bi && (sep)->se_bi)) \
+ && (sep->se_family == AF_INET || sep->se_family == AF_INET6) \
+ && ( ((sep)->se_accept && (sep)->se_socktype == SOCK_STREAM) \
+ || (sep)->se_socktype == SOCK_DGRAM))
+
+#ifdef LOGIN_CAP
+#include <login_cap.h>
+
+/* see init.c */
+#define RESOURCE_RC "daemon"
+
+#endif
+
+#ifndef MAXCHILD
+#define MAXCHILD -1 /* maximum number of this service
+ < 0 = no limit */
+#endif
+
+#ifndef MAXCPM
+#define MAXCPM -1 /* rate limit invocations from a
+ single remote address,
+ < 0 = no limit */
+#endif
+
+#ifndef MAXPERIP
+#define MAXPERIP -1 /* maximum number of this service
+ from a single remote address,
+ < 0 = no limit */
+#endif
+
+#ifndef TOOMANY
+#define TOOMANY 256 /* don't start more than TOOMANY */
+#endif
+#define CNT_INTVL 60 /* servers in CNT_INTVL sec. */
+#define RETRYTIME (60*10) /* retry after bind or server fail */
+#define MAX_MAXCHLD 32767 /* max allowable max children */
+
+#define SIGBLOCK (sigmask(SIGCHLD)|sigmask(SIGHUP)|sigmask(SIGALRM))
+
+void close_sep(struct servtab *);
+void flag_signal(int);
+void flag_config(int);
+void config(void);
+int cpmip(const struct servtab *, int);
+void endconfig(void);
+struct servtab *enter(struct servtab *);
+void freeconfig(struct servtab *);
+struct servtab *getconfigent(void);
+int matchservent(const char *, const char *, const char *);
+char *nextline(FILE *);
+void addchild(struct servtab *, int);
+void flag_reapchild(int);
+void reapchild(void);
+void enable(struct servtab *);
+void disable(struct servtab *);
+void flag_retry(int);
+void retry(void);
+int setconfig(void);
+void setup(struct servtab *);
+#ifdef IPSEC
+void ipsecsetup(struct servtab *);
+#endif
+void unregisterrpc(register struct servtab *sep);
+static struct conninfo *search_conn(struct servtab *sep, int ctrl);
+static int room_conn(struct servtab *sep, struct conninfo *conn);
+static void addchild_conn(struct conninfo *conn, pid_t pid);
+static void reapchild_conn(pid_t pid);
+static void free_conn(struct conninfo *conn);
+static void resize_conn(struct servtab *sep, int maxperip);
+static void free_connlist(struct servtab *sep);
+static void free_proc(struct procinfo *);
+static struct procinfo *search_proc(pid_t pid, int add);
+static int hashval(char *p, int len);
+
+int allow_severity;
+int deny_severity;
+int wrap_ex = 0;
+int wrap_bi = 0;
+int debug = 0;
+int dolog = 0;
+int maxsock; /* highest-numbered descriptor */
+fd_set allsock;
+int options;
+int timingout;
+int toomany = TOOMANY;
+int maxchild = MAXCHILD;
+int maxcpm = MAXCPM;
+int maxperip = MAXPERIP;
+struct servent *sp;
+struct rpcent *rpc;
+char *hostname = NULL;
+struct sockaddr_in *bind_sa4;
+int v4bind_ok = 0;
+#ifdef INET6
+struct sockaddr_in6 *bind_sa6;
+int v6bind_ok = 0;
+#endif
+int signalpipe[2];
+#ifdef SANITY_CHECK
+int nsock;
+#endif
+uid_t euid;
+gid_t egid;
+mode_t mask;
+
+struct servtab *servtab;
+
+extern struct biltin biltins[];
+
+const char *CONFIG = _PATH_INETDCONF;
+const char *pid_file = _PATH_INETDPID;
+struct pidfh *pfh = NULL;
+
+struct netconfig *udpconf, *tcpconf, *udp6conf, *tcp6conf;
+
+static LIST_HEAD(, procinfo) proctable[PERIPSIZE];
+
+int
+getvalue(const char *arg, int *value, const char *whine)
+{
+ int tmp;
+ char *p;
+
+ tmp = strtol(arg, &p, 0);
+ if (tmp < 0 || *p) {
+ syslog(LOG_ERR, whine, arg);
+ return 1; /* failure */
+ }
+ *value = tmp;
+ return 0; /* success */
+}
+
+static sa_family_t
+whichaf(struct request_info *req)
+{
+ struct sockaddr *sa;
+
+ sa = (struct sockaddr *)req->client->sin;
+ if (sa == NULL)
+ return AF_UNSPEC;
+ if (sa->sa_family == AF_INET6 &&
+ IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)sa)->sin6_addr))
+ return AF_INET;
+ return sa->sa_family;
+}
+
+int
+main(int argc, char **argv)
+{
+ struct servtab *sep;
+ struct passwd *pwd;
+ struct group *grp;
+ struct sigaction sa, saalrm, sachld, sahup, sapipe;
+ int ch, dofork;
+ pid_t pid;
+ char buf[50];
+#ifdef LOGIN_CAP
+ login_cap_t *lc = NULL;
+#endif
+ struct request_info req;
+ int denied;
+ char *service = NULL;
+ struct sockaddr_storage peer;
+ int i;
+ struct addrinfo hints, *res;
+ const char *servname;
+ int error;
+ struct conninfo *conn;
+
+ openlog("inetd", LOG_PID | LOG_NOWAIT | LOG_PERROR, LOG_DAEMON);
+
+ while ((ch = getopt(argc, argv, "dlwWR:a:c:C:p:s:")) != -1)
+ switch(ch) {
+ case 'd':
+ debug = 1;
+ options |= SO_DEBUG;
+ break;
+ case 'l':
+ dolog = 1;
+ break;
+ case 'R':
+ getvalue(optarg, &toomany,
+ "-R %s: bad value for service invocation rate");
+ break;
+ case 'c':
+ getvalue(optarg, &maxchild,
+ "-c %s: bad value for maximum children");
+ break;
+ case 'C':
+ getvalue(optarg, &maxcpm,
+ "-C %s: bad value for maximum children/minute");
+ break;
+ case 'a':
+ hostname = optarg;
+ break;
+ case 'p':
+ pid_file = optarg;
+ break;
+ case 's':
+ getvalue(optarg, &maxperip,
+ "-s %s: bad value for maximum children per source address");
+ break;
+ case 'w':
+ wrap_ex++;
+ break;
+ case 'W':
+ wrap_bi++;
+ break;
+ case '?':
+ default:
+ syslog(LOG_ERR,
+ "usage: inetd [-dlwW] [-a address] [-R rate]"
+ " [-c maximum] [-C rate]"
+ " [-p pidfile] [conf-file]");
+ exit(EX_USAGE);
+ }
+ /*
+ * Initialize Bind Addrs.
+ * When hostname is NULL, wild card bind addrs are obtained from
+ * getaddrinfo(). But getaddrinfo() requires at least one of
+ * hostname or servname is non NULL.
+ * So when hostname is NULL, set dummy value to servname.
+ * Since getaddrinfo() doesn't accept numeric servname, and
+ * we doesn't use ai_socktype of struct addrinfo returned
+ * from getaddrinfo(), we set dummy value to ai_socktype.
+ */
+ servname = (hostname == NULL) ? "0" /* dummy */ : NULL;
+
+ bzero(&hints, sizeof(struct addrinfo));
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM; /* dummy */
+ error = getaddrinfo(hostname, servname, &hints, &res);
+ if (error != 0) {
+ syslog(LOG_ERR, "-a %s: %s", hostname, gai_strerror(error));
+ if (error == EAI_SYSTEM)
+ syslog(LOG_ERR, "%s", strerror(errno));
+ exit(EX_USAGE);
+ }
+ do {
+ if (res->ai_addr == NULL) {
+ syslog(LOG_ERR, "-a %s: getaddrinfo failed", hostname);
+ exit(EX_USAGE);
+ }
+ switch (res->ai_addr->sa_family) {
+ case AF_INET:
+ if (v4bind_ok)
+ continue;
+ bind_sa4 = (struct sockaddr_in *)res->ai_addr;
+ /* init port num in case servname is dummy */
+ bind_sa4->sin_port = 0;
+ v4bind_ok = 1;
+ continue;
+#ifdef INET6
+ case AF_INET6:
+ if (v6bind_ok)
+ continue;
+ bind_sa6 = (struct sockaddr_in6 *)res->ai_addr;
+ /* init port num in case servname is dummy */
+ bind_sa6->sin6_port = 0;
+ v6bind_ok = 1;
+ continue;
+#endif
+ }
+ if (v4bind_ok
+#ifdef INET6
+ && v6bind_ok
+#endif
+ )
+ break;
+ } while ((res = res->ai_next) != NULL);
+ if (!v4bind_ok
+#ifdef INET6
+ && !v6bind_ok
+#endif
+ ) {
+ syslog(LOG_ERR, "-a %s: unknown address family", hostname);
+ exit(EX_USAGE);
+ }
+
+ euid = geteuid();
+ egid = getegid();
+ umask(mask = umask(0777));
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0)
+ CONFIG = argv[0];
+ if (access(CONFIG, R_OK) < 0)
+ syslog(LOG_ERR, "Accessing %s: %m, continuing anyway.", CONFIG);
+ if (debug == 0) {
+ pid_t otherpid;
+
+ pfh = pidfile_open(pid_file, 0600, &otherpid);
+ if (pfh == NULL) {
+ if (errno == EEXIST) {
+ syslog(LOG_ERR, "%s already running, pid: %d",
+ getprogname(), otherpid);
+ exit(EX_OSERR);
+ }
+ syslog(LOG_WARNING, "pidfile_open() failed: %m");
+ }
+
+ if (daemon(0, 0) < 0) {
+ syslog(LOG_WARNING, "daemon(0,0) failed: %m");
+ }
+ /* From now on we don't want syslog messages going to stderr. */
+ closelog();
+ openlog("inetd", LOG_PID | LOG_NOWAIT, LOG_DAEMON);
+ /*
+ * In case somebody has started inetd manually, we need to
+ * clear the logname, so that old servers run as root do not
+ * get the user's logname..
+ */
+ if (setlogin("") < 0) {
+ syslog(LOG_WARNING, "cannot clear logname: %m");
+ /* no big deal if it fails.. */
+ }
+ if (pfh != NULL && pidfile_write(pfh) == -1) {
+ syslog(LOG_WARNING, "pidfile_write(): %m");
+ }
+ }
+
+ if (madvise(NULL, 0, MADV_PROTECT) != 0)
+ syslog(LOG_WARNING, "madvise() failed: %s", strerror(errno));
+
+ for (i = 0; i < PERIPSIZE; ++i)
+ LIST_INIT(&proctable[i]);
+
+ if (v4bind_ok) {
+ udpconf = getnetconfigent("udp");
+ tcpconf = getnetconfigent("tcp");
+ if (udpconf == NULL || tcpconf == NULL) {
+ syslog(LOG_ERR, "unknown rpc/udp or rpc/tcp");
+ exit(EX_USAGE);
+ }
+ }
+#ifdef INET6
+ if (v6bind_ok) {
+ udp6conf = getnetconfigent("udp6");
+ tcp6conf = getnetconfigent("tcp6");
+ if (udp6conf == NULL || tcp6conf == NULL) {
+ syslog(LOG_ERR, "unknown rpc/udp6 or rpc/tcp6");
+ exit(EX_USAGE);
+ }
+ }
+#endif
+
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ sigaddset(&sa.sa_mask, SIGALRM);
+ sigaddset(&sa.sa_mask, SIGCHLD);
+ sigaddset(&sa.sa_mask, SIGHUP);
+ sa.sa_handler = flag_retry;
+ sigaction(SIGALRM, &sa, &saalrm);
+ config();
+ sa.sa_handler = flag_config;
+ sigaction(SIGHUP, &sa, &sahup);
+ sa.sa_handler = flag_reapchild;
+ sigaction(SIGCHLD, &sa, &sachld);
+ sa.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &sa, &sapipe);
+
+ {
+ /* space for daemons to overwrite environment for ps */
+#define DUMMYSIZE 100
+ char dummy[DUMMYSIZE];
+
+ (void)memset(dummy, 'x', DUMMYSIZE - 1);
+ dummy[DUMMYSIZE - 1] = '\0';
+ (void)setenv("inetd_dummy", dummy, 1);
+ }
+
+ if (pipe(signalpipe) != 0) {
+ syslog(LOG_ERR, "pipe: %m");
+ exit(EX_OSERR);
+ }
+ if (fcntl(signalpipe[0], F_SETFD, FD_CLOEXEC) < 0 ||
+ fcntl(signalpipe[1], F_SETFD, FD_CLOEXEC) < 0) {
+ syslog(LOG_ERR, "signalpipe: fcntl (F_SETFD, FD_CLOEXEC): %m");
+ exit(EX_OSERR);
+ }
+ FD_SET(signalpipe[0], &allsock);
+#ifdef SANITY_CHECK
+ nsock++;
+#endif
+ if (signalpipe[0] > maxsock)
+ maxsock = signalpipe[0];
+ if (signalpipe[1] > maxsock)
+ maxsock = signalpipe[1];
+
+ for (;;) {
+ int n, ctrl;
+ fd_set readable;
+
+#ifdef SANITY_CHECK
+ if (nsock == 0) {
+ syslog(LOG_ERR, "%s: nsock=0", __func__);
+ exit(EX_SOFTWARE);
+ }
+#endif
+ readable = allsock;
+ if ((n = select(maxsock + 1, &readable, (fd_set *)0,
+ (fd_set *)0, (struct timeval *)0)) <= 0) {
+ if (n < 0 && errno != EINTR) {
+ syslog(LOG_WARNING, "select: %m");
+ sleep(1);
+ }
+ continue;
+ }
+ /* handle any queued signal flags */
+ if (FD_ISSET(signalpipe[0], &readable)) {
+ int nsig;
+ if (ioctl(signalpipe[0], FIONREAD, &nsig) != 0) {
+ syslog(LOG_ERR, "ioctl: %m");
+ exit(EX_OSERR);
+ }
+ while (--nsig >= 0) {
+ char c;
+ if (read(signalpipe[0], &c, 1) != 1) {
+ syslog(LOG_ERR, "read: %m");
+ exit(EX_OSERR);
+ }
+ if (debug)
+ warnx("handling signal flag %c", c);
+ switch(c) {
+ case 'A': /* sigalrm */
+ retry();
+ break;
+ case 'C': /* sigchld */
+ reapchild();
+ break;
+ case 'H': /* sighup */
+ config();
+ break;
+ }
+ }
+ }
+ for (sep = servtab; n && sep; sep = sep->se_next)
+ if (sep->se_fd != -1 && FD_ISSET(sep->se_fd, &readable)) {
+ n--;
+ if (debug)
+ warnx("someone wants %s", sep->se_service);
+ dofork = !sep->se_bi || sep->se_bi->bi_fork || ISWRAP(sep);
+ conn = NULL;
+ if (sep->se_accept && sep->se_socktype == SOCK_STREAM) {
+ i = 1;
+ if (ioctl(sep->se_fd, FIONBIO, &i) < 0)
+ syslog(LOG_ERR, "ioctl (FIONBIO, 1): %m");
+ ctrl = accept(sep->se_fd, (struct sockaddr *)0,
+ (socklen_t *)0);
+ if (debug)
+ warnx("accept, ctrl %d", ctrl);
+ if (ctrl < 0) {
+ if (errno != EINTR)
+ syslog(LOG_WARNING,
+ "accept (for %s): %m",
+ sep->se_service);
+ if (sep->se_accept &&
+ sep->se_socktype == SOCK_STREAM)
+ close(ctrl);
+ continue;
+ }
+ i = 0;
+ if (ioctl(sep->se_fd, FIONBIO, &i) < 0)
+ syslog(LOG_ERR, "ioctl1(FIONBIO, 0): %m");
+ if (ioctl(ctrl, FIONBIO, &i) < 0)
+ syslog(LOG_ERR, "ioctl2(FIONBIO, 0): %m");
+ if (cpmip(sep, ctrl) < 0) {
+ close(ctrl);
+ continue;
+ }
+ if (dofork &&
+ (conn = search_conn(sep, ctrl)) != NULL &&
+ !room_conn(sep, conn)) {
+ close(ctrl);
+ continue;
+ }
+ } else
+ ctrl = sep->se_fd;
+ if (dolog && !ISWRAP(sep)) {
+ char pname[NI_MAXHOST] = "unknown";
+ socklen_t sl;
+ sl = sizeof(peer);
+ if (getpeername(ctrl, (struct sockaddr *)
+ &peer, &sl)) {
+ sl = sizeof(peer);
+ if (recvfrom(ctrl, buf, sizeof(buf),
+ MSG_PEEK,
+ (struct sockaddr *)&peer,
+ &sl) >= 0) {
+ getnameinfo((struct sockaddr *)&peer,
+ peer.ss_len,
+ pname, sizeof(pname),
+ NULL, 0, NI_NUMERICHOST);
+ }
+ } else {
+ getnameinfo((struct sockaddr *)&peer,
+ peer.ss_len,
+ pname, sizeof(pname),
+ NULL, 0, NI_NUMERICHOST);
+ }
+ syslog(LOG_INFO,"%s from %s", sep->se_service, pname);
+ }
+ (void) sigblock(SIGBLOCK);
+ pid = 0;
+ /*
+ * Fork for all external services, builtins which need to
+ * fork and anything we're wrapping (as wrapping might
+ * block or use hosts_options(5) twist).
+ */
+ if (dofork) {
+ if (sep->se_count++ == 0)
+ (void)clock_gettime(CLOCK_MONOTONIC_FAST, &sep->se_time);
+ else if (toomany > 0 && sep->se_count >= toomany) {
+ struct timespec now;
+
+ (void)clock_gettime(CLOCK_MONOTONIC_FAST, &now);
+ if (now.tv_sec - sep->se_time.tv_sec >
+ CNT_INTVL) {
+ sep->se_time = now;
+ sep->se_count = 1;
+ } else {
+ syslog(LOG_ERR,
+ "%s/%s server failing (looping), service terminated",
+ sep->se_service, sep->se_proto);
+ if (sep->se_accept &&
+ sep->se_socktype == SOCK_STREAM)
+ close(ctrl);
+ close_sep(sep);
+ free_conn(conn);
+ sigsetmask(0L);
+ if (!timingout) {
+ timingout = 1;
+ alarm(RETRYTIME);
+ }
+ continue;
+ }
+ }
+ pid = fork();
+ }
+ if (pid < 0) {
+ syslog(LOG_ERR, "fork: %m");
+ if (sep->se_accept &&
+ sep->se_socktype == SOCK_STREAM)
+ close(ctrl);
+ free_conn(conn);
+ sigsetmask(0L);
+ sleep(1);
+ continue;
+ }
+ if (pid) {
+ addchild_conn(conn, pid);
+ addchild(sep, pid);
+ }
+ sigsetmask(0L);
+ if (pid == 0) {
+ pidfile_close(pfh);
+ if (dofork) {
+ sigaction(SIGALRM, &saalrm, (struct sigaction *)0);
+ sigaction(SIGCHLD, &sachld, (struct sigaction *)0);
+ sigaction(SIGHUP, &sahup, (struct sigaction *)0);
+ /* SIGPIPE reset before exec */
+ }
+ /*
+ * Call tcpmux to find the real service to exec.
+ */
+ if (sep->se_bi &&
+ sep->se_bi->bi_fn == (bi_fn_t *) tcpmux) {
+ sep = tcpmux(ctrl);
+ if (sep == NULL) {
+ close(ctrl);
+ _exit(0);
+ }
+ }
+ if (ISWRAP(sep)) {
+ inetd_setproctitle("wrapping", ctrl);
+ service = sep->se_server_name ?
+ sep->se_server_name : sep->se_service;
+ request_init(&req, RQ_DAEMON, service, RQ_FILE, ctrl, 0);
+ fromhost(&req);
+ deny_severity = LIBWRAP_DENY_FACILITY|LIBWRAP_DENY_SEVERITY;
+ allow_severity = LIBWRAP_ALLOW_FACILITY|LIBWRAP_ALLOW_SEVERITY;
+ denied = !hosts_access(&req);
+ if (denied) {
+ syslog(deny_severity,
+ "refused connection from %.500s, service %s (%s%s)",
+ eval_client(&req), service, sep->se_proto,
+ (whichaf(&req) == AF_INET6) ? "6" : "");
+ if (sep->se_socktype != SOCK_STREAM)
+ recv(ctrl, buf, sizeof (buf), 0);
+ if (dofork) {
+ sleep(1);
+ _exit(0);
+ }
+ }
+ if (dolog) {
+ syslog(allow_severity,
+ "connection from %.500s, service %s (%s%s)",
+ eval_client(&req), service, sep->se_proto,
+ (whichaf(&req) == AF_INET6) ? "6" : "");
+ }
+ }
+ if (sep->se_bi) {
+ (*sep->se_bi->bi_fn)(ctrl, sep);
+ } else {
+ if (debug)
+ warnx("%d execl %s",
+ getpid(), sep->se_server);
+ /* Clear close-on-exec. */
+ if (fcntl(ctrl, F_SETFD, 0) < 0) {
+ syslog(LOG_ERR,
+ "%s/%s: fcntl (F_SETFD, 0): %m",
+ sep->se_service, sep->se_proto);
+ _exit(EX_OSERR);
+ }
+ if (ctrl != 0) {
+ dup2(ctrl, 0);
+ close(ctrl);
+ }
+ dup2(0, 1);
+ dup2(0, 2);
+ if ((pwd = getpwnam(sep->se_user)) == NULL) {
+ syslog(LOG_ERR,
+ "%s/%s: %s: no such user",
+ sep->se_service, sep->se_proto,
+ sep->se_user);
+ if (sep->se_socktype != SOCK_STREAM)
+ recv(0, buf, sizeof (buf), 0);
+ _exit(EX_NOUSER);
+ }
+ grp = NULL;
+ if ( sep->se_group != NULL
+ && (grp = getgrnam(sep->se_group)) == NULL
+ ) {
+ syslog(LOG_ERR,
+ "%s/%s: %s: no such group",
+ sep->se_service, sep->se_proto,
+ sep->se_group);
+ if (sep->se_socktype != SOCK_STREAM)
+ recv(0, buf, sizeof (buf), 0);
+ _exit(EX_NOUSER);
+ }
+ if (grp != NULL)
+ pwd->pw_gid = grp->gr_gid;
+#ifdef LOGIN_CAP
+ if ((lc = login_getclass(sep->se_class)) == NULL) {
+ /* error syslogged by getclass */
+ syslog(LOG_ERR,
+ "%s/%s: %s: login class error",
+ sep->se_service, sep->se_proto,
+ sep->se_class);
+ if (sep->se_socktype != SOCK_STREAM)
+ recv(0, buf, sizeof (buf), 0);
+ _exit(EX_NOUSER);
+ }
+#endif
+ if (setsid() < 0) {
+ syslog(LOG_ERR,
+ "%s: can't setsid(): %m",
+ sep->se_service);
+ /* _exit(EX_OSERR); not fatal yet */
+ }
+#ifdef LOGIN_CAP
+ if (setusercontext(lc, pwd, pwd->pw_uid,
+ LOGIN_SETALL & ~LOGIN_SETMAC)
+ != 0) {
+ syslog(LOG_ERR,
+ "%s: can't setusercontext(..%s..): %m",
+ sep->se_service, sep->se_user);
+ _exit(EX_OSERR);
+ }
+ login_close(lc);
+#else
+ if (pwd->pw_uid) {
+ if (setlogin(sep->se_user) < 0) {
+ syslog(LOG_ERR,
+ "%s: can't setlogin(%s): %m",
+ sep->se_service, sep->se_user);
+ /* _exit(EX_OSERR); not yet */
+ }
+ if (setgid(pwd->pw_gid) < 0) {
+ syslog(LOG_ERR,
+ "%s: can't set gid %d: %m",
+ sep->se_service, pwd->pw_gid);
+ _exit(EX_OSERR);
+ }
+ (void) initgroups(pwd->pw_name,
+ pwd->pw_gid);
+ if (setuid(pwd->pw_uid) < 0) {
+ syslog(LOG_ERR,
+ "%s: can't set uid %d: %m",
+ sep->se_service, pwd->pw_uid);
+ _exit(EX_OSERR);
+ }
+ }
+#endif
+ sigaction(SIGPIPE, &sapipe,
+ (struct sigaction *)0);
+ execv(sep->se_server, sep->se_argv);
+ syslog(LOG_ERR,
+ "cannot execute %s: %m", sep->se_server);
+ if (sep->se_socktype != SOCK_STREAM)
+ recv(0, buf, sizeof (buf), 0);
+ }
+ if (dofork)
+ _exit(0);
+ }
+ if (sep->se_accept && sep->se_socktype == SOCK_STREAM)
+ close(ctrl);
+ }
+ }
+}
+
+/*
+ * Add a signal flag to the signal flag queue for later handling
+ */
+
+void
+flag_signal(int c)
+{
+ char ch = c;
+
+ if (write(signalpipe[1], &ch, 1) != 1) {
+ syslog(LOG_ERR, "write: %m");
+ _exit(EX_OSERR);
+ }
+}
+
+/*
+ * Record a new child pid for this service. If we've reached the
+ * limit on children, then stop accepting incoming requests.
+ */
+
+void
+addchild(struct servtab *sep, pid_t pid)
+{
+ if (sep->se_maxchild <= 0)
+ return;
+#ifdef SANITY_CHECK
+ if (sep->se_numchild >= sep->se_maxchild) {
+ syslog(LOG_ERR, "%s: %d >= %d",
+ __func__, sep->se_numchild, sep->se_maxchild);
+ exit(EX_SOFTWARE);
+ }
+#endif
+ sep->se_pids[sep->se_numchild++] = pid;
+ if (sep->se_numchild == sep->se_maxchild)
+ disable(sep);
+}
+
+/*
+ * Some child process has exited. See if it's on somebody's list.
+ */
+
+void
+flag_reapchild(int signo __unused)
+{
+ flag_signal('C');
+}
+
+void
+reapchild(void)
+{
+ int k, status;
+ pid_t pid;
+ struct servtab *sep;
+
+ for (;;) {
+ pid = wait3(&status, WNOHANG, (struct rusage *)0);
+ if (pid <= 0)
+ break;
+ if (debug)
+ warnx("%d reaped, %s %u", pid,
+ WIFEXITED(status) ? "status" : "signal",
+ WIFEXITED(status) ? WEXITSTATUS(status)
+ : WTERMSIG(status));
+ for (sep = servtab; sep; sep = sep->se_next) {
+ for (k = 0; k < sep->se_numchild; k++)
+ if (sep->se_pids[k] == pid)
+ break;
+ if (k == sep->se_numchild)
+ continue;
+ if (sep->se_numchild == sep->se_maxchild)
+ enable(sep);
+ sep->se_pids[k] = sep->se_pids[--sep->se_numchild];
+ if (WIFSIGNALED(status) || WEXITSTATUS(status))
+ syslog(LOG_WARNING,
+ "%s[%d]: exited, %s %u",
+ sep->se_server, pid,
+ WIFEXITED(status) ? "status" : "signal",
+ WIFEXITED(status) ? WEXITSTATUS(status)
+ : WTERMSIG(status));
+ break;
+ }
+ reapchild_conn(pid);
+ }
+}
+
+void
+flag_config(int signo __unused)
+{
+ flag_signal('H');
+}
+
+void
+config(void)
+{
+ struct servtab *sep, *new, **sepp;
+ long omask;
+ int new_nomapped;
+#ifdef LOGIN_CAP
+ login_cap_t *lc = NULL;
+#endif
+
+ if (!setconfig()) {
+ syslog(LOG_ERR, "%s: %m", CONFIG);
+ return;
+ }
+ for (sep = servtab; sep; sep = sep->se_next)
+ sep->se_checked = 0;
+ while ((new = getconfigent())) {
+ if (getpwnam(new->se_user) == NULL) {
+ syslog(LOG_ERR,
+ "%s/%s: no such user '%s', service ignored",
+ new->se_service, new->se_proto, new->se_user);
+ continue;
+ }
+ if (new->se_group && getgrnam(new->se_group) == NULL) {
+ syslog(LOG_ERR,
+ "%s/%s: no such group '%s', service ignored",
+ new->se_service, new->se_proto, new->se_group);
+ continue;
+ }
+#ifdef LOGIN_CAP
+ if ((lc = login_getclass(new->se_class)) == NULL) {
+ /* error syslogged by getclass */
+ syslog(LOG_ERR,
+ "%s/%s: %s: login class error, service ignored",
+ new->se_service, new->se_proto, new->se_class);
+ continue;
+ }
+ login_close(lc);
+#endif
+ new_nomapped = new->se_nomapped;
+ for (sep = servtab; sep; sep = sep->se_next)
+ if (strcmp(sep->se_service, new->se_service) == 0 &&
+ strcmp(sep->se_proto, new->se_proto) == 0 &&
+ sep->se_rpc == new->se_rpc &&
+ sep->se_socktype == new->se_socktype &&
+ sep->se_family == new->se_family)
+ break;
+ if (sep != 0) {
+ int i;
+
+#define SWAP(t,a, b) { t c = a; a = b; b = c; }
+ omask = sigblock(SIGBLOCK);
+ if (sep->se_nomapped != new->se_nomapped) {
+ /* for rpc keep old nommaped till unregister */
+ if (!sep->se_rpc)
+ sep->se_nomapped = new->se_nomapped;
+ sep->se_reset = 1;
+ }
+ /* copy over outstanding child pids */
+ if (sep->se_maxchild > 0 && new->se_maxchild > 0) {
+ new->se_numchild = sep->se_numchild;
+ if (new->se_numchild > new->se_maxchild)
+ new->se_numchild = new->se_maxchild;
+ memcpy(new->se_pids, sep->se_pids,
+ new->se_numchild * sizeof(*new->se_pids));
+ }
+ SWAP(pid_t *, sep->se_pids, new->se_pids);
+ sep->se_maxchild = new->se_maxchild;
+ sep->se_numchild = new->se_numchild;
+ sep->se_maxcpm = new->se_maxcpm;
+ resize_conn(sep, new->se_maxperip);
+ sep->se_maxperip = new->se_maxperip;
+ sep->se_bi = new->se_bi;
+ /* might need to turn on or off service now */
+ if (sep->se_fd >= 0) {
+ if (sep->se_maxchild > 0
+ && sep->se_numchild == sep->se_maxchild) {
+ if (FD_ISSET(sep->se_fd, &allsock))
+ disable(sep);
+ } else {
+ if (!FD_ISSET(sep->se_fd, &allsock))
+ enable(sep);
+ }
+ }
+ sep->se_accept = new->se_accept;
+ SWAP(char *, sep->se_user, new->se_user);
+ SWAP(char *, sep->se_group, new->se_group);
+#ifdef LOGIN_CAP
+ SWAP(char *, sep->se_class, new->se_class);
+#endif
+ SWAP(char *, sep->se_server, new->se_server);
+ SWAP(char *, sep->se_server_name, new->se_server_name);
+ for (i = 0; i < MAXARGV; i++)
+ SWAP(char *, sep->se_argv[i], new->se_argv[i]);
+#ifdef IPSEC
+ SWAP(char *, sep->se_policy, new->se_policy);
+ ipsecsetup(sep);
+#endif
+ sigsetmask(omask);
+ freeconfig(new);
+ if (debug)
+ print_service("REDO", sep);
+ } else {
+ sep = enter(new);
+ if (debug)
+ print_service("ADD ", sep);
+ }
+ sep->se_checked = 1;
+ if (ISMUX(sep)) {
+ sep->se_fd = -1;
+ continue;
+ }
+ switch (sep->se_family) {
+ case AF_INET:
+ if (!v4bind_ok) {
+ sep->se_fd = -1;
+ continue;
+ }
+ break;
+#ifdef INET6
+ case AF_INET6:
+ if (!v6bind_ok) {
+ sep->se_fd = -1;
+ continue;
+ }
+ break;
+#endif
+ }
+ if (!sep->se_rpc) {
+ if (sep->se_family != AF_UNIX) {
+ sp = getservbyname(sep->se_service, sep->se_proto);
+ if (sp == 0) {
+ syslog(LOG_ERR, "%s/%s: unknown service",
+ sep->se_service, sep->se_proto);
+ sep->se_checked = 0;
+ continue;
+ }
+ }
+ switch (sep->se_family) {
+ case AF_INET:
+ if (sp->s_port != sep->se_ctrladdr4.sin_port) {
+ sep->se_ctrladdr4.sin_port =
+ sp->s_port;
+ sep->se_reset = 1;
+ }
+ break;
+#ifdef INET6
+ case AF_INET6:
+ if (sp->s_port !=
+ sep->se_ctrladdr6.sin6_port) {
+ sep->se_ctrladdr6.sin6_port =
+ sp->s_port;
+ sep->se_reset = 1;
+ }
+ break;
+#endif
+ }
+ if (sep->se_reset != 0 && sep->se_fd >= 0)
+ close_sep(sep);
+ } else {
+ rpc = getrpcbyname(sep->se_service);
+ if (rpc == 0) {
+ syslog(LOG_ERR, "%s/%s unknown RPC service",
+ sep->se_service, sep->se_proto);
+ if (sep->se_fd != -1)
+ (void) close(sep->se_fd);
+ sep->se_fd = -1;
+ continue;
+ }
+ if (sep->se_reset != 0 ||
+ rpc->r_number != sep->se_rpc_prog) {
+ if (sep->se_rpc_prog)
+ unregisterrpc(sep);
+ sep->se_rpc_prog = rpc->r_number;
+ if (sep->se_fd != -1)
+ (void) close(sep->se_fd);
+ sep->se_fd = -1;
+ }
+ sep->se_nomapped = new_nomapped;
+ }
+ sep->se_reset = 0;
+ if (sep->se_fd == -1)
+ setup(sep);
+ }
+ endconfig();
+ /*
+ * Purge anything not looked at above.
+ */
+ omask = sigblock(SIGBLOCK);
+ sepp = &servtab;
+ while ((sep = *sepp)) {
+ if (sep->se_checked) {
+ sepp = &sep->se_next;
+ continue;
+ }
+ *sepp = sep->se_next;
+ if (sep->se_fd >= 0)
+ close_sep(sep);
+ if (debug)
+ print_service("FREE", sep);
+ if (sep->se_rpc && sep->se_rpc_prog > 0)
+ unregisterrpc(sep);
+ freeconfig(sep);
+ free(sep);
+ }
+ (void) sigsetmask(omask);
+}
+
+void
+unregisterrpc(struct servtab *sep)
+{
+ u_int i;
+ struct servtab *sepp;
+ long omask;
+ struct netconfig *netid4, *netid6;
+
+ omask = sigblock(SIGBLOCK);
+ netid4 = sep->se_socktype == SOCK_DGRAM ? udpconf : tcpconf;
+ netid6 = sep->se_socktype == SOCK_DGRAM ? udp6conf : tcp6conf;
+ if (sep->se_family == AF_INET)
+ netid6 = NULL;
+ else if (sep->se_nomapped)
+ netid4 = NULL;
+ /*
+ * Conflict if same prog and protocol - In that case one should look
+ * to versions, but it is not interesting: having separate servers for
+ * different versions does not work well.
+ * Therefore one do not unregister if there is a conflict.
+ * There is also transport conflict if destroying INET when INET46
+ * exists, or destroying INET46 when INET exists
+ */
+ for (sepp = servtab; sepp; sepp = sepp->se_next) {
+ if (sepp == sep)
+ continue;
+ if (sepp->se_checked == 0 ||
+ !sepp->se_rpc ||
+ strcmp(sep->se_proto, sepp->se_proto) != 0 ||
+ sep->se_rpc_prog != sepp->se_rpc_prog)
+ continue;
+ if (sepp->se_family == AF_INET)
+ netid4 = NULL;
+ if (sepp->se_family == AF_INET6) {
+ netid6 = NULL;
+ if (!sep->se_nomapped)
+ netid4 = NULL;
+ }
+ if (netid4 == NULL && netid6 == NULL)
+ return;
+ }
+ if (debug)
+ print_service("UNREG", sep);
+ for (i = sep->se_rpc_lowvers; i <= sep->se_rpc_highvers; i++) {
+ if (netid4)
+ rpcb_unset(sep->se_rpc_prog, i, netid4);
+ if (netid6)
+ rpcb_unset(sep->se_rpc_prog, i, netid6);
+ }
+ if (sep->se_fd != -1)
+ (void) close(sep->se_fd);
+ sep->se_fd = -1;
+ (void) sigsetmask(omask);
+}
+
+void
+flag_retry(int signo __unused)
+{
+ flag_signal('A');
+}
+
+void
+retry(void)
+{
+ struct servtab *sep;
+
+ timingout = 0;
+ for (sep = servtab; sep; sep = sep->se_next)
+ if (sep->se_fd == -1 && !ISMUX(sep))
+ setup(sep);
+}
+
+void
+setup(struct servtab *sep)
+{
+ int on = 1;
+
+ if ((sep->se_fd = socket(sep->se_family, sep->se_socktype, 0)) < 0) {
+ if (debug)
+ warn("socket failed on %s/%s",
+ sep->se_service, sep->se_proto);
+ syslog(LOG_ERR, "%s/%s: socket: %m",
+ sep->se_service, sep->se_proto);
+ return;
+ }
+ /* Set all listening sockets to close-on-exec. */
+ if (fcntl(sep->se_fd, F_SETFD, FD_CLOEXEC) < 0) {
+ syslog(LOG_ERR, "%s/%s: fcntl (F_SETFD, FD_CLOEXEC): %m",
+ sep->se_service, sep->se_proto);
+ close(sep->se_fd);
+ return;
+ }
+#define turnon(fd, opt) \
+setsockopt(fd, SOL_SOCKET, opt, (char *)&on, sizeof (on))
+ if (strcmp(sep->se_proto, "tcp") == 0 && (options & SO_DEBUG) &&
+ turnon(sep->se_fd, SO_DEBUG) < 0)
+ syslog(LOG_ERR, "setsockopt (SO_DEBUG): %m");
+ if (turnon(sep->se_fd, SO_REUSEADDR) < 0)
+ syslog(LOG_ERR, "setsockopt (SO_REUSEADDR): %m");
+#ifdef SO_PRIVSTATE
+ if (turnon(sep->se_fd, SO_PRIVSTATE) < 0)
+ syslog(LOG_ERR, "setsockopt (SO_PRIVSTATE): %m");
+#endif
+ /* tftpd opens a new connection then needs more infos */
+ if ((sep->se_family == AF_INET6) &&
+ (strcmp(sep->se_proto, "udp") == 0) &&
+ (sep->se_accept == 0) &&
+ (setsockopt(sep->se_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO,
+ (char *)&on, sizeof (on)) < 0))
+ syslog(LOG_ERR, "setsockopt (IPV6_RECVPKTINFO): %m");
+ if (sep->se_family == AF_INET6) {
+ int flag = sep->se_nomapped ? 1 : 0;
+ if (setsockopt(sep->se_fd, IPPROTO_IPV6, IPV6_V6ONLY,
+ (char *)&flag, sizeof (flag)) < 0)
+ syslog(LOG_ERR, "setsockopt (IPV6_V6ONLY): %m");
+ }
+#undef turnon
+#ifdef IPSEC
+ ipsecsetup(sep);
+#endif
+ if (sep->se_family == AF_UNIX) {
+ (void) unlink(sep->se_ctrladdr_un.sun_path);
+ umask(0777); /* Make socket with conservative permissions */
+ }
+ if (bind(sep->se_fd, (struct sockaddr *)&sep->se_ctrladdr,
+ sep->se_ctrladdr_size) < 0) {
+ if (debug)
+ warn("bind failed on %s/%s",
+ sep->se_service, sep->se_proto);
+ syslog(LOG_ERR, "%s/%s: bind: %m",
+ sep->se_service, sep->se_proto);
+ (void) close(sep->se_fd);
+ sep->se_fd = -1;
+ if (!timingout) {
+ timingout = 1;
+ alarm(RETRYTIME);
+ }
+ if (sep->se_family == AF_UNIX)
+ umask(mask);
+ return;
+ }
+ if (sep->se_family == AF_UNIX) {
+ /* Ick - fch{own,mod} don't work on Unix domain sockets */
+ if (chown(sep->se_service, sep->se_sockuid, sep->se_sockgid) < 0)
+ syslog(LOG_ERR, "chown socket: %m");
+ if (chmod(sep->se_service, sep->se_sockmode) < 0)
+ syslog(LOG_ERR, "chmod socket: %m");
+ umask(mask);
+ }
+ if (sep->se_rpc) {
+ u_int i;
+ socklen_t len = sep->se_ctrladdr_size;
+ struct netconfig *netid, *netid2 = NULL;
+ struct sockaddr_in sock;
+ struct netbuf nbuf, nbuf2;
+
+ if (getsockname(sep->se_fd,
+ (struct sockaddr*)&sep->se_ctrladdr, &len) < 0){
+ syslog(LOG_ERR, "%s/%s: getsockname: %m",
+ sep->se_service, sep->se_proto);
+ (void) close(sep->se_fd);
+ sep->se_fd = -1;
+ return;
+ }
+ nbuf.buf = &sep->se_ctrladdr;
+ nbuf.len = sep->se_ctrladdr.sa_len;
+ if (sep->se_family == AF_INET)
+ netid = sep->se_socktype==SOCK_DGRAM? udpconf:tcpconf;
+ else {
+ netid = sep->se_socktype==SOCK_DGRAM? udp6conf:tcp6conf;
+ if (!sep->se_nomapped) { /* INET and INET6 */
+ netid2 = netid==udp6conf? udpconf:tcpconf;
+ memset(&sock, 0, sizeof sock); /* ADDR_ANY */
+ nbuf2.buf = &sock;
+ nbuf2.len = sock.sin_len = sizeof sock;
+ sock.sin_family = AF_INET;
+ sock.sin_port = sep->se_ctrladdr6.sin6_port;
+ }
+ }
+ if (debug)
+ print_service("REG ", sep);
+ for (i = sep->se_rpc_lowvers; i <= sep->se_rpc_highvers; i++) {
+ rpcb_unset(sep->se_rpc_prog, i, netid);
+ rpcb_set(sep->se_rpc_prog, i, netid, &nbuf);
+ if (netid2) {
+ rpcb_unset(sep->se_rpc_prog, i, netid2);
+ rpcb_set(sep->se_rpc_prog, i, netid2, &nbuf2);
+ }
+ }
+ }
+ if (sep->se_socktype == SOCK_STREAM)
+ listen(sep->se_fd, -1);
+ enable(sep);
+ if (debug) {
+ warnx("registered %s on %d",
+ sep->se_server, sep->se_fd);
+ }
+}
+
+#ifdef IPSEC
+void
+ipsecsetup(struct servtab *sep)
+{
+ char *buf;
+ char *policy_in = NULL;
+ char *policy_out = NULL;
+ int level;
+ int opt;
+
+ switch (sep->se_family) {
+ case AF_INET:
+ level = IPPROTO_IP;
+ opt = IP_IPSEC_POLICY;
+ break;
+#ifdef INET6
+ case AF_INET6:
+ level = IPPROTO_IPV6;
+ opt = IPV6_IPSEC_POLICY;
+ break;
+#endif
+ default:
+ return;
+ }
+
+ if (!sep->se_policy || sep->se_policy[0] == '\0') {
+ static char def_in[] = "in entrust", def_out[] = "out entrust";
+ policy_in = def_in;
+ policy_out = def_out;
+ } else {
+ if (!strncmp("in", sep->se_policy, 2))
+ policy_in = sep->se_policy;
+ else if (!strncmp("out", sep->se_policy, 3))
+ policy_out = sep->se_policy;
+ else {
+ syslog(LOG_ERR, "invalid security policy \"%s\"",
+ sep->se_policy);
+ return;
+ }
+ }
+
+ if (policy_in != NULL) {
+ buf = ipsec_set_policy(policy_in, strlen(policy_in));
+ if (buf != NULL) {
+ if (setsockopt(sep->se_fd, level, opt,
+ buf, ipsec_get_policylen(buf)) < 0 &&
+ debug != 0)
+ warnx("%s/%s: ipsec initialization failed; %s",
+ sep->se_service, sep->se_proto,
+ policy_in);
+ free(buf);
+ } else
+ syslog(LOG_ERR, "invalid security policy \"%s\"",
+ policy_in);
+ }
+ if (policy_out != NULL) {
+ buf = ipsec_set_policy(policy_out, strlen(policy_out));
+ if (buf != NULL) {
+ if (setsockopt(sep->se_fd, level, opt,
+ buf, ipsec_get_policylen(buf)) < 0 &&
+ debug != 0)
+ warnx("%s/%s: ipsec initialization failed; %s",
+ sep->se_service, sep->se_proto,
+ policy_out);
+ free(buf);
+ } else
+ syslog(LOG_ERR, "invalid security policy \"%s\"",
+ policy_out);
+ }
+}
+#endif
+
+/*
+ * Finish with a service and its socket.
+ */
+void
+close_sep(struct servtab *sep)
+{
+ if (sep->se_fd >= 0) {
+ if (FD_ISSET(sep->se_fd, &allsock))
+ disable(sep);
+ (void) close(sep->se_fd);
+ sep->se_fd = -1;
+ }
+ sep->se_count = 0;
+ sep->se_numchild = 0; /* forget about any existing children */
+}
+
+int
+matchservent(const char *name1, const char *name2, const char *proto)
+{
+ char **alias, *p;
+ struct servent *se;
+
+ if (strcmp(proto, "unix") == 0) {
+ if ((p = strrchr(name1, '/')) != NULL)
+ name1 = p + 1;
+ if ((p = strrchr(name2, '/')) != NULL)
+ name2 = p + 1;
+ }
+ if (strcmp(name1, name2) == 0)
+ return(1);
+ if ((se = getservbyname(name1, proto)) != NULL) {
+ if (strcmp(name2, se->s_name) == 0)
+ return(1);
+ for (alias = se->s_aliases; *alias; alias++)
+ if (strcmp(name2, *alias) == 0)
+ return(1);
+ }
+ return(0);
+}
+
+struct servtab *
+enter(struct servtab *cp)
+{
+ struct servtab *sep;
+ long omask;
+
+ sep = (struct servtab *)malloc(sizeof (*sep));
+ if (sep == (struct servtab *)0) {
+ syslog(LOG_ERR, "malloc: %m");
+ exit(EX_OSERR);
+ }
+ *sep = *cp;
+ sep->se_fd = -1;
+ omask = sigblock(SIGBLOCK);
+ sep->se_next = servtab;
+ servtab = sep;
+ sigsetmask(omask);
+ return (sep);
+}
+
+void
+enable(struct servtab *sep)
+{
+ if (debug)
+ warnx(
+ "enabling %s, fd %d", sep->se_service, sep->se_fd);
+#ifdef SANITY_CHECK
+ if (sep->se_fd < 0) {
+ syslog(LOG_ERR,
+ "%s: %s: bad fd", __func__, sep->se_service);
+ exit(EX_SOFTWARE);
+ }
+ if (ISMUX(sep)) {
+ syslog(LOG_ERR,
+ "%s: %s: is mux", __func__, sep->se_service);
+ exit(EX_SOFTWARE);
+ }
+ if (FD_ISSET(sep->se_fd, &allsock)) {
+ syslog(LOG_ERR,
+ "%s: %s: not off", __func__, sep->se_service);
+ exit(EX_SOFTWARE);
+ }
+ nsock++;
+#endif
+ FD_SET(sep->se_fd, &allsock);
+ if (sep->se_fd > maxsock)
+ maxsock = sep->se_fd;
+}
+
+void
+disable(struct servtab *sep)
+{
+ if (debug)
+ warnx(
+ "disabling %s, fd %d", sep->se_service, sep->se_fd);
+#ifdef SANITY_CHECK
+ if (sep->se_fd < 0) {
+ syslog(LOG_ERR,
+ "%s: %s: bad fd", __func__, sep->se_service);
+ exit(EX_SOFTWARE);
+ }
+ if (ISMUX(sep)) {
+ syslog(LOG_ERR,
+ "%s: %s: is mux", __func__, sep->se_service);
+ exit(EX_SOFTWARE);
+ }
+ if (!FD_ISSET(sep->se_fd, &allsock)) {
+ syslog(LOG_ERR,
+ "%s: %s: not on", __func__, sep->se_service);
+ exit(EX_SOFTWARE);
+ }
+ if (nsock == 0) {
+ syslog(LOG_ERR, "%s: nsock=0", __func__);
+ exit(EX_SOFTWARE);
+ }
+ nsock--;
+#endif
+ FD_CLR(sep->se_fd, &allsock);
+ if (sep->se_fd == maxsock)
+ maxsock--;
+}
+
+FILE *fconfig = NULL;
+struct servtab serv;
+char line[LINE_MAX];
+
+int
+setconfig(void)
+{
+
+ if (fconfig != NULL) {
+ fseek(fconfig, 0L, SEEK_SET);
+ return (1);
+ }
+ fconfig = fopen(CONFIG, "r");
+ return (fconfig != NULL);
+}
+
+void
+endconfig(void)
+{
+ if (fconfig) {
+ (void) fclose(fconfig);
+ fconfig = NULL;
+ }
+}
+
+struct servtab *
+getconfigent(void)
+{
+ struct servtab *sep = &serv;
+ int argc;
+ char *cp, *arg, *s;
+ char *versp;
+ static char TCPMUX_TOKEN[] = "tcpmux/";
+#define MUX_LEN (sizeof(TCPMUX_TOKEN)-1)
+#ifdef IPSEC
+ char *policy;
+#endif
+ int v4bind;
+#ifdef INET6
+ int v6bind;
+#endif
+ int i;
+
+#ifdef IPSEC
+ policy = NULL;
+#endif
+more:
+ v4bind = 0;
+#ifdef INET6
+ v6bind = 0;
+#endif
+ while ((cp = nextline(fconfig)) != NULL) {
+#ifdef IPSEC
+ /* lines starting with #@ is not a comment, but the policy */
+ if (cp[0] == '#' && cp[1] == '@') {
+ char *p;
+ for (p = cp + 2; p && *p && isspace(*p); p++)
+ ;
+ if (*p == '\0') {
+ if (policy)
+ free(policy);
+ policy = NULL;
+ } else if (ipsec_get_policylen(p) >= 0) {
+ if (policy)
+ free(policy);
+ policy = newstr(p);
+ } else {
+ syslog(LOG_ERR,
+ "%s: invalid ipsec policy \"%s\"",
+ CONFIG, p);
+ exit(EX_CONFIG);
+ }
+ }
+#endif
+ if (*cp == '#' || *cp == '\0')
+ continue;
+ break;
+ }
+ if (cp == NULL)
+ return ((struct servtab *)0);
+ /*
+ * clear the static buffer, since some fields (se_ctrladdr,
+ * for example) don't get initialized here.
+ */
+ memset(sep, 0, sizeof *sep);
+ arg = skip(&cp);
+ if (cp == NULL) {
+ /* got an empty line containing just blanks/tabs. */
+ goto more;
+ }
+ if (arg[0] == ':') { /* :user:group:perm: */
+ char *user, *group, *perm;
+ struct passwd *pw;
+ struct group *gr;
+ user = arg+1;
+ if ((group = strchr(user, ':')) == NULL) {
+ syslog(LOG_ERR, "no group after user '%s'", user);
+ goto more;
+ }
+ *group++ = '\0';
+ if ((perm = strchr(group, ':')) == NULL) {
+ syslog(LOG_ERR, "no mode after group '%s'", group);
+ goto more;
+ }
+ *perm++ = '\0';
+ if ((pw = getpwnam(user)) == NULL) {
+ syslog(LOG_ERR, "no such user '%s'", user);
+ goto more;
+ }
+ sep->se_sockuid = pw->pw_uid;
+ if ((gr = getgrnam(group)) == NULL) {
+ syslog(LOG_ERR, "no such user '%s'", group);
+ goto more;
+ }
+ sep->se_sockgid = gr->gr_gid;
+ sep->se_sockmode = strtol(perm, &arg, 8);
+ if (*arg != ':') {
+ syslog(LOG_ERR, "bad mode '%s'", perm);
+ goto more;
+ }
+ *arg++ = '\0';
+ } else {
+ sep->se_sockuid = euid;
+ sep->se_sockgid = egid;
+ sep->se_sockmode = 0200;
+ }
+ if (strncmp(arg, TCPMUX_TOKEN, MUX_LEN) == 0) {
+ char *c = arg + MUX_LEN;
+ if (*c == '+') {
+ sep->se_type = MUXPLUS_TYPE;
+ c++;
+ } else
+ sep->se_type = MUX_TYPE;
+ sep->se_service = newstr(c);
+ } else {
+ sep->se_service = newstr(arg);
+ sep->se_type = NORM_TYPE;
+ }
+ arg = sskip(&cp);
+ if (strcmp(arg, "stream") == 0)
+ sep->se_socktype = SOCK_STREAM;
+ else if (strcmp(arg, "dgram") == 0)
+ sep->se_socktype = SOCK_DGRAM;
+ else if (strcmp(arg, "rdm") == 0)
+ sep->se_socktype = SOCK_RDM;
+ else if (strcmp(arg, "seqpacket") == 0)
+ sep->se_socktype = SOCK_SEQPACKET;
+ else if (strcmp(arg, "raw") == 0)
+ sep->se_socktype = SOCK_RAW;
+ else
+ sep->se_socktype = -1;
+
+ arg = sskip(&cp);
+ if (strncmp(arg, "tcp", 3) == 0) {
+ sep->se_proto = newstr(strsep(&arg, "/"));
+ if (arg != NULL && (strcmp(arg, "faith") == 0)) {
+ syslog(LOG_ERR, "faith has been deprecated");
+ goto more;
+ }
+ } else {
+ if (sep->se_type == NORM_TYPE &&
+ strncmp(arg, "faith/", 6) == 0) {
+ syslog(LOG_ERR, "faith has been deprecated");
+ goto more;
+ }
+ sep->se_proto = newstr(arg);
+ }
+ if (strncmp(sep->se_proto, "rpc/", 4) == 0) {
+ memmove(sep->se_proto, sep->se_proto + 4,
+ strlen(sep->se_proto) + 1 - 4);
+ sep->se_rpc = 1;
+ sep->se_rpc_prog = sep->se_rpc_lowvers =
+ sep->se_rpc_highvers = 0;
+ if ((versp = strrchr(sep->se_service, '/'))) {
+ *versp++ = '\0';
+ switch (sscanf(versp, "%u-%u",
+ &sep->se_rpc_lowvers,
+ &sep->se_rpc_highvers)) {
+ case 2:
+ break;
+ case 1:
+ sep->se_rpc_highvers =
+ sep->se_rpc_lowvers;
+ break;
+ default:
+ syslog(LOG_ERR,
+ "bad RPC version specifier; %s",
+ sep->se_service);
+ freeconfig(sep);
+ goto more;
+ }
+ }
+ else {
+ sep->se_rpc_lowvers =
+ sep->se_rpc_highvers = 1;
+ }
+ }
+ sep->se_nomapped = 0;
+ if (strcmp(sep->se_proto, "unix") == 0) {
+ sep->se_family = AF_UNIX;
+ } else {
+ while (isdigit(sep->se_proto[strlen(sep->se_proto) - 1])) {
+#ifdef INET6
+ if (sep->se_proto[strlen(sep->se_proto) - 1] == '6') {
+ sep->se_proto[strlen(sep->se_proto) - 1] = '\0';
+ v6bind = 1;
+ continue;
+ }
+#endif
+ if (sep->se_proto[strlen(sep->se_proto) - 1] == '4') {
+ sep->se_proto[strlen(sep->se_proto) - 1] = '\0';
+ v4bind = 1;
+ continue;
+ }
+ /* illegal version num */
+ syslog(LOG_ERR, "bad IP version for %s", sep->se_proto);
+ freeconfig(sep);
+ goto more;
+ }
+#ifdef INET6
+ if (v6bind && !v6bind_ok) {
+ syslog(LOG_INFO, "IPv6 bind is ignored for %s",
+ sep->se_service);
+ if (v4bind && v4bind_ok)
+ v6bind = 0;
+ else {
+ freeconfig(sep);
+ goto more;
+ }
+ }
+ if (v6bind) {
+ sep->se_family = AF_INET6;
+ if (!v4bind || !v4bind_ok)
+ sep->se_nomapped = 1;
+ } else
+#endif
+ { /* default to v4 bind if not v6 bind */
+ if (!v4bind_ok) {
+ syslog(LOG_NOTICE, "IPv4 bind is ignored for %s",
+ sep->se_service);
+ freeconfig(sep);
+ goto more;
+ }
+ sep->se_family = AF_INET;
+ }
+ }
+ /* init ctladdr */
+ switch(sep->se_family) {
+ case AF_INET:
+ memcpy(&sep->se_ctrladdr4, bind_sa4,
+ sizeof(sep->se_ctrladdr4));
+ sep->se_ctrladdr_size = sizeof(sep->se_ctrladdr4);
+ break;
+#ifdef INET6
+ case AF_INET6:
+ memcpy(&sep->se_ctrladdr6, bind_sa6,
+ sizeof(sep->se_ctrladdr6));
+ sep->se_ctrladdr_size = sizeof(sep->se_ctrladdr6);
+ break;
+#endif
+ case AF_UNIX:
+ if (strlen(sep->se_service) >= sizeof(sep->se_ctrladdr_un.sun_path)) {
+ syslog(LOG_ERR,
+ "domain socket pathname too long for service %s",
+ sep->se_service);
+ goto more;
+ }
+ memset(&sep->se_ctrladdr, 0, sizeof(sep->se_ctrladdr));
+ sep->se_ctrladdr_un.sun_family = sep->se_family;
+ sep->se_ctrladdr_un.sun_len = strlen(sep->se_service);
+ strcpy(sep->se_ctrladdr_un.sun_path, sep->se_service);
+ sep->se_ctrladdr_size = SUN_LEN(&sep->se_ctrladdr_un);
+ }
+ arg = sskip(&cp);
+ if (!strncmp(arg, "wait", 4))
+ sep->se_accept = 0;
+ else if (!strncmp(arg, "nowait", 6))
+ sep->se_accept = 1;
+ else {
+ syslog(LOG_ERR,
+ "%s: bad wait/nowait for service %s",
+ CONFIG, sep->se_service);
+ goto more;
+ }
+ sep->se_maxchild = -1;
+ sep->se_maxcpm = -1;
+ sep->se_maxperip = -1;
+ if ((s = strchr(arg, '/')) != NULL) {
+ char *eptr;
+ u_long val;
+
+ val = strtoul(s + 1, &eptr, 10);
+ if (eptr == s + 1 || val > MAX_MAXCHLD) {
+ syslog(LOG_ERR,
+ "%s: bad max-child for service %s",
+ CONFIG, sep->se_service);
+ goto more;
+ }
+ if (debug)
+ if (!sep->se_accept && val != 1)
+ warnx("maxchild=%lu for wait service %s"
+ " not recommended", val, sep->se_service);
+ sep->se_maxchild = val;
+ if (*eptr == '/')
+ sep->se_maxcpm = strtol(eptr + 1, &eptr, 10);
+ if (*eptr == '/')
+ sep->se_maxperip = strtol(eptr + 1, &eptr, 10);
+ /*
+ * explicitly do not check for \0 for future expansion /
+ * backwards compatibility
+ */
+ }
+ if (ISMUX(sep)) {
+ /*
+ * Silently enforce "nowait" mode for TCPMUX services
+ * since they don't have an assigned port to listen on.
+ */
+ sep->se_accept = 1;
+ if (strcmp(sep->se_proto, "tcp")) {
+ syslog(LOG_ERR,
+ "%s: bad protocol for tcpmux service %s",
+ CONFIG, sep->se_service);
+ goto more;
+ }
+ if (sep->se_socktype != SOCK_STREAM) {
+ syslog(LOG_ERR,
+ "%s: bad socket type for tcpmux service %s",
+ CONFIG, sep->se_service);
+ goto more;
+ }
+ }
+ sep->se_user = newstr(sskip(&cp));
+#ifdef LOGIN_CAP
+ if ((s = strrchr(sep->se_user, '/')) != NULL) {
+ *s = '\0';
+ sep->se_class = newstr(s + 1);
+ } else
+ sep->se_class = newstr(RESOURCE_RC);
+#endif
+ if ((s = strrchr(sep->se_user, ':')) != NULL) {
+ *s = '\0';
+ sep->se_group = newstr(s + 1);
+ } else
+ sep->se_group = NULL;
+ sep->se_server = newstr(sskip(&cp));
+ if ((sep->se_server_name = strrchr(sep->se_server, '/')))
+ sep->se_server_name++;
+ if (strcmp(sep->se_server, "internal") == 0) {
+ struct biltin *bi;
+
+ for (bi = biltins; bi->bi_service; bi++)
+ if (bi->bi_socktype == sep->se_socktype &&
+ matchservent(bi->bi_service, sep->se_service,
+ sep->se_proto))
+ break;
+ if (bi->bi_service == 0) {
+ syslog(LOG_ERR, "internal service %s unknown",
+ sep->se_service);
+ goto more;
+ }
+ sep->se_accept = 1; /* force accept mode for built-ins */
+ sep->se_bi = bi;
+ } else
+ sep->se_bi = NULL;
+ if (sep->se_maxperip < 0)
+ sep->se_maxperip = maxperip;
+ if (sep->se_maxcpm < 0)
+ sep->se_maxcpm = maxcpm;
+ if (sep->se_maxchild < 0) { /* apply default max-children */
+ if (sep->se_bi && sep->se_bi->bi_maxchild >= 0)
+ sep->se_maxchild = sep->se_bi->bi_maxchild;
+ else if (sep->se_accept)
+ sep->se_maxchild = maxchild > 0 ? maxchild : 0;
+ else
+ sep->se_maxchild = 1;
+ }
+ if (sep->se_maxchild > 0) {
+ sep->se_pids = malloc(sep->se_maxchild * sizeof(*sep->se_pids));
+ if (sep->se_pids == NULL) {
+ syslog(LOG_ERR, "malloc: %m");
+ exit(EX_OSERR);
+ }
+ }
+ argc = 0;
+ for (arg = skip(&cp); cp; arg = skip(&cp))
+ if (argc < MAXARGV) {
+ sep->se_argv[argc++] = newstr(arg);
+ } else {
+ syslog(LOG_ERR,
+ "%s: too many arguments for service %s",
+ CONFIG, sep->se_service);
+ goto more;
+ }
+ while (argc <= MAXARGV)
+ sep->se_argv[argc++] = NULL;
+ for (i = 0; i < PERIPSIZE; ++i)
+ LIST_INIT(&sep->se_conn[i]);
+#ifdef IPSEC
+ sep->se_policy = policy ? newstr(policy) : NULL;
+#endif
+ return (sep);
+}
+
+void
+freeconfig(struct servtab *cp)
+{
+ int i;
+
+ if (cp->se_service)
+ free(cp->se_service);
+ if (cp->se_proto)
+ free(cp->se_proto);
+ if (cp->se_user)
+ free(cp->se_user);
+ if (cp->se_group)
+ free(cp->se_group);
+#ifdef LOGIN_CAP
+ if (cp->se_class)
+ free(cp->se_class);
+#endif
+ if (cp->se_server)
+ free(cp->se_server);
+ if (cp->se_pids)
+ free(cp->se_pids);
+ for (i = 0; i < MAXARGV; i++)
+ if (cp->se_argv[i])
+ free(cp->se_argv[i]);
+ free_connlist(cp);
+#ifdef IPSEC
+ if (cp->se_policy)
+ free(cp->se_policy);
+#endif
+}
+
+
+/*
+ * Safe skip - if skip returns null, log a syntax error in the
+ * configuration file and exit.
+ */
+char *
+sskip(char **cpp)
+{
+ char *cp;
+
+ cp = skip(cpp);
+ if (cp == NULL) {
+ syslog(LOG_ERR, "%s: syntax error", CONFIG);
+ exit(EX_DATAERR);
+ }
+ return (cp);
+}
+
+char *
+skip(char **cpp)
+{
+ char *cp = *cpp;
+ char *start;
+ char quote = '\0';
+
+again:
+ while (*cp == ' ' || *cp == '\t')
+ cp++;
+ if (*cp == '\0') {
+ int c;
+
+ c = getc(fconfig);
+ (void) ungetc(c, fconfig);
+ if (c == ' ' || c == '\t')
+ if ((cp = nextline(fconfig)))
+ goto again;
+ *cpp = (char *)0;
+ return ((char *)0);
+ }
+ if (*cp == '"' || *cp == '\'')
+ quote = *cp++;
+ start = cp;
+ if (quote)
+ while (*cp && *cp != quote)
+ cp++;
+ else
+ while (*cp && *cp != ' ' && *cp != '\t')
+ cp++;
+ if (*cp != '\0')
+ *cp++ = '\0';
+ *cpp = cp;
+ return (start);
+}
+
+char *
+nextline(FILE *fd)
+{
+ char *cp;
+
+ if (fgets(line, sizeof (line), fd) == NULL)
+ return ((char *)0);
+ cp = strchr(line, '\n');
+ if (cp)
+ *cp = '\0';
+ return (line);
+}
+
+char *
+newstr(const char *cp)
+{
+ char *cr;
+
+ if ((cr = strdup(cp != NULL ? cp : "")))
+ return (cr);
+ syslog(LOG_ERR, "strdup: %m");
+ exit(EX_OSERR);
+}
+
+void
+inetd_setproctitle(const char *a, int s)
+{
+ socklen_t size;
+ struct sockaddr_storage ss;
+ char buf[80], pbuf[NI_MAXHOST];
+
+ size = sizeof(ss);
+ if (getpeername(s, (struct sockaddr *)&ss, &size) == 0) {
+ getnameinfo((struct sockaddr *)&ss, size, pbuf, sizeof(pbuf),
+ NULL, 0, NI_NUMERICHOST);
+ (void) sprintf(buf, "%s [%s]", a, pbuf);
+ } else
+ (void) sprintf(buf, "%s", a);
+ setproctitle("%s", buf);
+}
+
+int
+check_loop(const struct sockaddr *sa, const struct servtab *sep)
+{
+ struct servtab *se2;
+ char pname[NI_MAXHOST];
+
+ for (se2 = servtab; se2; se2 = se2->se_next) {
+ if (!se2->se_bi || se2->se_socktype != SOCK_DGRAM)
+ continue;
+
+ switch (se2->se_family) {
+ case AF_INET:
+ if (((const struct sockaddr_in *)sa)->sin_port ==
+ se2->se_ctrladdr4.sin_port)
+ goto isloop;
+ continue;
+#ifdef INET6
+ case AF_INET6:
+ if (((const struct sockaddr_in6 *)sa)->sin6_port ==
+ se2->se_ctrladdr6.sin6_port)
+ goto isloop;
+ continue;
+#endif
+ default:
+ continue;
+ }
+ isloop:
+ getnameinfo(sa, sa->sa_len, pname, sizeof(pname), NULL, 0,
+ NI_NUMERICHOST);
+ syslog(LOG_WARNING, "%s/%s:%s/%s loop request REFUSED from %s",
+ sep->se_service, sep->se_proto,
+ se2->se_service, se2->se_proto,
+ pname);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * print_service:
+ * Dump relevant information to stderr
+ */
+void
+print_service(const char *action, const struct servtab *sep)
+{
+ fprintf(stderr,
+ "%s: %s proto=%s accept=%d max=%d user=%s group=%s"
+#ifdef LOGIN_CAP
+ "class=%s"
+#endif
+ " builtin=%p server=%s"
+#ifdef IPSEC
+ " policy=\"%s\""
+#endif
+ "\n",
+ action, sep->se_service, sep->se_proto,
+ sep->se_accept, sep->se_maxchild, sep->se_user, sep->se_group,
+#ifdef LOGIN_CAP
+ sep->se_class,
+#endif
+ (void *) sep->se_bi, sep->se_server
+#ifdef IPSEC
+ , (sep->se_policy ? sep->se_policy : "")
+#endif
+ );
+}
+
+#define CPMHSIZE 256
+#define CPMHMASK (CPMHSIZE-1)
+#define CHTGRAN 10
+#define CHTSIZE 6
+
+typedef struct CTime {
+ unsigned long ct_Ticks;
+ int ct_Count;
+} CTime;
+
+typedef struct CHash {
+ union {
+ struct in_addr c4_Addr;
+ struct in6_addr c6_Addr;
+ } cu_Addr;
+#define ch_Addr4 cu_Addr.c4_Addr
+#define ch_Addr6 cu_Addr.c6_Addr
+ int ch_Family;
+ time_t ch_LTime;
+ char *ch_Service;
+ CTime ch_Times[CHTSIZE];
+} CHash;
+
+CHash CHashAry[CPMHSIZE];
+
+int
+cpmip(const struct servtab *sep, int ctrl)
+{
+ struct sockaddr_storage rss;
+ socklen_t rssLen = sizeof(rss);
+ int r = 0;
+
+ /*
+ * If getpeername() fails, just let it through (if logging is
+ * enabled the condition is caught elsewhere)
+ */
+
+ if (sep->se_maxcpm > 0 &&
+ (sep->se_family == AF_INET || sep->se_family == AF_INET6) &&
+ getpeername(ctrl, (struct sockaddr *)&rss, &rssLen) == 0 ) {
+ time_t t = time(NULL);
+ int hv = 0xABC3D20F;
+ int i;
+ int cnt = 0;
+ CHash *chBest = NULL;
+ unsigned int ticks = t / CHTGRAN;
+ struct sockaddr_in *sin4;
+#ifdef INET6
+ struct sockaddr_in6 *sin6;
+#endif
+
+ sin4 = (struct sockaddr_in *)&rss;
+#ifdef INET6
+ sin6 = (struct sockaddr_in6 *)&rss;
+#endif
+ {
+ char *p;
+ int addrlen;
+
+ switch (rss.ss_family) {
+ case AF_INET:
+ p = (char *)&sin4->sin_addr;
+ addrlen = sizeof(struct in_addr);
+ break;
+#ifdef INET6
+ case AF_INET6:
+ p = (char *)&sin6->sin6_addr;
+ addrlen = sizeof(struct in6_addr);
+ break;
+#endif
+ default:
+ /* should not happen */
+ return -1;
+ }
+
+ for (i = 0; i < addrlen; ++i, ++p) {
+ hv = (hv << 5) ^ (hv >> 23) ^ *p;
+ }
+ hv = (hv ^ (hv >> 16));
+ }
+ for (i = 0; i < 5; ++i) {
+ CHash *ch = &CHashAry[(hv + i) & CPMHMASK];
+
+ if (rss.ss_family == AF_INET &&
+ ch->ch_Family == AF_INET &&
+ sin4->sin_addr.s_addr == ch->ch_Addr4.s_addr &&
+ ch->ch_Service && strcmp(sep->se_service,
+ ch->ch_Service) == 0) {
+ chBest = ch;
+ break;
+ }
+#ifdef INET6
+ if (rss.ss_family == AF_INET6 &&
+ ch->ch_Family == AF_INET6 &&
+ IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr,
+ &ch->ch_Addr6) != 0 &&
+ ch->ch_Service && strcmp(sep->se_service,
+ ch->ch_Service) == 0) {
+ chBest = ch;
+ break;
+ }
+#endif
+ if (chBest == NULL || ch->ch_LTime == 0 ||
+ ch->ch_LTime < chBest->ch_LTime) {
+ chBest = ch;
+ }
+ }
+ if ((rss.ss_family == AF_INET &&
+ (chBest->ch_Family != AF_INET ||
+ sin4->sin_addr.s_addr != chBest->ch_Addr4.s_addr)) ||
+ chBest->ch_Service == NULL ||
+ strcmp(sep->se_service, chBest->ch_Service) != 0) {
+ chBest->ch_Family = sin4->sin_family;
+ chBest->ch_Addr4 = sin4->sin_addr;
+ if (chBest->ch_Service)
+ free(chBest->ch_Service);
+ chBest->ch_Service = strdup(sep->se_service);
+ bzero(chBest->ch_Times, sizeof(chBest->ch_Times));
+ }
+#ifdef INET6
+ if ((rss.ss_family == AF_INET6 &&
+ (chBest->ch_Family != AF_INET6 ||
+ IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr,
+ &chBest->ch_Addr6) == 0)) ||
+ chBest->ch_Service == NULL ||
+ strcmp(sep->se_service, chBest->ch_Service) != 0) {
+ chBest->ch_Family = sin6->sin6_family;
+ chBest->ch_Addr6 = sin6->sin6_addr;
+ if (chBest->ch_Service)
+ free(chBest->ch_Service);
+ chBest->ch_Service = strdup(sep->se_service);
+ bzero(chBest->ch_Times, sizeof(chBest->ch_Times));
+ }
+#endif
+ chBest->ch_LTime = t;
+ {
+ CTime *ct = &chBest->ch_Times[ticks % CHTSIZE];
+ if (ct->ct_Ticks != ticks) {
+ ct->ct_Ticks = ticks;
+ ct->ct_Count = 0;
+ }
+ ++ct->ct_Count;
+ }
+ for (i = 0; i < CHTSIZE; ++i) {
+ CTime *ct = &chBest->ch_Times[i];
+ if (ct->ct_Ticks <= ticks &&
+ ct->ct_Ticks >= ticks - CHTSIZE) {
+ cnt += ct->ct_Count;
+ }
+ }
+ if ((cnt * 60) / (CHTSIZE * CHTGRAN) > sep->se_maxcpm) {
+ char pname[NI_MAXHOST];
+
+ getnameinfo((struct sockaddr *)&rss,
+ ((struct sockaddr *)&rss)->sa_len,
+ pname, sizeof(pname), NULL, 0,
+ NI_NUMERICHOST);
+ r = -1;
+ syslog(LOG_ERR,
+ "%s from %s exceeded counts/min (limit %d/min)",
+ sep->se_service, pname,
+ sep->se_maxcpm);
+ }
+ }
+ return(r);
+}
+
+static struct conninfo *
+search_conn(struct servtab *sep, int ctrl)
+{
+ struct sockaddr_storage ss;
+ socklen_t sslen = sizeof(ss);
+ struct conninfo *conn;
+ int hv;
+ char pname[NI_MAXHOST], pname2[NI_MAXHOST];
+
+ if (sep->se_maxperip <= 0)
+ return NULL;
+
+ /*
+ * If getpeername() fails, just let it through (if logging is
+ * enabled the condition is caught elsewhere)
+ */
+ if (getpeername(ctrl, (struct sockaddr *)&ss, &sslen) != 0)
+ return NULL;
+
+ switch (ss.ss_family) {
+ case AF_INET:
+ hv = hashval((char *)&((struct sockaddr_in *)&ss)->sin_addr,
+ sizeof(struct in_addr));
+ break;
+#ifdef INET6
+ case AF_INET6:
+ hv = hashval((char *)&((struct sockaddr_in6 *)&ss)->sin6_addr,
+ sizeof(struct in6_addr));
+ break;
+#endif
+ default:
+ /*
+ * Since we only support AF_INET and AF_INET6, just
+ * let other than AF_INET and AF_INET6 through.
+ */
+ return NULL;
+ }
+
+ if (getnameinfo((struct sockaddr *)&ss, sslen, pname, sizeof(pname),
+ NULL, 0, NI_NUMERICHOST) != 0)
+ return NULL;
+
+ LIST_FOREACH(conn, &sep->se_conn[hv], co_link) {
+ if (getnameinfo((struct sockaddr *)&conn->co_addr,
+ conn->co_addr.ss_len, pname2, sizeof(pname2), NULL, 0,
+ NI_NUMERICHOST) == 0 &&
+ strcmp(pname, pname2) == 0)
+ break;
+ }
+
+ if (conn == NULL) {
+ if ((conn = malloc(sizeof(struct conninfo))) == NULL) {
+ syslog(LOG_ERR, "malloc: %m");
+ exit(EX_OSERR);
+ }
+ conn->co_proc = malloc(sep->se_maxperip * sizeof(*conn->co_proc));
+ if (conn->co_proc == NULL) {
+ syslog(LOG_ERR, "malloc: %m");
+ exit(EX_OSERR);
+ }
+ memcpy(&conn->co_addr, (struct sockaddr *)&ss, sslen);
+ conn->co_numchild = 0;
+ LIST_INSERT_HEAD(&sep->se_conn[hv], conn, co_link);
+ }
+
+ /*
+ * Since a child process is not invoked yet, we cannot
+ * determine a pid of a child. So, co_proc and co_numchild
+ * should be filled leter.
+ */
+
+ return conn;
+}
+
+static int
+room_conn(struct servtab *sep, struct conninfo *conn)
+{
+ char pname[NI_MAXHOST];
+
+ if (conn->co_numchild >= sep->se_maxperip) {
+ getnameinfo((struct sockaddr *)&conn->co_addr,
+ conn->co_addr.ss_len, pname, sizeof(pname), NULL, 0,
+ NI_NUMERICHOST);
+ syslog(LOG_ERR, "%s from %s exceeded counts (limit %d)",
+ sep->se_service, pname, sep->se_maxperip);
+ return 0;
+ }
+ return 1;
+}
+
+static void
+addchild_conn(struct conninfo *conn, pid_t pid)
+{
+ struct procinfo *proc;
+
+ if (conn == NULL)
+ return;
+
+ if ((proc = search_proc(pid, 1)) != NULL) {
+ if (proc->pr_conn != NULL) {
+ syslog(LOG_ERR,
+ "addchild_conn: child already on process list");
+ exit(EX_OSERR);
+ }
+ proc->pr_conn = conn;
+ }
+
+ conn->co_proc[conn->co_numchild++] = proc;
+}
+
+static void
+reapchild_conn(pid_t pid)
+{
+ struct procinfo *proc;
+ struct conninfo *conn;
+ int i;
+
+ if ((proc = search_proc(pid, 0)) == NULL)
+ return;
+ if ((conn = proc->pr_conn) == NULL)
+ return;
+ for (i = 0; i < conn->co_numchild; ++i)
+ if (conn->co_proc[i] == proc) {
+ conn->co_proc[i] = conn->co_proc[--conn->co_numchild];
+ break;
+ }
+ free_proc(proc);
+ free_conn(conn);
+}
+
+static void
+resize_conn(struct servtab *sep, int maxpip)
+{
+ struct conninfo *conn;
+ int i, j;
+
+ if (sep->se_maxperip <= 0)
+ return;
+ if (maxpip <= 0) {
+ free_connlist(sep);
+ return;
+ }
+ for (i = 0; i < PERIPSIZE; ++i) {
+ LIST_FOREACH(conn, &sep->se_conn[i], co_link) {
+ for (j = maxpip; j < conn->co_numchild; ++j)
+ free_proc(conn->co_proc[j]);
+ conn->co_proc = realloc(conn->co_proc,
+ maxpip * sizeof(*conn->co_proc));
+ if (conn->co_proc == NULL) {
+ syslog(LOG_ERR, "realloc: %m");
+ exit(EX_OSERR);
+ }
+ if (conn->co_numchild > maxpip)
+ conn->co_numchild = maxpip;
+ }
+ }
+}
+
+static void
+free_connlist(struct servtab *sep)
+{
+ struct conninfo *conn;
+ int i, j;
+
+ for (i = 0; i < PERIPSIZE; ++i) {
+ while ((conn = LIST_FIRST(&sep->se_conn[i])) != NULL) {
+ for (j = 0; j < conn->co_numchild; ++j)
+ free_proc(conn->co_proc[j]);
+ conn->co_numchild = 0;
+ free_conn(conn);
+ }
+ }
+}
+
+static void
+free_conn(struct conninfo *conn)
+{
+ if (conn == NULL)
+ return;
+ if (conn->co_numchild <= 0) {
+ LIST_REMOVE(conn, co_link);
+ free(conn->co_proc);
+ free(conn);
+ }
+}
+
+static struct procinfo *
+search_proc(pid_t pid, int add)
+{
+ struct procinfo *proc;
+ int hv;
+
+ hv = hashval((char *)&pid, sizeof(pid));
+ LIST_FOREACH(proc, &proctable[hv], pr_link) {
+ if (proc->pr_pid == pid)
+ break;
+ }
+ if (proc == NULL && add) {
+ if ((proc = malloc(sizeof(struct procinfo))) == NULL) {
+ syslog(LOG_ERR, "malloc: %m");
+ exit(EX_OSERR);
+ }
+ proc->pr_pid = pid;
+ proc->pr_conn = NULL;
+ LIST_INSERT_HEAD(&proctable[hv], proc, pr_link);
+ }
+ return proc;
+}
+
+static void
+free_proc(struct procinfo *proc)
+{
+ if (proc == NULL)
+ return;
+ LIST_REMOVE(proc, pr_link);
+ free(proc);
+}
+
+static int
+hashval(char *p, int len)
+{
+ int i, hv = 0xABC3D20F;
+
+ for (i = 0; i < len; ++i, ++p)
+ hv = (hv << 5) ^ (hv >> 23) ^ *p;
+ hv = (hv ^ (hv >> 16)) & (PERIPSIZE - 1);
+ return hv;
+}
diff --git a/usr.sbin/inetd/inetd.h b/usr.sbin/inetd/inetd.h
new file mode 100644
index 0000000..06600f8
--- /dev/null
+++ b/usr.sbin/inetd/inetd.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 1983, 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/queue.h>
+
+#include <netinet/in.h>
+
+#include <stdio.h>
+
+#define BUFSIZE 8192
+#define LINESIZ 72
+
+#define NORM_TYPE 0
+#define MUX_TYPE 1
+#define MUXPLUS_TYPE 2
+#define FAITH_TYPE 4
+#define ISMUX(sep) (((sep)->se_type == MUX_TYPE) || \
+ ((sep)->se_type == MUXPLUS_TYPE))
+#define ISMUXPLUS(sep) ((sep)->se_type == MUXPLUS_TYPE)
+
+struct procinfo {
+ LIST_ENTRY(procinfo) pr_link;
+ pid_t pr_pid; /* child pid */
+ struct conninfo *pr_conn;
+};
+
+struct conninfo {
+ LIST_ENTRY(conninfo) co_link;
+ struct sockaddr_storage co_addr; /* source address */
+ int co_numchild; /* current number of children */
+ struct procinfo **co_proc; /* array of child proc entry */
+};
+
+#define PERIPSIZE 256
+
+struct servtab {
+ char *se_service; /* name of service */
+ int se_socktype; /* type of socket to use */
+ int se_family; /* address family */
+ char *se_proto; /* protocol used */
+ int se_maxchild; /* max number of children */
+ int se_maxcpm; /* max connects per IP per minute */
+ int se_numchild; /* current number of children */
+ pid_t *se_pids; /* array of child pids */
+ char *se_user; /* user name to run as */
+ char *se_group; /* group name to run as */
+#ifdef LOGIN_CAP
+ char *se_class; /* login class name to run with */
+#endif
+ struct biltin *se_bi; /* if built-in, description */
+ char *se_server; /* server program */
+ char *se_server_name; /* server program without path */
+#define MAXARGV 20
+ char *se_argv[MAXARGV+1]; /* program arguments */
+#ifdef IPSEC
+ char *se_policy; /* IPsec policy string */
+#endif
+ int se_fd; /* open descriptor */
+ union { /* bound address */
+ struct sockaddr se_un_ctrladdr;
+ struct sockaddr_in se_un_ctrladdr4;
+ struct sockaddr_in6 se_un_ctrladdr6;
+ struct sockaddr_un se_un_ctrladdr_un;
+ } se_un;
+#define se_ctrladdr se_un.se_un_ctrladdr
+#define se_ctrladdr4 se_un.se_un_ctrladdr4
+#define se_ctrladdr6 se_un.se_un_ctrladdr6
+#define se_ctrladdr_un se_un.se_un_ctrladdr_un
+ socklen_t se_ctrladdr_size;
+ uid_t se_sockuid; /* Owner for unix domain socket */
+ gid_t se_sockgid; /* Group for unix domain socket */
+ mode_t se_sockmode; /* Mode for unix domain socket */
+ u_char se_type; /* type: normal, mux, or mux+ */
+ u_char se_checked; /* looked at during merge */
+ u_char se_accept; /* i.e., wait/nowait mode */
+ u_char se_rpc; /* ==1 if RPC service */
+ int se_rpc_prog; /* RPC program number */
+ u_int se_rpc_lowvers; /* RPC low version */
+ u_int se_rpc_highvers; /* RPC high version */
+ int se_count; /* number started since se_time */
+ struct timespec se_time; /* start of se_count */
+ struct servtab *se_next;
+ struct se_flags {
+ u_int se_nomapped : 1;
+ u_int se_reset : 1;
+ } se_flags;
+ int se_maxperip; /* max number of children per src */
+ LIST_HEAD(, conninfo) se_conn[PERIPSIZE];
+};
+
+#define se_nomapped se_flags.se_nomapped
+#define se_reset se_flags.se_reset
+
+int check_loop(const struct sockaddr *, const struct servtab *sep);
+int getvalue(const char *, int *, const char *);
+char *newstr(const char *);
+void inetd_setproctitle(const char *, int);
+void print_service(const char *, const struct servtab *);
+char *sskip(char **);
+char *skip(char **);
+struct servtab *tcpmux(int);
+
+extern int debug;
+extern struct servtab *servtab;
+
+typedef void (bi_fn_t)(int, struct servtab *);
+
+struct biltin {
+ const char *bi_service; /* internally provided service name */
+ int bi_socktype; /* type of socket supported */
+ short bi_fork; /* 1 if should fork before call */
+ int bi_maxchild; /* max number of children, -1=default */
+ bi_fn_t *bi_fn; /* function which performs it */
+};
diff --git a/usr.sbin/inetd/pathnames.h b/usr.sbin/inetd/pathnames.h
new file mode 100644
index 0000000..d4fd81d
--- /dev/null
+++ b/usr.sbin/inetd/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/6/93
+ * $FreeBSD$
+ */
+
+#include <paths.h>
+
+#define _PATH_INETDCONF "/etc/inetd.conf"
+#define _PATH_INETDPID _PATH_VARRUN "inetd.pid"
diff --git a/usr.sbin/iostat/Makefile b/usr.sbin/iostat/Makefile
new file mode 100644
index 0000000..dfbf69d
--- /dev/null
+++ b/usr.sbin/iostat/Makefile
@@ -0,0 +1,11 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+PROG= iostat
+MAN= iostat.8
+
+LIBADD= devstat kvm m
+
+WARNS?= 1
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/iostat/Makefile.depend b/usr.sbin/iostat/Makefile.depend
new file mode 100644
index 0000000..ca404f2
--- /dev/null
+++ b/usr.sbin/iostat/Makefile.depend
@@ -0,0 +1,22 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libdevstat \
+ lib/libelf \
+ lib/libkvm \
+ lib/msun \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/iostat/iostat.8 b/usr.sbin/iostat/iostat.8
new file mode 100644
index 0000000..2073679
--- /dev/null
+++ b/usr.sbin/iostat/iostat.8
@@ -0,0 +1,516 @@
+.\"
+.\" Copyright (c) 1997 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$
+.\"
+.\" 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.
+.\"
+.\" @(#)iostat.8 8.1 (Berkeley) 6/6/93
+.\"
+.Dd May 22, 2015
+.Dt IOSTAT 8
+.Os
+.Sh NAME
+.Nm iostat
+.Nd report
+.Tn I/O
+statistics
+.Sh SYNOPSIS
+.Nm
+.Op Fl CdhIKoTxz?\&
+.Op Fl c Ar count
+.Op Fl M Ar core
+.Op Fl n Ar devs
+.Op Fl N Ar system
+.Oo
+.Fl t
+.Sm off
+.Ar type , if , pass
+.Sm on
+.Oc
+.Op Fl w Ar wait
+.Op Ar drives
+.Sh DESCRIPTION
+The
+.Nm
+utility displays kernel
+.Tn I/O
+statistics on terminal, device and cpu operations.
+The first statistics that are printed are averaged over the system uptime.
+To get information about the current activity, a suitable wait time should
+be specified, so that the subsequent sets of printed statistics will be
+averaged over that time.
+.Pp
+The options are as follows:
+.Bl -tag -width flag
+.It Fl c
+Repeat the display
+.Ar count
+times.
+If no repeat
+.Ar count
+is specified, the default depends on whether
+.Fl w
+is specified.
+With
+.Fl w
+the default repeat count is infinity, otherwise it is 1.
+.It Fl C
+Display CPU statistics.
+This is on by default, unless
+.Fl d
+or
+.Fl x
+is specified.
+.It Fl d
+Display only device statistics.
+If this flag is turned on, only device statistics will be displayed, unless
+.Fl C
+or
+.Fl T
+is also specified to enable the display of CPU or TTY statistics.
+.It Fl h
+Put
+.Nm
+in
+.Sq top
+mode.
+In this mode,
+.Nm
+will show devices in order from highest to lowest bytes
+per measurement cycle.
+.It Fl I
+Display total statistics for a given time period, rather than average
+statistics for each second during that time period.
+.It Fl K
+In the blocks transferred display (-o), display block count in kilobytes rather
+then the device native block size.
+.It Fl M
+Extract values associated with the name list from the specified core
+instead of the default
+.Dq Pa /dev/kmem .
+.It Fl n
+Display up to
+.Ar devs
+number of devices.
+The
+.Nm
+utility will display fewer devices if there are not
+.Ar devs
+devices present.
+.It Fl N
+Extract the name list from the specified system instead of the default
+.Dq Pa /boot/kernel/kernel .
+.It Fl o
+Display old-style
+.Nm
+device statistics.
+Sectors per second, transfers per second, and milliseconds per seek are
+displayed.
+If
+.Fl I
+is specified, total blocks/sectors, total transfers, and
+milliseconds per seek are displayed.
+.It Fl t
+Specify which types of devices to display.
+There are three different categories of devices:
+.Pp
+.Bl -tag -width indent -compact
+.It device type:
+.Bl -tag -width 9n -compact
+.It da
+Direct Access devices
+.It sa
+Sequential Access devices
+.It printer
+Printers
+.It proc
+Processor devices
+.It worm
+Write Once Read Multiple devices
+.It cd
+CD devices
+.It scanner
+Scanner devices
+.It optical
+Optical Memory devices
+.It changer
+Medium Changer devices
+.It comm
+Communication devices
+.It array
+Storage Array devices
+.It enclosure
+Enclosure Services devices
+.It floppy
+Floppy devices
+.El
+.Pp
+.It interface:
+.Bl -tag -width 9n -compact
+.It IDE
+Integrated Drive Electronics devices
+.It SCSI
+Small Computer System Interface devices
+.It other
+Any other device interface
+.El
+.Pp
+.It passthrough:
+.Bl -tag -width 9n -compact
+.It pass
+Passthrough devices
+.El
+.El
+.Pp
+The user must specify at least one device type, and may specify at most
+one device type from each category.
+Multiple device types in a single device type statement must be separated by
+commas.
+.Pp
+Any number of
+.Fl t
+arguments may be specified on the command line.
+All
+.Fl t
+arguments are ORed together to form a matching expression against which
+all devices in the system are compared.
+Any device that fully matches any
+.Fl t
+argument will be included in the
+.Nm
+output, up to the number of devices that can be displayed in
+80 columns, or the maximum number of devices specified by the user.
+.It Fl T
+Display TTY statistics.
+This is on by default, unless
+.Fl d
+or
+.Fl x
+is specified.
+.It Fl w
+Pause
+.Ar wait
+seconds between each display.
+If no
+.Ar wait
+interval is specified, the default is 1 second.
+.Pp
+The
+.Nm
+command will accept and honor a non-integer number of seconds.
+Note that the interval only has millisecond granularity.
+Finer values will be truncated.
+E.g.,
+.Dq Li -w1.0001
+is the same as
+.Dq Li -w1.000 .
+The interval will also suffer from modifications to
+.Va kern.hz
+so your mileage may vary.
+.It Fl x
+Show extended disk statistics.
+Each disk is displayed on a line of its own with all available statistics.
+If this flag is turned on, only disk statistics will be displayed, unless
+.Fl C
+or
+.Fl T
+is also specified to enable the display of CPU or TTY statistics.
+.It Fl z
+If
+.Fl x
+is specified, omit lines for devices with no activity.
+.It Fl ?\&
+Display a usage statement and exit.
+.El
+.Pp
+The
+.Nm
+utility displays its information in the following format:
+.Bl -tag -width flag
+.It tty
+.Bl -tag -width indent -compact
+.It tin
+characters read from terminals
+.It tout
+characters written to terminals
+.El
+.It devices
+Device operations.
+The header of the field is the device name and unit number.
+The
+.Nm
+utility
+will display as many devices as will fit in a standard 80 column screen, or
+the maximum number of devices in the system, whichever is smaller.
+If
+.Fl n
+is specified on the command line,
+.Nm
+will display the smaller of the
+requested number of devices, and the maximum number of devices in the system.
+To force
+.Nm
+to display specific drives, their names may be supplied on the command
+line.
+The
+.Nm
+utility
+will not display more devices than will fit in an 80 column screen, unless
+the
+.Fl n
+argument is given on the command line to specify a maximum number of
+devices to display.
+If fewer devices are specified on the command line than will fit in an 80
+column screen,
+.Nm
+will show only the specified devices.
+.Pp
+The standard
+.Nm
+device display shows the following statistics:
+.Pp
+.Bl -tag -width indent -compact
+.It KB/t
+kilobytes per transfer
+.It tps
+transfers per second
+.It MB/s
+megabytes per second
+.El
+.Pp
+The standard
+.Nm
+device display, with the
+.Fl I
+flag specified, shows the following statistics:
+.Pp
+.Bl -tag -width indent -compact
+.It KB/t
+kilobytes per transfer
+.It xfrs
+total number of transfers
+.It MB
+total number of megabytes transferred
+.El
+.Pp
+The extended
+.Nm
+device display, with the
+.Fl x
+flag specified, shows the following statistics:
+.Pp
+.Bl -tag -width indent -compact
+.It r/s
+read operations per second
+.It w/s
+write operations per second
+.It kr/s
+kilobytes read per second
+.It kw/s
+kilobytes write per second
+.It qlen
+transactions queue length
+.It svc_t
+average duration of transactions, in milliseconds
+.It %b
+% of time the device had one or more outstanding transactions
+.El
+.Pp
+The extended
+.Nm
+device display, with the
+.Fl x
+and
+.Fl I
+flags specified, shows the following statistics:
+.Pp
+.Bl -tag -width indent -compact
+.It r/i
+read operations per time period
+.It w/i
+write operations per time period
+.It kr/i
+kilobytes read per time period
+.It kw/i
+kilobytes write per time period
+.It qlen
+transactions queue length
+.It tsvc_t/i
+total duration of transactions per time period, in seconds
+.It sb/i
+total time the device had one or more outstanding transactions per
+time period, in seconds
+.El
+.Pp
+The old-style
+.Nm
+display (using
+.Fl o )
+shows the following statistics:
+.Pp
+.Bl -tag -width indent -compact
+.It sps
+sectors transferred per second
+.It tps
+transfers per second
+.It msps
+average milliseconds per transaction
+.El
+.Pp
+The old-style
+.Nm
+display, with the
+.Fl I
+flag specified, shows the following statistics:
+.Pp
+.Bl -tag -width indent -compact
+.It blk
+total blocks/sectors transferred
+.It xfr
+total transfers
+.It msps
+average milliseconds per transaction
+.El
+.It cpu
+.Bl -tag -width indent -compact
+.It \&us
+% of cpu time in user mode
+.It \&ni
+% of cpu time in user mode running niced processes
+.It \&sy
+% of cpu time in system mode
+.It \&in
+% of cpu time in interrupt mode
+.It \&id
+% of cpu time in idle mode
+.El
+.El
+.Sh FILES
+.Bl -tag -width /boot/kernel/kernel -compact
+.It Pa /boot/kernel/kernel
+Default kernel namelist.
+.It Pa /dev/kmem
+Default memory file.
+.El
+.Sh EXAMPLES
+.Dl iostat -w 1 da0 da1 cd0
+.Pp
+Display statistics for the first two Direct Access devices and the first
+CDROM device every second ad infinitum.
+.Pp
+.Dl iostat -c 2
+.Pp
+Display the statistics for the first four devices in the system twice, with
+a one second display interval.
+.Pp
+.Dl iostat -t da -t cd -w 1
+.Pp
+Display statistics for all CDROM and Direct Access devices every second
+ad infinitum.
+.Pp
+.Dl iostat -t da,scsi,pass -t cd,scsi,pass
+.Pp
+Display statistics once for all SCSI passthrough devices that provide access
+to either Direct Access or CDROM devices.
+.Pp
+.Dl iostat -h -n 8 -w 1
+.Pp
+Display up to 8 devices with the most I/O every second ad infinitum.
+.Pp
+.Dl iostat -dh -t da -w 1
+.Pp
+Omit the TTY and CPU displays, show devices in order of performance and
+show only Direct Access devices every second ad infinitum.
+.Pp
+.Dl iostat -Iw 3
+.Pp
+Display total statistics every three seconds ad infinitum.
+.Pp
+.Dl iostat -odICTw 2 -c 9
+.Pp
+Display total statistics using the old-style output format 9 times, with
+a two second interval between each measurement/display.
+The
+.Fl d
+flag generally disables the TTY and CPU displays, but since the
+.Fl T
+and
+.Fl C
+flags are given, the TTY and CPU displays will be displayed.
+.Sh SEE ALSO
+.Xr fstat 1 ,
+.Xr netstat 1 ,
+.Xr nfsstat 1 ,
+.Xr ps 1 ,
+.Xr systat 1 ,
+.Xr devstat 3 ,
+.Xr ctlstat 8 ,
+.Xr gstat 8 ,
+.Xr pstat 8 ,
+.Xr vmstat 8
+.Pp
+The sections starting with ``Interpreting system activity'' in
+.%T "Installing and Operating 4.3BSD" .
+.Sh HISTORY
+This version of
+.Nm
+first appeared in
+.Fx 3.0 .
+.Sh AUTHORS
+.An Kenneth Merry Aq Mt ken@FreeBSD.org
+.Sh BUGS
+The use of
+.Nm
+as a debugging tool for crash dumps is probably limited because there is
+currently no way to get statistics that only cover the time immediately before
+the crash.
diff --git a/usr.sbin/iostat/iostat.c b/usr.sbin/iostat/iostat.c
new file mode 100644
index 0000000..170ce0d
--- /dev/null
+++ b/usr.sbin/iostat/iostat.c
@@ -0,0 +1,1021 @@
+/*
+ * Copyright (c) 1997, 1998, 2000, 2001 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$
+ */
+/*
+ * Parts of this program are derived from the original FreeBSD iostat
+ * program:
+ */
+/*-
+ * Copyright (c) 1986, 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.
+ */
+/*
+ * Ideas for the new iostat statistics output modes taken from the NetBSD
+ * version of iostat:
+ */
+/*
+ * Copyright (c) 1996 John M. Vinopal
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 John M. Vinopal.
+ * 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/param.h>
+#include <sys/errno.h>
+#include <sys/resource.h>
+#include <sys/sysctl.h>
+
+#include <ctype.h>
+#include <devstat.h>
+#include <err.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <kvm.h>
+#include <limits.h>
+#include <math.h>
+#include <nlist.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+struct nlist namelist[] = {
+#define X_TTY_NIN 0
+ { "_tty_nin" },
+#define X_TTY_NOUT 1
+ { "_tty_nout" },
+#define X_BOOTTIME 2
+ { "_boottime" },
+#define X_END 2
+ { NULL },
+};
+
+#define IOSTAT_DEFAULT_ROWS 20 /* Traditional default `wrows' */
+
+struct statinfo cur, last;
+int num_devices;
+struct device_selection *dev_select;
+int maxshowdevs;
+volatile sig_atomic_t headercount;
+volatile sig_atomic_t wresized; /* Tty resized, when non-zero. */
+volatile sig_atomic_t alarm_rang;
+volatile sig_atomic_t return_requested;
+unsigned short wrows; /* Current number of tty rows. */
+int dflag = 0, Iflag = 0, Cflag = 0, Tflag = 0, oflag = 0, Kflag = 0;
+int xflag = 0, zflag = 0;
+
+/* local function declarations */
+static void usage(void);
+static void needhdr(int signo);
+static void needresize(int signo);
+static void needreturn(int signo);
+static void alarm_clock(int signo);
+static void doresize(void);
+static void phdr(void);
+static void devstats(int perf_select, long double etime, int havelast);
+static void cpustats(void);
+static int readvar(kvm_t *kd, const char *name, int nlid, void *ptr,
+ size_t len);
+
+static void
+usage(void)
+{
+ /*
+ * We also support the following 'traditional' syntax:
+ * iostat [drives] [wait [count]]
+ * This isn't mentioned in the man page, or the usage statement,
+ * but it is supported.
+ */
+ fprintf(stderr, "usage: iostat [-CdhIKoTxz?] [-c count] [-M core]"
+ " [-n devs] [-N system]\n"
+ "\t [-t type,if,pass] [-w wait] [drives]\n");
+}
+
+int
+main(int argc, char **argv)
+{
+ int c, i;
+ int tflag = 0, hflag = 0, cflag = 0, wflag = 0, nflag = 0;
+ int count = 0, waittime = 0;
+ char *memf = NULL, *nlistf = NULL;
+ struct devstat_match *matches;
+ struct itimerval alarmspec;
+ int num_matches = 0;
+ char errbuf[_POSIX2_LINE_MAX];
+ kvm_t *kd = NULL;
+ long generation;
+ int num_devices_specified;
+ int num_selected, num_selections;
+ long select_generation;
+ char **specified_devices;
+ devstat_select_mode select_mode;
+ float f;
+ int havelast = 0;
+
+ matches = NULL;
+ maxshowdevs = 3;
+
+ while ((c = getopt(argc, argv, "c:CdhIKM:n:N:ot:Tw:xz?")) != -1) {
+ switch(c) {
+ case 'c':
+ cflag++;
+ count = atoi(optarg);
+ if (count < 1)
+ errx(1, "count %d is < 1", count);
+ break;
+ case 'C':
+ Cflag++;
+ break;
+ case 'd':
+ dflag++;
+ break;
+ case 'h':
+ hflag++;
+ break;
+ case 'I':
+ Iflag++;
+ break;
+ case 'K':
+ Kflag++;
+ break;
+ case 'M':
+ memf = optarg;
+ break;
+ case 'n':
+ nflag++;
+ maxshowdevs = atoi(optarg);
+ if (maxshowdevs < 0)
+ errx(1, "number of devices %d is < 0",
+ maxshowdevs);
+ break;
+ case 'N':
+ nlistf = optarg;
+ break;
+ case 'o':
+ oflag++;
+ break;
+ case 't':
+ tflag++;
+ if (devstat_buildmatch(optarg, &matches,
+ &num_matches) != 0)
+ errx(1, "%s", devstat_errbuf);
+ break;
+ case 'T':
+ Tflag++;
+ break;
+ case 'w':
+ wflag++;
+ f = atof(optarg);
+ waittime = f * 1000;
+ if (waittime < 1)
+ errx(1, "wait time is < 1ms");
+ break;
+ case 'x':
+ xflag++;
+ break;
+ case 'z':
+ zflag++;
+ break;
+ default:
+ usage();
+ exit(1);
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (nlistf != NULL || memf != NULL) {
+ kd = kvm_openfiles(nlistf, memf, NULL, O_RDONLY, errbuf);
+
+ if (kd == NULL)
+ errx(1, "kvm_openfiles: %s", errbuf);
+
+ if (kvm_nlist(kd, namelist) == -1)
+ errx(1, "kvm_nlist: %s", kvm_geterr(kd));
+ }
+
+ /*
+ * Make sure that the userland devstat version matches the kernel
+ * devstat version. If not, exit and print a message informing
+ * the user of his mistake.
+ */
+ if (devstat_checkversion(kd) < 0)
+ errx(1, "%s", devstat_errbuf);
+
+ /*
+ * Make sure Tflag and/or Cflag are set if dflag == 0. If dflag is
+ * greater than 0, they may be 0 or non-zero.
+ */
+ if (dflag == 0 && xflag == 0) {
+ Cflag = 1;
+ Tflag = 1;
+ }
+
+ /* find out how many devices we have */
+ if ((num_devices = devstat_getnumdevs(kd)) < 0)
+ err(1, "can't get number of devices");
+
+ /*
+ * Figure out how many devices we should display.
+ */
+ if (nflag == 0) {
+ if (xflag > 0)
+ maxshowdevs = num_devices;
+ else if (oflag > 0) {
+ if ((dflag > 0) && (Cflag == 0) && (Tflag == 0))
+ maxshowdevs = 5;
+ else if ((dflag > 0) && (Tflag > 0) && (Cflag == 0))
+ maxshowdevs = 5;
+ else
+ maxshowdevs = 4;
+ } else {
+ if ((dflag > 0) && (Cflag == 0))
+ maxshowdevs = 4;
+ else
+ maxshowdevs = 3;
+ }
+ }
+
+ cur.dinfo = (struct devinfo *)calloc(1, sizeof(struct devinfo));
+ if (cur.dinfo == NULL)
+ err(1, "calloc failed");
+
+ last.dinfo = (struct devinfo *)calloc(1, sizeof(struct devinfo));
+ if (last.dinfo == NULL)
+ err(1, "calloc failed");
+
+ /*
+ * Grab all the devices. We don't look to see if the list has
+ * changed here, since it almost certainly has. We only look for
+ * errors.
+ */
+ if (devstat_getdevs(kd, &cur) == -1)
+ errx(1, "%s", devstat_errbuf);
+
+ num_devices = cur.dinfo->numdevs;
+ generation = cur.dinfo->generation;
+
+ /*
+ * If the user specified any devices on the command line, see if
+ * they are in the list of devices we have now.
+ */
+ specified_devices = (char **)malloc(sizeof(char *));
+ if (specified_devices == NULL)
+ err(1, "malloc failed");
+
+ for (num_devices_specified = 0; *argv; ++argv) {
+ if (isdigit(**argv))
+ break;
+ num_devices_specified++;
+ specified_devices = (char **)realloc(specified_devices,
+ sizeof(char *) *
+ num_devices_specified);
+ if (specified_devices == NULL)
+ err(1, "realloc failed");
+
+ specified_devices[num_devices_specified - 1] = *argv;
+
+ }
+ if (nflag == 0 && maxshowdevs < num_devices_specified)
+ maxshowdevs = num_devices_specified;
+
+ dev_select = NULL;
+
+ if ((num_devices_specified == 0) && (num_matches == 0))
+ select_mode = DS_SELECT_ADD;
+ else
+ select_mode = DS_SELECT_ONLY;
+
+ /*
+ * At this point, selectdevs will almost surely indicate that the
+ * device list has changed, so we don't look for return values of 0
+ * or 1. If we get back -1, though, there is an error.
+ */
+ if (devstat_selectdevs(&dev_select, &num_selected,
+ &num_selections, &select_generation, generation,
+ cur.dinfo->devices, num_devices, matches,
+ num_matches, specified_devices,
+ num_devices_specified, select_mode, maxshowdevs,
+ hflag) == -1)
+ errx(1, "%s", devstat_errbuf);
+
+ /*
+ * Look for the traditional wait time and count arguments.
+ */
+ if (*argv) {
+ f = atof(*argv);
+ waittime = f * 1000;
+
+ /* Let the user know he goofed, but keep going anyway */
+ if (wflag != 0)
+ warnx("discarding previous wait interval, using"
+ " %g instead", waittime / 1000.0);
+ wflag++;
+
+ if (*++argv) {
+ count = atoi(*argv);
+ if (cflag != 0)
+ warnx("discarding previous count, using %d"
+ " instead", count);
+ cflag++;
+ } else
+ count = -1;
+ }
+
+ /*
+ * If the user specified a count, but not an interval, we default
+ * to an interval of 1 second.
+ */
+ if ((wflag == 0) && (cflag > 0))
+ waittime = 1 * 1000;
+
+ /*
+ * If the user specified a wait time, but not a count, we want to
+ * go on ad infinitum. This can be redundant if the user uses the
+ * traditional method of specifying the wait, since in that case we
+ * already set count = -1 above. Oh well.
+ */
+ if ((wflag > 0) && (cflag == 0))
+ count = -1;
+
+ bzero(cur.cp_time, sizeof(cur.cp_time));
+ cur.tk_nout = 0;
+ cur.tk_nin = 0;
+
+ /*
+ * Set the snap time to the system boot time (ie: zero), so the
+ * stats are calculated since system boot.
+ */
+ cur.snap_time = 0;
+
+ /*
+ * If the user stops the program (control-Z) and then resumes it,
+ * print out the header again.
+ */
+ (void)signal(SIGCONT, needhdr);
+
+ /*
+ * If our standard output is a tty, then install a SIGWINCH handler
+ * and set wresized so that our first iteration through the main
+ * iostat loop will peek at the terminal's current rows to find out
+ * how many lines can fit in a screenful of output.
+ */
+ if (isatty(fileno(stdout)) != 0) {
+ wresized = 1;
+ (void)signal(SIGWINCH, needresize);
+ } else {
+ wresized = 0;
+ wrows = IOSTAT_DEFAULT_ROWS;
+ }
+
+ /*
+ * Register a SIGINT handler so that we can print out final statistics
+ * when we get that signal
+ */
+ (void)signal(SIGINT, needreturn);
+
+ /*
+ * Register a SIGALRM handler to implement sleeps if the user uses the
+ * -c or -w options
+ */
+ (void)signal(SIGALRM, alarm_clock);
+ alarmspec.it_interval.tv_sec = waittime / 1000;
+ alarmspec.it_interval.tv_usec = 1000 * (waittime % 1000);
+ alarmspec.it_value.tv_sec = waittime / 1000;
+ alarmspec.it_value.tv_usec = 1000 * (waittime % 1000);
+ setitimer(ITIMER_REAL, &alarmspec, NULL);
+
+ for (headercount = 1;;) {
+ struct devinfo *tmp_dinfo;
+ long tmp;
+ long double etime;
+ sigset_t sigmask, oldsigmask;
+
+ if (Tflag > 0) {
+ if ((readvar(kd, "kern.tty_nin", X_TTY_NIN, &cur.tk_nin,
+ sizeof(cur.tk_nin)) != 0)
+ || (readvar(kd, "kern.tty_nout", X_TTY_NOUT,
+ &cur.tk_nout, sizeof(cur.tk_nout))!= 0)) {
+ Tflag = 0;
+ warnx("disabling TTY statistics");
+ }
+ }
+
+ if (Cflag > 0) {
+ if (kd == NULL) {
+ if (readvar(kd, "kern.cp_time", 0,
+ &cur.cp_time, sizeof(cur.cp_time)) != 0)
+ Cflag = 0;
+ } else {
+ if (kvm_getcptime(kd, cur.cp_time) < 0) {
+ warnx("kvm_getcptime: %s",
+ kvm_geterr(kd));
+ Cflag = 0;
+ }
+ }
+ if (Cflag == 0)
+ warnx("disabling CPU time statistics");
+ }
+
+ if (!--headercount) {
+ phdr();
+ if (wresized != 0)
+ doresize();
+ headercount = wrows;
+ }
+
+ tmp_dinfo = last.dinfo;
+ last.dinfo = cur.dinfo;
+ cur.dinfo = tmp_dinfo;
+
+ last.snap_time = cur.snap_time;
+
+ /*
+ * Here what we want to do is refresh our device stats.
+ * devstat_getdevs() returns 1 when the device list has changed.
+ * If the device list has changed, we want to go through
+ * the selection process again, in case a device that we
+ * were previously displaying has gone away.
+ */
+ switch (devstat_getdevs(kd, &cur)) {
+ case -1:
+ errx(1, "%s", devstat_errbuf);
+ break;
+ case 1: {
+ int retval;
+
+ num_devices = cur.dinfo->numdevs;
+ generation = cur.dinfo->generation;
+ retval = devstat_selectdevs(&dev_select, &num_selected,
+ &num_selections,
+ &select_generation,
+ generation,
+ cur.dinfo->devices,
+ num_devices, matches,
+ num_matches,
+ specified_devices,
+ num_devices_specified,
+ select_mode, maxshowdevs,
+ hflag);
+ switch(retval) {
+ case -1:
+ errx(1, "%s", devstat_errbuf);
+ break;
+ case 1:
+ phdr();
+ if (wresized != 0)
+ doresize();
+ headercount = wrows;
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ /*
+ * We only want to re-select devices if we're in 'top'
+ * mode. This is the only mode where the devices selected
+ * could actually change.
+ */
+ if (hflag > 0) {
+ int retval;
+ retval = devstat_selectdevs(&dev_select, &num_selected,
+ &num_selections,
+ &select_generation,
+ generation,
+ cur.dinfo->devices,
+ num_devices, matches,
+ num_matches,
+ specified_devices,
+ num_devices_specified,
+ select_mode, maxshowdevs,
+ hflag);
+ switch(retval) {
+ case -1:
+ errx(1,"%s", devstat_errbuf);
+ break;
+ case 1:
+ phdr();
+ if (wresized != 0)
+ doresize();
+ headercount = wrows;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (Tflag > 0) {
+ tmp = cur.tk_nin;
+ cur.tk_nin -= last.tk_nin;
+ last.tk_nin = tmp;
+ tmp = cur.tk_nout;
+ cur.tk_nout -= last.tk_nout;
+ last.tk_nout = tmp;
+ }
+
+ etime = cur.snap_time - last.snap_time;
+
+ if (etime == 0.0)
+ etime = 1.0;
+
+ for (i = 0; i < CPUSTATES; i++) {
+ tmp = cur.cp_time[i];
+ cur.cp_time[i] -= last.cp_time[i];
+ last.cp_time[i] = tmp;
+ }
+
+ if (xflag == 0 && Tflag > 0)
+ printf("%4.0Lf %5.0Lf", cur.tk_nin / etime,
+ cur.tk_nout / etime);
+
+ devstats(hflag, etime, havelast);
+
+ if (xflag == 0) {
+ if (Cflag > 0)
+ cpustats();
+
+ printf("\n");
+ }
+ fflush(stdout);
+
+ if ((count >= 0 && --count <= 0) || return_requested)
+ break;
+
+ /*
+ * Use sigsuspend to safely sleep until either signal is
+ * received
+ */
+ alarm_rang = 0;
+ sigemptyset(&sigmask);
+ sigaddset(&sigmask, SIGINT);
+ sigaddset(&sigmask, SIGALRM);
+ sigprocmask(SIG_BLOCK, &sigmask, &oldsigmask);
+ while (! (alarm_rang || return_requested) ) {
+ sigsuspend(&oldsigmask);
+ }
+ sigprocmask(SIG_UNBLOCK, &sigmask, NULL);
+
+ havelast = 1;
+ }
+
+ exit(0);
+}
+
+/*
+ * Force a header to be prepended to the next output.
+ */
+void
+needhdr(int signo)
+{
+
+ headercount = 1;
+}
+
+/*
+ * When the terminal is resized, force an update of the maximum number of rows
+ * printed between each header repetition. Then force a new header to be
+ * prepended to the next output.
+ */
+void
+needresize(int signo)
+{
+
+ wresized = 1;
+ headercount = 1;
+}
+
+/*
+ * Record the alarm so the main loop can break its sleep
+ */
+void
+alarm_clock(int signo)
+{
+ alarm_rang = 1;
+}
+
+/*
+ * Request that the main loop exit soon
+ */
+void
+needreturn(int signo)
+{
+ return_requested = 1;
+}
+
+/*
+ * Update the global `wrows' count of terminal rows.
+ */
+void
+doresize(void)
+{
+ int status;
+ struct winsize w;
+
+ for (;;) {
+ status = ioctl(fileno(stdout), TIOCGWINSZ, &w);
+ if (status == -1 && errno == EINTR)
+ continue;
+ else if (status == -1)
+ err(1, "ioctl");
+ if (w.ws_row > 3)
+ wrows = w.ws_row - 3;
+ else
+ wrows = IOSTAT_DEFAULT_ROWS;
+ break;
+ }
+
+ /*
+ * Inhibit doresize() calls until we are rescheduled by SIGWINCH.
+ */
+ wresized = 0;
+}
+
+static void
+phdr(void)
+{
+ int i, printed;
+ char devbuf[256];
+
+ /*
+ * If xflag is set, we need a per-loop header, not a page header, so
+ * just return. We'll print the header in devstats().
+ */
+ if (xflag > 0)
+ return;
+
+ if (Tflag > 0)
+ (void)printf(" tty");
+ for (i = 0, printed=0;(i < num_devices) && (printed < maxshowdevs);i++){
+ int di;
+ if ((dev_select[i].selected != 0)
+ && (dev_select[i].selected <= maxshowdevs)) {
+ di = dev_select[i].position;
+ snprintf(devbuf, sizeof(devbuf), "%s%d",
+ cur.dinfo->devices[di].device_name,
+ cur.dinfo->devices[di].unit_number);
+ if (oflag > 0)
+ (void)printf("%13.6s ", devbuf);
+ else
+ printf("%16.6s ", devbuf);
+ printed++;
+ }
+ }
+ if (Cflag > 0)
+ (void)printf(" cpu\n");
+ else
+ (void)printf("\n");
+
+ if (Tflag > 0)
+ (void)printf(" tin tout");
+
+ for (i=0, printed = 0;(i < num_devices) && (printed < maxshowdevs);i++){
+ if ((dev_select[i].selected != 0)
+ && (dev_select[i].selected <= maxshowdevs)) {
+ if (oflag > 0) {
+ if (Iflag == 0)
+ (void)printf(" sps tps msps ");
+ else
+ (void)printf(" blk xfr msps ");
+ } else {
+ if (Iflag == 0)
+ printf(" KB/t tps MB/s ");
+ else
+ printf(" KB/t xfrs MB ");
+ }
+ printed++;
+ }
+ }
+ if (Cflag > 0)
+ (void)printf(" us ni sy in id\n");
+ else
+ printf("\n");
+
+}
+
+static void
+devstats(int perf_select, long double etime, int havelast)
+{
+ int dn;
+ long double transfers_per_second, transfers_per_second_read;
+ long double transfers_per_second_write;
+ long double kb_per_transfer, mb_per_second, mb_per_second_read;
+ long double mb_per_second_write;
+ u_int64_t total_bytes, total_transfers, total_blocks;
+ u_int64_t total_bytes_read, total_transfers_read;
+ u_int64_t total_bytes_write, total_transfers_write;
+ long double busy_pct, busy_time;
+ u_int64_t queue_len;
+ long double total_mb, blocks_per_second, total_duration;
+ long double ms_per_other, ms_per_read, ms_per_write, ms_per_transaction;
+ int firstline = 1;
+ char *devname;
+
+ if (xflag > 0) {
+ printf(" extended device statistics ");
+ if (Tflag > 0)
+ printf(" tty ");
+ if (Cflag > 0)
+ printf(" cpu ");
+ printf("\n");
+ if (Iflag == 0) {
+ printf("device r/s w/s kr/s kw/s "
+ " ms/r ms/w ms/o ms/t qlen %%b ");
+ } else {
+ printf("device r/i w/i kr/i"
+ " kw/i qlen tsvc_t/i sb/i ");
+ }
+ if (Tflag > 0)
+ printf("tin tout ");
+ if (Cflag > 0)
+ printf("us ni sy in id ");
+ printf("\n");
+ }
+
+ for (dn = 0; dn < num_devices; dn++) {
+ int di;
+
+ if (((perf_select == 0) && (dev_select[dn].selected == 0))
+ || (dev_select[dn].selected > maxshowdevs))
+ continue;
+
+ di = dev_select[dn].position;
+
+ if (devstat_compute_statistics(&cur.dinfo->devices[di],
+ havelast ? &last.dinfo->devices[di] : NULL, etime,
+ DSM_TOTAL_BYTES, &total_bytes,
+ DSM_TOTAL_BYTES_READ, &total_bytes_read,
+ DSM_TOTAL_BYTES_WRITE, &total_bytes_write,
+ DSM_TOTAL_TRANSFERS, &total_transfers,
+ DSM_TOTAL_TRANSFERS_READ, &total_transfers_read,
+ DSM_TOTAL_TRANSFERS_WRITE, &total_transfers_write,
+ DSM_TOTAL_BLOCKS, &total_blocks,
+ DSM_KB_PER_TRANSFER, &kb_per_transfer,
+ DSM_TRANSFERS_PER_SECOND, &transfers_per_second,
+ DSM_TRANSFERS_PER_SECOND_READ, &transfers_per_second_read,
+ DSM_TRANSFERS_PER_SECOND_WRITE, &transfers_per_second_write,
+ DSM_MB_PER_SECOND, &mb_per_second,
+ DSM_MB_PER_SECOND_READ, &mb_per_second_read,
+ DSM_MB_PER_SECOND_WRITE, &mb_per_second_write,
+ DSM_BLOCKS_PER_SECOND, &blocks_per_second,
+ DSM_MS_PER_TRANSACTION, &ms_per_transaction,
+ DSM_MS_PER_TRANSACTION_READ, &ms_per_read,
+ DSM_MS_PER_TRANSACTION_WRITE, &ms_per_write,
+ DSM_MS_PER_TRANSACTION_OTHER, &ms_per_other,
+ DSM_BUSY_PCT, &busy_pct,
+ DSM_QUEUE_LENGTH, &queue_len,
+ DSM_TOTAL_DURATION, &total_duration,
+ DSM_TOTAL_BUSY_TIME, &busy_time,
+ DSM_NONE) != 0)
+ errx(1, "%s", devstat_errbuf);
+
+ if (perf_select != 0) {
+ dev_select[dn].bytes = total_bytes;
+ if ((dev_select[dn].selected == 0)
+ || (dev_select[dn].selected > maxshowdevs))
+ continue;
+ }
+
+ if (Kflag > 0 || xflag > 0) {
+ int block_size = cur.dinfo->devices[di].block_size;
+ total_blocks = total_blocks * (block_size ?
+ block_size : 512) / 1024;
+ }
+
+ if (xflag > 0) {
+ if (asprintf(&devname, "%s%d",
+ cur.dinfo->devices[di].device_name,
+ cur.dinfo->devices[di].unit_number) == -1)
+ err(1, "asprintf");
+ /*
+ * If zflag is set, skip any devices with zero I/O.
+ */
+ if (zflag == 0 || transfers_per_second_read > 0.05 ||
+ transfers_per_second_write > 0.05 ||
+ mb_per_second_read > ((long double).0005)/1024 ||
+ mb_per_second_write > ((long double).0005)/1024 ||
+ busy_pct > 0.5) {
+ if (Iflag == 0)
+ printf("%-8.8s %5d %5d %8.1Lf "
+ "%8.1Lf %5d %5d %5d %5d "
+ "%4" PRIu64 " %3.0Lf ",
+ devname,
+ (int)transfers_per_second_read,
+ (int)transfers_per_second_write,
+ mb_per_second_read * 1024,
+ mb_per_second_write * 1024,
+ (int)ms_per_read, (int)ms_per_write,
+ (int)ms_per_other,
+ (int)ms_per_transaction,
+ queue_len, busy_pct);
+ else
+ printf("%-8.8s %11.1Lf %11.1Lf "
+ "%12.1Lf %12.1Lf %4" PRIu64
+ " %10.1Lf %9.1Lf ",
+ devname,
+ (long double)total_transfers_read,
+ (long double)total_transfers_write,
+ (long double)
+ total_bytes_read / 1024,
+ (long double)
+ total_bytes_write / 1024,
+ queue_len,
+ total_duration, busy_time);
+ if (firstline) {
+ /*
+ * If this is the first device
+ * we're printing, also print
+ * CPU or TTY stats if requested.
+ */
+ firstline = 0;
+ if (Tflag > 0)
+ printf("%4.0Lf%5.0Lf",
+ cur.tk_nin / etime,
+ cur.tk_nout / etime);
+ if (Cflag > 0)
+ cpustats();
+ }
+ printf("\n");
+ }
+ free(devname);
+ } else if (oflag > 0) {
+ int msdig = (ms_per_transaction < 100.0) ? 1 : 0;
+
+ if (Iflag == 0)
+ printf("%4.0Lf%4.0Lf%5.*Lf ",
+ blocks_per_second,
+ transfers_per_second,
+ msdig,
+ ms_per_transaction);
+ else
+ printf("%4.1" PRIu64 "%4.1" PRIu64 "%5.*Lf ",
+ total_blocks,
+ total_transfers,
+ msdig,
+ ms_per_transaction);
+ } else {
+ if (Iflag == 0)
+ printf(" %5.2Lf %3.0Lf %5.2Lf ",
+ kb_per_transfer,
+ transfers_per_second,
+ mb_per_second);
+ else {
+ total_mb = total_bytes;
+ total_mb /= 1024 * 1024;
+
+ printf(" %5.2Lf %3.1" PRIu64 " %5.2Lf ",
+ kb_per_transfer,
+ total_transfers,
+ total_mb);
+ }
+ }
+ }
+ if (xflag > 0 && zflag > 0 && firstline == 1 &&
+ (Tflag > 0 || Cflag > 0)) {
+ /*
+ * If zflag is set and we did not print any device
+ * lines I/O because they were all zero,
+ * print TTY/CPU stats.
+ */
+ printf("%52s","");
+ if (Tflag > 0)
+ printf("%4.0Lf %5.0Lf", cur.tk_nin / etime,
+ cur.tk_nout / etime);
+ if (Cflag > 0)
+ cpustats();
+ printf("\n");
+ }
+}
+
+static void
+cpustats(void)
+{
+ int state;
+ double time;
+
+ time = 0.0;
+
+ for (state = 0; state < CPUSTATES; ++state)
+ time += cur.cp_time[state];
+ for (state = 0; state < CPUSTATES; ++state)
+ printf(" %2.0f",
+ rint(100. * cur.cp_time[state] / (time ? time : 1)));
+}
+
+static int
+readvar(kvm_t *kd, const char *name, int nlid, void *ptr, size_t len)
+{
+ if (kd != NULL) {
+ ssize_t nbytes;
+
+ nbytes = kvm_read(kd, namelist[nlid].n_value, ptr, len);
+
+ if (nbytes < 0) {
+ warnx("kvm_read(%s): %s", namelist[nlid].n_name,
+ kvm_geterr(kd));
+ return (1);
+ }
+ if (nbytes != len) {
+ warnx("kvm_read(%s): expected %zu bytes, got %zd bytes",
+ namelist[nlid].n_name, len, nbytes);
+ return (1);
+ }
+ } else {
+ size_t nlen = len;
+
+ if (sysctlbyname(name, ptr, &nlen, NULL, 0) == -1) {
+ warn("sysctl(%s...) failed", name);
+ return (1);
+ }
+ if (nlen != len) {
+ warnx("sysctl(%s...): expected %lu, got %lu", name,
+ (unsigned long)len, (unsigned long)nlen);
+ return (1);
+ }
+ }
+ return (0);
+}
diff --git a/usr.sbin/iovctl/Makefile b/usr.sbin/iovctl/Makefile
new file mode 100644
index 0000000..d5b2780
--- /dev/null
+++ b/usr.sbin/iovctl/Makefile
@@ -0,0 +1,17 @@
+# $FreeBSD$
+
+PROG= iovctl
+SRCS= iovctl.c parse.c validate.c
+LIBADD= nv ucl m
+
+CFLAGS+=-I${.CURDIR}/../../contrib/libucl/include
+
+WARNS?=6
+
+MAN= \
+ iovctl.8 \
+ iovctl.conf.5 \
+
+.include <bsd.own.mk>
+.include <bsd.prog.mk>
+
diff --git a/usr.sbin/iovctl/Makefile.depend b/usr.sbin/iovctl/Makefile.depend
new file mode 100644
index 0000000..d883529
--- /dev/null
+++ b/usr.sbin/iovctl/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libnv \
+ lib/libucl \
+ lib/msun \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/iovctl/iovctl.8 b/usr.sbin/iovctl/iovctl.8
new file mode 100644
index 0000000..5ba1706
--- /dev/null
+++ b/usr.sbin/iovctl/iovctl.8
@@ -0,0 +1,123 @@
+.\"
+.\" Copyright (c) 2014 Sandvine 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$
+.\"
+.Dd July 8, 2015
+.Dt IOVCTL 8
+.Os
+.Sh NAME
+.Nm iovctl
+.Nd "PCI SR-IOV configuration utility"
+.Sh SYNOPSIS
+.Nm
+.Fl C
+.Op Fl f Ar config-file
+.Op Fl n
+.Nm
+.Fl D
+.Op Fl f Ar config-file | Fl d Ar device
+.Op Fl n
+.Nm
+.Fl S
+.Op Fl f Ar config-file | Fl d Ar device
+.Sh DESCRIPTION
+The
+.Nm
+utility creates or destroys PCI Single-Root I/O Virtualization
+.Pq SR-IOV
+Virtual Functions
+.Pq VFs .
+When invoked with the
+.Fl C
+flag,
+.Nm
+creates VFs as children of the Physical Function
+.Pq PF
+configured in the specified configuration file.
+When invoked with the
+.Fl D
+flag,
+.Nm
+destroys all VFs that are children of the specified device.
+Available PF devices can be seen in
+.Pa /dev/iov/ .
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl C
+Enable SR-IOV on the specified PF device and create VF children.
+This operation will fail if the PF already has VF children.
+This option must be used in conjunction with the
+.Fl f
+option.
+.It Fl d Ar device
+Specify the PF device to use for the given operation.
+.Ar device
+may either be the name of a PF device, or a full path name to a node in
+.Pa /dev/iov/ .
+This option may not be used with the
+.Fl C
+option.
+.It Fl D
+Delete all VF children of the specified PF device.
+This operation will fail if SR-IOV is not currently enabled on the specified
+device.
+.It Fl f Ar config-file
+Specify the pathname of the configuration file.
+For the
+.Fl C
+option, this file will be used to specify all configuration values.
+For the
+.Fl D
+and
+.Fl S
+options, this file will only be used to specify the name of the PF device.
+.Pp
+See
+.Xr iovctl.conf
+for a description of the config file format and documentation of the
+configuration parameters that apply to all PF drivers.
+See the PF driver manual page for configuration parameters specific to
+particular hardware.
+.It Fl n
+Perform a dry-run.
+Perform all validation of the specified action and print what would be done,
+but do not perform the actual creation or destruction of VFs.
+This option may not be used with the
+.Fl S
+flag.
+.It Fl S
+Read the configuration schema from the specified device and print its contents
+to stdout.
+This action may be used to discover the configuration parameters supported on
+a given PF device.
+.El
+.Sh SEE ALSO
+.Xr iovctl.conf 5 ,
+.Xr rc.conf 5
+.Sh AUTHORS
+This manual page was written by
+.An Ryan Stone Aq Mt rstone@FreeBSD.org .
diff --git a/usr.sbin/iovctl/iovctl.c b/usr.sbin/iovctl/iovctl.c
new file mode 100644
index 0000000..6c69f52
--- /dev/null
+++ b/usr.sbin/iovctl/iovctl.c
@@ -0,0 +1,403 @@
+/*-
+ * Copyright (c) 2013-2015 Sandvine Inc. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/iov.h>
+#include <sys/dnv.h>
+#include <sys/nv.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "iovctl.h"
+
+static void config_action(const char *filename, int dryrun);
+static void delete_action(const char *device, int dryrun);
+static void print_schema(const char *device);
+
+/*
+ * Fetch the config schema from the kernel via ioctl. This function has to
+ * call the ioctl twice: the first returns the amount of memory that we need
+ * to allocate for the schema, and the second actually fetches the schema.
+ */
+static nvlist_t *
+get_schema(int fd)
+{
+ struct pci_iov_schema arg;
+ nvlist_t *schema;
+ int error;
+
+ /* Do the ioctl() once to fetch the size of the schema. */
+ arg.schema = NULL;
+ arg.len = 0;
+ arg.error = 0;
+ error = ioctl(fd, IOV_GET_SCHEMA, &arg);
+ if (error != 0)
+ err(1, "Could not fetch size of config schema");
+
+ arg.schema = malloc(arg.len);
+ if (arg.schema == NULL)
+ err(1, "Could not allocate %zu bytes for schema",
+ arg.len);
+
+ /* Now do the ioctl() for real to get the schema. */
+ error = ioctl(fd, IOV_GET_SCHEMA, &arg);
+ if (error != 0 || arg.error != 0) {
+ if (arg.error != 0)
+ errno = arg.error;
+ err(1, "Could not fetch config schema");
+ }
+
+ schema = nvlist_unpack(arg.schema, arg.len, NV_FLAG_IGNORE_CASE);
+ if (schema == NULL)
+ err(1, "Could not unpack schema");
+
+ free(arg.schema);
+ return (schema);
+}
+
+/*
+ * Call the ioctl that activates SR-IOV and creates the VFs.
+ */
+static void
+config_iov(int fd, const char *dev_name, const nvlist_t *config, int dryrun)
+{
+ struct pci_iov_arg arg;
+ int error;
+
+ arg.config = nvlist_pack(config, &arg.len);
+ if (arg.config == NULL)
+ err(1, "Could not pack configuration");
+
+ if (dryrun) {
+ printf("Would enable SR-IOV on device '%s'.\n", dev_name);
+ printf(
+ "The following configuration parameters would be used:\n");
+ nvlist_fdump(config, stdout);
+ printf(
+ "The configuration parameters consume %zu bytes when packed.\n",
+ arg.len);
+ } else {
+ error = ioctl(fd, IOV_CONFIG, &arg);
+ if (error != 0)
+ err(1, "Failed to configure SR-IOV");
+ }
+
+ free(arg.config);
+}
+
+static int
+open_device(const char *dev_name)
+{
+ char *dev;
+ int fd;
+ size_t copied, size;
+ long path_max;
+
+ path_max = pathconf("/dev", _PC_PATH_MAX);
+ if (path_max < 0)
+ err(1, "Could not get maximum path length");
+
+ size = path_max;
+ dev = malloc(size);
+ if (dev == NULL)
+ err(1, "Could not allocate memory for device path");
+
+ if (dev_name[0] == '/')
+ copied = strlcpy(dev, dev_name, size);
+ else
+ copied = snprintf(dev, size, "/dev/iov/%s", dev_name);
+
+ /* >= to account for null terminator. */
+ if (copied >= size)
+ errx(1, "Provided file name too long");
+
+ fd = open(dev, O_RDWR);
+ if (fd < 0)
+ err(1, "Could not open device '%s'", dev);
+
+ free(dev);
+ return (fd);
+}
+
+static void
+usage(void)
+{
+
+ warnx("Usage: iovctl -C -f <config file> [-n]");
+ warnx(" iovctl -D [-d <PF device> | -f <config file>] [-n]");
+ warnx(" iovctl -S [-d <PF device> | -f <config file>]");
+ exit(1);
+
+}
+
+enum main_action {
+ NONE,
+ CONFIG,
+ DELETE,
+ PRINT_SCHEMA,
+};
+
+int
+main(int argc, char **argv)
+{
+ char *device;
+ const char *filename;
+ int ch, dryrun;
+ enum main_action action;
+
+ device = NULL;
+ filename = NULL;
+ dryrun = 0;
+ action = NONE;
+
+ while ((ch = getopt(argc, argv, "Cd:Df:nS")) != -1) {
+ switch (ch) {
+ case 'C':
+ if (action != NONE) {
+ warnx(
+ "Only one of -C, -D or -S may be specified");
+ usage();
+ }
+ action = CONFIG;
+ break;
+ case 'd':
+ device = strdup(optarg);
+ break;
+ case 'D':
+ if (action != NONE) {
+ warnx(
+ "Only one of -C, -D or -S may be specified");
+ usage();
+ }
+ action = DELETE;
+ break;
+ case 'f':
+ filename = optarg;
+ break;
+ case 'n':
+ dryrun = 1;
+ break;
+ case 'S':
+ if (action != NONE) {
+ warnx(
+ "Only one of -C, -D or -S may be specified");
+ usage();
+ }
+ action = PRINT_SCHEMA;
+ break;
+ case '?':
+ warnx("Unrecognized argument '-%c'\n", optopt);
+ usage();
+ break;
+ }
+ }
+
+ if (device != NULL && filename != NULL) {
+ warnx("Only one of the -d and -f flags may be specified");
+ usage();
+ }
+
+ if (device == NULL && filename == NULL) {
+ warnx("Either the -d or -f flag must be specified");
+ usage();
+ }
+
+ switch (action) {
+ case CONFIG:
+ if (filename == NULL) {
+ warnx("-d flag cannot be used with the -C flag");
+ usage();
+ }
+ config_action(filename, dryrun);
+ break;
+ case DELETE:
+ if (device == NULL)
+ device = find_device(filename);
+ delete_action(device, dryrun);
+ free(device);
+ break;
+ case PRINT_SCHEMA:
+ if (dryrun) {
+ warnx("-n flag cannot be used with the -S flag");
+ usage();
+ }
+ if (device == NULL)
+ device = find_device(filename);
+ print_schema(device);
+ free(device);
+ break;
+ default:
+ usage();
+ break;
+ }
+
+ exit(0);
+}
+
+static void
+config_action(const char *filename, int dryrun)
+{
+ char *dev;
+ nvlist_t *schema, *config;
+ int fd;
+
+ dev = find_device(filename);
+ fd = open(dev, O_RDWR);
+ if (fd < 0)
+ err(1, "Could not open device '%s'", dev);
+
+ schema = get_schema(fd);
+ config = parse_config_file(filename, schema);
+ if (config == NULL)
+ errx(1, "Could not parse config");
+
+ config_iov(fd, dev, config, dryrun);
+
+ nvlist_destroy(config);
+ nvlist_destroy(schema);
+ free(dev);
+ close(fd);
+}
+
+static void
+delete_action(const char *dev_name, int dryrun)
+{
+ int fd, error;
+
+ fd = open_device(dev_name);
+
+ if (dryrun)
+ printf("Would attempt to delete all VF children of '%s'\n",
+ dev_name);
+ else {
+ error = ioctl(fd, IOV_DELETE);
+ if (error != 0)
+ err(1, "Failed to delete VFs");
+ }
+
+ close(fd);
+}
+
+static void
+print_default_value(const nvlist_t *parameter, const char *type)
+{
+ const uint8_t *mac;
+ size_t size;
+
+ if (strcasecmp(type, "bool") == 0)
+ printf(" (default = %s)",
+ nvlist_get_bool(parameter, DEFAULT_SCHEMA_NAME) ? "true" :
+ "false");
+ else if (strcasecmp(type, "string") == 0)
+ printf(" (default = %s)",
+ nvlist_get_string(parameter, DEFAULT_SCHEMA_NAME));
+ else if (strcasecmp(type, "uint8_t") == 0)
+ printf(" (default = %ju)",
+ (uintmax_t)nvlist_get_number(parameter,
+ DEFAULT_SCHEMA_NAME));
+ else if (strcasecmp(type, "uint16_t") == 0)
+ printf(" (default = %ju)",
+ (uintmax_t)nvlist_get_number(parameter,
+ DEFAULT_SCHEMA_NAME));
+ else if (strcasecmp(type, "uint32_t") == 0)
+ printf(" (default = %ju)",
+ (uintmax_t)nvlist_get_number(parameter,
+ DEFAULT_SCHEMA_NAME));
+ else if (strcasecmp(type, "uint64_t") == 0)
+ printf(" (default = %ju)",
+ (uintmax_t)nvlist_get_number(parameter,
+ DEFAULT_SCHEMA_NAME));
+ else if (strcasecmp(type, "unicast-mac") == 0) {
+ mac = nvlist_get_binary(parameter, DEFAULT_SCHEMA_NAME, &size);
+ printf(" (default = %02x:%02x:%02x:%02x:%02x:%02x)", mac[0],
+ mac[1], mac[2], mac[3], mac[4], mac[5]);
+ } else
+ errx(1, "Unexpected type in schema: '%s'", type);
+}
+
+static void
+print_subsystem_schema(const nvlist_t * subsystem_schema)
+{
+ const char *name, *type;
+ const nvlist_t *parameter;
+ void *it;
+ int nvtype;
+
+ it = NULL;
+ while ((name = nvlist_next(subsystem_schema, &nvtype, &it)) != NULL) {
+ parameter = nvlist_get_nvlist(subsystem_schema, name);
+ type = nvlist_get_string(parameter, TYPE_SCHEMA_NAME);
+
+ printf("\t%s : %s", name, type);
+ if (dnvlist_get_bool(parameter, REQUIRED_SCHEMA_NAME, false))
+ printf(" (required)");
+ else if (nvlist_exists(parameter, DEFAULT_SCHEMA_NAME))
+ print_default_value(parameter, type);
+ else
+ printf(" (optional)");
+ printf("\n");
+ }
+}
+
+static void
+print_schema(const char *dev_name)
+{
+ nvlist_t *schema;
+ const nvlist_t *iov_schema, *driver_schema, *pf_schema, *vf_schema;
+ int fd;
+
+ fd = open_device(dev_name);
+ schema = get_schema(fd);
+
+ pf_schema = nvlist_get_nvlist(schema, PF_CONFIG_NAME);
+ iov_schema = nvlist_get_nvlist(pf_schema, IOV_CONFIG_NAME);
+ driver_schema = nvlist_get_nvlist(pf_schema, DRIVER_CONFIG_NAME);
+ printf(
+"The following configuration parameters may be configured on the PF:\n");
+ print_subsystem_schema(iov_schema);
+ print_subsystem_schema(driver_schema);
+
+ vf_schema = nvlist_get_nvlist(schema, VF_SCHEMA_NAME);
+ iov_schema = nvlist_get_nvlist(vf_schema, IOV_CONFIG_NAME);
+ driver_schema = nvlist_get_nvlist(vf_schema, DRIVER_CONFIG_NAME);
+ printf(
+"\nThe following configuration parameters may be configured on a VF:\n");
+ print_subsystem_schema(iov_schema);
+ print_subsystem_schema(driver_schema);
+
+ nvlist_destroy(schema);
+ close(fd);
+}
diff --git a/usr.sbin/iovctl/iovctl.conf.5 b/usr.sbin/iovctl/iovctl.conf.5
new file mode 100644
index 0000000..3617b1c
--- /dev/null
+++ b/usr.sbin/iovctl/iovctl.conf.5
@@ -0,0 +1,171 @@
+.\"
+.\" Copyright (c) 2014 Sandvine 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$
+.\"
+.Dd July 8, 2015
+.Dt IOVCTL.CONF 5
+.Os
+.Sh NAME
+.Nm iovctl.conf
+.Nd IOVCTL configuration file
+.Sh DESCRIPTION
+The
+.Nm
+file is the configuration file for the
+.Xr iovctl 8
+program.
+This file specifies configuration parameters for a single Physical Function
+.Pq PF
+device.
+To configure SR-IOV on multiple PF devices, use one configuration file for each
+PF.
+The locations of all
+.Xr iovctl 9
+configuration files are specified in
+.Xr rc.conf 5 .
+.Pp
+The
+.Nm
+file uses UCL format.
+UCL syntax is documented at the official UCL website:
+http://github.com/vstakhov/libucl.
+.Pp
+There are three types of sections in the
+.Nm
+file.
+A section is a key at the top level of the file with a list as its value.
+The list may contain the keys specified in the
+.Sx OPTIONS
+section of this manual page.
+Individual PF driver implementations may specify additional device-specific
+configuration keys that they will accept.
+The order in which sections appear in
+.Nm
+is ignored.
+No two sections may have the same key.
+For example, two sections for VF-1 must not be defined.
+.Pp
+The first section type is the PF section.
+This section always has the key "PF"; therefore, only one such section may be
+defined.
+This section defines configuration parameters that apply to the PF as a whole.
+.Pp
+The second section type is the VF section.
+This section has the key "VF-" followed by a VF index.
+VF indices start at 0 and always increment by 1.
+Valid VF indices are in the range of 0 to
+.Pq num_vfs - 1 .
+The VF index must be given as a decimal integer with no leading zeros.
+This section defines configuration parameters that apply to a single VF.
+.Pp
+The third section type is the default section.
+This section always has the key "DEFAULT"; therefore, only one such section may
+be specified.
+This section defines default configuration parameters that apply to all VFs.
+All configuration keys that are valid to be applied to a VF are valid in this
+section.
+An individual VF section may override a default specified in this section by
+providing a different value for the configuration parameter.
+Note that the default section applies to ALL VFs.
+The default section must appear before any VF sections.
+The default section may appear before or after the PF section.
+.Pp
+The following option types are supported:
+.Bl -tag -width indent
+.It boolean
+Accepts a boolean value of true or false.
+.It mac-addr
+Accepts a unicast MAC address specified as a string of the form
+xx:xx:xx:xx:xx:xx, where xx is one or two hexadecimal digits.
+.It string
+Accepts any string value.
+.It uint8_t
+Accepts any integer in the range 0 to 255, inclusive.
+.It uint16_t
+Accepts any integer in the range 0 to 65535, inclusive.
+.It uint32_t
+Accepts any integer in the range 0 to
+.Pq 2**32 - 1 ,
+inclusive.
+.It uint64_t
+Accepts any integer in the range 0 to
+.Pq 2**64 - 1 ,
+inclusive.
+.El
+.Sh OPTIONS
+The following parameters are accepted by all PF drivers:
+.Bl -tag -width indent
+.It device Pq string
+This parameter specifies the name of the PF device.
+This parameter is required to be specified.
+.It num_vfs Pq uint16_t
+This parameter specifies the number of VF children to create.
+This parameter may not be zero.
+The maximum value of this parameter is device-specific.
+.El
+.Pp
+The following parameters are accepted by all VFs:
+.Bl -tag -width indent
+.It passthrough Pq boolean
+This parameter controls whether the VF is reserved for the use of the
+.Xr bhyve 8
+hypervisor as a PCI passthrough device.
+If this parameter is set to true, then the VF will be reserved as a PCI
+passthrough device and it will not be accessible from the host OS.
+The default value of this parameter is false.
+.El
+.Pp
+See the PF driver manual page for configuration parameters specific to
+particular hardware.
+.Sh EXAMPLES
+This sample file will create 3 VFs as children of the ix0 device.
+VF-1 and VF-2 are set as
+.Xr bhyve 8
+passthrough devices through the use of the default section.
+VF-0 is not configured as a passthrough device as it explicitly overrides the
+default.
+VF-0 also sets a device-specific parameter named mac-addr.
+.Bd -literal -offset ident
+PF {
+ device : "ix0";
+ num_vfs : 3;
+}
+
+DEFAULT {
+ passthrough : true;
+}
+
+VF-0 {
+ mac-addr : "02:56:48:7e:d9:f7";
+ passthrough : false;
+}
+.Ed
+.Sh SEE ALSO
+.Xr rc.conf 5 ,
+.Xr iovctl 8
+.Sh AUTHORS
+This manual page was written by
+.An Ryan Stone Aq Mt rstone@FreeBSD.org .
diff --git a/usr.sbin/iovctl/iovctl.h b/usr.sbin/iovctl/iovctl.h
new file mode 100644
index 0000000..21d7e5c
--- /dev/null
+++ b/usr.sbin/iovctl/iovctl.h
@@ -0,0 +1,37 @@
+/*-
+ * Copyright (c) 2013-2015 Sandvine Inc. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 IOVCTL_H
+#define IOVCTL_H
+
+char * find_device(const char *);
+nvlist_t * parse_config_file(const char *, const nvlist_t *);
+void validate_config(nvlist_t *, const nvlist_t *, const regex_t *);
+
+#endif
+
diff --git a/usr.sbin/iovctl/parse.c b/usr.sbin/iovctl/parse.c
new file mode 100644
index 0000000..b2ec395
--- /dev/null
+++ b/usr.sbin/iovctl/parse.c
@@ -0,0 +1,416 @@
+/*-
+ * Copyright (c) 2014-2015 Sandvine Inc. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/iov.h>
+#include <sys/nv.h>
+#include <net/ethernet.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <regex.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ucl.h>
+#include <unistd.h>
+
+#include "iovctl.h"
+
+static void
+report_config_error(const char *key, const ucl_object_t *obj, const char *type)
+{
+
+ errx(1, "Value '%s' of key '%s' is not of type %s",
+ ucl_object_tostring(obj), key, type);
+}
+
+/*
+ * Verifies that the value specified in the config file is a boolean value, and
+ * then adds the value to the configuration.
+ */
+static void
+add_bool_config(const char *key, const ucl_object_t *obj, nvlist_t *config)
+{
+ bool val;
+
+ if (!ucl_object_toboolean_safe(obj, &val))
+ report_config_error(key, obj, "bool");
+
+ nvlist_add_bool(config, key, val);
+}
+
+/*
+ * Verifies that the value specified in the config file is a string, and then
+ * adds the value to the configuration.
+ */
+static void
+add_string_config(const char *key, const ucl_object_t *obj, nvlist_t *config)
+{
+ const char *val;
+
+ if (!ucl_object_tostring_safe(obj, &val))
+ report_config_error(key, obj, "string");
+
+ nvlist_add_string(config, key, val);
+}
+
+/*
+ * Verifies that the value specified in the config file is a integer value
+ * within the specified range, and then adds the value to the configuration.
+ */
+static void
+add_uint_config(const char *key, const ucl_object_t *obj, nvlist_t *config,
+ const char *type, uint64_t max)
+{
+ int64_t val;
+ uint64_t uval;
+
+ /* I must use a signed type here as libucl doesn't provide unsigned. */
+ if (!ucl_object_toint_safe(obj, &val))
+ report_config_error(key, obj, type);
+
+ if (val < 0)
+ report_config_error(key, obj, type);
+
+ uval = val;
+ if (uval > max)
+ report_config_error(key, obj, type);
+
+ nvlist_add_number(config, key, uval);
+}
+
+/*
+ * Verifies that the value specified in the config file is a unicast MAC
+ * address, and then adds the value to the configuration.
+ */
+static void
+add_unicast_mac_config(const char *key, const ucl_object_t *obj, nvlist_t *config)
+{
+ uint8_t mac[ETHER_ADDR_LEN];
+ const char *val, *token;
+ char *parse, *orig_parse, *tokpos, *endpos;
+ size_t len;
+ u_long value;
+ int i;
+
+ if (!ucl_object_tostring_safe(obj, &val))
+ report_config_error(key, obj, "unicast-mac");
+
+ parse = strdup(val);
+ orig_parse = parse;
+
+ i = 0;
+ while ((token = strtok_r(parse, ":", &tokpos)) != NULL) {
+ parse = NULL;
+
+ len = strlen(token);
+ if (len < 1 || len > 2)
+ report_config_error(key, obj, "unicast-mac");
+
+ value = strtoul(token, &endpos, 16);
+
+ if (*endpos != '\0')
+ report_config_error(key, obj, "unicast-mac");
+
+ if (value > UINT8_MAX)
+ report_config_error(key, obj, "unicast-mac");
+
+ if (i >= ETHER_ADDR_LEN)
+ report_config_error(key, obj, "unicast-mac");
+
+ mac[i] = value;
+ i++;
+ }
+
+ free(orig_parse);
+
+ if (i != ETHER_ADDR_LEN)
+ report_config_error(key, obj, "unicast-mac");
+
+ if (ETHER_IS_MULTICAST(mac))
+ errx(1, "Value '%s' of key '%s' is a multicast address",
+ ucl_object_tostring(obj), key);
+
+ nvlist_add_binary(config, key, mac, ETHER_ADDR_LEN);
+}
+
+/*
+ * Validates that the given configuation value has the right type as specified
+ * in the schema, and then adds the value to the configuation node.
+ */
+static void
+add_config(const char *key, const ucl_object_t *obj, nvlist_t *config,
+ const nvlist_t *schema)
+{
+ const char *type;
+
+ type = nvlist_get_string(schema, TYPE_SCHEMA_NAME);
+
+ if (strcasecmp(type, "bool") == 0)
+ add_bool_config(key, obj, config);
+ else if (strcasecmp(type, "string") == 0)
+ add_string_config(key, obj, config);
+ else if (strcasecmp(type, "uint8_t") == 0)
+ add_uint_config(key, obj, config, type, UINT8_MAX);
+ else if (strcasecmp(type, "uint16_t") == 0)
+ add_uint_config(key, obj, config, type, UINT16_MAX);
+ else if (strcasecmp(type, "uint32_t") == 0)
+ add_uint_config(key, obj, config, type, UINT32_MAX);
+ else if (strcasecmp(type, "uint64_t") == 0)
+ add_uint_config(key, obj, config, type, UINT64_MAX);
+ else if (strcasecmp(type, "unicast-mac") == 0)
+ add_unicast_mac_config(key, obj, config);
+ else
+ errx(1, "Unexpected type '%s' in schema", type);
+}
+
+/*
+ * Parses all values specified in a device section in the configuration file,
+ * validates that the key/value pair is valid in the schema, and then adds
+ * the key/value pair to the correct subsystem in the config.
+ */
+static void
+parse_device_config(const ucl_object_t *top, nvlist_t *config,
+ const char *subsystem, const nvlist_t *schema)
+{
+ ucl_object_iter_t it;
+ const ucl_object_t *obj;
+ nvlist_t *subsystem_config, *driver_config, *iov_config;
+ const nvlist_t *driver_schema, *iov_schema;
+ const char *key;
+
+ if (nvlist_exists(config, subsystem))
+ errx(1, "Multiple definitions of '%s' in config file",
+ subsystem);
+
+ driver_schema = nvlist_get_nvlist(schema, DRIVER_CONFIG_NAME);
+ iov_schema = nvlist_get_nvlist(schema, IOV_CONFIG_NAME);
+
+ driver_config = nvlist_create(NV_FLAG_IGNORE_CASE);
+ if (driver_config == NULL)
+ err(1, "Could not allocate config nvlist");
+
+ iov_config = nvlist_create(NV_FLAG_IGNORE_CASE);
+ if (iov_config == NULL)
+ err(1, "Could not allocate config nvlist");
+
+ subsystem_config = nvlist_create(NV_FLAG_IGNORE_CASE);
+ if (subsystem_config == NULL)
+ err(1, "Could not allocate config nvlist");
+
+ it = NULL;
+ while ((obj = ucl_iterate_object(top, &it, true)) != NULL) {
+ key = ucl_object_key(obj);
+
+ if (nvlist_exists_nvlist(iov_schema, key))
+ add_config(key, obj, iov_config,
+ nvlist_get_nvlist(iov_schema, key));
+ else if (nvlist_exists_nvlist(driver_schema, key))
+ add_config(key, obj, driver_config,
+ nvlist_get_nvlist(driver_schema, key));
+ else
+ errx(1, "%s: Invalid config key '%s'", subsystem, key);
+ }
+
+ nvlist_move_nvlist(subsystem_config, DRIVER_CONFIG_NAME, driver_config);
+ nvlist_move_nvlist(subsystem_config, IOV_CONFIG_NAME, iov_config);
+ nvlist_move_nvlist(config, subsystem, subsystem_config);
+}
+
+/*
+ * Parses the specified config file using the given schema, and returns an
+ * nvlist containing the configuration specified by the file.
+ *
+ * Exits with a message to stderr and an error if any config validation fails.
+ */
+nvlist_t *
+parse_config_file(const char *filename, const nvlist_t *schema)
+{
+ ucl_object_iter_t it;
+ struct ucl_parser *parser;
+ ucl_object_t *top;
+ const ucl_object_t *obj;
+ nvlist_t *config;
+ const nvlist_t *pf_schema, *vf_schema;
+ const char *errmsg, *key;
+ regex_t vf_pat;
+ int regex_err, processed_vf;
+
+ regex_err = regcomp(&vf_pat, "^"VF_PREFIX"([1-9][0-9]*|0)$",
+ REG_EXTENDED | REG_ICASE);
+ if (regex_err != 0)
+ errx(1, "Could not compile VF regex");
+
+ parser = ucl_parser_new(0);
+ if (parser == NULL)
+ err(1, "Could not allocate parser");
+
+ if (!ucl_parser_add_file(parser, filename))
+ err(1, "Could not open '%s' for reading", filename);
+
+ errmsg = ucl_parser_get_error(parser);
+ if (errmsg != NULL)
+ errx(1, "Could not parse '%s': %s", filename, errmsg);
+
+ config = nvlist_create(NV_FLAG_IGNORE_CASE);
+ if (config == NULL)
+ err(1, "Could not allocate config nvlist");
+
+ pf_schema = nvlist_get_nvlist(schema, PF_CONFIG_NAME);
+ vf_schema = nvlist_get_nvlist(schema, VF_SCHEMA_NAME);
+
+ processed_vf = 0;
+ top = ucl_parser_get_object(parser);
+ it = NULL;
+ while ((obj = ucl_iterate_object(top, &it, true)) != NULL) {
+ key = ucl_object_key(obj);
+
+ if (strcasecmp(key, PF_CONFIG_NAME) == 0)
+ parse_device_config(obj, config, key, pf_schema);
+ else if (strcasecmp(key, DEFAULT_SCHEMA_NAME) == 0) {
+ /*
+ * Enforce that the default section must come before all
+ * VF sections. This will hopefully prevent confusing
+ * the user by having a default value apply to a VF
+ * that was declared earlier in the file.
+ *
+ * This also gives us the flexibility to extend the file
+ * format in the future to allow for multiple default
+ * sections that do only apply to subsequent VF
+ * sections.
+ */
+ if (processed_vf)
+ errx(1,
+ "'default' section must precede all VF sections");
+
+ parse_device_config(obj, config, key, vf_schema);
+ } else if (regexec(&vf_pat, key, 0, NULL, 0) == 0) {
+ processed_vf = 1;
+ parse_device_config(obj, config, key, vf_schema);
+ } else
+ errx(1, "Unexpected top-level node: %s", key);
+ }
+
+ validate_config(config, schema, &vf_pat);
+
+ ucl_object_unref(top);
+ ucl_parser_free(parser);
+ regfree(&vf_pat);
+
+ return (config);
+}
+
+/*
+ * Parse the PF configuration section for and return the value specified for
+ * the device parameter, or NULL if the device is not specified.
+ */
+static const char *
+find_pf_device(const ucl_object_t *pf)
+{
+ ucl_object_iter_t it;
+ const ucl_object_t *obj;
+ const char *key, *device;
+
+ it = NULL;
+ while ((obj = ucl_iterate_object(pf, &it, true)) != NULL) {
+ key = ucl_object_key(obj);
+
+ if (strcasecmp(key, "device") == 0) {
+ if (!ucl_object_tostring_safe(obj, &device))
+ err(1,
+ "Config PF.device must be a string");
+
+ return (device);
+ }
+ }
+
+ return (NULL);
+}
+
+/*
+ * Manually parse the config file looking for the name of the PF device. We
+ * have to do this separately because we need the config schema to call the
+ * normal config file parsing code, and we need to know the name of the PF
+ * device so that we can fetch the schema from it.
+ *
+ * This will always exit on failure, so if it returns then it is guaranteed to
+ * have returned a valid device name.
+ */
+char *
+find_device(const char *filename)
+{
+ char *device;
+ const char *deviceName;
+ ucl_object_iter_t it;
+ struct ucl_parser *parser;
+ ucl_object_t *top;
+ const ucl_object_t *obj;
+ const char *errmsg, *key;
+ int error;
+
+ device = NULL;
+ deviceName = NULL;
+
+ parser = ucl_parser_new(0);
+ if (parser == NULL)
+ err(1, "Could not allocate parser");
+
+ if (!ucl_parser_add_file(parser, filename))
+ err(1, "Could not open '%s' for reading", filename);
+
+ errmsg = ucl_parser_get_error(parser);
+ if (errmsg != NULL)
+ errx(1, "Could not parse '%s': %s", filename, errmsg);
+
+ top = ucl_parser_get_object (parser);
+ it = NULL;
+ while ((obj = ucl_iterate_object(top, &it, true)) != NULL) {
+ key = ucl_object_key(obj);
+
+ if (strcasecmp(key, PF_CONFIG_NAME) == 0) {
+ deviceName = find_pf_device(obj);
+ break;
+ }
+ }
+
+ if (deviceName == NULL)
+ errx(1, "Config file does not specify device");
+
+ error = asprintf(&device, "/dev/iov/%s", deviceName);
+ if (error < 0)
+ err(1, "Could not allocate memory for device");
+
+ ucl_object_unref(top);
+ ucl_parser_free(parser);
+
+ return (device);
+}
diff --git a/usr.sbin/iovctl/validate.c b/usr.sbin/iovctl/validate.c
new file mode 100644
index 0000000..789175a
--- /dev/null
+++ b/usr.sbin/iovctl/validate.c
@@ -0,0 +1,274 @@
+/*-
+ * Copyright (c) 2014-2015 Sandvine Inc. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/iov.h>
+#include <sys/dnv.h>
+#include <sys/nv.h>
+
+#include <err.h>
+#include <regex.h>
+#include <stdlib.h>
+
+#include "iovctl.h"
+
+/*
+ * Returns a writeable pointer to the configuration for the given device.
+ * If no configuration exists, a new nvlist with empty driver and iov
+ * sections is allocated and returned.
+ *
+ * Returning a writeable pointer requires removing the configuration from config
+ * using nvlist_take. It is the responsibility of the caller to re-insert the
+ * nvlist in config with nvlist_move_nvlist.
+ */
+static nvlist_t *
+find_config(nvlist_t *config, const char * device)
+{
+ nvlist_t *subsystem, *empty_driver, *empty_iov;
+
+ subsystem = dnvlist_take_nvlist(config, device, NULL);
+
+ if (subsystem != NULL)
+ return (subsystem);
+
+ empty_driver = nvlist_create(NV_FLAG_IGNORE_CASE);
+ if (empty_driver == NULL)
+ err(1, "Could not allocate config nvlist");
+
+ empty_iov = nvlist_create(NV_FLAG_IGNORE_CASE);
+ if (empty_iov == NULL)
+ err(1, "Could not allocate config nvlist");
+
+ subsystem = nvlist_create(NV_FLAG_IGNORE_CASE);
+ if (subsystem == NULL)
+ err(1, "Could not allocate config nvlist");
+
+ nvlist_move_nvlist(subsystem, DRIVER_CONFIG_NAME, empty_driver);
+ nvlist_move_nvlist(subsystem, IOV_CONFIG_NAME, empty_iov);
+
+ return (subsystem);
+}
+
+static uint16_t
+parse_vf_num(const char *key, regmatch_t *matches)
+{
+ u_long vf_num;
+
+ vf_num = strtoul(key + matches[1].rm_so, NULL, 10);
+
+ if (vf_num > UINT16_MAX)
+ errx(1, "VF number %lu is too large to be valid",
+ vf_num);
+
+ return (vf_num);
+}
+
+/*
+ * Apply the default values specified in device_defaults to the specified
+ * subsystem in the given device_config.
+ *
+ * This function assumes that the values specified in device_defaults have
+ * already been validated.
+ */
+static void
+apply_subsystem_defaults(nvlist_t *device_config, const char *subsystem,
+ const nvlist_t *device_defaults)
+{
+ nvlist_t *config;
+ const nvlist_t *defaults;
+ const char *name;
+ void *cookie;
+ size_t len;
+ const void *bin;
+ int type;
+
+ config = nvlist_take_nvlist(device_config, subsystem);
+ defaults = nvlist_get_nvlist(device_defaults, subsystem);
+
+ cookie = NULL;
+ while ((name = nvlist_next(defaults, &type, &cookie)) != NULL) {
+ if (nvlist_exists(config, name))
+ continue;
+
+ switch (type) {
+ case NV_TYPE_BOOL:
+ nvlist_add_bool(config, name,
+ nvlist_get_bool(defaults, name));
+ break;
+ case NV_TYPE_NUMBER:
+ nvlist_add_number(config, name,
+ nvlist_get_number(defaults, name));
+ break;
+ case NV_TYPE_STRING:
+ nvlist_add_string(config, name,
+ nvlist_get_string(defaults, name));
+ break;
+ case NV_TYPE_NVLIST:
+ nvlist_add_nvlist(config, name,
+ nvlist_get_nvlist(defaults, name));
+ break;
+ case NV_TYPE_BINARY:
+ bin = nvlist_get_binary(defaults, name, &len);
+ nvlist_add_binary(config, name, bin, len);
+ break;
+ default:
+ errx(1, "Unexpected type '%d'", type);
+ }
+ }
+ nvlist_move_nvlist(device_config, subsystem, config);
+}
+
+/*
+ * Iterate over every subsystem in the given VF device and apply default values
+ * for parameters that were not configured with a value.
+ *
+ * This function assumes that the values specified in defaults have already been
+ * validated.
+ */
+static void
+apply_defaults(nvlist_t *vf, const nvlist_t *defaults)
+{
+
+ apply_subsystem_defaults(vf, DRIVER_CONFIG_NAME, defaults);
+ apply_subsystem_defaults(vf, IOV_CONFIG_NAME, defaults);
+}
+
+/*
+ * Validate that all required parameters have been configured in the specified
+ * subsystem.
+ */
+static void
+validate_subsystem(const nvlist_t *device, const nvlist_t *device_schema,
+ const char *subsystem_name, const char *config_name)
+{
+ const nvlist_t *subsystem, *schema, *config;
+ const char *name;
+ void *cookie;
+ int type;
+
+ subsystem = nvlist_get_nvlist(device, subsystem_name);
+ schema = nvlist_get_nvlist(device_schema, subsystem_name);
+
+ cookie = NULL;
+ while ((name = nvlist_next(schema, &type, &cookie)) != NULL) {
+ config = nvlist_get_nvlist(schema, name);
+
+ if (dnvlist_get_bool(config, REQUIRED_SCHEMA_NAME, false)) {
+ if (!nvlist_exists(subsystem, name))
+ errx(1,
+ "Required parameter '%s' not found in '%s'",
+ name, config_name);
+ }
+ }
+}
+
+/*
+ * Validate that all required parameters have been configured in all subsystems
+ * in the device.
+ */
+static void
+validate_device(const nvlist_t *device, const nvlist_t *schema,
+ const char *config_name)
+{
+
+ validate_subsystem(device, schema, DRIVER_CONFIG_NAME, config_name);
+ validate_subsystem(device, schema, IOV_CONFIG_NAME, config_name);
+}
+
+static uint16_t
+get_num_vfs(const nvlist_t *pf)
+{
+ const nvlist_t *iov;
+
+ iov = nvlist_get_nvlist(pf, IOV_CONFIG_NAME);
+ return (nvlist_get_number(iov, "num_vfs"));
+}
+
+/*
+ * Validates the configuration that has been parsed into config using the given
+ * config schema. Note that the parser is required to not insert configuration
+ * keys that are not valid in the schema, and to not insert configuration values
+ * that are of the incorrect type. Therefore this function will not validate
+ * either condition. This function is only responsible for inserting config
+ * file defaults in individual VF sections and removing the DEFAULT_SCHEMA_NAME
+ * subsystem from config, validating that all required parameters in the schema
+ * are present in each PF and VF subsystem, and that there is no VF subsystem
+ * section whose number exceeds num_vfs.
+ */
+void
+validate_config(nvlist_t *config, const nvlist_t *schema, const regex_t *vf_pat)
+{
+ char device_name[VF_MAX_NAME];
+ regmatch_t matches[2];
+ nvlist_t *defaults, *pf, *vf;
+ const nvlist_t *vf_schema;
+ const char *key;
+ void *cookie;
+ int i, type;
+ uint16_t vf_num, num_vfs;
+
+ pf = find_config(config, PF_CONFIG_NAME);
+ validate_device(pf, nvlist_get_nvlist(schema, PF_CONFIG_NAME),
+ PF_CONFIG_NAME);
+ nvlist_move_nvlist(config, PF_CONFIG_NAME, pf);
+
+ num_vfs = get_num_vfs(pf);
+ vf_schema = nvlist_get_nvlist(schema, VF_SCHEMA_NAME);
+
+ if (num_vfs == 0)
+ errx(1, "PF.num_vfs must be at least 1");
+
+ defaults = dnvlist_take_nvlist(config, DEFAULT_SCHEMA_NAME, NULL);
+
+ for (i = 0; i < num_vfs; i++) {
+ snprintf(device_name, sizeof(device_name), VF_PREFIX"%d",
+ i);
+
+ vf = find_config(config, device_name);
+
+ if (defaults != NULL)
+ apply_defaults(vf, defaults);
+
+ validate_device(vf, vf_schema, device_name);
+ nvlist_move_nvlist(config, device_name, vf);
+ }
+ nvlist_destroy(defaults);
+
+ cookie = NULL;
+ while ((key = nvlist_next(config, &type, &cookie)) != NULL) {
+ if (regexec(vf_pat, key, nitems(matches), matches, 0) == 0) {
+ vf_num = parse_vf_num(key, matches);
+ if (vf_num >= num_vfs)
+ errx(1,
+ "VF number %d is out of bounds (num_vfs=%d)",
+ vf_num, num_vfs);
+ }
+ }
+}
+
diff --git a/usr.sbin/ip6addrctl/Makefile b/usr.sbin/ip6addrctl/Makefile
new file mode 100644
index 0000000..cd2510e
--- /dev/null
+++ b/usr.sbin/ip6addrctl/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= ip6addrctl
+MAN= ip6addrctl.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ip6addrctl/Makefile.depend b/usr.sbin/ip6addrctl/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/ip6addrctl/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ip6addrctl/ip6addrctl.8 b/usr.sbin/ip6addrctl/ip6addrctl.8
new file mode 100644
index 0000000..59154d4
--- /dev/null
+++ b/usr.sbin/ip6addrctl/ip6addrctl.8
@@ -0,0 +1,126 @@
+.\" $KAME: ip6addrctl.8,v 1.3 2003/03/22 05:56:41 jinmei Exp $
+.\"
+.\" Copyright (C) 2001 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 25, 2001
+.Dt IP6ADDRCTL 8
+.Os
+.\"
+.Sh NAME
+.Nm ip6addrctl
+.Nd configure address selection policy for IPv6 and IPv4
+.\"
+.Sh SYNOPSIS
+.Nm
+.Op Cm show
+.Nm
+.Cm add
+.Ar prefix precedence label
+.Nm
+.Cm delete
+.Ar prefix
+.Nm
+.Cm flush
+.Nm
+.Cm install
+.Ar configfile
+.\"
+.Sh DESCRIPTION
+The
+.Nm
+utility manages the policy table of source and destination address
+selection for outgoing IPv4 and IPv6 packets.
+When
+.Nm
+is invoked without an argument or with a single argument
+.Cm show ,
+it prints the content of the policy table currently installed in the
+kernel.
+.Pp
+To modify the table, the following operations are available:
+.Bl -tag -width indent
+.It Cm add Ar prefix precedence label
+Add a policy entry.
+The
+.Ar prefix
+argument
+is an IPv6 prefix, which is a key for the entry.
+An IPv4 prefix should be specified with an IPv6 prefix using an
+IPv4-mapped IPv6 address.
+The
+.Ar precedence
+and
+.Ar label
+arguments
+are decimal numbers, which specify the precedence and label values
+for the entry, respectively.
+This operation should be performed without an existing entry for the
+prefix.
+.It Cm delete Ar prefix
+Delete a policy entry specified by
+.Ar prefix ,
+which should be an IPv6 prefix.
+A corresponding entry for the prefix should have already been
+installed.
+.It Cm flush
+Delete all existing policy entries in the kernel.
+.It Cm install Ar configfile
+Install policy entries from a configuration file named
+.Ar configfile .
+The configuration file should contain a set of policy entries.
+Each entry is specified in a single line which contains an IPv6 prefix,
+a decimal precedence value, and a decimal label value, separated with
+white space or tab characters.
+In the configuration file, lines beginning with the pound-sign
+.Pq Ql #
+are
+comments and are ignored.
+.El
+.\"
+.Sh EXIT STATUS
+.Ex -std
+.\"
+.Sh SEE ALSO
+.Rs
+.%A "Richard Draves"
+.%T "Default Address Selection for IPv6"
+.%N RFC 3484
+.Re
+.\"
+.Sh HISTORY
+The
+.Nm
+utility first appeared in the KAME IPv6 protocol stack kit.
+The original command name was
+.Nm addrselect ,
+but it was then renamed to the current one so that the name would
+describe its function well.
+.\" .Sh BUGS
+.\" (to be written)
diff --git a/usr.sbin/ip6addrctl/ip6addrctl.c b/usr.sbin/ip6addrctl/ip6addrctl.c
new file mode 100644
index 0000000..d9bf89d
--- /dev/null
+++ b/usr.sbin/ip6addrctl/ip6addrctl.c
@@ -0,0 +1,456 @@
+/* $KAME: ip6addrctl.c,v 1.3 2003/12/16 08:14:28 suz Exp $ */
+
+/*
+ * Copyright (C) 2001 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/socket.h>
+#include <sys/queue.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/sysctl.h>
+
+#include <net/if.h>
+
+#include <netinet/in.h>
+#include <netinet6/in6_var.h>
+
+#include <stdlib.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <limits.h>
+#include <string.h>
+#include <err.h>
+
+static char *configfile;
+
+struct policyqueue {
+ TAILQ_ENTRY(policyqueue) pc_entry;
+ struct in6_addrpolicy pc_policy;
+};
+TAILQ_HEAD(policyhead, policyqueue);
+static struct policyhead policyhead;
+
+static void usage(void);
+static void get_policy(void);
+static void dump_policy(void);
+static int mask2plen(struct sockaddr_in6 *);
+static int parse_prefix(const char *, struct in6_addrpolicy *);
+static void make_policy_fromfile(char *);
+static void plen2mask(struct sockaddr_in6 *, int);
+static void set_policy(void);
+static void add_policy(char *, char *, char *);
+static void delete_policy(char *);
+static void flush_policy(void);
+
+int
+main(int argc, char *argv[])
+{
+ TAILQ_INIT(&policyhead);
+
+ if (argc == 1 || strcasecmp(argv[1], "show") == 0) {
+ get_policy();
+ dump_policy();
+ } else if (strcasecmp(argv[1], "add") == 0) {
+ if (argc < 5)
+ usage();
+ add_policy(argv[2], argv[3], argv[4]);
+ } else if (strcasecmp(argv[1], "delete") == 0) {
+ if (argc < 3)
+ usage();
+ delete_policy(argv[2]);
+ } else if (strcasecmp(argv[1], "flush") == 0) {
+ get_policy();
+ flush_policy();
+ } else if (strcasecmp(argv[1], "install") == 0) {
+ if (argc < 3)
+ usage();
+ configfile = argv[2];
+ make_policy_fromfile(configfile);
+ set_policy();
+ } else
+ usage();
+
+ exit(0);
+}
+
+static void
+get_policy(void)
+{
+ int mib[] = { CTL_NET, PF_INET6, IPPROTO_IPV6, IPV6CTL_ADDRCTLPOLICY };
+ size_t l;
+ struct in6_addrpolicy *buf;
+ struct in6_addrpolicy *pol, *ep;
+
+ if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), NULL, &l, NULL, 0) < 0) {
+ err(1, "sysctl(IPV6CTL_ADDRCTLPOLICY)");
+ /* NOTREACHED */
+ }
+ if (l == 0) {
+ printf("no source-address-selection policy is installed\n");
+ return;
+ }
+ if ((buf = malloc(l)) == NULL) {
+ errx(1, "malloc failed");
+ /* NOTREACHED */
+ }
+ if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), buf, &l, NULL, 0) < 0) {
+ err(1, "sysctl(IPV6CTL_ADDRCTLPOLICY)");
+ /* NOTREACHED */
+ }
+
+ ep = buf + l/sizeof(*buf);
+ for (pol = buf; pol + 1 <= ep; pol++) {
+ struct policyqueue *new;
+
+ if ((new = malloc(sizeof(*new))) == NULL)
+ errx(1, "malloc failed\n");
+ new->pc_policy = *pol;
+ TAILQ_INSERT_TAIL(&policyhead, new, pc_entry);
+ }
+
+ free(buf);
+}
+
+static void
+dump_policy(void)
+{
+ size_t addrlen;
+ char addrbuf[NI_MAXHOST];
+ struct in6_addrpolicy *pol;
+ struct policyqueue *ent;
+ int plen, first = 1;
+
+ for (ent = TAILQ_FIRST(&policyhead); ent;
+ ent = TAILQ_NEXT(ent, pc_entry)) {
+ pol = &ent->pc_policy;
+ if (first) {
+ printf("%-30s %5s %5s %8s\n",
+ "Prefix", "Prec", "Label", "Use");
+ first = 0;
+ }
+
+ if ((getnameinfo((struct sockaddr *)&pol->addr,
+ sizeof(pol->addr), addrbuf, sizeof(addrbuf),
+ NULL, 0, NI_NUMERICHOST))) {
+ warnx("getnameinfo for prefix address failed");
+ continue;
+ }
+ if ((plen = mask2plen(&pol->addrmask)) < 0) {
+ warnx("invalid address mask");
+ continue;
+ }
+ addrlen = strlen(addrbuf);
+ if (addrlen + sizeof("/128") < sizeof(addrbuf)) {
+ snprintf(&addrbuf[addrlen],
+ sizeof(addrbuf) - addrlen - 1,
+ "/%d", plen);
+ printf("%-30s", addrbuf);
+ } else /* XXX */
+ printf("%s/%d", addrbuf, plen);
+ printf(" %5d %5d %8llu\n", pol->preced, pol->label,
+ (unsigned long long)pol->use);
+ }
+}
+
+#define SKIP_WHITE(p, emptyok) \
+ do { \
+ while((*(p) == ' ' || *(p) == '\t')) \
+ (p)++; \
+ if ((*(p) == '\0' || (*(p) == '\n')) && !(emptyok)) \
+ goto bad; \
+ } while (0);
+#define SKIP_WORD(p) \
+ do { \
+ while(*(p) != ' ' && *(p) != '\t') \
+ (p)++; \
+ if (*(p) == '\0' || *(p) == '\n') \
+ goto bad; \
+ } while (0);
+
+static void
+make_policy_fromfile(char *conf)
+{
+ char line[_POSIX2_LINE_MAX], *cp;
+ char *addrstr;
+ FILE *fp;
+ int count = 0;
+ struct in6_addrpolicy pol0;
+ struct policyqueue *new;
+
+ if ((fp = fopen(conf, "r")) == NULL)
+ err(1, "fopen: %s", conf);
+
+ while(fgets(line, sizeof(line), fp)) {
+ count++;
+ cp = line;
+
+ memset(&pol0, 0, sizeof(pol0));
+
+ /* get prefix */
+ SKIP_WHITE(cp, 1);
+ if (*cp == '\n') /* empty line */
+ continue;
+ if (*cp == '#')
+ continue;
+ addrstr = cp;
+ if (parse_prefix((const char *)addrstr, &pol0))
+ goto bad;
+
+ /* get precedence value */
+ SKIP_WORD(cp);
+ SKIP_WHITE(cp, 0);
+ pol0.preced = atoi(cp);
+
+ /* get label */
+ SKIP_WORD(cp);
+ SKIP_WHITE(cp, 0);
+ pol0.label = atoi(cp);
+
+ /* parse succeeded. make a control buffer entry. */
+ if ((new = malloc(sizeof(*new))) == NULL)
+ errx(1, "malloc failed\n");
+ memset(new, 0, sizeof(*new));
+ new->pc_policy = pol0;
+ TAILQ_INSERT_TAIL(&policyhead, new, pc_entry);
+ }
+
+ fclose(fp);
+ return;
+
+ bad:
+ errx(1, "parse failed at line %d", count);
+ /* NOTREACHED */
+}
+
+static int
+parse_prefix(const char *prefix0, struct in6_addrpolicy *pol)
+{
+ int e = 0, plen;
+ char *prefix, *plenstr;
+ struct addrinfo hints, *res;
+
+ if ((prefix = strdup(prefix0)) == NULL)
+ errx(1, "strdup failed");
+
+ if ((plenstr = strchr(prefix, '/')) == NULL) {
+ e = -1;
+ goto end;
+ }
+ *plenstr = '\0';
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_NUMERICHOST;
+ hints.ai_family = AF_INET6;
+
+ if ((e = getaddrinfo(prefix, NULL, &hints, &res)) != 0) {
+ warnx("getaddrinfo failed for %s: %s", prefix,
+ gai_strerror(e));
+ goto end;
+ }
+ memcpy(&pol->addr, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+ plen = atoi(plenstr + 1);
+ if (plen < 0 || plen > 128) {
+ warnx("invalid prefix length: %d", plen);
+ e = -1;
+ goto end;
+ }
+ plen2mask(&pol->addrmask, plen);
+
+ end:
+ free(prefix);
+ return(e);
+}
+
+static void
+plen2mask(struct sockaddr_in6 *mask, int plen)
+{
+ u_char *cp = (unsigned char *)&mask->sin6_addr;
+
+ memset(mask, 0, sizeof(*mask));
+ mask->sin6_family = AF_INET6; /* just in case */
+ mask->sin6_len = sizeof(*mask);
+
+ for(; plen >= 8; plen -= 8)
+ *cp++ = 0xff;
+ if (plen > 0)
+ *cp = (0xff << (8 - plen));
+}
+
+static void
+set_policy(void)
+{
+ struct policyqueue *ent;
+ int s;
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)) < 0)
+ err(1, "socket(UDP)");
+
+ for (ent = TAILQ_FIRST(&policyhead); ent;
+ ent = TAILQ_NEXT(ent, pc_entry)) {
+ if (ioctl(s, SIOCAADDRCTL_POLICY, &ent->pc_policy))
+ warn("ioctl(SIOCAADDRCTL_POLICY)");
+ }
+
+ close(s);
+}
+
+static int
+mask2plen(struct sockaddr_in6 *mask)
+{
+ int masklen, final = 0;
+ u_char *p, *lim;
+
+ masklen = 0;
+ lim = (u_char *)(mask + 1);
+ for (p = (u_char *)(&mask->sin6_addr); p < lim; p++) {
+ if (final && *p) {
+ goto bad;
+ }
+
+ switch (*p & 0xff) {
+ case 0xff:
+ masklen += 8;
+ break;
+ case 0xfe:
+ masklen += 7;
+ final++;
+ break;
+ case 0xfc:
+ masklen += 6;
+ final++;
+ break;
+ case 0xf8:
+ masklen += 5;
+ final++;
+ break;
+ case 0xf0:
+ masklen += 4;
+ final++;
+ break;
+ case 0xe0:
+ masklen += 3;
+ final++;
+ break;
+ case 0xc0:
+ masklen += 2;
+ final++;
+ break;
+ case 0x80:
+ masklen += 1;
+ final++;
+ break;
+ case 0x00:
+ final++;
+ break;
+ default:
+ goto bad;
+ break;
+ }
+ }
+ return(masklen);
+
+ bad:
+ return(-1);
+}
+
+static void
+add_policy(char *prefix, char *prec, char *label)
+{
+ struct in6_addrpolicy p;
+ int s;
+
+ memset(&p, 0, sizeof(p));
+
+ if (parse_prefix((const char *)prefix, &p))
+ errx(1, "bad prefix: %s", prefix);
+ p.preced = atoi(prec);
+ p.label = atoi(label);
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)) < 0)
+ err(1, "socket(UDP)");
+ if (ioctl(s, SIOCAADDRCTL_POLICY, &p))
+ err(1, "ioctl(SIOCAADDRCTL_POLICY)");
+
+ close(s);
+}
+
+static void
+delete_policy(char *prefix)
+{
+ struct in6_addrpolicy p;
+ int s;
+
+ memset(&p, 0, sizeof(p));
+
+ if (parse_prefix((const char *)prefix, &p))
+ errx(1, "bad prefix: %s", prefix);
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)) < 0)
+ err(1, "socket(UDP)");
+ if (ioctl(s, SIOCDADDRCTL_POLICY, &p))
+ err(1, "ioctl(SIOCDADDRCTL_POLICY)");
+
+ close(s);
+}
+
+static void
+flush_policy(void)
+{
+ struct policyqueue *ent;
+ int s;
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)) < 0)
+ err(1, "socket(UDP)");
+
+ for (ent = TAILQ_FIRST(&policyhead); ent;
+ ent = TAILQ_NEXT(ent, pc_entry)) {
+ if (ioctl(s, SIOCDADDRCTL_POLICY, &ent->pc_policy))
+ warn("ioctl(SIOCDADDRCTL_POLICY)");
+ }
+
+ close(s);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: ip6addrctl [show]\n");
+ fprintf(stderr, " ip6addrctl add "
+ "<prefix> <precedence> <label>\n");
+ fprintf(stderr, " ip6addrctl delete <prefix>\n");
+ fprintf(stderr, " ip6addrctl flush\n");
+ fprintf(stderr, " ip6addrctl install <configfile>\n");
+
+ exit(1);
+}
diff --git a/usr.sbin/ip6addrctl/ip6addrctl.conf.sample b/usr.sbin/ip6addrctl/ip6addrctl.conf.sample
new file mode 100644
index 0000000..a2b3759
--- /dev/null
+++ b/usr.sbin/ip6addrctl/ip6addrctl.conf.sample
@@ -0,0 +1,12 @@
+# default policy table based on RFC 3484.
+# usage: ip6addrctl install path_to_this_file
+#
+# $FreeBSD$
+#
+#Format:
+#Prefix Precedence Label
+::1/128 50 0
+::/0 40 1
+2002::/16 30 2
+::/96 20 3
+::ffff:0:0/96 10 4
diff --git a/usr.sbin/ipfwpcap/Makefile b/usr.sbin/ipfwpcap/Makefile
new file mode 100644
index 0000000..2fde890
--- /dev/null
+++ b/usr.sbin/ipfwpcap/Makefile
@@ -0,0 +1,18 @@
+#
+# From: Id: Makefile,v 1.2 2004/01/15 16:20:56 pkern Exp
+#
+# $FreeBSD$
+#
+
+PROG= ipfwpcap
+
+LIBADD= pcap
+
+MAN= ipfwpcap.8
+
+.include <bsd.prog.mk>
+
+test: $(CMD)
+ -rm /var/run/ipfwpcap.2000.pid
+ ./ipfwpcap -d 2000 - | tcpdump -r - -n -s 2000 -X
+
diff --git a/usr.sbin/ipfwpcap/Makefile.depend b/usr.sbin/ipfwpcap/Makefile.depend
new file mode 100644
index 0000000..268320b
--- /dev/null
+++ b/usr.sbin/ipfwpcap/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libpcap \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ipfwpcap/ipfwpcap.8 b/usr.sbin/ipfwpcap/ipfwpcap.8
new file mode 100644
index 0000000..a161ebb
--- /dev/null
+++ b/usr.sbin/ipfwpcap/ipfwpcap.8
@@ -0,0 +1,132 @@
+.\" Copyright (c) 2006 Niclas Zeising <zeising@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 May 22, 2006
+.Dt IPFWPCAP 8
+.Os
+.Sh NAME
+.Nm ipfwpcap
+.Nd "copy diverted packets to a file in tcpdump format"
+.Sh SYNOPSIS
+.Nm
+.Op Fl dr
+.Op Fl b Ar maxbytes
+.Op Fl p Ar maxpkts
+.Op Fl P Ar pidfile
+.Ar portnum
+.Ar dumpfile
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to copy diverted packets to a file in
+.Xr tcpdump 1
+format.
+The interesting packets are diverted by
+.Xr ipfw 8
+to a port on which
+.Nm
+listens.
+The packets are then dropped unless
+.Fl r
+is used.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl d
+Turns on extra debugging messages.
+.It Fl r
+Writes packets back to the
+.Xr divert 4
+socket.
+.It Fl rr
+Indicates that it is okay to quit if
+.Ar maxbytes
+or
+.Ar maxpkts
+are reached.
+Diverted packets will silently disappear if nothing is listening on the
+.Xr divert 4
+socket.
+.It Fl b Ar maxbytes
+Stop dumping after
+.Ar maxbytes
+bytes.
+.It Fl p Ar maxpkts
+Stop dumping after
+.Ar maxpkt
+packets.
+.It Fl P Ar pidfile
+File to store PID number in.
+Default is
+.Pa /var/run/ipwfpcap.portnr.pid .
+.El
+.Pp
+The
+.Ar portnum
+argument specifies which
+.Xr divert 4
+socket port to listen on.
+The
+.Ar dumpfile
+argument is the path to the file to write captured packets to.
+Specify
+.Sq Fl
+to write to stdout.
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+.Dl "ipfwpcap -r 8091 divt.log &"
+.Pp
+Starts
+.Nm
+as a background job listening to port 8091 and reflecting the packets
+back to the socket.
+.Pp
+.Dl "ipfw add 2864 divert 8091 ip from 192.0.2.101"
+.Pp
+Example
+.Xr ipfw 8
+rule to divert all packets from 192.0.2.101 to port 8091.
+See
+.Xr ipfw 8
+for details.
+.Sh SEE ALSO
+.Xr tcpdump 1 ,
+.Xr pcap 3 ,
+.Xr divert 4 ,
+.Xr ipfw 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 7.0 .
+.Sh AUTHORS
+.An -nosplit
+.Nm
+was written by
+.An P. Kern Aq Mt pkern@cns.utoronto.ca .
+This manual page was written by
+.An Niclas Zeising Aq Mt zeising@FreeBSD.org .
diff --git a/usr.sbin/ipfwpcap/ipfwpcap.c b/usr.sbin/ipfwpcap/ipfwpcap.c
new file mode 100644
index 0000000..d579bd9
--- /dev/null
+++ b/usr.sbin/ipfwpcap/ipfwpcap.c
@@ -0,0 +1,303 @@
+/*
+ * copy diverted (or tee'd) packets to a file in 'tcpdump' format
+ * (ie. this uses the '-lpcap' routines).
+ *
+ * example usage:
+ * # ipfwpcap -r 8091 divt.log &
+ * # ipfw add 2864 divert 8091 ip from 128.432.53.82 to any
+ * # ipfw add 2864 divert 8091 ip from any to 128.432.53.82
+ *
+ * the resulting dump file can be read with ...
+ * # tcpdump -nX -r divt.log
+ */
+/*
+ * Written by P Kern { pkern [AT] cns.utoronto.ca }
+ *
+ * Copyright (c) 2004 University of Toronto. All rights reserved.
+ * Anyone may use or copy this software except that this copyright
+ * notice remain intact and that credit is given where it is due.
+ * The University of Toronto and the author make no warranty and
+ * accept no liability for this software.
+ *
+ * From: Header: /local/src/local.lib/SRC/ipfwpcap/RCS/ipfwpcap.c,v 1.4 2004/01/15 16:19:07 pkern Exp
+ *
+ * $FreeBSD$
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <paths.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/param.h> /* for MAXPATHLEN */
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <netinet/in_systm.h> /* for IP_MAXPACKET */
+#include <netinet/ip.h> /* for IP_MAXPACKET */
+
+/* XXX normally defined in config.h */
+#define HAVE_STRLCPY 1
+#define HAVE_SNPRINTF 1
+#define HAVE_VSNPRINTF 1
+#include <pcap-int.h> /* see pcap(3) and /usr/src/contrib/libpcap/. */
+
+#ifdef IP_MAXPACKET
+#define BUFMAX IP_MAXPACKET
+#else
+#define BUFMAX 65535
+#endif
+
+#ifndef MAXPATHLEN
+#define MAXPATHLEN 1024
+#endif
+
+static int debug = 0;
+static int reflect = 0; /* 1 == write packet back to socket. */
+
+static ssize_t totbytes = 0, maxbytes = 0;
+static ssize_t totpkts = 0, maxpkts = 0;
+
+static char *prog = NULL;
+static char pidfile[MAXPATHLEN];
+
+/*
+ * tidy up.
+ */
+static void
+quit(int sig)
+{
+ (void) unlink(pidfile);
+ exit(sig);
+}
+
+/*
+ * do the "paper work"
+ * - save my own pid in /var/run/$0.{port#}.pid
+ */
+static void
+okay(int pn)
+{
+ int fd;
+ char *p, numbuf[80];
+
+ if (pidfile[0] == '\0') {
+ p = strrchr(prog, '/');
+ p = (p == NULL) ? prog : p + 1;
+
+ snprintf(pidfile, sizeof pidfile,
+ "%s%s.%d.pid", _PATH_VARRUN, p, pn);
+ }
+
+ fd = open(pidfile, O_WRONLY|O_CREAT|O_EXCL, 0644);
+ if (fd < 0) {
+ perror(pidfile);
+ exit(21);
+ }
+
+ siginterrupt(SIGTERM, 1);
+ siginterrupt(SIGHUP, 1);
+ signal(SIGTERM, quit);
+ signal(SIGHUP, quit);
+ signal(SIGINT, quit);
+
+ snprintf(numbuf, sizeof numbuf, "%d\n", getpid());
+ if (write(fd, numbuf, strlen(numbuf)) < 0) {
+ perror(pidfile);
+ quit(23);
+ }
+ (void) close(fd);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+"\n"
+"usage:\n"
+" %s [-dr] [-b maxbytes] [-p maxpkts] [-P pidfile] portnum dumpfile\n"
+"\n"
+"where:\n"
+" '-d' = enable debugging messages.\n"
+" '-r' = reflect. write packets back to the divert socket.\n"
+" (ie. simulate the original intent of \"ipfw tee\").\n"
+" '-rr' = indicate that it is okay to quit if packet-count or\n"
+" byte-count limits are reached (see the NOTE below\n"
+" about what this implies).\n"
+" '-b bytcnt' = stop dumping after {bytcnt} bytes.\n"
+" '-p pktcnt' = stop dumping after {pktcnt} packets.\n"
+" '-P pidfile' = alternate file to store the PID\n"
+" (default: /var/run/%s.{portnum}.pid).\n"
+"\n"
+" portnum = divert(4) socket port number.\n"
+" dumpfile = file to write captured packets (tcpdump format).\n"
+" (specify '-' to write packets to stdout).\n"
+"\n"
+"The '-r' option should not be necessary, but because \"ipfw tee\" is broken\n"
+"(see BUGS in ipfw(8) for details) this feature can be used along with\n"
+"an \"ipfw divert\" rule to simulate the original intent of \"ipfw tee\".\n"
+"\n"
+"NOTE: With an \"ipfw divert\" rule, diverted packets will silently\n"
+" disappear if there is nothing listening to the divert socket.\n"
+"\n", prog, prog);
+ exit(1);
+}
+
+int
+main(int ac, char *av[])
+{
+ int r, sd, portnum, l;
+ struct sockaddr_in sin;
+ int errflg = 0;
+
+ int nfd;
+ fd_set rds;
+
+ ssize_t nr;
+
+ char *dumpf, buf[BUFMAX];
+
+ pcap_t *p;
+ pcap_dumper_t *dp;
+ struct pcap_pkthdr phd;
+
+ prog = av[0];
+
+ while ((r = getopt(ac, av, "drb:p:P:")) != -1) {
+ switch (r) {
+ case 'd':
+ debug++;
+ break;
+ case 'r':
+ reflect++;
+ break;
+ case 'b':
+ maxbytes = (ssize_t) atol(optarg);
+ break;
+ case 'p':
+ maxpkts = (ssize_t) atoi(optarg);
+ break;
+ case 'P':
+ strcpy(pidfile, optarg);
+ break;
+ case '?':
+ default:
+ errflg++;
+ break;
+ }
+ }
+
+ if ((ac - optind) != 2 || errflg)
+ usage();
+
+ portnum = atoi(av[optind++]);
+ dumpf = av[optind];
+
+if (debug) fprintf(stderr, "bind to %d.\ndump to '%s'.\n", portnum, dumpf);
+
+ if ((r = socket(PF_INET, SOCK_RAW, IPPROTO_DIVERT)) == -1) {
+ perror("socket(DIVERT)");
+ exit(2);
+ }
+ sd = r;
+
+ sin.sin_port = htons(portnum);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = INADDR_ANY;
+
+ if (bind(sd, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
+ perror("bind(divert)");
+ exit(3);
+ }
+
+ p = pcap_open_dead(DLT_RAW, BUFMAX);
+ dp = pcap_dump_open(p, dumpf);
+ if (dp == NULL) {
+ pcap_perror(p, dumpf);
+ exit(4);
+ }
+
+ okay(portnum);
+
+ nfd = sd + 1;
+ for (;;) {
+ FD_ZERO(&rds);
+ FD_SET(sd, &rds);
+
+ r = select(nfd, &rds, NULL, NULL, NULL);
+ if (r == -1) {
+ if (errno == EINTR) continue;
+ perror("select");
+ quit(11);
+ }
+
+ if (!FD_ISSET(sd, &rds))
+ /* hmm. no work. */
+ continue;
+
+ /*
+ * use recvfrom(3 and sendto(3) as in natd(8).
+ * see /usr/src/sbin/natd/natd.c
+ * see ipfw(8) about using 'divert' and 'tee'.
+ */
+
+ /*
+ * read packet.
+ */
+ l = sizeof(sin);
+ nr = recvfrom(sd, buf, sizeof(buf), 0, (struct sockaddr *)&sin, &l);
+if (debug) fprintf(stderr, "recvfrom(%d) = %zd (%d)\n", sd, nr, l);
+ if (nr < 0 && errno != EINTR) {
+ perror("recvfrom(sd)");
+ quit(12);
+ }
+ if (nr <= 0) continue;
+
+ if (reflect) {
+ /*
+ * write packet back so it can continue
+ * being processed by any further IPFW rules.
+ */
+ l = sizeof(sin);
+ r = sendto(sd, buf, nr, 0, (struct sockaddr *)&sin, l);
+if (debug) fprintf(stderr, " sendto(%d) = %d\n", sd, r);
+ if (r < 0) { perror("sendto(sd)"); quit(13); }
+ }
+
+ /*
+ * check maximums, if any.
+ * but don't quit if must continue reflecting packets.
+ */
+ if (maxpkts) {
+ totpkts++;
+ if (totpkts > maxpkts) {
+ if (reflect == 1) continue;
+ quit(0);
+ }
+ }
+ if (maxbytes) {
+ totbytes += nr;
+ if (totbytes > maxbytes) {
+ if (reflect == 1) continue;
+ quit(0);
+ }
+ }
+
+ /*
+ * save packet in tcpdump(1) format. see pcap(3).
+ * divert packets are fully assembled. see ipfw(8).
+ */
+ (void) gettimeofday(&(phd.ts), NULL);
+ phd.caplen = phd.len = nr;
+ pcap_dump((u_char *)dp, &phd, buf);
+ if (ferror((FILE *)dp)) { perror(dumpf); quit(14); }
+ (void) fflush((FILE *)dp);
+ }
+
+ quit(0);
+}
diff --git a/usr.sbin/iscsid/Makefile b/usr.sbin/iscsid/Makefile
new file mode 100644
index 0000000..ca71f31
--- /dev/null
+++ b/usr.sbin/iscsid/Makefile
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+PROG= iscsid
+SRCS= chap.c discovery.c iscsid.c keys.c log.c login.c pdu.c
+CFLAGS+= -I${.CURDIR}
+CFLAGS+= -I${.CURDIR}/../../sys/cam
+CFLAGS+= -I${.CURDIR}/../../sys/dev/iscsi
+#CFLAGS+= -DICL_KERNEL_PROXY
+MAN= iscsid.8
+
+LIBADD= md util
+
+WARNS= 6
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/iscsid/Makefile.depend b/usr.sbin/iscsid/Makefile.depend
new file mode 100644
index 0000000..023ec43
--- /dev/null
+++ b/usr.sbin/iscsid/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libmd \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/iscsid/chap.c b/usr.sbin/iscsid/chap.c
new file mode 100644
index 0000000..30c1cd4
--- /dev/null
+++ b/usr.sbin/iscsid/chap.c
@@ -0,0 +1,422 @@
+/*-
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <resolv.h>
+#include <md5.h>
+
+#include "iscsid.h"
+
+static void
+chap_compute_md5(const char id, const char *secret,
+ const void *challenge, size_t challenge_len, void *response,
+ size_t response_len)
+{
+ MD5_CTX ctx;
+
+ assert(response_len == CHAP_DIGEST_LEN);
+
+ MD5Init(&ctx);
+ MD5Update(&ctx, &id, sizeof(id));
+ MD5Update(&ctx, secret, strlen(secret));
+ MD5Update(&ctx, challenge, challenge_len);
+ MD5Final(response, &ctx);
+}
+
+static int
+chap_hex2int(const char hex)
+{
+ switch (hex) {
+ case '0':
+ return (0x00);
+ case '1':
+ return (0x01);
+ case '2':
+ return (0x02);
+ case '3':
+ return (0x03);
+ case '4':
+ return (0x04);
+ case '5':
+ return (0x05);
+ case '6':
+ return (0x06);
+ case '7':
+ return (0x07);
+ case '8':
+ return (0x08);
+ case '9':
+ return (0x09);
+ case 'a':
+ case 'A':
+ return (0x0a);
+ case 'b':
+ case 'B':
+ return (0x0b);
+ case 'c':
+ case 'C':
+ return (0x0c);
+ case 'd':
+ case 'D':
+ return (0x0d);
+ case 'e':
+ case 'E':
+ return (0x0e);
+ case 'f':
+ case 'F':
+ return (0x0f);
+ default:
+ return (-1);
+ }
+}
+
+static int
+chap_b642bin(const char *b64, void **binp, size_t *bin_lenp)
+{
+ char *bin;
+ int b64_len, bin_len;
+
+ b64_len = strlen(b64);
+ bin_len = (b64_len + 3) / 4 * 3;
+ bin = calloc(bin_len, 1);
+ if (bin == NULL)
+ log_err(1, "calloc");
+
+ bin_len = b64_pton(b64, bin, bin_len);
+ if (bin_len < 0) {
+ log_warnx("malformed base64 variable");
+ free(bin);
+ return (-1);
+ }
+ *binp = bin;
+ *bin_lenp = bin_len;
+ return (0);
+}
+
+/*
+ * XXX: Review this _carefully_.
+ */
+static int
+chap_hex2bin(const char *hex, void **binp, size_t *bin_lenp)
+{
+ int i, hex_len, nibble;
+ bool lo = true; /* As opposed to 'hi'. */
+ char *bin;
+ size_t bin_off, bin_len;
+
+ if (strncasecmp(hex, "0b", strlen("0b")) == 0)
+ return (chap_b642bin(hex + 2, binp, bin_lenp));
+
+ if (strncasecmp(hex, "0x", strlen("0x")) != 0) {
+ log_warnx("malformed variable, should start with \"0x\""
+ " or \"0b\"");
+ return (-1);
+ }
+
+ hex += strlen("0x");
+ hex_len = strlen(hex);
+ if (hex_len < 1) {
+ log_warnx("malformed variable; doesn't contain anything "
+ "but \"0x\"");
+ return (-1);
+ }
+
+ bin_len = hex_len / 2 + hex_len % 2;
+ bin = calloc(bin_len, 1);
+ if (bin == NULL)
+ log_err(1, "calloc");
+
+ bin_off = bin_len - 1;
+ for (i = hex_len - 1; i >= 0; i--) {
+ nibble = chap_hex2int(hex[i]);
+ if (nibble < 0) {
+ log_warnx("malformed variable, invalid char \"%c\"",
+ hex[i]);
+ free(bin);
+ return (-1);
+ }
+
+ assert(bin_off < bin_len);
+ if (lo) {
+ bin[bin_off] = nibble;
+ lo = false;
+ } else {
+ bin[bin_off] |= nibble << 4;
+ bin_off--;
+ lo = true;
+ }
+ }
+
+ *binp = bin;
+ *bin_lenp = bin_len;
+ return (0);
+}
+
+#ifdef USE_BASE64
+static char *
+chap_bin2hex(const char *bin, size_t bin_len)
+{
+ unsigned char *b64, *tmp;
+ size_t b64_len;
+
+ b64_len = (bin_len + 2) / 3 * 4 + 3; /* +2 for "0b", +1 for '\0'. */
+ b64 = malloc(b64_len);
+ if (b64 == NULL)
+ log_err(1, "malloc");
+
+ tmp = b64;
+ tmp += sprintf(tmp, "0b");
+ b64_ntop(bin, bin_len, tmp, b64_len - 2);
+
+ return (b64);
+}
+#else
+static char *
+chap_bin2hex(const char *bin, size_t bin_len)
+{
+ unsigned char *hex, *tmp, ch;
+ size_t hex_len;
+ size_t i;
+
+ hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */
+ hex = malloc(hex_len);
+ if (hex == NULL)
+ log_err(1, "malloc");
+
+ tmp = hex;
+ tmp += sprintf(tmp, "0x");
+ for (i = 0; i < bin_len; i++) {
+ ch = bin[i];
+ tmp += sprintf(tmp, "%02x", ch);
+ }
+
+ return (hex);
+}
+#endif /* !USE_BASE64 */
+
+struct chap *
+chap_new(void)
+{
+ struct chap *chap;
+
+ chap = calloc(sizeof(*chap), 1);
+ if (chap == NULL)
+ log_err(1, "calloc");
+
+ /*
+ * Generate the challenge.
+ */
+ arc4random_buf(chap->chap_challenge, sizeof(chap->chap_challenge));
+ arc4random_buf(&chap->chap_id, sizeof(chap->chap_id));
+
+ return (chap);
+}
+
+char *
+chap_get_id(const struct chap *chap)
+{
+ char *chap_i;
+ int ret;
+
+ ret = asprintf(&chap_i, "%d", chap->chap_id);
+ if (ret < 0)
+ log_err(1, "asprintf");
+
+ return (chap_i);
+}
+
+char *
+chap_get_challenge(const struct chap *chap)
+{
+ char *chap_c;
+
+ chap_c = chap_bin2hex(chap->chap_challenge,
+ sizeof(chap->chap_challenge));
+
+ return (chap_c);
+}
+
+static int
+chap_receive_bin(struct chap *chap, void *response, size_t response_len)
+{
+
+ if (response_len != sizeof(chap->chap_response)) {
+ log_debugx("got CHAP response with invalid length; "
+ "got %zd, should be %zd",
+ response_len, sizeof(chap->chap_response));
+ return (1);
+ }
+
+ memcpy(chap->chap_response, response, response_len);
+ return (0);
+}
+
+int
+chap_receive(struct chap *chap, const char *response)
+{
+ void *response_bin;
+ size_t response_bin_len;
+ int error;
+
+ error = chap_hex2bin(response, &response_bin, &response_bin_len);
+ if (error != 0) {
+ log_debugx("got incorrectly encoded CHAP response \"%s\"",
+ response);
+ return (1);
+ }
+
+ error = chap_receive_bin(chap, response_bin, response_bin_len);
+ free(response_bin);
+
+ return (error);
+}
+
+int
+chap_authenticate(struct chap *chap, const char *secret)
+{
+ char expected_response[CHAP_DIGEST_LEN];
+
+ chap_compute_md5(chap->chap_id, secret,
+ chap->chap_challenge, sizeof(chap->chap_challenge),
+ expected_response, sizeof(expected_response));
+
+ if (memcmp(chap->chap_response,
+ expected_response, sizeof(expected_response)) != 0) {
+ return (-1);
+ }
+
+ return (0);
+}
+
+void
+chap_delete(struct chap *chap)
+{
+
+ free(chap);
+}
+
+struct rchap *
+rchap_new(const char *secret)
+{
+ struct rchap *rchap;
+
+ rchap = calloc(sizeof(*rchap), 1);
+ if (rchap == NULL)
+ log_err(1, "calloc");
+
+ rchap->rchap_secret = checked_strdup(secret);
+
+ return (rchap);
+}
+
+static void
+rchap_receive_bin(struct rchap *rchap, const unsigned char id,
+ const void *challenge, size_t challenge_len)
+{
+
+ rchap->rchap_id = id;
+ rchap->rchap_challenge = calloc(challenge_len, 1);
+ if (rchap->rchap_challenge == NULL)
+ log_err(1, "calloc");
+ memcpy(rchap->rchap_challenge, challenge, challenge_len);
+ rchap->rchap_challenge_len = challenge_len;
+}
+
+int
+rchap_receive(struct rchap *rchap, const char *id, const char *challenge)
+{
+ unsigned char id_bin;
+ void *challenge_bin;
+ size_t challenge_bin_len;
+
+ int error;
+
+ id_bin = strtoul(id, NULL, 10);
+
+ error = chap_hex2bin(challenge, &challenge_bin, &challenge_bin_len);
+ if (error != 0) {
+ log_debugx("got incorrectly encoded CHAP challenge \"%s\"",
+ challenge);
+ return (1);
+ }
+
+ rchap_receive_bin(rchap, id_bin, challenge_bin, challenge_bin_len);
+ free(challenge_bin);
+
+ return (0);
+}
+
+static void
+rchap_get_response_bin(struct rchap *rchap,
+ void **responsep, size_t *response_lenp)
+{
+ void *response_bin;
+ size_t response_bin_len = CHAP_DIGEST_LEN;
+
+ response_bin = calloc(response_bin_len, 1);
+ if (response_bin == NULL)
+ log_err(1, "calloc");
+
+ chap_compute_md5(rchap->rchap_id, rchap->rchap_secret,
+ rchap->rchap_challenge, rchap->rchap_challenge_len,
+ response_bin, response_bin_len);
+
+ *responsep = response_bin;
+ *response_lenp = response_bin_len;
+}
+
+char *
+rchap_get_response(struct rchap *rchap)
+{
+ void *response;
+ size_t response_len;
+ char *chap_r;
+
+ rchap_get_response_bin(rchap, &response, &response_len);
+ chap_r = chap_bin2hex(response, response_len);
+ free(response);
+
+ return (chap_r);
+}
+
+void
+rchap_delete(struct rchap *rchap)
+{
+
+ free(rchap->rchap_secret);
+ free(rchap->rchap_challenge);
+ free(rchap);
+}
diff --git a/usr.sbin/iscsid/discovery.c b/usr.sbin/iscsid/discovery.c
new file mode 100644
index 0000000..f5a0f66
--- /dev/null
+++ b/usr.sbin/iscsid/discovery.c
@@ -0,0 +1,220 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <stdbool.h>
+#include <string.h>
+#include <netinet/in.h>
+
+#include "iscsid.h"
+#include "iscsi_proto.h"
+
+static struct pdu *
+text_receive(struct connection *conn)
+{
+ struct pdu *response;
+ struct iscsi_bhs_text_response *bhstr;
+
+ response = pdu_new(conn);
+ pdu_receive(response);
+ if (response->pdu_bhs->bhs_opcode != ISCSI_BHS_OPCODE_TEXT_RESPONSE)
+ log_errx(1, "protocol error: received invalid opcode 0x%x",
+ response->pdu_bhs->bhs_opcode);
+ bhstr = (struct iscsi_bhs_text_response *)response->pdu_bhs;
+#if 0
+ if ((bhstr->bhstr_flags & BHSTR_FLAGS_FINAL) == 0)
+ log_errx(1, "received Text PDU without the \"F\" flag");
+#endif
+ /*
+ * XXX: Implement the C flag some day.
+ */
+ if ((bhstr->bhstr_flags & BHSTR_FLAGS_CONTINUE) != 0)
+ log_errx(1, "received Text PDU with unsupported \"C\" flag");
+ if (ntohl(bhstr->bhstr_statsn) != conn->conn_statsn + 1) {
+ log_errx(1, "received Text PDU with wrong StatSN: "
+ "is %u, should be %u", ntohl(bhstr->bhstr_statsn),
+ conn->conn_statsn + 1);
+ }
+ conn->conn_statsn = ntohl(bhstr->bhstr_statsn);
+
+ return (response);
+}
+
+static struct pdu *
+text_new_request(struct connection *conn)
+{
+ struct pdu *request;
+ struct iscsi_bhs_text_request *bhstr;
+
+ request = pdu_new(conn);
+ bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
+ bhstr->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_REQUEST |
+ ISCSI_BHS_OPCODE_IMMEDIATE;
+ bhstr->bhstr_flags = BHSTR_FLAGS_FINAL;
+ bhstr->bhstr_initiator_task_tag = 0;
+ bhstr->bhstr_target_transfer_tag = 0xffffffff;
+
+ bhstr->bhstr_initiator_task_tag = 0; /* XXX */
+ bhstr->bhstr_cmdsn = 0; /* XXX */
+ bhstr->bhstr_expstatsn = htonl(conn->conn_statsn + 1);
+
+ return (request);
+}
+
+static struct pdu *
+logout_receive(struct connection *conn)
+{
+ struct pdu *response;
+ struct iscsi_bhs_logout_response *bhslr;
+
+ response = pdu_new(conn);
+ pdu_receive(response);
+ if (response->pdu_bhs->bhs_opcode != ISCSI_BHS_OPCODE_LOGOUT_RESPONSE)
+ log_errx(1, "protocol error: received invalid opcode 0x%x",
+ response->pdu_bhs->bhs_opcode);
+ bhslr = (struct iscsi_bhs_logout_response *)response->pdu_bhs;
+ if (ntohs(bhslr->bhslr_response) != BHSLR_RESPONSE_CLOSED_SUCCESSFULLY)
+ log_warnx("received Logout Response with reason %d",
+ ntohs(bhslr->bhslr_response));
+ if (ntohl(bhslr->bhslr_statsn) != conn->conn_statsn + 1) {
+ log_errx(1, "received Logout PDU with wrong StatSN: "
+ "is %u, should be %u", ntohl(bhslr->bhslr_statsn),
+ conn->conn_statsn + 1);
+ }
+ conn->conn_statsn = ntohl(bhslr->bhslr_statsn);
+
+ return (response);
+}
+
+static struct pdu *
+logout_new_request(struct connection *conn)
+{
+ struct pdu *request;
+ struct iscsi_bhs_logout_request *bhslr;
+
+ request = pdu_new(conn);
+ bhslr = (struct iscsi_bhs_logout_request *)request->pdu_bhs;
+ bhslr->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_REQUEST |
+ ISCSI_BHS_OPCODE_IMMEDIATE;
+ bhslr->bhslr_reason = BHSLR_REASON_CLOSE_SESSION;
+ bhslr->bhslr_reason |= 0x80;
+ bhslr->bhslr_initiator_task_tag = 0; /* XXX */
+ bhslr->bhslr_cmdsn = 0; /* XXX */
+ bhslr->bhslr_expstatsn = htonl(conn->conn_statsn + 1);
+
+ return (request);
+}
+
+static void
+kernel_add(const struct connection *conn, const char *target)
+{
+ struct iscsi_session_add isa;
+ int error;
+
+ memset(&isa, 0, sizeof(isa));
+ memcpy(&isa.isa_conf, &conn->conn_conf, sizeof(isa.isa_conf));
+ strlcpy(isa.isa_conf.isc_target, target,
+ sizeof(isa.isa_conf.isc_target));
+ isa.isa_conf.isc_discovery = 0;
+ error = ioctl(conn->conn_iscsi_fd, ISCSISADD, &isa);
+ if (error != 0)
+ log_warn("failed to add %s: ISCSISADD", target);
+}
+
+static void
+kernel_remove(const struct connection *conn)
+{
+ struct iscsi_session_remove isr;
+ int error;
+
+ memset(&isr, 0, sizeof(isr));
+ isr.isr_session_id = conn->conn_session_id;
+ error = ioctl(conn->conn_iscsi_fd, ISCSISREMOVE, &isr);
+ if (error != 0)
+ log_warn("ISCSISREMOVE");
+}
+
+void
+discovery(struct connection *conn)
+{
+ struct pdu *request, *response;
+ struct keys *request_keys, *response_keys;
+ int i;
+
+ log_debugx("beginning discovery session");
+ request = text_new_request(conn);
+ request_keys = keys_new();
+ keys_add(request_keys, "SendTargets", "All");
+ keys_save(request_keys, request);
+ keys_delete(request_keys);
+ request_keys = NULL;
+ pdu_send(request);
+ pdu_delete(request);
+ request = NULL;
+
+ log_debugx("waiting for Text Response");
+ response = text_receive(conn);
+ response_keys = keys_new();
+ keys_load(response_keys, response);
+ for (i = 0; i < KEYS_MAX; i++) {
+ if (response_keys->keys_names[i] == NULL)
+ break;
+
+ if (strcmp(response_keys->keys_names[i], "TargetName") != 0)
+ continue;
+
+ log_debugx("adding target %s", response_keys->keys_values[i]);
+ /*
+ * XXX: Validate the target name?
+ */
+ kernel_add(conn, response_keys->keys_values[i]);
+ }
+ keys_delete(response_keys);
+ pdu_delete(response);
+
+ log_debugx("removing temporary discovery session");
+ kernel_remove(conn);
+
+ log_debugx("discovery done; logging out");
+ request = logout_new_request(conn);
+ pdu_send(request);
+ pdu_delete(request);
+ request = NULL;
+
+ log_debugx("waiting for Logout Response");
+ response = logout_receive(conn);
+ pdu_delete(response);
+
+ log_debugx("discovery session done");
+}
diff --git a/usr.sbin/iscsid/iscsid.8 b/usr.sbin/iscsid/iscsid.8
new file mode 100644
index 0000000..046a924
--- /dev/null
+++ b/usr.sbin/iscsid/iscsid.8
@@ -0,0 +1,115 @@
+.\" Copyright (c) 2012 The FreeBSD Foundation
+.\" All rights reserved.
+.\"
+.\" This software was 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 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 September 12, 2014
+.Dt ISCSID 8
+.Os
+.Sh NAME
+.Nm iscsid
+.Nd iSCSI initiator daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl P Ar pidfile
+.Op Fl d
+.Op Fl l Ar loglevel
+.Op Fl m Ar maxproc
+.Op Fl t Ar seconds
+.Sh DESCRIPTION
+The
+.Nm
+daemon is responsible for performing the Login Phase of iSCSI connections,
+as well as performing SendTargets discovery.
+.Pp
+Upon startup, the
+.Nm
+daemon opens the iSCSI initiator device file and waits for kernel requests.
+.Nm
+does not use any configuration files.
+All needed information is supplied by the kernel.
+.Pp
+When the
+.Nm
+daemon is not running, already established iSCSI connections continue
+to work.
+However, establishing new connections, or recovering existing ones in case
+of connection error, is not possible.
+.Pp
+The following options are available:
+.Bl -tag -width ".Fl P Ar pidfile"
+.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/iscsid.pid .
+.It Fl d
+Debug mode.
+The daemon sends verbose debug output to standard error, and does not
+put itself in the background.
+The daemon will also not fork and will exit after processing one connection.
+This option is only intended for debugging the initiator.
+.It Fl l Ar loglevel
+Specifies debug level.
+The default is 0.
+.It Fl m Ar maxproc
+Specifies limit for concurrently running child processes handling
+connections.
+The default is 30.
+Setting it to 0 disables the limit.
+.It Fl t Ar seconds
+Specifies timeout for login session, after which the connection
+will be forcibly terminated.
+The default is 60.
+Setting it to 0 disables the timeout.
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /var/run/iscsid.pid" -compact
+.It Pa /dev/iscsi
+The iSCSI initiator device file.
+.It Pa /var/run/iscsid.pid
+The default location of the
+.Nm
+PID file.
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility exits 0 on success, and >0 if an error occurs.
+.Sh SEE ALSO
+.Xr iscsi 4 ,
+.Xr iscsictl 8
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Fx 10.0 .
+.Sh AUTHORS
+The
+.Nm
+utility was developed by
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+under sponsorship from the FreeBSD Foundation.
diff --git a/usr.sbin/iscsid/iscsid.c b/usr.sbin/iscsid/iscsid.c
new file mode 100644
index 0000000..c1c8b1b
--- /dev/null
+++ b/usr.sbin/iscsid/iscsid.c
@@ -0,0 +1,603 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/socket.h>
+#include <sys/capsicum.h>
+#include <sys/wait.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libutil.h>
+
+#include "iscsid.h"
+
+static volatile bool sigalrm_received = false;
+
+static int nchildren = 0;
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: iscsid [-P pidfile][-d][-m maxproc][-t timeout]\n");
+ exit(1);
+}
+
+char *
+checked_strdup(const char *s)
+{
+ char *c;
+
+ c = strdup(s);
+ if (c == NULL)
+ log_err(1, "strdup");
+ return (c);
+}
+
+static void
+resolve_addr(const struct connection *conn, const char *address,
+ struct addrinfo **ai, bool initiator_side)
+{
+ struct addrinfo hints;
+ char *arg, *addr, *ch;
+ const char *port;
+ int error, colons = 0;
+
+ arg = checked_strdup(address);
+
+ if (arg[0] == '\0') {
+ fail(conn, "empty address");
+ log_errx(1, "empty address");
+ }
+ if (arg[0] == '[') {
+ /*
+ * IPv6 address in square brackets, perhaps with port.
+ */
+ arg++;
+ addr = strsep(&arg, "]");
+ if (arg == NULL) {
+ fail(conn, "malformed address");
+ log_errx(1, "malformed address %s", address);
+ }
+ if (arg[0] == '\0') {
+ port = NULL;
+ } else if (arg[0] == ':') {
+ port = arg + 1;
+ } else {
+ fail(conn, "malformed address");
+ log_errx(1, "malformed address %s", address);
+ }
+ } else {
+ /*
+ * Either IPv6 address without brackets - and without
+ * a port - or IPv4 address. Just count the colons.
+ */
+ for (ch = arg; *ch != '\0'; ch++) {
+ if (*ch == ':')
+ colons++;
+ }
+ if (colons > 1) {
+ addr = arg;
+ port = NULL;
+ } else {
+ addr = strsep(&arg, ":");
+ if (arg == NULL)
+ port = NULL;
+ else
+ port = arg;
+ }
+ }
+
+ if (port == NULL && !initiator_side)
+ port = "3260";
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV;
+ if (initiator_side)
+ hints.ai_flags |= AI_PASSIVE;
+
+ error = getaddrinfo(addr, port, &hints, ai);
+ if (error != 0) {
+ fail(conn, gai_strerror(error));
+ log_errx(1, "getaddrinfo for %s failed: %s",
+ address, gai_strerror(error));
+ }
+}
+
+static struct connection *
+connection_new(int iscsi_fd, const struct iscsi_daemon_request *request)
+{
+ struct connection *conn;
+ struct addrinfo *from_ai, *to_ai;
+ const char *from_addr, *to_addr;
+#ifdef ICL_KERNEL_PROXY
+ struct iscsi_daemon_connect idc;
+#endif
+ int error, sockbuf;
+
+ conn = calloc(1, sizeof(*conn));
+ if (conn == NULL)
+ log_err(1, "calloc");
+
+ /*
+ * Default values, from RFC 3720, section 12.
+ */
+ conn->conn_header_digest = CONN_DIGEST_NONE;
+ conn->conn_data_digest = CONN_DIGEST_NONE;
+ conn->conn_initial_r2t = true;
+ conn->conn_immediate_data = true;
+ conn->conn_max_data_segment_length = 8192;
+ conn->conn_max_burst_length = 262144;
+ conn->conn_first_burst_length = 65536;
+ conn->conn_iscsi_fd = iscsi_fd;
+
+ conn->conn_session_id = request->idr_session_id;
+ memcpy(&conn->conn_conf, &request->idr_conf, sizeof(conn->conn_conf));
+ memcpy(&conn->conn_isid, &request->idr_isid, sizeof(conn->conn_isid));
+ conn->conn_tsih = request->idr_tsih;
+ memcpy(&conn->conn_limits, &request->idr_limits, sizeof(conn->conn_limits));
+
+ from_addr = conn->conn_conf.isc_initiator_addr;
+ to_addr = conn->conn_conf.isc_target_addr;
+
+ if (from_addr[0] != '\0')
+ resolve_addr(conn, from_addr, &from_ai, true);
+ else
+ from_ai = NULL;
+
+ resolve_addr(conn, to_addr, &to_ai, false);
+
+#ifdef ICL_KERNEL_PROXY
+ if (conn->conn_conf.isc_iser) {
+ memset(&idc, 0, sizeof(idc));
+ idc.idc_session_id = conn->conn_session_id;
+ if (conn->conn_conf.isc_iser)
+ idc.idc_iser = 1;
+ idc.idc_domain = to_ai->ai_family;
+ idc.idc_socktype = to_ai->ai_socktype;
+ idc.idc_protocol = to_ai->ai_protocol;
+ if (from_ai != NULL) {
+ idc.idc_from_addr = from_ai->ai_addr;
+ idc.idc_from_addrlen = from_ai->ai_addrlen;
+ }
+ idc.idc_to_addr = to_ai->ai_addr;
+ idc.idc_to_addrlen = to_ai->ai_addrlen;
+
+ log_debugx("connecting to %s using ICL kernel proxy", to_addr);
+ error = ioctl(iscsi_fd, ISCSIDCONNECT, &idc);
+ if (error != 0) {
+ fail(conn, strerror(errno));
+ log_err(1, "failed to connect to %s "
+ "using ICL kernel proxy: ISCSIDCONNECT", to_addr);
+ }
+
+ return (conn);
+ }
+#endif /* ICL_KERNEL_PROXY */
+
+ if (conn->conn_conf.isc_iser) {
+ fail(conn, "iSER not supported");
+ log_errx(1, "iscsid(8) compiled without ICL_KERNEL_PROXY "
+ "does not support iSER");
+ }
+
+ conn->conn_socket = socket(to_ai->ai_family, to_ai->ai_socktype,
+ to_ai->ai_protocol);
+ if (conn->conn_socket < 0) {
+ fail(conn, strerror(errno));
+ log_err(1, "failed to create socket for %s", from_addr);
+ }
+ sockbuf = SOCKBUF_SIZE;
+ if (setsockopt(conn->conn_socket, SOL_SOCKET, SO_RCVBUF,
+ &sockbuf, sizeof(sockbuf)) == -1)
+ log_warn("setsockopt(SO_RCVBUF) failed");
+ sockbuf = SOCKBUF_SIZE;
+ if (setsockopt(conn->conn_socket, SOL_SOCKET, SO_SNDBUF,
+ &sockbuf, sizeof(sockbuf)) == -1)
+ log_warn("setsockopt(SO_SNDBUF) failed");
+ if (from_ai != NULL) {
+ error = bind(conn->conn_socket, from_ai->ai_addr,
+ from_ai->ai_addrlen);
+ if (error != 0) {
+ fail(conn, strerror(errno));
+ log_err(1, "failed to bind to %s", from_addr);
+ }
+ }
+ log_debugx("connecting to %s", to_addr);
+ error = connect(conn->conn_socket, to_ai->ai_addr, to_ai->ai_addrlen);
+ if (error != 0) {
+ fail(conn, strerror(errno));
+ log_err(1, "failed to connect to %s", to_addr);
+ }
+
+ return (conn);
+}
+
+static void
+handoff(struct connection *conn)
+{
+ struct iscsi_daemon_handoff idh;
+ int error;
+
+ log_debugx("handing off connection to the kernel");
+
+ memset(&idh, 0, sizeof(idh));
+ idh.idh_session_id = conn->conn_session_id;
+ idh.idh_socket = conn->conn_socket;
+ strlcpy(idh.idh_target_alias, conn->conn_target_alias,
+ sizeof(idh.idh_target_alias));
+ idh.idh_tsih = conn->conn_tsih;
+ idh.idh_statsn = conn->conn_statsn;
+ idh.idh_header_digest = conn->conn_header_digest;
+ idh.idh_data_digest = conn->conn_data_digest;
+ idh.idh_initial_r2t = conn->conn_initial_r2t;
+ idh.idh_immediate_data = conn->conn_immediate_data;
+ idh.idh_max_data_segment_length = conn->conn_max_data_segment_length;
+ idh.idh_max_burst_length = conn->conn_max_burst_length;
+ idh.idh_first_burst_length = conn->conn_first_burst_length;
+
+ error = ioctl(conn->conn_iscsi_fd, ISCSIDHANDOFF, &idh);
+ if (error != 0)
+ log_err(1, "ISCSIDHANDOFF");
+}
+
+void
+fail(const struct connection *conn, const char *reason)
+{
+ struct iscsi_daemon_fail idf;
+ int error;
+
+ memset(&idf, 0, sizeof(idf));
+ idf.idf_session_id = conn->conn_session_id;
+ strlcpy(idf.idf_reason, reason, sizeof(idf.idf_reason));
+
+ error = ioctl(conn->conn_iscsi_fd, ISCSIDFAIL, &idf);
+ if (error != 0)
+ log_err(1, "ISCSIDFAIL");
+}
+
+/*
+ * XXX: I CANT INTO LATIN
+ */
+static void
+capsicate(struct connection *conn)
+{
+ int error;
+ cap_rights_t rights;
+#ifdef ICL_KERNEL_PROXY
+ const unsigned long cmds[] = { ISCSIDCONNECT, ISCSIDSEND, ISCSIDRECEIVE,
+ ISCSIDHANDOFF, ISCSIDFAIL, ISCSISADD, ISCSISREMOVE, ISCSISMODIFY };
+#else
+ const unsigned long cmds[] = { ISCSIDHANDOFF, ISCSIDFAIL, ISCSISADD,
+ ISCSISREMOVE, ISCSISMODIFY };
+#endif
+
+ cap_rights_init(&rights, CAP_IOCTL);
+ error = cap_rights_limit(conn->conn_iscsi_fd, &rights);
+ if (error != 0 && errno != ENOSYS)
+ log_err(1, "cap_rights_limit");
+
+ error = cap_ioctls_limit(conn->conn_iscsi_fd, cmds,
+ sizeof(cmds) / sizeof(cmds[0]));
+ if (error != 0 && errno != ENOSYS)
+ log_err(1, "cap_ioctls_limit");
+
+ error = cap_enter();
+ if (error != 0 && errno != ENOSYS)
+ log_err(1, "cap_enter");
+
+ if (cap_sandboxed())
+ log_debugx("Capsicum capability mode enabled");
+ else
+ log_warnx("Capsicum capability mode not supported");
+}
+
+bool
+timed_out(void)
+{
+
+ return (sigalrm_received);
+}
+
+static void
+sigalrm_handler(int dummy __unused)
+{
+ /*
+ * It would be easiest to just log an error and exit. We can't
+ * do this, though, because log_errx() is not signal safe, since
+ * it calls syslog(3). Instead, set a flag checked by pdu_send()
+ * and pdu_receive(), to call log_errx() there. Should they fail
+ * to notice, we'll exit here one second later.
+ */
+ if (sigalrm_received) {
+ /*
+ * Oh well. Just give up and quit.
+ */
+ _exit(2);
+ }
+
+ sigalrm_received = true;
+}
+
+static void
+set_timeout(int timeout)
+{
+ struct sigaction sa;
+ struct itimerval itv;
+ int error;
+
+ if (timeout <= 0) {
+ log_debugx("session timeout disabled");
+ return;
+ }
+
+ bzero(&sa, sizeof(sa));
+ sa.sa_handler = sigalrm_handler;
+ sigfillset(&sa.sa_mask);
+ error = sigaction(SIGALRM, &sa, NULL);
+ if (error != 0)
+ log_err(1, "sigaction");
+
+ /*
+ * First SIGALRM will arive after conf_timeout seconds.
+ * If we do nothing, another one will arrive a second later.
+ */
+ bzero(&itv, sizeof(itv));
+ itv.it_interval.tv_sec = 1;
+ itv.it_value.tv_sec = timeout;
+
+ log_debugx("setting session timeout to %d seconds",
+ timeout);
+ error = setitimer(ITIMER_REAL, &itv, NULL);
+ if (error != 0)
+ log_err(1, "setitimer");
+}
+
+static void
+sigchld_handler(int dummy __unused)
+{
+
+ /*
+ * The only purpose of this handler is to make SIGCHLD
+ * interrupt the ISCSIDWAIT ioctl(2), so we can call
+ * wait_for_children().
+ */
+}
+
+static void
+register_sigchld(void)
+{
+ struct sigaction sa;
+ int error;
+
+ bzero(&sa, sizeof(sa));
+ sa.sa_handler = sigchld_handler;
+ sigfillset(&sa.sa_mask);
+ error = sigaction(SIGCHLD, &sa, NULL);
+ if (error != 0)
+ log_err(1, "sigaction");
+
+}
+
+static void
+handle_request(int iscsi_fd, const struct iscsi_daemon_request *request, int timeout)
+{
+ struct connection *conn;
+
+ log_set_peer_addr(request->idr_conf.isc_target_addr);
+ if (request->idr_conf.isc_target[0] != '\0') {
+ log_set_peer_name(request->idr_conf.isc_target);
+ setproctitle("%s (%s)", request->idr_conf.isc_target_addr, request->idr_conf.isc_target);
+ } else {
+ setproctitle("%s", request->idr_conf.isc_target_addr);
+ }
+
+ conn = connection_new(iscsi_fd, request);
+ set_timeout(timeout);
+ capsicate(conn);
+ login(conn);
+ if (conn->conn_conf.isc_discovery != 0)
+ discovery(conn);
+ else
+ handoff(conn);
+
+ log_debugx("nothing more to do; exiting");
+ exit (0);
+}
+
+static int
+wait_for_children(bool block)
+{
+ pid_t pid;
+ int status;
+ int num = 0;
+
+ for (;;) {
+ /*
+ * If "block" is true, wait for at least one process.
+ */
+ if (block && num == 0)
+ pid = wait4(-1, &status, 0, NULL);
+ else
+ pid = wait4(-1, &status, WNOHANG, NULL);
+ if (pid <= 0)
+ break;
+ if (WIFSIGNALED(status)) {
+ log_warnx("child process %d terminated with signal %d",
+ pid, WTERMSIG(status));
+ } else if (WEXITSTATUS(status) != 0) {
+ log_warnx("child process %d terminated with exit status %d",
+ pid, WEXITSTATUS(status));
+ } else {
+ log_debugx("child process %d terminated gracefully", pid);
+ }
+ num++;
+ }
+
+ return (num);
+}
+
+int
+main(int argc, char **argv)
+{
+ int ch, debug = 0, error, iscsi_fd, maxproc = 30, retval, saved_errno,
+ timeout = 60;
+ bool dont_daemonize = false;
+ struct pidfh *pidfh;
+ pid_t pid, otherpid;
+ const char *pidfile_path = DEFAULT_PIDFILE;
+ struct iscsi_daemon_request request;
+
+ while ((ch = getopt(argc, argv, "P:dl:m:t:")) != -1) {
+ switch (ch) {
+ case 'P':
+ pidfile_path = optarg;
+ break;
+ case 'd':
+ dont_daemonize = true;
+ debug++;
+ break;
+ case 'l':
+ debug = atoi(optarg);
+ break;
+ case 'm':
+ maxproc = atoi(optarg);
+ break;
+ case 't':
+ timeout = atoi(optarg);
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ if (argc != 0)
+ usage();
+
+ log_init(debug);
+
+ pidfh = pidfile_open(pidfile_path, 0600, &otherpid);
+ if (pidfh == NULL) {
+ if (errno == EEXIST)
+ log_errx(1, "daemon already running, pid: %jd.",
+ (intmax_t)otherpid);
+ log_err(1, "cannot open or create pidfile \"%s\"",
+ pidfile_path);
+ }
+
+ iscsi_fd = open(ISCSI_PATH, O_RDWR);
+ if (iscsi_fd < 0 && errno == ENOENT) {
+ saved_errno = errno;
+ retval = kldload("iscsi");
+ if (retval != -1)
+ iscsi_fd = open(ISCSI_PATH, O_RDWR);
+ else
+ errno = saved_errno;
+ }
+ if (iscsi_fd < 0)
+ log_err(1, "failed to open %s", ISCSI_PATH);
+
+ if (dont_daemonize == false) {
+ if (daemon(0, 0) == -1) {
+ log_warn("cannot daemonize");
+ pidfile_remove(pidfh);
+ exit(1);
+ }
+ }
+
+ pidfile_write(pidfh);
+
+ register_sigchld();
+
+ for (;;) {
+ log_debugx("waiting for request from the kernel");
+
+ memset(&request, 0, sizeof(request));
+ error = ioctl(iscsi_fd, ISCSIDWAIT, &request);
+ if (error != 0) {
+ if (errno == EINTR) {
+ nchildren -= wait_for_children(false);
+ assert(nchildren >= 0);
+ continue;
+ }
+
+ log_err(1, "ISCSIDWAIT");
+ }
+
+ if (dont_daemonize) {
+ log_debugx("not forking due to -d flag; "
+ "will exit after servicing a single request");
+ } else {
+ nchildren -= wait_for_children(false);
+ assert(nchildren >= 0);
+
+ while (maxproc > 0 && nchildren >= maxproc) {
+ log_debugx("maxproc limit of %d child processes hit; "
+ "waiting for child process to exit", maxproc);
+ nchildren -= wait_for_children(true);
+ assert(nchildren >= 0);
+ }
+ log_debugx("incoming connection; forking child process #%d",
+ nchildren);
+ nchildren++;
+
+ pid = fork();
+ if (pid < 0)
+ log_err(1, "fork");
+ if (pid > 0)
+ continue;
+ }
+
+ pidfile_close(pidfh);
+ handle_request(iscsi_fd, &request, timeout);
+ }
+
+ return (0);
+}
diff --git a/usr.sbin/iscsid/iscsid.h b/usr.sbin/iscsid/iscsid.h
new file mode 100644
index 0000000..0c37ca3
--- /dev/null
+++ b/usr.sbin/iscsid/iscsid.h
@@ -0,0 +1,149 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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$
+ */
+
+#ifndef ISCSID_H
+#define ISCSID_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <iscsi_ioctl.h>
+
+#define DEFAULT_PIDFILE "/var/run/iscsid.pid"
+
+#define CONN_DIGEST_NONE 0
+#define CONN_DIGEST_CRC32C 1
+
+#define CONN_MUTUAL_CHALLENGE_LEN 1024
+#define SOCKBUF_SIZE 1048576
+
+struct connection {
+ int conn_iscsi_fd;
+ int conn_socket;
+ unsigned int conn_session_id;
+ struct iscsi_session_conf conn_conf;
+ struct iscsi_session_limits conn_limits;
+ char conn_target_alias[ISCSI_ADDR_LEN];
+ uint8_t conn_isid[6];
+ uint16_t conn_tsih;
+ uint32_t conn_statsn;
+ int conn_header_digest;
+ int conn_data_digest;
+ bool conn_initial_r2t;
+ bool conn_immediate_data;
+ size_t conn_max_data_segment_length;
+ size_t conn_max_burst_length;
+ size_t conn_first_burst_length;
+ struct chap *conn_mutual_chap;
+};
+
+struct pdu {
+ struct connection *pdu_connection;
+ struct iscsi_bhs *pdu_bhs;
+ char *pdu_data;
+ size_t pdu_data_len;
+};
+
+#define KEYS_MAX 1024
+
+struct keys {
+ char *keys_names[KEYS_MAX];
+ char *keys_values[KEYS_MAX];
+ char *keys_data;
+ size_t keys_data_len;
+};
+
+#define CHAP_CHALLENGE_LEN 1024
+#define CHAP_DIGEST_LEN 16 /* Equal to MD5 digest size. */
+
+struct chap {
+ unsigned char chap_id;
+ char chap_challenge[CHAP_CHALLENGE_LEN];
+ char chap_response[CHAP_DIGEST_LEN];
+};
+
+struct rchap {
+ char *rchap_secret;
+ unsigned char rchap_id;
+ void *rchap_challenge;
+ size_t rchap_challenge_len;
+};
+
+struct chap *chap_new(void);
+char *chap_get_id(const struct chap *chap);
+char *chap_get_challenge(const struct chap *chap);
+int chap_receive(struct chap *chap, const char *response);
+int chap_authenticate(struct chap *chap,
+ const char *secret);
+void chap_delete(struct chap *chap);
+
+struct rchap *rchap_new(const char *secret);
+int rchap_receive(struct rchap *rchap,
+ const char *id, const char *challenge);
+char *rchap_get_response(struct rchap *rchap);
+void rchap_delete(struct rchap *rchap);
+
+struct keys *keys_new(void);
+void keys_delete(struct keys *key);
+void keys_load(struct keys *keys, const struct pdu *pdu);
+void keys_save(struct keys *keys, struct pdu *pdu);
+const char *keys_find(struct keys *keys, const char *name);
+void keys_add(struct keys *keys,
+ const char *name, const char *value);
+void keys_add_int(struct keys *keys,
+ const char *name, int value);
+
+struct pdu *pdu_new(struct connection *ic);
+struct pdu *pdu_new_response(struct pdu *request);
+void pdu_receive(struct pdu *request);
+void pdu_send(struct pdu *response);
+void pdu_delete(struct pdu *ip);
+
+void login(struct connection *ic);
+
+void discovery(struct connection *ic);
+
+void log_init(int level);
+void log_set_peer_name(const char *name);
+void log_set_peer_addr(const char *addr);
+void log_err(int, const char *, ...)
+ __dead2 __printflike(2, 3);
+void log_errx(int, const char *, ...)
+ __dead2 __printflike(2, 3);
+void log_warn(const char *, ...) __printflike(1, 2);
+void log_warnx(const char *, ...) __printflike(1, 2);
+void log_debugx(const char *, ...) __printflike(1, 2);
+
+char *checked_strdup(const char *);
+bool timed_out(void);
+void fail(const struct connection *, const char *);
+
+#endif /* !ISCSID_H */
diff --git a/usr.sbin/iscsid/keys.c b/usr.sbin/iscsid/keys.c
new file mode 100644
index 0000000..8ac4788
--- /dev/null
+++ b/usr.sbin/iscsid/keys.c
@@ -0,0 +1,198 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "iscsid.h"
+
+struct keys *
+keys_new(void)
+{
+ struct keys *keys;
+
+ keys = calloc(sizeof(*keys), 1);
+ if (keys == NULL)
+ log_err(1, "calloc");
+
+ return (keys);
+}
+
+void
+keys_delete(struct keys *keys)
+{
+
+ free(keys->keys_data);
+ free(keys);
+}
+
+void
+keys_load(struct keys *keys, const struct pdu *pdu)
+{
+ int i;
+ char *pair;
+ size_t pair_len;
+
+ if (pdu->pdu_data_len == 0)
+ return;
+
+ if (pdu->pdu_data[pdu->pdu_data_len - 1] != '\0')
+ log_errx(1, "protocol error: key not NULL-terminated\n");
+
+ assert(keys->keys_data == NULL);
+ keys->keys_data_len = pdu->pdu_data_len;
+ keys->keys_data = malloc(keys->keys_data_len);
+ if (keys->keys_data == NULL)
+ log_err(1, "malloc");
+ memcpy(keys->keys_data, pdu->pdu_data, keys->keys_data_len);
+
+ /*
+ * XXX: Review this carefully.
+ */
+ pair = keys->keys_data;
+ for (i = 0;; i++) {
+ if (i >= KEYS_MAX)
+ log_errx(1, "too many keys received");
+
+ pair_len = strlen(pair);
+
+ keys->keys_values[i] = pair;
+ keys->keys_names[i] = strsep(&keys->keys_values[i], "=");
+ if (keys->keys_names[i] == NULL || keys->keys_values[i] == NULL)
+ log_errx(1, "malformed keys");
+ log_debugx("key received: \"%s=%s\"",
+ keys->keys_names[i], keys->keys_values[i]);
+
+ pair += pair_len + 1; /* +1 to skip the terminating '\0'. */
+ if (pair == keys->keys_data + keys->keys_data_len)
+ break;
+ assert(pair < keys->keys_data + keys->keys_data_len);
+ }
+}
+
+void
+keys_save(struct keys *keys, struct pdu *pdu)
+{
+ char *data;
+ size_t len;
+ int i;
+
+ /*
+ * XXX: Not particularly efficient.
+ */
+ len = 0;
+ for (i = 0; i < KEYS_MAX; i++) {
+ if (keys->keys_names[i] == NULL)
+ break;
+ /*
+ * +1 for '=', +1 for '\0'.
+ */
+ len += strlen(keys->keys_names[i]) +
+ strlen(keys->keys_values[i]) + 2;
+ }
+
+ if (len == 0)
+ return;
+
+ data = malloc(len);
+ if (data == NULL)
+ log_err(1, "malloc");
+
+ pdu->pdu_data = data;
+ pdu->pdu_data_len = len;
+
+ for (i = 0; i < KEYS_MAX; i++) {
+ if (keys->keys_names[i] == NULL)
+ break;
+ data += sprintf(data, "%s=%s",
+ keys->keys_names[i], keys->keys_values[i]);
+ data += 1; /* for '\0'. */
+ }
+}
+
+const char *
+keys_find(struct keys *keys, const char *name)
+{
+ int i;
+
+ /*
+ * Note that we don't handle duplicated key names here,
+ * as they are not supposed to happen in requests, and if they do,
+ * it's an initiator error.
+ */
+ for (i = 0; i < KEYS_MAX; i++) {
+ if (keys->keys_names[i] == NULL)
+ return (NULL);
+ if (strcmp(keys->keys_names[i], name) == 0)
+ return (keys->keys_values[i]);
+ }
+ return (NULL);
+}
+
+void
+keys_add(struct keys *keys, const char *name, const char *value)
+{
+ int i;
+
+ log_debugx("key to send: \"%s=%s\"", name, value);
+
+ /*
+ * Note that we don't check for duplicates here, as they are perfectly
+ * fine in responses, e.g. the "TargetName" keys in discovery sesion
+ * response.
+ */
+ for (i = 0; i < KEYS_MAX; i++) {
+ if (keys->keys_names[i] == NULL) {
+ keys->keys_names[i] = checked_strdup(name);
+ keys->keys_values[i] = checked_strdup(value);
+ return;
+ }
+ }
+ log_errx(1, "too many keys");
+}
+
+void
+keys_add_int(struct keys *keys, const char *name, int value)
+{
+ char *str;
+ int ret;
+
+ ret = asprintf(&str, "%d", value);
+ if (ret <= 0)
+ log_err(1, "asprintf");
+
+ keys_add(keys, name, str);
+ free(str);
+}
diff --git a/usr.sbin/iscsid/log.c b/usr.sbin/iscsid/log.c
new file mode 100644
index 0000000..ea7755f
--- /dev/null
+++ b/usr.sbin/iscsid/log.c
@@ -0,0 +1,198 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <vis.h>
+
+#include "iscsid.h"
+
+static int log_level = 0;
+static char *peer_name = NULL;
+static char *peer_addr = NULL;
+
+#define MSGBUF_LEN 1024
+
+void
+log_init(int level)
+{
+
+ log_level = level;
+ openlog(getprogname(), LOG_NDELAY | LOG_PID, LOG_DAEMON);
+}
+
+void
+log_set_peer_name(const char *name)
+{
+
+ /*
+ * XXX: Turn it into assertion?
+ */
+ if (peer_name != NULL)
+ log_errx(1, "%s called twice", __func__);
+ if (peer_addr == NULL)
+ log_errx(1, "%s called before log_set_peer_addr", __func__);
+
+ peer_name = checked_strdup(name);
+}
+
+void
+log_set_peer_addr(const char *addr)
+{
+
+ /*
+ * XXX: Turn it into assertion?
+ */
+ if (peer_addr != NULL)
+ log_errx(1, "%s called twice", __func__);
+
+ peer_addr = checked_strdup(addr);
+}
+
+static void
+log_common(int priority, int log_errno, const char *fmt, va_list ap)
+{
+ static char msgbuf[MSGBUF_LEN];
+ static char msgbuf_strvised[MSGBUF_LEN * 4 + 1];
+ int ret;
+
+ ret = vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
+ if (ret < 0) {
+ fprintf(stderr, "%s: snprintf failed", getprogname());
+ syslog(LOG_CRIT, "snprintf failed");
+ exit(1);
+ }
+
+ ret = strnvis(msgbuf_strvised, sizeof(msgbuf_strvised), msgbuf, VIS_NL);
+ if (ret < 0) {
+ fprintf(stderr, "%s: strnvis failed", getprogname());
+ syslog(LOG_CRIT, "strnvis failed");
+ exit(1);
+ }
+
+ if (log_errno == -1) {
+ if (peer_name != NULL) {
+ fprintf(stderr, "%s: %s (%s): %s\n", getprogname(),
+ peer_addr, peer_name, msgbuf_strvised);
+ syslog(priority, "%s (%s): %s",
+ peer_addr, peer_name, msgbuf_strvised);
+ } else if (peer_addr != NULL) {
+ fprintf(stderr, "%s: %s: %s\n", getprogname(),
+ peer_addr, msgbuf_strvised);
+ syslog(priority, "%s: %s",
+ peer_addr, msgbuf_strvised);
+ } else {
+ fprintf(stderr, "%s: %s\n", getprogname(), msgbuf_strvised);
+ syslog(priority, "%s", msgbuf_strvised);
+ }
+
+ } else {
+ if (peer_name != NULL) {
+ fprintf(stderr, "%s: %s (%s): %s: %s\n", getprogname(),
+ peer_addr, peer_name, msgbuf_strvised, strerror(errno));
+ syslog(priority, "%s (%s): %s: %s",
+ peer_addr, peer_name, msgbuf_strvised, strerror(errno));
+ } else if (peer_addr != NULL) {
+ fprintf(stderr, "%s: %s: %s: %s\n", getprogname(),
+ peer_addr, msgbuf_strvised, strerror(errno));
+ syslog(priority, "%s: %s: %s",
+ peer_addr, msgbuf_strvised, strerror(errno));
+ } else {
+ fprintf(stderr, "%s: %s: %s\n", getprogname(),
+ msgbuf_strvised, strerror(errno));
+ syslog(priority, "%s: %s",
+ msgbuf_strvised, strerror(errno));
+ }
+ }
+}
+
+void
+log_err(int eval, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ log_common(LOG_CRIT, errno, fmt, ap);
+ va_end(ap);
+
+ exit(eval);
+}
+
+void
+log_errx(int eval, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ log_common(LOG_CRIT, -1, fmt, ap);
+ va_end(ap);
+
+ exit(eval);
+}
+
+void
+log_warn(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ log_common(LOG_WARNING, errno, fmt, ap);
+ va_end(ap);
+}
+
+void
+log_warnx(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ log_common(LOG_WARNING, -1, fmt, ap);
+ va_end(ap);
+}
+
+void
+log_debugx(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (log_level == 0)
+ return;
+
+ va_start(ap, fmt);
+ log_common(LOG_DEBUG, -1, fmt, ap);
+ va_end(ap);
+}
diff --git a/usr.sbin/iscsid/login.c b/usr.sbin/iscsid/login.c
new file mode 100644
index 0000000..ed0b9bf
--- /dev/null
+++ b/usr.sbin/iscsid/login.c
@@ -0,0 +1,831 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/in.h>
+
+#include "iscsid.h"
+#include "iscsi_proto.h"
+
+static int
+login_nsg(const struct pdu *response)
+{
+ struct iscsi_bhs_login_response *bhslr;
+
+ bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs;
+
+ return (bhslr->bhslr_flags & 0x03);
+}
+
+static void
+login_set_nsg(struct pdu *request, int nsg)
+{
+ struct iscsi_bhs_login_request *bhslr;
+
+ assert(nsg == BHSLR_STAGE_SECURITY_NEGOTIATION ||
+ nsg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION ||
+ nsg == BHSLR_STAGE_FULL_FEATURE_PHASE);
+
+ bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
+
+ bhslr->bhslr_flags &= 0xFC;
+ bhslr->bhslr_flags |= nsg;
+}
+
+static void
+login_set_csg(struct pdu *request, int csg)
+{
+ struct iscsi_bhs_login_request *bhslr;
+
+ assert(csg == BHSLR_STAGE_SECURITY_NEGOTIATION ||
+ csg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION ||
+ csg == BHSLR_STAGE_FULL_FEATURE_PHASE);
+
+ bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
+
+ bhslr->bhslr_flags &= 0xF3;
+ bhslr->bhslr_flags |= csg << 2;
+}
+
+static const char *
+login_target_error_str(int class, int detail)
+{
+ static char msg[128];
+
+ /*
+ * RFC 3270, 10.13.5. Status-Class and Status-Detail
+ */
+ switch (class) {
+ case 0x01:
+ switch (detail) {
+ case 0x01:
+ return ("Target moved temporarily");
+ case 0x02:
+ return ("Target moved permanently");
+ default:
+ snprintf(msg, sizeof(msg), "unknown redirection; "
+ "Status-Class 0x%x, Status-Detail 0x%x",
+ class, detail);
+ return (msg);
+ }
+ case 0x02:
+ switch (detail) {
+ case 0x00:
+ return ("Initiator error");
+ case 0x01:
+ return ("Authentication failure");
+ case 0x02:
+ return ("Authorization failure");
+ case 0x03:
+ return ("Not found");
+ case 0x04:
+ return ("Target removed");
+ case 0x05:
+ return ("Unsupported version");
+ case 0x06:
+ return ("Too many connections");
+ case 0x07:
+ return ("Missing parameter");
+ case 0x08:
+ return ("Can't include in session");
+ case 0x09:
+ return ("Session type not supported");
+ case 0x0a:
+ return ("Session does not exist");
+ case 0x0b:
+ return ("Invalid during login");
+ default:
+ snprintf(msg, sizeof(msg), "unknown initiator error; "
+ "Status-Class 0x%x, Status-Detail 0x%x",
+ class, detail);
+ return (msg);
+ }
+ case 0x03:
+ switch (detail) {
+ case 0x00:
+ return ("Target error");
+ case 0x01:
+ return ("Service unavailable");
+ case 0x02:
+ return ("Out of resources");
+ default:
+ snprintf(msg, sizeof(msg), "unknown target error; "
+ "Status-Class 0x%x, Status-Detail 0x%x",
+ class, detail);
+ return (msg);
+ }
+ default:
+ snprintf(msg, sizeof(msg), "unknown error; "
+ "Status-Class 0x%x, Status-Detail 0x%x",
+ class, detail);
+ return (msg);
+ }
+}
+
+static void
+kernel_modify(const struct connection *conn, const char *target_address)
+{
+ struct iscsi_session_modify ism;
+ int error;
+
+ memset(&ism, 0, sizeof(ism));
+ ism.ism_session_id = conn->conn_session_id;
+ memcpy(&ism.ism_conf, &conn->conn_conf, sizeof(ism.ism_conf));
+ strlcpy(ism.ism_conf.isc_target_addr, target_address,
+ sizeof(ism.ism_conf.isc_target));
+ error = ioctl(conn->conn_iscsi_fd, ISCSISMODIFY, &ism);
+ if (error != 0) {
+ log_err(1, "failed to redirect to %s: ISCSISMODIFY",
+ target_address);
+ }
+}
+
+/*
+ * XXX: The way it works is suboptimal; what should happen is described
+ * in draft-gilligan-iscsi-fault-tolerance-00. That, however, would
+ * be much more complicated: we would need to keep "dependencies"
+ * for sessions, so that, in case described in draft and using draft
+ * terminology, we would have three sessions: one for discovery,
+ * one for initial target portal, and one for redirect portal.
+ * This would allow us to "backtrack" on connection failure,
+ * as described in draft.
+ */
+static void
+login_handle_redirection(struct connection *conn, struct pdu *response)
+{
+ struct iscsi_bhs_login_response *bhslr;
+ struct keys *response_keys;
+ const char *target_address;
+
+ bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs;
+ assert (bhslr->bhslr_status_class == 1);
+
+ response_keys = keys_new();
+ keys_load(response_keys, response);
+
+ target_address = keys_find(response_keys, "TargetAddress");
+ if (target_address == NULL)
+ log_errx(1, "received redirection without TargetAddress");
+ if (target_address[0] == '\0')
+ log_errx(1, "received redirection with empty TargetAddress");
+ if (strlen(target_address) >=
+ sizeof(conn->conn_conf.isc_target_addr) - 1)
+ log_errx(1, "received TargetAddress is too long");
+
+ log_debugx("received redirection to \"%s\"", target_address);
+ kernel_modify(conn, target_address);
+ keys_delete(response_keys);
+}
+
+static struct pdu *
+login_receive(struct connection *conn)
+{
+ struct pdu *response;
+ struct iscsi_bhs_login_response *bhslr;
+ const char *errorstr;
+ static bool initial = true;
+
+ response = pdu_new(conn);
+ pdu_receive(response);
+ if (response->pdu_bhs->bhs_opcode != ISCSI_BHS_OPCODE_LOGIN_RESPONSE) {
+ log_errx(1, "protocol error: received invalid opcode 0x%x",
+ response->pdu_bhs->bhs_opcode);
+ }
+ bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs;
+ /*
+ * XXX: Implement the C flag some day.
+ */
+ if ((bhslr->bhslr_flags & BHSLR_FLAGS_CONTINUE) != 0)
+ log_errx(1, "received Login PDU with unsupported \"C\" flag");
+ if (bhslr->bhslr_version_max != 0x00)
+ log_errx(1, "received Login PDU with unsupported "
+ "Version-max 0x%x", bhslr->bhslr_version_max);
+ if (bhslr->bhslr_version_active != 0x00)
+ log_errx(1, "received Login PDU with unsupported "
+ "Version-active 0x%x", bhslr->bhslr_version_active);
+ if (bhslr->bhslr_status_class == 1) {
+ login_handle_redirection(conn, response);
+ log_debugx("redirection handled; exiting");
+ exit(0);
+ }
+ if (bhslr->bhslr_status_class != 0) {
+ errorstr = login_target_error_str(bhslr->bhslr_status_class,
+ bhslr->bhslr_status_detail);
+ fail(conn, errorstr);
+ log_errx(1, "target returned error: %s", errorstr);
+ }
+ if (initial == false &&
+ ntohl(bhslr->bhslr_statsn) != conn->conn_statsn + 1) {
+ /*
+ * It's a warning, not an error, to work around what seems
+ * to be bug in NetBSD iSCSI target.
+ */
+ log_warnx("received Login PDU with wrong StatSN: "
+ "is %u, should be %u", ntohl(bhslr->bhslr_statsn),
+ conn->conn_statsn + 1);
+ }
+ conn->conn_tsih = ntohs(bhslr->bhslr_tsih);
+ conn->conn_statsn = ntohl(bhslr->bhslr_statsn);
+
+ initial = false;
+
+ return (response);
+}
+
+static struct pdu *
+login_new_request(struct connection *conn, int csg)
+{
+ struct pdu *request;
+ struct iscsi_bhs_login_request *bhslr;
+ int nsg;
+
+ request = pdu_new(conn);
+ bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs;
+ bhslr->bhslr_opcode = ISCSI_BHS_OPCODE_LOGIN_REQUEST |
+ ISCSI_BHS_OPCODE_IMMEDIATE;
+
+ bhslr->bhslr_flags = BHSLR_FLAGS_TRANSIT;
+ switch (csg) {
+ case BHSLR_STAGE_SECURITY_NEGOTIATION:
+ nsg = BHSLR_STAGE_OPERATIONAL_NEGOTIATION;
+ break;
+ case BHSLR_STAGE_OPERATIONAL_NEGOTIATION:
+ nsg = BHSLR_STAGE_FULL_FEATURE_PHASE;
+ break;
+ default:
+ assert(!"invalid csg");
+ log_errx(1, "invalid csg %d", csg);
+ }
+ login_set_csg(request, csg);
+ login_set_nsg(request, nsg);
+
+ memcpy(bhslr->bhslr_isid, &conn->conn_isid, sizeof(bhslr->bhslr_isid));
+ bhslr->bhslr_tsih = htons(conn->conn_tsih);
+ bhslr->bhslr_initiator_task_tag = 0;
+ bhslr->bhslr_cmdsn = 0;
+ bhslr->bhslr_expstatsn = htonl(conn->conn_statsn + 1);
+
+ return (request);
+}
+
+static int
+login_list_prefers(const char *list,
+ const char *choice1, const char *choice2)
+{
+ char *tofree, *str, *token;
+
+ tofree = str = checked_strdup(list);
+
+ while ((token = strsep(&str, ",")) != NULL) {
+ if (strcmp(token, choice1) == 0) {
+ free(tofree);
+ return (1);
+ }
+ if (strcmp(token, choice2) == 0) {
+ free(tofree);
+ return (2);
+ }
+ }
+ free(tofree);
+ return (-1);
+}
+
+static void
+login_negotiate_key(struct connection *conn, const char *name,
+ const char *value)
+{
+ int which, tmp;
+
+ if (strcmp(name, "TargetAlias") == 0) {
+ strlcpy(conn->conn_target_alias, value,
+ sizeof(conn->conn_target_alias));
+ } else if (strcmp(value, "Irrelevant") == 0) {
+ /* Ignore. */
+ } else if (strcmp(name, "HeaderDigest") == 0) {
+ which = login_list_prefers(value, "CRC32C", "None");
+ switch (which) {
+ case 1:
+ log_debugx("target prefers CRC32C "
+ "for header digest; we'll use it");
+ conn->conn_header_digest = CONN_DIGEST_CRC32C;
+ break;
+ case 2:
+ log_debugx("target prefers not to do "
+ "header digest; we'll comply");
+ break;
+ default:
+ log_warnx("target sent unrecognized "
+ "HeaderDigest value \"%s\"; will use None", value);
+ break;
+ }
+ } else if (strcmp(name, "DataDigest") == 0) {
+ which = login_list_prefers(value, "CRC32C", "None");
+ switch (which) {
+ case 1:
+ log_debugx("target prefers CRC32C "
+ "for data digest; we'll use it");
+ conn->conn_data_digest = CONN_DIGEST_CRC32C;
+ break;
+ case 2:
+ log_debugx("target prefers not to do "
+ "data digest; we'll comply");
+ break;
+ default:
+ log_warnx("target sent unrecognized "
+ "DataDigest value \"%s\"; will use None", value);
+ break;
+ }
+ } else if (strcmp(name, "MaxConnections") == 0) {
+ /* Ignore. */
+ } else if (strcmp(name, "InitialR2T") == 0) {
+ if (strcmp(value, "Yes") == 0)
+ conn->conn_initial_r2t = true;
+ else
+ conn->conn_initial_r2t = false;
+ } else if (strcmp(name, "ImmediateData") == 0) {
+ if (strcmp(value, "Yes") == 0)
+ conn->conn_immediate_data = true;
+ else
+ conn->conn_immediate_data = false;
+ } else if (strcmp(name, "MaxRecvDataSegmentLength") == 0) {
+ tmp = strtoul(value, NULL, 10);
+ if (tmp <= 0)
+ log_errx(1, "received invalid "
+ "MaxRecvDataSegmentLength");
+ if (tmp > ISCSI_MAX_DATA_SEGMENT_LENGTH) {
+ log_debugx("capping MaxRecvDataSegmentLength "
+ "from %d to %d", tmp, ISCSI_MAX_DATA_SEGMENT_LENGTH);
+ tmp = ISCSI_MAX_DATA_SEGMENT_LENGTH;
+ }
+ conn->conn_max_data_segment_length = tmp;
+ } else if (strcmp(name, "MaxBurstLength") == 0) {
+ if (conn->conn_immediate_data) {
+ tmp = strtoul(value, NULL, 10);
+ if (tmp <= 0)
+ log_errx(1, "received invalid MaxBurstLength");
+ conn->conn_max_burst_length = tmp;
+ }
+ } else if (strcmp(name, "FirstBurstLength") == 0) {
+ tmp = strtoul(value, NULL, 10);
+ if (tmp <= 0)
+ log_errx(1, "received invalid FirstBurstLength");
+ conn->conn_first_burst_length = tmp;
+ } else if (strcmp(name, "DefaultTime2Wait") == 0) {
+ /* Ignore */
+ } else if (strcmp(name, "DefaultTime2Retain") == 0) {
+ /* Ignore */
+ } else if (strcmp(name, "MaxOutstandingR2T") == 0) {
+ /* Ignore */
+ } else if (strcmp(name, "DataPDUInOrder") == 0) {
+ /* Ignore */
+ } else if (strcmp(name, "DataSequenceInOrder") == 0) {
+ /* Ignore */
+ } else if (strcmp(name, "ErrorRecoveryLevel") == 0) {
+ /* Ignore */
+ } else if (strcmp(name, "OFMarker") == 0) {
+ /* Ignore */
+ } else if (strcmp(name, "IFMarker") == 0) {
+ /* Ignore */
+ } else if (strcmp(name, "TargetPortalGroupTag") == 0) {
+ /* Ignore */
+ } else {
+ log_debugx("unknown key \"%s\"; ignoring", name);
+ }
+}
+
+static void
+login_negotiate(struct connection *conn)
+{
+ struct pdu *request, *response;
+ struct keys *request_keys, *response_keys;
+ struct iscsi_bhs_login_response *bhslr;
+ int i, nrequests = 0;
+
+ log_debugx("beginning operational parameter negotiation");
+ request = login_new_request(conn, BHSLR_STAGE_OPERATIONAL_NEGOTIATION);
+ request_keys = keys_new();
+
+ log_debugx("offload \"%s\" limits MaxRecvDataSegmentLength to %zd",
+ conn->conn_conf.isc_offload,
+ conn->conn_limits.isl_max_data_segment_length);
+
+ /*
+ * The following keys are irrelevant for discovery sessions.
+ */
+ if (conn->conn_conf.isc_discovery == 0) {
+ if (conn->conn_conf.isc_header_digest != 0)
+ keys_add(request_keys, "HeaderDigest", "CRC32C");
+ else
+ keys_add(request_keys, "HeaderDigest", "None");
+ if (conn->conn_conf.isc_data_digest != 0)
+ keys_add(request_keys, "DataDigest", "CRC32C");
+ else
+ keys_add(request_keys, "DataDigest", "None");
+
+ keys_add(request_keys, "ImmediateData", "Yes");
+ keys_add_int(request_keys, "MaxBurstLength",
+ 2 * conn->conn_limits.isl_max_data_segment_length);
+ keys_add_int(request_keys, "FirstBurstLength",
+ conn->conn_limits.isl_max_data_segment_length);
+ keys_add(request_keys, "InitialR2T", "Yes");
+ keys_add(request_keys, "MaxOutstandingR2T", "1");
+ } else {
+ keys_add(request_keys, "HeaderDigest", "None");
+ keys_add(request_keys, "DataDigest", "None");
+ }
+
+ keys_add_int(request_keys, "MaxRecvDataSegmentLength",
+ conn->conn_limits.isl_max_data_segment_length);
+ keys_add(request_keys, "DefaultTime2Wait", "0");
+ keys_add(request_keys, "DefaultTime2Retain", "0");
+ keys_add(request_keys, "ErrorRecoveryLevel", "0");
+ keys_save(request_keys, request);
+ keys_delete(request_keys);
+ request_keys = NULL;
+ pdu_send(request);
+ pdu_delete(request);
+ request = NULL;
+
+ response = login_receive(conn);
+ response_keys = keys_new();
+ keys_load(response_keys, response);
+ for (i = 0; i < KEYS_MAX; i++) {
+ if (response_keys->keys_names[i] == NULL)
+ break;
+
+ login_negotiate_key(conn,
+ response_keys->keys_names[i], response_keys->keys_values[i]);
+ }
+
+ keys_delete(response_keys);
+ response_keys = NULL;
+
+ for (;;) {
+ bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs;
+ if ((bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) != 0)
+ break;
+
+ nrequests++;
+ if (nrequests > 5) {
+ log_warnx("received login response "
+ "without the \"T\" flag too many times; giving up");
+ break;
+ }
+
+ log_debugx("received login response "
+ "without the \"T\" flag; sending another request");
+
+ pdu_delete(response);
+
+ request = login_new_request(conn,
+ BHSLR_STAGE_OPERATIONAL_NEGOTIATION);
+ pdu_send(request);
+ pdu_delete(request);
+
+ response = login_receive(conn);
+ }
+
+ if (login_nsg(response) != BHSLR_STAGE_FULL_FEATURE_PHASE)
+ log_warnx("received final login response with wrong NSG 0x%x",
+ login_nsg(response));
+ pdu_delete(response);
+
+ log_debugx("operational parameter negotiation done; "
+ "transitioning to Full Feature phase");
+}
+
+static void
+login_send_chap_a(struct connection *conn)
+{
+ struct pdu *request;
+ struct keys *request_keys;
+
+ request = login_new_request(conn, BHSLR_STAGE_SECURITY_NEGOTIATION);
+ request_keys = keys_new();
+ keys_add(request_keys, "CHAP_A", "5");
+ keys_save(request_keys, request);
+ keys_delete(request_keys);
+ pdu_send(request);
+ pdu_delete(request);
+}
+
+static void
+login_send_chap_r(struct pdu *response)
+{
+ struct connection *conn;
+ struct pdu *request;
+ struct keys *request_keys, *response_keys;
+ struct rchap *rchap;
+ const char *chap_a, *chap_c, *chap_i;
+ char *chap_r;
+ int error;
+ char *mutual_chap_c, *mutual_chap_i;
+
+ /*
+ * As in the rest of the initiator, 'request' means
+ * 'initiator -> target', and 'response' means 'target -> initiator',
+ *
+ * So, here the 'response' from the target is the packet that contains
+ * CHAP challenge; our CHAP response goes into 'request'.
+ */
+
+ conn = response->pdu_connection;
+
+ response_keys = keys_new();
+ keys_load(response_keys, response);
+
+ /*
+ * First, compute the response.
+ */
+ chap_a = keys_find(response_keys, "CHAP_A");
+ if (chap_a == NULL)
+ log_errx(1, "received CHAP packet without CHAP_A");
+ chap_c = keys_find(response_keys, "CHAP_C");
+ if (chap_c == NULL)
+ log_errx(1, "received CHAP packet without CHAP_C");
+ chap_i = keys_find(response_keys, "CHAP_I");
+ if (chap_i == NULL)
+ log_errx(1, "received CHAP packet without CHAP_I");
+
+ if (strcmp(chap_a, "5") != 0) {
+ log_errx(1, "received CHAP packet "
+ "with unsupported CHAP_A \"%s\"", chap_a);
+ }
+
+ rchap = rchap_new(conn->conn_conf.isc_secret);
+ error = rchap_receive(rchap, chap_i, chap_c);
+ if (error != 0) {
+ log_errx(1, "received CHAP packet "
+ "with malformed CHAP_I or CHAP_C");
+ }
+ chap_r = rchap_get_response(rchap);
+ rchap_delete(rchap);
+
+ keys_delete(response_keys);
+
+ request = login_new_request(conn, BHSLR_STAGE_SECURITY_NEGOTIATION);
+ request_keys = keys_new();
+ keys_add(request_keys, "CHAP_N", conn->conn_conf.isc_user);
+ keys_add(request_keys, "CHAP_R", chap_r);
+ free(chap_r);
+
+ /*
+ * If we want mutual authentication, we're expected to send
+ * our CHAP_I/CHAP_C now.
+ */
+ if (conn->conn_conf.isc_mutual_user[0] != '\0') {
+ log_debugx("requesting mutual authentication; "
+ "binary challenge size is %zd bytes",
+ sizeof(conn->conn_mutual_chap->chap_challenge));
+
+ assert(conn->conn_mutual_chap == NULL);
+ conn->conn_mutual_chap = chap_new();
+ mutual_chap_i = chap_get_id(conn->conn_mutual_chap);
+ mutual_chap_c = chap_get_challenge(conn->conn_mutual_chap);
+ keys_add(request_keys, "CHAP_I", mutual_chap_i);
+ keys_add(request_keys, "CHAP_C", mutual_chap_c);
+ free(mutual_chap_i);
+ free(mutual_chap_c);
+ }
+
+ keys_save(request_keys, request);
+ keys_delete(request_keys);
+ pdu_send(request);
+ pdu_delete(request);
+}
+
+static void
+login_verify_mutual(const struct pdu *response)
+{
+ struct connection *conn;
+ struct keys *response_keys;
+ const char *chap_n, *chap_r;
+ int error;
+
+ conn = response->pdu_connection;
+
+ response_keys = keys_new();
+ keys_load(response_keys, response);
+
+ chap_n = keys_find(response_keys, "CHAP_N");
+ if (chap_n == NULL)
+ log_errx(1, "received CHAP Response PDU without CHAP_N");
+ chap_r = keys_find(response_keys, "CHAP_R");
+ if (chap_r == NULL)
+ log_errx(1, "received CHAP Response PDU without CHAP_R");
+
+ error = chap_receive(conn->conn_mutual_chap, chap_r);
+ if (error != 0)
+ log_errx(1, "received CHAP Response PDU with invalid CHAP_R");
+
+ if (strcmp(chap_n, conn->conn_conf.isc_mutual_user) != 0) {
+ fail(conn, "Mutual CHAP failed");
+ log_errx(1, "mutual CHAP authentication failed: wrong user");
+ }
+
+ error = chap_authenticate(conn->conn_mutual_chap,
+ conn->conn_conf.isc_mutual_secret);
+ if (error != 0) {
+ fail(conn, "Mutual CHAP failed");
+ log_errx(1, "mutual CHAP authentication failed: wrong secret");
+ }
+
+ keys_delete(response_keys);
+ chap_delete(conn->conn_mutual_chap);
+ conn->conn_mutual_chap = NULL;
+
+ log_debugx("mutual CHAP authentication succeeded");
+}
+
+static void
+login_chap(struct connection *conn)
+{
+ struct pdu *response;
+
+ log_debugx("beginning CHAP authentication; sending CHAP_A");
+ login_send_chap_a(conn);
+
+ log_debugx("waiting for CHAP_A/CHAP_C/CHAP_I");
+ response = login_receive(conn);
+
+ log_debugx("sending CHAP_N/CHAP_R");
+ login_send_chap_r(response);
+ pdu_delete(response);
+
+ /*
+ * XXX: Make sure this is not susceptible to MITM.
+ */
+
+ log_debugx("waiting for CHAP result");
+ response = login_receive(conn);
+ if (conn->conn_conf.isc_mutual_user[0] != '\0')
+ login_verify_mutual(response);
+ pdu_delete(response);
+
+ log_debugx("CHAP authentication done");
+}
+
+void
+login(struct connection *conn)
+{
+ struct pdu *request, *response;
+ struct keys *request_keys, *response_keys;
+ struct iscsi_bhs_login_response *bhslr2;
+ const char *auth_method;
+ int i;
+
+ log_debugx("beginning Login phase; sending Login PDU");
+ request = login_new_request(conn, BHSLR_STAGE_SECURITY_NEGOTIATION);
+ request_keys = keys_new();
+ if (conn->conn_conf.isc_mutual_user[0] != '\0') {
+ keys_add(request_keys, "AuthMethod", "CHAP");
+ } else if (conn->conn_conf.isc_user[0] != '\0') {
+ /*
+ * Give target a chance to skip authentication if it
+ * doesn't feel like it.
+ *
+ * None is first, CHAP second; this is to work around
+ * what seems to be LIO (Linux target) bug: otherwise,
+ * if target is configured with no authentication,
+ * and we are configured to authenticate, the target
+ * will erroneously respond with AuthMethod=CHAP
+ * instead of AuthMethod=None, and will subsequently
+ * fail the connection. This usually happens with
+ * Discovery sessions, which default to no authentication.
+ */
+ keys_add(request_keys, "AuthMethod", "None,CHAP");
+ } else {
+ keys_add(request_keys, "AuthMethod", "None");
+ }
+ keys_add(request_keys, "InitiatorName",
+ conn->conn_conf.isc_initiator);
+ if (conn->conn_conf.isc_initiator_alias[0] != '\0') {
+ keys_add(request_keys, "InitiatorAlias",
+ conn->conn_conf.isc_initiator_alias);
+ }
+ if (conn->conn_conf.isc_discovery == 0) {
+ keys_add(request_keys, "SessionType", "Normal");
+ keys_add(request_keys,
+ "TargetName", conn->conn_conf.isc_target);
+ } else {
+ keys_add(request_keys, "SessionType", "Discovery");
+ }
+ keys_save(request_keys, request);
+ keys_delete(request_keys);
+ pdu_send(request);
+ pdu_delete(request);
+
+ response = login_receive(conn);
+
+ response_keys = keys_new();
+ keys_load(response_keys, response);
+
+ for (i = 0; i < KEYS_MAX; i++) {
+ if (response_keys->keys_names[i] == NULL)
+ break;
+
+ /*
+ * Not interested in AuthMethod at this point; we only need
+ * to parse things such as TargetAlias.
+ *
+ * XXX: This is somewhat ugly. We should have a way to apply
+ * all the keys to the session and use that by default
+ * instead of discarding them.
+ */
+ if (strcmp(response_keys->keys_names[i], "AuthMethod") == 0)
+ continue;
+
+ login_negotiate_key(conn,
+ response_keys->keys_names[i], response_keys->keys_values[i]);
+ }
+
+ bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs;
+ if ((bhslr2->bhslr_flags & BHSLR_FLAGS_TRANSIT) != 0 &&
+ login_nsg(response) == BHSLR_STAGE_OPERATIONAL_NEGOTIATION) {
+ if (conn->conn_conf.isc_mutual_user[0] != '\0') {
+ log_errx(1, "target requested transition "
+ "to operational parameter negotiation, "
+ "but we require mutual CHAP");
+ }
+
+ log_debugx("target requested transition "
+ "to operational parameter negotiation");
+ keys_delete(response_keys);
+ pdu_delete(response);
+ login_negotiate(conn);
+ return;
+ }
+
+ auth_method = keys_find(response_keys, "AuthMethod");
+ if (auth_method == NULL)
+ log_errx(1, "received response without AuthMethod");
+ if (strcmp(auth_method, "None") == 0) {
+ if (conn->conn_conf.isc_mutual_user[0] != '\0') {
+ log_errx(1, "target does not require authantication, "
+ "but we require mutual CHAP");
+ }
+
+ log_debugx("target does not require authentication");
+ keys_delete(response_keys);
+ pdu_delete(response);
+ login_negotiate(conn);
+ return;
+ }
+
+ if (strcmp(auth_method, "CHAP") != 0) {
+ fail(conn, "Unsupported AuthMethod");
+ log_errx(1, "received response "
+ "with unsupported AuthMethod \"%s\"", auth_method);
+ }
+
+ if (conn->conn_conf.isc_user[0] == '\0' ||
+ conn->conn_conf.isc_secret[0] == '\0') {
+ fail(conn, "Authentication required");
+ log_errx(1, "target requests CHAP authentication, but we don't "
+ "have user and secret");
+ }
+
+ keys_delete(response_keys);
+ response_keys = NULL;
+ pdu_delete(response);
+ response = NULL;
+
+ login_chap(conn);
+ login_negotiate(conn);
+}
diff --git a/usr.sbin/iscsid/pdu.c b/usr.sbin/iscsid/pdu.c
new file mode 100644
index 0000000..4672ecd
--- /dev/null
+++ b/usr.sbin/iscsid/pdu.c
@@ -0,0 +1,297 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "iscsid.h"
+#include "iscsi_proto.h"
+
+#ifdef ICL_KERNEL_PROXY
+#include <sys/ioctl.h>
+#endif
+
+static int
+pdu_ahs_length(const struct pdu *pdu)
+{
+
+ return (pdu->pdu_bhs->bhs_total_ahs_len * 4);
+}
+
+static int
+pdu_data_segment_length(const struct pdu *pdu)
+{
+ uint32_t len = 0;
+
+ len += pdu->pdu_bhs->bhs_data_segment_len[0];
+ len <<= 8;
+ len += pdu->pdu_bhs->bhs_data_segment_len[1];
+ len <<= 8;
+ len += pdu->pdu_bhs->bhs_data_segment_len[2];
+
+ return (len);
+}
+
+static void
+pdu_set_data_segment_length(struct pdu *pdu, uint32_t len)
+{
+
+ pdu->pdu_bhs->bhs_data_segment_len[2] = len;
+ pdu->pdu_bhs->bhs_data_segment_len[1] = len >> 8;
+ pdu->pdu_bhs->bhs_data_segment_len[0] = len >> 16;
+}
+
+struct pdu *
+pdu_new(struct connection *conn)
+{
+ struct pdu *pdu;
+
+ pdu = calloc(sizeof(*pdu), 1);
+ if (pdu == NULL)
+ log_err(1, "calloc");
+
+ pdu->pdu_bhs = calloc(sizeof(*pdu->pdu_bhs), 1);
+ if (pdu->pdu_bhs == NULL)
+ log_err(1, "calloc");
+
+ pdu->pdu_connection = conn;
+
+ return (pdu);
+}
+
+struct pdu *
+pdu_new_response(struct pdu *request)
+{
+
+ return (pdu_new(request->pdu_connection));
+}
+
+#ifdef ICL_KERNEL_PROXY
+
+static void
+pdu_receive_proxy(struct pdu *pdu)
+{
+ struct iscsi_daemon_receive *idr;
+ size_t len;
+ int error;
+
+ assert(pdu->pdu_connection->conn_conf.isc_iser != 0);
+
+ pdu->pdu_data = malloc(ISCSI_MAX_DATA_SEGMENT_LENGTH);
+ if (pdu->pdu_data == NULL)
+ log_err(1, "malloc");
+
+ idr = calloc(1, sizeof(*idr));
+ if (idr == NULL)
+ log_err(1, "calloc");
+
+ idr->idr_session_id = pdu->pdu_connection->conn_session_id;
+ idr->idr_bhs = pdu->pdu_bhs;
+ idr->idr_data_segment_len = ISCSI_MAX_DATA_SEGMENT_LENGTH;
+ idr->idr_data_segment = pdu->pdu_data;
+
+ error = ioctl(pdu->pdu_connection->conn_iscsi_fd, ISCSIDRECEIVE, idr);
+ if (error != 0)
+ log_err(1, "ISCSIDRECEIVE");
+
+ len = pdu_ahs_length(pdu);
+ if (len > 0)
+ log_errx(1, "protocol error: non-empty AHS");
+
+ len = pdu_data_segment_length(pdu);
+ assert(len <= ISCSI_MAX_DATA_SEGMENT_LENGTH);
+ pdu->pdu_data_len = len;
+
+ free(idr);
+}
+
+static void
+pdu_send_proxy(struct pdu *pdu)
+{
+ struct iscsi_daemon_send *ids;
+ int error;
+
+ assert(pdu->pdu_connection->conn_conf.isc_iser != 0);
+
+ pdu_set_data_segment_length(pdu, pdu->pdu_data_len);
+
+ ids = calloc(1, sizeof(*ids));
+ if (ids == NULL)
+ log_err(1, "calloc");
+
+ ids->ids_session_id = pdu->pdu_connection->conn_session_id;
+ ids->ids_bhs = pdu->pdu_bhs;
+ ids->ids_data_segment_len = pdu->pdu_data_len;
+ ids->ids_data_segment = pdu->pdu_data;
+
+ error = ioctl(pdu->pdu_connection->conn_iscsi_fd, ISCSIDSEND, ids);
+ if (error != 0)
+ log_err(1, "ISCSIDSEND");
+
+ free(ids);
+}
+
+#endif /* ICL_KERNEL_PROXY */
+
+static size_t
+pdu_padding(const struct pdu *pdu)
+{
+
+ if ((pdu->pdu_data_len % 4) != 0)
+ return (4 - (pdu->pdu_data_len % 4));
+
+ return (0);
+}
+
+static void
+pdu_read(int fd, char *data, size_t len)
+{
+ ssize_t ret;
+
+ while (len > 0) {
+ ret = read(fd, data, len);
+ if (ret < 0) {
+ if (timed_out())
+ log_errx(1, "exiting due to timeout");
+ log_err(1, "read");
+ } else if (ret == 0)
+ log_errx(1, "read: connection lost");
+ len -= ret;
+ data += ret;
+ }
+}
+
+void
+pdu_receive(struct pdu *pdu)
+{
+ size_t len, padding;
+ char dummy[4];
+
+#ifdef ICL_KERNEL_PROXY
+ if (pdu->pdu_connection->conn_conf.isc_iser != 0)
+ return (pdu_receive_proxy(pdu));
+#endif
+
+ assert(pdu->pdu_connection->conn_conf.isc_iser == 0);
+
+ pdu_read(pdu->pdu_connection->conn_socket,
+ (char *)pdu->pdu_bhs, sizeof(*pdu->pdu_bhs));
+
+ len = pdu_ahs_length(pdu);
+ if (len > 0)
+ log_errx(1, "protocol error: non-empty AHS");
+
+ len = pdu_data_segment_length(pdu);
+ if (len > 0) {
+ if (len > ISCSI_MAX_DATA_SEGMENT_LENGTH) {
+ log_errx(1, "protocol error: received PDU "
+ "with DataSegmentLength exceeding %d",
+ ISCSI_MAX_DATA_SEGMENT_LENGTH);
+ }
+
+ pdu->pdu_data_len = len;
+ pdu->pdu_data = malloc(len);
+ if (pdu->pdu_data == NULL)
+ log_err(1, "malloc");
+
+ pdu_read(pdu->pdu_connection->conn_socket,
+ (char *)pdu->pdu_data, pdu->pdu_data_len);
+
+ padding = pdu_padding(pdu);
+ if (padding != 0) {
+ assert(padding < sizeof(dummy));
+ pdu_read(pdu->pdu_connection->conn_socket,
+ (char *)dummy, padding);
+ }
+ }
+}
+
+void
+pdu_send(struct pdu *pdu)
+{
+ ssize_t ret, total_len;
+ size_t padding;
+ uint32_t zero = 0;
+ struct iovec iov[3];
+ int iovcnt;
+
+#ifdef ICL_KERNEL_PROXY
+ if (pdu->pdu_connection->conn_conf.isc_iser != 0)
+ return (pdu_send_proxy(pdu));
+#endif
+
+ assert(pdu->pdu_connection->conn_conf.isc_iser == 0);
+
+ pdu_set_data_segment_length(pdu, pdu->pdu_data_len);
+ iov[0].iov_base = pdu->pdu_bhs;
+ iov[0].iov_len = sizeof(*pdu->pdu_bhs);
+ total_len = iov[0].iov_len;
+ iovcnt = 1;
+
+ if (pdu->pdu_data_len > 0) {
+ iov[1].iov_base = pdu->pdu_data;
+ iov[1].iov_len = pdu->pdu_data_len;
+ total_len += iov[1].iov_len;
+ iovcnt = 2;
+
+ padding = pdu_padding(pdu);
+ if (padding > 0) {
+ assert(padding < sizeof(zero));
+ iov[2].iov_base = &zero;
+ iov[2].iov_len = padding;
+ total_len += iov[2].iov_len;
+ iovcnt = 3;
+ }
+ }
+
+ ret = writev(pdu->pdu_connection->conn_socket, iov, iovcnt);
+ if (ret < 0) {
+ if (timed_out())
+ log_errx(1, "exiting due to timeout");
+ log_err(1, "writev");
+ }
+ if (ret != total_len)
+ log_errx(1, "short write");
+}
+
+void
+pdu_delete(struct pdu *pdu)
+{
+
+ free(pdu->pdu_data);
+ free(pdu->pdu_bhs);
+ free(pdu);
+}
diff --git a/usr.sbin/jail/Makefile b/usr.sbin/jail/Makefile
new file mode 100644
index 0000000..9dfdee5
--- /dev/null
+++ b/usr.sbin/jail/Makefile
@@ -0,0 +1,25 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= jail
+MAN= jail.8 jail.conf.5
+SRCS= jail.c command.c config.c state.c jailp.h jaillex.l jailparse.y y.tab.h
+
+LIBADD= jail kvm util l
+
+NO_WMISSING_VARIABLE_DECLARATIONS=
+
+YFLAGS+=-v
+CFLAGS+=-I. -I${.CURDIR}
+
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+= -DINET6
+.endif
+.if ${MK_INET_SUPPORT} != "no"
+CFLAGS+= -DINET
+.endif
+
+CLEANFILES= y.output
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/jail/Makefile.depend b/usr.sbin/jail/Makefile.depend
new file mode 100644
index 0000000..b24d6ea4
--- /dev/null
+++ b/usr.sbin/jail/Makefile.depend
@@ -0,0 +1,31 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libelf \
+ lib/libjail \
+ lib/libkvm \
+ lib/libutil \
+ usr.bin/lex/lib \
+ usr.bin/yacc.host \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+jaillex.o: jaillex.c
+jaillex.o: y.tab.h
+jaillex.po: jaillex.c
+jaillex.po: y.tab.h
+jailparse.o: jailparse.c
+jailparse.po: jailparse.c
+.endif
diff --git a/usr.sbin/jail/command.c b/usr.sbin/jail/command.c
new file mode 100644
index 0000000..f162c3c
--- /dev/null
+++ b/usr.sbin/jail/command.c
@@ -0,0 +1,979 @@
+/*-
+ * Copyright (c) 2011 James Gritton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/event.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/user.h>
+#include <sys/wait.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <kvm.h>
+#include <login_cap.h>
+#include <paths.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "jailp.h"
+
+#define DEFAULT_STOP_TIMEOUT 10
+#define PHASH_SIZE 256
+
+LIST_HEAD(phhead, phash);
+
+struct phash {
+ LIST_ENTRY(phash) le;
+ struct cfjail *j;
+ pid_t pid;
+};
+
+int paralimit = -1;
+
+extern char **environ;
+
+static int run_command(struct cfjail *j);
+static int add_proc(struct cfjail *j, pid_t pid);
+static void clear_procs(struct cfjail *j);
+static struct cfjail *find_proc(pid_t pid);
+static int term_procs(struct cfjail *j);
+static int get_user_info(struct cfjail *j, const char *username,
+ const struct passwd **pwdp, login_cap_t **lcapp);
+static int check_path(struct cfjail *j, const char *pname, const char *path,
+ int isfile, const char *umount_type);
+
+static struct cfjails sleeping = TAILQ_HEAD_INITIALIZER(sleeping);
+static struct cfjails runnable = TAILQ_HEAD_INITIALIZER(runnable);
+static struct cfstring dummystring = { .len = 1 };
+static struct phhead phash[PHASH_SIZE];
+static int kq;
+
+/*
+ * Run the next command associated with a jail.
+ */
+int
+next_command(struct cfjail *j)
+{
+ enum intparam comparam;
+ int create_failed, stopping;
+
+ if (paralimit == 0) {
+ requeue(j, &runnable);
+ return 1;
+ }
+ create_failed = (j->flags & (JF_STOP | JF_FAILED)) == JF_FAILED;
+ stopping = (j->flags & JF_STOP) != 0;
+ comparam = *j->comparam;
+ for (;;) {
+ if (j->comstring == NULL) {
+ j->comparam += create_failed ? -1 : 1;
+ switch ((comparam = *j->comparam)) {
+ case IP__NULL:
+ return 0;
+ case IP_MOUNT_DEVFS:
+ if (!bool_param(j->intparams[IP_MOUNT_DEVFS]))
+ continue;
+ j->comstring = &dummystring;
+ break;
+ case IP_MOUNT_FDESCFS:
+ if (!bool_param(j->intparams[IP_MOUNT_FDESCFS]))
+ continue;
+ j->comstring = &dummystring;
+ break;
+ case IP_MOUNT_PROCFS:
+ if (!bool_param(j->intparams[IP_MOUNT_PROCFS]))
+ continue;
+ j->comstring = &dummystring;
+ break;
+ case IP__OP:
+ case IP_STOP_TIMEOUT:
+ j->comstring = &dummystring;
+ break;
+ default:
+ if (j->intparams[comparam] == NULL)
+ continue;
+ j->comstring = create_failed || (stopping &&
+ (j->intparams[comparam]->flags & PF_REV))
+ ? TAILQ_LAST(&j->intparams[comparam]->val,
+ cfstrings)
+ : TAILQ_FIRST(&j->intparams[comparam]->val);
+ }
+ } else {
+ j->comstring = j->comstring == &dummystring ? NULL :
+ create_failed || (stopping &&
+ (j->intparams[comparam]->flags & PF_REV))
+ ? TAILQ_PREV(j->comstring, cfstrings, tq)
+ : TAILQ_NEXT(j->comstring, tq);
+ }
+ if (j->comstring == NULL || j->comstring->len == 0 ||
+ (create_failed && (comparam == IP_EXEC_PRESTART ||
+ comparam == IP_EXEC_START || comparam == IP_COMMAND ||
+ comparam == IP_EXEC_POSTSTART)))
+ continue;
+ switch (run_command(j)) {
+ case -1:
+ failed(j);
+ /* FALLTHROUGH */
+ case 1:
+ return 1;
+ }
+ }
+}
+
+/*
+ * Check command exit status
+ */
+int
+finish_command(struct cfjail *j)
+{
+ int error;
+
+ if (!(j->flags & JF_SLEEPQ))
+ return 0;
+ j->flags &= ~JF_SLEEPQ;
+ if (*j->comparam == IP_STOP_TIMEOUT)
+ {
+ j->flags &= ~JF_TIMEOUT;
+ j->pstatus = 0;
+ return 0;
+ }
+ paralimit++;
+ if (!TAILQ_EMPTY(&runnable))
+ requeue(TAILQ_FIRST(&runnable), &ready);
+ error = 0;
+ if (j->flags & JF_TIMEOUT) {
+ j->flags &= ~JF_TIMEOUT;
+ if (*j->comparam != IP_STOP_TIMEOUT) {
+ jail_warnx(j, "%s: timed out", j->comline);
+ failed(j);
+ error = -1;
+ } else if (verbose > 0)
+ jail_note(j, "timed out\n");
+ } else if (j->pstatus != 0) {
+ if (WIFSIGNALED(j->pstatus))
+ jail_warnx(j, "%s: exited on signal %d",
+ j->comline, WTERMSIG(j->pstatus));
+ else
+ jail_warnx(j, "%s: failed", j->comline);
+ j->pstatus = 0;
+ failed(j);
+ error = -1;
+ }
+ free(j->comline);
+ j->comline = NULL;
+ return error;
+}
+
+/*
+ * Check for finished processes or timeouts.
+ */
+struct cfjail *
+next_proc(int nonblock)
+{
+ struct kevent ke;
+ struct timespec ts;
+ struct timespec *tsp;
+ struct cfjail *j;
+
+ if (!TAILQ_EMPTY(&sleeping)) {
+ again:
+ tsp = NULL;
+ if ((j = TAILQ_FIRST(&sleeping)) && j->timeout.tv_sec) {
+ clock_gettime(CLOCK_REALTIME, &ts);
+ ts.tv_sec = j->timeout.tv_sec - ts.tv_sec;
+ ts.tv_nsec = j->timeout.tv_nsec - ts.tv_nsec;
+ if (ts.tv_nsec < 0) {
+ ts.tv_sec--;
+ ts.tv_nsec += 1000000000;
+ }
+ if (ts.tv_sec < 0 ||
+ (ts.tv_sec == 0 && ts.tv_nsec == 0)) {
+ j->flags |= JF_TIMEOUT;
+ clear_procs(j);
+ return j;
+ }
+ tsp = &ts;
+ }
+ if (nonblock) {
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+ tsp = &ts;
+ }
+ switch (kevent(kq, NULL, 0, &ke, 1, tsp)) {
+ case -1:
+ if (errno != EINTR)
+ err(1, "kevent");
+ goto again;
+ case 0:
+ if (!nonblock) {
+ j = TAILQ_FIRST(&sleeping);
+ j->flags |= JF_TIMEOUT;
+ clear_procs(j);
+ return j;
+ }
+ break;
+ case 1:
+ (void)waitpid(ke.ident, NULL, WNOHANG);
+ if ((j = find_proc(ke.ident))) {
+ j->pstatus = ke.data;
+ return j;
+ }
+ goto again;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * Run a single command for a jail, possible inside the jail.
+ */
+static int
+run_command(struct cfjail *j)
+{
+ const struct passwd *pwd;
+ const struct cfstring *comstring, *s;
+ login_cap_t *lcap;
+ const char **argv;
+ char *acs, *cs, *comcs, *devpath;
+ const char *jidstr, *conslog, *path, *ruleset, *term, *username;
+ enum intparam comparam;
+ size_t comlen;
+ pid_t pid;
+ int argc, bg, clean, consfd, down, fib, i, injail, sjuser, timeout;
+#if defined(INET) || defined(INET6)
+ char *addr, *extrap, *p, *val;
+#endif
+
+ static char *cleanenv;
+
+ /* Perform some operations that aren't actually commands */
+ comparam = *j->comparam;
+ down = j->flags & (JF_STOP | JF_FAILED);
+ switch (comparam) {
+ case IP_STOP_TIMEOUT:
+ return term_procs(j);
+
+ case IP__OP:
+ if (down) {
+ if (jail_remove(j->jid) < 0 && errno == EPERM) {
+ jail_warnx(j, "jail_remove: %s",
+ strerror(errno));
+ return -1;
+ }
+ if (verbose > 0 || (verbose == 0 && (j->flags & JF_STOP
+ ? note_remove : j->name != NULL)))
+ jail_note(j, "removed\n");
+ j->jid = -1;
+ if (j->flags & JF_STOP)
+ dep_done(j, DF_LIGHT);
+ else
+ j->flags &= ~JF_PERSIST;
+ } else {
+ if (create_jail(j) < 0)
+ return -1;
+ if (iflag)
+ printf("%d\n", j->jid);
+ if (verbose >= 0 && (j->name || verbose > 0))
+ jail_note(j, "created\n");
+ dep_done(j, DF_LIGHT);
+ }
+ return 0;
+
+ default: ;
+ }
+ /*
+ * Collect exec arguments. Internal commands for network and
+ * mounting build their own argument lists.
+ */
+ comstring = j->comstring;
+ bg = 0;
+ switch (comparam) {
+#ifdef INET
+ case IP__IP4_IFADDR:
+ argc = 0;
+ val = alloca(strlen(comstring->s) + 1);
+ strcpy(val, comstring->s);
+ cs = val;
+ extrap = NULL;
+ while ((p = strchr(cs, ' ')) != NULL && strlen(p) > 1) {
+ if (extrap == NULL) {
+ *p = '\0';
+ extrap = p + 1;
+ }
+ cs = p + 1;
+ argc++;
+ }
+
+ argv = alloca((8 + argc) * sizeof(char *));
+ argv[0] = _PATH_IFCONFIG;
+ if ((cs = strchr(val, '|'))) {
+ argv[1] = acs = alloca(cs - val + 1);
+ strlcpy(acs, val, cs - val + 1);
+ addr = cs + 1;
+ } else {
+ argv[1] = string_param(j->intparams[IP_INTERFACE]);
+ addr = val;
+ }
+ argv[2] = "inet";
+ if (!(cs = strchr(addr, '/'))) {
+ argv[3] = addr;
+ argv[4] = "netmask";
+ argv[5] = "255.255.255.255";
+ argc = 6;
+ } else if (strchr(cs + 1, '.')) {
+ argv[3] = acs = alloca(cs - addr + 1);
+ strlcpy(acs, addr, cs - addr + 1);
+ argv[4] = "netmask";
+ argv[5] = cs + 1;
+ argc = 6;
+ } else {
+ argv[3] = addr;
+ argc = 4;
+ }
+
+ if (!down) {
+ for (cs = strtok(extrap, " "); cs;
+ cs = strtok(NULL, " ")) {
+ size_t len = strlen(cs) + 1;
+ argv[argc++] = acs = alloca(len);
+ strlcpy(acs, cs, len);
+ }
+ }
+
+ argv[argc] = down ? "-alias" : "alias";
+ argv[argc + 1] = NULL;
+ break;
+#endif
+
+#ifdef INET6
+ case IP__IP6_IFADDR:
+ argc = 0;
+ val = alloca(strlen(comstring->s) + 1);
+ strcpy(val, comstring->s);
+ cs = val;
+ extrap = NULL;
+ while ((p = strchr(cs, ' ')) != NULL && strlen(p) > 1) {
+ if (extrap == NULL) {
+ *p = '\0';
+ extrap = p + 1;
+ }
+ cs = p + 1;
+ argc++;
+ }
+
+ argv = alloca((8 + argc) * sizeof(char *));
+ argv[0] = _PATH_IFCONFIG;
+ if ((cs = strchr(val, '|'))) {
+ argv[1] = acs = alloca(cs - val + 1);
+ strlcpy(acs, val, cs - val + 1);
+ addr = cs + 1;
+ } else {
+ argv[1] = string_param(j->intparams[IP_INTERFACE]);
+ addr = val;
+ }
+ argv[2] = "inet6";
+ argv[3] = addr;
+ if (!(cs = strchr(addr, '/'))) {
+ argv[4] = "prefixlen";
+ argv[5] = "128";
+ argc = 6;
+ } else
+ argc = 4;
+
+ if (!down) {
+ for (cs = strtok(extrap, " "); cs;
+ cs = strtok(NULL, " ")) {
+ size_t len = strlen(cs) + 1;
+ argv[argc++] = acs = alloca(len);
+ strlcpy(acs, cs, len);
+ }
+ }
+
+ argv[argc] = down ? "-alias" : "alias";
+ argv[argc + 1] = NULL;
+ break;
+#endif
+
+ case IP_VNET_INTERFACE:
+ argv = alloca(5 * sizeof(char *));
+ argv[0] = _PATH_IFCONFIG;
+ argv[1] = comstring->s;
+ argv[2] = down ? "-vnet" : "vnet";
+ jidstr = string_param(j->intparams[KP_JID]);
+ argv[3] = jidstr ? jidstr : string_param(j->intparams[KP_NAME]);
+ argv[4] = NULL;
+ break;
+
+ case IP_MOUNT:
+ case IP__MOUNT_FROM_FSTAB:
+ argv = alloca(8 * sizeof(char *));
+ comcs = alloca(comstring->len + 1);
+ strcpy(comcs, comstring->s);
+ argc = 0;
+ for (cs = strtok(comcs, " \t\f\v\r\n"); cs && argc < 4;
+ cs = strtok(NULL, " \t\f\v\r\n"))
+ argv[argc++] = cs;
+ if (argc == 0)
+ return 0;
+ if (argc < 3) {
+ jail_warnx(j, "%s: %s: missing information",
+ j->intparams[comparam]->name, comstring->s);
+ return -1;
+ }
+ if (check_path(j, j->intparams[comparam]->name, argv[1], 0,
+ down ? argv[2] : NULL) < 0)
+ return -1;
+ if (down) {
+ argv[4] = NULL;
+ argv[3] = argv[1];
+ argv[0] = "/sbin/umount";
+ } else {
+ if (argc == 4) {
+ argv[7] = NULL;
+ argv[6] = argv[1];
+ argv[5] = argv[0];
+ argv[4] = argv[3];
+ argv[3] = "-o";
+ } else {
+ argv[5] = NULL;
+ argv[4] = argv[1];
+ argv[3] = argv[0];
+ }
+ argv[0] = _PATH_MOUNT;
+ }
+ argv[1] = "-t";
+ break;
+
+ case IP_MOUNT_DEVFS:
+ argv = alloca(7 * sizeof(char *));
+ path = string_param(j->intparams[KP_PATH]);
+ if (path == NULL) {
+ jail_warnx(j, "mount.devfs: no path");
+ return -1;
+ }
+ devpath = alloca(strlen(path) + 5);
+ sprintf(devpath, "%s/dev", path);
+ if (check_path(j, "mount.devfs", devpath, 0,
+ down ? "devfs" : NULL) < 0)
+ return -1;
+ if (down) {
+ argv[0] = "/sbin/umount";
+ argv[1] = devpath;
+ argv[2] = NULL;
+ } else {
+ argv[0] = _PATH_MOUNT;
+ argv[1] = "-t";
+ argv[2] = "devfs";
+ ruleset = string_param(j->intparams[KP_DEVFS_RULESET]);
+ if (!ruleset)
+ ruleset = "4"; /* devfsrules_jail */
+ argv[3] = acs = alloca(11 + strlen(ruleset));
+ sprintf(acs, "-oruleset=%s", ruleset);
+ argv[4] = ".";
+ argv[5] = devpath;
+ argv[6] = NULL;
+ }
+ break;
+
+ case IP_MOUNT_FDESCFS:
+ argv = alloca(7 * sizeof(char *));
+ path = string_param(j->intparams[KP_PATH]);
+ if (path == NULL) {
+ jail_warnx(j, "mount.fdescfs: no path");
+ return -1;
+ }
+ devpath = alloca(strlen(path) + 8);
+ sprintf(devpath, "%s/dev/fd", path);
+ if (check_path(j, "mount.fdescfs", devpath, 0,
+ down ? "fdescfs" : NULL) < 0)
+ return -1;
+ if (down) {
+ argv[0] = "/sbin/umount";
+ argv[1] = devpath;
+ argv[2] = NULL;
+ } else {
+ argv[0] = _PATH_MOUNT;
+ argv[1] = "-t";
+ argv[2] = "fdescfs";
+ argv[3] = ".";
+ argv[4] = devpath;
+ argv[5] = NULL;
+ }
+ break;
+
+ case IP_MOUNT_PROCFS:
+ argv = alloca(7 * sizeof(char *));
+ path = string_param(j->intparams[KP_PATH]);
+ if (path == NULL) {
+ jail_warnx(j, "mount.procfs: no path");
+ return -1;
+ }
+ devpath = alloca(strlen(path) + 6);
+ sprintf(devpath, "%s/proc", path);
+ if (check_path(j, "mount.procfs", devpath, 0,
+ down ? "procfs" : NULL) < 0)
+ return -1;
+ if (down) {
+ argv[0] = "/sbin/umount";
+ argv[1] = devpath;
+ argv[2] = NULL;
+ } else {
+ argv[0] = _PATH_MOUNT;
+ argv[1] = "-t";
+ argv[2] = "procfs";
+ argv[3] = ".";
+ argv[4] = devpath;
+ argv[5] = NULL;
+ }
+ break;
+
+ case IP_COMMAND:
+ if (j->name != NULL)
+ goto default_command;
+ argc = 0;
+ TAILQ_FOREACH(s, &j->intparams[IP_COMMAND]->val, tq)
+ argc++;
+ argv = alloca((argc + 1) * sizeof(char *));
+ argc = 0;
+ TAILQ_FOREACH(s, &j->intparams[IP_COMMAND]->val, tq)
+ argv[argc++] = s->s;
+ argv[argc] = NULL;
+ j->comstring = &dummystring;
+ break;
+
+ default:
+ default_command:
+ if ((cs = strpbrk(comstring->s, "!\"$&'()*;<>?[\\]`{|}~")) &&
+ !(cs[0] == '&' && cs[1] == '\0')) {
+ argv = alloca(4 * sizeof(char *));
+ argv[0] = _PATH_BSHELL;
+ argv[1] = "-c";
+ argv[2] = comstring->s;
+ argv[3] = NULL;
+ } else {
+ if (cs) {
+ *cs = 0;
+ bg = 1;
+ }
+ comcs = alloca(comstring->len + 1);
+ strcpy(comcs, comstring->s);
+ argc = 0;
+ for (cs = strtok(comcs, " \t\f\v\r\n"); cs;
+ cs = strtok(NULL, " \t\f\v\r\n"))
+ argc++;
+ argv = alloca((argc + 1) * sizeof(char *));
+ strcpy(comcs, comstring->s);
+ argc = 0;
+ for (cs = strtok(comcs, " \t\f\v\r\n"); cs;
+ cs = strtok(NULL, " \t\f\v\r\n"))
+ argv[argc++] = cs;
+ argv[argc] = NULL;
+ }
+ }
+ if (argv[0] == NULL)
+ return 0;
+
+ if (int_param(j->intparams[IP_EXEC_TIMEOUT], &timeout) &&
+ timeout != 0) {
+ clock_gettime(CLOCK_REALTIME, &j->timeout);
+ j->timeout.tv_sec += timeout;
+ } else
+ j->timeout.tv_sec = 0;
+
+ injail = comparam == IP_EXEC_START || comparam == IP_COMMAND ||
+ comparam == IP_EXEC_STOP;
+ clean = bool_param(j->intparams[IP_EXEC_CLEAN]);
+ username = string_param(j->intparams[injail
+ ? IP_EXEC_JAIL_USER : IP_EXEC_SYSTEM_USER]);
+ sjuser = bool_param(j->intparams[IP_EXEC_SYSTEM_JAIL_USER]);
+
+ consfd = 0;
+ if (injail &&
+ (conslog = string_param(j->intparams[IP_EXEC_CONSOLELOG]))) {
+ if (check_path(j, "exec.consolelog", conslog, 1, NULL) < 0)
+ return -1;
+ consfd =
+ open(conslog, O_WRONLY | O_CREAT | O_APPEND, DEFFILEMODE);
+ if (consfd < 0) {
+ jail_warnx(j, "open %s: %s", conslog, strerror(errno));
+ return -1;
+ }
+ }
+
+ comlen = 0;
+ for (i = 0; argv[i]; i++)
+ comlen += strlen(argv[i]) + 1;
+ j->comline = cs = emalloc(comlen);
+ for (i = 0; argv[i]; i++) {
+ strcpy(cs, argv[i]);
+ if (argv[i + 1]) {
+ cs += strlen(argv[i]) + 1;
+ cs[-1] = ' ';
+ }
+ }
+ if (verbose > 0)
+ jail_note(j, "run command%s%s%s: %s\n",
+ injail ? " in jail" : "", username ? " as " : "",
+ username ? username : "", j->comline);
+
+ pid = fork();
+ if (pid < 0)
+ err(1, "fork");
+ if (pid > 0) {
+ if (bg || !add_proc(j, pid)) {
+ free(j->comline);
+ j->comline = NULL;
+ return 0;
+ } else {
+ paralimit--;
+ return 1;
+ }
+ }
+ if (bg)
+ setsid();
+
+ /* Set up the environment and run the command */
+ pwd = NULL;
+ lcap = NULL;
+ if ((clean || username) && injail && sjuser &&
+ get_user_info(j, username, &pwd, &lcap) < 0)
+ exit(1);
+ if (injail) {
+ /* jail_attach won't chdir along with its chroot. */
+ path = string_param(j->intparams[KP_PATH]);
+ if (path && chdir(path) < 0) {
+ jail_warnx(j, "chdir %s: %s", path, strerror(errno));
+ exit(1);
+ }
+ if (int_param(j->intparams[IP_EXEC_FIB], &fib) &&
+ setfib(fib) < 0) {
+ jail_warnx(j, "setfib: %s", strerror(errno));
+ exit(1);
+ }
+ if (jail_attach(j->jid) < 0) {
+ jail_warnx(j, "jail_attach: %s", strerror(errno));
+ exit(1);
+ }
+ }
+ if (clean || username) {
+ if (!(injail && sjuser) &&
+ get_user_info(j, username, &pwd, &lcap) < 0)
+ exit(1);
+ if (clean) {
+ term = getenv("TERM");
+ environ = &cleanenv;
+ setenv("PATH", "/bin:/usr/bin", 0);
+ if (term != NULL)
+ setenv("TERM", term, 1);
+ }
+ if (setgid(pwd->pw_gid) < 0) {
+ jail_warnx(j, "setgid %d: %s", pwd->pw_gid,
+ strerror(errno));
+ exit(1);
+ }
+ if (setusercontext(lcap, pwd, pwd->pw_uid, username
+ ? LOGIN_SETALL & ~LOGIN_SETGROUP & ~LOGIN_SETLOGIN
+ : LOGIN_SETPATH | LOGIN_SETENV) < 0) {
+ jail_warnx(j, "setusercontext %s: %s", pwd->pw_name,
+ strerror(errno));
+ exit(1);
+ }
+ login_close(lcap);
+ setenv("USER", pwd->pw_name, 1);
+ setenv("HOME", pwd->pw_dir, 1);
+ setenv("SHELL",
+ *pwd->pw_shell ? pwd->pw_shell : _PATH_BSHELL, 1);
+ if (clean && chdir(pwd->pw_dir) < 0) {
+ jail_warnx(j, "chdir %s: %s",
+ pwd->pw_dir, strerror(errno));
+ exit(1);
+ }
+ endpwent();
+ }
+
+ if (consfd != 0 && (dup2(consfd, 1) < 0 || dup2(consfd, 2) < 0)) {
+ jail_warnx(j, "exec.consolelog: %s", strerror(errno));
+ exit(1);
+ }
+ closefrom(3);
+ execvp(argv[0], __DECONST(char *const*, argv));
+ jail_warnx(j, "exec %s: %s", argv[0], strerror(errno));
+ exit(1);
+}
+
+/*
+ * Add a process to the hash, tied to a jail.
+ */
+static int
+add_proc(struct cfjail *j, pid_t pid)
+{
+ struct kevent ke;
+ struct cfjail *tj;
+ struct phash *ph;
+
+ if (!kq && (kq = kqueue()) < 0)
+ err(1, "kqueue");
+ EV_SET(&ke, pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL);
+ if (kevent(kq, &ke, 1, NULL, 0, NULL) < 0) {
+ if (errno == ESRCH)
+ return 0;
+ err(1, "kevent");
+ }
+ ph = emalloc(sizeof(struct phash));
+ ph->j = j;
+ ph->pid = pid;
+ LIST_INSERT_HEAD(&phash[pid % PHASH_SIZE], ph, le);
+ j->nprocs++;
+ j->flags |= JF_SLEEPQ;
+ if (j->timeout.tv_sec == 0)
+ requeue(j, &sleeping);
+ else {
+ /* File the jail in the sleep queue according to its timeout. */
+ TAILQ_REMOVE(j->queue, j, tq);
+ TAILQ_FOREACH(tj, &sleeping, tq) {
+ if (!tj->timeout.tv_sec ||
+ j->timeout.tv_sec < tj->timeout.tv_sec ||
+ (j->timeout.tv_sec == tj->timeout.tv_sec &&
+ j->timeout.tv_nsec <= tj->timeout.tv_nsec)) {
+ TAILQ_INSERT_BEFORE(tj, j, tq);
+ break;
+ }
+ }
+ if (tj == NULL)
+ TAILQ_INSERT_TAIL(&sleeping, j, tq);
+ j->queue = &sleeping;
+ }
+ return 1;
+}
+
+/*
+ * Remove any processes from the hash that correspond to a jail.
+ */
+static void
+clear_procs(struct cfjail *j)
+{
+ struct kevent ke;
+ struct phash *ph, *tph;
+ int i;
+
+ j->nprocs = 0;
+ for (i = 0; i < PHASH_SIZE; i++)
+ LIST_FOREACH_SAFE(ph, &phash[i], le, tph)
+ if (ph->j == j) {
+ EV_SET(&ke, ph->pid, EVFILT_PROC, EV_DELETE,
+ NOTE_EXIT, 0, NULL);
+ (void)kevent(kq, &ke, 1, NULL, 0, NULL);
+ LIST_REMOVE(ph, le);
+ free(ph);
+ }
+}
+
+/*
+ * Find the jail that corresponds to an exited process.
+ */
+static struct cfjail *
+find_proc(pid_t pid)
+{
+ struct cfjail *j;
+ struct phash *ph;
+
+ LIST_FOREACH(ph, &phash[pid % PHASH_SIZE], le)
+ if (ph->pid == pid) {
+ j = ph->j;
+ LIST_REMOVE(ph, le);
+ free(ph);
+ return --j->nprocs ? NULL : j;
+ }
+ return NULL;
+}
+
+/*
+ * Send SIGTERM to all processes in a jail and wait for them to die.
+ */
+static int
+term_procs(struct cfjail *j)
+{
+ struct kinfo_proc *ki;
+ int i, noted, pcnt, timeout;
+
+ static kvm_t *kd;
+
+ if (!int_param(j->intparams[IP_STOP_TIMEOUT], &timeout))
+ timeout = DEFAULT_STOP_TIMEOUT;
+ else if (timeout == 0)
+ return 0;
+
+ if (kd == NULL) {
+ kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL);
+ if (kd == NULL)
+ return 0;
+ }
+
+ ki = kvm_getprocs(kd, KERN_PROC_PROC, 0, &pcnt);
+ if (ki == NULL)
+ return 0;
+ noted = 0;
+ for (i = 0; i < pcnt; i++)
+ if (ki[i].ki_jid == j->jid &&
+ kill(ki[i].ki_pid, SIGTERM) == 0) {
+ (void)add_proc(j, ki[i].ki_pid);
+ if (verbose > 0) {
+ if (!noted) {
+ noted = 1;
+ jail_note(j, "sent SIGTERM to:");
+ }
+ printf(" %d", ki[i].ki_pid);
+ }
+ }
+ if (noted)
+ printf("\n");
+ if (j->nprocs > 0) {
+ clock_gettime(CLOCK_REALTIME, &j->timeout);
+ j->timeout.tv_sec += timeout;
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Look up a user in the passwd and login.conf files.
+ */
+static int
+get_user_info(struct cfjail *j, const char *username,
+ const struct passwd **pwdp, login_cap_t **lcapp)
+{
+ const struct passwd *pwd;
+
+ *pwdp = pwd = username ? getpwnam(username) : getpwuid(getuid());
+ if (pwd == NULL) {
+ if (errno)
+ jail_warnx(j, "getpwnam%s%s: %s", username ? " " : "",
+ username ? username : "", strerror(errno));
+ else if (username)
+ jail_warnx(j, "%s: no such user", username);
+ else
+ jail_warnx(j, "unknown uid %d", getuid());
+ return -1;
+ }
+ *lcapp = login_getpwclass(pwd);
+ if (*lcapp == NULL) {
+ jail_warnx(j, "getpwclass %s: %s", pwd->pw_name,
+ strerror(errno));
+ return -1;
+ }
+ /* Set the groups while the group file is still available */
+ if (initgroups(pwd->pw_name, pwd->pw_gid) < 0) {
+ jail_warnx(j, "initgroups %s: %s", pwd->pw_name,
+ strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Make sure a mount or consolelog path is a valid absolute pathname
+ * with no symlinks.
+ */
+static int
+check_path(struct cfjail *j, const char *pname, const char *path, int isfile,
+ const char *umount_type)
+{
+ struct stat st, mpst;
+ struct statfs stfs;
+ char *tpath, *p;
+ const char *jailpath;
+ size_t jplen;
+
+ if (path[0] != '/') {
+ jail_warnx(j, "%s: %s: not an absolute pathname",
+ pname, path);
+ return -1;
+ }
+ /*
+ * Only check for symlinks in components below the jail's path,
+ * since that's where the security risk lies.
+ */
+ jailpath = string_param(j->intparams[KP_PATH]);
+ if (jailpath == NULL)
+ jailpath = "";
+ jplen = strlen(jailpath);
+ if (!strncmp(path, jailpath, jplen) && path[jplen] == '/') {
+ tpath = alloca(strlen(path) + 1);
+ strcpy(tpath, path);
+ for (p = tpath + jplen; p != NULL; ) {
+ p = strchr(p + 1, '/');
+ if (p)
+ *p = '\0';
+ if (lstat(tpath, &st) < 0) {
+ if (errno == ENOENT && isfile && !p)
+ break;
+ jail_warnx(j, "%s: %s: %s", pname, tpath,
+ strerror(errno));
+ return -1;
+ }
+ if (S_ISLNK(st.st_mode)) {
+ jail_warnx(j, "%s: %s is a symbolic link",
+ pname, tpath);
+ return -1;
+ }
+ if (p)
+ *p = '/';
+ }
+ }
+ if (umount_type != NULL) {
+ if (stat(path, &st) < 0 || statfs(path, &stfs) < 0) {
+ jail_warnx(j, "%s: %s: %s", pname, path,
+ strerror(errno));
+ return -1;
+ }
+ if (stat(stfs.f_mntonname, &mpst) < 0) {
+ jail_warnx(j, "%s: %s: %s", pname, stfs.f_mntonname,
+ strerror(errno));
+ return -1;
+ }
+ if (st.st_ino != mpst.st_ino) {
+ jail_warnx(j, "%s: %s: not a mount point",
+ pname, path);
+ return -1;
+ }
+ if (strcmp(stfs.f_fstypename, umount_type)) {
+ jail_warnx(j, "%s: %s: not a %s mount",
+ pname, path, umount_type);
+ return -1;
+ }
+ }
+ return 0;
+}
diff --git a/usr.sbin/jail/config.c b/usr.sbin/jail/config.c
new file mode 100644
index 0000000..87b8ef9
--- /dev/null
+++ b/usr.sbin/jail/config.c
@@ -0,0 +1,850 @@
+/*-
+ * Copyright (c) 2011 James Gritton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/errno.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <err.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "jailp.h"
+
+struct ipspec {
+ const char *name;
+ unsigned flags;
+};
+
+extern FILE *yyin;
+extern int yynerrs;
+
+extern int yyparse(void);
+
+struct cfjails cfjails = TAILQ_HEAD_INITIALIZER(cfjails);
+
+static void free_param(struct cfparams *pp, struct cfparam *p);
+static void free_param_strings(struct cfparam *p);
+
+static const struct ipspec intparams[] = {
+ [IP_ALLOW_DYING] = {"allow.dying", PF_INTERNAL | PF_BOOL},
+ [IP_COMMAND] = {"command", PF_INTERNAL},
+ [IP_DEPEND] = {"depend", PF_INTERNAL},
+ [IP_EXEC_CLEAN] = {"exec.clean", PF_INTERNAL | PF_BOOL},
+ [IP_EXEC_CONSOLELOG] = {"exec.consolelog", PF_INTERNAL},
+ [IP_EXEC_FIB] = {"exec.fib", PF_INTERNAL | PF_INT},
+ [IP_EXEC_JAIL_USER] = {"exec.jail_user", PF_INTERNAL},
+ [IP_EXEC_POSTSTART] = {"exec.poststart", PF_INTERNAL},
+ [IP_EXEC_POSTSTOP] = {"exec.poststop", PF_INTERNAL},
+ [IP_EXEC_PRESTART] = {"exec.prestart", PF_INTERNAL},
+ [IP_EXEC_PRESTOP] = {"exec.prestop", PF_INTERNAL},
+ [IP_EXEC_START] = {"exec.start", PF_INTERNAL},
+ [IP_EXEC_STOP] = {"exec.stop", PF_INTERNAL},
+ [IP_EXEC_SYSTEM_JAIL_USER]= {"exec.system_jail_user",
+ PF_INTERNAL | PF_BOOL},
+ [IP_EXEC_SYSTEM_USER] = {"exec.system_user", PF_INTERNAL},
+ [IP_EXEC_TIMEOUT] = {"exec.timeout", PF_INTERNAL | PF_INT},
+#if defined(INET) || defined(INET6)
+ [IP_INTERFACE] = {"interface", PF_INTERNAL},
+ [IP_IP_HOSTNAME] = {"ip_hostname", PF_INTERNAL | PF_BOOL},
+#endif
+ [IP_MOUNT] = {"mount", PF_INTERNAL | PF_REV},
+ [IP_MOUNT_DEVFS] = {"mount.devfs", PF_INTERNAL | PF_BOOL},
+ [IP_MOUNT_FDESCFS] = {"mount.fdescfs", PF_INTERNAL | PF_BOOL},
+ [IP_MOUNT_PROCFS] = {"mount.procfs", PF_INTERNAL | PF_BOOL},
+ [IP_MOUNT_FSTAB] = {"mount.fstab", PF_INTERNAL},
+ [IP_STOP_TIMEOUT] = {"stop.timeout", PF_INTERNAL | PF_INT},
+ [IP_VNET_INTERFACE] = {"vnet.interface", PF_INTERNAL},
+#ifdef INET
+ [IP__IP4_IFADDR] = {"ip4.addr", PF_INTERNAL | PF_CONV | PF_REV},
+#endif
+#ifdef INET6
+ [IP__IP6_IFADDR] = {"ip6.addr", PF_INTERNAL | PF_CONV | PF_REV},
+#endif
+ [IP__MOUNT_FROM_FSTAB] = {"mount.fstab", PF_INTERNAL | PF_CONV | PF_REV},
+ [IP__OP] = {NULL, PF_CONV},
+ [KP_ALLOW_CHFLAGS] = {"allow.chflags", 0},
+ [KP_ALLOW_MOUNT] = {"allow.mount", 0},
+ [KP_ALLOW_RAW_SOCKETS] = {"allow.raw_sockets", 0},
+ [KP_ALLOW_SET_HOSTNAME]= {"allow.set_hostname", 0},
+ [KP_ALLOW_SOCKET_AF] = {"allow.socket_af", 0},
+ [KP_ALLOW_SYSVIPC] = {"allow.sysvipc", 0},
+ [KP_DEVFS_RULESET] = {"devfs_ruleset", 0},
+ [KP_ENFORCE_STATFS] = {"enforce_statfs", 0},
+ [KP_HOST_HOSTNAME] = {"host.hostname", 0},
+#ifdef INET
+ [KP_IP4_ADDR] = {"ip4.addr", 0},
+#endif
+#ifdef INET6
+ [KP_IP6_ADDR] = {"ip6.addr", 0},
+#endif
+ [KP_JID] = {"jid", PF_IMMUTABLE},
+ [KP_NAME] = {"name", PF_IMMUTABLE},
+ [KP_PATH] = {"path", 0},
+ [KP_PERSIST] = {"persist", 0},
+ [KP_SECURELEVEL] = {"securelevel", 0},
+ [KP_VNET] = {"vnet", 0},
+};
+
+/*
+ * Parse the jail configuration file.
+ */
+void
+load_config(void)
+{
+ struct cfjails wild;
+ struct cfparams opp;
+ struct cfjail *j, *tj, *wj;
+ struct cfparam *p, *vp, *tp;
+ struct cfstring *s, *vs, *ns;
+ struct cfvar *v, *vv;
+ char *ep;
+ int did_self, jseq, pgen;
+
+ if (!strcmp(cfname, "-")) {
+ cfname = "STDIN";
+ yyin = stdin;
+ } else {
+ yyin = fopen(cfname, "r");
+ if (!yyin)
+ err(1, "%s", cfname);
+ }
+ if (yyparse() || yynerrs)
+ exit(1);
+
+ /* Separate the wildcard jails out from the actual jails. */
+ jseq = 0;
+ TAILQ_INIT(&wild);
+ TAILQ_FOREACH_SAFE(j, &cfjails, tq, tj) {
+ j->seq = ++jseq;
+ if (wild_jail_name(j->name))
+ requeue(j, &wild);
+ }
+
+ TAILQ_FOREACH(j, &cfjails, tq) {
+ /* Set aside the jail's parameters. */
+ TAILQ_INIT(&opp);
+ TAILQ_CONCAT(&opp, &j->params, tq);
+ /*
+ * The jail name implies its "name" or "jid" parameter,
+ * though they may also be explicitly set later on.
+ */
+ add_param(j, NULL,
+ strtol(j->name, &ep, 10) && !*ep ? KP_JID : KP_NAME,
+ j->name);
+ /*
+ * Collect parameters for the jail, global parameters/variables,
+ * and any matching wildcard jails.
+ */
+ did_self = 0;
+ TAILQ_FOREACH(wj, &wild, tq) {
+ if (j->seq < wj->seq && !did_self) {
+ TAILQ_FOREACH(p, &opp, tq)
+ add_param(j, p, 0, NULL);
+ did_self = 1;
+ }
+ if (wild_jail_match(j->name, wj->name))
+ TAILQ_FOREACH(p, &wj->params, tq)
+ add_param(j, p, 0, NULL);
+ }
+ if (!did_self)
+ TAILQ_FOREACH(p, &opp, tq)
+ add_param(j, p, 0, NULL);
+
+ /* Resolve any variable substitutions. */
+ pgen = 0;
+ TAILQ_FOREACH(p, &j->params, tq) {
+ p->gen = ++pgen;
+ find_vars:
+ TAILQ_FOREACH(s, &p->val, tq) {
+ while ((v = STAILQ_FIRST(&s->vars))) {
+ TAILQ_FOREACH(vp, &j->params, tq)
+ if (!strcmp(vp->name, v->name))
+ break;
+ if (!vp) {
+ jail_warnx(j,
+ "%s: variable \"%s\" not found",
+ p->name, v->name);
+ bad_var:
+ j->flags |= JF_FAILED;
+ TAILQ_FOREACH(vp, &j->params, tq)
+ if (vp->gen == pgen)
+ vp->flags |= PF_BAD;
+ goto free_var;
+ }
+ if (vp->flags & PF_BAD)
+ goto bad_var;
+ if (vp->gen == pgen) {
+ jail_warnx(j, "%s: variable loop",
+ v->name);
+ goto bad_var;
+ }
+ TAILQ_FOREACH(vs, &vp->val, tq)
+ if (!STAILQ_EMPTY(&vs->vars)) {
+ vp->gen = pgen;
+ TAILQ_REMOVE(&j->params, vp,
+ tq);
+ TAILQ_INSERT_BEFORE(p, vp, tq);
+ p = vp;
+ goto find_vars;
+ }
+ vs = TAILQ_FIRST(&vp->val);
+ if (TAILQ_NEXT(vs, tq) != NULL &&
+ (s->s[0] != '\0' ||
+ STAILQ_NEXT(v, tq))) {
+ jail_warnx(j, "%s: array cannot be "
+ "substituted inline",
+ p->name);
+ goto bad_var;
+ }
+ s->s = erealloc(s->s, s->len + vs->len + 1);
+ memmove(s->s + v->pos + vs->len,
+ s->s + v->pos,
+ s->len - v->pos + 1);
+ memcpy(s->s + v->pos, vs->s, vs->len);
+ vv = v;
+ while ((vv = STAILQ_NEXT(vv, tq)))
+ vv->pos += vs->len;
+ s->len += vs->len;
+ while ((vs = TAILQ_NEXT(vs, tq))) {
+ ns = emalloc(sizeof(struct cfstring));
+ ns->s = estrdup(vs->s);
+ ns->len = vs->len;
+ STAILQ_INIT(&ns->vars);
+ TAILQ_INSERT_AFTER(&p->val, s, ns, tq);
+ s = ns;
+ }
+ free_var:
+ free(v->name);
+ STAILQ_REMOVE_HEAD(&s->vars, tq);
+ free(v);
+ }
+ }
+ }
+
+ /* Free the jail's original parameter list and any variables. */
+ while ((p = TAILQ_FIRST(&opp)))
+ free_param(&opp, p);
+ TAILQ_FOREACH_SAFE(p, &j->params, tq, tp)
+ if (p->flags & PF_VAR)
+ free_param(&j->params, p);
+ }
+ while ((wj = TAILQ_FIRST(&wild))) {
+ free(wj->name);
+ while ((p = TAILQ_FIRST(&wj->params)))
+ free_param(&wj->params, p);
+ TAILQ_REMOVE(&wild, wj, tq);
+ }
+}
+
+/*
+ * Create a new jail record.
+ */
+struct cfjail *
+add_jail(void)
+{
+ struct cfjail *j;
+
+ j = emalloc(sizeof(struct cfjail));
+ memset(j, 0, sizeof(struct cfjail));
+ TAILQ_INIT(&j->params);
+ STAILQ_INIT(&j->dep[DEP_FROM]);
+ STAILQ_INIT(&j->dep[DEP_TO]);
+ j->queue = &cfjails;
+ TAILQ_INSERT_TAIL(&cfjails, j, tq);
+ return j;
+}
+
+/*
+ * Add a parameter to a jail.
+ */
+void
+add_param(struct cfjail *j, const struct cfparam *p, enum intparam ipnum,
+ const char *value)
+{
+ struct cfstrings nss;
+ struct cfparam *dp, *np;
+ struct cfstring *s, *ns;
+ struct cfvar *v, *nv;
+ const char *name;
+ char *cs, *tname;
+ unsigned flags;
+
+ if (j == NULL) {
+ /* Create a single anonymous jail if one doesn't yet exist. */
+ j = TAILQ_LAST(&cfjails, cfjails);
+ if (j == NULL)
+ j = add_jail();
+ }
+ TAILQ_INIT(&nss);
+ if (p != NULL) {
+ name = p->name;
+ flags = p->flags;
+ /*
+ * Make a copy of the parameter's string list,
+ * which may be freed if it's overridden later.
+ */
+ TAILQ_FOREACH(s, &p->val, tq) {
+ ns = emalloc(sizeof(struct cfstring));
+ ns->s = estrdup(s->s);
+ ns->len = s->len;
+ STAILQ_INIT(&ns->vars);
+ STAILQ_FOREACH(v, &s->vars, tq) {
+ nv = emalloc(sizeof(struct cfvar));
+ nv->name = strdup(v->name);
+ nv->pos = v->pos;
+ STAILQ_INSERT_TAIL(&ns->vars, nv, tq);
+ }
+ TAILQ_INSERT_TAIL(&nss, ns, tq);
+ }
+ } else {
+ flags = PF_APPEND;
+ if (ipnum != IP__NULL) {
+ name = intparams[ipnum].name;
+ flags |= intparams[ipnum].flags;
+ } else if ((cs = strchr(value, '='))) {
+ tname = alloca(cs - value + 1);
+ strlcpy(tname, value, cs - value + 1);
+ name = tname;
+ value = cs + 1;
+ } else {
+ name = value;
+ value = NULL;
+ }
+ if (value != NULL) {
+ ns = emalloc(sizeof(struct cfstring));
+ ns->s = estrdup(value);
+ ns->len = strlen(value);
+ STAILQ_INIT(&ns->vars);
+ TAILQ_INSERT_TAIL(&nss, ns, tq);
+ }
+ }
+
+ /* See if this parameter has already been added. */
+ if (ipnum != IP__NULL)
+ dp = j->intparams[ipnum];
+ else
+ TAILQ_FOREACH(dp, &j->params, tq)
+ if (!(dp->flags & PF_CONV) && equalopts(dp->name, name))
+ break;
+ if (dp != NULL) {
+ /* Found it - append or replace. */
+ if (dp->flags & PF_IMMUTABLE) {
+ jail_warnx(j, "cannot redefine variable \"%s\".",
+ dp->name);
+ return;
+ }
+ if (strcmp(dp->name, name)) {
+ free(dp->name);
+ dp->name = estrdup(name);
+ }
+ if (!(flags & PF_APPEND) || TAILQ_EMPTY(&nss))
+ free_param_strings(dp);
+ TAILQ_CONCAT(&dp->val, &nss, tq);
+ dp->flags |= flags;
+ } else {
+ /* Not found - add it. */
+ np = emalloc(sizeof(struct cfparam));
+ np->name = estrdup(name);
+ TAILQ_INIT(&np->val);
+ TAILQ_CONCAT(&np->val, &nss, tq);
+ np->flags = flags;
+ np->gen = 0;
+ TAILQ_INSERT_TAIL(&j->params, np, tq);
+ if (ipnum != IP__NULL)
+ j->intparams[ipnum] = np;
+ else
+ for (ipnum = IP__NULL + 1; ipnum < IP_NPARAM; ipnum++)
+ if (!(intparams[ipnum].flags & PF_CONV) &&
+ equalopts(name, intparams[ipnum].name)) {
+ j->intparams[ipnum] = np;
+ np->flags |= intparams[ipnum].flags;
+ break;
+ }
+ }
+}
+
+/*
+ * Return if a boolean parameter exists and is true.
+ */
+int
+bool_param(const struct cfparam *p)
+{
+ const char *cs;
+
+ if (p == NULL)
+ return 0;
+ cs = strrchr(p->name, '.');
+ return !strncmp(cs ? cs + 1 : p->name, "no", 2) ^
+ (TAILQ_EMPTY(&p->val) ||
+ !strcasecmp(TAILQ_LAST(&p->val, cfstrings)->s, "true") ||
+ (strtol(TAILQ_LAST(&p->val, cfstrings)->s, NULL, 10)));
+}
+
+/*
+ * Set an integer if a parameter if it exists.
+ */
+int
+int_param(const struct cfparam *p, int *ip)
+{
+ if (p == NULL || TAILQ_EMPTY(&p->val))
+ return 0;
+ *ip = strtol(TAILQ_LAST(&p->val, cfstrings)->s, NULL, 10);
+ return 1;
+}
+
+/*
+ * Return the string value of a scalar parameter if it exists.
+ */
+const char *
+string_param(const struct cfparam *p)
+{
+ return (p && !TAILQ_EMPTY(&p->val)
+ ? TAILQ_LAST(&p->val, cfstrings)->s : NULL);
+}
+
+/*
+ * Check syntax and values of internal parameters. Set some internal
+ * parameters based on the values of others.
+ */
+int
+check_intparams(struct cfjail *j)
+{
+ struct cfparam *p;
+ struct cfstring *s;
+ FILE *f;
+ const char *val;
+ char *cs, *ep, *ln;
+ size_t lnlen;
+ int error;
+#if defined(INET) || defined(INET6)
+ struct addrinfo hints;
+ struct addrinfo *ai0, *ai;
+ const char *hostname;
+ int gicode, defif, prefix;
+#endif
+#ifdef INET
+ struct in_addr addr4;
+ int ip4ok;
+ char avalue4[INET_ADDRSTRLEN];
+#endif
+#ifdef INET6
+ struct in6_addr addr6;
+ int ip6ok;
+ char avalue6[INET6_ADDRSTRLEN];
+#endif
+
+ error = 0;
+ /* Check format of boolan and integer values. */
+ TAILQ_FOREACH(p, &j->params, tq) {
+ if (!TAILQ_EMPTY(&p->val) && (p->flags & (PF_BOOL | PF_INT))) {
+ val = TAILQ_LAST(&p->val, cfstrings)->s;
+ if (p->flags & PF_BOOL) {
+ if (strcasecmp(val, "false") &&
+ strcasecmp(val, "true") &&
+ ((void)strtol(val, &ep, 10), *ep)) {
+ jail_warnx(j,
+ "%s: unknown boolean value \"%s\"",
+ p->name, val);
+ error = -1;
+ }
+ } else {
+ (void)strtol(val, &ep, 10);
+ if (ep == val || *ep) {
+ jail_warnx(j,
+ "%s: non-integer value \"%s\"",
+ p->name, val);
+ error = -1;
+ }
+ }
+ }
+ }
+
+#if defined(INET) || defined(INET6)
+ /*
+ * The ip_hostname parameter looks up the hostname, and adds parameters
+ * for any IP addresses it finds.
+ */
+ if (((j->flags & JF_OP_MASK) != JF_STOP ||
+ j->intparams[IP_INTERFACE] != NULL) &&
+ bool_param(j->intparams[IP_IP_HOSTNAME]) &&
+ (hostname = string_param(j->intparams[KP_HOST_HOSTNAME]))) {
+ j->intparams[IP_IP_HOSTNAME] = NULL;
+ /*
+ * Silently ignore unsupported address families from
+ * DNS lookups.
+ */
+#ifdef INET
+ ip4ok = feature_present("inet");
+#endif
+#ifdef INET6
+ ip6ok = feature_present("inet6");
+#endif
+ if (
+#if defined(INET) && defined(INET6)
+ ip4ok || ip6ok
+#elif defined(INET)
+ ip4ok
+#elif defined(INET6)
+ ip6ok
+#endif
+ ) {
+ /* Look up the hostname (or get the address) */
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_family =
+#if defined(INET) && defined(INET6)
+ ip4ok ? (ip6ok ? PF_UNSPEC : PF_INET) : PF_INET6;
+#elif defined(INET)
+ PF_INET;
+#elif defined(INET6)
+ PF_INET6;
+#endif
+ gicode = getaddrinfo(hostname, NULL, &hints, &ai0);
+ if (gicode != 0) {
+ jail_warnx(j, "host.hostname %s: %s", hostname,
+ gai_strerror(gicode));
+ error = -1;
+ } else {
+ /*
+ * Convert the addresses to ASCII so jailparam
+ * can convert them back. Errors are not
+ * expected here.
+ */
+ for (ai = ai0; ai; ai = ai->ai_next)
+ switch (ai->ai_family) {
+#ifdef INET
+ case AF_INET:
+ memcpy(&addr4,
+ &((struct sockaddr_in *)
+ (void *)ai->ai_addr)->
+ sin_addr, sizeof(addr4));
+ if (inet_ntop(AF_INET,
+ &addr4, avalue4,
+ INET_ADDRSTRLEN) == NULL)
+ err(1, "inet_ntop");
+ add_param(j, NULL, KP_IP4_ADDR,
+ avalue4);
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ memcpy(&addr6,
+ &((struct sockaddr_in6 *)
+ (void *)ai->ai_addr)->
+ sin6_addr, sizeof(addr6));
+ if (inet_ntop(AF_INET6,
+ &addr6, avalue6,
+ INET6_ADDRSTRLEN) == NULL)
+ err(1, "inet_ntop");
+ add_param(j, NULL, KP_IP6_ADDR,
+ avalue6);
+ break;
+#endif
+ }
+ freeaddrinfo(ai0);
+ }
+ }
+ }
+
+ /*
+ * IP addresses may include an interface to set that address on,
+ * a netmask/suffix for that address and options for ifconfig.
+ * These are copied to an internal command parameter and then stripped
+ * so they won't be passed on to jailparam_set.
+ */
+ defif = string_param(j->intparams[IP_INTERFACE]) != NULL;
+#ifdef INET
+ if (j->intparams[KP_IP4_ADDR] != NULL) {
+ TAILQ_FOREACH(s, &j->intparams[KP_IP4_ADDR]->val, tq) {
+ cs = strchr(s->s, '|');
+ if (cs || defif)
+ add_param(j, NULL, IP__IP4_IFADDR, s->s);
+ if (cs) {
+ strcpy(s->s, cs + 1);
+ s->len -= cs + 1 - s->s;
+ }
+ if ((cs = strchr(s->s, '/'))) {
+ prefix = strtol(cs + 1, &ep, 10);
+ if (*ep == '.'
+ ? inet_pton(AF_INET, cs + 1, &addr4) != 1
+ : *ep || prefix < 0 || prefix > 32) {
+ jail_warnx(j,
+ "ip4.addr: bad netmask \"%s\"", cs);
+ error = -1;
+ }
+ *cs = '\0';
+ s->len = cs - s->s;
+ }
+ if ((cs = strchr(s->s, ' ')) != NULL) {
+ *cs = '\0';
+ s->len = cs - s->s;
+ }
+ }
+ }
+#endif
+#ifdef INET6
+ if (j->intparams[KP_IP6_ADDR] != NULL) {
+ TAILQ_FOREACH(s, &j->intparams[KP_IP6_ADDR]->val, tq) {
+ cs = strchr(s->s, '|');
+ if (cs || defif)
+ add_param(j, NULL, IP__IP6_IFADDR, s->s);
+ if (cs) {
+ strcpy(s->s, cs + 1);
+ s->len -= cs + 1 - s->s;
+ }
+ if ((cs = strchr(s->s, '/'))) {
+ prefix = strtol(cs + 1, &ep, 10);
+ if (*ep || prefix < 0 || prefix > 128) {
+ jail_warnx(j,
+ "ip6.addr: bad prefixlen \"%s\"",
+ cs);
+ error = -1;
+ }
+ *cs = '\0';
+ s->len = cs - s->s;
+ }
+ if ((cs = strchr(s->s, ' ')) != NULL) {
+ *cs = '\0';
+ s->len = cs - s->s;
+ }
+ }
+ }
+#endif
+#endif
+
+ /*
+ * Read mount.fstab file(s), and treat each line as its own mount
+ * parameter.
+ */
+ if (j->intparams[IP_MOUNT_FSTAB] != NULL) {
+ TAILQ_FOREACH(s, &j->intparams[IP_MOUNT_FSTAB]->val, tq) {
+ if (s->len == 0)
+ continue;
+ f = fopen(s->s, "r");
+ if (f == NULL) {
+ jail_warnx(j, "mount.fstab: %s: %s",
+ s->s, strerror(errno));
+ error = -1;
+ continue;
+ }
+ while ((ln = fgetln(f, &lnlen))) {
+ if ((cs = memchr(ln, '#', lnlen - 1)))
+ lnlen = cs - ln + 1;
+ if (ln[lnlen - 1] == '\n' ||
+ ln[lnlen - 1] == '#')
+ ln[lnlen - 1] = '\0';
+ else {
+ cs = alloca(lnlen + 1);
+ strlcpy(cs, ln, lnlen + 1);
+ ln = cs;
+ }
+ add_param(j, NULL, IP__MOUNT_FROM_FSTAB, ln);
+ }
+ fclose(f);
+ }
+ }
+ if (error)
+ failed(j);
+ return error;
+}
+
+/*
+ * Import parameters into libjail's binary jailparam format.
+ */
+int
+import_params(struct cfjail *j)
+{
+ struct cfparam *p;
+ struct cfstring *s, *ts;
+ struct jailparam *jp;
+ char *value, *cs;
+ size_t vallen;
+ int error;
+
+ error = 0;
+ j->njp = 0;
+ TAILQ_FOREACH(p, &j->params, tq)
+ if (!(p->flags & PF_INTERNAL))
+ j->njp++;
+ j->jp = jp = emalloc(j->njp * sizeof(struct jailparam));
+ TAILQ_FOREACH(p, &j->params, tq) {
+ if (p->flags & PF_INTERNAL)
+ continue;
+ if (jailparam_init(jp, p->name) < 0) {
+ error = -1;
+ jail_warnx(j, "%s", jail_errmsg);
+ jp++;
+ continue;
+ }
+ if (TAILQ_EMPTY(&p->val))
+ value = NULL;
+ else if (!jp->jp_elemlen ||
+ !TAILQ_NEXT(TAILQ_FIRST(&p->val), tq)) {
+ /*
+ * Scalar parameters silently discard multiple (array)
+ * values, keeping only the last value added. This
+ * lets values added from the command line append to
+ * arrays wthout pre-checking the type.
+ */
+ value = TAILQ_LAST(&p->val, cfstrings)->s;
+ } else {
+ /*
+ * Convert arrays into comma-separated strings, which
+ * jailparam_import will then convert back into arrays.
+ */
+ vallen = 0;
+ TAILQ_FOREACH(s, &p->val, tq)
+ vallen += s->len + 1;
+ value = alloca(vallen);
+ cs = value;
+ TAILQ_FOREACH_SAFE(s, &p->val, tq, ts) {
+ memcpy(cs, s->s, s->len);
+ cs += s->len + 1;
+ cs[-1] = ',';
+ }
+ value[vallen - 1] = '\0';
+ }
+ if (jailparam_import(jp, value) < 0) {
+ error = -1;
+ jail_warnx(j, "%s", jail_errmsg);
+ }
+ jp++;
+ }
+ if (error) {
+ jailparam_free(j->jp, j->njp);
+ free(j->jp);
+ j->jp = NULL;
+ failed(j);
+ }
+ return error;
+}
+
+/*
+ * Check if options are equal (with or without the "no" prefix).
+ */
+int
+equalopts(const char *opt1, const char *opt2)
+{
+ char *p;
+
+ /* "opt" vs. "opt" or "noopt" vs. "noopt" */
+ if (strcmp(opt1, opt2) == 0)
+ return (1);
+ /* "noopt" vs. "opt" */
+ if (strncmp(opt1, "no", 2) == 0 && strcmp(opt1 + 2, opt2) == 0)
+ return (1);
+ /* "opt" vs. "noopt" */
+ if (strncmp(opt2, "no", 2) == 0 && strcmp(opt1, opt2 + 2) == 0)
+ return (1);
+ while ((p = strchr(opt1, '.')) != NULL &&
+ !strncmp(opt1, opt2, ++p - opt1)) {
+ opt2 += p - opt1;
+ opt1 = p;
+ /* "foo.noopt" vs. "foo.opt" */
+ if (strncmp(opt1, "no", 2) == 0 && strcmp(opt1 + 2, opt2) == 0)
+ return (1);
+ /* "foo.opt" vs. "foo.noopt" */
+ if (strncmp(opt2, "no", 2) == 0 && strcmp(opt1, opt2 + 2) == 0)
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * See if a jail name matches a wildcard.
+ */
+int
+wild_jail_match(const char *jname, const char *wname)
+{
+ const char *jc, *jd, *wc, *wd;
+
+ /*
+ * A non-final "*" component in the wild name matches a single jail
+ * component, and a final "*" matches one or more jail components.
+ */
+ for (jc = jname, wc = wname;
+ (jd = strchr(jc, '.')) && (wd = strchr(wc, '.'));
+ jc = jd + 1, wc = wd + 1)
+ if (strncmp(jc, wc, jd - jc + 1) && strncmp(wc, "*.", 2))
+ return 0;
+ return (!strcmp(jc, wc) || !strcmp(wc, "*"));
+}
+
+/*
+ * Return if a jail name is a wildcard.
+ */
+int
+wild_jail_name(const char *wname)
+{
+ const char *wc;
+
+ for (wc = strchr(wname, '*'); wc; wc = strchr(wc + 1, '*'))
+ if ((wc == wname || wc[-1] == '.') &&
+ (wc[1] == '\0' || wc[1] == '.'))
+ return 1;
+ return 0;
+}
+
+/*
+ * Free a parameter record and all its strings and variables.
+ */
+static void
+free_param(struct cfparams *pp, struct cfparam *p)
+{
+ free(p->name);
+ free_param_strings(p);
+ TAILQ_REMOVE(pp, p, tq);
+ free(p);
+}
+
+static void
+free_param_strings(struct cfparam *p)
+{
+ struct cfstring *s;
+ struct cfvar *v;
+
+ while ((s = TAILQ_FIRST(&p->val))) {
+ free(s->s);
+ while ((v = STAILQ_FIRST(&s->vars))) {
+ free(v->name);
+ STAILQ_REMOVE_HEAD(&s->vars, tq);
+ free(v);
+ }
+ TAILQ_REMOVE(&p->val, s, tq);
+ free(s);
+ }
+}
diff --git a/usr.sbin/jail/jail.8 b/usr.sbin/jail/jail.8
new file mode 100644
index 0000000..c9b79c4
--- /dev/null
+++ b/usr.sbin/jail/jail.8
@@ -0,0 +1,1305 @@
+.\" Copyright (c) 2000, 2003 Robert N. M. Watson
+.\" Copyright (c) 2008-2012 James Gritton
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 20, 2015
+.Dt JAIL 8
+.Os
+.Sh NAME
+.Nm jail
+.Nd "manage system jails"
+.Sh SYNOPSIS
+.Nm
+.Op Fl dhilqv
+.Op Fl J Ar jid_file
+.Op Fl u Ar username
+.Op Fl U Ar username
+.Op Fl cmr
+.Ar param Ns = Ns Ar value ...
+.Op Cm command Ns = Ns Ar command ...
+.Nm
+.Op Fl dqv
+.Op Fl f Ar conf_file
+.Op Fl p Ar limit
+.Op Fl cmr
+.Op Ar jail
+.Nm
+.Op Fl qv
+.Op Fl f Ar conf_file
+.Op Fl rR
+.Op Cm * | Ar jail ...
+.Nm
+.Op Fl dhilqv
+.Op Fl J Ar jid_file
+.Op Fl u Ar username
+.Op Fl U Ar username
+.Op Fl n Ar jailname
+.Op Fl s Ar securelevel
+.Op Ar path hostname [ Ar ip Ns [ Ns Ar ,... Ns ]] Ar command ...
+.Sh DESCRIPTION
+The
+.Nm
+utility creates new jails, or modifies or removes existing jails.
+A jail
+.Pq or Dq prison
+is specified via parameters on the command line, or in the
+.Xr jail.conf 5
+file.
+.Pp
+At least one of the options
+.Fl c ,
+.Fl m
+or
+.Fl r
+must be specified.
+These options are used alone or in combination to describe the operation to
+perform:
+.Bl -tag -width indent
+.It Fl c
+Create a new jail.
+The jail
+.Va jid
+and
+.Va name
+parameters (if specified on the command line)
+must not refer to an existing jail.
+.It Fl m
+Modify an existing jail.
+One of the
+.Va jid
+or
+.Va name
+parameters must exist and refer to an existing jail.
+Some parameters may not be changed on a running jail.
+.It Fl r
+Remove the
+.Ar jail
+specified by jid or name.
+All jailed processes are killed, and all jails that are
+children of this jail are also
+removed.
+.It Fl rc
+Restart an existing jail.
+The jail is first removed and then re-created, as if
+.Dq Nm Fl r
+and
+.Dq Nm Fl c
+were run in succession.
+.It Fl cm
+Create a jail if it does not exist, or modify the jail if it does exist.
+.It Fl mr
+Modify an existing jail.
+The jail may be restarted if necessary to modify parameters than could
+not otherwise be changed.
+.It Fl cmr
+Create a jail if it doesn't exist, or modify (and possibly restart) the
+jail if it does exist.
+.El
+.Pp
+Other available options are:
+.Bl -tag -width indent
+.It Fl d
+Allow making changes to a dying jail, equivalent to the
+.Va allow.dying
+parameter.
+.It Fl f Ar conf_file
+Use configuration file
+.Ar conf_file
+instead of the default
+.Pa /etc/jail.conf .
+.It Fl h
+Resolve the
+.Va host.hostname
+parameter (or
+.Va hostname )
+and add all IP addresses returned by the resolver
+to the list of addresses for this jail.
+This is equivalent to the
+.Va ip_hostname
+parameter.
+.It Fl i
+Output (only) the jail identifier of the newly created jail(s).
+This implies the
+.Fl q
+option.
+.It Fl J Ar jid_file
+Write a
+.Ar jid_file
+file, containing the parameters used to start the jail.
+.It Fl l
+Run commands in a clean environment.
+This is deprecated and is equivalent to the exec.clean parameter.
+.It Fl n Ar jailname
+Set the jail's name.
+This is deprecated and is equivalent to the
+.Va name
+parameter.
+.It Fl p Ar limit
+Limit the number of commands from
+.Va exec.*
+that can run simultaneously.
+.It Fl q
+Suppress the message printed whenever a jail is created, modified or removed.
+Only error messages will be printed.
+.It Fl R
+A variation of the
+.Fl r
+option that removes an existing jail without using the configuration file.
+No removal-related parameters for this jail will be used \(em the jail will
+simply be removed.
+.It Fl s Ar securelevel
+Set the
+.Va kern.securelevel
+MIB entry to the specified value inside the newly created jail.
+This is deprecated and is equivalent to the
+.Va securelevel
+parameter.
+.It Fl u Ar username
+The user name from host environment as whom jailed commands should run.
+This is deprecated and is equivalent to the
+.Va exec.jail_user
+and
+.Va exec.system_jail_user
+parameters.
+.It Fl U Ar username
+The user name from the jailed environment as whom jailed commands should run.
+This is deprecated and is equivalent to the
+.Va exec.jail_user
+parameter.
+.It Fl v
+Print a message on every operation, such as running commands and
+mounting filesystems.
+.El
+.Pp
+If no arguments are given after the options, the operation (except
+remove) will be performed on all jails specified in the
+.Xr jail.conf 5
+file.
+A single argument of a jail name will operate only on the specified jail.
+The
+.Fl r
+and
+.Fl R
+options can also remove running jails that aren't in the
+.Xr jail.conf 5
+file, specified by name or jid.
+.Pp
+An argument of
+.Dq *
+is a wildcard that will operate on all jails, regardless of whether
+they appear in
+.Xr jail.conf 5 ;
+this is the surest way for
+.Fl r
+to remove all jails.
+If hierarchical jails exist, a partial-matching wildcard definition may
+be specified.
+For example, an argument of
+.Dq foo.*
+would apply to jails with names like
+.Dq foo.bar
+and
+.Dq foo.bar.baz .
+.Pp
+A jail may be specified with parameters directly on the command line.
+In this case, the
+.Xr jail.conf 5
+file will not be used.
+For backward compatibility, the command line may also have four fixed
+parameters, without names:
+.Ar path ,
+.Ar hostname ,
+.Ar ip ,
+and
+.Ar command .
+This mode will always create a new jail, and the
+.Fl c
+and
+.Fl m
+options do not apply (and must not be present).
+.Ss Jail Parameters
+Parameters in the
+.Xr jail.conf 5
+file, or on the command line, are generally of the form
+.Dq name=value .
+Some parameters are boolean, and do not have a value but are set by the
+name alone with or without a
+.Dq no
+prefix, e.g.
+.Va persist
+or
+.Va nopersist .
+They can also be given the values
+.Dq true
+and
+.Dq false .
+Other parameters may have more than one value, specified as a
+comma-separated list or with
+.Dq +=
+in the configuration file (see
+.Xr jail.conf 5
+for details).
+.Pp
+The
+.Nm
+utility recognizes two classes of parameters.
+There are the true jail
+parameters that are passed to the kernel when the jail is created,
+which can be seen with
+.Xr jls 8 ,
+and can (usually) be changed with
+.Dq Nm Fl m .
+Then there are pseudo-parameters that are only used by
+.Nm
+itself.
+.Pp
+Jails have a set of core parameters, and kernel modules can add their own
+jail parameters.
+The current set of available parameters can be retrieved via
+.Dq Nm sysctl Fl d Va security.jail.param .
+Any parameters not set will be given default values, often based on the
+current environment.
+The core parameters are:
+.Bl -tag -width indent
+.It Va jid
+The jail identifier.
+This will be assigned automatically to a new jail (or can be explicitly
+set), and can be used to identify the jail for later modification, or
+for such commands as
+.Xr jls 8
+or
+.Xr jexec 8 .
+.It Va name
+The jail name.
+This is an arbitrary string that identifies a jail (except it may not
+contain a
+.Sq \&. ) .
+Like the
+.Va jid ,
+it can be passed to later
+.Nm
+commands, or to
+.Xr jls 8
+or
+.Xr jexec 8 .
+If no
+.Va name
+is supplied, a default is assumed that is the same as the
+.Va jid .
+The
+.Va name
+parameter is implied by the
+.Xr jail.conf 5
+file format, and need not be explicitly set when using the configuration
+file.
+.It Va path
+The directory which is to be the root of the jail.
+Any commands run inside the jail, either by
+.Nm
+or from
+.Xr jexec 8 ,
+are run from this directory.
+.It Va ip4.addr
+A list of IPv4 addresses assigned to the jail.
+If this is set, the jail is restricted to using only these addresses.
+Any attempts to use other addresses fail, and attempts to use wildcard
+addresses silently use the jailed address instead.
+For IPv4 the first address given will be used as the source address
+when source address selection on unbound sockets cannot find a better
+match.
+It is only possible to start multiple jails with the same IP address
+if none of the jails has more than this single overlapping IP address
+assigned to itself.
+.It Va ip4.saddrsel
+A boolean option to change the formerly mentioned behaviour and disable
+IPv4 source address selection for the jail in favour of the primary
+IPv4 address of the jail.
+Source address selection is enabled by default for all jails and the
+.Va ip4.nosaddrsel
+setting of a parent jail is not inherited for any child jails.
+.It Va ip4
+Control the availability of IPv4 addresses.
+Possible values are
+.Dq inherit
+to allow unrestricted access to all system addresses,
+.Dq new
+to restrict addresses via
+.Va ip4.addr ,
+and
+.Dq disable
+to stop the jail from using IPv4 entirely.
+Setting the
+.Va ip4.addr
+parameter implies a value of
+.Dq new .
+.It Va ip6.addr , Va ip6.saddrsel , Va ip6
+A set of IPv6 options for the jail, the counterparts to
+.Va ip4.addr ,
+.Va ip4.saddrsel
+and
+.Va ip4
+above.
+.It Va vnet
+Create the jail with its own virtual network stack,
+with its own network interfaces, addresses, routing table, etc.
+The kernel must have been compiled with the
+.Sy VIMAGE option
+for this to be available.
+Possible values are
+.Dq inherit
+to use the system network stack, possibly with restricted IP addresses,
+and
+.Dq new
+to create a new network stack.
+.It Va host.hostname
+The hostname of the jail.
+Other similar parameters are
+.Va host.domainname ,
+.Va host.hostuuid
+and
+.Va host.hostid .
+.It Va host
+Set the origin of hostname and related information.
+Possible values are
+.Dq inherit
+to use the system information and
+.Dq new
+for the jail to use the information from the above fields.
+Setting any of the above fields implies a value of
+.Dq new .
+.It Va securelevel
+The value of the jail's
+.Va kern.securelevel
+sysctl.
+A jail never has a lower securelevel than its parent system, but by
+setting this parameter it may have a higher one.
+If the system securelevel is changed, any jail securelevels will be at
+least as secure.
+.It Va devfs_ruleset
+The number of the devfs ruleset that is enforced for mounting devfs in
+this jail.
+A value of zero (default) means no ruleset is enforced.
+Descendant jails inherit the parent jail's devfs ruleset enforcement.
+Mounting devfs inside a jail is possible only if the
+.Va allow.mount
+and
+.Va allow.mount.devfs
+permissions are effective and
+.Va enforce_statfs
+is set to a value lower than 2.
+Devfs rules and rulesets cannot be viewed or modified from inside a jail.
+.Pp
+NOTE: It is important that only appropriate device nodes in devfs be
+exposed to a jail; access to disk devices in the jail may permit processes
+in the jail to bypass the jail sandboxing by modifying files outside of
+the jail.
+See
+.Xr devfs 8
+for information on how to use devfs rules to limit access to entries
+in the per-jail devfs.
+A simple devfs ruleset for jails is available as ruleset #4 in
+.Pa /etc/defaults/devfs.rules .
+.It Va children.max
+The number of child jails allowed to be created by this jail (or by
+other jails under this jail).
+This limit is zero by default, indicating the jail is not allowed to
+create child jails.
+See the
+.Sx "Hierarchical Jails"
+section for more information.
+.It Va children.cur
+The number of descendants of this jail, including its own child jails
+and any jails created under them.
+.It Va enforce_statfs
+This determines what information processes in a jail are able to get
+about mount points.
+It affects the behaviour of the following syscalls:
+.Xr statfs 2 ,
+.Xr fstatfs 2 ,
+.Xr getfsstat 2 ,
+and
+.Xr fhstatfs 2
+(as well as similar compatibility syscalls).
+When set to 0, all mount points are available without any restrictions.
+When set to 1, only mount points below the jail's chroot directory are
+visible.
+In addition to that, the path to the jail's chroot directory is removed
+from the front of their pathnames.
+When set to 2 (default), above syscalls can operate only on a mount-point
+where the jail's chroot directory is located.
+.It Va persist
+Setting this boolean parameter allows a jail to exist without any
+processes.
+Normally, a command is run as part of jail creation, and then the jail
+is destroyed as its last process exits.
+A new jail must have either the
+.Va persist
+parameter or
+.Va exec.start
+or
+.Va command
+pseudo-parameter set.
+.It Va cpuset.id
+The ID of the cpuset associated with this jail (read-only).
+.It Va dying
+This is true if the jail is in the process of shutting down (read-only).
+.It Va parent
+The
+.Va jid
+of the parent of this jail, or zero if this is a top-level jail
+(read-only).
+.It Va osrelease
+The string for the jail's
+.Va kern.osrelease
+sysctl and uname -r.
+.It Va osreldate
+The number for the jail's
+.Va kern.osreldate
+and uname -K.
+.It Va allow.*
+Some restrictions of the jail environment may be set on a per-jail
+basis.
+With the exception of
+.Va allow.set_hostname ,
+these boolean parameters are off by default.
+.Bl -tag -width indent
+.It Va allow.set_hostname
+The jail's hostname may be changed via
+.Xr hostname 1
+or
+.Xr sethostname 3 .
+.It Va allow.sysvipc
+A process within the jail has access to System V IPC primitives.
+In the current jail implementation, System V primitives share a single
+namespace across the host and jail environments, meaning that processes
+within a jail would be able to communicate with (and potentially interfere
+with) processes outside of the jail, and in other jails.
+.It Va allow.raw_sockets
+The jail root is allowed to create raw sockets.
+Setting this parameter allows utilities like
+.Xr ping 8
+and
+.Xr traceroute 8
+to operate inside the jail.
+If this is set, the source IP addresses are enforced to comply
+with the IP address bound to the jail, regardless of whether or not
+the
+.Dv IP_HDRINCL
+flag has been set on the socket.
+Since raw sockets can be used to configure and interact with various
+network subsystems, extra caution should be used where privileged access
+to jails is given out to untrusted parties.
+.It Va allow.chflags
+Normally, privileged users inside a jail are treated as unprivileged by
+.Xr chflags 2 .
+When this parameter is set, such users are treated as privileged, and
+may manipulate system file flags subject to the usual constraints on
+.Va kern.securelevel .
+.It Va allow.mount
+privileged users inside the jail will be able to mount and unmount file
+system types marked as jail-friendly.
+The
+.Xr lsvfs 1
+command can be used to find file system types available for mount from
+within a jail.
+This permission is effective only if
+.Va enforce_statfs
+is set to a value lower than 2.
+.It Va allow.mount.devfs
+privileged users inside the jail will be able to mount and unmount the
+devfs file system.
+This permission is effective only together with
+.Va allow.mount
+and only when
+.Va enforce_statfs
+is set to a value lower than 2.
+The devfs ruleset should be restricted from the default by using the
+.Va devfs_ruleset
+option.
+.It Va allow.mount.fdescfs
+privileged users inside the jail will be able to mount and unmount the
+fdescfs file system.
+This permission is effective only together with
+.Va allow.mount
+and only when
+.Va enforce_statfs
+is set to a value lower than 2.
+.It Va allow.mount.nullfs
+privileged users inside the jail will be able to mount and unmount the
+nullfs file system.
+This permission is effective only together with
+.Va allow.mount
+and only when
+.Va enforce_statfs
+is set to a value lower than 2.
+.It Va allow.mount.procfs
+privileged users inside the jail will be able to mount and unmount the
+procfs file system.
+This permission is effective only together with
+.Va allow.mount
+and only when
+.Va enforce_statfs
+is set to a value lower than 2.
+.It Va allow.mount.linprocfs
+privileged users inside the jail will be able to mount and unmount the
+linprocfs file system.
+This permission is effective only together with
+.Va allow.mount
+and only when
+.Va enforce_statfs
+is set to a value lower than 2.
+.It Va allow.mount.linsysfs
+privileged users inside the jail will be able to mount and unmount the
+linsysfs file system.
+This permission is effective only together with
+.Va allow.mount
+and only when
+.Va enforce_statfs
+is set to a value lower than 2.
+.It Va allow.mount.tmpfs
+privileged users inside the jail will be able to mount and unmount the
+tmpfs file system.
+This permission is effective only together with
+.Va allow.mount
+and only when
+.Va enforce_statfs
+is set to a value lower than 2.
+.It Va allow.mount.zfs
+privileged users inside the jail will be able to mount and unmount the
+ZFS file system.
+This permission is effective only together with
+.Va allow.mount
+and only when
+.Va enforce_statfs
+is set to a value lower than 2.
+See
+.Xr zfs 8
+for information on how to configure the ZFS filesystem to operate from
+within a jail.
+.It Va allow.quotas
+The jail root may administer quotas on the jail's filesystem(s).
+This includes filesystems that the jail may share with other jails or
+with non-jailed parts of the system.
+.It Va allow.socket_af
+Sockets within a jail are normally restricted to IPv4, IPv6, local
+(UNIX), and route. This allows access to other protocol stacks that
+have not had jail functionality added to them.
+.El
+.El
+.Pp
+There are pseudo-parameters that are not passed to the kernel, but are
+used by
+.Nm
+to set up the jail environment, often by running specified commands
+when jails are created or removed.
+The
+.Va exec.*
+command parameters are
+.Xr sh 1
+command lines that are run in either the system or jail environment.
+They may be given multiple values, which would run the specified
+commands in sequence.
+All commands must succeed (return a zero exit status), or the jail will
+not be created or removed, as appropriate.
+.Pp
+The pseudo-parameters are:
+.Bl -tag -width indent
+.It Va exec.prestart
+Command(s) to run in the system environment before a jail is created.
+.It Va exec.start
+Command(s) to run in the jail environment when a jail is created.
+A typical command to run is
+.Dq sh /etc/rc .
+.It Va command
+A synonym for
+.Va exec.start
+for use when specifying a jail directly on the command line.
+Unlike other parameters whose value is a single string,
+.Va command
+uses the remainder of the
+.Nm
+command line as its own arguments.
+.It Va exec.poststart
+Command(s) to run in the system environment after a jail is created,
+and after any
+.Va exec.start
+commands have completed.
+.It Va exec.prestop
+Command(s) to run in the system environment before a jail is removed.
+.It Va exec.stop
+Command(s) to run in the jail environment before a jail is removed,
+and after any
+.Va exec.prestop
+commands have completed.
+A typical command to run is
+.Dq sh /etc/rc.shutdown .
+.It Va exec.poststop
+Command(s) to run in the system environment after a jail is removed.
+.It Va exec.clean
+Run commands in a clean environment.
+The environment is discarded except for
+.Ev HOME , SHELL , TERM
+and
+.Ev USER .
+.Ev HOME
+and
+.Ev SHELL
+are set to the target login's default values.
+.Ev USER
+is set to the target login.
+.Ev TERM
+is imported from the current environment.
+The environment variables from the login class capability database for the
+target login are also set.
+.It Va exec.jail_user
+The user to run commands as, when running in the jail environment.
+The default is to run the commands as the current user.
+.It Va exec.system_jail_user
+This boolean option looks for the
+.Va exec.jail_user
+in the system
+.Xr passwd 5
+file, instead of in the jail's file.
+.It Va exec.system_user
+The user to run commands as, when running in the system environment.
+The default is to run the commands as the current user.
+.It Va exec.timeout
+The maximum amount of time to wait for a command to complete, in
+seconds.
+If a command is still running after this timeout has passed,
+the jail will not be created or removed, as appropriate.
+.It Va exec.consolelog
+A file to direct command output (stdout and stderr) to.
+.It Va exec.fib
+The FIB (routing table) to set when running commands inside the jail.
+.It Va stop.timeout
+The maximum amount of time to wait for a jail's processes to exit
+after sending them a
+.Dv SIGTERM
+signal (which happens after the
+.Va exec.stop
+commands have completed).
+After this many seconds have passed, the jail will be removed, which
+will kill any remaining processes.
+If this is set to zero, no
+.Dv SIGTERM
+is sent and the jail is immediately removed.
+The default is 10 seconds.
+.It Va interface
+A network interface to add the jail's IP addresses
+.Va ( ip4.addr
+and
+.Va ip6.addr )
+to.
+An alias for each address will be added to the interface before the
+jail is created, and will be removed from the interface after the
+jail is removed.
+.It Va ip4.addr
+In addition to the IP addresses that are passed to the kernel, an
+interface, netmask and additional parameters (as supported by
+.Xr ifconfig 8 Ns )
+may also be specified, in the form
+.Dq Ar interface Ns | Ns Ar ip-address Ns / Ns Ar netmask param ... .
+If an interface is given before the IP address, an alias for the address
+will be added to that interface, as it is with the
+.Va interface
+parameter.
+If a netmask in either dotted-quad or CIDR form is given
+after an IP address, it will be used when adding the IP alias.
+If additional parameters are specified then they will also be used when
+adding the IP alias.
+.It Va ip6.addr
+In addition to the IP addresses that are passed to the kernel,
+an interface, prefix and additional parameters (as supported by
+.Xr ifconfig 8 Ns )
+may also be specified, in the form
+.Dq Ar interface Ns | Ns Ar ip-address Ns / Ns Ar prefix param ... .
+.It Va vnet.interface
+A network interface to give to a vnet-enabled jail after is it created.
+The interface will automatically be released when the jail is removed.
+.It Va ip_hostname
+Resolve the
+.Va host.hostname
+parameter and add all IP addresses returned by the resolver
+to the list of addresses
+.Po Va ip4.addr
+or
+.Va ip6.addr Pc
+for this jail.
+This may affect default address selection for outgoing IPv4 connections
+from jails.
+The address first returned by the resolver for each address family
+will be used as the primary address.
+.It Va mount
+A filesystem to mount before creating the jail (and to unmount after
+removing it), given as a single
+.Xr fstab 5
+line.
+.It Va mount.fstab
+An
+.Xr fstab 5
+format file containing filesystems to mount before creating a jail.
+.It Va mount.devfs
+Mount a
+.Xr devfs 5
+filesystem on the chrooted
+.Pa /dev
+directory, and apply the ruleset in the
+.Va devfs_ruleset
+parameter (or a default of ruleset 4: devfsrules_jail)
+to restrict the devices visible inside the jail.
+.It Va mount.fdescfs
+Mount a
+.Xr fdescfs 5
+filesystem on the chrooted
+.Pa /dev/fd
+directory.
+.It Va mount.procfs
+Mount a
+.Xr procfs 5
+filesystem on the chrooted
+.Pa /proc
+directory.
+.It Va allow.dying
+Allow making changes to a
+.Va dying
+jail.
+.It Va depend
+Specify a jail (or jails) that this jail depends on.
+Any such jails must be fully created, up to the last
+.Va exec.poststart
+command, before any action will taken to create this jail.
+When jails are removed the opposite is true:
+this jail must be fully removed, up to the last
+.Va exec.poststop
+command, before the jail(s) it depends on are stopped.
+.El
+.Sh EXAMPLES
+Jails are typically set up using one of two philosophies: either to
+constrain a specific application (possibly running with privilege), or
+to create a
+.Dq "virtual system image"
+running a variety of daemons and services.
+In both cases, a fairly complete file system install of
+.Fx
+is
+required, so as to provide the necessary command line tools, daemons,
+libraries, application configuration files, etc.
+However, for a virtual server configuration, a fair amount of
+additional work is required so as to replace the
+.Dq boot
+process.
+This manual page documents the configuration steps necessary to support
+either of these steps, although the configuration steps may need to be
+refined based on local requirements.
+.Ss "Setting up a Jail Directory Tree"
+To set up a jail directory tree containing an entire
+.Fx
+distribution, the following
+.Xr sh 1
+command script can be used:
+.Bd -literal
+D=/here/is/the/jail
+cd /usr/src
+mkdir -p $D
+make world DESTDIR=$D
+make distribution DESTDIR=$D
+.Ed
+.Pp
+In many cases this example would put far more in the jail than needed.
+In the other extreme case a jail might contain only one file:
+the executable to be run in the jail.
+.Pp
+We recommend experimentation, and caution that it is a lot easier to
+start with a
+.Dq fat
+jail and remove things until it stops working,
+than it is to start with a
+.Dq thin
+jail and add things until it works.
+.Ss "Setting Up a Jail"
+Do what was described in
+.Sx "Setting Up a Jail Directory Tree"
+to build the jail directory tree.
+For the sake of this example, we will
+assume you built it in
+.Pa /data/jail/testjail ,
+for a jail named
+.Dq testjail .
+Substitute below as needed with your
+own directory, IP address, and hostname.
+.Ss "Setting up the Host Environment"
+First, set up the real system's environment to be
+.Dq jail-friendly .
+For consistency, we will refer to the parent box as the
+.Dq "host environment" ,
+and to the jailed virtual machine as the
+.Dq "jail environment" .
+Since jails are implemented using IP aliases, one of the first things to do
+is to disable IP services on the host system that listen on all local
+IP addresses for a service.
+If a network service is present in the host environment that binds all
+available IP addresses rather than specific IP addresses, it may service
+requests sent to jail IP addresses if the jail did not bind the port.
+This means changing
+.Xr inetd 8
+to only listen on the
+appropriate IP address, and so forth.
+Add the following to
+.Pa /etc/rc.conf
+in the host environment:
+.Bd -literal -offset indent
+sendmail_enable="NO"
+inetd_flags="-wW -a 192.0.2.23"
+rpcbind_enable="NO"
+.Ed
+.Pp
+.Li 192.0.2.23
+is the native IP address for the host system, in this example.
+Daemons that run out of
+.Xr inetd 8
+can be easily configured to use only the specified host IP address.
+Other daemons
+will need to be manually configured \(em for some this is possible through
+.Xr rc.conf 5
+flags entries; for others it is necessary to modify per-application
+configuration files, or to recompile the application.
+The following frequently deployed services must have their individual
+configuration files modified to limit the application to listening
+to a specific IP address:
+.Pp
+To configure
+.Xr sshd 8 ,
+it is necessary to modify
+.Pa /etc/ssh/sshd_config .
+.Pp
+To configure
+.Xr sendmail 8 ,
+it is necessary to modify
+.Pa /etc/mail/sendmail.cf .
+.Pp
+For
+.Xr named 8 ,
+it is necessary to modify
+.Pa /etc/namedb/named.conf .
+.Pp
+In addition, a number of services must be recompiled in order to run
+them in the host environment.
+This includes most applications providing services using
+.Xr rpc 3 ,
+such as
+.Xr rpcbind 8 ,
+.Xr nfsd 8 ,
+and
+.Xr mountd 8 .
+In general, applications for which it is not possible to specify which
+IP address to bind should not be run in the host environment unless they
+should also service requests sent to jail IP addresses.
+Attempting to serve
+NFS from the host environment may also cause confusion, and cannot be
+easily reconfigured to use only specific IPs, as some NFS services are
+hosted directly from the kernel.
+Any third-party network software running
+in the host environment should also be checked and configured so that it
+does not bind all IP addresses, which would result in those services also
+appearing to be offered by the jail environments.
+.Pp
+Once
+these daemons have been disabled or fixed in the host environment, it is
+best to reboot so that all daemons are in a known state, to reduce the
+potential for confusion later (such as finding that when you send mail
+to a jail, and its sendmail is down, the mail is delivered to the host,
+etc.).
+.Ss "Configuring the Jail"
+Start any jail for the first time without configuring the network
+interface so that you can clean it up a little and set up accounts.
+As
+with any machine (virtual or not), you will need to set a root password, time
+zone, etc.
+Some of these steps apply only if you intend to run a full virtual server
+inside the jail; others apply both for constraining a particular application
+or for running a virtual server.
+.Pp
+Start a shell in the jail:
+.Bd -literal -offset indent
+jail -c path=/data/jail/testjail mount.devfs \\
+ host.hostname=testhostname ip4.addr=192.0.2.100 \\
+ command=/bin/sh
+.Ed
+.Pp
+Assuming no errors, you will end up with a shell prompt within the jail.
+You can now run
+.Pa /usr/sbin/sysinstall
+and do the post-install configuration to set various configuration options,
+or perform these actions manually by editing
+.Pa /etc/rc.conf ,
+etc.
+.Pp
+.Bl -bullet -offset indent -compact
+.It
+Configure
+.Pa /etc/resolv.conf
+so that name resolution within the jail will work correctly.
+.It
+Run
+.Xr newaliases 1
+to quell
+.Xr sendmail 8
+warnings.
+.It
+Set a root password, probably different from the real host system.
+.It
+Set the timezone.
+.It
+Add accounts for users in the jail environment.
+.It
+Install any packages the environment requires.
+.El
+.Pp
+You may also want to perform any package-specific configuration (web servers,
+SSH servers, etc), patch up
+.Pa /etc/syslog.conf
+so it logs as you would like, etc.
+If you are not using a virtual server, you may wish to modify
+.Xr syslogd 8
+in the host environment to listen on the syslog socket in the jail
+environment; in this example, the syslog socket would be stored in
+.Pa /data/jail/testjail/var/run/log .
+.Pp
+Exit from the shell, and the jail will be shut down.
+.Ss "Starting the Jail"
+You are now ready to restart the jail and bring up the environment with
+all of its daemons and other programs.
+Create an entry for the jail in
+.Pa /etc/jail.conf :
+.Bd -literal -offset indent
+testjail {
+ path = /tmp/jail/testjail;
+ mount.devfs;
+ host.hostname = testhostname;
+ ip4.addr = 192.0.2.100;
+ interface = ed0;
+ exec.start = "/bin/sh /etc/rc";
+ exec.stop = "/bin/sh /etc/rc.shutdown";
+}
+.Ed
+.Pp
+To start a virtual server environment,
+.Pa /etc/rc
+is run to launch various daemons and services, and
+.Pa /etc/rc.shutdown
+is run to shut them down when the jail is removed.
+If you are running a single application in the jail,
+substitute the command used to start the application for
+.Dq /bin/sh /etc/rc ;
+there may be some script available to cleanly shut down the application,
+or it may be sufficient to go without a stop command, and have
+.Nm
+send
+.Dv SIGTERM
+to the application.
+.Pp
+Start the jail by running:
+.Bd -literal -offset indent
+jail -c testjail
+.Ed
+.Pp
+A few warnings may be produced; however, it should all work properly.
+You should be able to see
+.Xr inetd 8 ,
+.Xr syslogd 8 ,
+and other processes running within the jail using
+.Xr ps 1 ,
+with the
+.Ql J
+flag appearing beside jailed processes.
+To see an active list of jails, use
+.Xr jls 8 .
+If
+.Xr sshd 8
+is enabled in the jail environment, you should be able to
+.Xr ssh 1
+to the hostname or IP address of the jailed environment, and log
+in using the accounts you created previously.
+.Pp
+It is possible to have jails started at boot time.
+Please refer to the
+.Dq jail_*
+variables in
+.Xr rc.conf 5
+for more information.
+.Ss "Managing the Jail"
+Normal machine shutdown commands, such as
+.Xr halt 8 ,
+.Xr reboot 8 ,
+and
+.Xr shutdown 8 ,
+cannot be used successfully within the jail.
+To kill all processes from within a jail, you may use one of the
+following commands, depending on what you want to accomplish:
+.Bd -literal -offset indent
+kill -TERM -1
+kill -KILL -1
+.Ed
+.Pp
+This will send the
+.Dv SIGTERM
+or
+.Dv SIGKILL
+signals to all processes in the jail \(em be careful not to run this from
+the host environment!
+Once all of the jail's processes have died, unless the jail was created
+with the
+.Va persist
+parameter, the jail will be removed.
+Depending on
+the intended use of the jail, you may also want to run
+.Pa /etc/rc.shutdown
+from within the jail.
+.Pp
+To shut down the jail from the outside, simply remove it with
+.Nm
+.Ar -r ,
+which will run any commands specified by
+.Va exec.stop ,
+and then send
+.Dv SIGTERM
+and eventually
+.Dv SIGKILL
+to any remaining jailed processes.
+.Pp
+The
+.Pa /proc/ Ns Ar pid Ns Pa /status
+file contains, as its last field, the name of the jail in which the
+process runs, or
+.Dq Li -
+to indicate that the process is not running within a jail.
+The
+.Xr ps 1
+command also shows a
+.Ql J
+flag for processes in a jail.
+.Pp
+You can also list/kill processes based on their jail ID.
+To show processes and their jail ID, use the following command:
+.Pp
+.Dl "ps ax -o pid,jid,args"
+.Pp
+To show and then kill processes in jail number 3 use the following commands:
+.Bd -literal -offset indent
+pgrep -lfj 3
+pkill -j 3
+.Ed
+or:
+.Pp
+.Dl "killall -j 3"
+.Ss "Jails and File Systems"
+It is not possible to
+.Xr mount 8
+or
+.Xr umount 8
+any file system inside a jail unless the file system is marked
+jail-friendly, the jail's
+.Va allow.mount
+parameter is set, and the jail's
+.Va enforce_statfs
+parameter is lower than 2.
+.Pp
+Multiple jails sharing the same file system can influence each other.
+For example, a user in one jail can fill the file system,
+leaving no space for processes in the other jail.
+Trying to use
+.Xr quota 1
+to prevent this will not work either, as the file system quotas
+are not aware of jails but only look at the user and group IDs.
+This means the same user ID in two jails share a single file
+system quota.
+One would need to use one file system per jail to make this work.
+.Ss "Sysctl MIB Entries"
+The read-only entry
+.Va security.jail.jailed
+can be used to determine if a process is running inside a jail (value
+is one) or not (value is zero).
+.Pp
+The variable
+.Va security.jail.max_af_ips
+determines how may address per address family a jail may have.
+The default is 255.
+.Pp
+Some MIB variables have per-jail settings.
+Changes to these variables by a jailed process do not affect the host
+environment, only the jail environment.
+These variables are
+.Va kern.securelevel ,
+.Va kern.hostname ,
+.Va kern.domainname ,
+.Va kern.hostid ,
+and
+.Va kern.hostuuid .
+.Ss "Hierarchical Jails"
+By setting a jail's
+.Va children.max
+parameter, processes within a jail may be able to create jails of their own.
+These child jails are kept in a hierarchy, with jails only able to see and/or
+modify the jails they created (or those jails' children).
+Each jail has a read-only
+.Va parent
+parameter, containing the
+.Va jid
+of the jail that created it; a
+.Va jid
+of 0 indicates the jail is a child of the current jail (or is a top-level
+jail if the current process isn't jailed).
+.Pp
+Jailed processes are not allowed to confer greater permissions than they
+themselves are given, e.g., if a jail is created with
+.Va allow.nomount ,
+it is not able to create a jail with
+.Va allow.mount
+set.
+Similarly, such restrictions as
+.Va ip4.addr
+and
+.Va securelevel
+may not be bypassed in child jails.
+.Pp
+A child jail may in turn create its own child jails if its own
+.Va children.max
+parameter is set (remember it is zero by default).
+These jails are visible to and can be modified by their parent and all
+ancestors.
+.Pp
+Jail names reflect this hierarchy, with a full name being an MIB-type string
+separated by dots.
+For example, if a base system process creates a jail
+.Dq foo ,
+and a process under that jail creates another jail
+.Dq bar ,
+then the second jail will be seen as
+.Dq foo.bar
+in the base system (though it is only seen as
+.Dq bar
+to any processes inside jail
+.Dq foo ) .
+Jids on the other hand exist in a single space, and each jail must have a
+unique jid.
+.Pp
+Like the names, a child jail's
+.Va path
+appears relative to its creator's own
+.Va path .
+This is by virtue of the child jail being created in the chrooted
+environment of the first jail.
+.Sh SEE ALSO
+.Xr killall 1 ,
+.Xr lsvfs 1 ,
+.Xr newaliases 1 ,
+.Xr pgrep 1 ,
+.Xr pkill 1 ,
+.Xr ps 1 ,
+.Xr quota 1 ,
+.Xr jail_set 2 ,
+.Xr devfs 5 ,
+.Xr fdescfs 5 ,
+.Xr jail.conf 5 ,
+.Xr linprocfs 5 ,
+.Xr linsysfs 5 ,
+.Xr procfs 5 ,
+.Xr rc.conf 5 ,
+.Xr sysctl.conf 5 ,
+.Xr chroot 8 ,
+.Xr devfs 8 ,
+.Xr halt 8 ,
+.Xr ifconfig 8 ,
+.Xr inetd 8 ,
+.Xr jexec 8 ,
+.Xr jls 8 ,
+.Xr mount 8 ,
+.Xr named 8 ,
+.Xr reboot 8 ,
+.Xr rpcbind 8 ,
+.Xr sendmail 8 ,
+.Xr shutdown 8 ,
+.Xr sysctl 8 ,
+.Xr syslogd 8 ,
+.Xr umount 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 4.0 .
+Hierarchical/extensible jails were introduced in
+.Fx 8.0 .
+The configuration file was introduced in
+.Fx 9.1 .
+.Sh AUTHORS
+.An -nosplit
+The jail feature was written by
+.An Poul-Henning Kamp
+for R&D Associates
+.Pa http://www.rndassociates.com/
+who contributed it to
+.Fx .
+.Pp
+.An Robert Watson
+wrote the extended documentation, found a few bugs, added
+a few new features, and cleaned up the userland jail environment.
+.Pp
+.An Bjoern A. Zeeb
+added multi-IP jail support for IPv4 and IPv6 based on a patch
+originally done by
+.An Pawel Jakub Dawidek
+for IPv4.
+.Pp
+.An James Gritton
+added the extensible jail parameters, hierarchical jails,
+and the configuration file.
+.Sh BUGS
+It might be a good idea to add an
+address alias flag such that daemons listening on all IPs
+.Pq Dv INADDR_ANY
+will not bind on that address, which would facilitate building a safe
+host environment such that host daemons do not impose on services offered
+from within jails.
+Currently, the simplest answer is to minimize services
+offered on the host, possibly limiting it to services offered from
+.Xr inetd 8
+which is easily configurable.
+.Sh NOTES
+Great care should be taken when managing directories visible within the jail.
+For example, if a jailed process has its current working directory set to a
+directory that is moved out of the jail's chroot, then the process may gain
+access to the file space outside of the jail.
+It is recommended that directories always be copied, rather than moved, out
+of a jail.
+.Pp
+In addition, there are several ways in which an unprivileged user
+outside the jail can cooperate with a privileged user inside the jail
+and thereby obtain elevated privileges in the host environment.
+Most of these attacks can be mitigated by ensuring that the jail root
+is not accessible to unprivileged users in the host environment.
+Regardless, as a general rule, untrusted users with privileged access
+to a jail should not be given access to the host environment.
diff --git a/usr.sbin/jail/jail.c b/usr.sbin/jail/jail.c
new file mode 100644
index 0000000..e42afa4
--- /dev/null
+++ b/usr.sbin/jail/jail.c
@@ -0,0 +1,1018 @@
+/*-
+ * Copyright (c) 1999 Poul-Henning Kamp.
+ * Copyright (c) 2009-2012 James Gritton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/stat.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "jailp.h"
+
+#define JP_RDTUN(jp) (((jp)->jp_ctltype & CTLFLAG_RDTUN) == CTLFLAG_RDTUN)
+
+struct permspec {
+ const char *name;
+ enum intparam ipnum;
+ int rev;
+};
+
+const char *cfname;
+int iflag;
+int note_remove;
+int verbose;
+
+static void clear_persist(struct cfjail *j);
+static int update_jail(struct cfjail *j);
+static int rdtun_params(struct cfjail *j, int dofail);
+static void running_jid(struct cfjail *j, int dflag);
+static void jail_quoted_warnx(const struct cfjail *j, const char *name_msg,
+ const char *noname_msg);
+static int jailparam_set_note(const struct cfjail *j, struct jailparam *jp,
+ unsigned njp, int flags);
+static void print_jail(FILE *fp, struct cfjail *j, int oldcl);
+static void print_param(FILE *fp, const struct cfparam *p, int sep, int doname);
+static void quoted_print(FILE *fp, char *str);
+static void usage(void);
+
+static struct permspec perm_sysctl[] = {
+ { "security.jail.set_hostname_allowed", KP_ALLOW_SET_HOSTNAME, 0 },
+ { "security.jail.sysvipc_allowed", KP_ALLOW_SYSVIPC, 0 },
+ { "security.jail.allow_raw_sockets", KP_ALLOW_RAW_SOCKETS, 0 },
+ { "security.jail.chflags_allowed", KP_ALLOW_CHFLAGS, 0 },
+ { "security.jail.mount_allowed", KP_ALLOW_MOUNT, 0 },
+ { "security.jail.socket_unixiproute_only", KP_ALLOW_SOCKET_AF, 1 },
+};
+
+static const enum intparam startcommands[] = {
+ IP__NULL,
+#ifdef INET
+ IP__IP4_IFADDR,
+#endif
+#ifdef INET6
+ IP__IP6_IFADDR,
+#endif
+ IP_MOUNT,
+ IP__MOUNT_FROM_FSTAB,
+ IP_MOUNT_DEVFS,
+ IP_MOUNT_FDESCFS,
+ IP_MOUNT_PROCFS,
+ IP_EXEC_PRESTART,
+ IP__OP,
+ IP_VNET_INTERFACE,
+ IP_EXEC_START,
+ IP_COMMAND,
+ IP_EXEC_POSTSTART,
+ IP__NULL
+};
+
+static const enum intparam stopcommands[] = {
+ IP__NULL,
+ IP_EXEC_PRESTOP,
+ IP_EXEC_STOP,
+ IP_STOP_TIMEOUT,
+ IP__OP,
+ IP_EXEC_POSTSTOP,
+ IP_MOUNT_PROCFS,
+ IP_MOUNT_FDESCFS,
+ IP_MOUNT_DEVFS,
+ IP__MOUNT_FROM_FSTAB,
+ IP_MOUNT,
+#ifdef INET6
+ IP__IP6_IFADDR,
+#endif
+#ifdef INET
+ IP__IP4_IFADDR,
+#endif
+ IP__NULL
+};
+
+int
+main(int argc, char **argv)
+{
+ struct stat st;
+ FILE *jfp;
+ struct cfjail *j;
+ char *JidFile;
+ size_t sysvallen;
+ unsigned op, pi;
+ int ch, docf, error, i, oldcl, sysval;
+ int dflag, Rflag;
+ char enforce_statfs[4];
+#if defined(INET) || defined(INET6)
+ char *cs, *ncs;
+#endif
+#if defined(INET) && defined(INET6)
+ struct in6_addr addr6;
+#endif
+
+ op = 0;
+ dflag = Rflag = 0;
+ docf = 1;
+ cfname = CONF_FILE;
+ JidFile = NULL;
+
+ while ((ch = getopt(argc, argv, "cdf:hiJ:lmn:p:qrRs:u:U:v")) != -1) {
+ switch (ch) {
+ case 'c':
+ op |= JF_START;
+ break;
+ case 'd':
+ dflag = 1;
+ break;
+ case 'f':
+ cfname = optarg;
+ break;
+ case 'h':
+#if defined(INET) || defined(INET6)
+ add_param(NULL, NULL, IP_IP_HOSTNAME, NULL);
+#endif
+ docf = 0;
+ break;
+ case 'i':
+ iflag = 1;
+ verbose = -1;
+ break;
+ case 'J':
+ JidFile = optarg;
+ break;
+ case 'l':
+ add_param(NULL, NULL, IP_EXEC_CLEAN, NULL);
+ docf = 0;
+ break;
+ case 'm':
+ op |= JF_SET;
+ break;
+ case 'n':
+ add_param(NULL, NULL, KP_NAME, optarg);
+ docf = 0;
+ break;
+ case 'p':
+ paralimit = strtol(optarg, NULL, 10);
+ if (paralimit == 0)
+ paralimit = -1;
+ break;
+ case 'q':
+ verbose = -1;
+ break;
+ case 'r':
+ op |= JF_STOP;
+ break;
+ case 'R':
+ op |= JF_STOP;
+ Rflag = 1;
+ break;
+ case 's':
+ add_param(NULL, NULL, KP_SECURELEVEL, optarg);
+ docf = 0;
+ break;
+ case 'u':
+ add_param(NULL, NULL, IP_EXEC_JAIL_USER, optarg);
+ add_param(NULL, NULL, IP_EXEC_SYSTEM_JAIL_USER, NULL);
+ docf = 0;
+ break;
+ case 'U':
+ add_param(NULL, NULL, IP_EXEC_JAIL_USER, optarg);
+ add_param(NULL, NULL, IP_EXEC_SYSTEM_JAIL_USER,
+ "false");
+ docf = 0;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Find out which of the four command line styles this is. */
+ oldcl = 0;
+ if (!op) {
+ /* Old-style command line with four fixed parameters */
+ if (argc < 4 || argv[0][0] != '/')
+ usage();
+ op = JF_START;
+ docf = 0;
+ oldcl = 1;
+ add_param(NULL, NULL, KP_PATH, argv[0]);
+ add_param(NULL, NULL, KP_HOST_HOSTNAME, argv[1]);
+#if defined(INET) || defined(INET6)
+ if (argv[2][0] != '\0') {
+ for (cs = argv[2];; cs = ncs + 1) {
+ ncs = strchr(cs, ',');
+ if (ncs)
+ *ncs = '\0';
+ add_param(NULL, NULL,
+#if defined(INET) && defined(INET6)
+ inet_pton(AF_INET6, cs, &addr6) == 1
+ ? KP_IP6_ADDR : KP_IP4_ADDR,
+#elif defined(INET)
+ KP_IP4_ADDR,
+#elif defined(INET6)
+ KP_IP6_ADDR,
+#endif
+ cs);
+ if (!ncs)
+ break;
+ }
+ }
+#endif
+ for (i = 3; i < argc; i++)
+ add_param(NULL, NULL, IP_COMMAND, argv[i]);
+ /* Emulate the defaults from security.jail.* sysctls. */
+ sysvallen = sizeof(sysval);
+ if (sysctlbyname("security.jail.jailed", &sysval, &sysvallen,
+ NULL, 0) == 0 && sysval == 0) {
+ for (pi = 0; pi < sizeof(perm_sysctl) /
+ sizeof(perm_sysctl[0]); pi++) {
+ sysvallen = sizeof(sysval);
+ if (sysctlbyname(perm_sysctl[pi].name,
+ &sysval, &sysvallen, NULL, 0) == 0)
+ add_param(NULL, NULL,
+ perm_sysctl[pi].ipnum,
+ (sysval ? 1 : 0) ^
+ perm_sysctl[pi].rev
+ ? NULL : "false");
+ }
+ sysvallen = sizeof(sysval);
+ if (sysctlbyname("security.jail.enforce_statfs",
+ &sysval, &sysvallen, NULL, 0) == 0) {
+ snprintf(enforce_statfs,
+ sizeof(enforce_statfs), "%d", sysval);
+ add_param(NULL, NULL, KP_ENFORCE_STATFS,
+ enforce_statfs);
+ }
+ }
+ } else if (op == JF_STOP) {
+ /* Jail remove, perhaps using the config file */
+ if (!docf || argc == 0)
+ usage();
+ if (!Rflag)
+ for (i = 0; i < argc; i++)
+ if (strchr(argv[i], '='))
+ usage();
+ if ((docf = !Rflag &&
+ (!strcmp(cfname, "-") || stat(cfname, &st) == 0)))
+ load_config();
+ note_remove = docf || argc > 1 || wild_jail_name(argv[0]);
+ } else if (argc > 1 || (argc == 1 && strchr(argv[0], '='))) {
+ /* Single jail specified on the command line */
+ if (Rflag)
+ usage();
+ docf = 0;
+ for (i = 0; i < argc; i++) {
+ if (!strncmp(argv[i], "command", 7) &&
+ (argv[i][7] == '\0' || argv[i][7] == '=')) {
+ if (argv[i][7] == '=')
+ add_param(NULL, NULL, IP_COMMAND,
+ argv[i] + 8);
+ for (i++; i < argc; i++)
+ add_param(NULL, NULL, IP_COMMAND,
+ argv[i]);
+ }
+#ifdef INET
+ else if (!strncmp(argv[i], "ip4.addr=", 9)) {
+ for (cs = argv[i] + 9;; cs = ncs + 1) {
+ ncs = strchr(cs, ',');
+ if (ncs)
+ *ncs = '\0';
+ add_param(NULL, NULL, KP_IP4_ADDR, cs);
+ if (!ncs)
+ break;
+ }
+ }
+#endif
+#ifdef INET6
+ else if (!strncmp(argv[i], "ip6.addr=", 9)) {
+ for (cs = argv[i] + 9;; cs = ncs + 1) {
+ ncs = strchr(cs, ',');
+ if (ncs)
+ *ncs = '\0';
+ add_param(NULL, NULL, KP_IP6_ADDR, cs);
+ if (!ncs)
+ break;
+ }
+ }
+#endif
+ else
+ add_param(NULL, NULL, 0, argv[i]);
+ }
+ } else {
+ /* From the config file, perhaps with a specified jail */
+ if (Rflag || !docf)
+ usage();
+ load_config();
+ }
+
+ /* Find out which jails will be run. */
+ dep_setup(docf);
+ error = 0;
+ if (op == JF_STOP) {
+ for (i = 0; i < argc; i++)
+ if (start_state(argv[i], docf, op, Rflag) < 0)
+ error = 1;
+ } else {
+ if (start_state(argv[0], docf, op, 0) < 0)
+ exit(1);
+ }
+
+ jfp = NULL;
+ if (JidFile != NULL) {
+ jfp = fopen(JidFile, "w");
+ if (jfp == NULL)
+ err(1, "open %s", JidFile);
+ setlinebuf(jfp);
+ }
+ setlinebuf(stdout);
+
+ /*
+ * The main loop: Get an available jail and perform the required
+ * operation on it. When that is done, the jail may be finished,
+ * or it may go back for the next step.
+ */
+ while ((j = next_jail()))
+ {
+ if (j->flags & JF_FAILED) {
+ error = 1;
+ if (j->comparam == NULL) {
+ dep_done(j, 0);
+ continue;
+ }
+ }
+ if (!(j->flags & JF_PARAMS))
+ {
+ j->flags |= JF_PARAMS;
+ if (dflag)
+ add_param(j, NULL, IP_ALLOW_DYING, NULL);
+ if (check_intparams(j) < 0)
+ continue;
+ if ((j->flags & (JF_START | JF_SET)) &&
+ import_params(j) < 0)
+ continue;
+ }
+ if (!j->jid)
+ running_jid(j,
+ (j->flags & (JF_SET | JF_DEPEND)) == JF_SET
+ ? dflag || bool_param(j->intparams[IP_ALLOW_DYING])
+ : 0);
+ if (finish_command(j))
+ continue;
+
+ switch (j->flags & JF_OP_MASK) {
+ /*
+ * These operations just turn into a different op
+ * depending on the jail's current status.
+ */
+ case JF_START_SET:
+ j->flags = j->jid < 0 ? JF_START : JF_SET;
+ break;
+ case JF_SET_RESTART:
+ if (j->jid < 0) {
+ jail_quoted_warnx(j, "not found",
+ "no jail specified");
+ failed(j);
+ continue;
+ }
+ j->flags = rdtun_params(j, 0) ? JF_RESTART : JF_SET;
+ if (j->flags == JF_RESTART)
+ dep_reset(j);
+ break;
+ case JF_START_SET_RESTART:
+ j->flags = j->jid < 0 ? JF_START
+ : rdtun_params(j, 0) ? JF_RESTART : JF_SET;
+ if (j->flags == JF_RESTART)
+ dep_reset(j);
+ }
+
+ switch (j->flags & JF_OP_MASK) {
+ case JF_START:
+ if (j->comparam == NULL) {
+ if (j->jid > 0 &&
+ !(j->flags & (JF_DEPEND | JF_WILD))) {
+ jail_quoted_warnx(j, "already exists",
+ NULL);
+ failed(j);
+ continue;
+ }
+ if (dep_check(j))
+ continue;
+ if (j->jid > 0)
+ goto jail_create_done;
+ j->comparam = startcommands;
+ j->comstring = NULL;
+ }
+ if (next_command(j))
+ continue;
+ jail_create_done:
+ clear_persist(j);
+ if (jfp != NULL)
+ print_jail(jfp, j, oldcl);
+ dep_done(j, 0);
+ break;
+
+ case JF_SET:
+ if (j->jid < 0 && !(j->flags & JF_DEPEND)) {
+ jail_quoted_warnx(j, "not found",
+ "no jail specified");
+ failed(j);
+ continue;
+ }
+ if (dep_check(j))
+ continue;
+ if (!(j->flags & JF_DEPEND)) {
+ if (rdtun_params(j, 1) < 0 ||
+ update_jail(j) < 0)
+ continue;
+ if (verbose >= 0 && (j->name || verbose > 0))
+ jail_note(j, "updated\n");
+ }
+ dep_done(j, 0);
+ break;
+
+ case JF_STOP:
+ case JF_RESTART:
+ if (j->comparam == NULL) {
+ if (dep_check(j))
+ continue;
+ if (j->jid < 0) {
+ if (!(j->flags & (JF_DEPEND|JF_WILD))) {
+ if (verbose >= 0)
+ jail_quoted_warnx(j,
+ "not found", NULL);
+ failed(j);
+ }
+ goto jail_remove_done;
+ }
+ j->comparam = stopcommands;
+ j->comstring = NULL;
+ } else if ((j->flags & JF_FAILED) && j->jid > 0)
+ goto jail_remove_done;
+ if (next_command(j))
+ continue;
+ jail_remove_done:
+ dep_done(j, 0);
+ if ((j->flags & (JF_START | JF_FAILED)) == JF_START) {
+ j->comparam = NULL;
+ j->flags &= ~JF_STOP;
+ dep_reset(j);
+ requeue(j, j->ndeps ? &depend : &ready);
+ }
+ break;
+ }
+ }
+
+ if (jfp != NULL)
+ fclose(jfp);
+ exit(error);
+}
+
+/*
+ * Mark a jail's failure for future handling.
+ */
+void
+failed(struct cfjail *j)
+{
+ j->flags |= JF_FAILED;
+ TAILQ_REMOVE(j->queue, j, tq);
+ TAILQ_INSERT_HEAD(&ready, j, tq);
+ j->queue = &ready;
+}
+
+/*
+ * Exit slightly more gracefully when out of memory.
+ */
+void *
+emalloc(size_t size)
+{
+ void *p;
+
+ p = malloc(size);
+ if (!p)
+ err(1, "malloc");
+ return p;
+}
+
+void *
+erealloc(void *ptr, size_t size)
+{
+ void *p;
+
+ p = realloc(ptr, size);
+ if (!p)
+ err(1, "malloc");
+ return p;
+}
+
+char *
+estrdup(const char *str)
+{
+ char *ns;
+
+ ns = strdup(str);
+ if (!ns)
+ err(1, "malloc");
+ return ns;
+}
+
+/*
+ * Print a message including an optional jail name.
+ */
+void
+jail_note(const struct cfjail *j, const char *fmt, ...)
+{
+ va_list ap, tap;
+ char *cs;
+ size_t len;
+
+ va_start(ap, fmt);
+ va_copy(tap, ap);
+ len = vsnprintf(NULL, 0, fmt, tap);
+ va_end(tap);
+ cs = alloca(len + 1);
+ (void)vsnprintf(cs, len + 1, fmt, ap);
+ va_end(ap);
+ if (j->name)
+ printf("%s: %s", j->name, cs);
+ else
+ printf("%s", cs);
+}
+
+/*
+ * Print a warning message including an optional jail name.
+ */
+void
+jail_warnx(const struct cfjail *j, const char *fmt, ...)
+{
+ va_list ap, tap;
+ char *cs;
+ size_t len;
+
+ va_start(ap, fmt);
+ va_copy(tap, ap);
+ len = vsnprintf(NULL, 0, fmt, tap);
+ va_end(tap);
+ cs = alloca(len + 1);
+ (void)vsnprintf(cs, len + 1, fmt, ap);
+ va_end(ap);
+ if (j->name)
+ warnx("%s: %s", j->name, cs);
+ else
+ warnx("%s", cs);
+}
+
+/*
+ * Create a new jail.
+ */
+int
+create_jail(struct cfjail *j)
+{
+ struct iovec jiov[4];
+ struct stat st;
+ struct jailparam *jp, *setparams, *setparams2, *sjp;
+ const char *path;
+ int dopersist, ns, jid, dying, didfail;
+
+ /*
+ * Check the jail's path, with a better error message than jail_set
+ * gives.
+ */
+ if ((path = string_param(j->intparams[KP_PATH]))) {
+ if (j->name != NULL && path[0] != '/') {
+ jail_warnx(j, "path %s: not an absolute pathname",
+ path);
+ return -1;
+ }
+ if (stat(path, &st) < 0) {
+ jail_warnx(j, "path %s: %s", path, strerror(errno));
+ return -1;
+ }
+ if (!S_ISDIR(st.st_mode)) {
+ jail_warnx(j, "path %s: %s", path, strerror(ENOTDIR));
+ return -1;
+ }
+ }
+
+ /*
+ * Copy all the parameters, except that "persist" is always set when
+ * there are commands to run later.
+ */
+ dopersist = !bool_param(j->intparams[KP_PERSIST]) &&
+ (j->intparams[IP_EXEC_START] || j->intparams[IP_COMMAND] ||
+ j->intparams[IP_EXEC_POSTSTART]);
+ sjp = setparams =
+ alloca((j->njp + dopersist) * sizeof(struct jailparam));
+ if (dopersist && jailparam_init(sjp++, "persist") < 0) {
+ jail_warnx(j, "%s", jail_errmsg);
+ return -1;
+ }
+ for (jp = j->jp; jp < j->jp + j->njp; jp++)
+ if (!dopersist || !equalopts(jp->jp_name, "persist"))
+ *sjp++ = *jp;
+ ns = sjp - setparams;
+
+ didfail = 0;
+ j->jid = jailparam_set_note(j, setparams, ns, JAIL_CREATE);
+ if (j->jid < 0 && errno == EEXIST &&
+ bool_param(j->intparams[IP_ALLOW_DYING]) &&
+ int_param(j->intparams[KP_JID], &jid) && jid != 0) {
+ /*
+ * The jail already exists, but may be dying.
+ * Make sure it is, in which case an update is appropriate.
+ */
+ jiov[0].iov_base = __DECONST(char *, "jid");
+ jiov[0].iov_len = sizeof("jid");
+ jiov[1].iov_base = &jid;
+ jiov[1].iov_len = sizeof(jid);
+ jiov[2].iov_base = __DECONST(char *, "dying");
+ jiov[2].iov_len = sizeof("dying");
+ jiov[3].iov_base = &dying;
+ jiov[3].iov_len = sizeof(dying);
+ if (jail_get(jiov, 4, JAIL_DYING) < 0) {
+ /*
+ * It could be that the jail just barely finished
+ * dying, or it could be that the jid never existed
+ * but the name does. In either case, another try
+ * at creating the jail should do the right thing.
+ */
+ if (errno == ENOENT)
+ j->jid = jailparam_set_note(j, setparams, ns,
+ JAIL_CREATE);
+ } else if (dying) {
+ j->jid = jid;
+ if (rdtun_params(j, 1) < 0) {
+ j->jid = -1;
+ didfail = 1;
+ } else {
+ sjp = setparams2 = alloca((j->njp + dopersist) *
+ sizeof(struct jailparam));
+ for (jp = setparams; jp < setparams + ns; jp++)
+ if (!JP_RDTUN(jp) ||
+ !strcmp(jp->jp_name, "jid"))
+ *sjp++ = *jp;
+ j->jid = jailparam_set_note(j, setparams2,
+ sjp - setparams2, JAIL_UPDATE | JAIL_DYING);
+ /*
+ * Again, perhaps the jail just finished dying.
+ */
+ if (j->jid < 0 && errno == ENOENT)
+ j->jid = jailparam_set_note(j,
+ setparams, ns, JAIL_CREATE);
+ }
+ }
+ }
+ if (j->jid < 0 && !didfail) {
+ jail_warnx(j, "%s", jail_errmsg);
+ failed(j);
+ }
+ if (dopersist) {
+ jailparam_free(setparams, 1);
+ if (j->jid > 0)
+ j->flags |= JF_PERSIST;
+ }
+ return j->jid;
+}
+
+/*
+ * Remove a temporarily set "persist" parameter.
+ */
+static void
+clear_persist(struct cfjail *j)
+{
+ struct iovec jiov[4];
+ int jid;
+
+ if (!(j->flags & JF_PERSIST))
+ return;
+ j->flags &= ~JF_PERSIST;
+ jiov[0].iov_base = __DECONST(char *, "jid");
+ jiov[0].iov_len = sizeof("jid");
+ jiov[1].iov_base = &j->jid;
+ jiov[1].iov_len = sizeof(j->jid);
+ jiov[2].iov_base = __DECONST(char *, "nopersist");
+ jiov[2].iov_len = sizeof("nopersist");
+ jiov[3].iov_base = NULL;
+ jiov[3].iov_len = 0;
+ jid = jail_set(jiov, 4, JAIL_UPDATE);
+ if (verbose > 0)
+ jail_note(j, "jail_set(JAIL_UPDATE) jid=%d nopersist%s%s\n",
+ j->jid, jid < 0 ? ": " : "",
+ jid < 0 ? strerror(errno) : "");
+}
+
+/*
+ * Set a jail's parameters.
+ */
+static int
+update_jail(struct cfjail *j)
+{
+ struct jailparam *jp, *setparams, *sjp;
+ int ns, jid;
+
+ ns = 0;
+ for (jp = j->jp; jp < j->jp + j->njp; jp++)
+ if (!JP_RDTUN(jp))
+ ns++;
+ if (ns == 0)
+ return 0;
+ sjp = setparams = alloca(++ns * sizeof(struct jailparam));
+ if (jailparam_init(sjp, "jid") < 0 ||
+ jailparam_import_raw(sjp, &j->jid, sizeof j->jid) < 0) {
+ jail_warnx(j, "%s", jail_errmsg);
+ failed(j);
+ return -1;
+ }
+ for (jp = j->jp; jp < j->jp + j->njp; jp++)
+ if (!JP_RDTUN(jp))
+ *++sjp = *jp;
+
+ jid = jailparam_set_note(j, setparams, ns,
+ bool_param(j->intparams[IP_ALLOW_DYING])
+ ? JAIL_UPDATE | JAIL_DYING : JAIL_UPDATE);
+ if (jid < 0) {
+ jail_warnx(j, "%s", jail_errmsg);
+ failed(j);
+ }
+ jailparam_free(setparams, 1);
+ return jid;
+}
+
+/*
+ * Return if a jail set would change any create-only parameters.
+ */
+static int
+rdtun_params(struct cfjail *j, int dofail)
+{
+ struct jailparam *jp, *rtparams, *rtjp;
+ int nrt, rval;
+
+ if (j->flags & JF_RDTUN)
+ return 0;
+ j->flags |= JF_RDTUN;
+ nrt = 0;
+ for (jp = j->jp; jp < j->jp + j->njp; jp++)
+ if (JP_RDTUN(jp) && strcmp(jp->jp_name, "jid"))
+ nrt++;
+ if (nrt == 0)
+ return 0;
+ rtjp = rtparams = alloca(++nrt * sizeof(struct jailparam));
+ if (jailparam_init(rtjp, "jid") < 0 ||
+ jailparam_import_raw(rtjp, &j->jid, sizeof j->jid) < 0) {
+ jail_warnx(j, "%s", jail_errmsg);
+ exit(1);
+ }
+ for (jp = j->jp; jp < j->jp + j->njp; jp++)
+ if (JP_RDTUN(jp) && strcmp(jp->jp_name, "jid"))
+ *++rtjp = *jp;
+ rval = 0;
+ if (jailparam_get(rtparams, nrt,
+ bool_param(j->intparams[IP_ALLOW_DYING]) ? JAIL_DYING : 0) > 0) {
+ rtjp = rtparams + 1;
+ for (jp = j->jp, rtjp = rtparams + 1; rtjp < rtparams + nrt;
+ jp++) {
+ if (JP_RDTUN(jp) && strcmp(jp->jp_name, "jid")) {
+ if (!((jp->jp_flags & (JP_BOOL | JP_NOBOOL)) &&
+ jp->jp_valuelen == 0 &&
+ *(int *)jp->jp_value) &&
+ !(rtjp->jp_valuelen == jp->jp_valuelen &&
+ !memcmp(rtjp->jp_value, jp->jp_value,
+ jp->jp_valuelen))) {
+ if (dofail) {
+ jail_warnx(j, "%s cannot be "
+ "changed after creation",
+ jp->jp_name);
+ failed(j);
+ rval = -1;
+ } else
+ rval = 1;
+ break;
+ }
+ rtjp++;
+ }
+ }
+ }
+ for (rtjp = rtparams + 1; rtjp < rtparams + nrt; rtjp++)
+ rtjp->jp_name = NULL;
+ jailparam_free(rtparams, nrt);
+ return rval;
+}
+
+/*
+ * Get the jail's jid if it is running.
+ */
+static void
+running_jid(struct cfjail *j, int dflag)
+{
+ struct iovec jiov[2];
+ const char *pval;
+ char *ep;
+ int jid;
+
+ if ((pval = string_param(j->intparams[KP_JID]))) {
+ if (!(jid = strtol(pval, &ep, 10)) || *ep) {
+ j->jid = -1;
+ return;
+ }
+ jiov[0].iov_base = __DECONST(char *, "jid");
+ jiov[0].iov_len = sizeof("jid");
+ jiov[1].iov_base = &jid;
+ jiov[1].iov_len = sizeof(jid);
+ } else if ((pval = string_param(j->intparams[KP_NAME]))) {
+ jiov[0].iov_base = __DECONST(char *, "name");
+ jiov[0].iov_len = sizeof("name");
+ jiov[1].iov_len = strlen(pval) + 1;
+ jiov[1].iov_base = alloca(jiov[1].iov_len);
+ strcpy(jiov[1].iov_base, pval);
+ } else {
+ j->jid = -1;
+ return;
+ }
+ j->jid = jail_get(jiov, 2, dflag ? JAIL_DYING : 0);
+}
+
+static void
+jail_quoted_warnx(const struct cfjail *j, const char *name_msg,
+ const char *noname_msg)
+{
+ const char *pval;
+
+ if ((pval = j->name) || (pval = string_param(j->intparams[KP_JID])) ||
+ (pval = string_param(j->intparams[KP_NAME])))
+ warnx("\"%s\" %s", pval, name_msg);
+ else
+ warnx("%s", noname_msg);
+}
+
+/*
+ * Set jail parameters and possibly print them out.
+ */
+static int
+jailparam_set_note(const struct cfjail *j, struct jailparam *jp, unsigned njp,
+ int flags)
+{
+ char *value;
+ int jid;
+ unsigned i;
+
+ jid = jailparam_set(jp, njp, flags);
+ if (verbose > 0) {
+ jail_note(j, "jail_set(%s%s)",
+ (flags & (JAIL_CREATE | JAIL_UPDATE)) == JAIL_CREATE
+ ? "JAIL_CREATE" : "JAIL_UPDATE",
+ (flags & JAIL_DYING) ? " | JAIL_DYING" : "");
+ for (i = 0; i < njp; i++) {
+ printf(" %s", jp[i].jp_name);
+ if (jp[i].jp_value == NULL)
+ continue;
+ putchar('=');
+ value = jailparam_export(jp + i);
+ if (value == NULL)
+ err(1, "jailparam_export");
+ quoted_print(stdout, value);
+ free(value);
+ }
+ if (jid < 0)
+ printf(": %s", strerror(errno));
+ printf("\n");
+ }
+ return jid;
+}
+
+/*
+ * Print a jail record.
+ */
+static void
+print_jail(FILE *fp, struct cfjail *j, int oldcl)
+{
+ struct cfparam *p;
+
+ if (oldcl) {
+ fprintf(fp, "%d\t", j->jid);
+ print_param(fp, j->intparams[KP_PATH], ',', 0);
+ putc('\t', fp);
+ print_param(fp, j->intparams[KP_HOST_HOSTNAME], ',', 0);
+ putc('\t', fp);
+#ifdef INET
+ print_param(fp, j->intparams[KP_IP4_ADDR], ',', 0);
+#ifdef INET6
+ if (j->intparams[KP_IP4_ADDR] &&
+ !TAILQ_EMPTY(&j->intparams[KP_IP4_ADDR]->val) &&
+ j->intparams[KP_IP6_ADDR] &&
+ !TAILQ_EMPTY(&j->intparams[KP_IP6_ADDR]->val))
+ putc(',', fp);
+#endif
+#endif
+#ifdef INET6
+ print_param(fp, j->intparams[KP_IP6_ADDR], ',', 0);
+#endif
+ putc('\t', fp);
+ print_param(fp, j->intparams[IP_COMMAND], ' ', 0);
+ } else {
+ fprintf(fp, "jid=%d", j->jid);
+ TAILQ_FOREACH(p, &j->params, tq)
+ if (strcmp(p->name, "jid")) {
+ putc(' ', fp);
+ print_param(fp, p, ',', 1);
+ }
+ }
+ putc('\n', fp);
+}
+
+/*
+ * Print a parameter value, or a name=value pair.
+ */
+static void
+print_param(FILE *fp, const struct cfparam *p, int sep, int doname)
+{
+ const struct cfstring *s, *ts;
+
+ if (doname)
+ fputs(p->name, fp);
+ if (p == NULL || TAILQ_EMPTY(&p->val))
+ return;
+ if (doname)
+ putc('=', fp);
+ TAILQ_FOREACH_SAFE(s, &p->val, tq, ts) {
+ quoted_print(fp, s->s);
+ if (ts != NULL)
+ putc(sep, fp);
+ }
+}
+
+/*
+ * Print a string with quotes around spaces.
+ */
+static void
+quoted_print(FILE *fp, char *str)
+{
+ int c, qc;
+ char *p = str;
+
+ qc = !*p ? '"'
+ : strchr(p, '\'') ? '"'
+ : strchr(p, '"') ? '\''
+ : strchr(p, ' ') || strchr(p, '\t') ? '"'
+ : 0;
+ if (qc)
+ putc(qc, fp);
+ while ((c = *p++)) {
+ if (c == '\\' || c == qc)
+ putc('\\', fp);
+ putc(c, fp);
+ }
+ if (qc)
+ putc(qc, fp);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr,
+ "usage: jail [-dhilqv] [-J jid_file] [-u username] [-U username]\n"
+ " -[cmr] param=value ... [command=command ...]\n"
+ " jail [-dqv] [-f file] -[cmr] [jail]\n"
+ " jail [-qv] [-f file] -[rR] ['*' | jail ...]\n"
+ " jail [-dhilqv] [-J jid_file] [-u username] [-U username]\n"
+ " [-n jailname] [-s securelevel]\n"
+ " path hostname [ip[,...]] command ...\n");
+ exit(1);
+}
diff --git a/usr.sbin/jail/jail.conf.5 b/usr.sbin/jail/jail.conf.5
new file mode 100644
index 0000000..d83bf61
--- /dev/null
+++ b/usr.sbin/jail/jail.conf.5
@@ -0,0 +1,232 @@
+.\" Copyright (c) 2012 James Gritton
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 13, 2014
+.Dt JAIL.CONF 5
+.Os
+.Sh NAME
+.Nm jail.conf
+.Nd configuration file for
+.Xr jail 8
+.Sh DESCRIPTION
+A
+.Xr jail 8
+configuration file consists of one or more jail definitions statements,
+and parameter or variable statements within those jail definitions.
+A jail definition statement looks something like a C compound statement.
+A parameter statement looks like a C assignment,
+including a terminating semicolon.
+.Pp
+The general syntax of a jail definition is:
+.Bd -literal -offset indent
+jailname {
+ parameter = "value";
+ parameter = "value";
+ ...
+}
+.Ed
+.Pp
+Each jail is required to have a
+.Va name
+at the front of its definition.
+This is used by
+.Xr jail 8
+to specify a jail on the command line and report the jail status,
+and is also passed to the kernel when creating the jail.
+.Ss Parameters
+A jail is defined by a set of named parameters, specified inside the
+jail definition.
+See
+.Xr jail 8
+for a list of jail parameters passed to the kernel,
+as well as internal parameters used when creating and removing jails.
+.Pp
+A typical parameter has a name and a value.
+Some parameters are boolean and may be specified with values of
+.Dq true
+or
+.Dq false ,
+or as valueless shortcuts, with a
+.Dq no
+prefix indicating a false value.
+For example, these are equivalent:
+.Bd -literal -offset indent
+allow.mount = "false";
+allow.nomount;
+.Ed
+.Pp
+Other parameters may have more than one value.
+A comma-separated list of values may be set in a single statement,
+or an existing parameter list may be appended to using
+.Dq += :
+.Bd -literal -offset indent
+ip4.addr = 10.1.1.1, 10.1.1.2, 10.1.1.3;
+
+ip4.addr = 10.1.1.1;
+ip4.addr += 10.1.1.2;
+ip4.addr += 10.1.1.3;
+.Ed
+.Pp
+Note the
+.Va name
+parameter is implicitly set to the name in the jail definition.
+.Ss String format
+Parameter values, including jail names, can be single tokens or quoted
+strings.
+A token is any sequence of characters that aren't considered special in
+the syntax of the configuration file (such as a semicolon or
+whitespace).
+If a value contains anything more than letters, numbers, dots, dashes
+and underscores, it is advisable to put quote marks around that value.
+Either single or double quotes may be used.
+.Pp
+Special characters may be quoted by preceding them with a backslash.
+Common C-style backslash character codes are also supported, including
+control characters and octal or hex ASCII codes.
+A backslash at the end of a line will ignore the subsequent newline and
+continue the string at the start of the next line.
+.Ss Variables
+A string may use shell-style variable substitution.
+A parameter or variable name preceded by a dollar sign, and possibly
+enclosed in braces, will be replaced with the value of that parameter or
+variable.
+For example, a jail's path may be defined in terms of its name or
+hostname:
+.Bd -literal -offset indent
+path = "/var/jail/$name";
+
+path = "/var/jail/${host.hostname}";
+.Ed
+.Pp
+Variable substitution occurs in unquoted tokens or in double-quoted
+strings, but not in single-quote strings.
+.Pp
+A variable is defined in the same way a parameter is, except that the
+variable name is preceded with a dollar sign:
+.Bd -literal -offset indent
+$parentdir = "/var/jail";
+path = "$parentdir/$name";
+.Ed
+.Pp
+The difference between parameters and variables is that variables are
+only used for substitution, while parameters are used both for
+substitution and for passing to the kernel.
+.Ss Wildcards
+A jail definition with a name of
+.Dq *
+is used to define wildcard parameters.
+Every defined jail will contain both the parameters from its own
+definition statement, as well as any parameters in a wildcard
+definition.
+.Pp
+Variable substitution is done on a per-jail basis, even when that
+substitution is for a parameter defined in a wildcard section.
+This is useful for wildcard parameters based on e.g. a jail's name.
+.Pp
+Later definitions in the configuration file supersede earlier ones, so a
+wildcard section placed before (above) a jail definition defines
+parameters that could be changed on a per-jail basis.
+Or a wildcard section placed after (below) all jails would contain
+parameters that always apply to every jail.
+Multiple wildcard statements are allowed, and wildcard parameters may
+also be specified outside of a jail definition statement.
+.Pp
+If hierarchical jails are defined, a partial-matching wildcard
+definition may be specified.
+For example, a definition with a name of
+.Dq foo.*
+would apply to jails with names like
+.Dq foo.bar
+and
+.Dq foo.bar.baz .
+.Ss Comments
+The configuration file may contain comments in the common C, C++, and
+shell formats:
+.Bd -literal -offset indent
+/* This is a C style comment.
+ * It may span multiple lines.
+ */
+
+// This is a C++ style comment.
+
+# This is a shell style comment.
+.Ed
+.Pp
+Comments are legal wherever whitespace is allowed, i.e. anywhere except
+in the middle of a string or a token.
+.Sh EXAMPLES
+.Bd -literal
+# Typical static defaults:
+# Use the rc scripts to start and stop jails. Mount jail's /dev.
+exec.start = "/bin/sh /etc/rc";
+exec.stop = "/bin/sh /etc/rc.shutdown";
+exec.clean;
+mount.devfs;
+
+# Dynamic wildcard parameter:
+# Base the path off the jail name.
+path = "/var/jail/$name";
+
+# A typical jail.
+foo {
+ host.hostname = "foo.com";
+ ip4.addr = 10.1.1.1, 10.1.1.2, 10.1.1.3;
+}
+
+# This jail overrides the defaults defined above.
+bar {
+ exec.start = '';
+ exec.stop = '';
+ path = /;
+ mount.nodevfs;
+ persist; // Required because there are no processes
+}
+.Ed
+.Sh SEE ALSO
+.Xr jail_set 2 ,
+.Xr rc.conf 5 ,
+.Xr jail 8 ,
+.Xr jls 8
+.Sh HISTORY
+The
+.Xr jail 8
+utility appeared in
+.Fx 4.0 .
+The
+.Nm
+file was added in
+.Fx 9.1 .
+.Sh AUTHORS
+.An -nosplit
+The jail feature was written by
+.An Poul-Henning Kamp
+for R&D Associates
+.Pa http://www.rndassociates.com/
+who contributed it to
+.Fx .
+.Pp
+.An James Gritton
+added the extensible jail parameters and configuration file.
diff --git a/usr.sbin/jail/jaillex.l b/usr.sbin/jail/jaillex.l
new file mode 100644
index 0000000..04de85f
--- /dev/null
+++ b/usr.sbin/jail/jaillex.l
@@ -0,0 +1,235 @@
+%{
+/*-
+ * Copyright (c) 2011 James Gritton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "jailp.h"
+#include "y.tab.h"
+
+extern int yynerrs;
+
+static ssize_t text2lval(size_t triml, size_t trimr, int tovar);
+
+static int instr;
+static int lineno = 1;
+
+#define YY_DECL int yylex(void)
+%}
+
+%option noinput
+%option nounput
+
+%start _ DQ
+
+%%
+
+ /* Whitespace or equivalent */
+<_>[ \t]+ instr = 0;
+<_>#.* ;
+<_>\/\/.* ;
+<_>\/\*([^*]|(\*+([^*\/])))*\*+\/ {
+ const char *s;
+
+ for (s = yytext; s < yytext + yyleng; s++)
+ if (*s == '\n')
+ lineno++;
+ instr = 0;
+ }
+<_>\n {
+ lineno++;
+ instr = 0;
+ }
+
+ /* Reserved tokens */
+<_>\+= {
+ instr = 0;
+ return PLEQ;
+ }
+<_>[,;={}] {
+ instr = 0;
+ return yytext[0];
+ }
+
+ /* Atomic (unquoted) strings */
+<_,DQ>[A-Za-z0-9_!%&()\-.:<>?@\[\]^`|~]+ |
+<_,DQ>\\(.|\n|[0-7]{1,3}|x[0-9A-Fa-f]{1,2}) |
+<_,DQ>[$*+/\\] {
+ (void)text2lval(0, 0, 0);
+ return instr ? STR1 : (instr = 1, STR);
+ }
+
+ /* Single and double quoted strings */
+<_>'([^\'\\]|\\(.|\n))*' {
+ (void)text2lval(1, 1, 0);
+ return instr ? STR1 : (instr = 1, STR);
+ }
+<_>\"([^"\\]|\\(.|\n))*\" |
+<DQ>[^\"$\\]([^"\\]|\\(.|\n))*\" {
+ size_t skip;
+ ssize_t atvar;
+
+ skip = yytext[0] == '"' ? 1 : 0;
+ atvar = text2lval(skip, 1, 1);
+ if (atvar < 0)
+ BEGIN _;
+ else {
+ /*
+ * The string has a variable inside it.
+ * Go into DQ mode to get the variable
+ * and then the rest of the string.
+ */
+ BEGIN DQ;
+ yyless(atvar);
+ }
+ return instr ? STR1 : (instr = 1, STR);
+ }
+<DQ>\" BEGIN _;
+
+ /* Variables, single-word or bracketed */
+<_,DQ>$[A-Za-z_][A-Za-z_0-9]* {
+ (void)text2lval(1, 0, 0);
+ return instr ? VAR1 : (instr = 1, VAR);
+ }
+<_>$\{([^\n{}]|\\(.|\n))*\} |
+<DQ>$\{([^\n\"{}]|\\(.|\n))*\} {
+ (void)text2lval(2, 1, 0);
+ return instr ? VAR1 : (instr = 1, VAR);
+ }
+
+ /* Partially formed bits worth complaining about */
+<_>\/\*([^*]|(\*+([^*\/])))*\** {
+ warnx("%s line %d: unterminated comment",
+ cfname, lineno);
+ yynerrs++;
+ }
+<_>'([^\n'\\]|\\.)* |
+<_>\"([^\n\"\\]|\\.)* {
+ warnx("%s line %d: unterminated string",
+ cfname, lineno);
+ yynerrs++;
+ }
+<_>$\{([^\n{}]|\\.)* |
+<DQ>$\{([^\n\"{}]|\\.)* {
+ warnx("%s line %d: unterminated variable",
+ cfname, lineno);
+ yynerrs++;
+ }
+
+ /* A hack because "<0>" rules aren't allowed */
+<_>. return yytext[0];
+.|\n {
+ BEGIN _;
+ yyless(0);
+ }
+
+%%
+
+void
+yyerror(const char *s)
+{
+ if (!yytext)
+ warnx("%s line %d: %s", cfname, lineno, s);
+ else if (!yytext[0])
+ warnx("%s: unexpected EOF", cfname);
+ else
+ warnx("%s line %d: %s: %s", cfname, lineno, yytext, s);
+}
+
+/*
+ * Copy string from yytext to yylval, handling backslash escapes,
+ * and optionally stopping at the beginning of a variable.
+ */
+static ssize_t
+text2lval(size_t triml, size_t trimr, int tovar)
+{
+ char *d;
+ const char *s, *se;
+
+ yylval.cs = d = emalloc(yyleng - trimr - triml + 1);
+ se = yytext + (yyleng - trimr);
+ for (s = yytext + triml; s < se; s++, d++) {
+ if (*s != '\\') {
+ if (tovar && *s == '$') {
+ *d = '\0';
+ return s - yytext;
+ }
+ if (*s == '\n')
+ lineno++;
+ *d = *s;
+ continue;
+ }
+ s++;
+ if (*s >= '0' && *s <= '7') {
+ *d = *s - '0';
+ if (s + 1 < se && s[1] >= '0' && s[1] <= '7') {
+ *d = 010 * *d + (*++s - '0');
+ if (s + 1 < se && s[1] >= '0' && s[1] <= '7')
+ *d = 010 * *d + (*++s - '0');
+ }
+ continue;
+ }
+ switch (*s) {
+ case 'a': *d = '\a'; break;
+ case 'b': *d = '\b'; break;
+ case 'f': *d = '\f'; break;
+ case 'n': *d = '\n'; break;
+ case 'r': *d = '\r'; break;
+ case 't': *d = '\t'; break;
+ case 'v': *d = '\v'; break;
+ case '\n': d--; lineno++; break;
+ default: *d = *s; break;
+ case 'x':
+ *d = 0;
+ if (s + 1 >= se)
+ break;
+ if (s[1] >= '0' && s[1] <= '9')
+ *d = *++s - '0';
+ else if (s[1] >= 'A' && s[1] <= 'F')
+ *d = *++s + (0xA - 'A');
+ else if (s[1] >= 'a' && s[1] <= 'a')
+ *d = *++s + (0xa - 'a');
+ else
+ break;
+ if (s + 1 >= se)
+ break;
+ if (s[1] >= '0' && s[1] <= '9')
+ *d = *d * 0x10 + (*++s - '0');
+ else if (s[1] >= 'A' && s[1] <= 'F')
+ *d = *d * 0x10 + (*++s + (0xA - 'A'));
+ else if (s[1] >= 'a' && s[1] <= 'a')
+ *d = *d * 0x10 + (*++s + (0xa - 'a'));
+ }
+ }
+ *d = '\0';
+ return -1;
+}
diff --git a/usr.sbin/jail/jailp.h b/usr.sbin/jail/jailp.h
new file mode 100644
index 0000000..4498f4b
--- /dev/null
+++ b/usr.sbin/jail/jailp.h
@@ -0,0 +1,237 @@
+/*-
+ * Copyright (c) 2011 James Gritton.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/types.h>
+#include <sys/jail.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <jail.h>
+
+#define CONF_FILE "/etc/jail.conf"
+
+#define DEP_FROM 0
+#define DEP_TO 1
+
+#define DF_SEEN 0x01 /* Dependency has been followed */
+#define DF_LIGHT 0x02 /* Implied dependency on jail existence only */
+#define DF_NOFAIL 0x04 /* Don't propagate failed jails */
+
+#define PF_VAR 0x01 /* This is a variable, not a true parameter */
+#define PF_APPEND 0x02 /* Append to existing parameter list */
+#define PF_BAD 0x04 /* Unable to resolve parameter value */
+#define PF_INTERNAL 0x08 /* Internal parameter, not passed to kernel */
+#define PF_BOOL 0x10 /* Boolean parameter */
+#define PF_INT 0x20 /* Integer parameter */
+#define PF_CONV 0x40 /* Parameter duplicated in converted form */
+#define PF_REV 0x80 /* Run commands in reverse order on stopping */
+#define PF_IMMUTABLE 0x100 /* Immutable parameter */
+
+#define JF_START 0x0001 /* -c */
+#define JF_SET 0x0002 /* -m */
+#define JF_STOP 0x0004 /* -r */
+#define JF_DEPEND 0x0008 /* Operation required by dependency */
+#define JF_WILD 0x0010 /* Not specified on the command line */
+#define JF_FAILED 0x0020 /* Operation failed */
+#define JF_PARAMS 0x0040 /* Parameters checked and imported */
+#define JF_RDTUN 0x0080 /* Create-only parameter check has been done */
+#define JF_PERSIST 0x0100 /* Jail is temporarily persistent */
+#define JF_TIMEOUT 0x0200 /* A command (or process kill) timed out */
+#define JF_SLEEPQ 0x0400 /* Waiting on a command and/or timeout */
+
+#define JF_OP_MASK (JF_START | JF_SET | JF_STOP)
+#define JF_RESTART (JF_START | JF_STOP)
+#define JF_START_SET (JF_START | JF_SET)
+#define JF_SET_RESTART (JF_SET | JF_STOP)
+#define JF_START_SET_RESTART (JF_START | JF_SET | JF_STOP)
+#define JF_DO_STOP(js) (((js) & (JF_SET | JF_STOP)) == JF_STOP)
+
+enum intparam {
+ IP__NULL = 0, /* Null command */
+ IP_ALLOW_DYING, /* Allow making changes to a dying jail */
+ IP_COMMAND, /* Command run inside jail at creation */
+ IP_DEPEND, /* Jail starts after (stops before) another */
+ IP_EXEC_CLEAN, /* Run commands in a clean environment */
+ IP_EXEC_CONSOLELOG, /* Redirect optput for commands run in jail */
+ IP_EXEC_FIB, /* Run jailed commands with this FIB */
+ IP_EXEC_JAIL_USER, /* Run jailed commands as this user */
+ IP_EXEC_POSTSTART, /* Commands run outside jail after creating */
+ IP_EXEC_POSTSTOP, /* Commands run outside jail after removing */
+ IP_EXEC_PRESTART, /* Commands run outside jail before creating */
+ IP_EXEC_PRESTOP, /* Commands run outside jail before removing */
+ IP_EXEC_START, /* Commands run inside jail on creation */
+ IP_EXEC_STOP, /* Commands run inside jail on removal */
+ IP_EXEC_SYSTEM_JAIL_USER,/* Get jail_user from system passwd file */
+ IP_EXEC_SYSTEM_USER, /* Run non-jailed commands as this user */
+ IP_EXEC_TIMEOUT, /* Time to wait for a command to complete */
+#if defined(INET) || defined(INET6)
+ IP_INTERFACE, /* Add IP addresses to this interface */
+ IP_IP_HOSTNAME, /* Get jail IP address(es) from hostname */
+#endif
+ IP_MOUNT, /* Mount points in fstab(5) form */
+ IP_MOUNT_DEVFS, /* Mount /dev under prison root */
+ IP_MOUNT_FDESCFS, /* Mount /dev/fd under prison root */
+ IP_MOUNT_PROCFS, /* Mount /proc under prison root */
+ IP_MOUNT_FSTAB, /* A standard fstab(5) file */
+ IP_STOP_TIMEOUT, /* Time to wait after sending SIGTERM */
+ IP_VNET_INTERFACE, /* Assign interface(s) to vnet jail */
+#ifdef INET
+ IP__IP4_IFADDR, /* Copy of ip4.addr with interface/netmask */
+#endif
+#ifdef INET6
+ IP__IP6_IFADDR, /* Copy of ip6.addr with interface/prefixlen */
+#endif
+ IP__MOUNT_FROM_FSTAB, /* Line from mount.fstab file */
+ IP__OP, /* Placeholder for requested operation */
+ KP_ALLOW_CHFLAGS,
+ KP_ALLOW_MOUNT,
+ KP_ALLOW_RAW_SOCKETS,
+ KP_ALLOW_SET_HOSTNAME,
+ KP_ALLOW_SOCKET_AF,
+ KP_ALLOW_SYSVIPC,
+ KP_DEVFS_RULESET,
+ KP_ENFORCE_STATFS,
+ KP_HOST_HOSTNAME,
+#ifdef INET
+ KP_IP4_ADDR,
+#endif
+#ifdef INET6
+ KP_IP6_ADDR,
+#endif
+ KP_JID,
+ KP_NAME,
+ KP_PATH,
+ KP_PERSIST,
+ KP_SECURELEVEL,
+ KP_VNET,
+ IP_NPARAM
+};
+
+STAILQ_HEAD(cfvars, cfvar);
+
+struct cfvar {
+ STAILQ_ENTRY(cfvar) tq;
+ char *name;
+ size_t pos;
+};
+
+TAILQ_HEAD(cfstrings, cfstring);
+
+struct cfstring {
+ TAILQ_ENTRY(cfstring) tq;
+ char *s;
+ size_t len;
+ struct cfvars vars;
+};
+
+TAILQ_HEAD(cfparams, cfparam);
+
+struct cfparam {
+ TAILQ_ENTRY(cfparam) tq;
+ char *name;
+ struct cfstrings val;
+ unsigned flags;
+ int gen;
+};
+
+TAILQ_HEAD(cfjails, cfjail);
+STAILQ_HEAD(cfdepends, cfdepend);
+
+struct cfjail {
+ TAILQ_ENTRY(cfjail) tq;
+ char *name;
+ char *comline;
+ struct cfparams params;
+ struct cfdepends dep[2];
+ struct cfjails *queue;
+ struct cfparam *intparams[IP_NPARAM];
+ struct cfstring *comstring;
+ struct jailparam *jp;
+ struct timespec timeout;
+ const enum intparam *comparam;
+ unsigned flags;
+ int jid;
+ int seq;
+ int pstatus;
+ int ndeps;
+ int njp;
+ int nprocs;
+};
+
+struct cfdepend {
+ STAILQ_ENTRY(cfdepend) tq[2];
+ struct cfjail *j[2];
+ unsigned flags;
+};
+
+extern void *emalloc(size_t);
+extern void *erealloc(void *, size_t);
+extern char *estrdup(const char *);
+extern int create_jail(struct cfjail *j);
+extern void failed(struct cfjail *j);
+extern void jail_note(const struct cfjail *j, const char *fmt, ...);
+extern void jail_warnx(const struct cfjail *j, const char *fmt, ...);
+
+extern int next_command(struct cfjail *j);
+extern int finish_command(struct cfjail *j);
+extern struct cfjail *next_proc(int nonblock);
+
+extern void load_config(void);
+extern struct cfjail *add_jail(void);
+extern void add_param(struct cfjail *j, const struct cfparam *p,
+ enum intparam ipnum, const char *value);
+extern int bool_param(const struct cfparam *p);
+extern int int_param(const struct cfparam *p, int *ip);
+extern const char *string_param(const struct cfparam *p);
+extern int check_intparams(struct cfjail *j);
+extern int import_params(struct cfjail *j);
+extern int equalopts(const char *opt1, const char *opt2);
+extern int wild_jail_name(const char *wname);
+extern int wild_jail_match(const char *jname, const char *wname);
+
+extern void dep_setup(int docf);
+extern int dep_check(struct cfjail *j);
+extern void dep_done(struct cfjail *j, unsigned flags);
+extern void dep_reset(struct cfjail *j);
+extern struct cfjail *next_jail(void);
+extern int start_state(const char *target, int docf, unsigned state,
+ int running);
+extern void requeue(struct cfjail *j, struct cfjails *queue);
+
+extern void yyerror(const char *);
+extern int yylex(void);
+
+extern struct cfjails cfjails;
+extern struct cfjails ready;
+extern struct cfjails depend;
+extern const char *cfname;
+extern int iflag;
+extern int note_remove;
+extern int paralimit;
+extern int verbose;
diff --git a/usr.sbin/jail/jailparse.y b/usr.sbin/jail/jailparse.y
new file mode 100644
index 0000000..d085eb8
--- /dev/null
+++ b/usr.sbin/jail/jailparse.y
@@ -0,0 +1,216 @@
+%{
+/*-
+ * Copyright (c) 2011 James Gritton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <stdlib.h>
+#include <string.h>
+
+#include "jailp.h"
+
+#ifdef DEBUG
+#define YYDEBUG 1
+#endif
+%}
+
+%union {
+ struct cfjail *j;
+ struct cfparams *pp;
+ struct cfparam *p;
+ struct cfstrings *ss;
+ struct cfstring *s;
+ char *cs;
+}
+
+%token PLEQ
+%token <cs> STR STR1 VAR VAR1
+
+%type <j> jail
+%type <pp> param_l
+%type <p> param name
+%type <ss> value
+%type <s> string
+
+%%
+
+/*
+ * A config file is a series of jails (containing parameters) and jail-less
+ * parameters which realy belong to a global pseudo-jail.
+ */
+conf :
+ ;
+ | conf jail
+ ;
+ | conf param ';'
+ {
+ struct cfjail *j;
+
+ j = TAILQ_LAST(&cfjails, cfjails);
+ if (!j || strcmp(j->name, "*")) {
+ j = add_jail();
+ j->name = estrdup("*");
+ }
+ TAILQ_INSERT_TAIL(&j->params, $2, tq);
+ }
+ | conf ';'
+
+jail : STR '{' param_l '}'
+ {
+ $$ = add_jail();
+ $$->name = $1;
+ TAILQ_CONCAT(&$$->params, $3, tq);
+ free($3);
+ }
+ ;
+
+param_l :
+ {
+ $$ = emalloc(sizeof(struct cfparams));
+ TAILQ_INIT($$);
+ }
+ | param_l param ';'
+ {
+ $$ = $1;
+ TAILQ_INSERT_TAIL($$, $2, tq);
+ }
+ | param_l ';'
+ ;
+
+/*
+ * Parameters have a name and an optional list of value strings,
+ * which may have "+=" or "=" preceding them.
+ */
+param : name
+ {
+ $$ = $1;
+ }
+ | name '=' value
+ {
+ $$ = $1;
+ TAILQ_CONCAT(&$$->val, $3, tq);
+ free($3);
+ }
+ | name PLEQ value
+ {
+ $$ = $1;
+ TAILQ_CONCAT(&$$->val, $3, tq);
+ $$->flags |= PF_APPEND;
+ free($3);
+ }
+ | name value
+ {
+ $$ = $1;
+ TAILQ_CONCAT(&$$->val, $2, tq);
+ free($2);
+ }
+ | error
+ {
+ }
+ ;
+
+/*
+ * A parameter has a fixed name. A variable definition looks just like a
+ * parameter except that the name is a variable.
+ */
+name : STR
+ {
+ $$ = emalloc(sizeof(struct cfparam));
+ $$->name = $1;
+ TAILQ_INIT(&$$->val);
+ $$->flags = 0;
+ }
+ | VAR
+ {
+ $$ = emalloc(sizeof(struct cfparam));
+ $$->name = $1;
+ TAILQ_INIT(&$$->val);
+ $$->flags = PF_VAR;
+ }
+ ;
+
+value : string
+ {
+ $$ = emalloc(sizeof(struct cfstrings));
+ TAILQ_INIT($$);
+ TAILQ_INSERT_TAIL($$, $1, tq);
+ }
+ | value ',' string
+ {
+ $$ = $1;
+ TAILQ_INSERT_TAIL($$, $3, tq);
+ }
+ ;
+
+/*
+ * Strings may be passed in pieces, because of quoting and/or variable
+ * interpolation. Reassemble them into a single string.
+ */
+string : STR
+ {
+ $$ = emalloc(sizeof(struct cfstring));
+ $$->s = $1;
+ $$->len = strlen($1);
+ STAILQ_INIT(&$$->vars);
+ }
+ | VAR
+ {
+ struct cfvar *v;
+
+ $$ = emalloc(sizeof(struct cfstring));
+ $$->s = estrdup("");
+ $$->len = 0;
+ STAILQ_INIT(&$$->vars);
+ v = emalloc(sizeof(struct cfvar));
+ v->name = $1;
+ v->pos = 0;
+ STAILQ_INSERT_TAIL(&$$->vars, v, tq);
+ }
+ | string STR1
+ {
+ size_t len1;
+
+ $$ = $1;
+ len1 = strlen($2);
+ $$->s = erealloc($$->s, $$->len + len1 + 1);
+ strcpy($$->s + $$->len, $2);
+ free($2);
+ $$->len += len1;
+ }
+ | string VAR1
+ {
+ struct cfvar *v;
+
+ $$ = $1;
+ v = emalloc(sizeof(struct cfvar));
+ v->name = $2;
+ v->pos = $$->len;
+ STAILQ_INSERT_TAIL(&$$->vars, v, tq);
+ }
+ ;
+
+%%
diff --git a/usr.sbin/jail/state.c b/usr.sbin/jail/state.c
new file mode 100644
index 0000000..b3eb942
--- /dev/null
+++ b/usr.sbin/jail/state.c
@@ -0,0 +1,468 @@
+/*-
+ * Copyright (c) 2011 James Gritton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/uio.h>
+
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "jailp.h"
+
+struct cfjails ready = TAILQ_HEAD_INITIALIZER(ready);
+struct cfjails depend = TAILQ_HEAD_INITIALIZER(depend);
+
+static void dep_add(struct cfjail *from, struct cfjail *to, unsigned flags);
+static int cmp_jailptr(const void *a, const void *b);
+static int cmp_jailptr_name(const void *a, const void *b);
+static struct cfjail *find_jail(const char *name);
+static int running_jid(const char *name, int flags);
+
+static struct cfjail **jails_byname;
+static size_t njails;
+
+/*
+ * Set up jail dependency lists.
+ */
+void
+dep_setup(int docf)
+{
+ struct cfjail *j, *dj;
+ struct cfparam *p;
+ struct cfstring *s;
+ struct cfdepend *d;
+ const char *cs;
+ char *pname;
+ size_t plen;
+ int deps, ldeps;
+
+ if (!docf) {
+ /*
+ * With no config file, let "depend" for a single jail
+ * look at currently running jails.
+ */
+ if ((j = TAILQ_FIRST(&cfjails)) &&
+ (p = j->intparams[IP_DEPEND])) {
+ TAILQ_FOREACH(s, &p->val, tq) {
+ if (running_jid(s->s, 0) < 0) {
+ warnx("depends on nonexistent jail "
+ "\"%s\"", s->s);
+ j->flags |= JF_FAILED;
+ }
+ }
+ }
+ return;
+ }
+
+ njails = 0;
+ TAILQ_FOREACH(j, &cfjails, tq)
+ njails++;
+ jails_byname = emalloc(njails * sizeof(struct cfjail *));
+ njails = 0;
+ TAILQ_FOREACH(j, &cfjails, tq)
+ jails_byname[njails++] = j;
+ qsort(jails_byname, njails, sizeof(struct cfjail *), cmp_jailptr);
+ deps = 0;
+ ldeps = 0;
+ plen = 0;
+ pname = NULL;
+ TAILQ_FOREACH(j, &cfjails, tq) {
+ if (j->flags & JF_FAILED)
+ continue;
+ if ((p = j->intparams[IP_DEPEND])) {
+ TAILQ_FOREACH(s, &p->val, tq) {
+ dj = find_jail(s->s);
+ if (dj != NULL) {
+ deps++;
+ dep_add(j, dj, 0);
+ } else {
+ jail_warnx(j,
+ "depends on undefined jail \"%s\"",
+ s->s);
+ j->flags |= JF_FAILED;
+ }
+ }
+ }
+ /* A jail has an implied dependency on its parent. */
+ if ((cs = strrchr(j->name, '.')))
+ {
+ if (plen < (size_t)(cs - j->name + 1)) {
+ plen = (cs - j->name) + 1;
+ pname = erealloc(pname, plen);
+ }
+ strlcpy(pname, j->name, plen);
+ dj = find_jail(pname);
+ if (dj != NULL) {
+ ldeps++;
+ dep_add(j, dj, DF_LIGHT);
+ }
+ }
+ }
+
+ /* Look for dependency loops. */
+ if (deps && (deps > 1 || ldeps)) {
+ (void)start_state(NULL, 0, 0, 0);
+ while ((j = TAILQ_FIRST(&ready))) {
+ requeue(j, &cfjails);
+ dep_done(j, DF_NOFAIL);
+ }
+ while ((j = TAILQ_FIRST(&depend)) != NULL) {
+ jail_warnx(j, "dependency loop");
+ j->flags |= JF_FAILED;
+ do {
+ requeue(j, &cfjails);
+ dep_done(j, DF_NOFAIL);
+ } while ((j = TAILQ_FIRST(&ready)));
+ }
+ TAILQ_FOREACH(j, &cfjails, tq)
+ STAILQ_FOREACH(d, &j->dep[DEP_FROM], tq[DEP_FROM])
+ d->flags &= ~DF_SEEN;
+ }
+ if (pname != NULL)
+ free(pname);
+}
+
+/*
+ * Return if a jail has dependencies.
+ */
+int
+dep_check(struct cfjail *j)
+{
+ int reset, depfrom, depto, ndeps, rev;
+ struct cfjail *dj;
+ struct cfdepend *d;
+
+ static int bits[] = { 0, 1, 1, 2, 1, 2, 2, 3 };
+
+ if (j->ndeps == 0)
+ return 0;
+ ndeps = 0;
+ if ((rev = JF_DO_STOP(j->flags))) {
+ depfrom = DEP_TO;
+ depto = DEP_FROM;
+ } else {
+ depfrom = DEP_FROM;
+ depto = DEP_TO;
+ }
+ STAILQ_FOREACH(d, &j->dep[depfrom], tq[depfrom]) {
+ if (d->flags & DF_SEEN)
+ continue;
+ dj = d->j[depto];
+ if (dj->flags & JF_FAILED) {
+ if (!(j->flags & (JF_DEPEND | JF_FAILED)) &&
+ verbose >= 0)
+ jail_warnx(j, "skipped");
+ j->flags |= JF_FAILED;
+ continue;
+ }
+ /*
+ * The dependee's state may be set (or changed) as a result of
+ * being in a dependency it wasn't in earlier.
+ */
+ reset = 0;
+ if (bits[dj->flags & JF_OP_MASK] <= 1) {
+ if (!(dj->flags & JF_OP_MASK)) {
+ reset = 1;
+ dj->flags |= JF_DEPEND;
+ requeue(dj, &ready);
+ }
+ /* Set or change the dependee's state. */
+ switch (j->flags & JF_OP_MASK) {
+ case JF_START:
+ dj->flags |= JF_START;
+ break;
+ case JF_SET:
+ if (!(dj->flags & JF_OP_MASK))
+ dj->flags |= JF_SET;
+ else if (dj->flags & JF_STOP)
+ dj->flags |= JF_START;
+ break;
+ case JF_STOP:
+ case JF_RESTART:
+ if (!(dj->flags & JF_STOP))
+ reset = 1;
+ dj->flags |= JF_STOP;
+ if (dj->flags & JF_SET)
+ dj->flags ^= (JF_START | JF_SET);
+ break;
+ }
+ }
+ if (reset)
+ dep_reset(dj);
+ if (!((d->flags & DF_LIGHT) &&
+ (rev ? dj->jid < 0 : dj->jid > 0)))
+ ndeps++;
+ }
+ if (ndeps == 0)
+ return 0;
+ requeue(j, &depend);
+ return 1;
+}
+
+/*
+ * Resolve any dependencies from a finished jail.
+ */
+void
+dep_done(struct cfjail *j, unsigned flags)
+{
+ struct cfjail *dj;
+ struct cfdepend *d;
+ int depfrom, depto;
+
+ if (JF_DO_STOP(j->flags)) {
+ depfrom = DEP_TO;
+ depto = DEP_FROM;
+ } else {
+ depfrom = DEP_FROM;
+ depto = DEP_TO;
+ }
+ STAILQ_FOREACH(d, &j->dep[depto], tq[depto]) {
+ if ((d->flags & DF_SEEN) | (flags & ~d->flags & DF_LIGHT))
+ continue;
+ d->flags |= DF_SEEN;
+ dj = d->j[depfrom];
+ if (!(flags & DF_NOFAIL) && (j->flags & JF_FAILED) &&
+ (j->flags & (JF_OP_MASK | JF_DEPEND)) !=
+ (JF_SET | JF_DEPEND)) {
+ if (!(dj->flags & (JF_DEPEND | JF_FAILED)) &&
+ verbose >= 0)
+ jail_warnx(dj, "skipped");
+ dj->flags |= JF_FAILED;
+ }
+ if (!--dj->ndeps && dj->queue == &depend)
+ requeue(dj, &ready);
+ }
+}
+
+/*
+ * Count a jail's dependencies and mark them as unseen.
+ */
+void
+dep_reset(struct cfjail *j)
+{
+ int depfrom;
+ struct cfdepend *d;
+
+ depfrom = JF_DO_STOP(j->flags) ? DEP_TO : DEP_FROM;
+ j->ndeps = 0;
+ STAILQ_FOREACH(d, &j->dep[depfrom], tq[depfrom])
+ j->ndeps++;
+}
+
+/*
+ * Find the next jail ready to do something.
+ */
+struct cfjail *
+next_jail(void)
+{
+ struct cfjail *j;
+
+ if (!(j = next_proc(!TAILQ_EMPTY(&ready))) &&
+ (j = TAILQ_FIRST(&ready)) && JF_DO_STOP(j->flags) &&
+ (j = TAILQ_LAST(&ready, cfjails)) && !JF_DO_STOP(j->flags)) {
+ TAILQ_FOREACH_REVERSE(j, &ready, cfjails, tq)
+ if (JF_DO_STOP(j->flags))
+ break;
+ }
+ if (j != NULL)
+ requeue(j, &cfjails);
+ return j;
+}
+
+/*
+ * Set jails to the proper start state.
+ */
+int
+start_state(const char *target, int docf, unsigned state, int running)
+{
+ struct iovec jiov[6];
+ struct cfjail *j, *tj;
+ int jid;
+ char namebuf[MAXHOSTNAMELEN];
+
+ if (!target || (!docf && state != JF_STOP) ||
+ (!running && !strcmp(target, "*"))) {
+ /*
+ * For a global wildcard (including no target specified),
+ * set the state on all jails and start with those that
+ * have no dependencies.
+ */
+ TAILQ_FOREACH_SAFE(j, &cfjails, tq, tj) {
+ j->flags = (j->flags & JF_FAILED) | state |
+ (docf ? JF_WILD : 0);
+ dep_reset(j);
+ requeue(j, j->ndeps ? &depend : &ready);
+ }
+ } else if (wild_jail_name(target)) {
+ /*
+ * For targets specified singly, or with a non-global wildcard,
+ * set their state and call them ready (even if there are
+ * dependencies). Leave everything else unqueued for now.
+ */
+ if (running) {
+ /*
+ * -R matches its wildcards against currently running
+ * jails, not against the config file.
+ */
+ jiov[0].iov_base = __DECONST(char *, "lastjid");
+ jiov[0].iov_len = sizeof("lastjid");
+ jiov[1].iov_base = &jid;
+ jiov[1].iov_len = sizeof(jid);
+ jiov[2].iov_base = __DECONST(char *, "jid");
+ jiov[2].iov_len = sizeof("jid");
+ jiov[3].iov_base = &jid;
+ jiov[3].iov_len = sizeof(jid);
+ jiov[4].iov_base = __DECONST(char *, "name");
+ jiov[4].iov_len = sizeof("name");
+ jiov[5].iov_base = &namebuf;
+ jiov[5].iov_len = sizeof(namebuf);
+ for (jid = 0; jail_get(jiov, 6, 0) > 0; ) {
+ if (wild_jail_match(namebuf, target)) {
+ j = add_jail();
+ j->name = estrdup(namebuf);
+ j->jid = jid;
+ j->flags = (j->flags & JF_FAILED) |
+ state | JF_WILD;
+ dep_reset(j);
+ requeue(j, &ready);
+ }
+ }
+ } else {
+ TAILQ_FOREACH_SAFE(j, &cfjails, tq, tj) {
+ if (wild_jail_match(j->name, target)) {
+ j->flags = (j->flags & JF_FAILED) |
+ state | JF_WILD;
+ dep_reset(j);
+ requeue(j, &ready);
+ }
+ }
+ }
+ } else {
+ j = find_jail(target);
+ if (j == NULL && state == JF_STOP) {
+ /* Allow -[rR] to specify a currently running jail. */
+ if ((jid = running_jid(target, JAIL_DYING)) > 0) {
+ j = add_jail();
+ j->name = estrdup(target);
+ j->jid = jid;
+ }
+ }
+ if (j == NULL) {
+ warnx("\"%s\" not found", target);
+ return -1;
+ }
+ j->flags = (j->flags & JF_FAILED) | state;
+ dep_reset(j);
+ requeue(j, &ready);
+ }
+ return 0;
+}
+
+/*
+ * Move a jail to a new list.
+ */
+void
+requeue(struct cfjail *j, struct cfjails *queue)
+{
+ if (j->queue != queue) {
+ TAILQ_REMOVE(j->queue, j, tq);
+ TAILQ_INSERT_TAIL(queue, j, tq);
+ j->queue = queue;
+ }
+}
+
+/*
+ * Add a dependency edge between two jails.
+ */
+static void
+dep_add(struct cfjail *from, struct cfjail *to, unsigned flags)
+{
+ struct cfdepend *d;
+
+ d = emalloc(sizeof(struct cfdepend));
+ d->flags = flags;
+ d->j[DEP_FROM] = from;
+ d->j[DEP_TO] = to;
+ STAILQ_INSERT_TAIL(&from->dep[DEP_FROM], d, tq[DEP_FROM]);
+ STAILQ_INSERT_TAIL(&to->dep[DEP_TO], d, tq[DEP_TO]);
+}
+
+/*
+ * Compare jail pointers for qsort/bsearch.
+ */
+static int
+cmp_jailptr(const void *a, const void *b)
+{
+ return strcmp((*((struct cfjail * const *)a))->name,
+ ((*(struct cfjail * const *)b))->name);
+}
+
+static int
+cmp_jailptr_name(const void *a, const void *b)
+{
+ return strcmp((const char *)a, ((*(struct cfjail * const *)b))->name);
+}
+
+/*
+ * Find a jail object by name.
+ */
+static struct cfjail *
+find_jail(const char *name)
+{
+ struct cfjail **jp;
+
+ jp = bsearch(name, jails_byname, njails, sizeof(struct cfjail *),
+ cmp_jailptr_name);
+ return jp ? *jp : NULL;
+}
+
+/*
+ * Return the named jail's jid if it is running, and -1 if it isn't.
+ */
+static int
+running_jid(const char *name, int flags)
+{
+ struct iovec jiov[2];
+ char *ep;
+ int jid;
+
+ if ((jid = strtol(name, &ep, 10)) && !*ep) {
+ jiov[0].iov_base = __DECONST(char *, "jid");
+ jiov[0].iov_len = sizeof("jid");
+ jiov[1].iov_base = &jid;
+ jiov[1].iov_len = sizeof(jid);
+ } else {
+ jiov[0].iov_base = __DECONST(char *, "name");
+ jiov[0].iov_len = sizeof("name");
+ jiov[1].iov_len = strlen(name) + 1;
+ jiov[1].iov_base = alloca(jiov[1].iov_len);
+ strcpy(jiov[1].iov_base, name);
+ }
+ return jail_get(jiov, 2, flags);
+}
diff --git a/usr.sbin/jexec/Makefile b/usr.sbin/jexec/Makefile
new file mode 100644
index 0000000..700da97
--- /dev/null
+++ b/usr.sbin/jexec/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= jexec
+MAN= jexec.8
+LIBADD= jail util
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/jexec/Makefile.depend b/usr.sbin/jexec/Makefile.depend
new file mode 100644
index 0000000..b92a98a
--- /dev/null
+++ b/usr.sbin/jexec/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libjail \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/jexec/jexec.8 b/usr.sbin/jexec/jexec.8
new file mode 100644
index 0000000..19ed42d
--- /dev/null
+++ b/usr.sbin/jexec/jexec.8
@@ -0,0 +1,85 @@
+.\"
+.\" Copyright (c) 2003 Mike Barcroft <mike@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 Jul 11, 2015
+.Dt JEXEC 8
+.Os
+.Sh NAME
+.Nm jexec
+.Nd "execute a command inside an existing jail"
+.Sh SYNOPSIS
+.Nm
+.Op Fl l
+.Op Fl u Ar username | Fl U Ar username
+.Ar jail Op Ar command ...
+.Sh DESCRIPTION
+The
+.Nm
+utility executes
+.Ar command
+inside the
+.Ar jail
+identified by its jid or name.
+If
+.Ar command
+is not specified then the user's shell is used.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl l
+Execute in a clean environment.
+The environment is discarded except for
+.Ev HOME , SHELL , TERM , USER ,
+and anything from the login class capability database for the user.
+.It Fl u Ar username
+The user name from host environment as whom the
+.Ar command
+should run.
+.It Fl U Ar username
+The user name from jailed environment as whom the
+.Ar command
+should run.
+.El
+.Sh SEE ALSO
+.Xr jail_attach 2 ,
+.Xr jail 8 ,
+.Xr jls 8
+.Sh HISTORY
+The
+.Nm
+utility was added in
+.Fx 5.1 .
+.Sh BUGS
+If the jail is not identified by
+.Ar jid
+there is a possible race in between the lookup of the jail
+and executing the command inside the jail.
+Giving a
+.Ar jid
+has a similar race as another process can stop the jail and
+start another one after the user looked up the
+.Ar jid .
diff --git a/usr.sbin/jexec/jexec.c b/usr.sbin/jexec/jexec.c
new file mode 100644
index 0000000..f8974dc
--- /dev/null
+++ b/usr.sbin/jexec/jexec.c
@@ -0,0 +1,191 @@
+/*-
+ * Copyright (c) 2003 Mike Barcroft <mike@FreeBSD.org>
+ * Copyright (c) 2008 Bjoern A. Zeeb <bz@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/param.h>
+#include <sys/jail.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <err.h>
+#include <errno.h>
+#include <jail.h>
+#include <limits.h>
+#include <login_cap.h>
+#include <paths.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+extern char **environ;
+
+static void get_user_info(const char *username, const struct passwd **pwdp,
+ login_cap_t **lcapp);
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ int jid;
+ login_cap_t *lcap = NULL;
+ int ch, clean, uflag, Uflag;
+ char *cleanenv;
+ const struct passwd *pwd = NULL;
+ const char *username, *shell, *term;
+
+ ch = clean = uflag = Uflag = 0;
+ username = NULL;
+
+ while ((ch = getopt(argc, argv, "lnu:U:")) != -1) {
+ switch (ch) {
+ case 'l':
+ clean = 1;
+ break;
+ case 'n':
+ /* Specified name, now unused */
+ break;
+ case 'u':
+ username = optarg;
+ uflag = 1;
+ break;
+ case 'U':
+ username = optarg;
+ Uflag = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc < 1)
+ usage();
+ if (uflag && Uflag)
+ usage();
+ if (uflag || (clean && !Uflag))
+ /* User info from the home environment */
+ get_user_info(username, &pwd, &lcap);
+
+ /* Attach to the jail */
+ jid = jail_getid(argv[0]);
+ if (jid < 0)
+ errx(1, "%s", jail_errmsg);
+ if (jail_attach(jid) == -1)
+ err(1, "jail_attach(%d)", jid);
+ if (chdir("/") == -1)
+ err(1, "chdir(): /");
+
+ /* Set up user environment */
+ if (clean || username != NULL) {
+ if (Uflag)
+ /* User info from the jail environment */
+ get_user_info(username, &pwd, &lcap);
+ if (clean) {
+ term = getenv("TERM");
+ cleanenv = NULL;
+ environ = &cleanenv;
+ setenv("PATH", "/bin:/usr/bin", 1);
+ if (term != NULL)
+ setenv("TERM", term, 1);
+ }
+ if (setgid(pwd->pw_gid) != 0)
+ err(1, "setgid");
+ if (setusercontext(lcap, pwd, pwd->pw_uid, username
+ ? LOGIN_SETALL & ~LOGIN_SETGROUP & ~LOGIN_SETLOGIN
+ : LOGIN_SETPATH | LOGIN_SETENV) != 0)
+ err(1, "setusercontext");
+ login_close(lcap);
+ setenv("USER", pwd->pw_name, 1);
+ setenv("HOME", pwd->pw_dir, 1);
+ setenv("SHELL",
+ *pwd->pw_shell ? pwd->pw_shell : _PATH_BSHELL, 1);
+ if (clean && chdir(pwd->pw_dir) < 0)
+ err(1, "chdir: %s", pwd->pw_dir);
+ endpwent();
+ }
+
+ /* Run the specified command, or the shell */
+ if (argc > 1) {
+ if (execvp(argv[1], argv + 1) < 0)
+ err(1, "execvp: %s", argv[1]);
+ } else {
+ if (!(shell = getenv("SHELL")))
+ shell = _PATH_BSHELL;
+ if (execlp(shell, shell, "-i", NULL) < 0)
+ err(1, "execlp: %s", shell);
+ }
+ exit(0);
+}
+
+static void
+get_user_info(const char *username, const struct passwd **pwdp,
+ login_cap_t **lcapp)
+{
+ uid_t uid;
+ const struct passwd *pwd;
+
+ errno = 0;
+ if (username) {
+ pwd = getpwnam(username);
+ if (pwd == NULL) {
+ if (errno)
+ err(1, "getpwnam: %s", username);
+ else
+ errx(1, "%s: no such user", username);
+ }
+ } else {
+ uid = getuid();
+ pwd = getpwuid(uid);
+ if (pwd == NULL) {
+ if (errno)
+ err(1, "getpwuid: %d", uid);
+ else
+ errx(1, "unknown uid: %d", uid);
+ }
+ }
+ *pwdp = pwd;
+ *lcapp = login_getpwclass(pwd);
+ if (*lcapp == NULL)
+ err(1, "getpwclass: %s", pwd->pw_name);
+ if (initgroups(pwd->pw_name, pwd->pw_gid) < 0)
+ err(1, "initgroups: %s", pwd->pw_name);
+}
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "%s\n",
+ "usage: jexec [-l] [-u username | -U username] jail [command ...]");
+ exit(1);
+}
diff --git a/usr.sbin/jls/Makefile b/usr.sbin/jls/Makefile
new file mode 100644
index 0000000..4c1de74
--- /dev/null
+++ b/usr.sbin/jls/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= jls
+MAN= jls.8
+LIBADD= jail xo
+
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+= -DINET6
+.endif
+.if ${MK_INET_SUPPORT} != "no"
+CFLAGS+= -DINET
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/jls/Makefile.depend b/usr.sbin/jls/Makefile.depend
new file mode 100644
index 0000000..50e6f23
--- /dev/null
+++ b/usr.sbin/jls/Makefile.depend
@@ -0,0 +1,22 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libjail \
+ lib/libutil \
+ lib/libxo \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/jls/jls.8 b/usr.sbin/jls/jls.8
new file mode 100644
index 0000000..3b83d73
--- /dev/null
+++ b/usr.sbin/jls/jls.8
@@ -0,0 +1,128 @@
+.\"
+.\" Copyright (c) 2003 Mike Barcroft <mike@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 July 20, 2012
+.Dt JLS 8
+.Os
+.Sh NAME
+.Nm jls
+.Nd "list jails"
+.Sh SYNOPSIS
+.Nm
+.Op Fl -libxo
+.Op Fl dhNnqsv
+.Op Fl j Ar jail
+.Op Ar parameter ...
+.Sh DESCRIPTION
+The
+.Nm
+utility lists all active jails, or the specified jail.
+Each jail is represented by one row which contains space-separated values of
+the listed
+.Ar parameters ,
+including the pseudo-parameter
+.Va all
+which will show all available jail parameters.
+A list of available parameters can be retrieved via
+.Dq Nm sysctl Fl d Va security.jail.param .
+See
+.Xr jail 8
+for a description of some core parameters.
+.Pp
+If no
+.Ar parameters
+or any of the options
+.Fl hns
+are given, the following four columns will be printed:
+jail identifier (jid), IP address (ip4.addr), hostname (host.hostname),
+and path (path).
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl -libxo
+Generate output via
+.Xr libxo 3
+in a selection of different human and machine readable formats.
+See
+.Xr xo_parse_args 3
+for details on command line arguments.
+.It Fl d
+List
+.Va dying
+as well as active jails.
+.It Fl h
+Print a header line containing the parameters listed.
+If no parameters are given on the command line,
+.Va all
+is assumed.
+.It Fl N
+In the standard display mode, print each jail's name instead of its
+numeric ID.
+If the jail does not have a name, the numeric ID is printed instead.
+.It Fl n
+Print parameters in
+.Dq name=value
+format, where each parameter is preceded by its name.
+If no parameters are given on the command line,
+.Va all
+is assumed.
+.It Fl q
+Put quotes around parameters if they contain spaces or quotes, or are
+the empty string.
+.It Fl s
+Print parameters suitable for passing to
+.Xr jail 8 ,
+skipping read-only and unused parameters.
+Implies
+.Fl nq .
+.It Fl v
+Extend the standard display with a multiple-line summary per jail,
+containing the following parameters:
+jail identifier (jid), hostname (host.hostname), path (path),
+jail name (name), jail state (dying), cpuset ID (cpuset),
+IP address(es) (ip4.addr and ip6.addr).
+.It Fl j Ar jail
+The jid or name of the
+.Ar jail
+to list.
+Without this option, all active jails will be listed.
+.El
+.Sh SEE ALSO
+.Xr jail_get 2 ,
+.Xr jail 8 ,
+.Xr jexec 8 ,
+.Xr libxo 3 ,
+.Xr xo_parse_args 3
+.Sh HISTORY
+The
+.Nm
+utility was added in
+.Fx 5.1 .
+Extensible jail parameters were introduced in
+.Fx 8.0 .
+libxo support was added in
+.Fx 11.0 .
diff --git a/usr.sbin/jls/jls.c b/usr.sbin/jls/jls.c
new file mode 100644
index 0000000..9dd7641
--- /dev/null
+++ b/usr.sbin/jls/jls.c
@@ -0,0 +1,555 @@
+/*-
+ * Copyright (c) 2003 Mike Barcroft <mike@FreeBSD.org>
+ * Copyright (c) 2008 Bjoern A. Zeeb <bz@FreeBSD.org>
+ * Copyright (c) 2009 James Gritton <jamie@FreeBSD.org>
+ * Copyright (c) 2015 Emmanuel Vadot <manu@bocal.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/param.h>
+#include <sys/jail.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <err.h>
+#include <errno.h>
+#include <jail.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <libxo/xo.h>
+
+#define JP_USER 0x01000000
+#define JP_OPT 0x02000000
+
+#define JLS_XO_VERSION "1"
+
+#define PRINT_DEFAULT 0x01
+#define PRINT_HEADER 0x02
+#define PRINT_NAMEVAL 0x04
+#define PRINT_QUOTED 0x08
+#define PRINT_SKIP 0x10
+#define PRINT_VERBOSE 0x20
+#define PRINT_JAIL_NAME 0x40
+
+static struct jailparam *params;
+static int *param_parent;
+static int nparams;
+#ifdef INET6
+static int ip6_ok;
+#endif
+#ifdef INET
+static int ip4_ok;
+#endif
+
+static int add_param(const char *name, void *value, size_t valuelen,
+ struct jailparam *source, unsigned flags);
+static int sort_param(const void *a, const void *b);
+static char *noname(const char *name);
+static char *nononame(const char *name);
+static int print_jail(int pflags, int jflags);
+static void quoted_print(int pflags, char *name, char *value);
+
+int
+main(int argc, char **argv)
+{
+ char *dot, *ep, *jname, *pname;
+ int c, i, jflags, jid, lastjid, pflags, spc;
+
+ argc = xo_parse_args(argc, argv);
+ if (argc < 0)
+ exit(1);
+
+ xo_set_version(JLS_XO_VERSION);
+ jname = NULL;
+ pflags = jflags = jid = 0;
+ while ((c = getopt(argc, argv, "adj:hNnqsv")) >= 0)
+ switch (c) {
+ case 'a':
+ case 'd':
+ jflags |= JAIL_DYING;
+ break;
+ case 'j':
+ jid = strtoul(optarg, &ep, 10);
+ if (!jid || *ep) {
+ jid = 0;
+ jname = optarg;
+ }
+ break;
+ case 'h':
+ pflags = (pflags & ~(PRINT_SKIP | PRINT_VERBOSE)) |
+ PRINT_HEADER;
+ break;
+ case 'N':
+ pflags |= PRINT_JAIL_NAME;
+ break;
+ case 'n':
+ pflags = (pflags & ~PRINT_VERBOSE) | PRINT_NAMEVAL;
+ break;
+ case 'q':
+ pflags |= PRINT_QUOTED;
+ break;
+ case 's':
+ pflags = (pflags & ~(PRINT_HEADER | PRINT_VERBOSE)) |
+ PRINT_NAMEVAL | PRINT_QUOTED | PRINT_SKIP;
+ break;
+ case 'v':
+ pflags = (pflags &
+ ~(PRINT_HEADER | PRINT_NAMEVAL | PRINT_SKIP)) |
+ PRINT_VERBOSE;
+ break;
+ default:
+ xo_errx(1, "usage: jls [-dhNnqv] [-j jail] [param ...]");
+ }
+
+#ifdef INET6
+ ip6_ok = feature_present("inet6");
+#endif
+#ifdef INET
+ ip4_ok = feature_present("inet");
+#endif
+
+ /* Add the parameters to print. */
+ if (optind == argc) {
+ if (pflags & (PRINT_HEADER | PRINT_NAMEVAL))
+ add_param("all", NULL, (size_t)0, NULL, JP_USER);
+ else if (pflags & PRINT_VERBOSE) {
+ add_param("jid", NULL, (size_t)0, NULL, JP_USER);
+ add_param("host.hostname", NULL, (size_t)0, NULL,
+ JP_USER);
+ add_param("path", NULL, (size_t)0, NULL, JP_USER);
+ add_param("name", NULL, (size_t)0, NULL, JP_USER);
+ add_param("dying", NULL, (size_t)0, NULL, JP_USER);
+ add_param("cpuset.id", NULL, (size_t)0, NULL, JP_USER);
+#ifdef INET
+ if (ip4_ok)
+ add_param("ip4.addr", NULL, (size_t)0, NULL,
+ JP_USER);
+#endif
+#ifdef INET6
+ if (ip6_ok)
+ add_param("ip6.addr", NULL, (size_t)0, NULL,
+ JP_USER | JP_OPT);
+#endif
+ } else {
+ pflags |= PRINT_DEFAULT;
+ if (pflags & PRINT_JAIL_NAME)
+ add_param("name", NULL, (size_t)0, NULL, JP_USER);
+ else
+ add_param("jid", NULL, (size_t)0, NULL, JP_USER);
+#ifdef INET
+ if (ip4_ok)
+ add_param("ip4.addr", NULL, (size_t)0, NULL,
+ JP_USER);
+#endif
+ add_param("host.hostname", NULL, (size_t)0, NULL,
+ JP_USER);
+ add_param("path", NULL, (size_t)0, NULL, JP_USER);
+ }
+ } else {
+ pflags &= ~PRINT_VERBOSE;
+ while (optind < argc)
+ add_param(argv[optind++], NULL, (size_t)0, NULL,
+ JP_USER);
+ }
+
+ if (pflags & PRINT_SKIP) {
+ /* Check for parameters with jailsys parents. */
+ for (i = 0; i < nparams; i++) {
+ if ((params[i].jp_flags & JP_USER) &&
+ (dot = strchr(params[i].jp_name, '.'))) {
+ pname = alloca((dot - params[i].jp_name) + 1);
+ strlcpy(pname, params[i].jp_name,
+ (dot - params[i].jp_name) + 1);
+ param_parent[i] = add_param(pname,
+ NULL, (size_t)0, NULL, JP_OPT);
+ }
+ }
+ }
+
+ /* Add the index key parameters. */
+ if (jid != 0)
+ add_param("jid", &jid, sizeof(jid), NULL, 0);
+ else if (jname != NULL)
+ add_param("name", jname, strlen(jname), NULL, 0);
+ else
+ add_param("lastjid", &lastjid, sizeof(lastjid), NULL, 0);
+
+ /* Print a header line if requested. */
+ if (pflags & PRINT_VERBOSE) {
+ xo_emit("{T:/%3s}{T:JID}{P: }{T:Hostname}{Pd:/%22s}{T:Path}\n",
+ "", "");
+ xo_emit("{P:/%8s}{T:Name}{Pd:/%26s}{T:State}\n", "", "");
+ xo_emit("{P:/%8s}{T:CPUSetID}\n", "");
+ xo_emit("{P:/%8s}{T:IP Address(es)}\n", "");
+ }
+ else if (pflags & PRINT_DEFAULT)
+ if (pflags & PRINT_JAIL_NAME)
+ xo_emit("{P: }{T:JID/%-15s}{P: }{T:IP Address/%-15s}"
+ "{P: }{T:Hostname/%-29s}{P: }{T:Path}\n");
+ else
+ xo_emit("{T:JID/%6s}{P: }{T:IP Address}{P:/%6s}"
+ "{T:Hostname}{P:/%22s}{T:Path}\n", "", "");
+ else if (pflags & PRINT_HEADER) {
+ for (i = spc = 0; i < nparams; i++)
+ if (params[i].jp_flags & JP_USER) {
+ if (spc)
+ xo_emit("{P: }");
+ else
+ spc = 1;
+ xo_emit(params[i].jp_name);
+ }
+ xo_emit("{P:\n}");
+ }
+
+ xo_open_container("jail-information");
+ xo_open_list("jail");
+ /* Fetch the jail(s) and print the parameters. */
+ if (jid != 0 || jname != NULL) {
+ if (print_jail(pflags, jflags) < 0)
+ xo_errx(1, "%s", jail_errmsg);
+ } else {
+ for (lastjid = 0;
+ (lastjid = print_jail(pflags, jflags)) >= 0; )
+ ;
+ if (errno != 0 && errno != ENOENT)
+ xo_errx(1, "%s", jail_errmsg);
+ }
+ xo_close_list("jail");
+ xo_close_container("jail-information");
+ xo_finish();
+ return (0);
+}
+
+static int
+add_param(const char *name, void *value, size_t valuelen,
+ struct jailparam *source, unsigned flags)
+{
+ struct jailparam *param, *tparams;
+ int i, tnparams;
+
+ static int paramlistsize;
+
+ /* The pseudo-parameter "all" scans the list of available parameters. */
+ if (!strcmp(name, "all")) {
+ tnparams = jailparam_all(&tparams);
+ if (tnparams < 0)
+ xo_errx(1, "%s", jail_errmsg);
+ qsort(tparams, (size_t)tnparams, sizeof(struct jailparam),
+ sort_param);
+ for (i = 0; i < tnparams; i++)
+ add_param(tparams[i].jp_name, NULL, (size_t)0,
+ tparams + i, flags);
+ free(tparams);
+ return -1;
+ }
+
+ /* Check for repeat parameters. */
+ for (i = 0; i < nparams; i++)
+ if (!strcmp(name, params[i].jp_name)) {
+ if (value != NULL && jailparam_import_raw(params + i,
+ value, valuelen) < 0)
+ xo_errx(1, "%s", jail_errmsg);
+ params[i].jp_flags |= flags;
+ if (source != NULL)
+ jailparam_free(source, 1);
+ return i;
+ }
+
+ /* Make sure there is room for the new param record. */
+ if (!nparams) {
+ paramlistsize = 32;
+ params = malloc(paramlistsize * sizeof(*params));
+ param_parent = malloc(paramlistsize * sizeof(*param_parent));
+ if (params == NULL || param_parent == NULL)
+ xo_err(1, "malloc");
+ } else if (nparams >= paramlistsize) {
+ paramlistsize *= 2;
+ params = realloc(params, paramlistsize * sizeof(*params));
+ param_parent = realloc(param_parent,
+ paramlistsize * sizeof(*param_parent));
+ if (params == NULL || param_parent == NULL)
+ xo_err(1, "realloc");
+ }
+
+ /* Look up the parameter. */
+ param_parent[nparams] = -1;
+ param = params + nparams++;
+ if (source != NULL) {
+ *param = *source;
+ param->jp_flags |= flags;
+ return param - params;
+ }
+ if (jailparam_init(param, name) < 0 ||
+ (value != NULL ? jailparam_import_raw(param, value, valuelen)
+ : jailparam_import(param, value)) < 0) {
+ if (flags & JP_OPT) {
+ nparams--;
+ return (-1);
+ }
+ xo_errx(1, "%s", jail_errmsg);
+ }
+ param->jp_flags = flags;
+ return param - params;
+}
+
+static int
+sort_param(const void *a, const void *b)
+{
+ const struct jailparam *parama, *paramb;
+ char *ap, *bp;
+
+ /* Put top-level parameters first. */
+ parama = a;
+ paramb = b;
+ ap = strchr(parama->jp_name, '.');
+ bp = strchr(paramb->jp_name, '.');
+ if (ap && !bp)
+ return (1);
+ if (bp && !ap)
+ return (-1);
+ return (strcmp(parama->jp_name, paramb->jp_name));
+}
+
+static char *
+noname(const char *name)
+{
+ char *nname, *p;
+
+ nname = malloc(strlen(name) + 3);
+ if (nname == NULL)
+ xo_err(1, "malloc");
+ p = strrchr(name, '.');
+ if (p != NULL)
+ sprintf(nname, "%.*s.no%s", (int)(p - name), name, p + 1);
+ else
+ sprintf(nname, "no%s", name);
+ return nname;
+}
+
+static char *
+nononame(const char *name)
+{
+ char *nname, *p;
+
+ p = strrchr(name, '.');
+ if (strncmp(p ? p + 1 : name, "no", 2))
+ return NULL;
+ nname = malloc(strlen(name) - 1);
+ if (nname == NULL)
+ xo_err(1, "malloc");
+ if (p != NULL)
+ sprintf(nname, "%.*s.%s", (int)(p - name), name, p + 3);
+ else
+ strcpy(nname, name + 2);
+ return nname;
+}
+
+static int
+print_jail(int pflags, int jflags)
+{
+ char *nname, *xo_nname;
+ char **param_values;
+ int i, ai, jid, count, n, spc;
+ char ipbuf[INET6_ADDRSTRLEN];
+
+ jid = jailparam_get(params, nparams, jflags);
+ if (jid < 0)
+ return jid;
+
+ xo_open_instance("jail");
+
+ if (pflags & PRINT_VERBOSE) {
+ xo_emit("{:jid/%6d}{P: }{:hostname/%-29.29s/%s}{P: }"
+ "{:path/%.74s/%s}\n",
+ *(int *)params[0].jp_value,
+ (char *)params[1].jp_value,
+ (char *)params[2].jp_value);
+ xo_emit("{P: }{:name/%-29.29s/%s}{P: }{:state/%.74s}\n",
+ (char *)params[3].jp_value,
+ *(int *)params[4].jp_value ? "DYING" : "ACTIVE");
+ xo_emit("{P: }{:cpusetid/%d}\n", *(int *)params[5].jp_value);
+ n = 6;
+#ifdef INET
+ if (ip4_ok && !strcmp(params[n].jp_name, "ip4.addr")) {
+ count = params[n].jp_valuelen / sizeof(struct in_addr);
+ for (ai = 0; ai < count; ai++)
+ if (inet_ntop(AF_INET,
+ &((struct in_addr *)params[n].jp_value)[ai],
+ ipbuf, sizeof(ipbuf)) == NULL)
+ xo_err(1, "inet_ntop");
+ else {
+ xo_emit("{P: }{l:ipv4_addrs}{P:\n}", ipbuf);
+ }
+ n++;
+ }
+#endif
+#ifdef INET6
+ if (ip6_ok && !strcmp(params[n].jp_name, "ip6.addr")) {
+ count = params[n].jp_valuelen / sizeof(struct in6_addr);
+ for (ai = 0; ai < count; ai++)
+ if (inet_ntop(AF_INET6,
+ &((struct in6_addr *)
+ params[n].jp_value)[ai],
+ ipbuf, sizeof(ipbuf)) == NULL)
+ xo_err(1, "inet_ntop");
+ else
+ xo_emit("{P: }{l:ipv6_addrs}{P:\n}", ipbuf);
+ n++;
+ }
+#endif
+ } else if (pflags & PRINT_DEFAULT) {
+ if (pflags & PRINT_JAIL_NAME)
+ xo_emit("{P: }{:name/%-15s/%s}{P: }",
+ (char *)params[0].jp_value);
+ else
+ xo_emit("{:jid/%6d}{P: }", *(int *)params[0].jp_value);
+ xo_emit("{:ipv4/%-15.15s/%s}{P: }{:hostname/%-29.29s/%s}{P: }{:path/%.74s/%s}\n",
+#ifdef INET
+ (!ip4_ok || params[1].jp_valuelen == 0) ? ""
+ : inet_ntoa(*(struct in_addr *)params[1].jp_value),
+ (char *)params[2-!ip4_ok].jp_value,
+ (char *)params[3-!ip4_ok].jp_value);
+#else
+ "-",
+ (char *)params[1].jp_value,
+ (char *)params[2].jp_value);
+#endif
+ } else {
+ param_values = alloca(nparams * sizeof(*param_values));
+ for (i = 0; i < nparams; i++) {
+ if (!(params[i].jp_flags & JP_USER))
+ continue;
+ param_values[i] = jailparam_export(params + i);
+ if (param_values[i] == NULL)
+ xo_errx(1, "%s", jail_errmsg);
+ }
+ for (i = spc = 0; i < nparams; i++) {
+ if (!(params[i].jp_flags & JP_USER))
+ continue;
+ if ((pflags & PRINT_SKIP) &&
+ ((!(params[i].jp_ctltype &
+ (CTLFLAG_WR | CTLFLAG_TUN))) ||
+ (param_parent[i] >= 0 &&
+ *(int *)params[param_parent[i]].jp_value !=
+ JAIL_SYS_NEW)))
+ continue;
+ if (spc)
+ xo_emit("{P: }");
+ else
+ spc = 1;
+ if (pflags & PRINT_NAMEVAL) {
+ /*
+ * Generally "name=value", but for booleans
+ * either "name" or "noname".
+ */
+ if (params[i].jp_flags &
+ (JP_BOOL | JP_NOBOOL)) {
+ if (*(int *)params[i].jp_value) {
+ asprintf(&xo_nname, "{en:%s/true}", params[i].jp_name);
+ xo_emit(xo_nname);
+ xo_emit("{d:/%s}", params[i].jp_name);
+ }
+ else {
+ nname = (params[i].jp_flags &
+ JP_NOBOOL) ?
+ nononame(params[i].jp_name)
+ : noname(params[i].jp_name);
+ if (params[i].jp_flags & JP_NOBOOL) {
+ asprintf(&xo_nname, "{en:%s/true}", params[i].jp_name);
+ xo_emit(xo_nname);
+ } else {
+ asprintf(&xo_nname, "{en:%s/false}", params[i].jp_name);
+ xo_emit(xo_nname);
+ }
+ xo_emit("{d:/%s}", nname);
+ free(nname);
+ }
+ free(xo_nname);
+ continue;
+ }
+ xo_emit("{d:%s}=", params[i].jp_name);
+ }
+ if (params[i].jp_valuelen == 0) {
+ if (pflags & PRINT_QUOTED)
+ xo_emit("{P:\"\"}");
+ else if (!(pflags & PRINT_NAMEVAL))
+ xo_emit("{P:-}");
+ } else {
+ quoted_print(pflags, params[i].jp_name, param_values[i]);
+ }
+ }
+ xo_emit("{P:\n}");
+ for (i = 0; i < nparams; i++)
+ if (params[i].jp_flags & JP_USER)
+ free(param_values[i]);
+ }
+
+ xo_close_instance("jail");
+ return (jid);
+}
+
+static void
+quoted_print(int pflags, char *name, char *value)
+{
+ int qc;
+ char *p = value;
+ char *param_name_value;
+
+ /* An empty string needs quoting. */
+ if (!*p) {
+ asprintf(&param_name_value, "{k:%s}{d:%s/\"\"}", name, name);
+ xo_emit(param_name_value);
+ free(param_name_value);
+ return;
+ }
+
+ asprintf(&param_name_value, "{:%s/%%s}", name);
+ /*
+ * The value will be surrounded by quotes if it contains spaces
+ * or quotes.
+ */
+ qc = strchr(p, '\'') ? '"'
+ : strchr(p, '"') ? '\''
+ : strchr(p, ' ') || strchr(p, '\t') ? '"'
+ : 0;
+
+ if (qc && pflags & PRINT_QUOTED)
+ xo_emit("{P:/%c}", qc);
+
+ xo_emit(param_name_value, value);
+
+ free(param_name_value);
+
+ if (qc && pflags & PRINT_QUOTED)
+ xo_emit("{P:/%c}", qc);
+}
diff --git a/usr.sbin/kbdcontrol/Makefile b/usr.sbin/kbdcontrol/Makefile
new file mode 100644
index 0000000..42fa3e8
--- /dev/null
+++ b/usr.sbin/kbdcontrol/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+PROG= kbdcontrol
+MAN= kbdcontrol.1 kbdmap.5
+MLINKS= kbdmap.5 keymap.5
+SRCS= kbdcontrol.c lex.l
+
+WARNS?= 4
+CFLAGS+= -I${.CURDIR}
+
+LIBADD= l
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/kbdcontrol/Makefile.depend b/usr.sbin/kbdcontrol/Makefile.depend
new file mode 100644
index 0000000..523c8ae
--- /dev/null
+++ b/usr.sbin/kbdcontrol/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ usr.bin/lex/lib \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+lex.o: lex.c
+lex.po: lex.c
+.endif
diff --git a/usr.sbin/kbdcontrol/kbdcontrol.1 b/usr.sbin/kbdcontrol/kbdcontrol.1
new file mode 100644
index 0000000..ea76b53
--- /dev/null
+++ b/usr.sbin/kbdcontrol/kbdcontrol.1
@@ -0,0 +1,291 @@
+.\"
+.\" kbdcontrol - a utility for manipulating the syscons or vt keyboard driver section
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" @(#)kbdcontrol.1
+.\" $FreeBSD$
+.\"
+.Dd January 29, 2008
+.Dt KBDCONTROL 1
+.Os
+.Sh NAME
+.Nm kbdcontrol
+.Nd keyboard control and configuration utility
+.Sh SYNOPSIS
+.Nm
+.Op Fl dFKix
+.Op Fl A Ar name
+.Op Fl a Ar name
+.Oo
+.Fl b
+.Ar duration . Ns Ar pitch | Ar belltype
+.Oc
+.Oo
+.Fl r
+.Ar delay . Ns Ar repeat | Ar speed
+.Oc
+.Op Fl l Ar keymap_file
+.Op Fl f Ar # Ar string
+.Op Fl k Ar keyboard_device
+.Op Fl L Ar keymap_file
+.Sh DESCRIPTION
+The
+.Nm
+command is used to set various keyboard related options for the
+.Xr syscons 4
+or
+.Xr vt 4
+console driver and the keyboard drivers,
+such as key map, keyboard repeat and delay rates, bell
+characteristics etc.
+.Pp
+Keyboard options may be automatically configured at system boot time by
+setting variables in
+.Pa /etc/rc.conf .
+See
+.Sx Boot Time Configuration
+below.
+.Pp
+The following command line options are supported:
+.Bl -tag -width indent
+.It Fl A Ar name
+Detach the keyboard, specified by the keyboard device name, from the keyboard
+multiplexer.
+When using this option, the standard input of the
+.Nm
+process should be redirected from the keyboard multiplexer keyboard device
+(if the keyboard multiplexer is not the active keyboard) or
+.Pa /dev/console
+(if the keyboard multiplexer is the active keyboard and
+you are not working on the system console).
+.It Fl a Ar name
+Attach the keyboard, specified by the keyboard device name, to the keyboard
+multiplexer.
+When using this option, the standard input of the
+.Nm
+process should be redirected from the keyboard multiplexer keyboard device
+(if the keyboard multiplexer is not the active keyboard) or
+.Pa /dev/console
+(if the keyboard multiplexer is the active keyboard and
+you are not working on the system console).
+.It Fl b Xo
+.Ar duration . Ns Ar pitch | Ar belltype
+.Xc
+Set the bell duration in milliseconds and pitch in hertz.
+If a
+.Ar belltype
+argument is specified, it may be one of
+.Cm normal
+which sets sound parameters back to normal values,
+.Cm off
+which disables the bell entirely, or
+.Cm visual
+which sets the bell to visual mode, i.e., flashes the screen instead.
+If
+.Ar belltype
+is preceded by the word
+.Cm quiet. ,
+the bell will not be rung when the ringing process is in the background vty.
+The
+.Cm visual
+bell, when chosen, applies to all vtys; other bell types
+can be set individually for each vty.
+.It Fl r Xo
+.Ar delay . Ns Ar repeat | Ar speed
+.Xc
+Set keyboard
+.Ar delay
+(250, 500, 750, 1000)
+and
+.Ar repeat
+(34, 38, 42, 46, 50, 55, 59, 63, 68, 76, 84, 92, 100, 110, 118, 126,
+136, 152, 168, 184, 200, 220, 236, 252, 272, 304, 336, 368, 400, 440,
+472, 504)
+rates, or if a
+.Ar speed
+argument is specified, it may be one of
+.Cm slow
+(1000.504),
+.Cm fast
+(250.34)
+or
+.Cm normal
+(500.126).
+.It Fl l Ar keymap_file
+Install keyboard map file from
+.Ar keymap_file .
+You may load the keyboard map file from a menu-driven command,
+.Xr kbdmap 1 .
+The format of keyboard map files is documented in the
+.Xr kbdmap 5
+manual page.
+.It Fl d
+Dump the current keyboard map onto stdout.
+The output may be redirected to a file and can be loaded
+back to the kernel later by the
+.Fl l
+option above.
+.It Fl f Ar # Ar string
+Set function key number
+.Ar #
+to send
+.Ar string .
+Refer to the man page for the keyboard driver
+(e.g.\&
+.Xr atkbd 4 )
+for available function keys and their numbers.
+.It Fl F
+Set function keys back to the standard definitions.
+.It Fl x
+Use hexadecimal numbers in keyboard map dump.
+.It Fl i
+Print brief information about the keyboard.
+.It Fl K
+Disconnect the keyboard from the console.
+You need to use the
+.Fl k
+option below to associate a keyboard with the console again.
+.It Fl k Ar keyboard_device
+Use the specified device as the console keyboard.
+When using this option, the standard input of the
+.Nm
+process should be redirected from
+.Pa /dev/console
+if you are not working on the system console
+(see the
+.Sx EXAMPLES
+section).
+.It Fl L Ar keymap_file
+Load keyboard map file from
+.Ar keymap_file
+and write the
+.Ft "struct keymap"
+compiled from it to stdout.
+This option is primarily intended for programmers and is probably
+of little use under normal circumstances.
+.El
+.Sh ENVIRONMENT
+The environment variable
+.Ev KEYMAP_PATH
+can hold an alternative path to the keyboard map files.
+.Sh KEYBOARD CONFIGURATION
+.Ss Boot Time Configuration
+You may set variables in
+.Pa /etc/rc.conf
+or
+.Pa /etc/rc.conf.local
+in order to configure the keyboard at boot time.
+The following is the list of relevant variables.
+.Pp
+.Bl -tag -width foo_bar_var -compact
+.It Ar keymap
+Specifies a keyboard map file for the
+.Fl l
+option.
+.It Ar keyrate
+Sets the keyboard repeat rate for the
+.Fl r
+option.
+.It Ar keychange
+Lists function key strings for the
+.Fl f
+option.
+.El
+.Pp
+See
+.Xr rc.conf 5
+for details.
+.Ss Driver Configuration
+The keyboard device driver may let you change default configuration
+options, such as the default keyboard map, so that you do not need to set up
+the options at boot time.
+See keyboard driver manuals
+(e.g.\&
+.Xr atkbd 4 ,
+.Xr ukbd 4 )
+for details.
+.Sh FILES
+.Bl -tag -width /usr/share/syscons/keymaps/foo_bar -compact
+.It Pa /usr/share/syscons/keymaps/*
+keyboard map files for syscons
+.It Pa /usr/share/vt/keymaps/*
+keyboard map files for vt
+.El
+.Sh EXAMPLES
+The following command will load the keyboard map file
+.Pa /usr/share/syscons/keymaps/ru.koi8-r.kbd .
+.Pp
+.Dl kbdcontrol -l /usr/share/syscons/keymaps/ru.koi8-r.kbd
+.Pp
+So long as the keyboard map file resides in
+.Pa /usr/share/syscons/keymaps
+(if using
+.Xr syscons 4 ) or
+.Pa /usr/share/vt/keymaps
+(if using
+.Xr vt 4 ) ,
+you may abbreviate the file name as
+.Pa ru.koi8-r .
+Since
+.Xr vt 4
+uses Unicode, the corresponding keyboard file names omit the encoding
+and typically are just a country code, e.g.\&
+.Pa ru .
+.Pp
+.Dl kbdcontrol -l ru.koi8-r
+.Pp
+The following command will make the function key 10 emit "telnet myhost".
+.Pp
+.Dl kbdcontrol -f 10 \&"telnet myhost\&"
+.Pp
+In order to get the visual effect for bell, but prevent the screen
+from flashing if the bell is to ring in the background screen,
+run the following command.
+.Pp
+.Dl kbdcontrol -b quiet.visual
+.Pp
+To change the default console keyboard to another keyboard,
+for example the first USB keyboard (see
+.Xr ukbd 4 ) ,
+use the following command.
+.Pp
+.Dl kbdcontrol -k /dev/ukbd0 < /dev/console
+.Pp
+To switch back to the default keyboard, use this command.
+.Pp
+.Dl kbdcontrol -k /dev/kbd0
+.Pp
+To allow using both the second USB keyboard and the first AT keyboard
+at the same time on console via the
+.Xr kbdmux 4
+driver, use the following sequence of commands.
+.Bd -literal -offset indent
+kbdcontrol -K < /dev/console
+kbdcontrol -a atkbd0 < /dev/kbdmux0
+kbdcontrol -a ukbd1 < /dev/kbdmux0
+kbdcontrol -k /dev/kbdmux0 < /dev/console
+.Ed
+.Sh SEE ALSO
+.Xr kbdmap 1 ,
+.Xr vidcontrol 1 ,
+.Xr atkbd 4 ,
+.Xr kbdmux 4 ,
+.Xr keyboard 4 ,
+.Xr screen 4 ,
+.Xr syscons 4 ,
+.Xr ukbd 4 ,
+.Xr vt 4 ,
+.Xr kbdmap 5 ,
+.Xr rc.conf 5
+.Sh AUTHORS
+.An S\(/oren Schmidt Aq Mt sos@FreeBSD.org
+.Sh BUGS
+Report when found.
diff --git a/usr.sbin/kbdcontrol/kbdcontrol.c b/usr.sbin/kbdcontrol/kbdcontrol.c
new file mode 100644
index 0000000..0f927ef
--- /dev/null
+++ b/usr.sbin/kbdcontrol/kbdcontrol.c
@@ -0,0 +1,1229 @@
+/*-
+ * Copyright (c) 1994-1995 Søren Schmidt
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer,
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <ctype.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/kbio.h>
+#include <sys/consio.h>
+#include <sys/sysctl.h>
+#include "path.h"
+#include "lex.h"
+
+/*
+ * HALT, PDWN, and PASTE aren't defined in 4.x, but we need them to bridge
+ * to 5.0-current so define them here as a stop gap transition measure.
+ */
+#ifndef HALT
+#define HALT 0xa1 /* halt machine */
+#endif
+#ifndef PDWN
+#define PDWN 0xa2 /* halt machine and power down */
+#endif
+#ifndef PASTE
+#define PASTE 0xa3 /* paste from cut-paste buffer */
+#endif
+
+#define SPECIAL 0x80000000
+
+static const char ctrl_names[32][4] = {
+ "nul", "soh", "stx", "etx", "eot", "enq", "ack", "bel",
+ "bs ", "ht ", "nl ", "vt ", "ff ", "cr ", "so ", "si ",
+ "dle", "dc1", "dc2", "dc3", "dc4", "nak", "syn", "etb",
+ "can", "em ", "sub", "esc", "fs ", "gs ", "rs ", "us "
+ };
+
+static const char acc_names[15][5] = {
+ "dgra", "dacu", "dcir", "dtil", "dmac", "dbre", "ddot",
+ "duml", "dsla", "drin", "dced", "dapo", "ddac", "dogo",
+ "dcar",
+ };
+
+static const char acc_names_u[15][5] = {
+ "DGRA", "DACU", "DCIR", "DTIL", "DMAC", "DBRE", "DDOT",
+ "DUML", "DSLA", "DRIN", "DCED", "DAPO", "DDAC", "DOGO",
+ "DCAR",
+ };
+
+static const char fkey_table[96][MAXFK] = {
+/* 01-04 */ "\033[M", "\033[N", "\033[O", "\033[P",
+/* 05-08 */ "\033[Q", "\033[R", "\033[S", "\033[T",
+/* 09-12 */ "\033[U", "\033[V", "\033[W", "\033[X",
+/* 13-16 */ "\033[Y", "\033[Z", "\033[a", "\033[b",
+/* 17-20 */ "\033[c", "\033[d", "\033[e", "\033[f",
+/* 21-24 */ "\033[g", "\033[h", "\033[i", "\033[j",
+/* 25-28 */ "\033[k", "\033[l", "\033[m", "\033[n",
+/* 29-32 */ "\033[o", "\033[p", "\033[q", "\033[r",
+/* 33-36 */ "\033[s", "\033[t", "\033[u", "\033[v",
+/* 37-40 */ "\033[w", "\033[x", "\033[y", "\033[z",
+/* 41-44 */ "\033[@", "\033[[", "\033[\\","\033[]",
+/* 45-48 */ "\033[^", "\033[_", "\033[`", "\033[{",
+/* 49-52 */ "\033[H", "\033[A", "\033[I", "-" ,
+/* 53-56 */ "\033[D", "\033[E", "\033[C", "+" ,
+/* 57-60 */ "\033[F", "\033[B", "\033[G", "\033[L",
+/* 61-64 */ "\177", "\033[J", "\033[~", "\033[}",
+/* 65-68 */ "" , "" , "" , "" ,
+/* 69-72 */ "" , "" , "" , "" ,
+/* 73-76 */ "" , "" , "" , "" ,
+/* 77-80 */ "" , "" , "" , "" ,
+/* 81-84 */ "" , "" , "" , "" ,
+/* 85-88 */ "" , "" , "" , "" ,
+/* 89-92 */ "" , "" , "" , "" ,
+/* 93-96 */ "" , "" , "" , "" ,
+ };
+
+static const int delays[] = {250, 500, 750, 1000};
+static const int repeats[] = { 34, 38, 42, 46, 50, 55, 59, 63,
+ 68, 76, 84, 92, 100, 110, 118, 126,
+ 136, 152, 168, 184, 200, 220, 236, 252,
+ 272, 304, 336, 368, 400, 440, 472, 504};
+static const int ndelays = (sizeof(delays) / sizeof(int));
+static const int nrepeats = (sizeof(repeats) / sizeof(int));
+static int hex = 0;
+static int token;
+
+int number;
+char letter;
+
+static void dump_accent_definition(char *name, accentmap_t *accentmap);
+static void dump_entry(int value);
+static void dump_key_definition(char *name, keymap_t *keymap);
+static int get_accent_definition_line(accentmap_t *);
+static int get_entry(void);
+static int get_key_definition_line(keymap_t *);
+static void load_keymap(char *opt, int dumponly);
+static void load_default_functionkeys(void);
+static char * nextarg(int ac, char **av, int *indp, int oc);
+static char * mkfullname(const char *s1, const char *s2, const char *s3);
+static void print_accent_definition_line(FILE *fp, int accent,
+ struct acc_t *key);
+static void print_entry(FILE *fp, int value);
+static void print_key_definition_line(FILE *fp, int scancode,
+ struct keyent_t *key);
+static void print_keymap(void);
+static void release_keyboard(void);
+static void mux_keyboard(u_int op, char *kbd);
+static void set_bell_values(char *opt);
+static void set_functionkey(char *keynumstr, char *string);
+static void set_keyboard(char *device);
+static void set_keyrates(char *opt);
+static void show_kbd_info(void);
+static void usage(void) __dead2;
+
+/* Detect presence of vt(4). */
+static int
+is_vt4(void)
+{
+ char vty_name[4] = "";
+ size_t len = sizeof(vty_name);
+
+ if (sysctlbyname("kern.vty", vty_name, &len, NULL, 0) != 0)
+ return (0);
+ return (strcmp(vty_name, "vt") == 0);
+}
+
+static char *
+nextarg(int ac, char **av, int *indp, int oc)
+{
+ if (*indp < ac)
+ return(av[(*indp)++]);
+ warnx("option requires two arguments -- %c", oc);
+ usage();
+}
+
+
+static char *
+mkfullname(const char *s1, const char *s2, const char *s3)
+{
+ static char *buf = NULL;
+ static int bufl = 0;
+ int f;
+
+ f = strlen(s1) + strlen(s2) + strlen(s3) + 1;
+ if (f > bufl) {
+ if (buf)
+ buf = (char *)realloc(buf, f);
+ else
+ buf = (char *)malloc(f);
+ }
+ if (!buf) {
+ bufl = 0;
+ return(NULL);
+ }
+
+ bufl = f;
+ strcpy(buf, s1);
+ strcat(buf, s2);
+ strcat(buf, s3);
+ return(buf);
+}
+
+
+static int
+get_entry(void)
+{
+ switch ((token = yylex())) {
+ case TNOP:
+ return NOP | SPECIAL;
+ case TLSH:
+ return LSH | SPECIAL;
+ case TRSH:
+ return RSH | SPECIAL;
+ case TCLK:
+ return CLK | SPECIAL;
+ case TNLK:
+ return NLK | SPECIAL;
+ case TSLK:
+ return SLK | SPECIAL;
+ case TBTAB:
+ return BTAB | SPECIAL;
+ case TLALT:
+ return LALT | SPECIAL;
+ case TLCTR:
+ return LCTR | SPECIAL;
+ case TNEXT:
+ return NEXT | SPECIAL;
+ case TPREV:
+ return PREV | SPECIAL;
+ case TRCTR:
+ return RCTR | SPECIAL;
+ case TRALT:
+ return RALT | SPECIAL;
+ case TALK:
+ return ALK | SPECIAL;
+ case TASH:
+ return ASH | SPECIAL;
+ case TMETA:
+ return META | SPECIAL;
+ case TRBT:
+ return RBT | SPECIAL;
+ case TDBG:
+ return DBG | SPECIAL;
+ case TSUSP:
+ return SUSP | SPECIAL;
+ case TSPSC:
+ return SPSC | SPECIAL;
+ case TPANIC:
+ return PNC | SPECIAL;
+ case TLSHA:
+ return LSHA | SPECIAL;
+ case TRSHA:
+ return RSHA | SPECIAL;
+ case TLCTRA:
+ return LCTRA | SPECIAL;
+ case TRCTRA:
+ return RCTRA | SPECIAL;
+ case TLALTA:
+ return LALTA | SPECIAL;
+ case TRALTA:
+ return RALTA | SPECIAL;
+ case THALT:
+ return HALT | SPECIAL;
+ case TPDWN:
+ return PDWN | SPECIAL;
+ case TPASTE:
+ return PASTE | SPECIAL;
+ case TACC:
+ if (ACC(number) > L_ACC)
+ return -1;
+ return ACC(number) | SPECIAL;
+ case TFUNC:
+ if (F(number) > L_FN)
+ return -1;
+ return F(number) | SPECIAL;
+ case TSCRN:
+ if (S(number) > L_SCR)
+ return -1;
+ return S(number) | SPECIAL;
+ case TLET:
+ return (unsigned char)letter;
+ case TNUM:
+ if (number < 0x000000 || number > 0x10FFFF)
+ return -1;
+ return number;
+ default:
+ return -1;
+ }
+}
+
+static int
+get_definition_line(FILE *fd, keymap_t *keymap, accentmap_t *accentmap)
+{
+ int c;
+
+ yyin = fd;
+
+ if (token < 0)
+ token = yylex();
+ switch (token) {
+ case TNUM:
+ c = get_key_definition_line(keymap);
+ if (c < 0)
+ errx(1, "invalid key definition");
+ if (c > keymap->n_keys)
+ keymap->n_keys = c;
+ break;
+ case TACC:
+ c = get_accent_definition_line(accentmap);
+ if (c < 0)
+ errx(1, "invalid accent key definition");
+ if (c > accentmap->n_accs)
+ accentmap->n_accs = c;
+ break;
+ case 0:
+ /* EOF */
+ return -1;
+ default:
+ errx(1, "illegal definition line");
+ }
+ return c;
+}
+
+static int
+get_key_definition_line(keymap_t *map)
+{
+ int i, def, scancode;
+
+ /* check scancode number */
+ if (number < 0 || number >= NUM_KEYS)
+ return -1;
+ scancode = number;
+
+ /* get key definitions */
+ map->key[scancode].spcl = 0;
+ for (i=0; i<NUM_STATES; i++) {
+ if ((def = get_entry()) == -1)
+ return -1;
+ if (def & SPECIAL)
+ map->key[scancode].spcl |= (0x80 >> i);
+ map->key[scancode].map[i] = def & ~SPECIAL;
+ }
+ /* get lock state key def */
+ if ((token = yylex()) != TFLAG)
+ return -1;
+ map->key[scancode].flgs = number;
+ token = yylex();
+ return (scancode + 1);
+}
+
+static int
+get_accent_definition_line(accentmap_t *map)
+{
+ int accent;
+ int c1, c2;
+ int i;
+
+ if (ACC(number) < F_ACC || ACC(number) > L_ACC)
+ /* number out of range */
+ return -1;
+ accent = number;
+ if (map->acc[accent].accchar != 0) {
+ /* this entry has already been defined before! */
+ errx(1, "duplicated accent key definition");
+ }
+
+ switch ((token = yylex())) {
+ case TLET:
+ map->acc[accent].accchar = letter;
+ break;
+ case TNUM:
+ map->acc[accent].accchar = number;
+ break;
+ default:
+ return -1;
+ }
+
+ for (i = 0; (token = yylex()) == '(';) {
+ switch ((token = yylex())) {
+ case TLET:
+ c1 = letter;
+ break;
+ case TNUM:
+ c1 = number;
+ break;
+ default:
+ return -1;
+ }
+ switch ((token = yylex())) {
+ case TLET:
+ c2 = letter;
+ break;
+ case TNUM:
+ c2 = number;
+ break;
+ default:
+ return -1;
+ }
+ if ((token = yylex()) != ')')
+ return -1;
+ if (i >= NUM_ACCENTCHARS) {
+ warnx("too many accented characters, ignored");
+ continue;
+ }
+ map->acc[accent].map[i][0] = c1;
+ map->acc[accent].map[i][1] = c2;
+ ++i;
+ }
+ return (accent + 1);
+}
+
+static void
+print_entry(FILE *fp, int value)
+{
+ int val = value & ~SPECIAL;
+
+ switch (value) {
+ case NOP | SPECIAL:
+ fprintf(fp, " nop ");
+ break;
+ case LSH | SPECIAL:
+ fprintf(fp, " lshift");
+ break;
+ case RSH | SPECIAL:
+ fprintf(fp, " rshift");
+ break;
+ case CLK | SPECIAL:
+ fprintf(fp, " clock ");
+ break;
+ case NLK | SPECIAL:
+ fprintf(fp, " nlock ");
+ break;
+ case SLK | SPECIAL:
+ fprintf(fp, " slock ");
+ break;
+ case BTAB | SPECIAL:
+ fprintf(fp, " btab ");
+ break;
+ case LALT | SPECIAL:
+ fprintf(fp, " lalt ");
+ break;
+ case LCTR | SPECIAL:
+ fprintf(fp, " lctrl ");
+ break;
+ case NEXT | SPECIAL:
+ fprintf(fp, " nscr ");
+ break;
+ case PREV | SPECIAL:
+ fprintf(fp, " pscr ");
+ break;
+ case RCTR | SPECIAL:
+ fprintf(fp, " rctrl ");
+ break;
+ case RALT | SPECIAL:
+ fprintf(fp, " ralt ");
+ break;
+ case ALK | SPECIAL:
+ fprintf(fp, " alock ");
+ break;
+ case ASH | SPECIAL:
+ fprintf(fp, " ashift");
+ break;
+ case META | SPECIAL:
+ fprintf(fp, " meta ");
+ break;
+ case RBT | SPECIAL:
+ fprintf(fp, " boot ");
+ break;
+ case DBG | SPECIAL:
+ fprintf(fp, " debug ");
+ break;
+ case SUSP | SPECIAL:
+ fprintf(fp, " susp ");
+ break;
+ case SPSC | SPECIAL:
+ fprintf(fp, " saver ");
+ break;
+ case PNC | SPECIAL:
+ fprintf(fp, " panic ");
+ break;
+ case LSHA | SPECIAL:
+ fprintf(fp, " lshifta");
+ break;
+ case RSHA | SPECIAL:
+ fprintf(fp, " rshifta");
+ break;
+ case LCTRA | SPECIAL:
+ fprintf(fp, " lctrla");
+ break;
+ case RCTRA | SPECIAL:
+ fprintf(fp, " rctrla");
+ break;
+ case LALTA | SPECIAL:
+ fprintf(fp, " lalta ");
+ break;
+ case RALTA | SPECIAL:
+ fprintf(fp, " ralta ");
+ break;
+ case HALT | SPECIAL:
+ fprintf(fp, " halt ");
+ break;
+ case PDWN | SPECIAL:
+ fprintf(fp, " pdwn ");
+ break;
+ case PASTE | SPECIAL:
+ fprintf(fp, " paste ");
+ break;
+ default:
+ if (value & SPECIAL) {
+ if (val >= F_FN && val <= L_FN)
+ fprintf(fp, " fkey%02d", val - F_FN + 1);
+ else if (val >= F_SCR && val <= L_SCR)
+ fprintf(fp, " scr%02d ", val - F_SCR + 1);
+ else if (val >= F_ACC && val <= L_ACC)
+ fprintf(fp, " %-6s", acc_names[val - F_ACC]);
+ else if (hex)
+ fprintf(fp, " 0x%02x ", val);
+ else
+ fprintf(fp, " %3d ", val);
+ }
+ else {
+ if (val < ' ')
+ fprintf(fp, " %s ", ctrl_names[val]);
+ else if (val == 127)
+ fprintf(fp, " del ");
+ else if (isascii(val) && isprint(val))
+ fprintf(fp, " '%c' ", val);
+ else if (hex)
+ fprintf(fp, " 0x%02x ", val);
+ else
+ fprintf(fp, " %3d ", val);
+ }
+ }
+}
+
+static void
+print_key_definition_line(FILE *fp, int scancode, struct keyent_t *key)
+{
+ int i;
+
+ /* print scancode number */
+ if (hex)
+ fprintf(fp, " 0x%02x ", scancode);
+ else
+ fprintf(fp, " %03d ", scancode);
+
+ /* print key definitions */
+ for (i=0; i<NUM_STATES; i++) {
+ if (key->spcl & (0x80 >> i))
+ print_entry(fp, key->map[i] | SPECIAL);
+ else
+ print_entry(fp, key->map[i]);
+ }
+
+ /* print lock state key def */
+ switch (key->flgs) {
+ case 0:
+ fprintf(fp, " O\n");
+ break;
+ case 1:
+ fprintf(fp, " C\n");
+ break;
+ case 2:
+ fprintf(fp, " N\n");
+ break;
+ case 3:
+ fprintf(fp, " B\n");
+ break;
+ }
+}
+
+static void
+print_accent_definition_line(FILE *fp, int accent, struct acc_t *key)
+{
+ int c;
+ int i;
+
+ if (key->accchar == 0)
+ return;
+
+ /* print accent number */
+ fprintf(fp, " %-6s", acc_names[accent]);
+ if (isascii(key->accchar) && isprint(key->accchar))
+ fprintf(fp, "'%c' ", key->accchar);
+ else if (hex)
+ fprintf(fp, "0x%02x ", key->accchar);
+ else
+ fprintf(fp, "%03d ", key->accchar);
+
+ for (i = 0; i < NUM_ACCENTCHARS; ++i) {
+ c = key->map[i][0];
+ if (c == 0)
+ break;
+ if ((i > 0) && ((i % 4) == 0))
+ fprintf(fp, "\n ");
+ if (isascii(c) && isprint(c))
+ fprintf(fp, "( '%c' ", c);
+ else if (hex)
+ fprintf(fp, "(0x%02x ", c);
+ else
+ fprintf(fp, "( %03d ", c);
+ c = key->map[i][1];
+ if (isascii(c) && isprint(c))
+ fprintf(fp, "'%c' ) ", c);
+ else if (hex)
+ fprintf(fp, "0x%02x) ", c);
+ else
+ fprintf(fp, "%03d ) ", c);
+ }
+ fprintf(fp, "\n");
+}
+
+static void
+dump_entry(int value)
+{
+ if (value & SPECIAL) {
+ value &= ~SPECIAL;
+ switch (value) {
+ case NOP:
+ printf(" NOP, ");
+ break;
+ case LSH:
+ printf(" LSH, ");
+ break;
+ case RSH:
+ printf(" RSH, ");
+ break;
+ case CLK:
+ printf(" CLK, ");
+ break;
+ case NLK:
+ printf(" NLK, ");
+ break;
+ case SLK:
+ printf(" SLK, ");
+ break;
+ case BTAB:
+ printf(" BTAB, ");
+ break;
+ case LALT:
+ printf(" LALT, ");
+ break;
+ case LCTR:
+ printf(" LCTR, ");
+ break;
+ case NEXT:
+ printf(" NEXT, ");
+ break;
+ case PREV:
+ printf(" PREV, ");
+ break;
+ case RCTR:
+ printf(" RCTR, ");
+ break;
+ case RALT:
+ printf(" RALT, ");
+ break;
+ case ALK:
+ printf(" ALK, ");
+ break;
+ case ASH:
+ printf(" ASH, ");
+ break;
+ case META:
+ printf(" META, ");
+ break;
+ case RBT:
+ printf(" RBT, ");
+ break;
+ case DBG:
+ printf(" DBG, ");
+ break;
+ case SUSP:
+ printf(" SUSP, ");
+ break;
+ case SPSC:
+ printf(" SPSC, ");
+ break;
+ case PNC:
+ printf(" PNC, ");
+ break;
+ case LSHA:
+ printf(" LSHA, ");
+ break;
+ case RSHA:
+ printf(" RSHA, ");
+ break;
+ case LCTRA:
+ printf("LCTRA, ");
+ break;
+ case RCTRA:
+ printf("RCTRA, ");
+ break;
+ case LALTA:
+ printf("LALTA, ");
+ break;
+ case RALTA:
+ printf("RALTA, ");
+ break;
+ case HALT:
+ printf(" HALT, ");
+ break;
+ case PDWN:
+ printf(" PDWN, ");
+ break;
+ case PASTE:
+ printf("PASTE, ");
+ break;
+ default:
+ if (value >= F_FN && value <= L_FN)
+ printf(" F(%2d),", value - F_FN + 1);
+ else if (value >= F_SCR && value <= L_SCR)
+ printf(" S(%2d),", value - F_SCR + 1);
+ else if (value >= F_ACC && value <= L_ACC)
+ printf(" %-4s, ", acc_names_u[value - F_ACC]);
+ else
+ printf(" 0x%02X, ", value);
+ break;
+ }
+ } else if (value == '\'') {
+ printf(" '\\'', ");
+ } else if (value == '\\') {
+ printf(" '\\\\', ");
+ } else if (isascii(value) && isprint(value)) {
+ printf(" '%c', ", value);
+ } else {
+ printf(" 0x%02X, ", value);
+ }
+}
+
+static void
+dump_key_definition(char *name, keymap_t *keymap)
+{
+ int i, j;
+
+ printf("static keymap_t keymap_%s = { 0x%02x, {\n",
+ name, (unsigned)keymap->n_keys);
+ printf(
+"/* alt\n"
+" * scan cntrl alt alt cntrl\n"
+" * code base shift cntrl shift alt shift cntrl shift spcl flgs\n"
+" * ---------------------------------------------------------------------------\n"
+" */\n");
+ for (i = 0; i < keymap->n_keys; i++) {
+ printf("/*%02x*/{{", i);
+ for (j = 0; j < NUM_STATES; j++) {
+ if (keymap->key[i].spcl & (0x80 >> j))
+ dump_entry(keymap->key[i].map[j] | SPECIAL);
+ else
+ dump_entry(keymap->key[i].map[j]);
+ }
+ printf("}, 0x%02X,0x%02X },\n",
+ (unsigned)keymap->key[i].spcl,
+ (unsigned)keymap->key[i].flgs);
+ }
+ printf("} };\n\n");
+}
+
+static void
+dump_accent_definition(char *name, accentmap_t *accentmap)
+{
+ int i, j;
+ int c;
+
+ printf("static accentmap_t accentmap_%s = { %d",
+ name, accentmap->n_accs);
+ if (accentmap->n_accs <= 0) {
+ printf(" };\n\n");
+ return;
+ }
+ printf(", {\n");
+ for (i = 0; i < NUM_DEADKEYS; i++) {
+ printf(" /* %s=%d */\n {", acc_names[i], i);
+ c = accentmap->acc[i].accchar;
+ if (c == '\'')
+ printf(" '\\'', {");
+ else if (c == '\\')
+ printf(" '\\\\', {");
+ else if (isascii(c) && isprint(c))
+ printf(" '%c', {", c);
+ else if (c == 0) {
+ printf(" 0x00 }, \n");
+ continue;
+ } else
+ printf(" 0x%02x, {", c);
+ for (j = 0; j < NUM_ACCENTCHARS; j++) {
+ c = accentmap->acc[i].map[j][0];
+ if (c == 0)
+ break;
+ if ((j > 0) && ((j % 4) == 0))
+ printf("\n\t ");
+ if (isascii(c) && isprint(c))
+ printf(" { '%c',", c);
+ else
+ printf(" { 0x%02x,", c);
+ printf("0x%02x },", accentmap->acc[i].map[j][1]);
+ }
+ printf(" }, },\n");
+ }
+ printf("} };\n\n");
+}
+
+static void
+load_keymap(char *opt, int dumponly)
+{
+ keymap_t keymap;
+ accentmap_t accentmap;
+ FILE *fd;
+ int i, j;
+ char *name, *cp;
+ char blank[] = "", keymap_path[] = KEYMAP_PATH;
+ char vt_keymap_path[] = VT_KEYMAP_PATH, dotkbd[] = ".kbd";
+ char *prefix[] = {blank, blank, keymap_path, NULL};
+ char *postfix[] = {blank, dotkbd, NULL};
+
+ if (is_vt4())
+ prefix[2] = vt_keymap_path;
+ cp = getenv("KEYMAP_PATH");
+ if (cp != NULL)
+ asprintf(&(prefix[0]), "%s/", cp);
+
+ fd = NULL;
+ for (i=0; prefix[i] && fd == NULL; i++) {
+ for (j=0; postfix[j] && fd == NULL; j++) {
+ name = mkfullname(prefix[i], opt, postfix[j]);
+ fd = fopen(name, "r");
+ }
+ }
+ if (fd == NULL) {
+ warn("keymap file \"%s\" not found", opt);
+ return;
+ }
+ memset(&keymap, 0, sizeof(keymap));
+ memset(&accentmap, 0, sizeof(accentmap));
+ token = -1;
+ while (1) {
+ if (get_definition_line(fd, &keymap, &accentmap) < 0)
+ break;
+ }
+ if (dumponly) {
+ /* fix up the filename to make it a valid C identifier */
+ for (cp = opt; *cp; cp++)
+ if (!isalpha(*cp) && !isdigit(*cp)) *cp = '_';
+ printf("/*\n"
+ " * Automatically generated from %s.\n"
+ " * DO NOT EDIT!\n"
+ " */\n", name);
+ dump_key_definition(opt, &keymap);
+ dump_accent_definition(opt, &accentmap);
+ return;
+ }
+ if ((keymap.n_keys > 0) && (ioctl(0, PIO_KEYMAP, &keymap) < 0)) {
+ warn("setting keymap");
+ fclose(fd);
+ return;
+ }
+ if ((accentmap.n_accs > 0)
+ && (ioctl(0, PIO_DEADKEYMAP, &accentmap) < 0)) {
+ warn("setting accentmap");
+ fclose(fd);
+ return;
+ }
+}
+
+static void
+print_keymap(void)
+{
+ keymap_t keymap;
+ accentmap_t accentmap;
+ int i;
+
+ if (ioctl(0, GIO_KEYMAP, &keymap) < 0)
+ err(1, "getting keymap");
+ if (ioctl(0, GIO_DEADKEYMAP, &accentmap) < 0)
+ memset(&accentmap, 0, sizeof(accentmap));
+ printf(
+"# alt\n"
+"# scan cntrl alt alt cntrl lock\n"
+"# code base shift cntrl shift alt shift cntrl shift state\n"
+"# ------------------------------------------------------------------\n"
+ );
+ for (i=0; i<keymap.n_keys; i++)
+ print_key_definition_line(stdout, i, &keymap.key[i]);
+
+ printf("\n");
+ for (i = 0; i < NUM_DEADKEYS; i++)
+ print_accent_definition_line(stdout, i, &accentmap.acc[i]);
+
+}
+
+static void
+load_default_functionkeys(void)
+{
+ fkeyarg_t fkey;
+ int i;
+
+ for (i=0; i<NUM_FKEYS; i++) {
+ fkey.keynum = i;
+ strcpy(fkey.keydef, fkey_table[i]);
+ fkey.flen = strlen(fkey_table[i]);
+ if (ioctl(0, SETFKEY, &fkey) < 0)
+ warn("setting function key");
+ }
+}
+
+static void
+set_functionkey(char *keynumstr, char *string)
+{
+ fkeyarg_t fkey;
+
+ if (!strcmp(keynumstr, "load") && !strcmp(string, "default")) {
+ load_default_functionkeys();
+ return;
+ }
+ fkey.keynum = atoi(keynumstr);
+ if (fkey.keynum < 1 || fkey.keynum > NUM_FKEYS) {
+ warnx("function key number must be between 1 and %d",
+ NUM_FKEYS);
+ return;
+ }
+ if ((fkey.flen = strlen(string)) > MAXFK) {
+ warnx("function key string too long (%d > %d)",
+ fkey.flen, MAXFK);
+ return;
+ }
+ strncpy(fkey.keydef, string, MAXFK);
+ fkey.keynum -= 1;
+ if (ioctl(0, SETFKEY, &fkey) < 0)
+ warn("setting function key");
+}
+
+static void
+set_bell_values(char *opt)
+{
+ int bell, duration, pitch;
+
+ bell = 0;
+ if (!strncmp(opt, "quiet.", 6)) {
+ bell = CONS_QUIET_BELL;
+ opt += 6;
+ }
+ if (!strcmp(opt, "visual"))
+ bell |= CONS_VISUAL_BELL;
+ else if (!strcmp(opt, "normal"))
+ duration = 5, pitch = 800;
+ else if (!strcmp(opt, "off"))
+ duration = 0, pitch = 0;
+ else {
+ char *v1;
+
+ bell = 0;
+ duration = strtol(opt, &v1, 0);
+ if ((duration < 0) || (*v1 != '.'))
+ goto badopt;
+ opt = ++v1;
+ pitch = strtol(opt, &v1, 0);
+ if ((pitch < 0) || (*opt == '\0') || (*v1 != '\0')) {
+badopt:
+ warnx("argument to -b must be duration.pitch or [quiet.]visual|normal|off");
+ return;
+ }
+ if (pitch != 0)
+ pitch = 1193182 / pitch; /* in Hz */
+ duration /= 10; /* in 10 m sec */
+ }
+
+ ioctl(0, CONS_BELLTYPE, &bell);
+ if (!(bell & CONS_VISUAL_BELL))
+ fprintf(stderr, "[=%d;%dB", pitch, duration);
+}
+
+static void
+set_keyrates(char *opt)
+{
+ int arg[2];
+ int repeat;
+ int delay;
+ int r, d;
+
+ if (!strcmp(opt, "slow")) {
+ delay = 1000, repeat = 500;
+ d = 3, r = 31;
+ } else if (!strcmp(opt, "normal")) {
+ delay = 500, repeat = 125;
+ d = 1, r = 15;
+ } else if (!strcmp(opt, "fast")) {
+ delay = repeat = 0;
+ d = r = 0;
+ } else {
+ int n;
+ char *v1;
+
+ delay = strtol(opt, &v1, 0);
+ if ((delay < 0) || (*v1 != '.'))
+ goto badopt;
+ opt = ++v1;
+ repeat = strtol(opt, &v1, 0);
+ if ((repeat < 0) || (*opt == '\0') || (*v1 != '\0')) {
+badopt:
+ warnx("argument to -r must be delay.repeat or slow|normal|fast");
+ return;
+ }
+ for (n = 0; n < ndelays - 1; n++)
+ if (delay <= delays[n])
+ break;
+ d = n;
+ for (n = 0; n < nrepeats - 1; n++)
+ if (repeat <= repeats[n])
+ break;
+ r = n;
+ }
+
+ arg[0] = delay;
+ arg[1] = repeat;
+ if (ioctl(0, KDSETREPEAT, arg)) {
+ if (ioctl(0, KDSETRAD, (d << 5) | r))
+ warn("setting keyboard rate");
+ }
+}
+
+static const char *
+get_kbd_type_name(int type)
+{
+ static struct {
+ int type;
+ const char *name;
+ } name_table[] = {
+ { KB_84, "AT 84" },
+ { KB_101, "AT 101/102" },
+ { KB_OTHER, "generic" },
+ };
+ unsigned int i;
+
+ for (i = 0; i < sizeof(name_table)/sizeof(name_table[0]); ++i) {
+ if (type == name_table[i].type)
+ return name_table[i].name;
+ }
+ return "unknown";
+}
+
+static void
+show_kbd_info(void)
+{
+ keyboard_info_t info;
+
+ if (ioctl(0, KDGKBINFO, &info) == -1) {
+ warn("unable to obtain keyboard information");
+ return;
+ }
+ printf("kbd%d:\n", info.kb_index);
+ printf(" %.*s%d, type:%s (%d)\n",
+ (int)sizeof(info.kb_name), info.kb_name, info.kb_unit,
+ get_kbd_type_name(info.kb_type), info.kb_type);
+}
+
+static void
+set_keyboard(char *device)
+{
+ keyboard_info_t info;
+ int fd;
+
+ fd = open(device, O_RDONLY);
+ if (fd < 0) {
+ warn("cannot open %s", device);
+ return;
+ }
+ if (ioctl(fd, KDGKBINFO, &info) == -1) {
+ warn("unable to obtain keyboard information");
+ close(fd);
+ return;
+ }
+ /*
+ * The keyboard device driver won't release the keyboard by
+ * the following ioctl, but it automatically will, when the device
+ * is closed. So, we don't check error here.
+ */
+ ioctl(fd, CONS_RELKBD, 0);
+ close(fd);
+#if 1
+ printf("kbd%d\n", info.kb_index);
+ printf(" %.*s%d, type:%s (%d)\n",
+ (int)sizeof(info.kb_name), info.kb_name, info.kb_unit,
+ get_kbd_type_name(info.kb_type), info.kb_type);
+#endif
+
+ if (ioctl(0, CONS_SETKBD, info.kb_index) == -1)
+ warn("unable to set keyboard");
+}
+
+static void
+release_keyboard(void)
+{
+ keyboard_info_t info;
+
+ /*
+ * If stdin is not associated with a keyboard, the following ioctl
+ * will fail.
+ */
+ if (ioctl(0, KDGKBINFO, &info) == -1) {
+ warn("unable to obtain keyboard information");
+ return;
+ }
+#if 1
+ printf("kbd%d\n", info.kb_index);
+ printf(" %.*s%d, type:%s (%d)\n",
+ (int)sizeof(info.kb_name), info.kb_name, info.kb_unit,
+ get_kbd_type_name(info.kb_type), info.kb_type);
+#endif
+ if (ioctl(0, CONS_RELKBD, 0) == -1)
+ warn("unable to release the keyboard");
+}
+
+static void
+mux_keyboard(u_int op, char *kbd)
+{
+ keyboard_info_t info;
+ char *unit, *ep;
+
+ /*
+ * If stdin is not associated with a keyboard, the following ioctl
+ * will fail.
+ */
+ if (ioctl(0, KDGKBINFO, &info) == -1) {
+ warn("unable to obtain keyboard information");
+ return;
+ }
+#if 1
+ printf("kbd%d\n", info.kb_index);
+ printf(" %.*s%d, type:%s (%d)\n",
+ (int)sizeof(info.kb_name), info.kb_name, info.kb_unit,
+ get_kbd_type_name(info.kb_type), info.kb_type);
+#endif
+ /*
+ * split kbd into name and unit. find the right most part of the
+ * kbd string that consist of only digits.
+ */
+
+ memset(&info, 0, sizeof(info));
+
+ info.kb_unit = -1;
+ ep = kbd - 1;
+
+ do {
+ unit = strpbrk(ep + 1, "0123456789");
+ if (unit != NULL) {
+ info.kb_unit = strtol(unit, &ep, 10);
+ if (*ep != '\0')
+ info.kb_unit = -1;
+ }
+ } while (unit != NULL && info.kb_unit == -1);
+
+ if (info.kb_unit == -1) {
+ warnx("unable to find keyboard driver unit in '%s'", kbd);
+ return;
+ }
+
+ if (unit == kbd) {
+ warnx("unable to find keyboard driver name in '%s'", kbd);
+ return;
+ }
+ if (unit - kbd >= (int) sizeof(info.kb_name)) {
+ warnx("keyboard name '%s' is too long", kbd);
+ return;
+ }
+
+ strncpy(info.kb_name, kbd, unit - kbd);
+
+ /*
+ * If stdin is not associated with a kbdmux(4) keyboard, the following
+ * ioctl will fail.
+ */
+
+ if (ioctl(0, op, &info) == -1)
+ warn("unable to (un)mux the keyboard");
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "%s\n%s\n%s\n",
+"usage: kbdcontrol [-dFKix] [-A name] [-a name] [-b duration.pitch | [quiet.]belltype]",
+" [-r delay.repeat | speed] [-l mapfile] [-f # string]",
+" [-k device] [-L mapfile]");
+ exit(1);
+}
+
+
+int
+main(int argc, char **argv)
+{
+ int opt;
+
+ while((opt = getopt(argc, argv, "A:a:b:df:iKk:Fl:L:r:x")) != -1)
+ switch(opt) {
+ case 'A':
+ case 'a':
+ mux_keyboard((opt == 'A')? KBRELKBD : KBADDKBD, optarg);
+ break;
+ case 'b':
+ set_bell_values(optarg);
+ break;
+ case 'd':
+ print_keymap();
+ break;
+ case 'l':
+ load_keymap(optarg, 0);
+ break;
+ case 'L':
+ load_keymap(optarg, 1);
+ break;
+ case 'f':
+ set_functionkey(optarg,
+ nextarg(argc, argv, &optind, 'f'));
+ break;
+ case 'F':
+ load_default_functionkeys();
+ break;
+ case 'i':
+ show_kbd_info();
+ break;
+ case 'K':
+ release_keyboard();
+ break;
+ case 'k':
+ set_keyboard(optarg);
+ break;
+ case 'r':
+ set_keyrates(optarg);
+ break;
+ case 'x':
+ hex = 1;
+ break;
+ default:
+ usage();
+ }
+ if ((optind != argc) || (argc == 1))
+ usage();
+ exit(0);
+}
diff --git a/usr.sbin/kbdcontrol/kbdmap.5 b/usr.sbin/kbdcontrol/kbdmap.5
new file mode 100644
index 0000000..fda2ada
--- /dev/null
+++ b/usr.sbin/kbdcontrol/kbdmap.5
@@ -0,0 +1,335 @@
+.\" Copyright (c) 2000
+.\" David Malone
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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$
+.\"
+.Dd January 2, 2016
+.Dt KBDMAP 5
+.Os
+.Sh NAME
+.Nm kbdmap
+.Nd keyboard map file format for kbdcontrol
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+A
+.Nm
+file describes how the keys on a keyboard should behave.
+These files can be loaded
+using
+.Xr kbdcontrol 1 ,
+or
+.Xr kbdmap 1
+can be used to select one of the default
+.Nm
+files interactively.
+A
+.Nm
+file can be specified in
+.Xr rc.conf 5 ,
+to be loaded
+at boot time.
+The current keymap may also be printed using
+.Xr kbdcontrol 1 .
+.Pp
+Each line in the file
+can describe a key or an accent.
+A
+.Ql #
+character begins a comment,
+which extends to the end of the line.
+.Pp
+The description of a key
+begins with the scancode for that key.
+Then the effect of the key
+under combinations of
+shift,
+control
+and alt
+are listed in the following order:
+no modifier,
+shift,
+control,
+control and shift,
+alt,
+alt and shift,
+alt and control,
+alt and control and shift.
+The action of the key
+under each modifier can be:
+.Bl -tag -width Ar
+.It ' Ns Ar symbol Ns No '
+The symbol the key should produce,
+in single quotes.
+.It Ar decnum
+The
+.Tn Unicode
+value to produce
+as a decimal number
+(see
+.Xr ascii 7 ) .
+For example, 32 for space.
+.It 0x Ns Ar hexnum
+The
+.Tn Unicode
+value to produce
+as a hexadecimal number.
+For example, 0x20 for space.
+.It Ar ctrlname
+One of the standard names
+for the
+.Tn ASCII
+control characters:
+nul,
+soh,
+stx,
+etx,
+eot,
+enq,
+ack,
+bel,
+bs,
+ht,
+lf,
+vt,
+ff,
+cr,
+so,
+si,
+dle,
+dc1,
+dc2,
+dc3,
+dc4,
+nak,
+syn,
+etb,
+can,
+em,
+sub,
+esc,
+fs,
+gs,
+rs,
+us,
+sp,
+del.
+.It Ar control-alias
+One of the historical aliases for certain
+.Tn ASCII
+control characters:
+nl,
+np,
+ns.
+.It Ar accentname
+By giving one of the accent names,
+the next key pressed will produce
+an accented character
+in accordance with that accent.
+See the description of accents below.
+The accent names are:
+dgra,
+dacu,
+dcir,
+dtil,
+dmac,
+dbre,
+ddot,
+duml,
+ddia,
+dsla,
+drin,
+dced,
+dapo,
+ddac,
+dogo,
+dcar.
+.It fkey Ns Ar N
+Act as the
+.Ar N Ns No th
+function key,
+where
+.Ar N
+is a decimal number in the range from 1 to 96.
+Refer to the
+.Xr atkbd 4
+manual page for a list of predefined function keys.
+You can use the
+.Fl f
+option of the
+.Xr kbdcontrol 1
+utility to assign arbitrary strings to function keys.
+.It lshift
+Act as left shift key.
+.It rshift
+Act as right shift key.
+.It clock
+Act as caps lock key.
+.It nlock
+Act as num lock key.
+.It slock
+Act as scroll lock key.
+.It lalt|alt
+Act as left alt key.
+.It btab
+Act as backwards tab.
+.It lctrl|ctrl
+Act as left control key.
+.It rctrl
+Act as right control key.
+.It ralt
+Act as right alt (altgr) key.
+.It alock
+Act as alt lock key.
+.It ashift
+Act as alt shift key.
+.It meta
+Act as meta key.
+.It lshifta|shifta
+Act as left shift key / alt lock.
+.It rshifta
+Act as right shift key / alt lock.
+.It lctrla|ctrla
+Act as left ctrl key / alt lock.
+.It rctrla
+Act as right ctrl key / alt lock.
+.It lalta|alta
+Act as left alt key / alt lock.
+.It ralta
+Act as right alt key / alt lock.
+.It nscr
+Act as switch to next screen.
+.It pscr
+Act as switch to previous screen.
+.It scr Ns Ar N
+Switch to screen
+.Ar N ,
+where
+.Ar N
+is a decimal number.
+.It boot
+Reboot the machine.
+.It halt
+Halt the machine.
+.It pdwn
+Halt the machine
+and attempt to power it down.
+.It debug
+Call the debugger.
+.It susp
+Use APM to suspend power.
+.It saver
+Activate screen saver
+by toggling between splash/text screen.
+.It panic
+Panic the system.
+The
+.Xr sysctl 8
+variable
+.Va machdep.enable_panic_key
+must be set to 1 to enable this feature.
+.It paste
+Act as mouse buffer paste.
+.El
+.Pp
+Finally,
+to complete the description of a key,
+a flag which describes
+the effect of caps lock and num lock
+on that key is given.
+The flag can be
+.Ql C
+to indicate that caps lock affects the key,
+.Ql N
+to indicate that num lock affects the key,
+.Ql B
+to indicate that both
+caps lock and num lock affects the key,
+or
+.Ql O
+to indicate that neither affects the key.
+.Pp
+An accent key works
+by modifying the behavior
+of the next key pressed.
+The description of an accent begins
+with one of the accent names
+given above.
+This is followed
+by the symbol for the accent,
+given in single quotes or
+as a decimal or hexadecimal
+.Tn Unicode
+value.
+This symbol will be produced
+if the accent key is pressed and
+then the space key is pressed.
+.Pp
+The description of the accent key
+continues with a list showing
+how it modifies various symbols,
+by giving pairs made up of the normal symbol and
+the modified symbol
+enclosed in parentheses.
+Both symbols in a pair can be given
+in either single quotes or
+as decimal or
+hexadecimal
+.Tn Unicode
+values.
+.Pp
+For example,
+consider the following extract from a
+.Nm :
+.Bd -literal -offset indent
+ 041 dgra 172 nop nop '|' '|' nop nop O
+ dgra '`' ( 'a' 224 ) ( 'A' 192 ) ( 'e' 232 ) ( 'E' 200 )
+ ( 'i' 236 ) ( 'I' 204 ) ( 'o' 242 ) ( 'O' 210 )
+ ( 'u' 249 ) ( 'U' 217 )
+.Ed
+This extract
+configures the backtick key on a UK keyboard
+to act as a grave accent key.
+Pressing backtick followed by space
+produces a backtick, and
+pressing a backtick followed by a vowel
+produces the ISO-8859-1 symbol
+for that vowel with a grave accent.
+.Sh FILES
+.Bl -tag -width /usr/share/syscons/keymaps/* -compact
+.It Pa /usr/share/syscons/keymaps/*
+standard keyboard map files for syscons
+.It Pa /usr/share/vt/keymaps/*
+standard keyboard map files for vt
+.El
+.Sh SEE ALSO
+.Xr kbdcontrol 1 ,
+.Xr kbdmap 1 ,
+.Xr keyboard 4 ,
+.Xr syscons 4 ,
+.Xr vt 4 ,
+.Xr ascii 7
+.Sh HISTORY
+This manual page first appeared in
+.Fx 4.2 .
diff --git a/usr.sbin/kbdcontrol/lex.h b/usr.sbin/kbdcontrol/lex.h
new file mode 100644
index 0000000..3806dc7
--- /dev/null
+++ b/usr.sbin/kbdcontrol/lex.h
@@ -0,0 +1,72 @@
+/*-
+ * Copyright (c) 1994-1995 Søren Schmidt
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer,
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must 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$
+ */
+
+#define TNOP 256
+#define TLSH 257
+#define TRSH 258
+#define TCLK 259
+#define TNLK 260
+#define TSLK 261
+#define TLALT 262
+#define TLCTR 263
+#define TNEXT 264
+#define TRCTR 265
+#define TRALT 266
+#define TALK 267
+#define TASH 268
+#define TMETA 269
+#define TRBT 270
+#define TDBG 271
+#define TFUNC 272
+#define TSCRN 273
+#define TLET 274
+#define TNUM 275
+#define TFLAG 276
+#define TBTAB 277
+#define TSUSP 278
+#define TACC 279
+#define TSPSC 280
+#define TPREV 281
+#define TPANIC 282
+#define TLSHA 283
+#define TRSHA 284
+#define TLCTRA 285
+#define TRCTRA 286
+#define TLALTA 287
+#define TRALTA 288
+#define THALT 289
+#define TPDWN 290
+#define TPASTE 291
+
+extern int number;
+extern char letter;
+extern FILE *yyin;
+
+extern int yylex(void);
diff --git a/usr.sbin/kbdcontrol/lex.l b/usr.sbin/kbdcontrol/lex.l
new file mode 100644
index 0000000..d4c6508
--- /dev/null
+++ b/usr.sbin/kbdcontrol/lex.l
@@ -0,0 +1,151 @@
+/*-
+ * Copyright (c) 1994-1995 Søren Schmidt
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer,
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must 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$
+ */
+
+%{
+
+#include "lex.h"
+
+%}
+
+%option nounput
+%option noinput
+
+D [0-9]
+X [0-9a-fA-F]
+A .
+L [OCNB]
+
+%%
+
+nop { return TNOP; }
+lshift { return TLSH; }
+rshift { return TRSH; }
+clock { return TCLK; }
+nlock { return TNLK; }
+slock { return TSLK; }
+lalt|alt { return TLALT; }
+btab { return TBTAB; }
+lctrl|ctrl { return TLCTR; }
+nscr { return TNEXT; }
+pscr { return TPREV; }
+rctrl { return TRCTR; }
+ralt { return TRALT; }
+alock { return TALK; }
+ashift { return TASH; }
+meta { return TMETA; }
+boot { return TRBT; }
+debug { return TDBG; }
+susp { return TSUSP; }
+saver { return TSPSC; }
+panic { return TPANIC; }
+lshifta|shifta { return TLSHA; }
+rshifta { return TRSHA; }
+lctrla|ctrla { return TLCTRA; }
+rctrla { return TRCTRA; }
+lalta|alta { return TLALTA; }
+ralta { return TRALTA; }
+halt { return THALT; }
+pdwn { return TPDWN; }
+paste { return TPASTE; }
+
+NUL|nul { number = 0; return TNUM; }
+SOH|soh { number = 1; return TNUM; }
+STX|stx { number = 2; return TNUM; }
+ETX|etx { number = 3; return TNUM; }
+EOT|eot { number = 4; return TNUM; }
+ENQ|enq { number = 5; return TNUM; }
+ACK|ack { number = 6; return TNUM; }
+BEL|bel { number = 7; return TNUM; }
+BS|bs { number = 8; return TNUM; }
+HT|ht { number = 9; return TNUM; }
+LF|lf|NL|nl { number = 10; return TNUM; }
+VT|vt { number = 11; return TNUM; }
+FF|ff|NP|np { number = 12; return TNUM; }
+CR|cr { number = 13; return TNUM; }
+SO|so { number = 14; return TNUM; }
+SI|si { number = 15; return TNUM; }
+DLE|dle { number = 16; return TNUM; }
+DC1|dc1 { number = 17; return TNUM; }
+DC2|dc2 { number = 18; return TNUM; }
+DC3|dc3 { number = 19; return TNUM; }
+DC4|dc4 { number = 20; return TNUM; }
+NAK|nak { number = 21; return TNUM; }
+SYN|syn { number = 22; return TNUM; }
+ETB|etb { number = 23; return TNUM; }
+CAN|can { number = 24; return TNUM; }
+EM|em { number = 25; return TNUM; }
+SUB|sub { number = 26; return TNUM; }
+ESC|esc { number = 27; return TNUM; }
+FS|fs { number = 28; return TNUM; }
+GS|gs { number = 29; return TNUM; }
+RS|rs { number = 30; return TNUM; }
+NS|ns { number = 31; return TNUM; }
+US|us { number = 31; return TNUM; }
+SP|sp { number = 32; return TNUM; }
+DEL|del { number = 127; return TNUM; }
+
+dgra|DGRA { number = 0; return TACC; }
+dacu|DACU { number = 1; return TACC; }
+dcir|DCIR { number = 2; return TACC; }
+dtil|DTIL { number = 3; return TACC; }
+dmac|DMAC { number = 4; return TACC; }
+dbre|DBRE { number = 5; return TACC; }
+ddot|DDOT { number = 6; return TACC; }
+duml|DUML { number = 7; return TACC; }
+ddia|DDIA { number = 7; return TACC; }
+dsla|DSLA { number = 8; return TACC; }
+drin|DRIN { number = 9; return TACC; }
+dced|DCED { number = 10; return TACC; }
+dapo|DAPO { number = 11; return TACC; }
+ddac|DDAC { number = 12; return TACC; }
+dogo|DOGO { number = 13; return TACC; }
+dcar|DCAR { number = 14; return TACC; }
+
+fkey{D}({D}*) {
+ sscanf(yytext+4, "%d", &number);
+ return TFUNC;
+ }
+scr{D}({D}*) {
+ sscanf(yytext+3, "%d", &number);
+ return TSCRN;
+ }
+'{A}' { letter = *(yytext+1); return TLET; }
+#({A}*) { /* ignore */ }
+0x{X}({X}*) { sscanf(yytext, "%x", &number); return TNUM; }
+{D}({D}*) { sscanf(yytext, "%d", &number); return TNUM; }
+{L} {
+ if (*yytext == 'O') number = 0;
+ if (*yytext == 'C') number = 1;
+ if (*yytext == 'N') number = 2;
+ if (*yytext == 'B') number = 3;
+ return TFLAG;
+ }
+[ \t\n] { /* ignore */ }
+. { return *yytext; }
diff --git a/usr.sbin/kbdcontrol/path.h b/usr.sbin/kbdcontrol/path.h
new file mode 100644
index 0000000..e1fa341
--- /dev/null
+++ b/usr.sbin/kbdcontrol/path.h
@@ -0,0 +1,8 @@
+/* $FreeBSD$ */
+
+#define KEYMAP_PATH "/usr/share/syscons/keymaps/"
+#define FONT_PATH "/usr/share/syscons/fonts/"
+#define SCRNMAP_PATH "/usr/share/syscons/scrnmaps/"
+
+#define VT_KEYMAP_PATH "/usr/share/vt/keymaps/"
+#define VT_FONT_PATH "/usr/share/vt/fonts/"
diff --git a/usr.sbin/kbdmap/Languages.phrases b/usr.sbin/kbdmap/Languages.phrases
new file mode 100644
index 0000000..8bf7922
--- /dev/null
+++ b/usr.sbin/kbdmap/Languages.phrases
@@ -0,0 +1,37 @@
+#
+# Please use only iso-latin character set, which available
+# at console *and* X11 (IBM cp850 does not)
+#
+# missing: Norwegian, Russian, Danish, Swedish
+#
+
+1. Chose your keyboard font
+
+2. Codepage
+
+3. English
+
+4. thin
+
+5. Multilingual Latin I
+
+6. Norwegian
+
+7. Russian
+
+8. West European
+
+9. Chose your keyboard language
+
+10. Danish
+
+11. French
+
+12. German
+
+13. Swedish
+
+14. United Kingdom
+
+15. United States of America
+
diff --git a/usr.sbin/kbdmap/Makefile b/usr.sbin/kbdmap/Makefile
new file mode 100644
index 0000000..ec49341
--- /dev/null
+++ b/usr.sbin/kbdmap/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= kbdmap
+LINKS= ${BINDIR}/kbdmap ${BINDIR}/vidfont
+MLINKS= kbdmap.1 vidfont.1
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/kbdmap/Makefile.depend b/usr.sbin/kbdmap/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/kbdmap/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/kbdmap/TODO b/usr.sbin/kbdmap/TODO
new file mode 100644
index 0000000..cef30f7
--- /dev/null
+++ b/usr.sbin/kbdmap/TODO
@@ -0,0 +1,6 @@
+$FreeBSD$
+
+o remember some hackers to translate Languages.phrases into
+ Norwegian, Russian, Danish, Swedish
+
+95/04/03 Wolfram
diff --git a/usr.sbin/kbdmap/kbdmap.1 b/usr.sbin/kbdmap/kbdmap.1
new file mode 100644
index 0000000..5d4cf0e
--- /dev/null
+++ b/usr.sbin/kbdmap/kbdmap.1
@@ -0,0 +1,155 @@
+.\" Copyright (c) March 1995 Wolfram Schneider <wosch@FreeBSD.org>. Berlin.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 3, 2002
+.Dt KBDMAP 1
+.Os
+.Sh NAME
+.Nm kbdmap ,
+.Nm vidfont
+.Nd front end for syscons and vt
+.Sh SYNOPSIS
+.Nm
+.Op Fl K
+.Op Fl V
+.Op Fl d | default
+.Op Fl h | help
+.Op Fl l | lang Ar language
+.Op Fl p | print
+.Op Fl r | restore
+.Op Fl s | show
+.Op Fl v | verbose
+.Sh DESCRIPTION
+The
+.Nm
+utility allows easy setting of available keymaps.
+The
+.Nm vidfont
+command allows the setting of fonts.
+Both examine a database for the keymaps and fonts.
+Descriptions are in English by default, but may also be
+in other languages.
+These programs are interactive and expect to run
+in a terminal to get required input (e.g., a keymap selection)
+from the user.
+.Pp
+It is strongly recommended to not choose
+.Tn MSDOS
+codepage keymaps
+or fonts.
+Use the
+.Tn ISO
+standard version if available!
+.Tn X11
+does not
+support
+.Tn MSDOS
+codepage.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl K
+Run as command
+.Nm .
+.It Fl V
+Run as command
+.Nm vidfont .
+.It Fl d , default
+Use default language.
+Ignore
+.Ev LANG
+environment variable.
+.It Fl h , help
+Print options and exit.
+.It Fl l , lang Ar language
+Use
+.Ar language
+for description and menu.
+.It Fl p , print
+Print description of available keymaps or fonts
+to stdout and exit.
+.It Fl r , restore
+Load default font from
+.Pa /etc/rc.conf .
+.It Fl s , show
+Show currently supported languages and exit.
+.It Fl v , verbose
+More warnings.
+.El
+.Sh ENVIRONMENT
+.Bl -tag -width LANG -compact
+.It Ev LANG
+preferred language
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /usr/share/syscons/keymaps/INDEX.keymaps" -compact
+.It Pa /usr/share/syscons/keymaps/INDEX.keymaps
+.It Pa /usr/share/vt/keymaps/INDEX.keymaps
+database for keymaps
+.It Pa /usr/share/syscons/fonts/INDEX.fonts
+.It Pa /usr/share/vt/fonts/INDEX.fonts
+database for fonts
+.It Pa /etc/rc.conf
+default font
+.It Pa /usr/local/share/locale/locale.alias
+describe common
+.Ev LANG
+values
+.El
+.Sh SEE ALSO
+.Xr dialog 1 ,
+.Xr kbdcontrol 1 ,
+.Xr vidcontrol 1 ,
+.Xr syscons 4 ,
+.Xr vt 4 ,
+.Xr kbdmap 5 ,
+.Xr rc.conf 5
+.Sh HISTORY
+The
+.Nm
+and
+.Nm vidfont
+commands appeared in
+.Fx 2.1 .
+.Sh AUTHORS
+.An -nosplit
+.An Wolfram Schneider Aq Mt wosch@FreeBSD.org
+wrote the original Perl version.
+The current version was rewritten in C by
+.An Jonathan Belson Aq Mt jon@witchspace.com
+for
+.Fx 5.0 .
+.Sh BUGS
+.\" .Nm kbdmap/vidfont
+.\" does not know which font is in use. E.g. if the current font
+.\" is iso-8859-1 and you chose lang 'ru' (for Russian)
+.\" you get funny latin1 characters and not russkij shrift.
+.\"
+The
+.Nm
+and
+.Nm vidfont
+utilities work only on a (virtual) console and not with
+.Tn X11 .
diff --git a/usr.sbin/kbdmap/kbdmap.c b/usr.sbin/kbdmap/kbdmap.c
new file mode 100644
index 0000000..0670d1b
--- /dev/null
+++ b/usr.sbin/kbdmap/kbdmap.c
@@ -0,0 +1,871 @@
+/*-
+ * Copyright (c) 2002 Jonathan Belson <jon@witchspace.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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/sysctl.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stringlist.h>
+#include <unistd.h>
+
+#include "kbdmap.h"
+
+
+static const char *lang_default = DEFAULT_LANG;
+static const char *font;
+static const char *lang;
+static const char *program;
+static const char *keymapdir = DEFAULT_VT_KEYMAP_DIR;
+static const char *fontdir = DEFAULT_VT_FONT_DIR;
+static const char *font_default = DEFAULT_VT_FONT;
+static const char *sysconfig = DEFAULT_SYSCONFIG;
+static const char *font_current;
+static const char *dir;
+static const char *menu = "";
+
+static int x11;
+static int using_vt;
+static int show;
+static int verbose;
+static int print;
+
+
+struct keymap {
+ char *desc;
+ char *keym;
+ int mark;
+ SLIST_ENTRY(keymap) entries;
+};
+static SLIST_HEAD(slisthead, keymap) head = SLIST_HEAD_INITIALIZER(head);
+
+
+/*
+ * Get keymap entry for 'key', or NULL of not found
+ */
+static struct keymap *
+get_keymap(const char *key)
+{
+ struct keymap *km;
+
+ SLIST_FOREACH(km, &head, entries)
+ if (!strcmp(km->keym, key))
+ return km;
+
+ return NULL;
+}
+
+/*
+ * Count the number of keymaps we found
+ */
+static int
+get_num_keymaps(void)
+{
+ struct keymap *km;
+ int count = 0;
+
+ SLIST_FOREACH(km, &head, entries)
+ count++;
+
+ return count;
+}
+
+/*
+ * Remove any keymap with given keym
+ */
+static void
+remove_keymap(const char *keym)
+{
+ struct keymap *km;
+
+ SLIST_FOREACH(km, &head, entries) {
+ if (!strcmp(keym, km->keym)) {
+ SLIST_REMOVE(&head, km, keymap, entries);
+ free(km);
+ break;
+ }
+ }
+}
+
+/*
+ * Add to hash with 'key'
+ */
+static void
+add_keymap(const char *desc, int mark, const char *keym)
+{
+ struct keymap *km, *km_new;
+
+ /* Is there already an entry with this key? */
+ SLIST_FOREACH(km, &head, entries) {
+ if (!strcmp(km->keym, keym)) {
+ /* Reuse this entry */
+ free(km->desc);
+ km->desc = strdup(desc);
+ km->mark = mark;
+ return;
+ }
+ }
+
+ km_new = (struct keymap *) malloc (sizeof(struct keymap));
+ km_new->desc = strdup(desc);
+ km_new->keym = strdup(keym);
+ km_new->mark = mark;
+
+ /* Add to keymap list */
+ SLIST_INSERT_HEAD(&head, km_new, entries);
+}
+
+/*
+ * Return 0 if syscons is in use (to select legacy defaults).
+ */
+static int
+check_vt(void)
+{
+ size_t len;
+ char term[3];
+
+ len = 3;
+ if (sysctlbyname("kern.vty", &term, &len, NULL, 0) != 0 ||
+ strcmp(term, "vt") != 0)
+ return 0;
+ return 1;
+}
+
+/*
+ * Figure out the default language to use.
+ */
+static const char *
+get_locale(void)
+{
+ const char *locale;
+
+ if ((locale = getenv("LC_ALL")) == NULL &&
+ (locale = getenv("LC_CTYPE")) == NULL &&
+ (locale = getenv("LANG")) == NULL)
+ locale = lang_default;
+
+ /* Check for alias */
+ if (!strcmp(locale, "C"))
+ locale = DEFAULT_LANG;
+
+ return locale;
+}
+
+/*
+ * Extract filename part
+ */
+static const char *
+extract_name(const char *name)
+{
+ char *p;
+
+ p = strrchr(name, '/');
+ if (p != NULL && p[1] != '\0')
+ return p + 1;
+
+ return name;
+}
+
+/*
+ * Return file extension or NULL
+ */
+static char *
+get_extension(const char *name)
+{
+ char *p;
+
+ p = strrchr(name, '.');
+
+ if (p != NULL && p[1] != '\0')
+ return p;
+
+ return NULL;
+}
+
+/*
+ * Read font from /etc/rc.conf else return default.
+ * Freeing the memory is the caller's responsibility.
+ */
+static char *
+get_font(void)
+{
+ char line[256], buf[20];
+ char *fnt = NULL;
+
+ FILE *fp = fopen(sysconfig, "r");
+ if (fp) {
+ while (fgets(line, sizeof(line), fp)) {
+ int a, b, matches;
+
+ if (line[0] == '#')
+ continue;
+
+ matches = sscanf(line,
+ " font%dx%d = \"%20[-.0-9a-zA-Z_]",
+ &a, &b, buf);
+ if (matches==3) {
+ if (strcmp(buf, "NO")) {
+ if (fnt)
+ free(fnt);
+ fnt = (char *) malloc(strlen(buf) + 1);
+ strcpy(fnt, buf);
+ }
+ }
+ }
+ fclose(fp);
+ } else
+ fprintf(stderr, "Could not open %s for reading\n", sysconfig);
+
+ return fnt;
+}
+
+/*
+ * Set a font using 'vidcontrol'
+ */
+static void
+vidcontrol(const char *fnt)
+{
+ char *tmp, *p, *q, *cmd;
+ char ch;
+ int i;
+
+ /* syscons test failed */
+ if (x11)
+ return;
+
+ if (using_vt) {
+ asprintf(&cmd, "vidcontrol -f %s", fnt);
+ system(cmd);
+ free(cmd);
+ return;
+ }
+
+ tmp = strdup(fnt);
+
+ /* Extract font size */
+ p = strrchr(tmp, '-');
+ if (p && p[1] != '\0') {
+ p++;
+ /* Remove any '.fnt' extension */
+ if ((q = strstr(p, ".fnt")))
+ *q = '\0';
+
+ /*
+ * Check font size is valid, with no trailing characters
+ * ('&ch' should not be matched)
+ */
+ if (sscanf(p, "%dx%d%c", &i, &i, &ch) != 2)
+ fprintf(stderr, "Which font size? %s\n", fnt);
+ else {
+ asprintf(&cmd, "vidcontrol -f %s %s", p, fnt);
+ if (verbose)
+ fprintf(stderr, "%s\n", cmd);
+ system(cmd);
+ free(cmd);
+ }
+ } else
+ fprintf(stderr, "Which font size? %s\n", fnt);
+
+ free(tmp);
+}
+
+/*
+ * Execute 'kbdcontrol' with the appropriate arguments
+ */
+static void
+do_kbdcontrol(struct keymap *km)
+{
+ char *kbd_cmd;
+ asprintf(&kbd_cmd, "kbdcontrol -l %s/%s", dir, km->keym);
+
+ if (!x11)
+ system(kbd_cmd);
+
+ fprintf(stderr, "keymap=\"%s\"\n", km->keym);
+ free(kbd_cmd);
+}
+
+/*
+ * Call 'vidcontrol' with the appropriate arguments
+ */
+static void
+do_vidfont(struct keymap *km)
+{
+ char *vid_cmd, *tmp, *p, *q;
+
+ asprintf(&vid_cmd, "%s/%s", dir, km->keym);
+ vidcontrol(vid_cmd);
+ free(vid_cmd);
+
+ tmp = strdup(km->keym);
+ p = strrchr(tmp, '-');
+ if (p && p[1]!='\0') {
+ p++;
+ q = get_extension(p);
+ if (q) {
+ *q = '\0';
+ printf("font%s=%s\n", p, km->keym);
+ }
+ }
+ free(tmp);
+}
+
+/*
+ * Display dialog from 'keymaps[]'
+ */
+static void
+show_dialog(struct keymap **km_sorted, int num_keymaps)
+{
+ FILE *fp;
+ char *cmd, *dialog;
+ char tmp_name[] = "/tmp/_kbd_lang.XXXX";
+ int fd, i, size;
+
+ fd = mkstemp(tmp_name);
+ if (fd == -1) {
+ fprintf(stderr, "Could not open temporary file \"%s\"\n",
+ tmp_name);
+ exit(1);
+ }
+ asprintf(&dialog, "/usr/bin/dialog --clear --title \"Keyboard Menu\" "
+ "--menu \"%s\" 0 0 0", menu);
+
+ /* start right font, assume that current font is equal
+ * to default font in /etc/rc.conf
+ *
+ * $font is the font which require the language $lang; e.g.
+ * russian *need* a koi8 font
+ * $font_current is the current font from /etc/rc.conf
+ */
+ if (font && strcmp(font, font_current))
+ vidcontrol(font);
+
+ /* Build up the command */
+ size = 0;
+ for (i=0; i<num_keymaps; i++) {
+ /*
+ * Each 'font' is passed as ' "font" ""', so allow the
+ * extra space
+ */
+ size += strlen(km_sorted[i]->desc) + 6;
+ }
+
+ /* Allow the space for '2> tmpfilename' redirection */
+ size += strlen(tmp_name) + 3;
+
+ cmd = (char *) malloc(strlen(dialog) + size + 1);
+ strcpy(cmd, dialog);
+
+ for (i=0; i<num_keymaps; i++) {
+ strcat(cmd, " \"");
+ strcat(cmd, km_sorted[i]->desc);
+ strcat(cmd, "\"");
+ strcat(cmd, " \"\"");
+ }
+
+ strcat(cmd, " 2>");
+ strcat(cmd, tmp_name);
+
+ /* Show the dialog.. */
+ system(cmd);
+
+ fp = fopen(tmp_name, "r");
+ if (fp) {
+ char choice[64];
+ if (fgets(choice, sizeof(choice), fp) != NULL) {
+ /* Find key for desc */
+ for (i=0; i<num_keymaps; i++) {
+ if (!strcmp(choice, km_sorted[i]->desc)) {
+ if (!strcmp(program, "kbdmap"))
+ do_kbdcontrol(km_sorted[i]);
+ else
+ do_vidfont(km_sorted[i]);
+ break;
+ }
+ }
+ } else {
+ if (font != NULL && strcmp(font, font_current))
+ /* Cancelled, restore old font */
+ vidcontrol(font_current);
+ }
+ fclose(fp);
+ } else
+ fprintf(stderr, "Failed to open temporary file");
+
+ /* Tidy up */
+ remove(tmp_name);
+ free(cmd);
+ free(dialog);
+ close(fd);
+}
+
+/*
+ * Search for 'token' in comma delimited array 'buffer'.
+ * Return true for found, false for not found.
+ */
+static int
+find_token(const char *buffer, const char *token)
+{
+ char *buffer_tmp, *buffer_copy, *inputstring;
+ char **ap;
+ int found;
+
+ buffer_copy = strdup(buffer);
+ buffer_tmp = buffer_copy;
+ inputstring = buffer_copy;
+ ap = &buffer_tmp;
+
+ found = 0;
+
+ while ((*ap = strsep(&inputstring, ",")) != NULL) {
+ if (strcmp(buffer_tmp, token) == 0) {
+ found = 1;
+ break;
+ }
+ }
+
+ free(buffer_copy);
+
+ return found;
+}
+
+/*
+ * Compare function for qsort
+ */
+static int
+compare_keymap(const void *a, const void *b)
+{
+
+ /* We've been passed pointers to pointers, so: */
+ const struct keymap *km1 = *((const struct keymap * const *) a);
+ const struct keymap *km2 = *((const struct keymap * const *) b);
+
+ return strcmp(km1->desc, km2->desc);
+}
+
+/*
+ * Compare function for qsort
+ */
+static int
+compare_lang(const void *a, const void *b)
+{
+ const char *l1 = *((const char * const *) a);
+ const char *l2 = *((const char * const *) b);
+
+ return strcmp(l1, l2);
+}
+
+/*
+ * Change '8x8' to '8x08' so qsort will put it before eg. '8x14'
+ */
+static void
+kludge_desc(struct keymap **km_sorted, int num_keymaps)
+{
+ int i;
+
+ for (i=0; i<num_keymaps; i++) {
+ char *p;
+ char *km = km_sorted[i]->desc;
+ if ((p = strstr(km, "8x8")) != NULL) {
+ int len;
+ int j;
+ int offset;
+
+ offset = p - km;
+
+ /* Make enough space for the extra '0' */
+ len = strlen(km);
+ km = realloc(km, len + 2);
+
+ for (j=len; j!=offset+1; j--)
+ km[j + 1] = km[j];
+
+ km[offset+2] = '0';
+
+ km_sorted[i]->desc = km;
+ }
+ }
+}
+
+/*
+ * Reverse 'kludge_desc()' - change '8x08' back to '8x8'
+ */
+static void
+unkludge_desc(struct keymap **km_sorted, int num_keymaps)
+{
+ int i;
+
+ for (i=0; i<num_keymaps; i++) {
+ char *p;
+ char *km = km_sorted[i]->desc;
+ if ((p = strstr(km, "8x08")) != NULL) {
+ p += 2;
+ while (*p++)
+ p[-1] = p[0];
+
+ km = realloc(km, p - km - 1);
+ km_sorted[i]->desc = km;
+ }
+ }
+}
+
+/*
+ * Return 0 if file exists and is readable, else -1
+ */
+static int
+check_file(const char *keym)
+{
+ int status = 0;
+
+ if (access(keym, R_OK) == -1) {
+ char *fn;
+ asprintf(&fn, "%s/%s", dir, keym);
+ if (access(fn, R_OK) == -1) {
+ if (verbose)
+ fprintf(stderr, "%s not found!\n", fn);
+ status = -1;
+ }
+ free(fn);
+ } else {
+ if (verbose)
+ fprintf(stderr, "No read permission for %s!\n", keym);
+ status = -1;
+ }
+
+ return status;
+}
+
+/*
+ * Read options from the relevant configuration file, then
+ * present to user.
+ */
+static void
+menu_read(void)
+{
+ const char *lg;
+ char *p;
+ int mark, num_keymaps, items, i;
+ char buffer[256], filename[PATH_MAX];
+ char keym[64], lng[64], desc[256];
+ char dialect[64], lang_abk[64];
+ struct keymap *km;
+ struct keymap **km_sorted;
+ struct dirent *dp;
+ StringList *lang_list;
+ FILE *fp;
+ DIR *dirp;
+
+ lang_list = sl_init();
+
+ sprintf(filename, "%s/INDEX.%s", dir, extract_name(dir));
+
+ /* en_US.ISO8859-1 -> en_..\.ISO8859-1 */
+ strlcpy(dialect, lang, sizeof(dialect));
+ if (strlen(dialect) >= 6 && dialect[2] == '_') {
+ dialect[3] = '.';
+ dialect[4] = '.';
+ }
+
+
+ /* en_US.ISO8859-1 -> en */
+ strlcpy(lang_abk, lang, sizeof(lang_abk));
+ if (strlen(lang_abk) >= 3 && lang_abk[2] == '_')
+ lang_abk[2] = '\0';
+
+ fprintf(stderr, "lang_default = %s\n", lang_default);
+ fprintf(stderr, "dialect = %s\n", dialect);
+ fprintf(stderr, "lang_abk = %s\n", lang_abk);
+
+ fp = fopen(filename, "r");
+ if (fp) {
+ int matches;
+ while (fgets(buffer, sizeof(buffer), fp)) {
+ p = buffer;
+ if (p[0] == '#')
+ continue;
+
+ while (isspace(*p))
+ p++;
+
+ if (*p == '\0')
+ continue;
+
+ /* Parse input, removing newline */
+ matches = sscanf(p, "%64[^:]:%64[^:]:%256[^:\n]",
+ keym, lng, desc);
+ if (matches == 3) {
+ if (strcmp(keym, "FONT")
+ && strcmp(keym, "MENU")) {
+ /* Check file exists & is readable */
+ if (check_file(keym) == -1)
+ continue;
+ }
+ }
+
+ if (show) {
+ /*
+ * Take note of supported languages, which
+ * might be in a comma-delimited list
+ */
+ char *tmp = strdup(lng);
+ char *delim = tmp;
+
+ for (delim = tmp; ; ) {
+ char ch = *delim++;
+ if (ch == ',' || ch == '\0') {
+ delim[-1] = '\0';
+ if (!sl_find(lang_list, tmp))
+ sl_add(lang_list, tmp);
+ if (ch == '\0')
+ break;
+ tmp = delim;
+ }
+ }
+ }
+ /* Set empty language to default language */
+ if (lng[0] == '\0')
+ lg = lang_default;
+ else
+ lg = lng;
+
+
+ /* 4) Your choice if it exists
+ * 3) Long match eg. en_GB.ISO8859-1 is equal to
+ * en_..\.ISO8859-1
+ * 2) short match 'de'
+ * 1) default langlist 'en'
+ * 0) any language
+ *
+ * Language may be a comma separated list
+ * A higher match overwrites a lower
+ * A later entry overwrites a previous if it exists
+ * twice in the database
+ */
+
+ /* Check for favoured language */
+ km = get_keymap(keym);
+ mark = (km) ? km->mark : 0;
+
+ if (find_token(lg, lang))
+ add_keymap(desc, 4, keym);
+ else if (mark <= 3 && find_token(lg, dialect))
+ add_keymap(desc, 3, keym);
+ else if (mark <= 2 && find_token(lg, lang_abk))
+ add_keymap(desc, 2, keym);
+ else if (mark <= 1 && find_token(lg, lang_default))
+ add_keymap(desc, 1, keym);
+ else if (mark <= 0)
+ add_keymap(desc, 0, keym);
+ }
+ fclose(fp);
+
+ } else
+ fprintf(stderr, "Could not open %s for reading\n", filename);
+
+ if (show) {
+ qsort(lang_list->sl_str, lang_list->sl_cur, sizeof(char*),
+ compare_lang);
+ printf("Currently supported languages: ");
+ for (i=0; i< (int) lang_list->sl_cur; i++)
+ printf("%s ", lang_list->sl_str[i]);
+ puts("");
+ exit(0);
+ }
+
+ km = get_keymap("MENU");
+ if (km)
+ /* Take note of menu title */
+ menu = strdup(km->desc);
+ km = get_keymap("FONT");
+ if (km)
+ /* Take note of language font */
+ font = strdup(km->desc);
+
+ /* Remove unwanted items from list */
+ remove_keymap("MENU");
+ remove_keymap("FONT");
+
+ /* Look for keymaps not in database */
+ dirp = opendir(dir);
+ if (dirp) {
+ while ((dp = readdir(dirp)) != NULL) {
+ const char *ext = get_extension(dp->d_name);
+ if (ext) {
+ if ((!strcmp(ext, ".fnt") ||
+ !strcmp(ext, ".kbd")) &&
+ !get_keymap(dp->d_name)) {
+ char *q;
+
+ /* Remove any .fnt or .kbd extension */
+ q = strdup(dp->d_name);
+ *(get_extension(q)) = '\0';
+ add_keymap(q, 0, dp->d_name);
+ free(q);
+
+ if (verbose)
+ fprintf(stderr,
+ "'%s' not in database\n",
+ dp->d_name);
+ }
+ }
+ }
+ closedir(dirp);
+ } else
+ fprintf(stderr, "Could not open directory '%s'\n", dir);
+
+ /* Sort items in keymap */
+ num_keymaps = get_num_keymaps();
+
+ km_sorted = (struct keymap **)
+ malloc(num_keymaps*sizeof(struct keymap *));
+
+ /* Make array of pointers to items in hash */
+ items = 0;
+ SLIST_FOREACH(km, &head, entries)
+ km_sorted[items++] = km;
+
+ /* Change '8x8' to '8x08' so sort works as we might expect... */
+ kludge_desc(km_sorted, num_keymaps);
+
+ qsort(km_sorted, num_keymaps, sizeof(struct keymap *), compare_keymap);
+
+ /* ...change back again */
+ unkludge_desc(km_sorted, num_keymaps);
+
+ if (print) {
+ for (i=0; i<num_keymaps; i++)
+ printf("%s\n", km_sorted[i]->desc);
+ exit(0);
+ }
+
+ show_dialog(km_sorted, num_keymaps);
+
+ free(km_sorted);
+}
+
+/*
+ * Display usage information and exit
+ */
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: %s\t[-K] [-V] [-d|-default] [-h|-help] "
+ "[-l|-lang language]\n\t\t[-p|-print] [-r|-restore] [-s|-show] "
+ "[-v|-verbose]\n", program);
+ exit(1);
+}
+
+static void
+parse_args(int argc, char **argv)
+{
+ int i;
+
+ for (i=1; i<argc; i++) {
+ if (argv[i][0] != '-')
+ usage();
+ else if (!strcmp(argv[i], "-help") || !strcmp(argv[i], "-h"))
+ usage();
+ else if (!strcmp(argv[i], "-verbose") || !strcmp(argv[i], "-v"))
+ verbose = 1;
+ else if (!strcmp(argv[i], "-lang") || !strcmp(argv[i], "-l"))
+ if (i + 1 == argc)
+ usage();
+ else
+ lang = argv[++i];
+ else if (!strcmp(argv[i], "-default") || !strcmp(argv[i], "-d"))
+ lang = lang_default;
+ else if (!strcmp(argv[i], "-show") || !strcmp(argv[i], "-s"))
+ show = 1;
+ else if (!strcmp(argv[i], "-print") || !strcmp(argv[i], "-p"))
+ print = 1;
+ else if (!strcmp(argv[i], "-restore") ||
+ !strcmp(argv[i], "-r")) {
+ vidcontrol(font_current);
+ exit(0);
+ } else if (!strcmp(argv[i], "-K"))
+ dir = keymapdir;
+ else if (!strcmp(argv[i], "-V"))
+ dir = fontdir;
+ else
+ usage();
+ }
+}
+
+/*
+ * A front-end for the 'vidfont' and 'kbdmap' programs.
+ */
+int
+main(int argc, char **argv)
+{
+
+ x11 = system("kbdcontrol -d >/dev/null");
+
+ if (x11) {
+ fprintf(stderr, "You are not on a virtual console - "
+ "expect certain strange side-effects\n");
+ sleep(2);
+ }
+
+ using_vt = check_vt();
+ if (using_vt == 0) {
+ keymapdir = DEFAULT_SC_KEYMAP_DIR;
+ fontdir = DEFAULT_SC_FONT_DIR;
+ font_default = DEFAULT_SC_FONT;
+ }
+
+ SLIST_INIT(&head);
+
+ lang = get_locale();
+
+ program = extract_name(argv[0]);
+
+ font_current = get_font();
+ if (font_current == NULL)
+ font_current = font_default;
+
+ if (strcmp(program, "kbdmap"))
+ dir = fontdir;
+ else
+ dir = keymapdir;
+
+ /* Parse command line arguments */
+ parse_args(argc, argv);
+
+ /* Read and display options */
+ menu_read();
+
+ return 0;
+}
diff --git a/usr.sbin/kbdmap/kbdmap.h b/usr.sbin/kbdmap/kbdmap.h
new file mode 100644
index 0000000..89d27b1
--- /dev/null
+++ b/usr.sbin/kbdmap/kbdmap.h
@@ -0,0 +1,39 @@
+/*-
+ * Copyright (c) 2002 Jonathan Belson <jon@witchspace.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$
+ */
+
+
+#define DEFAULT_LANG "en"
+#define DEFAULT_SYSCONFIG "/etc/rc.conf"
+
+#define DEFAULT_SC_KEYMAP_DIR "/usr/share/syscons/keymaps"
+#define DEFAULT_SC_FONT_DIR "/usr/share/syscons/fonts"
+#define DEFAULT_SC_FONT "cp437-8x16.fnt"
+
+#define DEFAULT_VT_KEYMAP_DIR "/usr/share/vt/keymaps"
+#define DEFAULT_VT_FONT_DIR "/usr/share/vt/fonts"
+#define DEFAULT_VT_FONT "vgarom-thin-8x16.fnt"
diff --git a/usr.sbin/keyserv/Makefile b/usr.sbin/keyserv/Makefile
new file mode 100644
index 0000000..db8a832
--- /dev/null
+++ b/usr.sbin/keyserv/Makefile
@@ -0,0 +1,25 @@
+# $FreeBSD$
+
+PROG= keyserv
+MAN= keyserv.8
+SRCS= keyserv.c setkey.c crypt_svc.c crypt_server.c crypt.h
+
+CFLAGS+= -DKEYSERV_RANDOM -DBROKEN_DES -I.
+
+LIBADD= mp rpcsvc
+
+WARNS?= 1
+
+RPCDIR= ${DESTDIR}/usr/include/rpcsvc
+
+CLEANFILES= crypt_svc.c crypt.h
+
+RPCGEN= RPCGEN_CPP=${CPP:Q} rpcgen -C
+
+crypt_svc.c: ${RPCDIR}/crypt.x
+ ${RPCGEN} -m -o ${.TARGET} ${RPCDIR}/crypt.x
+
+crypt.h: ${RPCDIR}/crypt.x
+ ${RPCGEN} -h -o ${.TARGET} ${RPCDIR}/crypt.x
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/keyserv/Makefile.depend b/usr.sbin/keyserv/Makefile.depend
new file mode 100644
index 0000000..839d12b
--- /dev/null
+++ b/usr.sbin/keyserv/Makefile.depend
@@ -0,0 +1,29 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libmp \
+ lib/librpcsvc \
+ secure/lib/libcrypto \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+crypt_server.o: crypt.h
+crypt_server.po: crypt.h
+crypt_svc.o: crypt.h
+crypt_svc.o: crypt_svc.c
+crypt_svc.po: crypt.h
+crypt_svc.po: crypt_svc.c
+.endif
diff --git a/usr.sbin/keyserv/crypt_server.c b/usr.sbin/keyserv/crypt_server.c
new file mode 100644
index 0000000..b4c6036
--- /dev/null
+++ b/usr.sbin/keyserv/crypt_server.c
@@ -0,0 +1,276 @@
+/*
+ * Copyright (c) 1996
+ * 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <dirent.h>
+#include <dlfcn.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <rpc/des_crypt.h>
+#include <rpc/des.h>
+#include "crypt.h"
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+/*
+ * The U.S. government stupidly believes that a) it can keep strong
+ * crypto code a secret and b) that doing so somehow protects national
+ * interests. It's wrong on both counts, but until it listens to reason
+ * we have to make certain compromises so it doesn't have an excuse to
+ * throw us in federal prison.
+ *
+ * Consequently, the core OS ships without DES support, and keyserv
+ * defaults to using ARCFOUR with only a 40 bit key, just like nutscrape.
+ * This breaks compatibility with Secure RPC on other systems, but it
+ * allows Secure RPC to work between FreeBSD systems that don't have the
+ * DES package installed without throwing security totally out the window.
+ *
+ * In order to avoid having to supply two versions of keyserv (one with
+ * DES and one without), we use dlopen() and friends to load libdes.so
+ * into our address space at runtime. We check for the presence of
+ * /usr/lib/libdes.so.3.0 at startup and load it if we find it. If we
+ * can't find it, or the __des_crypt symbol doesn't exist, we fall back
+ * to the ARCFOUR encryption code. The user can specify another path using
+ * the -p flag.
+ */
+
+ /* arcfour.h */
+typedef struct arcfour_key
+{
+ unsigned char state[256];
+ unsigned char x;
+ unsigned char y;
+} arcfour_key;
+
+static void prepare_key(unsigned char *key_data_ptr,int key_data_len,
+ arcfour_key *key);
+static void arcfour(unsigned char *buffer_ptr,int buffer_len,arcfour_key * key);
+static void swap_byte(unsigned char *a, unsigned char *b);
+
+static void prepare_key(unsigned char *key_data_ptr, int key_data_len,
+ arcfour_key *key)
+{
+ unsigned char index1;
+ unsigned char index2;
+ unsigned char* state;
+ short counter;
+
+ state = &key->state[0];
+ for(counter = 0; counter < 256; counter++)
+ state[counter] = counter;
+ key->x = 0;
+ key->y = 0;
+ index1 = 0;
+ index2 = 0;
+ for(counter = 0; counter < 256; counter++)
+ {
+ index2 = (key_data_ptr[index1] + state[counter] +
+ index2) % 256;
+ swap_byte(&state[counter], &state[index2]);
+
+ index1 = (index1 + 1) % key_data_len;
+ }
+}
+
+static void arcfour(unsigned char *buffer_ptr, int buffer_len, arcfour_key *key)
+{
+ unsigned char x;
+ unsigned char y;
+ unsigned char* state;
+ unsigned char xorIndex;
+ short counter;
+
+ x = key->x;
+ y = key->y;
+
+ state = &key->state[0];
+ for(counter = 0; counter < buffer_len; counter ++)
+ {
+ x = (x + 1) % 256;
+ y = (state[x] + y) % 256;
+ swap_byte(&state[x], &state[y]);
+
+ xorIndex = (state[x] + state[y]) % 256;
+
+ buffer_ptr[counter] ^= state[xorIndex];
+ }
+ key->x = x;
+ key->y = y;
+}
+
+static void swap_byte(unsigned char *a, unsigned char *b)
+{
+ unsigned char swapByte;
+
+ swapByte = *a;
+ *a = *b;
+ *b = swapByte;
+}
+
+/* Dummy _des_crypt function that uses ARCFOUR with a 40 bit key */
+int _arcfour_crypt(buf, len, desp)
+ char *buf;
+ int len;
+ struct desparams *desp;
+{
+ struct arcfour_key arcfourk;
+
+ /*
+ * U.S. government anti-crypto weasels take
+ * note: although we are supplied with a 64 bit
+ * key, we're only passing 40 bits to the ARCFOUR
+ * encryption code. So there.
+ */
+ prepare_key(desp->des_key, 5, &arcfourk);
+ arcfour(buf, len, &arcfourk);
+
+ return(DESERR_NOHWDEVICE);
+}
+
+int (*_my_crypt)(char *, int, struct desparams *) = NULL;
+
+static void *dlhandle;
+
+#ifndef _PATH_USRLIB
+#define _PATH_USRLIB "/usr/lib"
+#endif
+
+#ifndef LIBCRYPTO
+#define LIBCRYPTO "libcrypto.so.2"
+#endif
+
+void load_des(warn, libpath)
+ int warn;
+ char *libpath;
+{
+ char dlpath[MAXPATHLEN];
+
+ if (libpath == NULL) {
+ snprintf(dlpath, sizeof(dlpath), "%s/%s", _PATH_USRLIB, LIBCRYPTO);
+ } else
+ snprintf(dlpath, sizeof(dlpath), "%s", libpath);
+
+ if (dlpath != NULL && (dlhandle = dlopen(dlpath, 0444)) != NULL)
+ _my_crypt = (int (*)())dlsym(dlhandle, "_des_crypt");
+
+ if (_my_crypt == NULL) {
+ if (dlhandle != NULL)
+ dlclose(dlhandle);
+ _my_crypt = &_arcfour_crypt;
+ if (warn) {
+ printf ("DES support disabled -- using ARCFOUR instead.\n");
+ printf ("Warning: ARCFOUR cipher is not compatible with ");
+ printf ("other Secure RPC implementations.\nInstall ");
+ printf ("the FreeBSD 'des' distribution to enable");
+ printf (" DES encryption.\n");
+ }
+ } else {
+ if (warn) {
+ printf ("DES support enabled\n");
+ printf ("Using %s shared object.\n", dlpath);
+ }
+ }
+
+ return;
+}
+
+desresp *
+des_crypt_1_svc(desargs *argp, struct svc_req *rqstp)
+{
+ static desresp result;
+ struct desparams dparm;
+
+ if (argp->desbuf.desbuf_len > DES_MAXDATA) {
+ result.stat = DESERR_BADPARAM;
+ return(&result);
+ }
+
+
+ bcopy(argp->des_key, dparm.des_key, 8);
+ bcopy(argp->des_ivec, dparm.des_ivec, 8);
+ dparm.des_mode = (argp->des_mode == CBC_DES) ? CBC : ECB;
+ dparm.des_dir = (argp->des_dir == ENCRYPT_DES) ? ENCRYPT : DECRYPT;
+#ifdef BROKEN_DES
+ dparm.UDES.UDES_buf = argp->desbuf.desbuf_val;
+#endif
+
+ /*
+ * XXX This compensates for a bug in the libdes Secure RPC
+ * compat interface. (Actually, there are a couple.) The
+ * des_ecb_encrypt() routine in libdes only encrypts 8 bytes
+ * (64 bits) at a time. However, the Sun Secure RPC ecb_crypt()
+ * routine is supposed to be able to handle buffers up to 8Kbytes.
+ * The rpc_enc module in libdes ignores this fact and just drops
+ * the length parameter on the floor, encrypting only the
+ * first 64 bits of whatever buffer you feed it. We deal with
+ * this here: if we're using DES encryption, and we're using
+ * ECB mode, then we make a pass over the entire buffer
+ * ourselves. Note: the rpc_enc module incorrectly transposes
+ * the mode flags, so when you ask for CBC mode, you're really
+ * getting ECB mode.
+ */
+#ifdef BROKEN_DES
+ if (_my_crypt != &_arcfour_crypt && argp->des_mode == CBC) {
+#else
+ if (_my_crypt != &_arcfour_crypt && argp->des_mode == ECB) {
+#endif
+ int i;
+ char *dptr;
+
+ for (i = 0; i < argp->desbuf.desbuf_len / 8; i++) {
+ dptr = argp->desbuf.desbuf_val;
+ dptr += (i * 8);
+#ifdef BROKEN_DES
+ dparm.UDES.UDES_buf = dptr;
+#endif
+ result.stat = _my_crypt(dptr, 8, &dparm);
+ }
+ } else {
+ result.stat = _my_crypt(argp->desbuf.desbuf_val,
+ argp->desbuf.desbuf_len,
+ &dparm);
+ }
+
+ if (result.stat == DESERR_NONE || result.stat == DESERR_NOHWDEVICE) {
+ bcopy(dparm.des_ivec, result.des_ivec, 8);
+ result.desbuf.desbuf_len = argp->desbuf.desbuf_len;
+ result.desbuf.desbuf_val = argp->desbuf.desbuf_val;
+ }
+
+ return (&result);
+}
diff --git a/usr.sbin/keyserv/keyserv.8 b/usr.sbin/keyserv/keyserv.8
new file mode 100644
index 0000000..726f91c
--- /dev/null
+++ b/usr.sbin/keyserv/keyserv.8
@@ -0,0 +1,80 @@
+.\" @(#)keyserv.1m 1.21 93/07/14 SMI; from SVr4
+.\"macro stdmacro
+.\" Copyright 1989 AT&T
+.\" @(#)keyserv.8c 1.8 89/03/29 SMI;
+.\" $FreeBSD$
+.\".TH KEYSERV 8C "9 September 1987"
+.Dd September 14, 1992
+.Dt KEYSERV 8
+.Os
+.Sh NAME
+.Nm keyserv
+.Nd server for storing private encryption keys
+.Sh SYNOPSIS
+.Nm
+.Op Fl d
+.Op Fl D
+.Op Fl n
+.Op Fl p Ar path
+.Op Fl v
+.Sh DESCRIPTION
+The
+.Nm
+utility is a daemon that is used for storing the
+private encryption keys of each
+user logged into the system.
+These encryption keys are used for accessing
+secure network services such as secure
+.Tn NFS .
+.Pp
+Normally, root's key is read from the file
+.Pa /etc/.rootkey
+when the daemon is started.
+This is useful during power-fail reboots
+when no one is around to type a password.
+.Pp
+If a client with no secret key calls
+.Nm ,
+then the key of user
+.Em nobody
+is used instead as the default key.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl d
+Disable the use of default keys for
+.Em nobody .
+.It Fl D
+Run in debugging mode and log all requests to
+.Nm .
+.It Fl n
+Root's secret key is not read from
+.Pa /etc/.rootkey .
+Instead,
+.Nm
+prompts the user for the password to decrypt
+root's key stored in the
+.Pa /etc/publickey
+database and then stores the decrypted key in
+.Pa /etc/.rootkey
+for future use.
+This option is useful if the
+.Pa /etc/.rootkey
+file ever gets out of date or corrupted.
+.It Fl p Ar path
+Specify where to search for
+.Pa libdes.so.3 .
+Default is
+.Pa /usr/lib .
+.It Fl v
+Display status of DES support (enabled/disabled).
+.El
+.Sh FILES
+.Bl -tag -width /usr/lib/libdes.so.3 -compact
+.It Pa /etc/.rootkey
+.It Pa /usr/lib/libdes.so.3
+.El
+.Sh "SEE ALSO"
+.Xr keylogin 1 ,
+.Xr keylogout 1 ,
+.Xr publickey 5
diff --git a/usr.sbin/keyserv/keyserv.c b/usr.sbin/keyserv/keyserv.c
new file mode 100644
index 0000000..79bf90d
--- /dev/null
+++ b/usr.sbin/keyserv/keyserv.c
@@ -0,0 +1,821 @@
+/*
+ * Sun RPC is a product of Sun Microsystems, Inc. and is provided for
+ * unrestricted use provided that this legend is included on all tape
+ * media and as a part of the software program in whole or part. Users
+ * may copy or modify Sun RPC without charge, but are not authorized
+ * to license or distribute it to anyone else except as part of a product or
+ * program developed by the user.
+ *
+ * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
+ * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
+ *
+ * Sun RPC is provided with no support and without any obligation on the
+ * part of Sun Microsystems, Inc. to assist in its use, correction,
+ * modification or enhancement.
+ *
+ * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
+ * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC
+ * OR ANY PART THEREOF.
+ *
+ * In no event will Sun Microsystems, Inc. be liable for any lost revenue
+ * or profits or other special, indirect and consequential damages, even if
+ * Sun has been advised of the possibility of such damages.
+ *
+ * Sun Microsystems, Inc.
+ * 2550 Garcia Avenue
+ * Mountain View, California 94043
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)keyserv.c 1.15 94/04/25 SMI";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+/*
+ * Copyright (c) 1986 - 1991 by Sun Microsystems, Inc.
+ */
+
+/*
+ * Keyserver
+ * Store secret keys per uid. Do public key encryption and decryption
+ * operations. Generate "random" keys.
+ * Do not talk to anything but a local root
+ * process on the local transport only
+ */
+
+#include <err.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <rpc/rpc.h>
+#include <sys/param.h>
+#include <sys/file.h>
+#include <rpc/des_crypt.h>
+#include <rpc/des.h>
+#include <rpc/key_prot.h>
+#include <rpcsvc/crypt.h>
+#include "keyserv.h"
+
+#ifndef NGROUPS
+#define NGROUPS 16
+#endif
+
+#ifndef KEYSERVSOCK
+#define KEYSERVSOCK "/var/run/keyservsock"
+#endif
+
+static void randomize( des_block * );
+static void usage( void );
+static int getrootkey( des_block *, int );
+static int root_auth( SVCXPRT *, struct svc_req * );
+
+#ifdef DEBUG
+static int debugging = 1;
+#else
+static int debugging = 0;
+#endif
+
+static void keyprogram();
+static des_block masterkey;
+char *getenv();
+static char ROOTKEY[] = "/etc/.rootkey";
+
+/*
+ * Hack to allow the keyserver to use AUTH_DES (for authenticated
+ * NIS+ calls, for example). The only functions that get called
+ * are key_encryptsession_pk, key_decryptsession_pk, and key_gendes.
+ *
+ * The approach is to have the keyserver fill in pointers to local
+ * implementations of these functions, and to call those in key_call().
+ */
+
+extern cryptkeyres *(*__key_encryptsession_pk_LOCAL)();
+extern cryptkeyres *(*__key_decryptsession_pk_LOCAL)();
+extern des_block *(*__key_gendes_LOCAL)();
+extern int (*__des_crypt_LOCAL)();
+
+cryptkeyres *key_encrypt_pk_2_svc_prog( uid_t, cryptkeyarg2 * );
+cryptkeyres *key_decrypt_pk_2_svc_prog( uid_t, cryptkeyarg2 * );
+des_block *key_gen_1_svc_prog( void *, struct svc_req * );
+
+int
+main(argc, argv)
+ int argc;
+ char *argv[];
+{
+ int nflag = 0;
+ int c;
+ int warn = 0;
+ char *path = NULL;
+ void *localhandle;
+ register SVCXPRT *transp;
+ struct netconfig *nconf = NULL;
+
+ __key_encryptsession_pk_LOCAL = &key_encrypt_pk_2_svc_prog;
+ __key_decryptsession_pk_LOCAL = &key_decrypt_pk_2_svc_prog;
+ __key_gendes_LOCAL = &key_gen_1_svc_prog;
+
+ while ((c = getopt(argc, argv, "ndDvp:")) != -1)
+ switch (c) {
+ case 'n':
+ nflag++;
+ break;
+ case 'd':
+ pk_nodefaultkeys();
+ break;
+ case 'D':
+ debugging = 1;
+ break;
+ case 'v':
+ warn = 1;
+ break;
+ case 'p':
+ path = optarg;
+ break;
+ default:
+ usage();
+ }
+
+ load_des(warn, path);
+ __des_crypt_LOCAL = _my_crypt;
+ if (svc_auth_reg(AUTH_DES, _svcauth_des) == -1)
+ errx(1, "failed to register AUTH_DES authenticator");
+
+ if (optind != argc) {
+ usage();
+ }
+
+ /*
+ * Initialize
+ */
+ (void) umask(S_IXUSR|S_IXGRP|S_IXOTH);
+ if (geteuid() != 0)
+ errx(1, "keyserv must be run as root");
+ setmodulus(HEXMODULUS);
+ getrootkey(&masterkey, nflag);
+
+ rpcb_unset(KEY_PROG, KEY_VERS, NULL);
+ rpcb_unset(KEY_PROG, KEY_VERS2, NULL);
+
+ if (svc_create(keyprogram, KEY_PROG, KEY_VERS,
+ "netpath") == 0) {
+ (void) fprintf(stderr,
+ "%s: unable to create service\n", argv[0]);
+ exit(1);
+ }
+
+ if (svc_create(keyprogram, KEY_PROG, KEY_VERS2,
+ "netpath") == 0) {
+ (void) fprintf(stderr,
+ "%s: unable to create service\n", argv[0]);
+ exit(1);
+ }
+
+ localhandle = setnetconfig();
+ while ((nconf = getnetconfig(localhandle)) != NULL) {
+ if (nconf->nc_protofmly != NULL &&
+ strcmp(nconf->nc_protofmly, NC_LOOPBACK) == 0)
+ break;
+ }
+
+ if (nconf == NULL)
+ errx(1, "getnetconfig: %s", nc_sperror());
+
+ unlink(KEYSERVSOCK);
+ rpcb_unset(CRYPT_PROG, CRYPT_VERS, nconf);
+ transp = svcunix_create(RPC_ANYSOCK, 0, 0, KEYSERVSOCK);
+ if (transp == NULL)
+ errx(1, "cannot create AF_LOCAL service");
+ if (!svc_reg(transp, KEY_PROG, KEY_VERS, keyprogram, nconf))
+ errx(1, "unable to register (KEY_PROG, KEY_VERS, unix)");
+ if (!svc_reg(transp, KEY_PROG, KEY_VERS2, keyprogram, nconf))
+ errx(1, "unable to register (KEY_PROG, KEY_VERS2, unix)");
+ if (!svc_reg(transp, CRYPT_PROG, CRYPT_VERS, crypt_prog_1, nconf))
+ errx(1, "unable to register (CRYPT_PROG, CRYPT_VERS, unix)");
+
+ endnetconfig(localhandle);
+
+ (void) umask(066); /* paranoia */
+
+ if (!debugging) {
+ daemon(0,0);
+ }
+
+ signal(SIGPIPE, SIG_IGN);
+
+ svc_run();
+ abort();
+ /* NOTREACHED */
+}
+
+/*
+ * In the event that we don't get a root password, we try to
+ * randomize the master key the best we can
+ */
+static void
+randomize(master)
+ des_block *master;
+{
+#ifndef __FreeBSD__
+ int i;
+ int seed;
+ struct timeval tv;
+ int shift;
+
+ seed = 0;
+ for (i = 0; i < 1024; i++) {
+ (void)gettimeofday(&tv, NULL);
+ shift = i % 8 * sizeof (int);
+ seed ^= (tv.tv_usec << shift) | (tv.tv_usec >> (32 - shift));
+ }
+#endif
+#ifdef KEYSERV_RANDOM
+#ifdef __FreeBSD__
+ master->key.low = arc4random();
+ master->key.high = arc4random();
+#else
+ srandom(seed);
+ master->key.low = random();
+ master->key.high = random();
+#endif
+#else
+ /* use stupid dangerous bad rand() */
+#ifdef __FreeBSD__
+ sranddev();
+#else
+ srand(seed);
+#endif
+ master->key.low = rand();
+ master->key.high = rand();
+#endif
+}
+
+/*
+ * Try to get root's secret key, by prompting if terminal is a tty, else trying
+ * from standard input.
+ * Returns 1 on success.
+ */
+static int
+getrootkey(master, prompt)
+ des_block *master;
+ int prompt;
+{
+ char *passwd;
+ char name[MAXNETNAMELEN + 1];
+ char secret[HEXKEYBYTES];
+ key_netstarg netstore;
+ int fd;
+
+ if (!prompt) {
+ /*
+ * Read secret key out of ROOTKEY
+ */
+ fd = open(ROOTKEY, O_RDONLY, 0);
+ if (fd < 0) {
+ randomize(master);
+ return (0);
+ }
+ if (read(fd, secret, HEXKEYBYTES) < HEXKEYBYTES) {
+ warnx("the key read from %s was too short", ROOTKEY);
+ (void) close(fd);
+ return (0);
+ }
+ (void) close(fd);
+ if (!getnetname(name)) {
+ warnx(
+ "failed to generate host's netname when establishing root's key");
+ return (0);
+ }
+ memcpy(netstore.st_priv_key, secret, HEXKEYBYTES);
+ memset(netstore.st_pub_key, 0, HEXKEYBYTES);
+ netstore.st_netname = name;
+ if (pk_netput(0, &netstore) != KEY_SUCCESS) {
+ warnx("could not set root's key and netname");
+ return (0);
+ }
+ return (1);
+ }
+ /*
+ * Decrypt yellow pages publickey entry to get secret key
+ */
+ passwd = getpass("root password:");
+ passwd2des(passwd, (char *)master);
+ getnetname(name);
+ if (!getsecretkey(name, secret, passwd)) {
+ warnx("can't find %s's secret key", name);
+ return (0);
+ }
+ if (secret[0] == 0) {
+ warnx("password does not decrypt secret key for %s", name);
+ return (0);
+ }
+ (void) pk_setkey(0, secret);
+ /*
+ * Store it for future use in $ROOTKEY, if possible
+ */
+ fd = open(ROOTKEY, O_WRONLY|O_TRUNC|O_CREAT, 0);
+ if (fd > 0) {
+ char newline = '\n';
+
+ write(fd, secret, strlen(secret));
+ write(fd, &newline, sizeof (newline));
+ close(fd);
+ }
+ return (1);
+}
+
+/*
+ * Procedures to implement RPC service
+ */
+char *
+strstatus(status)
+ keystatus status;
+{
+ switch (status) {
+ case KEY_SUCCESS:
+ return ("KEY_SUCCESS");
+ case KEY_NOSECRET:
+ return ("KEY_NOSECRET");
+ case KEY_UNKNOWN:
+ return ("KEY_UNKNOWN");
+ case KEY_SYSTEMERR:
+ return ("KEY_SYSTEMERR");
+ default:
+ return ("(bad result code)");
+ }
+}
+
+keystatus *
+key_set_1_svc_prog(uid, key)
+ uid_t uid;
+ keybuf key;
+{
+ static keystatus status;
+
+ if (debugging) {
+ (void) fprintf(stderr, "set(%u, %.*s) = ", uid,
+ (int) sizeof (keybuf), key);
+ }
+ status = pk_setkey(uid, key);
+ if (debugging) {
+ (void) fprintf(stderr, "%s\n", strstatus(status));
+ (void) fflush(stderr);
+ }
+ return (&status);
+}
+
+cryptkeyres *
+key_encrypt_pk_2_svc_prog(uid, arg)
+ uid_t uid;
+ cryptkeyarg2 *arg;
+{
+ static cryptkeyres res;
+
+ if (debugging) {
+ (void) fprintf(stderr, "encrypt(%u, %s, %08x%08x) = ", uid,
+ arg->remotename, arg->deskey.key.high,
+ arg->deskey.key.low);
+ }
+ res.cryptkeyres_u.deskey = arg->deskey;
+ res.status = pk_encrypt(uid, arg->remotename, &(arg->remotekey),
+ &res.cryptkeyres_u.deskey);
+ if (debugging) {
+ if (res.status == KEY_SUCCESS) {
+ (void) fprintf(stderr, "%08x%08x\n",
+ res.cryptkeyres_u.deskey.key.high,
+ res.cryptkeyres_u.deskey.key.low);
+ } else {
+ (void) fprintf(stderr, "%s\n", strstatus(res.status));
+ }
+ (void) fflush(stderr);
+ }
+ return (&res);
+}
+
+cryptkeyres *
+key_decrypt_pk_2_svc_prog(uid, arg)
+ uid_t uid;
+ cryptkeyarg2 *arg;
+{
+ static cryptkeyres res;
+
+ if (debugging) {
+ (void) fprintf(stderr, "decrypt(%u, %s, %08x%08x) = ", uid,
+ arg->remotename, arg->deskey.key.high,
+ arg->deskey.key.low);
+ }
+ res.cryptkeyres_u.deskey = arg->deskey;
+ res.status = pk_decrypt(uid, arg->remotename, &(arg->remotekey),
+ &res.cryptkeyres_u.deskey);
+ if (debugging) {
+ if (res.status == KEY_SUCCESS) {
+ (void) fprintf(stderr, "%08x%08x\n",
+ res.cryptkeyres_u.deskey.key.high,
+ res.cryptkeyres_u.deskey.key.low);
+ } else {
+ (void) fprintf(stderr, "%s\n", strstatus(res.status));
+ }
+ (void) fflush(stderr);
+ }
+ return (&res);
+}
+
+keystatus *
+key_net_put_2_svc_prog(uid, arg)
+ uid_t uid;
+ key_netstarg *arg;
+{
+ static keystatus status;
+
+ if (debugging) {
+ (void) fprintf(stderr, "net_put(%s, %.*s, %.*s) = ",
+ arg->st_netname, (int)sizeof (arg->st_pub_key),
+ arg->st_pub_key, (int)sizeof (arg->st_priv_key),
+ arg->st_priv_key);
+ };
+
+ status = pk_netput(uid, arg);
+
+ if (debugging) {
+ (void) fprintf(stderr, "%s\n", strstatus(status));
+ (void) fflush(stderr);
+ }
+
+ return (&status);
+}
+
+key_netstres *
+key_net_get_2_svc_prog(uid, arg)
+ uid_t uid;
+ void *arg;
+{
+ static key_netstres keynetname;
+
+ if (debugging)
+ (void) fprintf(stderr, "net_get(%u) = ", uid);
+
+ keynetname.status = pk_netget(uid, &keynetname.key_netstres_u.knet);
+ if (debugging) {
+ if (keynetname.status == KEY_SUCCESS) {
+ fprintf(stderr, "<%s, %.*s, %.*s>\n",
+ keynetname.key_netstres_u.knet.st_netname,
+ (int)sizeof (keynetname.key_netstres_u.knet.st_pub_key),
+ keynetname.key_netstres_u.knet.st_pub_key,
+ (int)sizeof (keynetname.key_netstres_u.knet.st_priv_key),
+ keynetname.key_netstres_u.knet.st_priv_key);
+ } else {
+ (void) fprintf(stderr, "NOT FOUND\n");
+ }
+ (void) fflush(stderr);
+ }
+
+ return (&keynetname);
+
+}
+
+cryptkeyres *
+key_get_conv_2_svc_prog(uid, arg)
+ uid_t uid;
+ keybuf arg;
+{
+ static cryptkeyres res;
+
+ if (debugging)
+ (void) fprintf(stderr, "get_conv(%u, %.*s) = ", uid,
+ (int)sizeof (keybuf), arg);
+
+
+ res.status = pk_get_conv_key(uid, arg, &res);
+
+ if (debugging) {
+ if (res.status == KEY_SUCCESS) {
+ (void) fprintf(stderr, "%08x%08x\n",
+ res.cryptkeyres_u.deskey.key.high,
+ res.cryptkeyres_u.deskey.key.low);
+ } else {
+ (void) fprintf(stderr, "%s\n", strstatus(res.status));
+ }
+ (void) fflush(stderr);
+ }
+ return (&res);
+}
+
+
+cryptkeyres *
+key_encrypt_1_svc_prog(uid, arg)
+ uid_t uid;
+ cryptkeyarg *arg;
+{
+ static cryptkeyres res;
+
+ if (debugging) {
+ (void) fprintf(stderr, "encrypt(%u, %s, %08x%08x) = ", uid,
+ arg->remotename, arg->deskey.key.high,
+ arg->deskey.key.low);
+ }
+ res.cryptkeyres_u.deskey = arg->deskey;
+ res.status = pk_encrypt(uid, arg->remotename, NULL,
+ &res.cryptkeyres_u.deskey);
+ if (debugging) {
+ if (res.status == KEY_SUCCESS) {
+ (void) fprintf(stderr, "%08x%08x\n",
+ res.cryptkeyres_u.deskey.key.high,
+ res.cryptkeyres_u.deskey.key.low);
+ } else {
+ (void) fprintf(stderr, "%s\n", strstatus(res.status));
+ }
+ (void) fflush(stderr);
+ }
+ return (&res);
+}
+
+cryptkeyres *
+key_decrypt_1_svc_prog(uid, arg)
+ uid_t uid;
+ cryptkeyarg *arg;
+{
+ static cryptkeyres res;
+
+ if (debugging) {
+ (void) fprintf(stderr, "decrypt(%u, %s, %08x%08x) = ", uid,
+ arg->remotename, arg->deskey.key.high,
+ arg->deskey.key.low);
+ }
+ res.cryptkeyres_u.deskey = arg->deskey;
+ res.status = pk_decrypt(uid, arg->remotename, NULL,
+ &res.cryptkeyres_u.deskey);
+ if (debugging) {
+ if (res.status == KEY_SUCCESS) {
+ (void) fprintf(stderr, "%08x%08x\n",
+ res.cryptkeyres_u.deskey.key.high,
+ res.cryptkeyres_u.deskey.key.low);
+ } else {
+ (void) fprintf(stderr, "%s\n", strstatus(res.status));
+ }
+ (void) fflush(stderr);
+ }
+ return (&res);
+}
+
+/* ARGSUSED */
+des_block *
+key_gen_1_svc_prog(v, s)
+ void *v;
+ struct svc_req *s;
+{
+ struct timeval time;
+ static des_block keygen;
+ static des_block key;
+
+ (void)gettimeofday(&time, NULL);
+ keygen.key.high += (time.tv_sec ^ time.tv_usec);
+ keygen.key.low += (time.tv_sec ^ time.tv_usec);
+ ecb_crypt((char *)&masterkey, (char *)&keygen, sizeof (keygen),
+ DES_ENCRYPT | DES_HW);
+ key = keygen;
+ des_setparity((char *)&key);
+ if (debugging) {
+ (void) fprintf(stderr, "gen() = %08x%08x\n", key.key.high,
+ key.key.low);
+ (void) fflush(stderr);
+ }
+ return (&key);
+}
+
+getcredres *
+key_getcred_1_svc_prog(uid, name)
+ uid_t uid;
+ netnamestr *name;
+{
+ static getcredres res;
+ static u_int gids[NGROUPS];
+ struct unixcred *cred;
+
+ cred = &res.getcredres_u.cred;
+ cred->gids.gids_val = gids;
+ if (!netname2user(*name, (uid_t *) &cred->uid, (gid_t *) &cred->gid,
+ (int *)&cred->gids.gids_len, (gid_t *)gids)) {
+ res.status = KEY_UNKNOWN;
+ } else {
+ res.status = KEY_SUCCESS;
+ }
+ if (debugging) {
+ (void) fprintf(stderr, "getcred(%s) = ", *name);
+ if (res.status == KEY_SUCCESS) {
+ (void) fprintf(stderr, "uid=%d, gid=%d, grouplen=%d\n",
+ cred->uid, cred->gid, cred->gids.gids_len);
+ } else {
+ (void) fprintf(stderr, "%s\n", strstatus(res.status));
+ }
+ (void) fflush(stderr);
+ }
+ return (&res);
+}
+
+/*
+ * RPC boilerplate
+ */
+static void
+keyprogram(rqstp, transp)
+ struct svc_req *rqstp;
+ SVCXPRT *transp;
+{
+ union {
+ keybuf key_set_1_arg;
+ cryptkeyarg key_encrypt_1_arg;
+ cryptkeyarg key_decrypt_1_arg;
+ netnamestr key_getcred_1_arg;
+ cryptkeyarg key_encrypt_2_arg;
+ cryptkeyarg key_decrypt_2_arg;
+ netnamestr key_getcred_2_arg;
+ cryptkeyarg2 key_encrypt_pk_2_arg;
+ cryptkeyarg2 key_decrypt_pk_2_arg;
+ key_netstarg key_net_put_2_arg;
+ netobj key_get_conv_2_arg;
+ } argument;
+ char *result;
+ xdrproc_t xdr_argument, xdr_result;
+ char *(*local) ();
+ uid_t uid = -1;
+ int check_auth;
+
+ switch (rqstp->rq_proc) {
+ case NULLPROC:
+ svc_sendreply(transp, (xdrproc_t)xdr_void, NULL);
+ return;
+
+ case KEY_SET:
+ xdr_argument = (xdrproc_t)xdr_keybuf;
+ xdr_result = (xdrproc_t)xdr_int;
+ local = (char *(*)()) key_set_1_svc_prog;
+ check_auth = 1;
+ break;
+
+ case KEY_ENCRYPT:
+ xdr_argument = (xdrproc_t)xdr_cryptkeyarg;
+ xdr_result = (xdrproc_t)xdr_cryptkeyres;
+ local = (char *(*)()) key_encrypt_1_svc_prog;
+ check_auth = 1;
+ break;
+
+ case KEY_DECRYPT:
+ xdr_argument = (xdrproc_t)xdr_cryptkeyarg;
+ xdr_result = (xdrproc_t)xdr_cryptkeyres;
+ local = (char *(*)()) key_decrypt_1_svc_prog;
+ check_auth = 1;
+ break;
+
+ case KEY_GEN:
+ xdr_argument = (xdrproc_t)xdr_void;
+ xdr_result = (xdrproc_t)xdr_des_block;
+ local = (char *(*)()) key_gen_1_svc_prog;
+ check_auth = 0;
+ break;
+
+ case KEY_GETCRED:
+ xdr_argument = (xdrproc_t)xdr_netnamestr;
+ xdr_result = (xdrproc_t)xdr_getcredres;
+ local = (char *(*)()) key_getcred_1_svc_prog;
+ check_auth = 0;
+ break;
+
+ case KEY_ENCRYPT_PK:
+ xdr_argument = (xdrproc_t)xdr_cryptkeyarg2;
+ xdr_result = (xdrproc_t)xdr_cryptkeyres;
+ local = (char *(*)()) key_encrypt_pk_2_svc_prog;
+ check_auth = 1;
+ break;
+
+ case KEY_DECRYPT_PK:
+ xdr_argument = (xdrproc_t)xdr_cryptkeyarg2;
+ xdr_result = (xdrproc_t)xdr_cryptkeyres;
+ local = (char *(*)()) key_decrypt_pk_2_svc_prog;
+ check_auth = 1;
+ break;
+
+
+ case KEY_NET_PUT:
+ xdr_argument = (xdrproc_t)xdr_key_netstarg;
+ xdr_result = (xdrproc_t)xdr_keystatus;
+ local = (char *(*)()) key_net_put_2_svc_prog;
+ check_auth = 1;
+ break;
+
+ case KEY_NET_GET:
+ xdr_argument = (xdrproc_t) xdr_void;
+ xdr_result = (xdrproc_t)xdr_key_netstres;
+ local = (char *(*)()) key_net_get_2_svc_prog;
+ check_auth = 1;
+ break;
+
+ case KEY_GET_CONV:
+ xdr_argument = (xdrproc_t) xdr_keybuf;
+ xdr_result = (xdrproc_t)xdr_cryptkeyres;
+ local = (char *(*)()) key_get_conv_2_svc_prog;
+ check_auth = 1;
+ break;
+
+ default:
+ svcerr_noproc(transp);
+ return;
+ }
+ if (check_auth) {
+ if (root_auth(transp, rqstp) == 0) {
+ if (debugging) {
+ (void) fprintf(stderr,
+ "not local privileged process\n");
+ }
+ svcerr_weakauth(transp);
+ return;
+ }
+ if (rqstp->rq_cred.oa_flavor != AUTH_SYS) {
+ if (debugging) {
+ (void) fprintf(stderr,
+ "not unix authentication\n");
+ }
+ svcerr_weakauth(transp);
+ return;
+ }
+ uid = ((struct authsys_parms *)rqstp->rq_clntcred)->aup_uid;
+ }
+
+ memset(&argument, 0, sizeof (argument));
+ if (!svc_getargs(transp, xdr_argument, &argument)) {
+ svcerr_decode(transp);
+ return;
+ }
+ result = (*local) (uid, &argument);
+ if (!svc_sendreply(transp, xdr_result, result)) {
+ if (debugging)
+ (void) fprintf(stderr, "unable to reply\n");
+ svcerr_systemerr(transp);
+ }
+ if (!svc_freeargs(transp, xdr_argument, &argument)) {
+ if (debugging)
+ (void) fprintf(stderr,
+ "unable to free arguments\n");
+ exit(1);
+ }
+ return;
+}
+
+static int
+root_auth(trans, rqstp)
+ SVCXPRT *trans;
+ struct svc_req *rqstp;
+{
+ uid_t uid;
+ struct sockaddr *remote;
+
+ remote = svc_getrpccaller(trans)->buf;
+ if (remote->sa_family != AF_UNIX) {
+ if (debugging)
+ fprintf(stderr, "client didn't use AF_UNIX\n");
+ return (0);
+ }
+
+ if (__rpc_get_local_uid(trans, &uid) < 0) {
+ if (debugging)
+ fprintf(stderr, "__rpc_get_local_uid failed\n");
+ return (0);
+ }
+
+ if (debugging)
+ fprintf(stderr, "local_uid %u\n", uid);
+ if (uid == 0)
+ return (1);
+ if (rqstp->rq_cred.oa_flavor == AUTH_SYS) {
+ if (((uid_t) ((struct authunix_parms *)
+ rqstp->rq_clntcred)->aup_uid)
+ == uid) {
+ return (1);
+ } else {
+ if (debugging)
+ fprintf(stderr,
+ "local_uid %u mismatches auth %u\n", uid,
+((uid_t) ((struct authunix_parms *)rqstp->rq_clntcred)->aup_uid));
+ return (0);
+ }
+ } else {
+ if (debugging)
+ fprintf(stderr, "Not auth sys\n");
+ return (0);
+ }
+}
+
+static void
+usage()
+{
+ (void) fprintf(stderr,
+ "usage: keyserv [-n] [-D] [-d] [-v] [-p path]\n");
+ (void) fprintf(stderr, "-d disables the use of default keys\n");
+ exit(1);
+}
diff --git a/usr.sbin/keyserv/keyserv.h b/usr.sbin/keyserv/keyserv.h
new file mode 100644
index 0000000..07e4ab6
--- /dev/null
+++ b/usr.sbin/keyserv/keyserv.h
@@ -0,0 +1,17 @@
+/*
+ * $FreeBSD$
+ */
+extern void setmodulus(char *modx);
+
+extern keystatus pk_setkey( uid_t, keybuf );
+extern keystatus pk_encrypt( uid_t, char *, netobj *, des_block * );
+extern keystatus pk_decrypt( uid_t, char *, netobj *, des_block * );
+extern keystatus pk_netput( uid_t, key_netstarg * );
+extern keystatus pk_netget( uid_t, key_netstarg * );
+extern keystatus pk_get_conv_key( uid_t, keybuf, cryptkeyres * );
+extern void pk_nodefaultkeys( void );
+
+extern void crypt_prog_1( struct svc_req *, register SVCXPRT * );
+extern void load_des( int, char * );
+
+extern int (*_my_crypt)( char *, int, struct desparams * );
diff --git a/usr.sbin/keyserv/setkey.c b/usr.sbin/keyserv/setkey.c
new file mode 100644
index 0000000..a11d04d
--- /dev/null
+++ b/usr.sbin/keyserv/setkey.c
@@ -0,0 +1,550 @@
+/*
+ * Sun RPC is a product of Sun Microsystems, Inc. and is provided for
+ * unrestricted use provided that this legend is included on all tape
+ * media and as a part of the software program in whole or part. Users
+ * may copy or modify Sun RPC without charge, but are not authorized
+ * to license or distribute it to anyone else except as part of a product or
+ * program developed by the user.
+ *
+ * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
+ * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
+ *
+ * Sun RPC is provided with no support and without any obligation on the
+ * part of Sun Microsystems, Inc. to assist in its use, correction,
+ * modification or enhancement.
+ *
+ * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
+ * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC
+ * OR ANY PART THEREOF.
+ *
+ * In no event will Sun Microsystems, Inc. be liable for any lost revenue
+ * or profits or other special, indirect and consequential damages, even if
+ * Sun has been advised of the possibility of such damages.
+ *
+ * Sun Microsystems, Inc.
+ * 2550 Garcia Avenue
+ * Mountain View, California 94043
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)setkey.c 1.11 94/04/25 SMI";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+/*
+ * Copyright (c) 1986 - 1991 by Sun Microsystems, Inc.
+ */
+
+/*
+ * Do the real work of the keyserver.
+ * Store secret keys. Compute common keys,
+ * and use them to decrypt and encrypt DES keys.
+ * Cache the common keys, so the expensive computation is avoided.
+ */
+#include <mp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <rpc/rpc.h>
+#include <rpc/key_prot.h>
+#include <rpc/des_crypt.h>
+#include <rpc/des.h>
+#include <sys/errno.h>
+#include "keyserv.h"
+
+static MINT *MODULUS;
+static char *fetchsecretkey( uid_t );
+static void writecache( char *, char *, des_block * );
+static int readcache( char *, char *, des_block * );
+static void extractdeskey( MINT *, des_block * );
+static int storesecretkey( uid_t, keybuf );
+static keystatus pk_crypt( uid_t, char *, netobj *, des_block *, int);
+static int nodefaultkeys = 0;
+
+
+/*
+ * prohibit the nobody key on this machine k (the -d flag)
+ */
+void
+pk_nodefaultkeys()
+{
+ nodefaultkeys = 1;
+}
+
+/*
+ * Set the modulus for all our Diffie-Hellman operations
+ */
+void
+setmodulus(modx)
+ char *modx;
+{
+ MODULUS = mp_xtom(modx);
+}
+
+/*
+ * Set the secretkey key for this uid
+ */
+keystatus
+pk_setkey(uid, skey)
+ uid_t uid;
+ keybuf skey;
+{
+ if (!storesecretkey(uid, skey)) {
+ return (KEY_SYSTEMERR);
+ }
+ return (KEY_SUCCESS);
+}
+
+/*
+ * Encrypt the key using the public key associated with remote_name and the
+ * secret key associated with uid.
+ */
+keystatus
+pk_encrypt(uid, remote_name, remote_key, key)
+ uid_t uid;
+ char *remote_name;
+ netobj *remote_key;
+ des_block *key;
+{
+ return (pk_crypt(uid, remote_name, remote_key, key, DES_ENCRYPT));
+}
+
+/*
+ * Decrypt the key using the public key associated with remote_name and the
+ * secret key associated with uid.
+ */
+keystatus
+pk_decrypt(uid, remote_name, remote_key, key)
+ uid_t uid;
+ char *remote_name;
+ netobj *remote_key;
+ des_block *key;
+{
+ return (pk_crypt(uid, remote_name, remote_key, key, DES_DECRYPT));
+}
+
+static int store_netname( uid_t, key_netstarg * );
+static int fetch_netname( uid_t, key_netstarg * );
+
+keystatus
+pk_netput(uid, netstore)
+ uid_t uid;
+ key_netstarg *netstore;
+{
+ if (!store_netname(uid, netstore)) {
+ return (KEY_SYSTEMERR);
+ }
+ return (KEY_SUCCESS);
+}
+
+keystatus
+pk_netget(uid, netstore)
+ uid_t uid;
+ key_netstarg *netstore;
+{
+ if (!fetch_netname(uid, netstore)) {
+ return (KEY_SYSTEMERR);
+ }
+ return (KEY_SUCCESS);
+}
+
+
+/*
+ * Do the work of pk_encrypt && pk_decrypt
+ */
+static keystatus
+pk_crypt(uid, remote_name, remote_key, key, mode)
+ uid_t uid;
+ char *remote_name;
+ netobj *remote_key;
+ des_block *key;
+ int mode;
+{
+ char *xsecret;
+ char xpublic[1024];
+ char xsecret_hold[1024];
+ des_block deskey;
+ int err;
+ MINT *public;
+ MINT *secret;
+ MINT *common;
+ char zero[8];
+
+ xsecret = fetchsecretkey(uid);
+ if (xsecret == NULL || xsecret[0] == 0) {
+ memset(zero, 0, sizeof (zero));
+ xsecret = xsecret_hold;
+ if (nodefaultkeys)
+ return (KEY_NOSECRET);
+
+ if (!getsecretkey("nobody", xsecret, zero) || xsecret[0] == 0) {
+ return (KEY_NOSECRET);
+ }
+ }
+ if (remote_key) {
+ memcpy(xpublic, remote_key->n_bytes, remote_key->n_len);
+ } else {
+ bzero((char *)&xpublic, sizeof(xpublic));
+ if (!getpublickey(remote_name, xpublic)) {
+ if (nodefaultkeys || !getpublickey("nobody", xpublic))
+ return (KEY_UNKNOWN);
+ }
+ }
+
+ if (!readcache(xpublic, xsecret, &deskey)) {
+ public = mp_xtom(xpublic);
+ secret = mp_xtom(xsecret);
+ /* Sanity Check on public and private keys */
+ if ((public == NULL) || (secret == NULL))
+ return (KEY_SYSTEMERR);
+
+ common = mp_itom(0);
+ mp_pow(public, secret, MODULUS, common);
+ extractdeskey(common, &deskey);
+ writecache(xpublic, xsecret, &deskey);
+ mp_mfree(secret);
+ mp_mfree(public);
+ mp_mfree(common);
+ }
+ err = ecb_crypt((char *)&deskey, (char *)key, sizeof (des_block),
+ DES_HW | mode);
+ if (DES_FAILED(err)) {
+ return (KEY_SYSTEMERR);
+ }
+ return (KEY_SUCCESS);
+}
+
+keystatus
+pk_get_conv_key(uid, xpublic, result)
+ uid_t uid;
+ keybuf xpublic;
+ cryptkeyres *result;
+{
+ char *xsecret;
+ char xsecret_hold[1024];
+ MINT *public;
+ MINT *secret;
+ MINT *common;
+ char zero[8];
+
+
+ xsecret = fetchsecretkey(uid);
+
+ if (xsecret == NULL || xsecret[0] == 0) {
+ memset(zero, 0, sizeof (zero));
+ xsecret = xsecret_hold;
+ if (nodefaultkeys)
+ return (KEY_NOSECRET);
+
+ if (!getsecretkey("nobody", xsecret, zero) ||
+ xsecret[0] == 0)
+ return (KEY_NOSECRET);
+ }
+
+ if (!readcache(xpublic, xsecret, &result->cryptkeyres_u.deskey)) {
+ public = mp_xtom(xpublic);
+ secret = mp_xtom(xsecret);
+ /* Sanity Check on public and private keys */
+ if ((public == NULL) || (secret == NULL))
+ return (KEY_SYSTEMERR);
+
+ common = mp_itom(0);
+ mp_pow(public, secret, MODULUS, common);
+ extractdeskey(common, &result->cryptkeyres_u.deskey);
+ writecache(xpublic, xsecret, &result->cryptkeyres_u.deskey);
+ mp_mfree(secret);
+ mp_mfree(public);
+ mp_mfree(common);
+ }
+
+ return (KEY_SUCCESS);
+}
+
+/*
+ * Choose middle 64 bits of the common key to use as our des key, possibly
+ * overwriting the lower order bits by setting parity.
+ */
+static void
+extractdeskey(ck, deskey)
+ MINT *ck;
+ des_block *deskey;
+{
+ MINT *a;
+ short r;
+ int i;
+ short base = (1 << 8);
+ char *k;
+
+ a = mp_itom(0);
+#ifdef SOLARIS_MP
+ _mp_move(ck, a);
+#else
+ mp_move(ck, a);
+#endif
+ for (i = 0; i < ((KEYSIZE - 64) / 2) / 8; i++) {
+ mp_sdiv(a, base, a, &r);
+ }
+ k = deskey->c;
+ for (i = 0; i < 8; i++) {
+ mp_sdiv(a, base, a, &r);
+ *k++ = r;
+ }
+ mp_mfree(a);
+ des_setparity((char *)deskey);
+}
+
+/*
+ * Key storage management
+ */
+
+#define KEY_ONLY 0
+#define KEY_NAME 1
+struct secretkey_netname_list {
+ uid_t uid;
+ key_netstarg keynetdata;
+ u_char sc_flag;
+ struct secretkey_netname_list *next;
+};
+
+
+
+static struct secretkey_netname_list *g_secretkey_netname;
+
+/*
+ * Store the keys and netname for this uid
+ */
+static int
+store_netname(uid, netstore)
+ uid_t uid;
+ key_netstarg *netstore;
+{
+ struct secretkey_netname_list *new;
+ struct secretkey_netname_list **l;
+
+ for (l = &g_secretkey_netname; *l != NULL && (*l)->uid != uid;
+ l = &(*l)->next) {
+ }
+ if (*l == NULL) {
+ new = (struct secretkey_netname_list *)malloc(sizeof (*new));
+ if (new == NULL) {
+ return (0);
+ }
+ new->uid = uid;
+ new->next = NULL;
+ *l = new;
+ } else {
+ new = *l;
+ if (new->keynetdata.st_netname)
+ (void) free (new->keynetdata.st_netname);
+ }
+ memcpy(new->keynetdata.st_priv_key, netstore->st_priv_key,
+ HEXKEYBYTES);
+ memcpy(new->keynetdata.st_pub_key, netstore->st_pub_key, HEXKEYBYTES);
+
+ if (netstore->st_netname)
+ new->keynetdata.st_netname = strdup(netstore->st_netname);
+ else
+ new->keynetdata.st_netname = (char *)NULL;
+ new->sc_flag = KEY_NAME;
+ return (1);
+
+}
+
+/*
+ * Fetch the keys and netname for this uid
+ */
+
+static int
+fetch_netname(uid, key_netst)
+ uid_t uid;
+ struct key_netstarg *key_netst;
+{
+ struct secretkey_netname_list *l;
+
+ for (l = g_secretkey_netname; l != NULL; l = l->next) {
+ if ((l->uid == uid) && (l->sc_flag == KEY_NAME)){
+
+ memcpy(key_netst->st_priv_key,
+ l->keynetdata.st_priv_key, HEXKEYBYTES);
+
+ memcpy(key_netst->st_pub_key,
+ l->keynetdata.st_pub_key, HEXKEYBYTES);
+
+ if (l->keynetdata.st_netname)
+ key_netst->st_netname =
+ strdup(l->keynetdata.st_netname);
+ else
+ key_netst->st_netname = NULL;
+ return (1);
+ }
+ }
+
+ return (0);
+}
+
+static char *
+fetchsecretkey(uid)
+ uid_t uid;
+{
+ struct secretkey_netname_list *l;
+
+ for (l = g_secretkey_netname; l != NULL; l = l->next) {
+ if (l->uid == uid) {
+ return (l->keynetdata.st_priv_key);
+ }
+ }
+ return (NULL);
+}
+
+/*
+ * Store the secretkey for this uid
+ */
+static int
+storesecretkey(uid, key)
+ uid_t uid;
+ keybuf key;
+{
+ struct secretkey_netname_list *new;
+ struct secretkey_netname_list **l;
+
+ for (l = &g_secretkey_netname; *l != NULL && (*l)->uid != uid;
+ l = &(*l)->next) {
+ }
+ if (*l == NULL) {
+ new = (struct secretkey_netname_list *) malloc(sizeof (*new));
+ if (new == NULL) {
+ return (0);
+ }
+ new->uid = uid;
+ new->sc_flag = KEY_ONLY;
+ memset(new->keynetdata.st_pub_key, 0, HEXKEYBYTES);
+ new->keynetdata.st_netname = NULL;
+ new->next = NULL;
+ *l = new;
+ } else {
+ new = *l;
+ }
+
+ memcpy(new->keynetdata.st_priv_key, key,
+ HEXKEYBYTES);
+ return (1);
+}
+
+static int
+hexdigit(val)
+ int val;
+{
+ return ("0123456789abcdef"[val]);
+}
+
+void
+bin2hex(bin, hex, size)
+ unsigned char *bin;
+ unsigned char *hex;
+ int size;
+{
+ int i;
+
+ for (i = 0; i < size; i++) {
+ *hex++ = hexdigit(*bin >> 4);
+ *hex++ = hexdigit(*bin++ & 0xf);
+ }
+}
+
+static int
+hexval(dig)
+ char dig;
+{
+ if ('0' <= dig && dig <= '9') {
+ return (dig - '0');
+ } else if ('a' <= dig && dig <= 'f') {
+ return (dig - 'a' + 10);
+ } else if ('A' <= dig && dig <= 'F') {
+ return (dig - 'A' + 10);
+ } else {
+ return (-1);
+ }
+}
+
+void
+hex2bin(hex, bin, size)
+ unsigned char *hex;
+ unsigned char *bin;
+ int size;
+{
+ int i;
+
+ for (i = 0; i < size; i++) {
+ *bin = hexval(*hex++) << 4;
+ *bin++ |= hexval(*hex++);
+ }
+}
+
+/*
+ * Exponential caching management
+ */
+struct cachekey_list {
+ keybuf secret;
+ keybuf public;
+ des_block deskey;
+ struct cachekey_list *next;
+};
+static struct cachekey_list *g_cachedkeys;
+
+/*
+ * cache result of expensive multiple precision exponential operation
+ */
+static void
+writecache(pub, sec, deskey)
+ char *pub;
+ char *sec;
+ des_block *deskey;
+{
+ struct cachekey_list *new;
+
+ new = (struct cachekey_list *) malloc(sizeof (struct cachekey_list));
+ if (new == NULL) {
+ return;
+ }
+ memcpy(new->public, pub, sizeof (keybuf));
+ memcpy(new->secret, sec, sizeof (keybuf));
+ new->deskey = *deskey;
+ new->next = g_cachedkeys;
+ g_cachedkeys = new;
+}
+
+/*
+ * Try to find the common key in the cache
+ */
+static int
+readcache(pub, sec, deskey)
+ char *pub;
+ char *sec;
+ des_block *deskey;
+{
+ struct cachekey_list *found;
+ register struct cachekey_list **l;
+
+#define cachehit(pub, sec, list) \
+ (memcmp(pub, (list)->public, sizeof (keybuf)) == 0 && \
+ memcmp(sec, (list)->secret, sizeof (keybuf)) == 0)
+
+ for (l = &g_cachedkeys; (*l) != NULL && !cachehit(pub, sec, *l);
+ l = &(*l)->next)
+ ;
+ if ((*l) == NULL) {
+ return (0);
+ }
+ found = *l;
+ (*l) = (*l)->next;
+ found->next = g_cachedkeys;
+ g_cachedkeys = found;
+ *deskey = found->deskey;
+ return (1);
+}
diff --git a/usr.sbin/kgmon/Makefile b/usr.sbin/kgmon/Makefile
new file mode 100644
index 0000000..5f6a2a3
--- /dev/null
+++ b/usr.sbin/kgmon/Makefile
@@ -0,0 +1,16 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+PROG= kgmon
+MAN= kgmon.8
+WARNS?= 2
+
+# This program may safely be run setuid-root to allow non-root
+# users to start, stop, and reset profiling buffers.
+#
+#BINOWN=root
+#BINMODE=4555
+
+LIBADD= kvm
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/kgmon/Makefile.depend b/usr.sbin/kgmon/Makefile.depend
new file mode 100644
index 0000000..34582cd
--- /dev/null
+++ b/usr.sbin/kgmon/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libelf \
+ lib/libkvm \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/kgmon/kgmon.8 b/usr.sbin/kgmon/kgmon.8
new file mode 100644
index 0000000..b783d6f
--- /dev/null
+++ b/usr.sbin/kgmon/kgmon.8
@@ -0,0 +1,129 @@
+.\" 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.
+.\"
+.\" @(#)kgmon.8 8.1 (Berkeley) 6/6/93
+.\" $FreeBSD$
+.\"
+.Dd June 6, 1993
+.Dt KGMON 8
+.Os
+.Sh NAME
+.Nm kgmon
+.Nd generate a dump of the operating system's profile buffers
+.Sh SYNOPSIS
+.Nm
+.Op Fl Bbhpr
+.Op Fl M Ar core
+.Op Fl N Ar system
+.Sh DESCRIPTION
+The
+.Nm
+utility is used when profiling the operating system.
+When no arguments are supplied,
+.Nm
+indicates the state of operating system profiling as running,
+off, or not configured.
+(see
+.Xr config 8 )
+If the
+.Fl p
+flag is specified,
+.Nm
+extracts profile data from the operating system and produces a
+.Pa gmon.out
+file suitable for later analysis by
+.Xr gprof 1 .
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl B
+Resume the collection of high resolution profile data.
+.It Fl b
+Resume the collection of low resolution profile data.
+.It Fl h
+Stop the collection of profile data.
+.It Fl p
+Dump the contents of the profile buffers into a
+.Pa gmon.out
+file.
+.It Fl r
+Reset all the profile buffers.
+If the
+.Fl p
+flag is also specified, the
+.Pa gmon.out
+file is generated before the buffers are reset.
+.It Fl M
+Extract values associated with the name list from the specified core
+instead of the default
+.Pa /dev/kmem .
+.It Fl N
+Extract the name list from the specified system instead of the
+default
+.Pa /boot/kernel/kernel .
+.El
+.Pp
+If neither
+.Fl B
+nor
+.Fl b
+nor
+.Fl h
+is specified, the state of profiling collection remains unchanged.
+For example, if the
+.Fl p
+flag is specified and profile data is being collected,
+profiling will be momentarily suspended,
+the operating system profile buffers will be dumped,
+and profiling will be immediately resumed.
+.Pp
+The profile buffers should be reset when the resolution
+of the profile data is changed.
+.Sh FILES
+.Bl -tag -width /boot/kernel/kernel -compact
+.It Pa /boot/kernel/kernel
+the default system
+.It Pa /dev/kmem
+the default memory
+.El
+.Sh DIAGNOSTICS
+Users with only read permission on
+.Pa /dev/kmem
+cannot change the state
+of profiling collection.
+They can get a
+.Pa gmon.out
+file with the warning that the data may be
+inconsistent if profiling is in progress.
+.Sh SEE ALSO
+.Xr gprof 1 ,
+.Xr config 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.2 .
diff --git a/usr.sbin/kgmon/kgmon.c b/usr.sbin/kgmon/kgmon.c
new file mode 100644
index 0000000..3bc5351
--- /dev/null
+++ b/usr.sbin/kgmon/kgmon.c
@@ -0,0 +1,528 @@
+/*
+ * 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[] = "@(#)kgmon.c 8.1 (Berkeley) 6/6/93";
+#endif
+#endif /* not lint */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/file.h>
+#include <sys/time.h>
+#include <sys/sysctl.h>
+#include <sys/gmon.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <kvm.h>
+#include <limits.h>
+#include <nlist.h>
+#include <paths.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+struct nlist nl[] = {
+#define N_GMONPARAM 0
+ { "__gmonparam" },
+#define N_PROFHZ 1
+ { "_profhz" },
+ { NULL },
+};
+
+struct kvmvars {
+ kvm_t *kd;
+ struct gmonparam gpm;
+};
+
+int Bflag, bflag, hflag, kflag, rflag, pflag;
+int debug = 0;
+int getprof(struct kvmvars *);
+int getprofhz(struct kvmvars *);
+void kern_readonly(int);
+int openfiles(const char *, char *, struct kvmvars *);
+void setprof(struct kvmvars *kvp, int state);
+void dumpstate(struct kvmvars *kvp);
+void reset(struct kvmvars *kvp);
+static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ int ch, mode, disp, accessmode;
+ struct kvmvars kvmvars;
+ const char *systemname;
+ char *kmemf;
+
+ if (seteuid(getuid()) != 0) {
+ err(1, "seteuid failed\n");
+ }
+ kmemf = NULL;
+ systemname = NULL;
+ while ((ch = getopt(argc, argv, "M:N:Bbhpr")) != -1) {
+ switch((char)ch) {
+
+ case 'M':
+ kmemf = optarg;
+ kflag = 1;
+ break;
+
+ case 'N':
+ systemname = optarg;
+ break;
+
+ case 'B':
+ Bflag = 1;
+ break;
+
+ case 'b':
+ bflag = 1;
+ break;
+
+ case 'h':
+ hflag = 1;
+ break;
+
+ case 'p':
+ pflag = 1;
+ break;
+
+ case 'r':
+ rflag = 1;
+ break;
+
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+#define BACKWARD_COMPATIBILITY
+#ifdef BACKWARD_COMPATIBILITY
+ if (*argv) {
+ systemname = *argv;
+ if (*++argv) {
+ kmemf = *argv;
+ ++kflag;
+ }
+ }
+#endif
+ if (systemname == NULL)
+ systemname = getbootfile();
+ accessmode = openfiles(systemname, kmemf, &kvmvars);
+ mode = getprof(&kvmvars);
+ if (hflag)
+ disp = GMON_PROF_OFF;
+ else if (Bflag)
+ disp = GMON_PROF_HIRES;
+ else if (bflag)
+ disp = GMON_PROF_ON;
+ else
+ disp = mode;
+ if (pflag)
+ dumpstate(&kvmvars);
+ if (rflag)
+ reset(&kvmvars);
+ if (accessmode == O_RDWR)
+ setprof(&kvmvars, disp);
+ (void)fprintf(stdout, "kgmon: kernel profiling is %s.\n",
+ disp == GMON_PROF_OFF ? "off" :
+ disp == GMON_PROF_HIRES ? "running (high resolution)" :
+ disp == GMON_PROF_ON ? "running" :
+ disp == GMON_PROF_BUSY ? "busy" :
+ disp == GMON_PROF_ERROR ? "off (error)" :
+ "in an unknown state");
+ return (0);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: kgmon [-Bbhrp] [-M core] [-N system]\n");
+ exit(1);
+}
+
+/*
+ * Check that profiling is enabled and open any necessary files.
+ */
+int
+openfiles(const char *systemname, char *kmemf, struct kvmvars *kvp)
+{
+ size_t size;
+ int mib[3], state, openmode;
+ char errbuf[_POSIX2_LINE_MAX];
+
+ if (!kflag) {
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROF;
+ mib[2] = GPROF_STATE;
+ size = sizeof state;
+ if (sysctl(mib, 3, &state, &size, NULL, 0) < 0)
+ errx(20, "profiling not defined in kernel");
+ if (!(Bflag || bflag || hflag || rflag ||
+ (pflag &&
+ (state == GMON_PROF_HIRES || state == GMON_PROF_ON))))
+ return (O_RDONLY);
+ (void)seteuid(0);
+ if (sysctl(mib, 3, NULL, NULL, &state, size) >= 0)
+ return (O_RDWR);
+ (void)seteuid(getuid());
+ kern_readonly(state);
+ return (O_RDONLY);
+ }
+ openmode = (Bflag || bflag || hflag || pflag || rflag)
+ ? O_RDWR : O_RDONLY;
+ kvp->kd = kvm_openfiles(systemname, kmemf, NULL, openmode, errbuf);
+ if (kvp->kd == NULL) {
+ if (openmode == O_RDWR) {
+ openmode = O_RDONLY;
+ kvp->kd = kvm_openfiles(systemname, kmemf, NULL, O_RDONLY,
+ errbuf);
+ }
+ if (kvp->kd == NULL)
+ errx(2, "kvm_openfiles: %s", errbuf);
+ kern_readonly(GMON_PROF_ON);
+ }
+ if (kvm_nlist(kvp->kd, nl) < 0)
+ errx(3, "%s: no namelist", systemname);
+ if (!nl[N_GMONPARAM].n_value)
+ errx(20, "profiling not defined in kernel");
+ return (openmode);
+}
+
+/*
+ * Suppress options that require a writable kernel.
+ */
+void
+kern_readonly(int mode)
+{
+
+ (void)fprintf(stderr, "kgmon: kernel read-only: ");
+ if (pflag && (mode == GMON_PROF_HIRES || mode == GMON_PROF_ON))
+ (void)fprintf(stderr, "data may be inconsistent\n");
+ if (rflag)
+ (void)fprintf(stderr, "-r suppressed\n");
+ if (Bflag)
+ (void)fprintf(stderr, "-B suppressed\n");
+ if (bflag)
+ (void)fprintf(stderr, "-b suppressed\n");
+ if (hflag)
+ (void)fprintf(stderr, "-h suppressed\n");
+ rflag = Bflag = bflag = hflag = 0;
+}
+
+/*
+ * Get the state of kernel profiling.
+ */
+int
+getprof(struct kvmvars *kvp)
+{
+ size_t size;
+ int mib[3];
+
+ if (kflag) {
+ size = kvm_read(kvp->kd, nl[N_GMONPARAM].n_value, &kvp->gpm,
+ sizeof kvp->gpm);
+ } else {
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROF;
+ mib[2] = GPROF_GMONPARAM;
+ size = sizeof kvp->gpm;
+ if (sysctl(mib, 3, &kvp->gpm, &size, NULL, 0) < 0)
+ size = 0;
+ }
+
+ /*
+ * Accept certain undersized "structs" from old kernels. We need
+ * everything up to hashfraction, and want profrate and
+ * histcounter_type. Assume that the kernel doesn't put garbage
+ * in any padding that is returned instead of profrate and
+ * histcounter_type. This is a bad assumption for dead kernels,
+ * since kvm_read() will normally return garbage for bytes beyond
+ * the end of the actual kernel struct, if any.
+ */
+ if (size < offsetof(struct gmonparam, hashfraction) +
+ sizeof(kvp->gpm.hashfraction) || size > sizeof(kvp->gpm))
+ errx(4, "cannot get gmonparam: %s",
+ kflag ? kvm_geterr(kvp->kd) : strerror(errno));
+ bzero((char *)&kvp->gpm + size, sizeof(kvp->gpm) - size);
+ if (kvp->gpm.profrate == 0)
+ kvp->gpm.profrate = getprofhz(kvp);
+#ifdef __i386__
+ if (kvp->gpm.histcounter_type == 0) {
+ /*
+ * This fixup only works for not-so-old i386 kernels. The
+ * magic 16 is the kernel FUNCTION_ALIGNMENT. 64-bit
+ * counters are signed; smaller counters are unsigned.
+ */
+ kvp->gpm.histcounter_type = 16 /
+ (kvp->gpm.textsize / kvp->gpm.kcountsize) * CHAR_BIT;
+ if (kvp->gpm.histcounter_type == 64)
+ kvp->gpm.histcounter_type = -64;
+ }
+#endif
+
+ return (kvp->gpm.state);
+}
+
+/*
+ * Enable or disable kernel profiling according to the state variable.
+ */
+void
+setprof(struct kvmvars *kvp, int state)
+{
+ struct gmonparam *p = (struct gmonparam *)nl[N_GMONPARAM].n_value;
+ size_t sz;
+ int mib[3], oldstate;
+
+ sz = sizeof(state);
+ if (!kflag) {
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROF;
+ mib[2] = GPROF_STATE;
+ if (sysctl(mib, 3, &oldstate, &sz, NULL, 0) < 0)
+ goto bad;
+ if (oldstate == state)
+ return;
+ (void)seteuid(0);
+ if (sysctl(mib, 3, NULL, NULL, &state, sz) >= 0) {
+ (void)seteuid(getuid());
+ return;
+ }
+ (void)seteuid(getuid());
+ } else if (kvm_write(kvp->kd, (u_long)&p->state, (void *)&state, sz)
+ == (ssize_t)sz)
+ return;
+bad:
+ warnx("warning: cannot turn profiling %s",
+ state == GMON_PROF_OFF ? "off" : "on");
+}
+
+/*
+ * Build the gmon.out file.
+ */
+void
+dumpstate(struct kvmvars *kvp)
+{
+ register FILE *fp;
+ struct rawarc rawarc;
+ struct tostruct *tos;
+ u_long frompc;
+ u_short *froms, *tickbuf;
+ size_t i;
+ int mib[3];
+ struct gmonhdr h;
+ int fromindex, endfrom, toindex;
+
+ setprof(kvp, GMON_PROF_OFF);
+ fp = fopen("gmon.out", "w");
+ if (fp == 0) {
+ warn("gmon.out");
+ return;
+ }
+
+ /*
+ * Build the gmon header and write it to a file.
+ */
+ bzero(&h, sizeof(h));
+ h.lpc = kvp->gpm.lowpc;
+ h.hpc = kvp->gpm.highpc;
+ h.ncnt = kvp->gpm.kcountsize + sizeof(h);
+ h.version = GMONVERSION;
+ h.profrate = kvp->gpm.profrate;
+ h.histcounter_type = kvp->gpm.histcounter_type;
+ fwrite((char *)&h, sizeof(h), 1, fp);
+
+ /*
+ * Write out the tick buffer.
+ */
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROF;
+ if ((tickbuf = (u_short *)malloc(kvp->gpm.kcountsize)) == NULL)
+ errx(5, "cannot allocate kcount space");
+ if (kflag) {
+ i = kvm_read(kvp->kd, (u_long)kvp->gpm.kcount, (void *)tickbuf,
+ kvp->gpm.kcountsize);
+ } else {
+ mib[2] = GPROF_COUNT;
+ i = kvp->gpm.kcountsize;
+ if (sysctl(mib, 3, tickbuf, &i, NULL, 0) < 0)
+ i = 0;
+ }
+ if (i != kvp->gpm.kcountsize)
+ errx(6, "read ticks: read %lu, got %ld: %s",
+ kvp->gpm.kcountsize, (long)i,
+ kflag ? kvm_geterr(kvp->kd) : strerror(errno));
+ if ((fwrite(tickbuf, kvp->gpm.kcountsize, 1, fp)) != 1)
+ err(7, "writing tocks to gmon.out");
+ free(tickbuf);
+
+ /*
+ * Write out the arc info.
+ */
+ if ((froms = (u_short *)malloc(kvp->gpm.fromssize)) == NULL)
+ errx(8, "cannot allocate froms space");
+ if (kflag) {
+ i = kvm_read(kvp->kd, (u_long)kvp->gpm.froms, (void *)froms,
+ kvp->gpm.fromssize);
+ } else {
+ mib[2] = GPROF_FROMS;
+ i = kvp->gpm.fromssize;
+ if (sysctl(mib, 3, froms, &i, NULL, 0) < 0)
+ i = 0;
+ }
+ if (i != kvp->gpm.fromssize)
+ errx(9, "read froms: read %lu, got %ld: %s",
+ kvp->gpm.fromssize, (long)i,
+ kflag ? kvm_geterr(kvp->kd) : strerror(errno));
+ if ((tos = (struct tostruct *)malloc(kvp->gpm.tossize)) == NULL)
+ errx(10, "cannot allocate tos space");
+ if (kflag) {
+ i = kvm_read(kvp->kd, (u_long)kvp->gpm.tos, (void *)tos,
+ kvp->gpm.tossize);
+ } else {
+ mib[2] = GPROF_TOS;
+ i = kvp->gpm.tossize;
+ if (sysctl(mib, 3, tos, &i, NULL, 0) < 0)
+ i = 0;
+ }
+ if (i != kvp->gpm.tossize)
+ errx(11, "read tos: read %lu, got %ld: %s",
+ kvp->gpm.tossize, (long)i,
+ kflag ? kvm_geterr(kvp->kd) : strerror(errno));
+ if (debug)
+ warnx("lowpc 0x%lx, textsize 0x%lx",
+ (unsigned long)kvp->gpm.lowpc, kvp->gpm.textsize);
+ endfrom = kvp->gpm.fromssize / sizeof(*froms);
+ for (fromindex = 0; fromindex < endfrom; ++fromindex) {
+ if (froms[fromindex] == 0)
+ continue;
+ frompc = (u_long)kvp->gpm.lowpc +
+ (fromindex * kvp->gpm.hashfraction * sizeof(*froms));
+ for (toindex = froms[fromindex]; toindex != 0;
+ toindex = tos[toindex].link) {
+ if (debug)
+ warnx("[mcleanup] frompc 0x%lx selfpc 0x%lx "
+ "count %ld", frompc, tos[toindex].selfpc,
+ tos[toindex].count);
+ rawarc.raw_frompc = frompc;
+ rawarc.raw_selfpc = (u_long)tos[toindex].selfpc;
+ rawarc.raw_count = tos[toindex].count;
+ fwrite((char *)&rawarc, sizeof(rawarc), 1, fp);
+ }
+ }
+ fclose(fp);
+}
+
+/*
+ * Get the profiling rate.
+ */
+int
+getprofhz(struct kvmvars *kvp)
+{
+ size_t size;
+ int mib[2], profrate;
+ struct clockinfo clockrate;
+
+ if (kflag) {
+ profrate = 1;
+ if (kvm_read(kvp->kd, nl[N_PROFHZ].n_value, &profrate,
+ sizeof profrate) != sizeof profrate)
+ warnx("get clockrate: %s", kvm_geterr(kvp->kd));
+ return (profrate);
+ }
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_CLOCKRATE;
+ clockrate.profhz = 1;
+ size = sizeof clockrate;
+ if (sysctl(mib, 2, &clockrate, &size, NULL, 0) < 0)
+ warn("get clockrate");
+ return (clockrate.profhz);
+}
+
+/*
+ * Reset the kernel profiling date structures.
+ */
+void
+reset(struct kvmvars *kvp)
+{
+ char *zbuf;
+ u_long biggest;
+ int mib[3];
+
+ setprof(kvp, GMON_PROF_OFF);
+
+ biggest = kvp->gpm.kcountsize;
+ if (kvp->gpm.fromssize > biggest)
+ biggest = kvp->gpm.fromssize;
+ if (kvp->gpm.tossize > biggest)
+ biggest = kvp->gpm.tossize;
+ if ((zbuf = (char *)malloc(biggest)) == NULL)
+ errx(12, "cannot allocate zbuf space");
+ bzero(zbuf, biggest);
+ if (kflag) {
+ if (kvm_write(kvp->kd, (u_long)kvp->gpm.kcount, zbuf,
+ kvp->gpm.kcountsize) != (ssize_t)kvp->gpm.kcountsize)
+ errx(13, "tickbuf zero: %s", kvm_geterr(kvp->kd));
+ if (kvm_write(kvp->kd, (u_long)kvp->gpm.froms, zbuf,
+ kvp->gpm.fromssize) != (ssize_t)kvp->gpm.fromssize)
+ errx(14, "froms zero: %s", kvm_geterr(kvp->kd));
+ if (kvm_write(kvp->kd, (u_long)kvp->gpm.tos, zbuf,
+ kvp->gpm.tossize) != (ssize_t)kvp->gpm.tossize)
+ errx(15, "tos zero: %s", kvm_geterr(kvp->kd));
+ return;
+ }
+ (void)seteuid(0);
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROF;
+ mib[2] = GPROF_COUNT;
+ if (sysctl(mib, 3, NULL, NULL, zbuf, kvp->gpm.kcountsize) < 0)
+ err(13, "tickbuf zero");
+ mib[2] = GPROF_FROMS;
+ if (sysctl(mib, 3, NULL, NULL, zbuf, kvp->gpm.fromssize) < 0)
+ err(14, "froms zero");
+ mib[2] = GPROF_TOS;
+ if (sysctl(mib, 3, NULL, NULL, zbuf, kvp->gpm.tossize) < 0)
+ err(15, "tos zero");
+ (void)seteuid(getuid());
+ free(zbuf);
+}
diff --git a/usr.sbin/kgzip/Makefile b/usr.sbin/kgzip/Makefile
new file mode 100644
index 0000000..013c65f
--- /dev/null
+++ b/usr.sbin/kgzip/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PROG= kgzip
+MAN= kgzip.8
+SRCS= kgzip.c aouthdr.c elfhdr.c kgzcmp.c kgzld.c xio.c
+
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/kgzip/Makefile.depend b/usr.sbin/kgzip/Makefile.depend
new file mode 100644
index 0000000..79eb58b
--- /dev/null
+++ b/usr.sbin/kgzip/Makefile.depend
@@ -0,0 +1,16 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/kgzip/aouthdr.c b/usr.sbin/kgzip/aouthdr.c
new file mode 100644
index 0000000..27541e6
--- /dev/null
+++ b/usr.sbin/kgzip/aouthdr.c
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2000 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$
+ */
+
+#include <stddef.h>
+#include "aouthdr.h"
+
+#define KGZ_FIX_NSIZE 0 /* Run-time fixup */
+
+const struct kgz_aouthdr0 aouthdr0 = {
+ /* a.out header */
+ {
+ MID_I386 << 020 | OMAGIC, /* a_midmag */
+ 0, /* a_text */
+ sizeof(struct kgz_hdr) + KGZ_FIX_NSIZE, /* a_data */
+ 0, /* a_bss */
+ sizeof(struct nlist) * KGZ__STNUM, /* a_syms */
+ 0, /* a_entry */
+ 0, /* a_trsize */
+ 0 /* a_drsize */
+ }
+};
+
+const struct kgz_aouthdr1 aouthdr1 = {
+ /* Symbol table */
+ {
+ {
+ {
+ (char *)offsetof(struct kgz__strtab,
+ kgz) /* n_un */
+ },
+ N_DATA | N_EXT, /* n_type */
+ AUX_OBJECT, /* n_other */
+ 0, /* n_desc */
+ 0 /* n_value */
+ },
+ {
+ {
+ (char *)offsetof(struct kgz__strtab,
+ kgz_ndata) /* n_un */
+ },
+ N_DATA | N_EXT, /* n_type */
+ AUX_OBJECT, /* n_other */
+ 0, /* n_desc */
+ sizeof(struct kgz_hdr) /* n_value */
+ }
+ },
+ /* String table */
+ {
+ sizeof(struct kgz__strtab), /* length */
+ KGZ__STR_KGZ, /* kgz */
+ KGZ__STR_KGZ_NDATA /* kgz_ndata */
+ }
+};
diff --git a/usr.sbin/kgzip/aouthdr.h b/usr.sbin/kgzip/aouthdr.h
new file mode 100644
index 0000000..63c2dff
--- /dev/null
+++ b/usr.sbin/kgzip/aouthdr.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2000 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$
+ */
+
+#include <a.out.h>
+#include "kgz.h"
+
+/* Relocatable header: part 0 */
+struct kgz_aouthdr0 {
+ struct exec a;
+};
+
+/* Symbol table entries */
+#define KGZ__STNUM 2
+
+/* Symbol table strings */
+#define KGZ__STR_KGZ "_kgz"
+#define KGZ__STR_KGZ_NDATA "_kgz_ndata"
+
+/* String table */
+struct kgz__strtab {
+ unsigned long length;
+ char kgz[sizeof(KGZ__STR_KGZ)];
+ char kgz_ndata[sizeof(KGZ__STR_KGZ_NDATA)];
+};
+
+/* Relocatable header: part 1 */
+struct kgz_aouthdr1 {
+ struct nlist st[KGZ__STNUM];
+ struct kgz__strtab strtab;
+};
+
+extern const struct kgz_aouthdr0 aouthdr0;
+extern const struct kgz_aouthdr1 aouthdr1;
diff --git a/usr.sbin/kgzip/elfhdr.c b/usr.sbin/kgzip/elfhdr.c
new file mode 100644
index 0000000..12b1d5f
--- /dev/null
+++ b/usr.sbin/kgzip/elfhdr.c
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 1999 Global Technology Associates, 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$
+ */
+
+#include <sys/endian.h>
+#include <stddef.h>
+#include "elfhdr.h"
+
+#define KGZ_FIX_NSIZE 0 /* Run-time fixup */
+
+/*
+ * Relocatable header template.
+ */
+const struct kgz_elfhdr elfhdr = {
+ /* ELF header */
+ {
+ {
+ ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3, /* e_ident */
+ ELFCLASS32, ELFDATA2LSB, EV_CURRENT, 0,
+ 'F', 'r', 'e', 'e', 'B', 'S', 'D', 0
+ },
+ htole16(ET_EXEC), /* e_type */
+ htole16(EM_386), /* e_machine */
+ htole32(EV_CURRENT), /* e_version */
+ 0, /* e_entry */
+ 0, /* e_phoff */
+ htole32(offsetof(struct kgz_elfhdr, sh)), /* e_shoff */
+ 0, /* e_flags */
+ htole16(sizeof(Elf32_Ehdr)), /* e_ehsize */
+ 0, /* e_phentsize */
+ 0, /* e_phnum */
+ htole16(sizeof(Elf32_Shdr)), /* e_shentsize */
+ htole16(KGZ_SHNUM), /* e_shnum */
+ htole16(KGZ_SH_SHSTRTAB) /* e_shstrndx */
+ },
+ /* Section header */
+ {
+ {
+ 0, /* sh_name */
+ htole32(SHT_NULL), /* sh_type */
+ 0, /* sh_flags */
+ 0, /* sh_addr */
+ 0, /* sh_offset */
+ 0, /* sh_size */
+ htole32(SHN_UNDEF), /* sh_link */
+ 0, /* sh_info */
+ 0, /* sh_addralign */
+ 0 /* sh_entsize */
+ },
+ {
+ htole32(offsetof(struct kgz_shstrtab, symtab)), /* sh_name */
+ htole32(SHT_SYMTAB), /* sh_type */
+ 0, /* sh_flags */
+ 0, /* sh_addr */
+ htole32(offsetof(struct kgz_elfhdr, st)), /* sh_offset */
+ htole32(sizeof(Elf32_Sym) * KGZ_STNUM), /* sh_size */
+ htole32(KGZ_SH_STRTAB), /* sh_link */
+ htole32(1), /* sh_info */
+ htole32(4), /* sh_addralign */
+ htole32(sizeof(Elf32_Sym)) /* sh_entsize */
+ },
+ {
+ htole32(offsetof(struct kgz_shstrtab, shstrtab)), /* sh_name */
+ htole32(SHT_STRTAB), /* sh_type */
+ 0, /* sh_flags */
+ 0, /* sh_addr */
+ htole32(offsetof(struct kgz_elfhdr, shstrtab)), /* sh_offset */
+ htole32(sizeof(struct kgz_shstrtab)), /* sh_size */
+ htole32(SHN_UNDEF), /* sh_link */
+ 0, /* sh_info */
+ htole32(1), /* sh_addralign */
+ 0 /* sh_entsize */
+ },
+ {
+ htole32(offsetof(struct kgz_shstrtab, strtab)), /* sh_name */
+ htole32(SHT_STRTAB), /* sh_type */
+ 0, /* sh_flags */
+ 0, /* sh_addr */
+ htole32(offsetof(struct kgz_elfhdr, strtab)), /* sh_offset */
+ htole32(sizeof(struct kgz_strtab)), /* sh_size */
+ htole32(SHN_UNDEF), /* sh_link */
+ 0, /* sh_info */
+ htole32(1), /* sh_addralign */
+ 0 /* sh_entsize */
+ },
+ {
+ htole32(offsetof(struct kgz_shstrtab, data)), /* sh_name */
+ htole32(SHT_PROGBITS), /* sh_type */
+ htole32(SHF_ALLOC | SHF_WRITE), /* sh_flags */
+ 0, /* sh_addr */
+ htole32(sizeof(struct kgz_elfhdr)), /* sh_offset */
+ htole32(sizeof(struct kgz_hdr) + KGZ_FIX_NSIZE), /* sh_size */
+ htole32(SHN_UNDEF), /* sh_link */
+ 0, /* sh_info */
+ htole32(4), /* sh_addralign */
+ 0 /* sh_entsize */
+ }
+ },
+ /* Symbol table */
+ {
+ {
+ 0, /* st_name */
+ 0, /* st_value */
+ 0, /* st_size */
+ 0, /* st_info */
+ 0, /* st_other */
+ htole16(SHN_UNDEF) /* st_shndx */
+ },
+ {
+ htole32(offsetof(struct kgz_strtab, kgz)), /* st_name */
+ 0, /* st_value */
+ htole32(sizeof(struct kgz_hdr)), /* st_size */
+ ELF32_ST_INFO(STB_GLOBAL, STT_OBJECT), /* st_info */
+ 0, /* st_other */
+ htole16(KGZ_SH_DATA) /* st_shndx */
+ },
+ {
+ htole32(offsetof(struct kgz_strtab, kgz_ndata)), /* st_name */
+ htole32(sizeof(struct kgz_hdr)), /* st_value */
+ htole32(KGZ_FIX_NSIZE), /* st_size */
+ ELF32_ST_INFO(STB_GLOBAL, STT_OBJECT), /* st_info */
+ 0, /* st_other */
+ htole16(KGZ_SH_DATA) /* st_shndx */
+ }
+ },
+ /* Section header string table */
+ {
+ KGZ_SHSTR_ZERO, /* zero */
+ KGZ_SHSTR_SYMTAB, /* symtab */
+ KGZ_SHSTR_SHSTRTAB, /* shstrtab */
+ KGZ_SHSTR_STRTAB, /* strtab */
+ KGZ_SHSTR_DATA /* data */
+ },
+ /* String table */
+ {
+ KGZ_STR_ZERO, /* zero */
+ KGZ_STR_KGZ, /* kgz */
+ KGZ_STR_KGZ_NDATA /* kgz_ndata */
+ }
+};
diff --git a/usr.sbin/kgzip/elfhdr.h b/usr.sbin/kgzip/elfhdr.h
new file mode 100644
index 0000000..57333b0
--- /dev/null
+++ b/usr.sbin/kgzip/elfhdr.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 1999 Global Technology Associates, 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$
+ */
+
+#include <sys/types.h>
+#define __ELF_WORD_SIZE 32
+#include <sys/elf32.h>
+#include <sys/elf_generic.h>
+#include "kgz.h"
+
+/* Section header indices */
+#define KGZ_SH_SYMTAB 1
+#define KGZ_SH_SHSTRTAB 2
+#define KGZ_SH_STRTAB 3
+#define KGZ_SH_DATA 4
+#define KGZ_SHNUM 5
+
+/* Section header strings */
+#define KGZ_SHSTR_ZERO ""
+#define KGZ_SHSTR_SYMTAB ".symtab"
+#define KGZ_SHSTR_SHSTRTAB ".shstrtab"
+#define KGZ_SHSTR_STRTAB ".strtab"
+#define KGZ_SHSTR_DATA ".data"
+
+/* Section header string table */
+struct kgz_shstrtab {
+ char zero[sizeof(KGZ_SHSTR_ZERO)];
+ char symtab[sizeof(KGZ_SHSTR_SYMTAB)];
+ char shstrtab[sizeof(KGZ_SHSTR_SHSTRTAB)];
+ char strtab[sizeof(KGZ_SHSTR_STRTAB)];
+ char data[sizeof(KGZ_SHSTR_DATA)];
+};
+
+/* Symbol table indices */
+#define KGZ_ST_KGZ 1
+#define KGZ_ST_KGZ_NDATA 2
+#define KGZ_STNUM 3
+
+/* Symbol table strings */
+#define KGZ_STR_ZERO ""
+#define KGZ_STR_KGZ "kgz"
+#define KGZ_STR_KGZ_NDATA "kgz_ndata"
+
+/* String table */
+struct kgz_strtab {
+ char zero[sizeof(KGZ_STR_ZERO)];
+ char kgz[sizeof(KGZ_STR_KGZ)];
+ char kgz_ndata[sizeof(KGZ_STR_KGZ_NDATA)];
+};
+
+/* Relocatable header format */
+struct kgz_elfhdr {
+ Elf32_Ehdr e;
+ Elf32_Shdr sh[KGZ_SHNUM];
+ Elf32_Sym st[KGZ_STNUM];
+ struct kgz_shstrtab shstrtab;
+ struct kgz_strtab strtab;
+};
+
+extern const struct kgz_elfhdr elfhdr;
diff --git a/usr.sbin/kgzip/kgz.h b/usr.sbin/kgzip/kgz.h
new file mode 100644
index 0000000..3cc8e13
--- /dev/null
+++ b/usr.sbin/kgzip/kgz.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 1999 Global Technology Associates, 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$
+ */
+
+#ifndef _KGZ_H_
+#define _KGZ_H_
+
+#include <sys/types.h>
+
+/*
+ * KGZ definitions: kgzip(8) output.
+ */
+
+/* Values for ident[]. */
+#define KGZ_ID0 'K'
+#define KGZ_ID1 'G'
+#define KGZ_ID2 'Z'
+#define KGZ_ID3 '\0'
+
+/* KGZ header. */
+struct kgz_hdr {
+ char ident[4]; /* identification */
+ uint32_t dload; /* decoded image load address */
+ uint32_t dsize; /* decoded image size */
+ uint32_t isize; /* image size in memory */
+ uint32_t entry; /* program entry point */
+ uint32_t nsize; /* encoded image size */
+};
+
+extern struct kgz_hdr kgz; /* header */
+extern uint8_t kgz_ndata[]; /* encoded image */
+
+#endif /* !_KGZ_H_ */
diff --git a/usr.sbin/kgzip/kgzcmp.c b/usr.sbin/kgzip/kgzcmp.c
new file mode 100644
index 0000000..0ea4f0e
--- /dev/null
+++ b/usr.sbin/kgzip/kgzcmp.c
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 1999 Global Technology Associates, 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$
+ */
+
+#define _KERNEL
+#include <sys/param.h>
+#undef _KERNEL
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <a.out.h>
+
+#include "aouthdr.h"
+#include "elfhdr.h"
+#include "kgzip.h"
+
+static void mk_data(const struct iodesc *i, const struct iodesc *,
+ struct kgz_hdr *, size_t);
+static int ld_elf(const struct iodesc *, const struct iodesc *,
+ struct kgz_hdr *, const Elf32_Ehdr *);
+static int ld_aout(const struct iodesc *, const struct iodesc *,
+ struct kgz_hdr *, const struct exec *);
+
+/*
+ * Compress executable and output it in relocatable object format.
+ */
+void
+kgzcmp(struct kgz_hdr *kh, const char *f1, const char *f2)
+{
+ struct iodesc idi, ido;
+ struct kgz_hdr khle;
+
+ if ((idi.fd = open(idi.fname = f1, O_RDONLY)) == -1)
+ err(1, "%s", idi.fname);
+ if ((ido.fd = open(ido.fname = f2, O_CREAT | O_TRUNC | O_WRONLY,
+ 0666)) == -1)
+ err(1, "%s", ido.fname);
+ kh->ident[0] = KGZ_ID0;
+ kh->ident[1] = KGZ_ID1;
+ kh->ident[2] = KGZ_ID2;
+ kh->ident[3] = KGZ_ID3;
+ mk_data(&idi, &ido, kh,
+ (format == F_AOUT ? sizeof(struct kgz_aouthdr0) :
+ sizeof(struct kgz_elfhdr)) +
+ sizeof(struct kgz_hdr));
+ kh->dload &= 0xffffff;
+ kh->entry &= 0xffffff;
+ if (format == F_AOUT) {
+ struct kgz_aouthdr0 ahdr0 = aouthdr0;
+ struct kgz_aouthdr1 ahdr1 = aouthdr1;
+ unsigned x = (sizeof(struct kgz_hdr) + kh->nsize) & (16 - 1);
+ if (x) {
+ x = 16 - x;
+ xzero(&ido, x);
+ }
+ xwrite(&ido, &ahdr1, sizeof(ahdr1));
+ ahdr0.a.a_data += kh->nsize + x;
+ xseek(&ido, 0);
+ xwrite(&ido, &ahdr0, sizeof(ahdr0));
+ } else {
+ struct kgz_elfhdr ehdr = elfhdr;
+ ehdr.st[KGZ_ST_KGZ_NDATA].st_size = htole32(kh->nsize);
+ ehdr.sh[KGZ_SH_DATA].sh_size =
+ htole32(le32toh(ehdr.sh[KGZ_SH_DATA].sh_size) + kh->nsize);
+ xseek(&ido, 0);
+ xwrite(&ido, &ehdr, sizeof(ehdr));
+ }
+ khle = *kh;
+ khle.dload = htole32(khle.dload);
+ khle.dsize = htole32(khle.dsize);
+ khle.isize = htole32(khle.isize);
+ khle.entry = htole32(khle.entry);
+ khle.nsize = htole32(khle.nsize);
+ xwrite(&ido, &khle, sizeof(khle));
+ xclose(&ido);
+ xclose(&idi);
+}
+
+/*
+ * Make encoded (compressed) data.
+ */
+static void
+mk_data(const struct iodesc * idi, const struct iodesc * ido,
+ struct kgz_hdr * kh, size_t off)
+{
+ union {
+ struct exec ex;
+ Elf32_Ehdr ee;
+ } hdr;
+ struct stat sb;
+ struct iodesc idp;
+ int fd[2];
+ pid_t pid;
+ size_t n;
+ int fmt, status, e;
+
+ n = xread(idi, &hdr, sizeof(hdr), 0);
+ fmt = 0;
+ if (n >= sizeof(hdr.ee) && IS_ELF(hdr.ee))
+ fmt = F_ELF;
+ else if (n >= sizeof(hdr.ex) && N_GETMAGIC(hdr.ex) == ZMAGIC)
+ fmt = F_AOUT;
+ if (!fmt)
+ errx(1, "%s: Format not supported", idi->fname);
+ xseek(ido, off);
+ if (pipe(fd))
+ err(1, NULL);
+ switch (pid = fork()) {
+ case -1:
+ err(1, NULL);
+ case 0:
+ close(fd[1]);
+ dup2(fd[0], STDIN_FILENO);
+ close(fd[0]);
+ close(idi->fd);
+ dup2(ido->fd, STDOUT_FILENO);
+ close(ido->fd);
+ execlp("gzip", "gzip", "-9n", (char *)NULL);
+ warn(NULL);
+ _exit(1);
+ default:
+ close(fd[0]);
+ idp.fname = "(pipe)";
+ idp.fd = fd[1];
+ e = fmt == F_ELF ? ld_elf(idi, &idp, kh, &hdr.ee) :
+ fmt == F_AOUT ? ld_aout(idi, &idp, kh, &hdr.ex) : -1;
+ close(fd[1]);
+ if ((pid = waitpid(pid, &status, 0)) == -1)
+ err(1, NULL);
+ if (WIFSIGNALED(status) || WEXITSTATUS(status))
+ exit(1);
+ }
+ if (e)
+ errx(1, "%s: Invalid format", idi->fname);
+ if (fstat(ido->fd, &sb))
+ err(1, "%s", ido->fname);
+ kh->nsize = sb.st_size - off;
+}
+
+/*
+ * "Load" an ELF-format executable.
+ */
+static int
+ld_elf(const struct iodesc * idi, const struct iodesc * ido,
+ struct kgz_hdr * kh, const Elf32_Ehdr * e)
+{
+ Elf32_Phdr p;
+ size_t load, addr, n;
+ unsigned x, i;
+
+ load = addr = n = 0;
+ for (x = i = 0; i < e->e_phnum; i++) {
+ if (xread(idi, &p, sizeof(p),
+ e->e_phoff + i * e->e_phentsize) != e->e_phentsize)
+ return -1;
+ if (p.p_type != PT_LOAD)
+ continue;
+ if (!x)
+ load = addr = p.p_vaddr;
+ else {
+ if (p.p_vaddr < addr)
+ return -1;
+ n = p.p_vaddr - addr;
+ if (n) {
+ xzero(ido, n);
+ addr += n;
+ }
+ }
+ if (p.p_memsz < p.p_filesz)
+ return -1;
+ n = p.p_memsz - p.p_filesz;
+ xcopy(idi, ido, p.p_filesz, p.p_offset);
+ addr += p.p_filesz;
+ x++;
+ }
+ if (!x)
+ return -1;
+ kh->dload = load;
+ kh->dsize = addr - load;
+ kh->isize = kh->dsize + n;
+ kh->entry = e->e_entry;
+ return 0;
+}
+
+/*
+ * "Load" an a.out-format executable.
+ */
+static int
+ld_aout(const struct iodesc * idi, const struct iodesc * ido,
+ struct kgz_hdr * kh, const struct exec * a)
+{
+ size_t load, addr;
+
+ load = addr = N_TXTADDR(*a);
+ xcopy(idi, ido, le32toh(a->a_text), N_TXTOFF(*a));
+ addr += le32toh(a->a_text);
+ if (N_DATADDR(*a) != addr)
+ return -1;
+ xcopy(idi, ido, le32toh(a->a_data), N_DATOFF(*a));
+ addr += le32toh(a->a_data);
+ kh->dload = load;
+ kh->dsize = addr - load;
+ kh->isize = kh->dsize + le32toh(a->a_bss);
+ kh->entry = le32toh(a->a_entry);
+ return 0;
+}
diff --git a/usr.sbin/kgzip/kgzip.8 b/usr.sbin/kgzip/kgzip.8
new file mode 100644
index 0000000..72d0bf5
--- /dev/null
+++ b/usr.sbin/kgzip/kgzip.8
@@ -0,0 +1,143 @@
+.\" Copyright (c) 1999 Global Technology Associates, 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$
+.\"
+.Dd July 19, 1999
+.Dt KGZIP 8
+.Os
+.Sh NAME
+.Nm kgzip
+.Nd compress a kernel
+.Sh SYNOPSIS
+.Nm
+.Op Fl cv
+.Op Fl f Ar format
+.Op Fl l Ar loader
+.Op Fl o Ar output
+.Ar file
+.Sh DESCRIPTION
+The
+.Nm
+utility compresses a kernel or some other bootable binary.
+Operation
+is in two phases as follows:
+.Bl -enum
+.It
+A load image of the executable file is built which omits all but
+the
+.Sq text
+and
+.Sq data
+segments.
+This image is compressed using
+.Xr gzip 1
+and output as data in relocatable object format.
+.It
+The object file is linked with a special self-hosting loader, producing
+an executable suitable for booting with either the second- or
+third-level bootstraps.
+.El
+.Pp
+Supported object formats are 32-bit ELF and a.out ZMAGIC.
+.Pp
+If the
+.Ar file
+operand has a
+.Sq .o
+suffix, input is assumed to be for the link phase, and the first phase
+is omitted.
+.Pp
+The options are:
+.Bl -tag -width Fl
+.It Fl c
+Omit the link phase.
+.It Fl v
+Display object file information.
+.It Fl f Ar format
+Use
+.Ar format
+as the output format, where
+.Ar format
+is
+.Sq aout
+or
+.Sq elf .
+The default format is ELF.
+.It Fl l Ar loader
+Link
+.Ar loader
+as the loader.
+.It Fl o Ar output
+Name the output file
+.Ar output .
+The default is to use the input name with the suffix
+.Sq .o
+(for relocatables) or
+.Sq .kgz
+(for executables).
+.El
+.Sh NOTES
+Global variables equivalent to the following are defined in the output:
+.Bd -literal
+struct kgz_hdr {
+ char ident[4]; /* identification: "KGZ" */
+ uint32_t dload; /* decoded image load address */
+ uint32_t dsize; /* decoded image size */
+ uint32_t isize; /* image size in memory */
+ uint32_t entry; /* entry point */
+ uint32_t nsize; /* encoded image size */
+} kgz;
+
+uint8_t kgz_ndata[]; /* encoded data */
+.Ed
+.Pp
+The encoded data is simply
+.Xr gzip 1
+output: a header (with no optional fields); compressed data; and 32-bit
+CRC and size values.
+.Sh FILES
+.Bl -tag -width /usr/lib/kgzldr.o -compact
+.It Pa /usr/lib/kgzldr.o
+The default loader
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr gzip 1 ,
+.Xr ld 1 ,
+.Xr a.out 5 ,
+.Xr elf 5 ,
+.Xr boot 8 ,
+.Xr loader 8
+.Sh AUTHORS
+.An Robert Nordier Aq Mt rnordier@FreeBSD.org
+.Sh BUGS
+As symbols are lost, the usefulness of this utility for compressing
+kernels is limited to situations where
+.Xr loader 8
+cannot be used; otherwise the preferred method of compressing a kernel
+is simply to
+.Xr gzip 1
+it.
diff --git a/usr.sbin/kgzip/kgzip.c b/usr.sbin/kgzip/kgzip.c
new file mode 100644
index 0000000..d6c48f2
--- /dev/null
+++ b/usr.sbin/kgzip/kgzip.c
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 1999 Global Technology Associates, 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.
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <err.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "kgzip.h"
+
+#define FN_SRC 0 /* Filename: source */
+#define FN_OBJ 1 /* Filename: relocatable */
+#define FN_KGZ 2 /* Filename: executable */
+#define FN_CNT 3 /* Number of filenames */
+
+#define SFX_OBJ ".o" /* Filename suffix: relocatable */
+#define SFX_KGZ ".kgz" /* Filename suffix: executable */
+#define SFX_MAX 5 /* Size of larger filename suffix */
+
+const char *loader = "/usr/lib/kgzldr.o"; /* Default loader */
+int format; /* Output format */
+
+char *tname; /* Name of temporary file */
+
+static void cleanup(void);
+static void mk_fn(int, const char *, const char *, char *[]);
+static void usage(void);
+
+/*
+ * Compress a kernel.
+ */
+int
+main(int argc, char *argv[])
+{
+ static char *fn[FN_CNT];
+ struct kgz_hdr kh;
+ const char *output;
+ char *tmpdir;
+ int cflag, vflag, c;
+
+ tmpdir = getenv("TMPDIR");
+ if (asprintf(&tname, "%s/kgzXXXXXXXXXX",
+ tmpdir == NULL ? _PATH_TMP : tmpdir) == -1)
+ errx(1, "Out of memory");
+ output = NULL;
+ cflag = vflag = 0;
+ while ((c = getopt(argc, argv, "cvf:l:o:")) != -1)
+ switch (c) {
+ case 'c':
+ cflag = 1;
+ break;
+ case 'v':
+ vflag = 1;
+ break;
+ case 'f':
+ if (!strcmp(optarg, "aout"))
+ format = F_AOUT;
+ else if (!strcmp(optarg, "elf"))
+ format = F_ELF;
+ else
+ errx(1, "%s: Unknown format", optarg);
+ break;
+ case 'l':
+ loader = optarg;
+ break;
+ case 'o':
+ output = optarg;
+ break;
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 1)
+ usage();
+ atexit(cleanup);
+ mk_fn(cflag, *argv, output, fn);
+ memset(&kh, 0, sizeof(kh));
+ if (fn[FN_SRC]) {
+ if (!format)
+ format = F_ELF;
+ kgzcmp(&kh, fn[FN_SRC], fn[FN_OBJ]);
+ }
+ if (!cflag)
+ kgzld(&kh, fn[FN_OBJ], fn[FN_KGZ]);
+ if (vflag)
+ printf("dload=%#x dsize=%#x isize=%#x entry=%#x nsize=%#x\n",
+ kh.dload, kh.dsize, kh.isize, kh.entry, kh.nsize);
+ return 0;
+}
+
+/*
+ * Clean up after processing.
+ */
+static void
+cleanup(void)
+{
+ if (tname)
+ unlink(tname);
+}
+
+/*
+ * Make the required filenames.
+ */
+static void
+mk_fn(int cflag, const char *f1, const char *f2, char *fn[])
+{
+ const char *p, *s;
+ size_t n;
+ int i, fd;
+
+ i = 0;
+ s = strrchr(f1, 0);
+ n = sizeof(SFX_OBJ) - 1;
+ if ((size_t)(s - f1) > n && !memcmp(s - n, SFX_OBJ, n)) {
+ s -= n;
+ i++;
+ }
+ fn[i++] = (char *)f1;
+ if (i == FN_OBJ && !cflag) {
+ if ((fd = mkstemp(tname)) == -1)
+ err(1, NULL);
+ close(fd);
+ fn[i++] = tname;
+ }
+ if (!(fn[i] = (char *)f2)) {
+ p = (p = strrchr(f1, '/')) ? p + 1 : f1;
+ n = (size_t)(s - p);
+ if (!(fn[i] = malloc(n + SFX_MAX)))
+ err(1, NULL);
+ memcpy(fn[i], p, n);
+ strcpy(fn[i] + n, i == FN_OBJ ? SFX_OBJ : SFX_KGZ);
+ }
+}
+
+/*
+ * Display usage information.
+ */
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: kgzip [-cv] [-f format] [-l loader] [-o output] file\n");
+ exit(1);
+}
diff --git a/usr.sbin/kgzip/kgzip.h b/usr.sbin/kgzip/kgzip.h
new file mode 100644
index 0000000..f04f2a4
--- /dev/null
+++ b/usr.sbin/kgzip/kgzip.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 1999 Global Technology Associates, 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$
+ */
+
+#include "kgz.h"
+
+#define F_AOUT 1 /* Format: a.out */
+#define F_ELF 2 /* Format: ELF32 */
+
+/* Used by I/O routines */
+struct iodesc {
+ const char *fname; /* File name */
+ int fd; /* File descriptor */
+};
+
+extern const char *loader; /* Default loader */
+extern int format; /* Output format */
+
+void kgzcmp(struct kgz_hdr *, const char *, const char *);
+void kgzld(struct kgz_hdr *, const char *, const char *);
+
+void xclose(const struct iodesc *);
+void xcopy(const struct iodesc *, const struct iodesc *, size_t, off_t);
+void xzero(const struct iodesc *, size_t);
+size_t xread(const struct iodesc *, void *, size_t, off_t);
+void xwrite(const struct iodesc *, const void *, size_t);
+void xseek(const struct iodesc *, off_t);
diff --git a/usr.sbin/kgzip/kgzld.c b/usr.sbin/kgzip/kgzld.c
new file mode 100644
index 0000000..11a20a5
--- /dev/null
+++ b/usr.sbin/kgzip/kgzld.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 1999 Global Technology Associates, 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$
+ */
+
+#include <sys/types.h>
+#include <sys/endian.h>
+#include <sys/wait.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "aouthdr.h"
+#include "elfhdr.h"
+#include "kgzip.h"
+
+#define align(x, y) (((x) + (y) - 1) & ~((y) - 1))
+
+/*
+ * Link KGZ file and loader.
+ */
+void
+kgzld(struct kgz_hdr * kh, const char *f1, const char *f2)
+{
+ char addr[16];
+ struct iodesc idi;
+ pid_t pid;
+ size_t n;
+ int status;
+
+ if (strcmp(kh->ident, "KGZ")) {
+ if ((idi.fd = open(idi.fname = f1, O_RDONLY)) == -1)
+ err(1, "%s", idi.fname);
+ if (!format) {
+ union {
+ struct exec ex;
+ Elf32_Ehdr ee;
+ } hdr;
+ n = xread(&idi, &hdr, sizeof(hdr), 0);
+ if (n >= sizeof(hdr.ee) && IS_ELF(hdr.ee))
+ format = F_ELF;
+ else if (n >= sizeof(hdr.ex) &&
+ N_GETMAGIC(hdr.ex) == OMAGIC)
+ format = F_AOUT;
+ if (!format)
+ errx(1, "%s: Format not supported", idi.fname);
+ }
+ n = xread(&idi, kh, sizeof(*kh),
+ format == F_AOUT ? sizeof(struct kgz_aouthdr0)
+ : sizeof(struct kgz_elfhdr));
+ xclose(&idi);
+ if (n != sizeof(*kh) || strcmp(kh->ident, "KGZ"))
+ errx(1, "%s: Invalid format", idi.fname);
+ }
+ sprintf(addr, "%#x", align(kh->dload + kh->dsize, 0x1000));
+ switch (pid = fork()) {
+ case -1:
+ err(1, NULL);
+ case 0:
+ if (format == F_AOUT)
+ execlp("ld", "ld", "-aout", "-Z", "-T", addr, "-o", f2,
+ loader, f1, (char *)NULL);
+ else
+ execlp("ld", "ld", "-Ttext", addr, "-o", f2, loader, f1,
+ (char *)NULL);
+ warn(NULL);
+ _exit(1);
+ default:
+ if ((pid = waitpid(pid, &status, 0)) == -1)
+ err(1, NULL);
+ if (WIFSIGNALED(status) || WEXITSTATUS(status))
+ exit(1);
+ }
+}
diff --git a/usr.sbin/kgzip/xio.c b/usr.sbin/kgzip/xio.c
new file mode 100644
index 0000000..64481ff
--- /dev/null
+++ b/usr.sbin/kgzip/xio.c
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 1999 Global Technology Associates, 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$
+ */
+
+#include <err.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "kgzip.h"
+
+/*
+ * Close a file.
+ */
+void
+xclose(const struct iodesc *id)
+{
+ if (close(id->fd))
+ err(1, "%s", id->fname);
+}
+
+/*
+ * Copy bytes from one file to another.
+ */
+void
+xcopy(const struct iodesc * idi, const struct iodesc * ido,
+ size_t nbyte, off_t offset)
+{
+ char buf[8192];
+ size_t n;
+
+ while (nbyte) {
+ if ((n = sizeof(buf)) > nbyte)
+ n = nbyte;
+ if (xread(idi, buf, n, offset) != n)
+ errx(1, "%s: Short read", idi->fname);
+ xwrite(ido, buf, n);
+ nbyte -= n;
+ offset = -1;
+ }
+}
+
+/*
+ * Write binary zeroes to a file.
+ */
+void
+xzero(const struct iodesc * id, size_t nbyte)
+{
+ char buf[8192];
+ size_t n;
+
+ memset(buf, 0, sizeof(buf));
+ while (nbyte) {
+ if ((n = sizeof(buf)) > nbyte)
+ n = nbyte;
+ xwrite(id, buf, n);
+ nbyte -= n;
+ }
+}
+
+/*
+ * Read from a file.
+ */
+size_t
+xread(const struct iodesc * id, void *buf, size_t nbyte, off_t offset)
+{
+ ssize_t n;
+
+ if (offset != -1 && lseek(id->fd, offset, SEEK_SET) != offset)
+ err(1, "%s", id->fname);
+ if ((n = read(id->fd, buf, nbyte)) == -1)
+ err(1, "%s", id->fname);
+ return (size_t)n;
+}
+
+/*
+ * Write to a file.
+ */
+void
+xwrite(const struct iodesc * id, const void *buf, size_t nbyte)
+{
+ ssize_t n;
+
+ if ((n = write(id->fd, buf, nbyte)) == -1)
+ err(1, "%s", id->fname);
+ if ((size_t)n != nbyte)
+ errx(1, "%s: Short write", id->fname);
+}
+
+/*
+ * Reposition within a file.
+ */
+void
+xseek(const struct iodesc *id, off_t offset)
+{
+ if (lseek(id->fd, offset, SEEK_SET) != offset)
+ err(1, "%s", id->fname);
+}
diff --git a/usr.sbin/kldxref/Makefile b/usr.sbin/kldxref/Makefile
new file mode 100644
index 0000000..75e74ef
--- /dev/null
+++ b/usr.sbin/kldxref/Makefile
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+PROG= kldxref
+MAN= kldxref.8
+SRCS= kldxref.c ef.c ef_obj.c
+
+WARNS?= 2
+
+.if exists(ef_${MACHINE_CPUARCH}.c) && ${MACHINE_ARCH} != "powerpc64"
+SRCS+= ef_${MACHINE_CPUARCH}.c
+.else
+SRCS+= ef_nop.c
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/kldxref/Makefile.depend b/usr.sbin/kldxref/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/kldxref/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/kldxref/ef.c b/usr.sbin/kldxref/ef.c
new file mode 100644
index 0000000..88fbc34
--- /dev/null
+++ b/usr.sbin/kldxref/ef.c
@@ -0,0 +1,647 @@
+/*
+ * Copyright (c) 2000, Boris Popov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 Boris Popov.
+ * 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 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/linker.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <machine/elf.h>
+#define FREEBSD_ELF
+
+#include <err.h>
+
+#include "ef.h"
+
+#define MAXSEGS 2
+struct ef_file {
+ char* ef_name;
+ struct elf_file *ef_efile;
+ Elf_Phdr * ef_ph;
+ int ef_fd;
+ int ef_type;
+ Elf_Ehdr ef_hdr;
+ void* ef_fpage; /* First block of the file */
+ int ef_fplen; /* length of first block */
+ Elf_Dyn* ef_dyn; /* Symbol table etc. */
+ Elf_Hashelt ef_nbuckets;
+ Elf_Hashelt ef_nchains;
+ Elf_Hashelt* ef_buckets;
+ Elf_Hashelt* ef_chains;
+ Elf_Hashelt* ef_hashtab;
+ Elf_Off ef_stroff;
+ caddr_t ef_strtab;
+ int ef_strsz;
+ Elf_Off ef_symoff;
+ Elf_Sym* ef_symtab;
+ int ef_nsegs;
+ Elf_Phdr * ef_segs[MAXSEGS];
+ int ef_verbose;
+ Elf_Rel * ef_rel; /* relocation table */
+ int ef_relsz; /* number of entries */
+ Elf_Rela * ef_rela; /* relocation table */
+ int ef_relasz; /* number of entries */
+};
+
+static void ef_print_phdr(Elf_Phdr *);
+static u_long ef_get_offset(elf_file_t, Elf_Off);
+static int ef_parse_dynamic(elf_file_t);
+
+static int ef_get_type(elf_file_t ef);
+static int ef_close(elf_file_t ef);
+static int ef_read(elf_file_t ef, Elf_Off offset, size_t len, void* dest);
+static int ef_read_entry(elf_file_t ef, Elf_Off offset, size_t len, void **ptr);
+static int ef_seg_read(elf_file_t ef, Elf_Off offset, size_t len, void *dest);
+static int ef_seg_read_rel(elf_file_t ef, Elf_Off offset, size_t len,
+ void *dest);
+static int ef_seg_read_entry(elf_file_t ef, Elf_Off offset, size_t len,
+ void **ptr);
+static int ef_seg_read_entry_rel(elf_file_t ef, Elf_Off offset, size_t len,
+ void **ptr);
+static Elf_Addr ef_symaddr(elf_file_t ef, Elf_Size symidx);
+static int ef_lookup_set(elf_file_t ef, const char *name, long *startp,
+ long *stopp, long *countp);
+static int ef_lookup_symbol(elf_file_t ef, const char* name, Elf_Sym** sym);
+
+static struct elf_file_ops ef_file_ops = {
+ ef_get_type,
+ ef_close,
+ ef_read,
+ ef_read_entry,
+ ef_seg_read,
+ ef_seg_read_rel,
+ ef_seg_read_entry,
+ ef_seg_read_entry_rel,
+ ef_symaddr,
+ ef_lookup_set,
+ ef_lookup_symbol
+};
+
+static void
+ef_print_phdr(Elf_Phdr *phdr)
+{
+
+ if ((phdr->p_flags & PF_W) == 0) {
+ printf("text=0x%lx ", (long)phdr->p_filesz);
+ } else {
+ printf("data=0x%lx", (long)phdr->p_filesz);
+ if (phdr->p_filesz < phdr->p_memsz)
+ printf("+0x%lx", (long)(phdr->p_memsz - phdr->p_filesz));
+ printf(" ");
+ }
+}
+
+static u_long
+ef_get_offset(elf_file_t ef, Elf_Off off)
+{
+ Elf_Phdr *ph;
+ int i;
+
+ for (i = 0; i < ef->ef_nsegs; i++) {
+ ph = ef->ef_segs[i];
+ if (off >= ph->p_vaddr && off < ph->p_vaddr + ph->p_memsz) {
+ return ph->p_offset + (off - ph->p_vaddr);
+ }
+ }
+ return 0;
+}
+
+static int
+ef_get_type(elf_file_t ef)
+{
+
+ return (ef->ef_type);
+}
+
+/*
+ * next three functions copied from link_elf.c
+ */
+static unsigned long
+elf_hash(const char *name)
+{
+ const unsigned char *p = (const unsigned char *) name;
+ unsigned long h = 0;
+ unsigned long g;
+
+ while (*p != '\0') {
+ h = (h << 4) + *p++;
+ if ((g = h & 0xf0000000) != 0)
+ h ^= g >> 24;
+ h &= ~g;
+ }
+ return h;
+}
+
+static int
+ef_lookup_symbol(elf_file_t ef, const char* name, Elf_Sym** sym)
+{
+ unsigned long symnum;
+ Elf_Sym* symp;
+ char *strp;
+ unsigned long hash;
+
+ /* First, search hashed global symbols */
+ hash = elf_hash(name);
+ symnum = ef->ef_buckets[hash % ef->ef_nbuckets];
+
+ while (symnum != STN_UNDEF) {
+ if (symnum >= ef->ef_nchains) {
+ warnx("ef_lookup_symbol: file %s have corrupted symbol table\n",
+ ef->ef_name);
+ return ENOENT;
+ }
+
+ symp = ef->ef_symtab + symnum;
+ if (symp->st_name == 0) {
+ warnx("ef_lookup_symbol: file %s have corrupted symbol table\n",
+ ef->ef_name);
+ return ENOENT;
+ }
+
+ strp = ef->ef_strtab + symp->st_name;
+
+ if (strcmp(name, strp) == 0) {
+ if (symp->st_shndx != SHN_UNDEF ||
+ (symp->st_value != 0 &&
+ ELF_ST_TYPE(symp->st_info) == STT_FUNC)) {
+ *sym = symp;
+ return 0;
+ } else
+ return ENOENT;
+ }
+
+ symnum = ef->ef_chains[symnum];
+ }
+
+ return ENOENT;
+}
+
+static int
+ef_lookup_set(elf_file_t ef, const char *name, long *startp, long *stopp,
+ long *countp)
+{
+ Elf_Sym *sym;
+ char *setsym;
+ int error, len;
+
+ len = strlen(name) + sizeof("__start_set_"); /* sizeof includes \0 */
+ setsym = malloc(len);
+ if (setsym == NULL)
+ return (ENOMEM);
+
+ /* get address of first entry */
+ snprintf(setsym, len, "%s%s", "__start_set_", name);
+ error = ef_lookup_symbol(ef, setsym, &sym);
+ if (error)
+ goto out;
+ *startp = sym->st_value;
+
+ /* get address of last entry */
+ snprintf(setsym, len, "%s%s", "__stop_set_", name);
+ error = ef_lookup_symbol(ef, setsym, &sym);
+ if (error)
+ goto out;
+ *stopp = sym->st_value;
+
+ /* and the number of entries */
+ *countp = (*stopp - *startp) / sizeof(void *);
+
+out:
+ free(setsym);
+ return (error);
+}
+
+static Elf_Addr
+ef_symaddr(elf_file_t ef, Elf_Size symidx)
+{
+ const Elf_Sym *sym;
+
+ if (symidx >= ef->ef_nchains)
+ return (0);
+ sym = ef->ef_symtab + symidx;
+
+ if (ELF_ST_BIND(sym->st_info) == STB_LOCAL &&
+ sym->st_shndx != SHN_UNDEF && sym->st_value != 0)
+ return (sym->st_value);
+ return (0);
+}
+
+static int
+ef_parse_dynamic(elf_file_t ef)
+{
+ Elf_Dyn *dp;
+ Elf_Hashelt hashhdr[2];
+/* int plttype = DT_REL;*/
+ int error;
+ Elf_Off rel_off;
+ Elf_Off rela_off;
+ int rel_sz;
+ int rela_sz;
+ int rel_entry;
+ int rela_entry;
+
+ rel_off = rela_off = 0;
+ rel_sz = rela_sz = 0;
+ rel_entry = rela_entry = 0;
+ for (dp = ef->ef_dyn; dp->d_tag != DT_NULL; dp++) {
+ switch (dp->d_tag) {
+ case DT_HASH:
+ error = ef_read(ef, ef_get_offset(ef, dp->d_un.d_ptr),
+ sizeof(hashhdr), hashhdr);
+ if (error) {
+ warnx("can't read hash header (%lx)",
+ ef_get_offset(ef, dp->d_un.d_ptr));
+ return error;
+ }
+ ef->ef_nbuckets = hashhdr[0];
+ ef->ef_nchains = hashhdr[1];
+ error = ef_read_entry(ef, -1,
+ (hashhdr[0] + hashhdr[1]) * sizeof(Elf_Hashelt),
+ (void**)&ef->ef_hashtab);
+ if (error) {
+ warnx("can't read hash table");
+ return error;
+ }
+ ef->ef_buckets = ef->ef_hashtab;
+ ef->ef_chains = ef->ef_buckets + ef->ef_nbuckets;
+ break;
+ case DT_STRTAB:
+ ef->ef_stroff = dp->d_un.d_ptr;
+ break;
+ case DT_STRSZ:
+ ef->ef_strsz = dp->d_un.d_val;
+ break;
+ case DT_SYMTAB:
+ ef->ef_symoff = dp->d_un.d_ptr;
+ break;
+ case DT_SYMENT:
+ if (dp->d_un.d_val != sizeof(Elf_Sym))
+ return EFTYPE;
+ break;
+ case DT_REL:
+ if (rel_off != 0)
+ warnx("second DT_REL entry ignored");
+ rel_off = dp->d_un.d_ptr;
+ break;
+ case DT_RELSZ:
+ if (rel_sz != 0)
+ warnx("second DT_RELSZ entry ignored");
+ rel_sz = dp->d_un.d_val;
+ break;
+ case DT_RELENT:
+ if (rel_entry != 0)
+ warnx("second DT_RELENT entry ignored");
+ rel_entry = dp->d_un.d_val;
+ break;
+ case DT_RELA:
+ if (rela_off != 0)
+ warnx("second DT_RELA entry ignored");
+ rela_off = dp->d_un.d_ptr;
+ break;
+ case DT_RELASZ:
+ if (rela_sz != 0)
+ warnx("second DT_RELASZ entry ignored");
+ rela_sz = dp->d_un.d_val;
+ break;
+ case DT_RELAENT:
+ if (rela_entry != 0)
+ warnx("second DT_RELAENT entry ignored");
+ rela_entry = dp->d_un.d_val;
+ break;
+ }
+ }
+ if (ef->ef_symoff == 0) {
+ warnx("%s: no .dynsym section found\n", ef->ef_name);
+ return EFTYPE;
+ }
+ if (ef->ef_stroff == 0) {
+ warnx("%s: no .dynstr section found\n", ef->ef_name);
+ return EFTYPE;
+ }
+ if (ef_read_entry(ef, ef_get_offset(ef, ef->ef_symoff),
+ ef->ef_nchains * sizeof(Elf_Sym),
+ (void**)&ef->ef_symtab) != 0) {
+ if (ef->ef_verbose)
+ warnx("%s: can't load .dynsym section (0x%lx)",
+ ef->ef_name, (long)ef->ef_symoff);
+ return EIO;
+ }
+ if (ef_read_entry(ef, ef_get_offset(ef, ef->ef_stroff), ef->ef_strsz,
+ (void**)&ef->ef_strtab) != 0) {
+ warnx("can't load .dynstr section");
+ return EIO;
+ }
+ if (rel_off != 0) {
+ if (rel_entry == 0) {
+ warnx("%s: no DT_RELENT for DT_REL", ef->ef_name);
+ return (EFTYPE);
+ }
+ if (rel_entry != sizeof(Elf_Rel)) {
+ warnx("%s: inconsistent DT_RELENT value",
+ ef->ef_name);
+ return (EFTYPE);
+ }
+ if (rel_sz % rel_entry != 0) {
+ warnx("%s: inconsistent values for DT_RELSZ and "
+ "DT_RELENT", ef->ef_name);
+ return (EFTYPE);
+ }
+ if (ef_read_entry(ef, ef_get_offset(ef, rel_off), rel_sz,
+ (void **)&ef->ef_rel) != 0) {
+ warnx("%s: cannot load DT_REL section", ef->ef_name);
+ return (EIO);
+ }
+ ef->ef_relsz = rel_sz / rel_entry;
+ if (ef->ef_verbose)
+ warnx("%s: %d REL entries", ef->ef_name,
+ ef->ef_relsz);
+ }
+ if (rela_off != 0) {
+ if (rela_entry == 0) {
+ warnx("%s: no DT_RELAENT for DT_RELA", ef->ef_name);
+ return (EFTYPE);
+ }
+ if (rela_entry != sizeof(Elf_Rela)) {
+ warnx("%s: inconsistent DT_RELAENT value",
+ ef->ef_name);
+ return (EFTYPE);
+ }
+ if (rela_sz % rela_entry != 0) {
+ warnx("%s: inconsistent values for DT_RELASZ and "
+ "DT_RELAENT", ef->ef_name);
+ return (EFTYPE);
+ }
+ if (ef_read_entry(ef, ef_get_offset(ef, rela_off), rela_sz,
+ (void **)&ef->ef_rela) != 0) {
+ warnx("%s: cannot load DT_RELA section", ef->ef_name);
+ return (EIO);
+ }
+ ef->ef_relasz = rela_sz / rela_entry;
+ if (ef->ef_verbose)
+ warnx("%s: %d RELA entries", ef->ef_name,
+ ef->ef_relasz);
+ }
+ return 0;
+}
+
+static int
+ef_read(elf_file_t ef, Elf_Off offset, size_t len, void*dest)
+{
+ ssize_t r;
+
+ if (offset != (Elf_Off)-1) {
+ if (lseek(ef->ef_fd, offset, SEEK_SET) == -1)
+ return EIO;
+ }
+
+ r = read(ef->ef_fd, dest, len);
+ if (r != -1 && (size_t)r == len)
+ return 0;
+ else
+ return EIO;
+}
+
+static int
+ef_read_entry(elf_file_t ef, Elf_Off offset, size_t len, void**ptr)
+{
+ int error;
+
+ *ptr = malloc(len);
+ if (*ptr == NULL)
+ return ENOMEM;
+ error = ef_read(ef, offset, len, *ptr);
+ if (error)
+ free(*ptr);
+ return error;
+}
+
+static int
+ef_seg_read(elf_file_t ef, Elf_Off offset, size_t len, void*dest)
+{
+ u_long ofs = ef_get_offset(ef, offset);
+
+ if (ofs == 0) {
+ if (ef->ef_verbose)
+ warnx("ef_seg_read(%s): zero offset (%lx:%ld)",
+ ef->ef_name, (long)offset, ofs);
+ return EFAULT;
+ }
+ return ef_read(ef, ofs, len, dest);
+}
+
+static int
+ef_seg_read_rel(elf_file_t ef, Elf_Off offset, size_t len, void*dest)
+{
+ u_long ofs = ef_get_offset(ef, offset);
+ const Elf_Rela *a;
+ const Elf_Rel *r;
+ int error;
+
+ if (ofs == 0) {
+ if (ef->ef_verbose)
+ warnx("ef_seg_read(%s): zero offset (%lx:%ld)",
+ ef->ef_name, (long)offset, ofs);
+ return EFAULT;
+ }
+ if ((error = ef_read(ef, ofs, len, dest)) != 0)
+ return (error);
+
+ for (r = ef->ef_rel; r < &ef->ef_rel[ef->ef_relsz]; r++) {
+ error = ef_reloc(ef->ef_efile, r, EF_RELOC_REL, 0, offset, len,
+ dest);
+ if (error != 0)
+ return (error);
+ }
+ for (a = ef->ef_rela; a < &ef->ef_rela[ef->ef_relasz]; a++) {
+ error = ef_reloc(ef->ef_efile, a, EF_RELOC_RELA, 0, offset, len,
+ dest);
+ if (error != 0)
+ return (error);
+ }
+ return (0);
+}
+
+static int
+ef_seg_read_entry(elf_file_t ef, Elf_Off offset, size_t len, void**ptr)
+{
+ int error;
+
+ *ptr = malloc(len);
+ if (*ptr == NULL)
+ return ENOMEM;
+ error = ef_seg_read(ef, offset, len, *ptr);
+ if (error)
+ free(*ptr);
+ return error;
+}
+
+static int
+ef_seg_read_entry_rel(elf_file_t ef, Elf_Off offset, size_t len, void**ptr)
+{
+ int error;
+
+ *ptr = malloc(len);
+ if (*ptr == NULL)
+ return ENOMEM;
+ error = ef_seg_read_rel(ef, offset, len, *ptr);
+ if (error)
+ free(*ptr);
+ return error;
+}
+
+int
+ef_open(const char *filename, struct elf_file *efile, int verbose)
+{
+ elf_file_t ef;
+ Elf_Ehdr *hdr;
+ int fd;
+ int error;
+ int phlen, res;
+ int nsegs;
+ Elf_Phdr *phdr, *phdyn, *phlimit;
+
+ if (filename == NULL)
+ return EFTYPE;
+ if ((fd = open(filename, O_RDONLY)) == -1)
+ return errno;
+
+ ef = malloc(sizeof(*ef));
+ if (ef == NULL) {
+ close(fd);
+ return (ENOMEM);
+ }
+
+ efile->ef_ef = ef;
+ efile->ef_ops = &ef_file_ops;
+
+ bzero(ef, sizeof(*ef));
+ ef->ef_verbose = verbose;
+ ef->ef_fd = fd;
+ ef->ef_name = strdup(filename);
+ ef->ef_efile = efile;
+ hdr = (Elf_Ehdr *)&ef->ef_hdr;
+ do {
+ res = read(fd, hdr, sizeof(*hdr));
+ error = EFTYPE;
+ if (res != sizeof(*hdr))
+ break;
+ if (!IS_ELF(*hdr))
+ break;
+ if (hdr->e_ident[EI_CLASS] != ELF_TARG_CLASS ||
+ hdr->e_ident[EI_DATA] != ELF_TARG_DATA ||
+ hdr->e_ident[EI_VERSION] != EV_CURRENT ||
+ hdr->e_version != EV_CURRENT ||
+ hdr->e_machine != ELF_TARG_MACH ||
+ hdr->e_phentsize != sizeof(Elf_Phdr))
+ break;
+ phlen = hdr->e_phnum * sizeof(Elf_Phdr);
+ if (ef_read_entry(ef, hdr->e_phoff, phlen,
+ (void**)&ef->ef_ph) != 0)
+ break;
+ phdr = ef->ef_ph;
+ phlimit = phdr + hdr->e_phnum;
+ nsegs = 0;
+ phdyn = NULL;
+ while (phdr < phlimit) {
+ if (verbose > 1)
+ ef_print_phdr(phdr);
+ switch (phdr->p_type) {
+ case PT_LOAD:
+ if (nsegs < MAXSEGS)
+ ef->ef_segs[nsegs] = phdr;
+ nsegs++;
+ break;
+ case PT_PHDR:
+ break;
+ case PT_DYNAMIC:
+ phdyn = phdr;
+ break;
+ }
+ phdr++;
+ }
+ if (verbose > 1)
+ printf("\n");
+ if (phdyn == NULL) {
+ warnx("Skipping %s: not dynamically-linked",
+ filename);
+ break;
+ } else if (nsegs > MAXSEGS) {
+ warnx("%s: too many sections", filename);
+ break;
+ }
+ ef->ef_nsegs = nsegs;
+ if (ef_read_entry(ef, phdyn->p_offset,
+ phdyn->p_filesz, (void**)&ef->ef_dyn) != 0) {
+ printf("ef_read_entry failed\n");
+ break;
+ }
+ error = ef_parse_dynamic(ef);
+ if (error)
+ break;
+ if (hdr->e_type == ET_DYN) {
+ ef->ef_type = EFT_KLD;
+/* pad = (u_int)dest & PAGE_MASK;
+ if (pad)
+ dest += PAGE_SIZE - pad;*/
+ error = 0;
+ } else if (hdr->e_type == ET_EXEC) {
+/* dest = hdr->e_entry;
+ if (dest == 0)
+ break;*/
+ ef->ef_type = EFT_KERNEL;
+ error = 0;
+ } else
+ break;
+ } while(0);
+ if (error)
+ ef_close(ef);
+ return error;
+}
+
+static int
+ef_close(elf_file_t ef)
+{
+ close(ef->ef_fd);
+/* if (ef->ef_fpage)
+ free(ef->ef_fpage);*/
+ if (ef->ef_name)
+ free(ef->ef_name);
+ ef->ef_efile->ef_ops = NULL;
+ ef->ef_efile->ef_ef = NULL;
+ free(ef);
+ return 0;
+}
diff --git a/usr.sbin/kldxref/ef.h b/usr.sbin/kldxref/ef.h
new file mode 100644
index 0000000..5bb1985
--- /dev/null
+++ b/usr.sbin/kldxref/ef.h
@@ -0,0 +1,69 @@
+/* $FreeBSD$ */
+
+#ifndef _EF_H_
+#define _EF_H_
+
+#define EFT_KLD 1
+#define EFT_KERNEL 2
+
+#define EF_RELOC_REL 1
+#define EF_RELOC_RELA 2
+
+#define EF_GET_TYPE(ef) \
+ (ef)->ef_ops->get_type((ef)->ef_ef)
+#define EF_CLOSE(ef) \
+ (ef)->ef_ops->close((ef)->ef_ef)
+#define EF_READ(ef, offset, len, dest) \
+ (ef)->ef_ops->read((ef)->ef_ef, offset, len, dest)
+#define EF_READ_ENTRY(ef, offset, len, ptr) \
+ (ef)->ef_ops->read_entry((ef)->ef_ef, offset, len, ptr)
+#define EF_SEG_READ(ef, offset, len, dest) \
+ (ef)->ef_ops->seg_read((ef)->ef_ef, offset, len, dest)
+#define EF_SEG_READ_REL(ef, offset, len, dest) \
+ (ef)->ef_ops->seg_read_rel((ef)->ef_ef, offset, len, dest)
+#define EF_SEG_READ_ENTRY(ef, offset, len, ptr) \
+ (ef)->ef_ops->seg_read_entry((ef)->kf_ef, offset, len, ptr)
+#define EF_SEG_READ_ENTRY_REL(ef, offset, len, ptr) \
+ (ef)->ef_ops->seg_read_entry_rel((ef)->ef_ef, offset, len, ptr)
+#define EF_SYMADDR(ef, symidx) \
+ (ef)->ef_ops->symaddr((ef)->ef_ef, symidx)
+#define EF_LOOKUP_SET(ef, name, startp, stopp, countp) \
+ (ef)->ef_ops->lookup_set((ef)->ef_ef, name, startp, stopp, countp)
+#define EF_LOOKUP_SYMBOL(ef, name, sym) \
+ (ef)->ef_ops->lookup_symbol((ef)->ef_ef, name, sym)
+
+/* XXX, should have a different name. */
+typedef struct ef_file *elf_file_t;
+
+struct elf_file_ops {
+ int (*get_type)(elf_file_t ef);
+ int (*close)(elf_file_t ef);
+ int (*read)(elf_file_t ef, Elf_Off offset, size_t len, void* dest);
+ int (*read_entry)(elf_file_t ef, Elf_Off offset, size_t len,
+ void **ptr);
+ int (*seg_read)(elf_file_t ef, Elf_Off offset, size_t len, void *dest);
+ int (*seg_read_rel)(elf_file_t ef, Elf_Off offset, size_t len,
+ void *dest);
+ int (*seg_read_entry)(elf_file_t ef, Elf_Off offset, size_t len,
+ void**ptr);
+ int (*seg_read_entry_rel)(elf_file_t ef, Elf_Off offset, size_t len,
+ void**ptr);
+ Elf_Addr (*symaddr)(elf_file_t ef, Elf_Size symidx);
+ int (*lookup_set)(elf_file_t ef, const char *name, long *startp,
+ long *stopp, long *countp);
+ int (*lookup_symbol)(elf_file_t ef, const char* name, Elf_Sym** sym);
+};
+
+struct elf_file {
+ elf_file_t ef_ef;
+ struct elf_file_ops *ef_ops;
+};
+
+__BEGIN_DECLS
+int ef_open(const char *filename, struct elf_file *ef, int verbose);
+int ef_obj_open(const char *filename, struct elf_file *ef, int verbose);
+int ef_reloc(struct elf_file *ef, const void *reldata, int reltype,
+ Elf_Off relbase, Elf_Off dataoff, size_t len, void *dest);
+__END_DECLS
+
+#endif /* _EF_H_*/
diff --git a/usr.sbin/kldxref/ef_amd64.c b/usr.sbin/kldxref/ef_amd64.c
new file mode 100644
index 0000000..b90882d
--- /dev/null
+++ b/usr.sbin/kldxref/ef_amd64.c
@@ -0,0 +1,116 @@
+/*-
+ * Copyright (c) 2003 Jake Burkholder.
+ * Copyright 1996-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/types.h>
+#include <machine/elf.h>
+
+#include <err.h>
+#include <errno.h>
+
+#include "ef.h"
+
+/*
+ * Apply relocations to the values we got from the file. `relbase' is the
+ * target relocation address of the section, and `dataoff' is the target
+ * relocation address of the data in `dest'.
+ */
+int
+ef_reloc(struct elf_file *ef, const void *reldata, int reltype, Elf_Off relbase,
+ Elf_Off dataoff, size_t len, void *dest)
+{
+ Elf64_Addr *where, val;
+ Elf32_Addr *where32, val32;
+ Elf_Addr addend, addr;
+ Elf_Size rtype, symidx;
+ const Elf_Rel *rel;
+ const Elf_Rela *rela;
+
+ switch (reltype) {
+ case EF_RELOC_REL:
+ rel = (const Elf_Rel *)reldata;
+ where = (Elf_Addr *)(dest + relbase + rel->r_offset - dataoff);
+ addend = 0;
+ rtype = ELF_R_TYPE(rel->r_info);
+ symidx = ELF_R_SYM(rel->r_info);
+ break;
+ case EF_RELOC_RELA:
+ rela = (const Elf_Rela *)reldata;
+ where = (Elf_Addr *)(dest + relbase + rela->r_offset - dataoff);
+ addend = rela->r_addend;
+ rtype = ELF_R_TYPE(rela->r_info);
+ symidx = ELF_R_SYM(rela->r_info);
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ if ((char *)where < (char *)dest || (char *)where >= (char *)dest + len)
+ return (0);
+
+ if (reltype == EF_RELOC_REL) {
+ /* Addend is 32 bit on 32 bit relocs */
+ switch (rtype) {
+ case R_X86_64_PC32:
+ case R_X86_64_32S:
+ addend = *(Elf32_Addr *)where;
+ break;
+ default:
+ addend = *where;
+ break;
+ }
+ }
+
+ switch (rtype) {
+ case R_X86_64_NONE: /* none */
+ break;
+ case R_X86_64_64: /* S + A */
+ addr = EF_SYMADDR(ef, symidx);
+ val = addr + addend;
+ *where = val;
+ break;
+ case R_X86_64_32S: /* S + A sign extend */
+ addr = EF_SYMADDR(ef, symidx);
+ val32 = (Elf32_Addr)(addr + addend);
+ where32 = (Elf32_Addr *)where;
+ *where32 = val32;
+ break;
+ case R_X86_64_GLOB_DAT: /* S */
+ addr = EF_SYMADDR(ef, symidx);
+ *where = addr;
+ break;
+ case R_X86_64_RELATIVE: /* B + A */
+ addr = (Elf_Addr)addend + relbase;
+ val = addr;
+ *where = val;
+ break;
+ default:
+ warnx("unhandled relocation type %d", (int)rtype);
+ }
+ return (0);
+}
diff --git a/usr.sbin/kldxref/ef_i386.c b/usr.sbin/kldxref/ef_i386.c
new file mode 100644
index 0000000..b953f4a
--- /dev/null
+++ b/usr.sbin/kldxref/ef_i386.c
@@ -0,0 +1,96 @@
+/*-
+ * Copyright (c) 2003 Jake Burkholder.
+ * Copyright 1996-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/types.h>
+#include <machine/elf.h>
+
+#include <err.h>
+#include <errno.h>
+
+#include "ef.h"
+
+/*
+ * Apply relocations to the values we got from the file. `relbase' is the
+ * target relocation address of the section, and `dataoff' is the target
+ * relocation address of the data in `dest'.
+ */
+int
+ef_reloc(struct elf_file *ef, const void *reldata, int reltype, Elf_Off relbase,
+ Elf_Off dataoff, size_t len, void *_dest)
+{
+ Elf_Addr *where, addr, addend;
+ Elf_Size rtype, symidx;
+ const Elf_Rel *rel;
+ const Elf_Rela *rela;
+ char *dest = _dest;
+
+ switch (reltype) {
+ case EF_RELOC_REL:
+ rel = (const Elf_Rel *)reldata;
+ where = (Elf_Addr *)(dest + relbase + rel->r_offset - dataoff);
+ addend = 0;
+ rtype = ELF_R_TYPE(rel->r_info);
+ symidx = ELF_R_SYM(rel->r_info);
+ break;
+ case EF_RELOC_RELA:
+ rela = (const Elf_Rela *)reldata;
+ where = (Elf_Addr *)(dest + relbase + rela->r_offset - dataoff);
+ addend = rela->r_addend;
+ rtype = ELF_R_TYPE(rela->r_info);
+ symidx = ELF_R_SYM(rela->r_info);
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ if ((char *)where < (char *)dest || (char *)where >= (char *)dest + len)
+ return (0);
+
+ if (reltype == EF_RELOC_REL)
+ addend = *where;
+
+ switch (rtype) {
+ case R_386_RELATIVE: /* A + B */
+ addr = (Elf_Addr)addend + relbase;
+ *where = addr;
+ break;
+ case R_386_32: /* S + A - P */
+ addr = EF_SYMADDR(ef, symidx);
+ addr += addend;
+ *where = addr;
+ break;
+ case R_386_GLOB_DAT: /* S */
+ addr = EF_SYMADDR(ef, symidx);
+ *where = addr;
+ break;
+ default:
+ warnx("unhandled relocation type %d", (int)rtype);
+ }
+ return (0);
+}
diff --git a/usr.sbin/kldxref/ef_nop.c b/usr.sbin/kldxref/ef_nop.c
new file mode 100644
index 0000000..cbbd43c
--- /dev/null
+++ b/usr.sbin/kldxref/ef_nop.c
@@ -0,0 +1,40 @@
+/*-
+ * Copyright (c) 2003 Jake Burkholder.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <machine/elf.h>
+
+#include "ef.h"
+
+int
+ef_reloc(struct elf_file *ef, const void *reldata, int reltype, Elf_Off relbase,
+ Elf_Off dataoff, size_t len, void *dest)
+{
+
+ return (0);
+}
diff --git a/usr.sbin/kldxref/ef_obj.c b/usr.sbin/kldxref/ef_obj.c
new file mode 100644
index 0000000..e6099f2
--- /dev/null
+++ b/usr.sbin/kldxref/ef_obj.c
@@ -0,0 +1,606 @@
+/*
+ * Copyright (c) 2000, Boris Popov
+ * Copyright (c) 1998-2000 Doug Rabson
+ * Copyright (c) 2004 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 by Boris Popov.
+ * 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 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/linker.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <machine/elf.h>
+#define FREEBSD_ELF
+
+#include <err.h>
+
+#include "ef.h"
+
+typedef struct {
+ void *addr;
+ Elf_Off size;
+ int flags;
+ int sec; /* Original section */
+ char *name;
+} Elf_progent;
+
+typedef struct {
+ Elf_Rel *rel;
+ int nrel;
+ int sec;
+} Elf_relent;
+
+typedef struct {
+ Elf_Rela *rela;
+ int nrela;
+ int sec;
+} Elf_relaent;
+
+struct ef_file {
+ char *ef_name;
+ int ef_fd;
+ Elf_Ehdr ef_hdr;
+ struct elf_file *ef_efile;
+
+ caddr_t address;
+ Elf_Off size;
+ Elf_Shdr *e_shdr;
+
+ Elf_progent *progtab;
+ int nprogtab;
+
+ Elf_relaent *relatab;
+ int nrela;
+
+ Elf_relent *reltab;
+ int nrel;
+
+ Elf_Sym *ddbsymtab; /* The symbol table we are using */
+ long ddbsymcnt; /* Number of symbols */
+ caddr_t ddbstrtab; /* String table */
+ long ddbstrcnt; /* number of bytes in string table */
+
+ caddr_t shstrtab; /* Section name string table */
+ long shstrcnt; /* number of bytes in string table */
+
+ int ef_verbose;
+};
+
+static int ef_obj_get_type(elf_file_t ef);
+static int ef_obj_close(elf_file_t ef);
+static int ef_obj_read(elf_file_t ef, Elf_Off offset, size_t len, void* dest);
+static int ef_obj_read_entry(elf_file_t ef, Elf_Off offset, size_t len,
+ void **ptr);
+static int ef_obj_seg_read(elf_file_t ef, Elf_Off offset, size_t len,
+ void *dest);
+static int ef_obj_seg_read_rel(elf_file_t ef, Elf_Off offset, size_t len,
+ void *dest);
+static int ef_obj_seg_read_entry(elf_file_t ef, Elf_Off offset, size_t len,
+ void **ptr);
+static int ef_obj_seg_read_entry_rel(elf_file_t ef, Elf_Off offset, size_t len,
+ void **ptr);
+static Elf_Addr ef_obj_symaddr(elf_file_t ef, Elf_Size symidx);
+static int ef_obj_lookup_set(elf_file_t ef, const char *name, long *startp,
+ long *stopp, long *countp);
+static int ef_obj_lookup_symbol(elf_file_t ef, const char* name, Elf_Sym** sym);
+
+static struct elf_file_ops ef_obj_file_ops = {
+ ef_obj_get_type,
+ ef_obj_close,
+ ef_obj_read,
+ ef_obj_read_entry,
+ ef_obj_seg_read,
+ ef_obj_seg_read_rel,
+ ef_obj_seg_read_entry,
+ ef_obj_seg_read_entry_rel,
+ ef_obj_symaddr,
+ ef_obj_lookup_set,
+ ef_obj_lookup_symbol
+};
+
+static int
+ef_obj_get_type(elf_file_t __unused ef)
+{
+
+ return (EFT_KLD);
+}
+
+static int
+ef_obj_lookup_symbol(elf_file_t ef, const char* name, Elf_Sym** sym)
+{
+ Elf_Sym *symp;
+ const char *strp;
+ int i;
+
+ for (i = 0, symp = ef->ddbsymtab; i < ef->ddbsymcnt; i++, symp++) {
+ strp = ef->ddbstrtab + symp->st_name;
+ if (symp->st_shndx != SHN_UNDEF && strcmp(name, strp) == 0) {
+ *sym = symp;
+ return 0;
+ }
+ }
+ return ENOENT;
+}
+
+static int
+ef_obj_lookup_set(elf_file_t ef, const char *name, long *startp, long *stopp,
+ long *countp)
+{
+ int i;
+
+ for (i = 0; i < ef->nprogtab; i++) {
+ if ((strncmp(ef->progtab[i].name, "set_", 4) == 0) &&
+ strcmp(ef->progtab[i].name + 4, name) == 0) {
+ *startp = (char *)ef->progtab[i].addr - ef->address;
+ *stopp = (char *)ef->progtab[i].addr +
+ ef->progtab[i].size - ef->address;
+ *countp = (*stopp - *startp) / sizeof(void *);
+ return (0);
+ }
+ }
+ return (ESRCH);
+}
+
+static Elf_Addr
+ef_obj_symaddr(elf_file_t ef, Elf_Size symidx)
+{
+ const Elf_Sym *sym;
+
+ if (symidx >= (size_t) ef->ddbsymcnt)
+ return (0);
+ sym = ef->ddbsymtab + symidx;
+
+ if (sym->st_shndx != SHN_UNDEF)
+ return (sym->st_value - (Elf_Addr)ef->address);
+ return (0);
+}
+
+static int
+ef_obj_read(elf_file_t ef, Elf_Off offset, size_t len, void *dest)
+{
+ ssize_t r;
+
+ if (offset != (Elf_Off)-1) {
+ if (lseek(ef->ef_fd, offset, SEEK_SET) == -1)
+ return EIO;
+ }
+
+ r = read(ef->ef_fd, dest, len);
+ if (r != -1 && (size_t)r == len)
+ return 0;
+ else
+ return EIO;
+}
+
+static int
+ef_obj_read_entry(elf_file_t ef, Elf_Off offset, size_t len, void **ptr)
+{
+ int error;
+
+ *ptr = malloc(len);
+ if (*ptr == NULL)
+ return ENOMEM;
+ error = ef_obj_read(ef, offset, len, *ptr);
+ if (error)
+ free(*ptr);
+ return error;
+}
+
+static int
+ef_obj_seg_read(elf_file_t ef, Elf_Off offset, size_t len, void *dest)
+{
+
+ if (offset + len > ef->size) {
+ if (ef->ef_verbose)
+ warnx("ef_seg_read_rel(%s): bad offset/len (%lx:%ld)",
+ ef->ef_name, (long)offset, (long)len);
+ return (EFAULT);
+ }
+ bcopy(ef->address + offset, dest, len);
+ return (0);
+}
+
+static int
+ef_obj_seg_read_rel(elf_file_t ef, Elf_Off offset, size_t len, void *dest)
+{
+ char *memaddr;
+ Elf_Rel *r;
+ Elf_Rela *a;
+ Elf_Off secbase, dataoff;
+ int error, i, sec;
+
+ if (offset + len > ef->size) {
+ if (ef->ef_verbose)
+ warnx("ef_seg_read_rel(%s): bad offset/len (%lx:%ld)",
+ ef->ef_name, (long)offset, (long)len);
+ return (EFAULT);
+ }
+ bcopy(ef->address + offset, dest, len);
+
+ /* Find out which section contains the data. */
+ memaddr = ef->address + offset;
+ sec = -1;
+ secbase = dataoff = 0;
+ for (i = 0; i < ef->nprogtab; i++) {
+ if (ef->progtab[i].addr == NULL)
+ continue;
+ if (memaddr < (char *)ef->progtab[i].addr || memaddr + len >
+ (char *)ef->progtab[i].addr + ef->progtab[i].size)
+ continue;
+ sec = ef->progtab[i].sec;
+ /* We relocate to address 0. */
+ secbase = (char *)ef->progtab[i].addr - ef->address;
+ dataoff = memaddr - ef->address;
+ break;
+ }
+
+ if (sec == -1)
+ return (EFAULT);
+
+ /* Now do the relocations. */
+ for (i = 0; i < ef->nrel; i++) {
+ if (ef->reltab[i].sec != sec)
+ continue;
+ for (r = ef->reltab[i].rel;
+ r < &ef->reltab[i].rel[ef->reltab[i].nrel]; r++) {
+ error = ef_reloc(ef->ef_efile, r, EF_RELOC_REL, secbase,
+ dataoff, len, dest);
+ if (error != 0)
+ return (error);
+ }
+ }
+ for (i = 0; i < ef->nrela; i++) {
+ if (ef->relatab[i].sec != sec)
+ continue;
+ for (a = ef->relatab[i].rela;
+ a < &ef->relatab[i].rela[ef->relatab[i].nrela]; a++) {
+ error = ef_reloc(ef->ef_efile, a, EF_RELOC_RELA,
+ secbase, dataoff, len, dest);
+ if (error != 0)
+ return (error);
+ }
+ }
+ return (0);
+}
+
+static int
+ef_obj_seg_read_entry(elf_file_t ef, Elf_Off offset, size_t len, void **ptr)
+{
+ int error;
+
+ *ptr = malloc(len);
+ if (*ptr == NULL)
+ return ENOMEM;
+ error = ef_obj_seg_read(ef, offset, len, *ptr);
+ if (error)
+ free(*ptr);
+ return error;
+}
+
+static int
+ef_obj_seg_read_entry_rel(elf_file_t ef, Elf_Off offset, size_t len,
+ void **ptr)
+{
+ int error;
+
+ *ptr = malloc(len);
+ if (*ptr == NULL)
+ return ENOMEM;
+ error = ef_obj_seg_read_rel(ef, offset, len, *ptr);
+ if (error)
+ free(*ptr);
+ return error;
+}
+
+int
+ef_obj_open(const char *filename, struct elf_file *efile, int verbose)
+{
+ elf_file_t ef;
+ Elf_Ehdr *hdr;
+ Elf_Shdr *shdr;
+ Elf_Sym *es;
+ char *mapbase;
+ void *vtmp;
+ size_t mapsize, alignmask, max_addralign;
+ int error, fd, pb, ra, res, rl;
+ int i, j, nbytes, nsym, shstrindex, symstrindex, symtabindex;
+
+ if (filename == NULL)
+ return EFTYPE;
+ if ((fd = open(filename, O_RDONLY)) == -1)
+ return errno;
+
+ ef = calloc(1, sizeof(*ef));
+ if (ef == NULL) {
+ close(fd);
+ return (ENOMEM);
+ }
+
+ efile->ef_ef = ef;
+ efile->ef_ops = &ef_obj_file_ops;
+
+ ef->ef_verbose = verbose;
+ ef->ef_fd = fd;
+ ef->ef_name = strdup(filename);
+ ef->ef_efile = efile;
+ hdr = (Elf_Ehdr *)&ef->ef_hdr;
+
+ res = read(fd, hdr, sizeof(*hdr));
+ error = EFTYPE;
+ if (res != sizeof(*hdr))
+ goto out;
+ if (!IS_ELF(*hdr))
+ goto out;
+ if (hdr->e_ident[EI_CLASS] != ELF_TARG_CLASS ||
+ hdr->e_ident[EI_DATA] != ELF_TARG_DATA ||
+ hdr->e_ident[EI_VERSION] != EV_CURRENT ||
+ hdr->e_version != EV_CURRENT || hdr->e_machine != ELF_TARG_MACH ||
+ hdr->e_type != ET_REL)
+ goto out;
+
+ nbytes = hdr->e_shnum * hdr->e_shentsize;
+ if (nbytes == 0 || hdr->e_shoff == 0 ||
+ hdr->e_shentsize != sizeof(Elf_Shdr))
+ goto out;
+
+ if (ef_obj_read_entry(ef, hdr->e_shoff, nbytes, &vtmp) != 0) {
+ printf("ef_read_entry failed\n");
+ goto out;
+ }
+ ef->e_shdr = shdr = vtmp;
+
+ /* Scan the section header for information and table sizing. */
+ nsym = 0;
+ symtabindex = -1;
+ symstrindex = -1;
+ for (i = 0; i < hdr->e_shnum; i++) {
+ switch (shdr[i].sh_type) {
+ case SHT_PROGBITS:
+ case SHT_NOBITS:
+ ef->nprogtab++;
+ break;
+ case SHT_SYMTAB:
+ nsym++;
+ symtabindex = i;
+ symstrindex = shdr[i].sh_link;
+ break;
+ case SHT_REL:
+ ef->nrel++;
+ break;
+ case SHT_RELA:
+ ef->nrela++;
+ break;
+ case SHT_STRTAB:
+ break;
+ }
+ }
+
+ if (ef->nprogtab == 0) {
+ warnx("%s: file has no contents", filename);
+ goto out;
+ }
+ if (nsym != 1) {
+ warnx("%s: file has no valid symbol table", filename);
+ goto out;
+ }
+ if (symstrindex < 0 || symstrindex > hdr->e_shnum ||
+ shdr[symstrindex].sh_type != SHT_STRTAB) {
+ warnx("%s: file has invalid symbol strings", filename);
+ goto out;
+ }
+
+ /* Allocate space for tracking the load chunks */
+ if (ef->nprogtab != 0)
+ ef->progtab = calloc(ef->nprogtab, sizeof(*ef->progtab));
+ if (ef->nrel != 0)
+ ef->reltab = calloc(ef->nrel, sizeof(*ef->reltab));
+ if (ef->nrela != 0)
+ ef->relatab = calloc(ef->nrela, sizeof(*ef->relatab));
+ if ((ef->nprogtab != 0 && ef->progtab == NULL) ||
+ (ef->nrel != 0 && ef->reltab == NULL) ||
+ (ef->nrela != 0 && ef->relatab == NULL)) {
+ printf("malloc failed\n");
+ error = ENOMEM;
+ goto out;
+ }
+
+ ef->ddbsymcnt = shdr[symtabindex].sh_size / sizeof(Elf_Sym);
+ if (ef_obj_read_entry(ef, shdr[symtabindex].sh_offset,
+ shdr[symtabindex].sh_size, (void**)&ef->ddbsymtab) != 0) {
+ printf("ef_read_entry failed\n");
+ goto out;
+ }
+
+ ef->ddbstrcnt = shdr[symstrindex].sh_size;
+ if (ef_obj_read_entry(ef, shdr[symstrindex].sh_offset,
+ shdr[symstrindex].sh_size, (void**)&ef->ddbstrtab) != 0) {
+ printf("ef_read_entry failed\n");
+ goto out;
+ }
+
+ /* Do we have a string table for the section names? */
+ shstrindex = -1;
+ if (hdr->e_shstrndx != 0 &&
+ shdr[hdr->e_shstrndx].sh_type == SHT_STRTAB) {
+ shstrindex = hdr->e_shstrndx;
+ ef->shstrcnt = shdr[shstrindex].sh_size;
+ if (ef_obj_read_entry(ef, shdr[shstrindex].sh_offset,
+ shdr[shstrindex].sh_size, (void**)&ef->shstrtab) != 0) {
+ printf("ef_read_entry failed\n");
+ goto out;
+ }
+ }
+
+ /* Size up code/data(progbits) and bss(nobits). */
+ alignmask = 0;
+ max_addralign = 0;
+ mapsize = 0;
+ for (i = 0; i < hdr->e_shnum; i++) {
+ switch (shdr[i].sh_type) {
+ case SHT_PROGBITS:
+ case SHT_NOBITS:
+ alignmask = shdr[i].sh_addralign - 1;
+ if (shdr[i].sh_addralign > max_addralign)
+ max_addralign = shdr[i].sh_addralign;
+ mapsize += alignmask;
+ mapsize &= ~alignmask;
+ mapsize += shdr[i].sh_size;
+ break;
+ }
+ }
+
+ /* We know how much space we need for the text/data/bss/etc. */
+ ef->size = mapsize;
+ if (posix_memalign((void **)&ef->address, max_addralign, mapsize)) {
+ printf("posix_memalign failed\n");
+ goto out;
+ }
+ mapbase = ef->address;
+
+ /*
+ * Now load code/data(progbits), zero bss(nobits), allocate
+ * space for and load relocs
+ */
+ pb = 0;
+ rl = 0;
+ ra = 0;
+ alignmask = 0;
+ for (i = 0; i < hdr->e_shnum; i++) {
+ switch (shdr[i].sh_type) {
+ case SHT_PROGBITS:
+ case SHT_NOBITS:
+ alignmask = shdr[i].sh_addralign - 1;
+ mapbase += alignmask;
+ mapbase = (char *)((uintptr_t)mapbase & ~alignmask);
+ ef->progtab[pb].addr = (void *)(uintptr_t)mapbase;
+ if (shdr[i].sh_type == SHT_PROGBITS) {
+ ef->progtab[pb].name = "<<PROGBITS>>";
+ if (ef_obj_read(ef, shdr[i].sh_offset,
+ shdr[i].sh_size,
+ ef->progtab[pb].addr) != 0) {
+ printf("failed to read progbits\n");
+ goto out;
+ }
+ } else {
+ ef->progtab[pb].name = "<<NOBITS>>";
+ bzero(ef->progtab[pb].addr, shdr[i].sh_size);
+ }
+ ef->progtab[pb].size = shdr[i].sh_size;
+ ef->progtab[pb].sec = i;
+ if (ef->shstrtab && shdr[i].sh_name != 0)
+ ef->progtab[pb].name =
+ ef->shstrtab + shdr[i].sh_name;
+
+ /* Update all symbol values with the offset. */
+ for (j = 0; j < ef->ddbsymcnt; j++) {
+ es = &ef->ddbsymtab[j];
+ if (es->st_shndx != i)
+ continue;
+ es->st_value += (Elf_Addr)ef->progtab[pb].addr;
+ }
+ mapbase += shdr[i].sh_size;
+ pb++;
+ break;
+ case SHT_REL:
+ ef->reltab[rl].nrel = shdr[i].sh_size / sizeof(Elf_Rel);
+ ef->reltab[rl].sec = shdr[i].sh_info;
+ if (ef_obj_read_entry(ef, shdr[i].sh_offset,
+ shdr[i].sh_size, (void**)&ef->reltab[rl].rel) !=
+ 0) {
+ printf("ef_read_entry failed\n");
+ goto out;
+ }
+ rl++;
+ break;
+ case SHT_RELA:
+ ef->relatab[ra].nrela =
+ shdr[i].sh_size / sizeof(Elf_Rela);
+ ef->relatab[ra].sec = shdr[i].sh_info;
+ if (ef_obj_read_entry(ef, shdr[i].sh_offset,
+ shdr[i].sh_size, (void**)&ef->relatab[ra].rela) !=
+ 0) {
+ printf("ef_read_entry failed\n");
+ goto out;
+ }
+ ra++;
+ break;
+ }
+ }
+ error = 0;
+out:
+ if (error)
+ ef_obj_close(ef);
+ return error;
+}
+
+static int
+ef_obj_close(elf_file_t ef)
+{
+ int i;
+
+ close(ef->ef_fd);
+ if (ef->ef_name)
+ free(ef->ef_name);
+ if (ef->e_shdr != NULL)
+ free(ef->e_shdr);
+ if (ef->size != 0)
+ free(ef->address);
+ if (ef->nprogtab != 0)
+ free(ef->progtab);
+ if (ef->nrel != 0) {
+ for (i = 0; i < ef->nrel; i++)
+ if (ef->reltab[i].rel != NULL)
+ free(ef->reltab[i].rel);
+ free(ef->reltab);
+ }
+ if (ef->nrela != 0) {
+ for (i = 0; i < ef->nrela; i++)
+ if (ef->relatab[i].rela != NULL)
+ free(ef->relatab[i].rela);
+ free(ef->relatab);
+ }
+ if (ef->ddbsymtab != NULL)
+ free(ef->ddbsymtab);
+ if (ef->ddbstrtab != NULL)
+ free(ef->ddbstrtab);
+ if (ef->shstrtab != NULL)
+ free(ef->shstrtab);
+ ef->ef_efile->ef_ops = NULL;
+ ef->ef_efile->ef_ef = NULL;
+ free(ef);
+
+ return 0;
+}
diff --git a/usr.sbin/kldxref/ef_powerpc.c b/usr.sbin/kldxref/ef_powerpc.c
new file mode 100644
index 0000000..a96a727
--- /dev/null
+++ b/usr.sbin/kldxref/ef_powerpc.c
@@ -0,0 +1,74 @@
+/*-
+ * Copyright (c) 2005 Peter Grehan.
+ * Copyright 1996-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/types.h>
+#include <machine/elf.h>
+
+#include <err.h>
+#include <errno.h>
+#include <string.h>
+
+#include "ef.h"
+
+#include <stdio.h>
+
+/*
+ * Apply relocations to the values obtained from the file. `relbase' is the
+ * target relocation address of the section, and `dataoff/len' is the region
+ * that is to be relocated, and has been copied to *dest
+ */
+int
+ef_reloc(struct elf_file *ef, const void *reldata, int reltype, Elf_Off relbase,
+ Elf_Off dataoff, size_t len, void *dest)
+{
+ Elf_Addr *where, addend;
+ Elf_Size rtype, symidx;
+ const Elf_Rela *rela;
+
+ if (reltype != EF_RELOC_RELA)
+ return (EINVAL);
+
+ rela = (const Elf_Rela *)reldata;
+ where = (Elf_Addr *) ((Elf_Off)dest - dataoff + rela->r_offset);
+ addend = rela->r_addend;
+ rtype = ELF_R_TYPE(rela->r_info);
+ symidx = ELF_R_SYM(rela->r_info);
+
+ if ((char *)where < (char *)dest || (char *)where >= (char *)dest + len)
+ return (0);
+
+ switch(rtype) {
+ case R_PPC_RELATIVE: /* word32 B + A */
+ *where = relbase + addend;
+ break;
+ default:
+ warnx("unhandled relocation type %d", rtype);
+ }
+ return (0);
+}
diff --git a/usr.sbin/kldxref/ef_sparc64.c b/usr.sbin/kldxref/ef_sparc64.c
new file mode 100644
index 0000000..7ba2a43
--- /dev/null
+++ b/usr.sbin/kldxref/ef_sparc64.c
@@ -0,0 +1,69 @@
+/*-
+ * Copyright (c) 2003 Jake Burkholder.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <machine/elf.h>
+
+#include <err.h>
+#include <string.h>
+
+#include "ef.h"
+
+/*
+ * Apply relocations to the values we got from the file. `relbase' is the
+ * target relocation address of the section, and `dataoff' is the target
+ * relocation address of the data in `dest'.
+ */
+int
+ef_reloc(struct elf_file *ef, const void *reldata, int reltype, Elf_Off relbase,
+ Elf_Off dataoff, size_t len, void *dest)
+{
+ const Elf_Rela *a;
+ Elf_Size w;
+
+ switch (reltype) {
+ case EF_RELOC_RELA:
+ a = reldata;
+ if (relbase + a->r_offset >= dataoff && relbase + a->r_offset <
+ dataoff + len) {
+ switch (ELF_R_TYPE(a->r_info)) {
+ case R_SPARC_RELATIVE:
+ w = a->r_addend + relbase;
+ memcpy((u_char *)dest + (relbase + a->r_offset -
+ dataoff), &w, sizeof(w));
+ break;
+ default:
+ warnx("unhandled relocation type %u",
+ (unsigned int)ELF_R_TYPE(a->r_info));
+ break;
+ }
+ }
+ break;
+ }
+ return (0);
+}
diff --git a/usr.sbin/kldxref/fileformat b/usr.sbin/kldxref/fileformat
new file mode 100644
index 0000000..81d115c
--- /dev/null
+++ b/usr.sbin/kldxref/fileformat
@@ -0,0 +1,45 @@
+$FreeBSD$
+
+linker.hints file consists from the one or more records,
+and is processed by sys/kern/kern_linker.c::linker_hints_lookup()
+
+First record of file is special and determines its version:
+
+int version;
+
+ All subsequent records have following format:
+
+struct record {
+ int length; /* length of following data */
+ char data[length];
+};
+
+ Each record is aligned on sizeof(int) boundary. First integer of the field
+'data' determines its type:
+
+struct data {
+ int type; /* type of data. currently MDT_* values */
+};
+
+ The rest of record depends on the type.
+
+struct string {
+ uint8_t length; /* length of string */
+ char val[]; /* string itself (no terminating zero) */
+};
+
+struct data_mdt_version {
+ int type = MDT_VERSION;
+ struct string modname;
+ /* padding */
+ int version;
+ struct string kldname;
+ /* padding */
+};
+
+struct data_mdt_module {
+ int type = MDT_MODULE;
+ struct string modname;
+ struct string kldname;
+ /* padding */
+};
diff --git a/usr.sbin/kldxref/kldxref.8 b/usr.sbin/kldxref/kldxref.8
new file mode 100644
index 0000000..1a3b911
--- /dev/null
+++ b/usr.sbin/kldxref/kldxref.8
@@ -0,0 +1,95 @@
+.\"-
+.\" Copyright (c) 2001 Boris Popov
+.\" Copyright (c) 2001 Dag-Erling Coïdan Smørgrav
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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, 2001
+.Dt KLDXREF 8
+.Os
+.Sh NAME
+.Nm kldxref
+.Nd generate hints for the kernel loader
+.Sh SYNOPSIS
+.Nm
+.Op Fl Rdv
+.Op Fl f Ar hintsfile
+.Ar path ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to generate hint files which list modules, their
+version numbers, and the files that contain them.
+These hints are used by the kernel loader to determine where to find a
+particular KLD module.
+.Pp
+A separate hint file is generated for each directory listed on the
+command line that contains modules.
+If no hint records are generated for a particular directory, no hint
+file is created, and the preexisting hint file (if there was one in
+that directory) is removed.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl R
+Recurse into subdirectories.
+.It Fl d
+Do not generate a hint file, but print module metadata on standard
+output.
+.It Fl f Ar hintsfile
+Specify a different name for the hints files than
+.Pa linker.hints .
+.It Fl v
+Operate in verbose mode.
+.El
+.Sh EXAMPLES
+To build hint files for both standard and add-on modules:
+.Pp
+.Dl "kldxref /boot/kernel /boot/modules"
+.Pp
+To build hint files for all installed kernels:
+.Pp
+.Dl "kldxref -R /boot"
+.Sh SEE ALSO
+.Xr kld 4 ,
+.Xr kldconfig 8 ,
+.Xr kldload 8 ,
+.Xr kldstat 8 ,
+.Xr kldunload 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 5.0 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility was implemented by
+.An Boris Popov Aq Mt bp@FreeBSD.org .
+This manual page was written by
+.An Boris Popov Aq Mt bp@FreeBSD.org
+and
+.An Dag-Erling Sm\(/orgrav Aq Mt des@FreeBSD.org .
diff --git a/usr.sbin/kldxref/kldxref.c b/usr.sbin/kldxref/kldxref.c
new file mode 100644
index 0000000..01b7c65
--- /dev/null
+++ b/usr.sbin/kldxref/kldxref.c
@@ -0,0 +1,717 @@
+/*
+ * Copyright (c) 2000, Boris Popov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 Boris Popov.
+ * 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 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/param.h>
+#include <sys/endian.h>
+#include <sys/exec.h>
+#include <sys/queue.h>
+#include <sys/kernel.h>
+#include <sys/reboot.h>
+#include <sys/linker.h>
+#include <sys/stat.h>
+#include <sys/module.h>
+#define FREEBSD_ELF
+#include <err.h>
+#include <fts.h>
+#include <string.h>
+#include <machine/elf.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "ef.h"
+
+#define MAXRECSIZE (64 << 10) /* 64k */
+#define check(val) if ((error = (val)) != 0) break
+
+static int dflag; /* do not create a hint file, only write on stdout */
+static int verbose;
+
+static FILE *fxref; /* current hints file */
+
+static const char *xref_file = "linker.hints";
+
+/*
+ * A record is stored in the static buffer recbuf before going to disk.
+ */
+static char recbuf[MAXRECSIZE];
+static int recpos; /* current write position */
+static int reccnt; /* total record written to this file so far */
+
+static void
+intalign(void)
+{
+ recpos = (recpos + sizeof(int) - 1) & ~(sizeof(int) - 1);
+}
+
+static void
+record_start(void)
+{
+ recpos = 0;
+ memset(recbuf, 0, MAXRECSIZE);
+}
+
+static int
+record_end(void)
+{
+ if (recpos == 0)
+ return 0;
+ reccnt++;
+ intalign();
+ fwrite(&recpos, sizeof(recpos), 1, fxref);
+ return fwrite(recbuf, recpos, 1, fxref) != 1 ? errno : 0;
+}
+
+static int
+record_buf(const void *buf, int size)
+{
+ if (MAXRECSIZE - recpos < size)
+ errx(1, "record buffer overflow");
+ memcpy(recbuf + recpos, buf, size);
+ recpos += size;
+ return 0;
+}
+
+/*
+ * An int is stored in host order and aligned
+ */
+static int
+record_int(int val)
+{
+ intalign();
+ return record_buf(&val, sizeof(val));
+}
+
+/*
+ * A string is stored as 1-byte length plus data, no padding
+ */
+static int
+record_string(const char *str)
+{
+ int len, error;
+ u_char val;
+
+ if (dflag)
+ return 0;
+ val = len = strlen(str);
+ if (len > 255)
+ errx(1, "string %s too long", str);
+ error = record_buf(&val, sizeof(val));
+ if (error)
+ return error;
+ return record_buf(str, len);
+}
+
+/* From sys/isa/pnp.c */
+static char *
+pnp_eisaformat(uint32_t id)
+{
+ uint8_t *data;
+ static char idbuf[8];
+ const char hextoascii[] = "0123456789abcdef";
+
+ id = htole32(id);
+ data = (uint8_t *)&id;
+ idbuf[0] = '@' + ((data[0] & 0x7c) >> 2);
+ idbuf[1] = '@' + (((data[0] & 0x3) << 3) + ((data[1] & 0xe0) >> 5));
+ idbuf[2] = '@' + (data[1] & 0x1f);
+ idbuf[3] = hextoascii[(data[2] >> 4)];
+ idbuf[4] = hextoascii[(data[2] & 0xf)];
+ idbuf[5] = hextoascii[(data[3] >> 4)];
+ idbuf[6] = hextoascii[(data[3] & 0xf)];
+ idbuf[7] = 0;
+ return(idbuf);
+}
+
+struct pnp_elt
+{
+ int pe_kind; /* What kind of entry */
+#define TYPE_SZ_MASK 0x0f
+#define TYPE_FLAGGED 0x10 /* all f's is a wildcard */
+#define TYPE_INT 0x20 /* Is a number */
+#define TYPE_PAIRED 0x40
+#define TYPE_LE 0x80 /* Matches <= this value */
+#define TYPE_GE 0x100 /* Matches >= this value */
+#define TYPE_MASK 0x200 /* Specifies a mask to follow */
+#define TYPE_U8 (1 | TYPE_INT)
+#define TYPE_V8 (1 | TYPE_INT | TYPE_FLAGGED)
+#define TYPE_G16 (2 | TYPE_INT | TYPE_GE)
+#define TYPE_L16 (2 | TYPE_INT | TYPE_LE)
+#define TYPE_M16 (2 | TYPE_INT | TYPE_MASK)
+#define TYPE_U16 (2 | TYPE_INT)
+#define TYPE_V16 (2 | TYPE_INT | TYPE_FLAGGED)
+#define TYPE_U32 (4 | TYPE_INT)
+#define TYPE_V32 (4 | TYPE_INT | TYPE_FLAGGED)
+#define TYPE_W32 (4 | TYPE_INT | TYPE_PAIRED)
+#define TYPE_D 7
+#define TYPE_Z 8
+#define TYPE_P 9
+#define TYPE_E 10
+#define TYPE_T 11
+ int pe_offset; /* Offset within the element */
+ char * pe_key; /* pnp key name */
+ TAILQ_ENTRY(pnp_elt) next; /* Link */
+};
+typedef TAILQ_HEAD(pnp_head, pnp_elt) pnp_list;
+
+/*
+ * this function finds the data from the pnp table, as described by the
+ * the description and creates a new output (new_desc). This output table
+ * is a form that's easier for the agent that's automatically loading the
+ * modules.
+ *
+ * The format output is the simplified string from this routine in the
+ * same basic format as the pnp string, as documented in sys/module.h.
+ * First a string describing the format is output, the a count of the
+ * number of records, then each record. The format string also describes
+ * the length of each entry (though it isn't a fixed length when strings
+ * are present).
+ *
+ * type Output Meaning
+ * I uint32_t Integer equality comparison
+ * J uint32_t Pair of uint16_t fields converted to native
+ byte order. The two fields both must match.
+ * G uint32_t Greater than or equal to
+ * L uint32_t Less than or equal to
+ * M uint32_t Mask of which fields to test. Fields that
+ take up space increment the count. This
+ field must be first, and resets the count.
+ * D string Description of the device this pnp info is for
+ * Z string pnp string must match this
+ * T nothing T fields set pnp values that must be true for
+ * the entire table.
+ * Values are packed the same way that other values are packed in this file.
+ * Strings and int32_t's start on a 32-bit boundary and are padded with 0
+ * bytes. Objects that are smaller than uint32_t are converted, without
+ * sign extension to uint32_t to simplify parsing downstream.
+ */
+static int
+parse_pnp_list(const char *desc, char **new_desc, pnp_list *list)
+{
+ const char *walker = desc, *ep = desc + strlen(desc);
+ const char *colon, *semi;
+ struct pnp_elt *elt;
+ char *nd;
+ char type[8], key[32];
+ int off;
+
+ off = 0;
+ nd = *new_desc = malloc(strlen(desc) + 1);
+ if (verbose > 1)
+ printf("Converting %s into a list\n", desc);
+ while (walker < ep) {
+ colon = strchr(walker, ':');
+ semi = strchr(walker, ';');
+ if (semi != NULL && semi < colon)
+ goto err;
+ if (colon - walker > sizeof(type))
+ goto err;
+ strncpy(type, walker, colon - walker);
+ type[colon - walker] = '\0';
+ if (semi) {
+ if (semi - colon >= sizeof(key))
+ goto err;
+ strncpy(key, colon + 1, semi - colon - 1);
+ key[semi - colon - 1] = '\0';
+ walker = semi + 1;
+ } else {
+ if (strlen(colon + 1) >= sizeof(key))
+ goto err;
+ strcpy(key, colon + 1);
+ walker = ep;
+ }
+ if (verbose > 1)
+ printf("Found type %s for name %s\n", type, key);
+ /* Skip pointer place holders */
+ if (strcmp(type, "P") == 0) {
+ off += sizeof(void *);
+ continue;
+ }
+
+ /*
+ * Add a node of the appropriate type
+ */
+ elt = malloc(sizeof(struct pnp_elt) + strlen(key) + 1);
+ TAILQ_INSERT_TAIL(list, elt, next);
+ elt->pe_key = (char *)(elt + 1);
+ elt->pe_offset = off;
+ if (strcmp(type, "U8") == 0)
+ elt->pe_kind = TYPE_U8;
+ else if (strcmp(type, "V8") == 0)
+ elt->pe_kind = TYPE_V8;
+ else if (strcmp(type, "G16") == 0)
+ elt->pe_kind = TYPE_G16;
+ else if (strcmp(type, "L16") == 0)
+ elt->pe_kind = TYPE_L16;
+ else if (strcmp(type, "M16") == 0)
+ elt->pe_kind = TYPE_M16;
+ else if (strcmp(type, "U16") == 0)
+ elt->pe_kind = TYPE_U16;
+ else if (strcmp(type, "V16") == 0)
+ elt->pe_kind = TYPE_V16;
+ else if (strcmp(type, "U32") == 0)
+ elt->pe_kind = TYPE_U32;
+ else if (strcmp(type, "V32") == 0)
+ elt->pe_kind = TYPE_V32;
+ else if (strcmp(type, "W32") == 0)
+ elt->pe_kind = TYPE_W32;
+ else if (strcmp(type, "D") == 0) /* description char * */
+ elt->pe_kind = TYPE_D;
+ else if (strcmp(type, "Z") == 0) /* char * to match */
+ elt->pe_kind = TYPE_Z;
+ else if (strcmp(type, "P") == 0) /* Pointer -- ignored */
+ elt->pe_kind = TYPE_P;
+ else if (strcmp(type, "E") == 0) /* EISA PNP ID, as uint32_t */
+ elt->pe_kind = TYPE_E;
+ else if (strcmp(type, "T") == 0)
+ elt->pe_kind = TYPE_T;
+ else
+ goto err;
+ /*
+ * Maybe the rounding here needs to be more nuanced and/or somehow
+ * architecture specific. Fortunately, most tables in the system
+ * have sane ordering of types.
+ */
+ if (elt->pe_kind & TYPE_INT) {
+ elt->pe_offset = roundup2(elt->pe_offset, elt->pe_kind & TYPE_SZ_MASK);
+ off = elt->pe_offset + (elt->pe_kind & TYPE_SZ_MASK);
+ } else if (elt->pe_kind == TYPE_E) {
+ /* Type E stored as Int, displays as string */
+ elt->pe_offset = roundup2(elt->pe_offset, sizeof(uint32_t));
+ off = elt->pe_offset + sizeof(uint32_t);
+ } else if (elt->pe_kind == TYPE_T) {
+ /* doesn't actually consume space in the table */
+ off = elt->pe_offset;
+ } else {
+ elt->pe_offset = roundup2(elt->pe_offset, sizeof(void *));
+ off = elt->pe_offset + sizeof(void *);
+ }
+ if (elt->pe_kind & TYPE_PAIRED) {
+ char *word, *ctx;
+
+ for (word = strtok_r(key, "/", &ctx);
+ word; word = strtok_r(NULL, "/", &ctx)) {
+ sprintf(nd, "%c:%s;", elt->pe_kind & TYPE_FLAGGED ? 'J' : 'I',
+ word);
+ nd += strlen(nd);
+ }
+
+ }
+ else {
+ if (elt->pe_kind & TYPE_FLAGGED)
+ *nd++ = 'J';
+ else if (elt->pe_kind & TYPE_GE)
+ *nd++ = 'G';
+ else if (elt->pe_kind & TYPE_LE)
+ *nd++ = 'L';
+ else if (elt->pe_kind & TYPE_MASK)
+ *nd++ = 'M';
+ else if (elt->pe_kind & TYPE_INT)
+ *nd++ = 'I';
+ else if (elt->pe_kind == TYPE_D)
+ *nd++ = 'D';
+ else if (elt->pe_kind == TYPE_Z || elt->pe_kind == TYPE_E)
+ *nd++ = 'Z';
+ else if (elt->pe_kind == TYPE_T)
+ *nd++ = 'T';
+ else
+ errx(1, "Impossible type %x\n", elt->pe_kind);
+ *nd++ = ':';
+ strcpy(nd, key);
+ nd += strlen(nd);
+ *nd++ = ';';
+ }
+ }
+ *nd++ = '\0';
+ return 0;
+err:
+ errx(1, "Parse error of description string %s", desc);
+}
+
+static int
+parse_entry(struct mod_metadata *md, const char *cval,
+ struct elf_file *ef, const char *kldname)
+{
+ struct mod_depend mdp;
+ struct mod_version mdv;
+ struct mod_pnp_match_info pnp;
+ char descr[1024];
+ Elf_Off data = (Elf_Off)md->md_data;
+ int error = 0, i, len;
+ char *walker;
+ void *table;
+
+ record_start();
+ switch (md->md_type) {
+ case MDT_DEPEND:
+ if (!dflag)
+ break;
+ check(EF_SEG_READ(ef, data, sizeof(mdp), &mdp));
+ printf(" depends on %s.%d (%d,%d)\n", cval,
+ mdp.md_ver_preferred, mdp.md_ver_minimum, mdp.md_ver_maximum);
+ break;
+ case MDT_VERSION:
+ check(EF_SEG_READ(ef, data, sizeof(mdv), &mdv));
+ if (dflag) {
+ printf(" interface %s.%d\n", cval, mdv.mv_version);
+ } else {
+ record_int(MDT_VERSION);
+ record_string(cval);
+ record_int(mdv.mv_version);
+ record_string(kldname);
+ }
+ break;
+ case MDT_MODULE:
+ if (dflag) {
+ printf(" module %s\n", cval);
+ } else {
+ record_int(MDT_MODULE);
+ record_string(cval);
+ record_string(kldname);
+ }
+ break;
+ case MDT_PNP_INFO:
+ check(EF_SEG_READ_REL(ef, data, sizeof(pnp), &pnp));
+ check(EF_SEG_READ(ef, (Elf_Off)pnp.descr, sizeof(descr), descr));
+ descr[sizeof(descr) - 1] = '\0';
+ if (dflag) {
+ printf(" pnp info for bus %s format %s %d entries of %d bytes\n",
+ cval, descr, pnp.num_entry, pnp.entry_len);
+ } else {
+ pnp_list list;
+ struct pnp_elt *elt, *elt_tmp;
+ char *new_descr;
+
+ if (verbose > 1)
+ printf(" pnp info for bus %s format %s %d entries of %d bytes\n",
+ cval, descr, pnp.num_entry, pnp.entry_len);
+ /*
+ * Parse descr to weed out the chaff and to create a list
+ * of offsets to output.
+ */
+ TAILQ_INIT(&list);
+ parse_pnp_list(descr, &new_descr, &list);
+ record_int(MDT_PNP_INFO);
+ record_string(cval);
+ record_string(new_descr);
+ record_int(pnp.num_entry);
+ len = pnp.num_entry * pnp.entry_len;
+ walker = table = malloc(len);
+ check(EF_SEG_READ_REL(ef, (Elf_Off)pnp.table, len, table));
+
+ /*
+ * Walk the list and output things. We've collapsed all the
+ * variant forms of the table down to just ints and strings.
+ */
+ for (i = 0; i < pnp.num_entry; i++) {
+ TAILQ_FOREACH(elt, &list, next) {
+ uint8_t v1;
+ uint16_t v2;
+ uint32_t v4;
+ int value;
+ char buffer[1024];
+
+ if (elt->pe_kind == TYPE_W32) {
+ memcpy(&v4, walker + elt->pe_offset, sizeof(v4));
+ value = v4 & 0xffff;
+ record_int(value);
+ if (verbose > 1)
+ printf("W32:%#x", value);
+ value = (v4 >> 16) & 0xffff;
+ record_int(value);
+ if (verbose > 1)
+ printf(":%#x;", value);
+ } else if (elt->pe_kind & TYPE_INT) {
+ switch (elt->pe_kind & TYPE_SZ_MASK) {
+ case 1:
+ memcpy(&v1, walker + elt->pe_offset, sizeof(v1));
+ if ((elt->pe_kind & TYPE_FLAGGED) && v1 == 0xff)
+ value = -1;
+ else
+ value = v1;
+ break;
+ case 2:
+ memcpy(&v2, walker + elt->pe_offset, sizeof(v2));
+ if ((elt->pe_kind & TYPE_FLAGGED) && v2 == 0xffff)
+ value = -1;
+ else
+ value = v2;
+ break;
+ case 4:
+ memcpy(&v4, walker + elt->pe_offset, sizeof(v4));
+ if ((elt->pe_kind & TYPE_FLAGGED) && v4 == 0xffffffff)
+ value = -1;
+ else
+ value = v4;
+ break;
+ default:
+ errx(1, "Invalid size somehow %#x", elt->pe_kind);
+ }
+ if (verbose > 1)
+ printf("I:%#x;", value);
+ record_int(value);
+ } else if (elt->pe_kind == TYPE_T) {
+ /* Do nothing */
+ } else { /* E, Z or D -- P already filtered */
+ if (elt->pe_kind == TYPE_E) {
+ memcpy(&v4, walker + elt->pe_offset, sizeof(v4));
+ strcpy(buffer, pnp_eisaformat(v4));
+ } else {
+ char *ptr;
+
+ ptr = *(char **)(walker + elt->pe_offset);
+ buffer[0] = '\0';
+ if (ptr != 0) {
+ EF_SEG_READ(ef, (Elf_Off)ptr,
+ sizeof(buffer), buffer);
+ buffer[sizeof(buffer) - 1] = '\0';
+ }
+ }
+ if (verbose > 1)
+ printf("%c:%s;", elt->pe_kind == TYPE_E ? 'E' : (elt->pe_kind == TYPE_Z ? 'Z' : 'D'), buffer);
+ record_string(buffer);
+ }
+ }
+ if (verbose > 1)
+ printf("\n");
+ walker += pnp.entry_len;
+ }
+ /* Now free it */
+ TAILQ_FOREACH_SAFE(elt, &list, next, elt_tmp) {
+ TAILQ_REMOVE(&list, elt, next);
+ free(elt);
+ }
+ free(table);
+ }
+ break;
+ default:
+ warnx("unknown metadata record %d in file %s", md->md_type, kldname);
+ }
+ if (!error)
+ record_end();
+ return error;
+}
+
+static int
+read_kld(char *filename, char *kldname)
+{
+ struct mod_metadata md;
+ struct elf_file ef;
+ void **p, **orgp;
+ int error, eftype, nmlen;
+ long start, finish, entries;
+ char kldmodname[MAXMODNAME + 1], cval[MAXMODNAME + 1], *cp;
+
+ if (verbose || dflag)
+ printf("%s\n", filename);
+ error = ef_open(filename, &ef, verbose);
+ if (error) {
+ error = ef_obj_open(filename, &ef, verbose);
+ if (error) {
+ if (verbose)
+ warnc(error, "elf_open(%s)", filename);
+ return error;
+ }
+ }
+ eftype = EF_GET_TYPE(&ef);
+ if (eftype != EFT_KLD && eftype != EFT_KERNEL) {
+ EF_CLOSE(&ef);
+ return 0;
+ }
+ if (!dflag) {
+ cp = strrchr(kldname, '.');
+ nmlen = (cp != NULL) ? cp - kldname : (int)strlen(kldname);
+ if (nmlen > MAXMODNAME)
+ nmlen = MAXMODNAME;
+ strlcpy(kldmodname, kldname, nmlen);
+/* fprintf(fxref, "%s:%s:%d\n", kldmodname, kldname, 0);*/
+ }
+ do {
+ check(EF_LOOKUP_SET(&ef, MDT_SETNAME, &start, &finish,
+ &entries));
+ check(EF_SEG_READ_ENTRY_REL(&ef, start, sizeof(*p) * entries,
+ (void *)&p));
+ orgp = p;
+ while(entries--) {
+ check(EF_SEG_READ_REL(&ef, (Elf_Off)*p, sizeof(md),
+ &md));
+ p++;
+ check(EF_SEG_READ(&ef, (Elf_Off)md.md_cval,
+ sizeof(cval), cval));
+ cval[MAXMODNAME] = '\0';
+ parse_entry(&md, cval, &ef, kldname);
+ }
+ if (error)
+ warnc(error, "error while reading %s", filename);
+ free(orgp);
+ } while(0);
+ EF_CLOSE(&ef);
+ return error;
+}
+
+/*
+ * Create a temp file in directory root, make sure we don't
+ * overflow the buffer for the destination name
+ */
+static FILE *
+maketempfile(char *dest, const char *root)
+{
+ char *p;
+ int n, fd;
+
+ p = strrchr(root, '/');
+ n = p != NULL ? p - root + 1 : 0;
+ if (snprintf(dest, MAXPATHLEN, "%.*slhint.XXXXXX", n, root) >=
+ MAXPATHLEN) {
+ errno = ENAMETOOLONG;
+ return NULL;
+ }
+
+ fd = mkstemp(dest);
+ if (fd < 0)
+ return NULL;
+ fchmod(fd, 0644); /* nothing secret in the file */
+ return fdopen(fd, "w+");
+}
+
+static char xrefname[MAXPATHLEN], tempname[MAXPATHLEN];
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "%s\n",
+ "usage: kldxref [-Rdv] [-f hintsfile] path ..."
+ );
+ exit(1);
+}
+
+static int
+compare(const FTSENT *const *a, const FTSENT *const *b)
+{
+ if ((*a)->fts_info == FTS_D && (*b)->fts_info != FTS_D)
+ return 1;
+ if ((*a)->fts_info != FTS_D && (*b)->fts_info == FTS_D)
+ return -1;
+ return strcmp((*a)->fts_name, (*b)->fts_name);
+}
+
+int
+main(int argc, char *argv[])
+{
+ FTS *ftsp;
+ FTSENT *p;
+ int opt, fts_options, ival;
+ struct stat sb;
+
+ fts_options = FTS_PHYSICAL;
+
+ while ((opt = getopt(argc, argv, "Rdf:v")) != -1) {
+ switch (opt) {
+ case 'd': /* no hint file, only print on stdout */
+ dflag = 1;
+ break;
+ case 'f': /* use this name instead of linker.hints */
+ xref_file = optarg;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'R': /* recurse on directories */
+ fts_options |= FTS_COMFOLLOW;
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ if (argc - optind < 1)
+ usage();
+ argc -= optind;
+ argv += optind;
+
+ if (stat(argv[0], &sb) != 0)
+ err(1, "%s", argv[0]);
+ if ((sb.st_mode & S_IFDIR) == 0) {
+ errno = ENOTDIR;
+ err(1, "%s", argv[0]);
+ }
+
+ ftsp = fts_open(argv, fts_options, compare);
+ if (ftsp == NULL)
+ exit(1);
+
+ for (;;) {
+ p = fts_read(ftsp);
+ if ((p == NULL || p->fts_info == FTS_D) && fxref) {
+ /* close and rename the current hint file */
+ fclose(fxref);
+ fxref = NULL;
+ if (reccnt) {
+ rename(tempname, xrefname);
+ } else {
+ /* didn't find any entry, ignore this file */
+ unlink(tempname);
+ unlink(xrefname);
+ }
+ }
+ if (p == NULL)
+ break;
+ if (p->fts_info == FTS_D && !dflag) {
+ /* visiting a new directory, create a new hint file */
+ snprintf(xrefname, sizeof(xrefname), "%s/%s",
+ ftsp->fts_path, xref_file);
+ fxref = maketempfile(tempname, ftsp->fts_path);
+ if (fxref == NULL)
+ err(1, "can't create %s", tempname);
+ ival = 1;
+ fwrite(&ival, sizeof(ival), 1, fxref);
+ reccnt = 0;
+ }
+ /* skip non-files and separate debug files */
+ if (p->fts_info != FTS_F)
+ continue;
+ if (p->fts_namelen >= 6 &&
+ strcmp(p->fts_name + p->fts_namelen - 6, ".debug") == 0)
+ continue;
+ if (p->fts_namelen >= 8 &&
+ strcmp(p->fts_name + p->fts_namelen - 8, ".symbols") == 0)
+ continue;
+ read_kld(p->fts_path, p->fts_name);
+ }
+ fts_close(ftsp);
+ return 0;
+}
diff --git a/usr.sbin/lastlogin/Makefile b/usr.sbin/lastlogin/Makefile
new file mode 100644
index 0000000..715badd
--- /dev/null
+++ b/usr.sbin/lastlogin/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= lastlogin
+MAN= lastlogin.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lastlogin/Makefile.depend b/usr.sbin/lastlogin/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/lastlogin/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lastlogin/lastlogin.8 b/usr.sbin/lastlogin/lastlogin.8
new file mode 100644
index 0000000..fdbc871
--- /dev/null
+++ b/usr.sbin/lastlogin/lastlogin.8
@@ -0,0 +1,94 @@
+.\" $FreeBSD$
+.\" $NetBSD: lastlogin.8,v 1.3 1998/02/06 06:20:39 perry Exp $
+.\"
+.\" Copyright (c) 1996 John M. Vinopal
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 John M. Vinopal.
+.\" 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.
+.\"
+.Dd June 6, 2011
+.Dt LASTLOGIN 8
+.Os
+.Sh NAME
+.Nm lastlogin
+.Nd indicate last login time of users
+.Sh SYNOPSIS
+.Nm
+.Op Fl f Ar file
+.Op Fl rt
+.Op Ar user ...
+.Sh DESCRIPTION
+The
+.Nm
+utility will list the last login session of each specified
+.Ar user ,
+or for all users by default.
+Each line of output contains
+the user name, the tty from which the session was conducted, any
+hostname, and the start time for the session.
+.Pp
+If more than one
+.Ar user
+is given, the session information for each user is printed in
+the order given on the command line.
+Otherwise, information for all users is printed.
+By default, the entries are sorted by user name.
+.Pp
+The
+.Nm
+utility differs from
+.Xr last 1
+in that it only prints information regarding the very last login session.
+The last login database is never turned over or deleted in standard usage.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl f Ar file
+Open last login database
+.Ar file
+instead of the system-wide database.
+.It Fl r
+Print the entries in reverse sorted order.
+.It Fl t
+Sort the elements by last login time, instead of user name.
+.El
+.Sh FILES
+.Bl -tag -width /var/log/utx.lastlogin -compact
+.It Pa /var/log/utx.lastlogin
+last login database
+.El
+.Sh SEE ALSO
+.Xr last 1 ,
+.Xr getutxent 3 ,
+.Xr ac 8
+.Sh AUTHORS
+.An John M. Vinopal
+wrote this program in January 1996 and contributed it
+to the
+.Nx
+project.
diff --git a/usr.sbin/lastlogin/lastlogin.c b/usr.sbin/lastlogin/lastlogin.c
new file mode 100644
index 0000000..2f8dd78
--- /dev/null
+++ b/usr.sbin/lastlogin/lastlogin.c
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 1996 John M. Vinopal
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 John M. Vinopal.
+ * 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>
+#ifndef lint
+__RCSID("$FreeBSD$");
+__RCSID("$NetBSD: lastlogin.c,v 1.4 1998/02/03 04:45:35 perry Exp $");
+#endif
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <utmpx.h>
+
+ int main(int, char **);
+static void output(struct utmpx *);
+static void usage(void);
+static int utcmp_user(const void *, const void *);
+
+static int order = 1;
+static const char *file = NULL;
+static int (*utcmp)(const void *, const void *) = utcmp_user;
+
+static int
+utcmp_user(const void *u1, const void *u2)
+{
+
+ return (order * strcmp(((const struct utmpx *)u1)->ut_user,
+ ((const struct utmpx *)u2)->ut_user));
+}
+
+static int
+utcmp_time(const void *u1, const void *u2)
+{
+ time_t t1, t2;
+
+ t1 = ((const struct utmpx *)u1)->ut_tv.tv_sec;
+ t2 = ((const struct utmpx *)u2)->ut_tv.tv_sec;
+ return (t1 < t2 ? order : t1 > t2 ? -order : 0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ch, i, ulistsize;
+ struct utmpx *u, *ulist;
+
+ while ((ch = getopt(argc, argv, "f:rt")) != -1) {
+ switch (ch) {
+ case 'f':
+ file = optarg;
+ break;
+ case 'r':
+ order = -1;
+ break;
+ case 't':
+ utcmp = utcmp_time;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0) {
+ /* Process usernames given on the command line. */
+ for (i = 0; i < argc; i++) {
+ if (setutxdb(UTXDB_LASTLOGIN, file) != 0)
+ err(1, "failed to open lastlog database");
+ if ((u = getutxuser(argv[i])) == NULL) {
+ warnx("user '%s' not found", argv[i]);
+ continue;
+ }
+ output(u);
+ endutxent();
+ }
+ } else {
+ /* Read all lastlog entries, looking for active ones. */
+ if (setutxdb(UTXDB_LASTLOGIN, file) != 0)
+ err(1, "failed to open lastlog database");
+ ulist = NULL;
+ ulistsize = 0;
+ while ((u = getutxent()) != NULL) {
+ if (u->ut_type != USER_PROCESS)
+ continue;
+ if ((ulistsize % 16) == 0) {
+ ulist = realloc(ulist,
+ (ulistsize + 16) * sizeof(struct utmpx));
+ if (ulist == NULL)
+ err(1, "malloc");
+ }
+ ulist[ulistsize++] = *u;
+ }
+ endutxent();
+
+ qsort(ulist, ulistsize, sizeof(struct utmpx), utcmp);
+ for (i = 0; i < ulistsize; i++)
+ output(&ulist[i]);
+ }
+
+ exit(0);
+}
+
+/* Duplicate the output of last(1) */
+static void
+output(struct utmpx *u)
+{
+ time_t t = u->ut_tv.tv_sec;
+
+ printf("%-10s %-8s %-22.22s %s",
+ u->ut_user, u->ut_line, u->ut_host, ctime(&t));
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: lastlogin [-f file] [-rt] [user ...]\n");
+ exit(1);
+}
diff --git a/usr.sbin/lmcconfig/Makefile b/usr.sbin/lmcconfig/Makefile
new file mode 100644
index 0000000..be00544
--- /dev/null
+++ b/usr.sbin/lmcconfig/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+PROG= lmcconfig
+MAN= lmcconfig.8
+
+LIBADD= netgraph
+
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lmcconfig/Makefile.depend b/usr.sbin/lmcconfig/Makefile.depend
new file mode 100644
index 0000000..92ae034
--- /dev/null
+++ b/usr.sbin/lmcconfig/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libnetgraph \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lmcconfig/lmcconfig.8 b/usr.sbin/lmcconfig/lmcconfig.8
new file mode 100644
index 0000000..6eac257
--- /dev/null
+++ b/usr.sbin/lmcconfig/lmcconfig.8
@@ -0,0 +1,723 @@
+.\" Copyright (c) 2003 David Boggs. (boggs@boggs.palo-alto.ca.us)
+.\" All rights reserved.
+.\"
+.\" BSD 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.
+.\"
+.\" 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.
+.\"
+.\" GNU General Public License:
+.\"
+.\" This program is free software; you can redistribute it and/or modify it
+.\" under the terms of the GNU General Public License as published by the Free
+.\" Software Foundation; either version 2 of the License, or (at your option)
+.\" any later version.
+.\"
+.\" This program is distributed in the hope that it will be useful, but WITHOUT
+.\" ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+.\" FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+.\" more details.
+.\"
+.\" You should have received a copy of the GNU General Public License along with
+.\" this program; if not, write to the Free Software Foundation, Inc., 59
+.\" Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd October 3, 2005
+.Dt LMCCONFIG 8
+.Os
+.Sh NAME
+.Nm lmcconfig
+.Nd configuration program for
+.Tn SBE
+(formerly
+.Tn LMC )
+wide-area network interface cards
+.Sh SYNOPSIS
+.Nm
+.Ar interface
+.Op Fl abBcCdDeEfhLmMpPrsStTuwxXyYzZ?
+.Nm
+.Ar interface
+.Fl 1
+.Op Fl aABceEfFgiIlLpPstTuUxX
+.Nm
+.Ar interface
+.Fl 3
+.Op Fl aABcefFlLsSv
+.Sh DESCRIPTION
+The
+.Nm
+utility
+is the configuration program for the
+.Xr lmc 4
+wide-area network device driver.
+It sets control values, such as T3 framing format,
+and it displays status, such as that of integrated modems,
+that are beyond the scope of
+.Xr ifconfig 8 .
+.Pp
+The
+.Nm
+utility
+displays the interface status when no parameters are specified;
+see example below.
+For this case only, if no
+.Ar interface
+is specified, it defaults to
+.Dq Li lmc0 .
+.Pp
+Only the super-user may modify the configuration of a network interface.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Ar interface
+This is the name of the interface; the default is
+.Dq Li lmc0 .
+If
+.Xr netgraph 4
+is present and the interface name ends with a colon
+then Netgraph control messages are used,
+otherwise the
+.Xr ifnet 9
+kernel interface and socket
+.Xr ioctl 2
+system calls are used.
+.It Fl 1
+All parameters after this apply to the T1E1 card.
+.It Fl 3
+All parameters after this apply to the T3 card.
+.El
+.Ss Commands for all cards
+The following parameters apply to more than one card type.
+.Bl -tag -width indent
+.It Fl a Ar number
+Set Transmitter clock source to
+.Ar number .
+.Pp
+.Bl -column "1" "External connector" "T1E1, HSSIc" -offset 2m -compact
+.It "1" Ta "TxClk from modem" Ta "T1E1, HSSI" Ta "default"
+.It "2" Ta "Internal source" Ta "T1E1, HSSI"
+.It "3" Ta "RxClk from modem" Ta "T1E1, HSSIc" Ta "loop timed"
+.It "4" Ta "External connector" Ta "T1E1, HSSIc"
+.El
+.Pp
+An HSSI card normally takes its Tx clock from the modem connector
+(it is a DTE) but can use the PCI bus clock (typically 33 MHz)
+for loopback and null modem testing; values 3 and 4 are only
+applicable to a few rare CompactPCI/HSSI cards.
+.Pp
+A T1E1 card uses an on-board synthesized oscillator
+if the value is 1 or 2; it
+.Em loop times
+(uses the clock recovered by the receiver as the transmitter clock)
+if the value is 3; and it uses a clock from a header connector on
+the card if the value is 4.
+.Pp
+TxClk source is not applicable to other card types.
+.It Fl b
+Read BIOS ROM.
+Print the first 256 locations.
+The BIOS ROM is not used and not present on some cards.
+.It Fl B
+Write BIOS ROM.
+Write the first 256 locations with an address pattern.
+.It Fl c
+Use HDLC's 16-bit CRC polynomial: X^16+X^12+X^5+1 (default).
+.It Fl C
+Use HDLC's 32-bit CRC polynomial:
+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
+.It Fl d
+Clear the driver-level debug flag.
+Non-critical log messages are suppressed.
+.It Fl D
+Set the driver-level debug flag.
+The driver generates more log messages.
+The driver also generates more log messages if the interface-level debug
+flag is set by
+.Xr ifconfig 8 .
+.It Fl e
+Set DTE (Data Terminal Equipment) mode (default).
+An SSI card transmitter uses the Tx clock signal from the modem connector
+and receives the Data Carrier Detect pin (DCD).
+DTE/DCE is not applicable to other card types except
+a few rare CompactPCI/HSSI cards.
+.It Fl E
+Set DCE (Data Communication Equipment) mode.
+An SSI card transmitter uses an on-board synthesized oscillator
+and drives the Data Carrier Detect pin (DCD).
+.It Fl f Ar number
+Set the frequency of the built-in synthesized oscillator to
+.Ar number
+bits/second.
+The nearest frequency that the synthesizer can generate will be used.
+Only SSI cards and a few rare CompactPCI/HSSI cards have synthesizers.
+.It Fl F
+Set SPPP line protocol to Frame-Relay.
+Only works for
+.Fx 5.4
+and later.
+.It Fl h
+Print help (usage message).
+.It Fl i
+Set interface name (e.g.\&
+.Dq Li lmc0 ) .
+.It Fl L Ar number
+Set loopback mode to
+.Ar number .
+.Pp
+.Bl -column "99" "payload" "inward thru drvrs/rcvrsxxx" "HSSI, SSI" -offset 1m -compact
+.It "1" Ta "none" Ta "default" Ta \&
+.It "2" Ta "payload" Ta "outward thru framer" Ta "T1E1. T3"
+.It "3" Ta "line" Ta "outward thru line if" Ta "T1E1, T3, HSSIc"
+.It "4" Ta "other" Ta "inward thru line if" Ta "T1E1, T3"
+.It "5" Ta "inward" Ta "inward thru framer" Ta "T1E1, T3"
+.It "6" Ta "dual" Ta "inward and outward" Ta "T1E1, T3"
+.It "16" Ta "tulip" Ta "inward thru Tulip chip" Ta "all cards"
+.It "17" Ta "pins" Ta "inward thru drvrs/rcvrs" Ta "SSI"
+.It "18" Ta "LA/LL" Ta "assert LA/LL modem pin" Ta "HSSI, SSI"
+.It "19" Ta "LB/RL" Ta "assert LB/RL modem pin" Ta "HSSI, SSI"
+.El
+.It Fl m
+Read Tulip MII registers.
+Print the 32 16-bit registers in the Media Independent Interface.
+.It Fl M Ar addr Ar data
+Write Tulip MII register.
+Write
+.Ar data
+into register
+.Ar addr .
+.It Fl p
+Read Tulip PCI configuration registers.
+Print the first 16 32-bit registers in the PCI configuration space.
+.It Fl P Ar addr Ar data
+Write Tulip PCI configuration register.
+Write
+.Ar data
+into register
+.Ar addr .
+.It Fl s
+Read Tulip SROM.
+Print the 64 16-bit locations.
+The PCI subsystem vendor and device IDs are kept here.
+.It Fl S Ar number
+Write Tulip SROM.
+Initializes the Tulip SROM to card type
+.Ar number .
+.Pp
+.Bl -column "9" -offset 1m -compact
+.It 3 Ta HSSI
+.It 4 Ta T3
+.It 5 Ta SSI
+.It 6 Ta T1E1
+.It 7 Ta HSSIc
+.It 8 Ta SDSL
+.It 0 Ta auto-set from MII PHYID
+.El
+.Pp
+If
+.Ar number
+is zero, then the card type is computed from the gate array
+microcode version field in the MII PHYID register.
+.Em CAUTION :
+if the SROM is incorrect, the card will be unusable!
+This command is
+.Em so
+dangerous that
+.Nm
+must be edited and recompiled to enable it.
+.It Fl t
+Read Tulip CSRs.
+Print the 16 32-bit control and status registers.
+.It Fl T Ar addr Ar data
+Write Tulip CSR.
+Write
+.Ar data
+into CSR number
+.Ar addr .
+Note that
+.Ar addr
+is a CSR number (0-15) not a byte offset into CSR space.
+.It Fl u
+Reset event counters to zero.
+The driver counts events like packets in and out, errors, discards, etc.
+The time when the counters are reset is remembered.
+.It Fl U
+Reset gate array.
+Not needed during normal operation; just for testing.
+.It Fl v
+Set verbose mode: print more stuff.
+.It Fl V
+Print the card configuration \[em] see the
+.Sx EXAMPLES
+section.
+.It Fl w
+Load gate array from on-board ROM.
+Not needed during normal operation; just for testing.
+.It Fl W Ar filename
+Load gate array microcode from
+.Ar filename .
+.It Fl x
+Select RAWIP mode \[em] bypass line protocol code.
+.It Fl X
+Select line protocol code rather than RAWIP mode.
+.It Fl y
+Disable SPPP keep-alive packets.
+.It Fl Y
+Enable SPPP keep-alive packets.
+.It Fl z
+Set SPPP line protocol to Cisco-HDLC.
+.It Fl Z
+Set SPPP line protocol to PPP.
+.It Fl ?\&
+Print help (usage message).
+.El
+.Ss Commands for T1E1 cards
+The following parameters apply to the T1E1 card type:
+.Bl -tag -width indent
+.It Fl a Sm Cm y | a | b Sm
+Stop sending alarm signal.
+.Pp
+.Bl -column "y" "Yellow Alarm" "unframed all ones; aka AIS" -offset 1m -compact
+.It "y" Ta "Yellow Alarm" Ta "varies with framing"
+.It "a" Ta "Red Alarm" Ta "unframed all ones; aka AIS"
+.It "b" Ta "Blue Alarm" Ta "unframed all ones"
+.El
+.Pp
+Red alarm, also known as AIS (Alarm Indication Signal),
+and Blue alarm are identical in T1.
+.It Fl A Sm Cm y | a | b Sm
+Start sending alarm signal (see table above).
+.It Fl B Ar number
+Send a Bit Oriented Protocol (BOP) message with code
+.Ar number .
+BOP codes are six bits.
+.It Fl c Ar number
+Set cable length to
+.Ar number
+meters (default: 10 meters).
+This is used to set receiver sensitivity
+and transmitter line build-out.
+.It Fl d
+Print the status of the on-board DSU/CSU \[em] see the
+.Sx EXAMPLES
+section.
+.It Fl e Ar number
+Set the framing format to
+.Ar number :
+.Pp
+.Bl -column "99" -offset 1m -compact
+.It 9 Ta T1-SF/AMI
+.It 27 Ta T1-ESF/B8ZS (default)
+.It 0 Ta E1-FAS
+.It 8 Ta E1-FAS+CRC
+.It 16 Ta E1-FAS+CAS
+.It 24 Ta E1-FAS+CRC+CAS
+.It 32 Ta E1-NO-framing
+.El
+.It Fl E Ar number
+Enable 64Kb time slots (TSs) for the T1E1 card.
+The
+.Ar number
+argument
+is a 32-bit hex number (default 0xFFFFFFFF).
+The LSB is TS0 and the MSB is TS31.
+TS0 and TS25-31 are ignored in T1 mode.
+TS0 and TS16 are determined by the framing format in E1 mode.
+.It Fl f
+Read framer registers.
+Print the 512 8-bit registers in the framer chip.
+.It Fl F Ar addr Ar data
+Write framer register.
+Write
+.Ar data
+into register
+.Ar addr .
+.It Fl g Ar number
+Set receiver gain range to
+.Ar number :
+.Pp
+.Bl -column "0x00" "Medium" "auto-set based on cable length (default)" -offset 1m -compact
+.It "0x24" Ta "Short" Ta "0 to 20 dB of equalized gain"
+.It "0x2C" Ta "Medium" Ta "0 to 30 dB of equalized gain"
+.It "0x34" Ta "Long" Ta "0 to 40 dB of equalized gain"
+.It "0x3F" Ta "Extend" Ta "0 to 64 dB of equalized gain (wide open)"
+.It "0xFF" Ta "Auto" Ta "auto-set based on cable length (default)"
+.El
+.Pp
+This sets the level at which
+.Em Loss-Of-Signal
+is declared.
+.It Fl i
+Send a
+.Em CSU loopback deactivate
+inband command (T1-SF only).
+.It Fl I
+Send a
+.Em CSU loopback activate
+inband command (T1-SF only).
+.It Fl l
+Send a
+.Em line loopback deactivate
+BOP message (T1-ESF only).
+.It Fl L
+Send a
+.Em line loopback activate
+BOP message (T1-ESF only).
+.It Fl p
+Send a
+.Em payload loopback deactivate
+BOP message (T1-ESF only).
+.It Fl P
+Send a
+.Em payload loopback activate
+BOP message (T1-ESF only).
+.It Fl s
+Print the status of the on-board DSU/CSU \[em] see the
+.Sx EXAMPLES
+section.
+.It Fl t
+Stop sending test pattern.
+.It Fl T Ar number
+Start sending test pattern
+.Ar number :
+.Pp
+.Bl -column "99" -offset 1m -compact
+.It 0 Ta unframed X^11+X^9+1
+.It 1 Ta unframed X^15+X^14+1
+.It 2 Ta unframed X^20+X^17+1
+.It 3 Ta unframed X^23+X^18+1
+.It 4 Ta unframed X^11+X^9+1 with 7ZS
+.It 5 Ta unframed X^15+X^14+1 with 7ZS
+.It 6 Ta unframed X^20+X^17+1 with 14ZS (QRSS)
+.It 7 Ta unframed X^23+X^18+1 with 14ZS
+.It 8 Ta framed X^11+X^9+1
+.It 9 Ta framed X^15+X^14+1
+.It 10 Ta framed X^20+X^17+1
+.It 11 Ta framed X^23+X^18+1
+.It 12 Ta framed X^11+X^9+1 with 7ZS
+.It 13 Ta framed X^15+X^14+1 with 7ZS
+.It 14 Ta framed X^20+X^17+1 with 14ZS (QRSS)
+.It 15 Ta framed X^23+X^18+1 with 14ZS
+.El
+.It Fl u Ar number
+Set transmit pulse shape to
+.Ar number :
+.Pp
+.Bl -column "99" -offset 1m -compact
+.It 0 Ta T1 DSX 0 to 40 meters
+.It 2 Ta T1 DSX 40 to 80 meters
+.It 4 Ta T1 DSX 80 to 120 meters
+.It 6 Ta T1 DSX 120 to 160 meters
+.It 8 Ta T1 DSX 160 to 200 meters
+.It 10 Ta E1 75-ohm coax pair
+.It 12 Ta E1 120-ohm twisted pairs
+.It 14 Ta T1 CSU 200 to 2000 meters; set LBO
+.It 255 Ta auto-set based on cable length and framing format (default)
+.El
+.It Fl U Ar number
+Set transmit line build-out to
+.Ar number :
+.Pp
+.Bl -column "255" "22.5 dB" "FCC option A" -offset 1m -compact
+.It " 0" Ta "0 dB" Ta "FCC option A"
+.It " 16" Ta "7.5 dB" Ta "FCC option B"
+.It " 32" Ta "15 dB" Ta "FCC option C"
+.It " 48" Ta "22.5 dB" Ta "final span"
+.It "255" Ta "auto-set based on cable length (default)" Ta \&
+.El
+.Pp
+This is only applicable if the pulse shape is T1-CSU.
+.It Fl v
+Set verbose mode: print more stuff.
+.It Fl x
+Disable transmitter outputs.
+.It Fl X
+Enable transmitter outputs.
+.El
+.Ss Commands for T3 cards
+The following parameters apply to the T3 card type:
+.Bl -tag -width indent
+.It Fl a Sm Cm y | a | b | i Sm
+Stop sending alarm signal.
+.Pp
+.Bl -column "y" "Yellow Alarm" "framed 1010... aka AIS" -offset 1m -compact
+.It "y" Ta "Yellow Alarm" Ta "X-bits set to 0"
+.It "a" Ta "Red Alarm" Ta "framed 1010... aka AIS"
+.It "b" Ta "Blue Alarm" Ta "unframed all-ones"
+.It "i" Ta "Idle signal" Ta "framed 11001100..."
+.El
+.It Fl A Sm Cm y | a | b | i Sm
+Start sending alarm signal (see table above).
+.It Fl B Ar number
+Send a BOP (Bit Oriented Protocol) message with code
+.Ar number .
+BOP codes are six bits.
+.It Fl c Ar number
+Set cable length to
+.Ar number
+meters (default: 10 meters).
+This is used to set receiver sensitivity
+and transmitter line build-out.
+.It Fl d
+Print the status of the on-board T3 DSU \[em] see the
+.Sx EXAMPLES
+section.
+.It Fl e Ar number
+Set the framing format to
+.Ar number :
+.Pp
+.Bl -column "100" -offset 1m -compact
+.It 100 Ta T3-C-bit parity
+.It 101 Ta T3-M13 format
+.El
+.It Fl f
+Read framer registers.
+Print the 22 8-bit registers in the framer chip.
+.It Fl F Ar addr Ar data
+Write framer register.
+Write
+.Ar data
+into register
+.Ar addr .
+.It Fl l
+Send a
+.Em line loopback deactivate
+BOP message.
+.It Fl L
+Send a
+.Em line loopback activate
+BOP message.
+.It Fl s
+Print the status of the on-board T3 DSU \[em] see the
+.Sx EXAMPLES
+section.
+.It Fl S Ar number
+Set payload scrambler polynomial to
+.Ar number :
+.Pp
+.Bl -column "9" -offset 1m -compact
+.It 1 Ta payload scrambler disabled
+.It 2 Ta X^43+1: DigitalLink and Kentrox
+.It 3 Ta X^20+X^17+1 w/28ZS: Larscom
+.El
+Payload scrambler polynomials are not standardized.
+.It Fl v
+Set verbose mode: print more stuff.
+.It Fl V Ar number
+Set transmit frequency offset to
+.Ar number .
+Some T3 cards can offset the transmitter frequency from 44.736 MHz.
+.Ar Number
+is in the range (0..4095); 2048 is zero offset; step size is about 3 Hz.
+A
+.Ar number
+is written to a Digital-Analog Converter (DAC) which connects
+to a Voltage Controlled Crystal Oscillator (VCXO).
+.El
+.Ss Event Counters
+The device driver counts many interesting events such as
+packets in and out, errors and discards.
+The table below lists the event counters and describes what they count.
+.Bl -tag -width ".Va underruns"
+.It Va ibytes
+Bytes received in packets with good ending status.
+.It Va obytes
+Bytes transmitted.
+.It Va ipackets
+Packets received with good ending status.
+.It Va opackets
+Packets transmitted.
+.It Va ierrors
+Packets received with bad ending status.
+.It Va oerrors
+Packets transmitted with bad ending status.
+.It Va idiscards
+Packets received but discarded because
+the input queue was full or the interface was down.
+.It Va odiscards
+Packets presented for transmission but discarded because
+the output queue was full or the interface was down.
+.It Va txdma
+Packets presented for transmission but queued and retried later
+because no DMA descriptors were available.
+This can happen during normal operation and is not an indication of trouble.
+.It Va fifo-overrun
+Packets that started to arrive, but were aborted because
+the card was unable to DMA data to memory fast enough
+to prevent the receiver fifo from overflowing.
+.It Va fifo-underrun
+Packets that started to transmit but were aborted because
+the card was unable to DMA data from the memory fast enough
+to prevent the transmitter fifo from underflowing.
+When this happens, the transmitter threshold is increased,
+so that more bytes are required to be in the fifo
+before the transmitter is started.
+.It Va missed
+Packets that are missed because the receiver is stopped.
+.It Va overruns
+Packets that are missed because the receiver
+had no DMA descriptors available.
+.It Va fdl_pkts
+Packets received on the T1 Facility Data Link.
+.It Va crc-errs
+Cyclic Redundancy Checksum errors detected by the CRC-6 in
+T1 Extended SuperFrames (ESF) or the CRC-4 in E1 frames.
+.It Va lcv-errs
+Line Coding Violation errors:
+Alternate Mark Inversion (AMI) errors for T1-SF,
+Bipolar 8-Zero Substitution (B8ZS) errors for T1-ESF, or
+High Density Bipolar with 3-Zero Substitution (HDB3) errors for E1 or
+Bipolar 3-Zero Substitution (B3ZS) errors for T3.
+.It Va frm-errs
+T1 or T3 bit errors in the frame alignment signal.
+.It Va febe-errs
+Far End Block Errors:
+T1 or T3 bit errors detected by the device at the far end of the link.
+.It Va par-errs
+T3 bit errors detected by the hop-by-hop parity mechanism.
+.It Va cpar-errs
+T3 bit errors detected by the end-to-end parity mechanism.
+.It Va mfrm-errs
+T3 bit errors in the multi-frame alignment signal.
+.El
+.Ss Transmit Speed
+The hardware counts transmit clocks divided by 2048.
+The software computes
+.Dq "Tx speed"
+from this (see
+.Sx EXAMPLES
+below).
+The transmit clock is the bit rate of the circuit divided by two if the
+circuit is idle and divided by four if the circuit is carrying a packet.
+So an idle circuit reports a Tx speed equal to its bit rate,
+and a busy circuit reports a Tx speed equal to half its bit rate.
+.Pp
+This
+.Dq "bit rate"
+does not include circuit-level overhead bits
+(such as T1 or T3 frame bits) but does include HDLC stuff bits.
+An idle T1 circuit with a raw bit rate of 1544000 and a
+bit-rate-minus-overhead of 1536000 will report a
+.Dq "Tx speed"
+of ((1536000 bitand 4095) plus or minus 4096).
+Sometimes it will even get the correct answer of 1536000, and
+if the link is fully loaded it will report about 768000 bits/sec.
+.Pp
+It is not a perfect bit rate meter (the circuit must be idle),
+but it is a useful circuit utilization meter if you know the
+circuit bit rate and do some arithmetic.
+Software recalculates
+Tx speed once a second; the measurement period has some jitter.
+.Sh EXAMPLES
+When
+.Dq Li lmc0
+is a T1E1 card,
+.Dq Li lmcconfig lmc0
+generates the following output:
+.Bd -literal -offset 2m
+Card name: lmc0
+Card type: SBE/LMC T1E1 card
+Link status: Up
+Tx Speed: 1548288
+Line Prot/Pkg: Frame-Relay/SPPP
+SPPP Keep-alives: OFF
+CRC length: 16 bits
+Loopback: None
+Tx Clk src: Internal source
+Format-Frame/Code: T1-ESF/B8ZS
+TimeSlot [31-0]: 0x01FFFFFE
+Cable length: 10 meters
+Tx pulse shape: auto-set to T1-DSX: 0 to 40 meters
+Rx gain max: auto-set to 20.0 dB
+Current time: Thu Sep 29 21:48:51 2005
+Cntrs reset: Thu Sep 29 16:21:05 2005
+RX bytes: 15053836
+RX packets: 23271
+TX bytes: 1732169
+TX packets: 20526
+Rx fdl pkts: 5443
+.Ed
+.Pp
+When
+.Dq Li lmc0
+is a T1E1 card,
+.Dq Li "lmcconfig lmc0 -1 -d"
+generates the following output:
+.Bd -literal -offset 2m
+Format-Frame/Code: T1-ESF/B8ZS
+TimeSlot [31-0]: 0x01FFFFFE
+Tx Clk src: Internal source
+Tx Speed: 1548288
+Tx pulse shape: T1-DSX: 0 to 40 meters
+Tx outputs: Enabled
+Line impedance: 100 ohms
+Max line loss: 20.0 dB
+Cur line loss: 3.1 dB
+Invert data: No
+Line loop: No
+Payload loop: No
+Framer loop: No
+Analog loop: No
+Tx AIS: No
+Rx AIS: No
+Tx BOP RAI: No
+Rx BOP RAI: No
+Rx LOS analog: No
+Rx LOS digital: No
+Rx LOF: No
+Tx QRS: No
+Rx QRS: No
+LCV errors: 0
+CRC errors: 0
+Frame errors: 0
+Sev Err Frms: 0
+Change of Frm align: 0
+Loss of Frame events: 0
+Last Tx BOP msg: 0x00 (Yellow Alarm (far end LOF))
+Last Rx BOP msg: 0x00 (Yellow Alarm (far end LOF))
+SNMP Near-end performance data:
+ LCV=0 LOS=0 FE=0 CRC=0 AIS=0 SEF=0 OOF=0 RAI=0
+ANSI Far-end performance reports:
+ SEQ=1 CRC=0 SE=0 FE=0 LV=0 SL=0 LB=0
+ SEQ=0 CRC=0 SE=0 FE=0 LV=0 SL=0 LB=0
+ SEQ=3 CRC=0 SE=0 FE=0 LV=0 SL=0 LB=0
+ SEQ=2 CRC=0 SE=0 FE=0 LV=0 SL=0 LB=0
+.Ed
+.Sh DIAGNOSTICS
+Messages indicating the specified interface does not exist, or
+the user is not privileged and tried to alter an interface's configuration.
+.Sh SEE ALSO
+.Xr ioctl 2 ,
+.Xr lmc 4 ,
+.Xr netgraph 4 ,
+.Xr ifconfig 8 ,
+.Xr ifnet 9
+.Pp
+.Pa http://www.sbei.com/
+.Sh HISTORY
+This is a total rewrite of the program
+.Nm lmcctl
+by
+.An Andrew Stanley-Jones .
+.Sh AUTHORS
+.An David Boggs Aq Mt boggs@boggs.palo-alto.ca.us
diff --git a/usr.sbin/lmcconfig/lmcconfig.c b/usr.sbin/lmcconfig/lmcconfig.c
new file mode 100644
index 0000000..7fbc216
--- /dev/null
+++ b/usr.sbin/lmcconfig/lmcconfig.c
@@ -0,0 +1,2553 @@
+/*
+ * First author: Michael Graff.
+ * Copyright (c) 1997-2000 Lan Media Corp. (www.lanmedia.com).
+ * All rights reserved.
+ *
+ * Second author: Andrew Stanley-Jones.
+ * Copyright (c) 2000-2002 SBE Corp. (www.sbei.com).
+ * All rights reserved.
+ *
+ * Third author: David Boggs.
+ * Copyright (c) 2002-2004 David Boggs. (boggs@boggs.palo-alto.ca.us).
+ * All rights reserved.
+ *
+ * BSD 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.
+ *
+ * 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.
+ *
+ * GNU General Public License:
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Description:
+ *
+ * This program configures the Unix/Linux device driver for SBE Corp's
+ * wanADAPT and wanPMC series of Wide Area Network Interface Cards.
+ * There is a man page for this program; go find it.
+ *
+ * If Netgraph is present (FreeBSD only):
+ * cc -o lmcconfig -l netgraph -D NETGRAPH lmcconfig.c
+ * If Netgraph is NOT present:
+ * cc -o lmcconfig lmcconfig.c
+ * Install the executable program in /usr/local/sbin/lmcconfig.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+#include <unistd.h>
+#if defined(NETGRAPH)
+# include <netgraph.h>
+#endif
+#include <net/if.h>
+
+#include <dev/lmc/if_lmc.h>
+
+/* program global variables */
+char * progname; /* name of this program */
+char * ifname; /* interface name */
+int fdcs; /* ifnet File Desc or ng Ctl Socket */
+struct status status; /* card status (read only) */
+struct config config; /* card configuration (read/write) */
+int netgraph = 0; /* non-zero if netgraph present */
+int summary = 0; /* print summary at end */
+int update = 0; /* update driver config */
+int verbose = 0; /* verbose output */
+u_int8_t checksum; /* gate array ucode file checksum */
+
+/* Functions currently unused. Keep compiler happy and provide prototypes. */
+void ioctl_snmp_loop(u_int32_t);
+void init_srom(int);
+
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: %s interface [-abBcCdDeEfhiLmMpPsStTuUvVwWxXyYzZ?]\n", progname);
+ fprintf(stderr, "or\n");
+ fprintf(stderr, "Usage: %s interface -1 [-aABcdeEfFgiIlLpPstTuUvxX]\n", progname);
+ fprintf(stderr, "or\n");
+ fprintf(stderr, "Usage: %s interface -3 [-aABcdefFlLsSvV]\n\n", progname);
+ fprintf(stderr, "\tInterface is the interface name, e.g. '%s'\n", ifname);
+#if defined(NETGRAPH)
+ fprintf(stderr, "\tIf interface name ends with ':' then use netgraph\n");
+#endif
+ fprintf(stderr, "\t-1 following parameters apply to T1E1 cards\n");
+ fprintf(stderr, "\t-3 following parameters apply to T3 cards\n");
+ fprintf(stderr, "\t-a <number> Set Tx clock source, where:\n");
+ fprintf(stderr, "\t 1:modem Tx clk 2:int src 3:modem Rx Clk 4:ext conn\n");
+ fprintf(stderr, "\t-b Read and print bios rom addrs 0-255\n");
+ fprintf(stderr, "\t-B Write bios rom with address pattern\n");
+ fprintf(stderr, "\t-c Set 16-bit CRC (default)\n");
+ fprintf(stderr, "\t-C Set 32-bit CRC\n");
+ fprintf(stderr, "\t-d Clear driver DEBUG flag\n");
+ fprintf(stderr, "\t-D Set driver DEBUG flag (more log msgs)\n");
+ fprintf(stderr, "\t-e Set DTE mode (default)\n");
+ fprintf(stderr, "\t-E Set DCE mode\n");
+ fprintf(stderr, "\t-f <number> Set synth osc freq in bits/sec\n");
+ fprintf(stderr, "\t-F Set SPPP line protocol to Frame-Relay\n");
+ fprintf(stderr, "\t-h Help: this usage message\n");
+ fprintf(stderr, "\t-i Interface name (eg, lmc0)\n");
+ fprintf(stderr, "\t-L <number> Set loopback: 1:none 2:payload 3:line 4:other\n");
+ fprintf(stderr, "\t 5:inward 6:dual 16:Tulip 17:pins 18:LA/LL 19:LB/RL\n");
+ fprintf(stderr, "\t-m Read and print MII regs\n");
+ fprintf(stderr, "\t-M <addr> <data> Write MII reg\n");
+ fprintf(stderr, "\t-p Read and print PCI config regs\n");
+ fprintf(stderr, "\t-P <addr> <data> Write PCI config reg\n");
+ fprintf(stderr, "\t-s Read and print Tulip SROM\n");
+ fprintf(stderr, "\t-S <number> Initialize Tulip SROM\n");
+ fprintf(stderr, "\t-t Read and print Tulip Control/Status regs\n");
+ fprintf(stderr, "\t-T <addr> <data> Write Tulip Control/status reg\n");
+ fprintf(stderr, "\t-u Reset event counters\n");
+ fprintf(stderr, "\t-U Reset gate array\n");
+ fprintf(stderr, "\t-v Set verbose printout mode\n");
+ fprintf(stderr, "\t-V Print card configuration\n");
+ fprintf(stderr, "\t-w Load gate array from ROM\n");
+ fprintf(stderr, "\t-W <filename> Load gate array from file\n");
+ fprintf(stderr, "\t-x select RAWIP mode and bypass line protocols\n");
+ fprintf(stderr, "\t-X Select line protocols: SPPP, P2P or HDLC\n");
+ fprintf(stderr, "\t-y disable SPPP keep-alive packets\n");
+ fprintf(stderr, "\t-Y enable SPPP keep-alive packets\n");
+ fprintf(stderr, "\t-z Set SPPP line protocol to Cisco-HDLC\n");
+ fprintf(stderr, "\t-Z Set SPPP line protocol to PPP\n");
+
+ fprintf(stderr, "The -1 switch precedes T1/E1 commands.\n");
+ fprintf(stderr, "\t-a <y|b|a> Stop sending Yellow|Blue|AIS signal\n");
+ fprintf(stderr, "\t-A <y|b|a> Start sending Yellow|Blue|AIS signal\n");
+ fprintf(stderr, "\t-B <number> Send BOP msg 25 times\n");
+ fprintf(stderr, "\t-c <number> Set cable length in meters\n");
+ fprintf(stderr, "\t-d Print status of T1 DSU/CSU\n");
+ fprintf(stderr, "\t-e <number> Set framing format, where:\n");
+ fprintf(stderr, "\t 27:T1-ESF 9:T1-SF 0:E1-FAS 8:E1-FAS+CRC\n");
+ fprintf(stderr, "\t 16:E1-FAS+CAS 24:E1-FAS+CRC+CAS 32:E1-NO-FRAMING\n");
+ fprintf(stderr, "\t-E <32-bit hex number> 1 activates a channel and 0 deactivates it.\n");
+ fprintf(stderr, "\t Use this to config a link in fractional T1/E1 mode\n");
+ fprintf(stderr, "\t-f Read and print Framer/LIU registers\n");
+ fprintf(stderr, "\t-F <addr> <data> Write Framer/LIU register\n");
+ fprintf(stderr, "\t-g <number> Set receiver gain, where:\n");
+ fprintf(stderr, "\t 0:short range 1:medium range\n");
+ fprintf(stderr, "\t 2:long range 3:extended range\n");
+ fprintf(stderr, "\t 4:auto-set based on cable length\n");
+ fprintf(stderr, "\t-i Send 'CSU Loop Down' inband msg\n");
+ fprintf(stderr, "\t-I Send 'CSU Loop Up' inband msg\n");
+ fprintf(stderr, "\t-l Send 'Line Loop Down' BOP msg\n");
+ fprintf(stderr, "\t-L Send 'Line Loop Up' BOP msg\n");
+ fprintf(stderr, "\t-p Send 'Payload Loop Down' BOP msg\n");
+ fprintf(stderr, "\t-P Send 'Payload Loop Up' BOP msg\n");
+ fprintf(stderr, "\t-s Print status of T1 DSU/CSU\n");
+ fprintf(stderr, "\t-t Stop sending test pattern\n");
+ fprintf(stderr, "\t-T <number> Start sending test pattern, where:\n");
+ fprintf(stderr, "\t 0:unframed 2^11 1:unframed 2^15\n");
+ fprintf(stderr, "\t 2:unframed 2^20 3:unframed 2^23\n");
+ fprintf(stderr, "\t 4:unframed 2^11 w/ZS 5:unframed 2^15 w/ZS\n");
+ fprintf(stderr, "\t 6:unframed QRSS 7:unframed 2^23 w/ZS\n");
+ fprintf(stderr, "\t 8: framed 2^11 9: framed 2^15\n");
+ fprintf(stderr, "\t 10: framed 2^20 11: framed 2^23\n");
+ fprintf(stderr, "\t 12: framed 2^11 w/ZS 13: framed 2^15 w/ZS\n");
+ fprintf(stderr, "\t 14: framed QRSS 15: framed 2^23 w/ZS\n");
+ fprintf(stderr, "\t-u <number> Set transmitter pulse shape, where:\n");
+ fprintf(stderr, "\t 0:T1-DSX 0-40m 1:T1-DSX 40-80m\n");
+ fprintf(stderr, "\t 2:T1-DSX 80-120m 3:T1-DSX 120-160m\n");
+ fprintf(stderr, "\t 4:T1-DSX 160-200m 5:E1-G.703 75ohm coax\n");
+ fprintf(stderr, "\t 6:E1-G.703 120ohm TP 7:T1-CSU Long range\n");
+ fprintf(stderr, "\t 8:auto-set based on cable length (T1 only)\n");
+ fprintf(stderr, "\t-U <number> Set line build out where:\n");
+ fprintf(stderr, "\t 0:0dB 1:7.5dB 2:15dB 3:22.5dB\n");
+ fprintf(stderr, "\t 4:auto-set based on cable length\n");
+ fprintf(stderr, "\t-v Set verbose printout mode\n");
+ fprintf(stderr, "\t-x disable Transmitter outputs\n");
+ fprintf(stderr, "\t-X enable Transmitter outputs\n");
+
+ fprintf(stderr, "The -3 switch precedes T3 commands.\n");
+ fprintf(stderr, "\t-a <y|b|a|i> Stop sending Yellow|Blue|AIS|Idle signal\n");
+ fprintf(stderr, "\t-A <y|b|a|i> Start sending Yellow|Blue|AIS|Idle signal\n");
+ fprintf(stderr, "\t-B <bopcode> Send BOP msg 10 times\n");
+ fprintf(stderr, "\t-c <number> Set cable length in meters\n");
+ fprintf(stderr, "\t-d Print status of T3 DSU/CSU\n");
+ fprintf(stderr, "\t-e <number> Set T3 frame format, where:\n");
+ fprintf(stderr, "\t 100:C-Bit Parity 101:M13\n");
+ fprintf(stderr, "\t-f Read and print Framer registers\n");
+ fprintf(stderr, "\t-F <addr> <data> Write Framer register\n");
+ fprintf(stderr, "\t-l Send 'Line Loop Down' BOP msg\n");
+ fprintf(stderr, "\t-L Send 'Line Loop Up' BOP msg\n");
+ fprintf(stderr, "\t-s Print status of T3 DSU/CSU\n");
+ fprintf(stderr, "\t-S <number> Set DS3 scrambler mode, where:\n");
+ fprintf(stderr, "\t 1:OFF 2:DigitalLink|Kentrox 3:Larse\n");
+ fprintf(stderr, "\t-v Set verbose printout mode\n");
+ fprintf(stderr, "\t-V <number> Write to T3 VCXO freq control DAC\n");
+}
+
+static void
+call_driver(unsigned long cmd, struct iohdr *iohdr)
+{
+ int error = 0;
+
+ strncpy(iohdr->ifname, ifname, sizeof(iohdr->ifname));
+ iohdr->cookie = NGM_LMC_COOKIE;
+ iohdr->iohdr = iohdr;
+
+ /* Exchange data with a running device driver. */
+#if defined(NETGRAPH)
+ if (netgraph)
+ {
+ NgSendMsg(fdcs, ifname, NGM_LMC_COOKIE, cmd, iohdr, IOCPARM_LEN(cmd));
+ if (cmd & IOC_OUT)
+ {
+ int replen = sizeof(struct ng_mesg) + IOCPARM_LEN(cmd);
+ char rep[replen]; /* storage for the reply */
+ struct ng_mesg *reply = (struct ng_mesg *)rep;
+ int rl = NgRecvMsg(fdcs, reply, replen, NULL);
+ if (rl == replen)
+ bcopy(&reply->data, iohdr, IOCPARM_LEN(cmd));
+ else
+ {
+ fprintf(stderr, "%s: NgRecvMsg returned %d bytes, expected %d\n",
+ progname, rl, replen);
+ exit(1);
+ }
+ }
+ }
+ else
+#endif
+ {
+ if ((error = ioctl(fdcs, cmd, (caddr_t)iohdr)) < 0)
+ {
+ fprintf(stderr, "%s: ioctl() returned error code %d: %s\n",
+ progname, errno, strerror(errno));
+ if (errno == ENETDOWN)
+ printf("Type: 'ifconfig %s up' then try again.\n", ifname);
+ exit(1);
+ }
+ }
+
+ if (iohdr->cookie != NGM_LMC_COOKIE)
+ {
+ fprintf(stderr, "%s: cookie = 0x%08X, expected 0x%08X\n", progname, iohdr->cookie, NGM_LMC_COOKIE);
+ fprintf(stderr, "%s: This version of %s is incompatible with the device driver\n", progname, progname);
+ exit(1);
+ }
+}
+
+static u_int32_t
+read_pci_config(u_int8_t addr)
+{
+ struct ioctl ioctl;
+
+ ioctl.iohdr.direction = DIR_IOWR;
+ ioctl.iohdr.length = sizeof(struct ioctl);
+ ioctl.cmd = IOCTL_RW_PCI;
+ ioctl.address = addr;
+
+ call_driver(LMCIOCREAD, &ioctl.iohdr);
+
+ return ioctl.data;
+}
+
+static void
+write_pci_config(u_int8_t addr, u_int32_t data)
+{
+ struct ioctl ioctl;
+
+ ioctl.iohdr.direction = DIR_IOW;
+ ioctl.iohdr.length = sizeof(struct ioctl);
+ ioctl.cmd = IOCTL_RW_PCI;
+ ioctl.address = addr;
+ ioctl.data = data;
+
+ call_driver(LMCIOCWRITE, &ioctl.iohdr);
+}
+
+static u_int32_t
+read_csr(u_int8_t addr)
+{
+ struct ioctl ioctl;
+
+ ioctl.iohdr.direction = DIR_IOWR;
+ ioctl.iohdr.length = sizeof(struct ioctl);
+ ioctl.cmd = IOCTL_RW_CSR;
+ ioctl.address = addr;
+
+ call_driver(LMCIOCREAD, &ioctl.iohdr);
+
+ return ioctl.data;
+}
+
+static void
+write_csr(u_int8_t addr, u_int32_t data)
+{
+ struct ioctl ioctl;
+
+ ioctl.iohdr.direction = DIR_IOW;
+ ioctl.iohdr.length = sizeof(struct ioctl);
+ ioctl.cmd = IOCTL_RW_CSR;
+ ioctl.address = addr;
+ ioctl.data = data;
+
+ call_driver(LMCIOCWRITE, &ioctl.iohdr);
+}
+
+static u_int16_t
+read_srom(u_int8_t addr)
+{
+ struct ioctl ioctl;
+
+ ioctl.iohdr.direction = DIR_IOWR;
+ ioctl.iohdr.length = sizeof(struct ioctl);
+ ioctl.cmd = IOCTL_RW_SROM;
+ ioctl.address = addr;
+
+ call_driver(LMCIOCREAD, &ioctl.iohdr);
+
+ return ioctl.data;
+}
+
+static void
+write_srom(u_int8_t addr, u_int16_t data)
+{
+ struct ioctl ioctl;
+
+ ioctl.iohdr.direction = DIR_IOW;
+ ioctl.iohdr.length = sizeof(struct ioctl);
+ ioctl.cmd = IOCTL_RW_SROM;
+ ioctl.address = addr;
+ ioctl.data = data;
+
+ call_driver(LMCIOCWRITE, &ioctl.iohdr);
+}
+
+static u_int8_t
+read_bios_rom(u_int32_t addr)
+{
+ struct ioctl ioctl;
+
+ ioctl.iohdr.direction = DIR_IOWR;
+ ioctl.iohdr.length = sizeof(struct ioctl);
+ ioctl.cmd = IOCTL_RW_BIOS;
+ ioctl.address = addr;
+
+ call_driver(LMCIOCREAD, &ioctl.iohdr);
+
+ return ioctl.data;
+}
+
+static void
+write_bios_rom(u_int32_t addr, u_int8_t data)
+{
+ struct ioctl ioctl;
+
+ ioctl.iohdr.direction = DIR_IOW;
+ ioctl.iohdr.length = sizeof(struct ioctl);
+ ioctl.cmd = IOCTL_RW_BIOS;
+ ioctl.address = addr;
+ ioctl.data = data;
+
+ call_driver(LMCIOCWRITE, &ioctl.iohdr);
+}
+
+static u_int16_t
+read_mii(u_int8_t addr)
+{
+ struct ioctl ioctl;
+
+ ioctl.iohdr.direction = DIR_IOWR;
+ ioctl.iohdr.length = sizeof(struct ioctl);
+ ioctl.cmd = IOCTL_RW_MII;
+ ioctl.address = addr;
+
+ call_driver(LMCIOCREAD, &ioctl.iohdr);
+
+ return ioctl.data;
+}
+
+static void
+write_mii(u_int8_t addr, u_int16_t data)
+{
+ struct ioctl ioctl;
+
+ ioctl.iohdr.direction = DIR_IOW;
+ ioctl.iohdr.length = sizeof(struct ioctl);
+ ioctl.cmd = IOCTL_RW_MII;
+ ioctl.address = addr;
+ ioctl.data = data;
+
+ call_driver(LMCIOCWRITE, &ioctl.iohdr);
+}
+
+static unsigned char
+read_framer(u_int16_t addr)
+{
+ struct ioctl ioctl;
+
+ ioctl.iohdr.direction = DIR_IOWR;
+ ioctl.iohdr.length = sizeof(struct ioctl);
+ ioctl.cmd = IOCTL_RW_FRAME;
+ ioctl.address = addr;
+
+ call_driver(LMCIOCREAD, &ioctl.iohdr);
+
+ return ioctl.data;
+}
+
+static void
+write_framer(u_int16_t addr, u_int8_t data)
+{
+ struct ioctl ioctl;
+
+ ioctl.iohdr.direction = DIR_IOW;
+ ioctl.iohdr.length = sizeof(struct ioctl);
+ ioctl.cmd = IOCTL_RW_FRAME;
+ ioctl.address = addr;
+ ioctl.data = data;
+
+ call_driver(LMCIOCWRITE, &ioctl.iohdr);
+}
+
+static void
+write_synth(struct synth synth)
+{
+ struct ioctl ioctl;
+
+ ioctl.iohdr.direction = DIR_IOW;
+ ioctl.iohdr.length = sizeof(struct ioctl);
+ ioctl.cmd = IOCTL_WO_SYNTH;
+ bcopy(&synth, &ioctl.data, sizeof(synth));
+
+ call_driver(LMCIOCWRITE, &ioctl.iohdr);
+}
+
+static void
+write_dac(u_int16_t data)
+{
+ struct ioctl ioctl;
+
+ ioctl.iohdr.direction = DIR_IOW;
+ ioctl.iohdr.length = sizeof(struct ioctl);
+ ioctl.cmd = IOCTL_WO_DAC;
+ ioctl.data = data;
+
+ call_driver(LMCIOCWRITE, &ioctl.iohdr);
+}
+
+static void
+reset_xilinx(void)
+{
+ struct ioctl ioctl;
+
+ ioctl.iohdr.direction = DIR_IOWR;
+ ioctl.iohdr.length = sizeof(struct ioctl);
+ ioctl.cmd = IOCTL_XILINX_RESET;
+
+ call_driver(LMCIOCTL, &ioctl.iohdr);
+}
+
+static void
+load_xilinx_from_rom(void)
+{
+ struct ioctl ioctl;
+
+ ioctl.iohdr.direction = DIR_IOWR;
+ ioctl.iohdr.length = sizeof(struct ioctl);
+ ioctl.cmd = IOCTL_XILINX_ROM;
+
+ call_driver(LMCIOCTL, &ioctl.iohdr);
+}
+
+static void
+load_xilinx_from_file(char *ucode, u_int32_t len)
+{
+ struct ioctl ioctl;
+
+ ioctl.iohdr.direction = DIR_IOWR;
+ ioctl.iohdr.length = sizeof(struct ioctl);
+ ioctl.cmd = IOCTL_XILINX_FILE;
+ ioctl.data = len;
+ ioctl.ucode = ucode;
+
+ call_driver(LMCIOCTL, &ioctl.iohdr);
+}
+
+static void
+ioctl_snmp_send(u_int32_t send)
+{
+ struct ioctl ioctl;
+
+ ioctl.iohdr.direction = DIR_IOWR;
+ ioctl.iohdr.length = sizeof(struct ioctl);
+ ioctl.cmd = IOCTL_SNMP_SEND;
+ ioctl.data = send;
+
+ call_driver(LMCIOCTL, &ioctl.iohdr);
+}
+
+void
+ioctl_snmp_loop(u_int32_t loop)
+{
+ struct ioctl ioctl;
+
+ ioctl.iohdr.direction = DIR_IOWR;
+ ioctl.iohdr.length = sizeof(struct ioctl);
+ ioctl.cmd = IOCTL_SNMP_LOOP;
+ ioctl.data = loop;
+
+ call_driver(LMCIOCTL, &ioctl.iohdr);
+}
+
+static void
+ioctl_reset_cntrs(void)
+{
+ struct ioctl ioctl;
+
+ ioctl.iohdr.direction = DIR_IOWR;
+ ioctl.iohdr.length = sizeof(struct ioctl);
+ ioctl.cmd = IOCTL_RESET_CNTRS;
+
+ call_driver(LMCIOCTL, &ioctl.iohdr);
+}
+
+static void
+ioctl_read_config(void)
+{
+ config.iohdr.direction = DIR_IOWR;
+ config.iohdr.length = sizeof(struct config);
+
+ call_driver(LMCIOCGCFG, &config.iohdr);
+}
+
+static void
+ioctl_write_config(void)
+{
+ config.iohdr.direction = DIR_IOW;
+ config.iohdr.length = sizeof(struct config);
+
+ call_driver(LMCIOCSCFG, &config.iohdr);
+}
+
+static void
+ioctl_read_status(void)
+{
+ status.iohdr.direction = DIR_IOWR;
+ status.iohdr.length = sizeof(struct status);
+
+ call_driver(LMCIOCGSTAT, &status.iohdr);
+}
+
+static void
+print_card_name(void)
+{
+ printf("Card name:\t\t%s\n", ifname);
+}
+
+static void
+print_card_type(void)
+{
+ printf("Card type:\t\t");
+ switch(status.card_type)
+ {
+ case TLP_CSID_HSSI:
+ printf("HSSI (lmc5200)\n");
+ break;
+ case TLP_CSID_T3:
+ printf("T3 (lmc5245)\n");
+ break;
+ case TLP_CSID_SSI:
+ printf("SSI (lmc1000)\n");
+ break;
+ case TLP_CSID_T1E1:
+ printf("T1E1 (lmc1200)\n");
+ break;
+ case TLP_CSID_HSSIc:
+ printf("HSSI (lmc5200C)\n");
+ break;
+ default:
+ printf("unknown card_type: %d\n", status.card_type);
+ break;
+ }
+}
+
+static void
+print_status(void)
+{
+ char *status_string;
+
+ if (status.oper_status == STATUS_UP)
+ status_string = "Up";
+ else if (status.oper_status == STATUS_DOWN)
+ status_string = "Down";
+ else if (status.oper_status == STATUS_TEST)
+ status_string = "Test";
+ else
+ status_string = "Unknown";
+ printf("Link status:\t\t%s\n", status_string);
+}
+
+static void
+print_tx_speed(void)
+{
+ printf("Tx Speed:\t\t%u\n", status.tx_speed);
+}
+
+static void
+print_debug(void)
+{
+ if (config.debug != 0)
+ printf("Debug:\t\t\t%s\n", "On");
+}
+
+static void
+print_line_prot(void)
+{
+ char *on = "On", *off = "Off";
+
+ printf("Line Prot/Pkg:\t\t");
+ switch (status.line_prot)
+ {
+ case 0:
+ printf("NotSet/");
+ break;
+ case PROT_PPP:
+ printf("PPP/");
+ break;
+ case PROT_C_HDLC:
+ printf("Cisco-HDLC/");
+ break;
+ case PROT_FRM_RLY:
+ printf("Frame-Relay/");
+ break;
+ case PROT_IP_HDLC:
+ printf("IP-in-HDLC/");
+ break;
+ case PROT_ETH_HDLC:
+ printf("Ether-in-HDLC/");
+ break;
+ case PROT_X25:
+ printf("X25+LAPB/");
+ break;
+ default:
+ printf("unknown line_prot: %d/", status.line_prot);
+ break;
+ }
+
+ switch (status.line_pkg)
+ {
+ case 0:
+ printf("NotSet\n");
+ break;
+ case PKG_RAWIP:
+ printf("Driver\n");
+ break;
+ case PKG_NG:
+ printf("Netgraph\n");
+ break;
+ case PKG_GEN_HDLC:
+ printf("GenHDLC\n");
+ break;
+ case PKG_SPPP:
+ printf("SPPP\n");
+ break;
+ case PKG_P2P:
+ printf("P2P\n");
+ break;
+ default:
+ printf("unknown line_pkg: %d\n", status.line_pkg);
+ break;
+ }
+
+ if (status.line_pkg == PKG_SPPP)
+ printf("SPPP Keep-alives:\t%s\n",
+ config.keep_alive ? on : off);
+}
+
+static void
+print_crc_len(void)
+{
+ printf("CRC length:\t\t");
+ if (config.crc_len == CFG_CRC_0)
+ printf("no CRC\n");
+ else if (config.crc_len == CFG_CRC_16)
+ printf("16 bits\n");
+ else if (config.crc_len == CFG_CRC_32)
+ printf("32 bits\n");
+ else
+ printf("bad crc_len: %d\n", config.crc_len);
+}
+
+static void
+print_loop_back(void)
+{
+ printf("Loopback:\t\t");
+ switch (config.loop_back)
+ {
+ case CFG_LOOP_NONE:
+ printf("None\n");
+ break;
+ case CFG_LOOP_PAYLOAD:
+ printf("Outward thru framer (payload loop)\n");
+ break;
+ case CFG_LOOP_LINE:
+ printf("Outward thru line interface (line loop)\n");
+ break;
+ case CFG_LOOP_OTHER:
+ printf("Inward thru line interface\n");
+ break;
+ case CFG_LOOP_INWARD:
+ printf("Inward thru framer\n");
+ break;
+ case CFG_LOOP_DUAL:
+ printf("Inward & outward (dual loop)\n");
+ break;
+ case CFG_LOOP_TULIP:
+ printf("Inward thru Tulip chip\n");
+ break;
+ case CFG_LOOP_PINS:
+ printf("Inward thru drvrs/rcvrs\n");
+ break;
+ case CFG_LOOP_LL:
+ printf("LA/LL asserted\n");
+ break;
+ case CFG_LOOP_RL:
+ printf("LB/RL asserted\n");
+ break;
+ default:
+ printf("unknown loop_back: %d\n", config.loop_back);
+ break;
+ }
+}
+
+static void
+print_tx_clk_src(void)
+{
+ printf("Tx Clk src:\t\t");
+ switch (config.tx_clk_src)
+ {
+ case CFG_CLKMUX_ST:
+ printf("Tx Clk from modem\n");
+ break;
+ case CFG_CLKMUX_INT:
+ printf("Internal source\n");
+ break;
+ case CFG_CLKMUX_RT:
+ printf("Rx Clk from modem (loop timed)\n");
+ break;
+ case CFG_CLKMUX_EXT:
+ printf("External connector\n");
+ break;
+ default:
+ printf("unknown tx_clk_src: %d\n", config.tx_clk_src);
+ break;
+ }
+}
+
+static void
+print_format(void)
+{
+ printf("Format-Frame/Code:\t");
+ switch (config.format)
+ {
+ case CFG_FORMAT_T1SF:
+ printf("T1-SF/AMI\n");
+ break;
+ case CFG_FORMAT_T1ESF:
+ printf("T1-ESF/B8ZS\n");
+ break;
+ case CFG_FORMAT_E1FAS:
+ printf("E1-FAS/HDB3\n");
+ break;
+ case CFG_FORMAT_E1FASCRC:
+ printf("E1-FAS+CRC/HDB3\n");
+ break;
+ case CFG_FORMAT_E1FASCAS:
+ printf("E1-FAS+CAS/HDB3\n");
+ break;
+ case CFG_FORMAT_E1FASCRCCAS:
+ printf("E1-FAS+CRC+CAS/HDB3\n");
+ break;
+ case CFG_FORMAT_E1NONE:
+ printf("E1-NOFRAMING/HDB3\n");
+ break;
+ case CFG_FORMAT_T3CPAR:
+ printf("T3-CParity/B3ZS\n");
+ break;
+ case CFG_FORMAT_T3M13:
+ printf("T3-M13/B3ZS\n");
+ break;
+ default:
+ printf("unknown format: %d\n", config.format);
+ break;
+ }
+}
+
+static void
+print_dte_dce(void)
+{
+ printf("DTE or DCE:\t\t");
+ switch(config.dte_dce)
+ {
+ case CFG_DTE:
+ printf("DTE (receiving TxClk)\n");
+ break;
+ case CFG_DCE:
+ printf("DCE (driving TxClk)\n");
+ break;
+ default:
+ printf("unknown dte_dce: %d\n", config.dte_dce);
+ break;
+ }
+}
+
+static void
+print_synth_freq(void)
+{
+ double Fref = 20e6;
+ double Fout, Fvco;
+
+ /* decode the synthesizer params */
+ Fvco = (Fref * (config.synth.n<<(3*config.synth.v)))/config.synth.m;
+ Fout = Fvco / (1<<(config.synth.x+config.synth.r+config.synth.prescale));
+
+ printf("Synth freq:\t\t%.0f\n", Fout);
+}
+
+static void
+synth_freq(unsigned long target)
+{
+ unsigned int n, m, v, x, r;
+ double Fout, Fvco, Ftarg;
+ double newdiff, olddiff;
+ double bestF=0.0, bestV=0.0;
+ unsigned prescale = (target < 50000) ? 9:4;
+
+ Ftarg = target<<prescale;
+ for (n=3; n<=127; n++)
+ for (m=3; m<=127; m++)
+ for (v=0; v<=1; v++)
+ for (x=0; x<=3; x++)
+ for (r=0; r<=3; r++)
+ {
+ Fvco = (SYNTH_FREF * (n<<(3*v)))/m;
+ if (Fvco < SYNTH_FMIN || Fvco > SYNTH_FMAX) continue;
+ Fout = Fvco / (1<<(x+r));
+ if (Fout >= Ftarg)
+ newdiff = Fout - Ftarg;
+ else
+ newdiff = Ftarg - Fout;
+ if (bestF >= Ftarg)
+ olddiff = bestF - Ftarg;
+ else
+ olddiff = Ftarg - bestF;
+ if ((newdiff < olddiff) ||
+ ((newdiff == olddiff) && (Fvco < bestV)))
+ {
+ config.synth.n = n;
+ config.synth.m = m;
+ config.synth.v = v;
+ config.synth.x = x;
+ config.synth.r = r;
+ config.synth.prescale = prescale;
+ bestF = Fout;
+ bestV = Fvco;
+ }
+ }
+#if 0
+ printf("Fbest=%.0f, Ftarg=%u, Fout=%.0f\n", bestF>>prescale, target, bestF);
+ printf("N=%u, M=%u, V=%u, X=%u, R=%u\n", config.synth.n,
+ config.synth.m, config.synth.v, config.synth.x, config.synth.r);
+#endif
+}
+
+static void
+print_cable_len(void)
+{
+ printf("Cable length:\t\t%d meters\n", config.cable_len);
+}
+
+static void
+print_cable_type(void)
+{
+ printf("Cable type:\t\t");
+ if (status.cable_type > 7)
+ printf("unknown cable_type: %d\n", status.cable_type);
+ else
+ printf("%s\n", ssi_cables[status.cable_type]);
+}
+
+static void
+print_time_slots(void)
+{
+ printf("TimeSlot [31-0]:\t0x%08X\n", config.time_slots);
+}
+
+static void
+print_scrambler(void)
+{
+ printf("Scrambler:\t\t");
+ if (config.scrambler == CFG_SCRAM_OFF)
+ printf("off\n");
+ else if (config.scrambler == CFG_SCRAM_DL_KEN)
+ printf("DigLink/Kentrox: X^43+1\n");
+ else if (config.scrambler == CFG_SCRAM_LARS)
+ printf("Larse: X^20+X^17+1 w/28ZS\n");
+ else
+ printf("unknown scrambler: %d\n", config.scrambler);
+}
+
+static double
+vga_dbs(u_int8_t vga)
+{
+ if (vga < 0x0F) return 0.0;
+ if ((vga >= 0x0F) && (vga <= 0x1B)) return 0.0 + 0.77 * (vga - 0x0F);
+ if ((vga >= 0x1C) && (vga <= 0x33)) return 10.0 + 1.25 * (vga - 0x1C);
+ if ((vga >= 0x34) && (vga <= 0x39)) return 40.0 + 1.67 * (vga - 0x34);
+ if ((vga >= 0x3A) && (vga < 0x3F)) return 50.0 + 2.80 * (vga - 0x3A);
+ return 64.0;
+}
+
+static void
+print_rx_gain(void)
+{
+ printf("Rx gain max:\t\t");
+
+ if (config.rx_gain == CFG_GAIN_AUTO)
+ printf("auto-set to %02.1f dB\n",
+ vga_dbs(read_framer(Bt8370_VGA_MAX) & 0x3F));
+ else
+ printf("up to %02.1f dB\n", vga_dbs(config.rx_gain));
+}
+
+static void
+print_tx_lbo(void)
+{
+ u_int8_t saved_lbo = config.tx_lbo;
+
+ printf("LBO = ");
+ if (config.tx_lbo == CFG_LBO_AUTO)
+ {
+ config.tx_lbo = read_framer(Bt8370_TLIU_CR) & 0x30;
+ printf("auto-set to ");
+ }
+
+ switch (config.tx_lbo)
+ {
+ case CFG_LBO_0DB:
+ printf("0 dB\n");
+ break;
+ case CFG_LBO_7DB:
+ printf("7.5 dB\n");
+ break;
+ case CFG_LBO_15DB:
+ printf("15 dB\n");
+ break;
+ case CFG_LBO_22DB:
+ printf("22.5 dB\n");
+ break;
+ default:
+ printf("unknown tx_lbo: %d\n", config.tx_lbo);
+ break;
+ }
+
+ if (saved_lbo == CFG_LBO_AUTO)
+ config.tx_lbo = saved_lbo;
+}
+
+static void
+print_tx_pulse(void)
+{
+ u_int8_t saved_pulse = config.tx_pulse;
+
+ printf("Tx pulse shape:\t\t");
+ if (config.tx_pulse == CFG_PULSE_AUTO)
+ {
+ config.tx_pulse = read_framer(Bt8370_TLIU_CR) & 0x0E;
+ printf("auto-set to ");
+ }
+
+ switch (config.tx_pulse)
+ {
+ case CFG_PULSE_T1DSX0:
+ printf("T1-DSX: 0 to 40 meters\n");
+ break;
+ case CFG_PULSE_T1DSX1:
+ printf("T1-DSX: 40 to 80 meters\n");
+ break;
+ case CFG_PULSE_T1DSX2:
+ printf("T1-DSX: 80 to 120 meters\n");
+ break;
+ case CFG_PULSE_T1DSX3:
+ printf("T1-DSX: 120 to 160 meters\n");
+ break;
+ case CFG_PULSE_T1DSX4:
+ printf("T1-DSX: 160 to 200 meters\n");
+ break;
+ case CFG_PULSE_E1COAX:
+ printf("E1: Twin Coax\n");
+ break;
+ case CFG_PULSE_E1TWIST:
+ printf("E1: Twisted Pairs\n");
+ break;
+ case CFG_PULSE_T1CSU:
+ printf("T1-CSU; ");
+ print_tx_lbo();
+ break;
+ default:
+ printf("unknown tx_pulse: %d\n", config.tx_pulse);
+ break;
+ }
+
+ if (saved_pulse == CFG_PULSE_AUTO)
+ config.tx_pulse = saved_pulse;
+}
+
+static void
+print_ssi_sigs(void)
+{
+ u_int32_t mii16 = status.snmp.ssi.sigs;
+ char *on = "On", *off = "Off";
+
+ printf("Modem signals:\t\tDTR=%s DSR=%s RTS=%s CTS=%s\n",
+ (mii16 & MII16_SSI_DTR) ? on : off,
+ (mii16 & MII16_SSI_DSR) ? on : off,
+ (mii16 & MII16_SSI_RTS) ? on : off,
+ (mii16 & MII16_SSI_CTS) ? on : off);
+ printf("Modem signals:\t\tDCD=%s RI=%s LL=%s RL=%s TM=%s\n",
+ (mii16 & MII16_SSI_DCD) ? on : off,
+ (mii16 & MII16_SSI_RI) ? on : off,
+ (mii16 & MII16_SSI_LL) ? on : off,
+ (mii16 & MII16_SSI_RL) ? on : off,
+ (mii16 & MII16_SSI_TM) ? on : off);
+}
+
+static void
+print_hssi_sigs(void)
+{
+ u_int32_t mii16 = status.snmp.hssi.sigs;
+ char *on = "On", *off = "Off";
+
+ printf("Modem signals:\t\tTA=%s CA=%s\n",
+ (mii16 & MII16_HSSI_TA) ? on : off,
+ (mii16 & MII16_HSSI_CA) ? on : off);
+ printf("Modem signals:\t\tLA=%s LB=%s LC=%s TM=%s\n",
+ (mii16 & MII16_HSSI_LA) ? on : off,
+ (mii16 & MII16_HSSI_LB) ? on : off,
+ (mii16 & MII16_HSSI_LC) ? on : off,
+ (mii16 & MII16_HSSI_TM) ? on : off);
+}
+
+static void
+print_events(void)
+{
+ const char *reset_time;
+ time_t now;
+
+ now = time(NULL);
+ printf("Current time:\t\t%s", ctime(&now));
+ if (status.cntrs.reset_time.tv_sec < 1000)
+ reset_time = "Never\n";
+ else
+ reset_time = ctime(&status.cntrs.reset_time.tv_sec);
+ printf("Cntrs reset:\t\t%s", reset_time);
+
+ if (status.cntrs.ibytes) printf("Rx bytes:\t\t%ju\n", (uintmax_t)status.cntrs.ibytes);
+ if (status.cntrs.obytes) printf("Tx bytes:\t\t%ju\n", (uintmax_t)status.cntrs.obytes);
+ if (status.cntrs.ipackets) printf("Rx packets:\t\t%ju\n", (uintmax_t)status.cntrs.ipackets);
+ if (status.cntrs.opackets) printf("Tx packets:\t\t%ju\n", (uintmax_t)status.cntrs.opackets);
+ if (status.cntrs.ierrors) printf("Rx errors:\t\t%u\n", status.cntrs.ierrors);
+ if (status.cntrs.oerrors) printf("Tx errors:\t\t%u\n", status.cntrs.oerrors);
+ if (status.cntrs.idiscards) printf("Rx discards:\t\t%u\n", status.cntrs.idiscards);
+ if (status.cntrs.odiscards) printf("Tx discards:\t\t%u\n", status.cntrs.odiscards);
+ if (status.cntrs.fifo_over) printf("Rx fifo overruns:\t%u\n", status.cntrs.fifo_over);
+ if (status.cntrs.fifo_under) printf("Tx fifo underruns:\t%u\n", status.cntrs.fifo_under);
+ if (status.cntrs.missed) printf("Rx missed:\t\t%u\n", status.cntrs.missed);
+ if (status.cntrs.overruns) printf("Rx overruns:\t\t%u\n", status.cntrs.overruns);
+ if (status.cntrs.fdl_pkts) printf("Rx FDL pkts:\t\t%u\n", status.cntrs.fdl_pkts);
+ if (status.cntrs.crc_errs) printf("Rx CRC:\t\t\t%u\n", status.cntrs.crc_errs);
+ if (status.cntrs.lcv_errs) printf("Rx line code:\t\t%u\n", status.cntrs.lcv_errs);
+ if (status.cntrs.frm_errs) printf("Rx F-bits:\t\t%u\n", status.cntrs.frm_errs);
+ if (status.cntrs.febe_errs) printf("Rx FEBE:\t\t%u\n", status.cntrs.febe_errs);
+ if (status.cntrs.par_errs) printf("Rx P-parity:\t\t%u\n", status.cntrs.par_errs);
+ if (status.cntrs.cpar_errs) printf("Rx C-parity:\t\t%u\n", status.cntrs.cpar_errs);
+ if (status.cntrs.mfrm_errs) printf("Rx M-bits:\t\t%u\n", status.cntrs.mfrm_errs);
+ if (config.debug)
+ { /* These events are hard to explain and may worry users, */
+ if (status.cntrs.rxdma) printf("Rx no buffs:\t\t%u\n", status.cntrs.rxdma);
+ if (status.cntrs.txdma) printf("Tx no descs:\t\t%u\n", status.cntrs.txdma);
+ if (status.cntrs.lck_watch) printf("Lck watch:\t\t%u\n", status.cntrs.lck_watch);
+ if (status.cntrs.lck_ioctl) printf("Lck ioctl:\t\t%u\n", status.cntrs.lck_ioctl);
+ if (status.cntrs.lck_intr) printf("Lck intr:\t\t%u\n", status.cntrs.lck_intr);
+ }
+}
+
+static void
+print_summary(void)
+{
+ switch(status.card_type)
+ {
+ case TLP_CSID_HSSI:
+ {
+ print_card_name();
+ print_card_type();
+ print_debug();
+ print_status();
+ print_tx_speed();
+ print_line_prot();
+ print_crc_len();
+ print_loop_back();
+ print_tx_clk_src();
+ print_hssi_sigs();
+ print_events();
+ break;
+ }
+ case TLP_CSID_T3:
+ {
+ print_card_name();
+ print_card_type();
+ print_debug();
+ print_status();
+ print_tx_speed();
+ print_line_prot();
+ print_crc_len();
+ print_loop_back();
+ print_format();
+ print_cable_len();
+ print_scrambler();
+ print_events();
+ break;
+ }
+ case TLP_CSID_SSI:
+ {
+ print_card_name();
+ print_card_type();
+ print_debug();
+ print_status();
+ print_tx_speed();
+ print_line_prot();
+ print_crc_len();
+ print_loop_back();
+ print_dte_dce();
+ print_synth_freq();
+ print_cable_type();
+ print_ssi_sigs();
+ print_events();
+ break;
+ }
+ case TLP_CSID_T1E1:
+ {
+ print_card_name();
+ print_card_type();
+ print_debug();
+ print_status();
+ print_tx_speed();
+ print_line_prot();
+ print_crc_len();
+ print_loop_back();
+ print_tx_clk_src();
+ print_format();
+ print_time_slots();
+ print_cable_len();
+ print_tx_pulse();
+ print_rx_gain();
+ print_events();
+ break;
+ }
+ case TLP_CSID_HSSIc:
+ {
+ print_card_name();
+ print_card_type();
+ print_debug();
+ print_status();
+ print_line_prot();
+ print_tx_speed();
+ print_crc_len();
+ print_loop_back();
+ print_tx_clk_src();
+ print_dte_dce();
+ print_synth_freq();
+ print_hssi_sigs();
+ print_events();
+ break;
+ }
+ default:
+ {
+ printf("%s: Unknown card type: %d\n", ifname, status.card_type);
+ break;
+ }
+ }
+}
+
+static char *
+print_t3_bop(int bop_code)
+{
+ switch(bop_code)
+ {
+ case 0x00:
+ return "far end LOF";
+ case 0x0E:
+ return "far end LOS";
+ case 0x16:
+ return "far end AIS";
+ case 0x1A:
+ return "far end IDL";
+ case 0x07:
+ return "Line Loopback activate";
+ case 0x1C:
+ return "Line Loopback deactivate";
+ case 0x1B:
+ return "Entire DS3 line";
+ default:
+ return "Unknown BOP code";
+ }
+}
+
+static void
+print_t3_snmp(void)
+{
+ printf("SNMP performance data:\n");
+ printf(" LCV=%d", status.snmp.t3.lcv);
+ printf(" LOS=%d", (status.snmp.t3.line & TLINE_LOS) ? 1 : 0);
+ printf(" PCV=%d", status.snmp.t3.pcv);
+ printf(" CCV=%d", status.snmp.t3.ccv);
+ printf(" AIS=%d", (status.snmp.t3.line & TLINE_RX_AIS) ? 1 : 0);
+ printf(" SEF=%d", (status.snmp.t3.line & T1LINE_SEF) ? 1 : 0);
+ printf(" OOF=%d", (status.snmp.t3.line & TLINE_LOF) ? 1 : 0);
+ printf(" FEBE=%d", status.snmp.t3.febe);
+ printf(" RAI=%d", (status.snmp.t3.line & TLINE_RX_RAI) ? 1 : 0);
+ printf("\n");
+}
+
+static void
+print_t3_dsu(void)
+{
+ char *no = "No", *yes = "Yes";
+ u_int16_t mii16 = read_mii(16);
+ u_int8_t ctl1 = read_framer(T3CSR_CTL1);
+ u_int8_t ctl8 = read_framer(T3CSR_CTL8);
+ u_int8_t stat9 = read_framer(T3CSR_STAT9);
+ u_int8_t ctl12 = read_framer(T3CSR_CTL12);
+ u_int8_t stat16 = read_framer(T3CSR_STAT16);
+
+ printf("Framing: \t\t%s\n", ctl1 & CTL1_M13MODE ? "M13" : "CPAR");
+ print_tx_speed();
+ printf("Scrambler: \t\t%s\n", mii16 & MII16_DS3_SCRAM ? yes : no);
+ printf("Scram poly: \t\t%s\n", mii16 & MII16_DS3_POLY ? "X^20" : "X^43");
+ printf("Cable length \t\t%s\n", mii16 & MII16_DS3_ZERO ? "Short" : "Long");
+ printf("Line loop: \t\t%s\n", mii16 & MII16_DS3_LNLBK ? yes : no);
+ printf("Payload loop: \t\t%s\n", ctl12 & CTL12_RTPLOOP ? yes : no);
+ printf("Frame loop: \t\t%s\n", ctl1 & CTL1_3LOOP ? yes : no);
+ printf("Host loop: \t\t%s\n", mii16 & MII16_DS3_TRLBK ? yes : no);
+ printf("Transmit RAI: \t\t%s\n", ctl1 & CTL1_XTX ? no : yes);
+ printf("Receive RAI \t\t%s\n", stat16 & STAT16_XERR ? yes : no);
+ printf("Transmit AIS: \t\t%s\n", ctl1 & CTL1_TXAIS ? yes : no);
+ printf("Receive AIS: \t\t%s\n", stat16 & STAT16_RAIS ? yes : no);
+ printf("Transmit IDLE: \t\t%s\n", ctl1 & CTL1_TXIDL ? yes : no);
+ printf("Receive IDLE: \t\t%s\n", stat16 & STAT16_RIDL ? yes : no);
+ printf("Transmit BLUE: \t\t%s\n", ctl8 & CTL8_TBLU ? yes : no);
+ printf("Receive BLUE: \t\t%s\n", stat9 & STAT9_RBLU ? yes : no);
+ printf("Loss of Signal:\t\t%s\n", stat16 & STAT16_RLOS ? yes : no);
+ printf("Loss of Frame: \t\t%s\n", stat16 & STAT16_ROOF ? yes : no);
+ printf("Sev Err Frms: \t\t%s\n", stat16 & STAT16_SEF ? yes : no);
+ printf("Code errors: \t\t%d\n", read_framer(T3CSR_CVLO) + (read_framer(T3CSR_CVHI)<<8));
+ printf("C-Par errors: \t\t%d\n", read_framer(T3CSR_CERR));
+ printf("P-Par errors: \t\t%d\n", read_framer(T3CSR_PERR));
+ printf("F-Bit errors: \t\t%d\n", read_framer(T3CSR_FERR));
+ printf("M-Bit errors: \t\t%d\n", read_framer(T3CSR_MERR));
+ printf("FarEndBitErrs: \t\t%d\n", read_framer(T3CSR_FEBE));
+ printf("Last Tx FEAC msg:\t0x%02X (%s)\n",
+ read_framer(T3CSR_TX_FEAC) & 0x3F,
+ print_t3_bop(read_framer(T3CSR_TX_FEAC) & 0x3F));
+ printf("Last dbl FEAC msg;\t0x%02X (%s)\n",
+ read_framer(T3CSR_DBL_FEAC) & 0x3F,
+ print_t3_bop(read_framer(T3CSR_DBL_FEAC) & 0x3F));
+ printf("Last Rx FEAC msg:\t0x%02X (%s)\n",
+ read_framer(T3CSR_RX_FEAC) & 0x3F,
+ print_t3_bop(read_framer(T3CSR_RX_FEAC) & 0x3F));
+ print_t3_snmp();
+}
+
+static void
+t3_cmd(int argc, char **argv)
+{
+ int ch;
+
+ while ((ch = getopt(argc, argv, "a:A:B:c:de:fF:lLsS:vV:")) != -1)
+ {
+ switch (ch)
+ {
+ case 'a': /* stop alarms */
+ {
+ switch (optarg[0])
+ {
+ case 'a': /* Stop sending AIS Signal */
+ {
+ write_mii(16,
+ read_mii(16) & ~MII16_DS3_FRAME);
+ write_framer(T3CSR_CTL1,
+ read_framer(T3CSR_CTL1) & ~CTL1_TXAIS);
+ if (verbose) printf("Stop sending Alarm Indication Signal (AIS)\n");
+ break;
+ }
+ case 'b': /* Stop sending Blue signal */
+ {
+ write_mii(16,
+ read_mii(16) & ~MII16_DS3_FRAME);
+ write_framer(T3CSR_CTL8,
+ read_framer(T3CSR_CTL8) & ~CTL8_TBLU);
+ if (verbose) printf("Stop sending Blue signal\n");
+ break;
+ }
+ case 'i': /* Stop sending IDLE signal */
+ {
+ write_framer(T3CSR_CTL1,
+ read_framer(T3CSR_CTL1) & ~CTL1_TXIDL);
+ if (verbose) printf("Stop sending IDLE signal\n");
+ break;
+ }
+ case 'y': /* Stop sending Yellow alarm */
+ {
+ write_framer(T3CSR_CTL1,
+ read_framer(T3CSR_CTL1) | CTL1_XTX);
+ if (verbose) printf("Stop sending Yellow alarm\n");
+ break;
+ }
+ default:
+ printf("Unknown alarm: %c\n", optarg[0]);
+ break;
+ }
+ break;
+ }
+ case 'A': /* start alarms */
+ {
+ switch (optarg[0])
+ {
+ case 'a': /* Start sending AIS Signal */
+ {
+ write_mii(16,
+ read_mii(16) | MII16_DS3_FRAME);
+ write_framer(T3CSR_CTL1,
+ read_framer(T3CSR_CTL1) | CTL1_TXAIS);
+ if (verbose) printf("Sending AIS signal (framed 1010..)\n");
+ break;
+ }
+ case 'b': /* Start sending Blue signal */
+ {
+ write_mii(16,
+ read_mii(16) | MII16_DS3_FRAME);
+ write_framer(T3CSR_CTL8,
+ read_framer(T3CSR_CTL8) | CTL8_TBLU);
+ if (verbose) printf("Sending Blue signal (unframed all 1s)\n");
+ break;
+ }
+ case 'i': /* Start sending IDLE signal */
+ {
+ write_framer(T3CSR_CTL1,
+ read_framer(T3CSR_CTL1) | CTL1_TXIDL);
+ if (verbose) printf("Sending IDLE signal (framed 1100..)\n");
+ break;
+ }
+ case 'y': /* Start sending Yellow alarm */
+ {
+ write_framer(T3CSR_CTL1,
+ read_framer(T3CSR_CTL1) & ~CTL1_XTX);
+ if (verbose) printf("Sending Yellow alarm (X-bits=0)\n");
+ break;
+ }
+ default:
+ printf("Unknown alarm: %c\n", optarg[0]);
+ break;
+ }
+ break;
+ }
+ case 'B': /* send BOP msg */
+ {
+ u_int8_t bop = strtoul(optarg, NULL, 0);
+ write_framer(T3CSR_TX_FEAC, 0xC0 + bop);
+ if (verbose) printf("Sent '0x%02X' BOP msg 10 times\n", bop);
+ break;
+ }
+ case 'c': /* set cable length */
+ {
+ config.cable_len = strtoul(optarg, NULL, 0);
+ if (verbose) print_cable_len();
+ update = 1;
+ break;
+ }
+ case 'd': /* DSU status */
+ case 's': /* deprecated */
+ {
+ print_t3_dsu();
+ break;
+ }
+ case 'e': /* set framimg format */
+ {
+ config.format = strtoul(optarg, NULL, 0);
+ if (verbose) print_format();
+ update = 1;
+ break;
+ }
+ case 'f': /* read and print framer regs */
+ {
+ int i;
+ printf("TXC03401 regs:\n");
+ printf(" 0 1 2 3 4 5 6 7");
+ for (i=0; i<21; i++)
+ {
+ if (i%8 == 0) printf("\n%02X: ", i);
+ printf("%02X ", read_framer(i));
+ }
+ printf("\n\n");
+ break;
+ }
+ case 'F': /* write framer reg */
+ {
+ u_int32_t addr = strtoul(optarg, NULL, 0);
+ u_int32_t data = strtoul(argv[optind++], NULL, 0);
+ write_framer(addr, data);
+ if (verbose)
+ {
+ data = read_framer(addr);
+ printf("Write framer register: addr = 0x%02X data = 0x%02X\n", addr, data);
+ }
+ break;
+ }
+ case 'l': /* send DS3 line loopback deactivate BOP cmd */
+ {
+ ioctl_snmp_send(TSEND_RESET);
+ if (verbose) printf("Sent 'DS3 Line Loopback deactivate' BOP cmd\n");
+ break;
+ }
+ case 'L': /* send DS3 line loopback activate BOP cmd */
+ {
+ ioctl_snmp_send(TSEND_LINE);
+ if (verbose) printf("Sent 'DS3 Line Loopback activate' BOP cmd\n");
+ break;
+ }
+ case 'S': /* set scrambler */
+ {
+ config.scrambler = strtoul(optarg, NULL, 0);
+ if (verbose) print_scrambler();
+ update = 1;
+ break;
+ }
+ case 'v': /* set verbose mode */
+ {
+ verbose = 1;
+ break;
+ }
+ case 'V': /* set T3 freq control DAC */
+ {
+ u_int32_t dac = strtoul(optarg, NULL, 0);
+ write_dac(dac);
+ if (verbose) printf("VCXO DAC value is %d\n", dac);
+ break;
+ }
+ default:
+ {
+ printf("Unknown command char: %c\n", ch);
+ exit(1);
+ } /* case */
+ } /* switch */
+ } /* while */
+} /* proc */
+
+static void
+print_test_pattern(int patt)
+{
+ printf("Test Pattern:\t\t");
+ switch (patt)
+ {
+ case 0:
+ printf("unframed X^11+X^9+1\n");
+ break;
+ case 1:
+ printf("unframed X^15+X^14+1\n");
+ break;
+ case 2:
+ printf("unframed X^20+X^17+1\n");
+ break;
+ case 3:
+ printf("unframed X^23+X^18+1\n");
+ break;
+ case 4:
+ printf("unframed X^11+X^9+1 w/7ZS\n");
+ break;
+ case 5:
+ printf("unframed X^15+X^14+1 w/7ZS\n");
+ break;
+ case 6:
+ printf("unframed X^20+X^17+1 w/14ZS (QRSS)\n");
+ break;
+ case 7:
+ printf("unframed X^23+X^18+1 w/14ZS\n");
+ break;
+ case 8:
+ printf("framed X^11+X^9+1\n");
+ break;
+ case 9:
+ printf("framed X^15+X^14+1\n");
+ break;
+ case 10:
+ printf("framed X^20+X^17+1\n");
+ break;
+ case 11:
+ printf("framed X^23+X^18+1\n");
+ break;
+ case 12:;
+ printf("framed X^11+X^9+1 w/7ZS\n");
+ break;
+ case 13:
+ printf("framed X^15+X^14+1 w/7ZS\n");
+ break;
+ case 14:
+ printf("framed X^20+X^17+1 w/14ZS (QRSS)\n");
+ break;
+ case 15:
+ printf("framed X^23+X^18+1 w/14ZS\n");
+ break;
+ }
+}
+
+static char *
+print_t1_bop(int bop_code)
+{
+ switch(bop_code)
+ {
+ case 0x00:
+ return "Yellow Alarm (far end LOF)";
+ case 0x07:
+ return "Line Loop up";
+ case 0x1C:
+ return "Line Loop down";
+ case 0x0A:
+ return "Payload Loop up";
+ case 0x19:
+ return "Payload Loop down";
+ case 0x09:
+ return "Network Loop up";
+ case 0x12:
+ return "Network Loop down";
+ default:
+ return "Unknown BOP code";
+ }
+}
+
+static void
+print_far_report(int index)
+{
+ u_int16_t far = status.snmp.t1.prm[index];
+
+ printf(" SEQ=%d ", (far & T1PRM_SEQ)>>8);
+ if (far & T1PRM_G1) printf("CRC=1");
+ else if (far & T1PRM_G2) printf("CRC=1 to 5");
+ else if (far & T1PRM_G3) printf("CRC=5 to 10");
+ else if (far & T1PRM_G4) printf("CRC=10 to 100");
+ else if (far & T1PRM_G5) printf("CRC=100 to 319");
+ else if (far & T1PRM_G6) printf("CRC>=320");
+ else printf("CRC=0");
+ printf(" SE=%d", (far & T1PRM_SE) ? 1 : 0);
+ printf(" FE=%d", (far & T1PRM_FE) ? 1 : 0);
+ printf(" LV=%d", (far & T1PRM_LV) ? 1 : 0);
+ printf(" SL=%d", (far & T1PRM_SL) ? 1 : 0);
+ printf(" LB=%d", (far & T1PRM_LB) ? 1 : 0);
+ printf("\n");
+}
+
+static void
+print_t1_snmp(void)
+{
+ printf("SNMP Near-end performance data:\n");
+ printf(" LCV=%d", status.snmp.t1.lcv);
+ printf(" LOS=%d", (status.snmp.t1.line & TLINE_LOS) ? 1 : 0);
+ printf(" FE=%d", status.snmp.t1.fe);
+ printf(" CRC=%d", status.snmp.t1.crc);
+ printf(" AIS=%d", (status.snmp.t1.line & TLINE_RX_AIS) ? 1 : 0);
+ printf(" SEF=%d", (status.snmp.t1.line & T1LINE_SEF) ? 1 : 0);
+ printf(" OOF=%d", (status.snmp.t1.line & TLINE_LOF) ? 1 : 0);
+ printf(" RAI=%d",(status.snmp.t1.line & TLINE_RX_RAI) ? 1 : 0);
+ printf("\n");
+ if (config.format == CFG_FORMAT_T1ESF)
+ {
+ printf("ANSI Far-end performance reports:\n");
+ print_far_report(0);
+ print_far_report(1);
+ print_far_report(2);
+ print_far_report(3);
+ }
+}
+
+static void
+print_t1_dsu(void)
+{
+ char *no = "No", *yes = "Yes";
+ u_int16_t mii16 = read_mii(16);
+ u_int8_t isr0 = read_framer(Bt8370_ISR0);
+ u_int8_t loop = read_framer(Bt8370_LOOP);
+ u_int8_t vga_max = read_framer(Bt8370_VGA_MAX) & 0x3F;
+ u_int8_t alm1 = read_framer(Bt8370_ALM1);
+ u_int8_t alm3 = read_framer(Bt8370_ALM3);
+ u_int8_t talm = read_framer(Bt8370_TALM);
+ u_int8_t tpatt = read_framer(Bt8370_TPATT);
+ u_int8_t tpulse = read_framer(Bt8370_TLIU_CR);
+ u_int8_t vga;
+ u_int8_t saved_pulse, saved_lbo;
+
+ /* d/c write required before read */
+ write_framer(Bt8370_VGA, 0);
+ vga = read_framer(Bt8370_VGA) & 0x3F;
+
+ print_format();
+ print_time_slots();
+ print_tx_clk_src();
+ print_tx_speed();
+
+ saved_pulse = config.tx_pulse;
+ config.tx_pulse = tpulse & 0x0E;
+ saved_lbo = config.tx_lbo;
+ config.tx_lbo = tpulse & 0x30;
+ print_tx_pulse();
+ config.tx_pulse = saved_pulse;
+ config.tx_lbo = saved_lbo;
+
+ printf("Tx outputs: \t\t%sabled\n", (mii16 & MII16_T1_XOE) ? "En" : "Dis");
+ printf("Line impedance:\t\t%s ohms\n", (mii16 & MII16_T1_Z) ? "120" : "100");
+ printf("Max line loss: \t\t%4.1f dB\n", vga_dbs(vga_max));
+ printf("Cur line loss: \t\t%4.1f dB\n", vga_dbs(vga));
+ printf("Invert data: \t\t%s\n", (mii16 & MII16_T1_INVERT) ? yes : no);
+ printf("Line loop: \t\t%s\n", (loop & LOOP_LINE) ? yes : no);
+ printf("Payload loop: \t\t%s\n", (loop & LOOP_PAYLOAD) ? yes : no);
+ printf("Framer loop: \t\t%s\n", (loop & LOOP_FRAMER) ? yes : no);
+ printf("Analog loop: \t\t%s\n", (loop & LOOP_ANALOG) ? yes : no);
+ printf("Tx AIS: \t\t%s\n", ((talm & TALM_TAIS) ||
+ ((talm & TALM_AUTO_AIS) && (alm1 & ALM1_RLOS))) ? yes : no);
+ printf("Rx AIS: \t\t%s\n", (alm1 & ALM1_RAIS) ? yes : no);
+ if (((config.format & 1)==0) && (config.format != CFG_FORMAT_E1NONE))
+ {
+ printf("Tx RAI: \t\t%s\n", ((talm & TALM_TYEL) ||
+ ((talm & TALM_AUTO_YEL) && (alm3 & ALM3_FRED))) ? yes : no);
+ printf("Rx RAI: \t\t%s\n", (alm1 & ALM1_RYEL) ? yes : no);
+ }
+ if (config.format == CFG_FORMAT_T1ESF)
+ {
+ printf("Tx BOP RAI: \t\t%s\n", (alm1 & ALM1_RLOF) ? yes : no);
+ printf("Rx BOP RAI: \t\t%s\n", (alm1 & ALM1_RMYEL) ? yes : no);
+ }
+ if ((config.format & 0x11) == 0x10) /* E1CAS */
+ {
+ printf("Rx TS16 AIS: \t\t%s\n", (alm3 & ALM3_RMAIS) ? yes : no);
+ printf("Tx TS16 RAI; \t\t%s\n",
+ ((talm & TALM_AUTO_MYEL) && (alm3 & ALM3_SRED)) ? yes : no);
+ }
+ printf("Rx LOS analog: \t\t%s\n", (alm1 & ALM1_RALOS) ? yes : no);
+ printf("Rx LOS digital:\t\t%s\n", (alm1 & ALM1_RLOS) ? yes : no);
+ printf("Rx LOF: \t\t%s\n", (alm1 & ALM1_RLOF) ? yes : no);
+ printf("Tx QRS: \t\t%s\n", (tpatt & 0x10) ? yes : no);
+ printf("Rx QRS: \t\t%s\n", (isr0 & 0x10) ? yes : no);
+ printf("LCV errors: \t\t%d\n",
+ read_framer(Bt8370_LCV_LO) + (read_framer(Bt8370_LCV_HI)<<8));
+ if (config.format != CFG_FORMAT_E1NONE)
+ {
+ if ((config.format & 1)==0) printf("Far End Block Errors:\t%d\n",
+ read_framer(Bt8370_FEBE_LO) + (read_framer(Bt8370_FEBE_HI)<<8));
+ printf("CRC errors: \t\t%d\n",
+ read_framer(Bt8370_CRC_LO) + (read_framer(Bt8370_CRC_HI)<<8));
+ printf("Frame errors: \t\t%d\n",
+ read_framer(Bt8370_FERR_LO) + (read_framer(Bt8370_FERR_HI)<<8));
+ printf("Sev Err Frms: \t\t%d\n", read_framer(Bt8370_AERR) & 0x03);
+ printf("Change of Frm align:\t%d\n", (read_framer(Bt8370_AERR) & 0x0C)>>2);
+ printf("Loss of Frame events:\t%d\n", (read_framer(Bt8370_AERR) & 0xF0)>>4);
+ }
+ if (config.format == CFG_FORMAT_T1ESF)
+ {
+ printf("Last Tx BOP msg:\t0x%02X (%s)\n",
+ read_framer(Bt8370_TBOP), print_t1_bop(read_framer(Bt8370_TBOP)));
+ printf("Last Rx BOP msg:\t0x%02X (%s)\n",
+ read_framer(Bt8370_RBOP), print_t1_bop(read_framer(Bt8370_RBOP)&0x3F));
+ }
+ print_t1_snmp();
+}
+
+static void
+t1_cmd(int argc, char **argv)
+{
+ int ch;
+
+ while ((ch = getopt(argc, argv, "a:A:B:c:de:E:fF:g:iIlLpPstT:u:U:vxX")) != -1)
+ {
+ switch (ch)
+ {
+ case 'a': /* stop alarms */
+ {
+ switch (optarg[0])
+ {
+ case 'y': /* Stop sending Yellow Alarm */
+ {
+ if ((config.format == CFG_FORMAT_T1SF) ||
+ (config.format == CFG_FORMAT_E1NONE))
+ printf("No Yellow alarm for this frame format\n");
+ else if (config.format == CFG_FORMAT_T1ESF)
+ write_framer(Bt8370_BOP, 0xE0); /* rbop 25, tbop off */
+ else
+ {
+ u_int8_t talm = read_framer(Bt8370_TALM);
+ write_framer(Bt8370_TALM, talm & ~TALM_TYEL);
+ }
+ if (verbose) printf("Stop sending Yellow alarm\n");
+ break;
+ }
+ case 'a': /* Stop sending AIS */
+ case 'b': /* Stop sending Blue Alarm */
+ {
+ u_int8_t talm = read_framer(Bt8370_TALM);
+ write_framer(Bt8370_TALM, talm & ~TALM_TAIS);
+ if (verbose) printf("Stop sending AIS/Blue signal\n");
+ break;
+ }
+ default:
+ printf("Unknown alarm: %c\n", optarg[0]);
+ }
+ break;
+ }
+ case 'A': /* start alarms */
+ {
+ switch (optarg[0])
+ {
+ case 'y': /* Start sending Yellow Alarm */
+ {
+ if ((config.format == CFG_FORMAT_T1SF) ||
+ (config.format == CFG_FORMAT_E1NONE))
+ printf("No Yellow alarm for this frame format\n");
+ else if (config.format == CFG_FORMAT_T1ESF)
+ {
+ write_framer(Bt8370_BOP, 0x0F); /* rbop off, tbop cont */
+ write_framer(Bt8370_TBOP, T1BOP_OOF);
+ }
+ else
+ {
+ u_int8_t talm = read_framer(Bt8370_TALM);
+ write_framer(Bt8370_TALM, talm | TALM_TYEL);
+ }
+ if (verbose) printf("Sending Yellow alarm\n");
+ break;
+ }
+ case 'a': /* Start sending AIS */
+ case 'b': /* Start sending Blue Alarm */
+ {
+ u_int8_t talm = read_framer(Bt8370_TALM);
+ write_framer(Bt8370_TALM, talm | TALM_TAIS);
+ if (verbose) printf("Sending AIS/Blue signal\n");
+ break;
+ }
+ default:
+ printf("Unknown alarm: %c\n", optarg[0]);
+ }
+ break;
+ }
+ case 'B': /* send BOP msg */
+ {
+ u_int8_t bop = strtoul(optarg, NULL, 0);
+ if (config.format == CFG_FORMAT_T1ESF)
+ {
+ write_framer(Bt8370_BOP, 0x0B); /* rbop off, tbop 25 */
+ write_framer(Bt8370_TBOP, bop); /* start sending BOP msg */
+ sleep(1); /* sending 25 BOP msgs takes about 100 ms. */
+ write_framer(Bt8370_BOP, 0xE0); /* rbop 25, tbop off */
+ if (verbose) printf("Sent '0x%02X' BOP msg 25 times\n", bop);
+ }
+ else
+ printf("BOP msgs only work in T1-ESF format\n");
+ break;
+ }
+ case 'c': /* set cable length */
+ {
+ config.cable_len = strtoul(optarg, NULL, 0);
+ if (verbose) print_cable_len();
+ update = 1;
+ break;
+ }
+ case 'd': /* DSU status */
+ case 's': /* deprecated */
+ {
+ print_t1_dsu();
+ break;
+ }
+ case 'e': /* set framimg format */
+ {
+ config.format = strtoul(optarg, NULL, 0);
+ if (verbose) print_format();
+ update = 1;
+ break;
+ }
+ case 'E': /* set time slots */
+ {
+ config.time_slots = strtoul(optarg, NULL, 16);
+ if (verbose) print_time_slots();
+ update = 1;
+ break;
+ }
+ case 'f': /* read and print framer regs */
+ {
+ int i;
+ printf("Bt8370 regs:\n");
+ printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F");
+ for (i=0; i<512; i++)
+ {
+ if (i%16 == 0) printf("\n%03X: ", i);
+ printf("%02X ", read_framer(i));
+ }
+ printf("\n\n");
+ break;
+ }
+ case 'F': /* write framer reg */
+ {
+ u_int32_t addr = strtoul(optarg, NULL, 0);
+ u_int32_t data = strtoul(argv[optind++], NULL, 0);
+ write_framer(addr, data);
+ if (verbose)
+ {
+ data = read_framer(addr);
+ printf("Write framer register: addr = 0x%02X data = 0x%02X\n", addr, data);
+ }
+ break;
+ }
+ case 'g': /* set receiver gain */
+ {
+ config.rx_gain = strtoul(optarg, NULL, 0);
+ if (verbose) print_rx_gain();
+ update = 1;
+ break;
+ }
+ case 'i': /* send CSU loopback deactivate inband cmd */
+ {
+ if (config.format == CFG_FORMAT_T1SF)
+ {
+ if (verbose) printf("Sending 'CSU loop down' inband cmd for 10 secs...");
+ ioctl_snmp_send(TSEND_RESET);
+ sleep(10);
+ ioctl_snmp_send(TSEND_NORMAL);
+ if (verbose) printf("done\n");
+ }
+ else
+ printf("Inband loopback cmds only work in T1-SF format");
+ break;
+ }
+ case 'I': /* send CSU loopback activate inband cmd */
+ {
+ if (config.format == CFG_FORMAT_T1SF)
+ {
+ if (verbose) printf("Sending 'CSU loop up' inband cmd for 10 secs...");
+ ioctl_snmp_send(TSEND_LINE);
+ sleep(10);
+ ioctl_snmp_send(TSEND_NORMAL);
+ if (verbose) printf("done\n");
+ }
+ else
+ printf("Inband loopback cmds only work in T1-SF format");
+ break;
+ }
+ case 'l': /* send line loopback deactivate BOP msg */
+ {
+ if (config.format == CFG_FORMAT_T1ESF)
+ {
+ ioctl_snmp_send(TSEND_RESET);
+ if (verbose) printf("Sent 'Line Loop Down' BOP cmd\n");
+ }
+ else
+ printf("BOP msgs only work in T1-ESF format\n");
+ break;
+ }
+ case 'L': /* send line loopback activate BOP msg */
+ {
+ if (config.format == CFG_FORMAT_T1ESF)
+ {
+ ioctl_snmp_send(TSEND_LINE);
+ if (verbose) printf("Sent 'Line Loop Up' BOP cmd\n");
+ }
+ else
+ printf("BOP msgs only work in T1-ESF format\n");
+ break;
+ }
+ case 'p': /* send payload loopback deactivate BOP msg */
+ {
+ if (config.format == CFG_FORMAT_T1ESF)
+ {
+ ioctl_snmp_send(TSEND_RESET);
+ if (verbose) printf("Sent 'Payload Loop Down' BOP cmd\n");
+ }
+ else
+ printf("BOP msgs only work in T1-ESF format\n");
+ break;
+ }
+ case 'P': /* send payload loopback activate BOP msg */
+ {
+ if (config.format == CFG_FORMAT_T1ESF)
+ {
+ ioctl_snmp_send(TSEND_PAYLOAD);
+ if (verbose) printf("Sent 'Payload Loop Up' BOP cmd\n");
+ }
+ else
+ printf("BOP msgs only work in T1-ESF format\n");
+ break;
+ }
+ case 't': /* stop sending test pattern */
+ {
+ ioctl_snmp_send(TSEND_NORMAL);
+ if (verbose) printf("Stop sending test pattern\n");
+ break;
+ }
+ case 'T': /* start sending test pattern */
+ {
+ u_int8_t patt = strtoul(optarg, NULL, 0);
+ write_framer(Bt8370_TPATT, 0x10 + patt);
+ write_framer(Bt8370_RPATT, 0x30 + patt);
+ if (verbose) print_test_pattern(patt);
+ break;
+ }
+ case 'u': /* set transmit pulse shape */
+ {
+ config.tx_pulse = strtoul(optarg, NULL, 0);
+ if (verbose) print_tx_pulse();
+ update = 1;
+ break;
+ }
+ case 'U': /* set tx line build-out */
+ {
+ if (config.tx_pulse == CFG_PULSE_T1CSU)
+ {
+ config.tx_lbo = strtoul(optarg, NULL, 0);
+ if (verbose) print_tx_pulse();
+ update = 1;
+ }
+ else
+ printf("LBO only meaningful if Tx Pulse is T1CSU\n");
+ break;
+ }
+ case 'v': /* set verbose mode */
+ {
+ verbose = 1;
+ break;
+ }
+ case 'x': /* disable transmitter outputs */
+ {
+ write_mii(16, read_mii(16) & ~MII16_T1_XOE);
+ if (verbose) printf("Transmitter outputs disabled\n");
+ break;
+ }
+ case 'X': /* enable transmitter outputs */
+ {
+ write_mii(16, read_mii(16) | MII16_T1_XOE);
+ if (verbose) printf("Transmitter outputs enabled\n");
+ break;
+ }
+ default:
+ {
+ printf("Unknown command char: %c\n", ch);
+ exit(1);
+ } /* case */
+ } /* switch */
+ } /* while */
+} /* proc */
+
+/* used when reading Motorola S-Record format ROM files */
+static unsigned char
+read_hex(FILE *f)
+{
+ unsigned char a, b, c;
+ for (a=0, b=0; a<2; a++)
+ {
+ c = fgetc(f);
+ c -= 48;
+ if (c > 9) c -= 7;
+ b = (b<<4) | (c & 0xF);
+ }
+ checksum += b;
+ return b;
+}
+
+static void
+load_xilinx(char *name)
+{
+ FILE *f;
+ char *ucode;
+ int i, length;
+ int c;
+
+ if (verbose) printf("Load firmware from file %s...\n", name);
+ if ((f = fopen(name, "r")) == 0)
+ {
+ perror("Failed to open file");
+ exit(1);
+ }
+
+ ucode = (char *)malloc(8192); bzero(ucode, 8192);
+
+ c = fgetc(f);
+ if (c == 'X')
+ { /* Xilinx raw bits file (foo.rbt) */
+ /* skip seven lines of boiler plate */
+ for (i=0; i<7;) if ((c=fgetc(f))=='\n') i++;
+ /* build a dense bit array */
+ i = length = 0;
+ while ((c=fgetc(f))!=EOF)
+ { /* LSB first */
+ if (c=='1') ucode[length] |= 1<<i++;
+ if (c=='0') i++;
+ if (i==8) { i=0; length++; }
+ }
+ }
+ else if (c == 'S')
+ { /* Motarola S records (foo.exo) */
+ int blklen;
+ length = 0;
+ ungetc(c, f);
+ while ((c = fgetc(f)) != EOF)
+ {
+ if (c != 'S')
+ {
+ printf("I'm confused; I expected an 'S'\n");
+ exit(1);
+ }
+ c = fgetc(f);
+ if (c == '9') break;
+ else if (c == '1')
+ {
+ checksum = 0;
+ blklen = read_hex(f) -3;
+ read_hex(f); /* hi blkaddr */
+ read_hex(f); /* lo blkaddr */
+ for (i=0; i<blklen; i++)
+ ucode[length++] = read_hex(f);
+ read_hex(f); /* process but ignore checksum */
+ if (checksum != 0xFF)
+ {
+ printf("File checksum error\n");
+ exit(1);
+ }
+ c = fgetc(f); /* throw away eol */
+ c = fgetc(f); /* throw away eol */
+ }
+ else
+ {
+ printf("I'm confused; I expected a '1' or a '9'\n");
+ exit(1);
+ }
+ } /* while */
+ } /* Motorola S-Record */
+ else
+ {
+ printf("Unknown file type giving up\n");
+ exit(1);
+ }
+
+ load_xilinx_from_file(ucode, length);
+}
+
+/* 32-bit CRC calculated right-to-left over 8-bit bytes */
+static u_int32_t
+crc32(char *bufp, int len)
+{
+ int bit, i;
+ u_int32_t data;
+ u_int32_t crc = 0xFFFFFFFFL;
+ u_int32_t poly = 0xEDB88320L;
+
+ for (i = 0; i < len; i++)
+ for (data = *bufp++, bit = 0; bit < 8; bit++, data >>= 1)
+ crc = (crc >> 1) ^ (((crc ^ data) & 1) ? poly : 0);
+
+ return crc;
+}
+
+/* 8-bit CRC calculated left-to-right over 16-bit words */
+static u_int8_t
+crc8(u_int16_t *bufp, int len)
+{
+ int bit, i;
+ u_int16_t data;
+ u_int8_t crc = 0xFF;
+ u_int8_t poly = 0x07;
+
+ for (i = 0; i < len; i++)
+ for (data = *bufp++, bit = 15; bit >= 0; bit--)
+ {
+ if ((i==8) && (bit==7)) break;
+ crc = (crc << 1) ^ ((((crc >> 7) ^ (data >> bit)) & 1) ? poly : 0);
+ }
+ return crc;
+}
+
+/* HSSI=3, DS3=4, SSI=5, T1E1=6, HSSIc=7, SDSL=8 */
+void
+init_srom(int board)
+{
+ int i;
+ u_int16_t srom[64];
+
+ /* zero the entire rom */
+ for (i=0; i<64; i++) srom[i] = 0;
+
+ srom[0] = 0x1376; /* subsys vendor id */
+ srom[1] = board ? board : (read_mii(3)>>4 & 0xF) +1;
+ srom[8] = crc8(srom, 9);
+ /* Tulip hardware checks this checksum */
+ srom[10] = 0x6000; /* ethernet address */
+ srom[11] = 0x0099; /* ethernet address */
+ srom[12] = 0x0000; /* ethernet address */
+ /* srom checksum is low 16 bits of Ethernet CRC-32 */
+ srom[63] = crc32((char *)srom, 126) ^ 0xFFFFFFFFL;
+
+ /* write the SROM */
+#if 1 /* really write it */
+ for (i=0; i<64; i++) write_srom(i, srom[i]);
+#else /* print what would be written */
+ printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F");
+ for (i=0; i<64; i++)
+ {
+ if (i%8 == 0) printf("\n%02X: ", i<<1);
+ printf("%02X %02X ", srom[i] & 0xFF, srom[i]>>8);
+ }
+ printf("\n\n");
+#endif
+}
+
+int
+main(int argc, char **argv)
+{
+ int i, error, ch;
+ char *optstring = "13a:bBcCdDeEf:Fhi:L:mM:pP:sS:tT:uUvVwW:xXyYzZ?";
+
+ progname = (char *)argv[0];
+
+ /* Here is the overall plan:
+ * 1) Read the interface name from the command line.
+ * 2) Open the device; decide if netgraph is being used.
+ * 3) Read the current interface configuration from the driver.
+ * 4) Read the command line args and carry out their actions.
+ * 5) Write the modified interface configuration to the driver.
+ */
+
+ /* 1) Read the interface name from the command line. */
+#if __linux__
+ ifname = (argc==1) ? "hdlc0" : (char *) argv[1];
+#else
+ ifname = (argc==1) ? DEVICE_NAME"0" : (char *) argv[1];
+#endif
+
+ /* 2) Open the device; decide if netgraph is being used, */
+ /* use netgraph if ifname ends with ":" */
+ for (i=0; i<16; i++) if (ifname[i] == 0) break;
+
+ /* Get a socket type file descriptor. */
+#if defined(NETGRAPH)
+ if ((netgraph = (ifname[i-1] == ':')))
+ error = NgMkSockNode(NULL, &fdcs, NULL);
+ else
+#endif
+ error = fdcs = socket(AF_INET, SOCK_DGRAM, 0);
+ if (error < 0)
+ {
+ fprintf(stderr, "%s: %s() failed: %s\n", progname,
+ netgraph? "NgMkSockNode" : "socket", strerror(errno));
+ exit(1);
+ }
+
+ /* 3) Read the current interface configuration from the driver. */
+ ioctl_read_config();
+ ioctl_read_status();
+
+ summary = (argc <= 2); /* print summary at end */
+ update = 0; /* write to card at end */
+
+ /* 4) Read the command line args and carry out their actions. */
+ optind = 2;
+ while (((ch = getopt(argc, argv, optstring)) != -1) && (argc > 2))
+ {
+ switch (ch)
+ {
+ case '1': /* T1 commands */
+ {
+ if (verbose) printf("Doing T1 settings\n");
+ if (status.card_type != TLP_CSID_T1E1)
+ {
+ printf("T1 settings only apply to T1E1 cards\n");
+ exit(1);
+ }
+ t1_cmd(argc, argv);
+ break;
+ }
+ case '3': /* T3 commands */
+ {
+ if (verbose) printf("Doing T3 settings\n");
+ if (status.card_type != TLP_CSID_T3)
+ {
+ printf("T3 settings only apply to T3 cards\n");
+ exit(1);
+ }
+ t3_cmd(argc, argv);
+ break;
+ }
+ case 'a': /* clock source */
+ {
+ if ((status.card_type != TLP_CSID_T1E1) ||
+ (status.card_type != TLP_CSID_HSSI) ||
+ (status.card_type != TLP_CSID_HSSIc))
+ {
+ if (verbose) print_tx_clk_src();
+ config.tx_clk_src = strtoul(optarg, NULL, 0);
+ update = 1;
+ }
+ else
+ printf("txclksrc only applies to T1E1 and HSSI card types\n");
+ break;
+ }
+ case 'b': /* read bios rom */
+ {
+ int i;
+ printf("Bios ROM:\n");
+ printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F");
+ for (i=0; i<256; i++)
+ {
+ if (i%16 == 0) printf("\n%02X: ", i);
+ printf("%02X ", read_bios_rom(i));
+ }
+ printf("\n\n");
+ break;
+ }
+ case 'B': /* write bios rom */
+ {
+ int i;
+ for (i=0; i<256; i++) write_bios_rom(i, 255-i);
+ if (verbose) printf("wrote (0..255) to bios rom addrs (0..255)\n");
+ break;
+ }
+ case 'c': /* set crc_len = 16 */
+ {
+ config.crc_len = CFG_CRC_16;
+ if (verbose) print_crc_len();
+ update = 1;
+ break;
+ }
+ case 'C': /* set crc_len = 32 */
+ {
+ config.crc_len = CFG_CRC_32;
+ if (verbose) print_crc_len();
+ update = 1;
+ break;
+ }
+ case 'd': /* clear DEBUG flag */
+ {
+ config.debug = 0;
+ if (verbose) printf("DEBUG flag cleared\n");
+ update = 1;
+ break;
+ }
+ case 'D': /* set DEBUG flag */
+ {
+ config.debug = 1;
+ if (verbose) printf("DEBUG flag set\n");
+ update = 1;
+ break;
+ }
+ case 'e': /* set DTE (default) */
+ {
+ if ((status.card_type == TLP_CSID_SSI) ||
+ (status.card_type == TLP_CSID_HSSIc))
+ {
+ config.dte_dce = CFG_DTE;
+ if (verbose) print_dte_dce();
+ update = 1;
+ }
+ else
+ printf("DTE cmd only applies to SSI & HSSIc cards\n");
+ break;
+ }
+ case 'E': /* set DCE */
+ {
+ if ((status.card_type == TLP_CSID_SSI) ||
+ (status.card_type == TLP_CSID_HSSIc))
+ {
+ config.dte_dce = CFG_DCE;
+ if (verbose) print_dte_dce();
+ update = 1;
+ }
+ else
+ printf("DCE cmd only applies to SSI & HSSIc cards\n");
+ break;
+ }
+ case 'f': /* set synth osc freq */
+ {
+ if ((status.card_type == TLP_CSID_SSI) ||
+ (status.card_type == TLP_CSID_HSSIc))
+ {
+ synth_freq(strtoul(optarg, NULL, 0));
+ write_synth(config.synth);
+ if (verbose) print_synth_freq();
+ }
+ else
+ printf("synth osc freq only applies to SSI & HSSIc cards\n");
+ break;
+ }
+ case 'F': /* set SPPP line protocol to Frame-Relay */
+ {
+ config.line_prot = PROT_FRM_RLY;
+ config.keep_alive = 1; /* required for LMI operation */
+ if (verbose) printf("SPPP line protocol set to Frame-Relay\n");
+ update = 1;
+ break;
+ }
+ case 'h': /* help */
+ case '?':
+ {
+ usage();
+ exit(0);
+ }
+ case 'i': /* interface name */
+ {
+ /* already scanned this */
+ break;
+ }
+ case 'L': /* set loopback modes */
+ {
+ config.loop_back = strtoul(optarg, NULL, 0);
+ if (verbose) print_loop_back();
+ update = 1;
+ break;
+ }
+ case 'm': /* read and print MII regs */
+ {
+ printf("MII regs:\n");
+ printf(" 0 1 2 3 4 5 6 7");
+ for (i=0; i<32; i++)
+ {
+ u_int16_t mii = read_mii(i);
+ if (i%8 == 0) printf("\n%02X: ", i);
+ printf("%04X ", mii);
+ }
+ printf("\n\n");
+ break;
+ }
+ case 'M': /* write MII reg */
+ {
+ u_int32_t addr = strtoul(optarg, NULL, 0);
+ u_int32_t data = strtoul(argv[optind++], NULL, 0);
+ write_mii(addr, data);
+ if (verbose)
+ {
+ data = read_mii(addr);
+ printf("Write mii register: addr = 0x%02X data = 0x%04X\n", addr, data);
+ }
+ break;
+ }
+ case 'p': /* read and print PCI config regs */
+ {
+ int i;
+ printf("21140A PCI Config regs:\n");
+ printf(" 0 1 2 3");
+ for (i=0; i<16; i++)
+ {
+ if (i%4 == 0) printf("\n%X: ", i);
+ printf("%08X ", read_pci_config(i<<2));
+ }
+ printf("\n\n");
+ break;
+ }
+ case 'P': /* write PCI config reg */
+ {
+ u_int32_t addr = strtoul(optarg, NULL, 0);
+ u_int32_t data = strtoul(argv[optind++], NULL, 0);
+ write_pci_config(addr, data);
+ if (verbose)
+ {
+ data = read_pci_config(addr);
+ printf("Write PCI config reg: addr = 0x%02X data = 0x%08X\n", addr, data);
+ }
+ break;
+ }
+ case 's': /* read and print Tulip SROM */
+ {
+ int i;
+ printf("21140A SROM:\n");
+ printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F");
+ for (i=0; i<64; i++)
+ {
+ u_int16_t srom = read_srom(i);
+ if (i%8 == 0) printf("\n%02X: ", i<<1);
+ printf("%02X %02X ", srom & 0xFF, srom>>8);
+ }
+ printf("\n\n");
+ break;
+ }
+ case 'S': /* write Tulip SROM loc */
+ {
+#if 0 /* write a single location -- not too useful */
+ u_int32_t addr = strtoul(optarg, NULL, 0);
+ u_int32_t data = strtoul(argv[optind++], NULL, 0);
+ write_mii(addr, data);
+ data = read_mii(addr);
+ printf("Write SROM: addr = 0x%02X data = 0x%04X\n", addr, data);
+#endif
+#if 0 /* write the whole SROM -- very dangerous */
+ init_srom(strtoul(optarg, NULL, 0));
+#endif
+ printf("Caution! Recompile %s to enable this.\n", progname);
+ break;
+ }
+ case 't': /* read and print Tulip CSRs */
+ {
+ int i;
+ printf("21140A CSRs:\n");
+ printf(" 0 1 2 3");
+ for (i=0; i<16; i++)
+ {
+ if (i%4 == 0) printf("\n%X: ", i);
+ printf("%08X ", read_csr(i));
+ }
+ printf("\n\n");
+ break;
+ }
+ case 'T': /* write Tulip CSR */
+ {
+ u_int32_t addr = strtoul(optarg, NULL, 0);
+ u_int32_t data = strtoul(argv[optind++], NULL, 0);
+ write_csr(addr, data);
+ if (verbose)
+ {
+ data = read_csr(addr);
+ printf("Write 21140A CSR: addr = 0x%02X data = 0x%08X\n", addr, data);
+ }
+ break;
+ }
+ case 'u': /* reset event counters */
+ {
+ ioctl_reset_cntrs();
+ if (verbose) printf("Event counters reset\n");
+ break;
+ }
+ case 'U': /* reset gate array */
+ {
+ reset_xilinx();
+ if (verbose) printf("gate array reset\n");
+ break;
+ }
+ case 'v': /* set verbose mode */
+ {
+ verbose = 1;
+ break;
+ }
+ case 'V': /* print card configuration */
+ {
+ summary = 1;
+ break;
+ }
+ case 'w': /* load gate array microcode from ROM */
+ {
+ load_xilinx_from_rom();
+ if (verbose) printf("gate array configured from on-board ROM\n");
+ break;
+ }
+ case 'W': /* load gate array microcode from file */
+ {
+ load_xilinx(optarg);
+ if (verbose) printf("gate array configured from file %s\n", optarg);
+ break;
+ }
+ case 'x': /* select RAWIP protocol */
+ {
+ config.line_pkg = PKG_RAWIP;
+ if (verbose) printf("RAWIP mode selected\n");
+ update = 1;
+ break;
+ }
+ case 'X': /* Select in-kernel line protocol packages */
+ {
+ config.line_pkg = 0;
+ if (verbose) printf("line protocol mode selected\n");
+ update = 1;
+ break;
+ }
+ case 'y': /* disable SPPP keep-alive packets */
+ {
+ if ((config.line_pkg == PKG_SPPP) &&
+ (config.line_prot == PROT_FRM_RLY))
+ printf("keep-alives must be ON for Frame-Relay/SPPP\n");
+ else
+ {
+ config.keep_alive = 0;
+ if (verbose) printf("SPPP keep-alive packets disabled\n");
+ update = 1;
+ }
+ break;
+ }
+ case 'Y': /* enable SPPP keep-alive packets */
+ {
+ config.keep_alive = 1;
+ if (verbose) printf("SPPP keep-alive packets enabled\n");
+ update = 1;
+ break;
+ }
+ case 'z': /* set SPPP line protocol to Cisco HDLC */
+ {
+ config.line_prot = PROT_C_HDLC;
+ config.keep_alive = 1;
+ if (verbose) printf("SPPP line protocol set to Cisco-HDLC\n");
+ update = 1;
+ break;
+ }
+ case 'Z': /* set SPPP line protocol to PPP */
+ {
+ config.line_prot = PROT_PPP;
+ config.keep_alive = 0;
+ if (verbose) printf("SPPP line protocol set to PPP\n");
+ update = 1;
+ break;
+ }
+ default:
+ {
+ printf("Unknown command char: %c\n", ch);
+ exit(1);
+ }
+ } /* switch */
+ } /* while */
+
+ if (summary) print_summary();
+
+ /* 5) Write the modified interface configuration to the driver. */
+ if (update) ioctl_write_config();
+
+ exit(0);
+}
diff --git a/usr.sbin/lpr/Makefile b/usr.sbin/lpr/Makefile
new file mode 100644
index 0000000..5873c07
--- /dev/null
+++ b/usr.sbin/lpr/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+SUBDIR= common_source .WAIT \
+ chkprintcap lp lpc lpd lpq lpr lprm lptest pac \
+ filters filters.ru
+SUBDIR_PARALLEL=
+
+# Questions/ideas for lpr & friends could also be sent to:
+# freebsd-print@bostonradio.org
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/lpr/Makefile.inc b/usr.sbin/lpr/Makefile.inc
new file mode 100644
index 0000000..59f8ceb
--- /dev/null
+++ b/usr.sbin/lpr/Makefile.inc
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+= -DINET6
+.endif
+
+.include "../Makefile.inc"
diff --git a/usr.sbin/lpr/chkprintcap/Makefile b/usr.sbin/lpr/chkprintcap/Makefile
new file mode 100644
index 0000000..aa08882
--- /dev/null
+++ b/usr.sbin/lpr/chkprintcap/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../common_source
+
+PROG= chkprintcap
+MAN= chkprintcap.8
+SRCS= chkprintcap.c skimprintcap.c
+
+CFLAGS+= -I${.CURDIR}/../common_source
+
+LIBADD= lpr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/chkprintcap/Makefile.depend b/usr.sbin/lpr/chkprintcap/Makefile.depend
new file mode 100644
index 0000000..5d7143d
--- /dev/null
+++ b/usr.sbin/lpr/chkprintcap/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ usr.sbin/lpr/common_source \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/chkprintcap/chkprintcap.8 b/usr.sbin/lpr/chkprintcap/chkprintcap.8
new file mode 100644
index 0000000..b415462
--- /dev/null
+++ b/usr.sbin/lpr/chkprintcap/chkprintcap.8
@@ -0,0 +1,99 @@
+.\" Copyright 1997 Massachusetts Institute of Technology
+.\"
+.\" Permission to use, copy, modify, and distribute this software and
+.\" its documentation for any purpose and without fee is hereby
+.\" granted, provided that both the above copyright notice and this
+.\" permission notice appear in all copies, that both the above
+.\" copyright notice and this permission notice appear in all
+.\" supporting documentation, and that the name of M.I.T. not be used
+.\" in advertising or publicity pertaining to distribution of the
+.\" software without specific, written prior permission. M.I.T. makes
+.\" no representations about the suitability of this software for any
+.\" purpose. It is provided "as is" without express or implied
+.\" warranty.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS
+.\" ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
+.\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
+.\" SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+.\" USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+.\" ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+.\" OR TORT (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 November 30, 1997
+.Dt CHKPRINTCAP 8
+.Os
+.Sh NAME
+.Nm chkprintcap
+.Nd check validity of entries in the print spooler database
+.Sh SYNOPSIS
+.Nm
+.Op Fl d
+.Op Fl f Ar printcap
+.Sh DESCRIPTION
+The
+.Nm
+utility scans a
+.Xr printcap 5
+database
+(named by the
+.Ar printcap
+argument, or by default
+.Pa /etc/printcap ) ,
+looking for entries which are invalid in one way or another.
+The following checks are currently implemented:
+.Bl -enum -offset indent
+.It
+.Sq Li tc=
+references were properly expanded
+.It
+.Sq Li tc=
+references did not form a loop
+.It
+No two printers share the same spool directory
+.Sq ( Li sd=
+capability).
+.El
+.Pp
+The
+.Nm
+utility exits with a status equal to the number of errors encountered before
+processing stopped.
+(In some cases, processing can stop before the
+entire file is scanned.)
+.Pp
+If the
+.Fl d
+flag is given,
+.Nm
+will attempt to create any missing spool directories, giving them
+.Sq Li u=rwx,go=rx
+(0755) mode, group
+.Sq Li daemon ,
+and the owner specified by the
+.Sq Li du=
+capability in the database (default 1, which corresponds to user
+.Sq Li daemon ) .
+.Sh SEE ALSO
+.Xr lpr 1 ,
+.Xr printcap 5 ,
+.Xr lpd 8
+.Sh AUTHORS
+The
+.Nm
+utility was written by
+.An Garrett A. Wollman Aq Mt wollman@lcs.mit.edu .
+.Sh BUGS
+Not enough sanity-checking is done.
+At a minimum, the ownership and
+mode of the spool directories should also be checked.
+Other
+parameters whose value could cause
+.Xr lpd 8
+to fail should be diagnosed.
diff --git a/usr.sbin/lpr/chkprintcap/chkprintcap.c b/usr.sbin/lpr/chkprintcap/chkprintcap.c
new file mode 100644
index 0000000..91e8299
--- /dev/null
+++ b/usr.sbin/lpr/chkprintcap/chkprintcap.c
@@ -0,0 +1,318 @@
+/*
+ * Copyright 1997 Massachusetts Institute of Technology
+ *
+ * Permission to use, copy, modify, and distribute this software and
+ * its documentation for any purpose and without fee is hereby
+ * granted, provided that both the above copyright notice and this
+ * permission notice appear in all copies, that both the above
+ * copyright notice and this permission notice appear in all
+ * supporting documentation, and that the name of M.I.T. not be used
+ * in advertising or publicity pertaining to distribution of the
+ * software without specific, written prior permission. M.I.T. makes
+ * no representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS
+ * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
+ * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+static const char copyright[] =
+ "Copyright (C) 1997, Massachusetts Institute of Technology\r\n";
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <grp.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <sys/param.h> /* needed for lp.h but not used here */
+#include <dirent.h> /* ditto */
+#include "lp.h"
+#include "lp.local.h"
+#include "pathnames.h"
+#include "skimprintcap.h"
+
+static void check_spool_dirs(void);
+static int interpret_error(const struct printer *pp, int error);
+static void make_spool_dir(const struct printer *pp);
+static void note_spool_dir(const struct printer *pp, const struct stat *st);
+static void usage(void) __dead2;
+
+static int problems; /* number of problems encountered */
+
+/*
+ * chkprintcap - check the printcap file for syntactic and semantic errors
+ * Returns the number of problems found.
+ */
+int
+main(int argc, char **argv)
+{
+ struct skiminfo *skres;
+ char *pcap_fname;
+ int c, error, makedirs, more, queuecnt, verbosity;
+ struct printer myprinter, *pp;
+
+ makedirs = 0;
+ queuecnt = 0;
+ verbosity = 0;
+ pcap_fname = NULL;
+ pp = &myprinter;
+
+ while ((c = getopt(argc, argv, "df:v")) != -1) {
+ switch (c) {
+ case 'd':
+ makedirs = 1;
+ break;
+
+ case 'f':
+ pcap_fname = strdup(optarg);
+ setprintcap(pcap_fname);
+ break;
+
+ case 'v':
+ verbosity++;
+ break;
+
+ default:
+ usage();
+ }
+ }
+
+ if (optind != argc)
+ usage();
+
+ if (pcap_fname == NULL)
+ pcap_fname = strdup(_PATH_PRINTCAP);
+
+ /*
+ * Skim through the printcap file looking for simple user-mistakes
+ * which will produce the wrong result for the user, but which may
+ * be pretty hard for the user to notice. Such user-mistakes will
+ * only generate warning messages. The (fatal-) problem count will
+ * only be incremented if there is a system problem trying to read
+ * the printcap file.
+ */
+ skres = skim_printcap(pcap_fname, verbosity);
+ if (skres->fatalerr)
+ return (skres->fatalerr);
+
+ /*
+ * Now use the standard capability-db routines to check the values
+ * in each of the queues defined in the printcap file.
+ */
+ more = firstprinter(pp, &error);
+ if (interpret_error(pp, error) && more)
+ goto next;
+
+ while (more) {
+ struct stat stab;
+
+ queuecnt++;
+ errno = 0;
+ if (stat(pp->spool_dir, &stab) < 0) {
+ if (errno == ENOENT && makedirs) {
+ make_spool_dir(pp);
+ } else {
+ problems++;
+ warn("%s: %s", pp->printer, pp->spool_dir);
+ }
+ } else {
+ note_spool_dir(pp, &stab);
+ }
+
+ /* Make other queue-specific validity checks here... */
+
+next:
+ more = nextprinter(pp, &error);
+ if (interpret_error(pp, error) && more)
+ goto next;
+ }
+
+ check_spool_dirs();
+
+ if (queuecnt != skres->entries) {
+ warnx("WARNING: found %d entries when skimming %s,",
+ skres->entries, pcap_fname);
+ warnx("WARNING: but only found %d queues to process!",
+ queuecnt);
+ }
+ return (problems);
+}
+
+/*
+ * Interpret the error code. Returns 1 if we should skip to the next
+ * record (as this record is unlikely to make sense). If the problem
+ * is very severe, exit. Otherwise, return zero.
+ */
+static int
+interpret_error(const struct printer *pp, int error)
+{
+ switch(error) {
+ case PCAPERR_OSERR:
+ err(++problems, "reading printer database");
+ case PCAPERR_TCLOOP:
+ ++problems;
+ warnx("%s: loop detected in tc= expansion", pp->printer);
+ return 1;
+ case PCAPERR_TCOPEN:
+ warnx("%s: unresolved tc= expansion", pp->printer);
+ return 1;
+ case PCAPERR_SUCCESS:
+ break;
+ default:
+ errx(++problems, "unknown printcap library error %d", error);
+ }
+ return 0;
+}
+
+/*
+ * Keep the list of spool directories. Note that we don't whine
+ * until all spool directories are noted, so that all of the more serious
+ * problems are noted first. We keep the list sorted by st_dev and
+ * st_ino, so that the problem spool directories can be noted in
+ * a single loop.
+ */
+struct dirlist {
+ LIST_ENTRY(dirlist) link;
+ struct stat stab;
+ char *path;
+ char *printer;
+};
+
+static LIST_HEAD(, dirlist) dirlist;
+
+static int
+lessp(const struct dirlist *a, const struct dirlist *b)
+{
+ if (a->stab.st_dev == b->stab.st_dev)
+ return a->stab.st_ino < b->stab.st_ino;
+ return a->stab.st_dev < b->stab.st_dev;
+}
+
+static int
+equal(const struct dirlist *a, const struct dirlist *b)
+{
+ return ((a->stab.st_dev == b->stab.st_dev)
+ && (a->stab.st_ino == b->stab.st_ino));
+}
+
+static void
+note_spool_dir(const struct printer *pp, const struct stat *st)
+{
+ struct dirlist *dp, *dp2, *last;
+
+ dp = malloc(sizeof *dp);
+ if (dp == 0)
+ err(++problems, "malloc(%lu)", (u_long)sizeof *dp);
+
+ dp->stab = *st;
+ dp->printer = strdup(pp->printer);
+ if (dp->printer == 0)
+ err(++problems, "malloc(%lu)", strlen(pp->printer) + 1UL);
+ dp->path = strdup(pp->spool_dir);
+ if (dp->path == 0)
+ err(++problems, "malloc(%lu)", strlen(pp->spool_dir) + 1UL);
+
+ last = 0;
+ LIST_FOREACH(dp2, &dirlist, link) {
+ if(!lessp(dp, dp2))
+ break;
+ last = dp2;
+ }
+
+ if (last) {
+ LIST_INSERT_AFTER(last, dp, link);
+ } else {
+ LIST_INSERT_HEAD(&dirlist, dp, link);
+ }
+}
+
+static void
+check_spool_dirs(void)
+{
+ struct dirlist *dp, *dp2;
+
+ for (dp = LIST_FIRST(&dirlist); dp; dp = dp2) {
+ dp2 = LIST_NEXT(dp, link);
+
+ if (dp2 != 0 && equal(dp, dp2)) {
+ ++problems;
+ if (strcmp(dp->path, dp2->path) == 0) {
+ warnx("%s and %s share the same spool, %s",
+ dp->printer, dp2->printer, dp->path);
+ } else {
+ warnx("%s (%s) and %s (%s) are the same "
+ "directory", dp->path, dp->printer,
+ dp2->path, dp2->printer);
+ }
+ continue;
+ }
+ /* Should probably check owners and modes here. */
+ }
+}
+
+#ifndef SPOOL_DIR_MODE
+#define SPOOL_DIR_MODE (S_IRUSR | S_IWUSR | S_IXUSR \
+ | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)
+#endif
+
+static void
+make_spool_dir(const struct printer *pp)
+{
+ char *sd = pp->spool_dir;
+ struct group *gr;
+ struct stat stab;
+
+ if (mkdir(sd, S_IRUSR | S_IXUSR) < 0) {
+ problems++;
+ warn("%s: mkdir %s", pp->printer, sd);
+ return;
+ }
+ gr = getgrnam("daemon");
+ if (gr == 0)
+ errx(++problems, "cannot locate daemon group");
+
+ if (chown(sd, pp->daemon_user, gr->gr_gid) < 0) {
+ ++problems;
+ warn("%s: cannot change ownership to %ld:%ld", sd,
+ (long)pp->daemon_user, (long)gr->gr_gid);
+ return;
+ }
+
+ if (chmod(sd, SPOOL_DIR_MODE) < 0) {
+ ++problems;
+ warn("%s: cannot change mode to %lo", sd, (long)SPOOL_DIR_MODE);
+ return;
+ }
+ if (stat(sd, &stab) < 0)
+ err(++problems, "stat: %s", sd);
+
+ note_spool_dir(pp, &stab);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage:\n\tchkprintcap [-dv] [-f printcapfile]\n");
+ exit(1);
+}
diff --git a/usr.sbin/lpr/chkprintcap/skimprintcap.c b/usr.sbin/lpr/chkprintcap/skimprintcap.c
new file mode 100644
index 0000000..da28282
--- /dev/null
+++ b/usr.sbin/lpr/chkprintcap/skimprintcap.c
@@ -0,0 +1,260 @@
+/*
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * Copyright (c) 2001 - Garance Alistair Drosehn <gad@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.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the FreeBSD Project.
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ */
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <grp.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <sys/param.h> /* needed for lp.h but not used here */
+#include <dirent.h> /* ditto */
+#include "lp.h"
+#include "lp.local.h"
+#include "skimprintcap.h"
+
+/*
+ * Save the canonical queue name of the entry that is currently being
+ * scanned, in case a warning message is printed for the current queue.
+ * Only the first 'QENTRY_MAXLEN' characters will be saved, since this
+ * is only for warning messages. The variable includes space for the
+ * string " (entry " and a trailing ")", when the scanner is in the
+ * middle of an entry. When the scanner is not in a specific entry,
+ * the variable will be the a null string.
+ */
+#define QENTRY_MAXLEN 30
+#define QENTRY_PREFIX " (entry "
+static char skim_entryname[sizeof(QENTRY_PREFIX) + QENTRY_MAXLEN + 2];
+
+/*
+ * isgraph is defined to work on an 'int', in the range 0 to 255, plus EOF.
+ * Define a wrapper which can take 'char', either signed or unsigned.
+ */
+#define isgraphch(Anychar) isgraph(((int) Anychar) & 255)
+
+struct skiminfo *
+skim_printcap(const char *pcap_fname, int verbosity)
+{
+ struct skiminfo *skinf;
+ char buff[BUFSIZ];
+ char *ch, *curline, *endfield, *lastchar;
+ FILE *pc_file;
+ int missing_nl;
+ enum {NO_CONTINUE, WILL_CONTINUE, BAD_CONTINUE} is_cont, had_cont;
+ enum {CMNT_LINE, ENTRY_LINE, TAB_LINE, TABERR_LINE} is_type, had_type;
+
+ skinf = malloc(sizeof(struct skiminfo));
+ memset(skinf, 0, sizeof(struct skiminfo));
+
+ pc_file = fopen(pcap_fname, "r");
+ if (pc_file == NULL) {
+ warn("fopen(%s)", pcap_fname);
+ skinf->fatalerr++;
+ return (skinf); /* fatal error */
+ }
+
+ skim_entryname[0] = '0';
+
+ is_cont = NO_CONTINUE;
+ is_type = CMNT_LINE;
+ errno = 0;
+ curline = fgets(buff, sizeof(buff), pc_file);
+ while (curline != NULL) {
+ skinf->lines++;
+
+ /* Check for the expected newline char, and remove it */
+ missing_nl = 0;
+ lastchar = strchr(curline, '\n');
+ if (lastchar != NULL)
+ *lastchar = '\0';
+ else {
+ lastchar = strchr(curline, '\0');
+ missing_nl = 1;
+ }
+ if (curline < lastchar)
+ lastchar--;
+
+ /*
+ * Check for `\' (continuation-character) at end of line.
+ * If there is none, then trim off spaces and check again.
+ * This would be a bad line because it looks like it is
+ * continued, but it will not be treated that way.
+ */
+ had_cont = is_cont;
+ is_cont = NO_CONTINUE;
+ if (*lastchar == '\\') {
+ is_cont = WILL_CONTINUE;
+ lastchar--;
+ } else {
+ while ((curline < lastchar) && !isgraphch(*lastchar))
+ lastchar--;
+ if (*lastchar == '\\')
+ is_cont = BAD_CONTINUE;
+ }
+
+ had_type = is_type;
+ is_type = CMNT_LINE;
+ switch (*curline) {
+ case '\0': /* treat zero-length line as comment */
+ case '#':
+ skinf->comments++;
+ break;
+ case ' ':
+ case '\t':
+ is_type = TAB_LINE;
+ break;
+ default:
+ is_type = ENTRY_LINE;
+ skinf->entries++;
+
+ /* pick up the queue name, to use in warning messages */
+ ch = curline;
+ while ((ch <= lastchar) && (*ch != ':') && (*ch != '|'))
+ ch++;
+ ch--; /* last char of queue name */
+ strcpy(skim_entryname, QENTRY_PREFIX);
+ if ((ch - curline) > QENTRY_MAXLEN) {
+ strncat(skim_entryname, curline, QENTRY_MAXLEN
+ - 1);
+ strcat(skim_entryname, "+");
+ } else {
+ strncat(skim_entryname, curline, (ch - curline
+ + 1));
+ }
+ strlcat(skim_entryname, ")", sizeof(skim_entryname));
+ break;
+ }
+
+ /*
+ * Check to see if the previous line was a bad contination
+ * line. The check is delayed until now so a warning message
+ * is not printed when a "bad continuation" is on a comment
+ * line, and it just "continues" into another comment line.
+ */
+ if (had_cont == BAD_CONTINUE) {
+ if ((had_type != CMNT_LINE) || (is_type != CMNT_LINE) ||
+ (verbosity > 1)) {
+ skinf->warnings++;
+ warnx("Warning: blanks after trailing '\\',"
+ " at line %d%s", skinf->lines - 1,
+ skim_entryname);
+ }
+ }
+
+ /* If we are no longer in an entry, then forget the name */
+ if ((had_cont != WILL_CONTINUE) && (is_type != ENTRY_LINE)) {
+ skim_entryname[0] = '\0';
+ }
+
+ /*
+ * Print out warning for missing newline, done down here
+ * so we are sure to have the right entry-name for it.
+ */
+ if (missing_nl) {
+ skinf->warnings++;
+ warnx("Warning: No newline at end of line %d%s",
+ skinf->lines, skim_entryname);
+ }
+
+ /*
+ * Check for start-of-entry lines which do not include a
+ * ":" character (to indicate the end of the name field).
+ * This can cause standard printcap processing to ignore
+ * ALL of the following lines.
+ * XXXXX - May need to allow for the list-of-names to
+ * continue on to the following line...
+ */
+ if (is_type == ENTRY_LINE) {
+ endfield = strchr(curline, ':');
+ if (endfield == NULL) {
+ skinf->warnings++;
+ warnx("Warning: No ':' to terminate name-field"
+ " at line %d%s", skinf->lines,
+ skim_entryname);
+ }
+ }
+
+ /*
+ * Now check for cases where this line is (or is-not) a
+ * continuation of the previous line, and a person skimming
+ * the file would assume it is not (or is) a continuation.
+ */
+ switch (had_cont) {
+ case NO_CONTINUE:
+ case BAD_CONTINUE:
+ if (is_type == TAB_LINE) {
+ skinf->warnings++;
+ warnx("Warning: values-line after line with"
+ " NO trailing '\\', at line %d%s",
+ skinf->lines, skim_entryname);
+ }
+ break;
+
+ case WILL_CONTINUE:
+ if (is_type == ENTRY_LINE) {
+ skinf->warnings++;
+ warnx("Warning: new entry starts after line"
+ " with trailing '\\', at line %d%s",
+ skinf->lines, skim_entryname);
+ }
+ break;
+ }
+
+ /* get another line from printcap and repeat loop */
+ curline = fgets(buff, sizeof(buff), pc_file);
+ }
+
+ if (errno != 0) {
+ warn("fgets(%s)", pcap_fname);
+ skinf->fatalerr++; /* fatal error */
+ }
+
+ if (skinf->warnings > 0)
+ warnx("%4d warnings from skimming %s", skinf->warnings,
+ pcap_fname);
+
+ if (verbosity)
+ warnx("%4d lines (%d comments), %d entries for %s",
+ skinf->lines, skinf->comments, skinf->entries, pcap_fname);
+
+ fclose(pc_file);
+ return (skinf);
+}
diff --git a/usr.sbin/lpr/chkprintcap/skimprintcap.h b/usr.sbin/lpr/chkprintcap/skimprintcap.h
new file mode 100644
index 0000000..af74b48
--- /dev/null
+++ b/usr.sbin/lpr/chkprintcap/skimprintcap.h
@@ -0,0 +1,44 @@
+/*
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * Copyright (c) 2001 - Garance Alistair Drosehn <gad@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.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the FreeBSD Project.
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * $FreeBSD$
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ */
+
+struct skiminfo {
+ int comments;
+ int entries;
+ int fatalerr; /* fatal error, msg already printed */
+ int lines;
+ int warnings;
+};
+
+struct skiminfo *skim_printcap(const char *_pcap, int _verbosity);
diff --git a/usr.sbin/lpr/common_source/Makefile b/usr.sbin/lpr/common_source/Makefile
new file mode 100644
index 0000000..4f148f9
--- /dev/null
+++ b/usr.sbin/lpr/common_source/Makefile
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+#
+# Library of internal routines for the print spooler suite.
+# Originally these were compiled separately into each program,
+# but the library makes it much easier to modularize them.
+#
+LIB= lpr
+INTERNALLIB=
+SRCS= common.c ctlinfo.c displayq.c matchjobs.c net.c \
+ printcap.c request.c rmjob.c startdaemon.c
+
+WARNS?= 1
+
+.include <bsd.lib.mk>
diff --git a/usr.sbin/lpr/common_source/Makefile.depend b/usr.sbin/lpr/common_source/Makefile.depend
new file mode 100644
index 0000000..56ba329
--- /dev/null
+++ b/usr.sbin/lpr/common_source/Makefile.depend
@@ -0,0 +1,14 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/arpa \
+ include/xlocale \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/common_source/common.c b/usr.sbin/lpr/common_source/common.c
new file mode 100644
index 0000000..52d6c9f
--- /dev/null
+++ b/usr.sbin/lpr/common_source/common.c
@@ -0,0 +1,778 @@
+/*
+ * 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.
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)common.c 8.5 (Berkeley) 4/28/95";
+#endif /* not lint */
+#endif
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "lp.h"
+#include "lp.local.h"
+#include "pathnames.h"
+
+/*
+ * Routines and data common to all the line printer functions.
+ */
+char line[BUFSIZ];
+const char *progname; /* program name */
+
+static int compar(const void *_p1, const void *_p2);
+
+/*
+ * isdigit() takes a parameter of 'int', but expect values in the range
+ * of unsigned char. Define a wrapper which takes a value of type 'char',
+ * whether signed or unsigned, and ensure it ends up in the right range.
+ */
+#define isdigitch(Anychar) isdigit((u_char)(Anychar))
+
+/*
+ * Getline reads a line from the control file cfp, removes tabs, converts
+ * new-line to null and leaves it in line.
+ * Returns 0 at EOF or the number of characters read.
+ */
+int
+getline(FILE *cfp)
+{
+ register int linel = 0;
+ register char *lp = line;
+ register int c;
+
+ while ((c = getc(cfp)) != '\n' && (size_t)(linel+1) < sizeof(line)) {
+ if (c == EOF)
+ return(0);
+ if (c == '\t') {
+ do {
+ *lp++ = ' ';
+ linel++;
+ } while ((linel & 07) != 0 && (size_t)(linel+1) <
+ sizeof(line));
+ continue;
+ }
+ *lp++ = c;
+ linel++;
+ }
+ *lp++ = '\0';
+ return(linel);
+}
+
+/*
+ * Scan the current directory and make a list of daemon files sorted by
+ * creation time.
+ * Return the number of entries and a pointer to the list.
+ */
+int
+getq(const struct printer *pp, struct jobqueue *(*namelist[]))
+{
+ register struct dirent *d;
+ register struct jobqueue *q, **queue;
+ size_t arraysz, entrysz, nitems;
+ struct stat stbuf;
+ DIR *dirp;
+ int statres;
+
+ PRIV_START
+ if ((dirp = opendir(pp->spool_dir)) == NULL) {
+ PRIV_END
+ return (-1);
+ }
+ if (fstat(dirfd(dirp), &stbuf) < 0)
+ goto errdone;
+ PRIV_END
+
+ /*
+ * Estimate the array size by taking the size of the directory file
+ * and dividing it by a multiple of the minimum size entry.
+ */
+ arraysz = (stbuf.st_size / 24);
+ if (arraysz < 16)
+ arraysz = 16;
+ queue = (struct jobqueue **)malloc(arraysz * sizeof(struct jobqueue *));
+ if (queue == NULL)
+ goto errdone;
+
+ nitems = 0;
+ while ((d = readdir(dirp)) != NULL) {
+ if (d->d_name[0] != 'c' || d->d_name[1] != 'f')
+ continue; /* daemon control files only */
+ PRIV_START
+ statres = stat(d->d_name, &stbuf);
+ PRIV_END
+ if (statres < 0)
+ continue; /* Doesn't exist */
+ entrysz = sizeof(struct jobqueue) - sizeof(q->job_cfname) +
+ strlen(d->d_name) + 1;
+ q = (struct jobqueue *)malloc(entrysz);
+ if (q == NULL)
+ goto errdone;
+ q->job_matched = 0;
+ q->job_processed = 0;
+ q->job_time = stbuf.st_mtime;
+ strcpy(q->job_cfname, d->d_name);
+ /*
+ * Check to make sure the array has space left and
+ * realloc the maximum size.
+ */
+ if (++nitems > arraysz) {
+ arraysz *= 2;
+ queue = (struct jobqueue **)realloc((char *)queue,
+ arraysz * sizeof(struct jobqueue *));
+ if (queue == NULL)
+ goto errdone;
+ }
+ queue[nitems-1] = q;
+ }
+ closedir(dirp);
+ if (nitems)
+ qsort(queue, nitems, sizeof(struct jobqueue *), compar);
+ *namelist = queue;
+ return(nitems);
+
+errdone:
+ closedir(dirp);
+ PRIV_END
+ return (-1);
+}
+
+/*
+ * Compare modification times.
+ */
+static int
+compar(const void *p1, const void *p2)
+{
+ const struct jobqueue *qe1, *qe2;
+
+ qe1 = *(const struct jobqueue * const *)p1;
+ qe2 = *(const struct jobqueue * const *)p2;
+
+ if (qe1->job_time < qe2->job_time)
+ return (-1);
+ if (qe1->job_time > qe2->job_time)
+ return (1);
+ /*
+ * At this point, the two files have the same last-modification time.
+ * return a result based on filenames, so that 'cfA001some.host' will
+ * come before 'cfA002some.host'. Since the jobid ('001') will wrap
+ * around when it gets to '999', we also assume that '9xx' jobs are
+ * older than '0xx' jobs.
+ */
+ if ((qe1->job_cfname[3] == '9') && (qe2->job_cfname[3] == '0'))
+ return (-1);
+ if ((qe1->job_cfname[3] == '0') && (qe2->job_cfname[3] == '9'))
+ return (1);
+ return (strcmp(qe1->job_cfname, qe2->job_cfname));
+}
+
+/*
+ * A simple routine to determine the job number for a print job based on
+ * the name of its control file. The algorithm used here may look odd, but
+ * the main issue is that all parts of `lpd', `lpc', `lpq' & `lprm' must be
+ * using the same algorithm, whatever that algorithm may be. If the caller
+ * provides a non-null value for ''hostpp', then this returns a pointer to
+ * the start of the hostname (or IP address?) as found in the filename.
+ *
+ * Algorithm: The standard `cf' file has the job number start in position 4,
+ * but some implementations have that as an extra file-sequence letter, and
+ * start the job number in position 5. The job number is usually three bytes,
+ * but may be as many as five. Confusing matters still more, some Windows
+ * print servers will append an IP address to the job number, instead of
+ * the expected hostname. So, if the job number ends with a '.', then
+ * assume the correct jobnum value is the first three digits.
+ */
+int
+calc_jobnum(const char *cfname, const char **hostpp)
+{
+ int jnum;
+ const char *cp, *numstr, *hoststr;
+
+ numstr = cfname + 3;
+ if (!isdigitch(*numstr))
+ numstr++;
+ jnum = 0;
+ for (cp = numstr; (cp < numstr + 5) && isdigitch(*cp); cp++)
+ jnum = jnum * 10 + (*cp - '0');
+ hoststr = cp;
+
+ /*
+ * If the filename was built with an IP number instead of a hostname,
+ * then recalculate using only the first three digits found.
+ */
+ while(isdigitch(*cp))
+ cp++;
+ if (*cp == '.') {
+ jnum = 0;
+ for (cp = numstr; (cp < numstr + 3) && isdigitch(*cp); cp++)
+ jnum = jnum * 10 + (*cp - '0');
+ hoststr = cp;
+ }
+ if (hostpp != NULL)
+ *hostpp = hoststr;
+ return (jnum);
+}
+
+/* sleep n milliseconds */
+void
+delay(int millisec)
+{
+ struct timeval tdelay;
+
+ if (millisec <= 0 || millisec > 10000)
+ fatal((struct printer *)0, /* fatal() knows how to deal */
+ "unreasonable delay period (%d)", millisec);
+ tdelay.tv_sec = millisec / 1000;
+ tdelay.tv_usec = millisec * 1000 % 1000000;
+ (void) select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &tdelay);
+}
+
+char *
+lock_file_name(const struct printer *pp, char *buf, size_t len)
+{
+ static char staticbuf[MAXPATHLEN];
+
+ if (buf == 0)
+ buf = staticbuf;
+ if (len == 0)
+ len = MAXPATHLEN;
+
+ if (pp->lock_file[0] == '/')
+ strlcpy(buf, pp->lock_file, len);
+ else
+ snprintf(buf, len, "%s/%s", pp->spool_dir, pp->lock_file);
+
+ return buf;
+}
+
+char *
+status_file_name(const struct printer *pp, char *buf, size_t len)
+{
+ static char staticbuf[MAXPATHLEN];
+
+ if (buf == 0)
+ buf = staticbuf;
+ if (len == 0)
+ len = MAXPATHLEN;
+
+ if (pp->status_file[0] == '/')
+ strlcpy(buf, pp->status_file, len);
+ else
+ snprintf(buf, len, "%s/%s", pp->spool_dir, pp->status_file);
+
+ return buf;
+}
+
+/*
+ * Routine to change operational state of a print queue. The operational
+ * state is indicated by the access bits on the lock file for the queue.
+ * At present, this is only called from various routines in lpc/cmds.c.
+ *
+ * XXX - Note that this works by changing access-bits on the
+ * file, and you can only do that if you are the owner of
+ * the file, or root. Thus, this won't really work for
+ * userids in the "LPR_OPER" group, unless lpc is running
+ * setuid to root (or maybe setuid to daemon).
+ * Generally lpc is installed setgid to daemon, but does
+ * not run setuid.
+ */
+int
+set_qstate(int action, const char *lfname)
+{
+ struct stat stbuf;
+ mode_t chgbits, newbits, oldmask;
+ const char *failmsg, *okmsg;
+ static const char *nomsg = "no state msg";
+ int chres, errsav, fd, res, statres;
+
+ /*
+ * Find what the current access-bits are.
+ */
+ memset(&stbuf, 0, sizeof(stbuf));
+ PRIV_START
+ statres = stat(lfname, &stbuf);
+ errsav = errno;
+ PRIV_END
+ if ((statres < 0) && (errsav != ENOENT)) {
+ printf("\tcannot stat() lock file\n");
+ return (SQS_STATFAIL);
+ /* NOTREACHED */
+ }
+
+ /*
+ * Determine which bit(s) should change for the requested action.
+ */
+ chgbits = stbuf.st_mode;
+ newbits = LOCK_FILE_MODE;
+ okmsg = NULL;
+ failmsg = NULL;
+ if (action & SQS_QCHANGED) {
+ chgbits |= LFM_RESET_QUE;
+ newbits |= LFM_RESET_QUE;
+ /* The okmsg is not actually printed for this case. */
+ okmsg = nomsg;
+ failmsg = "set queue-changed";
+ }
+ if (action & SQS_DISABLEQ) {
+ chgbits |= LFM_QUEUE_DIS;
+ newbits |= LFM_QUEUE_DIS;
+ okmsg = "queuing disabled";
+ failmsg = "disable queuing";
+ }
+ if (action & SQS_STOPP) {
+ chgbits |= LFM_PRINT_DIS;
+ newbits |= LFM_PRINT_DIS;
+ okmsg = "printing disabled";
+ failmsg = "disable printing";
+ if (action & SQS_DISABLEQ) {
+ okmsg = "printer and queuing disabled";
+ failmsg = "disable queuing and printing";
+ }
+ }
+ if (action & SQS_ENABLEQ) {
+ chgbits &= ~LFM_QUEUE_DIS;
+ newbits &= ~LFM_QUEUE_DIS;
+ okmsg = "queuing enabled";
+ failmsg = "enable queuing";
+ }
+ if (action & SQS_STARTP) {
+ chgbits &= ~LFM_PRINT_DIS;
+ newbits &= ~LFM_PRINT_DIS;
+ okmsg = "printing enabled";
+ failmsg = "enable printing";
+ }
+ if (okmsg == NULL) {
+ /* This routine was called with an invalid action. */
+ printf("\t<error in set_qstate!>\n");
+ return (SQS_PARMERR);
+ /* NOTREACHED */
+ }
+
+ res = 0;
+ if (statres >= 0) {
+ /* The file already exists, so change the access. */
+ PRIV_START
+ chres = chmod(lfname, chgbits);
+ errsav = errno;
+ PRIV_END
+ res = SQS_CHGOK;
+ if (chres < 0)
+ res = SQS_CHGFAIL;
+ } else if (newbits == LOCK_FILE_MODE) {
+ /*
+ * The file does not exist, but the state requested is
+ * the same as the default state when no file exists.
+ * Thus, there is no need to create the file.
+ */
+ res = SQS_SKIPCREOK;
+ } else {
+ /*
+ * The file did not exist, so create it with the
+ * appropriate access bits for the requested action.
+ * Push a new umask around that create, to make sure
+ * all the read/write bits are set as desired.
+ */
+ oldmask = umask(S_IWOTH);
+ PRIV_START
+ fd = open(lfname, O_WRONLY|O_CREAT, newbits);
+ errsav = errno;
+ PRIV_END
+ umask(oldmask);
+ res = SQS_CREFAIL;
+ if (fd >= 0) {
+ res = SQS_CREOK;
+ close(fd);
+ }
+ }
+
+ switch (res) {
+ case SQS_CHGOK:
+ case SQS_CREOK:
+ case SQS_SKIPCREOK:
+ if (okmsg != nomsg)
+ printf("\t%s\n", okmsg);
+ break;
+ case SQS_CREFAIL:
+ printf("\tcannot create lock file: %s\n",
+ strerror(errsav));
+ break;
+ default:
+ printf("\tcannot %s: %s\n", failmsg, strerror(errsav));
+ break;
+ }
+
+ return (res);
+}
+
+/* routine to get a current timestamp, optionally in a standard-fmt string */
+void
+lpd_gettime(struct timespec *tsp, char *strp, size_t strsize)
+{
+ struct timespec local_ts;
+ struct timeval btime;
+ char tempstr[TIMESTR_SIZE];
+#ifdef STRFTIME_WRONG_z
+ char *destp;
+#endif
+
+ if (tsp == NULL)
+ tsp = &local_ts;
+
+ /* some platforms have a routine called clock_gettime, but the
+ * routine does nothing but return "not implemented". */
+ memset(tsp, 0, sizeof(struct timespec));
+ if (clock_gettime(CLOCK_REALTIME, tsp)) {
+ /* nanosec-aware rtn failed, fall back to microsec-aware rtn */
+ memset(tsp, 0, sizeof(struct timespec));
+ gettimeofday(&btime, NULL);
+ tsp->tv_sec = btime.tv_sec;
+ tsp->tv_nsec = btime.tv_usec * 1000;
+ }
+
+ /* caller may not need a character-ized version */
+ if ((strp == NULL) || (strsize < 1))
+ return;
+
+ strftime(tempstr, TIMESTR_SIZE, LPD_TIMESTAMP_PATTERN,
+ localtime(&tsp->tv_sec));
+
+ /*
+ * This check is for implementations of strftime which treat %z
+ * (timezone as [+-]hhmm ) like %Z (timezone as characters), or
+ * completely ignore %z. This section is not needed on freebsd.
+ * I'm not sure this is completely right, but it should work OK
+ * for EST and EDT...
+ */
+#ifdef STRFTIME_WRONG_z
+ destp = strrchr(tempstr, ':');
+ if (destp != NULL) {
+ destp += 3;
+ if ((*destp != '+') && (*destp != '-')) {
+ char savday[6];
+ int tzmin = timezone / 60;
+ int tzhr = tzmin / 60;
+ if (daylight)
+ tzhr--;
+ strcpy(savday, destp + strlen(destp) - 4);
+ snprintf(destp, (destp - tempstr), "%+03d%02d",
+ (-1*tzhr), tzmin % 60);
+ strcat(destp, savday);
+ }
+ }
+#endif
+
+ if (strsize > TIMESTR_SIZE) {
+ strsize = TIMESTR_SIZE;
+ strp[TIMESTR_SIZE+1] = '\0';
+ }
+ strlcpy(strp, tempstr, strsize);
+}
+
+/* routines for writing transfer-statistic records */
+void
+trstat_init(struct printer *pp, const char *fname, int filenum)
+{
+ register const char *srcp;
+ register char *destp, *endp;
+
+ /*
+ * Figure out the job id of this file. The filename should be
+ * 'cf', 'df', or maybe 'tf', followed by a letter (or sometimes
+ * two), followed by the jobnum, followed by a hostname.
+ * The jobnum is usually 3 digits, but might be as many as 5.
+ * Note that some care has to be taken parsing this, as the
+ * filename could be coming from a remote-host, and thus might
+ * not look anything like what is expected...
+ */
+ memset(pp->jobnum, 0, sizeof(pp->jobnum));
+ pp->jobnum[0] = '0';
+ srcp = strchr(fname, '/');
+ if (srcp == NULL)
+ srcp = fname;
+ destp = &(pp->jobnum[0]);
+ endp = destp + 5;
+ while (*srcp != '\0' && (*srcp < '0' || *srcp > '9'))
+ srcp++;
+ while (*srcp >= '0' && *srcp <= '9' && destp < endp)
+ *(destp++) = *(srcp++);
+
+ /* get the starting time in both numeric and string formats, and
+ * save those away along with the file-number */
+ pp->jobdfnum = filenum;
+ lpd_gettime(&pp->tr_start, pp->tr_timestr, (size_t)TIMESTR_SIZE);
+
+ return;
+}
+
+void
+trstat_write(struct printer *pp, tr_sendrecv sendrecv, size_t bytecnt,
+ const char *userid, const char *otherhost, const char *orighost)
+{
+#define STATLINE_SIZE 1024
+ double trtime;
+ size_t remspace;
+ int statfile;
+ char thishost[MAXHOSTNAMELEN], statline[STATLINE_SIZE];
+ char *eostat;
+ const char *lprhost, *recvdev, *recvhost, *rectype;
+ const char *sendhost, *statfname;
+#define UPD_EOSTAT(xStr) do { \
+ eostat = strchr(xStr, '\0'); \
+ remspace = eostat - xStr; \
+} while(0)
+
+ lpd_gettime(&pp->tr_done, NULL, (size_t)0);
+ trtime = DIFFTIME_TS(pp->tr_done, pp->tr_start);
+
+ gethostname(thishost, sizeof(thishost));
+ lprhost = sendhost = recvhost = recvdev = NULL;
+ switch (sendrecv) {
+ case TR_SENDING:
+ rectype = "send";
+ statfname = pp->stat_send;
+ sendhost = thishost;
+ recvhost = otherhost;
+ break;
+ case TR_RECVING:
+ rectype = "recv";
+ statfname = pp->stat_recv;
+ sendhost = otherhost;
+ recvhost = thishost;
+ break;
+ case TR_PRINTING:
+ /*
+ * This case is for copying to a device (presumably local,
+ * though filters using things like 'net/CAP' can confuse
+ * this assumption...).
+ */
+ rectype = "prnt";
+ statfname = pp->stat_send;
+ sendhost = thishost;
+ recvdev = _PATH_DEFDEVLP;
+ if (pp->lp) recvdev = pp->lp;
+ break;
+ default:
+ /* internal error... should we syslog/printf an error? */
+ return;
+ }
+ if (statfname == NULL)
+ return;
+
+ /*
+ * the original-host and userid are found out by reading thru the
+ * cf (control-file) for the job. Unfortunately, on incoming jobs
+ * the df's (data-files) are sent before the matching cf, so the
+ * orighost & userid are generally not-available for incoming jobs.
+ *
+ * (it would be nice to create a work-around for that..)
+ */
+ if (orighost && (*orighost != '\0'))
+ lprhost = orighost;
+ else
+ lprhost = ".na.";
+ if (*userid == '\0')
+ userid = NULL;
+
+ /*
+ * Format of statline.
+ * Some of the keywords listed here are not implemented here, but
+ * they are listed to reserve the meaning for a given keyword.
+ * Fields are separated by a blank. The fields in statline are:
+ * <tstamp> - time the transfer started
+ * <ptrqueue> - name of the printer queue (the short-name...)
+ * <hname> - hostname the file originally came from (the
+ * 'lpr host'), if known, or "_na_" if not known.
+ * <xxx> - id of job from that host (generally three digits)
+ * <n> - file count (# of file within job)
+ * <rectype> - 4-byte field indicating the type of transfer
+ * statistics record. "send" means it's from the
+ * host sending a datafile, "recv" means it's from
+ * a host as it receives a datafile.
+ * user=<userid> - user who sent the job (if known)
+ * secs=<n> - seconds it took to transfer the file
+ * bytes=<n> - number of bytes transfered (ie, "bytecount")
+ * bps=<n.n>e<n> - Bytes/sec (if the transfer was "big enough"
+ * for this to be useful)
+ * ! top=<str> - type of printer (if the type is defined in
+ * printcap, and if this statline is for sending
+ * a file to that ptr)
+ * ! qls=<n> - queue-length at start of send/print-ing a job
+ * ! qle=<n> - queue-length at end of send/print-ing a job
+ * sip=<addr> - IP address of sending host, only included when
+ * receiving a job.
+ * shost=<hname> - sending host (if that does != the original host)
+ * rhost=<hname> - hostname receiving the file (ie, "destination")
+ * rdev=<dev> - device receiving the file, when the file is being
+ * send to a device instead of a remote host.
+ *
+ * Note: A single print job may be transferred multiple times. The
+ * original 'lpr' occurs on one host, and that original host might
+ * send to some interim host (or print server). That interim host
+ * might turn around and send the job to yet another host (most likely
+ * the real printer). The 'shost=' parameter is only included if the
+ * sending host for this particular transfer is NOT the same as the
+ * host which did the original 'lpr'.
+ *
+ * Many values have 'something=' tags before them, because they are
+ * in some sense "optional", or their order may vary. "Optional" may
+ * mean in the sense that different SITES might choose to have other
+ * fields in the record, or that some fields are only included under
+ * some circumstances. Programs processing these records should not
+ * assume the order or existence of any of these keyword fields.
+ */
+ snprintf(statline, STATLINE_SIZE, "%s %s %s %s %03ld %s",
+ pp->tr_timestr, pp->printer, lprhost, pp->jobnum,
+ pp->jobdfnum, rectype);
+ UPD_EOSTAT(statline);
+
+ if (userid != NULL) {
+ snprintf(eostat, remspace, " user=%s", userid);
+ UPD_EOSTAT(statline);
+ }
+ snprintf(eostat, remspace, " secs=%#.2f bytes=%lu", trtime,
+ (unsigned long)bytecnt);
+ UPD_EOSTAT(statline);
+
+ /*
+ * The bps field duplicates info from bytes and secs, so do
+ * not bother to include it for very small files.
+ */
+ if ((bytecnt > 25000) && (trtime > 1.1)) {
+ snprintf(eostat, remspace, " bps=%#.2e",
+ ((double)bytecnt/trtime));
+ UPD_EOSTAT(statline);
+ }
+
+ if (sendrecv == TR_RECVING) {
+ if (remspace > 5+strlen(from_ip) ) {
+ snprintf(eostat, remspace, " sip=%s", from_ip);
+ UPD_EOSTAT(statline);
+ }
+ }
+ if (0 != strcmp(lprhost, sendhost)) {
+ if (remspace > 7+strlen(sendhost) ) {
+ snprintf(eostat, remspace, " shost=%s", sendhost);
+ UPD_EOSTAT(statline);
+ }
+ }
+ if (recvhost) {
+ if (remspace > 7+strlen(recvhost) ) {
+ snprintf(eostat, remspace, " rhost=%s", recvhost);
+ UPD_EOSTAT(statline);
+ }
+ }
+ if (recvdev) {
+ if (remspace > 6+strlen(recvdev) ) {
+ snprintf(eostat, remspace, " rdev=%s", recvdev);
+ UPD_EOSTAT(statline);
+ }
+ }
+ if (remspace > 1) {
+ strcpy(eostat, "\n");
+ } else {
+ /* probably should back up to just before the final " x=".. */
+ strcpy(statline+STATLINE_SIZE-2, "\n");
+ }
+ statfile = open(statfname, O_WRONLY|O_APPEND, 0664);
+ if (statfile < 0) {
+ /* statfile was given, but we can't open it. should we
+ * syslog/printf this as an error? */
+ return;
+ }
+ write(statfile, statline, strlen(statline));
+ close(statfile);
+
+ return;
+#undef UPD_EOSTAT
+}
+
+#include <stdarg.h>
+
+void
+fatal(const struct printer *pp, const char *msg, ...)
+{
+ va_list ap;
+ va_start(ap, msg);
+ /* this error message is being sent to the 'from_host' */
+ if (from_host != local_host)
+ (void)printf("%s: ", local_host);
+ (void)printf("%s: ", progname);
+ if (pp && pp->printer)
+ (void)printf("%s: ", pp->printer);
+ (void)vprintf(msg, ap);
+ va_end(ap);
+ (void)putchar('\n');
+ exit(1);
+}
+
+/*
+ * Close all file descriptors from START on up.
+ */
+void
+closeallfds(int start)
+{
+ int stop;
+
+ if (USE_CLOSEFROM) /* The faster, modern solution */
+ closefrom(start);
+ else {
+ /* This older logic can be pretty awful on some OS's. The
+ * getdtablesize() might return ``infinity'', and then this
+ * will waste a lot of time closing file descriptors which
+ * had never been open()-ed. */
+ stop = getdtablesize();
+ for (; start < stop; start++)
+ close(start);
+ }
+}
+
diff --git a/usr.sbin/lpr/common_source/ctlinfo.c b/usr.sbin/lpr/common_source/ctlinfo.c
new file mode 100644
index 0000000..c23e419
--- /dev/null
+++ b/usr.sbin/lpr/common_source/ctlinfo.c
@@ -0,0 +1,914 @@
+/*
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * Copyright (c) 2001,2011 - Garance Alistair Drosehn <gad@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.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the FreeBSD Project.
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ */
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+/*
+ * ctlinfo - This collection of routines will know everything there is to
+ * know about the information inside a control file ('cf*') which is used
+ * to describe a print job in lpr & friends. The eventual goal is that it
+ * will be the ONLY source file to know what's inside these control-files.
+ */
+
+/*
+ * Some define's useful for debuging.
+ * TRIGGERTEST_FNAME and DEBUGREADCF_FNAME, allow us to do testing on
+ * a per-spool-directory basis.
+ */
+/* #define TRIGGERTEST_FNAME "LpdTestRenameTF" */
+/* #define DEBUGREADCF_FNAME "LpdDebugReadCF" */
+/* #define LEAVE_TMPCF_FILES 1 */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include "ctlinfo.h"
+
+struct cjprivate {
+ struct cjobinfo pub;
+ char *cji_buff; /* buffer for getline */
+ char *cji_eobuff; /* last byte IN the buffer */
+ FILE *cji_fstream;
+ int cji_buffsize; /* # bytes in the buffer */
+ int cji_dumpit;
+};
+
+/*
+ * All the following take a parameter of 'int', but expect values in the
+ * range of unsigned char. Define wrappers which take values of type 'char',
+ * whether signed or unsigned, and ensure they end up in the right range.
+ */
+#define isdigitch(Anychar) isdigit((u_char)(Anychar))
+#define islowerch(Anychar) islower((u_char)(Anychar))
+#define isupperch(Anychar) isupper((u_char)(Anychar))
+#define tolowerch(Anychar) tolower((u_char)(Anychar))
+
+#define OTHER_USERID_CHARS "-_" /* special chars valid in a userid */
+
+#define roundup(x, y) ((((x)+((y)-1))/(y))*(y))
+
+/*
+ * This has to be large enough to fit the maximum length of a single line
+ * in a control-file, including the leading 'command id', a trailing '\n'
+ * and ending '\0'. The max size of an 'U'nlink line, for instance, is
+ * 1 ('U') + PATH_MAX (filename) + 2 ('\n\0'). The maximum 'H'ost line is
+ * 1 ('H') + NI_MAXHOST (remote hostname) + 2 ('\n\0'). Other lines can be
+ * even longer than those. So, pick some nice, large, arbitrary value.
+ */
+#define CTI_LINEMAX PATH_MAX+NI_MAXHOST+5
+
+extern const char *from_host; /* client's machine name */
+extern const char *from_ip; /* client machine's IP address */
+
+__BEGIN_DECLS
+void ctl_dumpcji(FILE *_dbg_stream, const char *_heading,
+ struct cjobinfo *_cjinf);
+static char *ctl_getline(struct cjobinfo *_cjinf);
+static void ctl_rewindcf(struct cjobinfo *_cjinf);
+char *ctl_rmjob(const char *_ptrname, const char *_cfname);
+__END_DECLS
+
+/*
+ * Here are some things which might be needed when compiling this under
+ * platforms other than FreeBSD.
+ */
+#ifndef __FreeBSD__
+# ifndef NAME_MAX
+# define NAME_MAX 255
+# endif
+# ifndef NI_MAXHOST
+# define NI_MAXHOST 1025
+# endif
+# ifndef PATH_MAX
+# define PATH_MAX 1024
+# endif
+__BEGIN_DECLS
+char *strdup(const char *_src);
+size_t strlcpy(char *_dst, const char *_src, size_t _siz);
+__END_DECLS
+#endif
+
+/*
+ * Control-files (cf*) have the following format.
+ *
+ * Each control-file describes a single job. It will list one or more
+ * "datafiles" (df*) which should be copied to some printer. Usually
+ * there is only one datafile per job. For the curious, RFC 1179 is an
+ * informal and out-of-date description of lpr/lpd circa 1990.
+ *
+ * Each line in the file gives an attribute of the job as a whole, or one
+ * of the datafiles in the job, or a "command" indicating something to do
+ * with one of the datafiles. Each line starts with an 'id' that indicates
+ * what that line is there for. The 'id' is historically a single byte,
+ * but may be multiple bytes (obviously it would be best if multi-byte ids
+ * started with some letter not already used as a single-byte id!).
+ * After the 'id', the remainder of the line will be the value of the
+ * indicated attribute, or a name of the datafile to be operated on.
+ *
+ * In the following lists of ids, the ids with a '!' in front of them are
+ * NOT explicitly supported by this version of lpd, or at least "not yet
+ * supported". They are only listed for reference purposes, so people
+ * won't be tempted to reuse the same id for a different purpose.
+ *
+ * The following are attributes of the job which should not appear more
+ * than once in a control file. Only the 'H' and 'P' lines are required
+ * by the RFC, but some implementations of lpr won't even get that right.
+ *
+ * ! A - [used by lprNG]
+ * B - As far as I know, this is never used as a single-byte id.
+ * Therefore, I intend to use it for multi-byte id codes.
+ * C - "class name" to display on banner page (this is sometimes
+ * used to hold options for print filters)
+ * ! D - [in lprNG, "timestamp" of when the job was submitted]
+ * ! E - "environment variables" to set [some versions of linux]
+ * H - "host name" of machine where the original 'lpr' was done
+ * I - "indent", the amount to indent output
+ * J - "job name" to display on banner page
+ * L - "literal" user's name as it should be displayed on the
+ * banner page (it is the existence of an 'L' line which
+ * indicates that a job should have a banner page).
+ * M - "mail", userid to mail to when done printing (with email
+ * going to 'M'@'H', so to speak).
+ * P - "person", the user's login name (e.g. for accounting)
+ * ! Q - [used by lprNG for queue-name]
+ * R - "resolution" in dpi, for some laser printer queues
+ * T - "title" for files sent thru 'pr'
+ * W - "width" to use for printing plain-text files
+ * Z - In BSD, "locale" to use for datafiles sent thru 'pr'.
+ * (this BSD usage should move to a different id...)
+ * [in lprNG - this line holds the "Z options"]
+ * 1 - "R font file" for files sent thru troff
+ * 2 - "I font file" for files sent thru troff
+ * 3 - "B font file" for files sent thru troff
+ * 4 - "S font file" for files sent thru troff
+ *
+ * The following are attributes attached to a datafile, and thus may
+ * appear multiple times in a control file (once per datafile):
+ *
+ * N - "name" of file (for display purposes, used by 'lpq')
+ * S - "stat() info" used for symbolic link ('lpr -s')
+ * security checks.
+ *
+ * The following indicate actions to take on a given datafile. The same
+ * datafile may appear on more than one "print this file" command in the
+ * control file. Note that ALL ids with lowercase letters are expected
+ * to be actions to "print this file":
+ *
+ * c - "file name", cifplot file to print. This action appears
+ * when the user has requested 'lpr -c'.
+ * d - "file name", dvi file to print, user requested 'lpr -d'
+ * f - "file name", a plain-text file to print = "standard"
+ * g - "file name", plot(1G) file to print, ie 'lpr -g'
+ * l - "file name", text file with control chars which should
+ * be printed literally, ie 'lpr -l' (note: some printers
+ * take this id as a request to print a postscript file,
+ * and because of *that* some OS's use 'l' to indicate
+ * that a datafile is a postscript file)
+ * n - "file name", ditroff(1) file to print, ie 'lpr -n'
+ * o - "file name", a postscript file to print. This id is
+ * described in the original RFC, but not much has been
+ * done with it. This 'lpr' does not generate control
+ * lines with 'o'-actions, but lpd's printjob processing
+ * will treat it the same as 'l'.
+ * p - "file name", text file to print with pr(1), ie 'lpr -p'
+ * t - "file name", troff(1) file to print, ie 'lpr -t'
+ * v - "file name", plain raster file to print
+ *
+ * U - "file name" of datafile to unlink (ie, remove file
+ * from spool directory. To be done in a 'Pass 2',
+ * AFTER having processed all datafiles in the job).
+ *
+ */
+
+void
+ctl_freeinf(struct cjobinfo *cjinf)
+{
+#define FREESTR(xStr) \
+ if (xStr != NULL) { \
+ free(xStr); \
+ xStr = NULL;\
+ }
+
+ struct cjprivate *cpriv;
+
+ if (cjinf == NULL)
+ return;
+ cpriv = cjinf->cji_priv;
+ if ((cpriv == NULL) || (cpriv != cpriv->pub.cji_priv)) {
+ syslog(LOG_ERR, "in ctl_freeinf(%p): invalid cjinf (cpriv %p)",
+ (void *)cjinf, (void *)cpriv);
+ return;
+ }
+
+ FREESTR(cpriv->pub.cji_accthost);
+ FREESTR(cpriv->pub.cji_acctuser);
+ FREESTR(cpriv->pub.cji_class);
+ FREESTR(cpriv->pub.cji_curqueue);
+ /* [cpriv->pub.cji_fname is part of cpriv-malloced area] */
+ FREESTR(cpriv->pub.cji_jobname);
+ FREESTR(cpriv->pub.cji_mailto);
+ FREESTR(cpriv->pub.cji_headruser);
+
+ if (cpriv->cji_fstream != NULL) {
+ fclose(cpriv->cji_fstream);
+ cpriv->cji_fstream = NULL;
+ }
+
+ cjinf->cji_priv = NULL;
+ free(cpriv);
+#undef FREESTR
+}
+
+#ifdef DEBUGREADCF_FNAME
+static FILE *ctl_dbgfile = NULL;
+static struct stat ctl_dbgstat;
+#endif
+static int ctl_dbgline = 0;
+
+struct cjobinfo *
+ctl_readcf(const char *ptrname, const char *cfname)
+{
+ int id;
+ char *lbuff;
+ void *cstart;
+ FILE *cfile;
+ struct cjprivate *cpriv;
+ struct cjobinfo *cjinf;
+ size_t msize, sroom, sroom2;
+
+ cfile = fopen(cfname, "r");
+ if (cfile == NULL) {
+ syslog(LOG_ERR, "%s: ctl_readcf error fopen(%s): %s",
+ ptrname, cfname, strerror(errno));
+ return NULL;
+ }
+
+ sroom = roundup(sizeof(struct cjprivate), 8);
+ sroom2 = sroom + strlen(cfname) + 1;
+ sroom2 = roundup(sroom2, 8);
+ msize = sroom2 + CTI_LINEMAX;
+ msize = roundup(msize, 8);
+ cstart = malloc(msize);
+ if (cstart == NULL)
+ return NULL;
+ memset(cstart, 0, msize);
+ cpriv = (struct cjprivate *)cstart;
+ cpriv->pub.cji_priv = cpriv;
+
+ cpriv->pub.cji_fname = (char *)cstart + sroom;
+ strcpy(cpriv->pub.cji_fname, cfname);
+ cpriv->cji_buff = (char *)cstart + sroom2;
+ cpriv->cji_buffsize = (int)(msize - sroom2);
+ cpriv->cji_eobuff = (char *)cstart + msize - 1;
+
+ cpriv->cji_fstream = cfile;
+ cpriv->pub.cji_curqueue = strdup(ptrname);
+
+ ctl_dbgline = 0;
+#ifdef DEBUGREADCF_FNAME
+ ctl_dbgfile = NULL;
+ id = stat(DEBUGREADCF_FNAME, &ctl_dbgstat);
+ if (id != -1) {
+ /* the file exists in this spool directory, write some simple
+ * debugging info to it */
+ ctl_dbgfile = fopen(DEBUGREADCF_FNAME, "a");
+ if (ctl_dbgfile != NULL) {
+ fprintf(ctl_dbgfile, "%s: s=%p r=%ld e=%p %p->%s\n",
+ ptrname, (void *)cpriv, (long)sroom,
+ cpriv->cji_eobuff, cpriv->pub.cji_fname,
+ cpriv->pub.cji_fname);
+ }
+ }
+#endif
+ /*
+ * Copy job-attribute values from control file to the struct of
+ * "public" information. In some cases, it is invalid for the
+ * value to be a null-string, so that is ignored.
+ */
+ cjinf = &(cpriv->pub);
+ lbuff = ctl_getline(cjinf);
+ while (lbuff != NULL) {
+ id = *lbuff++;
+ switch (id) {
+ case 'C':
+ cpriv->pub.cji_class = strdup(lbuff);
+ break;
+ case 'H':
+ if (*lbuff == '\0')
+ break;
+ cpriv->pub.cji_accthost = strdup(lbuff);
+ break;
+ case 'J':
+ cpriv->pub.cji_jobname = strdup(lbuff);
+ break;
+ case 'L':
+ cpriv->pub.cji_headruser = strdup(lbuff);
+ break;
+ case 'M':
+ /*
+ * No valid mail-to address would start with a minus.
+ * If this one does, it is probably some trickster who
+ * is trying to trigger options on sendmail. Ignore.
+ */
+ if (*lbuff == '-')
+ break;
+ if (*lbuff == '\0')
+ break;
+ cpriv->pub.cji_mailto = strdup(lbuff);
+ break;
+ case 'P':
+ if (*lbuff == '\0')
+ break;
+ /* The userid must not start with a minus sign */
+ if (*lbuff == '-')
+ *lbuff = '_';
+ cpriv->pub.cji_acctuser = strdup(lbuff);
+ break;
+ default:
+ if (islower(id)) {
+ cpriv->pub.cji_dfcount++;
+ }
+ break;
+ }
+ lbuff = ctl_getline(cjinf);
+ }
+
+ /* the 'H'ost and 'P'erson fields are *always* supposed to be there */
+ if (cpriv->pub.cji_accthost == NULL)
+ cpriv->pub.cji_accthost = strdup(".na.");
+ if (cpriv->pub.cji_acctuser == NULL)
+ cpriv->pub.cji_acctuser = strdup(".na.");
+
+#ifdef DEBUGREADCF_FNAME
+ if (ctl_dbgfile != NULL) {
+ if (cpriv->cji_dumpit)
+ ctl_dumpcji(ctl_dbgfile, "end readcf", &(cpriv->pub));
+ fclose(ctl_dbgfile);
+ ctl_dbgfile = NULL;
+ }
+#endif
+ return &(cpriv->pub);
+}
+
+/*
+ * This routine renames the temporary control file as received from some
+ * other (remote) host. That file will almost always with `tfA*', because
+ * recvjob.c creates the file by changing `c' to `t' in the original name
+ * for the control file. Now if you read the RFC, you would think that all
+ * control filenames start with `cfA*'. However, it seems there are some
+ * implementations which send control filenames which start with `cf'
+ * followed by *any* letter, so this routine can not assume what the third
+ * letter will (or will not) be. Sigh.
+ *
+ * So this will rewrite the temporary file to `rf*' (correcting any lines
+ * which need correcting), rename that `rf*' file to `cf*', and then remove
+ * the original `tf*' temporary file.
+ *
+ * The *main* purpose of this routine is to be paranoid about the contents
+ * of that control file. It is partially meant to protect against people
+ * TRYING to cause trouble (perhaps after breaking into root of some host
+ * that this host will accept print jobs from). The fact that we're willing
+ * to print jobs from some remote host does not mean that we should blindly
+ * do anything that host tells us to do.
+ *
+ * This is also meant to protect us from errors in other implementations of
+ * lpr, particularly since we may want to use some values from the control
+ * file as environment variables when it comes time to print, or as parameters
+ * to commands which will be exec'ed, or values in statistics records.
+ *
+ * This may also do some "conversions" between how different versions of
+ * lpr or lprNG define the contents of various lines in a control file.
+ *
+ * If there is an error, it returns a pointer to a descriptive error message.
+ * Error messages which are RETURNED (as opposed to syslog-ed) do not include
+ * the printer-queue name. Let the caller add that if it is wanted.
+ */
+char *
+ctl_renametf(const char *ptrname, const char *tfname)
+{
+ int chk3rd, has_uc, newfd, nogood, res;
+ FILE *newcf;
+ struct cjobinfo *cjinf;
+ char *lbuff, *slash, *cp;
+ char tfname2[NAME_MAX+1], cfname2[NAME_MAX+1];
+ char errm[CTI_LINEMAX];
+
+#ifdef TRIGGERTEST_FNAME
+ struct stat tstat;
+ res = stat(TRIGGERTEST_FNAME, &tstat);
+ if (res == -1) {
+ /*
+ * if the trigger file does NOT exist in this spool directory,
+ * then do the exact same steps that the pre-ctlinfo code had
+ * been doing. Ie, very little.
+ */
+ strlcpy(cfname2, tfname, sizeof(cfname2));
+ cfname2[0] = 'c';
+ res = link(tfname, cfname2);
+ if (res < 0) {
+ snprintf(errm, sizeof(errm),
+ "ctl_renametf error link(%s,%s): %s", tfname,
+ cfname2, strerror(errno));
+ return strdup(errm);
+ }
+ unlink(tfname);
+ return NULL;
+ }
+#endif
+ cjinf = NULL; /* in case of early jump to error_ret */
+ newcf = NULL; /* in case of early jump to error_ret */
+ *errm = '\0'; /* in case of early jump to error_ret */
+
+ chk3rd = tfname[2];
+ if ((tfname[0] != 't') || (tfname[1] != 'f') || (!isalpha(chk3rd))) {
+ snprintf(errm, sizeof(errm),
+ "ctl_renametf invalid filename: %s", tfname);
+ goto error_ret;
+ }
+
+ cjinf = ctl_readcf(ptrname, tfname);
+ if (cjinf == NULL) {
+ snprintf(errm, sizeof(errm),
+ "ctl_renametf error cti_readcf(%s)", tfname);
+ goto error_ret;
+ }
+
+ /*
+ * This uses open+fdopen instead of fopen because that combination
+ * gives us greater control over file-creation issues.
+ */
+ strlcpy(tfname2, tfname, sizeof(tfname2));
+ tfname2[0] = 'r'; /* rf<letter><job><hostname> */
+ newfd = open(tfname2, O_WRONLY|O_CREAT|O_TRUNC, 0660);
+ if (newfd == -1) {
+ snprintf(errm, sizeof(errm),
+ "ctl_renametf error open(%s): %s", tfname2,
+ strerror(errno));
+ goto error_ret;
+ }
+ newcf = fdopen(newfd, "w");
+ if (newcf == NULL) {
+ close(newfd);
+ snprintf(errm, sizeof(errm),
+ "ctl_renametf error fopen(%s): %s", tfname2,
+ strerror(errno));
+ goto error_ret;
+ }
+
+ /*
+ * Do extra sanity checks on some key job-attribute fields, and
+ * write them out first (thus making sure they are written in the
+ * order we generally expect them to be in).
+ */
+ /*
+ * Some lpr implementations on PC's set a null-string for their
+ * hostname. A MacOS 10 system which has not correctly setup
+ * /etc/hostconfig will claim a hostname of 'localhost'. Anything
+ * with blanks in it would be an invalid value for hostname. For
+ * any of these invalid hostname values, replace the given value
+ * with the name of the host that this job is coming from.
+ */
+ nogood = 0;
+ if (cjinf->cji_accthost == NULL)
+ nogood = 1;
+ else if (strcmp(cjinf->cji_accthost, ".na.") == 0)
+ nogood = 1;
+ else if (strcmp(cjinf->cji_accthost, "localhost") == 0)
+ nogood = 1;
+ else {
+ for (cp = cjinf->cji_accthost; *cp != '\0'; cp++) {
+ if (*cp <= ' ') {
+ nogood = 1;
+ break;
+ }
+ }
+ }
+ if (nogood)
+ fprintf(newcf, "H%s\n", from_host);
+ else
+ fprintf(newcf, "H%s\n", cjinf->cji_accthost);
+
+ /*
+ * Now do some sanity checks on the 'P' (original userid) value. Note
+ * that the 'P'erson line is the second line which is ALWAYS supposed
+ * to be present in a control file.
+ *
+ * There is no particularly good value to use for replacements, but
+ * at least make sure the value is something reasonable to use in
+ * environment variables and statistics records. Again, some PC
+ * implementations send a null-string for a value. Various Mac
+ * implementations will set whatever string the user has set for
+ * their 'Owner Name', which usually includes blanks, etc.
+ */
+ nogood = 0;
+ if (cjinf->cji_acctuser == NULL)
+ nogood = 1;
+ else if (strcmp(cjinf->cji_acctuser, ".na.") == 0)
+ ; /* No further checks needed... */
+ else {
+ has_uc = 0;
+ cp = cjinf->cji_acctuser;
+ if (*cp == '-')
+ *cp++ = '_';
+ for (; *cp != '\0'; cp++) {
+ if (islowerch(*cp) || isdigitch(*cp))
+ continue; /* Standard valid characters */
+ if (strchr(OTHER_USERID_CHARS, *cp) != NULL)
+ continue; /* Some more valid characters */
+ if (isupperch(*cp)) {
+ has_uc = 1; /* These may be valid... */
+ continue;
+ }
+ *cp = '_';
+ }
+ /*
+ * Some Windows hosts send print jobs where the correct userid
+ * has been converted to uppercase, and that can cause trouble
+ * for sites that expect the correct value (for something like
+ * accounting). On the other hand, some sites do use uppercase
+ * in their userids, so we can't blindly convert to lowercase.
+ */
+ if (has_uc && (getpwnam(cjinf->cji_acctuser) == NULL)) {
+ for (cp = cjinf->cji_acctuser; *cp != '\0'; cp++) {
+ if (isupperch(*cp))
+ *cp = tolowerch(*cp);
+ }
+ }
+ }
+ if (nogood)
+ fprintf(newcf, "P%s\n", ".na.");
+ else
+ fprintf(newcf, "P%s\n", cjinf->cji_acctuser);
+
+ /* No need for sanity checks on class, jobname, "literal" user. */
+ if (cjinf->cji_class != NULL)
+ fprintf(newcf, "C%s\n", cjinf->cji_class);
+ if (cjinf->cji_jobname != NULL)
+ fprintf(newcf, "J%s\n", cjinf->cji_jobname);
+ if (cjinf->cji_headruser != NULL)
+ fprintf(newcf, "L%s\n", cjinf->cji_headruser);
+
+ /*
+ * This should probably add more sanity checks on mailto value.
+ * Note that if the mailto value is "wrong", then there's no good
+ * way to know what the "correct" value would be, and we should not
+ * semd email to some random address. At least for now, just ignore
+ * any invalid values.
+ */
+ nogood = 0;
+ if (cjinf->cji_mailto == NULL)
+ nogood = 1;
+ else {
+ for (cp = cjinf->cji_mailto; *cp != '\0'; cp++) {
+ if (*cp <= ' ') {
+ nogood = 1;
+ break;
+ }
+ }
+ }
+ if (!nogood)
+ fprintf(newcf, "M%s\n", cjinf->cji_mailto);
+
+ /*
+ * Now go thru the old control file, copying all information which
+ * hasn't already been written into the new file.
+ */
+ ctl_rewindcf(cjinf);
+ lbuff = ctl_getline(cjinf);
+ while (lbuff != NULL) {
+ switch (lbuff[0]) {
+ case 'H':
+ case 'P':
+ case 'C':
+ case 'J':
+ case 'L':
+ case 'M':
+ /* already wrote values for these to the newcf */
+ break;
+ case 'N':
+ /* see comments under 'U'... */
+ if (cjinf->cji_dfcount == 0) {
+ /* in this case, 'N's will be done in 'U' */
+ break;
+ }
+ fprintf(newcf, "%s\n", lbuff);
+ break;
+ case 'U':
+ /*
+ * check for the very common case where the remote
+ * host had to process 'lpr -s -r', but it did not
+ * remove the Unlink line from the control file.
+ * Such Unlink lines will legitimately have a '/' in
+ * them, but it is the original lpr host which would
+ * have done the unlink of such files, and not any
+ * host receiving that job.
+ */
+ slash = strchr(lbuff, '/');
+ if (slash != NULL) {
+ break; /* skip this line */
+ }
+ /*
+ * Okay, another kind of broken lpr implementation
+ * is one which send datafiles, and Unlink's those
+ * datafiles, but never includes any PRINT request
+ * for those files. Experimentation shows that one
+ * copy of those datafiles should be printed with a
+ * format of 'f'. If this is an example of such a
+ * screwed-up control file, fix it here.
+ */
+ if (cjinf->cji_dfcount == 0) {
+ lbuff++;
+ if (strncmp(lbuff, "df", (size_t)2) == 0) {
+ fprintf(newcf, "f%s\n", lbuff);
+ fprintf(newcf, "U%s\n", lbuff);
+ fprintf(newcf, "N%s\n", lbuff);
+ }
+ break;
+ }
+ fprintf(newcf, "%s\n", lbuff);
+ break;
+ default:
+ fprintf(newcf, "%s\n", lbuff);
+ break;
+ }
+ lbuff = ctl_getline(cjinf);
+ }
+
+ ctl_freeinf(cjinf);
+ cjinf = NULL;
+
+ res = fclose(newcf);
+ newcf = NULL;
+ if (res != 0) {
+ snprintf(errm, sizeof(errm),
+ "ctl_renametf error fclose(%s): %s", tfname2,
+ strerror(errno));
+ goto error_ret;
+ }
+
+ strlcpy(cfname2, tfname, sizeof(cfname2));
+ cfname2[0] = 'c'; /* rename new file to 'cfA*' */
+ res = link(tfname2, cfname2);
+ if (res != 0) {
+ snprintf(errm, sizeof(errm),
+ "ctl_renametf error link(%s,%s): %s", tfname2, cfname2,
+ strerror(errno));
+ goto error_ret;
+ }
+
+ /* All the important work is done. Now just remove temp files */
+#ifdef LEAVE_TMPCF_FILES
+ {
+ struct stat tfstat;
+ size_t size1;
+ tfstat.st_size = 1; /* certainly invalid value */
+ res = stat(tfname, &tfstat);
+ size1 = tfstat.st_size;
+ tfstat.st_size = 2; /* certainly invalid value */
+ res = stat(tfname2, &tfstat);
+ /*
+ * If the sizes do not match, or either stat call failed,
+ * then do not remove the temp files, but just move them
+ * out of the way. This is so I can see what this routine
+ * had changed (and the files won't interfere with some
+ * later job coming in from the same host). In this case,
+ * we don't care if we clobber some previous file.
+ */
+ if (size1 != tfstat.st_size) {
+ strlcpy(cfname2, tfname, sizeof(cfname2));
+ strlcat(cfname2, "._T", sizeof(cfname2));
+ rename(tfname, cfname2);
+ strlcpy(cfname2, tfname2, sizeof(cfname2));
+ strlcat(cfname2, "._T", sizeof(cfname2));
+ rename(tfname2, cfname2);
+ return NULL;
+ }
+ }
+#endif
+ unlink(tfname);
+ unlink(tfname2);
+
+ return NULL;
+
+error_ret:
+ if (cjinf != NULL)
+ ctl_freeinf(cjinf);
+ if (newcf != NULL)
+ fclose(newcf);
+
+ if (*errm != '\0')
+ return strdup(errm);
+ return strdup("ctl_renametf internal (missed) error");
+}
+
+void
+ctl_rewindcf(struct cjobinfo *cjinf)
+{
+ struct cjprivate *cpriv;
+
+ if (cjinf == NULL)
+ return;
+ cpriv = cjinf->cji_priv;
+ if ((cpriv == NULL) || (cpriv != cpriv->pub.cji_priv)) {
+ syslog(LOG_ERR, "in ctl_rewindcf(%p): invalid cjinf (cpriv %p)",
+ (void *)cjinf, (void *)cpriv);
+ return;
+ }
+
+ rewind(cpriv->cji_fstream); /* assume no errors... :-) */
+}
+
+char *
+ctl_rmjob(const char *ptrname, const char *cfname)
+{
+ struct cjobinfo *cjinf;
+ char *lbuff;
+ char errm[CTI_LINEMAX];
+
+ cjinf = ctl_readcf(ptrname, cfname);
+ if (cjinf == NULL) {
+ snprintf(errm, sizeof(errm),
+ "ctl_renametf error cti_readcf(%s)", cfname);
+ return strdup(errm);
+ }
+
+ ctl_rewindcf(cjinf);
+ lbuff = ctl_getline(cjinf);
+ while (lbuff != NULL) {
+ /* obviously we need to fill in the following... */
+ switch (lbuff[0]) {
+ case 'S':
+ break;
+ case 'U':
+ break;
+ default:
+ break;
+ }
+ lbuff = ctl_getline(cjinf);
+ }
+
+ ctl_freeinf(cjinf);
+ cjinf = NULL;
+
+ return NULL;
+}
+
+/*
+ * The following routine was originally written to pin down a bug. It is
+ * no longer needed for that problem, but may be useful to keep around for
+ * other debugging.
+ */
+void
+ctl_dumpcji(FILE *dbg_stream, const char *heading, struct cjobinfo *cjinf)
+{
+#define PRINTSTR(xHdr,xStr) \
+ astr = xStr; \
+ ctl_dbgline++; \
+ fprintf(dbg_stream, "%4d] %12s = ", ctl_dbgline, xHdr); \
+ if (astr == NULL) \
+ fprintf(dbg_stream, "NULL\n"); \
+ else \
+ fprintf(dbg_stream, "%p -> %s\n", astr, astr)
+
+ struct cjprivate *cpriv;
+ char *astr;
+
+ if (cjinf == NULL) {
+ fprintf(dbg_stream,
+ "ctl_dumpcji: ptr to cjobinfo for '%s' is NULL\n",
+ heading);
+ return;
+ }
+ cpriv = cjinf->cji_priv;
+
+ fprintf(dbg_stream, "ctl_dumpcji: Dump '%s' of cjobinfo at %p->%p\n",
+ heading, (void *)cjinf, cpriv->cji_buff);
+
+ PRINTSTR("accthost.H", cpriv->pub.cji_accthost);
+ PRINTSTR("acctuser.P", cpriv->pub.cji_acctuser);
+ PRINTSTR("class.C", cpriv->pub.cji_class);
+ PRINTSTR("cf-qname", cpriv->pub.cji_curqueue);
+ PRINTSTR("cf-fname", cpriv->pub.cji_fname);
+ PRINTSTR("jobname.J", cpriv->pub.cji_jobname);
+ PRINTSTR("mailto.M", cpriv->pub.cji_mailto);
+ PRINTSTR("headruser.L", cpriv->pub.cji_headruser);
+
+ ctl_dbgline++;
+ fprintf(dbg_stream, "%4d] %12s = ", ctl_dbgline, "*cjprivate");
+ if (cpriv->pub.cji_priv == NULL)
+ fprintf(dbg_stream, "NULL !!\n");
+ else
+ fprintf(dbg_stream, "%p\n", (void *)cpriv->pub.cji_priv);
+
+ fprintf(dbg_stream, "|- - - - --> Dump '%s' complete\n", heading);
+
+ /* flush output for the benefit of anyone doing a 'tail -f' */
+ fflush(dbg_stream);
+
+#undef PRINTSTR
+}
+
+/*
+ * This routine reads in the next line from the control-file, and removes
+ * the trailing newline character.
+ *
+ * Historical note: Earlier versions of this routine did tab-expansion for
+ * ALL lines read in, which did not make any sense for most of the lines
+ * in a control file. For the lines where tab-expansion is useful, it will
+ * now have to be done by the calling routine.
+ */
+static char *
+ctl_getline(struct cjobinfo *cjinf)
+{
+ char *strp, *nl;
+ struct cjprivate *cpriv;
+
+ if (cjinf == NULL)
+ return NULL;
+ cpriv = cjinf->cji_priv;
+ if ((cpriv == NULL) || (cpriv != cpriv->pub.cji_priv)) {
+ syslog(LOG_ERR, "in ctl_getline(%p): invalid cjinf (cpriv %p)",
+ (void *)cjinf, (void *)cpriv);
+ return NULL;
+ }
+
+ errno = 0;
+ strp = fgets(cpriv->cji_buff, cpriv->cji_buffsize, cpriv->cji_fstream);
+ if (strp == NULL) {
+ if (errno != 0)
+ syslog(LOG_ERR, "%s: ctl_getline error fgets(%s): %s",
+ cpriv->pub.cji_curqueue, cpriv->pub.cji_fname,
+ strerror(errno));
+ return NULL;
+ }
+ nl = strchr(strp, '\n');
+ if (nl != NULL)
+ *nl = '\0';
+
+#ifdef DEBUGREADCF_FNAME
+ /* I'd like to find out if the previous work to expand tabs was ever
+ * really used, and if so, on what lines and for what reason.
+ * Yes, all this work probably means I'm obsessed about this 'tab'
+ * issue, but isn't programming a matter of obsession?
+ */
+ {
+ int tabcnt;
+ char *ch;
+
+ tabcnt = 0;
+ ch = strp;
+ for (ch = strp; *ch != '\0'; ch++) {
+ if (*ch == '\t')
+ tabcnt++;
+ }
+
+ if (tabcnt && (ctl_dbgfile != NULL)) {
+ cpriv->cji_dumpit++;
+ fprintf(ctl_dbgfile, "%s: tabs=%d '%s'\n",
+ cpriv->pub.cji_fname, tabcnt, cpriv->cji_buff);
+ }
+ }
+#endif
+ return strp;
+}
diff --git a/usr.sbin/lpr/common_source/ctlinfo.h b/usr.sbin/lpr/common_source/ctlinfo.h
new file mode 100644
index 0000000..49bf532
--- /dev/null
+++ b/usr.sbin/lpr/common_source/ctlinfo.h
@@ -0,0 +1,73 @@
+/*
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * Copyright (c) 2001,2011 - Garance Alistair Drosehn <gad@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.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the FreeBSD Project.
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * $FreeBSD$
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ */
+
+/*
+ * ctlinfo - This collection of routines will know everything there is to
+ * know about the information inside a control file ('cf*') which is used
+ * to describe a print job in lpr & friends. The eventual goal is that it
+ * will be the ONLY source file to know what's inside these control-files.
+ */
+
+struct cjprivate; /* used internal to ctl* routines */
+
+struct cjobinfo {
+ int cji_dfcount; /* number of data files to print */
+ int cji_uncount; /* number of unlink-file requests */
+ char *cji_accthost; /* the host that this job came from,
+ * for accounting purposes (usually
+ * the host where the original 'lpr'
+ * was done) */
+ char *cji_acctuser; /* userid who should be charged for
+ * this job (usually, the userid which
+ * did the original 'lpr') */
+ char *cji_class; /* class-name */
+ char *cji_curqueue; /* printer-queue that this cf-file is
+ * curently sitting in (mainly used
+ * in syslog error messages) */
+ char *cji_fname; /* filename of the control file */
+ char *cji_jobname; /* job-name (for banner) */
+ char *cji_mailto; /* userid to send email to (or null) */
+ char *cji_headruser; /* "literal" user-name (for banner) or
+ * NULL if no banner-page is wanted */
+ struct cjprivate *cji_priv;
+};
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+
+__BEGIN_DECLS
+void ctl_freeinf(struct cjobinfo *_cjinf);
+struct cjobinfo *ctl_readcf(const char *_ptrname, const char *_cfname);
+char *ctl_renametf(const char *_ptrname, const char *_tfname);
+__END_DECLS
diff --git a/usr.sbin/lpr/common_source/displayq.c b/usr.sbin/lpr/common_source/displayq.c
new file mode 100644
index 0000000..3c038d6
--- /dev/null
+++ b/usr.sbin/lpr/common_source/displayq.c
@@ -0,0 +1,636 @@
+/*
+ * 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 char sccsid[] = "@(#)displayq.c 8.4 (Berkeley) 4/28/95";
+#endif /* not lint */
+#endif
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#define psignal foil_gcc_psignal
+#define sys_siglist foil_gcc_siglist
+#include <unistd.h>
+#undef psignal
+#undef sys_siglist
+
+#include "lp.h"
+#include "lp.local.h"
+#include "pathnames.h"
+
+/*
+ * Routines to display the state of the queue.
+ */
+#define JOBCOL 40 /* column for job # in -l format */
+#define OWNCOL 7 /* start of Owner column in normal */
+#define SIZCOL 62 /* start of Size column in normal */
+
+/*
+ * isprint() takes a parameter of 'int', but expect values in the range
+ * of unsigned char. Define a wrapper which takes a value of type 'char',
+ * whether signed or unsigned, and ensure it ends up in the right range.
+ */
+#define isprintch(Anychar) isprint((u_char)(Anychar))
+
+/*
+ * Stuff for handling job specifications
+ */
+static int col; /* column on screen */
+static char current[MAXNAMLEN+1]; /* current file being printed */
+static char file[MAXNAMLEN+1]; /* print file name */
+static int first; /* first file in ``files'' column? */
+static int garbage; /* # of garbage cf files */
+static int lflag; /* long output option */
+static int rank; /* order to be printed (-1=none, 0=active) */
+static long totsize; /* total print job size in bytes */
+
+static const char *head0 = "Rank Owner Job Files";
+static const char *head1 = "Total Size\n";
+
+static void alarmhandler(int _signo);
+static void filtered_write(char *_obuffer, int _wlen, FILE *_wstream);
+static void daemonwarn(const struct printer *_pp);
+
+/*
+ * Display the current state of the queue. Format = 1 if long format.
+ */
+void
+displayq(struct printer *pp, int format)
+{
+ register struct jobqueue *q;
+ register int i, nitems, fd, ret;
+ char *cp, *endp;
+ struct jobqueue **queue;
+ struct stat statb;
+ FILE *fp;
+ void (*savealrm)(int);
+
+ lflag = format;
+ totsize = 0;
+ rank = -1;
+
+ if ((cp = checkremote(pp))) {
+ printf("Warning: %s\n", cp);
+ free(cp);
+ }
+
+ /*
+ * Print out local queue
+ * Find all the control files in the spooling directory
+ */
+ PRIV_START
+ if (chdir(pp->spool_dir) < 0)
+ fatal(pp, "cannot chdir to spooling directory: %s",
+ strerror(errno));
+ PRIV_END
+ if ((nitems = getq(pp, &queue)) < 0)
+ fatal(pp, "cannot examine spooling area\n");
+ PRIV_START
+ ret = stat(pp->lock_file, &statb);
+ PRIV_END
+ if (ret >= 0) {
+ if (statb.st_mode & LFM_PRINT_DIS) {
+ if (pp->remote)
+ printf("%s: ", local_host);
+ printf("Warning: %s is down: ", pp->printer);
+ PRIV_START
+ fd = open(pp->status_file, O_RDONLY|O_SHLOCK);
+ PRIV_END
+ if (fd >= 0) {
+ while ((i = read(fd, line, sizeof(line))) > 0)
+ (void) fwrite(line, 1, i, stdout);
+ (void) close(fd); /* unlocks as well */
+ } else
+ putchar('\n');
+ }
+ if (statb.st_mode & LFM_QUEUE_DIS) {
+ if (pp->remote)
+ printf("%s: ", local_host);
+ printf("Warning: %s queue is turned off\n",
+ pp->printer);
+ }
+ }
+
+ if (nitems) {
+ PRIV_START
+ fp = fopen(pp->lock_file, "r");
+ PRIV_END
+ if (fp == NULL)
+ daemonwarn(pp);
+ else {
+ /* get daemon pid */
+ cp = current;
+ endp = cp + sizeof(current) - 1;
+ while ((i = getc(fp)) != EOF && i != '\n') {
+ if (cp < endp)
+ *cp++ = i;
+ }
+ *cp = '\0';
+ i = atoi(current);
+ if (i <= 0) {
+ ret = -1;
+ } else {
+ PRIV_START
+ ret = kill(i, 0);
+ PRIV_END
+ }
+ if (ret < 0) {
+ daemonwarn(pp);
+ } else {
+ /* read current file name */
+ cp = current;
+ endp = cp + sizeof(current) - 1;
+ while ((i = getc(fp)) != EOF && i != '\n') {
+ if (cp < endp)
+ *cp++ = i;
+ }
+ *cp = '\0';
+ /*
+ * Print the status file.
+ */
+ if (pp->remote)
+ printf("%s: ", local_host);
+ PRIV_START
+ fd = open(pp->status_file, O_RDONLY|O_SHLOCK);
+ PRIV_END
+ if (fd >= 0) {
+ while ((i = read(fd, line,
+ sizeof(line))) > 0)
+ fwrite(line, 1, i, stdout);
+ close(fd); /* unlocks as well */
+ } else
+ putchar('\n');
+ }
+ (void) fclose(fp);
+ }
+ /*
+ * Now, examine the control files and print out the jobs to
+ * be done for each user.
+ */
+ if (!lflag)
+ header();
+ for (i = 0; i < nitems; i++) {
+ q = queue[i];
+ inform(pp, q->job_cfname);
+ free(q);
+ }
+ free(queue);
+ }
+ if (!pp->remote) {
+ if (nitems == 0)
+ puts("no entries");
+ return;
+ }
+
+ /*
+ * Print foreign queue
+ * Note that a file in transit may show up in either queue.
+ */
+ if (nitems)
+ putchar('\n');
+ (void) snprintf(line, sizeof(line), "%c%s", format ? '\4' : '\3',
+ pp->remote_queue);
+ cp = line;
+ for (i = 0; i < requests && cp-line+10 < sizeof(line) - 1; i++) {
+ cp += strlen(cp);
+ (void) sprintf(cp, " %d", requ[i]);
+ }
+ for (i = 0; i < users && cp - line + 1 + strlen(user[i]) <
+ sizeof(line) - 1; i++) {
+ cp += strlen(cp);
+ *cp++ = ' ';
+ (void) strcpy(cp, user[i]);
+ }
+ strcat(line, "\n");
+ savealrm = signal(SIGALRM, alarmhandler);
+ alarm(pp->conn_timeout);
+ fd = getport(pp, pp->remote_host, 0);
+ alarm(0);
+ (void)signal(SIGALRM, savealrm);
+ if (fd < 0) {
+ if (from_host != local_host)
+ printf("%s: ", local_host);
+ printf("connection to %s is down\n", pp->remote_host);
+ }
+ else {
+ i = strlen(line);
+ if (write(fd, line, i) != i)
+ fatal(pp, "Lost connection");
+ while ((i = read(fd, line, sizeof(line))) > 0)
+ filtered_write(line, i, stdout);
+ filtered_write(NULL, -1, stdout);
+ (void) close(fd);
+ }
+}
+
+/*
+ * The lpq-info read from remote hosts may contain unprintable characters,
+ * or carriage-returns instead of line-feeds. Clean those up before echoing
+ * the lpq-info line(s) to stdout. The info may also be missing any kind of
+ * end-of-line character. This also turns CRLF and LFCR into a plain LF.
+ *
+ * This routine may be called multiple times to process a single set of
+ * information, and after a set is finished this routine must be called
+ * one extra time with NULL specified as the buffer address.
+ */
+static void
+filtered_write(char *wbuffer, int wlen, FILE *wstream)
+{
+ static char lastchar, savedchar;
+ char *chkptr, *dest_end, *dest_ch, *nxtptr, *w_end;
+ int destlen;
+ char destbuf[BUFSIZ];
+
+ if (wbuffer == NULL) {
+ if (savedchar != '\0') {
+ if (savedchar == '\r')
+ savedchar = '\n';
+ fputc(savedchar, wstream);
+ lastchar = savedchar;
+ savedchar = '\0';
+ }
+ if (lastchar != '\0' && lastchar != '\n')
+ fputc('\n', wstream);
+ lastchar = '\0';
+ return;
+ }
+
+ dest_ch = &destbuf[0];
+ dest_end = dest_ch + sizeof(destbuf);
+ chkptr = wbuffer;
+ w_end = wbuffer + wlen;
+ lastchar = '\0';
+ if (savedchar != '\0') {
+ chkptr = &savedchar;
+ nxtptr = wbuffer;
+ } else
+ nxtptr = chkptr + 1;
+
+ while (chkptr < w_end) {
+ if (nxtptr < w_end) {
+ if ((*chkptr == '\r' && *nxtptr == '\n') ||
+ (*chkptr == '\n' && *nxtptr == '\r')) {
+ *dest_ch++ = '\n';
+ /* want to skip past that second character */
+ nxtptr++;
+ goto check_next;
+ }
+ } else {
+ /* This is the last byte in the buffer given on this
+ * call, so check if it could be the first-byte of a
+ * significant two-byte sequence. If it is, then
+ * don't write it out now, but save for checking in
+ * the next call.
+ */
+ savedchar = '\0';
+ if (*chkptr == '\r' || *chkptr == '\n') {
+ savedchar = *chkptr;
+ break;
+ }
+ }
+ if (*chkptr == '\r')
+ *dest_ch++ = '\n';
+#if 0 /* XXX - don't translate unprintable characters (yet) */
+ else if (*chkptr != '\t' && *chkptr != '\n' &&
+ !isprintch(*chkptr))
+ *dest_ch++ = '?';
+#endif
+ else
+ *dest_ch++ = *chkptr;
+
+check_next:
+ chkptr = nxtptr;
+ nxtptr = chkptr + 1;
+ if (dest_ch >= dest_end) {
+ destlen = dest_ch - &destbuf[0];
+ fwrite(destbuf, 1, destlen, wstream);
+ lastchar = destbuf[destlen - 1];
+ dest_ch = &destbuf[0];
+ }
+ }
+ destlen = dest_ch - &destbuf[0];
+ if (destlen > 0) {
+ fwrite(destbuf, 1, destlen, wstream);
+ lastchar = destbuf[destlen - 1];
+ }
+}
+
+/*
+ * Print a warning message if there is no daemon present.
+ */
+static void
+daemonwarn(const struct printer *pp)
+{
+ if (pp->remote)
+ printf("%s: ", local_host);
+ puts("Warning: no daemon present");
+ current[0] = '\0';
+}
+
+/*
+ * Print the header for the short listing format
+ */
+void
+header(void)
+{
+ printf("%s", head0);
+ col = strlen(head0)+1;
+ blankfill(SIZCOL);
+ printf("%s", head1);
+}
+
+void
+inform(const struct printer *pp, char *cf)
+{
+ int copycnt, jnum;
+ char savedname[MAXPATHLEN+1];
+ FILE *cfp;
+
+ /*
+ * There's a chance the control file has gone away
+ * in the meantime; if this is the case just keep going
+ */
+ PRIV_START
+ if ((cfp = fopen(cf, "r")) == NULL)
+ return;
+ PRIV_END
+
+ if (rank < 0)
+ rank = 0;
+ if (pp->remote || garbage || strcmp(cf, current))
+ rank++;
+
+ /*
+ * The cf-file may include commands to print more than one datafile
+ * from the user. For each datafile, the cf-file contains at least
+ * one line which starts with some format-specifier ('a'-'z'), and
+ * a second line ('N'ame) which indicates the original name the user
+ * specified for that file. There can be multiple format-spec lines
+ * for a single Name-line, if the user requested multiple copies of
+ * that file. Standard lpr puts the format-spec line(s) before the
+ * Name-line, while lprNG puts the Name-line before the format-spec
+ * line(s). This section needs to handle the lines in either order.
+ */
+ copycnt = 0;
+ file[0] = '\0';
+ savedname[0] = '\0';
+ jnum = calc_jobnum(cf, NULL);
+ while (getline(cfp)) {
+ switch (line[0]) {
+ case 'P': /* Was this file specified in the user's list? */
+ if (!inlist(line+1, cf)) {
+ fclose(cfp);
+ return;
+ }
+ if (lflag) {
+ printf("\n%s: ", line+1);
+ col = strlen(line+1) + 2;
+ prank(rank);
+ blankfill(JOBCOL);
+ printf(" [job %s]\n", cf+3);
+ } else {
+ col = 0;
+ prank(rank);
+ blankfill(OWNCOL);
+ printf("%-10s %-3d ", line+1, jnum);
+ col += 16;
+ first = 1;
+ }
+ continue;
+ default: /* some format specifer and file name? */
+ if (line[0] < 'a' || line[0] > 'z')
+ break;
+ if (copycnt == 0 || strcmp(file, line+1) != 0) {
+ strlcpy(file, line + 1, sizeof(file));
+ }
+ copycnt++;
+ /*
+ * deliberately 'continue' to another getline(), so
+ * all format-spec lines for this datafile are read
+ * in and counted before calling show()
+ */
+ continue;
+ case 'N':
+ strlcpy(savedname, line + 1, sizeof(savedname));
+ break;
+ }
+ if ((file[0] != '\0') && (savedname[0] != '\0')) {
+ show(savedname, file, copycnt);
+ copycnt = 0;
+ file[0] = '\0';
+ savedname[0] = '\0';
+ }
+ }
+ fclose(cfp);
+ /* check for a file which hasn't been shown yet */
+ if (file[0] != '\0') {
+ if (savedname[0] == '\0') {
+ /* a safeguard in case the N-ame line is missing */
+ strlcpy(savedname, file, sizeof(savedname));
+ }
+ show(savedname, file, copycnt);
+ }
+ if (!lflag) {
+ blankfill(SIZCOL);
+ printf("%ld bytes\n", totsize);
+ totsize = 0;
+ }
+}
+
+int
+inlist(char *uname, char *cfile)
+{
+ int *r, jnum;
+ char **u;
+ const char *cfhost;
+
+ if (users == 0 && requests == 0)
+ return(1);
+ /*
+ * Check to see if it's in the user list
+ */
+ for (u = user; u < &user[users]; u++)
+ if (!strcmp(*u, uname))
+ return(1);
+ /*
+ * Check the request list
+ */
+ jnum = calc_jobnum(cfile, &cfhost);
+ for (r = requ; r < &requ[requests]; r++)
+ if (*r == jnum && !strcmp(cfhost, from_host))
+ return(1);
+ return(0);
+}
+
+void
+show(const char *nfile, const char *datafile, int copies)
+{
+ if (strcmp(nfile, " ") == 0)
+ nfile = "(standard input)";
+ if (lflag)
+ ldump(nfile, datafile, copies);
+ else
+ dump(nfile, datafile, copies);
+}
+
+/*
+ * Fill the line with blanks to the specified column
+ */
+void
+blankfill(int tocol)
+{
+ while (col++ < tocol)
+ putchar(' ');
+}
+
+/*
+ * Give the abbreviated dump of the file names
+ */
+void
+dump(const char *nfile, const char *datafile, int copies)
+{
+ struct stat lbuf;
+ const char etctmpl[] = ", ...";
+ char etc[sizeof(etctmpl)];
+ char *lastsep;
+ short fill, nlen;
+ short rem, remetc;
+
+ /*
+ * Print as many filenames as will fit
+ * (leaving room for the 'total size' field)
+ */
+ fill = first ? 0 : 2; /* fill space for ``, '' */
+ nlen = strlen(nfile);
+ rem = SIZCOL - 1 - col;
+ if (nlen + fill > rem) {
+ if (first) {
+ /* print the right-most part of the name */
+ printf("...%s ", &nfile[3+nlen-rem]);
+ col = SIZCOL;
+ } else if (rem > 0) {
+ /* fit as much of the etc-string as we can */
+ remetc = rem;
+ if (rem > strlen(etctmpl))
+ remetc = strlen(etctmpl);
+ etc[0] = '\0';
+ strncat(etc, etctmpl, remetc);
+ printf("%s", etc);
+ col += remetc;
+ rem -= remetc;
+ /* room for the last segment of this filename? */
+ lastsep = strrchr(nfile, '/');
+ if ((lastsep != NULL) && (rem > strlen(lastsep))) {
+ /* print the right-most part of this name */
+ printf("%s", lastsep);
+ col += strlen(lastsep);
+ } else {
+ /* do not pack any more names in here */
+ blankfill(SIZCOL);
+ }
+ }
+ } else {
+ if (!first)
+ printf(", ");
+ printf("%s", nfile);
+ col += nlen + fill;
+ }
+ first = 0;
+
+ PRIV_START
+ if (*datafile && !stat(datafile, &lbuf))
+ totsize += copies * lbuf.st_size;
+ PRIV_END
+}
+
+/*
+ * Print the long info about the file
+ */
+void
+ldump(const char *nfile, const char *datafile, int copies)
+{
+ struct stat lbuf;
+
+ putchar('\t');
+ if (copies > 1)
+ printf("%-2d copies of %-19s", copies, nfile);
+ else
+ printf("%-32s", nfile);
+ if (*datafile && !stat(datafile, &lbuf))
+ printf(" %qd bytes", (long long) lbuf.st_size);
+ else
+ printf(" ??? bytes");
+ putchar('\n');
+}
+
+/*
+ * Print the job's rank in the queue,
+ * update col for screen management
+ */
+void
+prank(int n)
+{
+ char rline[100];
+ static const char *r[] = {
+ "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"
+ };
+
+ if (n == 0) {
+ printf("active");
+ col += 6;
+ return;
+ }
+ if ((n/10)%10 == 1)
+ (void)snprintf(rline, sizeof(rline), "%dth", n);
+ else
+ (void)snprintf(rline, sizeof(rline), "%d%s", n, r[n%10]);
+ col += strlen(rline);
+ printf("%s", rline);
+}
+
+void
+alarmhandler(int signo __unused)
+{
+ /* the signal is ignored */
+ /* (the '__unused' is just to avoid a compile-time warning) */
+}
diff --git a/usr.sbin/lpr/common_source/lp.cdefs.h b/usr.sbin/lpr/common_source/lp.cdefs.h
new file mode 100644
index 0000000..5806f39
--- /dev/null
+++ b/usr.sbin/lpr/common_source/lp.cdefs.h
@@ -0,0 +1,122 @@
+/*-
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * Copyright (c) 2003,2013 - Garance Alistair Drosehn <gad@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.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the FreeBSD Project.
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * $FreeBSD$
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ */
+
+/*
+ * The main goal of this include file is to provide a platform-neutral way
+ * to define some macros that lpr wants from FreeBSD's <sys/cdefs.h>. This
+ * will simply use the standard <sys/cdefs.h> when compiled in FreeBSD, but
+ * other OS's may not have /usr/include/sys/cdefs.h (or even if that file
+ * exists, it may not define all the macros that lpr will use).
+ */
+
+#if !defined(_LP_CDEFS_H_)
+#define _LP_CDEFS_H_
+
+/*
+ * For non-BSD platforms, you can compile lpr with -DHAVE_SYS_CDEFS_H
+ * if <sys/cdefs.h> should be included.
+ */
+#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+# define HAVE_SYS_CDEFS_H
+#endif
+#if defined(HAVE_SYS_CDEFS_H)
+# include <sys/cdefs.h>
+#endif
+
+/*
+ * FreeBSD added a closefrom() routine in release 8.0. When compiling
+ * `lpr' on other platforms you might want to include bsd-closefrom.c
+ * from the portable-openssh project.
+ */
+#ifndef USE_CLOSEFROM
+# if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+# define USE_CLOSEFROM 1
+# endif
+#endif
+/* The macro USE_CLOSEFROM must be defined with a value of 0 or 1. */
+#ifndef USE_CLOSEFROM
+# define USE_CLOSEFROM 0
+#endif
+
+/*
+ * __unused is a compiler-specific trick which can be used to avoid
+ * warnings about a variable which is defined but never referenced.
+ * Some lpr files use this, so define a null version if it was not
+ * defined by <sys/cdefs.h>.
+ */
+#if !defined(__unused)
+# define __unused
+#endif
+
+/*
+ * All the lpr source files will want to reference __FBSDID() to
+ * handle rcs id's.
+ */
+#if !defined(__FBSDID)
+# if defined(lint) || defined(STRIP_FBSDID)
+# define __FBSDID(s) struct skip_rcsid_struct
+# elif defined(__IDSTRING) /* NetBSD */
+# define __FBSDID(s) __IDSTRING(rcsid,s)
+# else
+# define __FBSDID(s) static const char rcsid[] __unused = s
+# endif
+#endif /* __FBSDID */
+
+/*
+ * Some lpr include files use __BEGIN_DECLS and __END_DECLS.
+ */
+#if !defined(__BEGIN_DECLS)
+# if defined(__cplusplus)
+# define __BEGIN_DECLS extern "C" {
+# define __END_DECLS }
+# else
+# define __BEGIN_DECLS
+# define __END_DECLS
+# endif
+#endif
+
+/*
+ * __printflike and __printf0like are a compiler-specific tricks to
+ * tell the compiler to check the format-codes in printf-like
+ * routines wrt the args that will be formatted.
+ */
+#if !defined(__printflike)
+# define __printflike(fmtarg, firstvararg)
+#endif
+#if !defined(__printf0like)
+# define __printf0like(fmtarg, firstvararg)
+#endif
+
+#endif /* !_LP_CDEFS_H_ */
diff --git a/usr.sbin/lpr/common_source/lp.h b/usr.sbin/lpr/common_source/lp.h
new file mode 100644
index 0000000..fb05d2f
--- /dev/null
+++ b/usr.sbin/lpr/common_source/lp.h
@@ -0,0 +1,317 @@
+/*
+ * 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.
+ *
+ * From: @(#)lp.h 8.2 (Berkeley) 4/28/95
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#include <time.h>
+#include <netdb.h>
+
+/*
+ * All this information used to be in global static variables shared
+ * mysteriously by various parts of the lpr/lpd suite.
+ * This structure attempts to centralize all these declarations in the
+ * hope that they can later be made more dynamic.
+ */
+enum lpd_filters { LPF_CIFPLOT, LPF_DVI, LPF_GRAPH, LPF_INPUT,
+ LPF_DITROFF, LPF_OUTPUT, LPF_FORTRAN, LPF_TROFF,
+ LPF_RASTER, LPF_COUNT };
+/* NB: there is a table in common.c giving the mapping from capability names */
+
+struct printer {
+ char *printer; /* printer name */
+ int remote; /* true if RM points to a remote host */
+ int rp_matches_local; /* true if rp has same name as us */
+ int tof; /* true if we are at top-of-form */
+ /* ------------------------------------------------------ */
+ char *acct_file; /* AF: accounting file */
+ long baud_rate; /* BR: baud rate if lp is a tty */
+ char *filters[LPF_COUNT]; /* CF, DF, GF, IF, NF, OF, RF, TF, VF */
+ long conn_timeout; /* CT: TCP connection timeout */
+ long daemon_user; /* DU: daemon user id -- XXX belongs ???? */
+ char *form_feed; /* FF: form feed */
+ long header_last; /* HL: print header last */
+ char *log_file; /* LF: log file */
+ char *lock_file; /* LO: lock file */
+ char *lp; /* LP: device name or network address */
+ long max_copies; /* MC: maximum number of copies allowed */
+ long max_blocks; /* MX: maximum number of blocks to copy */
+ long price100; /* PC: price per 100 units of output */
+ long page_length; /* PL: page length */
+ long page_width; /* PW: page width */
+ long page_pwidth; /* PX: page width in pixels */
+ long page_plength; /* PY: page length in pixels */
+ long resend_copies; /* RC: resend copies to remote host */
+ char *restrict_grp; /* RG: restricted group */
+ char *remote_host; /* RM: remote machine name */
+ char *remote_queue; /* RP: remote printer name */
+ long restricted; /* RS: restricted to those with local accts */
+ long rw; /* RW: open LP for reading and writing */
+ long short_banner; /* SB: short banner */
+ long no_copies; /* SC: suppress multiple copies */
+ char *spool_dir; /* SD: spool directory */
+ long no_formfeed; /* SF: suppress FF on each print job */
+ long no_header; /* SH: suppress header page */
+ char *stat_recv; /* SR: statistics file, receiving jobs */
+ char *stat_send; /* SS: statistics file, sending jobs */
+ char *status_file; /* ST: status file name */
+ char *trailer; /* TR: trailer string send when Q empties */
+ char *mode_set; /* MS: mode set, a la stty */
+
+ /* variables used by trstat*() to keep statistics on file transfers */
+#define JOBNUM_SIZE 8
+ char jobnum[JOBNUM_SIZE];
+ long jobdfnum; /* current datafile number within job */
+ struct timespec tr_start, tr_done;
+#define TIMESTR_SIZE 40 /* holds result from LPD_TIMESTAMP_PATTERN */
+ char tr_timestr[TIMESTR_SIZE];
+#define DIFFTIME_TS(endTS,startTS) \
+ ((double)(endTS.tv_sec - startTS.tv_sec) \
+ + (endTS.tv_nsec - startTS.tv_nsec) * 1.0e-9)
+};
+
+/*
+ * Lists of user names and job numbers, for the benefit of the structs
+ * defined below. We use TAILQs so that requests don't get mysteriously
+ * reversed in process.
+ */
+struct req_user {
+ TAILQ_ENTRY(req_user) ru_link; /* macro glue */
+ char ru_uname[1]; /* name of user */
+};
+TAILQ_HEAD(req_user_head, req_user);
+
+struct req_file {
+ TAILQ_ENTRY(req_file) rf_link; /* macro glue */
+ char rf_type; /* type (lowercase cf file letter) of file */
+ char *rf_prettyname; /* user-visible name of file */
+ char rf_fname[1]; /* name of file */
+};
+TAILQ_HEAD(req_file_head, req_file);
+
+struct req_jobid {
+ TAILQ_ENTRY(req_jobid) rj_link; /* macro glue */
+ int rj_job; /* job number */
+};
+TAILQ_HEAD(req_jobid_head, req_jobid);
+
+/*
+ * Encapsulate all the information relevant to a request in the
+ * lpr/lpd protocol.
+ */
+enum req_type { REQ_START, REQ_RECVJOB, REQ_LIST, REQ_DELETE };
+
+struct request {
+ enum req_type type; /* what sort of request is this? */
+ struct printer prtr; /* which printer is it for? */
+ int remote; /* did request arrive over network? */
+ char *logname; /* login name of requesting user */
+ char *authname; /* authenticated identity of requesting user */
+ char *prettyname; /* ``pretty'' name of requesting user */
+ int privileged; /* was the request from a privileged user? */
+ void *authinfo; /* authentication information */
+ int authentic; /* was the request securely authenticated? */
+
+ /* Information for queries and deletes... */
+ int nusers; /* length of following list... */
+ struct req_user_head users; /* list of users to query/delete */
+ int njobids; /* length of following list... */
+ struct req_jobid_head jobids; /* list of jobids to query/delete */
+};
+
+/*
+ * Global definitions for the line printer system.
+ */
+extern char line[BUFSIZ];
+extern const char *progname; /* program name (lpr, lpq, etc) */
+
+ /*
+ * 'local_host' is the name of the machine that lpd (lpr, whatever)
+ * is actually running on.
+ *
+ * 'from_host' will point to the 'host' variable when receiving a job
+ * from a user on the same host, or "somewhere else" when receiving a
+ * job from a remote host. If 'from_host != local_host', then 'from_ip'
+ * is the character representation of the IP address of from_host (note
+ * that string could be an IPv6 address).
+ *
+ * Also note that when 'from_host' is not pointing at 'local_host', the
+ * string it is pointing at may be as long as NI_MAXHOST (which is very
+ * likely to be much longer than MAXHOSTNAMELEN).
+ */
+extern char local_host[MAXHOSTNAMELEN];
+extern const char *from_host; /* client's machine name */
+extern const char *from_ip; /* client machine's IP address */
+
+extern int requ[]; /* job number of spool entries */
+extern int requests; /* # of spool requests */
+extern char *user[]; /* users to process */
+extern int users; /* # of users in user array */
+extern char *person; /* name of person doing lprm */
+extern u_char family; /* address family */
+
+/*
+ * Structure used for building a sorted list of control files.
+ * The job_processed value can be used by callers of getq(), to keep
+ * track of whatever processing they are doing.
+ */
+struct jobqueue {
+ time_t job_time; /* last-mod time of cf-file */
+ int job_matched; /* used by match_jobspec() */
+ int job_processed; /* set to zero by getq() */
+ char job_cfname[MAXNAMLEN+1]; /* control file name */
+};
+
+/* lpr/lpd generates readable timestamps for logfiles, etc. Have all those
+ * timestamps be in the same format wrt strftime(). This is ISO 8601 format,
+ * with the addition of an easy-readable day-of-the-week field. Note that
+ * '%T' = '%H:%M:%S', and that '%z' is not available on all platforms.
+ */
+#define LPD_TIMESTAMP_PATTERN "%Y-%m-%dT%T%z %a"
+
+/*
+ * Codes to indicate which statistic records trstat_write should write.
+ */
+typedef enum { TR_SENDING, TR_RECVING, TR_PRINTING } tr_sendrecv;
+
+/*
+ * Error codes for our mini printcap library.
+ */
+#define PCAPERR_TCLOOP (-3)
+#define PCAPERR_OSERR (-2)
+#define PCAPERR_NOTFOUND (-1)
+#define PCAPERR_SUCCESS 0
+#define PCAPERR_TCOPEN 1
+
+/*
+ * File modes for the various status files maintained by lpd.
+ */
+#define LOCK_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)
+#define LFM_PRINT_DIS (S_IXUSR)
+#define LFM_QUEUE_DIS (S_IXGRP)
+#define LFM_RESET_QUE (S_IXOTH)
+
+#define STAT_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)
+#define LOG_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)
+#define TEMP_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)
+
+/*
+ * Bit-flags for set_qstate() actions, followed by the return values.
+ */
+#define SQS_DISABLEQ 0x01 /* Disable the queuing of new jobs */
+#define SQS_STOPP 0x02 /* Stop the printing of jobs */
+#define SQS_ENABLEQ 0x10 /* Enable the queuing of new jobs */
+#define SQS_STARTP 0x20 /* Start the printing of jobs */
+#define SQS_QCHANGED 0x80 /* The queue has changed (new jobs, etc) */
+
+#define SQS_PARMERR -9 /* Invalid parameters from caller */
+#define SQS_CREFAIL -3 /* File did not exist, and create failed */
+#define SQS_CHGFAIL -2 /* File exists, but unable to change state */
+#define SQS_STATFAIL -1 /* Unable to stat() the lock file */
+#define SQS_CHGOK 1 /* File existed, and the state was changed */
+#define SQS_CREOK 2 /* File did not exist, but was created OK */
+#define SQS_SKIPCREOK 3 /* File did not exist, and there was */
+ /* no need to create it */
+
+/*
+ * Command codes used in the protocol.
+ */
+#define CMD_CHECK_QUE '\1'
+#define CMD_TAKE_THIS '\2'
+#define CMD_SHOWQ_SHORT '\3'
+#define CMD_SHOWQ_LONG '\4'
+#define CMD_RMJOB '\5'
+
+/*
+ * seteuid() macros.
+*/
+
+extern uid_t uid, euid;
+
+#define PRIV_START { \
+ if (seteuid(euid) != 0) err(1, "seteuid failed"); \
+}
+#define PRIV_END { \
+ if (seteuid(uid) != 0) err(1, "seteuid failed"); \
+}
+
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+
+__BEGIN_DECLS
+struct dirent;
+
+void blankfill(int _tocol);
+int calc_jobnum(const char *_cfname, const char **_hostpp);
+char *checkremote(struct printer *_pp);
+int chk(char *_file);
+void closeallfds(int _start);
+void delay(int _millisec);
+void displayq(struct printer *_pp, int _format);
+void dump(const char *_nfile, const char *_datafile, int _copies);
+void fatal(const struct printer *_pp, const char *_msg, ...)
+ __printflike(2, 3);
+int firstprinter(struct printer *_pp, int *_error);
+void free_printer(struct printer *_pp);
+void free_request(struct request *_rp);
+int getline(FILE *_cfp);
+int getport(const struct printer *_pp, const char *_rhost, int _rport);
+int getprintcap(const char *_printer, struct printer *_pp);
+int getq(const struct printer *_pp, struct jobqueue *(*_namelist[]));
+void header(void);
+void inform(const struct printer *_pp, char *_cf);
+void init_printer(struct printer *_pp);
+void init_request(struct request *_rp);
+int inlist(char *_uname, char *_cfile);
+int iscf(const struct dirent *_d);
+void ldump(const char *_nfile, const char *_datafile, int _copies);
+void lastprinter(void);
+int lockchk(struct printer *_pp, char *_slockf);
+char *lock_file_name(const struct printer *_pp, char *_buf, size_t _len);
+void lpd_gettime(struct timespec *_tsp, char *_strp, size_t _strsize);
+int nextprinter(struct printer *_pp, int *_error);
+const
+char *pcaperr(int _error);
+void prank(int _n);
+void process(const struct printer *_pp, char *_file);
+void rmjob(const char *_printer);
+void rmremote(const struct printer *_pp);
+void setprintcap(char *_newfile);
+int set_qstate(int _action, const char *_lfname);
+void show(const char *_nfile, const char *_datafile, int _copies);
+int startdaemon(const struct printer *_pp);
+char *status_file_name(const struct printer *_pp, char *_buf,
+ size_t _len);
+void trstat_init(struct printer *_pp, const char *_fname, int _filenum);
+void trstat_write(struct printer *_pp, tr_sendrecv _sendrecv,
+ size_t _bytecnt, const char *_userid, const char *_otherhost,
+ const char *_orighost);
+ssize_t writel(int _strm, ...);
+__END_DECLS
diff --git a/usr.sbin/lpr/common_source/lp.local.h b/usr.sbin/lpr/common_source/lp.local.h
new file mode 100644
index 0000000..79cc52a
--- /dev/null
+++ b/usr.sbin/lpr/common_source/lp.local.h
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ *
+ * @(#)lp.local.h 8.1 (Berkeley) 6/6/93
+ * $FreeBSD$
+ */
+
+/*
+ * Possibly, local parameters to the spooling system
+ */
+
+/*
+ * Defaults for line printer capabilities data base
+ */
+#define DEFLP "lp"
+#define DEFLOCK "lock"
+#define DEFSTAT "status"
+#define DEFMX 0
+#define DEFMAXCOPIES 0
+#define DEFFF "\f"
+#define DEFWIDTH 132
+#define DEFLENGTH 66
+#define DEFUID 1
+#define DEFTIMEOUT 120
+
+/*
+ * When files are created in the spooling area, they are normally
+ * readable only by their owner and the spooling group. If you
+ * want otherwise, change this mode.
+ */
+#define FILMOD 0660
+
+/*
+ * Printer is assumed to support LINELEN (for block chars)
+ * and background character (blank) is a space
+ */
+#define LINELEN 132
+#define BACKGND ' '
+
+#define HEIGHT 9 /* height of characters */
+#define WIDTH 8 /* width of characters */
+#define DROP 3 /* offset to drop characters with descenders */
+
+/*
+ * Define TERMCAP if the terminal capabilites are to be used for lpq.
+ */
+#define TERMCAP
+
+/*
+ * Maximum number of user and job requests for lpq and lprm.
+ */
+#define MAXUSERS 50
+#define MAXREQUESTS 50
diff --git a/usr.sbin/lpr/common_source/matchjobs.c b/usr.sbin/lpr/common_source/matchjobs.c
new file mode 100644
index 0000000..c7dd165
--- /dev/null
+++ b/usr.sbin/lpr/common_source/matchjobs.c
@@ -0,0 +1,568 @@
+/*
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * Copyright (c) 2002,2011 - Garance Alistair Drosehn <gad@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.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the FreeBSD Project
+ * or FreeBSD, Inc.
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ */
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+/*
+ * movejobs.c - The lpc commands which move jobs around.
+ */
+
+#include <sys/file.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <dirent.h> /* for MAXNAMLEN, for job_cfname in lp.h! */
+#include <ctype.h>
+#include <errno.h>
+#include <fnmatch.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "ctlinfo.h"
+#include "lp.h"
+#include "matchjobs.h"
+
+#define DEBUG_PARSEJS 0 /* set to 1 when testing */
+#define DEBUG_SCANJS 0 /* set to 1 when testing */
+
+static int match_jobspec(struct jobqueue *_jq, struct jobspec *_jspec);
+
+/*
+ * isdigit is defined to work on an 'int', in the range 0 to 255, plus EOF.
+ * Define a wrapper which can take 'char', either signed or unsigned.
+ */
+#define isdigitch(Anychar) isdigit(((int) Anychar) & 255)
+
+/*
+ * Format a single jobspec into a string fit for printing.
+ */
+void
+format_jobspec(struct jobspec *jspec, int fmt_wanted)
+{
+ char rangestr[40], buildstr[200];
+ const char fromuser[] = "from user ";
+ const char fromhost[] = "from host ";
+ size_t strsize;
+
+ /*
+ * If the struct already has a fmtstring, then release it
+ * before building a new one.
+ */
+ if (jspec->fmtoutput != NULL) {
+ free(jspec->fmtoutput);
+ jspec->fmtoutput = NULL;
+ }
+
+ jspec->pluralfmt = 1; /* assume a "plural result" */
+ rangestr[0] = '\0';
+ if (jspec->startnum >= 0) {
+ if (jspec->startnum != jspec->endrange)
+ snprintf(rangestr, sizeof(rangestr), "%ld-%ld",
+ jspec->startnum, jspec->endrange);
+ else {
+ jspec->pluralfmt = 0;
+ snprintf(rangestr, sizeof(rangestr), "%ld",
+ jspec->startnum);
+ }
+ }
+
+ strsize = sizeof(buildstr);
+ buildstr[0] = '\0';
+ switch (fmt_wanted) {
+ case FMTJS_TERSE:
+ /* Build everything but the hostname in a temp string. */
+ if (jspec->wanteduser != NULL)
+ strlcat(buildstr, jspec->wanteduser, strsize);
+ if (rangestr[0] != '\0') {
+ if (buildstr[0] != '\0')
+ strlcat(buildstr, ":", strsize);
+ strlcat(buildstr, rangestr, strsize);
+ }
+ if (jspec->wantedhost != NULL)
+ strlcat(buildstr, "@", strsize);
+
+ /* Get space for the final result, including hostname */
+ strsize = strlen(buildstr) + 1;
+ if (jspec->wantedhost != NULL)
+ strsize += strlen(jspec->wantedhost);
+ jspec->fmtoutput = malloc(strsize);
+
+ /* Put together the final result */
+ strlcpy(jspec->fmtoutput, buildstr, strsize);
+ if (jspec->wantedhost != NULL)
+ strlcat(jspec->fmtoutput, jspec->wantedhost, strsize);
+ break;
+
+ case FMTJS_VERBOSE:
+ default:
+ /* Build everything but the hostname in a temp string. */
+ strlcat(buildstr, rangestr, strsize);
+ if (jspec->wanteduser != NULL) {
+ if (rangestr[0] != '\0')
+ strlcat(buildstr, " ", strsize);
+ strlcat(buildstr, fromuser, strsize);
+ strlcat(buildstr, jspec->wanteduser, strsize);
+ }
+ if (jspec->wantedhost != NULL) {
+ if (jspec->wanteduser == NULL) {
+ if (rangestr[0] != '\0')
+ strlcat(buildstr, " ", strsize);
+ strlcat(buildstr, fromhost, strsize);
+ } else
+ strlcat(buildstr, "@", strsize);
+ }
+
+ /* Get space for the final result, including hostname */
+ strsize = strlen(buildstr) + 1;
+ if (jspec->wantedhost != NULL)
+ strsize += strlen(jspec->wantedhost);
+ jspec->fmtoutput = malloc(strsize);
+
+ /* Put together the final result */
+ strlcpy(jspec->fmtoutput, buildstr, strsize);
+ if (jspec->wantedhost != NULL)
+ strlcat(jspec->fmtoutput, jspec->wantedhost, strsize);
+ break;
+ }
+}
+
+/*
+ * Free all the jobspec-related information.
+ */
+void
+free_jobspec(struct jobspec_hdr *js_hdr)
+{
+ struct jobspec *jsinf;
+
+ while (!STAILQ_EMPTY(js_hdr)) {
+ jsinf = STAILQ_FIRST(js_hdr);
+ STAILQ_REMOVE_HEAD(js_hdr, nextjs);
+ if (jsinf->fmtoutput)
+ free(jsinf->fmtoutput);
+ if (jsinf->matcheduser)
+ free(jsinf->matcheduser);
+ free(jsinf);
+ }
+}
+
+/*
+ * This routine takes a string as typed in from the user, and parses it
+ * into a job-specification. A job specification would match one or more
+ * jobs in the queue of some single printer (the specification itself does
+ * not indicate which queue should be searched).
+ *
+ * This recognizes a job-number range by itself (all digits, or a range
+ * indicated by "digits-digits"), or a userid by itself. If a `:' is
+ * found, it is treated as a separator between a job-number range and
+ * a userid, where the job number range is the side which has a digit as
+ * the first character. If an `@' is found, everything to the right of
+ * it is treated as the hostname the job originated from.
+ *
+ * So, the user can specify:
+ * jobrange userid userid:jobrange jobrange:userid
+ * jobrange@hostname jobrange:userid@hostname
+ * userid@hostname userid:jobrange@hostname
+ *
+ * XXX - it would be nice to add "not options" too, such as ^user,
+ * ^jobrange, and @^hostname.
+ *
+ * This routine may modify the original input string if that input is
+ * valid. If the input was *not* valid, then this routine should return
+ * with the input string the same as when the routine was called.
+ */
+int
+parse_jobspec(char *jobstr, struct jobspec_hdr *js_hdr)
+{
+ struct jobspec *jsinfo;
+ char *atsign, *colon, *lhside, *numstr, *period, *rhside;
+ int jobnum;
+
+#if DEBUG_PARSEJS
+ printf("\t [ pjs-input = %s ]\n", jobstr);
+#endif
+
+ if ((jobstr == NULL) || (*jobstr == '\0'))
+ return (0);
+
+ jsinfo = malloc(sizeof(struct jobspec));
+ memset(jsinfo, 0, sizeof(struct jobspec));
+ jsinfo->startnum = jsinfo->endrange = -1;
+
+ /* Find the separator characters, and nullify them. */
+ numstr = NULL;
+ atsign = strchr(jobstr, '@');
+ colon = strchr(jobstr, ':');
+ if (atsign != NULL)
+ *atsign = '\0';
+ if (colon != NULL)
+ *colon = '\0';
+
+ /* The at-sign always indicates a hostname. */
+ if (atsign != NULL) {
+ rhside = atsign + 1;
+ if (*rhside != '\0')
+ jsinfo->wantedhost = rhside;
+ }
+
+ /* Finish splitting the input into three parts. */
+ rhside = NULL;
+ if (colon != NULL) {
+ rhside = colon + 1;
+ if (*rhside == '\0')
+ rhside = NULL;
+ }
+ lhside = NULL;
+ if (*jobstr != '\0')
+ lhside = jobstr;
+
+ /*
+ * If there is a `:' here, then it's either jobrange:userid,
+ * userid:jobrange, or (if @hostname was not given) perhaps it
+ * might be hostname:jobnum. The side which has a digit as the
+ * first character is assumed to be the jobrange. It is an
+ * input error if both sides start with a digit, or if neither
+ * side starts with a digit.
+ */
+ if ((lhside != NULL) && (rhside != NULL)) {
+ if (isdigitch(*lhside)) {
+ if (isdigitch(*rhside))
+ goto bad_input;
+ numstr = lhside;
+ jsinfo->wanteduser = rhside;
+ } else if (isdigitch(*rhside)) {
+ numstr = rhside;
+ /*
+ * The original implementation of 'lpc topq' accepted
+ * hostname:jobnum. If the input did not include a
+ * @hostname, then assume the userid is a hostname if
+ * it includes a '.'.
+ */
+ period = strchr(lhside, '.');
+ if ((atsign == NULL) && (period != NULL))
+ jsinfo->wantedhost = lhside;
+ else
+ jsinfo->wanteduser = lhside;
+ } else {
+ /* Neither side is a job number = user error */
+ goto bad_input;
+ }
+ } else if (lhside != NULL) {
+ if (isdigitch(*lhside))
+ numstr = lhside;
+ else
+ jsinfo->wanteduser = lhside;
+ } else if (rhside != NULL) {
+ if (isdigitch(*rhside))
+ numstr = rhside;
+ else
+ jsinfo->wanteduser = rhside;
+ }
+
+ /*
+ * Break down the numstr. It should be all digits, or a range
+ * specified as "\d+-\d+".
+ */
+ if (numstr != NULL) {
+ errno = 0;
+ jobnum = strtol(numstr, &numstr, 10);
+ if (errno != 0) /* error in conversion */
+ goto bad_input;
+ if (jobnum < 0) /* a bogus value for this purpose */
+ goto bad_input;
+ if (jobnum > 99999) /* too large for job number */
+ goto bad_input;
+ jsinfo->startnum = jsinfo->endrange = jobnum;
+
+ /* Check for a range of numbers */
+ if ((*numstr == '-') && (isdigitch(*(numstr + 1)))) {
+ numstr++;
+ errno = 0;
+ jobnum = strtol(numstr, &numstr, 10);
+ if (errno != 0) /* error in conversion */
+ goto bad_input;
+ if (jobnum < jsinfo->startnum)
+ goto bad_input;
+ if (jobnum > 99999) /* too large for job number */
+ goto bad_input;
+ jsinfo->endrange = jobnum;
+ }
+
+ /*
+ * If there is anything left in the numstr, and if the
+ * original string did not include a userid or a hostname,
+ * then this might be the ancient form of '\d+hostname'
+ * (with no separator between jobnum and hostname). Accept
+ * that for backwards compatibility, but otherwise any
+ * remaining characters mean a user-error. Note that the
+ * ancient form accepted only a single number, but this
+ * will also accept a range of numbers.
+ */
+ if (*numstr != '\0') {
+ if (atsign != NULL)
+ goto bad_input;
+ if (jsinfo->wantedhost != NULL)
+ goto bad_input;
+ if (jsinfo->wanteduser != NULL)
+ goto bad_input;
+ /* Treat as the rest of the string as a hostname */
+ jsinfo->wantedhost = numstr;
+ }
+ }
+
+ if ((jsinfo->startnum < 0) && (jsinfo->wanteduser == NULL) &&
+ (jsinfo->wantedhost == NULL))
+ goto bad_input;
+
+ /*
+ * The input was valid, in the sense that it could be parsed
+ * into the individual parts. Add this jobspec to the list
+ * of jobspecs.
+ */
+ STAILQ_INSERT_TAIL(js_hdr, jsinfo, nextjs);
+
+#if DEBUG_PARSEJS
+ printf("\t [ will check for");
+ if (jsinfo->startnum >= 0) {
+ if (jsinfo->startnum == jsinfo->endrange)
+ printf(" jobnum = %ld", jsinfo->startnum);
+ else
+ printf(" jobrange = %ld to %ld", jsinfo->startnum,
+ jsinfo->endrange);
+ } else {
+ printf(" jobs");
+ }
+ if ((jsinfo->wanteduser != NULL) || (jsinfo->wantedhost != NULL)) {
+ printf(" from");
+ if (jsinfo->wanteduser != NULL)
+ printf(" user = %s", jsinfo->wanteduser);
+ if (jsinfo->wantedhost != NULL)
+ printf(" host = %s", jsinfo->wantedhost);
+ }
+ printf("]\n");
+#endif
+
+ return (1);
+
+bad_input:
+ /*
+ * Restore any `@' and `:', in case the calling routine wants to
+ * write an error message which includes the input string.
+ */
+ if (atsign != NULL)
+ *atsign = '@';
+ if (colon != NULL)
+ *colon = ':';
+ if (jsinfo != NULL)
+ free(jsinfo);
+ return (0);
+}
+
+/*
+ * Check to see if a given job (specified by a jobqueue entry) matches
+ * all of the specifications in a given jobspec.
+ *
+ * Returns 0 if no match, 1 if the job does match.
+ */
+static int
+match_jobspec(struct jobqueue *jq, struct jobspec *jspec)
+{
+ struct cjobinfo *cfinf;
+ const char *cf_hoststr;
+ int jnum, match;
+
+#if DEBUG_SCANJS
+ printf("\t [ match-js checking %s ]\n", jq->job_cfname);
+#endif
+
+ if (jspec == NULL || jq == NULL)
+ return (0);
+
+ /*
+ * Keep track of which jobs have already been matched by this
+ * routine, and thus (probably) already processed.
+ */
+ if (jq->job_matched)
+ return (0);
+
+ jnum = calc_jobnum(jq->job_cfname, &cf_hoststr);
+ cfinf = NULL;
+ match = 0; /* assume the job will not match */
+ jspec->matcheduser = NULL;
+
+ /*
+ * Check the job-number range.
+ */
+ if (jspec->startnum >= 0) {
+ if (jnum < jspec->startnum)
+ goto nomatch;
+ if (jnum > jspec->endrange)
+ goto nomatch;
+ }
+
+ /*
+ * Check the hostname. Strictly speaking this should be done by
+ * reading the control file, but it is less expensive to check
+ * the hostname-part of the control file name. Also, this value
+ * can be easily seen in 'lpq -l', while there is no easy way for
+ * a user/operator to see the hostname in the control file.
+ */
+ if (jspec->wantedhost != NULL) {
+ if (fnmatch(jspec->wantedhost, cf_hoststr, 0) != 0)
+ goto nomatch;
+ }
+
+ /*
+ * Check for a match on the user name. This has to be done
+ * by reading the control file.
+ */
+ if (jspec->wanteduser != NULL) {
+ cfinf = ctl_readcf("fakeq", jq->job_cfname);
+ if (cfinf == NULL)
+ goto nomatch;
+ if (fnmatch(jspec->wanteduser, cfinf->cji_acctuser, 0) != 0)
+ goto nomatch;
+ }
+
+ /* This job matches all of the specified criteria. */
+ match = 1;
+ jq->job_matched = 1; /* avoid matching the job twice */
+ jspec->matchcnt++;
+ if (jspec->wanteduser != NULL) {
+ /*
+ * If the user specified a userid (which may have been a
+ * pattern), then the caller's "doentry()" routine might
+ * want to know the userid of this job that matched.
+ */
+ jspec->matcheduser = strdup(cfinf->cji_acctuser);
+ }
+#if DEBUG_SCANJS
+ printf("\t [ job matched! ]\n");
+#endif
+
+nomatch:
+ if (cfinf != NULL)
+ ctl_freeinf(cfinf);
+ return (match);
+}
+
+/*
+ * Scan a queue for all jobs which match a jobspec. The queue is scanned
+ * from top to bottom.
+ *
+ * The caller can provide a routine which will be executed for each job
+ * that does match. Note that the processing routine might do anything
+ * to the matched job -- including the removal of it.
+ *
+ * This returns the number of jobs which were matched.
+ */
+int
+scanq_jobspec(int qcount, struct jobqueue **squeue, int sopts, struct
+ jobspec_hdr *js_hdr, process_jqe doentry, void *doentryinfo)
+{
+ struct jobqueue **qent;
+ struct jobspec *jspec;
+ int cnt, matched, total;
+
+ if (qcount < 1)
+ return (0);
+ if (js_hdr == NULL)
+ return (-1);
+
+ /* The caller must specify one of the scanning orders */
+ if ((sopts & (SCQ_JSORDER|SCQ_QORDER)) == 0)
+ return (-1);
+
+ total = 0;
+ if (sopts & SCQ_JSORDER) {
+ /*
+ * For each job specification, scan through the queue
+ * looking for every job that matches.
+ */
+ STAILQ_FOREACH(jspec, js_hdr, nextjs) {
+ for (qent = squeue, cnt = 0; cnt < qcount;
+ qent++, cnt++) {
+ matched = match_jobspec(*qent, jspec);
+ if (!matched)
+ continue;
+ total++;
+ if (doentry != NULL)
+ doentry(doentryinfo, *qent, jspec);
+ if (jspec->matcheduser != NULL) {
+ free(jspec->matcheduser);
+ jspec->matcheduser = NULL;
+ }
+ }
+ /*
+ * The entire queue has been scanned for this
+ * jobspec. Call the user's routine again with
+ * a NULL queue-entry, so it can print out any
+ * kind of per-jobspec summary.
+ */
+ if (doentry != NULL)
+ doentry(doentryinfo, NULL, jspec);
+ }
+ } else {
+ /*
+ * For each job in the queue, check all of the job
+ * specifications to see if any one of them matches
+ * that job.
+ */
+ for (qent = squeue, cnt = 0; cnt < qcount;
+ qent++, cnt++) {
+ STAILQ_FOREACH(jspec, js_hdr, nextjs) {
+ matched = match_jobspec(*qent, jspec);
+ if (!matched)
+ continue;
+ total++;
+ if (doentry != NULL)
+ doentry(doentryinfo, *qent, jspec);
+ if (jspec->matcheduser != NULL) {
+ free(jspec->matcheduser);
+ jspec->matcheduser = NULL;
+ }
+ /*
+ * Once there is a match, then there is no
+ * point in checking this same job against
+ * all the other jobspec's.
+ */
+ break;
+ }
+ }
+ }
+
+ return (total);
+}
diff --git a/usr.sbin/lpr/common_source/matchjobs.h b/usr.sbin/lpr/common_source/matchjobs.h
new file mode 100644
index 0000000..6e4b9ff
--- /dev/null
+++ b/usr.sbin/lpr/common_source/matchjobs.h
@@ -0,0 +1,102 @@
+/*
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * Copyright (c) 2002 - Garance Alistair Drosehn <gad@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.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the FreeBSD Project
+ * or FreeBSD, Inc.
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * $FreeBSD$
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ */
+
+#include <sys/queue.h>
+
+/*
+ * The "matcheduser" field is *only* valid during the call to the
+ * given "doentry()" routine, and is only set if the specification
+ * included a userid.
+ */
+struct jobspec {
+ STAILQ_ENTRY(jobspec) nextjs;
+ char *wantedhost;
+ char *wanteduser;
+ char *matcheduser; /* only valid for "doentry()" */
+ char *fmtoutput; /* set by format_jobspec() */
+ long startnum;
+ long endrange;
+ int pluralfmt; /* boolean set by format_jobspec() */
+ uint matchcnt;
+};
+STAILQ_HEAD(jobspec_hdr, jobspec);
+
+/*
+ * Format options for format_jobspec.
+ */
+#define FMTJS_TERSE 1 /* user:jobrange@host */
+#define FMTJS_VERBOSE 2 /* jobrange from user@host */
+
+/*
+ * Options for scanq_jobspec.
+ *
+ * The caller must choose the order that entries should be scanned:
+ * 1) JSORDER: Matched jobs are processed (by calling the "doentry()"
+ * routine) in the order that the user specified those jobs.
+ * 2) QORDER: Matched jobs are processed in the order that the jobs are
+ * listed the queue. This guarantees that the "doentry()" routine
+ * will be called only once per job.
+ *
+ * There is a "job_matched" variable in struct jobqueue, which is used
+ * to make sure that the "doentry()" will only be called once for any
+ * given job in JSORDER processing. The "doentry()" routine can turn
+ * that off, if it does want to be called multiple times when the job
+ * is matched by multiple specifiers.
+ *
+ * The JSORDER processing will also call the "doentry()" routine once
+ * after each scan of the queue, with the jobqueue set to null. This
+ * provides a way for the caller to print out a summary message for
+ * each jobspec that was given.
+ */
+#define SCQ_JSORDER 0x0001 /* follow the user-specified order */
+#define SCQ_QORDER 0x0002 /* the order of jobs in the queue */
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+
+__BEGIN_DECLS
+struct jobqueue;
+
+typedef int process_jqe(void *_myinfo, struct jobqueue *_jq,
+ struct jobspec *_jspec);
+
+void format_jobspec(struct jobspec *_jspec, int _fmt_wanted);
+void free_jobspec(struct jobspec_hdr *_js_hdr);
+int scanq_jobspec(int _qitems, struct jobqueue **_squeue, int _sopts,
+ struct jobspec_hdr *_js_hdr, process_jqe _doentry,
+ void *_doentryinfo);
+int parse_jobspec(char *_jobstr, struct jobspec_hdr *_js_hdr);
+__END_DECLS
+
diff --git a/usr.sbin/lpr/common_source/net.c b/usr.sbin/lpr/common_source/net.c
new file mode 100644
index 0000000..ab6fa16
--- /dev/null
+++ b/usr.sbin/lpr/common_source/net.c
@@ -0,0 +1,298 @@
+/*
+ * 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.
+ * 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: @(#)common.c 8.5 (Berkeley) 4/28/95
+ */
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <dirent.h> /* required for lp.h, not used here */
+#include <err.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "lp.h"
+#include "lp.local.h"
+#include "pathnames.h"
+
+/*
+ * 'local_host' is always the hostname of the machine which is running
+ * lpr (lpd, whatever), while 'from_host' either points at 'local_host'
+ * or points at a different buffer when receiving a job from a remote
+ * machine (and that buffer has the hostname of that remote machine).
+ */
+char local_host[MAXHOSTNAMELEN]; /* host running lpd/lpr */
+const char *from_host = local_host; /* client's machine name */
+const char *from_ip = ""; /* client machine's IP address */
+
+#ifdef INET6
+u_char family = PF_UNSPEC;
+#else
+u_char family = PF_INET;
+#endif
+
+/*
+ * Create a TCP connection to host "rhost" at port "rport".
+ * If rport == 0, then use the printer service port.
+ * Most of this code comes from rcmd.c.
+ */
+int
+getport(const struct printer *pp, const char *rhost, int rport)
+{
+ struct addrinfo hints, *res, *ai;
+ int s, timo = 1, lport = IPPORT_RESERVED - 1;
+ int error, refused = 0;
+
+ /*
+ * Get the host address and port number to connect to.
+ */
+ if (rhost == NULL)
+ fatal(pp, "no remote host to connect to");
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = 0;
+ error = getaddrinfo(rhost, (rport == 0 ? "printer" : NULL),
+ &hints, &res);
+ if (error)
+ fatal(pp, "%s\n", gai_strerror(error));
+ if (rport != 0)
+ ((struct sockaddr_in *) res->ai_addr)->sin_port = htons(rport);
+
+ /*
+ * Try connecting to the server.
+ */
+ ai = res;
+retry:
+ PRIV_START
+ s = rresvport_af(&lport, ai->ai_family);
+ PRIV_END
+ if (s < 0) {
+ if (errno != EAGAIN) {
+ if (ai->ai_next) {
+ ai = ai->ai_next;
+ goto retry;
+ }
+ if (refused && timo <= 16) {
+ sleep(timo);
+ timo *= 2;
+ refused = 0;
+ ai = res;
+ goto retry;
+ }
+ }
+ freeaddrinfo(res);
+ return(-1);
+ }
+ if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0) {
+ error = errno;
+ (void) close(s);
+ errno = error;
+ /*
+ * This used to decrement lport, but the current semantics
+ * of rresvport do not provide such a function (in fact,
+ * rresvport should guarantee that the chosen port will
+ * never result in an EADDRINUSE).
+ */
+ if (errno == EADDRINUSE) {
+ goto retry;
+ }
+
+ if (errno == ECONNREFUSED)
+ refused++;
+
+ if (ai->ai_next != NULL) {
+ ai = ai->ai_next;
+ goto retry;
+ }
+ if (refused && timo <= 16) {
+ sleep(timo);
+ timo *= 2;
+ refused = 0;
+ ai = res;
+ goto retry;
+ }
+ freeaddrinfo(res);
+ return(-1);
+ }
+ freeaddrinfo(res);
+ return(s);
+}
+
+/*
+ * Figure out whether the local machine is the same
+ * as the remote machine (RM) entry (if it exists).
+ * We do this by counting the intersection of our
+ * address list and theirs. This is better than the
+ * old method (comparing the canonical names), as it
+ * allows load-sharing between multiple print servers.
+ * The return value is an error message which must be
+ * free()d.
+ */
+char *
+checkremote(struct printer *pp)
+{
+ char lclhost[MAXHOSTNAMELEN];
+ struct addrinfo hints, *local_res, *remote_res, *lr, *rr;
+ char *error;
+ int ncommonaddrs, errno;
+ char h1[NI_MAXHOST], h2[NI_MAXHOST];
+
+ if (!pp->rp_matches_local) { /* Remote printer doesn't match local */
+ pp->remote = 1;
+ return NULL;
+ }
+
+ pp->remote = 0; /* assume printer is local */
+ if (pp->remote_host == NULL)
+ return NULL;
+
+ /* get the addresses of the local host */
+ gethostname(lclhost, sizeof(lclhost));
+ lclhost[sizeof(lclhost) - 1] = '\0';
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE;
+ if ((errno = getaddrinfo(lclhost, NULL, &hints, &local_res)) != 0) {
+ asprintf(&error, "unable to get official name "
+ "for local machine %s: %s",
+ lclhost, gai_strerror(errno));
+ return error;
+ }
+
+ /* get the official name of RM */
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE;
+ if ((errno = getaddrinfo(pp->remote_host, NULL,
+ &hints, &remote_res)) != 0) {
+ asprintf(&error, "unable to get address list for "
+ "remote machine %s: %s",
+ pp->remote_host, gai_strerror(errno));
+ freeaddrinfo(local_res);
+ return error;
+ }
+
+ ncommonaddrs = 0;
+ for (lr = local_res; lr; lr = lr->ai_next) {
+ h1[0] = '\0';
+ if (getnameinfo(lr->ai_addr, lr->ai_addrlen, h1, sizeof(h1),
+ NULL, 0, NI_NUMERICHOST) != 0)
+ continue;
+ for (rr = remote_res; rr; rr = rr->ai_next) {
+ h2[0] = '\0';
+ if (getnameinfo(rr->ai_addr, rr->ai_addrlen,
+ h2, sizeof(h2), NULL, 0,
+ NI_NUMERICHOST) != 0)
+ continue;
+ if (strcmp(h1, h2) == 0)
+ ncommonaddrs++;
+ }
+ }
+
+ /*
+ * if the two hosts do not share at least one IP address
+ * then the printer must be remote.
+ */
+ if (ncommonaddrs == 0)
+ pp->remote = 1;
+ freeaddrinfo(local_res);
+ freeaddrinfo(remote_res);
+ return NULL;
+}
+
+/*
+ * This isn't really network-related, but it's used here to write
+ * multi-part strings onto sockets without using stdio. Return
+ * values are as for writev(2).
+ */
+ssize_t
+writel(int strm, ...)
+{
+ va_list ap;
+ int i, n;
+ const char *cp;
+#define NIOV 12
+ struct iovec iov[NIOV], *iovp = iov;
+ ssize_t retval;
+
+ /* first count them */
+ va_start(ap, strm);
+ n = 0;
+ do {
+ cp = va_arg(ap, char *);
+ n++;
+ } while (cp);
+ va_end(ap);
+ n--; /* correct for count of trailing null */
+
+ if (n > NIOV) {
+ iovp = malloc(n * sizeof *iovp);
+ if (iovp == 0)
+ return -1;
+ }
+
+ /* now make up iovec and send */
+ va_start(ap, strm);
+ for (i = 0; i < n; i++) {
+ iovp[i].iov_base = va_arg(ap, char *);
+ iovp[i].iov_len = strlen(iovp[i].iov_base);
+ }
+ va_end(ap);
+ retval = writev(strm, iovp, n);
+ if (iovp != iov)
+ free(iovp);
+ return retval;
+}
diff --git a/usr.sbin/lpr/common_source/pathnames.h b/usr.sbin/lpr/common_source/pathnames.h
new file mode 100644
index 0000000..0be302a
--- /dev/null
+++ b/usr.sbin/lpr/common_source/pathnames.h
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ *
+ * $FreeBSD$
+ *
+ * @(#)pathnames.h 8.1 (Berkeley) 6/6/93
+ */
+
+#include <paths.h>
+
+#define _PATH_DEFDEVLP "/dev/lp"
+#define _PATH_DEFSPOOL "/var/spool/output/lpd"
+#define _PATH_HOSTSEQUIV "/etc/hosts.equiv"
+#define _PATH_HOSTSLPD "/etc/hosts.lpd"
+#define _PATH_MASTERLOCK "/var/spool/output/lpd.lock"
+#define _PATH_PR "/usr/bin/pr"
+#define _PATH_PRINTCAP "/etc/printcap"
+#define _PATH_SOCKETNAME "/var/run/printer"
+#define _PATH_VFONT "/usr/libdata/vfont/"
+#define _PATH_VFONTB "/usr/libdata/vfont/B"
+#define _PATH_VFONTI "/usr/libdata/vfont/I"
+#define _PATH_VFONTR "/usr/libdata/vfont/R"
+#define _PATH_VFONTS "/usr/libdata/vfont/S"
+#define _PATH_CHKPRINTCAP "/usr/sbin/chkprintcap"
diff --git a/usr.sbin/lpr/common_source/printcap.c b/usr.sbin/lpr/common_source/printcap.c
new file mode 100644
index 0000000..0d3644b
--- /dev/null
+++ b/usr.sbin/lpr/common_source/printcap.c
@@ -0,0 +1,451 @@
+/*
+ * 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.
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)printcap.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+#endif
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/param.h> /* required for lp.h, but not used here */
+#include <sys/dirent.h> /* ditto */
+#include "lp.h"
+#include "lp.local.h"
+#include "pathnames.h"
+
+/*
+ * Routines and data used in processing the printcap file.
+ */
+static char *printcapdb[2] = { _PATH_PRINTCAP, 0 }; /* list for cget* */
+
+static char *capdb_canonical_name(const char *_bp);
+static int capdb_getaltlog(char *_bp, const char *_shrt,
+ const char *_lng);
+static int capdb_getaltnum(char *_bp, const char *_shrt,
+ const char *_lng, long _dflt, long *_result);
+static int capdb_getaltstr(char *_bp, const char *_shrt,
+ const char *lng, const char *_dflt, char **_result);
+static int getprintcap_int(char *_bp, struct printer *_pp);
+
+/*
+ * Change the name of the printcap file. Used by chkprintcap(8),
+ * but could be used by other members of the suite with appropriate
+ * security measures.
+ */
+void
+setprintcap(char *newfile)
+{
+ printcapdb[0] = newfile;
+}
+
+/*
+ * Read the printcap database for printer `printer' into the
+ * struct printer pointed by `pp'. Return values are as for
+ * cgetent(3): -1 means we could not find what we wanted, -2
+ * means a system error occurred (and errno is set), -3 if a
+ * reference (`tc=') loop was detected, and 0 means success.
+ *
+ * Copied from lpr; should add additional capabilities as they
+ * are required by the other programs in the suite so that
+ * printcap-reading is consistent across the entire family.
+ */
+int
+getprintcap(const char *printer, struct printer *pp)
+{
+ int status;
+ char *XXX;
+ char *bp;
+
+ /*
+ * A bug in the declaration of cgetent(3) means that we have
+ * to hide the constness of its third argument.
+ */
+ XXX = (char *)printer;
+ if ((status = cgetent(&bp, printcapdb, XXX)) < 0)
+ return status;
+ status = getprintcap_int(bp, pp);
+ free(bp);
+ return status;
+}
+
+/*
+ * Map the status values returned by cgetfirst/cgetnext into those
+ * used by cgetent, returning truth if there are more records to
+ * examine. This points out what is arguably a bug in the cget*
+ * interface (or at least a nasty wart).
+ */
+static int
+firstnextmap(int *status)
+{
+ switch (*status) {
+ case 0:
+ return 0;
+ case 1:
+ *status = 0;
+ return 1;
+ case 2:
+ *status = 1;
+ return 1;
+ case -1:
+ *status = -2;
+ return 0;
+ case -2:
+ *status = -3;
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/*
+ * Scan through the database of printers using cgetfirst/cgetnext.
+ * Return false of error or end-of-database; else true.
+ */
+int
+firstprinter(struct printer *pp, int *error)
+{
+ int status;
+ char *bp;
+
+ init_printer(pp);
+ status = cgetfirst(&bp, printcapdb);
+ if (firstnextmap(&status) == 0) {
+ if (error)
+ *error = status;
+ return 0;
+ }
+ if (error)
+ *error = status;
+ status = getprintcap_int(bp, pp);
+ free(bp);
+ if (error && status)
+ *error = status;
+ return 1;
+}
+
+int
+nextprinter(struct printer *pp, int *error)
+{
+ int status;
+ char *bp;
+
+ free_printer(pp);
+ status = cgetnext(&bp, printcapdb);
+ if (firstnextmap(&status) == 0) {
+ if (error)
+ *error = status;
+ return 0;
+ }
+ if (error)
+ *error = status;
+ status = getprintcap_int(bp, pp);
+ free(bp);
+ if (error && status)
+ *error = status;
+ return 1;
+}
+
+void
+lastprinter(void)
+{
+ cgetclose();
+}
+
+/*
+ * This must match the order of declaration of enum filter in lp.h.
+ */
+static const char *filters[] = {
+ "cf", "df", "gf", "if", "nf", "of", "rf", "tf", "vf"
+};
+
+static const char *longfilters[] = {
+ "filt.cifplot", "filt.dvi", "filt.plot", "filt.input", "filt.ditroff",
+ "filt.output", "filt.fortran", "filt.troff", "filt.raster"
+};
+
+/*
+ * Internal routine for both getprintcap() and nextprinter().
+ * Actually parse the printcap entry using cget* functions.
+ * Also attempt to figure out the canonical name of the printer
+ * and store a malloced copy of it in pp->printer.
+ */
+static int
+getprintcap_int(char *bp, struct printer *pp)
+{
+ enum lpd_filters filt;
+ char *rp_name;
+ int error;
+
+ if ((pp->printer = capdb_canonical_name(bp)) == 0)
+ return PCAPERR_OSERR;
+
+#define CHK(x) do {if ((x) == PCAPERR_OSERR) return PCAPERR_OSERR;}while(0)
+ CHK(capdb_getaltstr(bp, "af", "acct.file", 0, &pp->acct_file));
+ CHK(capdb_getaltnum(bp, "br", "tty.rate", 0, &pp->baud_rate));
+ CHK(capdb_getaltnum(bp, "ct", "remote.timeout", DEFTIMEOUT,
+ &pp->conn_timeout));
+ CHK(capdb_getaltnum(bp, "du", "daemon.user", DEFUID,
+ &pp->daemon_user));
+ CHK(capdb_getaltstr(bp, "ff", "job.formfeed", DEFFF, &pp->form_feed));
+ CHK(capdb_getaltstr(bp, "lf", "spool.log", _PATH_CONSOLE,
+ &pp->log_file));
+ CHK(capdb_getaltstr(bp, "lo", "spool.lock", DEFLOCK, &pp->lock_file));
+ CHK(capdb_getaltstr(bp, "lp", "tty.device", _PATH_DEFDEVLP, &pp->lp));
+ CHK(capdb_getaltnum(bp, "mc", "max.copies", DEFMAXCOPIES,
+ &pp->max_copies));
+ CHK(capdb_getaltstr(bp, "ms", "tty.mode", 0, &pp->mode_set));
+ CHK(capdb_getaltnum(bp, "mx", "max.blocks", DEFMX, &pp->max_blocks));
+ CHK(capdb_getaltnum(bp, "pc", "acct.price", 0, &pp->price100));
+ CHK(capdb_getaltnum(bp, "pl", "page.length", DEFLENGTH,
+ &pp->page_length));
+ CHK(capdb_getaltnum(bp, "pw", "page.width", DEFWIDTH,
+ &pp->page_width));
+ CHK(capdb_getaltnum(bp, "px", "page.pwidth", 0, &pp->page_pwidth));
+ CHK(capdb_getaltnum(bp, "py", "page.plength", 0, &pp->page_plength));
+ CHK(capdb_getaltstr(bp, "rg", "daemon.restrictgrp", 0,
+ &pp->restrict_grp));
+ CHK(capdb_getaltstr(bp, "rm", "remote.host", 0, &pp->remote_host));
+ CHK(capdb_getaltstr(bp, "rp", "remote.queue", DEFLP,
+ &pp->remote_queue));
+ CHK(capdb_getaltstr(bp, "sd", "spool.dir", _PATH_DEFSPOOL,
+ &pp->spool_dir));
+ CHK(capdb_getaltstr(bp, "sr", "stat.recv", 0, &pp->stat_recv));
+ CHK(capdb_getaltstr(bp, "ss", "stat.send", 0, &pp->stat_send));
+ CHK(capdb_getaltstr(bp, "st", "spool.status", DEFSTAT,
+ &pp->status_file));
+ CHK(capdb_getaltstr(bp, "tr", "job.trailer", 0, &pp->trailer));
+
+ pp->resend_copies = capdb_getaltlog(bp, "rc", "remote.resend_copies");
+ pp->restricted = capdb_getaltlog(bp, "rs", "daemon.restricted");
+ pp->short_banner = capdb_getaltlog(bp, "sb", "banner.short");
+ pp->no_copies = capdb_getaltlog(bp, "sc", "job.no_copies");
+ pp->no_formfeed = capdb_getaltlog(bp, "sf", "job.no_formfeed");
+ pp->no_header = capdb_getaltlog(bp, "sh", "banner.disable");
+ pp->header_last = capdb_getaltlog(bp, "hl", "banner.last");
+ pp->rw = capdb_getaltlog(bp, "rw", "tty.rw");
+ pp->tof = !capdb_getaltlog(bp, "fo", "job.topofform");
+
+ /*
+ * Decide if the remote printer name matches the local printer name.
+ * If no name is given then we assume they mean them to match.
+ * If a name is given see if the rp_name is one of the names for
+ * this printer.
+ */
+ pp->rp_matches_local = 1;
+ CHK((error = capdb_getaltstr(bp, "rp", "remote.queue", 0, &rp_name)));
+ if (error != PCAPERR_NOTFOUND && rp_name != NULL) {
+ if (cgetmatch(bp,rp_name) != 0)
+ pp->rp_matches_local = 0;
+ free(rp_name);
+ }
+
+ /*
+ * Filters:
+ */
+ for (filt = 0; filt < LPF_COUNT; filt++) {
+ CHK(capdb_getaltstr(bp, filters[filt], longfilters[filt], 0,
+ &pp->filters[filt]));
+ }
+
+ return 0;
+}
+
+/*
+ * Decode the error codes returned by cgetent() using the names we
+ * made up for them from "lp.h".
+ * This would have been much better done with Common Error, >sigh<.
+ * Perhaps this can be fixed in the next incarnation of cget*.
+ */
+const char *
+pcaperr(int error)
+{
+ switch(error) {
+ case PCAPERR_TCOPEN:
+ return "unresolved tc= expansion";
+ case PCAPERR_SUCCESS:
+ return "no error";
+ case PCAPERR_NOTFOUND:
+ return "printer not found";
+ case PCAPERR_OSERR:
+ return strerror(errno);
+ case PCAPERR_TCLOOP:
+ return "loop detected in tc= expansion";
+ default:
+ return "unknown printcap error";
+ }
+}
+
+/*
+ * Initialize a `struct printer' to contain values harmless to
+ * the other routines in liblpr.
+ */
+void
+init_printer(struct printer *pp)
+{
+ static struct printer zero;
+ *pp = zero;
+}
+
+/*
+ * Free the dynamically-allocated strings in a `struct printer'.
+ * Idempotent.
+ */
+void
+free_printer(struct printer *pp)
+{
+ enum lpd_filters filt;
+#define cfree(x) do { if (x) free(x); } while(0)
+ cfree(pp->printer);
+ cfree(pp->acct_file);
+ for (filt = 0; filt < LPF_COUNT; filt++)
+ cfree(pp->filters[filt]);
+ cfree(pp->form_feed);
+ cfree(pp->log_file);
+ cfree(pp->lock_file);
+ cfree(pp->lp);
+ cfree(pp->restrict_grp);
+ cfree(pp->remote_host);
+ cfree(pp->remote_queue);
+ cfree(pp->spool_dir);
+ cfree(pp->stat_recv);
+ cfree(pp->stat_send);
+ cfree(pp->status_file);
+ cfree(pp->trailer);
+ cfree(pp->mode_set);
+
+ init_printer(pp);
+}
+
+
+/*
+ * The following routines are part of what would be a sensible library
+ * interface to capability databases. Maybe someday this will become
+ * the default.
+ */
+
+/*
+ * It provides similar functionality to cgetstr(),
+ * except that it provides for both a long and a short
+ * capability name and allows for a default to be specified.
+ */
+static int
+capdb_getaltstr(char *bp, const char *shrt, const char *lng,
+ const char *dflt, char **result)
+{
+ int status;
+
+ status = cgetstr(bp, (char *)/*XXX*/lng, result);
+ if (status >= 0 || status == PCAPERR_OSERR)
+ return status;
+ status = cgetstr(bp, (char *)/*XXX*/shrt, result);
+ if (status >= 0 || status == PCAPERR_OSERR)
+ return status;
+ if (dflt) {
+ *result = strdup(dflt);
+ if (*result == 0)
+ return PCAPERR_OSERR;
+ return strlen(*result);
+ }
+ return PCAPERR_NOTFOUND;
+}
+
+/*
+ * The same, only for integers.
+ */
+static int
+capdb_getaltnum(char *bp, const char *shrt, const char *lng, long dflt,
+ long *result)
+{
+ int status;
+
+ status = cgetnum(bp, (char *)/*XXX*/lng, result);
+ if (status >= 0)
+ return status;
+ status = cgetnum(bp, (char *)/*XXX*/shrt, result);
+ if (status >= 0)
+ return status;
+ *result = dflt;
+ return 0;
+}
+
+/*
+ * Likewise for logical values. There's no need for a default parameter
+ * because the default is always false.
+ */
+static int
+capdb_getaltlog(char *bp, const char *shrt, const char *lng)
+{
+ if (cgetcap(bp, (char *)/*XXX*/lng, ':'))
+ return 1;
+ if (cgetcap(bp, (char *)/*XXX*/shrt, ':'))
+ return 1;
+ return 0;
+}
+
+/*
+ * Also should be a part of a better cget* library.
+ * Given a capdb entry, attempt to figure out what its canonical name
+ * is, and return a malloced copy of it. The canonical name is
+ * considered to be the first one listed.
+ */
+static char *
+capdb_canonical_name(const char *bp)
+{
+ char *retval;
+ const char *nameend;
+
+ nameend = strpbrk(bp, "|:");
+ if (nameend == 0)
+ nameend = bp + 1;
+ if ((retval = malloc(nameend - bp + 1)) != 0) {
+ retval[0] = '\0';
+ strncat(retval, bp, nameend - bp);
+ }
+ return retval;
+}
+
+
diff --git a/usr.sbin/lpr/common_source/request.c b/usr.sbin/lpr/common_source/request.c
new file mode 100644
index 0000000..b650e5c
--- /dev/null
+++ b/usr.sbin/lpr/common_source/request.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright 1997 Massachusetts Institute of Technology
+ *
+ * Permission to use, copy, modify, and distribute this software and
+ * its documentation for any purpose and without fee is hereby
+ * granted, provided that both the above copyright notice and this
+ * permission notice appear in all copies, that both the above
+ * copyright notice and this permission notice appear in all
+ * supporting documentation, and that the name of M.I.T. not be used
+ * in advertising or publicity pertaining to distribution of the
+ * software without specific, written prior permission. M.I.T. makes
+ * no representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS
+ * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
+ * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+static const char copyright[] =
+ "Copyright (C) 1997, Massachusetts Institute of Technology\r\n";
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <sys/param.h> /* needed for lp.h but not used here */
+#include <dirent.h> /* ditto */
+#include <stdio.h> /* ditto */
+#include "lp.h"
+#include "lp.local.h"
+
+void
+init_request(struct request *rp)
+{
+ static struct request zero;
+
+ *rp = zero;
+ TAILQ_INIT(&rp->users);
+ TAILQ_INIT(&rp->jobids);
+}
+
+void
+free_request(struct request *rp)
+{
+ struct req_user *ru;
+ struct req_jobid *rj;
+
+ if (rp->logname)
+ free(rp->logname);
+ if (rp->authname)
+ free(rp->authname);
+ if (rp->prettyname)
+ free(rp->prettyname);
+ if (rp->authinfo)
+ free(rp->authinfo);
+ while ((ru = TAILQ_FIRST(&rp->users)) != 0) {
+ TAILQ_REMOVE(&rp->users, ru, ru_link);
+ free(ru);
+ }
+ while ((rj = TAILQ_FIRST(&rp->jobids)) != 0) {
+ TAILQ_REMOVE(&rp->jobids, rj, rj_link);
+ free(rj);
+ }
+ init_request(rp);
+}
diff --git a/usr.sbin/lpr/common_source/rmjob.c b/usr.sbin/lpr/common_source/rmjob.c
new file mode 100644
index 0000000..c89a6f0
--- /dev/null
+++ b/usr.sbin/lpr/common_source/rmjob.c
@@ -0,0 +1,392 @@
+/*
+ * 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 char sccsid[] = "@(#)rmjob.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+#endif
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/uio.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#define psignal foil_gcc_psignal
+#define sys_siglist foil_gcc_siglist
+#include <unistd.h>
+#undef psignal
+#undef sys_siglist
+
+#include "lp.h"
+#include "lp.local.h"
+#include "pathnames.h"
+
+/*
+ * rmjob - remove the specified jobs from the queue.
+ */
+
+/*
+ * Stuff for handling lprm specifications
+ */
+static char root[] = "root";
+static int all = 0; /* eliminate all files (root only) */
+static int cur_daemon; /* daemon's pid */
+static char current[7+MAXHOSTNAMELEN]; /* active control file name */
+
+static void alarmhandler(int _signo);
+static void do_unlink(char *_file);
+static int isowner(char *_owner, char *_file, const char *_cfhost);
+
+void
+rmjob(const char *printer)
+{
+ register int i, nitems;
+ int assassinated = 0;
+ struct dirent **files;
+ char *cp;
+ struct printer myprinter, *pp = &myprinter;
+
+ init_printer(pp);
+ if ((i = getprintcap(printer, pp)) < 0)
+ fatal(pp, "getprintcap: %s", pcaperr(i));
+ if ((cp = checkremote(pp))) {
+ printf("Warning: %s\n", cp);
+ free(cp);
+ }
+
+ /*
+ * If the format was `lprm -' and the user isn't the super-user,
+ * then fake things to look like he said `lprm user'.
+ */
+ if (users < 0) {
+ if (getuid() == 0)
+ all = 1; /* all files in local queue */
+ else {
+ user[0] = person;
+ users = 1;
+ }
+ }
+ if (!strcmp(person, "-all")) {
+ if (from_host == local_host)
+ fatal(pp, "The login name \"-all\" is reserved");
+ all = 1; /* all those from 'from_host' */
+ person = root;
+ }
+
+ PRIV_START
+ if (chdir(pp->spool_dir) < 0)
+ fatal(pp, "cannot chdir to spool directory");
+ if ((nitems = scandir(".", &files, iscf, NULL)) < 0)
+ fatal(pp, "cannot access spool directory");
+ PRIV_END
+
+ if (nitems) {
+ /*
+ * Check for an active printer daemon (in which case we
+ * kill it if it is reading our file) then remove stuff
+ * (after which we have to restart the daemon).
+ */
+ if (lockchk(pp, pp->lock_file) && chk(current)) {
+ PRIV_START
+ assassinated = kill(cur_daemon, SIGINT) == 0;
+ PRIV_END
+ if (!assassinated)
+ fatal(pp, "cannot kill printer daemon");
+ }
+ /*
+ * process the files
+ */
+ for (i = 0; i < nitems; i++)
+ process(pp, files[i]->d_name);
+ }
+ rmremote(pp);
+ /*
+ * Restart the printer daemon if it was killed
+ */
+ if (assassinated && !startdaemon(pp))
+ fatal(pp, "cannot restart printer daemon\n");
+ exit(0);
+}
+
+/*
+ * Process a lock file: collect the pid of the active
+ * daemon and the file name of the active spool entry.
+ * Return boolean indicating existence of a lock file.
+ */
+int
+lockchk(struct printer *pp, char *slockf)
+{
+ register FILE *fp;
+ register int i, n;
+
+ PRIV_START
+ if ((fp = fopen(slockf, "r")) == NULL) {
+ if (errno == EACCES)
+ fatal(pp, "%s: %s", slockf, strerror(errno));
+ else
+ return(0);
+ }
+ PRIV_END
+ if (!getline(fp)) {
+ (void) fclose(fp);
+ return(0); /* no daemon present */
+ }
+ cur_daemon = atoi(line);
+ if (kill(cur_daemon, 0) < 0 && errno != EPERM) {
+ (void) fclose(fp);
+ return(0); /* no daemon present */
+ }
+ for (i = 1; (n = fread(current, sizeof(char), sizeof(current), fp)) <= 0; i++) {
+ if (i > 5) {
+ n = 1;
+ break;
+ }
+ sleep(i);
+ }
+ current[n-1] = '\0';
+ (void) fclose(fp);
+ return(1);
+}
+
+/*
+ * Process a control file.
+ */
+void
+process(const struct printer *pp, char *file)
+{
+ FILE *cfp;
+
+ if (!chk(file))
+ return;
+ PRIV_START
+ if ((cfp = fopen(file, "r")) == NULL)
+ fatal(pp, "cannot open %s", file);
+ PRIV_END
+ while (getline(cfp)) {
+ switch (line[0]) {
+ case 'U': /* unlink associated files */
+ if (strchr(line+1, '/') || strncmp(line+1, "df", 2))
+ break;
+ do_unlink(line+1);
+ }
+ }
+ (void) fclose(cfp);
+ do_unlink(file);
+}
+
+static void
+do_unlink(char *file)
+{
+ int ret;
+
+ if (from_host != local_host)
+ printf("%s: ", local_host);
+ PRIV_START
+ ret = unlink(file);
+ PRIV_END
+ printf(ret ? "cannot dequeue %s\n" : "%s dequeued\n", file);
+}
+
+/*
+ * Do the dirty work in checking
+ */
+int
+chk(char *file)
+{
+ int *r, jnum;
+ char **u;
+ const char *cfhost;
+ FILE *cfp;
+
+ /*
+ * Check for valid cf file name (mostly checking current).
+ */
+ if (strlen(file) < 7 || file[0] != 'c' || file[1] != 'f')
+ return(0);
+
+ jnum = calc_jobnum(file, &cfhost);
+ if (all && (from_host == local_host || !strcmp(from_host, cfhost)))
+ return(1);
+
+ /*
+ * get the owner's name from the control file.
+ */
+ PRIV_START
+ if ((cfp = fopen(file, "r")) == NULL)
+ return(0);
+ PRIV_END
+ while (getline(cfp)) {
+ if (line[0] == 'P')
+ break;
+ }
+ (void) fclose(cfp);
+ if (line[0] != 'P')
+ return(0);
+
+ if (users == 0 && requests == 0)
+ return(!strcmp(file, current) && isowner(line+1, file, cfhost));
+ /*
+ * Check the request list
+ */
+ for (r = requ; r < &requ[requests]; r++)
+ if (*r == jnum && isowner(line+1, file, cfhost))
+ return(1);
+ /*
+ * Check to see if it's in the user list
+ */
+ for (u = user; u < &user[users]; u++)
+ if (!strcmp(*u, line+1) && isowner(line+1, file, cfhost))
+ return(1);
+ return(0);
+}
+
+/*
+ * If root is removing a file on the local machine, allow it.
+ * If root is removing a file from a remote machine, only allow
+ * files sent from the remote machine to be removed.
+ * Normal users can only remove the file from where it was sent.
+ */
+static int
+isowner(char *owner, char *file, const char *cfhost)
+{
+ if (!strcmp(person, root) && (from_host == local_host ||
+ !strcmp(from_host, cfhost)))
+ return (1);
+ if (!strcmp(person, owner) && !strcmp(from_host, cfhost))
+ return (1);
+ if (from_host != local_host)
+ printf("%s: ", local_host);
+ printf("%s: Permission denied\n", file);
+ return(0);
+}
+
+/*
+ * Check to see if we are sending files to a remote machine. If we are,
+ * then try removing files on the remote machine.
+ */
+void
+rmremote(const struct printer *pp)
+{
+ int i, elem, firstreq, niov, rem, totlen;
+ char buf[BUFSIZ];
+ void (*savealrm)(int);
+ struct iovec *iov;
+
+ if (!pp->remote)
+ return; /* not sending to a remote machine */
+
+ /*
+ * Flush stdout so the user can see what has been deleted
+ * while we wait (possibly) for the connection.
+ */
+ fflush(stdout);
+
+ /*
+ * Counting:
+ * 4 == "\5" + remote_queue + " " + person
+ * 2 * users == " " + user[i] for each user
+ * requests == asprintf results for each request
+ * 1 == "\n"
+ * Although laborious, doing it this way makes it possible for
+ * us to process requests of indeterminate length without
+ * applying an arbitrary limit. Arbitrary Limits Are Bad (tm).
+ */
+ if (users > 0)
+ niov = 4 + 2 * users + requests + 1;
+ else
+ niov = 4 + requests + 1;
+ iov = malloc(niov * sizeof *iov);
+ if (iov == 0)
+ fatal(pp, "out of memory in rmremote()");
+ iov[0].iov_base = "\5";
+ iov[1].iov_base = pp->remote_queue;
+ iov[2].iov_base = " ";
+ iov[3].iov_base = all ? "-all" : person;
+ elem = 4;
+ for (i = 0; i < users; i++) {
+ iov[elem].iov_base = " ";
+ iov[elem + 1].iov_base = user[i];
+ elem += 2;
+ }
+ firstreq = elem;
+ for (i = 0; i < requests; i++) {
+ asprintf((char **)&iov[elem].iov_base, " %d", requ[i]);
+ if (iov[elem].iov_base == 0)
+ fatal(pp, "out of memory in rmremote()");
+ elem++;
+ }
+ iov[elem++].iov_base = "\n";
+ for (totlen = i = 0; i < niov; i++)
+ totlen += (iov[i].iov_len = strlen(iov[i].iov_base));
+
+ savealrm = signal(SIGALRM, alarmhandler);
+ alarm(pp->conn_timeout);
+ rem = getport(pp, pp->remote_host, 0);
+ (void)signal(SIGALRM, savealrm);
+ if (rem < 0) {
+ if (from_host != local_host)
+ printf("%s: ", local_host);
+ printf("connection to %s is down\n", pp->remote_host);
+ } else {
+ if (writev(rem, iov, niov) != totlen)
+ fatal(pp, "Lost connection");
+ while ((i = read(rem, buf, sizeof(buf))) > 0)
+ (void) fwrite(buf, 1, i, stdout);
+ (void) close(rem);
+ }
+ for (i = 0; i < requests; i++)
+ free(iov[firstreq + i].iov_base);
+ free(iov);
+}
+
+/*
+ * Return 1 if the filename begins with 'cf'
+ */
+int
+iscf(const struct dirent *d)
+{
+ return(d->d_name[0] == 'c' && d->d_name[1] == 'f');
+}
+
+void
+alarmhandler(int signo __unused)
+{
+ /* the signal is ignored */
+ /* (the '__unused' is just to avoid a compile-time warning) */
+}
diff --git a/usr.sbin/lpr/common_source/startdaemon.c b/usr.sbin/lpr/common_source/startdaemon.c
new file mode 100644
index 0000000..8e4f60a
--- /dev/null
+++ b/usr.sbin/lpr/common_source/startdaemon.c
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 1983, 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 char sccsid[] = "@(#)startdaemon.c 8.2 (Berkeley) 4/17/94";
+#endif /* not lint */
+#endif
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+
+#include <dirent.h>
+#include <err.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include "lp.h"
+#include "pathnames.h"
+
+/*
+ * Tell the printer daemon that there are new files in the spool directory.
+ */
+
+int
+startdaemon(const struct printer *pp)
+{
+ struct sockaddr_un un;
+ register int s, n;
+ int connectres;
+ char c;
+
+ s = socket(PF_LOCAL, SOCK_STREAM, 0);
+ if (s < 0) {
+ warn("socket");
+ return(0);
+ }
+ memset(&un, 0, sizeof(un));
+ un.sun_family = AF_LOCAL;
+ strcpy(un.sun_path, _PATH_SOCKETNAME);
+#ifndef SUN_LEN
+#define SUN_LEN(unp) (strlen((unp)->sun_path) + 2)
+#endif
+ PRIV_START
+ connectres = connect(s, (struct sockaddr *)&un, SUN_LEN(&un));
+ PRIV_END
+ if (connectres < 0) {
+ warn("Unable to connect to %s", _PATH_SOCKETNAME);
+ warnx("Check to see if the master 'lpd' process is running.");
+ (void) close(s);
+ return(0);
+ }
+
+ /*
+ * Avoid overruns without putting artificial limitations on
+ * the length.
+ */
+ if (writel(s, "\1", pp->printer, "\n", (char *)0) <= 0) {
+ warn("write");
+ (void) close(s);
+ return(0);
+ }
+ if (read(s, &c, 1) == 1) {
+ if (c == '\0') { /* everything is OK */
+ (void) close(s);
+ return(1);
+ }
+ putchar(c);
+ }
+ while ((n = read(s, &c, 1)) > 0)
+ putchar(c);
+ (void) close(s);
+ return(0);
+}
diff --git a/usr.sbin/lpr/filters.ru/Makefile b/usr.sbin/lpr/filters.ru/Makefile
new file mode 100644
index 0000000..ee15541
--- /dev/null
+++ b/usr.sbin/lpr/filters.ru/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+SUBDIR= koi2alt koi2855
+
+FILES= bjc-240.sh.sample
+
+.include "Makefile.inc"
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/filters.ru/Makefile.depend b/usr.sbin/lpr/filters.ru/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/lpr/filters.ru/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/filters.ru/Makefile.inc b/usr.sbin/lpr/filters.ru/Makefile.inc
new file mode 100644
index 0000000..36a6f46
--- /dev/null
+++ b/usr.sbin/lpr/filters.ru/Makefile.inc
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+BINDIR= /usr/libexec/lpr/ru
+
+WARNS?= 3
diff --git a/usr.sbin/lpr/filters.ru/bjc-240.sh.sample b/usr.sbin/lpr/filters.ru/bjc-240.sh.sample
new file mode 100644
index 0000000..1bc907a
--- /dev/null
+++ b/usr.sbin/lpr/filters.ru/bjc-240.sh.sample
@@ -0,0 +1,64 @@
+#!/bin/sh
+#
+# Canon BJC-240 Setup script
+#
+# Settings are:
+# Epson LQ emulation, A4, Code Page 866, 66 lines, Roman font, Smoothing,
+# HQ mode, no CR translation, power off in 10min, auto power on
+#
+
+printf "\033[K\00200\037" | tr 0 "\0"
+
+cat << EOF1
+BJLSTART
+ControlMode=BJ
+Font=Roman
+PageLength=12
+CodePage=866
+AutoLF=Off
+TextScaleMode=On
+AutoCR=Off
+CharacterSet=Set2
+AGM=Off
+BJLEND
+EOF1
+
+printf "\033[K\00200\037" | tr 0 "\0"
+
+cat << EOF2
+BJLSTART
+ControlMode=LQ
+Font=Roman
+PageLength=12
+CodePage=866
+AutoLF=Off
+TextScaleMode=On
+CharacterSet=Graphics
+International=USA
+BJLEND
+EOF2
+
+printf "\033[K\00200\037" | tr 0 "\0"
+
+cat << EOF3
+BJLSTART
+@SetControlMode=LQ
+BJLEND
+EOF3
+
+printf "\033[K\00200\037" | tr 0 "\0"
+
+cat << EOF4
+BJLSTART
+ControlMode=Common
+PrintMode=HQ
+Reduction=Off
+Smoothing=On
+PaperSelect=A4
+I/D-Buffer=Input
+AutoPowerOff=10
+AutoPowerOn=Enable
+BJLEND
+EOF4
+
+exec /usr/libexec/lpr/ru/koi2alt $*
diff --git a/usr.sbin/lpr/filters.ru/koi2855/Makefile b/usr.sbin/lpr/filters.ru/koi2855/Makefile
new file mode 100644
index 0000000..71ffc09
--- /dev/null
+++ b/usr.sbin/lpr/filters.ru/koi2855/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= koi2855
+MAN=
+
+CFLAGS+= -I${.CURDIR}/../../common_source
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/filters.ru/koi2855/Makefile.depend b/usr.sbin/lpr/filters.ru/koi2855/Makefile.depend
new file mode 100644
index 0000000..9cb890b
--- /dev/null
+++ b/usr.sbin/lpr/filters.ru/koi2855/Makefile.depend
@@ -0,0 +1,17 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/filters.ru/koi2855/koi2855.c b/usr.sbin/lpr/filters.ru/koi2855/koi2855.c
new file mode 100644
index 0000000..8ecc3ef
--- /dev/null
+++ b/usr.sbin/lpr/filters.ru/koi2855/koi2855.c
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 1999 by Andrey A. Chernov, Moscow, Russia.
+ * (C) 18 Sep 1999, Alex G. Bulushev (bag@demos.su)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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.
+ */
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+/*
+ * KOI8-R -> CP855 conversion filter (Russian character sets)
+ */
+
+#include <sys/types.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int length = 66;
+int lines;
+
+unsigned char koi2855 [] = {
+0xc4, 0xb3, 0xda, 0xbf, 0xc0, 0xd9, 0xc3, 0xb4,
+0xc2, 0xc1, 0xc5, 0xdf, 0xdc, 0xdb, 0xdb, 0xdb,
+0xb0, 0xb1, 0xb2, 0xda, 0xfe, 0x2e, 0xad, 0x3d,
+0xae, 0xaf, 0xff, 0xd9, 0xcf, 0x32, 0x2e, 0x25,
+0xcd, 0xba, 0xc9, 0x84, 0xc9, 0xc9, 0xbb, 0xbb,
+0xbb, 0xc8, 0xc8, 0xc8, 0xbc, 0xbc, 0xbc, 0xcc,
+0xcc, 0xcc, 0xb9, 0x85, 0xb9, 0xb9, 0xcb, 0xcb,
+0xcb, 0xca, 0xca, 0xca, 0xce, 0xce, 0xce, 0x43,
+0x9c, 0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac,
+0xb5, 0xb7, 0xbd, 0xc6, 0xd0, 0xd2, 0xd4, 0xd6,
+0xd8, 0xde, 0xe1, 0xe3, 0xe5, 0xe7, 0xe9, 0xeb,
+0xed, 0xf1, 0xf3, 0xf5, 0xf7, 0xf9, 0xfb, 0x9e,
+0x9d, 0xa1, 0xa3, 0xa5, 0xa7, 0xa9, 0xab, 0xad,
+0xb6, 0xb8, 0xbe, 0xc7, 0xd1, 0xd3, 0xd5, 0xd7,
+0xdd, 0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec,
+0xee, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0x9f
+};
+
+int main(int argc, char *argv[])
+{
+ int c, i;
+ char *cp;
+
+ while (--argc) {
+ if (*(cp = *++argv) == '-') {
+ switch (*++cp) {
+ case 'l':
+ if ((i = atoi(++cp)) > 0)
+ length = i;
+ break;
+ }
+ }
+ }
+
+ while ((c = getchar()) != EOF) {
+ if (c == '\031') {
+ if ((c = getchar()) == '\1') {
+ lines = 0;
+ fflush(stdout);
+ kill(getpid(), SIGSTOP);
+ continue;
+ } else {
+ ungetc(c, stdin);
+ c = '\031';
+ }
+ } else if (c & 0x80) {
+ putchar(koi2855[c & 0x7F]);
+ continue;
+ } else if (c == '\n')
+ lines++;
+ else if (c == '\f')
+ lines = length;
+ putchar(c);
+ if (lines >= length) {
+ lines = 0;
+ fflush(stdout);
+ }
+ }
+ return 0;
+}
diff --git a/usr.sbin/lpr/filters.ru/koi2alt/Makefile b/usr.sbin/lpr/filters.ru/koi2alt/Makefile
new file mode 100644
index 0000000..98f7ffe
--- /dev/null
+++ b/usr.sbin/lpr/filters.ru/koi2alt/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= koi2alt
+MAN=
+
+CFLAGS+= -I${.CURDIR}/../../common_source
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/filters.ru/koi2alt/Makefile.depend b/usr.sbin/lpr/filters.ru/koi2alt/Makefile.depend
new file mode 100644
index 0000000..9cb890b
--- /dev/null
+++ b/usr.sbin/lpr/filters.ru/koi2alt/Makefile.depend
@@ -0,0 +1,17 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/filters.ru/koi2alt/koi2alt.c b/usr.sbin/lpr/filters.ru/koi2alt/koi2alt.c
new file mode 100644
index 0000000..a7a5b5d
--- /dev/null
+++ b/usr.sbin/lpr/filters.ru/koi2alt/koi2alt.c
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 1993-98 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.
+ */
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+/*
+ * KOI8-R -> CP866 conversion filter (Russian character sets)
+ */
+
+#include <sys/types.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int length = 66;
+int lines;
+
+char *koi2alt[] = {
+/* 0 1 2 3 4 5 6 7 */
+/* 8 9 A B C D E F */
+/* 8 */ "\xc4","\xb3","\xda","\xbf","\xc0","\xd9","\xc3","\xb4",
+ "\xc2","\xc1","\xc5","\xdf","\xdc","\xdb","\xdd","\xde",
+/* 9 */ "\xb0","\xb1","\xb2","\xb3","\xfe","\xf9","\xfb","-\b~",
+ "<\b_",">\b_","\xff","\xb3","\xf8","2\b-","\xfa",":\b-",
+/* A */ "\xcd","\xba","\xd5","\xf1","\xd6","\xc9","\xb8","\xb7",
+ "\xbb","\xd4","\xd3","\xc8","\xbe","\xbd","\xbc","\xc6",
+/* B */ "\xc7","\xcc","\xb5","\xf0","\xb6","\xb9","\xd1","\xd2",
+ "\xcb","\xcf","\xd0","\xca","\xd8","\xd7","\xce","c\b_",
+/* C */ "\xee","\xa0","\xa1","\xe6","\xa4","\xa5","\xe4","\xa3",
+ "\xe5","\xa8","\xa9","\xaa","\xab","\xac","\xad","\xae",
+/* D */ "\xaf","\xef","\xe0","\xe1","\xe2","\xe3","\xa6","\xa2",
+ "\xec","\xeb","\xa7","\xe8","\xed","\xe9","\xe7","\xea",
+/* E */ "\x9e","\x80","\x81","\x96","\x84","\x85","\x94","\x83",
+ "\x95","\x88","\x89","\x8a","\x8b","\x8c","\x8d","\x8e",
+/* F */ "\x8f","\x9f","\x90","\x91","\x92","\x93","\x86","\x82",
+ "\x9c","\x9b","\x87","\x98","\x9d","\x99","\x97","\x9a"
+};
+
+int main(int argc, char *argv[])
+{
+ int c, i;
+ char *cp;
+
+ while (--argc) {
+ if (*(cp = *++argv) == '-') {
+ switch (*++cp) {
+ case 'l':
+ if ((i = atoi(++cp)) > 0)
+ length = i;
+ break;
+ }
+ }
+ }
+
+ while ((c = getchar()) != EOF) {
+ if (c == '\031') {
+ if ((c = getchar()) == '\1') {
+ lines = 0;
+ fflush(stdout);
+ kill(getpid(), SIGSTOP);
+ continue;
+ } else {
+ ungetc(c, stdin);
+ c = '\031';
+ }
+ } else if (c & 0x80) {
+ fputs(koi2alt[c & 0x7F], stdout);
+ continue;
+ } else if (c == '\n')
+ lines++;
+ else if (c == '\f')
+ lines = length;
+ putchar(c);
+ if (lines >= length) {
+ lines = 0;
+ fflush(stdout);
+ }
+ }
+ return 0;
+}
diff --git a/usr.sbin/lpr/filters/Makefile b/usr.sbin/lpr/filters/Makefile
new file mode 100644
index 0000000..7976752
--- /dev/null
+++ b/usr.sbin/lpr/filters/Makefile
@@ -0,0 +1,11 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+BINDIR= ${LIBEXECDIR}/lpr
+
+PROG= lpf
+MAN=
+
+CFLAGS+= -I${.CURDIR}/../common_source
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/filters/Makefile.depend b/usr.sbin/lpr/filters/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/lpr/filters/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/filters/lpf.c b/usr.sbin/lpr/filters/lpf.c
new file mode 100644
index 0000000..f8ffe52
--- /dev/null
+++ b/usr.sbin/lpr/filters/lpf.c
@@ -0,0 +1,218 @@
+/*
+ * 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 */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)lpf.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+/*
+ * filter which reads the output of nroff and converts lines
+ * with ^H's to overwritten lines. Thus this works like 'ul'
+ * but is much better: it can handle more than 2 overwrites
+ * and it is written with some style.
+ * modified by kls to use register references instead of arrays
+ * to try to gain a little speed.
+ */
+
+#include <signal.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#define MAXWIDTH 132
+#define MAXREP 10
+
+static char buf[MAXREP][MAXWIDTH];
+static int maxcol[MAXREP] = {-1};
+static int lineno;
+static int width = 132; /* default line length */
+static int length = 66; /* page length */
+static int indent; /* indentation length */
+static int npages = 1;
+static int literal; /* print control characters */
+static char *name; /* user's login name */
+static char *host; /* user's machine name */
+static char *acctfile; /* accounting information file */
+
+int
+main(int argc, char *argv[])
+{
+ register FILE *p = stdin, *o = stdout;
+ register int i, col;
+ register char *cp;
+ int done, linedone, maxrep;
+ char *limit;
+ int ch;
+
+ while (--argc) {
+ if (*(cp = *++argv) == '-') {
+ switch (cp[1]) {
+ case 'n':
+ argc--;
+ name = *++argv;
+ break;
+
+ case 'h':
+ argc--;
+ host = *++argv;
+ break;
+
+ case 'w':
+ if ((i = atoi(&cp[2])) > 0 && i <= MAXWIDTH)
+ width = i;
+ break;
+
+ case 'l':
+ length = atoi(&cp[2]);
+ break;
+
+ case 'i':
+ indent = atoi(&cp[2]);
+ break;
+
+ case 'c': /* Print control chars */
+ literal++;
+ break;
+ }
+ } else
+ acctfile = cp;
+ }
+
+ memset(buf, ' ', sizeof(buf));
+ done = 0;
+
+ while (!done) {
+ col = indent;
+ maxrep = -1;
+ linedone = 0;
+ while (!linedone) {
+ switch (ch = getc(p)) {
+ case EOF:
+ linedone = done = 1;
+ ch = '\n';
+ break;
+
+ case '\f':
+ lineno = length;
+ case '\n':
+ if (maxrep < 0)
+ maxrep = 0;
+ linedone = 1;
+ break;
+
+ case '\b':
+ if (--col < indent)
+ col = indent;
+ break;
+
+ case '\r':
+ col = indent;
+ break;
+
+ case '\t':
+ col = ((col - indent) | 07) + indent + 1;
+ break;
+
+ case '\031':
+ /*
+ * lpd needs to use a different filter to
+ * print data so stop what we are doing and
+ * wait for lpd to restart us.
+ */
+ if ((ch = getchar()) == '\1') {
+ fflush(stdout);
+ kill(getpid(), SIGSTOP);
+ break;
+ } else {
+ ungetc(ch, stdin);
+ ch = '\031';
+ }
+
+ default:
+ if (col >= width || (!literal && ch < ' ')) {
+ col++;
+ break;
+ }
+ cp = &buf[0][col];
+ for (i = 0; i < MAXREP; i++) {
+ if (i > maxrep)
+ maxrep = i;
+ if (*cp == ' ') {
+ *cp = ch;
+ if (col > maxcol[i])
+ maxcol[i] = col;
+ break;
+ }
+ cp += MAXWIDTH;
+ }
+ col++;
+ break;
+ }
+ }
+
+ /* print out lines */
+ for (i = 0; i <= maxrep; i++) {
+ for (cp = buf[i], limit = cp+maxcol[i]; cp <= limit;) {
+ putc(*cp, o);
+ *cp++ = ' ';
+ }
+ if (i < maxrep)
+ putc('\r', o);
+ else
+ putc(ch, o);
+ if (++lineno >= length) {
+ fflush(o);
+ npages++;
+ lineno = 0;
+ }
+ maxcol[i] = -1;
+ }
+ }
+ if (lineno) { /* be sure to end on a page boundary */
+ putchar('\f');
+ npages++;
+ }
+ if (name && acctfile && access(acctfile, 02) >= 0 &&
+ freopen(acctfile, "a", stdout) != NULL) {
+ printf("%7.2f\t%s:%s\n", (float)npages, host, name);
+ }
+ exit(0);
+}
diff --git a/usr.sbin/lpr/lp/Makefile b/usr.sbin/lpr/lp/Makefile
new file mode 100644
index 0000000..2fcec43
--- /dev/null
+++ b/usr.sbin/lpr/lp/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+BINDIR= /usr/bin
+
+SCRIPTS=lp.sh
+MAN= lp.1
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/lp/Makefile.depend b/usr.sbin/lpr/lp/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/lpr/lp/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/lp/lp.1 b/usr.sbin/lpr/lp/lp.1
new file mode 100644
index 0000000..903bb03
--- /dev/null
+++ b/usr.sbin/lpr/lp/lp.1
@@ -0,0 +1,125 @@
+.\"
+.\" Copyright (c) 1995 Joerg Wunsch
+.\"
+.\" All rights reserved.
+.\"
+.\" This program is free software.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 Joerg Wunsch
+.\" 4. The name of the developer may not be used to endorse or promote
+.\" products derived from this software without specific prior written
+.\" permission.
+.\"
+.\" 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd January 22, 1995
+.Dt LP 1
+.Os
+.Sh NAME
+.Nm lp
+.Nd front-end to the print spooler
+.Sh SYNOPSIS
+.Nm
+.Op Fl cs
+.Op Fl o Ar option
+.Op Fl d Ar printer
+.Op Fl n Ar num
+.Op Ar name ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is a front-end to the print spooler as required by the
+.St -p1003.2
+specification.
+It effectively invokes
+.Xr lpr 1
+with the proper set of arguments.
+.Pp
+It generally prints the named files on the destination printer.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl c
+Make the
+.Nm
+command exit only after further access to any of the input files is no
+longer required.
+The application can then safely delete or modify the
+files without affecting the output operation.
+.It Fl d Ar dest
+Specify a particular printer.
+If no
+.Fl d
+is provided on the command line, the contents of the environment
+variables
+.Ev LPDEST
+or
+.Ev PRINTER
+(with this precedence)
+are taken as the destination printer.
+.It Fl m
+Send mail upon completion.
+.It Fl n Ar num
+Specify that
+.Ar num
+copies of each of the named files shall be printed.
+.It Fl o Ar option
+Printer specific options.
+Not supported, provided only as a compatibility
+option for SVR.
+.It Fl s
+Silent operation.
+Not supported,
+provided only as a compatibility option for
+.St -susv2 .
+.It Fl t Ar title
+Set the job title to
+.Ar title .
+.El
+.Sh ENVIRONMENT
+As described above, the variables
+.Ev LPDEST
+and
+.Ev PRINTER
+are examined to select the destination printer.
+.Sh SEE ALSO
+.Xr lpr 1
+.Sh STANDARDS
+The
+.Nm
+command is expected to comply with the
+.St -p1003.2
+specification.
+.Sh AUTHORS
+This implementation of the
+.Nm
+command has been written by
+.An J\(:org Wunsch .
+.Sh BUGS
+The
+.St -p1003.2
+specification does not provide any means to print non-text files.
+It
+rather requires the files to be printed to be text files limited to
+reasonable line lengths and printable characters.
diff --git a/usr.sbin/lpr/lp/lp.sh b/usr.sbin/lpr/lp/lp.sh
new file mode 100644
index 0000000..dbec053
--- /dev/null
+++ b/usr.sbin/lpr/lp/lp.sh
@@ -0,0 +1,81 @@
+#!/bin/sh
+#
+#
+# Copyright (c) 1995 Joerg Wunsch
+#
+# All rights reserved.
+#
+# This program is free software.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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 Joerg Wunsch
+# 4. The name of the developer may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# 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.
+#
+#
+# Posix 1003.2 compliant print spooler interface.
+#
+# $FreeBSD$
+#
+
+ncopies=""
+symlink="-s"
+mailafter=""
+title=""
+
+# Posix says LPDEST gets precedence over PRINTER
+dest=${LPDEST:-${PRINTER:-lp}}
+
+#
+# XXX We include the -o flag as a dummy. Posix 1003.2 does not require
+# it, but the rationale mentions it as a possible future extension.
+# XXX We include the -s flag as a dummy. SUSv2 requires it,
+# although we do not yet emit the affected messages.
+#
+while getopts "cd:mn:o:st:" option
+do
+ case $option in
+
+ c) # copy files before printing
+ symlink="";;
+ d) # destination
+ dest="${OPTARG}";;
+ m) # mail after job
+ mailafter="-m";;
+ n) # number of copies
+ ncopies="-#${OPTARG}";;
+ o) # (printer option)
+ : ;;
+ s) # (silent option)
+ : ;;
+ t) # title for banner page
+ title="${OPTARG}";;
+ *) # (error msg printed by getopts)
+ exit 2;;
+ esac
+done
+
+shift $(($OPTIND - 1))
+
+exec /usr/bin/lpr "-P${dest}" ${symlink} ${ncopies} ${mailafter} ${title:+-J"${title}"} "$@"
diff --git a/usr.sbin/lpr/lpc/Makefile b/usr.sbin/lpr/lpc/Makefile
new file mode 100644
index 0000000..43f1f7a
--- /dev/null
+++ b/usr.sbin/lpr/lpc/Makefile
@@ -0,0 +1,18 @@
+# From: @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../common_source
+
+PROG= lpc
+MAN= lpc.8
+SRCS= lpc.c cmds.c cmdtab.c movejobs.c
+BINGRP= daemon
+BINMODE= 2555
+
+CFLAGS+= -I${.CURDIR}/../common_source
+
+WARNS?= 0
+
+LIBADD= lpr edit
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/lpc/Makefile.depend b/usr.sbin/lpr/lpc/Makefile.depend
new file mode 100644
index 0000000..69f631f
--- /dev/null
+++ b/usr.sbin/lpr/lpc/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libedit \
+ lib/ncurses/ncursesw \
+ usr.sbin/lpr/common_source \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/lpc/cmds.c b/usr.sbin/lpr/lpc/cmds.c
new file mode 100644
index 0000000..f960f7c
--- /dev/null
+++ b/usr.sbin/lpr/lpc/cmds.c
@@ -0,0 +1,1330 @@
+/*
+ * 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 */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)cmds.c 8.2 (Berkeley) 4/28/95";
+#endif /* not lint */
+#endif
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+/*
+ * lpc -- line printer control program -- commands:
+ */
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+
+#include <signal.h>
+#include <fcntl.h>
+#include <err.h>
+#include <errno.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include "lp.h"
+#include "lp.local.h"
+#include "lpc.h"
+#include "extern.h"
+#include "pathnames.h"
+
+/*
+ * Return values from kill_qtask().
+ */
+#define KQT_LFERROR -2
+#define KQT_KILLFAIL -1
+#define KQT_NODAEMON 0
+#define KQT_KILLOK 1
+
+static char *args2line(int argc, char **argv);
+static int doarg(char *_job);
+static int doselect(const struct dirent *_d);
+static int kill_qtask(const char *lf);
+static int sortq(const struct dirent **a, const struct dirent **b);
+static int touch(struct jobqueue *_jq);
+static void unlinkf(char *_name);
+static void upstat(struct printer *_pp, const char *_msg, int _notify);
+static void wrapup_clean(int _laststatus);
+
+/*
+ * generic framework for commands which operate on all or a specified
+ * set of printers
+ */
+enum qsel_val { /* how a given ptr was selected */
+ QSEL_UNKNOWN = -1, /* ... not selected yet */
+ QSEL_BYNAME = 0, /* ... user specifed it by name */
+ QSEL_ALL = 1 /* ... user wants "all" printers */
+ /* (with more to come) */
+};
+
+static enum qsel_val generic_qselect; /* indicates how ptr was selected */
+static int generic_initerr; /* result of initrtn processing */
+static char *generic_cmdname;
+static char *generic_msg; /* if a -msg was specified */
+static char *generic_nullarg;
+static void (*generic_wrapup)(int _last_status); /* perform rtn wrap-up */
+
+void
+generic(void (*specificrtn)(struct printer *_pp), int cmdopts,
+ void (*initrtn)(int _argc, char *_argv[]), int argc, char *argv[])
+{
+ int cmdstatus, more, targc;
+ struct printer myprinter, *pp;
+ char **margv, **targv;
+
+ if (argc == 1) {
+ /*
+ * Usage needs a special case for 'down': The user must
+ * either include `-msg', or only the first parameter
+ * that they give will be processed as a printer name.
+ */
+ printf("usage: %s {all | printer ...}", argv[0]);
+ if (strcmp(argv[0], "down") == 0) {
+ printf(" -msg [<text> ...]\n");
+ printf(" or: down {all | printer} [<text> ...]");
+ } else if (cmdopts & LPC_MSGOPT)
+ printf(" [-msg <text> ...]");
+ printf("\n");
+ return;
+ }
+
+ /* The first argument is the command name. */
+ generic_cmdname = *argv++;
+ argc--;
+
+ /*
+ * The initialization routine for a command might set a generic
+ * "wrapup" routine, which should be called after processing all
+ * the printers in the command. This might print summary info.
+ *
+ * Note that the initialization routine may also parse (and
+ * nullify) some of the parameters given on the command, leaving
+ * only the parameters which have to do with printer names.
+ */
+ pp = &myprinter;
+ generic_wrapup = NULL;
+ generic_qselect = QSEL_UNKNOWN;
+ cmdstatus = 0;
+ /* this just needs to be a distinct value of type 'char *' */
+ if (generic_nullarg == NULL)
+ generic_nullarg = strdup("");
+
+ /*
+ * Some commands accept a -msg argument, which indicates that
+ * all remaining arguments should be combined into a string.
+ */
+ generic_msg = NULL;
+ if (cmdopts & LPC_MSGOPT) {
+ targc = argc;
+ targv = argv;
+ for (; targc > 0; targc--, targv++) {
+ if (strcmp(*targv, "-msg") == 0) {
+ argc -= targc;
+ generic_msg = args2line(targc - 1, targv + 1);
+ break;
+ }
+ }
+ if (argc < 1) {
+ printf("error: No printer name(s) specified before"
+ " '-msg'.\n");
+ printf("usage: %s {all | printer ...}",
+ generic_cmdname);
+ printf(" [-msg <text> ...]\n");
+ return;
+ }
+ }
+
+ /* call initialization routine, if there is one for this cmd */
+ if (initrtn != NULL) {
+ generic_initerr = 0;
+ (*initrtn)(argc, argv);
+ if (generic_initerr)
+ return;
+ /*
+ * The initrtn may have null'ed out some of the parameters.
+ * Compact the parameter list to remove those nulls, and
+ * correct the arg-count.
+ */
+ targc = argc;
+ targv = argv;
+ margv = argv;
+ argc = 0;
+ for (; targc > 0; targc--, targv++) {
+ if (*targv != generic_nullarg) {
+ if (targv != margv)
+ *margv = *targv;
+ margv++;
+ argc++;
+ }
+ }
+ }
+
+ if (argc == 1 && strcmp(*argv, "all") == 0) {
+ generic_qselect = QSEL_ALL;
+ more = firstprinter(pp, &cmdstatus);
+ if (cmdstatus)
+ goto looperr;
+ while (more) {
+ (*specificrtn)(pp);
+ do {
+ more = nextprinter(pp, &cmdstatus);
+looperr:
+ switch (cmdstatus) {
+ case PCAPERR_TCOPEN:
+ printf("warning: %s: unresolved "
+ "tc= reference(s) ",
+ pp->printer);
+ case PCAPERR_SUCCESS:
+ break;
+ default:
+ fatal(pp, "%s", pcaperr(cmdstatus));
+ }
+ } while (more && cmdstatus);
+ }
+ goto wrapup;
+ }
+
+ generic_qselect = QSEL_BYNAME; /* specifically-named ptrs */
+ for (; argc > 0; argc--, argv++) {
+ init_printer(pp);
+ cmdstatus = getprintcap(*argv, pp);
+ switch (cmdstatus) {
+ default:
+ fatal(pp, "%s", pcaperr(cmdstatus));
+ case PCAPERR_NOTFOUND:
+ printf("unknown printer %s\n", *argv);
+ continue;
+ case PCAPERR_TCOPEN:
+ printf("warning: %s: unresolved tc= reference(s)\n",
+ *argv);
+ break;
+ case PCAPERR_SUCCESS:
+ break;
+ }
+ (*specificrtn)(pp);
+ }
+
+wrapup:
+ if (generic_wrapup) {
+ (*generic_wrapup)(cmdstatus);
+ }
+ free_printer(pp);
+ if (generic_msg)
+ free(generic_msg);
+}
+
+/*
+ * Convert an argv-array of character strings into a single string.
+ */
+static char *
+args2line(int argc, char **argv)
+{
+ char *cp1, *cend;
+ const char *cp2;
+ char buf[1024];
+
+ if (argc <= 0)
+ return strdup("\n");
+
+ cp1 = buf;
+ cend = buf + sizeof(buf) - 1; /* save room for '\0' */
+ while (--argc >= 0) {
+ cp2 = *argv++;
+ while ((cp1 < cend) && (*cp1++ = *cp2++))
+ ;
+ cp1[-1] = ' ';
+ }
+ cp1[-1] = '\n';
+ *cp1 = '\0';
+ return strdup(buf);
+}
+
+/*
+ * Kill the current daemon, to stop printing of the active job.
+ */
+static int
+kill_qtask(const char *lf)
+{
+ FILE *fp;
+ pid_t pid;
+ int errsav, killres, lockres, res;
+
+ PRIV_START
+ fp = fopen(lf, "r");
+ errsav = errno;
+ PRIV_END
+ res = KQT_NODAEMON;
+ if (fp == NULL) {
+ /*
+ * If there is no lock file, then there is no daemon to
+ * kill. Any other error return means there is some
+ * kind of problem with the lock file.
+ */
+ if (errsav != ENOENT)
+ res = KQT_LFERROR;
+ goto killdone;
+ }
+
+ /* If the lock file is empty, then there is no daemon to kill */
+ if (getline(fp) == 0)
+ goto killdone;
+
+ /*
+ * If the file can be locked without blocking, then there
+ * no daemon to kill, or we should not try to kill it.
+ *
+ * XXX - not sure I understand the reasoning behind this...
+ */
+ lockres = flock(fileno(fp), LOCK_SH|LOCK_NB);
+ (void) fclose(fp);
+ if (lockres == 0)
+ goto killdone;
+
+ pid = atoi(line);
+ if (pid < 0) {
+ /*
+ * If we got a negative pid, then the contents of the
+ * lock file is not valid.
+ */
+ res = KQT_LFERROR;
+ goto killdone;
+ }
+
+ PRIV_END
+ killres = kill(pid, SIGTERM);
+ errsav = errno;
+ PRIV_END
+ if (killres == 0) {
+ res = KQT_KILLOK;
+ printf("\tdaemon (pid %d) killed\n", pid);
+ } else if (errno == ESRCH) {
+ res = KQT_NODAEMON;
+ } else {
+ res = KQT_KILLFAIL;
+ printf("\tWarning: daemon (pid %d) not killed:\n", pid);
+ printf("\t %s\n", strerror(errsav));
+ }
+
+killdone:
+ switch (res) {
+ case KQT_LFERROR:
+ printf("\tcannot open lock file: %s\n",
+ strerror(errsav));
+ break;
+ case KQT_NODAEMON:
+ printf("\tno daemon to abort\n");
+ break;
+ case KQT_KILLFAIL:
+ case KQT_KILLOK:
+ /* These two already printed messages to the user. */
+ break;
+ default:
+ printf("\t<internal error in kill_qtask>\n");
+ break;
+ }
+
+ return (res);
+}
+
+/*
+ * Write a message into the status file.
+ */
+static void
+upstat(struct printer *pp, const char *msg, int notifyuser)
+{
+ int fd;
+ char statfile[MAXPATHLEN];
+
+ status_file_name(pp, statfile, sizeof statfile);
+ umask(0);
+ PRIV_START
+ fd = open(statfile, O_WRONLY|O_CREAT|O_EXLOCK, STAT_FILE_MODE);
+ PRIV_END
+ if (fd < 0) {
+ printf("\tcannot create status file: %s\n", strerror(errno));
+ return;
+ }
+ (void) ftruncate(fd, 0);
+ if (msg == NULL)
+ (void) write(fd, "\n", 1);
+ else
+ (void) write(fd, msg, strlen(msg));
+ (void) close(fd);
+ if (notifyuser) {
+ if ((msg == (char *)NULL) || (strcmp(msg, "\n") == 0))
+ printf("\tstatus message is now set to nothing.\n");
+ else
+ printf("\tstatus message is now: %s", msg);
+ }
+}
+
+/*
+ * kill an existing daemon and disable printing.
+ */
+void
+abort_q(struct printer *pp)
+{
+ int killres, setres;
+ char lf[MAXPATHLEN];
+
+ lock_file_name(pp, lf, sizeof lf);
+ printf("%s:\n", pp->printer);
+
+ /*
+ * Turn on the owner execute bit of the lock file to disable printing.
+ */
+ setres = set_qstate(SQS_STOPP, lf);
+
+ /*
+ * If set_qstate found that there already was a lock file, then
+ * call a routine which will read that lock file and kill the
+ * lpd-process which is listed in that lock file. If the lock
+ * file did not exist, then either there is no daemon running
+ * for this queue, or there is one running but *it* could not
+ * write a lock file (which means we can not determine the
+ * process id of that lpd-process).
+ */
+ switch (setres) {
+ case SQS_CHGOK:
+ case SQS_CHGFAIL:
+ /* Kill the process */
+ killres = kill_qtask(lf);
+ break;
+ case SQS_CREOK:
+ case SQS_CREFAIL:
+ printf("\tno daemon to abort\n");
+ break;
+ case SQS_STATFAIL:
+ printf("\tassuming no daemon to abort\n");
+ break;
+ default:
+ printf("\t<unexpected result (%d) from set_qstate>\n",
+ setres);
+ break;
+ }
+
+ if (setres >= 0)
+ upstat(pp, "printing disabled\n", 0);
+}
+
+/*
+ * "global" variables for all the routines related to 'clean' and 'tclean'
+ */
+static time_t cln_now; /* current time */
+static double cln_minage; /* minimum age before file is removed */
+static long cln_sizecnt; /* amount of space freed up */
+static int cln_debug; /* print extra debugging msgs */
+static int cln_filecnt; /* number of files destroyed */
+static int cln_foundcore; /* found a core file! */
+static int cln_queuecnt; /* number of queues checked */
+static int cln_testonly; /* remove-files vs just-print-info */
+
+static int
+doselect(const struct dirent *d)
+{
+ int c = d->d_name[0];
+
+ if ((c == 'c' || c == 'd' || c == 'r' || c == 't') &&
+ d->d_name[1] == 'f')
+ return 1;
+ if (c == 'c') {
+ if (!strcmp(d->d_name, "core"))
+ cln_foundcore = 1;
+ }
+ if (c == 'e') {
+ if (!strncmp(d->d_name, "errs.", 5))
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Comparison routine that clean_q() uses for scandir.
+ *
+ * The purpose of this sort is to have all `df' files end up immediately
+ * after the matching `cf' file. For files matching `cf', `df', `rf', or
+ * `tf', it sorts by job number and machine, then by `cf', `df', `rf', or
+ * `tf', and then by the sequence letter (which is A-Z, or a-z). This
+ * routine may also see filenames which do not start with `cf', `df', `rf',
+ * or `tf' (such as `errs.*'), and those are simply sorted by the full
+ * filename.
+ *
+ * XXX
+ * This assumes that all control files start with `cfA*', and it turns
+ * out there are a few implementations of lpr which will create `cfB*'
+ * filenames (they will have datafile names which start with `dfB*').
+ */
+static int
+sortq(const struct dirent **a, const struct dirent **b)
+{
+ const int a_lt_b = -1, a_gt_b = 1, cat_other = 10;
+ const char *fname_a, *fname_b, *jnum_a, *jnum_b;
+ int cat_a, cat_b, ch, res, seq_a, seq_b;
+
+ fname_a = (*a)->d_name;
+ fname_b = (*b)->d_name;
+
+ /*
+ * First separate filenames into categories. Categories are
+ * legitimate `cf', `df', `rf' & `tf' filenames, and "other" - in
+ * that order. It is critical that the mapping be exactly the
+ * same for 'a' vs 'b', so define a macro for the job.
+ *
+ * [aside: the standard `cf' file has the jobnumber start in
+ * position 4, but some implementations have that as an extra
+ * file-sequence letter, and start the job number in position 5.]
+ */
+#define MAP_TO_CAT(fname_X,cat_X,jnum_X,seq_X) do { \
+ cat_X = cat_other; \
+ ch = *(fname_X + 2); \
+ jnum_X = fname_X + 3; \
+ seq_X = 0; \
+ if ((*(fname_X + 1) == 'f') && (isalpha(ch))) { \
+ seq_X = ch; \
+ if (*fname_X == 'c') \
+ cat_X = 1; \
+ else if (*fname_X == 'd') \
+ cat_X = 2; \
+ else if (*fname_X == 'r') \
+ cat_X = 3; \
+ else if (*fname_X == 't') \
+ cat_X = 4; \
+ if (cat_X != cat_other) { \
+ ch = *jnum_X; \
+ if (!isdigit(ch)) { \
+ if (isalpha(ch)) { \
+ jnum_X++; \
+ ch = *jnum_X; \
+ seq_X = (seq_X << 8) + ch; \
+ } \
+ if (!isdigit(ch)) \
+ cat_X = cat_other; \
+ } \
+ } \
+ } \
+} while (0)
+
+ MAP_TO_CAT(fname_a, cat_a, jnum_a, seq_a);
+ MAP_TO_CAT(fname_b, cat_b, jnum_b, seq_b);
+
+#undef MAP_TO_CAT
+
+ /* First handle all cases which have "other" files */
+ if ((cat_a >= cat_other) || (cat_b >= cat_other)) {
+ /* for two "other" files, just compare the full name */
+ if (cat_a == cat_b)
+ res = strcmp(fname_a, fname_b);
+ else if (cat_a < cat_b)
+ res = a_lt_b;
+ else
+ res = a_gt_b;
+ goto have_res;
+ }
+
+ /*
+ * At this point, we know both files are legitimate `cf', `df', `rf',
+ * or `tf' files. Compare them by job-number and machine name.
+ */
+ res = strcmp(jnum_a, jnum_b);
+ if (res != 0)
+ goto have_res;
+
+ /*
+ * We have two files which belong to the same job. Sort based
+ * on the category of file (`c' before `d', etc).
+ */
+ if (cat_a < cat_b) {
+ res = a_lt_b;
+ goto have_res;
+ } else if (cat_a > cat_b) {
+ res = a_gt_b;
+ goto have_res;
+ }
+
+ /*
+ * Two files in the same category for a single job. Sort based
+ * on the sequence letter(s). (usually `A' through `Z', etc).
+ */
+ if (seq_a < seq_b) {
+ res = a_lt_b;
+ goto have_res;
+ } else if (seq_a > seq_b) {
+ res = a_gt_b;
+ goto have_res;
+ }
+
+ /*
+ * Given that the filenames in a directory are unique, this SHOULD
+ * never happen (unless there are logic errors in this routine).
+ * But if it does happen, we must return "is equal" or the caller
+ * might see inconsistent results in the sorting order, and that
+ * can trigger other problems.
+ */
+ printf("\t*** Error in sortq: %s == %s !\n", fname_a, fname_b);
+ printf("\t*** cat %d == %d ; seq = %d %d\n", cat_a, cat_b,
+ seq_a, seq_b);
+ res = 0;
+
+have_res:
+ return res;
+}
+
+/*
+ * Remove all spool files and temporaries from the spooling area.
+ * Or, perhaps:
+ * Remove incomplete jobs from spooling area.
+ */
+
+void
+clean_gi(int argc, char *argv[])
+{
+
+ /* init some fields before 'clean' is called for each queue */
+ cln_queuecnt = 0;
+ cln_now = time(NULL);
+ cln_minage = 3600.0; /* only delete files >1h old */
+ cln_filecnt = 0;
+ cln_sizecnt = 0;
+ cln_debug = 0;
+ cln_testonly = 0;
+ generic_wrapup = &wrapup_clean;
+
+ /* see if there are any options specified before the ptr list */
+ for (; argc > 0; argc--, argv++) {
+ if (**argv != '-')
+ break;
+ if (strcmp(*argv, "-d") == 0) {
+ /* just an example of an option... */
+ cln_debug++;
+ *argv = generic_nullarg; /* "erase" it */
+ } else {
+ printf("Invalid option '%s'\n", *argv);
+ generic_initerr = 1;
+ }
+ }
+
+ return;
+}
+
+void
+tclean_gi(int argc, char *argv[])
+{
+
+ /* only difference between 'clean' and 'tclean' is one value */
+ /* (...and the fact that 'clean' is priv and 'tclean' is not) */
+ clean_gi(argc, argv);
+ cln_testonly = 1;
+
+ return;
+}
+
+void
+clean_q(struct printer *pp)
+{
+ char *cp, *cp1, *lp;
+ struct dirent **queue;
+ size_t linerem;
+ int didhead, i, n, nitems, rmcp;
+
+ cln_queuecnt++;
+
+ didhead = 0;
+ if (generic_qselect == QSEL_BYNAME) {
+ printf("%s:\n", pp->printer);
+ didhead = 1;
+ }
+
+ lp = line;
+ cp = pp->spool_dir;
+ while (lp < &line[sizeof(line) - 1]) {
+ if ((*lp++ = *cp++) == 0)
+ break;
+ }
+ lp[-1] = '/';
+ linerem = sizeof(line) - (lp - line);
+
+ cln_foundcore = 0;
+ PRIV_START
+ nitems = scandir(pp->spool_dir, &queue, doselect, sortq);
+ PRIV_END
+ if (nitems < 0) {
+ if (!didhead) {
+ printf("%s:\n", pp->printer);
+ didhead = 1;
+ }
+ printf("\tcannot examine spool directory\n");
+ return;
+ }
+ if (cln_foundcore) {
+ if (!didhead) {
+ printf("%s:\n", pp->printer);
+ didhead = 1;
+ }
+ printf("\t** found a core file in %s !\n", pp->spool_dir);
+ }
+ if (nitems == 0)
+ return;
+ if (!didhead)
+ printf("%s:\n", pp->printer);
+ if (cln_debug) {
+ printf("\t** ----- Sorted list of files being checked:\n");
+ i = 0;
+ do {
+ cp = queue[i]->d_name;
+ printf("\t** [%3d] = %s\n", i, cp);
+ } while (++i < nitems);
+ printf("\t** ----- end of sorted list\n");
+ }
+ i = 0;
+ do {
+ cp = queue[i]->d_name;
+ rmcp = 0;
+ if (*cp == 'c') {
+ /*
+ * A control file. Look for matching data-files.
+ */
+ /* XXX
+ * Note the logic here assumes that the hostname
+ * part of cf-filenames match the hostname part
+ * in df-filenames, and that is not necessarily
+ * true (eg: for multi-homed hosts). This needs
+ * some further thought...
+ */
+ n = 0;
+ while (i + 1 < nitems) {
+ cp1 = queue[i + 1]->d_name;
+ if (*cp1 != 'd' || strcmp(cp + 3, cp1 + 3))
+ break;
+ i++;
+ n++;
+ }
+ if (n == 0) {
+ rmcp = 1;
+ }
+ } else if (*cp == 'e') {
+ /*
+ * Must be an errrs or email temp file.
+ */
+ rmcp = 1;
+ } else {
+ /*
+ * Must be a df with no cf (otherwise, it would have
+ * been skipped above) or an rf or tf file (which can
+ * always be removed if it is old enough).
+ */
+ rmcp = 1;
+ }
+ if (rmcp) {
+ if (strlen(cp) >= linerem) {
+ printf("\t** internal error: 'line' overflow!\n");
+ printf("\t** spooldir = %s\n", pp->spool_dir);
+ printf("\t** cp = %s\n", cp);
+ return;
+ }
+ strlcpy(lp, cp, linerem);
+ unlinkf(line);
+ }
+ } while (++i < nitems);
+}
+
+static void
+wrapup_clean(int laststatus __unused)
+{
+
+ printf("Checked %d queues, and ", cln_queuecnt);
+ if (cln_filecnt < 1) {
+ printf("no cruft was found\n");
+ return;
+ }
+ if (cln_testonly) {
+ printf("would have ");
+ }
+ printf("removed %d files (%ld bytes).\n", cln_filecnt, cln_sizecnt);
+}
+
+static void
+unlinkf(char *name)
+{
+ struct stat stbuf;
+ double agemod, agestat;
+ int res;
+ char linkbuf[BUFSIZ];
+
+ /*
+ * We have to use lstat() instead of stat(), in case this is a df*
+ * "file" which is really a symlink due to 'lpr -s' processing. In
+ * that case, we need to check the last-mod time of the symlink, and
+ * not the file that the symlink is pointed at.
+ */
+ PRIV_START
+ res = lstat(name, &stbuf);
+ PRIV_END
+ if (res < 0) {
+ printf("\terror return from stat(%s):\n", name);
+ printf("\t %s\n", strerror(errno));
+ return;
+ }
+
+ agemod = difftime(cln_now, stbuf.st_mtime);
+ agestat = difftime(cln_now, stbuf.st_ctime);
+ if (cln_debug > 1) {
+ /* this debugging-aid probably is not needed any more... */
+ printf("\t\t modify age=%g secs, stat age=%g secs\n",
+ agemod, agestat);
+ }
+ if ((agemod <= cln_minage) && (agestat <= cln_minage))
+ return;
+
+ /*
+ * if this file is a symlink, then find out the target of the
+ * symlink before unlink-ing the file itself
+ */
+ if (S_ISLNK(stbuf.st_mode)) {
+ PRIV_START
+ res = readlink(name, linkbuf, sizeof(linkbuf));
+ PRIV_END
+ if (res < 0) {
+ printf("\terror return from readlink(%s):\n", name);
+ printf("\t %s\n", strerror(errno));
+ return;
+ }
+ if (res == sizeof(linkbuf))
+ res--;
+ linkbuf[res] = '\0';
+ }
+
+ cln_filecnt++;
+ cln_sizecnt += stbuf.st_size;
+
+ if (cln_testonly) {
+ printf("\twould remove %s\n", name);
+ if (S_ISLNK(stbuf.st_mode)) {
+ printf("\t (which is a symlink to %s)\n", linkbuf);
+ }
+ } else {
+ PRIV_START
+ res = unlink(name);
+ PRIV_END
+ if (res < 0)
+ printf("\tcannot remove %s (!)\n", name);
+ else
+ printf("\tremoved %s\n", name);
+ /* XXX
+ * Note that for a df* file, this code should also check to see
+ * if it is a symlink to some other file, and if the original
+ * lpr command included '-r' ("remove file"). Of course, this
+ * code would not be removing the df* file unless there was no
+ * matching cf* file, and without the cf* file it is currently
+ * impossible to determine if '-r' had been specified...
+ *
+ * As a result of this quandry, we may be leaving behind a
+ * user's file that was supposed to have been removed after
+ * being printed. This may effect services such as CAP or
+ * samba, if they were configured to use 'lpr -r', and if
+ * datafiles are not being properly removed.
+ */
+ if (S_ISLNK(stbuf.st_mode)) {
+ printf("\t (which was a symlink to %s)\n", linkbuf);
+ }
+ }
+}
+
+/*
+ * Enable queuing to the printer (allow lpr to add new jobs to the queue).
+ */
+void
+enable_q(struct printer *pp)
+{
+ int setres;
+ char lf[MAXPATHLEN];
+
+ lock_file_name(pp, lf, sizeof lf);
+ printf("%s:\n", pp->printer);
+
+ setres = set_qstate(SQS_ENABLEQ, lf);
+}
+
+/*
+ * Disable queuing.
+ */
+void
+disable_q(struct printer *pp)
+{
+ int setres;
+ char lf[MAXPATHLEN];
+
+ lock_file_name(pp, lf, sizeof lf);
+ printf("%s:\n", pp->printer);
+
+ setres = set_qstate(SQS_DISABLEQ, lf);
+}
+
+/*
+ * Disable queuing and printing and put a message into the status file
+ * (reason for being down). If the user specified `-msg', then use
+ * everything after that as the message for the status file. If the
+ * user did NOT specify `-msg', then the command should take the first
+ * parameter as the printer name, and all remaining parameters as the
+ * message for the status file. (This is to be compatible with the
+ * original definition of 'down', which was implemented long before
+ * `-msg' was around).
+ */
+void
+down_gi(int argc, char *argv[])
+{
+
+ /* If `-msg' was specified, then this routine has nothing to do. */
+ if (generic_msg != NULL)
+ return;
+
+ /*
+ * If the user only gave one parameter, then use a default msg.
+ * (if argc == 1 at this point, then *argv == name of printer).
+ */
+ if (argc == 1) {
+ generic_msg = strdup("printing disabled\n");
+ return;
+ }
+
+ /*
+ * The user specified multiple parameters, and did not specify
+ * `-msg'. Build a message from all the parameters after the
+ * first one (and nullify those parameters so generic-processing
+ * will not process them as printer-queue names).
+ */
+ argc--;
+ argv++;
+ generic_msg = args2line(argc, argv);
+ for (; argc > 0; argc--, argv++)
+ *argv = generic_nullarg; /* "erase" it */
+}
+
+void
+down_q(struct printer *pp)
+{
+ int setres;
+ char lf[MAXPATHLEN];
+
+ lock_file_name(pp, lf, sizeof lf);
+ printf("%s:\n", pp->printer);
+
+ setres = set_qstate(SQS_DISABLEQ+SQS_STOPP, lf);
+ if (setres >= 0)
+ upstat(pp, generic_msg, 1);
+}
+
+/*
+ * Exit lpc
+ */
+void
+quit(int argc __unused, char *argv[] __unused)
+{
+ exit(0);
+}
+
+/*
+ * Kill and restart the daemon.
+ */
+void
+restart_q(struct printer *pp)
+{
+ int killres, setres, startok;
+ char lf[MAXPATHLEN];
+
+ lock_file_name(pp, lf, sizeof lf);
+ printf("%s:\n", pp->printer);
+
+ killres = kill_qtask(lf);
+
+ /*
+ * XXX - if the kill worked, we should probably sleep for
+ * a second or so before trying to restart the queue.
+ */
+
+ /* make sure the queue is set to print jobs */
+ setres = set_qstate(SQS_STARTP, lf);
+
+ PRIV_START
+ startok = startdaemon(pp);
+ PRIV_END
+ if (!startok)
+ printf("\tcouldn't restart daemon\n");
+ else
+ printf("\tdaemon restarted\n");
+}
+
+/*
+ * Set the status message of each queue listed. Requires a "-msg"
+ * parameter to indicate the end of the queue list and start of msg text.
+ */
+void
+setstatus_gi(int argc __unused, char *argv[] __unused)
+{
+
+ if (generic_msg == NULL) {
+ printf("You must specify '-msg' before the text of the new status message.\n");
+ generic_initerr = 1;
+ }
+}
+
+void
+setstatus_q(struct printer *pp)
+{
+ struct stat stbuf;
+ int not_shown;
+ char lf[MAXPATHLEN];
+
+ lock_file_name(pp, lf, sizeof lf);
+ printf("%s:\n", pp->printer);
+
+ upstat(pp, generic_msg, 1);
+
+ /*
+ * Warn the user if 'lpq' will not display this new status-message.
+ * Note that if lock file does not exist, then the queue is enabled
+ * for both queuing and printing.
+ */
+ not_shown = 1;
+ if (stat(lf, &stbuf) >= 0) {
+ if (stbuf.st_mode & LFM_PRINT_DIS)
+ not_shown = 0;
+ }
+ if (not_shown) {
+ printf("\tnote: This queue currently has printing enabled,\n");
+ printf("\t so this -msg will only be shown by 'lpq' if\n");
+ printf("\t a job is actively printing on it.\n");
+ }
+}
+
+/*
+ * Enable printing on the specified printer and startup the daemon.
+ */
+void
+start_q(struct printer *pp)
+{
+ int setres, startok;
+ char lf[MAXPATHLEN];
+
+ lock_file_name(pp, lf, sizeof lf);
+ printf("%s:\n", pp->printer);
+
+ setres = set_qstate(SQS_STARTP, lf);
+
+ PRIV_START
+ startok = startdaemon(pp);
+ PRIV_END
+ if (!startok)
+ printf("\tcouldn't start daemon\n");
+ else
+ printf("\tdaemon started\n");
+ PRIV_END
+}
+
+/*
+ * Print the status of the printer queue.
+ */
+void
+status(struct printer *pp)
+{
+ struct stat stbuf;
+ register int fd, i;
+ register struct dirent *dp;
+ DIR *dirp;
+ char file[MAXPATHLEN];
+
+ printf("%s:\n", pp->printer);
+ lock_file_name(pp, file, sizeof file);
+ if (stat(file, &stbuf) >= 0) {
+ printf("\tqueuing is %s\n",
+ ((stbuf.st_mode & LFM_QUEUE_DIS) ? "disabled"
+ : "enabled"));
+ printf("\tprinting is %s\n",
+ ((stbuf.st_mode & LFM_PRINT_DIS) ? "disabled"
+ : "enabled"));
+ } else {
+ printf("\tqueuing is enabled\n");
+ printf("\tprinting is enabled\n");
+ }
+ if ((dirp = opendir(pp->spool_dir)) == NULL) {
+ printf("\tcannot examine spool directory\n");
+ return;
+ }
+ i = 0;
+ while ((dp = readdir(dirp)) != NULL) {
+ if (*dp->d_name == 'c' && dp->d_name[1] == 'f')
+ i++;
+ }
+ closedir(dirp);
+ if (i == 0)
+ printf("\tno entries in spool area\n");
+ else if (i == 1)
+ printf("\t1 entry in spool area\n");
+ else
+ printf("\t%d entries in spool area\n", i);
+ fd = open(file, O_RDONLY);
+ if (fd < 0 || flock(fd, LOCK_SH|LOCK_NB) == 0) {
+ (void) close(fd); /* unlocks as well */
+ printf("\tprinter idle\n");
+ return;
+ }
+ (void) close(fd);
+ /* print out the contents of the status file, if it exists */
+ status_file_name(pp, file, sizeof file);
+ fd = open(file, O_RDONLY|O_SHLOCK);
+ if (fd >= 0) {
+ (void) fstat(fd, &stbuf);
+ if (stbuf.st_size > 0) {
+ putchar('\t');
+ while ((i = read(fd, line, sizeof(line))) > 0)
+ (void) fwrite(line, 1, i, stdout);
+ }
+ (void) close(fd); /* unlocks as well */
+ }
+}
+
+/*
+ * Stop the specified daemon after completing the current job and disable
+ * printing.
+ */
+void
+stop_q(struct printer *pp)
+{
+ int setres;
+ char lf[MAXPATHLEN];
+
+ lock_file_name(pp, lf, sizeof lf);
+ printf("%s:\n", pp->printer);
+
+ setres = set_qstate(SQS_STOPP, lf);
+
+ if (setres >= 0)
+ upstat(pp, "printing disabled\n", 0);
+}
+
+struct jobqueue **queue;
+int nitems;
+time_t mtime;
+
+/*
+ * Put the specified jobs at the top of printer queue.
+ */
+void
+topq(int argc, char *argv[])
+{
+ register int i;
+ struct stat stbuf;
+ int cmdstatus, changed;
+ struct printer myprinter, *pp = &myprinter;
+
+ if (argc < 3) {
+ printf("usage: topq printer [jobnum ...] [user ...]\n");
+ return;
+ }
+
+ --argc;
+ ++argv;
+ init_printer(pp);
+ cmdstatus = getprintcap(*argv, pp);
+ switch(cmdstatus) {
+ default:
+ fatal(pp, "%s", pcaperr(cmdstatus));
+ case PCAPERR_NOTFOUND:
+ printf("unknown printer %s\n", *argv);
+ return;
+ case PCAPERR_TCOPEN:
+ printf("warning: %s: unresolved tc= reference(s)", *argv);
+ break;
+ case PCAPERR_SUCCESS:
+ break;
+ }
+ printf("%s:\n", pp->printer);
+
+ PRIV_START
+ if (chdir(pp->spool_dir) < 0) {
+ printf("\tcannot chdir to %s\n", pp->spool_dir);
+ goto out;
+ }
+ PRIV_END
+ nitems = getq(pp, &queue);
+ if (nitems == 0)
+ return;
+ changed = 0;
+ mtime = queue[0]->job_time;
+ for (i = argc; --i; ) {
+ if (doarg(argv[i]) == 0) {
+ printf("\tjob %s is not in the queue\n", argv[i]);
+ continue;
+ } else
+ changed++;
+ }
+ for (i = 0; i < nitems; i++)
+ free(queue[i]);
+ free(queue);
+ if (!changed) {
+ printf("\tqueue order unchanged\n");
+ return;
+ }
+ /*
+ * Turn on the public execute bit of the lock file to
+ * get lpd to rebuild the queue after the current job.
+ */
+ PRIV_START
+ if (changed && stat(pp->lock_file, &stbuf) >= 0)
+ (void) chmod(pp->lock_file, stbuf.st_mode | LFM_RESET_QUE);
+
+out:
+ PRIV_END
+}
+
+/*
+ * Reposition the job by changing the modification time of
+ * the control file.
+ */
+static int
+touch(struct jobqueue *jq)
+{
+ struct timeval tvp[2];
+ int ret;
+
+ tvp[0].tv_sec = tvp[1].tv_sec = --mtime;
+ tvp[0].tv_usec = tvp[1].tv_usec = 0;
+ PRIV_START
+ ret = utimes(jq->job_cfname, tvp);
+ PRIV_END
+ return (ret);
+}
+
+/*
+ * Checks if specified job name is in the printer's queue.
+ * Returns: negative (-1) if argument name is not in the queue.
+ */
+static int
+doarg(char *job)
+{
+ register struct jobqueue **qq;
+ register int jobnum, n;
+ register char *cp, *machine;
+ int cnt = 0;
+ FILE *fp;
+
+ /*
+ * Look for a job item consisting of system name, colon, number
+ * (example: ucbarpa:114)
+ */
+ if ((cp = strchr(job, ':')) != NULL) {
+ machine = job;
+ *cp++ = '\0';
+ job = cp;
+ } else
+ machine = NULL;
+
+ /*
+ * Check for job specified by number (example: 112 or 235ucbarpa).
+ */
+ if (isdigit(*job)) {
+ jobnum = 0;
+ do
+ jobnum = jobnum * 10 + (*job++ - '0');
+ while (isdigit(*job));
+ for (qq = queue + nitems; --qq >= queue; ) {
+ n = 0;
+ for (cp = (*qq)->job_cfname+3; isdigit(*cp); )
+ n = n * 10 + (*cp++ - '0');
+ if (jobnum != n)
+ continue;
+ if (*job && strcmp(job, cp) != 0)
+ continue;
+ if (machine != NULL && strcmp(machine, cp) != 0)
+ continue;
+ if (touch(*qq) == 0) {
+ printf("\tmoved %s\n", (*qq)->job_cfname);
+ cnt++;
+ }
+ }
+ return(cnt);
+ }
+ /*
+ * Process item consisting of owner's name (example: henry).
+ */
+ for (qq = queue + nitems; --qq >= queue; ) {
+ PRIV_START
+ fp = fopen((*qq)->job_cfname, "r");
+ PRIV_END
+ if (fp == NULL)
+ continue;
+ while (getline(fp) > 0)
+ if (line[0] == 'P')
+ break;
+ (void) fclose(fp);
+ if (line[0] != 'P' || strcmp(job, line+1) != 0)
+ continue;
+ if (touch(*qq) == 0) {
+ printf("\tmoved %s\n", (*qq)->job_cfname);
+ cnt++;
+ }
+ }
+ return(cnt);
+}
+
+/*
+ * Enable both queuing & printing, and start printer (undo `down').
+ */
+void
+up_q(struct printer *pp)
+{
+ int setres, startok;
+ char lf[MAXPATHLEN];
+
+ lock_file_name(pp, lf, sizeof lf);
+ printf("%s:\n", pp->printer);
+
+ setres = set_qstate(SQS_ENABLEQ+SQS_STARTP, lf);
+
+ PRIV_START
+ startok = startdaemon(pp);
+ PRIV_END
+ if (!startok)
+ printf("\tcouldn't start daemon\n");
+ else
+ printf("\tdaemon started\n");
+}
diff --git a/usr.sbin/lpr/lpc/cmdtab.c b/usr.sbin/lpr/lpc/cmdtab.c
new file mode 100644
index 0000000..1e8e4da
--- /dev/null
+++ b/usr.sbin/lpr/lpc/cmdtab.c
@@ -0,0 +1,90 @@
+/*
+ * 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 char sccsid[] = "@(#)cmdtab.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+#include "lpc.h"
+#include "extern.h"
+
+/*
+ * lpc -- command tables
+ */
+char aborthelp[] = "terminate a spooling daemon immediately and disable printing";
+char botmqhelp[] = "move job(s) to the bottom of printer queue";
+char cleanhelp[] = "remove cruft files from a queue";
+char enablehelp[] = "turn a spooling queue on";
+char disablehelp[] = "turn a spooling queue off";
+char downhelp[] = "do a 'stop' followed by 'disable' and put a message in status";
+char helphelp[] = "get help on commands";
+char quithelp[] = "exit lpc";
+char restarthelp[] = "kill (if possible) and restart a spooling daemon";
+char setstatushelp[] = "set the status message of a queue, requires\n"
+ "\t\t\"-msg\" before the text of the new message";
+char starthelp[] = "enable printing and start a spooling daemon";
+char statushelp[] = "show status of daemon and queue";
+char stophelp[] = "stop a spooling daemon after current job completes and disable printing";
+char tcleanhelp[] = "test to see what files a clean cmd would remove";
+char topqhelp[] = "move job(s) to the top of printer queue";
+char uphelp[] = "enable everything and restart spooling daemon";
+
+/* Use some abbreviations so entries won't need to wrap */
+#define PR LPC_PRIVCMD
+#define M LPC_MSGOPT
+
+struct cmd cmdtab[] = {
+ { "abort", aborthelp, PR, 0, abort_q },
+ { "bottomq", botmqhelp, PR, bottomq_cmd, 0 },
+ { "clean", cleanhelp, PR, clean_gi, clean_q },
+ { "enable", enablehelp, PR, 0, enable_q },
+ { "exit", quithelp, 0, quit, 0 },
+ { "disable", disablehelp, PR, 0, disable_q },
+ { "down", downhelp, PR|M, down_gi, down_q },
+ { "help", helphelp, 0, help, 0 },
+ { "quit", quithelp, 0, quit, 0 },
+ { "restart", restarthelp, 0, 0, restart_q },
+ { "start", starthelp, PR, 0, start_q },
+ { "status", statushelp, 0, 0, status },
+ { "setstatus", setstatushelp, PR|M, setstatus_gi, setstatus_q },
+ { "stop", stophelp, PR, 0, stop_q },
+ { "tclean", tcleanhelp, 0, tclean_gi, clean_q },
+ { "topq", topqhelp, PR, topq_cmd, 0 },
+ { "up", uphelp, PR, 0, up_q },
+ { "?", helphelp, 0, help, 0 },
+ { "xtopq", topqhelp, PR, topq, 0 },
+ { 0, 0, 0, 0, 0},
+};
+
+int NCMDS = sizeof (cmdtab) / sizeof (cmdtab[0]);
diff --git a/usr.sbin/lpr/lpc/extern.h b/usr.sbin/lpr/lpc/extern.h
new file mode 100644
index 0000000..45844a5
--- /dev/null
+++ b/usr.sbin/lpr/lpc/extern.h
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ *
+ * @(#)extern.h 8.1 (Berkeley) 6/6/93
+ *
+ * $FreeBSD$
+ */
+
+
+#include <sys/types.h>
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+
+/*
+ * Options for setup_myprinter().
+ */
+#define SUMP_NOHEADER 0x0001 /* Do not print a header line */
+#define SUMP_CHDIR_SD 0x0002 /* chdir into the spool directory */
+
+__BEGIN_DECLS
+void abort_q(struct printer *_pp);
+void bottomq_cmd(int _argc, char *_argv[]);
+void clean_gi(int _argc, char *_argv[]);
+void clean_q(struct printer *_pp);
+void disable_q(struct printer *_pp);
+void down_gi(int _argc, char *_argv[]);
+void down_q(struct printer *_pp);
+void enable_q(struct printer *_pp);
+void generic(void (*_specificrtn)(struct printer *_pp), int _cmdopts,
+ void (*_initcmd)(int _argc, char *_argv[]),
+ int _argc, char *_argv[]);
+void help(int _argc, char *_argv[]);
+void quit(int _argc, char *_argv[]);
+void restart_q(struct printer *_pp);
+void setstatus_gi(int _argc, char *_argv[]);
+void setstatus_q(struct printer *_pp);
+void start_q(struct printer *_pp);
+void status(struct printer *_pp);
+void stop_q(struct printer *_pp);
+void tclean_gi(int _argc, char *_argv[]);
+void topq_cmd(int _argc, char *_argv[]);
+void up_q(struct printer *_pp);
+void topq(int _argc, char *_argv[]); /* X-version */
+
+/* from lpc.c: */
+struct printer *setup_myprinter(char *_pwanted, struct printer *_pp,
+ int _sump_opts);
+__END_DECLS
+
+extern int NCMDS;
+extern struct cmd cmdtab[];
+extern uid_t uid, euid;
diff --git a/usr.sbin/lpr/lpc/lpc.8 b/usr.sbin/lpr/lpc/lpc.8
new file mode 100644
index 0000000..5a66865
--- /dev/null
+++ b/usr.sbin/lpr/lpc/lpc.8
@@ -0,0 +1,309 @@
+.\" 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.
+.\"
+.\" @(#)lpc.8 8.5 (Berkeley) 4/28/95
+.\" $FreeBSD$
+.\"
+.Dd July 16, 2002
+.Dt LPC 8
+.Os
+.Sh NAME
+.Nm lpc
+.Nd line printer control program
+.Sh SYNOPSIS
+.Nm
+.Op Ar command Op Ar argument ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is used by the system administrator to control the
+operation of the line printer system.
+For each line printer configured in
+.Pa /etc/printcap ,
+.Nm
+may be used to:
+.Bl -bullet -offset indent
+.It
+disable or enable a printer,
+.It
+disable or enable a printer's spooling queue,
+.It
+rearrange the order of jobs in a spooling queue,
+.It
+find the status of printers, and their associated
+spooling queues and printer daemons,
+.It
+change the status message for printer queues (the status message
+may be seen by users as part of the output of the
+.Xr lpq 1
+utility).
+.El
+.Pp
+Without any arguments,
+.Nm
+will prompt for commands from the standard input.
+If arguments are supplied,
+.Nm
+interprets the first argument as a command and the remaining
+arguments as parameters to the command.
+The standard input
+may be redirected causing
+.Nm
+to read commands from file.
+Commands may be abbreviated;
+the following is the list of recognized commands.
+.Pp
+.Bl -tag -width indent -compact
+.It Ic \&? Op Ar command ...
+.It Ic help Op Ar command ...
+Print a short description of each command specified in the argument list,
+or, if no argument is given, a list of the recognized commands.
+.Pp
+.It Ic abort Brq Cm all | Ar printer
+Terminate an active spooling daemon on the local host immediately and
+then disable printing (preventing new daemons from being started by
+.Xr lpr 1 )
+for the specified printers.
+.Pp
+.It Ic bottomq Ar printer Op Ar jobspec ...
+Take the specified jobs in the order specified and move them to the
+bottom of the printer queue.
+Each
+.Ar jobspec
+can match multiple print jobs.
+The full description of a
+.Ar jobspec
+is given below.
+.Pp
+.It Ic clean Brq Cm all | Ar printer
+Remove any temporary files, data files, and control files that cannot
+be printed (i.e., do not form a complete printer job)
+from the specified printer queue(s) on the local machine.
+This command will also look for
+.Pa core
+files in spool directory
+for each printer queue, and list any that are found.
+It will not remove any
+.Pa core
+files.
+See also the
+.Ic tclean
+command.
+.Pp
+.It Ic disable Brq Cm all | Ar printer
+Turn the specified printer queues off.
+This prevents new
+printer jobs from being entered into the queue by
+.Xr lpr 1 .
+.Pp
+.It Ic down Bro Cm all | Ar printer ... Brc Cm -msg Ar message ...
+.It Ic down Bro Cm all | Ar printer Brc Ar message ...
+Turn the specified printer queue off, disable printing and put
+.Ar message
+in the printer status file.
+When specifying more than one printer queue, the
+.Ic -msg
+argument is required to separate the list of printers from the text
+that will be the new status message.
+The message does not need to be quoted, the
+remaining arguments are treated like
+.Xr echo 1 .
+This is normally used to take a printer down, and let other users
+find out why it is down (the
+.Xr lpq 1
+utility will indicate that the printer is down and will print the
+status message).
+.Pp
+.It Ic enable Brq Cm all | Ar printer
+Enable spooling on the local queue for the listed printers.
+This will allow
+.Xr lpr 1
+to put new jobs in the spool queue.
+.Pp
+.It Ic exit
+.It Ic quit
+Exit from
+.Nm .
+.Pp
+.It Ic restart Brq Cm all | Ar printer
+Attempt to start a new printer daemon.
+This is useful when some abnormal condition causes the daemon to
+die unexpectedly, leaving jobs in the queue.
+.Xr lpq 1
+will report that there is no daemon present when this condition occurs.
+If the user is the super-user,
+try to abort the current daemon first (i.e., kill and restart a stuck daemon).
+.Pp
+.It Ic setstatus Bro Cm all | Ar printer Brc Cm -msg Ar message ...
+Set the status message for the specified printers.
+The
+.Ic -msg
+argument is required to separate the list of printers from the text
+that will be the new status message.
+This is normally used to change the status message when the printer
+queue is no longer active after printing has been disabled, and you
+want to change what users will see in the output of the
+.Xr lpq 1
+utility.
+.Pp
+.It Ic start Brq Cm all | Ar printer
+Enable printing and start a spooling daemon for the listed printers.
+.Pp
+.It Ic status Brq Cm all | Ar printer
+Display the status of daemons and queues on the local machine.
+.Pp
+.It Ic stop Brq Cm all | Ar printer
+Stop a spooling daemon after the current job completes and disable
+printing.
+.Pp
+.It Ic tclean Brq Cm all | Ar printer
+This will do a test-run of the
+.Ic clean
+command.
+All the same checking is done, but the command will only print out
+messages saying what a similar
+.Ic clean
+command would do if the user typed it in.
+It will not remove any files.
+Note that the
+.Ic clean
+command is a privileged command, while the
+.Ic tclean
+command is not restricted.
+.Pp
+.It Ic topq Ar printer Op Ar jobspec ...
+Take the specified jobs in the order specified and move them to the
+top of the printer queue.
+Each
+.Ar jobspec
+can match multiple print jobs.
+The full description of a
+.Ar jobspec
+is given below.
+.Pp
+.It Ic up Brq Cm all | Ar printer
+Enable everything and start a new printer daemon.
+Undoes the effects of
+.Ic down .
+.El
+.Pp
+Commands such as
+.Ic topq
+and
+.Ic bottomq
+can take one or more
+.Ar jobspec
+to specify which jobs the command should operate on.
+A
+.Ar jobspec
+can be:
+.Bl -bullet
+.It
+a single job number, which will match all jobs in the printer's queue
+which have the same job number.
+Eg:
+.Ar 17 ,
+.It
+a range of job numbers, which will match all jobs with a number between
+the starting and ending job numbers, inclusive.
+Eg:
+.Ar 21-32 ,
+.It
+a specific userid, which will match all jobs which were sent by that
+user.
+Eg:
+.Ar jones ,
+.It
+a host name, when prefixed by an `@', which will match all jobs in
+the queue which were sent from the given host.
+Eg:
+.Ar @freebsd.org ,
+.It
+a job range and a userid, separated by a `:', which will match all jobs
+which both match the job range and were sent by the specified user.
+Eg:
+.Ar jones:17
+or
+.Ar 21-32:jones ,
+.It
+a job range and/or a userid, followed by a host name, which will match
+all jobs which match all the specified criteria.
+Eg:
+.Ar jones@freebsd.org
+or
+.Ar 21-32@freebsd.org
+or
+.Ar jones:17@freebsd.org .
+.El
+.Pp
+The values for userid and host name can also include pattern-matching
+characters, similar to the pattern matching done for filenames in
+most command shells.
+Note that if you enter a
+.Ic topq
+or
+.Ic bottomq
+command as parameters on the initial
+.Nm
+command, then the shell will expand any pattern-matching characters
+that it can (based on what files in finds in the current directory)
+before
+.Nm
+processes the command.
+In that case, any parameters which include pattern-matching characters
+should be enclosed in quotes, so that the shell will not try to
+expand them.
+.Sh FILES
+.Bl -tag -width /var/spool/*/lockx -compact
+.It Pa /etc/printcap
+printer description file
+.It Pa /var/spool/*
+spool directories
+.It Pa /var/spool/*/lock
+lock file for queue control
+.El
+.Sh DIAGNOSTICS
+.Bl -diag
+.It "?Ambiguous command"
+abbreviation matches more than one command
+.It "?Invalid command"
+no match was found
+.It "?Privileged command"
+you must be a member of group "operator" or root to execute this command
+.El
+.Sh SEE ALSO
+.Xr lpq 1 ,
+.Xr lpr 1 ,
+.Xr lprm 1 ,
+.Xr printcap 5 ,
+.Xr chkprintcap 8 ,
+.Xr lpd 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.2 .
diff --git a/usr.sbin/lpr/lpc/lpc.c b/usr.sbin/lpr/lpc/lpc.c
new file mode 100644
index 0000000..cc58bd9
--- /dev/null
+++ b/usr.sbin/lpr/lpc/lpc.c
@@ -0,0 +1,419 @@
+/*
+ * 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 */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)lpc.c 8.3 (Berkeley) 4/28/95";
+#endif /* not lint */
+#endif
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <grp.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <string.h>
+#include <unistd.h>
+#include <histedit.h>
+
+#include "lp.h"
+#include "lpc.h"
+#include "extern.h"
+
+#ifndef LPR_OPER
+#define LPR_OPER "operator" /* group name of lpr operators */
+#endif
+
+/*
+ * lpc -- line printer control program
+ */
+
+#define MAX_CMDLINE 200
+#define MAX_MARGV 20
+static int fromatty;
+
+static char cmdline[MAX_CMDLINE];
+static int margc;
+static char *margv[MAX_MARGV];
+uid_t uid, euid;
+
+int main(int _argc, char *_argv[]);
+static void cmdscanner(void);
+static struct cmd *getcmd(const char *_name);
+static void intr(int _signo);
+static void makeargv(void);
+static int ingroup(const char *_grname);
+
+int
+main(int argc, char *argv[])
+{
+ register struct cmd *c;
+
+ euid = geteuid();
+ uid = getuid();
+ PRIV_END
+ progname = argv[0];
+ openlog("lpd", 0, LOG_LPR);
+
+ if (--argc > 0) {
+ c = getcmd(*++argv);
+ if (c == (struct cmd *)-1) {
+ printf("?Ambiguous command\n");
+ exit(1);
+ }
+ if (c == 0) {
+ printf("?Invalid command\n");
+ exit(1);
+ }
+ if ((c->c_opts & LPC_PRIVCMD) && getuid() &&
+ ingroup(LPR_OPER) == 0) {
+ printf("?Privileged command\n");
+ exit(1);
+ }
+ if (c->c_generic != 0)
+ generic(c->c_generic, c->c_opts, c->c_handler,
+ argc, argv);
+ else
+ (*c->c_handler)(argc, argv);
+ exit(0);
+ }
+ fromatty = isatty(fileno(stdin));
+ if (!fromatty)
+ signal(SIGINT, intr);
+ for (;;) {
+ cmdscanner();
+ }
+}
+
+static void
+intr(int signo __unused)
+{
+ /* (the '__unused' is just to avoid a compile-time warning) */
+ exit(0);
+}
+
+static const char *
+lpc_prompt(void)
+{
+ return ("lpc> ");
+}
+
+/*
+ * Command parser.
+ */
+static void
+cmdscanner(void)
+{
+ register struct cmd *c;
+ static EditLine *el;
+ static History *hist;
+ HistEvent he;
+ size_t len;
+ int num;
+ const char *bp;
+
+ num = 0;
+ bp = NULL;
+ el = NULL;
+ hist = NULL;
+ for (;;) {
+ if (fromatty) {
+ if (!el) {
+ el = el_init("lpc", stdin, stdout, stderr);
+ hist = history_init();
+ history(hist, &he, H_SETSIZE, 100);
+ el_set(el, EL_HIST, history, hist);
+ el_set(el, EL_EDITOR, "emacs");
+ el_set(el, EL_PROMPT, lpc_prompt);
+ el_set(el, EL_SIGNAL, 1);
+ el_source(el, NULL);
+ /*
+ * EditLine init may call 'cgetset()' to set a
+ * capability-db meant for termcap (eg: to set
+ * terminal type 'xterm'). Reset that now, or
+ * that same db-information will be used for
+ * printcap (giving us an "xterm" printer, with
+ * all kinds of invalid capabilities...).
+ */
+ cgetset(NULL);
+ }
+ if ((bp = el_gets(el, &num)) == NULL || num == 0)
+ quit(0, NULL);
+
+ len = (num > MAX_CMDLINE - 1) ? MAX_CMDLINE - 1 : num;
+ memcpy(cmdline, bp, len);
+ cmdline[len] = 0;
+ history(hist, &he, H_ENTER, bp);
+
+ } else {
+ if (fgets(cmdline, MAX_CMDLINE, stdin) == NULL)
+ quit(0, NULL);
+ if (cmdline[0] == 0 || cmdline[0] == '\n')
+ break;
+ }
+
+ makeargv();
+ if (margc == 0)
+ continue;
+ if (el != NULL && el_parse(el, margc, margv) != -1)
+ continue;
+
+ c = getcmd(margv[0]);
+ if (c == (struct cmd *)-1) {
+ printf("?Ambiguous command\n");
+ continue;
+ }
+ if (c == 0) {
+ printf("?Invalid command\n");
+ continue;
+ }
+ if ((c->c_opts & LPC_PRIVCMD) && getuid() &&
+ ingroup(LPR_OPER) == 0) {
+ printf("?Privileged command\n");
+ continue;
+ }
+
+ /*
+ * Two different commands might have the same generic rtn
+ * (eg: "clean" and "tclean"), and just use different
+ * handler routines for distinct command-setup. The handler
+ * routine might also be set on a generic routine for
+ * initial parameter processing.
+ */
+ if (c->c_generic != 0)
+ generic(c->c_generic, c->c_opts, c->c_handler,
+ margc, margv);
+ else
+ (*c->c_handler)(margc, margv);
+ }
+}
+
+static struct cmd *
+getcmd(const char *name)
+{
+ register const char *p, *q;
+ register struct cmd *c, *found;
+ register int nmatches, longest;
+
+ longest = 0;
+ nmatches = 0;
+ found = 0;
+ for (c = cmdtab; (p = c->c_name); c++) {
+ for (q = name; *q == *p++; q++)
+ if (*q == 0) /* exact match? */
+ return(c);
+ if (!*q) { /* the name was a prefix */
+ if (q - name > longest) {
+ longest = q - name;
+ nmatches = 1;
+ found = c;
+ } else if (q - name == longest)
+ nmatches++;
+ }
+ }
+ if (nmatches > 1)
+ return((struct cmd *)-1);
+ return(found);
+}
+
+/*
+ * Slice a string up into argc/argv.
+ */
+static void
+makeargv(void)
+{
+ register char *cp;
+ register char **argp = margv;
+ register int n = 0;
+
+ margc = 0;
+ for (cp = cmdline; *cp && (size_t)(cp - cmdline) < sizeof(cmdline) &&
+ n < MAX_MARGV - 1; n++) {
+ while (isspace(*cp))
+ cp++;
+ if (*cp == '\0')
+ break;
+ *argp++ = cp;
+ margc += 1;
+ while (*cp != '\0' && !isspace(*cp))
+ cp++;
+ if (*cp == '\0')
+ break;
+ *cp++ = '\0';
+ }
+ *argp++ = 0;
+}
+
+#define HELPINDENT (sizeof ("directory"))
+
+/*
+ * Help command.
+ */
+void
+help(int argc, char *argv[])
+{
+ register struct cmd *c;
+
+ if (argc == 1) {
+ register int i, j, w;
+ int columns, width = 0, lines;
+
+ printf("Commands may be abbreviated. Commands are:\n\n");
+ for (c = cmdtab; c->c_name; c++) {
+ int len = strlen(c->c_name);
+
+ if (len > width)
+ width = len;
+ }
+ width = (width + 8) &~ 7;
+ columns = 80 / width;
+ if (columns == 0)
+ columns = 1;
+ lines = (NCMDS + columns - 1) / columns;
+ for (i = 0; i < lines; i++) {
+ for (j = 0; j < columns; j++) {
+ c = cmdtab + j * lines + i;
+ if (c->c_name)
+ printf("%s", c->c_name);
+ if (c + lines >= &cmdtab[NCMDS]) {
+ printf("\n");
+ break;
+ }
+ w = strlen(c->c_name);
+ while (w < width) {
+ w = (w + 8) &~ 7;
+ putchar('\t');
+ }
+ }
+ }
+ return;
+ }
+ while (--argc > 0) {
+ register char *arg;
+ arg = *++argv;
+ c = getcmd(arg);
+ if (c == (struct cmd *)-1)
+ printf("?Ambiguous help command %s\n", arg);
+ else if (c == (struct cmd *)0)
+ printf("?Invalid help command %s\n", arg);
+ else
+ printf("%-*s\t%s\n", (int) HELPINDENT,
+ c->c_name, c->c_help);
+ }
+}
+
+/*
+ * return non-zero if the user is a member of the given group
+ */
+static int
+ingroup(const char *grname)
+{
+ static struct group *gptr=NULL;
+ static int ngroups = 0;
+ static long ngroups_max;
+ static gid_t *groups;
+ register gid_t gid;
+ register int i;
+
+ if (gptr == NULL) {
+ if ((gptr = getgrnam(grname)) == NULL) {
+ warnx("warning: unknown group '%s'", grname);
+ return(0);
+ }
+ ngroups_max = sysconf(_SC_NGROUPS_MAX);
+ if ((groups = malloc(sizeof(gid_t) * ngroups_max)) == NULL)
+ err(1, "malloc");
+ ngroups = getgroups(ngroups_max, groups);
+ if (ngroups < 0)
+ err(1, "getgroups");
+ }
+ gid = gptr->gr_gid;
+ for (i = 0; i < ngroups; i++)
+ if (gid == groups[i])
+ return(1);
+ return(0);
+}
+
+/*
+ * Routine to get the information for a single printer (which will be
+ * called by the routines which implement individual commands).
+ * Note: This is for commands operating on a *single* printer.
+ */
+struct printer *
+setup_myprinter(char *pwanted, struct printer *pp, int sump_opts)
+{
+ int cdres, cmdstatus;
+
+ init_printer(pp);
+ cmdstatus = getprintcap(pwanted, pp);
+ switch (cmdstatus) {
+ default:
+ fatal(pp, "%s", pcaperr(cmdstatus));
+ /* NOTREACHED */
+ case PCAPERR_NOTFOUND:
+ printf("unknown printer %s\n", pwanted);
+ return (NULL);
+ case PCAPERR_TCOPEN:
+ printf("warning: %s: unresolved tc= reference(s)", pwanted);
+ break;
+ case PCAPERR_SUCCESS:
+ break;
+ }
+ if ((sump_opts & SUMP_NOHEADER) == 0)
+ printf("%s:\n", pp->printer);
+
+ if (sump_opts & SUMP_CHDIR_SD) {
+ PRIV_START
+ cdres = chdir(pp->spool_dir);
+ PRIV_END
+ if (cdres < 0) {
+ printf("\tcannot chdir to %s\n", pp->spool_dir);
+ free_printer(pp);
+ return (NULL);
+ }
+ }
+
+ return (pp);
+}
diff --git a/usr.sbin/lpr/lpc/lpc.h b/usr.sbin/lpr/lpc/lpc.h
new file mode 100644
index 0000000..ee52903
--- /dev/null
+++ b/usr.sbin/lpr/lpc/lpc.h
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ *
+ * @(#)lpc.h 8.1 (Berkeley) 6/6/93
+ *
+ * $FreeBSD$
+ */
+
+/*
+ * Line Printer Control (lpc) program.
+ */
+struct printer;
+
+#define LPC_PRIVCMD 0x0001 /* a privileged command */
+#define LPC_MSGOPT 0x0002 /* command recognizes -msg option */
+
+struct cmd {
+ const char *c_name; /* command name */
+ const char *c_help; /* help message */
+ const int c_opts; /* flags (eg: privileged command) */
+ /* routine to do all the work for plain cmds, or
+ * initialization work for generic-printer cmds: */
+ void (*c_handler)(int, char *[]);
+ /* routine to do the work for generic-printer cmds: */
+ void (*c_generic)(struct printer *);
+};
diff --git a/usr.sbin/lpr/lpc/movejobs.c b/usr.sbin/lpr/lpc/movejobs.c
new file mode 100644
index 0000000..c349601
--- /dev/null
+++ b/usr.sbin/lpr/lpc/movejobs.c
@@ -0,0 +1,271 @@
+/*
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * Copyright (c) 2002 - Garance Alistair Drosehn <gad@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.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the FreeBSD Project
+ * or FreeBSD, Inc.
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ */
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+/*
+ * movejobs.c - The lpc commands which move jobs around.
+ */
+
+#include <sys/file.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <dirent.h> /* just for MAXNAMLEN, for job_cfname in lp.h! */
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "lp.h"
+#include "lpc.h"
+#include "matchjobs.h"
+#include "extern.h"
+
+/* Values for origcmd in tqbq_common() */
+#define IS_TOPQ 1
+#define IS_BOTQ 2
+
+static int process_jobs(int _argc, char *_argv[], process_jqe
+ _process_rtn, void *myinfo);
+static process_jqe touch_jqe;
+static void tqbq_common(int _argc, char *_argv[], int _origcmd);
+
+/*
+ * isdigit is defined to work on an 'int', in the range 0 to 255, plus EOF.
+ * Define a wrapper which can take 'char', either signed or unsigned.
+ */
+#define isdigitch(Anychar) isdigit(((int) Anychar) & 255)
+
+struct touchjqe_info { /* for topq/bottomq */
+ time_t newtime;
+};
+
+static int nitems;
+static struct jobqueue **queue;
+
+/*
+ * Process all the jobs, as specified by the user.
+ */
+static int
+process_jobs(int argc, char *argv[], process_jqe process_rtn, void *myinfo)
+{
+ struct jobspec_hdr jobs_wanted;
+ int i, matchcnt, pjres;
+
+ STAILQ_INIT(&jobs_wanted);
+ for (i = 0; i < argc; i++) {
+ pjres = parse_jobspec(argv[i], &jobs_wanted);
+ if (pjres == 0) {
+ printf("\tinvalid job specifier: %s\n", argv[i]);
+ continue;
+ }
+ }
+ matchcnt = scanq_jobspec(nitems, queue, SCQ_JSORDER, &jobs_wanted,
+ process_rtn, myinfo);
+
+ free_jobspec(&jobs_wanted);
+ return (matchcnt);
+}
+
+/*
+ * Reposition the job by changing the modification time of the
+ * control file.
+ */
+static int
+touch_jqe(void *myinfo, struct jobqueue *jq, struct jobspec *jspec)
+{
+ struct timeval tvp[2];
+ struct touchjqe_info *touch_info;
+ int ret;
+
+ /*
+ * If the entire queue has been scanned for the current jobspec,
+ * then let the user know if there were no jobs matched by that
+ * specification.
+ */
+ if (jq == NULL) {
+ if (jspec->matchcnt == 0) {
+ format_jobspec(jspec, FMTJS_VERBOSE);
+ if (jspec->pluralfmt)
+ printf("\tjobs %s are not in the queue\n",
+ jspec->fmtoutput);
+ else
+ printf("\tjob %s is not in the queue\n",
+ jspec->fmtoutput);
+ }
+ return (1);
+ }
+
+ /*
+ * Do a little juggling with "matched" vs "processed", so a single
+ * job can be matched by multiple specifications, and yet it will
+ * be moved only once. This is so, eg, 'topq lp 7 7' will not
+ * complain "job 7 is not in queue" for the second specification.
+ */
+ jq->job_matched = 0;
+ if (jq->job_processed) {
+ printf("\tmoved %s earlier\n", jq->job_cfname);
+ return (1);
+ }
+ jq->job_processed = 1;
+
+ touch_info = myinfo;
+ tvp[0].tv_sec = tvp[1].tv_sec = ++touch_info->newtime;
+ tvp[0].tv_usec = tvp[1].tv_usec = 0;
+ PRIV_START
+ ret = utimes(jq->job_cfname, tvp);
+ PRIV_END
+
+ if (ret == 0) {
+ if (jspec->matcheduser)
+ printf("\tmoved %s (user %s)\n", jq->job_cfname,
+ jspec->matcheduser);
+ else
+ printf("\tmoved %s\n", jq->job_cfname);
+ }
+ return (ret);
+}
+
+/*
+ * Put the specified jobs at the bottom of printer queue.
+ */
+void
+bottomq_cmd(int argc, char *argv[])
+{
+
+ if (argc < 3) {
+ printf("usage: bottomq printer [jobspec ...]\n");
+ return;
+ }
+ --argc; /* First argv was the command name */
+ ++argv;
+
+ tqbq_common(argc, argv, IS_BOTQ);
+}
+
+/*
+ * Put the specified jobs at the top of printer queue.
+ */
+void
+topq_cmd(int argc, char *argv[])
+{
+
+ if (argc < 3) {
+ printf("usage: topq printer [jobspec ...]\n");
+ return;
+ }
+ --argc; /* First argv was the command name */
+ ++argv;
+
+ tqbq_common(argc, argv, IS_TOPQ);
+}
+
+/*
+ * Processing in common between topq and bottomq commands.
+ */
+void
+tqbq_common(int argc, char *argv[], int origcmd)
+{
+ struct printer myprinter, *pp;
+ struct touchjqe_info touch_info;
+ int i, movecnt, setres;
+
+ pp = setup_myprinter(*argv, &myprinter, SUMP_CHDIR_SD);
+ if (pp == NULL)
+ return;
+ --argc; /* Second argv was the printer name */
+ ++argv;
+
+ nitems = getq(pp, &queue);
+ if (nitems == 0) {
+ printf("\tthere are no jobs in the queue\n");
+ free_printer(pp);
+ return;
+ }
+
+ /*
+ * The only real difference between topq and bottomq is the
+ * initial value used for newtime.
+ */
+ switch (origcmd) {
+ case IS_BOTQ:
+ /*
+ * When moving jobs to the bottom of the queue, pick a
+ * starting value which is one second after the last job
+ * in the queue.
+ */
+ touch_info.newtime = queue[nitems - 1]->job_time + 1;
+ break;
+ case IS_TOPQ:
+ /*
+ * When moving jobs to the top of the queue, the greatest
+ * number of jobs which could be moved is all the jobs
+ * that are in the queue. Pick a starting value which
+ * leaves plenty of room for all existing jobs.
+ */
+ touch_info.newtime = queue[0]->job_time - nitems - 5;
+ break;
+ default:
+ printf("\ninternal error in topq/bottomq processing.\n");
+ return;
+ }
+
+ movecnt = process_jobs(argc, argv, touch_jqe, &touch_info);
+
+ /*
+ * If any jobs were moved, then chmod the lock file to notify any
+ * active process for this queue that the queue has changed, so
+ * it will rescan the queue to find out the new job order.
+ */
+ if (movecnt == 0)
+ printf("\tqueue order unchanged\n");
+ else {
+ setres = set_qstate(SQS_QCHANGED, pp->lock_file);
+ if (setres < 0)
+ printf("\t* queue order changed for %s, but the\n"
+ "\t* attempt to set_qstate() failed [%d]!\n",
+ pp->printer, setres);
+ }
+
+ for (i = 0; i < nitems; i++)
+ free(queue[i]);
+ free(queue);
+ free_printer(pp);
+}
+
diff --git a/usr.sbin/lpr/lpd/Makefile b/usr.sbin/lpr/lpd/Makefile
new file mode 100644
index 0000000..a35212b
--- /dev/null
+++ b/usr.sbin/lpr/lpd/Makefile
@@ -0,0 +1,14 @@
+# From: @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+PROG= lpd
+MAN= lpd.8
+SRCS= lpd.c printjob.c recvjob.c lpdchar.c modes.c
+
+CFLAGS+= -I${.CURDIR}/../common_source
+
+WARNS?= 1
+
+LIBADD= lpr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/lpd/Makefile.depend b/usr.sbin/lpr/lpd/Makefile.depend
new file mode 100644
index 0000000..42a1eb5
--- /dev/null
+++ b/usr.sbin/lpr/lpd/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ usr.sbin/lpr/common_source \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/lpd/extern.h b/usr.sbin/lpr/lpd/extern.h
new file mode 100644
index 0000000..90eb0be
--- /dev/null
+++ b/usr.sbin/lpr/lpd/extern.h
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ *
+ * From: @(#)extern.h 8.1 (Berkeley) 6/6/93
+ * $FreeBSD$
+ */
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+
+extern char scnkey[][HEIGHT]; /* in lpdchar.c */
+extern int lflag; /* in lpd.c */
+
+struct printer;
+struct termios;
+
+__BEGIN_DECLS
+void printjob(struct printer *_pp);
+void startprinting(const char *_printer);
+void recvjob(const char *_printer);
+int msearch(char *_str, struct termios *_ip);
+__END_DECLS
diff --git a/usr.sbin/lpr/lpd/lpd.8 b/usr.sbin/lpr/lpd/lpd.8
new file mode 100644
index 0000000..a3d7aa9
--- /dev/null
+++ b/usr.sbin/lpr/lpd/lpd.8
@@ -0,0 +1,341 @@
+.\" 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.
+.\"
+.\" @(#)lpd.8 8.3 (Berkeley) 4/19/94
+.\" $FreeBSD$
+.\"
+.Dd June 6, 2001
+.Dt LPD 8
+.Os
+.Sh NAME
+.Nm lpd
+.Nd line printer spooler daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl cdlpsW46
+.Op Ar port#
+.Sh DESCRIPTION
+The
+.Nm
+utility
+is the line printer daemon (spool area handler) and is normally invoked
+at boot time from the
+.Xr rc 8
+file.
+It makes a single pass through the
+.Xr printcap 5
+file to find out about the existing printers and
+prints any files left after a crash.
+It then uses the system calls
+.Xr listen 2
+and
+.Xr accept 2
+to receive requests to print files in the queue,
+transfer files to the spooling area, display the queue,
+or remove jobs from the queue.
+In each case, it forks a child to handle
+the request so the parent can continue to listen for more requests.
+.Pp
+Available options:
+.Bl -tag -width Ds
+.It Fl c
+By default, if some remote host has a connection error while trying to
+send a print request to
+.Nm
+on a local host,
+.Nm
+will only send error message to that remote host.
+The
+.Fl c
+flag causes
+.Nm
+to also log all of those connection errors via
+.Xr syslog 3 .
+.It Fl d
+Turn on
+.Dv SO_DEBUG
+on the Internet listening socket (see
+.Xr setsockopt 2 ) .
+.It Fl l
+The
+.Fl l
+flag causes
+.Nm
+to log valid requests received from the network.
+This can be useful
+for debugging purposes.
+.It Fl p
+The
+.Fl p
+flag is a synonym for the
+.Fl s
+flag.
+It is being deprecated, and may be removed in a
+future version of
+.Nm .
+.It Fl s
+The
+.Fl s
+(secure) flag causes
+.Nm
+not to open an Internet listening socket.
+This means that
+.Nm
+will not accept any connections from any remote
+hosts, although it will still accept print requests
+from all local users.
+.It Fl W
+By default, the
+.Nm
+daemon will only accept connections which originate
+from a reserved-port (<1024) on the remote host.
+The
+.Fl W
+flag causes
+.Nm
+to accept connections coming from any port.
+This is can be useful when you want to accept print jobs
+from certain implementations of lpr written for Windows.
+.It Fl 4
+Inet only.
+.It Fl 6
+Inet6 only.
+.It Fl 46
+Inet and inet6 (default).
+.It Ar "port#"
+The Internet port number used to rendezvous
+with other processes is normally obtained with
+.Xr getservbyname 3
+but can be changed with the
+.Ar port#
+argument.
+.El
+.Pp
+Access control is provided by two means.
+First, all requests must come from
+one of the machines listed in the file
+.Pa /etc/hosts.equiv
+or
+.Pa /etc/hosts.lpd .
+Second, if the
+.Li rs
+capability is specified in the
+.Xr printcap 5
+entry for the printer being accessed,
+.Em lpr
+requests will only be honored for those users with accounts on the
+machine with the printer.
+.Pp
+The file
+.Em minfree
+in each spool directory contains the number of kilobytes to leave free
+so that the line printer queue will not completely fill the disk.
+The
+.Em minfree
+file can be edited with your favorite text editor.
+.Pp
+The daemon begins processing files
+after it has successfully set the lock for exclusive
+access (described a bit later),
+and scans the spool directory
+for files beginning with
+.Em cf .
+Lines in each
+.Em cf
+file specify files to be printed or non-printing actions to be
+performed.
+Each such line begins with a key character
+to specify what to do with the remainder of the line.
+.Bl -tag -width Ds
+.It J
+Job Name.
+String to be used for the job name on the burst page.
+.It C
+Classification.
+String to be used for the classification line
+on the burst page.
+.It L
+Literal.
+The line contains identification info from
+the password file and causes the banner page to be printed.
+.It T
+Title.
+String to be used as the title for
+.Xr pr 1 .
+.It H
+Host Name.
+Name of the machine where
+.Xr lpr 1
+was invoked.
+.It P
+Person.
+Login name of the person who invoked
+.Xr lpr 1 .
+This is used to verify ownership by
+.Xr lprm 1 .
+.It M
+Send mail to the specified user when the current print job completes.
+.It f
+Formatted File.
+Name of a file to print which is already formatted.
+.It l
+Like ``f'' but passes control characters and does not make page breaks.
+.It p
+Name of a file to print using
+.Xr pr 1
+as a filter.
+.It t
+Troff File.
+The file contains
+.Xr troff 1
+output (cat phototypesetter commands).
+.It n
+Ditroff File.
+The file contains device independent troff
+output.
+.It r
+DVI File.
+The file contains
+.Tn Tex l
+output
+DVI format from Stanford.
+.It g
+Graph File.
+The file contains data produced by
+.Xr plot 3 .
+.It c
+Cifplot File.
+The file contains data produced by
+.Em cifplot .
+.It v
+The file contains a raster image.
+.It r
+The file contains text data with
+FORTRAN carriage control characters.
+.It \&1
+Troff Font R.
+Name of the font file to use instead of the default.
+.It \&2
+Troff Font I.
+Name of the font file to use instead of the default.
+.It \&3
+Troff Font B.
+Name of the font file to use instead of the default.
+.It \&4
+Troff Font S.
+Name of the font file to use instead of the default.
+.It W
+Width.
+Changes the page width (in characters) used by
+.Xr pr 1
+and the text filters.
+.It I
+Indent.
+The number of characters to indent the output by (in ASCII).
+.It U
+Unlink.
+Name of file to remove upon completion of printing.
+.It N
+File name.
+The name of the file which is being printed, or a blank
+for the standard input (when
+.Xr lpr 1
+is invoked in a pipeline).
+.It Z
+Locale.
+String to be used as the locale for
+.Xr pr 1 .
+.El
+.Pp
+If a file cannot be opened, a message will be logged via
+.Xr syslog 3
+using the
+.Em LOG_LPR
+facility.
+The
+.Nm
+utility will try up to 20 times
+to reopen a file it expects to be there, after which it will
+skip the file to be printed.
+.Pp
+The
+.Nm
+utility uses
+.Xr flock 2
+to provide exclusive access to the lock file and to prevent multiple
+daemons from becoming active simultaneously.
+If the daemon should be killed
+or die unexpectedly, the lock file need not be removed.
+The lock file is kept in a readable
+.Tn ASCII
+form
+and contains two lines.
+The first is the process id of the daemon and the second is the control
+file name of the current job being printed.
+The second line is updated to
+reflect the current status of
+.Nm
+for the programs
+.Xr lpq 1
+and
+.Xr lprm 1 .
+.Sh FILES
+.Bl -tag -width "/var/spool/*/minfree" -compact
+.It Pa /etc/printcap
+printer description file
+.It Pa /var/spool/*
+spool directories
+.It Pa /var/spool/*/minfree
+minimum free space to leave
+.It Pa /dev/lp*
+line printer devices
+.It Pa /var/run/printer
+socket for local requests
+.It Pa /etc/hosts.equiv
+lists machine names allowed printer access
+.It Pa /etc/hosts.lpd
+lists machine names allowed printer access,
+but not under same administrative control.
+.El
+.Sh SEE ALSO
+.Xr lpq 1 ,
+.Xr lpr 1 ,
+.Xr lprm 1 ,
+.Xr setsockopt 2 ,
+.Xr syslog 3 ,
+.Xr hosts.lpd 5 ,
+.Xr printcap 5 ,
+.Xr chkprintcap 8 ,
+.Xr lpc 8 ,
+.Xr pac 8
+.Rs
+.%T "4.2 BSD Line Printer Spooler Manual"
+.Re
+.Sh HISTORY
+An
+.Nm
+daemon appeared in Version 6 AT&T UNIX.
diff --git a/usr.sbin/lpr/lpd/lpd.c b/usr.sbin/lpr/lpd/lpd.c
new file mode 100644
index 0000000..4aa49ca
--- /dev/null
+++ b/usr.sbin/lpr/lpd/lpd.c
@@ -0,0 +1,942 @@
+/*
+ * Copyright (c) 1983, 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) 1983, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)lpd.c 8.7 (Berkeley) 5/10/95";
+#endif /* not lint */
+#endif
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+/*
+ * lpd -- line printer daemon.
+ *
+ * Listen for a connection and perform the requested operation.
+ * Operations are:
+ * \1printer\n
+ * check the queue for jobs and print any found.
+ * \2printer\n
+ * receive a job from another machine and queue it.
+ * \3printer [users ...] [jobs ...]\n
+ * return the current state of the queue (short form).
+ * \4printer [users ...] [jobs ...]\n
+ * return the current state of the queue (long form).
+ * \5printer person [users ...] [jobs ...]\n
+ * remove jobs from the queue.
+ *
+ * Strategy to maintain protected spooling area:
+ * 1. Spooling area is writable only by daemon and spooling group
+ * 2. lpr runs setuid root and setgrp spooling group; it uses
+ * root to access any file it wants (verifying things before
+ * with an access call) and group id to know how it should
+ * set up ownership of files in the spooling area.
+ * 3. Files in spooling area are owned by root, group spooling
+ * group, with mode 660.
+ * 4. lpd, lpq and lprm run setuid daemon and setgrp spooling group to
+ * access files and printer. Users can't get to anything
+ * w/o help of lpq and lprm programs.
+ */
+
+#include <sys/param.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <netdb.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <signal.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <ctype.h>
+#include "lp.h"
+#include "lp.local.h"
+#include "pathnames.h"
+#include "extern.h"
+
+int lflag; /* log requests flag */
+int sflag; /* no incoming port flag */
+int from_remote; /* from remote socket */
+
+int main(int argc, char **_argv);
+static void reapchild(int _signo);
+static void mcleanup(int _signo);
+static void doit(void);
+static void startup(void);
+static void chkhost(struct sockaddr *_f, int _ch_opts);
+static int ckqueue(struct printer *_pp);
+static void fhosterr(int _ch_opts, char *_sysmsg, char *_usermsg);
+static int *socksetup(int _af, int _debuglvl);
+static void usage(void);
+
+/* XXX from libc/net/rcmd.c */
+extern int __ivaliduser_sa(FILE *, struct sockaddr *, socklen_t,
+ const char *, const char *);
+
+uid_t uid, euid;
+
+#define LPD_NOPORTCHK 0001 /* skip reserved-port check */
+#define LPD_LOGCONNERR 0002 /* (sys)log connection errors */
+#define LPD_ADDFROMLINE 0004 /* just used for fhosterr() */
+
+int
+main(int argc, char **argv)
+{
+ int ch_options, errs, f, funix, *finet, i, lfd, socket_debug;
+ fd_set defreadfds;
+ struct sockaddr_un un, fromunix;
+ struct sockaddr_storage frominet;
+ socklen_t fromlen;
+ sigset_t omask, nmask;
+ struct servent *sp, serv;
+ int inet_flag = 0, inet6_flag = 0;
+
+ euid = geteuid(); /* these shouldn't be different */
+ uid = getuid();
+
+ ch_options = 0;
+ socket_debug = 0;
+ gethostname(local_host, sizeof(local_host));
+
+ progname = "lpd";
+
+ if (euid != 0)
+ errx(EX_NOPERM,"must run as root");
+
+ errs = 0;
+ while ((i = getopt(argc, argv, "cdlpswW46")) != -1)
+ switch (i) {
+ case 'c':
+ /* log all kinds of connection-errors to syslog */
+ ch_options |= LPD_LOGCONNERR;
+ break;
+ case 'd':
+ socket_debug++;
+ break;
+ case 'l':
+ lflag++;
+ break;
+ case 'p': /* letter initially used for -s */
+ /*
+ * This will probably be removed with 5.0-release.
+ */
+ /* FALLTHROUGH */
+ case 's': /* secure (no inet) */
+ sflag++;
+ break;
+ case 'w': /* netbsd uses -w for maxwait */
+ /*
+ * This will be removed after the release of 4.4, as
+ * it conflicts with -w in netbsd's lpd. For now it
+ * is just a warning, so we won't suddenly break lpd
+ * for anyone who is currently using the option.
+ */
+ syslog(LOG_WARNING,
+ "NOTE: the -w option has been renamed -W");
+ syslog(LOG_WARNING,
+ "NOTE: please change your lpd config to use -W");
+ /* FALLTHROUGH */
+ case 'W':
+ /* allow connections coming from a non-reserved port */
+ /* (done by some lpr-implementations for MS-Windows) */
+ ch_options |= LPD_NOPORTCHK;
+ break;
+ case '4':
+ family = PF_INET;
+ inet_flag++;
+ break;
+ case '6':
+#ifdef INET6
+ family = PF_INET6;
+ inet6_flag++;
+#else
+ errx(EX_USAGE, "lpd compiled sans INET6 (IPv6 support)");
+#endif
+ break;
+ /*
+ * The following options are not in FreeBSD (yet?), but are
+ * listed here to "reserve" them, because the option-letters
+ * are used by either NetBSD or OpenBSD (as of July 2001).
+ */
+ case 'b': /* set bind-addr */
+ case 'n': /* set max num of children */
+ case 'r': /* allow 'of' for remote ptrs */
+ /* ...[not needed in freebsd] */
+ /* FALLTHROUGH */
+ default:
+ errs++;
+ }
+ if (inet_flag && inet6_flag)
+ family = PF_UNSPEC;
+ argc -= optind;
+ argv += optind;
+ if (errs)
+ usage();
+
+ if (argc == 1) {
+ if ((i = atoi(argv[0])) == 0)
+ usage();
+ if (i < 0 || i > USHRT_MAX)
+ errx(EX_USAGE, "port # %d is invalid", i);
+
+ serv.s_port = htons(i);
+ sp = &serv;
+ argc--;
+ } else {
+ sp = getservbyname("printer", "tcp");
+ if (sp == NULL)
+ errx(EX_OSFILE, "printer/tcp: unknown service");
+ }
+
+ if (argc != 0)
+ usage();
+
+ /*
+ * We run chkprintcap right away to catch any errors and blat them
+ * to stderr while we still have it open, rather than sending them
+ * to syslog and leaving the user wondering why lpd started and
+ * then stopped. There should probably be a command-line flag to
+ * ignore errors from chkprintcap.
+ */
+ {
+ pid_t pid;
+ int status;
+ pid = fork();
+ if (pid < 0) {
+ err(EX_OSERR, "cannot fork");
+ } else if (pid == 0) { /* child */
+ execl(_PATH_CHKPRINTCAP, _PATH_CHKPRINTCAP, (char *)0);
+ err(EX_OSERR, "cannot execute %s", _PATH_CHKPRINTCAP);
+ }
+ if (waitpid(pid, &status, 0) < 0) {
+ err(EX_OSERR, "cannot wait");
+ }
+ if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
+ errx(EX_OSFILE, "%d errors in printcap file, exiting",
+ WEXITSTATUS(status));
+ }
+
+#ifndef DEBUG
+ /*
+ * Set up standard environment by detaching from the parent.
+ */
+ daemon(0, 0);
+#endif
+
+ openlog("lpd", LOG_PID, LOG_LPR);
+ syslog(LOG_INFO, "lpd startup: logging=%d%s%s", lflag,
+ socket_debug ? " dbg" : "", sflag ? " net-secure" : "");
+ (void) umask(0);
+ /*
+ * NB: This depends on O_NONBLOCK semantics doing the right thing;
+ * i.e., applying only to the O_EXLOCK and not to the rest of the
+ * open/creation. As of 1997-12-02, this is the case for commonly-
+ * used filesystems. There are other places in this code which
+ * make the same assumption.
+ */
+ lfd = open(_PATH_MASTERLOCK, O_WRONLY|O_CREAT|O_EXLOCK|O_NONBLOCK,
+ LOCK_FILE_MODE);
+ if (lfd < 0) {
+ if (errno == EWOULDBLOCK) /* active daemon present */
+ exit(0);
+ syslog(LOG_ERR, "%s: %m", _PATH_MASTERLOCK);
+ exit(1);
+ }
+ fcntl(lfd, F_SETFL, 0); /* turn off non-blocking mode */
+ ftruncate(lfd, 0);
+ /*
+ * write process id for others to know
+ */
+ sprintf(line, "%u\n", getpid());
+ f = strlen(line);
+ if (write(lfd, line, f) != f) {
+ syslog(LOG_ERR, "%s: %m", _PATH_MASTERLOCK);
+ exit(1);
+ }
+ signal(SIGCHLD, reapchild);
+ /*
+ * Restart all the printers.
+ */
+ startup();
+ (void) unlink(_PATH_SOCKETNAME);
+ funix = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (funix < 0) {
+ syslog(LOG_ERR, "socket: %m");
+ exit(1);
+ }
+
+ sigemptyset(&nmask);
+ sigaddset(&nmask, SIGHUP);
+ sigaddset(&nmask, SIGINT);
+ sigaddset(&nmask, SIGQUIT);
+ sigaddset(&nmask, SIGTERM);
+ sigprocmask(SIG_BLOCK, &nmask, &omask);
+
+ (void) umask(07);
+ signal(SIGHUP, mcleanup);
+ signal(SIGINT, mcleanup);
+ signal(SIGQUIT, mcleanup);
+ signal(SIGTERM, mcleanup);
+ memset(&un, 0, sizeof(un));
+ un.sun_family = AF_UNIX;
+ strcpy(un.sun_path, _PATH_SOCKETNAME);
+#ifndef SUN_LEN
+#define SUN_LEN(unp) (strlen((unp)->sun_path) + 2)
+#endif
+ if (bind(funix, (struct sockaddr *)&un, SUN_LEN(&un)) < 0) {
+ syslog(LOG_ERR, "ubind: %m");
+ exit(1);
+ }
+ (void) umask(0);
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *)0);
+ FD_ZERO(&defreadfds);
+ FD_SET(funix, &defreadfds);
+ listen(funix, 5);
+ if (sflag == 0) {
+ finet = socksetup(family, socket_debug);
+ } else
+ finet = NULL; /* pretend we couldn't open TCP socket. */
+ if (finet) {
+ for (i = 1; i <= *finet; i++) {
+ FD_SET(finet[i], &defreadfds);
+ listen(finet[i], 5);
+ }
+ }
+ /*
+ * Main loop: accept, do a request, continue.
+ */
+ memset(&frominet, 0, sizeof(frominet));
+ memset(&fromunix, 0, sizeof(fromunix));
+ if (lflag)
+ syslog(LOG_INFO, "lpd startup: ready to accept requests");
+ /*
+ * XXX - should be redone for multi-protocol
+ */
+ for (;;) {
+ int domain, nfds, s;
+ fd_set readfds;
+
+ FD_COPY(&defreadfds, &readfds);
+ nfds = select(20, &readfds, 0, 0, 0);
+ if (nfds <= 0) {
+ if (nfds < 0 && errno != EINTR)
+ syslog(LOG_WARNING, "select: %m");
+ continue;
+ }
+ domain = -1; /* avoid compile-time warning */
+ s = -1; /* avoid compile-time warning */
+ if (FD_ISSET(funix, &readfds)) {
+ domain = AF_UNIX, fromlen = sizeof(fromunix);
+ s = accept(funix,
+ (struct sockaddr *)&fromunix, &fromlen);
+ } else {
+ for (i = 1; i <= *finet; i++)
+ if (FD_ISSET(finet[i], &readfds)) {
+ domain = AF_INET;
+ fromlen = sizeof(frominet);
+ s = accept(finet[i],
+ (struct sockaddr *)&frominet,
+ &fromlen);
+ }
+ }
+ if (s < 0) {
+ if (errno != EINTR)
+ syslog(LOG_WARNING, "accept: %m");
+ continue;
+ }
+ if (fork() == 0) {
+ /*
+ * Note that printjob() also plays around with
+ * signal-handling routines, and may need to be
+ * changed when making changes to signal-handling.
+ */
+ signal(SIGCHLD, SIG_DFL);
+ signal(SIGHUP, SIG_IGN);
+ signal(SIGINT, SIG_IGN);
+ signal(SIGQUIT, SIG_IGN);
+ signal(SIGTERM, SIG_IGN);
+ (void) close(funix);
+ if (sflag == 0 && finet) {
+ for (i = 1; i <= *finet; i++)
+ (void)close(finet[i]);
+ }
+ dup2(s, STDOUT_FILENO);
+ (void) close(s);
+ if (domain == AF_INET) {
+ /* for both AF_INET and AF_INET6 */
+ from_remote = 1;
+ chkhost((struct sockaddr *)&frominet,
+ ch_options);
+ } else
+ from_remote = 0;
+ doit();
+ exit(0);
+ }
+ (void) close(s);
+ }
+}
+
+static void
+reapchild(int signo __unused)
+{
+ int status;
+
+ while (wait3(&status, WNOHANG, 0) > 0)
+ ;
+}
+
+static void
+mcleanup(int signo)
+{
+ /*
+ * XXX syslog(3) is not signal-safe.
+ */
+ if (lflag) {
+ if (signo)
+ syslog(LOG_INFO, "exiting on signal %d", signo);
+ else
+ syslog(LOG_INFO, "exiting");
+ }
+ unlink(_PATH_SOCKETNAME);
+ exit(0);
+}
+
+/*
+ * Stuff for handling job specifications
+ */
+char *user[MAXUSERS]; /* users to process */
+int users; /* # of users in user array */
+int requ[MAXREQUESTS]; /* job number of spool entries */
+int requests; /* # of spool requests */
+char *person; /* name of person doing lprm */
+
+ /* buffer to hold the client's machine-name */
+static char frombuf[MAXHOSTNAMELEN];
+char cbuf[BUFSIZ]; /* command line buffer */
+const char *cmdnames[] = {
+ "null",
+ "printjob",
+ "recvjob",
+ "displayq short",
+ "displayq long",
+ "rmjob"
+};
+
+static void
+doit(void)
+{
+ char *cp, *printer;
+ int n;
+ int status;
+ struct printer myprinter, *pp = &myprinter;
+
+ init_printer(&myprinter);
+
+ for (;;) {
+ cp = cbuf;
+ do {
+ if (cp >= &cbuf[sizeof(cbuf) - 1])
+ fatal(0, "Command line too long");
+ if ((n = read(STDOUT_FILENO, cp, 1)) != 1) {
+ if (n < 0)
+ fatal(0, "Lost connection");
+ return;
+ }
+ } while (*cp++ != '\n');
+ *--cp = '\0';
+ cp = cbuf;
+ if (lflag) {
+ if (*cp >= '\1' && *cp <= '\5')
+ syslog(LOG_INFO, "%s requests %s %s",
+ from_host, cmdnames[(u_char)*cp], cp+1);
+ else
+ syslog(LOG_INFO, "bad request (%d) from %s",
+ *cp, from_host);
+ }
+ switch (*cp++) {
+ case CMD_CHECK_QUE: /* check the queue, print any jobs there */
+ startprinting(cp);
+ break;
+ case CMD_TAKE_THIS: /* receive files to be queued */
+ if (!from_remote) {
+ syslog(LOG_INFO, "illegal request (%d)", *cp);
+ exit(1);
+ }
+ recvjob(cp);
+ break;
+ case CMD_SHOWQ_SHORT: /* display the queue (short form) */
+ case CMD_SHOWQ_LONG: /* display the queue (long form) */
+ /* XXX - this all needs to be redone. */
+ printer = cp;
+ while (*cp) {
+ if (*cp != ' ') {
+ cp++;
+ continue;
+ }
+ *cp++ = '\0';
+ while (isspace(*cp))
+ cp++;
+ if (*cp == '\0')
+ break;
+ if (isdigit(*cp)) {
+ if (requests >= MAXREQUESTS)
+ fatal(0, "Too many requests");
+ requ[requests++] = atoi(cp);
+ } else {
+ if (users >= MAXUSERS)
+ fatal(0, "Too many users");
+ user[users++] = cp;
+ }
+ }
+ status = getprintcap(printer, pp);
+ if (status < 0)
+ fatal(pp, "%s", pcaperr(status));
+ displayq(pp, cbuf[0] == CMD_SHOWQ_LONG);
+ exit(0);
+ case CMD_RMJOB: /* remove a job from the queue */
+ if (!from_remote) {
+ syslog(LOG_INFO, "illegal request (%d)", *cp);
+ exit(1);
+ }
+ printer = cp;
+ while (*cp && *cp != ' ')
+ cp++;
+ if (!*cp)
+ break;
+ *cp++ = '\0';
+ person = cp;
+ while (*cp) {
+ if (*cp != ' ') {
+ cp++;
+ continue;
+ }
+ *cp++ = '\0';
+ while (isspace(*cp))
+ cp++;
+ if (*cp == '\0')
+ break;
+ if (isdigit(*cp)) {
+ if (requests >= MAXREQUESTS)
+ fatal(0, "Too many requests");
+ requ[requests++] = atoi(cp);
+ } else {
+ if (users >= MAXUSERS)
+ fatal(0, "Too many users");
+ user[users++] = cp;
+ }
+ }
+ rmjob(printer);
+ break;
+ }
+ fatal(0, "Illegal service request");
+ }
+}
+
+/*
+ * Make a pass through the printcap database and start printing any
+ * files left from the last time the machine went down.
+ */
+static void
+startup(void)
+{
+ int pid, status, more;
+ struct printer myprinter, *pp = &myprinter;
+
+ more = firstprinter(pp, &status);
+ if (status)
+ goto errloop;
+ while (more) {
+ if (ckqueue(pp) <= 0) {
+ goto next;
+ }
+ if (lflag)
+ syslog(LOG_INFO, "lpd startup: work for %s",
+ pp->printer);
+ if ((pid = fork()) < 0) {
+ syslog(LOG_WARNING, "lpd startup: cannot fork for %s",
+ pp->printer);
+ mcleanup(0);
+ }
+ if (pid == 0) {
+ lastprinter();
+ printjob(pp);
+ /* NOTREACHED */
+ }
+ do {
+next:
+ more = nextprinter(pp, &status);
+errloop:
+ if (status)
+ syslog(LOG_WARNING,
+ "lpd startup: printcap entry for %s has errors, skipping",
+ pp->printer ? pp->printer : "<noname?>");
+ } while (more && status);
+ }
+}
+
+/*
+ * Make sure there's some work to do before forking off a child
+ */
+static int
+ckqueue(struct printer *pp)
+{
+ register struct dirent *d;
+ DIR *dirp;
+ char *spooldir;
+
+ spooldir = pp->spool_dir;
+ if ((dirp = opendir(spooldir)) == NULL)
+ return (-1);
+ while ((d = readdir(dirp)) != NULL) {
+ if (d->d_name[0] != 'c' || d->d_name[1] != 'f')
+ continue; /* daemon control files only */
+ closedir(dirp);
+ return (1); /* found something */
+ }
+ closedir(dirp);
+ return (0);
+}
+
+#define DUMMY ":nobody::"
+
+/*
+ * Check to see if the host connecting to this host has access to any
+ * lpd services on this host.
+ */
+static void
+chkhost(struct sockaddr *f, int ch_opts)
+{
+ struct addrinfo hints, *res, *r;
+ register FILE *hostf;
+ char hostbuf[NI_MAXHOST], ip[NI_MAXHOST];
+ char serv[NI_MAXSERV];
+ char *syserr, *usererr;
+ int error, errsav, fpass, good, wantsl;
+
+ wantsl = 0;
+ if (ch_opts & LPD_LOGCONNERR)
+ wantsl = 1; /* also syslog the errors */
+
+ from_host = ".na.";
+
+ /* Need real hostname for temporary filenames */
+ error = getnameinfo(f, f->sa_len, hostbuf, sizeof(hostbuf), NULL, 0,
+ NI_NAMEREQD);
+ if (error) {
+ errsav = error;
+ error = getnameinfo(f, f->sa_len, hostbuf, sizeof(hostbuf),
+ NULL, 0, NI_NUMERICHOST);
+ if (error) {
+ asprintf(&syserr,
+ "can not determine hostname for remote host (%d,%d)",
+ errsav, error);
+ asprintf(&usererr,
+ "Host name for your address is not known");
+ fhosterr(ch_opts, syserr, usererr);
+ /* NOTREACHED */
+ }
+ asprintf(&syserr,
+ "Host name for remote host (%s) not known (%d)",
+ hostbuf, errsav);
+ asprintf(&usererr,
+ "Host name for your address (%s) is not known",
+ hostbuf);
+ fhosterr(ch_opts, syserr, usererr);
+ /* NOTREACHED */
+ }
+
+ strlcpy(frombuf, hostbuf, sizeof(frombuf));
+ from_host = frombuf;
+ ch_opts |= LPD_ADDFROMLINE;
+
+ /* Need address in stringform for comparison (no DNS lookup here) */
+ error = getnameinfo(f, f->sa_len, hostbuf, sizeof(hostbuf), NULL, 0,
+ NI_NUMERICHOST);
+ if (error) {
+ asprintf(&syserr, "Cannot print IP address (error %d)",
+ error);
+ asprintf(&usererr, "Cannot print IP address for your host");
+ fhosterr(ch_opts, syserr, usererr);
+ /* NOTREACHED */
+ }
+ from_ip = strdup(hostbuf);
+
+ /* Reject numeric addresses */
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_DGRAM; /*dummy*/
+ hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
+ if (getaddrinfo(from_host, NULL, &hints, &res) == 0) {
+ freeaddrinfo(res);
+ /* This syslog message already includes from_host */
+ ch_opts &= ~LPD_ADDFROMLINE;
+ asprintf(&syserr, "reverse lookup results in non-FQDN %s",
+ from_host);
+ /* same message to both syslog and remote user */
+ fhosterr(ch_opts, syserr, syserr);
+ /* NOTREACHED */
+ }
+
+ /* Check for spoof, ala rlogind */
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_DGRAM; /*dummy*/
+ error = getaddrinfo(from_host, NULL, &hints, &res);
+ if (error) {
+ asprintf(&syserr, "dns lookup for address %s failed: %s",
+ from_ip, gai_strerror(error));
+ asprintf(&usererr, "hostname for your address (%s) unknown: %s",
+ from_ip, gai_strerror(error));
+ fhosterr(ch_opts, syserr, usererr);
+ /* NOTREACHED */
+ }
+ good = 0;
+ for (r = res; good == 0 && r; r = r->ai_next) {
+ error = getnameinfo(r->ai_addr, r->ai_addrlen, ip, sizeof(ip),
+ NULL, 0, NI_NUMERICHOST);
+ if (!error && !strcmp(from_ip, ip))
+ good = 1;
+ }
+ if (res)
+ freeaddrinfo(res);
+ if (good == 0) {
+ asprintf(&syserr, "address for remote host (%s) not matched",
+ from_ip);
+ asprintf(&usererr,
+ "address for your hostname (%s) not matched", from_ip);
+ fhosterr(ch_opts, syserr, usererr);
+ /* NOTREACHED */
+ }
+
+ fpass = 1;
+ hostf = fopen(_PATH_HOSTSEQUIV, "r");
+again:
+ if (hostf) {
+ if (__ivaliduser_sa(hostf, f, f->sa_len, DUMMY, DUMMY) == 0) {
+ (void) fclose(hostf);
+ goto foundhost;
+ }
+ (void) fclose(hostf);
+ }
+ if (fpass == 1) {
+ fpass = 2;
+ hostf = fopen(_PATH_HOSTSLPD, "r");
+ goto again;
+ }
+ /* This syslog message already includes from_host */
+ ch_opts &= ~LPD_ADDFROMLINE;
+ asprintf(&syserr, "refused connection from %s, sip=%s", from_host,
+ from_ip);
+ asprintf(&usererr,
+ "Print-services are not available to your host (%s).", from_host);
+ fhosterr(ch_opts, syserr, usererr);
+ /* NOTREACHED */
+
+foundhost:
+ if (ch_opts & LPD_NOPORTCHK)
+ return; /* skip the reserved-port check */
+
+ error = getnameinfo(f, f->sa_len, NULL, 0, serv, sizeof(serv),
+ NI_NUMERICSERV);
+ if (error) {
+ /* same message to both syslog and remote user */
+ asprintf(&syserr, "malformed from-address (%d)", error);
+ fhosterr(ch_opts, syserr, syserr);
+ /* NOTREACHED */
+ }
+
+ if (atoi(serv) >= IPPORT_RESERVED) {
+ /* same message to both syslog and remote user */
+ asprintf(&syserr, "connected from invalid port (%s)", serv);
+ fhosterr(ch_opts, syserr, syserr);
+ /* NOTREACHED */
+ }
+}
+
+/*
+ * Handle fatal errors in chkhost. The first message will optionally be
+ * sent to syslog, the second one is sent to the connecting host.
+ *
+ * The idea is that the syslog message is meant for an administrator of a
+ * print server (the host receiving connections), while the usermsg is meant
+ * for a remote user who may or may not be clueful, and may or may not be
+ * doing something nefarious. Some remote users (eg, MS-Windows...) may not
+ * even see whatever message is sent, which is why there's the option to
+ * start 'lpd' with the connection-errors also sent to syslog.
+ *
+ * Given that hostnames can theoretically be fairly long (well, over 250
+ * bytes), it would probably be helpful to have the 'from_host' field at
+ * the end of any error messages which include that info.
+ *
+ * These are Fatal host-connection errors, so this routine does not return.
+ */
+static void
+fhosterr(int ch_opts, char *sysmsg, char *usermsg)
+{
+
+ /*
+ * If lpd was started up to print connection errors, then write
+ * the syslog message before the user message.
+ * And for many of the syslog messages, it is helpful to first
+ * write the from_host (if it is known) as a separate syslog
+ * message, since the hostname may be so long.
+ */
+ if (ch_opts & LPD_LOGCONNERR) {
+ if (ch_opts & LPD_ADDFROMLINE) {
+ syslog(LOG_WARNING, "for connection from %s:", from_host);
+ }
+ syslog(LOG_WARNING, "%s", sysmsg);
+ }
+
+ /*
+ * Now send the error message to the remote host which is trying
+ * to make the connection.
+ */
+ printf("%s [@%s]: %s\n", progname, local_host, usermsg);
+ fflush(stdout);
+
+ /*
+ * Add a minimal delay before exiting (and disconnecting from the
+ * sending-host). This is just in case that machine responds by
+ * INSTANTLY retrying (and instantly re-failing...). This may also
+ * give the other side more time to read the error message.
+ */
+ sleep(2); /* a paranoid throttling measure */
+ exit(1);
+}
+
+/* setup server socket for specified address family */
+/* if af is PF_UNSPEC more than one socket may be returned */
+/* the returned list is dynamically allocated, so caller needs to free it */
+static int *
+socksetup(int af, int debuglvl)
+{
+ struct addrinfo hints, *res, *r;
+ int error, maxs, *s, *socks;
+ const int on = 1;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = af;
+ hints.ai_socktype = SOCK_STREAM;
+ error = getaddrinfo(NULL, "printer", &hints, &res);
+ if (error) {
+ syslog(LOG_ERR, "%s", gai_strerror(error));
+ mcleanup(0);
+ }
+
+ /* Count max number of sockets we may open */
+ for (maxs = 0, r = res; r; r = r->ai_next, maxs++)
+ ;
+ socks = malloc((maxs + 1) * sizeof(int));
+ if (!socks) {
+ syslog(LOG_ERR, "couldn't allocate memory for sockets");
+ mcleanup(0);
+ }
+
+ *socks = 0; /* num of sockets counter at start of array */
+ s = socks + 1;
+ for (r = res; r; r = r->ai_next) {
+ *s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
+ if (*s < 0) {
+ syslog(LOG_DEBUG, "socket(): %m");
+ continue;
+ }
+ if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))
+ < 0) {
+ syslog(LOG_ERR, "setsockopt(SO_REUSEADDR): %m");
+ close(*s);
+ continue;
+ }
+ if (debuglvl)
+ if (setsockopt(*s, SOL_SOCKET, SO_DEBUG, &debuglvl,
+ sizeof(debuglvl)) < 0) {
+ syslog(LOG_ERR, "setsockopt (SO_DEBUG): %m");
+ close(*s);
+ continue;
+ }
+ if (r->ai_family == AF_INET6) {
+ if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY,
+ &on, sizeof(on)) < 0) {
+ syslog(LOG_ERR,
+ "setsockopt (IPV6_V6ONLY): %m");
+ close(*s);
+ continue;
+ }
+ }
+ if (bind(*s, r->ai_addr, r->ai_addrlen) < 0) {
+ syslog(LOG_DEBUG, "bind(): %m");
+ close(*s);
+ continue;
+ }
+ (*socks)++;
+ s++;
+ }
+
+ if (res)
+ freeaddrinfo(res);
+
+ if (*socks == 0) {
+ syslog(LOG_ERR, "Couldn't bind to any socket");
+ free(socks);
+ mcleanup(0);
+ }
+ return(socks);
+}
+
+static void
+usage(void)
+{
+#ifdef INET6
+ fprintf(stderr, "usage: lpd [-cdlsW46] [port#]\n");
+#else
+ fprintf(stderr, "usage: lpd [-cdlsW] [port#]\n");
+#endif
+ exit(EX_USAGE);
+}
diff --git a/usr.sbin/lpr/lpd/lpdchar.c b/usr.sbin/lpr/lpd/lpdchar.c
new file mode 100644
index 0000000..cb9f355
--- /dev/null
+++ b/usr.sbin/lpr/lpd/lpdchar.c
@@ -0,0 +1,1068 @@
+/*
+ * 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 char sccsid[] = "@(#)lpdchar.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+/*
+ * Character set for line printer daemon
+ */
+#include "lp.local.h"
+#include "extern.h"
+
+#define c_______ 0
+#define c______1 01
+#define c_____1_ 02
+#define c____1__ 04
+#define c____11_ 06
+#define c___1___ 010
+#define c___1__1 011
+#define c___1_1_ 012
+#define c___11__ 014
+#define c__1____ 020
+#define c__1__1_ 022
+#define c__1_1__ 024
+#define c__11___ 030
+#define c__111__ 034
+#define c__111_1 035
+#define c__1111_ 036
+#define c__11111 037
+#define c_1_____ 040
+#define c_1____1 041
+#define c_1___1_ 042
+#define c_1__1__ 044
+#define c_1_1___ 050
+#define c_1_1__1 051
+#define c_1_1_1_ 052
+#define c_11____ 060
+#define c_11_11_ 066
+#define c_111___ 070
+#define c_111__1 071
+#define c_111_1_ 072
+#define c_1111__ 074
+#define c_1111_1 075
+#define c_11111_ 076
+#define c_111111 077
+#define c1______ 0100
+#define c1_____1 0101
+#define c1____1_ 0102
+#define c1____11 0103
+#define c1___1__ 0104
+#define c1___1_1 0105
+#define c1___11_ 0106
+#define c1__1___ 0110
+#define c1__1__1 0111
+#define c1__11_1 0115
+#define c1__1111 0117
+#define c1_1____ 0120
+#define c1_1___1 0121
+#define c1_1_1_1 0125
+#define c1_1_11_ 0126
+#define c1_111__ 0134
+#define c1_1111_ 0136
+#define c11____1 0141
+#define c11___1_ 0142
+#define c11___11 0143
+#define c11_1___ 0150
+#define c11_1__1 0151
+#define c111_11_ 0166
+#define c1111___ 0170
+#define c11111__ 0174
+#define c111111_ 0176
+#define c1111111 0177
+
+char scnkey[][HEIGHT] = /* this is relatively easy to modify */
+ /* just look: */
+{
+ { c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______ }, /* */
+
+ { c__11___,
+ c__11___,
+ c__11___,
+ c__11___,
+ c__11___,
+ c_______,
+ c_______,
+ c__11___,
+ c__11___ }, /* ! */
+
+ { c_1__1__,
+ c_1__1__,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______ }, /* " */
+
+ { c_______,
+ c__1_1__,
+ c__1_1__,
+ c1111111,
+ c__1_1__,
+ c1111111,
+ c__1_1__,
+ c__1_1__,
+ c_______ }, /* # */
+
+ { c___1___,
+ c_11111_,
+ c1__1__1,
+ c1__1___,
+ c_11111_,
+ c___1__1,
+ c1__1__1,
+ c_11111_,
+ c___1___ }, /* $ */
+
+ { c_1_____,
+ c1_1___1,
+ c_1___1_,
+ c____1__,
+ c___1___,
+ c__1____,
+ c_1___1_,
+ c1___1_1,
+ c_____1_ }, /* % */
+
+ { c_11____,
+ c1__1___,
+ c1___1__,
+ c_1_1___,
+ c__1____,
+ c_1_1__1,
+ c1___11_,
+ c1___11_,
+ c_111__1 }, /* & */
+
+ { c___11__,
+ c___11__,
+ c___1___,
+ c__1____,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______ }, /* ' */
+
+ { c____1__,
+ c___1___,
+ c__1____,
+ c__1____,
+ c__1____,
+ c__1____,
+ c__1____,
+ c___1___,
+ c____1__ }, /* ( */
+
+ { c__1____,
+ c___1___,
+ c____1__,
+ c____1__,
+ c____1__,
+ c____1__,
+ c____1__,
+ c___1___,
+ c__1____ }, /* ) */
+
+ { c_______,
+ c___1___,
+ c1__1__1,
+ c_1_1_1_,
+ c__111__,
+ c_1_1_1_,
+ c1__1__1,
+ c___1___,
+ c_______ }, /* * */
+
+ { c_______,
+ c___1___,
+ c___1___,
+ c___1___,
+ c1111111,
+ c___1___,
+ c___1___,
+ c___1___,
+ c_______ }, /* + */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_______,
+ c__11___,
+ c__11___,
+ c__1____,
+ c_1_____,
+ c_______ }, /* , */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_______,
+ c1111111,
+ c_______,
+ c_______,
+ c_______,
+ c_______ }, /* - */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c__11___,
+ c__11___ }, /* . */
+
+ { c_______,
+ c______1,
+ c_____1_,
+ c____1__,
+ c___1___,
+ c__1____,
+ c_1_____,
+ c1______,
+ c_______ }, /* / */
+
+ { c_11111_,
+ c1_____1,
+ c1____11,
+ c1___1_1,
+ c1__1__1,
+ c1_1___1,
+ c11____1,
+ c1_____1,
+ c_11111_ }, /* 0 */
+
+ { c___1___,
+ c__11___,
+ c_1_1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c_11111_ }, /* 1 */
+
+ { c_11111_,
+ c1_____1,
+ c______1,
+ c_____1_,
+ c__111__,
+ c_1_____,
+ c1______,
+ c1______,
+ c1111111 }, /* 2 */
+
+ { c_11111_,
+ c1_____1,
+ c______1,
+ c______1,
+ c__1111_,
+ c______1,
+ c______1,
+ c1_____1,
+ c_11111_ }, /* 3 */
+
+ { c_____1_,
+ c____11_,
+ c___1_1_,
+ c__1__1_,
+ c_1___1_,
+ c1____1_,
+ c1111111,
+ c_____1_,
+ c_____1_ }, /* 4 */
+
+ { c1111111,
+ c1______,
+ c1______,
+ c11111__,
+ c_____1_,
+ c______1,
+ c______1,
+ c1____1_,
+ c_1111__ }, /* 5 */
+
+ { c__1111_,
+ c_1_____,
+ c1______,
+ c1______,
+ c1_1111_,
+ c11____1,
+ c1_____1,
+ c1_____1,
+ c_11111_ }, /* 6 */
+
+ { c1111111,
+ c1_____1,
+ c_____1_,
+ c____1__,
+ c___1___,
+ c__1____,
+ c__1____,
+ c__1____,
+ c__1____ }, /* 7 */
+
+ { c_11111_,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c_11111_,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c_11111_ }, /* 8 */
+
+ { c_11111_,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c_111111,
+ c______1,
+ c______1,
+ c1_____1,
+ c_1111__ }, /* 9 */
+
+ { c_______,
+ c_______,
+ c_______,
+ c__11___,
+ c__11___,
+ c_______,
+ c_______,
+ c__11___,
+ c__11___ }, /* : */
+
+
+ { c__11___,
+ c__11___,
+ c_______,
+ c_______,
+ c__11___,
+ c__11___,
+ c__1____,
+ c_1_____,
+ c_______ }, /* ; */
+
+ { c____1__,
+ c___1___,
+ c__1____,
+ c_1_____,
+ c1______,
+ c_1_____,
+ c__1____,
+ c___1___,
+ c____1__ }, /* < */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1111111,
+ c_______,
+ c1111111,
+ c_______,
+ c_______,
+ c_______ }, /* = */
+
+ { c__1____,
+ c___1___,
+ c____1__,
+ c_____1_,
+ c______1,
+ c_____1_,
+ c____1__,
+ c___1___,
+ c__1____ }, /* > */
+
+ { c__1111_,
+ c_1____1,
+ c_1____1,
+ c______1,
+ c____11_,
+ c___1___,
+ c___1___,
+ c_______,
+ c___1___ }, /* ? */
+
+ { c__1111_,
+ c_1____1,
+ c1__11_1,
+ c1_1_1_1,
+ c1_1_1_1,
+ c1_1111_,
+ c1______,
+ c_1____1,
+ c__1111_ }, /* @ */
+
+ { c__111__,
+ c_1___1_,
+ c1_____1,
+ c1_____1,
+ c1111111,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1 }, /* A */
+
+ { c111111_,
+ c_1____1,
+ c_1____1,
+ c_1____1,
+ c_11111_,
+ c_1____1,
+ c_1____1,
+ c_1____1,
+ c111111_ }, /* B */
+
+ { c__1111_,
+ c_1____1,
+ c1______,
+ c1______,
+ c1______,
+ c1______,
+ c1______,
+ c_1____1,
+ c__1111_ }, /* C */
+
+ { c11111__,
+ c_1___1_,
+ c_1____1,
+ c_1____1,
+ c_1____1,
+ c_1____1,
+ c_1____1,
+ c_1___1_,
+ c11111__ }, /* D */
+
+ { c1111111,
+ c1______,
+ c1______,
+ c1______,
+ c111111_,
+ c1______,
+ c1______,
+ c1______,
+ c1111111 }, /* E */
+
+ { c1111111,
+ c1______,
+ c1______,
+ c1______,
+ c111111_,
+ c1______,
+ c1______,
+ c1______,
+ c1______ }, /* F */
+
+ { c__1111_,
+ c_1____1,
+ c1______,
+ c1______,
+ c1______,
+ c1__1111,
+ c1_____1,
+ c_1____1,
+ c__1111_ }, /* G */
+
+ { c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1111111,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1 }, /* H */
+
+ { c_11111_,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c_11111_ }, /* I */
+
+ { c__11111,
+ c____1__,
+ c____1__,
+ c____1__,
+ c____1__,
+ c____1__,
+ c____1__,
+ c1___1__,
+ c_111___ }, /* J */
+
+ { c1_____1,
+ c1____1_,
+ c1___1__,
+ c1__1___,
+ c1_1____,
+ c11_1___,
+ c1___1__,
+ c1____1_,
+ c1_____1 }, /* K */
+
+ { c1______,
+ c1______,
+ c1______,
+ c1______,
+ c1______,
+ c1______,
+ c1______,
+ c1______,
+ c1111111 }, /* L */
+
+ { c1_____1,
+ c11___11,
+ c1_1_1_1,
+ c1__1__1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1 }, /* M */
+
+ { c1_____1,
+ c11____1,
+ c1_1___1,
+ c1__1__1,
+ c1___1_1,
+ c1____11,
+ c1_____1,
+ c1_____1,
+ c1_____1 }, /* N */
+
+ { c__111__,
+ c_1___1_,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c_1___1_,
+ c__111__ }, /* O */
+
+ { c111111_,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c111111_,
+ c1______,
+ c1______,
+ c1______,
+ c1______ }, /* P */
+
+ { c__111__,
+ c_1___1_,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1__1__1,
+ c1___1_1,
+ c_1___1_,
+ c__111_1 }, /* Q */
+
+ { c111111_,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c111111_,
+ c1__1___,
+ c1___1__,
+ c1____1_,
+ c1_____1 }, /* R */
+
+ { c_11111_,
+ c1_____1,
+ c1______,
+ c1______,
+ c_11111_,
+ c______1,
+ c______1,
+ c1_____1,
+ c_11111_ }, /* S */
+
+ { c1111111,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___ }, /* T */
+
+ { c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c_11111_ }, /* U */
+
+ { c1_____1,
+ c1_____1,
+ c1_____1,
+ c_1___1_,
+ c_1___1_,
+ c__1_1__,
+ c__1_1__,
+ c___1___,
+ c___1___ }, /* V */
+
+ { c1_____1,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c1__1__1,
+ c1__1__1,
+ c1_1_1_1,
+ c11___11,
+ c1_____1 }, /* W */
+
+ { c1_____1,
+ c1_____1,
+ c_1___1_,
+ c__1_1__,
+ c___1___,
+ c__1_1__,
+ c_1___1_,
+ c1_____1,
+ c1_____1 }, /* X */
+
+ { c1_____1,
+ c1_____1,
+ c_1___1_,
+ c__1_1__,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___ }, /* Y */
+
+ { c1111111,
+ c______1,
+ c_____1_,
+ c____1__,
+ c___1___,
+ c__1____,
+ c_1_____,
+ c1______,
+ c1111111 }, /* Z */
+
+ { c_1111__,
+ c_1_____,
+ c_1_____,
+ c_1_____,
+ c_1_____,
+ c_1_____,
+ c_1_____,
+ c_1_____,
+ c_1111__ }, /* [ */
+
+ { c_______,
+ c1______,
+ c_1_____,
+ c__1____,
+ c___1___,
+ c____1__,
+ c_____1_,
+ c______1,
+ c_______ }, /* \ */
+
+ { c__1111_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c__1111_ }, /* ] */
+
+ { c___1___,
+ c__1_1__,
+ c_1___1_,
+ c1_____1,
+ c_______,
+ c_______,
+ c_______,
+ c_______ }, /* ^ */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c1111111,
+ c_______ }, /* _ */
+
+ { c__11___,
+ c__11___,
+ c___1___,
+ c____1__,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______ }, /* ` */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_1111__,
+ c_____1_,
+ c_11111_,
+ c1_____1,
+ c1____11,
+ c_1111_1 }, /* a */
+
+ { c1______,
+ c1______,
+ c1______,
+ c1_111__,
+ c11___1_,
+ c1_____1,
+ c1_____1,
+ c11___1_,
+ c1_111__ }, /* b */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_1111__,
+ c1____1_,
+ c1______,
+ c1______,
+ c1____1_,
+ c_1111__ }, /* c */
+
+ { c_____1_,
+ c_____1_,
+ c_____1_,
+ c_111_1_,
+ c1___11_,
+ c1____1_,
+ c1____1_,
+ c1___11_,
+ c_111_1_ }, /* d */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_1111__,
+ c1____1_,
+ c111111_,
+ c1______,
+ c1____1_,
+ c_1111__ }, /* e */
+
+ { c___11__,
+ c__1__1_,
+ c__1____,
+ c__1____,
+ c11111__,
+ c__1____,
+ c__1____,
+ c__1____,
+ c__1____ }, /* f */
+
+ { c_111_1_,
+ c1___11_,
+ c1____1_,
+ c1____1_,
+ c1___11_,
+ c_111_1_,
+ c_____1_,
+ c1____1_,
+ c_1111__ }, /* g */
+
+ { c1______,
+ c1______,
+ c1______,
+ c1_111__,
+ c11___1_,
+ c1____1_,
+ c1____1_,
+ c1____1_,
+ c1____1_ }, /* h */
+
+ { c_______,
+ c___1___,
+ c_______,
+ c__11___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c__111__ }, /* i */
+
+ { c____11_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_____1_,
+ c_1___1_,
+ c__111__ }, /* j */
+
+ { c1______,
+ c1______,
+ c1______,
+ c1___1__,
+ c1__1___,
+ c1_1____,
+ c11_1___,
+ c1___1__,
+ c1____1_ }, /* k */
+
+ { c__11___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c__111__ }, /* l */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1_1_11_,
+ c11_1__1,
+ c1__1__1,
+ c1__1__1,
+ c1__1__1,
+ c1__1__1 }, /* m */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1_111__,
+ c11___1_,
+ c1____1_,
+ c1____1_,
+ c1____1_,
+ c1____1_ }, /* n */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_1111__,
+ c1____1_,
+ c1____1_,
+ c1____1_,
+ c1____1_,
+ c_1111__ }, /* o */
+
+ { c1_111__,
+ c11___1_,
+ c1____1_,
+ c1____1_,
+ c11___1_,
+ c1_111__,
+ c1______,
+ c1______,
+ c1______ }, /* p */
+
+ { c_111_1_,
+ c1___11_,
+ c1____1_,
+ c1____1_,
+ c1___11_,
+ c_111_1_,
+ c_____1_,
+ c_____1_,
+ c_____1_ }, /* q */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1_111__,
+ c11___1_,
+ c1______,
+ c1______,
+ c1______,
+ c1______ }, /* r */
+
+ { c_______,
+ c_______,
+ c_______,
+ c_1111__,
+ c1____1_,
+ c_11____,
+ c___11__,
+ c1____1_,
+ c_1111__ }, /* s */
+
+ { c_______,
+ c__1____,
+ c__1____,
+ c11111__,
+ c__1____,
+ c__1____,
+ c__1____,
+ c__1__1_,
+ c___11__ }, /* t */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1____1_,
+ c1____1_,
+ c1____1_,
+ c1____1_,
+ c1___11_,
+ c_111_1_ }, /* u */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1_____1,
+ c1_____1,
+ c1_____1,
+ c_1___1_,
+ c__1_1__,
+ c___1___ }, /* v */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1_____1,
+ c1__1__1,
+ c1__1__1,
+ c1__1__1,
+ c1__1__1,
+ c_11_11_ }, /* w */
+
+ { c_______,
+ c_______,
+ c_______,
+ c1____1_,
+ c_1__1__,
+ c__11___,
+ c__11___,
+ c_1__1__,
+ c1____1_ }, /* x */
+
+ { c1____1_,
+ c1____1_,
+ c1____1_,
+ c1____1_,
+ c1___11_,
+ c_111_1_,
+ c_____1_,
+ c1____1_,
+ c_1111__ }, /* y */
+
+ { c_______,
+ c_______,
+ c_______,
+ c111111_,
+ c____1__,
+ c___1___,
+ c__1____,
+ c_1_____,
+ c111111_ }, /* z */
+
+ { c___11__,
+ c__1____,
+ c__1____,
+ c__1____,
+ c_1_____,
+ c__1____,
+ c__1____,
+ c__1____,
+ c___11__ }, /* } */
+
+ { c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___,
+ c___1___ }, /* | */
+
+ { c__11___,
+ c____1__,
+ c____1__,
+ c____1__,
+ c_____1_,
+ c____1__,
+ c____1__,
+ c____1__,
+ c__11___ }, /* } */
+
+ { c_11____,
+ c1__1__1,
+ c____11_,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______,
+ c_______ }, /* ~ */
+
+ { c_1__1__,
+ c1__1__1,
+ c__1__1_,
+ c_1__1__,
+ c1__1__1,
+ c__1__1_,
+ c_1__1__,
+ c1__1__1,
+ c__1__1_ } /* rub-out */
+};
diff --git a/usr.sbin/lpr/lpd/modes.c b/usr.sbin/lpr/lpd/modes.c
new file mode 100644
index 0000000..52f1229
--- /dev/null
+++ b/usr.sbin/lpr/lpd/modes.c
@@ -0,0 +1,231 @@
+/*-
+ * Copyright (c) 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)modes.c 8.3 (Berkeley) 4/2/94";
+#endif /* not lint */
+#endif
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+#include <stddef.h>
+#include <string.h>
+#include <termios.h>
+#include "lp.local.h"
+#include "extern.h"
+
+struct modes {
+ const char *name;
+ const long set;
+ const long unset;
+};
+
+/*
+ * The code in optlist() depends on minus options following regular
+ * options, i.e. "foo" must immediately precede "-foo".
+ */
+struct modes cmodes[] = {
+ { "cs5", CS5, CSIZE },
+ { "cs6", CS6, CSIZE },
+ { "cs7", CS7, CSIZE },
+ { "cs8", CS8, CSIZE },
+ { "cstopb", CSTOPB, 0 },
+ { "-cstopb", 0, CSTOPB },
+ { "cread", CREAD, 0 },
+ { "-cread", 0, CREAD },
+ { "parenb", PARENB, 0 },
+ { "-parenb", 0, PARENB },
+ { "parodd", PARODD, 0 },
+ { "-parodd", 0, PARODD },
+ { "parity", PARENB | CS7, PARODD | CSIZE },
+ { "-parity", CS8, PARODD | PARENB | CSIZE },
+ { "evenp", PARENB | CS7, PARODD | CSIZE },
+ { "-evenp", CS8, PARODD | PARENB | CSIZE },
+ { "oddp", PARENB | CS7 | PARODD, CSIZE },
+ { "-oddp", CS8, PARODD | PARENB | CSIZE },
+ { "pass8", CS8, PARODD | PARENB | CSIZE },
+ { "-pass8", PARENB | CS7, PARODD | CSIZE },
+ { "hupcl", HUPCL, 0 },
+ { "-hupcl", 0, HUPCL },
+ { "hup", HUPCL, 0 },
+ { "-hup", 0, HUPCL },
+ { "clocal", CLOCAL, 0 },
+ { "-clocal", 0, CLOCAL },
+ { "crtscts", CRTSCTS, 0 },
+ { "-crtscts", 0, CRTSCTS },
+ { "ctsflow", CCTS_OFLOW, 0 },
+ { "-ctsflow", 0, CCTS_OFLOW },
+ { "dsrflow", CDSR_OFLOW, 0 },
+ { "-dsrflow", 0, CDSR_OFLOW },
+ { "dtrflow", CDTR_IFLOW, 0 },
+ { "-dtrflow", 0, CDTR_IFLOW },
+ { "rtsflow", CRTS_IFLOW, 0 },
+ { "-rtsflow", 0, CRTS_IFLOW },
+ { "mdmbuf", MDMBUF, 0 },
+ { "-mdmbuf", 0, MDMBUF },
+ { NULL, 0, 0},
+};
+
+struct modes imodes[] = {
+ { "ignbrk", IGNBRK, 0 },
+ { "-ignbrk", 0, IGNBRK },
+ { "brkint", BRKINT, 0 },
+ { "-brkint", 0, BRKINT },
+ { "ignpar", IGNPAR, 0 },
+ { "-ignpar", 0, IGNPAR },
+ { "parmrk", PARMRK, 0 },
+ { "-parmrk", 0, PARMRK },
+ { "inpck", INPCK, 0 },
+ { "-inpck", 0, INPCK },
+ { "istrip", ISTRIP, 0 },
+ { "-istrip", 0, ISTRIP },
+ { "inlcr", INLCR, 0 },
+ { "-inlcr", 0, INLCR },
+ { "igncr", IGNCR, 0 },
+ { "-igncr", 0, IGNCR },
+ { "icrnl", ICRNL, 0 },
+ { "-icrnl", 0, ICRNL },
+ { "ixon", IXON, 0 },
+ { "-ixon", 0, IXON },
+ { "flow", IXON, 0 },
+ { "-flow", 0, IXON },
+ { "ixoff", IXOFF, 0 },
+ { "-ixoff", 0, IXOFF },
+ { "tandem", IXOFF, 0 },
+ { "-tandem", 0, IXOFF },
+ { "ixany", IXANY, 0 },
+ { "-ixany", 0, IXANY },
+ { "decctlq", 0, IXANY },
+ { "-decctlq", IXANY, 0 },
+ { "imaxbel", IMAXBEL, 0 },
+ { "-imaxbel", 0, IMAXBEL },
+ { NULL, 0, 0},
+};
+
+struct modes lmodes[] = {
+ { "echo", ECHO, 0 },
+ { "-echo", 0, ECHO },
+ { "echoe", ECHOE, 0 },
+ { "-echoe", 0, ECHOE },
+ { "crterase", ECHOE, 0 },
+ { "-crterase", 0, ECHOE },
+ { "crtbs", ECHOE, 0 }, /* crtbs not supported, close enough */
+ { "-crtbs", 0, ECHOE },
+ { "echok", ECHOK, 0 },
+ { "-echok", 0, ECHOK },
+ { "echoke", ECHOKE, 0 },
+ { "-echoke", 0, ECHOKE },
+ { "crtkill", ECHOKE, 0 },
+ { "-crtkill", 0, ECHOKE },
+ { "altwerase", ALTWERASE, 0 },
+ { "-altwerase", 0, ALTWERASE },
+ { "iexten", IEXTEN, 0 },
+ { "-iexten", 0, IEXTEN },
+ { "echonl", ECHONL, 0 },
+ { "-echonl", 0, ECHONL },
+ { "echoctl", ECHOCTL, 0 },
+ { "-echoctl", 0, ECHOCTL },
+ { "ctlecho", ECHOCTL, 0 },
+ { "-ctlecho", 0, ECHOCTL },
+ { "echoprt", ECHOPRT, 0 },
+ { "-echoprt", 0, ECHOPRT },
+ { "prterase", ECHOPRT, 0 },
+ { "-prterase", 0, ECHOPRT },
+ { "isig", ISIG, 0 },
+ { "-isig", 0, ISIG },
+ { "icanon", ICANON, 0 },
+ { "-icanon", 0, ICANON },
+ { "noflsh", NOFLSH, 0 },
+ { "-noflsh", 0, NOFLSH },
+ { "tostop", TOSTOP, 0 },
+ { "-tostop", 0, TOSTOP },
+ { "flusho", FLUSHO, 0 },
+ { "-flusho", 0, FLUSHO },
+ { "pendin", PENDIN, 0 },
+ { "-pendin", 0, PENDIN },
+ { "crt", ECHOE|ECHOKE|ECHOCTL, ECHOK|ECHOPRT },
+ { "-crt", ECHOK, ECHOE|ECHOKE|ECHOCTL },
+ { "newcrt", ECHOE|ECHOKE|ECHOCTL, ECHOK|ECHOPRT },
+ { "-newcrt", ECHOK, ECHOE|ECHOKE|ECHOCTL },
+ { "nokerninfo", NOKERNINFO, 0 },
+ { "-nokerninfo",0, NOKERNINFO },
+ { "kerninfo", 0, NOKERNINFO },
+ { "-kerninfo", NOKERNINFO, 0 },
+ { NULL, 0, 0},
+};
+
+struct modes omodes[] = {
+ { "opost", OPOST, 0 },
+ { "-opost", 0, OPOST },
+ { "litout", 0, OPOST },
+ { "-litout", OPOST, 0 },
+ { "onlcr", ONLCR, 0 },
+ { "-onlcr", 0, ONLCR },
+ { "tabs", 0, OXTABS }, /* "preserve" tabs */
+ { "-tabs", OXTABS, 0 },
+ { "oxtabs", OXTABS, 0 },
+ { "-oxtabs", 0, OXTABS },
+ { NULL, 0, 0},
+};
+
+#define CHK(name, s) (*name == s[0] && !strcmp(name, s))
+
+int
+msearch(char *str, struct termios *ip)
+{
+ struct modes *mp;
+
+ for (mp = cmodes; mp->name; ++mp)
+ if (CHK(str, mp->name)) {
+ ip->c_cflag &= ~mp->unset;
+ ip->c_cflag |= mp->set;
+ return (1);
+ }
+ for (mp = imodes; mp->name; ++mp)
+ if (CHK(str, mp->name)) {
+ ip->c_iflag &= ~mp->unset;
+ ip->c_iflag |= mp->set;
+ return (1);
+ }
+ for (mp = lmodes; mp->name; ++mp)
+ if (CHK(str, mp->name)) {
+ ip->c_lflag &= ~mp->unset;
+ ip->c_lflag |= mp->set;
+ return (1);
+ }
+ for (mp = omodes; mp->name; ++mp)
+ if (CHK(str, mp->name)) {
+ ip->c_oflag &= ~mp->unset;
+ ip->c_oflag |= mp->set;
+ return (1);
+ }
+ return (0);
+}
diff --git a/usr.sbin/lpr/lpd/printjob.c b/usr.sbin/lpr/lpd/printjob.c
new file mode 100644
index 0000000..06ea1b0
--- /dev/null
+++ b/usr.sbin/lpr/lpd/printjob.c
@@ -0,0 +1,2020 @@
+/*
+ * 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 */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)printjob.c 8.7 (Berkeley) 5/10/95";
+#endif /* not lint */
+#endif
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+/*
+ * printjob -- print jobs in the queue.
+ *
+ * NOTE: the lock file is used to pass information to lpq and lprm.
+ * it does not need to be removed because file locks are dynamic.
+ */
+
+#include <sys/param.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <pwd.h>
+#include <unistd.h>
+#include <signal.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <time.h>
+#include "lp.h"
+#include "lp.local.h"
+#include "pathnames.h"
+#include "extern.h"
+
+#define DORETURN 0 /* dofork should return "can't fork" error */
+#define DOABORT 1 /* dofork should just die if fork() fails */
+
+/*
+ * The buffer size to use when reading/writing spool files.
+ */
+#define SPL_BUFSIZ BUFSIZ
+
+/*
+ * Error tokens
+ */
+#define REPRINT -2
+#define ERROR -1
+#define OK 0
+#define FATALERR 1
+#define NOACCT 2
+#define FILTERERR 3
+#define ACCESS 4
+
+static dev_t fdev; /* device of file pointed to by symlink */
+static ino_t fino; /* inode of file pointed to by symlink */
+static FILE *cfp; /* control file */
+static pid_t of_pid; /* process id of output filter, if any */
+static int child; /* id of any filters */
+static int job_dfcnt; /* count of datafiles in current user job */
+static int lfd; /* lock file descriptor */
+static int ofd; /* output filter file descriptor */
+static int tfd = -1; /* output filter temp file output */
+static int pfd; /* prstatic inter file descriptor */
+static int prchild; /* id of pr process */
+static char title[80]; /* ``pr'' title */
+static char locale[80]; /* ``pr'' locale */
+
+/* these two are set from pp->daemon_user, but only if they are needed */
+static char *daemon_uname; /* set from pwd->pw_name */
+static int daemon_defgid;
+
+static char class[32]; /* classification field */
+static char origin_host[MAXHOSTNAMELEN]; /* user's host machine */
+ /* indentation size in static characters */
+static char indent[10] = "-i0";
+static char jobname[100]; /* job or file name */
+static char length[10] = "-l"; /* page length in lines */
+static char logname[32]; /* user's login name */
+static char pxlength[10] = "-y"; /* page length in pixels */
+static char pxwidth[10] = "-x"; /* page width in pixels */
+/* tempstderr is the filename used to catch stderr from exec-ing filters */
+static char tempstderr[] = "errs.XXXXXXX";
+static char width[10] = "-w"; /* page width in static characters */
+#define TFILENAME "fltXXXXXX"
+static char tfile[] = TFILENAME; /* file name for filter output */
+
+static void abortpr(int _signo);
+static void alarmhandler(int _signo);
+static void banner(struct printer *_pp, char *_name1, char *_name2);
+static int dofork(const struct printer *_pp, int _action);
+static int dropit(int _c);
+static int execfilter(struct printer *_pp, char *_f_cmd, char **_f_av,
+ int _infd, int _outfd);
+static void init(struct printer *_pp);
+static void openpr(const struct printer *_pp);
+static void opennet(const struct printer *_pp);
+static void opentty(const struct printer *_pp);
+static void openrem(const struct printer *pp);
+static int print(struct printer *_pp, int _format, char *_file);
+static int printit(struct printer *_pp, char *_file);
+static void pstatus(const struct printer *_pp, const char *_msg, ...)
+ __printflike(2, 3);
+static char response(const struct printer *_pp);
+static void scan_out(struct printer *_pp, int _scfd, char *_scsp,
+ int _dlm);
+static char *scnline(int _key, char *_p, int _c);
+static int sendfile(struct printer *_pp, int _type, char *_file,
+ char _format, int _copyreq);
+static int sendit(struct printer *_pp, char *_file);
+static void sendmail(struct printer *_pp, char *_userid, int _bombed);
+static void setty(const struct printer *_pp);
+static void wait4data(struct printer *_pp, const char *_dfile);
+
+void
+printjob(struct printer *pp)
+{
+ struct stat stb;
+ register struct jobqueue *q, **qp;
+ struct jobqueue **queue;
+ register int i, nitems;
+ off_t pidoff;
+ pid_t printpid;
+ int errcnt, jobcount, statok, tempfd;
+
+ jobcount = 0;
+ init(pp); /* set up capabilities */
+ (void) write(STDOUT_FILENO, "", 1); /* ack that daemon is started */
+ (void) close(STDERR_FILENO); /* set up log file */
+ if (open(pp->log_file, O_WRONLY|O_APPEND, LOG_FILE_MODE) < 0) {
+ syslog(LOG_ERR, "%s: open(%s): %m", pp->printer,
+ pp->log_file);
+ (void) open(_PATH_DEVNULL, O_WRONLY);
+ }
+ if(setgid(getegid()) != 0) err(1, "setgid() failed");
+ printpid = getpid(); /* for use with lprm */
+ setpgid((pid_t)0, printpid);
+
+ /*
+ * At initial lpd startup, printjob may be called with various
+ * signal handlers in effect. After that initial startup, any
+ * calls to printjob will have a *different* set of signal-handlers
+ * in effect. Make sure all handlers are the ones we want.
+ */
+ signal(SIGCHLD, SIG_DFL);
+ signal(SIGHUP, abortpr);
+ signal(SIGINT, abortpr);
+ signal(SIGQUIT, abortpr);
+ signal(SIGTERM, abortpr);
+
+ /*
+ * uses short form file names
+ */
+ if (chdir(pp->spool_dir) < 0) {
+ syslog(LOG_ERR, "%s: chdir(%s): %m", pp->printer,
+ pp->spool_dir);
+ exit(1);
+ }
+ statok = stat(pp->lock_file, &stb);
+ if (statok == 0 && (stb.st_mode & LFM_PRINT_DIS))
+ exit(0); /* printing disabled */
+ umask(S_IWOTH);
+ lfd = open(pp->lock_file, O_WRONLY|O_CREAT|O_EXLOCK|O_NONBLOCK,
+ LOCK_FILE_MODE);
+ if (lfd < 0) {
+ if (errno == EWOULDBLOCK) /* active daemon present */
+ exit(0);
+ syslog(LOG_ERR, "%s: open(%s): %m", pp->printer,
+ pp->lock_file);
+ exit(1);
+ }
+ /*
+ * If the initial call to stat() failed, then lock_file will have
+ * been created by open(). Update &stb to match that new file.
+ */
+ if (statok != 0)
+ statok = stat(pp->lock_file, &stb);
+ /* turn off non-blocking mode (was turned on for lock effects only) */
+ if (fcntl(lfd, F_SETFL, 0) < 0) {
+ syslog(LOG_ERR, "%s: fcntl(%s): %m", pp->printer,
+ pp->lock_file);
+ exit(1);
+ }
+ ftruncate(lfd, 0);
+ /*
+ * write process id for others to know
+ */
+ sprintf(line, "%u\n", printpid);
+ pidoff = i = strlen(line);
+ if (write(lfd, line, i) != i) {
+ syslog(LOG_ERR, "%s: write(%s): %m", pp->printer,
+ pp->lock_file);
+ exit(1);
+ }
+ /*
+ * search the spool directory for work and sort by queue order.
+ */
+ if ((nitems = getq(pp, &queue)) < 0) {
+ syslog(LOG_ERR, "%s: can't scan %s", pp->printer,
+ pp->spool_dir);
+ exit(1);
+ }
+ if (nitems == 0) /* no work to do */
+ exit(0);
+ if (stb.st_mode & LFM_RESET_QUE) { /* reset queue flag */
+ if (fchmod(lfd, stb.st_mode & ~LFM_RESET_QUE) < 0)
+ syslog(LOG_ERR, "%s: fchmod(%s): %m", pp->printer,
+ pp->lock_file);
+ }
+
+ /* create a file which will be used to hold stderr from filters */
+ if ((tempfd = mkstemp(tempstderr)) == -1) {
+ syslog(LOG_ERR, "%s: mkstemp(%s): %m", pp->printer,
+ tempstderr);
+ exit(1);
+ }
+ if ((i = fchmod(tempfd, 0664)) == -1) {
+ syslog(LOG_ERR, "%s: fchmod(%s): %m", pp->printer,
+ tempstderr);
+ exit(1);
+ }
+ /* lpd doesn't need it to be open, it just needs it to exist */
+ close(tempfd);
+
+ openpr(pp); /* open printer or remote */
+again:
+ /*
+ * we found something to do now do it --
+ * write the name of the current control file into the lock file
+ * so the spool queue program can tell what we're working on
+ */
+ for (qp = queue; nitems--; free((char *) q)) {
+ q = *qp++;
+ if (stat(q->job_cfname, &stb) < 0)
+ continue;
+ errcnt = 0;
+ restart:
+ (void) lseek(lfd, pidoff, 0);
+ (void) snprintf(line, sizeof(line), "%s\n", q->job_cfname);
+ i = strlen(line);
+ if (write(lfd, line, i) != i)
+ syslog(LOG_ERR, "%s: write(%s): %m", pp->printer,
+ pp->lock_file);
+ if (!pp->remote)
+ i = printit(pp, q->job_cfname);
+ else
+ i = sendit(pp, q->job_cfname);
+ /*
+ * Check to see if we are supposed to stop printing or
+ * if we are to rebuild the queue.
+ */
+ if (fstat(lfd, &stb) == 0) {
+ /* stop printing before starting next job? */
+ if (stb.st_mode & LFM_PRINT_DIS)
+ goto done;
+ /* rebuild queue (after lpc topq) */
+ if (stb.st_mode & LFM_RESET_QUE) {
+ for (free(q); nitems--; free(q))
+ q = *qp++;
+ if (fchmod(lfd, stb.st_mode & ~LFM_RESET_QUE)
+ < 0)
+ syslog(LOG_WARNING,
+ "%s: fchmod(%s): %m",
+ pp->printer, pp->lock_file);
+ break;
+ }
+ }
+ if (i == OK) /* all files of this job printed */
+ jobcount++;
+ else if (i == REPRINT && ++errcnt < 5) {
+ /* try reprinting the job */
+ syslog(LOG_INFO, "restarting %s", pp->printer);
+ if (of_pid > 0) {
+ kill(of_pid, SIGCONT); /* to be sure */
+ (void) close(ofd);
+ while ((i = wait(NULL)) > 0 && i != of_pid)
+ ;
+ if (i < 0)
+ syslog(LOG_WARNING, "%s: after kill(of=%d), wait() returned: %m",
+ pp->printer, of_pid);
+ of_pid = 0;
+ }
+ (void) close(pfd); /* close printer */
+ if (ftruncate(lfd, pidoff) < 0)
+ syslog(LOG_WARNING, "%s: ftruncate(%s): %m",
+ pp->printer, pp->lock_file);
+ openpr(pp); /* try to reopen printer */
+ goto restart;
+ } else {
+ syslog(LOG_WARNING, "%s: job could not be %s (%s)",
+ pp->printer,
+ pp->remote ? "sent to remote host" : "printed",
+ q->job_cfname);
+ if (i == REPRINT) {
+ /* ensure we don't attempt this job again */
+ (void) unlink(q->job_cfname);
+ q->job_cfname[0] = 'd';
+ (void) unlink(q->job_cfname);
+ if (logname[0])
+ sendmail(pp, logname, FATALERR);
+ }
+ }
+ }
+ free(queue);
+ /*
+ * search the spool directory for more work.
+ */
+ if ((nitems = getq(pp, &queue)) < 0) {
+ syslog(LOG_ERR, "%s: can't scan %s", pp->printer,
+ pp->spool_dir);
+ exit(1);
+ }
+ if (nitems == 0) { /* no more work to do */
+ done:
+ if (jobcount > 0) { /* jobs actually printed */
+ if (!pp->no_formfeed && !pp->tof)
+ (void) write(ofd, pp->form_feed,
+ strlen(pp->form_feed));
+ if (pp->trailer != NULL) /* output trailer */
+ (void) write(ofd, pp->trailer,
+ strlen(pp->trailer));
+ }
+ (void) close(ofd);
+ (void) wait(NULL);
+ (void) unlink(tempstderr);
+ exit(0);
+ }
+ goto again;
+}
+
+char fonts[4][50]; /* fonts for troff */
+
+char ifonts[4][40] = {
+ _PATH_VFONTR,
+ _PATH_VFONTI,
+ _PATH_VFONTB,
+ _PATH_VFONTS,
+};
+
+/*
+ * The remaining part is the reading of the control file (cf)
+ * and performing the various actions.
+ */
+static int
+printit(struct printer *pp, char *file)
+{
+ register int i;
+ char *cp;
+ int bombed, didignorehdr;
+
+ bombed = OK;
+ didignorehdr = 0;
+ /*
+ * open control file; ignore if no longer there.
+ */
+ if ((cfp = fopen(file, "r")) == NULL) {
+ syslog(LOG_INFO, "%s: fopen(%s): %m", pp->printer, file);
+ return (OK);
+ }
+ /*
+ * Reset troff fonts.
+ */
+ for (i = 0; i < 4; i++)
+ strcpy(fonts[i], ifonts[i]);
+ sprintf(&width[2], "%ld", pp->page_width);
+ strcpy(indent+2, "0");
+
+ /* initialize job-specific count of datafiles processed */
+ job_dfcnt = 0;
+
+ /*
+ * read the control file for work to do
+ *
+ * file format -- first character in the line is a command
+ * rest of the line is the argument.
+ * valid commands are:
+ *
+ * S -- "stat info" for symbolic link protection
+ * J -- "job name" on banner page
+ * C -- "class name" on banner page
+ * L -- "literal" user's name to print on banner
+ * T -- "title" for pr
+ * H -- "host name" of machine where lpr was done
+ * P -- "person" user's login name
+ * I -- "indent" amount to indent output
+ * R -- laser dpi "resolution"
+ * f -- "file name" name of text file to print
+ * l -- "file name" text file with control chars
+ * o -- "file name" postscript file, according to
+ * the RFC. Here it is treated like an 'f'.
+ * p -- "file name" text file to print with pr(1)
+ * t -- "file name" troff(1) file to print
+ * n -- "file name" ditroff(1) file to print
+ * d -- "file name" dvi file to print
+ * g -- "file name" plot(1G) file to print
+ * v -- "file name" plain raster file to print
+ * c -- "file name" cifplot file to print
+ * 1 -- "R font file" for troff
+ * 2 -- "I font file" for troff
+ * 3 -- "B font file" for troff
+ * 4 -- "S font file" for troff
+ * N -- "name" of file (used by lpq)
+ * U -- "unlink" name of file to remove
+ * (after we print it. (Pass 2 only)).
+ * M -- "mail" to user when done printing
+ * Z -- "locale" for pr
+ *
+ * getline reads a line and expands tabs to blanks
+ */
+
+ /* pass 1 */
+
+ while (getline(cfp))
+ switch (line[0]) {
+ case 'H':
+ strlcpy(origin_host, line + 1, sizeof(origin_host));
+ if (class[0] == '\0') {
+ strlcpy(class, line+1, sizeof(class));
+ }
+ continue;
+
+ case 'P':
+ strlcpy(logname, line + 1, sizeof(logname));
+ if (pp->restricted) { /* restricted */
+ if (getpwnam(logname) == NULL) {
+ bombed = NOACCT;
+ sendmail(pp, line+1, bombed);
+ goto pass2;
+ }
+ }
+ continue;
+
+ case 'S':
+ cp = line+1;
+ i = 0;
+ while (*cp >= '0' && *cp <= '9')
+ i = i * 10 + (*cp++ - '0');
+ fdev = i;
+ cp++;
+ i = 0;
+ while (*cp >= '0' && *cp <= '9')
+ i = i * 10 + (*cp++ - '0');
+ fino = i;
+ continue;
+
+ case 'J':
+ if (line[1] != '\0') {
+ strlcpy(jobname, line + 1, sizeof(jobname));
+ } else
+ strcpy(jobname, " ");
+ continue;
+
+ case 'C':
+ if (line[1] != '\0')
+ strlcpy(class, line + 1, sizeof(class));
+ else if (class[0] == '\0') {
+ /* XXX - why call gethostname instead of
+ * just strlcpy'ing local_host? */
+ gethostname(class, sizeof(class));
+ class[sizeof(class) - 1] = '\0';
+ }
+ continue;
+
+ case 'T': /* header title for pr */
+ strlcpy(title, line + 1, sizeof(title));
+ continue;
+
+ case 'L': /* identification line */
+ if (!pp->no_header && !pp->header_last)
+ banner(pp, line+1, jobname);
+ continue;
+
+ case '1': /* troff fonts */
+ case '2':
+ case '3':
+ case '4':
+ if (line[1] != '\0') {
+ strlcpy(fonts[line[0]-'1'], line + 1,
+ (size_t)50);
+ }
+ continue;
+
+ case 'W': /* page width */
+ strlcpy(width+2, line + 1, sizeof(width) - 2);
+ continue;
+
+ case 'I': /* indent amount */
+ strlcpy(indent+2, line + 1, sizeof(indent) - 2);
+ continue;
+
+ case 'Z': /* locale for pr */
+ strlcpy(locale, line + 1, sizeof(locale));
+ continue;
+
+ default: /* some file to print */
+ /* only lowercase cmd-codes include a file-to-print */
+ if ((line[0] < 'a') || (line[0] > 'z')) {
+ /* ignore any other lines */
+ if (lflag <= 1)
+ continue;
+ if (!didignorehdr) {
+ syslog(LOG_INFO, "%s: in %s :",
+ pp->printer, file);
+ didignorehdr = 1;
+ }
+ syslog(LOG_INFO, "%s: ignoring line: '%c' %s",
+ pp->printer, line[0], &line[1]);
+ continue;
+ }
+ i = print(pp, line[0], line+1);
+ switch (i) {
+ case ERROR:
+ if (bombed == OK)
+ bombed = FATALERR;
+ break;
+ case REPRINT:
+ (void) fclose(cfp);
+ return (REPRINT);
+ case FILTERERR:
+ case ACCESS:
+ bombed = i;
+ sendmail(pp, logname, bombed);
+ }
+ title[0] = '\0';
+ continue;
+
+ case 'N':
+ case 'U':
+ case 'M':
+ case 'R':
+ continue;
+ }
+
+ /* pass 2 */
+
+pass2:
+ fseek(cfp, 0L, 0);
+ while (getline(cfp))
+ switch (line[0]) {
+ case 'L': /* identification line */
+ if (!pp->no_header && pp->header_last)
+ banner(pp, line+1, jobname);
+ continue;
+
+ case 'M':
+ if (bombed < NOACCT) /* already sent if >= NOACCT */
+ sendmail(pp, line+1, bombed);
+ continue;
+
+ case 'U':
+ if (strchr(line+1, '/'))
+ continue;
+ (void) unlink(line+1);
+ }
+ /*
+ * clean-up in case another control file exists
+ */
+ (void) fclose(cfp);
+ (void) unlink(file);
+ return (bombed == OK ? OK : ERROR);
+}
+
+/*
+ * Print a file.
+ * Set up the chain [ PR [ | {IF, OF} ] ] or {IF, RF, TF, NF, DF, CF, VF}.
+ * Return -1 if a non-recoverable error occurred,
+ * 2 if the filter detected some errors (but printed the job anyway),
+ * 1 if we should try to reprint this job and
+ * 0 if all is well.
+ * Note: all filters take stdin as the file, stdout as the printer,
+ * stderr as the log file, and must not ignore SIGINT.
+ */
+static int
+print(struct printer *pp, int format, char *file)
+{
+ register int n, i;
+ register char *prog;
+ int fi, fo;
+ FILE *fp;
+ char *av[15], buf[SPL_BUFSIZ];
+ pid_t wpid;
+ int p[2], retcode, stopped, wstatus, wstatus_set;
+ struct stat stb;
+
+ /* Make sure the entire data file has arrived. */
+ wait4data(pp, file);
+
+ if (lstat(file, &stb) < 0 || (fi = open(file, O_RDONLY)) < 0) {
+ syslog(LOG_INFO, "%s: unable to open %s ('%c' line)",
+ pp->printer, file, format);
+ return (ERROR);
+ }
+ /*
+ * Check to see if data file is a symbolic link. If so, it should
+ * still point to the same file or someone is trying to print
+ * something he shouldn't.
+ */
+ if ((stb.st_mode & S_IFMT) == S_IFLNK && fstat(fi, &stb) == 0 &&
+ (stb.st_dev != fdev || stb.st_ino != fino))
+ return (ACCESS);
+
+ job_dfcnt++; /* increment datafile counter for this job */
+ stopped = 0; /* output filter is not stopped */
+
+ /* everything seems OK, start it up */
+ if (!pp->no_formfeed && !pp->tof) { /* start on a fresh page */
+ (void) write(ofd, pp->form_feed, strlen(pp->form_feed));
+ pp->tof = 1;
+ }
+ if (pp->filters[LPF_INPUT] == NULL
+ && (format == 'f' || format == 'l' || format == 'o')) {
+ pp->tof = 0;
+ while ((n = read(fi, buf, SPL_BUFSIZ)) > 0)
+ if (write(ofd, buf, n) != n) {
+ (void) close(fi);
+ return (REPRINT);
+ }
+ (void) close(fi);
+ return (OK);
+ }
+ switch (format) {
+ case 'p': /* print file using 'pr' */
+ if (pp->filters[LPF_INPUT] == NULL) { /* use output filter */
+ prog = _PATH_PR;
+ i = 0;
+ av[i++] = "pr";
+ av[i++] = width;
+ av[i++] = length;
+ av[i++] = "-h";
+ av[i++] = *title ? title : " ";
+ av[i++] = "-L";
+ av[i++] = *locale ? locale : "C";
+ av[i++] = "-F";
+ av[i] = 0;
+ fo = ofd;
+ goto start;
+ }
+ pipe(p);
+ if ((prchild = dofork(pp, DORETURN)) == 0) { /* child */
+ dup2(fi, STDIN_FILENO); /* file is stdin */
+ dup2(p[1], STDOUT_FILENO); /* pipe is stdout */
+ closelog();
+ closeallfds(3);
+ execl(_PATH_PR, "pr", width, length,
+ "-h", *title ? title : " ",
+ "-L", *locale ? locale : "C",
+ "-F", (char *)0);
+ syslog(LOG_ERR, "cannot execl %s", _PATH_PR);
+ exit(2);
+ }
+ (void) close(p[1]); /* close output side */
+ (void) close(fi);
+ if (prchild < 0) {
+ prchild = 0;
+ (void) close(p[0]);
+ return (ERROR);
+ }
+ fi = p[0]; /* use pipe for input */
+ case 'f': /* print plain text file */
+ prog = pp->filters[LPF_INPUT];
+ av[1] = width;
+ av[2] = length;
+ av[3] = indent;
+ n = 4;
+ break;
+ case 'o': /* print postscript file */
+ /*
+ * Treat this as a "plain file with control characters", and
+ * assume the standard LPF_INPUT filter will recognize that
+ * the data is postscript and know what to do with it. These
+ * 'o'-file requests could come from MacOS 10.1 systems.
+ * (later versions of MacOS 10 will explicitly use 'l')
+ * A postscript file can contain binary data, which is why 'l'
+ * is somewhat more appropriate than 'f'.
+ */
+ /* FALLTHROUGH */
+ case 'l': /* like 'f' but pass control characters */
+ prog = pp->filters[LPF_INPUT];
+ av[1] = "-c";
+ av[2] = width;
+ av[3] = length;
+ av[4] = indent;
+ n = 5;
+ break;
+ case 'r': /* print a fortran text file */
+ prog = pp->filters[LPF_FORTRAN];
+ av[1] = width;
+ av[2] = length;
+ n = 3;
+ break;
+ case 't': /* print troff output */
+ case 'n': /* print ditroff output */
+ case 'd': /* print tex output */
+ (void) unlink(".railmag");
+ if ((fo = creat(".railmag", FILMOD)) < 0) {
+ syslog(LOG_ERR, "%s: cannot create .railmag",
+ pp->printer);
+ (void) unlink(".railmag");
+ } else {
+ for (n = 0; n < 4; n++) {
+ if (fonts[n][0] != '/')
+ (void) write(fo, _PATH_VFONT,
+ sizeof(_PATH_VFONT) - 1);
+ (void) write(fo, fonts[n], strlen(fonts[n]));
+ (void) write(fo, "\n", 1);
+ }
+ (void) close(fo);
+ }
+ prog = (format == 't') ? pp->filters[LPF_TROFF]
+ : ((format == 'n') ? pp->filters[LPF_DITROFF]
+ : pp->filters[LPF_DVI]);
+ av[1] = pxwidth;
+ av[2] = pxlength;
+ n = 3;
+ break;
+ case 'c': /* print cifplot output */
+ prog = pp->filters[LPF_CIFPLOT];
+ av[1] = pxwidth;
+ av[2] = pxlength;
+ n = 3;
+ break;
+ case 'g': /* print plot(1G) output */
+ prog = pp->filters[LPF_GRAPH];
+ av[1] = pxwidth;
+ av[2] = pxlength;
+ n = 3;
+ break;
+ case 'v': /* print raster output */
+ prog = pp->filters[LPF_RASTER];
+ av[1] = pxwidth;
+ av[2] = pxlength;
+ n = 3;
+ break;
+ default:
+ (void) close(fi);
+ syslog(LOG_ERR, "%s: illegal format character '%c'",
+ pp->printer, format);
+ return (ERROR);
+ }
+ if (prog == NULL) {
+ (void) close(fi);
+ syslog(LOG_ERR,
+ "%s: no filter found in printcap for format character '%c'",
+ pp->printer, format);
+ return (ERROR);
+ }
+ if ((av[0] = strrchr(prog, '/')) != NULL)
+ av[0]++;
+ else
+ av[0] = prog;
+ av[n++] = "-n";
+ av[n++] = logname;
+ av[n++] = "-h";
+ av[n++] = origin_host;
+ av[n++] = pp->acct_file;
+ av[n] = 0;
+ fo = pfd;
+ if (of_pid > 0) { /* stop output filter */
+ write(ofd, "\031\1", 2);
+ while ((wpid =
+ wait3(&wstatus, WUNTRACED, 0)) > 0 && wpid != of_pid)
+ ;
+ if (wpid < 0)
+ syslog(LOG_WARNING,
+ "%s: after stopping 'of', wait3() returned: %m",
+ pp->printer);
+ else if (!WIFSTOPPED(wstatus)) {
+ (void) close(fi);
+ syslog(LOG_WARNING, "%s: output filter died "
+ "(pid=%d retcode=%d termsig=%d)",
+ pp->printer, of_pid, WEXITSTATUS(wstatus),
+ WTERMSIG(wstatus));
+ return (REPRINT);
+ }
+ stopped++;
+ }
+start:
+ if ((child = dofork(pp, DORETURN)) == 0) { /* child */
+ dup2(fi, STDIN_FILENO);
+ dup2(fo, STDOUT_FILENO);
+ /* setup stderr for the filter (child process)
+ * so it goes to our temporary errors file */
+ n = open(tempstderr, O_WRONLY|O_TRUNC, 0664);
+ if (n >= 0)
+ dup2(n, STDERR_FILENO);
+ closelog();
+ closeallfds(3);
+ execv(prog, av);
+ syslog(LOG_ERR, "%s: cannot execv(%s): %m", pp->printer,
+ prog);
+ exit(2);
+ }
+ (void) close(fi);
+ wstatus_set = 0;
+ if (child < 0)
+ retcode = 100;
+ else {
+ while ((wpid = wait(&wstatus)) > 0 && wpid != child)
+ ;
+ if (wpid < 0) {
+ retcode = 100;
+ syslog(LOG_WARNING,
+ "%s: after execv(%s), wait() returned: %m",
+ pp->printer, prog);
+ } else {
+ wstatus_set = 1;
+ retcode = WEXITSTATUS(wstatus);
+ }
+ }
+ child = 0;
+ prchild = 0;
+ if (stopped) { /* restart output filter */
+ if (kill(of_pid, SIGCONT) < 0) {
+ syslog(LOG_ERR, "cannot restart output filter");
+ exit(1);
+ }
+ }
+ pp->tof = 0;
+
+ /* Copy the filter's output to "lf" logfile */
+ if ((fp = fopen(tempstderr, "r"))) {
+ while (fgets(buf, sizeof(buf), fp))
+ fputs(buf, stderr);
+ fclose(fp);
+ }
+
+ if (wstatus_set && !WIFEXITED(wstatus)) {
+ syslog(LOG_WARNING, "%s: filter '%c' terminated (termsig=%d)",
+ pp->printer, format, WTERMSIG(wstatus));
+ return (ERROR);
+ }
+ switch (retcode) {
+ case 0:
+ pp->tof = 1;
+ return (OK);
+ case 1:
+ return (REPRINT);
+ case 2:
+ return (ERROR);
+ default:
+ syslog(LOG_WARNING, "%s: filter '%c' exited (retcode=%d)",
+ pp->printer, format, retcode);
+ return (FILTERERR);
+ }
+}
+
+/*
+ * Send the daemon control file (cf) and any data files.
+ * Return -1 if a non-recoverable error occurred, 1 if a recoverable error and
+ * 0 if all is well.
+ */
+static int
+sendit(struct printer *pp, char *file)
+{
+ int dfcopies, err, i;
+ char *cp, last[sizeof(line)];
+
+ /*
+ * open control file
+ */
+ if ((cfp = fopen(file, "r")) == NULL)
+ return (OK);
+
+ /* initialize job-specific count of datafiles processed */
+ job_dfcnt = 0;
+
+ /*
+ * read the control file for work to do
+ *
+ * file format -- first character in the line is a command
+ * rest of the line is the argument.
+ * commands of interest are:
+ *
+ * a-z -- "file name" name of file to print
+ * U -- "unlink" name of file to remove
+ * (after we print it. (Pass 2 only)).
+ */
+
+ /*
+ * pass 1
+ */
+ err = OK;
+ while (getline(cfp)) {
+ again:
+ if (line[0] == 'S') {
+ cp = line+1;
+ i = 0;
+ while (*cp >= '0' && *cp <= '9')
+ i = i * 10 + (*cp++ - '0');
+ fdev = i;
+ cp++;
+ i = 0;
+ while (*cp >= '0' && *cp <= '9')
+ i = i * 10 + (*cp++ - '0');
+ fino = i;
+ } else if (line[0] == 'H') {
+ strlcpy(origin_host, line + 1, sizeof(origin_host));
+ if (class[0] == '\0') {
+ strlcpy(class, line + 1, sizeof(class));
+ }
+ } else if (line[0] == 'P') {
+ strlcpy(logname, line + 1, sizeof(logname));
+ if (pp->restricted) { /* restricted */
+ if (getpwnam(logname) == NULL) {
+ sendmail(pp, line+1, NOACCT);
+ err = ERROR;
+ break;
+ }
+ }
+ } else if (line[0] == 'I') {
+ strlcpy(indent+2, line + 1, sizeof(indent) - 2);
+ } else if (line[0] >= 'a' && line[0] <= 'z') {
+ dfcopies = 1;
+ strcpy(last, line);
+ while ((i = getline(cfp)) != 0) {
+ if (strcmp(last, line) != 0)
+ break;
+ dfcopies++;
+ }
+ switch (sendfile(pp, '\3', last+1, *last, dfcopies)) {
+ case OK:
+ if (i)
+ goto again;
+ break;
+ case REPRINT:
+ (void) fclose(cfp);
+ return (REPRINT);
+ case ACCESS:
+ sendmail(pp, logname, ACCESS);
+ case ERROR:
+ err = ERROR;
+ }
+ break;
+ }
+ }
+ if (err == OK && sendfile(pp, '\2', file, '\0', 1) > 0) {
+ (void) fclose(cfp);
+ return (REPRINT);
+ }
+ /*
+ * pass 2
+ */
+ fseek(cfp, 0L, 0);
+ while (getline(cfp))
+ if (line[0] == 'U' && !strchr(line+1, '/'))
+ (void) unlink(line+1);
+ /*
+ * clean-up in case another control file exists
+ */
+ (void) fclose(cfp);
+ (void) unlink(file);
+ return (err);
+}
+
+/*
+ * Send a data file to the remote machine and spool it.
+ * Return positive if we should try resending.
+ */
+static int
+sendfile(struct printer *pp, int type, char *file, char format, int copyreq)
+{
+ int i, amt;
+ struct stat stb;
+ char *av[15], *filtcmd;
+ char buf[SPL_BUFSIZ], opt_c[4], opt_h[4], opt_n[4];
+ int copycnt, filtstat, narg, resp, sfd, sfres, sizerr, statrc;
+
+ /* Make sure the entire data file has arrived. */
+ wait4data(pp, file);
+
+ statrc = lstat(file, &stb);
+ if (statrc < 0) {
+ syslog(LOG_ERR, "%s: error from lstat(%s): %m",
+ pp->printer, file);
+ return (ERROR);
+ }
+ sfd = open(file, O_RDONLY);
+ if (sfd < 0) {
+ syslog(LOG_ERR, "%s: error from open(%s,O_RDONLY): %m",
+ pp->printer, file);
+ return (ERROR);
+ }
+ /*
+ * Check to see if data file is a symbolic link. If so, it should
+ * still point to the same file or someone is trying to print something
+ * he shouldn't.
+ */
+ if ((stb.st_mode & S_IFMT) == S_IFLNK && fstat(sfd, &stb) == 0 &&
+ (stb.st_dev != fdev || stb.st_ino != fino)) {
+ close(sfd);
+ return (ACCESS);
+ }
+
+ /* Everything seems OK for reading the file, now to send it */
+ filtcmd = NULL;
+ sizerr = 0;
+ tfd = -1;
+ if (type == '\3') {
+ /*
+ * Type == 3 means this is a datafile, not a control file.
+ * Increment the counter of data-files in this job, and
+ * then check for input or output filters (which are only
+ * applied to datafiles, not control files).
+ */
+ job_dfcnt++;
+
+ /*
+ * Note that here we are filtering datafiles, one at a time,
+ * as they are sent to the remote machine. Here, the *only*
+ * difference between an input filter (`if=') and an output
+ * filter (`of=') is the argument list that the filter is
+ * started up with. Here, the output filter is executed
+ * for each individual file as it is sent. This is not the
+ * same as local print queues, where the output filter is
+ * started up once, and then all jobs are passed thru that
+ * single invocation of the output filter.
+ *
+ * Also note that a queue for a remote-machine can have an
+ * input filter or an output filter, but not both.
+ */
+ if (pp->filters[LPF_INPUT]) {
+ filtcmd = pp->filters[LPF_INPUT];
+ av[0] = filtcmd;
+ narg = 0;
+ strcpy(opt_c, "-c");
+ strcpy(opt_h, "-h");
+ strcpy(opt_n, "-n");
+ if (format == 'l')
+ av[++narg] = opt_c;
+ av[++narg] = width;
+ av[++narg] = length;
+ av[++narg] = indent;
+ av[++narg] = opt_n;
+ av[++narg] = logname;
+ av[++narg] = opt_h;
+ av[++narg] = origin_host;
+ av[++narg] = pp->acct_file;
+ av[++narg] = NULL;
+ } else if (pp->filters[LPF_OUTPUT]) {
+ filtcmd = pp->filters[LPF_OUTPUT];
+ av[0] = filtcmd;
+ narg = 0;
+ av[++narg] = width;
+ av[++narg] = length;
+ av[++narg] = NULL;
+ }
+ }
+ if (filtcmd) {
+ /*
+ * If there is an input or output filter, we have to run
+ * the datafile thru that filter and store the result as
+ * a temporary spool file, because the protocol requires
+ * that we send the remote host the file-size before we
+ * start to send any of the data.
+ */
+ strcpy(tfile, TFILENAME);
+ tfd = mkstemp(tfile);
+ if (tfd == -1) {
+ syslog(LOG_ERR, "%s: mkstemp(%s): %m", pp->printer,
+ TFILENAME);
+ sfres = ERROR;
+ goto return_sfres;
+ }
+ filtstat = execfilter(pp, filtcmd, av, sfd, tfd);
+
+ /* process the return-code from the filter */
+ switch (filtstat) {
+ case 0:
+ break;
+ case 1:
+ sfres = REPRINT;
+ goto return_sfres;
+ case 2:
+ sfres = ERROR;
+ goto return_sfres;
+ default:
+ syslog(LOG_WARNING,
+ "%s: filter '%c' exited (retcode=%d)",
+ pp->printer, format, filtstat);
+ sfres = FILTERERR;
+ goto return_sfres;
+ }
+ statrc = fstat(tfd, &stb); /* to find size of tfile */
+ if (statrc < 0) {
+ syslog(LOG_ERR,
+ "%s: error processing 'if', fstat(%s): %m",
+ pp->printer, tfile);
+ sfres = ERROR;
+ goto return_sfres;
+ }
+ close(sfd);
+ sfd = tfd;
+ lseek(sfd, 0, SEEK_SET);
+ }
+
+ copycnt = 0;
+sendagain:
+ copycnt++;
+
+ if (copycnt < 2)
+ (void) sprintf(buf, "%c%" PRId64 " %s\n", type, stb.st_size,
+ file);
+ else
+ (void) sprintf(buf, "%c%" PRId64 " %s_c%d\n", type, stb.st_size,
+ file, copycnt);
+ amt = strlen(buf);
+ for (i = 0; ; i++) {
+ if (write(pfd, buf, amt) != amt ||
+ (resp = response(pp)) < 0 || resp == '\1') {
+ sfres = REPRINT;
+ goto return_sfres;
+ } else if (resp == '\0')
+ break;
+ if (i == 0)
+ pstatus(pp,
+ "no space on remote; waiting for queue to drain");
+ if (i == 10)
+ syslog(LOG_ALERT, "%s: can't send to %s; queue full",
+ pp->printer, pp->remote_host);
+ sleep(5 * 60);
+ }
+ if (i)
+ pstatus(pp, "sending to %s", pp->remote_host);
+ /*
+ * XXX - we should change trstat_init()/trstat_write() to include
+ * the copycnt in the statistics record it may write.
+ */
+ if (type == '\3')
+ trstat_init(pp, file, job_dfcnt);
+ for (i = 0; i < stb.st_size; i += SPL_BUFSIZ) {
+ amt = SPL_BUFSIZ;
+ if (i + amt > stb.st_size)
+ amt = stb.st_size - i;
+ if (sizerr == 0 && read(sfd, buf, amt) != amt)
+ sizerr = 1;
+ if (write(pfd, buf, amt) != amt) {
+ sfres = REPRINT;
+ goto return_sfres;
+ }
+ }
+
+ if (sizerr) {
+ syslog(LOG_INFO, "%s: %s: changed size", pp->printer, file);
+ /* tell recvjob to ignore this file */
+ (void) write(pfd, "\1", 1);
+ sfres = ERROR;
+ goto return_sfres;
+ }
+ if (write(pfd, "", 1) != 1 || response(pp)) {
+ sfres = REPRINT;
+ goto return_sfres;
+ }
+ if (type == '\3') {
+ trstat_write(pp, TR_SENDING, stb.st_size, logname,
+ pp->remote_host, origin_host);
+ /*
+ * Usually we only need to send one copy of a datafile,
+ * because the control-file will simply print the same
+ * file multiple times. However, some printers ignore
+ * the control file, and simply print each data file as
+ * it arrives. For such "remote hosts", we need to
+ * transfer the same data file multiple times. Such a
+ * a host is indicated by adding 'rc' to the printcap
+ * entry.
+ * XXX - Right now this ONLY works for remote hosts which
+ * do ignore the name of the data file, because
+ * this sends the file multiple times with slight
+ * changes to the filename. To do this right would
+ * require that we also rewrite the control file
+ * to match those filenames.
+ */
+ if (pp->resend_copies && (copycnt < copyreq)) {
+ lseek(sfd, 0, SEEK_SET);
+ goto sendagain;
+ }
+ }
+ sfres = OK;
+
+return_sfres:
+ (void)close(sfd);
+ if (tfd != -1) {
+ /*
+ * If tfd is set, then it is the same value as sfd, and
+ * therefore it is already closed at this point. All
+ * we need to do is remove the temporary file.
+ */
+ tfd = -1;
+ unlink(tfile);
+ }
+ return (sfres);
+}
+
+/*
+ * Some print servers send the control-file first, and then start sending the
+ * matching data file(s). That is not the correct order. If some queue is
+ * already printing an active job, then when that job is finished the queue
+ * may proceed to the control file of any incoming print job. This turns
+ * into a race between the process which is receiving the data file, and the
+ * process which is actively printing the very same file. When the remote
+ * server sends files in the wrong order, it is even possible that a queue
+ * will start to print a data file before the file has been created!
+ *
+ * So before we start to print() or send() a data file, we call this routine
+ * to make sure the data file is not still changing in size. Note that this
+ * problem will only happen for jobs arriving from a remote host, and that
+ * the process which has decided to print this job (and is thus making this
+ * check) is *not* the process which is receiving the job.
+ *
+ * A second benefit of this is that any incoming job is guaranteed to appear
+ * in a queue listing for at least a few seconds after it has arrived. Some
+ * lpr implementations get confused if they send a job and it disappears
+ * from the queue before they can check on it.
+ */
+#define MAXWAIT_ARRIVE 16 /* max to wait for the file to *exist* */
+#define MAXWAIT_4DATA (20*60) /* max to wait for it to stop changing */
+#define MINWAIT_4DATA 4 /* This value must be >= 1 */
+#define DEBUG_MINWAIT 1
+static void
+wait4data(struct printer *pp, const char *dfile)
+{
+ const char *cp;
+ int statres;
+ u_int sleepreq;
+ size_t dlen, hlen;
+ time_t amtslept, cur_time, prev_mtime;
+ struct stat statdf;
+
+ /* Skip these checks if the print job is from the local host. */
+ dlen = strlen(dfile);
+ hlen = strlen(local_host);
+ if (dlen > hlen) {
+ cp = dfile + dlen - hlen;
+ if (strcmp(cp, local_host) == 0)
+ return;
+ }
+
+ /*
+ * If this data file does not exist, then wait up to MAXWAIT_ARRIVE
+ * seconds for it to arrive.
+ */
+ amtslept = 0;
+ statres = stat(dfile, &statdf);
+ while (statres < 0 && amtslept < MAXWAIT_ARRIVE) {
+ if (amtslept == 0)
+ pstatus(pp, "Waiting for data file from remote host");
+ amtslept += MINWAIT_4DATA - sleep(MINWAIT_4DATA);
+ statres = stat(dfile, &statdf);
+ }
+ if (statres < 0) {
+ /* The file still does not exist, so just give up on it. */
+ syslog(LOG_WARNING, "%s: wait4data() abandoned wait for %s",
+ pp->printer, dfile);
+ return;
+ }
+
+ /*
+ * The file exists, so keep waiting until the data file has not
+ * changed for some reasonable amount of time. Extra care is
+ * taken when computing wait-times, just in case there are data
+ * files with a last-modify time in the future. While that is
+ * very unlikely to happen, it can happen when the system has
+ * a flakey time-of-day clock.
+ */
+ prev_mtime = statdf.st_mtime;
+ cur_time = time(NULL);
+ if (statdf.st_mtime >= cur_time - MINWAIT_4DATA) {
+ if (statdf.st_mtime >= cur_time) /* some TOD oddity */
+ sleepreq = MINWAIT_4DATA;
+ else
+ sleepreq = cur_time - statdf.st_mtime;
+ if (amtslept == 0)
+ pstatus(pp, "Waiting for data file from remote host");
+ amtslept += sleepreq - sleep(sleepreq);
+ statres = stat(dfile, &statdf);
+ }
+ sleepreq = MINWAIT_4DATA;
+ while (statres == 0 && amtslept < MAXWAIT_4DATA) {
+ if (statdf.st_mtime == prev_mtime)
+ break;
+ prev_mtime = statdf.st_mtime;
+ amtslept += sleepreq - sleep(sleepreq);
+ statres = stat(dfile, &statdf);
+ }
+
+ if (statres != 0)
+ syslog(LOG_WARNING, "%s: %s disappeared during wait4data()",
+ pp->printer, dfile);
+ else if (amtslept > MAXWAIT_4DATA)
+ syslog(LOG_WARNING,
+ "%s: %s still changing after %lu secs in wait4data()",
+ pp->printer, dfile, (unsigned long)amtslept);
+#if DEBUG_MINWAIT
+ else if (amtslept > MINWAIT_4DATA)
+ syslog(LOG_INFO, "%s: slept %lu secs in wait4data(%s)",
+ pp->printer, (unsigned long)amtslept, dfile);
+#endif
+}
+#undef MAXWAIT_ARRIVE
+#undef MAXWAIT_4DATA
+#undef MINWAIT_4DATA
+
+/*
+ * This routine is called to execute one of the filters as was
+ * specified in a printcap entry. While the child-process will read
+ * all of 'infd', it is up to the caller to close that file descriptor
+ * in the parent process.
+ */
+static int
+execfilter(struct printer *pp, char *f_cmd, char *f_av[], int infd, int outfd)
+{
+ pid_t fpid, wpid;
+ int errfd, retcode, wstatus;
+ FILE *errfp;
+ char buf[BUFSIZ], *slash;
+
+ fpid = dofork(pp, DORETURN);
+ if (fpid != 0) {
+ /*
+ * This is the parent process, which just waits for the child
+ * to complete and then returns the result. Note that it is
+ * the child process which reads the input stream.
+ */
+ if (fpid < 0)
+ retcode = 100;
+ else {
+ while ((wpid = wait(&wstatus)) > 0 &&
+ wpid != fpid)
+ ;
+ if (wpid < 0) {
+ retcode = 100;
+ syslog(LOG_WARNING,
+ "%s: after execv(%s), wait() returned: %m",
+ pp->printer, f_cmd);
+ } else
+ retcode = WEXITSTATUS(wstatus);
+ }
+
+ /*
+ * Copy everything the filter wrote to stderr from our
+ * temporary errors file to the "lf=" logfile.
+ */
+ errfp = fopen(tempstderr, "r");
+ if (errfp) {
+ while (fgets(buf, sizeof(buf), errfp))
+ fputs(buf, stderr);
+ fclose(errfp);
+ }
+
+ return (retcode);
+ }
+
+ /*
+ * This is the child process, which is the one that executes the
+ * given filter.
+ */
+ /*
+ * If the first parameter has any slashes in it, then change it
+ * to point to the first character after the last slash.
+ */
+ slash = strrchr(f_av[0], '/');
+ if (slash != NULL)
+ f_av[0] = slash + 1;
+ /*
+ * XXX - in the future, this should setup an explicit list of
+ * environment variables and use execve()!
+ */
+
+ /*
+ * Setup stdin, stdout, and stderr as we want them when the filter
+ * is running. Stderr is setup so it points to a temporary errors
+ * file, and the parent process will copy that temporary file to
+ * the real logfile after the filter completes.
+ */
+ dup2(infd, STDIN_FILENO);
+ dup2(outfd, STDOUT_FILENO);
+ errfd = open(tempstderr, O_WRONLY|O_TRUNC, 0664);
+ if (errfd >= 0)
+ dup2(errfd, STDERR_FILENO);
+ closelog();
+ closeallfds(3);
+ execv(f_cmd, f_av);
+ syslog(LOG_ERR, "%s: cannot execv(%s): %m", pp->printer, f_cmd);
+ exit(2);
+ /* NOTREACHED */
+}
+
+/*
+ * Check to make sure there have been no errors and that both programs
+ * are in sync with eachother.
+ * Return non-zero if the connection was lost.
+ */
+static char
+response(const struct printer *pp)
+{
+ char resp;
+
+ if (read(pfd, &resp, 1) != 1) {
+ syslog(LOG_INFO, "%s: lost connection", pp->printer);
+ return (-1);
+ }
+ return (resp);
+}
+
+/*
+ * Banner printing stuff
+ */
+static void
+banner(struct printer *pp, char *name1, char *name2)
+{
+ time_t tvec;
+
+ time(&tvec);
+ if (!pp->no_formfeed && !pp->tof)
+ (void) write(ofd, pp->form_feed, strlen(pp->form_feed));
+ if (pp->short_banner) { /* short banner only */
+ if (class[0]) {
+ (void) write(ofd, class, strlen(class));
+ (void) write(ofd, ":", 1);
+ }
+ (void) write(ofd, name1, strlen(name1));
+ (void) write(ofd, " Job: ", 7);
+ (void) write(ofd, name2, strlen(name2));
+ (void) write(ofd, " Date: ", 8);
+ (void) write(ofd, ctime(&tvec), 24);
+ (void) write(ofd, "\n", 1);
+ } else { /* normal banner */
+ (void) write(ofd, "\n\n\n", 3);
+ scan_out(pp, ofd, name1, '\0');
+ (void) write(ofd, "\n\n", 2);
+ scan_out(pp, ofd, name2, '\0');
+ if (class[0]) {
+ (void) write(ofd,"\n\n\n",3);
+ scan_out(pp, ofd, class, '\0');
+ }
+ (void) write(ofd, "\n\n\n\n\t\t\t\t\tJob: ", 15);
+ (void) write(ofd, name2, strlen(name2));
+ (void) write(ofd, "\n\t\t\t\t\tDate: ", 12);
+ (void) write(ofd, ctime(&tvec), 24);
+ (void) write(ofd, "\n", 1);
+ }
+ if (!pp->no_formfeed)
+ (void) write(ofd, pp->form_feed, strlen(pp->form_feed));
+ pp->tof = 1;
+}
+
+static char *
+scnline(int key, char *p, int c)
+{
+ register int scnwidth;
+
+ for (scnwidth = WIDTH; --scnwidth;) {
+ key <<= 1;
+ *p++ = key & 0200 ? c : BACKGND;
+ }
+ return (p);
+}
+
+#define TRC(q) (((q)-' ')&0177)
+
+static void
+scan_out(struct printer *pp, int scfd, char *scsp, int dlm)
+{
+ register char *strp;
+ register int nchrs, j;
+ char outbuf[LINELEN+1], *sp, c, cc;
+ int d, scnhgt;
+
+ for (scnhgt = 0; scnhgt++ < HEIGHT+DROP; ) {
+ strp = &outbuf[0];
+ sp = scsp;
+ for (nchrs = 0; ; ) {
+ d = dropit(c = TRC(cc = *sp++));
+ if ((!d && scnhgt > HEIGHT) || (scnhgt <= DROP && d))
+ for (j = WIDTH; --j;)
+ *strp++ = BACKGND;
+ else
+ strp = scnline(scnkey[(int)c][scnhgt-1-d], strp, cc);
+ if (*sp == dlm || *sp == '\0' ||
+ nchrs++ >= pp->page_width/(WIDTH+1)-1)
+ break;
+ *strp++ = BACKGND;
+ *strp++ = BACKGND;
+ }
+ while (*--strp == BACKGND && strp >= outbuf)
+ ;
+ strp++;
+ *strp++ = '\n';
+ (void) write(scfd, outbuf, strp-outbuf);
+ }
+}
+
+static int
+dropit(int c)
+{
+ switch(c) {
+
+ case TRC('_'):
+ case TRC(';'):
+ case TRC(','):
+ case TRC('g'):
+ case TRC('j'):
+ case TRC('p'):
+ case TRC('q'):
+ case TRC('y'):
+ return (DROP);
+
+ default:
+ return (0);
+ }
+}
+
+/*
+ * sendmail ---
+ * tell people about job completion
+ */
+static void
+sendmail(struct printer *pp, char *userid, int bombed)
+{
+ register int i;
+ int p[2], s;
+ register const char *cp;
+ struct stat stb;
+ FILE *fp;
+
+ pipe(p);
+ if ((s = dofork(pp, DORETURN)) == 0) { /* child */
+ dup2(p[0], STDIN_FILENO);
+ closelog();
+ closeallfds(3);
+ if ((cp = strrchr(_PATH_SENDMAIL, '/')) != NULL)
+ cp++;
+ else
+ cp = _PATH_SENDMAIL;
+ execl(_PATH_SENDMAIL, cp, "-t", (char *)0);
+ _exit(0);
+ } else if (s > 0) { /* parent */
+ dup2(p[1], STDOUT_FILENO);
+ printf("To: %s@%s\n", userid, origin_host);
+ printf("Subject: %s printer job \"%s\"\n", pp->printer,
+ *jobname ? jobname : "<unknown>");
+ printf("Reply-To: root@%s\n\n", local_host);
+ printf("Your printer job ");
+ if (*jobname)
+ printf("(%s) ", jobname);
+
+ switch (bombed) {
+ case OK:
+ cp = "OK";
+ printf("\ncompleted successfully\n");
+ break;
+ default:
+ case FATALERR:
+ cp = "FATALERR";
+ printf("\ncould not be printed\n");
+ break;
+ case NOACCT:
+ cp = "NOACCT";
+ printf("\ncould not be printed without an account on %s\n",
+ local_host);
+ break;
+ case FILTERERR:
+ cp = "FILTERERR";
+ if (stat(tempstderr, &stb) < 0 || stb.st_size == 0
+ || (fp = fopen(tempstderr, "r")) == NULL) {
+ printf("\nhad some errors and may not have printed\n");
+ break;
+ }
+ printf("\nhad the following errors and may not have printed:\n");
+ while ((i = getc(fp)) != EOF)
+ putchar(i);
+ (void) fclose(fp);
+ break;
+ case ACCESS:
+ cp = "ACCESS";
+ printf("\nwas not printed because it was not linked to the original file\n");
+ }
+ fflush(stdout);
+ (void) close(STDOUT_FILENO);
+ } else {
+ syslog(LOG_WARNING, "unable to send mail to %s: %m", userid);
+ return;
+ }
+ (void) close(p[0]);
+ (void) close(p[1]);
+ wait(NULL);
+ syslog(LOG_INFO, "mail sent to user %s about job %s on printer %s (%s)",
+ userid, *jobname ? jobname : "<unknown>", pp->printer, cp);
+}
+
+/*
+ * dofork - fork with retries on failure
+ */
+static int
+dofork(const struct printer *pp, int action)
+{
+ pid_t forkpid;
+ int i, fail;
+ struct passwd *pwd;
+
+ forkpid = -1;
+ if (daemon_uname == NULL) {
+ pwd = getpwuid(pp->daemon_user);
+ if (pwd == NULL) {
+ syslog(LOG_ERR, "%s: Can't lookup default daemon uid (%ld) in password file",
+ pp->printer, pp->daemon_user);
+ goto error_ret;
+ }
+ daemon_uname = strdup(pwd->pw_name);
+ daemon_defgid = pwd->pw_gid;
+ }
+
+ for (i = 0; i < 20; i++) {
+ forkpid = fork();
+ if (forkpid < 0) {
+ sleep((unsigned)(i*i));
+ continue;
+ }
+ /*
+ * Child should run as daemon instead of root
+ */
+ if (forkpid == 0) {
+ errno = 0;
+ fail = initgroups(daemon_uname, daemon_defgid);
+ if (fail) {
+ syslog(LOG_ERR, "%s: initgroups(%s,%u): %m",
+ pp->printer, daemon_uname, daemon_defgid);
+ break;
+ }
+ fail = setgid(daemon_defgid);
+ if (fail) {
+ syslog(LOG_ERR, "%s: setgid(%u): %m",
+ pp->printer, daemon_defgid);
+ break;
+ }
+ fail = setuid(pp->daemon_user);
+ if (fail) {
+ syslog(LOG_ERR, "%s: setuid(%ld): %m",
+ pp->printer, pp->daemon_user);
+ break;
+ }
+ }
+ return (forkpid);
+ }
+
+ /*
+ * An error occurred. If the error is in the child process, then
+ * this routine MUST always exit(). DORETURN only effects how
+ * errors should be handled in the parent process.
+ */
+error_ret:
+ if (forkpid == 0) {
+ syslog(LOG_ERR, "%s: dofork(): aborting child process...",
+ pp->printer);
+ exit(1);
+ }
+ syslog(LOG_ERR, "%s: dofork(): failure in fork", pp->printer);
+
+ sleep(1); /* throttle errors, as a safety measure */
+ switch (action) {
+ case DORETURN:
+ return (-1);
+ default:
+ syslog(LOG_ERR, "bad action (%d) to dofork", action);
+ /* FALLTHROUGH */
+ case DOABORT:
+ exit(1);
+ }
+ /*NOTREACHED*/
+}
+
+/*
+ * Kill child processes to abort current job.
+ */
+static void
+abortpr(int signo __unused)
+{
+
+ (void) unlink(tempstderr);
+ kill(0, SIGINT);
+ if (of_pid > 0)
+ kill(of_pid, SIGCONT);
+ while (wait(NULL) > 0)
+ ;
+ if (of_pid > 0 && tfd != -1)
+ unlink(tfile);
+ exit(0);
+}
+
+static void
+init(struct printer *pp)
+{
+ char *s;
+
+ sprintf(&width[2], "%ld", pp->page_width);
+ sprintf(&length[2], "%ld", pp->page_length);
+ sprintf(&pxwidth[2], "%ld", pp->page_pwidth);
+ sprintf(&pxlength[2], "%ld", pp->page_plength);
+ if ((s = checkremote(pp)) != 0) {
+ syslog(LOG_WARNING, "%s", s);
+ free(s);
+ }
+}
+
+void
+startprinting(const char *printer)
+{
+ struct printer myprinter, *pp = &myprinter;
+ int status;
+
+ init_printer(pp);
+ status = getprintcap(printer, pp);
+ switch(status) {
+ case PCAPERR_OSERR:
+ syslog(LOG_ERR, "can't open printer description file: %m");
+ exit(1);
+ case PCAPERR_NOTFOUND:
+ syslog(LOG_ERR, "unknown printer: %s", printer);
+ exit(1);
+ case PCAPERR_TCLOOP:
+ fatal(pp, "potential reference loop detected in printcap file");
+ default:
+ break;
+ }
+ printjob(pp);
+}
+
+/*
+ * Acquire line printer or remote connection.
+ */
+static void
+openpr(const struct printer *pp)
+{
+ int p[2];
+ char *cp;
+
+ if (pp->remote) {
+ openrem(pp);
+ /*
+ * Lpd does support the setting of 'of=' filters for
+ * jobs going to remote machines, but that does not
+ * have the same meaning as 'of=' does when handling
+ * local print queues. For remote machines, all 'of='
+ * filter processing is handled in sendfile(), and that
+ * does not use these global "output filter" variables.
+ */
+ ofd = -1;
+ of_pid = 0;
+ return;
+ } else if (*pp->lp) {
+ if (strchr(pp->lp, '@') != NULL)
+ opennet(pp);
+ else
+ opentty(pp);
+ } else {
+ syslog(LOG_ERR, "%s: no line printer device or host name",
+ pp->printer);
+ exit(1);
+ }
+
+ /*
+ * Start up an output filter, if needed.
+ */
+ if (pp->filters[LPF_OUTPUT] && !pp->filters[LPF_INPUT] && !of_pid) {
+ pipe(p);
+ if (pp->remote) {
+ strcpy(tfile, TFILENAME);
+ tfd = mkstemp(tfile);
+ }
+ if ((of_pid = dofork(pp, DOABORT)) == 0) { /* child */
+ dup2(p[0], STDIN_FILENO); /* pipe is std in */
+ /* tfile/printer is stdout */
+ dup2(pp->remote ? tfd : pfd, STDOUT_FILENO);
+ closelog();
+ closeallfds(3);
+ if ((cp = strrchr(pp->filters[LPF_OUTPUT], '/')) == NULL)
+ cp = pp->filters[LPF_OUTPUT];
+ else
+ cp++;
+ execl(pp->filters[LPF_OUTPUT], cp, width, length,
+ (char *)0);
+ syslog(LOG_ERR, "%s: execl(%s): %m", pp->printer,
+ pp->filters[LPF_OUTPUT]);
+ exit(1);
+ }
+ (void) close(p[0]); /* close input side */
+ ofd = p[1]; /* use pipe for output */
+ } else {
+ ofd = pfd;
+ of_pid = 0;
+ }
+}
+
+/*
+ * Printer connected directly to the network
+ * or to a terminal server on the net
+ */
+static void
+opennet(const struct printer *pp)
+{
+ register int i;
+ int resp;
+ u_long port;
+ char *ep;
+ void (*savealrm)(int);
+
+ port = strtoul(pp->lp, &ep, 0);
+ if (*ep != '@' || port > 65535) {
+ syslog(LOG_ERR, "%s: bad port number: %s", pp->printer,
+ pp->lp);
+ exit(1);
+ }
+ ep++;
+
+ for (i = 1; ; i = i < 256 ? i << 1 : i) {
+ resp = -1;
+ savealrm = signal(SIGALRM, alarmhandler);
+ alarm(pp->conn_timeout);
+ pfd = getport(pp, ep, port);
+ alarm(0);
+ (void)signal(SIGALRM, savealrm);
+ if (pfd < 0 && errno == ECONNREFUSED)
+ resp = 1;
+ else if (pfd >= 0) {
+ /*
+ * need to delay a bit for rs232 lines
+ * to stabilize in case printer is
+ * connected via a terminal server
+ */
+ delay(500);
+ break;
+ }
+ if (i == 1) {
+ if (resp < 0)
+ pstatus(pp, "waiting for %s to come up",
+ pp->lp);
+ else
+ pstatus(pp,
+ "waiting for access to printer on %s",
+ pp->lp);
+ }
+ sleep(i);
+ }
+ pstatus(pp, "sending to %s port %lu", ep, port);
+}
+
+/*
+ * Printer is connected to an RS232 port on this host
+ */
+static void
+opentty(const struct printer *pp)
+{
+ register int i;
+
+ for (i = 1; ; i = i < 32 ? i << 1 : i) {
+ pfd = open(pp->lp, pp->rw ? O_RDWR : O_WRONLY);
+ if (pfd >= 0) {
+ delay(500);
+ break;
+ }
+ if (errno == ENOENT) {
+ syslog(LOG_ERR, "%s: %m", pp->lp);
+ exit(1);
+ }
+ if (i == 1)
+ pstatus(pp,
+ "waiting for %s to become ready (offline?)",
+ pp->printer);
+ sleep(i);
+ }
+ if (isatty(pfd))
+ setty(pp);
+ pstatus(pp, "%s is ready and printing", pp->printer);
+}
+
+/*
+ * Printer is on a remote host
+ */
+static void
+openrem(const struct printer *pp)
+{
+ register int i;
+ int resp;
+ void (*savealrm)(int);
+
+ for (i = 1; ; i = i < 256 ? i << 1 : i) {
+ resp = -1;
+ savealrm = signal(SIGALRM, alarmhandler);
+ alarm(pp->conn_timeout);
+ pfd = getport(pp, pp->remote_host, 0);
+ alarm(0);
+ (void)signal(SIGALRM, savealrm);
+ if (pfd >= 0) {
+ if ((writel(pfd, "\2", pp->remote_queue, "\n",
+ (char *)0)
+ == 2 + strlen(pp->remote_queue))
+ && (resp = response(pp)) == 0)
+ break;
+ (void) close(pfd);
+ }
+ if (i == 1) {
+ if (resp < 0)
+ pstatus(pp, "waiting for %s to come up",
+ pp->remote_host);
+ else {
+ pstatus(pp,
+ "waiting for queue to be enabled on %s",
+ pp->remote_host);
+ i = 256;
+ }
+ }
+ sleep(i);
+ }
+ pstatus(pp, "sending to %s", pp->remote_host);
+}
+
+/*
+ * setup tty lines.
+ */
+static void
+setty(const struct printer *pp)
+{
+ struct termios ttybuf;
+
+ if (ioctl(pfd, TIOCEXCL, (char *)0) < 0) {
+ syslog(LOG_ERR, "%s: ioctl(TIOCEXCL): %m", pp->printer);
+ exit(1);
+ }
+ if (tcgetattr(pfd, &ttybuf) < 0) {
+ syslog(LOG_ERR, "%s: tcgetattr: %m", pp->printer);
+ exit(1);
+ }
+ if (pp->baud_rate > 0)
+ cfsetspeed(&ttybuf, pp->baud_rate);
+ if (pp->mode_set) {
+ char *s = strdup(pp->mode_set), *tmp;
+
+ while ((tmp = strsep(&s, ",")) != NULL) {
+ (void) msearch(tmp, &ttybuf);
+ }
+ }
+ if (pp->mode_set != 0 || pp->baud_rate > 0) {
+ if (tcsetattr(pfd, TCSAFLUSH, &ttybuf) == -1) {
+ syslog(LOG_ERR, "%s: tcsetattr: %m", pp->printer);
+ }
+ }
+}
+
+#include <stdarg.h>
+
+static void
+pstatus(const struct printer *pp, const char *msg, ...)
+{
+ int fd;
+ char *buf;
+ va_list ap;
+ va_start(ap, msg);
+
+ umask(S_IWOTH);
+ fd = open(pp->status_file, O_WRONLY|O_CREAT|O_EXLOCK, STAT_FILE_MODE);
+ if (fd < 0) {
+ syslog(LOG_ERR, "%s: open(%s): %m", pp->printer,
+ pp->status_file);
+ exit(1);
+ }
+ ftruncate(fd, 0);
+ vasprintf(&buf, msg, ap);
+ va_end(ap);
+ writel(fd, buf, "\n", (char *)0);
+ close(fd);
+ free(buf);
+}
+
+void
+alarmhandler(int signo __unused)
+{
+ /* the signal is ignored */
+ /* (the '__unused' is just to avoid a compile-time warning) */
+}
diff --git a/usr.sbin/lpr/lpd/recvjob.c b/usr.sbin/lpr/lpd/recvjob.c
new file mode 100644
index 0000000..ec8834c
--- /dev/null
+++ b/usr.sbin/lpr/lpd/recvjob.c
@@ -0,0 +1,405 @@
+/*
+ * 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 */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)recvjob.c 8.2 (Berkeley) 4/27/95";
+#endif /* not lint */
+#endif
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+/*
+ * Receive printer jobs from the network, queue them and
+ * start the printer daemon.
+ */
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <errno.h>
+#include <syslog.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "lp.h"
+#include "lp.local.h"
+#include "ctlinfo.h"
+#include "extern.h"
+#include "pathnames.h"
+
+#define ack() (void) write(STDOUT_FILENO, sp, (size_t)1);
+
+/*
+ * The buffer size to use when reading/writing spool files.
+ */
+#define SPL_BUFSIZ BUFSIZ
+
+static char dfname[NAME_MAX]; /* data files */
+static int minfree; /* keep at least minfree blocks available */
+static const char *sp = "";
+static char tfname[NAME_MAX]; /* tmp copy of cf before linking */
+
+static int chksize(int _size);
+static void frecverr(const char *_msg, ...) __printf0like(1, 2);
+static int noresponse(void);
+static void rcleanup(int _signo);
+static int read_number(const char *_fn);
+static int readfile(struct printer *_pp, char *_file, size_t _size);
+static int readjob(struct printer *_pp);
+
+
+void
+recvjob(const char *printer)
+{
+ struct stat stb;
+ int status;
+ struct printer myprinter, *pp = &myprinter;
+
+ /*
+ * Perform lookup for printer name or abbreviation
+ */
+ init_printer(pp);
+ status = getprintcap(printer, pp);
+ switch (status) {
+ case PCAPERR_OSERR:
+ frecverr("cannot open printer description file");
+ break;
+ case PCAPERR_NOTFOUND:
+ frecverr("unknown printer %s", printer);
+ break;
+ case PCAPERR_TCLOOP:
+ fatal(pp, "potential reference loop detected in printcap file");
+ default:
+ break;
+ }
+
+ (void) close(STDERR_FILENO); /* set up log file */
+ if (open(pp->log_file, O_WRONLY|O_APPEND, 0664) < 0) {
+ syslog(LOG_ERR, "%s: %m", pp->log_file);
+ (void) open(_PATH_DEVNULL, O_WRONLY);
+ }
+
+ if (chdir(pp->spool_dir) < 0)
+ frecverr("%s: chdir(%s): %s", pp->printer, pp->spool_dir,
+ strerror(errno));
+ if (stat(pp->lock_file, &stb) == 0) {
+ if (stb.st_mode & 010) {
+ /* queue is disabled */
+ putchar('\1'); /* return error code */
+ exit(1);
+ }
+ } else if (stat(pp->spool_dir, &stb) < 0)
+ frecverr("%s: stat(%s): %s", pp->printer, pp->spool_dir,
+ strerror(errno));
+ minfree = 2 * read_number("minfree"); /* scale KB to 512 blocks */
+ signal(SIGTERM, rcleanup);
+ signal(SIGPIPE, rcleanup);
+
+ if (readjob(pp))
+ printjob(pp);
+}
+
+/*
+ * Read printer jobs sent by lpd and copy them to the spooling directory.
+ * Return the number of jobs successfully transfered.
+ */
+static int
+readjob(struct printer *pp)
+{
+ register int size;
+ int cfcnt, dfcnt;
+ char *cp, *clastp, *errmsg;
+ char givenid[32], givenhost[MAXHOSTNAMELEN];
+
+ ack();
+ cfcnt = 0;
+ dfcnt = 0;
+ for (;;) {
+ /*
+ * Read a command to tell us what to do
+ */
+ cp = line;
+ clastp = line + sizeof(line) - 1;
+ do {
+ size = read(STDOUT_FILENO, cp, (size_t)1);
+ if (size != (ssize_t)1) {
+ if (size < (ssize_t)0) {
+ frecverr("%s: lost connection",
+ pp->printer);
+ /*NOTREACHED*/
+ }
+ return (cfcnt);
+ }
+ } while ((*cp++ != '\n') && (cp <= clastp));
+ if (cp > clastp) {
+ frecverr("%s: readjob overflow", pp->printer);
+ /*NOTREACHED*/
+ }
+ *--cp = '\0';
+ cp = line;
+ switch (*cp++) {
+ case '\1': /* cleanup because data sent was bad */
+ rcleanup(0);
+ continue;
+
+ case '\2': /* read cf file */
+ size = 0;
+ dfcnt = 0;
+ while (*cp >= '0' && *cp <= '9')
+ size = size * 10 + (*cp++ - '0');
+ if (*cp++ != ' ')
+ break;
+ /*
+ * host name has been authenticated, we use our
+ * view of the host name since we may be passed
+ * something different than what gethostbyaddr()
+ * returns
+ */
+ strlcpy(cp + 6, from_host, sizeof(line)
+ + (size_t)(line - cp - 6));
+ if (strchr(cp, '/')) {
+ frecverr("readjob: %s: illegal path name", cp);
+ /*NOTREACHED*/
+ }
+ strlcpy(tfname, cp, sizeof(tfname));
+ tfname[sizeof (tfname) - 1] = '\0';
+ tfname[0] = 't';
+ if (!chksize(size)) {
+ (void) write(STDOUT_FILENO, "\2", (size_t)1);
+ continue;
+ }
+ if (!readfile(pp, tfname, (size_t)size)) {
+ rcleanup(0);
+ continue;
+ }
+ errmsg = ctl_renametf(pp->printer, tfname);
+ tfname[0] = '\0';
+ if (errmsg != NULL) {
+ frecverr("%s: %s", pp->printer, errmsg);
+ /*NOTREACHED*/
+ }
+ cfcnt++;
+ continue;
+
+ case '\3': /* read df file */
+ *givenid = '\0';
+ *givenhost = '\0';
+ size = 0;
+ while (*cp >= '0' && *cp <= '9')
+ size = size * 10 + (*cp++ - '0');
+ if (*cp++ != ' ')
+ break;
+ if (strchr(cp, '/')) {
+ frecverr("readjob: %s: illegal path name", cp);
+ /*NOTREACHED*/
+ }
+ if (!chksize(size)) {
+ (void) write(STDOUT_FILENO, "\2", (size_t)1);
+ continue;
+ }
+ strlcpy(dfname, cp, sizeof(dfname));
+ dfcnt++;
+ trstat_init(pp, dfname, dfcnt);
+ (void) readfile(pp, dfname, (size_t)size);
+ trstat_write(pp, TR_RECVING, (size_t)size, givenid,
+ from_host, givenhost);
+ continue;
+ }
+ frecverr("protocol screwup: %s", line);
+ /*NOTREACHED*/
+ }
+}
+
+/*
+ * Read files send by lpd and copy them to the spooling directory.
+ */
+static int
+readfile(struct printer *pp, char *file, size_t size)
+{
+ register char *cp;
+ char buf[SPL_BUFSIZ];
+ size_t amt, i;
+ int err, fd, j;
+
+ fd = open(file, O_CREAT|O_EXCL|O_WRONLY, FILMOD);
+ if (fd < 0) {
+ frecverr("%s: readfile: error on open(%s): %s",
+ pp->printer, file, strerror(errno));
+ /*NOTREACHED*/
+ }
+ ack();
+ err = 0;
+ for (i = 0; i < size; i += SPL_BUFSIZ) {
+ amt = SPL_BUFSIZ;
+ cp = buf;
+ if (i + amt > size)
+ amt = size - i;
+ do {
+ j = read(STDOUT_FILENO, cp, amt);
+ if (j <= 0) {
+ frecverr("%s: lost connection", pp->printer);
+ /*NOTREACHED*/
+ }
+ amt -= j;
+ cp += j;
+ } while (amt > 0);
+ amt = SPL_BUFSIZ;
+ if (i + amt > size)
+ amt = size - i;
+ if (write(fd, buf, amt) != (ssize_t)amt) {
+ err++;
+ break;
+ }
+ }
+ (void) close(fd);
+ if (err) {
+ frecverr("%s: write error on close(%s)", pp->printer, file);
+ /*NOTREACHED*/
+ }
+ if (noresponse()) { /* file sent had bad data in it */
+ if (strchr(file, '/') == NULL)
+ (void) unlink(file);
+ return (0);
+ }
+ ack();
+ return (1);
+}
+
+static int
+noresponse(void)
+{
+ char resp;
+
+ if (read(STDOUT_FILENO, &resp, (size_t)1) != 1) {
+ frecverr("lost connection in noresponse()");
+ /*NOTREACHED*/
+ }
+ if (resp == '\0')
+ return(0);
+ return(1);
+}
+
+/*
+ * Check to see if there is enough space on the disk for size bytes.
+ * 1 == OK, 0 == Not OK.
+ */
+static int
+chksize(int size)
+{
+ int64_t spacefree;
+ struct statfs sfb;
+
+ if (statfs(".", &sfb) < 0) {
+ syslog(LOG_ERR, "%s: %m", "statfs(\".\")");
+ return (1);
+ }
+ spacefree = sfb.f_bavail * (sfb.f_bsize / 512);
+ size = (size + 511) / 512;
+ if (minfree + size > spacefree)
+ return(0);
+ return(1);
+}
+
+static int
+read_number(const char *fn)
+{
+ char lin[80];
+ register FILE *fp;
+
+ if ((fp = fopen(fn, "r")) == NULL)
+ return (0);
+ if (fgets(lin, sizeof(lin), fp) == NULL) {
+ fclose(fp);
+ return (0);
+ }
+ fclose(fp);
+ return (atoi(lin));
+}
+
+/*
+ * Remove all the files associated with the current job being transfered.
+ */
+static void
+rcleanup(int signo __unused)
+{
+ if (tfname[0] && strchr(tfname, '/') == NULL)
+ (void) unlink(tfname);
+ if (dfname[0] && strchr(dfname, '/') == NULL) {
+ do {
+ do
+ (void) unlink(dfname);
+ while (dfname[2]-- != 'A');
+ dfname[2] = 'z';
+ } while (dfname[0]-- != 'd');
+ }
+ dfname[0] = '\0';
+}
+
+#include <stdarg.h>
+
+static void
+frecverr(const char *msg, ...)
+{
+ va_list ap;
+ va_start(ap, msg);
+ syslog(LOG_ERR, "Error receiving job from %s:", from_host);
+ vsyslog(LOG_ERR, msg, ap);
+ va_end(ap);
+ /*
+ * rcleanup is not called until AFTER logging the error message,
+ * because rcleanup will zap some variables which may have been
+ * supplied as parameters for that msg...
+ */
+ rcleanup(0);
+ /*
+ * Add a minimal delay before returning the final error code to
+ * the sending host. This just in case that machine responds
+ * this error by INSTANTLY retrying (and instantly re-failing...).
+ * It would be stupid of the sending host to do that, but if there
+ * was a broken implementation which did it, the result might be
+ * obscure performance problems and a flood of syslog messages on
+ * the receiving host.
+ */
+ sleep(2); /* a paranoid throttling measure */
+ putchar('\1'); /* return error code */
+ exit(1);
+}
diff --git a/usr.sbin/lpr/lpq/Makefile b/usr.sbin/lpr/lpq/Makefile
new file mode 100644
index 0000000..7ea22fa
--- /dev/null
+++ b/usr.sbin/lpr/lpq/Makefile
@@ -0,0 +1,15 @@
+# From: @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+BINDIR= /usr/bin
+
+PROG= lpq
+BINOWN= root
+BINGRP= daemon
+BINMODE= 6555
+
+CFLAGS+= -I${.CURDIR}/../common_source
+
+LIBADD= lpr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/lpq/Makefile.depend b/usr.sbin/lpr/lpq/Makefile.depend
new file mode 100644
index 0000000..5d7143d
--- /dev/null
+++ b/usr.sbin/lpr/lpq/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ usr.sbin/lpr/common_source \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/lpq/lpq.1 b/usr.sbin/lpr/lpq/lpq.1
new file mode 100644
index 0000000..0efc982
--- /dev/null
+++ b/usr.sbin/lpr/lpq/lpq.1
@@ -0,0 +1,138 @@
+.\" Copyright (c) 1983, 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.
+.\"
+.\" @(#)lpq.1 8.2 (Berkeley) 4/28/95
+.\" $FreeBSD$
+.\"
+.Dd April 28, 1995
+.Dt LPQ 1
+.Os
+.Sh NAME
+.Nm lpq
+.Nd spool queue examination program
+.Sh SYNOPSIS
+.Nm
+.Op Fl a
+.Op Fl l
+.Op Fl P Ns Ar printer
+.Op job # ...\&
+.Op user ...\&
+.Sh DESCRIPTION
+The
+.Nm
+utility examines the spooling area used by
+.Xr lpd 8
+for printing files on the line printer, and reports the status of the
+specified jobs or all jobs associated with a user.
+The
+.Nm
+utility invoked
+without any arguments reports on any jobs currently in the queue.
+.Pp
+Options:
+.Bl -tag -width indent
+.It Fl P
+Specify a particular printer, otherwise the default
+line printer is used (or the value of the
+.Ev PRINTER
+variable in the
+environment).
+All other arguments supplied are interpreted as user
+names or job numbers to filter out only those jobs of interest.
+.It Fl l
+Information about each of the files comprising the job entry
+is printed.
+Normally, only as much information as will fit on one line is displayed.
+.It Fl a
+Report on the local queues for all printers,
+rather than just the specified printer.
+.El
+.Pp
+For each job submitted (i.e., invocation of
+.Xr lpr 1 )
+.Nm
+reports the user's name, current rank in the queue, the
+names of files comprising the job, the job identifier (a number which
+may be supplied to
+.Xr lprm 1
+for removing a specific job), and the total size in bytes.
+Job ordering is dependent on
+the algorithm used to scan the spooling directory and is supposed
+to be
+.Tn FIFO
+(First in First Out).
+File names comprising a job may be unavailable
+(when
+.Xr lpr 1
+is used as a sink in a pipeline) in which case the file
+is indicated as ``(standard input)''.
+.Pp
+If
+.Nm
+warns that there is no daemon present (i.e., due to some malfunction),
+the
+.Xr lpc 8
+command can be used to restart the printer daemon.
+.Sh ENVIRONMENT
+If the following environment variable exists, it is used by
+.Nm :
+.Bl -tag -width PRINTER
+.It Ev PRINTER
+Specifies an alternate default printer.
+.El
+.Sh FILES
+.Bl -tag -width "/var/spool/*/lock" -compact
+.It Pa /etc/printcap
+To determine printer characteristics.
+.It Pa /var/spool/*
+The spooling directory, as determined from printcap.
+.It Pa /var/spool/*/cf*
+Control files specifying jobs.
+.It Pa /var/spool/*/lock
+The lock file to obtain the currently active job.
+.El
+.Sh DIAGNOSTICS
+Unable to open various files.
+The lock file being malformed.
+Garbage
+files when there is no daemon active, but files in the spooling directory.
+.Sh SEE ALSO
+.Xr lpr 1 ,
+.Xr lprm 1 ,
+.Xr lpc 8 ,
+.Xr lpd 8
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.Bx 3 .
+.Sh BUGS
+Due to the dynamic nature of the information in the spooling directory
+.Nm
+may report unreliably.
+Output formatting is sensitive to the line length of the terminal;
+this can results in widely spaced columns.
diff --git a/usr.sbin/lpr/lpq/lpq.c b/usr.sbin/lpr/lpq/lpq.c
new file mode 100644
index 0000000..f39c81b
--- /dev/null
+++ b/usr.sbin/lpr/lpq/lpq.c
@@ -0,0 +1,195 @@
+/*
+ * 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 */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)lpq.c 8.3 (Berkeley) 5/10/95";
+#endif /* not lint */
+#endif
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+/*
+ * Spool Queue examination program
+ *
+ * lpq [-a] [-l] [-Pprinter] [user...] [job...]
+ *
+ * -a show all non-null queues on the local machine
+ * -l long output
+ * -P used to identify printer as per lpr/lprm
+ */
+
+#include <sys/param.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "lp.h"
+#include "lp.local.h"
+#include "pathnames.h"
+
+int requ[MAXREQUESTS]; /* job number of spool entries */
+int requests; /* # of spool requests */
+char *user[MAXUSERS]; /* users to process */
+int users; /* # of users in user array */
+
+uid_t uid, euid;
+
+static int ckqueue(const struct printer *_pp);
+static void usage(void);
+int main(int _argc, char **_argv);
+
+int
+main(int argc, char **argv)
+{
+ int ch, aflag, lflag;
+ const char *printer;
+ struct printer myprinter, *pp = &myprinter;
+
+ printer = NULL;
+ euid = geteuid();
+ uid = getuid();
+ PRIV_END
+ progname = *argv;
+ if (gethostname(local_host, sizeof(local_host)))
+ err(1, "gethostname");
+ openlog("lpd", 0, LOG_LPR);
+
+ aflag = lflag = 0;
+ while ((ch = getopt(argc, argv, "alP:")) != -1)
+ switch((char)ch) {
+ case 'a':
+ ++aflag;
+ break;
+ case 'l': /* long output */
+ ++lflag;
+ break;
+ case 'P': /* printer name */
+ printer = optarg;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+
+ if (!aflag && printer == NULL && (printer = getenv("PRINTER")) == NULL)
+ printer = DEFLP;
+
+ for (argc -= optind, argv += optind; argc; --argc, ++argv)
+ if (isdigit(argv[0][0])) {
+ if (requests >= MAXREQUESTS)
+ fatal(0, "too many requests");
+ requ[requests++] = atoi(*argv);
+ }
+ else {
+ if (users >= MAXUSERS)
+ fatal(0, "too many users");
+ user[users++] = *argv;
+ }
+
+ if (aflag) {
+ int more, status;
+
+ more = firstprinter(pp, &status);
+ if (status)
+ goto looperr;
+ while (more) {
+ if (ckqueue(pp) > 0) {
+ printf("%s:\n", pp->printer);
+ displayq(pp, lflag);
+ printf("\n");
+ }
+ do {
+ more = nextprinter(pp, &status);
+looperr:
+ switch (status) {
+ case PCAPERR_TCOPEN:
+ printf("warning: %s: unresolved "
+ "tc= reference(s) ",
+ pp->printer);
+ case PCAPERR_SUCCESS:
+ break;
+ default:
+ fatal(pp, "%s", pcaperr(status));
+ }
+ } while (more && status);
+ }
+ } else {
+ int status;
+
+ init_printer(pp);
+ status = getprintcap(printer, pp);
+ if (status < 0)
+ fatal(pp, "%s", pcaperr(status));
+
+ displayq(pp, lflag);
+ }
+ exit(0);
+}
+
+static int
+ckqueue(const struct printer *pp)
+{
+ register struct dirent *d;
+ DIR *dirp;
+ char *spooldir;
+
+ spooldir = pp->spool_dir;
+ if ((dirp = opendir(spooldir)) == NULL)
+ return (-1);
+ while ((d = readdir(dirp)) != NULL) {
+ if (d->d_name[0] != 'c' || d->d_name[1] != 'f')
+ continue; /* daemon control files only */
+ closedir(dirp);
+ return (1); /* found something */
+ }
+ closedir(dirp);
+ return (0);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: lpq [-a] [-l] [-Pprinter] [user ...] [job ...]\n");
+ exit(1);
+}
diff --git a/usr.sbin/lpr/lpr/Makefile b/usr.sbin/lpr/lpr/Makefile
new file mode 100644
index 0000000..2841a79
--- /dev/null
+++ b/usr.sbin/lpr/lpr/Makefile
@@ -0,0 +1,20 @@
+# From: @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../common_source
+
+BINDIR= /usr/bin
+
+PROG= lpr
+MAN= lpr.1 printcap.5
+BINOWN= root
+BINGRP= daemon
+BINMODE= 6555
+
+CFLAGS+= -I${.CURDIR}/../common_source
+
+WARNS?= 2
+
+LIBADD= lpr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/lpr/Makefile.depend b/usr.sbin/lpr/lpr/Makefile.depend
new file mode 100644
index 0000000..5d7143d
--- /dev/null
+++ b/usr.sbin/lpr/lpr/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ usr.sbin/lpr/common_source \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/lpr/lpr.1 b/usr.sbin/lpr/lpr/lpr.1
new file mode 100644
index 0000000..00695b9
--- /dev/null
+++ b/usr.sbin/lpr/lpr/lpr.1
@@ -0,0 +1,321 @@
+.\" Copyright (c) 1980, 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.
+.\"
+.\" From @(#)lpr.1 8.1 (Berkeley) 6/6/93
+.\" $FreeBSD$
+.\" "
+.Dd June 6, 1993
+.Dt LPR 1
+.Os
+.Sh NAME
+.Nm lpr
+.Nd off line print
+.Sh SYNOPSIS
+.Nm
+.Op Fl P Ns Ar printer
+.Op Fl \&# Ns Ar num
+.Op Fl C Ar class
+.Op Fl J Ar job
+.Op Fl L Ar locale
+.Op Fl T Ar title
+.Op Fl U Ar user
+.Op Fl Z Ar daemon-options
+.Op Fl i Ar numcols
+.Op Fl 1234 Ar font
+.Op Fl w Ar num
+.Op Fl cdfghlnmprstv
+.Op Ar name ...
+.Sh DESCRIPTION
+The
+.Nm
+utility uses a spooling daemon to print the named files when facilities
+become available.
+If no names appear, the standard input is assumed.
+.Pp
+The following single letter options are used to notify the line printer
+spooler that the files are not standard text files.
+The spooling daemon will
+use the appropriate filters to print the data accordingly.
+Note that not all spoolers implement filters for all data types,
+and some sites may use these types for other purposes than the ones
+described here.
+.Bl -tag -width indent
+.It Fl d
+The files are assumed to contain data in
+.Tn DVI
+format from the
+.Tn TeX
+typesetting system.
+.It Fl f
+Use a filter which interprets the first character of each line as a
+standard
+.Tn FORTRAN
+carriage control character.
+.It Fl l
+Use a filter which allows control characters to be printed and suppresses
+page breaks.
+.It Fl p
+Use
+.Xr pr 1
+to format the files.
+.El
+.Pp
+The following options are historical and not directly supported by any
+software included in
+.Fx .
+.Bl -tag -width indent
+.It Fl c
+The files are assumed to contain data produced by
+.Xr cifplot 1 .
+.It Fl g
+The files are assumed to contain standard plot data as produced by the
+.Ux
+.Xr plot 3
+routines.
+.It Fl n
+The files are assumed to contain data from
+.Em ditroff
+(device independent troff).
+.It Fl t
+The files are assumed to contain
+.Tn C/A/T
+phototypesetter commands from ancient versions of
+.Ux
+.Xr troff 1 .
+.It Fl v
+The files are assumed to contain a raster image for devices like the
+Benson Varian.
+.El
+.Pp
+These options apply to the handling of
+the print job:
+.Bl -tag -width indent
+.It Fl P
+Force output to a specific printer.
+Normally,
+the default printer is used (site dependent), or the value of the
+environment variable
+.Ev PRINTER
+is used.
+.It Fl h
+Suppress the printing of the burst page.
+.It Fl m
+Send mail upon completion.
+.It Fl r
+Remove the file upon completion of spooling or upon completion of
+printing (with the
+.Fl s
+option).
+.It Fl s
+Use symbolic links.
+Usually files are copied to the spool directory.
+The
+.Fl s
+option will use
+.Xr symlink 2
+to link data files rather than trying to copy them so large files can be
+printed.
+This means the files should
+not be modified or removed until they have been printed.
+.El
+.Pp
+The remaining options apply to copies, the page display, and headers:
+.Bl -tag -width indent
+.It Fl \&# Ns Ar num
+The quantity
+.Ar num
+is the number of copies desired of each file named.
+For example,
+.Bd -literal -offset indent
+lpr \-#3 foo.c bar.c more.c
+.Ed
+would result in 3 copies of the file foo.c, followed by 3 copies
+of the file bar.c, etc.
+On the other hand,
+.Bd -literal -offset indent
+cat foo.c bar.c more.c \&| lpr \-#3
+.Ed
+.Pp
+will give three copies of the concatenation of the files.
+Often
+a site will disable this feature to encourage use of a photocopier
+instead.
+.It Xo
+.Fl Ns Op Cm 1234
+.Ar font
+.Xc
+Specifies a
+.Ar font
+to be mounted on font position
+.Ar i .
+The daemon
+will construct a
+.Li .railmag
+file referencing
+the font pathname.
+.It Fl C Ar class
+Job classification
+to use on the burst page.
+For example,
+.Bd -literal -offset indent
+lpr \-C EECS foo.c
+.Ed
+.Pp
+causes the system name (the name returned by
+.Xr hostname 1 )
+to be replaced on the burst page by
+.Tn EECS ,
+and the file foo.c to be printed.
+.It Fl J Ar job
+Job name to print on the burst page.
+Normally, the first file's name is used.
+.It Fl L Ar locale
+Use
+.Ar locale
+specified as argument instead of one found in environment.
+(Only effective when filtering through
+.Xr pr 1
+is requested using the
+.Fl p
+option.)
+.It Fl T Ar title
+Title name for
+.Xr pr 1 ,
+instead of the file name.
+.It Fl U Ar user
+User name to print on the burst page,
+also for accounting purposes.
+This option is only honored if the real user-id is daemon
+(or that specified in the printcap file instead of daemon),
+and is intended for those instances where print filters wish to requeue jobs.
+.It Fl Z Ar daemon-options
+Some spoolers, such as
+.Tn LPRng ,
+accept additional per-job options using a
+.Ql Z
+control line.
+When
+.Fl Z
+is specified, and
+.Fl p
+.Pq Xr pr 1
+is not requested, the specified
+.Ar daemon-options
+will be passed to the remote
+.Tn LPRng
+spooler.
+.It Fl i Ar numcols
+The output is indented by
+.Pq Ar numcols .
+.It Fl w Ar num
+Uses
+.Ar num
+as the page width for
+.Xr pr 1 .
+.El
+.Sh ENVIRONMENT
+If the following environment variable exists, it is used by
+.Nm :
+.Bl -tag -width PRINTER
+.It Ev PRINTER
+Specifies an alternate default printer.
+.El
+.Sh FILES
+.Bl -tag -width /var/spool/output/*/tf* -compact
+.It Pa /etc/passwd
+Personal identification.
+.It Pa /etc/printcap
+Printer capabilities data base.
+.It Pa /usr/sbin/lpd
+Line printer daemons.
+.It Pa /var/spool/output/*
+Directories used for spooling.
+.It Pa /var/spool/output/*/cf*
+Daemon control files.
+.It Pa /var/spool/output/*/df*
+Data files specified in "cf" files.
+.It Pa /var/spool/output/*/tf*
+Temporary copies of "cf" files.
+.El
+.Sh DIAGNOSTICS
+If you try to spool too large a file, it will be truncated.
+The
+.Nm
+utility will object to printing binary files.
+If a user other than root prints a file and spooling is disabled,
+.Nm
+will print a message saying so and will not put jobs in the queue.
+If a connection to
+.Xr lpd 8
+on the local machine cannot be made,
+.Nm
+will say that the daemon cannot be started.
+Diagnostics may be printed in the daemon's log file
+regarding missing spool files by
+.Xr lpd 8 .
+.Sh SEE ALSO
+.Xr lpq 1 ,
+.Xr lprm 1 ,
+.Xr pr 1 ,
+.Xr symlink 2 ,
+.Xr printcap 5 ,
+.Xr lpc 8 ,
+.Xr lpd 8
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 3 .
+.Sh BUGS
+Fonts for
+.Xr troff 1
+and
+.Tn TeX
+reside on the host with the printer.
+It is currently not possible to
+use local font libraries.
+.Pp
+The
+.Ql Z
+control file line is used for two different purposes; for
+standard
+.Fx
+.Xr lpd 8 ,
+it specifies a locale to be passed to
+.Xr pr 1 .
+For
+.Tn LPRng
+.Xr lpd 8 ,
+it specifies additional options to be interpreted by the spooler's
+input and output filters.
+When submitting jobs via
+.Nm ,
+.Fl p
+.Fl L Ar locale
+is used in the former context, and
+.Fl Z Ar daemon-options
+is used in the latter.
diff --git a/usr.sbin/lpr/lpr/lpr.c b/usr.sbin/lpr/lpr/lpr.c
new file mode 100644
index 0000000..9a4382f
--- /dev/null
+++ b/usr.sbin/lpr/lpr/lpr.c
@@ -0,0 +1,892 @@
+/*
+ * Copyright (c) 1983, 1989, 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.
+ * 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.
+ */
+
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1983, 1989, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)lpr.c 8.4 (Berkeley) 4/28/95";
+#endif /* not lint */
+#endif
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+/*
+ * lpr -- off line print
+ *
+ * Allows multiple printers and printers on remote machines by
+ * using information from a printer data base.
+ */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <netinet/in.h> /* N_BADMAG uses ntohl() */
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <a.out.h>
+#include <err.h>
+#include <locale.h>
+#include <signal.h>
+#include <syslog.h>
+#include <pwd.h>
+#include <grp.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include "lp.h"
+#include "lp.local.h"
+#include "pathnames.h"
+
+static char *cfname; /* daemon control files, linked from tf's */
+static char *class = local_host; /* class title on header page */
+static char *dfname; /* data files */
+static char *fonts[4]; /* troff font names */
+static char format = 'f'; /* format char for printing files */
+static int hdr = 1; /* print header or not (default is yes) */
+static int iflag; /* indentation wanted */
+static int inchar; /* location to increment char in file names */
+static int indent; /* amount to indent */
+static const char *jobname; /* job name on header page */
+static int mailflg; /* send mail */
+static int nact; /* number of jobs to act on */
+static int ncopies = 1; /* # of copies to make */
+static char *lpr_username; /* person sending the print job(s) */
+static int qflag; /* q job, but don't exec daemon */
+static int rflag; /* remove files upon completion */
+static int sflag; /* symbolic link flag */
+static int tfd; /* control file descriptor */
+static char *tfname; /* tmp copy of cf before linking */
+static char *title; /* pr'ing title */
+static char *locale; /* pr'ing locale */
+static int userid; /* user id */
+static char *Uflag; /* user name specified with -U flag */
+static char *width; /* width for versatec printing */
+static char *Zflag; /* extra filter options for LPRng servers */
+
+static struct stat statb;
+
+static void card(int _c, const char *_p2);
+static int checkwriteperm(const char *_file, const char *_directory);
+static void chkprinter(const char *_ptrname, struct printer *_pp);
+static void cleanup(int _signo);
+static void copy(const struct printer *_pp, int _f, const char _n[]);
+static char *itoa(int _i);
+static const char *linked(const char *_file);
+int main(int _argc, char *_argv[]);
+static char *lmktemp(const struct printer *_pp, const char *_id,
+ int _num, int len);
+static void mktemps(const struct printer *_pp);
+static int nfile(char *_n);
+static int test(const char *_file);
+static void usage(void);
+
+uid_t uid, euid;
+
+int
+main(int argc, char *argv[])
+{
+ struct passwd *pw;
+ struct group *gptr;
+ const char *arg, *cp, *printer;
+ char *p;
+ char buf[BUFSIZ];
+ int c, i, f, errs;
+ int ret, didlink;
+ struct stat stb;
+ struct stat statb1, statb2;
+ struct printer myprinter, *pp = &myprinter;
+
+ printer = NULL;
+ euid = geteuid();
+ uid = getuid();
+ PRIV_END
+ if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
+ signal(SIGHUP, cleanup);
+ if (signal(SIGINT, SIG_IGN) != SIG_IGN)
+ signal(SIGINT, cleanup);
+ if (signal(SIGQUIT, SIG_IGN) != SIG_IGN)
+ signal(SIGQUIT, cleanup);
+ if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
+ signal(SIGTERM, cleanup);
+
+ progname = argv[0];
+ gethostname(local_host, sizeof(local_host));
+ openlog("lpd", 0, LOG_LPR);
+
+ errs = 0;
+ while ((c = getopt(argc, argv,
+ ":#:1:2:3:4:C:J:L:P:T:U:Z:cdfghi:lnmprstvw:"))
+ != -1)
+ switch (c) {
+ case '#': /* n copies */
+ i = strtol(optarg, &p, 10);
+ if (*p)
+ errx(1, "Bad argument to -#, number expected");
+ if (i > 0)
+ ncopies = i;
+ break;
+
+ case '1': /* troff fonts */
+ case '2':
+ case '3':
+ case '4':
+ fonts[optopt - '1'] = optarg;
+ break;
+
+ case 'C': /* classification spec */
+ hdr++;
+ class = optarg;
+ break;
+
+ case 'J': /* job name */
+ hdr++;
+ jobname = optarg;
+ break;
+
+ case 'P': /* specifiy printer name */
+ printer = optarg;
+ break;
+
+ case 'L': /* pr's locale */
+ locale = optarg;
+ break;
+
+ case 'T': /* pr's title line */
+ title = optarg;
+ break;
+
+ case 'U': /* user name */
+ hdr++;
+ Uflag = optarg;
+ break;
+
+ case 'Z':
+ Zflag = optarg;
+ break;
+
+ case 'c': /* print cifplot output */
+ case 'd': /* print tex output (dvi files) */
+ case 'g': /* print graph(1G) output */
+ case 'l': /* literal output */
+ case 'n': /* print ditroff output */
+ case 't': /* print troff output (cat files) */
+ case 'p': /* print using ``pr'' */
+ case 'v': /* print vplot output */
+ format = optopt;
+ break;
+
+ case 'f': /* print fortran output */
+ format = 'r';
+ break;
+
+ case 'h': /* nulifiy header page */
+ hdr = 0;
+ break;
+
+ case 'i': /* indent output */
+ iflag++;
+ indent = strtol(optarg, &p, 10);
+ if (*p)
+ errx(1, "Bad argument to -i, number expected");
+ break;
+
+ case 'm': /* send mail when done */
+ mailflg++;
+ break;
+
+ case 'q': /* just queue job */
+ qflag++;
+ break;
+
+ case 'r': /* remove file when done */
+ rflag++;
+ break;
+
+ case 's': /* try to link files */
+ sflag++;
+ break;
+
+ case 'w': /* versatec page width */
+ width = optarg;
+ break;
+
+ case ':': /* catch "missing argument" error */
+ if (optopt == 'i') {
+ iflag++; /* -i without args is valid */
+ indent = 8;
+ } else
+ errs++;
+ break;
+
+ default:
+ errs++;
+ }
+ argc -= optind;
+ argv += optind;
+ if (errs)
+ usage();
+ if (printer == NULL && (printer = getenv("PRINTER")) == NULL)
+ printer = DEFLP;
+ chkprinter(printer, pp);
+ if (pp->no_copies && ncopies > 1)
+ errx(1, "multiple copies are not allowed");
+ if (pp->max_copies > 0 && ncopies > pp->max_copies)
+ errx(1, "only %ld copies are allowed", pp->max_copies);
+ /*
+ * Get the identity of the person doing the lpr using the same
+ * algorithm as lprm. Actually, not quite -- lprm will override
+ * the login name with "root" if the user is running as root;
+ * the daemon actually checks for the string "root" in its
+ * permission checking. Sigh.
+ */
+ userid = getuid();
+ if (Uflag) {
+ if (userid != 0 && userid != pp->daemon_user)
+ errx(1, "only privileged users may use the `-U' flag");
+ lpr_username = Uflag; /* -U person doing 'lpr' */
+ } else {
+ lpr_username = getlogin(); /* person doing 'lpr' */
+ if (userid != pp->daemon_user || lpr_username == 0) {
+ if ((pw = getpwuid(userid)) == NULL)
+ errx(1, "Who are you?");
+ lpr_username = pw->pw_name;
+ }
+ }
+
+ /*
+ * Check for restricted group access.
+ */
+ if (pp->restrict_grp != NULL && userid != pp->daemon_user) {
+ if ((gptr = getgrnam(pp->restrict_grp)) == NULL)
+ errx(1, "Restricted group specified incorrectly");
+ if (gptr->gr_gid != getgid()) {
+ while (*gptr->gr_mem != NULL) {
+ if ((strcmp(lpr_username, *gptr->gr_mem)) == 0)
+ break;
+ gptr->gr_mem++;
+ }
+ if (*gptr->gr_mem == NULL)
+ errx(1, "Not a member of the restricted group");
+ }
+ }
+ /*
+ * Check to make sure queuing is enabled if userid is not root.
+ */
+ lock_file_name(pp, buf, sizeof buf);
+ if (userid && stat(buf, &stb) == 0 && (stb.st_mode & LFM_QUEUE_DIS))
+ errx(1, "Printer queue is disabled");
+ /*
+ * Initialize the control file.
+ */
+ mktemps(pp);
+ tfd = nfile(tfname);
+ PRIV_START
+ (void) fchown(tfd, pp->daemon_user, -1);
+ /* owned by daemon for protection */
+ PRIV_END
+ card('H', local_host);
+ card('P', lpr_username);
+ card('C', class);
+ if (hdr && !pp->no_header) {
+ if (jobname == NULL) {
+ if (argc == 0)
+ jobname = "stdin";
+ else
+ jobname = ((arg = strrchr(argv[0], '/'))
+ ? arg + 1 : argv[0]);
+ }
+ card('J', jobname);
+ card('L', lpr_username);
+ }
+ if (format != 'p' && Zflag != 0)
+ card('Z', Zflag);
+ if (iflag)
+ card('I', itoa(indent));
+ if (mailflg)
+ card('M', lpr_username);
+ if (format == 't' || format == 'n' || format == 'd')
+ for (i = 0; i < 4; i++)
+ if (fonts[i] != NULL)
+ card('1'+i, fonts[i]);
+ if (width != NULL)
+ card('W', width);
+ /*
+ * XXX
+ * Our use of `Z' here is incompatible with LPRng's
+ * use. We assume that the only use of our existing
+ * `Z' card is as shown for `p' format (pr) files.
+ */
+ if (format == 'p') {
+ char *s;
+
+ if (locale)
+ card('Z', locale);
+ else if ((s = setlocale(LC_TIME, "")) != NULL)
+ card('Z', s);
+ }
+
+ /*
+ * Read the files and spool them.
+ */
+ if (argc == 0)
+ copy(pp, 0, " ");
+ else while (argc--) {
+ if (argv[0][0] == '-' && argv[0][1] == '\0') {
+ /* use stdin */
+ copy(pp, 0, " ");
+ argv++;
+ continue;
+ }
+ if ((f = test(arg = *argv++)) < 0)
+ continue; /* file unreasonable */
+
+ if (sflag && (cp = linked(arg)) != NULL) {
+ (void)snprintf(buf, sizeof(buf), "%ju %ju",
+ (uintmax_t)statb.st_dev, (uintmax_t)statb.st_ino);
+ card('S', buf);
+ if (format == 'p')
+ card('T', title ? title : arg);
+ for (i = 0; i < ncopies; i++)
+ card(format, &dfname[inchar-2]);
+ card('U', &dfname[inchar-2]);
+ if (f)
+ card('U', cp);
+ card('N', arg);
+ dfname[inchar]++;
+ nact++;
+ continue;
+ }
+ if (sflag)
+ printf("%s: %s: not linked, copying instead\n",
+ progname, arg);
+
+ if (f) {
+ /*
+ * The user wants the file removed after it is copied
+ * to the spool area, so see if the file can be moved
+ * instead of copy/unlink'ed. This is much faster and
+ * uses less spool space than copying the file. This
+ * can be very significant when running services like
+ * samba, pcnfs, CAP, et al.
+ */
+ PRIV_START
+ didlink = 0;
+ /*
+ * There are several things to check to avoid any
+ * security issues. Some of these are redundant
+ * under BSD's, but are necessary when lpr is built
+ * under some other OS's (which I do do...)
+ */
+ if (lstat(arg, &statb1) < 0)
+ goto nohardlink;
+ if (S_ISLNK(statb1.st_mode))
+ goto nohardlink;
+ if (link(arg, dfname) != 0)
+ goto nohardlink;
+ didlink = 1;
+ /*
+ * Make sure the user hasn't tried to trick us via
+ * any race conditions
+ */
+ if (lstat(dfname, &statb2) < 0)
+ goto nohardlink;
+ if (statb1.st_dev != statb2.st_dev)
+ goto nohardlink;
+ if (statb1.st_ino != statb2.st_ino)
+ goto nohardlink;
+ /*
+ * Skip if the file already had multiple hard links,
+ * because changing the owner and access-bits would
+ * change ALL versions of the file
+ */
+ if (statb2.st_nlink > 2)
+ goto nohardlink;
+ /*
+ * If we can access and remove the original file
+ * without special setuid-ness then this method is
+ * safe. Otherwise, abandon the move and fall back
+ * to the (usual) copy method.
+ */
+ PRIV_END
+ ret = access(dfname, R_OK);
+ if (ret == 0)
+ ret = unlink(arg);
+ PRIV_START
+ if (ret != 0)
+ goto nohardlink;
+ /*
+ * Unlink of user file was successful. Change the
+ * owner and permissions, add entries to the control
+ * file, and skip the file copying step.
+ */
+ chown(dfname, pp->daemon_user, getegid());
+ chmod(dfname, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+ PRIV_END
+ if (format == 'p')
+ card('T', title ? title : arg);
+ for (i = 0; i < ncopies; i++)
+ card(format, &dfname[inchar-2]);
+ card('U', &dfname[inchar-2]);
+ card('N', arg);
+ nact++;
+ continue;
+ nohardlink:
+ if (didlink)
+ unlink(dfname);
+ PRIV_END /* restore old uid */
+ } /* end: if (f) */
+
+ if ((i = open(arg, O_RDONLY)) < 0) {
+ printf("%s: cannot open %s\n", progname, arg);
+ } else {
+ copy(pp, i, arg);
+ (void) close(i);
+ if (f && unlink(arg) < 0)
+ printf("%s: %s: not removed\n", progname, arg);
+ }
+ }
+
+ if (nact) {
+ (void) close(tfd);
+ tfname[inchar]--;
+ /*
+ * Touch the control file to fix position in the queue.
+ */
+ PRIV_START
+ if ((tfd = open(tfname, O_RDWR)) >= 0) {
+ char touch_c;
+
+ if (read(tfd, &touch_c, 1) == 1 &&
+ lseek(tfd, (off_t)0, 0) == 0 &&
+ write(tfd, &touch_c, 1) != 1) {
+ printf("%s: cannot touch %s\n", progname,
+ tfname);
+ tfname[inchar]++;
+ cleanup(0);
+ }
+ (void) close(tfd);
+ }
+ if (link(tfname, cfname) < 0) {
+ printf("%s: cannot rename %s\n", progname, cfname);
+ tfname[inchar]++;
+ cleanup(0);
+ }
+ unlink(tfname);
+ PRIV_END
+ if (qflag) /* just q things up */
+ exit(0);
+ if (!startdaemon(pp))
+ printf("jobs queued, but cannot start daemon.\n");
+ exit(0);
+ }
+ cleanup(0);
+ return (1);
+ /* NOTREACHED */
+}
+
+/*
+ * Create the file n and copy from file descriptor f.
+ */
+static void
+copy(const struct printer *pp, int f, const char n[])
+{
+ register int fd, i, nr, nc;
+ char buf[BUFSIZ];
+
+ if (format == 'p')
+ card('T', title ? title : n);
+ for (i = 0; i < ncopies; i++)
+ card(format, &dfname[inchar-2]);
+ card('U', &dfname[inchar-2]);
+ card('N', n);
+ fd = nfile(dfname);
+ nr = nc = 0;
+ while ((i = read(f, buf, BUFSIZ)) > 0) {
+ if (write(fd, buf, i) != i) {
+ printf("%s: %s: temp file write error\n", progname, n);
+ break;
+ }
+ nc += i;
+ if (nc >= BUFSIZ) {
+ nc -= BUFSIZ;
+ nr++;
+ if (pp->max_blocks > 0 && nr > pp->max_blocks) {
+ printf("%s: %s: copy file is too large\n",
+ progname, n);
+ break;
+ }
+ }
+ }
+ (void) close(fd);
+ if (nc==0 && nr==0)
+ printf("%s: %s: empty input file\n", progname,
+ f ? n : "stdin");
+ else
+ nact++;
+}
+
+/*
+ * Try and link the file to dfname. Return a pointer to the full
+ * path name if successful.
+ */
+static const char *
+linked(const char *file)
+{
+ register char *cp;
+ static char buf[MAXPATHLEN];
+ register int ret;
+
+ if (*file != '/') {
+ if (getcwd(buf, sizeof(buf)) == NULL)
+ return(NULL);
+ while (file[0] == '.') {
+ switch (file[1]) {
+ case '/':
+ file += 2;
+ continue;
+ case '.':
+ if (file[2] == '/') {
+ if ((cp = strrchr(buf, '/')) != NULL)
+ *cp = '\0';
+ file += 3;
+ continue;
+ }
+ }
+ break;
+ }
+ strncat(buf, "/", sizeof(buf) - strlen(buf) - 1);
+ strncat(buf, file, sizeof(buf) - strlen(buf) - 1);
+ file = buf;
+ }
+ PRIV_START
+ ret = symlink(file, dfname);
+ PRIV_END
+ return(ret ? NULL : file);
+}
+
+/*
+ * Put a line into the control file.
+ */
+static void
+card(int c, const char *p2)
+{
+ char buf[BUFSIZ];
+ register char *p1 = buf;
+ size_t len = 2;
+
+ *p1++ = c;
+ while ((c = *p2++) != '\0' && len < sizeof(buf)) {
+ *p1++ = (c == '\n') ? ' ' : c;
+ len++;
+ }
+ *p1++ = '\n';
+ write(tfd, buf, len);
+}
+
+/*
+ * Create a new file in the spool directory.
+ */
+static int
+nfile(char *n)
+{
+ register int f;
+ int oldumask = umask(0); /* should block signals */
+
+ PRIV_START
+ f = open(n, O_WRONLY | O_EXCL | O_CREAT, FILMOD);
+ (void) umask(oldumask);
+ if (f < 0) {
+ printf("%s: cannot create %s\n", progname, n);
+ cleanup(0);
+ }
+ if (fchown(f, userid, -1) < 0) {
+ printf("%s: cannot chown %s\n", progname, n);
+ cleanup(0); /* cleanup does exit */
+ }
+ PRIV_END
+ if (++n[inchar] > 'z') {
+ if (++n[inchar-2] == 't') {
+ printf("too many files - break up the job\n");
+ cleanup(0);
+ }
+ n[inchar] = 'A';
+ } else if (n[inchar] == '[')
+ n[inchar] = 'a';
+ return(f);
+}
+
+/*
+ * Cleanup after interrupts and errors.
+ */
+static void
+cleanup(int signo __unused)
+{
+ register int i;
+
+ signal(SIGHUP, SIG_IGN);
+ signal(SIGINT, SIG_IGN);
+ signal(SIGQUIT, SIG_IGN);
+ signal(SIGTERM, SIG_IGN);
+ i = inchar;
+ PRIV_START
+ if (tfname)
+ do
+ unlink(tfname);
+ while (tfname[i]-- != 'A');
+ if (cfname)
+ do
+ unlink(cfname);
+ while (cfname[i]-- != 'A');
+ if (dfname)
+ do {
+ do
+ unlink(dfname);
+ while (dfname[i]-- != 'A');
+ dfname[i] = 'z';
+ } while (dfname[i-2]-- != 'd');
+ exit(1);
+}
+
+/*
+ * Test to see if this is a printable file.
+ * Return -1 if it is not, 0 if its printable, and 1 if
+ * we should remove it after printing.
+ */
+static int
+test(const char *file)
+{
+ struct exec execb;
+ size_t dlen;
+ int fd;
+ char *cp, *dirpath;
+
+ if (access(file, 4) < 0) {
+ printf("%s: cannot access %s\n", progname, file);
+ return(-1);
+ }
+ if (stat(file, &statb) < 0) {
+ printf("%s: cannot stat %s\n", progname, file);
+ return(-1);
+ }
+ if ((statb.st_mode & S_IFMT) == S_IFDIR) {
+ printf("%s: %s is a directory\n", progname, file);
+ return(-1);
+ }
+ if (statb.st_size == 0) {
+ printf("%s: %s is an empty file\n", progname, file);
+ return(-1);
+ }
+ if ((fd = open(file, O_RDONLY)) < 0) {
+ printf("%s: cannot open %s\n", progname, file);
+ return(-1);
+ }
+ /*
+ * XXX Shall we add a similar test for ELF?
+ */
+ if (read(fd, &execb, sizeof(execb)) == sizeof(execb) &&
+ !N_BADMAG(execb)) {
+ printf("%s: %s is an executable program", progname, file);
+ goto error1;
+ }
+ (void) close(fd);
+ if (rflag) {
+ /*
+ * aside: note that 'cp' is technically a 'const char *'
+ * (because it points into 'file'), even though strrchr
+ * returns a value of type 'char *'.
+ */
+ if ((cp = strrchr(file, '/')) == NULL) {
+ if (checkwriteperm(file,".") == 0)
+ return(1);
+ } else {
+ if (cp == file) {
+ fd = checkwriteperm(file,"/");
+ } else {
+ /* strlcpy will change the '/' to '\0' */
+ dlen = cp - file + 1;
+ dirpath = malloc(dlen);
+ strlcpy(dirpath, file, dlen);
+ fd = checkwriteperm(file, dirpath);
+ free(dirpath);
+ }
+ if (fd == 0)
+ return(1);
+ }
+ printf("%s: %s: is not removable by you\n", progname, file);
+ }
+ return(0);
+
+error1:
+ printf(" and is unprintable\n");
+ (void) close(fd);
+ return(-1);
+}
+
+static int
+checkwriteperm(const char *file, const char *directory)
+{
+ struct stat stats;
+ if (access(directory, W_OK) == 0) {
+ stat(directory, &stats);
+ if (stats.st_mode & S_ISVTX) {
+ stat(file, &stats);
+ if(stats.st_uid == userid) {
+ return(0);
+ }
+ } else return(0);
+ }
+ return(-1);
+}
+
+/*
+ * itoa - integer to string conversion
+ */
+static char *
+itoa(int i)
+{
+ static char b[10] = "########";
+ register char *p;
+
+ p = &b[8];
+ do
+ *p-- = i%10 + '0';
+ while (i /= 10);
+ return(++p);
+}
+
+/*
+ * Perform lookup for printer name or abbreviation --
+ */
+static void
+chkprinter(const char *ptrname, struct printer *pp)
+{
+ int status;
+
+ init_printer(pp);
+ status = getprintcap(ptrname, pp);
+ switch(status) {
+ case PCAPERR_OSERR:
+ case PCAPERR_TCLOOP:
+ errx(1, "%s: %s", ptrname, pcaperr(status));
+ case PCAPERR_NOTFOUND:
+ errx(1, "%s: unknown printer", ptrname);
+ case PCAPERR_TCOPEN:
+ warnx("%s: unresolved tc= reference(s)", ptrname);
+ }
+}
+
+/*
+ * Tell the user what we wanna get.
+ */
+static void
+usage(void)
+{
+ fprintf(stderr, "%s\n",
+"usage: lpr [-Pprinter] [-#num] [-C class] [-J job] [-T title] [-U user]\n"
+ "\t[-Z daemon-options] [-i[numcols]] [-i[numcols]] [-1234 font]\n"
+ "\t[-L locale] [-wnum] [-cdfghlnmprstv] [name ...]");
+ exit(1);
+}
+
+
+/*
+ * Make the temp files.
+ */
+static void
+mktemps(const struct printer *pp)
+{
+ register int len, fd, n;
+ register char *cp;
+ char buf[BUFSIZ];
+
+ (void) snprintf(buf, sizeof(buf), "%s/.seq", pp->spool_dir);
+ PRIV_START
+ if ((fd = open(buf, O_RDWR|O_CREAT, 0664)) < 0) {
+ printf("%s: cannot create %s\n", progname, buf);
+ exit(1);
+ }
+ if (flock(fd, LOCK_EX)) {
+ printf("%s: cannot lock %s\n", progname, buf);
+ exit(1);
+ }
+ PRIV_END
+ n = 0;
+ if ((len = read(fd, buf, sizeof(buf))) > 0) {
+ for (cp = buf; len--; ) {
+ if (*cp < '0' || *cp > '9')
+ break;
+ n = n * 10 + (*cp++ - '0');
+ }
+ }
+ len = strlen(pp->spool_dir) + strlen(local_host) + 8;
+ tfname = lmktemp(pp, "tf", n, len);
+ cfname = lmktemp(pp, "cf", n, len);
+ dfname = lmktemp(pp, "df", n, len);
+ inchar = strlen(pp->spool_dir) + 3;
+ n = (n + 1) % 1000;
+ (void) lseek(fd, (off_t)0, 0);
+ snprintf(buf, sizeof(buf), "%03d\n", n);
+ (void) write(fd, buf, strlen(buf));
+ (void) close(fd); /* unlocks as well */
+}
+
+/*
+ * Make a temp file name.
+ */
+static char *
+lmktemp(const struct printer *pp, const char *id, int num, int len)
+{
+ register char *s;
+
+ if ((s = malloc(len)) == NULL)
+ errx(1, "out of memory");
+ (void) snprintf(s, len, "%s/%sA%03d%s", pp->spool_dir, id, num,
+ local_host);
+ return(s);
+}
diff --git a/usr.sbin/lpr/lpr/printcap.5 b/usr.sbin/lpr/lpr/printcap.5
new file mode 100644
index 0000000..3cccd3a
--- /dev/null
+++ b/usr.sbin/lpr/lpr/printcap.5
@@ -0,0 +1,434 @@
+.\" 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.
+.\"
+.\" @(#)printcap.5 8.2 (Berkeley) 12/11/93
+.\" $FreeBSD$
+.\"
+.Dd October 11, 2000
+.Dt PRINTCAP 5
+.Os
+.Sh NAME
+.Nm printcap
+.Nd printer capability data base
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+The
+.Nm Printcap
+function
+is a simplified version of the
+.Xr termcap 5
+data base
+used to describe line printers.
+The spooling system accesses the
+.Nm
+file every time it is used, allowing dynamic
+addition and deletion of printers.
+Each entry in the data base
+is used to describe one printer.
+This data base may not be
+substituted for, as is possible for
+.Xr termcap 5 ,
+because it may allow accounting to be bypassed.
+.Pp
+The default printer is normally
+.Em lp ,
+though the environment variable
+.Ev PRINTER
+may be used to override this.
+Each spooling utility supports an option,
+.Fl P Ar printer ,
+to allow explicit naming of a destination printer.
+.Pp
+Refer to the
+.%T "4.3 BSD Line Printer Spooler Manual"
+for a complete discussion on how to setup the database for a given printer.
+.Sh CAPABILITIES
+Refer to
+.Xr termcap 5
+for a description of the file layout.
+.Bl -column Namexxx Typexx "/var/spool/lpdxxxxx"
+.Sy "Name Type Default Description"
+.It "af str" Ta Dv NULL Ta No "name of accounting file"
+.It "br num none if lp is a tty, set the baud rate"
+.Xr ( ioctl 2
+call)
+.It "cf str" Ta Dv NULL Ta No "cifplot data filter"
+.It "ct num 120 TCP connection timeout in seconds"
+.It "df str" Ta Dv NULL Ta No "tex data filter"
+.Tn ( DVI
+format)
+.It "du num 1 UID to run daemon as"
+.It "ff str" Ta So Li \ef Sc Ta No "string to send for a form feed"
+.It "fo bool false print a form feed when device is opened"
+.It "gf str" Ta Dv NULL Ta No "graph data filter"
+.Xr ( plot 3
+format
+.It "hl bool false print the burst header page last"
+.It "ic bool false driver supports (non standard) ioctl to indent printout"
+.It "if str" Ta Dv NULL Ta No "name of text filter which does accounting"
+.It "lf str" Ta Pa /dev/console Ta No "error logging file name"
+.It "lo str" Ta Pa lock Ta No "name of lock file"
+.It "lp str" Ta Pa /dev/lp Ta No "device name to open for output, or" Em port Ns @ Ns Em machine No "to open a TCP socket"
+.It "mc num 0 maximum number of copies which can be requested on"
+.Xr lpr 1 ,
+zero = unlimited
+.It "ms str" Ta Dv NULL Ta No "if lp is a tty, a comma-separated,"
+.Xr stty 1 Ns -like
+list describing the tty modes
+.It "mx num 0 maximum file size (in"
+.Dv BUFSIZ
+blocks), zero = unlimited
+.It "nd str" Ta Dv NULL Ta No "next directory for list of queues (unimplemented)"
+.It "nf str" Ta Dv NULL Ta No "ditroff data filter (device independent troff)"
+.It "of str" Ta Dv NULL Ta No "name of output filtering program"
+.It "pc num 200 price per foot or page in hundredths of cents"
+.It "pl num 66 page length (in lines)"
+.It "pw num 132 page width (in characters)"
+.It "px num 0 page width in pixels (horizontal)"
+.It "py num 0 page length in pixels (vertical)"
+.It "rc bool false when sending to a remote host, resend copies (see below)"
+.It "rf str" Ta Dv NULL Ta No "filter for printing"
+.Tn FORTRAN
+style text files
+.It "rg str" Ta Dv NULL Ta No "restricted group. Only members of group allowed access"
+.It "rm str" Ta Dv NULL Ta No "machine name for remote printer"
+.It "rp str" Ta Pa lp Ta No "remote printer name argument"
+.It "rs bool false restrict remote users to those with local accounts"
+.It "rw bool false open the printer device for reading and writing"
+.It "sb bool false short banner (one line only)"
+.It "sc bool false suppress multiple copies"
+.It "sd str" Ta Pa /var/spool/lpd Ta No "spool directory"
+.It "sf bool false suppress form feeds"
+.It "sh bool false suppress printing of burst page header"
+.It "sr str" Ta Dv NULL Ta No "file name to hold statistics of each datafile as it is received"
+.It "ss str" Ta Dv NULL Ta No "file name to hold statistics of each datafile as it is sent"
+.It "st str" Ta Pa status Ta No "status file name"
+.It "tf str" Ta Dv NULL Ta No "troff data filter (cat phototypesetter)"
+.It "tr str" Ta Dv NULL Ta No "trailer string to print when queue empties"
+.It "vf str" Ta Dv NULL Ta No "raster image filter"
+.El
+.Pp
+Each two-letter capability has a human-readable alternate name.
+.Bl -column "Short form" "Long form"
+.Sy "Short form Long form"
+.It "af acct.file"
+.It "br tty.rate"
+.It "cf filt.cifplot"
+.It "ct remote.timeout"
+.It "df filt.dvi"
+.It "du daemon.user"
+.It "ff job.formfeed"
+.It "fo job.topofform"
+.It "gf filt.plot"
+.It "hl banner.last"
+.It "if filt.input"
+.It "lf spool.log"
+.It "lo spool.lock"
+.It "lp tty.device"
+.It "mc max.copies"
+.It "ms tty.mode"
+.It "mx max.blocks"
+.It "nf filt.ditroff"
+.It "of filt.output"
+.It "pc acct.price"
+.It "pl page.length"
+.It "pw page.width"
+.It "px page.pwidth"
+.It "py page.plength"
+.It "rc remote.resend_copies"
+.It "rf filt.fortran"
+.It "rg daemon.restrictgrp"
+.It "rm remote.host"
+.It "rp remote.queue"
+.It "rs daemon.restricted"
+.It "rw tty.rw"
+.It "sb banner.short"
+.It "sc job.no_copies"
+.It "sd spool.dir"
+.It "sf job.no_formfeed"
+.It "sh banner.disable"
+.It "sr stat.recv"
+.It "ss stat.send"
+.It "st spool.status"
+.It "tf filt.troff"
+.It "tr job.trailer"
+.It "vf filt.raster"
+.El
+.Pp
+If the local line printer driver supports indentation, the daemon
+must understand how to invoke it.
+.Sh FILTERS
+The
+.Xr lpd 8
+daemon creates a pipeline of
+.Em filters
+to process files for various printer types.
+The filters selected depend on the flags passed to
+.Xr lpr 1 .
+The pipeline set up is:
+.Bd -literal -offset indent
+p pr | if regular text + pr(1)
+none if regular text
+c cf cifplot
+d df DVI (tex)
+g gf plot(3)
+n nf ditroff
+f rf Fortran
+t tf troff
+v vf raster image
+.Ed
+.Pp
+The
+.Sy if
+filter is invoked with arguments:
+.Bd -ragged -offset indent
+.Cm if
+.Op Fl c
+.Fl w Ns Ar width
+.Fl l Ns Ar length
+.Fl i Ns Ar indent
+.Fl n Ar login
+.Fl h Ar host acct-file
+.Ed
+.Pp
+The
+.Fl c
+flag is passed only if the
+.Fl l
+flag (pass control characters literally)
+is specified to
+.Xr lpr 1 .
+The
+.Ar Width
+function
+and
+.Ar length
+specify the page width and length
+(from
+.Cm pw
+and
+.Cm pl
+respectively) in characters.
+The
+.Fl n
+and
+.Fl h
+parameters specify the login name and host name of the owner
+of the job respectively.
+The
+.Ar Acct-file
+function
+is passed from the
+.Cm af
+.Nm
+entry.
+.Pp
+If no
+.Cm if
+is specified,
+.Cm of
+is used instead,
+with the distinction that
+.Cm of
+is opened only once,
+while
+.Cm if
+is opened for every individual job.
+Thus,
+.Cm if
+is better suited to performing accounting.
+The
+.Cm of
+is only given the
+.Ar width
+and
+.Ar length
+flags.
+.Pp
+All other filters are called as:
+.Bd -ragged -offset indent
+.Nm filter
+.Fl x Ns Ar width
+.Fl y Ns Ar length
+.Fl n Ar login
+.Fl h Ar host acct-file
+.Ed
+.Pp
+where
+.Ar width
+and
+.Ar length
+are represented in pixels,
+specified by the
+.Cm px
+and
+.Cm py
+entries respectively.
+.Pp
+All filters take
+.Em stdin
+as the file,
+.Em stdout
+as the printer,
+may log either to
+.Em stderr
+or using
+.Xr syslog 3 ,
+and must not ignore
+.Dv SIGINT .
+.Sh REMOTE PRINTING
+When printing to a remote printer using
+.Cm rm ,
+it is possible to use either
+.Cm if
+or
+.Cm of .
+If both are specified,
+.Cm of
+is ignored.
+Both filters behave the same except that they are passed
+different arguments as above.
+Specifically, the output filter is
+terminated and restarted for each file transmitted.
+This is necessary
+in order to pass the resulting size to the remote
+.Xr lpd 8 .
+.Pp
+If the
+.Fl p
+flag was passed to
+.Xr lpr 1 ,
+.Xr pr 1
+is not executed locally, but is requested of the remote
+.Xr lpd 8 .
+Any input filtering via
+.Cm if
+will therefore happen before
+.Xr pr 1
+is executed rather than afterwards.
+.Pp
+There are some models of network printers which accept jobs from
+.Xr lpd 8 ,
+but they ignore the control file for a job and simply print
+each data file as it arrives at the printer.
+One side-effect of this behavior is that the printer will ignore any request
+for multiple copies as given with the
+.Fl #
+flag on the
+.Xr lpr 1
+command.
+The
+.Cm rc
+entry will cause
+.Xr lpd 8
+to resend each data file for each copy that the user
+originally requested.
+Note that the
+.Cm rc
+entry should only be specified on hosts which send jobs directly to
+the printer.
+.Pp
+If
+.Cm lp
+is specified as
+.Em port Ns @ Ns Em machine
+(and
+.Cm rm
+is not in use), print data will be sent directly to the given
+.Em port
+on the given
+.Em machine .
+.Sh TRANSFER STATISTICS
+When a print job is transferred to a remote machine (which might be
+another unix box, or may be a network printer), it may be useful
+to keep statistics on each transfer.
+The
+.Cm sr
+and
+.Cm ss
+options indicate filenames that lpd should use to store such
+statistics.
+A statistics line is written for each datafile of a
+job as the file is successfully transferred.
+The format of the
+line is the same for both the sending and receiving side of a
+transfer.
+.Pp
+Statistics on datafiles being received would be used on a print
+server, if you are interested in network performance between a
+variety of machines which are sending jobs to that print server.
+The print server could collect statistics on the speed of each
+print job as it arrived on the server.
+.Pp
+Statistics on datafiles being sent might be used as a minimal
+accounting record, when you want to know who sent which jobs to a
+remote printer, when they were sent, and how large (in bytes) the
+files were.
+This will not give include any idea of how many pages
+were printed, because there is no standard way to get that information
+back from a remote (network) printer in this case.
+.Sh LOGGING
+Error messages generated by the line printer programs themselves
+(that is, the
+.Xr lpd 8
+and related programs)
+are logged by
+.Xr syslog 3
+using the
+.Dv LPR
+facility.
+Messages printed on
+.Em stderr
+of one of the filters
+are sent to the corresponding
+.Cm lf
+file.
+The filters may, of course, use
+.Xr syslogd 8
+themselves.
+.Pp
+Error messages sent to the console have a carriage return and a line
+feed appended to them, rather than just a line feed.
+.Sh SEE ALSO
+.Xr lpq 1 ,
+.Xr lpr 1 ,
+.Xr lprm 1 ,
+.Xr hosts.lpd 5 ,
+.Xr termcap 5 ,
+.Xr chkprintcap 8 ,
+.Xr lpc 8 ,
+.Xr lpd 8 ,
+.Xr pac 8
+.Rs
+.%T "4.3 BSD Line Printer Spooler Manual"
+.Re
+.Sh HISTORY
+The
+.Nm
+file format appeared in
+.Bx 4.2 .
diff --git a/usr.sbin/lpr/lprm/Makefile b/usr.sbin/lpr/lprm/Makefile
new file mode 100644
index 0000000..5724d53
--- /dev/null
+++ b/usr.sbin/lpr/lprm/Makefile
@@ -0,0 +1,17 @@
+# From: @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../common_source
+
+BINDIR= /usr/bin
+
+PROG= lprm
+BINOWN= root
+BINGRP= daemon
+BINMODE= 6555
+
+CFLAGS+= -I${.CURDIR}/../common_source
+
+LIBADD= lpr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/lprm/Makefile.depend b/usr.sbin/lpr/lprm/Makefile.depend
new file mode 100644
index 0000000..5d7143d
--- /dev/null
+++ b/usr.sbin/lpr/lprm/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ usr.sbin/lpr/common_source \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/lprm/lprm.1 b/usr.sbin/lpr/lprm/lprm.1
new file mode 100644
index 0000000..9a8c640
--- /dev/null
+++ b/usr.sbin/lpr/lprm/lprm.1
@@ -0,0 +1,147 @@
+.\" Copyright (c) 1983, 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.
+.\"
+.\" @(#)lprm.1 8.1 (Berkeley) 6/6/93
+.\" $FreeBSD$
+.\"
+.Dd June 6, 1993
+.Dt LPRM 1
+.Os
+.Sh NAME
+.Nm lprm
+.Nd remove jobs from the line printer spooling queue
+.Sh SYNOPSIS
+.Nm
+.Op Fl P Ns Ar printer
+.Op Fl
+.Op job # ...\&
+.Op Ar user ...\&
+.Sh DESCRIPTION
+The
+.Nm
+utility will remove a job, or jobs, from a printer's spool queue.
+Since the spooling directory is protected from users, using
+.Nm
+is normally the only method by which a user may remove a job.
+The owner of a job is determined by the user's login name
+and host name on the machine where the
+.Xr lpr 1
+command was invoked.
+.Pp
+Options and arguments:
+.Bl -tag -width indent
+.It Fl P Ns Ar printer
+Specify the queue associated with a specific
+.Ar printer
+(otherwise the default printer is used).
+.It Fl
+If a single
+.Sq Fl
+is given,
+.Nm
+will remove all jobs which a user
+owns.
+If the super-user employs this flag, the spool queue will
+be emptied entirely.
+.It Ar user
+Cause
+.Nm
+to attempt to remove any jobs queued belonging to that user
+(or users).
+This form of invoking
+.Nm
+is useful only to the super-user.
+.It Ar job\ \&#
+A user may dequeue an individual job by specifying its job number.
+This number may be obtained from the
+.Xr lpq 1
+program, e.g.\&
+.Bd -literal -offset indent
+\&% lpq \-l
+
+1st:ken [job #013ucbarpa]
+ (standard input) 100 bytes
+% lprm 13
+.Ed
+.El
+.Pp
+If neither arguments or options are given,
+.Nm
+will delete the currently active job if it is
+owned by the user who invoked
+.Nm .
+.Pp
+The
+.Nm
+utility announces the names of any files it removes and is silent if
+there are no jobs in the queue which match the request list.
+.Pp
+The
+.Nm
+utility will kill off an active daemon, if necessary, before removing
+any spooling files.
+If a daemon is killed, a new one is
+automatically restarted upon completion of file removals.
+.Sh ENVIRONMENT
+If the following environment variable exists, it is utilized by
+.Nm .
+.Bl -tag -width PRINTER
+.It Ev PRINTER
+If the environment variable
+.Ev PRINTER
+exists,
+and a printer has not been specified with the
+.Fl P
+option,
+the default printer is assumed from
+.Ev PRINTER .
+.El
+.Sh FILES
+.Bl -tag -width /var/spool/*/lock/ -compact
+.It Pa /etc/printcap
+Printer characteristics file.
+.It Pa /var/spool/*
+Spooling directories.
+.It Pa /var/spool/*/lock
+Lock file used to obtain the pid of the current
+daemon and the job number of the currently active job.
+.El
+.Sh DIAGNOSTICS
+``Permission denied" if the user tries to remove files other than his
+own.
+.Sh SEE ALSO
+.Xr lpq 1 ,
+.Xr lpr 1 ,
+.Xr lpd 8
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 3.0 .
+.Sh BUGS
+Since there are race conditions possible in the update of the lock file,
+the currently active job may be incorrectly identified.
diff --git a/usr.sbin/lpr/lprm/lprm.c b/usr.sbin/lpr/lprm/lprm.c
new file mode 100644
index 0000000..35d8814
--- /dev/null
+++ b/usr.sbin/lpr/lprm/lprm.c
@@ -0,0 +1,162 @@
+/*
+ * 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 */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)lprm.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+/*
+ * lprm - remove the current user's spool entry
+ *
+ * lprm [-] [[job #] [user] ...]
+ *
+ * Using information in the lock file, lprm will kill the
+ * currently active daemon (if necessary), remove the associated files,
+ * and startup a new daemon. Priviledged users may remove anyone's spool
+ * entries, otherwise one can only remove their own.
+ */
+
+#include <sys/param.h>
+
+#include <syslog.h>
+#include <dirent.h>
+#include <err.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include "lp.h"
+#include "lp.local.h"
+
+/*
+ * Stuff for handling job specifications
+ */
+char *person; /* name of person doing lprm */
+int requ[MAXREQUESTS]; /* job number of spool entries */
+int requests; /* # of spool requests */
+char *user[MAXUSERS]; /* users to process */
+int users; /* # of users in user array */
+uid_t uid, euid; /* real and effective user id's */
+
+static char luser[16]; /* buffer for person */
+
+int main(int argc, char *_argv[]);
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ char *arg;
+ const char *printer;
+ struct passwd *p;
+ static char root[] = "root";
+
+ printer = NULL;
+ uid = getuid();
+ euid = geteuid();
+ PRIV_END /* be safe */
+ progname = argv[0];
+ gethostname(local_host, sizeof(local_host));
+ openlog("lpd", 0, LOG_LPR);
+
+ /*
+ * Bogus code later checks for string equality between
+ * `person' and "root", so if we are root, better make sure
+ * that code will succeed.
+ */
+ if (getuid() == 0) {
+ person = root;
+ } else if ((person = getlogin()) == NULL) {
+ if ((p = getpwuid(getuid())) == NULL)
+ fatal(0, "Who are you?");
+ if (strlen(p->pw_name) >= sizeof(luser))
+ fatal(0, "Your name is too long");
+ strcpy(luser, p->pw_name);
+ person = luser;
+ }
+ while (--argc) {
+ if ((arg = *++argv)[0] == '-')
+ switch (arg[1]) {
+ case 'P':
+ if (arg[2])
+ printer = &arg[2];
+ else if (argc > 1) {
+ argc--;
+ printer = *++argv;
+ }
+ break;
+ case '\0':
+ if (!users) {
+ users = -1;
+ break;
+ }
+ default:
+ usage();
+ }
+ else {
+ if (users < 0)
+ usage();
+ if (isdigit(arg[0])) {
+ if (requests >= MAXREQUESTS)
+ fatal(0, "Too many requests");
+ requ[requests++] = atoi(arg);
+ } else {
+ if (users >= MAXUSERS)
+ fatal(0, "Too many users");
+ user[users++] = arg;
+ }
+ }
+ }
+ if (printer == NULL && (printer = getenv("PRINTER")) == NULL)
+ printer = DEFLP;
+
+ rmjob(printer);
+ exit(0);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: lprm [-] [-Pprinter] [[job #] [user] ...]\n");
+ exit(2);
+}
diff --git a/usr.sbin/lpr/lptest/Makefile b/usr.sbin/lpr/lptest/Makefile
new file mode 100644
index 0000000..65b1c7c
--- /dev/null
+++ b/usr.sbin/lpr/lptest/Makefile
@@ -0,0 +1,8 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+PROG= lptest
+
+CFLAGS+= -I${.CURDIR}/../common_source
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/lptest/Makefile.depend b/usr.sbin/lpr/lptest/Makefile.depend
new file mode 100644
index 0000000..9cb890b
--- /dev/null
+++ b/usr.sbin/lpr/lptest/Makefile.depend
@@ -0,0 +1,17 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/lptest/lptest.1 b/usr.sbin/lpr/lptest/lptest.1
new file mode 100644
index 0000000..53fb98e
--- /dev/null
+++ b/usr.sbin/lpr/lptest/lptest.1
@@ -0,0 +1,73 @@
+.\" Copyright (c) 1985, 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.
+.\"
+.\" @(#)lptest.1 8.2 (Berkeley) 12/30/93
+.\" $FreeBSD$
+.\"
+.Dd December 30, 1993
+.Dt LPTEST 1
+.Os
+.Sh NAME
+.Nm lptest
+.Nd generate lineprinter ripple pattern
+.Sh SYNOPSIS
+.Nm
+.Op Ar length
+.Op Ar count
+.Sh DESCRIPTION
+The
+.Nm
+utility writes the traditional "ripple test" pattern on standard output.
+In 96 lines,
+this pattern will print all 96 printable
+.Tn ASCII
+characters
+in each position.
+While originally created to test printers, it is quite
+useful for testing terminals,
+driving terminal ports for debugging purposes,
+or any other task where a quick supply of random data is needed.
+.Pp
+The
+.Ar length
+argument specifies the output line length if the default
+length of 79 is inappropriate.
+.Pp
+The
+.Ar count
+argument specifies the number of output lines to be generated if
+the default count of 200 is inappropriate.
+Note that if
+.Ar count
+is to be specified,
+.Ar length
+must be also be specified.
+.Sh HISTORY
+A
+.Nm
+utility appeared in
+.Bx 4.3 .
diff --git a/usr.sbin/lpr/lptest/lptest.c b/usr.sbin/lpr/lptest/lptest.c
new file mode 100644
index 0000000..07c90d7
--- /dev/null
+++ b/usr.sbin/lpr/lptest/lptest.c
@@ -0,0 +1,85 @@
+/*
+ * 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 */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)lptest.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <err.h>
+
+/*
+ * lptest -- line printer test program (and other devices).
+ */
+int
+main(int argc, char **argv)
+{
+ int len, count;
+ register int i, j, fc, nc;
+ char outbuf[BUFSIZ];
+
+ setbuf(stdout, outbuf);
+ if (argc >= 2)
+ len = atoi(argv[1]);
+ else
+ len = 79;
+ if (argc >= 3)
+ count = atoi(argv[2]);
+ else
+ count = 200;
+ fc = ' ';
+ for (i = 0; i < count; i++) {
+ if (++fc == 0177)
+ fc = ' ';
+ nc = fc;
+ for (j = 0; j < len; j++) {
+ if (putchar(nc) == EOF)
+ err(1, "Write error");
+ if (++nc == 0177)
+ nc = ' ';
+ }
+ if (putchar('\n') == EOF)
+ err(1, "Write error");
+ }
+ (void) fflush(stdout);
+ exit(0);
+}
diff --git a/usr.sbin/lpr/pac/Makefile b/usr.sbin/lpr/pac/Makefile
new file mode 100644
index 0000000..faf8e58
--- /dev/null
+++ b/usr.sbin/lpr/pac/Makefile
@@ -0,0 +1,13 @@
+# From: @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../common_source
+
+PROG= pac
+MAN= pac.8
+
+CFLAGS+= -I${.CURDIR}/../common_source
+
+LIBADD= lpr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lpr/pac/Makefile.depend b/usr.sbin/lpr/pac/Makefile.depend
new file mode 100644
index 0000000..5d7143d
--- /dev/null
+++ b/usr.sbin/lpr/pac/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ usr.sbin/lpr/common_source \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lpr/pac/pac.8 b/usr.sbin/lpr/pac/pac.8
new file mode 100644
index 0000000..17777de
--- /dev/null
+++ b/usr.sbin/lpr/pac/pac.8
@@ -0,0 +1,105 @@
+.\" 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.
+.\"
+.\" @(#)pac.8 8.1 (Berkeley) 6/6/93
+.\" $FreeBSD$
+.\"
+.Dd June 6, 1993
+.Dt PAC 8
+.Os
+.Sh NAME
+.Nm pac
+.Nd printer/plotter accounting information
+.Sh SYNOPSIS
+.Nm
+.Op Fl P Ns Ar printer
+.Op Fl c
+.Op Fl m
+.Op Fl p Ns Ar price
+.Op Fl s
+.Op Fl r
+.Op Ar name ...
+.Sh DESCRIPTION
+The
+.Nm
+utility reads the printer/plotter accounting files, accumulating the number
+of pages (the usual case) or feet (for raster devices)
+of paper consumed by each user, and printing out
+how much each user consumed in pages or feet and dollars.
+.Pp
+Options and operands available:
+.Bl -tag -width PPprinter
+.It Fl P Ns Ar printer
+Accounting is done for the named printer.
+Normally, accounting is done for the default printer (site dependent) or
+the value of the environment variable
+.Ev PRINTER
+is used.
+.It Fl c
+Cause the output to be sorted by cost; usually the
+output is sorted alphabetically by name.
+.It Fl m
+Cause the host name to be ignored in the accounting file.
+This
+allows for a user on multiple machines to have all of his printing
+charges grouped together.
+.It Fl p Ns Ar price
+The value
+.Ar price
+is used for the cost in dollars instead of the default value of 0.02
+or the price specified in
+.Pa /etc/printcap .
+.It Fl r
+Reverse the sorting order.
+.It Fl s
+Accounting information is summarized on the
+summary accounting file; this summarization is necessary since on a
+busy system, the accounting file can grow by several lines per day.
+.It Ar names
+Statistics are only printed for user(s)
+.Ar name ;
+usually, statistics are printed for every user who has used any paper.
+.El
+.Sh FILES
+.Bl -tag -width /var/account/?_sum -compact
+.It Pa /var/account/?acct
+raw accounting files
+.It Pa /var/account/?_sum
+summary accounting files
+.It Pa /etc/printcap
+printer capability data base
+.El
+.Sh SEE ALSO
+.Xr printcap 5
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.0 .
+.Sh BUGS
+The relationship between the computed price and reality is
+as yet unknown.
diff --git a/usr.sbin/lpr/pac/pac.c b/usr.sbin/lpr/pac/pac.c
new file mode 100644
index 0000000..5fdd54f
--- /dev/null
+++ b/usr.sbin/lpr/pac/pac.c
@@ -0,0 +1,450 @@
+/*
+ * 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 */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)pac.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+
+#include "lp.cdefs.h" /* A cross-platform version of <sys/cdefs.h> */
+__FBSDID("$FreeBSD$");
+
+/*
+ * Do Printer accounting summary.
+ * Currently, usage is
+ * pac [-Pprinter] [-pprice] [-s] [-r] [-c] [-m] [user ...]
+ * to print the usage information for the named people.
+ */
+
+#include <sys/param.h>
+
+#include <dirent.h>
+#include <err.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include "lp.h"
+#include "lp.local.h"
+
+static char *acctfile; /* accounting file (input data) */
+static int allflag = 1; /* Get stats on everybody */
+static int errs;
+static size_t hcount; /* Count of hash entries */
+static int mflag = 0; /* disregard machine names */
+static int pflag = 0; /* 1 if -p on cmd line */
+static float price = 0.02; /* cost per page (or what ever) */
+static int reverse; /* Reverse sort order */
+static int sort; /* Sort by cost */
+static char *sumfile; /* summary file */
+static int summarize; /* Compress accounting file */
+
+uid_t uid, euid;
+
+/*
+ * Grossness follows:
+ * Names to be accumulated are hashed into the following
+ * table.
+ */
+
+#define HSHSIZE 97 /* Number of hash buckets */
+
+struct hent {
+ struct hent *h_link; /* Forward hash link */
+ char *h_name; /* Name of this user */
+ float h_feetpages; /* Feet or pages of paper */
+ int h_count; /* Number of runs */
+};
+
+static struct hent *hashtab[HSHSIZE]; /* Hash table proper */
+
+int main(int argc, char **_argv);
+static void account(FILE *_acctf);
+static int any(int _ch, const char _str[]);
+static int chkprinter(const char *_ptrname);
+static void dumpit(void);
+static int hash(const char _name[]);
+static struct hent *enter(const char _name[]);
+static struct hent *lookup(const char _name[]);
+static int qucmp(const void *_a, const void *_b);
+static void rewrite(void);
+static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ FILE *acctf;
+ const char *cp, *printer;
+
+ printer = NULL;
+ euid = geteuid(); /* these aren't used in pac(1) */
+ uid = getuid();
+ while (--argc) {
+ cp = *++argv;
+ if (*cp++ == '-') {
+ switch(*cp++) {
+ case 'P':
+ /*
+ * Printer name.
+ */
+ printer = cp;
+ continue;
+
+ case 'p':
+ /*
+ * get the price.
+ */
+ price = atof(cp);
+ pflag = 1;
+ continue;
+
+ case 's':
+ /*
+ * Summarize and compress accounting file.
+ */
+ summarize++;
+ continue;
+
+ case 'c':
+ /*
+ * Sort by cost.
+ */
+ sort++;
+ continue;
+
+ case 'm':
+ /*
+ * disregard machine names for each user
+ */
+ mflag = 1;
+ continue;
+
+ case 'r':
+ /*
+ * Reverse sorting order.
+ */
+ reverse++;
+ continue;
+
+ default:
+ usage();
+ }
+ }
+ (void) enter(--cp);
+ allflag = 0;
+ }
+ if (printer == NULL && (printer = getenv("PRINTER")) == NULL)
+ printer = DEFLP;
+ if (!chkprinter(printer)) {
+ printf("pac: unknown printer %s\n", printer);
+ exit(2);
+ }
+
+ if ((acctf = fopen(acctfile, "r")) == NULL) {
+ perror(acctfile);
+ exit(1);
+ }
+ account(acctf);
+ fclose(acctf);
+ if ((acctf = fopen(sumfile, "r")) != NULL) {
+ account(acctf);
+ fclose(acctf);
+ }
+ if (summarize)
+ rewrite();
+ else
+ dumpit();
+ exit(errs);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: pac [-Pprinter] [-pprice] [-s] [-c] [-r] [-m] [user ...]\n");
+ exit(1);
+}
+
+/*
+ * Read the entire accounting file, accumulating statistics
+ * for the users that we have in the hash table. If allflag
+ * is set, then just gather the facts on everyone.
+ * Note that we must accommodate both the active and summary file
+ * formats here.
+ * Host names are ignored if the -m flag is present.
+ */
+static void
+account(FILE *acctf)
+{
+ char linebuf[BUFSIZ];
+ double t;
+ register char *cp, *cp2;
+ register struct hent *hp;
+ register int ic;
+
+ while (fgets(linebuf, BUFSIZ, acctf) != NULL) {
+ cp = linebuf;
+ while (any(*cp, " \t"))
+ cp++;
+ t = atof(cp);
+ while (any(*cp, ".0123456789"))
+ cp++;
+ while (any(*cp, " \t"))
+ cp++;
+ for (cp2 = cp; !any(*cp2, " \t\n"); cp2++)
+ ;
+ ic = atoi(cp2);
+ *cp2 = '\0';
+ if (mflag && strchr(cp, ':'))
+ cp = strchr(cp, ':') + 1;
+ hp = lookup(cp);
+ if (hp == NULL) {
+ if (!allflag)
+ continue;
+ hp = enter(cp);
+ }
+ hp->h_feetpages += t;
+ if (ic)
+ hp->h_count += ic;
+ else
+ hp->h_count++;
+ }
+}
+
+/*
+ * Sort the hashed entries by name or footage
+ * and print it all out.
+ */
+static void
+dumpit(void)
+{
+ struct hent **base;
+ register struct hent *hp, **ap;
+ register int hno, runs;
+ size_t c;
+ float feet;
+
+ hp = hashtab[0];
+ hno = 1;
+ base = (struct hent **) calloc(sizeof hp, hcount);
+ for (ap = base, c = hcount; c--; ap++) {
+ while (hp == NULL)
+ hp = hashtab[hno++];
+ *ap = hp;
+ hp = hp->h_link;
+ }
+ qsort(base, hcount, sizeof hp, qucmp);
+ printf(" Login pages/feet runs price\n");
+ feet = 0.0;
+ runs = 0;
+ for (ap = base, c = hcount; c--; ap++) {
+ hp = *ap;
+ runs += hp->h_count;
+ feet += hp->h_feetpages;
+ printf("%-24s %7.2f %4d $%6.2f\n", hp->h_name,
+ hp->h_feetpages, hp->h_count, hp->h_feetpages * price);
+ }
+ if (allflag) {
+ printf("\n");
+ printf("%-24s %7.2f %4d $%6.2f\n", "total", feet,
+ runs, feet * price);
+ }
+}
+
+/*
+ * Rewrite the summary file with the summary information we have accumulated.
+ */
+static void
+rewrite(void)
+{
+ register struct hent *hp;
+ register int i;
+ FILE *acctf;
+
+ if ((acctf = fopen(sumfile, "w")) == NULL) {
+ warn("%s", sumfile);
+ errs++;
+ return;
+ }
+ for (i = 0; i < HSHSIZE; i++) {
+ hp = hashtab[i];
+ while (hp != NULL) {
+ fprintf(acctf, "%7.2f\t%s\t%d\n", hp->h_feetpages,
+ hp->h_name, hp->h_count);
+ hp = hp->h_link;
+ }
+ }
+ fflush(acctf);
+ if (ferror(acctf)) {
+ warn("%s", sumfile);
+ errs++;
+ }
+ fclose(acctf);
+ if ((acctf = fopen(acctfile, "w")) == NULL)
+ warn("%s", acctfile);
+ else
+ fclose(acctf);
+}
+
+/*
+ * Hashing routines.
+ */
+
+/*
+ * Enter the name into the hash table and return the pointer allocated.
+ */
+
+static struct hent *
+enter(const char name[])
+{
+ register struct hent *hp;
+ register int h;
+
+ if ((hp = lookup(name)) != NULL)
+ return(hp);
+ h = hash(name);
+ hcount++;
+ hp = (struct hent *) calloc(sizeof *hp, (size_t)1);
+ hp->h_name = (char *) calloc(sizeof(char), strlen(name)+1);
+ strcpy(hp->h_name, name);
+ hp->h_feetpages = 0.0;
+ hp->h_count = 0;
+ hp->h_link = hashtab[h];
+ hashtab[h] = hp;
+ return(hp);
+}
+
+/*
+ * Lookup a name in the hash table and return a pointer
+ * to it.
+ */
+
+static struct hent *
+lookup(const char name[])
+{
+ register int h;
+ register struct hent *hp;
+
+ h = hash(name);
+ for (hp = hashtab[h]; hp != NULL; hp = hp->h_link)
+ if (strcmp(hp->h_name, name) == 0)
+ return(hp);
+ return(NULL);
+}
+
+/*
+ * Hash the passed name and return the index in
+ * the hash table to begin the search.
+ */
+static int
+hash(const char name[])
+{
+ register int h;
+ register const char *cp;
+
+ for (cp = name, h = 0; *cp; h = (h << 2) + *cp++)
+ ;
+ return((h & 0x7fffffff) % HSHSIZE);
+}
+
+/*
+ * Other stuff
+ */
+static int
+any(int ch, const char str[])
+{
+ register int c = ch;
+ register const char *cp = str;
+
+ while (*cp)
+ if (*cp++ == c)
+ return(1);
+ return(0);
+}
+
+/*
+ * The qsort comparison routine.
+ * The comparison is ascii collating order
+ * or by feet of typesetter film, according to sort.
+ */
+static int
+qucmp(const void *a, const void *b)
+{
+ register const struct hent *h1, *h2;
+ register int r;
+
+ h1 = *(const struct hent * const *)a;
+ h2 = *(const struct hent * const *)b;
+ if (sort)
+ r = h1->h_feetpages < h2->h_feetpages ?
+ -1 : h1->h_feetpages > h2->h_feetpages;
+ else
+ r = strcmp(h1->h_name, h2->h_name);
+ return(reverse ? -r : r);
+}
+
+/*
+ * Perform lookup for printer name or abbreviation --
+ */
+static int
+chkprinter(const char *ptrname)
+{
+ int stat;
+ struct printer myprinter, *pp = &myprinter;
+
+ init_printer(&myprinter);
+ stat = getprintcap(ptrname, pp);
+ switch(stat) {
+ case PCAPERR_OSERR:
+ printf("pac: getprintcap: %s\n", pcaperr(stat));
+ exit(3);
+ case PCAPERR_NOTFOUND:
+ return 0;
+ case PCAPERR_TCLOOP:
+ fatal(pp, "%s", pcaperr(stat));
+ }
+ if ((acctfile = pp->acct_file) == NULL)
+ errx(3, "accounting not enabled for printer %s", ptrname);
+ if (!pflag && pp->price100)
+ price = pp->price100/10000.0;
+ sumfile = (char *) calloc(sizeof(char), strlen(acctfile)+5);
+ if (sumfile == NULL)
+ errx(1, "calloc failed");
+ strcpy(sumfile, acctfile);
+ strcat(sumfile, "_sum");
+ return(1);
+}
diff --git a/usr.sbin/lptcontrol/Makefile b/usr.sbin/lptcontrol/Makefile
new file mode 100644
index 0000000..0cff620
--- /dev/null
+++ b/usr.sbin/lptcontrol/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= lptcontrol
+MAN= lptcontrol.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/lptcontrol/Makefile.depend b/usr.sbin/lptcontrol/Makefile.depend
new file mode 100644
index 0000000..9cb890b
--- /dev/null
+++ b/usr.sbin/lptcontrol/Makefile.depend
@@ -0,0 +1,17 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/lptcontrol/lptcontrol.8 b/usr.sbin/lptcontrol/lptcontrol.8
new file mode 100644
index 0000000..7723078
--- /dev/null
+++ b/usr.sbin/lptcontrol/lptcontrol.8
@@ -0,0 +1,90 @@
+.\"
+.\" lptcontrol - a utility for manipulating the lpt driver
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" $FreeBSD$
+.Dd October 1, 2004
+.Dt LPTCONTROL 8
+.Os
+.Sh NAME
+.Nm lptcontrol
+.Nd a utility for manipulating the lpt printer driver
+.Sh SYNOPSIS
+.Nm
+.Fl e | i | p | s
+.Op Fl d Ar control_device
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to set either the interrupt-driven, extended, standard,
+or polling mode of individual
+.Xr lpt 4
+devices.
+When a printer is switched from
+a mode to another, this change will only take effect
+the next time the device is opened.
+.Pp
+Extended mode is anything the parallel port interface can support.
+For an
+ECP/ISA parallel port, it may be FIFO+DMA or ECP.
+.Pp
+The following command line options are supported:
+.Bl -tag -width indent
+.It Fl e
+Turn on extended mode.
+.It Fl i
+Turn on interrupt-driven mode.
+.It Fl p
+Turn on polled mode.
+.It Fl s
+Turn on standard mode, i.e., turn off extended mode.
+.It Fl d Ar control_device
+Set the mode of the printer control device specified by
+.Ar control_device .
+The default value for
+.Ar control_device
+is
+.Pa /dev/lpt0.ctl .
+.El
+.Pp
+One of
+.Fl e , i , p
+or
+.Fl s
+must be specified.
+.Sh FILES
+.Bl -tag -width /sys/i386/conf/GENERIC -compact
+.It Pa /dev/lpt?
+printer devices
+.It Pa /dev/lpt?.ctl
+printer control devices
+.It Pa /sys/i386/conf/GENERIC
+kernel configuration file
+.It Pa /boot/device.hints
+device hints for the parallel port chipset driver,
+.Xr ppc 4
+.El
+.Sh SEE ALSO
+.Xr lpt 4 ,
+.Xr ppc 4 ,
+.Xr device.hints 5
+.Sh HISTORY
+The
+.Nm
+utility
+first appeared in
+.Fx 1.1.5
+.Sh AUTHORS
+.An Geoffrey M. Rehmet
+.Sh BUGS
+The control device name should never have been an option,
+but should have been an optional argument.
+Because of this, a single argument is treated as a device name.
diff --git a/usr.sbin/lptcontrol/lptcontrol.c b/usr.sbin/lptcontrol/lptcontrol.c
new file mode 100644
index 0000000..62e5b85
--- /dev/null
+++ b/usr.sbin/lptcontrol/lptcontrol.c
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 1994 Geoffrey M. Rehmet
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 Geoffrey M. Rehmet
+ * 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 <dev/ppbus/lptio.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define DEFAULT_DEVICE "/dev/lpt0.ctl"
+#define IRQ_UNSPECIFIED -1
+#define DO_POLL 0
+#define USE_IRQ 1
+#define USE_EXT_MODE 2
+#define USE_STD_MODE 3
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "usage: lptcontrol -e | -i | -p | -s [[-d] controldevice]\n");
+ exit(1);
+}
+
+int main (int argc, char **argv)
+{
+ const char *device;
+ int fd;
+ int irq_status;
+ int opt;
+
+ device = DEFAULT_DEVICE;
+ irq_status = IRQ_UNSPECIFIED;
+ while ((opt = getopt(argc, argv, "d:eips")) != -1)
+ switch (opt) {
+ case 'd':
+ device = optarg;
+ break;
+ case 'e':
+ irq_status = USE_EXT_MODE;
+ break;
+ case 'i':
+ irq_status = USE_IRQ;
+ break;
+ case 'p':
+ irq_status = DO_POLL;
+ break;
+ case 's':
+ irq_status = USE_STD_MODE;
+ break;
+ case '?':
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ argc -= optind;
+ argv += optind;
+ /* POLA: DTRT if -d was forgotten, but device name was specified. */
+ if (argc == 1) {
+ device = argv[0];
+ --argc;
+ }
+
+ if (irq_status == IRQ_UNSPECIFIED || argc != 0)
+ usage();
+
+ if ((fd = open(device, O_WRONLY)) < 0)
+ err(1, "open");
+ if (ioctl(fd, LPT_IRQ, &irq_status) < 0)
+ err(1, "ioctl");
+ close(fd);
+
+ return(0);
+}
diff --git a/usr.sbin/mailstats/Makefile b/usr.sbin/mailstats/Makefile
new file mode 100644
index 0000000..3d03ff8
--- /dev/null
+++ b/usr.sbin/mailstats/Makefile
@@ -0,0 +1,30 @@
+# @(#)Makefile 8.2 (Berkeley) 9/21/96
+# $FreeBSD$
+
+SENDMAIL_DIR= ${.CURDIR}/../../contrib/sendmail
+.PATH: ${SENDMAIL_DIR}/mailstats
+
+PROG= mailstats
+SRCS= mailstats.c
+MAN= mailstats.8
+
+CFLAGS+= -I${SENDMAIL_DIR}/src -I${SENDMAIL_DIR}/include -I.
+CFLAGS+= -DNOT_SENDMAIL
+
+WARNS?= 2
+
+LIBADD= sm smutil
+
+SRCS+= sm_os.h
+CLEANFILES+=sm_os.h
+
+# User customizations to the sendmail build environment
+CFLAGS+= ${SENDMAIL_CFLAGS}
+DPADD+= ${SENDMAIL_DPADD}
+LDADD+= ${SENDMAIL_LDADD}
+LDFLAGS+= ${SENDMAIL_LDFLAGS}
+
+sm_os.h: ${SENDMAIL_DIR}/include/sm/os/sm_os_freebsd.h .NOMETA
+ ln -sf ${.ALLSRC} ${.TARGET}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/mailstats/Makefile.depend b/usr.sbin/mailstats/Makefile.depend
new file mode 100644
index 0000000..0f94091
--- /dev/null
+++ b/usr.sbin/mailstats/Makefile.depend
@@ -0,0 +1,22 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libsm \
+ lib/libsmutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+mailstats.o: sm_os.h
+mailstats.po: sm_os.h
+.endif
diff --git a/usr.sbin/mailwrapper/Makefile b/usr.sbin/mailwrapper/Makefile
new file mode 100644
index 0000000..584cab8
--- /dev/null
+++ b/usr.sbin/mailwrapper/Makefile
@@ -0,0 +1,36 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+.if ${MK_MAILWRAPPER} != "no"
+PROG= mailwrapper
+MAN= mailwrapper.8
+
+LIBADD= util
+.endif
+
+.if ${MK_MAILWRAPPER} != "no" || ${MK_SENDMAIL} != "no"
+SYMLINKS= ${BINDIR}/mailwrapper /usr/sbin/sendmail \
+ ${BINDIR}/mailwrapper /usr/sbin/hoststat \
+ ${BINDIR}/mailwrapper /usr/sbin/purgestat \
+ ${BINDIR}/mailwrapper /usr/bin/newaliases \
+ ${BINDIR}/mailwrapper /usr/bin/mailq
+
+.if ${MK_MAILWRAPPER} == "no" && ${MK_SENDMAIL} != "no"
+SYMLINKS+= /usr/libexec/sendmail/sendmail ${BINDIR}/mailwrapper
+.endif
+.endif
+
+.if ${MK_MAILWRAPPER} != "no" && ${MK_SENDMAIL} == "no"
+SYMLINKS+= ${BINDIR}/mailwrapper /bin/rmail
+.endif
+
+.if ${MK_MAILWRAPPER} != "no"
+.if !exists(${DESTDIR}/etc/mail/mailer.conf)
+FILES= ${.CURDIR}/../../etc/mail/mailer.conf
+FILESDIR= /etc/mail
+FILESMODE= 644
+.endif
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/mailwrapper/Makefile.depend b/usr.sbin/mailwrapper/Makefile.depend
new file mode 100644
index 0000000..58f9a33
--- /dev/null
+++ b/usr.sbin/mailwrapper/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/mailwrapper/mailwrapper.8 b/usr.sbin/mailwrapper/mailwrapper.8
new file mode 100644
index 0000000..b382d77
--- /dev/null
+++ b/usr.sbin/mailwrapper/mailwrapper.8
@@ -0,0 +1,167 @@
+.\" $OpenBSD: mailwrapper.8,v 1.10 2009/02/07 16:58:23 martynas Exp $
+.\" $NetBSD: mailwrapper.8,v 1.11 2002/02/08 01:38:50 ross Exp $
+.\" $FreeBSD$
+.\"
+.\" 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.
+.\"
+.Dd August 27, 2014
+.Dt MAILWRAPPER 8
+.Os
+.Sh NAME
+.Nm mailwrapper
+.Nd invoke appropriate MTA software based on configuration file
+.Sh SYNOPSIS
+Special.
+See below.
+.Sh DESCRIPTION
+At one time, the only Mail Transfer Agent (MTA) software easily available
+was
+.Xr sendmail 8 .
+As a result of this, most Mail User Agents (MUAs) such as
+.Xr mail 1
+had the path and calling conventions expected by
+.Xr sendmail 8
+compiled in.
+.Pp
+Times have changed, however.
+On a modern
+.Ux
+system, the administrator may wish to use one of several
+available MTAs.
+.Pp
+It would be difficult to modify all MUA software typically available
+on a system, so most of the authors of alternative MTAs have written
+their front end message submission programs so that they use the same
+calling conventions as
+.Xr sendmail 8
+and may be put into place instead of
+.Xr sendmail 8
+in
+.Pa /usr/sbin/sendmail .
+.Pp
+.Xr sendmail 8
+also typically has aliases named
+.Xr mailq 1
+and
+.Xr newaliases 1
+linked to it.
+The program knows to behave differently when its
+.Va argv[0]
+is
+.Dq mailq
+or
+.Dq newaliases
+and behaves appropriately.
+Typically, replacement MTAs provide similar
+functionality, either through a program that also switches behavior
+based on calling name, or through a set of programs that provide
+similar functionality.
+.Pp
+Although having drop-in replacements for
+.Xr sendmail 8
+helps in installing alternative MTAs, it essentially makes the
+configuration of the system depend on hand installing new programs in
+.Pa /usr .
+This leads to configuration problems for many administrators, since
+they may wish to install a new MTA without altering the system
+provided
+.Pa /usr .
+(This may be, for example, to avoid having upgrade problems when a new
+version of the system is installed over the old.)
+They may also have a shared
+.Pa /usr
+among several
+machines, and may wish to avoid placing implicit configuration
+information in a read-only
+.Pa /usr .
+.Pp
+The
+.Nm
+utility is designed to replace
+.Pa /usr/sbin/sendmail
+and to invoke an appropriate MTA instead of
+.Xr sendmail 8
+based on configuration information placed in
+.Pa ${LOCALBASE}/etc/mail/mailer.conf
+falling back on
+.Pa /etc/mail/mailer.conf .
+This permits the administrator to configure which MTA is to be invoked on
+the system at run time.
+.Pp
+Other configuration files may need to be altered when replacing
+.Xr sendmail 8 .
+For example, if the replacement MTA does not support the
+.Fl A
+option with
+.Xr mailq 1 ,
+.Va daily_status_include_submit_mailq
+should be turned off in
+.Pa /etc/periodic.conf .
+.Sh FILES
+Configuration for
+.Nm
+is kept in
+.Pa ${LOCALBASE}/etc/mail/mailer.conf
+or
+.Pa /etc/mail/mailer.conf .
+.Pa /usr/sbin/sendmail
+is typically set up as a symbolic link to
+.Nm
+which is not usually invoked on its own.
+.Sh EXIT STATUS
+.Ex -std
+.Sh DIAGNOSTICS
+The
+.Nm
+will print a diagnostic if its configuration file is missing or malformed,
+or does not contain a mapping for the name under which it was invoked.
+.Sh SEE ALSO
+.Xr mail 1 ,
+.Xr mailq 1 ,
+.Xr newaliases 1 ,
+.Xr mailer.conf 5 ,
+.Xr periodic.conf 5 ,
+.Xr sendmail 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Nx 1.4
+and then
+.Fx 4.0 .
+.Sh AUTHORS
+.An Perry E. Metzger Aq Mt perry@piermont.com
+.Sh BUGS
+The entire reason this program exists is a crock.
+Instead, a command
+for how to submit mail should be standardized, and all the "behave
+differently if invoked with a different name" behavior of things like
+.Xr mailq 1
+should go away.
diff --git a/usr.sbin/mailwrapper/mailwrapper.c b/usr.sbin/mailwrapper/mailwrapper.c
new file mode 100644
index 0000000..96c9190
--- /dev/null
+++ b/usr.sbin/mailwrapper/mailwrapper.c
@@ -0,0 +1,173 @@
+/* $OpenBSD: mailwrapper.c,v 1.18 2007/11/06 14:39:19 otto Exp $ */
+/* $NetBSD: mailwrapper.c,v 1.9 2003/03/09 08:10:43 mjl 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <libutil.h>
+#include <sysexits.h>
+#include <syslog.h>
+
+#include "pathnames.h"
+
+struct arglist {
+ size_t argc, maxc;
+ char **argv;
+};
+
+int main(int, char *[], char *[]);
+
+static void initarg(struct arglist *);
+static void addarg(struct arglist *, const char *);
+
+static void
+initarg(struct arglist *al)
+{
+ al->argc = 0;
+ al->maxc = 10;
+ if ((al->argv = calloc(al->maxc, sizeof(char *))) == NULL)
+ err(EX_TEMPFAIL, "calloc");
+}
+
+static void
+addarg(struct arglist *al, const char *arg)
+{
+
+ if (al->argc == al->maxc) {
+ al->maxc <<= 1;
+ al->argv = realloc(al->argv, al->maxc * sizeof(char *));
+ if (al->argv == NULL)
+ err(EX_TEMPFAIL, "realloc");
+ }
+ if (arg == NULL)
+ al->argv[al->argc++] = NULL;
+ else if ((al->argv[al->argc++] = strdup(arg)) == NULL)
+ err(EX_TEMPFAIL, "strdup");
+}
+
+int
+main(int argc, char *argv[], char *envp[])
+{
+ FILE *config;
+ char *line, *cp, *from, *to, *ap;
+ const char *progname;
+ char localmailerconf[MAXPATHLEN];
+ const char *mailerconf;
+ size_t len, lineno = 0;
+ int i;
+ struct arglist al;
+
+ /* change __progname to mailwrapper so we get sensible error messages */
+ progname = getprogname();
+ setprogname("mailwrapper");
+
+ initarg(&al);
+ addarg(&al, argv[0]);
+
+ snprintf(localmailerconf, MAXPATHLEN, "%s/etc/mail/mailer.conf",
+ getenv("LOCALBASE") ? getenv("LOCALBASE") : "/usr/local");
+
+ mailerconf = localmailerconf;
+ if ((config = fopen(localmailerconf, "r")) == NULL)
+ mailerconf = _PATH_MAILERCONF;
+
+ if (config == NULL && ((config = fopen(mailerconf, "r")) == NULL)) {
+ addarg(&al, NULL);
+ openlog(getprogname(), LOG_PID, LOG_MAIL);
+ syslog(LOG_INFO, "cannot open %s, using %s as default MTA",
+ mailerconf, _PATH_DEFAULTMTA);
+ closelog();
+ execve(_PATH_DEFAULTMTA, al.argv, envp);
+ err(EX_OSERR, "cannot exec %s", _PATH_DEFAULTMTA);
+ /*NOTREACHED*/
+ }
+
+ for (;;) {
+ if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL) {
+ if (feof(config))
+ errx(EX_CONFIG, "no mapping in %s", mailerconf);
+ err(EX_CONFIG, "cannot parse line %lu", (u_long)lineno);
+ }
+
+#define WS " \t\n"
+ cp = line;
+
+ cp += strspn(cp, WS);
+ if (cp[0] == '\0') {
+ /* empty line */
+ free(line);
+ continue;
+ }
+
+ if ((from = strsep(&cp, WS)) == NULL || cp == NULL)
+ goto parse_error;
+
+ cp += strspn(cp, WS);
+
+ if ((to = strsep(&cp, WS)) == NULL)
+ goto parse_error;
+
+ if (strcmp(from, progname) == 0) {
+ for (ap = strsep(&cp, WS); ap != NULL;
+ ap = strsep(&cp, WS)) {
+ if (*ap)
+ addarg(&al, ap);
+ }
+ break;
+ }
+
+ free(line);
+ }
+
+ (void)fclose(config);
+
+ for (i = 1; i < argc; i++)
+ addarg(&al, argv[i]);
+
+ addarg(&al, NULL);
+ execve(to, al.argv, envp);
+ err(EX_OSERR, "cannot exec %s", to);
+ /*NOTREACHED*/
+parse_error:
+ errx(EX_CONFIG, "parse error in %s at line %lu",
+ mailerconf, (u_long)lineno);
+ /*NOTREACHED*/
+}
diff --git a/usr.sbin/mailwrapper/pathnames.h b/usr.sbin/mailwrapper/pathnames.h
new file mode 100644
index 0000000..d8ae498
--- /dev/null
+++ b/usr.sbin/mailwrapper/pathnames.h
@@ -0,0 +1,35 @@
+/* $FreeBSD$ */
+
+/*
+ * 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.
+ */
+
+#define _PATH_MAILERCONF "/etc/mail/mailer.conf"
+#define _PATH_DEFAULTMTA "/usr/libexec/sendmail/sendmail"
diff --git a/usr.sbin/makefs/Makefile b/usr.sbin/makefs/Makefile
new file mode 100644
index 0000000..7a0ebf0
--- /dev/null
+++ b/usr.sbin/makefs/Makefile
@@ -0,0 +1,41 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= makefs
+
+CFLAGS+=-I${.CURDIR}
+
+SRCS= cd9660.c ffs.c \
+ makefs.c \
+ mtree.c \
+ walk.c
+MAN= makefs.8
+
+WARNS?= 2
+
+.include "${.CURDIR}/cd9660/Makefile.inc"
+.include "${.CURDIR}/ffs/Makefile.inc"
+
+CFLAGS+=-DHAVE_STRUCT_STAT_ST_FLAGS=1
+CFLAGS+=-DHAVE_STRUCT_STAT_ST_GEN=1
+
+.PATH: ${.CURDIR}/../../contrib/mtree
+CFLAGS+=-I${.CURDIR}/../../contrib/mtree
+SRCS+= getid.c misc.c spec.c
+
+.PATH: ${.CURDIR}/../../contrib/mknod
+CFLAGS+=-I${.CURDIR}/../../contrib/mknod
+SRCS+= pack_dev.c
+
+.PATH: ${.CURDIR}/../../sys/ufs/ffs
+SRCS+= ffs_tables.c
+
+CFLAGS+= -I${.CURDIR}/../../lib/libnetbsd
+LIBADD= netbsd util sbuf
+
+.if ${MK_TESTS} != "no"
+SUBDIR+= tests
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/makefs/Makefile.depend b/usr.sbin/makefs/Makefile.depend
new file mode 100644
index 0000000..51e7dfa
--- /dev/null
+++ b/usr.sbin/makefs/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libnetbsd \
+ lib/libsbuf \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/makefs/cd9660.c b/usr.sbin/makefs/cd9660.c
new file mode 100644
index 0000000..e05e52a
--- /dev/null
+++ b/usr.sbin/makefs/cd9660.c
@@ -0,0 +1,2129 @@
+/* $NetBSD: cd9660.c,v 1.31 2011/08/06 23:25:19 christos Exp $ */
+
+/*
+ * Copyright (c) 2005 Daniel Watt, Walter Deignan, Ryan Gabrys, Alan
+ * Perez-Rathke and Ram Vedam. All rights reserved.
+ *
+ * This code was written by Daniel Watt, Walter Deignan, Ryan Gabrys,
+ * Alan Perez-Rathke and Ram Vedam.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE,DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (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) 2001 Wasabi Systems, Inc.
+ * All rights reserved.
+ *
+ * Written by Luke Mewburn 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.
+ */
+/*
+ * 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.
+ * 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>
+__FBSDID("$FreeBSD$");
+
+#include <string.h>
+#include <ctype.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+
+#include "makefs.h"
+#include "cd9660.h"
+#include "cd9660/iso9660_rrip.h"
+#include "cd9660/cd9660_archimedes.h"
+
+/*
+ * Global variables
+ */
+iso9660_disk diskStructure;
+
+static void cd9660_finalize_PVD(void);
+static cd9660node *cd9660_allocate_cd9660node(void);
+static void cd9660_set_defaults(void);
+static int cd9660_arguments_set_string(const char *, const char *, int,
+ char, char *);
+static void cd9660_populate_iso_dir_record(
+ struct _iso_directory_record_cd9660 *, u_char, u_char, u_char,
+ const char *);
+static void cd9660_setup_root_node(void);
+static int cd9660_setup_volume_descriptors(void);
+#if 0
+static int cd9660_fill_extended_attribute_record(cd9660node *);
+#endif
+static void cd9660_sort_nodes(cd9660node *);
+static int cd9660_translate_node_common(cd9660node *);
+static int cd9660_translate_node(fsnode *, cd9660node *);
+static int cd9660_compare_filename(const char *, const char *);
+static void cd9660_sorted_child_insert(cd9660node *, cd9660node *);
+static int cd9660_handle_collisions(cd9660node *, int);
+static cd9660node *cd9660_rename_filename(cd9660node *, int, int);
+static void cd9660_copy_filenames(cd9660node *);
+static void cd9660_sorting_nodes(cd9660node *);
+static int cd9660_count_collisions(cd9660node *);
+static cd9660node *cd9660_rrip_move_directory(cd9660node *);
+static int cd9660_add_dot_records(cd9660node *);
+
+static void cd9660_convert_structure(fsnode *, cd9660node *, int,
+ int *, int *);
+static void cd9660_free_structure(cd9660node *);
+static int cd9660_generate_path_table(void);
+static int cd9660_level1_convert_filename(const char *, char *, int);
+static int cd9660_level2_convert_filename(const char *, char *, int);
+#if 0
+static int cd9660_joliet_convert_filename(const char *, char *, int);
+#endif
+static int cd9660_convert_filename(const char *, char *, int);
+static void cd9660_populate_dot_records(cd9660node *);
+static int64_t cd9660_compute_offsets(cd9660node *, int64_t);
+#if 0
+static int cd9660_copy_stat_info(cd9660node *, cd9660node *, int);
+#endif
+static cd9660node *cd9660_create_virtual_entry(const char *, cd9660node *, int,
+ int);
+static cd9660node *cd9660_create_file(const char *, cd9660node *, cd9660node *);
+static cd9660node *cd9660_create_directory(const char *, cd9660node *,
+ cd9660node *);
+static cd9660node *cd9660_create_special_directory(u_char, cd9660node *);
+
+
+/*
+ * Allocate and initialize a cd9660node
+ * @returns struct cd9660node * Pointer to new node, or NULL on error
+ */
+static cd9660node *
+cd9660_allocate_cd9660node(void)
+{
+ cd9660node *temp;
+
+ if ((temp = calloc(1, sizeof(cd9660node))) == NULL)
+ err(EXIT_FAILURE, "%s: calloc", __func__);
+ TAILQ_INIT(&temp->cn_children);
+ temp->parent = temp->dot_record = temp->dot_dot_record = NULL;
+ temp->ptnext = temp->ptprev = temp->ptlast = NULL;
+ temp->node = NULL;
+ temp->isoDirRecord = NULL;
+ temp->isoExtAttributes = NULL;
+ temp->rr_real_parent = temp->rr_relocated = NULL;
+ temp->su_tail_data = NULL;
+ return temp;
+}
+
+int cd9660_defaults_set = 0;
+
+/**
+* Set default values for cd9660 extension to makefs
+*/
+static void
+cd9660_set_defaults(void)
+{
+ /*Fix the sector size for now, though the spec allows for other sizes*/
+ diskStructure.sectorSize = 2048;
+
+ /* Set up defaults in our own structure */
+ diskStructure.verbose_level = 0;
+ diskStructure.keep_bad_images = 0;
+ diskStructure.follow_sym_links = 0;
+ diskStructure.isoLevel = 2;
+
+ diskStructure.rock_ridge_enabled = 0;
+ diskStructure.rock_ridge_renamed_dir_name = 0;
+ diskStructure.rock_ridge_move_count = 0;
+ diskStructure.rr_moved_dir = 0;
+
+ diskStructure.archimedes_enabled = 0;
+ diskStructure.chrp_boot = 0;
+
+ diskStructure.include_padding_areas = 1;
+
+ /* Spec breaking functionality */
+ diskStructure.allow_deep_trees =
+ diskStructure.allow_start_dot =
+ diskStructure.allow_max_name =
+ diskStructure.allow_illegal_chars =
+ diskStructure.allow_lowercase =
+ diskStructure.allow_multidot =
+ diskStructure.omit_trailing_period = 0;
+
+ /* Make sure the PVD is clear */
+ memset(&diskStructure.primaryDescriptor, 0, 2048);
+
+ memset(diskStructure.primaryDescriptor.publisher_id, 0x20,128);
+ memset(diskStructure.primaryDescriptor.preparer_id, 0x20,128);
+ memset(diskStructure.primaryDescriptor.application_id, 0x20,128);
+ memset(diskStructure.primaryDescriptor.copyright_file_id, 0x20,37);
+ memset(diskStructure.primaryDescriptor.abstract_file_id, 0x20,37);
+ memset(diskStructure.primaryDescriptor.bibliographic_file_id, 0x20,37);
+
+ strcpy(diskStructure.primaryDescriptor.system_id, "FreeBSD");
+
+ cd9660_defaults_set = 1;
+
+ /* Boot support: Initially disabled */
+ diskStructure.has_generic_bootimage = 0;
+ diskStructure.generic_bootimage = NULL;
+
+ diskStructure.boot_image_directory = 0;
+ /*memset(diskStructure.boot_descriptor, 0, 2048);*/
+
+ diskStructure.is_bootable = 0;
+ TAILQ_INIT(&diskStructure.boot_images);
+ LIST_INIT(&diskStructure.boot_entries);
+}
+
+void
+cd9660_prep_opts(fsinfo_t *fsopts __unused)
+{
+ cd9660_set_defaults();
+}
+
+void
+cd9660_cleanup_opts(fsinfo_t *fsopts __unused)
+{
+
+}
+
+static int
+cd9660_arguments_set_string(const char *val, const char *fieldtitle, int length,
+ char testmode, char * dest)
+{
+ int len, test;
+
+ if (val == NULL)
+ warnx("error: The %s requires a string argument", fieldtitle);
+ else if ((len = strlen(val)) <= length) {
+ if (testmode == 'd')
+ test = cd9660_valid_d_chars(val);
+ else
+ test = cd9660_valid_a_chars(val);
+ if (test) {
+ memcpy(dest, val, len);
+ if (test == 2)
+ cd9660_uppercase_characters(dest, len);
+ return 1;
+ } else
+ warnx("error: The %s must be composed of "
+ "%c-characters", fieldtitle, testmode);
+ } else
+ warnx("error: The %s must be at most 32 characters long",
+ fieldtitle);
+ return 0;
+}
+
+/*
+ * Command-line parsing function
+ */
+
+int
+cd9660_parse_opts(const char *option, fsinfo_t *fsopts)
+{
+ char *var, *val;
+ int rv;
+ /* Set up allowed options - integer options ONLY */
+ option_t cd9660_options[] = {
+ { "l", &diskStructure.isoLevel, 1, 2, "ISO Level" },
+ { "isolevel", &diskStructure.isoLevel, 1, 2, "ISO Level" },
+ { "verbose", &diskStructure.verbose_level, 0, 2,
+ "Turns on verbose output" },
+ { "v", &diskStructure.verbose_level, 0 , 2,
+ "Turns on verbose output"},
+ { .name = NULL }
+ };
+
+ if (cd9660_defaults_set == 0)
+ cd9660_set_defaults();
+
+ /*
+ * Todo : finish implementing this, and make a function that
+ * parses them
+ */
+ /*
+ string_option_t cd9660_string_options[] = {
+ { "L", "Label", &diskStructure.primaryDescriptor.volume_id, 1, 32, "Disk Label", ISO_STRING_FILTER_DCHARS },
+ { NULL }
+ }
+ */
+
+ assert(option != NULL);
+
+ if (debug & DEBUG_FS_PARSE_OPTS)
+ printf("cd9660_parse_opts: got `%s'\n", option);
+
+ if ((var = strdup(option)) == NULL)
+ err(1, "allocating memory for copy of option string");
+ rv = 1;
+
+ val = strchr(var, '=');
+ if (val != NULL)
+ *val++ = '\0';
+
+ /* First handle options with no parameters */
+ if (strcmp(var, "h") == 0) {
+ diskStructure.displayHelp = 1;
+ rv = 1;
+ } else if (CD9660_IS_COMMAND_ARG_DUAL(var, "S", "follow-symlinks")) {
+ /* this is not handled yet */
+ diskStructure.follow_sym_links = 1;
+ rv = 1;
+ } else if (CD9660_IS_COMMAND_ARG_DUAL(var, "L", "label")) {
+ rv = cd9660_arguments_set_string(val, "Disk Label", 32, 'd',
+ diskStructure.primaryDescriptor.volume_id);
+ } else if (CD9660_IS_COMMAND_ARG_DUAL(var, "A", "applicationid")) {
+ rv = cd9660_arguments_set_string(val, "Application Identifier", 128, 'a',
+ diskStructure.primaryDescriptor.application_id);
+ } else if(CD9660_IS_COMMAND_ARG_DUAL(var, "P", "publisher")) {
+ rv = cd9660_arguments_set_string(val, "Publisher Identifier",
+ 128, 'a', diskStructure.primaryDescriptor.publisher_id);
+ } else if (CD9660_IS_COMMAND_ARG_DUAL(var, "p", "preparer")) {
+ rv = cd9660_arguments_set_string(val, "Preparer Identifier",
+ 128, 'a', diskStructure.primaryDescriptor.preparer_id);
+ } else if (CD9660_IS_COMMAND_ARG_DUAL(var, "V", "volumeid")) {
+ rv = cd9660_arguments_set_string(val, "Volume Set Identifier",
+ 128, 'a', diskStructure.primaryDescriptor.volume_set_id);
+ /* Boot options */
+ } else if (CD9660_IS_COMMAND_ARG_DUAL(var, "B", "bootimage")) {
+ if (val == NULL)
+ warnx("error: The Boot Image parameter requires a valid boot information string");
+ else
+ rv = cd9660_add_boot_disk(val);
+ } else if (CD9660_IS_COMMAND_ARG(var, "bootimagedir")) {
+ /*
+ * XXXfvdl this is unused.
+ */
+ if (val == NULL)
+ errx(1, "error: The Boot Image Directory parameter"
+ " requires a directory name\n");
+ else {
+ if ((diskStructure.boot_image_directory =
+ malloc(strlen(val) + 1)) == NULL) {
+ CD9660_MEM_ALLOC_ERROR("cd9660_parse_opts");
+ exit(1);
+ }
+
+ /* BIG TODO: Add the max length function here */
+ cd9660_arguments_set_string(val, "Boot Image Directory",
+ 12 , 'd', diskStructure.boot_image_directory);
+ }
+ } else if (CD9660_IS_COMMAND_ARG_DUAL(var, "G", "generic-bootimage")) {
+ if (val == NULL)
+ warnx("error: The Boot Image parameter requires a valid boot information string");
+ else
+ rv = cd9660_add_generic_bootimage(val);
+ } else if (CD9660_IS_COMMAND_ARG(var, "no-trailing-padding"))
+ diskStructure.include_padding_areas = 0;
+ /* RRIP */
+ else if (CD9660_IS_COMMAND_ARG_DUAL(var, "R", "rockridge"))
+ diskStructure.rock_ridge_enabled = 1;
+ else if (CD9660_IS_COMMAND_ARG_DUAL(var, "A", "archimedes"))
+ diskStructure.archimedes_enabled = 1;
+ else if (CD9660_IS_COMMAND_ARG(var, "chrp-boot"))
+ diskStructure.chrp_boot = 1;
+ else if (CD9660_IS_COMMAND_ARG_DUAL(var, "K", "keep-bad-images"))
+ diskStructure.keep_bad_images = 1;
+ else if (CD9660_IS_COMMAND_ARG(var, "allow-deep-trees"))
+ diskStructure.allow_deep_trees = 1;
+ else if (CD9660_IS_COMMAND_ARG(var, "allow-max-name"))
+ diskStructure.allow_max_name = 1;
+ else if (CD9660_IS_COMMAND_ARG(var, "allow-illegal-chars"))
+ diskStructure.allow_illegal_chars = 1;
+ else if (CD9660_IS_COMMAND_ARG(var, "allow-lowercase"))
+ diskStructure.allow_lowercase = 1;
+ else if (CD9660_IS_COMMAND_ARG(var,"allow-multidot"))
+ diskStructure.allow_multidot = 1;
+ else if (CD9660_IS_COMMAND_ARG(var, "omit-trailing-period"))
+ diskStructure.omit_trailing_period = 1;
+ else if (CD9660_IS_COMMAND_ARG(var, "no-emul-boot") ||
+ CD9660_IS_COMMAND_ARG(var, "no-boot") ||
+ CD9660_IS_COMMAND_ARG(var, "hard-disk-boot")) {
+ cd9660_eltorito_add_boot_option(var, 0);
+
+ /* End of flag variables */
+ } else if (CD9660_IS_COMMAND_ARG(var, "boot-load-segment")) {
+ if (val == NULL) {
+ warnx("Option `%s' doesn't contain a value", var);
+ rv = 0;
+ } else {
+ cd9660_eltorito_add_boot_option(var, val);
+ }
+ } else {
+ if (val == NULL) {
+ warnx("Option `%s' doesn't contain a value", var);
+ rv = 0;
+ } else
+ rv = set_option(cd9660_options, var, val);
+ }
+
+ free(var);
+ return (rv);
+}
+
+/*
+ * Main function for cd9660_makefs
+ * Builds the ISO image file
+ * @param const char *image The image filename to create
+ * @param const char *dir The directory that is being read
+ * @param struct fsnode *root The root node of the filesystem tree
+ * @param struct fsinfo_t *fsopts Any options
+ */
+void
+cd9660_makefs(const char *image, const char *dir, fsnode *root,
+ fsinfo_t *fsopts)
+{
+ int64_t startoffset;
+ int numDirectories;
+ uint64_t pathTableSectors;
+ int64_t firstAvailableSector;
+ int64_t totalSpace;
+ int error;
+ cd9660node *real_root;
+
+ if (diskStructure.verbose_level > 0)
+ printf("cd9660_makefs: ISO level is %i\n",
+ diskStructure.isoLevel);
+ if (diskStructure.isoLevel < 2 &&
+ diskStructure.allow_multidot)
+ errx(1, "allow-multidot requires iso level of 2\n");
+
+ assert(image != NULL);
+ assert(dir != NULL);
+ assert(root != NULL);
+
+ if (diskStructure.displayHelp) {
+ /*
+ * Display help here - probably want to put it in
+ * a separate function
+ */
+ return;
+ }
+
+ if (diskStructure.verbose_level > 0)
+ printf("cd9660_makefs: image %s directory %s root %p\n",
+ image, dir, root);
+
+ /* Set up some constants. Later, these will be defined with options */
+
+ /* Counter needed for path tables */
+ numDirectories = 0;
+
+ /* Convert tree to our own format */
+ /* Actually, we now need to add the REAL root node, at level 0 */
+
+ real_root = cd9660_allocate_cd9660node();
+ if ((real_root->isoDirRecord =
+ malloc( sizeof(iso_directory_record_cd9660) )) == NULL) {
+ CD9660_MEM_ALLOC_ERROR("cd9660_makefs");
+ exit(1);
+ }
+
+ /* Leave filename blank for root */
+ memset(real_root->isoDirRecord->name, 0,
+ ISO_FILENAME_MAXLENGTH_WITH_PADDING);
+
+ real_root->level = 0;
+ diskStructure.rootNode = real_root;
+ real_root->type = CD9660_TYPE_DIR;
+ error = 0;
+ real_root->node = root;
+ cd9660_convert_structure(root, real_root, 1, &numDirectories, &error);
+
+ if (TAILQ_EMPTY(&real_root->cn_children)) {
+ errx(1, "cd9660_makefs: converted directory is empty. "
+ "Tree conversion failed\n");
+ } else if (error != 0) {
+ errx(1, "cd9660_makefs: tree conversion failed\n");
+ } else {
+ if (diskStructure.verbose_level > 0)
+ printf("cd9660_makefs: tree converted\n");
+ }
+
+ /* Add the dot and dot dot records */
+ cd9660_add_dot_records(real_root);
+
+ cd9660_setup_root_node();
+
+ if (diskStructure.verbose_level > 0)
+ printf("cd9660_makefs: done converting tree\n");
+
+ /* non-SUSP extensions */
+ if (diskStructure.archimedes_enabled)
+ archimedes_convert_tree(diskStructure.rootNode);
+
+ /* Rock ridge / SUSP init pass */
+ if (diskStructure.rock_ridge_enabled) {
+ cd9660_susp_initialize(diskStructure.rootNode,
+ diskStructure.rootNode, NULL);
+ }
+
+ /* Build path table structure */
+ diskStructure.pathTableLength = cd9660_generate_path_table();
+
+ pathTableSectors = CD9660_BLOCKS(diskStructure.sectorSize,
+ diskStructure.pathTableLength);
+
+ firstAvailableSector = cd9660_setup_volume_descriptors();
+ if (diskStructure.is_bootable) {
+ firstAvailableSector = cd9660_setup_boot(firstAvailableSector);
+ if (firstAvailableSector < 0)
+ errx(1, "setup_boot failed");
+ }
+ /* LE first, then BE */
+ diskStructure.primaryLittleEndianTableSector = firstAvailableSector;
+ diskStructure.primaryBigEndianTableSector =
+ diskStructure.primaryLittleEndianTableSector + pathTableSectors;
+
+ /* Set the secondary ones to -1, not going to use them for now */
+ diskStructure.secondaryBigEndianTableSector = -1;
+ diskStructure.secondaryLittleEndianTableSector = -1;
+
+ diskStructure.dataFirstSector =
+ diskStructure.primaryBigEndianTableSector + pathTableSectors;
+ if (diskStructure.verbose_level > 0)
+ printf("cd9660_makefs: Path table conversion complete. "
+ "Each table is %i bytes, or %" PRIu64 " sectors.\n",
+ diskStructure.pathTableLength, pathTableSectors);
+
+ startoffset = diskStructure.sectorSize*diskStructure.dataFirstSector;
+
+ totalSpace = cd9660_compute_offsets(real_root, startoffset);
+
+ diskStructure.totalSectors = diskStructure.dataFirstSector +
+ CD9660_BLOCKS(diskStructure.sectorSize, totalSpace);
+
+ /* Disabled until pass 1 is done */
+ if (diskStructure.rock_ridge_enabled) {
+ diskStructure.susp_continuation_area_start_sector =
+ diskStructure.totalSectors;
+ diskStructure.totalSectors +=
+ CD9660_BLOCKS(diskStructure.sectorSize,
+ diskStructure.susp_continuation_area_size);
+ cd9660_susp_finalize(diskStructure.rootNode);
+ }
+
+
+ cd9660_finalize_PVD();
+
+ /* Add padding sectors, just for testing purposes right now */
+ /* diskStructure.totalSectors+=150; */
+
+ /* Debugging output */
+ if (diskStructure.verbose_level > 0) {
+ printf("cd9660_makefs: Sectors 0-15 reserved\n");
+ printf("cd9660_makefs: Primary path tables starts in sector %"
+ PRId64 "\n", diskStructure.primaryLittleEndianTableSector);
+ printf("cd9660_makefs: File data starts in sector %"
+ PRId64 "\n", diskStructure.dataFirstSector);
+ printf("cd9660_makefs: Total sectors: %"
+ PRId64 "\n", diskStructure.totalSectors);
+ }
+
+ /*
+ * Add padding sectors at the end
+ * TODO: Clean this up and separate padding
+ */
+ if (diskStructure.include_padding_areas)
+ diskStructure.totalSectors += 150;
+
+ cd9660_write_image(image);
+
+ if (diskStructure.verbose_level > 1) {
+ debug_print_volume_descriptor_information();
+ debug_print_tree(real_root,0);
+ debug_print_path_tree(real_root);
+ }
+
+ /* Clean up data structures */
+ cd9660_free_structure(real_root);
+
+ if (diskStructure.verbose_level > 0)
+ printf("cd9660_makefs: done\n");
+}
+
+/* Generic function pointer - implement later */
+typedef int (*cd9660node_func)(cd9660node *);
+
+static void
+cd9660_finalize_PVD(void)
+{
+ time_t tim;
+
+ /* root should be a fixed size of 34 bytes since it has no name */
+ memcpy(diskStructure.primaryDescriptor.root_directory_record,
+ diskStructure.rootNode->dot_record->isoDirRecord, 34);
+
+ /* In RRIP, this might be longer than 34 */
+ diskStructure.primaryDescriptor.root_directory_record[0] = 34;
+
+ /* Set up all the important numbers in the PVD */
+ cd9660_bothendian_dword(diskStructure.totalSectors,
+ (unsigned char *)diskStructure.primaryDescriptor.volume_space_size);
+ cd9660_bothendian_word(1,
+ (unsigned char *)diskStructure.primaryDescriptor.volume_set_size);
+ cd9660_bothendian_word(1,
+ (unsigned char *)
+ diskStructure.primaryDescriptor.volume_sequence_number);
+ cd9660_bothendian_word(diskStructure.sectorSize,
+ (unsigned char *)
+ diskStructure.primaryDescriptor.logical_block_size);
+ cd9660_bothendian_dword(diskStructure.pathTableLength,
+ (unsigned char *)diskStructure.primaryDescriptor.path_table_size);
+
+ cd9660_731(diskStructure.primaryLittleEndianTableSector,
+ (u_char *)diskStructure.primaryDescriptor.type_l_path_table);
+ cd9660_732(diskStructure.primaryBigEndianTableSector,
+ (u_char *)diskStructure.primaryDescriptor.type_m_path_table);
+
+ diskStructure.primaryDescriptor.file_structure_version[0] = 1;
+
+ /* Pad all strings with spaces instead of nulls */
+ cd9660_pad_string_spaces(diskStructure.primaryDescriptor.volume_id, 32);
+ cd9660_pad_string_spaces(diskStructure.primaryDescriptor.system_id, 32);
+ cd9660_pad_string_spaces(diskStructure.primaryDescriptor.volume_set_id,
+ 128);
+ cd9660_pad_string_spaces(diskStructure.primaryDescriptor.publisher_id,
+ 128);
+ cd9660_pad_string_spaces(diskStructure.primaryDescriptor.preparer_id,
+ 128);
+ cd9660_pad_string_spaces(diskStructure.primaryDescriptor.application_id,
+ 128);
+ cd9660_pad_string_spaces(
+ diskStructure.primaryDescriptor.copyright_file_id, 37);
+ cd9660_pad_string_spaces(
+ diskStructure.primaryDescriptor.abstract_file_id, 37);
+ cd9660_pad_string_spaces(
+ diskStructure.primaryDescriptor.bibliographic_file_id, 37);
+
+ /* Setup dates */
+ time(&tim);
+ cd9660_time_8426(
+ (unsigned char *)diskStructure.primaryDescriptor.creation_date,
+ tim);
+ cd9660_time_8426(
+ (unsigned char *)diskStructure.primaryDescriptor.modification_date,
+ tim);
+
+ /*
+ cd9660_set_date(diskStructure.primaryDescriptor.expiration_date, now);
+ */
+
+ memset(diskStructure.primaryDescriptor.expiration_date, '0', 16);
+ diskStructure.primaryDescriptor.expiration_date[16] = 0;
+ cd9660_time_8426(
+ (unsigned char *)diskStructure.primaryDescriptor.effective_date,
+ tim);
+}
+
+static void
+cd9660_populate_iso_dir_record(struct _iso_directory_record_cd9660 *record,
+ u_char ext_attr_length, u_char flags,
+ u_char name_len, const char * name)
+{
+ record->ext_attr_length[0] = ext_attr_length;
+ record->flags[0] = ISO_FLAG_CLEAR | flags;
+ record->file_unit_size[0] = 0;
+ record->interleave[0] = 0;
+ cd9660_bothendian_word(1, record->volume_sequence_number);
+ record->name_len[0] = name_len;
+ memset(record->name, '\0', sizeof (record->name));
+ memcpy(record->name, name, name_len);
+ record->length[0] = 33 + name_len;
+
+ /* Todo : better rounding */
+ record->length[0] += (record->length[0] & 1) ? 1 : 0;
+}
+
+static void
+cd9660_setup_root_node(void)
+{
+ cd9660_populate_iso_dir_record(diskStructure.rootNode->isoDirRecord,
+ 0, ISO_FLAG_DIRECTORY, 1, "\0");
+
+}
+
+/*********** SUPPORT FUNCTIONS ***********/
+static int
+cd9660_setup_volume_descriptors(void)
+{
+ /* Boot volume descriptor should come second */
+ int sector = 16;
+ /* For now, a fixed 2 : PVD and terminator */
+ volume_descriptor *temp, *t;
+
+ /* Set up the PVD */
+ if ((temp = malloc(sizeof(volume_descriptor))) == NULL) {
+ CD9660_MEM_ALLOC_ERROR("cd9660_setup_volume_descriptors");
+ exit(1);
+ }
+
+ temp->volumeDescriptorData =
+ (unsigned char *)&diskStructure.primaryDescriptor;
+ temp->volumeDescriptorData[0] = ISO_VOLUME_DESCRIPTOR_PVD;
+ temp->volumeDescriptorData[6] = 1;
+ temp->sector = sector;
+ memcpy(temp->volumeDescriptorData + 1,
+ ISO_VOLUME_DESCRIPTOR_STANDARD_ID, 5);
+ diskStructure.firstVolumeDescriptor = temp;
+
+ sector++;
+ /* Set up boot support if enabled. BVD must reside in sector 17 */
+ if (diskStructure.is_bootable) {
+ if ((t = malloc(sizeof(volume_descriptor))) == NULL) {
+ CD9660_MEM_ALLOC_ERROR(
+ "cd9660_setup_volume_descriptors");
+ exit(1);
+ }
+ if ((t->volumeDescriptorData = malloc(2048)) == NULL) {
+ CD9660_MEM_ALLOC_ERROR(
+ "cd9660_setup_volume_descriptors");
+ exit(1);
+ }
+ temp->next = t;
+ temp = t;
+ memset(t->volumeDescriptorData, 0, 2048);
+ t->sector = 17;
+ if (diskStructure.verbose_level > 0)
+ printf("Setting up boot volume descriptor\n");
+ cd9660_setup_boot_volume_descriptor(t);
+ sector++;
+ }
+
+ /* Set up the terminator */
+ if ((t = malloc(sizeof(volume_descriptor))) == NULL) {
+ CD9660_MEM_ALLOC_ERROR("cd9660_setup_volume_descriptors");
+ exit(1);
+ }
+ if ((t->volumeDescriptorData = malloc(2048)) == NULL) {
+ CD9660_MEM_ALLOC_ERROR("cd9660_setup_volume_descriptors");
+ exit(1);
+ }
+
+ temp->next = t;
+ memset(t->volumeDescriptorData, 0, 2048);
+ t->volumeDescriptorData[0] = ISO_VOLUME_DESCRIPTOR_TERMINATOR;
+ t->next = 0;
+ t->volumeDescriptorData[6] = 1;
+ t->sector = sector;
+ memcpy(t->volumeDescriptorData + 1,
+ ISO_VOLUME_DESCRIPTOR_STANDARD_ID, 5);
+
+ sector++;
+ return sector;
+}
+
+#if 0
+/*
+ * Populate EAR at some point. Not required, but is used by NetBSD's
+ * cd9660 support
+ */
+static int
+cd9660_fill_extended_attribute_record(cd9660node *node)
+{
+ if ((node->isoExtAttributes =
+ malloc(sizeof(struct iso_extended_attributes))) == NULL) {
+ CD9660_MEM_ALLOC_ERROR("cd9660_fill_extended_attribute_record");
+ exit(1);
+ };
+
+ return 1;
+}
+#endif
+
+static int
+cd9660_translate_node_common(cd9660node *newnode)
+{
+ time_t tim;
+ int test;
+ u_char flag;
+ char temp[ISO_FILENAME_MAXLENGTH_WITH_PADDING];
+
+ /* Now populate the isoDirRecord structure */
+ memset(temp, 0, ISO_FILENAME_MAXLENGTH_WITH_PADDING);
+
+ test = cd9660_convert_filename(newnode->node->name,
+ temp, !(S_ISDIR(newnode->node->type)));
+
+ flag = ISO_FLAG_CLEAR;
+ if (S_ISDIR(newnode->node->type))
+ flag |= ISO_FLAG_DIRECTORY;
+
+ cd9660_populate_iso_dir_record(newnode->isoDirRecord, 0,
+ flag, strlen(temp), temp);
+
+ /* Set the various dates */
+
+ /* If we want to use the current date and time */
+ time(&tim);
+
+ cd9660_time_915(newnode->isoDirRecord->date, tim);
+
+ cd9660_bothendian_dword(newnode->fileDataLength,
+ newnode->isoDirRecord->size);
+ /* If the file is a link, we want to set the size to 0 */
+ if (S_ISLNK(newnode->node->type))
+ newnode->fileDataLength = 0;
+
+ return 1;
+}
+
+/*
+ * Translate fsnode to cd9660node
+ * Translate filenames and other metadata, including dates, sizes,
+ * permissions, etc
+ * @param struct fsnode * The node generated by makefs
+ * @param struct cd9660node * The intermediate node to be written to
+ * @returns int 0 on failure, 1 on success
+ */
+static int
+cd9660_translate_node(fsnode *node, cd9660node *newnode)
+{
+ if (node == NULL) {
+ if (diskStructure.verbose_level > 0)
+ printf("cd9660_translate_node: NULL node passed, "
+ "returning\n");
+ return 0;
+ }
+ if ((newnode->isoDirRecord =
+ malloc(sizeof(iso_directory_record_cd9660))) == NULL) {
+ CD9660_MEM_ALLOC_ERROR("cd9660_translate_node");
+ return 0;
+ }
+
+ /* Set the node pointer */
+ newnode->node = node;
+
+ /* Set the size */
+ if (!(S_ISDIR(node->type)))
+ newnode->fileDataLength = node->inode->st.st_size;
+
+ if (cd9660_translate_node_common(newnode) == 0)
+ return 0;
+
+ /* Finally, overwrite some of the values that are set by default */
+ cd9660_time_915(newnode->isoDirRecord->date, node->inode->st.st_mtime);
+
+ return 1;
+}
+
+/*
+ * Compares two ISO filenames
+ * @param const char * The first file name
+ * @param const char * The second file name
+ * @returns : -1 if first is less than second, 0 if they are the same, 1 if
+ * the second is greater than the first
+ */
+static int
+cd9660_compare_filename(const char *first, const char *second)
+{
+ /*
+ * This can be made more optimal once it has been tested
+ * (the extra character, for example, is for testing)
+ */
+
+ int p1 = 0;
+ int p2 = 0;
+ char c1, c2;
+ /* First, on the filename */
+
+ while (p1 < ISO_FILENAME_MAXLENGTH_BEFORE_VERSION-1
+ && p2 < ISO_FILENAME_MAXLENGTH_BEFORE_VERSION-1) {
+ c1 = first[p1];
+ c2 = second[p2];
+ if (c1 == '.' && c2 =='.')
+ break;
+ else if (c1 == '.') {
+ p2++;
+ c1 = ' ';
+ } else if (c2 == '.') {
+ p1++;
+ c2 = ' ';
+ } else {
+ p1++;
+ p2++;
+ }
+
+ if (c1 < c2)
+ return -1;
+ else if (c1 > c2) {
+ return 1;
+ }
+ }
+
+ if (first[p1] == '.' && second[p2] == '.') {
+ p1++;
+ p2++;
+ while (p1 < ISO_FILENAME_MAXLENGTH_BEFORE_VERSION - 1
+ && p2 < ISO_FILENAME_MAXLENGTH_BEFORE_VERSION - 1) {
+ c1 = first[p1];
+ c2 = second[p2];
+ if (c1 == ';' && c2 == ';')
+ break;
+ else if (c1 == ';') {
+ p2++;
+ c1 = ' ';
+ } else if (c2 == ';') {
+ p1++;
+ c2 = ' ';
+ } else {
+ p1++;
+ p2++;
+ }
+
+ if (c1 < c2)
+ return -1;
+ else if (c1 > c2)
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Insert a node into list with ISO sorting rules
+ * @param cd9660node * The head node of the list
+ * @param cd9660node * The node to be inserted
+ */
+static void
+cd9660_sorted_child_insert(cd9660node *parent, cd9660node *cn_new)
+{
+ int compare;
+ cd9660node *cn;
+ struct cd9660_children_head *head = &parent->cn_children;
+
+ /* TODO: Optimize? */
+ cn_new->parent = parent;
+
+ /*
+ * first will either be 0, the . or the ..
+ * if . or .., this means no other entry may be written before first
+ * if 0, the new node may be inserted at the head
+ */
+
+ TAILQ_FOREACH(cn, head, cn_next_child) {
+ /*
+ * Dont insert a node twice -
+ * that would cause an infinite loop
+ */
+ if (cn_new == cn)
+ return;
+
+ compare = cd9660_compare_filename(cn_new->isoDirRecord->name,
+ cn->isoDirRecord->name);
+
+ if (compare == 0)
+ compare = cd9660_compare_filename(cn_new->node->name,
+ cn->node->name);
+
+ if (compare < 0)
+ break;
+ }
+ if (cn == NULL)
+ TAILQ_INSERT_TAIL(head, cn_new, cn_next_child);
+ else
+ TAILQ_INSERT_BEFORE(cn, cn_new, cn_next_child);
+}
+
+/*
+ * Called After cd9660_sorted_child_insert
+ * handles file collisions by suffixing each filname with ~n
+ * where n represents the files respective place in the ordering
+ */
+static int
+cd9660_handle_collisions(cd9660node *colliding, int past)
+{
+ cd9660node *iter, *next, *prev;
+ int skip;
+ int delete_chars = 0;
+ int temp_past = past;
+ int temp_skip;
+ int flag = 0;
+ cd9660node *end_of_range;
+
+ for (iter = TAILQ_FIRST(&colliding->cn_children);
+ iter != NULL && (next = TAILQ_NEXT(iter, cn_next_child)) != NULL;) {
+ if (strcmp(iter->isoDirRecord->name,
+ next->isoDirRecord->name) != 0) {
+ iter = TAILQ_NEXT(iter, cn_next_child);
+ continue;
+ }
+ flag = 1;
+ temp_skip = skip = cd9660_count_collisions(iter);
+ end_of_range = iter;
+ while (temp_skip > 0) {
+ temp_skip--;
+ end_of_range = TAILQ_NEXT(end_of_range, cn_next_child);
+ }
+ temp_past = past;
+ while (temp_past > 0) {
+ if ((next = TAILQ_NEXT(end_of_range, cn_next_child)) != NULL)
+ end_of_range = next;
+ else if ((prev = TAILQ_PREV(iter, cd9660_children_head, cn_next_child)) != NULL)
+ iter = prev;
+ else
+ delete_chars++;
+ temp_past--;
+ }
+ skip += past;
+ iter = cd9660_rename_filename(iter, skip, delete_chars);
+ }
+ return flag;
+}
+
+
+static cd9660node *
+cd9660_rename_filename(cd9660node *iter, int num, int delete_chars)
+{
+ int i = 0;
+ int numbts, digit, digits, temp, powers, count;
+ char *naming;
+ int maxlength;
+ char *tmp;
+
+ if (diskStructure.verbose_level > 0)
+ printf("Rename_filename called\n");
+
+ assert(1 <= diskStructure.isoLevel && diskStructure.isoLevel <= 2);
+ /* TODO : A LOT of chanes regarding 8.3 filenames */
+ if (diskStructure.isoLevel == 1)
+ maxlength = 8;
+ else if (diskStructure.isoLevel == 2)
+ maxlength = 31;
+ else
+ maxlength = ISO_FILENAME_MAXLENGTH_BEFORE_VERSION;
+
+ tmp = malloc(ISO_FILENAME_MAXLENGTH_WITH_PADDING);
+
+ while (i < num) {
+ powers = 1;
+ count = 0;
+ digits = 1;
+ while (((int)(i / powers) ) >= 10) {
+ digits++;
+ powers = powers * 10;
+ }
+
+ naming = iter->o_name;
+
+ /*
+ while ((*naming != '.') && (*naming != ';')) {
+ naming++;
+ count++;
+ }
+ */
+
+ while (count < maxlength) {
+ if (*naming == ';')
+ break;
+ naming++;
+ count++;
+ }
+
+ if ((count + digits) < maxlength)
+ numbts = count;
+ else
+ numbts = maxlength - (digits);
+ numbts -= delete_chars;
+
+ /* 8.3 rules - keep the extension, add before the dot */
+
+ /*
+ * This code makes a bunch of assumptions.
+ * See if you can spot them all :)
+ */
+
+ /*
+ if (diskStructure.isoLevel == 1) {
+ numbts = 8 - digits - delete_chars;
+ if (dot < 0) {
+
+ } else {
+ if (dot < 8) {
+ memmove(&tmp[numbts],&tmp[dot],4);
+ }
+ }
+ }
+ */
+
+ /* (copying just the filename before the '.' */
+ memcpy(tmp, (iter->o_name), numbts);
+
+ /* adding the appropriate number following the name */
+ temp = i;
+ while (digits > 0) {
+ digit = (int)(temp / powers);
+ temp = temp - digit * powers;
+ sprintf(&tmp[numbts] , "%d", digit);
+ digits--;
+ numbts++;
+ powers = powers / 10;
+ }
+
+ while ((*naming != ';') && (numbts < maxlength)) {
+ tmp[numbts] = (*naming);
+ naming++;
+ numbts++;
+ }
+
+ tmp[numbts] = ';';
+ tmp[numbts+1] = '1';
+ tmp[numbts+2] = '\0';
+
+ /*
+ * now tmp has exactly the identifier
+ * we want so we'll copy it back to record
+ */
+ memcpy((iter->isoDirRecord->name), tmp, numbts + 3);
+
+ iter = TAILQ_NEXT(iter, cn_next_child);
+ i++;
+ }
+
+ free(tmp);
+ return iter;
+}
+
+/* Todo: Figure out why these functions are nec. */
+static void
+cd9660_copy_filenames(cd9660node *node)
+{
+ cd9660node *cn;
+
+ if (TAILQ_EMPTY(&node->cn_children))
+ return;
+
+ if (TAILQ_FIRST(&node->cn_children)->isoDirRecord == NULL) {
+ debug_print_tree(diskStructure.rootNode, 0);
+ exit(1);
+ }
+
+ TAILQ_FOREACH(cn, &node->cn_children, cn_next_child) {
+ cd9660_copy_filenames(cn);
+ memcpy(cn->o_name, cn->isoDirRecord->name,
+ ISO_FILENAME_MAXLENGTH_WITH_PADDING);
+ }
+}
+
+static void
+cd9660_sorting_nodes(cd9660node *node)
+{
+ cd9660node *cn;
+
+ TAILQ_FOREACH(cn, &node->cn_children, cn_next_child)
+ cd9660_sorting_nodes(cn);
+ cd9660_sort_nodes(node);
+}
+
+/* XXX Bubble sort. */
+static void
+cd9660_sort_nodes(cd9660node *node)
+{
+ cd9660node *cn, *next;
+
+ do {
+ TAILQ_FOREACH(cn, &node->cn_children, cn_next_child) {
+ if ((next = TAILQ_NEXT(cn, cn_next_child)) == NULL)
+ return;
+ else if (strcmp(next->isoDirRecord->name,
+ cn->isoDirRecord->name) >= 0)
+ continue;
+ TAILQ_REMOVE(&node->cn_children, next, cn_next_child);
+ TAILQ_INSERT_BEFORE(cn, next, cn_next_child);
+ break;
+ }
+ } while (cn != NULL);
+}
+
+static int
+cd9660_count_collisions(cd9660node *copy)
+{
+ int count = 0;
+ cd9660node *iter, *next;
+
+ for (iter = copy;
+ (next = TAILQ_NEXT(iter, cn_next_child)) != NULL;
+ iter = next) {
+ if (cd9660_compare_filename(iter->isoDirRecord->name,
+ next->isoDirRecord->name) == 0)
+ count++;
+ else
+ return count;
+ }
+#if 0
+ if ((next = TAILQ_NEXT(iter, cn_next_child)) != NULL) {
+ printf("cd9660_recurse_on_collision: count is %i \n", count);
+ compare = cd9660_compare_filename(iter->isoDirRecord->name,
+ next->isoDirRecord->name);
+ if (compare == 0) {
+ count++;
+ return cd9660_recurse_on_collision(next, count);
+ } else
+ return count;
+ }
+#endif
+ return count;
+}
+
+static cd9660node *
+cd9660_rrip_move_directory(cd9660node *dir)
+{
+ char newname[9];
+ cd9660node *tfile;
+
+ /*
+ * This function needs to:
+ * 1) Create an empty virtual file in place of the old directory
+ * 2) Point the virtual file to the new directory
+ * 3) Point the relocated directory to its old parent
+ * 4) Move the directory specified by dir into rr_moved_dir,
+ * and rename it to "diskStructure.rock_ridge_move_count" (as a string)
+ */
+
+ /* First see if the moved directory even exists */
+ if (diskStructure.rr_moved_dir == NULL) {
+ diskStructure.rr_moved_dir =
+ cd9660_create_directory(ISO_RRIP_DEFAULT_MOVE_DIR_NAME,
+ diskStructure.rootNode, dir);
+ if (diskStructure.rr_moved_dir == NULL)
+ return 0;
+ }
+
+ /* Create a file with the same ORIGINAL name */
+ tfile = cd9660_create_file(dir->node->name, dir->parent, dir);
+ if (tfile == NULL)
+ return NULL;
+
+ diskStructure.rock_ridge_move_count++;
+ snprintf(newname, sizeof(newname), "%08i",
+ diskStructure.rock_ridge_move_count);
+
+ /* Point to old parent */
+ dir->rr_real_parent = dir->parent;
+
+ /* Place the placeholder file */
+ if (TAILQ_EMPTY(&dir->rr_real_parent->cn_children)) {
+ TAILQ_INSERT_HEAD(&dir->rr_real_parent->cn_children, tfile,
+ cn_next_child);
+ } else {
+ cd9660_sorted_child_insert(dir->rr_real_parent, tfile);
+ }
+
+ /* Point to new parent */
+ dir->parent = diskStructure.rr_moved_dir;
+
+ /* Point the file to the moved directory */
+ tfile->rr_relocated = dir;
+
+ /* Actually move the directory */
+ cd9660_sorted_child_insert(diskStructure.rr_moved_dir, dir);
+
+ /* TODO: Inherit permissions / ownership (basically the entire inode) */
+
+ /* Set the new name */
+ memset(dir->isoDirRecord->name, 0, ISO_FILENAME_MAXLENGTH_WITH_PADDING);
+ strncpy(dir->isoDirRecord->name, newname, 8);
+ dir->isoDirRecord->length[0] = 34 + 8;
+ dir->isoDirRecord->name_len[0] = 8;
+
+ return dir;
+}
+
+static int
+cd9660_add_dot_records(cd9660node *root)
+{
+ struct cd9660_children_head *head = &root->cn_children;
+ cd9660node *cn;
+
+ TAILQ_FOREACH(cn, head, cn_next_child) {
+ if ((cn->type & CD9660_TYPE_DIR) == 0)
+ continue;
+ /* Recursion first */
+ cd9660_add_dot_records(cn);
+ }
+ cd9660_create_special_directory(CD9660_TYPE_DOT, root);
+ cd9660_create_special_directory(CD9660_TYPE_DOTDOT, root);
+ return 1;
+}
+
+/*
+ * Convert node to cd9660 structure
+ * This function is designed to be called recursively on the root node of
+ * the filesystem
+ * Lots of recursion going on here, want to make sure it is efficient
+ * @param struct fsnode * The root node to be converted
+ * @param struct cd9660* The parent node (should not be NULL)
+ * @param int Current directory depth
+ * @param int* Running count of the number of directories that are being created
+ */
+static void
+cd9660_convert_structure(fsnode *root, cd9660node *parent_node, int level,
+ int *numDirectories, int *error)
+{
+ fsnode *iterator = root;
+ cd9660node *this_node;
+ int working_level;
+ int add;
+ int flag = 0;
+ int counter = 0;
+
+ /*
+ * Newer, more efficient method, reduces recursion depth
+ */
+ if (root == NULL) {
+ warnx("%s: root is null\n", __func__);
+ return;
+ }
+
+ /* Test for an empty directory - makefs still gives us the . record */
+ if ((S_ISDIR(root->type)) && (root->name[0] == '.')
+ && (root->name[1] == '\0')) {
+ root = root->next;
+ if (root == NULL)
+ return;
+ }
+ if ((this_node = cd9660_allocate_cd9660node()) == NULL) {
+ CD9660_MEM_ALLOC_ERROR(__func__);
+ }
+
+ /*
+ * To reduce the number of recursive calls, we will iterate over
+ * the next pointers to the right.
+ */
+ while (iterator != NULL) {
+ add = 1;
+ /*
+ * Increment the directory count if this is a directory
+ * Ignore "." entries. We will generate them later
+ */
+ if (!S_ISDIR(iterator->type) ||
+ strcmp(iterator->name, ".") != 0) {
+
+ /* Translate the node, including its filename */
+ this_node->parent = parent_node;
+ cd9660_translate_node(iterator, this_node);
+ this_node->level = level;
+
+ if (S_ISDIR(iterator->type)) {
+ (*numDirectories)++;
+ this_node->type = CD9660_TYPE_DIR;
+ working_level = level + 1;
+
+ /*
+ * If at level 8, directory would be at 8
+ * and have children at 9 which is not
+ * allowed as per ISO spec
+ */
+ if (level == 8) {
+ if ((!diskStructure.allow_deep_trees) &&
+ (!diskStructure.rock_ridge_enabled)) {
+ warnx("error: found entry "
+ "with depth greater "
+ "than 8.");
+ (*error) = 1;
+ return;
+ } else if (diskStructure.
+ rock_ridge_enabled) {
+ working_level = 3;
+ /*
+ * Moved directory is actually
+ * at level 2.
+ */
+ this_node->level =
+ working_level - 1;
+ if (cd9660_rrip_move_directory(
+ this_node) == 0) {
+ warnx("Failure in "
+ "cd9660_rrip_"
+ "move_directory"
+ );
+ (*error) = 1;
+ return;
+ }
+ add = 0;
+ }
+ }
+
+ /* Do the recursive call on the children */
+ if (iterator->child != 0) {
+ cd9660_convert_structure(
+ iterator->child, this_node,
+ working_level,
+ numDirectories, error);
+
+ if ((*error) == 1) {
+ warnx("%s: Error on recursive "
+ "call", __func__);
+ return;
+ }
+ }
+
+ } else {
+ /* Only directories should have children */
+ assert(iterator->child == NULL);
+
+ this_node->type = CD9660_TYPE_FILE;
+ }
+
+ /*
+ * Finally, do a sorted insert
+ */
+ if (add) {
+ cd9660_sorted_child_insert(
+ parent_node, this_node);
+ }
+
+ /*Allocate new temp_node */
+ if (iterator->next != 0) {
+ this_node = cd9660_allocate_cd9660node();
+ if (this_node == NULL)
+ CD9660_MEM_ALLOC_ERROR(__func__);
+ }
+ }
+ iterator = iterator->next;
+ }
+
+ /* cd9660_handle_collisions(first_node); */
+
+ /* TODO: need cleanup */
+ cd9660_copy_filenames(parent_node);
+
+ do {
+ flag = cd9660_handle_collisions(parent_node, counter);
+ counter++;
+ cd9660_sorting_nodes(parent_node);
+ } while ((flag == 1) && (counter < 100));
+}
+
+/*
+ * Clean up the cd9660node tree
+ * This is designed to be called recursively on the root node
+ * @param struct cd9660node *root The node to free
+ * @returns void
+ */
+static void
+cd9660_free_structure(cd9660node *root)
+{
+ cd9660node *cn;
+
+ while ((cn = TAILQ_FIRST(&root->cn_children)) != NULL) {
+ TAILQ_REMOVE(&root->cn_children, cn, cn_next_child);
+ cd9660_free_structure(cn);
+ }
+ free(root);
+}
+
+/*
+ * Be a little more memory conservative:
+ * instead of having the TAILQ_ENTRY as part of the cd9660node,
+ * just create a temporary structure
+ */
+struct ptq_entry
+{
+ TAILQ_ENTRY(ptq_entry) ptq;
+ cd9660node *node;
+} *n;
+
+#define PTQUEUE_NEW(n,s,r,t){\
+ n = malloc(sizeof(struct s)); \
+ if (n == NULL) \
+ return r; \
+ n->node = t;\
+}
+
+/*
+ * Generate the path tables
+ * The specific implementation of this function is left as an exercise to the
+ * programmer. It could be done recursively. Make sure you read how the path
+ * table has to be laid out, it has levels.
+ * @param struct iso9660_disk *disk The disk image
+ * @returns int The number of built path tables (between 1 and 4), 0 on failure
+ */
+static int
+cd9660_generate_path_table(void)
+{
+ cd9660node *cn, *dirNode = diskStructure.rootNode;
+ cd9660node *last = dirNode;
+ int pathTableSize = 0; /* computed as we go */
+ int counter = 1; /* root gets a count of 0 */
+
+ TAILQ_HEAD(cd9660_pt_head, ptq_entry) pt_head;
+ TAILQ_INIT(&pt_head);
+
+ PTQUEUE_NEW(n, ptq_entry, -1, diskStructure.rootNode);
+
+ /* Push the root node */
+ TAILQ_INSERT_HEAD(&pt_head, n, ptq);
+
+ /* Breadth-first traversal of file structure */
+ while (pt_head.tqh_first != 0) {
+ n = pt_head.tqh_first;
+ dirNode = n->node;
+ TAILQ_REMOVE(&pt_head, pt_head.tqh_first, ptq);
+ free(n);
+
+ /* Update the size */
+ pathTableSize += ISO_PATHTABLE_ENTRY_BASESIZE
+ + dirNode->isoDirRecord->name_len[0]+
+ (dirNode->isoDirRecord->name_len[0] % 2 == 0 ? 0 : 1);
+ /* includes the padding bit */
+
+ dirNode->ptnumber=counter;
+ if (dirNode != last) {
+ last->ptnext = dirNode;
+ dirNode->ptprev = last;
+ }
+ last = dirNode;
+
+ /* Push children onto queue */
+ TAILQ_FOREACH(cn, &dirNode->cn_children, cn_next_child) {
+ /*
+ * Dont add the DOT and DOTDOT types to the path
+ * table.
+ */
+ if ((cn->type != CD9660_TYPE_DOT)
+ && (cn->type != CD9660_TYPE_DOTDOT)) {
+
+ if (S_ISDIR(cn->node->type)) {
+ PTQUEUE_NEW(n, ptq_entry, -1, cn);
+ TAILQ_INSERT_TAIL(&pt_head, n, ptq);
+ }
+ }
+ }
+ counter++;
+ }
+ return pathTableSize;
+}
+
+void
+cd9660_compute_full_filename(cd9660node *node, char *buf)
+{
+ int len;
+
+ len = CD9660MAXPATH + 1;
+ len = snprintf(buf, len, "%s/%s/%s", node->node->root,
+ node->node->path, node->node->name);
+ if (len > CD9660MAXPATH)
+ errx(1, "Pathname too long.");
+}
+
+/* NEW filename conversion method */
+typedef int(*cd9660_filename_conversion_functor)(const char *, char *, int);
+
+
+/*
+ * TODO: These two functions are almost identical.
+ * Some code cleanup is possible here
+ *
+ * XXX bounds checking!
+ */
+static int
+cd9660_level1_convert_filename(const char *oldname, char *newname, int is_file)
+{
+ /*
+ * ISO 9660 : 10.1
+ * File Name shall not contain more than 8 d or d1 characters
+ * File Name Extension shall not contain more than 3 d or d1 characters
+ * Directory Identifier shall not contain more than 8 d or d1 characters
+ */
+ int namelen = 0;
+ int extlen = 0;
+ int found_ext = 0;
+
+ while (*oldname != '\0' && extlen < 3) {
+ /* Handle period first, as it is special */
+ if (*oldname == '.') {
+ if (found_ext) {
+ *newname++ = '_';
+ extlen ++;
+ }
+ else {
+ *newname++ = '.';
+ found_ext = 1;
+ }
+ } else {
+ /* cut RISC OS file type off ISO name */
+ if (diskStructure.archimedes_enabled &&
+ *oldname == ',' && strlen(oldname) == 4)
+ break;
+ /* Enforce 12.3 / 8 */
+ if (namelen == 8 && !found_ext)
+ break;
+
+ if (islower((unsigned char)*oldname))
+ *newname++ = toupper((unsigned char)*oldname);
+ else if (isupper((unsigned char)*oldname)
+ || isdigit((unsigned char)*oldname))
+ *newname++ = *oldname;
+ else
+ *newname++ = '_';
+
+ if (found_ext)
+ extlen++;
+ else
+ namelen++;
+ }
+ oldname ++;
+ }
+ if (is_file) {
+ if (!found_ext && !diskStructure.omit_trailing_period)
+ *newname++ = '.';
+ /* Add version */
+ sprintf(newname, ";%i", 1);
+ }
+ return namelen + extlen + found_ext;
+}
+
+/* XXX bounds checking! */
+static int
+cd9660_level2_convert_filename(const char *oldname, char *newname, int is_file)
+{
+ /*
+ * ISO 9660 : 7.5.1
+ * File name : 0+ d or d1 characters
+ * separator 1 (.)
+ * File name extension : 0+ d or d1 characters
+ * separator 2 (;)
+ * File version number (5 characters, 1-32767)
+ * 1 <= Sum of File name and File name extension <= 30
+ */
+ int namelen = 0;
+ int extlen = 0;
+ int found_ext = 0;
+
+ while (*oldname != '\0' && namelen + extlen < 30) {
+ /* Handle period first, as it is special */
+ if (*oldname == '.') {
+ if (found_ext) {
+ if (diskStructure.allow_multidot) {
+ *newname++ = '.';
+ } else {
+ *newname++ = '_';
+ }
+ extlen ++;
+ }
+ else {
+ *newname++ = '.';
+ found_ext = 1;
+ }
+ } else {
+ /* cut RISC OS file type off ISO name */
+ if (diskStructure.archimedes_enabled &&
+ *oldname == ',' && strlen(oldname) == 4)
+ break;
+
+ if (islower((unsigned char)*oldname))
+ *newname++ = toupper((unsigned char)*oldname);
+ else if (isupper((unsigned char)*oldname) ||
+ isdigit((unsigned char)*oldname))
+ *newname++ = *oldname;
+ else if (diskStructure.allow_multidot &&
+ *oldname == '.') {
+ *newname++ = '.';
+ } else {
+ *newname++ = '_';
+ }
+
+ if (found_ext)
+ extlen++;
+ else
+ namelen++;
+ }
+ oldname ++;
+ }
+ if (is_file) {
+ if (!found_ext && !diskStructure.omit_trailing_period)
+ *newname++ = '.';
+ /* Add version */
+ sprintf(newname, ";%i", 1);
+ }
+ return namelen + extlen + found_ext;
+}
+
+#if 0
+static int
+cd9660_joliet_convert_filename(const char *oldname, char *newname, int is_file)
+{
+ /* TODO: implement later, move to cd9660_joliet.c ?? */
+}
+#endif
+
+
+/*
+ * Convert a file name to ISO compliant file name
+ * @param char * oldname The original filename
+ * @param char ** newname The new file name, in the appropriate character
+ * set and of appropriate length
+ * @param int 1 if file, 0 if directory
+ * @returns int The length of the new string
+ */
+static int
+cd9660_convert_filename(const char *oldname, char *newname, int is_file)
+{
+ assert(1 <= diskStructure.isoLevel && diskStructure.isoLevel <= 2);
+ /* NEW */
+ cd9660_filename_conversion_functor conversion_function = 0;
+ if (diskStructure.isoLevel == 1)
+ conversion_function = &cd9660_level1_convert_filename;
+ else if (diskStructure.isoLevel == 2)
+ conversion_function = &cd9660_level2_convert_filename;
+ return (*conversion_function)(oldname, newname, is_file);
+}
+
+int
+cd9660_compute_record_size(cd9660node *node)
+{
+ int size = node->isoDirRecord->length[0];
+
+ if (diskStructure.rock_ridge_enabled)
+ size += node->susp_entry_size;
+ size += node->su_tail_size;
+ size += size & 1; /* Ensure length of record is even. */
+ assert(size <= 254);
+ return size;
+}
+
+static void
+cd9660_populate_dot_records(cd9660node *node)
+{
+ node->dot_record->fileDataSector = node->fileDataSector;
+ memcpy(node->dot_record->isoDirRecord,node->isoDirRecord, 34);
+ node->dot_record->isoDirRecord->name_len[0] = 1;
+ node->dot_record->isoDirRecord->name[0] = 0;
+ node->dot_record->isoDirRecord->name[1] = 0;
+ node->dot_record->isoDirRecord->length[0] = 34;
+ node->dot_record->fileRecordSize =
+ cd9660_compute_record_size(node->dot_record);
+
+ if (node == diskStructure.rootNode) {
+ node->dot_dot_record->fileDataSector = node->fileDataSector;
+ memcpy(node->dot_dot_record->isoDirRecord,node->isoDirRecord,
+ 34);
+ } else {
+ node->dot_dot_record->fileDataSector =
+ node->parent->fileDataSector;
+ memcpy(node->dot_dot_record->isoDirRecord,
+ node->parent->isoDirRecord,34);
+ }
+ node->dot_dot_record->isoDirRecord->name_len[0] = 1;
+ node->dot_dot_record->isoDirRecord->name[0] = 1;
+ node->dot_dot_record->isoDirRecord->name[1] = 0;
+ node->dot_dot_record->isoDirRecord->length[0] = 34;
+ node->dot_dot_record->fileRecordSize =
+ cd9660_compute_record_size(node->dot_dot_record);
+}
+
+/*
+ * @param struct cd9660node *node The node
+ * @param int The offset (in bytes) - SHOULD align to the beginning of a sector
+ * @returns int The total size of files and directory entries (should be
+ * a multiple of sector size)
+*/
+static int64_t
+cd9660_compute_offsets(cd9660node *node, int64_t startOffset)
+{
+ /*
+ * This function needs to compute the size of directory records and
+ * runs, file lengths, and set the appropriate variables both in
+ * cd9660node and isoDirEntry
+ */
+ int64_t used_bytes = 0;
+ int64_t current_sector_usage = 0;
+ cd9660node *child;
+ fsinode *inode;
+ int64_t r;
+
+ assert(node != NULL);
+
+
+ /*
+ * NOTE : There needs to be some special case detection for
+ * the "real root" node, since for it, node->node is undefined
+ */
+
+ node->fileDataSector = -1;
+
+ if (node->type & CD9660_TYPE_DIR) {
+ node->fileRecordSize = cd9660_compute_record_size(node);
+ /*Set what sector this directory starts in*/
+ node->fileDataSector =
+ CD9660_BLOCKS(diskStructure.sectorSize,startOffset);
+
+ cd9660_bothendian_dword(node->fileDataSector,
+ node->isoDirRecord->extent);
+
+ /*
+ * First loop over children, need to know the size of
+ * their directory records
+ */
+ node->fileSectorsUsed = 1;
+ TAILQ_FOREACH(child, &node->cn_children, cn_next_child) {
+ node->fileDataLength +=
+ cd9660_compute_record_size(child);
+ if ((cd9660_compute_record_size(child) +
+ current_sector_usage) >=
+ diskStructure.sectorSize) {
+ current_sector_usage = 0;
+ node->fileSectorsUsed++;
+ }
+
+ current_sector_usage +=
+ cd9660_compute_record_size(child);
+ }
+
+ cd9660_bothendian_dword(node->fileSectorsUsed *
+ diskStructure.sectorSize,node->isoDirRecord->size);
+
+ /*
+ * This should point to the sector after the directory
+ * record (or, the first byte in that sector)
+ */
+ used_bytes += node->fileSectorsUsed * diskStructure.sectorSize;
+
+ for (child = TAILQ_NEXT(node->dot_dot_record, cn_next_child);
+ child != NULL; child = TAILQ_NEXT(child, cn_next_child)) {
+ /* Directories need recursive call */
+ if (S_ISDIR(child->node->type)) {
+ r = cd9660_compute_offsets(child,
+ used_bytes + startOffset);
+
+ if (r != -1)
+ used_bytes += r;
+ else
+ return -1;
+ }
+ }
+
+ /* Explicitly set the . and .. records */
+ cd9660_populate_dot_records(node);
+
+ /* Finally, do another iteration to write the file data*/
+ for (child = TAILQ_NEXT(node->dot_dot_record, cn_next_child);
+ child != NULL;
+ child = TAILQ_NEXT(child, cn_next_child)) {
+ /* Files need extent set */
+ if (S_ISDIR(child->node->type))
+ continue;
+ child->fileRecordSize =
+ cd9660_compute_record_size(child);
+
+ child->fileSectorsUsed =
+ CD9660_BLOCKS(diskStructure.sectorSize,
+ child->fileDataLength);
+
+ inode = child->node->inode;
+ if ((inode->flags & FI_ALLOCATED) == 0) {
+ inode->ino =
+ CD9660_BLOCKS(diskStructure.sectorSize,
+ used_bytes + startOffset);
+ inode->flags |= FI_ALLOCATED;
+ used_bytes += child->fileSectorsUsed *
+ diskStructure.sectorSize;
+ } else {
+ INODE_WARNX(("%s: already allocated inode %d "
+ "data sectors at %" PRIu32, __func__,
+ (int)inode->st.st_ino, inode->ino));
+ }
+ child->fileDataSector = inode->ino;
+ cd9660_bothendian_dword(child->fileDataSector,
+ child->isoDirRecord->extent);
+ }
+ }
+
+ return used_bytes;
+}
+
+#if 0
+/* Might get rid of this func */
+static int
+cd9660_copy_stat_info(cd9660node *from, cd9660node *to, int file)
+{
+ to->node->inode->st.st_dev = 0;
+ to->node->inode->st.st_ino = 0;
+ to->node->inode->st.st_size = 0;
+ to->node->inode->st.st_blksize = from->node->inode->st.st_blksize;
+ to->node->inode->st.st_atime = from->node->inode->st.st_atime;
+ to->node->inode->st.st_mtime = from->node->inode->st.st_mtime;
+ to->node->inode->st.st_ctime = from->node->inode->st.st_ctime;
+ to->node->inode->st.st_uid = from->node->inode->st.st_uid;
+ to->node->inode->st.st_gid = from->node->inode->st.st_gid;
+ to->node->inode->st.st_mode = from->node->inode->st.st_mode;
+ /* Clear out type */
+ to->node->inode->st.st_mode = to->node->inode->st.st_mode & ~(S_IFMT);
+ if (file)
+ to->node->inode->st.st_mode |= S_IFREG;
+ else
+ to->node->inode->st.st_mode |= S_IFDIR;
+ return 1;
+}
+#endif
+
+static cd9660node *
+cd9660_create_virtual_entry(const char *name, cd9660node *parent, int file,
+ int insert)
+{
+ cd9660node *temp;
+ fsnode * tfsnode;
+
+ assert(parent != NULL);
+
+ temp = cd9660_allocate_cd9660node();
+ if (temp == NULL)
+ return NULL;
+
+ if ((tfsnode = malloc(sizeof(fsnode))) == NULL) {
+ CD9660_MEM_ALLOC_ERROR("cd9660_create_virtual_entry");
+ return NULL;
+ }
+
+ /* Assume for now name is a valid length */
+ if ((tfsnode->name = malloc(strlen(name) + 1)) == NULL) {
+ CD9660_MEM_ALLOC_ERROR("cd9660_create_virtual_entry");
+ return NULL;
+ }
+
+ if ((temp->isoDirRecord =
+ malloc(sizeof(iso_directory_record_cd9660))) == NULL) {
+ CD9660_MEM_ALLOC_ERROR("cd9660_create_virtual_entry");
+ return NULL;
+ }
+
+ strcpy(tfsnode->name, name);
+
+ cd9660_convert_filename(tfsnode->name, temp->isoDirRecord->name, file);
+
+ temp->node = tfsnode;
+ temp->parent = parent;
+
+ if (insert) {
+ if (temp->parent != NULL) {
+ temp->level = temp->parent->level + 1;
+ if (!TAILQ_EMPTY(&temp->parent->cn_children))
+ cd9660_sorted_child_insert(temp->parent, temp);
+ else
+ TAILQ_INSERT_HEAD(&temp->parent->cn_children,
+ temp, cn_next_child);
+ }
+ }
+
+ if (parent->node != NULL) {
+ tfsnode->type = parent->node->type;
+ }
+
+ /* Clear out file type bits */
+ tfsnode->type &= ~(S_IFMT);
+ if (file)
+ tfsnode->type |= S_IFREG;
+ else
+ tfsnode->type |= S_IFDIR;
+
+ /* Indicate that there is no spec entry (inode) */
+ tfsnode->flags &= ~(FSNODE_F_HASSPEC);
+#if 0
+ cd9660_copy_stat_info(parent, temp, file);
+#endif
+ return temp;
+}
+
+static cd9660node *
+cd9660_create_file(const char * name, cd9660node *parent, cd9660node *me)
+{
+ cd9660node *temp;
+
+ temp = cd9660_create_virtual_entry(name,parent,1,1);
+ if (temp == NULL)
+ return NULL;
+
+ temp->fileDataLength = 0;
+
+ temp->type = CD9660_TYPE_FILE | CD9660_TYPE_VIRTUAL;
+
+ if ((temp->node->inode = calloc(1, sizeof(fsinode))) == NULL)
+ return NULL;
+ *temp->node->inode = *me->node->inode;
+
+ if (cd9660_translate_node_common(temp) == 0)
+ return NULL;
+ return temp;
+}
+
+/*
+ * Create a new directory which does not exist on disk
+ * @param const char * name The name to assign to the directory
+ * @param const char * parent Pointer to the parent directory
+ * @returns cd9660node * Pointer to the new directory
+ */
+static cd9660node *
+cd9660_create_directory(const char *name, cd9660node *parent, cd9660node *me)
+{
+ cd9660node *temp;
+
+ temp = cd9660_create_virtual_entry(name,parent,0,1);
+ if (temp == NULL)
+ return NULL;
+ temp->node->type |= S_IFDIR;
+
+ temp->type = CD9660_TYPE_DIR | CD9660_TYPE_VIRTUAL;
+
+ if ((temp->node->inode = calloc(1, sizeof(fsinode))) == NULL)
+ return NULL;
+ *temp->node->inode = *me->node->inode;
+
+ if (cd9660_translate_node_common(temp) == 0)
+ return NULL;
+ return temp;
+}
+
+static cd9660node *
+cd9660_create_special_directory(u_char type, cd9660node *parent)
+{
+ cd9660node *temp, *first;
+ char na[2];
+
+ assert(parent != NULL);
+
+ if (type == CD9660_TYPE_DOT)
+ na[0] = 0;
+ else if (type == CD9660_TYPE_DOTDOT)
+ na[0] = 1;
+ else
+ return 0;
+
+ na[1] = 0;
+ if ((temp = cd9660_create_virtual_entry(na, parent, 0, 0)) == NULL)
+ return NULL;
+
+ temp->parent = parent;
+ temp->type = type;
+ temp->isoDirRecord->length[0] = 34;
+ /* Dot record is always first */
+ if (type == CD9660_TYPE_DOT) {
+ parent->dot_record = temp;
+ TAILQ_INSERT_HEAD(&parent->cn_children, temp, cn_next_child);
+ /* DotDot should be second */
+ } else if (type == CD9660_TYPE_DOTDOT) {
+ parent->dot_dot_record = temp;
+ /*
+ * If the first child is the dot record, insert
+ * this second. Otherwise, insert it at the head.
+ */
+ if ((first = TAILQ_FIRST(&parent->cn_children)) == NULL ||
+ (first->type & CD9660_TYPE_DOT) == 0) {
+ TAILQ_INSERT_HEAD(&parent->cn_children, temp,
+ cn_next_child);
+ } else {
+ TAILQ_INSERT_AFTER(&parent->cn_children, first, temp,
+ cn_next_child);
+ }
+ }
+
+ return temp;
+}
+
+int
+cd9660_add_generic_bootimage(const char *bootimage)
+{
+ struct stat stbuf;
+
+ assert(bootimage != NULL);
+
+ if (*bootimage == '\0') {
+ warnx("Error: Boot image must be a filename");
+ return 0;
+ }
+
+ if ((diskStructure.generic_bootimage = strdup(bootimage)) == NULL) {
+ warn("%s: strdup", __func__);
+ return 0;
+ }
+
+ /* Get information about the file */
+ if (lstat(diskStructure.generic_bootimage, &stbuf) == -1)
+ err(EXIT_FAILURE, "%s: lstat(\"%s\")", __func__,
+ diskStructure.generic_bootimage);
+
+ if (stbuf.st_size > 32768) {
+ warnx("Error: Boot image must be no greater than 32768 bytes");
+ return 0;
+ }
+
+ if (diskStructure.verbose_level > 0) {
+ printf("Generic boot image image has size %lld\n",
+ (long long)stbuf.st_size);
+ }
+
+ diskStructure.has_generic_bootimage = 1;
+
+ return 1;
+}
diff --git a/usr.sbin/makefs/cd9660.h b/usr.sbin/makefs/cd9660.h
new file mode 100644
index 0000000..41125b7
--- /dev/null
+++ b/usr.sbin/makefs/cd9660.h
@@ -0,0 +1,364 @@
+/* $NetBSD: cd9660.h,v 1.17 2011/06/23 02:35:56 enami Exp $ */
+
+/*
+ * Copyright (c) 2005 Daniel Watt, Walter Deignan, Ryan Gabrys, Alan
+ * Perez-Rathke and Ram Vedam. All rights reserved.
+ *
+ * This code was written by Daniel Watt, Walter Deignan, Ryan Gabrys,
+ * Alan Perez-Rathke and Ram Vedam.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE,DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (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 _MAKEFS_CD9660_H
+#define _MAKEFS_CD9660_H
+
+#include <inttypes.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <limits.h>
+#include <sys/queue.h>
+#include <sys/param.h>
+#include <sys/endian.h>
+
+#include "makefs.h"
+#include "iso.h"
+#include "iso_rrip.h"
+#include "cd9660/cd9660_eltorito.h"
+
+#ifdef DEBUG
+#define INODE_WARNX(__x) warnx __x
+#else /* DEBUG */
+#define INODE_WARNX(__x)
+#endif /* DEBUG */
+
+#define CD9660MAXPATH 4096
+
+#define ISO_STRING_FILTER_NONE = 0x00
+#define ISO_STRING_FILTER_DCHARS = 0x01
+#define ISO_STRING_FILTER_ACHARS = 0x02
+
+/*
+Extended preferences type, in the spirit of what makefs gives us (only ints)
+*/
+typedef struct {
+ const char *shortName; /* Short option */
+ const char *name; /* option name */
+ char *value; /* where to stuff the value */
+ int minLength; /* minimum for value */
+ int maxLength; /* maximum for value */
+ const char *desc; /* option description */
+ int filterFlags;
+} string_option_t;
+
+/******** STRUCTURES **********/
+
+/*Defaults*/
+#define ISO_DEFAULT_VOLUMEID "MAKEFS_CD9660_IMAGE"
+#define ISO_DEFAULT_APPID "MAKEFS"
+#define ISO_DEFAULT_PUBLISHER "MAKEFS"
+#define ISO_DEFAULT_PREPARER "MAKEFS"
+
+#define ISO_VOLUME_DESCRIPTOR_STANDARD_ID "CD001"
+#define ISO_VOLUME_DESCRIPTOR_BOOT 0
+#define ISO_VOLUME_DESCRIPTOR_PVD 1
+#define ISO_VOLUME_DESCRIPTOR_TERMINATOR 255
+
+/*30 for name and extension, as well as version number and padding bit*/
+#define ISO_FILENAME_MAXLENGTH_BEFORE_VERSION 30
+#define ISO_FILENAME_MAXLENGTH 36
+#define ISO_FILENAME_MAXLENGTH_WITH_PADDING 37
+
+#define ISO_FLAG_CLEAR 0x00
+#define ISO_FLAG_HIDDEN 0x01
+#define ISO_FLAG_DIRECTORY 0x02
+#define ISO_FLAG_ASSOCIATED 0x04
+#define ISO_FLAG_PERMISSIONS 0x08
+#define ISO_FLAG_RESERVED5 0x10
+#define ISO_FLAG_RESERVED6 0x20
+#define ISO_FLAG_FINAL_RECORD 0x40
+
+#define ISO_PATHTABLE_ENTRY_BASESIZE 8
+
+#define ISO_RRIP_DEFAULT_MOVE_DIR_NAME "RR_MOVED"
+#define RRIP_DEFAULT_MOVE_DIR_NAME ".rr_moved"
+
+#define CD9660_BLOCKS(__sector_size, __bytes) \
+ howmany((__bytes), (__sector_size))
+
+#define CD9660_MEM_ALLOC_ERROR(_F) \
+ err(EXIT_FAILURE, "%s, %s l. %d", _F, __FILE__, __LINE__)
+
+#define CD9660_IS_COMMAND_ARG_DUAL(var,short,long)\
+ (strcmp((var),(short)) == 0) || (strcmp((var),(long))==0)
+
+#define CD9660_IS_COMMAND_ARG(var,arg)\
+ (strcmp((var),(arg)) == 0)
+
+#define CD9660_TYPE_FILE 0x01
+#define CD9660_TYPE_DIR 0x02
+#define CD9660_TYPE_DOT 0x04
+#define CD9660_TYPE_DOTDOT 0x08
+#define CD9660_TYPE_VIRTUAL 0x80
+
+#define CD9660_INODE_HASH_SIZE 1024
+#define CD9660_SECTOR_SIZE 2048
+
+#define CD9660_END_PADDING 150
+
+/* Slight modification of the ISO structure in iso.h */
+typedef struct _iso_directory_record_cd9660 {
+ u_char length [ISODCL (1, 1)]; /* 711 */
+ u_char ext_attr_length [ISODCL (2, 2)]; /* 711 */
+ u_char extent [ISODCL (3, 10)]; /* 733 */
+ u_char size [ISODCL (11, 18)]; /* 733 */
+ u_char date [ISODCL (19, 25)]; /* 7 by 711 */
+ u_char flags [ISODCL (26, 26)];
+ u_char file_unit_size [ISODCL (27, 27)]; /* 711 */
+ u_char interleave [ISODCL (28, 28)]; /* 711 */
+ u_char volume_sequence_number [ISODCL (29, 32)]; /* 723 */
+ u_char name_len [ISODCL (33, 33)]; /* 711 */
+ char name [ISO_FILENAME_MAXLENGTH_WITH_PADDING];
+} iso_directory_record_cd9660;
+
+/* TODO: Lots of optimization of this structure */
+typedef struct _cd9660node {
+ u_char type;/* Used internally */
+ /* Tree structure */
+ struct _cd9660node *parent; /* parent (NULL if root) */
+ TAILQ_HEAD(cd9660_children_head, _cd9660node) cn_children;
+ TAILQ_ENTRY(_cd9660node) cn_next_child;
+
+ struct _cd9660node *dot_record; /* For directories, used mainly in RRIP */
+ struct _cd9660node *dot_dot_record;
+
+ fsnode *node; /* pointer to fsnode */
+ struct _iso_directory_record_cd9660 *isoDirRecord;
+ struct iso_extended_attributes *isoExtAttributes;
+
+ /***** SIZE CALCULATION *****/
+ /*already stored in isoDirRecord, but this is an int version, and will be
+ copied to isoDirRecord on writing*/
+ uint32_t fileDataSector;
+
+ /*
+ * same thing, though some notes:
+ * If a file, this is the file size
+ * If a directory, this is the size of all its children's
+ * directory records
+ * plus necessary padding
+ */
+ int64_t fileDataLength;
+
+ int64_t fileSectorsUsed;
+ int fileRecordSize;/*copy of a variable, int for quicker calculations*/
+
+ /* Old name, used for renaming - needs to be optimized but low priority */
+ char o_name [ISO_FILENAME_MAXLENGTH_WITH_PADDING];
+
+ /***** SPACE RESERVED FOR EXTENSIONS *****/
+ /* For memory efficiency's sake - we should move this to a separate struct
+ and point to null if not needed */
+ /* For Rock Ridge */
+ struct _cd9660node *rr_real_parent, *rr_relocated;
+
+ int64_t susp_entry_size;
+ int64_t susp_dot_entry_size;
+ int64_t susp_dot_dot_entry_size;
+
+ /* Continuation area stuff */
+ int64_t susp_entry_ce_start;
+ int64_t susp_dot_ce_start;
+ int64_t susp_dot_dot_ce_start;
+
+ int64_t susp_entry_ce_length;
+ int64_t susp_dot_ce_length;
+ int64_t susp_dot_dot_ce_length;
+
+ /* Data to put at the end of the System Use field */
+ int64_t su_tail_size;
+ char *su_tail_data;
+
+ /*** PATH TABLE STUFF ***/
+ int level; /*depth*/
+ int ptnumber;
+ struct _cd9660node *ptnext, *ptprev, *ptlast;
+
+ /* SUSP entries */
+ TAILQ_HEAD(susp_linked_list, ISO_SUSP_ATTRIBUTES) head;
+} cd9660node;
+
+typedef struct _path_table_entry
+{
+ u_char length[ISODCL (1, 1)];
+ u_char extended_attribute_length[ISODCL (2, 2)];
+ u_char first_sector[ISODCL (3, 6)];
+ u_char parent_number[ISODCL (7, 8)];
+ u_char name[ISO_FILENAME_MAXLENGTH_WITH_PADDING];
+} path_table_entry;
+
+typedef struct _volume_descriptor
+{
+ u_char *volumeDescriptorData; /*ALWAYS 2048 bytes long*/
+ int64_t sector;
+ struct _volume_descriptor *next;
+} volume_descriptor;
+
+typedef struct _iso9660_disk {
+ int sectorSize;
+ struct iso_primary_descriptor primaryDescriptor;
+ struct iso_supplementary_descriptor supplementaryDescriptor;
+
+ volume_descriptor *firstVolumeDescriptor;
+
+ cd9660node *rootNode;
+
+ /* Important sector numbers here */
+ /* primaryDescriptor.type_l_path_table*/
+ int64_t primaryBigEndianTableSector;
+
+ /* primaryDescriptor.type_m_path_table*/
+ int64_t primaryLittleEndianTableSector;
+
+ /* primaryDescriptor.opt_type_l_path_table*/
+ int64_t secondaryBigEndianTableSector;
+
+ /* primaryDescriptor.opt_type_m_path_table*/
+ int64_t secondaryLittleEndianTableSector;
+
+ /* primaryDescriptor.path_table_size*/
+ int pathTableLength;
+ int64_t dataFirstSector;
+
+ int64_t totalSectors;
+ /* OPTIONS GO HERE */
+ int isoLevel;
+
+ int include_padding_areas;
+
+ int follow_sym_links;
+ int verbose_level;
+ int displayHelp;
+ int keep_bad_images;
+
+ /* SUSP options and variables */
+ int64_t susp_continuation_area_start_sector;
+ int64_t susp_continuation_area_size;
+ int64_t susp_continuation_area_current_free;
+
+ int rock_ridge_enabled;
+ /* Other Rock Ridge Variables */
+ char *rock_ridge_renamed_dir_name;
+ int rock_ridge_move_count;
+ cd9660node *rr_moved_dir;
+
+ int archimedes_enabled;
+ int chrp_boot;
+
+ /* Spec breaking options */
+ u_char allow_deep_trees;
+ u_char allow_start_dot;
+ u_char allow_max_name; /* Allow 37 char filenames*/
+ u_char allow_illegal_chars; /* ~, !, # */
+ u_char allow_lowercase;
+ u_char allow_multidot;
+ u_char omit_trailing_period;
+
+ /* BOOT INFORMATION HERE */
+ int has_generic_bootimage; /* Default to 0 */
+ char *generic_bootimage;
+
+ int is_bootable;/* Default to 0 */
+ int64_t boot_catalog_sector;
+ boot_volume_descriptor *boot_descriptor;
+ char * boot_image_directory;
+
+ TAILQ_HEAD(boot_image_list,cd9660_boot_image) boot_images;
+ int image_serialno;
+ LIST_HEAD(boot_catalog_entries,boot_catalog_entry) boot_entries;
+
+} iso9660_disk;
+
+/******** GLOBAL VARIABLES ***********/
+extern iso9660_disk diskStructure;
+
+/************ FUNCTIONS **************/
+int cd9660_valid_a_chars(const char *);
+int cd9660_valid_d_chars(const char *);
+void cd9660_uppercase_characters(char *, int);
+
+/* ISO Data Types */
+void cd9660_721(uint16_t, unsigned char *);
+void cd9660_731(uint32_t, unsigned char *);
+void cd9660_722(uint16_t, unsigned char *);
+void cd9660_732(uint32_t, unsigned char *);
+void cd9660_bothendian_dword(uint32_t dw, unsigned char *);
+void cd9660_bothendian_word(uint16_t dw, unsigned char *);
+void cd9660_set_date(char *, time_t);
+void cd9660_time_8426(unsigned char *, time_t);
+void cd9660_time_915(unsigned char *, time_t);
+
+/*** Boot Functions ***/
+int cd9660_write_generic_bootimage(FILE *);
+int cd9660_add_generic_bootimage(const char *);
+int cd9660_write_boot(FILE *);
+int cd9660_add_boot_disk(const char *);
+int cd9660_eltorito_add_boot_option(const char *, const char *);
+int cd9660_setup_boot(int);
+int cd9660_setup_boot_volume_descriptor(volume_descriptor *);
+
+
+/*** Write Functions ***/
+int cd9660_write_image(const char *image);
+int cd9660_copy_file(FILE *, off_t, const char *);
+
+void cd9660_compute_full_filename(cd9660node *, char *);
+int cd9660_compute_record_size(cd9660node *);
+
+/* Debugging functions */
+void debug_print_tree(cd9660node *,int);
+void debug_print_path_tree(cd9660node *);
+void debug_print_volume_descriptor_information(void);
+void debug_dump_to_xml_ptentry(path_table_entry *,int, int);
+void debug_dump_to_xml_path_table(FILE *, off_t, int, int);
+void debug_dump_to_xml(FILE *);
+int debug_get_encoded_number(unsigned char *, int);
+void debug_dump_integer(const char *, char *,int);
+void debug_dump_string(const char *,unsigned char *,int);
+void debug_dump_directory_record_9_1(unsigned char *);
+void debug_dump_to_xml_volume_descriptor(unsigned char *,int);
+
+void cd9660_pad_string_spaces(char *, int);
+
+#endif
diff --git a/usr.sbin/makefs/cd9660/Makefile.inc b/usr.sbin/makefs/cd9660/Makefile.inc
new file mode 100644
index 0000000..1455fcd
--- /dev/null
+++ b/usr.sbin/makefs/cd9660/Makefile.inc
@@ -0,0 +1,9 @@
+# $FreeBSD$
+#
+
+.PATH: ${.CURDIR}/cd9660 ${.CURDIR}/../../sys/fs/cd9660/
+
+CFLAGS+=-I${.CURDIR}/../../sys/fs/cd9660/
+
+SRCS+= cd9660_strings.c cd9660_debug.c cd9660_eltorito.c \
+ cd9660_write.c cd9660_conversion.c iso9660_rrip.c cd9660_archimedes.c
diff --git a/usr.sbin/makefs/cd9660/cd9660_archimedes.c b/usr.sbin/makefs/cd9660/cd9660_archimedes.c
new file mode 100644
index 0000000..d18a0f7
--- /dev/null
+++ b/usr.sbin/makefs/cd9660/cd9660_archimedes.c
@@ -0,0 +1,126 @@
+/* $NetBSD: cd9660_archimedes.c,v 1.1 2009/01/10 22:06:29 bjh21 Exp $ */
+
+/*-
+ * Copyright (c) 1998, 2009 Ben Harris
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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.
+ */
+/*
+ * cd9660_archimedes.c - support for RISC OS "ARCHIMEDES" extension
+ *
+ * RISC OS CDFS looks for a special block at the end of the System Use
+ * Field for each file. If present, this contains the RISC OS load
+ * and exec address (used to hold the file timestamp and type), the
+ * file attributes, and a flag indicating whether the first character
+ * of the filename should be replaced with '!' (since many special
+ * RISC OS filenames do).
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "makefs.h"
+#include "cd9660.h"
+#include "cd9660_archimedes.h"
+
+/*
+ * Convert a Unix time_t (non-leap seconds since 1970-01-01) to a RISC
+ * OS time (non-leap(?) centiseconds since 1900-01-01(?)).
+ */
+
+static u_int64_t
+riscos_date(time_t unixtime)
+{
+ u_int64_t base;
+
+ base = 31536000ULL * 70 + 86400 * 17;
+ return (((u_int64_t)unixtime) + base)*100;
+}
+
+/*
+ * Add "ARCHIMEDES" metadata to a node if that seems appropriate.
+ *
+ * We touch regular files with names matching /,[0-9a-f]{3}$/ and
+ * directories matching /^!/.
+ */
+static void
+archimedes_convert_node(cd9660node *node)
+{
+ struct ISO_ARCHIMEDES *arc;
+ size_t len;
+ int type = -1;
+ uint64_t stamp;
+
+ if (node->su_tail_data != NULL)
+ /* Something else already has the tail. */
+ return;
+
+ len = strlen(node->node->name);
+ if (len < 1) return;
+
+ if (len >= 4 && node->node->name[len-4] == ',')
+ /* XXX should support ,xxx and ,lxa */
+ type = strtoul(node->node->name + len - 3, NULL, 16);
+ if (type == -1 && node->node->name[0] != '!')
+ return;
+ if (type == -1) type = 0;
+
+ assert(sizeof(struct ISO_ARCHIMEDES) == 32);
+ if ((arc = calloc(1, sizeof(struct ISO_ARCHIMEDES))) == NULL) {
+ CD9660_MEM_ALLOC_ERROR("archimedes_convert_node");
+ exit(1);
+ }
+
+ stamp = riscos_date(node->node->inode->st.st_mtime);
+
+ memcpy(arc->magic, "ARCHIMEDES", 10);
+ cd9660_731(0xfff00000 | (type << 8) | (stamp >> 32), arc->loadaddr);
+ cd9660_731(stamp & 0x00ffffffffULL, arc->execaddr);
+ arc->ro_attr = RO_ACCESS_UR | RO_ACCESS_OR;
+ arc->cdfs_attr = node->node->name[0] == '!' ? CDFS_PLING : 0;
+ node->su_tail_data = (void *)arc;
+ node->su_tail_size = sizeof(*arc);
+}
+
+/*
+ * Add "ARCHIMEDES" metadata to an entire tree recursively.
+ */
+void
+archimedes_convert_tree(cd9660node *node)
+{
+ cd9660node *cn;
+
+ assert(node != NULL);
+
+ archimedes_convert_node(node);
+
+ /* Recurse on children. */
+ TAILQ_FOREACH(cn, &node->cn_children, cn_next_child)
+ archimedes_convert_tree(cn);
+}
diff --git a/usr.sbin/makefs/cd9660/cd9660_archimedes.h b/usr.sbin/makefs/cd9660/cd9660_archimedes.h
new file mode 100644
index 0000000..2717bf4
--- /dev/null
+++ b/usr.sbin/makefs/cd9660/cd9660_archimedes.h
@@ -0,0 +1,50 @@
+/* $NetBSD: cd9660_archimedes.h,v 1.1 2009/01/10 22:06:29 bjh21 Exp $ */
+
+/*-
+ * Copyright (c) 1998, 2009 Ben Harris
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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.
+ */
+/*
+ * cd9660_archimedes.c - support for RISC OS "ARCHIMEDES" extension
+ *
+ * $FreeBSD$
+ */
+
+struct ISO_ARCHIMEDES {
+ char magic[10]; /* "ARCHIMEDES" */
+ unsigned char loadaddr[4]; /* Load address, little-endian */
+ unsigned char execaddr[4]; /* Exec address, little-endian */
+ unsigned char ro_attr; /* RISC OS attributes */
+#define RO_ACCESS_UR 0x01 /* Owner read */
+#define RO_ACCESS_UW 0x02 /* Owner write */
+#define RO_ACCESS_L 0x04 /* Locked */
+#define RO_ACCESS_OR 0x10 /* Public read */
+#define RO_ACCESS_OW 0x20 /* Public write */
+ unsigned char cdfs_attr; /* Extra attributes for CDFS */
+#define CDFS_PLING 0x01 /* Filename begins with '!' */
+ char reserved[12];
+};
+
+extern void archimedes_convert_tree(cd9660node *);
diff --git a/usr.sbin/makefs/cd9660/cd9660_conversion.c b/usr.sbin/makefs/cd9660/cd9660_conversion.c
new file mode 100644
index 0000000..43fc7c5
--- /dev/null
+++ b/usr.sbin/makefs/cd9660/cd9660_conversion.c
@@ -0,0 +1,200 @@
+/* $NetBSD: cd9660_conversion.c,v 1.4 2007/03/14 14:11:17 christos Exp $ */
+
+/*
+ * Copyright (c) 2005 Daniel Watt, Walter Deignan, Ryan Gabrys, Alan
+ * Perez-Rathke and Ram Vedam. All rights reserved.
+ *
+ * This code was written by Daniel Watt, Walter Deignan, Ryan Gabrys,
+ * Alan Perez-Rathke and Ram Vedam.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE,DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (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 "cd9660.h"
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+static char cd9660_compute_gm_offset(time_t);
+
+#if 0
+static inline int
+cd9660_pad_even(length)
+int length;
+{
+ return length + (length & 0x01);
+}
+#endif
+
+/*
+* These can probably be implemented using a macro
+*/
+
+/* Little endian */
+void
+cd9660_721(uint16_t w, unsigned char *twochar)
+{
+#if BYTE_ORDER == BIG_ENDIAN
+ w = bswap16(w);
+#endif
+ memcpy(twochar,&w,2);
+}
+
+void
+cd9660_731(uint32_t w, unsigned char *fourchar)
+{
+#if BYTE_ORDER == BIG_ENDIAN
+ w = bswap32(w);
+#endif
+ memcpy(fourchar,&w,4);
+}
+
+/* Big endian */
+void
+cd9660_722(uint16_t w, unsigned char *twochar)
+{
+#if BYTE_ORDER == LITTLE_ENDIAN
+ w = bswap16(w);
+#endif
+ memcpy(twochar,&w,2);
+}
+
+void
+cd9660_732(uint32_t w, unsigned char *fourchar)
+{
+#if BYTE_ORDER == LITTLE_ENDIAN
+ w = bswap32(w);
+#endif
+ memcpy(fourchar,&w,4);
+}
+
+/**
+* Convert a dword into a double endian string of eight characters
+* @param int The double word to convert
+* @param char* The string to write the both endian double word to - It is assumed this is allocated and at least
+* eight characters long
+*/
+void
+cd9660_bothendian_dword(uint32_t dw, unsigned char *eightchar)
+{
+ uint32_t le, be;
+#if BYTE_ORDER == LITTLE_ENDIAN
+ le = dw;
+ be = bswap32(dw);
+#endif
+#if BYTE_ORDER == BIG_ENDIAN
+ be = dw;
+ le = bswap32(dw);
+#endif
+ memcpy(eightchar, &le, 4);
+ memcpy((eightchar+4), &be, 4);
+}
+
+/**
+* Convert a word into a double endian string of four characters
+* @param int The word to convert
+* @param char* The string to write the both endian word to - It is assumed this is allocated and at least
+* four characters long
+*/
+void
+cd9660_bothendian_word(uint16_t dw, unsigned char *fourchar)
+{
+ uint16_t le, be;
+#if BYTE_ORDER == LITTLE_ENDIAN
+ le = dw;
+ be = bswap16(dw);
+#endif
+#if BYTE_ORDER == BIG_ENDIAN
+ be = dw;
+ le = bswap16(dw);
+#endif
+ memcpy(fourchar, &le, 2);
+ memcpy((fourchar+2), &be, 2);
+}
+
+void
+cd9660_pad_string_spaces(char *str, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i ++) {
+ if (str[i] == '\0')
+ str[i] = 0x20;
+ }
+}
+
+static char
+cd9660_compute_gm_offset(time_t tim)
+{
+ struct tm t, gm;
+
+ (void)localtime_r(&tim, &t);
+ (void)gmtime_r(&tim, &gm);
+ gm.tm_year -= t.tm_year;
+ gm.tm_yday -= t.tm_yday;
+ gm.tm_hour -= t.tm_hour;
+ gm.tm_min -= t.tm_min;
+ if (gm.tm_year < 0)
+ gm.tm_yday = -1;
+ else if (gm.tm_year > 0)
+ gm.tm_yday = 1;
+
+ return (char)(-(gm.tm_min + 60* (24 * gm.tm_yday + gm.tm_hour)) / 15);
+}
+
+/* Long dates: 17 characters */
+void
+cd9660_time_8426(unsigned char *buf, time_t tim)
+{
+ struct tm t;
+ char temp[18];
+
+ (void)localtime_r(&tim, &t);
+ (void)snprintf(temp, sizeof(temp), "%04i%02i%02i%02i%02i%02i%02i",
+ 1900+(int)t.tm_year,
+ (int)t.tm_mon+1,
+ (int)t.tm_mday,
+ (int)t.tm_hour,
+ (int)t.tm_min,
+ (int)t.tm_sec,
+ 0);
+ (void)memcpy(buf, temp, 16);
+ buf[16] = cd9660_compute_gm_offset(tim);
+}
+
+/* Short dates: 7 characters */
+void
+cd9660_time_915(unsigned char *buf, time_t tim)
+{
+ struct tm t;
+
+ (void)localtime_r(&tim, &t);
+ buf[0] = t.tm_year;
+ buf[1] = t.tm_mon+1;
+ buf[2] = t.tm_mday;
+ buf[3] = t.tm_hour;
+ buf[4] = t.tm_min;
+ buf[5] = t.tm_sec;
+ buf[6] = cd9660_compute_gm_offset(tim);
+}
diff --git a/usr.sbin/makefs/cd9660/cd9660_debug.c b/usr.sbin/makefs/cd9660/cd9660_debug.c
new file mode 100644
index 0000000..d94ccbe
--- /dev/null
+++ b/usr.sbin/makefs/cd9660/cd9660_debug.c
@@ -0,0 +1,488 @@
+/* $NetBSD: cd9660_debug.c,v 1.11 2010/10/27 18:51:35 christos Exp $ */
+
+/*
+ * Copyright (c) 2005 Daniel Watt, Walter Deignan, Ryan Gabrys, Alan
+ * Perez-Rathke and Ram Vedam. All rights reserved.
+ *
+ * This code was written by Daniel Watt, Walter Deignan, Ryan Gabrys,
+ * Alan Perez-Rathke and Ram Vedam.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE,DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (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 "makefs.h"
+#include "cd9660.h"
+#include "iso9660_rrip.h"
+
+static void debug_print_susp_attrs(cd9660node *, int);
+static void debug_dump_to_xml_padded_hex_output(const char *, unsigned char *,
+ int);
+
+static inline void
+print_n_tabs(int n)
+{
+ int i;
+
+ for (i = 1; i <= n; i ++)
+ printf("\t");
+}
+
+#if 0
+void
+debug_print_rrip_info(n)
+cd9660node *n;
+{
+ struct ISO_SUSP_ATTRIBUTES *t;
+ TAILQ_FOREACH(t, &node->head, rr_ll) {
+
+ }
+}
+#endif
+
+static void
+debug_print_susp_attrs(cd9660node *n, int indent)
+{
+ struct ISO_SUSP_ATTRIBUTES *t;
+
+ TAILQ_FOREACH(t, &n->head, rr_ll) {
+ print_n_tabs(indent);
+ printf("-");
+ printf("%c%c: L:%i",t->attr.su_entry.SP.h.type[0],
+ t->attr.su_entry.SP.h.type[1],
+ (int)t->attr.su_entry.SP.h.length[0]);
+ printf("\n");
+ }
+}
+
+void
+debug_print_tree(cd9660node *node, int level)
+{
+#if !HAVE_NBTOOL_CONFIG_H
+ cd9660node *cn;
+
+ print_n_tabs(level);
+ if (node->type & CD9660_TYPE_DOT) {
+ printf(". (%i)\n",
+ isonum_733(node->isoDirRecord->extent));
+ } else if (node->type & CD9660_TYPE_DOTDOT) {
+ printf("..(%i)\n",
+ isonum_733(node->isoDirRecord->extent));
+ } else if (node->isoDirRecord->name[0]=='\0') {
+ printf("(ROOT) (%" PRIu32 " to %" PRId64 ")\n",
+ node->fileDataSector,
+ node->fileDataSector +
+ node->fileSectorsUsed - 1);
+ } else {
+ printf("%s (%s) (%" PRIu32 " to %" PRId64 ")\n",
+ node->isoDirRecord->name,
+ (node->isoDirRecord->flags[0]
+ & ISO_FLAG_DIRECTORY) ? "DIR" : "FILE",
+ node->fileDataSector,
+ (node->fileSectorsUsed == 0) ?
+ node->fileDataSector :
+ node->fileDataSector
+ + node->fileSectorsUsed - 1);
+ }
+ if (diskStructure.rock_ridge_enabled)
+ debug_print_susp_attrs(node, level + 1);
+ TAILQ_FOREACH(cn, &node->cn_children, cn_next_child)
+ debug_print_tree(cn, level + 1);
+#else
+ printf("Sorry, debugging is not supported in host-tools mode.\n");
+#endif
+}
+
+void
+debug_print_path_tree(cd9660node *n)
+{
+ cd9660node *iterator = n;
+
+ /* Only display this message when called with the root node */
+ if (n->parent == NULL)
+ printf("debug_print_path_table: Dumping path table contents\n");
+
+ while (iterator != NULL) {
+ if (iterator->isoDirRecord->name[0] == '\0')
+ printf("0) (ROOT)\n");
+ else
+ printf("%i) %s\n", iterator->level,
+ iterator->isoDirRecord->name);
+
+ iterator = iterator->ptnext;
+ }
+}
+
+void
+debug_print_volume_descriptor_information(void)
+{
+ volume_descriptor *tmp = diskStructure.firstVolumeDescriptor;
+ char temp[CD9660_SECTOR_SIZE];
+
+ printf("==Listing Volume Descriptors==\n");
+
+ while (tmp != NULL) {
+ memset(temp, 0, CD9660_SECTOR_SIZE);
+ memcpy(temp, tmp->volumeDescriptorData + 1, 5);
+ printf("Volume descriptor in sector %" PRId64
+ ": type %i, ID %s\n",
+ tmp->sector, tmp->volumeDescriptorData[0], temp);
+ switch(tmp->volumeDescriptorData[0]) {
+ case 0:/*boot record*/
+ break;
+
+ case 1: /* PVD */
+ break;
+
+ case 2: /* SVD */
+ break;
+
+ case 3: /* Volume Partition Descriptor */
+ break;
+
+ case 255: /* terminator */
+ break;
+ }
+ tmp = tmp->next;
+ }
+
+ printf("==Done Listing Volume Descriptors==\n");
+}
+
+void
+debug_dump_to_xml_ptentry(path_table_entry *pttemp, int num, int mode)
+{
+ printf("<ptentry num=\"%i\">\n" ,num);
+ printf("<length>%i</length>\n", pttemp->length[0]);
+ printf("<extended_attribute_length>%i</extended_attribute_length>\n",
+ pttemp->extended_attribute_length[0]);
+ printf("<parent_number>%i</parent_number>\n",
+ debug_get_encoded_number(pttemp->parent_number,mode));
+ debug_dump_to_xml_padded_hex_output("name",
+ pttemp->name, pttemp->length[0]);
+ printf("</ptentry>\n");
+}
+
+void
+debug_dump_to_xml_path_table(FILE *fd, off_t sector, int size, int mode)
+{
+ path_table_entry pttemp;
+ int t = 0;
+ int n = 0;
+
+ if (fseeko(fd, CD9660_SECTOR_SIZE * sector, SEEK_SET) == -1)
+ err(1, "fseeko");
+
+ while (t < size) {
+ /* Read fixed data first */
+ fread(&pttemp, 1, 8, fd);
+ t += 8;
+ /* Read variable */
+ fread(((unsigned char*)&pttemp) + 8, 1, pttemp.length[0], fd);
+ t += pttemp.length[0];
+ debug_dump_to_xml_ptentry(&pttemp, n, mode);
+ n++;
+ }
+
+}
+
+/*
+ * XML Debug output functions
+ * Dump hierarchy of CD, as well as volume info, to XML
+ * Can be used later to diff against a standard,
+ * or just provide easy to read detailed debug output
+ */
+void
+debug_dump_to_xml(FILE *fd)
+{
+ unsigned char buf[CD9660_SECTOR_SIZE];
+ off_t sector;
+ int t, t2;
+ struct iso_primary_descriptor primaryVD;
+ struct _boot_volume_descriptor bootVD;
+
+ printf("<cd9660dump>\n");
+
+ /* Display Volume Descriptors */
+ sector = 16;
+ do {
+ if (fseeko(fd, CD9660_SECTOR_SIZE * sector, SEEK_SET) == -1)
+ err(1, "fseeko");
+ fread(buf, 1, CD9660_SECTOR_SIZE, fd);
+ t = (int)((unsigned char)buf[0]);
+ switch (t) {
+ case 0:
+ memcpy(&bootVD, buf, CD9660_SECTOR_SIZE);
+ break;
+ case 1:
+ memcpy(&primaryVD, buf, CD9660_SECTOR_SIZE);
+ break;
+ }
+ debug_dump_to_xml_volume_descriptor(buf, sector);
+ sector++;
+ } while (t != 255);
+
+ t = debug_get_encoded_number((u_char *)primaryVD.type_l_path_table,
+ 731);
+ t2 = debug_get_encoded_number((u_char *)primaryVD.path_table_size, 733);
+ printf("Path table 1 located at sector %i and is %i bytes long\n",
+ t,t2);
+ debug_dump_to_xml_path_table(fd, t, t2, 721);
+
+ t = debug_get_encoded_number((u_char *)primaryVD.type_m_path_table,
+ 731);
+ debug_dump_to_xml_path_table(fd, t, t2, 722);
+
+ printf("</cd9660dump>\n");
+}
+
+static void
+debug_dump_to_xml_padded_hex_output(const char *element, unsigned char *buf,
+ int len)
+{
+ int i;
+ int t;
+
+ printf("<%s>",element);
+ for (i = 0; i < len; i++) {
+ t = (unsigned char)buf[i];
+ if (t >= 32 && t < 127)
+ printf("%c",t);
+ }
+ printf("</%s>\n",element);
+
+ printf("<%s:hex>",element);
+ for (i = 0; i < len; i++) {
+ t = (unsigned char)buf[i];
+ printf(" %x",t);
+ }
+ printf("</%s:hex>\n",element);
+}
+
+int
+debug_get_encoded_number(unsigned char* buf, int mode)
+{
+#if !HAVE_NBTOOL_CONFIG_H
+ switch (mode) {
+ /* 711: Single bite */
+ case 711:
+ return isonum_711(buf);
+
+ /* 712: Single signed byte */
+ case 712:
+ return isonum_712((signed char *)buf);
+
+ /* 721: 16 bit LE */
+ case 721:
+ return isonum_721(buf);
+
+ /* 731: 32 bit LE */
+ case 731:
+ return isonum_731(buf);
+
+ /* 722: 16 bit BE */
+ case 722:
+ return isonum_722(buf);
+
+ /* 732: 32 bit BE */
+ case 732:
+ return isonum_732(buf);
+
+ /* 723: 16 bit bothE */
+ case 723:
+ return isonum_723(buf);
+
+ /* 733: 32 bit bothE */
+ case 733:
+ return isonum_733(buf);
+ }
+#endif
+ return 0;
+}
+
+void
+debug_dump_integer(const char *element, char* buf, int mode)
+{
+ printf("<%s>%i</%s>\n", element,
+ debug_get_encoded_number((unsigned char *)buf, mode), element);
+}
+
+void
+debug_dump_string(const char *element __unused, unsigned char *buf __unused, int len __unused)
+{
+
+}
+
+void
+debug_dump_directory_record_9_1(unsigned char* buf)
+{
+ printf("<directoryrecord>\n");
+ debug_dump_integer("length",
+ ((struct iso_directory_record*) buf)->length, 711);
+ debug_dump_integer("ext_attr_length",
+ ((struct iso_directory_record*) buf)->ext_attr_length,711);
+ debug_dump_integer("extent",
+ (char *)((struct iso_directory_record*) buf)->extent, 733);
+ debug_dump_integer("size",
+ (char *)((struct iso_directory_record*) buf)->size, 733);
+ debug_dump_integer("flags",
+ ((struct iso_directory_record*) buf)->flags, 711);
+ debug_dump_integer("file_unit_size",
+ ((struct iso_directory_record*) buf)->file_unit_size,711);
+ debug_dump_integer("interleave",
+ ((struct iso_directory_record*) buf)->interleave, 711);
+ debug_dump_integer("volume_sequence_number",
+ ((struct iso_directory_record*) buf)->volume_sequence_number,
+ 723);
+ debug_dump_integer("name_len",
+ ((struct iso_directory_record*) buf)->name_len, 711);
+ debug_dump_to_xml_padded_hex_output("name",
+ (u_char *)((struct iso_directory_record*) buf)->name,
+ debug_get_encoded_number((u_char *)
+ ((struct iso_directory_record*) buf)->length, 711));
+ printf("</directoryrecord>\n");
+}
+
+
+void
+debug_dump_to_xml_volume_descriptor(unsigned char* buf, int sector)
+{
+ printf("<volumedescriptor sector=\"%i\">\n", sector);
+ printf("<vdtype>");
+ switch(buf[0]) {
+ case 0:
+ printf("boot");
+ break;
+
+ case 1:
+ printf("primary");
+ break;
+
+ case 2:
+ printf("supplementary");
+ break;
+
+ case 3:
+ printf("volume partition descriptor");
+ break;
+
+ case 255:
+ printf("terminator");
+ break;
+ }
+
+ printf("</vdtype>\n");
+ switch(buf[0]) {
+ case 1:
+ debug_dump_integer("type",
+ ((struct iso_primary_descriptor*)buf)->type, 711);
+ debug_dump_to_xml_padded_hex_output("id",
+ (u_char *)((struct iso_primary_descriptor*) buf)->id,
+ ISODCL ( 2, 6));
+ debug_dump_integer("version",
+ ((struct iso_primary_descriptor*)buf)->version,
+ 711);
+ debug_dump_to_xml_padded_hex_output("system_id",
+ (u_char *)((struct iso_primary_descriptor*)buf)->system_id,
+ ISODCL(9,40));
+ debug_dump_to_xml_padded_hex_output("volume_id",
+ (u_char *)((struct iso_primary_descriptor*)buf)->volume_id,
+ ISODCL(41,72));
+ debug_dump_integer("volume_space_size",
+ ((struct iso_primary_descriptor*)buf)->volume_space_size,
+ 733);
+ debug_dump_integer("volume_set_size",
+ ((struct iso_primary_descriptor*)buf)->volume_set_size,
+ 733);
+ debug_dump_integer("volume_sequence_number",
+ ((struct iso_primary_descriptor*)buf)->volume_sequence_number,
+ 723);
+ debug_dump_integer("logical_block_size",
+ ((struct iso_primary_descriptor*)buf)->logical_block_size,
+ 723);
+ debug_dump_integer("path_table_size",
+ ((struct iso_primary_descriptor*)buf)->path_table_size,
+ 733);
+ debug_dump_integer("type_l_path_table",
+ ((struct iso_primary_descriptor*)buf)->type_l_path_table,
+ 731);
+ debug_dump_integer("opt_type_l_path_table",
+ ((struct iso_primary_descriptor*)buf)->opt_type_l_path_table,
+ 731);
+ debug_dump_integer("type_m_path_table",
+ ((struct iso_primary_descriptor*)buf)->type_m_path_table,
+ 732);
+ debug_dump_integer("opt_type_m_path_table",
+ ((struct iso_primary_descriptor*)buf)->opt_type_m_path_table,732);
+ debug_dump_directory_record_9_1(
+ (u_char *)((struct iso_primary_descriptor*)buf)->root_directory_record);
+ debug_dump_to_xml_padded_hex_output("volume_set_id",
+ (u_char *)((struct iso_primary_descriptor*) buf)->volume_set_id,
+ ISODCL (191, 318));
+ debug_dump_to_xml_padded_hex_output("publisher_id",
+ (u_char *)((struct iso_primary_descriptor*) buf)->publisher_id,
+ ISODCL (319, 446));
+ debug_dump_to_xml_padded_hex_output("preparer_id",
+ (u_char *)((struct iso_primary_descriptor*) buf)->preparer_id,
+ ISODCL (447, 574));
+ debug_dump_to_xml_padded_hex_output("application_id",
+ (u_char *)((struct iso_primary_descriptor*) buf)->application_id,
+ ISODCL (575, 702));
+ debug_dump_to_xml_padded_hex_output("copyright_file_id",
+ (u_char *)((struct iso_primary_descriptor*) buf)->copyright_file_id,
+ ISODCL (703, 739));
+ debug_dump_to_xml_padded_hex_output("abstract_file_id",
+ (u_char *)((struct iso_primary_descriptor*) buf)->abstract_file_id,
+ ISODCL (740, 776));
+ debug_dump_to_xml_padded_hex_output("bibliographic_file_id",
+ (u_char *)((struct iso_primary_descriptor*) buf)->bibliographic_file_id,
+ ISODCL (777, 813));
+
+ debug_dump_to_xml_padded_hex_output("creation_date",
+ (u_char *)((struct iso_primary_descriptor*) buf)->creation_date,
+ ISODCL (814, 830));
+ debug_dump_to_xml_padded_hex_output("modification_date",
+ (u_char *)((struct iso_primary_descriptor*) buf)->modification_date,
+ ISODCL (831, 847));
+ debug_dump_to_xml_padded_hex_output("expiration_date",
+ (u_char *)((struct iso_primary_descriptor*) buf)->expiration_date,
+ ISODCL (848, 864));
+ debug_dump_to_xml_padded_hex_output("effective_date",
+ (u_char *)((struct iso_primary_descriptor*) buf)->effective_date,
+ ISODCL (865, 881));
+
+ debug_dump_to_xml_padded_hex_output("file_structure_version",
+ (u_char *)((struct iso_primary_descriptor*) buf)->file_structure_version,
+ ISODCL(882,882));
+ break;
+ }
+ printf("</volumedescriptor>\n");
+}
+
diff --git a/usr.sbin/makefs/cd9660/cd9660_eltorito.c b/usr.sbin/makefs/cd9660/cd9660_eltorito.c
new file mode 100644
index 0000000..f26a554
--- /dev/null
+++ b/usr.sbin/makefs/cd9660/cd9660_eltorito.c
@@ -0,0 +1,711 @@
+/* $NetBSD: cd9660_eltorito.c,v 1.17 2011/06/23 02:35:56 enami Exp $ */
+
+/*
+ * Copyright (c) 2005 Daniel Watt, Walter Deignan, Ryan Gabrys, Alan
+ * Perez-Rathke and Ram Vedam. All rights reserved.
+ *
+ * This code was written by Daniel Watt, Walter Deignan, Ryan Gabrys,
+ * Alan Perez-Rathke and Ram Vedam.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE,DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (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 "cd9660.h"
+#include "cd9660_eltorito.h"
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#ifdef DEBUG
+#define ELTORITO_DPRINTF(__x) printf __x
+#else
+#define ELTORITO_DPRINTF(__x)
+#endif
+
+static struct boot_catalog_entry *cd9660_init_boot_catalog_entry(void);
+static struct boot_catalog_entry *cd9660_boot_setup_validation_entry(char);
+static struct boot_catalog_entry *cd9660_boot_setup_default_entry(
+ struct cd9660_boot_image *);
+static struct boot_catalog_entry *cd9660_boot_setup_section_head(char);
+static struct boot_catalog_entry *cd9660_boot_setup_validation_entry(char);
+#if 0
+static u_char cd9660_boot_get_system_type(struct cd9660_boot_image *);
+#endif
+
+int
+cd9660_add_boot_disk(const char *boot_info)
+{
+ struct stat stbuf;
+ const char *mode_msg;
+ char *temp;
+ char *sysname;
+ char *filename;
+ struct cd9660_boot_image *new_image, *tmp_image;
+
+ assert(boot_info != NULL);
+
+ if (*boot_info == '\0') {
+ warnx("Error: Boot disk information must be in the "
+ "format 'system;filename'");
+ return 0;
+ }
+
+ /* First decode the boot information */
+ if ((temp = strdup(boot_info)) == NULL) {
+ warn("%s: strdup", __func__);
+ return 0;
+ }
+
+ sysname = temp;
+ filename = strchr(sysname, ';');
+ if (filename == NULL) {
+ warnx("supply boot disk information in the format "
+ "'system;filename'");
+ free(temp);
+ return 0;
+ }
+
+ *filename++ = '\0';
+
+ if (diskStructure.verbose_level > 0) {
+ printf("Found bootdisk with system %s, and filename %s\n",
+ sysname, filename);
+ }
+ if ((new_image = malloc(sizeof(*new_image))) == NULL) {
+ warn("%s: malloc", __func__);
+ free(temp);
+ return 0;
+ }
+ (void)memset(new_image, 0, sizeof(*new_image));
+ new_image->loadSegment = 0; /* default for now */
+
+ /* Decode System */
+ if (strcmp(sysname, "i386") == 0)
+ new_image->system = ET_SYS_X86;
+ else if (strcmp(sysname, "powerpc") == 0)
+ new_image->system = ET_SYS_PPC;
+ else if (strcmp(sysname, "macppc") == 0 ||
+ strcmp(sysname, "mac68k") == 0)
+ new_image->system = ET_SYS_MAC;
+ else {
+ warnx("boot disk system must be "
+ "i386, powerpc, macppc, or mac68k");
+ free(temp);
+ free(new_image);
+ return 0;
+ }
+
+
+ if ((new_image->filename = strdup(filename)) == NULL) {
+ warn("%s: strdup", __func__);
+ free(temp);
+ free(new_image);
+ return 0;
+ }
+
+ free(temp);
+
+ /* Get information about the file */
+ if (lstat(new_image->filename, &stbuf) == -1)
+ err(EXIT_FAILURE, "%s: lstat(\"%s\")", __func__,
+ new_image->filename);
+
+ switch (stbuf.st_size) {
+ case 1440 * 1024:
+ new_image->targetMode = ET_MEDIA_144FDD;
+ mode_msg = "Assigned boot image to 1.44 emulation mode";
+ break;
+ case 1200 * 1024:
+ new_image->targetMode = ET_MEDIA_12FDD;
+ mode_msg = "Assigned boot image to 1.2 emulation mode";
+ break;
+ case 2880 * 1024:
+ new_image->targetMode = ET_MEDIA_288FDD;
+ mode_msg = "Assigned boot image to 2.88 emulation mode";
+ break;
+ default:
+ new_image->targetMode = ET_MEDIA_NOEM;
+ mode_msg = "Assigned boot image to no emulation mode";
+ break;
+ }
+
+ if (diskStructure.verbose_level > 0)
+ printf("%s\n", mode_msg);
+
+ new_image->size = stbuf.st_size;
+ new_image->num_sectors =
+ howmany(new_image->size, diskStructure.sectorSize) *
+ howmany(diskStructure.sectorSize, 512);
+ if (diskStructure.verbose_level > 0) {
+ printf("New image has size %d, uses %d 512-byte sectors\n",
+ new_image->size, new_image->num_sectors);
+ }
+ new_image->sector = -1;
+ /* Bootable by default */
+ new_image->bootable = ET_BOOTABLE;
+ /* Add boot disk */
+
+ /* Group images for the same platform together. */
+ TAILQ_FOREACH(tmp_image, &diskStructure.boot_images, image_list) {
+ if (tmp_image->system != new_image->system)
+ break;
+ }
+
+ if (tmp_image == NULL) {
+ TAILQ_INSERT_HEAD(&diskStructure.boot_images, new_image,
+ image_list);
+ } else
+ TAILQ_INSERT_BEFORE(tmp_image, new_image, image_list);
+
+ new_image->serialno = diskStructure.image_serialno++;
+
+ /* TODO : Need to do anything about the boot image in the tree? */
+ diskStructure.is_bootable = 1;
+
+ return 1;
+}
+
+int
+cd9660_eltorito_add_boot_option(const char *option_string, const char *value)
+{
+ char *eptr;
+ struct cd9660_boot_image *image;
+
+ assert(option_string != NULL);
+
+ /* Find the last image added */
+ TAILQ_FOREACH(image, &diskStructure.boot_images, image_list) {
+ if (image->serialno + 1 == diskStructure.image_serialno)
+ break;
+ }
+ if (image == NULL)
+ errx(EXIT_FAILURE, "Attempted to add boot option, "
+ "but no boot images have been specified");
+
+ if (strcmp(option_string, "no-emul-boot") == 0) {
+ image->targetMode = ET_MEDIA_NOEM;
+ } else if (strcmp(option_string, "no-boot") == 0) {
+ image->bootable = ET_NOT_BOOTABLE;
+ } else if (strcmp(option_string, "hard-disk-boot") == 0) {
+ image->targetMode = ET_MEDIA_HDD;
+ } else if (strcmp(option_string, "boot-load-segment") == 0) {
+ image->loadSegment = strtoul(value, &eptr, 16);
+ if (eptr == value || *eptr != '\0' || errno != ERANGE) {
+ warn("%s: strtoul", __func__);
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+ return 1;
+}
+
+static struct boot_catalog_entry *
+cd9660_init_boot_catalog_entry(void)
+{
+ struct boot_catalog_entry *temp;
+
+ if ((temp = malloc(sizeof(*temp))) == NULL)
+ return NULL;
+
+ return memset(temp, 0, sizeof(*temp));
+}
+
+static struct boot_catalog_entry *
+cd9660_boot_setup_validation_entry(char sys)
+{
+ struct boot_catalog_entry *entry;
+ boot_catalog_validation_entry *ve;
+ int16_t checksum;
+ unsigned char *csptr;
+ int i;
+ entry = cd9660_init_boot_catalog_entry();
+
+ if (entry == NULL) {
+ warnx("Error: memory allocation failed in "
+ "cd9660_boot_setup_validation_entry");
+ return 0;
+ }
+ ve = &entry->entry_data.VE;
+
+ ve->header_id[0] = 1;
+ ve->platform_id[0] = sys;
+ ve->key[0] = 0x55;
+ ve->key[1] = 0xAA;
+
+ /* Calculate checksum */
+ checksum = 0;
+ cd9660_721(0, ve->checksum);
+ csptr = (unsigned char*)ve;
+ for (i = 0; i < sizeof(*ve); i += 2) {
+ checksum += (int16_t)csptr[i];
+ checksum += 256 * (int16_t)csptr[i + 1];
+ }
+ checksum = -checksum;
+ cd9660_721(checksum, ve->checksum);
+
+ ELTORITO_DPRINTF(("%s: header_id %d, platform_id %d, key[0] %d, key[1] %d, "
+ "checksum %04x\n", __func__, ve->header_id[0], ve->platform_id[0],
+ ve->key[0], ve->key[1], checksum));
+ return entry;
+}
+
+static struct boot_catalog_entry *
+cd9660_boot_setup_default_entry(struct cd9660_boot_image *disk)
+{
+ struct boot_catalog_entry *default_entry;
+ boot_catalog_initial_entry *ie;
+
+ default_entry = cd9660_init_boot_catalog_entry();
+ if (default_entry == NULL)
+ return NULL;
+
+ ie = &default_entry->entry_data.IE;
+
+ ie->boot_indicator[0] = disk->bootable;
+ ie->media_type[0] = disk->targetMode;
+ cd9660_721(disk->loadSegment, ie->load_segment);
+ ie->system_type[0] = disk->system;
+ cd9660_721(disk->num_sectors, ie->sector_count);
+ cd9660_731(disk->sector, ie->load_rba);
+
+ ELTORITO_DPRINTF(("%s: boot indicator %d, media type %d, "
+ "load segment %04x, system type %d, sector count %d, "
+ "load rba %d\n", __func__, ie->boot_indicator[0],
+ ie->media_type[0], disk->loadSegment, ie->system_type[0],
+ disk->num_sectors, disk->sector));
+ return default_entry;
+}
+
+static struct boot_catalog_entry *
+cd9660_boot_setup_section_head(char platform)
+{
+ struct boot_catalog_entry *entry;
+ boot_catalog_section_header *sh;
+
+ entry = cd9660_init_boot_catalog_entry();
+ if (entry == NULL)
+ return NULL;
+
+ sh = &entry->entry_data.SH;
+ /* More by default. The last one will manually be set to 0x91 */
+ sh->header_indicator[0] = ET_SECTION_HEADER_MORE;
+ sh->platform_id[0] = platform;
+ sh->num_section_entries[0] = 0;
+ return entry;
+}
+
+static struct boot_catalog_entry *
+cd9660_boot_setup_section_entry(struct cd9660_boot_image *disk)
+{
+ struct boot_catalog_entry *entry;
+ boot_catalog_section_entry *se;
+ if ((entry = cd9660_init_boot_catalog_entry()) == NULL)
+ return NULL;
+
+ se = &entry->entry_data.SE;
+
+ se->boot_indicator[0] = ET_BOOTABLE;
+ se->media_type[0] = disk->targetMode;
+ cd9660_721(disk->loadSegment, se->load_segment);
+ cd9660_721(disk->num_sectors, se->sector_count);
+ cd9660_731(disk->sector, se->load_rba);
+ return entry;
+}
+
+#if 0
+static u_char
+cd9660_boot_get_system_type(struct cd9660_boot_image *disk)
+{
+ /*
+ For hard drive booting, we need to examine the MBR to figure
+ out what the partition type is
+ */
+ return 0;
+}
+#endif
+
+/*
+ * Set up the BVD, Boot catalog, and the boot entries, but do no writing
+ */
+int
+cd9660_setup_boot(int first_sector)
+{
+ int sector;
+ int used_sectors;
+ int num_entries = 0;
+ int catalog_sectors;
+ struct boot_catalog_entry *x86_head, *mac_head, *ppc_head,
+ *valid_entry, *default_entry, *temp, *head, **headp, *next;
+ struct cd9660_boot_image *tmp_disk;
+
+ headp = NULL;
+ x86_head = mac_head = ppc_head = NULL;
+
+ /* If there are no boot disks, don't bother building boot information */
+ if (TAILQ_EMPTY(&diskStructure.boot_images))
+ return 0;
+
+ /* Point to catalog: For now assume it consumes one sector */
+ ELTORITO_DPRINTF(("Boot catalog will go in sector %d\n", first_sector));
+ diskStructure.boot_catalog_sector = first_sector;
+ cd9660_bothendian_dword(first_sector,
+ diskStructure.boot_descriptor->boot_catalog_pointer);
+
+ /* Step 1: Generate boot catalog */
+ /* Step 1a: Validation entry */
+ valid_entry = cd9660_boot_setup_validation_entry(ET_SYS_X86);
+ if (valid_entry == NULL)
+ return -1;
+
+ /*
+ * Count how many boot images there are,
+ * and how many sectors they consume.
+ */
+ num_entries = 1;
+ used_sectors = 0;
+
+ TAILQ_FOREACH(tmp_disk, &diskStructure.boot_images, image_list) {
+ used_sectors += tmp_disk->num_sectors;
+
+ /* One default entry per image */
+ num_entries++;
+ }
+ catalog_sectors = howmany(num_entries * 0x20, diskStructure.sectorSize);
+ used_sectors += catalog_sectors;
+
+ if (diskStructure.verbose_level > 0) {
+ printf("%s: there will be %i entries consuming %i sectors. "
+ "Catalog is %i sectors\n", __func__, num_entries,
+ used_sectors, catalog_sectors);
+ }
+
+ /* Populate sector numbers */
+ sector = first_sector + catalog_sectors;
+ TAILQ_FOREACH(tmp_disk, &diskStructure.boot_images, image_list) {
+ tmp_disk->sector = sector;
+ sector += tmp_disk->num_sectors;
+ }
+
+ LIST_INSERT_HEAD(&diskStructure.boot_entries, valid_entry, ll_struct);
+
+ /* Step 1b: Initial/default entry */
+ /* TODO : PARAM */
+ tmp_disk = TAILQ_FIRST(&diskStructure.boot_images);
+ default_entry = cd9660_boot_setup_default_entry(tmp_disk);
+ if (default_entry == NULL) {
+ warnx("Error: memory allocation failed in cd9660_setup_boot");
+ return -1;
+ }
+
+ LIST_INSERT_AFTER(valid_entry, default_entry, ll_struct);
+
+ /* Todo: multiple default entries? */
+
+ tmp_disk = TAILQ_NEXT(tmp_disk, image_list);
+
+ temp = default_entry;
+
+ /* If multiple boot images are given : */
+ while (tmp_disk != NULL) {
+ /* Step 2: Section header */
+ switch (tmp_disk->system) {
+ case ET_SYS_X86:
+ headp = &x86_head;
+ break;
+ case ET_SYS_PPC:
+ headp = &ppc_head;
+ break;
+ case ET_SYS_MAC:
+ headp = &mac_head;
+ break;
+ default:
+ warnx("%s: internal error: unknown system type",
+ __func__);
+ return -1;
+ }
+
+ if (*headp == NULL) {
+ head =
+ cd9660_boot_setup_section_head(tmp_disk->system);
+ if (head == NULL) {
+ warnx("Error: memory allocation failed in "
+ "cd9660_setup_boot");
+ return -1;
+ }
+ LIST_INSERT_AFTER(default_entry, head, ll_struct);
+ *headp = head;
+ } else
+ head = *headp;
+
+ head->entry_data.SH.num_section_entries[0]++;
+
+ /* Step 2a: Section entry and extensions */
+ temp = cd9660_boot_setup_section_entry(tmp_disk);
+ if (temp == NULL) {
+ warn("%s: cd9660_boot_setup_section_entry", __func__);
+ return -1;
+ }
+
+ while ((next = LIST_NEXT(head, ll_struct)) != NULL &&
+ next->entry_type == ET_ENTRY_SE)
+ head = next;
+
+ LIST_INSERT_AFTER(head, temp, ll_struct);
+ tmp_disk = TAILQ_NEXT(tmp_disk, image_list);
+ }
+
+ /* TODO: Remaining boot disks when implemented */
+
+ return first_sector + used_sectors;
+}
+
+int
+cd9660_setup_boot_volume_descriptor(volume_descriptor *bvd)
+{
+ boot_volume_descriptor *bvdData =
+ (boot_volume_descriptor*)bvd->volumeDescriptorData;
+
+ bvdData->boot_record_indicator[0] = ISO_VOLUME_DESCRIPTOR_BOOT;
+ memcpy(bvdData->identifier, ISO_VOLUME_DESCRIPTOR_STANDARD_ID, 5);
+ bvdData->version[0] = 1;
+ memcpy(bvdData->boot_system_identifier, ET_ID, 23);
+ memcpy(bvdData->identifier, ISO_VOLUME_DESCRIPTOR_STANDARD_ID, 5);
+ diskStructure.boot_descriptor =
+ (boot_volume_descriptor*) bvd->volumeDescriptorData;
+ return 1;
+}
+
+static int
+cd9660_write_mbr_partition_entry(FILE *fd, int idx, off_t sector_start,
+ off_t nsectors, int type)
+{
+ uint8_t val;
+ uint32_t lba;
+
+ if (fseeko(fd, (off_t)(idx) * 16 + 0x1be, SEEK_SET) == -1)
+ err(1, "fseeko");
+
+ val = 0x80; /* Bootable */
+ fwrite(&val, sizeof(val), 1, fd);
+
+ val = 0xff; /* CHS begin */
+ fwrite(&val, sizeof(val), 1, fd);
+ fwrite(&val, sizeof(val), 1, fd);
+ fwrite(&val, sizeof(val), 1, fd);
+
+ val = type; /* Part type */
+ fwrite(&val, sizeof(val), 1, fd);
+
+ val = 0xff; /* CHS end */
+ fwrite(&val, sizeof(val), 1, fd);
+ fwrite(&val, sizeof(val), 1, fd);
+ fwrite(&val, sizeof(val), 1, fd);
+
+ /* LBA extent */
+ lba = htole32(sector_start);
+ fwrite(&lba, sizeof(lba), 1, fd);
+ lba = htole32(nsectors);
+ fwrite(&lba, sizeof(lba), 1, fd);
+
+ return 0;
+}
+
+static int
+cd9660_write_apm_partition_entry(FILE *fd, int idx, int total_partitions,
+ off_t sector_start, off_t nsectors, off_t sector_size,
+ const char *part_name, const char *part_type)
+{
+ uint32_t apm32, part_status;
+ uint16_t apm16;
+
+ /* See Apple Tech Note 1189 for the details about the pmPartStatus
+ * flags.
+ * Below the flags which are default:
+ * - IsValid 0x01
+ * - IsAllocated 0x02
+ * - IsReadable 0x10
+ * - IsWritable 0x20
+ */
+ part_status = 0x01 | 0x02 | 0x10 | 0x20;
+
+ if (fseeko(fd, (off_t)(idx + 1) * sector_size, SEEK_SET) == -1)
+ err(1, "fseeko");
+
+ /* Signature */
+ apm16 = htobe16(0x504d);
+ fwrite(&apm16, sizeof(apm16), 1, fd);
+ apm16 = 0;
+ fwrite(&apm16, sizeof(apm16), 1, fd);
+
+ /* Total number of partitions */
+ apm32 = htobe32(total_partitions);
+ fwrite(&apm32, sizeof(apm32), 1, fd);
+ /* Bounds */
+ apm32 = htobe32(sector_start);
+ fwrite(&apm32, sizeof(apm32), 1, fd);
+ apm32 = htobe32(nsectors);
+ fwrite(&apm32, sizeof(apm32), 1, fd);
+
+ fwrite(part_name, strlen(part_name) + 1, 1, fd);
+ fseek(fd, 32 - strlen(part_name) - 1, SEEK_CUR);
+ fwrite(part_type, strlen(part_type) + 1, 1, fd);
+ fseek(fd, 32 - strlen(part_type) - 1, SEEK_CUR);
+
+ apm32 = 0;
+ /* pmLgDataStart */
+ fwrite(&apm32, sizeof(apm32), 1, fd);
+ /* pmDataCnt */
+ apm32 = htobe32(nsectors);
+ fwrite(&apm32, sizeof(apm32), 1, fd);
+ /* pmPartStatus */
+ apm32 = htobe32(part_status);
+ fwrite(&apm32, sizeof(apm32), 1, fd);
+
+ return 0;
+}
+
+int
+cd9660_write_boot(FILE *fd)
+{
+ struct boot_catalog_entry *e;
+ struct cd9660_boot_image *t;
+ int apm_partitions = 0;
+ int mbr_partitions = 0;
+
+ /* write boot catalog */
+ if (fseeko(fd, (off_t)diskStructure.boot_catalog_sector *
+ diskStructure.sectorSize, SEEK_SET) == -1)
+ err(1, "fseeko");
+
+ if (diskStructure.verbose_level > 0) {
+ printf("Writing boot catalog to sector %" PRId64 "\n",
+ diskStructure.boot_catalog_sector);
+ }
+ LIST_FOREACH(e, &diskStructure.boot_entries, ll_struct) {
+ if (diskStructure.verbose_level > 0) {
+ printf("Writing catalog entry of type %d\n",
+ e->entry_type);
+ }
+ /*
+ * It doesn't matter which one gets written
+ * since they are the same size
+ */
+ fwrite(&(e->entry_data.VE), 1, 32, fd);
+ }
+ if (diskStructure.verbose_level > 0)
+ printf("Finished writing boot catalog\n");
+
+ /* copy boot images */
+ TAILQ_FOREACH(t, &diskStructure.boot_images, image_list) {
+ if (diskStructure.verbose_level > 0) {
+ printf("Writing boot image from %s to sectors %d\n",
+ t->filename, t->sector);
+ }
+ cd9660_copy_file(fd, t->sector, t->filename);
+
+ if (t->system == ET_SYS_MAC)
+ apm_partitions++;
+ if (t->system == ET_SYS_PPC)
+ mbr_partitions++;
+ }
+
+ /* some systems need partition tables as well */
+ if (mbr_partitions > 0 || diskStructure.chrp_boot) {
+ uint16_t sig;
+
+ fseek(fd, 0x1fe, SEEK_SET);
+ sig = htole16(0xaa55);
+ fwrite(&sig, sizeof(sig), 1, fd);
+
+ mbr_partitions = 0;
+
+ /* Write ISO9660 descriptor, enclosing the whole disk */
+ if (diskStructure.chrp_boot)
+ cd9660_write_mbr_partition_entry(fd, mbr_partitions++,
+ 0, diskStructure.totalSectors *
+ (diskStructure.sectorSize / 512), 0x96);
+
+ /* Write all partition entries */
+ TAILQ_FOREACH(t, &diskStructure.boot_images, image_list) {
+ if (t->system != ET_SYS_PPC)
+ continue;
+ cd9660_write_mbr_partition_entry(fd, mbr_partitions++,
+ t->sector * (diskStructure.sectorSize / 512),
+ t->num_sectors * (diskStructure.sectorSize / 512),
+ 0x41 /* PReP Boot */);
+ }
+ }
+
+ if (apm_partitions > 0) {
+ /* Write DDR and global APM info */
+ uint32_t apm32;
+ uint16_t apm16;
+ int total_parts;
+
+ fseek(fd, 0, SEEK_SET);
+ apm16 = htobe16(0x4552);
+ fwrite(&apm16, sizeof(apm16), 1, fd);
+ /* Device block size */
+ apm16 = htobe16(512);
+ fwrite(&apm16, sizeof(apm16), 1, fd);
+ /* Device block count */
+ apm32 = htobe32(diskStructure.totalSectors *
+ (diskStructure.sectorSize / 512));
+ fwrite(&apm32, sizeof(apm32), 1, fd);
+ /* Device type/id */
+ apm16 = htobe16(1);
+ fwrite(&apm16, sizeof(apm16), 1, fd);
+ fwrite(&apm16, sizeof(apm16), 1, fd);
+
+ /* Count total needed entries */
+ total_parts = 2 + apm_partitions; /* Self + ISO9660 */
+
+ /* Write self-descriptor */
+ cd9660_write_apm_partition_entry(fd, 0, total_parts, 1,
+ total_parts, 512, "Apple", "Apple_partition_map");
+
+ /* Write all partition entries */
+ apm_partitions = 0;
+ TAILQ_FOREACH(t, &diskStructure.boot_images, image_list) {
+ if (t->system != ET_SYS_MAC)
+ continue;
+
+ cd9660_write_apm_partition_entry(fd,
+ 1 + apm_partitions++, total_parts,
+ t->sector * (diskStructure.sectorSize / 512),
+ t->num_sectors * (diskStructure.sectorSize / 512),
+ 512, "CD Boot", "Apple_Bootstrap");
+ }
+ /* Write ISO9660 descriptor, enclosing the whole disk */
+ cd9660_write_apm_partition_entry(fd, 2 + apm_partitions,
+ total_parts, 0, diskStructure.totalSectors *
+ (diskStructure.sectorSize / 512), 512, "ISO9660",
+ "CD_ROM_Mode_1");
+ }
+
+ return 0;
+}
+
diff --git a/usr.sbin/makefs/cd9660/cd9660_eltorito.h b/usr.sbin/makefs/cd9660/cd9660_eltorito.h
new file mode 100644
index 0000000..02f153d
--- /dev/null
+++ b/usr.sbin/makefs/cd9660/cd9660_eltorito.h
@@ -0,0 +1,164 @@
+/* $NetBSD: cd9660_eltorito.h,v 1.5 2009/07/04 14:31:38 ahoka Exp $ */
+
+/*
+ * Copyright (c) 2005 Daniel Watt, Walter Deignan, Ryan Gabrys, Alan
+ * Perez-Rathke and Ram Vedam. All rights reserved.
+ *
+ * This code was written by Daniel Watt, Walter Deignan, Ryan Gabrys,
+ * Alan Perez-Rathke and Ram Vedam.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE,DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (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 _CD9660_ELTORITO_H_
+#define _CD9660_ELTORITO_H_
+
+/* Boot defines */
+#define ET_ID "EL TORITO SPECIFICATION"
+#define ET_SYS_X86 0
+#define ET_SYS_PPC 1
+#define ET_SYS_MAC 2
+
+#define ET_BOOT_ENTRY_SIZE 0x20
+
+#define ET_BOOTABLE 0x88
+#define ET_NOT_BOOTABLE 0
+
+#define ET_MEDIA_NOEM 0
+#define ET_MEDIA_12FDD 1
+#define ET_MEDIA_144FDD 2
+#define ET_MEDIA_288FDD 3
+#define ET_MEDIA_HDD 4
+
+#define ET_INDICATOR_HEADERMORE 0x90
+#define ET_INDICATOR_HEADERLAST 0x91
+#define ET_INDICATOR_EXTENSION 0x44
+
+/*** Boot Structures ***/
+
+typedef struct _boot_volume_descriptor {
+ u_char boot_record_indicator [ISODCL(0x00,0x00)];
+ u_char identifier [ISODCL(0x01,0x05)];
+ u_char version [ISODCL(0x06,0x06)];
+ u_char boot_system_identifier [ISODCL(0x07,0x26)];
+ u_char unused1 [ISODCL(0x27,0x46)];
+ u_char boot_catalog_pointer [ISODCL(0x47,0x4A)];
+ u_char unused2 [ISODCL(0x4B,0x7FF)];
+} boot_volume_descriptor;
+
+typedef struct _boot_catalog_validation_entry {
+ u_char header_id [ISODCL(0x00,0x00)];
+ u_char platform_id [ISODCL(0x01,0x01)];
+ u_char reserved1 [ISODCL(0x02,0x03)];
+ u_char manufacturer [ISODCL(0x04,0x1B)];
+ u_char checksum [ISODCL(0x1C,0x1D)];
+ u_char key [ISODCL(0x1E,0x1F)];
+} boot_catalog_validation_entry;
+
+typedef struct _boot_catalog_initial_entry {
+ u_char boot_indicator [ISODCL(0x00,0x00)];
+ u_char media_type [ISODCL(0x01,0x01)];
+ u_char load_segment [ISODCL(0x02,0x03)];
+ u_char system_type [ISODCL(0x04,0x04)];
+ u_char unused_1 [ISODCL(0x05,0x05)];
+ u_char sector_count [ISODCL(0x06,0x07)];
+ u_char load_rba [ISODCL(0x08,0x0B)];
+ u_char unused_2 [ISODCL(0x0C,0x1F)];
+} boot_catalog_initial_entry;
+
+#define ET_SECTION_HEADER_MORE 0x90
+#define ET_SECTION_HEADER_LAST 0x91
+
+typedef struct _boot_catalog_section_header {
+ u_char header_indicator [ISODCL(0x00,0x00)];
+ u_char platform_id [ISODCL(0x01,0x01)];
+ u_char num_section_entries [ISODCL(0x02,0x03)];
+ u_char id_string [ISODCL(0x04,0x1F)];
+} boot_catalog_section_header;
+
+typedef struct _boot_catalog_section_entry {
+ u_char boot_indicator [ISODCL(0x00,0x00)];
+ u_char media_type [ISODCL(0x01,0x01)];
+ u_char load_segment [ISODCL(0x02,0x03)];
+ u_char system_type [ISODCL(0x04,0x04)];
+ u_char unused_1 [ISODCL(0x05,0x05)];
+ u_char sector_count [ISODCL(0x06,0x07)];
+ u_char load_rba [ISODCL(0x08,0x0B)];
+ u_char selection_criteria [ISODCL(0x0C,0x0C)];
+ u_char vendor_criteria [ISODCL(0x0D,0x1F)];
+} boot_catalog_section_entry;
+
+typedef struct _boot_catalog_section_entry_extension {
+ u_char extension_indicator [ISODCL(0x00,0x00)];
+ u_char flags [ISODCL(0x01,0x01)];
+ u_char vendor_criteria [ISODCL(0x02,0x1F)];
+} boot_catalog_section_entry_extension;
+
+#define ET_ENTRY_VE 1
+#define ET_ENTRY_IE 2
+#define ET_ENTRY_SH 3
+#define ET_ENTRY_SE 4
+#define ET_ENTRY_EX 5
+
+struct boot_catalog_entry {
+ char entry_type;
+ union {
+ boot_catalog_validation_entry VE;
+ boot_catalog_initial_entry IE;
+ boot_catalog_section_header SH;
+ boot_catalog_section_entry SE;
+ boot_catalog_section_entry_extension EX;
+ } entry_data;
+
+ LIST_ENTRY(boot_catalog_entry) ll_struct;
+};
+
+/* Temporary structure */
+struct cd9660_boot_image {
+ char *filename;
+ int size;
+ int sector; /* copied to LoadRBA */
+ int num_sectors;
+ unsigned int loadSegment;
+ u_char targetMode;
+ u_char system;
+ u_char bootable;
+ /*
+ * If the boot image exists in the filesystem
+ * already, this is a pointer to that node. For the sake
+ * of simplicity in future versions, this pointer is only
+ * to the node in the primary volume. This SHOULD be done
+ * via a hashtable lookup.
+ */
+ struct _cd9660node *boot_image_node;
+ TAILQ_ENTRY(cd9660_boot_image) image_list;
+ int serialno;
+};
+
+
+#endif /* _CD9660_ELTORITO_H_ */
+
diff --git a/usr.sbin/makefs/cd9660/cd9660_strings.c b/usr.sbin/makefs/cd9660/cd9660_strings.c
new file mode 100644
index 0000000..6280a5c
--- /dev/null
+++ b/usr.sbin/makefs/cd9660/cd9660_strings.c
@@ -0,0 +1,120 @@
+/* $NetBSD: cd9660_strings.c,v 1.4 2007/01/16 17:32:05 hubertf Exp $ */
+
+/*
+ * Copyright (c) 2005 Daniel Watt, Walter Deignan, Ryan Gabrys, Alan
+ * Perez-Rathke and Ram Vedam. All rights reserved.
+ *
+ * This code was written by Daniel Watt, Walter Deignan, Ryan Gabrys,
+ * Alan Perez-Rathke and Ram Vedam.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE,DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (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/mount.h>
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+#include <sys/param.h>
+#include <ctype.h>
+
+#include "makefs.h"
+#include "cd9660.h"
+
+
+void
+cd9660_uppercase_characters(char *str, int len)
+{
+ int p;
+
+ for (p = 0; p < len; p++) {
+ if (islower((unsigned char)str[p]) )
+ str[p] -= 32;
+ }
+}
+
+static inline int
+cd9660_is_d_char(char c)
+{
+ return (isupper((unsigned char)c)
+ || c == '_'
+ || (c >= '0' && c <= '9'));
+}
+
+static inline int
+cd9660_is_a_char(char c)
+{
+ return (isupper((unsigned char)c)
+ || c == '_'
+ || (c >= '%' && c <= '?')
+ || (c >= ' ' && c <= '\"'));
+}
+
+/*
+ * Test a string to see if it is composed of valid a characters
+ * @param const char* The string to test
+ * @returns int 1 if valid, 2 if valid if characters are converted to
+ * upper case, 0 otherwise
+ */
+int
+cd9660_valid_a_chars(const char *str)
+{
+ const char *c = str;
+ int upperFound = 0;
+
+ while ((*c) != '\0') {
+ if (!(cd9660_is_a_char(*c))) {
+ if (islower((unsigned char)*c) )
+ upperFound = 1;
+ else
+ return 0;
+ }
+ c++;
+ }
+ return upperFound + 1;
+}
+
+/*
+ * Test a string to see if it is composed of valid d characters
+ * @param const char* The string to test
+ * @returns int 1 if valid, 2 if valid if characters are converted to
+ * upper case, 0 otherwise
+ */
+int
+cd9660_valid_d_chars(const char *str)
+{
+ const char *c=str;
+ int upperFound = 0;
+
+ while ((*c) != '\0') {
+ if (!(cd9660_is_d_char(*c))) {
+ if (islower((unsigned char)*c) )
+ upperFound = 1;
+ else
+ return 0;
+ }
+ c++;
+ }
+ return upperFound + 1;
+}
diff --git a/usr.sbin/makefs/cd9660/cd9660_write.c b/usr.sbin/makefs/cd9660/cd9660_write.c
new file mode 100644
index 0000000..e17752a
--- /dev/null
+++ b/usr.sbin/makefs/cd9660/cd9660_write.c
@@ -0,0 +1,524 @@
+/* $NetBSD: cd9660_write.c,v 1.14 2011/01/04 09:48:21 wiz Exp $ */
+
+/*
+ * Copyright (c) 2005 Daniel Watt, Walter Deignan, Ryan Gabrys, Alan
+ * Perez-Rathke and Ram Vedam. All rights reserved.
+ *
+ * This code was written by Daniel Watt, Walter Deignan, Ryan Gabrys,
+ * Alan Perez-Rathke and Ram Vedam.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE,DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (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 "cd9660.h"
+#include "iso9660_rrip.h"
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+static int cd9660_write_volume_descriptors(FILE *);
+static int cd9660_write_path_table(FILE *, off_t, int);
+static int cd9660_write_path_tables(FILE *);
+static int cd9660_write_file(FILE *, cd9660node *);
+static int cd9660_write_filedata(FILE *, off_t, const unsigned char *, int);
+#if 0
+static int cd9660_write_buffered(FILE *, off_t, int, const unsigned char *);
+#endif
+static void cd9660_write_rr(FILE *, cd9660node *, off_t, off_t);
+
+/*
+ * Write the image
+ * Writes the entire image
+ * @param const char* The filename for the image
+ * @returns int 1 on success, 0 on failure
+ */
+int
+cd9660_write_image(const char* image)
+{
+ FILE *fd;
+ int status;
+ char buf[CD9660_SECTOR_SIZE];
+
+ if ((fd = fopen(image, "w+")) == NULL) {
+ err(EXIT_FAILURE, "%s: Can't open `%s' for writing", __func__,
+ image);
+ }
+
+ if (diskStructure.verbose_level > 0)
+ printf("Writing image\n");
+
+ if (diskStructure.has_generic_bootimage) {
+ status = cd9660_copy_file(fd, 0,
+ diskStructure.generic_bootimage);
+ if (status == 0) {
+ warnx("%s: Error writing generic boot image",
+ __func__);
+ goto cleanup_bad_image;
+ }
+ }
+
+ /* Write the volume descriptors */
+ status = cd9660_write_volume_descriptors(fd);
+ if (status == 0) {
+ warnx("%s: Error writing volume descriptors to image",
+ __func__);
+ goto cleanup_bad_image;
+ }
+
+ if (diskStructure.verbose_level > 0)
+ printf("Volume descriptors written\n");
+
+ /*
+ * Write the path tables: there are actually four, but right
+ * now we are only concearned with two.
+ */
+ status = cd9660_write_path_tables(fd);
+ if (status == 0) {
+ warnx("%s: Error writing path tables to image", __func__);
+ goto cleanup_bad_image;
+ }
+
+ if (diskStructure.verbose_level > 0)
+ printf("Path tables written\n");
+
+ /* Write the directories and files */
+ status = cd9660_write_file(fd, diskStructure.rootNode);
+ if (status == 0) {
+ warnx("%s: Error writing files to image", __func__);
+ goto cleanup_bad_image;
+ }
+
+ if (diskStructure.is_bootable) {
+ cd9660_write_boot(fd);
+ }
+
+ /* Write padding bits. This is temporary */
+ memset(buf, 0, CD9660_SECTOR_SIZE);
+ cd9660_write_filedata(fd, diskStructure.totalSectors - 1, buf, 1);
+
+ if (diskStructure.verbose_level > 0)
+ printf("Files written\n");
+ fclose(fd);
+
+ if (diskStructure.verbose_level > 0)
+ printf("Image closed\n");
+ return 1;
+
+cleanup_bad_image:
+ fclose(fd);
+ if (!diskStructure.keep_bad_images)
+ unlink(image);
+ if (diskStructure.verbose_level > 0)
+ printf("Bad image cleaned up\n");
+ return 0;
+}
+
+static int
+cd9660_write_volume_descriptors(FILE *fd)
+{
+ volume_descriptor *vd_temp = diskStructure.firstVolumeDescriptor;
+ int pos;
+
+ while (vd_temp != NULL) {
+ pos = vd_temp->sector * diskStructure.sectorSize;
+ cd9660_write_filedata(fd, vd_temp->sector,
+ vd_temp->volumeDescriptorData, 1);
+ vd_temp = vd_temp->next;
+ }
+ return 1;
+}
+
+/*
+ * Write out an individual path table
+ * Used just to keep redundant code to a minimum
+ * @param FILE *fd Valid file pointer
+ * @param int Sector to start writing path table to
+ * @param int Endian mode : BIG_ENDIAN or LITTLE_ENDIAN
+ * @returns int 1 on success, 0 on failure
+ */
+static int
+cd9660_write_path_table(FILE *fd, off_t sector, int mode)
+{
+ int path_table_sectors = CD9660_BLOCKS(diskStructure.sectorSize,
+ diskStructure.pathTableLength);
+ unsigned char *buffer;
+ unsigned char *buffer_head;
+ int len, ret;
+ path_table_entry temp_entry;
+ cd9660node *ptcur;
+
+ buffer = malloc(diskStructure.sectorSize * path_table_sectors);
+ if (buffer == NULL) {
+ warnx("%s: Memory allocation error allocating buffer",
+ __func__);
+ return 0;
+ }
+ buffer_head = buffer;
+ memset(buffer, 0, diskStructure.sectorSize * path_table_sectors);
+
+ ptcur = diskStructure.rootNode;
+
+ while (ptcur != NULL) {
+ memset(&temp_entry, 0, sizeof(path_table_entry));
+ temp_entry.length[0] = ptcur->isoDirRecord->name_len[0];
+ temp_entry.extended_attribute_length[0] =
+ ptcur->isoDirRecord->ext_attr_length[0];
+ memcpy(temp_entry.name, ptcur->isoDirRecord->name,
+ temp_entry.length[0] + 1);
+
+ /* round up */
+ len = temp_entry.length[0] + 8 + (temp_entry.length[0] & 0x01);
+
+ /* todo: function pointers instead */
+ if (mode == LITTLE_ENDIAN) {
+ cd9660_731(ptcur->fileDataSector,
+ temp_entry.first_sector);
+ cd9660_721((ptcur->parent == NULL ?
+ 1 : ptcur->parent->ptnumber),
+ temp_entry.parent_number);
+ } else {
+ cd9660_732(ptcur->fileDataSector,
+ temp_entry.first_sector);
+ cd9660_722((ptcur->parent == NULL ?
+ 1 : ptcur->parent->ptnumber),
+ temp_entry.parent_number);
+ }
+
+
+ memcpy(buffer, &temp_entry, len);
+ buffer += len;
+
+ ptcur = ptcur->ptnext;
+ }
+
+ ret = cd9660_write_filedata(fd, sector, buffer_head,
+ path_table_sectors);
+ free(buffer_head);
+ return ret;
+}
+
+
+/*
+ * Write out the path tables to disk
+ * Each file descriptor should be pointed to by the PVD, so we know which
+ * sector to copy them to. One thing to watch out for: the only path tables
+ * stored are in the endian mode that the application is compiled for. So,
+ * the first thing to do is write out that path table, then to write the one
+ * in the other endian mode requires to convert the endianness of each entry
+ * in the table. The best way to do this would be to create a temporary
+ * path_table_entry structure, then for each path table entry, copy it to
+ * the temporary entry, translate, then copy that to disk.
+ *
+ * @param FILE* Valid file descriptor
+ * @returns int 0 on failure, 1 on success
+ */
+static int
+cd9660_write_path_tables(FILE *fd)
+{
+ if (cd9660_write_path_table(fd,
+ diskStructure.primaryLittleEndianTableSector, LITTLE_ENDIAN) == 0)
+ return 0;
+
+ if (cd9660_write_path_table(fd,
+ diskStructure.primaryBigEndianTableSector, BIG_ENDIAN) == 0)
+ return 0;
+
+ /* @TODO: handle remaining two path tables */
+ return 1;
+}
+
+/*
+ * Write a file to disk
+ * Writes a file, its directory record, and its data to disk
+ * This file is designed to be called RECURSIVELY, so initially call it
+ * with the root node. All of the records should store what sector the
+ * file goes in, so no computation should be necessary.
+ *
+ * @param int fd Valid file descriptor
+ * @param struct cd9660node* writenode Pointer to the file to be written
+ * @returns int 0 on failure, 1 on success
+ */
+static int
+cd9660_write_file(FILE *fd, cd9660node *writenode)
+{
+ char *buf;
+ char *temp_file_name;
+ int ret;
+ off_t working_sector;
+ int cur_sector_offset;
+ int written;
+ iso_directory_record_cd9660 temp_record;
+ cd9660node *temp;
+ int rv = 0;
+
+ /* Todo : clean up variables */
+
+ temp_file_name = malloc(CD9660MAXPATH + 1);
+ if (temp_file_name == NULL)
+ err(EXIT_FAILURE, "%s: malloc", __func__);
+
+ memset(temp_file_name, 0, CD9660MAXPATH + 1);
+
+ buf = malloc(diskStructure.sectorSize);
+ if (buf == NULL)
+ err(EXIT_FAILURE, "%s: malloc", __func__);
+
+ if ((writenode->level != 0) &&
+ !(writenode->node->type & S_IFDIR)) {
+ fsinode *inode = writenode->node->inode;
+ /* Only attempt to write unwritten files that have length. */
+ if ((inode->flags & FI_WRITTEN) != 0) {
+ INODE_WARNX(("%s: skipping written inode %d", __func__,
+ (int)inode->st.st_ino));
+ } else if (writenode->fileDataLength > 0) {
+ INODE_WARNX(("%s: writing inode %d blocks at %" PRIu32,
+ __func__, (int)inode->st.st_ino, inode->ino));
+ inode->flags |= FI_WRITTEN;
+ if (writenode->node->contents == NULL)
+ cd9660_compute_full_filename(writenode,
+ temp_file_name);
+ ret = cd9660_copy_file(fd, writenode->fileDataSector,
+ (writenode->node->contents != NULL) ?
+ writenode->node->contents : temp_file_name);
+ if (ret == 0)
+ goto out;
+ }
+ } else {
+ /*
+ * Here is a new revelation that ECMA didn't explain
+ * (at least not well).
+ * ALL . and .. records store the name "\0" and "\1"
+ * respectively. So, for each directory, we have to
+ * make a new node.
+ *
+ * This is where it gets kinda messy, since we have to
+ * be careful of sector boundaries
+ */
+ cur_sector_offset = 0;
+ working_sector = writenode->fileDataSector;
+ if (fseeko(fd, working_sector * diskStructure.sectorSize,
+ SEEK_SET) == -1)
+ err(1, "fseeko");
+
+ /*
+ * Now loop over children, writing out their directory
+ * records - beware of sector boundaries
+ */
+ TAILQ_FOREACH(temp, &writenode->cn_children, cn_next_child) {
+ /*
+ * Copy the temporary record and adjust its size
+ * if necessary
+ */
+ memcpy(&temp_record, temp->isoDirRecord,
+ sizeof(iso_directory_record_cd9660));
+
+ temp_record.length[0] =
+ cd9660_compute_record_size(temp);
+
+ if (temp_record.length[0] + cur_sector_offset >=
+ diskStructure.sectorSize) {
+ cur_sector_offset = 0;
+ working_sector++;
+
+ /* Seek to the next sector. */
+ if (fseeko(fd, working_sector *
+ diskStructure.sectorSize, SEEK_SET) == -1)
+ err(1, "fseeko");
+ }
+ /* Write out the basic ISO directory record */
+ written = fwrite(&temp_record, 1,
+ temp->isoDirRecord->length[0], fd);
+ if (diskStructure.rock_ridge_enabled) {
+ cd9660_write_rr(fd, temp,
+ cur_sector_offset, working_sector);
+ }
+ if (fseeko(fd, working_sector *
+ diskStructure.sectorSize + cur_sector_offset +
+ temp_record.length[0] - temp->su_tail_size,
+ SEEK_SET) == -1)
+ err(1, "fseeko");
+ if (temp->su_tail_size > 0)
+ fwrite(temp->su_tail_data, 1,
+ temp->su_tail_size, fd);
+ if (ferror(fd)) {
+ warnx("%s: write error", __func__);
+ goto out;
+ }
+ cur_sector_offset += temp_record.length[0];
+
+ }
+
+ /*
+ * Recurse on children.
+ */
+ TAILQ_FOREACH(temp, &writenode->cn_children, cn_next_child) {
+ if ((ret = cd9660_write_file(fd, temp)) == 0)
+ goto out;
+ }
+ }
+ rv = 1;
+out:
+ free(temp_file_name);
+ free(buf);
+ return rv;
+}
+
+/*
+ * Wrapper function to write a buffer (one sector) to disk.
+ * Seeks and writes the buffer.
+ * NOTE: You dont NEED to use this function, but it might make your
+ * life easier if you have to write things that align to a sector
+ * (such as volume descriptors).
+ *
+ * @param int fd Valid file descriptor
+ * @param int sector Sector number to write to
+ * @param const unsigned char* Buffer to write. This should be the
+ * size of a sector, and if only a portion
+ * is written, the rest should be set to 0.
+ */
+static int
+cd9660_write_filedata(FILE *fd, off_t sector, const unsigned char *buf,
+ int numsecs)
+{
+ off_t curpos;
+ size_t success;
+
+ curpos = ftello(fd);
+
+ if (fseeko(fd, sector * diskStructure.sectorSize, SEEK_SET) == -1)
+ err(1, "fseeko");
+
+ success = fwrite(buf, diskStructure.sectorSize * numsecs, 1, fd);
+
+ if (fseeko(fd, curpos, SEEK_SET) == -1)
+ err(1, "fseeko");
+
+ if (success == 1)
+ success = diskStructure.sectorSize * numsecs;
+ return success;
+}
+
+#if 0
+static int
+cd9660_write_buffered(FILE *fd, off_t offset, int buff_len,
+ const unsigned char* buffer)
+{
+ static int working_sector = -1;
+ static char buf[CD9660_SECTOR_SIZE];
+
+ return 0;
+}
+#endif
+
+int
+cd9660_copy_file(FILE *fd, off_t start_sector, const char *filename)
+{
+ FILE *rf;
+ int bytes_read;
+ off_t sector = start_sector;
+ int buf_size = diskStructure.sectorSize;
+ char *buf;
+
+ buf = malloc(buf_size);
+ if (buf == NULL)
+ err(EXIT_FAILURE, "%s: malloc", __func__);
+
+ if ((rf = fopen(filename, "rb")) == NULL) {
+ warn("%s: cannot open %s", __func__, filename);
+ free(buf);
+ return 0;
+ }
+
+ if (diskStructure.verbose_level > 1)
+ printf("Writing file: %s\n",filename);
+
+ if (fseeko(fd, start_sector * diskStructure.sectorSize, SEEK_SET) == -1)
+ err(1, "fseeko");
+
+ while (!feof(rf)) {
+ bytes_read = fread(buf,1,buf_size,rf);
+ if (ferror(rf)) {
+ warn("%s: fread", __func__);
+ free(buf);
+ (void)fclose(rf);
+ return 0;
+ }
+
+ fwrite(buf,1,bytes_read,fd);
+ if (ferror(fd)) {
+ warn("%s: fwrite", __func__);
+ free(buf);
+ (void)fclose(rf);
+ return 0;
+ }
+ sector++;
+ }
+
+ fclose(rf);
+ free(buf);
+ return 1;
+}
+
+static void
+cd9660_write_rr(FILE *fd, cd9660node *writenode, off_t offset, off_t sector)
+{
+ int in_ca = 0;
+ struct ISO_SUSP_ATTRIBUTES *myattr;
+
+ offset += writenode->isoDirRecord->length[0];
+ if (fseeko(fd, sector * diskStructure.sectorSize + offset, SEEK_SET) ==
+ -1)
+ err(1, "fseeko");
+ /* Offset now points at the end of the record */
+ TAILQ_FOREACH(myattr, &writenode->head, rr_ll) {
+ fwrite(&(myattr->attr), CD9660_SUSP_ENTRY_SIZE(myattr), 1, fd);
+
+ if (!in_ca) {
+ offset += CD9660_SUSP_ENTRY_SIZE(myattr);
+ if (myattr->last_in_suf) {
+ /*
+ * Point the offset to the start of this
+ * record's CE area
+ */
+ if (fseeko(fd, ((off_t)diskStructure.
+ susp_continuation_area_start_sector *
+ diskStructure.sectorSize)
+ + writenode->susp_entry_ce_start,
+ SEEK_SET) == -1)
+ err(1, "fseeko");
+ in_ca = 1;
+ }
+ }
+ }
+
+ /*
+ * If we had to go to the continuation area, head back to
+ * where we should be.
+ */
+ if (in_ca)
+ if (fseeko(fd, sector * diskStructure.sectorSize + offset,
+ SEEK_SET) == -1)
+ err(1, "fseeko");
+}
diff --git a/usr.sbin/makefs/cd9660/iso9660_rrip.c b/usr.sbin/makefs/cd9660/iso9660_rrip.c
new file mode 100644
index 0000000..749747b
--- /dev/null
+++ b/usr.sbin/makefs/cd9660/iso9660_rrip.c
@@ -0,0 +1,838 @@
+/* $NetBSD: iso9660_rrip.c,v 1.14 2014/05/30 13:14:47 martin Exp $ */
+
+/*
+ * Copyright (c) 2005 Daniel Watt, Walter Deignan, Ryan Gabrys, Alan
+ * Perez-Rathke and Ram Vedam. All rights reserved.
+ *
+ * This code was written by Daniel Watt, Walter Deignan, Ryan Gabrys,
+ * Alan Perez-Rathke and Ram Vedam.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE,DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (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 will hold all the function definitions
+ * defined in iso9660_rrip.h
+ */
+
+#include "makefs.h"
+#include "cd9660.h"
+#include "iso9660_rrip.h"
+#include <sys/queue.h>
+#include <stdio.h>
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+static void cd9660_rrip_initialize_inode(cd9660node *);
+static int cd9660_susp_handle_continuation(cd9660node *);
+static int cd9660_susp_handle_continuation_common(cd9660node *, int);
+
+int
+cd9660_susp_initialize(cd9660node *node, cd9660node *parent,
+ cd9660node *grandparent)
+{
+ cd9660node *cn;
+ int r;
+
+ /* Make sure the node is not NULL. If it is, there are major problems */
+ assert(node != NULL);
+
+ if (!(node->type & CD9660_TYPE_DOT) &&
+ !(node->type & CD9660_TYPE_DOTDOT))
+ TAILQ_INIT(&(node->head));
+ if (node->dot_record != 0)
+ TAILQ_INIT(&(node->dot_record->head));
+ if (node->dot_dot_record != 0)
+ TAILQ_INIT(&(node->dot_dot_record->head));
+
+ /* SUSP specific entries here */
+ if ((r = cd9660_susp_initialize_node(node)) < 0)
+ return r;
+
+ /* currently called cd9660node_rrip_init_links */
+ r = cd9660_rrip_initialize_node(node, parent, grandparent);
+ if (r < 0)
+ return r;
+
+ /*
+ * See if we need a CE record, and set all of the
+ * associated counters.
+ *
+ * This should be called after all extensions. After
+ * this is called, no new records should be added.
+ */
+ if ((r = cd9660_susp_handle_continuation(node)) < 0)
+ return r;
+
+ /* Recurse on children. */
+ TAILQ_FOREACH(cn, &node->cn_children, cn_next_child) {
+ if ((r = cd9660_susp_initialize(cn, node, parent)) < 0)
+ return 0;
+ }
+ return 1;
+}
+
+int
+cd9660_susp_finalize(cd9660node *node)
+{
+ cd9660node *temp;
+ int r;
+
+ assert(node != NULL);
+
+ if (node == diskStructure.rootNode)
+ diskStructure.susp_continuation_area_current_free = 0;
+
+ if ((r = cd9660_susp_finalize_node(node)) < 0)
+ return r;
+ if ((r = cd9660_rrip_finalize_node(node)) < 0)
+ return r;
+
+ TAILQ_FOREACH(temp, &node->cn_children, cn_next_child) {
+ if ((r = cd9660_susp_finalize(temp)) < 0)
+ return r;
+ }
+ return 1;
+}
+
+/*
+ * If we really wanted to speed things up, we could have some sort of
+ * lookup table on the SUSP entry type that calls a functor. Or, we could
+ * combine the functions. These functions are kept separate to allow
+ * easier addition of other extensions.
+
+ * For the sake of simplicity and clarity, we won't be doing that for now.
+ */
+
+/*
+ * SUSP needs to update the following types:
+ * CE (continuation area)
+ */
+int
+cd9660_susp_finalize_node(cd9660node *node)
+{
+ struct ISO_SUSP_ATTRIBUTES *t;
+
+ /* Handle CE counters */
+ if (node->susp_entry_ce_length > 0) {
+ node->susp_entry_ce_start =
+ diskStructure.susp_continuation_area_current_free;
+ diskStructure.susp_continuation_area_current_free +=
+ node->susp_entry_ce_length;
+ }
+
+ TAILQ_FOREACH(t, &node->head, rr_ll) {
+ if (t->susp_type != SUSP_TYPE_SUSP ||
+ t->entry_type != SUSP_ENTRY_SUSP_CE)
+ continue;
+ cd9660_bothendian_dword(
+ diskStructure.
+ susp_continuation_area_start_sector,
+ t->attr.su_entry.CE.ca_sector);
+
+ cd9660_bothendian_dword(
+ diskStructure.
+ susp_continuation_area_start_sector,
+ t->attr.su_entry.CE.ca_sector);
+ cd9660_bothendian_dword(node->susp_entry_ce_start,
+ t->attr.su_entry.CE.offset);
+ cd9660_bothendian_dword(node->susp_entry_ce_length,
+ t->attr.su_entry.CE.length);
+ }
+ return 0;
+}
+
+int
+cd9660_rrip_finalize_node(cd9660node *node)
+{
+ struct ISO_SUSP_ATTRIBUTES *t;
+
+ TAILQ_FOREACH(t, &node->head, rr_ll) {
+ if (t->susp_type != SUSP_TYPE_RRIP)
+ continue;
+ switch (t->entry_type) {
+ case SUSP_ENTRY_RRIP_CL:
+ /* Look at rr_relocated*/
+ if (node->rr_relocated == NULL)
+ return -1;
+ cd9660_bothendian_dword(
+ node->rr_relocated->fileDataSector,
+ (unsigned char *)
+ t->attr.rr_entry.CL.dir_loc);
+ break;
+ case SUSP_ENTRY_RRIP_PL:
+ /* Look at rr_real_parent */
+ if (node->parent == NULL ||
+ node->parent->rr_real_parent == NULL)
+ return -1;
+ cd9660_bothendian_dword(
+ node->parent->rr_real_parent->fileDataSector,
+ (unsigned char *)
+ t->attr.rr_entry.PL.dir_loc);
+ break;
+ }
+ }
+ return 0;
+}
+
+static int
+cd9660_susp_handle_continuation_common(cd9660node *node, int space)
+{
+ int ca_used, susp_used, susp_used_pre_ce, working;
+ struct ISO_SUSP_ATTRIBUTES *temp, *pre_ce, *last, *CE, *ST;
+
+ pre_ce = last = NULL;
+ working = 254 - space;
+ if (node->su_tail_size > 0)
+ /* Allow 4 bytes for "ST" record. */
+ working -= node->su_tail_size + 4;
+ /* printf("There are %i bytes to work with\n",working); */
+
+ susp_used_pre_ce = susp_used = 0;
+ ca_used = 0;
+ TAILQ_FOREACH(temp, &node->head, rr_ll) {
+ if (working < 0)
+ break;
+ /*
+ * printf("SUSP Entry found, length is %i\n",
+ * CD9660_SUSP_ENTRY_SIZE(temp));
+ */
+ working -= CD9660_SUSP_ENTRY_SIZE(temp);
+ if (working >= 0) {
+ last = temp;
+ susp_used += CD9660_SUSP_ENTRY_SIZE(temp);
+ }
+ if (working >= 28) {
+ /*
+ * Remember the last entry after which we
+ * could insert a "CE" entry.
+ */
+ pre_ce = last;
+ susp_used_pre_ce = susp_used;
+ }
+ }
+
+ /* A CE entry is needed */
+ if (working <= 0) {
+ CE = cd9660node_susp_create_node(SUSP_TYPE_SUSP,
+ SUSP_ENTRY_SUSP_CE, "CE", SUSP_LOC_ENTRY);
+ cd9660_susp_ce(CE, node);
+ /* This will automatically insert at the appropriate location */
+ if (pre_ce != NULL)
+ TAILQ_INSERT_AFTER(&node->head, pre_ce, CE, rr_ll);
+ else
+ TAILQ_INSERT_HEAD(&node->head, CE, rr_ll);
+ last = CE;
+ susp_used = susp_used_pre_ce + 28;
+ /* Count how much CA data is necessary */
+ for (temp = TAILQ_NEXT(last, rr_ll); temp != NULL;
+ temp = TAILQ_NEXT(temp, rr_ll)) {
+ ca_used += CD9660_SUSP_ENTRY_SIZE(temp);
+ }
+ }
+
+ /* An ST entry is needed */
+ if (node->su_tail_size > 0) {
+ ST = cd9660node_susp_create_node(SUSP_TYPE_SUSP,
+ SUSP_ENTRY_SUSP_ST, "ST", SUSP_LOC_ENTRY);
+ cd9660_susp_st(ST, node);
+ if (last != NULL)
+ TAILQ_INSERT_AFTER(&node->head, last, ST, rr_ll);
+ else
+ TAILQ_INSERT_HEAD(&node->head, ST, rr_ll);
+ last = ST;
+ susp_used += 4;
+ }
+ if (last != NULL)
+ last->last_in_suf = 1;
+
+ node->susp_entry_size = susp_used;
+ node->susp_entry_ce_length = ca_used;
+
+ diskStructure.susp_continuation_area_size += ca_used;
+ return 1;
+}
+
+/* See if a continuation entry is needed for each of the different types */
+static int
+cd9660_susp_handle_continuation(cd9660node *node)
+{
+ assert (node != NULL);
+
+ /* Entry */
+ if (cd9660_susp_handle_continuation_common(
+ node,(int)(node->isoDirRecord->length[0])) < 0)
+ return 0;
+
+ return 1;
+}
+
+int
+cd9660_susp_initialize_node(cd9660node *node)
+{
+ struct ISO_SUSP_ATTRIBUTES *temp;
+
+ /*
+ * Requirements/notes:
+ * CE: is added for us where needed
+ * ST: not sure if it is even required, but if so, should be
+ * handled by the CE code
+ * PD: isn't needed (though might be added for testing)
+ * SP: is stored ONLY on the . record of the root directory
+ * ES: not sure
+ */
+
+ /* Check for root directory, add SP and ER if needed. */
+ if (node->type & CD9660_TYPE_DOT) {
+ if (node->parent == diskStructure.rootNode) {
+ temp = cd9660node_susp_create_node(SUSP_TYPE_SUSP,
+ SUSP_ENTRY_SUSP_SP, "SP", SUSP_LOC_DOT);
+ cd9660_susp_sp(temp, node);
+
+ /* Should be first entry. */
+ TAILQ_INSERT_HEAD(&node->head, temp, rr_ll);
+ }
+ }
+ return 1;
+}
+
+static void
+cd9660_rrip_initialize_inode(cd9660node *node)
+{
+ struct ISO_SUSP_ATTRIBUTES *attr;
+
+ /*
+ * Inode dependent values - this may change,
+ * but for now virtual files and directories do
+ * not have an inode structure
+ */
+
+ if ((node->node != NULL) && (node->node->inode != NULL)) {
+ /* PX - POSIX attributes */
+ attr = cd9660node_susp_create_node(SUSP_TYPE_RRIP,
+ SUSP_ENTRY_RRIP_PX, "PX", SUSP_LOC_ENTRY);
+ cd9660node_rrip_px(attr, node->node);
+
+ TAILQ_INSERT_TAIL(&node->head, attr, rr_ll);
+
+ /* TF - timestamp */
+ attr = cd9660node_susp_create_node(SUSP_TYPE_RRIP,
+ SUSP_ENTRY_RRIP_TF, "TF", SUSP_LOC_ENTRY);
+ cd9660node_rrip_tf(attr, node->node);
+ TAILQ_INSERT_TAIL(&node->head, attr, rr_ll);
+
+ /* SL - Symbolic link */
+ /* ?????????? Dan - why is this here? */
+ if (TAILQ_EMPTY(&node->cn_children) &&
+ node->node->inode != NULL &&
+ S_ISLNK(node->node->inode->st.st_mode))
+ cd9660_createSL(node);
+
+ /* PN - device number */
+ if (node->node->inode != NULL &&
+ ((S_ISCHR(node->node->inode->st.st_mode) ||
+ S_ISBLK(node->node->inode->st.st_mode)))) {
+ attr =
+ cd9660node_susp_create_node(SUSP_TYPE_RRIP,
+ SUSP_ENTRY_RRIP_PN, "PN",
+ SUSP_LOC_ENTRY);
+ cd9660node_rrip_pn(attr, node->node);
+ TAILQ_INSERT_TAIL(&node->head, attr, rr_ll);
+ }
+ }
+}
+
+int
+cd9660_rrip_initialize_node(cd9660node *node, cd9660node *parent,
+ cd9660node *grandparent)
+{
+ struct ISO_SUSP_ATTRIBUTES *current = NULL;
+
+ assert(node != NULL);
+
+ if (node->type & CD9660_TYPE_DOT) {
+ /*
+ * Handle ER - should be the only entry to appear on
+ * a "." record
+ */
+ if (node->parent == diskStructure.rootNode) {
+ cd9660_susp_ER(node, 1, SUSP_RRIP_ER_EXT_ID,
+ SUSP_RRIP_ER_EXT_DES, SUSP_RRIP_ER_EXT_SRC);
+ }
+ if (parent != NULL && parent->node != NULL &&
+ parent->node->inode != NULL) {
+ /* PX - POSIX attributes */
+ current = cd9660node_susp_create_node(SUSP_TYPE_RRIP,
+ SUSP_ENTRY_RRIP_PX, "PX", SUSP_LOC_ENTRY);
+ cd9660node_rrip_px(current, parent->node);
+ TAILQ_INSERT_TAIL(&node->head, current, rr_ll);
+ }
+ } else if (node->type & CD9660_TYPE_DOTDOT) {
+ if (grandparent != NULL && grandparent->node != NULL &&
+ grandparent->node->inode != NULL) {
+ /* PX - POSIX attributes */
+ current = cd9660node_susp_create_node(SUSP_TYPE_RRIP,
+ SUSP_ENTRY_RRIP_PX, "PX", SUSP_LOC_ENTRY);
+ cd9660node_rrip_px(current, grandparent->node);
+ TAILQ_INSERT_TAIL(&node->head, current, rr_ll);
+ }
+ /* Handle PL */
+ if (parent != NULL && parent->rr_real_parent != NULL) {
+ current = cd9660node_susp_create_node(SUSP_TYPE_RRIP,
+ SUSP_ENTRY_RRIP_PL, "PL", SUSP_LOC_DOTDOT);
+ cd9660_rrip_PL(current,node);
+ TAILQ_INSERT_TAIL(&node->head, current, rr_ll);
+ }
+ } else {
+ cd9660_rrip_initialize_inode(node);
+
+ /*
+ * Not every node needs a NM set - only if the name is
+ * actually different. IE: If a file is TEST -> TEST,
+ * no NM. test -> TEST, need a NM
+ *
+ * The rr_moved_dir needs to be assigned a NM record as well.
+ */
+ if (node == diskStructure.rr_moved_dir) {
+ cd9660_rrip_add_NM(node, RRIP_DEFAULT_MOVE_DIR_NAME);
+ }
+ else if ((node->node != NULL) &&
+ ((strlen(node->node->name) !=
+ (uint8_t)node->isoDirRecord->name_len[0]) ||
+ (memcmp(node->node->name,node->isoDirRecord->name,
+ (uint8_t)node->isoDirRecord->name_len[0]) != 0))) {
+ cd9660_rrip_NM(node);
+ }
+
+
+
+ /* Rock ridge directory relocation code here. */
+
+ /* First handle the CL for the placeholder file. */
+ if (node->rr_relocated != NULL) {
+ current = cd9660node_susp_create_node(SUSP_TYPE_RRIP,
+ SUSP_ENTRY_RRIP_CL, "CL", SUSP_LOC_ENTRY);
+ cd9660_rrip_CL(current, node);
+ TAILQ_INSERT_TAIL(&node->head, current, rr_ll);
+ }
+
+ /* Handle RE*/
+ if (node->rr_real_parent != NULL) {
+ current = cd9660node_susp_create_node(SUSP_TYPE_RRIP,
+ SUSP_ENTRY_RRIP_RE, "RE", SUSP_LOC_ENTRY);
+ cd9660_rrip_RE(current,node);
+ TAILQ_INSERT_TAIL(&node->head, current, rr_ll);
+ }
+ }
+ return 1;
+}
+
+struct ISO_SUSP_ATTRIBUTES*
+cd9660node_susp_create_node(int susp_type, int entry_type, const char *type_id,
+ int write_loc)
+{
+ struct ISO_SUSP_ATTRIBUTES* temp;
+
+ if ((temp = malloc(sizeof(struct ISO_SUSP_ATTRIBUTES))) == NULL) {
+ CD9660_MEM_ALLOC_ERROR("cd9660node_susp_create_node");
+ exit(1);
+ }
+
+ temp->susp_type = susp_type;
+ temp->entry_type = entry_type;
+ temp->last_in_suf = 0;
+ /* Phase this out */
+ temp->type_of[0] = type_id[0];
+ temp->type_of[1] = type_id[1];
+ temp->write_location = write_loc;
+
+ /*
+ * Since the first four bytes is common, lets go ahead and
+ * set the type identifier, since we are passing that to this
+ * function anyhow.
+ */
+ temp->attr.su_entry.SP.h.type[0] = type_id[0];
+ temp->attr.su_entry.SP.h.type[1] = type_id[1];
+ return temp;
+}
+
+int
+cd9660_rrip_PL(struct ISO_SUSP_ATTRIBUTES* p, cd9660node *node __unused)
+{
+ p->attr.rr_entry.PL.h.length[0] = 12;
+ p->attr.rr_entry.PL.h.version[0] = 1;
+ return 1;
+}
+
+int
+cd9660_rrip_CL(struct ISO_SUSP_ATTRIBUTES *p, cd9660node *node __unused)
+{
+ p->attr.rr_entry.CL.h.length[0] = 12;
+ p->attr.rr_entry.CL.h.version[0] = 1;
+ return 1;
+}
+
+int
+cd9660_rrip_RE(struct ISO_SUSP_ATTRIBUTES *p, cd9660node *node __unused)
+{
+ p->attr.rr_entry.RE.h.length[0] = 4;
+ p->attr.rr_entry.RE.h.version[0] = 1;
+ return 1;
+}
+
+void
+cd9660_createSL(cd9660node *node)
+{
+ struct ISO_SUSP_ATTRIBUTES* current;
+ int path_count, dir_count, done, i, j, dir_copied;
+ char temp_cr[255];
+ char temp_sl[255]; /* used in copying continuation entry*/
+ char* sl_ptr;
+
+ sl_ptr = node->node->symlink;
+
+ done = 0;
+ path_count = 0;
+ dir_count = 0;
+ dir_copied = 0;
+ current = cd9660node_susp_create_node(SUSP_TYPE_RRIP,
+ SUSP_ENTRY_RRIP_SL, "SL", SUSP_LOC_ENTRY);
+
+ current->attr.rr_entry.SL.h.version[0] = 1;
+ current->attr.rr_entry.SL.flags[0] = SL_FLAGS_NONE;
+
+ if (*sl_ptr == '/') {
+ temp_cr[0] = SL_FLAGS_ROOT;
+ temp_cr[1] = 0;
+ memcpy(current->attr.rr_entry.SL.component + path_count,
+ temp_cr, 2);
+ path_count += 2;
+ sl_ptr++;
+ }
+
+ for (i = 0; i < (dir_count + 2); i++)
+ temp_cr[i] = '\0';
+
+ while (!done) {
+ while ((*sl_ptr != '/') && (*sl_ptr != '\0')) {
+ dir_copied = 1;
+ if (*sl_ptr == '.') {
+ if ((*(sl_ptr + 1) == '/') || (*(sl_ptr + 1)
+ == '\0')) {
+ temp_cr[0] = SL_FLAGS_CURRENT;
+ sl_ptr++;
+ } else if(*(sl_ptr + 1) == '.') {
+ if ((*(sl_ptr + 2) == '/') ||
+ (*(sl_ptr + 2) == '\0')) {
+ temp_cr[0] = SL_FLAGS_PARENT;
+ sl_ptr += 2;
+ }
+ } else {
+ temp_cr[dir_count+2] = *sl_ptr;
+ sl_ptr++;
+ dir_count++;
+ }
+ } else {
+ temp_cr[dir_count + 2] = *sl_ptr;
+ sl_ptr++;
+ dir_count++;
+ }
+ }
+
+ if ((path_count + dir_count) >= 249) {
+ current->attr.rr_entry.SL.flags[0] |= SL_FLAGS_CONTINUE;
+
+ j = 0;
+
+ if (path_count <= 249) {
+ while(j != (249 - path_count)) {
+ temp_sl[j] = temp_cr[j];
+ j++;
+ }
+ temp_sl[0] = SL_FLAGS_CONTINUE;
+ temp_sl[1] = j - 2;
+ memcpy(
+ current->attr.rr_entry.SL.component +
+ path_count,
+ temp_sl, j);
+ }
+
+ path_count += j;
+ current->attr.rr_entry.SL.h.length[0] = path_count + 5;
+ TAILQ_INSERT_TAIL(&node->head, current, rr_ll);
+ current= cd9660node_susp_create_node(SUSP_TYPE_RRIP,
+ SUSP_ENTRY_RRIP_SL, "SL", SUSP_LOC_ENTRY);
+ current->attr.rr_entry.SL.h.version[0] = 1;
+ current->attr.rr_entry.SL.flags[0] = SL_FLAGS_NONE;
+
+ path_count = 0;
+
+ if (dir_count > 2) {
+ while (j != dir_count + 2) {
+ current->attr.rr_entry.SL.component[
+ path_count + 2] = temp_cr[j];
+ j++;
+ path_count++;
+ }
+ current->attr.rr_entry.SL.component[1]
+ = path_count;
+ path_count+= 2;
+ } else {
+ while(j != dir_count) {
+ current->attr.rr_entry.SL.component[
+ path_count+2] = temp_cr[j];
+ j++;
+ path_count++;
+ }
+ }
+ } else {
+ if (dir_copied == 1) {
+ temp_cr[1] = dir_count;
+ memcpy(current->attr.rr_entry.SL.component +
+ path_count,
+ temp_cr, dir_count + 2);
+ path_count += dir_count + 2;
+ }
+ }
+
+ if (*sl_ptr == '\0') {
+ done = 1;
+ current->attr.rr_entry.SL.h.length[0] = path_count + 5;
+ TAILQ_INSERT_TAIL(&node->head, current, rr_ll);
+ } else {
+ sl_ptr++;
+ dir_count = 0;
+ dir_copied = 0;
+ for(i = 0; i < 255; i++) {
+ temp_cr[i] = '\0';
+ }
+ }
+ }
+}
+
+int
+cd9660node_rrip_px(struct ISO_SUSP_ATTRIBUTES *v, fsnode *pxinfo)
+{
+ v->attr.rr_entry.PX.h.length[0] = 44;
+ v->attr.rr_entry.PX.h.version[0] = 1;
+ cd9660_bothendian_dword(pxinfo->inode->st.st_mode,
+ v->attr.rr_entry.PX.mode);
+ cd9660_bothendian_dword(pxinfo->inode->st.st_nlink,
+ v->attr.rr_entry.PX.links);
+ cd9660_bothendian_dword(pxinfo->inode->st.st_uid,
+ v->attr.rr_entry.PX.uid);
+ cd9660_bothendian_dword(pxinfo->inode->st.st_gid,
+ v->attr.rr_entry.PX.gid);
+ cd9660_bothendian_dword(pxinfo->inode->st.st_ino,
+ v->attr.rr_entry.PX.serial);
+
+ return 1;
+}
+
+int
+cd9660node_rrip_pn(struct ISO_SUSP_ATTRIBUTES *pn_field, fsnode *fnode)
+{
+ pn_field->attr.rr_entry.PN.h.length[0] = 20;
+ pn_field->attr.rr_entry.PN.h.version[0] = 1;
+
+ if (sizeof (fnode->inode->st.st_rdev) > 4)
+ cd9660_bothendian_dword(
+ (uint64_t)fnode->inode->st.st_rdev >> 32,
+ pn_field->attr.rr_entry.PN.high);
+ else
+ cd9660_bothendian_dword(0, pn_field->attr.rr_entry.PN.high);
+
+ cd9660_bothendian_dword(fnode->inode->st.st_rdev & 0xffffffff,
+ pn_field->attr.rr_entry.PN.low);
+ return 1;
+}
+
+#if 0
+int
+cd9660node_rrip_nm(struct ISO_SUSP_ATTRIBUTES *p, cd9660node *file_node)
+{
+ int nm_length = strlen(file_node->isoDirRecord->name) + 5;
+ p->attr.rr_entry.NM.h.type[0] = 'N';
+ p->attr.rr_entry.NM.h.type[1] = 'M';
+ sprintf(p->attr.rr_entry.NM.altname, "%s", file_node->isoDirRecord->name);
+ p->attr.rr_entry.NM.h.length[0] = (unsigned char)nm_length;
+ p->attr.rr_entry.NM.h.version[0] = (unsigned char)1;
+ p->attr.rr_entry.NM.flags[0] = (unsigned char) NM_PARENT;
+ return 1;
+}
+#endif
+
+int
+cd9660node_rrip_tf(struct ISO_SUSP_ATTRIBUTES *p, fsnode *_node)
+{
+ p->attr.rr_entry.TF.flags[0] = TF_MODIFY | TF_ACCESS | TF_ATTRIBUTES;
+ p->attr.rr_entry.TF.h.length[0] = 5;
+ p->attr.rr_entry.TF.h.version[0] = 1;
+
+ /*
+ * Need to add creation time, backup time,
+ * expiration time, and effective time.
+ */
+
+ cd9660_time_915(p->attr.rr_entry.TF.timestamp,
+ _node->inode->st.st_atime);
+ p->attr.rr_entry.TF.h.length[0] += 7;
+
+ cd9660_time_915(p->attr.rr_entry.TF.timestamp + 7,
+ _node->inode->st.st_mtime);
+ p->attr.rr_entry.TF.h.length[0] += 7;
+
+ cd9660_time_915(p->attr.rr_entry.TF.timestamp + 14,
+ _node->inode->st.st_ctime);
+ p->attr.rr_entry.TF.h.length[0] += 7;
+ return 1;
+}
+
+int
+cd9660_susp_sp(struct ISO_SUSP_ATTRIBUTES *p, cd9660node *spinfo __unused)
+{
+ p->attr.su_entry.SP.h.length[0] = 7;
+ p->attr.su_entry.SP.h.version[0] = 1;
+ p->attr.su_entry.SP.check[0] = 0xBE;
+ p->attr.su_entry.SP.check[1] = 0xEF;
+ p->attr.su_entry.SP.len_skp[0] = 0;
+ return 1;
+}
+
+int
+cd9660_susp_st(struct ISO_SUSP_ATTRIBUTES *p, cd9660node *stinfo __unused)
+{
+ p->attr.su_entry.ST.h.type[0] = 'S';
+ p->attr.su_entry.ST.h.type[1] = 'T';
+ p->attr.su_entry.ST.h.length[0] = 4;
+ p->attr.su_entry.ST.h.version[0] = 1;
+ return 1;
+}
+
+int
+cd9660_susp_ce(struct ISO_SUSP_ATTRIBUTES *p, cd9660node *spinfo __unused)
+{
+ p->attr.su_entry.CE.h.length[0] = 28;
+ p->attr.su_entry.CE.h.version[0] = 1;
+ /* Other attributes dont matter right now, will be updated later */
+ return 1;
+}
+
+int
+cd9660_susp_pd(struct ISO_SUSP_ATTRIBUTES *p __unused, int length __unused)
+{
+ return 1;
+}
+
+void
+cd9660_rrip_add_NM(cd9660node *node, const char *name)
+{
+ int working,len;
+ const char *p;
+ struct ISO_SUSP_ATTRIBUTES *r;
+
+ /*
+ * Each NM record has 254 byes to work with. This means that
+ * the name data itself only has 249 bytes to work with. So, a
+ * name with 251 characters would require two nm records.
+ */
+ p = name;
+ working = 1;
+ while (working) {
+ r = cd9660node_susp_create_node(SUSP_TYPE_RRIP,
+ SUSP_ENTRY_RRIP_NM, "NM", SUSP_LOC_ENTRY);
+ r->attr.rr_entry.NM.h.version[0] = 1;
+ r->attr.rr_entry.NM.flags[0] = RRIP_NM_FLAGS_NONE;
+ len = strlen(p);
+
+ if (len > 249) {
+ len = 249;
+ r->attr.rr_entry.NM.flags[0] = RRIP_NM_FLAGS_CONTINUE;
+ } else {
+ working = 0;
+ }
+ memcpy(r->attr.rr_entry.NM.altname, p, len);
+ r->attr.rr_entry.NM.h.length[0] = 5 + len;
+
+ TAILQ_INSERT_TAIL(&node->head, r, rr_ll);
+
+ p += len;
+ }
+}
+
+void
+cd9660_rrip_NM(cd9660node *node)
+{
+ cd9660_rrip_add_NM(node, node->node->name);
+}
+
+struct ISO_SUSP_ATTRIBUTES*
+cd9660_susp_ER(cd9660node *node,
+ u_char ext_version, const char* ext_id, const char* ext_des,
+ const char* ext_src)
+{
+ int l;
+ struct ISO_SUSP_ATTRIBUTES *r;
+
+ r = cd9660node_susp_create_node(SUSP_TYPE_SUSP,
+ SUSP_ENTRY_SUSP_ER, "ER", SUSP_LOC_DOT);
+
+ /* Fixed data is 8 bytes */
+ r->attr.su_entry.ER.h.length[0] = 8;
+ r->attr.su_entry.ER.h.version[0] = 1;
+
+ r->attr.su_entry.ER.len_id[0] = (u_char)strlen(ext_id);
+ r->attr.su_entry.ER.len_des[0] = (u_char)strlen(ext_des);
+ r->attr.su_entry.ER.len_src[0] = (u_char)strlen(ext_src);
+
+ l = r->attr.su_entry.ER.len_id[0] +
+ r->attr.su_entry.ER.len_src[0] +
+ r->attr.su_entry.ER.len_des[0];
+
+ /* Everything must fit. */
+ assert(l + r->attr.su_entry.ER.h.length[0] <= 254);
+
+ r->attr.su_entry.ER.h.length[0] += (u_char)l;
+
+
+ r->attr.su_entry.ER.ext_ver[0] = ext_version;
+ memcpy(r->attr.su_entry.ER.ext_data, ext_id,
+ (int)r->attr.su_entry.ER.len_id[0]);
+ l = (int) r->attr.su_entry.ER.len_id[0];
+ memcpy(r->attr.su_entry.ER.ext_data + l,ext_des,
+ (int)r->attr.su_entry.ER.len_des[0]);
+
+ l += (int)r->attr.su_entry.ER.len_des[0];
+ memcpy(r->attr.su_entry.ER.ext_data + l,ext_src,
+ (int)r->attr.su_entry.ER.len_src[0]);
+
+ TAILQ_INSERT_TAIL(&node->head, r, rr_ll);
+ return r;
+}
+
+struct ISO_SUSP_ATTRIBUTES*
+cd9660_susp_ES(struct ISO_SUSP_ATTRIBUTES *last __unused, cd9660node *node __unused)
+{
+ return NULL;
+}
diff --git a/usr.sbin/makefs/cd9660/iso9660_rrip.h b/usr.sbin/makefs/cd9660/iso9660_rrip.h
new file mode 100644
index 0000000..3570301
--- /dev/null
+++ b/usr.sbin/makefs/cd9660/iso9660_rrip.h
@@ -0,0 +1,290 @@
+/* $NetBSD: iso9660_rrip.h,v 1.5 2009/01/10 22:06:29 bjh21 Exp $ */
+
+/*
+ * Copyright (c) 2005 Daniel Watt, Walter Deignan, Ryan Gabrys, Alan
+ * Perez-Rathke and Ram Vedam. All rights reserved.
+ *
+ * This code was written by Daniel Watt, Walter Deignan, Ryan Gabrys,
+ * Alan Perez-Rathke and Ram Vedam.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL DANIEL WATT, WALTER DEIGNAN, RYAN
+ * GABRYS, ALAN PEREZ-RATHKE AND RAM VEDAM BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE,DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (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 __ISO9660_RRIP_H__
+#define __ISO9660_RRIP_H__
+
+/*
+ * This will hold all the functions needed to
+ * write an ISO 9660 image with Rock Ridge Extensions
+ */
+
+/* For writing must use ISO_RRIP_EXTREF structure */
+
+#include "makefs.h"
+#include <cd9660_rrip.h>
+#include "cd9660.h"
+#include <sys/queue.h>
+
+#define PX_LENGTH 0x2C
+#define PN_LENGTH 0x14
+#define TF_CREATION 0x00
+#define TF_MODIFY 0x01
+#define TF_ACCESS 0x02
+#define TF_ATTRIBUTES 0x04
+#define TF_BACKUP 0x08
+#define TF_EXPIRATION 0x10
+#define TF_EFFECTIVE 0x20
+#define TF_LONGFORM 0x40
+#define NM_CONTINUE 0x80
+#define NM_CURRENT 0x100
+#define NM_PARENT 0x200
+
+
+#define SUSP_LOC_ENTRY 0x01
+#define SUSP_LOC_DOT 0x02
+#define SUSP_LOC_DOTDOT 0x04
+
+#define SUSP_TYPE_SUSP 1
+#define SUSP_TYPE_RRIP 2
+
+#define SUSP_ENTRY_SUSP_CE 1
+#define SUSP_ENTRY_SUSP_PD 2
+#define SUSP_ENTRY_SUSP_SP 3
+#define SUSP_ENTRY_SUSP_ST 4
+#define SUSP_ENTRY_SUSP_ER 5
+#define SUSP_ENTRY_SUSP_ES 6
+
+#define SUSP_ENTRY_RRIP_PX 1
+#define SUSP_ENTRY_RRIP_PN 2
+#define SUSP_ENTRY_RRIP_SL 3
+#define SUSP_ENTRY_RRIP_NM 4
+#define SUSP_ENTRY_RRIP_CL 5
+#define SUSP_ENTRY_RRIP_PL 6
+#define SUSP_ENTRY_RRIP_RE 7
+#define SUSP_ENTRY_RRIP_TF 8
+#define SUSP_ENTRY_RRIP_SF 9
+
+#define SUSP_RRIP_ER_EXT_ID "IEEE_P1282"
+#define SUSP_RRIP_ER_EXT_DES "THE IEEE P1282 PROTOCOL PROVIDES SUPPORT FOR POSIX FILE SYSTEM SEMANTICS."
+#define SUSP_RRIP_ER_EXT_SRC "PLEASE CONTACT THE IEEE STANDARDS DEPARTMENT, PISCATAWAY, NJ, USA FOR THE P1282 SPECIFICATION."
+
+#define SL_FLAGS_NONE 0
+#define SL_FLAGS_CONTINUE 1
+#define SL_FLAGS_CURRENT 2
+#define SL_FLAGS_PARENT 4
+#define SL_FLAGS_ROOT 8
+
+typedef struct {
+ ISO_SUSP_HEADER h;
+ u_char mode [ISODCL(5,12)];
+ u_char links [ISODCL(13,20)];
+ u_char uid [ISODCL(21,28)];
+ u_char gid [ISODCL(29,36)];
+ u_char serial [ISODCL(37,44)];
+} ISO_RRIP_PX;
+
+typedef struct {
+ ISO_SUSP_HEADER h;
+ u_char high [ISODCL(5,12)];
+ u_char low [ISODCL(13,20)];
+} ISO_RRIP_PN;
+
+typedef struct {
+ ISO_SUSP_HEADER h;
+ u_char flags [ISODCL ( 4, 4)];
+ u_char component [ISODCL ( 4, 256)];
+ u_int nBytes;
+} ISO_RRIP_SL;
+
+typedef struct {
+ ISO_SUSP_HEADER h;
+ u_char flags [ISODCL ( 4, 4)];
+ u_char timestamp [ISODCL ( 5, 256)];
+} ISO_RRIP_TF;
+
+#define RRIP_NM_FLAGS_NONE 0x00
+#define RRIP_NM_FLAGS_CONTINUE 0x01
+#define RRIP_NM_FLAGS_CURRENT 0x02
+#define RRIP_NM_FLAGS_PARENT 0x04
+
+typedef struct {
+ ISO_SUSP_HEADER h;
+ u_char flags [ISODCL ( 4, 4)];
+ u_char altname [ISODCL ( 4, 256)];
+} ISO_RRIP_NM;
+
+/* Note that this is the same structure as cd9660_rrip.h : ISO_RRIP_CONT */
+typedef struct {
+ ISO_SUSP_HEADER h;
+ u_char ca_sector [ISODCL ( 5, 12)];
+ u_char offset [ISODCL ( 13, 20)];
+ u_char length [ISODCL ( 21, 28)];
+} ISO_SUSP_CE;
+
+typedef struct {
+ ISO_SUSP_HEADER h;
+ u_char padding_area [ISODCL ( 4, 256)];
+} ISO_SUSP_PD;
+
+typedef struct {
+ ISO_SUSP_HEADER h;
+ u_char check [ISODCL ( 4, 5)];
+ u_char len_skp [ISODCL ( 6, 6)];
+} ISO_SUSP_SP;
+
+typedef struct {
+ ISO_SUSP_HEADER h;
+} ISO_SUSP_ST;
+
+typedef struct {
+ ISO_SUSP_HEADER h;
+ u_char len_id [ISODCL ( 4, 4)];
+ u_char len_des [ISODCL ( 5, 5)];
+ u_char len_src [ISODCL ( 6, 6)];
+ u_char ext_ver [ISODCL ( 7, 7)];
+ u_char ext_data [ISODCL (8,256)];
+/* u_char ext_id [ISODCL ( 8, 256)];
+ u_char ext_des [ISODCL ( 257, 513)];
+ u_char ext_src [ISODCL ( 514, 770)];*/
+} ISO_SUSP_ER;
+
+typedef struct {
+ ISO_SUSP_HEADER h;
+ u_char ext_seq [ISODCL ( 4, 4)];
+} ISO_SUSP_ES;
+
+typedef union {
+ ISO_RRIP_PX PX;
+ ISO_RRIP_PN PN;
+ ISO_RRIP_SL SL;
+ ISO_RRIP_NM NM;
+ ISO_RRIP_CLINK CL;
+ ISO_RRIP_PLINK PL;
+ ISO_RRIP_RELDIR RE;
+ ISO_RRIP_TF TF;
+} rrip_entry;
+
+typedef union {
+ ISO_SUSP_CE CE;
+ ISO_SUSP_PD PD;
+ ISO_SUSP_SP SP;
+ ISO_SUSP_ST ST;
+ ISO_SUSP_ER ER;
+ ISO_SUSP_ES ES;
+} susp_entry;
+
+typedef union {
+ susp_entry su_entry;
+ rrip_entry rr_entry;
+} SUSP_ENTRIES;
+
+struct ISO_SUSP_ATTRIBUTES {
+ SUSP_ENTRIES attr;
+ int type;
+ char type_of[2];
+ char last_in_suf; /* last entry in the System Use Field? */
+ /* Dan's addons - will merge later. This allows use of a switch */
+ char susp_type; /* SUSP or RRIP */
+ char entry_type; /* Record type */
+ char write_location;
+ TAILQ_ENTRY(ISO_SUSP_ATTRIBUTES) rr_ll;
+};
+
+#define CD9660_SUSP_ENTRY_SIZE(entry)\
+ ((int) ((entry)->attr.su_entry.SP.h.length[0]))
+
+/* Recursive function - move later to func pointer code*/
+int cd9660_susp_finalize(cd9660node *);
+
+/* These two operate on single nodes */
+int cd9660_susp_finalize_node(cd9660node *);
+int cd9660_rrip_finalize_node(cd9660node *);
+
+/* POSIX File attribute */
+int cd9660node_rrip_px(struct ISO_SUSP_ATTRIBUTES *, fsnode *);
+
+/* Device number */
+int cd9660node_rrip_pn(struct ISO_SUSP_ATTRIBUTES *, fsnode *);
+
+/* Symbolic link */
+int cd9660node_rrip_SL(struct ISO_SUSP_ATTRIBUTES *, fsnode *);
+
+/* Alternate Name function */
+void cd9660_rrip_NM(cd9660node *);
+void cd9660_rrip_add_NM(cd9660node *,const char *);
+
+/* Parent and child link function */
+int cd9660_rrip_PL(struct ISO_SUSP_ATTRIBUTES *, cd9660node *);
+int cd9660_rrip_CL(struct ISO_SUSP_ATTRIBUTES *, cd9660node *);
+int cd9660_rrip_RE(struct ISO_SUSP_ATTRIBUTES *, cd9660node *);
+
+int cd9660node_rrip_tf(struct ISO_SUSP_ATTRIBUTES *, fsnode *);
+
+
+
+/*
+ * Relocation directory function. I'm not quite sure what
+ * sort of parameters are needed, but personally I don't think
+ * any parameters are needed except for the memory address where
+ * the information needs to be put in
+ */
+int cd9660node_rrip_re(void *, fsnode *);
+
+/*
+ * Don't know if this function is needed because it apparently is an
+ * optional feature that does not really need to be implemented but I
+ * thought I should add it anyway.
+ */
+int cd9660_susp_ce (struct ISO_SUSP_ATTRIBUTES *, cd9660node *);
+int cd9660_susp_pd (struct ISO_SUSP_ATTRIBUTES *, int);
+int cd9660_susp_sp (struct ISO_SUSP_ATTRIBUTES *, cd9660node *);
+int cd9660_susp_st (struct ISO_SUSP_ATTRIBUTES *, cd9660node *);
+
+struct ISO_SUSP_ATTRIBUTES *cd9660_susp_ER(cd9660node *, u_char, const char *,
+ const char *, const char *);
+struct ISO_SUSP_ATTRIBUTES *cd9660_susp_ES(struct ISO_SUSP_ATTRIBUTES*,
+ cd9660node *);
+
+
+/* Helper functions */
+
+/* Common SUSP/RRIP functions */
+int cd9660_susp_initialize(cd9660node *, cd9660node *, cd9660node *);
+int cd9660_susp_initialize_node(cd9660node *);
+struct ISO_SUSP_ATTRIBUTES *cd9660node_susp_create_node(int, int, const char *,
+ int);
+struct ISO_SUSP_ATTRIBUTES *cd9660node_susp_add_entry(cd9660node *,
+ struct ISO_SUSP_ATTRIBUTES *, struct ISO_SUSP_ATTRIBUTES *, int);
+
+/* RRIP specific functions */
+int cd9660_rrip_initialize_node(cd9660node *, cd9660node *, cd9660node *);
+void cd9660_createSL(cd9660node *);
+
+/* Functions that probably can be removed */
+/* int cd9660node_initialize_node(int, char *); */
+
+
+#endif
diff --git a/usr.sbin/makefs/ffs.c b/usr.sbin/makefs/ffs.c
new file mode 100644
index 0000000..dd97c01
--- /dev/null
+++ b/usr.sbin/makefs/ffs.c
@@ -0,0 +1,1173 @@
+/* $NetBSD: ffs.c,v 1.44 2009/04/28 22:49:26 joerg Exp $ */
+
+/*
+ * Copyright (c) 2001 Wasabi Systems, Inc.
+ * All rights reserved.
+ *
+ * Written by Luke Mewburn 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.
+ */
+/*
+ * 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.
+ * 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.
+ *
+ * @(#)ffs_alloc.c 8.19 (Berkeley) 7/13/95
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+
+#include <sys/mount.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "makefs.h"
+#include "ffs.h"
+
+#if HAVE_STRUCT_STATVFS_F_IOSIZE && HAVE_FSTATVFS
+#include <sys/statvfs.h>
+#endif
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ufs/dir.h>
+#include <ufs/ffs/fs.h>
+
+
+#include "ffs/ufs_bswap.h"
+#include "ffs/ufs_inode.h"
+#include "ffs/newfs_extern.h"
+#include "ffs/ffs_extern.h"
+
+#undef DIP
+#define DIP(dp, field) \
+ ((ffs_opts->version == 1) ? \
+ (dp)->ffs1_din.di_##field : (dp)->ffs2_din.di_##field)
+
+/*
+ * Various file system defaults (cribbed from newfs(8)).
+ */
+#define DFL_FRAGSIZE 1024 /* fragment size */
+#define DFL_BLKSIZE 8192 /* block size */
+#define DFL_SECSIZE 512 /* sector size */
+#define DFL_CYLSPERGROUP 65536 /* cylinders per group */
+#define DFL_FRAGSPERINODE 4 /* fragments per inode */
+#define DFL_ROTDELAY 0 /* rotational delay */
+#define DFL_NRPOS 1 /* rotational positions */
+#define DFL_RPM 3600 /* rpm of disk */
+#define DFL_NSECTORS 64 /* # of sectors */
+#define DFL_NTRACKS 16 /* # of tracks */
+
+
+typedef struct {
+ u_char *buf; /* buf for directory */
+ doff_t size; /* full size of buf */
+ doff_t cur; /* offset of current entry */
+} dirbuf_t;
+
+
+static int ffs_create_image(const char *, fsinfo_t *);
+static void ffs_dump_fsinfo(fsinfo_t *);
+static void ffs_dump_dirbuf(dirbuf_t *, const char *, int);
+static void ffs_make_dirbuf(dirbuf_t *, const char *, fsnode *, int);
+static int ffs_populate_dir(const char *, fsnode *, fsinfo_t *);
+static void ffs_size_dir(fsnode *, fsinfo_t *);
+static void ffs_validate(const char *, fsnode *, fsinfo_t *);
+static void ffs_write_file(union dinode *, uint32_t, void *, fsinfo_t *);
+static void ffs_write_inode(union dinode *, uint32_t, const fsinfo_t *);
+static void *ffs_build_dinode1(struct ufs1_dinode *, dirbuf_t *, fsnode *,
+ fsnode *, fsinfo_t *);
+static void *ffs_build_dinode2(struct ufs2_dinode *, dirbuf_t *, fsnode *,
+ fsnode *, fsinfo_t *);
+
+
+
+int sectorsize; /* XXX: for buf.c::getblk() */
+
+ /* publicly visible functions */
+
+void
+ffs_prep_opts(fsinfo_t *fsopts)
+{
+ ffs_opt_t *ffs_opts;
+
+ if ((ffs_opts = calloc(1, sizeof(ffs_opt_t))) == NULL)
+ err(1, "Allocating memory for ffs_options");
+
+ fsopts->fs_specific = ffs_opts;
+
+ ffs_opts->bsize= -1;
+ ffs_opts->fsize= -1;
+ ffs_opts->cpg= -1;
+ ffs_opts->density= -1;
+ ffs_opts->minfree= -1;
+ ffs_opts->optimization= -1;
+ ffs_opts->maxcontig= -1;
+ ffs_opts->maxbpg= -1;
+ ffs_opts->avgfilesize= -1;
+ ffs_opts->avgfpdir= -1;
+ ffs_opts->version = 1;
+}
+
+void
+ffs_cleanup_opts(fsinfo_t *fsopts)
+{
+ if (fsopts->fs_specific)
+ free(fsopts->fs_specific);
+}
+
+int
+ffs_parse_opts(const char *option, fsinfo_t *fsopts)
+{
+ ffs_opt_t *ffs_opts = fsopts->fs_specific;
+
+ option_t ffs_options[] = {
+ { "bsize", &ffs_opts->bsize, 1, INT_MAX,
+ "block size" },
+ { "fsize", &ffs_opts->fsize, 1, INT_MAX,
+ "fragment size" },
+ { "density", &ffs_opts->density, 1, INT_MAX,
+ "bytes per inode" },
+ { "minfree", &ffs_opts->minfree, 0, 99,
+ "minfree" },
+ { "maxbpg", &ffs_opts->maxbpg, 1, INT_MAX,
+ "max blocks per file in a cg" },
+ { "avgfilesize", &ffs_opts->avgfilesize,1, INT_MAX,
+ "expected average file size" },
+ { "avgfpdir", &ffs_opts->avgfpdir, 1, INT_MAX,
+ "expected # of files per directory" },
+ { "extent", &ffs_opts->maxbsize, 1, INT_MAX,
+ "maximum # extent size" },
+ { "maxbpcg", &ffs_opts->maxblkspercg,1, INT_MAX,
+ "max # of blocks per group" },
+ { "version", &ffs_opts->version, 1, 2,
+ "UFS version" },
+ { .name = NULL }
+ };
+
+ char *var, *val;
+ int rv;
+
+ assert(option != NULL);
+ assert(fsopts != NULL);
+ assert(ffs_opts != NULL);
+
+ if (debug & DEBUG_FS_PARSE_OPTS)
+ printf("ffs_parse_opts: got `%s'\n", option);
+
+ if ((var = strdup(option)) == NULL)
+ err(1, "Allocating memory for copy of option string");
+ rv = 0;
+
+ if ((val = strchr(var, '=')) == NULL) {
+ warnx("Option `%s' doesn't contain a value", var);
+ goto leave_ffs_parse_opts;
+ }
+ *val++ = '\0';
+
+ if (strcmp(var, "optimization") == 0) {
+ if (strcmp(val, "time") == 0) {
+ ffs_opts->optimization = FS_OPTTIME;
+ } else if (strcmp(val, "space") == 0) {
+ ffs_opts->optimization = FS_OPTSPACE;
+ } else {
+ warnx("Invalid optimization `%s'", val);
+ goto leave_ffs_parse_opts;
+ }
+ rv = 1;
+ } else if (strcmp(var, "label") == 0) {
+ strlcpy(ffs_opts->label, val, sizeof(ffs_opts->label));
+ rv = 1;
+ } else
+ rv = set_option(ffs_options, var, val);
+
+ leave_ffs_parse_opts:
+ if (var)
+ free(var);
+ return (rv);
+}
+
+
+void
+ffs_makefs(const char *image, const char *dir, fsnode *root, fsinfo_t *fsopts)
+{
+ struct fs *superblock;
+ struct timeval start;
+
+ assert(image != NULL);
+ assert(dir != NULL);
+ assert(root != NULL);
+ assert(fsopts != NULL);
+
+ if (debug & DEBUG_FS_MAKEFS)
+ printf("ffs_makefs: image %s directory %s root %p\n",
+ image, dir, root);
+
+ /* validate tree and options */
+ TIMER_START(start);
+ ffs_validate(dir, root, fsopts);
+ TIMER_RESULTS(start, "ffs_validate");
+
+ printf("Calculated size of `%s': %lld bytes, %lld inodes\n",
+ image, (long long)fsopts->size, (long long)fsopts->inodes);
+
+ /* create image */
+ TIMER_START(start);
+ if (ffs_create_image(image, fsopts) == -1)
+ errx(1, "Image file `%s' not created.", image);
+ TIMER_RESULTS(start, "ffs_create_image");
+
+ fsopts->curinode = ROOTINO;
+
+ if (debug & DEBUG_FS_MAKEFS)
+ putchar('\n');
+
+ /* populate image */
+ printf("Populating `%s'\n", image);
+ TIMER_START(start);
+ if (! ffs_populate_dir(dir, root, fsopts))
+ errx(1, "Image file `%s' not populated.", image);
+ TIMER_RESULTS(start, "ffs_populate_dir");
+
+ /* ensure no outstanding buffers remain */
+ if (debug & DEBUG_FS_MAKEFS)
+ bcleanup();
+
+ /* update various superblock parameters */
+ superblock = fsopts->superblock;
+ superblock->fs_fmod = 0;
+ superblock->fs_old_cstotal.cs_ndir = superblock->fs_cstotal.cs_ndir;
+ superblock->fs_old_cstotal.cs_nbfree = superblock->fs_cstotal.cs_nbfree;
+ superblock->fs_old_cstotal.cs_nifree = superblock->fs_cstotal.cs_nifree;
+ superblock->fs_old_cstotal.cs_nffree = superblock->fs_cstotal.cs_nffree;
+
+ /* write out superblock; image is now complete */
+ ffs_write_superblock(fsopts->superblock, fsopts);
+ if (close(fsopts->fd) == -1)
+ err(1, "Closing `%s'", image);
+ fsopts->fd = -1;
+ printf("Image `%s' complete\n", image);
+}
+
+ /* end of public functions */
+
+
+static void
+ffs_validate(const char *dir, fsnode *root, fsinfo_t *fsopts)
+{
+ int32_t ncg = 1;
+#if notyet
+ int32_t spc, nspf, ncyl, fssize;
+#endif
+ ffs_opt_t *ffs_opts = fsopts->fs_specific;
+
+ assert(dir != NULL);
+ assert(root != NULL);
+ assert(fsopts != NULL);
+ assert(ffs_opts != NULL);
+
+ if (debug & DEBUG_FS_VALIDATE) {
+ printf("ffs_validate: before defaults set:\n");
+ ffs_dump_fsinfo(fsopts);
+ }
+
+ /* set FFS defaults */
+ if (fsopts->sectorsize == -1)
+ fsopts->sectorsize = DFL_SECSIZE;
+ if (ffs_opts->fsize == -1)
+ ffs_opts->fsize = MAX(DFL_FRAGSIZE, fsopts->sectorsize);
+ if (ffs_opts->bsize == -1)
+ ffs_opts->bsize = MIN(DFL_BLKSIZE, 8 * ffs_opts->fsize);
+ if (ffs_opts->cpg == -1)
+ ffs_opts->cpg = DFL_CYLSPERGROUP;
+ else
+ ffs_opts->cpgflg = 1;
+ /* fsopts->density is set below */
+ if (ffs_opts->nsectors == -1)
+ ffs_opts->nsectors = DFL_NSECTORS;
+ if (ffs_opts->minfree == -1)
+ ffs_opts->minfree = MINFREE;
+ if (ffs_opts->optimization == -1)
+ ffs_opts->optimization = DEFAULTOPT;
+ if (ffs_opts->maxcontig == -1)
+ ffs_opts->maxcontig =
+ MAX(1, MIN(MAXPHYS, FFS_MAXBSIZE) / ffs_opts->bsize);
+ /* XXX ondisk32 */
+ if (ffs_opts->maxbpg == -1)
+ ffs_opts->maxbpg = ffs_opts->bsize / sizeof(int32_t);
+ if (ffs_opts->avgfilesize == -1)
+ ffs_opts->avgfilesize = AVFILESIZ;
+ if (ffs_opts->avgfpdir == -1)
+ ffs_opts->avgfpdir = AFPDIR;
+
+ if (fsopts->maxsize > 0 &&
+ roundup(fsopts->minsize, ffs_opts->bsize) > fsopts->maxsize)
+ errx(1, "`%s' minsize of %lld rounded up to ffs bsize of %d "
+ "exceeds maxsize %lld. Lower bsize, or round the minimum "
+ "and maximum sizes to bsize.", dir,
+ (long long)fsopts->minsize, ffs_opts->bsize,
+ (long long)fsopts->maxsize);
+
+ /* calculate size of tree */
+ ffs_size_dir(root, fsopts);
+ fsopts->inodes += ROOTINO; /* include first two inodes */
+
+ if (debug & DEBUG_FS_VALIDATE)
+ printf("ffs_validate: size of tree: %lld bytes, %lld inodes\n",
+ (long long)fsopts->size, (long long)fsopts->inodes);
+
+ /* add requested slop */
+ fsopts->size += fsopts->freeblocks;
+ fsopts->inodes += fsopts->freefiles;
+ if (fsopts->freefilepc > 0)
+ fsopts->inodes =
+ fsopts->inodes * (100 + fsopts->freefilepc) / 100;
+ if (fsopts->freeblockpc > 0)
+ fsopts->size =
+ fsopts->size * (100 + fsopts->freeblockpc) / 100;
+
+ /* add space needed for superblocks */
+ /*
+ * The old SBOFF (SBLOCK_UFS1) is used here because makefs is
+ * typically used for small filesystems where space matters.
+ * XXX make this an option.
+ */
+ fsopts->size += (SBLOCK_UFS1 + SBLOCKSIZE) * ncg;
+ /* add space needed to store inodes, x3 for blockmaps, etc */
+ if (ffs_opts->version == 1)
+ fsopts->size += ncg * DINODE1_SIZE *
+ roundup(fsopts->inodes / ncg,
+ ffs_opts->bsize / DINODE1_SIZE);
+ else
+ fsopts->size += ncg * DINODE2_SIZE *
+ roundup(fsopts->inodes / ncg,
+ ffs_opts->bsize / DINODE2_SIZE);
+
+ /* add minfree */
+ if (ffs_opts->minfree > 0)
+ fsopts->size =
+ fsopts->size * (100 + ffs_opts->minfree) / 100;
+ /*
+ * XXX any other fs slop to add, such as csum's, bitmaps, etc ??
+ */
+
+ if (fsopts->size < fsopts->minsize) /* ensure meets minimum size */
+ fsopts->size = fsopts->minsize;
+
+ /* round up to the next block */
+ fsopts->size = roundup(fsopts->size, ffs_opts->bsize);
+
+ /* round up to requested block size, if any */
+ if (fsopts->roundup > 0)
+ fsopts->size = roundup(fsopts->size, fsopts->roundup);
+
+ /* calculate density if necessary */
+ if (ffs_opts->density == -1)
+ ffs_opts->density = fsopts->size / fsopts->inodes + 1;
+
+ if (debug & DEBUG_FS_VALIDATE) {
+ printf("ffs_validate: after defaults set:\n");
+ ffs_dump_fsinfo(fsopts);
+ printf("ffs_validate: dir %s; %lld bytes, %lld inodes\n",
+ dir, (long long)fsopts->size, (long long)fsopts->inodes);
+ }
+ sectorsize = fsopts->sectorsize; /* XXX - see earlier */
+
+ /* now check calculated sizes vs requested sizes */
+ if (fsopts->maxsize > 0 && fsopts->size > fsopts->maxsize) {
+ errx(1, "`%s' size of %lld is larger than the maxsize of %lld.",
+ dir, (long long)fsopts->size, (long long)fsopts->maxsize);
+ }
+}
+
+
+static void
+ffs_dump_fsinfo(fsinfo_t *f)
+{
+
+ ffs_opt_t *fs = f->fs_specific;
+
+ printf("fsopts at %p\n", f);
+
+ printf("\tsize %lld, inodes %lld, curinode %u\n",
+ (long long)f->size, (long long)f->inodes, f->curinode);
+
+ printf("\tminsize %lld, maxsize %lld\n",
+ (long long)f->minsize, (long long)f->maxsize);
+ printf("\tfree files %lld, freefile %% %d\n",
+ (long long)f->freefiles, f->freefilepc);
+ printf("\tfree blocks %lld, freeblock %% %d\n",
+ (long long)f->freeblocks, f->freeblockpc);
+ printf("\tneedswap %d, sectorsize %d\n", f->needswap, f->sectorsize);
+
+ printf("\tbsize %d, fsize %d, cpg %d, density %d\n",
+ fs->bsize, fs->fsize, fs->cpg, fs->density);
+ printf("\tnsectors %d, rpm %d, minfree %d\n",
+ fs->nsectors, fs->rpm, fs->minfree);
+ printf("\tmaxcontig %d, maxbpg %d\n",
+ fs->maxcontig, fs->maxbpg);
+ printf("\toptimization %s\n",
+ fs->optimization == FS_OPTSPACE ? "space" : "time");
+}
+
+
+static int
+ffs_create_image(const char *image, fsinfo_t *fsopts)
+{
+#if HAVE_STRUCT_STATVFS_F_IOSIZE && HAVE_FSTATVFS
+ struct statvfs sfs;
+#endif
+ struct fs *fs;
+ char *buf;
+ int i, bufsize;
+ off_t bufrem;
+
+ assert (image != NULL);
+ assert (fsopts != NULL);
+
+ /* create image */
+ if ((fsopts->fd = open(image, O_RDWR | O_CREAT | O_TRUNC, 0666))
+ == -1) {
+ warn("Can't open `%s' for writing", image);
+ return (-1);
+ }
+
+ /* zero image */
+#if HAVE_STRUCT_STATVFS_F_IOSIZE && HAVE_FSTATVFS
+ if (fstatvfs(fsopts->fd, &sfs) == -1) {
+#endif
+ bufsize = 8192;
+#if HAVE_STRUCT_STATVFS_F_IOSIZE && HAVE_FSTATVFS
+ warn("can't fstatvfs `%s', using default %d byte chunk",
+ image, bufsize);
+ } else
+ bufsize = sfs.f_iosize;
+#endif
+ bufrem = fsopts->size;
+ if (fsopts->sparse) {
+ if (ftruncate(fsopts->fd, bufrem) == -1) {
+ warn("sparse option disabled.\n");
+ fsopts->sparse = 0;
+ }
+ }
+ if (fsopts->sparse) {
+ /* File truncated at bufrem. Remaining is 0 */
+ bufrem = 0;
+ buf = NULL;
+ } else {
+ if (debug & DEBUG_FS_CREATE_IMAGE)
+ printf("zero-ing image `%s', %lld sectors, "
+ "using %d byte chunks\n", image, (long long)bufrem,
+ bufsize);
+ if ((buf = calloc(1, bufsize)) == NULL) {
+ warn("Can't create buffer for sector");
+ return (-1);
+ }
+ }
+ while (bufrem > 0) {
+ i = write(fsopts->fd, buf, MIN(bufsize, bufrem));
+ if (i == -1) {
+ warn("zeroing image, %lld bytes to go",
+ (long long)bufrem);
+ free(buf);
+ return (-1);
+ }
+ bufrem -= i;
+ }
+ if (buf)
+ free(buf);
+
+ /* make the file system */
+ if (debug & DEBUG_FS_CREATE_IMAGE)
+ printf("calling mkfs(\"%s\", ...)\n", image);
+ fs = ffs_mkfs(image, fsopts);
+ fsopts->superblock = (void *)fs;
+ if (debug & DEBUG_FS_CREATE_IMAGE) {
+ time_t t;
+
+ t = (time_t)((struct fs *)fsopts->superblock)->fs_time;
+ printf("mkfs returned %p; fs_time %s",
+ fsopts->superblock, ctime(&t));
+ printf("fs totals: nbfree %lld, nffree %lld, nifree %lld, ndir %lld\n",
+ (long long)fs->fs_cstotal.cs_nbfree,
+ (long long)fs->fs_cstotal.cs_nffree,
+ (long long)fs->fs_cstotal.cs_nifree,
+ (long long)fs->fs_cstotal.cs_ndir);
+ }
+
+ if (fs->fs_cstotal.cs_nifree + ROOTINO < fsopts->inodes) {
+ warnx(
+ "Image file `%s' has %lld free inodes; %lld are required.",
+ image,
+ (long long)(fs->fs_cstotal.cs_nifree + ROOTINO),
+ (long long)fsopts->inodes);
+ return (-1);
+ }
+ return (fsopts->fd);
+}
+
+
+static void
+ffs_size_dir(fsnode *root, fsinfo_t *fsopts)
+{
+ struct direct tmpdir;
+ fsnode * node;
+ int curdirsize, this;
+ ffs_opt_t *ffs_opts = fsopts->fs_specific;
+
+ /* node may be NULL (empty directory) */
+ assert(fsopts != NULL);
+ assert(ffs_opts != NULL);
+
+ if (debug & DEBUG_FS_SIZE_DIR)
+ printf("ffs_size_dir: entry: bytes %lld inodes %lld\n",
+ (long long)fsopts->size, (long long)fsopts->inodes);
+
+#define ADDDIRENT(e) do { \
+ tmpdir.d_namlen = strlen((e)); \
+ this = DIRSIZ_SWAP(0, &tmpdir, 0); \
+ if (debug & DEBUG_FS_SIZE_DIR_ADD_DIRENT) \
+ printf("ADDDIRENT: was: %s (%d) this %d cur %d\n", \
+ e, tmpdir.d_namlen, this, curdirsize); \
+ if (this + curdirsize > roundup(curdirsize, DIRBLKSIZ)) \
+ curdirsize = roundup(curdirsize, DIRBLKSIZ); \
+ curdirsize += this; \
+ if (debug & DEBUG_FS_SIZE_DIR_ADD_DIRENT) \
+ printf("ADDDIRENT: now: %s (%d) this %d cur %d\n", \
+ e, tmpdir.d_namlen, this, curdirsize); \
+} while (0);
+
+ /*
+ * XXX this needs to take into account extra space consumed
+ * by indirect blocks, etc.
+ */
+#define ADDSIZE(x) do { \
+ fsopts->size += roundup((x), ffs_opts->fsize); \
+} while (0);
+
+ curdirsize = 0;
+ for (node = root; node != NULL; node = node->next) {
+ ADDDIRENT(node->name);
+ if (node == root) { /* we're at "." */
+ assert(strcmp(node->name, ".") == 0);
+ ADDDIRENT("..");
+ } else if ((node->inode->flags & FI_SIZED) == 0) {
+ /* don't count duplicate names */
+ node->inode->flags |= FI_SIZED;
+ if (debug & DEBUG_FS_SIZE_DIR_NODE)
+ printf("ffs_size_dir: `%s' size %lld\n",
+ node->name,
+ (long long)node->inode->st.st_size);
+ fsopts->inodes++;
+ if (node->type == S_IFREG)
+ ADDSIZE(node->inode->st.st_size);
+ if (node->type == S_IFLNK) {
+ int slen;
+
+ slen = strlen(node->symlink) + 1;
+ if (slen >= (ffs_opts->version == 1 ?
+ MAXSYMLINKLEN_UFS1 :
+ MAXSYMLINKLEN_UFS2))
+ ADDSIZE(slen);
+ }
+ }
+ if (node->type == S_IFDIR)
+ ffs_size_dir(node->child, fsopts);
+ }
+ ADDSIZE(curdirsize);
+
+ if (debug & DEBUG_FS_SIZE_DIR)
+ printf("ffs_size_dir: exit: size %lld inodes %lld\n",
+ (long long)fsopts->size, (long long)fsopts->inodes);
+}
+
+static void *
+ffs_build_dinode1(struct ufs1_dinode *dinp, dirbuf_t *dbufp, fsnode *cur,
+ fsnode *root, fsinfo_t *fsopts)
+{
+ int slen;
+ void *membuf;
+
+ memset(dinp, 0, sizeof(*dinp));
+ dinp->di_mode = cur->inode->st.st_mode;
+ dinp->di_nlink = cur->inode->nlink;
+ dinp->di_size = cur->inode->st.st_size;
+ dinp->di_atime = cur->inode->st.st_atime;
+ dinp->di_mtime = cur->inode->st.st_mtime;
+ dinp->di_ctime = cur->inode->st.st_ctime;
+#if HAVE_STRUCT_STAT_ST_MTIMENSEC
+ dinp->di_atimensec = cur->inode->st.st_atimensec;
+ dinp->di_mtimensec = cur->inode->st.st_mtimensec;
+ dinp->di_ctimensec = cur->inode->st.st_ctimensec;
+#endif
+#if HAVE_STRUCT_STAT_ST_FLAGS
+ dinp->di_flags = cur->inode->st.st_flags;
+#endif
+#if HAVE_STRUCT_STAT_ST_GEN
+ dinp->di_gen = cur->inode->st.st_gen;
+#endif
+ dinp->di_uid = cur->inode->st.st_uid;
+ dinp->di_gid = cur->inode->st.st_gid;
+ /* not set: di_db, di_ib, di_blocks, di_spare */
+
+ membuf = NULL;
+ if (cur == root) { /* "."; write dirbuf */
+ membuf = dbufp->buf;
+ dinp->di_size = dbufp->size;
+ } else if (S_ISBLK(cur->type) || S_ISCHR(cur->type)) {
+ dinp->di_size = 0; /* a device */
+ dinp->di_rdev =
+ ufs_rw32(cur->inode->st.st_rdev, fsopts->needswap);
+ } else if (S_ISLNK(cur->type)) { /* symlink */
+ slen = strlen(cur->symlink);
+ if (slen < MAXSYMLINKLEN_UFS1) { /* short link */
+ memcpy(dinp->di_db, cur->symlink, slen);
+ } else
+ membuf = cur->symlink;
+ dinp->di_size = slen;
+ }
+ return membuf;
+}
+
+static void *
+ffs_build_dinode2(struct ufs2_dinode *dinp, dirbuf_t *dbufp, fsnode *cur,
+ fsnode *root, fsinfo_t *fsopts)
+{
+ int slen;
+ void *membuf;
+
+ memset(dinp, 0, sizeof(*dinp));
+ dinp->di_mode = cur->inode->st.st_mode;
+ dinp->di_nlink = cur->inode->nlink;
+ dinp->di_size = cur->inode->st.st_size;
+ dinp->di_atime = cur->inode->st.st_atime;
+ dinp->di_mtime = cur->inode->st.st_mtime;
+ dinp->di_ctime = cur->inode->st.st_ctime;
+#if HAVE_STRUCT_STAT_ST_MTIMENSEC
+ dinp->di_atimensec = cur->inode->st.st_atimensec;
+ dinp->di_mtimensec = cur->inode->st.st_mtimensec;
+ dinp->di_ctimensec = cur->inode->st.st_ctimensec;
+#endif
+#if HAVE_STRUCT_STAT_ST_FLAGS
+ dinp->di_flags = cur->inode->st.st_flags;
+#endif
+#if HAVE_STRUCT_STAT_ST_GEN
+ dinp->di_gen = cur->inode->st.st_gen;
+#endif
+#if HAVE_STRUCT_STAT_BIRTHTIME
+ dinp->di_birthtime = cur->inode->st.st_birthtime;
+ dinp->di_birthnsec = cur->inode->st.st_birthtimensec;
+#endif
+ dinp->di_uid = cur->inode->st.st_uid;
+ dinp->di_gid = cur->inode->st.st_gid;
+ /* not set: di_db, di_ib, di_blocks, di_spare */
+
+ membuf = NULL;
+ if (cur == root) { /* "."; write dirbuf */
+ membuf = dbufp->buf;
+ dinp->di_size = dbufp->size;
+ } else if (S_ISBLK(cur->type) || S_ISCHR(cur->type)) {
+ dinp->di_size = 0; /* a device */
+ dinp->di_rdev =
+ ufs_rw64(cur->inode->st.st_rdev, fsopts->needswap);
+ } else if (S_ISLNK(cur->type)) { /* symlink */
+ slen = strlen(cur->symlink);
+ if (slen < MAXSYMLINKLEN_UFS2) { /* short link */
+ memcpy(dinp->di_db, cur->symlink, slen);
+ } else
+ membuf = cur->symlink;
+ dinp->di_size = slen;
+ }
+ return membuf;
+}
+
+static int
+ffs_populate_dir(const char *dir, fsnode *root, fsinfo_t *fsopts)
+{
+ fsnode *cur;
+ dirbuf_t dirbuf;
+ union dinode din;
+ void *membuf;
+ char path[MAXPATHLEN + 1];
+ ffs_opt_t *ffs_opts = fsopts->fs_specific;
+
+ assert(dir != NULL);
+ assert(root != NULL);
+ assert(fsopts != NULL);
+ assert(ffs_opts != NULL);
+
+ (void)memset(&dirbuf, 0, sizeof(dirbuf));
+
+ if (debug & DEBUG_FS_POPULATE)
+ printf("ffs_populate_dir: PASS 1 dir %s node %p\n", dir, root);
+
+ /*
+ * pass 1: allocate inode numbers, build directory `file'
+ */
+ for (cur = root; cur != NULL; cur = cur->next) {
+ if ((cur->inode->flags & FI_ALLOCATED) == 0) {
+ cur->inode->flags |= FI_ALLOCATED;
+ if (cur == root && cur->parent != NULL)
+ cur->inode->ino = cur->parent->inode->ino;
+ else {
+ cur->inode->ino = fsopts->curinode;
+ fsopts->curinode++;
+ }
+ }
+ ffs_make_dirbuf(&dirbuf, cur->name, cur, fsopts->needswap);
+ if (cur == root) { /* we're at "."; add ".." */
+ ffs_make_dirbuf(&dirbuf, "..",
+ cur->parent == NULL ? cur : cur->parent->first,
+ fsopts->needswap);
+ root->inode->nlink++; /* count my parent's link */
+ } else if (cur->child != NULL)
+ root->inode->nlink++; /* count my child's link */
+
+ /*
+ * XXX possibly write file and long symlinks here,
+ * ensuring that blocks get written before inodes?
+ * otoh, this isn't a real filesystem, so who
+ * cares about ordering? :-)
+ */
+ }
+ if (debug & DEBUG_FS_POPULATE_DIRBUF)
+ ffs_dump_dirbuf(&dirbuf, dir, fsopts->needswap);
+
+ /*
+ * pass 2: write out dirbuf, then non-directories at this level
+ */
+ if (debug & DEBUG_FS_POPULATE)
+ printf("ffs_populate_dir: PASS 2 dir %s\n", dir);
+ for (cur = root; cur != NULL; cur = cur->next) {
+ if (cur->inode->flags & FI_WRITTEN)
+ continue; /* skip hard-linked entries */
+ cur->inode->flags |= FI_WRITTEN;
+
+ if (cur->contents == NULL) {
+ if (snprintf(path, sizeof(path), "%s/%s/%s", cur->root,
+ cur->path, cur->name) >= (int)sizeof(path))
+ errx(1, "Pathname too long.");
+ }
+
+ if (cur->child != NULL)
+ continue; /* child creates own inode */
+
+ /* build on-disk inode */
+ if (ffs_opts->version == 1)
+ membuf = ffs_build_dinode1(&din.ffs1_din, &dirbuf, cur,
+ root, fsopts);
+ else
+ membuf = ffs_build_dinode2(&din.ffs2_din, &dirbuf, cur,
+ root, fsopts);
+
+ if (debug & DEBUG_FS_POPULATE_NODE) {
+ printf("ffs_populate_dir: writing ino %d, %s",
+ cur->inode->ino, inode_type(cur->type));
+ if (cur->inode->nlink > 1)
+ printf(", nlink %d", cur->inode->nlink);
+ putchar('\n');
+ }
+
+ if (membuf != NULL) {
+ ffs_write_file(&din, cur->inode->ino, membuf, fsopts);
+ } else if (S_ISREG(cur->type)) {
+ ffs_write_file(&din, cur->inode->ino,
+ (cur->contents) ? cur->contents : path, fsopts);
+ } else {
+ assert (! S_ISDIR(cur->type));
+ ffs_write_inode(&din, cur->inode->ino, fsopts);
+ }
+ }
+
+ /*
+ * pass 3: write out sub-directories
+ */
+ if (debug & DEBUG_FS_POPULATE)
+ printf("ffs_populate_dir: PASS 3 dir %s\n", dir);
+ for (cur = root; cur != NULL; cur = cur->next) {
+ if (cur->child == NULL)
+ continue;
+ if (snprintf(path, sizeof(path), "%s/%s", dir, cur->name)
+ >= sizeof(path))
+ errx(1, "Pathname too long.");
+ if (! ffs_populate_dir(path, cur->child, fsopts))
+ return (0);
+ }
+
+ if (debug & DEBUG_FS_POPULATE)
+ printf("ffs_populate_dir: DONE dir %s\n", dir);
+
+ /* cleanup */
+ if (dirbuf.buf != NULL)
+ free(dirbuf.buf);
+ return (1);
+}
+
+
+static void
+ffs_write_file(union dinode *din, uint32_t ino, void *buf, fsinfo_t *fsopts)
+{
+ int isfile, ffd;
+ char *fbuf, *p;
+ off_t bufleft, chunk, offset;
+ ssize_t nread;
+ struct inode in;
+ struct buf * bp;
+ ffs_opt_t *ffs_opts = fsopts->fs_specific;
+
+ assert (din != NULL);
+ assert (buf != NULL);
+ assert (fsopts != NULL);
+ assert (ffs_opts != NULL);
+
+ isfile = S_ISREG(DIP(din, mode));
+ fbuf = NULL;
+ ffd = -1;
+ p = NULL;
+
+ in.i_fs = (struct fs *)fsopts->superblock;
+
+ if (debug & DEBUG_FS_WRITE_FILE) {
+ printf(
+ "ffs_write_file: ino %u, din %p, isfile %d, %s, size %lld",
+ ino, din, isfile, inode_type(DIP(din, mode) & S_IFMT),
+ (long long)DIP(din, size));
+ if (isfile)
+ printf(", file '%s'\n", (char *)buf);
+ else
+ printf(", buffer %p\n", buf);
+ }
+
+ in.i_number = ino;
+ in.i_size = DIP(din, size);
+ if (ffs_opts->version == 1)
+ memcpy(&in.i_din.ffs1_din, &din->ffs1_din,
+ sizeof(in.i_din.ffs1_din));
+ else
+ memcpy(&in.i_din.ffs2_din, &din->ffs2_din,
+ sizeof(in.i_din.ffs2_din));
+ in.i_fd = fsopts->fd;
+
+ if (DIP(din, size) == 0)
+ goto write_inode_and_leave; /* mmm, cheating */
+
+ if (isfile) {
+ if ((fbuf = malloc(ffs_opts->bsize)) == NULL)
+ err(1, "Allocating memory for write buffer");
+ if ((ffd = open((char *)buf, O_RDONLY, 0444)) == -1) {
+ warn("Can't open `%s' for reading", (char *)buf);
+ goto leave_ffs_write_file;
+ }
+ } else {
+ p = buf;
+ }
+
+ chunk = 0;
+ for (bufleft = DIP(din, size); bufleft > 0; bufleft -= chunk) {
+ chunk = MIN(bufleft, ffs_opts->bsize);
+ if (!isfile)
+ ;
+ else if ((nread = read(ffd, fbuf, chunk)) == -1)
+ err(EXIT_FAILURE, "Reading `%s', %lld bytes to go",
+ (char *)buf, (long long)bufleft);
+ else if (nread != chunk)
+ errx(EXIT_FAILURE, "Reading `%s', %lld bytes to go, "
+ "read %zd bytes, expected %ju bytes, does "
+ "metalog size= attribute mismatch source size?",
+ (char *)buf, (long long)bufleft, nread,
+ (uintmax_t)chunk);
+ else
+ p = fbuf;
+ offset = DIP(din, size) - bufleft;
+ if (debug & DEBUG_FS_WRITE_FILE_BLOCK)
+ printf(
+ "ffs_write_file: write %p offset %lld size %lld left %lld\n",
+ p, (long long)offset,
+ (long long)chunk, (long long)bufleft);
+ /*
+ * XXX if holey support is desired, do the check here
+ *
+ * XXX might need to write out last bit in fragroundup
+ * sized chunk. however, ffs_balloc() handles this for us
+ */
+ errno = ffs_balloc(&in, offset, chunk, &bp);
+ bad_ffs_write_file:
+ if (errno != 0)
+ err(1,
+ "Writing inode %d (%s), bytes %lld + %lld",
+ ino,
+ isfile ? (char *)buf :
+ inode_type(DIP(din, mode) & S_IFMT),
+ (long long)offset, (long long)chunk);
+ memcpy(bp->b_data, p, chunk);
+ errno = bwrite(bp);
+ if (errno != 0)
+ goto bad_ffs_write_file;
+ brelse(bp);
+ if (!isfile)
+ p += chunk;
+ }
+
+ write_inode_and_leave:
+ ffs_write_inode(&in.i_din, in.i_number, fsopts);
+
+ leave_ffs_write_file:
+ if (fbuf)
+ free(fbuf);
+ if (ffd != -1)
+ close(ffd);
+}
+
+
+static void
+ffs_dump_dirbuf(dirbuf_t *dbuf, const char *dir, int needswap)
+{
+ doff_t i;
+ struct direct *de;
+ uint16_t reclen;
+
+ assert (dbuf != NULL);
+ assert (dir != NULL);
+ printf("ffs_dump_dirbuf: dir %s size %d cur %d\n",
+ dir, dbuf->size, dbuf->cur);
+
+ for (i = 0; i < dbuf->size; ) {
+ de = (struct direct *)(dbuf->buf + i);
+ reclen = ufs_rw16(de->d_reclen, needswap);
+ printf(
+ " inode %4d %7s offset %4d reclen %3d namlen %3d name %s\n",
+ ufs_rw32(de->d_ino, needswap),
+ inode_type(DTTOIF(de->d_type)), i, reclen,
+ de->d_namlen, de->d_name);
+ i += reclen;
+ assert(reclen > 0);
+ }
+}
+
+static void
+ffs_make_dirbuf(dirbuf_t *dbuf, const char *name, fsnode *node, int needswap)
+{
+ struct direct de, *dp;
+ uint16_t llen, reclen;
+ u_char *newbuf;
+
+ assert (dbuf != NULL);
+ assert (name != NULL);
+ assert (node != NULL);
+ /* create direct entry */
+ (void)memset(&de, 0, sizeof(de));
+ de.d_ino = ufs_rw32(node->inode->ino, needswap);
+ de.d_type = IFTODT(node->type);
+ de.d_namlen = (uint8_t)strlen(name);
+ strcpy(de.d_name, name);
+ reclen = DIRSIZ_SWAP(0, &de, needswap);
+ de.d_reclen = ufs_rw16(reclen, needswap);
+
+ dp = (struct direct *)(dbuf->buf + dbuf->cur);
+ llen = 0;
+ if (dp != NULL)
+ llen = DIRSIZ_SWAP(0, dp, needswap);
+
+ if (debug & DEBUG_FS_MAKE_DIRBUF)
+ printf(
+ "ffs_make_dirbuf: dbuf siz %d cur %d lastlen %d\n"
+ " ino %d type %d reclen %d namlen %d name %.30s\n",
+ dbuf->size, dbuf->cur, llen,
+ ufs_rw32(de.d_ino, needswap), de.d_type, reclen,
+ de.d_namlen, de.d_name);
+
+ if (reclen + dbuf->cur + llen > roundup(dbuf->size, DIRBLKSIZ)) {
+ if (debug & DEBUG_FS_MAKE_DIRBUF)
+ printf("ffs_make_dirbuf: growing buf to %d\n",
+ dbuf->size + DIRBLKSIZ);
+ if ((newbuf = realloc(dbuf->buf, dbuf->size + DIRBLKSIZ)) == NULL)
+ err(1, "Allocating memory for directory buffer");
+ dbuf->buf = newbuf;
+ dbuf->size += DIRBLKSIZ;
+ memset(dbuf->buf + dbuf->size - DIRBLKSIZ, 0, DIRBLKSIZ);
+ dbuf->cur = dbuf->size - DIRBLKSIZ;
+ } else if (dp) { /* shrink end of previous */
+ dp->d_reclen = ufs_rw16(llen,needswap);
+ dbuf->cur += llen;
+ }
+ dp = (struct direct *)(dbuf->buf + dbuf->cur);
+ memcpy(dp, &de, reclen);
+ dp->d_reclen = ufs_rw16(dbuf->size - dbuf->cur, needswap);
+}
+
+/*
+ * cribbed from sys/ufs/ffs/ffs_alloc.c
+ */
+static void
+ffs_write_inode(union dinode *dp, uint32_t ino, const fsinfo_t *fsopts)
+{
+ char *buf;
+ struct ufs1_dinode *dp1;
+ struct ufs2_dinode *dp2, *dip;
+ struct cg *cgp;
+ struct fs *fs;
+ int cg, cgino, i;
+ daddr_t d;
+ char sbbuf[FFS_MAXBSIZE];
+ int32_t initediblk;
+ ffs_opt_t *ffs_opts = fsopts->fs_specific;
+
+ assert (dp != NULL);
+ assert (ino > 0);
+ assert (fsopts != NULL);
+ assert (ffs_opts != NULL);
+
+ fs = (struct fs *)fsopts->superblock;
+ cg = ino_to_cg(fs, ino);
+ cgino = ino % fs->fs_ipg;
+ if (debug & DEBUG_FS_WRITE_INODE)
+ printf("ffs_write_inode: din %p ino %u cg %d cgino %d\n",
+ dp, ino, cg, cgino);
+
+ ffs_rdfs(fsbtodb(fs, cgtod(fs, cg)), (int)fs->fs_cgsize, &sbbuf,
+ fsopts);
+ cgp = (struct cg *)sbbuf;
+ if (!cg_chkmagic_swap(cgp, fsopts->needswap))
+ errx(1, "ffs_write_inode: cg %d: bad magic number", cg);
+
+ assert (isclr(cg_inosused_swap(cgp, fsopts->needswap), cgino));
+
+ buf = malloc(fs->fs_bsize);
+ if (buf == NULL)
+ errx(1, "ffs_write_inode: cg %d: can't alloc inode block", cg);
+
+ dp1 = (struct ufs1_dinode *)buf;
+ dp2 = (struct ufs2_dinode *)buf;
+
+ if (fs->fs_cstotal.cs_nifree == 0)
+ errx(1, "ffs_write_inode: fs out of inodes for ino %u",
+ ino);
+ if (fs->fs_cs(fs, cg).cs_nifree == 0)
+ errx(1,
+ "ffs_write_inode: cg %d out of inodes for ino %u",
+ cg, ino);
+ setbit(cg_inosused_swap(cgp, fsopts->needswap), cgino);
+ ufs_add32(cgp->cg_cs.cs_nifree, -1, fsopts->needswap);
+ fs->fs_cstotal.cs_nifree--;
+ fs->fs_cs(fs, cg).cs_nifree--;
+ if (S_ISDIR(DIP(dp, mode))) {
+ ufs_add32(cgp->cg_cs.cs_ndir, 1, fsopts->needswap);
+ fs->fs_cstotal.cs_ndir++;
+ fs->fs_cs(fs, cg).cs_ndir++;
+ }
+
+ /*
+ * Initialize inode blocks on the fly for UFS2.
+ */
+ initediblk = ufs_rw32(cgp->cg_initediblk, fsopts->needswap);
+ if (ffs_opts->version == 2 && cgino + INOPB(fs) > initediblk &&
+ initediblk < ufs_rw32(cgp->cg_niblk, fsopts->needswap)) {
+ memset(buf, 0, fs->fs_bsize);
+ dip = (struct ufs2_dinode *)buf;
+ srandom(time(NULL));
+ for (i = 0; i < INOPB(fs); i++) {
+ dip->di_gen = random() / 2 + 1;
+ dip++;
+ }
+ ffs_wtfs(fsbtodb(fs, ino_to_fsba(fs,
+ cg * fs->fs_ipg + initediblk)),
+ fs->fs_bsize, buf, fsopts);
+ initediblk += INOPB(fs);
+ cgp->cg_initediblk = ufs_rw32(initediblk, fsopts->needswap);
+ }
+
+
+ ffs_wtfs(fsbtodb(fs, cgtod(fs, cg)), (int)fs->fs_cgsize, &sbbuf,
+ fsopts);
+
+ /* now write inode */
+ d = fsbtodb(fs, ino_to_fsba(fs, ino));
+ ffs_rdfs(d, fs->fs_bsize, buf, fsopts);
+ if (fsopts->needswap) {
+ if (ffs_opts->version == 1)
+ ffs_dinode1_swap(&dp->ffs1_din,
+ &dp1[ino_to_fsbo(fs, ino)]);
+ else
+ ffs_dinode2_swap(&dp->ffs2_din,
+ &dp2[ino_to_fsbo(fs, ino)]);
+ } else {
+ if (ffs_opts->version == 1)
+ dp1[ino_to_fsbo(fs, ino)] = dp->ffs1_din;
+ else
+ dp2[ino_to_fsbo(fs, ino)] = dp->ffs2_din;
+ }
+ ffs_wtfs(d, fs->fs_bsize, buf, fsopts);
+ free(buf);
+}
+
+void
+panic(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vwarnx(fmt, ap);
+ va_end(ap);
+ exit(1);
+}
diff --git a/usr.sbin/makefs/ffs.h b/usr.sbin/makefs/ffs.h
new file mode 100644
index 0000000..65cfbd0
--- /dev/null
+++ b/usr.sbin/makefs/ffs.h
@@ -0,0 +1,70 @@
+/* $NetBSD: ffs.h,v 1.1 2004/12/20 20:51:42 jmc Exp $ */
+
+/*
+ * Copyright (c) 2001-2003 Wasabi Systems, Inc.
+ * All rights reserved.
+ *
+ * Written by Luke Mewburn 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _FFS_H
+#define _FFS_H
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+typedef struct {
+ char label[MAXVOLLEN]; /* volume name/label */
+ int bsize; /* block size */
+ int fsize; /* fragment size */
+ int cpg; /* cylinders per group */
+ int cpgflg; /* cpg was specified by user */
+ int density; /* bytes per inode */
+ int ntracks; /* number of tracks */
+ int nsectors; /* number of sectors */
+ int rpm; /* rpm */
+ int minfree; /* free space threshold */
+ int optimization; /* optimization (space or time) */
+ int maxcontig; /* max contiguous blocks to allocate */
+ int rotdelay; /* rotational delay between blocks */
+ int maxbpg; /* maximum blocks per file in a cyl group */
+ int nrpos; /* # of distinguished rotational positions */
+ int avgfilesize; /* expected average file size */
+ int avgfpdir; /* expected # of files per directory */
+ int version; /* filesystem version (1 = FFS, 2 = UFS2) */
+ int maxbsize; /* maximum extent size */
+ int maxblkspercg; /* max # of blocks per cylinder group */
+ /* XXX: support `old' file systems ? */
+} ffs_opt_t;
+
+#endif /* _FFS_H */
diff --git a/usr.sbin/makefs/ffs/Makefile.inc b/usr.sbin/makefs/ffs/Makefile.inc
new file mode 100644
index 0000000..d681c4e
--- /dev/null
+++ b/usr.sbin/makefs/ffs/Makefile.inc
@@ -0,0 +1,9 @@
+# $FreeBSD$
+#
+
+.PATH: ${.CURDIR}/ffs ${.CURDIR}/../../sys/ufs/ffs
+
+CFLAGS+= -I${.CURDIR}/../../sys/ufs/ffs
+
+SRCS+= ffs_alloc.c ffs_balloc.c ffs_bswap.c ffs_subr.c ufs_bmap.c
+SRCS+= buf.c mkfs.c
diff --git a/usr.sbin/makefs/ffs/buf.c b/usr.sbin/makefs/ffs/buf.c
new file mode 100644
index 0000000..06538f5
--- /dev/null
+++ b/usr.sbin/makefs/ffs/buf.c
@@ -0,0 +1,222 @@
+/* $NetBSD: buf.c,v 1.12 2004/06/20 22:20:18 jmc Exp $ */
+
+/*
+ * Copyright (c) 2001 Wasabi Systems, Inc.
+ * All rights reserved.
+ *
+ * Written by Luke Mewburn 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/time.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "makefs.h"
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include "ffs/buf.h"
+#include "ffs/ufs_inode.h"
+
+extern int sectorsize; /* XXX: from ffs.c & mkfs.c */
+
+TAILQ_HEAD(buftailhead,buf) buftail;
+
+int
+bread(int fd, struct fs *fs, daddr_t blkno, int size, struct buf **bpp)
+{
+ off_t offset;
+ ssize_t rv;
+
+ assert (fs != NULL);
+ assert (bpp != NULL);
+
+ if (debug & DEBUG_BUF_BREAD)
+ printf("bread: fs %p blkno %lld size %d\n",
+ fs, (long long)blkno, size);
+ *bpp = getblk(fd, fs, blkno, size);
+ offset = (*bpp)->b_blkno * sectorsize; /* XXX */
+ if (debug & DEBUG_BUF_BREAD)
+ printf("bread: bp %p blkno %lld offset %lld bcount %ld\n",
+ (*bpp), (long long)(*bpp)->b_blkno, (long long) offset,
+ (*bpp)->b_bcount);
+ if (lseek((*bpp)->b_fd, offset, SEEK_SET) == -1)
+ err(1, "bread: lseek %lld (%lld)",
+ (long long)(*bpp)->b_blkno, (long long)offset);
+ rv = read((*bpp)->b_fd, (*bpp)->b_data, (*bpp)->b_bcount);
+ if (debug & DEBUG_BUF_BREAD)
+ printf("bread: read %ld (%lld) returned %d\n",
+ (*bpp)->b_bcount, (long long)offset, (int)rv);
+ if (rv == -1) /* read error */
+ err(1, "bread: read %ld (%lld) returned %d",
+ (*bpp)->b_bcount, (long long)offset, (int)rv);
+ else if (rv != (*bpp)->b_bcount) /* short read */
+ err(1, "bread: read %ld (%lld) returned %d",
+ (*bpp)->b_bcount, (long long)offset, (int)rv);
+ else
+ return (0);
+}
+
+void
+brelse(struct buf *bp)
+{
+
+ assert (bp != NULL);
+ assert (bp->b_data != NULL);
+
+ if (bp->b_lblkno < 0) {
+ /*
+ * XXX don't remove any buffers with negative logical block
+ * numbers (lblkno), so that we retain the mapping
+ * of negative lblkno -> real blkno that ffs_balloc()
+ * sets up.
+ *
+ * if we instead released these buffers, and implemented
+ * ufs_strategy() (and ufs_bmaparray()) and called those
+ * from bread() and bwrite() to convert the lblkno to
+ * a real blkno, we'd add a lot more code & complexity
+ * and reading off disk, for little gain, because this
+ * simple hack works for our purpose.
+ */
+ bp->b_bcount = 0;
+ return;
+ }
+
+ TAILQ_REMOVE(&buftail, bp, b_tailq);
+ free(bp->b_data);
+ free(bp);
+}
+
+int
+bwrite(struct buf *bp)
+{
+ off_t offset;
+ ssize_t rv;
+
+ assert (bp != NULL);
+ offset = bp->b_blkno * sectorsize; /* XXX */
+ if (debug & DEBUG_BUF_BWRITE)
+ printf("bwrite: bp %p blkno %lld offset %lld bcount %ld\n",
+ bp, (long long)bp->b_blkno, (long long) offset,
+ bp->b_bcount);
+ if (lseek(bp->b_fd, offset, SEEK_SET) == -1)
+ return (errno);
+ rv = write(bp->b_fd, bp->b_data, bp->b_bcount);
+ if (debug & DEBUG_BUF_BWRITE)
+ printf("bwrite: write %ld (offset %lld) returned %lld\n",
+ bp->b_bcount, (long long)offset, (long long)rv);
+ if (rv == bp->b_bcount)
+ return (0);
+ else if (rv == -1) /* write error */
+ return (errno);
+ else /* short write ? */
+ return (EAGAIN);
+}
+
+void
+bcleanup(void)
+{
+ struct buf *bp;
+
+ /*
+ * XXX this really shouldn't be necessary, but i'm curious to
+ * know why there's still some buffers lying around that
+ * aren't brelse()d
+ */
+
+ if (TAILQ_EMPTY(&buftail))
+ return;
+
+ printf("bcleanup: unflushed buffers:\n");
+ TAILQ_FOREACH(bp, &buftail, b_tailq) {
+ printf("\tlblkno %10lld blkno %10lld count %6ld bufsize %6ld\n",
+ (long long)bp->b_lblkno, (long long)bp->b_blkno,
+ bp->b_bcount, bp->b_bufsize);
+ }
+ printf("bcleanup: done\n");
+}
+
+struct buf *
+getblk(int fd, struct fs *fs, daddr_t blkno, int size)
+{
+ static int buftailinitted;
+ struct buf *bp;
+ void *n;
+
+ assert (fs != NULL);
+ if (debug & DEBUG_BUF_GETBLK)
+ printf("getblk: fs %p blkno %lld size %d\n", fs,
+ (long long)blkno, size);
+
+ bp = NULL;
+ if (!buftailinitted) {
+ if (debug & DEBUG_BUF_GETBLK)
+ printf("getblk: initialising tailq\n");
+ TAILQ_INIT(&buftail);
+ buftailinitted = 1;
+ } else {
+ TAILQ_FOREACH(bp, &buftail, b_tailq) {
+ if (bp->b_lblkno != blkno)
+ continue;
+ break;
+ }
+ }
+ if (bp == NULL) {
+ if ((bp = calloc(1, sizeof(struct buf))) == NULL)
+ err(1, "getblk: calloc");
+
+ bp->b_bufsize = 0;
+ bp->b_blkno = bp->b_lblkno = blkno;
+ bp->b_fd = fd;
+ bp->b_fs = fs;
+ bp->b_data = NULL;
+ TAILQ_INSERT_HEAD(&buftail, bp, b_tailq);
+ }
+ bp->b_bcount = size;
+ if (bp->b_data == NULL || bp->b_bcount > bp->b_bufsize) {
+ n = realloc(bp->b_data, size);
+ if (n == NULL)
+ err(1, "getblk: realloc b_data %ld", bp->b_bcount);
+ bp->b_data = n;
+ bp->b_bufsize = size;
+ }
+
+ return (bp);
+}
diff --git a/usr.sbin/makefs/ffs/buf.h b/usr.sbin/makefs/ffs/buf.h
new file mode 100644
index 0000000..02c6713
--- /dev/null
+++ b/usr.sbin/makefs/ffs/buf.h
@@ -0,0 +1,67 @@
+/* $NetBSD: buf.h,v 1.2 2001/11/02 03:12:49 lukem Exp $ */
+
+/*
+ * Copyright (c) 2001 Wasabi Systems, Inc.
+ * All rights reserved.
+ *
+ * Written by Luke Mewburn 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _FFS_BUF_H
+#define _FFS_BUF_H
+
+#include <sys/param.h>
+#include <sys/queue.h>
+
+struct buf {
+ void * b_data;
+ long b_bufsize;
+ long b_bcount;
+ daddr_t b_blkno;
+ daddr_t b_lblkno;
+ int b_fd;
+ struct fs * b_fs;
+
+ TAILQ_ENTRY(buf) b_tailq;
+};
+
+void bcleanup(void);
+int bread(int, struct fs *, daddr_t, int, struct buf **);
+void brelse(struct buf *);
+int bwrite(struct buf *);
+struct buf * getblk(int, struct fs *, daddr_t, int);
+
+#define bdwrite(bp) bwrite(bp)
+#define clrbuf(bp) memset((bp)->b_data, 0, (u_int)(bp)->b_bcount)
+
+#endif /* _FFS_BUF_H */
diff --git a/usr.sbin/makefs/ffs/ffs_alloc.c b/usr.sbin/makefs/ffs/ffs_alloc.c
new file mode 100644
index 0000000..afab869
--- /dev/null
+++ b/usr.sbin/makefs/ffs/ffs_alloc.c
@@ -0,0 +1,681 @@
+/* $NetBSD: ffs_alloc.c,v 1.14 2004/06/20 22:20:18 jmc Exp $ */
+/* From: NetBSD: ffs_alloc.c,v 1.50 2001/09/06 02:16:01 lukem Exp */
+
+/*
+ * 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) 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.
+ * 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.
+ *
+ * @(#)ffs_alloc.c 8.19 (Berkeley) 7/13/95
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/time.h>
+
+#include <errno.h>
+#include <stdint.h>
+
+#include "makefs.h"
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include "ffs/ufs_bswap.h"
+#include "ffs/buf.h"
+#include "ffs/ufs_inode.h"
+#include "ffs/ffs_extern.h"
+
+static int scanc(u_int, const u_char *, const u_char *, int);
+
+static daddr_t ffs_alloccg(struct inode *, int, daddr_t, int);
+static daddr_t ffs_alloccgblk(struct inode *, struct buf *, daddr_t);
+static daddr_t ffs_hashalloc(struct inode *, int, daddr_t, int,
+ daddr_t (*)(struct inode *, int, daddr_t, int));
+static int32_t ffs_mapsearch(struct fs *, struct cg *, daddr_t, int);
+
+/*
+ * Allocate a block in the file system.
+ *
+ * The size of the requested block is given, which must be some
+ * multiple of fs_fsize and <= fs_bsize.
+ * A preference may be optionally specified. If a preference is given
+ * the following hierarchy is used to allocate a block:
+ * 1) allocate the requested block.
+ * 2) allocate a rotationally optimal block in the same cylinder.
+ * 3) allocate a block in the same cylinder group.
+ * 4) quadradically rehash into other cylinder groups, until an
+ * available block is located.
+ * If no block preference is given the following hierarchy is used
+ * to allocate a block:
+ * 1) allocate a block in the cylinder group that contains the
+ * inode for the file.
+ * 2) quadradically rehash into other cylinder groups, until an
+ * available block is located.
+ */
+int
+ffs_alloc(struct inode *ip, daddr_t lbn __unused, daddr_t bpref, int size,
+ daddr_t *bnp)
+{
+ struct fs *fs = ip->i_fs;
+ daddr_t bno;
+ int cg;
+
+ *bnp = 0;
+ if (size > fs->fs_bsize || fragoff(fs, size) != 0) {
+ errx(1, "ffs_alloc: bad size: bsize %d size %d",
+ fs->fs_bsize, size);
+ }
+ if (size == fs->fs_bsize && fs->fs_cstotal.cs_nbfree == 0)
+ goto nospace;
+ if (bpref >= fs->fs_size)
+ bpref = 0;
+ if (bpref == 0)
+ cg = ino_to_cg(fs, ip->i_number);
+ else
+ cg = dtog(fs, bpref);
+ bno = ffs_hashalloc(ip, cg, bpref, size, ffs_alloccg);
+ if (bno > 0) {
+ if (ip->i_fs->fs_magic == FS_UFS1_MAGIC)
+ ip->i_ffs1_blocks += size / DEV_BSIZE;
+ else
+ ip->i_ffs2_blocks += size / DEV_BSIZE;
+ *bnp = bno;
+ return (0);
+ }
+nospace:
+ return (ENOSPC);
+}
+
+/*
+ * Select the desired position for the next block in a file. The file is
+ * logically divided into sections. The first section is composed of the
+ * direct blocks. Each additional section contains fs_maxbpg blocks.
+ *
+ * If no blocks have been allocated in the first section, the policy is to
+ * request a block in the same cylinder group as the inode that describes
+ * the file. If no blocks have been allocated in any other section, the
+ * policy is to place the section in a cylinder group with a greater than
+ * average number of free blocks. An appropriate cylinder group is found
+ * by using a rotor that sweeps the cylinder groups. When a new group of
+ * blocks is needed, the sweep begins in the cylinder group following the
+ * cylinder group from which the previous allocation was made. The sweep
+ * continues until a cylinder group with greater than the average number
+ * of free blocks is found. If the allocation is for the first block in an
+ * indirect block, the information on the previous allocation is unavailable;
+ * here a best guess is made based upon the logical block number being
+ * allocated.
+ *
+ * If a section is already partially allocated, the policy is to
+ * contiguously allocate fs_maxcontig blocks. The end of one of these
+ * contiguous blocks and the beginning of the next is physically separated
+ * so that the disk head will be in transit between them for at least
+ * fs_rotdelay milliseconds. This is to allow time for the processor to
+ * schedule another I/O transfer.
+ */
+/* XXX ondisk32 */
+daddr_t
+ffs_blkpref_ufs1(struct inode *ip, daddr_t lbn, int indx, int32_t *bap)
+{
+ struct fs *fs;
+ int cg;
+ int avgbfree, startcg;
+
+ fs = ip->i_fs;
+ if (indx % fs->fs_maxbpg == 0 || bap[indx - 1] == 0) {
+ if (lbn < NDADDR + NINDIR(fs)) {
+ cg = ino_to_cg(fs, ip->i_number);
+ return (fs->fs_fpg * cg + fs->fs_frag);
+ }
+ /*
+ * Find a cylinder with greater than average number of
+ * unused data blocks.
+ */
+ if (indx == 0 || bap[indx - 1] == 0)
+ startcg =
+ ino_to_cg(fs, ip->i_number) + lbn / fs->fs_maxbpg;
+ else
+ startcg = dtog(fs,
+ ufs_rw32(bap[indx - 1], UFS_FSNEEDSWAP(fs)) + 1);
+ startcg %= fs->fs_ncg;
+ avgbfree = fs->fs_cstotal.cs_nbfree / fs->fs_ncg;
+ for (cg = startcg; cg < fs->fs_ncg; cg++)
+ if (fs->fs_cs(fs, cg).cs_nbfree >= avgbfree)
+ return (fs->fs_fpg * cg + fs->fs_frag);
+ for (cg = 0; cg <= startcg; cg++)
+ if (fs->fs_cs(fs, cg).cs_nbfree >= avgbfree)
+ return (fs->fs_fpg * cg + fs->fs_frag);
+ return (0);
+ }
+ /*
+ * We just always try to lay things out contiguously.
+ */
+ return ufs_rw32(bap[indx - 1], UFS_FSNEEDSWAP(fs)) + fs->fs_frag;
+}
+
+daddr_t
+ffs_blkpref_ufs2(struct inode *ip, daddr_t lbn, int indx, int64_t *bap)
+{
+ struct fs *fs;
+ int cg;
+ int avgbfree, startcg;
+
+ fs = ip->i_fs;
+ if (indx % fs->fs_maxbpg == 0 || bap[indx - 1] == 0) {
+ if (lbn < NDADDR + NINDIR(fs)) {
+ cg = ino_to_cg(fs, ip->i_number);
+ return (fs->fs_fpg * cg + fs->fs_frag);
+ }
+ /*
+ * Find a cylinder with greater than average number of
+ * unused data blocks.
+ */
+ if (indx == 0 || bap[indx - 1] == 0)
+ startcg =
+ ino_to_cg(fs, ip->i_number) + lbn / fs->fs_maxbpg;
+ else
+ startcg = dtog(fs,
+ ufs_rw64(bap[indx - 1], UFS_FSNEEDSWAP(fs)) + 1);
+ startcg %= fs->fs_ncg;
+ avgbfree = fs->fs_cstotal.cs_nbfree / fs->fs_ncg;
+ for (cg = startcg; cg < fs->fs_ncg; cg++)
+ if (fs->fs_cs(fs, cg).cs_nbfree >= avgbfree) {
+ return (fs->fs_fpg * cg + fs->fs_frag);
+ }
+ for (cg = 0; cg < startcg; cg++)
+ if (fs->fs_cs(fs, cg).cs_nbfree >= avgbfree) {
+ return (fs->fs_fpg * cg + fs->fs_frag);
+ }
+ return (0);
+ }
+ /*
+ * We just always try to lay things out contiguously.
+ */
+ return ufs_rw64(bap[indx - 1], UFS_FSNEEDSWAP(fs)) + fs->fs_frag;
+}
+
+/*
+ * Implement the cylinder overflow algorithm.
+ *
+ * The policy implemented by this algorithm is:
+ * 1) allocate the block in its requested cylinder group.
+ * 2) quadradically rehash on the cylinder group number.
+ * 3) brute force search for a free block.
+ *
+ * `size': size for data blocks, mode for inodes
+ */
+/*VARARGS5*/
+static daddr_t
+ffs_hashalloc(struct inode *ip, int cg, daddr_t pref, int size,
+ daddr_t (*allocator)(struct inode *, int, daddr_t, int))
+{
+ struct fs *fs;
+ daddr_t result;
+ int i, icg = cg;
+
+ fs = ip->i_fs;
+ /*
+ * 1: preferred cylinder group
+ */
+ result = (*allocator)(ip, cg, pref, size);
+ if (result)
+ return (result);
+ /*
+ * 2: quadratic rehash
+ */
+ for (i = 1; i < fs->fs_ncg; i *= 2) {
+ cg += i;
+ if (cg >= fs->fs_ncg)
+ cg -= fs->fs_ncg;
+ result = (*allocator)(ip, cg, 0, size);
+ if (result)
+ return (result);
+ }
+ /*
+ * 3: brute force search
+ * Note that we start at i == 2, since 0 was checked initially,
+ * and 1 is always checked in the quadratic rehash.
+ */
+ cg = (icg + 2) % fs->fs_ncg;
+ for (i = 2; i < fs->fs_ncg; i++) {
+ result = (*allocator)(ip, cg, 0, size);
+ if (result)
+ return (result);
+ cg++;
+ if (cg == fs->fs_ncg)
+ cg = 0;
+ }
+ return (0);
+}
+
+/*
+ * Determine whether a block can be allocated.
+ *
+ * Check to see if a block of the appropriate size is available,
+ * and if it is, allocate it.
+ */
+static daddr_t
+ffs_alloccg(struct inode *ip, int cg, daddr_t bpref, int size)
+{
+ struct cg *cgp;
+ struct buf *bp;
+ daddr_t bno, blkno;
+ int error, frags, allocsiz, i;
+ struct fs *fs = ip->i_fs;
+ const int needswap = UFS_FSNEEDSWAP(fs);
+
+ if (fs->fs_cs(fs, cg).cs_nbfree == 0 && size == fs->fs_bsize)
+ return (0);
+ error = bread(ip->i_fd, ip->i_fs, fsbtodb(fs, cgtod(fs, cg)),
+ (int)fs->fs_cgsize, &bp);
+ if (error) {
+ brelse(bp);
+ return (0);
+ }
+ cgp = (struct cg *)bp->b_data;
+ if (!cg_chkmagic_swap(cgp, needswap) ||
+ (cgp->cg_cs.cs_nbfree == 0 && size == fs->fs_bsize)) {
+ brelse(bp);
+ return (0);
+ }
+ if (size == fs->fs_bsize) {
+ bno = ffs_alloccgblk(ip, bp, bpref);
+ bdwrite(bp);
+ return (bno);
+ }
+ /*
+ * check to see if any fragments are already available
+ * allocsiz is the size which will be allocated, hacking
+ * it down to a smaller size if necessary
+ */
+ frags = numfrags(fs, size);
+ for (allocsiz = frags; allocsiz < fs->fs_frag; allocsiz++)
+ if (cgp->cg_frsum[allocsiz] != 0)
+ break;
+ if (allocsiz == fs->fs_frag) {
+ /*
+ * no fragments were available, so a block will be
+ * allocated, and hacked up
+ */
+ if (cgp->cg_cs.cs_nbfree == 0) {
+ brelse(bp);
+ return (0);
+ }
+ bno = ffs_alloccgblk(ip, bp, bpref);
+ bpref = dtogd(fs, bno);
+ for (i = frags; i < fs->fs_frag; i++)
+ setbit(cg_blksfree_swap(cgp, needswap), bpref + i);
+ i = fs->fs_frag - frags;
+ ufs_add32(cgp->cg_cs.cs_nffree, i, needswap);
+ fs->fs_cstotal.cs_nffree += i;
+ fs->fs_cs(fs, cg).cs_nffree += i;
+ fs->fs_fmod = 1;
+ ufs_add32(cgp->cg_frsum[i], 1, needswap);
+ bdwrite(bp);
+ return (bno);
+ }
+ bno = ffs_mapsearch(fs, cgp, bpref, allocsiz);
+ for (i = 0; i < frags; i++)
+ clrbit(cg_blksfree_swap(cgp, needswap), bno + i);
+ ufs_add32(cgp->cg_cs.cs_nffree, -frags, needswap);
+ fs->fs_cstotal.cs_nffree -= frags;
+ fs->fs_cs(fs, cg).cs_nffree -= frags;
+ fs->fs_fmod = 1;
+ ufs_add32(cgp->cg_frsum[allocsiz], -1, needswap);
+ if (frags != allocsiz)
+ ufs_add32(cgp->cg_frsum[allocsiz - frags], 1, needswap);
+ blkno = cg * fs->fs_fpg + bno;
+ bdwrite(bp);
+ return blkno;
+}
+
+/*
+ * Allocate a block in a cylinder group.
+ *
+ * This algorithm implements the following policy:
+ * 1) allocate the requested block.
+ * 2) allocate a rotationally optimal block in the same cylinder.
+ * 3) allocate the next available block on the block rotor for the
+ * specified cylinder group.
+ * Note that this routine only allocates fs_bsize blocks; these
+ * blocks may be fragmented by the routine that allocates them.
+ */
+static daddr_t
+ffs_alloccgblk(struct inode *ip, struct buf *bp, daddr_t bpref)
+{
+ struct cg *cgp;
+ daddr_t blkno;
+ int32_t bno;
+ struct fs *fs = ip->i_fs;
+ const int needswap = UFS_FSNEEDSWAP(fs);
+ u_int8_t *blksfree_swap;
+
+ cgp = (struct cg *)bp->b_data;
+ blksfree_swap = cg_blksfree_swap(cgp, needswap);
+ if (bpref == 0 || (uint32_t)dtog(fs, bpref) != ufs_rw32(cgp->cg_cgx, needswap)) {
+ bpref = ufs_rw32(cgp->cg_rotor, needswap);
+ } else {
+ bpref = blknum(fs, bpref);
+ bno = dtogd(fs, bpref);
+ /*
+ * if the requested block is available, use it
+ */
+ if (ffs_isblock(fs, blksfree_swap, fragstoblks(fs, bno)))
+ goto gotit;
+ }
+ /*
+ * Take the next available one in this cylinder group.
+ */
+ bno = ffs_mapsearch(fs, cgp, bpref, (int)fs->fs_frag);
+ if (bno < 0)
+ return (0);
+ cgp->cg_rotor = ufs_rw32(bno, needswap);
+gotit:
+ blkno = fragstoblks(fs, bno);
+ ffs_clrblock(fs, blksfree_swap, (long)blkno);
+ ffs_clusteracct(fs, cgp, blkno, -1);
+ ufs_add32(cgp->cg_cs.cs_nbfree, -1, needswap);
+ fs->fs_cstotal.cs_nbfree--;
+ fs->fs_cs(fs, ufs_rw32(cgp->cg_cgx, needswap)).cs_nbfree--;
+ fs->fs_fmod = 1;
+ blkno = ufs_rw32(cgp->cg_cgx, needswap) * fs->fs_fpg + bno;
+ return (blkno);
+}
+
+/*
+ * Free a block or fragment.
+ *
+ * The specified block or fragment is placed back in the
+ * free map. If a fragment is deallocated, a possible
+ * block reassembly is checked.
+ */
+void
+ffs_blkfree(struct inode *ip, daddr_t bno, long size)
+{
+ struct cg *cgp;
+ struct buf *bp;
+ int32_t fragno, cgbno;
+ int i, error, cg, blk, frags, bbase;
+ struct fs *fs = ip->i_fs;
+ const int needswap = UFS_FSNEEDSWAP(fs);
+
+ if (size > fs->fs_bsize || fragoff(fs, size) != 0 ||
+ fragnum(fs, bno) + numfrags(fs, size) > fs->fs_frag) {
+ errx(1, "blkfree: bad size: bno %lld bsize %d size %ld",
+ (long long)bno, fs->fs_bsize, size);
+ }
+ cg = dtog(fs, bno);
+ if (bno >= fs->fs_size) {
+ warnx("bad block %lld, ino %ju", (long long)bno,
+ (uintmax_t)ip->i_number);
+ return;
+ }
+ error = bread(ip->i_fd, ip->i_fs, fsbtodb(fs, cgtod(fs, cg)),
+ (int)fs->fs_cgsize, &bp);
+ if (error) {
+ brelse(bp);
+ return;
+ }
+ cgp = (struct cg *)bp->b_data;
+ if (!cg_chkmagic_swap(cgp, needswap)) {
+ brelse(bp);
+ return;
+ }
+ cgbno = dtogd(fs, bno);
+ if (size == fs->fs_bsize) {
+ fragno = fragstoblks(fs, cgbno);
+ if (!ffs_isfreeblock(fs, cg_blksfree_swap(cgp, needswap), fragno)) {
+ errx(1, "blkfree: freeing free block %lld",
+ (long long)bno);
+ }
+ ffs_setblock(fs, cg_blksfree_swap(cgp, needswap), fragno);
+ ffs_clusteracct(fs, cgp, fragno, 1);
+ ufs_add32(cgp->cg_cs.cs_nbfree, 1, needswap);
+ 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, cg_blksfree_swap(cgp, needswap), bbase);
+ ffs_fragacct_swap(fs, blk, cgp->cg_frsum, -1, needswap);
+ /*
+ * deallocate the fragment
+ */
+ frags = numfrags(fs, size);
+ for (i = 0; i < frags; i++) {
+ if (isset(cg_blksfree_swap(cgp, needswap), cgbno + i)) {
+ errx(1, "blkfree: freeing free frag: block %lld",
+ (long long)(cgbno + i));
+ }
+ setbit(cg_blksfree_swap(cgp, needswap), cgbno + i);
+ }
+ ufs_add32(cgp->cg_cs.cs_nffree, i, needswap);
+ 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, cg_blksfree_swap(cgp, needswap), bbase);
+ ffs_fragacct_swap(fs, blk, cgp->cg_frsum, 1, needswap);
+ /*
+ * if a complete block has been reassembled, account for it
+ */
+ fragno = fragstoblks(fs, bbase);
+ if (ffs_isblock(fs, cg_blksfree_swap(cgp, needswap), fragno)) {
+ ufs_add32(cgp->cg_cs.cs_nffree, -fs->fs_frag, needswap);
+ fs->fs_cstotal.cs_nffree -= fs->fs_frag;
+ fs->fs_cs(fs, cg).cs_nffree -= fs->fs_frag;
+ ffs_clusteracct(fs, cgp, fragno, 1);
+ ufs_add32(cgp->cg_cs.cs_nbfree, 1, needswap);
+ fs->fs_cstotal.cs_nbfree++;
+ fs->fs_cs(fs, cg).cs_nbfree++;
+ }
+ }
+ fs->fs_fmod = 1;
+ bdwrite(bp);
+}
+
+
+static int
+scanc(u_int size, const u_char *cp, const u_char table[], int mask)
+{
+ const u_char *end = &cp[size];
+
+ while (cp < end && (table[*cp] & mask) == 0)
+ cp++;
+ return (end - cp);
+}
+
+/*
+ * Find a block of the specified size in the specified cylinder group.
+ *
+ * It is a panic if a request is made to find a block if none are
+ * available.
+ */
+static int32_t
+ffs_mapsearch(struct fs *fs, struct cg *cgp, daddr_t bpref, int allocsiz)
+{
+ int32_t bno;
+ int start, len, loc, i;
+ int blk, field, subfield, pos;
+ int ostart, olen;
+ const int needswap = UFS_FSNEEDSWAP(fs);
+
+ /*
+ * find the fragment by searching through the free block
+ * map for an appropriate bit pattern
+ */
+ if (bpref)
+ start = dtogd(fs, bpref) / NBBY;
+ else
+ start = ufs_rw32(cgp->cg_frotor, needswap) / NBBY;
+ len = howmany(fs->fs_fpg, NBBY) - start;
+ ostart = start;
+ olen = len;
+ loc = scanc((u_int)len,
+ (const u_char *)&cg_blksfree_swap(cgp, needswap)[start],
+ (const u_char *)fragtbl[fs->fs_frag],
+ (1 << (allocsiz - 1 + (fs->fs_frag % NBBY))));
+ if (loc == 0) {
+ len = start + 1;
+ start = 0;
+ loc = scanc((u_int)len,
+ (const u_char *)&cg_blksfree_swap(cgp, needswap)[0],
+ (const u_char *)fragtbl[fs->fs_frag],
+ (1 << (allocsiz - 1 + (fs->fs_frag % NBBY))));
+ if (loc == 0) {
+ errx(1,
+ "ffs_alloccg: map corrupted: start %d len %d offset %d %ld",
+ ostart, olen,
+ ufs_rw32(cgp->cg_freeoff, needswap),
+ (long)cg_blksfree_swap(cgp, needswap) - (long)cgp);
+ /* NOTREACHED */
+ }
+ }
+ bno = (start + len - loc) * NBBY;
+ cgp->cg_frotor = ufs_rw32(bno, needswap);
+ /*
+ * found the byte in the map
+ * sift through the bits to find the selected frag
+ */
+ for (i = bno + NBBY; bno < i; bno += fs->fs_frag) {
+ blk = blkmap(fs, cg_blksfree_swap(cgp, needswap), bno);
+ blk <<= 1;
+ field = around[allocsiz];
+ subfield = inside[allocsiz];
+ for (pos = 0; pos <= fs->fs_frag - allocsiz; pos++) {
+ if ((blk & field) == subfield)
+ return (bno + pos);
+ field <<= 1;
+ subfield <<= 1;
+ }
+ }
+ errx(1, "ffs_alloccg: block not in map: bno %lld", (long long)bno);
+ return (-1);
+}
+
+/*
+ * Update the cluster map because of an allocation or free.
+ *
+ * Cnt == 1 means free; cnt == -1 means allocating.
+ */
+void
+ffs_clusteracct(struct fs *fs, struct cg *cgp, int32_t blkno, int cnt)
+{
+ int32_t *sump;
+ int32_t *lp;
+ u_char *freemapp, *mapp;
+ int i, start, end, forw, back, map, bit;
+ const int needswap = UFS_FSNEEDSWAP(fs);
+
+ if (fs->fs_contigsumsize <= 0)
+ return;
+ freemapp = cg_clustersfree_swap(cgp, needswap);
+ sump = cg_clustersum_swap(cgp, needswap);
+ /*
+ * Allocate or clear the actual block.
+ */
+ if (cnt > 0)
+ setbit(freemapp, blkno);
+ else
+ clrbit(freemapp, blkno);
+ /*
+ * Find the size of the cluster going forward.
+ */
+ start = blkno + 1;
+ end = start + fs->fs_contigsumsize;
+ if ((unsigned)end >= ufs_rw32(cgp->cg_nclusterblks, needswap))
+ end = ufs_rw32(cgp->cg_nclusterblks, needswap);
+ mapp = &freemapp[start / NBBY];
+ map = *mapp++;
+ bit = 1 << (start % NBBY);
+ for (i = start; i < end; i++) {
+ if ((map & bit) == 0)
+ break;
+ if ((i & (NBBY - 1)) != (NBBY - 1)) {
+ bit <<= 1;
+ } else {
+ map = *mapp++;
+ bit = 1;
+ }
+ }
+ forw = i - start;
+ /*
+ * Find the size of the cluster going backward.
+ */
+ start = blkno - 1;
+ end = start - fs->fs_contigsumsize;
+ if (end < 0)
+ end = -1;
+ mapp = &freemapp[start / NBBY];
+ map = *mapp--;
+ bit = 1 << (start % NBBY);
+ for (i = start; i > end; i--) {
+ if ((map & bit) == 0)
+ break;
+ if ((i & (NBBY - 1)) != 0) {
+ bit >>= 1;
+ } else {
+ map = *mapp--;
+ bit = 1 << (NBBY - 1);
+ }
+ }
+ back = start - i;
+ /*
+ * Account for old cluster and the possibly new forward and
+ * back clusters.
+ */
+ i = back + forw + 1;
+ if (i > fs->fs_contigsumsize)
+ i = fs->fs_contigsumsize;
+ ufs_add32(sump[i], cnt, needswap);
+ if (back > 0)
+ ufs_add32(sump[back], -cnt, needswap);
+ if (forw > 0)
+ ufs_add32(sump[forw], -cnt, needswap);
+
+ /*
+ * Update cluster summary information.
+ */
+ lp = &sump[fs->fs_contigsumsize];
+ for (i = fs->fs_contigsumsize; i > 0; i--)
+ if (ufs_rw32(*lp--, needswap) > 0)
+ break;
+ fs->fs_maxcluster[ufs_rw32(cgp->cg_cgx, needswap)] = i;
+}
diff --git a/usr.sbin/makefs/ffs/ffs_balloc.c b/usr.sbin/makefs/ffs/ffs_balloc.c
new file mode 100644
index 0000000..0a048ad
--- /dev/null
+++ b/usr.sbin/makefs/ffs/ffs_balloc.c
@@ -0,0 +1,578 @@
+/* $NetBSD: ffs_balloc.c,v 1.13 2004/06/20 22:20:18 jmc Exp $ */
+/* From NetBSD: ffs_balloc.c,v 1.25 2001/08/08 08:36:36 lukem Exp */
+
+/*
+ * 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.
+ * 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.
+ *
+ * @(#)ffs_balloc.c 8.8 (Berkeley) 6/16/95
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/time.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "makefs.h"
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include "ffs/ufs_bswap.h"
+#include "ffs/buf.h"
+#include "ffs/ufs_inode.h"
+#include "ffs/ffs_extern.h"
+
+static int ffs_balloc_ufs1(struct inode *, off_t, int, struct buf **);
+static int ffs_balloc_ufs2(struct inode *, off_t, int, struct buf **);
+
+/*
+ * Balloc defines the structure of file system storage
+ * by allocating the physical blocks on a device given
+ * the inode and the logical block number in a file.
+ *
+ * Assume: flags == B_SYNC | B_CLRBUF
+ */
+
+int
+ffs_balloc(struct inode *ip, off_t offset, int bufsize, struct buf **bpp)
+{
+ if (ip->i_fs->fs_magic == FS_UFS2_MAGIC)
+ return ffs_balloc_ufs2(ip, offset, bufsize, bpp);
+ else
+ return ffs_balloc_ufs1(ip, offset, bufsize, bpp);
+}
+
+static int
+ffs_balloc_ufs1(struct inode *ip, off_t offset, int bufsize, struct buf **bpp)
+{
+ daddr_t lbn, lastlbn;
+ int size;
+ int32_t nb;
+ struct buf *bp, *nbp;
+ struct fs *fs = ip->i_fs;
+ struct indir indirs[NIADDR + 2];
+ daddr_t newb, pref;
+ int32_t *bap;
+ int osize, nsize, num, i, error;
+ int32_t *allocblk, allociblk[NIADDR + 1];
+ int32_t *allocib;
+ const int needswap = UFS_FSNEEDSWAP(fs);
+
+ lbn = lblkno(fs, offset);
+ size = blkoff(fs, offset) + bufsize;
+ if (bpp != NULL) {
+ *bpp = NULL;
+ }
+
+ assert(size <= fs->fs_bsize);
+ if (lbn < 0)
+ return (EFBIG);
+
+ /*
+ * If the next write will extend the file into a new block,
+ * and the file is currently composed of a fragment
+ * this fragment has to be extended to be a full block.
+ */
+
+ lastlbn = lblkno(fs, ip->i_ffs1_size);
+ if (lastlbn < NDADDR && lastlbn < lbn) {
+ nb = lastlbn;
+ osize = blksize(fs, ip, nb);
+ if (osize < fs->fs_bsize && osize > 0) {
+ warnx("need to ffs_realloccg; not supported!");
+ abort();
+ }
+ }
+
+ /*
+ * The first NDADDR blocks are direct blocks
+ */
+
+ if (lbn < NDADDR) {
+ nb = ufs_rw32(ip->i_ffs1_db[lbn], needswap);
+ if (nb != 0 && ip->i_ffs1_size >= lblktosize(fs, lbn + 1)) {
+
+ /*
+ * The block is an already-allocated direct block
+ * and the file already extends past this block,
+ * thus this must be a whole block.
+ * Just read the block (if requested).
+ */
+
+ if (bpp != NULL) {
+ error = bread(ip->i_fd, ip->i_fs, lbn,
+ fs->fs_bsize, bpp);
+ if (error) {
+ brelse(*bpp);
+ return (error);
+ }
+ }
+ return (0);
+ }
+ if (nb != 0) {
+
+ /*
+ * Consider need to reallocate a fragment.
+ */
+
+ osize = fragroundup(fs, blkoff(fs, ip->i_ffs1_size));
+ nsize = fragroundup(fs, size);
+ if (nsize <= osize) {
+
+ /*
+ * The existing block is already
+ * at least as big as we want.
+ * Just read the block (if requested).
+ */
+
+ if (bpp != NULL) {
+ error = bread(ip->i_fd, ip->i_fs, lbn,
+ osize, bpp);
+ if (error) {
+ brelse(*bpp);
+ return (error);
+ }
+ }
+ return 0;
+ } else {
+ warnx("need to ffs_realloccg; not supported!");
+ abort();
+ }
+ } else {
+
+ /*
+ * the block was not previously allocated,
+ * allocate a new block or fragment.
+ */
+
+ if (ip->i_ffs1_size < lblktosize(fs, lbn + 1))
+ nsize = fragroundup(fs, size);
+ else
+ nsize = fs->fs_bsize;
+ error = ffs_alloc(ip, lbn,
+ ffs_blkpref_ufs1(ip, lbn, (int)lbn,
+ &ip->i_ffs1_db[0]),
+ nsize, &newb);
+ if (error)
+ return (error);
+ if (bpp != NULL) {
+ bp = getblk(ip->i_fd, ip->i_fs, lbn, nsize);
+ bp->b_blkno = fsbtodb(fs, newb);
+ clrbuf(bp);
+ *bpp = bp;
+ }
+ }
+ ip->i_ffs1_db[lbn] = ufs_rw32((int32_t)newb, needswap);
+ return (0);
+ }
+
+ /*
+ * Determine the number of levels of indirection.
+ */
+
+ pref = 0;
+ if ((error = ufs_getlbns(ip, lbn, indirs, &num)) != 0)
+ return (error);
+
+ if (num < 1) {
+ warnx("ffs_balloc: ufs_getlbns returned indirect block");
+ abort();
+ }
+
+ /*
+ * Fetch the first indirect block allocating if necessary.
+ */
+
+ --num;
+ nb = ufs_rw32(ip->i_ffs1_ib[indirs[0].in_off], needswap);
+ allocib = NULL;
+ allocblk = allociblk;
+ if (nb == 0) {
+ pref = ffs_blkpref_ufs1(ip, lbn, 0, (int32_t *)0);
+ error = ffs_alloc(ip, lbn, pref, (int)fs->fs_bsize, &newb);
+ if (error)
+ return error;
+ nb = newb;
+ *allocblk++ = nb;
+ bp = getblk(ip->i_fd, ip->i_fs, indirs[1].in_lbn, fs->fs_bsize);
+ bp->b_blkno = fsbtodb(fs, nb);
+ clrbuf(bp);
+ /*
+ * Write synchronously so that indirect blocks
+ * never point at garbage.
+ */
+ if ((error = bwrite(bp)) != 0)
+ return error;
+ allocib = &ip->i_ffs1_ib[indirs[0].in_off];
+ *allocib = ufs_rw32((int32_t)nb, needswap);
+ }
+
+ /*
+ * Fetch through the indirect blocks, allocating as necessary.
+ */
+
+ for (i = 1;;) {
+ error = bread(ip->i_fd, ip->i_fs, indirs[i].in_lbn,
+ fs->fs_bsize, &bp);
+ if (error) {
+ brelse(bp);
+ return error;
+ }
+ bap = (int32_t *)bp->b_data;
+ nb = ufs_rw32(bap[indirs[i].in_off], needswap);
+ if (i == num)
+ break;
+ i++;
+ if (nb != 0) {
+ brelse(bp);
+ continue;
+ }
+ if (pref == 0)
+ pref = ffs_blkpref_ufs1(ip, lbn, 0, (int32_t *)0);
+ error = ffs_alloc(ip, lbn, pref, (int)fs->fs_bsize, &newb);
+ if (error) {
+ brelse(bp);
+ return error;
+ }
+ nb = newb;
+ *allocblk++ = nb;
+ nbp = getblk(ip->i_fd, ip->i_fs, indirs[i].in_lbn,
+ fs->fs_bsize);
+ nbp->b_blkno = fsbtodb(fs, nb);
+ clrbuf(nbp);
+ /*
+ * Write synchronously so that indirect blocks
+ * never point at garbage.
+ */
+
+ if ((error = bwrite(nbp)) != 0) {
+ brelse(bp);
+ return error;
+ }
+ bap[indirs[i - 1].in_off] = ufs_rw32(nb, needswap);
+
+ bwrite(bp);
+ }
+
+ /*
+ * Get the data block, allocating if necessary.
+ */
+
+ if (nb == 0) {
+ pref = ffs_blkpref_ufs1(ip, lbn, indirs[num].in_off, &bap[0]);
+ error = ffs_alloc(ip, lbn, pref, (int)fs->fs_bsize, &newb);
+ if (error) {
+ brelse(bp);
+ return error;
+ }
+ nb = newb;
+ *allocblk++ = nb;
+ if (bpp != NULL) {
+ nbp = getblk(ip->i_fd, ip->i_fs, lbn, fs->fs_bsize);
+ nbp->b_blkno = fsbtodb(fs, nb);
+ clrbuf(nbp);
+ *bpp = nbp;
+ }
+ bap[indirs[num].in_off] = ufs_rw32(nb, needswap);
+
+ /*
+ * If required, write synchronously, otherwise use
+ * delayed write.
+ */
+ bwrite(bp);
+ return (0);
+ }
+ brelse(bp);
+ if (bpp != NULL) {
+ error = bread(ip->i_fd, ip->i_fs, lbn, (int)fs->fs_bsize, &nbp);
+ if (error) {
+ brelse(nbp);
+ return error;
+ }
+ *bpp = nbp;
+ }
+ return (0);
+}
+
+static int
+ffs_balloc_ufs2(struct inode *ip, off_t offset, int bufsize, struct buf **bpp)
+{
+ daddr_t lbn, lastlbn;
+ int size;
+ struct buf *bp, *nbp;
+ struct fs *fs = ip->i_fs;
+ struct indir indirs[NIADDR + 2];
+ daddr_t newb, pref, nb;
+ int64_t *bap;
+ int osize, nsize, num, i, error;
+ int64_t *allocblk, allociblk[NIADDR + 1];
+ int64_t *allocib;
+ const int needswap = UFS_FSNEEDSWAP(fs);
+
+ lbn = lblkno(fs, offset);
+ size = blkoff(fs, offset) + bufsize;
+ if (bpp != NULL) {
+ *bpp = NULL;
+ }
+
+ assert(size <= fs->fs_bsize);
+ if (lbn < 0)
+ return (EFBIG);
+
+ /*
+ * If the next write will extend the file into a new block,
+ * and the file is currently composed of a fragment
+ * this fragment has to be extended to be a full block.
+ */
+
+ lastlbn = lblkno(fs, ip->i_ffs2_size);
+ if (lastlbn < NDADDR && lastlbn < lbn) {
+ nb = lastlbn;
+ osize = blksize(fs, ip, nb);
+ if (osize < fs->fs_bsize && osize > 0) {
+ warnx("need to ffs_realloccg; not supported!");
+ abort();
+ }
+ }
+
+ /*
+ * The first NDADDR blocks are direct blocks
+ */
+
+ if (lbn < NDADDR) {
+ nb = ufs_rw64(ip->i_ffs2_db[lbn], needswap);
+ if (nb != 0 && ip->i_ffs2_size >= lblktosize(fs, lbn + 1)) {
+
+ /*
+ * The block is an already-allocated direct block
+ * and the file already extends past this block,
+ * thus this must be a whole block.
+ * Just read the block (if requested).
+ */
+
+ if (bpp != NULL) {
+ error = bread(ip->i_fd, ip->i_fs, lbn,
+ fs->fs_bsize, bpp);
+ if (error) {
+ brelse(*bpp);
+ return (error);
+ }
+ }
+ return (0);
+ }
+ if (nb != 0) {
+
+ /*
+ * Consider need to reallocate a fragment.
+ */
+
+ osize = fragroundup(fs, blkoff(fs, ip->i_ffs2_size));
+ nsize = fragroundup(fs, size);
+ if (nsize <= osize) {
+
+ /*
+ * The existing block is already
+ * at least as big as we want.
+ * Just read the block (if requested).
+ */
+
+ if (bpp != NULL) {
+ error = bread(ip->i_fd, ip->i_fs, lbn,
+ osize, bpp);
+ if (error) {
+ brelse(*bpp);
+ return (error);
+ }
+ }
+ return 0;
+ } else {
+ warnx("need to ffs_realloccg; not supported!");
+ abort();
+ }
+ } else {
+
+ /*
+ * the block was not previously allocated,
+ * allocate a new block or fragment.
+ */
+
+ if (ip->i_ffs2_size < lblktosize(fs, lbn + 1))
+ nsize = fragroundup(fs, size);
+ else
+ nsize = fs->fs_bsize;
+ error = ffs_alloc(ip, lbn,
+ ffs_blkpref_ufs2(ip, lbn, (int)lbn,
+ &ip->i_ffs2_db[0]),
+ nsize, &newb);
+ if (error)
+ return (error);
+ if (bpp != NULL) {
+ bp = getblk(ip->i_fd, ip->i_fs, lbn, nsize);
+ bp->b_blkno = fsbtodb(fs, newb);
+ clrbuf(bp);
+ *bpp = bp;
+ }
+ }
+ ip->i_ffs2_db[lbn] = ufs_rw64(newb, needswap);
+ return (0);
+ }
+
+ /*
+ * Determine the number of levels of indirection.
+ */
+
+ pref = 0;
+ if ((error = ufs_getlbns(ip, lbn, indirs, &num)) != 0)
+ return (error);
+
+ if (num < 1) {
+ warnx("ffs_balloc: ufs_getlbns returned indirect block");
+ abort();
+ }
+
+ /*
+ * Fetch the first indirect block allocating if necessary.
+ */
+
+ --num;
+ nb = ufs_rw64(ip->i_ffs2_ib[indirs[0].in_off], needswap);
+ allocib = NULL;
+ allocblk = allociblk;
+ if (nb == 0) {
+ pref = ffs_blkpref_ufs2(ip, lbn, 0, (int64_t *)0);
+ error = ffs_alloc(ip, lbn, pref, (int)fs->fs_bsize, &newb);
+ if (error)
+ return error;
+ nb = newb;
+ *allocblk++ = nb;
+ bp = getblk(ip->i_fd, ip->i_fs, indirs[1].in_lbn, fs->fs_bsize);
+ bp->b_blkno = fsbtodb(fs, nb);
+ clrbuf(bp);
+ /*
+ * Write synchronously so that indirect blocks
+ * never point at garbage.
+ */
+ if ((error = bwrite(bp)) != 0)
+ return error;
+ allocib = &ip->i_ffs2_ib[indirs[0].in_off];
+ *allocib = ufs_rw64(nb, needswap);
+ }
+
+ /*
+ * Fetch through the indirect blocks, allocating as necessary.
+ */
+
+ for (i = 1;;) {
+ error = bread(ip->i_fd, ip->i_fs, indirs[i].in_lbn,
+ fs->fs_bsize, &bp);
+ if (error) {
+ brelse(bp);
+ return error;
+ }
+ bap = (int64_t *)bp->b_data;
+ nb = ufs_rw64(bap[indirs[i].in_off], needswap);
+ if (i == num)
+ break;
+ i++;
+ if (nb != 0) {
+ brelse(bp);
+ continue;
+ }
+ if (pref == 0)
+ pref = ffs_blkpref_ufs2(ip, lbn, 0, (int64_t *)0);
+ error = ffs_alloc(ip, lbn, pref, (int)fs->fs_bsize, &newb);
+ if (error) {
+ brelse(bp);
+ return error;
+ }
+ nb = newb;
+ *allocblk++ = nb;
+ nbp = getblk(ip->i_fd, ip->i_fs, indirs[i].in_lbn,
+ fs->fs_bsize);
+ nbp->b_blkno = fsbtodb(fs, nb);
+ clrbuf(nbp);
+ /*
+ * Write synchronously so that indirect blocks
+ * never point at garbage.
+ */
+
+ if ((error = bwrite(nbp)) != 0) {
+ brelse(bp);
+ return error;
+ }
+ bap[indirs[i - 1].in_off] = ufs_rw64(nb, needswap);
+
+ bwrite(bp);
+ }
+
+ /*
+ * Get the data block, allocating if necessary.
+ */
+
+ if (nb == 0) {
+ pref = ffs_blkpref_ufs2(ip, lbn, indirs[num].in_off, &bap[0]);
+ error = ffs_alloc(ip, lbn, pref, (int)fs->fs_bsize, &newb);
+ if (error) {
+ brelse(bp);
+ return error;
+ }
+ nb = newb;
+ *allocblk++ = nb;
+ if (bpp != NULL) {
+ nbp = getblk(ip->i_fd, ip->i_fs, lbn, fs->fs_bsize);
+ nbp->b_blkno = fsbtodb(fs, nb);
+ clrbuf(nbp);
+ *bpp = nbp;
+ }
+ bap[indirs[num].in_off] = ufs_rw64(nb, needswap);
+
+ /*
+ * If required, write synchronously, otherwise use
+ * delayed write.
+ */
+ bwrite(bp);
+ return (0);
+ }
+ brelse(bp);
+ if (bpp != NULL) {
+ error = bread(ip->i_fd, ip->i_fs, lbn, (int)fs->fs_bsize, &nbp);
+ if (error) {
+ brelse(nbp);
+ return error;
+ }
+ *bpp = nbp;
+ }
+ return (0);
+}
diff --git a/usr.sbin/makefs/ffs/ffs_bswap.c b/usr.sbin/makefs/ffs/ffs_bswap.c
new file mode 100644
index 0000000..e62eb19
--- /dev/null
+++ b/usr.sbin/makefs/ffs/ffs_bswap.c
@@ -0,0 +1,260 @@
+/* $NetBSD: ffs_bswap.c,v 1.28 2004/05/25 14:54:59 hannken Exp $ */
+
+/*
+ * Copyright (c) 1998 Manuel Bouyer.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 Manuel Bouyer.
+ * 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>
+#if defined(_KERNEL)
+#include <sys/systm.h>
+#endif
+
+#include <ufs/ufs/dinode.h>
+#include "ffs/ufs_bswap.h"
+#include <ufs/ffs/fs.h>
+
+#if !defined(_KERNEL)
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#define panic(x) printf("%s\n", (x)), abort()
+#endif
+
+#define fs_old_postbloff fs_spare5[0]
+#define fs_old_rotbloff fs_spare5[1]
+#define fs_old_postbl_start fs_maxbsize
+#define fs_old_headswitch fs_id[0]
+#define fs_old_trkseek fs_id[1]
+#define fs_old_csmask fs_spare1[0]
+#define fs_old_csshift fs_spare1[1]
+
+#define FS_42POSTBLFMT -1 /* 4.2BSD rotational table format */
+#define FS_DYNAMICPOSTBLFMT 1 /* dynamic rotational table format */
+
+void ffs_csum_swap(struct csum *o, struct csum *n, int size);
+void ffs_csumtotal_swap(struct csum_total *o, struct csum_total *n);
+
+void
+ffs_sb_swap(struct fs *o, struct fs *n)
+{
+ size_t i;
+ u_int32_t *o32, *n32;
+
+ /*
+ * In order to avoid a lot of lines, as the first N fields (52)
+ * of the superblock up to fs_fmod are u_int32_t, we just loop
+ * here to convert them.
+ */
+ o32 = (u_int32_t *)o;
+ n32 = (u_int32_t *)n;
+ for (i = 0; i < offsetof(struct fs, fs_fmod) / sizeof(u_int32_t); i++)
+ n32[i] = bswap32(o32[i]);
+
+ n->fs_swuid = bswap64(o->fs_swuid);
+ n->fs_cgrotor = bswap32(o->fs_cgrotor); /* Unused */
+ n->fs_old_cpc = bswap32(o->fs_old_cpc);
+
+ /* These fields overlap with a possible location for the
+ * historic FS_DYNAMICPOSTBLFMT postbl table, and with the
+ * first half of the historic FS_42POSTBLFMT postbl table.
+ */
+ n->fs_maxbsize = bswap32(o->fs_maxbsize);
+ n->fs_sblockloc = bswap64(o->fs_sblockloc);
+ ffs_csumtotal_swap(&o->fs_cstotal, &n->fs_cstotal);
+ n->fs_time = bswap64(o->fs_time);
+ n->fs_size = bswap64(o->fs_size);
+ n->fs_dsize = bswap64(o->fs_dsize);
+ n->fs_csaddr = bswap64(o->fs_csaddr);
+ n->fs_pendingblocks = bswap64(o->fs_pendingblocks);
+ n->fs_pendinginodes = bswap32(o->fs_pendinginodes);
+
+ /* These fields overlap with the second half of the
+ * historic FS_42POSTBLFMT postbl table
+ */
+ for (i = 0; i < FSMAXSNAP; i++)
+ n->fs_snapinum[i] = bswap32(o->fs_snapinum[i]);
+ n->fs_avgfilesize = bswap32(o->fs_avgfilesize);
+ n->fs_avgfpdir = bswap32(o->fs_avgfpdir);
+ /* fs_sparecon[28] - ignore for now */
+ n->fs_flags = bswap32(o->fs_flags);
+ n->fs_contigsumsize = bswap32(o->fs_contigsumsize);
+ n->fs_maxsymlinklen = bswap32(o->fs_maxsymlinklen);
+ n->fs_old_inodefmt = bswap32(o->fs_old_inodefmt);
+ n->fs_maxfilesize = bswap64(o->fs_maxfilesize);
+ n->fs_qbmask = bswap64(o->fs_qbmask);
+ n->fs_qfmask = bswap64(o->fs_qfmask);
+ n->fs_state = bswap32(o->fs_state);
+ n->fs_old_postblformat = bswap32(o->fs_old_postblformat);
+ n->fs_old_nrpos = bswap32(o->fs_old_nrpos);
+ n->fs_old_postbloff = bswap32(o->fs_old_postbloff);
+ n->fs_old_rotbloff = bswap32(o->fs_old_rotbloff);
+
+ n->fs_magic = bswap32(o->fs_magic);
+}
+
+void
+ffs_dinode1_swap(struct ufs1_dinode *o, struct ufs1_dinode *n)
+{
+
+ n->di_mode = bswap16(o->di_mode);
+ n->di_nlink = bswap16(o->di_nlink);
+ n->di_size = bswap64(o->di_size);
+ n->di_atime = bswap32(o->di_atime);
+ n->di_atimensec = bswap32(o->di_atimensec);
+ n->di_mtime = bswap32(o->di_mtime);
+ n->di_mtimensec = bswap32(o->di_mtimensec);
+ n->di_ctime = bswap32(o->di_ctime);
+ n->di_ctimensec = bswap32(o->di_ctimensec);
+ memcpy(n->di_db, o->di_db, (NDADDR + NIADDR) * sizeof(u_int32_t));
+ n->di_flags = bswap32(o->di_flags);
+ n->di_blocks = bswap32(o->di_blocks);
+ n->di_gen = bswap32(o->di_gen);
+ n->di_uid = bswap32(o->di_uid);
+ n->di_gid = bswap32(o->di_gid);
+}
+
+void
+ffs_dinode2_swap(struct ufs2_dinode *o, struct ufs2_dinode *n)
+{
+ n->di_mode = bswap16(o->di_mode);
+ n->di_nlink = bswap16(o->di_nlink);
+ n->di_uid = bswap32(o->di_uid);
+ n->di_gid = bswap32(o->di_gid);
+ n->di_blksize = bswap32(o->di_blksize);
+ n->di_size = bswap64(o->di_size);
+ n->di_blocks = bswap64(o->di_blocks);
+ n->di_atime = bswap64(o->di_atime);
+ n->di_atimensec = bswap32(o->di_atimensec);
+ n->di_mtime = bswap64(o->di_mtime);
+ n->di_mtimensec = bswap32(o->di_mtimensec);
+ n->di_ctime = bswap64(o->di_ctime);
+ n->di_ctimensec = bswap32(o->di_ctimensec);
+ n->di_birthtime = bswap64(o->di_ctime);
+ n->di_birthnsec = bswap32(o->di_ctimensec);
+ n->di_gen = bswap32(o->di_gen);
+ n->di_kernflags = bswap32(o->di_kernflags);
+ n->di_flags = bswap32(o->di_flags);
+ n->di_extsize = bswap32(o->di_extsize);
+ memcpy(n->di_extb, o->di_extb, (NXADDR + NDADDR + NIADDR) * 8);
+}
+
+void
+ffs_csum_swap(struct csum *o, struct csum *n, int size)
+{
+ size_t i;
+ u_int32_t *oint, *nint;
+
+ oint = (u_int32_t*)o;
+ nint = (u_int32_t*)n;
+
+ for (i = 0; i < size / sizeof(u_int32_t); i++)
+ nint[i] = bswap32(oint[i]);
+}
+
+void
+ffs_csumtotal_swap(struct csum_total *o, struct csum_total *n)
+{
+ n->cs_ndir = bswap64(o->cs_ndir);
+ n->cs_nbfree = bswap64(o->cs_nbfree);
+ n->cs_nifree = bswap64(o->cs_nifree);
+ n->cs_nffree = bswap64(o->cs_nffree);
+}
+
+/*
+ * Note that ffs_cg_swap may be called with o == n.
+ */
+void
+ffs_cg_swap(struct cg *o, struct cg *n, struct fs *fs)
+{
+ int i;
+ u_int32_t *n32, *o32;
+ u_int16_t *n16, *o16;
+ int32_t btotoff, boff, clustersumoff;
+
+ n->cg_firstfield = bswap32(o->cg_firstfield);
+ n->cg_magic = bswap32(o->cg_magic);
+ n->cg_old_time = bswap32(o->cg_old_time);
+ n->cg_cgx = bswap32(o->cg_cgx);
+ n->cg_old_ncyl = bswap16(o->cg_old_ncyl);
+ n->cg_old_niblk = bswap16(o->cg_old_niblk);
+ n->cg_ndblk = bswap32(o->cg_ndblk);
+ n->cg_cs.cs_ndir = bswap32(o->cg_cs.cs_ndir);
+ n->cg_cs.cs_nbfree = bswap32(o->cg_cs.cs_nbfree);
+ n->cg_cs.cs_nifree = bswap32(o->cg_cs.cs_nifree);
+ n->cg_cs.cs_nffree = bswap32(o->cg_cs.cs_nffree);
+ n->cg_rotor = bswap32(o->cg_rotor);
+ n->cg_frotor = bswap32(o->cg_frotor);
+ n->cg_irotor = bswap32(o->cg_irotor);
+ for (i = 0; i < MAXFRAG; i++)
+ n->cg_frsum[i] = bswap32(o->cg_frsum[i]);
+
+ n->cg_old_btotoff = bswap32(o->cg_old_btotoff);
+ n->cg_old_boff = bswap32(o->cg_old_boff);
+ n->cg_iusedoff = bswap32(o->cg_iusedoff);
+ n->cg_freeoff = bswap32(o->cg_freeoff);
+ n->cg_nextfreeoff = bswap32(o->cg_nextfreeoff);
+ n->cg_clustersumoff = bswap32(o->cg_clustersumoff);
+ n->cg_clusteroff = bswap32(o->cg_clusteroff);
+ n->cg_nclusterblks = bswap32(o->cg_nclusterblks);
+ n->cg_niblk = bswap32(o->cg_niblk);
+ n->cg_initediblk = bswap32(o->cg_initediblk);
+ n->cg_time = bswap64(o->cg_time);
+
+ if (fs->fs_magic == FS_UFS2_MAGIC)
+ return;
+
+ if (n->cg_magic == CG_MAGIC) {
+ btotoff = n->cg_old_btotoff;
+ boff = n->cg_old_boff;
+ clustersumoff = n->cg_clustersumoff;
+ } else {
+ btotoff = bswap32(n->cg_old_btotoff);
+ boff = bswap32(n->cg_old_boff);
+ clustersumoff = bswap32(n->cg_clustersumoff);
+ }
+ n32 = (u_int32_t *)((u_int8_t *)n + btotoff);
+ o32 = (u_int32_t *)((u_int8_t *)o + btotoff);
+ n16 = (u_int16_t *)((u_int8_t *)n + boff);
+ o16 = (u_int16_t *)((u_int8_t *)o + boff);
+
+ for (i = 0; i < fs->fs_old_cpg; i++)
+ n32[i] = bswap32(o32[i]);
+
+ for (i = 0; i < fs->fs_old_cpg * fs->fs_old_nrpos; i++)
+ n16[i] = bswap16(o16[i]);
+
+ n32 = (u_int32_t *)((u_int8_t *)n + clustersumoff);
+ o32 = (u_int32_t *)((u_int8_t *)o + clustersumoff);
+ for (i = 1; i < fs->fs_contigsumsize + 1; i++)
+ n32[i] = bswap32(o32[i]);
+}
diff --git a/usr.sbin/makefs/ffs/ffs_extern.h b/usr.sbin/makefs/ffs/ffs_extern.h
new file mode 100644
index 0000000..7755823
--- /dev/null
+++ b/usr.sbin/makefs/ffs/ffs_extern.h
@@ -0,0 +1,77 @@
+/* $NetBSD: ffs_extern.h,v 1.6 2003/08/07 11:25:33 agc Exp $ */
+/* From: NetBSD: ffs_extern.h,v 1.19 2001/08/17 02:18:48 lukem Exp */
+
+/*-
+ * Copyright (c) 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.
+ * 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.
+ *
+ * @(#)ffs_extern.h 8.6 (Berkeley) 3/30/95
+ * $FreeBSD$
+ */
+
+#include "ffs/buf.h"
+
+struct inode;
+
+/*
+ * Structure used to pass around logical block paths generated by
+ * ufs_getlbns and used by truncate and bmap code.
+ */
+struct indir {
+ daddr_t in_lbn; /* Logical block number. */
+ int in_off; /* Offset in buffer. */
+};
+
+ /* ffs.c */
+_Noreturn void panic(const char *, ...) __printflike(1, 2);
+
+ /* ffs_alloc.c */
+int ffs_alloc(struct inode *, daddr_t, daddr_t, int, daddr_t *);
+daddr_t ffs_blkpref_ufs1(struct inode *, daddr_t, int, int32_t *);
+daddr_t ffs_blkpref_ufs2(struct inode *, daddr_t, int, int64_t *);
+void ffs_blkfree(struct inode *, daddr_t, long);
+void ffs_clusteracct(struct fs *, struct cg *, int32_t, int);
+
+ /* ffs_balloc.c */
+int ffs_balloc(struct inode *, off_t, int, struct buf **);
+
+ /* ffs_bswap.c */
+void ffs_sb_swap(struct fs*, struct fs *);
+void ffs_dinode1_swap(struct ufs1_dinode *, struct ufs1_dinode *);
+void ffs_dinode2_swap(struct ufs2_dinode *, struct ufs2_dinode *);
+void ffs_csum_swap(struct csum *, struct csum *, int);
+void ffs_cg_swap(struct cg *, struct cg *, struct fs *);
+
+ /* ffs_subr.c */
+void ffs_fragacct(struct fs *, int, int32_t[], int, int);
+int ffs_isblock(struct fs *, u_char *, int32_t);
+int ffs_isfreeblock(struct fs *, u_char *, int32_t);
+void ffs_clrblock(struct fs *, u_char *, int32_t);
+void ffs_setblock(struct fs *, u_char *, int32_t);
+
+ /* ufs_bmap.c */
+int ufs_getlbns(struct inode *, daddr_t, struct indir *, int *);
diff --git a/usr.sbin/makefs/ffs/ffs_subr.c b/usr.sbin/makefs/ffs/ffs_subr.c
new file mode 100644
index 0000000..b55174d
--- /dev/null
+++ b/usr.sbin/makefs/ffs/ffs_subr.c
@@ -0,0 +1,193 @@
+/* $NetBSD: ffs_subr.c,v 1.32 2003/12/30 12:33:24 pk Exp $ */
+
+/*
+ * 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.
+ * 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.
+ *
+ * @(#)ffs_subr.c 8.5 (Berkeley) 3/21/95
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+#include "ffs/ffs_extern.h"
+#include "ffs/ufs_bswap.h"
+
+/*
+ * Update the frsum fields to reflect addition or deletion
+ * of some frags.
+ */
+void
+ffs_fragacct_swap(struct fs *fs, int fragmap, int32_t fraglist[], int cnt, int needswap)
+{
+ int inblk;
+ int field, subfield;
+ int siz, pos;
+
+ inblk = (int)(fragtbl[fs->fs_frag][fragmap]) << 1;
+ fragmap <<= 1;
+ for (siz = 1; siz < fs->fs_frag; siz++) {
+ if ((inblk & (1 << (siz + (fs->fs_frag & (NBBY - 1))))) == 0)
+ continue;
+ field = around[siz];
+ subfield = inside[siz];
+ for (pos = siz; pos <= fs->fs_frag; pos++) {
+ if ((fragmap & field) == subfield) {
+ fraglist[siz] = ufs_rw32(
+ ufs_rw32(fraglist[siz], needswap) + cnt,
+ needswap);
+ pos += siz;
+ field <<= siz;
+ subfield <<= siz;
+ }
+ field <<= 1;
+ subfield <<= 1;
+ }
+ }
+}
+
+/*
+ * block operations
+ *
+ * check if a block is available
+ * returns true if all the corresponding bits in the free map are 1
+ * returns false if any corresponding bit in the free map is 0
+ */
+int
+ffs_isblock(fs, cp, h)
+ struct fs *fs;
+ u_char *cp;
+ int32_t h;
+{
+ u_char mask;
+
+ switch ((int)fs->fs_fragshift) {
+ case 3:
+ return (cp[h] == 0xff);
+ case 2:
+ mask = 0x0f << ((h & 0x1) << 2);
+ return ((cp[h >> 1] & mask) == mask);
+ case 1:
+ mask = 0x03 << ((h & 0x3) << 1);
+ return ((cp[h >> 2] & mask) == mask);
+ case 0:
+ mask = 0x01 << (h & 0x7);
+ return ((cp[h >> 3] & mask) == mask);
+ default:
+ panic("ffs_isblock: unknown fs_fragshift %d",
+ (int)fs->fs_fragshift);
+ }
+}
+
+/*
+ * check if a block is completely allocated
+ * returns true if all the corresponding bits in the free map are 0
+ * returns false if any corresponding bit in the free map is 1
+ */
+int
+ffs_isfreeblock(fs, cp, h)
+ struct fs *fs;
+ u_char *cp;
+ int32_t h;
+{
+
+ switch ((int)fs->fs_fragshift) {
+ case 3:
+ return (cp[h] == 0);
+ case 2:
+ return ((cp[h >> 1] & (0x0f << ((h & 0x1) << 2))) == 0);
+ case 1:
+ return ((cp[h >> 2] & (0x03 << ((h & 0x3) << 1))) == 0);
+ case 0:
+ return ((cp[h >> 3] & (0x01 << (h & 0x7))) == 0);
+ default:
+ panic("ffs_isfreeblock: unknown fs_fragshift %d",
+ (int)fs->fs_fragshift);
+ }
+}
+
+/*
+ * take a block out of the map
+ */
+void
+ffs_clrblock(fs, cp, h)
+ struct fs *fs;
+ u_char *cp;
+ int32_t h;
+{
+
+ switch ((int)fs->fs_fragshift) {
+ case 3:
+ cp[h] = 0;
+ return;
+ case 2:
+ cp[h >> 1] &= ~(0x0f << ((h & 0x1) << 2));
+ return;
+ case 1:
+ cp[h >> 2] &= ~(0x03 << ((h & 0x3) << 1));
+ return;
+ case 0:
+ cp[h >> 3] &= ~(0x01 << (h & 0x7));
+ return;
+ default:
+ panic("ffs_clrblock: unknown fs_fragshift %d",
+ (int)fs->fs_fragshift);
+ }
+}
+
+/*
+ * put a block into the map
+ */
+void
+ffs_setblock(fs, cp, h)
+ struct fs *fs;
+ u_char *cp;
+ int32_t h;
+{
+
+ switch ((int)fs->fs_fragshift) {
+ case 3:
+ cp[h] = 0xff;
+ return;
+ case 2:
+ cp[h >> 1] |= (0x0f << ((h & 0x1) << 2));
+ return;
+ case 1:
+ cp[h >> 2] |= (0x03 << ((h & 0x3) << 1));
+ return;
+ case 0:
+ cp[h >> 3] |= (0x01 << (h & 0x7));
+ return;
+ default:
+ panic("ffs_setblock: unknown fs_fragshift %d",
+ (int)fs->fs_fragshift);
+ }
+}
diff --git a/usr.sbin/makefs/ffs/mkfs.c b/usr.sbin/makefs/ffs/mkfs.c
new file mode 100644
index 0000000..b1bdd09
--- /dev/null
+++ b/usr.sbin/makefs/ffs/mkfs.c
@@ -0,0 +1,839 @@
+/* $NetBSD: mkfs.c,v 1.20 2004/06/24 22:30:13 lukem Exp $ */
+
+/*
+ * 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.
+ * 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>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "makefs.h"
+#include "ffs.h"
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include "ffs/ufs_bswap.h"
+#include "ffs/ufs_inode.h"
+#include "ffs/ffs_extern.h"
+#include "ffs/newfs_extern.h"
+
+#ifndef BBSIZE
+#define BBSIZE 8192 /* size of boot area, with label */
+#endif
+
+static void initcg(int, time_t, const fsinfo_t *);
+static int ilog2(int);
+
+static int count_digits(int);
+
+/*
+ * make file system for cylinder-group style file systems
+ */
+#define UMASK 0755
+#define POWEROF2(num) (((num) & ((num) - 1)) == 0)
+
+union {
+ struct fs fs;
+ char pad[SBLOCKSIZE];
+} fsun;
+#define sblock fsun.fs
+struct csum *fscs;
+
+union {
+ struct cg cg;
+ char pad[FFS_MAXBSIZE];
+} cgun;
+#define acg cgun.cg
+
+char *iobuf;
+int iobufsize;
+
+char writebuf[FFS_MAXBSIZE];
+
+static int Oflag; /* format as an 4.3BSD file system */
+static int64_t fssize; /* file system size */
+static int sectorsize; /* bytes/sector */
+static int fsize; /* fragment size */
+static int bsize; /* block size */
+static int maxbsize; /* maximum clustering */
+static int maxblkspercg;
+static int minfree; /* free space threshold */
+static int opt; /* optimization preference (space or time) */
+static int density; /* number of bytes per inode */
+static int maxcontig; /* max contiguous blocks to allocate */
+static int maxbpg; /* maximum blocks per file in a cyl group */
+static int bbsize; /* boot block size */
+static int sbsize; /* superblock size */
+static int avgfilesize; /* expected average file size */
+static int avgfpdir; /* expected number of files per directory */
+
+struct fs *
+ffs_mkfs(const char *fsys, const fsinfo_t *fsopts)
+{
+ int fragsperinode, optimalfpg, origdensity, minfpg, lastminfpg;
+ int32_t cylno, i, csfrags;
+ long long sizepb;
+ void *space;
+ int size, blks;
+ int nprintcols, printcolwidth;
+ ffs_opt_t *ffs_opts = fsopts->fs_specific;
+
+ Oflag = ffs_opts->version;
+ fssize = fsopts->size / fsopts->sectorsize;
+ sectorsize = fsopts->sectorsize;
+ fsize = ffs_opts->fsize;
+ bsize = ffs_opts->bsize;
+ maxbsize = ffs_opts->maxbsize;
+ maxblkspercg = ffs_opts->maxblkspercg;
+ minfree = ffs_opts->minfree;
+ opt = ffs_opts->optimization;
+ density = ffs_opts->density;
+ maxcontig = ffs_opts->maxcontig;
+ maxbpg = ffs_opts->maxbpg;
+ avgfilesize = ffs_opts->avgfilesize;
+ avgfpdir = ffs_opts->avgfpdir;
+ bbsize = BBSIZE;
+ sbsize = SBLOCKSIZE;
+
+ strlcpy(sblock.fs_volname, ffs_opts->label, sizeof(sblock.fs_volname));
+
+ if (Oflag == 0) {
+ sblock.fs_old_inodefmt = FS_42INODEFMT;
+ sblock.fs_maxsymlinklen = 0;
+ sblock.fs_old_flags = 0;
+ } else {
+ sblock.fs_old_inodefmt = FS_44INODEFMT;
+ sblock.fs_maxsymlinklen = (Oflag == 1 ? MAXSYMLINKLEN_UFS1 :
+ MAXSYMLINKLEN_UFS2);
+ sblock.fs_old_flags = FS_FLAGS_UPDATED;
+ sblock.fs_flags = 0;
+ }
+ /*
+ * 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 %lld\n", (long long)fssize);
+ exit(13);
+ }
+ ffs_wtfs(fssize - 1, sectorsize, (char *)&sblock, fsopts);
+
+ /*
+ * collect and verify the filesystem density info
+ */
+ sblock.fs_avgfilesize = avgfilesize;
+ sblock.fs_avgfpdir = avgfpdir;
+ 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);
+ /*
+ * 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("fragment size %d is too small, minimum is %d\n",
+ sblock.fs_fsize, sectorsize);
+ exit(18);
+ }
+ if (sblock.fs_bsize < MINBSIZE) {
+ printf("block size %d is too small, minimum is %d\n",
+ sblock.fs_bsize, MINBSIZE);
+ exit(19);
+ }
+ if (sblock.fs_bsize > FFS_MAXBSIZE) {
+ printf("block size %d is too large, maximum is %d\n",
+ sblock.fs_bsize, FFS_MAXBSIZE);
+ exit(19);
+ }
+ if (sblock.fs_bsize < sblock.fs_fsize) {
+ printf("block size (%d) cannot be smaller than fragment size (%d)\n",
+ sblock.fs_bsize, sblock.fs_fsize);
+ exit(20);
+ }
+
+ 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;
+ }
+ 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;
+ for (sblock.fs_bshift = 0, i = sblock.fs_bsize; i > 1; i >>= 1)
+ sblock.fs_bshift++;
+ for (sblock.fs_fshift = 0, i = sblock.fs_fsize; i > 1; i >>= 1)
+ sblock.fs_fshift++;
+ sblock.fs_frag = numfrags(&sblock, sblock.fs_bsize);
+ for (sblock.fs_fragshift = 0, i = sblock.fs_frag; i > 1; i >>= 1)
+ sblock.fs_fragshift++;
+ if (sblock.fs_frag > MAXFRAG) {
+ printf("fragment size %d is too small, "
+ "minimum with block size %d is %d\n",
+ sblock.fs_fsize, sblock.fs_bsize,
+ sblock.fs_bsize / MAXFRAG);
+ exit(21);
+ }
+ sblock.fs_fsbtodb = ilog2(sblock.fs_fsize / sectorsize);
+ sblock.fs_size = sblock.fs_providersize = fssize =
+ dbtofsb(&sblock, fssize);
+
+ if (Oflag <= 1) {
+ sblock.fs_magic = FS_UFS1_MAGIC;
+ 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_magic = FS_UFS2_MAGIC;
+ 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 = (daddr_t)(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;
+ }
+
+ /*
+ * 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
+ * 1 cylinder group. 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.
+ */
+ origdensity = density;
+ for (;;) {
+ fragsperinode = MAX(numfrags(&sblock, density), 1);
+ 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);
+
+ if (maxblkspercg <= 0 || maxblkspercg >= fssize)
+ maxblkspercg = fssize - 1;
+ /*
+ * Start packing more blocks into the cylinder group until
+ * it cannot grow any larger, the number of cylinder groups
+ * drops below 1, or we reach the size requested.
+ */
+ for ( ; sblock.fs_fpg < maxblkspercg; sblock.fs_fpg += sblock.fs_frag) {
+ sblock.fs_ipg = roundup(howmany(sblock.fs_fpg, fragsperinode),
+ INOPB(&sblock));
+ if (sblock.fs_size / sblock.fs_fpg < 1)
+ 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 %lld < minimum size of %d\n",
+ (long long)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));
+
+ /*
+ * Setup memory for temporary in-core cylgroup summaries.
+ * Cribbed from ffs_mountfs().
+ */
+ size = sblock.fs_cssize;
+ blks = howmany(size, sblock.fs_fsize);
+ if (sblock.fs_contigsumsize > 0)
+ size += sblock.fs_ncg * sizeof(int32_t);
+ if ((space = (char *)calloc(1, size)) == NULL)
+ err(1, "memory allocation error for cg summaries");
+ sblock.fs_csp = space;
+ space = (char *)space + sblock.fs_cssize;
+ if (sblock.fs_contigsumsize > 0) {
+ int32_t *lp;
+
+ sblock.fs_maxcluster = lp = space;
+ for (i = 0; i < sblock.fs_ncg; i++)
+ *lp++ = sblock.fs_contigsumsize;
+ }
+
+ sblock.fs_sbsize = fragroundup(&sblock, sizeof(struct fs));
+ if (sblock.fs_sbsize > SBLOCKSIZE)
+ sblock.fs_sbsize = SBLOCKSIZE;
+ sblock.fs_minfree = minfree;
+ sblock.fs_maxcontig = maxcontig;
+ sblock.fs_maxbpg = maxbpg;
+ sblock.fs_optim = opt;
+ sblock.fs_cgrotor = 0;
+ sblock.fs_pendingblocks = 0;
+ sblock.fs_pendinginodes = 0;
+ sblock.fs_cstotal.cs_ndir = 0;
+ sblock.fs_cstotal.cs_nbfree = 0;
+ sblock.fs_cstotal.cs_nifree = 0;
+ sblock.fs_cstotal.cs_nffree = 0;
+ sblock.fs_fmod = 0;
+ sblock.fs_ronly = 0;
+ sblock.fs_state = 0;
+ sblock.fs_clean = FS_ISCLEAN;
+ sblock.fs_ronly = 0;
+ sblock.fs_id[0] = start_time.tv_sec;
+ sblock.fs_id[1] = 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 = start_time.tv_sec;
+ if (Oflag <= 1) {
+ sblock.fs_old_time = start_time.tv_sec;
+ 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 (%lld sectors) block size %d, "
+ "fragment size %d\n",
+ fsys, (float)sblock.fs_size * sblock.fs_fsize * B2MBFACTOR,
+ (long long)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);
+#undef B2MBFACTOR
+ /*
+ * Now determine how wide each column will be, and calculate how
+ * many columns will fit in a 76 char line. 76 is the width of the
+ * subwindows in sysinst.
+ */
+ printcolwidth = count_digits(
+ fsbtodb(&sblock, cgsblock(&sblock, sblock.fs_ncg -1)));
+ nprintcols = 76 / (printcolwidth + 2);
+
+ /*
+ * 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 = malloc(iobufsize)) == 0) {
+ printf("Cannot allocate I/O buffer\n");
+ exit(38);
+ }
+ memset(iobuf, 0, iobufsize);
+ /*
+ * Make a copy of the superblock into the buffer that we will be
+ * writing out in each cylinder group.
+ */
+ memcpy(writebuf, &sblock, sbsize);
+ if (fsopts->needswap)
+ ffs_sb_swap(&sblock, (struct fs*)writebuf);
+ memcpy(iobuf, writebuf, SBLOCKSIZE);
+
+ printf("super-block backups (for fsck -b #) at:");
+ for (cylno = 0; cylno < sblock.fs_ncg; cylno++) {
+ initcg(cylno, start_time.tv_sec, fsopts);
+ if (cylno % nprintcols == 0)
+ printf("\n");
+ printf(" %*lld,", printcolwidth,
+ (long long)fsbtodb(&sblock, cgsblock(&sblock, cylno)));
+ fflush(stdout);
+ }
+ printf("\n");
+
+ /*
+ * Now construct the initial file system,
+ * then write out the super-block.
+ */
+ sblock.fs_time = start_time.tv_sec;
+ 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 (fsopts->needswap)
+ sblock.fs_flags |= FS_SWAPPED;
+ ffs_write_superblock(&sblock, fsopts);
+ return (&sblock);
+}
+
+/*
+ * Write out the superblock and its duplicates,
+ * and the cylinder group summaries
+ */
+void
+ffs_write_superblock(struct fs *fs, const fsinfo_t *fsopts)
+{
+ int cylno, size, blks, i, saveflag;
+ void *space;
+ char *wrbuf;
+
+ saveflag = fs->fs_flags & FS_INTERNAL;
+ fs->fs_flags &= ~FS_INTERNAL;
+
+ memcpy(writebuf, &sblock, sbsize);
+ if (fsopts->needswap)
+ ffs_sb_swap(fs, (struct fs*)writebuf);
+ ffs_wtfs(fs->fs_sblockloc / sectorsize, sbsize, writebuf, fsopts);
+
+ /* Write out the duplicate super blocks */
+ for (cylno = 0; cylno < fs->fs_ncg; cylno++)
+ ffs_wtfs(fsbtodb(fs, cgsblock(fs, cylno)),
+ sbsize, writebuf, fsopts);
+
+ /* Write out the cylinder group summaries */
+ size = fs->fs_cssize;
+ blks = howmany(size, fs->fs_fsize);
+ space = (void *)fs->fs_csp;
+ if ((wrbuf = malloc(size)) == NULL)
+ err(1, "ffs_write_superblock: malloc %d", size);
+ for (i = 0; i < blks; i+= fs->fs_frag) {
+ size = fs->fs_bsize;
+ if (i + fs->fs_frag > blks)
+ size = (blks - i) * fs->fs_fsize;
+ if (fsopts->needswap)
+ ffs_csum_swap((struct csum *)space,
+ (struct csum *)wrbuf, size);
+ else
+ memcpy(wrbuf, space, (u_int)size);
+ ffs_wtfs(fsbtodb(fs, fs->fs_csaddr + i), size, wrbuf, fsopts);
+ space = (char *)space + size;
+ }
+ free(wrbuf);
+ fs->fs_flags |= saveflag;
+}
+
+/*
+ * Initialize a cylinder group.
+ */
+static void
+initcg(int cylno, time_t utime, const fsinfo_t *fsopts)
+{
+ daddr_t cbase, dmax;
+ int32_t i, j, d, dlower, dupper, blkno;
+ struct ufs1_dinode *dp1;
+ struct ufs2_dinode *dp2;
+ int start;
+
+ /*
+ * 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);
+ 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_fragshift;
+ start = &acg.cg_space[0] - (u_char *)(&acg.cg_firstfield);
+ if (Oflag == 2) {
+ acg.cg_iusedoff = start;
+ } else {
+ if (cylno == sblock.fs_ncg - 1)
+ acg.cg_old_ncyl = howmany(acg.cg_ndblk,
+ sblock.fs_fpg / sblock.fs_old_cpg);
+ 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);
+ if (sblock.fs_contigsumsize <= 0) {
+ acg.cg_nextfreeoff = acg.cg_freeoff +
+ howmany(sblock.fs_fpg, CHAR_BIT);
+ } else {
+ acg.cg_clustersumoff = acg.cg_freeoff +
+ howmany(sblock.fs_fpg, CHAR_BIT) - sizeof(int32_t);
+ acg.cg_clustersumoff =
+ roundup(acg.cg_clustersumoff, sizeof(int32_t));
+ acg.cg_clusteroff = acg.cg_clustersumoff +
+ (sblock.fs_contigsumsize + 1) * sizeof(int32_t);
+ acg.cg_nextfreeoff = acg.cg_clusteroff +
+ howmany(fragstoblks(&sblock, sblock.fs_fpg), CHAR_BIT);
+ }
+ if (acg.cg_nextfreeoff > 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 < ROOTINO; i++) {
+ setbit(cg_inosused_swap(&acg, 0), i);
+ acg.cg_cs.cs_nifree--;
+ }
+ if (cylno > 0) {
+ /*
+ * In cylno 0, beginning space is reserved
+ * for boot and super blocks.
+ */
+ for (d = 0, blkno = 0; d < dlower;) {
+ ffs_setblock(&sblock, cg_blksfree_swap(&acg, 0), blkno);
+ if (sblock.fs_contigsumsize > 0)
+ setbit(cg_clustersfree_swap(&acg, 0), blkno);
+ acg.cg_cs.cs_nbfree++;
+ d += sblock.fs_frag;
+ blkno++;
+ }
+ }
+ if ((i = (dupper & (sblock.fs_frag - 1))) != 0) {
+ acg.cg_frsum[sblock.fs_frag - i]++;
+ for (d = dupper + sblock.fs_frag - i; dupper < d; dupper++) {
+ setbit(cg_blksfree_swap(&acg, 0), dupper);
+ acg.cg_cs.cs_nffree++;
+ }
+ }
+ for (d = dupper, blkno = dupper >> sblock.fs_fragshift;
+ d + sblock.fs_frag <= acg.cg_ndblk; ) {
+ ffs_setblock(&sblock, cg_blksfree_swap(&acg, 0), blkno);
+ if (sblock.fs_contigsumsize > 0)
+ setbit(cg_clustersfree_swap(&acg, 0), blkno);
+ acg.cg_cs.cs_nbfree++;
+ d += sblock.fs_frag;
+ blkno++;
+ }
+ if (d < acg.cg_ndblk) {
+ acg.cg_frsum[acg.cg_ndblk - d]++;
+ for (; d < acg.cg_ndblk; d++) {
+ setbit(cg_blksfree_swap(&acg, 0), d);
+ acg.cg_cs.cs_nffree++;
+ }
+ }
+ if (sblock.fs_contigsumsize > 0) {
+ int32_t *sump = cg_clustersum_swap(&acg, 0);
+ u_char *mapp = cg_clustersfree_swap(&acg, 0);
+ 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_cs(&sblock, cylno) = 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;
+ memcpy(&iobuf[start], &acg, sblock.fs_cgsize);
+ if (fsopts->needswap)
+ ffs_cg_swap(&acg, (struct cg*)&iobuf[start], &sblock);
+ 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) {
+ /* No need to swap, it'll stay random */
+ dp1->di_gen = random();
+ dp1++;
+ } else {
+ dp2->di_gen = random();
+ dp2++;
+ }
+ }
+ ffs_wtfs(fsbtodb(&sblock, cgsblock(&sblock, cylno)), iobufsize, iobuf,
+ fsopts);
+ /*
+ * 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 = random();
+ dp1++;
+ }
+ ffs_wtfs(fsbtodb(&sblock, cgimin(&sblock, cylno) + i),
+ sblock.fs_bsize, &iobuf[start], fsopts);
+ }
+ }
+}
+
+/*
+ * read a block from the file system
+ */
+void
+ffs_rdfs(daddr_t bno, int size, void *bf, const fsinfo_t *fsopts)
+{
+ int n;
+ off_t offset;
+
+ offset = bno;
+ offset *= fsopts->sectorsize;
+ if (lseek(fsopts->fd, offset, SEEK_SET) < 0)
+ err(1, "ffs_rdfs: seek error for sector %lld: %s\n",
+ (long long)bno, strerror(errno));
+ n = read(fsopts->fd, bf, size);
+ if (n == -1) {
+ abort();
+ err(1, "ffs_rdfs: read error bno %lld size %d", (long long)bno,
+ size);
+ }
+ else if (n != size)
+ errx(1, "ffs_rdfs: read error for sector %lld: %s\n",
+ (long long)bno, strerror(errno));
+}
+
+/*
+ * write a block to the file system
+ */
+void
+ffs_wtfs(daddr_t bno, int size, void *bf, const fsinfo_t *fsopts)
+{
+ int n;
+ off_t offset;
+
+ offset = bno;
+ offset *= fsopts->sectorsize;
+ if (lseek(fsopts->fd, offset, SEEK_SET) < 0)
+ err(1, "wtfs: seek error for sector %lld: %s\n",
+ (long long)bno, strerror(errno));
+ n = write(fsopts->fd, bf, size);
+ if (n == -1)
+ err(1, "wtfs: write error for sector %lld: %s\n",
+ (long long)bno, strerror(errno));
+ else if (n != size)
+ errx(1, "wtfs: write error for sector %lld: %s\n",
+ (long long)bno, strerror(errno));
+}
+
+
+/* Determine how many digits are needed to print a given integer */
+static int
+count_digits(int num)
+{
+ int ndig;
+
+ for(ndig = 1; num > 9; num /=10, ndig++);
+
+ return (ndig);
+}
+
+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);
+}
diff --git a/usr.sbin/makefs/ffs/newfs_extern.h b/usr.sbin/makefs/ffs/newfs_extern.h
new file mode 100644
index 0000000..a54c0e2
--- /dev/null
+++ b/usr.sbin/makefs/ffs/newfs_extern.h
@@ -0,0 +1,36 @@
+/* $NetBSD: newfs_extern.h,v 1.3 2009/10/21 01:07:47 snj Exp $ */
+/* From: NetBSD: extern.h,v 1.3 2000/12/01 12:03:27 simonb Exp $ */
+
+/*
+ * Copyright (c) 1997 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$
+ */
+
+/* prototypes */
+struct fs *ffs_mkfs(const char *, const fsinfo_t *);
+void ffs_write_superblock(struct fs *, const fsinfo_t *);
+void ffs_rdfs(daddr_t, int, void *, const fsinfo_t *);
+void ffs_wtfs(daddr_t, int, void *, const fsinfo_t *);
+
+#define FFS_MAXBSIZE 65536
diff --git a/usr.sbin/makefs/ffs/ufs_bmap.c b/usr.sbin/makefs/ffs/ufs_bmap.c
new file mode 100644
index 0000000..b65b416
--- /dev/null
+++ b/usr.sbin/makefs/ffs/ufs_bmap.c
@@ -0,0 +1,140 @@
+/* $NetBSD: ufs_bmap.c,v 1.14 2004/06/20 22:20:18 jmc Exp $ */
+/* From: NetBSD: ufs_bmap.c,v 1.14 2001/11/08 05:00:51 chs Exp */
+
+/*
+ * Copyright (c) 1989, 1991, 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.
+ * 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.
+ *
+ * @(#)ufs_bmap.c 8.8 (Berkeley) 8/11/95
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/time.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <strings.h>
+
+#include "makefs.h"
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include "ffs/ufs_bswap.h"
+#include "ffs/ufs_inode.h"
+#include "ffs/ffs_extern.h"
+
+/*
+ * Create an array of logical block number/offset pairs which represent the
+ * path of indirect blocks required to access a data block. The first "pair"
+ * contains the logical block number of the appropriate single, double or
+ * triple indirect block and the offset into the inode indirect block array.
+ * Note, the logical block number of the inode single/double/triple indirect
+ * block appears twice in the array, once with the offset into the i_ffs_ib and
+ * once with the offset into the page itself.
+ */
+int
+ufs_getlbns(struct inode *ip, daddr_t bn, struct indir *ap, int *nump)
+{
+ daddr_t metalbn, realbn;
+ int64_t blockcnt;
+ int lbc;
+ int i, numlevels, off;
+ u_long lognindir;
+
+ lognindir = ffs(NINDIR(ip->i_fs)) - 1;
+ if (nump)
+ *nump = 0;
+ numlevels = 0;
+ realbn = bn;
+ if ((long)bn < 0)
+ bn = -(long)bn;
+
+ assert (bn >= NDADDR);
+
+ /*
+ * Determine the number of levels of indirection. After this loop
+ * is done, blockcnt indicates the number of data blocks possible
+ * at the given level of indirection, and NIADDR - i is the number
+ * of levels of indirection needed to locate the requested block.
+ */
+
+ bn -= NDADDR;
+ for (lbc = 0, i = NIADDR;; i--, bn -= blockcnt) {
+ if (i == 0)
+ return (EFBIG);
+
+ lbc += lognindir;
+ blockcnt = (int64_t)1 << lbc;
+
+ if (bn < blockcnt)
+ break;
+ }
+
+ /* Calculate the address of the first meta-block. */
+ if (realbn >= 0)
+ metalbn = -(realbn - bn + NIADDR - i);
+ else
+ metalbn = -(-realbn - bn + NIADDR - i);
+
+ /*
+ * At each iteration, off is the offset into the bap array which is
+ * an array of disk addresses at the current level of indirection.
+ * The logical block number and the offset in that block are stored
+ * into the argument array.
+ */
+ ap->in_lbn = metalbn;
+ ap->in_off = off = NIADDR - i;
+ ap++;
+ for (++numlevels; i <= NIADDR; i++) {
+ /* If searching for a meta-data block, quit when found. */
+ if (metalbn == realbn)
+ break;
+
+ lbc -= lognindir;
+ blockcnt = (int64_t)1 << lbc;
+ off = (bn >> lbc) & (NINDIR(ip->i_fs) - 1);
+
+ ++numlevels;
+ ap->in_lbn = metalbn;
+ ap->in_off = off;
+ ++ap;
+
+ metalbn -= -1 + (off << lbc);
+ }
+ if (nump)
+ *nump = numlevels;
+ return (0);
+}
diff --git a/usr.sbin/makefs/ffs/ufs_bswap.h b/usr.sbin/makefs/ffs/ufs_bswap.h
new file mode 100644
index 0000000..6e7cc42
--- /dev/null
+++ b/usr.sbin/makefs/ffs/ufs_bswap.h
@@ -0,0 +1,90 @@
+/* $NetBSD: ufs_bswap.h,v 1.13 2003/10/05 17:48:50 bouyer Exp $ */
+
+/*
+ * Copyright (c) 1998 Manuel Bouyer.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 Manuel Bouyer.
+ * 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$
+ */
+
+#ifndef _UFS_UFS_BSWAP_H_
+#define _UFS_UFS_BSWAP_H_
+
+#if defined(_KERNEL_OPT)
+#include "opt_ffs.h"
+#endif
+
+#include <sys/endian.h>
+
+#include "makefs.h"
+
+/* Macros to access UFS flags */
+#ifdef FFS_EI
+#define UFS_MPNEEDSWAP(mp) (VFSTOUFS(mp)->um_flags & UFS_NEEDSWAP)
+#define UFS_FSNEEDSWAP(fs) ((fs)->fs_flags & FS_SWAPPED)
+#define UFS_IPNEEDSWAP(ip) UFS_MPNEEDSWAP(ITOV(ip)->v_mount)
+#else
+#define UFS_MPNEEDSWAP(mp) (0)
+#define UFS_FSNEEDSWAP(fs) (0)
+#define UFS_IPNEEDSWAP(ip) (0)
+#endif
+
+#if !defined(_KERNEL) || defined(FFS_EI)
+/* inlines for access to swapped data */
+static __inline u_int16_t ufs_rw16 __P((u_int16_t, int));
+static __inline u_int32_t ufs_rw32 __P((u_int32_t, int));
+static __inline u_int64_t ufs_rw64 __P((u_int64_t, int));
+
+static __inline u_int16_t
+ufs_rw16(u_int16_t a, int ns)
+{
+ return ((ns) ? bswap16(a) : (a));
+}
+static __inline u_int32_t
+ufs_rw32(u_int32_t a, int ns)
+{
+ return ((ns) ? bswap32(a) : (a));
+}
+static __inline u_int64_t
+ufs_rw64(u_int64_t a, int ns)
+{
+ return ((ns) ? bswap64(a) : (a));
+}
+#else
+#define ufs_rw16(a, ns) ((uint16_t)(a))
+#define ufs_rw32(a, ns) ((uint32_t)(a))
+#define ufs_rw64(a, ns) ((uint64_t)(a))
+#endif
+
+#define ufs_add16(a, b, ns) \
+ (a) = ufs_rw16(ufs_rw16((a), (ns)) + (b), (ns))
+#define ufs_add32(a, b, ns) \
+ (a) = ufs_rw32(ufs_rw32((a), (ns)) + (b), (ns))
+#define ufs_add64(a, b, ns) \
+ (a) = ufs_rw64(ufs_rw64((a), (ns)) + (b), (ns))
+
+#endif /* !_UFS_UFS_BSWAP_H_ */
diff --git a/usr.sbin/makefs/ffs/ufs_inode.h b/usr.sbin/makefs/ffs/ufs_inode.h
new file mode 100644
index 0000000..8286f86
--- /dev/null
+++ b/usr.sbin/makefs/ffs/ufs_inode.h
@@ -0,0 +1,97 @@
+/* $NetBSD: ufs_inode.h,v 1.3 2003/08/07 11:25:34 agc Exp $ */
+/* From: NetBSD: inode.h,v 1.27 2001/12/18 10:57:23 fvdl Exp $ */
+
+/*
+ * Copyright (c) 1982, 1989, 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.
+ * 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.
+ *
+ * @(#)inode.h 8.9 (Berkeley) 5/14/95
+ * $FreeBSD$
+ */
+
+union dinode {
+ struct ufs1_dinode ffs1_din;
+ struct ufs2_dinode ffs2_din;
+};
+
+struct inode {
+ ino_t i_number; /* The identity of the inode. */
+ struct fs *i_fs; /* File system */
+ union dinode i_din;
+ int i_fd; /* File descriptor */
+ uint64_t i_size;
+};
+
+#define i_ffs1_atime i_din.ffs1_din.di_atime
+#define i_ffs1_atimensec i_din.ffs1_din.di_atimensec
+#define i_ffs1_blocks i_din.ffs1_din.di_blocks
+#define i_ffs1_ctime i_din.ffs1_din.di_ctime
+#define i_ffs1_ctimensec i_din.ffs1_din.di_ctimensec
+#define i_ffs1_db i_din.ffs1_din.di_db
+#define i_ffs1_flags i_din.ffs1_din.di_flags
+#define i_ffs1_gen i_din.ffs1_din.di_gen
+#define i_ffs11_gid i_din.ffs1_din.di_gid
+#define i_ffs1_ib i_din.ffs1_din.di_ib
+#define i_ffs1_mode i_din.ffs1_din.di_mode
+#define i_ffs1_mtime i_din.ffs1_din.di_mtime
+#define i_ffs1_mtimensec i_din.ffs1_din.di_mtimensec
+#define i_ffs1_nlink i_din.ffs1_din.di_nlink
+#define i_ffs1_rdev i_din.ffs1_din.di_rdev
+#define i_ffs1_shortlink i_din.ffs1_din.db
+#define i_ffs1_size i_din.ffs1_din.di_size
+#define i_ffs1_uid i_din.ffs1_din.di_uid
+
+#define i_ffs2_atime i_din.ffs2_din.di_atime
+#define i_ffs2_atimensec i_din.ffs2_din.di_atimensec
+#define i_ffs2_blocks i_din.ffs2_din.di_blocks
+#define i_ffs2_ctime i_din.ffs2_din.di_ctime
+#define i_ffs2_ctimensec i_din.ffs2_din.di_ctimensec
+#define i_ffs2_birthtime i_din.ffs2_din.di_birthtime
+#define i_ffs2_birthnsec i_din.ffs2_din.di_birthnsec
+#define i_ffs2_db i_din.ffs2_din.di_db
+#define i_ffs2_flags i_din.ffs2_din.di_flags
+#define i_ffs2_gen i_din.ffs2_din.di_gen
+#define i_ffs21_gid i_din.ffs2_din.di_gid
+#define i_ffs2_ib i_din.ffs2_din.di_ib
+#define i_ffs2_mode i_din.ffs2_din.di_mode
+#define i_ffs2_mtime i_din.ffs2_din.di_mtime
+#define i_ffs2_mtimensec i_din.ffs2_din.di_mtimensec
+#define i_ffs2_nlink i_din.ffs2_din.di_nlink
+#define i_ffs2_rdev i_din.ffs2_din.di_rdev
+#define i_ffs2_shortlink i_din.ffs2_din.db
+#define i_ffs2_size i_din.ffs2_din.di_size
+#define i_ffs2_uid i_din.ffs2_din.di_uid
+
+#undef DIP
+#define DIP(ip, field) \
+ (((ip)->i_fs->fs_magic == FS_UFS1_MAGIC) ? \
+ (ip)->i_ffs1_##field : (ip)->i_ffs2_##field)
diff --git a/usr.sbin/makefs/makefs.8 b/usr.sbin/makefs/makefs.8
new file mode 100644
index 0000000..f80dc53
--- /dev/null
+++ b/usr.sbin/makefs/makefs.8
@@ -0,0 +1,398 @@
+.\" $NetBSD: makefs.8,v 1.32 2009/01/20 20:47:25 bjh21 Exp $
+.\"
+.\" Copyright (c) 2001-2003 Wasabi Systems, Inc.
+.\" All rights reserved.
+.\"
+.\" Written by Luke Mewburn 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd November 9, 2015
+.Dt MAKEFS 8
+.Os
+.Sh NAME
+.Nm makefs
+.Nd create a file system image from a directory tree or a mtree manifest
+.Sh SYNOPSIS
+.Nm
+.Op Fl DxZ
+.Op Fl B Ar endian
+.Op Fl b Ar free-blocks
+.Op Fl d Ar debug-mask
+.Op Fl F Ar mtree-specfile
+.Op Fl f Ar free-files
+.Op Fl M Ar minimum-size
+.Op Fl m Ar maximum-size
+.Op Fl N Ar userdb-dir
+.Op Fl o Ar fs-options
+.Op Fl R Ar roundup-size
+.Op Fl S Ar sector-size
+.Op Fl s Ar image-size
+.Op Fl t Ar fs-type
+.Ar image-file
+.Ar directory | manifest
+.Op Ar extra-directory ...
+.Sh DESCRIPTION
+The utility
+.Nm
+creates a file system image into
+.Ar image-file
+from the directory tree
+.Ar directory
+or from the mtree manifest
+.Ar manifest .
+If any optional directory trees are passed in the
+.Ar extra-directory
+arguments, then the directory tree of each argument will be merged
+into the
+.Ar directory
+or
+.Ar manifest
+first before creating
+.Ar image-file .
+No special devices or privileges are required to perform this task.
+.Pp
+The options are as follows:
+.Bl -tag -width flag
+.It Fl B Ar endian
+Set the byte order of the image to
+.Ar endian .
+Valid byte orders are
+.Ql 4321 ,
+.Ql big ,
+or
+.Ql be
+for big endian, and
+.Ql 1234 ,
+.Ql little ,
+or
+.Ql le
+for little endian.
+Some file systems may have a fixed byte order; in those cases this
+argument will be ignored.
+.It Fl b Ar free-blocks
+Ensure that a minimum of
+.Ar free-blocks
+free blocks exist in the image.
+An optional
+.Ql %
+suffix may be provided to indicate that
+.Ar free-blocks
+indicates a percentage of the calculated image size.
+.It Fl D
+Treat duplicate paths in an mtree manifest as warnings not error.
+.It Fl d Ar debug-mask
+Enable various levels of debugging, depending upon which bits are
+set in
+.Ar debug-mask .
+XXX: document these
+.It Fl F Ar mtree-specfile
+Use
+.Ar mtree-specfile
+as an
+.Xr mtree 8
+.Sq specfile
+specification.
+This option has no effect when the image is created from a mtree manifest
+rather than a directory.
+.Pp
+If a specfile entry exists in the underlying file system, its
+permissions and modification time will be used unless specifically
+overridden by the specfile.
+An error will be raised if the type of entry in the specfile
+conflicts with that of an existing entry.
+.Pp
+In the opposite case (where a specfile entry does not have an entry
+in the underlying file system) the following occurs:
+If the specfile entry is marked
+.Sy optional ,
+the specfile entry is ignored.
+Otherwise, the entry will be created in the image, and it is
+necessary to specify at least the following parameters in the
+specfile:
+.Sy type ,
+.Sy mode ,
+.Sy gname ,
+or
+.Sy gid ,
+and
+.Sy uname
+or
+.Sy uid ,
+and
+.Sy link
+(in the case of symbolic links).
+If
+.Sy time
+isn't provided, the current time will be used.
+If
+.Sy flags
+isn't provided, the current file flags will be used.
+Missing regular file entries will be created as zero-length files.
+.It Fl f Ar free-files
+Ensure that a minimum of
+.Ar free-files
+free files (inodes) exist in the image.
+An optional
+.Ql %
+suffix may be provided to indicate that
+.Ar free-files
+indicates a percentage of the calculated image size.
+.It Fl M Ar minimum-size
+Set the minimum size of the file system image to
+.Ar minimum-size .
+.It Fl m Ar maximum-size
+Set the maximum size of the file system image to
+.Ar maximum-size .
+An error will be raised if the target file system needs to be larger
+than this to accommodate the provided directory tree.
+.It Fl N Ar userdb-dir
+Use the user database text file
+.Pa master.passwd
+and group database text file
+.Pa group
+from
+.Ar userdb-dir ,
+rather than using the results from the system's
+.Xr getpwnam 3
+and
+.Xr getgrnam 3
+(and related) library calls.
+.It Fl o Ar fs-options
+Set file system specific options.
+.Ar fs-options
+is a comma separated list of options.
+Valid file system specific options are detailed below.
+.It Fl p
+Deprecated.
+See the
+.Fl Z
+flag.
+.It Fl R Ar roundup-size
+Round the image up to
+.Ar roundup-size .
+.Ar roundup-size
+should be a multiple of the file system block size.
+This option only applies to the
+.Sy ffs
+file system type.
+.It Fl S Ar sector-size
+Set the file system sector size to
+.Ar sector-size .
+.\" XXX: next line also true for cd9660?
+Defaults to 512.
+.It Fl s Ar image-size
+Set the size of the file system image to
+.Ar image-size .
+.It Fl t Ar fs-type
+Create an
+.Ar fs-type
+file system image.
+The following file system types are supported:
+.Bl -tag -width cd9660 -offset indent
+.It Sy ffs
+BSD fast file system (default).
+.It Sy cd9660
+ISO 9660 file system.
+.El
+.It Fl x
+Exclude file system nodes not explicitly listed in the specfile.
+.It Fl Z
+Create a sparse file for
+.Sy ffs .
+This is useful for virtual machine images.
+.El
+.Pp
+Where sizes are specified, a decimal number of bytes is expected.
+Two or more numbers may be separated by an
+.Dq x
+to indicate a product.
+Each number may have one of the following optional suffixes:
+.Bl -tag -width 3n -offset indent -compact
+.It b
+Block; multiply by 512
+.It k
+Kibi; multiply by 1024 (1 KiB)
+.It m
+Mebi; multiply by 1048576 (1 MiB)
+.It g
+Gibi; multiply by 1073741824 (1 GiB)
+.It t
+Tebi; multiply by 1099511627776 (1 TiB)
+.It w
+Word; multiply by the number of bytes in an integer
+.El
+.\"
+.\"
+.Ss FFS-specific options
+.Sy ffs
+images have ffs-specific optional parameters that may be provided.
+Each of the options consists of a keyword, an equal sign
+.Pq Ql = ,
+and a value.
+The following keywords are supported:
+.Pp
+.Bl -tag -width optimization -offset indent -compact
+.It Sy avgfilesize
+Expected average file size.
+.It Sy avgfpdir
+Expected number of files per directory.
+.It Sy bsize
+Block size.
+.It Sy density
+Bytes per inode.
+.It Sy fsize
+Fragment size.
+.It Sy label
+Label name of the image.
+.It Sy maxbpg
+Maximum blocks per file in a cylinder group.
+.It Sy minfree
+Minimum % free.
+.It Sy optimization
+Optimization preference; one of
+.Ql space
+or
+.Ql time .
+.It Sy extent
+Maximum extent size.
+.It Sy maxbpcg
+Maximum total number of blocks in a cylinder group.
+.It Sy version
+UFS version.
+1 for FFS (default), 2 for UFS2.
+.El
+.Ss CD9660-specific options
+.Sy cd9660
+images have ISO9660-specific optional parameters that may be
+provided.
+The arguments consist of a keyword and, optionally, an equal sign
+.Pq Ql = ,
+and a value.
+The following keywords are supported:
+.Pp
+.Bl -tag -width omit-trailing-period -offset indent -compact
+.It Sy allow-deep-trees
+Allow the directory structure to exceed the maximum specified in
+the spec.
+.It Sy allow-illegal-chars
+Allow illegal characters in filenames. This option is not implemented.
+.It Sy allow-lowercase
+Allow lowercase characters in filenames. This option is not implemented.
+.It Sy allow-max-name
+Allow 37 instead of 33 characters for filenames by omitting the
+version id.
+.It Sy allow-multidot
+Allow multiple dots in a filename.
+.It Sy applicationid
+Application ID of the image.
+.It Sy archimedes
+Use the
+.Ql ARCHIMEDES
+extension to encode
+.Tn RISC OS
+metadata.
+.It Sy bootimagedir
+Boot image directory. This option is not implemented.
+.It Sy chrp-boot
+Write an MBR partition table to the image to allow older CHRP hardware to
+boot.
+.It Sy boot-load-segment
+Set load segment for the boot image.
+.It Sy bootimage
+Filename of a boot image in the format
+.Dq sysid;filename ,
+where
+.Dq sysid
+is one of
+.Ql i386 ,
+.Ql mac68k ,
+.Ql macppc ,
+or
+.Ql powerpc .
+.It Sy generic-bootimage
+Load a generic boot image into the first 32K of the cd9660 image.
+.It Sy hard-disk-boot
+Boot image is a hard disk image.
+.It Sy isolevel
+An integer representing the ISO 9660 interchange level where
+.Dq level
+is either
+.Ql 1
+or
+.Ql 2 .
+.Dq level
+.Ql 3
+is not implemented.
+.It Sy keep-bad-images
+Do not discard images whose write was aborted due to an error.
+For debugging purposes.
+.It Sy label
+Label name of the image.
+.It Sy no-boot
+Boot image is not bootable.
+.It Sy no-emul-boot
+Boot image is a
+.Dq no emulation
+ElTorito image.
+.It Sy no-trailing-padding
+Do not pad the image (apparently Linux needs the padding).
+.It Sy omit-trailing-period
+Omit trailing periods in filenames.
+.It Sy preparer
+Preparer ID of the image.
+.It Sy publisher
+Publisher ID of the image.
+.It Sy rockridge
+Use RockRidge extensions (for longer filenames, etc.).
+.It Sy verbose
+Turns on verbose output.
+.It Sy volumeid
+Volume set identifier of the image.
+.El
+.Sh SEE ALSO
+.Xr mtree 5 ,
+.Xr mtree 8 ,
+.Xr newfs 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Nx 1.6 .
+.Sh AUTHORS
+.An Luke Mewburn Aq Mt lukem@NetBSD.org
+(original program),
+.An Daniel Watt ,
+.An Walter Deignan ,
+.An Ryan Gabrys ,
+.An Alan Perez-Rathke ,
+.An Ram Vedam
+(cd9660 support)
diff --git a/usr.sbin/makefs/makefs.c b/usr.sbin/makefs/makefs.c
new file mode 100644
index 0000000..a7ca751
--- /dev/null
+++ b/usr.sbin/makefs/makefs.c
@@ -0,0 +1,376 @@
+/* $NetBSD: makefs.c,v 1.26 2006/10/22 21:11:56 christos Exp $ */
+
+/*
+ * Copyright (c) 2001-2003 Wasabi Systems, Inc.
+ * All rights reserved.
+ *
+ * Written by Luke Mewburn 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "makefs.h"
+#include "mtree.h"
+
+/*
+ * list of supported file systems and dispatch functions
+ */
+typedef struct {
+ const char *type;
+ void (*prepare_options)(fsinfo_t *);
+ int (*parse_options)(const char *, fsinfo_t *);
+ void (*cleanup_options)(fsinfo_t *);
+ void (*make_fs)(const char *, const char *, fsnode *,
+ fsinfo_t *);
+} fstype_t;
+
+static fstype_t fstypes[] = {
+#define ENTRY(name) { \
+ # name, name ## _prep_opts, name ## _parse_opts, \
+ name ## _cleanup_opts, name ## _makefs \
+}
+ ENTRY(ffs),
+ ENTRY(cd9660),
+ { .type = NULL },
+};
+
+u_int debug;
+int dupsok;
+struct timespec start_time;
+
+static fstype_t *get_fstype(const char *);
+static void usage(void);
+int main(int, char *[]);
+
+int
+main(int argc, char *argv[])
+{
+ struct stat sb;
+ struct timeval start;
+ fstype_t *fstype;
+ fsinfo_t fsoptions;
+ fsnode *root;
+ int ch, i, len;
+ char *subtree;
+ char *specfile;
+
+ setprogname(argv[0]);
+
+ debug = 0;
+ if ((fstype = get_fstype(DEFAULT_FSTYPE)) == NULL)
+ errx(1, "Unknown default fs type `%s'.", DEFAULT_FSTYPE);
+
+ /* set default fsoptions */
+ (void)memset(&fsoptions, 0, sizeof(fsoptions));
+ fsoptions.fd = -1;
+ fsoptions.sectorsize = -1;
+
+ if (fstype->prepare_options)
+ fstype->prepare_options(&fsoptions);
+
+ specfile = NULL;
+ if (gettimeofday(&start, NULL) == -1)
+ err(1, "Unable to get system time");
+
+ start_time.tv_sec = start.tv_sec;
+ start_time.tv_nsec = start.tv_usec * 1000;
+
+ while ((ch = getopt(argc, argv, "B:b:Dd:f:F:M:m:N:o:pR:s:S:t:xZ")) != -1) {
+ switch (ch) {
+
+ case 'B':
+ if (strcmp(optarg, "be") == 0 ||
+ strcmp(optarg, "4321") == 0 ||
+ strcmp(optarg, "big") == 0) {
+#if BYTE_ORDER == LITTLE_ENDIAN
+ fsoptions.needswap = 1;
+#endif
+ } else if (strcmp(optarg, "le") == 0 ||
+ strcmp(optarg, "1234") == 0 ||
+ strcmp(optarg, "little") == 0) {
+#if BYTE_ORDER == BIG_ENDIAN
+ fsoptions.needswap = 1;
+#endif
+ } else {
+ warnx("Invalid endian `%s'.", optarg);
+ usage();
+ }
+ break;
+
+ case 'b':
+ len = strlen(optarg) - 1;
+ if (optarg[len] == '%') {
+ optarg[len] = '\0';
+ fsoptions.freeblockpc =
+ strsuftoll("free block percentage",
+ optarg, 0, 99);
+ } else {
+ fsoptions.freeblocks =
+ strsuftoll("free blocks",
+ optarg, 0, LLONG_MAX);
+ }
+ break;
+
+ case 'D':
+ dupsok = 1;
+ break;
+
+ case 'd':
+ debug = strtoll(optarg, NULL, 0);
+ break;
+
+ case 'f':
+ len = strlen(optarg) - 1;
+ if (optarg[len] == '%') {
+ optarg[len] = '\0';
+ fsoptions.freefilepc =
+ strsuftoll("free file percentage",
+ optarg, 0, 99);
+ } else {
+ fsoptions.freefiles =
+ strsuftoll("free files",
+ optarg, 0, LLONG_MAX);
+ }
+ break;
+
+ case 'F':
+ specfile = optarg;
+ break;
+
+ case 'M':
+ fsoptions.minsize =
+ strsuftoll("minimum size", optarg, 1LL, LLONG_MAX);
+ break;
+
+ case 'N':
+ if (! setup_getid(optarg))
+ errx(1,
+ "Unable to use user and group databases in `%s'",
+ optarg);
+ break;
+
+ case 'm':
+ fsoptions.maxsize =
+ strsuftoll("maximum size", optarg, 1LL, LLONG_MAX);
+ break;
+
+ case 'o':
+ {
+ char *p;
+
+ while ((p = strsep(&optarg, ",")) != NULL) {
+ if (*p == '\0')
+ errx(1, "Empty option");
+ if (! fstype->parse_options(p, &fsoptions))
+ usage();
+ }
+ break;
+ }
+ case 'p':
+ /* Deprecated in favor of 'Z' */
+ fsoptions.sparse = 1;
+ break;
+
+ case 'R':
+ /* Round image size up to specified block size */
+ fsoptions.roundup =
+ strsuftoll("roundup-size", optarg, 0, LLONG_MAX);
+ break;
+
+ case 's':
+ fsoptions.minsize = fsoptions.maxsize =
+ strsuftoll("size", optarg, 1LL, LLONG_MAX);
+ break;
+
+ case 'S':
+ fsoptions.sectorsize =
+ (int)strsuftoll("sector size", optarg,
+ 1LL, INT_MAX);
+ break;
+
+ case 't':
+ /* Check current one and cleanup if necessary. */
+ if (fstype->cleanup_options)
+ fstype->cleanup_options(&fsoptions);
+ fsoptions.fs_specific = NULL;
+ if ((fstype = get_fstype(optarg)) == NULL)
+ errx(1, "Unknown fs type `%s'.", optarg);
+ fstype->prepare_options(&fsoptions);
+ break;
+
+ case 'x':
+ fsoptions.onlyspec = 1;
+ break;
+
+ case 'Z':
+ /* Superscedes 'p' for compatibility with NetBSD makefs(8) */
+ fsoptions.sparse = 1;
+ break;
+
+ case '?':
+ default:
+ usage();
+ /* NOTREACHED */
+
+ }
+ }
+ if (debug) {
+ printf("debug mask: 0x%08x\n", debug);
+ printf("start time: %ld.%ld, %s",
+ (long)start_time.tv_sec, (long)start_time.tv_nsec,
+ ctime(&start_time.tv_sec));
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 2)
+ usage();
+
+ /* -x must be accompanied by -F */
+ if (fsoptions.onlyspec != 0 && specfile == NULL)
+ errx(1, "-x requires -F mtree-specfile.");
+
+ /* Accept '-' as meaning "read from standard input". */
+ if (strcmp(argv[1], "-") == 0)
+ sb.st_mode = S_IFREG;
+ else {
+ if (stat(argv[1], &sb) == -1)
+ err(1, "Can't stat `%s'", argv[1]);
+ }
+
+ switch (sb.st_mode & S_IFMT) {
+ case S_IFDIR: /* walk the tree */
+ subtree = argv[1];
+ TIMER_START(start);
+ root = walk_dir(subtree, ".", NULL, NULL);
+ TIMER_RESULTS(start, "walk_dir");
+ break;
+ case S_IFREG: /* read the manifest file */
+ subtree = ".";
+ TIMER_START(start);
+ root = read_mtree(argv[1], NULL);
+ TIMER_RESULTS(start, "manifest");
+ break;
+ default:
+ errx(1, "%s: not a file or directory", argv[1]);
+ /* NOTREACHED */
+ }
+
+ /* append extra directory */
+ for (i = 2; i < argc; i++) {
+ if (stat(argv[i], &sb) == -1)
+ err(1, "Can't stat `%s'", argv[i]);
+ if (!S_ISDIR(sb.st_mode))
+ errx(1, "%s: not a directory", argv[i]);
+ TIMER_START(start);
+ root = walk_dir(argv[i], ".", NULL, root);
+ TIMER_RESULTS(start, "walk_dir2");
+ }
+
+ if (specfile) { /* apply a specfile */
+ TIMER_START(start);
+ apply_specfile(specfile, subtree, root, fsoptions.onlyspec);
+ TIMER_RESULTS(start, "apply_specfile");
+ }
+
+ if (debug & DEBUG_DUMP_FSNODES) {
+ printf("\nparent: %s\n", subtree);
+ dump_fsnodes(root);
+ putchar('\n');
+ }
+
+ /* build the file system */
+ TIMER_START(start);
+ fstype->make_fs(argv[0], subtree, root, &fsoptions);
+ TIMER_RESULTS(start, "make_fs");
+
+ free_fsnodes(root);
+
+ exit(0);
+ /* NOTREACHED */
+}
+
+
+int
+set_option(option_t *options, const char *var, const char *val)
+{
+ int i;
+
+ for (i = 0; options[i].name != NULL; i++) {
+ if (strcmp(options[i].name, var) != 0)
+ continue;
+ *options[i].value = (int)strsuftoll(options[i].desc, val,
+ options[i].minimum, options[i].maximum);
+ return (1);
+ }
+ warnx("Unknown option `%s'", var);
+ return (0);
+}
+
+
+static fstype_t *
+get_fstype(const char *type)
+{
+ int i;
+
+ for (i = 0; fstypes[i].type != NULL; i++)
+ if (strcmp(fstypes[i].type, type) == 0)
+ return (&fstypes[i]);
+ return (NULL);
+}
+
+static void
+usage(void)
+{
+ const char *prog;
+
+ prog = getprogname();
+ fprintf(stderr,
+"usage: %s [-t fs-type] [-o fs-options] [-d debug-mask] [-B endian]\n"
+"\t[-S sector-size] [-M minimum-size] [-m maximum-size] [-R roundup-size]\n"
+"\t[-s image-size] [-b free-blocks] [-f free-files] [-F mtree-specfile]\n"
+"\t[-xZ] [-N userdb-dir] image-file directory | manifest [extra-directory ...]\n",
+ prog);
+ exit(1);
+}
diff --git a/usr.sbin/makefs/makefs.h b/usr.sbin/makefs/makefs.h
new file mode 100644
index 0000000..ba80f74
--- /dev/null
+++ b/usr.sbin/makefs/makefs.h
@@ -0,0 +1,286 @@
+/* $NetBSD: makefs.h,v 1.20 2008/12/28 21:51:46 christos Exp $ */
+
+/*
+ * Copyright (c) 2001 Wasabi Systems, Inc.
+ * All rights reserved.
+ *
+ * Written by Luke Mewburn 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _MAKEFS_H
+#define _MAKEFS_H
+
+#include <sys/stat.h>
+#include <err.h>
+
+/*
+ * fsnode -
+ * a component of the tree; contains a filename, a pointer to
+ * fsinode, optional symlink name, and tree pointers
+ *
+ * fsinode -
+ * equivalent to an inode, containing target file system inode number,
+ * refcount (nlink), and stat buffer
+ *
+ * A tree of fsnodes looks like this:
+ *
+ * name "." "bin" "netbsd"
+ * type S_IFDIR S_IFDIR S_IFREG
+ * next > > NULL
+ * parent NULL NULL NULL
+ * child NULL v
+ *
+ * name "." "ls"
+ * type S_IFDIR S_IFREG
+ * next > NULL
+ * parent ^ ^ (to "bin")
+ * child NULL NULL
+ *
+ * Notes:
+ * - first always points to first entry, at current level, which
+ * must be "." when the tree has been built; during build it may
+ * not be if "." hasn't yet been found by readdir(2).
+ */
+
+enum fi_flags {
+ FI_SIZED = 1<<0, /* inode sized */
+ FI_ALLOCATED = 1<<1, /* fsinode->ino allocated */
+ FI_WRITTEN = 1<<2, /* inode written */
+};
+
+typedef struct {
+ uint32_t ino; /* inode number used on target fs */
+ uint32_t nlink; /* number of links to this entry */
+ enum fi_flags flags; /* flags used by fs specific code */
+ struct stat st; /* stat entry */
+} fsinode;
+
+typedef struct _fsnode {
+ struct _fsnode *parent; /* parent (NULL if root) */
+ struct _fsnode *child; /* child (if type == S_IFDIR) */
+ struct _fsnode *next; /* next */
+ struct _fsnode *first; /* first node of current level (".") */
+ uint32_t type; /* type of entry */
+ fsinode *inode; /* actual inode data */
+ char *symlink; /* symlink target */
+ char *contents; /* file to provide contents */
+ const char *root; /* root path */
+ char *path; /* directory name */
+ char *name; /* file name */
+ int flags; /* misc flags */
+} fsnode;
+
+#define FSNODE_F_HASSPEC 0x01 /* fsnode has a spec entry */
+#define FSNODE_F_OPTIONAL 0x02 /* fsnode is optional */
+
+/*
+ * fsinfo_t - contains various settings and parameters pertaining to
+ * the image, including current settings, global options, and fs
+ * specific options
+ */
+typedef struct {
+ /* current settings */
+ off_t size; /* total size */
+ off_t inodes; /* number of inodes */
+ uint32_t curinode; /* current inode */
+
+ /* image settings */
+ int fd; /* file descriptor of image */
+ void *superblock; /* superblock */
+ int onlyspec; /* only add entries in specfile */
+
+
+ /* global options */
+ off_t minsize; /* minimum size image should be */
+ off_t maxsize; /* maximum size image can be */
+ off_t freefiles; /* free file entries to leave */
+ int freefilepc; /* free file % */
+ off_t freeblocks; /* free blocks to leave */
+ int freeblockpc; /* free block % */
+ int needswap; /* non-zero if byte swapping needed */
+ int sectorsize; /* sector size */
+ int sparse; /* sparse image, don't fill it with zeros */
+ off_t roundup; /* round image size up to this value */
+
+ void *fs_specific; /* File system specific additions. */
+} fsinfo_t;
+
+
+/*
+ * option_t - contains option name, description, pointer to location to store
+ * result, and range checks for the result. Used to simplify fs specific
+ * option setting
+ */
+typedef struct {
+ const char *name; /* option name */
+ int *value; /* where to stuff the value */
+ int minimum; /* minimum for value */
+ int maximum; /* maximum for value */
+ const char *desc; /* option description */
+} option_t;
+
+
+void apply_specfile(const char *, const char *, fsnode *, int);
+void dump_fsnodes(fsnode *);
+const char * inode_type(mode_t);
+fsnode * read_mtree(const char *, fsnode *);
+int set_option(option_t *, const char *, const char *);
+fsnode * walk_dir(const char *, const char *, fsnode *, fsnode *);
+void free_fsnodes(fsnode *);
+
+void ffs_prep_opts(fsinfo_t *);
+int ffs_parse_opts(const char *, fsinfo_t *);
+void ffs_cleanup_opts(fsinfo_t *);
+void ffs_makefs(const char *, const char *, fsnode *, fsinfo_t *);
+
+void cd9660_prep_opts(fsinfo_t *);
+int cd9660_parse_opts(const char *, fsinfo_t *);
+void cd9660_cleanup_opts(fsinfo_t *);
+void cd9660_makefs(const char *, const char *, fsnode *, fsinfo_t *);
+
+
+extern u_int debug;
+extern int dupsok;
+extern struct timespec start_time;
+
+/*
+ * If -x is specified, we want to exclude nodes which do not appear
+ * in the spec file.
+ */
+#define FSNODE_EXCLUDE_P(opts, fsnode) \
+ ((opts)->onlyspec != 0 && ((fsnode)->flags & FSNODE_F_HASSPEC) == 0)
+
+#define DEBUG_TIME 0x00000001
+ /* debug bits 1..3 unused at this time */
+#define DEBUG_WALK_DIR 0x00000010
+#define DEBUG_WALK_DIR_NODE 0x00000020
+#define DEBUG_WALK_DIR_LINKCHECK 0x00000040
+#define DEBUG_DUMP_FSNODES 0x00000080
+#define DEBUG_DUMP_FSNODES_VERBOSE 0x00000100
+#define DEBUG_FS_PARSE_OPTS 0x00000200
+#define DEBUG_FS_MAKEFS 0x00000400
+#define DEBUG_FS_VALIDATE 0x00000800
+#define DEBUG_FS_CREATE_IMAGE 0x00001000
+#define DEBUG_FS_SIZE_DIR 0x00002000
+#define DEBUG_FS_SIZE_DIR_NODE 0x00004000
+#define DEBUG_FS_SIZE_DIR_ADD_DIRENT 0x00008000
+#define DEBUG_FS_POPULATE 0x00010000
+#define DEBUG_FS_POPULATE_DIRBUF 0x00020000
+#define DEBUG_FS_POPULATE_NODE 0x00040000
+#define DEBUG_FS_WRITE_FILE 0x00080000
+#define DEBUG_FS_WRITE_FILE_BLOCK 0x00100000
+#define DEBUG_FS_MAKE_DIRBUF 0x00200000
+#define DEBUG_FS_WRITE_INODE 0x00400000
+#define DEBUG_BUF_BREAD 0x00800000
+#define DEBUG_BUF_BWRITE 0x01000000
+#define DEBUG_BUF_GETBLK 0x02000000
+#define DEBUG_APPLY_SPECFILE 0x04000000
+#define DEBUG_APPLY_SPECENTRY 0x08000000
+#define DEBUG_APPLY_SPECONLY 0x10000000
+
+
+#define TIMER_START(x) \
+ if (debug & DEBUG_TIME) \
+ gettimeofday(&(x), NULL)
+
+#define TIMER_RESULTS(x,d) \
+ if (debug & DEBUG_TIME) { \
+ struct timeval end, td; \
+ gettimeofday(&end, NULL); \
+ timersub(&end, &(x), &td); \
+ printf("%s took %lld.%06ld seconds\n", \
+ (d), (long long)td.tv_sec, \
+ (long)td.tv_usec); \
+ }
+
+
+#ifndef DEFAULT_FSTYPE
+#define DEFAULT_FSTYPE "ffs"
+#endif
+
+
+/*
+ * ffs specific settings
+ * ---------------------
+ */
+
+#define FFS_EI /* for opposite endian support in ffs headers */
+
+/*
+ * Write-arounds/compat shims for endian-agnostic support.
+ * These belong in the kernel if/when it's possible to mount
+ * filesystems w/ either byte order.
+ */
+
+/*
+ * File system internal flags, also in fs_flags.
+ * (Pick highest number to avoid conflicts with others)
+ */
+#define FS_SWAPPED 0x80000000 /* file system is endian swapped */
+#define FS_INTERNAL 0x80000000 /* mask for internal flags */
+
+#define FS_ISCLEAN 1
+
+#define DINODE1_SIZE (sizeof(struct ufs1_dinode))
+#define DINODE2_SIZE (sizeof(struct ufs2_dinode))
+
+#define MAXSYMLINKLEN_UFS1 ((NDADDR + NIADDR) * sizeof(ufs1_daddr_t))
+#define MAXSYMLINKLEN_UFS2 ((NDADDR + NIADDR) * sizeof(ufs2_daddr_t))
+
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+#define DIRSIZ_SWAP(oldfmt, dp, needswap) \
+ (((oldfmt) && !(needswap)) ? \
+ DIRECTSIZ((dp)->d_type) : DIRECTSIZ((dp)->d_namlen))
+#else
+#define DIRSIZ_SWAP(oldfmt, dp, needswap) \
+ (((oldfmt) && (needswap)) ? \
+ DIRECTSIZ((dp)->d_type) : DIRECTSIZ((dp)->d_namlen))
+#endif
+
+#define cg_chkmagic_swap(cgp, ns) \
+ (ufs_rw32((cgp)->cg_magic, (ns)) == CG_MAGIC)
+#define cg_inosused_swap(cgp, ns) \
+ ((u_int8_t *)((u_int8_t *)(cgp) + ufs_rw32((cgp)->cg_iusedoff, (ns))))
+#define cg_blksfree_swap(cgp, ns) \
+ ((u_int8_t *)((u_int8_t *)(cgp) + ufs_rw32((cgp)->cg_freeoff, (ns))))
+#define cg_clustersfree_swap(cgp, ns) \
+ ((u_int8_t *)((u_int8_t *)(cgp) + ufs_rw32((cgp)->cg_clusteroff, (ns))))
+#define cg_clustersum_swap(cgp, ns) \
+ ((int32_t *)((uintptr_t)(cgp) + ufs_rw32((cgp)->cg_clustersumoff, ns)))
+
+struct fs;
+void ffs_fragacct_swap(struct fs *, int, int32_t [], int, int);
+
+fsinode *link_check(fsinode *);
+
+#endif /* _MAKEFS_H */
diff --git a/usr.sbin/makefs/mtree.c b/usr.sbin/makefs/mtree.c
new file mode 100644
index 0000000..8a687bf
--- /dev/null
+++ b/usr.sbin/makefs/mtree.c
@@ -0,0 +1,1132 @@
+/*-
+ * Copyright (c) 2011 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 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/sbuf.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <inttypes.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include "makefs.h"
+
+#define IS_DOT(nm) ((nm)[0] == '.' && (nm)[1] == '\0')
+#define IS_DOTDOT(nm) ((nm)[0] == '.' && (nm)[1] == '.' && (nm)[2] == '\0')
+
+struct mtree_fileinfo {
+ SLIST_ENTRY(mtree_fileinfo) next;
+ FILE *fp;
+ const char *name;
+ u_int line;
+};
+
+/* Global state used while parsing. */
+static SLIST_HEAD(, mtree_fileinfo) mtree_fileinfo =
+ SLIST_HEAD_INITIALIZER(mtree_fileinfo);
+static fsnode *mtree_root;
+static fsnode *mtree_current;
+static fsnode mtree_global;
+static fsinode mtree_global_inode;
+static u_int errors, warnings;
+
+static void mtree_error(const char *, ...) __printflike(1, 2);
+static void mtree_warning(const char *, ...) __printflike(1, 2);
+
+static int
+mtree_file_push(const char *name, FILE *fp)
+{
+ struct mtree_fileinfo *fi;
+
+ fi = malloc(sizeof(*fi));
+ if (fi == NULL)
+ return (ENOMEM);
+
+ if (strcmp(name, "-") == 0)
+ fi->name = strdup("(stdin)");
+ else
+ fi->name = strdup(name);
+ if (fi->name == NULL) {
+ free(fi);
+ return (ENOMEM);
+ }
+
+ fi->fp = fp;
+ fi->line = 0;
+
+ SLIST_INSERT_HEAD(&mtree_fileinfo, fi, next);
+ return (0);
+}
+
+static void
+mtree_print(const char *msgtype, const char *fmt, va_list ap)
+{
+ struct mtree_fileinfo *fi;
+
+ if (msgtype != NULL) {
+ fi = SLIST_FIRST(&mtree_fileinfo);
+ if (fi != NULL)
+ fprintf(stderr, "%s:%u: ", fi->name, fi->line);
+ fprintf(stderr, "%s: ", msgtype);
+ }
+ vfprintf(stderr, fmt, ap);
+}
+
+static void
+mtree_error(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ mtree_print("error", fmt, ap);
+ va_end(ap);
+
+ errors++;
+ fputc('\n', stderr);
+}
+
+static void
+mtree_warning(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ mtree_print("warning", fmt, ap);
+ va_end(ap);
+
+ warnings++;
+ fputc('\n', stderr);
+}
+
+#ifndef MAKEFS_MAX_TREE_DEPTH
+# define MAKEFS_MAX_TREE_DEPTH (MAXPATHLEN/2)
+#endif
+
+/* construct path to node->name */
+static char *
+mtree_file_path(fsnode *node)
+{
+ fsnode *pnode;
+ struct sbuf *sb;
+ char *res, *rp[MAKEFS_MAX_TREE_DEPTH];
+ int depth;
+
+ depth = 0;
+ rp[depth] = node->name;
+ for (pnode = node->parent; pnode && depth < MAKEFS_MAX_TREE_DEPTH;
+ pnode = pnode->parent) {
+ if (strcmp(pnode->name, ".") == 0)
+ break;
+ rp[++depth] = pnode->name;
+ }
+
+ sb = sbuf_new_auto();
+ if (sb == NULL) {
+ errno = ENOMEM;
+ return (NULL);
+ }
+ while (depth > 0) {
+ sbuf_cat(sb, rp[depth--]);
+ sbuf_putc(sb, '/');
+ }
+ sbuf_cat(sb, rp[depth]);
+ sbuf_finish(sb);
+ res = strdup(sbuf_data(sb));
+ sbuf_delete(sb);
+ if (res == NULL)
+ errno = ENOMEM;
+ return res;
+
+}
+
+/* mtree_resolve() sets errno to indicate why NULL was returned. */
+static char *
+mtree_resolve(const char *spec, int *istemp)
+{
+ struct sbuf *sb;
+ char *res, *var = NULL;
+ const char *base, *p, *v;
+ size_t len;
+ int c, error, quoted, subst;
+
+ len = strlen(spec);
+ if (len == 0) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ c = (len > 1) ? (spec[0] == spec[len - 1]) ? spec[0] : 0 : 0;
+ *istemp = (c == '`') ? 1 : 0;
+ subst = (c == '`' || c == '"') ? 1 : 0;
+ quoted = (subst || c == '\'') ? 1 : 0;
+
+ if (!subst) {
+ res = strdup(spec + quoted);
+ if (res != NULL && quoted)
+ res[len - 2] = '\0';
+ return (res);
+ }
+
+ sb = sbuf_new_auto();
+ if (sb == NULL) {
+ errno = ENOMEM;
+ return (NULL);
+ }
+
+ base = spec + 1;
+ len -= 2;
+ error = 0;
+ while (len > 0) {
+ p = strchr(base, '$');
+ if (p == NULL) {
+ sbuf_bcat(sb, base, len);
+ base += len;
+ len = 0;
+ continue;
+ }
+ /* The following is safe. spec always starts with a quote. */
+ if (p[-1] == '\\')
+ p--;
+ if (base != p) {
+ sbuf_bcat(sb, base, p - base);
+ len -= p - base;
+ base = p;
+ }
+ if (*p == '\\') {
+ sbuf_putc(sb, '$');
+ base += 2;
+ len -= 2;
+ continue;
+ }
+ /* Skip the '$'. */
+ base++;
+ len--;
+ /* Handle ${X} vs $X. */
+ v = base;
+ if (*base == '{') {
+ p = strchr(v, '}');
+ if (p == NULL)
+ p = v;
+ } else
+ p = v;
+ len -= (p + 1) - base;
+ base = p + 1;
+
+ if (v == p) {
+ sbuf_putc(sb, *v);
+ continue;
+ }
+
+ error = ENOMEM;
+ var = calloc(p - v, 1);
+ if (var == NULL)
+ break;
+
+ memcpy(var, v + 1, p - v - 1);
+ if (strcmp(var, ".CURDIR") == 0) {
+ res = getcwd(NULL, 0);
+ if (res == NULL)
+ break;
+ } else if (strcmp(var, ".PROG") == 0) {
+ res = strdup(getprogname());
+ if (res == NULL)
+ break;
+ } else {
+ v = getenv(var);
+ if (v != NULL) {
+ res = strdup(v);
+ if (res == NULL)
+ break;
+ } else
+ res = NULL;
+ }
+ error = 0;
+
+ if (res != NULL) {
+ sbuf_cat(sb, res);
+ free(res);
+ }
+ free(var);
+ var = NULL;
+ }
+
+ free(var);
+ sbuf_finish(sb);
+ res = (error == 0) ? strdup(sbuf_data(sb)) : NULL;
+ sbuf_delete(sb);
+ if (res == NULL)
+ errno = ENOMEM;
+ return (res);
+}
+
+static int
+skip_over(FILE *fp, const char *cs)
+{
+ int c;
+
+ c = getc(fp);
+ while (c != EOF && strchr(cs, c) != NULL)
+ c = getc(fp);
+ if (c != EOF) {
+ ungetc(c, fp);
+ return (0);
+ }
+ return (ferror(fp) ? errno : -1);
+}
+
+static int
+skip_to(FILE *fp, const char *cs)
+{
+ int c;
+
+ c = getc(fp);
+ while (c != EOF && strchr(cs, c) == NULL)
+ c = getc(fp);
+ if (c != EOF) {
+ ungetc(c, fp);
+ return (0);
+ }
+ return (ferror(fp) ? errno : -1);
+}
+
+static int
+read_word(FILE *fp, char *buf, size_t bufsz)
+{
+ struct mtree_fileinfo *fi;
+ size_t idx, qidx;
+ int c, done, error, esc, qlvl;
+
+ if (bufsz == 0)
+ return (EINVAL);
+
+ done = 0;
+ esc = 0;
+ idx = 0;
+ qidx = -1;
+ qlvl = 0;
+ do {
+ c = getc(fp);
+ switch (c) {
+ case EOF:
+ buf[idx] = '\0';
+ error = ferror(fp) ? errno : -1;
+ if (error == -1)
+ mtree_error("unexpected end of file");
+ return (error);
+ case '#': /* comment -- skip to end of line. */
+ if (!esc) {
+ error = skip_to(fp, "\n");
+ if (!error)
+ continue;
+ }
+ break;
+ case '\\':
+ esc++;
+ if (esc == 1)
+ continue;
+ break;
+ case '`':
+ case '\'':
+ case '"':
+ if (esc)
+ break;
+ if (qlvl == 0) {
+ qlvl++;
+ qidx = idx;
+ } else if (c == buf[qidx]) {
+ qlvl--;
+ if (qlvl > 0) {
+ do {
+ qidx--;
+ } while (buf[qidx] != '`' &&
+ buf[qidx] != '\'' &&
+ buf[qidx] != '"');
+ } else
+ qidx = -1;
+ } else {
+ qlvl++;
+ qidx = idx;
+ }
+ break;
+ case ' ':
+ case '\t':
+ case '\n':
+ if (!esc && qlvl == 0) {
+ ungetc(c, fp);
+ c = '\0';
+ done = 1;
+ break;
+ }
+ if (c == '\n') {
+ /*
+ * We going to eat the newline ourselves.
+ */
+ if (qlvl > 0)
+ mtree_warning("quoted word straddles "
+ "onto next line.");
+ fi = SLIST_FIRST(&mtree_fileinfo);
+ fi->line++;
+ }
+ break;
+ case 'a':
+ if (esc)
+ c = '\a';
+ break;
+ case 'b':
+ if (esc)
+ c = '\b';
+ break;
+ case 'f':
+ if (esc)
+ c = '\f';
+ break;
+ case 'n':
+ if (esc)
+ c = '\n';
+ break;
+ case 'r':
+ if (esc)
+ c = '\r';
+ break;
+ case 't':
+ if (esc)
+ c = '\t';
+ break;
+ case 'v':
+ if (esc)
+ c = '\v';
+ break;
+ }
+ buf[idx++] = c;
+ esc = 0;
+ } while (idx < bufsz && !done);
+
+ if (idx >= bufsz) {
+ mtree_error("word too long to fit buffer (max %zu characters)",
+ bufsz);
+ skip_to(fp, " \t\n");
+ }
+ return (0);
+}
+
+static fsnode *
+create_node(const char *name, u_int type, fsnode *parent, fsnode *global)
+{
+ fsnode *n;
+
+ n = calloc(1, sizeof(*n));
+ if (n == NULL)
+ return (NULL);
+
+ n->name = strdup(name);
+ if (n->name == NULL) {
+ free(n);
+ return (NULL);
+ }
+
+ n->type = (type == 0) ? global->type : type;
+ n->parent = parent;
+
+ n->inode = calloc(1, sizeof(*n->inode));
+ if (n->inode == NULL) {
+ free(n->name);
+ free(n);
+ return (NULL);
+ }
+
+ /* Assign global options/defaults. */
+ bcopy(global->inode, n->inode, sizeof(*n->inode));
+ n->inode->st.st_mode = (n->inode->st.st_mode & ~S_IFMT) | n->type;
+
+ if (n->type == S_IFLNK)
+ n->symlink = global->symlink;
+ else if (n->type == S_IFREG)
+ n->contents = global->contents;
+
+ return (n);
+}
+
+static void
+destroy_node(fsnode *n)
+{
+
+ assert(n != NULL);
+ assert(n->name != NULL);
+ assert(n->inode != NULL);
+
+ free(n->inode);
+ free(n->name);
+ free(n);
+}
+
+static int
+read_number(const char *tok, u_int base, intmax_t *res, intmax_t min,
+ intmax_t max)
+{
+ char *end;
+ intmax_t val;
+
+ val = strtoimax(tok, &end, base);
+ if (end == tok || end[0] != '\0')
+ return (EINVAL);
+ if (val < min || val > max)
+ return (EDOM);
+ *res = val;
+ return (0);
+}
+
+static int
+read_mtree_keywords(FILE *fp, fsnode *node)
+{
+ char keyword[PATH_MAX];
+ char *name, *p, *value;
+ gid_t gid;
+ uid_t uid;
+ struct stat *st, sb;
+ intmax_t num;
+ u_long flset, flclr;
+ int error, istemp, type;
+
+ st = &node->inode->st;
+ do {
+ error = skip_over(fp, " \t");
+ if (error)
+ break;
+
+ error = read_word(fp, keyword, sizeof(keyword));
+ if (error)
+ break;
+
+ if (keyword[0] == '\0')
+ break;
+
+ value = strchr(keyword, '=');
+ if (value != NULL)
+ *value++ = '\0';
+
+ /*
+ * We use EINVAL, ENOATTR, ENOSYS and ENXIO to signal
+ * certain conditions:
+ * EINVAL - Value provided for a keyword that does
+ * not take a value. The value is ignored.
+ * ENOATTR - Value missing for a keyword that needs
+ * a value. The keyword is ignored.
+ * ENOSYS - Unsupported keyword encountered. The
+ * keyword is ignored.
+ * ENXIO - Value provided for a keyword that does
+ * not take a value. The value is ignored.
+ */
+ switch (keyword[0]) {
+ case 'c':
+ if (strcmp(keyword, "contents") == 0) {
+ if (value == NULL) {
+ error = ENOATTR;
+ break;
+ }
+ node->contents = strdup(value);
+ } else
+ error = ENOSYS;
+ break;
+ case 'f':
+ if (strcmp(keyword, "flags") == 0) {
+ if (value == NULL) {
+ error = ENOATTR;
+ break;
+ }
+ flset = flclr = 0;
+ if (!strtofflags(&value, &flset, &flclr)) {
+ st->st_flags &= ~flclr;
+ st->st_flags |= flset;
+ } else
+ error = errno;
+ } else
+ error = ENOSYS;
+ break;
+ case 'g':
+ if (strcmp(keyword, "gid") == 0) {
+ if (value == NULL) {
+ error = ENOATTR;
+ break;
+ }
+ error = read_number(value, 10, &num,
+ 0, UINT_MAX);
+ if (!error)
+ st->st_gid = num;
+ } else if (strcmp(keyword, "gname") == 0) {
+ if (value == NULL) {
+ error = ENOATTR;
+ break;
+ }
+ if (gid_from_group(value, &gid) == 0)
+ st->st_gid = gid;
+ else
+ error = EINVAL;
+ } else
+ error = ENOSYS;
+ break;
+ case 'l':
+ if (strcmp(keyword, "link") == 0) {
+ if (value == NULL) {
+ error = ENOATTR;
+ break;
+ }
+ node->symlink = strdup(value);
+ } else
+ error = ENOSYS;
+ break;
+ case 'm':
+ if (strcmp(keyword, "mode") == 0) {
+ if (value == NULL) {
+ error = ENOATTR;
+ break;
+ }
+ if (value[0] >= '0' && value[0] <= '9') {
+ error = read_number(value, 8, &num,
+ 0, 07777);
+ if (!error) {
+ st->st_mode &= S_IFMT;
+ st->st_mode |= num;
+ }
+ } else {
+ /* Symbolic mode not supported. */
+ error = EINVAL;
+ break;
+ }
+ } else
+ error = ENOSYS;
+ break;
+ case 'o':
+ if (strcmp(keyword, "optional") == 0) {
+ if (value != NULL)
+ error = ENXIO;
+ node->flags |= FSNODE_F_OPTIONAL;
+ } else
+ error = ENOSYS;
+ break;
+ case 's':
+ if (strcmp(keyword, "size") == 0) {
+ if (value == NULL) {
+ error = ENOATTR;
+ break;
+ }
+ error = read_number(value, 10, &num,
+ 0, INTMAX_MAX);
+ if (!error)
+ st->st_size = num;
+ } else
+ error = ENOSYS;
+ break;
+ case 't':
+ if (strcmp(keyword, "time") == 0) {
+ if (value == NULL) {
+ error = ENOATTR;
+ break;
+ }
+ p = strchr(value, '.');
+ if (p != NULL)
+ *p++ = '\0';
+ error = read_number(value, 10, &num, 0,
+ INTMAX_MAX);
+ if (error)
+ break;
+ st->st_atime = num;
+ st->st_ctime = num;
+ st->st_mtime = num;
+ if (p == NULL)
+ break;
+ error = read_number(p, 10, &num, 0,
+ INTMAX_MAX);
+ if (error)
+ break;
+ if (num != 0)
+ error = EINVAL;
+ } else if (strcmp(keyword, "type") == 0) {
+ if (value == NULL) {
+ error = ENOATTR;
+ break;
+ }
+ if (strcmp(value, "dir") == 0)
+ node->type = S_IFDIR;
+ else if (strcmp(value, "file") == 0)
+ node->type = S_IFREG;
+ else if (strcmp(value, "link") == 0)
+ node->type = S_IFLNK;
+ else
+ error = EINVAL;
+ } else
+ error = ENOSYS;
+ break;
+ case 'u':
+ if (strcmp(keyword, "uid") == 0) {
+ if (value == NULL) {
+ error = ENOATTR;
+ break;
+ }
+ error = read_number(value, 10, &num,
+ 0, UINT_MAX);
+ if (!error)
+ st->st_uid = num;
+ } else if (strcmp(keyword, "uname") == 0) {
+ if (value == NULL) {
+ error = ENOATTR;
+ break;
+ }
+ if (uid_from_user(value, &uid) == 0)
+ st->st_uid = uid;
+ else
+ error = EINVAL;
+ } else
+ error = ENOSYS;
+ break;
+ default:
+ error = ENOSYS;
+ break;
+ }
+
+ switch (error) {
+ case EINVAL:
+ mtree_error("%s: invalid value '%s'", keyword, value);
+ break;
+ case ENOATTR:
+ mtree_error("%s: keyword needs a value", keyword);
+ break;
+ case ENOSYS:
+ mtree_warning("%s: unsupported keyword", keyword);
+ break;
+ case ENXIO:
+ mtree_error("%s: keyword does not take a value",
+ keyword);
+ break;
+ }
+ } while (1);
+
+ if (error)
+ return (error);
+
+ st->st_mode = (st->st_mode & ~S_IFMT) | node->type;
+
+ /* Nothing more to do for the global defaults. */
+ if (node->name == NULL)
+ return (0);
+
+ /*
+ * Be intelligent about the file type.
+ */
+ if (node->contents != NULL) {
+ if (node->symlink != NULL) {
+ mtree_error("%s: both link and contents keywords "
+ "defined", node->name);
+ return (0);
+ }
+ type = S_IFREG;
+ } else if (node->type != 0) {
+ type = node->type;
+ if (type == S_IFREG) {
+ /* the named path is the default contents */
+ node->contents = mtree_file_path(node);
+ }
+ } else
+ type = (node->symlink != NULL) ? S_IFLNK : S_IFDIR;
+
+ if (node->type == 0)
+ node->type = type;
+
+ if (node->type != type) {
+ mtree_error("%s: file type and defined keywords to not match",
+ node->name);
+ return (0);
+ }
+
+ st->st_mode = (st->st_mode & ~S_IFMT) | node->type;
+
+ if (node->contents == NULL)
+ return (0);
+
+ name = mtree_resolve(node->contents, &istemp);
+ if (name == NULL)
+ return (errno);
+
+ if (stat(name, &sb) != 0) {
+ mtree_error("%s: contents file '%s' not found", node->name,
+ name);
+ free(name);
+ return (0);
+ }
+
+ /*
+ * Check for hardlinks. If the contents key is used, then the check
+ * will only trigger if the contents file is a link even if it is used
+ * by more than one file
+ */
+ if (sb.st_nlink > 1) {
+ fsinode *curino;
+
+ st->st_ino = sb.st_ino;
+ st->st_dev = sb.st_dev;
+ curino = link_check(node->inode);
+ if (curino != NULL) {
+ free(node->inode);
+ node->inode = curino;
+ node->inode->nlink++;
+ }
+ }
+
+ free(node->contents);
+ node->contents = name;
+ st->st_size = sb.st_size;
+ return (0);
+}
+
+static int
+read_mtree_command(FILE *fp)
+{
+ char cmd[10];
+ int error;
+
+ error = read_word(fp, cmd, sizeof(cmd));
+ if (error)
+ goto out;
+
+ error = read_mtree_keywords(fp, &mtree_global);
+
+ out:
+ skip_to(fp, "\n");
+ (void)getc(fp);
+ return (error);
+}
+
+static int
+read_mtree_spec1(FILE *fp, bool def, const char *name)
+{
+ fsnode *last, *node, *parent;
+ u_int type;
+ int error;
+
+ assert(name[0] != '\0');
+
+ /*
+ * Treat '..' specially, because it only changes our current
+ * directory. We don't create a node for it. We simply ignore
+ * any keywords that may appear on the line as well.
+ * Going up a directory is a little non-obvious. A directory
+ * node has a corresponding '.' child. The parent of '.' is
+ * not the '.' node of the parent directory, but the directory
+ * node within the parent to which the child relates. However,
+ * going up a directory means we need to find the '.' node to
+ * which the directoy node is linked. This we can do via the
+ * first * pointer, because '.' is always the first entry in a
+ * directory.
+ */
+ if (IS_DOTDOT(name)) {
+ /* This deals with NULL pointers as well. */
+ if (mtree_current == mtree_root) {
+ mtree_warning("ignoring .. in root directory");
+ return (0);
+ }
+
+ node = mtree_current;
+
+ assert(node != NULL);
+ assert(IS_DOT(node->name));
+ assert(node->first == node);
+
+ /* Get the corresponding directory node in the parent. */
+ node = mtree_current->parent;
+
+ assert(node != NULL);
+ assert(!IS_DOT(node->name));
+
+ node = node->first;
+
+ assert(node != NULL);
+ assert(IS_DOT(node->name));
+ assert(node->first == node);
+
+ mtree_current = node;
+ return (0);
+ }
+
+ /*
+ * If we don't have a current directory and the first specification
+ * (either implicit or defined) is not '.', then we need to create
+ * a '.' node first (using a recursive call).
+ */
+ if (!IS_DOT(name) && mtree_current == NULL) {
+ error = read_mtree_spec1(fp, false, ".");
+ if (error)
+ return (error);
+ }
+
+ /*
+ * Lookup the name in the current directory (if we have a current
+ * directory) to make sure we do not create multiple nodes for the
+ * same component. For non-definitions, if we find a node with the
+ * same name, simply change the current directory. For definitions
+ * more happens.
+ */
+ last = NULL;
+ node = mtree_current;
+ while (node != NULL) {
+ assert(node->first == mtree_current);
+
+ if (strcmp(name, node->name) == 0) {
+ if (def == true) {
+ if (!dupsok)
+ mtree_error(
+ "duplicate definition of %s",
+ name);
+ else
+ mtree_warning(
+ "duplicate definition of %s",
+ name);
+ return (0);
+ }
+
+ if (node->type != S_IFDIR) {
+ mtree_error("%s is not a directory", name);
+ return (0);
+ }
+
+ assert(!IS_DOT(name));
+
+ node = node->child;
+
+ assert(node != NULL);
+ assert(IS_DOT(node->name));
+
+ mtree_current = node;
+ return (0);
+ }
+
+ last = node;
+ node = last->next;
+ }
+
+ parent = (mtree_current != NULL) ? mtree_current->parent : NULL;
+ type = (def == false || IS_DOT(name)) ? S_IFDIR : 0;
+ node = create_node(name, type, parent, &mtree_global);
+ if (node == NULL)
+ return (ENOMEM);
+
+ if (def == true) {
+ error = read_mtree_keywords(fp, node);
+ if (error) {
+ destroy_node(node);
+ return (error);
+ }
+ }
+
+ node->first = (mtree_current != NULL) ? mtree_current : node;
+
+ if (last != NULL)
+ last->next = node;
+
+ if (node->type != S_IFDIR)
+ return (0);
+
+ if (!IS_DOT(node->name)) {
+ parent = node;
+ node = create_node(".", S_IFDIR, parent, parent);
+ if (node == NULL) {
+ last->next = NULL;
+ destroy_node(parent);
+ return (ENOMEM);
+ }
+ parent->child = node;
+ node->first = node;
+ }
+
+ assert(node != NULL);
+ assert(IS_DOT(node->name));
+ assert(node->first == node);
+
+ mtree_current = node;
+ if (mtree_root == NULL)
+ mtree_root = node;
+
+ return (0);
+}
+
+static int
+read_mtree_spec(FILE *fp)
+{
+ char pathspec[PATH_MAX];
+ char *cp;
+ int error;
+
+ error = read_word(fp, pathspec, sizeof(pathspec));
+ if (error)
+ goto out;
+
+ cp = strchr(pathspec, '/');
+ if (cp != NULL) {
+ /* Absolute pathname */
+ mtree_current = mtree_root;
+
+ do {
+ *cp++ = '\0';
+
+ /* Disallow '..' as a component. */
+ if (IS_DOTDOT(pathspec)) {
+ mtree_error("absolute path cannot contain "
+ ".. component");
+ goto out;
+ }
+
+ /* Ignore multiple adjacent slashes and '.'. */
+ if (pathspec[0] != '\0' && !IS_DOT(pathspec))
+ error = read_mtree_spec1(fp, false, pathspec);
+ memmove(pathspec, cp, strlen(cp) + 1);
+ cp = strchr(pathspec, '/');
+ } while (!error && cp != NULL);
+
+ /* Disallow '.' and '..' as the last component. */
+ if (!error && (IS_DOT(pathspec) || IS_DOTDOT(pathspec))) {
+ mtree_error("absolute path cannot contain . or .. "
+ "components");
+ goto out;
+ }
+ }
+
+ /* Ignore absolute specfications that end with a slash. */
+ if (!error && pathspec[0] != '\0')
+ error = read_mtree_spec1(fp, true, pathspec);
+
+ out:
+ skip_to(fp, "\n");
+ (void)getc(fp);
+ return (error);
+}
+
+fsnode *
+read_mtree(const char *fname, fsnode *node)
+{
+ struct mtree_fileinfo *fi;
+ FILE *fp;
+ int c, error;
+
+ /* We do not yet support nesting... */
+ assert(node == NULL);
+
+ if (strcmp(fname, "-") == 0)
+ fp = stdin;
+ else {
+ fp = fopen(fname, "r");
+ if (fp == NULL)
+ err(1, "Can't open `%s'", fname);
+ }
+
+ error = mtree_file_push(fname, fp);
+ if (error)
+ goto out;
+
+ bzero(&mtree_global, sizeof(mtree_global));
+ bzero(&mtree_global_inode, sizeof(mtree_global_inode));
+ mtree_global.inode = &mtree_global_inode;
+ mtree_global_inode.nlink = 1;
+ mtree_global_inode.st.st_nlink = 1;
+ mtree_global_inode.st.st_atime = mtree_global_inode.st.st_ctime =
+ mtree_global_inode.st.st_mtime = time(NULL);
+ errors = warnings = 0;
+
+ setgroupent(1);
+ setpassent(1);
+
+ mtree_root = node;
+ mtree_current = node;
+ do {
+ /* Start of a new line... */
+ fi = SLIST_FIRST(&mtree_fileinfo);
+ fi->line++;
+
+ error = skip_over(fp, " \t");
+ if (error)
+ break;
+
+ c = getc(fp);
+ if (c == EOF) {
+ error = ferror(fp) ? errno : -1;
+ break;
+ }
+
+ switch (c) {
+ case '\n': /* empty line */
+ error = 0;
+ break;
+ case '#': /* comment -- skip to end of line. */
+ error = skip_to(fp, "\n");
+ if (!error)
+ (void)getc(fp);
+ break;
+ case '/': /* special commands */
+ error = read_mtree_command(fp);
+ break;
+ default: /* specification */
+ ungetc(c, fp);
+ error = read_mtree_spec(fp);
+ break;
+ }
+ } while (!error);
+
+ endpwent();
+ endgrent();
+
+ if (error <= 0 && (errors || warnings)) {
+ warnx("%u error(s) and %u warning(s) in mtree manifest",
+ errors, warnings);
+ if (errors)
+ exit(1);
+ }
+
+ out:
+ if (error > 0)
+ errc(1, error, "Error reading mtree file");
+
+ if (fp != stdin)
+ fclose(fp);
+
+ if (mtree_root != NULL)
+ return (mtree_root);
+
+ /* Handle empty specifications. */
+ node = create_node(".", S_IFDIR, NULL, &mtree_global);
+ node->first = node;
+ return (node);
+}
diff --git a/usr.sbin/makefs/tests/Makefile b/usr.sbin/makefs/tests/Makefile
new file mode 100644
index 0000000..4373277
--- /dev/null
+++ b/usr.sbin/makefs/tests/Makefile
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+ATF_TESTS_SH+= makefs_cd9660_tests
+ATF_TESTS_SH+= makefs_ffs_tests
+
+BINDIR= ${TESTSDIR}
+
+SCRIPTS+= makefs_tests_common.sh
+SCRIPTSNAME_makefs_tests_common.sh= makefs_tests_common.sh
+
+.for t in ${ATF_TESTS_SH}
+TEST_METADATA.$t+= required_user="root"
+.endfor
+
+.include <bsd.test.mk>
diff --git a/usr.sbin/makefs/tests/makefs_cd9660_tests.sh b/usr.sbin/makefs/tests/makefs_cd9660_tests.sh
new file mode 100755
index 0000000..161b56b
--- /dev/null
+++ b/usr.sbin/makefs/tests/makefs_cd9660_tests.sh
@@ -0,0 +1,373 @@
+#
+# Copyright 2015 EMC Corp.
+# 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
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (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$
+#
+
+# A note on specs:
+# - A copy of the ISO-9660 spec can be found here:
+# http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-119.pdf
+# - Any references to `rockridge` are referring to the `Rock Ridge` extensions
+# of the ISO-9660 spec. A copy of the draft `IEEE-P1282` spec can be found
+# here:
+# http://www.ymi.com/ymi/sites/default/files/pdf/Rockridge.pdf
+
+MAKEFS="makefs -t cd9660"
+MOUNT="mount_cd9660"
+
+. "$(dirname "$0")/makefs_tests_common.sh"
+
+common_cleanup()
+{
+ if ! test_md_device=$(cat $TEST_MD_DEVICE_FILE); then
+ echo "$TEST_MD_DEVICE_FILE could not be opened; has an md(4) device been attached?"
+ return
+ fi
+
+ umount -f /dev/$test_md_device || :
+ mdconfig -d -u $test_md_device || :
+}
+
+check_base_iso9660_image_contents()
+{
+ # Symlinks are treated like files when rockridge support isn't
+ # specified
+ check_image_contents "$@" -X c
+
+ atf_check -e empty -o empty -s exit:0 test -L $TEST_INPUTS_DIR/c
+ atf_check -e empty -o empty -s exit:0 test -f $TEST_MOUNT_DIR/c
+}
+
+atf_test_case D_flag cleanup
+D_flag_body()
+{
+ atf_skip "makefs crashes with SIGBUS with dupe mtree entries; see FreeBSD bug # 192839"
+
+ create_test_inputs
+
+ atf_check -e empty -o save:$TEST_SPEC_FILE -s exit:0 \
+ mtree -cp $TEST_INPUTS_DIR
+ atf_check -e empty -o not-empty -s exit:0 \
+ $MAKEFS -F $TEST_SPEC_FILE -M 1m $TEST_IMAGE $TEST_INPUTS_DIR
+
+ atf_check -e empty -o empty -s exit:0 \
+ cp $TEST_SPEC_FILE spec2.mtree
+ atf_check -e empty -o save:dupe_$TEST_SPEC_FILE -s exit:0 \
+ cat $TEST_SPEC_FILE spec2.mtree
+
+ atf_check -e empty -o not-empty -s not-exit:0 \
+ $MAKEFS -F dupe_$TEST_SPEC_FILE -M 1m $TEST_IMAGE $TEST_INPUTS_DIR
+ atf_check -e empty -o not-empty -s exit:0 \
+ $MAKEFS -D -F dupe_$TEST_SPEC_FILE -M 1m $TEST_IMAGE $TEST_INPUTS_DIR
+}
+D_flag_cleanup()
+{
+ common_cleanup
+}
+
+atf_test_case F_flag cleanup
+F_flag_body()
+{
+ create_test_inputs
+
+ atf_check -e empty -o save:$TEST_SPEC_FILE -s exit:0 \
+ mtree -cp $TEST_INPUTS_DIR
+
+ atf_check -e empty -o empty -s exit:0 \
+ $MAKEFS -F $TEST_SPEC_FILE -M 1m $TEST_IMAGE $TEST_INPUTS_DIR
+
+ mount_image
+ check_base_iso9660_image_contents
+}
+F_flag_cleanup()
+{
+ common_cleanup
+}
+
+atf_test_case from_mtree_spec_file cleanup
+from_mtree_spec_file_body()
+{
+ create_test_inputs
+
+ atf_check -e empty -o save:$TEST_SPEC_FILE -s exit:0 \
+ mtree -c -k "$DEFAULT_MTREE_KEYWORDS" -p $TEST_INPUTS_DIR
+ cd $TEST_INPUTS_DIR
+ atf_check -e empty -o empty -s exit:0 \
+ $MAKEFS $TEST_IMAGE $TEST_SPEC_FILE
+ cd -
+
+ mount_image
+ check_base_iso9660_image_contents
+}
+from_mtree_spec_file_cleanup()
+{
+ common_cleanup
+}
+
+atf_test_case from_multiple_dirs cleanup
+from_multiple_dirs_body()
+{
+ test_inputs_dir2=$TMPDIR/inputs2
+
+ create_test_inputs
+
+ atf_check -e empty -o empty -s exit:0 mkdir -p $test_inputs_dir2
+ atf_check -e empty -o empty -s exit:0 \
+ touch $test_inputs_dir2/multiple_dirs_test_file
+
+ atf_check -e empty -o empty -s exit:0 \
+ $MAKEFS $TEST_IMAGE $TEST_INPUTS_DIR $test_inputs_dir2
+
+ mount_image
+ check_base_iso9660_image_contents -d $test_inputs_dir2
+}
+from_multiple_dirs_cleanup()
+{
+ common_cleanup
+}
+
+atf_test_case from_single_dir cleanup
+from_single_dir_body()
+{
+ create_test_inputs
+
+ atf_check -e empty -o empty -s exit:0 \
+ $MAKEFS $TEST_IMAGE $TEST_INPUTS_DIR
+
+ mount_image
+ check_base_iso9660_image_contents
+}
+from_single_dir_cleanup()
+{
+ common_cleanup
+}
+
+atf_test_case o_flag_allow_deep_trees cleanup
+o_flag_allow_deep_trees_body()
+{
+ create_test_inputs
+
+ # Make sure the "more than 8 levels deep" requirement is met.
+ atf_check -e empty -o empty -s exit:0 \
+ mkdir -p $TEST_INPUTS_DIR/a/b/c/d/e/f/g/h/i/j
+
+ atf_check -e empty -o empty -s exit:0 \
+ $MAKEFS -o allow-deep-trees $TEST_IMAGE $TEST_INPUTS_DIR
+
+ mount_image
+ check_base_iso9660_image_contents
+}
+o_flag_allow_deep_trees_cleanup()
+{
+ common_cleanup
+}
+
+atf_test_case o_flag_allow_max_name cleanup
+o_flag_allow_max_name_body()
+{
+ atf_expect_fail "-o allow-max-name doesn't appear to be implemented on FreeBSD's copy of makefs [yet]"
+
+ create_test_inputs
+
+ long_path=$TEST_INPUTS_DIR/$(jot -s '' -b 0 37)
+
+ # Make sure the "37 char name" limit requirement is met.
+ atf_check -e empty -o empty -s exit:0 touch $long_path
+
+ atf_check -e empty -o empty -s exit:0 \
+ $MAKEFS -o allow-max-name $TEST_IMAGE $TEST_INPUTS_DIR
+
+ mount_image
+ check_base_iso9660_image_contents
+}
+o_flag_allow_max_name_cleanup()
+{
+ common_cleanup
+}
+
+atf_test_case o_flag_isolevel_1 cleanup
+o_flag_isolevel_1_body()
+{
+ atf_expect_fail "this testcase needs work; the filenames generated seem incorrect/corrupt"
+
+ create_test_inputs
+
+ atf_check -e empty -o empty -s exit:0 \
+ $MAKEFS -o isolevel=1 $TEST_IMAGE $TEST_INPUTS_DIR
+
+ mount_image
+ check_base_iso9660_image_contents
+}
+o_flag_isolevel_1_cleanup()
+{
+ common_cleanup
+}
+
+atf_test_case o_flag_isolevel_2 cleanup
+o_flag_isolevel_2_body()
+{
+ create_test_inputs
+
+ atf_check -e empty -o empty -s exit:0 \
+ $MAKEFS -o isolevel=2 $TEST_IMAGE $TEST_INPUTS_DIR
+
+ mount_image
+ check_base_iso9660_image_contents
+}
+o_flag_isolevel_2_cleanup()
+{
+ common_cleanup
+}
+
+atf_test_case o_flag_isolevel_3 cleanup
+o_flag_isolevel_3_body()
+{
+ create_test_inputs
+
+ # XXX: isolevel=3 isn't implemented yet. See FreeBSD bug # 203645
+ if true; then
+ atf_check -e match:'makefs: ISO Level 3 is greater than 2\.' -o empty -s not-exit:0 \
+ $MAKEFS -o isolevel=3 $TEST_IMAGE $TEST_INPUTS_DIR
+ else
+ atf_check -e empty -o empty -s exit:0 \
+ $MAKEFS -o isolevel=3 $TEST_IMAGE $TEST_INPUTS_DIR
+ mount_image
+ check_base_iso9660_image_contents
+ fi
+}
+o_flag_isolevel_3_cleanup()
+{
+ common_cleanup
+}
+
+atf_test_case o_flag_preparer
+o_flag_preparer_body()
+{
+ create_test_dirs
+
+ preparer='My Very First ISO'
+ preparer_uppercase="$(echo $preparer | tr '[[:lower:]]' '[[:upper:]]')"
+
+ atf_check -e empty -o empty -s exit:0 touch $TEST_INPUTS_DIR/dummy_file
+ atf_check -e empty -o empty -s exit:0 \
+ $MAKEFS -o preparer="$preparer" $TEST_IMAGE $TEST_INPUTS_DIR
+ atf_check -e empty -o match:"$preparer_uppercase" -s exit:0 \
+ strings $TEST_IMAGE
+}
+
+atf_test_case o_flag_publisher
+o_flag_publisher_body()
+{
+ create_test_dirs
+
+ publisher='My Super Awesome Publishing Company LTD'
+ publisher_uppercase="$(echo $publisher | tr '[[:lower:]]' '[[:upper:]]')"
+
+ atf_check -e empty -o empty -s exit:0 touch $TEST_INPUTS_DIR/dummy_file
+ atf_check -e empty -o empty -s exit:0 \
+ $MAKEFS -o publisher="$publisher" $TEST_IMAGE $TEST_INPUTS_DIR
+ atf_check -e empty -o match:"$publisher_uppercase" -s exit:0 \
+ strings $TEST_IMAGE
+}
+
+atf_test_case o_flag_rockridge cleanup
+o_flag_rockridge_body()
+{
+ create_test_dirs
+
+ # Make sure the "more than 8 levels deep" requirement is met.
+ atf_check -e empty -o empty -s exit:0 \
+ mkdir -p $TEST_INPUTS_DIR/a/b/c/d/e/f/g/h/i/j
+
+ # Make sure the "pathname larger than 255 chars" requirement is met.
+ #
+ # $long_path's needs to be nested in a directory, as creating it
+ # outright as a 256 char filename via touch will fail with ENAMETOOLONG
+ long_path=$TEST_INPUTS_DIR/$(jot -s '/' -b "$(jot -s '' -b 0 64)" 4)
+ atf_check -e empty -o empty -s exit:0 mkdir -p "$(dirname $long_path)"
+ atf_check -e empty -o empty -s exit:0 touch "$long_path"
+
+ atf_check -e empty -o empty -s exit:0 \
+ $MAKEFS -o rockridge $TEST_IMAGE $TEST_INPUTS_DIR
+
+ mount_image
+ check_image_contents -X .rr_moved
+
+ # .rr_moved is a special directory created when you have deep directory
+ # trees with rock ridge extensions on
+ atf_check -e empty -o empty -s exit:0 \
+ test -d $TEST_MOUNT_DIR/.rr_moved
+}
+o_flag_rockridge_cleanup()
+{
+ common_cleanup
+}
+
+atf_test_case o_flag_rockridge_dev_nodes cleanup
+o_flag_rockridge_dev_nodes_head()
+{
+ atf_set "descr" "Functional tests to ensure that dev nodes are handled properly with rockridge extensions (NetBSD kern/48852; FreeBSD bug 203648)"
+}
+o_flag_rockridge_dev_nodes_body()
+{
+ create_test_dirs
+
+ (tar -cvf - -C /dev null && touch .tar_ok) | \
+ atf_check -e not-empty -o empty -s exit:0 tar -xvf - -C "$TEST_INPUTS_DIR"
+
+ atf_check -e empty -o empty -s exit:0 test -c $TEST_INPUTS_DIR/null
+ atf_check -e empty -o empty -s exit:0 test -f .tar_ok
+
+ atf_check -e empty -o empty -s exit:0 \
+ $MAKEFS -o rockridge $TEST_IMAGE $TEST_INPUTS_DIR
+
+ mount_image
+ check_image_contents
+}
+o_flag_rockridge_dev_nodes_cleanup()
+{
+ common_cleanup
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case D_flag
+ atf_add_test_case F_flag
+
+ atf_add_test_case from_mtree_spec_file
+ atf_add_test_case from_multiple_dirs
+ atf_add_test_case from_single_dir
+
+ atf_add_test_case o_flag_allow_deep_trees
+ atf_add_test_case o_flag_allow_max_name
+ atf_add_test_case o_flag_isolevel_1
+ atf_add_test_case o_flag_isolevel_2
+ atf_add_test_case o_flag_isolevel_3
+ atf_add_test_case o_flag_preparer
+ atf_add_test_case o_flag_publisher
+ atf_add_test_case o_flag_rockridge
+ atf_add_test_case o_flag_rockridge_dev_nodes
+}
diff --git a/usr.sbin/makefs/tests/makefs_ffs_tests.sh b/usr.sbin/makefs/tests/makefs_ffs_tests.sh
new file mode 100755
index 0000000..121c2a2
--- /dev/null
+++ b/usr.sbin/makefs/tests/makefs_ffs_tests.sh
@@ -0,0 +1,237 @@
+#
+# Copyright 2015 EMC Corp.
+# 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
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (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$
+#
+
+MAKEFS="makefs -t ffs"
+MOUNT="mount"
+
+. "$(dirname "$0")/makefs_tests_common.sh"
+
+TEST_TUNEFS_OUTPUT=$TMPDIR/tunefs.output
+
+common_cleanup()
+{
+ if ! test_md_device=$(cat $TEST_MD_DEVICE_FILE); then
+ echo "$TEST_MD_DEVICE_FILE could not be opened; has an md(4) device been attached?"
+ return
+ fi
+
+ umount -f /dev/$test_md_device || :
+ mdconfig -d -u $test_md_device || :
+}
+
+check_ffs_image_contents()
+{
+ atf_check -e save:$TEST_TUNEFS_OUTPUT -o empty -s exit:0 \
+ tunefs -p /dev/$(cat $TEST_MD_DEVICE_FILE)
+
+ check_image_contents "$@"
+}
+
+atf_test_case D_flag cleanup
+D_flag_body()
+{
+ atf_skip "makefs crashes with SIGBUS with dupe mtree entries; see FreeBSD bug # 192839"
+
+ create_test_inputs
+
+ atf_check -e empty -o save:$TEST_SPEC_FILE -s exit:0 \
+ mtree -cp $TEST_INPUTS_DIR
+ atf_check -e empty -o not-empty -s exit:0 \
+ $MAKEFS -F $TEST_SPEC_FILE -M 1m $TEST_IMAGE $TEST_INPUTS_DIR
+
+ atf_check -e empty -o empty -s exit:0 \
+ cp $TEST_SPEC_FILE spec2.mtree
+ atf_check -e empty -o save:dupe_$TEST_SPEC_FILE -s exit:0 \
+ cat $TEST_SPEC_FILE spec2.mtree
+
+ atf_check -e empty -o not-empty -s not-exit:0 \
+ $MAKEFS -F dupe_$TEST_SPEC_FILE -M 1m $TEST_IMAGE $TEST_INPUTS_DIR
+ atf_check -e empty -o not-empty -s exit:0 \
+ $MAKEFS -D -F dupe_$TEST_SPEC_FILE -M 1m $TEST_IMAGE $TEST_INPUTS_DIR
+}
+D_flag_cleanup()
+{
+ common_cleanup
+}
+
+atf_test_case F_flag cleanup
+F_flag_body()
+{
+ create_test_inputs
+
+ atf_check -e empty -o save:$TEST_SPEC_FILE -s exit:0 \
+ mtree -cp $TEST_INPUTS_DIR
+
+ atf_check -e empty -o not-empty -s exit:0 \
+ $MAKEFS -F $TEST_SPEC_FILE -M 1m $TEST_IMAGE $TEST_INPUTS_DIR
+
+ mount_image
+ check_ffs_image_contents
+}
+F_flag_cleanup()
+{
+ common_cleanup
+}
+
+atf_test_case from_mtree_spec_file cleanup
+from_mtree_spec_file_body()
+{
+ create_test_inputs
+
+ atf_check -e empty -o save:$TEST_SPEC_FILE -s exit:0 \
+ mtree -c -k "$DEFAULT_MTREE_KEYWORDS" -p $TEST_INPUTS_DIR
+
+ cd $TEST_INPUTS_DIR
+ atf_check -e empty -o not-empty -s exit:0 \
+ $MAKEFS $TEST_IMAGE $TEST_SPEC_FILE
+ cd -
+
+ mount_image
+ check_ffs_image_contents
+}
+from_mtree_spec_file_cleanup()
+{
+ common_cleanup
+}
+
+atf_test_case from_multiple_dirs cleanup
+from_multiple_dirs_body()
+{
+ test_inputs_dir2=$TMPDIR/inputs2
+
+ create_test_inputs
+
+ atf_check -e empty -o empty -s exit:0 mkdir -p $test_inputs_dir2
+ atf_check -e empty -o empty -s exit:0 \
+ touch $test_inputs_dir2/multiple_dirs_test_file
+
+ atf_check -e empty -o not-empty -s exit:0 \
+ $MAKEFS $TEST_IMAGE $TEST_INPUTS_DIR $test_inputs_dir2
+
+ mount_image
+ check_image_contents -d $test_inputs_dir2
+}
+from_multiple_dirs_cleanup()
+{
+ common_cleanup
+}
+
+atf_test_case from_single_dir cleanup
+from_single_dir_body()
+{
+ create_test_inputs
+
+ atf_check -e empty -o not-empty -s exit:0 \
+ $MAKEFS -M 1m $TEST_IMAGE $TEST_INPUTS_DIR
+
+ mount_image
+ check_ffs_image_contents
+}
+from_single_dir_cleanup()
+{
+ common_cleanup
+}
+
+atf_test_case o_flag_version_1 cleanup
+o_flag_version_1_body()
+{
+ ffs_version=1
+
+ platform=$(uname)
+ case "$platform" in
+ FreeBSD)
+ ffs_label=UFS${ffs_version}
+ ;;
+ NetBSD)
+ ffs_label=FFSv${ffs_version}
+ ;;
+ *)
+ atf_skip "Unsupported platform"
+ ;;
+ esac
+
+ create_test_inputs
+
+ atf_check -e empty -o not-empty -s exit:0 \
+ $MAKEFS -M 1m -o version=$ffs_version $TEST_IMAGE $TEST_INPUTS_DIR
+
+ mount_image
+ atf_check -e empty -o match:"$ffs_label" dumpfs $TEST_MOUNT_DIR
+ check_ffs_image_contents
+}
+o_flag_version_1_cleanup()
+{
+ common_cleanup
+}
+
+atf_test_case o_flag_version_2 cleanup
+o_flag_version_2_body()
+{
+ ffs_version=2
+
+ platform=$(uname)
+ case "$platform" in
+ FreeBSD)
+ ffs_label=UFS${ffs_version}
+ ;;
+ NetBSD)
+ ffs_label=FFSv${ffs_version}
+ ;;
+ *)
+ atf_skip "Unsupported platform"
+ ;;
+ esac
+
+ create_test_inputs
+
+ atf_check -e empty -o not-empty -s exit:0 \
+ $MAKEFS -M 1m -o version=$ffs_version $TEST_IMAGE $TEST_INPUTS_DIR
+
+ mount_image
+ atf_check -e empty -o match:"$ffs_label" dumpfs $TEST_MOUNT_DIR
+ check_ffs_image_contents
+}
+o_flag_version_2_cleanup()
+{
+ common_cleanup
+}
+
+atf_init_test_cases()
+{
+
+ atf_add_test_case D_flag
+ atf_add_test_case F_flag
+
+ atf_add_test_case from_mtree_spec_file
+ atf_add_test_case from_multiple_dirs
+ atf_add_test_case from_single_dir
+
+ atf_add_test_case o_flag_version_1
+ atf_add_test_case o_flag_version_2
+}
diff --git a/usr.sbin/makefs/tests/makefs_tests_common.sh b/usr.sbin/makefs/tests/makefs_tests_common.sh
new file mode 100755
index 0000000..add0b88
--- /dev/null
+++ b/usr.sbin/makefs/tests/makefs_tests_common.sh
@@ -0,0 +1,152 @@
+#
+# Copyright 2015 EMC Corp.
+# 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
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (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$
+#
+
+KB=1024
+: ${TMPDIR=/tmp}
+# TODO: add mtree `time` support; get a lot of errors like this right now when
+# passing generating disk images with keyword mtree support, like:
+#
+# `[...]/mtree.spec:8: error: time: invalid value '1446458503'`
+#
+#DEFAULT_MTREE_KEYWORDS="type,mode,gid,uid,size,link,time"
+DEFAULT_MTREE_KEYWORDS="type,mode,gid,uid,size,link"
+TEST_IMAGE="$TMPDIR/test.img"
+TEST_INPUTS_DIR="$TMPDIR/inputs"
+TEST_MD_DEVICE_FILE="$TMPDIR/md.output"
+TEST_MOUNT_DIR="$TMPDIR/mnt"
+TEST_SPEC_FILE="$TMPDIR/mtree.spec"
+
+check_image_contents()
+{
+ local directories=$TEST_INPUTS_DIR
+ local excludes mtree_excludes_arg mtree_file
+ local mtree_keywords="$DEFAULT_MTREE_KEYWORDS"
+
+ while getopts "d:f:m:X:" flag; do
+ case "$flag" in
+ d)
+ directories="$directories $OPTARG"
+ ;;
+ f)
+ mtree_file=$OPTARG
+ ;;
+ m)
+ mtree_keywords=$OPTARG
+ ;;
+ X)
+ excludes="$excludes $OPTARG"
+ ;;
+ *)
+ echo "usage: check_image_contents [-d directory ...] [-f mtree-file] [-m mtree-keywords] [-X exclude]"
+ atf_fail "unhandled option: $flag"
+ ;;
+ esac
+ done
+
+ if [ -n "$excludes" ]; then
+ echo "$excludes" | tr ' ' '\n' > excludes.txt
+ mtree_excludes_arg="-X excludes.txt"
+ fi
+
+ if [ -z "$mtree_file" ]; then
+ mtree_file=input_spec.mtree
+ for directory in $directories; do
+ mtree -c -k $mtree_keywords -p $directory $mtree_excludes_arg
+ done > $mtree_file
+ fi
+
+ echo "<---- Input spec BEGIN ---->"
+ cat $mtree_file
+ echo "<---- Input spec END ---->"
+ atf_check -e empty -o empty -s exit:0 \
+ mtree -UW -f $mtree_file \
+ -p $TEST_MOUNT_DIR \
+ $mtree_excludes_arg
+}
+
+create_test_dirs()
+{
+ atf_check -e empty -s exit:0 mkdir -m 0777 -p $TEST_MOUNT_DIR
+ atf_check -e empty -s exit:0 mkdir -m 0777 -p $TEST_INPUTS_DIR
+}
+
+create_test_inputs()
+{
+ create_test_dirs
+
+ cd $TEST_INPUTS_DIR
+
+ atf_check -e empty -s exit:0 mkdir -m 0755 -p a/b/1
+ atf_check -e empty -s exit:0 ln -s a/b c
+ atf_check -e empty -s exit:0 touch d
+ atf_check -e empty -s exit:0 ln d e
+ atf_check -e empty -s exit:0 touch .f
+ atf_check -e empty -s exit:0 mkdir .g
+ # XXX: fifos on the filesystem don't match fifos created by makefs for
+ # some odd reason.
+ #atf_check -e empty -s exit:0 mkfifo h
+ atf_check -e ignore -s exit:0 dd if=/dev/zero of=i count=1000 bs=1
+ atf_check -e empty -s exit:0 touch klmn
+ atf_check -e empty -s exit:0 touch opqr
+ atf_check -e empty -s exit:0 touch stuv
+ atf_check -e empty -s exit:0 install -m 0755 /dev/null wxyz
+ atf_check -e empty -s exit:0 touch 0b00000001
+ atf_check -e empty -s exit:0 touch 0b00000010
+ atf_check -e empty -s exit:0 touch 0b00000011
+ atf_check -e empty -s exit:0 touch 0b00000100
+ atf_check -e empty -s exit:0 touch 0b00000101
+ atf_check -e empty -s exit:0 touch 0b00000110
+ atf_check -e empty -s exit:0 touch 0b00000111
+ atf_check -e empty -s exit:0 touch 0b00001000
+ atf_check -e empty -s exit:0 touch 0b00001001
+ atf_check -e empty -s exit:0 touch 0b00001010
+ atf_check -e empty -s exit:0 touch 0b00001011
+ atf_check -e empty -s exit:0 touch 0b00001100
+ atf_check -e empty -s exit:0 touch 0b00001101
+ atf_check -e empty -s exit:0 touch 0b00001110
+
+ for filesize in 1 512 $(( 2 * $KB )) $(( 10 * $KB )) $(( 512 * $KB )); \
+ do
+ atf_check -e ignore -o empty -s exit:0 \
+ dd if=/dev/zero of=${filesize}.file bs=1 \
+ count=1 oseek=${filesize} conv=sparse
+ files="${files} ${filesize}.file"
+ done
+
+ cd -
+}
+
+mount_image()
+{
+ atf_check -e empty -o save:$TEST_MD_DEVICE_FILE -s exit:0 \
+ mdconfig -a -f $TEST_IMAGE
+ atf_check -e empty -o empty -s exit:0 \
+ $MOUNT /dev/$(cat $TEST_MD_DEVICE_FILE) $TEST_MOUNT_DIR
+}
+
diff --git a/usr.sbin/makefs/walk.c b/usr.sbin/makefs/walk.c
new file mode 100644
index 0000000..2fc964a
--- /dev/null
+++ b/usr.sbin/makefs/walk.c
@@ -0,0 +1,687 @@
+/* $NetBSD: walk.c,v 1.24 2008/12/28 21:51:46 christos Exp $ */
+
+/*
+ * Copyright (c) 2001 Wasabi Systems, Inc.
+ * All rights reserved.
+ *
+ * Written by Luke Mewburn 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.
+ */
+
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "makefs.h"
+#include "mtree.h"
+#include "extern.h"
+
+static void apply_specdir(const char *, NODE *, fsnode *, int);
+static void apply_specentry(const char *, NODE *, fsnode *);
+static fsnode *create_fsnode(const char *, const char *, const char *,
+ struct stat *);
+
+
+/*
+ * walk_dir --
+ * build a tree of fsnodes from `root' and `dir', with a parent
+ * fsnode of `parent' (which may be NULL for the root of the tree).
+ * append the tree to a fsnode of `join' if it is not NULL.
+ * each "level" is a directory, with the "." entry guaranteed to be
+ * at the start of the list, and without ".." entries.
+ */
+fsnode *
+walk_dir(const char *root, const char *dir, fsnode *parent, fsnode *join)
+{
+ fsnode *first, *cur, *prev, *last;
+ DIR *dirp;
+ struct dirent *dent;
+ char path[MAXPATHLEN + 1];
+ struct stat stbuf;
+ char *name, *rp;
+ int dot, len;
+
+ assert(root != NULL);
+ assert(dir != NULL);
+
+ len = snprintf(path, sizeof(path), "%s/%s", root, dir);
+ if (len >= (int)sizeof(path))
+ errx(1, "Pathname too long.");
+ if (debug & DEBUG_WALK_DIR)
+ printf("walk_dir: %s %p\n", path, parent);
+ if ((dirp = opendir(path)) == NULL)
+ err(1, "Can't opendir `%s'", path);
+ rp = path + strlen(root) + 1;
+ if (join != NULL) {
+ first = cur = join;
+ while (cur->next != NULL)
+ cur = cur->next;
+ prev = cur;
+ } else
+ first = prev = NULL;
+ last = prev;
+ while ((dent = readdir(dirp)) != NULL) {
+ name = dent->d_name;
+ dot = 0;
+ if (name[0] == '.')
+ switch (name[1]) {
+ case '\0': /* "." */
+ if (join != NULL)
+ continue;
+ dot = 1;
+ break;
+ case '.': /* ".." */
+ if (name[2] == '\0')
+ continue;
+ /* FALLTHROUGH */
+ default:
+ dot = 0;
+ }
+ if (debug & DEBUG_WALK_DIR_NODE)
+ printf("scanning %s/%s/%s\n", root, dir, name);
+ if (snprintf(path + len, sizeof(path) - len, "/%s", name) >=
+ (int)sizeof(path) - len)
+ errx(1, "Pathname too long.");
+ if (lstat(path, &stbuf) == -1)
+ err(1, "Can't lstat `%s'", path);
+#ifdef S_ISSOCK
+ if (S_ISSOCK(stbuf.st_mode & S_IFMT)) {
+ if (debug & DEBUG_WALK_DIR_NODE)
+ printf(" skipping socket %s\n", path);
+ continue;
+ }
+#endif
+
+ if (join != NULL) {
+ cur = join->next;
+ for (;;) {
+ if (cur == NULL || strcmp(cur->name, name) == 0)
+ break;
+ if (cur == last) {
+ cur = NULL;
+ break;
+ }
+ cur = cur->next;
+ }
+ if (cur != NULL) {
+ if (S_ISDIR(cur->type) &&
+ S_ISDIR(stbuf.st_mode)) {
+ if (debug & DEBUG_WALK_DIR_NODE)
+ printf("merging %s with %p\n",
+ path, cur->child);
+ cur->child = walk_dir(root, rp, cur,
+ cur->child);
+ continue;
+ }
+ errx(1, "Can't merge %s `%s' with existing %s",
+ inode_type(stbuf.st_mode), path,
+ inode_type(cur->type));
+ }
+ }
+
+ cur = create_fsnode(root, dir, name, &stbuf);
+ cur->parent = parent;
+ if (dot) {
+ /* ensure "." is at the start of the list */
+ cur->next = first;
+ first = cur;
+ if (! prev)
+ prev = cur;
+ cur->first = first;
+ } else { /* not "." */
+ if (prev)
+ prev->next = cur;
+ prev = cur;
+ if (!first)
+ first = cur;
+ cur->first = first;
+ if (S_ISDIR(cur->type)) {
+ cur->child = walk_dir(root, rp, cur, NULL);
+ continue;
+ }
+ }
+ if (stbuf.st_nlink > 1) {
+ fsinode *curino;
+
+ curino = link_check(cur->inode);
+ if (curino != NULL) {
+ free(cur->inode);
+ cur->inode = curino;
+ cur->inode->nlink++;
+ if (debug & DEBUG_WALK_DIR_LINKCHECK)
+ printf("link_check: found [%llu, %llu]\n",
+ (unsigned long long)curino->st.st_dev,
+ (unsigned long long)curino->st.st_ino);
+ }
+ }
+ if (S_ISLNK(cur->type)) {
+ char slink[PATH_MAX+1];
+ int llen;
+
+ llen = readlink(path, slink, sizeof(slink) - 1);
+ if (llen == -1)
+ err(1, "Readlink `%s'", path);
+ slink[llen] = '\0';
+ if ((cur->symlink = strdup(slink)) == NULL)
+ err(1, "Memory allocation error");
+ }
+ }
+ assert(first != NULL);
+ if (join == NULL)
+ for (cur = first->next; cur != NULL; cur = cur->next)
+ cur->first = first;
+ if (closedir(dirp) == -1)
+ err(1, "Can't closedir `%s/%s'", root, dir);
+ return (first);
+}
+
+static fsnode *
+create_fsnode(const char *root, const char *path, const char *name,
+ struct stat *stbuf)
+{
+ fsnode *cur;
+
+ if ((cur = calloc(1, sizeof(fsnode))) == NULL ||
+ (cur->path = strdup(path)) == NULL ||
+ (cur->name = strdup(name)) == NULL ||
+ (cur->inode = calloc(1, sizeof(fsinode))) == NULL)
+ err(1, "Memory allocation error");
+ cur->root = root;
+ cur->type = stbuf->st_mode & S_IFMT;
+ cur->inode->nlink = 1;
+ cur->inode->st = *stbuf;
+ return (cur);
+}
+
+/*
+ * free_fsnodes --
+ * Removes node from tree and frees it and all of
+ * its descendants.
+ */
+void
+free_fsnodes(fsnode *node)
+{
+ fsnode *cur, *next;
+
+ assert(node != NULL);
+
+ /* for ".", start with actual parent node */
+ if (node->first == node) {
+ assert(node->name[0] == '.' && node->name[1] == '\0');
+ if (node->parent) {
+ assert(node->parent->child == node);
+ node = node->parent;
+ }
+ }
+
+ /* Find ourselves in our sibling list and unlink */
+ if (node->first != node) {
+ for (cur = node->first; cur->next; cur = cur->next) {
+ if (cur->next == node) {
+ cur->next = node->next;
+ node->next = NULL;
+ break;
+ }
+ }
+ }
+
+ for (cur = node; cur != NULL; cur = next) {
+ next = cur->next;
+ if (cur->child) {
+ cur->child->parent = NULL;
+ free_fsnodes(cur->child);
+ }
+ if (cur->inode->nlink-- == 1)
+ free(cur->inode);
+ if (cur->symlink)
+ free(cur->symlink);
+ free(cur->path);
+ free(cur->name);
+ free(cur);
+ }
+}
+
+/*
+ * apply_specfile --
+ * read in the mtree(8) specfile, and apply it to the tree
+ * at dir,parent. parameters in parent on equivalent types
+ * will be changed to those found in specfile, and missing
+ * entries will be added.
+ */
+void
+apply_specfile(const char *specfile, const char *dir, fsnode *parent, int speconly)
+{
+ struct timeval start;
+ FILE *fp;
+ NODE *root;
+
+ assert(specfile != NULL);
+ assert(parent != NULL);
+
+ if (debug & DEBUG_APPLY_SPECFILE)
+ printf("apply_specfile: %s, %s %p\n", specfile, dir, parent);
+
+ /* read in the specfile */
+ if ((fp = fopen(specfile, "r")) == NULL)
+ err(1, "Can't open `%s'", specfile);
+ TIMER_START(start);
+ root = spec(fp);
+ TIMER_RESULTS(start, "spec");
+ if (fclose(fp) == EOF)
+ err(1, "Can't close `%s'", specfile);
+
+ /* perform some sanity checks */
+ if (root == NULL)
+ errx(1, "Specfile `%s' did not contain a tree", specfile);
+ assert(strcmp(root->name, ".") == 0);
+ assert(root->type == F_DIR);
+
+ /* merge in the changes */
+ apply_specdir(dir, root, parent, speconly);
+
+}
+
+static void
+apply_specdir(const char *dir, NODE *specnode, fsnode *dirnode, int speconly)
+{
+ char path[MAXPATHLEN + 1];
+ NODE *curnode;
+ fsnode *curfsnode;
+
+ assert(specnode != NULL);
+ assert(dirnode != NULL);
+
+ if (debug & DEBUG_APPLY_SPECFILE)
+ printf("apply_specdir: %s %p %p\n", dir, specnode, dirnode);
+
+ if (specnode->type != F_DIR)
+ errx(1, "Specfile node `%s/%s' is not a directory",
+ dir, specnode->name);
+ if (dirnode->type != S_IFDIR)
+ errx(1, "Directory node `%s/%s' is not a directory",
+ dir, dirnode->name);
+
+ apply_specentry(dir, specnode, dirnode);
+
+ /* Remove any filesystem nodes not found in specfile */
+ /* XXX inefficient. This is O^2 in each dir and it would
+ * have been better never to have walked this part of the tree
+ * to begin with
+ */
+ if (speconly) {
+ fsnode *next;
+ assert(dirnode->name[0] == '.' && dirnode->name[1] == '\0');
+ for (curfsnode = dirnode->next; curfsnode != NULL; curfsnode = next) {
+ next = curfsnode->next;
+ for (curnode = specnode->child; curnode != NULL;
+ curnode = curnode->next) {
+ if (strcmp(curnode->name, curfsnode->name) == 0)
+ break;
+ }
+ if (curnode == NULL) {
+ if (debug & DEBUG_APPLY_SPECONLY) {
+ printf("apply_specdir: trimming %s/%s %p\n", dir, curfsnode->name, curfsnode);
+ }
+ free_fsnodes(curfsnode);
+ }
+ }
+ }
+
+ /* now walk specnode->child matching up with dirnode */
+ for (curnode = specnode->child; curnode != NULL;
+ curnode = curnode->next) {
+ if (debug & DEBUG_APPLY_SPECENTRY)
+ printf("apply_specdir: spec %s\n",
+ curnode->name);
+ for (curfsnode = dirnode->next; curfsnode != NULL;
+ curfsnode = curfsnode->next) {
+#if 0 /* too verbose for now */
+ if (debug & DEBUG_APPLY_SPECENTRY)
+ printf("apply_specdir: dirent %s\n",
+ curfsnode->name);
+#endif
+ if (strcmp(curnode->name, curfsnode->name) == 0)
+ break;
+ }
+ if (snprintf(path, sizeof(path), "%s/%s",
+ dir, curnode->name) >= sizeof(path))
+ errx(1, "Pathname too long.");
+ if (curfsnode == NULL) { /* need new entry */
+ struct stat stbuf;
+
+ /*
+ * don't add optional spec entries
+ * that lack an existing fs entry
+ */
+ if ((curnode->flags & F_OPT) &&
+ lstat(path, &stbuf) == -1)
+ continue;
+
+ /* check that enough info is provided */
+#define NODETEST(t, m) \
+ if (!(t)) \
+ errx(1, "`%s': %s not provided", path, m)
+ NODETEST(curnode->flags & F_TYPE, "type");
+ NODETEST(curnode->flags & F_MODE, "mode");
+ /* XXX: require F_TIME ? */
+ NODETEST(curnode->flags & F_GID ||
+ curnode->flags & F_GNAME, "group");
+ NODETEST(curnode->flags & F_UID ||
+ curnode->flags & F_UNAME, "user");
+/* if (curnode->type == F_BLOCK || curnode->type == F_CHAR)
+ NODETEST(curnode->flags & F_DEV,
+ "device number");*/
+#undef NODETEST
+
+ if (debug & DEBUG_APPLY_SPECFILE)
+ printf("apply_specdir: adding %s\n",
+ curnode->name);
+ /* build minimal fsnode */
+ memset(&stbuf, 0, sizeof(stbuf));
+ stbuf.st_mode = nodetoino(curnode->type);
+ stbuf.st_nlink = 1;
+ stbuf.st_mtime = stbuf.st_atime =
+ stbuf.st_ctime = start_time.tv_sec;
+#if HAVE_STRUCT_STAT_ST_MTIMENSEC
+ stbuf.st_mtimensec = stbuf.st_atimensec =
+ stbuf.st_ctimensec = start_time.tv_nsec;
+#endif
+ curfsnode = create_fsnode(".", ".", curnode->name,
+ &stbuf);
+ curfsnode->parent = dirnode->parent;
+ curfsnode->first = dirnode;
+ curfsnode->next = dirnode->next;
+ dirnode->next = curfsnode;
+ if (curfsnode->type == S_IFDIR) {
+ /* for dirs, make "." entry as well */
+ curfsnode->child = create_fsnode(".", ".", ".",
+ &stbuf);
+ curfsnode->child->parent = curfsnode;
+ curfsnode->child->first = curfsnode->child;
+ }
+ if (curfsnode->type == S_IFLNK) {
+ assert(curnode->slink != NULL);
+ /* for symlinks, copy the target */
+ if ((curfsnode->symlink =
+ strdup(curnode->slink)) == NULL)
+ err(1, "Memory allocation error");
+ }
+ }
+ apply_specentry(dir, curnode, curfsnode);
+ if (curnode->type == F_DIR) {
+ if (curfsnode->type != S_IFDIR)
+ errx(1, "`%s' is not a directory", path);
+ assert (curfsnode->child != NULL);
+ apply_specdir(path, curnode, curfsnode->child, speconly);
+ }
+ }
+}
+
+static void
+apply_specentry(const char *dir, NODE *specnode, fsnode *dirnode)
+{
+
+ assert(specnode != NULL);
+ assert(dirnode != NULL);
+
+ if (nodetoino(specnode->type) != dirnode->type)
+ errx(1, "`%s/%s' type mismatch: specfile %s, tree %s",
+ dir, specnode->name, inode_type(nodetoino(specnode->type)),
+ inode_type(dirnode->type));
+
+ if (debug & DEBUG_APPLY_SPECENTRY)
+ printf("apply_specentry: %s/%s\n", dir, dirnode->name);
+
+#define ASEPRINT(t, b, o, n) \
+ if (debug & DEBUG_APPLY_SPECENTRY) \
+ printf("\t\t\tchanging %s from " b " to " b "\n", \
+ t, o, n)
+
+ if (specnode->flags & (F_GID | F_GNAME)) {
+ ASEPRINT("gid", "%d",
+ dirnode->inode->st.st_gid, specnode->st_gid);
+ dirnode->inode->st.st_gid = specnode->st_gid;
+ }
+ if (specnode->flags & F_MODE) {
+ ASEPRINT("mode", "%#o",
+ dirnode->inode->st.st_mode & ALLPERMS, specnode->st_mode);
+ dirnode->inode->st.st_mode &= ~ALLPERMS;
+ dirnode->inode->st.st_mode |= (specnode->st_mode & ALLPERMS);
+ }
+ /* XXX: ignoring F_NLINK for now */
+ if (specnode->flags & F_SIZE) {
+ ASEPRINT("size", "%lld",
+ (long long)dirnode->inode->st.st_size,
+ (long long)specnode->st_size);
+ dirnode->inode->st.st_size = specnode->st_size;
+ }
+ if (specnode->flags & F_SLINK) {
+ assert(dirnode->symlink != NULL);
+ assert(specnode->slink != NULL);
+ ASEPRINT("symlink", "%s", dirnode->symlink, specnode->slink);
+ free(dirnode->symlink);
+ if ((dirnode->symlink = strdup(specnode->slink)) == NULL)
+ err(1, "Memory allocation error");
+ }
+ if (specnode->flags & F_TIME) {
+ ASEPRINT("time", "%ld",
+ (long)dirnode->inode->st.st_mtime,
+ (long)specnode->st_mtimespec.tv_sec);
+ dirnode->inode->st.st_mtime = specnode->st_mtimespec.tv_sec;
+ dirnode->inode->st.st_atime = specnode->st_mtimespec.tv_sec;
+ dirnode->inode->st.st_ctime = start_time.tv_sec;
+#if HAVE_STRUCT_STAT_ST_MTIMENSEC
+ dirnode->inode->st.st_mtimensec = specnode->st_mtimespec.tv_nsec;
+ dirnode->inode->st.st_atimensec = specnode->st_mtimespec.tv_nsec;
+ dirnode->inode->st.st_ctimensec = start_time.tv_nsec;
+#endif
+ }
+ if (specnode->flags & (F_UID | F_UNAME)) {
+ ASEPRINT("uid", "%d",
+ dirnode->inode->st.st_uid, specnode->st_uid);
+ dirnode->inode->st.st_uid = specnode->st_uid;
+ }
+#if HAVE_STRUCT_STAT_ST_FLAGS
+ if (specnode->flags & F_FLAGS) {
+ ASEPRINT("flags", "%#lX",
+ (unsigned long)dirnode->inode->st.st_flags,
+ (unsigned long)specnode->st_flags);
+ dirnode->inode->st.st_flags = specnode->st_flags;
+ }
+#endif
+/* if (specnode->flags & F_DEV) {
+ ASEPRINT("rdev", "%#llx",
+ (unsigned long long)dirnode->inode->st.st_rdev,
+ (unsigned long long)specnode->st_rdev);
+ dirnode->inode->st.st_rdev = specnode->st_rdev;
+ }*/
+#undef ASEPRINT
+
+ dirnode->flags |= FSNODE_F_HASSPEC;
+}
+
+
+/*
+ * dump_fsnodes --
+ * dump the fsnodes from `cur'
+ */
+void
+dump_fsnodes(fsnode *root)
+{
+ fsnode *cur;
+ char path[MAXPATHLEN + 1];
+
+ printf("dump_fsnodes: %s %p\n", root->path, root);
+ for (cur = root; cur != NULL; cur = cur->next) {
+ if (snprintf(path, sizeof(path), "%s/%s", cur->path,
+ cur->name) >= (int)sizeof(path))
+ errx(1, "Pathname too long.");
+
+ if (debug & DEBUG_DUMP_FSNODES_VERBOSE)
+ printf("cur=%8p parent=%8p first=%8p ",
+ cur, cur->parent, cur->first);
+ printf("%7s: %s", inode_type(cur->type), path);
+ if (S_ISLNK(cur->type)) {
+ assert(cur->symlink != NULL);
+ printf(" -> %s", cur->symlink);
+ } else {
+ assert (cur->symlink == NULL);
+ }
+ if (cur->inode->nlink > 1)
+ printf(", nlinks=%d", cur->inode->nlink);
+ putchar('\n');
+
+ if (cur->child) {
+ assert (cur->type == S_IFDIR);
+ dump_fsnodes(cur->child);
+ }
+ }
+ printf("dump_fsnodes: finished %s/%s\n", root->path, root->name);
+}
+
+
+/*
+ * inode_type --
+ * for a given inode type `mode', return a descriptive string.
+ * for most cases, uses inotype() from mtree/misc.c
+ */
+const char *
+inode_type(mode_t mode)
+{
+ if (S_ISREG(mode))
+ return ("file");
+ if (S_ISLNK(mode))
+ return ("symlink");
+ if (S_ISDIR(mode))
+ return ("dir");
+ if (S_ISLNK(mode))
+ return ("link");
+ if (S_ISFIFO(mode))
+ return ("fifo");
+ if (S_ISSOCK(mode))
+ return ("socket");
+ /* XXX should not happen but handle them */
+ if (S_ISCHR(mode))
+ return ("char");
+ if (S_ISBLK(mode))
+ return ("block");
+ return ("unknown");
+}
+
+
+/*
+ * link_check --
+ * return pointer to fsinode matching `entry's st_ino & st_dev if it exists,
+ * otherwise add `entry' to table and return NULL
+ */
+/* This was borrowed from du.c and tweaked to keep an fsnode
+ * pointer instead. -- dbj@netbsd.org
+ */
+fsinode *
+link_check(fsinode *entry)
+{
+ static struct entry {
+ fsinode *data;
+ } *htable;
+ static int htshift; /* log(allocated size) */
+ static int htmask; /* allocated size - 1 */
+ static int htused; /* 2*number of insertions */
+ int h, h2;
+ uint64_t tmp;
+ /* this constant is (1<<64)/((1+sqrt(5))/2)
+ * aka (word size)/(golden ratio)
+ */
+ const uint64_t HTCONST = 11400714819323198485ULL;
+ const int HTBITS = 64;
+
+ /* Never store zero in hashtable */
+ assert(entry);
+
+ /* Extend hash table if necessary, keep load under 0.5 */
+ if (htused<<1 >= htmask) {
+ struct entry *ohtable;
+
+ if (!htable)
+ htshift = 10; /* starting hashtable size */
+ else
+ htshift++; /* exponential hashtable growth */
+
+ htmask = (1 << htshift) - 1;
+ htused = 0;
+
+ ohtable = htable;
+ htable = calloc(htmask+1, sizeof(*htable));
+ if (!htable)
+ err(1, "Memory allocation error");
+
+ /* populate newly allocated hashtable */
+ if (ohtable) {
+ int i;
+ for (i = 0; i <= htmask>>1; i++)
+ if (ohtable[i].data)
+ link_check(ohtable[i].data);
+ free(ohtable);
+ }
+ }
+
+ /* multiplicative hashing */
+ tmp = entry->st.st_dev;
+ tmp <<= HTBITS>>1;
+ tmp |= entry->st.st_ino;
+ tmp *= HTCONST;
+ h = tmp >> (HTBITS - htshift);
+ h2 = 1 | ( tmp >> (HTBITS - (htshift<<1) - 1)); /* must be odd */
+
+ /* open address hashtable search with double hash probing */
+ while (htable[h].data) {
+ if ((htable[h].data->st.st_ino == entry->st.st_ino) &&
+ (htable[h].data->st.st_dev == entry->st.st_dev)) {
+ return htable[h].data;
+ }
+ h = (h + h2) & htmask;
+ }
+
+ /* Insert the current entry into hashtable */
+ htable[h].data = entry;
+ htused++;
+ return NULL;
+}
diff --git a/usr.sbin/makemap/Makefile b/usr.sbin/makemap/Makefile
new file mode 100644
index 0000000..af5f742
--- /dev/null
+++ b/usr.sbin/makemap/Makefile
@@ -0,0 +1,30 @@
+# @(#)Makefile 8.4 (Berkeley) 6/10/97
+# $FreeBSD$
+
+SENDMAIL_DIR=${.CURDIR}/../../contrib/sendmail
+.PATH: ${SENDMAIL_DIR}/makemap
+
+PROG= makemap
+SRCS= makemap.c
+MAN= makemap.8
+
+CFLAGS+= -I${SENDMAIL_DIR}/src -I${SENDMAIL_DIR}/include -I.
+CFLAGS+= -DNEWDB -DNOT_SENDMAIL
+
+WARNS?= 2
+
+LIBADD= sm smdb smutil
+
+SRCS+= sm_os.h
+CLEANFILES+=sm_os.h
+
+# User customizations to the sendmail build environment
+CFLAGS+=${SENDMAIL_CFLAGS}
+DPADD+=${SENDMAIL_DPADD}
+LDADD+=${SENDMAIL_LDADD}
+LDFLAGS+=${SENDMAIL_LDFLAGS}
+
+sm_os.h: ${SENDMAIL_DIR}/include/sm/os/sm_os_freebsd.h .NOMETA
+ ln -sf ${.ALLSRC} ${.TARGET}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/makemap/Makefile.depend b/usr.sbin/makemap/Makefile.depend
new file mode 100644
index 0000000..3e7ef5e
--- /dev/null
+++ b/usr.sbin/makemap/Makefile.depend
@@ -0,0 +1,23 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libsm \
+ lib/libsmdb \
+ lib/libsmutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+makemap.o: sm_os.h
+makemap.po: sm_os.h
+.endif
diff --git a/usr.sbin/manctl/Makefile b/usr.sbin/manctl/Makefile
new file mode 100644
index 0000000..9e0c12e
--- /dev/null
+++ b/usr.sbin/manctl/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+SCRIPTS=manctl.sh
+MAN= manctl.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/manctl/Makefile.depend b/usr.sbin/manctl/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/manctl/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/manctl/manctl.8 b/usr.sbin/manctl/manctl.8
new file mode 100644
index 0000000..b8e001d
--- /dev/null
+++ b/usr.sbin/manctl/manctl.8
@@ -0,0 +1,58 @@
+.\" Copyright (c) 1996 Wolfram Schneider <wosch@FreeBSD.org>. Berlin.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 1, 1996
+.Dt MANCTL 8
+.Os
+.Sh NAME
+.Nm manctl
+.Nd manipulating manual pages
+.Sh SYNOPSIS
+.Nm
+.Op Fl compress
+.Op Fl uncompress
+.Op Fl purge
+.Op Fl help
+.Ar path ...
+.Sh DESCRIPTION
+The
+.Nm
+utility compress or uncompress manual pages in directory path.
+If possible, .so's are replaced with hard links.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl help
+Print options and exit.
+.It Fl compress
+Compress uncompressed man pages (eliminating .so's).
+.It Fl uncompress
+Uncompress compressed man pages.
+.It Fl purge
+Purge old formatted man pages (not implemented yet).
+.El
+.Sh SEE ALSO
+.Xr catman 1 ,
+.Xr man 1
diff --git a/usr.sbin/manctl/manctl.sh b/usr.sbin/manctl/manctl.sh
new file mode 100644
index 0000000..23e2087
--- /dev/null
+++ b/usr.sbin/manctl/manctl.sh
@@ -0,0 +1,380 @@
+#!/bin/sh
+#
+# Copyright (c) 1994 Geoffrey M. Rehmet, Rhodes University
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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 Geoffrey M. Rehmet
+# 4. Neither the name of Geoffrey M. Rehmet nor that of Rhodes University
+# 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 GEOFFREY M. REHMET OR RHODES UNIVERSITY BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (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$
+#
+# manctl:
+# a utility for manipulating manual pages
+# functions:
+# compress uncompressed man pages (elliminating .so's)
+# this is now two-pass. If possible, .so's
+# are replaced with hard links
+# uncompress compressed man pages
+# purge old formatted man pages (not implemented yet)
+# Things to watch out for:
+# Hard links - careful with g(un)zipping!
+# .so's - throw everything through soelim before gzip!
+# symlinks - ignore these - eg: expn is its own man page:
+# don't want to compress this!
+#
+PATH=/bin:/sbin:/usr/bin:/usr/sbin; export PATH
+
+#
+# purge cat? directories
+#
+do_purge()
+{
+ echo "purge $@" 2>&1
+ echo "not implemented yet\n" 2>&1
+}
+
+
+#
+# Uncompress one page
+#
+uncompress_page()
+{
+ local pname
+ local fname
+ local sect
+ local ext
+
+ # break up file name
+ pname=$1
+ IFS='.' ; set $pname
+ # less than 3 fields - don't know what to do with this
+ if [ $# -lt 3 ] ; then
+ IFS=" " ; echo ignoring $pname 1>&2 ; return 0 ;
+ fi
+ # construct name and section
+ fname=$1 ; shift
+ while [ $# -gt 2 ] ; do
+ fname=$fname.$1
+ shift
+ done
+ sect=$1
+ ext=$2
+
+ IFS=" "
+ case "$ext" in
+ gz|Z) {
+ IFS=" " ; set `file $pname`
+ if [ $2 != "gzip" ] ; then
+ echo moving hard link $pname 1>&2
+ mv $pname $fname.$ext # link
+ else
+ if [ $2 != "symbolic" ] ; then
+ echo gunzipping page $pname 1>&2
+ temp=`mktemp -t manager` || exit 1
+ gunzip -c $pname > $temp
+ chmod u+w $pname
+ cp $temp $pname
+ chmod 444 $pname
+ mv $pname $fname.$sect
+ rm -f $temp
+ else
+ # skip symlinks - this can be
+ # a program like expn, which is
+ # its own man page !
+ echo skipping symlink $pname 1>&2
+ fi
+ fi };;
+ *) {
+ IFS=" "
+ echo skipping file $pname 1>&2
+ } ;;
+ esac
+ # reset IFS - this is important!
+ IFS=" "
+}
+
+
+#
+# Uncompress manpages in paths
+#
+do_uncompress()
+{
+ local i
+ local dir
+ local workdir
+
+ workdir=`pwd`
+ while [ $# != 0 ] ; do
+ if [ -d $1 ] ; then
+ dir=$1
+ cd $dir
+ for i in * ; do
+ case $i in
+ *cat?) ;; # ignore cat directories
+ *) {
+ if [ -d $i ] ; then
+ do_uncompress $i
+ else
+ if [ -e $i ] ; then
+ uncompress_page $i
+ fi
+ fi } ;;
+ esac
+ done
+ cd $workdir
+ else
+ echo "directory $1 not found" 1>&2
+ fi
+ shift
+ done
+}
+
+#
+# Remove .so's from one file
+#
+so_purge_page()
+{
+ local so_entries
+ local lines
+ local fname
+
+ so_entries=`grep "^\.so" $1 | wc -l`
+ if [ $so_entries -eq 0 ] ; then return 0 ; fi
+
+ # we have a page with a .so in it
+ echo $1 contains a .so entry 2>&1
+
+ # now check how many lines in the file
+ lines=`wc -l < $1`
+
+ # if the file is only one line long, we can replace it
+ # with a hard link!
+ if [ $lines -eq 1 ] ; then
+ fname=$1;
+ echo replacing $fname with a hard link
+ set `cat $fname`;
+ rm -f $fname
+ ln ../$2 $fname
+ else
+ echo inlining page $fname 1>&2
+ temp=`mktemp -t manager` || exit 1
+ cat $fname | \
+ (cd .. ; soelim ) > $temp
+ chmod u+w $fname
+ cp $temp $fname
+ chmod 444 $fname
+ rm -f $temp
+ fi
+}
+
+#
+# Remove .so entries from man pages
+# If a page consists of just one line with a .so,
+# replace it with a hard link
+#
+remove_so()
+{
+ local pname
+ local fname
+ local sect
+
+ # break up file name
+ pname=$1
+ IFS='.' ; set $pname
+ if [ $# -lt 2 ] ; then
+ IFS=" " ; echo ignoring $pname 1>&2 ; return 0 ;
+ fi
+ # construct name and section
+ fname=$1 ; shift
+ while [ $# -gt 1 ] ; do
+ fname=$fname.$1
+ shift
+ done
+ sect=$1
+
+ IFS=" "
+ case "$sect" in
+ gz) { echo file $pname already gzipped 1>&2 ; } ;;
+ Z) { echo file $pname already compressed 1>&2 ; } ;;
+ [12345678ln]*){
+ IFS=" " ; set `file $pname`
+ if [ $2 = "gzip" ] ; then
+ echo moving hard link $pname 1>&2
+ mv $pname $pname.gz # link
+ else
+ if [ $2 != "symbolic" ] ; then
+ echo "removing .so's in page $pname" 1>&2
+ so_purge_page $pname
+ else
+ # skip symlink - this can be
+ # a program like expn, which is
+ # its own man page !
+ echo skipping symlink $pname 1>&2
+ fi
+ fi };;
+ *) {
+ IFS=" "
+ echo skipping file $pname 1>&2
+ } ;;
+ esac
+ # reset IFS - this is important!
+ IFS=" "
+}
+
+
+#
+# compress one page
+# We need to watch out for hard links here.
+#
+compress_page()
+{
+ local pname
+ local fname
+ local sect
+
+ # break up file name
+ pname=$1
+ IFS='.' ; set $pname
+ if [ $# -lt 2 ] ; then
+ IFS=" " ; echo ignoring $pname 1>&2 ; return 0 ;
+ fi
+ # construct name and section
+ fname=$1 ; shift
+ while [ $# -gt 1 ] ; do
+ fname=$fname.$1
+ shift
+ done
+ sect=$1
+
+ IFS=" "
+ case "$sect" in
+ gz) { echo file $pname already gzipped 1>&2 ; } ;;
+ Z) { echo file $pname already compressed 1>&2 ; } ;;
+ [12345678ln]*){
+ IFS=" " ; set `file $pname`
+ if [ $2 = "gzip" ] ; then
+ echo moving hard link $pname 1>&2
+ mv $pname $pname.gz # link
+ else
+ if [ $2 != "symbolic" ] ; then
+ echo gzipping page $pname 1>&2
+ temp=`mktemp -t manager` || exit 1
+ cat $pname | \
+ (cd .. ; soelim )| gzip -c -- > $temp
+ chmod u+w $pname
+ cp $temp $pname
+ chmod 444 $pname
+ mv $pname $pname.gz
+ rm -f $temp
+ else
+ # skip symlink - this can be
+ # a program like expn, which is
+ # its own man page !
+ echo skipping symlink $pname 1>&2
+ fi
+ fi };;
+ *) {
+ IFS=" "
+ echo skipping file $pname 1>&2
+ } ;;
+ esac
+ # reset IFS - this is important!
+ IFS=" "
+}
+
+#
+# Compress man pages in paths
+#
+do_compress_so()
+{
+ local i
+ local dir
+ local workdir
+ local what
+
+ what=$1
+ shift
+ workdir=`pwd`
+ while [ $# != 0 ] ; do
+ if [ -d $1 ] ; then
+ dir=$1
+ cd $dir
+ for i in * ; do
+ case $i in
+ *cat?) ;; # ignore cat directories
+ *) {
+ if [ -d $i ] ; then
+ do_compress_so $what $i
+ else
+ if [ -e $i ] ; then
+ $what $i
+ fi
+ fi } ;;
+ esac
+ done
+ cd $workdir
+ else
+ echo "directory $1 not found" 1>&2
+ fi
+ shift
+ done
+}
+
+#
+# Display a usage message
+#
+ctl_usage()
+{
+ echo "usage: $1 -compress <path> ... " 1>&2
+ echo " $1 -uncompress <path> ... " 1>&2
+ echo " $1 -purge <days> <path> ... " 1>&2
+ echo " $1 -purge expire <path> ... " 1>&2
+ exit 1
+}
+
+#
+# remove .so's and do compress
+#
+do_compress()
+{
+ # First remove all so's from the pages to be compressed
+ do_compress_so remove_so "$@"
+ # now do ahead and compress the pages
+ do_compress_so compress_page "$@"
+}
+
+#
+# dispatch options
+#
+if [ $# -lt 2 ] ; then ctl_usage $0 ; fi ;
+
+case "$1" in
+ -compress) shift ; do_compress "$@" ;;
+ -uncompress) shift ; do_uncompress "$@" ;;
+ -purge) shift ; do_purge "$@" ;;
+ *) ctl_usage $0 ;;
+esac
diff --git a/usr.sbin/memcontrol/Makefile b/usr.sbin/memcontrol/Makefile
new file mode 100644
index 0000000..d465d53
--- /dev/null
+++ b/usr.sbin/memcontrol/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= memcontrol
+MAN= memcontrol.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/memcontrol/Makefile.depend b/usr.sbin/memcontrol/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/memcontrol/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/memcontrol/memcontrol.8 b/usr.sbin/memcontrol/memcontrol.8
new file mode 100644
index 0000000..ad0e533
--- /dev/null
+++ b/usr.sbin/memcontrol/memcontrol.8
@@ -0,0 +1,111 @@
+.\" Copyright (c) 1999 Chris Costello
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 15, 2002
+.Dt MEMCONTROL 8
+.Os
+.Sh NAME
+.Nm memcontrol
+.Nd "control system cache behaviour with respect to memory"
+.Sh SYNOPSIS
+.Nm
+.Cm list
+.Op Fl a
+.Nm
+.Cm set
+.Fl b Ar base
+.Fl l Ar length
+.Fl o Ar owner
+.Ar attribute
+.Nm
+.Cm clear
+.Fl o Ar owner
+.Nm
+.Cm clear
+.Fl b Ar base
+.Fl l Ar length
+.Sh DESCRIPTION
+A number of supported system architectures allow the behaviour of the CPU
+cache to be programmed to behave differently depending on the region being
+written.
+.Pp
+The
+.Nm
+utility
+provides an interface to this facility, allowing CPU cache behavior to
+be altered for ranges of system physical memory.
+.Pp
+These ranges are typically power-of-2 aligned and sized, however the specific
+rules governing their layout vary between architectures.
+The
+.Nm
+utility does not attempt to enforce these rules, however the system will
+reject any attempt to set an illegal combination.
+.Bl -tag -width ".Cm clear"
+.It Cm list
+List range slots.
+.Bl -tag -width indent
+.It Fl a
+List all range slots, even those that are inactive.
+.El
+.It Cm set
+Set memory range attributes.
+.Bl -tag -width indent
+.It Fl b Ar base
+Memory range base address.
+.It Fl l Ar length
+Length of memory range in bytes, power of 2.
+.It Fl o Ar owner
+Text identifier for this setting (7 char max).
+.It Ar attribute
+Attributes applied to this range; combinations of
+.Cm force , uncacheable , write-combine , write-through , write-back ,
+and
+.Cm write-protect .
+.El
+.It Cm clear
+Clear memory range attributes.
+Ranges may be cleared by owner or by
+base/length combination.
+.Pp
+To clear based on ownership:
+.Bl -tag -width indent
+.It Fl o Ar owner
+All ranges with this owner will be cleared.
+.El
+.Pp
+To clear based on the base/length combination:
+.Bl -tag -width indent
+.It Fl b Ar base
+Memory range base address.
+.It Fl l Ar length
+Length of memory range in bytes, power of 2.
+.El
+.Pp
+Base and length must exactly match an existing range.
+.El
+.Sh SEE ALSO
+.Xr mem 4
diff --git a/usr.sbin/memcontrol/memcontrol.c b/usr.sbin/memcontrol/memcontrol.c
new file mode 100644
index 0000000..36329af
--- /dev/null
+++ b/usr.sbin/memcontrol/memcontrol.c
@@ -0,0 +1,342 @@
+/*-
+ * Copyright (c) 1999 Michael Smith <msmith@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/types.h>
+#include <sys/ioctl.h>
+#include <sys/memrange.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static struct {
+ const char *name;
+ int val;
+ int kind;
+#define MDF_SETTABLE (1<<0)
+} attrnames[] = {
+ {"uncacheable", MDF_UNCACHEABLE, MDF_SETTABLE},
+ {"write-combine", MDF_WRITECOMBINE, MDF_SETTABLE},
+ {"write-through", MDF_WRITETHROUGH, MDF_SETTABLE},
+ {"write-back", MDF_WRITEBACK, MDF_SETTABLE},
+ {"write-protect", MDF_WRITEPROTECT, MDF_SETTABLE},
+ {"force", MDF_FORCE, MDF_SETTABLE},
+ {"unknown", MDF_UNKNOWN, 0},
+ {"fixed-base", MDF_FIXBASE, 0},
+ {"fixed-length", MDF_FIXLEN, 0},
+ {"set-by-firmware", MDF_FIRMWARE, 0},
+ {"active", MDF_ACTIVE, MDF_SETTABLE},
+ {"bogus", MDF_BOGUS, 0},
+ {NULL, 0, 0}
+};
+
+static void listfunc(int memfd, int argc, char *argv[]);
+static void setfunc(int memfd, int argc, char *argv[]);
+static void clearfunc(int memfd, int argc, char *argv[]);
+static void helpfunc(int memfd, int argc, char *argv[]);
+static void help(const char *what);
+
+static struct {
+ const char *cmd;
+ const char *desc;
+ void (*func)(int memfd, int argc, char *argv[]);
+} functions[] = {
+ {"list",
+ "List current memory range attributes\n"
+ " list [-a]\n"
+ " -a list all range slots, even those that are inactive",
+ listfunc},
+ {"set",
+ "Set memory range attributes\n"
+ " set -b <base> -l <length> -o <owner> <attribute>\n"
+ " <base> memory range base address\n"
+ " <length> length of memory range in bytes, power of 2\n"
+ " <owner> text identifier for this setting (7 char max)\n"
+ " <attribute> attribute(s) to be applied to this range:\n"
+ " uncacheable\n"
+ " write-combine\n"
+ " write-through\n"
+ " write-back\n"
+ " write-protect",
+ setfunc},
+ {"clear",
+ "Clear memory range attributes\n"
+ " clear -o <owner>\n"
+ " <owner> all ranges with this owner will be cleared\n"
+ " clear -b <base> -l <length>\n"
+ " <base> memory range base address\n"
+ " <length> length of memory range in bytes, power of 2\n"
+ " Base and length must exactly match an existing range",
+ clearfunc},
+ {NULL, NULL, helpfunc}
+};
+
+int
+main(int argc, char *argv[])
+{
+ int i, memfd;
+
+ if (argc < 2) {
+ help(NULL);
+ } else {
+ if ((memfd = open(_PATH_MEM, O_RDONLY)) == -1)
+ err(1, "can't open %s", _PATH_MEM);
+
+ for (i = 0; functions[i].cmd != NULL; i++)
+ if (!strcmp(argv[1], functions[i].cmd))
+ break;
+ functions[i].func(memfd, argc - 1, argv + 1);
+ close(memfd);
+ }
+ return(0);
+}
+
+static struct mem_range_desc *
+mrgetall(int memfd, int *nmr)
+{
+ struct mem_range_desc *mrd;
+ struct mem_range_op mro;
+
+ mro.mo_arg[0] = 0;
+ if (ioctl(memfd, MEMRANGE_GET, &mro))
+ err(1, "can't size range descriptor array");
+
+ *nmr = mro.mo_arg[0];
+ mrd = malloc(*nmr * sizeof(struct mem_range_desc));
+ if (mrd == NULL)
+ errx(1, "can't allocate %zd bytes for %d range descriptors",
+ *nmr * sizeof(struct mem_range_desc), *nmr);
+
+ mro.mo_arg[0] = *nmr;
+ mro.mo_desc = mrd;
+ if (ioctl(memfd, MEMRANGE_GET, &mro))
+ err(1, "can't fetch range descriptor array");
+
+ return(mrd);
+}
+
+
+static void
+listfunc(int memfd, int argc, char *argv[])
+{
+ struct mem_range_desc *mrd;
+ int nd, i, j;
+ int ch;
+ int showall = 0;
+ char *owner;
+
+ owner = NULL;
+ while ((ch = getopt(argc, argv, "ao:")) != -1)
+ switch(ch) {
+ case 'a':
+ showall = 1;
+ break;
+ case 'o':
+ owner = strdup(optarg);
+ break;
+ case '?':
+ default:
+ help("list");
+ }
+
+ mrd = mrgetall(memfd, &nd);
+
+ for (i = 0; i < nd; i++) {
+ if (!showall && !(mrd[i].mr_flags & MDF_ACTIVE))
+ continue;
+ if (owner && strcmp(mrd[i].mr_owner, owner))
+ continue;
+ printf("0x%" PRIx64 "/0x%" PRIx64 " %.8s ", mrd[i].mr_base, mrd[i].mr_len,
+ mrd[i].mr_owner[0] ? mrd[i].mr_owner : "-");
+ for (j = 0; attrnames[j].name != NULL; j++)
+ if (mrd[i].mr_flags & attrnames[j].val)
+ printf("%s ", attrnames[j].name);
+ printf("\n");
+ }
+ free(mrd);
+ if (owner)
+ free(owner);
+}
+
+static void
+setfunc(int memfd, int argc, char *argv[])
+{
+ struct mem_range_desc mrd;
+ struct mem_range_op mro;
+ int i;
+ int ch;
+ char *ep;
+
+ mrd.mr_base = 0;
+ mrd.mr_len = 0;
+ mrd.mr_flags = 0;
+ strcpy(mrd.mr_owner, "user");
+ while ((ch = getopt(argc, argv, "b:l:o:")) != -1)
+ switch(ch) {
+ case 'b':
+ mrd.mr_base = strtouq(optarg, &ep, 0);
+ if ((ep == optarg) || (*ep != 0))
+ help("set");
+ break;
+ case 'l':
+ mrd.mr_len = strtouq(optarg, &ep, 0);
+ if ((ep == optarg) || (*ep != 0))
+ help("set");
+ break;
+ case 'o':
+ if ((*optarg == 0) || (strlen(optarg) > 7))
+ help("set");
+ strcpy(mrd.mr_owner, optarg);
+ break;
+
+ case '?':
+ default:
+ help("set");
+ }
+
+ if (mrd.mr_len == 0)
+ help("set");
+
+ argc -= optind;
+ argv += optind;
+
+ while(argc--) {
+ for (i = 0; attrnames[i].name != NULL; i++) {
+ if (!strcmp(attrnames[i].name, argv[0])) {
+ if (!(attrnames[i].kind & MDF_SETTABLE))
+ help("flags");
+ mrd.mr_flags |= attrnames[i].val;
+ break;
+ }
+ }
+ if (attrnames[i].name == NULL)
+ help("flags");
+ argv++;
+ }
+
+ mro.mo_desc = &mrd;
+ mro.mo_arg[0] = 0;
+ if (ioctl(memfd, MEMRANGE_SET, &mro))
+ err(1, "can't set range");
+}
+
+static void
+clearfunc(int memfd, int argc, char *argv[])
+{
+ struct mem_range_desc mrd, *mrdp;
+ struct mem_range_op mro;
+ int i, nd;
+ int ch;
+ char *ep, *owner;
+
+ mrd.mr_base = 0;
+ mrd.mr_len = 0;
+ owner = NULL;
+ while ((ch = getopt(argc, argv, "b:l:o:")) != -1)
+ switch(ch) {
+ case 'b':
+ mrd.mr_base = strtouq(optarg, &ep, 0);
+ if ((ep == optarg) || (*ep != 0))
+ help("clear");
+ break;
+ case 'l':
+ mrd.mr_len = strtouq(optarg, &ep, 0);
+ if ((ep == optarg) || (*ep != 0))
+ help("clear");
+ break;
+ case 'o':
+ if ((*optarg == 0) || (strlen(optarg) > 7))
+ help("clear");
+ owner = strdup(optarg);
+ break;
+
+ case '?':
+ default:
+ help("clear");
+ }
+
+ if (owner != NULL) {
+ /* clear-by-owner */
+ if ((mrd.mr_base != 0) || (mrd.mr_len != 0))
+ help("clear");
+
+ mrdp = mrgetall(memfd, &nd);
+ mro.mo_arg[0] = MEMRANGE_SET_REMOVE;
+ for (i = 0; i < nd; i++) {
+ if (!strcmp(owner, mrdp[i].mr_owner) &&
+ (mrdp[i].mr_flags & MDF_ACTIVE) &&
+ !(mrdp[i].mr_flags & MDF_FIXACTIVE)) {
+
+ mro.mo_desc = mrdp + i;
+ if (ioctl(memfd, MEMRANGE_SET, &mro))
+ warn("couldn't clear range owned by '%s'", owner);
+ }
+ }
+ } else if (mrd.mr_len != 0) {
+ /* clear-by-base/len */
+ mro.mo_arg[0] = MEMRANGE_SET_REMOVE;
+ mro.mo_desc = &mrd;
+ if (ioctl(memfd, MEMRANGE_SET, &mro))
+ err(1, "couldn't clear range");
+ } else {
+ help("clear");
+ }
+}
+
+static void
+helpfunc(__unused int memfd, __unused int argc, char *argv[])
+{
+ help(argv[1]);
+}
+
+static void
+help(const char *what)
+{
+ int i;
+
+ if (what != NULL) {
+ /* find a function that matches */
+ for (i = 0; functions[i].cmd != NULL; i++)
+ if (!strcmp(what, functions[i].cmd)) {
+ fprintf(stderr, "%s\n", functions[i].desc);
+ return;
+ }
+ fprintf(stderr, "Unknown command '%s'\n", what);
+ }
+
+ /* print general help */
+ fprintf(stderr, "Valid commands are :\n");
+ for (i = 0; functions[i].cmd != NULL; i++)
+ fprintf(stderr, " %s\n", functions[i].cmd);
+ fprintf(stderr, "Use help <command> for command-specific help\n");
+}
diff --git a/usr.sbin/mergemaster/Makefile b/usr.sbin/mergemaster/Makefile
new file mode 100644
index 0000000..257fbeb
--- /dev/null
+++ b/usr.sbin/mergemaster/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+SCRIPTS=mergemaster.sh
+MAN= mergemaster.8
+
+.include <bsd.prog.mk>
+
diff --git a/usr.sbin/mergemaster/Makefile.depend b/usr.sbin/mergemaster/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/mergemaster/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/mergemaster/mergemaster.8 b/usr.sbin/mergemaster/mergemaster.8
new file mode 100644
index 0000000..d88bdd0
--- /dev/null
+++ b/usr.sbin/mergemaster/mergemaster.8
@@ -0,0 +1,475 @@
+.\" Copyright (c) 1998-2011 Douglas Barton
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 29, 2015
+.Dt MERGEMASTER 8
+.Os
+.Sh NAME
+.Nm mergemaster
+.Nd merge configuration files, et al during an upgrade
+.Sh SYNOPSIS
+.Nm
+.Op Fl scrvhpCP
+.Op Fl a|iFU
+.Op Fl -run-updates=[always|never]
+.Op Fl m Ar /path/to/sources
+.Op Fl t Ar /path/to/temp/root
+.Op Fl d
+.Op Fl u Ar N
+.Op Fl w Ar N
+.Op Fl A Ar Target architecture
+.Op Fl D Ar /destdir/path
+.Sh DESCRIPTION
+The
+.Nm
+utility is a Bourne shell script which is designed to aid you
+in updating the various configuration and other files
+associated with
+.Fx .
+It is
+.Sy HIGHLY
+recommended that you back up your
+.Pa /etc
+directory before beginning this process.
+.Pp
+The script uses
+.Pa /usr/src/Makefile
+to build a temporary root environment from
+.Pa /
+down, populating that environment with the various
+files.
+You can specify a different source directory
+with the
+.Fl m
+command line option, or specify the destination
+directory with the
+.Fl D
+option.
+It then compares each file in that environment
+to its installed counterpart.
+When the script finds a
+change in the new file, or there is no installed
+version of the new file it gives you four options to
+deal with it.
+You can install the new file as is,
+delete the new file, merge the old and new
+files (as appropriate) using
+.Xr sdiff 1
+or leave the file in the temporary root environment to
+merge by hand later.
+.Pp
+By default it creates the temporary root in
+.Pa /var/tmp/temproot
+and compares the
+Version Control System (VCS) Id strings ($FreeBSD)
+for files that have them, deleting
+the temporary file if the strings match.
+If there is
+no Id string, or if the strings are different it
+compares the files themselves.
+You can
+also specify that the script ignore the Id strings and
+compare every file with the
+.Fl s
+option.
+Using the
+.Fl F
+option
+.Nm
+will install the new file for you if they differ only by
+VCS strings.
+.Pp
+The merge menu option is designed to let you easily combine your
+customizations from the old version of a file into the new one.
+While you can use the merge function to incorporate changes from
+files that you have not customized,
+it is not recommended.
+.Pp
+The
+.Nm
+utility checks your umask and issues a warning for anything
+other than 022.
+While it is not mandatory to grant
+world read permissions for most configuration files, you
+may run into problems without them.
+If you choose a
+umask other than 022 and experience trouble later this
+could be the cause.
+.Pa /etc/master.passwd
+is treated as a special case.
+If you choose to install
+this file or a merged version of it the file permissions
+are always 600 (rw-------) for security reasons.
+After
+installing an updated version of this file you should
+probably run
+.Xr pwd_mkdb 8
+with the
+.Fl p
+option to rebuild your password databases
+and recreate
+.Pa /etc/passwd .
+.Pp
+The script uses the owner and group ids
+that the files are created with by
+.Pa /usr/src/etc/Makefile ,
+and file permissions as specified by the umask.
+Unified diffs are used by default to display any
+differences unless you choose context diffs.
+.Pp
+The
+.Nm
+utility will source scripts that you specify right before
+it starts the comparison, and after it is done running.
+The easiest way to handle this is to place the path
+to the script(s) in the appropriate variables in your
+.Pa .mergemasterrc
+file.
+The script sourced before comparison is named in
+.Ev MM_PRE_COMPARE_SCRIPT ,
+and the one sourced after the script is done is
+.Ev MM_EXIT_SCRIPT .
+This is the recommended way to specify local modifications,
+or files that you want to give special handling to.
+This includes files that you want to be deleted without
+being compared.
+Because the named scripts are sourced from within
+.Nm ,
+all of the script's variables are available for use in
+your custom script.
+You can also use
+.Pa /etc/mergemaster.rc
+which will be read before
+.Pa .mergemasterrc .
+Options specified on the command line are updated last,
+and therefore can override both files.
+.Pp
+When the comparison is done if there are any files remaining
+in the temproot directory they will be listed, and if the
+.Fl a
+option is not in use the user will be given the option of
+deleting the temproot directory.
+If there are no files remaining in the temproot directory
+it will be deleted.
+.Pp
+The options are as follows:
+.Bl -tag -width Fl
+.It Fl s
+Perform a strict comparison, diffing every pair of files.
+This comparison is performed line by line,
+without regard to VCS Ids.
+.It Fl c
+Use context diffs instead of unified diffs.
+.It Fl r
+Re-run
+.Nm
+on a previously cleaned directory, skipping the creation of
+the temporary root environment.
+This option is compatible
+with all other options.
+.It Fl v
+Be more verbose about the process.
+You should probably use
+this option the first time you run
+.Nm .
+This option also gives you a list of files that exist
+only in the installed version of
+.Pa /etc .
+.It Fl a
+Run automatically.
+This option will leave all the files that
+differ from the installed versions in the temporary directory
+to be dealt with by hand.
+If the
+.Pa temproot
+directory exists, it creates a new one in a previously
+non-existent directory.
+This option unsets the verbose flag,
+and is not compatible with
+.Fl i ,
+.Fl F ,
+or
+.Fl U .
+Setting
+.Fl a
+makes
+.Fl w
+superfluous.
+.It Fl h
+Display usage and help information.
+.It Fl i
+Automatically install any files that do not exist in the
+destination directory.
+.It Fl p
+Pre-buildworld mode.
+Compares only files known to be essential to the success of
+{build|install}world,
+including
+.Pa /etc/make.conf .
+.It Fl F
+If the files differ only by VCS Id ($FreeBSD)
+install the new file.
+.It Fl C
+After a standard
+.Nm
+run,
+compares your rc.conf[.local] options to the defaults.
+.It Fl P
+Preserve files that you replace in
+.Pa /var/tmp/mergemaster/preserved-files-<date> ,
+or another directory you specify in your
+.Nm
+rc file.
+.It Fl U
+Attempt to auto upgrade files that have not been user modified.
+This option can be dangerous when there are critical changes
+in the new versions that affect your running system.
+.It Fl -run-updates=[always|never]
+Specify always or never to run newaliases, pwd_mkdb, etc.
+at the end of the comparison run.
+If this option is omitted the default is to prompt the user
+for each update as necessary.
+.It Fl m Ar /path/to/sources
+Specify the path to the directory where you want to do the
+.Xr make 1 .
+(In other words, where your sources are, but -s was already
+taken.)
+In older versions of
+.Nm
+the path to
+.Pa src/etc
+was required.
+.Nm
+will convert the path if this older method is used.
+.It Fl t Ar /path/to/temp/root
+Create the temporary root environment in
+.Pa /path/to/temp/root
+instead of the default
+.Pa /var/tmp/temproot .
+.It Fl d
+Add the date and time to the name of the temporary
+root directory.
+If
+.Fl t
+is specified, this option must
+follow it if you want the date added too.
+.It Fl u Ar N
+Specify a numeric umask.
+The default is 022.
+.It Fl w Ar N
+Supply an alternate screen width to the
+.Xr sdiff 1
+command in numbers of columns.
+The default is 80.
+.It Fl A Ar Target architecture
+Specify an alternative
+.Ev TARGET_ARCH
+architecture name.
+.It Fl D Ar /path
+Specify the destination directory for the installed files.
+.El
+.Sh ENVIRONMENT
+The
+.Nm
+utility uses the
+.Ev PAGER
+environment variable if set.
+Otherwise it uses
+.Xr more 1 .
+If
+.Ev PAGER
+specifies a program outside
+its
+limited
+.Ev PATH
+without specifying the full path,
+.Nm
+prompts you with options on how to proceed.
+The
+.Ev MM_PRE_COMPARE_SCRIPT
+and
+.Ev MM_EXIT_SCRIPT
+variables are used as described above.
+Other variables that are used by the script internally
+can be specified in
+.Pa .mergemasterrc
+as described in more detail below.
+.Sh FILES
+.Bl -tag -width $HOME/.mergemasterrc -compact
+.It Pa /etc/mergemaster.rc
+.It Pa $HOME/.mergemasterrc
+.El
+.Pp
+The
+.Nm
+utility will
+.Ic .\&
+(source) these files if they exist.
+Command line options
+will override rc file options.
+.Pa $HOME/.mergemasterrc
+overrides
+.Pa /etc/mergemaster.rc .
+Here is an example
+with all values commented out:
+.Bd -literal
+# These are options for mergemaster, with their default values listed
+# The following options have command line overrides
+#
+# The target architecture (-A, unset by default)
+#ARCHSTRING='TARGET_ARCH=<foo>'
+#
+# Sourcedir is the directory to do the 'make' in (-m)
+#SOURCEDIR='/usr/src'
+#
+# Directory to install the temporary root environment into (-t)
+#TEMPROOT='/var/tmp/temproot'
+#
+# Specify the destination directory for the installed files (-D)
+#DESTDIR=
+#
+# Strict comparison skips the VCS Id test and compares every file (-s)
+#STRICT=no
+#
+# Type of diff, such as unified, context, etc. (-c)
+#DIFF_FLAG='-u'
+#
+# Install the new file if it differs only by VCS Id ($FreeBSD, -F)
+#FREEBSD_ID=
+#
+# Verbose mode includes more details and additional checks (-v)
+#VERBOSE=
+#
+# Automatically install files that do not exist on the system already (-i)
+#AUTO_INSTALL=
+#
+# Automatically upgrade files that have not been user modified (-U)
+# ***DANGEROUS***
+#AUTO_UPGRADE=
+#
+# Either always or never run newaliases, pwd_mkdb at the end (--run-updates)
+#RUN_UPDATES=
+#
+# Compare /etc/rc.conf[.local] to /etc/defaults/rc.conf (-C)
+#COMP_CONFS=
+#
+# Preserve files that you replace (-P)
+#PRESERVE_FILES=
+#PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-`date +%y%m%d-%H%M%S`
+#
+# The umask for mergemaster to compare the default file's modes to (-u)
+#NEW_UMASK=022
+#
+# The following options have no command line overrides
+#
+# Files to always avoid comparing
+#IGNORE_FILES='/etc/motd /etc/printcap foo bar'
+#
+# Additional options for diff. This will get unset when using -s.
+#DIFF_OPTIONS='-Bb' # Ignore changes in whitespace
+#
+# Location to store the list of mtree values for AUTO_UPGRADE purposes
+#MTREEDB='/var/db'
+#
+# For those who just cannot stand including the full path to PAGER
+#DONT_CHECK_PAGER=
+#
+# If you set 'yes' above, make sure to include the PATH to your pager
+#PATH=/bin:/usr/bin:/usr/sbin
+#
+# Delete stale files in /etc/rc.d without prompting
+#DELETE_STALE_RC_FILES=
+#
+# Specify the path to scripts to run before the comparison starts,
+# and/or after the script has finished its work
+#MM_PRE_COMPARE_SCRIPT=
+#MM_EXIT_SCRIPT=
+.Ed
+.Sh EXIT STATUS
+Exit status is 0 on successful completion, or if the user bails out
+manually at some point during execution.
+.Pp
+Exit status is 1 if it fails for one of the following reasons:
+.Pp
+Invalid command line option
+.Pp
+Failure to create the temporary root environment
+.Pp
+Failure to populate the temporary root
+.Pp
+Presence of the 'nodev' option in
+.Pa <DESTDIR>/etc/fstab
+.Pp
+Failure to install a file
+.Sh EXAMPLES
+Typically all you will need to do is type
+.Nm
+at the prompt and the script will do all the work for you.
+.Pp
+To use context diffs and have
+.Nm
+explain more things as it goes along, use:
+.Pp
+.Dl # mergemaster -cv
+.Pp
+To specify that
+.Nm
+put the temporary root environment in
+.Pa /usr/tmp/root ,
+use:
+.Pp
+.Dl # mergemaster -t /usr/tmp/root
+.Pp
+To specify a 110 column screen with a strict
+comparison, use:
+.Pp
+.Dl # mergemaster -sw 110
+.Sh SEE ALSO
+.Xr diff 1 ,
+.Xr make 1 ,
+.Xr more 1 ,
+.Xr sdiff 1 ,
+.Xr pwd_mkdb 8
+.Pp
+.Pa /usr/src/etc/Makefile
+.Rs
+.%U http://www.FreeBSD.org/doc/en_US.ISO8859-1/books/handbook/makeworld.html
+.%T The Cutting Edge (using make world)
+.%A Nik Clayton
+.Re
+.Sh HISTORY
+The
+.Nm
+utility was first publicly available on one of my
+web pages in a much simpler form under the name
+.Pa comproot
+on 13 March 1998.
+The idea for creating the
+temporary root environment comes from Nik Clayton's
+make world tutorial which is referenced above.
+.Sh AUTHORS
+This manual page and the script itself were written by
+.An Douglas Barton Aq Mt dougb@FreeBSD.org .
diff --git a/usr.sbin/mergemaster/mergemaster.sh b/usr.sbin/mergemaster/mergemaster.sh
new file mode 100755
index 0000000..9bf4f9f
--- /dev/null
+++ b/usr.sbin/mergemaster/mergemaster.sh
@@ -0,0 +1,1414 @@
+#!/bin/sh
+
+# mergemaster
+
+# Compare files created by /usr/src/etc/Makefile (or the directory
+# the user specifies) with the currently installed copies.
+
+# Copyright (c) 1998-2012 Douglas Barton, All rights reserved
+# Please see detailed copyright below
+
+# $FreeBSD$
+
+PATH=/bin:/usr/bin:/usr/sbin
+
+display_usage () {
+ VERSION_NUMBER=`grep "[$]FreeBSD:" $0 | cut -d ' ' -f 4`
+ echo "mergemaster version ${VERSION_NUMBER}"
+ echo 'Usage: mergemaster [-scrvhpCP] [-a|[-iFU]] [--run-updates=always|never]'
+ echo ' [-m /path] [-t /path] [-d] [-u N] [-w N] [-A arch] [-D /path]'
+ echo "Options:"
+ echo " -s Strict comparison (diff every pair of files)"
+ echo " -c Use context diff instead of unified diff"
+ echo " -r Re-run on a previously cleaned directory (skip temproot creation)"
+ echo " -v Be more verbose about the process, include additional checks"
+ echo " -a Leave all files that differ to merge by hand"
+ echo " -h Display more complete help"
+ echo ' -i Automatically install files that do not exist in destination directory'
+ echo ' -p Pre-buildworld mode, only compares crucial files'
+ echo ' -F Install files that differ only by revision control Id ($FreeBSD)'
+ echo ' -C Compare local rc.conf variables to the defaults'
+ echo ' -P Preserve files that are overwritten'
+ echo " -U Attempt to auto upgrade files that have not been user modified"
+ echo ' ***DANGEROUS***'
+ echo ' --run-updates= Specify always or never to run newalises, pwd_mkdb, etc.'
+ echo ''
+ echo " -m /path/directory Specify location of source to do the make in"
+ echo " -t /path/directory Specify temp root directory"
+ echo " -d Add date and time to directory name (e.g., /var/tmp/temproot.`date +%m%d.%H.%M`)"
+ echo " -u N Specify a numeric umask"
+ echo " -w N Specify a screen width in columns to sdiff"
+ echo " -A architecture Alternative architecture name to pass to make"
+ echo ' -D /path/directory Specify the destination directory to install files to'
+ echo ''
+}
+
+display_help () {
+ echo "* To specify a directory other than /var/tmp/temproot for the"
+ echo " temporary root environment, use -t /path/to/temp/root"
+ echo "* The -w option takes a number as an argument for the column width"
+ echo " of the screen. The default is 80."
+ echo '* The -a option causes mergemaster to run without prompting.'
+}
+
+# Loop allowing the user to use sdiff to merge files and display the merged
+# file.
+merge_loop () {
+ case "${VERBOSE}" in
+ '') ;;
+ *)
+ echo " *** Type h at the sdiff prompt (%) to get usage help"
+ ;;
+ esac
+ echo ''
+ MERGE_AGAIN=yes
+ while [ "${MERGE_AGAIN}" = "yes" ]; do
+ # Prime file.merged so we don't blat the owner/group id's
+ cp -p "${COMPFILE}" "${COMPFILE}.merged"
+ sdiff -o "${COMPFILE}.merged" --text --suppress-common-lines \
+ --width=${SCREEN_WIDTH:-80} "${DESTDIR}${COMPFILE#.}" "${COMPFILE}"
+ INSTALL_MERGED=V
+ while [ "${INSTALL_MERGED}" = "v" -o "${INSTALL_MERGED}" = "V" ]; do
+ echo ''
+ echo " Use 'i' to install merged file"
+ echo " Use 'r' to re-do the merge"
+ echo " Use 'v' to view the merged file"
+ echo " Default is to leave the temporary file to deal with by hand"
+ echo ''
+ echo -n " *** How should I deal with the merged file? [Leave it for later] "
+ read INSTALL_MERGED
+
+ case "${INSTALL_MERGED}" in
+ [iI])
+ mv "${COMPFILE}.merged" "${COMPFILE}"
+ echo ''
+ if mm_install "${COMPFILE}"; then
+ echo " *** Merged version of ${COMPFILE} installed successfully"
+ else
+ echo " *** Problem installing ${COMPFILE}, it will remain to merge by hand later"
+ fi
+ unset MERGE_AGAIN
+ ;;
+ [rR])
+ rm "${COMPFILE}.merged"
+ ;;
+ [vV])
+ ${PAGER} "${COMPFILE}.merged"
+ ;;
+ '')
+ echo " *** ${COMPFILE} will remain for your consideration"
+ unset MERGE_AGAIN
+ ;;
+ *)
+ echo "invalid choice: ${INSTALL_MERGED}"
+ INSTALL_MERGED=V
+ ;;
+ esac
+ done
+ done
+}
+
+# Loop showing user differences between files, allow merge, skip or install
+# options
+diff_loop () {
+
+ HANDLE_COMPFILE=v
+
+ while [ "${HANDLE_COMPFILE}" = "v" -o "${HANDLE_COMPFILE}" = "V" -o \
+ "${HANDLE_COMPFILE}" = "NOT V" ]; do
+ if [ -f "${DESTDIR}${COMPFILE#.}" -a -f "${COMPFILE}" ]; then
+ if [ -n "${AUTO_UPGRADE}" -a -n "${CHANGED}" ]; then
+ case "${CHANGED}" in
+ *:${DESTDIR}${COMPFILE#.}:*) ;; # File has been modified
+ *)
+ echo ''
+ echo " *** ${COMPFILE} has not been user modified."
+ echo ''
+
+ if mm_install "${COMPFILE}"; then
+ echo " *** ${COMPFILE} upgraded successfully"
+ echo ''
+ # Make the list print one file per line
+ AUTO_UPGRADED_FILES="${AUTO_UPGRADED_FILES} ${DESTDIR}${COMPFILE#.}
+"
+ else
+ echo " *** Problem upgrading ${COMPFILE}, it will remain to merge by hand"
+ fi
+ return
+ ;;
+ esac
+ fi
+ if [ "${HANDLE_COMPFILE}" = "v" -o "${HANDLE_COMPFILE}" = "V" ]; then
+ echo ''
+ echo ' ====================================================================== '
+ echo ''
+ (
+ echo " *** Displaying differences between ${COMPFILE} and installed version:"
+ echo ''
+ diff ${DIFF_FLAG} ${DIFF_OPTIONS} "${DESTDIR}${COMPFILE#.}" "${COMPFILE}"
+ ) | ${PAGER}
+ echo ''
+ fi
+ else
+ echo ''
+ echo " *** There is no installed version of ${COMPFILE}"
+ echo ''
+ case "${AUTO_INSTALL}" in
+ [Yy][Ee][Ss])
+ echo ''
+ if mm_install "${COMPFILE}"; then
+ echo " *** ${COMPFILE} installed successfully"
+ echo ''
+ # Make the list print one file per line
+ AUTO_INSTALLED_FILES="${AUTO_INSTALLED_FILES} ${DESTDIR}${COMPFILE#.}
+"
+ else
+ echo " *** Problem installing ${COMPFILE}, it will remain to merge by hand"
+ fi
+ return
+ ;;
+ *)
+ NO_INSTALLED=yes
+ ;;
+ esac
+ fi
+
+ echo " Use 'd' to delete the temporary ${COMPFILE}"
+ echo " Use 'i' to install the temporary ${COMPFILE}"
+ case "${NO_INSTALLED}" in
+ '')
+ echo " Use 'm' to merge the temporary and installed versions"
+ echo " Use 'v' to view the diff results again"
+ ;;
+ esac
+ echo ''
+ echo " Default is to leave the temporary file to deal with by hand"
+ echo ''
+ echo -n "How should I deal with this? [Leave it for later] "
+ read HANDLE_COMPFILE
+
+ case "${HANDLE_COMPFILE}" in
+ [dD])
+ rm "${COMPFILE}"
+ echo ''
+ echo " *** Deleting ${COMPFILE}"
+ ;;
+ [iI])
+ echo ''
+ if mm_install "${COMPFILE}"; then
+ echo " *** ${COMPFILE} installed successfully"
+ else
+ echo " *** Problem installing ${COMPFILE}, it will remain to merge by hand"
+ fi
+ ;;
+ [mM])
+ case "${NO_INSTALLED}" in
+ '')
+ # interact with user to merge files
+ merge_loop
+ ;;
+ *)
+ echo ''
+ echo " *** There is no installed version of ${COMPFILE}"
+ echo ''
+ HANDLE_COMPFILE="NOT V"
+ ;;
+ esac # End of "No installed version of file but user selected merge" test
+ ;;
+ [vV])
+ continue
+ ;;
+ '')
+ echo ''
+ echo " *** ${COMPFILE} will remain for your consideration"
+ ;;
+ *)
+ # invalid choice, show menu again.
+ echo "invalid choice: ${HANDLE_COMPFILE}"
+ echo ''
+ HANDLE_COMPFILE="NOT V"
+ continue
+ ;;
+ esac # End of "How to handle files that are different"
+ done
+ unset NO_INSTALLED
+ echo ''
+ case "${VERBOSE}" in
+ '') ;;
+ *)
+ sleep 3
+ ;;
+ esac
+}
+
+press_to_continue () {
+ local DISCARD
+ echo -n ' *** Press the [Enter] or [Return] key to continue '
+ read DISCARD
+}
+
+# Set the default path for the temporary root environment
+#
+TEMPROOT='/var/tmp/temproot'
+
+# Read /etc/mergemaster.rc first so the one in $HOME can override
+#
+if [ -r /etc/mergemaster.rc ]; then
+ . /etc/mergemaster.rc
+fi
+
+# Read .mergemasterrc before command line so CLI can override
+#
+if [ -r "$HOME/.mergemasterrc" ]; then
+ . "$HOME/.mergemasterrc"
+fi
+
+for var in "$@" ; do
+ case "$var" in
+ --run-updates*)
+ RUN_UPDATES=`echo ${var#--run-updates=} | tr [:upper:] [:lower:]`
+ ;;
+ *)
+ newopts="$newopts $var"
+ ;;
+ esac
+done
+
+set -- $newopts
+unset var newopts
+
+# Check the command line options
+#
+while getopts ":ascrvhipCPm:t:du:w:D:A:FU" COMMAND_LINE_ARGUMENT ; do
+ case "${COMMAND_LINE_ARGUMENT}" in
+ A)
+ ARCHSTRING='TARGET_ARCH='${OPTARG}
+ ;;
+ F)
+ FREEBSD_ID=yes
+ ;;
+ U)
+ AUTO_UPGRADE=yes
+ ;;
+ s)
+ STRICT=yes
+ unset DIFF_OPTIONS
+ ;;
+ c)
+ DIFF_FLAG='-c'
+ ;;
+ r)
+ RERUN=yes
+ ;;
+ v)
+ case "${AUTO_RUN}" in
+ '') VERBOSE=yes ;;
+ esac
+ ;;
+ a)
+ AUTO_RUN=yes
+ unset VERBOSE
+ ;;
+ h)
+ display_usage
+ display_help
+ exit 0
+ ;;
+ i)
+ AUTO_INSTALL=yes
+ ;;
+ C)
+ COMP_CONFS=yes
+ ;;
+ P)
+ PRESERVE_FILES=yes
+ ;;
+ p)
+ PRE_WORLD=yes
+ unset COMP_CONFS
+ unset AUTO_RUN
+ ;;
+ m)
+ SOURCEDIR=${OPTARG}
+ ;;
+ t)
+ TEMPROOT=${OPTARG}
+ ;;
+ d)
+ TEMPROOT=${TEMPROOT}.`date +%m%d.%H.%M`
+ ;;
+ u)
+ NEW_UMASK=${OPTARG}
+ ;;
+ w)
+ SCREEN_WIDTH=${OPTARG}
+ ;;
+ D)
+ DESTDIR=${OPTARG}
+ ;;
+ *)
+ display_usage
+ exit 1
+ ;;
+ esac
+done
+
+if [ -n "$AUTO_RUN" ]; then
+ if [ -n "$FREEBSD_ID" -o -n "$AUTO_UPGRADE" -o -n "$AUTO_INSTALL" ]; then
+ echo ''
+ echo "*** You have included the -a option along with one or more options"
+ echo ' that indicate that you wish mergemaster to actually make updates'
+ echo ' (-F, -U, or -i), however these options are not compatible.'
+ echo ' Please read mergemaster(8) for more information.'
+ echo ''
+ exit 1
+ fi
+fi
+
+# Assign the location of the mtree database
+#
+MTREEDB=${MTREEDB:-${DESTDIR}/var/db}
+MTREEFILE="${MTREEDB}/mergemaster.mtree"
+
+# Don't force the user to set this in the mergemaster rc file
+if [ -n "${PRESERVE_FILES}" -a -z "${PRESERVE_FILES_DIR}" ]; then
+ PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-`date +%y%m%d-%H%M%S`
+ mkdir -p ${PRESERVE_FILES_DIR}
+fi
+
+# Check for the mtree database in DESTDIR
+case "${AUTO_UPGRADE}" in
+'') ;; # If the option is not set no need to run the test or warn the user
+*)
+ if [ ! -s "${MTREEFILE}" ]; then
+ echo ''
+ echo "*** Unable to find mtree database (${MTREEFILE})."
+ echo " Skipping auto-upgrade on this run."
+ echo " It will be created for the next run when this one is complete."
+ echo ''
+ case "${AUTO_RUN}" in
+ '')
+ press_to_continue
+ ;;
+ esac
+ unset AUTO_UPGRADE
+ fi
+ ;;
+esac
+
+if [ -e "${DESTDIR}/etc/fstab" ]; then
+ if grep -q nodev ${DESTDIR}/etc/fstab; then
+ echo ''
+ echo "*** You have the deprecated 'nodev' option in ${DESTDIR}/etc/fstab."
+ echo " This can prevent the filesystem from being mounted on reboot."
+ echo " Please update your fstab before continuing."
+ echo " See fstab(5) for more information."
+ echo ''
+ exit 1
+ fi
+fi
+
+echo ''
+
+# If the user has a pager defined, make sure we can run it
+#
+case "${DONT_CHECK_PAGER}" in
+'')
+check_pager () {
+ while ! type "${PAGER%% *}" >/dev/null; do
+ echo " *** Your PAGER environment variable specifies '${PAGER}', but"
+ echo " due to the limited PATH that I use for security reasons,"
+ echo " I cannot execute it. So, what would you like to do?"
+ echo ''
+ echo " Use 'e' to exit mergemaster and fix your PAGER variable"
+ echo " Use 'l' to set PAGER to 'less' for this run"
+ echo " Use 'm' to use plain old 'more' as your PAGER for this run"
+ echo ''
+ echo " or you may type an absolute path to PAGER for this run"
+ echo ''
+ echo " Default is to use plain old 'more' "
+ echo ''
+ echo -n "What should I do? [Use 'more'] "
+ read FIXPAGER
+
+ case "${FIXPAGER}" in
+ [eE])
+ exit 0
+ ;;
+ [lL])
+ PAGER=less
+ ;;
+ [mM]|'')
+ PAGER=more
+ ;;
+ /*)
+ PAGER="$FIXPAGER"
+ ;;
+ *)
+ echo ''
+ echo "invalid choice: ${FIXPAGER}"
+ esac
+ echo ''
+ done
+}
+ if [ -n "${PAGER}" ]; then
+ check_pager
+ fi
+ ;;
+esac
+
+# If user has a pager defined, or got assigned one above, use it.
+# If not, use more.
+#
+PAGER=${PAGER:-more}
+
+if [ -n "${VERBOSE}" -a ! "${PAGER}" = "more" ]; then
+ echo " *** You have ${PAGER} defined as your pager so we will use that"
+ echo ''
+ sleep 3
+fi
+
+# Assign the diff flag once so we will not have to keep testing it
+#
+DIFF_FLAG=${DIFF_FLAG:--u}
+
+# Assign the source directory
+#
+SOURCEDIR=${SOURCEDIR:-/usr/src}
+if [ ! -f ${SOURCEDIR}/Makefile.inc1 -a \
+ -f ${SOURCEDIR}/../Makefile.inc1 ]; then
+ echo " *** The source directory you specified (${SOURCEDIR})"
+ echo " will be reset to ${SOURCEDIR}/.."
+ echo ''
+ sleep 3
+ SOURCEDIR=${SOURCEDIR}/..
+fi
+SOURCEDIR=$(realpath "$SOURCEDIR")
+
+# Setup make to use system files from SOURCEDIR
+MM_MAKE="make ${ARCHSTRING} -m ${SOURCEDIR}/share/mk"
+
+# Check DESTDIR against the mergemaster mtree database to see what
+# files the user changed from the reference files.
+#
+if [ -n "${AUTO_UPGRADE}" -a -s "${MTREEFILE}" ]; then
+ # Force FreeBSD 9 compatible output when available.
+ if mtree -F freebsd9 -c -p /var/empty/ > /dev/null 2>&1; then
+ MTREE_FLAVOR="-F freebsd9"
+ else
+ MTREE_FLAVOR=
+ fi
+ CHANGED=:
+ for file in `mtree -eqL ${MTREE_FLAVOR} -f ${MTREEFILE} -p ${DESTDIR}/ \
+ 2>/dev/null | awk '($2 == "changed") {print $1}'`; do
+ if [ -f "${DESTDIR}/$file" ]; then
+ CHANGED="${CHANGED}${DESTDIR}/${file}:"
+ fi
+ done
+ [ "$CHANGED" = ':' ] && unset CHANGED
+fi
+
+# Check the width of the user's terminal
+#
+if [ -t 0 ]; then
+ w=`tput columns`
+ case "${w}" in
+ 0|'') ;; # No-op, since the input is not valid
+ *)
+ case "${SCREEN_WIDTH}" in
+ '') SCREEN_WIDTH="${w}" ;;
+ "${w}") ;; # No-op, since they are the same
+ *)
+ echo -n "*** You entered ${SCREEN_WIDTH} as your screen width, but stty "
+ echo "thinks it is ${w}."
+ echo ''
+ echo -n "What would you like to use? [${w}] "
+ read SCREEN_WIDTH
+ case "${SCREEN_WIDTH}" in
+ '') SCREEN_WIDTH="${w}" ;;
+ esac
+ ;;
+ esac
+ esac
+fi
+
+# Define what $Id tag to look for to aid portability.
+#
+ID_TAG=FreeBSD
+
+delete_temproot () {
+ rm -rf "${TEMPROOT}" 2>/dev/null
+ chflags -R 0 "${TEMPROOT}" 2>/dev/null
+ rm -rf "${TEMPROOT}" || { echo "*** Unable to delete ${TEMPROOT}"; exit 1; }
+}
+
+case "${RERUN}" in
+'')
+ # Set up the loop to test for the existence of the
+ # temp root directory.
+ #
+ TEST_TEMP_ROOT=yes
+ while [ "${TEST_TEMP_ROOT}" = "yes" ]; do
+ if [ -d "${TEMPROOT}" ]; then
+ echo "*** The directory specified for the temporary root environment,"
+ echo " ${TEMPROOT}, exists. This can be a security risk if untrusted"
+ echo " users have access to the system."
+ echo ''
+ case "${AUTO_RUN}" in
+ '')
+ echo " Use 'd' to delete the old ${TEMPROOT} and continue"
+ echo " Use 't' to select a new temporary root directory"
+ echo " Use 'e' to exit mergemaster"
+ echo ''
+ echo " Default is to use ${TEMPROOT} as is"
+ echo ''
+ echo -n "How should I deal with this? [Use the existing ${TEMPROOT}] "
+ read DELORNOT
+
+ case "${DELORNOT}" in
+ [dD])
+ echo ''
+ echo " *** Deleting the old ${TEMPROOT}"
+ echo ''
+ delete_temproot
+ unset TEST_TEMP_ROOT
+ ;;
+ [tT])
+ echo " *** Enter new directory name for temporary root environment"
+ read TEMPROOT
+ ;;
+ [eE])
+ exit 0
+ ;;
+ '')
+ echo ''
+ echo " *** Leaving ${TEMPROOT} intact"
+ echo ''
+ unset TEST_TEMP_ROOT
+ ;;
+ *)
+ echo ''
+ echo "invalid choice: ${DELORNOT}"
+ echo ''
+ ;;
+ esac
+ ;;
+ *)
+ # If this is an auto-run, try a hopefully safe alternative then
+ # re-test anyway.
+ TEMPROOT=/var/tmp/temproot.`date +%m%d.%H.%M.%S`
+ ;;
+ esac
+ else
+ unset TEST_TEMP_ROOT
+ fi
+ done
+
+ echo "*** Creating the temporary root environment in ${TEMPROOT}"
+
+ if mkdir -p "${TEMPROOT}"; then
+ echo " *** ${TEMPROOT} ready for use"
+ fi
+
+ if [ ! -d "${TEMPROOT}" ]; then
+ echo ''
+ echo " *** FATAL ERROR: Cannot create ${TEMPROOT}"
+ echo ''
+ exit 1
+ fi
+
+ echo " *** Creating and populating directory structure in ${TEMPROOT}"
+ echo ''
+
+ case "${VERBOSE}" in
+ '') ;;
+ *)
+ press_to_continue
+ ;;
+ esac
+
+ case "${PRE_WORLD}" in
+ '')
+ { cd ${SOURCEDIR} &&
+ case "${DESTDIR}" in
+ '') ;;
+ *)
+ ${MM_MAKE} DESTDIR=${DESTDIR} distrib-dirs >/dev/null
+ ;;
+ esac
+ ${MM_MAKE} DESTDIR=${TEMPROOT} distrib-dirs >/dev/null &&
+ ${MM_MAKE} _obj SUBDIR_OVERRIDE=etc >/dev/null &&
+ ${MM_MAKE} everything SUBDIR_OVERRIDE=etc >/dev/null &&
+ ${MM_MAKE} DESTDIR=${TEMPROOT} distribution >/dev/null;} ||
+ { echo '';
+ echo " *** FATAL ERROR: Cannot 'cd' to ${SOURCEDIR} and install files to";
+ echo " the temproot environment";
+ echo '';
+ exit 1;}
+ ;;
+ *)
+ # Only set up files that are crucial to {build|install}world
+ { mkdir -p ${TEMPROOT}/etc &&
+ cp -p ${SOURCEDIR}/etc/master.passwd ${TEMPROOT}/etc &&
+ install -p -o root -g wheel -m 0644 ${SOURCEDIR}/etc/group ${TEMPROOT}/etc;} ||
+ { echo '';
+ echo ' *** FATAL ERROR: Cannot copy files to the temproot environment';
+ echo '';
+ exit 1;}
+ ;;
+ esac
+
+ # Doing the inventory and removing files that we don't want to compare only
+ # makes sense if we are not doing a rerun, since we have no way of knowing
+ # what happened to the files during previous incarnations.
+ case "${VERBOSE}" in
+ '') ;;
+ *)
+ echo ''
+ echo ' *** The following files exist only in the installed version of'
+ echo " ${DESTDIR}/etc. In the vast majority of cases these files"
+ echo ' are necessary parts of the system and should not be deleted.'
+ echo ' However because these files are not updated by this process you'
+ echo ' might want to verify their status before rebooting your system.'
+ echo ''
+ press_to_continue
+ diff -qr ${DESTDIR}/etc ${TEMPROOT}/etc | grep "^Only in ${DESTDIR}/etc" | ${PAGER}
+ echo ''
+ press_to_continue
+ ;;
+ esac
+
+ case "${IGNORE_MOTD}" in
+ '') ;;
+ *)
+ echo ''
+ echo "*** You have the IGNORE_MOTD option set in your mergemaster rc file."
+ echo " This option is deprecated in favor of the IGNORE_FILES option."
+ echo " Please update your rc file accordingly."
+ echo ''
+ exit 1
+ ;;
+ esac
+
+ # Avoid comparing the following user specified files
+ for file in ${IGNORE_FILES}; do
+ test -e ${TEMPROOT}/${file} && unlink ${TEMPROOT}/${file}
+ done
+
+ # We really don't want to have to deal with files like login.conf.db, pwd.db,
+ # or spwd.db. Instead, we want to compare the text versions, and run *_mkdb.
+ # Prompt the user to do so below, as needed.
+ #
+ rm -f ${TEMPROOT}/etc/*.db ${TEMPROOT}/etc/passwd \
+ ${TEMPROOT}/var/db/services.db
+
+ # We only need to compare things like freebsd.cf once
+ find ${TEMPROOT}/usr/obj -type f -delete 2>/dev/null
+
+ # Delete stuff we do not need to keep the mtree database small,
+ # and to make the actual comparison faster.
+ find ${TEMPROOT}/usr -type l -delete 2>/dev/null
+ find ${TEMPROOT} -type f -size 0 -delete 2>/dev/null
+ find -d ${TEMPROOT} -type d -empty -mindepth 1 -delete 2>/dev/null
+
+ # Build the mtree database in a temporary location.
+ case "${PRE_WORLD}" in
+ '') MTREENEW=`mktemp -t mergemaster.mtree`
+ mtree -nci -p ${TEMPROOT} -k size,md5digest > ${MTREENEW} 2>/dev/null
+ ;;
+ *) # We don't want to mess with the mtree database on a pre-world run or
+ # when re-scanning a previously-built tree.
+ ;;
+ esac
+ ;; # End of the "RERUN" test
+esac
+
+# Get ready to start comparing files
+
+# Check umask if not specified on the command line,
+# and we are not doing an autorun
+#
+if [ -z "${NEW_UMASK}" -a -z "${AUTO_RUN}" ]; then
+ USER_UMASK=`umask`
+ case "${USER_UMASK}" in
+ 0022|022) ;;
+ *)
+ echo ''
+ echo " *** Your umask is currently set to ${USER_UMASK}. By default, this script"
+ echo " installs all files with the same user, group and modes that"
+ echo " they are created with by ${SOURCEDIR}/etc/Makefile, compared to"
+ echo " a umask of 022. This umask allows world read permission when"
+ echo " the file's default permissions have it."
+ echo ''
+ echo " No world permissions can sometimes cause problems. A umask of"
+ echo " 022 will restore the default behavior, but is not mandatory."
+ echo " /etc/master.passwd is a special case. Its file permissions"
+ echo " will be 600 (rw-------) if installed."
+ echo ''
+ echo -n "What umask should I use? [${USER_UMASK}] "
+ read NEW_UMASK
+
+ NEW_UMASK="${NEW_UMASK:-$USER_UMASK}"
+ ;;
+ esac
+ echo ''
+fi
+
+CONFIRMED_UMASK=${NEW_UMASK:-0022}
+
+#
+# Warn users who still have old rc files
+#
+for file in atm devfs diskless1 diskless2 network network6 pccard \
+ serial syscons sysctl alpha amd64 i386 sparc64; do
+ if [ -f "${DESTDIR}/etc/rc.${file}" ]; then
+ OLD_RC_PRESENT=1
+ break
+ fi
+done
+
+case "${OLD_RC_PRESENT}" in
+1)
+ echo ''
+ echo " *** There are elements of the old rc system in ${DESTDIR}/etc/."
+ echo ''
+ echo ' While these scripts will not hurt anything, they are not'
+ echo ' functional on an up to date system, and can be removed.'
+ echo ''
+
+ case "${AUTO_RUN}" in
+ '')
+ echo -n 'Move these files to /var/tmp/mergemaster/old_rc? [yes] '
+ read MOVE_OLD_RC
+
+ case "${MOVE_OLD_RC}" in
+ [nN]*) ;;
+ *)
+ mkdir -p /var/tmp/mergemaster/old_rc
+ for file in atm devfs diskless1 diskless2 network network6 pccard \
+ serial syscons sysctl alpha amd64 i386 sparc64; do
+ if [ -f "${DESTDIR}/etc/rc.${file}" ]; then
+ mv ${DESTDIR}/etc/rc.${file} /var/tmp/mergemaster/old_rc/
+ fi
+ done
+ echo ' The files have been moved'
+ press_to_continue
+ ;;
+ esac
+ ;;
+ *) ;;
+ esac
+esac
+
+# Use the umask/mode information to install the files
+# Create directories as needed
+#
+install_error () {
+ echo "*** FATAL ERROR: Unable to install ${1} to ${2}"
+ echo ''
+ exit 1
+}
+
+do_install_and_rm () {
+ case "${PRESERVE_FILES}" in
+ [Yy][Ee][Ss])
+ if [ -f "${3}/${2##*/}" ]; then
+ mkdir -p ${PRESERVE_FILES_DIR}/${2%/*}
+ cp ${3}/${2##*/} ${PRESERVE_FILES_DIR}/${2%/*}
+ fi
+ ;;
+ esac
+
+ if [ ! -d "${3}/${2##*/}" ]; then
+ if install -m ${1} ${2} ${3}; then
+ unlink ${2}
+ else
+ install_error ${2} ${3}
+ fi
+ else
+ install_error ${2} ${3}
+ fi
+}
+
+# 4095 = "obase=10;ibase=8;07777" | bc
+find_mode () {
+ local OCTAL
+ OCTAL=$(( ~$(echo "obase=10; ibase=8; ${CONFIRMED_UMASK}" | bc) & 4095 &
+ $(echo "obase=10; ibase=8; $(stat -f "%OMp%OLp" ${1})" | bc) ))
+ printf "%04o\n" ${OCTAL}
+}
+
+mm_install () {
+ local INSTALL_DIR
+ INSTALL_DIR=${1#.}
+ INSTALL_DIR=${INSTALL_DIR%/*}
+
+ case "${INSTALL_DIR}" in
+ '')
+ INSTALL_DIR=/
+ ;;
+ esac
+
+ if [ -n "${DESTDIR}${INSTALL_DIR}" -a ! -d "${DESTDIR}${INSTALL_DIR}" ]; then
+ DIR_MODE=`find_mode "${TEMPROOT}/${INSTALL_DIR}"`
+ install -d -o root -g wheel -m "${DIR_MODE}" "${DESTDIR}${INSTALL_DIR}" ||
+ install_error $1 ${DESTDIR}${INSTALL_DIR}
+ fi
+
+ FILE_MODE=`find_mode "${1}"`
+
+ if [ ! -x "${1}" ]; then
+ case "${1#.}" in
+ /etc/mail/aliases)
+ NEED_NEWALIASES=yes
+ ;;
+ /etc/login.conf)
+ NEED_CAP_MKDB=yes
+ ;;
+ /etc/services)
+ NEED_SERVICES_MKDB=yes
+ ;;
+ /etc/master.passwd)
+ do_install_and_rm 600 "${1}" "${DESTDIR}${INSTALL_DIR}"
+ NEED_PWD_MKDB=yes
+ DONT_INSTALL=yes
+ ;;
+ /.cshrc | /.profile)
+ local st_nlink
+
+ # install will unlink the file before it installs the new one,
+ # so we have to restore/create the link afterwards.
+ #
+ st_nlink=0 # In case the file does not yet exist
+ eval $(stat -s ${DESTDIR}${COMPFILE#.} 2>/dev/null)
+
+ do_install_and_rm "${FILE_MODE}" "${1}" "${DESTDIR}${INSTALL_DIR}"
+
+ if [ -n "${AUTO_INSTALL}" -a $st_nlink -gt 1 ]; then
+ HANDLE_LINK=l
+ else
+ case "${LINK_EXPLAINED}" in
+ '')
+ echo " *** Historically BSD derived systems have had a"
+ echo " hard link from /.cshrc and /.profile to"
+ echo " their namesakes in /root. Please indicate"
+ echo " your preference below for bringing your"
+ echo " installed files up to date."
+ echo ''
+ LINK_EXPLAINED=yes
+ ;;
+ esac
+
+ echo " Use 'd' to delete the temporary ${COMPFILE}"
+ echo " Use 'l' to delete the existing ${DESTDIR}/root/${COMPFILE##*/} and create the link"
+ echo ''
+ echo " Default is to leave the temporary file to deal with by hand"
+ echo ''
+ echo -n " How should I handle ${COMPFILE}? [Leave it to install later] "
+ read HANDLE_LINK
+ fi
+
+ case "${HANDLE_LINK}" in
+ [dD]*)
+ rm "${COMPFILE}"
+ echo ''
+ echo " *** Deleting ${COMPFILE}"
+ ;;
+ [lL]*)
+ echo ''
+ unlink ${DESTDIR}/root/${COMPFILE##*/}
+ if ln ${DESTDIR}${COMPFILE#.} ${DESTDIR}/root/${COMPFILE##*/}; then
+ echo " *** Link from ${DESTDIR}${COMPFILE#.} to ${DESTDIR}/root/${COMPFILE##*/} installed successfully"
+ else
+ echo " *** Error linking ${DESTDIR}${COMPFILE#.} to ${DESTDIR}/root/${COMPFILE##*/}"
+ echo " *** ${COMPFILE} will remain for your consideration"
+ fi
+ ;;
+ *)
+ echo " *** ${COMPFILE} will remain for your consideration"
+ ;;
+ esac
+ return
+ ;;
+ esac
+
+ case "${DONT_INSTALL}" in
+ '')
+ do_install_and_rm "${FILE_MODE}" "${1}" "${DESTDIR}${INSTALL_DIR}"
+ ;;
+ *)
+ unset DONT_INSTALL
+ ;;
+ esac
+ else # File matched -x
+ do_install_and_rm "${FILE_MODE}" "${1}" "${DESTDIR}${INSTALL_DIR}"
+ fi
+ return $?
+}
+
+if [ ! -d "${TEMPROOT}" ]; then
+ echo "*** FATAL ERROR: The temproot directory (${TEMPROOT})"
+ echo ' has disappeared!'
+ echo ''
+ exit 1
+fi
+
+echo ''
+echo "*** Beginning comparison"
+echo ''
+
+# Pre-world does not populate /etc/rc.d.
+# It is very possible that a previous run would have deleted files in
+# ${TEMPROOT}/etc/rc.d, thus creating a lot of false positives.
+if [ -z "${PRE_WORLD}" -a -z "${RERUN}" ]; then
+ echo " *** Checking ${DESTDIR}/etc/rc.d for stale files"
+ echo ''
+ cd "${DESTDIR}/etc/rc.d" &&
+ for file in *; do
+ if [ ! -e "${TEMPROOT}/etc/rc.d/${file}" ]; then
+ STALE_RC_FILES="${STALE_RC_FILES} ${file}"
+ fi
+ done
+ case "${STALE_RC_FILES}" in
+ ''|' *')
+ echo ' *** No stale files found'
+ ;;
+ *)
+ echo " *** The following files exist in ${DESTDIR}/etc/rc.d but not in"
+ echo " ${TEMPROOT}/etc/rc.d/:"
+ echo ''
+ echo "${STALE_RC_FILES}"
+ echo ''
+ echo ' The presence of stale files in this directory can cause the'
+ echo ' dreaded unpredictable results, and therefore it is highly'
+ echo ' recommended that you delete them.'
+ case "${AUTO_RUN}" in
+ '')
+ echo ''
+ echo -n ' *** Delete them now? [n] '
+ read DELETE_STALE_RC_FILES
+ case "${DELETE_STALE_RC_FILES}" in
+ [yY])
+ echo ' *** Deleting ... '
+ rm ${STALE_RC_FILES}
+ echo ' done.'
+ ;;
+ *)
+ echo ' *** Files will not be deleted'
+ ;;
+ esac
+ sleep 2
+ ;;
+ *)
+ if [ -n "${DELETE_STALE_RC_FILES}" ]; then
+ echo ' *** Deleting ... '
+ rm ${STALE_RC_FILES}
+ echo ' done.'
+ fi
+ esac
+ ;;
+ esac
+ echo ''
+fi
+
+cd "${TEMPROOT}"
+
+if [ -r "${MM_PRE_COMPARE_SCRIPT}" ]; then
+ . "${MM_PRE_COMPARE_SCRIPT}"
+fi
+
+# Things that were files/directories/links in one version can sometimes
+# change to something else in a newer version. So we need to explicitly
+# test for this, and warn the user if what we find does not match.
+#
+for COMPFILE in `find . | sort` ; do
+ if [ -e "${DESTDIR}${COMPFILE#.}" ]; then
+ INSTALLED_TYPE=`stat -f '%HT' ${DESTDIR}${COMPFILE#.}`
+ else
+ continue
+ fi
+ TEMPROOT_TYPE=`stat -f '%HT' $COMPFILE`
+
+ if [ ! "$TEMPROOT_TYPE" = "$INSTALLED_TYPE" ]; then
+ [ "$COMPFILE" = '.' ] && continue
+ TEMPROOT_TYPE=`echo $TEMPROOT_TYPE | tr [:upper:] [:lower:]`
+ INSTALLED_TYPE=`echo $INSTALLED_TYPE | tr [:upper:] [:lower:]`
+
+ echo "*** The installed file ${DESTDIR}${COMPFILE#.} has the type \"$INSTALLED_TYPE\""
+ echo " but the new version has the type \"$TEMPROOT_TYPE\""
+ echo ''
+ echo " How would you like to handle this?"
+ echo ''
+ echo " Use 'r' to remove ${DESTDIR}${COMPFILE#.}"
+ case "$TEMPROOT_TYPE" in
+ 'symbolic link')
+ TARGET=`readlink $COMPFILE`
+ echo " and create a link to $TARGET in its place" ;;
+ *) echo " You will be able to install it as a \"$TEMPROOT_TYPE\"" ;;
+ esac
+ echo ''
+ echo " Use 'i' to ignore this"
+ echo ''
+ echo -n " How to proceed? [i] "
+ read ANSWER
+ case "$ANSWER" in
+ [rR]) case "${PRESERVE_FILES}" in
+ [Yy][Ee][Ss])
+ mv ${DESTDIR}${COMPFILE#.} ${PRESERVE_FILES_DIR}/ || exit 1 ;;
+ *) rm -rf ${DESTDIR}${COMPFILE#.} ;;
+ esac
+ case "$TEMPROOT_TYPE" in
+ 'symbolic link') ln -sf $TARGET ${DESTDIR}${COMPFILE#.} ;;
+ esac ;;
+ *) echo ''
+ echo "*** See the man page about adding ${COMPFILE#.} to the list of IGNORE_FILES"
+ press_to_continue ;;
+ esac
+ echo ''
+ fi
+done
+
+for COMPFILE in `find . -type f | sort`; do
+
+ # First, check to see if the file exists in DESTDIR. If not, the
+ # diff_loop function knows how to handle it.
+ #
+ if [ ! -e "${DESTDIR}${COMPFILE#.}" ]; then
+ case "${AUTO_RUN}" in
+ '')
+ diff_loop
+ ;;
+ *)
+ case "${AUTO_INSTALL}" in
+ '')
+ # If this is an auto run, make it official
+ echo " *** ${COMPFILE} will remain for your consideration"
+ ;;
+ *)
+ diff_loop
+ ;;
+ esac
+ ;;
+ esac # Auto run test
+ continue
+ fi
+
+ case "${STRICT}" in
+ '' | [Nn][Oo])
+ # Compare $Id's first so if the file hasn't been modified
+ # local changes will be ignored.
+ # If the files have the same $Id, delete the one in temproot so the
+ # user will have less to wade through if files are left to merge by hand.
+ #
+ ID1=`grep "[$]${ID_TAG}:" ${DESTDIR}${COMPFILE#.} 2>/dev/null`
+ ID2=`grep "[$]${ID_TAG}:" ${COMPFILE} 2>/dev/null` || ID2=none
+
+ case "${ID2}" in
+ "${ID1}")
+ echo " *** Temp ${COMPFILE} and installed have the same Id, deleting"
+ rm "${COMPFILE}"
+ ;;
+ esac
+ ;;
+ esac
+
+ # If the file is still here either because the $Ids are different, the
+ # file doesn't have an $Id, or we're using STRICT mode; look at the diff.
+ #
+ if [ -f "${COMPFILE}" ]; then
+
+ # Do an absolute diff first to see if the files are actually different.
+ # If they're not different, delete the one in temproot.
+ #
+ if diff -q ${DIFF_OPTIONS} "${DESTDIR}${COMPFILE#.}" "${COMPFILE}" > \
+ /dev/null 2>&1; then
+ echo " *** Temp ${COMPFILE} and installed are the same, deleting"
+ rm "${COMPFILE}"
+ else
+ # Ok, the files are different, so show the user where they differ.
+ # Use user's choice of diff methods; and user's pager if they have one.
+ # Use more if not.
+ # Use unified diffs by default. Context diffs give me a headache. :)
+ #
+ # If the user chose the -F option, test for that before proceeding
+ #
+ if [ -n "$FREEBSD_ID" ]; then
+ if diff -q -I'[$]FreeBSD.*[$]' "${DESTDIR}${COMPFILE#.}" "${COMPFILE}" > \
+ /dev/null 2>&1; then
+ if mm_install "${COMPFILE}"; then
+ echo "*** Updated revision control Id for ${DESTDIR}${COMPFILE#.}"
+ else
+ echo "*** Problem installing ${COMPFILE}, it will remain to merge by hand later"
+ fi
+ continue
+ fi
+ fi
+ case "${AUTO_RUN}" in
+ '')
+ # prompt user to install/delete/merge changes
+ diff_loop
+ ;;
+ *)
+ # If this is an auto run, make it official
+ echo " *** ${COMPFILE} will remain for your consideration"
+ ;;
+ esac # Auto run test
+ fi # Yes, the files are different
+ fi # Yes, the file still remains to be checked
+done # This is for the for way up there at the beginning of the comparison
+
+echo ''
+echo "*** Comparison complete"
+
+if [ -s "${MTREENEW}" ]; then
+ echo "*** Saving mtree database for future upgrades"
+ test -e "${MTREEFILE}" && unlink ${MTREEFILE}
+ mv ${MTREENEW} ${MTREEFILE}
+fi
+
+echo ''
+
+TEST_FOR_FILES=`find ${TEMPROOT} -type f -size +0 2>/dev/null`
+if [ -n "${TEST_FOR_FILES}" ]; then
+ echo "*** Files that remain for you to merge by hand:"
+ find "${TEMPROOT}" -type f -size +0 | sort
+ echo ''
+
+ case "${AUTO_RUN}" in
+ '')
+ echo -n "Do you wish to delete what is left of ${TEMPROOT}? [no] "
+ read DEL_TEMPROOT
+ case "${DEL_TEMPROOT}" in
+ [yY]*)
+ delete_temproot
+ ;;
+ *)
+ echo " *** ${TEMPROOT} will remain"
+ ;;
+ esac
+ ;;
+ *) ;;
+ esac
+else
+ echo "*** ${TEMPROOT} is empty, deleting"
+ delete_temproot
+fi
+
+case "${AUTO_INSTALLED_FILES}" in
+'') ;;
+*)
+ case "${AUTO_RUN}" in
+ '')
+ (
+ echo ''
+ echo '*** You chose the automatic install option for files that did not'
+ echo ' exist on your system. The following were installed for you:'
+ echo "${AUTO_INSTALLED_FILES}"
+ ) | ${PAGER}
+ ;;
+ *)
+ echo ''
+ echo '*** You chose the automatic install option for files that did not'
+ echo ' exist on your system. The following were installed for you:'
+ echo "${AUTO_INSTALLED_FILES}"
+ ;;
+ esac
+ ;;
+esac
+
+case "${AUTO_UPGRADED_FILES}" in
+'') ;;
+*)
+ case "${AUTO_RUN}" in
+ '')
+ (
+ echo ''
+ echo '*** You chose the automatic upgrade option for files that you did'
+ echo ' not alter on your system. The following were upgraded for you:'
+ echo "${AUTO_UPGRADED_FILES}"
+ ) | ${PAGER}
+ ;;
+ *)
+ echo ''
+ echo '*** You chose the automatic upgrade option for files that you did'
+ echo ' not alter on your system. The following were upgraded for you:'
+ echo "${AUTO_UPGRADED_FILES}"
+ ;;
+ esac
+ ;;
+esac
+
+run_it_now () {
+ [ -n "$AUTO_RUN" ] && return
+
+ local answer
+
+ echo ''
+ while : ; do
+ if [ "$RUN_UPDATES" = always ]; then
+ answer=y
+ elif [ "$RUN_UPDATES" = never ]; then
+ answer=n
+ else
+ echo -n ' Would you like to run it now? y or n [n] '
+ read answer
+ fi
+
+ case "$answer" in
+ y)
+ echo " Running ${1}"
+ echo ''
+ eval "${1}"
+ return
+ ;;
+ ''|n)
+ if [ ! "$RUN_UPDATES" = never ]; then
+ echo ''
+ echo " *** Cancelled"
+ echo ''
+ fi
+ echo " Make sure to run ${1} yourself"
+ return
+ ;;
+ *)
+ echo ''
+ echo " *** Sorry, I do not understand your answer (${answer})"
+ echo ''
+ esac
+ done
+}
+
+case "${NEED_NEWALIASES}" in
+'') ;;
+*)
+ echo ''
+ if [ -n "${DESTDIR}" ]; then
+ echo "*** You installed a new aliases file into ${DESTDIR}/etc/mail, but"
+ echo " the newaliases command is limited to the directories configured"
+ echo " in sendmail.cf. Make sure to create your aliases database by"
+ echo " hand when your sendmail configuration is done."
+ else
+ echo "*** You installed a new aliases file, so make sure that you run"
+ echo " '/usr/bin/newaliases' to rebuild your aliases database"
+ run_it_now '/usr/bin/newaliases'
+ fi
+ ;;
+esac
+
+case "${NEED_CAP_MKDB}" in
+'') ;;
+*)
+ echo ''
+ echo "*** You installed a login.conf file, so make sure that you run"
+ echo " '/usr/bin/cap_mkdb ${DESTDIR}/etc/login.conf'"
+ echo " to rebuild your login.conf database"
+ run_it_now "/usr/bin/cap_mkdb ${DESTDIR}/etc/login.conf"
+ ;;
+esac
+
+case "${NEED_SERVICES_MKDB}" in
+'') ;;
+*)
+ echo ''
+ echo "*** You installed a services file, so make sure that you run"
+ echo " '/usr/sbin/services_mkdb -q -o ${DESTDIR}/var/db/services.db ${DESTDIR}/etc/services'"
+ echo " to rebuild your services database"
+ run_it_now "/usr/sbin/services_mkdb -q -o ${DESTDIR}/var/db/services.db ${DESTDIR}/etc/services"
+ ;;
+esac
+
+case "${NEED_PWD_MKDB}" in
+'') ;;
+*)
+ echo ''
+ echo "*** You installed a new master.passwd file, so make sure that you run"
+ if [ -n "${DESTDIR}" ]; then
+ echo " '/usr/sbin/pwd_mkdb -d ${DESTDIR}/etc -p ${DESTDIR}/etc/master.passwd'"
+ echo " to rebuild your password files"
+ run_it_now "/usr/sbin/pwd_mkdb -d ${DESTDIR}/etc -p ${DESTDIR}/etc/master.passwd"
+ else
+ echo " '/usr/sbin/pwd_mkdb -p /etc/master.passwd'"
+ echo " to rebuild your password files"
+ run_it_now '/usr/sbin/pwd_mkdb -p /etc/master.passwd'
+ fi
+ ;;
+esac
+
+if [ -e "${DESTDIR}/etc/localtime" -a ! -L "${DESTDIR}/etc/localtime" -a -z "${PRE_WORLD}" ]; then # Ignore if TZ == UTC
+ echo ''
+ [ -n "${DESTDIR}" ] && tzs_args="-C ${DESTDIR}"
+ if [ -f "${DESTDIR}/var/db/zoneinfo" ]; then
+ echo "*** Reinstalling `cat ${DESTDIR}/var/db/zoneinfo` as ${DESTDIR}/etc/localtime"
+ tzsetup $tzs_args -r
+ else
+ echo "*** There is no ${DESTDIR}/var/db/zoneinfo file to update ${DESTDIR}/etc/localtime."
+ echo ' You should run tzsetup'
+ run_it_now "tzsetup $tzs_args"
+ fi
+fi
+
+echo ''
+
+if [ -r "${MM_EXIT_SCRIPT}" ]; then
+ . "${MM_EXIT_SCRIPT}"
+fi
+
+case "${COMP_CONFS}" in
+'') ;;
+*)
+ . ${DESTDIR}/etc/defaults/rc.conf
+
+ (echo ''
+ echo "*** Comparing conf files: ${rc_conf_files}"
+
+ for CONF_FILE in ${rc_conf_files}; do
+ if [ -r "${DESTDIR}${CONF_FILE}" ]; then
+ echo ''
+ echo "*** From ${DESTDIR}${CONF_FILE}"
+ echo "*** From ${DESTDIR}/etc/defaults/rc.conf"
+
+ for RC_CONF_VAR in `grep -i ^[a-z] ${DESTDIR}${CONF_FILE} |
+ cut -d '=' -f 1`; do
+ echo ''
+ grep -w ^${RC_CONF_VAR} ${DESTDIR}${CONF_FILE}
+ grep -w ^${RC_CONF_VAR} ${DESTDIR}/etc/defaults/rc.conf ||
+ echo ' * No default variable with this name'
+ done
+ fi
+ done) | ${PAGER}
+ echo ''
+ ;;
+esac
+
+if [ -n "${PRESERVE_FILES}" ]; then
+ find -d $PRESERVE_FILES_DIR -type d -empty -delete 2>/dev/null
+ rmdir $PRESERVE_FILES_DIR 2>/dev/null
+fi
+
+exit 0
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+# Copyright (c) 1998-2012 Douglas Barton
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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.
diff --git a/usr.sbin/mfiutil/Makefile b/usr.sbin/mfiutil/Makefile
new file mode 100644
index 0000000..dc6f3e4
--- /dev/null
+++ b/usr.sbin/mfiutil/Makefile
@@ -0,0 +1,18 @@
+# $FreeBSD$
+PROG= mfiutil
+
+SRCS= mfiutil.c mfi_bbu.c mfi_cmd.c mfi_config.c mfi_drive.c mfi_evt.c \
+ mfi_flash.c mfi_patrol.c mfi_show.c mfi_volume.c mfi_foreign.c \
+ mfi_properties.c
+MAN8= mfiutil.8
+
+CFLAGS.gcc+= -fno-builtin-strftime
+
+LIBADD= util
+
+# Here be dragons
+.ifdef DEBUG
+CFLAGS+= -DDEBUG
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/mfiutil/Makefile.depend b/usr.sbin/mfiutil/Makefile.depend
new file mode 100644
index 0000000..58f9a33
--- /dev/null
+++ b/usr.sbin/mfiutil/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/mfiutil/mfi_bbu.c b/usr.sbin/mfiutil/mfi_bbu.c
new file mode 100644
index 0000000..db16367
--- /dev/null
+++ b/usr.sbin/mfiutil/mfi_bbu.c
@@ -0,0 +1,249 @@
+/*-
+ * Copyright (c) 2013 Sandvine 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$
+ */
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <sys/stat.h>
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include "mfiutil.h"
+
+/* The autolearn period is given in seconds. */
+void
+mfi_autolearn_period(uint32_t period, char *buf, size_t sz)
+{
+ unsigned int d, h;
+ char *tmp;
+
+ d = period / (24 * 3600);
+ h = (period % (24 * 3600)) / 3600;
+
+ tmp = buf;
+ if (d != 0) {
+ tmp += snprintf(buf, sz, "%u day%s", d, d == 1 ? "" : "s");
+ sz -= tmp - buf;
+ if (h != 0) {
+ tmp += snprintf(tmp, sz, ", ");
+ sz -= 2;
+ }
+ }
+ if (h != 0)
+ snprintf(tmp, sz, "%u hour%s", h, h == 1 ? "" : "s");
+
+ if (d == 0 && h == 0)
+ snprintf(tmp, sz, "less than 1 hour");
+}
+
+/* The time to the next relearn is given in seconds since 1/1/2000. */
+void
+mfi_next_learn_time(uint32_t next_learn_time, char *buf, size_t sz)
+{
+ time_t basetime;
+ struct tm tm;
+ size_t len;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_year = 100;
+ basetime = timegm(&tm);
+ basetime += (time_t)next_learn_time;
+ len = snprintf(buf, sz, "%s", ctime(&basetime));
+ if (len > 0)
+ /* Get rid of the newline added by ctime(3). */
+ buf[len - 1] = '\0';
+}
+
+void
+mfi_autolearn_mode(uint8_t mode, char *buf, size_t sz)
+{
+
+ switch (mode) {
+ case 0:
+ snprintf(buf, sz, "enabled");
+ break;
+ case 1:
+ snprintf(buf, sz, "disabled");
+ break;
+ case 2:
+ snprintf(buf, sz, "warn via event");
+ break;
+ default:
+ snprintf(buf, sz, "mode 0x%02x", mode);
+ break;
+ }
+}
+
+int
+mfi_bbu_get_props(int fd, struct mfi_bbu_properties *props, uint8_t *statusp)
+{
+
+ return (mfi_dcmd_command(fd, MFI_DCMD_BBU_GET_PROP, props,
+ sizeof(*props), NULL, 0, statusp));
+}
+
+int
+mfi_bbu_set_props(int fd, struct mfi_bbu_properties *props, uint8_t *statusp)
+{
+
+ return (mfi_dcmd_command(fd, MFI_DCMD_BBU_SET_PROP, props,
+ sizeof(*props), NULL, 0, statusp));
+}
+
+static int
+start_bbu_learn(int ac, char **av __unused)
+{
+ uint8_t status;
+ int error, fd;
+
+ status = MFI_STAT_OK;
+ error = 0;
+
+ if (ac != 1) {
+ warnx("start learn: unexpected arguments");
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ if (mfi_dcmd_command(fd, MFI_DCMD_BBU_START_LEARN, NULL, 0, NULL, 0,
+ &status) < 0) {
+ error = errno;
+ warn("Failed to start BBU learn");
+ } else if (status != MFI_STAT_OK) {
+ warnx("Failed to start BBU learn: %s", mfi_status(status));
+ error = EIO;
+ }
+
+ return (error);
+}
+MFI_COMMAND(start, learn, start_bbu_learn);
+
+static int
+update_bbu_props(int ac, char **av)
+{
+ struct mfi_bbu_properties props;
+ unsigned long delay;
+ uint8_t status;
+ int error, fd;
+ char *mode, *endptr;
+
+ status = MFI_STAT_OK;
+ error = 0;
+
+ if (ac != 3) {
+ warnx("bbu: property and value required");
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ if (mfi_bbu_get_props(fd, &props, &status) < 0) {
+ error = errno;
+ warn("Failed to get BBU properties");
+ goto done;
+ } else if (status != MFI_STAT_OK) {
+ warnx("Failed to get BBU properties: %s", mfi_status(status));
+ error = EIO;
+ goto done;
+ }
+
+ if (strcmp(av[1], "learn-delay") == 0) {
+ delay = strtoul(av[2], &endptr, 10);
+ if (strlen(av[2]) == 0 || *endptr != '\0' || delay > 255) {
+ warnx("Invalid learn delay '%s'", av[2]);
+ error = EINVAL;
+ goto done;
+ }
+
+ props.learn_delay_interval = delay;
+ } else if (strcmp(av[1], "autolearn-mode") == 0) {
+ mode = av[2];
+
+ if (strcmp(av[2], "enable") == 0)
+ props.auto_learn_mode = 0;
+ else if (strcmp(av[2], "disable") == 0)
+ props.auto_learn_mode = 1;
+ else if (mode[0] >= '0' && mode[0] <= '2' && mode[1] == '\0')
+ props.auto_learn_mode = mode[0] - '0';
+ else {
+ warnx("Invalid mode '%s'", mode);
+ error = EINVAL;
+ goto done;
+ }
+ } else if (strcmp(av[1], "bbu-mode") == 0) {
+ if (props.bbu_mode == 0) {
+ warnx("This BBU does not implement different modes");
+ error = EINVAL;
+ goto done;
+ }
+
+ /* The mode must be an integer between 1 and 5. */
+ mode = av[2];
+ if (mode[0] < '1' || mode[0] > '5' || mode[1] != '\0') {
+ warnx("Invalid mode '%s'", mode);
+ error = EINVAL;
+ goto done;
+ }
+
+ props.bbu_mode = mode[0] - '0';
+ } else {
+ warnx("bbu: Invalid command '%s'", av[1]);
+ error = EINVAL;
+ goto done;
+ }
+
+ if (mfi_bbu_set_props(fd, &props, &status) < 0) {
+ error = errno;
+ warn("Failed to set BBU properties");
+ goto done;
+ } else if (status != MFI_STAT_OK) {
+ warnx("Failed to set BBU properties: %s", mfi_status(status));
+ error = EIO;
+ goto done;
+ }
+
+done:
+ close(fd);
+
+ return (error);
+}
+MFI_COMMAND(top, bbu, update_bbu_props);
diff --git a/usr.sbin/mfiutil/mfi_cmd.c b/usr.sbin/mfiutil/mfi_cmd.c
new file mode 100644
index 0000000..3cf703a
--- /dev/null
+++ b/usr.sbin/mfiutil/mfi_cmd.c
@@ -0,0 +1,351 @@
+/*-
+ * Copyright (c) 2008, 2009 Yahoo!, 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.
+ * 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/errno.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <sys/uio.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mfiutil.h"
+#include <dev/mfi/mfi_ioctl.h>
+
+static const char *mfi_status_codes[] = {
+ "Command completed successfully",
+ "Invalid command",
+ "Invalid DMCD opcode",
+ "Invalid parameter",
+ "Invalid Sequence Number",
+ "Abort isn't possible for the requested command",
+ "Application 'host' code not found",
+ "Application in use",
+ "Application not initialized",
+ "Array index invalid",
+ "Array row not empty",
+ "Configuration resource conflict",
+ "Device not found",
+ "Drive too small",
+ "Flash memory allocation failed",
+ "Flash download already in progress",
+ "Flash operation failed",
+ "Bad flash image",
+ "Incomplete flash image",
+ "Flash not open",
+ "Flash not started",
+ "Flush failed",
+ "Specified application doesn't have host-resident code",
+ "Volume consistency check in progress",
+ "Volume initialization in progress",
+ "Volume LBA out of range",
+ "Maximum number of volumes are already configured",
+ "Volume is not OPTIMAL",
+ "Volume rebuild in progress",
+ "Volume reconstruction in progress",
+ "Volume RAID level is wrong for requested operation",
+ "Too many spares assigned",
+ "Scratch memory not available",
+ "Error writing MFC data to SEEPROM",
+ "Required hardware is missing",
+ "Item not found",
+ "Volume drives are not within an enclosure",
+ "Drive clear in progress",
+ "Drive type mismatch (SATA vs SAS)",
+ "Patrol read disabled",
+ "Invalid row index",
+ "SAS Config - Invalid action",
+ "SAS Config - Invalid data",
+ "SAS Config - Invalid page",
+ "SAS Config - Invalid type",
+ "SCSI command completed with error",
+ "SCSI I/O request failed",
+ "SCSI RESERVATION_CONFLICT",
+ "One or more flush operations during shutdown failed",
+ "Firmware time is not set",
+ "Wrong firmware or drive state",
+ "Volume is offline",
+ "Peer controller rejected request",
+ "Unable to inform peer of communication changes",
+ "Volume reservation already in progress",
+ "I2C errors were detected",
+ "PCI errors occurred during XOR/DMA operation",
+ "Diagnostics failed",
+ "Unable to process command as boot messages are pending",
+ "Foreign configuration is incomplete"
+};
+
+const char *
+mfi_status(u_int status_code)
+{
+ static char buffer[16];
+
+ if (status_code == MFI_STAT_INVALID_STATUS)
+ return ("Invalid status");
+ if (status_code < sizeof(mfi_status_codes) / sizeof(char *))
+ return (mfi_status_codes[status_code]);
+ snprintf(buffer, sizeof(buffer), "Status: 0x%02x", status_code);
+ return (buffer);
+}
+
+const char *
+mfi_raid_level(uint8_t primary_level, uint8_t secondary_level)
+{
+ static char buf[16];
+
+ switch (primary_level) {
+ case DDF_RAID0:
+ return ("RAID-0");
+ case DDF_RAID1:
+ if (secondary_level != 0)
+ return ("RAID-10");
+ else
+ return ("RAID-1");
+ case DDF_RAID1E:
+ return ("RAID-1E");
+ case DDF_RAID3:
+ return ("RAID-3");
+ case DDF_RAID5:
+ if (secondary_level != 0)
+ return ("RAID-50");
+ else
+ return ("RAID-5");
+ case DDF_RAID5E:
+ return ("RAID-5E");
+ case DDF_RAID5EE:
+ return ("RAID-5EE");
+ case DDF_RAID6:
+ if (secondary_level != 0)
+ return ("RAID-60");
+ else
+ return ("RAID-6");
+ case DDF_JBOD:
+ return ("JBOD");
+ case DDF_CONCAT:
+ return ("CONCAT");
+ default:
+ sprintf(buf, "LVL 0x%02x", primary_level);
+ return (buf);
+ }
+}
+
+static int
+mfi_query_disk(int fd, uint8_t target_id, struct mfi_query_disk *info)
+{
+
+ bzero(info, sizeof(*info));
+ info->array_id = target_id;
+ if (ioctl(fd, MFIIO_QUERY_DISK, info) < 0)
+ return (-1);
+ if (!info->present) {
+ errno = ENXIO;
+ return (-1);
+ }
+ return (0);
+}
+
+const char *
+mfi_volume_name(int fd, uint8_t target_id)
+{
+ static struct mfi_query_disk info;
+ static char buf[4];
+
+ if (mfi_query_disk(fd, target_id, &info) < 0) {
+ snprintf(buf, sizeof(buf), "%d", target_id);
+ return (buf);
+ }
+ return (info.devname);
+}
+
+int
+mfi_volume_busy(int fd, uint8_t target_id)
+{
+ struct mfi_query_disk info;
+
+ /* Assume it isn't mounted if we can't get information. */
+ if (mfi_query_disk(fd, target_id, &info) < 0)
+ return (0);
+ return (info.open != 0);
+}
+
+/*
+ * Check if the running kernel supports changing the RAID
+ * configuration of the mfi controller.
+ */
+int
+mfi_reconfig_supported(void)
+{
+ char mibname[64];
+ size_t len;
+ int dummy;
+
+ len = sizeof(dummy);
+ snprintf(mibname, sizeof(mibname), "dev.mfi.%d.delete_busy_volumes",
+ mfi_unit);
+ return (sysctlbyname(mibname, &dummy, &len, NULL, 0) == 0);
+}
+
+int
+mfi_lookup_volume(int fd, const char *name, uint8_t *target_id)
+{
+ struct mfi_query_disk info;
+ struct mfi_ld_list list;
+ char *cp;
+ long val;
+ u_int i;
+
+ /* If it's a valid number, treat it as a raw target ID. */
+ val = strtol(name, &cp, 0);
+ if (*cp == '\0') {
+ *target_id = val;
+ return (0);
+ }
+
+ if (mfi_dcmd_command(fd, MFI_DCMD_LD_GET_LIST, &list, sizeof(list),
+ NULL, 0, NULL) < 0)
+ return (-1);
+
+ for (i = 0; i < list.ld_count; i++) {
+ if (mfi_query_disk(fd, list.ld_list[i].ld.v.target_id,
+ &info) < 0)
+ continue;
+ if (strcmp(name, info.devname) == 0) {
+ *target_id = list.ld_list[i].ld.v.target_id;
+ return (0);
+ }
+ }
+ errno = EINVAL;
+ return (-1);
+}
+
+int
+mfi_dcmd_command(int fd, uint32_t opcode, void *buf, size_t bufsize,
+ uint8_t *mbox, size_t mboxlen, uint8_t *statusp)
+{
+ struct mfi_ioc_passthru ioc;
+ struct mfi_dcmd_frame *dcmd;
+ int r;
+
+ if ((mbox != NULL && (mboxlen == 0 || mboxlen > MFI_MBOX_SIZE)) ||
+ (mbox == NULL && mboxlen != 0)) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ bzero(&ioc, sizeof(ioc));
+ dcmd = &ioc.ioc_frame;
+ if (mbox)
+ bcopy(mbox, dcmd->mbox, mboxlen);
+ dcmd->header.cmd = MFI_CMD_DCMD;
+ dcmd->header.timeout = 0;
+ dcmd->header.flags = 0;
+ dcmd->header.data_len = bufsize;
+ dcmd->opcode = opcode;
+
+ ioc.buf = buf;
+ ioc.buf_size = bufsize;
+ r = ioctl(fd, MFIIO_PASSTHRU, &ioc);
+ if (r < 0)
+ return (r);
+
+ if (statusp != NULL)
+ *statusp = dcmd->header.cmd_status;
+ else if (dcmd->header.cmd_status != MFI_STAT_OK) {
+ warnx("Command failed: %s",
+ mfi_status(dcmd->header.cmd_status));
+ errno = EIO;
+ return (-1);
+ }
+ return (0);
+}
+
+int
+mfi_ctrl_get_info(int fd, struct mfi_ctrl_info *info, uint8_t *statusp)
+{
+
+ return (mfi_dcmd_command(fd, MFI_DCMD_CTRL_GETINFO, info,
+ sizeof(struct mfi_ctrl_info), NULL, 0, statusp));
+}
+
+int
+mfi_open(int unit, int acs)
+{
+ char path[MAXPATHLEN];
+
+ snprintf(path, sizeof(path), "/dev/mfi%d", unit);
+ return (open(path, acs));
+}
+
+void
+mfi_display_progress(const char *label, struct mfi_progress *prog)
+{
+ uint seconds;
+
+ printf("%s: %.2f%% complete, after %ds", label,
+ (float)prog->progress * 100 / 0xffff, prog->elapsed_seconds);
+ if (prog->progress != 0 && prog->elapsed_seconds > 10) {
+ printf(" finished in ");
+ seconds = (0x10000 * (uint32_t)prog->elapsed_seconds) /
+ prog->progress - prog->elapsed_seconds;
+ if (seconds > 3600)
+ printf("%u:", seconds / 3600);
+ if (seconds > 60) {
+ seconds %= 3600;
+ printf("%02u:%02u", seconds / 60, seconds % 60);
+ } else
+ printf("%us", seconds);
+ }
+ printf("\n");
+}
+
+int
+mfi_table_handler(struct mfiutil_command **start, struct mfiutil_command **end,
+ int ac, char **av)
+{
+ struct mfiutil_command **cmd;
+
+ if (ac < 2) {
+ warnx("The %s command requires a sub-command.", av[0]);
+ return (EINVAL);
+ }
+ for (cmd = start; cmd < end; cmd++) {
+ if (strcmp((*cmd)->name, av[1]) == 0)
+ return ((*cmd)->handler(ac - 1, av + 1));
+ }
+
+ warnx("%s is not a valid sub-command of %s.", av[1], av[0]);
+ return (ENOENT);
+}
diff --git a/usr.sbin/mfiutil/mfi_config.c b/usr.sbin/mfiutil/mfi_config.c
new file mode 100644
index 0000000..a919214
--- /dev/null
+++ b/usr.sbin/mfiutil/mfi_config.c
@@ -0,0 +1,1287 @@
+/*-
+ * Copyright (c) 2008, 2009 Yahoo!, 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.
+ * 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>
+#ifdef DEBUG
+#include <sys/sysctl.h>
+#endif
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libutil.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "mfiutil.h"
+
+static int add_spare(int ac, char **av);
+static int remove_spare(int ac, char **av);
+
+static long
+dehumanize(const char *value)
+{
+ char *vtp;
+ long iv;
+
+ if (value == NULL)
+ return (0);
+ iv = strtoq(value, &vtp, 0);
+ if (vtp == value || (vtp[0] != '\0' && vtp[1] != '\0')) {
+ return (0);
+ }
+ switch (vtp[0]) {
+ case 't': case 'T':
+ iv *= 1024;
+ case 'g': case 'G':
+ iv *= 1024;
+ case 'm': case 'M':
+ iv *= 1024;
+ case 'k': case 'K':
+ iv *= 1024;
+ case '\0':
+ break;
+ default:
+ return (0);
+ }
+ return (iv);
+}
+
+int
+mfi_config_read(int fd, struct mfi_config_data **configp)
+{
+ return mfi_config_read_opcode(fd, MFI_DCMD_CFG_READ, configp, NULL, 0);
+}
+
+int
+mfi_config_read_opcode(int fd, uint32_t opcode, struct mfi_config_data **configp,
+ uint8_t *mbox, size_t mboxlen)
+{
+ struct mfi_config_data *config;
+ uint32_t config_size;
+ int error;
+
+ /*
+ * Keep fetching the config in a loop until we have a large enough
+ * buffer to hold the entire configuration.
+ */
+ config = NULL;
+ config_size = 1024;
+fetch:
+ config = reallocf(config, config_size);
+ if (config == NULL)
+ return (-1);
+ if (mfi_dcmd_command(fd, opcode, config,
+ config_size, mbox, mboxlen, NULL) < 0) {
+ error = errno;
+ free(config);
+ errno = error;
+ return (-1);
+ }
+
+ if (config->size > config_size) {
+ config_size = config->size;
+ goto fetch;
+ }
+
+ *configp = config;
+ return (0);
+}
+
+static struct mfi_array *
+mfi_config_lookup_array(struct mfi_config_data *config, uint16_t array_ref)
+{
+ struct mfi_array *ar;
+ char *p;
+ int i;
+
+ p = (char *)config->array;
+ for (i = 0; i < config->array_count; i++) {
+ ar = (struct mfi_array *)p;
+ if (ar->array_ref == array_ref)
+ return (ar);
+ p += config->array_size;
+ }
+
+ return (NULL);
+}
+
+static struct mfi_ld_config *
+mfi_config_lookup_volume(struct mfi_config_data *config, uint8_t target_id)
+{
+ struct mfi_ld_config *ld;
+ char *p;
+ int i;
+
+ p = (char *)config->array + config->array_count * config->array_size;
+ for (i = 0; i < config->log_drv_count; i++) {
+ ld = (struct mfi_ld_config *)p;
+ if (ld->properties.ld.v.target_id == target_id)
+ return (ld);
+ p += config->log_drv_size;
+ }
+
+ return (NULL);
+}
+
+static int
+clear_config(int ac __unused, char **av __unused)
+{
+ struct mfi_ld_list list;
+ int ch, error, fd;
+ u_int i;
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ if (!mfi_reconfig_supported()) {
+ warnx("The current mfi(4) driver does not support "
+ "configuration changes.");
+ close(fd);
+ return (EOPNOTSUPP);
+ }
+
+ if (mfi_ld_get_list(fd, &list, NULL) < 0) {
+ error = errno;
+ warn("Failed to get volume list");
+ close(fd);
+ return (error);
+ }
+
+ for (i = 0; i < list.ld_count; i++) {
+ if (mfi_volume_busy(fd, list.ld_list[i].ld.v.target_id)) {
+ warnx("Volume %s is busy and cannot be deleted",
+ mfi_volume_name(fd, list.ld_list[i].ld.v.target_id));
+ close(fd);
+ return (EBUSY);
+ }
+ }
+
+ printf(
+ "Are you sure you wish to clear the configuration on mfi%u? [y/N] ",
+ mfi_unit);
+ ch = getchar();
+ if (ch != 'y' && ch != 'Y') {
+ printf("\nAborting\n");
+ close(fd);
+ return (0);
+ }
+
+ if (mfi_dcmd_command(fd, MFI_DCMD_CFG_CLEAR, NULL, 0, NULL, 0, NULL) < 0) {
+ error = errno;
+ warn("Failed to clear configuration");
+ close(fd);
+ return (error);
+ }
+
+ printf("mfi%d: Configuration cleared\n", mfi_unit);
+ close(fd);
+
+ return (0);
+}
+MFI_COMMAND(top, clear, clear_config);
+
+#define MAX_DRIVES_PER_ARRAY MFI_MAX_ROW_SIZE
+#define MFI_ARRAY_SIZE sizeof(struct mfi_array)
+
+#define RT_RAID0 0
+#define RT_RAID1 1
+#define RT_RAID5 2
+#define RT_RAID6 3
+#define RT_JBOD 4
+#define RT_CONCAT 5
+#define RT_RAID10 6
+#define RT_RAID50 7
+#define RT_RAID60 8
+
+static int
+compare_int(const void *one, const void *two)
+{
+ int first, second;
+
+ first = *(const int *)one;
+ second = *(const int *)two;
+
+ return (first - second);
+}
+
+static struct raid_type_entry {
+ const char *name;
+ int raid_type;
+} raid_type_table[] = {
+ { "raid0", RT_RAID0 },
+ { "raid-0", RT_RAID0 },
+ { "raid1", RT_RAID1 },
+ { "raid-1", RT_RAID1 },
+ { "mirror", RT_RAID1 },
+ { "raid5", RT_RAID5 },
+ { "raid-5", RT_RAID5 },
+ { "raid6", RT_RAID6 },
+ { "raid-6", RT_RAID6 },
+ { "jbod", RT_JBOD },
+ { "concat", RT_CONCAT },
+ { "raid10", RT_RAID10 },
+ { "raid1+0", RT_RAID10 },
+ { "raid-10", RT_RAID10 },
+ { "raid-1+0", RT_RAID10 },
+ { "raid50", RT_RAID50 },
+ { "raid5+0", RT_RAID50 },
+ { "raid-50", RT_RAID50 },
+ { "raid-5+0", RT_RAID50 },
+ { "raid60", RT_RAID60 },
+ { "raid6+0", RT_RAID60 },
+ { "raid-60", RT_RAID60 },
+ { "raid-6+0", RT_RAID60 },
+ { NULL, 0 },
+};
+
+struct config_id_state {
+ int array_count;
+ int log_drv_count;
+ int *arrays;
+ int *volumes;
+ uint16_t array_ref;
+ uint8_t target_id;
+};
+
+struct array_info {
+ int drive_count;
+ struct mfi_pd_info *drives;
+ struct mfi_array *array;
+};
+
+/* Parse a comma-separated list of drives for an array. */
+static int
+parse_array(int fd, int raid_type, char *array_str, struct array_info *info)
+{
+ struct mfi_pd_info *pinfo;
+ uint16_t device_id;
+ char *cp;
+ u_int count;
+ int error;
+
+ cp = array_str;
+ for (count = 0; cp != NULL; count++) {
+ cp = strchr(cp, ',');
+ if (cp != NULL) {
+ cp++;
+ if (*cp == ',') {
+ warnx("Invalid drive list '%s'", array_str);
+ return (EINVAL);
+ }
+ }
+ }
+
+ /* Validate the number of drives for this array. */
+ if (count >= MAX_DRIVES_PER_ARRAY) {
+ warnx("Too many drives for a single array: max is %d",
+ MAX_DRIVES_PER_ARRAY);
+ return (EINVAL);
+ }
+ switch (raid_type) {
+ case RT_RAID1:
+ case RT_RAID10:
+ if (count % 2 != 0) {
+ warnx("RAID1 and RAID10 require an even number of "
+ "drives in each array");
+ return (EINVAL);
+ }
+ break;
+ case RT_RAID5:
+ case RT_RAID50:
+ if (count < 3) {
+ warnx("RAID5 and RAID50 require at least 3 drives in "
+ "each array");
+ return (EINVAL);
+ }
+ break;
+ case RT_RAID6:
+ case RT_RAID60:
+ if (count < 4) {
+ warnx("RAID6 and RAID60 require at least 4 drives in "
+ "each array");
+ return (EINVAL);
+ }
+ break;
+ }
+
+ /* Validate each drive. */
+ info->drives = calloc(count, sizeof(struct mfi_pd_info));
+ if (info->drives == NULL) {
+ warnx("malloc failed");
+ return (ENOMEM);
+ }
+ info->drive_count = count;
+ for (pinfo = info->drives; (cp = strsep(&array_str, ",")) != NULL;
+ pinfo++) {
+ error = mfi_lookup_drive(fd, cp, &device_id);
+ if (error) {
+ free(info->drives);
+ info->drives = NULL;
+ return (error);
+ }
+
+ if (mfi_pd_get_info(fd, device_id, pinfo, NULL) < 0) {
+ error = errno;
+ warn("Failed to fetch drive info for drive %s", cp);
+ free(info->drives);
+ info->drives = NULL;
+ return (error);
+ }
+
+ if (pinfo->fw_state != MFI_PD_STATE_UNCONFIGURED_GOOD) {
+ warnx("Drive %u is not available", device_id);
+ free(info->drives);
+ info->drives = NULL;
+ return (EINVAL);
+ }
+
+ if (pinfo->state.ddf.v.pd_type.is_foreign) {
+ warnx("Drive %u is foreign", device_id);
+ free(info->drives);
+ info->drives = NULL;
+ return (EINVAL);
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * Find the next free array ref assuming that 'array_ref' is the last
+ * one used. 'array_ref' should be 0xffff for the initial test.
+ */
+static uint16_t
+find_next_array(struct config_id_state *state)
+{
+ int i;
+
+ /* Assume the current one is used. */
+ state->array_ref++;
+
+ /* Find the next free one. */
+ for (i = 0; i < state->array_count; i++)
+ if (state->arrays[i] == state->array_ref)
+ state->array_ref++;
+ return (state->array_ref);
+}
+
+/*
+ * Find the next free volume ID assuming that 'target_id' is the last
+ * one used. 'target_id' should be 0xff for the initial test.
+ */
+static uint8_t
+find_next_volume(struct config_id_state *state)
+{
+ int i;
+
+ /* Assume the current one is used. */
+ state->target_id++;
+
+ /* Find the next free one. */
+ for (i = 0; i < state->log_drv_count; i++)
+ if (state->volumes[i] == state->target_id)
+ state->target_id++;
+ return (state->target_id);
+}
+
+/* Populate an array with drives. */
+static void
+build_array(int fd __unused, char *arrayp, struct array_info *array_info,
+ struct config_id_state *state, int verbose)
+{
+ struct mfi_array *ar = (struct mfi_array *)arrayp;
+ int i;
+
+ ar->size = array_info->drives[0].coerced_size;
+ ar->num_drives = array_info->drive_count;
+ ar->array_ref = find_next_array(state);
+ for (i = 0; i < array_info->drive_count; i++) {
+ if (verbose)
+ printf("Adding drive %s to array %u\n",
+ mfi_drive_name(NULL,
+ array_info->drives[i].ref.v.device_id,
+ MFI_DNAME_DEVICE_ID|MFI_DNAME_HONOR_OPTS),
+ ar->array_ref);
+ if (ar->size > array_info->drives[i].coerced_size)
+ ar->size = array_info->drives[i].coerced_size;
+ ar->pd[i].ref = array_info->drives[i].ref;
+ ar->pd[i].fw_state = MFI_PD_STATE_ONLINE;
+ }
+ array_info->array = ar;
+}
+
+/*
+ * Create a volume that spans one or more arrays.
+ */
+static void
+build_volume(char *volumep, int narrays, struct array_info *arrays,
+ int raid_type, long stripe_size, struct config_id_state *state, int verbose)
+{
+ struct mfi_ld_config *ld = (struct mfi_ld_config *)volumep;
+ struct mfi_array *ar;
+ int i;
+
+ /* properties */
+ ld->properties.ld.v.target_id = find_next_volume(state);
+ ld->properties.ld.v.seq = 0;
+ ld->properties.default_cache_policy = MR_LD_CACHE_ALLOW_WRITE_CACHE |
+ MR_LD_CACHE_WRITE_BACK;
+ ld->properties.access_policy = MFI_LD_ACCESS_RW;
+ ld->properties.disk_cache_policy = MR_PD_CACHE_UNCHANGED;
+ ld->properties.current_cache_policy = MR_LD_CACHE_ALLOW_WRITE_CACHE |
+ MR_LD_CACHE_WRITE_BACK;
+ ld->properties.no_bgi = 0;
+
+ /* params */
+ switch (raid_type) {
+ case RT_RAID0:
+ case RT_JBOD:
+ ld->params.primary_raid_level = DDF_RAID0;
+ ld->params.raid_level_qualifier = 0;
+ ld->params.secondary_raid_level = 0;
+ break;
+ case RT_RAID1:
+ ld->params.primary_raid_level = DDF_RAID1;
+ ld->params.raid_level_qualifier = 0;
+ ld->params.secondary_raid_level = 0;
+ break;
+ case RT_RAID5:
+ ld->params.primary_raid_level = DDF_RAID5;
+ ld->params.raid_level_qualifier = 3;
+ ld->params.secondary_raid_level = 0;
+ break;
+ case RT_RAID6:
+ ld->params.primary_raid_level = DDF_RAID6;
+ ld->params.raid_level_qualifier = 3;
+ ld->params.secondary_raid_level = 0;
+ break;
+ case RT_CONCAT:
+ ld->params.primary_raid_level = DDF_CONCAT;
+ ld->params.raid_level_qualifier = 0;
+ ld->params.secondary_raid_level = 0;
+ break;
+ case RT_RAID10:
+ ld->params.primary_raid_level = DDF_RAID1;
+ ld->params.raid_level_qualifier = 0;
+ ld->params.secondary_raid_level = 3; /* XXX? */
+ break;
+ case RT_RAID50:
+ /*
+ * XXX: This appears to work though the card's BIOS
+ * complains that the configuration is foreign. The
+ * BIOS setup does not allow for creation of RAID-50
+ * or RAID-60 arrays. The only nested array
+ * configuration it allows for is RAID-10.
+ */
+ ld->params.primary_raid_level = DDF_RAID5;
+ ld->params.raid_level_qualifier = 3;
+ ld->params.secondary_raid_level = 3; /* XXX? */
+ break;
+ case RT_RAID60:
+ ld->params.primary_raid_level = DDF_RAID6;
+ ld->params.raid_level_qualifier = 3;
+ ld->params.secondary_raid_level = 3; /* XXX? */
+ break;
+ }
+
+ /*
+ * Stripe size is encoded as (2 ^ N) * 512 = stripe_size. Use
+ * ffs() to simulate log2(stripe_size).
+ */
+ ld->params.stripe_size = ffs(stripe_size) - 1 - 9;
+ ld->params.num_drives = arrays[0].array->num_drives;
+ ld->params.span_depth = narrays;
+ ld->params.state = MFI_LD_STATE_OPTIMAL;
+ ld->params.init_state = MFI_LD_PARAMS_INIT_NO;
+ ld->params.is_consistent = 0;
+
+ /* spans */
+ for (i = 0; i < narrays; i++) {
+ ar = arrays[i].array;
+ if (verbose)
+ printf("Adding array %u to volume %u\n", ar->array_ref,
+ ld->properties.ld.v.target_id);
+ ld->span[i].start_block = 0;
+ ld->span[i].num_blocks = ar->size;
+ ld->span[i].array_ref = ar->array_ref;
+ }
+}
+
+static int
+create_volume(int ac, char **av)
+{
+ struct mfi_config_data *config;
+ struct mfi_array *ar;
+ struct mfi_ld_config *ld;
+ struct config_id_state state;
+ size_t config_size;
+ char *p, *cfg_arrays, *cfg_volumes;
+ int error, fd, i, raid_type;
+ int narrays, nvolumes, arrays_per_volume;
+ struct array_info *arrays;
+ long stripe_size;
+#ifdef DEBUG
+ int dump;
+#endif
+ int ch, verbose;
+
+ /*
+ * Backwards compat. Map 'create volume' to 'create' and
+ * 'create spare' to 'add'.
+ */
+ if (ac > 1) {
+ if (strcmp(av[1], "volume") == 0) {
+ av++;
+ ac--;
+ } else if (strcmp(av[1], "spare") == 0) {
+ av++;
+ ac--;
+ return (add_spare(ac, av));
+ }
+ }
+
+ if (ac < 2) {
+ warnx("create volume: volume type required");
+ return (EINVAL);
+ }
+
+ bzero(&state, sizeof(state));
+ config = NULL;
+ arrays = NULL;
+ narrays = 0;
+ error = 0;
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ if (!mfi_reconfig_supported()) {
+ warnx("The current mfi(4) driver does not support "
+ "configuration changes.");
+ error = EOPNOTSUPP;
+ goto error;
+ }
+
+ /* Lookup the RAID type first. */
+ raid_type = -1;
+ for (i = 0; raid_type_table[i].name != NULL; i++)
+ if (strcasecmp(raid_type_table[i].name, av[1]) == 0) {
+ raid_type = raid_type_table[i].raid_type;
+ break;
+ }
+
+ if (raid_type == -1) {
+ warnx("Unknown or unsupported volume type %s", av[1]);
+ error = EINVAL;
+ goto error;
+ }
+
+ /* Parse any options. */
+ optind = 2;
+#ifdef DEBUG
+ dump = 0;
+#endif
+ verbose = 0;
+ stripe_size = 64 * 1024;
+
+ while ((ch = getopt(ac, av, "ds:v")) != -1) {
+ switch (ch) {
+#ifdef DEBUG
+ case 'd':
+ dump = 1;
+ break;
+#endif
+ case 's':
+ stripe_size = dehumanize(optarg);
+ if ((stripe_size < 512) || (!powerof2(stripe_size)))
+ stripe_size = 64 * 1024;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ case '?':
+ default:
+ error = EINVAL;
+ goto error;
+ }
+ }
+ ac -= optind;
+ av += optind;
+
+ /* Parse all the arrays. */
+ narrays = ac;
+ if (narrays == 0) {
+ warnx("At least one drive list is required");
+ error = EINVAL;
+ goto error;
+ }
+ switch (raid_type) {
+ case RT_RAID0:
+ case RT_RAID1:
+ case RT_RAID5:
+ case RT_RAID6:
+ case RT_CONCAT:
+ if (narrays != 1) {
+ warnx("Only one drive list can be specified");
+ error = EINVAL;
+ goto error;
+ }
+ break;
+ case RT_RAID10:
+ case RT_RAID50:
+ case RT_RAID60:
+ if (narrays < 1) {
+ warnx("RAID10, RAID50, and RAID60 require at least "
+ "two drive lists");
+ error = EINVAL;
+ goto error;
+ }
+ if (narrays > MFI_MAX_SPAN_DEPTH) {
+ warnx("Volume spans more than %d arrays",
+ MFI_MAX_SPAN_DEPTH);
+ error = EINVAL;
+ goto error;
+ }
+ break;
+ }
+ arrays = calloc(narrays, sizeof(*arrays));
+ if (arrays == NULL) {
+ warnx("malloc failed");
+ error = ENOMEM;
+ goto error;
+ }
+ for (i = 0; i < narrays; i++) {
+ error = parse_array(fd, raid_type, av[i], &arrays[i]);
+ if (error)
+ goto error;
+ }
+
+ switch (raid_type) {
+ case RT_RAID10:
+ case RT_RAID50:
+ case RT_RAID60:
+ for (i = 1; i < narrays; i++) {
+ if (arrays[i].drive_count != arrays[0].drive_count) {
+ warnx("All arrays must contain the same "
+ "number of drives");
+ error = EINVAL;
+ goto error;
+ }
+ }
+ break;
+ }
+
+ /*
+ * Fetch the current config and build sorted lists of existing
+ * array and volume identifiers.
+ */
+ if (mfi_config_read(fd, &config) < 0) {
+ error = errno;
+ warn("Failed to read configuration");
+ goto error;
+ }
+ p = (char *)config->array;
+ state.array_ref = 0xffff;
+ state.target_id = 0xff;
+ state.array_count = config->array_count;
+ if (config->array_count > 0) {
+ state.arrays = calloc(config->array_count, sizeof(int));
+ if (state.arrays == NULL) {
+ warnx("malloc failed");
+ error = ENOMEM;
+ goto error;
+ }
+ for (i = 0; i < config->array_count; i++) {
+ ar = (struct mfi_array *)p;
+ state.arrays[i] = ar->array_ref;
+ p += config->array_size;
+ }
+ qsort(state.arrays, config->array_count, sizeof(int),
+ compare_int);
+ } else
+ state.arrays = NULL;
+ state.log_drv_count = config->log_drv_count;
+ if (config->log_drv_count) {
+ state.volumes = calloc(config->log_drv_count, sizeof(int));
+ if (state.volumes == NULL) {
+ warnx("malloc failed");
+ error = ENOMEM;
+ goto error;
+ }
+ for (i = 0; i < config->log_drv_count; i++) {
+ ld = (struct mfi_ld_config *)p;
+ state.volumes[i] = ld->properties.ld.v.target_id;
+ p += config->log_drv_size;
+ }
+ qsort(state.volumes, config->log_drv_count, sizeof(int),
+ compare_int);
+ } else
+ state.volumes = NULL;
+ free(config);
+
+ /* Determine the size of the configuration we will build. */
+ switch (raid_type) {
+ case RT_RAID0:
+ case RT_RAID1:
+ case RT_RAID5:
+ case RT_RAID6:
+ case RT_CONCAT:
+ case RT_JBOD:
+ /* Each volume spans a single array. */
+ nvolumes = narrays;
+ break;
+ case RT_RAID10:
+ case RT_RAID50:
+ case RT_RAID60:
+ /* A single volume spans multiple arrays. */
+ nvolumes = 1;
+ break;
+ default:
+ /* Pacify gcc. */
+ abort();
+ }
+
+ config_size = sizeof(struct mfi_config_data) +
+ sizeof(struct mfi_ld_config) * nvolumes + MFI_ARRAY_SIZE * narrays;
+ config = calloc(1, config_size);
+ if (config == NULL) {
+ warnx("malloc failed");
+ error = ENOMEM;
+ goto error;
+ }
+ config->size = config_size;
+ config->array_count = narrays;
+ config->array_size = MFI_ARRAY_SIZE; /* XXX: Firmware hardcode */
+ config->log_drv_count = nvolumes;
+ config->log_drv_size = sizeof(struct mfi_ld_config);
+ config->spares_count = 0;
+ config->spares_size = 40; /* XXX: Firmware hardcode */
+ cfg_arrays = (char *)config->array;
+ cfg_volumes = cfg_arrays + config->array_size * narrays;
+
+ /* Build the arrays. */
+ for (i = 0; i < narrays; i++) {
+ build_array(fd, cfg_arrays, &arrays[i], &state, verbose);
+ cfg_arrays += config->array_size;
+ }
+
+ /* Now build the volume(s). */
+ arrays_per_volume = narrays / nvolumes;
+ for (i = 0; i < nvolumes; i++) {
+ build_volume(cfg_volumes, arrays_per_volume,
+ &arrays[i * arrays_per_volume], raid_type, stripe_size,
+ &state, verbose);
+ cfg_volumes += config->log_drv_size;
+ }
+
+#ifdef DEBUG
+ if (dump)
+ dump_config(fd, config, NULL);
+#endif
+
+ /* Send the new config to the controller. */
+ if (mfi_dcmd_command(fd, MFI_DCMD_CFG_ADD, config, config_size,
+ NULL, 0, NULL) < 0) {
+ error = errno;
+ warn("Failed to add volume");
+ /* FALLTHROUGH */
+ }
+
+error:
+ /* Clean up. */
+ free(config);
+ free(state.volumes);
+ free(state.arrays);
+ if (arrays != NULL) {
+ for (i = 0; i < narrays; i++)
+ free(arrays[i].drives);
+ free(arrays);
+ }
+ close(fd);
+
+ return (error);
+}
+MFI_COMMAND(top, create, create_volume);
+
+static int
+delete_volume(int ac, char **av)
+{
+ struct mfi_ld_info info;
+ int error, fd;
+ uint8_t target_id, mbox[4];
+
+ /*
+ * Backwards compat. Map 'delete volume' to 'delete' and
+ * 'delete spare' to 'remove'.
+ */
+ if (ac > 1) {
+ if (strcmp(av[1], "volume") == 0) {
+ av++;
+ ac--;
+ } else if (strcmp(av[1], "spare") == 0) {
+ av++;
+ ac--;
+ return (remove_spare(ac, av));
+ }
+ }
+
+ if (ac != 2) {
+ warnx("delete volume: volume required");
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ if (!mfi_reconfig_supported()) {
+ warnx("The current mfi(4) driver does not support "
+ "configuration changes.");
+ close(fd);
+ return (EOPNOTSUPP);
+ }
+
+ if (mfi_lookup_volume(fd, av[1], &target_id) < 0) {
+ error = errno;
+ warn("Invalid volume %s", av[1]);
+ close(fd);
+ return (error);
+ }
+
+ if (mfi_ld_get_info(fd, target_id, &info, NULL) < 0) {
+ error = errno;
+ warn("Failed to get info for volume %d", target_id);
+ close(fd);
+ return (error);
+ }
+
+ if (mfi_volume_busy(fd, target_id)) {
+ warnx("Volume %s is busy and cannot be deleted",
+ mfi_volume_name(fd, target_id));
+ close(fd);
+ return (EBUSY);
+ }
+
+ mbox_store_ldref(mbox, &info.ld_config.properties.ld);
+ if (mfi_dcmd_command(fd, MFI_DCMD_LD_DELETE, NULL, 0, mbox,
+ sizeof(mbox), NULL) < 0) {
+ error = errno;
+ warn("Failed to delete volume");
+ close(fd);
+ return (error);
+ }
+
+ close(fd);
+
+ return (0);
+}
+MFI_COMMAND(top, delete, delete_volume);
+
+static int
+add_spare(int ac, char **av)
+{
+ struct mfi_pd_info info;
+ struct mfi_config_data *config;
+ struct mfi_array *ar;
+ struct mfi_ld_config *ld;
+ struct mfi_spare *spare;
+ uint16_t device_id;
+ uint8_t target_id;
+ char *p;
+ int error, fd, i;
+
+ if (ac < 2) {
+ warnx("add spare: drive required");
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ config = NULL;
+ spare = NULL;
+ error = mfi_lookup_drive(fd, av[1], &device_id);
+ if (error)
+ goto error;
+
+ if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) {
+ error = errno;
+ warn("Failed to fetch drive info");
+ goto error;
+ }
+
+ if (info.fw_state != MFI_PD_STATE_UNCONFIGURED_GOOD) {
+ warnx("Drive %u is not available", device_id);
+ error = EINVAL;
+ goto error;
+ }
+
+ if (ac > 2) {
+ if (mfi_lookup_volume(fd, av[2], &target_id) < 0) {
+ error = errno;
+ warn("Invalid volume %s", av[2]);
+ goto error;
+ }
+ }
+
+ if (mfi_config_read(fd, &config) < 0) {
+ error = errno;
+ warn("Failed to read configuration");
+ goto error;
+ }
+
+ spare = malloc(sizeof(struct mfi_spare) + sizeof(uint16_t) *
+ config->array_count);
+ if (spare == NULL) {
+ warnx("malloc failed");
+ error = ENOMEM;
+ goto error;
+ }
+ bzero(spare, sizeof(struct mfi_spare));
+ spare->ref = info.ref;
+
+ if (ac == 2) {
+ /* Global spare backs all arrays. */
+ p = (char *)config->array;
+ for (i = 0; i < config->array_count; i++) {
+ ar = (struct mfi_array *)p;
+ if (ar->size > info.coerced_size) {
+ warnx("Spare isn't large enough for array %u",
+ ar->array_ref);
+ error = EINVAL;
+ goto error;
+ }
+ p += config->array_size;
+ }
+ spare->array_count = 0;
+ } else {
+ /*
+ * Dedicated spares only back the arrays for a
+ * specific volume.
+ */
+ ld = mfi_config_lookup_volume(config, target_id);
+ if (ld == NULL) {
+ warnx("Did not find volume %d", target_id);
+ error = EINVAL;
+ goto error;
+ }
+
+ spare->spare_type |= MFI_SPARE_DEDICATED;
+ spare->array_count = ld->params.span_depth;
+ for (i = 0; i < ld->params.span_depth; i++) {
+ ar = mfi_config_lookup_array(config,
+ ld->span[i].array_ref);
+ if (ar == NULL) {
+ warnx("Missing array; inconsistent config?");
+ error = ENXIO;
+ goto error;
+ }
+ if (ar->size > info.coerced_size) {
+ warnx("Spare isn't large enough for array %u",
+ ar->array_ref);
+ error = EINVAL;
+ goto error;
+ }
+ spare->array_ref[i] = ar->array_ref;
+ }
+ }
+
+ if (mfi_dcmd_command(fd, MFI_DCMD_CFG_MAKE_SPARE, spare,
+ sizeof(struct mfi_spare) + sizeof(uint16_t) * spare->array_count,
+ NULL, 0, NULL) < 0) {
+ error = errno;
+ warn("Failed to assign spare");
+ /* FALLTHROUGH. */
+ }
+
+error:
+ free(spare);
+ free(config);
+ close(fd);
+
+ return (error);
+}
+MFI_COMMAND(top, add, add_spare);
+
+static int
+remove_spare(int ac, char **av)
+{
+ struct mfi_pd_info info;
+ int error, fd;
+ uint16_t device_id;
+ uint8_t mbox[4];
+
+ if (ac != 2) {
+ warnx("remove spare: drive required");
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ error = mfi_lookup_drive(fd, av[1], &device_id);
+ if (error) {
+ close(fd);
+ return (error);
+ }
+
+ /* Get the info for this drive. */
+ if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) {
+ error = errno;
+ warn("Failed to fetch info for drive %u", device_id);
+ close(fd);
+ return (error);
+ }
+
+ if (info.fw_state != MFI_PD_STATE_HOT_SPARE) {
+ warnx("Drive %u is not a hot spare", device_id);
+ close(fd);
+ return (EINVAL);
+ }
+
+ mbox_store_pdref(mbox, &info.ref);
+ if (mfi_dcmd_command(fd, MFI_DCMD_CFG_REMOVE_SPARE, NULL, 0, mbox,
+ sizeof(mbox), NULL) < 0) {
+ error = errno;
+ warn("Failed to delete spare");
+ close(fd);
+ return (error);
+ }
+
+ close(fd);
+
+ return (0);
+}
+MFI_COMMAND(top, remove, remove_spare);
+
+/* Display raw data about a config. */
+void
+dump_config(int fd, struct mfi_config_data *config, const char *msg_prefix)
+{
+ struct mfi_array *ar;
+ struct mfi_ld_config *ld;
+ struct mfi_spare *sp;
+ struct mfi_pd_info pinfo;
+ uint16_t device_id;
+ char *p;
+ int i, j;
+
+ if (NULL == msg_prefix)
+ msg_prefix = "Configuration (Debug)";
+
+ printf(
+ "mfi%d %s: %d arrays, %d volumes, %d spares\n", mfi_unit,
+ msg_prefix, config->array_count, config->log_drv_count,
+ config->spares_count);
+ printf(" array size: %u\n", config->array_size);
+ printf(" volume size: %u\n", config->log_drv_size);
+ printf(" spare size: %u\n", config->spares_size);
+ p = (char *)config->array;
+
+ for (i = 0; i < config->array_count; i++) {
+ ar = (struct mfi_array *)p;
+ printf(" array %u of %u drives:\n", ar->array_ref,
+ ar->num_drives);
+ printf(" size = %ju\n", (uintmax_t)ar->size);
+ for (j = 0; j < ar->num_drives; j++) {
+ device_id = ar->pd[j].ref.v.device_id;
+ if (device_id == 0xffff)
+ printf(" drive MISSING\n");
+ else {
+ printf(" drive %u %s\n", device_id,
+ mfi_pdstate(ar->pd[j].fw_state));
+ if (mfi_pd_get_info(fd, device_id, &pinfo,
+ NULL) >= 0) {
+ printf(" raw size: %ju\n",
+ (uintmax_t)pinfo.raw_size);
+ printf(" non-coerced size: %ju\n",
+ (uintmax_t)pinfo.non_coerced_size);
+ printf(" coerced size: %ju\n",
+ (uintmax_t)pinfo.coerced_size);
+ }
+ }
+ }
+ p += config->array_size;
+ }
+
+ for (i = 0; i < config->log_drv_count; i++) {
+ ld = (struct mfi_ld_config *)p;
+ printf(" volume %s ",
+ mfi_volume_name(fd, ld->properties.ld.v.target_id));
+ printf("%s %s",
+ mfi_raid_level(ld->params.primary_raid_level,
+ ld->params.secondary_raid_level),
+ mfi_ldstate(ld->params.state));
+ if (ld->properties.name[0] != '\0')
+ printf(" <%s>", ld->properties.name);
+ printf("\n");
+ printf(" primary raid level: %u\n",
+ ld->params.primary_raid_level);
+ printf(" raid level qualifier: %u\n",
+ ld->params.raid_level_qualifier);
+ printf(" secondary raid level: %u\n",
+ ld->params.secondary_raid_level);
+ printf(" stripe size: %u\n", ld->params.stripe_size);
+ printf(" num drives: %u\n", ld->params.num_drives);
+ printf(" init state: %u\n", ld->params.init_state);
+ printf(" consistent: %u\n", ld->params.is_consistent);
+ printf(" no bgi: %u\n", ld->properties.no_bgi);
+ printf(" spans:\n");
+ for (j = 0; j < ld->params.span_depth; j++) {
+ printf(" array %u @ ", ld->span[j].array_ref);
+ printf("%ju : %ju\n",
+ (uintmax_t)ld->span[j].start_block,
+ (uintmax_t)ld->span[j].num_blocks);
+ }
+ p += config->log_drv_size;
+ }
+
+ for (i = 0; i < config->spares_count; i++) {
+ sp = (struct mfi_spare *)p;
+ printf(" %s spare %u ",
+ sp->spare_type & MFI_SPARE_DEDICATED ? "dedicated" :
+ "global", sp->ref.v.device_id);
+ printf("%s", mfi_pdstate(MFI_PD_STATE_HOT_SPARE));
+ printf(" backs:\n");
+ for (j = 0; j < sp->array_count; j++)
+ printf(" array %u\n", sp->array_ref[j]);
+ p += config->spares_size;
+ }
+}
+
+#ifdef DEBUG
+static int
+debug_config(int ac, char **av)
+{
+ struct mfi_config_data *config;
+ int error, fd;
+
+ if (ac != 1) {
+ warnx("debug: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ /* Get the config from the controller. */
+ if (mfi_config_read(fd, &config) < 0) {
+ error = errno;
+ warn("Failed to get config");
+ close(fd);
+ return (error);
+ }
+
+ /* Dump out the configuration. */
+ dump_config(fd, config, NULL);
+ free(config);
+ close(fd);
+
+ return (0);
+}
+MFI_COMMAND(top, debug, debug_config);
+
+static int
+dump(int ac, char **av)
+{
+ struct mfi_config_data *config;
+ char buf[64];
+ size_t len;
+ int error, fd;
+
+ if (ac != 1) {
+ warnx("dump: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ /* Get the stashed copy of the last dcmd from the driver. */
+ snprintf(buf, sizeof(buf), "dev.mfi.%d.debug_command", mfi_unit);
+ if (sysctlbyname(buf, NULL, &len, NULL, 0) < 0) {
+ error = errno;
+ warn("Failed to read debug command");
+ if (error == ENOENT)
+ error = EOPNOTSUPP;
+ close(fd);
+ return (error);
+ }
+
+ config = malloc(len);
+ if (config == NULL) {
+ warnx("malloc failed");
+ close(fd);
+ return (ENOMEM);
+ }
+ if (sysctlbyname(buf, config, &len, NULL, 0) < 0) {
+ error = errno;
+ warn("Failed to read debug command");
+ free(config);
+ close(fd);
+ return (error);
+ }
+ dump_config(fd, config, NULL);
+ free(config);
+ close(fd);
+
+ return (0);
+}
+MFI_COMMAND(top, dump, dump);
+#endif
diff --git a/usr.sbin/mfiutil/mfi_drive.c b/usr.sbin/mfiutil/mfi_drive.c
new file mode 100644
index 0000000..2bc902e
--- /dev/null
+++ b/usr.sbin/mfiutil/mfi_drive.c
@@ -0,0 +1,772 @@
+/*-
+ * Copyright (c) 2008, 2009 Yahoo!, 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.
+ * 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/types.h>
+#include <sys/errno.h>
+#include <ctype.h>
+#include <err.h>
+#include <fcntl.h>
+#include <libutil.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <cam/scsi/scsi_all.h>
+#include "mfiutil.h"
+
+MFI_TABLE(top, drive);
+
+/*
+ * Print the name of a drive either by drive number as %2u or by enclosure:slot
+ * as Exx:Sxx (or both). Use default unless command line options override it
+ * and the command allows this (which we usually do unless we already print
+ * both). We prefer pinfo if given, otherwise try to look it up by device_id.
+ */
+const char *
+mfi_drive_name(struct mfi_pd_info *pinfo, uint16_t device_id, uint32_t def)
+{
+ struct mfi_pd_info info;
+ static char buf[16];
+ char *p;
+ int error, fd, len;
+
+ if ((def & MFI_DNAME_HONOR_OPTS) != 0 &&
+ (mfi_opts & (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID)) != 0)
+ def = mfi_opts & (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID);
+
+ buf[0] = '\0';
+ if (pinfo == NULL && def & MFI_DNAME_ES) {
+ /* Fallback in case of error, just ignore flags. */
+ if (device_id == 0xffff)
+ snprintf(buf, sizeof(buf), "MISSING");
+ else
+ snprintf(buf, sizeof(buf), "%2u", device_id);
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ warn("mfi_open");
+ return (buf);
+ }
+
+ /* Get the info for this drive. */
+ if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) {
+ warn("Failed to fetch info for drive %2u", device_id);
+ close(fd);
+ return (buf);
+ }
+
+ close(fd);
+ pinfo = &info;
+ }
+
+ p = buf;
+ len = sizeof(buf);
+ if (def & MFI_DNAME_DEVICE_ID) {
+ if (device_id == 0xffff)
+ error = snprintf(p, len, "MISSING");
+ else
+ error = snprintf(p, len, "%2u", device_id);
+ if (error >= 0) {
+ p += error;
+ len -= error;
+ }
+ }
+ if ((def & (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID)) ==
+ (MFI_DNAME_ES|MFI_DNAME_DEVICE_ID) && len >= 2) {
+ *p++ = ' ';
+ len--;
+ *p = '\0';
+ len--;
+ }
+ if (def & MFI_DNAME_ES) {
+ if (pinfo->encl_device_id == 0xffff)
+ error = snprintf(p, len, "S%u",
+ pinfo->slot_number);
+ else if (pinfo->encl_device_id == pinfo->ref.v.device_id)
+ error = snprintf(p, len, "E%u",
+ pinfo->encl_index);
+ else
+ error = snprintf(p, len, "E%u:S%u",
+ pinfo->encl_index, pinfo->slot_number);
+ if (error >= 0) {
+ p += error;
+ len -= error;
+ }
+ }
+
+ return (buf);
+}
+
+const char *
+mfi_pdstate(enum mfi_pd_state state)
+{
+ static char buf[16];
+
+ switch (state) {
+ case MFI_PD_STATE_UNCONFIGURED_GOOD:
+ return ("UNCONFIGURED GOOD");
+ case MFI_PD_STATE_UNCONFIGURED_BAD:
+ return ("UNCONFIGURED BAD");
+ case MFI_PD_STATE_HOT_SPARE:
+ return ("HOT SPARE");
+ case MFI_PD_STATE_OFFLINE:
+ return ("OFFLINE");
+ case MFI_PD_STATE_FAILED:
+ return ("FAILED");
+ case MFI_PD_STATE_REBUILD:
+ return ("REBUILD");
+ case MFI_PD_STATE_ONLINE:
+ return ("ONLINE");
+ case MFI_PD_STATE_COPYBACK:
+ return ("COPYBACK");
+ case MFI_PD_STATE_SYSTEM:
+ return ("JBOD");
+ default:
+ sprintf(buf, "PSTATE 0x%04x", state);
+ return (buf);
+ }
+}
+
+int
+mfi_lookup_drive(int fd, char *drive, uint16_t *device_id)
+{
+ struct mfi_pd_list *list;
+ long val;
+ int error;
+ u_int i;
+ char *cp;
+ uint8_t encl, slot;
+
+ /* Look for a raw device id first. */
+ val = strtol(drive, &cp, 0);
+ if (*cp == '\0') {
+ if (val < 0 || val >= 0xffff)
+ goto bad;
+ *device_id = val;
+ return (0);
+ }
+
+ /* Support for MegaCli style [Exx]:Syy notation. */
+ if (toupper(drive[0]) == 'E' || toupper(drive[0]) == 'S') {
+ if (drive[1] == '\0')
+ goto bad;
+ cp = drive;
+ if (toupper(drive[0]) == 'E') {
+ cp++; /* Eat 'E' */
+ val = strtol(cp, &cp, 0);
+ if (val < 0 || val > 0xff || *cp != ':')
+ goto bad;
+ encl = val;
+ cp++; /* Eat ':' */
+ if (toupper(*cp) != 'S')
+ goto bad;
+ } else
+ encl = 0xff;
+ cp++; /* Eat 'S' */
+ if (*cp == '\0')
+ goto bad;
+ val = strtol(cp, &cp, 0);
+ if (val < 0 || val > 0xff || *cp != '\0')
+ goto bad;
+ slot = val;
+
+ if (mfi_pd_get_list(fd, &list, NULL) < 0) {
+ error = errno;
+ warn("Failed to fetch drive list");
+ return (error);
+ }
+
+ for (i = 0; i < list->count; i++) {
+ if (list->addr[i].scsi_dev_type != 0)
+ continue;
+
+ if (((encl == 0xff &&
+ list->addr[i].encl_device_id == 0xffff) ||
+ list->addr[i].encl_index == encl) &&
+ list->addr[i].slot_number == slot) {
+ *device_id = list->addr[i].device_id;
+ free(list);
+ return (0);
+ }
+ }
+ free(list);
+ warnx("Unknown drive %s", drive);
+ return (EINVAL);
+ }
+
+bad:
+ warnx("Invalid drive number %s", drive);
+ return (EINVAL);
+}
+
+static void
+mbox_store_device_id(uint8_t *mbox, uint16_t device_id)
+{
+
+ mbox[0] = device_id & 0xff;
+ mbox[1] = device_id >> 8;
+}
+
+void
+mbox_store_pdref(uint8_t *mbox, union mfi_pd_ref *ref)
+{
+
+ mbox[0] = ref->v.device_id & 0xff;
+ mbox[1] = ref->v.device_id >> 8;
+ mbox[2] = ref->v.seq_num & 0xff;
+ mbox[3] = ref->v.seq_num >> 8;
+}
+
+int
+mfi_pd_get_list(int fd, struct mfi_pd_list **listp, uint8_t *statusp)
+{
+ struct mfi_pd_list *list;
+ uint32_t list_size;
+
+ /*
+ * Keep fetching the list in a loop until we have a large enough
+ * buffer to hold the entire list.
+ */
+ list = NULL;
+ list_size = 1024;
+fetch:
+ list = reallocf(list, list_size);
+ if (list == NULL)
+ return (-1);
+ if (mfi_dcmd_command(fd, MFI_DCMD_PD_GET_LIST, list, list_size, NULL,
+ 0, statusp) < 0) {
+ free(list);
+ return (-1);
+ }
+
+ if (list->size > list_size) {
+ list_size = list->size;
+ goto fetch;
+ }
+
+ *listp = list;
+ return (0);
+}
+
+int
+mfi_pd_get_info(int fd, uint16_t device_id, struct mfi_pd_info *info,
+ uint8_t *statusp)
+{
+ uint8_t mbox[2];
+
+ mbox_store_device_id(&mbox[0], device_id);
+ return (mfi_dcmd_command(fd, MFI_DCMD_PD_GET_INFO, info,
+ sizeof(struct mfi_pd_info), mbox, 2, statusp));
+}
+
+static void
+cam_strvis(char *dst, const char *src, int srclen, int dstlen)
+{
+
+ /* Trim leading/trailing spaces, nulls. */
+ while (srclen > 0 && src[0] == ' ')
+ src++, srclen--;
+ while (srclen > 0
+ && (src[srclen-1] == ' ' || src[srclen-1] == '\0'))
+ srclen--;
+
+ while (srclen > 0 && dstlen > 1) {
+ char *cur_pos = dst;
+
+ if (*src < 0x20) {
+ /* SCSI-II Specifies that these should never occur. */
+ /* non-printable character */
+ if (dstlen > 4) {
+ *cur_pos++ = '\\';
+ *cur_pos++ = ((*src & 0300) >> 6) + '0';
+ *cur_pos++ = ((*src & 0070) >> 3) + '0';
+ *cur_pos++ = ((*src & 0007) >> 0) + '0';
+ } else {
+ *cur_pos++ = '?';
+ }
+ } else {
+ /* normal character */
+ *cur_pos++ = *src;
+ }
+ src++;
+ srclen--;
+ dstlen -= cur_pos - dst;
+ dst = cur_pos;
+ }
+ *dst = '\0';
+}
+
+/* Borrowed heavily from scsi_all.c:scsi_print_inquiry(). */
+const char *
+mfi_pd_inq_string(struct mfi_pd_info *info)
+{
+ struct scsi_inquiry_data iqd, *inq_data = &iqd;
+ char vendor[16], product[48], revision[16], rstr[12], serial[SID_VENDOR_SPECIFIC_0_SIZE];
+ static char inq_string[64];
+
+ memcpy(inq_data, info->inquiry_data,
+ (sizeof (iqd) < sizeof (info->inquiry_data))?
+ sizeof (iqd) : sizeof (info->inquiry_data));
+ if (SID_QUAL_IS_VENDOR_UNIQUE(inq_data))
+ return (NULL);
+ if (SID_TYPE(inq_data) != T_DIRECT)
+ return (NULL);
+ if (SID_QUAL(inq_data) != SID_QUAL_LU_CONNECTED)
+ return (NULL);
+
+ cam_strvis(vendor, inq_data->vendor, sizeof(inq_data->vendor),
+ sizeof(vendor));
+ cam_strvis(product, inq_data->product, sizeof(inq_data->product),
+ sizeof(product));
+ cam_strvis(revision, inq_data->revision, sizeof(inq_data->revision),
+ sizeof(revision));
+ cam_strvis(serial, (char *)inq_data->vendor_specific0, sizeof(inq_data->vendor_specific0),
+ sizeof(serial));
+
+ /* Hack for SATA disks, no idea how to tell speed. */
+ if (strcmp(vendor, "ATA") == 0) {
+ snprintf(inq_string, sizeof(inq_string), "<%s %s serial=%s> SATA",
+ product, revision, serial);
+ return (inq_string);
+ }
+
+ switch (SID_ANSI_REV(inq_data)) {
+ case SCSI_REV_CCS:
+ strcpy(rstr, "SCSI-CCS");
+ break;
+ case 5:
+ strcpy(rstr, "SAS");
+ break;
+ default:
+ snprintf(rstr, sizeof (rstr), "SCSI-%d",
+ SID_ANSI_REV(inq_data));
+ break;
+ }
+ snprintf(inq_string, sizeof(inq_string), "<%s %s %s serial=%s> %s", vendor,
+ product, revision, serial, rstr);
+ return (inq_string);
+}
+
+/* Helper function to set a drive to a given state. */
+static int
+drive_set_state(char *drive, uint16_t new_state)
+{
+ struct mfi_pd_info info;
+ uint16_t device_id;
+ uint8_t mbox[6];
+ int error, fd;
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ error = mfi_lookup_drive(fd, drive, &device_id);
+ if (error) {
+ close(fd);
+ return (error);
+ }
+
+ /* Get the info for this drive. */
+ if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) {
+ error = errno;
+ warn("Failed to fetch info for drive %u", device_id);
+ close(fd);
+ return (error);
+ }
+
+ /* Try to change the state. */
+ if (info.fw_state == new_state) {
+ warnx("Drive %u is already in the desired state", device_id);
+ close(fd);
+ return (EINVAL);
+ }
+
+ mbox_store_pdref(&mbox[0], &info.ref);
+ mbox[4] = new_state & 0xff;
+ mbox[5] = new_state >> 8;
+ if (mfi_dcmd_command(fd, MFI_DCMD_PD_STATE_SET, NULL, 0, mbox, 6,
+ NULL) < 0) {
+ error = errno;
+ warn("Failed to set drive %u to %s", device_id,
+ mfi_pdstate(new_state));
+ close(fd);
+ return (error);
+ }
+
+ close(fd);
+
+ return (0);
+}
+
+static int
+fail_drive(int ac, char **av)
+{
+
+ if (ac != 2) {
+ warnx("fail: %s", ac > 2 ? "extra arguments" :
+ "drive required");
+ return (EINVAL);
+ }
+
+ return (drive_set_state(av[1], MFI_PD_STATE_FAILED));
+}
+MFI_COMMAND(top, fail, fail_drive);
+
+static int
+good_drive(int ac, char **av)
+{
+
+ if (ac != 2) {
+ warnx("good: %s", ac > 2 ? "extra arguments" :
+ "drive required");
+ return (EINVAL);
+ }
+
+ return (drive_set_state(av[1], MFI_PD_STATE_UNCONFIGURED_GOOD));
+}
+MFI_COMMAND(top, good, good_drive);
+
+static int
+rebuild_drive(int ac, char **av)
+{
+
+ if (ac != 2) {
+ warnx("rebuild: %s", ac > 2 ? "extra arguments" :
+ "drive required");
+ return (EINVAL);
+ }
+
+ return (drive_set_state(av[1], MFI_PD_STATE_REBUILD));
+}
+MFI_COMMAND(top, rebuild, rebuild_drive);
+
+static int
+syspd_drive(int ac, char **av)
+{
+
+ if (ac != 2) {
+ warnx("syspd: %s", ac > 2 ? "extra arguments" :
+ "drive required");
+ return (EINVAL);
+ }
+
+ return (drive_set_state(av[1], MFI_PD_STATE_SYSTEM));
+}
+MFI_COMMAND(top, syspd, syspd_drive);
+
+static int
+start_rebuild(int ac, char **av)
+{
+ struct mfi_pd_info info;
+ uint16_t device_id;
+ uint8_t mbox[4];
+ int error, fd;
+
+ if (ac != 2) {
+ warnx("start rebuild: %s", ac > 2 ? "extra arguments" :
+ "drive required");
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ error = mfi_lookup_drive(fd, av[1], &device_id);
+ if (error) {
+ close(fd);
+ return (error);
+ }
+
+ /* Get the info for this drive. */
+ if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) {
+ error = errno;
+ warn("Failed to fetch info for drive %u", device_id);
+ close(fd);
+ return (error);
+ }
+
+ /* Check the state, must be REBUILD. */
+ if (info.fw_state != MFI_PD_STATE_REBUILD) {
+ warnx("Drive %d is not in the REBUILD state", device_id);
+ close(fd);
+ return (EINVAL);
+ }
+
+ /* Start the rebuild. */
+ mbox_store_pdref(&mbox[0], &info.ref);
+ if (mfi_dcmd_command(fd, MFI_DCMD_PD_REBUILD_START, NULL, 0, mbox, 4,
+ NULL) < 0) {
+ error = errno;
+ warn("Failed to start rebuild on drive %u", device_id);
+ close(fd);
+ return (error);
+ }
+ close(fd);
+
+ return (0);
+}
+MFI_COMMAND(start, rebuild, start_rebuild);
+
+static int
+abort_rebuild(int ac, char **av)
+{
+ struct mfi_pd_info info;
+ uint16_t device_id;
+ uint8_t mbox[4];
+ int error, fd;
+
+ if (ac != 2) {
+ warnx("abort rebuild: %s", ac > 2 ? "extra arguments" :
+ "drive required");
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ error = mfi_lookup_drive(fd, av[1], &device_id);
+ if (error) {
+ close(fd);
+ return (error);
+ }
+
+ /* Get the info for this drive. */
+ if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) {
+ error = errno;
+ warn("Failed to fetch info for drive %u", device_id);
+ close(fd);
+ return (error);
+ }
+
+ /* Check the state, must be REBUILD. */
+ if (info.fw_state != MFI_PD_STATE_REBUILD) {
+ warn("Drive %d is not in the REBUILD state", device_id);
+ close(fd);
+ return (EINVAL);
+ }
+
+ /* Abort the rebuild. */
+ mbox_store_pdref(&mbox[0], &info.ref);
+ if (mfi_dcmd_command(fd, MFI_DCMD_PD_REBUILD_ABORT, NULL, 0, mbox, 4,
+ NULL) < 0) {
+ error = errno;
+ warn("Failed to abort rebuild on drive %u", device_id);
+ close(fd);
+ return (error);
+ }
+ close(fd);
+
+ return (0);
+}
+MFI_COMMAND(abort, rebuild, abort_rebuild);
+
+static int
+drive_progress(int ac, char **av)
+{
+ struct mfi_pd_info info;
+ uint16_t device_id;
+ int error, fd;
+
+ if (ac != 2) {
+ warnx("drive progress: %s", ac > 2 ? "extra arguments" :
+ "drive required");
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ error = mfi_lookup_drive(fd, av[1], &device_id);
+ if (error) {
+ close(fd);
+ return (error);
+ }
+
+ /* Get the info for this drive. */
+ if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) {
+ error = errno;
+ warn("Failed to fetch info for drive %u", device_id);
+ close(fd);
+ return (error);
+ }
+ close(fd);
+
+ /* Display any of the active events. */
+ if (info.prog_info.active & MFI_PD_PROGRESS_REBUILD)
+ mfi_display_progress("Rebuild", &info.prog_info.rbld);
+ if (info.prog_info.active & MFI_PD_PROGRESS_PATROL)
+ mfi_display_progress("Patrol Read", &info.prog_info.patrol);
+ if (info.prog_info.active & MFI_PD_PROGRESS_CLEAR)
+ mfi_display_progress("Clear", &info.prog_info.clear);
+ if ((info.prog_info.active & (MFI_PD_PROGRESS_REBUILD |
+ MFI_PD_PROGRESS_PATROL | MFI_PD_PROGRESS_CLEAR)) == 0)
+ printf("No activity in progress for drive %s.\n",
+ mfi_drive_name(NULL, device_id,
+ MFI_DNAME_DEVICE_ID|MFI_DNAME_HONOR_OPTS));
+
+ return (0);
+}
+MFI_COMMAND(drive, progress, drive_progress);
+
+static int
+drive_clear(int ac, char **av)
+{
+ struct mfi_pd_info info;
+ uint32_t opcode;
+ uint16_t device_id;
+ uint8_t mbox[4];
+ char *s1;
+ int error, fd;
+
+ if (ac != 3) {
+ warnx("drive clear: %s", ac > 3 ? "extra arguments" :
+ "drive and action requires");
+ return (EINVAL);
+ }
+
+ for (s1 = av[2]; *s1 != '\0'; s1++)
+ *s1 = tolower(*s1);
+ if (strcmp(av[2], "start") == 0)
+ opcode = MFI_DCMD_PD_CLEAR_START;
+ else if ((strcmp(av[2], "stop") == 0) || (strcmp(av[2], "abort") == 0))
+ opcode = MFI_DCMD_PD_CLEAR_ABORT;
+ else {
+ warnx("drive clear: invalid action, must be 'start' or 'stop'\n");
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ error = mfi_lookup_drive(fd, av[1], &device_id);
+ if (error) {
+ close(fd);
+ return (error);
+ }
+
+ /* Get the info for this drive. */
+ if (mfi_pd_get_info(fd, device_id, &info, NULL) < 0) {
+ error = errno;
+ warn("Failed to fetch info for drive %u", device_id);
+ close(fd);
+ return (error);
+ }
+
+ mbox_store_pdref(&mbox[0], &info.ref);
+ if (mfi_dcmd_command(fd, opcode, NULL, 0, mbox, 4, NULL) < 0) {
+ error = errno;
+ warn("Failed to %s clear on drive %u",
+ opcode == MFI_DCMD_PD_CLEAR_START ? "start" : "stop",
+ device_id);
+ close(fd);
+ return (error);
+ }
+
+ close(fd);
+ return (0);
+}
+MFI_COMMAND(drive, clear, drive_clear);
+
+static int
+drive_locate(int ac, char **av)
+{
+ uint16_t device_id;
+ uint32_t opcode;
+ int error, fd;
+ uint8_t mbox[4];
+
+ if (ac != 3) {
+ warnx("locate: %s", ac > 3 ? "extra arguments" :
+ "drive and state required");
+ return (EINVAL);
+ }
+
+ if (strcasecmp(av[2], "on") == 0 || strcasecmp(av[2], "start") == 0)
+ opcode = MFI_DCMD_PD_LOCATE_START;
+ else if (strcasecmp(av[2], "off") == 0 ||
+ strcasecmp(av[2], "stop") == 0)
+ opcode = MFI_DCMD_PD_LOCATE_STOP;
+ else {
+ warnx("locate: invalid state %s", av[2]);
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ error = mfi_lookup_drive(fd, av[1], &device_id);
+ if (error) {
+ close(fd);
+ return (error);
+ }
+
+
+ mbox_store_device_id(&mbox[0], device_id);
+ mbox[2] = 0;
+ mbox[3] = 0;
+ if (mfi_dcmd_command(fd, opcode, NULL, 0, mbox, 4, NULL) < 0) {
+ error = errno;
+ warn("Failed to %s locate on drive %u",
+ opcode == MFI_DCMD_PD_LOCATE_START ? "start" : "stop",
+ device_id);
+ close(fd);
+ return (error);
+ }
+ close(fd);
+
+ return (0);
+}
+MFI_COMMAND(top, locate, drive_locate);
diff --git a/usr.sbin/mfiutil/mfi_evt.c b/usr.sbin/mfiutil/mfi_evt.c
new file mode 100644
index 0000000..901c0bd
--- /dev/null
+++ b/usr.sbin/mfiutil/mfi_evt.c
@@ -0,0 +1,701 @@
+/*-
+ * Copyright (c) 2008, 2009 Yahoo!, 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.
+ * 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/types.h>
+#include <sys/errno.h>
+#include <err.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <time.h>
+#include <unistd.h>
+#include "mfiutil.h"
+
+static int
+mfi_event_get_info(int fd, struct mfi_evt_log_state *info, uint8_t *statusp)
+{
+
+ return (mfi_dcmd_command(fd, MFI_DCMD_CTRL_EVENT_GETINFO, info,
+ sizeof(struct mfi_evt_log_state), NULL, 0, statusp));
+}
+
+static int
+mfi_get_events(int fd, struct mfi_evt_list *list, int num_events,
+ union mfi_evt filter, uint32_t start_seq, uint8_t *statusp)
+{
+ uint32_t mbox[2];
+ size_t size;
+
+ mbox[0] = start_seq;
+ mbox[1] = filter.word;
+ size = sizeof(struct mfi_evt_list) + sizeof(struct mfi_evt_detail) *
+ (num_events - 1);
+ return (mfi_dcmd_command(fd, MFI_DCMD_CTRL_EVENT_GET, list, size,
+ (uint8_t *)&mbox, sizeof(mbox), statusp));
+}
+
+static int
+show_logstate(int ac, char **av __unused)
+{
+ struct mfi_evt_log_state info;
+ int error, fd;
+
+ if (ac != 1) {
+ warnx("show logstate: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ if (mfi_event_get_info(fd, &info, NULL) < 0) {
+ error = errno;
+ warn("Failed to get event log info");
+ close(fd);
+ return (error);
+ }
+
+ printf("mfi%d Event Log Sequence Numbers:\n", mfi_unit);
+ printf(" Newest Seq #: %u\n", info.newest_seq_num);
+ printf(" Oldest Seq #: %u\n", info.oldest_seq_num);
+ printf(" Clear Seq #: %u\n", info.clear_seq_num);
+ printf("Shutdown Seq #: %u\n", info.shutdown_seq_num);
+ printf(" Boot Seq #: %u\n", info.boot_seq_num);
+
+ close(fd);
+
+ return (0);
+}
+MFI_COMMAND(show, logstate, show_logstate);
+
+static int
+parse_seq(struct mfi_evt_log_state *info, char *arg, uint32_t *seq)
+{
+ char *cp;
+ long val;
+
+ if (strcasecmp(arg, "newest") == 0) {
+ *seq = info->newest_seq_num;
+ return (0);
+ }
+ if (strcasecmp(arg, "oldest") == 0) {
+ *seq = info->oldest_seq_num;
+ return (0);
+ }
+ if (strcasecmp(arg, "clear") == 0) {
+ *seq = info->clear_seq_num;
+ return (0);
+ }
+ if (strcasecmp(arg, "shutdown") == 0) {
+ *seq = info->shutdown_seq_num;
+ return (0);
+ }
+ if (strcasecmp(arg, "boot") == 0) {
+ *seq = info->boot_seq_num;
+ return (0);
+ }
+ val = strtol(arg, &cp, 0);
+ if (*cp != '\0' || val < 0) {
+ errno = EINVAL;
+ return (-1);
+ }
+ *seq = val;
+ return (0);
+}
+
+static int
+parse_locale(char *arg, uint16_t *locale)
+{
+ char *cp;
+ long val;
+
+ if (strncasecmp(arg, "vol", 3) == 0 || strcasecmp(arg, "ld") == 0) {
+ *locale = MFI_EVT_LOCALE_LD;
+ return (0);
+ }
+ if (strncasecmp(arg, "drive", 5) == 0 || strcasecmp(arg, "pd") == 0) {
+ *locale = MFI_EVT_LOCALE_PD;
+ return (0);
+ }
+ if (strncasecmp(arg, "encl", 4) == 0) {
+ *locale = MFI_EVT_LOCALE_ENCL;
+ return (0);
+ }
+ if (strncasecmp(arg, "batt", 4) == 0 ||
+ strncasecmp(arg, "bbu", 3) == 0) {
+ *locale = MFI_EVT_LOCALE_BBU;
+ return (0);
+ }
+ if (strcasecmp(arg, "sas") == 0) {
+ *locale = MFI_EVT_LOCALE_SAS;
+ return (0);
+ }
+ if (strcasecmp(arg, "ctrl") == 0 || strncasecmp(arg, "cont", 4) == 0) {
+ *locale = MFI_EVT_LOCALE_CTRL;
+ return (0);
+ }
+ if (strcasecmp(arg, "config") == 0) {
+ *locale = MFI_EVT_LOCALE_CONFIG;
+ return (0);
+ }
+ if (strcasecmp(arg, "cluster") == 0) {
+ *locale = MFI_EVT_LOCALE_CLUSTER;
+ return (0);
+ }
+ if (strcasecmp(arg, "all") == 0) {
+ *locale = MFI_EVT_LOCALE_ALL;
+ return (0);
+ }
+ val = strtol(arg, &cp, 0);
+ if (*cp != '\0' || val < 0 || val > 0xffff) {
+ errno = EINVAL;
+ return (-1);
+ }
+ *locale = val;
+ return (0);
+}
+
+static int
+parse_class(char *arg, int8_t *class)
+{
+ char *cp;
+ long val;
+
+ if (strcasecmp(arg, "debug") == 0) {
+ *class = MFI_EVT_CLASS_DEBUG;
+ return (0);
+ }
+ if (strncasecmp(arg, "prog", 4) == 0) {
+ *class = MFI_EVT_CLASS_PROGRESS;
+ return (0);
+ }
+ if (strncasecmp(arg, "info", 4) == 0) {
+ *class = MFI_EVT_CLASS_INFO;
+ return (0);
+ }
+ if (strncasecmp(arg, "warn", 4) == 0) {
+ *class = MFI_EVT_CLASS_WARNING;
+ return (0);
+ }
+ if (strncasecmp(arg, "crit", 4) == 0) {
+ *class = MFI_EVT_CLASS_CRITICAL;
+ return (0);
+ }
+ if (strcasecmp(arg, "fatal") == 0) {
+ *class = MFI_EVT_CLASS_FATAL;
+ return (0);
+ }
+ if (strcasecmp(arg, "dead") == 0) {
+ *class = MFI_EVT_CLASS_DEAD;
+ return (0);
+ }
+ val = strtol(arg, &cp, 0);
+ if (*cp != '\0' || val < -128 || val > 127) {
+ errno = EINVAL;
+ return (-1);
+ }
+ *class = val;
+ return (0);
+}
+
+/*
+ * The timestamp is the number of seconds since 00:00 Jan 1, 2000. If
+ * the bits in 24-31 are all set, then it is the number of seconds since
+ * boot.
+ */
+static const char *
+format_timestamp(uint32_t timestamp)
+{
+ static char buffer[32];
+ static time_t base;
+ time_t t;
+ struct tm tm;
+
+ if ((timestamp & 0xff000000) == 0xff000000) {
+ snprintf(buffer, sizeof(buffer), "boot + %us", timestamp &
+ 0x00ffffff);
+ return (buffer);
+ }
+
+ if (base == 0) {
+ /* Compute 00:00 Jan 1, 2000 offset. */
+ bzero(&tm, sizeof(tm));
+ tm.tm_mday = 1;
+ tm.tm_year = (2000 - 1900);
+ base = mktime(&tm);
+ }
+ if (base == -1) {
+ snprintf(buffer, sizeof(buffer), "%us", timestamp);
+ return (buffer);
+ }
+ t = base + timestamp;
+ strftime(buffer, sizeof(buffer), "%+", localtime(&t));
+ return (buffer);
+}
+
+static const char *
+format_locale(uint16_t locale)
+{
+ static char buffer[8];
+
+ switch (locale) {
+ case MFI_EVT_LOCALE_LD:
+ return ("VOLUME");
+ case MFI_EVT_LOCALE_PD:
+ return ("DRIVE");
+ case MFI_EVT_LOCALE_ENCL:
+ return ("ENCL");
+ case MFI_EVT_LOCALE_BBU:
+ return ("BATTERY");
+ case MFI_EVT_LOCALE_SAS:
+ return ("SAS");
+ case MFI_EVT_LOCALE_CTRL:
+ return ("CTRL");
+ case MFI_EVT_LOCALE_CONFIG:
+ return ("CONFIG");
+ case MFI_EVT_LOCALE_CLUSTER:
+ return ("CLUSTER");
+ case MFI_EVT_LOCALE_ALL:
+ return ("ALL");
+ default:
+ snprintf(buffer, sizeof(buffer), "0x%04x", locale);
+ return (buffer);
+ }
+}
+
+static const char *
+format_class(int8_t class)
+{
+ static char buffer[6];
+
+ switch (class) {
+ case MFI_EVT_CLASS_DEBUG:
+ return ("debug");
+ case MFI_EVT_CLASS_PROGRESS:
+ return ("progress");
+ case MFI_EVT_CLASS_INFO:
+ return ("info");
+ case MFI_EVT_CLASS_WARNING:
+ return ("WARN");
+ case MFI_EVT_CLASS_CRITICAL:
+ return ("CRIT");
+ case MFI_EVT_CLASS_FATAL:
+ return ("FATAL");
+ case MFI_EVT_CLASS_DEAD:
+ return ("DEAD");
+ default:
+ snprintf(buffer, sizeof(buffer), "%d", class);
+ return (buffer);
+ }
+}
+
+/* Simulates %D from kernel printf(9). */
+static void
+simple_hex(void *ptr, size_t length, const char *separator)
+{
+ unsigned char *cp;
+ u_int i;
+
+ if (length == 0)
+ return;
+ cp = ptr;
+ printf("%02x", cp[0]);
+ for (i = 1; i < length; i++)
+ printf("%s%02x", separator, cp[i]);
+}
+
+static const char *
+pdrive_location(struct mfi_evt_pd *pd)
+{
+ static char buffer[16];
+
+ if (pd->enclosure_index == 0)
+ snprintf(buffer, sizeof(buffer), "%02d(s%d)", pd->device_id,
+ pd->slot_number);
+ else
+ snprintf(buffer, sizeof(buffer), "%02d(e%d/s%d)", pd->device_id,
+ pd->enclosure_index, pd->slot_number);
+ return (buffer);
+}
+
+static const char *
+volume_name(int fd, struct mfi_evt_ld *ld)
+{
+
+ return (mfi_volume_name(fd, ld->target_id));
+}
+
+/* Ripped from sys/dev/mfi/mfi.c. */
+static void
+mfi_decode_evt(int fd, struct mfi_evt_detail *detail, int verbose)
+{
+
+ printf("%5d (%s/%s/%s) - ", detail->seq, format_timestamp(detail->time),
+ format_locale(detail->evt_class.members.locale),
+ format_class(detail->evt_class.members.evt_class));
+ switch (detail->arg_type) {
+ case MR_EVT_ARGS_NONE:
+ break;
+ case MR_EVT_ARGS_CDB_SENSE:
+ if (verbose) {
+ printf("PD %s CDB ",
+ pdrive_location(&detail->args.cdb_sense.pd)
+ );
+ simple_hex(detail->args.cdb_sense.cdb,
+ detail->args.cdb_sense.cdb_len, ":");
+ printf(" Sense ");
+ simple_hex(detail->args.cdb_sense.sense,
+ detail->args.cdb_sense.sense_len, ":");
+ printf(":\n ");
+ }
+ break;
+ case MR_EVT_ARGS_LD:
+ printf("VOL %s event: ", volume_name(fd, &detail->args.ld));
+ break;
+ case MR_EVT_ARGS_LD_COUNT:
+ printf("VOL %s", volume_name(fd, &detail->args.ld_count.ld));
+ if (verbose) {
+ printf(" count %lld: ",
+ (long long)detail->args.ld_count.count);
+ }
+ printf(": ");
+ break;
+ case MR_EVT_ARGS_LD_LBA:
+ printf("VOL %s", volume_name(fd, &detail->args.ld_count.ld));
+ if (verbose) {
+ printf(" lba %lld",
+ (long long)detail->args.ld_lba.lba);
+ }
+ printf(": ");
+ break;
+ case MR_EVT_ARGS_LD_OWNER:
+ printf("VOL %s", volume_name(fd, &detail->args.ld_count.ld));
+ if (verbose) {
+ printf(" owner changed: prior %d, new %d",
+ detail->args.ld_owner.pre_owner,
+ detail->args.ld_owner.new_owner);
+ }
+ printf(": ");
+ break;
+ case MR_EVT_ARGS_LD_LBA_PD_LBA:
+ printf("VOL %s", volume_name(fd, &detail->args.ld_count.ld));
+ if (verbose) {
+ printf(" lba %lld, physical drive PD %s lba %lld",
+ (long long)detail->args.ld_lba_pd_lba.ld_lba,
+ pdrive_location(&detail->args.ld_lba_pd_lba.pd),
+ (long long)detail->args.ld_lba_pd_lba.pd_lba);
+ }
+ printf(": ");
+ break;
+ case MR_EVT_ARGS_LD_PROG:
+ printf("VOL %s", volume_name(fd, &detail->args.ld_prog.ld));
+ if (verbose) {
+ printf(" progress %d%% in %ds",
+ detail->args.ld_prog.prog.progress/655,
+ detail->args.ld_prog.prog.elapsed_seconds);
+ }
+ printf(": ");
+ break;
+ case MR_EVT_ARGS_LD_STATE:
+ printf("VOL %s", volume_name(fd, &detail->args.ld_prog.ld));
+ if (verbose) {
+ printf(" state prior %s new %s",
+ mfi_ldstate(detail->args.ld_state.prev_state),
+ mfi_ldstate(detail->args.ld_state.new_state));
+ }
+ printf(": ");
+ break;
+ case MR_EVT_ARGS_LD_STRIP:
+ printf("VOL %s", volume_name(fd, &detail->args.ld_strip.ld));
+ if (verbose) {
+ printf(" strip %lld",
+ (long long)detail->args.ld_strip.strip);
+ }
+ printf(": ");
+ break;
+ case MR_EVT_ARGS_PD:
+ if (verbose) {
+ printf("PD %s event: ",
+ pdrive_location(&detail->args.pd));
+ }
+ break;
+ case MR_EVT_ARGS_PD_ERR:
+ if (verbose) {
+ printf("PD %s err %d: ",
+ pdrive_location(&detail->args.pd_err.pd),
+ detail->args.pd_err.err);
+ }
+ break;
+ case MR_EVT_ARGS_PD_LBA:
+ if (verbose) {
+ printf("PD %s lba %lld: ",
+ pdrive_location(&detail->args.pd_lba.pd),
+ (long long)detail->args.pd_lba.lba);
+ }
+ break;
+ case MR_EVT_ARGS_PD_LBA_LD:
+ if (verbose) {
+ printf("PD %s lba %lld VOL %s: ",
+ pdrive_location(&detail->args.pd_lba_ld.pd),
+ (long long)detail->args.pd_lba.lba,
+ volume_name(fd, &detail->args.pd_lba_ld.ld));
+ }
+ break;
+ case MR_EVT_ARGS_PD_PROG:
+ if (verbose) {
+ printf("PD %s progress %d%% seconds %ds: ",
+ pdrive_location(&detail->args.pd_prog.pd),
+ detail->args.pd_prog.prog.progress/655,
+ detail->args.pd_prog.prog.elapsed_seconds);
+ }
+ break;
+ case MR_EVT_ARGS_PD_STATE:
+ if (verbose) {
+ printf("PD %s state prior %s new %s: ",
+ pdrive_location(&detail->args.pd_prog.pd),
+ mfi_pdstate(detail->args.pd_state.prev_state),
+ mfi_pdstate(detail->args.pd_state.new_state));
+ }
+ break;
+ case MR_EVT_ARGS_PCI:
+ if (verbose) {
+ printf("PCI 0x%04x 0x%04x 0x%04x 0x%04x: ",
+ detail->args.pci.venderId,
+ detail->args.pci.deviceId,
+ detail->args.pci.subVenderId,
+ detail->args.pci.subDeviceId);
+ }
+ break;
+ case MR_EVT_ARGS_RATE:
+ if (verbose) {
+ printf("Rebuild rate %d: ", detail->args.rate);
+ }
+ break;
+ case MR_EVT_ARGS_TIME:
+ if (verbose) {
+ printf("Adapter time %s; %d seconds since power on: ",
+ format_timestamp(detail->args.time.rtc),
+ detail->args.time.elapsedSeconds);
+ }
+ break;
+ case MR_EVT_ARGS_ECC:
+ if (verbose) {
+ printf("Adapter ECC %x,%x: %s: ",
+ detail->args.ecc.ecar,
+ detail->args.ecc.elog,
+ detail->args.ecc.str);
+ }
+ break;
+ default:
+ if (verbose) {
+ printf("Type %d: ", detail->arg_type);
+ }
+ break;
+ }
+ printf("%s\n", detail->description);
+}
+
+static int
+show_events(int ac, char **av)
+{
+ struct mfi_evt_log_state info;
+ struct mfi_evt_list *list;
+ union mfi_evt filter;
+ bool first;
+ long val;
+ char *cp;
+ ssize_t size;
+ uint32_t seq, start, stop;
+ uint8_t status;
+ int ch, error, fd, num_events, verbose;
+ u_int i;
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ if (mfi_event_get_info(fd, &info, NULL) < 0) {
+ error = errno;
+ warn("Failed to get event log info");
+ close(fd);
+ return (error);
+ }
+
+ /* Default settings. */
+ num_events = 15;
+ filter.members.reserved = 0;
+ filter.members.locale = MFI_EVT_LOCALE_ALL;
+ filter.members.evt_class = MFI_EVT_CLASS_WARNING;
+ start = info.boot_seq_num;
+ stop = info.newest_seq_num;
+ verbose = 0;
+
+ /* Parse any options. */
+ optind = 1;
+ while ((ch = getopt(ac, av, "c:l:n:v")) != -1) {
+ switch (ch) {
+ case 'c':
+ if (parse_class(optarg, &filter.members.evt_class) < 0) {
+ error = errno;
+ warn("Error parsing event class");
+ close(fd);
+ return (error);
+ }
+ break;
+ case 'l':
+ if (parse_locale(optarg, &filter.members.locale) < 0) {
+ error = errno;
+ warn("Error parsing event locale");
+ close(fd);
+ return (error);
+ }
+ break;
+ case 'n':
+ val = strtol(optarg, &cp, 0);
+ if (*cp != '\0' || val <= 0) {
+ warnx("Invalid event count");
+ close(fd);
+ return (EINVAL);
+ }
+ num_events = val;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ case '?':
+ default:
+ close(fd);
+ return (EINVAL);
+ }
+ }
+ ac -= optind;
+ av += optind;
+
+ /* Determine buffer size and validate it. */
+ size = sizeof(struct mfi_evt_list) + sizeof(struct mfi_evt_detail) *
+ (num_events - 1);
+ if (size > getpagesize()) {
+ warnx("Event count is too high");
+ close(fd);
+ return (EINVAL);
+ }
+
+ /* Handle optional start and stop sequence numbers. */
+ if (ac > 2) {
+ warnx("show events: extra arguments");
+ close(fd);
+ return (EINVAL);
+ }
+ if (ac > 0 && parse_seq(&info, av[0], &start) < 0) {
+ error = errno;
+ warn("Error parsing starting sequence number");
+ close(fd);
+ return (error);
+ }
+ if (ac > 1 && parse_seq(&info, av[1], &stop) < 0) {
+ error = errno;
+ warn("Error parsing ending sequence number");
+ close(fd);
+ return (error);
+ }
+
+ list = malloc(size);
+ if (list == NULL) {
+ warnx("malloc failed");
+ close(fd);
+ return (ENOMEM);
+ }
+ first = true;
+ seq = start;
+ for (;;) {
+ if (mfi_get_events(fd, list, num_events, filter, seq,
+ &status) < 0) {
+ error = errno;
+ warn("Failed to fetch events");
+ free(list);
+ close(fd);
+ return (error);
+ }
+ if (status == MFI_STAT_NOT_FOUND) {
+ break;
+ }
+ if (status != MFI_STAT_OK) {
+ warnx("Error fetching events: %s", mfi_status(status));
+ free(list);
+ close(fd);
+ return (EIO);
+ }
+
+ for (i = 0; i < list->count; i++) {
+ /*
+ * If this event is newer than 'stop_seq' then
+ * break out of the loop. Note that the log
+ * is a circular buffer so we have to handle
+ * the case that our stop point is earlier in
+ * the buffer than our start point.
+ */
+ if (list->event[i].seq > stop) {
+ if (start <= stop)
+ goto finish;
+ else if (list->event[i].seq < start)
+ goto finish;
+ }
+ mfi_decode_evt(fd, &list->event[i], verbose);
+ first = false;
+ }
+
+ /*
+ * XXX: If the event's seq # is the end of the buffer
+ * then this probably won't do the right thing. We
+ * need to know the size of the buffer somehow.
+ */
+ seq = list->event[list->count - 1].seq + 1;
+
+ }
+finish:
+ if (first)
+ warnx("No matching events found");
+
+ free(list);
+ close(fd);
+
+ return (0);
+}
+MFI_COMMAND(show, events, show_events);
diff --git a/usr.sbin/mfiutil/mfi_flash.c b/usr.sbin/mfiutil/mfi_flash.c
new file mode 100644
index 0000000..d640cf7
--- /dev/null
+++ b/usr.sbin/mfiutil/mfi_flash.c
@@ -0,0 +1,192 @@
+/*-
+ * Copyright (c) 2008, 2009 Yahoo!, 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.
+ * 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/errno.h>
+#include <sys/stat.h>
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "mfiutil.h"
+
+#define FLASH_BUF_SIZE (64 * 1024)
+
+static int
+display_pending_firmware(int fd)
+{
+ struct mfi_ctrl_info info;
+ struct mfi_info_component header;
+ int error;
+ u_int i;
+
+ if (mfi_ctrl_get_info(fd, &info, NULL) < 0) {
+ error = errno;
+ warn("Failed to get controller info");
+ return (error);
+ }
+
+ printf("mfi%d Pending Firmware Images:\n", mfi_unit);
+ strcpy(header.name, "Name");
+ strcpy(header.version, "Version");
+ strcpy(header.build_date, "Date");
+ strcpy(header.build_time, "Time");
+ scan_firmware(&header);
+ if (info.pending_image_component_count > 8)
+ info.pending_image_component_count = 8;
+ for (i = 0; i < info.pending_image_component_count; i++)
+ scan_firmware(&info.pending_image_component[i]);
+ display_firmware(&header, "");
+ for (i = 0; i < info.pending_image_component_count; i++)
+ display_firmware(&info.pending_image_component[i], "");
+
+ return (0);
+}
+
+static void
+mbox_store_word(uint8_t *mbox, uint32_t val)
+{
+
+ mbox[0] = val & 0xff;
+ mbox[1] = val >> 8 & 0xff;
+ mbox[2] = val >> 16 & 0xff;
+ mbox[3] = val >> 24;
+}
+
+static int
+flash_adapter(int ac, char **av)
+{
+ struct mfi_progress dummy;
+ off_t offset;
+ size_t nread;
+ char *buf;
+ struct stat sb;
+ int error, fd, flash;
+ uint8_t mbox[4], status;
+
+ if (ac != 2) {
+ warnx("flash: Firmware file required");
+ return (EINVAL);
+ }
+
+ flash = open(av[1], O_RDONLY);
+ if (flash < 0) {
+ error = errno;
+ warn("flash: Failed to open %s", av[1]);
+ return (error);
+ }
+
+ buf = NULL;
+ fd = -1;
+
+ if (fstat(flash, &sb) < 0) {
+ error = errno;
+ warn("fstat(%s)", av[1]);
+ goto error;
+ }
+ if (sb.st_size % 1024 != 0 || sb.st_size > 0x7fffffff) {
+ warnx("Invalid flash file size");
+ error = EINVAL;
+ goto error;
+ }
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ goto error;
+ }
+
+ /* First, ask the firmware to allocate space for the flash file. */
+ mbox_store_word(mbox, sb.st_size);
+ mfi_dcmd_command(fd, MFI_DCMD_FLASH_FW_OPEN, NULL, 0, mbox, 4, &status);
+ if (status != MFI_STAT_OK) {
+ warnx("Failed to alloc flash memory: %s", mfi_status(status));
+ error = EIO;
+ goto error;
+ }
+
+ /* Upload the file 64k at a time. */
+ buf = malloc(FLASH_BUF_SIZE);
+ if (buf == NULL) {
+ warnx("malloc failed");
+ error = ENOMEM;
+ goto error;
+ }
+ offset = 0;
+ while (sb.st_size > 0) {
+ nread = read(flash, buf, FLASH_BUF_SIZE);
+ if (nread <= 0 || nread % 1024 != 0) {
+ warnx("Bad read from flash file");
+ mfi_dcmd_command(fd, MFI_DCMD_FLASH_FW_CLOSE, NULL, 0,
+ NULL, 0, NULL);
+ error = ENXIO;
+ goto error;
+ }
+
+ mbox_store_word(mbox, offset);
+ mfi_dcmd_command(fd, MFI_DCMD_FLASH_FW_DOWNLOAD, buf, nread,
+ mbox, 4, &status);
+ if (status != MFI_STAT_OK) {
+ warnx("Flash download failed: %s", mfi_status(status));
+ mfi_dcmd_command(fd, MFI_DCMD_FLASH_FW_CLOSE, NULL, 0,
+ NULL, 0, NULL);
+ error = ENXIO;
+ goto error;
+ }
+ sb.st_size -= nread;
+ offset += nread;
+ }
+
+ /* Kick off the flash. */
+ printf("WARNING: Firmware flash in progress, do not reboot machine... ");
+ fflush(stdout);
+ mfi_dcmd_command(fd, MFI_DCMD_FLASH_FW_FLASH, &dummy, sizeof(dummy),
+ NULL, 0, &status);
+ if (status != MFI_STAT_OK) {
+ printf("failed:\n\t%s\n", mfi_status(status));
+ error = ENXIO;
+ goto error;
+ }
+ printf("finished\n");
+ error = display_pending_firmware(fd);
+
+error:
+ free(buf);
+ if (fd >= 0)
+ close(fd);
+ close(flash);
+
+ return (error);
+}
+MFI_COMMAND(top, flash, flash_adapter);
diff --git a/usr.sbin/mfiutil/mfi_foreign.c b/usr.sbin/mfiutil/mfi_foreign.c
new file mode 100644
index 0000000..25e2add
--- /dev/null
+++ b/usr.sbin/mfiutil/mfi_foreign.c
@@ -0,0 +1,364 @@
+/*
+ * Copyright (c) 2013 smh@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/param.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libutil.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "mfiutil.h"
+
+MFI_TABLE(top, foreign);
+
+static int
+foreign_clear(__unused int ac, __unused char **av)
+{
+ int ch, error, fd;
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ printf(
+ "Are you sure you wish to clear ALL foreign configurations"
+ " on mfi%u? [y/N] ", mfi_unit);
+
+ ch = getchar();
+ if (ch != 'y' && ch != 'Y') {
+ printf("\nAborting\n");
+ close(fd);
+ return (0);
+ }
+
+ if (mfi_dcmd_command(fd, MFI_DCMD_CFG_FOREIGN_CLEAR, NULL, 0, NULL,
+ 0, NULL) < 0) {
+ error = errno;
+ warn("Failed to clear foreign configuration");
+ close(fd);
+ return (error);
+ }
+
+ printf("mfi%d: Foreign configuration cleared\n", mfi_unit);
+ close(fd);
+ return (0);
+}
+MFI_COMMAND(foreign, clear, foreign_clear);
+
+static int
+foreign_scan(__unused int ac, __unused char **av)
+{
+ struct mfi_foreign_scan_info info;
+ int error, fd;
+
+ fd = mfi_open(mfi_unit, O_RDONLY);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ if (mfi_dcmd_command(fd, MFI_DCMD_CFG_FOREIGN_SCAN, &info,
+ sizeof(info), NULL, 0, NULL) < 0) {
+ error = errno;
+ warn("Failed to scan foreign configuration");
+ close(fd);
+ return (error);
+ }
+
+ printf("mfi%d: Found %d foreign configurations\n", mfi_unit,
+ info.count);
+ close(fd);
+ return (0);
+}
+MFI_COMMAND(foreign, scan, foreign_scan);
+
+static int
+foreign_show_cfg(int fd, uint32_t opcode, uint8_t cfgidx, int diagnostic)
+{
+ struct mfi_config_data *config;
+ char prefix[64];
+ int error;
+ uint8_t mbox[4];
+
+ bzero(mbox, sizeof(mbox));
+ mbox[0] = cfgidx;
+ if (mfi_config_read_opcode(fd, opcode, &config, mbox, sizeof(mbox)) < 0) {
+ error = errno;
+ warn("Failed to get foreign config %d", error);
+ close(fd);
+ return (error);
+ }
+
+ if (opcode == MFI_DCMD_CFG_FOREIGN_PREVIEW)
+ sprintf(prefix, "Foreign configuration preview %d", cfgidx);
+ else
+ sprintf(prefix, "Foreign configuration %d", cfgidx);
+ /*
+ * MegaCli uses DCMD opcodes: 0x03100200 (which fails) followed by
+ * 0x1a721880 which returns what looks to be drive / volume info
+ * but we have no real information on what these are or what they do
+ * so we're currently relying solely on the config returned above
+ */
+ if (diagnostic)
+ dump_config(fd, config, prefix);
+ else {
+ char *ld_list;
+ int i;
+
+ ld_list = (char *)(config->array);
+
+ printf("%s: %d arrays, %d volumes, %d spares\n", prefix,
+ config->array_count, config->log_drv_count,
+ config->spares_count);
+
+
+ for (i = 0; i < config->array_count; i++)
+ ld_list += config->array_size;
+
+ for (i = 0; i < config->log_drv_count; i++) {
+ const char *level;
+ char size[6], stripe[5];
+ struct mfi_ld_config *ld;
+
+ ld = (struct mfi_ld_config *)ld_list;
+
+ format_stripe(stripe, sizeof(stripe),
+ ld->params.stripe_size);
+ /*
+ * foreign configs don't seem to have a secondary raid level
+ * but, we can use span depth here as if a LD spans multiple
+ * arrays of disks (2 raid 1 sets for example), we will have an
+ * indication based on the spam depth. swb
+ */
+ level = mfi_raid_level(ld->params.primary_raid_level,
+ (ld->params.span_depth - 1));
+
+ humanize_number(size, sizeof(size), ld->span[0].num_blocks * 512,
+ "", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
+
+ printf(" ID%d ", i);
+ printf("(%6s) %-8s |",
+ size, level);
+ printf("volume spans %d %s\n", ld->params.span_depth,
+ (ld->params.span_depth > 1) ? "arrays" : "array");
+ for (int j = 0; j < ld->params.span_depth; j++) {
+ char *ar_list;
+ struct mfi_array *ar;
+ uint16_t device_id;
+
+ printf(" array %u @ ", ld->span[j].array_ref);
+ humanize_number(size, sizeof(size), ld->span[j].num_blocks * 512,
+ "", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
+
+ printf("(%6s)\n",size);
+ ar_list = (char *)config->array + (ld->span[j].array_ref * config->array_size);
+
+ ar = (struct mfi_array *)ar_list;
+ for (int k = 0; k < ar->num_drives; k++) {
+ device_id = ar->pd[k].ref.v.device_id;
+ if (device_id == 0xffff)
+ printf(" drive MISSING\n");
+ else {
+ printf(" drive %u %s\n", device_id,
+ mfi_pdstate(ar->pd[k].fw_state));
+ }
+ }
+
+ }
+ ld_list += config->log_drv_size;
+ }
+ }
+
+ free(config);
+
+ return (0);
+}
+
+int
+display_format(int ac, char **av, int diagnostic, mfi_dcmd_t display_cmd)
+{
+ struct mfi_foreign_scan_info info;
+ uint8_t i;
+ int error, fd;
+
+ if (ac > 2) {
+ warnx("foreign display: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDONLY);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ if (mfi_dcmd_command(fd, MFI_DCMD_CFG_FOREIGN_SCAN, &info,
+ sizeof(info), NULL, 0, NULL) < 0) {
+ error = errno;
+ warn("Failed to scan foreign configuration");
+ close(fd);
+ return (error);
+ }
+
+ if (info.count == 0) {
+ warnx("foreign display: no foreign configs found");
+ close(fd);
+ return (EINVAL);
+ }
+
+ if (ac == 1) {
+ for (i = 0; i < info.count; i++) {
+ error = foreign_show_cfg(fd,
+ display_cmd, i, diagnostic);
+ if(error != 0) {
+ close(fd);
+ return (error);
+ }
+ if (i < info.count - 1)
+ printf("\n");
+ }
+ } else if (ac == 2) {
+ error = foreign_show_cfg(fd,
+ display_cmd, atoi(av[1]), diagnostic);
+ if (error != 0) {
+ close(fd);
+ return (error);
+ }
+ }
+
+ close(fd);
+ return (0);
+}
+
+static int
+foreign_display(int ac, char **av)
+{
+ return(display_format(ac, av, 1/*diagnostic output*/, MFI_DCMD_CFG_FOREIGN_DISPLAY));
+}
+MFI_COMMAND(foreign, diag, foreign_display);
+
+static int
+foreign_preview(int ac, char **av)
+{
+ return(display_format(ac, av, 1/*diagnostic output*/, MFI_DCMD_CFG_FOREIGN_PREVIEW));
+}
+MFI_COMMAND(foreign, preview, foreign_preview);
+
+static int
+foreign_import(int ac, char **av)
+{
+ struct mfi_foreign_scan_info info;
+ int ch, error, fd;
+ uint8_t cfgidx;
+ uint8_t mbox[4];
+
+ if (ac > 2) {
+ warnx("foreign preview: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ if (mfi_dcmd_command(fd, MFI_DCMD_CFG_FOREIGN_SCAN, &info,
+ sizeof(info), NULL, 0, NULL) < 0) {
+ error = errno;
+ warn("Failed to scan foreign configuration");
+ close(fd);
+ return (error);
+ }
+
+ if (info.count == 0) {
+ warnx("foreign import: no foreign configs found");
+ close(fd);
+ return (EINVAL);
+ }
+
+ if (ac == 1) {
+ cfgidx = 0xff;
+ printf("Are you sure you wish to import ALL foreign "
+ "configurations on mfi%u? [y/N] ", mfi_unit);
+ } else {
+ /*
+ * While this is docmmented for MegaCli this failed with
+ * exit code 0x03 on the test controller which was a Supermicro
+ * SMC2108 with firmware 12.12.0-0095 which is a LSI 2108 based
+ * controller.
+ */
+ cfgidx = atoi(av[1]);
+ if (cfgidx >= info.count) {
+ warnx("Invalid foreign config %d specified max is %d",
+ cfgidx, info.count - 1);
+ close(fd);
+ return (EINVAL);
+ }
+ printf("Are you sure you wish to import the foreign "
+ "configuration %d on mfi%u? [y/N] ", cfgidx, mfi_unit);
+ }
+
+ ch = getchar();
+ if (ch != 'y' && ch != 'Y') {
+ printf("\nAborting\n");
+ close(fd);
+ return (0);
+ }
+
+ bzero(mbox, sizeof(mbox));
+ mbox[0] = cfgidx;
+ if (mfi_dcmd_command(fd, MFI_DCMD_CFG_FOREIGN_IMPORT, NULL, 0, mbox,
+ sizeof(mbox), NULL) < 0) {
+ error = errno;
+ warn("Failed to import foreign configuration");
+ close(fd);
+ return (error);
+ }
+
+ if (ac == 1)
+ printf("mfi%d: All foreign configurations imported\n",
+ mfi_unit);
+ else
+ printf("mfi%d: Foreign configuration %d imported\n", mfi_unit,
+ cfgidx);
+ close(fd);
+ return (0);
+}
+MFI_COMMAND(foreign, import, foreign_import);
diff --git a/usr.sbin/mfiutil/mfi_patrol.c b/usr.sbin/mfiutil/mfi_patrol.c
new file mode 100644
index 0000000..c3e47f3
--- /dev/null
+++ b/usr.sbin/mfiutil/mfi_patrol.c
@@ -0,0 +1,336 @@
+/*-
+ * Copyright (c) 2008, 2009 Yahoo!, 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.
+ * 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/types.h>
+#include <sys/errno.h>
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include "mfiutil.h"
+
+static char *
+adapter_time(time_t now, uint32_t at_now, uint32_t at)
+{
+ time_t t;
+
+ t = (now - at_now) + at;
+ return (ctime(&t));
+}
+
+static void
+mfi_get_time(int fd, uint32_t *at)
+{
+
+ if (mfi_dcmd_command(fd, MFI_DCMD_TIME_SECS_GET, at, sizeof(*at), NULL,
+ 0, NULL) < 0) {
+ warn("Couldn't fetch adapter time");
+ at = 0;
+ }
+}
+
+static int
+patrol_get_props(int fd, struct mfi_pr_properties *prop)
+{
+ int error;
+
+ if (mfi_dcmd_command(fd, MFI_DCMD_PR_GET_PROPERTIES, prop,
+ sizeof(*prop), NULL, 0, NULL) < 0) {
+ error = errno;
+ warn("Failed to get patrol read properties");
+ return (error);
+ }
+ return (0);
+}
+
+static int
+show_patrol(int ac __unused, char **av __unused)
+{
+ struct mfi_pr_properties prop;
+ struct mfi_pr_status status;
+ struct mfi_pd_list *list;
+ struct mfi_pd_info info;
+ char label[24];
+ time_t now;
+ uint32_t at;
+ int error, fd;
+ u_int i;
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ time(&now);
+ mfi_get_time(fd, &at);
+ error = patrol_get_props(fd, &prop);
+ if (error) {
+ close(fd);
+ return (error);
+ }
+ printf("Operation Mode: ");
+ switch (prop.op_mode) {
+ case MFI_PR_OPMODE_AUTO:
+ printf("auto\n");
+ break;
+ case MFI_PR_OPMODE_MANUAL:
+ printf("manual\n");
+ break;
+ case MFI_PR_OPMODE_DISABLED:
+ printf("disabled\n");
+ break;
+ default:
+ printf("??? (%02x)\n", prop.op_mode);
+ break;
+ }
+ if (prop.op_mode == MFI_PR_OPMODE_AUTO) {
+ if (at != 0 && prop.next_exec)
+ printf(" Next Run Starts: %s", adapter_time(now, at,
+ prop.next_exec));
+ if (prop.exec_freq == 0xffffffff)
+ printf(" Runs Execute Continuously\n");
+ else if (prop.exec_freq != 0)
+ printf(" Runs Start Every %u seconds\n",
+ prop.exec_freq);
+ }
+
+ if (mfi_dcmd_command(fd, MFI_DCMD_PR_GET_STATUS, &status,
+ sizeof(status), NULL, 0, NULL) < 0) {
+ error = errno;
+ warn("Failed to get patrol read properties");
+ close(fd);
+ return (error);
+ }
+ printf("Runs Completed: %u\n", status.num_iteration);
+ printf("Current State: ");
+ switch (status.state) {
+ case MFI_PR_STATE_STOPPED:
+ printf("stopped\n");
+ break;
+ case MFI_PR_STATE_READY:
+ printf("ready\n");
+ break;
+ case MFI_PR_STATE_ACTIVE:
+ printf("active\n");
+ break;
+ case MFI_PR_STATE_ABORTED:
+ printf("aborted\n");
+ break;
+ default:
+ printf("??? (%02x)\n", status.state);
+ break;
+ }
+ if (status.state == MFI_PR_STATE_ACTIVE) {
+ if (mfi_pd_get_list(fd, &list, NULL) < 0) {
+ error = errno;
+ warn("Failed to get drive list");
+ close(fd);
+ return (error);
+ }
+
+ for (i = 0; i < list->count; i++) {
+ if (list->addr[i].scsi_dev_type != 0)
+ continue;
+
+ if (mfi_pd_get_info(fd, list->addr[i].device_id, &info,
+ NULL) < 0) {
+ error = errno;
+ warn("Failed to fetch info for drive %u",
+ list->addr[i].device_id);
+ free(list);
+ close(fd);
+ return (error);
+ }
+ if (info.prog_info.active & MFI_PD_PROGRESS_PATROL) {
+ snprintf(label, sizeof(label), " Drive %s",
+ mfi_drive_name(NULL,
+ list->addr[i].device_id,
+ MFI_DNAME_DEVICE_ID|MFI_DNAME_HONOR_OPTS));
+ mfi_display_progress(label,
+ &info.prog_info.patrol);
+ }
+ }
+ free(list);
+ }
+
+ close(fd);
+
+ return (0);
+}
+MFI_COMMAND(show, patrol, show_patrol);
+
+static int
+start_patrol(int ac __unused, char **av __unused)
+{
+ int error, fd;
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ if (mfi_dcmd_command(fd, MFI_DCMD_PR_START, NULL, 0, NULL, 0, NULL) <
+ 0) {
+ error = errno;
+ warn("Failed to start patrol read");
+ close(fd);
+ return (error);
+ }
+
+ close(fd);
+
+ return (0);
+}
+MFI_COMMAND(start, patrol, start_patrol);
+
+static int
+stop_patrol(int ac __unused, char **av __unused)
+{
+ int error, fd;
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ if (mfi_dcmd_command(fd, MFI_DCMD_PR_STOP, NULL, 0, NULL, 0, NULL) <
+ 0) {
+ error = errno;
+ warn("Failed to stop patrol read");
+ close(fd);
+ return (error);
+ }
+
+ close(fd);
+
+ return (0);
+}
+MFI_COMMAND(stop, patrol, stop_patrol);
+
+static int
+patrol_config(int ac, char **av)
+{
+ struct mfi_pr_properties prop;
+ long val;
+ time_t now;
+ int error, fd;
+ uint32_t at, next_exec, exec_freq;
+ char *cp;
+ uint8_t op_mode;
+
+ exec_freq = 0; /* GCC too stupid */
+ next_exec = 0;
+ if (ac < 2) {
+ warnx("patrol: command required");
+ return (EINVAL);
+ }
+ if (strcasecmp(av[1], "auto") == 0) {
+ op_mode = MFI_PR_OPMODE_AUTO;
+ if (ac > 2) {
+ if (strcasecmp(av[2], "continuously") == 0)
+ exec_freq = 0xffffffff;
+ else {
+ val = strtol(av[2], &cp, 0);
+ if (*cp != '\0') {
+ warnx("patrol: Invalid interval %s",
+ av[2]);
+ return (EINVAL);
+ }
+ exec_freq = val;
+ }
+ }
+ if (ac > 3) {
+ val = strtol(av[3], &cp, 0);
+ if (*cp != '\0' || val < 0) {
+ warnx("patrol: Invalid start time %s", av[3]);
+ return (EINVAL);
+ }
+ next_exec = val;
+ }
+ } else if (strcasecmp(av[1], "manual") == 0)
+ op_mode = MFI_PR_OPMODE_MANUAL;
+ else if (strcasecmp(av[1], "disable") == 0)
+ op_mode = MFI_PR_OPMODE_DISABLED;
+ else {
+ warnx("patrol: Invalid command %s", av[1]);
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ error = patrol_get_props(fd, &prop);
+ if (error) {
+ close(fd);
+ return (error);
+ }
+ prop.op_mode = op_mode;
+ if (op_mode == MFI_PR_OPMODE_AUTO) {
+ if (ac > 2)
+ prop.exec_freq = exec_freq;
+ if (ac > 3) {
+ time(&now);
+ mfi_get_time(fd, &at);
+ if (at == 0) {
+ close(fd);
+ return (ENXIO);
+ }
+ prop.next_exec = at + next_exec;
+ printf("Starting next patrol read at %s",
+ adapter_time(now, at, prop.next_exec));
+ }
+ }
+ if (mfi_dcmd_command(fd, MFI_DCMD_PR_SET_PROPERTIES, &prop,
+ sizeof(prop), NULL, 0, NULL) < 0) {
+ error = errno;
+ warn("Failed to set patrol read properties");
+ close(fd);
+ return (error);
+ }
+
+ close(fd);
+
+ return (0);
+}
+MFI_COMMAND(top, patrol, patrol_config);
diff --git a/usr.sbin/mfiutil/mfi_properties.c b/usr.sbin/mfiutil/mfi_properties.c
new file mode 100644
index 0000000..b03d522
--- /dev/null
+++ b/usr.sbin/mfiutil/mfi_properties.c
@@ -0,0 +1,171 @@
+/*-
+ * Copyright (c) 2013 Yahoo!, 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$
+ */
+
+#include <sys/errno.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <sys/uio.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mfiutil.h"
+#include <dev/mfi/mfi_ioctl.h>
+
+MFI_TABLE(top, ctrlprop);
+
+static int
+mfi_ctrl_get_properties(int fd, struct mfi_ctrl_props *info)
+{
+
+ return (mfi_dcmd_command(fd, MFI_DCMD_CTRL_GET_PROPS, info,
+ sizeof(struct mfi_ctrl_props), NULL, 0, NULL));
+}
+
+static int
+mfi_ctrl_set_properties(int fd, struct mfi_ctrl_props *info)
+{
+
+ return (mfi_dcmd_command(fd, MFI_DCMD_CTRL_SET_PROPS, info,
+ sizeof(struct mfi_ctrl_props), NULL, 0, NULL));
+}
+
+/*
+ * aquite the controller properties data structure modify the
+ * rebuild rate if requested and then retun
+ */
+static int
+mfi_ctrl_rebuild_rate(int ac, char **av)
+{
+ int error, fd;
+ struct mfi_ctrl_props ctrl_props;
+
+ if (ac > 2) {
+ warn("mfi_ctrl_set_rebuild_rate");
+ return(-1);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ error = mfi_ctrl_get_properties(fd, &ctrl_props);
+ if ( error < 0) {
+ error = errno;
+ warn("Failed to get controller properties");
+ close(fd);
+ return (error);
+ }
+ /*
+ * User requested a change to the rebuild rate
+ */
+ if (ac > 1) {
+ ctrl_props.rebuild_rate = atoi(av[ac - 1]);
+ error = mfi_ctrl_set_properties(fd, &ctrl_props);
+ if ( error < 0) {
+ error = errno;
+ warn("Failed to set controller properties");
+ close(fd);
+ return (error);
+ }
+
+ error = mfi_ctrl_get_properties(fd, &ctrl_props);
+ if ( error < 0) {
+ error = errno;
+ warn("Failed to get controller properties");
+ close(fd);
+ return (error);
+ }
+ }
+ printf ("controller rebuild rate: %%%u \n",
+ ctrl_props.rebuild_rate);
+ return (0);
+}
+MFI_COMMAND(ctrlprop, rebuild, mfi_ctrl_rebuild_rate);
+
+static int
+mfi_ctrl_alarm_enable(int ac, char **av)
+{
+ int error, fd;
+ struct mfi_ctrl_props ctrl_props;
+
+ if (ac > 2) {
+ warn("mfi_ctrl_alarm_enable");
+ return(-1);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ error = mfi_ctrl_get_properties(fd, &ctrl_props);
+ if ( error < 0) {
+ error = errno;
+ warn("Failed to get controller properties");
+ close(fd);
+ return (error);
+ }
+ printf ("controller alarm was : %s\n",
+ (ctrl_props.alarm_enable ? "enabled" : "disabled"));
+
+ if (ac > 1) {
+ ctrl_props.alarm_enable = atoi(av[ac - 1]);
+ error = mfi_ctrl_set_properties(fd, &ctrl_props);
+ if ( error < 0) {
+ error = errno;
+ warn("Failed to set controller properties");
+ close(fd);
+ return (error);
+ }
+
+ error = mfi_ctrl_get_properties(fd, &ctrl_props);
+ if ( error < 0) {
+ error = errno;
+ warn("Failed to get controller properties");
+ close(fd);
+ return (error);
+ }
+ }
+ printf ("controller alarm was : %s\n",
+ (ctrl_props.alarm_enable ? "enabled" : "disabled"));
+ return (0);
+}
+
+MFI_COMMAND(ctrlprop, alarm, mfi_ctrl_alarm_enable);
diff --git a/usr.sbin/mfiutil/mfi_show.c b/usr.sbin/mfiutil/mfi_show.c
new file mode 100644
index 0000000..f541045
--- /dev/null
+++ b/usr.sbin/mfiutil/mfi_show.c
@@ -0,0 +1,788 @@
+/*-
+ * Copyright (c) 2008, 2009 Yahoo!, 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.
+ * 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/types.h>
+#include <sys/errno.h>
+#include <err.h>
+#include <fcntl.h>
+#include <libutil.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "mfiutil.h"
+
+static const char* foreign_state = " (FOREIGN)";
+
+MFI_TABLE(top, show);
+
+void
+format_stripe(char *buf, size_t buflen, uint8_t stripe)
+{
+
+ humanize_number(buf, buflen, (1 << stripe) * 512, "", HN_AUTOSCALE,
+ HN_B | HN_NOSPACE);
+}
+
+static int
+show_adapter(int ac, char **av __unused)
+{
+ struct mfi_ctrl_info info;
+ char stripe[5];
+ int error, fd, comma;
+
+ if (ac != 1) {
+ warnx("show adapter: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDONLY);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ if (mfi_ctrl_get_info(fd, &info, NULL) < 0) {
+ error = errno;
+ warn("Failed to get controller info");
+ close(fd);
+ return (error);
+ }
+ printf("mfi%d Adapter:\n", mfi_unit);
+ printf(" Product Name: %.80s\n", info.product_name);
+ printf(" Serial Number: %.32s\n", info.serial_number);
+ if (info.package_version[0] != '\0')
+ printf(" Firmware: %s\n", info.package_version);
+ printf(" RAID Levels:");
+#ifdef DEBUG
+ printf(" (%#x)", info.raid_levels);
+#endif
+ comma = 0;
+ if (info.raid_levels & MFI_INFO_RAID_0) {
+ printf(" JBOD, RAID0");
+ comma = 1;
+ }
+ if (info.raid_levels & MFI_INFO_RAID_1) {
+ printf("%s RAID1", comma ? "," : "");
+ comma = 1;
+ }
+ if (info.raid_levels & MFI_INFO_RAID_5) {
+ printf("%s RAID5", comma ? "," : "");
+ comma = 1;
+ }
+ if (info.raid_levels & MFI_INFO_RAID_1E) {
+ printf("%s RAID1E", comma ? "," : "");
+ comma = 1;
+ }
+ if (info.raid_levels & MFI_INFO_RAID_6) {
+ printf("%s RAID6", comma ? "," : "");
+ comma = 1;
+ }
+ if ((info.raid_levels & (MFI_INFO_RAID_0 | MFI_INFO_RAID_1)) ==
+ (MFI_INFO_RAID_0 | MFI_INFO_RAID_1)) {
+ printf("%s RAID10", comma ? "," : "");
+ comma = 1;
+ }
+ if ((info.raid_levels & (MFI_INFO_RAID_0 | MFI_INFO_RAID_5)) ==
+ (MFI_INFO_RAID_0 | MFI_INFO_RAID_5)) {
+ printf("%s RAID50", comma ? "," : "");
+ comma = 1;
+ }
+ printf("\n");
+ printf(" Battery Backup: ");
+ if (info.hw_present & MFI_INFO_HW_BBU)
+ printf("present\n");
+ else
+ printf("not present\n");
+ if (info.hw_present & MFI_INFO_HW_NVRAM)
+ printf(" NVRAM: %uK\n", info.nvram_size);
+ printf(" Onboard Memory: %uM\n", info.memory_size);
+ format_stripe(stripe, sizeof(stripe), info.stripe_sz_ops.min);
+ printf(" Minimum Stripe: %s\n", stripe);
+ format_stripe(stripe, sizeof(stripe), info.stripe_sz_ops.max);
+ printf(" Maximum Stripe: %s\n", stripe);
+
+ close(fd);
+
+ return (0);
+}
+MFI_COMMAND(show, adapter, show_adapter);
+
+static int
+show_battery(int ac, char **av __unused)
+{
+ struct mfi_bbu_capacity_info cap;
+ struct mfi_bbu_design_info design;
+ struct mfi_bbu_properties props;
+ struct mfi_bbu_status stat;
+ uint8_t status;
+ int comma, error, fd, show_capacity, show_props;
+ char buf[32];
+
+ if (ac != 1) {
+ warnx("show battery: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDONLY);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ if (mfi_dcmd_command(fd, MFI_DCMD_BBU_GET_CAPACITY_INFO, &cap,
+ sizeof(cap), NULL, 0, &status) < 0) {
+ error = errno;
+ warn("Failed to get capacity info");
+ close(fd);
+ return (error);
+ }
+ if (status == MFI_STAT_NO_HW_PRESENT) {
+ printf("mfi%d: No battery present\n", mfi_unit);
+ close(fd);
+ return (0);
+ }
+ show_capacity = (status == MFI_STAT_OK);
+
+ if (mfi_dcmd_command(fd, MFI_DCMD_BBU_GET_DESIGN_INFO, &design,
+ sizeof(design), NULL, 0, NULL) < 0) {
+ error = errno;
+ warn("Failed to get design info");
+ close(fd);
+ return (error);
+ }
+
+ if (mfi_dcmd_command(fd, MFI_DCMD_BBU_GET_STATUS, &stat, sizeof(stat),
+ NULL, 0, NULL) < 0) {
+ error = errno;
+ warn("Failed to get status");
+ close(fd);
+ return (error);
+ }
+
+ if (mfi_bbu_get_props(fd, &props, &status) < 0) {
+ error = errno;
+ warn("Failed to get properties");
+ close(fd);
+ return (error);
+ }
+ show_props = (status == MFI_STAT_OK);
+
+ printf("mfi%d: Battery State:\n", mfi_unit);
+ printf(" Manufacture Date: %d/%d/%d\n", design.mfg_date >> 5 & 0x0f,
+ design.mfg_date & 0x1f, design.mfg_date >> 9 & 0xffff);
+ printf(" Serial Number: %d\n", design.serial_number);
+ printf(" Manufacturer: %s\n", design.mfg_name);
+ printf(" Model: %s\n", design.device_name);
+ printf(" Chemistry: %s\n", design.device_chemistry);
+ printf(" Design Capacity: %d mAh\n", design.design_capacity);
+ if (show_capacity) {
+ printf(" Full Charge Capacity: %d mAh\n",
+ cap.full_charge_capacity);
+ printf(" Current Capacity: %d mAh\n",
+ cap.remaining_capacity);
+ printf(" Charge Cycles: %d\n", cap.cycle_count);
+ printf(" Current Charge: %d%%\n", cap.relative_charge);
+ }
+ printf(" Design Voltage: %d mV\n", design.design_voltage);
+ printf(" Current Voltage: %d mV\n", stat.voltage);
+ printf(" Temperature: %d C\n", stat.temperature);
+ if (show_props) {
+ mfi_autolearn_period(props.auto_learn_period, buf, sizeof(buf));
+ printf(" Autolearn period: %s\n", buf);
+ if (props.auto_learn_mode != 0)
+ snprintf(buf, sizeof(buf), "never");
+ else
+ mfi_next_learn_time(props.next_learn_time, buf,
+ sizeof(buf));
+ printf(" Next learn time: %s\n", buf);
+ printf(" Learn delay interval: %u hour%s\n",
+ props.learn_delay_interval,
+ props.learn_delay_interval != 1 ? "s" : "");
+ mfi_autolearn_mode(props.auto_learn_mode, buf, sizeof(buf));
+ printf(" Autolearn mode: %s\n", buf);
+ if (props.bbu_mode != 0)
+ printf(" BBU Mode: %d\n", props.bbu_mode);
+ }
+ printf(" Status:");
+ comma = 0;
+ if (stat.fw_status & MFI_BBU_STATE_PACK_MISSING) {
+ printf(" PACK_MISSING");
+ comma = 1;
+ }
+ if (stat.fw_status & MFI_BBU_STATE_VOLTAGE_LOW) {
+ printf("%s VOLTAGE_LOW", comma ? "," : "");
+ comma = 1;
+ }
+ if (stat.fw_status & MFI_BBU_STATE_TEMPERATURE_HIGH) {
+ printf("%s TEMPERATURE_HIGH", comma ? "," : "");
+ comma = 1;
+ }
+ if (stat.fw_status & MFI_BBU_STATE_CHARGE_ACTIVE) {
+ printf("%s CHARGING", comma ? "," : "");
+ comma = 1;
+ }
+ if (stat.fw_status & MFI_BBU_STATE_DISCHARGE_ACTIVE) {
+ printf("%s DISCHARGING", comma ? "," : "");
+ comma = 1;
+ }
+ if (stat.fw_status & MFI_BBU_STATE_LEARN_CYC_REQ) {
+ printf("%s LEARN_CYCLE_REQUESTED", comma ? "," : "");
+ comma = 1;
+ }
+ if (stat.fw_status & MFI_BBU_STATE_LEARN_CYC_ACTIVE) {
+ printf("%s LEARN_CYCLE_ACTIVE", comma ? "," : "");
+ comma = 1;
+ }
+ if (stat.fw_status & MFI_BBU_STATE_LEARN_CYC_FAIL) {
+ printf("%s LEARN_CYCLE_FAIL", comma ? "," : "");
+ comma = 1;
+ }
+ if (stat.fw_status & MFI_BBU_STATE_LEARN_CYC_TIMEOUT) {
+ printf("%s LEARN_CYCLE_TIMEOUT", comma ? "," : "");
+ comma = 1;
+ }
+ if (stat.fw_status & MFI_BBU_STATE_I2C_ERR_DETECT) {
+ printf("%s I2C_ERROR_DETECT", comma ? "," : "");
+ comma = 1;
+ }
+
+ if (!comma)
+ printf(" normal");
+ printf("\n");
+ switch (stat.battery_type) {
+ case MFI_BBU_TYPE_BBU:
+ printf(" State of Health: %s\n",
+ stat.detail.bbu.is_SOH_good ? "good" : "bad");
+ break;
+ }
+
+ close(fd);
+
+ return (0);
+}
+MFI_COMMAND(show, battery, show_battery);
+
+void
+print_ld(struct mfi_ld_info *info, int state_len)
+{
+ struct mfi_ld_params *params = &info->ld_config.params;
+ const char *level;
+ char size[6], stripe[5];
+
+ humanize_number(size, sizeof(size), info->size * 512,
+ "", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
+ format_stripe(stripe, sizeof(stripe),
+ info->ld_config.params.stripe_size);
+ level = mfi_raid_level(params->primary_raid_level,
+ params->secondary_raid_level);
+ if (state_len > 0)
+ printf("(%6s) %-8s %6s %-*s", size, level, stripe, state_len,
+ mfi_ldstate(params->state));
+ else
+ printf("(%s) %s %s %s", size, level, stripe,
+ mfi_ldstate(params->state));
+}
+
+void
+print_pd(struct mfi_pd_info *info, int state_len)
+{
+ const char *s;
+ char buf[256];
+
+ humanize_number(buf, 6, info->raw_size * 512, "",
+ HN_AUTOSCALE, HN_B | HN_NOSPACE |HN_DECIMAL);
+ printf("(%6s) ", buf);
+ if (info->state.ddf.v.pd_type.is_foreign) {
+ sprintf(buf, "%s%s", mfi_pdstate(info->fw_state), foreign_state);
+ s = buf;
+ } else
+ s = mfi_pdstate(info->fw_state);
+ if (state_len > 0)
+ printf("%-*s", state_len, s);
+ else
+ printf("%s",s);
+ s = mfi_pd_inq_string(info);
+ if (s != NULL)
+ printf(" %s", s);
+}
+
+static int
+show_config(int ac, char **av __unused)
+{
+ struct mfi_config_data *config;
+ struct mfi_array *ar;
+ struct mfi_ld_config *ld;
+ struct mfi_spare *sp;
+ struct mfi_ld_info linfo;
+ struct mfi_pd_info pinfo;
+ uint16_t device_id;
+ char *p;
+ int error, fd, i, j;
+
+ if (ac != 1) {
+ warnx("show config: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDONLY);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ /* Get the config from the controller. */
+ if (mfi_config_read(fd, &config) < 0) {
+ error = errno;
+ warn("Failed to get config");
+ close(fd);
+ return (error);
+ }
+
+ /* Dump out the configuration. */
+ printf("mfi%d Configuration: %d arrays, %d volumes, %d spares\n",
+ mfi_unit, config->array_count, config->log_drv_count,
+ config->spares_count);
+ p = (char *)config->array;
+
+ for (i = 0; i < config->array_count; i++) {
+ ar = (struct mfi_array *)p;
+ printf(" array %u of %u drives:\n", ar->array_ref,
+ ar->num_drives);
+ for (j = 0; j < ar->num_drives; j++) {
+ device_id = ar->pd[j].ref.v.device_id;
+ printf(" drive %s ", mfi_drive_name(NULL,
+ device_id,
+ MFI_DNAME_DEVICE_ID|MFI_DNAME_HONOR_OPTS));
+ if (device_id != 0xffff) {
+ if (mfi_pd_get_info(fd, device_id, &pinfo,
+ NULL) < 0)
+ printf("%s",
+ mfi_pdstate(ar->pd[j].fw_state));
+ else
+ print_pd(&pinfo, -1);
+ }
+ printf("\n");
+ }
+ p += config->array_size;
+ }
+
+ for (i = 0; i < config->log_drv_count; i++) {
+ ld = (struct mfi_ld_config *)p;
+ printf(" volume %s ",
+ mfi_volume_name(fd, ld->properties.ld.v.target_id));
+ if (mfi_ld_get_info(fd, ld->properties.ld.v.target_id, &linfo,
+ NULL) < 0) {
+ printf("%s %s",
+ mfi_raid_level(ld->params.primary_raid_level,
+ ld->params.secondary_raid_level),
+ mfi_ldstate(ld->params.state));
+ } else
+ print_ld(&linfo, -1);
+ if (ld->properties.name[0] != '\0')
+ printf(" <%s>", ld->properties.name);
+ printf(" spans:\n");
+ for (j = 0; j < ld->params.span_depth; j++)
+ printf(" array %u\n", ld->span[j].array_ref);
+ p += config->log_drv_size;
+ }
+
+ for (i = 0; i < config->spares_count; i++) {
+ sp = (struct mfi_spare *)p;
+ printf(" %s spare %s ",
+ sp->spare_type & MFI_SPARE_DEDICATED ? "dedicated" :
+ "global", mfi_drive_name(NULL, sp->ref.v.device_id,
+ MFI_DNAME_DEVICE_ID|MFI_DNAME_HONOR_OPTS));
+ if (mfi_pd_get_info(fd, sp->ref.v.device_id, &pinfo, NULL) < 0)
+ printf("%s", mfi_pdstate(MFI_PD_STATE_HOT_SPARE));
+ else
+ print_pd(&pinfo, -1);
+ if (sp->spare_type & MFI_SPARE_DEDICATED) {
+ printf(" backs:\n");
+ for (j = 0; j < sp->array_count; j++)
+ printf(" array %u\n", sp->array_ref[j]);
+ } else
+ printf("\n");
+ p += config->spares_size;
+ }
+ free(config);
+ close(fd);
+
+ return (0);
+}
+MFI_COMMAND(show, config, show_config);
+
+static int
+show_volumes(int ac, char **av __unused)
+{
+ struct mfi_ld_list list;
+ struct mfi_ld_info info;
+ int error, fd;
+ u_int i, len, state_len;
+
+ if (ac != 1) {
+ warnx("show volumes: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDONLY);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ /* Get the logical drive list from the controller. */
+ if (mfi_ld_get_list(fd, &list, NULL) < 0) {
+ error = errno;
+ warn("Failed to get volume list");
+ close(fd);
+ return (error);
+ }
+
+ /* List the volumes. */
+ printf("mfi%d Volumes:\n", mfi_unit);
+ state_len = strlen("State");
+ for (i = 0; i < list.ld_count; i++) {
+ len = strlen(mfi_ldstate(list.ld_list[i].state));
+ if (len > state_len)
+ state_len = len;
+ }
+ printf(" Id Size Level Stripe ");
+ len = state_len - strlen("State");
+ for (i = 0; i < (len + 1) / 2; i++)
+ printf(" ");
+ printf("State");
+ for (i = 0; i < len / 2; i++)
+ printf(" ");
+ printf(" Cache Name\n");
+ for (i = 0; i < list.ld_count; i++) {
+ if (mfi_ld_get_info(fd, list.ld_list[i].ld.v.target_id, &info,
+ NULL) < 0) {
+ error = errno;
+ warn("Failed to get info for volume %d",
+ list.ld_list[i].ld.v.target_id);
+ close(fd);
+ return (error);
+ }
+ printf("%6s ",
+ mfi_volume_name(fd, list.ld_list[i].ld.v.target_id));
+ print_ld(&info, state_len);
+ switch (info.ld_config.properties.current_cache_policy &
+ (MR_LD_CACHE_ALLOW_WRITE_CACHE |
+ MR_LD_CACHE_ALLOW_READ_CACHE)) {
+ case 0:
+ printf(" Disabled");
+ break;
+ case MR_LD_CACHE_ALLOW_READ_CACHE:
+ printf(" Reads ");
+ break;
+ case MR_LD_CACHE_ALLOW_WRITE_CACHE:
+ printf(" Writes ");
+ break;
+ case MR_LD_CACHE_ALLOW_WRITE_CACHE |
+ MR_LD_CACHE_ALLOW_READ_CACHE:
+ printf(" Enabled ");
+ break;
+ }
+ if (info.ld_config.properties.name[0] != '\0')
+ printf(" <%s>", info.ld_config.properties.name);
+ printf("\n");
+ }
+ close(fd);
+
+ return (0);
+}
+MFI_COMMAND(show, volumes, show_volumes);
+
+static int
+show_drives(int ac, char **av __unused)
+{
+ struct mfi_pd_list *list;
+ struct mfi_pd_info info;
+ u_int i, len, state_len;
+ int error, fd;
+
+ if (ac != 1) {
+ warnx("show drives: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDONLY);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ list = NULL;
+ if (mfi_pd_get_list(fd, &list, NULL) < 0) {
+ error = errno;
+ warn("Failed to get drive list");
+ goto error;
+ }
+
+ /* Walk the list of drives to determine width of state column. */
+ state_len = 0;
+ for (i = 0; i < list->count; i++) {
+ if (list->addr[i].scsi_dev_type != 0)
+ continue;
+
+ if (mfi_pd_get_info(fd, list->addr[i].device_id, &info,
+ NULL) < 0) {
+ error = errno;
+ warn("Failed to fetch info for drive %u",
+ list->addr[i].device_id);
+ goto error;
+ }
+ len = strlen(mfi_pdstate(info.fw_state));
+ if (info.state.ddf.v.pd_type.is_foreign)
+ len += strlen(foreign_state);
+ if (len > state_len)
+ state_len = len;
+ }
+
+ /* List the drives. */
+ printf("mfi%d Physical Drives:\n", mfi_unit);
+ for (i = 0; i < list->count; i++) {
+
+ /* Skip non-hard disks. */
+ if (list->addr[i].scsi_dev_type != 0)
+ continue;
+
+ /* Fetch details for this drive. */
+ if (mfi_pd_get_info(fd, list->addr[i].device_id, &info,
+ NULL) < 0) {
+ error = errno;
+ warn("Failed to fetch info for drive %u",
+ list->addr[i].device_id);
+ goto error;
+ }
+
+ printf("%s ", mfi_drive_name(&info, list->addr[i].device_id,
+ MFI_DNAME_DEVICE_ID));
+ print_pd(&info, state_len);
+ printf(" %s", mfi_drive_name(&info, list->addr[i].device_id,
+ MFI_DNAME_ES));
+ printf("\n");
+ }
+ error = 0;
+error:
+ free(list);
+ close(fd);
+
+ return (error);
+}
+MFI_COMMAND(show, drives, show_drives);
+
+static int
+show_firmware(int ac, char **av __unused)
+{
+ struct mfi_ctrl_info info;
+ struct mfi_info_component header;
+ int error, fd;
+ u_int i;
+
+ if (ac != 1) {
+ warnx("show firmware: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDONLY);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ if (mfi_ctrl_get_info(fd, &info, NULL) < 0) {
+ error = errno;
+ warn("Failed to get controller info");
+ close(fd);
+ return (error);
+ }
+
+ if (info.package_version[0] != '\0')
+ printf("mfi%d Firmware Package Version: %s\n", mfi_unit,
+ info.package_version);
+ printf("mfi%d Firmware Images:\n", mfi_unit);
+ strcpy(header.name, "Name");
+ strcpy(header.version, "Version");
+ strcpy(header.build_date, "Date");
+ strcpy(header.build_time, "Time");
+ scan_firmware(&header);
+ if (info.image_component_count > 8)
+ info.image_component_count = 8;
+ for (i = 0; i < info.image_component_count; i++)
+ scan_firmware(&info.image_component[i]);
+ if (info.pending_image_component_count > 8)
+ info.pending_image_component_count = 8;
+ for (i = 0; i < info.pending_image_component_count; i++)
+ scan_firmware(&info.pending_image_component[i]);
+ display_firmware(&header, "Status");
+ for (i = 0; i < info.image_component_count; i++)
+ display_firmware(&info.image_component[i], "active");
+ for (i = 0; i < info.pending_image_component_count; i++)
+ display_firmware(&info.pending_image_component[i], "pending");
+
+ close(fd);
+
+ return (0);
+}
+MFI_COMMAND(show, firmware, show_firmware);
+
+static int
+show_progress(int ac, char **av __unused)
+{
+ struct mfi_ld_list llist;
+ struct mfi_pd_list *plist;
+ struct mfi_ld_info linfo;
+ struct mfi_pd_info pinfo;
+ int busy, error, fd;
+ u_int i;
+ uint16_t device_id;
+ uint8_t target_id;
+
+ if (ac != 1) {
+ warnx("show progress: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDONLY);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ if (mfi_ld_get_list(fd, &llist, NULL) < 0) {
+ error = errno;
+ warn("Failed to get volume list");
+ close(fd);
+ return (error);
+ }
+ if (mfi_pd_get_list(fd, &plist, NULL) < 0) {
+ error = errno;
+ warn("Failed to get drive list");
+ close(fd);
+ return (error);
+ }
+
+ busy = 0;
+ for (i = 0; i < llist.ld_count; i++) {
+ target_id = llist.ld_list[i].ld.v.target_id;
+ if (mfi_ld_get_info(fd, target_id, &linfo, NULL) < 0) {
+ error = errno;
+ warn("Failed to get info for volume %s",
+ mfi_volume_name(fd, target_id));
+ free(plist);
+ close(fd);
+ return (error);
+ }
+ if (linfo.progress.active & MFI_LD_PROGRESS_CC) {
+ printf("volume %s ", mfi_volume_name(fd, target_id));
+ mfi_display_progress("Consistency Check",
+ &linfo.progress.cc);
+ busy = 1;
+ }
+ if (linfo.progress.active & MFI_LD_PROGRESS_BGI) {
+ printf("volume %s ", mfi_volume_name(fd, target_id));
+ mfi_display_progress("Background Init",
+ &linfo.progress.bgi);
+ busy = 1;
+ }
+ if (linfo.progress.active & MFI_LD_PROGRESS_FGI) {
+ printf("volume %s ", mfi_volume_name(fd, target_id));
+ mfi_display_progress("Foreground Init",
+ &linfo.progress.fgi);
+ busy = 1;
+ }
+ if (linfo.progress.active & MFI_LD_PROGRESS_RECON) {
+ printf("volume %s ", mfi_volume_name(fd, target_id));
+ mfi_display_progress("Reconstruction",
+ &linfo.progress.recon);
+ busy = 1;
+ }
+ }
+
+ for (i = 0; i < plist->count; i++) {
+ if (plist->addr[i].scsi_dev_type != 0)
+ continue;
+
+ device_id = plist->addr[i].device_id;
+ if (mfi_pd_get_info(fd, device_id, &pinfo, NULL) < 0) {
+ error = errno;
+ warn("Failed to fetch info for drive %u", device_id);
+ free(plist);
+ close(fd);
+ return (error);
+ }
+
+ if (pinfo.prog_info.active & MFI_PD_PROGRESS_REBUILD) {
+ printf("drive %s ", mfi_drive_name(NULL, device_id,
+ MFI_DNAME_DEVICE_ID|MFI_DNAME_HONOR_OPTS));
+ mfi_display_progress("Rebuild", &pinfo.prog_info.rbld);
+ busy = 1;
+ }
+ if (pinfo.prog_info.active & MFI_PD_PROGRESS_PATROL) {
+ printf("drive %s ", mfi_drive_name(NULL, device_id,
+ MFI_DNAME_DEVICE_ID|MFI_DNAME_HONOR_OPTS));
+ mfi_display_progress("Patrol Read",
+ &pinfo.prog_info.patrol);
+ busy = 1;
+ }
+ if (pinfo.prog_info.active & MFI_PD_PROGRESS_CLEAR) {
+ printf("drive %s ", mfi_drive_name(NULL, device_id,
+ MFI_DNAME_DEVICE_ID|MFI_DNAME_HONOR_OPTS));
+ mfi_display_progress("Clear", &pinfo.prog_info.clear);
+ busy = 1;
+ }
+ }
+
+ free(plist);
+ close(fd);
+
+ if (!busy)
+ printf("No activity in progress for adapter mfi%d\n", mfi_unit);
+
+ return (0);
+}
+MFI_COMMAND(show, progress, show_progress);
+
+static int
+show_foreign(int ac, char **av)
+{
+ return(display_format(ac, av, 0/*normal display*/, MFI_DCMD_CFG_FOREIGN_DISPLAY));
+}
+MFI_COMMAND(show, foreign, show_foreign);
diff --git a/usr.sbin/mfiutil/mfi_volume.c b/usr.sbin/mfiutil/mfi_volume.c
new file mode 100644
index 0000000..2306256
--- /dev/null
+++ b/usr.sbin/mfiutil/mfi_volume.c
@@ -0,0 +1,498 @@
+/*-
+ * Copyright (c) 2008, 2009 Yahoo!, 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.
+ * 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/types.h>
+#include <sys/errno.h>
+#include <err.h>
+#include <fcntl.h>
+#include <libutil.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "mfiutil.h"
+
+MFI_TABLE(top, volume);
+
+const char *
+mfi_ldstate(enum mfi_ld_state state)
+{
+ static char buf[16];
+
+ switch (state) {
+ case MFI_LD_STATE_OFFLINE:
+ return ("OFFLINE");
+ case MFI_LD_STATE_PARTIALLY_DEGRADED:
+ return ("PARTIALLY DEGRADED");
+ case MFI_LD_STATE_DEGRADED:
+ return ("DEGRADED");
+ case MFI_LD_STATE_OPTIMAL:
+ return ("OPTIMAL");
+ default:
+ sprintf(buf, "LSTATE 0x%02x", state);
+ return (buf);
+ }
+}
+
+void
+mbox_store_ldref(uint8_t *mbox, union mfi_ld_ref *ref)
+{
+
+ mbox[0] = ref->v.target_id;
+ mbox[1] = ref->v.reserved;
+ mbox[2] = ref->v.seq & 0xff;
+ mbox[3] = ref->v.seq >> 8;
+}
+
+int
+mfi_ld_get_list(int fd, struct mfi_ld_list *list, uint8_t *statusp)
+{
+
+ return (mfi_dcmd_command(fd, MFI_DCMD_LD_GET_LIST, list,
+ sizeof(struct mfi_ld_list), NULL, 0, statusp));
+}
+
+int
+mfi_ld_get_info(int fd, uint8_t target_id, struct mfi_ld_info *info,
+ uint8_t *statusp)
+{
+ uint8_t mbox[1];
+
+ mbox[0] = target_id;
+ return (mfi_dcmd_command(fd, MFI_DCMD_LD_GET_INFO, info,
+ sizeof(struct mfi_ld_info), mbox, 1, statusp));
+}
+
+static int
+mfi_ld_get_props(int fd, uint8_t target_id, struct mfi_ld_props *props)
+{
+ uint8_t mbox[1];
+
+ mbox[0] = target_id;
+ return (mfi_dcmd_command(fd, MFI_DCMD_LD_GET_PROP, props,
+ sizeof(struct mfi_ld_props), mbox, 1, NULL));
+}
+
+static int
+mfi_ld_set_props(int fd, struct mfi_ld_props *props)
+{
+ uint8_t mbox[4];
+
+ mbox_store_ldref(mbox, &props->ld);
+ return (mfi_dcmd_command(fd, MFI_DCMD_LD_SET_PROP, props,
+ sizeof(struct mfi_ld_props), mbox, 4, NULL));
+}
+
+static int
+update_cache_policy(int fd, struct mfi_ld_props *old, struct mfi_ld_props *new)
+{
+ int error;
+ uint8_t changes, policy;
+
+ if (old->default_cache_policy == new->default_cache_policy &&
+ old->disk_cache_policy == new->disk_cache_policy)
+ return (0);
+ policy = new->default_cache_policy;
+ changes = policy ^ old->default_cache_policy;
+ if (changes & MR_LD_CACHE_ALLOW_WRITE_CACHE)
+ printf("%s caching of I/O writes\n",
+ policy & MR_LD_CACHE_ALLOW_WRITE_CACHE ? "Enabling" :
+ "Disabling");
+ if (changes & MR_LD_CACHE_ALLOW_READ_CACHE)
+ printf("%s caching of I/O reads\n",
+ policy & MR_LD_CACHE_ALLOW_READ_CACHE ? "Enabling" :
+ "Disabling");
+ if (changes & MR_LD_CACHE_WRITE_BACK)
+ printf("Setting write cache policy to %s\n",
+ policy & MR_LD_CACHE_WRITE_BACK ? "write-back" :
+ "write-through");
+ if (changes & (MR_LD_CACHE_READ_AHEAD | MR_LD_CACHE_READ_ADAPTIVE))
+ printf("Setting read ahead policy to %s\n",
+ policy & MR_LD_CACHE_READ_AHEAD ?
+ (policy & MR_LD_CACHE_READ_ADAPTIVE ?
+ "adaptive" : "always") : "none");
+ if (changes & MR_LD_CACHE_WRITE_CACHE_BAD_BBU)
+ printf("%s write caching with bad BBU\n",
+ policy & MR_LD_CACHE_WRITE_CACHE_BAD_BBU ? "Enabling" :
+ "Disabling");
+ if (old->disk_cache_policy != new->disk_cache_policy) {
+ switch (new->disk_cache_policy) {
+ case MR_PD_CACHE_ENABLE:
+ printf("Enabling write-cache on physical drives\n");
+ break;
+ case MR_PD_CACHE_DISABLE:
+ printf("Disabling write-cache on physical drives\n");
+ break;
+ case MR_PD_CACHE_UNCHANGED:
+ printf("Using default write-cache setting on physical drives\n");
+ break;
+ }
+ }
+
+ if (mfi_ld_set_props(fd, new) < 0) {
+ error = errno;
+ warn("Failed to set volume properties");
+ return (error);
+ }
+ return (0);
+}
+
+static void
+stage_cache_setting(struct mfi_ld_props *props, uint8_t new_policy,
+ uint8_t mask)
+{
+
+ props->default_cache_policy &= ~mask;
+ props->default_cache_policy |= new_policy;
+}
+
+/*
+ * Parse a single cache directive modifying the passed in policy.
+ * Returns -1 on a parse error and the number of arguments consumed
+ * on success.
+ */
+static int
+process_cache_command(int ac, char **av, struct mfi_ld_props *props)
+{
+ uint8_t policy;
+
+ /* I/O cache settings. */
+ if (strcmp(av[0], "all") == 0 || strcmp(av[0], "enable") == 0) {
+ stage_cache_setting(props, MR_LD_CACHE_ALLOW_READ_CACHE |
+ MR_LD_CACHE_ALLOW_WRITE_CACHE,
+ MR_LD_CACHE_ALLOW_READ_CACHE |
+ MR_LD_CACHE_ALLOW_WRITE_CACHE);
+ return (1);
+ }
+ if (strcmp(av[0], "none") == 0 || strcmp(av[0], "disable") == 0) {
+ stage_cache_setting(props, 0, MR_LD_CACHE_ALLOW_READ_CACHE |
+ MR_LD_CACHE_ALLOW_WRITE_CACHE);
+ return (1);
+ }
+ if (strcmp(av[0], "reads") == 0) {
+ stage_cache_setting(props, MR_LD_CACHE_ALLOW_READ_CACHE,
+ MR_LD_CACHE_ALLOW_READ_CACHE |
+ MR_LD_CACHE_ALLOW_WRITE_CACHE);
+ return (1);
+ }
+ if (strcmp(av[0], "writes") == 0) {
+ stage_cache_setting(props, MR_LD_CACHE_ALLOW_WRITE_CACHE,
+ MR_LD_CACHE_ALLOW_READ_CACHE |
+ MR_LD_CACHE_ALLOW_WRITE_CACHE);
+ return (1);
+ }
+
+ /* Write cache behavior. */
+ if (strcmp(av[0], "write-back") == 0) {
+ stage_cache_setting(props, MR_LD_CACHE_WRITE_BACK,
+ MR_LD_CACHE_WRITE_BACK);
+ return (1);
+ }
+ if (strcmp(av[0], "write-through") == 0) {
+ stage_cache_setting(props, 0, MR_LD_CACHE_WRITE_BACK);
+ return (1);
+ }
+ if (strcmp(av[0], "bad-bbu-write-cache") == 0) {
+ if (ac < 2) {
+ warnx("cache: bad BBU setting required");
+ return (-1);
+ }
+ if (strcmp(av[1], "enable") == 0)
+ policy = MR_LD_CACHE_WRITE_CACHE_BAD_BBU;
+ else if (strcmp(av[1], "disable") == 0)
+ policy = 0;
+ else {
+ warnx("cache: invalid bad BBU setting");
+ return (-1);
+ }
+ stage_cache_setting(props, policy,
+ MR_LD_CACHE_WRITE_CACHE_BAD_BBU);
+ return (2);
+ }
+
+ /* Read cache behavior. */
+ if (strcmp(av[0], "read-ahead") == 0) {
+ if (ac < 2) {
+ warnx("cache: read-ahead setting required");
+ return (-1);
+ }
+ if (strcmp(av[1], "none") == 0)
+ policy = 0;
+ else if (strcmp(av[1], "always") == 0)
+ policy = MR_LD_CACHE_READ_AHEAD;
+ else if (strcmp(av[1], "adaptive") == 0)
+ policy = MR_LD_CACHE_READ_AHEAD |
+ MR_LD_CACHE_READ_ADAPTIVE;
+ else {
+ warnx("cache: invalid read-ahead setting");
+ return (-1);
+ }
+ stage_cache_setting(props, policy, MR_LD_CACHE_READ_AHEAD |
+ MR_LD_CACHE_READ_ADAPTIVE);
+ return (2);
+ }
+
+ /* Drive write-cache behavior. */
+ if (strcmp(av[0], "write-cache") == 0) {
+ if (ac < 2) {
+ warnx("cache: write-cache setting required");
+ return (-1);
+ }
+ if (strcmp(av[1], "enable") == 0)
+ props->disk_cache_policy = MR_PD_CACHE_ENABLE;
+ else if (strcmp(av[1], "disable") == 0)
+ props->disk_cache_policy = MR_PD_CACHE_DISABLE;
+ else if (strcmp(av[1], "default") == 0)
+ props->disk_cache_policy = MR_PD_CACHE_UNCHANGED;
+ else {
+ warnx("cache: invalid write-cache setting");
+ return (-1);
+ }
+ return (2);
+ }
+
+ warnx("cache: Invalid command");
+ return (-1);
+}
+
+static int
+volume_cache(int ac, char **av)
+{
+ struct mfi_ld_props props, new;
+ int error, fd, consumed;
+ uint8_t target_id;
+
+ if (ac < 2) {
+ warnx("cache: volume required");
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ if (mfi_lookup_volume(fd, av[1], &target_id) < 0) {
+ error = errno;
+ warn("Invalid volume: %s", av[1]);
+ close(fd);
+ return (error);
+ }
+
+ if (mfi_ld_get_props(fd, target_id, &props) < 0) {
+ error = errno;
+ warn("Failed to fetch volume properties");
+ close(fd);
+ return (error);
+ }
+
+ if (ac == 2) {
+ printf("mfi%u volume %s cache settings:\n", mfi_unit,
+ mfi_volume_name(fd, target_id));
+ printf(" I/O caching: ");
+ switch (props.default_cache_policy &
+ (MR_LD_CACHE_ALLOW_WRITE_CACHE |
+ MR_LD_CACHE_ALLOW_READ_CACHE)) {
+ case 0:
+ printf("disabled\n");
+ break;
+ case MR_LD_CACHE_ALLOW_WRITE_CACHE:
+ printf("writes\n");
+ break;
+ case MR_LD_CACHE_ALLOW_READ_CACHE:
+ printf("reads\n");
+ break;
+ case MR_LD_CACHE_ALLOW_WRITE_CACHE |
+ MR_LD_CACHE_ALLOW_READ_CACHE:
+ printf("writes and reads\n");
+ break;
+ }
+ printf(" write caching: %s\n",
+ props.default_cache_policy & MR_LD_CACHE_WRITE_BACK ?
+ "write-back" : "write-through");
+ printf("write cache with bad BBU: %s\n",
+ props.default_cache_policy &
+ MR_LD_CACHE_WRITE_CACHE_BAD_BBU ? "enabled" : "disabled");
+ printf(" read ahead: %s\n",
+ props.default_cache_policy & MR_LD_CACHE_READ_AHEAD ?
+ (props.default_cache_policy & MR_LD_CACHE_READ_ADAPTIVE ?
+ "adaptive" : "always") : "none");
+ printf(" drive write cache: ");
+ switch (props.disk_cache_policy) {
+ case MR_PD_CACHE_UNCHANGED:
+ printf("default\n");
+ break;
+ case MR_PD_CACHE_ENABLE:
+ printf("enabled\n");
+ break;
+ case MR_PD_CACHE_DISABLE:
+ printf("disabled\n");
+ break;
+ default:
+ printf("??? %d\n", props.disk_cache_policy);
+ break;
+ }
+ if (props.default_cache_policy != props.current_cache_policy)
+ printf(
+ "Cache disabled due to dead battery or ongoing battery relearn\n");
+ error = 0;
+ } else {
+ new = props;
+ av += 2;
+ ac -= 2;
+ while (ac > 0) {
+ consumed = process_cache_command(ac, av, &new);
+ if (consumed < 0) {
+ close(fd);
+ return (EINVAL);
+ }
+ av += consumed;
+ ac -= consumed;
+ }
+ error = update_cache_policy(fd, &props, &new);
+ }
+ close(fd);
+
+ return (error);
+}
+MFI_COMMAND(top, cache, volume_cache);
+
+static int
+volume_name(int ac, char **av)
+{
+ struct mfi_ld_props props;
+ int error, fd;
+ uint8_t target_id;
+
+ if (ac != 3) {
+ warnx("name: volume and name required");
+ return (EINVAL);
+ }
+
+ if (strlen(av[2]) >= sizeof(props.name)) {
+ warnx("name: new name is too long");
+ return (ENOSPC);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDWR);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ if (mfi_lookup_volume(fd, av[1], &target_id) < 0) {
+ error = errno;
+ warn("Invalid volume: %s", av[1]);
+ close(fd);
+ return (error);
+ }
+
+ if (mfi_ld_get_props(fd, target_id, &props) < 0) {
+ error = errno;
+ warn("Failed to fetch volume properties");
+ close(fd);
+ return (error);
+ }
+
+ printf("mfi%u volume %s name changed from \"%s\" to \"%s\"\n", mfi_unit,
+ mfi_volume_name(fd, target_id), props.name, av[2]);
+ bzero(props.name, sizeof(props.name));
+ strcpy(props.name, av[2]);
+ if (mfi_ld_set_props(fd, &props) < 0) {
+ error = errno;
+ warn("Failed to set volume properties");
+ close(fd);
+ return (error);
+ }
+
+ close(fd);
+
+ return (0);
+}
+MFI_COMMAND(top, name, volume_name);
+
+static int
+volume_progress(int ac, char **av)
+{
+ struct mfi_ld_info info;
+ int error, fd;
+ uint8_t target_id;
+
+ if (ac != 2) {
+ warnx("volume progress: %s", ac > 2 ? "extra arguments" :
+ "volume required");
+ return (EINVAL);
+ }
+
+ fd = mfi_open(mfi_unit, O_RDONLY);
+ if (fd < 0) {
+ error = errno;
+ warn("mfi_open");
+ return (error);
+ }
+
+ if (mfi_lookup_volume(fd, av[1], &target_id) < 0) {
+ error = errno;
+ warn("Invalid volume: %s", av[1]);
+ close(fd);
+ return (error);
+ }
+
+ /* Get the info for this drive. */
+ if (mfi_ld_get_info(fd, target_id, &info, NULL) < 0) {
+ error = errno;
+ warn("Failed to fetch info for volume %s",
+ mfi_volume_name(fd, target_id));
+ close(fd);
+ return (error);
+ }
+
+ /* Display any of the active events. */
+ if (info.progress.active & MFI_LD_PROGRESS_CC)
+ mfi_display_progress("Consistency Check", &info.progress.cc);
+ if (info.progress.active & MFI_LD_PROGRESS_BGI)
+ mfi_display_progress("Background Init", &info.progress.bgi);
+ if (info.progress.active & MFI_LD_PROGRESS_FGI)
+ mfi_display_progress("Foreground Init", &info.progress.fgi);
+ if (info.progress.active & MFI_LD_PROGRESS_RECON)
+ mfi_display_progress("Reconstruction", &info.progress.recon);
+ if ((info.progress.active & (MFI_LD_PROGRESS_CC | MFI_LD_PROGRESS_BGI |
+ MFI_LD_PROGRESS_FGI | MFI_LD_PROGRESS_RECON)) == 0)
+ printf("No activity in progress for volume %s.\n",
+ mfi_volume_name(fd, target_id));
+ close(fd);
+
+ return (0);
+}
+MFI_COMMAND(volume, progress, volume_progress);
diff --git a/usr.sbin/mfiutil/mfiutil.8 b/usr.sbin/mfiutil/mfiutil.8
new file mode 100644
index 0000000..e3adc0b
--- /dev/null
+++ b/usr.sbin/mfiutil/mfiutil.8
@@ -0,0 +1,723 @@
+.\" Copyright (c) 2008, 2009 Yahoo!, 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.
+.\" 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 September 2, 2011
+.Dt MFIUTIL 8
+.Os
+.Sh NAME
+.Nm mfiutil
+.Nd Utility for managing LSI MegaRAID SAS controllers
+.Sh SYNOPSIS
+.Nm
+.Cm version
+.Nm
+.Op Fl u Ar unit
+.Cm show adapter
+.Nm
+.Op Fl u Ar unit
+.Cm show battery
+.Nm
+.Op Fl d
+.Op Fl e
+.Op Fl u Ar unit
+.Cm show config
+.Nm
+.Op Fl u Ar unit
+.Cm show drives
+.Nm
+.Op Fl u Ar unit
+.Cm show events
+.Op Fl c Ar class
+.Op Fl l Ar locale
+.Op Fl n Ar count
+.Op Fl v
+.Op Ar start Op Ar stop
+.Nm
+.Op Fl u Ar unit
+.Cm show firmware
+.Nm
+.Op Fl u Ar unit
+.Cm show foreign Op Ar volume
+.Nm
+.Op Fl u Ar unit
+.Cm show logstate
+.Nm
+.Op Fl d
+.Op Fl e
+.Op Fl u Ar unit
+.Cm show patrol
+.Nm
+.Op Fl d
+.Op Fl e
+.Op Fl u Ar unit
+.Cm show progress
+.Nm
+.Op Fl u Ar unit
+.Cm show volumes
+.Nm
+.Op Fl u Ar unit
+.Cm fail Ar drive
+.Nm
+.Op Fl u Ar unit
+.Cm good Ar drive
+.Nm
+.Op Fl u Ar unit
+.Cm rebuild Ar drive
+.Nm
+.Op Fl u Ar unit
+.Cm syspd Ar drive
+.Nm
+.Op Fl u Ar unit
+.Cm drive progress Ar drive
+.Nm
+.Op Fl u Ar unit
+.Cm drive clear Ar drive Brq "start | stop"
+.Nm
+.Op Fl u Ar unit
+.Cm start rebuild Ar drive
+.Nm
+.Op Fl u Ar unit
+.Cm abort rebuild Ar drive
+.Nm
+.Op Fl u Ar unit
+.Cm locate Ar drive Brq "on | off"
+.Nm
+.Op Fl u Ar unit
+.Cm cache Ar volume Op Ar setting Oo Ar value Oc Op ...
+.Nm
+.Op Fl u Ar unit
+.Cm name Ar volume Ar name
+.Nm
+.Op Fl u Ar unit
+.Cm volume progress Ar volume
+.Nm
+.Op Fl u Ar unit
+.Cm clear
+.Nm
+.Op Fl u Ar unit
+.Cm create Ar type
+.Op Fl v
+.Op Fl s Ar stripe_size
+.Ar drive Ns Op \&, Ns Ar drive Ns Op ",..."
+.Op Ar drive Ns Op \&, Ns Ar drive Ns Op ",..."
+.Nm
+.Op Fl u Ar unit
+.Cm delete Ar volume
+.Nm
+.Op Fl u Ar unit
+.Cm add Ar drive Op Ar volume
+.Nm
+.Op Fl u Ar unit
+.Cm remove Ar drive
+.Nm
+.Op Fl u Ar unit
+.Cm start patrol
+.Nm
+.Op Fl u Ar unit
+.Cm stop patrol
+.Nm
+.Op Fl u Ar unit
+.Cm patrol Ar command Op Ar interval Op Ar start
+.Nm
+.Op Fl u Ar unit
+.Cm foreign scan
+.Nm
+.Op Fl u Ar unit
+.Cm foreign clear Op Ar config
+.Nm
+.Op Fl u Ar unit
+.Cm foreign diag Op Ar config
+.Nm
+.Op Fl u Ar unit
+.Cm foreign preview Op Ar config
+.Nm
+.Op Fl u Ar unit
+.Cm foreign import Op Ar config
+.Nm
+.Op Fl u Ar unit
+.Cm flash Ar file
+.Nm
+.Op Fl u Ar unit
+.Cm start learn
+.Nm
+.Op Fl u Ar unit
+.Cm bbu Ar setting Ar value
+.Nm
+.Op Fl u Ar unit
+.Cm ctrlprop Ar rebuild Op Ar rate
+.Nm
+.Op Fl u Ar unit
+.Cm ctrlprop Ar alarm Op Ar 0/1
+.Sh DESCRIPTION
+The
+.Nm
+utility can be used to display or modify various parameters on LSI
+MegaRAID SAS RAID controllers.
+Each invocation of
+.Nm
+consists of zero or more global options followed by a command.
+Commands may support additional optional or required arguments after the
+command.
+.Pp
+Currently one global option is supported:
+.Bl -tag -width indent
+.It Fl u Ar unit
+.Ar unit
+specifies the unit of the controller to work with.
+If no unit is specified,
+then unit 0 is used.
+.El
+.Pp
+Various commands accept either or both of the two options:
+.Bl -tag -width indent
+.It Fl d
+Print numeric device IDs as drive identifier.
+This is the default.
+Useful in combination with
+.Fl e
+to print both, numeric device IDs and enclosure:slot information.
+.It Fl e
+Print drive identifiers in enclosure:slot form.
+See next paragraph on format details in context of input rather than
+output.
+.El
+.Pp
+Drives may be specified in two forms.
+First,
+a drive may be identified by its device ID.
+The device ID for configured drives can be found in
+.Cm show config .
+Second,
+a drive may be identified by its location as
+.Sm off
+.Op E Ar xx Ns \&:
+.Li S Ns Ar yy
+.Sm on
+where
+.Ar xx
+is the enclosure
+and
+.Ar yy
+is the slot for each drive as displayed in
+.Cm show drives .
+.Pp
+Volumes may be specified in two forms.
+First,
+a volume may be identified by its target ID.
+Second,
+on the volume may be specified by the corresponding
+.Em mfidX
+device,
+such as
+.Em mfid0 .
+.Pp
+The
+.Nm
+utility supports several different groups of commands.
+The first group of commands provide information about the controller,
+the volumes it manages, and the drives it controls.
+The second group of commands are used to manage the physical drives
+attached to the controller.
+The third group of commands are used to manage the logical volumes
+managed by the controller.
+The fourth group of commands are used to manage the drive configuration for
+the controller.
+The fifth group of commands are used to manage controller-wide operations.
+.Pp
+The informational commands include:
+.Bl -tag -width indent
+.It Cm version
+Displays the version of
+.Nm .
+.It Cm show adapter
+Displays information about the RAID controller such as the model number.
+.It Cm show battery
+Displays information about the battery from the battery backup unit.
+.It Cm show config
+Displays the volume and drive configuration for the controller.
+Each array is listed along with the physical drives the array is built from.
+Each volume is listed along with the arrays that the volume spans.
+If any hot spare drives are configured, then they are listed as well.
+.It Cm show drives
+Lists all of the physical drives attached to the controller.
+.It Xo Cm show events
+.Op Fl c Ar class
+.Op Fl l Ar locale
+.Op Fl n Ar count
+.Op Fl v
+.Op Ar start Op Ar stop
+.Xc
+Display entries from the controller's event log.
+The controller maintains a circular buffer of events.
+Each event is tagged with a class and locale.
+.Pp
+The
+.Ar class
+parameter limits the output to entries at the specified class or higher.
+The default class is
+.Dq warn .
+The available classes from lowest priority to highest are:
+.Bl -tag -width indent
+.It Cm debug
+Debug messages.
+.It Cm progress
+Periodic progress updates for long-running operations such as background
+initializations, array rebuilds, or patrol reads.
+.It Cm info
+Informational messages such as drive insertions and volume creations.
+.It Cm warn
+Indicates that some component may be close to failing.
+.It Cm crit
+A component has failed, but no data is lost.
+For example, a volume becoming degraded due to a drive failure.
+.It Cm fatal
+A component has failed resulting in data loss.
+.It Cm dead
+The controller itself has died.
+.El
+.Pp
+The
+.Ar locale
+parameter limits the output to entries for the specified part of the controller.
+The default locale is
+.Dq all .
+The available locales are
+.Dq volume ,
+.Dq drive ,
+.Dq enclosure ,
+.Dq battery ,
+.Dq sas ,
+.Dq controller ,
+.Dq config ,
+.Dq cluster ,
+and
+.Dq all .
+.Pp
+The
+.Ar count
+parameter is a debugging aid that specifies the number of events to fetch from
+the controller for each low-level request.
+The default is 15 events.
+.Pp
+By default, matching event log entries from the previous shutdown up to the
+present are displayed. This range can be adjusted via the
+.Ar start
+and
+.Ar stop
+parameters.
+Each of these parameters can either be specified as a log entry number or as
+one of the following aliases:
+.Bl -tag -width indent
+.It Cm newest
+The newest entry in the event log.
+.It Cm oldest
+The oldest entry in the event log.
+.It Cm clear
+The first entry since the event log was cleared.
+.It Cm shutdown
+The entry in the event log corresponding to the last time the controller was
+cleanly shut down.
+.It Cm boot
+The entry in the event log corresponding to the most recent boot.
+.El
+.It Cm show firmware
+Lists all of the firmware images present on the controller.
+.It Cm show foreign
+Displays detected foreign configurations on disks for importation or removal.
+.It Cm show logstate
+Display the various sequence numbers associated with the event log.
+.It Cm show patrol
+Display the status of the controller's patrol read operation.
+.It Cm show progress
+Report the current progress and estimated completion time for active
+operations on all volumes and drives.
+.It Cm show volumes
+Lists all of the logical volumes managed by the controller.
+.El
+.Pp
+The physical drive management commands include:
+.Bl -tag -width indent
+.It Cm fail Ar drive
+Mark
+.Ar drive
+as failed.
+.Ar Drive
+must be an online drive that is part of an array.
+.It Cm good Ar drive
+Mark
+.Ar drive
+as an unconfigured good drive.
+.Ar Drive
+must not be part of an existing array.
+.It Cm rebuild Ar drive
+Mark a failed
+.Ar drive
+that is still part of an array as a good drive suitable for a rebuild.
+The firmware should kick off an array rebuild on its own if a failed drive
+is marked as a rebuild drive.
+.It Cm syspd Ar drive
+Present the drive to the host operating system as a disk SYSPD block device in
+the format /dev/mfisyspdX. Clear this flag with
+.Cm good
+.Ar drive
+.It Cm drive progress Ar drive
+Report the current progress and estimated completion time of drive operations
+such as rebuilds or patrol reads.
+.It Cm drive clear Ar drive Brq "start | stop"
+Start or stop the writing of all 0x00 characters to a drive.
+.It Cm start rebuild Ar drive
+Manually start a rebuild on
+.Ar drive .
+.It Cm abort rebuild Ar drive
+Abort an in-progress rebuild operation on
+.Ar drive .
+It can be resumed with the
+.Cm start rebuild
+command.
+.It Cm locate Ar drive Brq "on | off"
+Change the state of the external LED associated with
+.Ar drive .
+.El
+.Pp
+The logical volume management commands include:
+.Bl -tag -width indent
+.It Cm cache Ar volume Op Ar setting Oo Ar value Oc Op ...
+If no
+.Ar setting
+arguments are supplied, then the current cache policy for
+.Ar volume
+is displayed;
+otherwise,
+the cache policy for
+.Ar volume
+is modified.
+One or more
+.Ar setting
+arguments may be given.
+Some settings take an additional
+.Ar value
+argument as noted below.
+The valid settings are:
+.Bl -tag -width indent
+.It Cm enable
+Enable caching for both read and write I/O operations.
+.It Cm disable
+Disable caching for both read and write I/O operations.
+.It Cm reads
+Enable caching only for read I/O operations.
+.It Cm writes
+Enable caching only for write I/O operations.
+.It Cm write-back
+Use write-back policy for cached writes.
+.It Cm write-through
+Use write-through policy for cached writes.
+.It Cm read-ahead Ar value
+Set the read ahead policy for cached reads.
+The
+.Ar value
+argument can be set to either
+.Dq none ,
+.Dq adaptive ,
+or
+.Dq always .
+.It Cm bad-bbu-write-cache Ar value
+Control the behavior of I/O write caching if the battery is dead or
+missing.
+The
+.Ar value
+argument can be set to either
+.Dq disable
+or
+.Dq enable .
+In general this setting should be left disabled to avoid data loss when
+the system loses power.
+.It Cm write-cache Ar value
+Control the write caches on the physical drives backing
+.Ar volume .
+The
+.Ar value
+argument can be set to either
+.Dq disable ,
+.Dq enable ,
+or
+.Dq default .
+.Pp
+In general this setting should be left disabled to avoid data loss when the
+physical drives lose power.
+The battery backup of the RAID controller does not save data in the write
+caches of the physical drives.
+.El
+.It Cm name Ar volume Ar name
+Sets the name of
+.Ar volume
+to
+.Ar name .
+.It Cm volume progress Ar volume
+Report the current progress and estimated completion time of volume operations
+such as consistency checks and initializations.
+.El
+.Pp
+The configuration commands include:
+.Bl -tag -width indent
+.It Cm clear
+Delete the entire configuration including all volumes, arrays, and spares.
+.It Xo Cm create Ar type
+.Op Fl v
+.Op Fl s Ar stripe_size
+.Ar drive Ns Op \&, Ns Ar drive Ns Op ",..."
+.Op Ar drive Ns Op \&, Ns Ar drive Ns Op ",..."
+.Xc
+Create a new volume.
+The
+.Ar type
+specifies the type of volume to create.
+Currently supported types include:
+.Bl -tag -width indent
+.It Cm jbod
+Creates a RAID0 volume for each drive specified.
+Each drive must be specified as a separate argument.
+.It Cm raid0
+Creates one RAID0 volume spanning the drives listed in the single drive list.
+.It Cm raid1
+Creates one RAID1 volume spanning the drives listed in the single drive list.
+.It Cm raid5
+Creates one RAID5 volume spanning the drives listed in the single drive list.
+.It Cm raid6
+Creates one RAID6 volume spanning the drives listed in the single drive list.
+.It Cm raid10
+Creates one RAID10 volume spanning multiple RAID1 arrays.
+The drives for each RAID1 array are specified as a single drive list.
+.It Cm raid50
+Creates one RAID50 volume spanning multiple RAID5 arrays.
+The drives for each RAID5 array are specified as a single drive list.
+.It Cm raid60
+Creates one RAID60 volume spanning multiple RAID6 arrays.
+The drives for each RAID6 array are specified as a single drive list.
+.It Cm concat
+Creates a single volume by concatenating all of the drives in the single drive
+list.
+.El
+.Pp
+.Sy Note:
+Not all volume types are supported by all controllers.
+.Pp
+If the
+.Fl v
+flag is specified after
+.Ar type ,
+then more verbose output will be enabled.
+Currently this just provides notification as drives are added to arrays and
+arrays to volumes when building the configuration.
+.Pp
+The
+.Fl s
+.Ar stripe_size
+parameter allows the stripe size of the array to be set.
+By default a stripe size of 64K is used.
+Valid values are 512 through 1M, though the MFI firmware may reject some
+values.
+.It Cm delete Ar volume
+Delete the volume
+.Ar volume .
+.It Cm add Ar drive Op Ar volume
+Mark
+.Ar drive
+as a hot spare.
+.Ar Drive
+must be in the unconfigured good state.
+If
+.Ar volume
+is specified,
+then the hot spare will be dedicated to arrays backing that volume.
+Otherwise,
+.Ar drive
+will be used as a global hot spare backing all arrays for this controller.
+Note that
+.Ar drive
+must be as large as the smallest drive in all of the arrays it is going to
+back.
+.It Cm remove Ar drive
+Remove the hot spare
+.Ar drive
+from service.
+It will be placed in the unconfigured good state.
+.El
+.Pp
+The controller management commands include:
+.Bl -tag -width indent
+.It Cm patrol Ar command Op Ar interval Op Ar start
+Set the patrol read operation mode.
+The
+.Ar command
+argument can be one of the following values:
+.Bl -tag -width indent
+.It Cm disable
+Disable patrol reads.
+.It Cm auto
+Enable periodic patrol reads initiated by the firmware.
+The optional
+.Ar interval
+argument specifies the interval in seconds between patrol reads.
+If patrol reads should be run continuously,
+then
+.Ar interval
+should consist of the word
+.Dq continuously .
+The optional
+.Ar start
+argument specifies a non-negative, relative start time for the next patrol read.
+If an interval or start time is not specified,
+then the existing setting will be used.
+.It Cm manual
+Enable manual patrol reads that are only initiated by the user.
+.El
+.It Cm start patrol
+Start a patrol read operation.
+.It Cm stop patrol
+Stop a currently running patrol read operation.
+.It Cm foreign scan
+Scan for foreign configurations and display the number found. The
+.Ar config
+argument for the commands below takes the form of a number from 0 to the total
+configurations found.
+.It Cm foreign clear Op config
+Clear the specified foreign
+.Ar config
+or all if no
+.Ar config
+argument is provided.
+.It Cm foreign diag Op config
+Display a diagnostic display of the specified foreign
+.Ar config
+or all if no
+.Ar config
+argument is provided.
+.It Cm foreign preview Op config
+Preview the specified foreign
+.Ar config
+after import or all if no
+.Ar config
+argument is provided.
+.It Cm foreign import Op config
+Import the specified foreign
+.Ar config
+or all if no
+.Ar config
+argument is provided.
+.It Cm flash Ar file
+Updates the flash on the controller with the firmware stored in
+.Ar file .
+A reboot is required for the new firmware to take effect.
+.It Cm start learn
+Start a battery relearn.
+Note that this seems to always result in the battery being completely drained,
+regardless of the BBU mode.
+In particular, the controller write cache will be disabled during the relearn
+even if transparent learning mode is enabled.
+.It Cm bbu Ar setting Ar value
+Update battery backup unit (BBU) properties related to battery relearning.
+The following settings are configurable:
+.Bl -tag -width indent
+.It Cm learn-delay
+Add a delay to the next scheduled battery relearn event. This setting is
+given in hours and must lie in the range of 0 to 255.
+.It Cm autolearn-mode
+Enable or disable automatic periodic battery relearning.
+The setting may be set to
+.Dq enable
+or
+.Dq disable
+to respectively enable or disable the relearn cycle.
+Alternatively, a mode of 0, 1 or 2 may be given.
+Mode 0 enables periodic relearning, mode 1 disables it, and mode 2 disables
+it and logs a warning to the event log when it detects that a battery relearn
+should be performed.
+.It Cm bbu-mode
+Set the BBU's mode of operation. This setting is not supported by all BBUs.
+Where it is supported, the possible values are the integers between 1 and 5
+inclusive.
+Modes 1, 2 and 3 enable a transparent learn cycle, whereas modes 4 and 5 do not.
+The BBU's data retention time is greater when transparent learning is not used.
+.El
+.It Cm ctrlprop Ar rebuild Op Ar rate
+With no arguments display the rate of rebuild (percentage)a for volumes.
+With an integer argument (0-100), set that value as the new rebuild rate for volumes.
+.It Cm ctrlprop Ar alarm Op Ar 0/1
+With no arguments display the current alarm enable/disable status.
+With a 0, disable alarms. With a 1, enable alarms.
+.El
+.Sh EXAMPLES
+Configure the cache for volume mfid0 to cache only writes:
+.Pp
+.Dl Nm Cm cache mfid0 writes
+.Dl Nm Cm cache mfid0 write-back
+.Pp
+Create a RAID5 array spanning the first four disks in the second enclosure:
+.Pp
+.Dl Nm Cm create raid5 e1:s0,e1:s1,e1:s2,e1:s4
+.Pp
+Configure the first three disks on a controller as JBOD:
+.Pp
+.Dl Nm Cm create jbod 0 1 2
+.Pp
+Create a RAID10 volume that spans two arrays each of which contains two disks
+from two different enclosures:
+.Pp
+.Dl Nm Cm create raid10 e1:s0,e1:s1 e2:s0,e2:s1
+.Pp
+Add drive with the device ID of 4 as a global hot spare:
+.Pp
+.Dl Nm Cm add 4
+.Pp
+Add the drive in slot 2 in the main chassis as a hot spare for volume mfid0:
+.Pp
+.Dl Nm Cm add s2 mfid0
+.Pp
+Reconfigure a disk as a SYSPD block device with no RAID
+.Pp
+.Dl Nm Cm syspd 0
+.Pp
+Configure the adapter to run periodic patrol reads once a week with the first
+patrol read starting in 5 minutes:
+.Pp
+.Dl Nm Cm patrol auto 604800 300
+.Pp
+Display the second detected foreign configuration:
+.Pp
+.Dl Nm Cm show foreign 1
+.Pp
+Set the current rebuild rate for volumes to 40%:
+.Dl Nm Cm ctrlprop rebuild 40
+.Sh SEE ALSO
+.Xr mfi 4
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 8.0 .
diff --git a/usr.sbin/mfiutil/mfiutil.c b/usr.sbin/mfiutil/mfiutil.c
new file mode 100644
index 0000000..85ce25c
--- /dev/null
+++ b/usr.sbin/mfiutil/mfiutil.c
@@ -0,0 +1,185 @@
+/*-
+ * Copyright (c) 2008, 2009 Yahoo!, 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.
+ * 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/errno.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "mfiutil.h"
+
+SET_DECLARE(MFI_DATASET(top), struct mfiutil_command);
+
+MFI_TABLE(top, start);
+MFI_TABLE(top, stop);
+MFI_TABLE(top, abort);
+
+int mfi_unit;
+u_int mfi_opts;
+static int fw_name_width, fw_version_width, fw_date_width, fw_time_width;
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: mfiutil [-de] [-u unit] <command> ...\n\n");
+ fprintf(stderr, "Commands include:\n");
+ fprintf(stderr, " version\n");
+ fprintf(stderr, " show adapter - display controller information\n");
+ fprintf(stderr, " show battery - display battery information\n");
+ fprintf(stderr, " show config - display RAID configuration\n");
+ fprintf(stderr, " show drives - list physical drives\n");
+ fprintf(stderr, " show events - display event log\n");
+ fprintf(stderr, " show firmware - list firmware images\n");
+ fprintf(stderr, " show foreign - display detected foreign volumes\n");
+ fprintf(stderr, " show logstate - display event log sequence numbers\n");
+ fprintf(stderr, " show volumes - list logical volumes\n");
+ fprintf(stderr, " show patrol - display patrol read status\n");
+ fprintf(stderr, " show progress - display status of active operations\n");
+ fprintf(stderr, " fail <drive> - fail a physical drive\n");
+ fprintf(stderr, " good <drive> - set a failed/SYSPD drive as UNCONFIGURED\n");
+ fprintf(stderr, " rebuild <drive> - mark failed drive ready for rebuild\n");
+ fprintf(stderr, " syspd <drive> - set drive into use as SYSPD JBOD\n");
+ fprintf(stderr, " drive progress <drive> - display status of active operations\n");
+ fprintf(stderr, " drive clear <drive> <start|stop> - clear a drive with all 0x00\n");
+ fprintf(stderr, " start rebuild <drive>\n");
+ fprintf(stderr, " abort rebuild <drive>\n");
+ fprintf(stderr, " locate <drive> <on|off> - toggle drive LED\n");
+ fprintf(stderr, " cache <volume> [command [setting]]\n");
+ fprintf(stderr, " name <volume> <name>\n");
+ fprintf(stderr, " volume progress <volume> - display status of active operations\n");
+ fprintf(stderr, " clear - clear volume configuration\n");
+ fprintf(stderr, " create <type> [-v] [-s stripe_size] <drive>[,<drive>[,...]] [<drive>[,<drive>[,...]]\n");
+ fprintf(stderr, " delete <volume>\n");
+ fprintf(stderr, " add <drive> [volume] - add a hot spare\n");
+ fprintf(stderr, " remove <drive> - remove a hot spare\n");
+ fprintf(stderr, " patrol <disable|auto|manual> [interval [start]]\n");
+ fprintf(stderr, " start patrol - start a patrol read\n");
+ fprintf(stderr, " stop patrol - stop a patrol read\n");
+ fprintf(stderr, " foreign scan - scan for foreign configurations\n");
+ fprintf(stderr, " foreign clear [volume] - clear foreign configurations (default all)\n");
+ fprintf(stderr, " foreign diag [volume] - diagnostic display foreign configurations (default all)\n");
+ fprintf(stderr, " foreign preview [volume] - preview foreign configurations (default all)\n");
+ fprintf(stderr, " foreign import [volume] - import foreign configurations (default all)\n");
+ fprintf(stderr, " flash <firmware>\n");
+ fprintf(stderr, " start learn - start a BBU relearn\n");
+ fprintf(stderr, " bbu <setting> <value> - set BBU properties\n");
+ fprintf(stderr, " ctrlprop rebuild [rate] - get/set the volume rebuild rate\n");
+ fprintf(stderr, " ctrlprop alarm [0/1] - enable/disable controller alarms\n");
+#ifdef DEBUG
+ fprintf(stderr, " debug - debug 'show config'\n");
+ fprintf(stderr, " dump - display 'saved' config\n");
+#endif
+ exit(1);
+}
+
+static int
+version(int ac __unused, char **av __unused)
+{
+
+ printf("mfiutil version 1.0.15");
+#ifdef DEBUG
+ printf(" (DEBUG)");
+#endif
+ printf("\n");
+ return (0);
+}
+MFI_COMMAND(top, version, version);
+
+int
+main(int ac, char **av)
+{
+ struct mfiutil_command **cmd;
+ int ch;
+
+ while ((ch = getopt(ac, av, "deu:")) != -1) {
+ switch (ch) {
+ case 'd':
+ mfi_opts |= MFI_DNAME_DEVICE_ID;
+ break;
+ case 'e':
+ mfi_opts |= MFI_DNAME_ES;
+ break;
+ case 'u':
+ mfi_unit = atoi(optarg);
+ break;
+ case '?':
+ usage();
+ }
+ }
+
+ av += optind;
+ ac -= optind;
+
+ /* getopt() eats av[0], so we can't use mfi_table_handler() directly. */
+ if (ac == 0)
+ usage();
+
+ SET_FOREACH(cmd, MFI_DATASET(top)) {
+ if (strcmp((*cmd)->name, av[0]) == 0) {
+ if ((*cmd)->handler(ac, av))
+ return (1);
+ else
+ return (0);
+ }
+ }
+ warnx("Unknown command %s.", av[0]);
+ return (1);
+}
+
+void
+scan_firmware(struct mfi_info_component *comp)
+{
+ int len;
+
+ len = strlen(comp->name);
+ if (fw_name_width < len)
+ fw_name_width = len;
+ len = strlen(comp->version);
+ if (fw_version_width < len)
+ fw_version_width = len;
+ len = strlen(comp->build_date);
+ if (fw_date_width < len)
+ fw_date_width = len;
+ len = strlen(comp->build_time);
+ if (fw_time_width < len)
+ fw_time_width = len;
+}
+
+void
+display_firmware(struct mfi_info_component *comp, const char *tag)
+{
+
+ printf("%-*s %-*s %-*s %-*s %s\n", fw_name_width, comp->name,
+ fw_version_width, comp->version, fw_date_width, comp->build_date,
+ fw_time_width, comp->build_time, tag);
+}
diff --git a/usr.sbin/mfiutil/mfiutil.h b/usr.sbin/mfiutil/mfiutil.h
new file mode 100644
index 0000000..251816a
--- /dev/null
+++ b/usr.sbin/mfiutil/mfiutil.h
@@ -0,0 +1,183 @@
+/*-
+ * Copyright (c) 2008, 2009 Yahoo!, 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.
+ * 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$
+ */
+
+#ifndef __MFIUTIL_H__
+#define __MFIUTIL_H__
+
+#include <sys/cdefs.h>
+#include <sys/linker_set.h>
+
+#include <dev/mfi/mfireg.h>
+
+/* 4.x compat */
+#ifndef SET_DECLARE
+
+/* <sys/cdefs.h> */
+#define __used
+#define __section(x) __attribute__((__section__(x)))
+
+/* <sys/linker_set.h> */
+#undef __MAKE_SET
+#undef DATA_SET
+
+#define __MAKE_SET(set, sym) \
+ static void const * const __set_##set##_sym_##sym \
+ __section("set_" #set) __used = &sym
+
+#define DATA_SET(set, sym) __MAKE_SET(set, sym)
+
+#define SET_DECLARE(set, ptype) \
+ extern ptype *__CONCAT(__start_set_,set); \
+ extern ptype *__CONCAT(__stop_set_,set)
+
+#define SET_BEGIN(set) \
+ (&__CONCAT(__start_set_,set))
+#define SET_LIMIT(set) \
+ (&__CONCAT(__stop_set_,set))
+
+#define SET_FOREACH(pvar, set) \
+ for (pvar = SET_BEGIN(set); pvar < SET_LIMIT(set); pvar++)
+
+int humanize_number(char *_buf, size_t _len, int64_t _number,
+ const char *_suffix, int _scale, int _flags);
+
+/* humanize_number(3) */
+#define HN_DECIMAL 0x01
+#define HN_NOSPACE 0x02
+#define HN_B 0x04
+#define HN_DIVISOR_1000 0x08
+
+#define HN_GETSCALE 0x10
+#define HN_AUTOSCALE 0x20
+
+#endif
+
+/* Constants for DDF RAID levels. */
+#define DDF_RAID0 0x00
+#define DDF_RAID1 0x01
+#define DDF_RAID3 0x03
+#define DDF_RAID5 0x05
+#define DDF_RAID6 0x06
+#define DDF_RAID1E 0x11
+#define DDF_JBOD 0x0f
+#define DDF_CONCAT 0x1f
+#define DDF_RAID5E 0x15
+#define DDF_RAID5EE 0x25
+
+struct mfiutil_command {
+ const char *name;
+ int (*handler)(int ac, char **av);
+};
+
+#define MFI_DATASET(name) mfiutil_ ## name ## _table
+
+#define MFI_COMMAND(set, name, function) \
+ static struct mfiutil_command function ## _mfiutil_command = \
+ { #name, function }; \
+ DATA_SET(MFI_DATASET(set), function ## _mfiutil_command)
+
+#define MFI_TABLE(set, name) \
+ SET_DECLARE(MFI_DATASET(name), struct mfiutil_command); \
+ \
+ static int \
+ mfiutil_ ## name ## _table_handler(int ac, char **av) \
+ { \
+ return (mfi_table_handler(SET_BEGIN(MFI_DATASET(name)), \
+ SET_LIMIT(MFI_DATASET(name)), ac, av)); \
+ } \
+ MFI_COMMAND(set, name, mfiutil_ ## name ## _table_handler)
+
+/* Drive name printing options */
+#define MFI_DNAME_ES 0x0001 /* E%u:S%u */
+#define MFI_DNAME_DEVICE_ID 0x0002 /* %u */
+#define MFI_DNAME_HONOR_OPTS 0x8000 /* Allow cmd line to override default */
+
+extern int mfi_unit;
+
+extern u_int mfi_opts;
+
+/* We currently don't know the full details of the following struct */
+struct mfi_foreign_scan_cfg {
+ char data[24];
+};
+
+struct mfi_foreign_scan_info {
+ uint32_t count; /* Number of foreign configs found */
+ struct mfi_foreign_scan_cfg cfgs[8];
+};
+
+void mbox_store_ldref(uint8_t *mbox, union mfi_ld_ref *ref);
+void mbox_store_pdref(uint8_t *mbox, union mfi_pd_ref *ref);
+void mfi_display_progress(const char *label, struct mfi_progress *prog);
+int mfi_table_handler(struct mfiutil_command **start,
+ struct mfiutil_command **end, int ac, char **av);
+const char *mfi_raid_level(uint8_t primary_level, uint8_t secondary_level);
+const char *mfi_ldstate(enum mfi_ld_state state);
+const char *mfi_pdstate(enum mfi_pd_state state);
+const char *mfi_pd_inq_string(struct mfi_pd_info *info);
+const char *mfi_volume_name(int fd, uint8_t target_id);
+int mfi_volume_busy(int fd, uint8_t target_id);
+int mfi_config_read(int fd, struct mfi_config_data **configp);
+int mfi_config_read_opcode(int fd, uint32_t opcode,
+ struct mfi_config_data **configp, uint8_t *mbox, size_t mboxlen);
+int mfi_lookup_drive(int fd, char *drive, uint16_t *device_id);
+int mfi_lookup_volume(int fd, const char *name, uint8_t *target_id);
+int mfi_dcmd_command(int fd, uint32_t opcode, void *buf, size_t bufsize,
+ uint8_t *mbox, size_t mboxlen, uint8_t *statusp);
+int mfi_open(int unit, int acs);
+int mfi_ctrl_get_info(int fd, struct mfi_ctrl_info *info, uint8_t *statusp);
+int mfi_ld_get_info(int fd, uint8_t target_id, struct mfi_ld_info *info,
+ uint8_t *statusp);
+int mfi_ld_get_list(int fd, struct mfi_ld_list *list, uint8_t *statusp);
+int mfi_pd_get_info(int fd, uint16_t device_id, struct mfi_pd_info *info,
+ uint8_t *statusp);
+int mfi_pd_get_list(int fd, struct mfi_pd_list **listp, uint8_t *statusp);
+int mfi_reconfig_supported(void);
+const char *mfi_status(u_int status_code);
+const char *mfi_drive_name(struct mfi_pd_info *pinfo, uint16_t device_id,
+ uint32_t def);
+void format_stripe(char *buf, size_t buflen, uint8_t stripe);
+void print_ld(struct mfi_ld_info *info, int state_len);
+void print_pd(struct mfi_pd_info *info, int state_len);
+void dump_config(int fd, struct mfi_config_data *config, const char *msg_prefix);
+int mfi_bbu_get_props(int fd, struct mfi_bbu_properties *props,
+ uint8_t *statusp);
+int mfi_bbu_set_props(int fd, struct mfi_bbu_properties *props,
+ uint8_t *statusp);
+void mfi_autolearn_period(uint32_t, char *, size_t);
+void mfi_next_learn_time(uint32_t, char *, size_t);
+void mfi_autolearn_mode(uint8_t, char *, size_t);
+
+void scan_firmware(struct mfi_info_component *comp);
+void display_firmware(struct mfi_info_component *comp, const char *tag);
+
+int display_format(int ac, char **av, int diagnostic, mfi_dcmd_t display_cmd);
+#endif /* !__MFIUTIL_H__ */
diff --git a/usr.sbin/mixer/Makefile b/usr.sbin/mixer/Makefile
new file mode 100644
index 0000000..abc0182
--- /dev/null
+++ b/usr.sbin/mixer/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= mixer
+MAN= mixer.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/mixer/Makefile.depend b/usr.sbin/mixer/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/mixer/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/mixer/mixer.8 b/usr.sbin/mixer/mixer.8
new file mode 100644
index 0000000..6e569cc
--- /dev/null
+++ b/usr.sbin/mixer/mixer.8
@@ -0,0 +1,181 @@
+.\" Copyright (c) 1997
+.\" Mike Pritchard <mpp@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.
+.\" 3. Neither the name of the author 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 MIKE PRITCHARD AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED 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 2, 2014
+.Dt MIXER 8
+.Os
+.Sh NAME
+.Nm mixer
+.Nd set/display soundcard mixer values
+.Sh SYNOPSIS
+.Nm
+.Op Fl f Ar device
+.Op Fl s | S
+.Oo
+.Ar dev
+.Sm off
+.Oo
+.Op Cm + | -
+.Ar lvol
+.Op : Oo Cm + | - Oc Ar rvol
+.Oc
+.Oc
+.Sm on
+.Ar ...
+.Nm
+.Op Fl f Ar device
+.Op Fl s | S
+.Cm recsrc
+.Ar ...
+.Nm
+.Op Fl f Ar device
+.Op Fl s | S
+.Sm off
+.Bro
+.Cm ^ | + | - | =
+.Brc
+.Cm rec
+.Sm on
+.Ar rdev ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to set and display soundcard mixer device levels.
+It may
+also be used to start and stop recording from the soundcard.
+The list
+of mixer devices that may be modified are:
+.Bd -ragged -offset indent
+vol, bass, treble, synth, pcm, speaker, line, mic, cd, mix,
+pcm2, rec, igain, ogain, line1, line2, line3, dig1, dig2, dig3,
+phin, phout, video, radio, and monitor.
+.Ed
+.Pp
+Not all mixer devices are available.
+.Pp
+Without any arguments,
+.Nm
+displays the current settings for all supported devices, followed by information
+about the current recording input devices.
+If the
+.Ar dev
+argument is specified,
+.Nm
+displays only the value for that
+.Ar dev .
+.Pp
+To modify the mixer value
+.Ar dev ,
+the optional left and right channel settings of
+.Ar lvol Ns Op : Ns Ar rvol
+may be specified.
+The
+.Ar lvol
+and
+.Ar rvol
+arguments may be from 0 - 100.
+Omitting
+.Ar dev
+and including only the channel settings will change the main volume level.
+.Pp
+If the left or right channel settings are prefixed with
+.Cm +
+or
+.Cm - ,
+the value following will be used as a relative adjustment, modifying the
+current settings by the amount specified.
+.Pp
+If the
+.Fl s
+flag is used, the current mixer values will be displayed in a format suitable
+for use as the command-line arguments to a future invocation of
+.Nm
+(as above).
+.Pp
+The
+.Fl S
+flag provides the above output without mixing field separators.
+.Pp
+To change the recording device you use one of:
+.Bl -tag -width =rec -offset indent
+.It Cm ^rec
+toggles
+.Ar rdev
+of possible recording devices
+.It Cm +rec
+adds
+.Ar rdev
+to possible recording devices
+.It Cm -rec
+removes
+.Ar rdev
+from possible recording devices
+.It Cm =rec
+sets the recording device to
+.Ar rdev
+.El
+.Pp
+The above commands work on an internal mask.
+After all the options
+have been parsed, it will set then read the mask from the sound card.
+This will let you see EXACTLY what the soundcard is using for the
+recording device(s).
+.Pp
+The option recsrc will display the current recording devices.
+.Pp
+The option
+.Fl f
+.Ar device
+will open
+.Ar device
+as the mixer device.
+.Sh FILES
+.Bl -tag -width /dev/mixer -compact
+.It Pa /dev/mixer
+the default mixer device
+.El
+.Sh SEE ALSO
+.Xr cdcontrol 1 ,
+.Xr sound 4
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 2.0.5 .
+.Sh AUTHORS
+.An -nosplit
+Original source by
+.An Craig Metz Aq Mt cmetz@thor.tjhsst.edu
+and
+.An Hannu Savolainen .
+Mostly rewritten by
+.An John-Mark Gurney Aq Mt jmg@FreeBSD.org .
+This
+manual page was written by
+.An Mike Pritchard Aq Mt mpp@FreeBSD.org .
diff --git a/usr.sbin/mixer/mixer.c b/usr.sbin/mixer/mixer.c
new file mode 100644
index 0000000..49b57d8
--- /dev/null
+++ b/usr.sbin/mixer/mixer.c
@@ -0,0 +1,336 @@
+/*
+ * This is an example of a mixer program for Linux
+ *
+ * updated 1/1/93 to add stereo, level query, broken
+ * devmask kludge - cmetz@thor.tjhsst.edu
+ *
+ * (C) Craig Metz and Hannu Savolainen 1993.
+ *
+ * You may do anything you wish with this program.
+ *
+ * ditto for my modifications (John-Mark Gurney, 1997)
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <err.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/soundcard.h>
+
+static const char *names[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES;
+
+static void usage(int devmask, int recmask);
+static int res_name(const char *name, int mask);
+static void print_recsrc(int recsrc, int recmask, int sflag);
+
+static void
+usage(int devmask, int recmask)
+{
+ int i, n;
+
+ printf("usage: mixer [-f device] [-s | -S] [dev [+|-][voll[:[+|-]volr]] ...\n"
+ " mixer [-f device] [-s | -S] recsrc ...\n"
+ " mixer [-f device] [-s | -S] {^|+|-|=}rec rdev ...\n");
+ if (devmask != 0) {
+ printf(" devices: ");
+ for (i = 0, n = 0; i < SOUND_MIXER_NRDEVICES; i++)
+ if ((1 << i) & devmask) {
+ if (n)
+ printf(", ");
+ printf("%s", names[i]);
+ n++;
+ }
+ }
+ if (recmask != 0) {
+ printf("\n rec devices: ");
+ for (i = 0, n = 0; i < SOUND_MIXER_NRDEVICES; i++)
+ if ((1 << i) & recmask) {
+ if (n)
+ printf(", ");
+ printf("%s", names[i]);
+ n++;
+ }
+ }
+ printf("\n");
+ exit(1);
+}
+
+static int
+res_name(const char *name, int mask)
+{
+ int foo;
+
+ for (foo = 0; foo < SOUND_MIXER_NRDEVICES; foo++)
+ if ((1 << foo) & mask && strcmp(names[foo], name) == 0)
+ break;
+
+ return (foo == SOUND_MIXER_NRDEVICES ? -1 : foo);
+}
+
+static void
+print_recsrc(int recsrc, int recmask, int sflag)
+{
+ int i, n;
+
+ if (recmask == 0)
+ return;
+
+ if (!sflag)
+ printf("Recording source: ");
+
+ for (i = 0, n = 0; i < SOUND_MIXER_NRDEVICES; i++)
+ if ((1 << i) & recsrc) {
+ if (sflag)
+ printf("%srec ", n ? " +" : "=");
+ else if (n)
+ printf(", ");
+ printf("%s", names[i]);
+ n++;
+ }
+ if (!sflag)
+ printf("\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ char mixer[PATH_MAX] = "/dev/mixer";
+ char lstr[5], rstr[5];
+ char *name, *eptr;
+ int devmask = 0, recmask = 0, recsrc = 0, orecsrc;
+ int dusage = 0, drecsrc = 0, sflag = 0, Sflag = 0;
+ int l, r, lrel, rrel;
+ int ch, foo, bar, baz, dev, m, n, t;
+
+ if ((name = strdup(basename(argv[0]))) == NULL)
+ err(1, "strdup()");
+ if (strncmp(name, "mixer", 5) == 0 && name[5] != '\0') {
+ n = strtol(name + 5, &eptr, 10) - 1;
+ if (n > 0 && *eptr == '\0')
+ snprintf(mixer, PATH_MAX - 1, "/dev/mixer%d", n);
+ }
+ free(name);
+ name = mixer;
+
+ n = 1;
+ for (;;) {
+ if (n >= argc || *argv[n] != '-')
+ break;
+ if (strlen(argv[n]) != 2) {
+ if (strcmp(argv[n] + 1, "rec") != 0)
+ dusage = 1;
+ break;
+ }
+ ch = *(argv[n] + 1);
+ if (ch == 'f' && n < argc - 1) {
+ name = argv[n + 1];
+ n += 2;
+ } else if (ch == 's') {
+ sflag = 1;
+ n++;
+ } else if (ch == 'S') {
+ Sflag = 1;
+ n++;
+ } else {
+ dusage = 1;
+ break;
+ }
+ }
+ if (sflag && Sflag)
+ dusage = 1;
+
+ argc -= n - 1;
+ argv += n - 1;
+
+ if ((baz = open(name, O_RDWR)) < 0)
+ err(1, "%s", name);
+ if (ioctl(baz, SOUND_MIXER_READ_DEVMASK, &devmask) == -1)
+ err(1, "SOUND_MIXER_READ_DEVMASK");
+ if (ioctl(baz, SOUND_MIXER_READ_RECMASK, &recmask) == -1)
+ err(1, "SOUND_MIXER_READ_RECMASK");
+ if (ioctl(baz, SOUND_MIXER_READ_RECSRC, &recsrc) == -1)
+ err(1, "SOUND_MIXER_READ_RECSRC");
+ orecsrc = recsrc;
+
+ if (argc == 1 && dusage == 0) {
+ for (foo = 0, n = 0; foo < SOUND_MIXER_NRDEVICES; foo++) {
+ if (!((1 << foo) & devmask))
+ continue;
+ if (ioctl(baz, MIXER_READ(foo),&bar) == -1) {
+ warn("MIXER_READ");
+ continue;
+ }
+ if (Sflag || sflag) {
+ printf("%s%s%c%d:%d", n ? " " : "",
+ names[foo], Sflag ? ':' : ' ',
+ bar & 0x7f, (bar >> 8) & 0x7f);
+ n++;
+ } else
+ printf("Mixer %-8s is currently set to "
+ "%3d:%d\n", names[foo], bar & 0x7f,
+ (bar >> 8) & 0x7f);
+ }
+ if (n && recmask)
+ printf(" ");
+ print_recsrc(recsrc, recmask, Sflag || sflag);
+ return (0);
+ }
+
+ argc--;
+ argv++;
+
+ n = 0;
+ while (argc > 0 && dusage == 0) {
+ if (strcmp("recsrc", *argv) == 0) {
+ drecsrc = 1;
+ argc--;
+ argv++;
+ continue;
+ } else if (strcmp("rec", *argv + 1) == 0) {
+ if (**argv != '+' && **argv != '-' &&
+ **argv != '=' && **argv != '^') {
+ warnx("unknown modifier: %c", **argv);
+ dusage = 1;
+ break;
+ }
+ if (argc <= 1) {
+ warnx("no recording device specified");
+ dusage = 1;
+ break;
+ }
+ if ((dev = res_name(argv[1], recmask)) == -1) {
+ warnx("unknown recording device: %s", argv[1]);
+ dusage = 1;
+ break;
+ }
+ switch (**argv) {
+ case '+':
+ recsrc |= (1 << dev);
+ break;
+ case '-':
+ recsrc &= ~(1 << dev);
+ break;
+ case '=':
+ recsrc = (1 << dev);
+ break;
+ case '^':
+ recsrc ^= (1 << dev);
+ break;
+ }
+ drecsrc = 1;
+ argc -= 2;
+ argv += 2;
+ continue;
+ }
+
+ if ((t = sscanf(*argv, "%d:%d", &l, &r)) > 0)
+ dev = 0;
+ else if ((dev = res_name(*argv, devmask)) == -1) {
+ warnx("unknown device: %s", *argv);
+ dusage = 1;
+ break;
+ }
+
+ lrel = rrel = 0;
+ if (argc > 1) {
+ m = sscanf(argv[1], "%7[^:]:%7s", lstr, rstr);
+ if (m > 0) {
+ if (*lstr == '+' || *lstr == '-')
+ lrel = rrel = 1;
+ l = strtol(lstr, NULL, 10);
+ }
+ if (m > 1) {
+ if (*rstr == '+' || *rstr == '-')
+ rrel = 1;
+ r = strtol(rstr, NULL, 10);
+ }
+ }
+
+ switch (argc > 1 ? m : t) {
+ case 0:
+ if (ioctl(baz, MIXER_READ(dev), &bar) == -1) {
+ warn("MIXER_READ");
+ argc--;
+ argv++;
+ continue;
+ }
+ if (Sflag || sflag) {
+ printf("%s%s%c%d:%d", n ? " " : "",
+ names[dev], Sflag ? ':' : ' ',
+ bar & 0x7f, (bar >> 8) & 0x7f);
+ n++;
+ } else
+ printf("Mixer %-8s is currently set to "
+ "%3d:%d\n", names[dev], bar & 0x7f,
+ (bar >> 8) & 0x7f);
+
+ argc--;
+ argv++;
+ break;
+ case 1:
+ r = l;
+ /* FALLTHROUGH */
+ case 2:
+ if (ioctl(baz, MIXER_READ(dev), &bar) == -1) {
+ warn("MIXER_READ");
+ argc--;
+ argv++;
+ continue;
+ }
+
+ if (lrel)
+ l = (bar & 0x7f) + l;
+ if (rrel)
+ r = ((bar >> 8) & 0x7f) + r;
+
+ if (l < 0)
+ l = 0;
+ else if (l > 100)
+ l = 100;
+ if (r < 0)
+ r = 0;
+ else if (r > 100)
+ r = 100;
+
+ if (!Sflag)
+ printf("Setting the mixer %s from %d:%d to "
+ "%d:%d.\n", names[dev], bar & 0x7f,
+ (bar >> 8) & 0x7f, l, r);
+
+ l |= r << 8;
+ if (ioctl(baz, MIXER_WRITE(dev), &l) == -1)
+ warn("WRITE_MIXER");
+
+ argc -= 2;
+ argv += 2;
+ break;
+ }
+ }
+
+ if (dusage) {
+ close(baz);
+ usage(devmask, recmask);
+ /* NOTREACHED */
+ }
+
+ if (orecsrc != recsrc) {
+ if (ioctl(baz, SOUND_MIXER_WRITE_RECSRC, &recsrc) == -1)
+ err(1, "SOUND_MIXER_WRITE_RECSRC");
+ if (ioctl(baz, SOUND_MIXER_READ_RECSRC, &recsrc) == -1)
+ err(1, "SOUND_MIXER_READ_RECSRC");
+ }
+
+ if (drecsrc)
+ print_recsrc(recsrc, recmask, Sflag || sflag);
+
+ close(baz);
+
+ return (0);
+}
diff --git a/usr.sbin/mld6query/Makefile b/usr.sbin/mld6query/Makefile
new file mode 100644
index 0000000..1d4b443
--- /dev/null
+++ b/usr.sbin/mld6query/Makefile
@@ -0,0 +1,25 @@
+# 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$
+
+PROG= mld6query
+MAN= mld6query.8
+SRCS= mld6.c
+
+CFLAGS+= -DIPSEC -DUSE_RFC2292BIS
+
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/mld6query/Makefile.depend b/usr.sbin/mld6query/Makefile.depend
new file mode 100644
index 0000000..54c1f6f
--- /dev/null
+++ b/usr.sbin/mld6query/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/mld6query/mld6.c b/usr.sbin/mld6query/mld6.c
new file mode 100644
index 0000000..a3c8342
--- /dev/null
+++ b/usr.sbin/mld6query/mld6.c
@@ -0,0 +1,352 @@
+/* $KAME: mld6.c,v 1.15 2003/04/02 11:29:54 suz Exp $ */
+
+/*
+ * Copyright (C) 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/uio.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <ifaddrs.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include <net/if.h>
+
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+
+#include <arpa/inet.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <err.h>
+
+/* portability with older KAME headers */
+#ifndef MLD_LISTENER_QUERY
+#define MLD_LISTENER_QUERY MLD6_LISTENER_QUERY
+#define MLD_LISTENER_REPORT MLD6_LISTENER_REPORT
+#define MLD_LISTENER_DONE MLD6_LISTENER_DONE
+#define MLD_MTRACE_RESP MLD6_MTRACE_RESP
+#define MLD_MTRACE MLD6_MTRACE
+#define mld_hdr mld6_hdr
+#define mld_type mld6_type
+#define mld_code mld6_code
+#define mld_cksum mld6_cksum
+#define mld_maxdelay mld6_maxdelay
+#define mld_reserved mld6_reserved
+#define mld_addr mld6_addr
+#endif
+#ifndef IP6OPT_ROUTER_ALERT
+#define IP6OPT_ROUTER_ALERT IP6OPT_RTALERT
+#endif
+
+struct msghdr m;
+struct sockaddr_in6 dst;
+struct mld_hdr mldh;
+struct in6_addr maddr = IN6ADDR_ANY_INIT, any = IN6ADDR_ANY_INIT;
+struct ipv6_mreq mreq;
+u_short ifindex;
+int s;
+
+#define QUERY_RESPONSE_INTERVAL 10000
+
+void make_msg(int index, struct in6_addr *addr, u_int type);
+void usage(void);
+void dump(int);
+void quit(int);
+
+int
+main(int argc, char *argv[])
+{
+ int i;
+ struct icmp6_filter filt;
+ u_int hlim = 1;
+ fd_set fdset;
+ struct itimerval itimer;
+ u_int type;
+ int ch;
+
+ type = MLD_LISTENER_QUERY;
+ while ((ch = getopt(argc, argv, "dr")) != -1) {
+ switch (ch) {
+ case 'd':
+ type = MLD_LISTENER_DONE;
+ break;
+ case 'r':
+ type = MLD_LISTENER_REPORT;
+ break;
+ default:
+ usage();
+ /*NOTREACHED*/
+ }
+ }
+
+ argv += optind;
+ argc -= optind;
+
+ if (argc != 1 && argc != 2)
+ usage();
+
+ ifindex = (u_short)if_nametoindex(argv[0]);
+ if (ifindex == 0)
+ usage();
+ if (argc == 2 && inet_pton(AF_INET6, argv[1], &maddr) != 1)
+ usage();
+
+ if ((s = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0)
+ err(1, "socket");
+
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hlim,
+ sizeof(hlim)) == -1)
+ err(1, "setsockopt(IPV6_MULTICAST_HOPS)");
+
+ mreq.ipv6mr_multiaddr = any;
+ mreq.ipv6mr_interface = ifindex;
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq,
+ sizeof(mreq)) == -1)
+ err(1, "setsockopt(IPV6_JOIN_GROUP)");
+
+ ICMP6_FILTER_SETBLOCKALL(&filt);
+ ICMP6_FILTER_SETPASS(ICMP6_MEMBERSHIP_QUERY, &filt);
+ ICMP6_FILTER_SETPASS(ICMP6_MEMBERSHIP_REPORT, &filt);
+ ICMP6_FILTER_SETPASS(ICMP6_MEMBERSHIP_REDUCTION, &filt);
+ if (setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &filt,
+ sizeof(filt)) < 0)
+ err(1, "setsockopt(ICMP6_FILTER)");
+
+ make_msg(ifindex, &maddr, type);
+
+ if (sendmsg(s, &m, 0) < 0)
+ err(1, "sendmsg");
+
+ itimer.it_value.tv_sec = QUERY_RESPONSE_INTERVAL / 1000;
+ itimer.it_interval.tv_sec = 0;
+ itimer.it_interval.tv_usec = 0;
+ itimer.it_value.tv_usec = 0;
+
+ (void)signal(SIGALRM, quit);
+ (void)setitimer(ITIMER_REAL, &itimer, NULL);
+
+ FD_ZERO(&fdset);
+ if (s >= FD_SETSIZE)
+ errx(1, "descriptor too big");
+ for (;;) {
+ FD_SET(s, &fdset);
+ if ((i = select(s + 1, &fdset, NULL, NULL, NULL)) < 0)
+ perror("select");
+ if (i == 0)
+ continue;
+ else
+ dump(s);
+ }
+}
+
+void
+make_msg(int index, struct in6_addr *addr, u_int type)
+{
+ static struct iovec iov[2];
+ static u_char *cmsgbuf;
+ int cmsglen, hbhlen = 0;
+#ifdef USE_RFC2292BIS
+ void *hbhbuf = NULL, *optp = NULL;
+ int currentlen;
+#else
+ u_int8_t raopt[IP6OPT_RTALERT_LEN];
+#endif
+ struct in6_pktinfo *pi;
+ struct cmsghdr *cmsgp;
+ u_short rtalert_code = htons(IP6OPT_RTALERT_MLD);
+ struct ifaddrs *ifa, *ifap;
+ struct in6_addr src;
+
+ dst.sin6_len = sizeof(dst);
+ dst.sin6_family = AF_INET6;
+ if (IN6_IS_ADDR_UNSPECIFIED(addr)) {
+ if (inet_pton(AF_INET6, "ff02::1", &dst.sin6_addr) != 1)
+ errx(1, "inet_pton failed");
+ }
+ else
+ dst.sin6_addr = *addr;
+ m.msg_name = (caddr_t)&dst;
+ m.msg_namelen = dst.sin6_len;
+ iov[0].iov_base = (caddr_t)&mldh;
+ iov[0].iov_len = sizeof(mldh);
+ m.msg_iov = iov;
+ m.msg_iovlen = 1;
+
+ bzero(&mldh, sizeof(mldh));
+ mldh.mld_type = type & 0xff;
+ mldh.mld_maxdelay = htons(QUERY_RESPONSE_INTERVAL);
+ mldh.mld_addr = *addr;
+
+ /* MLD packet should be advertised from linklocal address */
+ getifaddrs(&ifa);
+ for (ifap = ifa; ifap; ifap = ifap->ifa_next) {
+ if (index != if_nametoindex(ifap->ifa_name))
+ continue;
+
+ if (ifap->ifa_addr->sa_family != AF_INET6)
+ continue;
+ if (!IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6 *)
+ ifap->ifa_addr)->sin6_addr))
+ continue;
+ break;
+ }
+ if (ifap == NULL)
+ errx(1, "no linkocal address is available");
+ memcpy(&src, &((struct sockaddr_in6 *)ifap->ifa_addr)->sin6_addr,
+ sizeof(src));
+ freeifaddrs(ifa);
+#ifdef __KAME__
+ /* remove embedded ifindex */
+ src.s6_addr[2] = src.s6_addr[3] = 0;
+#endif
+
+#ifdef USE_RFC2292BIS
+ if ((hbhlen = inet6_opt_init(NULL, 0)) == -1)
+ errx(1, "inet6_opt_init(0) failed");
+ if ((hbhlen = inet6_opt_append(NULL, 0, hbhlen, IP6OPT_ROUTER_ALERT, 2,
+ 2, NULL)) == -1)
+ errx(1, "inet6_opt_append(0) failed");
+ if ((hbhlen = inet6_opt_finish(NULL, 0, hbhlen)) == -1)
+ errx(1, "inet6_opt_finish(0) failed");
+ cmsglen = CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(hbhlen);
+#else
+ hbhlen = sizeof(raopt);
+ cmsglen = CMSG_SPACE(sizeof(struct in6_pktinfo)) +
+ inet6_option_space(hbhlen);
+#endif
+
+ if ((cmsgbuf = malloc(cmsglen)) == NULL)
+ errx(1, "can't allocate enough memory for cmsg");
+ cmsgp = (struct cmsghdr *)cmsgbuf;
+ m.msg_control = (caddr_t)cmsgbuf;
+ m.msg_controllen = cmsglen;
+ /* specify the outgoing interface */
+ cmsgp->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
+ cmsgp->cmsg_level = IPPROTO_IPV6;
+ cmsgp->cmsg_type = IPV6_PKTINFO;
+ pi = (struct in6_pktinfo *)CMSG_DATA(cmsgp);
+ pi->ipi6_ifindex = index;
+ memcpy(&pi->ipi6_addr, &src, sizeof(pi->ipi6_addr));
+ /* specifiy to insert router alert option in a hop-by-hop opt hdr. */
+ cmsgp = CMSG_NXTHDR(&m, cmsgp);
+#ifdef USE_RFC2292BIS
+ cmsgp->cmsg_len = CMSG_LEN(hbhlen);
+ cmsgp->cmsg_level = IPPROTO_IPV6;
+ cmsgp->cmsg_type = IPV6_HOPOPTS;
+ hbhbuf = CMSG_DATA(cmsgp);
+ if ((currentlen = inet6_opt_init(hbhbuf, hbhlen)) == -1)
+ errx(1, "inet6_opt_init(len = %d) failed", hbhlen);
+ if ((currentlen = inet6_opt_append(hbhbuf, hbhlen, currentlen,
+ IP6OPT_ROUTER_ALERT, 2,
+ 2, &optp)) == -1)
+ errx(1, "inet6_opt_append(currentlen = %d, hbhlen = %d) failed",
+ currentlen, hbhlen);
+ (void)inet6_opt_set_val(optp, 0, &rtalert_code, sizeof(rtalert_code));
+ if ((currentlen = inet6_opt_finish(hbhbuf, hbhlen, currentlen)) == -1)
+ errx(1, "inet6_opt_finish(buf) failed");
+#else /* old advanced API */
+ if (inet6_option_init((void *)cmsgp, &cmsgp, IPV6_HOPOPTS))
+ errx(1, "inet6_option_init failed\n");
+ raopt[0] = IP6OPT_ROUTER_ALERT;
+ raopt[1] = IP6OPT_RTALERT_LEN - 2;
+ memcpy(&raopt[2], (caddr_t)&rtalert_code, sizeof(u_short));
+ if (inet6_option_append(cmsgp, raopt, 4, 0))
+ errx(1, "inet6_option_append failed\n");
+#endif
+}
+
+void
+dump(int s)
+{
+ int i;
+ struct mld_hdr *mld;
+ u_char buf[1024];
+ struct sockaddr_in6 from;
+ int from_len = sizeof(from);
+ char ntop_buf[256];
+
+ if ((i = recvfrom(s, buf, sizeof(buf), 0,
+ (struct sockaddr *)&from,
+ &from_len)) < 0)
+ return;
+
+ if (i < sizeof(struct mld_hdr)) {
+ printf("too short!\n");
+ return;
+ }
+
+ mld = (struct mld_hdr *)buf;
+
+ printf("from %s, ", inet_ntop(AF_INET6, &from.sin6_addr,
+ ntop_buf, sizeof(ntop_buf)));
+
+ switch (mld->mld_type) {
+ case ICMP6_MEMBERSHIP_QUERY:
+ printf("type=Multicast Listener Query, ");
+ break;
+ case ICMP6_MEMBERSHIP_REPORT:
+ printf("type=Multicast Listener Report, ");
+ break;
+ case ICMP6_MEMBERSHIP_REDUCTION:
+ printf("type=Multicast Listener Done, ");
+ break;
+ }
+ printf("addr=%s\n", inet_ntop(AF_INET6, &mld->mld_addr,
+ ntop_buf, sizeof(ntop_buf)));
+
+ fflush(stdout);
+}
+
+void
+quit(int signum __unused)
+{
+ mreq.ipv6mr_multiaddr = any;
+ mreq.ipv6mr_interface = ifindex;
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &mreq,
+ sizeof(mreq)) == -1)
+ err(1, "setsockopt(IPV6_LEAVE_GROUP)");
+
+ exit(0);
+}
+
+void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: mld6query ifname [addr]\n");
+ exit(1);
+}
diff --git a/usr.sbin/mld6query/mld6query.8 b/usr.sbin/mld6query/mld6query.8
new file mode 100644
index 0000000..1516c2e
--- /dev/null
+++ b/usr.sbin/mld6query/mld6query.8
@@ -0,0 +1,91 @@
+.\" $KAME: mld6query.8,v 1.5 2000/12/04 06:28:23 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 May 17, 1998
+.Dt MLD6QUERY 8
+.Os
+.\"
+.Sh NAME
+.Nm mld6query
+.Nd send multicast listener query
+.\"
+.Sh SYNOPSIS
+.Nm
+.Op Fl dr
+.Ar intface
+.Op Ar maddr
+.\"
+.Sh DESCRIPTION
+The
+.Nm
+utility sends an IPv6 multicast listener discovery (MLD) query packet toward
+the specified multicast address,
+.Ar maddr ,
+toward interface
+.Ar intface .
+If you omit
+.Ar maddr ,
+linklocal all nodes multicast address(ff02::1) is used.
+.Pp
+After sending a query,
+.Nm
+waits for replies for at most 10 seconds.
+If a reply is returned,
+.Nm
+prints it with its type and then waits for another reply.
+.Pp
+This program is provided only for debugging.
+It is not necessary for normal use.
+.Pp
+With
+.Fl d ,
+.Nm
+will transmit MLD done packet instead of MLD query packet.
+With
+.Fl r ,
+similarly, MLD report packet will be transmitted.
+.Fl dr
+options are for debugging purposes only.
+.\"
+.Sh EXIT STATUS
+The program exits with 0 on success, non-zero on failures.
+.\"
+.\" .Sh SEE ALSO
+.\"
+.Sh HISTORY
+The
+.Nm
+utility first appeared in WIDE/KAME IPv6 protocol stack kit.
+.Sh BUGS
+The
+.Nm
+utility does not take care of multicast addresses which have non link-local
+scope.
diff --git a/usr.sbin/mlxcontrol/Makefile b/usr.sbin/mlxcontrol/Makefile
new file mode 100644
index 0000000..038544b
--- /dev/null
+++ b/usr.sbin/mlxcontrol/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+PROG= mlxcontrol
+MAN= mlxcontrol.8
+SRCS= command.c config.c interface.c util.c
+
+CFLAGS+= -I${.CURDIR}/../../sys
+
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/mlxcontrol/Makefile.depend b/usr.sbin/mlxcontrol/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/mlxcontrol/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/mlxcontrol/command.c b/usr.sbin/mlxcontrol/command.c
new file mode 100644
index 0000000..a7dc88f
--- /dev/null
+++ b/usr.sbin/mlxcontrol/command.c
@@ -0,0 +1,712 @@
+/*-
+ * Copyright (c) 1999 Michael Smith
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <fcntl.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <err.h>
+
+#include <dev/mlx/mlxio.h>
+#include <dev/mlx/mlxreg.h>
+
+#include "mlxcontrol.h"
+
+static int cmd_status(int argc, char *argv[]);
+static int cmd_rescan(int argc, char *argv[]);
+static int cmd_detach(int argc, char *argv[]);
+static int cmd_check(int argc, char *argv[]);
+static int cmd_rebuild(int argc, char *argv[]);
+#ifdef SUPPORT_PAUSE
+static int cmd_pause(int argc, char *argv[]);
+#endif
+static int cmd_help(int argc, char *argv[]);
+
+extern int cmd_config(int argc, char *argv[]);
+
+
+struct
+{
+ char *cmd;
+ int (*func)(int argc, char *argv[]);
+ char *desc;
+ char *text;
+} commands[] = {
+ {"status", cmd_status,
+ "displays device status",
+ " status [-qv] [<drive>...]\n"
+ " Display status for <drive> or all drives if none is listed\n"
+ " -q Suppress output.\n"
+ " -v Display verbose information.\n"
+ " Returns 0 if all drives tested are online, 1 if one or more are\n"
+ " critical, and 2 if one or more are offline."},
+ {"rescan", cmd_rescan,
+ "scan for new system drives",
+ " rescan <controller> [<controller>...]\n"
+ " Rescan <controller> for system drives.\n"
+ " rescan -a\n"
+ " Rescan all controllers for system drives."},
+ {"detach", cmd_detach,
+ "detach system drives",
+ " detach <drive> [<drive>...]\n"
+ " Detaches <drive> from the controller.\n"
+ " detach -a <controller>\n"
+ " Detaches all drives on <controller>."},
+ {"check", cmd_check,
+ "consistency-check a system drive",
+ " check <drive>\n"
+ " Requests a check and rebuild of the parity information on <drive>.\n"
+ " Note that each controller can only check one system drive at a time."},
+ {"rebuild", cmd_rebuild,
+ "initiate a rebuild of a dead physical drive",
+ " rebuild <controller> <physdrive>\n"
+ " All system drives using space on the physical drive <physdrive>\n"
+ " are rebuilt, reconstructing all data on the drive.\n"
+ " Note that each controller can only perform one rebuild at a time."},
+#ifdef SUPPORT_PAUSE
+ {"pause", cmd_pause,
+ "pauses controller channels",
+ " pause [-t <howlong>] [-d <delay>] <controller> [<channel>...]\n"
+ " Pauses SCSI I/O on <channel> and <controller>. If no channel is specified,\n"
+ " all channels are paused.\n"
+ " <howlong> How long (seconds) to pause for (default 30).\n"
+ " <delay> How long (seconds) to wait before pausing (default 30).\n"
+ " pause <controller> -c\n"
+ " Cancels any pending pause operation on <controller>."},
+#endif
+ {"config", cmd_config,
+ "examine and update controller configuration",
+ " config <controller>\n"
+ " Print configuration for <controller>."},
+ {"help", cmd_help,
+ "give help on usage",
+ ""},
+ {NULL, NULL, NULL, NULL}
+};
+
+/********************************************************************************
+ * Command dispatch and global options parsing.
+ */
+
+int
+main(int argc, char *argv[])
+{
+ int ch, i, oargc;
+ char **oargv;
+
+ oargc = argc;
+ oargv = argv;
+ while ((ch = getopt(argc, argv, "")) != -1)
+ switch(ch) {
+ default:
+ return(cmd_help(0, NULL));
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0)
+ for (i = 0; commands[i].cmd != NULL; i++)
+ if (!strcmp(argv[0], commands[i].cmd))
+ return(commands[i].func(argc, argv));
+
+ return(cmd_help(oargc, oargv));
+}
+
+/********************************************************************************
+ * Helptext output
+ */
+static int
+cmd_help(int argc, char *argv[])
+{
+ int i;
+
+ if (argc > 1)
+ for (i = 0; commands[i].cmd != NULL; i++)
+ if (!strcmp(argv[1], commands[i].cmd)) {
+ fprintf(stderr, "%s\n", commands[i].text);
+ fflush(stderr);
+ return(0);
+ }
+
+ if (argv != NULL)
+ fprintf(stderr, "Unknown command '%s'.\n", argv[1]);
+ fprintf(stderr, "Valid commands are:\n");
+ for (i = 0; commands[i].cmd != NULL; i++)
+ fprintf(stderr, " %-20s %s\n", commands[i].cmd, commands[i].desc);
+ fflush(stderr);
+ return(0);
+}
+
+/********************************************************************************
+ * Status output
+ *
+ * status [-qv] [<device> ...]
+ * Prints status for <device>, or all if none listed.
+ *
+ * -q Suppresses output, command returns 0 if devices are OK, 1 if one or
+ * more devices are critical, 2 if one or more devices are offline.
+ */
+static struct mlx_rebuild_status rs;
+static int rs_ctrlr = -1;
+static int status_result = 0;
+
+/* XXX more verbosity! */
+static void
+status_print(int unit, void *arg)
+{
+ int verbosity = *(int *)arg;
+ int fd, result, ctrlr, sysdrive, statvalid;
+
+ /* Find which controller and what system drive we are */
+ statvalid = 0;
+ if (mlxd_find_ctrlr(unit, &ctrlr, &sysdrive)) {
+ warnx("couldn't get controller/drive for %s", drivepath(unit));
+ } else {
+ /* If we don't have rebuild stats for this controller, get them */
+ if (rs_ctrlr == ctrlr) {
+ statvalid = 1;
+ } else {
+ if ((fd = open(ctrlrpath(ctrlr), 0)) < 0) {
+ warn("can't open %s", ctrlrpath(ctrlr));
+ } else {
+ if (ioctl(fd, MLX_REBUILDSTAT, &rs) < 0) {
+ warn("ioctl MLX_REBUILDSTAT");
+ } else {
+ rs_ctrlr = ctrlr;
+ statvalid = 1;
+ }
+ close(fd);
+ }
+ }
+ }
+
+ /* Get the device */
+ if ((fd = open(drivepath(unit), 0)) < 0) {
+ warn("can't open %s", drivepath(unit));
+ return;
+ }
+
+ /* Get its status */
+ if (ioctl(fd, MLXD_STATUS, &result) < 0) {
+ warn("ioctl MLXD_STATUS");
+ } else {
+ switch(result) {
+ case MLX_SYSD_ONLINE:
+ if (verbosity > 0)
+ printf("%s: online", drivename(unit));
+ break;
+ case MLX_SYSD_CRITICAL:
+ if (verbosity > 0)
+ printf("%s: critical", drivename(unit));
+ if (status_result < 1)
+ status_result = 1;
+ break;
+ case MLX_SYSD_OFFLINE:
+ if (verbosity > 0)
+ printf("%s: offline", drivename(unit));
+ if (status_result < 2)
+ status_result = 2;
+ break;
+ default:
+ if (verbosity > 0) {
+ printf("%s: unknown status 0x%x", drivename(unit), result);
+ }
+ }
+ if (verbosity > 0) {
+ /* rebuild/check in progress on this drive? */
+ if (statvalid && (rs_ctrlr == ctrlr) &&
+ (rs.rs_drive == sysdrive) && (rs.rs_code != MLX_REBUILDSTAT_IDLE)) {
+ switch(rs.rs_code) {
+ case MLX_REBUILDSTAT_REBUILDCHECK:
+ printf(" [consistency check");
+ break;
+ case MLX_REBUILDSTAT_ADDCAPACITY:
+ printf(" [add capacity");
+ break;
+ case MLX_REBUILDSTAT_ADDCAPACITYINIT:
+ printf(" [add capacity init");
+ break;
+ default:
+ printf(" [unknown operation");
+ }
+ printf(": %d/%d, %d%% complete]",
+ rs.rs_remaining, rs.rs_size,
+ ((rs.rs_size - rs.rs_remaining) / (rs.rs_size / 100)));
+ }
+ printf("\n");
+ }
+ }
+ close(fd);
+}
+
+static struct
+{
+ int hwid;
+ char *name;
+} mlx_controller_names[] = {
+ {0x01, "960P/PD"},
+ {0x02, "960PL"},
+ {0x10, "960PG"},
+ {0x11, "960PJ"},
+ {0x12, "960PR"},
+ {0x13, "960PT"},
+ {0x14, "960PTL0"},
+ {0x15, "960PRL"},
+ {0x16, "960PTL1"},
+ {0x20, "1100PVX"},
+ {-1, NULL}
+};
+
+static void
+controller_print(int unit, void *arg)
+{
+ struct mlx_enquiry2 enq;
+ struct mlx_phys_drv pd;
+ int verbosity = *(int *)arg;
+ static char buf[80];
+ char *model;
+ int i, channel, target;
+
+ if (verbosity == 0)
+ return;
+
+ /* fetch and print controller data */
+ if (mlx_enquiry(unit, &enq)) {
+ printf("mlx%d: error submitting ENQUIRY2\n", unit);
+ } else {
+
+ for (i = 0, model = NULL; mlx_controller_names[i].name != NULL; i++) {
+ if ((enq.me_hardware_id & 0xff) == mlx_controller_names[i].hwid) {
+ model = mlx_controller_names[i].name;
+ break;
+ }
+ }
+ if (model == NULL) {
+ sprintf(buf, " model 0x%x", enq.me_hardware_id & 0xff);
+ model = buf;
+ }
+
+ printf("mlx%d: DAC%s, %d channel%s, firmware %d.%02d-%c-%02d, %dMB RAM\n",
+ unit, model,
+ enq.me_actual_channels,
+ enq.me_actual_channels > 1 ? "s" : "",
+ enq.me_firmware_id & 0xff,
+ (enq.me_firmware_id >> 8) & 0xff,
+ (enq.me_firmware_id >> 16),
+ (enq.me_firmware_id >> 24) & 0xff,
+ enq.me_mem_size / (1024 * 1024));
+
+ if (verbosity > 1) {
+ printf(" Hardware ID 0x%08x\n", enq.me_hardware_id);
+ printf(" Firmware ID 0x%08x\n", enq.me_firmware_id);
+ printf(" Configured/Actual channels %d/%d\n", enq.me_configured_channels,
+ enq.me_actual_channels);
+ printf(" Max Targets %d\n", enq.me_max_targets);
+ printf(" Max Tags %d\n", enq.me_max_tags);
+ printf(" Max System Drives %d\n", enq.me_max_sys_drives);
+ printf(" Max Arms %d\n", enq.me_max_arms);
+ printf(" Max Spans %d\n", enq.me_max_spans);
+ printf(" DRAM/cache/flash/NVRAM size %d/%d/%d/%d\n", enq.me_mem_size,
+ enq.me_cache_size, enq.me_flash_size, enq.me_nvram_size);
+ printf(" DRAM type %d\n", enq.me_mem_type);
+ printf(" Clock Speed %dns\n", enq.me_clock_speed);
+ printf(" Hardware Speed %dns\n", enq.me_hardware_speed);
+ printf(" Max Commands %d\n", enq.me_max_commands);
+ printf(" Max SG Entries %d\n", enq.me_max_sg);
+ printf(" Max DP %d\n", enq.me_max_dp);
+ printf(" Max IOD %d\n", enq.me_max_iod);
+ printf(" Max Comb %d\n", enq.me_max_comb);
+ printf(" Latency %ds\n", enq.me_latency);
+ printf(" SCSI Timeout %ds\n", enq.me_scsi_timeout);
+ printf(" Min Free Lines %d\n", enq.me_min_freelines);
+ printf(" Rate Constant %d\n", enq.me_rate_const);
+ printf(" MAXBLK %d\n", enq.me_maxblk);
+ printf(" Blocking Factor %d sectors\n", enq.me_blocking_factor);
+ printf(" Cache Line Size %d blocks\n", enq.me_cacheline);
+ printf(" SCSI Capability %s%dMHz, %d bit\n",
+ enq.me_scsi_cap & (1<<4) ? "differential " : "",
+ (1 << ((enq.me_scsi_cap >> 2) & 3)) * 10,
+ 8 << (enq.me_scsi_cap & 0x3));
+ printf(" Firmware Build Number %d\n", enq.me_firmware_build);
+ printf(" Fault Management Type %d\n", enq.me_fault_mgmt_type);
+#if 0
+ printf(" Features %b\n", enq.me_firmware_features,
+ "\20\4Background Init\3Read Ahead\2MORE\1Cluster\n");
+#endif
+ }
+
+ /* fetch and print physical drive data */
+ for (channel = 0; channel < enq.me_configured_channels; channel++) {
+ for (target = 0; target < enq.me_max_targets; target++) {
+ if ((mlx_get_device_state(unit, channel, target, &pd) == 0) &&
+ (pd.pd_flags1 & MLX_PHYS_DRV_PRESENT)) {
+ mlx_print_phys_drv(&pd, channel, target, " ", verbosity - 1);
+ if (verbosity > 1) {
+ /* XXX print device statistics? */
+ }
+ }
+ }
+ }
+ }
+}
+
+static int
+cmd_status(int argc, char *argv[])
+{
+ int ch, verbosity = 1, i, unit;
+
+ optreset = 1;
+ optind = 1;
+ while ((ch = getopt(argc, argv, "qv")) != -1)
+ switch(ch) {
+ case 'q':
+ verbosity = 0;
+ break;
+ case 'v':
+ verbosity = 2;
+ break;
+ default:
+ return(cmd_help(argc, argv));
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1) {
+ mlx_foreach(controller_print, &verbosity);
+ mlxd_foreach(status_print, &verbosity);
+ } else {
+ for (i = 0; i < argc; i++) {
+ if ((unit = driveunit(argv[i])) == -1) {
+ warnx("'%s' is not a valid drive", argv[i]);
+ } else {
+ status_print(unit, &verbosity);
+ }
+ }
+ }
+ return(status_result);
+}
+
+/********************************************************************************
+ * Recscan for system drives on one or more controllers.
+ *
+ * rescan <controller> [<controller>...]
+ * rescan -a
+ */
+static void
+rescan_ctrlr(int unit, void *junk)
+{
+ int fd;
+
+ /* Get the device */
+ if ((fd = open(ctrlrpath(unit), 0)) < 0) {
+ warn("can't open %s", ctrlrpath(unit));
+ return;
+ }
+
+ if (ioctl(fd, MLX_RESCAN_DRIVES) < 0)
+ warn("can't rescan %s", ctrlrname(unit));
+ close(fd);
+}
+
+static int
+cmd_rescan(int argc, char *argv[])
+{
+ int all = 0, i, ch, unit;
+
+ optreset = 1;
+ optind = 1;
+ while ((ch = getopt(argc, argv, "a")) != -1)
+ switch(ch) {
+ case 'a':
+ all = 1;
+ break;
+ default:
+ return(cmd_help(argc, argv));
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (all) {
+ mlx_foreach(rescan_ctrlr, NULL);
+ } else {
+ for (i = 0; i < argc; i++) {
+ if ((unit = ctrlrunit(argv[i])) == -1) {
+ warnx("'%s' is not a valid controller", argv[i]);
+ } else {
+ rescan_ctrlr(unit, NULL);
+ }
+ }
+ }
+ return(0);
+}
+
+/********************************************************************************
+ * Detach one or more system drives from a controller.
+ *
+ * detach <drive> [<drive>...]
+ * Detach <drive>.
+ *
+ * detach -a <controller> [<controller>...]
+ * Detach all drives on <controller>.
+ *
+ */
+static void
+detach_drive(int unit, void *arg)
+{
+ int fd;
+
+ /* Get the device */
+ if ((fd = open(ctrlrpath(unit), 0)) < 0) {
+ warn("can't open %s", ctrlrpath(unit));
+ return;
+ }
+
+ if (ioctl(fd, MLX_DETACH_DRIVE, &unit) < 0)
+ warn("can't detach %s", drivename(unit));
+ close(fd);
+}
+
+static int
+cmd_detach(int argc, char *argv[])
+{
+ struct mlxd_foreach_action ma;
+ int all = 0, i, ch, unit;
+
+ optreset = 1;
+ optind = 1;
+ while ((ch = getopt(argc, argv, "a")) != -1)
+ switch(ch) {
+ case 'a':
+ all = 1;
+ break;
+ default:
+ return(cmd_help(argc, argv));
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (all) {
+ ma.func = detach_drive;
+ ma.arg = &unit;
+ for (i = 0; i < argc; i++) {
+ if ((unit = ctrlrunit(argv[i])) == -1) {
+ warnx("'%s' is not a valid controller", argv[i]);
+ } else {
+ mlxd_foreach_ctrlr(unit, &ma);
+ }
+ }
+ } else {
+ for (i = 0; i < argc; i++) {
+ if ((unit = driveunit(argv[i])) == -1) {
+ warnx("'%s' is not a valid drive", argv[i]);
+ } else {
+ /* run across all controllers to find this drive */
+ mlx_foreach(detach_drive, &unit);
+ }
+ }
+ }
+ return(0);
+}
+
+/********************************************************************************
+ * Initiate a consistency check on a system drive.
+ *
+ * check [<drive>]
+ * Start a check of <drive>
+ *
+ */
+static int
+cmd_check(int argc, char *argv[])
+{
+ int unit, fd, result;
+
+ if (argc != 2)
+ return(cmd_help(argc, argv));
+
+ if ((unit = driveunit(argv[1])) == -1) {
+ warnx("'%s' is not a valid drive", argv[1]);
+ } else {
+
+ /* Get the device */
+ if ((fd = open(drivepath(unit), 0)) < 0) {
+ warn("can't open %s", drivepath(unit));
+ } else {
+ /* Try to start the check */
+ if ((ioctl(fd, MLXD_CHECKASYNC, &result)) < 0) {
+ switch(result) {
+ case 0x0002:
+ warnx("one or more of the SCSI disks on which the drive '%s' depends is DEAD", argv[1]);
+ break;
+ case 0x0105:
+ warnx("drive %s is invalid, or not a drive which can be checked", argv[1]);
+ break;
+ case 0x0106:
+ warnx("drive rebuild or consistency check is already in progress on this controller");
+ break;
+ default:
+ warn("ioctl MLXD_CHECKASYNC");
+ }
+ }
+ }
+ }
+ return(0);
+}
+
+/********************************************************************************
+ * Initiate a physical drive rebuild
+ *
+ * rebuild <controller> <channel>:<target>
+ * Start a rebuild of <controller>:<channel>:<target>
+ *
+ */
+static int
+cmd_rebuild(int argc, char *argv[])
+{
+ struct mlx_rebuild_request rb;
+ int unit, fd;
+
+ if (argc != 3)
+ return(cmd_help(argc, argv));
+
+ /* parse arguments */
+ if ((unit = ctrlrunit(argv[1])) == -1) {
+ warnx("'%s' is not a valid controller", argv[1]);
+ return(1);
+ }
+ /* try diskXXXX and unknownXXXX as we report the latter for a dead drive ... */
+ if ((sscanf(argv[2], "disk%2d%2d", &rb.rr_channel, &rb.rr_target) != 2) &&
+ (sscanf(argv[2], "unknown%2d%2d", &rb.rr_channel, &rb.rr_target) != 2)) {
+ warnx("'%s' is not a valid physical drive", argv[2]);
+ return(1);
+ }
+ /* get the device */
+ if ((fd = open(ctrlrpath(unit), 0)) < 0) {
+ warn("can't open %s", ctrlrpath(unit));
+ return(1);
+ }
+ /* try to start the rebuild */
+ if ((ioctl(fd, MLX_REBUILDASYNC, &rb)) < 0) {
+ switch(rb.rr_status) {
+ case 0x0002:
+ warnx("the drive at %d:%d is already ONLINE", rb.rr_channel, rb.rr_target);
+ break;
+ case 0x0004:
+ warnx("drive failed during rebuild");
+ break;
+ case 0x0105:
+ warnx("there is no drive at channel %d, target %d", rb.rr_channel, rb.rr_target);
+ break;
+ case 0x0106:
+ warnx("drive rebuild or consistency check is already in progress on this controller");
+ break;
+ default:
+ warn("ioctl MLXD_REBUILDASYNC");
+ }
+ }
+ return(0);
+}
+
+#ifdef SUPPORT_PAUSE
+/********************************************************************************
+ * Pause one or more channels on a controller
+ *
+ * pause [-d <delay>] [-t <time>] <controller> [<channel>...]
+ * Pauses <channel> (or all channels) for <time> seconds after a
+ * delay of <delay> seconds.
+ * pause <controller> -c
+ * Cancels pending pause
+ */
+static int
+cmd_pause(int argc, char *argv[])
+{
+ struct mlx_pause mp;
+ int unit, i, ch, fd, cancel = 0;
+ char *cp;
+ int oargc = argc;
+ char **oargv = argv;
+
+ mp.mp_which = 0;
+ mp.mp_when = 30;
+ mp.mp_howlong = 30;
+ optreset = 1;
+ optind = 1;
+ while ((ch = getopt(argc, argv, "cd:t:")) != -1)
+ switch(ch) {
+ case 'c':
+ cancel = 1;
+ break;
+ case 'd':
+ mp.mp_when = strtol(optarg, &cp, 0);
+ if (*cp != 0)
+ return(cmd_help(argc, argv));
+ break;
+ case 't':
+ mp.mp_howlong = strtol(optarg, &cp, 0);
+ if (*cp != 0)
+ return(cmd_help(argc, argv));
+ break;
+ default:
+ return(cmd_help(argc, argv));
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* get controller unit number that we're working on */
+ if ((argc < 1) || ((unit = ctrlrunit(argv[0])) == -1))
+ return(cmd_help(oargc, oargv));
+
+ /* Get the device */
+ if ((fd = open(ctrlrpath(unit), 0)) < 0) {
+ warn("can't open %s", ctrlrpath(unit));
+ return(1);
+ }
+
+ if (argc == 1) {
+ /* controller-wide pause/cancel */
+ mp.mp_which = cancel ? MLX_PAUSE_CANCEL : MLX_PAUSE_ALL;
+ } else {
+ for (i = 1; i < argc; i++) {
+ ch = strtol(argv[i], &cp, 0);
+ if (*cp != 0) {
+ warnx("bad channel number '%s'", argv[i]);
+ continue;
+ } else {
+ mp.mp_which |= (1 << ch);
+ }
+ }
+ }
+ if ((ioctl(fd, MLX_PAUSE_CHANNEL, &mp)) < 0)
+ warn("couldn't %s %s", cancel ? "cancel pause on" : "pause", ctrlrname(unit));
+ close(fd);
+ return(0);
+}
+#endif /* SUPPORT_PAUSE */
+
diff --git a/usr.sbin/mlxcontrol/config.c b/usr.sbin/mlxcontrol/config.c
new file mode 100644
index 0000000..a525658
--- /dev/null
+++ b/usr.sbin/mlxcontrol/config.c
@@ -0,0 +1,157 @@
+/*-
+ * Copyright (c) 1999 Michael Smith
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <fcntl.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <err.h>
+
+#include <dev/mlx/mlxio.h>
+#include <dev/mlx/mlxreg.h>
+
+#include "mlxcontrol.h"
+
+static void print_span(struct mlx_sys_drv_span *span, int arms);
+static void print_sys_drive(struct conf_config *conf, int drvno);
+static void print_phys_drive(struct conf_config *conf, int chn, int targ);
+
+/********************************************************************************
+ * Get the configuration from the selected controller.
+ *
+ * config <controller>
+ * Print the configuration for <controller>
+ *
+ * XXX update to support adding/deleting drives.
+ */
+
+int
+cmd_config(int argc, char *argv[])
+{
+ struct conf_config conf;
+ int unit = 0; /* XXX */
+ int i, j;
+
+ bzero(&conf.cc_cfg, sizeof(conf.cc_cfg));
+ if (mlx_read_configuration(unit, &conf.cc_cfg)) {
+ printf("mlx%d: error submitting READ CONFIGURATION\n", unit);
+ } else {
+
+ printf("# Controller <INSERT DETAILS HERE>\n");
+ printf("#\n# Physical devices connected:\n");
+ for (i = 0; i < 5; i++)
+ for (j = 0; j < 16; j++)
+ print_phys_drive(&conf, i, j);
+ printf("#\n# System Drives defined:\n");
+
+ for (i = 0; i < conf.cc_cfg.cc_num_sys_drives; i++)
+ print_sys_drive(&conf, i);
+ }
+ return(0);
+}
+
+
+/********************************************************************************
+ * Print details for the system drive (drvno) in a format that we will be
+ * able to parse later.
+ *
+ * drive?? <raidlevel> <writemode>
+ * span? 0x????????-0x???????? ????MB on <disk> [...]
+ * ...
+ */
+static void
+print_span(struct mlx_sys_drv_span *span, int arms)
+{
+ int i;
+
+ printf("0x%08x-0x%08x %uMB on", span->sp_start_lba, span->sp_start_lba + span->sp_nblks, span->sp_nblks / 2048);
+ for (i = 0; i < arms; i++)
+ printf(" disk%02d%02d", span->sp_arm[i] >> 4, span->sp_arm[i] & 0x0f);
+ printf("\n");
+}
+
+static void
+print_sys_drive(struct conf_config *conf, int drvno)
+{
+ struct mlx_sys_drv *drv = &conf->cc_cfg.cc_sys_drives[drvno];
+ int i;
+
+ printf("drive%02d ", drvno);
+ switch(drv->sd_raidlevel & 0xf) {
+ case MLX_SYS_DRV_RAID0:
+ printf("RAID0");
+ break;
+ case MLX_SYS_DRV_RAID1:
+ printf("RAID1");
+ break;
+ case MLX_SYS_DRV_RAID3:
+ printf("RAID3");
+ break;
+ case MLX_SYS_DRV_RAID5:
+ printf("RAID5");
+ break;
+ case MLX_SYS_DRV_RAID6:
+ printf("RAID6");
+ break;
+ case MLX_SYS_DRV_JBOD:
+ printf("JBOD");
+ break;
+ default:
+ printf("RAID?");
+ }
+ printf(" write%s\n", drv->sd_raidlevel & MLX_SYS_DRV_WRITEBACK ? "back" : "through");
+
+ for (i = 0; i < drv->sd_valid_spans; i++) {
+ printf(" span%d ", i);
+ print_span(&drv->sd_span[i], drv->sd_valid_arms);
+ }
+}
+
+/********************************************************************************
+ * Print details for the physical drive at chn/targ in a format suitable for
+ * human consumption.
+ *
+ * <type>CCTT (<state>) "<vendor>/<model>"
+ * ????MB <features>
+ *
+ */
+static void
+print_phys_drive(struct conf_config *conf, int chn, int targ)
+{
+ struct mlx_phys_drv *drv = &conf->cc_cfg.cc_phys_drives[chn * 16 + targ];
+
+ /* if the drive isn't present, don't print it */
+ if (!(drv->pd_flags1 & MLX_PHYS_DRV_PRESENT))
+ return;
+
+ mlx_print_phys_drv(drv, chn, targ, "# ", 1);
+}
+
+
diff --git a/usr.sbin/mlxcontrol/interface.c b/usr.sbin/mlxcontrol/interface.c
new file mode 100644
index 0000000..c0c241b
--- /dev/null
+++ b/usr.sbin/mlxcontrol/interface.c
@@ -0,0 +1,289 @@
+/*-
+ * Copyright (c) 1999 Michael Smith
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <cam/scsi/scsi_all.h>
+
+#include <dev/mlx/mlxio.h>
+#include <dev/mlx/mlxreg.h>
+
+#include "mlxcontrol.h"
+
+/********************************************************************************
+ * Iterate over all mlx devices, call (func) with each ones' path and (arg)
+ */
+void
+mlx_foreach(void (*func)(int unit, void *arg), void *arg)
+{
+ int i, fd;
+
+ /* limit total count for sanity */
+ for (i = 0; i < 64; i++) {
+ /* verify we can open it */
+ if ((fd = open(ctrlrpath(i), 0)) >= 0)
+ close(fd);
+ /* if we can, do */
+ if (fd >= 0) {
+ func(i, arg);
+ }
+ }
+}
+
+/********************************************************************************
+ * Open the controller (unit) and give the fd to (func) along with (arg)
+ */
+void
+mlx_perform(int unit, void (*func)(int fd, void *arg), void *arg)
+{
+ int fd;
+
+ if ((fd = open(ctrlrpath(unit), 0)) >= 0) {
+ func(fd, arg);
+ close(fd);
+ }
+}
+
+/********************************************************************************
+ * Iterate over all mlxd devices, call (func) with each ones' path and (arg)
+ */
+void
+mlxd_foreach_ctrlr(int unit, void *arg)
+{
+ struct mlxd_foreach_action *ma = (struct mlxd_foreach_action *)arg;
+ int i, fd, ctrlfd;
+
+ /* Get the device */
+ if ((ctrlfd = open(ctrlrpath(unit), 0)) < 0)
+ return;
+
+ for (i = -1; ;) {
+ /* Get the unit number of the next child device */
+ if (ioctl(ctrlfd, MLX_NEXT_CHILD, &i) < 0) {
+ close(ctrlfd);
+ return;
+ }
+
+ /* check that we can open this unit */
+ if ((fd = open(drivepath(i), 0)) >= 0)
+ close(fd);
+ /* if we can, do */
+ if (fd >= 0) {
+ ma->func(i, ma->arg);
+ }
+ }
+}
+
+void
+mlxd_foreach(void (*func)(int unit, void *arg), void *arg)
+{
+ struct mlxd_foreach_action ma;
+
+ ma.func = func;
+ ma.arg = arg;
+ mlx_foreach(mlxd_foreach_ctrlr, &ma);
+}
+
+/********************************************************************************
+ * Find the controller that manages the drive (unit), return controller number
+ * and system drive number on that controller.
+ */
+static struct
+{
+ int unit;
+ int ctrlr;
+ int sysdrive;
+} mlxd_find_ctrlr_param;
+
+static void
+mlxd_find_ctrlr_search(int unit, void *arg)
+{
+ int i, fd;
+
+ /* Get the device */
+ if ((fd = open(ctrlrpath(unit), 0)) >= 0) {
+ for (i = -1; ;) {
+ /* Get the unit number of the next child device */
+ if (ioctl(fd, MLX_NEXT_CHILD, &i) < 0)
+ break;
+
+ /* is this child the unit we want? */
+ if (i == mlxd_find_ctrlr_param.unit) {
+ mlxd_find_ctrlr_param.ctrlr = unit;
+ if (ioctl(fd, MLX_GET_SYSDRIVE, &i) == 0)
+ mlxd_find_ctrlr_param.sysdrive = i;
+ }
+ }
+ close(fd);
+ }
+}
+
+int
+mlxd_find_ctrlr(int unit, int *ctrlr, int *sysdrive)
+{
+ mlxd_find_ctrlr_param.unit = unit;
+ mlxd_find_ctrlr_param.ctrlr = -1;
+ mlxd_find_ctrlr_param.sysdrive = -1;
+
+ mlx_foreach(mlxd_find_ctrlr_search, NULL);
+ if ((mlxd_find_ctrlr_param.ctrlr != -1) && (mlxd_find_ctrlr_param.sysdrive != -1)) {
+ *ctrlr = mlxd_find_ctrlr_param.ctrlr;
+ *sysdrive = mlxd_find_ctrlr_param.sysdrive;
+ return(0);
+ }
+ return(1);
+}
+
+
+/********************************************************************************
+ * Send a command to the controller on (fd)
+ */
+
+void
+mlx_command(int fd, void *arg)
+{
+ struct mlx_usercommand *cmd = (struct mlx_usercommand *)arg;
+ int error;
+
+ error = ioctl(fd, MLX_COMMAND, cmd);
+ if (error != 0)
+ cmd->mu_error = error;
+}
+
+/********************************************************************************
+ * Perform an ENQUIRY2 command and return information related to the controller
+ * (unit)
+ */
+int
+mlx_enquiry(int unit, struct mlx_enquiry2 *enq)
+{
+ struct mlx_usercommand cmd;
+
+ /* build the command */
+ cmd.mu_datasize = sizeof(*enq);
+ cmd.mu_buf = enq;
+ cmd.mu_bufptr = 8;
+ cmd.mu_command[0] = MLX_CMD_ENQUIRY2;
+
+ /* hand it off for processing */
+ mlx_perform(unit, mlx_command, (void *)&cmd);
+
+ return(cmd.mu_status != 0);
+}
+
+
+/********************************************************************************
+ * Perform a READ CONFIGURATION command and return information related to the controller
+ * (unit)
+ */
+int
+mlx_read_configuration(int unit, struct mlx_core_cfg *cfg)
+{
+ struct mlx_usercommand cmd;
+
+ /* build the command */
+ cmd.mu_datasize = sizeof(*cfg);
+ cmd.mu_buf = cfg;
+ cmd.mu_bufptr = 8;
+ cmd.mu_command[0] = MLX_CMD_READ_CONFIG;
+
+ /* hand it off for processing */
+ mlx_perform(unit, mlx_command, (void *)&cmd);
+
+ return(cmd.mu_status != 0);
+}
+
+/********************************************************************************
+ * Perform a SCSI INQUIRY command and return pointers to the relevant data.
+ */
+int
+mlx_scsi_inquiry(int unit, int channel, int target, char **vendor, char **device, char **revision)
+{
+ struct mlx_usercommand cmd;
+ static struct {
+ struct mlx_dcdb dcdb;
+ union {
+ struct scsi_inquiry_data inq;
+ u_int8_t pad[SHORT_INQUIRY_LENGTH];
+ } d;
+ } __attribute__ ((packed)) dcdb_cmd;
+ struct scsi_inquiry *inq_cmd = (struct scsi_inquiry *)&dcdb_cmd.dcdb.dcdb_cdb[0];
+
+ /* build the command */
+ cmd.mu_datasize = sizeof(dcdb_cmd);
+ cmd.mu_buf = &dcdb_cmd;
+ cmd.mu_command[0] = MLX_CMD_DIRECT_CDB;
+
+ /* build the DCDB */
+ bzero(&dcdb_cmd, sizeof(dcdb_cmd));
+ dcdb_cmd.dcdb.dcdb_channel = channel;
+ dcdb_cmd.dcdb.dcdb_target = target;
+ dcdb_cmd.dcdb.dcdb_flags = MLX_DCDB_DATA_IN | MLX_DCDB_TIMEOUT_10S;
+ dcdb_cmd.dcdb.dcdb_datasize = SHORT_INQUIRY_LENGTH;
+ dcdb_cmd.dcdb.dcdb_cdb_length = 6;
+ dcdb_cmd.dcdb.dcdb_sense_length = SSD_FULL_SIZE;
+
+ /* build the cdb */
+ inq_cmd->opcode = INQUIRY;
+ scsi_ulto2b(SHORT_INQUIRY_LENGTH, inq_cmd->length);
+
+ /* hand it off for processing */
+ mlx_perform(unit, mlx_command, &cmd);
+
+ if (cmd.mu_status == 0) {
+ *vendor = &dcdb_cmd.d.inq.vendor[0];
+ *device = &dcdb_cmd.d.inq.product[0];
+ *revision = &dcdb_cmd.d.inq.revision[0];
+ }
+ return(cmd.mu_status);
+}
+
+/********************************************************************************
+ * Perform a GET DEVICE STATE command and return pointers to the relevant data.
+ */
+int
+mlx_get_device_state(int unit, int channel, int target, struct mlx_phys_drv *drv)
+{
+ struct mlx_usercommand cmd;
+
+ /* build the command */
+ cmd.mu_datasize = sizeof(*drv);
+ cmd.mu_buf = drv;
+ cmd.mu_bufptr = 8;
+ cmd.mu_command[0] = MLX_CMD_DEVICE_STATE;
+ cmd.mu_command[2] = channel;
+ cmd.mu_command[3] = target;
+
+ /* hand it off for processing */
+ mlx_perform(unit, mlx_command, (void *)&cmd);
+
+ return(cmd.mu_status != 0);
+}
diff --git a/usr.sbin/mlxcontrol/mlxcontrol.8 b/usr.sbin/mlxcontrol/mlxcontrol.8
new file mode 100644
index 0000000..33788b9
--- /dev/null
+++ b/usr.sbin/mlxcontrol/mlxcontrol.8
@@ -0,0 +1,148 @@
+.\"
+.\" Copyright (c) 2000 Michael Smith
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the 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 ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED 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 April 10, 2000
+.Dt MLXCONTROL 8
+.Os
+.Sh NAME
+.Nm mlxcontrol
+.Nd Mylex DAC-family RAID management utility
+.Sh SYNOPSIS
+.Nm
+.Aq command
+.Op args
+.Nm
+status
+.Op Fl qv
+.Op Ar drive
+.Nm
+rescan
+.Ar controller
+.Op Ar controller ...
+.Nm
+detach
+.Ar drive
+.Op Ar drive ...
+.Nm
+detach
+.Fl a
+.Nm
+check
+.Ar drive
+.Nm
+config
+.Ar controller
+.Nm
+help
+.Ar command
+.Sh DESCRIPTION
+The
+.Nm
+utility provides status monitoring and management functions
+for devices attached
+to the
+.Xr mlx 4
+driver.
+.Pp
+Controller names are of the form "mlxN"
+where N is the unit number of the controller.
+Drive names are of the form "mlxdN"
+where N is the unit number of the drive.
+Do not specify the path to a device node.
+.Bl -tag -width rebuild
+.It status
+Print the status of controllers and system drives.
+If one or more drives are specified,
+only print information about these drives,
+otherwise print information
+about all controllers and drives in the system.
+With the
+.Fl v
+flag, display much more verbose information.
+With the
+.Fl q
+flag, do not print any output.
+This command returns
+0 if all drives tested are online,
+1 if one or more drives are critical and
+2 if one or more are offline.
+.It rescan
+Rescan one or more controllers for non-attached system drives
+(e.g.\& drives that have been
+detached or created subsequent to driver initialisation).
+If the
+.Fl a
+flag is supplied, rescan all controllers in the system.
+.It detach
+Detach one or more system drives.
+Drives must be unmounted
+and not opened by any other utility for this command to succeed.
+If the
+.Fl a
+flag is supplied, detach all system drives from the nominated controller.
+.It check
+Initiate a consistency check and repair pass on a redundant system drive
+(e.g.\& RAID1 or RAID5).
+The controller will scan the system drive and repair any inconsistencies.
+This command returns immediately;
+use the
+.Ar status
+command to monitor the progress of the check.
+.It rebuild
+Requires two arguments,
+.Ar controller
+and
+.Ar physdrive
+as specified in the
+output of the
+.Ar status
+command.
+All system drives using space on the physical drive
+.Ar physdrive
+are rebuilt, reconstructing all data on the drive.
+Note that each controller can only perform one rebuild at a time.
+This command returns immediately; use the
+.Ar status
+command to monitor the progress of the rebuild.
+.It config
+Print the current configuration from the nominated controller.
+This command will be updated
+to allow addition/deletion of system drives from a configuration
+in a future release.
+.It help
+Print usage information for
+.Ar command .
+.El
+.Sh AUTHORS
+The
+.Nm
+utility was written by
+.An Michael Smith Aq Mt msmith@FreeBSD.org .
+.Sh BUGS
+The
+.Ar config
+command does not yet support modifying system drive configuration.
+.Pp
+Error log extraction is not yet supported.
diff --git a/usr.sbin/mlxcontrol/mlxcontrol.h b/usr.sbin/mlxcontrol/mlxcontrol.h
new file mode 100644
index 0000000..ab0f2ab
--- /dev/null
+++ b/usr.sbin/mlxcontrol/mlxcontrol.h
@@ -0,0 +1,89 @@
+/*-
+ * Copyright (c) 1999 Michael Smith
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/queue.h>
+
+#define debug(fmt, args...) printf("%s: " fmt "\n", __func__ , ##args)
+
+struct mlxd_foreach_action
+{
+ void (*func)(int unit, void *arg);
+ void *arg;
+};
+
+extern void mlx_foreach(void (*func)(int unit, void *arg), void *arg);
+void mlxd_foreach_ctrlr(int unit, void *arg);
+extern void mlxd_foreach(void (*func)(int unit, void *arg), void *arg);
+extern int mlxd_find_ctrlr(int unit, int *ctrlr, int *sysdrive);
+extern void mlx_perform(int unit, void (*func)(int fd, void *arg), void *arg);
+extern void mlx_command(int fd, void *arg);
+extern int mlx_enquiry(int unit, struct mlx_enquiry2 *enq);
+extern int mlx_read_configuration(int unit, struct mlx_core_cfg *cfg);
+extern int mlx_scsi_inquiry(int unit, int bus, int target, char **vendor, char **device, char **revision);
+extern int mlx_get_device_state(int fd, int channel, int target, struct mlx_phys_drv *drv);
+
+extern char *ctrlrpath(int unit);
+extern char *ctrlrname(int unit);
+extern char *drivepath(int unit);
+extern char *drivename(int unit);
+extern int ctrlrunit(char *str);
+extern int driveunit(char *str);
+
+extern void mlx_print_phys_drv(struct mlx_phys_drv *drv, int channel, int target, char *prefix, int verbose);
+
+struct conf_phys_drv
+{
+ TAILQ_ENTRY(conf_phys_drv) pd_link;
+ int pd_bus;
+ int pd_target;
+ struct mlx_phys_drv pd_drv;
+};
+
+struct conf_span
+{
+ TAILQ_ENTRY(conf_span) s_link;
+ struct conf_phys_drv *s_drvs[8];
+ struct mlx_sys_drv_span s_span;
+};
+
+struct conf_sys_drv
+{
+ TAILQ_ENTRY(conf_sys_drv) sd_link;
+ struct conf_span *sd_spans[4];
+ struct mlx_sys_drv sd_drv;
+};
+
+struct conf_config
+{
+ TAILQ_HEAD(,conf_phys_drv) cc_phys_drvs;
+ TAILQ_HEAD(,conf_span) cc_spans;
+ TAILQ_HEAD(,conf_sys_drv) cc_sys_drvs;
+ struct conf_sys_drv *cc_drives[32];
+ struct mlx_core_cfg cc_cfg;
+};
+
diff --git a/usr.sbin/mlxcontrol/util.c b/usr.sbin/mlxcontrol/util.c
new file mode 100644
index 0000000..37cd2b7
--- /dev/null
+++ b/usr.sbin/mlxcontrol/util.c
@@ -0,0 +1,172 @@
+/*-
+ * Copyright (c) 1999 Michael Smith
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <stdio.h>
+#include <paths.h>
+#include <string.h>
+
+#include <dev/mlx/mlxio.h>
+#include <dev/mlx/mlxreg.h>
+
+#include "mlxcontrol.h"
+
+/********************************************************************************
+ * Various name-producing and -parsing functions
+ */
+
+/* return path of controller (unit) */
+char *
+ctrlrpath(int unit)
+{
+ static char buf[32];
+
+ sprintf(buf, "%s%s", _PATH_DEV, ctrlrname(unit));
+ return(buf);
+}
+
+/* return name of controller (unit) */
+char *
+ctrlrname(int unit)
+{
+ static char buf[32];
+
+ sprintf(buf, "mlx%d", unit);
+ return(buf);
+}
+
+/* return path of drive (unit) */
+char *
+drivepath(int unit)
+{
+ static char buf[32];
+
+ sprintf(buf, "%s%s", _PATH_DEV, drivename(unit));
+ return(buf);
+}
+
+/* return name of drive (unit) */
+char *
+drivename(int unit)
+{
+ static char buf[32];
+
+ sprintf(buf, "mlxd%d", unit);
+ return(buf);
+}
+
+/* get controller unit number from name in (str) */
+int
+ctrlrunit(char *str)
+{
+ int unit;
+
+ if (sscanf(str, "mlx%d", &unit) == 1)
+ return(unit);
+ return(-1);
+}
+
+/* get drive unit number from name in (str) */
+int
+driveunit(char *str)
+{
+ int unit;
+
+ if (sscanf(str, "mlxd%d", &unit) == 1)
+ return(unit);
+ return(-1);
+}
+
+/********************************************************************************
+ * Standardised output of various data structures.
+ */
+
+void
+mlx_print_phys_drv(struct mlx_phys_drv *drv, int chn, int targ, char *prefix, int verbose)
+{
+ char *type, *device, *vendor, *revision;
+
+ switch(drv->pd_flags2 & 0x03) {
+ case MLX_PHYS_DRV_DISK:
+ type = "disk";
+ break;
+ case MLX_PHYS_DRV_SEQUENTIAL:
+ type = "tape";
+ break;
+ case MLX_PHYS_DRV_CDROM:
+ type= "cdrom";
+ break;
+ case MLX_PHYS_DRV_OTHER:
+ default:
+ type = "unknown";
+ break;
+ }
+ printf("%s%s%02d%02d ", prefix, type, chn, targ);
+ switch(drv->pd_status) {
+ case MLX_PHYS_DRV_DEAD:
+ printf(" (dead) ");
+ break;
+ case MLX_PHYS_DRV_WRONLY:
+ printf(" (write-only) ");
+ break;
+ case MLX_PHYS_DRV_ONLINE:
+ printf(" (online) ");
+ break;
+ case MLX_PHYS_DRV_STANDBY:
+ printf(" (standby) ");
+ break;
+ default:
+ printf(" (0x%02x) ", drv->pd_status);
+ }
+ printf("\n");
+
+ if (verbose) {
+
+ printf("%s ", prefix);
+ if (!mlx_scsi_inquiry(0, chn, targ, &vendor, &device, &revision)) {
+ printf("'%8.8s' '%16.16s' '%4.4s'", vendor, device, revision);
+ } else {
+ printf("<IDENTIFY FAILED>");
+ }
+
+ printf(" %dMB ", drv->pd_config_size / 2048);
+
+ if (drv->pd_flags2 & MLX_PHYS_DRV_FAST20) {
+ printf(" fast20");
+ } else if (drv->pd_flags2 & MLX_PHYS_DRV_FAST) {
+ printf(" fast");
+ }
+ if (drv->pd_flags2 & MLX_PHYS_DRV_WIDE)
+ printf(" wide");
+ if (drv->pd_flags2 & MLX_PHYS_DRV_SYNC)
+ printf(" sync");
+ if (drv->pd_flags2 & MLX_PHYS_DRV_TAG)
+ printf(" tag-enabled");
+ printf("\n");
+ }
+}
diff --git a/usr.sbin/mount_smbfs/Makefile b/usr.sbin/mount_smbfs/Makefile
new file mode 100644
index 0000000..c4c2d1c
--- /dev/null
+++ b/usr.sbin/mount_smbfs/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+PROG= mount_smbfs
+SRCS= mount_smbfs.c getmntopts.c
+MAN= mount_smbfs.8
+
+MOUNTDIR= ${.CURDIR}/../../sbin/mount
+CONTRIBDIR= ${.CURDIR}/../../contrib/smbfs
+CFLAGS+= -DSMBFS -I${MOUNTDIR} -I${CONTRIBDIR}/include
+
+LIBADD= smb
+
+.PATH: ${CONTRIBDIR}/mount_smbfs
+.PATH: ${MOUNTDIR}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/mount_smbfs/Makefile.depend b/usr.sbin/mount_smbfs/Makefile.depend
new file mode 100644
index 0000000..9c676a0
--- /dev/null
+++ b/usr.sbin/mount_smbfs/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libkiconv \
+ lib/libsmb \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/mountd/Makefile b/usr.sbin/mountd/Makefile
new file mode 100644
index 0000000..ab32fa3
--- /dev/null
+++ b/usr.sbin/mountd/Makefile
@@ -0,0 +1,16 @@
+# From: @(#)Makefile 8.3 (Berkeley) 1/25/94
+# $FreeBSD$
+
+PROG= mountd
+SRCS= mountd.c getmntopts.c
+MAN= exports.5 netgroup.5 mountd.8
+
+MOUNT= ${.CURDIR}/../../sbin/mount
+CFLAGS+= -I${MOUNT}
+WARNS?= 2
+
+.PATH: ${MOUNT}
+
+LIBADD= util
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/mountd/Makefile.depend b/usr.sbin/mountd/Makefile.depend
new file mode 100644
index 0000000..0a49c9e
--- /dev/null
+++ b/usr.sbin/mountd/Makefile.depend
@@ -0,0 +1,22 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/mountd/exports.5 b/usr.sbin/mountd/exports.5
new file mode 100644
index 0000000..018a865
--- /dev/null
+++ b/usr.sbin/mountd/exports.5
@@ -0,0 +1,515 @@
+.\" 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.
+.\"
+.\" @(#)exports.5 8.3 (Berkeley) 3/29/95
+.\" $FreeBSD$
+.\"
+.Dd August 14, 2014
+.Dt EXPORTS 5
+.Os
+.Sh NAME
+.Nm exports
+.Nd define remote mount points for
+.Tn NFS
+mount requests
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+The
+.Nm
+file specifies remote mount points for the
+.Tn NFS
+mount protocol per the
+.Tn NFS
+server specification; see
+.%T "Network File System Protocol Specification" ,
+RFC1094, Appendix A and
+.%T "NFS: Network File System Version 3 Specification" ,
+Appendix I.
+.Pp
+Each line in the file
+(other than comment lines that begin with a #)
+specifies the mount point(s) and export flags within one local server
+file system or the NFSv4 tree root for one or more hosts.
+A long line may be split over several lines by ending all but the
+last line with a backslash
+.Pq Ql \e .
+A host may be specified only once for each local file or the NFSv4 tree root on the
+server and there may be only one default entry for each server
+file system that applies to all other hosts.
+The latter exports the file system to the
+.Dq world
+and should
+be used only when the file system contains public information.
+.Pp
+In a mount entry,
+the first field(s) specify the directory path(s) within a server file system
+that can be mounted on by the corresponding client(s).
+There are three forms of this specification.
+The first is to list all mount points as absolute
+directory paths separated by whitespace.
+This list of directory paths should be considered an
+.Dq administrative control ,
+since it is only enforced by the
+.Xr mountd 8
+daemon and not the kernel.
+As such, it only applies to NFSv2 and NFSv3 mounts and only
+with respect to the client's use of the mount protocol.
+The second is to specify the pathname of the root of the file system
+followed by the
+.Fl alldirs
+flag;
+this form allows the host(s) to mount at any point within the file system,
+including regular files if the
+.Fl r
+option is used on
+.Xr mountd 8 .
+Because NFSv4 does not use the mount protocol,
+the
+.Dq administrative controls
+are not applied and all directories within this server
+file system are mountable via NFSv4 even if the
+.Fl alldirs
+flag has not been specified.
+The third form has the string ``V4:'' followed by a single absolute path
+name, to specify the NFSv4 tree root.
+This line does not export any file system, but simply marks where the root
+of the server's directory tree is for NFSv4 clients.
+The exported file systems for NFSv4 are specified via the other lines
+in the
+.Nm
+file in the same way as for NFSv2 and NFSv3.
+The pathnames must not have any symbolic links in them and should not have
+any
+.Dq Pa \&.
+or
+.Dq Pa ..
+components.
+Mount points for a file system may appear on multiple lines each with
+different sets of hosts and export options.
+.Pp
+The second component of a line specifies how the file system is to be
+exported to the host set.
+The option flags specify whether the file system
+is exported read-only or read-write and how the client UID is mapped to
+user credentials on the server.
+For the NFSv4 tree root, the only option that can be specified in this
+section is
+.Fl sec .
+.Pp
+Export options are specified as follows:
+.Pp
+.Sm off
+.Fl maproot Li = Sy user
+.Sm on
+The credential of the specified user is used for remote access by root.
+The credential includes all the groups to which the user is a member
+on the local machine (see
+.Xr id 1 ) .
+The user may be specified by name or number.
+The user string may be quoted, or use backslash escaping.
+.Pp
+.Sm off
+.Fl maproot Li = Sy user:group1:group2:...
+.Sm on
+The colon separated list is used to specify the precise credential
+to be used for remote access by root.
+The elements of the list may be either names or numbers.
+Note that user: should be used to distinguish a credential containing
+no groups from a complete credential for that user.
+The group names may be quoted, or use backslash escaping.
+.Pp
+.Sm off
+.Fl mapall Li = Sy user
+.Sm on
+or
+.Sm off
+.Fl mapall Li = Sy user:group1:group2:...
+.Sm on
+specifies a mapping for all client UIDs (including root)
+using the same semantics as
+.Fl maproot .
+.Pp
+The option
+.Fl r
+is a synonym for
+.Fl maproot
+in an effort to be backward compatible with older export file formats.
+.Pp
+In the absence of
+.Fl maproot
+and
+.Fl mapall
+options, remote accesses by root will result in using a credential of -2:-2.
+All other users will be mapped to their remote credential.
+If a
+.Fl maproot
+option is given,
+remote access by root will be mapped to that credential instead of -2:-2.
+If a
+.Fl mapall
+option is given,
+all users (including root) will be mapped to that credential in
+place of their own.
+.Pp
+.Sm off
+.Fl sec Li = Sy flavor1:flavor2...
+.Sm on
+specifies a colon separated list of acceptable security flavors to be
+used for remote access.
+Supported security flavors are sys, krb5, krb5i and krb5p.
+If multiple flavors are listed, they should be ordered with the most
+preferred flavor first.
+If this option is not present,
+the default security flavor list of just sys is used.
+.Pp
+The
+.Fl ro
+option specifies that the file system should be exported read-only
+(default read/write).
+The option
+.Fl o
+is a synonym for
+.Fl ro
+in an effort to be backward compatible with older export file formats.
+.Pp
+.Tn WebNFS
+exports strictly according to the spec (RFC 2054 and RFC 2055) can
+be done with the
+.Fl public
+flag.
+However, this flag in itself allows r/w access to all files in
+the file system, not requiring reserved ports and not remapping UIDs.
+It
+is only provided to conform to the spec, and should normally not be used.
+For a
+.Tn WebNFS
+export,
+use the
+.Fl webnfs
+flag, which implies
+.Fl public ,
+.Sm off
+.Fl mapall No = Sy nobody
+.Sm on
+and
+.Fl ro .
+Note that only one file system can be
+.Tn WebNFS
+exported on a server.
+.Pp
+A
+.Sm off
+.Fl index No = Pa file
+.Sm on
+option can be used to specify a file whose handle will be returned if
+a directory is looked up using the public filehandle
+.Pq Tn WebNFS .
+This is to mimic the behavior of URLs.
+If no
+.Fl index
+option is specified, a directory filehandle will be returned as usual.
+The
+.Fl index
+option only makes sense in combination with the
+.Fl public
+or
+.Fl webnfs
+flags.
+.Pp
+Specifying the
+.Fl quiet
+option will inhibit some of the syslog diagnostics for bad lines in
+.Pa /etc/exports .
+This can be useful to avoid annoying error messages for known possible
+problems (see
+.Sx EXAMPLES
+below).
+.Pp
+The third component of a line specifies the host set to which the line applies.
+The set may be specified in three ways.
+The first way is to list the host name(s) separated by white space.
+(Standard Internet
+.Dq dot
+addresses may be used in place of names.)
+The second way is to specify a
+.Dq netgroup
+as defined in the
+.Pa netgroup
+file (see
+.Xr netgroup 5 ) .
+The third way is to specify an Internet subnetwork using a network and
+network mask that is defined as the set of all hosts with addresses within
+the subnetwork.
+This latter approach requires less overhead within the
+kernel and is recommended for cases where the export line refers to a
+large number of clients within an administrative subnet.
+.Pp
+The first two cases are specified by simply listing the name(s) separated
+by whitespace.
+All names are checked to see if they are
+.Dq netgroup
+names
+first and are assumed to be hostnames otherwise.
+Using the full domain specification for a hostname can normally
+circumvent the problem of a host that has the same name as a netgroup.
+The third case is specified by the flag
+.Sm off
+.Fl network Li = Sy netname Op Li / Ar prefixlength
+.Sm on
+and optionally
+.Sm off
+.Fl mask No = Sy netmask .
+.Sm on
+The netmask may be specified either by attaching a
+.Ar prefixlength
+to the
+.Fl network
+option, or by using a separate
+.Fl mask
+option.
+If the mask is not specified, it will default to the mask for that network
+class (A, B or C; see
+.Xr inet 4 ) .
+See the
+.Sx EXAMPLES
+section below.
+.Pp
+Scoped IPv6 address must carry scope identifier as documented in
+.Xr inet6 4 .
+For example,
+.Dq Li fe80::%re2/10
+is used to specify
+.Li fe80::/10
+on
+.Li re2
+interface.
+.Pp
+For the third form which specifies the NFSv4 tree root, the directory path
+specifies the location within the server's file system tree which is the
+root of the NFSv4 tree.
+There can only be one NFSv4 root directory per server.
+As such, all entries of this form must specify the same directory path.
+For file systems other than ZFS,
+this location can be any directory and does not
+need to be within an exported file system. If it is not in an exported
+file system, a very limited set of operations are permitted, so that an
+NFSv4 client can traverse the tree to an exported file system.
+Although parts of the NFSv4 tree can be non-exported, the entire NFSv4 tree
+must consist of local file systems capable of being exported via NFS.
+All ZFS file systems in the subtree below the NFSv4 tree root must be
+exported.
+NFSv4 does not use the mount protocol and does permit clients to cross server
+mount point boundaries, although not all clients are capable of crossing the
+mount points.
+.Pp
+The
+.Fl sec
+option on these line(s) specifies what security flavors may be used for
+NFSv4 operations that do not use file handles. Since these operations
+(SetClientID, SetClientIDConfirm, Renew, DelegPurge and ReleaseLockOnwer)
+allocate/modify state in the server, it is possible to restrict some clients to
+the use of the krb5[ip] security flavors, via this option.
+See the
+.Sx EXAMPLES
+section below.
+This third form is meaningless for NFSv2 and NFSv3 and is ignored for them.
+.Pp
+The
+.Xr mountd 8
+utility can be made to re-read the
+.Nm
+file by sending it a hangup signal as follows:
+.Bd -literal -offset indent
+/etc/rc.d/mountd reload
+.Ed
+.Pp
+After sending the
+.Dv SIGHUP ,
+check the
+.Xr syslogd 8
+output to see whether
+.Xr mountd 8
+logged any parsing errors in the
+.Nm
+file.
+.Sh FILES
+.Bl -tag -width /etc/exports -compact
+.It Pa /etc/exports
+the default remote mount-point file
+.El
+.Sh EXAMPLES
+.Bd -literal -offset indent
+/usr /usr/local -maproot=0:10 friends
+/usr -maproot=daemon grumpy.cis.uoguelph.ca 131.104.48.16
+/usr -ro -mapall=nobody
+/u -maproot=bin: -network 131.104.48 -mask 255.255.255.0
+/a -network 192.168.0/24
+/a -network 3ffe:1ce1:1:fe80::/64
+/u2 -maproot=root friends
+/u2 -alldirs -network cis-net -mask cis-mask
+/cdrom -alldirs,quiet,ro -network 192.168.33.0 -mask 255.255.255.0
+/private -sec=krb5i
+/secret -sec=krb5p
+V4: / -sec=krb5:krb5i:krb5p -network 131.104.48 -mask 255.255.255.0
+V4: / -sec=sys:krb5:krb5i:krb5p grumpy.cis.uoguelph.ca
+.Ed
+.Pp
+Given that
+.Pa /usr , /u , /a
+and
+.Pa /u2
+are
+local file system mount points, the above example specifies the following:
+.Pp
+The file system rooted at
+.Pa /usr
+is exported to hosts
+.Em friends
+where friends is specified in the netgroup file
+with users mapped to their remote credentials and
+root mapped to UID 0 and group 10.
+It is exported read-write and the hosts in
+.Dq friends
+can mount either
+.Pa /usr
+or
+.Pa /usr/local .
+It is exported to
+.Em 131.104.48.16
+and
+.Em grumpy.cis.uoguelph.ca
+with users mapped to their remote credentials and
+root mapped to the user and groups associated with
+.Dq daemon ;
+it is exported to the rest of the world as read-only with
+all users mapped to the user and groups associated with
+.Dq nobody .
+.Pp
+The file system rooted at
+.Pa /u
+is exported to all hosts on the subnetwork
+.Em 131.104.48
+with root mapped to the UID for
+.Dq bin
+and with no group access.
+.Pp
+The file system rooted at
+.Pa /u2
+is exported to the hosts in
+.Dq friends
+with root mapped to UID and groups
+associated with
+.Dq root ;
+it is exported to all hosts on network
+.Dq cis-net
+allowing mounts at any
+directory within /u2.
+.Pp
+The file system rooted at
+.Pa /a
+is exported to the network 192.168.0.0, with a netmask of 255.255.255.0.
+However, the netmask length in the entry for
+.Pa /a
+is not specified through a
+.Fl mask
+option, but through the
+.Li / Ns Ar prefix
+notation.
+.Pp
+The file system rooted at
+.Pa /a
+is also exported to the IPv6 network
+.Li 3ffe:1ce1:1:fe80::
+address, using the upper 64 bits as the prefix.
+Note that, unlike with IPv4 network addresses, the specified network
+address must be complete, and not just contain the upper bits.
+With IPv6 addresses, the
+.Fl mask
+option must not be used.
+.Pp
+The file system rooted at
+.Pa /cdrom
+will be exported read-only to the entire network 192.168.33.0/24, including
+all its subdirectories.
+Since
+.Pa /cdrom
+is the conventional mountpoint for a CD-ROM device, this export will
+fail if no CD-ROM medium is currently mounted there since that line
+would then attempt to export a subdirectory of the root file system
+with the
+.Fl alldirs
+option which is not allowed.
+The
+.Fl quiet
+option will then suppress the error message for this condition that
+would normally be syslogged.
+As soon as an actual CD-ROM is going to be mounted,
+.Xr mount 8
+will notify
+.Xr mountd 8
+about this situation, and the
+.Pa /cdrom
+file system will be exported as intended.
+Note that without using the
+.Fl alldirs
+option, the export would always succeed.
+While there is no CD-ROM medium mounted under
+.Pa /cdrom ,
+it would export the (normally empty) directory
+.Pa /cdrom
+of the root file system instead.
+.Pp
+The file system rooted at
+.Pa /private
+will be exported using Kerberos 5 authentication and will require
+integrity protected messages for all accesses.
+The file system rooted at
+.Pa /secret
+will also be exported using Kerberos 5 authentication and all messages
+used to access it will be encrypted.
+.Pp
+For the experimental server, the NFSv4 tree is rooted at ``/'',
+and any client within the 131.104.48 subnet is permitted to perform NFSv4 state
+operations on the server, so long as valid Kerberos credentials are provided.
+The machine grumpy.cis.uoguelph.ca is permitted to perform NFSv4 state
+operations on the server using AUTH_SYS credentials, as well as Kerberos ones.
+.Sh SEE ALSO
+.Xr nfsv4 4 ,
+.Xr netgroup 5 ,
+.Xr mountd 8 ,
+.Xr nfsd 8 ,
+.Xr showmount 8
+.Sh BUGS
+The export options are tied to the local mount points in the kernel and
+must be non-contradictory for any exported subdirectory of the local
+server mount point.
+It is recommended that all exported directories within the same server
+file system be specified on adjacent lines going down the tree.
+You cannot specify a hostname that is also the name of a netgroup.
+Specifying the full domain specification for a hostname can normally
+circumvent the problem.
diff --git a/usr.sbin/mountd/mountd.8 b/usr.sbin/mountd/mountd.8
new file mode 100644
index 0000000..4dbf5ff
--- /dev/null
+++ b/usr.sbin/mountd/mountd.8
@@ -0,0 +1,198 @@
+.\" 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.
+.\"
+.\" @(#)mountd.8 8.4 (Berkeley) 4/28/95
+.\" $FreeBSD$
+.\"
+.Dd October 14, 2012
+.Dt MOUNTD 8
+.Os
+.Sh NAME
+.Nm mountd
+.Nd service remote
+.Tn NFS
+mount requests
+.Sh SYNOPSIS
+.Nm
+.Op Fl 2delnrS
+.Op Fl h Ar bindip
+.Op Fl p Ar port
+.Op Ar exportsfile ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is the server for
+.Tn NFS
+mount requests from other client machines.
+It listens for service requests at the port indicated in the
+.Tn NFS
+server specification; see
+.%T "Network File System Protocol Specification" ,
+RFC1094, Appendix A and
+.%T "NFS: Network File System Version 3 Protocol Specification" ,
+Appendix I.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl 2
+Allow the administrator to force clients to use only the
+version 2
+.Tn NFS
+protocol to mount file systems from this server.
+.It Fl d
+Output debugging information.
+.Nm
+will not detach from the controlling terminal and will print
+debugging messages to stderr.
+.It Fl e
+Ignored; included for backward compatibility.
+.It Fl h Ar bindip
+Specify specific IP addresses to bind to for TCP and UDP requests.
+This option may be specified multiple times.
+If no
+.Fl h
+option is specified,
+.Nm
+will bind to
+.Dv INADDR_ANY .
+Note that when specifying IP addresses with
+.Fl h ,
+.Nm
+will automatically add
+.Li 127.0.0.1
+and if IPv6 is enabled,
+.Li ::1
+to the list.
+.It Fl l
+Cause all succeeded
+.Nm
+requests to be logged.
+.It Fl n
+Allow non-root mount requests to be served.
+This should only be specified if there are clients such as PC's,
+that require it.
+It will automatically clear the vfs.nfsrv.nfs_privport sysctl flag, which
+controls if the kernel will accept NFS requests from reserved ports only.
+.It Fl p Ar port
+Force
+.Nm
+to bind to the specified port, for both
+.Dv AF_INET
+and
+.Dv AF_INET6
+address families.
+This is typically done to ensure that the port which
+.Nm
+binds to is a known quantity which can be used in firewall rulesets.
+If
+.Nm
+cannot bind to this port, an appropriate error will be recorded in
+the system log, and the daemon will then exit.
+.It Fl r
+Allow mount RPCs requests for regular files to be served.
+Although this seems to violate the mount protocol specification,
+some diskless workstations do mount requests for
+their swapfiles and expect them to be regular files.
+Since a regular file cannot be specified in
+.Pa /etc/exports ,
+the entire file system in which the swapfiles resides
+will have to be exported with the
+.Fl alldirs
+flag.
+.It Ar exportsfile
+Specify an alternate location
+for the exports file.
+More than one exports file can be specified.
+.It Fl S
+Tell mountd to suspend/resume execution of the nfsd threads whenever
+the exports list is being reloaded.
+This avoids intermittent access
+errors for clients that do NFS RPCs while the exports are being
+reloaded, but introduces a delay in RPC response while the reload
+is in progress.
+If
+.Nm
+crashes while an exports load is in progress,
+.Nm
+must be restarted to get the nfsd threads running again, if this
+option is used.
+.El
+.Pp
+When
+.Nm
+is started,
+it loads the export host addresses and options into the kernel
+using the
+.Xr mount 2
+system call.
+After changing the exports file,
+a hangup signal should be sent to the
+.Nm
+daemon
+to get it to reload the export information.
+After sending the SIGHUP
+(kill \-s HUP `cat /var/run/mountd.pid`),
+check the syslog output to see if
+.Nm
+logged any parsing
+errors in the exports file.
+.Pp
+If
+.Nm
+detects that the running kernel does not include
+.Tn NFS
+support, it will attempt to load a loadable kernel module containing
+.Tn NFS
+code, using
+.Xr kldload 2 .
+If this fails, or no
+.Tn NFS
+KLD was available,
+.Nm
+exits with an error.
+.Sh FILES
+.Bl -tag -width /var/run/mountd.pid -compact
+.It Pa /etc/exports
+the list of exported file systems
+.It Pa /var/run/mountd.pid
+the pid of the currently running mountd
+.It Pa /var/db/mountdtab
+the current list of remote mounted file systems
+.El
+.Sh SEE ALSO
+.Xr nfsstat 1 ,
+.Xr kldload 2 ,
+.Xr nfsv4 4 ,
+.Xr exports 5 ,
+.Xr nfsd 8 ,
+.Xr rpcbind 8 ,
+.Xr showmount 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Bx 4.4 .
diff --git a/usr.sbin/mountd/mountd.c b/usr.sbin/mountd/mountd.c
new file mode 100644
index 0000000..d6da2bc
--- /dev/null
+++ b/usr.sbin/mountd/mountd.c
@@ -0,0 +1,3290 @@
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Herb Hasler and 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.
+ */
+
+#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*/
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)mountd.c 8.15 (Berkeley) 5/1/95";
+#endif /*not lint*/
+#endif
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/fcntl.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/syslog.h>
+
+#include <rpc/rpc.h>
+#include <rpc/rpc_com.h>
+#include <rpc/pmap_clnt.h>
+#include <rpc/pmap_prot.h>
+#include <rpcsvc/mount.h>
+#include <nfs/nfsproto.h>
+#include <nfs/nfssvc.h>
+#include <nfsserver/nfs.h>
+
+#include <fs/nfs/nfsport.h>
+
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <grp.h>
+#include <libutil.h>
+#include <limits.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "pathnames.h"
+#include "mntopts.h"
+
+#ifdef DEBUG
+#include <stdarg.h>
+#endif
+
+/*
+ * Structures for keeping the mount list and export list
+ */
+struct mountlist {
+ struct mountlist *ml_next;
+ char ml_host[MNTNAMLEN+1];
+ char ml_dirp[MNTPATHLEN+1];
+};
+
+struct dirlist {
+ struct dirlist *dp_left;
+ struct dirlist *dp_right;
+ int dp_flag;
+ struct hostlist *dp_hosts; /* List of hosts this dir exported to */
+ char dp_dirp[1]; /* Actually malloc'd to size of dir */
+};
+/* dp_flag bits */
+#define DP_DEFSET 0x1
+#define DP_HOSTSET 0x2
+
+struct exportlist {
+ struct exportlist *ex_next;
+ struct dirlist *ex_dirl;
+ struct dirlist *ex_defdir;
+ int ex_flag;
+ fsid_t ex_fs;
+ char *ex_fsdir;
+ char *ex_indexfile;
+ int ex_numsecflavors;
+ int ex_secflavors[MAXSECFLAVORS];
+ int ex_defnumsecflavors;
+ int ex_defsecflavors[MAXSECFLAVORS];
+};
+/* ex_flag bits */
+#define EX_LINKED 0x1
+
+struct netmsk {
+ struct sockaddr_storage nt_net;
+ struct sockaddr_storage nt_mask;
+ char *nt_name;
+};
+
+union grouptypes {
+ struct addrinfo *gt_addrinfo;
+ struct netmsk gt_net;
+};
+
+struct grouplist {
+ int gr_type;
+ union grouptypes gr_ptr;
+ struct grouplist *gr_next;
+ int gr_numsecflavors;
+ int gr_secflavors[MAXSECFLAVORS];
+};
+/* Group types */
+#define GT_NULL 0x0
+#define GT_HOST 0x1
+#define GT_NET 0x2
+#define GT_DEFAULT 0x3
+#define GT_IGNORE 0x5
+
+struct hostlist {
+ int ht_flag; /* Uses DP_xx bits */
+ struct grouplist *ht_grp;
+ struct hostlist *ht_next;
+};
+
+struct fhreturn {
+ int fhr_flag;
+ int fhr_vers;
+ nfsfh_t fhr_fh;
+ int fhr_numsecflavors;
+ int *fhr_secflavors;
+};
+
+#define GETPORT_MAXTRY 20 /* Max tries to get a port # */
+
+/* Global defs */
+static char *add_expdir(struct dirlist **, char *, int);
+static void add_dlist(struct dirlist **, struct dirlist *,
+ struct grouplist *, int, struct exportlist *);
+static void add_mlist(char *, char *);
+static int check_dirpath(char *);
+static int check_options(struct dirlist *);
+static int checkmask(struct sockaddr *sa);
+static int chk_host(struct dirlist *, struct sockaddr *, int *, int *,
+ int *, int **);
+static char *strsep_quote(char **stringp, const char *delim);
+static int create_service(struct netconfig *nconf);
+static void complete_service(struct netconfig *nconf, char *port_str);
+static void clearout_service(void);
+static void del_mlist(char *hostp, char *dirp);
+static struct dirlist *dirp_search(struct dirlist *, char *);
+static int do_mount(struct exportlist *, struct grouplist *, int,
+ struct xucred *, char *, int, struct statfs *);
+static int do_opt(char **, char **, struct exportlist *,
+ struct grouplist *, int *, int *, struct xucred *);
+static struct exportlist *ex_search(fsid_t *);
+static struct exportlist *get_exp(void);
+static void free_dir(struct dirlist *);
+static void free_exp(struct exportlist *);
+static void free_grp(struct grouplist *);
+static void free_host(struct hostlist *);
+static void get_exportlist(void);
+static int get_host(char *, struct grouplist *, struct grouplist *);
+static struct hostlist *get_ht(void);
+static int get_line(void);
+static void get_mountlist(void);
+static int get_net(char *, struct netmsk *, int);
+static void getexp_err(struct exportlist *, struct grouplist *);
+static struct grouplist *get_grp(void);
+static void hang_dirp(struct dirlist *, struct grouplist *,
+ struct exportlist *, int);
+static void huphandler(int sig);
+static int makemask(struct sockaddr_storage *ssp, int bitlen);
+static void mntsrv(struct svc_req *, SVCXPRT *);
+static void nextfield(char **, char **);
+static void out_of_mem(void);
+static void parsecred(char *, struct xucred *);
+static int parsesec(char *, struct exportlist *);
+static int put_exlist(struct dirlist *, XDR *, struct dirlist *,
+ int *, int);
+static void *sa_rawaddr(struct sockaddr *sa, int *nbytes);
+static int sacmp(struct sockaddr *sa1, struct sockaddr *sa2,
+ struct sockaddr *samask);
+static int scan_tree(struct dirlist *, struct sockaddr *);
+static void usage(void);
+static int xdr_dir(XDR *, char *);
+static int xdr_explist(XDR *, caddr_t);
+static int xdr_explist_brief(XDR *, caddr_t);
+static int xdr_explist_common(XDR *, caddr_t, int);
+static int xdr_fhs(XDR *, caddr_t);
+static int xdr_mlist(XDR *, caddr_t);
+static void terminate(int);
+
+static struct exportlist *exphead;
+static struct mountlist *mlhead;
+static struct grouplist *grphead;
+static char *exnames_default[2] = { _PATH_EXPORTS, NULL };
+static char **exnames;
+static char **hosts = NULL;
+static struct xucred def_anon = {
+ XUCRED_VERSION,
+ (uid_t)-2,
+ 1,
+ { (gid_t)-2 },
+ NULL
+};
+static int force_v2 = 0;
+static int resvport_only = 1;
+static int nhosts = 0;
+static int dir_only = 1;
+static int dolog = 0;
+static int got_sighup = 0;
+static int xcreated = 0;
+
+static char *svcport_str = NULL;
+static int mallocd_svcport = 0;
+static int *sock_fd;
+static int sock_fdcnt;
+static int sock_fdpos;
+static int suspend_nfsd = 0;
+
+static int opt_flags;
+static int have_v6 = 1;
+
+static int v4root_phase = 0;
+static char v4root_dirpath[PATH_MAX + 1];
+static int has_publicfh = 0;
+
+static struct pidfh *pfh = NULL;
+/* Bits for opt_flags above */
+#define OP_MAPROOT 0x01
+#define OP_MAPALL 0x02
+/* 0x4 free */
+#define OP_MASK 0x08
+#define OP_NET 0x10
+#define OP_ALLDIRS 0x40
+#define OP_HAVEMASK 0x80 /* A mask was specified or inferred. */
+#define OP_QUIET 0x100
+#define OP_MASKLEN 0x200
+#define OP_SEC 0x400
+
+#ifdef DEBUG
+static int debug = 1;
+static void SYSLOG(int, const char *, ...) __printflike(2, 3);
+#define syslog SYSLOG
+#else
+static int debug = 0;
+#endif
+
+/*
+ * Similar to strsep(), but it allows for quoted strings
+ * and escaped characters.
+ *
+ * It returns the string (or NULL, if *stringp is NULL),
+ * which is a de-quoted version of the string if necessary.
+ *
+ * It modifies *stringp in place.
+ */
+static char *
+strsep_quote(char **stringp, const char *delim)
+{
+ char *srcptr, *dstptr, *retval;
+ char quot = 0;
+
+ if (stringp == NULL || *stringp == NULL)
+ return (NULL);
+
+ srcptr = dstptr = retval = *stringp;
+
+ while (*srcptr) {
+ /*
+ * We're looking for several edge cases here.
+ * First: if we're in quote state (quot != 0),
+ * then we ignore the delim characters, but otherwise
+ * process as normal, unless it is the quote character.
+ * Second: if the current character is a backslash,
+ * we take the next character as-is, without checking
+ * for delim, quote, or backslash. Exception: if the
+ * next character is a NUL, that's the end of the string.
+ * Third: if the character is a quote character, we toggle
+ * quote state.
+ * Otherwise: check the current character for NUL, or
+ * being in delim, and end the string if either is true.
+ */
+ if (*srcptr == '\\') {
+ srcptr++;
+ /*
+ * The edge case here is if the next character
+ * is NUL, we want to stop processing. But if
+ * it's not NUL, then we simply want to copy it.
+ */
+ if (*srcptr) {
+ *dstptr++ = *srcptr++;
+ }
+ continue;
+ }
+ if (quot == 0 && (*srcptr == '\'' || *srcptr == '"')) {
+ quot = *srcptr++;
+ continue;
+ }
+ if (quot && *srcptr == quot) {
+ /* End of the quoted part */
+ quot = 0;
+ srcptr++;
+ continue;
+ }
+ if (!quot && strchr(delim, *srcptr))
+ break;
+ *dstptr++ = *srcptr++;
+ }
+
+ *dstptr = 0; /* Terminate the string */
+ *stringp = (*srcptr == '\0') ? NULL : srcptr + 1;
+ return (retval);
+}
+
+/*
+ * Mountd server for NFS mount protocol as described in:
+ * NFS: Network File System Protocol Specification, RFC1094, Appendix A
+ * The optional arguments are the exports file name
+ * default: _PATH_EXPORTS
+ * and "-n" to allow nonroot mount.
+ */
+int
+main(int argc, char **argv)
+{
+ fd_set readfds;
+ struct netconfig *nconf;
+ char *endptr, **hosts_bak;
+ void *nc_handle;
+ pid_t otherpid;
+ in_port_t svcport;
+ int c, k, s;
+ int maxrec = RPC_MAXDATASIZE;
+ int attempt_cnt, port_len, port_pos, ret;
+ char **port_list;
+
+ /* Check that another mountd isn't already running. */
+ pfh = pidfile_open(_PATH_MOUNTDPID, 0600, &otherpid);
+ if (pfh == NULL) {
+ if (errno == EEXIST)
+ errx(1, "mountd already running, pid: %d.", otherpid);
+ warn("cannot open or create pidfile");
+ }
+
+ s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ if (s < 0)
+ have_v6 = 0;
+ else
+ close(s);
+
+ while ((c = getopt(argc, argv, "2deh:lnp:rS")) != -1)
+ switch (c) {
+ case '2':
+ force_v2 = 1;
+ break;
+ case 'e':
+ /* now a no-op, since this is the default */
+ break;
+ case 'n':
+ resvport_only = 0;
+ break;
+ case 'r':
+ dir_only = 0;
+ break;
+ case 'd':
+ debug = debug ? 0 : 1;
+ break;
+ case 'l':
+ dolog = 1;
+ break;
+ case 'p':
+ endptr = NULL;
+ svcport = (in_port_t)strtoul(optarg, &endptr, 10);
+ if (endptr == NULL || *endptr != '\0' ||
+ svcport == 0 || svcport >= IPPORT_MAX)
+ usage();
+ svcport_str = strdup(optarg);
+ break;
+ case 'h':
+ ++nhosts;
+ hosts_bak = hosts;
+ hosts_bak = realloc(hosts, nhosts * sizeof(char *));
+ if (hosts_bak == NULL) {
+ if (hosts != NULL) {
+ for (k = 0; k < nhosts; k++)
+ free(hosts[k]);
+ free(hosts);
+ out_of_mem();
+ }
+ }
+ hosts = hosts_bak;
+ hosts[nhosts - 1] = strdup(optarg);
+ if (hosts[nhosts - 1] == NULL) {
+ for (k = 0; k < (nhosts - 1); k++)
+ free(hosts[k]);
+ free(hosts);
+ out_of_mem();
+ }
+ break;
+ case 'S':
+ suspend_nfsd = 1;
+ break;
+ default:
+ usage();
+ };
+
+ if (modfind("nfsd") < 0) {
+ /* Not present in kernel, try loading it */
+ if (kldload("nfsd") < 0 || modfind("nfsd") < 0)
+ errx(1, "NFS server is not available");
+ }
+
+ argc -= optind;
+ argv += optind;
+ grphead = (struct grouplist *)NULL;
+ exphead = (struct exportlist *)NULL;
+ mlhead = (struct mountlist *)NULL;
+ if (argc > 0)
+ exnames = argv;
+ else
+ exnames = exnames_default;
+ openlog("mountd", LOG_PID, LOG_DAEMON);
+ if (debug)
+ warnx("getting export list");
+ get_exportlist();
+ if (debug)
+ warnx("getting mount list");
+ get_mountlist();
+ if (debug)
+ warnx("here we go");
+ if (debug == 0) {
+ daemon(0, 0);
+ signal(SIGINT, SIG_IGN);
+ signal(SIGQUIT, SIG_IGN);
+ }
+ signal(SIGHUP, huphandler);
+ signal(SIGTERM, terminate);
+ signal(SIGPIPE, SIG_IGN);
+
+ pidfile_write(pfh);
+
+ rpcb_unset(MOUNTPROG, MOUNTVERS, NULL);
+ rpcb_unset(MOUNTPROG, MOUNTVERS3, NULL);
+ rpc_control(RPC_SVC_CONNMAXREC_SET, &maxrec);
+
+ if (!resvport_only) {
+ if (sysctlbyname("vfs.nfsrv.nfs_privport", NULL, NULL,
+ &resvport_only, sizeof(resvport_only)) != 0 &&
+ errno != ENOENT) {
+ syslog(LOG_ERR, "sysctl: %m");
+ exit(1);
+ }
+ }
+
+ /*
+ * If no hosts were specified, add a wildcard entry to bind to
+ * INADDR_ANY. Otherwise make sure 127.0.0.1 and ::1 are added to the
+ * list.
+ */
+ if (nhosts == 0) {
+ hosts = malloc(sizeof(char *));
+ if (hosts == NULL)
+ out_of_mem();
+ hosts[0] = "*";
+ nhosts = 1;
+ } else {
+ hosts_bak = hosts;
+ if (have_v6) {
+ hosts_bak = realloc(hosts, (nhosts + 2) *
+ sizeof(char *));
+ if (hosts_bak == NULL) {
+ for (k = 0; k < nhosts; k++)
+ free(hosts[k]);
+ free(hosts);
+ out_of_mem();
+ } else
+ hosts = hosts_bak;
+ nhosts += 2;
+ hosts[nhosts - 2] = "::1";
+ } else {
+ hosts_bak = realloc(hosts, (nhosts + 1) * sizeof(char *));
+ if (hosts_bak == NULL) {
+ for (k = 0; k < nhosts; k++)
+ free(hosts[k]);
+ free(hosts);
+ out_of_mem();
+ } else {
+ nhosts += 1;
+ hosts = hosts_bak;
+ }
+ }
+
+ hosts[nhosts - 1] = "127.0.0.1";
+ }
+
+ attempt_cnt = 1;
+ sock_fdcnt = 0;
+ sock_fd = NULL;
+ port_list = NULL;
+ port_len = 0;
+ nc_handle = setnetconfig();
+ while ((nconf = getnetconfig(nc_handle))) {
+ if (nconf->nc_flag & NC_VISIBLE) {
+ if (have_v6 == 0 && strcmp(nconf->nc_protofmly,
+ "inet6") == 0) {
+ /* DO NOTHING */
+ } else {
+ ret = create_service(nconf);
+ if (ret == 1)
+ /* Ignore this call */
+ continue;
+ if (ret < 0) {
+ /*
+ * Failed to bind port, so close off
+ * all sockets created and try again
+ * if the port# was dynamically
+ * assigned via bind(2).
+ */
+ clearout_service();
+ if (mallocd_svcport != 0 &&
+ attempt_cnt < GETPORT_MAXTRY) {
+ free(svcport_str);
+ svcport_str = NULL;
+ mallocd_svcport = 0;
+ } else {
+ errno = EADDRINUSE;
+ syslog(LOG_ERR,
+ "bindresvport_sa: %m");
+ exit(1);
+ }
+
+ /* Start over at the first service. */
+ free(sock_fd);
+ sock_fdcnt = 0;
+ sock_fd = NULL;
+ nc_handle = setnetconfig();
+ attempt_cnt++;
+ } else if (mallocd_svcport != 0 &&
+ attempt_cnt == GETPORT_MAXTRY) {
+ /*
+ * For the last attempt, allow
+ * different port #s for each nconf
+ * by saving the svcport_str and
+ * setting it back to NULL.
+ */
+ port_list = realloc(port_list,
+ (port_len + 1) * sizeof(char *));
+ if (port_list == NULL)
+ out_of_mem();
+ port_list[port_len++] = svcport_str;
+ svcport_str = NULL;
+ mallocd_svcport = 0;
+ }
+ }
+ }
+ }
+
+ /*
+ * Successfully bound the ports, so call complete_service() to
+ * do the rest of the setup on the service(s).
+ */
+ sock_fdpos = 0;
+ port_pos = 0;
+ nc_handle = setnetconfig();
+ while ((nconf = getnetconfig(nc_handle))) {
+ if (nconf->nc_flag & NC_VISIBLE) {
+ if (have_v6 == 0 && strcmp(nconf->nc_protofmly,
+ "inet6") == 0) {
+ /* DO NOTHING */
+ } else if (port_list != NULL) {
+ if (port_pos >= port_len) {
+ syslog(LOG_ERR, "too many port#s");
+ exit(1);
+ }
+ complete_service(nconf, port_list[port_pos++]);
+ } else
+ complete_service(nconf, svcport_str);
+ }
+ }
+ endnetconfig(nc_handle);
+ free(sock_fd);
+ if (port_list != NULL) {
+ for (port_pos = 0; port_pos < port_len; port_pos++)
+ free(port_list[port_pos]);
+ free(port_list);
+ }
+
+ if (xcreated == 0) {
+ syslog(LOG_ERR, "could not create any services");
+ exit(1);
+ }
+
+ /* Expand svc_run() here so that we can call get_exportlist(). */
+ for (;;) {
+ if (got_sighup) {
+ get_exportlist();
+ got_sighup = 0;
+ }
+ readfds = svc_fdset;
+ switch (select(svc_maxfd + 1, &readfds, NULL, NULL, NULL)) {
+ case -1:
+ if (errno == EINTR)
+ continue;
+ syslog(LOG_ERR, "mountd died: select: %m");
+ exit(1);
+ case 0:
+ continue;
+ default:
+ svc_getreqset(&readfds);
+ }
+ }
+}
+
+/*
+ * This routine creates and binds sockets on the appropriate
+ * addresses. It gets called one time for each transport.
+ * It returns 0 upon success, 1 for ingore the call and -1 to indicate
+ * bind failed with EADDRINUSE.
+ * Any file descriptors that have been created are stored in sock_fd and
+ * the total count of them is maintained in sock_fdcnt.
+ */
+static int
+create_service(struct netconfig *nconf)
+{
+ struct addrinfo hints, *res = NULL;
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ struct __rpc_sockinfo si;
+ int aicode;
+ int fd;
+ int nhostsbak;
+ int one = 1;
+ int r;
+ u_int32_t host_addr[4]; /* IPv4 or IPv6 */
+ int mallocd_res;
+
+ if ((nconf->nc_semantics != NC_TPI_CLTS) &&
+ (nconf->nc_semantics != NC_TPI_COTS) &&
+ (nconf->nc_semantics != NC_TPI_COTS_ORD))
+ return (1); /* not my type */
+
+ /*
+ * XXX - using RPC library internal functions.
+ */
+ if (!__rpc_nconf2sockinfo(nconf, &si)) {
+ syslog(LOG_ERR, "cannot get information for %s",
+ nconf->nc_netid);
+ return (1);
+ }
+
+ /* Get mountd's address on this transport */
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = si.si_af;
+ hints.ai_socktype = si.si_socktype;
+ hints.ai_protocol = si.si_proto;
+
+ /*
+ * Bind to specific IPs if asked to
+ */
+ nhostsbak = nhosts;
+ while (nhostsbak > 0) {
+ --nhostsbak;
+ sock_fd = realloc(sock_fd, (sock_fdcnt + 1) * sizeof(int));
+ if (sock_fd == NULL)
+ out_of_mem();
+ sock_fd[sock_fdcnt++] = -1; /* Set invalid for now. */
+ mallocd_res = 0;
+
+ hints.ai_flags = AI_PASSIVE;
+
+ /*
+ * XXX - using RPC library internal functions.
+ */
+ if ((fd = __rpc_nconf2fd(nconf)) < 0) {
+ int non_fatal = 0;
+ if (errno == EAFNOSUPPORT &&
+ nconf->nc_semantics != NC_TPI_CLTS)
+ non_fatal = 1;
+
+ syslog(non_fatal ? LOG_DEBUG : LOG_ERR,
+ "cannot create socket for %s", nconf->nc_netid);
+ if (non_fatal != 0)
+ continue;
+ exit(1);
+ }
+
+ switch (hints.ai_family) {
+ case AF_INET:
+ if (inet_pton(AF_INET, hosts[nhostsbak],
+ host_addr) == 1) {
+ hints.ai_flags |= AI_NUMERICHOST;
+ } else {
+ /*
+ * Skip if we have an AF_INET6 address.
+ */
+ if (inet_pton(AF_INET6, hosts[nhostsbak],
+ host_addr) == 1) {
+ close(fd);
+ continue;
+ }
+ }
+ break;
+ case AF_INET6:
+ if (inet_pton(AF_INET6, hosts[nhostsbak],
+ host_addr) == 1) {
+ hints.ai_flags |= AI_NUMERICHOST;
+ } else {
+ /*
+ * Skip if we have an AF_INET address.
+ */
+ if (inet_pton(AF_INET, hosts[nhostsbak],
+ host_addr) == 1) {
+ close(fd);
+ continue;
+ }
+ }
+
+ /*
+ * We're doing host-based access checks here, so don't
+ * allow v4-in-v6 to confuse things. The kernel will
+ * disable it by default on NFS sockets too.
+ */
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one,
+ sizeof one) < 0) {
+ syslog(LOG_ERR,
+ "can't disable v4-in-v6 on IPv6 socket");
+ exit(1);
+ }
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * If no hosts were specified, just bind to INADDR_ANY
+ */
+ if (strcmp("*", hosts[nhostsbak]) == 0) {
+ if (svcport_str == NULL) {
+ res = malloc(sizeof(struct addrinfo));
+ if (res == NULL)
+ out_of_mem();
+ mallocd_res = 1;
+ res->ai_flags = hints.ai_flags;
+ res->ai_family = hints.ai_family;
+ res->ai_protocol = hints.ai_protocol;
+ switch (res->ai_family) {
+ case AF_INET:
+ sin = malloc(sizeof(struct sockaddr_in));
+ if (sin == NULL)
+ out_of_mem();
+ sin->sin_family = AF_INET;
+ sin->sin_port = htons(0);
+ sin->sin_addr.s_addr = htonl(INADDR_ANY);
+ res->ai_addr = (struct sockaddr*) sin;
+ res->ai_addrlen = (socklen_t)
+ sizeof(struct sockaddr_in);
+ break;
+ case AF_INET6:
+ sin6 = malloc(sizeof(struct sockaddr_in6));
+ if (sin6 == NULL)
+ out_of_mem();
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_port = htons(0);
+ sin6->sin6_addr = in6addr_any;
+ res->ai_addr = (struct sockaddr*) sin6;
+ res->ai_addrlen = (socklen_t)
+ sizeof(struct sockaddr_in6);
+ break;
+ default:
+ syslog(LOG_ERR, "bad addr fam %d",
+ res->ai_family);
+ exit(1);
+ }
+ } else {
+ if ((aicode = getaddrinfo(NULL, svcport_str,
+ &hints, &res)) != 0) {
+ syslog(LOG_ERR,
+ "cannot get local address for %s: %s",
+ nconf->nc_netid,
+ gai_strerror(aicode));
+ close(fd);
+ continue;
+ }
+ }
+ } else {
+ if ((aicode = getaddrinfo(hosts[nhostsbak], svcport_str,
+ &hints, &res)) != 0) {
+ syslog(LOG_ERR,
+ "cannot get local address for %s: %s",
+ nconf->nc_netid, gai_strerror(aicode));
+ close(fd);
+ continue;
+ }
+ }
+
+ /* Store the fd. */
+ sock_fd[sock_fdcnt - 1] = fd;
+
+ /* Now, attempt the bind. */
+ r = bindresvport_sa(fd, res->ai_addr);
+ if (r != 0) {
+ if (errno == EADDRINUSE && mallocd_svcport != 0) {
+ if (mallocd_res != 0) {
+ free(res->ai_addr);
+ free(res);
+ } else
+ freeaddrinfo(res);
+ return (-1);
+ }
+ syslog(LOG_ERR, "bindresvport_sa: %m");
+ exit(1);
+ }
+
+ if (svcport_str == NULL) {
+ svcport_str = malloc(NI_MAXSERV * sizeof(char));
+ if (svcport_str == NULL)
+ out_of_mem();
+ mallocd_svcport = 1;
+
+ if (getnameinfo(res->ai_addr,
+ res->ai_addr->sa_len, NULL, NI_MAXHOST,
+ svcport_str, NI_MAXSERV * sizeof(char),
+ NI_NUMERICHOST | NI_NUMERICSERV))
+ errx(1, "Cannot get port number");
+ }
+ if (mallocd_res != 0) {
+ free(res->ai_addr);
+ free(res);
+ } else
+ freeaddrinfo(res);
+ res = NULL;
+ }
+ return (0);
+}
+
+/*
+ * Called after all the create_service() calls have succeeded, to complete
+ * the setup and registration.
+ */
+static void
+complete_service(struct netconfig *nconf, char *port_str)
+{
+ struct addrinfo hints, *res = NULL;
+ struct __rpc_sockinfo si;
+ struct netbuf servaddr;
+ SVCXPRT *transp = NULL;
+ int aicode, fd, nhostsbak;
+ int registered = 0;
+
+ if ((nconf->nc_semantics != NC_TPI_CLTS) &&
+ (nconf->nc_semantics != NC_TPI_COTS) &&
+ (nconf->nc_semantics != NC_TPI_COTS_ORD))
+ return; /* not my type */
+
+ /*
+ * XXX - using RPC library internal functions.
+ */
+ if (!__rpc_nconf2sockinfo(nconf, &si)) {
+ syslog(LOG_ERR, "cannot get information for %s",
+ nconf->nc_netid);
+ return;
+ }
+
+ nhostsbak = nhosts;
+ while (nhostsbak > 0) {
+ --nhostsbak;
+ if (sock_fdpos >= sock_fdcnt) {
+ /* Should never happen. */
+ syslog(LOG_ERR, "Ran out of socket fd's");
+ return;
+ }
+ fd = sock_fd[sock_fdpos++];
+ if (fd < 0)
+ continue;
+
+ if (nconf->nc_semantics != NC_TPI_CLTS)
+ listen(fd, SOMAXCONN);
+
+ if (nconf->nc_semantics == NC_TPI_CLTS )
+ transp = svc_dg_create(fd, 0, 0);
+ else
+ transp = svc_vc_create(fd, RPC_MAXDATASIZE,
+ RPC_MAXDATASIZE);
+
+ if (transp != (SVCXPRT *) NULL) {
+ if (!svc_reg(transp, MOUNTPROG, MOUNTVERS, mntsrv,
+ NULL))
+ syslog(LOG_ERR,
+ "can't register %s MOUNTVERS service",
+ nconf->nc_netid);
+ if (!force_v2) {
+ if (!svc_reg(transp, MOUNTPROG, MOUNTVERS3,
+ mntsrv, NULL))
+ syslog(LOG_ERR,
+ "can't register %s MOUNTVERS3 service",
+ nconf->nc_netid);
+ }
+ } else
+ syslog(LOG_WARNING, "can't create %s services",
+ nconf->nc_netid);
+
+ if (registered == 0) {
+ registered = 1;
+ memset(&hints, 0, sizeof hints);
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = si.si_af;
+ hints.ai_socktype = si.si_socktype;
+ hints.ai_protocol = si.si_proto;
+
+ if ((aicode = getaddrinfo(NULL, port_str, &hints,
+ &res)) != 0) {
+ syslog(LOG_ERR, "cannot get local address: %s",
+ gai_strerror(aicode));
+ exit(1);
+ }
+
+ servaddr.buf = malloc(res->ai_addrlen);
+ memcpy(servaddr.buf, res->ai_addr, res->ai_addrlen);
+ servaddr.len = res->ai_addrlen;
+
+ rpcb_set(MOUNTPROG, MOUNTVERS, nconf, &servaddr);
+ rpcb_set(MOUNTPROG, MOUNTVERS3, nconf, &servaddr);
+
+ xcreated++;
+ freeaddrinfo(res);
+ }
+ } /* end while */
+}
+
+/*
+ * Clear out sockets after a failure to bind one of them, so that the
+ * cycle of socket creation/binding can start anew.
+ */
+static void
+clearout_service(void)
+{
+ int i;
+
+ for (i = 0; i < sock_fdcnt; i++) {
+ if (sock_fd[i] >= 0) {
+ shutdown(sock_fd[i], SHUT_RDWR);
+ close(sock_fd[i]);
+ }
+ }
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: mountd [-2] [-d] [-e] [-l] [-n] [-p <port>] [-r] "
+ "[-S] [-h <bindip>] [export_file ...]\n");
+ exit(1);
+}
+
+/*
+ * The mount rpc service
+ */
+void
+mntsrv(struct svc_req *rqstp, SVCXPRT *transp)
+{
+ struct exportlist *ep;
+ struct dirlist *dp;
+ struct fhreturn fhr;
+ struct stat stb;
+ struct statfs fsb;
+ char host[NI_MAXHOST], numerichost[NI_MAXHOST];
+ int lookup_failed = 1;
+ struct sockaddr *saddr;
+ u_short sport;
+ char rpcpath[MNTPATHLEN + 1], dirpath[MAXPATHLEN];
+ int bad = 0, defset, hostset;
+ sigset_t sighup_mask;
+ int numsecflavors, *secflavorsp;
+
+ sigemptyset(&sighup_mask);
+ sigaddset(&sighup_mask, SIGHUP);
+ saddr = svc_getrpccaller(transp)->buf;
+ switch (saddr->sa_family) {
+ case AF_INET6:
+ sport = ntohs(((struct sockaddr_in6 *)saddr)->sin6_port);
+ break;
+ case AF_INET:
+ sport = ntohs(((struct sockaddr_in *)saddr)->sin_port);
+ break;
+ default:
+ syslog(LOG_ERR, "request from unknown address family");
+ return;
+ }
+ lookup_failed = getnameinfo(saddr, saddr->sa_len, host, sizeof host,
+ NULL, 0, 0);
+ getnameinfo(saddr, saddr->sa_len, numerichost,
+ sizeof numerichost, NULL, 0, NI_NUMERICHOST);
+ switch (rqstp->rq_proc) {
+ case NULLPROC:
+ if (!svc_sendreply(transp, (xdrproc_t)xdr_void, NULL))
+ syslog(LOG_ERR, "can't send reply");
+ return;
+ case MOUNTPROC_MNT:
+ if (sport >= IPPORT_RESERVED && resvport_only) {
+ syslog(LOG_NOTICE,
+ "mount request from %s from unprivileged port",
+ numerichost);
+ svcerr_weakauth(transp);
+ return;
+ }
+ if (!svc_getargs(transp, (xdrproc_t)xdr_dir, rpcpath)) {
+ syslog(LOG_NOTICE, "undecodable mount request from %s",
+ numerichost);
+ svcerr_decode(transp);
+ return;
+ }
+
+ /*
+ * Get the real pathname and make sure it is a directory
+ * or a regular file if the -r option was specified
+ * and it exists.
+ */
+ if (realpath(rpcpath, dirpath) == NULL ||
+ stat(dirpath, &stb) < 0 ||
+ (!S_ISDIR(stb.st_mode) &&
+ (dir_only || !S_ISREG(stb.st_mode))) ||
+ statfs(dirpath, &fsb) < 0) {
+ chdir("/"); /* Just in case realpath doesn't */
+ syslog(LOG_NOTICE,
+ "mount request from %s for non existent path %s",
+ numerichost, dirpath);
+ if (debug)
+ warnx("stat failed on %s", dirpath);
+ bad = ENOENT; /* We will send error reply later */
+ }
+
+ /* Check in the exports list */
+ sigprocmask(SIG_BLOCK, &sighup_mask, NULL);
+ ep = ex_search(&fsb.f_fsid);
+ hostset = defset = 0;
+ if (ep && (chk_host(ep->ex_defdir, saddr, &defset, &hostset,
+ &numsecflavors, &secflavorsp) ||
+ ((dp = dirp_search(ep->ex_dirl, dirpath)) &&
+ chk_host(dp, saddr, &defset, &hostset, &numsecflavors,
+ &secflavorsp)) ||
+ (defset && scan_tree(ep->ex_defdir, saddr) == 0 &&
+ scan_tree(ep->ex_dirl, saddr) == 0))) {
+ if (bad) {
+ if (!svc_sendreply(transp, (xdrproc_t)xdr_long,
+ (caddr_t)&bad))
+ syslog(LOG_ERR, "can't send reply");
+ sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL);
+ return;
+ }
+ if (hostset & DP_HOSTSET) {
+ fhr.fhr_flag = hostset;
+ fhr.fhr_numsecflavors = numsecflavors;
+ fhr.fhr_secflavors = secflavorsp;
+ } else {
+ fhr.fhr_flag = defset;
+ fhr.fhr_numsecflavors = ep->ex_defnumsecflavors;
+ fhr.fhr_secflavors = ep->ex_defsecflavors;
+ }
+ fhr.fhr_vers = rqstp->rq_vers;
+ /* Get the file handle */
+ memset(&fhr.fhr_fh, 0, sizeof(nfsfh_t));
+ if (getfh(dirpath, (fhandle_t *)&fhr.fhr_fh) < 0) {
+ bad = errno;
+ syslog(LOG_ERR, "can't get fh for %s", dirpath);
+ if (!svc_sendreply(transp, (xdrproc_t)xdr_long,
+ (caddr_t)&bad))
+ syslog(LOG_ERR, "can't send reply");
+ sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL);
+ return;
+ }
+ if (!svc_sendreply(transp, (xdrproc_t)xdr_fhs,
+ (caddr_t)&fhr))
+ syslog(LOG_ERR, "can't send reply");
+ if (!lookup_failed)
+ add_mlist(host, dirpath);
+ else
+ add_mlist(numerichost, dirpath);
+ if (debug)
+ warnx("mount successful");
+ if (dolog)
+ syslog(LOG_NOTICE,
+ "mount request succeeded from %s for %s",
+ numerichost, dirpath);
+ } else {
+ bad = EACCES;
+ syslog(LOG_NOTICE,
+ "mount request denied from %s for %s",
+ numerichost, dirpath);
+ }
+
+ if (bad && !svc_sendreply(transp, (xdrproc_t)xdr_long,
+ (caddr_t)&bad))
+ syslog(LOG_ERR, "can't send reply");
+ sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL);
+ return;
+ case MOUNTPROC_DUMP:
+ if (!svc_sendreply(transp, (xdrproc_t)xdr_mlist, (caddr_t)NULL))
+ syslog(LOG_ERR, "can't send reply");
+ else if (dolog)
+ syslog(LOG_NOTICE,
+ "dump request succeeded from %s",
+ numerichost);
+ return;
+ case MOUNTPROC_UMNT:
+ if (sport >= IPPORT_RESERVED && resvport_only) {
+ syslog(LOG_NOTICE,
+ "umount request from %s from unprivileged port",
+ numerichost);
+ svcerr_weakauth(transp);
+ return;
+ }
+ if (!svc_getargs(transp, (xdrproc_t)xdr_dir, rpcpath)) {
+ syslog(LOG_NOTICE, "undecodable umount request from %s",
+ numerichost);
+ svcerr_decode(transp);
+ return;
+ }
+ if (realpath(rpcpath, dirpath) == NULL) {
+ syslog(LOG_NOTICE, "umount request from %s "
+ "for non existent path %s",
+ numerichost, dirpath);
+ }
+ if (!svc_sendreply(transp, (xdrproc_t)xdr_void, (caddr_t)NULL))
+ syslog(LOG_ERR, "can't send reply");
+ if (!lookup_failed)
+ del_mlist(host, dirpath);
+ del_mlist(numerichost, dirpath);
+ if (dolog)
+ syslog(LOG_NOTICE,
+ "umount request succeeded from %s for %s",
+ numerichost, dirpath);
+ return;
+ case MOUNTPROC_UMNTALL:
+ if (sport >= IPPORT_RESERVED && resvport_only) {
+ syslog(LOG_NOTICE,
+ "umountall request from %s from unprivileged port",
+ numerichost);
+ svcerr_weakauth(transp);
+ return;
+ }
+ if (!svc_sendreply(transp, (xdrproc_t)xdr_void, (caddr_t)NULL))
+ syslog(LOG_ERR, "can't send reply");
+ if (!lookup_failed)
+ del_mlist(host, NULL);
+ del_mlist(numerichost, NULL);
+ if (dolog)
+ syslog(LOG_NOTICE,
+ "umountall request succeeded from %s",
+ numerichost);
+ return;
+ case MOUNTPROC_EXPORT:
+ if (!svc_sendreply(transp, (xdrproc_t)xdr_explist, (caddr_t)NULL))
+ if (!svc_sendreply(transp, (xdrproc_t)xdr_explist_brief,
+ (caddr_t)NULL))
+ syslog(LOG_ERR, "can't send reply");
+ if (dolog)
+ syslog(LOG_NOTICE,
+ "export request succeeded from %s",
+ numerichost);
+ return;
+ default:
+ svcerr_noproc(transp);
+ return;
+ }
+}
+
+/*
+ * Xdr conversion for a dirpath string
+ */
+static int
+xdr_dir(XDR *xdrsp, char *dirp)
+{
+ return (xdr_string(xdrsp, &dirp, MNTPATHLEN));
+}
+
+/*
+ * Xdr routine to generate file handle reply
+ */
+static int
+xdr_fhs(XDR *xdrsp, caddr_t cp)
+{
+ struct fhreturn *fhrp = (struct fhreturn *)cp;
+ u_long ok = 0, len, auth;
+ int i;
+
+ if (!xdr_long(xdrsp, &ok))
+ return (0);
+ switch (fhrp->fhr_vers) {
+ case 1:
+ return (xdr_opaque(xdrsp, (caddr_t)&fhrp->fhr_fh, NFSX_V2FH));
+ case 3:
+ len = NFSX_V3FH;
+ if (!xdr_long(xdrsp, &len))
+ return (0);
+ if (!xdr_opaque(xdrsp, (caddr_t)&fhrp->fhr_fh, len))
+ return (0);
+ if (fhrp->fhr_numsecflavors) {
+ if (!xdr_int(xdrsp, &fhrp->fhr_numsecflavors))
+ return (0);
+ for (i = 0; i < fhrp->fhr_numsecflavors; i++)
+ if (!xdr_int(xdrsp, &fhrp->fhr_secflavors[i]))
+ return (0);
+ return (1);
+ } else {
+ auth = AUTH_SYS;
+ len = 1;
+ if (!xdr_long(xdrsp, &len))
+ return (0);
+ return (xdr_long(xdrsp, &auth));
+ }
+ };
+ return (0);
+}
+
+static int
+xdr_mlist(XDR *xdrsp, caddr_t cp __unused)
+{
+ struct mountlist *mlp;
+ int true = 1;
+ int false = 0;
+ char *strp;
+
+ mlp = mlhead;
+ while (mlp) {
+ if (!xdr_bool(xdrsp, &true))
+ return (0);
+ strp = &mlp->ml_host[0];
+ if (!xdr_string(xdrsp, &strp, MNTNAMLEN))
+ return (0);
+ strp = &mlp->ml_dirp[0];
+ if (!xdr_string(xdrsp, &strp, MNTPATHLEN))
+ return (0);
+ mlp = mlp->ml_next;
+ }
+ if (!xdr_bool(xdrsp, &false))
+ return (0);
+ return (1);
+}
+
+/*
+ * Xdr conversion for export list
+ */
+static int
+xdr_explist_common(XDR *xdrsp, caddr_t cp __unused, int brief)
+{
+ struct exportlist *ep;
+ int false = 0;
+ int putdef;
+ sigset_t sighup_mask;
+
+ sigemptyset(&sighup_mask);
+ sigaddset(&sighup_mask, SIGHUP);
+ sigprocmask(SIG_BLOCK, &sighup_mask, NULL);
+ ep = exphead;
+ while (ep) {
+ putdef = 0;
+ if (put_exlist(ep->ex_dirl, xdrsp, ep->ex_defdir,
+ &putdef, brief))
+ goto errout;
+ if (ep->ex_defdir && putdef == 0 &&
+ put_exlist(ep->ex_defdir, xdrsp, (struct dirlist *)NULL,
+ &putdef, brief))
+ goto errout;
+ ep = ep->ex_next;
+ }
+ sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL);
+ if (!xdr_bool(xdrsp, &false))
+ return (0);
+ return (1);
+errout:
+ sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL);
+ return (0);
+}
+
+/*
+ * Called from xdr_explist() to traverse the tree and export the
+ * directory paths.
+ */
+static int
+put_exlist(struct dirlist *dp, XDR *xdrsp, struct dirlist *adp, int *putdefp,
+ int brief)
+{
+ struct grouplist *grp;
+ struct hostlist *hp;
+ int true = 1;
+ int false = 0;
+ int gotalldir = 0;
+ char *strp;
+
+ if (dp) {
+ if (put_exlist(dp->dp_left, xdrsp, adp, putdefp, brief))
+ return (1);
+ if (!xdr_bool(xdrsp, &true))
+ return (1);
+ strp = dp->dp_dirp;
+ if (!xdr_string(xdrsp, &strp, MNTPATHLEN))
+ return (1);
+ if (adp && !strcmp(dp->dp_dirp, adp->dp_dirp)) {
+ gotalldir = 1;
+ *putdefp = 1;
+ }
+ if (brief) {
+ if (!xdr_bool(xdrsp, &true))
+ return (1);
+ strp = "(...)";
+ if (!xdr_string(xdrsp, &strp, MNTPATHLEN))
+ return (1);
+ } else if ((dp->dp_flag & DP_DEFSET) == 0 &&
+ (gotalldir == 0 || (adp->dp_flag & DP_DEFSET) == 0)) {
+ hp = dp->dp_hosts;
+ while (hp) {
+ grp = hp->ht_grp;
+ if (grp->gr_type == GT_HOST) {
+ if (!xdr_bool(xdrsp, &true))
+ return (1);
+ strp = grp->gr_ptr.gt_addrinfo->ai_canonname;
+ if (!xdr_string(xdrsp, &strp,
+ MNTNAMLEN))
+ return (1);
+ } else if (grp->gr_type == GT_NET) {
+ if (!xdr_bool(xdrsp, &true))
+ return (1);
+ strp = grp->gr_ptr.gt_net.nt_name;
+ if (!xdr_string(xdrsp, &strp,
+ MNTNAMLEN))
+ return (1);
+ }
+ hp = hp->ht_next;
+ if (gotalldir && hp == (struct hostlist *)NULL) {
+ hp = adp->dp_hosts;
+ gotalldir = 0;
+ }
+ }
+ }
+ if (!xdr_bool(xdrsp, &false))
+ return (1);
+ if (put_exlist(dp->dp_right, xdrsp, adp, putdefp, brief))
+ return (1);
+ }
+ return (0);
+}
+
+static int
+xdr_explist(XDR *xdrsp, caddr_t cp)
+{
+
+ return xdr_explist_common(xdrsp, cp, 0);
+}
+
+static int
+xdr_explist_brief(XDR *xdrsp, caddr_t cp)
+{
+
+ return xdr_explist_common(xdrsp, cp, 1);
+}
+
+static char *line;
+static size_t linesize;
+static FILE *exp_file;
+
+/*
+ * Get the export list from one, currently open file
+ */
+static void
+get_exportlist_one(void)
+{
+ struct exportlist *ep, *ep2;
+ struct grouplist *grp, *tgrp;
+ struct exportlist **epp;
+ struct dirlist *dirhead;
+ struct statfs fsb;
+ struct xucred anon;
+ char *cp, *endcp, *dirp, *hst, *usr, *dom, savedc;
+ int len, has_host, exflags, got_nondir, dirplen, netgrp;
+
+ v4root_phase = 0;
+ dirhead = (struct dirlist *)NULL;
+ while (get_line()) {
+ if (debug)
+ warnx("got line %s", line);
+ cp = line;
+ nextfield(&cp, &endcp);
+ if (*cp == '#')
+ goto nextline;
+
+ /*
+ * Set defaults.
+ */
+ has_host = FALSE;
+ anon = def_anon;
+ exflags = MNT_EXPORTED;
+ got_nondir = 0;
+ opt_flags = 0;
+ ep = (struct exportlist *)NULL;
+ dirp = NULL;
+
+ /*
+ * Handle the V4 root dir.
+ */
+ if (*cp == 'V' && *(cp + 1) == '4' && *(cp + 2) == ':') {
+ /*
+ * V4: just indicates that it is the v4 root point,
+ * so skip over that and set v4root_phase.
+ */
+ if (v4root_phase > 0) {
+ syslog(LOG_ERR, "V4:duplicate line, ignored");
+ goto nextline;
+ }
+ v4root_phase = 1;
+ cp += 3;
+ nextfield(&cp, &endcp);
+ }
+
+ /*
+ * Create new exports list entry
+ */
+ len = endcp-cp;
+ tgrp = grp = get_grp();
+ while (len > 0) {
+ if (len > MNTNAMLEN) {
+ getexp_err(ep, tgrp);
+ goto nextline;
+ }
+ if (*cp == '-') {
+ if (ep == (struct exportlist *)NULL) {
+ getexp_err(ep, tgrp);
+ goto nextline;
+ }
+ if (debug)
+ warnx("doing opt %s", cp);
+ got_nondir = 1;
+ if (do_opt(&cp, &endcp, ep, grp, &has_host,
+ &exflags, &anon)) {
+ getexp_err(ep, tgrp);
+ goto nextline;
+ }
+ } else if (*cp == '/') {
+ savedc = *endcp;
+ *endcp = '\0';
+ if (v4root_phase > 1) {
+ if (dirp != NULL) {
+ syslog(LOG_ERR, "Multiple V4 dirs");
+ getexp_err(ep, tgrp);
+ goto nextline;
+ }
+ }
+ if (check_dirpath(cp) &&
+ statfs(cp, &fsb) >= 0) {
+ if ((fsb.f_flags & MNT_AUTOMOUNTED) != 0)
+ syslog(LOG_ERR, "Warning: exporting of "
+ "automounted fs %s not supported", cp);
+ if (got_nondir) {
+ syslog(LOG_ERR, "dirs must be first");
+ getexp_err(ep, tgrp);
+ goto nextline;
+ }
+ if (v4root_phase == 1) {
+ if (dirp != NULL) {
+ syslog(LOG_ERR, "Multiple V4 dirs");
+ getexp_err(ep, tgrp);
+ goto nextline;
+ }
+ if (strlen(v4root_dirpath) == 0) {
+ strlcpy(v4root_dirpath, cp,
+ sizeof (v4root_dirpath));
+ } else if (strcmp(v4root_dirpath, cp)
+ != 0) {
+ syslog(LOG_ERR,
+ "different V4 dirpath %s", cp);
+ getexp_err(ep, tgrp);
+ goto nextline;
+ }
+ dirp = cp;
+ v4root_phase = 2;
+ got_nondir = 1;
+ ep = get_exp();
+ } else {
+ if (ep) {
+ if (ep->ex_fs.val[0] !=
+ fsb.f_fsid.val[0] ||
+ ep->ex_fs.val[1] !=
+ fsb.f_fsid.val[1]) {
+ getexp_err(ep, tgrp);
+ goto nextline;
+ }
+ } else {
+ /*
+ * See if this directory is already
+ * in the list.
+ */
+ ep = ex_search(&fsb.f_fsid);
+ if (ep == (struct exportlist *)NULL) {
+ ep = get_exp();
+ ep->ex_fs = fsb.f_fsid;
+ ep->ex_fsdir = (char *)malloc
+ (strlen(fsb.f_mntonname) + 1);
+ if (ep->ex_fsdir)
+ strcpy(ep->ex_fsdir,
+ fsb.f_mntonname);
+ else
+ out_of_mem();
+ if (debug)
+ warnx(
+ "making new ep fs=0x%x,0x%x",
+ fsb.f_fsid.val[0],
+ fsb.f_fsid.val[1]);
+ } else if (debug)
+ warnx("found ep fs=0x%x,0x%x",
+ fsb.f_fsid.val[0],
+ fsb.f_fsid.val[1]);
+ }
+
+ /*
+ * Add dirpath to export mount point.
+ */
+ dirp = add_expdir(&dirhead, cp, len);
+ dirplen = len;
+ }
+ } else {
+ getexp_err(ep, tgrp);
+ goto nextline;
+ }
+ *endcp = savedc;
+ } else {
+ savedc = *endcp;
+ *endcp = '\0';
+ got_nondir = 1;
+ if (ep == (struct exportlist *)NULL) {
+ getexp_err(ep, tgrp);
+ goto nextline;
+ }
+
+ /*
+ * Get the host or netgroup.
+ */
+ setnetgrent(cp);
+ netgrp = getnetgrent(&hst, &usr, &dom);
+ do {
+ if (has_host) {
+ grp->gr_next = get_grp();
+ grp = grp->gr_next;
+ }
+ if (netgrp) {
+ if (hst == 0) {
+ syslog(LOG_ERR,
+ "null hostname in netgroup %s, skipping", cp);
+ grp->gr_type = GT_IGNORE;
+ } else if (get_host(hst, grp, tgrp)) {
+ syslog(LOG_ERR,
+ "bad host %s in netgroup %s, skipping", hst, cp);
+ grp->gr_type = GT_IGNORE;
+ }
+ } else if (get_host(cp, grp, tgrp)) {
+ syslog(LOG_ERR, "bad host %s, skipping", cp);
+ grp->gr_type = GT_IGNORE;
+ }
+ has_host = TRUE;
+ } while (netgrp && getnetgrent(&hst, &usr, &dom));
+ endnetgrent();
+ *endcp = savedc;
+ }
+ cp = endcp;
+ nextfield(&cp, &endcp);
+ len = endcp - cp;
+ }
+ if (check_options(dirhead)) {
+ getexp_err(ep, tgrp);
+ goto nextline;
+ }
+ if (!has_host) {
+ grp->gr_type = GT_DEFAULT;
+ if (debug)
+ warnx("adding a default entry");
+
+ /*
+ * Don't allow a network export coincide with a list of
+ * host(s) on the same line.
+ */
+ } else if ((opt_flags & OP_NET) && tgrp->gr_next) {
+ syslog(LOG_ERR, "network/host conflict");
+ getexp_err(ep, tgrp);
+ goto nextline;
+
+ /*
+ * If an export list was specified on this line, make sure
+ * that we have at least one valid entry, otherwise skip it.
+ */
+ } else {
+ grp = tgrp;
+ while (grp && grp->gr_type == GT_IGNORE)
+ grp = grp->gr_next;
+ if (! grp) {
+ getexp_err(ep, tgrp);
+ goto nextline;
+ }
+ }
+
+ if (v4root_phase == 1) {
+ syslog(LOG_ERR, "V4:root, no dirp, ignored");
+ getexp_err(ep, tgrp);
+ goto nextline;
+ }
+
+ /*
+ * Loop through hosts, pushing the exports into the kernel.
+ * After loop, tgrp points to the start of the list and
+ * grp points to the last entry in the list.
+ */
+ grp = tgrp;
+ do {
+ if (do_mount(ep, grp, exflags, &anon, dirp, dirplen,
+ &fsb)) {
+ getexp_err(ep, tgrp);
+ goto nextline;
+ }
+ } while (grp->gr_next && (grp = grp->gr_next));
+
+ /*
+ * For V4: don't enter in mount lists.
+ */
+ if (v4root_phase > 0 && v4root_phase <= 2) {
+ /*
+ * Since these structures aren't used by mountd,
+ * free them up now.
+ */
+ if (ep != NULL)
+ free_exp(ep);
+ while (tgrp != NULL) {
+ grp = tgrp;
+ tgrp = tgrp->gr_next;
+ free_grp(grp);
+ }
+ goto nextline;
+ }
+
+ /*
+ * Success. Update the data structures.
+ */
+ if (has_host) {
+ hang_dirp(dirhead, tgrp, ep, opt_flags);
+ grp->gr_next = grphead;
+ grphead = tgrp;
+ } else {
+ hang_dirp(dirhead, (struct grouplist *)NULL, ep,
+ opt_flags);
+ free_grp(grp);
+ }
+ dirhead = (struct dirlist *)NULL;
+ if ((ep->ex_flag & EX_LINKED) == 0) {
+ ep2 = exphead;
+ epp = &exphead;
+
+ /*
+ * Insert in the list in alphabetical order.
+ */
+ while (ep2 && strcmp(ep2->ex_fsdir, ep->ex_fsdir) < 0) {
+ epp = &ep2->ex_next;
+ ep2 = ep2->ex_next;
+ }
+ if (ep2)
+ ep->ex_next = ep2;
+ *epp = ep;
+ ep->ex_flag |= EX_LINKED;
+ }
+nextline:
+ v4root_phase = 0;
+ if (dirhead) {
+ free_dir(dirhead);
+ dirhead = (struct dirlist *)NULL;
+ }
+ }
+}
+
+/*
+ * Get the export list from all specified files
+ */
+static void
+get_exportlist(void)
+{
+ struct exportlist *ep, *ep2;
+ struct grouplist *grp, *tgrp;
+ struct export_args export;
+ struct iovec *iov;
+ struct statfs *fsp, *mntbufp;
+ struct xvfsconf vfc;
+ char errmsg[255];
+ int num, i;
+ int iovlen;
+ int done;
+ struct nfsex_args eargs;
+
+ if (suspend_nfsd != 0)
+ (void)nfssvc(NFSSVC_SUSPENDNFSD, NULL);
+ v4root_dirpath[0] = '\0';
+ bzero(&export, sizeof(export));
+ export.ex_flags = MNT_DELEXPORT;
+ iov = NULL;
+ iovlen = 0;
+ bzero(errmsg, sizeof(errmsg));
+
+ /*
+ * First, get rid of the old list
+ */
+ ep = exphead;
+ while (ep) {
+ ep2 = ep;
+ ep = ep->ex_next;
+ free_exp(ep2);
+ }
+ exphead = (struct exportlist *)NULL;
+
+ grp = grphead;
+ while (grp) {
+ tgrp = grp;
+ grp = grp->gr_next;
+ free_grp(tgrp);
+ }
+ grphead = (struct grouplist *)NULL;
+
+ /*
+ * and the old V4 root dir.
+ */
+ bzero(&eargs, sizeof (eargs));
+ eargs.export.ex_flags = MNT_DELEXPORT;
+ if (nfssvc(NFSSVC_V4ROOTEXPORT, (caddr_t)&eargs) < 0 &&
+ errno != ENOENT)
+ syslog(LOG_ERR, "Can't delete exports for V4:");
+
+ /*
+ * and clear flag that notes if a public fh has been exported.
+ */
+ has_publicfh = 0;
+
+ /*
+ * And delete exports that are in the kernel for all local
+ * filesystems.
+ * XXX: Should know how to handle all local exportable filesystems.
+ */
+ num = getmntinfo(&mntbufp, MNT_NOWAIT);
+
+ if (num > 0) {
+ build_iovec(&iov, &iovlen, "fstype", NULL, 0);
+ build_iovec(&iov, &iovlen, "fspath", NULL, 0);
+ build_iovec(&iov, &iovlen, "from", NULL, 0);
+ build_iovec(&iov, &iovlen, "update", NULL, 0);
+ build_iovec(&iov, &iovlen, "export", &export, sizeof(export));
+ build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg));
+ }
+
+ for (i = 0; i < num; i++) {
+ fsp = &mntbufp[i];
+ if (getvfsbyname(fsp->f_fstypename, &vfc) != 0) {
+ syslog(LOG_ERR, "getvfsbyname() failed for %s",
+ fsp->f_fstypename);
+ continue;
+ }
+
+ /*
+ * We do not need to delete "export" flag from
+ * filesystems that do not have it set.
+ */
+ if (!(fsp->f_flags & MNT_EXPORTED))
+ continue;
+ /*
+ * Do not delete export for network filesystem by
+ * passing "export" arg to nmount().
+ * It only makes sense to do this for local filesystems.
+ */
+ if (vfc.vfc_flags & VFCF_NETWORK)
+ continue;
+
+ iov[1].iov_base = fsp->f_fstypename;
+ iov[1].iov_len = strlen(fsp->f_fstypename) + 1;
+ iov[3].iov_base = fsp->f_mntonname;
+ iov[3].iov_len = strlen(fsp->f_mntonname) + 1;
+ iov[5].iov_base = fsp->f_mntfromname;
+ iov[5].iov_len = strlen(fsp->f_mntfromname) + 1;
+ errmsg[0] = '\0';
+
+ /*
+ * EXDEV is returned when path exists but is not a
+ * mount point. May happens if raced with unmount.
+ */
+ if (nmount(iov, iovlen, fsp->f_flags) < 0 &&
+ errno != ENOENT && errno != ENOTSUP && errno != EXDEV) {
+ syslog(LOG_ERR,
+ "can't delete exports for %s: %m %s",
+ fsp->f_mntonname, errmsg);
+ }
+ }
+
+ if (iov != NULL) {
+ /* Free strings allocated by strdup() in getmntopts.c */
+ free(iov[0].iov_base); /* fstype */
+ free(iov[2].iov_base); /* fspath */
+ free(iov[4].iov_base); /* from */
+ free(iov[6].iov_base); /* update */
+ free(iov[8].iov_base); /* export */
+ free(iov[10].iov_base); /* errmsg */
+
+ /* free iov, allocated by realloc() */
+ free(iov);
+ iovlen = 0;
+ }
+
+ /*
+ * Read in the exports file and build the list, calling
+ * nmount() as we go along to push the export rules into the kernel.
+ */
+ done = 0;
+ for (i = 0; exnames[i] != NULL; i++) {
+ if (debug)
+ warnx("reading exports from %s", exnames[i]);
+ if ((exp_file = fopen(exnames[i], "r")) == NULL) {
+ syslog(LOG_WARNING, "can't open %s", exnames[i]);
+ continue;
+ }
+ get_exportlist_one();
+ fclose(exp_file);
+ done++;
+ }
+ if (done == 0) {
+ syslog(LOG_ERR, "can't open any exports file");
+ exit(2);
+ }
+
+ /*
+ * If there was no public fh, clear any previous one set.
+ */
+ if (has_publicfh == 0)
+ (void) nfssvc(NFSSVC_NOPUBLICFH, NULL);
+
+ /* Resume the nfsd. If they weren't suspended, this is harmless. */
+ (void)nfssvc(NFSSVC_RESUMENFSD, NULL);
+}
+
+/*
+ * Allocate an export list element
+ */
+static struct exportlist *
+get_exp(void)
+{
+ struct exportlist *ep;
+
+ ep = (struct exportlist *)calloc(1, sizeof (struct exportlist));
+ if (ep == (struct exportlist *)NULL)
+ out_of_mem();
+ return (ep);
+}
+
+/*
+ * Allocate a group list element
+ */
+static struct grouplist *
+get_grp(void)
+{
+ struct grouplist *gp;
+
+ gp = (struct grouplist *)calloc(1, sizeof (struct grouplist));
+ if (gp == (struct grouplist *)NULL)
+ out_of_mem();
+ return (gp);
+}
+
+/*
+ * Clean up upon an error in get_exportlist().
+ */
+static void
+getexp_err(struct exportlist *ep, struct grouplist *grp)
+{
+ struct grouplist *tgrp;
+
+ if (!(opt_flags & OP_QUIET))
+ syslog(LOG_ERR, "bad exports list line %s", line);
+ if (ep && (ep->ex_flag & EX_LINKED) == 0)
+ free_exp(ep);
+ while (grp) {
+ tgrp = grp;
+ grp = grp->gr_next;
+ free_grp(tgrp);
+ }
+}
+
+/*
+ * Search the export list for a matching fs.
+ */
+static struct exportlist *
+ex_search(fsid_t *fsid)
+{
+ struct exportlist *ep;
+
+ ep = exphead;
+ while (ep) {
+ if (ep->ex_fs.val[0] == fsid->val[0] &&
+ ep->ex_fs.val[1] == fsid->val[1])
+ return (ep);
+ ep = ep->ex_next;
+ }
+ return (ep);
+}
+
+/*
+ * Add a directory path to the list.
+ */
+static char *
+add_expdir(struct dirlist **dpp, char *cp, int len)
+{
+ struct dirlist *dp;
+
+ dp = (struct dirlist *)malloc(sizeof (struct dirlist) + len);
+ if (dp == (struct dirlist *)NULL)
+ out_of_mem();
+ dp->dp_left = *dpp;
+ dp->dp_right = (struct dirlist *)NULL;
+ dp->dp_flag = 0;
+ dp->dp_hosts = (struct hostlist *)NULL;
+ strcpy(dp->dp_dirp, cp);
+ *dpp = dp;
+ return (dp->dp_dirp);
+}
+
+/*
+ * Hang the dir list element off the dirpath binary tree as required
+ * and update the entry for host.
+ */
+static void
+hang_dirp(struct dirlist *dp, struct grouplist *grp, struct exportlist *ep,
+ int flags)
+{
+ struct hostlist *hp;
+ struct dirlist *dp2;
+
+ if (flags & OP_ALLDIRS) {
+ if (ep->ex_defdir)
+ free((caddr_t)dp);
+ else
+ ep->ex_defdir = dp;
+ if (grp == (struct grouplist *)NULL) {
+ ep->ex_defdir->dp_flag |= DP_DEFSET;
+ /* Save the default security flavors list. */
+ ep->ex_defnumsecflavors = ep->ex_numsecflavors;
+ if (ep->ex_numsecflavors > 0)
+ memcpy(ep->ex_defsecflavors, ep->ex_secflavors,
+ sizeof(ep->ex_secflavors));
+ } else while (grp) {
+ hp = get_ht();
+ hp->ht_grp = grp;
+ hp->ht_next = ep->ex_defdir->dp_hosts;
+ ep->ex_defdir->dp_hosts = hp;
+ /* Save the security flavors list for this host set. */
+ grp->gr_numsecflavors = ep->ex_numsecflavors;
+ if (ep->ex_numsecflavors > 0)
+ memcpy(grp->gr_secflavors, ep->ex_secflavors,
+ sizeof(ep->ex_secflavors));
+ grp = grp->gr_next;
+ }
+ } else {
+
+ /*
+ * Loop through the directories adding them to the tree.
+ */
+ while (dp) {
+ dp2 = dp->dp_left;
+ add_dlist(&ep->ex_dirl, dp, grp, flags, ep);
+ dp = dp2;
+ }
+ }
+}
+
+/*
+ * Traverse the binary tree either updating a node that is already there
+ * for the new directory or adding the new node.
+ */
+static void
+add_dlist(struct dirlist **dpp, struct dirlist *newdp, struct grouplist *grp,
+ int flags, struct exportlist *ep)
+{
+ struct dirlist *dp;
+ struct hostlist *hp;
+ int cmp;
+
+ dp = *dpp;
+ if (dp) {
+ cmp = strcmp(dp->dp_dirp, newdp->dp_dirp);
+ if (cmp > 0) {
+ add_dlist(&dp->dp_left, newdp, grp, flags, ep);
+ return;
+ } else if (cmp < 0) {
+ add_dlist(&dp->dp_right, newdp, grp, flags, ep);
+ return;
+ } else
+ free((caddr_t)newdp);
+ } else {
+ dp = newdp;
+ dp->dp_left = (struct dirlist *)NULL;
+ *dpp = dp;
+ }
+ if (grp) {
+
+ /*
+ * Hang all of the host(s) off of the directory point.
+ */
+ do {
+ hp = get_ht();
+ hp->ht_grp = grp;
+ hp->ht_next = dp->dp_hosts;
+ dp->dp_hosts = hp;
+ /* Save the security flavors list for this host set. */
+ grp->gr_numsecflavors = ep->ex_numsecflavors;
+ if (ep->ex_numsecflavors > 0)
+ memcpy(grp->gr_secflavors, ep->ex_secflavors,
+ sizeof(ep->ex_secflavors));
+ grp = grp->gr_next;
+ } while (grp);
+ } else {
+ dp->dp_flag |= DP_DEFSET;
+ /* Save the default security flavors list. */
+ ep->ex_defnumsecflavors = ep->ex_numsecflavors;
+ if (ep->ex_numsecflavors > 0)
+ memcpy(ep->ex_defsecflavors, ep->ex_secflavors,
+ sizeof(ep->ex_secflavors));
+ }
+}
+
+/*
+ * Search for a dirpath on the export point.
+ */
+static struct dirlist *
+dirp_search(struct dirlist *dp, char *dirp)
+{
+ int cmp;
+
+ if (dp) {
+ cmp = strcmp(dp->dp_dirp, dirp);
+ if (cmp > 0)
+ return (dirp_search(dp->dp_left, dirp));
+ else if (cmp < 0)
+ return (dirp_search(dp->dp_right, dirp));
+ else
+ return (dp);
+ }
+ return (dp);
+}
+
+/*
+ * Scan for a host match in a directory tree.
+ */
+static int
+chk_host(struct dirlist *dp, struct sockaddr *saddr, int *defsetp,
+ int *hostsetp, int *numsecflavors, int **secflavorsp)
+{
+ struct hostlist *hp;
+ struct grouplist *grp;
+ struct addrinfo *ai;
+
+ if (dp) {
+ if (dp->dp_flag & DP_DEFSET)
+ *defsetp = dp->dp_flag;
+ hp = dp->dp_hosts;
+ while (hp) {
+ grp = hp->ht_grp;
+ switch (grp->gr_type) {
+ case GT_HOST:
+ ai = grp->gr_ptr.gt_addrinfo;
+ for (; ai; ai = ai->ai_next) {
+ if (!sacmp(ai->ai_addr, saddr, NULL)) {
+ *hostsetp =
+ (hp->ht_flag | DP_HOSTSET);
+ if (numsecflavors != NULL) {
+ *numsecflavors =
+ grp->gr_numsecflavors;
+ *secflavorsp =
+ grp->gr_secflavors;
+ }
+ return (1);
+ }
+ }
+ break;
+ case GT_NET:
+ if (!sacmp(saddr, (struct sockaddr *)
+ &grp->gr_ptr.gt_net.nt_net,
+ (struct sockaddr *)
+ &grp->gr_ptr.gt_net.nt_mask)) {
+ *hostsetp = (hp->ht_flag | DP_HOSTSET);
+ if (numsecflavors != NULL) {
+ *numsecflavors =
+ grp->gr_numsecflavors;
+ *secflavorsp =
+ grp->gr_secflavors;
+ }
+ return (1);
+ }
+ break;
+ }
+ hp = hp->ht_next;
+ }
+ }
+ return (0);
+}
+
+/*
+ * Scan tree for a host that matches the address.
+ */
+static int
+scan_tree(struct dirlist *dp, struct sockaddr *saddr)
+{
+ int defset, hostset;
+
+ if (dp) {
+ if (scan_tree(dp->dp_left, saddr))
+ return (1);
+ if (chk_host(dp, saddr, &defset, &hostset, NULL, NULL))
+ return (1);
+ if (scan_tree(dp->dp_right, saddr))
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * Traverse the dirlist tree and free it up.
+ */
+static void
+free_dir(struct dirlist *dp)
+{
+
+ if (dp) {
+ free_dir(dp->dp_left);
+ free_dir(dp->dp_right);
+ free_host(dp->dp_hosts);
+ free((caddr_t)dp);
+ }
+}
+
+/*
+ * Parse a colon separated list of security flavors
+ */
+static int
+parsesec(char *seclist, struct exportlist *ep)
+{
+ char *cp, savedc;
+ int flavor;
+
+ ep->ex_numsecflavors = 0;
+ for (;;) {
+ cp = strchr(seclist, ':');
+ if (cp) {
+ savedc = *cp;
+ *cp = '\0';
+ }
+
+ if (!strcmp(seclist, "sys"))
+ flavor = AUTH_SYS;
+ else if (!strcmp(seclist, "krb5"))
+ flavor = RPCSEC_GSS_KRB5;
+ else if (!strcmp(seclist, "krb5i"))
+ flavor = RPCSEC_GSS_KRB5I;
+ else if (!strcmp(seclist, "krb5p"))
+ flavor = RPCSEC_GSS_KRB5P;
+ else {
+ if (cp)
+ *cp = savedc;
+ syslog(LOG_ERR, "bad sec flavor: %s", seclist);
+ return (1);
+ }
+ if (ep->ex_numsecflavors == MAXSECFLAVORS) {
+ if (cp)
+ *cp = savedc;
+ syslog(LOG_ERR, "too many sec flavors: %s", seclist);
+ return (1);
+ }
+ ep->ex_secflavors[ep->ex_numsecflavors] = flavor;
+ ep->ex_numsecflavors++;
+ if (cp) {
+ *cp = savedc;
+ seclist = cp + 1;
+ } else {
+ break;
+ }
+ }
+ return (0);
+}
+
+/*
+ * Parse the option string and update fields.
+ * Option arguments may either be -<option>=<value> or
+ * -<option> <value>
+ */
+static int
+do_opt(char **cpp, char **endcpp, struct exportlist *ep, struct grouplist *grp,
+ int *has_hostp, int *exflagsp, struct xucred *cr)
+{
+ char *cpoptarg, *cpoptend;
+ char *cp, *endcp, *cpopt, savedc, savedc2;
+ int allflag, usedarg;
+
+ savedc2 = '\0';
+ cpopt = *cpp;
+ cpopt++;
+ cp = *endcpp;
+ savedc = *cp;
+ *cp = '\0';
+ while (cpopt && *cpopt) {
+ allflag = 1;
+ usedarg = -2;
+ if ((cpoptend = strchr(cpopt, ','))) {
+ *cpoptend++ = '\0';
+ if ((cpoptarg = strchr(cpopt, '=')))
+ *cpoptarg++ = '\0';
+ } else {
+ if ((cpoptarg = strchr(cpopt, '=')))
+ *cpoptarg++ = '\0';
+ else {
+ *cp = savedc;
+ nextfield(&cp, &endcp);
+ **endcpp = '\0';
+ if (endcp > cp && *cp != '-') {
+ cpoptarg = cp;
+ savedc2 = *endcp;
+ *endcp = '\0';
+ usedarg = 0;
+ }
+ }
+ }
+ if (!strcmp(cpopt, "ro") || !strcmp(cpopt, "o")) {
+ *exflagsp |= MNT_EXRDONLY;
+ } else if (cpoptarg && (!strcmp(cpopt, "maproot") ||
+ !(allflag = strcmp(cpopt, "mapall")) ||
+ !strcmp(cpopt, "root") || !strcmp(cpopt, "r"))) {
+ usedarg++;
+ parsecred(cpoptarg, cr);
+ if (allflag == 0) {
+ *exflagsp |= MNT_EXPORTANON;
+ opt_flags |= OP_MAPALL;
+ } else
+ opt_flags |= OP_MAPROOT;
+ } else if (cpoptarg && (!strcmp(cpopt, "mask") ||
+ !strcmp(cpopt, "m"))) {
+ if (get_net(cpoptarg, &grp->gr_ptr.gt_net, 1)) {
+ syslog(LOG_ERR, "bad mask: %s", cpoptarg);
+ return (1);
+ }
+ usedarg++;
+ opt_flags |= OP_MASK;
+ } else if (cpoptarg && (!strcmp(cpopt, "network") ||
+ !strcmp(cpopt, "n"))) {
+ if (strchr(cpoptarg, '/') != NULL) {
+ if (debug)
+ fprintf(stderr, "setting OP_MASKLEN\n");
+ opt_flags |= OP_MASKLEN;
+ }
+ if (grp->gr_type != GT_NULL) {
+ syslog(LOG_ERR, "network/host conflict");
+ return (1);
+ } else if (get_net(cpoptarg, &grp->gr_ptr.gt_net, 0)) {
+ syslog(LOG_ERR, "bad net: %s", cpoptarg);
+ return (1);
+ }
+ grp->gr_type = GT_NET;
+ *has_hostp = 1;
+ usedarg++;
+ opt_flags |= OP_NET;
+ } else if (!strcmp(cpopt, "alldirs")) {
+ opt_flags |= OP_ALLDIRS;
+ } else if (!strcmp(cpopt, "public")) {
+ *exflagsp |= MNT_EXPUBLIC;
+ } else if (!strcmp(cpopt, "webnfs")) {
+ *exflagsp |= (MNT_EXPUBLIC|MNT_EXRDONLY|MNT_EXPORTANON);
+ opt_flags |= OP_MAPALL;
+ } else if (cpoptarg && !strcmp(cpopt, "index")) {
+ ep->ex_indexfile = strdup(cpoptarg);
+ } else if (!strcmp(cpopt, "quiet")) {
+ opt_flags |= OP_QUIET;
+ } else if (cpoptarg && !strcmp(cpopt, "sec")) {
+ if (parsesec(cpoptarg, ep))
+ return (1);
+ opt_flags |= OP_SEC;
+ usedarg++;
+ } else {
+ syslog(LOG_ERR, "bad opt %s", cpopt);
+ return (1);
+ }
+ if (usedarg >= 0) {
+ *endcp = savedc2;
+ **endcpp = savedc;
+ if (usedarg > 0) {
+ *cpp = cp;
+ *endcpp = endcp;
+ }
+ return (0);
+ }
+ cpopt = cpoptend;
+ }
+ **endcpp = savedc;
+ return (0);
+}
+
+/*
+ * Translate a character string to the corresponding list of network
+ * addresses for a hostname.
+ */
+static int
+get_host(char *cp, struct grouplist *grp, struct grouplist *tgrp)
+{
+ struct grouplist *checkgrp;
+ struct addrinfo *ai, *tai, hints;
+ int ecode;
+ char host[NI_MAXHOST];
+
+ if (grp->gr_type != GT_NULL) {
+ syslog(LOG_ERR, "Bad netgroup type for ip host %s", cp);
+ return (1);
+ }
+ memset(&hints, 0, sizeof hints);
+ hints.ai_flags = AI_CANONNAME;
+ hints.ai_protocol = IPPROTO_UDP;
+ ecode = getaddrinfo(cp, NULL, &hints, &ai);
+ if (ecode != 0) {
+ syslog(LOG_ERR,"can't get address info for host %s", cp);
+ return 1;
+ }
+ grp->gr_ptr.gt_addrinfo = ai;
+ while (ai != NULL) {
+ if (ai->ai_canonname == NULL) {
+ if (getnameinfo(ai->ai_addr, ai->ai_addrlen, host,
+ sizeof host, NULL, 0, NI_NUMERICHOST) != 0)
+ strlcpy(host, "?", sizeof(host));
+ ai->ai_canonname = strdup(host);
+ ai->ai_flags |= AI_CANONNAME;
+ }
+ if (debug)
+ fprintf(stderr, "got host %s\n", ai->ai_canonname);
+ /*
+ * Sanity check: make sure we don't already have an entry
+ * for this host in the grouplist.
+ */
+ for (checkgrp = tgrp; checkgrp != NULL;
+ checkgrp = checkgrp->gr_next) {
+ if (checkgrp->gr_type != GT_HOST)
+ continue;
+ for (tai = checkgrp->gr_ptr.gt_addrinfo; tai != NULL;
+ tai = tai->ai_next) {
+ if (sacmp(tai->ai_addr, ai->ai_addr, NULL) != 0)
+ continue;
+ if (debug)
+ fprintf(stderr,
+ "ignoring duplicate host %s\n",
+ ai->ai_canonname);
+ grp->gr_type = GT_IGNORE;
+ return (0);
+ }
+ }
+ ai = ai->ai_next;
+ }
+ grp->gr_type = GT_HOST;
+ return (0);
+}
+
+/*
+ * Free up an exports list component
+ */
+static void
+free_exp(struct exportlist *ep)
+{
+
+ if (ep->ex_defdir) {
+ free_host(ep->ex_defdir->dp_hosts);
+ free((caddr_t)ep->ex_defdir);
+ }
+ if (ep->ex_fsdir)
+ free(ep->ex_fsdir);
+ if (ep->ex_indexfile)
+ free(ep->ex_indexfile);
+ free_dir(ep->ex_dirl);
+ free((caddr_t)ep);
+}
+
+/*
+ * Free hosts.
+ */
+static void
+free_host(struct hostlist *hp)
+{
+ struct hostlist *hp2;
+
+ while (hp) {
+ hp2 = hp;
+ hp = hp->ht_next;
+ free((caddr_t)hp2);
+ }
+}
+
+static struct hostlist *
+get_ht(void)
+{
+ struct hostlist *hp;
+
+ hp = (struct hostlist *)malloc(sizeof (struct hostlist));
+ if (hp == (struct hostlist *)NULL)
+ out_of_mem();
+ hp->ht_next = (struct hostlist *)NULL;
+ hp->ht_flag = 0;
+ return (hp);
+}
+
+/*
+ * Out of memory, fatal
+ */
+static void
+out_of_mem(void)
+{
+
+ syslog(LOG_ERR, "out of memory");
+ exit(2);
+}
+
+/*
+ * Do the nmount() syscall with the update flag to push the export info into
+ * the kernel.
+ */
+static int
+do_mount(struct exportlist *ep, struct grouplist *grp, int exflags,
+ struct xucred *anoncrp, char *dirp, int dirplen, struct statfs *fsb)
+{
+ struct statfs fsb1;
+ struct addrinfo *ai;
+ struct export_args *eap;
+ char errmsg[255];
+ char *cp;
+ int done;
+ char savedc;
+ struct iovec *iov;
+ int i, iovlen;
+ int ret;
+ struct nfsex_args nfsea;
+
+ eap = &nfsea.export;
+
+ cp = NULL;
+ savedc = '\0';
+ iov = NULL;
+ iovlen = 0;
+ ret = 0;
+
+ bzero(eap, sizeof (struct export_args));
+ bzero(errmsg, sizeof(errmsg));
+ eap->ex_flags = exflags;
+ eap->ex_anon = *anoncrp;
+ eap->ex_indexfile = ep->ex_indexfile;
+ if (grp->gr_type == GT_HOST)
+ ai = grp->gr_ptr.gt_addrinfo;
+ else
+ ai = NULL;
+ eap->ex_numsecflavors = ep->ex_numsecflavors;
+ for (i = 0; i < eap->ex_numsecflavors; i++)
+ eap->ex_secflavors[i] = ep->ex_secflavors[i];
+ if (eap->ex_numsecflavors == 0) {
+ eap->ex_numsecflavors = 1;
+ eap->ex_secflavors[0] = AUTH_SYS;
+ }
+ done = FALSE;
+
+ if (v4root_phase == 0) {
+ build_iovec(&iov, &iovlen, "fstype", NULL, 0);
+ build_iovec(&iov, &iovlen, "fspath", NULL, 0);
+ build_iovec(&iov, &iovlen, "from", NULL, 0);
+ build_iovec(&iov, &iovlen, "update", NULL, 0);
+ build_iovec(&iov, &iovlen, "export", eap,
+ sizeof (struct export_args));
+ build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg));
+ }
+
+ while (!done) {
+ switch (grp->gr_type) {
+ case GT_HOST:
+ if (ai->ai_addr->sa_family == AF_INET6 && have_v6 == 0)
+ goto skip;
+ eap->ex_addr = ai->ai_addr;
+ eap->ex_addrlen = ai->ai_addrlen;
+ eap->ex_masklen = 0;
+ break;
+ case GT_NET:
+ if (grp->gr_ptr.gt_net.nt_net.ss_family == AF_INET6 &&
+ have_v6 == 0)
+ goto skip;
+ eap->ex_addr =
+ (struct sockaddr *)&grp->gr_ptr.gt_net.nt_net;
+ eap->ex_addrlen =
+ ((struct sockaddr *)&grp->gr_ptr.gt_net.nt_net)->sa_len;
+ eap->ex_mask =
+ (struct sockaddr *)&grp->gr_ptr.gt_net.nt_mask;
+ eap->ex_masklen = ((struct sockaddr *)&grp->gr_ptr.gt_net.nt_mask)->sa_len;
+ break;
+ case GT_DEFAULT:
+ eap->ex_addr = NULL;
+ eap->ex_addrlen = 0;
+ eap->ex_mask = NULL;
+ eap->ex_masklen = 0;
+ break;
+ case GT_IGNORE:
+ ret = 0;
+ goto error_exit;
+ break;
+ default:
+ syslog(LOG_ERR, "bad grouptype");
+ if (cp)
+ *cp = savedc;
+ ret = 1;
+ goto error_exit;
+ };
+
+ /*
+ * For V4:, use the nfssvc() syscall, instead of mount().
+ */
+ if (v4root_phase == 2) {
+ nfsea.fspec = v4root_dirpath;
+ if (nfssvc(NFSSVC_V4ROOTEXPORT, (caddr_t)&nfsea) < 0) {
+ syslog(LOG_ERR, "Exporting V4: failed");
+ return (2);
+ }
+ } else {
+ /*
+ * XXX:
+ * Maybe I should just use the fsb->f_mntonname path
+ * instead of looping back up the dirp to the mount
+ * point??
+ * Also, needs to know how to export all types of local
+ * exportable filesystems and not just "ufs".
+ */
+ iov[1].iov_base = fsb->f_fstypename; /* "fstype" */
+ iov[1].iov_len = strlen(fsb->f_fstypename) + 1;
+ iov[3].iov_base = fsb->f_mntonname; /* "fspath" */
+ iov[3].iov_len = strlen(fsb->f_mntonname) + 1;
+ iov[5].iov_base = fsb->f_mntfromname; /* "from" */
+ iov[5].iov_len = strlen(fsb->f_mntfromname) + 1;
+ errmsg[0] = '\0';
+
+ while (nmount(iov, iovlen, fsb->f_flags) < 0) {
+ if (cp)
+ *cp-- = savedc;
+ else
+ cp = dirp + dirplen - 1;
+ if (opt_flags & OP_QUIET) {
+ ret = 1;
+ goto error_exit;
+ }
+ if (errno == EPERM) {
+ if (debug)
+ warnx("can't change attributes for %s: %s",
+ dirp, errmsg);
+ syslog(LOG_ERR,
+ "can't change attributes for %s: %s",
+ dirp, errmsg);
+ ret = 1;
+ goto error_exit;
+ }
+ if (opt_flags & OP_ALLDIRS) {
+ if (errno == EINVAL)
+ syslog(LOG_ERR,
+ "-alldirs requested but %s is not a filesystem mountpoint",
+ dirp);
+ else
+ syslog(LOG_ERR,
+ "could not remount %s: %m",
+ dirp);
+ ret = 1;
+ goto error_exit;
+ }
+ /* back up over the last component */
+ while (*cp == '/' && cp > dirp)
+ cp--;
+ while (*(cp - 1) != '/' && cp > dirp)
+ cp--;
+ if (cp == dirp) {
+ if (debug)
+ warnx("mnt unsucc");
+ syslog(LOG_ERR, "can't export %s %s",
+ dirp, errmsg);
+ ret = 1;
+ goto error_exit;
+ }
+ savedc = *cp;
+ *cp = '\0';
+ /*
+ * Check that we're still on the same
+ * filesystem.
+ */
+ if (statfs(dirp, &fsb1) != 0 ||
+ bcmp(&fsb1.f_fsid, &fsb->f_fsid,
+ sizeof (fsb1.f_fsid)) != 0) {
+ *cp = savedc;
+ syslog(LOG_ERR,
+ "can't export %s %s", dirp,
+ errmsg);
+ ret = 1;
+ goto error_exit;
+ }
+ }
+ }
+
+ /*
+ * For the experimental server:
+ * If this is the public directory, get the file handle
+ * and load it into the kernel via the nfssvc() syscall.
+ */
+ if ((exflags & MNT_EXPUBLIC) != 0) {
+ fhandle_t fh;
+ char *public_name;
+
+ if (eap->ex_indexfile != NULL)
+ public_name = eap->ex_indexfile;
+ else
+ public_name = dirp;
+ if (getfh(public_name, &fh) < 0)
+ syslog(LOG_ERR,
+ "Can't get public fh for %s", public_name);
+ else if (nfssvc(NFSSVC_PUBLICFH, (caddr_t)&fh) < 0)
+ syslog(LOG_ERR,
+ "Can't set public fh for %s", public_name);
+ else
+ has_publicfh = 1;
+ }
+skip:
+ if (ai != NULL)
+ ai = ai->ai_next;
+ if (ai == NULL)
+ done = TRUE;
+ }
+ if (cp)
+ *cp = savedc;
+error_exit:
+ /* free strings allocated by strdup() in getmntopts.c */
+ if (iov != NULL) {
+ free(iov[0].iov_base); /* fstype */
+ free(iov[2].iov_base); /* fspath */
+ free(iov[4].iov_base); /* from */
+ free(iov[6].iov_base); /* update */
+ free(iov[8].iov_base); /* export */
+ free(iov[10].iov_base); /* errmsg */
+
+ /* free iov, allocated by realloc() */
+ free(iov);
+ }
+ return (ret);
+}
+
+/*
+ * Translate a net address.
+ *
+ * If `maskflg' is nonzero, then `cp' is a netmask, not a network address.
+ */
+static int
+get_net(char *cp, struct netmsk *net, int maskflg)
+{
+ struct netent *np = NULL;
+ char *name, *p, *prefp;
+ struct sockaddr_in sin;
+ struct sockaddr *sa = NULL;
+ struct addrinfo hints, *ai = NULL;
+ char netname[NI_MAXHOST];
+ long preflen;
+
+ p = prefp = NULL;
+ if ((opt_flags & OP_MASKLEN) && !maskflg) {
+ p = strchr(cp, '/');
+ *p = '\0';
+ prefp = p + 1;
+ }
+
+ /*
+ * Check for a numeric address first. We wish to avoid
+ * possible DNS lookups in getnetbyname().
+ */
+ if (isxdigit(*cp) || *cp == ':') {
+ memset(&hints, 0, sizeof hints);
+ /* Ensure the mask and the network have the same family. */
+ if (maskflg && (opt_flags & OP_NET))
+ hints.ai_family = net->nt_net.ss_family;
+ else if (!maskflg && (opt_flags & OP_HAVEMASK))
+ hints.ai_family = net->nt_mask.ss_family;
+ else
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_flags = AI_NUMERICHOST;
+ if (getaddrinfo(cp, NULL, &hints, &ai) == 0)
+ sa = ai->ai_addr;
+ if (sa != NULL && ai->ai_family == AF_INET) {
+ /*
+ * The address in `cp' is really a network address, so
+ * use inet_network() to re-interpret this correctly.
+ * e.g. "127.1" means 127.1.0.0, not 127.0.0.1.
+ */
+ bzero(&sin, sizeof sin);
+ sin.sin_family = AF_INET;
+ sin.sin_len = sizeof sin;
+ sin.sin_addr = inet_makeaddr(inet_network(cp), 0);
+ if (debug)
+ fprintf(stderr, "get_net: v4 addr %s\n",
+ inet_ntoa(sin.sin_addr));
+ sa = (struct sockaddr *)&sin;
+ }
+ }
+ if (sa == NULL && (np = getnetbyname(cp)) != NULL) {
+ bzero(&sin, sizeof sin);
+ sin.sin_family = AF_INET;
+ sin.sin_len = sizeof sin;
+ sin.sin_addr = inet_makeaddr(np->n_net, 0);
+ sa = (struct sockaddr *)&sin;
+ }
+ if (sa == NULL)
+ goto fail;
+
+ if (maskflg) {
+ /* The specified sockaddr is a mask. */
+ if (checkmask(sa) != 0)
+ goto fail;
+ bcopy(sa, &net->nt_mask, sa->sa_len);
+ opt_flags |= OP_HAVEMASK;
+ } else {
+ /* The specified sockaddr is a network address. */
+ bcopy(sa, &net->nt_net, sa->sa_len);
+
+ /* Get a network name for the export list. */
+ if (np) {
+ name = np->n_name;
+ } else if (getnameinfo(sa, sa->sa_len, netname, sizeof netname,
+ NULL, 0, NI_NUMERICHOST) == 0) {
+ name = netname;
+ } else {
+ goto fail;
+ }
+ if ((net->nt_name = strdup(name)) == NULL)
+ out_of_mem();
+
+ /*
+ * Extract a mask from either a "/<masklen>" suffix, or
+ * from the class of an IPv4 address.
+ */
+ if (opt_flags & OP_MASKLEN) {
+ preflen = strtol(prefp, NULL, 10);
+ if (preflen < 0L || preflen == LONG_MAX)
+ goto fail;
+ bcopy(sa, &net->nt_mask, sa->sa_len);
+ if (makemask(&net->nt_mask, (int)preflen) != 0)
+ goto fail;
+ opt_flags |= OP_HAVEMASK;
+ *p = '/';
+ } else if (sa->sa_family == AF_INET &&
+ (opt_flags & OP_MASK) == 0) {
+ in_addr_t addr;
+
+ addr = ((struct sockaddr_in *)sa)->sin_addr.s_addr;
+ if (IN_CLASSA(addr))
+ preflen = 8;
+ else if (IN_CLASSB(addr))
+ preflen = 16;
+ else if (IN_CLASSC(addr))
+ preflen = 24;
+ else if (IN_CLASSD(addr))
+ preflen = 28;
+ else
+ preflen = 32; /* XXX */
+
+ bcopy(sa, &net->nt_mask, sa->sa_len);
+ makemask(&net->nt_mask, (int)preflen);
+ opt_flags |= OP_HAVEMASK;
+ }
+ }
+
+ if (ai)
+ freeaddrinfo(ai);
+ return 0;
+
+fail:
+ if (ai)
+ freeaddrinfo(ai);
+ return 1;
+}
+
+/*
+ * Parse out the next white space separated field
+ */
+static void
+nextfield(char **cp, char **endcp)
+{
+ char *p;
+
+ p = *cp;
+ while (*p == ' ' || *p == '\t')
+ p++;
+ if (*p == '\n' || *p == '\0')
+ *cp = *endcp = p;
+ else {
+ *cp = p++;
+ while (*p != ' ' && *p != '\t' && *p != '\n' && *p != '\0')
+ p++;
+ *endcp = p;
+ }
+}
+
+/*
+ * Get an exports file line. Skip over blank lines and handle line
+ * continuations.
+ */
+static int
+get_line(void)
+{
+ char *p, *cp;
+ size_t len;
+ int totlen, cont_line;
+
+ /*
+ * Loop around ignoring blank lines and getting all continuation lines.
+ */
+ p = line;
+ totlen = 0;
+ do {
+ if ((p = fgetln(exp_file, &len)) == NULL)
+ return (0);
+ cp = p + len - 1;
+ cont_line = 0;
+ while (cp >= p &&
+ (*cp == ' ' || *cp == '\t' || *cp == '\n' || *cp == '\\')) {
+ if (*cp == '\\')
+ cont_line = 1;
+ cp--;
+ len--;
+ }
+ if (cont_line) {
+ *++cp = ' ';
+ len++;
+ }
+ if (linesize < len + totlen + 1) {
+ linesize = len + totlen + 1;
+ line = realloc(line, linesize);
+ if (line == NULL)
+ out_of_mem();
+ }
+ memcpy(line + totlen, p, len);
+ totlen += len;
+ line[totlen] = '\0';
+ } while (totlen == 0 || cont_line);
+ return (1);
+}
+
+/*
+ * Parse a description of a credential.
+ */
+static void
+parsecred(char *namelist, struct xucred *cr)
+{
+ char *name;
+ int cnt;
+ char *names;
+ struct passwd *pw;
+ struct group *gr;
+ gid_t groups[XU_NGROUPS + 1];
+ int ngroups;
+
+ cr->cr_version = XUCRED_VERSION;
+ /*
+ * Set up the unprivileged user.
+ */
+ cr->cr_uid = -2;
+ cr->cr_groups[0] = -2;
+ cr->cr_ngroups = 1;
+ /*
+ * Get the user's password table entry.
+ */
+ names = strsep_quote(&namelist, " \t\n");
+ name = strsep(&names, ":");
+ /* Bug? name could be NULL here */
+ if (isdigit(*name) || *name == '-')
+ pw = getpwuid(atoi(name));
+ else
+ pw = getpwnam(name);
+ /*
+ * Credentials specified as those of a user.
+ */
+ if (names == NULL) {
+ if (pw == NULL) {
+ syslog(LOG_ERR, "unknown user: %s", name);
+ return;
+ }
+ cr->cr_uid = pw->pw_uid;
+ ngroups = XU_NGROUPS + 1;
+ if (getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups))
+ syslog(LOG_ERR, "too many groups");
+ /*
+ * Compress out duplicate.
+ */
+ cr->cr_ngroups = ngroups - 1;
+ cr->cr_groups[0] = groups[0];
+ for (cnt = 2; cnt < ngroups; cnt++)
+ cr->cr_groups[cnt - 1] = groups[cnt];
+ return;
+ }
+ /*
+ * Explicit credential specified as a colon separated list:
+ * uid:gid:gid:...
+ */
+ if (pw != NULL)
+ cr->cr_uid = pw->pw_uid;
+ else if (isdigit(*name) || *name == '-')
+ cr->cr_uid = atoi(name);
+ else {
+ syslog(LOG_ERR, "unknown user: %s", name);
+ return;
+ }
+ cr->cr_ngroups = 0;
+ while (names != NULL && *names != '\0' && cr->cr_ngroups < XU_NGROUPS) {
+ name = strsep(&names, ":");
+ if (isdigit(*name) || *name == '-') {
+ cr->cr_groups[cr->cr_ngroups++] = atoi(name);
+ } else {
+ if ((gr = getgrnam(name)) == NULL) {
+ syslog(LOG_ERR, "unknown group: %s", name);
+ continue;
+ }
+ cr->cr_groups[cr->cr_ngroups++] = gr->gr_gid;
+ }
+ }
+ if (names != NULL && *names != '\0' && cr->cr_ngroups == XU_NGROUPS)
+ syslog(LOG_ERR, "too many groups");
+}
+
+#define STRSIZ (MNTNAMLEN+MNTPATHLEN+50)
+/*
+ * Routines that maintain the remote mounttab
+ */
+static void
+get_mountlist(void)
+{
+ struct mountlist *mlp, **mlpp;
+ char *host, *dirp, *cp;
+ char str[STRSIZ];
+ FILE *mlfile;
+
+ if ((mlfile = fopen(_PATH_RMOUNTLIST, "r")) == NULL) {
+ if (errno == ENOENT)
+ return;
+ else {
+ syslog(LOG_ERR, "can't open %s", _PATH_RMOUNTLIST);
+ return;
+ }
+ }
+ mlpp = &mlhead;
+ while (fgets(str, STRSIZ, mlfile) != NULL) {
+ cp = str;
+ host = strsep(&cp, " \t\n");
+ dirp = strsep(&cp, " \t\n");
+ if (host == NULL || dirp == NULL)
+ continue;
+ mlp = (struct mountlist *)malloc(sizeof (*mlp));
+ if (mlp == (struct mountlist *)NULL)
+ out_of_mem();
+ strncpy(mlp->ml_host, host, MNTNAMLEN);
+ mlp->ml_host[MNTNAMLEN] = '\0';
+ strncpy(mlp->ml_dirp, dirp, MNTPATHLEN);
+ mlp->ml_dirp[MNTPATHLEN] = '\0';
+ mlp->ml_next = (struct mountlist *)NULL;
+ *mlpp = mlp;
+ mlpp = &mlp->ml_next;
+ }
+ fclose(mlfile);
+}
+
+static void
+del_mlist(char *hostp, char *dirp)
+{
+ struct mountlist *mlp, **mlpp;
+ struct mountlist *mlp2;
+ FILE *mlfile;
+ int fnd = 0;
+
+ mlpp = &mlhead;
+ mlp = mlhead;
+ while (mlp) {
+ if (!strcmp(mlp->ml_host, hostp) &&
+ (!dirp || !strcmp(mlp->ml_dirp, dirp))) {
+ fnd = 1;
+ mlp2 = mlp;
+ *mlpp = mlp = mlp->ml_next;
+ free((caddr_t)mlp2);
+ } else {
+ mlpp = &mlp->ml_next;
+ mlp = mlp->ml_next;
+ }
+ }
+ if (fnd) {
+ if ((mlfile = fopen(_PATH_RMOUNTLIST, "w")) == NULL) {
+ syslog(LOG_ERR,"can't update %s", _PATH_RMOUNTLIST);
+ return;
+ }
+ mlp = mlhead;
+ while (mlp) {
+ fprintf(mlfile, "%s %s\n", mlp->ml_host, mlp->ml_dirp);
+ mlp = mlp->ml_next;
+ }
+ fclose(mlfile);
+ }
+}
+
+static void
+add_mlist(char *hostp, char *dirp)
+{
+ struct mountlist *mlp, **mlpp;
+ FILE *mlfile;
+
+ mlpp = &mlhead;
+ mlp = mlhead;
+ while (mlp) {
+ if (!strcmp(mlp->ml_host, hostp) && !strcmp(mlp->ml_dirp, dirp))
+ return;
+ mlpp = &mlp->ml_next;
+ mlp = mlp->ml_next;
+ }
+ mlp = (struct mountlist *)malloc(sizeof (*mlp));
+ if (mlp == (struct mountlist *)NULL)
+ out_of_mem();
+ strncpy(mlp->ml_host, hostp, MNTNAMLEN);
+ mlp->ml_host[MNTNAMLEN] = '\0';
+ strncpy(mlp->ml_dirp, dirp, MNTPATHLEN);
+ mlp->ml_dirp[MNTPATHLEN] = '\0';
+ mlp->ml_next = (struct mountlist *)NULL;
+ *mlpp = mlp;
+ if ((mlfile = fopen(_PATH_RMOUNTLIST, "a")) == NULL) {
+ syslog(LOG_ERR, "can't update %s", _PATH_RMOUNTLIST);
+ return;
+ }
+ fprintf(mlfile, "%s %s\n", mlp->ml_host, mlp->ml_dirp);
+ fclose(mlfile);
+}
+
+/*
+ * Free up a group list.
+ */
+static void
+free_grp(struct grouplist *grp)
+{
+ if (grp->gr_type == GT_HOST) {
+ if (grp->gr_ptr.gt_addrinfo != NULL)
+ freeaddrinfo(grp->gr_ptr.gt_addrinfo);
+ } else if (grp->gr_type == GT_NET) {
+ if (grp->gr_ptr.gt_net.nt_name)
+ free(grp->gr_ptr.gt_net.nt_name);
+ }
+ free((caddr_t)grp);
+}
+
+#ifdef DEBUG
+static void
+SYSLOG(int pri, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+#endif /* DEBUG */
+
+/*
+ * Check options for consistency.
+ */
+static int
+check_options(struct dirlist *dp)
+{
+
+ if (v4root_phase == 0 && dp == NULL)
+ return (1);
+ if ((opt_flags & (OP_MAPROOT | OP_MAPALL)) == (OP_MAPROOT | OP_MAPALL)) {
+ syslog(LOG_ERR, "-mapall and -maproot mutually exclusive");
+ return (1);
+ }
+ if ((opt_flags & OP_MASK) && (opt_flags & OP_NET) == 0) {
+ syslog(LOG_ERR, "-mask requires -network");
+ return (1);
+ }
+ if ((opt_flags & OP_NET) && (opt_flags & OP_HAVEMASK) == 0) {
+ syslog(LOG_ERR, "-network requires mask specification");
+ return (1);
+ }
+ if ((opt_flags & OP_MASK) && (opt_flags & OP_MASKLEN)) {
+ syslog(LOG_ERR, "-mask and /masklen are mutually exclusive");
+ return (1);
+ }
+ if (v4root_phase > 0 &&
+ (opt_flags &
+ ~(OP_SEC | OP_MASK | OP_NET | OP_HAVEMASK | OP_MASKLEN)) != 0) {
+ syslog(LOG_ERR,"only -sec,-net,-mask options allowed on V4:");
+ return (1);
+ }
+ if ((opt_flags & OP_ALLDIRS) && dp->dp_left) {
+ syslog(LOG_ERR, "-alldirs has multiple directories");
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * Check an absolute directory path for any symbolic links. Return true
+ */
+static int
+check_dirpath(char *dirp)
+{
+ char *cp;
+ int ret = 1;
+ struct stat sb;
+
+ cp = dirp + 1;
+ while (*cp && ret) {
+ if (*cp == '/') {
+ *cp = '\0';
+ if (lstat(dirp, &sb) < 0 || !S_ISDIR(sb.st_mode))
+ ret = 0;
+ *cp = '/';
+ }
+ cp++;
+ }
+ if (lstat(dirp, &sb) < 0 || !S_ISDIR(sb.st_mode))
+ ret = 0;
+ return (ret);
+}
+
+/*
+ * Make a netmask according to the specified prefix length. The ss_family
+ * and other non-address fields must be initialised before calling this.
+ */
+static int
+makemask(struct sockaddr_storage *ssp, int bitlen)
+{
+ u_char *p;
+ int bits, i, len;
+
+ if ((p = sa_rawaddr((struct sockaddr *)ssp, &len)) == NULL)
+ return (-1);
+ if (bitlen > len * CHAR_BIT)
+ return (-1);
+
+ for (i = 0; i < len; i++) {
+ bits = (bitlen > CHAR_BIT) ? CHAR_BIT : bitlen;
+ *p++ = (u_char)~0 << (CHAR_BIT - bits);
+ bitlen -= bits;
+ }
+ return 0;
+}
+
+/*
+ * Check that the sockaddr is a valid netmask. Returns 0 if the mask
+ * is acceptable (i.e. of the form 1...10....0).
+ */
+static int
+checkmask(struct sockaddr *sa)
+{
+ u_char *mask;
+ int i, len;
+
+ if ((mask = sa_rawaddr(sa, &len)) == NULL)
+ return (-1);
+
+ for (i = 0; i < len; i++)
+ if (mask[i] != 0xff)
+ break;
+ if (i < len) {
+ if (~mask[i] & (u_char)(~mask[i] + 1))
+ return (-1);
+ i++;
+ }
+ for (; i < len; i++)
+ if (mask[i] != 0)
+ return (-1);
+ return (0);
+}
+
+/*
+ * Compare two sockaddrs according to a specified mask. Return zero if
+ * `sa1' matches `sa2' when filtered by the netmask in `samask'.
+ * If samask is NULL, perform a full comparison.
+ */
+static int
+sacmp(struct sockaddr *sa1, struct sockaddr *sa2, struct sockaddr *samask)
+{
+ unsigned char *p1, *p2, *mask;
+ int len, i;
+
+ if (sa1->sa_family != sa2->sa_family ||
+ (p1 = sa_rawaddr(sa1, &len)) == NULL ||
+ (p2 = sa_rawaddr(sa2, NULL)) == NULL)
+ return (1);
+
+ switch (sa1->sa_family) {
+ case AF_INET6:
+ if (((struct sockaddr_in6 *)sa1)->sin6_scope_id !=
+ ((struct sockaddr_in6 *)sa2)->sin6_scope_id)
+ return (1);
+ break;
+ }
+
+ /* Simple binary comparison if no mask specified. */
+ if (samask == NULL)
+ return (memcmp(p1, p2, len));
+
+ /* Set up the mask, and do a mask-based comparison. */
+ if (sa1->sa_family != samask->sa_family ||
+ (mask = sa_rawaddr(samask, NULL)) == NULL)
+ return (1);
+
+ for (i = 0; i < len; i++)
+ if ((p1[i] & mask[i]) != (p2[i] & mask[i]))
+ return (1);
+ return (0);
+}
+
+/*
+ * Return a pointer to the part of the sockaddr that contains the
+ * raw address, and set *nbytes to its length in bytes. Returns
+ * NULL if the address family is unknown.
+ */
+static void *
+sa_rawaddr(struct sockaddr *sa, int *nbytes) {
+ void *p;
+ int len;
+
+ switch (sa->sa_family) {
+ case AF_INET:
+ len = sizeof(((struct sockaddr_in *)sa)->sin_addr);
+ p = &((struct sockaddr_in *)sa)->sin_addr;
+ break;
+ case AF_INET6:
+ len = sizeof(((struct sockaddr_in6 *)sa)->sin6_addr);
+ p = &((struct sockaddr_in6 *)sa)->sin6_addr;
+ break;
+ default:
+ p = NULL;
+ len = 0;
+ }
+
+ if (nbytes != NULL)
+ *nbytes = len;
+ return (p);
+}
+
+static void
+huphandler(int sig __unused)
+{
+
+ got_sighup = 1;
+}
+
+static void
+terminate(int sig __unused)
+{
+ pidfile_remove(pfh);
+ rpcb_unset(MOUNTPROG, MOUNTVERS, NULL);
+ rpcb_unset(MOUNTPROG, MOUNTVERS3, NULL);
+ exit (0);
+}
diff --git a/usr.sbin/mountd/netgroup.5 b/usr.sbin/mountd/netgroup.5
new file mode 100644
index 0000000..c94f880
--- /dev/null
+++ b/usr.sbin/mountd/netgroup.5
@@ -0,0 +1,190 @@
+.\" 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.
+.\"
+.\" @(#)netgroup.5 8.2 (Berkeley) 12/11/93
+.\" $FreeBSD$
+.\"
+.Dd December 11, 1993
+.Dt NETGROUP 5
+.Os
+.Sh NAME
+.Nm netgroup
+.Nd defines network groups
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+The
+.Nm
+file
+specifies ``netgroups'', which are sets of
+.Sy (host, user, domain)
+tuples that are to be given similar network access.
+.Pp
+Each line in the file
+consists of a netgroup name followed by a list of the members of the
+netgroup.
+Each member can be either the name of another netgroup or a specification
+of a tuple as follows:
+.Bd -literal -offset indent
+(host, user, domain)
+.Ed
+.Pp
+where the
+.Sy host ,
+.Sy user ,
+and
+.Sy domain
+are character string names for the corresponding component.
+Any of the comma separated fields may be empty to specify a ``wildcard'' value
+or may consist of the string ``-'' to specify ``no valid value''.
+The members of the list may be separated by whitespace and/or commas;
+the ``\e'' character may be used at the end of a line to specify
+line continuation.
+Lines are limited to 1024 characters.
+The functions specified in
+.Xr getnetgrent 3
+should normally be used to access the
+.Nm
+database.
+.Pp
+Lines that begin with a # are treated as comments.
+.Sh NIS/YP INTERACTION
+On most other platforms,
+.Nm Ns s
+are only used in conjunction with
+.Tn NIS
+and local
+.Pa /etc/netgroup
+files are ignored.
+With
+.Fx ,
+.Nm Ns s
+can be used with either
+.Tn NIS
+or local files, but there are certain
+caveats to consider.
+The existing
+.Nm
+system is extremely inefficient where
+.Fn innetgr 3
+lookups are concerned since
+.Nm
+memberships are computed on the fly.
+By contrast, the
+.Tn NIS
+.Nm
+database consists of three separate maps (netgroup, netgroup.byuser
+and netgroup.byhost) that are keyed to allow
+.Fn innetgr 3
+lookups to be done quickly.
+The
+.Fx
+.Nm
+system can interact with the
+.Tn NIS
+.Nm
+maps in the following ways:
+.Bl -bullet -offset indent
+.It
+If the
+.Pa /etc/netgroup
+file does not exist, or it exists and is empty, or
+it exists and contains only a
+.Sq + ,
+and
+.Tn NIS
+is running,
+.Nm
+lookups will be done exclusively through
+.Tn NIS ,
+with
+.Fn innetgr 3
+taking advantage of the netgroup.byuser and
+netgroup.byhost maps to speed up searches.
+(This
+is more or less compatible with the behavior of SunOS and
+similar platforms.)
+.It
+If the
+.Pa /etc/netgroup
+exists and contains only local
+.Nm
+information (with no
+.Tn NIS
+.Sq +
+token), then only the local
+.Nm
+information will be processed (and
+.Tn NIS
+will be ignored).
+.It
+If
+.Pa /etc/netgroup
+exists and contains both local netgroup data
+.Pa and
+the
+.Tn NIS
+.Sq +
+token, the local data and the
+.Tn NIS
+netgroup
+map will be processed as a single combined
+.Nm
+database.
+While this configuration is the most flexible, it
+is also the least efficient: in particular,
+.Fn innetgr 3
+lookups will be especially slow if the
+database is large.
+.El
+.Sh FILES
+.Bl -tag -width /etc/netgroup -compact
+.It Pa /etc/netgroup
+the netgroup database
+.El
+.Sh COMPATIBILITY
+The file format is compatible with that of various vendors, however it
+appears that not all vendors use an identical format.
+.Sh SEE ALSO
+.Xr getnetgrent 3 ,
+.Xr exports 5
+.Sh BUGS
+The interpretation of access restrictions based on the member tuples of a
+netgroup is left up to the various network applications.
+Also, it is not obvious how the domain specification
+applies to the
+.Bx
+environment.
+.Pp
+The
+.Nm
+database should be stored in the form of a
+hashed
+.Xr db 3
+database just like the
+.Xr passwd 5
+database to speed up reverse lookups.
diff --git a/usr.sbin/mountd/pathnames.h b/usr.sbin/mountd/pathnames.h
new file mode 100644
index 0000000..2b73977
--- /dev/null
+++ b/usr.sbin/mountd/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_EXPORTS "/etc/exports"
+#define _PATH_RMOUNTLIST "/var/db/mountdtab"
+#define _PATH_MOUNTDPID "/var/run/mountd.pid"
diff --git a/usr.sbin/moused/Makefile b/usr.sbin/moused/Makefile
new file mode 100644
index 0000000..0f9eac5
--- /dev/null
+++ b/usr.sbin/moused/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+PROG= moused
+MAN= moused.8
+
+LIBADD= m util
+
+#BINMODE=4555
+#PRECIOUSPROG=
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/moused/Makefile.depend b/usr.sbin/moused/Makefile.depend
new file mode 100644
index 0000000..a79b1eb
--- /dev/null
+++ b/usr.sbin/moused/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+ lib/msun \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/moused/moused.8 b/usr.sbin/moused/moused.8
new file mode 100644
index 0000000..2799092
--- /dev/null
+++ b/usr.sbin/moused/moused.8
@@ -0,0 +1,854 @@
+.\" Copyright (c) 1996
+.\" Mike Pritchard <mpp@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.
+.\" 3. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by Mike Pritchard.
+.\" 4. Neither the name of the author 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 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 May 15, 2008
+.Dt MOUSED 8
+.Os
+.Sh NAME
+.Nm moused
+.Nd pass mouse data to the console driver
+.Sh SYNOPSIS
+.Nm
+.Op Fl DPRacdfs
+.Op Fl I Ar file
+.Op Fl F Ar rate
+.Op Fl r Ar resolution
+.Op Fl S Ar baudrate
+.Op Fl VH Op Fl U Ar distance Fl L Ar distance
+.Op Fl A Ar exp Ns Op , Ns Ar offset
+.Op Fl a Ar X Ns Op , Ns Ar Y
+.Op Fl C Ar threshold
+.Op Fl m Ar N=M
+.Op Fl w Ar N
+.Op Fl z Ar target
+.Op Fl t Ar mousetype
+.Op Fl l Ar level
+.Op Fl 3 Op Fl E Ar timeout
+.Op Fl T Ar distance Ns Op , Ns Ar time Ns Op , Ns Ar after
+.Fl p Ar port
+.Pp
+.Nm
+.Op Fl Pd
+.Fl p Ar port
+.Fl i Ar info
+.Sh DESCRIPTION
+The
+.Nm
+utility and the console driver work together to support
+mouse operation in the text console and user programs.
+They virtualize the mouse and provide user programs with mouse data
+in the standard format
+(see
+.Xr sysmouse 4 ) .
+.Pp
+The mouse daemon listens to the specified port for mouse data,
+interprets and then passes it via ioctls to the console driver.
+The mouse daemon
+reports translation movement, button press/release
+events and movement of the roller or the wheel if available.
+The roller/wheel movement is reported as
+.Dq Z
+axis movement.
+.Pp
+The console driver will display the mouse pointer on the screen
+and provide cut and paste functions if the mouse pointer is enabled
+in the virtual console via
+.Xr vidcontrol 1 .
+If
+.Xr sysmouse 4
+is opened by the user program, the console driver also passes the mouse
+data to the device so that the user program will see it.
+.Pp
+If the mouse daemon receives the signal
+.Dv SIGHUP ,
+it will reopen the mouse port and reinitialize itself.
+Useful if
+the mouse is attached/detached while the system is suspended.
+.Pp
+If the mouse daemon receives the signal
+.Dv SIGUSR1 ,
+it will stop passing mouse events.
+Sending the signal
+.Dv SIGUSR1
+again will resume passing mouse events.
+Useful if your typing on a laptop is
+interrupted by accidentally touching the mouse pad.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl 3
+Emulate the third (middle) button for 2-button mice.
+It is emulated
+by pressing the left and right physical buttons simultaneously.
+.It Fl C Ar threshold
+Set double click speed as the maximum interval in msec between button clicks.
+Without this option, the default value of 500 msec will be assumed.
+This option will have effect only on the cut and paste operations
+in the text mode console.
+The user program which is reading mouse data
+via
+.Xr sysmouse 4
+will not be affected.
+.It Fl D
+Lower DTR on the serial port.
+This option is valid only if
+.Ar mousesystems
+is selected as the protocol type.
+The DTR line may need to be dropped for a 3-button mouse
+to operate in the
+.Ar mousesystems
+mode.
+.It Fl E Ar timeout
+When the third button emulation is enabled
+(see above),
+the
+.Nm
+utility waits
+.Ar timeout
+msec at most before deciding whether two buttons are being pressed
+simultaneously.
+The default timeout is 100 msec.
+.It Fl F Ar rate
+Set the report rate (reports/sec) of the device if supported.
+.It Fl L Ar distance
+When
+.Dq Virtual Scrolling
+is enabled, the
+.Fl L
+option can be used to set the
+.Ar distance
+(in pixels) that the mouse must move before a scroll event
+is generated. This effectively controls the scrolling speed.
+The default
+.Ar distance
+is 2 pixels.
+.It Fl H
+Enable
+.Dq Horizontal Virtual Scrolling .
+With this option set, holding the middle mouse
+button down will cause motion to be interpreted as
+horizontal scrolling.
+Use the
+.Fl U
+option to set the distance the mouse must move before the scrolling mode is
+activated and the
+.Fl L
+option to set the scrolling speed.
+This option may be used with or without the
+.Fl V
+option.
+.It Fl I Ar file
+Write the process id of the
+.Nm
+utility in the specified file.
+Without this option, the process id will be stored in
+.Pa /var/run/moused.pid .
+.It Fl P
+Do not start the Plug and Play COM device enumeration procedure
+when identifying the serial mouse.
+If this option is given together with the
+.Fl i
+option, the
+.Nm
+utility will not be able to print useful information for the serial mouse.
+.It Fl R
+Lower RTS on the serial port.
+This option is valid only if
+.Ar mousesystems
+is selected as the protocol type by the
+.Fl t
+option below.
+It is often used with the
+.Fl D
+option above.
+Both RTS and DTR lines may need to be dropped for
+a 3-button mouse to operate in the
+.Ar mousesystems
+mode.
+.It Fl S Ar baudrate
+Select the baudrate for the serial port (1200 to 9600).
+Not all serial mice support this option.
+.It Fl T Ar distance Ns Op , Ns Ar time Ns Op , Ns Ar after
+Terminate drift.
+Use this option if mouse pointer slowly wanders when mouse is not moved.
+Movements up to
+.Ar distance
+(for example 4) pixels (X+Y) in
+.Ar time
+msec (default 500) are ignored, except during
+.Ar after
+msec (default 4000) since last real mouse movement.
+.It Fl V
+Enable
+.Dq Virtual Scrolling .
+With this option set, holding the middle mouse
+button down will cause motion to be interpreted as scrolling.
+Use the
+.Fl U
+option to set the distance the mouse must move before the scrolling mode is
+activated and the
+.Fl L
+option to set the scrolling speed.
+.It Fl U Ar distance
+When
+.Dq Virtual Scrolling
+is enabled, the
+.Fl U
+option can be used to set the
+.Ar distance
+(in pixels) that the mouse must move before the scrolling
+mode is activated.
+The default
+.Ar distance
+is 3 pixels.
+.It Fl A Ar exp Ns Op , Ns Ar offset
+Apply exponential (dynamic) acceleration to mouse movements:
+the faster you move the mouse, the more it will be accelerated.
+That means that small mouse movements are not accelerated,
+so they are still very accurate, while a faster movement will
+drive the pointer quickly across the screen.
+.Pp
+The
+.Ar exp
+value specifies the exponent, which is basically
+the amount of acceleration. Useful values are in the
+range 1.1 to 2.0, but it depends on your mouse hardware
+and your personal preference. A value of 1.0 means no
+exponential acceleration. A value of 2.0 means squared
+acceleration (i.e. if you move the mouse twice as fast,
+the pointer will move four times as fast on the screen).
+Values beyond 2.0 are possible but not recommended.
+A good value to start is probably 1.5.
+.Pp
+The optional
+.Ar offset
+value specifies the distance at which the acceleration
+begins. The default is 1.0, which means that the
+acceleration is applied to movements larger than one unit.
+If you specify a larger value, it takes more speed for
+the acceleration to kick in, i.e. the speed range for
+small and accurate movements is wider.
+Usually the default should be sufficient, but if you're
+not satisfied with the behaviour, try a value of 2.0.
+.Pp
+Note that the
+.Fl A
+option interacts badly with the X server's own acceleration,
+which doesn't work very well anyway. Therefore it is
+recommended to switch it off if necessary:
+.Dq xset m 1 .
+.It Fl a Ar X Ns Op , Ns Ar Y
+Accelerate or decelerate the mouse input.
+This is a linear acceleration only.
+Values less than 1.0 slow down movement, values greater than 1.0 speed it
+up.
+Specifying only one value sets the acceleration for both axes.
+.Pp
+You can use the
+.Fl a
+and
+.Fl A
+options at the same time to have the combined effect
+of linear and exponential acceleration.
+.It Fl c
+Some mice report middle button down events
+as if the left and right buttons are being pressed.
+This option handles this.
+.It Fl d
+Enable debugging messages.
+.It Fl f
+Do not become a daemon and instead run as a foreground process.
+Useful for testing and debugging.
+.It Fl i Ar info
+Print specified information and quit.
+Available pieces of
+information are:
+.Pp
+.Bl -tag -compact -width modelxxx
+.It Ar port
+Port (device file) name, i.e.\&
+.Pa /dev/cuau0 ,
+.Pa /dev/mse0
+and
+.Pa /dev/psm0 .
+.It Ar if
+Interface type: serial, bus, inport or ps/2.
+.It Ar type
+Protocol type.
+It is one of the types listed under the
+.Fl t
+option below or
+.Ar sysmouse
+if the driver supports the
+.Ar sysmouse
+data format standard.
+.It Ar model
+Mouse model.
+The
+.Nm
+utility may not always be able to identify the model.
+.It Ar all
+All of the above items.
+Print port, interface, type and model in this order
+in one line.
+.El
+.Pp
+If the
+.Nm
+utility cannot determine the requested information, it prints
+.Dq Li unknown
+or
+.Dq Li generic .
+.It Fl l Ar level
+Specifies at which level
+.Nm
+should operate the mouse driver.
+Refer to
+.Sx Operation Levels
+in
+.Xr psm 4
+for more information on this.
+.It Fl m Ar N=M
+Assign the physical button
+.Ar M
+to the logical button
+.Ar N .
+You may specify as many instances of this option as you like.
+More than one physical button may be assigned to a logical button at the
+same time.
+In this case the logical button will be down,
+if either of the assigned physical buttons is held down.
+Do not put space around
+.Ql = .
+.It Fl p Ar port
+Use
+.Ar port
+to communicate with the mouse.
+.It Fl r Ar resolution
+Set the resolution of the device; in Dots Per Inch, or
+.Ar low ,
+.Ar medium-low ,
+.Ar medium-high
+or
+.Ar high .
+This option may not be supported by all the device.
+.It Fl s
+Select a baudrate of 9600 for the serial line.
+Not all serial mice support this option.
+.It Fl t Ar type
+Specify the protocol type of the mouse attached to the port.
+You may explicitly specify a type listed below, or use
+.Ar auto
+to let the
+.Nm
+utility automatically select an appropriate protocol for the given
+mouse.
+If you entirely omit this option in the command line,
+.Fl t Ar auto
+is assumed.
+Under normal circumstances,
+you need to use this option only if the
+.Nm
+utility is not able to detect the protocol automatically
+(see
+.Sx "Configuring Mouse Daemon" ) .
+.Pp
+Note that if a protocol type is specified with this option, the
+.Fl P
+option above is implied and Plug and Play COM device enumeration
+procedure will be disabled.
+.Pp
+Also note that if your mouse is attached to the PS/2 mouse port, you should
+always choose
+.Ar auto
+or
+.Ar ps/2 ,
+regardless of the brand and model of the mouse.
+Likewise, if your
+mouse is attached to the bus mouse port, choose
+.Ar auto
+or
+.Ar busmouse .
+Serial mouse protocols will not work with these mice.
+.Pp
+For the USB mouse, the protocol must be
+.Ar auto .
+No other protocol will work with the USB mouse.
+.Pp
+Valid types for this option are
+listed below.
+.Pp
+For the serial mouse:
+.Bl -tag -compact -width mousesystemsxxx
+.It Ar microsoft
+Microsoft serial mouse protocol.
+Most 2-button serial mice use this protocol.
+.It Ar intellimouse
+Microsoft IntelliMouse protocol.
+Genius NetMouse,
+.Tn ASCII
+Mie Mouse,
+Logitech MouseMan+ and FirstMouse+ use this protocol too.
+Other mice with a roller/wheel may be compatible with this protocol.
+.It Ar mousesystems
+MouseSystems 5-byte protocol.
+3-button mice may use this protocol.
+.It Ar mmseries
+MM Series mouse protocol.
+.It Ar logitech
+Logitech mouse protocol.
+Note that this is for old Logitech models.
+.Ar mouseman
+or
+.Ar intellimouse
+should be specified for newer models.
+.It Ar mouseman
+Logitech MouseMan and TrackMan protocol.
+Some 3-button mice may be compatible
+with this protocol.
+Note that MouseMan+ and FirstMouse+ use
+.Ar intellimouse
+protocol rather than this one.
+.It Ar glidepoint
+ALPS GlidePoint protocol.
+.It Ar thinkingmouse
+Kensington ThinkingMouse protocol.
+.It Ar mmhitab
+Hitachi tablet protocol.
+.It Ar x10mouseremote
+X10 MouseRemote.
+.It Ar kidspad
+Genius Kidspad and Easypad protocol.
+.It Ar versapad
+Interlink VersaPad protocol.
+.It Ar gtco_digipad
+GTCO Digipad protocol.
+.El
+.Pp
+For the bus and InPort mouse:
+.Bl -tag -compact -width mousesystemsxxx
+.It Ar busmouse
+This is the only protocol type available for
+the bus and InPort mouse and should be specified for any bus mice
+and InPort mice, regardless of the brand.
+.El
+.Pp
+For the PS/2 mouse:
+.Bl -tag -compact -width mousesystemsxxx
+.It Ar ps/2
+This is the only protocol type available for the PS/2 mouse
+and should be specified for any PS/2 mice, regardless of the brand.
+.El
+.Pp
+For the USB mouse,
+.Ar auto
+is the only protocol type available for the USB mouse
+and should be specified for any USB mice, regardless of the brand.
+.It Fl w Ar N
+Make the physical button
+.Ar N
+act as the wheel mode button.
+While this button is pressed, X and Y axis movement is reported to be zero
+and the Y axis movement is mapped to Z axis.
+You may further map the Z axis movement to virtual buttons by the
+.Fl z
+option below.
+.It Fl z Ar target
+Map Z axis (roller/wheel) movement to another axis or to virtual buttons.
+Valid
+.Ar target
+maybe:
+.Bl -tag -compact -width x__
+.It Ar x
+.It Ar y
+X or Y axis movement will be reported when the Z axis movement is detected.
+.It Ar N
+Report down events for the virtual buttons
+.Ar N
+and
+.Ar N+1
+respectively when negative and positive Z axis movement
+is detected.
+There do not need to be physical buttons
+.Ar N
+and
+.Ar N+1 .
+Note that mapping to logical buttons is carried out after mapping
+from the Z axis movement to the virtual buttons is done.
+.It Ar N1 N2
+Report down events for the virtual buttons
+.Ar N1
+and
+.Ar N2
+respectively when negative and positive Z axis movement
+is detected.
+.It Ar N1 N2 N3 N4
+This is useful for the mouse with two wheels of which
+the second wheel is used to generate horizontal scroll action,
+and for the mouse which has a knob or a stick which can detect
+the horizontal force applied by the user.
+.Pp
+The motion of the second wheel will be mapped to the buttons
+.Ar N3 ,
+for the negative direction, and
+.Ar N4 ,
+for the positive direction.
+If the buttons
+.Ar N3
+and
+.Ar N4
+actually exist in this mouse, their actions will not be detected.
+.Pp
+Note that horizontal movement or second roller/wheel movement may not
+always be detected,
+because there appears to be no accepted standard as to how it is encoded.
+.Pp
+Note also that some mice think left is the negative horizontal direction;
+others may think otherwise.
+Moreover, there are some mice whose two wheels are both mounted vertically,
+and the direction of the second vertical wheel does not match the
+first one.
+.El
+.El
+.Ss Configuring Mouse Daemon
+The first thing you need to know is the interface type
+of the mouse you are going to use.
+It can be determined by looking at the connector of the mouse.
+The serial mouse has a D-Sub female 9- or 25-pin connector.
+The bus and InPort mice have either a D-Sub male 9-pin connector
+or a round DIN 9-pin connector.
+The PS/2 mouse is equipped with a small, round DIN 6-pin connector.
+Some mice come with adapters with which the connector can
+be converted to another.
+If you are to use such an adapter,
+remember the connector at the very end of the mouse/adapter pair is
+what matters.
+The USB mouse has a flat rectangular connector.
+.Pp
+The next thing to decide is a port to use for the given interface.
+For the bus, InPort and PS/2 mice, there is little choice:
+the bus and InPort mice always use
+.Pa /dev/mse0 ,
+and the PS/2 mouse is always at
+.Pa /dev/psm0 .
+There may be more than one serial port to which the serial
+mouse can be attached.
+Many people often assign the first, built-in
+serial port
+.Pa /dev/cuau0
+to the mouse.
+You can attach multiple USB mice to your system or to your USB hub.
+They are accessible as
+.Pa /dev/ums0 , /dev/ums1 ,
+and so on.
+.Pp
+You may want to create a symbolic link
+.Pa /dev/mouse
+pointing to the real port to which the mouse is connected, so that you
+can easily distinguish which is your
+.Dq mouse
+port later.
+.Pp
+The next step is to guess the appropriate protocol type for the mouse.
+The
+.Nm
+utility may be able to automatically determine the protocol type.
+Run the
+.Nm
+utility with the
+.Fl i
+option and see what it says.
+If the command can identify
+the protocol type, no further investigation is necessary on your part.
+You may start the daemon without explicitly specifying a protocol type
+(see
+.Sx EXAMPLES ) .
+.Pp
+The command may print
+.Ar sysmouse
+if the mouse driver supports this protocol type.
+.Pp
+Note that the
+.Dv type
+and
+.Dv model
+printed by the
+.Fl i
+option do not necessarily match the product name of the pointing device
+in question, but they may give the name of the device with which it is
+compatible.
+.Pp
+If the
+.Fl i
+option yields nothing, you need to specify a protocol type to the
+.Nm
+utility by the
+.Fl t
+option.
+You have to make a guess and try.
+There is rule of thumb:
+.Pp
+.Bl -enum -compact -width 1.X
+.It
+The bus and InPort mice always use
+.Ar busmouse
+protocol regardless of the brand of the mouse.
+.It
+The
+.Ar ps/2
+protocol should always be specified for the PS/2 mouse
+regardless of the brand of the mouse.
+.It
+You must specify the
+.Ar auto
+protocol for the USB mouse.
+.It
+Most 2-button serial mice support the
+.Ar microsoft
+protocol.
+.It
+3-button serial mice may work with the
+.Ar mousesystems
+protocol.
+If it does not, it may work with the
+.Ar microsoft
+protocol although
+the third (middle) button will not function.
+3-button serial mice may also work with the
+.Ar mouseman
+protocol under which the third button may function as expected.
+.It
+3-button serial mice may have a small switch to choose between
+.Dq MS
+and
+.Dq PC ,
+or
+.Dq 2
+and
+.Dq 3 .
+.Dq MS
+or
+.Dq 2
+usually mean the
+.Ar microsoft
+protocol.
+.Dq PC
+or
+.Dq 3
+will choose the
+.Ar mousesystems
+protocol.
+.It
+If the mouse has a roller or a wheel, it may be compatible with the
+.Ar intellimouse
+protocol.
+.El
+.Pp
+To test if the selected protocol type is correct for the given mouse,
+enable the mouse pointer in the current virtual console,
+.Pp
+.Dl "vidcontrol -m on"
+.Pp
+start the mouse daemon in the foreground mode,
+.Pp
+.Dl "moused -f -p <selected_port> -t <selected_protocol>"
+.Pp
+and see if the mouse pointer travels correctly
+according to the mouse movement.
+Then try cut & paste features by
+clicking the left, right and middle buttons.
+Type ^C to stop
+the command.
+.Ss Multiple Mice
+As many instances of the mouse daemon as the number of mice attached to
+the system may be run simultaneously; one
+instance for each mouse.
+This is useful if the user wants to use the built-in PS/2 pointing device
+of a laptop computer while on the road, but wants to use a serial
+mouse when s/he attaches the system to the docking station in the office.
+Run two mouse daemons and tell the application program
+(such as the
+.Tn "X\ Window System" )
+to use
+.Xr sysmouse 4 ,
+then the application program will always see mouse data from either mouse.
+When the serial mouse is not attached, the corresponding mouse daemon
+will not detect any movement or button state change and the application
+program will only see mouse data coming from the daemon for the
+PS/2 mouse.
+In contrast when both mice are attached and both of them
+are moved at the same time in this configuration,
+the mouse pointer will travel across the screen just as if movement of
+the mice is combined all together.
+.Sh FILES
+.Bl -tag -width /dev/consolectl -compact
+.It Pa /dev/consolectl
+device to control the console
+.It Pa /dev/mse%d
+bus and InPort mouse driver
+.It Pa /dev/psm%d
+PS/2 mouse driver
+.It Pa /dev/sysmouse
+virtualized mouse driver
+.It Pa /dev/ttyv%d
+virtual consoles
+.It Pa /dev/ums%d
+USB mouse driver
+.It Pa /var/run/moused.pid
+process id of the currently running
+.Nm
+utility
+.It Pa /var/run/MouseRemote
+UNIX-domain stream socket for X10 MouseRemote events
+.El
+.Sh EXAMPLES
+.Dl "moused -p /dev/cuau0 -i type"
+.Pp
+Let the
+.Nm
+utility determine the protocol type of the mouse at the serial port
+.Pa /dev/cuau0 .
+If successful, the command will print the type, otherwise it will say
+.Dq Li unknown .
+.Bd -literal -offset indent
+moused -p /dev/cuau0
+vidcontrol -m on
+.Ed
+.Pp
+If the
+.Nm
+utility is able to identify the protocol type of the mouse at the specified
+port automatically, you can start the daemon without the
+.Fl t
+option and enable the mouse pointer in the text console as above.
+.Bd -literal -offset indent
+moused -p /dev/mouse -t microsoft
+vidcontrol -m on
+.Ed
+.Pp
+Start the mouse daemon on the serial port
+.Pa /dev/mouse .
+The protocol type
+.Ar microsoft
+is explicitly specified by the
+.Fl t
+option.
+.Pp
+.Dl "moused -p /dev/mouse -m 1=3 -m 3=1"
+.Pp
+Assign the physical button 3 (right button) to the logical button 1
+(logical left) and the physical button 1 (left) to the logical
+button 3 (logical right).
+This will effectively swap the left and right buttons.
+.Pp
+.Dl "moused -p /dev/mouse -t intellimouse -z 4"
+.Pp
+Report negative Z axis movement (i.e., mouse wheel) as the button 4 pressed
+and positive Z axis movement (i.e., mouse wheel) as the button 5 pressed.
+.Pp
+If you add
+.Pp
+.Dl "ALL ALL = NOPASSWD: /usr/bin/killall -USR1 moused"
+.Pp
+to your
+.Pa /usr/local/etc/sudoers
+file, and bind
+.Pp
+.Dl "killall -USR1 moused"
+.Pp
+to a key in your window manager, you can suspend mouse events on your laptop if
+you keep brushing over the mouse pad while typing.
+.Sh SEE ALSO
+.Xr kill 1 ,
+.Xr vidcontrol 1 ,
+.Xr xset 1 ,
+.Xr keyboard 4 ,
+.Xr mse 4 ,
+.Xr psm 4 ,
+.Xr screen 4 ,
+.Xr sysmouse 4 ,
+.Xr ums 4
+.Sh STANDARDS
+The
+.Nm
+utility partially supports
+.Dq Plug and Play External COM Device Specification
+in order to support PnP serial mice.
+However, due to various degrees of conformance to the specification by
+existing serial mice, it does not strictly follow the version 1.0 of the
+standard.
+Even with this less strict approach,
+it may not always determine an appropriate protocol type
+for the given serial mouse.
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 2.2 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility was written by
+.An Michael Smith Aq Mt msmith@FreeBSD.org .
+This manual page was written by
+.An Mike Pritchard Aq Mt mpp@FreeBSD.org .
+The command and manual page have since been updated by
+.An Kazutaka Yokota Aq Mt yokota@FreeBSD.org .
+.Sh CAVEATS
+Many pad devices behave as if the first (left) button were pressed if
+the user
+.Dq taps
+the surface of the pad.
+In contrast, some ALPS GlidePoint and Interlink VersaPad models
+treat the tapping action
+as fourth button events.
+Use the option
+.Dq Fl m Li 1=4
+for these models
+to obtain the same effect as the other pad devices.
+.Pp
+Cut and paste functions in the virtual console assume that there
+are three buttons on the mouse.
+The logical button 1 (logical left) selects a region of text in the
+console and copies it to the cut buffer.
+The logical button 3 (logical right) extends the selected region.
+The logical button 2 (logical middle) pastes the selected text
+at the text cursor position.
+If the mouse has only two buttons, the middle, `paste' button
+is not available.
+To obtain the paste function, use the
+.Fl 3
+option to emulate the middle button, or use the
+.Fl m
+option to assign the physical right button to the logical middle button:
+.Dq Fl m Li 2=3 .
diff --git a/usr.sbin/moused/moused.c b/usr.sbin/moused/moused.c
new file mode 100644
index 0000000..0e278c4
--- /dev/null
+++ b/usr.sbin/moused/moused.c
@@ -0,0 +1,3427 @@
+/**
+ ** Copyright (c) 1995 Michael Smith, All rights reserved.
+ **
+ ** Redistribution and use in source and binary forms, with or without
+ ** modification, are permitted provided that the following conditions
+ ** are met:
+ ** 1. Redistributions of source code must retain the above copyright
+ ** notice, this list of conditions and the following disclaimer as
+ ** the first lines of this file unmodified.
+ ** 2. Redistributions in binary form must 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 Michael Smith.
+ ** 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 Michael Smith ``AS IS'' AND ANY
+ ** EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ ** PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Michael Smith BE LIABLE FOR
+ ** ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ ** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ ** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ ** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ ** EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **
+ **/
+
+/**
+ ** MOUSED.C
+ **
+ ** Mouse daemon : listens to a serial port, the bus mouse interface, or
+ ** the PS/2 mouse port for mouse data stream, interprets data and passes
+ ** ioctls off to the console driver.
+ **
+ ** The mouse interface functions are derived closely from the mouse
+ ** handler in the XFree86 X server. Many thanks to the XFree86 people
+ ** for their great work!
+ **
+ **/
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/consio.h>
+#include <sys/mouse.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/un.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libutil.h>
+#include <limits.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <termios.h>
+#include <unistd.h>
+#include <math.h>
+
+#define MAX_CLICKTHRESHOLD 2000 /* 2 seconds */
+#define MAX_BUTTON2TIMEOUT 2000 /* 2 seconds */
+#define DFLT_CLICKTHRESHOLD 500 /* 0.5 second */
+#define DFLT_BUTTON2TIMEOUT 100 /* 0.1 second */
+#define DFLT_SCROLLTHRESHOLD 3 /* 3 pixels */
+#define DFLT_SCROLLSPEED 2 /* 2 pixels */
+
+/* Abort 3-button emulation delay after this many movement events. */
+#define BUTTON2_MAXMOVE 3
+
+#define TRUE 1
+#define FALSE 0
+
+#define MOUSE_XAXIS (-1)
+#define MOUSE_YAXIS (-2)
+
+/* Logitech PS2++ protocol */
+#define MOUSE_PS2PLUS_CHECKBITS(b) \
+ ((((b[2] & 0x03) << 2) | 0x02) == (b[1] & 0x0f))
+#define MOUSE_PS2PLUS_PACKET_TYPE(b) \
+ (((b[0] & 0x30) >> 2) | ((b[1] & 0x30) >> 4))
+
+#define ChordMiddle 0x0001
+#define Emulate3Button 0x0002
+#define ClearDTR 0x0004
+#define ClearRTS 0x0008
+#define NoPnP 0x0010
+#define VirtualScroll 0x0020
+#define HVirtualScroll 0x0040
+#define ExponentialAcc 0x0080
+
+#define ID_NONE 0
+#define ID_PORT 1
+#define ID_IF 2
+#define ID_TYPE 4
+#define ID_MODEL 8
+#define ID_ALL (ID_PORT | ID_IF | ID_TYPE | ID_MODEL)
+
+/* Operations on timespecs */
+#define tsclr(tvp) ((tvp)->tv_sec = (tvp)->tv_nsec = 0)
+#define tscmp(tvp, uvp, cmp) \
+ (((tvp)->tv_sec == (uvp)->tv_sec) ? \
+ ((tvp)->tv_nsec cmp (uvp)->tv_nsec) : \
+ ((tvp)->tv_sec cmp (uvp)->tv_sec))
+#define tssub(tvp, uvp, vvp) \
+ do { \
+ (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \
+ (vvp)->tv_nsec = (tvp)->tv_nsec - (uvp)->tv_nsec; \
+ if ((vvp)->tv_nsec < 0) { \
+ (vvp)->tv_sec--; \
+ (vvp)->tv_nsec += 1000000000; \
+ } \
+ } while (0)
+
+#define debug(...) do { \
+ if (debug && nodaemon) \
+ warnx(__VA_ARGS__); \
+} while (0)
+
+#define logerr(e, ...) do { \
+ log_or_warn(LOG_DAEMON | LOG_ERR, errno, __VA_ARGS__); \
+ exit(e); \
+} while (0)
+
+#define logerrx(e, ...) do { \
+ log_or_warn(LOG_DAEMON | LOG_ERR, 0, __VA_ARGS__); \
+ exit(e); \
+} while (0)
+
+#define logwarn(...) \
+ log_or_warn(LOG_DAEMON | LOG_WARNING, errno, __VA_ARGS__)
+
+#define logwarnx(...) \
+ log_or_warn(LOG_DAEMON | LOG_WARNING, 0, __VA_ARGS__)
+
+/* structures */
+
+/* symbol table entry */
+typedef struct {
+ const char *name;
+ int val;
+ int val2;
+} symtab_t;
+
+/* serial PnP ID string */
+typedef struct {
+ int revision; /* PnP revision, 100 for 1.00 */
+ const char *eisaid; /* EISA ID including mfr ID and product ID */
+ char *serial; /* serial No, optional */
+ const char *class; /* device class, optional */
+ char *compat; /* list of compatible drivers, optional */
+ char *description; /* product description, optional */
+ int neisaid; /* length of the above fields... */
+ int nserial;
+ int nclass;
+ int ncompat;
+ int ndescription;
+} pnpid_t;
+
+/* global variables */
+
+static int debug = 0;
+static int nodaemon = FALSE;
+static int background = FALSE;
+static int paused = FALSE;
+static int identify = ID_NONE;
+static int extioctl = FALSE;
+static const char *pidfile = "/var/run/moused.pid";
+static struct pidfh *pfh;
+
+#define SCROLL_NOTSCROLLING 0
+#define SCROLL_PREPARE 1
+#define SCROLL_SCROLLING 2
+
+static int scroll_state;
+static int scroll_movement;
+static int hscroll_movement;
+
+/* local variables */
+
+/* interface (the table must be ordered by MOUSE_IF_XXX in mouse.h) */
+static symtab_t rifs[] = {
+ { "serial", MOUSE_IF_SERIAL, 0 },
+ { "bus", MOUSE_IF_BUS, 0 },
+ { "inport", MOUSE_IF_INPORT, 0 },
+ { "ps/2", MOUSE_IF_PS2, 0 },
+ { "sysmouse", MOUSE_IF_SYSMOUSE, 0 },
+ { "usb", MOUSE_IF_USB, 0 },
+ { NULL, MOUSE_IF_UNKNOWN, 0 },
+};
+
+/* types (the table must be ordered by MOUSE_PROTO_XXX in mouse.h) */
+static const char *rnames[] = {
+ "microsoft",
+ "mousesystems",
+ "logitech",
+ "mmseries",
+ "mouseman",
+ "busmouse",
+ "inportmouse",
+ "ps/2",
+ "mmhitab",
+ "glidepoint",
+ "intellimouse",
+ "thinkingmouse",
+ "sysmouse",
+ "x10mouseremote",
+ "kidspad",
+ "versapad",
+ "jogdial",
+#if notyet
+ "mariqua",
+#endif
+ "gtco_digipad",
+ NULL
+};
+
+/* models */
+static symtab_t rmodels[] = {
+ { "NetScroll", MOUSE_MODEL_NETSCROLL, 0 },
+ { "NetMouse/NetScroll Optical", MOUSE_MODEL_NET, 0 },
+ { "GlidePoint", MOUSE_MODEL_GLIDEPOINT, 0 },
+ { "ThinkingMouse", MOUSE_MODEL_THINK, 0 },
+ { "IntelliMouse", MOUSE_MODEL_INTELLI, 0 },
+ { "EasyScroll/SmartScroll", MOUSE_MODEL_EASYSCROLL, 0 },
+ { "MouseMan+", MOUSE_MODEL_MOUSEMANPLUS, 0 },
+ { "Kidspad", MOUSE_MODEL_KIDSPAD, 0 },
+ { "VersaPad", MOUSE_MODEL_VERSAPAD, 0 },
+ { "IntelliMouse Explorer", MOUSE_MODEL_EXPLORER, 0 },
+ { "4D Mouse", MOUSE_MODEL_4D, 0 },
+ { "4D+ Mouse", MOUSE_MODEL_4DPLUS, 0 },
+ { "Synaptics Touchpad", MOUSE_MODEL_SYNAPTICS, 0 },
+ { "TrackPoint", MOUSE_MODEL_TRACKPOINT, 0 },
+ { "generic", MOUSE_MODEL_GENERIC, 0 },
+ { NULL, MOUSE_MODEL_UNKNOWN, 0 },
+};
+
+/* PnP EISA/product IDs */
+static symtab_t pnpprod[] = {
+ /* Kensignton ThinkingMouse */
+ { "KML0001", MOUSE_PROTO_THINK, MOUSE_MODEL_THINK },
+ /* MS IntelliMouse */
+ { "MSH0001", MOUSE_PROTO_INTELLI, MOUSE_MODEL_INTELLI },
+ /* MS IntelliMouse TrackBall */
+ { "MSH0004", MOUSE_PROTO_INTELLI, MOUSE_MODEL_INTELLI },
+ /* Tremon Wheel Mouse MUSD */
+ { "HTK0001", MOUSE_PROTO_INTELLI, MOUSE_MODEL_INTELLI },
+ /* Genius PnP Mouse */
+ { "KYE0001", MOUSE_PROTO_MS, MOUSE_MODEL_GENERIC },
+ /* MouseSystems SmartScroll Mouse (OEM from Genius?) */
+ { "KYE0002", MOUSE_PROTO_MS, MOUSE_MODEL_EASYSCROLL },
+ /* Genius NetMouse */
+ { "KYE0003", MOUSE_PROTO_INTELLI, MOUSE_MODEL_NET },
+ /* Genius Kidspad, Easypad and other tablets */
+ { "KYE0005", MOUSE_PROTO_KIDSPAD, MOUSE_MODEL_KIDSPAD },
+ /* Genius EZScroll */
+ { "KYEEZ00", MOUSE_PROTO_MS, MOUSE_MODEL_EASYSCROLL },
+ /* Logitech Cordless MouseMan Wheel */
+ { "LGI8033", MOUSE_PROTO_INTELLI, MOUSE_MODEL_MOUSEMANPLUS },
+ /* Logitech MouseMan (new 4 button model) */
+ { "LGI800C", MOUSE_PROTO_INTELLI, MOUSE_MODEL_MOUSEMANPLUS },
+ /* Logitech MouseMan+ */
+ { "LGI8050", MOUSE_PROTO_INTELLI, MOUSE_MODEL_MOUSEMANPLUS },
+ /* Logitech FirstMouse+ */
+ { "LGI8051", MOUSE_PROTO_INTELLI, MOUSE_MODEL_MOUSEMANPLUS },
+ /* Logitech serial */
+ { "LGI8001", MOUSE_PROTO_LOGIMOUSEMAN, MOUSE_MODEL_GENERIC },
+ /* A4 Tech 4D/4D+ Mouse */
+ { "A4W0005", MOUSE_PROTO_INTELLI, MOUSE_MODEL_4D },
+ /* 8D Scroll Mouse */
+ { "PEC9802", MOUSE_PROTO_INTELLI, MOUSE_MODEL_INTELLI },
+ /* Mitsumi Wireless Scroll Mouse */
+ { "MTM6401", MOUSE_PROTO_INTELLI, MOUSE_MODEL_INTELLI },
+
+ /* MS bus */
+ { "PNP0F00", MOUSE_PROTO_BUS, MOUSE_MODEL_GENERIC },
+ /* MS serial */
+ { "PNP0F01", MOUSE_PROTO_MS, MOUSE_MODEL_GENERIC },
+ /* MS InPort */
+ { "PNP0F02", MOUSE_PROTO_INPORT, MOUSE_MODEL_GENERIC },
+ /* MS PS/2 */
+ { "PNP0F03", MOUSE_PROTO_PS2, MOUSE_MODEL_GENERIC },
+ /*
+ * EzScroll returns PNP0F04 in the compatible device field; but it
+ * doesn't look compatible... XXX
+ */
+ /* MouseSystems */
+ { "PNP0F04", MOUSE_PROTO_MSC, MOUSE_MODEL_GENERIC },
+ /* MouseSystems */
+ { "PNP0F05", MOUSE_PROTO_MSC, MOUSE_MODEL_GENERIC },
+#if notyet
+ /* Genius Mouse */
+ { "PNP0F06", MOUSE_PROTO_XXX, MOUSE_MODEL_GENERIC },
+ /* Genius Mouse */
+ { "PNP0F07", MOUSE_PROTO_XXX, MOUSE_MODEL_GENERIC },
+#endif
+ /* Logitech serial */
+ { "PNP0F08", MOUSE_PROTO_LOGIMOUSEMAN, MOUSE_MODEL_GENERIC },
+ /* MS BallPoint serial */
+ { "PNP0F09", MOUSE_PROTO_MS, MOUSE_MODEL_GENERIC },
+ /* MS PnP serial */
+ { "PNP0F0A", MOUSE_PROTO_MS, MOUSE_MODEL_GENERIC },
+ /* MS PnP BallPoint serial */
+ { "PNP0F0B", MOUSE_PROTO_MS, MOUSE_MODEL_GENERIC },
+ /* MS serial comatible */
+ { "PNP0F0C", MOUSE_PROTO_MS, MOUSE_MODEL_GENERIC },
+ /* MS InPort comatible */
+ { "PNP0F0D", MOUSE_PROTO_INPORT, MOUSE_MODEL_GENERIC },
+ /* MS PS/2 comatible */
+ { "PNP0F0E", MOUSE_PROTO_PS2, MOUSE_MODEL_GENERIC },
+ /* MS BallPoint comatible */
+ { "PNP0F0F", MOUSE_PROTO_MS, MOUSE_MODEL_GENERIC },
+#if notyet
+ /* TI QuickPort */
+ { "PNP0F10", MOUSE_PROTO_XXX, MOUSE_MODEL_GENERIC },
+#endif
+ /* MS bus comatible */
+ { "PNP0F11", MOUSE_PROTO_BUS, MOUSE_MODEL_GENERIC },
+ /* Logitech PS/2 */
+ { "PNP0F12", MOUSE_PROTO_PS2, MOUSE_MODEL_GENERIC },
+ /* PS/2 */
+ { "PNP0F13", MOUSE_PROTO_PS2, MOUSE_MODEL_GENERIC },
+#if notyet
+ /* MS Kids Mouse */
+ { "PNP0F14", MOUSE_PROTO_XXX, MOUSE_MODEL_GENERIC },
+#endif
+ /* Logitech bus */
+ { "PNP0F15", MOUSE_PROTO_BUS, MOUSE_MODEL_GENERIC },
+#if notyet
+ /* Logitech SWIFT */
+ { "PNP0F16", MOUSE_PROTO_XXX, MOUSE_MODEL_GENERIC },
+#endif
+ /* Logitech serial compat */
+ { "PNP0F17", MOUSE_PROTO_LOGIMOUSEMAN, MOUSE_MODEL_GENERIC },
+ /* Logitech bus compatible */
+ { "PNP0F18", MOUSE_PROTO_BUS, MOUSE_MODEL_GENERIC },
+ /* Logitech PS/2 compatible */
+ { "PNP0F19", MOUSE_PROTO_PS2, MOUSE_MODEL_GENERIC },
+#if notyet
+ /* Logitech SWIFT compatible */
+ { "PNP0F1A", MOUSE_PROTO_XXX, MOUSE_MODEL_GENERIC },
+ /* HP Omnibook */
+ { "PNP0F1B", MOUSE_PROTO_XXX, MOUSE_MODEL_GENERIC },
+ /* Compaq LTE TrackBall PS/2 */
+ { "PNP0F1C", MOUSE_PROTO_XXX, MOUSE_MODEL_GENERIC },
+ /* Compaq LTE TrackBall serial */
+ { "PNP0F1D", MOUSE_PROTO_XXX, MOUSE_MODEL_GENERIC },
+ /* MS Kidts Trackball */
+ { "PNP0F1E", MOUSE_PROTO_XXX, MOUSE_MODEL_GENERIC },
+#endif
+ /* Interlink VersaPad */
+ { "LNK0001", MOUSE_PROTO_VERSAPAD, MOUSE_MODEL_VERSAPAD },
+
+ { NULL, MOUSE_PROTO_UNKNOWN, MOUSE_MODEL_GENERIC },
+};
+
+/* the table must be ordered by MOUSE_PROTO_XXX in mouse.h */
+static unsigned short rodentcflags[] =
+{
+ (CS7 | CREAD | CLOCAL | HUPCL), /* MicroSoft */
+ (CS8 | CSTOPB | CREAD | CLOCAL | HUPCL), /* MouseSystems */
+ (CS8 | CSTOPB | CREAD | CLOCAL | HUPCL), /* Logitech */
+ (CS8 | PARENB | PARODD | CREAD | CLOCAL | HUPCL), /* MMSeries */
+ (CS7 | CREAD | CLOCAL | HUPCL), /* MouseMan */
+ 0, /* Bus */
+ 0, /* InPort */
+ 0, /* PS/2 */
+ (CS8 | CREAD | CLOCAL | HUPCL), /* MM HitTablet */
+ (CS7 | CREAD | CLOCAL | HUPCL), /* GlidePoint */
+ (CS7 | CREAD | CLOCAL | HUPCL), /* IntelliMouse */
+ (CS7 | CREAD | CLOCAL | HUPCL), /* Thinking Mouse */
+ (CS8 | CSTOPB | CREAD | CLOCAL | HUPCL), /* sysmouse */
+ (CS7 | CREAD | CLOCAL | HUPCL), /* X10 MouseRemote */
+ (CS8 | PARENB | PARODD | CREAD | CLOCAL | HUPCL), /* kidspad etc. */
+ (CS8 | CREAD | CLOCAL | HUPCL), /* VersaPad */
+ 0, /* JogDial */
+#if notyet
+ (CS8 | CSTOPB | CREAD | CLOCAL | HUPCL), /* Mariqua */
+#endif
+ (CS8 | CREAD | HUPCL ), /* GTCO Digi-Pad */
+};
+
+static struct rodentparam {
+ int flags;
+ const char *portname; /* /dev/XXX */
+ int rtype; /* MOUSE_PROTO_XXX */
+ int level; /* operation level: 0 or greater */
+ int baudrate;
+ int rate; /* report rate */
+ int resolution; /* MOUSE_RES_XXX or a positive number */
+ int zmap[4]; /* MOUSE_{X|Y}AXIS or a button number */
+ int wmode; /* wheel mode button number */
+ int mfd; /* mouse file descriptor */
+ int cfd; /* /dev/consolectl file descriptor */
+ int mremsfd; /* mouse remote server file descriptor */
+ int mremcfd; /* mouse remote client file descriptor */
+ int is_removable; /* set if device is removable, like USB */
+ long clickthreshold; /* double click speed in msec */
+ long button2timeout; /* 3 button emulation timeout */
+ mousehw_t hw; /* mouse device hardware information */
+ mousemode_t mode; /* protocol information */
+ float accelx; /* Acceleration in the X axis */
+ float accely; /* Acceleration in the Y axis */
+ float expoaccel; /* Exponential acceleration */
+ float expoffset; /* Movement offset for exponential accel. */
+ float remainx; /* Remainder on X and Y axis, respectively... */
+ float remainy; /* ... to compensate for rounding errors. */
+ int scrollthreshold; /* Movement distance before virtual scrolling */
+ int scrollspeed; /* Movement distance to rate of scrolling */
+} rodent = {
+ .flags = 0,
+ .portname = NULL,
+ .rtype = MOUSE_PROTO_UNKNOWN,
+ .level = -1,
+ .baudrate = 1200,
+ .rate = 0,
+ .resolution = MOUSE_RES_UNKNOWN,
+ .zmap = { 0, 0, 0, 0 },
+ .wmode = 0,
+ .mfd = -1,
+ .cfd = -1,
+ .mremsfd = -1,
+ .mremcfd = -1,
+ .is_removable = 0,
+ .clickthreshold = DFLT_CLICKTHRESHOLD,
+ .button2timeout = DFLT_BUTTON2TIMEOUT,
+ .accelx = 1.0,
+ .accely = 1.0,
+ .expoaccel = 1.0,
+ .expoffset = 1.0,
+ .remainx = 0.0,
+ .remainy = 0.0,
+ .scrollthreshold = DFLT_SCROLLTHRESHOLD,
+ .scrollspeed = DFLT_SCROLLSPEED,
+};
+
+/* button status */
+struct button_state {
+ int count; /* 0: up, 1: single click, 2: double click,... */
+ struct timespec ts; /* timestamp on the last button event */
+};
+static struct button_state bstate[MOUSE_MAXBUTTON]; /* button state */
+static struct button_state *mstate[MOUSE_MAXBUTTON];/* mapped button st.*/
+static struct button_state zstate[4]; /* Z/W axis state */
+
+/* state machine for 3 button emulation */
+
+#define S0 0 /* start */
+#define S1 1 /* button 1 delayed down */
+#define S2 2 /* button 3 delayed down */
+#define S3 3 /* both buttons down -> button 2 down */
+#define S4 4 /* button 1 delayed up */
+#define S5 5 /* button 1 down */
+#define S6 6 /* button 3 down */
+#define S7 7 /* both buttons down */
+#define S8 8 /* button 3 delayed up */
+#define S9 9 /* button 1 or 3 up after S3 */
+
+#define A(b1, b3) (((b1) ? 2 : 0) | ((b3) ? 1 : 0))
+#define A_TIMEOUT 4
+#define S_DELAYED(st) (states[st].s[A_TIMEOUT] != (st))
+
+static struct {
+ int s[A_TIMEOUT + 1];
+ int buttons;
+ int mask;
+ int timeout;
+} states[10] = {
+ /* S0 */
+ { { S0, S2, S1, S3, S0 }, 0, ~(MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN), FALSE },
+ /* S1 */
+ { { S4, S2, S1, S3, S5 }, 0, ~MOUSE_BUTTON1DOWN, FALSE },
+ /* S2 */
+ { { S8, S2, S1, S3, S6 }, 0, ~MOUSE_BUTTON3DOWN, FALSE },
+ /* S3 */
+ { { S0, S9, S9, S3, S3 }, MOUSE_BUTTON2DOWN, ~0, FALSE },
+ /* S4 */
+ { { S0, S2, S1, S3, S0 }, MOUSE_BUTTON1DOWN, ~0, TRUE },
+ /* S5 */
+ { { S0, S2, S5, S7, S5 }, MOUSE_BUTTON1DOWN, ~0, FALSE },
+ /* S6 */
+ { { S0, S6, S1, S7, S6 }, MOUSE_BUTTON3DOWN, ~0, FALSE },
+ /* S7 */
+ { { S0, S6, S5, S7, S7 }, MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN, ~0, FALSE },
+ /* S8 */
+ { { S0, S2, S1, S3, S0 }, MOUSE_BUTTON3DOWN, ~0, TRUE },
+ /* S9 */
+ { { S0, S9, S9, S3, S9 }, 0, ~(MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN), FALSE },
+};
+static int mouse_button_state;
+static struct timespec mouse_button_state_ts;
+static int mouse_move_delayed;
+
+static jmp_buf env;
+
+struct drift_xy {
+ int x;
+ int y;
+};
+static int drift_distance = 4; /* max steps X+Y */
+static int drift_time = 500; /* in 0.5 sec */
+static struct timespec drift_time_ts;
+static struct timespec drift_2time_ts; /* 2*drift_time */
+static int drift_after = 4000; /* 4 sec */
+static struct timespec drift_after_ts;
+static int drift_terminate = FALSE;
+static struct timespec drift_current_ts;
+static struct timespec drift_tmp;
+static struct timespec drift_last_activity = {0, 0};
+static struct timespec drift_since = {0, 0};
+static struct drift_xy drift_last = {0, 0}; /* steps in last drift_time */
+static struct drift_xy drift_previous = {0, 0}; /* steps in prev. drift_time */
+
+/* function prototypes */
+
+static void linacc(int, int, int*, int*);
+static void expoacc(int, int, int*, int*);
+static void moused(void);
+static void hup(int sig);
+static void cleanup(int sig);
+static void pause_mouse(int sig);
+static void usage(void);
+static void log_or_warn(int log_pri, int errnum, const char *fmt, ...)
+ __printflike(3, 4);
+
+static int r_identify(void);
+static const char *r_if(int type);
+static const char *r_name(int type);
+static const char *r_model(int model);
+static void r_init(void);
+static int r_protocol(u_char b, mousestatus_t *act);
+static int r_statetrans(mousestatus_t *a1, mousestatus_t *a2, int trans);
+static int r_installmap(char *arg);
+static void r_map(mousestatus_t *act1, mousestatus_t *act2);
+static void r_timestamp(mousestatus_t *act);
+static int r_timeout(void);
+static void r_click(mousestatus_t *act);
+static void setmousespeed(int old, int new, unsigned cflag);
+
+static int pnpwakeup1(void);
+static int pnpwakeup2(void);
+static int pnpgets(char *buf);
+static int pnpparse(pnpid_t *id, char *buf, int len);
+static symtab_t *pnpproto(pnpid_t *id);
+
+static symtab_t *gettoken(symtab_t *tab, const char *s, int len);
+static const char *gettokenname(symtab_t *tab, int val);
+
+static void mremote_serversetup(void);
+static void mremote_clientchg(int add);
+
+static int kidspad(u_char rxc, mousestatus_t *act);
+static int gtco_digipad(u_char, mousestatus_t *);
+
+int
+main(int argc, char *argv[])
+{
+ int c;
+ int i;
+ int j;
+
+ for (i = 0; i < MOUSE_MAXBUTTON; ++i)
+ mstate[i] = &bstate[i];
+
+ while ((c = getopt(argc, argv, "3A:C:DE:F:HI:L:PRS:T:VU:a:cdfhi:l:m:p:r:st:w:z:")) != -1)
+ switch(c) {
+
+ case '3':
+ rodent.flags |= Emulate3Button;
+ break;
+
+ case 'E':
+ rodent.button2timeout = atoi(optarg);
+ if ((rodent.button2timeout < 0) ||
+ (rodent.button2timeout > MAX_BUTTON2TIMEOUT)) {
+ warnx("invalid argument `%s'", optarg);
+ usage();
+ }
+ break;
+
+ case 'a':
+ i = sscanf(optarg, "%f,%f", &rodent.accelx, &rodent.accely);
+ if (i == 0) {
+ warnx("invalid linear acceleration argument '%s'", optarg);
+ usage();
+ }
+
+ if (i == 1)
+ rodent.accely = rodent.accelx;
+
+ break;
+
+ case 'A':
+ rodent.flags |= ExponentialAcc;
+ i = sscanf(optarg, "%f,%f", &rodent.expoaccel, &rodent.expoffset);
+ if (i == 0) {
+ warnx("invalid exponential acceleration argument '%s'", optarg);
+ usage();
+ }
+
+ if (i == 1)
+ rodent.expoffset = 1.0;
+
+ break;
+
+ case 'c':
+ rodent.flags |= ChordMiddle;
+ break;
+
+ case 'd':
+ ++debug;
+ break;
+
+ case 'f':
+ nodaemon = TRUE;
+ break;
+
+ case 'i':
+ if (strcmp(optarg, "all") == 0)
+ identify = ID_ALL;
+ else if (strcmp(optarg, "port") == 0)
+ identify = ID_PORT;
+ else if (strcmp(optarg, "if") == 0)
+ identify = ID_IF;
+ else if (strcmp(optarg, "type") == 0)
+ identify = ID_TYPE;
+ else if (strcmp(optarg, "model") == 0)
+ identify = ID_MODEL;
+ else {
+ warnx("invalid argument `%s'", optarg);
+ usage();
+ }
+ nodaemon = TRUE;
+ break;
+
+ case 'l':
+ rodent.level = atoi(optarg);
+ if ((rodent.level < 0) || (rodent.level > 4)) {
+ warnx("invalid argument `%s'", optarg);
+ usage();
+ }
+ break;
+
+ case 'm':
+ if (!r_installmap(optarg)) {
+ warnx("invalid argument `%s'", optarg);
+ usage();
+ }
+ break;
+
+ case 'p':
+ rodent.portname = optarg;
+ break;
+
+ case 'r':
+ if (strcmp(optarg, "high") == 0)
+ rodent.resolution = MOUSE_RES_HIGH;
+ else if (strcmp(optarg, "medium-high") == 0)
+ rodent.resolution = MOUSE_RES_HIGH;
+ else if (strcmp(optarg, "medium-low") == 0)
+ rodent.resolution = MOUSE_RES_MEDIUMLOW;
+ else if (strcmp(optarg, "low") == 0)
+ rodent.resolution = MOUSE_RES_LOW;
+ else if (strcmp(optarg, "default") == 0)
+ rodent.resolution = MOUSE_RES_DEFAULT;
+ else {
+ rodent.resolution = atoi(optarg);
+ if (rodent.resolution <= 0) {
+ warnx("invalid argument `%s'", optarg);
+ usage();
+ }
+ }
+ break;
+
+ case 's':
+ rodent.baudrate = 9600;
+ break;
+
+ case 'w':
+ i = atoi(optarg);
+ if ((i <= 0) || (i > MOUSE_MAXBUTTON)) {
+ warnx("invalid argument `%s'", optarg);
+ usage();
+ }
+ rodent.wmode = 1 << (i - 1);
+ break;
+
+ case 'z':
+ if (strcmp(optarg, "x") == 0)
+ rodent.zmap[0] = MOUSE_XAXIS;
+ else if (strcmp(optarg, "y") == 0)
+ rodent.zmap[0] = MOUSE_YAXIS;
+ else {
+ i = atoi(optarg);
+ /*
+ * Use button i for negative Z axis movement and
+ * button (i + 1) for positive Z axis movement.
+ */
+ if ((i <= 0) || (i > MOUSE_MAXBUTTON - 1)) {
+ warnx("invalid argument `%s'", optarg);
+ usage();
+ }
+ rodent.zmap[0] = i;
+ rodent.zmap[1] = i + 1;
+ debug("optind: %d, optarg: '%s'", optind, optarg);
+ for (j = 1; j < 4; ++j) {
+ if ((optind >= argc) || !isdigit(*argv[optind]))
+ break;
+ i = atoi(argv[optind]);
+ if ((i <= 0) || (i > MOUSE_MAXBUTTON - 1)) {
+ warnx("invalid argument `%s'", argv[optind]);
+ usage();
+ }
+ rodent.zmap[j] = i;
+ ++optind;
+ }
+ if ((rodent.zmap[2] != 0) && (rodent.zmap[3] == 0))
+ rodent.zmap[3] = rodent.zmap[2] + 1;
+ }
+ break;
+
+ case 'C':
+ rodent.clickthreshold = atoi(optarg);
+ if ((rodent.clickthreshold < 0) ||
+ (rodent.clickthreshold > MAX_CLICKTHRESHOLD)) {
+ warnx("invalid argument `%s'", optarg);
+ usage();
+ }
+ break;
+
+ case 'D':
+ rodent.flags |= ClearDTR;
+ break;
+
+ case 'F':
+ rodent.rate = atoi(optarg);
+ if (rodent.rate <= 0) {
+ warnx("invalid argument `%s'", optarg);
+ usage();
+ }
+ break;
+
+ case 'H':
+ rodent.flags |= HVirtualScroll;
+ break;
+
+ case 'I':
+ pidfile = optarg;
+ break;
+
+ case 'L':
+ rodent.scrollspeed = atoi(optarg);
+ if (rodent.scrollspeed < 0) {
+ warnx("invalid argument `%s'", optarg);
+ usage();
+ }
+ break;
+
+ case 'P':
+ rodent.flags |= NoPnP;
+ break;
+
+ case 'R':
+ rodent.flags |= ClearRTS;
+ break;
+
+ case 'S':
+ rodent.baudrate = atoi(optarg);
+ if (rodent.baudrate <= 0) {
+ warnx("invalid argument `%s'", optarg);
+ usage();
+ }
+ debug("rodent baudrate %d", rodent.baudrate);
+ break;
+
+ case 'T':
+ drift_terminate = TRUE;
+ sscanf(optarg, "%d,%d,%d", &drift_distance, &drift_time,
+ &drift_after);
+ if (drift_distance <= 0 || drift_time <= 0 || drift_after <= 0) {
+ warnx("invalid argument `%s'", optarg);
+ usage();
+ }
+ debug("terminate drift: distance %d, time %d, after %d",
+ drift_distance, drift_time, drift_after);
+ drift_time_ts.tv_sec = drift_time / 1000;
+ drift_time_ts.tv_nsec = (drift_time % 1000) * 1000000;
+ drift_2time_ts.tv_sec = (drift_time *= 2) / 1000;
+ drift_2time_ts.tv_nsec = (drift_time % 1000) * 1000000;
+ drift_after_ts.tv_sec = drift_after / 1000;
+ drift_after_ts.tv_nsec = (drift_after % 1000) * 1000000;
+ break;
+
+ case 't':
+ if (strcmp(optarg, "auto") == 0) {
+ rodent.rtype = MOUSE_PROTO_UNKNOWN;
+ rodent.flags &= ~NoPnP;
+ rodent.level = -1;
+ break;
+ }
+ for (i = 0; rnames[i] != NULL; i++)
+ if (strcmp(optarg, rnames[i]) == 0) {
+ rodent.rtype = i;
+ rodent.flags |= NoPnP;
+ rodent.level = (i == MOUSE_PROTO_SYSMOUSE) ? 1 : 0;
+ break;
+ }
+ if (rnames[i] == NULL) {
+ warnx("no such mouse type `%s'", optarg);
+ usage();
+ }
+ break;
+
+ case 'V':
+ rodent.flags |= VirtualScroll;
+ break;
+ case 'U':
+ rodent.scrollthreshold = atoi(optarg);
+ if (rodent.scrollthreshold < 0) {
+ warnx("invalid argument `%s'", optarg);
+ usage();
+ }
+ break;
+
+ case 'h':
+ case '?':
+ default:
+ usage();
+ }
+
+ /* fix Z axis mapping */
+ for (i = 0; i < 4; ++i) {
+ if (rodent.zmap[i] > 0) {
+ for (j = 0; j < MOUSE_MAXBUTTON; ++j) {
+ if (mstate[j] == &bstate[rodent.zmap[i] - 1])
+ mstate[j] = &zstate[i];
+ }
+ rodent.zmap[i] = 1 << (rodent.zmap[i] - 1);
+ }
+ }
+
+ /* the default port name */
+ switch(rodent.rtype) {
+
+ case MOUSE_PROTO_INPORT:
+ /* INPORT and BUS are the same... */
+ rodent.rtype = MOUSE_PROTO_BUS;
+ /* FALLTHROUGH */
+ case MOUSE_PROTO_BUS:
+ if (!rodent.portname)
+ rodent.portname = "/dev/mse0";
+ break;
+
+ case MOUSE_PROTO_PS2:
+ if (!rodent.portname)
+ rodent.portname = "/dev/psm0";
+ break;
+
+ default:
+ if (rodent.portname)
+ break;
+ warnx("no port name specified");
+ usage();
+ }
+
+ if (strncmp(rodent.portname, "/dev/ums", 8) == 0)
+ rodent.is_removable = 1;
+
+ for (;;) {
+ if (setjmp(env) == 0) {
+ signal(SIGHUP, hup);
+ signal(SIGINT , cleanup);
+ signal(SIGQUIT, cleanup);
+ signal(SIGTERM, cleanup);
+ signal(SIGUSR1, pause_mouse);
+
+ rodent.mfd = open(rodent.portname, O_RDWR | O_NONBLOCK);
+ if (rodent.mfd == -1)
+ logerr(1, "unable to open %s", rodent.portname);
+ if (r_identify() == MOUSE_PROTO_UNKNOWN) {
+ logwarnx("cannot determine mouse type on %s", rodent.portname);
+ close(rodent.mfd);
+ rodent.mfd = -1;
+ }
+
+ /* print some information */
+ if (identify != ID_NONE) {
+ if (identify == ID_ALL)
+ printf("%s %s %s %s\n",
+ rodent.portname, r_if(rodent.hw.iftype),
+ r_name(rodent.rtype), r_model(rodent.hw.model));
+ else if (identify & ID_PORT)
+ printf("%s\n", rodent.portname);
+ else if (identify & ID_IF)
+ printf("%s\n", r_if(rodent.hw.iftype));
+ else if (identify & ID_TYPE)
+ printf("%s\n", r_name(rodent.rtype));
+ else if (identify & ID_MODEL)
+ printf("%s\n", r_model(rodent.hw.model));
+ exit(0);
+ } else {
+ debug("port: %s interface: %s type: %s model: %s",
+ rodent.portname, r_if(rodent.hw.iftype),
+ r_name(rodent.rtype), r_model(rodent.hw.model));
+ }
+
+ if (rodent.mfd == -1) {
+ /*
+ * We cannot continue because of error. Exit if the
+ * program has not become a daemon. Otherwise, block
+ * until the user corrects the problem and issues SIGHUP.
+ */
+ if (!background)
+ exit(1);
+ sigpause(0);
+ }
+
+ r_init(); /* call init function */
+ moused();
+ }
+
+ if (rodent.mfd != -1)
+ close(rodent.mfd);
+ if (rodent.cfd != -1)
+ close(rodent.cfd);
+ rodent.mfd = rodent.cfd = -1;
+ if (rodent.is_removable)
+ exit(0);
+ }
+ /* NOT REACHED */
+
+ exit(0);
+}
+
+/*
+ * Function to calculate linear acceleration.
+ *
+ * If there are any rounding errors, the remainder
+ * is stored in the remainx and remainy variables
+ * and taken into account upon the next movement.
+ */
+
+static void
+linacc(int dx, int dy, int *movex, int *movey)
+{
+ float fdx, fdy;
+
+ if (dx == 0 && dy == 0) {
+ *movex = *movey = 0;
+ return;
+ }
+ fdx = dx * rodent.accelx + rodent.remainx;
+ fdy = dy * rodent.accely + rodent.remainy;
+ *movex = lround(fdx);
+ *movey = lround(fdy);
+ rodent.remainx = fdx - *movex;
+ rodent.remainy = fdy - *movey;
+}
+
+/*
+ * Function to calculate exponential acceleration.
+ * (Also includes linear acceleration if enabled.)
+ *
+ * In order to give a smoother behaviour, we record the four
+ * most recent non-zero movements and use their average value
+ * to calculate the acceleration.
+ */
+
+static void
+expoacc(int dx, int dy, int *movex, int *movey)
+{
+ static float lastlength[3] = {0.0, 0.0, 0.0};
+ float fdx, fdy, length, lbase, accel;
+
+ if (dx == 0 && dy == 0) {
+ *movex = *movey = 0;
+ return;
+ }
+ fdx = dx * rodent.accelx;
+ fdy = dy * rodent.accely;
+ length = sqrtf((fdx * fdx) + (fdy * fdy)); /* Pythagoras */
+ length = (length + lastlength[0] + lastlength[1] + lastlength[2]) / 4;
+ lbase = length / rodent.expoffset;
+ accel = powf(lbase, rodent.expoaccel) / lbase;
+ fdx = fdx * accel + rodent.remainx;
+ fdy = fdy * accel + rodent.remainy;
+ *movex = lroundf(fdx);
+ *movey = lroundf(fdy);
+ rodent.remainx = fdx - *movex;
+ rodent.remainy = fdy - *movey;
+ lastlength[2] = lastlength[1];
+ lastlength[1] = lastlength[0];
+ lastlength[0] = length; /* Insert new average, not original length! */
+}
+
+static void
+moused(void)
+{
+ struct mouse_info mouse;
+ mousestatus_t action0; /* original mouse action */
+ mousestatus_t action; /* interim buffer */
+ mousestatus_t action2; /* mapped action */
+ struct timeval timeout;
+ fd_set fds;
+ u_char b;
+ pid_t mpid;
+ int flags;
+ int c;
+ int i;
+
+ if ((rodent.cfd = open("/dev/consolectl", O_RDWR, 0)) == -1)
+ logerr(1, "cannot open /dev/consolectl");
+
+ if (!nodaemon && !background) {
+ pfh = pidfile_open(pidfile, 0600, &mpid);
+ if (pfh == NULL) {
+ if (errno == EEXIST)
+ logerrx(1, "moused already running, pid: %d", mpid);
+ logwarn("cannot open pid file");
+ }
+ if (daemon(0, 0)) {
+ int saved_errno = errno;
+ pidfile_remove(pfh);
+ errno = saved_errno;
+ logerr(1, "failed to become a daemon");
+ } else {
+ background = TRUE;
+ pidfile_write(pfh);
+ }
+ }
+
+ /* clear mouse data */
+ bzero(&action0, sizeof(action0));
+ bzero(&action, sizeof(action));
+ bzero(&action2, sizeof(action2));
+ bzero(&mouse, sizeof(mouse));
+ mouse_button_state = S0;
+ clock_gettime(CLOCK_MONOTONIC_FAST, &mouse_button_state_ts);
+ mouse_move_delayed = 0;
+ for (i = 0; i < MOUSE_MAXBUTTON; ++i) {
+ bstate[i].count = 0;
+ bstate[i].ts = mouse_button_state_ts;
+ }
+ for (i = 0; i < (int)(sizeof(zstate) / sizeof(zstate[0])); ++i) {
+ zstate[i].count = 0;
+ zstate[i].ts = mouse_button_state_ts;
+ }
+
+ /* choose which ioctl command to use */
+ mouse.operation = MOUSE_MOTION_EVENT;
+ extioctl = (ioctl(rodent.cfd, CONS_MOUSECTL, &mouse) == 0);
+
+ /* process mouse data */
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 20000; /* 20 msec */
+ for (;;) {
+
+ FD_ZERO(&fds);
+ FD_SET(rodent.mfd, &fds);
+ if (rodent.mremsfd >= 0)
+ FD_SET(rodent.mremsfd, &fds);
+ if (rodent.mremcfd >= 0)
+ FD_SET(rodent.mremcfd, &fds);
+
+ c = select(FD_SETSIZE, &fds, NULL, NULL,
+ ((rodent.flags & Emulate3Button) &&
+ S_DELAYED(mouse_button_state)) ? &timeout : NULL);
+ if (c < 0) { /* error */
+ logwarn("failed to read from mouse");
+ continue;
+ } else if (c == 0) { /* timeout */
+ /* assert(rodent.flags & Emulate3Button) */
+ action0.button = action0.obutton;
+ action0.dx = action0.dy = action0.dz = 0;
+ action0.flags = flags = 0;
+ if (r_timeout() && r_statetrans(&action0, &action, A_TIMEOUT)) {
+ if (debug > 2)
+ debug("flags:%08x buttons:%08x obuttons:%08x",
+ action.flags, action.button, action.obutton);
+ } else {
+ action0.obutton = action0.button;
+ continue;
+ }
+ } else {
+ /* MouseRemote client connect/disconnect */
+ if ((rodent.mremsfd >= 0) && FD_ISSET(rodent.mremsfd, &fds)) {
+ mremote_clientchg(TRUE);
+ continue;
+ }
+ if ((rodent.mremcfd >= 0) && FD_ISSET(rodent.mremcfd, &fds)) {
+ mremote_clientchg(FALSE);
+ continue;
+ }
+ /* mouse movement */
+ if (read(rodent.mfd, &b, 1) == -1) {
+ if (errno == EWOULDBLOCK)
+ continue;
+ else
+ return;
+ }
+ if ((flags = r_protocol(b, &action0)) == 0)
+ continue;
+
+ if ((rodent.flags & VirtualScroll) || (rodent.flags & HVirtualScroll)) {
+ /* Allow middle button drags to scroll up and down */
+ if (action0.button == MOUSE_BUTTON2DOWN) {
+ if (scroll_state == SCROLL_NOTSCROLLING) {
+ scroll_state = SCROLL_PREPARE;
+ scroll_movement = hscroll_movement = 0;
+ debug("PREPARING TO SCROLL");
+ }
+ debug("[BUTTON2] flags:%08x buttons:%08x obuttons:%08x",
+ action.flags, action.button, action.obutton);
+ } else {
+ debug("[NOTBUTTON2] flags:%08x buttons:%08x obuttons:%08x",
+ action.flags, action.button, action.obutton);
+
+ /* This isn't a middle button down... move along... */
+ if (scroll_state == SCROLL_SCROLLING) {
+ /*
+ * We were scrolling, someone let go of button 2.
+ * Now turn autoscroll off.
+ */
+ scroll_state = SCROLL_NOTSCROLLING;
+ debug("DONE WITH SCROLLING / %d", scroll_state);
+ } else if (scroll_state == SCROLL_PREPARE) {
+ mousestatus_t newaction = action0;
+
+ /* We were preparing to scroll, but we never moved... */
+ r_timestamp(&action0);
+ r_statetrans(&action0, &newaction,
+ A(newaction.button & MOUSE_BUTTON1DOWN,
+ action0.button & MOUSE_BUTTON3DOWN));
+
+ /* Send middle down */
+ newaction.button = MOUSE_BUTTON2DOWN;
+ r_click(&newaction);
+
+ /* Send middle up */
+ r_timestamp(&newaction);
+ newaction.obutton = newaction.button;
+ newaction.button = action0.button;
+ r_click(&newaction);
+ }
+ }
+ }
+
+ r_timestamp(&action0);
+ r_statetrans(&action0, &action,
+ A(action0.button & MOUSE_BUTTON1DOWN,
+ action0.button & MOUSE_BUTTON3DOWN));
+ debug("flags:%08x buttons:%08x obuttons:%08x", action.flags,
+ action.button, action.obutton);
+ }
+ action0.obutton = action0.button;
+ flags &= MOUSE_POSCHANGED;
+ flags |= action.obutton ^ action.button;
+ action.flags = flags;
+
+ if (flags) { /* handler detected action */
+ r_map(&action, &action2);
+ debug("activity : buttons 0x%08x dx %d dy %d dz %d",
+ action2.button, action2.dx, action2.dy, action2.dz);
+
+ if ((rodent.flags & VirtualScroll) || (rodent.flags & HVirtualScroll)) {
+ /*
+ * If *only* the middle button is pressed AND we are moving
+ * the stick/trackpoint/nipple, scroll!
+ */
+ if (scroll_state == SCROLL_PREPARE) {
+ /* Middle button down, waiting for movement threshold */
+ if (action2.dy || action2.dx) {
+ if (rodent.flags & VirtualScroll) {
+ scroll_movement += action2.dy;
+ if (scroll_movement < -rodent.scrollthreshold) {
+ scroll_state = SCROLL_SCROLLING;
+ } else if (scroll_movement > rodent.scrollthreshold) {
+ scroll_state = SCROLL_SCROLLING;
+ }
+ }
+ if (rodent.flags & HVirtualScroll) {
+ hscroll_movement += action2.dx;
+ if (hscroll_movement < -rodent.scrollthreshold) {
+ scroll_state = SCROLL_SCROLLING;
+ } else if (hscroll_movement > rodent.scrollthreshold) {
+ scroll_state = SCROLL_SCROLLING;
+ }
+ }
+ if (scroll_state == SCROLL_SCROLLING) scroll_movement = hscroll_movement = 0;
+ }
+ } else if (scroll_state == SCROLL_SCROLLING) {
+ if (rodent.flags & VirtualScroll) {
+ scroll_movement += action2.dy;
+ debug("SCROLL: %d", scroll_movement);
+ if (scroll_movement < -rodent.scrollspeed) {
+ /* Scroll down */
+ action2.dz = -1;
+ scroll_movement = 0;
+ }
+ else if (scroll_movement > rodent.scrollspeed) {
+ /* Scroll up */
+ action2.dz = 1;
+ scroll_movement = 0;
+ }
+ }
+ if (rodent.flags & HVirtualScroll) {
+ hscroll_movement += action2.dx;
+ debug("HORIZONTAL SCROLL: %d", hscroll_movement);
+
+ if (hscroll_movement < -rodent.scrollspeed) {
+ action2.dz = -2;
+ hscroll_movement = 0;
+ }
+ else if (hscroll_movement > rodent.scrollspeed) {
+ action2.dz = 2;
+ hscroll_movement = 0;
+ }
+ }
+
+ /* Don't move while scrolling */
+ action2.dx = action2.dy = 0;
+ }
+ }
+
+ if (drift_terminate) {
+ if ((flags & MOUSE_POSCHANGED) == 0 || action.dz || action2.dz)
+ drift_last_activity = drift_current_ts;
+ else {
+ /* X or/and Y movement only - possibly drift */
+ tssub(&drift_current_ts, &drift_last_activity, &drift_tmp);
+ if (tscmp(&drift_tmp, &drift_after_ts, >)) {
+ tssub(&drift_current_ts, &drift_since, &drift_tmp);
+ if (tscmp(&drift_tmp, &drift_time_ts, <)) {
+ drift_last.x += action2.dx;
+ drift_last.y += action2.dy;
+ } else {
+ /* discard old accumulated steps (drift) */
+ if (tscmp(&drift_tmp, &drift_2time_ts, >))
+ drift_previous.x = drift_previous.y = 0;
+ else
+ drift_previous = drift_last;
+ drift_last.x = action2.dx;
+ drift_last.y = action2.dy;
+ drift_since = drift_current_ts;
+ }
+ if (abs(drift_last.x) + abs(drift_last.y)
+ > drift_distance) {
+ /* real movement, pass all accumulated steps */
+ action2.dx = drift_previous.x + drift_last.x;
+ action2.dy = drift_previous.y + drift_last.y;
+ /* and reset accumulators */
+ tsclr(&drift_since);
+ drift_last.x = drift_last.y = 0;
+ /* drift_previous will be cleared at next movement*/
+ drift_last_activity = drift_current_ts;
+ } else {
+ continue; /* don't pass current movement to
+ * console driver */
+ }
+ }
+ }
+ }
+
+ if (extioctl) {
+ /* Defer clicks until we aren't VirtualScroll'ing. */
+ if (scroll_state == SCROLL_NOTSCROLLING)
+ r_click(&action2);
+
+ if (action2.flags & MOUSE_POSCHANGED) {
+ mouse.operation = MOUSE_MOTION_EVENT;
+ mouse.u.data.buttons = action2.button;
+ if (rodent.flags & ExponentialAcc) {
+ expoacc(action2.dx, action2.dy,
+ &mouse.u.data.x, &mouse.u.data.y);
+ }
+ else {
+ linacc(action2.dx, action2.dy,
+ &mouse.u.data.x, &mouse.u.data.y);
+ }
+ mouse.u.data.z = action2.dz;
+ if (debug < 2)
+ if (!paused)
+ ioctl(rodent.cfd, CONS_MOUSECTL, &mouse);
+ }
+ } else {
+ mouse.operation = MOUSE_ACTION;
+ mouse.u.data.buttons = action2.button;
+ if (rodent.flags & ExponentialAcc) {
+ expoacc(action2.dx, action2.dy,
+ &mouse.u.data.x, &mouse.u.data.y);
+ }
+ else {
+ linacc(action2.dx, action2.dy,
+ &mouse.u.data.x, &mouse.u.data.y);
+ }
+ mouse.u.data.z = action2.dz;
+ if (debug < 2)
+ if (!paused)
+ ioctl(rodent.cfd, CONS_MOUSECTL, &mouse);
+ }
+
+ /*
+ * If the Z axis movement is mapped to an imaginary physical
+ * button, we need to cook up a corresponding button `up' event
+ * after sending a button `down' event.
+ */
+ if ((rodent.zmap[0] > 0) && (action.dz != 0)) {
+ action.obutton = action.button;
+ action.dx = action.dy = action.dz = 0;
+ r_map(&action, &action2);
+ debug("activity : buttons 0x%08x dx %d dy %d dz %d",
+ action2.button, action2.dx, action2.dy, action2.dz);
+
+ if (extioctl) {
+ r_click(&action2);
+ } else {
+ mouse.operation = MOUSE_ACTION;
+ mouse.u.data.buttons = action2.button;
+ mouse.u.data.x = mouse.u.data.y = mouse.u.data.z = 0;
+ if (debug < 2)
+ if (!paused)
+ ioctl(rodent.cfd, CONS_MOUSECTL, &mouse);
+ }
+ }
+ }
+ }
+ /* NOT REACHED */
+}
+
+static void
+hup(__unused int sig)
+{
+ longjmp(env, 1);
+}
+
+static void
+cleanup(__unused int sig)
+{
+ if (rodent.rtype == MOUSE_PROTO_X10MOUSEREM)
+ unlink(_PATH_MOUSEREMOTE);
+ exit(0);
+}
+
+static void
+pause_mouse(__unused int sig)
+{
+ paused = !paused;
+}
+
+/**
+ ** usage
+ **
+ ** Complain, and free the CPU for more worthy tasks
+ **/
+static void
+usage(void)
+{
+ fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n",
+ "usage: moused [-DRcdfs] [-I file] [-F rate] [-r resolution] [-S baudrate]",
+ " [-VH [-U threshold]] [-a X[,Y]] [-C threshold] [-m N=M] [-w N]",
+ " [-z N] [-t <mousetype>] [-l level] [-3 [-E timeout]]",
+ " [-T distance[,time[,after]]] -p <port>",
+ " moused [-d] -i <port|if|type|model|all> -p <port>");
+ exit(1);
+}
+
+/*
+ * Output an error message to syslog or stderr as appropriate. If
+ * `errnum' is non-zero, append its string form to the message.
+ */
+static void
+log_or_warn(int log_pri, int errnum, const char *fmt, ...)
+{
+ va_list ap;
+ char buf[256];
+
+ va_start(ap, fmt);
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+ if (errnum) {
+ strlcat(buf, ": ", sizeof(buf));
+ strlcat(buf, strerror(errnum), sizeof(buf));
+ }
+
+ if (background)
+ syslog(log_pri, "%s", buf);
+ else
+ warnx("%s", buf);
+}
+
+/**
+ ** Mouse interface code, courtesy of XFree86 3.1.2.
+ **
+ ** Note: Various bits have been trimmed, and in my shortsighted enthusiasm
+ ** to clean, reformat and rationalise naming, it's quite possible that
+ ** some things in here have been broken.
+ **
+ ** I hope not 8)
+ **
+ ** The following code is derived from a module marked :
+ **/
+
+/* $XConsortium: xf86_Mouse.c,v 1.2 94/10/12 20:33:21 kaleb Exp $ */
+/* $XFree86: xc/programs/Xserver/hw/xfree86/common/xf86_Mouse.c,v 3.2 1995/01/28
+ 17:03:40 dawes Exp $ */
+/*
+ *
+ * Copyright 1990,91 by Thomas Roell, Dinkelscherben, Germany.
+ * Copyright 1993 by David Dawes <dawes@physics.su.oz.au>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the names of Thomas Roell and David Dawes not be
+ * used in advertising or publicity pertaining to distribution of the
+ * software without specific, written prior permission. Thomas Roell
+ * and David Dawes makes no representations about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ *
+ * THOMAS ROELL AND DAVID DAWES DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THOMAS ROELL OR DAVID DAWES BE LIABLE FOR ANY
+ * SPECIAL, 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.
+ *
+ */
+
+/**
+ ** GlidePoint support from XFree86 3.2.
+ ** Derived from the module:
+ **/
+
+/* $XFree86: xc/programs/Xserver/hw/xfree86/common/xf86_Mouse.c,v 3.19 1996/10/16 14:40:51 dawes Exp $ */
+/* $XConsortium: xf86_Mouse.c /main/10 1996/01/30 15:16:12 kaleb $ */
+
+/* the following table must be ordered by MOUSE_PROTO_XXX in mouse.h */
+static unsigned char proto[][7] = {
+ /* hd_mask hd_id dp_mask dp_id bytes b4_mask b4_id */
+ { 0x40, 0x40, 0x40, 0x00, 3, ~0x23, 0x00 }, /* MicroSoft */
+ { 0xf8, 0x80, 0x00, 0x00, 5, 0x00, 0xff }, /* MouseSystems */
+ { 0xe0, 0x80, 0x80, 0x00, 3, 0x00, 0xff }, /* Logitech */
+ { 0xe0, 0x80, 0x80, 0x00, 3, 0x00, 0xff }, /* MMSeries */
+ { 0x40, 0x40, 0x40, 0x00, 3, ~0x33, 0x00 }, /* MouseMan */
+ { 0xf8, 0x80, 0x00, 0x00, 5, 0x00, 0xff }, /* Bus */
+ { 0xf8, 0x80, 0x00, 0x00, 5, 0x00, 0xff }, /* InPort */
+ { 0xc0, 0x00, 0x00, 0x00, 3, 0x00, 0xff }, /* PS/2 mouse */
+ { 0xe0, 0x80, 0x80, 0x00, 3, 0x00, 0xff }, /* MM HitTablet */
+ { 0x40, 0x40, 0x40, 0x00, 3, ~0x33, 0x00 }, /* GlidePoint */
+ { 0x40, 0x40, 0x40, 0x00, 3, ~0x3f, 0x00 }, /* IntelliMouse */
+ { 0x40, 0x40, 0x40, 0x00, 3, ~0x33, 0x00 }, /* ThinkingMouse */
+ { 0xf8, 0x80, 0x00, 0x00, 5, 0x00, 0xff }, /* sysmouse */
+ { 0x40, 0x40, 0x40, 0x00, 3, ~0x23, 0x00 }, /* X10 MouseRem */
+ { 0x80, 0x80, 0x00, 0x00, 5, 0x00, 0xff }, /* KIDSPAD */
+ { 0xc3, 0xc0, 0x00, 0x00, 6, 0x00, 0xff }, /* VersaPad */
+ { 0x00, 0x00, 0x00, 0x00, 1, 0x00, 0xff }, /* JogDial */
+#if notyet
+ { 0xf8, 0x80, 0x00, 0x00, 5, ~0x2f, 0x10 }, /* Mariqua */
+#endif
+};
+static unsigned char cur_proto[7];
+
+static int
+r_identify(void)
+{
+ char pnpbuf[256]; /* PnP identifier string may be up to 256 bytes long */
+ pnpid_t pnpid;
+ symtab_t *t;
+ int level;
+ int len;
+
+ /* set the driver operation level, if applicable */
+ if (rodent.level < 0)
+ rodent.level = 1;
+ ioctl(rodent.mfd, MOUSE_SETLEVEL, &rodent.level);
+ rodent.level = (ioctl(rodent.mfd, MOUSE_GETLEVEL, &level) == 0) ? level : 0;
+
+ /*
+ * Interrogate the driver and get some intelligence on the device...
+ * The following ioctl functions are not always supported by device
+ * drivers. When the driver doesn't support them, we just trust the
+ * user to supply valid information.
+ */
+ rodent.hw.iftype = MOUSE_IF_UNKNOWN;
+ rodent.hw.model = MOUSE_MODEL_GENERIC;
+ ioctl(rodent.mfd, MOUSE_GETHWINFO, &rodent.hw);
+
+ if (rodent.rtype != MOUSE_PROTO_UNKNOWN)
+ bcopy(proto[rodent.rtype], cur_proto, sizeof(cur_proto));
+ rodent.mode.protocol = MOUSE_PROTO_UNKNOWN;
+ rodent.mode.rate = -1;
+ rodent.mode.resolution = MOUSE_RES_UNKNOWN;
+ rodent.mode.accelfactor = 0;
+ rodent.mode.level = 0;
+ if (ioctl(rodent.mfd, MOUSE_GETMODE, &rodent.mode) == 0) {
+ if (rodent.mode.protocol == MOUSE_PROTO_UNKNOWN ||
+ rodent.mode.protocol >= (int)(sizeof(proto) / sizeof(proto[0]))) {
+ logwarnx("unknown mouse protocol (%d)", rodent.mode.protocol);
+ return (MOUSE_PROTO_UNKNOWN);
+ } else {
+ /* INPORT and BUS are the same... */
+ if (rodent.mode.protocol == MOUSE_PROTO_INPORT)
+ rodent.mode.protocol = MOUSE_PROTO_BUS;
+ if (rodent.mode.protocol != rodent.rtype) {
+ /* Hmm, the driver doesn't agree with the user... */
+ if (rodent.rtype != MOUSE_PROTO_UNKNOWN)
+ logwarnx("mouse type mismatch (%s != %s), %s is assumed",
+ r_name(rodent.mode.protocol), r_name(rodent.rtype),
+ r_name(rodent.mode.protocol));
+ rodent.rtype = rodent.mode.protocol;
+ bcopy(proto[rodent.rtype], cur_proto, sizeof(cur_proto));
+ }
+ }
+ cur_proto[4] = rodent.mode.packetsize;
+ cur_proto[0] = rodent.mode.syncmask[0]; /* header byte bit mask */
+ cur_proto[1] = rodent.mode.syncmask[1]; /* header bit pattern */
+ }
+
+ /* maybe this is a PnP mouse... */
+ if (rodent.mode.protocol == MOUSE_PROTO_UNKNOWN) {
+
+ if (rodent.flags & NoPnP)
+ return (rodent.rtype);
+ if (((len = pnpgets(pnpbuf)) <= 0) || !pnpparse(&pnpid, pnpbuf, len))
+ return (rodent.rtype);
+
+ debug("PnP serial mouse: '%*.*s' '%*.*s' '%*.*s'",
+ pnpid.neisaid, pnpid.neisaid, pnpid.eisaid,
+ pnpid.ncompat, pnpid.ncompat, pnpid.compat,
+ pnpid.ndescription, pnpid.ndescription, pnpid.description);
+
+ /* we have a valid PnP serial device ID */
+ rodent.hw.iftype = MOUSE_IF_SERIAL;
+ t = pnpproto(&pnpid);
+ if (t != NULL) {
+ rodent.mode.protocol = t->val;
+ rodent.hw.model = t->val2;
+ } else {
+ rodent.mode.protocol = MOUSE_PROTO_UNKNOWN;
+ }
+ if (rodent.mode.protocol == MOUSE_PROTO_INPORT)
+ rodent.mode.protocol = MOUSE_PROTO_BUS;
+
+ /* make final adjustment */
+ if (rodent.mode.protocol != MOUSE_PROTO_UNKNOWN) {
+ if (rodent.mode.protocol != rodent.rtype) {
+ /* Hmm, the device doesn't agree with the user... */
+ if (rodent.rtype != MOUSE_PROTO_UNKNOWN)
+ logwarnx("mouse type mismatch (%s != %s), %s is assumed",
+ r_name(rodent.mode.protocol), r_name(rodent.rtype),
+ r_name(rodent.mode.protocol));
+ rodent.rtype = rodent.mode.protocol;
+ bcopy(proto[rodent.rtype], cur_proto, sizeof(cur_proto));
+ }
+ }
+ }
+
+ debug("proto params: %02x %02x %02x %02x %d %02x %02x",
+ cur_proto[0], cur_proto[1], cur_proto[2], cur_proto[3],
+ cur_proto[4], cur_proto[5], cur_proto[6]);
+
+ return (rodent.rtype);
+}
+
+static const char *
+r_if(int iftype)
+{
+
+ return (gettokenname(rifs, iftype));
+}
+
+static const char *
+r_name(int type)
+{
+ const char *unknown = "unknown";
+
+ return (type == MOUSE_PROTO_UNKNOWN ||
+ type >= (int)(sizeof(rnames) / sizeof(rnames[0])) ?
+ unknown : rnames[type]);
+}
+
+static const char *
+r_model(int model)
+{
+
+ return (gettokenname(rmodels, model));
+}
+
+static void
+r_init(void)
+{
+ unsigned char buf[16]; /* scrach buffer */
+ fd_set fds;
+ const char *s;
+ char c;
+ int i;
+
+ /**
+ ** This comment is a little out of context here, but it contains
+ ** some useful information...
+ ********************************************************************
+ **
+ ** The following lines take care of the Logitech MouseMan protocols.
+ **
+ ** NOTE: There are different versions of both MouseMan and TrackMan!
+ ** Hence I add another protocol P_LOGIMAN, which the user can
+ ** specify as MouseMan in his XF86Config file. This entry was
+ ** formerly handled as a special case of P_MS. However, people
+ ** who don't have the middle button problem, can still specify
+ ** Microsoft and use P_MS.
+ **
+ ** By default, these mice should use a 3 byte Microsoft protocol
+ ** plus a 4th byte for the middle button. However, the mouse might
+ ** have switched to a different protocol before we use it, so I send
+ ** the proper sequence just in case.
+ **
+ ** NOTE: - all commands to (at least the European) MouseMan have to
+ ** be sent at 1200 Baud.
+ ** - each command starts with a '*'.
+ ** - whenever the MouseMan receives a '*', it will switch back
+ ** to 1200 Baud. Hence I have to select the desired protocol
+ ** first, then select the baud rate.
+ **
+ ** The protocols supported by the (European) MouseMan are:
+ ** - 5 byte packed binary protocol, as with the Mouse Systems
+ ** mouse. Selected by sequence "*U".
+ ** - 2 button 3 byte MicroSoft compatible protocol. Selected
+ ** by sequence "*V".
+ ** - 3 button 3+1 byte MicroSoft compatible protocol (default).
+ ** Selected by sequence "*X".
+ **
+ ** The following baud rates are supported:
+ ** - 1200 Baud (default). Selected by sequence "*n".
+ ** - 9600 Baud. Selected by sequence "*q".
+ **
+ ** Selecting a sample rate is no longer supported with the MouseMan!
+ ** Some additional lines in xf86Config.c take care of ill configured
+ ** baud rates and sample rates. (The user will get an error.)
+ */
+
+ switch (rodent.rtype) {
+
+ case MOUSE_PROTO_LOGI:
+ /*
+ * The baud rate selection command must be sent at the current
+ * baud rate; try all likely settings
+ */
+ setmousespeed(9600, rodent.baudrate, rodentcflags[rodent.rtype]);
+ setmousespeed(4800, rodent.baudrate, rodentcflags[rodent.rtype]);
+ setmousespeed(2400, rodent.baudrate, rodentcflags[rodent.rtype]);
+ setmousespeed(1200, rodent.baudrate, rodentcflags[rodent.rtype]);
+ /* select MM series data format */
+ write(rodent.mfd, "S", 1);
+ setmousespeed(rodent.baudrate, rodent.baudrate,
+ rodentcflags[MOUSE_PROTO_MM]);
+ /* select report rate/frequency */
+ if (rodent.rate <= 0) write(rodent.mfd, "O", 1);
+ else if (rodent.rate <= 15) write(rodent.mfd, "J", 1);
+ else if (rodent.rate <= 27) write(rodent.mfd, "K", 1);
+ else if (rodent.rate <= 42) write(rodent.mfd, "L", 1);
+ else if (rodent.rate <= 60) write(rodent.mfd, "R", 1);
+ else if (rodent.rate <= 85) write(rodent.mfd, "M", 1);
+ else if (rodent.rate <= 125) write(rodent.mfd, "Q", 1);
+ else write(rodent.mfd, "N", 1);
+ break;
+
+ case MOUSE_PROTO_LOGIMOUSEMAN:
+ /* The command must always be sent at 1200 baud */
+ setmousespeed(1200, 1200, rodentcflags[rodent.rtype]);
+ write(rodent.mfd, "*X", 2);
+ setmousespeed(1200, rodent.baudrate, rodentcflags[rodent.rtype]);
+ break;
+
+ case MOUSE_PROTO_HITTAB:
+ setmousespeed(1200, rodent.baudrate, rodentcflags[rodent.rtype]);
+
+ /*
+ * Initialize Hitachi PUMA Plus - Model 1212E to desired settings.
+ * The tablet must be configured to be in MM mode, NO parity,
+ * Binary Format. xf86Info.sampleRate controls the sensativity
+ * of the tablet. We only use this tablet for it's 4-button puck
+ * so we don't run in "Absolute Mode"
+ */
+ write(rodent.mfd, "z8", 2); /* Set Parity = "NONE" */
+ usleep(50000);
+ write(rodent.mfd, "zb", 2); /* Set Format = "Binary" */
+ usleep(50000);
+ write(rodent.mfd, "@", 1); /* Set Report Mode = "Stream" */
+ usleep(50000);
+ write(rodent.mfd, "R", 1); /* Set Output Rate = "45 rps" */
+ usleep(50000);
+ write(rodent.mfd, "I\x20", 2); /* Set Incrememtal Mode "20" */
+ usleep(50000);
+ write(rodent.mfd, "E", 1); /* Set Data Type = "Relative */
+ usleep(50000);
+
+ /* Resolution is in 'lines per inch' on the Hitachi tablet */
+ if (rodent.resolution == MOUSE_RES_LOW) c = 'g';
+ else if (rodent.resolution == MOUSE_RES_MEDIUMLOW) c = 'e';
+ else if (rodent.resolution == MOUSE_RES_MEDIUMHIGH) c = 'h';
+ else if (rodent.resolution == MOUSE_RES_HIGH) c = 'd';
+ else if (rodent.resolution <= 40) c = 'g';
+ else if (rodent.resolution <= 100) c = 'd';
+ else if (rodent.resolution <= 200) c = 'e';
+ else if (rodent.resolution <= 500) c = 'h';
+ else if (rodent.resolution <= 1000) c = 'j';
+ else c = 'd';
+ write(rodent.mfd, &c, 1);
+ usleep(50000);
+
+ write(rodent.mfd, "\021", 1); /* Resume DATA output */
+ break;
+
+ case MOUSE_PROTO_THINK:
+ setmousespeed(1200, rodent.baudrate, rodentcflags[rodent.rtype]);
+ /* the PnP ID string may be sent again, discard it */
+ usleep(200000);
+ i = FREAD;
+ ioctl(rodent.mfd, TIOCFLUSH, &i);
+ /* send the command to initialize the beast */
+ for (s = "E5E5"; *s; ++s) {
+ write(rodent.mfd, s, 1);
+ FD_ZERO(&fds);
+ FD_SET(rodent.mfd, &fds);
+ if (select(FD_SETSIZE, &fds, NULL, NULL, NULL) <= 0)
+ break;
+ read(rodent.mfd, &c, 1);
+ debug("%c", c);
+ if (c != *s)
+ break;
+ }
+ break;
+
+ case MOUSE_PROTO_JOGDIAL:
+ break;
+ case MOUSE_PROTO_MSC:
+ setmousespeed(1200, rodent.baudrate, rodentcflags[rodent.rtype]);
+ if (rodent.flags & ClearDTR) {
+ i = TIOCM_DTR;
+ ioctl(rodent.mfd, TIOCMBIC, &i);
+ }
+ if (rodent.flags & ClearRTS) {
+ i = TIOCM_RTS;
+ ioctl(rodent.mfd, TIOCMBIC, &i);
+ }
+ break;
+
+ case MOUSE_PROTO_SYSMOUSE:
+ if (rodent.hw.iftype == MOUSE_IF_SYSMOUSE)
+ setmousespeed(1200, rodent.baudrate, rodentcflags[rodent.rtype]);
+ /* FALLTHROUGH */
+
+ case MOUSE_PROTO_BUS:
+ case MOUSE_PROTO_INPORT:
+ case MOUSE_PROTO_PS2:
+ if (rodent.rate >= 0)
+ rodent.mode.rate = rodent.rate;
+ if (rodent.resolution != MOUSE_RES_UNKNOWN)
+ rodent.mode.resolution = rodent.resolution;
+ ioctl(rodent.mfd, MOUSE_SETMODE, &rodent.mode);
+ break;
+
+ case MOUSE_PROTO_X10MOUSEREM:
+ mremote_serversetup();
+ setmousespeed(1200, rodent.baudrate, rodentcflags[rodent.rtype]);
+ break;
+
+
+ case MOUSE_PROTO_VERSAPAD:
+ tcsendbreak(rodent.mfd, 0); /* send break for 400 msec */
+ i = FREAD;
+ ioctl(rodent.mfd, TIOCFLUSH, &i);
+ for (i = 0; i < 7; ++i) {
+ FD_ZERO(&fds);
+ FD_SET(rodent.mfd, &fds);
+ if (select(FD_SETSIZE, &fds, NULL, NULL, NULL) <= 0)
+ break;
+ read(rodent.mfd, &c, 1);
+ buf[i] = c;
+ }
+ debug("%s\n", buf);
+ if ((buf[0] != 'V') || (buf[1] != 'P')|| (buf[7] != '\r'))
+ break;
+ setmousespeed(9600, rodent.baudrate, rodentcflags[rodent.rtype]);
+ tcsendbreak(rodent.mfd, 0); /* send break for 400 msec again */
+ for (i = 0; i < 7; ++i) {
+ FD_ZERO(&fds);
+ FD_SET(rodent.mfd, &fds);
+ if (select(FD_SETSIZE, &fds, NULL, NULL, NULL) <= 0)
+ break;
+ read(rodent.mfd, &c, 1);
+ debug("%c", c);
+ if (c != buf[i])
+ break;
+ }
+ i = FREAD;
+ ioctl(rodent.mfd, TIOCFLUSH, &i);
+ break;
+
+ default:
+ setmousespeed(1200, rodent.baudrate, rodentcflags[rodent.rtype]);
+ break;
+ }
+}
+
+static int
+r_protocol(u_char rBuf, mousestatus_t *act)
+{
+ /* MOUSE_MSS_BUTTON?DOWN -> MOUSE_BUTTON?DOWN */
+ static int butmapmss[4] = { /* Microsoft, MouseMan, GlidePoint,
+ IntelliMouse, Thinking Mouse */
+ 0,
+ MOUSE_BUTTON3DOWN,
+ MOUSE_BUTTON1DOWN,
+ MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN,
+ };
+ static int butmapmss2[4] = { /* Microsoft, MouseMan, GlidePoint,
+ Thinking Mouse */
+ 0,
+ MOUSE_BUTTON4DOWN,
+ MOUSE_BUTTON2DOWN,
+ MOUSE_BUTTON2DOWN | MOUSE_BUTTON4DOWN,
+ };
+ /* MOUSE_INTELLI_BUTTON?DOWN -> MOUSE_BUTTON?DOWN */
+ static int butmapintelli[4] = { /* IntelliMouse, NetMouse, Mie Mouse,
+ MouseMan+ */
+ 0,
+ MOUSE_BUTTON2DOWN,
+ MOUSE_BUTTON4DOWN,
+ MOUSE_BUTTON2DOWN | MOUSE_BUTTON4DOWN,
+ };
+ /* MOUSE_MSC_BUTTON?UP -> MOUSE_BUTTON?DOWN */
+ static int butmapmsc[8] = { /* MouseSystems, MMSeries, Logitech,
+ Bus, sysmouse */
+ 0,
+ MOUSE_BUTTON3DOWN,
+ MOUSE_BUTTON2DOWN,
+ MOUSE_BUTTON2DOWN | MOUSE_BUTTON3DOWN,
+ MOUSE_BUTTON1DOWN,
+ MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN,
+ MOUSE_BUTTON1DOWN | MOUSE_BUTTON2DOWN,
+ MOUSE_BUTTON1DOWN | MOUSE_BUTTON2DOWN | MOUSE_BUTTON3DOWN
+ };
+ /* MOUSE_PS2_BUTTON?DOWN -> MOUSE_BUTTON?DOWN */
+ static int butmapps2[8] = { /* PS/2 */
+ 0,
+ MOUSE_BUTTON1DOWN,
+ MOUSE_BUTTON3DOWN,
+ MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN,
+ MOUSE_BUTTON2DOWN,
+ MOUSE_BUTTON1DOWN | MOUSE_BUTTON2DOWN,
+ MOUSE_BUTTON2DOWN | MOUSE_BUTTON3DOWN,
+ MOUSE_BUTTON1DOWN | MOUSE_BUTTON2DOWN | MOUSE_BUTTON3DOWN
+ };
+ /* for Hitachi tablet */
+ static int butmaphit[8] = { /* MM HitTablet */
+ 0,
+ MOUSE_BUTTON3DOWN,
+ MOUSE_BUTTON2DOWN,
+ MOUSE_BUTTON1DOWN,
+ MOUSE_BUTTON4DOWN,
+ MOUSE_BUTTON5DOWN,
+ MOUSE_BUTTON6DOWN,
+ MOUSE_BUTTON7DOWN,
+ };
+ /* for serial VersaPad */
+ static int butmapversa[8] = { /* VersaPad */
+ 0,
+ 0,
+ MOUSE_BUTTON3DOWN,
+ MOUSE_BUTTON3DOWN,
+ MOUSE_BUTTON1DOWN,
+ MOUSE_BUTTON1DOWN,
+ MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN,
+ MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN,
+ };
+ /* for PS/2 VersaPad */
+ static int butmapversaps2[8] = { /* VersaPad */
+ 0,
+ MOUSE_BUTTON3DOWN,
+ 0,
+ MOUSE_BUTTON3DOWN,
+ MOUSE_BUTTON1DOWN,
+ MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN,
+ MOUSE_BUTTON1DOWN,
+ MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN,
+ };
+ static int pBufP = 0;
+ static unsigned char pBuf[8];
+ static int prev_x, prev_y;
+ static int on = FALSE;
+ int x, y;
+
+ debug("received char 0x%x",(int)rBuf);
+ if (rodent.rtype == MOUSE_PROTO_KIDSPAD)
+ return (kidspad(rBuf, act));
+ if (rodent.rtype == MOUSE_PROTO_GTCO_DIGIPAD)
+ return (gtco_digipad(rBuf, act));
+
+ /*
+ * Hack for resyncing: We check here for a package that is:
+ * a) illegal (detected by wrong data-package header)
+ * b) invalid (0x80 == -128 and that might be wrong for MouseSystems)
+ * c) bad header-package
+ *
+ * NOTE: b) is a voilation of the MouseSystems-Protocol, since values of
+ * -128 are allowed, but since they are very seldom we can easily
+ * use them as package-header with no button pressed.
+ * NOTE/2: On a PS/2 mouse any byte is valid as a data byte. Furthermore,
+ * 0x80 is not valid as a header byte. For a PS/2 mouse we skip
+ * checking data bytes.
+ * For resyncing a PS/2 mouse we require the two most significant
+ * bits in the header byte to be 0. These are the overflow bits,
+ * and in case of an overflow we actually lose sync. Overflows
+ * are very rare, however, and we quickly gain sync again after
+ * an overflow condition. This is the best we can do. (Actually,
+ * we could use bit 0x08 in the header byte for resyncing, since
+ * that bit is supposed to be always on, but nobody told
+ * Microsoft...)
+ */
+
+ if (pBufP != 0 && rodent.rtype != MOUSE_PROTO_PS2 &&
+ ((rBuf & cur_proto[2]) != cur_proto[3] || rBuf == 0x80))
+ {
+ pBufP = 0; /* skip package */
+ }
+
+ if (pBufP == 0 && (rBuf & cur_proto[0]) != cur_proto[1])
+ return (0);
+
+ /* is there an extra data byte? */
+ if (pBufP >= cur_proto[4] && (rBuf & cur_proto[0]) != cur_proto[1])
+ {
+ /*
+ * Hack for Logitech MouseMan Mouse - Middle button
+ *
+ * Unfortunately this mouse has variable length packets: the standard
+ * Microsoft 3 byte packet plus an optional 4th byte whenever the
+ * middle button status changes.
+ *
+ * We have already processed the standard packet with the movement
+ * and button info. Now post an event message with the old status
+ * of the left and right buttons and the updated middle button.
+ */
+
+ /*
+ * Even worse, different MouseMen and TrackMen differ in the 4th
+ * byte: some will send 0x00/0x20, others 0x01/0x21, or even
+ * 0x02/0x22, so I have to strip off the lower bits.
+ *
+ * [JCH-96/01/21]
+ * HACK for ALPS "fourth button". (It's bit 0x10 of the "fourth byte"
+ * and it is activated by tapping the glidepad with the finger! 8^)
+ * We map it to bit bit3, and the reverse map in xf86Events just has
+ * to be extended so that it is identified as Button 4. The lower
+ * half of the reverse-map may remain unchanged.
+ */
+
+ /*
+ * [KY-97/08/03]
+ * Receive the fourth byte only when preceding three bytes have
+ * been detected (pBufP >= cur_proto[4]). In the previous
+ * versions, the test was pBufP == 0; thus, we may have mistakingly
+ * received a byte even if we didn't see anything preceding
+ * the byte.
+ */
+
+ if ((rBuf & cur_proto[5]) != cur_proto[6]) {
+ pBufP = 0;
+ return (0);
+ }
+
+ switch (rodent.rtype) {
+#if notyet
+ case MOUSE_PROTO_MARIQUA:
+ /*
+ * This mouse has 16! buttons in addition to the standard
+ * three of them. They return 0x10 though 0x1f in the
+ * so-called `ten key' mode and 0x30 though 0x3f in the
+ * `function key' mode. As there are only 31 bits for
+ * button state (including the standard three), we ignore
+ * the bit 0x20 and don't distinguish the two modes.
+ */
+ act->dx = act->dy = act->dz = 0;
+ act->obutton = act->button;
+ rBuf &= 0x1f;
+ act->button = (1 << (rBuf - 13))
+ | (act->obutton & (MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN));
+ /*
+ * FIXME: this is a button "down" event. There needs to be
+ * a corresponding button "up" event... XXX
+ */
+ break;
+#endif /* notyet */
+ case MOUSE_PROTO_JOGDIAL:
+ break;
+
+ /*
+ * IntelliMouse, NetMouse (including NetMouse Pro) and Mie Mouse
+ * always send the fourth byte, whereas the fourth byte is
+ * optional for GlidePoint and ThinkingMouse. The fourth byte
+ * is also optional for MouseMan+ and FirstMouse+ in their
+ * native mode. It is always sent if they are in the IntelliMouse
+ * compatible mode.
+ */
+ case MOUSE_PROTO_INTELLI: /* IntelliMouse, NetMouse, Mie Mouse,
+ MouseMan+ */
+ act->dx = act->dy = 0;
+ act->dz = (rBuf & 0x08) ? (rBuf & 0x0f) - 16 : (rBuf & 0x0f);
+ if ((act->dz >= 7) || (act->dz <= -7))
+ act->dz = 0;
+ act->obutton = act->button;
+ act->button = butmapintelli[(rBuf & MOUSE_MSS_BUTTONS) >> 4]
+ | (act->obutton & (MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN));
+ break;
+
+ default:
+ act->dx = act->dy = act->dz = 0;
+ act->obutton = act->button;
+ act->button = butmapmss2[(rBuf & MOUSE_MSS_BUTTONS) >> 4]
+ | (act->obutton & (MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN));
+ break;
+ }
+
+ act->flags = ((act->dx || act->dy || act->dz) ? MOUSE_POSCHANGED : 0)
+ | (act->obutton ^ act->button);
+ pBufP = 0;
+ return (act->flags);
+ }
+
+ if (pBufP >= cur_proto[4])
+ pBufP = 0;
+ pBuf[pBufP++] = rBuf;
+ if (pBufP != cur_proto[4])
+ return (0);
+
+ /*
+ * assembly full package
+ */
+
+ debug("assembled full packet (len %d) %x,%x,%x,%x,%x,%x,%x,%x",
+ cur_proto[4],
+ pBuf[0], pBuf[1], pBuf[2], pBuf[3],
+ pBuf[4], pBuf[5], pBuf[6], pBuf[7]);
+
+ act->dz = 0;
+ act->obutton = act->button;
+ switch (rodent.rtype)
+ {
+ case MOUSE_PROTO_MS: /* Microsoft */
+ case MOUSE_PROTO_LOGIMOUSEMAN: /* MouseMan/TrackMan */
+ case MOUSE_PROTO_X10MOUSEREM: /* X10 MouseRemote */
+ act->button = act->obutton & MOUSE_BUTTON4DOWN;
+ if (rodent.flags & ChordMiddle)
+ act->button |= ((pBuf[0] & MOUSE_MSS_BUTTONS) == MOUSE_MSS_BUTTONS)
+ ? MOUSE_BUTTON2DOWN
+ : butmapmss[(pBuf[0] & MOUSE_MSS_BUTTONS) >> 4];
+ else
+ act->button |= (act->obutton & MOUSE_BUTTON2DOWN)
+ | butmapmss[(pBuf[0] & MOUSE_MSS_BUTTONS) >> 4];
+
+ /* Send X10 btn events to remote client (ensure -128-+127 range) */
+ if ((rodent.rtype == MOUSE_PROTO_X10MOUSEREM) &&
+ ((pBuf[0] & 0xFC) == 0x44) && (pBuf[2] == 0x3F)) {
+ if (rodent.mremcfd >= 0) {
+ unsigned char key = (signed char)(((pBuf[0] & 0x03) << 6) |
+ (pBuf[1] & 0x3F));
+ write(rodent.mremcfd, &key, 1);
+ }
+ return (0);
+ }
+
+ act->dx = (signed char)(((pBuf[0] & 0x03) << 6) | (pBuf[1] & 0x3F));
+ act->dy = (signed char)(((pBuf[0] & 0x0C) << 4) | (pBuf[2] & 0x3F));
+ break;
+
+ case MOUSE_PROTO_GLIDEPOINT: /* GlidePoint */
+ case MOUSE_PROTO_THINK: /* ThinkingMouse */
+ case MOUSE_PROTO_INTELLI: /* IntelliMouse, NetMouse, Mie Mouse,
+ MouseMan+ */
+ act->button = (act->obutton & (MOUSE_BUTTON2DOWN | MOUSE_BUTTON4DOWN))
+ | butmapmss[(pBuf[0] & MOUSE_MSS_BUTTONS) >> 4];
+ act->dx = (signed char)(((pBuf[0] & 0x03) << 6) | (pBuf[1] & 0x3F));
+ act->dy = (signed char)(((pBuf[0] & 0x0C) << 4) | (pBuf[2] & 0x3F));
+ break;
+
+ case MOUSE_PROTO_MSC: /* MouseSystems Corp */
+#if notyet
+ case MOUSE_PROTO_MARIQUA: /* Mariqua */
+#endif
+ act->button = butmapmsc[(~pBuf[0]) & MOUSE_MSC_BUTTONS];
+ act->dx = (signed char)(pBuf[1]) + (signed char)(pBuf[3]);
+ act->dy = - ((signed char)(pBuf[2]) + (signed char)(pBuf[4]));
+ break;
+
+ case MOUSE_PROTO_JOGDIAL: /* JogDial */
+ if (rBuf == 0x6c)
+ act->dz = -1;
+ if (rBuf == 0x72)
+ act->dz = 1;
+ if (rBuf == 0x64)
+ act->button = MOUSE_BUTTON1DOWN;
+ if (rBuf == 0x75)
+ act->button = 0;
+ break;
+
+ case MOUSE_PROTO_HITTAB: /* MM HitTablet */
+ act->button = butmaphit[pBuf[0] & 0x07];
+ act->dx = (pBuf[0] & MOUSE_MM_XPOSITIVE) ? pBuf[1] : - pBuf[1];
+ act->dy = (pBuf[0] & MOUSE_MM_YPOSITIVE) ? - pBuf[2] : pBuf[2];
+ break;
+
+ case MOUSE_PROTO_MM: /* MM Series */
+ case MOUSE_PROTO_LOGI: /* Logitech Mice */
+ act->button = butmapmsc[pBuf[0] & MOUSE_MSC_BUTTONS];
+ act->dx = (pBuf[0] & MOUSE_MM_XPOSITIVE) ? pBuf[1] : - pBuf[1];
+ act->dy = (pBuf[0] & MOUSE_MM_YPOSITIVE) ? - pBuf[2] : pBuf[2];
+ break;
+
+ case MOUSE_PROTO_VERSAPAD: /* VersaPad */
+ act->button = butmapversa[(pBuf[0] & MOUSE_VERSA_BUTTONS) >> 3];
+ act->button |= (pBuf[0] & MOUSE_VERSA_TAP) ? MOUSE_BUTTON4DOWN : 0;
+ act->dx = act->dy = 0;
+ if (!(pBuf[0] & MOUSE_VERSA_IN_USE)) {
+ on = FALSE;
+ break;
+ }
+ x = (pBuf[2] << 6) | pBuf[1];
+ if (x & 0x800)
+ x -= 0x1000;
+ y = (pBuf[4] << 6) | pBuf[3];
+ if (y & 0x800)
+ y -= 0x1000;
+ if (on) {
+ act->dx = prev_x - x;
+ act->dy = prev_y - y;
+ } else {
+ on = TRUE;
+ }
+ prev_x = x;
+ prev_y = y;
+ break;
+
+ case MOUSE_PROTO_BUS: /* Bus */
+ case MOUSE_PROTO_INPORT: /* InPort */
+ act->button = butmapmsc[(~pBuf[0]) & MOUSE_MSC_BUTTONS];
+ act->dx = (signed char)pBuf[1];
+ act->dy = - (signed char)pBuf[2];
+ break;
+
+ case MOUSE_PROTO_PS2: /* PS/2 */
+ act->button = butmapps2[pBuf[0] & MOUSE_PS2_BUTTONS];
+ act->dx = (pBuf[0] & MOUSE_PS2_XNEG) ? pBuf[1] - 256 : pBuf[1];
+ act->dy = (pBuf[0] & MOUSE_PS2_YNEG) ? -(pBuf[2] - 256) : -pBuf[2];
+ /*
+ * Moused usually operates the psm driver at the operation level 1
+ * which sends mouse data in MOUSE_PROTO_SYSMOUSE protocol.
+ * The following code takes effect only when the user explicitly
+ * requets the level 2 at which wheel movement and additional button
+ * actions are encoded in model-dependent formats. At the level 0
+ * the following code is no-op because the psm driver says the model
+ * is MOUSE_MODEL_GENERIC.
+ */
+ switch (rodent.hw.model) {
+ case MOUSE_MODEL_EXPLORER:
+ /* wheel and additional button data is in the fourth byte */
+ act->dz = (pBuf[3] & MOUSE_EXPLORER_ZNEG)
+ ? (pBuf[3] & 0x0f) - 16 : (pBuf[3] & 0x0f);
+ act->button |= (pBuf[3] & MOUSE_EXPLORER_BUTTON4DOWN)
+ ? MOUSE_BUTTON4DOWN : 0;
+ act->button |= (pBuf[3] & MOUSE_EXPLORER_BUTTON5DOWN)
+ ? MOUSE_BUTTON5DOWN : 0;
+ break;
+ case MOUSE_MODEL_INTELLI:
+ case MOUSE_MODEL_NET:
+ /* wheel data is in the fourth byte */
+ act->dz = (signed char)pBuf[3];
+ if ((act->dz >= 7) || (act->dz <= -7))
+ act->dz = 0;
+ /* some compatible mice may have additional buttons */
+ act->button |= (pBuf[0] & MOUSE_PS2INTELLI_BUTTON4DOWN)
+ ? MOUSE_BUTTON4DOWN : 0;
+ act->button |= (pBuf[0] & MOUSE_PS2INTELLI_BUTTON5DOWN)
+ ? MOUSE_BUTTON5DOWN : 0;
+ break;
+ case MOUSE_MODEL_MOUSEMANPLUS:
+ if (((pBuf[0] & MOUSE_PS2PLUS_SYNCMASK) == MOUSE_PS2PLUS_SYNC)
+ && (abs(act->dx) > 191)
+ && MOUSE_PS2PLUS_CHECKBITS(pBuf)) {
+ /* the extended data packet encodes button and wheel events */
+ switch (MOUSE_PS2PLUS_PACKET_TYPE(pBuf)) {
+ case 1:
+ /* wheel data packet */
+ act->dx = act->dy = 0;
+ if (pBuf[2] & 0x80) {
+ /* horizontal roller count - ignore it XXX*/
+ } else {
+ /* vertical roller count */
+ act->dz = (pBuf[2] & MOUSE_PS2PLUS_ZNEG)
+ ? (pBuf[2] & 0x0f) - 16 : (pBuf[2] & 0x0f);
+ }
+ act->button |= (pBuf[2] & MOUSE_PS2PLUS_BUTTON4DOWN)
+ ? MOUSE_BUTTON4DOWN : 0;
+ act->button |= (pBuf[2] & MOUSE_PS2PLUS_BUTTON5DOWN)
+ ? MOUSE_BUTTON5DOWN : 0;
+ break;
+ case 2:
+ /* this packet type is reserved by Logitech */
+ /*
+ * IBM ScrollPoint Mouse uses this packet type to
+ * encode both vertical and horizontal scroll movement.
+ */
+ act->dx = act->dy = 0;
+ /* horizontal roller count */
+ if (pBuf[2] & 0x0f)
+ act->dz = (pBuf[2] & MOUSE_SPOINT_WNEG) ? -2 : 2;
+ /* vertical roller count */
+ if (pBuf[2] & 0xf0)
+ act->dz = (pBuf[2] & MOUSE_SPOINT_ZNEG) ? -1 : 1;
+#if 0
+ /* vertical roller count */
+ act->dz = (pBuf[2] & MOUSE_SPOINT_ZNEG)
+ ? ((pBuf[2] >> 4) & 0x0f) - 16
+ : ((pBuf[2] >> 4) & 0x0f);
+ /* horizontal roller count */
+ act->dw = (pBuf[2] & MOUSE_SPOINT_WNEG)
+ ? (pBuf[2] & 0x0f) - 16 : (pBuf[2] & 0x0f);
+#endif
+ break;
+ case 0:
+ /* device type packet - shouldn't happen */
+ /* FALLTHROUGH */
+ default:
+ act->dx = act->dy = 0;
+ act->button = act->obutton;
+ debug("unknown PS2++ packet type %d: 0x%02x 0x%02x 0x%02x\n",
+ MOUSE_PS2PLUS_PACKET_TYPE(pBuf),
+ pBuf[0], pBuf[1], pBuf[2]);
+ break;
+ }
+ } else {
+ /* preserve button states */
+ act->button |= act->obutton & MOUSE_EXTBUTTONS;
+ }
+ break;
+ case MOUSE_MODEL_GLIDEPOINT:
+ /* `tapping' action */
+ act->button |= ((pBuf[0] & MOUSE_PS2_TAP)) ? 0 : MOUSE_BUTTON4DOWN;
+ break;
+ case MOUSE_MODEL_NETSCROLL:
+ /* three additional bytes encode buttons and wheel events */
+ act->button |= (pBuf[3] & MOUSE_PS2_BUTTON3DOWN)
+ ? MOUSE_BUTTON4DOWN : 0;
+ act->button |= (pBuf[3] & MOUSE_PS2_BUTTON1DOWN)
+ ? MOUSE_BUTTON5DOWN : 0;
+ act->dz = (pBuf[3] & MOUSE_PS2_XNEG) ? pBuf[4] - 256 : pBuf[4];
+ break;
+ case MOUSE_MODEL_THINK:
+ /* the fourth button state in the first byte */
+ act->button |= (pBuf[0] & MOUSE_PS2_TAP) ? MOUSE_BUTTON4DOWN : 0;
+ break;
+ case MOUSE_MODEL_VERSAPAD:
+ act->button = butmapversaps2[pBuf[0] & MOUSE_PS2VERSA_BUTTONS];
+ act->button |=
+ (pBuf[0] & MOUSE_PS2VERSA_TAP) ? MOUSE_BUTTON4DOWN : 0;
+ act->dx = act->dy = 0;
+ if (!(pBuf[0] & MOUSE_PS2VERSA_IN_USE)) {
+ on = FALSE;
+ break;
+ }
+ x = ((pBuf[4] << 8) & 0xf00) | pBuf[1];
+ if (x & 0x800)
+ x -= 0x1000;
+ y = ((pBuf[4] << 4) & 0xf00) | pBuf[2];
+ if (y & 0x800)
+ y -= 0x1000;
+ if (on) {
+ act->dx = prev_x - x;
+ act->dy = prev_y - y;
+ } else {
+ on = TRUE;
+ }
+ prev_x = x;
+ prev_y = y;
+ break;
+ case MOUSE_MODEL_4D:
+ act->dx = (pBuf[1] & 0x80) ? pBuf[1] - 256 : pBuf[1];
+ act->dy = (pBuf[2] & 0x80) ? -(pBuf[2] - 256) : -pBuf[2];
+ switch (pBuf[0] & MOUSE_4D_WHEELBITS) {
+ case 0x10:
+ act->dz = 1;
+ break;
+ case 0x30:
+ act->dz = -1;
+ break;
+ case 0x40: /* 2nd wheel rolling right XXX */
+ act->dz = 2;
+ break;
+ case 0xc0: /* 2nd wheel rolling left XXX */
+ act->dz = -2;
+ break;
+ }
+ break;
+ case MOUSE_MODEL_4DPLUS:
+ if ((act->dx < 16 - 256) && (act->dy > 256 - 16)) {
+ act->dx = act->dy = 0;
+ if (pBuf[2] & MOUSE_4DPLUS_BUTTON4DOWN)
+ act->button |= MOUSE_BUTTON4DOWN;
+ act->dz = (pBuf[2] & MOUSE_4DPLUS_ZNEG)
+ ? ((pBuf[2] & 0x07) - 8) : (pBuf[2] & 0x07);
+ } else {
+ /* preserve previous button states */
+ act->button |= act->obutton & MOUSE_EXTBUTTONS;
+ }
+ break;
+ case MOUSE_MODEL_GENERIC:
+ default:
+ break;
+ }
+ break;
+
+ case MOUSE_PROTO_SYSMOUSE: /* sysmouse */
+ act->button = butmapmsc[(~pBuf[0]) & MOUSE_SYS_STDBUTTONS];
+ act->dx = (signed char)(pBuf[1]) + (signed char)(pBuf[3]);
+ act->dy = - ((signed char)(pBuf[2]) + (signed char)(pBuf[4]));
+ if (rodent.level == 1) {
+ act->dz = ((signed char)(pBuf[5] << 1) + (signed char)(pBuf[6] << 1)) >> 1;
+ act->button |= ((~pBuf[7] & MOUSE_SYS_EXTBUTTONS) << 3);
+ }
+ break;
+
+ default:
+ return (0);
+ }
+ /*
+ * We don't reset pBufP here yet, as there may be an additional data
+ * byte in some protocols. See above.
+ */
+
+ /* has something changed? */
+ act->flags = ((act->dx || act->dy || act->dz) ? MOUSE_POSCHANGED : 0)
+ | (act->obutton ^ act->button);
+
+ return (act->flags);
+}
+
+static int
+r_statetrans(mousestatus_t *a1, mousestatus_t *a2, int trans)
+{
+ int changed;
+ int flags;
+
+ a2->dx = a1->dx;
+ a2->dy = a1->dy;
+ a2->dz = a1->dz;
+ a2->obutton = a2->button;
+ a2->button = a1->button;
+ a2->flags = a1->flags;
+ changed = FALSE;
+
+ if (rodent.flags & Emulate3Button) {
+ if (debug > 2)
+ debug("state:%d, trans:%d -> state:%d",
+ mouse_button_state, trans,
+ states[mouse_button_state].s[trans]);
+ /*
+ * Avoid re-ordering button and movement events. While a button
+ * event is deferred, throw away up to BUTTON2_MAXMOVE movement
+ * events to allow for mouse jitter. If more movement events
+ * occur, then complete the deferred button events immediately.
+ */
+ if ((a2->dx != 0 || a2->dy != 0) &&
+ S_DELAYED(states[mouse_button_state].s[trans])) {
+ if (++mouse_move_delayed > BUTTON2_MAXMOVE) {
+ mouse_move_delayed = 0;
+ mouse_button_state =
+ states[mouse_button_state].s[A_TIMEOUT];
+ changed = TRUE;
+ } else
+ a2->dx = a2->dy = 0;
+ } else
+ mouse_move_delayed = 0;
+ if (mouse_button_state != states[mouse_button_state].s[trans])
+ changed = TRUE;
+ if (changed)
+ clock_gettime(CLOCK_MONOTONIC_FAST, &mouse_button_state_ts);
+ mouse_button_state = states[mouse_button_state].s[trans];
+ a2->button &=
+ ~(MOUSE_BUTTON1DOWN | MOUSE_BUTTON2DOWN | MOUSE_BUTTON3DOWN);
+ a2->button &= states[mouse_button_state].mask;
+ a2->button |= states[mouse_button_state].buttons;
+ flags = a2->flags & MOUSE_POSCHANGED;
+ flags |= a2->obutton ^ a2->button;
+ if (flags & MOUSE_BUTTON2DOWN) {
+ a2->flags = flags & MOUSE_BUTTON2DOWN;
+ r_timestamp(a2);
+ }
+ a2->flags = flags;
+ }
+ return (changed);
+}
+
+/* phisical to logical button mapping */
+static int p2l[MOUSE_MAXBUTTON] = {
+ MOUSE_BUTTON1DOWN, MOUSE_BUTTON2DOWN, MOUSE_BUTTON3DOWN, MOUSE_BUTTON4DOWN,
+ MOUSE_BUTTON5DOWN, MOUSE_BUTTON6DOWN, MOUSE_BUTTON7DOWN, MOUSE_BUTTON8DOWN,
+ 0x00000100, 0x00000200, 0x00000400, 0x00000800,
+ 0x00001000, 0x00002000, 0x00004000, 0x00008000,
+ 0x00010000, 0x00020000, 0x00040000, 0x00080000,
+ 0x00100000, 0x00200000, 0x00400000, 0x00800000,
+ 0x01000000, 0x02000000, 0x04000000, 0x08000000,
+ 0x10000000, 0x20000000, 0x40000000,
+};
+
+static char *
+skipspace(char *s)
+{
+ while(isspace(*s))
+ ++s;
+ return (s);
+}
+
+static int
+r_installmap(char *arg)
+{
+ int pbutton;
+ int lbutton;
+ char *s;
+
+ while (*arg) {
+ arg = skipspace(arg);
+ s = arg;
+ while (isdigit(*arg))
+ ++arg;
+ arg = skipspace(arg);
+ if ((arg <= s) || (*arg != '='))
+ return (FALSE);
+ lbutton = atoi(s);
+
+ arg = skipspace(++arg);
+ s = arg;
+ while (isdigit(*arg))
+ ++arg;
+ if ((arg <= s) || (!isspace(*arg) && (*arg != '\0')))
+ return (FALSE);
+ pbutton = atoi(s);
+
+ if ((lbutton <= 0) || (lbutton > MOUSE_MAXBUTTON))
+ return (FALSE);
+ if ((pbutton <= 0) || (pbutton > MOUSE_MAXBUTTON))
+ return (FALSE);
+ p2l[pbutton - 1] = 1 << (lbutton - 1);
+ mstate[lbutton - 1] = &bstate[pbutton - 1];
+ }
+
+ return (TRUE);
+}
+
+static void
+r_map(mousestatus_t *act1, mousestatus_t *act2)
+{
+ register int pb;
+ register int pbuttons;
+ int lbuttons;
+
+ pbuttons = act1->button;
+ lbuttons = 0;
+
+ act2->obutton = act2->button;
+ if (pbuttons & rodent.wmode) {
+ pbuttons &= ~rodent.wmode;
+ act1->dz = act1->dy;
+ act1->dx = 0;
+ act1->dy = 0;
+ }
+ act2->dx = act1->dx;
+ act2->dy = act1->dy;
+ act2->dz = act1->dz;
+
+ switch (rodent.zmap[0]) {
+ case 0: /* do nothing */
+ break;
+ case MOUSE_XAXIS:
+ if (act1->dz != 0) {
+ act2->dx = act1->dz;
+ act2->dz = 0;
+ }
+ break;
+ case MOUSE_YAXIS:
+ if (act1->dz != 0) {
+ act2->dy = act1->dz;
+ act2->dz = 0;
+ }
+ break;
+ default: /* buttons */
+ pbuttons &= ~(rodent.zmap[0] | rodent.zmap[1]
+ | rodent.zmap[2] | rodent.zmap[3]);
+ if ((act1->dz < -1) && rodent.zmap[2]) {
+ pbuttons |= rodent.zmap[2];
+ zstate[2].count = 1;
+ } else if (act1->dz < 0) {
+ pbuttons |= rodent.zmap[0];
+ zstate[0].count = 1;
+ } else if ((act1->dz > 1) && rodent.zmap[3]) {
+ pbuttons |= rodent.zmap[3];
+ zstate[3].count = 1;
+ } else if (act1->dz > 0) {
+ pbuttons |= rodent.zmap[1];
+ zstate[1].count = 1;
+ }
+ act2->dz = 0;
+ break;
+ }
+
+ for (pb = 0; (pb < MOUSE_MAXBUTTON) && (pbuttons != 0); ++pb) {
+ lbuttons |= (pbuttons & 1) ? p2l[pb] : 0;
+ pbuttons >>= 1;
+ }
+ act2->button = lbuttons;
+
+ act2->flags = ((act2->dx || act2->dy || act2->dz) ? MOUSE_POSCHANGED : 0)
+ | (act2->obutton ^ act2->button);
+}
+
+static void
+r_timestamp(mousestatus_t *act)
+{
+ struct timespec ts;
+ struct timespec ts1;
+ struct timespec ts2;
+ struct timespec ts3;
+ int button;
+ int mask;
+ int i;
+
+ mask = act->flags & MOUSE_BUTTONS;
+#if 0
+ if (mask == 0)
+ return;
+#endif
+
+ clock_gettime(CLOCK_MONOTONIC_FAST, &ts1);
+ drift_current_ts = ts1;
+
+ /* double click threshold */
+ ts2.tv_sec = rodent.clickthreshold / 1000;
+ ts2.tv_nsec = (rodent.clickthreshold % 1000) * 1000000;
+ tssub(&ts1, &ts2, &ts);
+ debug("ts: %jd %ld", (intmax_t)ts.tv_sec, ts.tv_nsec);
+
+ /* 3 button emulation timeout */
+ ts2.tv_sec = rodent.button2timeout / 1000;
+ ts2.tv_nsec = (rodent.button2timeout % 1000) * 1000000;
+ tssub(&ts1, &ts2, &ts3);
+
+ button = MOUSE_BUTTON1DOWN;
+ for (i = 0; (i < MOUSE_MAXBUTTON) && (mask != 0); ++i) {
+ if (mask & 1) {
+ if (act->button & button) {
+ /* the button is down */
+ debug(" : %jd %ld",
+ (intmax_t)bstate[i].ts.tv_sec, bstate[i].ts.tv_nsec);
+ if (tscmp(&ts, &bstate[i].ts, >)) {
+ bstate[i].count = 1;
+ } else {
+ ++bstate[i].count;
+ }
+ bstate[i].ts = ts1;
+ } else {
+ /* the button is up */
+ bstate[i].ts = ts1;
+ }
+ } else {
+ if (act->button & button) {
+ /* the button has been down */
+ if (tscmp(&ts3, &bstate[i].ts, >)) {
+ bstate[i].count = 1;
+ bstate[i].ts = ts1;
+ act->flags |= button;
+ debug("button %d timeout", i + 1);
+ }
+ } else {
+ /* the button has been up */
+ }
+ }
+ button <<= 1;
+ mask >>= 1;
+ }
+}
+
+static int
+r_timeout(void)
+{
+ struct timespec ts;
+ struct timespec ts1;
+ struct timespec ts2;
+
+ if (states[mouse_button_state].timeout)
+ return (TRUE);
+ clock_gettime(CLOCK_MONOTONIC_FAST, &ts1);
+ ts2.tv_sec = rodent.button2timeout / 1000;
+ ts2.tv_nsec = (rodent.button2timeout % 1000) * 1000000;
+ tssub(&ts1, &ts2, &ts);
+ return (tscmp(&ts, &mouse_button_state_ts, >));
+}
+
+static void
+r_click(mousestatus_t *act)
+{
+ struct mouse_info mouse;
+ int button;
+ int mask;
+ int i;
+
+ mask = act->flags & MOUSE_BUTTONS;
+ if (mask == 0)
+ return;
+
+ button = MOUSE_BUTTON1DOWN;
+ for (i = 0; (i < MOUSE_MAXBUTTON) && (mask != 0); ++i) {
+ if (mask & 1) {
+ debug("mstate[%d]->count:%d", i, mstate[i]->count);
+ if (act->button & button) {
+ /* the button is down */
+ mouse.u.event.value = mstate[i]->count;
+ } else {
+ /* the button is up */
+ mouse.u.event.value = 0;
+ }
+ mouse.operation = MOUSE_BUTTON_EVENT;
+ mouse.u.event.id = button;
+ if (debug < 2)
+ if (!paused)
+ ioctl(rodent.cfd, CONS_MOUSECTL, &mouse);
+ debug("button %d count %d", i + 1, mouse.u.event.value);
+ }
+ button <<= 1;
+ mask >>= 1;
+ }
+}
+
+/* $XConsortium: posix_tty.c,v 1.3 95/01/05 20:42:55 kaleb Exp $ */
+/* $XFree86: xc/programs/Xserver/hw/xfree86/os-support/shared/posix_tty.c,v 3.4 1995/01/28 17:05:03 dawes Exp $ */
+/*
+ * Copyright 1993 by David Dawes <dawes@physics.su.oz.au>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of David Dawes
+ * not be used in advertising or publicity pertaining to distribution of
+ * the software without specific, written prior permission.
+ * David Dawes makes no representations about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ *
+ * DAVID DAWES DISCLAIMS ALL WARRANTIES WITH REGARD TO
+ * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL DAVID DAWES BE LIABLE FOR
+ * ANY SPECIAL, 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.
+ *
+ */
+
+
+static void
+setmousespeed(int old, int new, unsigned cflag)
+{
+ struct termios tty;
+ const char *c;
+
+ if (tcgetattr(rodent.mfd, &tty) < 0)
+ {
+ logwarn("unable to get status of mouse fd");
+ return;
+ }
+
+ tty.c_iflag = IGNBRK | IGNPAR;
+ tty.c_oflag = 0;
+ tty.c_lflag = 0;
+ tty.c_cflag = (tcflag_t)cflag;
+ tty.c_cc[VTIME] = 0;
+ tty.c_cc[VMIN] = 1;
+
+ switch (old)
+ {
+ case 9600:
+ cfsetispeed(&tty, B9600);
+ cfsetospeed(&tty, B9600);
+ break;
+ case 4800:
+ cfsetispeed(&tty, B4800);
+ cfsetospeed(&tty, B4800);
+ break;
+ case 2400:
+ cfsetispeed(&tty, B2400);
+ cfsetospeed(&tty, B2400);
+ break;
+ case 1200:
+ default:
+ cfsetispeed(&tty, B1200);
+ cfsetospeed(&tty, B1200);
+ }
+
+ if (tcsetattr(rodent.mfd, TCSADRAIN, &tty) < 0)
+ {
+ logwarn("unable to set status of mouse fd");
+ return;
+ }
+
+ switch (new)
+ {
+ case 9600:
+ c = "*q";
+ cfsetispeed(&tty, B9600);
+ cfsetospeed(&tty, B9600);
+ break;
+ case 4800:
+ c = "*p";
+ cfsetispeed(&tty, B4800);
+ cfsetospeed(&tty, B4800);
+ break;
+ case 2400:
+ c = "*o";
+ cfsetispeed(&tty, B2400);
+ cfsetospeed(&tty, B2400);
+ break;
+ case 1200:
+ default:
+ c = "*n";
+ cfsetispeed(&tty, B1200);
+ cfsetospeed(&tty, B1200);
+ }
+
+ if (rodent.rtype == MOUSE_PROTO_LOGIMOUSEMAN
+ || rodent.rtype == MOUSE_PROTO_LOGI)
+ {
+ if (write(rodent.mfd, c, 2) != 2)
+ {
+ logwarn("unable to write to mouse fd");
+ return;
+ }
+ }
+ usleep(100000);
+
+ if (tcsetattr(rodent.mfd, TCSADRAIN, &tty) < 0)
+ logwarn("unable to set status of mouse fd");
+}
+
+/*
+ * PnP COM device support
+ *
+ * It's a simplistic implementation, but it works :-)
+ * KY, 31/7/97.
+ */
+
+/*
+ * Try to elicit a PnP ID as described in
+ * Microsoft, Hayes: "Plug and Play External COM Device Specification,
+ * rev 1.00", 1995.
+ *
+ * The routine does not fully implement the COM Enumerator as par Section
+ * 2.1 of the document. In particular, we don't have idle state in which
+ * the driver software monitors the com port for dynamic connection or
+ * removal of a device at the port, because `moused' simply quits if no
+ * device is found.
+ *
+ * In addition, as PnP COM device enumeration procedure slightly has
+ * changed since its first publication, devices which follow earlier
+ * revisions of the above spec. may fail to respond if the rev 1.0
+ * procedure is used. XXX
+ */
+static int
+pnpwakeup1(void)
+{
+ struct timeval timeout;
+ fd_set fds;
+ int i;
+
+ /*
+ * This is the procedure described in rev 1.0 of PnP COM device spec.
+ * Unfortunately, some devices which comform to earlier revisions of
+ * the spec gets confused and do not return the ID string...
+ */
+ debug("PnP COM device rev 1.0 probe...");
+
+ /* port initialization (2.1.2) */
+ ioctl(rodent.mfd, TIOCMGET, &i);
+ i |= TIOCM_DTR; /* DTR = 1 */
+ i &= ~TIOCM_RTS; /* RTS = 0 */
+ ioctl(rodent.mfd, TIOCMSET, &i);
+ usleep(240000);
+
+ /*
+ * The PnP COM device spec. dictates that the mouse must set DSR
+ * in response to DTR (by hardware or by software) and that if DSR is
+ * not asserted, the host computer should think that there is no device
+ * at this serial port. But some mice just don't do that...
+ */
+ ioctl(rodent.mfd, TIOCMGET, &i);
+ debug("modem status 0%o", i);
+ if ((i & TIOCM_DSR) == 0)
+ return (FALSE);
+
+ /* port setup, 1st phase (2.1.3) */
+ setmousespeed(1200, 1200, (CS7 | CREAD | CLOCAL | HUPCL));
+ i = TIOCM_DTR | TIOCM_RTS; /* DTR = 0, RTS = 0 */
+ ioctl(rodent.mfd, TIOCMBIC, &i);
+ usleep(240000);
+ i = TIOCM_DTR; /* DTR = 1, RTS = 0 */
+ ioctl(rodent.mfd, TIOCMBIS, &i);
+ usleep(240000);
+
+ /* wait for response, 1st phase (2.1.4) */
+ i = FREAD;
+ ioctl(rodent.mfd, TIOCFLUSH, &i);
+ i = TIOCM_RTS; /* DTR = 1, RTS = 1 */
+ ioctl(rodent.mfd, TIOCMBIS, &i);
+
+ /* try to read something */
+ FD_ZERO(&fds);
+ FD_SET(rodent.mfd, &fds);
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 240000;
+ if (select(FD_SETSIZE, &fds, NULL, NULL, &timeout) > 0) {
+ debug("pnpwakeup1(): valid response in first phase.");
+ return (TRUE);
+ }
+
+ /* port setup, 2nd phase (2.1.5) */
+ i = TIOCM_DTR | TIOCM_RTS; /* DTR = 0, RTS = 0 */
+ ioctl(rodent.mfd, TIOCMBIC, &i);
+ usleep(240000);
+
+ /* wait for respose, 2nd phase (2.1.6) */
+ i = FREAD;
+ ioctl(rodent.mfd, TIOCFLUSH, &i);
+ i = TIOCM_DTR | TIOCM_RTS; /* DTR = 1, RTS = 1 */
+ ioctl(rodent.mfd, TIOCMBIS, &i);
+
+ /* try to read something */
+ FD_ZERO(&fds);
+ FD_SET(rodent.mfd, &fds);
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 240000;
+ if (select(FD_SETSIZE, &fds, NULL, NULL, &timeout) > 0) {
+ debug("pnpwakeup1(): valid response in second phase.");
+ return (TRUE);
+ }
+
+ return (FALSE);
+}
+
+static int
+pnpwakeup2(void)
+{
+ struct timeval timeout;
+ fd_set fds;
+ int i;
+
+ /*
+ * This is a simplified procedure; it simply toggles RTS.
+ */
+ debug("alternate probe...");
+
+ ioctl(rodent.mfd, TIOCMGET, &i);
+ i |= TIOCM_DTR; /* DTR = 1 */
+ i &= ~TIOCM_RTS; /* RTS = 0 */
+ ioctl(rodent.mfd, TIOCMSET, &i);
+ usleep(240000);
+
+ setmousespeed(1200, 1200, (CS7 | CREAD | CLOCAL | HUPCL));
+
+ /* wait for respose */
+ i = FREAD;
+ ioctl(rodent.mfd, TIOCFLUSH, &i);
+ i = TIOCM_DTR | TIOCM_RTS; /* DTR = 1, RTS = 1 */
+ ioctl(rodent.mfd, TIOCMBIS, &i);
+
+ /* try to read something */
+ FD_ZERO(&fds);
+ FD_SET(rodent.mfd, &fds);
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 240000;
+ if (select(FD_SETSIZE, &fds, NULL, NULL, &timeout) > 0) {
+ debug("pnpwakeup2(): valid response.");
+ return (TRUE);
+ }
+
+ return (FALSE);
+}
+
+static int
+pnpgets(char *buf)
+{
+ struct timeval timeout;
+ fd_set fds;
+ int begin;
+ int i;
+ char c;
+
+ if (!pnpwakeup1() && !pnpwakeup2()) {
+ /*
+ * According to PnP spec, we should set DTR = 1 and RTS = 0 while
+ * in idle state. But, `moused' shall set DTR = RTS = 1 and proceed,
+ * assuming there is something at the port even if it didn't
+ * respond to the PnP enumeration procedure.
+ */
+ i = TIOCM_DTR | TIOCM_RTS; /* DTR = 1, RTS = 1 */
+ ioctl(rodent.mfd, TIOCMBIS, &i);
+ return (0);
+ }
+
+ /* collect PnP COM device ID (2.1.7) */
+ begin = -1;
+ i = 0;
+ usleep(240000); /* the mouse must send `Begin ID' within 200msec */
+ while (read(rodent.mfd, &c, 1) == 1) {
+ /* we may see "M", or "M3..." before `Begin ID' */
+ buf[i++] = c;
+ if ((c == 0x08) || (c == 0x28)) { /* Begin ID */
+ debug("begin-id %02x", c);
+ begin = i - 1;
+ break;
+ }
+ debug("%c %02x", c, c);
+ if (i >= 256)
+ break;
+ }
+ if (begin < 0) {
+ /* we haven't seen `Begin ID' in time... */
+ goto connect_idle;
+ }
+
+ ++c; /* make it `End ID' */
+ for (;;) {
+ FD_ZERO(&fds);
+ FD_SET(rodent.mfd, &fds);
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 240000;
+ if (select(FD_SETSIZE, &fds, NULL, NULL, &timeout) <= 0)
+ break;
+
+ read(rodent.mfd, &buf[i], 1);
+ if (buf[i++] == c) /* End ID */
+ break;
+ if (i >= 256)
+ break;
+ }
+ if (begin > 0) {
+ i -= begin;
+ bcopy(&buf[begin], &buf[0], i);
+ }
+ /* string may not be human readable... */
+ debug("len:%d, '%-*.*s'", i, i, i, buf);
+
+ if (buf[i - 1] == c)
+ return (i); /* a valid PnP string */
+
+ /*
+ * According to PnP spec, we should set DTR = 1 and RTS = 0 while
+ * in idle state. But, `moused' shall leave the modem control lines
+ * as they are. See above.
+ */
+connect_idle:
+
+ /* we may still have something in the buffer */
+ return ((i > 0) ? i : 0);
+}
+
+static int
+pnpparse(pnpid_t *id, char *buf, int len)
+{
+ char s[3];
+ int offset;
+ int sum = 0;
+ int i, j;
+
+ id->revision = 0;
+ id->eisaid = NULL;
+ id->serial = NULL;
+ id->class = NULL;
+ id->compat = NULL;
+ id->description = NULL;
+ id->neisaid = 0;
+ id->nserial = 0;
+ id->nclass = 0;
+ id->ncompat = 0;
+ id->ndescription = 0;
+
+ if ((buf[0] != 0x28) && (buf[0] != 0x08)) {
+ /* non-PnP mice */
+ switch(buf[0]) {
+ default:
+ return (FALSE);
+ case 'M': /* Microsoft */
+ id->eisaid = "PNP0F01";
+ break;
+ case 'H': /* MouseSystems */
+ id->eisaid = "PNP0F04";
+ break;
+ }
+ id->neisaid = strlen(id->eisaid);
+ id->class = "MOUSE";
+ id->nclass = strlen(id->class);
+ debug("non-PnP mouse '%c'", buf[0]);
+ return (TRUE);
+ }
+
+ /* PnP mice */
+ offset = 0x28 - buf[0];
+
+ /* calculate checksum */
+ for (i = 0; i < len - 3; ++i) {
+ sum += buf[i];
+ buf[i] += offset;
+ }
+ sum += buf[len - 1];
+ for (; i < len; ++i)
+ buf[i] += offset;
+ debug("PnP ID string: '%*.*s'", len, len, buf);
+
+ /* revision */
+ buf[1] -= offset;
+ buf[2] -= offset;
+ id->revision = ((buf[1] & 0x3f) << 6) | (buf[2] & 0x3f);
+ debug("PnP rev %d.%02d", id->revision / 100, id->revision % 100);
+
+ /* EISA vender and product ID */
+ id->eisaid = &buf[3];
+ id->neisaid = 7;
+
+ /* option strings */
+ i = 10;
+ if (buf[i] == '\\') {
+ /* device serial # */
+ for (j = ++i; i < len; ++i) {
+ if (buf[i] == '\\')
+ break;
+ }
+ if (i >= len)
+ i -= 3;
+ if (i - j == 8) {
+ id->serial = &buf[j];
+ id->nserial = 8;
+ }
+ }
+ if (buf[i] == '\\') {
+ /* PnP class */
+ for (j = ++i; i < len; ++i) {
+ if (buf[i] == '\\')
+ break;
+ }
+ if (i >= len)
+ i -= 3;
+ if (i > j + 1) {
+ id->class = &buf[j];
+ id->nclass = i - j;
+ }
+ }
+ if (buf[i] == '\\') {
+ /* compatible driver */
+ for (j = ++i; i < len; ++i) {
+ if (buf[i] == '\\')
+ break;
+ }
+ /*
+ * PnP COM spec prior to v0.96 allowed '*' in this field,
+ * it's not allowed now; just igore it.
+ */
+ if (buf[j] == '*')
+ ++j;
+ if (i >= len)
+ i -= 3;
+ if (i > j + 1) {
+ id->compat = &buf[j];
+ id->ncompat = i - j;
+ }
+ }
+ if (buf[i] == '\\') {
+ /* product description */
+ for (j = ++i; i < len; ++i) {
+ if (buf[i] == ';')
+ break;
+ }
+ if (i >= len)
+ i -= 3;
+ if (i > j + 1) {
+ id->description = &buf[j];
+ id->ndescription = i - j;
+ }
+ }
+
+ /* checksum exists if there are any optional fields */
+ if ((id->nserial > 0) || (id->nclass > 0)
+ || (id->ncompat > 0) || (id->ndescription > 0)) {
+ debug("PnP checksum: 0x%X", sum);
+ sprintf(s, "%02X", sum & 0x0ff);
+ if (strncmp(s, &buf[len - 3], 2) != 0) {
+#if 0
+ /*
+ * I found some mice do not comply with the PnP COM device
+ * spec regarding checksum... XXX
+ */
+ logwarnx("PnP checksum error", 0);
+ return (FALSE);
+#endif
+ }
+ }
+
+ return (TRUE);
+}
+
+static symtab_t *
+pnpproto(pnpid_t *id)
+{
+ symtab_t *t;
+ int i, j;
+
+ if (id->nclass > 0)
+ if (strncmp(id->class, "MOUSE", id->nclass) != 0 &&
+ strncmp(id->class, "TABLET", id->nclass) != 0)
+ /* this is not a mouse! */
+ return (NULL);
+
+ if (id->neisaid > 0) {
+ t = gettoken(pnpprod, id->eisaid, id->neisaid);
+ if (t->val != MOUSE_PROTO_UNKNOWN)
+ return (t);
+ }
+
+ /*
+ * The 'Compatible drivers' field may contain more than one
+ * ID separated by ','.
+ */
+ if (id->ncompat <= 0)
+ return (NULL);
+ for (i = 0; i < id->ncompat; ++i) {
+ for (j = i; id->compat[i] != ','; ++i)
+ if (i >= id->ncompat)
+ break;
+ if (i > j) {
+ t = gettoken(pnpprod, id->compat + j, i - j);
+ if (t->val != MOUSE_PROTO_UNKNOWN)
+ return (t);
+ }
+ }
+
+ return (NULL);
+}
+
+/* name/val mapping */
+
+static symtab_t *
+gettoken(symtab_t *tab, const char *s, int len)
+{
+ int i;
+
+ for (i = 0; tab[i].name != NULL; ++i) {
+ if (strncmp(tab[i].name, s, len) == 0)
+ break;
+ }
+ return (&tab[i]);
+}
+
+static const char *
+gettokenname(symtab_t *tab, int val)
+{
+ static const char unknown[] = "unknown";
+ int i;
+
+ for (i = 0; tab[i].name != NULL; ++i) {
+ if (tab[i].val == val)
+ return (tab[i].name);
+ }
+ return (unknown);
+}
+
+
+/*
+ * code to read from the Genius Kidspad tablet.
+
+The tablet responds to the COM PnP protocol 1.0 with EISA-ID KYE0005,
+and to pre-pnp probes (RTS toggle) with 'T' (tablet ?)
+9600, 8 bit, parity odd.
+
+The tablet puts out 5 bytes. b0 (mask 0xb8, value 0xb8) contains
+the proximity, tip and button info:
+ (byte0 & 0x1) true = tip pressed
+ (byte0 & 0x2) true = button pressed
+ (byte0 & 0x40) false = pen in proximity of tablet.
+
+The next 4 bytes are used for coordinates xl, xh, yl, yh (7 bits valid).
+
+Only absolute coordinates are returned, so we use the following approach:
+we store the last coordinates sent when the pen went out of the tablet,
+
+
+ *
+ */
+
+typedef enum {
+ S_IDLE, S_PROXY, S_FIRST, S_DOWN, S_UP
+} k_status;
+
+static int
+kidspad(u_char rxc, mousestatus_t *act)
+{
+ static int buf[5];
+ static int buflen = 0, b_prev = 0 , x_prev = -1, y_prev = -1;
+ static k_status status = S_IDLE;
+ static struct timespec now;
+
+ int x, y;
+
+ if (buflen > 0 && (rxc & 0x80)) {
+ fprintf(stderr, "invalid code %d 0x%x\n", buflen, rxc);
+ buflen = 0;
+ }
+ if (buflen == 0 && (rxc & 0xb8) != 0xb8) {
+ fprintf(stderr, "invalid code 0 0x%x\n", rxc);
+ return (0); /* invalid code, no action */
+ }
+ buf[buflen++] = rxc;
+ if (buflen < 5)
+ return (0);
+
+ buflen = 0; /* for next time... */
+
+ x = buf[1]+128*(buf[2] - 7);
+ if (x < 0) x = 0;
+ y = 28*128 - (buf[3] + 128* (buf[4] - 7));
+ if (y < 0) y = 0;
+
+ x /= 8;
+ y /= 8;
+
+ act->flags = 0;
+ act->obutton = act->button;
+ act->dx = act->dy = act->dz = 0;
+ clock_gettime(CLOCK_MONOTONIC_FAST, &now);
+ if (buf[0] & 0x40) /* pen went out of reach */
+ status = S_IDLE;
+ else if (status == S_IDLE) { /* pen is newly near the tablet */
+ act->flags |= MOUSE_POSCHANGED; /* force update */
+ status = S_PROXY;
+ x_prev = x;
+ y_prev = y;
+ }
+ act->dx = x - x_prev;
+ act->dy = y - y_prev;
+ if (act->dx || act->dy)
+ act->flags |= MOUSE_POSCHANGED;
+ x_prev = x;
+ y_prev = y;
+ if (b_prev != 0 && b_prev != buf[0]) { /* possibly record button change */
+ act->button = 0;
+ if (buf[0] & 0x01) /* tip pressed */
+ act->button |= MOUSE_BUTTON1DOWN;
+ if (buf[0] & 0x02) /* button pressed */
+ act->button |= MOUSE_BUTTON2DOWN;
+ act->flags |= MOUSE_BUTTONSCHANGED;
+ }
+ b_prev = buf[0];
+ return (act->flags);
+}
+
+static int
+gtco_digipad (u_char rxc, mousestatus_t *act)
+{
+ static u_char buf[5];
+ static int buflen = 0, b_prev = 0 , x_prev = -1, y_prev = -1;
+ static k_status status = S_IDLE;
+ int x, y;
+
+#define GTCO_HEADER 0x80
+#define GTCO_PROXIMITY 0x40
+#define GTCO_START (GTCO_HEADER|GTCO_PROXIMITY)
+#define GTCO_BUTTONMASK 0x3c
+
+
+ if (buflen > 0 && ((rxc & GTCO_HEADER) != GTCO_HEADER)) {
+ fprintf(stderr, "invalid code %d 0x%x\n", buflen, rxc);
+ buflen = 0;
+ }
+ if (buflen == 0 && (rxc & GTCO_START) != GTCO_START) {
+ fprintf(stderr, "invalid code 0 0x%x\n", rxc);
+ return (0); /* invalid code, no action */
+ }
+
+ buf[buflen++] = rxc;
+ if (buflen < 5)
+ return (0);
+
+ buflen = 0; /* for next time... */
+
+ x = ((buf[2] & ~GTCO_START) << 6 | (buf[1] & ~GTCO_START));
+ y = 4768 - ((buf[4] & ~GTCO_START) << 6 | (buf[3] & ~GTCO_START));
+
+ x /= 2.5;
+ y /= 2.5;
+
+ act->flags = 0;
+ act->obutton = act->button;
+ act->dx = act->dy = act->dz = 0;
+
+ if ((buf[0] & 0x40) == 0) /* pen went out of reach */
+ status = S_IDLE;
+ else if (status == S_IDLE) { /* pen is newly near the tablet */
+ act->flags |= MOUSE_POSCHANGED; /* force update */
+ status = S_PROXY;
+ x_prev = x;
+ y_prev = y;
+ }
+
+ act->dx = x - x_prev;
+ act->dy = y - y_prev;
+ if (act->dx || act->dy)
+ act->flags |= MOUSE_POSCHANGED;
+ x_prev = x;
+ y_prev = y;
+
+ /* possibly record button change */
+ if (b_prev != 0 && b_prev != buf[0]) {
+ act->button = 0;
+ if (buf[0] & 0x04) {
+ /* tip pressed/yellow */
+ act->button |= MOUSE_BUTTON1DOWN;
+ }
+ if (buf[0] & 0x08) {
+ /* grey/white */
+ act->button |= MOUSE_BUTTON2DOWN;
+ }
+ if (buf[0] & 0x10) {
+ /* black/green */
+ act->button |= MOUSE_BUTTON3DOWN;
+ }
+ if (buf[0] & 0x20) {
+ /* tip+grey/blue */
+ act->button |= MOUSE_BUTTON4DOWN;
+ }
+ act->flags |= MOUSE_BUTTONSCHANGED;
+ }
+ b_prev = buf[0];
+ return (act->flags);
+}
+
+static void
+mremote_serversetup(void)
+{
+ struct sockaddr_un ad;
+
+ /* Open a UNIX domain stream socket to listen for mouse remote clients */
+ unlink(_PATH_MOUSEREMOTE);
+
+ if ((rodent.mremsfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+ logerrx(1, "unable to create unix domain socket %s",_PATH_MOUSEREMOTE);
+
+ umask(0111);
+
+ bzero(&ad, sizeof(ad));
+ ad.sun_family = AF_UNIX;
+ strcpy(ad.sun_path, _PATH_MOUSEREMOTE);
+#ifndef SUN_LEN
+#define SUN_LEN(unp) (((char *)(unp)->sun_path - (char *)(unp)) + \
+ strlen((unp)->path))
+#endif
+ if (bind(rodent.mremsfd, (struct sockaddr *) &ad, SUN_LEN(&ad)) < 0)
+ logerrx(1, "unable to bind unix domain socket %s", _PATH_MOUSEREMOTE);
+
+ listen(rodent.mremsfd, 1);
+}
+
+static void
+mremote_clientchg(int add)
+{
+ struct sockaddr_un ad;
+ socklen_t ad_len;
+ int fd;
+
+ if (rodent.rtype != MOUSE_PROTO_X10MOUSEREM)
+ return;
+
+ if (add) {
+ /* Accept client connection, if we don't already have one */
+ ad_len = sizeof(ad);
+ fd = accept(rodent.mremsfd, (struct sockaddr *) &ad, &ad_len);
+ if (fd < 0)
+ logwarnx("failed accept on mouse remote socket");
+
+ if (rodent.mremcfd < 0) {
+ rodent.mremcfd = fd;
+ debug("remote client connect...accepted");
+ }
+ else {
+ close(fd);
+ debug("another remote client connect...disconnected");
+ }
+ }
+ else {
+ /* Client disconnected */
+ debug("remote client disconnected");
+ close(rodent.mremcfd);
+ rodent.mremcfd = -1;
+ }
+}
diff --git a/usr.sbin/mpsutil/Makefile b/usr.sbin/mpsutil/Makefile
new file mode 100644
index 0000000..8ee4ec5
--- /dev/null
+++ b/usr.sbin/mpsutil/Makefile
@@ -0,0 +1,22 @@
+# $FreeBSD$
+
+PROG= mpsutil
+SRCS= mps_cmd.c mps_flash.c mps_show.c mpsutil.c
+MAN= mpsutil.8
+
+WARNS?= 3
+
+#LIBADD= cam util
+LINKS= ${BINDIR}/mpsutil ${BINDIR}/mprutil
+MLINKS= mpsutil.8 mprutil.8
+
+CFLAGS+= -I${.CURDIR}/../../sys -I. -DUSE_MPT_IOCTLS
+# Avoid dirdep dependency on libutil
+CFLAGS+= -I${SRCTOP}/lib/libutil
+
+# Here be dragons
+.ifdef DEBUG
+CFLAGS+= -DDEBUG
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/mpsutil/Makefile.depend b/usr.sbin/mpsutil/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/mpsutil/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/mpsutil/mpr_ioctl.h b/usr.sbin/mpsutil/mpr_ioctl.h
new file mode 100644
index 0000000..82627b9
--- /dev/null
+++ b/usr.sbin/mpsutil/mpr_ioctl.h
@@ -0,0 +1,388 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@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.
+ * 3. 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 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.
+ *
+ * LSI MPT-Fusion Host Adapter FreeBSD userland interface
+ *
+ * $FreeBSD$
+ */
+/*-
+ * Copyright (c) 2011-2014 LSI Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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.
+ *
+ * LSI MPT-Fusion Host Adapter FreeBSD
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _MPR_IOCTL_H_
+#define _MPR_IOCTL_H_
+
+#include <dev/mpr/mpi/mpi2_type.h>
+#include <dev/mpr/mpi/mpi2.h>
+#include <dev/mpr/mpi/mpi2_cnfg.h>
+#include <dev/mpr/mpi/mpi2_sas.h>
+
+/*
+ * For the read header requests, the header should include the page
+ * type or extended page type, page number, and page version. The
+ * buffer and length are unused. The completed header is returned in
+ * the 'header' member.
+ *
+ * For the read page and write page requests, 'buf' should point to a
+ * buffer of 'len' bytes which holds the entire page (including the
+ * header).
+ *
+ * All requests specify the page address in 'page_address'.
+ */
+struct mpr_cfg_page_req {
+ MPI2_CONFIG_PAGE_HEADER header;
+ uint32_t page_address;
+ void *buf;
+ int len;
+ uint16_t ioc_status;
+};
+
+struct mpr_ext_cfg_page_req {
+ MPI2_CONFIG_EXTENDED_PAGE_HEADER header;
+ uint32_t page_address;
+ void *buf;
+ int len;
+ uint16_t ioc_status;
+};
+
+struct mpr_raid_action {
+ uint8_t action;
+ uint8_t volume_bus;
+ uint8_t volume_id;
+ uint8_t phys_disk_num;
+ uint32_t action_data_word;
+ void *buf;
+ int len;
+ uint32_t volume_status;
+ uint32_t action_data[4];
+ uint16_t action_status;
+ uint16_t ioc_status;
+ uint8_t write;
+};
+
+struct mpr_usr_command {
+ void *req;
+ uint32_t req_len;
+ void *rpl;
+ uint32_t rpl_len;
+ void *buf;
+ int len;
+ uint32_t flags;
+};
+
+typedef struct mpr_pci_bits
+{
+ union {
+ struct {
+ uint32_t DeviceNumber :5;
+ uint32_t FunctionNumber :3;
+ uint32_t BusNumber :24;
+ } bits;
+ uint32_t AsDWORD;
+ } u;
+ uint32_t PciSegmentId;
+} mpr_pci_bits_t;
+
+/*
+ * The following is the MPRIOCTL_GET_ADAPTER_DATA data structure. This data
+ * structure is setup so that we hopefully are properly aligned for both
+ * 32-bit and 64-bit mode applications.
+ *
+ * Adapter Type - Value = 6 = SCSI Protocol through SAS-3 adapter
+ *
+ * MPI Port Number - The PCI Function number for this device
+ *
+ * PCI Device HW Id - The PCI device number for this device
+ *
+ */
+#define MPRIOCTL_ADAPTER_TYPE_SAS3 6
+typedef struct mpr_adapter_data
+{
+ uint32_t StructureLength;
+ uint32_t AdapterType;
+ uint32_t MpiPortNumber;
+ uint32_t PCIDeviceHwId;
+ uint32_t PCIDeviceHwRev;
+ uint32_t SubSystemId;
+ uint32_t SubsystemVendorId;
+ uint32_t Reserved1;
+ uint32_t MpiFirmwareVersion;
+ uint32_t BiosVersion;
+ uint8_t DriverVersion[32];
+ uint8_t Reserved2;
+ uint8_t ScsiId;
+ uint16_t Reserved3;
+ mpr_pci_bits_t PciInformation;
+} mpr_adapter_data_t;
+
+
+typedef struct mpr_update_flash
+{
+ uint64_t PtrBuffer;
+ uint32_t ImageChecksum;
+ uint32_t ImageOffset;
+ uint32_t ImageSize;
+ uint32_t ImageType;
+} mpr_update_flash_t;
+
+
+#define MPR_PASS_THRU_DIRECTION_NONE 0
+#define MPR_PASS_THRU_DIRECTION_READ 1
+#define MPR_PASS_THRU_DIRECTION_WRITE 2
+#define MPR_PASS_THRU_DIRECTION_BOTH 3
+
+typedef struct mpr_pass_thru
+{
+ uint64_t PtrRequest;
+ uint64_t PtrReply;
+ uint64_t PtrData;
+ uint32_t RequestSize;
+ uint32_t ReplySize;
+ uint32_t DataSize;
+ uint32_t DataDirection;
+ uint64_t PtrDataOut;
+ uint32_t DataOutSize;
+ uint32_t Timeout;
+} mpr_pass_thru_t;
+
+
+/*
+ * Event queue defines
+ */
+#define MPR_EVENT_QUEUE_SIZE (50) /* Max Events stored in driver */
+#define MPR_MAX_EVENT_DATA_LENGTH (48) /* Size of each event in Dwords */
+
+typedef struct mpr_event_query
+{
+ uint16_t Entries;
+ uint16_t Reserved;
+ uint32_t Types[4];
+} mpr_event_query_t;
+
+typedef struct mpr_event_enable
+{
+ uint32_t Types[4];
+} mpr_event_enable_t;
+
+/*
+ * Event record entry for ioctl.
+ */
+typedef struct mpr_event_entry
+{
+ uint32_t Type;
+ uint32_t Number;
+ uint32_t Data[MPR_MAX_EVENT_DATA_LENGTH];
+} mpr_event_entry_t;
+
+typedef struct mpr_event_report
+{
+ uint32_t Size;
+ uint64_t PtrEvents;
+} mpr_event_report_t;
+
+
+typedef struct mpr_pci_info
+{
+ uint32_t BusNumber;
+ uint8_t DeviceNumber;
+ uint8_t FunctionNumber;
+ uint16_t InterruptVector;
+ uint8_t PciHeader[256];
+} mpr_pci_info_t;
+
+
+typedef struct mpr_diag_action
+{
+ uint32_t Action;
+ uint32_t Length;
+ uint64_t PtrDiagAction;
+ uint32_t ReturnCode;
+} mpr_diag_action_t;
+
+#define MPR_FW_DIAGNOSTIC_UID_NOT_FOUND (0xFF)
+
+#define MPR_FW_DIAG_NEW (0x806E6577)
+
+#define MPR_FW_DIAG_TYPE_REGISTER (0x00000001)
+#define MPR_FW_DIAG_TYPE_UNREGISTER (0x00000002)
+#define MPR_FW_DIAG_TYPE_QUERY (0x00000003)
+#define MPR_FW_DIAG_TYPE_READ_BUFFER (0x00000004)
+#define MPR_FW_DIAG_TYPE_RELEASE (0x00000005)
+
+#define MPR_FW_DIAG_INVALID_UID (0x00000000)
+
+#define MPR_DIAG_SUCCESS 0
+#define MPR_DIAG_FAILURE 1
+
+#define MPR_FW_DIAG_ERROR_SUCCESS (0x00000000)
+#define MPR_FW_DIAG_ERROR_FAILURE (0x00000001)
+#define MPR_FW_DIAG_ERROR_INVALID_PARAMETER (0x00000002)
+#define MPR_FW_DIAG_ERROR_POST_FAILED (0x00000010)
+#define MPR_FW_DIAG_ERROR_INVALID_UID (0x00000011)
+#define MPR_FW_DIAG_ERROR_RELEASE_FAILED (0x00000012)
+#define MPR_FW_DIAG_ERROR_NO_BUFFER (0x00000013)
+#define MPR_FW_DIAG_ERROR_ALREADY_RELEASED (0x00000014)
+
+
+typedef struct mpr_fw_diag_register
+{
+ uint8_t ExtendedType;
+ uint8_t BufferType;
+ uint16_t ApplicationFlags;
+ uint32_t DiagnosticFlags;
+ uint32_t ProductSpecific[23];
+ uint32_t RequestedBufferSize;
+ uint32_t UniqueId;
+} mpr_fw_diag_register_t;
+
+typedef struct mpr_fw_diag_unregister
+{
+ uint32_t UniqueId;
+} mpr_fw_diag_unregister_t;
+
+#define MPR_FW_DIAG_FLAG_APP_OWNED (0x0001)
+#define MPR_FW_DIAG_FLAG_BUFFER_VALID (0x0002)
+#define MPR_FW_DIAG_FLAG_FW_BUFFER_ACCESS (0x0004)
+
+typedef struct mpr_fw_diag_query
+{
+ uint8_t ExtendedType;
+ uint8_t BufferType;
+ uint16_t ApplicationFlags;
+ uint32_t DiagnosticFlags;
+ uint32_t ProductSpecific[23];
+ uint32_t TotalBufferSize;
+ uint32_t DriverAddedBufferSize;
+ uint32_t UniqueId;
+} mpr_fw_diag_query_t;
+
+typedef struct mpr_fw_diag_release
+{
+ uint32_t UniqueId;
+} mpr_fw_diag_release_t;
+
+#define MPR_FW_DIAG_FLAG_REREGISTER (0x0001)
+#define MPR_FW_DIAG_FLAG_FORCE_RELEASE (0x0002)
+
+typedef struct mpr_diag_read_buffer
+{
+ uint8_t Status;
+ uint8_t Reserved;
+ uint16_t Flags;
+ uint32_t StartingOffset;
+ uint32_t BytesToRead;
+ uint32_t UniqueId;
+ uint64_t PtrDataBuffer;
+} mpr_diag_read_buffer_t;
+
+/*
+ * Register Access
+ */
+#define REG_IO_READ 1
+#define REG_IO_WRITE 2
+#define REG_MEM_READ 3
+#define REG_MEM_WRITE 4
+
+typedef struct mpr_reg_access
+{
+ uint32_t Command;
+ uint32_t RegOffset;
+ uint32_t RegData;
+} mpr_reg_access_t;
+
+typedef struct mpr_btdh_mapping
+{
+ uint16_t TargetID;
+ uint16_t Bus;
+ uint16_t DevHandle;
+ uint16_t Reserved;
+} mpr_btdh_mapping_t;
+
+#define MPRIO_MPR_COMMAND_FLAG_VERBOSE 0x01
+#define MPRIO_MPR_COMMAND_FLAG_DEBUG 0x02
+#define MPRIO_READ_CFG_HEADER _IOWR('M', 200, struct mpr_cfg_page_req)
+#define MPRIO_READ_CFG_PAGE _IOWR('M', 201, struct mpr_cfg_page_req)
+#define MPRIO_READ_EXT_CFG_HEADER _IOWR('M', 202, struct mpr_ext_cfg_page_req)
+#define MPRIO_READ_EXT_CFG_PAGE _IOWR('M', 203, struct mpr_ext_cfg_page_req)
+#define MPRIO_WRITE_CFG_PAGE _IOWR('M', 204, struct mpr_cfg_page_req)
+#define MPRIO_RAID_ACTION _IOWR('M', 205, struct mpr_raid_action)
+#define MPRIO_MPR_COMMAND _IOWR('M', 210, struct mpr_usr_command)
+
+#ifndef MPTIOCTL
+#define MPTIOCTL ('I')
+#define MPTIOCTL_GET_ADAPTER_DATA _IOWR(MPTIOCTL, 1,\
+ struct mpr_adapter_data)
+#define MPTIOCTL_UPDATE_FLASH _IOWR(MPTIOCTL, 2,\
+ struct mpr_update_flash)
+#define MPTIOCTL_RESET_ADAPTER _IO(MPTIOCTL, 3)
+#define MPTIOCTL_PASS_THRU _IOWR(MPTIOCTL, 4,\
+ struct mpr_pass_thru)
+#define MPTIOCTL_EVENT_QUERY _IOWR(MPTIOCTL, 5,\
+ struct mpr_event_query)
+#define MPTIOCTL_EVENT_ENABLE _IOWR(MPTIOCTL, 6,\
+ struct mpr_event_enable)
+#define MPTIOCTL_EVENT_REPORT _IOWR(MPTIOCTL, 7,\
+ struct mpr_event_report)
+#define MPTIOCTL_GET_PCI_INFO _IOWR(MPTIOCTL, 8,\
+ struct mpr_pci_info)
+#define MPTIOCTL_DIAG_ACTION _IOWR(MPTIOCTL, 9,\
+ struct mpr_diag_action)
+#define MPTIOCTL_REG_ACCESS _IOWR(MPTIOCTL, 10,\
+ struct mpr_reg_access)
+#define MPTIOCTL_BTDH_MAPPING _IOWR(MPTIOCTL, 11,\
+ struct mpr_btdh_mapping)
+#endif
+
+#endif /* !_MPR_IOCTL_H_ */
diff --git a/usr.sbin/mpsutil/mps_cmd.c b/usr.sbin/mpsutil/mps_cmd.c
new file mode 100644
index 0000000..24ed74e
--- /dev/null
+++ b/usr.sbin/mpsutil/mps_cmd.c
@@ -0,0 +1,731 @@
+/*-
+ * Copyright (c) 2015 Baptiste Daroussin <bapt@FreeBSD.org>
+ *
+ * Copyright (c) 2015 Netflix, Inc.
+ * All rights reserved.
+ * Written by: Scott Long <scottl@freebsd.org>
+ *
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@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.
+ * 3. 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 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>
+__RCSID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <sys/ioctl.h>
+#if 0
+#include <sys/mps_ioctl.h>
+#else
+#include "mps_ioctl.h"
+#include "mpr_ioctl.h"
+#endif
+#include <sys/sysctl.h>
+#include <sys/uio.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mpsutil.h"
+
+#ifndef USE_MPT_IOCTLS
+#define USE_MPT_IOCTLS
+#endif
+
+static const char *mps_ioc_status_codes[] = {
+ "Success", /* 0x0000 */
+ "Invalid function",
+ "Busy",
+ "Invalid scatter-gather list",
+ "Internal error",
+ "Reserved",
+ "Insufficient resources",
+ "Invalid field",
+ "Invalid state", /* 0x0008 */
+ "Operation state not supported",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* 0x0010 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* 0x0018 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "Invalid configuration action", /* 0x0020 */
+ "Invalid configuration type",
+ "Invalid configuration page",
+ "Invalid configuration data",
+ "No configuration defaults",
+ "Unable to commit configuration change",
+ NULL,
+ NULL,
+ NULL, /* 0x0028 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* 0x0030 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* 0x0038 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "Recovered SCSI error", /* 0x0040 */
+ "Invalid SCSI bus",
+ "Invalid SCSI target ID",
+ "SCSI device not there",
+ "SCSI data overrun",
+ "SCSI data underrun",
+ "SCSI I/O error",
+ "SCSI protocol error",
+ "SCSI task terminated", /* 0x0048 */
+ "SCSI residual mismatch",
+ "SCSI task management failed",
+ "SCSI I/O controller terminated",
+ "SCSI external controller terminated",
+ "EEDP guard error",
+ "EEDP reference tag error",
+ "EEDP application tag error",
+ NULL, /* 0x0050 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* 0x0058 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "SCSI target priority I/O", /* 0x0060 */
+ "Invalid SCSI target port",
+ "Invalid SCSI target I/O index",
+ "SCSI target aborted",
+ "No connection retryable",
+ "No connection",
+ "FC aborted",
+ "Invalid FC receive ID",
+ "FC did invalid", /* 0x0068 */
+ "FC node logged out",
+ "Transfer count mismatch",
+ "STS data not set",
+ "FC exchange canceled",
+ "Data offset error",
+ "Too much write data",
+ "IU too short",
+ "ACK NAK timeout", /* 0x0070 */
+ "NAK received",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* 0x0078 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "LAN device not found", /* 0x0080 */
+ "LAN device failure",
+ "LAN transmit error",
+ "LAN transmit aborted",
+ "LAN receive error",
+ "LAN receive aborted",
+ "LAN partial packet",
+ "LAN canceled",
+ NULL, /* 0x0088 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "SAS SMP request failed", /* 0x0090 */
+ "SAS SMP data overrun",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "Inband aborted", /* 0x0098 */
+ "No inband connection",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "Diagnostic released", /* 0x00A0 */
+};
+
+struct mprs_pass_thru {
+ uint64_t PtrRequest;
+ uint64_t PtrReply;
+ uint64_t PtrData;
+ uint32_t RequestSize;
+ uint32_t ReplySize;
+ uint32_t DataSize;
+ uint32_t DataDirection;
+ uint64_t PtrDataOut;
+ uint32_t DataOutSize;
+ uint32_t Timeout;
+};
+
+struct mprs_btdh_mapping {
+ uint16_t TargetID;
+ uint16_t Bus;
+ uint16_t DevHandle;
+ uint16_t Reserved;
+};
+
+const char *
+mps_ioc_status(U16 IOCStatus)
+{
+ static char buffer[16];
+
+ IOCStatus &= MPI2_IOCSTATUS_MASK;
+ if (IOCStatus < sizeof(mps_ioc_status_codes) / sizeof(char *) &&
+ mps_ioc_status_codes[IOCStatus] != NULL)
+ return (mps_ioc_status_codes[IOCStatus]);
+ snprintf(buffer, sizeof(buffer), "Status: 0x%04x", IOCStatus);
+ return (buffer);
+}
+
+#ifdef USE_MPT_IOCTLS
+int
+mps_map_btdh(int fd, uint16_t *devhandle, uint16_t *bus, uint16_t *target)
+{
+ int error;
+ struct mprs_btdh_mapping map;
+
+ map.Bus = *bus;
+ map.TargetID = *target;
+ map.DevHandle = *devhandle;
+
+ if ((error = ioctl(fd, MPTIOCTL_BTDH_MAPPING, &map)) != 0) {
+ error = errno;
+ warn("Failed to map bus/target/device");
+ return (error);
+ }
+
+ *bus = map.Bus;
+ *target = map.TargetID;
+ *devhandle = map.DevHandle;
+
+ return (0);
+}
+
+int
+mps_read_config_page_header(int fd, U8 PageType, U8 PageNumber, U32 PageAddress,
+ MPI2_CONFIG_PAGE_HEADER *header, U16 *IOCStatus)
+{
+ MPI2_CONFIG_REQUEST req;
+ MPI2_CONFIG_REPLY reply;
+
+ bzero(&req, sizeof(req));
+ req.Function = MPI2_FUNCTION_CONFIG;
+ req.Action = MPI2_CONFIG_ACTION_PAGE_HEADER;
+ req.Header.PageType = PageType;
+ req.Header.PageNumber = PageNumber;
+ req.PageAddress = PageAddress;
+
+ if (mps_pass_command(fd, &req, sizeof(req), &reply, sizeof(reply),
+ NULL, 0, NULL, 0, 30))
+ return (errno);
+
+ if (!IOC_STATUS_SUCCESS(reply.IOCStatus)) {
+ if (IOCStatus != NULL)
+ *IOCStatus = reply.IOCStatus;
+ return (EIO);
+ }
+ if (header == NULL)
+ return (EINVAL);
+ *header = reply.Header;
+ return (0);
+}
+
+int
+mps_read_ext_config_page_header(int fd, U8 ExtPageType, U8 PageNumber, U32 PageAddress, MPI2_CONFIG_PAGE_HEADER *header, U16 *ExtPageLength, U16 *IOCStatus)
+{
+ MPI2_CONFIG_REQUEST req;
+ MPI2_CONFIG_REPLY reply;
+
+ bzero(&req, sizeof(req));
+ req.Function = MPI2_FUNCTION_CONFIG;
+ req.Action = MPI2_CONFIG_ACTION_PAGE_HEADER;
+ req.Header.PageType = MPI2_CONFIG_PAGETYPE_EXTENDED;
+ req.ExtPageType = ExtPageType;
+ req.Header.PageNumber = PageNumber;
+ req.PageAddress = PageAddress;
+
+ if (mps_pass_command(fd, &req, sizeof(req), &reply, sizeof(reply),
+ NULL, 0, NULL, 0, 30))
+ return (errno);
+
+ if (!IOC_STATUS_SUCCESS(reply.IOCStatus)) {
+ if (IOCStatus != NULL)
+ *IOCStatus = reply.IOCStatus;
+ return (EIO);
+ }
+ if ((header == NULL) || (ExtPageLength == NULL))
+ return (EINVAL);
+ *header = reply.Header;
+ *ExtPageLength = reply.ExtPageLength;
+ return (0);
+}
+
+void *
+mps_read_config_page(int fd, U8 PageType, U8 PageNumber, U32 PageAddress,
+ U16 *IOCStatus)
+{
+ MPI2_CONFIG_REQUEST req;
+ MPI2_CONFIG_PAGE_HEADER header;
+ MPI2_CONFIG_REPLY reply;
+ void *buf;
+ int error, len;
+
+ bzero(&header, sizeof(header));
+ error = mps_read_config_page_header(fd, PageType, PageNumber,
+ PageAddress, &header, IOCStatus);
+ if (error) {
+ errno = error;
+ return (NULL);
+ }
+
+ bzero(&req, sizeof(req));
+ req.Function = MPI2_FUNCTION_CONFIG;
+ req.Action = MPI2_CONFIG_ACTION_PAGE_READ_CURRENT;
+ req.PageAddress = PageAddress;
+ req.Header = header;
+ req.Header.PageLength = reply.Header.PageLength;
+ if (reply.Header.PageLength == 0)
+ req.Header.PageLength = 4;
+
+ len = req.Header.PageLength * 4;
+ buf = malloc(len);
+ if (mps_pass_command(fd, &req, sizeof(req), &reply, sizeof(reply),
+ buf, len, NULL, 0, 30)) {
+ error = errno;
+ free(buf);
+ errno = error;
+ return (NULL);
+ }
+ if (!IOC_STATUS_SUCCESS(reply.IOCStatus)) {
+ if (IOCStatus != NULL)
+ *IOCStatus = reply.IOCStatus;
+ else
+ warnx("Reading config page failed: 0x%x %s",
+ reply.IOCStatus, mps_ioc_status(reply.IOCStatus));
+ free(buf);
+ errno = EIO;
+ return (NULL);
+ }
+ return (buf);
+}
+
+void *
+mps_read_extended_config_page(int fd, U8 ExtPageType, U8 PageVersion,
+ U8 PageNumber, U32 PageAddress, U16 *IOCStatus)
+{
+ MPI2_CONFIG_REQUEST req;
+ MPI2_CONFIG_PAGE_HEADER header;
+ MPI2_CONFIG_REPLY reply;
+ U16 pagelen;
+ void *buf;
+ int error, len;
+
+ if (IOCStatus != NULL)
+ *IOCStatus = MPI2_IOCSTATUS_SUCCESS;
+ bzero(&header, sizeof(header));
+ error = mps_read_ext_config_page_header(fd, ExtPageType, PageNumber,
+ PageAddress, &header, &pagelen, IOCStatus);
+ if (error) {
+ errno = error;
+ return (NULL);
+ }
+
+ bzero(&req, sizeof(req));
+ req.Function = MPI2_FUNCTION_CONFIG;
+ req.Action = MPI2_CONFIG_ACTION_PAGE_READ_CURRENT;
+ req.PageAddress = PageAddress;
+ req.Header = header;
+ if (pagelen == 0)
+ pagelen = 4;
+ req.ExtPageLength = pagelen;
+ req.ExtPageType = ExtPageType;
+
+ len = pagelen * 4;
+ buf = malloc(len);
+ if (mps_pass_command(fd, &req, sizeof(req), &reply, sizeof(reply),
+ buf, len, NULL, 0, 30)) {
+ error = errno;
+ free(buf);
+ errno = error;
+ return (NULL);
+ }
+ if (!IOC_STATUS_SUCCESS(reply.IOCStatus)) {
+ if (IOCStatus != NULL)
+ *IOCStatus = reply.IOCStatus;
+ else
+ warnx("Reading extended config page failed: %s",
+ mps_ioc_status(reply.IOCStatus));
+ free(buf);
+ errno = EIO;
+ return (NULL);
+ }
+ return (buf);
+}
+
+int
+mps_firmware_send(int fd, unsigned char *fw, uint32_t len, bool bios)
+{
+ MPI2_FW_DOWNLOAD_REQUEST req;
+ MPI2_FW_DOWNLOAD_REPLY reply;
+
+ bzero(&req, sizeof(req));
+ bzero(&reply, sizeof(reply));
+ req.Function = MPI2_FUNCTION_FW_DOWNLOAD;
+ req.ImageType = bios ? MPI2_FW_DOWNLOAD_ITYPE_BIOS : MPI2_FW_DOWNLOAD_ITYPE_FW;
+ req.TotalImageSize = len;
+ req.MsgFlags = MPI2_FW_DOWNLOAD_MSGFLGS_LAST_SEGMENT;
+
+ if (mps_user_command(fd, &req, sizeof(req), &reply, sizeof(reply),
+ fw, len, 0)) {
+ return (-1);
+ }
+ return (0);
+}
+
+int
+mps_firmware_get(int fd, unsigned char **firmware, bool bios)
+{
+ MPI2_FW_UPLOAD_REQUEST req;
+ MPI2_FW_UPLOAD_REPLY reply;
+ int size;
+
+ *firmware = NULL;
+ bzero(&req, sizeof(req));
+ bzero(&reply, sizeof(reply));
+ req.Function = MPI2_FUNCTION_FW_UPLOAD;
+ req.ImageType = bios ? MPI2_FW_DOWNLOAD_ITYPE_BIOS : MPI2_FW_DOWNLOAD_ITYPE_FW;
+
+ if (mps_user_command(fd, &req, sizeof(req), &reply, sizeof(reply),
+ NULL, 0, 0)) {
+ return (-1);
+ }
+ if (reply.ActualImageSize == 0) {
+ return (-1);
+ }
+
+ size = reply.ActualImageSize;
+ *firmware = calloc(1, sizeof(unsigned char) * size);
+ if (*firmware == NULL) {
+ warn("calloc");
+ return (-1);
+ }
+ if (mps_user_command(fd, &req, sizeof(req), &reply, sizeof(reply),
+ *firmware, size, 0)) {
+ free(*firmware);
+ return (-1);
+ }
+
+ return (size);
+}
+
+#else
+
+int
+mps_read_config_page_header(int fd, U8 PageType, U8 PageNumber, U32 PageAddress,
+ MPI2_CONFIG_PAGE_HEADER *header, U16 *IOCStatus)
+{
+ struct mps_cfg_page_req req;
+
+ if (IOCStatus != NULL)
+ *IOCStatus = MPI2_IOCSTATUS_SUCCESS;
+ if (header == NULL)
+ return (EINVAL);
+ bzero(&req, sizeof(req));
+ req.header.PageType = PageType;
+ req.header.PageNumber = PageNumber;
+ req.page_address = PageAddress;
+ if (ioctl(fd, MPSIO_READ_CFG_HEADER, &req) < 0)
+ return (errno);
+ if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
+ if (IOCStatus != NULL)
+ *IOCStatus = req.ioc_status;
+ return (EIO);
+ }
+ bcopy(&req.header, header, sizeof(*header));
+ return (0);
+}
+
+void *
+mps_read_config_page(int fd, U8 PageType, U8 PageNumber, U32 PageAddress,
+ U16 *IOCStatus)
+{
+ struct mps_cfg_page_req req;
+ void *buf;
+ int error;
+
+ error = mps_read_config_page_header(fd, PageType, PageNumber,
+ PageAddress, &req.header, IOCStatus);
+ if (error) {
+ errno = error;
+ return (NULL);
+ }
+
+ if (req.header.PageLength == 0)
+ req.header.PageLength = 4;
+ req.len = req.header.PageLength * 4;
+ buf = malloc(req.len);
+ req.buf = buf;
+ bcopy(&req.header, buf, sizeof(req.header));
+ if (ioctl(fd, MPSIO_READ_CFG_PAGE, &req) < 0) {
+ error = errno;
+ free(buf);
+ errno = error;
+ return (NULL);
+ }
+ if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
+ if (IOCStatus != NULL)
+ *IOCStatus = req.ioc_status;
+ else
+ warnx("Reading config page failed: 0x%x %s",
+ req.ioc_status, mps_ioc_status(req.ioc_status));
+ free(buf);
+ errno = EIO;
+ return (NULL);
+ }
+ return (buf);
+}
+
+void *
+mps_read_extended_config_page(int fd, U8 ExtPageType, U8 PageVersion,
+ U8 PageNumber, U32 PageAddress, U16 *IOCStatus)
+{
+ struct mps_ext_cfg_page_req req;
+ void *buf;
+ int error;
+
+ if (IOCStatus != NULL)
+ *IOCStatus = MPI2_IOCSTATUS_SUCCESS;
+ bzero(&req, sizeof(req));
+ req.header.PageVersion = PageVersion;
+ req.header.PageNumber = PageNumber;
+ req.header.ExtPageType = ExtPageType;
+ req.page_address = PageAddress;
+ if (ioctl(fd, MPSIO_READ_EXT_CFG_HEADER, &req) < 0)
+ return (NULL);
+ if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
+ if (IOCStatus != NULL)
+ *IOCStatus = req.ioc_status;
+ else
+ warnx("Reading extended config page header failed: %s",
+ mps_ioc_status(req.ioc_status));
+ errno = EIO;
+ return (NULL);
+ }
+ req.len = req.header.ExtPageLength * 4;
+ buf = malloc(req.len);
+ req.buf = buf;
+ bcopy(&req.header, buf, sizeof(req.header));
+ if (ioctl(fd, MPSIO_READ_EXT_CFG_PAGE, &req) < 0) {
+ error = errno;
+ free(buf);
+ errno = error;
+ return (NULL);
+ }
+ if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
+ if (IOCStatus != NULL)
+ *IOCStatus = req.ioc_status;
+ else
+ warnx("Reading extended config page failed: %s",
+ mps_ioc_status(req.ioc_status));
+ free(buf);
+ errno = EIO;
+ return (NULL);
+ }
+ return (buf);
+}
+#endif
+
+int
+mps_open(int unit)
+{
+ char path[MAXPATHLEN];
+
+ snprintf(path, sizeof(path), "/dev/mp%s%d", is_mps ? "s": "r", unit);
+ return (open(path, O_RDWR));
+}
+
+int
+mps_user_command(int fd, void *req, uint32_t req_len, void *reply,
+ uint32_t reply_len, void *buffer, int len, uint32_t flags)
+{
+ struct mps_usr_command cmd;
+
+ bzero(&cmd, sizeof(struct mps_usr_command));
+ cmd.req = req;
+ cmd.req_len = req_len;
+ cmd.rpl = reply;
+ cmd.rpl_len = reply_len;
+ cmd.buf = buffer;
+ cmd.len = len;
+ cmd.flags = flags;
+
+ if (ioctl(fd, is_mps ? MPSIO_MPS_COMMAND : MPRIO_MPR_COMMAND, &cmd) < 0)
+ return (errno);
+ return (0);
+}
+
+int
+mps_pass_command(int fd, void *req, uint32_t req_len, void *reply,
+ uint32_t reply_len, void *data_in, uint32_t datain_len, void *data_out,
+ uint32_t dataout_len, uint32_t timeout)
+{
+ struct mprs_pass_thru pass;
+
+ pass.PtrRequest = (uint64_t)(uintptr_t)req;
+ pass.PtrReply = (uint64_t)(uintptr_t)reply;
+ pass.PtrData = (uint64_t)(uintptr_t)data_in;
+ pass.PtrDataOut = (uint64_t)(uintptr_t)data_out;
+ pass.RequestSize = req_len;
+ pass.ReplySize = reply_len;
+ pass.DataSize = datain_len;
+ pass.DataOutSize = dataout_len;
+ if (datain_len && dataout_len) {
+ if (is_mps) {
+ pass.DataDirection = MPS_PASS_THRU_DIRECTION_BOTH;
+ } else {
+ pass.DataDirection = MPR_PASS_THRU_DIRECTION_BOTH;
+ }
+ } else if (datain_len) {
+ if (is_mps) {
+ pass.DataDirection = MPS_PASS_THRU_DIRECTION_READ;
+ } else {
+ pass.DataDirection = MPR_PASS_THRU_DIRECTION_READ;
+ }
+ } else if (dataout_len) {
+ if (is_mps) {
+ pass.DataDirection = MPS_PASS_THRU_DIRECTION_WRITE;
+ } else {
+ pass.DataDirection = MPR_PASS_THRU_DIRECTION_WRITE;
+ }
+ } else {
+ if (is_mps) {
+ pass.DataDirection = MPS_PASS_THRU_DIRECTION_NONE;
+ } else {
+ pass.DataDirection = MPR_PASS_THRU_DIRECTION_NONE;
+ }
+ }
+ pass.Timeout = timeout;
+
+ if (ioctl(fd, MPTIOCTL_PASS_THRU, &pass) < 0)
+ return (errno);
+ return (0);
+}
+
+MPI2_IOC_FACTS_REPLY *
+mps_get_iocfacts(int fd)
+{
+ MPI2_IOC_FACTS_REPLY *facts;
+ MPI2_IOC_FACTS_REQUEST req;
+ int error;
+
+ facts = malloc(sizeof(MPI2_IOC_FACTS_REPLY));
+ if (facts == NULL) {
+ errno = ENOMEM;
+ return (NULL);
+ }
+
+ bzero(&req, sizeof(MPI2_IOC_FACTS_REQUEST));
+ req.Function = MPI2_FUNCTION_IOC_FACTS;
+
+#if 1
+ error = mps_pass_command(fd, &req, sizeof(MPI2_IOC_FACTS_REQUEST),
+ facts, sizeof(MPI2_IOC_FACTS_REPLY), NULL, 0, NULL, 0, 10);
+#else
+ error = mps_user_command(fd, &req, sizeof(MPI2_IOC_FACTS_REQUEST),
+ facts, sizeof(MPI2_IOC_FACTS_REPLY), NULL, 0, 0);
+#endif
+ if (error) {
+ free(facts);
+ return (NULL);
+ }
+
+ if (!IOC_STATUS_SUCCESS(facts->IOCStatus)) {
+ free(facts);
+ errno = EINVAL;
+ return (NULL);
+ }
+ return (facts);
+}
+
diff --git a/usr.sbin/mpsutil/mps_flash.c b/usr.sbin/mpsutil/mps_flash.c
new file mode 100644
index 0000000..e22f29c
--- /dev/null
+++ b/usr.sbin/mpsutil/mps_flash.c
@@ -0,0 +1,237 @@
+/*-
+ * Copyright (c) 2015 Baptiste Daroussin <bapt@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>
+__RCSID("$FreeBSD$");
+
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <sys/mman.h>
+
+#include <errno.h>
+#include <err.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mpsutil.h"
+
+MPS_TABLE(top, flash);
+
+static int
+flash_save(int argc, char **argv)
+{
+ const char *firmware_file;
+ unsigned char *firmware_buffer = NULL;
+ int error, fd, size;
+ bool bios = false;
+ ssize_t written = 0, ret = 0;
+
+ if (argc < 2) {
+ warnx("missing argument: expecting 'firmware' or bios'");
+ return (EINVAL);
+ }
+
+ if (strcmp(argv[1], "bios") == 0) {
+ bios = true;
+ } else if (strcmp(argv[1], "firmware") != 0) {
+ warnx("Invalid argument '%s', expecting 'firmware' or 'bios'",
+ argv[1]);
+ }
+
+ if (argc > 4) {
+ warnx("save %s: extra arguments", argv[1]);
+ return (EINVAL);
+ }
+
+ firmware_file = argv[1];
+ if (argc == 3) {
+ firmware_file = argv[2];
+ }
+
+ fd = mps_open(mps_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mps_open");
+ return (error);
+ }
+
+ if ((size = mps_firmware_get(fd, &firmware_buffer, bios)) < 0) {
+ warnx("Fail to save %s", argv[1]);
+ return (1);
+ }
+
+ close(fd);
+ if (size > 0) {
+ fd = open(firmware_file, O_CREAT | O_TRUNC | O_RDWR, 0644);
+ if (fd <0) {
+ error = errno;
+ warn("open");
+ free(firmware_buffer);
+ return (error);
+ }
+ while (written != size) {
+ if ((ret = write(fd, firmware_buffer + written, size - written)) <0) {
+ error = errno;
+ warn("write");
+ free(firmware_buffer);
+ return (error);
+ }
+ written += ret;
+ }
+ close(fd);
+ }
+ free(firmware_buffer);
+ printf("%s successfully saved as %s\n", argv[1], firmware_file);
+ return (0);
+}
+
+MPS_COMMAND(flash, save, flash_save, "[firmware|bios] [file]",
+ "Save firmware/bios into a file");
+
+static int
+flash_update(int argc, char **argv)
+{
+ int error, fd;
+ unsigned char *mem = NULL;
+ struct stat st;
+ bool bios = false;
+ MPI2_FW_IMAGE_HEADER *fwheader;
+ MPI2_IOC_FACTS_REPLY *facts;
+
+ if (argc < 2) {
+ warnx("missing argument: expecting 'firmware' or bios'");
+ return (EINVAL);
+ }
+
+ if (strcmp(argv[1], "bios") == 0) {
+ bios = true;
+ } else if (strcmp(argv[1], "firmware") != 0) {
+ warnx("Invalid argument '%s', expecting 'firmware' or 'bios'",
+ argv[1]);
+ }
+
+ if (argc > 4) {
+ warnx("update firmware: extra arguments");
+ return (EINVAL);
+ }
+
+ if (argc != 3) {
+ warnx("no firmware specified");
+ return (EINVAL);
+ }
+
+ if (stat(argv[2], &st) == -1) {
+ error = errno;
+ warn("stat");
+ return (error);
+ }
+
+ fd = open(argv[2], O_RDONLY);
+ if (fd < 0) {
+ error = errno;
+ warn("open");
+ return (error);
+ }
+
+ mem = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (mem == MAP_FAILED) {
+ error = errno;
+ warn("mmap");
+ close(fd);
+ return (error);
+ }
+ close(fd);
+
+ fd = mps_open(mps_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mps_open");
+ munmap(mem, st.st_size);
+ return (error);
+ }
+
+ if ((facts = mps_get_iocfacts(fd)) == NULL) {
+ warnx("could not get controller IOCFacts\n");
+ munmap(mem, st.st_size);
+ close(fd);
+ return (EINVAL);
+ }
+
+ if (bios) {
+ /* Check boot record magic number */
+ if (((mem[0x01]<<8) + mem[0x00]) != 0xaa55) {
+ warnx("Invalid bios: no boot record magic number");
+ munmap(mem, st.st_size);
+ close(fd);
+ return (1);
+ }
+ if ((st.st_size % 512) != 0) {
+ warnx("Invalid bios: size not a multiple of 512");
+ munmap(mem, st.st_size);
+ close(fd);
+ return (1);
+ }
+ } else {
+ fwheader = (MPI2_FW_IMAGE_HEADER *)mem;
+ if (fwheader->VendorID != MPI2_MFGPAGE_VENDORID_LSI) {
+ warnx("Invalid firmware:");
+ warnx(" Expected Vendor ID: %04x",
+ MPI2_MFGPAGE_VENDORID_LSI);
+ warnx(" Image Vendor ID: %04x", fwheader->VendorID);
+ munmap(mem, st.st_size);
+ close(fd);
+ return (1);
+ }
+
+ if (fwheader->ProductID != facts->ProductID) {
+ warnx("Invalid image:");
+ warnx(" Expected Product ID: %04x", facts->ProductID);
+ warnx(" Image Product ID: %04x", fwheader->ProductID);
+ munmap(mem, st.st_size);
+ close(fd);
+ return (1);
+ }
+ }
+
+ printf("Updating %s...\n", argv[1]);
+ if (mps_firmware_send(fd, mem, st.st_size, bios) < 0) {
+ warnx("Fail to update %s", argv[1]);
+ munmap(mem, st.st_size);
+ close(fd);
+ return (1);
+ }
+
+ munmap(mem, st.st_size);
+ close(fd);
+ printf("%s successfully updated\n", argv[1]);
+ return (0);
+}
+
+MPS_COMMAND(flash, update, flash_update, "[firmware|bios] file",
+ "Update firmware/bios");
diff --git a/usr.sbin/mpsutil/mps_ioctl.h b/usr.sbin/mpsutil/mps_ioctl.h
new file mode 100644
index 0000000..a52f80e
--- /dev/null
+++ b/usr.sbin/mpsutil/mps_ioctl.h
@@ -0,0 +1,387 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@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.
+ * 3. 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 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.
+ *
+ * LSI MPT-Fusion Host Adapter FreeBSD userland interface
+ *
+ * $FreeBSD$
+ */
+/*-
+ * Copyright (c) 2011, 2012 LSI Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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.
+ *
+ * LSI MPT-Fusion Host Adapter FreeBSD
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _MPS_IOCTL_H_
+#define _MPS_IOCTL_H_
+
+#include <dev/mps/mpi/mpi2_type.h>
+#include <dev/mps/mpi/mpi2.h>
+#include <dev/mps/mpi/mpi2_cnfg.h>
+#include <dev/mps/mpi/mpi2_sas.h>
+
+/*
+ * For the read header requests, the header should include the page
+ * type or extended page type, page number, and page version. The
+ * buffer and length are unused. The completed header is returned in
+ * the 'header' member.
+ *
+ * For the read page and write page requests, 'buf' should point to a
+ * buffer of 'len' bytes which holds the entire page (including the
+ * header).
+ *
+ * All requests specify the page address in 'page_address'.
+ */
+struct mps_cfg_page_req {
+ MPI2_CONFIG_PAGE_HEADER header;
+ uint32_t page_address;
+ void *buf;
+ int len;
+ uint16_t ioc_status;
+};
+
+struct mps_ext_cfg_page_req {
+ MPI2_CONFIG_EXTENDED_PAGE_HEADER header;
+ uint32_t page_address;
+ void *buf;
+ int len;
+ uint16_t ioc_status;
+};
+
+struct mps_raid_action {
+ uint8_t action;
+ uint8_t volume_bus;
+ uint8_t volume_id;
+ uint8_t phys_disk_num;
+ uint32_t action_data_word;
+ void *buf;
+ int len;
+ uint32_t volume_status;
+ uint32_t action_data[4];
+ uint16_t action_status;
+ uint16_t ioc_status;
+ uint8_t write;
+};
+
+struct mps_usr_command {
+ void *req;
+ uint32_t req_len;
+ void *rpl;
+ uint32_t rpl_len;
+ void *buf;
+ int len;
+ uint32_t flags;
+};
+
+typedef struct mps_pci_bits
+{
+ union {
+ struct {
+ uint32_t DeviceNumber :5;
+ uint32_t FunctionNumber :3;
+ uint32_t BusNumber :24;
+ } bits;
+ uint32_t AsDWORD;
+ } u;
+ uint32_t PciSegmentId;
+} mps_pci_bits_t;
+
+/*
+ * The following is the MPSIOCTL_GET_ADAPTER_DATA data structure. This data
+ * structure is setup so that we hopefully are properly aligned for both
+ * 32-bit and 64-bit mode applications.
+ *
+ * Adapter Type - Value = 4 = SCSI Protocol through SAS-2 adapter
+ *
+ * MPI Port Number - The PCI Function number for this device
+ *
+ * PCI Device HW Id - The PCI device number for this device
+ *
+ */
+#define MPSIOCTL_ADAPTER_TYPE_SAS2 4
+#define MPSIOCTL_ADAPTER_TYPE_SAS2_SSS6200 5
+typedef struct mps_adapter_data
+{
+ uint32_t StructureLength;
+ uint32_t AdapterType;
+ uint32_t MpiPortNumber;
+ uint32_t PCIDeviceHwId;
+ uint32_t PCIDeviceHwRev;
+ uint32_t SubSystemId;
+ uint32_t SubsystemVendorId;
+ uint32_t Reserved1;
+ uint32_t MpiFirmwareVersion;
+ uint32_t BiosVersion;
+ uint8_t DriverVersion[32];
+ uint8_t Reserved2;
+ uint8_t ScsiId;
+ uint16_t Reserved3;
+ mps_pci_bits_t PciInformation;
+} mps_adapter_data_t;
+
+
+typedef struct mps_update_flash
+{
+ uint64_t PtrBuffer;
+ uint32_t ImageChecksum;
+ uint32_t ImageOffset;
+ uint32_t ImageSize;
+ uint32_t ImageType;
+} mps_update_flash_t;
+
+
+#define MPS_PASS_THRU_DIRECTION_NONE 0
+#define MPS_PASS_THRU_DIRECTION_READ 1
+#define MPS_PASS_THRU_DIRECTION_WRITE 2
+#define MPS_PASS_THRU_DIRECTION_BOTH 3
+
+typedef struct mps_pass_thru
+{
+ uint64_t PtrRequest;
+ uint64_t PtrReply;
+ uint64_t PtrData;
+ uint32_t RequestSize;
+ uint32_t ReplySize;
+ uint32_t DataSize;
+ uint32_t DataDirection;
+ uint64_t PtrDataOut;
+ uint32_t DataOutSize;
+ uint32_t Timeout;
+} mps_pass_thru_t;
+
+
+/*
+ * Event queue defines
+ */
+#define MPS_EVENT_QUEUE_SIZE (50) /* Max Events stored in driver */
+#define MPS_MAX_EVENT_DATA_LENGTH (48) /* Size of each event in Dwords */
+
+typedef struct mps_event_query
+{
+ uint16_t Entries;
+ uint16_t Reserved;
+ uint32_t Types[4];
+} mps_event_query_t;
+
+typedef struct mps_event_enable
+{
+ uint32_t Types[4];
+} mps_event_enable_t;
+
+/*
+ * Event record entry for ioctl.
+ */
+typedef struct mps_event_entry
+{
+ uint32_t Type;
+ uint32_t Number;
+ uint32_t Data[MPS_MAX_EVENT_DATA_LENGTH];
+} mps_event_entry_t;
+
+typedef struct mps_event_report
+{
+ uint32_t Size;
+ uint64_t PtrEvents;
+} mps_event_report_t;
+
+
+typedef struct mps_pci_info
+{
+ uint32_t BusNumber;
+ uint8_t DeviceNumber;
+ uint8_t FunctionNumber;
+ uint16_t InterruptVector;
+ uint8_t PciHeader[256];
+} mps_pci_info_t;
+
+
+typedef struct mps_diag_action
+{
+ uint32_t Action;
+ uint32_t Length;
+ uint64_t PtrDiagAction;
+ uint32_t ReturnCode;
+} mps_diag_action_t;
+
+#define MPS_FW_DIAGNOSTIC_UID_NOT_FOUND (0xFF)
+
+#define MPS_FW_DIAG_NEW (0x806E6577)
+
+#define MPS_FW_DIAG_TYPE_REGISTER (0x00000001)
+#define MPS_FW_DIAG_TYPE_UNREGISTER (0x00000002)
+#define MPS_FW_DIAG_TYPE_QUERY (0x00000003)
+#define MPS_FW_DIAG_TYPE_READ_BUFFER (0x00000004)
+#define MPS_FW_DIAG_TYPE_RELEASE (0x00000005)
+
+#define MPS_FW_DIAG_INVALID_UID (0x00000000)
+
+#define MPS_DIAG_SUCCESS 0
+#define MPS_DIAG_FAILURE 1
+
+#define MPS_FW_DIAG_ERROR_SUCCESS (0x00000000)
+#define MPS_FW_DIAG_ERROR_FAILURE (0x00000001)
+#define MPS_FW_DIAG_ERROR_INVALID_PARAMETER (0x00000002)
+#define MPS_FW_DIAG_ERROR_POST_FAILED (0x00000010)
+#define MPS_FW_DIAG_ERROR_INVALID_UID (0x00000011)
+#define MPS_FW_DIAG_ERROR_RELEASE_FAILED (0x00000012)
+#define MPS_FW_DIAG_ERROR_NO_BUFFER (0x00000013)
+#define MPS_FW_DIAG_ERROR_ALREADY_RELEASED (0x00000014)
+
+
+typedef struct mps_fw_diag_register
+{
+ uint8_t ExtendedType;
+ uint8_t BufferType;
+ uint16_t ApplicationFlags;
+ uint32_t DiagnosticFlags;
+ uint32_t ProductSpecific[23];
+ uint32_t RequestedBufferSize;
+ uint32_t UniqueId;
+} mps_fw_diag_register_t;
+
+typedef struct mps_fw_diag_unregister
+{
+ uint32_t UniqueId;
+} mps_fw_diag_unregister_t;
+
+#define MPS_FW_DIAG_FLAG_APP_OWNED (0x0001)
+#define MPS_FW_DIAG_FLAG_BUFFER_VALID (0x0002)
+#define MPS_FW_DIAG_FLAG_FW_BUFFER_ACCESS (0x0004)
+
+typedef struct mps_fw_diag_query
+{
+ uint8_t ExtendedType;
+ uint8_t BufferType;
+ uint16_t ApplicationFlags;
+ uint32_t DiagnosticFlags;
+ uint32_t ProductSpecific[23];
+ uint32_t TotalBufferSize;
+ uint32_t DriverAddedBufferSize;
+ uint32_t UniqueId;
+} mps_fw_diag_query_t;
+
+typedef struct mps_fw_diag_release
+{
+ uint32_t UniqueId;
+} mps_fw_diag_release_t;
+
+#define MPS_FW_DIAG_FLAG_REREGISTER (0x0001)
+#define MPS_FW_DIAG_FLAG_FORCE_RELEASE (0x0002)
+
+typedef struct mps_diag_read_buffer
+{
+ uint8_t Status;
+ uint8_t Reserved;
+ uint16_t Flags;
+ uint32_t StartingOffset;
+ uint32_t BytesToRead;
+ uint32_t UniqueId;
+ uint64_t PtrDataBuffer;
+} mps_diag_read_buffer_t;
+
+/*
+ * Register Access
+ */
+#define REG_IO_READ 1
+#define REG_IO_WRITE 2
+#define REG_MEM_READ 3
+#define REG_MEM_WRITE 4
+
+typedef struct mps_reg_access
+{
+ uint32_t Command;
+ uint32_t RegOffset;
+ uint32_t RegData;
+} mps_reg_access_t;
+
+typedef struct mps_btdh_mapping
+{
+ uint16_t TargetID;
+ uint16_t Bus;
+ uint16_t DevHandle;
+ uint16_t Reserved;
+} mps_btdh_mapping_t;
+
+#define MPSIO_MPS_COMMAND_FLAG_VERBOSE 0x01
+#define MPSIO_MPS_COMMAND_FLAG_DEBUG 0x02
+#define MPSIO_READ_CFG_HEADER _IOWR('M', 200, struct mps_cfg_page_req)
+#define MPSIO_READ_CFG_PAGE _IOWR('M', 201, struct mps_cfg_page_req)
+#define MPSIO_READ_EXT_CFG_HEADER _IOWR('M', 202, struct mps_ext_cfg_page_req)
+#define MPSIO_READ_EXT_CFG_PAGE _IOWR('M', 203, struct mps_ext_cfg_page_req)
+#define MPSIO_WRITE_CFG_PAGE _IOWR('M', 204, struct mps_cfg_page_req)
+#define MPSIO_RAID_ACTION _IOWR('M', 205, struct mps_raid_action)
+#define MPSIO_MPS_COMMAND _IOWR('M', 210, struct mps_usr_command)
+
+#define MPTIOCTL ('I')
+#define MPTIOCTL_GET_ADAPTER_DATA _IOWR(MPTIOCTL, 1,\
+ struct mps_adapter_data)
+#define MPTIOCTL_UPDATE_FLASH _IOWR(MPTIOCTL, 2,\
+ struct mps_update_flash)
+#define MPTIOCTL_RESET_ADAPTER _IO(MPTIOCTL, 3)
+#define MPTIOCTL_PASS_THRU _IOWR(MPTIOCTL, 4,\
+ struct mps_pass_thru)
+#define MPTIOCTL_EVENT_QUERY _IOWR(MPTIOCTL, 5,\
+ struct mps_event_query)
+#define MPTIOCTL_EVENT_ENABLE _IOWR(MPTIOCTL, 6,\
+ struct mps_event_enable)
+#define MPTIOCTL_EVENT_REPORT _IOWR(MPTIOCTL, 7,\
+ struct mps_event_report)
+#define MPTIOCTL_GET_PCI_INFO _IOWR(MPTIOCTL, 8,\
+ struct mps_pci_info)
+#define MPTIOCTL_DIAG_ACTION _IOWR(MPTIOCTL, 9,\
+ struct mps_diag_action)
+#define MPTIOCTL_REG_ACCESS _IOWR(MPTIOCTL, 10,\
+ struct mps_reg_access)
+#define MPTIOCTL_BTDH_MAPPING _IOWR(MPTIOCTL, 11,\
+ struct mps_btdh_mapping)
+
+#endif /* !_MPS_IOCTL_H_ */
diff --git a/usr.sbin/mpsutil/mps_show.c b/usr.sbin/mpsutil/mps_show.c
new file mode 100644
index 0000000..d6a7840
--- /dev/null
+++ b/usr.sbin/mpsutil/mps_show.c
@@ -0,0 +1,772 @@
+/*-
+ * Copyright (c) 2015 Netflix, Inc.
+ * All rights reserved.
+ * Written by: Scott Long <scottl@freebsd.org>
+ *
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@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.
+ * 3. 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 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>
+__RCSID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <err.h>
+#include <libutil.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "mpsutil.h"
+
+static char * get_device_speed(uint8_t rate);
+static char * get_device_type(uint32_t di);
+static int show_all(int ac, char **av);
+static int show_devices(int ac, char **av);
+static int show_enclosures(int ac, char **av);
+static int show_expanders(int ac, char **av);
+
+MPS_TABLE(top, show);
+
+#define STANDALONE_STATE "ONLINE"
+
+static int
+show_adapter(int ac, char **av)
+{
+ MPI2_CONFIG_PAGE_SASIOUNIT_0 *sas0;
+ MPI2_CONFIG_PAGE_SASIOUNIT_1 *sas1;
+ MPI2_SAS_IO_UNIT0_PHY_DATA *phy0;
+ MPI2_SAS_IO_UNIT1_PHY_DATA *phy1;
+ MPI2_CONFIG_PAGE_MAN_0 *man0;
+ MPI2_CONFIG_PAGE_BIOS_3 *bios3;
+ MPI2_IOC_FACTS_REPLY *facts;
+ U16 IOCStatus;
+ char *speed, *minspeed, *maxspeed, *isdisabled, *type;
+ char devhandle[5], ctrlhandle[5];
+ int error, fd, v, i;
+
+ if (ac != 1) {
+ warnx("show adapter: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mps_open(mps_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mps_open");
+ return (error);
+ }
+
+ man0 = mps_read_man_page(fd, 0, NULL);
+ if (man0 == NULL) {
+ error = errno;
+ warn("Failed to get controller info");
+ return (error);
+ }
+ if (man0->Header.PageLength < sizeof(*man0) / 4) {
+ warnx("Invalid controller info");
+ return (EINVAL);
+ }
+ printf("mp%s%d Adapter:\n", is_mps ? "s": "r", mps_unit);
+ printf(" Board Name: %.16s\n", man0->BoardName);
+ printf(" Board Assembly: %.16s\n", man0->BoardAssembly);
+ printf(" Chip Name: %.16s\n", man0->ChipName);
+ printf(" Chip Revision: %.16s\n", man0->ChipRevision);
+ free(man0);
+
+ bios3 = mps_read_config_page(fd, MPI2_CONFIG_PAGETYPE_BIOS, 3, 0, NULL);
+ if (bios3 == NULL) {
+ error = errno;
+ warn("Failed to get BIOS page 3 info");
+ return (error);
+ }
+ v = bios3->BiosVersion;
+ printf(" BIOS Revision: %d.%02d.%02d.%02d\n",
+ ((v & 0xff000000) >> 24), ((v &0xff0000) >> 16),
+ ((v & 0xff00) >> 8), (v & 0xff));
+ free(bios3);
+
+ if ((facts = mps_get_iocfacts(fd)) == NULL) {
+ printf("could not get controller IOCFacts\n");
+ close(fd);
+ return (errno);
+ }
+ v = facts->FWVersion.Word;
+ printf("Firmware Revision: %d.%02d.%02d.%02d\n",
+ ((v & 0xff000000) >> 24), ((v &0xff0000) >> 16),
+ ((v & 0xff00) >> 8), (v & 0xff));
+ printf(" Integrated RAID: %s\n",
+ (facts->IOCCapabilities & MPI2_IOCFACTS_CAPABILITY_INTEGRATED_RAID)
+ ? "yes" : "no");
+ free(facts);
+
+ fd = mps_open(mps_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mps_open");
+ return (error);
+ }
+
+ sas0 = mps_read_extended_config_page(fd,
+ MPI2_CONFIG_EXTPAGETYPE_SAS_IO_UNIT,
+ MPI2_SASIOUNITPAGE0_PAGEVERSION, 0, 0, &IOCStatus);
+ if (sas0 == NULL) {
+ error = errno;
+ warn("Error retrieving SAS IO Unit page %d", IOCStatus);
+ return (error);
+ }
+
+ sas1 = mps_read_extended_config_page(fd,
+ MPI2_CONFIG_EXTPAGETYPE_SAS_IO_UNIT,
+ MPI2_SASIOUNITPAGE1_PAGEVERSION, 1, 0, &IOCStatus);
+ if (sas0 == NULL) {
+ error = errno;
+ warn("Error retrieving SAS IO Unit page %d", IOCStatus);
+ return (error);
+ }
+ printf("\n");
+
+ printf("%-8s%-12s%-11s%-10s%-8s%-7s%-7s%s\n", "PhyNum", "CtlrHandle",
+ "DevHandle", "Disabled", "Speed", "Min", "Max", "Device");
+ for (i = 0; i < sas0->NumPhys; i++) {
+ phy0 = &sas0->PhyData[i];
+ phy1 = &sas1->PhyData[i];
+ if (phy0->PortFlags &
+ MPI2_SASIOUNIT0_PORTFLAGS_DISCOVERY_IN_PROGRESS) {
+ printf("Discovery still in progress\n");
+ continue;
+ }
+ if (phy0->PhyFlags & MPI2_SASIOUNIT0_PHYFLAGS_PHY_DISABLED)
+ isdisabled = "Y";
+ else
+ isdisabled = "N";
+
+ minspeed = get_device_speed(phy1->MaxMinLinkRate);
+ maxspeed = get_device_speed(phy1->MaxMinLinkRate >> 4);
+ type = get_device_type(phy0->ControllerPhyDeviceInfo);
+
+ if (phy0->AttachedDevHandle != 0) {
+ snprintf(devhandle, 5, "%04x", phy0->AttachedDevHandle);
+ snprintf(ctrlhandle, 5, "%04x",
+ phy0->ControllerDevHandle);
+ speed = get_device_speed(phy0->NegotiatedLinkRate);
+ } else {
+ snprintf(devhandle, 5, " ");
+ snprintf(ctrlhandle, 5, " ");
+ speed = " ";
+ }
+ printf("%-8d%-12s%-11s%-10s%-8s%-7s%-7s%s\n",
+ i, ctrlhandle, devhandle, isdisabled, speed, minspeed,
+ maxspeed, type);
+ }
+ free(sas0);
+ free(sas1);
+ printf("\n");
+ close(fd);
+ return (0);
+}
+
+MPS_COMMAND(show, adapter, show_adapter, "", "display controller information")
+
+static int
+show_iocfacts(int ac, char **av)
+{
+ MPI2_IOC_FACTS_REPLY *facts;
+ int error, fd;
+
+ fd = mps_open(mps_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mps_open");
+ return (error);
+ }
+
+ if ((facts = mps_get_iocfacts(fd)) == NULL) {
+ printf("could not get controller IOCFacts\n");
+ close(fd);
+ return (errno);
+ }
+
+ printf(" MaxChainDepth: %d\n", facts->MaxChainDepth);
+ printf(" WhoInit: 0x%x\n", facts->WhoInit);
+ printf(" NumberOfPorts: %d\n", facts->NumberOfPorts);
+ printf(" MaxMSIxVectors: %d\n", facts->MaxMSIxVectors);
+ printf(" RequestCredit: %d\n", facts->RequestCredit);
+ printf(" ProductID: 0x%x\n", facts->ProductID);
+ printf(" IOCCapabilities: 0x%x\n", facts->IOCCapabilities);
+ printf(" FWVersion: 0x%08x\n", facts->FWVersion.Word);
+ printf(" IOCRequestFrameSize: %d\n", facts->IOCRequestFrameSize);
+ printf(" MaxInitiators: %d\n", facts->MaxInitiators);
+ printf(" MaxTargets: %d\n", facts->MaxTargets);
+ printf(" MaxSasExpanders: %d\n", facts->MaxSasExpanders);
+ printf(" MaxEnclosures: %d\n", facts->MaxEnclosures);
+ printf(" ProtocolFlags: 0x%x\n", facts->ProtocolFlags);
+ printf(" HighPriorityCredit: %d\n", facts->HighPriorityCredit);
+ printf("MaxRepDescPostQDepth: %d\n",
+ facts->MaxReplyDescriptorPostQueueDepth);
+ printf(" ReplyFrameSize: %d\n", facts->ReplyFrameSize);
+ printf(" MaxVolumes: %d\n", facts->MaxVolumes);
+ printf(" MaxDevHandle: %d\n", facts->MaxDevHandle);
+ printf("MaxPersistentEntries: %d\n", facts->MaxPersistentEntries);
+ printf(" MinDevHandle: %d\n", facts->MinDevHandle);
+
+ free(facts);
+ return (0);
+}
+
+MPS_COMMAND(show, iocfacts, show_iocfacts, "", "Show IOC Facts Message");
+
+static int
+show_adapters(int ac, char **av)
+{
+ MPI2_CONFIG_PAGE_MAN_0 *man0;
+ MPI2_IOC_FACTS_REPLY *facts;
+ int unit, fd, error;
+
+ printf("Device Name\t Chip Name Board Name Firmware\n");
+ for (unit = 0; unit < MPS_MAX_UNIT; unit++) {
+ fd = mps_open(unit);
+ if (fd < 0)
+ continue;
+ facts = mps_get_iocfacts(fd);
+ if (facts == NULL) {
+ error = errno;
+ warn("Faled to get controller iocfacts");
+ close(fd);
+ return (error);
+ }
+ man0 = mps_read_man_page(fd, 0, NULL);
+ if (man0 == NULL) {
+ error = errno;
+ warn("Failed to get controller info");
+ close(fd);
+ return (error);
+ }
+ if (man0->Header.PageLength < sizeof(*man0) / 4) {
+ warnx("Invalid controller info");
+ close(fd);
+ free(man0);
+ return (EINVAL);
+ }
+ printf("/dev/mp%s%d\t%16s %16s %08x\n",
+ is_mps ? "s": "r", unit,
+ man0->ChipName, man0->BoardName, facts->FWVersion.Word);
+ free(man0);
+ free(facts);
+ close(fd);
+ }
+ return (0);
+}
+MPS_COMMAND(show, adapters, show_adapters, "", "Show a summary of all adapters");
+
+static char *
+get_device_type(uint32_t di)
+{
+
+ if (di & 0x4000)
+ return ("SEP Target ");
+ if (di & 0x2000)
+ return ("ATAPI Target ");
+ if (di & 0x400)
+ return ("SAS Target ");
+ if (di & 0x200)
+ return ("STP Target ");
+ if (di & 0x100)
+ return ("SMP Target ");
+ if (di & 0x80)
+ return ("SATA Target ");
+ if (di & 0x70)
+ return ("SAS Initiator ");
+ if (di & 0x8)
+ return ("SATA Initiator");
+ if ((di & 0x7) == 0)
+ return ("No Device ");
+ return ("Unknown Device");
+}
+
+static char *
+get_enc_type(uint32_t flags, int *issep)
+{
+ char *type;
+
+ *issep = 0;
+ switch (flags & 0xf) {
+ case 0x01:
+ type = "Direct Attached SES-2";
+ *issep = 1;
+ break;
+ case 0x02:
+ type = "Direct Attached SGPIO";
+ break;
+ case 0x03:
+ type = "Expander SGPIO";
+ break;
+ case 0x04:
+ type = "External SES-2";
+ *issep = 1;
+ break;
+ case 0x05:
+ type = "Direct Attached GPIO";
+ break;
+ case 0x0:
+ default:
+ return ("Unknown");
+ }
+
+ return (type);
+}
+
+static char *
+mps_device_speed[] = {
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "1.5",
+ "3.0",
+ "6.0",
+ "12 "
+};
+
+static char *
+get_device_speed(uint8_t rate)
+{
+ char *speed;
+
+ rate &= 0xf;
+ if (rate >= sizeof(mps_device_speed))
+ return ("Unk");
+
+ if ((speed = mps_device_speed[rate]) == NULL)
+ return ("???");
+ return (speed);
+}
+
+static char *
+mps_page_name[] = {
+ "IO Unit",
+ "IOC",
+ "BIOS",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "RAID Volume",
+ "Manufacturing",
+ "RAID Physical Disk",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "SAS IO Unit",
+ "SAS Expander",
+ "SAS Device",
+ "SAS PHY",
+ "Log",
+ "Enclosure",
+ "RAID Configuration",
+ "Driver Persistent Mapping",
+ "SAS Port",
+ "Ethernet Port",
+ "Extended Manufacturing"
+};
+
+static char *
+get_page_name(u_int page)
+{
+ char *name;
+
+ if (page >= sizeof(mps_page_name))
+ return ("Unknown");
+ if ((name = mps_page_name[page]) == NULL)
+ return ("Unknown");
+ return (name);
+}
+
+static int
+show_all(int ac, char **av)
+{
+ int error;
+
+ printf("Adapter:\n");
+ error = show_adapter(ac, av);
+ printf("Devices:\n");
+ error = show_devices(ac, av);
+ printf("Enclosures:\n");
+ error = show_enclosures(ac, av);
+ printf("Expanders:\n");
+ error = show_expanders(ac, av);
+ return (error);
+}
+MPS_COMMAND(show, all, show_all, "", "Show all devices");
+
+static int
+show_devices(int ac, char **av)
+{
+ MPI2_CONFIG_PAGE_SASIOUNIT_0 *sas0;
+ MPI2_SAS_IO_UNIT0_PHY_DATA *phydata;
+ MPI2_CONFIG_PAGE_SAS_DEV_0 *device;
+ MPI2_CONFIG_PAGE_EXPANDER_1 *exp1;
+ uint16_t IOCStatus, handle, bus, target;
+ char *type, *speed, enchandle[5], slot[3], bt[8];
+ char buf[256];
+ int fd, error, nphys;
+
+ fd = mps_open(mps_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mps_open");
+ return (error);
+ }
+
+ sas0 = mps_read_extended_config_page(fd,
+ MPI2_CONFIG_EXTPAGETYPE_SAS_IO_UNIT,
+ MPI2_SASIOUNITPAGE0_PAGEVERSION, 0, 0, &IOCStatus);
+ if (sas0 == NULL) {
+ error = errno;
+ warn("Error retrieving SAS IO Unit page %d", IOCStatus);
+ return (error);
+ }
+ nphys = sas0->NumPhys;
+
+ printf("B____%-5s%-17s%-8s%-10s%-14s%-6s%-5s%-6s%s\n",
+ "T", "SAS Address", "Handle", "Parent", "Device", "Speed",
+ "Enc", "Slot", "Wdt");
+ handle = 0xffff;
+ while (1) {
+ device = mps_read_extended_config_page(fd,
+ MPI2_CONFIG_EXTPAGETYPE_SAS_DEVICE,
+ MPI2_SASDEVICE0_PAGEVERSION, 0,
+ MPI2_SAS_DEVICE_PGAD_FORM_GET_NEXT_HANDLE | handle,
+ &IOCStatus);
+ if (device == NULL) {
+ if (IOCStatus == MPI2_IOCSTATUS_CONFIG_INVALID_PAGE)
+ break;
+ error = errno;
+ warn("Error retrieving device page");
+ return (error);
+ }
+ handle = device->DevHandle;
+
+ if (device->ParentDevHandle == 0x0) {
+ free(device);
+ continue;
+ }
+
+ bus = 0xffff;
+ target = 0xffff;
+ error = mps_map_btdh(fd, &handle, &bus, &target);
+ if (error) {
+ free(device);
+ continue;
+ }
+ if ((bus == 0xffff) || (target == 0xffff))
+ snprintf(bt, sizeof(bt), " ");
+ else
+ snprintf(bt, sizeof(bt), "%02d %02d", bus, target);
+
+ type = get_device_type(device->DeviceInfo);
+
+ if (device->PhyNum < nphys) {
+ phydata = &sas0->PhyData[device->PhyNum];
+ speed = get_device_speed(phydata->NegotiatedLinkRate);
+ } else if (device->ParentDevHandle > 0) {
+ exp1 = mps_read_extended_config_page(fd,
+ MPI2_CONFIG_EXTPAGETYPE_SAS_EXPANDER,
+ MPI2_SASEXPANDER1_PAGEVERSION, 1,
+ MPI2_SAS_EXPAND_PGAD_FORM_HNDL_PHY_NUM |
+ (device->PhyNum <<
+ MPI2_SAS_EXPAND_PGAD_PHYNUM_SHIFT) |
+ device->ParentDevHandle, &IOCStatus);
+ if (exp1 == NULL) {
+ if (IOCStatus != MPI2_IOCSTATUS_CONFIG_INVALID_PAGE) {
+ error = errno;
+ warn("Error retrieving expander page 1: 0x%x",
+ IOCStatus);
+ return (error);
+ }
+ speed = " ";
+ } else {
+ speed = get_device_speed(exp1->NegotiatedLinkRate);
+ free(exp1);
+ }
+ } else
+ speed = " ";
+
+ if (device->EnclosureHandle != 0) {
+ snprintf(enchandle, 5, "%04x", device->EnclosureHandle);
+ snprintf(slot, 3, "%02d", device->Slot);
+ } else {
+ snprintf(enchandle, 5, " ");
+ snprintf(slot, 3, " ");
+ }
+ printf("%-10s", bt);
+ snprintf(buf, sizeof(buf), "%08x%08x", device->SASAddress.High,
+ device->SASAddress.Low);
+ printf("%-17s", buf);
+ snprintf(buf, sizeof(buf), "%04x", device->DevHandle);
+ printf("%-8s", buf);
+ snprintf(buf, sizeof(buf), "%04x", device->ParentDevHandle);
+ printf("%-10s", buf);
+ printf("%-14s%-6s%-5s%-6s%d\n", type, speed,
+ enchandle, slot, device->MaxPortConnections);
+ free(device);
+ }
+ printf("\n");
+ free(sas0);
+ close(fd);
+ return (0);
+}
+MPS_COMMAND(show, devices, show_devices, "", "Show attached devices");
+
+static int
+show_enclosures(int ac, char **av)
+{
+ MPI2_CONFIG_PAGE_SAS_ENCLOSURE_0 *enc;
+ char *type, sepstr[5];
+ uint16_t IOCStatus, handle;
+ int fd, error, issep;
+
+ fd = mps_open(mps_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mps_open");
+ return (error);
+ }
+
+ printf("Slots Logical ID SEPHandle EncHandle Type\n");
+ handle = 0xffff;
+ while (1) {
+ enc = mps_read_extended_config_page(fd,
+ MPI2_CONFIG_EXTPAGETYPE_ENCLOSURE,
+ MPI2_SASENCLOSURE0_PAGEVERSION, 0,
+ MPI2_SAS_ENCLOS_PGAD_FORM_GET_NEXT_HANDLE | handle,
+ &IOCStatus);
+ if (enc == NULL) {
+ if (IOCStatus == MPI2_IOCSTATUS_CONFIG_INVALID_PAGE)
+ break;
+ error = errno;
+ warn("Error retrieving enclosure page");
+ return (error);
+ }
+ type = get_enc_type(enc->Flags, &issep);
+ if (issep == 0)
+ snprintf(sepstr, 5, " ");
+ else
+ snprintf(sepstr, 5, "%04x", enc->SEPDevHandle);
+ printf(" %.2d %08x%08x %s %04x %s\n",
+ enc->NumSlots, enc->EnclosureLogicalID.High,
+ enc->EnclosureLogicalID.Low, sepstr, enc->EnclosureHandle,
+ type);
+ handle = enc->EnclosureHandle;
+ free(enc);
+ }
+ printf("\n");
+ close(fd);
+ return (0);
+}
+MPS_COMMAND(show, enclosures, show_enclosures, "", "Show attached enclosures");
+
+static int
+show_expanders(int ac, char **av)
+{
+ MPI2_CONFIG_PAGE_EXPANDER_0 *exp0;
+ MPI2_CONFIG_PAGE_EXPANDER_1 *exp1;
+ uint16_t IOCStatus, handle;
+ char enchandle[5], parent[5], rphy[3], rhandle[5];
+ char *speed, *min, *max, *type;
+ int fd, error, nphys, i;
+
+ fd = mps_open(mps_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mps_open");
+ return (error);
+ }
+
+ printf("NumPhys SAS Address DevHandle Parent EncHandle SAS Level\n");
+ handle = 0xffff;
+ while (1) {
+ exp0 = mps_read_extended_config_page(fd,
+ MPI2_CONFIG_EXTPAGETYPE_SAS_EXPANDER,
+ MPI2_SASEXPANDER0_PAGEVERSION, 0,
+ MPI2_SAS_EXPAND_PGAD_FORM_GET_NEXT_HNDL | handle,
+ &IOCStatus);
+ if (exp0 == NULL) {
+ if (IOCStatus == MPI2_IOCSTATUS_CONFIG_INVALID_PAGE)
+ break;
+ error = errno;
+ warn("Error retrieving expander page 0");
+ return (error);
+ }
+
+ nphys = exp0->NumPhys;
+ handle = exp0->DevHandle;
+
+ if (exp0->EnclosureHandle == 0x00)
+ snprintf(enchandle, 5, " ");
+ else
+ snprintf(enchandle, 5, "%04d", exp0->EnclosureHandle);
+ if (exp0->ParentDevHandle == 0x0)
+ snprintf(parent, 5, " ");
+ else
+ snprintf(parent, 5, "%04x", exp0->ParentDevHandle);
+ printf(" %02d %08x%08x %04x %s %s %d\n",
+ exp0->NumPhys, exp0->SASAddress.High, exp0->SASAddress.Low,
+ exp0->DevHandle, parent, enchandle, exp0->SASLevel);
+
+ printf("\n");
+ printf(" Phy RemotePhy DevHandle Speed Min Max Device\n");
+ for (i = 0; i < nphys; i++) {
+ exp1 = mps_read_extended_config_page(fd,
+ MPI2_CONFIG_EXTPAGETYPE_SAS_EXPANDER,
+ MPI2_SASEXPANDER1_PAGEVERSION, 1,
+ MPI2_SAS_EXPAND_PGAD_FORM_HNDL_PHY_NUM |
+ (i << MPI2_SAS_EXPAND_PGAD_PHYNUM_SHIFT) |
+ exp0->DevHandle, &IOCStatus);
+ if (exp1 == NULL) {
+ if (IOCStatus !=
+ MPI2_IOCSTATUS_CONFIG_INVALID_PAGE)
+ warn("Error retrieving expander pg 1");
+ continue;
+ }
+ type = get_device_type(exp1->AttachedDeviceInfo);
+ if ((exp1->AttachedDeviceInfo &0x7) == 0) {
+ speed = " ";
+ snprintf(rphy, 3, " ");
+ snprintf(rhandle, 5, " ");
+ } else {
+ speed = get_device_speed(
+ exp1->NegotiatedLinkRate);
+ snprintf(rphy, 3, "%02d",
+ exp1->AttachedPhyIdentifier);
+ snprintf(rhandle, 5, "%04x",
+ exp1->AttachedDevHandle);
+ }
+ min = get_device_speed(exp1->HwLinkRate);
+ max = get_device_speed(exp1->HwLinkRate >> 4);
+ printf(" %02d %s %s %s %s %s %s\n", exp1->Phy, rphy, rhandle, speed, min, max, type);
+
+ free(exp1);
+ }
+ free(exp0);
+ }
+
+ printf("\n");
+ close(fd);
+ return (0);
+}
+
+MPS_COMMAND(show, expanders, show_expanders, "", "Show attached expanders");
+
+static int
+show_cfgpage(int ac, char **av)
+{
+ MPI2_CONFIG_PAGE_HEADER *hdr;
+ MPI2_CONFIG_EXTENDED_PAGE_HEADER *ehdr;
+ void *data;
+ uint32_t addr;
+ uint16_t IOCStatus;
+ uint8_t page, num;
+ int fd, error, len, attrs;
+ char *pgname, *pgattr;
+
+ fd = mps_open(mps_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mps_open");
+ return (error);
+ }
+
+ addr = 0;
+ num = 0;
+ page = 0;
+
+ switch (ac) {
+ case 4:
+ addr = (uint32_t)strtoul(av[3], NULL, 0);
+ case 3:
+ num = (uint8_t)strtoul(av[2], NULL, 0);
+ case 2:
+ page = (uint8_t)strtoul(av[1], NULL, 0);
+ break;
+ default:
+ errno = EINVAL;
+ warn("cfgpage: not enough arguments");
+ return (EINVAL);
+ }
+
+ if (page >= 0x10)
+ data = mps_read_extended_config_page(fd, page, 0, num, addr,
+ &IOCStatus);
+ else
+ data = mps_read_config_page(fd, page, num, addr, &IOCStatus);
+
+ if (data == NULL) {
+ error = errno;
+ warn("Error retrieving cfg page: %s\n",
+ mps_ioc_status(IOCStatus));
+ return (error);
+ }
+
+ if (page >= 0x10) {
+ ehdr = data;
+ len = ehdr->ExtPageLength * 4;
+ page = ehdr->ExtPageType;
+ attrs = ehdr->PageType >> 4;
+ } else {
+ hdr = data;
+ len = hdr->PageLength * 4;
+ page = hdr->PageType & 0xf;
+ attrs = hdr->PageType >> 4;
+ }
+
+ pgname = get_page_name(page);
+ if (attrs == 0)
+ pgattr = "Read-only";
+ else if (attrs == 1)
+ pgattr = "Read-Write";
+ else if (attrs == 2)
+ pgattr = "Read-Write Persistent";
+ else
+ pgattr = "Unknown Page Attribute";
+
+ printf("Page 0x%x: %s %d, %s\n", page, pgname, num, pgattr);
+ hexdump(data, len, NULL, HD_REVERSED | 4);
+ free(data);
+ return (0);
+}
+
+MPS_COMMAND(show, cfgpage, show_cfgpage, "page [num] [addr]", "Display config page");
diff --git a/usr.sbin/mpsutil/mpsutil.8 b/usr.sbin/mpsutil/mpsutil.8
new file mode 100644
index 0000000..6634b37
--- /dev/null
+++ b/usr.sbin/mpsutil/mpsutil.8
@@ -0,0 +1,165 @@
+.\"
+.\" Copyright (c) Baptiste Daroussin <bapt@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$
+.\"
+.Dd November 17, 2015
+.Dt MPSUTIL 8
+.Os
+.Sh NAME
+.Nm mpsutil ,
+.Nm mprutil
+.Nd Utility for managing LSI Fusion-MPT 2/3 controllers
+.Sh SYNOPSIS
+.Nm
+.Cm version
+.Nm
+.Op Fl u Ar unit
+.Cm show adapter
+.Nm
+.Op Fl u Ar unit
+.Cm show adapters
+.Nm
+.Op Fl u Ar unit
+.Cm show all
+.Nm
+.Op Fl u Ar unit
+.Cm show cfgpages page
+.Op Ar num
+.Op Ar addr
+.Nm
+.Op Fl u Ar unit
+.Cm show devices
+.Nm
+.Op Fl u Ar unit
+.Cm show enclosures
+.Nm
+.Op Fl u Ar unit
+.Cm show expanders
+.Nm
+.Op Fl u Ar unit
+.Cm show iocfacts
+.Nm
+.Op Fl u Ar unit
+.Cm flash save
+.Op Ar firmware Ns | Ns Ar bios
+.Op Ar file
+.Nm
+.Op Fl u Ar unit
+.Cm flash update
+.Op Ar firmware Ns | Ns Ar bios
+.Ar file
+.Sh DESCRIPTION
+The
+.Nm
+utility can be used to display or modify various parameters on LSI
+Fusion-MPS 2 controllers.
+.Pp
+The
+.Nm mprutil
+utility can be used to display or modify various parameters on LSI
+Fusion-MPS 3 controllers.
+.Pp
+The
+.Nm mprutil
+utility behave identically to
+.Nm .
+(same program)
+.Pp
+Each invocation of
+.Nm
+consists of zero or more global options followed by a command.
+Commands may support additional optional or required arguments after the
+command.
+.Pp
+Currently one global option is supported:
+.Bl -tag -width indent
+.It Fl u Ar unit
+.Ar unit
+specifies the unit of the controller to work with.
+If no unit is specified,
+then unit 0 is used.
+.El
+.Pp
+The
+.Nm
+utility supports several different groups of commands.
+The first group of commands provide information about the controller.
+The second group of commands are used to manager controller-wide operations.
+.Pp
+The informational commands include:
+.Bl -tag -width indent
+.It Cm version
+Displays the version of
+.Nm .
+.It Cm show adapter
+Displays information about the controller such as the model number or firmware
+version.
+.It Cm show adapters
+Displays a summary of all adapters.
+.It Cm show all
+Displays all devices, expanders and enclosures.
+.It Cm show devices
+Displays all devices.
+.It Cm show expanders
+Displays all expanders.
+.It Cm show enclosures
+Displays all enclosures.
+.It Cm show iocfacts
+Displays IOC Facts messages.
+.It Cm show cfgpage page Oo Ar num Oc Op Ar addr
+Show IOC Facts Message
+.El
+.Pp
+Controller management commands include:
+.Bl -tag -width indent
+.It Cm flash save Oo Ar firmware Ns | Ns Ar bios Oc Op Ar file
+Save the
+.Ar firmware
+or
+.Ar bios
+from the controller into a local
+.Ar file .
+If no
+.Ar file
+is specified then the file will be named
+.Pa firmware
+or
+.Pa bios .
+.It Cm flash update Oo Ar firmware Ns | Ns Ar bios Oc Ar file
+Replace the
+.Ar firmware
+or
+.Ar bios
+from the controller with the one specified via
+.Ar file .
+.El
+.Sh SEE ALSO
+.Xr mpr 4 ,
+.Xr mps 4
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 11.0 .
diff --git a/usr.sbin/mpsutil/mpsutil.c b/usr.sbin/mpsutil/mpsutil.c
new file mode 100644
index 0000000..666a46e
--- /dev/null
+++ b/usr.sbin/mpsutil/mpsutil.c
@@ -0,0 +1,207 @@
+/*-
+ * Copyright (c) 2015 Netflix, Inc.
+ * All rights reserved.
+ * Written by: Scott Long <scottl@freebsd.org>
+ *
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@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.
+ * 3. 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 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>
+__RCSID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "mpsutil.h"
+
+SET_DECLARE(MPS_DATASET(top), struct mpsutil_command);
+SET_DECLARE(MPS_DATASET(usage), struct mpsutil_usage);
+
+int mps_unit;
+int is_mps;
+
+static void
+usage(void)
+{
+ struct mpsutil_usage **cmd;
+ const char *args, *desc;
+
+ fprintf(stderr, "usage: %s [-u unit] <command> ...\n\n", getprogname());
+ fprintf(stderr, "Commands include:\n");
+ SET_FOREACH(cmd, MPS_DATASET(usage)) {
+ if (*cmd == NULL)
+ fprintf(stderr, "\n");
+ else
+ (*cmd)->handler(&args, &desc);
+ if (strncmp((*cmd)->set, "top", 3) == 0)
+ fprintf(stderr, "%s %-30s\t%s\n",
+ (*cmd)->name, args, desc);
+ else
+ fprintf(stderr, "%s %s %-30s\t%s\n",
+ (*cmd)->set, (*cmd)->name, args, desc);
+ }
+ exit(1);
+}
+
+static int
+version(int ac, char **av)
+{
+
+ printf("%s: version %s", MPSUTIL_VERSION, getprogname());
+#ifdef DEBUG
+ printf(" (DEBUG)");
+#endif
+ printf("\n");
+ return (0);
+}
+
+MPS_COMMAND(top, version, version, "", "version")
+
+int
+main(int ac, char **av)
+{
+ struct mpsutil_command **cmd;
+ int ch;
+
+ is_mps = !strcmp(getprogname(), "mpsutil");
+
+ while ((ch = getopt(ac, av, "u:h?")) != -1) {
+ switch (ch) {
+ case 'u':
+ mps_unit = atoi(optarg);
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return (1);
+ }
+ }
+
+ av += optind;
+ ac -= optind;
+
+ /* getopt() eats av[0], so we can't use mpt_table_handler() directly. */
+ if (ac == 0) {
+ usage();
+ return (1);
+ }
+
+ SET_FOREACH(cmd, MPS_DATASET(top)) {
+ if (strcmp((*cmd)->name, av[0]) == 0) {
+ if ((*cmd)->handler(ac, av))
+ return (1);
+ else
+ return (0);
+ }
+ }
+ warnx("Unknown command %s.", av[0]);
+ return (1);
+}
+
+int
+mps_table_handler(struct mpsutil_command **start, struct mpsutil_command **end,
+ int ac, char **av)
+{
+ struct mpsutil_command **cmd;
+
+ if (ac < 2) {
+ warnx("The %s command requires a sub-command.", av[0]);
+ return (EINVAL);
+ }
+ for (cmd = start; cmd < end; cmd++) {
+ if (strcmp((*cmd)->name, av[1]) == 0)
+ return ((*cmd)->handler(ac - 1, av + 1));
+ }
+
+ warnx("%s is not a valid sub-command of %s.", av[1], av[0]);
+ return (ENOENT);
+}
+
+void
+hexdump(const void *ptr, int length, const char *hdr, int flags)
+{
+ int i, j, k;
+ int cols;
+ const unsigned char *cp;
+ char delim;
+
+ if ((flags & HD_DELIM_MASK) != 0)
+ delim = (flags & HD_DELIM_MASK) >> 8;
+ else
+ delim = ' ';
+
+ if ((flags & HD_COLUMN_MASK) != 0)
+ cols = flags & HD_COLUMN_MASK;
+ else
+ cols = 16;
+
+ cp = ptr;
+ for (i = 0; i < length; i+= cols) {
+ if (hdr != NULL)
+ printf("%s", hdr);
+
+ if ((flags & HD_OMIT_COUNT) == 0)
+ printf("%04x ", i);
+
+ if ((flags & HD_OMIT_HEX) == 0) {
+ for (j = 0; j < cols; j++) {
+ if (flags & HD_REVERSED)
+ k = i + (cols - 1 - j);
+ else
+ k = i + j;
+ if (k < length)
+ printf("%c%02x", delim, cp[k]);
+ else
+ printf(" ");
+ }
+ }
+
+ if ((flags & HD_OMIT_CHARS) == 0) {
+ printf(" |");
+ for (j = 0; j < cols; j++) {
+ if (flags & HD_REVERSED)
+ k = i + (cols - 1 - j);
+ else
+ k = i + j;
+ if (k >= length)
+ printf(" ");
+ else if (cp[k] >= ' ' && cp[k] <= '~')
+ printf("%c", cp[k]);
+ else
+ printf(".");
+ }
+ printf("|");
+ }
+ printf("\n");
+ }
+}
diff --git a/usr.sbin/mpsutil/mpsutil.h b/usr.sbin/mpsutil/mpsutil.h
new file mode 100644
index 0000000..46c7d44
--- /dev/null
+++ b/usr.sbin/mpsutil/mpsutil.h
@@ -0,0 +1,147 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@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.
+ * 3. 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 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 __MPSUTIL_H__
+#define __MPSUTIL_H__
+
+#include <sys/cdefs.h>
+#include <sys/linker_set.h>
+#include <stdbool.h>
+
+#include <dev/mps/mpi/mpi2_type.h>
+#include <dev/mps/mpi/mpi2.h>
+#include <dev/mps/mpi/mpi2_cnfg.h>
+#include <dev/mps/mpi/mpi2_raid.h>
+#include <dev/mps/mpi/mpi2_ioc.h>
+
+#define MPSUTIL_VERSION "1.0.0"
+
+#define IOC_STATUS_SUCCESS(status) \
+ (((status) & MPI2_IOCSTATUS_MASK) == MPI2_IOCSTATUS_SUCCESS)
+
+struct mpsutil_command {
+ const char *name;
+ int (*handler)(int ac, char **av);
+};
+struct mpsutil_usage {
+ const char *set;
+ const char *name;
+ void (*handler)(const char **, const char**);
+};
+
+#define MPS_DATASET(name) mpsutil_ ## name ## _table
+
+#define MPS_COMMAND(set, name, function, args, desc) \
+ static struct mpsutil_command function ## _mpsutil_command = \
+ { #name, function }; \
+ DATA_SET(MPS_DATASET(set), function ## _mpsutil_command); \
+ static void \
+ function ## _usage(const char **a3, const char **a4) \
+ { \
+ *a3 = args; \
+ *a4 = desc; \
+ return; \
+ }; \
+ static struct mpsutil_usage function ## _mpsutil_usage = \
+ { #set, #name, function ## _usage }; \
+ DATA_SET(MPS_DATASET(usage), function ## _mpsutil_usage);
+
+#define _MPS_COMMAND(set, name, function) \
+ static struct mpsutil_command function ## _mpsutil_command = \
+ { #name, function }; \
+ DATA_SET(MPS_DATASET(set), function ## _mpsutil_command);
+
+#define MPS_TABLE(set, name) \
+ SET_DECLARE(MPS_DATASET(name), struct mpsutil_command); \
+ \
+ static int \
+ mpsutil_ ## name ## _table_handler(int ac, char **av) \
+ { \
+ return (mps_table_handler(SET_BEGIN(MPS_DATASET(name)), \
+ SET_LIMIT(MPS_DATASET(name)), ac, av)); \
+ } \
+ _MPS_COMMAND(set, name, mpsutil_ ## name ## _table_handler)
+
+extern int mps_unit;
+extern int is_mps;
+#define MPS_MAX_UNIT 10
+
+void hexdump(const void *ptr, int length, const char *hdr, int flags);
+#define HD_COLUMN_MASK 0xff
+#define HD_DELIM_MASK 0xff00
+#define HD_OMIT_COUNT (1 << 16)
+#define HD_OMIT_HEX (1 << 17)
+#define HD_OMIT_CHARS (1 << 18)
+#define HD_REVERSED (1 << 19)
+
+int mps_open(int unit);
+int mps_table_handler(struct mpsutil_command **start,
+ struct mpsutil_command **end, int ac, char **av);
+int mps_user_command(int fd, void *req, uint32_t req_len, void *reply,
+ uint32_t reply_len, void *buffer, int len, uint32_t flags);
+int mps_pass_command(int fd, void *req, uint32_t req_len, void *reply,
+ uint32_t reply_len, void *data_in, uint32_t datain_len, void *data_out,
+ uint32_t dataout_len, uint32_t timeout);
+int mps_read_config_page_header(int fd, U8 PageType, U8 PageNumber,
+ U32 PageAddress, MPI2_CONFIG_PAGE_HEADER *header, U16 *IOCStatus);
+int mps_read_ext_config_page_header(int fd, U8 ExtPageType, U8 PageNumber,
+ U32 PageAddress, MPI2_CONFIG_PAGE_HEADER *header,
+ U16 *ExtPageLen, U16 *IOCStatus);
+void *mps_read_config_page(int fd, U8 PageType, U8 PageNumber,
+ U32 PageAddress, U16 *IOCStatus);
+void *mps_read_extended_config_page(int fd, U8 ExtPageType, U8 PageVersion,
+ U8 PageNumber, U32 PageAddress, U16 *IOCStatus);
+int mps_map_btdh(int fd, uint16_t *devhandle, uint16_t *bus,
+ uint16_t *target);
+const char *mps_ioc_status(U16 IOCStatus);
+int mps_firmware_send(int fd, unsigned char *buf, uint32_t len, bool bios);
+int mps_firmware_get(int fd, unsigned char **buf, bool bios);
+
+static __inline void *
+mps_read_man_page(int fd, U8 PageNumber, U16 *IOCStatus)
+{
+
+ return (mps_read_config_page(fd, MPI2_CONFIG_PAGETYPE_MANUFACTURING,
+ PageNumber, 0, IOCStatus));
+}
+
+static __inline void *
+mps_read_ioc_page(int fd, U8 PageNumber, U16 *IOCStatus)
+{
+
+ return (mps_read_config_page(fd, MPI2_CONFIG_PAGETYPE_IOC, PageNumber,
+ 0, IOCStatus));
+}
+
+MPI2_IOC_FACTS_REPLY * mps_get_iocfacts(int fd);
+
+#endif /* !__MPSUTIL_H__ */
diff --git a/usr.sbin/mptable/Makefile b/usr.sbin/mptable/Makefile
new file mode 100644
index 0000000..cff7602
--- /dev/null
+++ b/usr.sbin/mptable/Makefile
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+PROG= mptable
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/mptable/Makefile.depend b/usr.sbin/mptable/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/mptable/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/mptable/mptable.1 b/usr.sbin/mptable/mptable.1
new file mode 100644
index 0000000..83e4cea
--- /dev/null
+++ b/usr.sbin/mptable/mptable.1
@@ -0,0 +1,69 @@
+.\" Copyright (c) 1996
+.\" Steve Passe <fsmp@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. The name of the developer 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 April 28, 1997
+.Dt MPTABLE 1
+.Os
+.Sh NAME
+.Nm mptable
+.Nd display MP configuration table
+.Sh SYNOPSIS
+.Nm
+.Op Fl dmesg
+.Op Fl verbose
+.Op Fl grope
+.Op Fl help
+.Sh DESCRIPTION
+The
+.Nm
+command finds and analyzes the MP configuration table on
+an Intel(tm) MP spec capable motherboard.
+It is useful for debugging an SMP kernel that will not boot, as well
+as examining the configuration of a system.
+It can be run with a UniProcessor kernel.
+.Pp
+It must be run with gid kmem privileges.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl dmesg
+Include a dmesg dump.
+.It Fl grope
+Look in areas it should not NEED to, use ONLY as a last resort.
+.It Fl help
+Print a usage message and exits.
+.It Fl verbose
+Print extra info.
+.El
+.Sh SEE ALSO
+.Xr smp 4 ,
+.Xr dmesg 8
+.Sh HISTORY
+The
+.Nm
+command first appeared in
+.Fx 3.0 .
+.Sh AUTHORS
+.An Steve Passe Aq Mt fsmp@FreeBSD.org
diff --git a/usr.sbin/mptable/mptable.c b/usr.sbin/mptable/mptable.c
new file mode 100644
index 0000000..c6fca67
--- /dev/null
+++ b/usr.sbin/mptable/mptable.c
@@ -0,0 +1,934 @@
+/*
+ * Copyright (c) 1996, by Steve Passe
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the 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 developer 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.
+ */
+
+/*
+ * mptable.c
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+/*
+ * this will cause the raw mp table to be dumped to /tmp/mpdump
+ *
+#define RAW_DUMP
+ */
+
+#define MP_SIG 0x5f504d5f /* _MP_ */
+#define EXTENDED_PROCESSING_READY
+#define OEM_PROCESSING_READY_NOT
+
+#include <sys/param.h>
+#include <sys/mman.h>
+#include <x86/mptable.h>
+#include <err.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define SEP_LINE \
+"\n-------------------------------------------------------------------------------\n"
+
+#define SEP_LINE2 \
+"\n===============================================================================\n"
+
+/* EBDA is @ 40:0e in real-mode terms */
+#define EBDA_POINTER 0x040e /* location of EBDA pointer */
+
+/* CMOS 'top of mem' is @ 40:13 in real-mode terms */
+#define TOPOFMEM_POINTER 0x0413 /* BIOS: base memory size */
+
+#define DEFAULT_TOPOFMEM 0xa0000
+
+#define BIOS_BASE 0xf0000
+#define BIOS_BASE2 0xe0000
+#define BIOS_SIZE 0x10000
+#define ONE_KBYTE 1024
+
+#define GROPE_AREA1 0x80000
+#define GROPE_AREA2 0x90000
+#define GROPE_SIZE 0x10000
+
+#define MAXPNSTR 132
+
+typedef struct BUSTYPENAME {
+ u_char type;
+ char name[ 7 ];
+} busTypeName;
+
+static const busTypeName busTypeTable[] =
+{
+ { CBUS, "CBUS" },
+ { CBUSII, "CBUSII" },
+ { EISA, "EISA" },
+ { UNKNOWN_BUSTYPE, "---" },
+ { UNKNOWN_BUSTYPE, "---" },
+ { ISA, "ISA" },
+ { UNKNOWN_BUSTYPE, "---" },
+ { UNKNOWN_BUSTYPE, "---" },
+ { UNKNOWN_BUSTYPE, "---" },
+ { UNKNOWN_BUSTYPE, "---" },
+ { UNKNOWN_BUSTYPE, "---" },
+ { UNKNOWN_BUSTYPE, "---" },
+ { PCI, "PCI" },
+ { UNKNOWN_BUSTYPE, "---" },
+ { UNKNOWN_BUSTYPE, "---" },
+ { UNKNOWN_BUSTYPE, "---" },
+ { UNKNOWN_BUSTYPE, "---" },
+ { UNKNOWN_BUSTYPE, "---" },
+ { UNKNOWN_BUSTYPE, "---" }
+};
+
+static const char *whereStrings[] = {
+ "Extended BIOS Data Area",
+ "BIOS top of memory",
+ "Default top of memory",
+ "BIOS",
+ "Extended BIOS",
+ "GROPE AREA #1",
+ "GROPE AREA #2"
+};
+
+static void apic_probe( u_int32_t* paddr, int* where );
+
+static void MPConfigDefault( int featureByte );
+
+static void MPFloatingPointer( u_int32_t paddr, int where, mpfps_t* mpfpsp );
+static void MPConfigTableHeader( u_int32_t pap );
+
+static void seekEntry( u_int32_t addr );
+static void readEntry( void* entry, int size );
+static void *mapEntry( u_int32_t addr, int size );
+
+static void processorEntry( proc_entry_ptr entry );
+static void busEntry( bus_entry_ptr entry );
+static void ioApicEntry( io_apic_entry_ptr entry );
+static void intEntry( int_entry_ptr entry );
+
+static void sasEntry( sas_entry_ptr entry );
+static void bhdEntry( bhd_entry_ptr entry );
+static void cbasmEntry( cbasm_entry_ptr entry );
+
+static void doDmesg( void );
+static void pnstr( char* s, int c );
+
+/* global data */
+static int pfd; /* physical /dev/mem fd */
+
+static int busses[256];
+static int apics[256];
+
+static int ncpu;
+static int nbus;
+static int napic;
+static int nintr;
+
+static int dmesg;
+static int grope;
+static int verbose;
+
+static void
+usage( void )
+{
+ fprintf( stderr, "usage: mptable [-dmesg] [-verbose] [-grope] [-help]\n" );
+ exit( 0 );
+}
+
+/*
+ *
+ */
+int
+main( int argc, char *argv[] )
+{
+ u_int32_t paddr;
+ int where;
+ mpfps_t mpfps;
+ int defaultConfig;
+
+ int ch;
+
+ /* announce ourselves */
+ puts( SEP_LINE2 );
+
+ printf( "MPTable\n" );
+
+ while ((ch = getopt(argc, argv, "d:g:h:v:")) != -1) {
+ switch(ch) {
+ case 'd':
+ if ( strcmp( optarg, "mesg") == 0 )
+ dmesg = 1;
+ else
+ dmesg = 0;
+ break;
+ case 'h':
+ if ( strcmp( optarg, "elp") == 0 )
+ usage();
+ break;
+ case 'g':
+ if ( strcmp( optarg, "rope") == 0 )
+ grope = 1;
+ break;
+ case 'v':
+ if ( strcmp( optarg, "erbose") == 0 )
+ verbose = 1;
+ break;
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+ optreset = 1;
+ optind = 0;
+ }
+
+ /* open physical memory for access to MP structures */
+ if ( (pfd = open( _PATH_MEM, O_RDONLY )) < 0 )
+ err( 1, "mem open" );
+
+ /* probe for MP structures */
+ apic_probe( &paddr, &where );
+ if ( where <= 0 ) {
+ fprintf( stderr, "\n MP FPS NOT found,\n" );
+ if (!grope)
+ fprintf( stderr, " suggest trying -grope option!!!\n\n" );
+ return 1;
+ }
+
+ if ( verbose )
+ printf( "\n MP FPS found in %s @ physical addr: 0x%08x\n",
+ whereStrings[ where - 1 ], paddr );
+
+ puts( SEP_LINE );
+
+ /* analyze the MP Floating Pointer Structure */
+ MPFloatingPointer( paddr, where, &mpfps );
+
+ puts( SEP_LINE );
+
+ /* check whether an MP config table exists */
+ if ( (defaultConfig = mpfps->config_type) )
+ MPConfigDefault( defaultConfig );
+ else
+ MPConfigTableHeader( mpfps->pap );
+
+ /* do a dmesg output */
+ if ( dmesg )
+ doDmesg();
+
+ puts( SEP_LINE2 );
+
+ return 0;
+}
+
+
+/*
+ * set PHYSICAL address of MP floating pointer structure
+ */
+#define NEXT(X) ((X) += 4)
+static void
+apic_probe( u_int32_t* paddr, int* where )
+{
+ /*
+ * c rewrite of apic_probe() by Jack F. Vogel
+ */
+
+ int x;
+ u_short segment;
+ u_int32_t target;
+ u_int buffer[ BIOS_SIZE / sizeof( int ) ];
+
+ if ( verbose )
+ printf( "\n" );
+
+ /* search Extended Bios Data Area, if present */
+ if ( verbose )
+ printf( " looking for EBDA pointer @ 0x%04x, ", EBDA_POINTER );
+ seekEntry( (u_int32_t)EBDA_POINTER );
+ readEntry( &segment, 2 );
+ if ( segment ) { /* search EBDA */
+ target = (u_int32_t)segment << 4;
+ if ( verbose )
+ printf( "found, searching EBDA @ 0x%08x\n", target );
+ seekEntry( target );
+ readEntry( buffer, ONE_KBYTE );
+
+ for ( x = 0; x < ONE_KBYTE / (int)sizeof ( unsigned int ); NEXT(x) ) {
+ if ( buffer[ x ] == MP_SIG ) {
+ *where = 1;
+ *paddr = (x * sizeof( unsigned int )) + target;
+ return;
+ }
+ }
+ }
+ else {
+ if ( verbose )
+ printf( "NOT found\n" );
+ }
+
+ /* read CMOS for real top of mem */
+ seekEntry( (u_int32_t)TOPOFMEM_POINTER );
+ readEntry( &segment, 2 );
+ --segment; /* less ONE_KBYTE */
+ target = segment * 1024;
+ if ( verbose )
+ printf( " searching CMOS 'top of mem' @ 0x%08x (%dK)\n",
+ target, segment );
+ seekEntry( target );
+ readEntry( buffer, ONE_KBYTE );
+
+ for ( x = 0; x < ONE_KBYTE / (int)sizeof ( unsigned int ); NEXT(x) ) {
+ if ( buffer[ x ] == MP_SIG ) {
+ *where = 2;
+ *paddr = (x * sizeof( unsigned int )) + target;
+ return;
+ }
+ }
+
+ /* we don't necessarily believe CMOS, check base of the last 1K of 640K */
+ if ( target != (DEFAULT_TOPOFMEM - 1024)) {
+ target = (DEFAULT_TOPOFMEM - 1024);
+ if ( verbose )
+ printf( " searching default 'top of mem' @ 0x%08x (%dK)\n",
+ target, (target / 1024) );
+ seekEntry( target );
+ readEntry( buffer, ONE_KBYTE );
+
+ for ( x = 0; x < ONE_KBYTE / (int)sizeof ( unsigned int ); NEXT(x) ) {
+ if ( buffer[ x ] == MP_SIG ) {
+ *where = 3;
+ *paddr = (x * sizeof( unsigned int )) + target;
+ return;
+ }
+ }
+ }
+
+ /* search the BIOS */
+ if ( verbose )
+ printf( " searching BIOS @ 0x%08x\n", BIOS_BASE );
+ seekEntry( BIOS_BASE );
+ readEntry( buffer, BIOS_SIZE );
+
+ for ( x = 0; x < BIOS_SIZE / (int)sizeof( unsigned int ); NEXT(x) ) {
+ if ( buffer[ x ] == MP_SIG ) {
+ *where = 4;
+ *paddr = (x * sizeof( unsigned int )) + BIOS_BASE;
+ return;
+ }
+ }
+
+ /* search the extended BIOS */
+ if ( verbose )
+ printf( " searching extended BIOS @ 0x%08x\n", BIOS_BASE2 );
+ seekEntry( BIOS_BASE2 );
+ readEntry( buffer, BIOS_SIZE );
+
+ for ( x = 0; x < BIOS_SIZE / (int)sizeof( unsigned int ); NEXT(x) ) {
+ if ( buffer[ x ] == MP_SIG ) {
+ *where = 5;
+ *paddr = (x * sizeof( unsigned int )) + BIOS_BASE2;
+ return;
+ }
+ }
+
+ if ( grope ) {
+ /* search additional memory */
+ target = GROPE_AREA1;
+ if ( verbose )
+ printf( " groping memory @ 0x%08x\n", target );
+ seekEntry( target );
+ readEntry( buffer, GROPE_SIZE );
+
+ for ( x = 0; x < GROPE_SIZE / (int)sizeof( unsigned int ); NEXT(x) ) {
+ if ( buffer[ x ] == MP_SIG ) {
+ *where = 6;
+ *paddr = (x * sizeof( unsigned int )) + GROPE_AREA1;
+ return;
+ }
+ }
+
+ target = GROPE_AREA2;
+ if ( verbose )
+ printf( " groping memory @ 0x%08x\n", target );
+ seekEntry( target );
+ readEntry( buffer, GROPE_SIZE );
+
+ for ( x = 0; x < GROPE_SIZE / (int)sizeof( unsigned int ); NEXT(x) ) {
+ if ( buffer[ x ] == MP_SIG ) {
+ *where = 7;
+ *paddr = (x * sizeof( unsigned int )) + GROPE_AREA2;
+ return;
+ }
+ }
+ }
+
+ *where = 0;
+ *paddr = (u_int32_t)0;
+}
+
+
+/*
+ *
+ */
+static void
+MPFloatingPointer( u_int32_t paddr, int where, mpfps_t* mpfpsp )
+{
+ mpfps_t mpfps;
+
+ /* map in mpfps structure*/
+ *mpfpsp = mpfps = mapEntry( paddr, sizeof( *mpfps ) );
+
+ /* show its contents */
+ printf( "MP Floating Pointer Structure:\n\n" );
+
+ printf( " location:\t\t\t" );
+ switch ( where )
+ {
+ case 1:
+ printf( "EBDA\n" );
+ break;
+ case 2:
+ printf( "BIOS base memory\n" );
+ break;
+ case 3:
+ printf( "DEFAULT base memory (639K)\n" );
+ break;
+ case 4:
+ printf( "BIOS\n" );
+ break;
+ case 5:
+ printf( "Extended BIOS\n" );
+ break;
+
+ case 0:
+ printf( "NOT found!\n" );
+ exit( 1 );
+ default:
+ printf( "BOGUS!\n" );
+ exit( 1 );
+ }
+ printf( " physical address:\t\t0x%08x\n", paddr );
+
+ printf( " signature:\t\t\t'" );
+ pnstr( mpfps->signature, 4 );
+ printf( "'\n" );
+
+ printf( " length:\t\t\t%d bytes\n", mpfps->length * 16 );
+ printf( " version:\t\t\t1.%1d\n", mpfps->spec_rev );
+ printf( " checksum:\t\t\t0x%02x\n", mpfps->checksum );
+
+ /* bits 0:6 are RESERVED */
+ if ( mpfps->mpfb2 & 0x7f ) {
+ printf( " warning, MP feature byte 2: 0x%02x\n", mpfps->mpfb2 );
+ }
+
+ /* bit 7 is IMCRP */
+ printf( " mode:\t\t\t\t%s\n", (mpfps->mpfb2 & MPFB2_IMCR_PRESENT) ?
+ "PIC" : "Virtual Wire" );
+
+ /* MP feature bytes 3-5 are expected to be ZERO */
+ if ( mpfps->mpfb3 )
+ printf( " warning, MP feature byte 3 NONZERO!\n" );
+ if ( mpfps->mpfb4 )
+ printf( " warning, MP feature byte 4 NONZERO!\n" );
+ if ( mpfps->mpfb5 )
+ printf( " warning, MP feature byte 5 NONZERO!\n" );
+}
+
+
+/*
+ *
+ */
+static void
+MPConfigDefault( int featureByte )
+{
+ printf( " MP default config type: %d\n\n", featureByte );
+ switch ( featureByte ) {
+ case 1:
+ printf( " bus: ISA, APIC: 82489DX\n" );
+ break;
+ case 2:
+ printf( " bus: EISA, APIC: 82489DX\n" );
+ break;
+ case 3:
+ printf( " bus: EISA, APIC: 82489DX\n" );
+ break;
+ case 4:
+ printf( " bus: MCA, APIC: 82489DX\n" );
+ break;
+ case 5:
+ printf( " bus: ISA+PCI, APIC: Integrated\n" );
+ break;
+ case 6:
+ printf( " bus: EISA+PCI, APIC: Integrated\n" );
+ break;
+ case 7:
+ printf( " bus: MCA+PCI, APIC: Integrated\n" );
+ break;
+ default:
+ printf( " future type\n" );
+ break;
+ }
+
+ switch ( featureByte ) {
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ nbus = 1;
+ break;
+ case 5:
+ case 6:
+ case 7:
+ nbus = 2;
+ break;
+ default:
+ printf( " future type\n" );
+ break;
+ }
+
+ ncpu = 2;
+ napic = 1;
+ nintr = 16;
+}
+
+
+/*
+ *
+ */
+static void
+MPConfigTableHeader( u_int32_t pap )
+{
+ mpcth_t cth;
+ int x;
+ int totalSize;
+ int c;
+ int oldtype, entrytype;
+ u_int8_t *entry;
+
+ if ( pap == 0 ) {
+ printf( "MP Configuration Table Header MISSING!\n" );
+ exit( 1 );
+ }
+
+ /* map in cth structure */
+ cth = mapEntry( pap, sizeof( *cth ) );
+
+ printf( "MP Config Table Header:\n\n" );
+
+ printf( " physical address:\t\t0x%08x\n", pap );
+
+ printf( " signature:\t\t\t'" );
+ pnstr( cth->signature, 4 );
+ printf( "'\n" );
+
+ printf( " base table length:\t\t%d\n", cth->base_table_length );
+
+ printf( " version:\t\t\t1.%1d\n", cth->spec_rev );
+ printf( " checksum:\t\t\t0x%02x\n", cth->checksum );
+
+ printf( " OEM ID:\t\t\t'" );
+ pnstr( cth->oem_id, 8 );
+ printf( "'\n" );
+
+ printf( " Product ID:\t\t\t'" );
+ pnstr( cth->product_id, 12 );
+ printf( "'\n" );
+
+ printf( " OEM table pointer:\t\t0x%08x\n", cth->oem_table_pointer );
+ printf( " OEM table size:\t\t%d\n", cth->oem_table_size );
+
+ printf( " entry count:\t\t\t%d\n", cth->entry_count );
+
+ printf( " local APIC address:\t\t0x%08x\n", cth->apic_address );
+
+ printf( " extended table length:\t%d\n", cth->extended_table_length );
+ printf( " extended table checksum:\t%d\n", cth->extended_table_checksum );
+
+ totalSize = cth->base_table_length - sizeof( struct MPCTH );
+
+ puts( SEP_LINE );
+
+ printf( "MP Config Base Table Entries:\n\n" );
+
+ /* initialize tables */
+ for (x = 0; x < (int)nitems(busses); x++)
+ busses[x] = 0xff;
+
+ for (x = 0; x < (int)nitems(apics); x++)
+ apics[x] = 0xff;
+
+ ncpu = 0;
+ nbus = 0;
+ napic = 0;
+ nintr = 0;
+
+ oldtype = -1;
+ entry = mapEntry(pap + sizeof(*cth), cth->base_table_length);
+ for (c = cth->entry_count; c; c--) {
+ entrytype = *entry;
+ if (entrytype != oldtype)
+ printf("--\n");
+ if (entrytype < oldtype)
+ printf("MPTABLE OUT OF ORDER!\n");
+ switch (entrytype) {
+ case MPCT_ENTRY_PROCESSOR:
+ if (oldtype != MPCT_ENTRY_PROCESSOR)
+ printf( "Processors:\tAPIC ID\tVersion\tState"
+ "\t\tFamily\tModel\tStep\tFlags\n" );
+ processorEntry((proc_entry_ptr)entry);
+ entry += sizeof(struct PROCENTRY);
+ break;
+
+ case MPCT_ENTRY_BUS:
+ if (oldtype != MPCT_ENTRY_BUS)
+ printf( "Bus:\t\tBus ID\tType\n" );
+ busEntry((bus_entry_ptr)entry);
+ entry += sizeof(struct BUSENTRY);
+ break;
+
+ case MPCT_ENTRY_IOAPIC:
+ if (oldtype != MPCT_ENTRY_IOAPIC)
+ printf( "I/O APICs:\tAPIC ID\tVersion\tState\t\tAddress\n" );
+ ioApicEntry((io_apic_entry_ptr)entry);
+ entry += sizeof(struct IOAPICENTRY);
+ break;
+
+ case MPCT_ENTRY_INT:
+ if (oldtype != MPCT_ENTRY_INT)
+ printf( "I/O Ints:\tType\tPolarity Trigger\tBus ID\t IRQ\tAPIC ID\tPIN#\n" );
+ intEntry((int_entry_ptr)entry);
+ entry += sizeof(struct INTENTRY);
+ break;
+
+ case MPCT_ENTRY_LOCAL_INT:
+ if (oldtype != MPCT_ENTRY_LOCAL_INT)
+ printf( "Local Ints:\tType\tPolarity Trigger\tBus ID\t IRQ\tAPIC ID\tPIN#\n" );
+ intEntry((int_entry_ptr)entry);
+ entry += sizeof(struct INTENTRY);
+ break;
+
+ default:
+ printf("MPTABLE HOSED! record type = %d\n", entrytype);
+ exit(1);
+ }
+ oldtype = entrytype;
+ }
+
+
+#if defined( EXTENDED_PROCESSING_READY )
+ /* process any extended data */
+ if ( cth->extended_table_length ) {
+ ext_entry_ptr ext_entry, end;
+
+ puts( SEP_LINE );
+
+ printf( "MP Config Extended Table Entries:\n\n" );
+
+ ext_entry = mapEntry(pap + cth->base_table_length,
+ cth->extended_table_length);
+ end = (ext_entry_ptr)((char *)ext_entry + cth->extended_table_length);
+ while (ext_entry < end) {
+ switch (ext_entry->type) {
+ case MPCT_EXTENTRY_SAS:
+ sasEntry((sas_entry_ptr)ext_entry);
+ break;
+ case MPCT_EXTENTRY_BHD:
+ bhdEntry((bhd_entry_ptr)ext_entry);
+ break;
+ case MPCT_EXTENTRY_CBASM:
+ cbasmEntry((cbasm_entry_ptr)ext_entry);
+ break;
+ default:
+ printf( "Extended Table HOSED!\n" );
+ exit( 1 );
+ }
+
+ ext_entry = (ext_entry_ptr)((char *)ext_entry + ext_entry->length);
+ }
+ }
+#endif /* EXTENDED_PROCESSING_READY */
+
+ /* process any OEM data */
+ if ( cth->oem_table_pointer && (cth->oem_table_size > 0) ) {
+#if defined( OEM_PROCESSING_READY )
+# error your on your own here!
+ /* map in oem table structure */
+ oemdata = mapEntry( cth->oem_table_pointer, cth->oem_table_size);
+
+ /** process it */
+#else
+ printf( "\nyou need to modify the source to handle OEM data!\n\n" );
+#endif /* OEM_PROCESSING_READY */
+ }
+
+ fflush( stdout );
+
+#if defined( RAW_DUMP )
+{
+ int ofd;
+ void *dumpbuf;
+
+ ofd = open( "/tmp/mpdump", O_CREAT | O_RDWR, 0666 );
+
+ dumpbuf = mapEntry( paddr, 1024 );
+ write( ofd, dumpbuf, 1024 );
+ close( ofd );
+}
+#endif /* RAW_DUMP */
+}
+
+
+/*
+ *
+ */
+static void
+seekEntry( u_int32_t addr )
+{
+ if ( lseek( pfd, (off_t)addr, SEEK_SET ) < 0 )
+ err( 1, "%s seek", _PATH_MEM );
+}
+
+
+/*
+ *
+ */
+static void
+readEntry( void* entry, int size )
+{
+ if ( read( pfd, entry, size ) != size )
+ err( 1, "readEntry" );
+}
+
+static void *
+mapEntry( u_int32_t addr, int size )
+{
+ void *p;
+
+ p = mmap( NULL, size, PROT_READ, MAP_SHARED, pfd, addr );
+ if (p == MAP_FAILED)
+ err( 1, "mapEntry" );
+ return (p);
+}
+
+static void
+processorEntry( proc_entry_ptr entry )
+{
+
+ /* count it */
+ ++ncpu;
+
+ printf( "\t\t%2d", entry->apic_id );
+ printf( "\t 0x%2x", entry->apic_version );
+
+ printf( "\t %s, %s",
+ (entry->cpu_flags & PROCENTRY_FLAG_BP) ? "BSP" : "AP",
+ (entry->cpu_flags & PROCENTRY_FLAG_EN) ? "usable" : "unusable" );
+
+ printf( "\t %d\t %d\t %d",
+ (entry->cpu_signature >> 8) & 0x0f,
+ (entry->cpu_signature >> 4) & 0x0f,
+ entry->cpu_signature & 0x0f );
+
+ printf( "\t 0x%04x\n", entry->feature_flags );
+}
+
+
+/*
+ *
+ */
+static int
+lookupBusType( char* name )
+{
+ int x;
+
+ for ( x = 0; x < MAX_BUSTYPE; ++x )
+ if ( strcmp( busTypeTable[ x ].name, name ) == 0 )
+ return busTypeTable[ x ].type;
+
+ return UNKNOWN_BUSTYPE;
+}
+
+
+static void
+busEntry( bus_entry_ptr entry )
+{
+ int x;
+ char name[ 8 ];
+ char c;
+
+ /* count it */
+ ++nbus;
+
+ printf( "\t\t%2d", entry->bus_id );
+ printf( "\t " ); pnstr( entry->bus_type, 6 ); printf( "\n" );
+
+ for ( x = 0; x < 6; ++x ) {
+ if ( (c = entry->bus_type[ x ]) == ' ' )
+ break;
+ name[ x ] = c;
+ }
+ name[ x ] = '\0';
+ busses[ entry->bus_id ] = lookupBusType( name );
+}
+
+
+static void
+ioApicEntry( io_apic_entry_ptr entry )
+{
+
+ /* count it */
+ ++napic;
+
+ printf( "\t\t%2d", entry->apic_id );
+ printf( "\t 0x%02x", entry->apic_version );
+ printf( "\t %s",
+ (entry->apic_flags & IOAPICENTRY_FLAG_EN) ? "usable" : "unusable" );
+ printf( "\t\t 0x%x\n", entry->apic_address );
+
+ apics[ entry->apic_id ] = entry->apic_id;
+}
+
+
+static const char *intTypes[] = {
+ "INT", "NMI", "SMI", "ExtINT"
+};
+
+static const char *polarityMode[] = {
+ "conforms", "active-hi", "reserved", "active-lo"
+};
+static const char *triggerMode[] = {
+ "conforms", "edge", "reserved", "level"
+};
+
+static void
+intEntry( int_entry_ptr entry )
+{
+
+ /* count it */
+ if ( entry->type == MPCT_ENTRY_INT )
+ ++nintr;
+
+ printf( "\t\t%s", intTypes[ entry->int_type ] );
+
+ printf( "\t%9s", polarityMode[ entry->int_flags & INTENTRY_FLAGS_POLARITY ] );
+ printf( "%12s", triggerMode[ (entry->int_flags & INTENTRY_FLAGS_TRIGGER) >> 2 ] );
+
+ printf( "\t %5d", entry->src_bus_id );
+ if ( busses[ entry->src_bus_id ] == PCI )
+ printf( "\t%2d:%c",
+ (entry->src_bus_irq >> 2) & 0x1f,
+ (entry->src_bus_irq & 0x03) + 'A' );
+ else
+ printf( "\t %3d", entry->src_bus_irq );
+ printf( "\t %6d", entry->dst_apic_id );
+ printf( "\t %3d\n", entry->dst_apic_int );
+}
+
+
+static void
+sasEntry( sas_entry_ptr entry )
+{
+
+ printf( "--\nSystem Address Space\n");
+ printf( " bus ID: %d", entry->bus_id );
+ printf( " address type: " );
+ switch ( entry->address_type ) {
+ case SASENTRY_TYPE_IO:
+ printf( "I/O address\n" );
+ break;
+ case SASENTRY_TYPE_MEMORY:
+ printf( "memory address\n" );
+ break;
+ case SASENTRY_TYPE_PREFETCH:
+ printf( "prefetch address\n" );
+ break;
+ default:
+ printf( "UNKNOWN type\n" );
+ break;
+ }
+
+ printf( " address base: 0x%jx\n", (uintmax_t)entry->address_base );
+ printf( " address range: 0x%jx\n", (uintmax_t)entry->address_length );
+}
+
+
+static void
+bhdEntry( bhd_entry_ptr entry )
+{
+
+ printf( "--\nBus Hierarchy\n" );
+ printf( " bus ID: %d", entry->bus_id );
+ printf( " bus info: 0x%02x", entry->bus_info );
+ printf( " parent bus ID: %d\n", entry->parent_bus );
+}
+
+
+static void
+cbasmEntry( cbasm_entry_ptr entry )
+{
+
+ printf( "--\nCompatibility Bus Address\n" );
+ printf( " bus ID: %d", entry->bus_id );
+ printf( " address modifier: %s\n",
+ (entry->address_mod & CBASMENTRY_ADDRESS_MOD_SUBTRACT) ?
+ "subtract" : "add" );
+ printf( " predefined range: 0x%08x\n", entry->predefined_range );
+}
+
+
+/*
+ * do a dmesg output
+ */
+static void
+doDmesg( void )
+{
+ puts( SEP_LINE );
+
+ printf( "dmesg output:\n\n" );
+ fflush( stdout );
+ system( "dmesg" );
+}
+
+
+/*
+ *
+ */
+static void
+pnstr( char* s, int c )
+{
+ char string[ MAXPNSTR + 1 ];
+
+ if ( c > MAXPNSTR )
+ c = MAXPNSTR;
+ strncpy( string, s, c );
+ string[ c ] = '\0';
+ printf( "%s", string );
+}
diff --git a/usr.sbin/mptutil/Makefile b/usr.sbin/mptutil/Makefile
new file mode 100644
index 0000000..2054c26
--- /dev/null
+++ b/usr.sbin/mptutil/Makefile
@@ -0,0 +1,18 @@
+# $FreeBSD$
+
+PROG= mptutil
+SRCS= mptutil.c mpt_cam.c mpt_cmd.c mpt_config.c mpt_drive.c mpt_evt.c \
+ mpt_show.c mpt_volume.c
+# mpt_flash.c
+MAN= mptutil.8
+
+WARNS?= 3
+
+LIBADD= cam util
+
+# Here be dragons
+.ifdef DEBUG
+CFLAGS+= -DDEBUG
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/mptutil/Makefile.depend b/usr.sbin/mptutil/Makefile.depend
new file mode 100644
index 0000000..48a48dd
--- /dev/null
+++ b/usr.sbin/mptutil/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcam \
+ lib/libcompiler_rt \
+ lib/libsbuf \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/mptutil/mpt_cam.c b/usr.sbin/mptutil/mpt_cam.c
new file mode 100644
index 0000000..6a8ff07
--- /dev/null
+++ b/usr.sbin/mptutil/mpt_cam.c
@@ -0,0 +1,569 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@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.
+ * 3. 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 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>
+__RCSID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <camlib.h>
+#include <cam/scsi/scsi_message.h>
+#include <cam/scsi/scsi_pass.h>
+
+#include "mptutil.h"
+
+static int xptfd;
+
+static int
+xpt_open(void)
+{
+
+ if (xptfd == 0)
+ xptfd = open(XPT_DEVICE, O_RDWR);
+ return (xptfd);
+}
+
+/* Fetch the path id of bus 0 for the opened mpt controller. */
+static int
+fetch_path_id(path_id_t *path_id)
+{
+ struct bus_match_pattern *b;
+ union ccb ccb;
+ size_t bufsize;
+ int error;
+
+ if (xpt_open() < 0)
+ return (ENXIO);
+
+ /* First, find the path id of bus 0 for this mpt controller. */
+ bzero(&ccb, sizeof(ccb));
+
+ ccb.ccb_h.func_code = XPT_DEV_MATCH;
+
+ bufsize = sizeof(struct dev_match_result) * 1;
+ ccb.cdm.num_matches = 0;
+ ccb.cdm.match_buf_len = bufsize;
+ ccb.cdm.matches = calloc(1, bufsize);
+
+ bufsize = sizeof(struct dev_match_pattern) * 1;
+ ccb.cdm.num_patterns = 1;
+ ccb.cdm.pattern_buf_len = bufsize;
+ ccb.cdm.patterns = calloc(1, bufsize);
+
+ /* Match mptX bus 0. */
+ ccb.cdm.patterns[0].type = DEV_MATCH_BUS;
+ b = &ccb.cdm.patterns[0].pattern.bus_pattern;
+ snprintf(b->dev_name, sizeof(b->dev_name), "mpt");
+ b->unit_number = mpt_unit;
+ b->bus_id = 0;
+ b->flags = BUS_MATCH_NAME | BUS_MATCH_UNIT | BUS_MATCH_BUS_ID;
+
+ if (ioctl(xptfd, CAMIOCOMMAND, &ccb) < 0) {
+ error = errno;
+ free(ccb.cdm.matches);
+ free(ccb.cdm.patterns);
+ return (error);
+ }
+ free(ccb.cdm.patterns);
+
+ if (((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) ||
+ (ccb.cdm.status != CAM_DEV_MATCH_LAST)) {
+ warnx("fetch_path_id got CAM error %#x, CDM error %d\n",
+ ccb.ccb_h.status, ccb.cdm.status);
+ free(ccb.cdm.matches);
+ return (EIO);
+ }
+
+ /* We should have exactly 1 match for the bus. */
+ if (ccb.cdm.num_matches != 1 ||
+ ccb.cdm.matches[0].type != DEV_MATCH_BUS) {
+ free(ccb.cdm.matches);
+ return (ENOENT);
+ }
+ *path_id = ccb.cdm.matches[0].result.bus_result.path_id;
+ free(ccb.cdm.matches);
+ return (0);
+}
+
+int
+mpt_query_disk(U8 VolumeBus, U8 VolumeID, struct mpt_query_disk *qd)
+{
+ struct periph_match_pattern *p;
+ struct periph_match_result *r;
+ union ccb ccb;
+ path_id_t path_id;
+ size_t bufsize;
+ int error;
+
+ /* mpt(4) only handles devices on bus 0. */
+ if (VolumeBus != 0)
+ return (ENXIO);
+
+ if (xpt_open() < 0)
+ return (ENXIO);
+
+ /* Find the path ID of bus 0. */
+ error = fetch_path_id(&path_id);
+ if (error)
+ return (error);
+
+ bzero(&ccb, sizeof(ccb));
+
+ ccb.ccb_h.func_code = XPT_DEV_MATCH;
+ 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;
+
+ bufsize = sizeof(struct dev_match_result) * 5;
+ ccb.cdm.num_matches = 0;
+ ccb.cdm.match_buf_len = bufsize;
+ ccb.cdm.matches = calloc(1, bufsize);
+
+ bufsize = sizeof(struct dev_match_pattern) * 1;
+ ccb.cdm.num_patterns = 1;
+ ccb.cdm.pattern_buf_len = bufsize;
+ ccb.cdm.patterns = calloc(1, bufsize);
+
+ /* Look for a "da" device at the specified target and lun. */
+ ccb.cdm.patterns[0].type = DEV_MATCH_PERIPH;
+ p = &ccb.cdm.patterns[0].pattern.periph_pattern;
+ p->path_id = path_id;
+ snprintf(p->periph_name, sizeof(p->periph_name), "da");
+ p->target_id = VolumeID;
+ p->flags = PERIPH_MATCH_PATH | PERIPH_MATCH_NAME | PERIPH_MATCH_TARGET;
+
+ if (ioctl(xptfd, CAMIOCOMMAND, &ccb) < 0) {
+ error = errno;
+ free(ccb.cdm.matches);
+ free(ccb.cdm.patterns);
+ return (error);
+ }
+ free(ccb.cdm.patterns);
+
+ if (((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) ||
+ (ccb.cdm.status != CAM_DEV_MATCH_LAST)) {
+ warnx("mpt_query_disk got CAM error %#x, CDM error %d\n",
+ ccb.ccb_h.status, ccb.cdm.status);
+ free(ccb.cdm.matches);
+ return (EIO);
+ }
+
+ /*
+ * We should have exactly 1 match for the peripheral.
+ * However, if we don't get a match, don't print an error
+ * message and return ENOENT.
+ */
+ if (ccb.cdm.num_matches == 0) {
+ free(ccb.cdm.matches);
+ return (ENOENT);
+ }
+ if (ccb.cdm.num_matches != 1) {
+ warnx("mpt_query_disk got %d matches, expected 1",
+ ccb.cdm.num_matches);
+ free(ccb.cdm.matches);
+ return (EIO);
+ }
+ if (ccb.cdm.matches[0].type != DEV_MATCH_PERIPH) {
+ warnx("mpt_query_disk got wrong CAM match");
+ free(ccb.cdm.matches);
+ return (EIO);
+ }
+
+ /* Copy out the data. */
+ r = &ccb.cdm.matches[1].result.periph_result;
+ snprintf(qd->devname, sizeof(qd->devname), "%s%d", r->periph_name,
+ r->unit_number);
+ free(ccb.cdm.matches);
+
+ return (0);
+}
+
+static int
+periph_is_volume(CONFIG_PAGE_IOC_2 *ioc2, struct periph_match_result *r)
+{
+ CONFIG_PAGE_IOC_2_RAID_VOL *vol;
+ int i;
+
+ if (ioc2 == NULL)
+ return (0);
+ vol = ioc2->RaidVolume;
+ for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
+ if (vol->VolumeBus == 0 && vol->VolumeID == r->target_id)
+ return (1);
+ }
+ return (0);
+}
+
+/* Much borrowed from scsireadcapacity() in src/sbin/camcontrol/camcontrol.c. */
+static int
+fetch_scsi_capacity(struct cam_device *dev, struct mpt_standalone_disk *disk)
+{
+ struct scsi_read_capacity_data rcap;
+ struct scsi_read_capacity_data_long rcaplong;
+ union ccb *ccb;
+ int error;
+
+ ccb = cam_getccb(dev);
+ if (ccb == NULL)
+ return (ENOMEM);
+
+ /* Zero the rest of the ccb. */
+ bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) -
+ sizeof(struct ccb_hdr));
+
+ scsi_read_capacity(&ccb->csio, 1, NULL, MSG_SIMPLE_Q_TAG, &rcap,
+ SSD_FULL_SIZE, 5000);
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (cam_send_ccb(dev, ccb) < 0) {
+ error = errno;
+ cam_freeccb(ccb);
+ return (error);
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ cam_freeccb(ccb);
+ return (EIO);
+ }
+ cam_freeccb(ccb);
+
+ /*
+ * 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 (scsi_4btoul(rcap.addr) != 0xffffffff) {
+ disk->maxlba = scsi_4btoul(rcap.addr);
+ return (0);
+ }
+
+ /* Zero the rest of the ccb. */
+ bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) -
+ sizeof(struct ccb_hdr));
+
+ scsi_read_capacity_16(&ccb->csio, 1, NULL, MSG_SIMPLE_Q_TAG, 0, 0, 0,
+ (uint8_t *)&rcaplong, sizeof(rcaplong), SSD_FULL_SIZE, 5000);
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (cam_send_ccb(dev, ccb) < 0) {
+ error = errno;
+ cam_freeccb(ccb);
+ return (error);
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ cam_freeccb(ccb);
+ return (EIO);
+ }
+ cam_freeccb(ccb);
+
+ disk->maxlba = scsi_8btou64(rcaplong.addr);
+ return (0);
+}
+
+/* Borrowed heavily from scsi_all.c:scsi_print_inquiry(). */
+static void
+format_scsi_inquiry(struct mpt_standalone_disk *disk,
+ struct scsi_inquiry_data *inq_data)
+{
+ char vendor[16], product[48], revision[16], rstr[12];
+
+ if (SID_QUAL_IS_VENDOR_UNIQUE(inq_data))
+ return;
+ if (SID_TYPE(inq_data) != T_DIRECT)
+ return;
+ if (SID_QUAL(inq_data) != SID_QUAL_LU_CONNECTED)
+ return;
+
+ cam_strvis(vendor, inq_data->vendor, sizeof(inq_data->vendor),
+ sizeof(vendor));
+ cam_strvis(product, inq_data->product, sizeof(inq_data->product),
+ sizeof(product));
+ cam_strvis(revision, inq_data->revision, sizeof(inq_data->revision),
+ sizeof(revision));
+
+ /* Hack for SATA disks, no idea how to tell speed. */
+ if (strcmp(vendor, "ATA") == 0) {
+ snprintf(disk->inqstring, sizeof(disk->inqstring),
+ "<%s %s> SATA", product, revision);
+ return;
+ }
+
+ switch (SID_ANSI_REV(inq_data)) {
+ case SCSI_REV_CCS:
+ strcpy(rstr, "SCSI-CCS");
+ break;
+ case 5:
+ strcpy(rstr, "SAS");
+ break;
+ default:
+ snprintf(rstr, sizeof (rstr), "SCSI-%d",
+ SID_ANSI_REV(inq_data));
+ break;
+ }
+ snprintf(disk->inqstring, sizeof(disk->inqstring), "<%s %s %s> %s",
+ vendor, product, revision, rstr);
+}
+
+/* Much borrowed from scsiinquiry() in src/sbin/camcontrol/camcontrol.c. */
+static int
+fetch_scsi_inquiry(struct cam_device *dev, struct mpt_standalone_disk *disk)
+{
+ struct scsi_inquiry_data *inq_buf;
+ union ccb *ccb;
+ int error;
+
+ ccb = cam_getccb(dev);
+ if (ccb == NULL)
+ return (ENOMEM);
+
+ /* Zero the rest of the ccb. */
+ bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio) -
+ sizeof(struct ccb_hdr));
+
+ inq_buf = calloc(1, sizeof(*inq_buf));
+ if (inq_buf == NULL) {
+ cam_freeccb(ccb);
+ return (ENOMEM);
+ }
+ scsi_inquiry(&ccb->csio, 1, NULL, MSG_SIMPLE_Q_TAG, (void *)inq_buf,
+ SHORT_INQUIRY_LENGTH, 0, 0, SSD_FULL_SIZE, 5000);
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (cam_send_ccb(dev, ccb) < 0) {
+ error = errno;
+ free(inq_buf);
+ cam_freeccb(ccb);
+ return (error);
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ free(inq_buf);
+ cam_freeccb(ccb);
+ return (EIO);
+ }
+
+ cam_freeccb(ccb);
+ format_scsi_inquiry(disk, inq_buf);
+ free(inq_buf);
+ return (0);
+}
+
+int
+mpt_fetch_disks(int fd, int *ndisks, struct mpt_standalone_disk **disksp)
+{
+ CONFIG_PAGE_IOC_2 *ioc2;
+ struct mpt_standalone_disk *disks;
+ struct periph_match_pattern *p;
+ struct periph_match_result *r;
+ struct cam_device *dev;
+ union ccb ccb;
+ path_id_t path_id;
+ size_t bufsize;
+ int count, error;
+ uint32_t i;
+
+ if (xpt_open() < 0)
+ return (ENXIO);
+
+ error = fetch_path_id(&path_id);
+ if (error)
+ return (error);
+
+ for (count = 100;; count+= 100) {
+ /* Try to fetch 'count' disks in one go. */
+ bzero(&ccb, sizeof(ccb));
+
+ ccb.ccb_h.func_code = XPT_DEV_MATCH;
+
+ bufsize = sizeof(struct dev_match_result) * (count + 1);
+ ccb.cdm.num_matches = 0;
+ ccb.cdm.match_buf_len = bufsize;
+ ccb.cdm.matches = calloc(1, bufsize);
+
+ bufsize = sizeof(struct dev_match_pattern) * 1;
+ ccb.cdm.num_patterns = 1;
+ ccb.cdm.pattern_buf_len = bufsize;
+ ccb.cdm.patterns = calloc(1, bufsize);
+
+ /* Match any "da" peripherals. */
+ ccb.cdm.patterns[0].type = DEV_MATCH_PERIPH;
+ p = &ccb.cdm.patterns[0].pattern.periph_pattern;
+ p->path_id = path_id;
+ snprintf(p->periph_name, sizeof(p->periph_name), "da");
+ p->flags = PERIPH_MATCH_PATH | PERIPH_MATCH_NAME;
+
+ if (ioctl(xptfd, CAMIOCOMMAND, &ccb) < 0) {
+ error = errno;
+ free(ccb.cdm.matches);
+ free(ccb.cdm.patterns);
+ return (error);
+ }
+ free(ccb.cdm.patterns);
+
+ /* Check for CCB errors. */
+ if ((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ free(ccb.cdm.matches);
+ return (EIO);
+ }
+
+ /* If we need a longer list, try again. */
+ if (ccb.cdm.status == CAM_DEV_MATCH_MORE) {
+ free(ccb.cdm.matches);
+ continue;
+ }
+
+ /* If we got an error, abort. */
+ if (ccb.cdm.status != CAM_DEV_MATCH_LAST) {
+ free(ccb.cdm.matches);
+ return (EIO);
+ }
+ break;
+ }
+
+ /* Shortcut if we don't have any "da" devices. */
+ if (ccb.cdm.num_matches == 0) {
+ free(ccb.cdm.matches);
+ *ndisks = 0;
+ *disksp = NULL;
+ return (0);
+ }
+
+ /* We should have N matches, 1 for each "da" device. */
+ for (i = 0; i < ccb.cdm.num_matches; i++) {
+ if (ccb.cdm.matches[i].type != DEV_MATCH_PERIPH) {
+ warnx("mpt_fetch_disks got wrong CAM matches");
+ free(ccb.cdm.matches);
+ return (EIO);
+ }
+ }
+
+ /*
+ * Some of the "da" peripherals may be for RAID volumes, so
+ * fetch the IOC 2 page (list of RAID volumes) so we can
+ * exclude them from the list.
+ */
+ ioc2 = mpt_read_ioc_page(fd, 2, NULL);
+ if (ioc2 == NULL)
+ return (errno);
+ disks = calloc(ccb.cdm.num_matches, sizeof(*disks));
+ count = 0;
+ for (i = 0; i < ccb.cdm.num_matches; i++) {
+ r = &ccb.cdm.matches[i].result.periph_result;
+ if (periph_is_volume(ioc2, r))
+ continue;
+ disks[count].bus = 0;
+ disks[count].target = r->target_id;
+ snprintf(disks[count].devname, sizeof(disks[count].devname),
+ "%s%d", r->periph_name, r->unit_number);
+
+ dev = cam_open_device(disks[count].devname, O_RDWR);
+ if (dev != NULL) {
+ fetch_scsi_capacity(dev, &disks[count]);
+ fetch_scsi_inquiry(dev, &disks[count]);
+ cam_close_device(dev);
+ }
+ count++;
+ }
+ free(ccb.cdm.matches);
+ free(ioc2);
+
+ *ndisks = count;
+ *disksp = disks;
+ return (0);
+}
+
+/*
+ * Instruct the mpt(4) device to rescan its busses to find new devices
+ * such as disks whose RAID physdisk page was removed or volumes that
+ * were created. If id is -1, the entire bus is rescanned.
+ * Otherwise, only devices at the specified ID are rescanned. If bus
+ * is -1, then all busses are scanned instead of the specified bus.
+ * Note that currently, only bus 0 is supported.
+ */
+int
+mpt_rescan_bus(int bus, int id)
+{
+ union ccb ccb;
+ path_id_t path_id;
+ int error;
+
+ /* mpt(4) only handles devices on bus 0. */
+ if (bus != -1 && bus != 0)
+ return (EINVAL);
+
+ if (xpt_open() < 0)
+ return (ENXIO);
+
+ error = fetch_path_id(&path_id);
+ if (error)
+ return (error);
+
+ /* Perform the actual rescan. */
+ bzero(&ccb, sizeof(ccb));
+ ccb.ccb_h.path_id = path_id;
+ if (id == -1) {
+ ccb.ccb_h.func_code = XPT_SCAN_BUS;
+ ccb.ccb_h.target_id = CAM_TARGET_WILDCARD;
+ ccb.ccb_h.target_lun = CAM_LUN_WILDCARD;
+ ccb.ccb_h.timeout = 5000;
+ } else {
+ ccb.ccb_h.func_code = XPT_SCAN_LUN;
+ ccb.ccb_h.target_id = id;
+ ccb.ccb_h.target_lun = 0;
+ }
+ ccb.crcn.flags = CAM_FLAG_NONE;
+
+ /* Run this at a low priority. */
+ ccb.ccb_h.pinfo.priority = 5;
+
+ if (ioctl(xptfd, CAMIOCOMMAND, &ccb) == -1)
+ return (errno);
+
+ if ((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ warnx("mpt_rescan_bus rescan got CAM error %#x\n",
+ ccb.ccb_h.status & CAM_STATUS_MASK);
+ return (EIO);
+ }
+
+ return (0);
+}
diff --git a/usr.sbin/mptutil/mpt_cmd.c b/usr.sbin/mptutil/mpt_cmd.c
new file mode 100644
index 0000000..8104fc9
--- /dev/null
+++ b/usr.sbin/mptutil/mpt_cmd.c
@@ -0,0 +1,629 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@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.
+ * 3. 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 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>
+__RCSID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <sys/ioctl.h>
+#include <sys/mpt_ioctl.h>
+#include <sys/sysctl.h>
+#include <sys/uio.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mptutil.h"
+
+static const char *mpt_ioc_status_codes[] = {
+ "Success", /* 0x0000 */
+ "Invalid function",
+ "Busy",
+ "Invalid scatter-gather list",
+ "Internal error",
+ "Reserved",
+ "Insufficient resources",
+ "Invalid field",
+ "Invalid state", /* 0x0008 */
+ "Operation state not supported",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* 0x0010 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* 0x0018 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "Invalid configuration action", /* 0x0020 */
+ "Invalid configuration type",
+ "Invalid configuration page",
+ "Invalid configuration data",
+ "No configuration defaults",
+ "Unable to commit configuration change",
+ NULL,
+ NULL,
+ NULL, /* 0x0028 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* 0x0030 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* 0x0038 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "Recovered SCSI error", /* 0x0040 */
+ "Invalid SCSI bus",
+ "Invalid SCSI target ID",
+ "SCSI device not there",
+ "SCSI data overrun",
+ "SCSI data underrun",
+ "SCSI I/O error",
+ "SCSI protocol error",
+ "SCSI task terminated", /* 0x0048 */
+ "SCSI residual mismatch",
+ "SCSI task management failed",
+ "SCSI I/O controller terminated",
+ "SCSI external controller terminated",
+ "EEDP guard error",
+ "EEDP reference tag error",
+ "EEDP application tag error",
+ NULL, /* 0x0050 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* 0x0058 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "SCSI target priority I/O", /* 0x0060 */
+ "Invalid SCSI target port",
+ "Invalid SCSI target I/O index",
+ "SCSI target aborted",
+ "No connection retryable",
+ "No connection",
+ "FC aborted",
+ "Invalid FC receive ID",
+ "FC did invalid", /* 0x0068 */
+ "FC node logged out",
+ "Transfer count mismatch",
+ "STS data not set",
+ "FC exchange canceled",
+ "Data offset error",
+ "Too much write data",
+ "IU too short",
+ "ACK NAK timeout", /* 0x0070 */
+ "NAK received",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* 0x0078 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "LAN device not found", /* 0x0080 */
+ "LAN device failure",
+ "LAN transmit error",
+ "LAN transmit aborted",
+ "LAN receive error",
+ "LAN receive aborted",
+ "LAN partial packet",
+ "LAN canceled",
+ NULL, /* 0x0088 */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "SAS SMP request failed", /* 0x0090 */
+ "SAS SMP data overrun",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "Inband aborted", /* 0x0098 */
+ "No inband connection",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "Diagnostic released", /* 0x00A0 */
+};
+
+static const char *mpt_raid_action_status_codes[] = {
+ "Success",
+ "Invalid action",
+ "Failure",
+ "Operation in progress",
+};
+
+const char *
+mpt_ioc_status(U16 IOCStatus)
+{
+ static char buffer[16];
+
+ IOCStatus &= MPI_IOCSTATUS_MASK;
+ if (IOCStatus < sizeof(mpt_ioc_status_codes) / sizeof(char *) &&
+ mpt_ioc_status_codes[IOCStatus] != NULL)
+ return (mpt_ioc_status_codes[IOCStatus]);
+ snprintf(buffer, sizeof(buffer), "Status: 0x%04x", IOCStatus);
+ return (buffer);
+}
+
+const char *
+mpt_raid_status(U16 ActionStatus)
+{
+ static char buffer[16];
+
+ if (ActionStatus < sizeof(mpt_raid_action_status_codes) /
+ sizeof(char *))
+ return (mpt_raid_action_status_codes[ActionStatus]);
+ snprintf(buffer, sizeof(buffer), "Status: 0x%04x", ActionStatus);
+ return (buffer);
+}
+
+const char *
+mpt_raid_level(U8 VolumeType)
+{
+ static char buf[16];
+
+ switch (VolumeType) {
+ case MPI_RAID_VOL_TYPE_IS:
+ return ("RAID-0");
+ case MPI_RAID_VOL_TYPE_IM:
+ return ("RAID-1");
+ case MPI_RAID_VOL_TYPE_IME:
+ return ("RAID-1E");
+ case MPI_RAID_VOL_TYPE_RAID_5:
+ return ("RAID-5");
+ case MPI_RAID_VOL_TYPE_RAID_6:
+ return ("RAID-6");
+ case MPI_RAID_VOL_TYPE_RAID_10:
+ return ("RAID-10");
+ case MPI_RAID_VOL_TYPE_RAID_50:
+ return ("RAID-50");
+ default:
+ sprintf(buf, "LVL 0x%02x", VolumeType);
+ return (buf);
+ }
+}
+
+const char *
+mpt_volume_name(U8 VolumeBus, U8 VolumeID)
+{
+ static struct mpt_query_disk info;
+ static char buf[16];
+
+ if (mpt_query_disk(VolumeBus, VolumeID, &info) != 0) {
+ /*
+ * We only print out the bus number if it is non-zero
+ * since mpt(4) only supports devices on bus zero
+ * anyway.
+ */
+ if (VolumeBus == 0)
+ snprintf(buf, sizeof(buf), "%d", VolumeID);
+ else
+ snprintf(buf, sizeof(buf), "%d:%d", VolumeBus,
+ VolumeID);
+ return (buf);
+ }
+ return (info.devname);
+}
+
+int
+mpt_lookup_volume(int fd, const char *name, U8 *VolumeBus, U8 *VolumeID)
+{
+ CONFIG_PAGE_IOC_2 *ioc2;
+ CONFIG_PAGE_IOC_2_RAID_VOL *vol;
+ struct mpt_query_disk info;
+ char *cp;
+ long bus, id;
+ int i;
+
+ /*
+ * Check for a raw [<bus>:]<id> string. If the bus is not
+ * specified, assume bus 0.
+ */
+ bus = strtol(name, &cp, 0);
+ if (*cp == ':') {
+ id = strtol(cp + 1, &cp, 0);
+ if (*cp == '\0') {
+ if (bus < 0 || bus > 0xff || id < 0 || id > 0xff) {
+ return (EINVAL);
+ }
+ *VolumeBus = bus;
+ *VolumeID = id;
+ return (0);
+ }
+ } else if (*cp == '\0') {
+ if (bus < 0 || bus > 0xff)
+ return (EINVAL);
+ *VolumeBus = 0;
+ *VolumeID = bus;
+ return (0);
+ }
+
+ ioc2 = mpt_read_ioc_page(fd, 2, NULL);
+ if (ioc2 == NULL)
+ return (errno);
+
+ vol = ioc2->RaidVolume;
+ for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
+ if (mpt_query_disk(vol->VolumeBus, vol->VolumeID, &info) != 0)
+ continue;
+ if (strcmp(name, info.devname) == 0) {
+ *VolumeBus = vol->VolumeBus;
+ *VolumeID = vol->VolumeID;
+ free(ioc2);
+ return (0);
+ }
+ }
+ free(ioc2);
+ return (EINVAL);
+}
+
+int
+mpt_read_config_page_header(int fd, U8 PageType, U8 PageNumber, U32 PageAddress,
+ CONFIG_PAGE_HEADER *header, U16 *IOCStatus)
+{
+ struct mpt_cfg_page_req req;
+
+ if (IOCStatus != NULL)
+ *IOCStatus = MPI_IOCSTATUS_SUCCESS;
+ bzero(&req, sizeof(req));
+ req.header.PageType = PageType;
+ req.header.PageNumber = PageNumber;
+ req.page_address = PageAddress;
+ if (ioctl(fd, MPTIO_READ_CFG_HEADER, &req) < 0)
+ return (errno);
+ if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
+ if (IOCStatus != NULL)
+ *IOCStatus = req.ioc_status;
+ else
+ warnx("Reading config page header failed: %s",
+ mpt_ioc_status(req.ioc_status));
+ return (EIO);
+ }
+ *header = req.header;
+ return (0);
+}
+
+void *
+mpt_read_config_page(int fd, U8 PageType, U8 PageNumber, U32 PageAddress,
+ U16 *IOCStatus)
+{
+ struct mpt_cfg_page_req req;
+ void *buf;
+ int error;
+
+ if (IOCStatus != NULL)
+ *IOCStatus = MPI_IOCSTATUS_SUCCESS;
+ bzero(&req, sizeof(req));
+ req.header.PageType = PageType;
+ req.header.PageNumber = PageNumber;
+ req.page_address = PageAddress;
+ if (ioctl(fd, MPTIO_READ_CFG_HEADER, &req) < 0)
+ return (NULL);
+ if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
+ if (IOCStatus != NULL)
+ *IOCStatus = req.ioc_status;
+ else
+ warnx("Reading config page header failed: %s",
+ mpt_ioc_status(req.ioc_status));
+ errno = EIO;
+ return (NULL);
+ }
+ req.len = req.header.PageLength * 4;
+ buf = malloc(req.len);
+ req.buf = buf;
+ bcopy(&req.header, buf, sizeof(req.header));
+ if (ioctl(fd, MPTIO_READ_CFG_PAGE, &req) < 0) {
+ error = errno;
+ free(buf);
+ errno = error;
+ return (NULL);
+ }
+ if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
+ if (IOCStatus != NULL)
+ *IOCStatus = req.ioc_status;
+ else
+ warnx("Reading config page failed: %s",
+ mpt_ioc_status(req.ioc_status));
+ free(buf);
+ errno = EIO;
+ return (NULL);
+ }
+ return (buf);
+}
+
+void *
+mpt_read_extended_config_page(int fd, U8 ExtPageType, U8 PageVersion,
+ U8 PageNumber, U32 PageAddress, U16 *IOCStatus)
+{
+ struct mpt_ext_cfg_page_req req;
+ void *buf;
+ int error;
+
+ if (IOCStatus != NULL)
+ *IOCStatus = MPI_IOCSTATUS_SUCCESS;
+ bzero(&req, sizeof(req));
+ req.header.PageVersion = PageVersion;
+ req.header.PageNumber = PageNumber;
+ req.header.ExtPageType = ExtPageType;
+ req.page_address = PageAddress;
+ if (ioctl(fd, MPTIO_READ_EXT_CFG_HEADER, &req) < 0)
+ return (NULL);
+ if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
+ if (IOCStatus != NULL)
+ *IOCStatus = req.ioc_status;
+ else
+ warnx("Reading extended config page header failed: %s",
+ mpt_ioc_status(req.ioc_status));
+ errno = EIO;
+ return (NULL);
+ }
+ req.len = req.header.ExtPageLength * 4;
+ buf = malloc(req.len);
+ req.buf = buf;
+ bcopy(&req.header, buf, sizeof(req.header));
+ if (ioctl(fd, MPTIO_READ_EXT_CFG_PAGE, &req) < 0) {
+ error = errno;
+ free(buf);
+ errno = error;
+ return (NULL);
+ }
+ if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
+ if (IOCStatus != NULL)
+ *IOCStatus = req.ioc_status;
+ else
+ warnx("Reading extended config page failed: %s",
+ mpt_ioc_status(req.ioc_status));
+ free(buf);
+ errno = EIO;
+ return (NULL);
+ }
+ return (buf);
+}
+
+int
+mpt_write_config_page(int fd, void *buf, U16 *IOCStatus)
+{
+ CONFIG_PAGE_HEADER *hdr;
+ struct mpt_cfg_page_req req;
+
+ if (IOCStatus != NULL)
+ *IOCStatus = MPI_IOCSTATUS_SUCCESS;
+ bzero(&req, sizeof(req));
+ req.buf = buf;
+ hdr = buf;
+ req.len = hdr->PageLength * 4;
+ if (ioctl(fd, MPTIO_WRITE_CFG_PAGE, &req) < 0)
+ return (errno);
+ if (!IOC_STATUS_SUCCESS(req.ioc_status)) {
+ if (IOCStatus != NULL) {
+ *IOCStatus = req.ioc_status;
+ return (0);
+ }
+ warnx("Writing config page failed: %s",
+ mpt_ioc_status(req.ioc_status));
+ return (EIO);
+ }
+ return (0);
+}
+
+int
+mpt_raid_action(int fd, U8 Action, U8 VolumeBus, U8 VolumeID, U8 PhysDiskNum,
+ U32 ActionDataWord, void *buf, int len, RAID_VOL0_STATUS *VolumeStatus,
+ U32 *ActionData, int datalen, U16 *IOCStatus, U16 *ActionStatus, int write)
+{
+ struct mpt_raid_action raid_act;
+
+ if (IOCStatus != NULL)
+ *IOCStatus = MPI_IOCSTATUS_SUCCESS;
+ if (datalen < 0 || (unsigned)datalen > sizeof(raid_act.action_data))
+ return (EINVAL);
+ bzero(&raid_act, sizeof(raid_act));
+ raid_act.action = Action;
+ raid_act.volume_bus = VolumeBus;
+ raid_act.volume_id = VolumeID;
+ raid_act.phys_disk_num = PhysDiskNum;
+ raid_act.action_data_word = ActionDataWord;
+ if (buf != NULL && len != 0) {
+ raid_act.buf = buf;
+ raid_act.len = len;
+ raid_act.write = write;
+ }
+
+ if (ioctl(fd, MPTIO_RAID_ACTION, &raid_act) < 0)
+ return (errno);
+
+ if (!IOC_STATUS_SUCCESS(raid_act.ioc_status)) {
+ if (IOCStatus != NULL) {
+ *IOCStatus = raid_act.ioc_status;
+ return (0);
+ }
+ warnx("RAID action failed: %s",
+ mpt_ioc_status(raid_act.ioc_status));
+ return (EIO);
+ }
+
+ if (ActionStatus != NULL)
+ *ActionStatus = raid_act.action_status;
+ if (raid_act.action_status != MPI_RAID_ACTION_ASTATUS_SUCCESS) {
+ if (ActionStatus != NULL)
+ return (0);
+ warnx("RAID action failed: %s",
+ mpt_raid_status(raid_act.action_status));
+ return (EIO);
+ }
+
+ if (VolumeStatus != NULL)
+ *((U32 *)VolumeStatus) = raid_act.volume_status;
+ if (ActionData != NULL)
+ bcopy(raid_act.action_data, ActionData, datalen);
+ return (0);
+}
+
+int
+mpt_open(int unit)
+{
+ char path[MAXPATHLEN];
+
+ snprintf(path, sizeof(path), "/dev/mpt%d", unit);
+ return (open(path, O_RDWR));
+}
+
+int
+mpt_table_handler(struct mptutil_command **start, struct mptutil_command **end,
+ int ac, char **av)
+{
+ struct mptutil_command **cmd;
+
+ if (ac < 2) {
+ warnx("The %s command requires a sub-command.", av[0]);
+ return (EINVAL);
+ }
+ for (cmd = start; cmd < end; cmd++) {
+ if (strcmp((*cmd)->name, av[1]) == 0)
+ return ((*cmd)->handler(ac - 1, av + 1));
+ }
+
+ warnx("%s is not a valid sub-command of %s.", av[1], av[0]);
+ return (ENOENT);
+}
+
+#ifdef DEBUG
+void
+hexdump(const void *ptr, int length, const char *hdr, int flags)
+{
+ int i, j, k;
+ int cols;
+ const unsigned char *cp;
+ char delim;
+
+ if ((flags & HD_DELIM_MASK) != 0)
+ delim = (flags & HD_DELIM_MASK) >> 8;
+ else
+ delim = ' ';
+
+ if ((flags & HD_COLUMN_MASK) != 0)
+ cols = flags & HD_COLUMN_MASK;
+ else
+ cols = 16;
+
+ cp = ptr;
+ for (i = 0; i < length; i+= cols) {
+ if (hdr != NULL)
+ printf("%s", hdr);
+
+ if ((flags & HD_OMIT_COUNT) == 0)
+ printf("%04x ", i);
+
+ if ((flags & HD_OMIT_HEX) == 0) {
+ for (j = 0; j < cols; j++) {
+ k = i + j;
+ if (k < length)
+ printf("%c%02x", delim, cp[k]);
+ else
+ printf(" ");
+ }
+ }
+
+ if ((flags & HD_OMIT_CHARS) == 0) {
+ printf(" |");
+ for (j = 0; j < cols; j++) {
+ k = i + j;
+ if (k >= length)
+ printf(" ");
+ else if (cp[k] >= ' ' && cp[k] <= '~')
+ printf("%c", cp[k]);
+ else
+ printf(".");
+ }
+ printf("|");
+ }
+ printf("\n");
+ }
+}
+#endif
diff --git a/usr.sbin/mptutil/mpt_config.c b/usr.sbin/mptutil/mpt_config.c
new file mode 100644
index 0000000..17b9945
--- /dev/null
+++ b/usr.sbin/mptutil/mpt_config.c
@@ -0,0 +1,1199 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@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.
+ * 3. 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 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>
+__RCSID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <err.h>
+#include <fcntl.h>
+#include <libutil.h>
+#include <paths.h>
+#ifdef DEBUG
+#include <stdint.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "mptutil.h"
+
+#ifdef DEBUG
+static void dump_config(CONFIG_PAGE_RAID_VOL_0 *vol);
+#endif
+
+static long
+dehumanize(const char *value)
+{
+ char *vtp;
+ long iv;
+
+ if (value == NULL)
+ return (0);
+ iv = strtoq(value, &vtp, 0);
+ if (vtp == value || (vtp[0] != '\0' && vtp[1] != '\0')) {
+ return (0);
+ }
+ switch (vtp[0]) {
+ case 't': case 'T':
+ iv *= 1024;
+ case 'g': case 'G':
+ iv *= 1024;
+ case 'm': case 'M':
+ iv *= 1024;
+ case 'k': case 'K':
+ iv *= 1024;
+ case '\0':
+ break;
+ default:
+ return (0);
+ }
+ return (iv);
+}
+
+/*
+ * Lock the volume by opening its /dev device read/write. This will
+ * only work if nothing else has it opened (including mounts). We
+ * leak the fd on purpose since this application is not long-running.
+ */
+int
+mpt_lock_volume(U8 VolumeBus, U8 VolumeID)
+{
+ char path[MAXPATHLEN];
+ struct mpt_query_disk qd;
+ int error, vfd;
+
+ error = mpt_query_disk(VolumeBus, VolumeID, &qd);
+ if (error == ENOENT)
+ /*
+ * This means there isn't a CAM device associated with
+ * the volume, and thus it is already implicitly
+ * locked, so just return.
+ */
+ return (0);
+ if (error) {
+ warnc(error, "Unable to lookup volume device name");
+ return (error);
+ }
+ snprintf(path, sizeof(path), "%s%s", _PATH_DEV, qd.devname);
+ vfd = open(path, O_RDWR);
+ if (vfd < 0) {
+ error = errno;
+ warn("Unable to lock volume %s", qd.devname);
+ return (error);
+ }
+ return (0);
+}
+
+static int
+mpt_lock_physdisk(struct mpt_standalone_disk *disk)
+{
+ char path[MAXPATHLEN];
+ int dfd, error;
+
+ snprintf(path, sizeof(path), "%s%s", _PATH_DEV, disk->devname);
+ dfd = open(path, O_RDWR);
+ if (dfd < 0) {
+ error = errno;
+ warn("Unable to lock disk %s", disk->devname);
+ return (error);
+ }
+ return (0);
+}
+
+static int
+mpt_lookup_standalone_disk(const char *name, struct mpt_standalone_disk *disks,
+ int ndisks, int *index)
+{
+ char *cp;
+ long bus, id;
+ int i;
+
+ /* Check for a raw <bus>:<id> string. */
+ bus = strtol(name, &cp, 0);
+ if (*cp == ':') {
+ id = strtol(cp + 1, &cp, 0);
+ if (*cp == '\0') {
+ if (bus < 0 || bus > 0xff || id < 0 || id > 0xff) {
+ return (EINVAL);
+ }
+ for (i = 0; i < ndisks; i++) {
+ if (disks[i].bus == (U8)bus &&
+ disks[i].target == (U8)id) {
+ *index = i;
+ return (0);
+ }
+ }
+ return (ENOENT);
+ }
+ }
+
+ if (name[0] == 'd' && name[1] == 'a') {
+ for (i = 0; i < ndisks; i++) {
+ if (strcmp(name, disks[i].devname) == 0) {
+ *index = i;
+ return (0);
+ }
+ }
+ return (ENOENT);
+ }
+
+ return (EINVAL);
+}
+
+/*
+ * Mark a standalone disk as being a physical disk.
+ */
+static int
+mpt_create_physdisk(int fd, struct mpt_standalone_disk *disk, U8 *PhysDiskNum)
+{
+ CONFIG_PAGE_HEADER header;
+ CONFIG_PAGE_RAID_PHYS_DISK_0 *config_page;
+ int error;
+ U32 ActionData;
+
+ error = mpt_read_config_page_header(fd, MPI_CONFIG_PAGETYPE_RAID_PHYSDISK,
+ 0, 0, &header, NULL);
+ if (error)
+ return (error);
+ if (header.PageVersion > MPI_RAIDPHYSDISKPAGE0_PAGEVERSION) {
+ warnx("Unsupported RAID physdisk page 0 version %d",
+ header.PageVersion);
+ return (EOPNOTSUPP);
+ }
+ config_page = calloc(1, sizeof(CONFIG_PAGE_RAID_PHYS_DISK_0));
+ config_page->Header.PageType = MPI_CONFIG_PAGETYPE_RAID_PHYSDISK;
+ config_page->Header.PageNumber = 0;
+ config_page->Header.PageLength = sizeof(CONFIG_PAGE_RAID_PHYS_DISK_0) /
+ 4;
+ config_page->PhysDiskIOC = 0; /* XXX */
+ config_page->PhysDiskBus = disk->bus;
+ config_page->PhysDiskID = disk->target;
+
+ /* XXX: Enclosure info for PhysDiskSettings? */
+ error = mpt_raid_action(fd, MPI_RAID_ACTION_CREATE_PHYSDISK, 0, 0, 0, 0,
+ config_page, sizeof(CONFIG_PAGE_RAID_PHYS_DISK_0), NULL,
+ &ActionData, sizeof(ActionData), NULL, NULL, 1);
+ if (error)
+ return (error);
+ *PhysDiskNum = ActionData & 0xff;
+ return (0);
+}
+
+static int
+mpt_delete_physdisk(int fd, U8 PhysDiskNum)
+{
+
+ return (mpt_raid_action(fd, MPI_RAID_ACTION_DELETE_PHYSDISK, 0, 0,
+ PhysDiskNum, 0, NULL, 0, NULL, NULL, 0, NULL, NULL, 0));
+}
+
+/*
+ * MPT's firmware does not have a clear command. Instead, we
+ * implement it by deleting each array and disk by hand.
+ */
+static int
+clear_config(int ac, char **av)
+{
+ CONFIG_PAGE_IOC_2 *ioc2;
+ CONFIG_PAGE_IOC_2_RAID_VOL *vol;
+ CONFIG_PAGE_IOC_3 *ioc3;
+ IOC_3_PHYS_DISK *disk;
+ CONFIG_PAGE_IOC_5 *ioc5;
+ IOC_5_HOT_SPARE *spare;
+ int ch, error, fd, i;
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mpt_open");
+ return (error);
+ }
+
+ ioc2 = mpt_read_ioc_page(fd, 2, NULL);
+ if (ioc2 == NULL) {
+ error = errno;
+ warn("Failed to fetch volume list");
+ return (error);
+ }
+
+ /* Lock all the volumes first. */
+ vol = ioc2->RaidVolume;
+ for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
+ if (mpt_lock_volume(vol->VolumeBus, vol->VolumeID) < 0) {
+ warnx("Volume %s is busy and cannot be deleted",
+ mpt_volume_name(vol->VolumeBus, vol->VolumeID));
+ return (EBUSY);
+ }
+ }
+
+ printf(
+ "Are you sure you wish to clear the configuration on mpt%u? [y/N] ",
+ mpt_unit);
+ ch = getchar();
+ if (ch != 'y' && ch != 'Y') {
+ printf("\nAborting\n");
+ return (0);
+ }
+
+ /* Delete all the volumes. */
+ vol = ioc2->RaidVolume;
+ for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
+ error = mpt_raid_action(fd, MPI_RAID_ACTION_DELETE_VOLUME,
+ vol->VolumeBus, vol->VolumeID, 0,
+ MPI_RAID_ACTION_ADATA_DEL_PHYS_DISKS |
+ MPI_RAID_ACTION_ADATA_ZERO_LBA0, NULL, 0, NULL, NULL, 0,
+ NULL, NULL, 0);
+ if (error)
+ warnc(error, "Failed to delete volume %s",
+ mpt_volume_name(vol->VolumeBus, vol->VolumeID));
+ }
+ free(ioc2);
+
+ /* Delete all the spares. */
+ ioc5 = mpt_read_ioc_page(fd, 5, NULL);
+ if (ioc5 == NULL)
+ warn("Failed to fetch spare list");
+ else {
+ spare = ioc5->HotSpare;
+ for (i = 0; i < ioc5->NumHotSpares; spare++, i++)
+ if (mpt_delete_physdisk(fd, spare->PhysDiskNum) < 0)
+ warn("Failed to delete physical disk %d",
+ spare->PhysDiskNum);
+ free(ioc5);
+ }
+
+ /* Delete any RAID physdisks that may be left. */
+ ioc3 = mpt_read_ioc_page(fd, 3, NULL);
+ if (ioc3 == NULL)
+ warn("Failed to fetch drive list");
+ else {
+ disk = ioc3->PhysDisk;
+ for (i = 0; i < ioc3->NumPhysDisks; disk++, i++)
+ if (mpt_delete_physdisk(fd, disk->PhysDiskNum) < 0)
+ warn("Failed to delete physical disk %d",
+ disk->PhysDiskNum);
+ free(ioc3);
+ }
+
+ printf("mpt%d: Configuration cleared\n", mpt_unit);
+ mpt_rescan_bus(-1, -1);
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(top, clear, clear_config);
+
+#define RT_RAID0 0
+#define RT_RAID1 1
+#define RT_RAID1E 2
+
+static struct raid_type_entry {
+ const char *name;
+ int raid_type;
+} raid_type_table[] = {
+ { "raid0", RT_RAID0 },
+ { "raid-0", RT_RAID0 },
+ { "raid1", RT_RAID1 },
+ { "raid-1", RT_RAID1 },
+ { "mirror", RT_RAID1 },
+ { "raid1e", RT_RAID1E },
+ { "raid-1e", RT_RAID1E },
+ { NULL, 0 },
+};
+
+struct config_id_state {
+ struct mpt_standalone_disk *sdisks;
+ struct mpt_drive_list *list;
+ CONFIG_PAGE_IOC_2 *ioc2;
+ U8 target_id;
+ int nsdisks;
+};
+
+struct drive_info {
+ CONFIG_PAGE_RAID_PHYS_DISK_0 *info;
+ struct mpt_standalone_disk *sdisk;
+};
+
+struct volume_info {
+ int drive_count;
+ struct drive_info *drives;
+};
+
+/* Parse a comma-separated list of drives for a volume. */
+static int
+parse_volume(int fd, int raid_type, struct config_id_state *state,
+ char *volume_str, struct volume_info *info)
+{
+ struct drive_info *dinfo;
+ U8 PhysDiskNum;
+ char *cp;
+ int count, error, i;
+
+ cp = volume_str;
+ for (count = 0; cp != NULL; count++) {
+ cp = strchr(cp, ',');
+ if (cp != NULL) {
+ cp++;
+ if (*cp == ',') {
+ warnx("Invalid drive list '%s'", volume_str);
+ return (EINVAL);
+ }
+ }
+ }
+
+ /* Validate the number of drives for this volume. */
+ switch (raid_type) {
+ case RT_RAID0:
+ if (count < 2) {
+ warnx("RAID0 requires at least 2 drives in each "
+ "array");
+ return (EINVAL);
+ }
+ break;
+ case RT_RAID1:
+ if (count != 2) {
+ warnx("RAID1 requires exactly 2 drives in each "
+ "array");
+ return (EINVAL);
+ }
+ break;
+ case RT_RAID1E:
+ if (count < 3) {
+ warnx("RAID1E requires at least 3 drives in each "
+ "array");
+ return (EINVAL);
+ }
+ break;
+ }
+
+ /* Validate each drive. */
+ info->drives = calloc(count, sizeof(struct drive_info));
+ info->drive_count = count;
+ for (dinfo = info->drives; (cp = strsep(&volume_str, ",")) != NULL;
+ dinfo++) {
+ /* If this drive is already a RAID phys just fetch the info. */
+ error = mpt_lookup_drive(state->list, cp, &PhysDiskNum);
+ if (error == 0) {
+ dinfo->info = mpt_pd_info(fd, PhysDiskNum, NULL);
+ if (dinfo->info == NULL)
+ return (errno);
+ continue;
+ }
+
+ /* See if it is a standalone disk. */
+ if (mpt_lookup_standalone_disk(cp, state->sdisks,
+ state->nsdisks, &i) < 0) {
+ error = errno;
+ warn("Unable to lookup drive %s", cp);
+ return (error);
+ }
+ dinfo->sdisk = &state->sdisks[i];
+
+ /* Lock the disk, we will create phys disk pages later. */
+ if (mpt_lock_physdisk(dinfo->sdisk) < 0)
+ return (errno);
+ }
+
+ return (0);
+}
+
+/*
+ * Add RAID physdisk pages for any standalone disks that a volume is
+ * going to use.
+ */
+static int
+add_drives(int fd, struct volume_info *info, int verbose)
+{
+ struct drive_info *dinfo;
+ U8 PhysDiskNum;
+ int error, i;
+
+ for (i = 0, dinfo = info->drives; i < info->drive_count;
+ i++, dinfo++) {
+ if (dinfo->info == NULL) {
+ if (mpt_create_physdisk(fd, dinfo->sdisk,
+ &PhysDiskNum) < 0) {
+ error = errno;
+ warn(
+ "Failed to create physical disk page for %s",
+ dinfo->sdisk->devname);
+ return (error);
+ }
+ if (verbose)
+ printf("Added drive %s with PhysDiskNum %u\n",
+ dinfo->sdisk->devname, PhysDiskNum);
+
+ dinfo->info = mpt_pd_info(fd, PhysDiskNum, NULL);
+ if (dinfo->info == NULL)
+ return (errno);
+ }
+ }
+ return (0);
+}
+
+/*
+ * Find the next free target ID assuming that 'target_id' is the last
+ * one used. 'target_id' should be 0xff for the initial test.
+ */
+static U8
+find_next_volume(struct config_id_state *state)
+{
+ CONFIG_PAGE_IOC_2_RAID_VOL *vol;
+ int i;
+
+restart:
+ /* Assume the current one is used. */
+ state->target_id++;
+
+ /* Search drives first. */
+ for (i = 0; i < state->nsdisks; i++)
+ if (state->sdisks[i].target == state->target_id)
+ goto restart;
+ for (i = 0; i < state->list->ndrives; i++)
+ if (state->list->drives[i]->PhysDiskID == state->target_id)
+ goto restart;
+
+ /* Search volumes second. */
+ vol = state->ioc2->RaidVolume;
+ for (i = 0; i < state->ioc2->NumActiveVolumes; vol++, i++)
+ if (vol->VolumeID == state->target_id)
+ goto restart;
+
+ return (state->target_id);
+}
+
+/* Create a volume and populate it with drives. */
+static CONFIG_PAGE_RAID_VOL_0 *
+build_volume(int fd, struct volume_info *info, int raid_type, long stripe_size,
+ struct config_id_state *state, int verbose)
+{
+ CONFIG_PAGE_HEADER header;
+ CONFIG_PAGE_RAID_VOL_0 *vol;
+ RAID_VOL0_PHYS_DISK *rdisk;
+ struct drive_info *dinfo;
+ U32 MinLBA;
+ uint64_t MaxLBA;
+ size_t page_size;
+ int error, i;
+
+ error = mpt_read_config_page_header(fd, MPI_CONFIG_PAGETYPE_RAID_VOLUME,
+ 0, 0, &header, NULL);
+ if (error) {
+ errno = error;
+ return (NULL);
+ }
+ if (header.PageVersion > MPI_RAIDVOLPAGE0_PAGEVERSION) {
+ warnx("Unsupported RAID volume page 0 version %d",
+ header.PageVersion);
+ errno = EOPNOTSUPP;
+ return (NULL);
+ }
+ page_size = sizeof(CONFIG_PAGE_RAID_VOL_0) +
+ sizeof(RAID_VOL0_PHYS_DISK) * (info->drive_count - 1);
+ vol = calloc(1, page_size);
+ if (vol == NULL)
+ return (NULL);
+
+ /* Header */
+ vol->Header.PageType = MPI_CONFIG_PAGETYPE_RAID_VOLUME;
+ vol->Header.PageNumber = 0;
+ vol->Header.PageLength = page_size / 4;
+
+ /* Properties */
+ vol->VolumeID = find_next_volume(state);
+ vol->VolumeBus = 0;
+ vol->VolumeIOC = 0; /* XXX */
+ vol->VolumeStatus.Flags = MPI_RAIDVOL0_STATUS_FLAG_ENABLED;
+ vol->VolumeStatus.State = MPI_RAIDVOL0_STATUS_STATE_OPTIMAL;
+ vol->VolumeSettings.Settings = MPI_RAIDVOL0_SETTING_USE_DEFAULTS;
+ vol->VolumeSettings.HotSparePool = MPI_RAID_HOT_SPARE_POOL_0;
+ vol->NumPhysDisks = info->drive_count;
+
+ /* Find the smallest drive. */
+ MinLBA = info->drives[0].info->MaxLBA;
+ for (i = 1; i < info->drive_count; i++)
+ if (info->drives[i].info->MaxLBA < MinLBA)
+ MinLBA = info->drives[i].info->MaxLBA;
+
+ /*
+ * Now chop off 512MB at the end to leave room for the
+ * metadata. The controller might only use 64MB, but we just
+ * chop off the max to be simple.
+ */
+ MinLBA -= (512 * 1024 * 1024) / 512;
+
+ switch (raid_type) {
+ case RT_RAID0:
+ vol->VolumeType = MPI_RAID_VOL_TYPE_IS;
+ vol->StripeSize = stripe_size / 512;
+ MaxLBA = MinLBA * info->drive_count;
+ break;
+ case RT_RAID1:
+ vol->VolumeType = MPI_RAID_VOL_TYPE_IM;
+ MaxLBA = MinLBA * (info->drive_count / 2);
+ break;
+ case RT_RAID1E:
+ vol->VolumeType = MPI_RAID_VOL_TYPE_IME;
+ vol->StripeSize = stripe_size / 512;
+ MaxLBA = MinLBA * info->drive_count / 2;
+ break;
+ default:
+ /* Pacify gcc. */
+ abort();
+ }
+
+ /*
+ * If the controller doesn't support 64-bit addressing and the
+ * new volume is larger than 2^32 blocks, warn the user and
+ * truncate the volume.
+ */
+ if (MaxLBA >> 32 != 0 &&
+ !(state->ioc2->CapabilitiesFlags &
+ MPI_IOCPAGE2_CAP_FLAGS_RAID_64_BIT_ADDRESSING)) {
+ warnx(
+ "Controller does not support volumes > 2TB, truncating volume.");
+ MaxLBA = 0xffffffff;
+ }
+ vol->MaxLBA = MaxLBA;
+ vol->MaxLBAHigh = MaxLBA >> 32;
+
+ /* Populate drives. */
+ for (i = 0, dinfo = info->drives, rdisk = vol->PhysDisk;
+ i < info->drive_count; i++, dinfo++, rdisk++) {
+ if (verbose)
+ printf("Adding drive %u (%u:%u) to volume %u:%u\n",
+ dinfo->info->PhysDiskNum, dinfo->info->PhysDiskBus,
+ dinfo->info->PhysDiskID, vol->VolumeBus,
+ vol->VolumeID);
+ if (raid_type == RT_RAID1) {
+ if (i == 0)
+ rdisk->PhysDiskMap =
+ MPI_RAIDVOL0_PHYSDISK_PRIMARY;
+ else
+ rdisk->PhysDiskMap =
+ MPI_RAIDVOL0_PHYSDISK_SECONDARY;
+ } else
+ rdisk->PhysDiskMap = i;
+ rdisk->PhysDiskNum = dinfo->info->PhysDiskNum;
+ }
+
+ return (vol);
+}
+
+static int
+create_volume(int ac, char **av)
+{
+ CONFIG_PAGE_RAID_VOL_0 *vol;
+ struct config_id_state state;
+ struct volume_info *info;
+ long stripe_size;
+ int ch, error, fd, i, quick, raid_type, verbose;
+#ifdef DEBUG
+ int dump;
+#endif
+
+ if (ac < 2) {
+ warnx("create: volume type required");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mpt_open");
+ return (error);
+ }
+
+ /* Lookup the RAID type first. */
+ raid_type = -1;
+ for (i = 0; raid_type_table[i].name != NULL; i++)
+ if (strcasecmp(raid_type_table[i].name, av[1]) == 0) {
+ raid_type = raid_type_table[i].raid_type;
+ break;
+ }
+
+ if (raid_type == -1) {
+ warnx("Unknown or unsupported volume type %s", av[1]);
+ return (EINVAL);
+ }
+
+ /* Parse any options. */
+ optind = 2;
+#ifdef DEBUG
+ dump = 0;
+#endif
+ quick = 0;
+ verbose = 0;
+ stripe_size = 64 * 1024;
+
+ while ((ch = getopt(ac, av, "dqs:v")) != -1) {
+ switch (ch) {
+#ifdef DEBUG
+ case 'd':
+ dump = 1;
+ break;
+#endif
+ case 'q':
+ quick = 1;
+ break;
+ case 's':
+ stripe_size = dehumanize(optarg);
+ if ((stripe_size < 512) || (!powerof2(stripe_size))) {
+ warnx("Invalid stripe size %s", optarg);
+ return (EINVAL);
+ }
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ case '?':
+ default:
+ return (EINVAL);
+ }
+ }
+ ac -= optind;
+ av += optind;
+
+ /* Fetch existing config data. */
+ state.ioc2 = mpt_read_ioc_page(fd, 2, NULL);
+ if (state.ioc2 == NULL) {
+ error = errno;
+ warn("Failed to read volume list");
+ return (error);
+ }
+ state.list = mpt_pd_list(fd);
+ if (state.list == NULL)
+ return (errno);
+ error = mpt_fetch_disks(fd, &state.nsdisks, &state.sdisks);
+ if (error) {
+ warn("Failed to fetch standalone disk list");
+ return (error);
+ }
+ state.target_id = 0xff;
+
+ /* Parse the drive list. */
+ if (ac != 1) {
+ warnx("Exactly one drive list is required");
+ return (EINVAL);
+ }
+ info = calloc(1, sizeof(*info));
+ if (info == NULL)
+ return (ENOMEM);
+ error = parse_volume(fd, raid_type, &state, av[0], info);
+ if (error)
+ return (error);
+
+ /* Create RAID physdisk pages for standalone disks. */
+ error = add_drives(fd, info, verbose);
+ if (error)
+ return (error);
+
+ /* Build the volume. */
+ vol = build_volume(fd, info, raid_type, stripe_size, &state, verbose);
+ if (vol == NULL)
+ return (errno);
+
+#ifdef DEBUG
+ if (dump) {
+ dump_config(vol);
+ goto skip;
+ }
+#endif
+
+ /* Send the new volume to the controller. */
+ error = mpt_raid_action(fd, MPI_RAID_ACTION_CREATE_VOLUME, vol->VolumeBus,
+ vol->VolumeID, 0, quick ? MPI_RAID_ACTION_ADATA_DO_NOT_SYNC : 0,
+ vol, vol->Header.PageLength * 4, NULL, NULL, 0, NULL, NULL, 1);
+ if (error) {
+ errno = error;
+ warn("Failed to add volume");
+ return (error);
+ }
+
+#ifdef DEBUG
+skip:
+#endif
+ mpt_rescan_bus(vol->VolumeBus, vol->VolumeID);
+
+ /* Clean up. */
+ free(vol);
+ free(info);
+ free(state.sdisks);
+ mpt_free_pd_list(state.list);
+ free(state.ioc2);
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(top, create, create_volume);
+
+static int
+delete_volume(int ac, char **av)
+{
+ U8 VolumeBus, VolumeID;
+ int error, fd;
+
+ if (ac != 2) {
+ warnx("delete: volume required");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mpt_open");
+ return (error);
+ }
+
+ error = mpt_lookup_volume(fd, av[1], &VolumeBus, &VolumeID);
+ if (error) {
+ warnc(error, "Invalid volume %s", av[1]);
+ return (error);
+ }
+
+ if (mpt_lock_volume(VolumeBus, VolumeID) < 0)
+ return (errno);
+
+ error = mpt_raid_action(fd, MPI_RAID_ACTION_DELETE_VOLUME, VolumeBus,
+ VolumeID, 0, MPI_RAID_ACTION_ADATA_DEL_PHYS_DISKS |
+ MPI_RAID_ACTION_ADATA_ZERO_LBA0, NULL, 0, NULL, NULL, 0, NULL,
+ NULL, 0);
+ if (error) {
+ warnc(error, "Failed to delete volume");
+ return (error);
+ }
+
+ mpt_rescan_bus(-1, -1);
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(top, delete, delete_volume);
+
+static int
+find_volume_spare_pool(int fd, const char *name, int *pool)
+{
+ CONFIG_PAGE_RAID_VOL_0 *info;
+ CONFIG_PAGE_IOC_2 *ioc2;
+ CONFIG_PAGE_IOC_2_RAID_VOL *vol;
+ U8 VolumeBus, VolumeID;
+ int error, i, j, new_pool, pool_count[7];
+
+ error = mpt_lookup_volume(fd, name, &VolumeBus, &VolumeID);
+ if (error) {
+ warnc(error, "Invalid volume %s", name);
+ return (error);
+ }
+
+ info = mpt_vol_info(fd, VolumeBus, VolumeID, NULL);
+ if (info == NULL)
+ return (errno);
+
+ /*
+ * Check for an existing pool other than pool 0 (used for
+ * global spares).
+ */
+ if ((info->VolumeSettings.HotSparePool & ~MPI_RAID_HOT_SPARE_POOL_0) !=
+ 0) {
+ *pool = 1 << (ffs(info->VolumeSettings.HotSparePool &
+ ~MPI_RAID_HOT_SPARE_POOL_0) - 1);
+ return (0);
+ }
+ free(info);
+
+ /*
+ * Try to find a free pool. First, figure out which pools are
+ * in use.
+ */
+ ioc2 = mpt_read_ioc_page(fd, 2, NULL);
+ if (ioc2 == NULL) {
+ error = errno;
+ warn("Failed to fetch volume list");
+ return (error);
+ }
+ bzero(pool_count, sizeof(pool_count));
+ vol = ioc2->RaidVolume;
+ for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
+ info = mpt_vol_info(fd, vol->VolumeBus, vol->VolumeID, NULL);
+ if (info == NULL)
+ return (errno);
+ for (j = 0; j < 7; j++)
+ if (info->VolumeSettings.HotSparePool & (1 << (j + 1)))
+ pool_count[j]++;
+ free(info);
+ }
+ free(ioc2);
+
+ /* Find the pool with the lowest use count. */
+ new_pool = 0;
+ for (i = 1; i < 7; i++)
+ if (pool_count[i] < pool_count[new_pool])
+ new_pool = i;
+ new_pool++;
+
+ /* Add this pool to the volume. */
+ info = mpt_vol_info(fd, VolumeBus, VolumeID, NULL);
+ if (info == NULL)
+ return (error);
+ info->VolumeSettings.HotSparePool |= (1 << new_pool);
+ error = mpt_raid_action(fd, MPI_RAID_ACTION_CHANGE_VOLUME_SETTINGS,
+ VolumeBus, VolumeID, 0, *(U32 *)&info->VolumeSettings, NULL, 0,
+ NULL, NULL, 0, NULL, NULL, 0);
+ if (error) {
+ warnx("Failed to add spare pool %d to %s", new_pool,
+ mpt_volume_name(VolumeBus, VolumeID));
+ return (error);
+ }
+ free(info);
+
+ *pool = (1 << new_pool);
+ return (0);
+}
+
+static int
+add_spare(int ac, char **av)
+{
+ CONFIG_PAGE_RAID_PHYS_DISK_0 *info;
+ struct mpt_standalone_disk *sdisks;
+ struct mpt_drive_list *list;
+ U8 PhysDiskNum;
+ int error, fd, i, nsdisks, pool;
+
+ if (ac < 2) {
+ warnx("add spare: drive required");
+ return (EINVAL);
+ }
+ if (ac > 3) {
+ warnx("add spare: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mpt_open");
+ return (error);
+ }
+
+ if (ac == 3) {
+ error = find_volume_spare_pool(fd, av[2], &pool);
+ if (error)
+ return (error);
+ } else
+ pool = MPI_RAID_HOT_SPARE_POOL_0;
+
+ list = mpt_pd_list(fd);
+ if (list == NULL)
+ return (errno);
+
+ error = mpt_lookup_drive(list, av[1], &PhysDiskNum);
+ if (error) {
+ error = mpt_fetch_disks(fd, &nsdisks, &sdisks);
+ if (error != 0) {
+ warn("Failed to fetch standalone disk list");
+ return (error);
+ }
+
+ if (mpt_lookup_standalone_disk(av[1], sdisks, nsdisks, &i) <
+ 0) {
+ error = errno;
+ warn("Unable to lookup drive %s", av[1]);
+ return (error);
+ }
+
+ if (mpt_lock_physdisk(&sdisks[i]) < 0)
+ return (errno);
+
+ if (mpt_create_physdisk(fd, &sdisks[i], &PhysDiskNum) < 0) {
+ error = errno;
+ warn("Failed to create physical disk page");
+ return (error);
+ }
+ free(sdisks);
+ }
+ mpt_free_pd_list(list);
+
+ info = mpt_pd_info(fd, PhysDiskNum, NULL);
+ if (info == NULL) {
+ error = errno;
+ warn("Failed to fetch drive info");
+ return (error);
+ }
+
+ info->PhysDiskSettings.HotSparePool = pool;
+ error = mpt_raid_action(fd, MPI_RAID_ACTION_CHANGE_PHYSDISK_SETTINGS, 0,
+ 0, PhysDiskNum, *(U32 *)&info->PhysDiskSettings, NULL, 0, NULL,
+ NULL, 0, NULL, NULL, 0);
+ if (error) {
+ warnc(error, "Failed to assign spare");
+ return (error);
+ }
+
+ free(info);
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(top, add, add_spare);
+
+static int
+remove_spare(int ac, char **av)
+{
+ CONFIG_PAGE_RAID_PHYS_DISK_0 *info;
+ struct mpt_drive_list *list;
+ U8 PhysDiskNum;
+ int error, fd;
+
+ if (ac != 2) {
+ warnx("remove spare: drive required");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mpt_open");
+ return (error);
+ }
+
+ list = mpt_pd_list(fd);
+ if (list == NULL)
+ return (errno);
+
+ error = mpt_lookup_drive(list, av[1], &PhysDiskNum);
+ if (error) {
+ warn("Failed to find drive %s", av[1]);
+ return (error);
+ }
+ mpt_free_pd_list(list);
+
+
+ info = mpt_pd_info(fd, PhysDiskNum, NULL);
+ if (info == NULL) {
+ error = errno;
+ warn("Failed to fetch drive info");
+ return (error);
+ }
+
+ if (info->PhysDiskSettings.HotSparePool == 0) {
+ warnx("Drive %u is not a hot spare", PhysDiskNum);
+ return (EINVAL);
+ }
+
+ if (mpt_delete_physdisk(fd, PhysDiskNum) < 0) {
+ error = errno;
+ warn("Failed to delete physical disk page");
+ return (error);
+ }
+
+ mpt_rescan_bus(info->PhysDiskBus, info->PhysDiskID);
+ free(info);
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(top, remove, remove_spare);
+
+#ifdef DEBUG
+MPT_TABLE(top, pd);
+
+static int
+pd_create(int ac, char **av)
+{
+ struct mpt_standalone_disk *disks;
+ int error, fd, i, ndisks;
+ U8 PhysDiskNum;
+
+ if (ac != 2) {
+ warnx("pd create: drive required");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mpt_open");
+ return (error);
+ }
+
+ error = mpt_fetch_disks(fd, &ndisks, &disks);
+ if (error != 0) {
+ warn("Failed to fetch standalone disk list");
+ return (error);
+ }
+
+ if (mpt_lookup_standalone_disk(av[1], disks, ndisks, &i) < 0) {
+ error = errno;
+ warn("Unable to lookup drive");
+ return (error);
+ }
+
+ if (mpt_lock_physdisk(&disks[i]) < 0)
+ return (errno);
+
+ if (mpt_create_physdisk(fd, &disks[i], &PhysDiskNum) < 0) {
+ error = errno;
+ warn("Failed to create physical disk page");
+ return (error);
+ }
+ free(disks);
+
+ printf("Added drive %s with PhysDiskNum %u\n", av[1], PhysDiskNum);
+
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(pd, create, pd_create);
+
+static int
+pd_delete(int ac, char **av)
+{
+ CONFIG_PAGE_RAID_PHYS_DISK_0 *info;
+ struct mpt_drive_list *list;
+ int error, fd;
+ U8 PhysDiskNum;
+
+ if (ac != 2) {
+ warnx("pd delete: drive required");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mpt_open");
+ return (error);
+ }
+
+ list = mpt_pd_list(fd);
+ if (list == NULL)
+ return (errno);
+
+ if (mpt_lookup_drive(list, av[1], &PhysDiskNum) < 0) {
+ error = errno;
+ warn("Failed to find drive %s", av[1]);
+ return (error);
+ }
+ mpt_free_pd_list(list);
+
+ info = mpt_pd_info(fd, PhysDiskNum, NULL);
+ if (info == NULL) {
+ error = errno;
+ warn("Failed to fetch drive info");
+ return (error);
+ }
+
+ if (mpt_delete_physdisk(fd, PhysDiskNum) < 0) {
+ error = errno;
+ warn("Failed to delete physical disk page");
+ return (error);
+ }
+
+ mpt_rescan_bus(info->PhysDiskBus, info->PhysDiskID);
+ free(info);
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(pd, delete, pd_delete);
+
+/* Display raw data about a volume config. */
+static void
+dump_config(CONFIG_PAGE_RAID_VOL_0 *vol)
+{
+ int i;
+
+ printf("Volume Configuration (Debug):\n");
+ printf(
+ " Page Header: Type 0x%02x Number 0x%02x Length 0x%02x(%u) Version 0x%02x\n",
+ vol->Header.PageType, vol->Header.PageNumber,
+ vol->Header.PageLength, vol->Header.PageLength * 4,
+ vol->Header.PageVersion);
+ printf(" Address: %d:%d IOC %d\n", vol->VolumeBus, vol->VolumeID,
+ vol->VolumeIOC);
+ printf(" Type: %d (%s)\n", vol->VolumeType,
+ mpt_raid_level(vol->VolumeType));
+ printf(" Status: %s (Flags 0x%02x)\n",
+ mpt_volstate(vol->VolumeStatus.State), vol->VolumeStatus.Flags);
+ printf(" Settings: 0x%04x (Spare Pools 0x%02x)\n",
+ vol->VolumeSettings.Settings, vol->VolumeSettings.HotSparePool);
+ printf(" MaxLBA: %ju\n", (uintmax_t)vol->MaxLBAHigh << 32 |
+ vol->MaxLBA);
+ printf(" Stripe Size: %ld\n", (long)vol->StripeSize * 512);
+ printf(" %d Disks:\n", vol->NumPhysDisks);
+
+ for (i = 0; i < vol->NumPhysDisks; i++)
+ printf(" Disk %d: Num 0x%02x Map 0x%02x\n", i,
+ vol->PhysDisk[i].PhysDiskNum, vol->PhysDisk[i].PhysDiskMap);
+}
+
+static int
+debug_config(int ac, char **av)
+{
+ CONFIG_PAGE_RAID_VOL_0 *vol;
+ U8 VolumeBus, VolumeID;
+ int error, fd;
+
+ if (ac != 2) {
+ warnx("debug: volume required");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mpt_open");
+ return (error);
+ }
+
+ error = mpt_lookup_volume(fd, av[1], &VolumeBus, &VolumeID);
+ if (error) {
+ warnc(error, "Invalid volume: %s", av[1]);
+ return (error);
+ }
+
+ vol = mpt_vol_info(fd, VolumeBus, VolumeID, NULL);
+ if (vol == NULL) {
+ error = errno;
+ warn("Failed to get volume info");
+ return (error);
+ }
+
+ dump_config(vol);
+ free(vol);
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(top, debug, debug_config);
+#endif
diff --git a/usr.sbin/mptutil/mpt_drive.c b/usr.sbin/mptutil/mpt_drive.c
new file mode 100644
index 0000000..049ce71
--- /dev/null
+++ b/usr.sbin/mptutil/mpt_drive.c
@@ -0,0 +1,405 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@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.
+ * 3. 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 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>
+__RCSID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <ctype.h>
+#include <err.h>
+#include <libutil.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include <camlib.h>
+#include <cam/scsi/scsi_all.h>
+
+#include "mptutil.h"
+
+const char *
+mpt_pdstate(CONFIG_PAGE_RAID_PHYS_DISK_0 *info)
+{
+ static char buf[16];
+
+ switch (info->PhysDiskStatus.State) {
+ case MPI_PHYSDISK0_STATUS_ONLINE:
+ if ((info->PhysDiskStatus.Flags &
+ MPI_PHYSDISK0_STATUS_FLAG_OUT_OF_SYNC) &&
+ info->PhysDiskSettings.HotSparePool == 0)
+ return ("REBUILD");
+ else
+ return ("ONLINE");
+ case MPI_PHYSDISK0_STATUS_MISSING:
+ return ("MISSING");
+ case MPI_PHYSDISK0_STATUS_NOT_COMPATIBLE:
+ return ("NOT COMPATIBLE");
+ case MPI_PHYSDISK0_STATUS_FAILED:
+ return ("FAILED");
+ case MPI_PHYSDISK0_STATUS_INITIALIZING:
+ return ("INITIALIZING");
+ case MPI_PHYSDISK0_STATUS_OFFLINE_REQUESTED:
+ return ("OFFLINE REQUESTED");
+ case MPI_PHYSDISK0_STATUS_FAILED_REQUESTED:
+ return ("FAILED REQUESTED");
+ case MPI_PHYSDISK0_STATUS_OTHER_OFFLINE:
+ return ("OTHER OFFLINE");
+ default:
+ sprintf(buf, "PSTATE 0x%02x", info->PhysDiskStatus.State);
+ return (buf);
+ }
+}
+
+/*
+ * There are several ways to enumerate physical disks. Unfortunately,
+ * none of them are truly complete, so we have to build a union of all of
+ * them. Specifically:
+ *
+ * - IOC2 : This gives us a list of volumes, and by walking the volumes we
+ * can enumerate all of the drives attached to volumes including
+ * online drives and failed drives.
+ * - IOC3 : This gives us a list of all online physical drives including
+ * drives that are not part of a volume nor a spare drive. It
+ * does not include any failed drives.
+ * - IOC5 : This gives us a list of all spare drives including failed
+ * spares.
+ *
+ * The specific edge cases are that 1) a failed volume member can only be
+ * found via IOC2, 2) a drive that is neither a volume member nor a spare
+ * can only be found via IOC3, and 3) a failed spare can only be found via
+ * IOC5.
+ *
+ * To handle this, walk all of the three lists and use the following
+ * routine to add each drive encountered. It quietly succeeds if the
+ * drive is already present in the list. It also sorts the list as it
+ * inserts new drives.
+ */
+static int
+mpt_pd_insert(int fd, struct mpt_drive_list *list, U8 PhysDiskNum)
+{
+ int i, j;
+
+ /*
+ * First, do a simple linear search to see if we have already
+ * seen this drive.
+ */
+ for (i = 0; i < list->ndrives; i++) {
+ if (list->drives[i]->PhysDiskNum == PhysDiskNum)
+ return (0);
+ if (list->drives[i]->PhysDiskNum > PhysDiskNum)
+ break;
+ }
+
+ /*
+ * 'i' is our slot for the 'new' drive. Make room and then
+ * read the drive info.
+ */
+ for (j = list->ndrives - 1; j >= i; j--)
+ list->drives[j + 1] = list->drives[j];
+ list->drives[i] = mpt_pd_info(fd, PhysDiskNum, NULL);
+ if (list->drives[i] == NULL)
+ return (errno);
+ list->ndrives++;
+ return (0);
+}
+
+struct mpt_drive_list *
+mpt_pd_list(int fd)
+{
+ CONFIG_PAGE_IOC_2 *ioc2;
+ CONFIG_PAGE_IOC_2_RAID_VOL *vol;
+ CONFIG_PAGE_RAID_VOL_0 **volumes;
+ RAID_VOL0_PHYS_DISK *rdisk;
+ CONFIG_PAGE_IOC_3 *ioc3;
+ IOC_3_PHYS_DISK *disk;
+ CONFIG_PAGE_IOC_5 *ioc5;
+ IOC_5_HOT_SPARE *spare;
+ struct mpt_drive_list *list;
+ int count, error, i, j;
+
+ ioc2 = mpt_read_ioc_page(fd, 2, NULL);
+ if (ioc2 == NULL) {
+ error = errno;
+ warn("Failed to fetch volume list");
+ errno = error;
+ return (NULL);
+ }
+
+ ioc3 = mpt_read_ioc_page(fd, 3, NULL);
+ if (ioc3 == NULL) {
+ error = errno;
+ warn("Failed to fetch drive list");
+ free(ioc2);
+ errno = error;
+ return (NULL);
+ }
+
+ ioc5 = mpt_read_ioc_page(fd, 5, NULL);
+ if (ioc5 == NULL) {
+ error = errno;
+ warn("Failed to fetch spare list");
+ free(ioc3);
+ free(ioc2);
+ errno = error;
+ return (NULL);
+ }
+
+ /*
+ * Go ahead and read the info for all the volumes. For this
+ * pass we figure out how many physical drives there are.
+ */
+ volumes = malloc(sizeof(*volumes) * ioc2->NumActiveVolumes);
+ count = 0;
+ vol = ioc2->RaidVolume;
+ for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
+ volumes[i] = mpt_vol_info(fd, vol->VolumeBus, vol->VolumeID,
+ NULL);
+ if (volumes[i] == NULL) {
+ error = errno;
+ warn("Failed to read volume info");
+ errno = error;
+ return (NULL);
+ }
+ count += volumes[i]->NumPhysDisks;
+ }
+ count += ioc3->NumPhysDisks;
+ count += ioc5->NumHotSpares;
+
+ /* Walk the various lists enumerating drives. */
+ list = malloc(sizeof(*list) + sizeof(CONFIG_PAGE_RAID_PHYS_DISK_0) *
+ count);
+ list->ndrives = 0;
+
+ for (i = 0; i < ioc2->NumActiveVolumes; i++) {
+ rdisk = volumes[i]->PhysDisk;
+ for (j = 0; j < volumes[i]->NumPhysDisks; rdisk++, j++)
+ if (mpt_pd_insert(fd, list, rdisk->PhysDiskNum) < 0)
+ return (NULL);
+ free(volumes[i]);
+ }
+ free(ioc2);
+ free(volumes);
+
+ spare = ioc5->HotSpare;
+ for (i = 0; i < ioc5->NumHotSpares; spare++, i++)
+ if (mpt_pd_insert(fd, list, spare->PhysDiskNum) < 0)
+ return (NULL);
+ free(ioc5);
+
+ disk = ioc3->PhysDisk;
+ for (i = 0; i < ioc3->NumPhysDisks; disk++, i++)
+ if (mpt_pd_insert(fd, list, disk->PhysDiskNum) < 0)
+ return (NULL);
+ free(ioc3);
+
+ return (list);
+}
+
+void
+mpt_free_pd_list(struct mpt_drive_list *list)
+{
+ int i;
+
+ for (i = 0; i < list->ndrives; i++)
+ free(list->drives[i]);
+ free(list);
+}
+
+int
+mpt_lookup_drive(struct mpt_drive_list *list, const char *drive,
+ U8 *PhysDiskNum)
+{
+ long val;
+ uint8_t bus, id;
+ char *cp;
+
+ /* Look for a raw device id first. */
+ val = strtol(drive, &cp, 0);
+ if (*cp == '\0') {
+ if (val < 0 || val > 0xff)
+ goto bad;
+ *PhysDiskNum = val;
+ return (0);
+ }
+
+ /* Look for a <bus>:<id> string. */
+ if (*cp == ':') {
+ if (val < 0 || val > 0xff)
+ goto bad;
+ bus = val;
+ val = strtol(cp + 1, &cp, 0);
+ if (*cp != '\0')
+ goto bad;
+ if (val < 0 || val > 0xff)
+ goto bad;
+ id = val;
+
+ for (val = 0; val < list->ndrives; val++) {
+ if (list->drives[val]->PhysDiskBus == bus &&
+ list->drives[val]->PhysDiskID == id) {
+ *PhysDiskNum = list->drives[val]->PhysDiskNum;
+ return (0);
+ }
+ }
+ return (ENOENT);
+ }
+
+bad:
+ return (EINVAL);
+}
+
+/* Borrowed heavily from scsi_all.c:scsi_print_inquiry(). */
+const char *
+mpt_pd_inq_string(CONFIG_PAGE_RAID_PHYS_DISK_0 *pd_info)
+{
+ RAID_PHYS_DISK0_INQUIRY_DATA *inq_data;
+ u_char vendor[9], product[17], revision[5];
+ static char inq_string[64];
+
+ inq_data = &pd_info->InquiryData;
+ cam_strvis(vendor, inq_data->VendorID, sizeof(inq_data->VendorID),
+ sizeof(vendor));
+ cam_strvis(product, inq_data->ProductID, sizeof(inq_data->ProductID),
+ sizeof(product));
+ cam_strvis(revision, inq_data->ProductRevLevel,
+ sizeof(inq_data->ProductRevLevel), sizeof(revision));
+
+ /* Total hack. */
+ if (strcmp(vendor, "ATA") == 0)
+ snprintf(inq_string, sizeof(inq_string), "<%s %s> SATA",
+ product, revision);
+ else
+ snprintf(inq_string, sizeof(inq_string), "<%s %s %s> SAS",
+ vendor, product, revision);
+ return (inq_string);
+}
+
+/* Helper function to set a drive to a given state. */
+static int
+drive_set_state(char *drive, U8 Action, U8 State, const char *name)
+{
+ CONFIG_PAGE_RAID_PHYS_DISK_0 *info;
+ struct mpt_drive_list *list;
+ U8 PhysDiskNum;
+ int error, fd;
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mpt_open");
+ return (error);
+ }
+
+ list = mpt_pd_list(fd);
+ if (list == NULL)
+ return (errno);
+
+ if (mpt_lookup_drive(list, drive, &PhysDiskNum) < 0) {
+ error = errno;
+ warn("Failed to find drive %s", drive);
+ return (error);
+ }
+ mpt_free_pd_list(list);
+
+ /* Get the info for this drive. */
+ info = mpt_pd_info(fd, PhysDiskNum, NULL);
+ if (info == NULL) {
+ error = errno;
+ warn("Failed to fetch info for drive %u", PhysDiskNum);
+ return (error);
+ }
+
+ /* Try to change the state. */
+ if (info->PhysDiskStatus.State == State) {
+ warnx("Drive %u is already in the desired state", PhysDiskNum);
+ return (EINVAL);
+ }
+
+ error = mpt_raid_action(fd, Action, 0, 0, PhysDiskNum, 0, NULL, 0, NULL,
+ NULL, 0, NULL, NULL, 0);
+ if (error) {
+ warnc(error, "Failed to set drive %u to %s", PhysDiskNum, name);
+ return (error);
+ }
+
+ free(info);
+ close(fd);
+
+ return (0);
+}
+
+static int
+fail_drive(int ac, char **av)
+{
+
+ if (ac != 2) {
+ warnx("fail: %s", ac > 2 ? "extra arguments" :
+ "drive required");
+ return (EINVAL);
+ }
+
+ return (drive_set_state(av[1], MPI_RAID_ACTION_FAIL_PHYSDISK,
+ MPI_PHYSDISK0_STATUS_FAILED_REQUESTED, "FAILED"));
+}
+MPT_COMMAND(top, fail, fail_drive);
+
+static int
+online_drive(int ac, char **av)
+{
+
+ if (ac != 2) {
+ warnx("online: %s", ac > 2 ? "extra arguments" :
+ "drive required");
+ return (EINVAL);
+ }
+
+ return (drive_set_state(av[1], MPI_RAID_ACTION_PHYSDISK_ONLINE,
+ MPI_PHYSDISK0_STATUS_ONLINE, "ONLINE"));
+}
+MPT_COMMAND(top, online, online_drive);
+
+static int
+offline_drive(int ac, char **av)
+{
+
+ if (ac != 2) {
+ warnx("offline: %s", ac > 2 ? "extra arguments" :
+ "drive required");
+ return (EINVAL);
+ }
+
+ return (drive_set_state(av[1], MPI_RAID_ACTION_PHYSDISK_OFFLINE,
+ MPI_PHYSDISK0_STATUS_OFFLINE_REQUESTED, "OFFLINE"));
+}
+MPT_COMMAND(top, offline, offline_drive);
diff --git a/usr.sbin/mptutil/mpt_evt.c b/usr.sbin/mptutil/mpt_evt.c
new file mode 100644
index 0000000..9ccdebd
--- /dev/null
+++ b/usr.sbin/mptutil/mpt_evt.c
@@ -0,0 +1,159 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@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.
+ * 3. 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 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>
+__RCSID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <ctype.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "mptutil.h"
+
+static CONFIG_PAGE_LOG_0 *
+mpt_get_events(int fd, U16 *IOCStatus)
+{
+
+ return (mpt_read_extended_config_page(fd, MPI_CONFIG_EXTPAGETYPE_LOG,
+ 0, 0, 0, IOCStatus));
+}
+
+/*
+ * 1 2 3 4 5 6 7
+ * 1234567890123456789012345678901234567890123456789012345678901234567890
+ * < ID> < time > <ty> <X XX XX XX XX XX XX XX XX XX XX XX XX XX |..............|
+ * ID Time Type Log Data
+ */
+static void
+mpt_print_event(MPI_LOG_0_ENTRY *entry, int verbose)
+{
+ int i;
+
+ printf("%5d %7ds %4x ", entry->LogSequence, entry->TimeStamp,
+ entry->LogEntryQualifier);
+ for (i = 0; i < 14; i++)
+ printf("%02x ", entry->LogData[i]);
+ printf("|");
+ for (i = 0; i < 14; i++)
+ printf("%c", isprint(entry->LogData[i]) ? entry->LogData[i] :
+ '.');
+ printf("|\n");
+ printf(" ");
+ for (i = 0; i < 14; i++)
+ printf("%02x ", entry->LogData[i + 14]);
+ printf("|");
+ for (i = 0; i < 14; i++)
+ printf("%c", isprint(entry->LogData[i + 14]) ?
+ entry->LogData[i + 14] : '.');
+ printf("|\n");
+}
+
+static int
+event_compare(const void *first, const void *second)
+{
+ MPI_LOG_0_ENTRY * const *one;
+ MPI_LOG_0_ENTRY * const *two;
+
+ one = first;
+ two = second;
+ return ((*one)->LogSequence - ((*two)->LogSequence));
+}
+
+static int
+show_events(int ac, char **av)
+{
+ CONFIG_PAGE_LOG_0 *log;
+ MPI_LOG_0_ENTRY **entries;
+ int ch, error, fd, i, num_events, verbose;
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mpt_open");
+ return (error);
+ }
+
+ log = mpt_get_events(fd, NULL);
+ if (log == NULL) {
+ error = errno;
+ warn("Failed to get event log info");
+ return (error);
+ }
+
+ /* Default settings. */
+ verbose = 0;
+
+ /* Parse any options. */
+ optind = 1;
+ while ((ch = getopt(ac, av, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ verbose = 1;
+ break;
+ case '?':
+ default:
+ return (EINVAL);
+ }
+ }
+ ac -= optind;
+ av += optind;
+
+ /* Build a list of valid entries and sort them by sequence. */
+ entries = malloc(sizeof(MPI_LOG_0_ENTRY *) * log->NumLogEntries);
+ if (entries == NULL)
+ return (ENOMEM);
+ num_events = 0;
+ for (i = 0; i < log->NumLogEntries; i++) {
+ if (log->LogEntry[i].LogEntryQualifier ==
+ MPI_LOG_0_ENTRY_QUAL_ENTRY_UNUSED)
+ continue;
+ entries[num_events] = &log->LogEntry[i];
+ num_events++;
+ }
+
+ qsort(entries, num_events, sizeof(MPI_LOG_0_ENTRY *), event_compare);
+
+ if (num_events == 0)
+ printf("Event log is empty\n");
+ else {
+ printf(" ID Time Type Log Data\n");
+ for (i = 0; i < num_events; i++)
+ mpt_print_event(entries[i], verbose);
+ }
+
+ free(entries);
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(show, events, show_events);
diff --git a/usr.sbin/mptutil/mpt_show.c b/usr.sbin/mptutil/mpt_show.c
new file mode 100644
index 0000000..153d73e
--- /dev/null
+++ b/usr.sbin/mptutil/mpt_show.c
@@ -0,0 +1,575 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@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.
+ * 3. 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 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>
+__RCSID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <err.h>
+#include <libutil.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "mptutil.h"
+
+MPT_TABLE(top, show);
+
+#define STANDALONE_STATE "ONLINE"
+
+static void
+format_stripe(char *buf, size_t buflen, U32 stripe)
+{
+
+ humanize_number(buf, buflen, stripe * 512, "", HN_AUTOSCALE,
+ HN_B | HN_NOSPACE);
+}
+
+static void
+display_stripe_map(const char *label, U32 StripeMap)
+{
+ char stripe[5];
+ int comma, i;
+
+ comma = 0;
+ printf("%s: ", label);
+ for (i = 0; StripeMap != 0; i++, StripeMap >>= 1)
+ if (StripeMap & 1) {
+ format_stripe(stripe, sizeof(stripe), 1 << i);
+ if (comma)
+ printf(", ");
+ printf("%s", stripe);
+ comma = 1;
+ }
+ printf("\n");
+}
+
+static int
+show_adapter(int ac, char **av)
+{
+ CONFIG_PAGE_MANUFACTURING_0 *man0;
+ CONFIG_PAGE_IOC_2 *ioc2;
+ CONFIG_PAGE_IOC_6 *ioc6;
+ U16 IOCStatus;
+ int comma, error, fd;
+
+ if (ac != 1) {
+ warnx("show adapter: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mpt_open");
+ return (error);
+ }
+
+ man0 = mpt_read_man_page(fd, 0, NULL);
+ if (man0 == NULL) {
+ error = errno;
+ warn("Failed to get controller info");
+ return (error);
+ }
+ if (man0->Header.PageLength < sizeof(*man0) / 4) {
+ warnx("Invalid controller info");
+ return (EINVAL);
+ }
+ printf("mpt%d Adapter:\n", mpt_unit);
+ printf(" Board Name: %.16s\n", man0->BoardName);
+ printf(" Board Assembly: %.16s\n", man0->BoardAssembly);
+ printf(" Chip Name: %.16s\n", man0->ChipName);
+ printf(" Chip Revision: %.16s\n", man0->ChipRevision);
+
+ free(man0);
+
+ ioc2 = mpt_read_ioc_page(fd, 2, &IOCStatus);
+ if (ioc2 != NULL) {
+ printf(" RAID Levels:");
+ comma = 0;
+ if (ioc2->CapabilitiesFlags &
+ MPI_IOCPAGE2_CAP_FLAGS_IS_SUPPORT) {
+ printf(" RAID0");
+ comma = 1;
+ }
+ if (ioc2->CapabilitiesFlags &
+ MPI_IOCPAGE2_CAP_FLAGS_IM_SUPPORT) {
+ printf("%s RAID1", comma ? "," : "");
+ comma = 1;
+ }
+ if (ioc2->CapabilitiesFlags &
+ MPI_IOCPAGE2_CAP_FLAGS_IME_SUPPORT) {
+ printf("%s RAID1E", comma ? "," : "");
+ comma = 1;
+ }
+ if (ioc2->CapabilitiesFlags &
+ MPI_IOCPAGE2_CAP_FLAGS_RAID_5_SUPPORT) {
+ printf("%s RAID5", comma ? "," : "");
+ comma = 1;
+ }
+ if (ioc2->CapabilitiesFlags &
+ MPI_IOCPAGE2_CAP_FLAGS_RAID_6_SUPPORT) {
+ printf("%s RAID6", comma ? "," : "");
+ comma = 1;
+ }
+ if (ioc2->CapabilitiesFlags &
+ MPI_IOCPAGE2_CAP_FLAGS_RAID_10_SUPPORT) {
+ printf("%s RAID10", comma ? "," : "");
+ comma = 1;
+ }
+ if (ioc2->CapabilitiesFlags &
+ MPI_IOCPAGE2_CAP_FLAGS_RAID_50_SUPPORT) {
+ printf("%s RAID50", comma ? "," : "");
+ comma = 1;
+ }
+ if (!comma)
+ printf(" none");
+ printf("\n");
+ free(ioc2);
+ } else if ((IOCStatus & MPI_IOCSTATUS_MASK) !=
+ MPI_IOCSTATUS_CONFIG_INVALID_PAGE)
+ warnx("mpt_read_ioc_page(2): %s", mpt_ioc_status(IOCStatus));
+
+ ioc6 = mpt_read_ioc_page(fd, 6, &IOCStatus);
+ if (ioc6 != NULL) {
+ display_stripe_map(" RAID0 Stripes",
+ ioc6->SupportedStripeSizeMapIS);
+ display_stripe_map(" RAID1E Stripes",
+ ioc6->SupportedStripeSizeMapIME);
+ printf(" RAID0 Drives/Vol: %u", ioc6->MinDrivesIS);
+ if (ioc6->MinDrivesIS != ioc6->MaxDrivesIS)
+ printf("-%u", ioc6->MaxDrivesIS);
+ printf("\n");
+ printf(" RAID1 Drives/Vol: %u", ioc6->MinDrivesIM);
+ if (ioc6->MinDrivesIM != ioc6->MaxDrivesIM)
+ printf("-%u", ioc6->MaxDrivesIM);
+ printf("\n");
+ printf("RAID1E Drives/Vol: %u", ioc6->MinDrivesIME);
+ if (ioc6->MinDrivesIME != ioc6->MaxDrivesIME)
+ printf("-%u", ioc6->MaxDrivesIME);
+ printf("\n");
+ free(ioc6);
+ } else if ((IOCStatus & MPI_IOCSTATUS_MASK) !=
+ MPI_IOCSTATUS_CONFIG_INVALID_PAGE)
+ warnx("mpt_read_ioc_page(6): %s", mpt_ioc_status(IOCStatus));
+
+ /* TODO: Add an ioctl to fetch IOC_FACTS and print firmware version. */
+
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(show, adapter, show_adapter);
+
+static void
+print_vol(CONFIG_PAGE_RAID_VOL_0 *info, int state_len)
+{
+ uint64_t size;
+ const char *level, *state;
+ char buf[6], stripe[5];
+
+ size = ((uint64_t)info->MaxLBAHigh << 32) | info->MaxLBA;
+ humanize_number(buf, sizeof(buf), (size + 1) * 512, "", HN_AUTOSCALE,
+ HN_B | HN_NOSPACE | HN_DECIMAL);
+ if (info->VolumeType == MPI_RAID_VOL_TYPE_IM)
+ stripe[0] = '\0';
+ else
+ format_stripe(stripe, sizeof(stripe), info->StripeSize);
+ level = mpt_raid_level(info->VolumeType);
+ state = mpt_volstate(info->VolumeStatus.State);
+ if (state_len > 0)
+ printf("(%6s) %-8s %6s %-*s", buf, level, stripe, state_len,
+ state);
+ else if (stripe[0] != '\0')
+ printf("(%s) %s %s %s", buf, level, stripe, state);
+ else
+ printf("(%s) %s %s", buf, level, state);
+}
+
+static void
+print_pd(CONFIG_PAGE_RAID_PHYS_DISK_0 *info, int state_len, int location)
+{
+ const char *inq, *state;
+ char buf[6];
+
+ humanize_number(buf, sizeof(buf), ((uint64_t)info->MaxLBA + 1) * 512,
+ "", HN_AUTOSCALE, HN_B | HN_NOSPACE |HN_DECIMAL);
+ state = mpt_pdstate(info);
+ if (state_len > 0)
+ printf("(%6s) %-*s", buf, state_len, state);
+ else
+ printf("(%s) %s", buf, state);
+ inq = mpt_pd_inq_string(info);
+ if (inq != NULL)
+ printf(" %s", inq);
+ if (!location)
+ return;
+ printf(" bus %d id %d", info->PhysDiskBus, info->PhysDiskID);
+}
+
+static void
+print_standalone(struct mpt_standalone_disk *disk, int state_len, int location)
+{
+ char buf[6];
+
+ humanize_number(buf, sizeof(buf), (disk->maxlba + 1) * 512,
+ "", HN_AUTOSCALE, HN_B | HN_NOSPACE |HN_DECIMAL);
+ if (state_len > 0)
+ printf("(%6s) %-*s", buf, state_len, STANDALONE_STATE);
+ else
+ printf("(%s) %s", buf, STANDALONE_STATE);
+ if (disk->inqstring[0] != '\0')
+ printf(" %s", disk->inqstring);
+ if (!location)
+ return;
+ printf(" bus %d id %d", disk->bus, disk->target);
+}
+
+static void
+print_spare_pools(U8 HotSparePool)
+{
+ int i;
+
+ if (HotSparePool == 0) {
+ printf("none");
+ return;
+ }
+ for (i = 0; HotSparePool != 0; i++) {
+ if (HotSparePool & 1) {
+ printf("%d", i);
+ if (HotSparePool == 1)
+ break;
+ printf(", ");
+ }
+ HotSparePool >>= 1;
+ }
+}
+
+static int
+show_config(int ac, char **av)
+{
+ CONFIG_PAGE_IOC_2 *ioc2;
+ CONFIG_PAGE_IOC_2_RAID_VOL *vol;
+ CONFIG_PAGE_IOC_5 *ioc5;
+ IOC_5_HOT_SPARE *spare;
+ CONFIG_PAGE_RAID_VOL_0 *vinfo;
+ RAID_VOL0_PHYS_DISK *disk;
+ CONFIG_PAGE_RAID_VOL_1 *vnames;
+ CONFIG_PAGE_RAID_PHYS_DISK_0 *pinfo;
+ struct mpt_standalone_disk *sdisks;
+ int error, fd, i, j, nsdisks;
+
+ if (ac != 1) {
+ warnx("show config: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mpt_open");
+ return (error);
+ }
+
+ /* Get the config from the controller. */
+ ioc2 = mpt_read_ioc_page(fd, 2, NULL);
+ ioc5 = mpt_read_ioc_page(fd, 5, NULL);
+ if (ioc2 == NULL || ioc5 == NULL) {
+ error = errno;
+ warn("Failed to get config");
+ return (error);
+ }
+ if (mpt_fetch_disks(fd, &nsdisks, &sdisks) < 0) {
+ error = errno;
+ warn("Failed to get standalone drive list");
+ return (error);
+ }
+
+ /* Dump out the configuration. */
+ printf("mpt%d Configuration: %d volumes, %d drives\n",
+ mpt_unit, ioc2->NumActiveVolumes, ioc2->NumActivePhysDisks +
+ nsdisks);
+ vol = ioc2->RaidVolume;
+ for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
+ printf(" volume %s ", mpt_volume_name(vol->VolumeBus,
+ vol->VolumeID));
+ vinfo = mpt_vol_info(fd, vol->VolumeBus, vol->VolumeID, NULL);
+ if (vinfo == NULL) {
+ printf("%s UNKNOWN", mpt_raid_level(vol->VolumeType));
+ } else
+ print_vol(vinfo, -1);
+ vnames = mpt_vol_names(fd, vol->VolumeBus, vol->VolumeID, NULL);
+ if (vnames != NULL) {
+ if (vnames->Name[0] != '\0')
+ printf(" <%s>", vnames->Name);
+ free(vnames);
+ }
+ if (vinfo == NULL) {
+ printf("\n");
+ continue;
+ }
+ printf(" spans:\n");
+ disk = vinfo->PhysDisk;
+ for (j = 0; j < vinfo->NumPhysDisks; disk++, j++) {
+ printf(" drive %u ", disk->PhysDiskNum);
+ pinfo = mpt_pd_info(fd, disk->PhysDiskNum, NULL);
+ if (pinfo != NULL) {
+ print_pd(pinfo, -1, 0);
+ free(pinfo);
+ }
+ printf("\n");
+ }
+ if (vinfo->VolumeSettings.HotSparePool != 0) {
+ printf(" spare pools: ");
+ print_spare_pools(vinfo->VolumeSettings.HotSparePool);
+ printf("\n");
+ }
+ free(vinfo);
+ }
+
+ spare = ioc5->HotSpare;
+ for (i = 0; i < ioc5->NumHotSpares; spare++, i++) {
+ printf(" spare %u ", spare->PhysDiskNum);
+ pinfo = mpt_pd_info(fd, spare->PhysDiskNum, NULL);
+ if (pinfo != NULL) {
+ print_pd(pinfo, -1, 0);
+ free(pinfo);
+ }
+ printf(" backs pool %d\n", ffs(spare->HotSparePool) - 1);
+ }
+ for (i = 0; i < nsdisks; i++) {
+ printf(" drive %s ", sdisks[i].devname);
+ print_standalone(&sdisks[i], -1, 0);
+ printf("\n");
+ }
+ free(ioc2);
+ free(ioc5);
+ free(sdisks);
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(show, config, show_config);
+
+static int
+show_volumes(int ac, char **av)
+{
+ CONFIG_PAGE_IOC_2 *ioc2;
+ CONFIG_PAGE_IOC_2_RAID_VOL *vol;
+ CONFIG_PAGE_RAID_VOL_0 **volumes;
+ CONFIG_PAGE_RAID_VOL_1 *vnames;
+ int error, fd, i, len, state_len;
+
+ if (ac != 1) {
+ warnx("show volumes: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mpt_open");
+ return (error);
+ }
+
+ /* Get the volume list from the controller. */
+ ioc2 = mpt_read_ioc_page(fd, 2, NULL);
+ if (ioc2 == NULL) {
+ error = errno;
+ warn("Failed to get volume list");
+ return (error);
+ }
+
+ /*
+ * Go ahead and read the info for all the volumes and figure
+ * out the maximum width of the state field.
+ */
+ volumes = malloc(sizeof(*volumes) * ioc2->NumActiveVolumes);
+ state_len = strlen("State");
+ vol = ioc2->RaidVolume;
+ for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
+ volumes[i] = mpt_vol_info(fd, vol->VolumeBus, vol->VolumeID,
+ NULL);
+ if (volumes[i] == NULL)
+ len = strlen("UNKNOWN");
+ else
+ len = strlen(mpt_volstate(
+ volumes[i]->VolumeStatus.State));
+ if (len > state_len)
+ state_len = len;
+ }
+ printf("mpt%d Volumes:\n", mpt_unit);
+ printf(" Id Size Level Stripe ");
+ len = state_len - strlen("State");
+ for (i = 0; i < (len + 1) / 2; i++)
+ printf(" ");
+ printf("State");
+ for (i = 0; i < len / 2; i++)
+ printf(" ");
+ printf(" Write-Cache Name\n");
+ vol = ioc2->RaidVolume;
+ for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
+ printf("%6s ", mpt_volume_name(vol->VolumeBus, vol->VolumeID));
+ if (volumes[i] != NULL)
+ print_vol(volumes[i], state_len);
+ else
+ printf(" %-8s %-*s",
+ mpt_raid_level(vol->VolumeType), state_len,
+ "UNKNOWN");
+ if (volumes[i] != NULL) {
+ if (volumes[i]->VolumeSettings.Settings &
+ MPI_RAIDVOL0_SETTING_WRITE_CACHING_ENABLE)
+ printf(" Enabled ");
+ else
+ printf(" Disabled ");
+ } else
+ printf(" ");
+ free(volumes[i]);
+ vnames = mpt_vol_names(fd, vol->VolumeBus, vol->VolumeID, NULL);
+ if (vnames != NULL) {
+ if (vnames->Name[0] != '\0')
+ printf(" <%s>", vnames->Name);
+ free(vnames);
+ }
+ printf("\n");
+ }
+ free(ioc2);
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(show, volumes, show_volumes);
+
+static int
+show_drives(int ac, char **av)
+{
+ struct mpt_drive_list *list;
+ struct mpt_standalone_disk *sdisks;
+ int error, fd, i, len, nsdisks, state_len;
+
+ if (ac != 1) {
+ warnx("show drives: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mpt_open");
+ return (error);
+ }
+
+ /* Get the drive list. */
+ list = mpt_pd_list(fd);
+ if (list == NULL) {
+ error = errno;
+ warn("Failed to get drive list");
+ return (error);
+ }
+
+ /* Fetch the list of standalone disks for this controller. */
+ state_len = 0;
+ if (mpt_fetch_disks(fd, &nsdisks, &sdisks) != 0) {
+ nsdisks = 0;
+ sdisks = NULL;
+ }
+ if (nsdisks != 0)
+ state_len = strlen(STANDALONE_STATE);
+
+ /* Walk the drive list to determine width of state column. */
+ for (i = 0; i < list->ndrives; i++) {
+ len = strlen(mpt_pdstate(list->drives[i]));
+ if (len > state_len)
+ state_len = len;
+ }
+
+ /* List the drives. */
+ printf("mpt%d Physical Drives:\n", mpt_unit);
+ for (i = 0; i < list->ndrives; i++) {
+ printf("%4u ", list->drives[i]->PhysDiskNum);
+ print_pd(list->drives[i], state_len, 1);
+ printf("\n");
+ }
+ mpt_free_pd_list(list);
+ for (i = 0; i < nsdisks; i++) {
+ printf("%4s ", sdisks[i].devname);
+ print_standalone(&sdisks[i], state_len, 1);
+ printf("\n");
+ }
+ free(sdisks);
+
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(show, drives, show_drives);
+
+#ifdef DEBUG
+static int
+show_physdisks(int ac, char **av)
+{
+ CONFIG_PAGE_RAID_PHYS_DISK_0 *pinfo;
+ U16 IOCStatus;
+ int error, fd, i;
+
+ if (ac != 1) {
+ warnx("show drives: extra arguments");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mpt_open");
+ return (error);
+ }
+
+ /* Try to find each possible phys disk page. */
+ for (i = 0; i <= 0xff; i++) {
+ pinfo = mpt_pd_info(fd, i, &IOCStatus);
+ if (pinfo == NULL) {
+ if ((IOCStatus & MPI_IOCSTATUS_MASK) !=
+ MPI_IOCSTATUS_CONFIG_INVALID_PAGE)
+ warnx("mpt_pd_info(%d): %s", i,
+ mpt_ioc_status(IOCStatus));
+ continue;
+ }
+ printf("%3u ", i);
+ print_pd(pinfo, -1, 1);
+ printf("\n");
+ }
+
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(show, pd, show_physdisks);
+#endif
diff --git a/usr.sbin/mptutil/mpt_volume.c b/usr.sbin/mptutil/mpt_volume.c
new file mode 100644
index 0000000..2b273c7
--- /dev/null
+++ b/usr.sbin/mptutil/mpt_volume.c
@@ -0,0 +1,258 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@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.
+ * 3. 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 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>
+__RCSID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <err.h>
+#include <libutil.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include "mptutil.h"
+
+MPT_TABLE(top, volume);
+
+const char *
+mpt_volstate(U8 State)
+{
+ static char buf[16];
+
+ switch (State) {
+ case MPI_RAIDVOL0_STATUS_STATE_OPTIMAL:
+ return ("OPTIMAL");
+ case MPI_RAIDVOL0_STATUS_STATE_DEGRADED:
+ return ("DEGRADED");
+ case MPI_RAIDVOL0_STATUS_STATE_FAILED:
+ return ("FAILED");
+ case MPI_RAIDVOL0_STATUS_STATE_MISSING:
+ return ("MISSING");
+ default:
+ sprintf(buf, "VSTATE 0x%02x", State);
+ return (buf);
+ }
+}
+
+static int
+volume_name(int ac, char **av)
+{
+ CONFIG_PAGE_RAID_VOL_1 *vnames;
+ U8 VolumeBus, VolumeID;
+ int error, fd;
+
+ if (ac != 3) {
+ warnx("name: volume and name required");
+ return (EINVAL);
+ }
+
+ if (strlen(av[2]) >= sizeof(vnames->Name)) {
+ warnx("name: new name is too long");
+ return (ENOSPC);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mpt_open");
+ return (error);
+ }
+
+ error = mpt_lookup_volume(fd, av[1], &VolumeBus, &VolumeID);
+ if (error) {
+ warnc(error, "Invalid volume: %s", av[1]);
+ return (error);
+ }
+
+ vnames = mpt_vol_names(fd, VolumeBus, VolumeID, NULL);
+ if (vnames == NULL) {
+ error = errno;
+ warn("Failed to fetch volume names");
+ return (error);
+ }
+
+ if (vnames->Header.PageType != MPI_CONFIG_PAGEATTR_CHANGEABLE) {
+ warnx("Volume name is read only");
+ return (EOPNOTSUPP);
+ }
+ printf("mpt%u changing volume %s name from \"%s\" to \"%s\"\n",
+ mpt_unit, mpt_volume_name(VolumeBus, VolumeID), vnames->Name,
+ av[2]);
+ bzero(vnames->Name, sizeof(vnames->Name));
+ strcpy(vnames->Name, av[2]);
+
+ if (mpt_write_config_page(fd, vnames, NULL) < 0) {
+ error = errno;
+ warn("Failed to set volume name");
+ return (error);
+ }
+
+ free(vnames);
+ close(fd);
+
+ return (0);
+}
+MPT_COMMAND(top, name, volume_name);
+
+static int
+volume_status(int ac, char **av)
+{
+ MPI_RAID_VOL_INDICATOR prog;
+ RAID_VOL0_STATUS VolumeStatus;
+ uint64_t total, remaining;
+ float pct;
+ U8 VolumeBus, VolumeID;
+ int error, fd;
+
+ if (ac != 2) {
+ warnx("volume status: %s", ac > 2 ? "extra arguments" :
+ "volume required");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mpt_open");
+ return (error);
+ }
+
+ error = mpt_lookup_volume(fd, av[1], &VolumeBus, &VolumeID);
+ if (error) {
+ warnc(error, "Invalid volume: %s", av[1]);
+ return (error);
+ }
+
+ error = mpt_raid_action(fd, MPI_RAID_ACTION_INDICATOR_STRUCT, VolumeBus,
+ VolumeID, 0, 0, NULL, 0, &VolumeStatus, (U32 *)&prog, sizeof(prog),
+ NULL, NULL, 0);
+ if (error) {
+ warnc(error, "Fetching volume status failed");
+ return (error);
+ }
+
+ printf("Volume %s status:\n", mpt_volume_name(VolumeBus, VolumeID));
+ printf(" state: %s\n", mpt_volstate(VolumeStatus.State));
+ printf(" flags:");
+ if (VolumeStatus.Flags & MPI_RAIDVOL0_STATUS_FLAG_ENABLED)
+ printf(" ENABLED");
+ else
+ printf(" DISABLED");
+ if (VolumeStatus.Flags & MPI_RAIDVOL0_STATUS_FLAG_QUIESCED)
+ printf(", QUIESCED");
+ if (VolumeStatus.Flags & MPI_RAIDVOL0_STATUS_FLAG_RESYNC_IN_PROGRESS)
+ printf(", REBUILDING");
+ if (VolumeStatus.Flags & MPI_RAIDVOL0_STATUS_FLAG_VOLUME_INACTIVE)
+ printf(", INACTIVE");
+ if (VolumeStatus.Flags & MPI_RAIDVOL0_STATUS_FLAG_BAD_BLOCK_TABLE_FULL)
+ printf(", BAD BLOCK TABLE FULL");
+ printf("\n");
+ if (VolumeStatus.Flags & MPI_RAIDVOL0_STATUS_FLAG_RESYNC_IN_PROGRESS) {
+ total = (uint64_t)prog.TotalBlocks.High << 32 |
+ prog.TotalBlocks.Low;
+ remaining = (uint64_t)prog.BlocksRemaining.High << 32 |
+ prog.BlocksRemaining.Low;
+ pct = (float)(total - remaining) * 100 / total;
+ printf(" resync: %.2f%% complete\n", pct);
+ }
+
+ close(fd);
+ return (0);
+}
+MPT_COMMAND(volume, status, volume_status);
+
+static int
+volume_cache(int ac, char **av)
+{
+ CONFIG_PAGE_RAID_VOL_0 *volume;
+ U32 Settings, NewSettings;
+ U8 VolumeBus, VolumeID;
+ char *s1;
+ int error, fd;
+
+ if (ac != 3) {
+ warnx("volume cache: %s", ac > 3 ? "extra arguments" :
+ "missing arguments");
+ return (EINVAL);
+ }
+
+ for (s1 = av[2]; *s1 != '\0'; s1++)
+ *s1 = tolower(*s1);
+ if ((strcmp(av[2], "enable")) && (strcmp(av[2], "enabled")) &&
+ (strcmp(av[2], "disable")) && (strcmp(av[2], "disabled"))) {
+ warnx("volume cache: invalid flag, must be 'enable' or 'disable'\n");
+ return (EINVAL);
+ }
+
+ fd = mpt_open(mpt_unit);
+ if (fd < 0) {
+ error = errno;
+ warn("mpt_open");
+ return (error);
+ }
+
+ error = mpt_lookup_volume(fd, av[1], &VolumeBus, &VolumeID);
+ if (error) {
+ warnc(error, "Invalid volume: %s", av[1]);
+ return (error);
+ }
+
+ volume = mpt_vol_info(fd, VolumeBus, VolumeID, NULL);
+ if (volume == NULL)
+ return (errno);
+
+ Settings = volume->VolumeSettings.Settings;
+
+ NewSettings = Settings;
+ if (strncmp(av[2], "enable", sizeof("enable")) == 0)
+ NewSettings |= 0x01;
+ if (strncmp(av[2], "disable", sizeof("disable")) == 0)
+ NewSettings &= ~0x01;
+
+ if (NewSettings == Settings) {
+ warnx("volume cache unchanged");
+ close(fd);
+ return (0);
+ }
+
+ volume->VolumeSettings.Settings = NewSettings;
+ error = mpt_raid_action(fd, MPI_RAID_ACTION_CHANGE_VOLUME_SETTINGS,
+ VolumeBus, VolumeID, 0, *(U32 *)&volume->VolumeSettings, NULL, 0,
+ NULL, NULL, 0, NULL, NULL, 0);
+ if (error)
+ warnc(error, "volume cache change failed");
+
+ close(fd);
+ return (error);
+}
+MPT_COMMAND(volume, cache, volume_cache);
diff --git a/usr.sbin/mptutil/mptutil.8 b/usr.sbin/mptutil/mptutil.8
new file mode 100644
index 0000000..e8b617f
--- /dev/null
+++ b/usr.sbin/mptutil/mptutil.8
@@ -0,0 +1,397 @@
+.\"
+.\" Copyright (c) 2008 Yahoo!, Inc.
+.\" All rights reserved.
+.\" Written by: John Baldwin <jhb@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.
+.\" 3. 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 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 16, 2009
+.Dt MPTUTIL 8
+.Os
+.Sh NAME
+.Nm mptutil
+.Nd Utility for managing LSI Fusion-MPT controllers
+.Sh SYNOPSIS
+.Nm
+.Cm version
+.Nm
+.Op Fl u Ar unit
+.Cm show adapter
+.Nm
+.Op Fl u Ar unit
+.Cm show config
+.Nm
+.Op Fl u Ar unit
+.Cm show drives
+.Nm
+.Op Fl u Ar unit
+.Cm show events
+.Nm
+.Op Fl u Ar unit
+.Cm show volumes
+.Nm
+.Op Fl u Ar unit
+.Cm fail Ar drive
+.Nm
+.Op Fl u Ar unit
+.Cm online Ar drive
+.Nm
+.Op Fl u Ar unit
+.Cm offline Ar drive
+.Nm
+.Op Fl u Ar unit
+.Cm name Ar volume Ar name
+.Nm
+.Op Fl u Ar unit
+.Cm volume status Ar volume
+.Nm
+.Op Fl u Ar unit
+.Cm volume cache Ar volume
+.Ar enable|disable
+.Nm
+.Op Fl u Ar unit
+.Cm clear
+.Nm
+.Op Fl u Ar unit
+.Cm create Ar type
+.Op Fl q
+.Op Fl v
+.Op Fl s Ar stripe_size
+.Ar drive Ns Op \&, Ns Ar drive Ns Op ",..."
+.Nm
+.Op Fl u Ar unit
+.Cm delete Ar volume
+.Nm
+.Op Fl u Ar unit
+.Cm add Ar drive Op Ar volume
+.Nm
+.Op Fl u Ar unit
+.Cm remove Ar drive
+.Sh DESCRIPTION
+The
+.Nm
+utility can be used to display or modify various parameters on LSI
+Fusion-MPT controllers.
+Each invocation of
+.Nm
+consists of zero or more global options followed by a command.
+Commands may support additional optional or required arguments after the
+command.
+.Pp
+Currently one global option is supported:
+.Bl -tag -width indent
+.It Fl u Ar unit
+.Ar unit
+specifies the unit of the controller to work with.
+If no unit is specified,
+then unit 0 is used.
+.El
+.Pp
+Volumes may be specified in two forms.
+First,
+a volume may be identified by its location as
+.Sm off
+.Op Ar xx Ns \&:
+.Ar yy
+.Sm on
+where
+.Ar xx
+is the bus ID and
+.Ar yy
+is the target ID.
+If the bus ID is omitted,
+the volume is assumed to be on bus 0.
+Second,
+on the volume may be specified by the corresponding
+.Em daX
+device,
+such as
+.Em da0 .
+.Pp
+The
+.Xr mpt 4
+controller divides drives up into two categories.
+Configured drives belong to a RAID volume either as a member drive or as a hot
+spare.
+Each configured drive is assigned a unique device ID such as 0 or 1 that is
+show in
+.Cm show config ,
+and in the first column of
+.Cm show drives .
+Any drive not associated with a RAID volume as either a member or a hot spare
+is a standalone drive.
+Standalone drives are visible to the operating system as SCSI disk devices.
+As a result, drives may be specified in three forms.
+First,
+a configured drive may be identified by its device ID.
+Second,
+any drive may be identified by its location as
+.Sm off
+.Ar xx Ns \&:
+.Ar yy
+.Sm on
+where
+.Ar xx
+is the bus ID and
+.Ar yy
+is the target ID for each drive as displayed in
+.Cm show drives .
+Note that unlike volumes,
+a drive location always requires the bus ID to avoid confusion with device IDs.
+Third,
+a standalone drive that is not part of a volume may be identified by its
+corresponding
+.Em daX
+device as displayed in
+.Cm show drives .
+.Pp
+The
+.Nm
+utility supports several different groups of commands.
+The first group of commands provide information about the controller,
+the volumes it manages, and the drives it controls.
+The second group of commands are used to manage the physical drives
+attached to the controller.
+The third group of commands are used to manage the logical volumes
+managed by the controller.
+The fourth group of commands are used to manage the drive configuration for
+the controller.
+.Pp
+The informational commands include:
+.Bl -tag -width indent
+.It Cm version
+Displays the version of
+.Nm .
+.It Cm show adapter
+Displays information about the RAID controller such as the model number.
+.It Cm show config
+Displays the volume and drive configuration for the controller.
+Each volume is listed along with the physical drives that the volume spans.
+If any hot spare drives are configured, then they are listed as well.
+.It Cm show drives
+Lists all of the physical drives attached to the controller.
+.It Cm show events
+Display all the entries from the controller's event log.
+Due to lack of documentation this command is not very useful currently and
+just dumps each log entry in hex.
+.It Cm show volumes
+Lists all of the logical volumes managed by the controller.
+.El
+.Pp
+The physical drive management commands include:
+.Bl -tag -width indent
+.It Cm fail Ar drive
+Mark
+.Ar drive
+as
+.Dq failed requested .
+Note that this state is different from the
+.Dq failed
+state that is used when the firmware fails a drive.
+.Ar Drive
+must be a configured drive.
+.It Cm online Ar drive
+Mark
+.Ar drive
+as an online drive.
+.Ar Drive
+must be part a configured drive in either the
+.Dq offline
+or
+.Dq failed requested
+states.
+.It Cm offline Ar drive
+Mark
+.Ar drive
+as offline.
+.Ar Drive
+must be a configured, online drive.
+.El
+.Pp
+The logical volume management commands include:
+.Bl -tag -width indent
+.It Cm name Ar volume Ar name
+Sets the name of
+.Ar volume
+to
+.Ar name .
+.It Cm volume cache Ar volume Ar enable|disable
+Enables or disables the drive write cache for the member drives of
+.Ar volume .
+.It Cm volume status Ar volume
+Display more detailed status about a single volume including the current
+progress of a rebuild operation if one is being performed.
+.El
+.Pp
+The configuration commands include:
+.Bl -tag -width indent
+.It Cm clear
+Delete the entire configuration including all volumes and spares.
+All drives will become standalone drives.
+.It Xo Cm create Ar type
+.Op Fl q
+.Op Fl v
+.Op Fl s Ar stripe_size
+.Ar drive Ns Op \&, Ns Ar drive Ns Op ",..."
+.Xc
+Create a new volume.
+The
+.Ar type
+specifies the type of volume to create.
+Currently supported types include:
+.Bl -tag -width indent
+.It Cm raid0
+Creates one RAID0 volume spanning the drives listed in the single drive list.
+.It Cm raid1
+Creates one RAID1 volume spanning the drives listed in the single drive list.
+.It Cm raid1e
+Creates one RAID1E volume spanning the drives listed in the single drive list.
+.El
+.Pp
+.Sy Note:
+Not all volume types are supported by all controllers.
+.Pp
+If the
+.Fl q
+flag is specified after
+.Ar type ,
+then a
+.Dq quick
+initialization of the volume will be done.
+This is useful when the drives do not contain any existing data that need
+to be preserved.
+.Pp
+If the
+.Fl v
+flag is specified after
+.Ar type ,
+then more verbose output will be enabled.
+Currently this just provides notification as drives are added to volumes
+when building the configuration.
+.Pp
+The
+.Fl s
+.Ar stripe_size
+parameter allows the stripe size of the array to be set.
+By default a stripe size of 64K is used.
+The list of valid values for a given
+.Ar type
+are listed in the output of
+.Cm show adapter .
+.It Cm delete Ar volume
+Delete the volume
+.Ar volume .
+Member drives will become standalone drives.
+.It Cm add Ar drive Op Ar volume
+Mark
+.Ar drive
+as a hot spare.
+.Ar Drive
+must not be a member of a volume.
+If
+.Ar volume
+is specified,
+then the hot spare will be dedicated to that volume.
+Otherwise,
+.Ar drive
+will be used as a global hot spare backing all volumes for this controller.
+Note that
+.Ar drive
+must be as large as the smallest drive in all of the volumes it is going to
+back.
+.It Cm remove Ar drive
+Remove the hot spare
+.Ar drive
+from service.
+It will become a standalone drive.
+.El
+.Sh EXAMPLES
+Mark the drive at bus 0 target 4 as offline:
+.Pp
+.Dl Nm Cm offline 0:4
+.Pp
+Create a RAID1 array from the two standalone drives
+.Va da1
+and
+.Va da2 :
+.Pp
+.Dl Nm Cm create raid1 da1,da2
+.Pp
+Mark standalone drive
+.Va da3
+as a global hot spare:
+.Pp
+.Dl Nm Cm add da3
+.Sh SEE ALSO
+.Xr mpt 4
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 8.0 .
+.Sh BUGS
+The handling of spare drives appears to be unreliable.
+The
+.Xr mpt 4
+firmware manages spares via spare drive
+.Dq pools .
+There are eight pools numbered 0 through 7.
+Each spare drive can only be assigned to a single pool.
+Each volume can be backed by any combination of zero or more spare pools.
+The
+.Nm
+utility attempts to use the following algorithm for managing spares.
+Global spares are always assigned to pool 0,
+and all volumes are always backed by pool 0.
+For dedicated spares,
+.Nm
+assigns one of the remaining 7 pools to each volume and
+assigns dedicated drives to that pool.
+In practice however, it seems that assigning a drive as a spare does not
+take effect until the box has been rebooted.
+Also, the firmware renumbers the spare pool assignments after a reboot
+which undoes the effects of the algorithm above.
+Simple cases such as assigning global spares seem to work ok
+.Pq albeit requiring a reboot to take effect
+but more
+.Dq exotic
+configurations may not work reliably.
+.Pp
+Drive configuration commands result in an excessive flood of messages on the
+console.
+.Pp
+The mpt version 1 API that is used by
+.Nm
+and
+.Xr mpt 4
+does not support volumes above two terabytes.
+This is a limitation of the API.
+If you are using this adapter with volumes larger than two terabytes, use the adapter in JBOD mode.
+Utilize
+.Xr geom 8 ,
+.Xr zfs 8 ,
+or another software volume manager to work around this limitation.
diff --git a/usr.sbin/mptutil/mptutil.c b/usr.sbin/mptutil/mptutil.c
new file mode 100644
index 0000000..63e8efa
--- /dev/null
+++ b/usr.sbin/mptutil/mptutil.c
@@ -0,0 +1,125 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@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.
+ * 3. 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 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>
+__RCSID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "mptutil.h"
+
+SET_DECLARE(MPT_DATASET(top), struct mptutil_command);
+
+int mpt_unit;
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: mptutil [-u unit] <command> ...\n\n");
+ fprintf(stderr, "Commands include:\n");
+ fprintf(stderr, " version\n");
+ fprintf(stderr, " show adapter - display controller information\n");
+ fprintf(stderr, " show config - display RAID configuration\n");
+ fprintf(stderr, " show drives - list physical drives\n");
+ fprintf(stderr, " show events - display event log\n");
+ fprintf(stderr, " show volumes - list logical volumes\n");
+ fprintf(stderr, " fail <drive> - fail a physical drive\n");
+ fprintf(stderr, " online <drive> - bring an offline physical drive online\n");
+ fprintf(stderr, " offline <drive> - mark a physical drive offline\n");
+ fprintf(stderr, " name <volume> <name>\n");
+ fprintf(stderr, " volume status <volume> - display volume status\n");
+ fprintf(stderr, " volume cache <volume> <enable|disable>\n");
+ fprintf(stderr, " - Enable or disable the volume drive caches\n");
+ fprintf(stderr, " clear - clear volume configuration\n");
+ fprintf(stderr, " create <type> [-vq] [-s stripe] <drive>[,<drive>[,...]]\n");
+ fprintf(stderr, " delete <volume>\n");
+ fprintf(stderr, " add <drive> [volume] - add a hot spare\n");
+ fprintf(stderr, " remove <drive> - remove a hot spare\n");
+#ifdef DEBUG
+ fprintf(stderr, " pd create <drive> - create RAID physdisk\n");
+ fprintf(stderr, " pd delete <drive> - delete RAID physdisk\n");
+ fprintf(stderr, " debug - debug 'show config'\n");
+#endif
+ exit(1);
+}
+
+static int
+version(int ac, char **av)
+{
+
+ printf("mptutil version 1.0.3");
+#ifdef DEBUG
+ printf(" (DEBUG)");
+#endif
+ printf("\n");
+ return (0);
+}
+MPT_COMMAND(top, version, version);
+
+int
+main(int ac, char **av)
+{
+ struct mptutil_command **cmd;
+ int ch;
+
+ while ((ch = getopt(ac, av, "u:")) != -1) {
+ switch (ch) {
+ case 'u':
+ mpt_unit = atoi(optarg);
+ break;
+ case '?':
+ usage();
+ }
+ }
+
+ av += optind;
+ ac -= optind;
+
+ /* getopt() eats av[0], so we can't use mpt_table_handler() directly. */
+ if (ac == 0)
+ usage();
+
+ SET_FOREACH(cmd, MPT_DATASET(top)) {
+ if (strcmp((*cmd)->name, av[0]) == 0) {
+ if ((*cmd)->handler(ac, av))
+ return (1);
+ else
+ return (0);
+ }
+ }
+ warnx("Unknown command %s.", av[0]);
+ return (1);
+}
diff --git a/usr.sbin/mptutil/mptutil.h b/usr.sbin/mptutil/mptutil.h
new file mode 100644
index 0000000..3951235
--- /dev/null
+++ b/usr.sbin/mptutil/mptutil.h
@@ -0,0 +1,178 @@
+/*-
+ * Copyright (c) 2008 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@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.
+ * 3. 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 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 __MPTUTIL_H__
+#define __MPTUTIL_H__
+
+#include <sys/cdefs.h>
+#include <sys/linker_set.h>
+
+#include <dev/mpt/mpilib/mpi_type.h>
+#include <dev/mpt/mpilib/mpi.h>
+#include <dev/mpt/mpilib/mpi_cnfg.h>
+#include <dev/mpt/mpilib/mpi_raid.h>
+
+#define IOC_STATUS_SUCCESS(status) \
+ (((status) & MPI_IOCSTATUS_MASK) == MPI_IOCSTATUS_SUCCESS)
+
+struct mpt_query_disk {
+ char devname[SPECNAMELEN + 1];
+};
+
+struct mpt_standalone_disk {
+ uint64_t maxlba;
+ char inqstring[64];
+ char devname[SPECNAMELEN + 1];
+ u_int bus;
+ u_int target;
+};
+
+struct mpt_drive_list {
+ int ndrives;
+ CONFIG_PAGE_RAID_PHYS_DISK_0 *drives[0];
+};
+
+struct mptutil_command {
+ const char *name;
+ int (*handler)(int ac, char **av);
+};
+
+#define MPT_DATASET(name) mptutil_ ## name ## _table
+
+#define MPT_COMMAND(set, name, function) \
+ static struct mptutil_command function ## _mptutil_command = \
+ { #name, function }; \
+ DATA_SET(MPT_DATASET(set), function ## _mptutil_command)
+
+#define MPT_TABLE(set, name) \
+ SET_DECLARE(MPT_DATASET(name), struct mptutil_command); \
+ \
+ static int \
+ mptutil_ ## name ## _table_handler(int ac, char **av) \
+ { \
+ return (mpt_table_handler(SET_BEGIN(MPT_DATASET(name)), \
+ SET_LIMIT(MPT_DATASET(name)), ac, av)); \
+ } \
+ MPT_COMMAND(set, name, mptutil_ ## name ## _table_handler)
+
+extern int mpt_unit;
+
+#ifdef DEBUG
+void hexdump(const void *ptr, int length, const char *hdr, int flags);
+#define HD_COLUMN_MASK 0xff
+#define HD_DELIM_MASK 0xff00
+#define HD_OMIT_COUNT (1 << 16)
+#define HD_OMIT_HEX (1 << 17)
+#define HD_OMIT_CHARS (1 << 18)
+#endif
+
+int mpt_table_handler(struct mptutil_command **start,
+ struct mptutil_command **end, int ac, char **av);
+int mpt_read_config_page_header(int fd, U8 PageType, U8 PageNumber,
+ U32 PageAddress, CONFIG_PAGE_HEADER *header, U16 *IOCStatus);
+void *mpt_read_config_page(int fd, U8 PageType, U8 PageNumber,
+ U32 PageAddress, U16 *IOCStatus);
+void *mpt_read_extended_config_page(int fd, U8 ExtPageType, U8 PageVersion,
+ U8 PageNumber, U32 PageAddress, U16 *IOCStatus);
+int mpt_write_config_page(int fd, void *buf, U16 *IOCStatus);
+const char *mpt_ioc_status(U16 IOCStatus);
+int mpt_raid_action(int fd, U8 Action, U8 VolumeBus, U8 VolumeID,
+ U8 PhysDiskNum, U32 ActionDataWord, void *buf, int len,
+ RAID_VOL0_STATUS *VolumeStatus, U32 *ActionData, int datalen,
+ U16 *IOCStatus, U16 *ActionStatus, int write);
+const char *mpt_raid_status(U16 ActionStatus);
+int mpt_open(int unit);
+const char *mpt_raid_level(U8 VolumeType);
+const char *mpt_volstate(U8 State);
+const char *mpt_pdstate(CONFIG_PAGE_RAID_PHYS_DISK_0 *info);
+const char *mpt_pd_inq_string(CONFIG_PAGE_RAID_PHYS_DISK_0 *pd_info);
+struct mpt_drive_list *mpt_pd_list(int fd);
+void mpt_free_pd_list(struct mpt_drive_list *list);
+int mpt_query_disk(U8 VolumeBus, U8 VolumeID, struct mpt_query_disk *qd);
+const char *mpt_volume_name(U8 VolumeBus, U8 VolumeID);
+int mpt_fetch_disks(int fd, int *ndisks,
+ struct mpt_standalone_disk **disksp);
+int mpt_lock_volume(U8 VolumeBus, U8 VolumeID);
+int mpt_lookup_drive(struct mpt_drive_list *list, const char *drive,
+ U8 *PhysDiskNum);
+int mpt_lookup_volume(int fd, const char *name, U8 *VolumeBus,
+ U8 *VolumeID);
+int mpt_rescan_bus(int bus, int id);
+
+static __inline void *
+mpt_read_man_page(int fd, U8 PageNumber, U16 *IOCStatus)
+{
+
+ return (mpt_read_config_page(fd, MPI_CONFIG_PAGETYPE_MANUFACTURING,
+ PageNumber, 0, IOCStatus));
+}
+
+static __inline void *
+mpt_read_ioc_page(int fd, U8 PageNumber, U16 *IOCStatus)
+{
+
+ return (mpt_read_config_page(fd, MPI_CONFIG_PAGETYPE_IOC, PageNumber,
+ 0, IOCStatus));
+}
+
+static __inline U32
+mpt_vol_pageaddr(U8 VolumeBus, U8 VolumeID)
+{
+
+ return (VolumeBus << 8 | VolumeID);
+}
+
+static __inline CONFIG_PAGE_RAID_VOL_0 *
+mpt_vol_info(int fd, U8 VolumeBus, U8 VolumeID, U16 *IOCStatus)
+{
+
+ return (mpt_read_config_page(fd, MPI_CONFIG_PAGETYPE_RAID_VOLUME, 0,
+ mpt_vol_pageaddr(VolumeBus, VolumeID), IOCStatus));
+}
+
+static __inline CONFIG_PAGE_RAID_VOL_1 *
+mpt_vol_names(int fd, U8 VolumeBus, U8 VolumeID, U16 *IOCStatus)
+{
+
+ return (mpt_read_config_page(fd, MPI_CONFIG_PAGETYPE_RAID_VOLUME, 1,
+ mpt_vol_pageaddr(VolumeBus, VolumeID), IOCStatus));
+}
+
+static __inline CONFIG_PAGE_RAID_PHYS_DISK_0 *
+mpt_pd_info(int fd, U8 PhysDiskNum, U16 *IOCStatus)
+{
+
+ return (mpt_read_config_page(fd, MPI_CONFIG_PAGETYPE_RAID_PHYSDISK, 0,
+ PhysDiskNum, IOCStatus));
+}
+
+#endif /* !__MPTUTIL_H__ */
diff --git a/usr.sbin/mtest/Makefile b/usr.sbin/mtest/Makefile
new file mode 100644
index 0000000..22c417c
--- /dev/null
+++ b/usr.sbin/mtest/Makefile
@@ -0,0 +1,18 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= mtest
+MAN= mtest.8
+
+BINMODE= 555
+WARNS?= 2
+
+# XXX This assumes INET support in the base system.
+CFLAGS+=-DINET
+
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+=-DINET6
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/mtest/Makefile.depend b/usr.sbin/mtest/Makefile.depend
new file mode 100644
index 0000000..54c1f6f
--- /dev/null
+++ b/usr.sbin/mtest/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/mtest/mtest.8 b/usr.sbin/mtest/mtest.8
new file mode 100644
index 0000000..5ccfa94
--- /dev/null
+++ b/usr.sbin/mtest/mtest.8
@@ -0,0 +1,174 @@
+.\"
+.\" Copyright (c) 2007-2009 Bruce Simpson.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 April 29, 2009
+.Dt MTEST 8
+.Os
+.Sh NAME
+.Nm mtest
+.Nd test multicast socket operations
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+The
+.Nm
+utility
+is a small program for testing multicast socket operations.
+.Pp
+It accepts the following commands, interactively, or as part of a scripted
+input file (useful for automated testing):
+.Pp
+.Bl -tag -width "a ifname e.e.e.e e.e.e.e" -compact -offset indent
+.\"
+.It Ic a Ar ifname Ar mac-addr
+Join the link-layer group address
+.Ar mac-addr
+on interface
+.Ar ifname .
+The group address should be in IEEE 802 MAC format,
+delimited by colon (':') characters.
+.It Ic d Ar ifname Ar mac-addr
+Leave the link-layer group address
+.Ar mac-addr
+on interface
+.Ar ifname .
+.It Ic m Ar ifname Ar 1/0
+Set or reset ALLMULTI mode on interface
+.Ar ifname .
+This option is deprecated and is now a no-op.
+.It Ic p Ar ifname Ar 1/0
+Set or reset promiscuous mode on interface
+.Ar ifname .
+.Pp
+.It Ic j Ar mcast-addr Ar ifname Op Ar source-addr
+Join the multicast address
+.Ar mcast-addr
+on the interface with name
+.Ar ifname .
+.Pp
+If an optional source
+.Ar source-addr
+is specified, a source-specific join will be performed;
+if
+.Nm
+is already joined to the multicast address, the source
+will be added to its filter list.
+.Pp
+.It Ic l Ar mcast-addr Ar ifname Op Ar source-addr
+Leave the multicast address
+.Ar mcast-addr
+on the interface with address
+.Ar ifname .
+If a source
+.Ar source-addr
+is specified, only that source will be left.
+.\"
+.It Ic i Ar mcast-addr Ar ifname Ar n Ar source-addr ...
+Set the socket with membership of
+.Ar mcast-addr
+on interface
+.Ar ifname
+to include filter mode, and add
+.Ar n
+sources beginning with
+.Ar source-addr
+to the inclusion filter list.
+.\"
+.It Ic e Ar mcast-addr Ar ifname Ar n Ar source-addr ...
+Set the socket with membership of
+.Ar mcast-addr
+on interface
+.Ar ifname
+to exclude filter mode, and add
+.Ar n
+sources beginning with
+.Ar source-addr
+to the exclusion filter list.
+.\"
+.It Ic t Ar mcast-addr Ar ifname Ar source-addr
+Set the socket with membership of
+.Ar mcast-addr
+on interface
+.Ar ifname
+to block traffic from source
+.Ar source-addr .
+.\"
+.It Ic b Ar mcast-addr Ar ifname Ar source-addr
+Set the socket with membership of
+.Ar mcast-addr
+on interface
+.Ar ifname
+to allow traffic from source
+.Ar source-addr .
+.\"
+.Pp
+.It Ic g Ar mcast-addr Ar ifname Ar n
+Print
+.Ar n
+source filter entries for
+.Ar mcast-addr
+on interface
+.Ar ifname .
+.\"
+.Pp
+.It Ic f Ar filename
+Read commands from the file
+.Ar filename .
+.It Ic s Ar n
+Sleep for
+.Ar n
+seconds.
+.It Ic ?\&
+List legal commands.
+.It Ic q
+Quit the program.
+.El
+.Sh IMPLEMENTATION NOTES
+For each command implemented by
+.Nm ,
+the address family of each argument must be identical; it is not possible
+to mix IPv4 multicast memberships with IPv6, for example.
+.Pp
+To support IPv6, all commands have now changed to accept an interface
+name rather than an interface address.
+For IPv4, the program will perform
+a lookup of the primary IP address based on the interface name.
+This may fail if no primary IP address is assigned.
+.Sh SEE ALSO
+.Rs
+.%A D. Thaler
+.%A B. Fenner
+.%A B. Quinn
+.%T "Socket Interface Extensions for Multicast Filters"
+.%O RFC 3678
+.Re
+.Sh AUTHORS
+.An -split
+.An "Bruce Simpson"
+.An "Steve Deering"
+.An "Wilbert De Graaf"
diff --git a/usr.sbin/mtest/mtest.c b/usr.sbin/mtest/mtest.c
new file mode 100644
index 0000000..a28fab7
--- /dev/null
+++ b/usr.sbin/mtest/mtest.c
@@ -0,0 +1,851 @@
+/*-
+ * Copyright (c) 2007-2009 Bruce Simpson.
+ * Copyright (c) 2000 Wilbert De Graaf.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Diagnostic and test utility for multicast sockets.
+ * XXX: This file currently assumes INET support in the base system.
+ * TODO: Support embedded KAME Scope ID in IPv6 group addresses.
+ * TODO: Use IPv4 link-local address when source address selection
+ * is implemented; use MCAST_JOIN_SOURCE for IPv4.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/ethernet.h>
+#ifdef INET
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/ip_var.h>
+#endif
+#ifdef INET6
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <err.h>
+#include <unistd.h>
+
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ifaddrs.h>
+
+union sockunion {
+ struct sockaddr_storage ss;
+ struct sockaddr sa;
+ struct sockaddr_dl sdl;
+#ifdef INET
+ struct sockaddr_in sin;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 sin6;
+#endif
+};
+typedef union sockunion sockunion_t;
+
+union mrequnion {
+#ifdef INET
+ struct ip_mreq mr;
+ struct ip_mreq_source mrs;
+#endif
+#ifdef INET6
+ struct ipv6_mreq mr6;
+ struct group_source_req gr;
+#endif
+};
+typedef union mrequnion mrequnion_t;
+
+#define MAX_ADDRS 20
+#define STR_SIZE 20
+#define LINE_LENGTH 80
+
+#ifdef INET
+static int __ifindex_to_primary_ip(const uint32_t, struct in_addr *);
+#endif
+static uint32_t parse_cmd_args(sockunion_t *, sockunion_t *,
+ const char *, const char *, const char *);
+static void process_file(char *, int, int);
+static void process_cmd(char*, int, int, FILE *);
+static int su_cmp(const void *, const void *);
+static void usage(void);
+
+/*
+ * Ordering predicate for qsort().
+ */
+static int
+su_cmp(const void *a, const void *b)
+{
+ const sockunion_t *sua = (const sockunion_t *)a;
+ const sockunion_t *sub = (const sockunion_t *)b;
+
+ assert(sua->sa.sa_family == sub->sa.sa_family);
+
+ switch (sua->sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ return ((int)(sua->sin.sin_addr.s_addr -
+ sub->sin.sin_addr.s_addr));
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ return (memcmp(&sua->sin6.sin6_addr, &sub->sin6.sin6_addr,
+ sizeof(struct in6_addr)));
+ break;
+#endif
+ default:
+ break;
+ }
+
+ assert(sua->sa.sa_len == sub->sa.sa_len);
+ return (memcmp(sua, sub, sua->sa.sa_len));
+}
+
+#ifdef INET
+/*
+ * Internal: Map an interface index to primary IPv4 address.
+ * This is somewhat inefficient. This is a useful enough operation
+ * that it probably belongs in the C library.
+ * Return zero if found, -1 on error, 1 on not found.
+ */
+static int
+__ifindex_to_primary_ip(const uint32_t ifindex, struct in_addr *pina)
+{
+ char ifname[IFNAMSIZ];
+ struct ifaddrs *ifa;
+ struct ifaddrs *ifaddrs;
+ sockunion_t *psu;
+ int retval;
+
+ assert(ifindex != 0);
+
+ retval = -1;
+ if (if_indextoname(ifindex, ifname) == NULL)
+ return (retval);
+ if (getifaddrs(&ifaddrs) < 0)
+ return (retval);
+
+ /*
+ * Find the ifaddr entry corresponding to the interface name,
+ * and return the first matching IPv4 address.
+ */
+ retval = 1;
+ for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
+ if (strcmp(ifa->ifa_name, ifname) != 0)
+ continue;
+ psu = (sockunion_t *)ifa->ifa_addr;
+ if (psu && psu->sa.sa_family == AF_INET) {
+ retval = 0;
+ memcpy(pina, &psu->sin.sin_addr,
+ sizeof(struct in_addr));
+ break;
+ }
+ }
+
+ if (retval != 0)
+ errno = EADDRNOTAVAIL; /* XXX */
+
+ freeifaddrs(ifaddrs);
+ return (retval);
+}
+#endif /* INET */
+
+int
+main(int argc, char **argv)
+{
+ char line[LINE_LENGTH];
+ char *p;
+ int i, s, s6;
+
+ s = -1;
+ s6 = -1;
+#ifdef INET
+ s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (s == -1 && errno != EAFNOSUPPORT)
+ err(1, "can't open IPv4 socket");
+#endif
+#ifdef INET6
+ s6 = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ if (s6 == -1 && errno != EAFNOSUPPORT)
+ err(1, "can't open IPv6 socket");
+#endif
+ if (s == -1 && s6 == -1)
+ errc(1, EPROTONOSUPPORT, "can't open socket");
+
+ if (argc < 2) {
+ if (isatty(STDIN_FILENO)) {
+ printf("multicast membership test program; "
+ "enter ? for list of commands\n");
+ }
+ do {
+ if (fgets(line, sizeof(line), stdin) != NULL) {
+ if (line[0] != 'f')
+ process_cmd(line, s, s6, stdin);
+ else {
+ /* Get the filename */
+ for (i = 1; isblank(line[i]); i++);
+ if ((p = (char*)strchr(line, '\n'))
+ != NULL)
+ *p = '\0';
+ process_file(&line[i], s, s6);
+ }
+ }
+ } while (!feof(stdin));
+ } else {
+ for (i = 1; i < argc; i++) {
+ process_file(argv[i], s, s6);
+ }
+ }
+
+ if (s != -1)
+ close(s);
+ if (s6 != -1)
+ close(s6);
+
+ exit (0);
+}
+
+static void
+process_file(char *fname, int s, int s6)
+{
+ char line[80];
+ FILE *fp;
+ char *lineptr;
+
+ fp = fopen(fname, "r");
+ if (fp == NULL) {
+ warn("fopen");
+ return;
+ }
+
+ /* Skip comments and empty lines. */
+ while (fgets(line, sizeof(line), fp) != NULL) {
+ lineptr = line;
+ while (isblank(*lineptr))
+ lineptr++;
+ if (*lineptr != '#' && *lineptr != '\n')
+ process_cmd(lineptr, s, s6, fp);
+ }
+
+ fclose(fp);
+}
+
+/*
+ * Parse join/leave/allow/block arguments, given:
+ * str1: group (as AF_INET or AF_INET6 printable)
+ * str2: ifname
+ * str3: optional source address (may be NULL).
+ * This argument must have the same parsed address family as str1.
+ * Return the ifindex of ifname, or 0 if any parse element failed.
+ */
+static uint32_t
+parse_cmd_args(sockunion_t *psu, sockunion_t *psu2,
+ const char *str1, const char *str2, const char *str3)
+{
+ struct addrinfo hints;
+ struct addrinfo *res;
+ uint32_t ifindex;
+ int af, error;
+
+ assert(psu != NULL);
+ assert(str1 != NULL);
+ assert(str2 != NULL);
+
+ af = AF_UNSPEC;
+
+ ifindex = if_nametoindex(str2);
+ if (ifindex == 0)
+ return (0);
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_flags = AI_NUMERICHOST;
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+
+ memset(psu, 0, sizeof(sockunion_t));
+ psu->sa.sa_family = AF_UNSPEC;
+
+ error = getaddrinfo(str1, "0", &hints, &res);
+ if (error) {
+ warnx("getaddrinfo: %s", gai_strerror(error));
+ return (0);
+ }
+ assert(res != NULL);
+ af = res->ai_family;
+ memcpy(psu, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+
+ /* sscanf() may pass the empty string. */
+ if (psu2 != NULL && str3 != NULL && *str3 != '\0') {
+ memset(psu2, 0, sizeof(sockunion_t));
+ psu2->sa.sa_family = AF_UNSPEC;
+
+ /* look for following address family; str3 is *optional*. */
+ hints.ai_family = af;
+ error = getaddrinfo(str3, "0", &hints, &res);
+ if (error) {
+ warnx("getaddrinfo: %s", gai_strerror(error));
+ ifindex = 0;
+ } else {
+ if (af != res->ai_family) {
+ errno = EINVAL; /* XXX */
+ ifindex = 0;
+ }
+ memcpy(psu2, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+ }
+ }
+
+ return (ifindex);
+}
+
+static __inline int
+af2sock(const int af, int s, int s6)
+{
+
+#ifdef INET
+ if (af == AF_INET)
+ return (s);
+#endif
+#ifdef INET6
+ if (af == AF_INET6)
+ return (s6);
+#endif
+ return (-1);
+}
+
+static __inline int
+af2socklen(const int af)
+{
+
+#ifdef INET
+ if (af == AF_INET)
+ return (sizeof(struct sockaddr_in));
+#endif
+#ifdef INET6
+ if (af == AF_INET6)
+ return (sizeof(struct sockaddr_in6));
+#endif
+ return (-1);
+}
+
+static void
+process_cmd(char *cmd, int s, int s6, FILE *fp __unused)
+{
+ char str1[STR_SIZE];
+ char str2[STR_SIZE];
+ char str3[STR_SIZE];
+ mrequnion_t mr;
+ sockunion_t su, su2;
+ struct ifreq ifr;
+ char *line;
+ char *toptname;
+ void *optval;
+ uint32_t fmode, ifindex;
+ socklen_t optlen;
+ int af, error, f, flags, i, level, n, optname;
+
+ af = AF_UNSPEC;
+ su.sa.sa_family = AF_UNSPEC;
+ su2.sa.sa_family = AF_UNSPEC;
+
+ line = cmd;
+ while (isblank(*++line))
+ ; /* Skip whitespace. */
+
+ switch (*cmd) {
+ case '?':
+ usage();
+ break;
+
+ case 'q':
+ close(s);
+ exit(0);
+
+ case 's':
+ if ((sscanf(line, "%d", &n) != 1) || (n < 1)) {
+ printf("-1\n");
+ break;
+ }
+ sleep(n);
+ printf("ok\n");
+ break;
+
+ case 'j':
+ case 'l':
+ str3[0] = '\0';
+ toptname = "";
+ sscanf(line, "%s %s %s", str1, str2, str3);
+ ifindex = parse_cmd_args(&su, &su2, str1, str2, str3);
+ if (ifindex == 0) {
+ printf("-1\n");
+ break;
+ }
+ af = su.sa.sa_family;
+#ifdef INET
+ if (af == AF_INET) {
+ struct in_addr ina;
+
+ error = __ifindex_to_primary_ip(ifindex, &ina);
+ if (error != 0) {
+ warn("primary_ip_lookup %s", str2);
+ printf("-1\n");
+ break;
+ }
+ level = IPPROTO_IP;
+
+ if (su2.sa.sa_family != AF_UNSPEC) {
+ mr.mrs.imr_multiaddr = su.sin.sin_addr;
+ mr.mrs.imr_sourceaddr = su2.sin.sin_addr;
+ mr.mrs.imr_interface = ina;
+ optname = (*cmd == 'j') ?
+ IP_ADD_SOURCE_MEMBERSHIP :
+ IP_DROP_SOURCE_MEMBERSHIP;
+ toptname = (*cmd == 'j') ?
+ "IP_ADD_SOURCE_MEMBERSHIP" :
+ "IP_DROP_SOURCE_MEMBERSHIP";
+ optval = (void *)&mr.mrs;
+ optlen = sizeof(mr.mrs);
+ } else {
+ mr.mr.imr_multiaddr = su.sin.sin_addr;
+ mr.mr.imr_interface = ina;
+ optname = (*cmd == 'j') ?
+ IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP;
+ toptname = (*cmd == 'j') ?
+ "IP_ADD_MEMBERSHIP" : "IP_DROP_MEMBERSHIP";
+ optval = (void *)&mr.mr;
+ optlen = sizeof(mr.mr);
+ }
+ if (s < 0) {
+ warnc(EPROTONOSUPPORT, "setsockopt %s",
+ toptname);
+ } else if (setsockopt(s, level, optname, optval,
+ optlen) == 0) {
+ printf("ok\n");
+ break;
+ } else {
+ warn("setsockopt %s", toptname);
+ }
+ }
+#ifdef INET6
+ else
+#endif /* INET with INET6 */
+#endif /* INET */
+#ifdef INET6
+ if (af == AF_INET6) {
+ level = IPPROTO_IPV6;
+ if (su2.sa.sa_family != AF_UNSPEC) {
+ mr.gr.gsr_interface = ifindex;
+ mr.gr.gsr_group = su.ss;
+ mr.gr.gsr_source = su2.ss;
+ optname = (*cmd == 'j') ?
+ MCAST_JOIN_SOURCE_GROUP:
+ MCAST_LEAVE_SOURCE_GROUP;
+ toptname = (*cmd == 'j') ?
+ "MCAST_JOIN_SOURCE_GROUP":
+ "MCAST_LEAVE_SOURCE_GROUP";
+ optval = (void *)&mr.gr;
+ optlen = sizeof(mr.gr);
+ } else {
+ mr.mr6.ipv6mr_multiaddr = su.sin6.sin6_addr;
+ mr.mr6.ipv6mr_interface = ifindex;
+ optname = (*cmd == 'j') ?
+ IPV6_JOIN_GROUP :
+ IPV6_LEAVE_GROUP;
+ toptname = (*cmd == 'j') ?
+ "IPV6_JOIN_GROUP" :
+ "IPV6_LEAVE_GROUP";
+ optval = (void *)&mr.mr6;
+ optlen = sizeof(mr.mr6);
+ }
+ if (s6 < 0) {
+ warnc(EPROTONOSUPPORT, "setsockopt %s",
+ toptname);
+ } else if (setsockopt(s6, level, optname, optval,
+ optlen) == 0) {
+ printf("ok\n");
+ break;
+ } else {
+ warn("setsockopt %s", toptname);
+ }
+ }
+#endif /* INET6 */
+ /* FALLTHROUGH */
+ printf("-1\n");
+ break;
+
+ /*
+ * Set the socket to include or exclude filter mode, and
+ * add some sources to the filterlist, using the full-state API.
+ */
+ case 'i':
+ case 'e': {
+ sockunion_t sources[MAX_ADDRS];
+ struct addrinfo hints;
+ struct addrinfo *res;
+ char *cp;
+ int af1;
+
+ n = 0;
+ fmode = (*cmd == 'i') ? MCAST_INCLUDE : MCAST_EXCLUDE;
+ if ((sscanf(line, "%s %s %d", str1, str2, &n)) != 3) {
+ printf("-1\n");
+ break;
+ }
+
+ ifindex = parse_cmd_args(&su, NULL, str1, str2, NULL);
+ if (ifindex == 0 || n < 0 || n > MAX_ADDRS) {
+ printf("-1\n");
+ break;
+ }
+ af = su.sa.sa_family;
+ if (af2sock(af, s, s6) == -1) {
+ warnc(EPROTONOSUPPORT, "setsourcefilter");
+ break;
+ }
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_flags = AI_NUMERICHOST;
+ hints.ai_family = af;
+ hints.ai_socktype = SOCK_DGRAM;
+
+ for (i = 0; i < n; i++) {
+ sockunion_t *psu = (sockunion_t *)&sources[i];
+ /*
+ * Trim trailing whitespace, as getaddrinfo()
+ * can't cope with it.
+ */
+ fgets(str1, sizeof(str1), fp);
+ cp = strchr(str1, '\n');
+ if (cp != NULL)
+ *cp = '\0';
+
+ res = NULL;
+ error = getaddrinfo(str1, "0", &hints, &res);
+ if (error)
+ break;
+ assert(res != NULL);
+
+ memset(psu, 0, sizeof(sockunion_t));
+ af1 = res->ai_family;
+ if (af1 == af)
+ memcpy(psu, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+ if (af1 != af)
+ break;
+ }
+ if (i < n) {
+ if (error)
+ warnx("getaddrinfo: %s", gai_strerror(error));
+ printf("-1\n");
+ break;
+ }
+ if (setsourcefilter(af2sock(af, s, s6), ifindex,
+ &su.sa, su.sa.sa_len, fmode, n, &sources[0].ss) != 0)
+ warn("setsourcefilter");
+ else
+ printf("ok\n");
+ } break;
+
+ /*
+ * Allow or block traffic from a source, using the
+ * delta based api.
+ */
+ case 't':
+ case 'b': {
+ str3[0] = '\0';
+ toptname = "";
+ sscanf(line, "%s %s %s", str1, str2, str3);
+ ifindex = parse_cmd_args(&su, &su2, str1, str2, str3);
+ if (ifindex == 0 || su2.sa.sa_family == AF_UNSPEC) {
+ printf("-1\n");
+ break;
+ }
+ af = su.sa.sa_family;
+ if (af2sock(af, s, s6) == -1) {
+ warnc(EPROTONOSUPPORT, "getsourcefilter");
+ break;
+ }
+
+ /* First determine our current filter mode. */
+ n = 0;
+ if (getsourcefilter(af2sock(af, s, s6), ifindex,
+ &su.sa, su.sa.sa_len, &fmode, &n, NULL) != 0) {
+ warn("getsourcefilter");
+ break;
+ }
+#ifdef INET
+ if (af == AF_INET) {
+ struct in_addr ina;
+
+ error = __ifindex_to_primary_ip(ifindex, &ina);
+ if (error != 0) {
+ warn("primary_ip_lookup %s", str2);
+ printf("-1\n");
+ break;
+ }
+ level = IPPROTO_IP;
+ optval = (void *)&mr.mrs;
+ optlen = sizeof(mr.mrs);
+ mr.mrs.imr_multiaddr = su.sin.sin_addr;
+ mr.mrs.imr_sourceaddr = su2.sin.sin_addr;
+ mr.mrs.imr_interface = ina;
+ if (fmode == MCAST_EXCLUDE) {
+ /* Any-source mode socket membership. */
+ optname = (*cmd == 't') ?
+ IP_UNBLOCK_SOURCE :
+ IP_BLOCK_SOURCE;
+ toptname = (*cmd == 't') ?
+ "IP_UNBLOCK_SOURCE" :
+ "IP_BLOCK_SOURCE";
+ } else {
+ /* Source-specific mode socket membership. */
+ optname = (*cmd == 't') ?
+ IP_ADD_SOURCE_MEMBERSHIP :
+ IP_DROP_SOURCE_MEMBERSHIP;
+ toptname = (*cmd == 't') ?
+ "IP_ADD_SOURCE_MEMBERSHIP" :
+ "IP_DROP_SOURCE_MEMBERSHIP";
+ }
+ if (setsockopt(s, level, optname, optval,
+ optlen) == 0) {
+ printf("ok\n");
+ break;
+ } else {
+ warn("setsockopt %s", toptname);
+ }
+ }
+#ifdef INET6
+ else
+#endif /* INET with INET6 */
+#endif /* INET */
+#ifdef INET6
+ if (af == AF_INET6) {
+ level = IPPROTO_IPV6;
+ mr.gr.gsr_interface = ifindex;
+ mr.gr.gsr_group = su.ss;
+ mr.gr.gsr_source = su2.ss;
+ if (fmode == MCAST_EXCLUDE) {
+ /* Any-source mode socket membership. */
+ optname = (*cmd == 't') ?
+ MCAST_UNBLOCK_SOURCE :
+ MCAST_BLOCK_SOURCE;
+ toptname = (*cmd == 't') ?
+ "MCAST_UNBLOCK_SOURCE" :
+ "MCAST_BLOCK_SOURCE";
+ } else {
+ /* Source-specific mode socket membership. */
+ optname = (*cmd == 't') ?
+ MCAST_JOIN_SOURCE_GROUP :
+ MCAST_LEAVE_SOURCE_GROUP;
+ toptname = (*cmd == 't') ?
+ "MCAST_JOIN_SOURCE_GROUP":
+ "MCAST_LEAVE_SOURCE_GROUP";
+ }
+ optval = (void *)&mr.gr;
+ optlen = sizeof(mr.gr);
+ if (setsockopt(s6, level, optname, optval,
+ optlen) == 0) {
+ printf("ok\n");
+ break;
+ } else {
+ warn("setsockopt %s", toptname);
+ }
+ }
+#endif /* INET6 */
+ /* FALLTHROUGH */
+ printf("-1\n");
+ } break;
+
+ case 'g': {
+ sockunion_t sources[MAX_ADDRS];
+ char addrbuf[NI_MAXHOST];
+ int nreqsrc, nsrc;
+
+ if ((sscanf(line, "%s %s %d", str1, str2, &nreqsrc)) != 3) {
+ printf("-1\n");
+ break;
+ }
+ ifindex = parse_cmd_args(&su, NULL, str1, str2, NULL);
+ if (ifindex == 0 || (n < 0 || n > MAX_ADDRS)) {
+ printf("-1\n");
+ break;
+ }
+
+ af = su.sa.sa_family;
+ if (af2sock(af, s, s6) == -1) {
+ warnc(EPROTONOSUPPORT, "getsourcefilter");
+ break;
+ }
+ nsrc = nreqsrc;
+ if (getsourcefilter(af2sock(af, s, s6), ifindex, &su.sa,
+ su.sa.sa_len, &fmode, &nsrc, &sources[0].ss) != 0) {
+ warn("getsourcefilter");
+ printf("-1\n");
+ break;
+ }
+ printf("%s\n", (fmode == MCAST_INCLUDE) ? "include" :
+ "exclude");
+ printf("%d\n", nsrc);
+
+ nsrc = MIN(nreqsrc, nsrc);
+ fprintf(stderr, "hexdump of sources:\n");
+ uint8_t *bp = (uint8_t *)&sources[0];
+ for (i = 0; i < (nsrc * sizeof(sources[0])); i++) {
+ fprintf(stderr, "%02x", bp[i]);
+ }
+ fprintf(stderr, "\nend hexdump\n");
+
+ qsort(sources, nsrc, af2socklen(af), su_cmp);
+ for (i = 0; i < nsrc; i++) {
+ sockunion_t *psu = (sockunion_t *)&sources[i];
+ addrbuf[0] = '\0';
+ error = getnameinfo(&psu->sa, psu->sa.sa_len,
+ addrbuf, sizeof(addrbuf), NULL, 0,
+ NI_NUMERICHOST);
+ if (error)
+ warnx("getnameinfo: %s", gai_strerror(error));
+ else
+ printf("%s\n", addrbuf);
+ }
+ printf("ok\n");
+ } break;
+
+ /* link-layer stuff follows. */
+
+ case 'a':
+ case 'd': {
+ struct sockaddr_dl *dlp;
+ struct ether_addr *ep;
+
+ memset(&ifr, 0, sizeof(struct ifreq));
+ dlp = (struct sockaddr_dl *)&ifr.ifr_addr;
+ dlp->sdl_len = sizeof(struct sockaddr_dl);
+ dlp->sdl_family = AF_LINK;
+ dlp->sdl_index = 0;
+ dlp->sdl_nlen = 0;
+ dlp->sdl_alen = ETHER_ADDR_LEN;
+ dlp->sdl_slen = 0;
+ if (sscanf(line, "%s %s", str1, str2) != 2) {
+ warnc(EINVAL, "sscanf");
+ break;
+ }
+ ep = ether_aton(str2);
+ if (ep == NULL) {
+ warnc(EINVAL, "ether_aton");
+ break;
+ }
+ strlcpy(ifr.ifr_name, str1, IF_NAMESIZE);
+ memcpy(LLADDR(dlp), ep, ETHER_ADDR_LEN);
+ if (ioctl(s, (*cmd == 'a') ? SIOCADDMULTI : SIOCDELMULTI,
+ &ifr) == -1) {
+ warn("ioctl SIOCADDMULTI/SIOCDELMULTI");
+ printf("-1\n");
+ } else
+ printf("ok\n");
+ break;
+ }
+
+ case 'm':
+ fprintf(stderr,
+ "warning: IFF_ALLMULTI cannot be set from userland "
+ "in FreeBSD; command ignored.\n");
+ printf("-1\n");
+ break;
+
+ case 'p':
+ if (sscanf(line, "%s %u", ifr.ifr_name, &f) != 2) {
+ printf("-1\n");
+ break;
+ }
+ if (ioctl(s, SIOCGIFFLAGS, &ifr) == -1) {
+ warn("ioctl SIOCGIFFLAGS");
+ break;
+ }
+ flags = (ifr.ifr_flags & 0xffff) | (ifr.ifr_flagshigh << 16);
+ if (f == 0) {
+ flags &= ~IFF_PPROMISC;
+ } else {
+ flags |= IFF_PPROMISC;
+ }
+ ifr.ifr_flags = flags & 0xffff;
+ ifr.ifr_flagshigh = flags >> 16;
+ if (ioctl(s, SIOCSIFFLAGS, &ifr) == -1)
+ warn("ioctl SIOCGIFFLAGS");
+ else
+ printf( "changed to 0x%08x\n", flags );
+ break;
+
+ case '\n':
+ break;
+ default:
+ printf("invalid command\n");
+ break;
+ }
+}
+
+static void
+usage(void)
+{
+
+ printf("j mcast-addr ifname [src-addr] - join IP multicast group\n");
+ printf("l mcast-addr ifname [src-addr] - leave IP multicast group\n");
+ printf(
+"i mcast-addr ifname n - set n include mode src filter\n");
+ printf(
+"e mcast-addr ifname n - set n exclude mode src filter\n");
+ printf("t mcast-addr ifname src-addr - allow traffic from src\n");
+ printf("b mcast-addr ifname src-addr - block traffic from src\n");
+ printf("g mcast-addr ifname n - get and show n src filters\n");
+ printf("a ifname mac-addr - add link multicast filter\n");
+ printf("d ifname mac-addr - delete link multicast filter\n");
+ printf("m ifname 1/0 - set/clear ether allmulti flag\n");
+ printf("p ifname 1/0 - set/clear ether promisc flag\n");
+ printf("f filename - read command(s) from file\n");
+ printf("s seconds - sleep for some time\n");
+ printf("q - quit\n");
+}
+
diff --git a/usr.sbin/nandsim/Makefile b/usr.sbin/nandsim/Makefile
new file mode 100644
index 0000000..9269ab5
--- /dev/null
+++ b/usr.sbin/nandsim/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= nandsim
+SRCS= nandsim.c nandsim_rcfile.c nandsim_cfgparse.c
+BINDIR= /usr/sbin
+MAN= nandsim.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/nandsim/Makefile.depend b/usr.sbin/nandsim/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/nandsim/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/nandsim/nandsim.8 b/usr.sbin/nandsim/nandsim.8
new file mode 100644
index 0000000..0951cc7
--- /dev/null
+++ b/usr.sbin/nandsim/nandsim.8
@@ -0,0 +1,229 @@
+.\" 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 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 10, 2010
+.Dt NANDSIM 8
+.Os
+.Sh NAME
+.Nm nandsim
+.Nd NAND simulator control program
+.Sh SYNOPSIS
+.Nm
+.Ic status
+.Aq ctrl_no | Fl -all | Fl a
+.Op Fl v
+.Nm
+.Ic conf
+.Aq filename
+.Nm
+.Ic start
+.Aq ctrl_no
+.Nm
+.Ic mod
+.Aq ctrl_no:cs_no | Fl l Aq loglevel
+.Op Fl p Aq prog_time
+.Op Fl e Aq erase_time
+.Op Fl r Aq read_time
+.Op Fl E Aq error_ratio
+.Op Fl h
+.Nm
+.Ic stop
+.Aq ctrl_no
+.Nm
+.Ic error
+.Aq ctrl_no:cs_no
+.Aq page_num
+.Aq column
+.Aq length
+.Aq pattern
+.Nm
+.Ic bb
+.Aq ctrl_no:cs_no
+.Op blk_num,blk_num2,...
+.Op Fl U
+.Op Fl L
+.Nm
+.Ic freeze
+.Op ctrl_no
+.Nm
+.Ic log
+.Aq ctrl_no | Fl -all | Fl a
+.Nm
+.Ic stats
+.Aq ctrl_no:cs_no
+.Aq page_num
+.Nm
+.Ic dump
+.Aq ctrl_no:cs_no
+.Aq filename
+.Nm
+.Ic restore
+.Aq ctrl_no:chip_no
+.Aq filename
+.Nm
+.Ic destroy
+.Aq ctrl_no[:cs_no] | Fl -all | Fl a
+.Nm
+.Ic help
+.Op Fl v
+.Sh COMMAND DESCRIPTION
+Controllers and chips are arranged into a simple hierarchy.
+There can be up to 4 controllers configured, each with 4 chip select (CS) lines.
+A given chip is connected to one of the chip selects.
+.Pp
+Controllers are specified as
+.Aq ctrl_no ;
+chip selects are specified as
+.Aq cs_no .
+.Bl -tag -width periphlist
+.It Ic status
+Gets controller(s) status. If
+.Fl a
+or
+.Fl -all
+flag is specified - command will print status of every controller
+currently available.
+Optional flag
+.Fl v
+causes printing complete information about the controller, and all
+chips attached to it.
+.It Ic conf
+Reads simulator configuration from a specified file (this includes
+the simulation "layout" i.e. controllers-chips assignments).
+Configuration changes for an already started simulation require a
+full stop-start cycle in order to take effect i.e.:
+.Bl -column
+.It nandsim stop ...
+.It nandsim destroy ...
+.Pp
+.It << edit config file >>
+.Pp
+.It nandsim conf ...
+.It nandsim start ...
+.El
+.It Ic mod
+Alters simulator parameters on-the-fly.
+If controller number and CS pair is not specified, the general
+simulator parameters (not specific to a controller or a chip) will be modified.
+Changing chip's parameters requires specifying both controller number and CS
+to which the given chip is connected.
+Parameters which can be altered:
+.Pp
+General simulator related:
+.Bl -tag -width flag
+.It Fl l Aq log_level
+change logging level to
+.Aq log_level
+.El
+.Pp
+Chip related:
+.Bl -tag -width flag
+.It Fl p Aq prog_time
+change prog time for specified chip to
+.Aq prog_time
+.It Fl e Aq erase_time
+change erase time for specified chip to
+.Aq erase_time
+.It Fl r Aq read_time
+change read time for specified chip to
+.Aq read_time
+.It Fl E Aq error_ratio
+change error ratio for specified chip to
+.Aq error_ratio .
+Error ratio is a number of errors per million read/write bytes.
+.El
+.Pp
+Additionally, flag
+.Fl h
+will list parameters which can be altered.
+.El
+.Bl -tag -width periphlist
+.It Ic bb
+Marks/unmarks a specified block as bad.
+To mark/unmark the bad condition an a block, the following parameters
+have to be supplied: controller number, CS number, and at least one
+block number.
+It is possible to specify multiple blocks, by separating blocks numbers
+with a comma.
+The following options can be used for the 'bb' command:
+.Bl -tag -width flag
+.It Fl U
+unmark the bad previously marked block as bad.
+.It Fl L
+list all blocks marked as bad on a given chip.
+.El
+.It Ic log
+Prints activity log of the specified controller to stdout; if
+controller number is not specified, logs for all available
+controllers are printed.
+.It Ic stats
+Print statistics of the selected controller, chip and page.
+Statistics includes read count, write count, raw read count, raw
+write count, ECC stats (succeeded corrections, failed correction).
+.It Ic dump
+Dumps a snaphot of a single chip (including data and bad blocks
+information, wearout level) into the file.
+.It Ic restore
+Restores chip state from a dump-file snapshot (produced previously
+with the 'dump' command).
+.It Ic start
+Starts a controller i.e. the simulation.
+.It Ic stop
+Stops an already started controller; if the controller number is not
+supplied, attempts to stop all currently working controllers.
+.It Ic destroy
+Removes existing active chip/controller and its configuration from
+memory and releases the resources.
+Specifying flag
+.Fl a
+or
+.Fl -all
+causes removal of every chip and controller.
+Controller must be stopped in order to be destroyed.
+.It Ic error
+Directly overwrites a certain number of bytes in the specified page
+at a given offset with a supplied pattern (which mimics the
+corruption of flash contents).
+.It Ic help
+Prints synopsis,
+.Fl v
+gives more verbose output.
+.It Ic freeze
+Stops simulation of given controller (simulates power-loss).
+All commands issues to any chip on this controller are ignored.
+.El
+.Sh SEE ALSO
+.Xr nand 4 ,
+.Xr nandsim 4 ,
+.Xr nandsim.conf 5
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 10.0 .
+.Sh AUTHORS
+This utility was written by
+.An Lukasz Wojcik .
diff --git a/usr.sbin/nandsim/nandsim.c b/usr.sbin/nandsim/nandsim.c
new file mode 100644
index 0000000..bd3d080
--- /dev/null
+++ b/usr.sbin/nandsim/nandsim.c
@@ -0,0 +1,1397 @@
+/*-
+ * Copyright (C) 2009-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 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.
+ */
+
+/*
+ * Control application for the NAND simulator.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/errno.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <dev/nand/nandsim.h>
+#include <dev/nand/nand_dev.h>
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <sysexits.h>
+
+#include "nandsim_cfgparse.h"
+
+#define SIMDEVICE "/dev/nandsim.ioctl"
+
+#define error(fmt, args...) do { \
+ printf("ERROR: " fmt "\n", ##args); } while (0)
+
+#define warn(fmt, args...) do { \
+ printf("WARNING: " fmt "\n", ##args); } while (0)
+
+#define DEBUG
+#undef DEBUG
+
+#ifdef DEBUG
+#define debug(fmt, args...) do { \
+ printf("NANDSIM_CONF:" fmt "\n", ##args); } while (0)
+#else
+#define debug(fmt, args...) do {} while(0)
+#endif
+
+#define NANDSIM_RAM_LOG_SIZE 16384
+
+#define MSG_NOTRUNNING "Controller#%d is not running.Please start" \
+ " it first."
+#define MSG_RUNNING "Controller#%d is already running!"
+#define MSG_CTRLCHIPNEEDED "You have to specify ctrl_no:cs_no pair!"
+#define MSG_STATUSACQCTRLCHIP "Could not acquire status for ctrl#%d chip#%d"
+#define MSG_STATUSACQCTRL "Could not acquire status for ctrl#%d"
+#define MSG_NOCHIP "There is no such chip configured (chip#%d "\
+ "at ctrl#%d)!"
+
+#define MSG_NOCTRL "Controller#%d is not configured!"
+#define MSG_NOTCONFIGDCTRLCHIP "Chip connected to ctrl#%d at cs#%d " \
+ "is not configured."
+
+typedef int (commandfunc_t)(int , char **);
+
+static struct nandsim_command *getcommand(char *);
+static int parse_devstring(char *, int *, int *);
+static void printchip(struct sim_chip *, uint8_t);
+static void printctrl(struct sim_ctrl *);
+static int opendev(int *);
+static commandfunc_t cmdstatus;
+static commandfunc_t cmdconf;
+static commandfunc_t cmdstart;
+static commandfunc_t cmdstop;
+static commandfunc_t cmdmod;
+static commandfunc_t cmderror;
+static commandfunc_t cmdbb;
+static commandfunc_t cmdfreeze;
+static commandfunc_t cmdlog;
+static commandfunc_t cmdstats;
+static commandfunc_t cmddump;
+static commandfunc_t cmdrestore;
+static commandfunc_t cmddestroy;
+static commandfunc_t cmdhelp;
+static int checkusage(int, int, char **);
+static int is_chip_created(int, int, int *);
+static int is_ctrl_created(int, int *);
+static int is_ctrl_running(int, int *);
+static int assert_chip_connected(int , int);
+static int printstats(int, int, uint32_t, int);
+
+struct nandsim_command {
+ const char *cmd_name; /* Command name */
+ commandfunc_t *commandfunc; /* Ptr to command function */
+ uint8_t req_argc; /* Mandatory arguments count */
+ const char *usagestring; /* Usage string */
+};
+
+static struct nandsim_command commands[] = {
+ {"status", cmdstatus, 1,
+ "status <ctl_no|--all|-a> [-v]\n" },
+ {"conf", cmdconf, 1,
+ "conf <filename>\n" },
+ {"start", cmdstart, 1,
+ "start <ctrl_no>\n" },
+ {"mod", cmdmod, 2,
+ "mod [-l <loglevel>] | <ctl_no:cs_no> [-p <prog_time>]\n"
+ "\t[-e <erase_time>] [-r <read_time>]\n"
+ "\t[-E <error_ratio>] | [-h]\n" },
+ {"stop", cmdstop, 1,
+ "stop <ctrl_no>\n" },
+ {"error", cmderror, 5,
+ "error <ctrl_no:cs_no> <page_num> <column> <length> <pattern>\n" },
+ {"bb", cmdbb, 2,
+ "bb <ctl_no:cs_no> [blk_num1,blk_num2,..] [-U] [-L]\n" },
+ {"freeze", cmdfreeze, 1,
+ "freeze [ctrl_no]\n" },
+ {"log", cmdlog, 1,
+ "log <ctrl_no|--all|-a>\n" },
+ {"stats", cmdstats, 2,
+ "stats <ctrl_no:cs_no> <pagenumber>\n" },
+ {"dump", cmddump, 2,
+ "dump <ctrl_no:cs_no> <filename>\n" },
+ {"restore", cmdrestore, 2,
+ "restore <ctrl_no:chip_no> <filename>\n" },
+ {"destroy", cmddestroy, 1,
+ "destroy <ctrl_no[:cs_no]|--all|-a>\n" },
+ {"help", cmdhelp, 0,
+ "help [-v]" },
+ {NULL, NULL, 0, NULL},
+};
+
+
+/* Parse command name, and start appropriate function */
+static struct nandsim_command*
+getcommand(char *arg)
+{
+ struct nandsim_command *opts;
+
+ for (opts = commands; (opts != NULL) &&
+ (opts->cmd_name != NULL); opts++) {
+ if (strcmp(opts->cmd_name, arg) == 0)
+ return (opts);
+ }
+ return (NULL);
+}
+
+/*
+ * Parse given string in format <ctrl_no>:<cs_no>, if possible -- set
+ * ctrl and/or cs, and return 0 (success) or 1 (in case of error).
+ *
+ * ctrl == 0xff && chip == 0xff : '--all' flag specified
+ * ctrl != 0xff && chip != 0xff : both ctrl & chip were specified
+ * ctrl != 0xff && chip == 0xff : only ctrl was specified
+ */
+static int
+parse_devstring(char *str, int *ctrl, int *cs)
+{
+ char *tmpstr;
+ unsigned int num = 0;
+
+ /* Ignore white spaces at the beginning */
+ while (isspace(*str) && (*str != '\0'))
+ str++;
+
+ *ctrl = 0xff;
+ *cs = 0xff;
+ if (strcmp(str, "--all") == 0 ||
+ strcmp(str, "-a") == 0) {
+ /* If --all or -a is specified, ctl==chip==0xff */
+ debug("CTRL=%d CHIP=%d\n", *ctrl, *cs);
+ return (0);
+ }
+ /* Separate token and try to convert it to int */
+ tmpstr = (char *)strtok(str, ":");
+ if ((tmpstr != NULL) && (*tmpstr != '\0')) {
+ if (convert_arguint(tmpstr, &num) != 0)
+ return (1);
+
+ if (num > MAX_SIM_DEV - 1) {
+ error("Invalid ctrl_no supplied: %s. Valid ctrl_no "
+ "value must lie between 0 and 3!", tmpstr);
+ return (1);
+ }
+
+ *ctrl = num;
+ tmpstr = (char *)strtok(NULL, ":");
+
+ if ((tmpstr != NULL) && (*tmpstr != '\0')) {
+ if (convert_arguint(tmpstr, &num) != 0)
+ return (1);
+
+ /* Check if chip_no is valid */
+ if (num > MAX_CTRL_CS - 1) {
+ error("Invalid chip_no supplied: %s. Valid "
+ "chip_no value must lie between 0 and 3!",
+ tmpstr);
+ return (1);
+ }
+ *cs = num;
+ }
+ } else
+ /* Empty devstring supplied */
+ return (1);
+
+ debug("CTRL=%d CHIP=%d\n", *ctrl, *cs);
+ return (0);
+}
+
+static int
+opendev(int *fd)
+{
+
+ *fd = open(SIMDEVICE, O_RDWR);
+ if (*fd == -1) {
+ error("Could not open simulator device file (%s)!",
+ SIMDEVICE);
+ return (EX_OSFILE);
+ }
+ return (EX_OK);
+}
+
+static int
+opencdev(int *cdevd, int ctrl, int chip)
+{
+ char fname[255];
+
+ sprintf(fname, "/dev/nandsim%d.%d", ctrl, chip);
+ *cdevd = open(fname, O_RDWR);
+ if (*cdevd == -1)
+ return (EX_NOINPUT);
+
+ return (EX_OK);
+}
+
+/*
+ * Check if given arguments count match requirements. If no, or
+ * --help (-h) flag is specified -- return 1 (print usage)
+ */
+static int
+checkusage(int gargc, int argsreqd, char **gargv)
+{
+
+ if (gargc < argsreqd + 2 || (gargc >= (argsreqd + 2) &&
+ (strcmp(gargv[1], "--help") == 0 ||
+ strcmp(gargv[1], "-h") == 0)))
+ return (1);
+
+ return (0);
+}
+
+static int
+cmdstatus(int gargc, char **gargv)
+{
+ int chip = 0, ctl = 0, err = 0, fd, idx, idx2, start, stop;
+ uint8_t verbose = 0;
+ struct sim_ctrl ctrlconf;
+ struct sim_chip chipconf;
+
+ err = parse_devstring(gargv[2], &ctl, &chip);
+ if (err) {
+ return (EX_USAGE);
+ } else if (ctl == 0xff) {
+ /* Every controller */
+ start = 0;
+ stop = MAX_SIM_DEV-1;
+ } else {
+ /* Specified controller only */
+ start = ctl;
+ stop = ctl;
+ }
+
+ if (opendev(&fd) != EX_OK)
+ return (EX_OSFILE);
+
+ for (idx = 0; idx < gargc; idx ++)
+ if (strcmp(gargv[idx], "-v") == 0 ||
+ strcmp(gargv[idx], "--verbose") == 0)
+ verbose = 1;
+
+ for (idx = start; idx <= stop; idx++) {
+ ctrlconf.num = idx;
+ err = ioctl(fd, NANDSIM_STATUS_CTRL, &ctrlconf);
+ if (err) {
+ err = EX_SOFTWARE;
+ error(MSG_STATUSACQCTRL, idx);
+ continue;
+ }
+
+ printctrl(&ctrlconf);
+
+ for (idx2 = 0; idx2 < MAX_CTRL_CS; idx2++) {
+ chipconf.num = idx2;
+ chipconf.ctrl_num = idx;
+
+ err = ioctl(fd, NANDSIM_STATUS_CHIP, &chipconf);
+ if (err) {
+ err = EX_SOFTWARE;
+ error(MSG_STATUSACQCTRL, idx);
+ continue;
+ }
+
+ printchip(&chipconf, verbose);
+ }
+ }
+ close(fd);
+ return (err);
+}
+
+static int
+cmdconf(int gargc __unused, char **gargv)
+{
+ int err;
+
+ err = parse_config(gargv[2], SIMDEVICE);
+ if (err)
+ return (EX_DATAERR);
+
+ return (EX_OK);
+}
+
+static int
+cmdstart(int gargc __unused, char **gargv)
+{
+ int chip = 0, ctl = 0, err = 0, fd, running, state;
+
+ err = parse_devstring(gargv[2], &ctl, &chip);
+ if (err)
+ return (EX_USAGE);
+
+ err = is_ctrl_created(ctl, &state);
+ if (err) {
+ return (EX_SOFTWARE);
+ } else if (state == 0) {
+ error(MSG_NOCTRL, ctl);
+ return (EX_SOFTWARE);
+ }
+
+ err = is_ctrl_running(ctl, &running);
+ if (err)
+ return (EX_SOFTWARE);
+
+ if (running) {
+ warn(MSG_RUNNING, ctl);
+ } else {
+ if (opendev(&fd) != EX_OK)
+ return (EX_OSFILE);
+
+ err = ioctl(fd, NANDSIM_START_CTRL, &ctl);
+ close(fd);
+ if (err) {
+ error("Cannot start controller#%d", ctl);
+ err = EX_SOFTWARE;
+ }
+ }
+ return (err);
+}
+
+static int
+cmdstop(int gargc __unused, char **gargv)
+{
+ int chip = 0, ctl = 0, err = 0, fd, running;
+
+ err = parse_devstring(gargv[2], &ctl, &chip);
+ if (err)
+ return (EX_USAGE);
+
+ err = is_ctrl_running(ctl, &running);
+ if (err)
+ return (EX_SOFTWARE);
+
+ if (!running) {
+ error(MSG_NOTRUNNING, ctl);
+ } else {
+ if (opendev(&fd) != EX_OK)
+ return (EX_OSFILE);
+
+ err = ioctl(fd, NANDSIM_STOP_CTRL, &ctl);
+ close(fd);
+ if (err) {
+ error("Cannot stop controller#%d", ctl);
+ err = EX_SOFTWARE;
+ }
+ }
+
+ return (err);
+}
+
+static int
+cmdmod(int gargc __unused, char **gargv)
+{
+ int chip, ctl, err = 0, fd = -1, i;
+ struct sim_mod mods;
+
+ if (gargc >= 4) {
+ if (strcmp(gargv[2], "--loglevel") == 0 || strcmp(gargv[2],
+ "-l") == 0) {
+ /* Set loglevel (ctrl:chip pair independent) */
+ mods.field = SIM_MOD_LOG_LEVEL;
+
+ if (convert_arguint(gargv[3], &mods.new_value) != 0)
+ return (EX_SOFTWARE);
+
+ if (opendev(&fd) != EX_OK)
+ return (EX_OSFILE);
+
+ err = ioctl(fd, NANDSIM_MODIFY, &mods);
+ if (err) {
+ error("simulator parameter %s could not be "
+ "modified !", gargv[3]);
+ close(fd);
+ return (EX_SOFTWARE);
+ }
+
+ debug("request : loglevel = %d\n", mods.new_value);
+ close(fd);
+ return (EX_OK);
+ }
+ }
+
+ err = parse_devstring(gargv[2], &ctl, &chip);
+ if (err)
+ return (EX_USAGE);
+
+ else if (chip == 0xff) {
+ error(MSG_CTRLCHIPNEEDED);
+ return (EX_USAGE);
+ }
+
+ if (!assert_chip_connected(ctl, chip))
+ return (EX_SOFTWARE);
+
+ if (opendev(&fd) != EX_OK)
+ return (EX_OSFILE);
+
+ /* Find out which flags were passed */
+ for (i = 3; i < gargc; i++) {
+
+ if (convert_arguint(gargv[i + 1], &mods.new_value) != 0)
+ continue;
+
+ if (strcmp(gargv[i], "--prog-time") == 0 ||
+ strcmp(gargv[i], "-p") == 0) {
+
+ mods.field = SIM_MOD_PROG_TIME;
+ debug("request : progtime = %d\n", mods.new_value);
+
+ } else if (strcmp(gargv[i], "--erase-time") == 0 ||
+ strcmp(gargv[i], "-e") == 0) {
+
+ mods.field = SIM_MOD_ERASE_TIME;
+ debug("request : eraseime = %d\n", mods.new_value);
+
+ } else if (strcmp(gargv[i], "--read-time") == 0 ||
+ strcmp(gargv[i], "-r") == 0) {
+
+ mods.field = SIM_MOD_READ_TIME;
+ debug("request : read_time = %d\n", mods.new_value);
+
+ } else if (strcmp(gargv[i], "--error-ratio") == 0 ||
+ strcmp(gargv[i], "-E") == 0) {
+
+ mods.field = SIM_MOD_ERROR_RATIO;
+ debug("request : error_ratio = %d\n", mods.new_value);
+
+ } else {
+ /* Flag not recognized, or nothing specified. */
+ error("Unrecognized flag:%s\n", gargv[i]);
+ if (fd >= 0)
+ close(fd);
+ return (EX_USAGE);
+ }
+
+ mods.chip_num = chip;
+ mods.ctrl_num = ctl;
+
+ /* Call appropriate ioctl */
+ err = ioctl(fd, NANDSIM_MODIFY, &mods);
+ if (err) {
+ error("simulator parameter %s could not be modified! ",
+ gargv[i]);
+ continue;
+ }
+ i++;
+ }
+ close(fd);
+ return (EX_OK);
+}
+
+static int
+cmderror(int gargc __unused, char **gargv)
+{
+ uint32_t page, column, len, pattern;
+ int chip = 0, ctl = 0, err = 0, fd;
+ struct sim_error sim_err;
+
+ err = parse_devstring(gargv[2], &ctl, &chip);
+ if (err)
+ return (EX_USAGE);
+
+ if (chip == 0xff) {
+ error(MSG_CTRLCHIPNEEDED);
+ return (EX_USAGE);
+ }
+
+ if (convert_arguint(gargv[3], &page) ||
+ convert_arguint(gargv[4], &column) ||
+ convert_arguint(gargv[5], &len) ||
+ convert_arguint(gargv[6], &pattern))
+ return (EX_SOFTWARE);
+
+ if (!assert_chip_connected(ctl, chip))
+ return (EX_SOFTWARE);
+
+ sim_err.page_num = page;
+ sim_err.column = column;
+ sim_err.len = len;
+ sim_err.pattern = pattern;
+ sim_err.ctrl_num = ctl;
+ sim_err.chip_num = chip;
+
+ if (opendev(&fd) != EX_OK)
+ return (EX_OSFILE);
+
+ err = ioctl(fd, NANDSIM_INJECT_ERROR, &sim_err);
+
+ close(fd);
+ if (err) {
+ error("Could not inject error !");
+ return (EX_SOFTWARE);
+ }
+ return (EX_OK);
+}
+
+static int
+cmdbb(int gargc, char **gargv)
+{
+ struct sim_block_state bs;
+ struct chip_param_io cparams;
+ uint32_t blkidx;
+ int c, cdevd, chip = 0, ctl = 0, err = 0, fd, idx;
+ uint8_t flagL = 0, flagU = 0;
+ int *badblocks = NULL;
+
+ /* Check for --list/-L or --unmark/-U flags */
+ for (idx = 3; idx < gargc; idx++) {
+ if (strcmp(gargv[idx], "--list") == 0 ||
+ strcmp(gargv[idx], "-L") == 0)
+ flagL = idx;
+ if (strcmp(gargv[idx], "--unmark") == 0 ||
+ strcmp(gargv[idx], "-U") == 0)
+ flagU = idx;
+ }
+
+ if (flagL == 2 || flagU == 2 || flagU == 3)
+ return (EX_USAGE);
+
+ err = parse_devstring(gargv[2], &ctl, &chip);
+ if (err) {
+ return (EX_USAGE);
+ }
+ if (chip == 0xff || ctl == 0xff) {
+ error(MSG_CTRLCHIPNEEDED);
+ return (EX_USAGE);
+ }
+
+ bs.ctrl_num = ctl;
+ bs.chip_num = chip;
+
+ if (!assert_chip_connected(ctl, chip))
+ return (EX_SOFTWARE);
+
+ if (opencdev(&cdevd, ctl, chip) != EX_OK)
+ return (EX_OSFILE);
+
+ err = ioctl(cdevd, NAND_IO_GET_CHIP_PARAM, &cparams);
+ if (err)
+ return (EX_SOFTWARE);
+
+ close(cdevd);
+
+ bs.ctrl_num = ctl;
+ bs.chip_num = chip;
+
+ if (opendev(&fd) != EX_OK)
+ return (EX_OSFILE);
+
+ if (flagL != 3) {
+ /*
+ * Flag -L was specified either after blocklist or was not
+ * specified at all.
+ */
+ c = parse_intarray(gargv[3], &badblocks);
+
+ for (idx = 0; idx < c; idx++) {
+ bs.block_num = badblocks[idx];
+ /* Do not change wearout */
+ bs.wearout = -1;
+ bs.state = (flagU == 0) ? NANDSIM_BAD_BLOCK :
+ NANDSIM_GOOD_BLOCK;
+
+ err = ioctl(fd, NANDSIM_SET_BLOCK_STATE, &bs);
+ if (err) {
+ error("Could not set bad block(%d) for "
+ "controller (%d)!",
+ badblocks[idx], ctl);
+ err = EX_SOFTWARE;
+ break;
+ }
+ }
+ }
+ if (flagL != 0) {
+ /* If flag -L was specified (anywhere) */
+ for (blkidx = 0; blkidx < cparams.blocks; blkidx++) {
+ bs.block_num = blkidx;
+ /* Do not change the wearout */
+ bs.wearout = -1;
+ err = ioctl(fd, NANDSIM_GET_BLOCK_STATE, &bs);
+ if (err) {
+ error("Could not acquire block state");
+ err = EX_SOFTWARE;
+ continue;
+ }
+ printf("Block#%d: wear count: %d %s\n", blkidx,
+ bs.wearout,
+ (bs.state == NANDSIM_BAD_BLOCK) ? "BAD":"GOOD");
+ }
+ }
+ close(fd);
+ return (err);
+}
+
+static int
+cmdfreeze(int gargc __unused, char **gargv)
+{
+ int chip = 0, ctl = 0, err = 0, fd, i, start = 0, state, stop = 0;
+ struct sim_ctrl_chip ctrlchip;
+
+ err = parse_devstring(gargv[2], &ctl, &chip);
+ if (err)
+ return (EX_USAGE);
+
+ if (ctl == 0xff) {
+ error("You have to specify at least controller number");
+ return (EX_USAGE);
+ }
+
+ if (ctl != 0xff && chip == 0xff) {
+ start = 0;
+ stop = MAX_CTRL_CS - 1;
+ } else {
+ start = chip;
+ stop = chip;
+ }
+
+ ctrlchip.ctrl_num = ctl;
+
+ err = is_ctrl_running(ctl, &state);
+ if (err)
+ return (EX_SOFTWARE);
+ if (state == 0) {
+ error(MSG_NOTRUNNING, ctl);
+ return (EX_SOFTWARE);
+ }
+
+ if (opendev(&fd) != EX_OK)
+ return (EX_OSFILE);
+
+ for (i = start; i <= stop; i++) {
+ err = is_chip_created(ctl, i, &state);
+ if (err)
+ return (EX_SOFTWARE);
+ else if (state == 0) {
+ continue;
+ }
+
+ ctrlchip.chip_num = i;
+ err = ioctl(fd, NANDSIM_FREEZE, &ctrlchip);
+ if (err) {
+ error("Could not freeze ctrl#%d chip#%d", ctl, i);
+ close(fd);
+ return (EX_SOFTWARE);
+ }
+ }
+ close(fd);
+ return (EX_OK);
+}
+
+static int
+cmdlog(int gargc __unused, char **gargv)
+{
+ struct sim_log log;
+ int chip = 0, ctl = 0, err = 0, fd, idx, start = 0, stop = 0;
+ char *logbuf;
+
+ err = parse_devstring(gargv[2], &ctl, &chip);
+ if (err)
+ return (EX_USAGE);
+
+ logbuf = (char *)malloc(sizeof(char) * NANDSIM_RAM_LOG_SIZE);
+ if (logbuf == NULL) {
+ error("Not enough memory to create log buffer");
+ return (EX_SOFTWARE);
+ }
+
+ memset(logbuf, 0, NANDSIM_RAM_LOG_SIZE);
+ log.log = logbuf;
+ log.len = NANDSIM_RAM_LOG_SIZE;
+
+ if (ctl == 0xff) {
+ start = 0;
+ stop = MAX_SIM_DEV-1;
+ } else {
+ start = ctl;
+ stop = ctl;
+ }
+
+ if (opendev(&fd) != EX_OK) {
+ free(logbuf);
+ return (EX_OSFILE);
+ }
+
+ /* Print logs for selected controller(s) */
+ for (idx = start; idx <= stop; idx++) {
+ log.ctrl_num = idx;
+
+ err = ioctl(fd, NANDSIM_PRINT_LOG, &log);
+ if (err) {
+ error("Could not get log for controller %d!", idx);
+ continue;
+ }
+
+ printf("Logs for controller#%d:\n%s\n", idx, logbuf);
+ }
+
+ free(logbuf);
+ close(fd);
+ return (EX_OK);
+}
+
+static int
+cmdstats(int gargc __unused, char **gargv)
+{
+ int cdevd, chip = 0, ctl = 0, err = 0;
+ uint32_t pageno = 0;
+
+ err = parse_devstring(gargv[2], &ctl, &chip);
+
+ if (err)
+ return (EX_USAGE);
+
+ if (chip == 0xff) {
+ error(MSG_CTRLCHIPNEEDED);
+ return (EX_USAGE);
+ }
+
+ if (convert_arguint(gargv[3], &pageno) != 0)
+ return (EX_USAGE);
+
+ if (!assert_chip_connected(ctl, chip))
+ return (EX_SOFTWARE);
+
+ if (opencdev(&cdevd, ctl, chip) != EX_OK)
+ return (EX_OSFILE);
+
+ err = printstats(ctl, chip, pageno, cdevd);
+ if (err) {
+ close(cdevd);
+ return (EX_SOFTWARE);
+ }
+ close(cdevd);
+ return (EX_OK);
+}
+
+static int
+cmddump(int gargc __unused, char **gargv)
+{
+ struct sim_dump dump;
+ struct sim_block_state bs;
+ struct chip_param_io cparams;
+ int chip = 0, ctl = 0, err = EX_OK, fd, dumpfd;
+ uint32_t blkidx, bwritten = 0, totalwritten = 0;
+ void *buf;
+
+ err = parse_devstring(gargv[2], &ctl, &chip);
+ if (err)
+ return (EX_USAGE);
+
+ if (chip == 0xff || ctl == 0xff) {
+ error(MSG_CTRLCHIPNEEDED);
+ return (EX_USAGE);
+ }
+
+ if (!assert_chip_connected(ctl, chip))
+ return (EX_SOFTWARE);
+
+ if (opencdev(&fd, ctl, chip) != EX_OK)
+ return (EX_OSFILE);
+
+ err = ioctl(fd, NAND_IO_GET_CHIP_PARAM, &cparams);
+ if (err) {
+ error("Cannot get parameters for chip %d:%d", ctl, chip);
+ close(fd);
+ return (EX_SOFTWARE);
+ }
+ close(fd);
+
+ dump.ctrl_num = ctl;
+ dump.chip_num = chip;
+
+ dump.len = cparams.pages_per_block * (cparams.page_size +
+ cparams.oob_size);
+
+ buf = malloc(dump.len);
+ if (buf == NULL) {
+ error("Could not allocate memory!");
+ return (EX_SOFTWARE);
+ }
+ dump.data = buf;
+
+ errno = 0;
+ dumpfd = open(gargv[3], O_WRONLY | O_CREAT, 0666);
+ if (dumpfd == -1) {
+ error("Cannot create dump file.");
+ free(buf);
+ return (EX_SOFTWARE);
+ }
+
+ if (opendev(&fd)) {
+ close(dumpfd);
+ free(buf);
+ return (EX_SOFTWARE);
+ }
+
+ bs.ctrl_num = ctl;
+ bs.chip_num = chip;
+
+ /* First uint32_t in file shall contain block count */
+ if (write(dumpfd, &cparams, sizeof(cparams)) < (int)sizeof(cparams)) {
+ error("Error writing to dumpfile!");
+ close(fd);
+ close(dumpfd);
+ free(buf);
+ return (EX_SOFTWARE);
+ }
+
+ /*
+ * First loop acquires blocks states and writes them to
+ * the dump file.
+ */
+ for (blkidx = 0; blkidx < cparams.blocks; blkidx++) {
+ bs.block_num = blkidx;
+ err = ioctl(fd, NANDSIM_GET_BLOCK_STATE, &bs);
+ if (err) {
+ error("Could not get bad block(%d) for "
+ "controller (%d)!", blkidx, ctl);
+ close(fd);
+ close(dumpfd);
+ free(buf);
+ return (EX_SOFTWARE);
+ }
+
+ bwritten = write(dumpfd, &bs, sizeof(bs));
+ if (bwritten != sizeof(bs)) {
+ error("Error writing to dumpfile");
+ close(fd);
+ close(dumpfd);
+ free(buf);
+ return (EX_SOFTWARE);
+ }
+ }
+
+ /* Second loop dumps the data */
+ for (blkidx = 0; blkidx < cparams.blocks; blkidx++) {
+ debug("Block#%d...", blkidx);
+ dump.block_num = blkidx;
+
+ err = ioctl(fd, NANDSIM_DUMP, &dump);
+ if (err) {
+ error("Could not dump ctrl#%d chip#%d "
+ "block#%d", ctl, chip, blkidx);
+ err = EX_SOFTWARE;
+ break;
+ }
+
+ bwritten = write(dumpfd, dump.data, dump.len);
+ if (bwritten != dump.len) {
+ error("Error writing to dumpfile");
+ err = EX_SOFTWARE;
+ break;
+ }
+ debug("OK!\n");
+ totalwritten += bwritten;
+ }
+ printf("%d out of %d B written.\n", totalwritten, dump.len * blkidx);
+
+ close(fd);
+ close(dumpfd);
+ free(buf);
+ return (err);
+}
+
+static int
+cmdrestore(int gargc __unused, char **gargv)
+{
+ struct sim_dump dump;
+ struct sim_block_state bs;
+ struct stat filestat;
+ int chip = 0, ctl = 0, err = 0, fd, dumpfd = -1;
+ uint32_t blkidx, blksz, fsize = 0, expfilesz;
+ void *buf;
+ struct chip_param_io cparams, dumpcparams;
+
+ err = parse_devstring(gargv[2], &ctl, &chip);
+ if (err)
+ return (EX_USAGE);
+ else if (ctl == 0xff) {
+ error(MSG_CTRLCHIPNEEDED);
+ return (EX_USAGE);
+ }
+
+ if (!assert_chip_connected(ctl, chip))
+ return (EX_SOFTWARE);
+
+ /* Get chip geometry */
+ if (opencdev(&fd, ctl, chip) != EX_OK)
+ return (EX_OSFILE);
+
+ err = ioctl(fd, NAND_IO_GET_CHIP_PARAM, &cparams);
+ if (err) {
+ error("Cannot get parameters for chip %d:%d", ctl, chip);
+ close(fd);
+ return (err);
+ }
+ close(fd);
+
+ /* Obtain dump file size */
+ errno = 0;
+ if (stat(gargv[3], &filestat) != 0) {
+ error("Could not acquire file size! : %s",
+ strerror(errno));
+ return (EX_IOERR);
+ }
+
+ fsize = filestat.st_size;
+ blksz = cparams.pages_per_block * (cparams.page_size +
+ cparams.oob_size);
+
+ /* Expected dump file size for chip */
+ expfilesz = cparams.blocks * (blksz + sizeof(bs)) + sizeof(cparams);
+
+ if (fsize != expfilesz) {
+ error("File size does not match chip geometry (file size: %d"
+ ", dump size: %d)", fsize, expfilesz);
+ return (EX_SOFTWARE);
+ }
+
+ dumpfd = open(gargv[3], O_RDONLY);
+ if (dumpfd == -1) {
+ error("Could not open dump file!");
+ return (EX_IOERR);
+ }
+
+ /* Read chip params saved in dumpfile */
+ read(dumpfd, &dumpcparams, sizeof(dumpcparams));
+
+ /* XXX */
+ if (bcmp(&dumpcparams, &cparams, sizeof(cparams)) != 0) {
+ error("Supplied dump is created for a chip with different "
+ "chip configuration!");
+ close(dumpfd);
+ return (EX_SOFTWARE);
+ }
+
+ if (opendev(&fd) != EX_OK) {
+ close(dumpfd);
+ return (EX_OSFILE);
+ }
+
+ buf = malloc(blksz);
+ if (buf == NULL) {
+ error("Could not allocate memory for block buffer");
+ close(dumpfd);
+ close(fd);
+ return (EX_SOFTWARE);
+ }
+
+ dump.ctrl_num = ctl;
+ dump.chip_num = chip;
+ dump.data = buf;
+ /* Restore block states and wearouts */
+ for (blkidx = 0; blkidx < cparams.blocks; blkidx++) {
+ dump.block_num = blkidx;
+ if (read(dumpfd, &bs, sizeof(bs)) != sizeof(bs)) {
+ error("Error reading dumpfile");
+ close(dumpfd);
+ close(fd);
+ free(buf);
+ return (EX_SOFTWARE);
+ }
+ bs.ctrl_num = ctl;
+ bs.chip_num = chip;
+ debug("BLKIDX=%d BLOCKS=%d CTRL=%d CHIP=%d STATE=%d\n"
+ "WEAROUT=%d BS.CTRL_NUM=%d BS.CHIP_NUM=%d\n",
+ blkidx, cparams.blocks, dump.ctrl_num, dump.chip_num,
+ bs.state, bs.wearout, bs.ctrl_num, bs.chip_num);
+
+ err = ioctl(fd, NANDSIM_SET_BLOCK_STATE, &bs);
+ if (err) {
+ error("Could not set bad block(%d) for "
+ "controller: %d, chip: %d!", blkidx, ctl, chip);
+ close(dumpfd);
+ close(fd);
+ free(buf);
+ return (EX_SOFTWARE);
+ }
+ }
+ /* Restore data */
+ for (blkidx = 0; blkidx < cparams.blocks; blkidx++) {
+ errno = 0;
+ dump.len = read(dumpfd, buf, blksz);
+ if (errno) {
+ error("Failed to read block#%d from dumpfile.", blkidx);
+ err = EX_SOFTWARE;
+ break;
+ }
+ dump.block_num = blkidx;
+ err = ioctl(fd, NANDSIM_RESTORE, &dump);
+ if (err) {
+ error("Could not restore block#%d of ctrl#%d chip#%d"
+ ": %s", blkidx, ctl, chip, strerror(errno));
+ err = EX_SOFTWARE;
+ break;
+ }
+ }
+
+ free(buf);
+ close(dumpfd);
+ close(fd);
+ return (err);
+
+}
+
+static int
+cmddestroy(int gargc __unused, char **gargv)
+{
+ int chip = 0, ctl = 0, err = 0, fd, idx, idx2, state;
+ int chipstart, chipstop, ctrlstart, ctrlstop;
+ struct sim_chip_destroy chip_destroy;
+
+ err = parse_devstring(gargv[2], &ctl, &chip);
+
+ if (err)
+ return (EX_USAGE);
+
+ if (ctl == 0xff) {
+ /* Every chip at every controller */
+ ctrlstart = chipstart = 0;
+ ctrlstop = MAX_SIM_DEV - 1;
+ chipstop = MAX_CTRL_CS - 1;
+ } else {
+ ctrlstart = ctrlstop = ctl;
+ if (chip == 0xff) {
+ /* Every chip at selected controller */
+ chipstart = 0;
+ chipstop = MAX_CTRL_CS - 1;
+ } else
+ /* Selected chip at selected controller */
+ chipstart = chipstop = chip;
+ }
+ debug("CTRLSTART=%d CTRLSTOP=%d CHIPSTART=%d CHIPSTOP=%d\n",
+ ctrlstart, ctrlstop, chipstart, chipstop);
+ for (idx = ctrlstart; idx <= ctrlstop; idx++) {
+ err = is_ctrl_created(idx, &state);
+ if (err) {
+ error("Could not acquire ctrl#%d state. Cannot "
+ "destroy controller.", idx);
+ return (EX_SOFTWARE);
+ }
+ if (state == 0) {
+ continue;
+ }
+ err = is_ctrl_running(idx, &state);
+ if (err) {
+ error(MSG_STATUSACQCTRL, idx);
+ return (EX_SOFTWARE);
+ }
+ if (state != 0) {
+ error(MSG_RUNNING, ctl);
+ return (EX_SOFTWARE);
+ }
+ if (opendev(&fd) != EX_OK)
+ return (EX_OSFILE);
+
+ for (idx2 = chipstart; idx2 <= chipstop; idx2++) {
+ err = is_chip_created(idx, idx2, &state);
+ if (err) {
+ error(MSG_STATUSACQCTRLCHIP, idx2, idx);
+ continue;
+ }
+ if (state == 0)
+ /* There is no such chip running */
+ continue;
+ chip_destroy.ctrl_num = idx;
+ chip_destroy.chip_num = idx2;
+ ioctl(fd, NANDSIM_DESTROY_CHIP,
+ &chip_destroy);
+ }
+ /* If chip isn't explicitly specified -- destroy ctrl */
+ if (chip == 0xff) {
+ err = ioctl(fd, NANDSIM_DESTROY_CTRL, &idx);
+ if (err) {
+ error("Could not destroy ctrl#%d", idx);
+ continue;
+ }
+ }
+ close(fd);
+ }
+ return (err);
+}
+
+int
+main(int argc, char **argv)
+{
+ struct nandsim_command *cmdopts;
+ int retcode = 0;
+
+ if (argc < 2) {
+ cmdhelp(argc, argv);
+ retcode = EX_USAGE;
+ } else {
+ cmdopts = getcommand(argv[1]);
+ if (cmdopts != NULL && cmdopts->commandfunc != NULL) {
+ if (checkusage(argc, cmdopts->req_argc, argv) == 1) {
+ /* Print command specific usage */
+ printf("nandsim %s", cmdopts->usagestring);
+ return (EX_USAGE);
+ }
+ retcode = cmdopts->commandfunc(argc, argv);
+
+ if (retcode == EX_USAGE) {
+ /* Print command-specific usage */
+ printf("nandsim %s", cmdopts->usagestring);
+ } else if (retcode == EX_OSFILE) {
+ error("Could not open device file");
+ }
+
+ } else {
+ error("Unknown command!");
+ retcode = EX_USAGE;
+ }
+ }
+ return (retcode);
+}
+
+static int
+cmdhelp(int gargc __unused, char **gargv __unused)
+{
+ struct nandsim_command *opts;
+
+ printf("usage: nandsim <command> [command params] [params]\n\n");
+
+ for (opts = commands; (opts != NULL) &&
+ (opts->cmd_name != NULL); opts++)
+ printf("nandsim %s", opts->usagestring);
+
+ printf("\n");
+ return (EX_OK);
+}
+
+static void
+printchip(struct sim_chip *chip, uint8_t verbose)
+{
+
+ if (chip->created == 0)
+ return;
+ if (verbose > 0) {
+ printf("\n[Chip info]\n");
+ printf("num= %d\nctrl_num=%d\ndevice_id=%02x"
+ "\tmanufacturer_id=%02x\ndevice_model=%s\nmanufacturer="
+ "%s\ncol_addr_cycles=%d\nrow_addr_cycles=%d"
+ "\npage_size=%d\noob_size=%d\npages_per_block=%d\n"
+ "blocks_per_lun=%d\nluns=%d\n\nprog_time=%d\n"
+ "erase_time=%d\nread_time=%d\n"
+ "error_ratio=%d\nwear_level=%d\nwrite_protect=%c\n"
+ "chip_width=%db\n", chip->num, chip->ctrl_num,
+ chip->device_id, chip->manufact_id,chip->device_model,
+ chip->manufacturer, chip->col_addr_cycles,
+ chip->row_addr_cycles, chip->page_size,
+ chip->oob_size, chip->pgs_per_blk, chip->blks_per_lun,
+ chip->luns,chip->prog_time, chip->erase_time,
+ chip->read_time, chip->error_ratio, chip->wear_level,
+ (chip->is_wp == 0) ? 'N':'Y', chip->width);
+ } else {
+ printf("[Chip info]\n");
+ printf("\tnum=%d\n\tdevice_model=%s\n\tmanufacturer=%s\n"
+ "\tpage_size=%d\n\twrite_protect=%s\n",
+ chip->num, chip->device_model, chip->manufacturer,
+ chip->page_size, (chip->is_wp == 0) ? "NO":"YES");
+ }
+}
+
+static void
+printctrl(struct sim_ctrl *ctrl)
+{
+ int i;
+
+ if (ctrl->created == 0) {
+ printf(MSG_NOCTRL "\n", ctrl->num);
+ return;
+ }
+ printf("\n[Controller info]\n");
+ printf("\trunning: %s\n", ctrl->running ? "yes" : "no");
+ printf("\tnum cs: %d\n", ctrl->num_cs);
+ printf("\tecc: %d\n", ctrl->ecc);
+ printf("\tlog_filename: %s\n", ctrl->filename);
+ printf("\tecc_layout:");
+ for (i = 0; i < MAX_ECC_BYTES; i++) {
+ if (ctrl->ecc_layout[i] == 0xffff)
+ break;
+ else
+ printf("%c%d", i%16 ? ' ' : '\n',
+ ctrl->ecc_layout[i]);
+ }
+ printf("\n");
+}
+
+static int
+is_ctrl_running(int ctrl_no, int *running)
+{
+ struct sim_ctrl ctrl;
+ int err, fd;
+
+ ctrl.num = ctrl_no;
+ if (opendev(&fd) != EX_OK)
+ return (EX_OSFILE);
+
+ err = ioctl(fd, NANDSIM_STATUS_CTRL, &ctrl);
+ if (err) {
+ error(MSG_STATUSACQCTRL, ctrl_no);
+ close(fd);
+ return (err);
+ }
+ *running = ctrl.running;
+ close(fd);
+ return (0);
+}
+
+static int
+is_ctrl_created(int ctrl_no, int *created)
+{
+ struct sim_ctrl ctrl;
+ int err, fd;
+
+ ctrl.num = ctrl_no;
+
+ if (opendev(&fd) != EX_OK)
+ return (EX_OSFILE);
+
+ err = ioctl(fd, NANDSIM_STATUS_CTRL, &ctrl);
+ if (err) {
+ error("Could not acquire conf for ctrl#%d", ctrl_no);
+ close(fd);
+ return (err);
+ }
+ *created = ctrl.created;
+ close(fd);
+ return (0);
+}
+
+static int
+is_chip_created(int ctrl_no, int chip_no, int *created)
+{
+ struct sim_chip chip;
+ int err, fd;
+
+ chip.ctrl_num = ctrl_no;
+ chip.num = chip_no;
+
+ if (opendev(&fd) != EX_OK)
+ return (EX_OSFILE);
+
+ err = ioctl(fd, NANDSIM_STATUS_CHIP, &chip);
+ if (err) {
+ error("Could not acquire conf for chip#%d", chip_no);
+ close(fd);
+ return (err);
+ }
+ *created = chip.created;
+ close(fd);
+ return (0);
+}
+
+static int
+assert_chip_connected(int ctrl_no, int chip_no)
+{
+ int created, running;
+
+ if (is_ctrl_created(ctrl_no, &created))
+ return (0);
+
+ if (!created) {
+ error(MSG_NOCTRL, ctrl_no);
+ return (0);
+ }
+
+ if (is_chip_created(ctrl_no, chip_no, &created))
+ return (0);
+
+ if (!created) {
+ error(MSG_NOTCONFIGDCTRLCHIP, ctrl_no, chip_no);
+ return (0);
+ }
+
+ if (is_ctrl_running(ctrl_no, &running))
+ return (0);
+
+ if (!running) {
+ error(MSG_NOTRUNNING, ctrl_no);
+ return (0);
+ }
+
+ return (1);
+}
+
+static int
+printstats(int ctrlno, int chipno, uint32_t pageno, int cdevd)
+{
+ struct page_stat_io pstats;
+ struct block_stat_io bstats;
+ struct chip_param_io cparams;
+ uint32_t blkidx;
+ int err;
+
+ /* Gather information about chip */
+ err = ioctl(cdevd, NAND_IO_GET_CHIP_PARAM, &cparams);
+
+ if (err) {
+ error("Could not acquire chip info for chip attached to cs#"
+ "%d, ctrl#%d", chipno, ctrlno);
+ return (EX_SOFTWARE);
+ }
+
+ blkidx = (pageno / cparams.pages_per_block);
+ bstats.block_num = blkidx;
+
+ err = ioctl(cdevd, NAND_IO_BLOCK_STAT, &bstats);
+ if (err) {
+ error("Could not acquire block#%d statistics!", blkidx);
+ return (ENXIO);
+ }
+
+ printf("Block #%d erased: %d\n", blkidx, bstats.block_erased);
+ pstats.page_num = pageno;
+
+ err = ioctl(cdevd, NAND_IO_PAGE_STAT, &pstats);
+ if (err) {
+ error("Could not acquire page statistics!");
+ return (ENXIO);
+ }
+
+ debug("BLOCKIDX = %d PAGENO (REL. TO BLK) = %d\n", blkidx,
+ pstats.page_num);
+
+ printf("Page#%d : reads:%d writes:%d \n\traw reads:%d raw writes:%d "
+ "\n\tecc_succeeded:%d ecc_corrected:%d ecc_failed:%d\n",
+ pstats.page_num, pstats.page_read, pstats.page_written,
+ pstats.page_raw_read, pstats.page_raw_written,
+ pstats.ecc_succeded, pstats.ecc_corrected, pstats.ecc_failed);
+ return (0);
+}
diff --git a/usr.sbin/nandsim/nandsim_cfgparse.c b/usr.sbin/nandsim/nandsim_cfgparse.c
new file mode 100644
index 0000000..a9b5eb1
--- /dev/null
+++ b/usr.sbin/nandsim/nandsim_cfgparse.c
@@ -0,0 +1,959 @@
+/*-
+ * Copyright (C) 2009-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 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/errno.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+
+#include <dev/nand/nandsim.h>
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "nandsim_cfgparse.h"
+
+#define warn(fmt, args...) do { \
+ printf("WARNING: " fmt "\n", ##args); } while (0)
+
+#define error(fmt, args...) do { \
+ printf("ERROR: " fmt "\n", ##args); } while (0)
+
+#define MSG_MANDATORYKEYMISSING "mandatory key \"%s\" value belonging to " \
+ "section \"%s\" is missing!\n"
+
+#define DEBUG
+#undef DEBUG
+
+#ifdef DEBUG
+#define debug(fmt, args...) do { \
+ printf("NANDSIM_CONF:" fmt "\n", ##args); } while (0)
+#else
+#define debug(fmt, args...) do {} while(0)
+#endif
+
+#define STRBUFSIZ 2000
+
+/* Macros extracts type and type size */
+#define TYPE(x) ((x) & 0xf8)
+#define SIZE(x) (((x) & 0x07))
+
+/* Erase/Prog/Read time max and min values */
+#define DELAYTIME_MIN 10000
+#define DELAYTIME_MAX 10000000
+
+/* Structure holding configuration for controller. */
+static struct sim_ctrl ctrl_conf;
+/* Structure holding configuration for chip. */
+static struct sim_chip chip_conf;
+
+static struct nandsim_key nandsim_ctrl_keys[] = {
+ {"num_cs", 1, VALUE_UINT | SIZE_8, (void *)&ctrl_conf.num_cs, 0},
+ {"ctrl_num", 1, VALUE_UINT | SIZE_8, (void *)&ctrl_conf.num, 0},
+
+ {"ecc_layout", 1, VALUE_UINTARRAY | SIZE_16,
+ (void *)&ctrl_conf.ecc_layout, MAX_ECC_BYTES},
+
+ {"filename", 0, VALUE_STRING,
+ (void *)&ctrl_conf.filename, FILENAME_SIZE},
+
+ {"ecc", 0, VALUE_BOOL, (void *)&ctrl_conf.ecc, 0},
+ {NULL, 0, 0, NULL, 0},
+};
+
+static struct nandsim_key nandsim_chip_keys[] = {
+ {"chip_cs", 1, VALUE_UINT | SIZE_8, (void *)&chip_conf.num, 0},
+ {"chip_ctrl", 1, VALUE_UINT | SIZE_8, (void *)&chip_conf.ctrl_num,
+ 0},
+ {"device_id", 1, VALUE_UINT | SIZE_8, (void *)&chip_conf.device_id,
+ 0},
+ {"manufacturer_id", 1, VALUE_UINT | SIZE_8,
+ (void *)&chip_conf.manufact_id, 0},
+ {"model", 0, VALUE_STRING, (void *)&chip_conf.device_model,
+ DEV_MODEL_STR_SIZE},
+ {"manufacturer", 0, VALUE_STRING, (void *)&chip_conf.manufacturer,
+ MAN_STR_SIZE},
+ {"page_size", 1, VALUE_UINT | SIZE_32, (void *)&chip_conf.page_size,
+ 0},
+ {"oob_size", 1, VALUE_UINT | SIZE_32, (void *)&chip_conf.oob_size,
+ 0},
+ {"pages_per_block", 1, VALUE_UINT | SIZE_32,
+ (void *)&chip_conf.pgs_per_blk, 0},
+ {"blocks_per_lun", 1, VALUE_UINT | SIZE_32,
+ (void *)&chip_conf.blks_per_lun, 0},
+ {"luns", 1, VALUE_UINT | SIZE_32, (void *)&chip_conf.luns, 0},
+ {"column_addr_cycle", 1,VALUE_UINT | SIZE_8,
+ (void *)&chip_conf.col_addr_cycles, 0},
+ {"row_addr_cycle", 1, VALUE_UINT | SIZE_8,
+ (void *)&chip_conf.row_addr_cycles, 0},
+ {"program_time", 0, VALUE_UINT | SIZE_32,
+ (void *)&chip_conf.prog_time, 0},
+ {"erase_time", 0, VALUE_UINT | SIZE_32,
+ (void *)&chip_conf.erase_time, 0},
+ {"read_time", 0, VALUE_UINT | SIZE_32,
+ (void *)&chip_conf.read_time, 0},
+ {"width", 1, VALUE_UINT | SIZE_8, (void *)&chip_conf.width, 0},
+ {"wear_out", 1, VALUE_UINT | SIZE_32, (void *)&chip_conf.wear_level,
+ 0},
+ {"bad_block_map", 0, VALUE_UINTARRAY | SIZE_32,
+ (void *)&chip_conf.bad_block_map, MAX_BAD_BLOCKS},
+ {NULL, 0, 0, NULL, 0},
+};
+
+static struct nandsim_section sections[] = {
+ {"ctrl", (struct nandsim_key *)&nandsim_ctrl_keys},
+ {"chip", (struct nandsim_key *)&nandsim_chip_keys},
+ {NULL, NULL},
+};
+
+static uint8_t logoutputtoint(char *, int *);
+static uint8_t validate_chips(struct sim_chip *, int, struct sim_ctrl *, int);
+static uint8_t validate_ctrls(struct sim_ctrl *, int);
+static int configure_sim(const char *, struct rcfile *);
+static int create_ctrls(struct rcfile *, struct sim_ctrl **, int *);
+static int create_chips(struct rcfile *, struct sim_chip **, int *);
+static void destroy_ctrls(struct sim_ctrl *);
+static void destroy_chips(struct sim_chip *);
+static int validate_section_config(struct rcfile *, const char *, int);
+
+int
+convert_argint(char *arg, int *value)
+{
+
+ if (arg == NULL || value == NULL)
+ return (EINVAL);
+
+ errno = 0;
+ *value = (int)strtol(arg, NULL, 0);
+ if (*value == 0 && errno != 0) {
+ error("Cannot convert to number argument \'%s\'", arg);
+ return (EINVAL);
+ }
+ return (0);
+}
+
+int
+convert_arguint(char *arg, unsigned int *value)
+{
+
+ if (arg == NULL || value == NULL)
+ return (EINVAL);
+
+ errno = 0;
+ *value = (unsigned int)strtol(arg, NULL, 0);
+ if (*value == 0 && errno != 0) {
+ error("Cannot convert to number argument \'%s\'", arg);
+ return (EINVAL);
+ }
+ return (0);
+}
+
+/* Parse given ',' separated list of bytes into buffer. */
+int
+parse_intarray(char *array, int **buffer)
+{
+ char *tmp, *tmpstr, *origstr;
+ unsigned int currbufp = 0, i;
+ unsigned int count = 0, from = 0, to = 0;
+
+ /* Remove square braces */
+ if (array[0] == '[')
+ array ++;
+ if (array[strlen(array)-1] == ']')
+ array[strlen(array)-1] = ',';
+
+ from = strlen(array);
+ origstr = (char *)malloc(sizeof(char) * from);
+ strcpy(origstr, array);
+
+ tmpstr = (char *)strtok(array, ",");
+ /* First loop checks for how big int array we need to allocate */
+ while (tmpstr != NULL) {
+ errno = 0;
+ if ((tmp = strchr(tmpstr, '-')) != NULL) {
+ *tmp = ' ';
+ if (convert_arguint(tmpstr, &from) ||
+ convert_arguint(tmp, &to)) {
+ free(origstr);
+ return (EINVAL);
+ }
+
+ count += to - from + 1;
+ } else {
+ if (convert_arguint(tmpstr, &from)) {
+ free(origstr);
+ return (EINVAL);
+ }
+ count++;
+ }
+ tmpstr = (char *)strtok(NULL, ",");
+ }
+
+ if (count == 0)
+ goto out;
+
+ /* Allocate buffer of ints */
+ tmpstr = (char *)strtok(origstr, ",");
+ *buffer = malloc(count * sizeof(int));
+
+ /* Second loop is just inserting converted values into int array */
+ while (tmpstr != NULL) {
+ errno = 0;
+ if ((tmp = strchr(tmpstr, '-')) != NULL) {
+ *tmp = ' ';
+ from = strtol(tmpstr, NULL, 0);
+ to = strtol(tmp, NULL, 0);
+ tmpstr = strtok(NULL, ",");
+ for (i = from; i <= to; i ++)
+ (*buffer)[currbufp++] = i;
+ continue;
+ }
+ errno = 0;
+ from = (int)strtol(tmpstr, NULL, 0);
+ (*buffer)[currbufp++] = from;
+ tmpstr = (char *)strtok(NULL, ",");
+ }
+out:
+ free(origstr);
+ return (count);
+}
+
+/* Convert logoutput strings literals into appropriate ints. */
+static uint8_t
+logoutputtoint(char *logoutput, int *output)
+{
+ int out;
+
+ if (strcmp(logoutput, "file") == 0)
+ out = NANDSIM_OUTPUT_FILE;
+
+ else if (strcmp(logoutput, "console") == 0)
+ out = NANDSIM_OUTPUT_CONSOLE;
+
+ else if (strcmp(logoutput, "ram") == 0)
+ out = NANDSIM_OUTPUT_RAM;
+
+ else if (strcmp(logoutput, "none") == 0)
+ out = NANDSIM_OUTPUT_NONE;
+ else
+ out = -1;
+
+ *output = out;
+
+ if (out == -1)
+ return (EINVAL);
+ else
+ return (0);
+}
+
+static int
+configure_sim(const char *devfname, struct rcfile *f)
+{
+ struct sim_param sim_conf;
+ char buf[255];
+ int err, tmpv, fd;
+
+ err = rc_getint(f, "sim", 0, "log_level", &tmpv);
+
+ if (tmpv < 0 || tmpv > 255 || err) {
+ error("Bad log level specified (%d)\n", tmpv);
+ return (ENOTSUP);
+ } else
+ sim_conf.log_level = tmpv;
+
+ rc_getstring(f, "sim", 0, "log_output", 255, (char *)&buf);
+
+ tmpv = -1;
+ err = logoutputtoint((char *)&buf, &tmpv);
+ if (err) {
+ error("Log output specified in config file does not seem to "
+ "be valid (%s)!", (char *)&buf);
+ return (ENOTSUP);
+ }
+
+ sim_conf.log_output = tmpv;
+
+ fd = open(devfname, O_RDWR);
+ if (fd == -1) {
+ error("could not open simulator device file (%s)!",
+ devfname);
+ return (EX_OSFILE);
+ }
+
+ err = ioctl(fd, NANDSIM_SIM_PARAM, &sim_conf);
+ if (err) {
+ error("simulator parameters could not be modified: %s",
+ strerror(errno));
+ close(fd);
+ return (ENXIO);
+ }
+
+ close(fd);
+ return (EX_OK);
+}
+
+static int
+create_ctrls(struct rcfile *f, struct sim_ctrl **ctrls, int *cnt)
+{
+ int count, i;
+ struct sim_ctrl *ctrlsptr;
+
+ count = rc_getsectionscount(f, "ctrl");
+ if (count > MAX_SIM_DEV) {
+ error("Too many CTRL sections specified(%d)", count);
+ return (ENOTSUP);
+ } else if (count == 0) {
+ error("No ctrl sections specified");
+ return (ENOENT);
+ }
+
+ ctrlsptr = (struct sim_ctrl *)malloc(sizeof(struct sim_ctrl) * count);
+ if (ctrlsptr == NULL) {
+ error("Could not allocate memory for ctrl configuration");
+ return (ENOMEM);
+ }
+
+ for (i = 0; i < count; i++) {
+ bzero((void *)&ctrl_conf, sizeof(ctrl_conf));
+
+ /*
+ * ECC layout have to end up with 0xffff, so
+ * we're filling buffer with 0xff. If ecc_layout is
+ * defined in config file, values will be overridden.
+ */
+ memset((void *)&ctrl_conf.ecc_layout, 0xff,
+ sizeof(ctrl_conf.ecc_layout));
+
+ if (validate_section_config(f, "ctrl", i) != 0) {
+ free(ctrlsptr);
+ return (EINVAL);
+ }
+
+ if (parse_section(f, "ctrl", i) != 0) {
+ free(ctrlsptr);
+ return (EINVAL);
+ }
+
+ memcpy(&ctrlsptr[i], &ctrl_conf, sizeof(ctrl_conf));
+ /* Try to create ctrl with config parsed */
+ debug("NUM=%d\nNUM_CS=%d\nECC=%d\nFILENAME=%s\nECC_LAYOUT[0]"
+ "=%d\nECC_LAYOUT[1]=%d\n\n",
+ ctrlsptr[i].num, ctrlsptr[i].num_cs, ctrlsptr[i].ecc,
+ ctrlsptr[i].filename, ctrlsptr[i].ecc_layout[0],
+ ctrlsptr[i].ecc_layout[1]);
+ }
+ *cnt = count;
+ *ctrls = ctrlsptr;
+ return (0);
+}
+
+static void
+destroy_ctrls(struct sim_ctrl *ctrls)
+{
+
+ free(ctrls);
+}
+
+static int
+create_chips(struct rcfile *f, struct sim_chip **chips, int *cnt)
+{
+ struct sim_chip *chipsptr;
+ int count, i;
+
+ count = rc_getsectionscount(f, "chip");
+ if (count > (MAX_CTRL_CS * MAX_SIM_DEV)) {
+ error("Too many chip sections specified(%d)", count);
+ return (ENOTSUP);
+ } else if (count == 0) {
+ error("No chip sections specified");
+ return (ENOENT);
+ }
+
+ chipsptr = (struct sim_chip *)malloc(sizeof(struct sim_chip) * count);
+ if (chipsptr == NULL) {
+ error("Could not allocate memory for chip configuration");
+ return (ENOMEM);
+ }
+
+ for (i = 0; i < count; i++) {
+ bzero((void *)&chip_conf, sizeof(chip_conf));
+
+ /*
+ * Bad block map have to end up with 0xffff, so
+ * we're filling array with 0xff. If bad block map is
+ * defined in config file, values will be overridden.
+ */
+ memset((void *)&chip_conf.bad_block_map, 0xff,
+ sizeof(chip_conf.bad_block_map));
+
+ if (validate_section_config(f, "chip", i) != 0) {
+ free(chipsptr);
+ return (EINVAL);
+ }
+
+ if (parse_section(f, "chip", i) != 0) {
+ free(chipsptr);
+ return (EINVAL);
+ }
+
+ memcpy(&chipsptr[i], &chip_conf, sizeof(chip_conf));
+
+ /* Try to create chip with config parsed */
+ debug("CHIP:\nNUM=%d\nCTRL_NUM=%d\nDEVID=%d\nMANID=%d\n"
+ "PAGE_SZ=%d\nOOBSZ=%d\nREAD_T=%d\nDEVMODEL=%s\n"
+ "MAN=%s\nCOLADDRCYCLES=%d\nROWADDRCYCLES=%d\nCHWIDTH=%d\n"
+ "PGS/BLK=%d\nBLK/LUN=%d\nLUNS=%d\nERR_RATIO=%d\n"
+ "WEARLEVEL=%d\nISWP=%d\n\n\n\n",
+ chipsptr[i].num, chipsptr[i].ctrl_num,
+ chipsptr[i].device_id, chipsptr[i].manufact_id,
+ chipsptr[i].page_size, chipsptr[i].oob_size,
+ chipsptr[i].read_time, chipsptr[i].device_model,
+ chipsptr[i].manufacturer, chipsptr[i].col_addr_cycles,
+ chipsptr[i].row_addr_cycles, chipsptr[i].width,
+ chipsptr[i].pgs_per_blk, chipsptr[i].blks_per_lun,
+ chipsptr[i].luns, chipsptr[i].error_ratio,
+ chipsptr[i].wear_level, chipsptr[i].is_wp);
+ }
+ *cnt = count;
+ *chips = chipsptr;
+ return (0);
+}
+
+static void
+destroy_chips(struct sim_chip *chips)
+{
+
+ free(chips);
+}
+
+int
+parse_config(char *cfgfname, const char *devfname)
+{
+ int err = 0, fd;
+ unsigned int chipsectionscnt, ctrlsectionscnt, i;
+ struct rcfile *f;
+ struct sim_chip *chips;
+ struct sim_ctrl *ctrls;
+
+ err = rc_open(cfgfname, "r", &f);
+ if (err) {
+ error("could not open configuration file (%s)", cfgfname);
+ return (EX_NOINPUT);
+ }
+
+ /* First, try to configure simulator itself. */
+ if (configure_sim(devfname, f) != EX_OK) {
+ rc_close(f);
+ return (EINVAL);
+ }
+
+ debug("SIM CONFIGURED!\n");
+ /* Then create controllers' configs */
+ if (create_ctrls(f, &ctrls, &ctrlsectionscnt) != 0) {
+ rc_close(f);
+ return (ENXIO);
+ }
+ debug("CTRLS CONFIG READ!\n");
+
+ /* Then create chips' configs */
+ if (create_chips(f, &chips, &chipsectionscnt) != 0) {
+ destroy_ctrls(ctrls);
+ rc_close(f);
+ return (ENXIO);
+ }
+ debug("CHIPS CONFIG READ!\n");
+
+ if (validate_ctrls(ctrls, ctrlsectionscnt) != 0) {
+ destroy_ctrls(ctrls);
+ destroy_chips(chips);
+ rc_close(f);
+ return (EX_SOFTWARE);
+ }
+ if (validate_chips(chips, chipsectionscnt, ctrls,
+ ctrlsectionscnt) != 0) {
+ destroy_ctrls(ctrls);
+ destroy_chips(chips);
+ rc_close(f);
+ return (EX_SOFTWARE);
+ }
+
+ /* Open device */
+ fd = open(devfname, O_RDWR);
+ if (fd == -1) {
+ error("could not open simulator device file (%s)!",
+ devfname);
+ rc_close(f);
+ destroy_chips(chips);
+ destroy_ctrls(ctrls);
+ return (EX_OSFILE);
+ }
+
+ debug("SIM CONFIG STARTED!\n");
+
+ /* At this stage, both ctrls' and chips' configs should be valid */
+ for (i = 0; i < ctrlsectionscnt; i++) {
+ err = ioctl(fd, NANDSIM_CREATE_CTRL, &ctrls[i]);
+ if (err) {
+ if (err == EEXIST)
+ error("Controller#%d already created\n",
+ ctrls[i].num);
+ else if (err == EINVAL)
+ error("Incorrect controler number (%d)\n",
+ ctrls[i].num);
+ else
+ error("Could not created controller#%d\n",
+ ctrls[i].num);
+ /* Errors during controller creation stops parsing */
+ close(fd);
+ rc_close(f);
+ destroy_ctrls(ctrls);
+ destroy_chips(chips);
+ return (ENXIO);
+ }
+ debug("CTRL#%d CONFIG STARTED!\n", i);
+ }
+
+ for (i = 0; i < chipsectionscnt; i++) {
+ err = ioctl(fd, NANDSIM_CREATE_CHIP, &chips[i]);
+ if (err) {
+ if (err == EEXIST)
+ error("Chip#%d for controller#%d already "
+ "created\n", chips[i].num,
+ chips[i].ctrl_num);
+ else if (err == EINVAL)
+ error("Incorrect chip number (%d:%d)\n",
+ chips[i].num, chips[i].ctrl_num);
+ else
+ error("Could not create chip (%d:%d)\n",
+ chips[i].num, chips[i].ctrl_num);
+ error("Could not start chip#%d\n", i);
+ destroy_chips(chips);
+ destroy_ctrls(ctrls);
+ close(fd);
+ rc_close(f);
+ return (ENXIO);
+ }
+ }
+ debug("CHIPS CONFIG STARTED!\n");
+
+ close(fd);
+ rc_close(f);
+ destroy_chips(chips);
+ destroy_ctrls(ctrls);
+ return (0);
+}
+
+/*
+ * Function tries to get appropriate value for given key, convert it to
+ * array of ints (of given size), and perform all the necessary checks and
+ * conversions.
+ */
+static int
+get_argument_intarray(const char *sect_name, int sectno,
+ struct nandsim_key *key, struct rcfile *f)
+{
+ char strbuf[STRBUFSIZ];
+ int *intbuf;
+ int getres;
+ uint32_t cnt, i = 0;
+
+ getres = rc_getstring(f, sect_name, sectno, key->keyname, STRBUFSIZ,
+ (char *)&strbuf);
+
+ if (getres != 0) {
+ if (key->mandatory != 0) {
+ error(MSG_MANDATORYKEYMISSING, key->keyname,
+ sect_name);
+ return (EINVAL);
+ } else
+ /* Non-mandatory key, not present -- skip */
+ return (0);
+ }
+ cnt = parse_intarray((char *)&strbuf, &intbuf);
+ cnt = (cnt <= key->maxlength) ? cnt : key->maxlength;
+
+ for (i = 0; i < cnt; i++) {
+ if (SIZE(key->valuetype) == SIZE_8)
+ *((uint8_t *)(key->field) + i) =
+ (uint8_t)intbuf[i];
+ else if (SIZE(key->valuetype) == SIZE_16)
+ *((uint16_t *)(key->field) + i) =
+ (uint16_t)intbuf[i];
+ else
+ *((uint32_t *)(key->field) + i) =
+ (uint32_t)intbuf[i];
+ }
+ free(intbuf);
+ return (0);
+}
+
+/*
+ * Function tries to get appropriate value for given key, convert it to
+ * int of certain length.
+ */
+static int
+get_argument_int(const char *sect_name, int sectno, struct nandsim_key *key,
+ struct rcfile *f)
+{
+ int getres;
+ uint32_t val;
+
+ getres = rc_getint(f, sect_name, sectno, key->keyname, &val);
+ if (getres != 0) {
+
+ if (key->mandatory != 0) {
+ error(MSG_MANDATORYKEYMISSING, key->keyname,
+ sect_name);
+
+ return (EINVAL);
+ } else
+ /* Non-mandatory key, not present -- skip */
+ return (0);
+ }
+ if (SIZE(key->valuetype) == SIZE_8)
+ *(uint8_t *)(key->field) = (uint8_t)val;
+ else if (SIZE(key->valuetype) == SIZE_16)
+ *(uint16_t *)(key->field) = (uint16_t)val;
+ else
+ *(uint32_t *)(key->field) = (uint32_t)val;
+ return (0);
+}
+
+/* Function tries to get string value for given key */
+static int
+get_argument_string(const char *sect_name, int sectno,
+ struct nandsim_key *key, struct rcfile *f)
+{
+ char strbuf[STRBUFSIZ];
+ int getres;
+
+ getres = rc_getstring(f, sect_name, sectno, key->keyname, STRBUFSIZ,
+ strbuf);
+
+ if (getres != 0) {
+ if (key->mandatory != 0) {
+ error(MSG_MANDATORYKEYMISSING, key->keyname,
+ sect_name);
+ return (1);
+ } else
+ /* Non-mandatory key, not present -- skip */
+ return (0);
+ }
+ strncpy(key->field, (char *)&strbuf, (size_t)(key->maxlength - 1));
+ return (0);
+}
+
+/* Function tries to get on/off value for given key */
+static int
+get_argument_bool(const char *sect_name, int sectno, struct nandsim_key *key,
+ struct rcfile *f)
+{
+ int getres, val;
+
+ getres = rc_getbool(f, sect_name, sectno, key->keyname, &val);
+ if (getres != 0) {
+ if (key->mandatory != 0) {
+ error(MSG_MANDATORYKEYMISSING, key->keyname,
+ sect_name);
+ return (1);
+ } else
+ /* Non-mandatory key, not present -- skip */
+ return (0);
+ }
+ *(uint8_t *)key->field = (uint8_t)val;
+ return (0);
+}
+
+int
+parse_section(struct rcfile *f, const char *sect_name, int sectno)
+{
+ struct nandsim_key *key;
+ struct nandsim_section *sect = (struct nandsim_section *)&sections;
+ int getres = 0;
+
+ while (1) {
+ if (sect == NULL)
+ return (EINVAL);
+
+ if (strcmp(sect->name, sect_name) == 0)
+ break;
+ else
+ sect++;
+ }
+ key = sect->keys;
+ do {
+ debug("->Section: %s, Key: %s, type: %d, size: %d",
+ sect_name, key->keyname, TYPE(key->valuetype),
+ SIZE(key->valuetype)/2);
+
+ switch (TYPE(key->valuetype)) {
+ case VALUE_UINT:
+ /* Single int value */
+ getres = get_argument_int(sect_name, sectno, key, f);
+
+ if (getres != 0)
+ return (getres);
+
+ break;
+ case VALUE_UINTARRAY:
+ /* Array of ints */
+ getres = get_argument_intarray(sect_name,
+ sectno, key, f);
+
+ if (getres != 0)
+ return (getres);
+
+ break;
+ case VALUE_STRING:
+ /* Array of chars */
+ getres = get_argument_string(sect_name, sectno, key,
+ f);
+
+ if (getres != 0)
+ return (getres);
+
+ break;
+ case VALUE_BOOL:
+ /* Boolean value (true/false/on/off/yes/no) */
+ getres = get_argument_bool(sect_name, sectno, key,
+ f);
+
+ if (getres != 0)
+ return (getres);
+
+ break;
+ }
+ } while ((++key)->keyname != NULL);
+
+ return (0);
+}
+
+static uint8_t
+validate_chips(struct sim_chip *chips, int chipcnt,
+ struct sim_ctrl *ctrls, int ctrlcnt)
+{
+ int cchipcnt, i, width, j, id, max;
+
+ cchipcnt = chipcnt;
+ for (chipcnt -= 1; chipcnt >= 0; chipcnt--) {
+ if (chips[chipcnt].num >= MAX_CTRL_CS) {
+ error("chip no. too high (%d)!!\n",
+ chips[chipcnt].num);
+ return (EINVAL);
+ }
+
+ if (chips[chipcnt].ctrl_num >= MAX_SIM_DEV) {
+ error("controller no. too high (%d)!!\n",
+ chips[chipcnt].ctrl_num);
+ return (EINVAL);
+ }
+
+ if (chips[chipcnt].width != 8 &&
+ chips[chipcnt].width != 16) {
+ error("invalid width:%d for chip#%d",
+ chips[chipcnt].width, chips[chipcnt].num);
+ return (EINVAL);
+ }
+
+ /* Check if page size is > 512 and if its power of 2 */
+ if (chips[chipcnt].page_size < 512 ||
+ (chips[chipcnt].page_size &
+ (chips[chipcnt].page_size - 1)) != 0) {
+ error("invalid page size:%d for chip#%d at ctrl#%d!!"
+ "\n", chips[chipcnt].page_size,
+ chips[chipcnt].num,
+ chips[chipcnt].ctrl_num);
+ return (EINVAL);
+ }
+
+ /* Check if controller no. ctrl_num is configured */
+ for (i = 0, id = -1; i < ctrlcnt && id == -1; i++)
+ if (ctrls[i].num == chips[chipcnt].ctrl_num)
+ id = i;
+
+ if (i == ctrlcnt && id == -1) {
+ error("Missing configuration for controller %d"
+ " (at least one chip is connected to it)",
+ chips[chipcnt].ctrl_num);
+ return (EINVAL);
+ } else {
+ /*
+ * Controller is configured -> check oob_size
+ * validity
+ */
+ i = 0;
+ max = ctrls[id].ecc_layout[0];
+ while (i < MAX_ECC_BYTES &&
+ ctrls[id].ecc_layout[i] != 0xffff) {
+
+ if (ctrls[id].ecc_layout[i] > max)
+ max = ctrls[id].ecc_layout[i];
+ i++;
+ }
+
+ if (chips[chipcnt].oob_size < (unsigned)i) {
+ error("OOB size for chip#%d at ctrl#%d is "
+ "smaller than ecc layout length!",
+ chips[chipcnt].num,
+ chips[chipcnt].ctrl_num);
+ exit(EINVAL);
+ }
+
+ if (chips[chipcnt].oob_size < (unsigned)max) {
+ error("OOB size for chip#%d at ctrl#%d is "
+ "smaller than maximal ecc position in "
+ "defined layout!", chips[chipcnt].num,
+ chips[chipcnt].ctrl_num);
+ exit(EINVAL);
+ }
+
+
+ }
+
+ if ((chips[chipcnt].erase_time < DELAYTIME_MIN ||
+ chips[chipcnt].erase_time > DELAYTIME_MAX) &&
+ chips[chipcnt].erase_time != 0) {
+ error("Invalid erase time value for chip#%d at "
+ "ctrl#%d",
+ chips[chipcnt].num,
+ chips[chipcnt].ctrl_num);
+ return (EINVAL);
+ }
+
+ if ((chips[chipcnt].prog_time < DELAYTIME_MIN ||
+ chips[chipcnt].prog_time > DELAYTIME_MAX) &&
+ chips[chipcnt].prog_time != 0) {
+ error("Invalid prog time value for chip#%d at "
+ "ctr#%d!",
+ chips[chipcnt].num,
+ chips[chipcnt].ctrl_num);
+ return (EINVAL);
+ }
+
+ if ((chips[chipcnt].read_time < DELAYTIME_MIN ||
+ chips[chipcnt].read_time > DELAYTIME_MAX) &&
+ chips[chipcnt].read_time != 0) {
+ error("Invalid read time value for chip#%d at "
+ "ctrl#%d!",
+ chips[chipcnt].num,
+ chips[chipcnt].ctrl_num);
+ return (EINVAL);
+ }
+ }
+ /* Check if chips attached to the same controller, have same width */
+ for (i = 0; i < ctrlcnt; i++) {
+ width = -1;
+ for (j = 0; j < cchipcnt; j++) {
+ if (chips[j].ctrl_num == i) {
+ if (width == -1) {
+ width = chips[j].width;
+ } else {
+ if (width != chips[j].width) {
+ error("Chips attached to "
+ "ctrl#%d have different "
+ "widths!\n", i);
+ return (EINVAL);
+ }
+ }
+ }
+ }
+ }
+
+ return (0);
+}
+
+static uint8_t
+validate_ctrls(struct sim_ctrl *ctrl, int ctrlcnt)
+{
+ for (ctrlcnt -= 1; ctrlcnt >= 0; ctrlcnt--) {
+ if (ctrl[ctrlcnt].num > MAX_SIM_DEV) {
+ error("Controller no. too high (%d)!!\n",
+ ctrl[ctrlcnt].num);
+ return (EINVAL);
+ }
+ if (ctrl[ctrlcnt].num_cs > MAX_CTRL_CS) {
+ error("Too many CS (%d)!!\n", ctrl[ctrlcnt].num_cs);
+ return (EINVAL);
+ }
+ if (ctrl[ctrlcnt].ecc != 0 && ctrl[ctrlcnt].ecc != 1) {
+ error("ECC is set to neither 0 nor 1 !\n");
+ return (EINVAL);
+ }
+ }
+
+ return (0);
+}
+
+static int validate_section_config(struct rcfile *f, const char *sect_name,
+ int sectno)
+{
+ struct nandsim_key *key;
+ struct nandsim_section *sect;
+ char **keys_tbl;
+ int i, match;
+
+ for (match = 0, sect = (struct nandsim_section *)&sections;
+ sect != NULL; sect++) {
+ if (strcmp(sect->name, sect_name) == 0) {
+ match = 1;
+ break;
+ }
+ }
+
+ if (match == 0)
+ return (EINVAL);
+
+ keys_tbl = rc_getkeys(f, sect_name, sectno);
+ if (keys_tbl == NULL)
+ return (ENOMEM);
+
+ for (i = 0; keys_tbl[i] != NULL; i++) {
+ key = sect->keys;
+ match = 0;
+ do {
+ if (strcmp(keys_tbl[i], key->keyname) == 0) {
+ match = 1;
+ break;
+ }
+ } while ((++key)->keyname != NULL);
+
+ if (match == 0) {
+ error("Invalid key in config file: %s\n", keys_tbl[i]);
+ free(keys_tbl);
+ return (EINVAL);
+ }
+ }
+
+ free(keys_tbl);
+ return (0);
+}
diff --git a/usr.sbin/nandsim/nandsim_cfgparse.h b/usr.sbin/nandsim/nandsim_cfgparse.h
new file mode 100644
index 0000000..b9c642a
--- /dev/null
+++ b/usr.sbin/nandsim/nandsim_cfgparse.h
@@ -0,0 +1,86 @@
+/*-
+ * Copyright (C) 2009-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 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 _NANDSIM_CONFPARSER_H_
+#define _NANDSIM_CONFPARSER_H_
+
+#define VALUE_UINT 0x08
+#define VALUE_INT 0x10
+#define VALUE_UINTARRAY 0x18
+#define VALUE_INTARRAY 0x20
+#define VALUE_STRING 0x28
+#define VALUE_CHAR 0x40
+#define VALUE_BOOL 0x48
+
+#define SIZE_8 0x01
+#define SIZE_16 0x02
+#define SIZE_32 0x04
+
+#include "nandsim_rcfile.h"
+
+/*
+ * keyname = name of a key,
+ * mandatory = is key mandatory in section belonging to, 0=false 1=true
+ * valuetype = what kind of value is assigned to that key, e.g.
+ * VALUE_UINT | SIZE_8 -- unsigned uint size 8 bits;
+ * VALUE_UINTARRAY | SIZE_8 -- array of uints 8-bit long;
+ * VALUE_BOOL -- 'on', 'off','true','false','yes' or 'no'
+ * literals;
+ * VALUE_STRING -- strings
+ * field = ptr to the field that should hold value for parsed value
+ * maxlength = contains maximum length of an array (used only with either
+ * VALUE_STRING or VALUE_(U)INTARRAY value types.
+ */
+struct nandsim_key {
+ const char *keyname;
+ uint8_t mandatory;
+ uint8_t valuetype;
+ void *field;
+ uint32_t maxlength;
+};
+struct nandsim_section {
+ const char *name;
+ struct nandsim_key *keys;
+};
+
+struct nandsim_config {
+ struct sim_param **simparams;
+ struct sim_chip **simchips;
+ struct sim_ctrl **simctrls;
+ int chipcnt;
+ int ctrlcnt;
+};
+
+int parse_intarray(char *, int **);
+int parse_config(char *, const char *);
+int parse_section(struct rcfile *, const char *, int);
+int compare_configs(struct nandsim_config *, struct nandsim_config *);
+int convert_argint(char *, int *);
+int convert_arguint(char *, unsigned int *);
+
+#endif /* _NANDSIM_CONFPARSER_H_ */
diff --git a/usr.sbin/nandsim/nandsim_rcfile.c b/usr.sbin/nandsim/nandsim_rcfile.c
new file mode 100644
index 0000000..0f99e7b
--- /dev/null
+++ b/usr.sbin/nandsim/nandsim_rcfile.c
@@ -0,0 +1,440 @@
+/*
+ * Copyright (c) 1999, Boris Popov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the names of 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 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.
+ *
+ * from: FreeBSD: src/lib/libncp/ncpl_rcfile.c,v 1.5 2007/01/09 23:27:39 imp Exp
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <pwd.h>
+#include <unistd.h>
+
+#include "nandsim_rcfile.h"
+
+SLIST_HEAD(rcfile_head, rcfile);
+static struct rcfile_head pf_head = {NULL};
+static struct rcsection *rc_findsect(struct rcfile *rcp,
+ const char *sectname, int sect_id);
+static struct rcsection *rc_addsect(struct rcfile *rcp,
+ const char *sectname);
+static int rc_sect_free(struct rcsection *rsp);
+static struct rckey *rc_sect_findkey(struct rcsection *rsp,
+ const char *keyname);
+static struct rckey *rc_sect_addkey(struct rcsection *rsp, const char *name,
+ char *value);
+static void rc_key_free(struct rckey *p);
+static void rc_parse(struct rcfile *rcp);
+
+static struct rcfile* rc_find(const char *filename);
+
+/*
+ * open rcfile and load its content, if already open - return previous handle
+ */
+int
+rc_open(const char *filename, const char *mode,struct rcfile **rcfile)
+{
+ struct rcfile *rcp;
+ FILE *f;
+ rcp = rc_find(filename);
+ if (rcp) {
+ *rcfile = rcp;
+ return (0);
+ }
+ f = fopen (filename, mode);
+ if (f == NULL)
+ return errno;
+ rcp = malloc(sizeof(struct rcfile));
+ if (rcp == NULL) {
+ fclose(f);
+ return ENOMEM;
+ }
+ bzero(rcp, sizeof(struct rcfile));
+ rcp->rf_name = strdup(filename);
+ rcp->rf_f = f;
+ SLIST_INSERT_HEAD(&pf_head, rcp, rf_next);
+ rc_parse(rcp);
+ *rcfile = rcp;
+ return (0);
+}
+
+int
+rc_close(struct rcfile *rcp)
+{
+ struct rcsection *p,*n;
+
+ fclose(rcp->rf_f);
+ for (p = SLIST_FIRST(&rcp->rf_sect); p; ) {
+ n = p;
+ p = SLIST_NEXT(p,rs_next);
+ rc_sect_free(n);
+ }
+ free(rcp->rf_name);
+ SLIST_REMOVE(&pf_head, rcp, rcfile, rf_next);
+ free(rcp);
+ return (0);
+}
+
+static struct rcfile*
+rc_find(const char *filename)
+{
+ struct rcfile *p;
+
+ SLIST_FOREACH(p, &pf_head, rf_next)
+ if (strcmp (filename, p->rf_name) == 0)
+ return (p);
+ return (0);
+}
+
+/* Find section with given name and id */
+static struct rcsection *
+rc_findsect(struct rcfile *rcp, const char *sectname, int sect_id)
+{
+ struct rcsection *p;
+
+ SLIST_FOREACH(p, &rcp->rf_sect, rs_next)
+ if (strcmp(p->rs_name, sectname) == 0 && p->rs_id == sect_id)
+ return (p);
+ return (NULL);
+}
+
+static struct rcsection *
+rc_addsect(struct rcfile *rcp, const char *sectname)
+{
+ struct rcsection *p;
+ int id = 0;
+ p = rc_findsect(rcp, sectname, 0);
+ if (p) {
+ /*
+ * If section with that name already exists -- add one more,
+ * same named, but with different id (higher by one)
+ */
+ while (p != NULL) {
+ id = p->rs_id + 1;
+ p = rc_findsect(rcp, sectname, id);
+ }
+ }
+ p = malloc(sizeof(*p));
+ if (!p)
+ return (NULL);
+ p->rs_name = strdup(sectname);
+ p->rs_id = id;
+ SLIST_INIT(&p->rs_keys);
+ SLIST_INSERT_HEAD(&rcp->rf_sect, p, rs_next);
+ return (p);
+}
+
+static int
+rc_sect_free(struct rcsection *rsp)
+{
+ struct rckey *p,*n;
+
+ for (p = SLIST_FIRST(&rsp->rs_keys); p; ) {
+ n = p;
+ p = SLIST_NEXT(p,rk_next);
+ rc_key_free(n);
+ }
+ free(rsp->rs_name);
+ free(rsp);
+ return (0);
+}
+
+static struct rckey *
+rc_sect_findkey(struct rcsection *rsp, const char *keyname)
+{
+ struct rckey *p;
+
+ SLIST_FOREACH(p, &rsp->rs_keys, rk_next)
+ if (strcmp(p->rk_name, keyname)==0)
+ return (p);
+ return (NULL);
+}
+
+static struct rckey *
+rc_sect_addkey(struct rcsection *rsp, const char *name, char *value)
+{
+ struct rckey *p;
+ p = rc_sect_findkey(rsp, name);
+ if (p) {
+ free(p->rk_value);
+ } else {
+ p = malloc(sizeof(*p));
+ if (!p)
+ return (NULL);
+ SLIST_INSERT_HEAD(&rsp->rs_keys, p, rk_next);
+ p->rk_name = strdup(name);
+ }
+ p->rk_value = value ? strdup(value) : strdup("");
+ return (p);
+}
+
+static void
+rc_key_free(struct rckey *p)
+{
+ free(p->rk_value);
+ free(p->rk_name);
+ free(p);
+}
+
+enum { stNewLine, stHeader, stSkipToEOL, stGetKey, stGetValue};
+
+static void
+rc_parse(struct rcfile *rcp)
+{
+ FILE *f = rcp->rf_f;
+ int state = stNewLine, c;
+ struct rcsection *rsp = NULL;
+ struct rckey *rkp = NULL;
+ char buf[2048];
+ char *next = buf, *last = &buf[sizeof(buf)-1];
+
+ while ((c = getc (f)) != EOF) {
+ if (c == '\r')
+ continue;
+ if (state == stNewLine) {
+ next = buf;
+ if (isspace(c))
+ continue; /* skip leading junk */
+ if (c == '[') {
+ state = stHeader;
+ rsp = NULL;
+ continue;
+ }
+ if (c == '#' || c == ';') {
+ state = stSkipToEOL;
+ } else { /* something meaningful */
+ state = stGetKey;
+ }
+ }
+ if (state == stSkipToEOL || next == last) {/* ignore long lines */
+ if (c == '\n') {
+ state = stNewLine;
+ next = buf;
+ }
+ continue;
+ }
+ if (state == stHeader) {
+ if (c == ']') {
+ *next = 0;
+ next = buf;
+ rsp = rc_addsect(rcp, buf);
+ state = stSkipToEOL;
+ } else
+ *next++ = c;
+ continue;
+ }
+ if (state == stGetKey) {
+ if (c == ' ' || c == '\t')/* side effect: 'key name='*/
+ continue; /* become 'keyname=' */
+ if (c == '\n') { /* silently ignore ... */
+ state = stNewLine;
+ continue;
+ }
+ if (c != '=') {
+ *next++ = c;
+ continue;
+ }
+ *next = 0;
+ if (rsp == NULL) {
+ fprintf(stderr, "Key '%s' defined before "
+ "section\n", buf);
+ state = stSkipToEOL;
+ continue;
+ }
+ rkp = rc_sect_addkey(rsp, buf, NULL);
+ next = buf;
+ state = stGetValue;
+ continue;
+ }
+ /* only stGetValue left */
+ if (state != stGetValue) {
+ fprintf(stderr, "Well, I can't parse file "
+ "'%s'\n",rcp->rf_name);
+ state = stSkipToEOL;
+ }
+ if (c != '\n') {
+ *next++ = c;
+ continue;
+ }
+ *next = 0;
+ rkp->rk_value = strdup(buf);
+ state = stNewLine;
+ rkp = NULL;
+ } /* while */
+ if (c == EOF && state == stGetValue) {
+ *next = 0;
+ rkp->rk_value = strdup(buf);
+ }
+}
+
+int
+rc_getstringptr(struct rcfile *rcp, const char *section, int sect_id,
+ const char *key, char **dest)
+{
+ struct rcsection *rsp;
+ struct rckey *rkp;
+
+ *dest = NULL;
+ rsp = rc_findsect(rcp, section, sect_id);
+ if (!rsp)
+ return (ENOENT);
+ rkp = rc_sect_findkey(rsp,key);
+ if (!rkp)
+ return (ENOENT);
+ *dest = rkp->rk_value;
+ return (0);
+}
+
+int
+rc_getstring(struct rcfile *rcp, const char *section, int sect_id,
+ const char *key, unsigned int maxlen, char *dest)
+{
+ char *value;
+ int error;
+
+ error = rc_getstringptr(rcp, section, sect_id, key, &value);
+ if (error)
+ return (error);
+ if (strlen(value) >= maxlen) {
+ fprintf(stderr, "line too long for key '%s' in section '%s',"
+ "max = %d\n",key, section, maxlen);
+ return (EINVAL);
+ }
+ strcpy(dest,value);
+ return (0);
+}
+
+int
+rc_getint(struct rcfile *rcp, const char *section, int sect_id,
+ const char *key, int *value)
+{
+ struct rcsection *rsp;
+ struct rckey *rkp;
+
+ rsp = rc_findsect(rcp, section, sect_id);
+ if (!rsp)
+ return (ENOENT);
+ rkp = rc_sect_findkey(rsp,key);
+ if (!rkp)
+ return (ENOENT);
+ errno = 0;
+ *value = strtol(rkp->rk_value,NULL,0);
+ if (errno) {
+ fprintf(stderr, "invalid int value '%s' for key '%s' in "
+ "section '%s'\n",rkp->rk_value,key,section);
+ return (errno);
+ }
+ return (0);
+}
+
+/*
+ * 1,yes,true
+ * 0,no,false
+ */
+int
+rc_getbool(struct rcfile *rcp, const char *section, int sect_id,
+ const char *key, int *value)
+{
+ struct rcsection *rsp;
+ struct rckey *rkp;
+ char *p;
+
+ rsp = rc_findsect(rcp, section, sect_id);
+ if (!rsp)
+ return (ENOENT);
+ rkp = rc_sect_findkey(rsp,key);
+ if (!rkp)
+ return (ENOENT);
+ p = rkp->rk_value;
+ while (*p && isspace(*p)) p++;
+ if (*p == '0' || strcasecmp(p,"no") == 0 ||
+ strcasecmp(p, "false") == 0 ||
+ strcasecmp(p, "off") == 0) {
+ *value = 0;
+ return (0);
+ }
+ if (*p == '1' || strcasecmp(p,"yes") == 0 ||
+ strcasecmp(p, "true") == 0 ||
+ strcasecmp(p, "on") == 0) {
+ *value = 1;
+ return (0);
+ }
+ fprintf(stderr, "invalid boolean value '%s' for key '%s' in section "
+ "'%s' \n",p, key, section);
+ return (EINVAL);
+}
+
+/* Count how many sections with given name exists in configuration. */
+int rc_getsectionscount(struct rcfile *f, const char *sectname)
+{
+ struct rcsection *p;
+ int count = 0;
+
+ p = rc_findsect(f, sectname, 0);
+ if (p) {
+ while (p != NULL) {
+ count = p->rs_id + 1;
+ p = rc_findsect(f, sectname, count);
+ }
+ return (count);
+ } else
+ return (0);
+}
+
+char **
+rc_getkeys(struct rcfile *rcp, const char *sectname, int sect_id)
+{
+ struct rcsection *rsp;
+ struct rckey *p;
+ char **names_tbl;
+ int i = 0, count = 0;
+
+ rsp = rc_findsect(rcp, sectname, sect_id);
+ if (rsp == NULL)
+ return (NULL);
+
+ SLIST_FOREACH(p, &rsp->rs_keys, rk_next)
+ count++;
+
+ names_tbl = malloc(sizeof(char *) * (count + 1));
+ if (names_tbl == NULL)
+ return (NULL);
+
+ SLIST_FOREACH(p, &rsp->rs_keys, rk_next)
+ names_tbl[i++] = p->rk_name;
+
+ names_tbl[i] = NULL;
+ return (names_tbl);
+}
+
diff --git a/usr.sbin/nandsim/nandsim_rcfile.h b/usr.sbin/nandsim/nandsim_rcfile.h
new file mode 100644
index 0000000..f5c3ce9
--- /dev/null
+++ b/usr.sbin/nandsim/nandsim_rcfile.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 1999, Boris Popov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the names of 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 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$
+ *
+ * from: FreeBSD: src/lib/libncp/ncpl_rcfile.c,v 1.5 2007/01/09 23:27:39 imp Exp
+ */
+
+#ifndef _SIMRC_H_
+#define _SIMRC_H_
+
+#include <sys/queue.h>
+
+struct rckey {
+ SLIST_ENTRY(rckey) rk_next;
+ char *rk_name; /* key name */
+ char *rk_value; /* key value */
+};
+
+struct rcsection {
+ SLIST_ENTRY(rcsection) rs_next;
+ SLIST_HEAD(rckey_head,rckey) rs_keys; /* key list */
+ char *rs_name; /* section name */
+ int rs_id; /* allow few same named */
+};
+
+struct rcfile {
+ SLIST_ENTRY(rcfile) rf_next;
+ SLIST_HEAD(rcsec_head, rcsection) rf_sect; /* sections list */
+ char *rf_name; /* file name */
+ FILE *rf_f; /* file desc */
+};
+
+int rc_open(const char *, const char *,struct rcfile **);
+int rc_close(struct rcfile *);
+int rc_getstringptr(struct rcfile *, const char *, int, const char *,
+ char **);
+int rc_getstring(struct rcfile *, const char *, int, const char *,
+ unsigned int, char *);
+int rc_getint(struct rcfile *, const char *, int, const char *, int *);
+int rc_getbool(struct rcfile *, const char *, int, const char *, int *);
+int rc_getsectionscount(struct rcfile *, const char *);
+char **rc_getkeys(struct rcfile *, const char *, int);
+
+#endif /* _SIMRC_H_ */
diff --git a/usr.sbin/nandsim/sample.conf b/usr.sbin/nandsim/sample.conf
new file mode 100644
index 0000000..bc534e1
--- /dev/null
+++ b/usr.sbin/nandsim/sample.conf
@@ -0,0 +1,174 @@
+#-
+# Copyright (C) 2009-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.
+#
+# $FreeBSD$
+
+#
+# Sample NANDsim configuration file.
+#
+
+#############################################################################
+#
+# [sim] General (common) simulator configuration section.
+#
+[sim]
+# log_level=0..255
+log_level=11
+
+# log_output=[none, console, ram, file]
+#
+# When log_output=file is specified, each [ctrl] section must have a
+# corresponding 'log_filename' field provided, which specifies log file name
+# to be used.
+log_output=none
+
+#############################################################################
+#
+# [ctrl] Controller configuration section.
+#
+# There can be a number of controllers defined for simulation, each has a
+# dedicated [ctrl] section. With a given controller there are associated
+# subordinate NAND chips, which are tied to chip select lines.
+#
+[ctrl]
+# The number of this controller.
+# ctrl_num=0..3
+ctrl_num=0
+
+# The number of chip selects available at this controller.
+# num_cs=1..4
+num_cs=1
+
+# ECC enable flag.
+# ecc=[on|off]
+ecc=on
+
+# ECC layout. This is the list of byte offsets within OOB area, which comprise
+# the ECC contents set.
+#
+# ecc_layout=[byte1, byte2-byte3, ..byten]
+ecc_layout=[0-53]
+
+# Absolute path to the log file for this controller.
+#log_filename=/var/log/nandsim-ctl0.log
+
+
+#############################################################################
+#
+# [chip] Chip configuration section.
+#
+# There can be a number of individual NAND chip devices defined for
+# simulation, and each has a dedicated [chip] section.
+#
+# A particular chip needs to be associated with its parent NAND controller by
+# specifying the following fields: controller number (chip_ctrl) and the chip
+# select line it is connected to (chip_cs). The chip can be connected to only
+# a single (and unique) controller:cs pair.
+#
+[chip]
+# The number of parent controller. This has to fit one of the controller
+# instance number (ctrl_num from [ctrl] section).
+# chip_ctrl=0..3
+chip_ctrl=0
+
+# Chip select line.
+# chip_cs=0..3
+chip_cs=0
+
+# ONFI device identifier.
+# device_id=0x00..0xff
+device_id=0xd3
+
+# ONFI manufacturer identifier.
+# manufacturer_id=0x00..0xff
+manufacturer_id=0xec
+
+# Textual description of the chip.
+# model="model_name"
+model="k9xxg08uxM:1GiB 3,3V 8-bit"
+
+# Textual name of the chip manufacturer.
+# manufacturer="manufacturer name"
+manufacturer="SAMSUNG"
+
+# page_size=[must be power of 2 and >= 512] (in bytes)
+page_size=2048
+# oob_size=[>0]
+oob_size=64
+# pages_per_block=n*32
+pages_per_block=64
+# blocks_per_lun=[>0]
+blocks_per_lun=4096
+# luns=1..N
+luns=1
+# column_addr_cycle=[1,2]
+column_addr_cycle=2
+# row_addr_cycle=[1,2,3]
+row_addr_cycle=3
+
+# program_time= (in us)
+program_time=0
+# erase_time= (in us)
+erase_time=0
+# read_time= (in us)
+read_time=0
+# ccs_time= (in us)
+#ccs_time=200
+
+# Simulate write-protect on the chip.
+# write_protect=[yes|no]
+#write_protect=no
+
+# Blocks wear-out threshold. Each block has a counter of program-erase cycles;
+# when this counter reaches 'wear_out' value a given block is treated as a bad
+# block (access will report error).
+#
+# Setting wear_out to 0 means that blocks will never wear out.
+#
+# wear_out=0..100000
+wear_out=50000
+
+# Errors per million read/write bytes. This simulates an accidental read/write
+# block error, which can happen in real devices with certain probability. Note
+# this isn't a bad block condition i.e. the block at which the read/write
+# operation is simulated to fail here remains usable, only the operation has
+# not succeeded (this is where ECC comes into play and is supposed to correct
+# such problems).
+#
+# error_ratio=0..1000000
+#error_ratio=50
+
+# Chip data bus width. All chips connected to the same controller must have
+# the same bus width.
+#
+# width=[8|16]
+width=8
+
+# Bad block map. NANDsim emulates bad block behavior upon accessing a block
+# with number from the specified list.
+#
+# bad_block_map=[bad_block1, bad_block2-bad_block3, ..bad_blockn]
+bad_block_map=[100-200]
+
diff --git a/usr.sbin/nandtool/Makefile b/usr.sbin/nandtool/Makefile
new file mode 100644
index 0000000..c01c2fd
--- /dev/null
+++ b/usr.sbin/nandtool/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+PROG= nandtool
+SRCS= nandtool.c nand_read.c nand_write.c nand_erase.c nand_info.c
+SRCS+= nand_readoob.c nand_writeoob.c
+BINDIR= /usr/sbin
+LIBADD= geom
+MAN= nandtool.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/nandtool/Makefile.depend b/usr.sbin/nandtool/Makefile.depend
new file mode 100644
index 0000000..851372c
--- /dev/null
+++ b/usr.sbin/nandtool/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libexpat \
+ lib/libgeom \
+ lib/libsbuf \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/nandtool/nand_erase.c b/usr.sbin/nandtool/nand_erase.c
new file mode 100644
index 0000000..50bfaa6
--- /dev/null
+++ b/usr.sbin/nandtool/nand_erase.c
@@ -0,0 +1,114 @@
+/*-
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/disk.h>
+#include <libgeom.h>
+#include <dev/nand/nand_dev.h>
+#include "nandtool.h"
+
+int nand_erase(struct cmd_param *params)
+{
+ struct chip_param_io chip_params;
+ char *dev;
+ int fd = -1, ret = 0;
+ off_t pos, count;
+ off_t start, nblocks, i;
+ int block_size, mult;
+
+ if (!(dev = param_get_string(params, "dev"))) {
+ fprintf(stderr, "Please supply valid 'dev' parameter.\n");
+ return (1);
+ }
+
+ if (param_has_value(params, "count"))
+ count = param_get_intx(params, "count");
+ else
+ count = 1;
+
+ if ((fd = g_open(dev, 1)) < 0) {
+ perrorf("Cannot open %s", dev);
+ return (1);
+ }
+
+ if (ioctl(fd, NAND_IO_GET_CHIP_PARAM, &chip_params) == -1) {
+ perrorf("Cannot ioctl(NAND_IO_GET_CHIP_PARAM)");
+ ret = 1;
+ goto out;
+ }
+
+ block_size = chip_params.page_size * chip_params.pages_per_block;
+
+ if (param_has_value(params, "page")) {
+ pos = chip_params.page_size * param_get_intx(params, "page");
+ mult = chip_params.page_size;
+ } else if (param_has_value(params, "block")) {
+ pos = block_size * param_get_intx(params, "block");
+ mult = block_size;
+ } else if (param_has_value(params, "pos")) {
+ pos = param_get_intx(params, "pos");
+ mult = 1;
+ } else {
+ /* Erase whole chip */
+ if (ioctl(fd, DIOCGMEDIASIZE, &count) == -1) {
+ ret = 1;
+ goto out;
+ }
+
+ pos = 0;
+ mult = 1;
+ }
+
+ if (pos % block_size) {
+ fprintf(stderr, "Position must be block-size aligned!\n");
+ ret = 1;
+ goto out;
+ }
+
+ count *= mult;
+ start = pos / block_size;
+ nblocks = count / block_size;
+
+ for (i = 0; i < nblocks; i++) {
+ if (g_delete(fd, (start + i) * block_size, block_size) == -1) {
+ perrorf("Cannot erase block %d - probably a bad block",
+ start + i);
+ ret = 1;
+ }
+ }
+
+out:
+ g_close(fd);
+
+ return (ret);
+}
+
diff --git a/usr.sbin/nandtool/nand_info.c b/usr.sbin/nandtool/nand_info.c
new file mode 100644
index 0000000..38fe010
--- /dev/null
+++ b/usr.sbin/nandtool/nand_info.c
@@ -0,0 +1,86 @@
+/*-
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <libgeom.h>
+#include <sys/disk.h>
+#include <dev/nand/nand_dev.h>
+#include "nandtool.h"
+
+int nand_info(struct cmd_param *params)
+{
+ struct chip_param_io chip_params;
+ int fd = -1, ret = 0;
+ int block_size;
+ off_t chip_size, media_size;
+ const char *dev;
+
+ if ((dev = param_get_string(params, "dev")) == NULL) {
+ fprintf(stderr, "Please supply 'dev' parameter, eg. "
+ "'dev=/dev/gnand0'\n");
+ return (1);
+ }
+
+ if ((fd = g_open(dev, 1)) == -1) {
+ perrorf("Cannot open %s", dev);
+ return (1);
+ }
+
+ if (ioctl(fd, NAND_IO_GET_CHIP_PARAM, &chip_params) == -1) {
+ perrorf("Cannot ioctl(NAND_IO_GET_CHIP_PARAM)");
+ ret = 1;
+ goto out;
+ }
+
+ if (ioctl(fd, DIOCGMEDIASIZE, &media_size) == -1) {
+ perrorf("Cannot ioctl(DIOCGMEDIASIZE)");
+ ret = 1;
+ goto out;
+ }
+
+ block_size = chip_params.page_size * chip_params.pages_per_block;
+ chip_size = block_size * chip_params.blocks;
+
+ printf("Device:\t\t\t%s\n", dev);
+ printf("Page size:\t\t%d bytes\n", chip_params.page_size);
+ printf("Block size:\t\t%d bytes (%d KB)\n", block_size,
+ block_size / 1024);
+ printf("OOB size per page:\t%d bytes\n", chip_params.oob_size);
+ printf("Chip size:\t\t%jd MB\n", (uintmax_t)(chip_size / 1024 / 1024));
+ printf("Slice size:\t\t%jd MB\n",
+ (uintmax_t)(media_size / 1024 / 1024));
+
+out:
+ g_close(fd);
+
+ return (ret);
+}
diff --git a/usr.sbin/nandtool/nand_read.c b/usr.sbin/nandtool/nand_read.c
new file mode 100644
index 0000000..5267b7d
--- /dev/null
+++ b/usr.sbin/nandtool/nand_read.c
@@ -0,0 +1,139 @@
+/*-
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <libgeom.h>
+#include <sys/disk.h>
+#include <dev/nand/nand_dev.h>
+#include "nandtool.h"
+
+int nand_read(struct cmd_param *params)
+{
+ struct chip_param_io chip_params;
+ int fd = -1, out_fd = -1, done = 0, ret = 0;
+ char *dev, *out;
+ int pos, count, mult, block_size;
+ uint8_t *buf = NULL;
+
+ if (!(dev = param_get_string(params, "dev"))) {
+ fprintf(stderr, "You must specify 'dev' parameter\n");
+ return (1);
+ }
+
+ if ((out = param_get_string(params, "out"))) {
+ out_fd = open(out, O_WRONLY|O_CREAT);
+ if (out_fd == -1) {
+ perrorf("Cannot open %s for writing", out);
+ return (1);
+ }
+ }
+
+ if ((fd = g_open(dev, 1)) == -1) {
+ perrorf("Cannot open %s", dev);
+ ret = 1;
+ goto out;
+ }
+
+ if (ioctl(fd, NAND_IO_GET_CHIP_PARAM, &chip_params) == -1) {
+ perrorf("Cannot ioctl(NAND_IO_GET_CHIP_PARAM)");
+ ret = 1;
+ goto out;
+ }
+
+ block_size = chip_params.page_size * chip_params.pages_per_block;
+
+ if (param_has_value(params, "page")) {
+ pos = chip_params.page_size * param_get_int(params, "page");
+ mult = chip_params.page_size;
+ } else if (param_has_value(params, "block")) {
+ pos = block_size * param_get_int(params, "block");
+ mult = block_size;
+ } else if (param_has_value(params, "pos")) {
+ pos = param_get_int(params, "pos");
+ mult = 1;
+ if (pos % chip_params.page_size) {
+ fprintf(stderr, "Position must be page-size aligned!\n");
+ ret = 1;
+ goto out;
+ }
+ } else {
+ fprintf(stderr, "You must specify one of: 'block', 'page',"
+ "'pos' arguments\n");
+ ret = 1;
+ goto out;
+ }
+
+ if (!(param_has_value(params, "count")))
+ count = mult;
+ else
+ count = param_get_int(params, "count") * mult;
+
+ if (!(buf = malloc(chip_params.page_size))) {
+ perrorf("Cannot allocate buffer [size %x]",
+ chip_params.page_size);
+ ret = 1;
+ goto out;
+ }
+
+ lseek(fd, pos, SEEK_SET);
+
+ while (done < count) {
+ if ((ret = read(fd, buf, chip_params.page_size)) !=
+ (int32_t)chip_params.page_size) {
+ perrorf("read error (read %d bytes)", ret);
+ goto out;
+ }
+
+ if (out_fd != -1) {
+ done += ret;
+ if ((ret = write(out_fd, buf, chip_params.page_size)) !=
+ (int32_t)chip_params.page_size) {
+ perrorf("write error (written %d bytes)", ret);
+ ret = 1;
+ goto out;
+ }
+ } else {
+ hexdumpoffset(buf, chip_params.page_size, done);
+ done += ret;
+ }
+ }
+
+out:
+ g_close(fd);
+ if (out_fd != -1)
+ close(out_fd);
+ if (buf)
+ free(buf);
+
+ return (ret);
+}
+
diff --git a/usr.sbin/nandtool/nand_readoob.c b/usr.sbin/nandtool/nand_readoob.c
new file mode 100644
index 0000000..37fd14b
--- /dev/null
+++ b/usr.sbin/nandtool/nand_readoob.c
@@ -0,0 +1,111 @@
+/*-
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <libgeom.h>
+#include <sys/types.h>
+#include <sys/disk.h>
+#include <dev/nand/nand_dev.h>
+#include "nandtool.h"
+
+int nand_read_oob(struct cmd_param *params)
+{
+ struct chip_param_io chip_params;
+ struct nand_oob_rw req;
+ char *dev, *out;
+ int fd = -1, fd_out = -1, ret = 0;
+ int page;
+ uint8_t *buf = NULL;
+
+ if ((page = param_get_int(params, "page")) < 0) {
+ fprintf(stderr, "You must supply valid 'page' argument.\n");
+ return (1);
+ }
+
+ if (!(dev = param_get_string(params, "dev"))) {
+ fprintf(stderr, "You must supply 'dev' argument.\n");
+ return (1);
+ }
+
+ if ((out = param_get_string(params, "out"))) {
+ if ((fd_out = open(out, O_WRONLY | O_CREAT)) == -1) {
+ perrorf("Cannot open %s", out);
+ ret = 1;
+ goto out;
+ }
+ }
+
+ if ((fd = g_open(dev, 1)) == -1) {
+ perrorf("Cannot open %s", dev);
+ ret = 1;
+ goto out;
+ }
+
+ if (ioctl(fd, NAND_IO_GET_CHIP_PARAM, &chip_params) == -1) {
+ perrorf("Cannot ioctl(NAND_IO_GET_CHIP_PARAM)");
+ ret = 1;
+ goto out;
+ }
+
+ buf = malloc(chip_params.oob_size);
+ if (buf == NULL) {
+ perrorf("Cannot allocate %d bytes\n", chip_params.oob_size);
+ ret = 1;
+ goto out;
+ }
+
+ req.page = page;
+ req.len = chip_params.oob_size;
+ req.data = buf;
+
+ if (ioctl(fd, NAND_IO_OOB_READ, &req) == -1) {
+ perrorf("Cannot read OOB from %s", dev);
+ ret = 1;
+ goto out;
+ }
+
+ if (fd_out != -1)
+ write(fd_out, buf, chip_params.oob_size);
+ else
+ hexdump(buf, chip_params.oob_size);
+
+out:
+ close(fd_out);
+
+ if (fd != -1)
+ g_close(fd);
+ if (buf)
+ free(buf);
+
+ return (ret);
+}
+
diff --git a/usr.sbin/nandtool/nand_write.c b/usr.sbin/nandtool/nand_write.c
new file mode 100644
index 0000000..157c6aa
--- /dev/null
+++ b/usr.sbin/nandtool/nand_write.c
@@ -0,0 +1,143 @@
+/*-
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <libgeom.h>
+#include <sys/disk.h>
+#include <dev/nand/nand_dev.h>
+#include "nandtool.h"
+
+int nand_write(struct cmd_param *params)
+{
+ struct chip_param_io chip_params;
+ char *dev, *file;
+ int in_fd = -1, ret = 0, done = 0;
+ int fd, block_size, mult, pos, count;
+ uint8_t *buf = NULL;
+
+ if (!(dev = param_get_string(params, "dev"))) {
+ fprintf(stderr, "Please supply 'dev' argument.\n");
+ return (1);
+ }
+
+ if (!(file = param_get_string(params, "in"))) {
+ fprintf(stderr, "Please supply 'in' argument.\n");
+ return (1);
+ }
+
+ if ((fd = g_open(dev, 1)) == -1) {
+ perrorf("Cannot open %s", dev);
+ return (1);
+ }
+
+ if ((in_fd = open(file, O_RDONLY)) == -1) {
+ perrorf("Cannot open file %s", file);
+ ret = 1;
+ goto out;
+ }
+
+ if (ioctl(fd, NAND_IO_GET_CHIP_PARAM, &chip_params) == -1) {
+ perrorf("Cannot ioctl(NAND_IO_GET_CHIP_PARAM)");
+ ret = 1;
+ goto out;
+ }
+
+ block_size = chip_params.page_size * chip_params.pages_per_block;
+
+ if (param_has_value(params, "page")) {
+ pos = chip_params.page_size * param_get_int(params, "page");
+ mult = chip_params.page_size;
+ } else if (param_has_value(params, "block")) {
+ pos = block_size * param_get_int(params, "block");
+ mult = block_size;
+ } else if (param_has_value(params, "pos")) {
+ pos = param_get_int(params, "pos");
+ mult = 1;
+ if (pos % chip_params.page_size) {
+ fprintf(stderr, "Position must be page-size "
+ "aligned!\n");
+ ret = 1;
+ goto out;
+ }
+ } else {
+ fprintf(stderr, "You must specify one of: 'block', 'page',"
+ "'pos' arguments\n");
+ ret = 1;
+ goto out;
+ }
+
+ if (!(param_has_value(params, "count")))
+ count = mult;
+ else
+ count = param_get_int(params, "count") * mult;
+
+ if (!(buf = malloc(chip_params.page_size))) {
+ perrorf("Cannot allocate buffer [size %x]",
+ chip_params.page_size);
+ ret = 1;
+ goto out;
+ }
+
+ lseek(fd, pos, SEEK_SET);
+
+ while (done < count) {
+ if ((ret = read(in_fd, buf, chip_params.page_size)) !=
+ (int32_t)chip_params.page_size) {
+ if (ret > 0) {
+ /* End of file ahead, truncate here */
+ break;
+ } else {
+ perrorf("Cannot read from %s", file);
+ ret = 1;
+ goto out;
+ }
+ }
+
+ if ((ret = write(fd, buf, chip_params.page_size)) !=
+ (int32_t)chip_params.page_size) {
+ ret = 1;
+ goto out;
+ }
+
+ done += ret;
+ }
+
+out:
+ g_close(fd);
+ if (in_fd != -1)
+ close(in_fd);
+ if (buf)
+ free(buf);
+
+ return (ret);
+}
+
diff --git a/usr.sbin/nandtool/nand_writeoob.c b/usr.sbin/nandtool/nand_writeoob.c
new file mode 100644
index 0000000..53cb32a
--- /dev/null
+++ b/usr.sbin/nandtool/nand_writeoob.c
@@ -0,0 +1,113 @@
+/*-
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <libgeom.h>
+#include <sys/disk.h>
+#include <dev/nand/nand_dev.h>
+#include "nandtool.h"
+
+int nand_write_oob(struct cmd_param *params)
+{
+ struct chip_param_io chip_params;
+ struct nand_oob_rw req;
+ char *dev, *in;
+ int fd = -1, fd_in = -1, ret = 0;
+ uint8_t *buf = NULL;
+ int page;
+
+ if (!(dev = param_get_string(params, "dev"))) {
+ fprintf(stderr, "Please supply valid 'dev' parameter.\n");
+ return (1);
+ }
+
+ if (!(in = param_get_string(params, "in"))) {
+ fprintf(stderr, "Please supply valid 'in' parameter.\n");
+ return (1);
+ }
+
+ if ((page = param_get_int(params, "page")) < 0) {
+ fprintf(stderr, "Please supply valid 'page' parameter.\n");
+ return (1);
+ }
+
+ if ((fd = g_open(dev, 1)) == -1) {
+ perrorf("Cannot open %s", dev);
+ return (1);
+ }
+
+ if ((fd_in = open(in, O_RDONLY)) == -1) {
+ perrorf("Cannot open %s", in);
+ ret = 1;
+ goto out;
+ }
+
+ if (ioctl(fd, NAND_IO_GET_CHIP_PARAM, &chip_params) == -1) {
+ perrorf("Cannot ioctl(NAND_IO_GET_CHIP_PARAM)");
+ ret = 1;
+ goto out;
+ }
+
+ buf = malloc(chip_params.oob_size);
+ if (buf == NULL) {
+ perrorf("Cannot allocate %d bytes\n", chip_params.oob_size);
+ ret = 1;
+ goto out;
+ }
+
+ if (read(fd_in, buf, chip_params.oob_size) == -1) {
+ perrorf("Cannot read from %s", in);
+ ret = 1;
+ goto out;
+ }
+
+ req.page = page;
+ req.len = chip_params.oob_size;
+ req.data = buf;
+
+ if (ioctl(fd, NAND_IO_OOB_PROG, &req) == -1) {
+ perrorf("Cannot write OOB to %s", dev);
+ ret = 1;
+ goto out;
+ }
+
+out:
+ g_close(fd);
+ if (fd_in != -1)
+ close(fd_in);
+ if (buf)
+ free(buf);
+
+ return (ret);
+}
+
+
diff --git a/usr.sbin/nandtool/nandtool.8 b/usr.sbin/nandtool/nandtool.8
new file mode 100644
index 0000000..8f8f1de
--- /dev/null
+++ b/usr.sbin/nandtool/nandtool.8
@@ -0,0 +1,184 @@
+.\" 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 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 10, 2012
+.Dt NANDTOOL 8
+.Os
+.Sh NAME
+.Nm nandtool
+.Nd NAND devices swiss army knife
+.Sh SYNOPSIS
+.Nm
+.Ar command
+.Op Ar operands ...
+.Sh DESCRIPTION
+The
+.Nm
+utility can be used to perform various operations on
+.Xr gnand 4
+devices (read, write, erase,
+read and write OOB area and to get info about NAND flash chip).
+.Pp
+The following commands are available:
+.Bl -tag -width ".Cm of Ns = Ns Ar file"
+.It Cm read Ns
+Read pages from NAND device.
+.It Cm write Ns
+Write pages to NAND device.
+.It Cm erase Ns
+Erase blocks.
+Requires offset aligned to block granularity.
+.It Cm info Ns
+Get information about NAND chip (page size, block size, OOB area size, chip size
+and media size)
+.It Cm readoob Ns
+Read OOB area from specified page.
+.It Cm writeoob Ns
+Write OOB area bound to specified page.
+.It Cm help Ns
+Get usage info.
+.El
+.Sh COMMAND read
+The following operands are available for
+.Nm
+.Cm read
+command:
+.Bl -tag -width ".Cm of Ns = Ns Ar file"
+.It Cm dev Ns = Ns Ar <path>
+Path to a
+.Xr gnand 4
+device node, required for all operations.
+.It Cm out Ns = Ns Ar <file>
+Output file path. If not specified, page contents
+will be dumped to stdout in format similar to
+.Xr hexdump 1
+.It Cm page Ns = Ns Ar <n>
+Offset on device, expressed as page number.
+.It Cm block Ns = Ns Ar <n>
+Offset on device, expressed as block number.
+.It Cm pos Ns = Ns Ar <n>
+Offset on device, expressed in bytes (however, must be aligned
+to page granularity).
+.It Cm count Ns = Ns Ar <n>
+Count of objects (pages, blocks, bytes).
+.El
+.Sh COMMAND readoob
+The following operands are available for
+.Nm
+.Cm readoob
+command:
+.Bl -tag -width ".Cm of Ns = Ns Ar file"
+.It Cm dev Ns = Ns Ar <path>
+Path to NAND device node.
+.It Cm page Ns = Ns Ar <n>
+Offset on device, expressed as page number.
+.It Cm out Ns = Ns Ar <file>
+Output file path, optional.
+.El
+.Sh COMMAND write
+The following operands are available for
+.Nm
+.Cm write
+command:
+.Bl -tag -width ".Cm of Ns = Ns Ar file"
+.It Cm dev Ns = Ns Ar <path>
+Path to NAND device node.
+.It Cm page Ns = Ns Ar <n>
+Offset on device, expressed as page number.
+.It Cm block Ns = Ns Ar <n>
+Offset on device, expressed as block number.
+.It Cm pos Ns = Ns Ar <n>
+Offset on device, expressed in bytes (however, must be aligned
+to page granularity).
+.It Cm in Ns = Ns Ar <file>
+Input file path.
+.El
+.Sh COMMAND writeoob
+The following operands are available for
+.Nm
+.Cm writeoob
+command:
+.Bl -tag -width ".Cm of Ns = Ns Ar file"
+.It Cm dev Ns = Ns Ar <path>
+Path to NAND device node.
+.It Cm page Ns = Ns Ar <n>
+Offset on device, expressed as page number.
+.It Cm in Ns = Ns Ar <file>
+Input file path.
+.El
+.Sh COMMAND erase
+The following operands are available for
+.Nm
+.Cm erase
+command:
+.Bl -tag -width ".Cm of Ns = Ns Ar file"
+.It Cm dev Ns = Ns Ar <path>
+Path to NAND device node.
+.It Cm page Ns = Ns Ar <n>
+Offset on device, expressed as page number.
+.It Cm block Ns = Ns Ar <n>
+Offset on device, expressed as block number.
+.It Cm pos Ns = Ns Ar <n>
+Offset on device, epressed in bytes (however, must be aligned
+to block granularity).
+.It Cm count Ns = Ns Ar <n>
+Count of objects (pages, blocks, bytes).
+.El
+.Pp
+WARNING: The only required parameter for the \fBerase\fP command is
+.Ar dev .
+When no other arguments are provided the whole device is erased!
+.Sh COMMAND info
+There is only one operand available for
+.Nm
+.Cm info
+command:
+.Bl -tag -width ".Cm of Ns = Ns Ar file"
+.It Cm dev Ns = Ns Ar <path>
+Path to NAND device node.
+.El
+.Sh COMMAND help
+There is only one operand available for
+.Nm
+.Cm help
+command:
+.Bl -tag -width ".Cm of Ns = Ns Ar file"
+.It Cm topic Ns = Ns Ar <name>
+Help topic.
+.El
+.Sh EXIT STATUS
+.Ex -std
+If the supplied argument
+.Ar dev
+points to a device node other than gnand<num> or gnand.raw<num> both
+.Nm
+.Cm readoob
+and
+.Nm
+.Cm writeoob
+return error.
+.Sh SEE ALSO
+.Xr gnand 4
diff --git a/usr.sbin/nandtool/nandtool.c b/usr.sbin/nandtool/nandtool.c
new file mode 100644
index 0000000..bd7075b
--- /dev/null
+++ b/usr.sbin/nandtool/nandtool.c
@@ -0,0 +1,283 @@
+/*-
+ * 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 <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <sysexits.h>
+#include <libgeom.h>
+#include "nandtool.h"
+#include "usage.h"
+
+int usage(struct cmd_param *);
+
+static const struct {
+ const char *name;
+ const char *usage;
+ int (*handler)(struct cmd_param *);
+} commands[] = {
+ { "help", nand_help_usage, usage },
+ { "read", nand_read_usage, nand_read },
+ { "write", nand_write_usage, nand_write },
+ { "erase", nand_erase_usage, nand_erase },
+ { "readoob", nand_read_oob_usage, nand_read_oob },
+ { "writeoob", nand_write_oob_usage, nand_write_oob },
+ { "info", nand_info_usage, nand_info },
+ { NULL, NULL, NULL },
+};
+
+static char *
+_param_get_stringx(struct cmd_param *params, const char *name, int doexit)
+{
+ int i;
+
+ for (i = 0; params[i].name[0] != '\0'; i++) {
+ if (!strcmp(params[i].name, name))
+ return params[i].value;
+ }
+
+ if (doexit) {
+ perrorf("Missing parameter %s", name);
+ exit(1);
+ }
+ return (NULL);
+}
+
+char *
+param_get_string(struct cmd_param *params, const char *name)
+{
+
+ return (_param_get_stringx(params, name, 0));
+}
+
+static int
+_param_get_intx(struct cmd_param *params, const char *name, int doexit)
+{
+ int ret;
+ char *str = _param_get_stringx(params, name, doexit);
+
+ if (!str)
+ return (-1);
+
+ errno = 0;
+ ret = (int)strtol(str, (char **)NULL, 10);
+ if (errno) {
+ if (doexit) {
+ perrorf("Invalid value for parameter %s", name);
+ exit(1);
+ }
+ return (-1);
+ }
+
+ return (ret);
+}
+
+int
+param_get_intx(struct cmd_param *params, const char *name)
+{
+
+ return (_param_get_intx(params, name, 1));
+}
+
+int
+param_get_int(struct cmd_param *params, const char *name)
+{
+
+ return (_param_get_intx(params, name, 0));
+}
+
+int
+param_get_boolean(struct cmd_param *params, const char *name)
+{
+ char *str = param_get_string(params, name);
+
+ if (!str)
+ return (0);
+
+ if (!strcmp(str, "true") || !strcmp(str, "yes"))
+ return (1);
+
+ return (0);
+}
+
+int
+param_has_value(struct cmd_param *params, const char *name)
+{
+ int i;
+
+ for (i = 0; params[i].name[0] != '\0'; i++) {
+ if (!strcmp(params[i].name, name))
+ return (1);
+ }
+
+ return (0);
+}
+
+int
+param_get_count(struct cmd_param *params)
+{
+ int i;
+
+ for (i = 0; params[i].name[0] != '\0'; i++);
+
+ return (i);
+}
+
+void
+hexdumpoffset(uint8_t *buf, int length, int off)
+{
+ int i, j;
+ for (i = 0; i < length; i += 16) {
+ printf("%08x: ", off + i);
+
+ for (j = 0; j < 16; j++)
+ printf("%02x ", buf[i+j]);
+
+ printf("| ");
+
+ for (j = 0; j < 16; j++) {
+ printf("%c", isalnum(buf[i+j])
+ ? buf[i+j]
+ : '.');
+ }
+
+ printf("\n");
+ }
+}
+
+void
+hexdump(uint8_t *buf, int length)
+{
+
+ hexdumpoffset(buf, length, 0);
+}
+
+void *
+xmalloc(size_t len)
+{
+ void *ret = malloc(len);
+
+ if (!ret) {
+ fprintf(stderr, "Cannot allocate buffer of %zd bytes. "
+ "Exiting.\n", len);
+ exit(EX_OSERR);
+ }
+
+ return (ret);
+}
+
+void
+perrorf(const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+ fprintf(stderr, ": %s\n", strerror(errno));
+}
+
+int
+usage(struct cmd_param *params)
+{
+ int i;
+
+ if (!params || !param_get_count(params)) {
+ fprintf(stderr, "Usage: nandtool <command> [arguments...]\n");
+ fprintf(stderr, "Arguments are in form 'name=value'.\n\n");
+ fprintf(stderr, "Available commands:\n");
+
+ for (i = 0; commands[i].name != NULL; i++)
+ fprintf(stderr, "\t%s\n", commands[i].name);
+
+ fprintf(stderr, "\n");
+ fprintf(stderr, "For information about particular command, "
+ "type:\n");
+ fprintf(stderr, "'nandtool help topic=<command>'\n");
+ } else if (param_has_value(params, "topic")) {
+ for (i = 0; commands[i].name != NULL; i++) {
+ if (!strcmp(param_get_string(params, "topic"),
+ commands[i].name)) {
+ fprintf(stderr, commands[i].usage, "nandtool");
+ return (0);
+ }
+ }
+
+ fprintf(stderr, "No such command\n");
+ return (EX_SOFTWARE);
+ } else {
+ fprintf(stderr, "Wrong arguments given. Try: 'nandtool help'\n");
+ }
+
+ return (EX_USAGE);
+}
+
+int
+main(int argc, const char *argv[])
+{
+ struct cmd_param *params;
+ int i, ret, idx;
+
+ if (argc < 2) {
+ usage(NULL);
+ return (0);
+ }
+
+ params = malloc(sizeof(struct cmd_param) * (argc - 1));
+
+ for (i = 2, idx = 0; i < argc; i++, idx++) {
+ if (sscanf(argv[i], "%63[^=]=%63s", params[idx].name,
+ params[idx].value) < 2) {
+ fprintf(stderr, "Syntax error in argument %d. "
+ "Argument should be in form 'name=value'.\n", i);
+ free(params);
+ return (-1);
+ }
+ }
+
+ params[idx].name[0] = '\0';
+ params[idx].value[0] = '\0';
+
+ for (i = 0; commands[i].name != NULL; i++) {
+ if (!strcmp(commands[i].name, argv[1])) {
+ ret = commands[i].handler(params);
+ free(params);
+ return (ret);
+ }
+ }
+
+ free(params);
+ fprintf(stderr, "Unknown command. Try '%s help'\n", argv[0]);
+
+ return (-1);
+}
+
diff --git a/usr.sbin/nandtool/nandtool.h b/usr.sbin/nandtool/nandtool.h
new file mode 100644
index 0000000..639f95e
--- /dev/null
+++ b/usr.sbin/nandtool/nandtool.h
@@ -0,0 +1,57 @@
+/*-
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef __UTILS_H
+#define __UTILS_H
+
+struct cmd_param
+{
+ char name[64];
+ char value[64];
+};
+
+char *param_get_string(struct cmd_param *, const char *);
+int param_get_int(struct cmd_param *, const char *);
+int param_get_intx(struct cmd_param *, const char *);
+int param_get_boolean(struct cmd_param *, const char *);
+int param_has_value(struct cmd_param *, const char *);
+int param_get_count(struct cmd_param *);
+void perrorf(const char *, ...);
+void hexdumpoffset(uint8_t *, int, int);
+void hexdump(uint8_t *, int);
+void *xmalloc(size_t);
+
+/* Command handlers */
+int nand_read(struct cmd_param *);
+int nand_write(struct cmd_param *);
+int nand_read_oob(struct cmd_param *);
+int nand_write_oob(struct cmd_param *);
+int nand_erase(struct cmd_param *);
+int nand_info(struct cmd_param *);
+
+#endif /* __UTILS_H */
diff --git a/usr.sbin/nandtool/usage.h b/usr.sbin/nandtool/usage.h
new file mode 100644
index 0000000..74e8543
--- /dev/null
+++ b/usr.sbin/nandtool/usage.h
@@ -0,0 +1,112 @@
+/*-
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef __USAGE_H
+#define __USAGE_H
+
+static const char nand_help_usage[] =
+ "Usage: %s help topic=<cmd>\n"
+ "\n"
+ "Arguments:\n"
+ "\tcmd\t- [help|read|write|erase|readoob|writeoob|info]\n"
+ "\n";
+
+static const char nand_read_usage[] =
+ "Usage: %s read dev=<gnand_device> (block|page|pos)=n [count=n]\n"
+ "\n"
+ "Arguments:\n"
+ "\tdev\t- path to gnand device node\n"
+ "\tblock\t- starting block or\n"
+ "\tpage\t- starting page or\n"
+ "\tpos\t- starting position (in bytes, must be page-aligned)\n"
+ "\tout\t- output file (hexdump to stdout if not supplied)\n"
+ "\n"
+ "Note that you can only specify only one of: 'block', 'page', 'pos'\n"
+ "parameters at once. 'count' parameter is meaningful in terms of used\n"
+ "unit (page, block or byte).\n";
+
+static const char nand_write_usage[] =
+ "Usage: %s write dev=<gnand_device> in=<file> (block|page|pos)=n [count=n]\n"
+ "\n"
+ "Arguments:\n"
+ "\tdev\t- path to gnand device node\n"
+ "\tin\t- path to input file which be writed to gnand\n"
+ "\tblock\t- starting block or\n"
+ "\tpage\t- starting page or\n"
+ "\tpos\t- starting position (in bytes, must be page-aligned)\n"
+ "\tcount\t- byte/page/block count\n"
+ "\n"
+ "";
+
+static const char nand_erase_usage[] =
+ "Usage: %s erase dev=<gnand_device> (block|page|pos)=n [count=n]\n"
+ "\n"
+ "Arguments:\n"
+ "\tdev\t- path to gnand device node\n"
+ "\tblock\t- starting block or\n"
+ "\tpage\t- starting page or\n"
+ "\tpos\t- starting position (in bytes, muse be block-aligned)\n"
+ "\tcount\t- byte/page/block count\n"
+ "\n"
+ "NOTE: position and count for erase operation MUST be block-aligned\n";
+
+static const char nand_read_oob_usage[] =
+ "Usage: %s readoob dev=<gnand_device> page=n [out=file] [count=n]\n"
+ "\n"
+ "Arguments:\n"
+ "\tdev\t- path to gnand device node\n"
+ "\tpage\t- page (page) number\n"
+ "\tout\t- outut file (hexdump to stdout if not supplied)\n"
+ "\tcount\t- page count (default is 1)\n"
+ "\n"
+ "If you supply count parameter with value other than 1, data will be\n"
+ "read from subsequent page's OOB areas\n";
+
+static const char nand_write_oob_usage[] =
+ "Usage: %s writeoob dev=<gnand_device> in=<file> page=n [count=n]\n"
+ "\n"
+ "\tdev\t- path to gnand device node\n"
+ "\tin\t- path to file containing data which will be written\n"
+ "\tpage\t- page (page) number\n"
+ "\n"
+ "If you supply count parameter with value other than 1, data will be\n"
+ "written to subsequent page's OOB areas\n";
+
+static const char nand_info_usage[] =
+ "Usage: %s info dev=<gnand_device>\n"
+ "\n"
+ "Arguments:\n"
+ "\tdev\t- path to gnand device node\n";
+
+static const char nand_stats_usage[] =
+ "Usage: %s stats dev=<gnand_device> (page|block)=<n>\n"
+ "\n"
+ "Arguments:\n"
+ "\tdev\t- path to gnand device node\n";
+
+#endif /* __USAGE_H */
diff --git a/usr.sbin/ndiscvt/Makefile b/usr.sbin/ndiscvt/Makefile
new file mode 100644
index 0000000..f0facf4
--- /dev/null
+++ b/usr.sbin/ndiscvt/Makefile
@@ -0,0 +1,29 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../sys/compat/ndis
+
+PROG= ndiscvt
+SRCS= ndiscvt.c
+SRCS+= subr_pe.c
+SRCS+= inf.c inf-token.l inf-parse.y y.tab.h
+
+MAN= ndiscvt.8
+MAN+= ndisgen.8
+
+WARNS?= 4
+NO_WCAST_ALIGN=
+
+LIBADD= l
+
+YFLAGS+=-v
+
+CFLAGS+=-I. -I${.CURDIR} -I${.CURDIR}/../../sys
+
+CLEANFILES= y.output
+
+FILES= windrv_stub.c
+FILESDIR= ${SHAREDIR}/misc
+
+SCRIPTS= ndisgen.sh
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ndiscvt/Makefile.depend b/usr.sbin/ndiscvt/Makefile.depend
new file mode 100644
index 0000000..f55c806
--- /dev/null
+++ b/usr.sbin/ndiscvt/Makefile.depend
@@ -0,0 +1,26 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ usr.bin/lex/lib \
+ usr.bin/yacc.host \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+inf-parse.o: inf-parse.c
+inf-parse.po: inf-parse.c
+inf-token.o: inf-token.c
+inf-token.o: y.tab.h
+inf-token.po: inf-token.c
+inf-token.po: y.tab.h
+.endif
diff --git a/usr.sbin/ndiscvt/inf-parse.y b/usr.sbin/ndiscvt/inf-parse.y
new file mode 100644
index 0000000..2a8876d
--- /dev/null
+++ b/usr.sbin/ndiscvt/inf-parse.y
@@ -0,0 +1,110 @@
+%{
+/*
+ * Copyright (c) 2003
+ * Bill Paul <wpaul@windriver.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 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/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include "inf.h"
+
+extern int yylex (void);
+extern void yyerror(const char *);
+%}
+
+%token EQUALS COMMA EOL
+%token <str> SECTION
+%token <str> STRING
+%token <str> WORD
+
+%union {
+ char *str;
+}
+
+%%
+
+inf_file
+ : inf_list
+ |
+ ;
+
+inf_list
+ : inf
+ | inf_list inf
+ ;
+
+inf
+ : SECTION EOL
+ { section_add($1); }
+ | WORD EQUALS assign EOL
+ { assign_add($1); }
+ | WORD COMMA regkey EOL
+ { regkey_add($1); }
+ | WORD EOL
+ { define_add($1); }
+ | EOL
+ ;
+
+assign
+ : WORD
+ { push_word($1); }
+ | STRING
+ { push_word($1); }
+ | WORD COMMA assign
+ { push_word($1); }
+ | STRING COMMA assign
+ { push_word($1); }
+ | COMMA assign
+ { push_word(NULL); }
+ | COMMA
+ { push_word(NULL); }
+ |
+ ;
+
+regkey
+ : WORD
+ { push_word($1); }
+ | STRING
+ { push_word($1); }
+ | WORD COMMA regkey
+ { push_word($1); }
+ | STRING COMMA regkey
+ { push_word($1); }
+ | COMMA regkey
+ { push_word(NULL); }
+ | COMMA
+ { push_word(NULL); }
+ ;
+%%
diff --git a/usr.sbin/ndiscvt/inf-token.l b/usr.sbin/ndiscvt/inf-token.l
new file mode 100644
index 0000000..3e2a127
--- /dev/null
+++ b/usr.sbin/ndiscvt/inf-token.l
@@ -0,0 +1,131 @@
+%{
+/*
+ * Copyright (c) 2003
+ * Bill Paul <wpaul@windriver.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 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/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <regex.h>
+#include <ctype.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "y.tab.h"
+
+int lineno = 1;
+
+int yylex(void);
+void yyerror(const char *);
+
+static void
+update_lineno(const char *cp)
+{
+ while (*cp)
+ if (*cp++ == '\n')
+ lineno++;
+}
+
+%}
+
+%option nounput
+%option noinput
+
+%%
+
+[ \t]+ ;
+\n { lineno++; return EOL; }
+\r ;
+;.*$ ;
+\/\/.*$ ;
+= { return EQUALS; }
+, { return COMMA; }
+\"(\\\"|[^"]|\"\")*\" {
+ int len = strlen(yytext) - 2;
+ int blen = len + 1;
+ char *walker;
+ int i;
+ update_lineno(yytext);
+ yylval.str = (char *)malloc(blen);
+ if (yylval.str == NULL)
+ goto out;
+ walker = yylval.str;
+ for (i = 1; i <= len; i++) {
+ if (yytext[i] == '\"') {
+ switch (yytext[i + 1]) {
+ case '\"':
+ i++;
+ break;
+ default:
+ break;
+ }
+ }
+ if (yytext[i] == '\\') {
+ switch (yytext[i + 1]) {
+ case '\n':
+ i += 2;
+ while(isspace(yytext[i]))
+ i++;
+ break;
+ case '\"':
+ i++;
+ break;
+ case '(':
+ i++;
+ break;
+ default:
+ break;
+ }
+ }
+ *walker++ = yytext[i];
+ }
+ *walker++ = '\0';
+ out:;
+ return STRING;
+ }
+\[[a-zA-Z0-9%&\{\}\-\.\/_\\\*\ ]+\] {
+ int len = strlen(yytext);
+ yytext[len-1] = '\0';
+ yylval.str = strdup(yytext+1);
+ return SECTION;
+ }
+[a-zA-Z0-9%&\{\}\-\.\/_\\\*]+ {
+ yylval.str = strdup(yytext);
+ return WORD;
+ }
+%%
+
+void
+yyerror(const char *s)
+{
+ errx(1, "line %d: %s%s %s.", lineno, yytext, yytext?":":"", s);
+}
diff --git a/usr.sbin/ndiscvt/inf.c b/usr.sbin/ndiscvt/inf.c
new file mode 100644
index 0000000..4b30da0
--- /dev/null
+++ b/usr.sbin/ndiscvt/inf.c
@@ -0,0 +1,916 @@
+/*
+ * Copyright (c) 2003
+ * Bill Paul <wpaul@windriver.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 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/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include <sys/queue.h>
+
+#include "inf.h"
+
+extern FILE *yyin;
+int yyparse (void);
+
+const char *words[W_MAX]; /* More than we'll need. */
+int idx;
+
+static struct section_head sh;
+static struct reg_head rh;
+static struct assign_head ah;
+
+static char *sstrdup (const char *);
+static struct assign
+ *find_assign (const char *, const char *);
+static struct assign
+ *find_next_assign
+ (struct assign *);
+static struct section
+ *find_section (const char *);
+static void dump_deviceids_pci (void);
+static void dump_deviceids_pcmcia (void);
+static void dump_deviceids_usb (void);
+static void dump_pci_id (const char *);
+static void dump_pcmcia_id (const char *);
+static void dump_usb_id (const char *);
+static void dump_regvals (void);
+static void dump_paramreg (const struct section *,
+ const struct reg *, int);
+
+static FILE *ofp;
+
+int
+inf_parse (FILE *fp, FILE *outfp)
+{
+ TAILQ_INIT(&sh);
+ TAILQ_INIT(&rh);
+ TAILQ_INIT(&ah);
+
+ ofp = outfp;
+ yyin = fp;
+ yyparse();
+
+ dump_deviceids_pci();
+ dump_deviceids_pcmcia();
+ dump_deviceids_usb();
+ fprintf(outfp, "#ifdef NDIS_REGVALS\n");
+ dump_regvals();
+ fprintf(outfp, "#endif /* NDIS_REGVALS */\n");
+
+ return (0);
+}
+
+void
+section_add (const char *s)
+{
+ struct section *sec;
+
+ sec = malloc(sizeof(struct section));
+ bzero(sec, sizeof(struct section));
+ sec->name = s;
+ TAILQ_INSERT_TAIL(&sh, sec, link);
+
+ return;
+}
+
+static struct assign *
+find_assign (const char *s, const char *k)
+{
+ struct assign *assign;
+ char newkey[256];
+
+ /* Deal with string section lookups. */
+
+ if (k != NULL && k[0] == '%') {
+ bzero(newkey, sizeof(newkey));
+ strncpy(newkey, k + 1, strlen(k) - 2);
+ k = newkey;
+ }
+
+ TAILQ_FOREACH(assign, &ah, link) {
+ if (strcasecmp(assign->section->name, s) == 0) {
+ if (k == NULL)
+ return(assign);
+ else
+ if (strcasecmp(assign->key, k) == 0)
+ return(assign);
+ }
+ }
+ return(NULL);
+}
+
+static struct assign *
+find_next_assign (struct assign *a)
+{
+ struct assign *assign;
+
+ TAILQ_FOREACH(assign, &ah, link) {
+ if (assign == a)
+ break;
+ }
+
+ assign = assign->link.tqe_next;
+
+ if (assign == NULL || assign->section != a->section)
+ return(NULL);
+
+ return (assign);
+}
+
+static const char *
+stringcvt(const char *s)
+{
+ struct assign *manf;
+
+ manf = find_assign("strings", s);
+ if (manf == NULL)
+ return(s);
+ return(manf->vals[0]);
+}
+
+struct section *
+find_section (const char *s)
+{
+ struct section *section;
+
+ TAILQ_FOREACH(section, &sh, link) {
+ if (strcasecmp(section->name, s) == 0)
+ return(section);
+ }
+ return(NULL);
+}
+
+static void
+dump_pcmcia_id(const char *s)
+{
+ char *manstr, *devstr;
+ char *p0, *p;
+
+ p0 = __DECONST(char *, s);
+
+ p = strchr(p0, '\\');
+ if (p == NULL)
+ return;
+ p0 = p + 1;
+
+ p = strchr(p0, '-');
+ if (p == NULL)
+ return;
+ *p = '\0';
+
+ manstr = p0;
+
+ /* Convert any underscores to spaces. */
+
+ while (*p0 != '\0') {
+ if (*p0 == '_')
+ *p0 = ' ';
+ p0++;
+ }
+
+ p0 = p + 1;
+ p = strchr(p0, '-');
+ if (p == NULL)
+ return;
+ *p = '\0';
+
+ devstr = p0;
+
+ /* Convert any underscores to spaces. */
+
+ while (*p0 != '\0') {
+ if (*p0 == '_')
+ *p0 = ' ';
+ p0++;
+ }
+
+ fprintf(ofp, "\t\\\n\t{ \"%s\", \"%s\", ", manstr, devstr);
+ return;
+}
+
+static void
+dump_pci_id(const char *s)
+{
+ char *p;
+ char vidstr[7], didstr[7], subsysstr[14];
+
+ p = strcasestr(s, "VEN_");
+ if (p == NULL)
+ return;
+ p += 4;
+ strcpy(vidstr, "0x");
+ strncat(vidstr, p, 4);
+ p = strcasestr(s, "DEV_");
+ if (p == NULL)
+ return;
+ p += 4;
+ strcpy(didstr, "0x");
+ strncat(didstr, p, 4);
+ if (p == NULL)
+ return;
+ p = strcasestr(s, "SUBSYS_");
+ if (p == NULL)
+ strcpy(subsysstr, "0x00000000");
+ else {
+ p += 7;
+ strcpy(subsysstr, "0x");
+ strncat(subsysstr, p, 8);
+ }
+
+ fprintf(ofp, "\t\\\n\t{ %s, %s, %s, ", vidstr, didstr, subsysstr);
+ return;
+}
+
+static void
+dump_usb_id(const char *s)
+{
+ char *p;
+ char vidstr[7], pidstr[7];
+
+ p = strcasestr(s, "VID_");
+ if (p == NULL)
+ return;
+ p += 4;
+ strcpy(vidstr, "0x");
+ strncat(vidstr, p, 4);
+ p = strcasestr(s, "PID_");
+ if (p == NULL)
+ return;
+ p += 4;
+ strcpy(pidstr, "0x");
+ strncat(pidstr, p, 4);
+ if (p == NULL)
+ return;
+
+ fprintf(ofp, "\t\\\n\t{ %s, %s, ", vidstr, pidstr);
+}
+
+static void
+dump_deviceids_pci()
+{
+ struct assign *manf, *dev;
+ struct section *sec;
+ struct assign *assign;
+ char xpsec[256];
+ int first = 1, found = 0;
+
+ /* Find manufacturer name */
+ manf = find_assign("Manufacturer", NULL);
+
+nextmanf:
+
+ /* Find manufacturer section */
+ if (manf->vals[1] != NULL &&
+ (strcasecmp(manf->vals[1], "NT.5.1") == 0 ||
+ strcasecmp(manf->vals[1], "NTx86") == 0 ||
+ strcasecmp(manf->vals[1], "NTx86.5.1") == 0 ||
+ strcasecmp(manf->vals[1], "NTamd64") == 0)) {
+ /* Handle Windows XP INF files. */
+ snprintf(xpsec, sizeof(xpsec), "%s.%s",
+ manf->vals[0], manf->vals[1]);
+ sec = find_section(xpsec);
+ } else
+ sec = find_section(manf->vals[0]);
+
+ /* See if there are any PCI device definitions. */
+
+ TAILQ_FOREACH(assign, &ah, link) {
+ if (assign->section == sec) {
+ dev = find_assign("strings", assign->key);
+ if (strcasestr(assign->vals[1], "PCI") != NULL) {
+ found++;
+ break;
+ }
+ }
+ }
+
+ if (found == 0)
+ goto done;
+
+ found = 0;
+
+ if (first == 1) {
+ /* Emit start of PCI device table */
+ fprintf (ofp, "#define NDIS_PCI_DEV_TABLE");
+ first = 0;
+ }
+
+retry:
+
+ /*
+ * Now run through all the device names listed
+ * in the manufacturer section and dump out the
+ * device descriptions and vendor/device IDs.
+ */
+
+ TAILQ_FOREACH(assign, &ah, link) {
+ if (assign->section == sec) {
+ dev = find_assign("strings", assign->key);
+ /* Emit device IDs. */
+ if (strcasestr(assign->vals[1], "PCI") != NULL)
+ dump_pci_id(assign->vals[1]);
+ else
+ continue;
+ /* Emit device description */
+ fprintf (ofp, "\t\\\n\t\"%s\" },", dev->vals[0]);
+ found++;
+ }
+ }
+
+ /* Someone tried to fool us. Shame on them. */
+ if (!found) {
+ found++;
+ sec = find_section(manf->vals[0]);
+ goto retry;
+ }
+
+ /* Handle Manufacturer sections with multiple entries. */
+ manf = find_next_assign(manf);
+
+ if (manf != NULL)
+ goto nextmanf;
+
+done:
+ /* Emit end of table */
+
+ fprintf(ofp, "\n\n");
+
+ return;
+}
+
+static void
+dump_deviceids_pcmcia()
+{
+ struct assign *manf, *dev;
+ struct section *sec;
+ struct assign *assign;
+ char xpsec[256];
+ int first = 1, found = 0;
+
+ /* Find manufacturer name */
+ manf = find_assign("Manufacturer", NULL);
+
+nextmanf:
+
+ /* Find manufacturer section */
+ if (manf->vals[1] != NULL &&
+ (strcasecmp(manf->vals[1], "NT.5.1") == 0 ||
+ strcasecmp(manf->vals[1], "NTx86") == 0 ||
+ strcasecmp(manf->vals[1], "NTx86.5.1") == 0 ||
+ strcasecmp(manf->vals[1], "NTamd64") == 0)) {
+ /* Handle Windows XP INF files. */
+ snprintf(xpsec, sizeof(xpsec), "%s.%s",
+ manf->vals[0], manf->vals[1]);
+ sec = find_section(xpsec);
+ } else
+ sec = find_section(manf->vals[0]);
+
+ /* See if there are any PCMCIA device definitions. */
+
+ TAILQ_FOREACH(assign, &ah, link) {
+ if (assign->section == sec) {
+ dev = find_assign("strings", assign->key);
+ if (strcasestr(assign->vals[1], "PCMCIA") != NULL) {
+ found++;
+ break;
+ }
+ }
+ }
+
+ if (found == 0)
+ goto done;
+
+ found = 0;
+
+ if (first == 1) {
+ /* Emit start of PCMCIA device table */
+ fprintf (ofp, "#define NDIS_PCMCIA_DEV_TABLE");
+ first = 0;
+ }
+
+retry:
+
+ /*
+ * Now run through all the device names listed
+ * in the manufacturer section and dump out the
+ * device descriptions and vendor/device IDs.
+ */
+
+ TAILQ_FOREACH(assign, &ah, link) {
+ if (assign->section == sec) {
+ dev = find_assign("strings", assign->key);
+ /* Emit device IDs. */
+ if (strcasestr(assign->vals[1], "PCMCIA") != NULL)
+ dump_pcmcia_id(assign->vals[1]);
+ else
+ continue;
+ /* Emit device description */
+ fprintf (ofp, "\t\\\n\t\"%s\" },", dev->vals[0]);
+ found++;
+ }
+ }
+
+ /* Someone tried to fool us. Shame on them. */
+ if (!found) {
+ found++;
+ sec = find_section(manf->vals[0]);
+ goto retry;
+ }
+
+ /* Handle Manufacturer sections with multiple entries. */
+ manf = find_next_assign(manf);
+
+ if (manf != NULL)
+ goto nextmanf;
+
+done:
+ /* Emit end of table */
+
+ fprintf(ofp, "\n\n");
+
+ return;
+}
+
+static void
+dump_deviceids_usb()
+{
+ struct assign *manf, *dev;
+ struct section *sec;
+ struct assign *assign;
+ char xpsec[256];
+ int first = 1, found = 0;
+
+ /* Find manufacturer name */
+ manf = find_assign("Manufacturer", NULL);
+
+nextmanf:
+
+ /* Find manufacturer section */
+ if (manf->vals[1] != NULL &&
+ (strcasecmp(manf->vals[1], "NT.5.1") == 0 ||
+ strcasecmp(manf->vals[1], "NTx86") == 0 ||
+ strcasecmp(manf->vals[1], "NTx86.5.1") == 0 ||
+ strcasecmp(manf->vals[1], "NTamd64") == 0)) {
+ /* Handle Windows XP INF files. */
+ snprintf(xpsec, sizeof(xpsec), "%s.%s",
+ manf->vals[0], manf->vals[1]);
+ sec = find_section(xpsec);
+ } else
+ sec = find_section(manf->vals[0]);
+
+ /* See if there are any USB device definitions. */
+
+ TAILQ_FOREACH(assign, &ah, link) {
+ if (assign->section == sec) {
+ dev = find_assign("strings", assign->key);
+ if (strcasestr(assign->vals[1], "USB") != NULL) {
+ found++;
+ break;
+ }
+ }
+ }
+
+ if (found == 0)
+ goto done;
+
+ found = 0;
+
+ if (first == 1) {
+ /* Emit start of USB device table */
+ fprintf (ofp, "#define NDIS_USB_DEV_TABLE");
+ first = 0;
+ }
+
+retry:
+
+ /*
+ * Now run through all the device names listed
+ * in the manufacturer section and dump out the
+ * device descriptions and vendor/device IDs.
+ */
+
+ TAILQ_FOREACH(assign, &ah, link) {
+ if (assign->section == sec) {
+ dev = find_assign("strings", assign->key);
+ /* Emit device IDs. */
+ if (strcasestr(assign->vals[1], "USB") != NULL)
+ dump_usb_id(assign->vals[1]);
+ else
+ continue;
+ /* Emit device description */
+ fprintf (ofp, "\t\\\n\t\"%s\" },", dev->vals[0]);
+ found++;
+ }
+ }
+
+ /* Someone tried to fool us. Shame on them. */
+ if (!found) {
+ found++;
+ sec = find_section(manf->vals[0]);
+ goto retry;
+ }
+
+ /* Handle Manufacturer sections with multiple entries. */
+ manf = find_next_assign(manf);
+
+ if (manf != NULL)
+ goto nextmanf;
+
+done:
+ /* Emit end of table */
+
+ fprintf(ofp, "\n\n");
+
+ return;
+}
+
+static void
+dump_addreg(const char *s, int devidx)
+{
+ struct section *sec;
+ struct reg *reg;
+
+ /* Find the addreg section */
+ sec = find_section(s);
+
+ /* Dump all the keys defined in it. */
+ TAILQ_FOREACH(reg, &rh, link) {
+ /*
+ * Keys with an empty subkey are very easy to parse,
+ * so just deal with them here. If a parameter key
+ * of the same name also exists, prefer that one and
+ * skip this one.
+ */
+ if (reg->section == sec) {
+ if (reg->subkey == NULL) {
+ fprintf(ofp, "\n\t{ \"%s\",", reg->key);
+ fprintf(ofp,"\n\t\"%s \",", reg->key);
+ fprintf(ofp, "\n\t{ \"%s\" }, %d },",
+ reg->value == NULL ? "" :
+ stringcvt(reg->value), devidx);
+ } else if (strncasecmp(reg->subkey,
+ "Ndi\\params", strlen("Ndi\\params")-1) == 0 &&
+ (reg->key != NULL && strcasecmp(reg->key,
+ "ParamDesc") == 0))
+ dump_paramreg(sec, reg, devidx);
+ }
+ }
+
+ return;
+}
+
+static void
+dump_enumreg(const struct section *s, const struct reg *r)
+{
+ struct reg *reg;
+ char enumkey[256];
+
+ sprintf(enumkey, "%s\\enum", r->subkey);
+ TAILQ_FOREACH(reg, &rh, link) {
+ if (reg->section != s)
+ continue;
+ if (reg->subkey == NULL || strcasecmp(reg->subkey, enumkey))
+ continue;
+ fprintf(ofp, " [%s=%s]", reg->key,
+ stringcvt(reg->value));
+ }
+ return;
+}
+
+static void
+dump_editreg(const struct section *s, const struct reg *r)
+{
+ struct reg *reg;
+
+ TAILQ_FOREACH(reg, &rh, link) {
+ if (reg->section != s)
+ continue;
+ if (reg->subkey == NULL || strcasecmp(reg->subkey, r->subkey))
+ continue;
+ if (reg->key == NULL)
+ continue;
+ if (strcasecmp(reg->key, "LimitText") == 0)
+ fprintf(ofp, " [maxchars=%s]", reg->value);
+ if (strcasecmp(reg->key, "Optional") == 0 &&
+ strcmp(reg->value, "1") == 0)
+ fprintf(ofp, " [optional]");
+ }
+ return;
+}
+
+/* Use this for int too */
+static void
+dump_dwordreg(const struct section *s, const struct reg *r)
+{
+ struct reg *reg;
+
+ TAILQ_FOREACH(reg, &rh, link) {
+ if (reg->section != s)
+ continue;
+ if (reg->subkey == NULL || strcasecmp(reg->subkey, r->subkey))
+ continue;
+ if (reg->key == NULL)
+ continue;
+ if (strcasecmp(reg->key, "min") == 0)
+ fprintf(ofp, " [min=%s]", reg->value);
+ if (strcasecmp(reg->key, "max") == 0)
+ fprintf(ofp, " [max=%s]", reg->value);
+ }
+ return;
+}
+
+static void
+dump_defaultinfo(const struct section *s, const struct reg *r, int devidx)
+{
+ struct reg *reg;
+ TAILQ_FOREACH(reg, &rh, link) {
+ if (reg->section != s)
+ continue;
+ if (reg->subkey == NULL || strcasecmp(reg->subkey, r->subkey))
+ continue;
+ if (reg->key == NULL || strcasecmp(reg->key, "Default"))
+ continue;
+ fprintf(ofp, "\n\t{ \"%s\" }, %d },", reg->value == NULL ? "" :
+ stringcvt(reg->value), devidx);
+ return;
+ }
+ /* Default registry entry missing */
+ fprintf(ofp, "\n\t{ \"\" }, %d },", devidx);
+ return;
+}
+
+static void
+dump_paramdesc(const struct section *s, const struct reg *r)
+{
+ struct reg *reg;
+ TAILQ_FOREACH(reg, &rh, link) {
+ if (reg->section != s)
+ continue;
+ if (reg->subkey == NULL || strcasecmp(reg->subkey, r->subkey))
+ continue;
+ if (reg->key == NULL || strcasecmp(reg->key, "ParamDesc"))
+ continue;
+ fprintf(ofp, "\n\t\"%s", stringcvt(r->value));
+ break;
+ }
+ return;
+}
+
+static void
+dump_typeinfo(const struct section *s, const struct reg *r)
+{
+ struct reg *reg;
+ TAILQ_FOREACH(reg, &rh, link) {
+ if (reg->section != s)
+ continue;
+ if (reg->subkey == NULL || strcasecmp(reg->subkey, r->subkey))
+ continue;
+ if (reg->key == NULL)
+ continue;
+ if (strcasecmp(reg->key, "type"))
+ continue;
+ if (strcasecmp(reg->value, "dword") == 0 ||
+ strcasecmp(reg->value, "int") == 0)
+ dump_dwordreg(s, r);
+ if (strcasecmp(reg->value, "enum") == 0)
+ dump_enumreg(s, r);
+ if (strcasecmp(reg->value, "edit") == 0)
+ dump_editreg(s, r);
+ }
+ return;
+}
+
+static void
+dump_paramreg(const struct section *s, const struct reg *r, int devidx)
+{
+ const char *keyname;
+
+ keyname = r->subkey + strlen("Ndi\\params\\");
+ fprintf(ofp, "\n\t{ \"%s\",", keyname);
+ dump_paramdesc(s, r);
+ dump_typeinfo(s, r);
+ fprintf(ofp, "\",");
+ dump_defaultinfo(s, r, devidx);
+
+ return;
+}
+
+static void
+dump_regvals(void)
+{
+ struct assign *manf, *dev;
+ struct section *sec;
+ struct assign *assign;
+ char sname[256];
+ int found = 0, i, is_winxp = 0, is_winnt = 0, devidx = 0;
+
+ /* Find signature to check for special case of WinNT. */
+ assign = find_assign("version", "signature");
+ if (strcasecmp(assign->vals[0], "$windows nt$") == 0)
+ is_winnt++;
+
+ /* Emit start of block */
+ fprintf (ofp, "ndis_cfg ndis_regvals[] = {");
+
+ /* Find manufacturer name */
+ manf = find_assign("Manufacturer", NULL);
+
+nextmanf:
+
+ /* Find manufacturer section */
+ if (manf->vals[1] != NULL &&
+ (strcasecmp(manf->vals[1], "NT.5.1") == 0 ||
+ strcasecmp(manf->vals[1], "NTx86") == 0 ||
+ strcasecmp(manf->vals[1], "NTx86.5.1") == 0 ||
+ strcasecmp(manf->vals[1], "NTamd64") == 0)) {
+ is_winxp++;
+ /* Handle Windows XP INF files. */
+ snprintf(sname, sizeof(sname), "%s.%s",
+ manf->vals[0], manf->vals[1]);
+ sec = find_section(sname);
+ } else
+ sec = find_section(manf->vals[0]);
+
+retry:
+
+ TAILQ_FOREACH(assign, &ah, link) {
+ if (assign->section == sec) {
+ found++;
+ /*
+ * Find all the AddReg sections.
+ * Look for section names with .NT, unless
+ * this is a WinXP .INF file.
+ */
+
+ if (is_winxp) {
+ sprintf(sname, "%s.NTx86", assign->vals[0]);
+ dev = find_assign(sname, "AddReg");
+ if (dev == NULL) {
+ sprintf(sname, "%s.NT",
+ assign->vals[0]);
+ dev = find_assign(sname, "AddReg");
+ }
+ if (dev == NULL)
+ dev = find_assign(assign->vals[0],
+ "AddReg");
+ } else {
+ sprintf(sname, "%s.NT", assign->vals[0]);
+ dev = find_assign(sname, "AddReg");
+ if (dev == NULL && is_winnt)
+ dev = find_assign(assign->vals[0],
+ "AddReg");
+ }
+ /* Section not found. */
+ if (dev == NULL)
+ continue;
+ for (i = 0; i < W_MAX; i++) {
+ if (dev->vals[i] != NULL)
+ dump_addreg(dev->vals[i], devidx);
+ }
+ devidx++;
+ }
+ }
+
+ if (!found) {
+ sec = find_section(manf->vals[0]);
+ is_winxp = 0;
+ found++;
+ goto retry;
+ }
+
+ manf = find_next_assign(manf);
+
+ if (manf != NULL)
+ goto nextmanf;
+
+ fprintf(ofp, "\n\t{ NULL, NULL, { 0 }, 0 }\n};\n\n");
+
+ return;
+}
+
+void
+assign_add (const char *a)
+{
+ struct assign *assign;
+ int i;
+
+ assign = malloc(sizeof(struct assign));
+ bzero(assign, sizeof(struct assign));
+ assign->section = TAILQ_LAST(&sh, section_head);
+ assign->key = sstrdup(a);
+ for (i = 0; i < idx; i++)
+ assign->vals[(idx - 1) - i] = sstrdup(words[i]);
+ TAILQ_INSERT_TAIL(&ah, assign, link);
+
+ clear_words();
+ return;
+}
+
+void
+define_add (const char *d __unused)
+{
+#ifdef notdef
+ fprintf(stderr, "define \"%s\"\n", d);
+#endif
+ return;
+}
+
+static char *
+sstrdup(const char *str)
+{
+ if (str != NULL && strlen(str))
+ return (strdup(str));
+ return (NULL);
+}
+
+static int
+satoi (const char *nptr)
+{
+ if (nptr != NULL && strlen(nptr))
+ return (atoi(nptr));
+ return (0);
+}
+
+void
+regkey_add (const char *r)
+{
+ struct reg *reg;
+
+ reg = malloc(sizeof(struct reg));
+ bzero(reg, sizeof(struct reg));
+ reg->section = TAILQ_LAST(&sh, section_head);
+ reg->root = sstrdup(r);
+ reg->subkey = sstrdup(words[3]);
+ reg->key = sstrdup(words[2]);
+ reg->flags = satoi(words[1]);
+ reg->value = sstrdup(words[0]);
+ TAILQ_INSERT_TAIL(&rh, reg, link);
+
+ free(__DECONST(char *, r));
+ clear_words();
+ return;
+}
+
+void
+push_word (const char *w)
+{
+
+ if (idx == W_MAX) {
+ fprintf(stderr, "too many words; try bumping W_MAX in inf.h\n");
+ exit(1);
+ }
+
+ if (w && strlen(w))
+ words[idx++] = w;
+ else
+ words[idx++] = NULL;
+ return;
+}
+
+void
+clear_words (void)
+{
+ int i;
+
+ for (i = 0; i < idx; i++) {
+ if (words[i]) {
+ free(__DECONST(char *, words[i]));
+ }
+ }
+ idx = 0;
+ bzero(words, sizeof(words));
+ return;
+}
diff --git a/usr.sbin/ndiscvt/inf.h b/usr.sbin/ndiscvt/inf.h
new file mode 100644
index 0000000..ba08d67
--- /dev/null
+++ b/usr.sbin/ndiscvt/inf.h
@@ -0,0 +1,61 @@
+/*
+ * $Id: inf.h,v 1.3 2003/11/30 21:58:16 winter Exp $
+ *
+ * $FreeBSD$
+ */
+
+#define W_MAX 32
+
+struct section {
+ const char * name;
+
+ TAILQ_ENTRY(section) link;
+};
+TAILQ_HEAD(section_head, section);
+
+struct assign {
+ struct section *section;
+
+ const char * key;
+ const char * vals[W_MAX];
+
+ TAILQ_ENTRY(assign) link;
+};
+TAILQ_HEAD(assign_head, assign);
+
+struct reg {
+ struct section *section;
+
+ const char * root;
+ const char * subkey;
+ const char * key;
+ u_int flags;
+ const char * value;
+
+ TAILQ_ENTRY(reg) link;
+};
+TAILQ_HEAD(reg_head, reg);
+
+#define FLG_ADDREG_TYPE_SZ 0x00000000
+#define FLG_ADDREG_BINVALUETYPE 0x00000001
+#define FLG_ADDREG_NOCLOBBER 0x00000002
+#define FLG_ADDREG_DELVAL 0x00000004
+#define FLG_ADDREG_APPEND 0x00000008
+#define FLG_ADDREG_KEYONLY 0x00000010
+#define FLG_ADDREG_OVERWRITEONLY 0x00000020
+#define FLG_ADDREG_64BITKEY 0x00001000
+#define FLG_ADDREG_KEYONLY_COMMON 0x00002000
+#define FLG_ADDREG_32BITKEY 0x00004000
+#define FLG_ADDREG_TYPE_MULTI_SZ 0x00010000
+#define FLG_ADDREG_TYPE_EXPAND_SZ 0x00020000
+#define FLG_ADDREG_TYPE_DWORD 0x00010001
+#define FLG_ADDREG_TYPE_NONE 0x00020001
+
+extern void section_add (const char *);
+extern void assign_add (const char *);
+extern void define_add (const char *);
+extern void regkey_add (const char *);
+
+extern void push_word (const char *);
+extern void clear_words (void);
+extern int inf_parse (FILE *, FILE *);
diff --git a/usr.sbin/ndiscvt/ndiscvt.8 b/usr.sbin/ndiscvt/ndiscvt.8
new file mode 100644
index 0000000..f497495
--- /dev/null
+++ b/usr.sbin/ndiscvt/ndiscvt.8
@@ -0,0 +1,283 @@
+.\" Copyright (c) 2003
+.\" Bill Paul <wpaul@windriver.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 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd December 10, 2003
+.Dt NDISCVT 8
+.Os
+.Sh NAME
+.Nm ndiscvt
+.Nd convert
+.Tn Windows\[rg]
+NDIS drivers for use with FreeBSD
+.Sh SYNOPSIS
+.Nm
+.Op Fl O
+.Op Fl i Ar inffile
+.Fl s Ar sysfile
+.Op Fl n Ar devname
+.Op Fl o Ar outfile
+.Nm
+.Op Fl f Ar firmfile
+.Sh DESCRIPTION
+The
+.Nm
+utility transforms a
+.Tn Windows\[rg]
+NDIS driver into a data file which
+is used to build an
+.Xr ndis 4
+compatibility driver module.
+.Tn Windows\[rg]
+drivers consist of two main parts: a
+.Pa .SYS
+file, which contains the actual driver executable code,
+and an
+.Pa .INF
+file, which provides the
+.Tn Windows\[rg]
+installer with device
+identifier information and a list of driver-specific registry keys.
+The
+.Nm
+utility can convert these files into a header file that is compiled
+into
+.Pa if_ndis.c
+to create an object code module that can be linked into
+the
+.Fx
+kernel.
+.Pp
+The
+.Pa .INF
+file is typically required since only it contains device
+identification data such as PCI vendor and device IDs or PCMCIA
+identifier strings.
+The
+.Pa .INF
+file may be optionally omitted however,
+in which case the
+.Nm
+utility will only perform the conversion of the
+.Pa .SYS
+file.
+This is useful for debugging purposes only.
+.Sh OPTIONS
+The options are as follows:
+.Bl -tag -width indent
+.It Fl i Ar inffile
+Open and parse the specified
+.Pa .INF
+file when performing conversion.
+The
+.Nm
+utility will parse this file and emit a device identification
+structure and registry key configuration structures which will be
+used by the
+.Xr ndis 4
+driver and
+.Xr ndisapi 9
+kernel subsystem.
+If this is omitted,
+.Nm
+will emit a dummy configuration structure only.
+.It Fl s Ar sysfile
+Open and parse the specified
+.Pa .SYS
+file.
+This file must contain a
+.Tn Windows\[rg]
+driver image.
+The
+.Nm
+utility will perform some manipulation of the sections within the
+executable file to make runtime linking within the kernel a little
+easier and then convert the image into a data array.
+.It Fl n Ar devname
+Specify an alternate name for the network device/interface which will
+be created when the driver is instantiated.
+If you need to load more
+than one NDIS driver into your system (i.e., if you have two different
+network cards in your system which require NDIS driver support), each
+module you create must have a unique name.
+Device can not be larger than
+.Dv IFNAMSIZ .
+If no name is specified, the driver will use the
+default a default name
+.Pq Dq Li ndis .
+.It Fl o Ar outfile
+Specify the output file in which to place the resulting data.
+This can be any file pathname.
+If
+.Ar outfile
+is a single dash
+.Pq Sq Fl ,
+the data will be written to the standard output.
+The
+.Pa if_ndis.c
+module expects to find the driver data in a file called
+.Pa ndis_driver_data.h ,
+so it is recommended that this name be used.
+.It Fl O
+Generate both an
+.Pa ndis_driver_data.h
+file and
+an
+.Pa ndis_driver.data.o
+file.
+The latter file will contain a copy of the
+.Tn Windows\[rg]
+.Pa .SYS
+driver image encoded as a
+.Fx
+ELF object file
+(created with
+.Xr objcopy 1 ) .
+Turning the
+.Tn Windows\[rg]
+driver image directly into an object code file saves disk space
+and compilation time.
+.It Fl f Ar firmfile
+A few NDIS drivers come with additional files that the core
+driver module will load during initialization time.
+Typically,
+these files contain firmware which the driver will transfer to
+the device in order to make it fully operational.
+In
+.Tn Windows\[rg] ,
+these files are usually just copied into one of the system
+directories along with the driver itself.
+.Pp
+In
+.Fx
+there are two mechanism for loading these files.
+If the driver
+is built as a loadable kernel module which is loaded after the
+kernel has finished booting
+(and after the root file system has
+been mounted),
+the extra files can simply be copied to the
+.Pa /compat/ndis
+directory, and they will be loaded into the kernel on demand when the
+driver needs them.
+.Pp
+If however the driver is required to bootstrap the system
+(i.e., if
+the NDIS-based network interface is to be used for diskless/PXE
+booting),
+the files need to be pre-loaded by the bootstrap
+loader in order to be accessible, since the driver will need them
+before the root file system has been mounted.
+However, the bootstrap
+loader is only able to load files that are shared
+.Fx
+binary objects.
+.Pp
+The
+.Fl f
+flag can be used to convert an arbitrary file
+.Ar firmfile
+into shared object format
+(the actual conversion is done using
+the
+.Xr objcopy 1
+and
+.Xr ld 1
+commands).
+The resulting files can then be copied to the
+.Pa /boot/kernel
+directory, and can be pre-loaded directly from the boot loader
+prompt, or automatically by editing the
+.Xr loader.conf 5
+file.
+If desired, the files can also be loaded into memory
+at runtime using the
+.Xr kldload 8
+command.
+.Pp
+When an NDIS driver tries to open an external file, the
+.Xr ndisapi 9
+code will first search for a loaded kernel module that matches the
+name specified in the open request, and if that fails, it will then
+try to open the file from the
+.Pa /compat/ndis
+directory as well.
+Note that during kernel bootstrap, the ability
+to open files from
+.Pa /compat/ndis
+is disabled: only the module search will be performed.
+.Pp
+When using the
+.Fl f
+flag,
+.Nm
+will generate both a relocatable object file
+(with a
+.Pa .o
+extension)
+and a shared object file
+(with a
+.Pa .ko
+extension).
+The shared object is the one that should be placed in
+the
+.Pa /boot/kernel
+directory.
+The relocatable object file is useful if the user wishes
+to create a completely static kernel image: the object file can be
+linked into the kernel directly along with the driver itself.
+Some
+editing of the kernel configuration files will be necessary in order
+to have the extra object included in the build.
+.El
+.Sh SEE ALSO
+.Xr ld 1 ,
+.Xr objcopy 1 ,
+.Xr ndis 4 ,
+.Xr kldload 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 5.3 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility was written by
+.An Bill Paul Aq Mt wpaul@windriver.com .
+The
+.Xr lex 1
+and
+.Xr yacc 1
+.Pa INF
+file parser was written by
+.An Matthew Dodd Aq Mt mdodd@FreeBSD.org .
diff --git a/usr.sbin/ndiscvt/ndiscvt.c b/usr.sbin/ndiscvt/ndiscvt.c
new file mode 100644
index 0000000..7636c4c
--- /dev/null
+++ b/usr.sbin/ndiscvt/ndiscvt.c
@@ -0,0 +1,432 @@
+/*
+ * Copyright (c) 2003
+ * Bill Paul <wpaul@windriver.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 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/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <libgen.h>
+#include <err.h>
+#include <ctype.h>
+
+#include <compat/ndis/pe_var.h>
+
+#include "inf.h"
+
+static int insert_padding(void **, int *);
+extern const char *__progname;
+
+/*
+ * Sections within Windows PE files are defined using virtual
+ * and physical address offsets and virtual and physical sizes.
+ * The physical values define how the section data is stored in
+ * the executable file while the virtual values describe how the
+ * sections will look once loaded into memory. It happens that
+ * the linker in the Microsoft(r) DDK will tend to generate
+ * binaries where the virtual and physical values are identical,
+ * which means in most cases we can just transfer the file
+ * directly to memory without any fixups. This is not always
+ * the case though, so we have to be prepared to handle files
+ * where the in-memory section layout differs from the disk file
+ * section layout.
+ *
+ * There are two kinds of variations that can occur: the relative
+ * virtual address of the section might be different from the
+ * physical file offset, and the virtual section size might be
+ * different from the physical size (for example, the physical
+ * size of the .data section might be 1024 bytes, but the virtual
+ * size might be 1384 bytes, indicating that the data section should
+ * actually use up 1384 bytes in RAM and be padded with zeros). What we
+ * do is read the original file into memory and then make an in-memory
+ * copy with all of the sections relocated, re-sized and zero padded
+ * according to the virtual values specified in the section headers.
+ * We then emit the fixed up image file for use by the if_ndis driver.
+ * This way, we don't have to do the fixups inside the kernel.
+ */
+
+#define ROUND_DOWN(n, align) (((uintptr_t)n) & ~((align) - 1l))
+#define ROUND_UP(n, align) ROUND_DOWN(((uintptr_t)n) + (align) - 1l, \
+ (align))
+
+#define SET_HDRS(x) \
+ dos_hdr = (image_dos_header *)x; \
+ nt_hdr = (image_nt_header *)(x + dos_hdr->idh_lfanew); \
+ sect_hdr = IMAGE_FIRST_SECTION(nt_hdr);
+
+static int
+insert_padding(void **imgbase, int *imglen)
+{
+ image_section_header *sect_hdr;
+ image_dos_header *dos_hdr;
+ image_nt_header *nt_hdr;
+ image_optional_header opt_hdr;
+ int i = 0, sections, curlen = 0;
+ int offaccum = 0, oldraddr, oldrlen;
+ uint8_t *newimg, *tmp;
+
+ newimg = malloc(*imglen);
+
+ if (newimg == NULL)
+ return(ENOMEM);
+
+ bcopy(*imgbase, newimg, *imglen);
+ curlen = *imglen;
+
+ if (pe_get_optional_header((vm_offset_t)newimg, &opt_hdr))
+ return(0);
+
+ sections = pe_numsections((vm_offset_t)newimg);
+
+ SET_HDRS(newimg);
+
+ for (i = 0; i < sections; i++) {
+ oldraddr = sect_hdr->ish_rawdataaddr;
+ oldrlen = sect_hdr->ish_rawdatasize;
+ sect_hdr->ish_rawdataaddr = sect_hdr->ish_vaddr;
+ offaccum += ROUND_UP(sect_hdr->ish_vaddr - oldraddr,
+ opt_hdr.ioh_filealign);
+ offaccum +=
+ ROUND_UP(sect_hdr->ish_misc.ish_vsize,
+ opt_hdr.ioh_filealign) -
+ ROUND_UP(sect_hdr->ish_rawdatasize,
+ opt_hdr.ioh_filealign);
+ tmp = realloc(newimg, *imglen + offaccum);
+ if (tmp == NULL) {
+ free(newimg);
+ return(ENOMEM);
+ }
+ newimg = tmp;
+ SET_HDRS(newimg);
+ sect_hdr += i;
+ bzero(newimg + sect_hdr->ish_rawdataaddr,
+ ROUND_UP(sect_hdr->ish_misc.ish_vsize,
+ opt_hdr.ioh_filealign));
+ bcopy((uint8_t *)(*imgbase) + oldraddr,
+ newimg + sect_hdr->ish_rawdataaddr, oldrlen);
+ sect_hdr++;
+ }
+
+ free(*imgbase);
+
+ *imgbase = newimg;
+ *imglen += offaccum;
+
+ return(0);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: %s [-O] [-i <inffile>] -s <sysfile> "
+ "[-n devname] [-o outfile]\n", __progname);
+ fprintf(stderr, " %s -f <firmfile>\n", __progname);
+
+ exit(1);
+}
+
+static void
+bincvt(char *sysfile, char *outfile, void *img, int fsize)
+{
+ char *ptr;
+ char tname[] = "/tmp/ndiscvt.XXXXXX";
+ char sysbuf[1024];
+ FILE *binfp;
+
+ mkstemp(tname);
+
+ binfp = fopen(tname, "a+");
+ if (binfp == NULL)
+ err(1, "opening %s failed", tname);
+
+ if (fwrite(img, fsize, 1, binfp) != 1)
+ err(1, "failed to output binary image");
+
+ fclose(binfp);
+
+ outfile = strdup(basename(outfile));
+ if (strchr(outfile, '.'))
+ *strchr(outfile, '.') = '\0';
+
+ snprintf(sysbuf, sizeof(sysbuf),
+#ifdef __i386__
+ "objcopy -I binary -O elf32-i386-freebsd -B i386 %s %s.o\n",
+#endif
+#ifdef __amd64__
+ "objcopy -I binary -O elf64-x86-64-freebsd -B i386 %s %s.o\n",
+#endif
+ tname, outfile);
+ printf("%s", sysbuf);
+ system(sysbuf);
+ unlink(tname);
+
+ ptr = tname;
+ while (*ptr) {
+ if (*ptr == '/' || *ptr == '.')
+ *ptr = '_';
+ ptr++;
+ }
+
+ snprintf(sysbuf, sizeof(sysbuf),
+ "objcopy --redefine-sym _binary_%s_start=ndis_%s_drv_data_start "
+ "--strip-symbol _binary_%s_size "
+ "--redefine-sym _binary_%s_end=ndis_%s_drv_data_end %s.o %s.o\n",
+ tname, sysfile, tname, tname, sysfile, outfile, outfile);
+ printf("%s", sysbuf);
+ system(sysbuf);
+
+ return;
+}
+
+static void
+firmcvt(char *firmfile)
+{
+ char *basefile, *outfile, *ptr;
+ char sysbuf[1024];
+
+ outfile = strdup(basename(firmfile));
+ basefile = strdup(outfile);
+
+ snprintf(sysbuf, sizeof(sysbuf),
+#ifdef __i386__
+ "objcopy -I binary -O elf32-i386-freebsd -B i386 %s %s.o\n",
+#endif
+#ifdef __amd64__
+ "objcopy -I binary -O elf64-x86-64-freebsd -B i386 %s %s.o\n",
+#endif
+ firmfile, outfile);
+ printf("%s", sysbuf);
+ system(sysbuf);
+
+ ptr = firmfile;
+ while (*ptr) {
+ if (*ptr == '/' || *ptr == '.')
+ *ptr = '_';
+ ptr++;
+ }
+ ptr = basefile;
+ while (*ptr) {
+ if (*ptr == '/' || *ptr == '.')
+ *ptr = '_';
+ else
+ *ptr = tolower(*ptr);
+ ptr++;
+ }
+
+ snprintf(sysbuf, sizeof(sysbuf),
+ "objcopy --redefine-sym _binary_%s_start=%s_start "
+ "--strip-symbol _binary_%s_size "
+ "--redefine-sym _binary_%s_end=%s_end %s.o %s.o\n",
+ firmfile, basefile, firmfile, firmfile,
+ basefile, outfile, outfile);
+ ptr = sysbuf;
+ printf("%s", sysbuf);
+ system(sysbuf);
+
+ snprintf(sysbuf, sizeof(sysbuf),
+ "ld -Bshareable -d -warn-common -o %s.ko %s.o\n",
+ outfile, outfile);
+ printf("%s", sysbuf);
+ system(sysbuf);
+
+ free(basefile);
+
+ exit(0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp, *outfp;
+ int i, bin = 0;
+ void *img;
+ int n, fsize, cnt;
+ unsigned char *ptr;
+ char *inffile = NULL, *sysfile = NULL;
+ char *outfile = NULL, *firmfile = NULL;
+ char *dname = NULL;
+ int ch;
+
+ while((ch = getopt(argc, argv, "i:s:o:n:f:O")) != -1) {
+ switch(ch) {
+ case 'f':
+ firmfile = optarg;
+ break;
+ case 'i':
+ inffile = optarg;
+ break;
+ case 's':
+ sysfile = optarg;
+ break;
+ case 'o':
+ outfile = optarg;
+ break;
+ case 'n':
+ dname = optarg;
+ break;
+ case 'O':
+ bin = 1;
+ break;
+ default:
+ usage();
+ break;
+ }
+ }
+
+ if (firmfile != NULL)
+ firmcvt(firmfile);
+
+ if (sysfile == NULL)
+ usage();
+
+ /* Open the .SYS file and load it into memory */
+ fp = fopen(sysfile, "r");
+ if (fp == NULL)
+ err(1, "opening .SYS file '%s' failed", sysfile);
+ fseek (fp, 0L, SEEK_END);
+ fsize = ftell (fp);
+ rewind (fp);
+ img = calloc(fsize, 1);
+ n = fread (img, fsize, 1, fp);
+ if (n == 0)
+ err(1, "reading .SYS file '%s' failed", sysfile);
+
+ fclose(fp);
+
+ if (insert_padding(&img, &fsize)) {
+ fprintf(stderr, "section relocation failed\n");
+ exit(1);
+ }
+
+ if (outfile == NULL || strcmp(outfile, "-") == 0)
+ outfp = stdout;
+ else {
+ outfp = fopen(outfile, "w");
+ if (outfp == NULL)
+ err(1, "opening output file '%s' failed", outfile);
+ }
+
+ fprintf(outfp, "\n/*\n");
+ fprintf(outfp, " * Generated from %s and %s (%d bytes)\n",
+ inffile == NULL ? "<notused>" : inffile, sysfile, fsize);
+ fprintf(outfp, " */\n\n");
+
+ if (dname != NULL) {
+ if (strlen(dname) > IFNAMSIZ)
+ err(1, "selected device name '%s' is "
+ "too long (max chars: %d)", dname, IFNAMSIZ);
+ fprintf (outfp, "#define NDIS_DEVNAME \"%s\"\n", dname);
+ fprintf (outfp, "#define NDIS_MODNAME %s\n\n", dname);
+ }
+
+ if (inffile == NULL) {
+ fprintf (outfp, "#ifdef NDIS_REGVALS\n");
+ fprintf (outfp, "ndis_cfg ndis_regvals[] = {\n");
+ fprintf (outfp, "\t{ NULL, NULL, { 0 }, 0 }\n");
+ fprintf (outfp, "#endif /* NDIS_REGVALS */\n");
+
+ fprintf (outfp, "};\n\n");
+ } else {
+ fp = fopen(inffile, "r");
+ if (fp == NULL)
+ err(1, "opening .INF file '%s' failed", inffile);
+
+
+ inf_parse(fp, outfp);
+ fclose(fp);
+ }
+
+ fprintf(outfp, "\n#ifdef NDIS_IMAGE\n");
+
+ if (bin) {
+ sysfile = strdup(basename(sysfile));
+ ptr = (unsigned char *)sysfile;
+ while (*ptr) {
+ if (*ptr == '.')
+ *ptr = '_';
+ ptr++;
+ }
+ fprintf(outfp,
+ "\nextern unsigned char ndis_%s_drv_data_start[];\n",
+ sysfile);
+ fprintf(outfp, "static unsigned char *drv_data = "
+ "ndis_%s_drv_data_start;\n\n", sysfile);
+ bincvt(sysfile, outfile, img, fsize);
+ goto done;
+ }
+
+
+ fprintf(outfp, "\nextern unsigned char drv_data[];\n\n");
+
+ fprintf(outfp, "__asm__(\".data\");\n");
+ fprintf(outfp, "__asm__(\".globl drv_data\");\n");
+ fprintf(outfp, "__asm__(\".type drv_data, @object\");\n");
+ fprintf(outfp, "__asm__(\".size drv_data, %d\");\n", fsize);
+ fprintf(outfp, "__asm__(\"drv_data:\");\n");
+
+ ptr = img;
+ cnt = 0;
+ while(cnt < fsize) {
+ fprintf (outfp, "__asm__(\".byte ");
+ for (i = 0; i < 10; i++) {
+ cnt++;
+ if (cnt == fsize) {
+ fprintf(outfp, "0x%.2X\");\n", ptr[i]);
+ goto done;
+ } else {
+ if (i == 9)
+ fprintf(outfp, "0x%.2X\");\n", ptr[i]);
+ else
+ fprintf(outfp, "0x%.2X, ", ptr[i]);
+ }
+ }
+ ptr += 10;
+ }
+
+done:
+
+ fprintf(outfp, "#endif /* NDIS_IMAGE */\n");
+
+ if (fp != NULL)
+ fclose(fp);
+ fclose(outfp);
+ free(img);
+ exit(0);
+}
diff --git a/usr.sbin/ndiscvt/ndisgen.8 b/usr.sbin/ndiscvt/ndisgen.8
new file mode 100644
index 0000000..2fb4f40
--- /dev/null
+++ b/usr.sbin/ndiscvt/ndisgen.8
@@ -0,0 +1,86 @@
+.\" Copyright (c) 2005
+.\" Bill Paul <wpaul@windriver.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 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd April 24, 2005
+.Dt NDISGEN 8
+.Os
+.Sh NAME
+.Nm ndisgen
+.Nd generate a FreeBSD driver module from a
+.Tn Windows\[rg]
+NDIS driver distribution
+.Sh SYNOPSIS
+.Nm
+.Op Ar /path/to/INF /path/to/SYS
+.Sh DESCRIPTION
+The
+.Nm
+script uses the
+.Xr ndiscvt 8
+utility and other tools to generate a
+.Fx
+loadable driver module
+and a static ELF object module from a
+.Tn Windows\[rg]
+NDIS driver, for use with the
+.Xr ndis 4
+compatibility module.
+.Pp
+The
+.Nm
+script is interactive and contains its own help section.
+Unless the paths to both files are supplied on the command line,
+the script will prompt the user for the
+.Pa .INF
+and
+.Pa .SYS
+files needed to generate the
+.Fx
+driver module.
+The script will also prompt for
+any firmware or other external files needed.
+.Sh SEE ALSO
+.Xr ld 1 ,
+.Xr objcopy 1 ,
+.Xr ndis 4 ,
+.Xr kldload 8 ,
+.Xr ndiscvt 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 6.0 .
+.Sh AUTHORS
+The
+.Nm
+utility was written by
+.An Bill Paul Aq Mt wpaul@windriver.com .
diff --git a/usr.sbin/ndiscvt/ndisgen.sh b/usr.sbin/ndiscvt/ndisgen.sh
new file mode 100644
index 0000000..97e8364
--- /dev/null
+++ b/usr.sbin/ndiscvt/ndisgen.sh
@@ -0,0 +1,559 @@
+#!/bin/sh
+#
+# Copyright (c) 2005
+# Bill Paul <wpaul@windriver.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 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.
+#
+# $FreeBSD$
+#
+
+header () {
+clear
+echo " =================================================================="
+echo " ------------------ Windows(r) driver converter -------------------"
+echo " =================================================================="
+echo ""
+}
+
+mainmenu() {
+header
+echo " This script is designed to guide you through the process"
+echo " of converting a Windows(r) binary driver module and .INF"
+echo " specification file into a FreeBSD ELF kernel module for use"
+echo " with the NDIS compatibility system."
+echo ""
+echo " The following options are available:"
+echo ""
+echo " 1] Learn about the NDIS compatibility system"
+echo " 2] Convert individual firmware files"
+echo " 3] Convert driver"
+echo " 4] Exit"
+echo ""
+echo -n " Enter your selection here and press return: "
+read KEYPRESS
+return
+}
+
+
+help1 () {
+header
+echo " General information"
+echo ""
+echo " The NDIS compatibility system is designed to let you use Windows(r)"
+echo " binary drivers for networking devices with FreeBSD, in cases where"
+echo " a native FreeBSD driver is not available due to hardware manufacturer"
+echo " oversight or stupidity. NDIS stands for Network Driver Interface"
+echo " Standard, and refers to the programming model used to write Windows(r)"
+echo " network drivers. (These are often called \"NDIS miniport\" drivers.)"
+echo ""
+echo " In order to use your network device in NDIS compatibility mode,"
+echo " you need the Windows(r) driver that goes with it. Also, the driver"
+echo " must be compiled for the same architecture as the release of FreeBSD"
+echo " you have installed. At this time, the i386 and amd64 architectures"
+echo " are both supported. Note that you cannot use a Windows/i386 driver"
+echo " with FreeBSD/amd64: you must obtain a Windows/amd64 driver."
+echo ""
+echo -n " Press return to continue... "
+read KEYPRESS
+return
+}
+
+help2() {
+header
+echo " Where to get drivers"
+echo ""
+echo " If you purchased your network card separately from your computer,"
+echo " there should have been a driver distribution CD included with the"
+echo " card which contains Windows(r) drivers. The NDIS compatibility"
+echo " system is designed to emulate the NDIS API of a couple of different"
+echo " Windows(r) releases, however it works best with drivers designed"
+echo " for NDIS 5.0 or later. Drivers distributed for Windows 2000 should"
+echo " work; however, for best results you should use a driver designed"
+echo " for Windows XP or Windows Server 2003."
+echo ""
+echo " If your card was supplied with your computer, or is a built-in device,"
+echo " drivers may have been included on a special driver bundle CD shipped"
+echo " with the computer."
+echo ""
+echo " If you don't have a driver CD, you should be able to find a driver"
+echo " kit on the card or computer vendor's web site."
+echo ""
+echo -n " Press return to continue... "
+read KEYPRESS
+return
+}
+
+help3 () {
+header
+echo " What files do I need?"
+echo ""
+echo " In most cases, you will need only two files: a .INF file and a .SYS"
+echo " file. The .INF file is a text file used by the Windows(r) installer to"
+echo " perform the driver installation. It contains information that tells"
+echo " the installer what devices the driver supports and what registry keys"
+echo " should be created to control driver configuration. The .SYS file"
+echo " is the actual driver executable code in Windows(r) Portable Executable"
+echo " (PE) format. Note that sometimes the .INF file is supplied in Unicode"
+echo " format. Unicode .INF files must be converted to ASCII form with the"
+echo " iconv(1) utility before this installer script can use them."
+echo " Occasionally, a driver may require firmware or register setup"
+echo " files that are external to the main .SYS file. These are provided"
+echo " on the same CD with the driver itself, and sometimes have a .BIN"
+echo " extension, though they can be named almost anything. You will need"
+echo " these additional files to make your device work with the NDIS"
+echo " compatibility system as well."
+echo ""
+echo -n " Press return to continue... "
+read KEYPRESS
+return
+}
+
+help4 () {
+header
+echo " How does it all work?"
+echo ""
+echo " The installer script uses the ndiscvt(1) utility to convert the .INF,"
+echo " .SYS and optional firmware files into a FreeBSD kernel loadable module"
+echo " (.ko) file. This module can be loaded via the kldload(8) utility or"
+echo " loaded automatically via the /boot/loader.conf file. The ndiscvt(1)"
+echo " utility extracts the device ID information and registry key data"
+echo " from the .INF file and converts it into a C header file. It also uses"
+echo " the objcopy(1) utility to convert the .SYS file and optional firmware"
+echo " files into ELF objects. The header file is compiled into a small C"
+echo " stub file which contains a small amount of code to interface with"
+echo " the FreeBSD module system. This stub is linked together with the"
+echo " converted ELF objects to form a FreeBSD kernel module. A static ELF"
+echo " object (.o) file is also created. This file can be linked into a"
+echo " static kernel image for those who want/need a fully linked kernel"
+echo " image (possibly for embedded bootstrap purposes, or just plain old"
+echo " experimentation)."
+echo ""
+echo -n " Press return to continue... "
+read KEYPRESS
+return
+}
+
+help5 () {
+header
+echo " Prerequisites"
+echo ""
+echo " Converting a driver requires the following utilities:"
+echo ""
+echo " - The FreeBSD C compiler, cc(1) (part of the base install)."
+echo " - The FreeBSD linker, ld(1) (part of the base install)."
+echo " - The objcopy(1) utility (part of the base install)."
+echo " - The ndiscvt(1) utility (part of the base install)."
+echo ""
+echo " If you happen to end up with a .INF file that's in Unicode format,"
+echo " then you'll also need:"
+echo ""
+echo " - The iconv(1) utility."
+echo ""
+echo " If you have installed the X Window system or some sort of desktop"
+echo " environment, then iconv(1) should already be present. If not, you"
+echo " will need to install the libiconv package or port."
+echo ""
+echo -n " Press return to continue... "
+read KEYPRESS
+return
+}
+
+infconv () {
+header
+echo " INF file validation"
+
+if [ -z "$INFPATH" ]; then
+ echo ""
+ echo ""
+ echo " A .INF file is most often provided as an ASCII file, however"
+ echo " files with multilanguage support are provided in Unicode format."
+ echo " Please type in the path to your .INF file now."
+ echo ""
+ echo -n " > "
+ read INFPATH
+fi
+
+if [ ${INFPATH} ] && [ -e ${INFPATH} ]; then
+ INFTYPE=`${EGREP} -i -c "Signature|.S.i.g.n.a.t.u.r.e" ${INFPATH}`
+ if [ ${INFTYPE} -le 0 ]; then
+ echo ""
+ echo " I don't recognize this file format. It may not be a valid .INF file."
+ echo ""
+ echo -n " Press enter to try again, or ^C to quit. "
+ read KEYPRESS
+ INFPATH=""
+ return
+ fi
+
+ INFTYPE=`${EGREP} -i -c "Class.*=.*Net" ${INFPATH}`
+ if [ ${INFTYPE} -gt 0 ]; then
+ echo ""
+ echo " This .INF file appears to be ASCII."
+ echo ""
+ echo -n " Press return to continue... "
+ read KEYPRESS
+ return
+ fi
+
+ INFTYPE=`${EGREP} -i -c ".C.l.a.s.s.*=.*N.e.t" ${INFPATH}`
+ if [ ${INFTYPE} -gt 0 ]; then
+ echo ""
+ echo " This .INF file appears to be Unicode."
+ if [ -e ${ICONVPATH} ]; then
+ echo " Trying to convert to ASCII..."
+ ${ICONVPATH} -f utf-16 -t utf-8 ${INFPATH} > ${INFFILE}
+ INFPATH=${INFFILE}
+ echo " Done."
+ echo ""
+ echo -n " Press return to continue... "
+ read KEYPRESS
+ else
+ echo " The iconv(1) utility does not appear to be installed."
+ echo " Please install this utility or convert the .INF file"
+ echo " to ASCII and run this utility again."
+ echo ""
+ exit
+ fi
+ return
+ fi
+
+ echo ""
+ echo " I don't recognize this file format. It may not be a valid .INF file."
+ echo ""
+ echo -n " Press enter to try again, or ^C to quit. "
+ read KEYPRESS
+ INFPATH=""
+else
+ echo ""
+ echo " The file '${INFPATH}' was not found."
+ echo ""
+ echo -n " Press enter to try again, or ^C to quit. "
+ read KEYPRESS
+ INFPATH=""
+fi
+return
+}
+
+sysconv() {
+header
+echo " Driver file validation"
+
+if [ ! -r "$SYSPATH" ]; then
+ echo ""
+ echo ""
+ echo " Now you need to specify the name of the Windows(r) driver .SYS"
+ echo " file for your device. Note that if you are running FreeBSD/amd64,"
+ echo " then you must provide a driver that has been compiled for the"
+ echo " 64-bit Windows(r) platform. If a 64-bit driver is not available"
+ echo " for your device, you must install FreeBSD/i386 and use the"
+ echo " 32-bit driver instead."
+ echo ""
+ echo " Please type in the path to the Windows(r) driver .SYS file now."
+ echo ""
+ echo -n " > "
+ read SYSPATH
+fi
+
+if [ ${SYSPATH} ] && [ -e ${SYSPATH} ]; then
+ SYSTYPE=`${FILE} ${SYSPATH}`
+
+ case ${SYSTYPE} in
+ *Windows*)
+ echo ""
+ echo " This .SYS file appears to be in Windows(r) PE format."
+ echo ""
+ echo -n " Press return to continue... "
+ read KEYPRESS
+ SYSBASE=`${BASENAME} ${SYSPATH} | ${TR} '.' '_'`
+ ;;
+ *)
+ echo ""
+ echo " I don't recognize this file format. It may not be a valid .SYS file."
+ echo ""
+
+ echo -n " Press enter to try again, or ^C to quit. "
+ read KEYPRESS
+ SYSPATH=""
+ ;;
+ esac
+else
+ echo ""
+ echo " The file '${SYSPATH}' was not found."
+ echo ""
+ echo -n " Press enter to try again, or ^C to quit. "
+ read KEYPRESS
+ SYSPATH=""
+fi
+return
+}
+
+ndiscvt() {
+header
+echo " Driver file conversion"
+echo ""
+echo " The script will now try to convert the .INF and .SYS files"
+echo " using the ndiscvt(1) utility. This utility can handle most"
+echo " .INF files; however, occasionally it can fail to parse some files"
+echo " due to subtle syntax issues: the .INF syntax is very complex,"
+echo " and the Windows(r) parser will sometimes allow files with small"
+echo " syntax errors to be processed correctly which ndiscvt(1) will"
+echo " not. If the conversion fails, you may have to edit the .INF"
+echo " file by hand to remove the offending lines."
+echo ""
+echo -n " Press enter to try converting the files now: "
+read KEYPRESS
+if ! ${NDISCVT} -i ${INFPATH} -s ${SYSPATH} -O -o ${DNAME}.h > /dev/null; then
+ echo "CONVERSION FAILED"
+ exit
+else
+ echo ""
+ echo " Conversion was successful."
+ echo ""
+ echo -n " Press enter to continue... "
+ read KEYPRESS
+fi
+return
+}
+
+firmcvt() {
+ while : ; do
+header
+echo " Firmware file conversion"
+echo ""
+echo " If your driver uses additional firmware files, please list them"
+echo " below. When you're finished, just press enter to continue. (If your"
+echo " driver doesn't need any extra firmware files, just press enter"
+echo " to move to the next step.)"
+echo ""
+ echo -n " > "
+ read FIRMPATH
+
+ if [ ${FIRMPATH} ]; then
+ if [ ! -e ${FIRMPATH} ]; then
+ echo ""
+ echo " The file '${FIRMPATH}' was not found"
+ echo ""
+ echo -n " Press enter to try again, or ^C to quit. "
+ read KEYPRESS
+ continue
+ fi
+ if ! ${NDISCVT} -f ${FIRMPATH} > /dev/null; then
+ echo ""
+ echo "CONVERSION FAILED"
+ else
+ echo ""
+ echo " Conversion was successful."
+ echo ""
+ FRMBASE=`${BASENAME} ${FIRMPATH}`
+ FRMBASE="${FRMBASE}.o"
+ FRMLIST="${FRMLIST} ${FRMBASE}"
+ fi
+ echo -n " Press enter to continue... "
+ read KEYPRESS
+ else
+ break
+ fi
+ done
+
+header
+echo ""
+echo " List of files converted firmware files:"
+echo ""
+for i in ${FRMLIST}
+do
+ echo " "$i
+done
+echo ""
+echo -n " Press enter to continue... "
+read KEYPRESS
+return
+}
+
+drvgen () {
+header
+echo " Kernel module generation"
+echo ""
+echo ""
+echo " The script will now try to generate the kernel driver module."
+echo " This is the last step. Once this module is generated, you should"
+echo " be able to load it just like any other FreeBSD driver module."
+echo ""
+echo " Press enter to compile the stub module and generate the driver"
+echo -n " module now: "
+read KEYPRESS
+echo ""
+echo -n " Generating Makefile... "
+echo ".PATH: ${PWD} ${STUBPATH}" > ${MAKEFILE}
+echo "KMOD= ${SYSBASE}" >> ${MAKEFILE}
+echo "SRCS+= ${STUBFILE} ${DNAME}.h bus_if.h device_if.h" >> ${MAKEFILE}
+echo "OBJS+=${FRMLIST} ${DNAME}.o" >> ${MAKEFILE}
+echo "CFLAGS+= \\" >> ${MAKEFILE}
+echo " -DDRV_DATA_START=ndis_${SYSBASE}_drv_data_start \\" >> ${MAKEFILE}
+echo " -DDRV_NAME=ndis_${SYSBASE} \\" >> ${MAKEFILE}
+echo " -DDRV_DATA_END=ndis_${SYSBASE}_drv_data_end" >> ${MAKEFILE}
+echo "CLEANFILES+= \\" >> ${MAKEFILE}
+echo " ${INFFILE} \\" >> ${MAKEFILE}
+echo " ${DNAME}.h \\" >> ${MAKEFILE}
+echo " ${DNAME}.o" >> ${MAKEFILE}
+echo ".include <bsd.kmod.mk>" >> ${MAKEFILE}
+if [ -f ${MAKEFILE} ]; then
+ echo "done."
+else
+ echo "generating Makefile failed. Exiting."
+ echo ""
+ exit
+fi
+echo -n " Building kernel module... "
+echo "" > bus_if.h
+echo "" > device_if.h
+if ! ${MAKE} -f ${MAKEFILE} depend > /dev/null; then
+ echo "build failed. Exiting."
+ echo ""
+ exit
+fi
+if ! ${MAKE} -f ${MAKEFILE} all > /dev/null; then
+ echo "build failed. Exiting."
+ echo ""
+ exit
+else
+ if [ -f ${SYSBASE}.ko ]; then
+ ${MV} ${SYSBASE}.ko ${SYSBASE}.kmod
+ echo "done."
+ else
+ echo "build failed. Exiting."
+ echo ""
+ exit
+ fi
+fi
+echo -n " Cleaning up... "
+if ! ${MAKE} -f ${MAKEFILE} clean cleandepend > /dev/null; then
+ echo "cleanup failed. Exiting."
+ echo ""
+ exit
+else
+ echo "done."
+fi
+${RM} ${MAKEFILE}
+${MV} ${SYSBASE}.kmod ${SYSBASE}.ko
+echo ""
+echo " The file ${SYSBASE}.ko has been successfully generated."
+echo " You can kldload this module to get started."
+echo ""
+echo -n " Press return to exit. "
+read KEYPRESS
+echo ""
+echo ""
+return
+}
+
+convert_driver () {
+ while : ; do
+ infconv
+ if [ ${INFPATH} ]; then
+ break
+ fi
+ done
+
+ while : ; do
+ sysconv
+ if [ ${SYSPATH} ]; then
+ break
+ fi
+ done
+
+ ndiscvt
+ firmcvt
+ drvgen
+ return
+}
+
+ICONVPATH=/usr/bin/iconv
+NDISCVT=/usr/sbin/ndiscvt
+STUBPATH=/usr/share/misc
+STUBFILE=windrv_stub.c
+DNAME=windrv
+CP=/bin/cp
+MV=/bin/mv
+RM=/bin/rm
+TR=/usr/bin/tr
+FILE=/usr/bin/file
+EGREP=/usr/bin/egrep
+MAKE=/usr/bin/make
+BASENAME=/usr/bin/basename
+TOUCH=/usr/bin/touch
+MKTEMP=/usr/bin/mktemp
+
+MAKEFILE=`${MKTEMP} /tmp/Makefile.XXXXXX`
+INFFILE=`${MKTEMP} /tmp/ascii_inf.XXXXXX`
+
+INFPATH=""
+FRMLIST=""
+SYSPATH=""
+SYSBASE=""
+FRMBASE=""
+
+if [ -r "$1" -a -r "$2" ]; then
+ # Looks like the user supplied .INF and .SYS files on the command line
+ INFPATH=$1
+ SYSPATH=$2
+ convert_driver && exit 0
+fi
+
+while : ; do
+ mainmenu
+ case ${KEYPRESS} in
+ 1)
+ help1
+ help2
+ help3
+ help4
+ help5
+ ;;
+ 2)
+ firmcvt
+ ;;
+ 3)
+ convert_driver
+ ;;
+ 4)
+ header
+ echo ""
+ echo " Be seeing you!"
+ echo ""
+ exit
+ ;;
+ *)
+ header
+ echo ""
+ echo -n " Sorry, I didn't understand that. Press enter to try again: "
+ read KEYPRESS
+ ;;
+ esac
+done
+exit
diff --git a/usr.sbin/ndiscvt/windrv_stub.c b/usr.sbin/ndiscvt/windrv_stub.c
new file mode 100644
index 0000000..46a0e83
--- /dev/null
+++ b/usr.sbin/ndiscvt/windrv_stub.c
@@ -0,0 +1,266 @@
+/*-
+ * Copyright (c) 2005
+ * Bill Paul <wpaul@windriver.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 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/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/conf.h>
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+#include <sys/bus.h>
+
+#define NDIS_REGVALS
+
+struct ndis_cfg {
+ char *nc_cfgkey;
+ char *nc_cfgdesc;
+ char nc_val[256];
+ int nc_idx;
+};
+
+typedef struct ndis_cfg ndis_cfg;
+
+#include "windrv.h"
+
+struct ndis_pci_type {
+ uint16_t ndis_vid;
+ uint16_t ndis_did;
+ uint32_t ndis_subsys;
+ char *ndis_name;
+};
+
+struct ndis_pccard_type {
+ const char *ndis_vid;
+ const char *ndis_did;
+ char *ndis_name;
+};
+
+struct ndis_usb_type {
+ uint16_t ndis_vid;
+ uint16_t ndis_did;
+ char *ndis_name;
+};
+
+#ifdef NDIS_PCI_DEV_TABLE
+static struct ndis_pci_type ndis_devs_pci[] = {
+ NDIS_PCI_DEV_TABLE
+ { 0, 0, 0, NULL }
+};
+#endif
+
+#ifdef NDIS_PCMCIA_DEV_TABLE
+static struct ndis_pccard_type ndis_devs_pccard[] = {
+ NDIS_PCMCIA_DEV_TABLE
+ { NULL, NULL, NULL }
+};
+#endif
+
+#ifdef NDIS_USB_DEV_TABLE
+static struct ndis_usb_type ndis_devs_usb[] = {
+ NDIS_USB_DEV_TABLE
+ { 0, 0, NULL }
+};
+#endif
+
+enum interface_type {
+ InterfaceTypeUndefined = -1,
+ Internal,
+ Isa,
+ Eisa,
+ MicroChannel,
+ TurboChannel,
+ PCIBus,
+ VMEBus,
+ NuBus,
+ PCMCIABus,
+ CBus,
+ MPIBus,
+ MPSABus,
+ ProcessorInternal,
+ InternalPowerBus,
+ PNPISABus,
+ PNPBus,
+ MaximumInterfaceType
+};
+
+typedef enum interface_type interface_type;
+
+/*
+ * XXX
+ * Ordinarily, device_probe_desc is defined in device_if.h, which
+ * is created from device_if.m. The problem is, the latter file
+ * is only available if you have the kernel source code installed,
+ * and not all users choose to install it. I'd like to let people
+ * load Windows driver modules with the minimal amount of hassle
+ * and dependencies. <sys/bus.h> wants both device_if.h and bus_if.h
+ * to be defined, but it turns out the only thing we really need
+ * to get this module compiled is device_probe_desc, so we define
+ * that here, and let the build script create empty copies of
+ * device_if.h and bus_if.h to make the compiler happy.
+ */
+
+extern struct kobjop_desc device_probe_desc;
+typedef int device_probe_t(device_t dev);
+
+extern int windrv_load(module_t, vm_offset_t, size_t,
+ interface_type, void *, void *);
+extern int windrv_unload(module_t, vm_offset_t, size_t);
+
+#ifndef DRV_DATA_START
+#define DRV_DATA_START UNDEF_START
+#endif
+
+#ifndef DRV_DATA_END
+#define DRV_DATA_END UNDEF_END
+#endif
+
+#ifndef DRV_NAME
+#define DRV_NAME UNDEF_NAME
+#endif
+
+extern uint8_t DRV_DATA_START;
+extern uint8_t DRV_DATA_END;
+
+/*
+ * The following is stub code that makes it look as though we want
+ * to be a child device of all the buses that our supported devices
+ * might want to attach to. Our probe routine always fails. The
+ * reason we need this code is so that loading an ELF-ified Windows
+ * driver module will trigger a bus reprobe.
+ */
+
+#define MODULE_DECL(x) \
+ MODULE_DEPEND(x, ndisapi, 1, 1, 1); \
+ MODULE_DEPEND(x, ndis, 1, 1, 1)
+
+MODULE_DECL(DRV_NAME);
+
+static int windrv_probe(device_t);
+static int windrv_modevent(module_t, int, void *);
+static int windrv_loaded = 0;
+
+static device_method_t windrv_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, windrv_probe),
+
+ { 0, 0 }
+};
+
+static driver_t windrv_driver = {
+ "windrv_stub",
+ windrv_methods,
+ 0
+};
+
+static devclass_t windrv_devclass;
+
+#define DRIVER_DECL(x) \
+ DRIVER_MODULE(x, pci, windrv_driver, \
+ windrv_devclass, windrv_modevent, NULL); \
+ DRIVER_MODULE(x, cardbus, windrv_driver, \
+ windrv_devclass, windrv_modevent, NULL); \
+ DRIVER_MODULE(x, pccard, windrv_driver, \
+ windrv_devclass, windrv_modevent, NULL); \
+ DRIVER_MODULE(x, uhub, windrv_driver, \
+ windrv_devclass, windrv_modevent, NULL); \
+ MODULE_VERSION(x, 1)
+
+DRIVER_DECL(DRV_NAME);
+
+static int
+windrv_probe(dev)
+ device_t dev;
+{
+ return (ENXIO);
+}
+
+static int
+windrv_modevent(mod, cmd, arg)
+ module_t mod;
+ int cmd;
+ void *arg;
+{
+ int drv_data_len;
+ int error = 0;
+ vm_offset_t drv_data_start;
+ vm_offset_t drv_data_end;
+
+ drv_data_start = (vm_offset_t)&DRV_DATA_START;
+ drv_data_end = (vm_offset_t)&DRV_DATA_END;
+
+ drv_data_len = drv_data_end - drv_data_start;
+ switch (cmd) {
+ case MOD_LOAD:
+ windrv_loaded++;
+ if (windrv_loaded > 1)
+ break;
+#ifdef NDIS_PCI_DEV_TABLE
+ windrv_load(mod, drv_data_start, drv_data_len, PCIBus,
+ ndis_devs_pci, &ndis_regvals);
+#endif
+#ifdef NDIS_PCMCIA_DEV_TABLE
+ windrv_load(mod, drv_data_start, drv_data_len, PCMCIABus,
+ ndis_devs_pccard, &ndis_regvals);
+#endif
+#ifdef NDIS_USB_DEV_TABLE
+ windrv_load(mod, drv_data_start, drv_data_len, PNPBus,
+ ndis_devs_usb, &ndis_regvals);
+#endif
+ break;
+ case MOD_UNLOAD:
+ windrv_loaded--;
+ if (windrv_loaded > 0)
+ break;
+#ifdef NDIS_PCI_DEV_TABLE
+ windrv_unload(mod, drv_data_start, drv_data_len);
+#endif
+#ifdef NDIS_PCMCIA_DEV_TABLE
+ windrv_unload(mod, drv_data_start, drv_data_len);
+#endif
+#ifdef NDIS_USB_DEV_TABLE
+ windrv_unload(mod, drv_data_start, drv_data_len);
+#endif
+ break;
+ case MOD_SHUTDOWN:
+ break;
+ default:
+ error = EINVAL;
+ break;
+ }
+
+ return (error);
+}
diff --git a/usr.sbin/ndp/Makefile b/usr.sbin/ndp/Makefile
new file mode 100644
index 0000000..552ac53
--- /dev/null
+++ b/usr.sbin/ndp/Makefile
@@ -0,0 +1,27 @@
+# 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$
+
+.PATH: ${.CURDIR}/../../contrib/tcpdump
+
+PROG= ndp
+MAN= ndp.8
+SRCS= ndp.c gmt2local.c
+
+CFLAGS+= -I. -I${.CURDIR} -I${.CURDIR}/../../contrib/tcpdump
+CFLAGS+= -D_U_=""
+
+WARNS?= 1
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ndp/Makefile.depend b/usr.sbin/ndp/Makefile.depend
new file mode 100644
index 0000000..54c1f6f
--- /dev/null
+++ b/usr.sbin/ndp/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ndp/ndp.8 b/usr.sbin/ndp/ndp.8
new file mode 100644
index 0000000..c2a4c1d
--- /dev/null
+++ b/usr.sbin/ndp/ndp.8
@@ -0,0 +1,313 @@
+.\" $KAME: ndp.8,v 1.28 2002/07/17 08:46:33 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 May 9, 2014
+.Dt NDP 8
+.Os
+.\"
+.Sh NAME
+.Nm ndp
+.Nd control/diagnose IPv6 neighbor discovery protocol
+.\"
+.Sh SYNOPSIS
+.Nm
+.Op Fl nt
+.Ar hostname
+.Nm
+.Op Fl nt
+.Fl a | c | p
+.Nm
+.Op Fl nt
+.Fl r
+.Nm
+.Op Fl nt
+.Fl H | P | R
+.Nm
+.Op Fl nt
+.Fl A Ar wait
+.Nm
+.Op Fl nt
+.Fl d Ar hostname
+.Nm
+.Op Fl nt
+.Fl f Ar filename
+.Nm
+.Op Fl nt
+.Fl i
+.Ar interface
+.Op Ar expressions ...
+.Nm
+.Op Fl nt
+.Fl I Op Ar interface | Li delete
+.Nm
+.Op Fl nt
+.Fl s Ar nodename etheraddr
+.Op Li temp
+.Op Li proxy
+.\"
+.Sh DESCRIPTION
+The
+.Nm
+utility manipulates the address mapping table
+used by the Neighbor Discovery Protocol (NDP).
+.Bl -tag -width indent
+.It Fl a
+Dump the currently existing NDP entries.
+The following information will be printed:
+.Bl -tag -width "Neighbor"
+.It Neighbor
+IPv6 address of the neighbor.
+.It Linklayer Address
+Linklayer address of the neighbor.
+It could be
+.Dq Li (incomplete)
+when the address is not available.
+.It Netif
+Network interface associated with the neighbor cache entry.
+.It Expire
+The time until expiry of the entry.
+The entry could become
+.Dq Li permanent ,
+in which case it will never expire.
+.It S
+State of the neighbor cache entry, as a single letter:
+.Pp
+.Bl -tag -width indent -compact
+.It N
+Nostate
+.It W
+Waitdelete
+.It I
+Incomplete
+.It R
+Reachable
+.It S
+Stale
+.It D
+Delay
+.It P
+Probe
+.It ?\&
+Unknown state (should never happen).
+.El
+.It Flags
+Flags on the neighbor cache entry, in a single letter.
+They are: Router, proxy neighbor advertisement
+.Pq Dq p .
+The field could be followed by a decimal number,
+which means the number of NS probes the node has sent during the current state.
+.El
+.It Fl A Ar wait
+Repeat
+.Fl a
+(dump NDP entries)
+every
+.Ar wait
+seconds.
+.It Fl c
+Erase all the NDP entries.
+.It Fl d
+Delete specified NDP entry.
+.It Fl f Ar filename
+Cause the file
+.Ar filename
+to be read and multiple entries to be set in the
+.Tn NDP
+table.
+Entries
+in the file should be of the form
+.Pp
+.Bd -ragged -offset indent -compact
+.Ar hostname ether_addr
+.Op Cm temp
+.Op Cm proxy
+.Ed
+.Pp
+with argument meanings as given above.
+Leading whitespace and empty lines are ignored.
+A
+.Ql #
+character will mark the rest of the line as a comment.
+.It Fl H
+Harmonize consistency between the routing table and the default router
+list; install the top entry of the list into the kernel routing table.
+.It Fl I
+Shows the default interface used as the default route when
+there is no default router.
+.It Fl I Ar interface
+Specifies the default interface used as the default route when
+there is no default router.
+The
+.Ar interface
+will be used as the default.
+.It Fl I Li delete
+The current default interface will be deleted from the kernel.
+.It Fl i Ar interface Op Ar expressions ...
+View ND information for the specified interface.
+If additional arguments
+.Ar expressions
+are given,
+.Nm
+sets or clears the flags or variables for the interface as specified in
+the expression.
+Each expression should be separated by white spaces or tab characters.
+Possible expressions are as follows.
+Some of the expressions can begin with the
+special character
+.Ql - ,
+which means the flag specified in the expression should be cleared.
+Note that you need
+.Fl -
+before
+.Fl foo
+in this case.
+.\"
+.Bl -tag -width indent
+.It Ic nud
+Turn on or off NUD (Neighbor Unreachability Detection) on the
+interface.
+NUD is usually turned on by default.
+.It Ic accept_rtadv
+Specify whether or not to accept Router Advertisement messages
+received on the
+.Ar interface .
+This flag is set by
+.Va net.inet6.ip6.accept_rtadv
+sysctl variable.
+.It Ic auto_linklocal
+Specify whether or not to perform automatic link-local address configuration
+on
+.Ar interface .
+This flag is set by
+.Va net.inet6.ip6.auto_linklocal
+sysctl variable.
+.It Ic no_prefer_iface
+The address on the outgoing interface is preferred by source addess
+selection rule.
+If this flag is set, stop treating the address on the
+.Ar interface
+as special even when the
+.Ar interface
+is outgoing interface.
+The default value of this flag is off.
+.It Ic disabled
+Disable IPv6 operation on the interface.
+When disabled, the interface discards any IPv6 packets
+received on or being sent to the interface.
+In the sending case, an error of ENETDOWN will be returned to the
+application.
+This flag is typically set automatically in the kernel as a result of
+a certain failure of Duplicate Address Detection.
+If the auto_linklocal per-interface flag is set, automatic link-local
+address configuration is performed again when this flag is cleared.
+.It Ic basereachable Ns Li = Ns Pq Ar number
+Specify the BaseReachbleTimer on the interface in millisecond.
+.It Ic retrans Ns Li = Ns Pq Ar number
+Specify the RetransTimer on the interface in millisecond.
+.It Ic curhlim Ns Li = Ns Pq Ar number
+Specify the Cur Hop Limit on the interface.
+.El
+.It Fl n
+Do not try to resolve numeric addresses to hostnames.
+.It Fl p
+Show prefix list.
+The following information will be printed:
+.Bl -tag -width indent
+.It Cm if
+The network interface associated with this prefix.
+.It Cm flags
+The status of the prefix, expressed by a combination of the following
+letters:
+.Pp
+.Bl -tag -width indent -compact
+.It Cm A
+This prefix can be used for stateless address autoconfiguration.
+.It Cm L, Cm O
+This prefix can be used for on-link determination; that is, it can be
+used to determine whether a given destination address is on-link.
+.It Cm D
+There are no reachable routers advertising this prefix.
+.El
+.It Cm vltime
+Valid lifetime; the length of time for which the prefix and a stateless
+autoconfigured address generated from this prefix can be used for the
+source or destination address of a packet.
+.It Cm pltime
+Preferred lifetime; the length of time for which the prefix and a stateless
+autoconfigured address generated from this prefix can be used by upper-layer
+protocols unrestrictedly.
+.It Cm expire
+This is the remaining time that the prefix is in the valid state.
+.It Cm ref
+The number of kernel references held for this prefix.
+.El
+.It Fl P
+Flush all the entries in the prefix list.
+.It Fl r
+Show default router list.
+.It Fl R
+Flush all the entries in the default router list.
+.It Fl s
+Register an NDP entry for a node.
+The entry will be permanent unless the word
+.Li temp
+is given in the command.
+If the word
+.Li proxy
+is given, this system will act as a proxy NDP server,
+responding to requests for
+.Ar hostname
+even though the host address is not its own.
+.It Fl t
+Print timestamp for each entry,
+to make it possible to merge the output with
+.Xr tcpdump 1 .
+Most useful when used with
+.Fl A .
+.El
+.\"
+.Sh EXIT STATUS
+.Ex -std
+.\"
+.Sh SEE ALSO
+.Xr arp 8
+.\"
+.Sh HISTORY
+The
+.Nm
+utility first appeared in the WIDE Hydrangea IPv6 protocol stack kit.
+The
+.Fl I Ar auto_linklocal
+flag first appeared in
+.Fx 8.0 .
+.\"
+.\" .Sh BUGS
+.\" (to be written)
diff --git a/usr.sbin/ndp/ndp.c b/usr.sbin/ndp/ndp.c
new file mode 100644
index 0000000..cf80149
--- /dev/null
+++ b/usr.sbin/ndp/ndp.c
@@ -0,0 +1,1375 @@
+/* $FreeBSD$ */
+/* $KAME: ndp.c,v 1.104 2003/06/27 07:48:39 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.
+ */
+/*
+ * Copyright (c) 1984, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Sun Microsystems, 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.
+ */
+
+/*
+ * Based on:
+ * "@(#) Copyright (c) 1984, 1993\n\
+ * The Regents of the University of California. All rights reserved.\n";
+ *
+ * "@(#)arp.c 8.2 (Berkeley) 1/2/94";
+ */
+
+/*
+ * ndp - display, set, delete and flush neighbor cache
+ */
+
+
+#include <sys/param.h>
+#include <sys/file.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/queue.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/route.h>
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <netinet/icmp6.h>
+#include <netinet6/in6_var.h>
+#include <netinet6/nd6.h>
+
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <netdb.h>
+#include <errno.h>
+#include <nlist.h>
+#include <stdio.h>
+#include <string.h>
+#include <paths.h>
+#include <err.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include "gmt2local.h"
+
+#define NEXTADDR(w, s) \
+ if (rtm->rtm_addrs & (w)) { \
+ bcopy((char *)&s, cp, sizeof(s)); cp += SA_SIZE(&s);}
+
+
+static pid_t pid;
+static int nflag;
+static int tflag;
+static int32_t thiszone; /* time difference with gmt */
+static int s = -1;
+static int repeat = 0;
+
+static char host_buf[NI_MAXHOST]; /* getnameinfo() */
+static char ifix_buf[IFNAMSIZ]; /* if_indextoname() */
+
+static int file(char *);
+static void getsocket(void);
+static int set(int, char **);
+static void get(char *);
+static int delete(char *);
+static void dump(struct sockaddr_in6 *, int);
+static struct in6_nbrinfo *getnbrinfo(struct in6_addr *, int, int);
+static char *ether_str(struct sockaddr_dl *);
+static int ndp_ether_aton(char *, u_char *);
+static void usage(void);
+static int rtmsg(int);
+static void ifinfo(char *, int, char **);
+static void rtrlist(void);
+static void plist(void);
+static void pfx_flush(void);
+static void rtr_flush(void);
+static void harmonize_rtr(void);
+#ifdef SIOCSDEFIFACE_IN6 /* XXX: check SIOCGDEFIFACE_IN6 as well? */
+static void getdefif(void);
+static void setdefif(char *);
+#endif
+static char *sec2str(time_t);
+static void ts_print(const struct timeval *);
+
+static char *rtpref_str[] = {
+ "medium", /* 00 */
+ "high", /* 01 */
+ "rsv", /* 10 */
+ "low" /* 11 */
+};
+
+int
+main(int argc, char **argv)
+{
+ int ch, mode = 0;
+ char *arg = NULL;
+
+ pid = getpid();
+ thiszone = gmt2local(0);
+ while ((ch = getopt(argc, argv, "acd:f:Ii:nprstA:HPR")) != -1)
+ switch (ch) {
+ case 'a':
+ case 'c':
+ case 'p':
+ case 'r':
+ case 'H':
+ case 'P':
+ case 'R':
+ case 's':
+ case 'I':
+ if (mode) {
+ usage();
+ /*NOTREACHED*/
+ }
+ mode = ch;
+ arg = NULL;
+ break;
+ case 'f':
+ exit(file(optarg) ? 1 : 0);
+ case 'd':
+ case 'i':
+ if (mode) {
+ usage();
+ /*NOTREACHED*/
+ }
+ mode = ch;
+ arg = optarg;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 't':
+ tflag = 1;
+ break;
+ case 'A':
+ if (mode) {
+ usage();
+ /*NOTREACHED*/
+ }
+ mode = 'a';
+ repeat = atoi(optarg);
+ if (repeat < 0) {
+ usage();
+ /*NOTREACHED*/
+ }
+ break;
+ default:
+ usage();
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ switch (mode) {
+ case 'a':
+ case 'c':
+ if (argc != 0) {
+ usage();
+ /*NOTREACHED*/
+ }
+ dump(0, mode == 'c');
+ break;
+ case 'd':
+ if (argc != 0) {
+ usage();
+ /*NOTREACHED*/
+ }
+ delete(arg);
+ break;
+ case 'I':
+#ifdef SIOCSDEFIFACE_IN6 /* XXX: check SIOCGDEFIFACE_IN6 as well? */
+ if (argc > 1) {
+ usage();
+ /*NOTREACHED*/
+ } else if (argc == 1) {
+ if (strcmp(*argv, "delete") == 0 ||
+ if_nametoindex(*argv))
+ setdefif(*argv);
+ else
+ errx(1, "invalid interface %s", *argv);
+ }
+ getdefif(); /* always call it to print the result */
+ break;
+#else
+ errx(1, "not supported yet");
+ /*NOTREACHED*/
+#endif
+ case 'p':
+ if (argc != 0) {
+ usage();
+ /*NOTREACHED*/
+ }
+ plist();
+ break;
+ case 'i':
+ ifinfo(arg, argc, argv);
+ break;
+ case 'r':
+ if (argc != 0) {
+ usage();
+ /*NOTREACHED*/
+ }
+ rtrlist();
+ break;
+ case 's':
+ if (argc < 2 || argc > 4)
+ usage();
+ exit(set(argc, argv) ? 1 : 0);
+ case 'H':
+ if (argc != 0) {
+ usage();
+ /*NOTREACHED*/
+ }
+ harmonize_rtr();
+ break;
+ case 'P':
+ if (argc != 0) {
+ usage();
+ /*NOTREACHED*/
+ }
+ pfx_flush();
+ break;
+ case 'R':
+ if (argc != 0) {
+ usage();
+ /*NOTREACHED*/
+ }
+ rtr_flush();
+ break;
+ case 0:
+ if (argc != 1) {
+ usage();
+ /*NOTREACHED*/
+ }
+ get(argv[0]);
+ break;
+ }
+ exit(0);
+}
+
+/*
+ * Process a file to set standard ndp entries
+ */
+static int
+file(char *name)
+{
+ FILE *fp;
+ int i, retval;
+ char line[100], arg[5][50], *args[5], *p;
+
+ if ((fp = fopen(name, "r")) == NULL)
+ err(1, "cannot open %s", name);
+ args[0] = &arg[0][0];
+ args[1] = &arg[1][0];
+ args[2] = &arg[2][0];
+ args[3] = &arg[3][0];
+ args[4] = &arg[4][0];
+ retval = 0;
+ while (fgets(line, sizeof(line), fp) != NULL) {
+ if ((p = strchr(line, '#')) != NULL)
+ *p = '\0';
+ for (p = line; isblank(*p); p++);
+ if (*p == '\n' || *p == '\0')
+ continue;
+ i = sscanf(line, "%49s %49s %49s %49s %49s",
+ arg[0], arg[1], arg[2], arg[3], arg[4]);
+ if (i < 2) {
+ warnx("bad line: %s", line);
+ retval = 1;
+ continue;
+ }
+ if (set(i, args))
+ retval = 1;
+ }
+ fclose(fp);
+ return (retval);
+}
+
+static void
+getsocket()
+{
+ if (s < 0) {
+ s = socket(PF_ROUTE, SOCK_RAW, 0);
+ if (s < 0) {
+ err(1, "socket");
+ /* NOTREACHED */
+ }
+ }
+}
+
+static struct sockaddr_in6 so_mask = {
+ .sin6_len = sizeof(so_mask),
+ .sin6_family = AF_INET6
+};
+static struct sockaddr_in6 blank_sin = {
+ .sin6_len = sizeof(blank_sin),
+ .sin6_family = AF_INET6
+};
+static struct sockaddr_in6 sin_m;
+static struct sockaddr_dl blank_sdl = {
+ .sdl_len = sizeof(blank_sdl),
+ .sdl_family = AF_LINK
+};
+static struct sockaddr_dl sdl_m;
+static time_t expire_time;
+static int flags, found_entry;
+static struct {
+ struct rt_msghdr m_rtm;
+ char m_space[512];
+} m_rtmsg;
+
+/*
+ * Set an individual neighbor cache entry
+ */
+static int
+set(int argc, char **argv)
+{
+ register struct sockaddr_in6 *sin = &sin_m;
+ register struct sockaddr_dl *sdl;
+ register struct rt_msghdr *rtm = &(m_rtmsg.m_rtm);
+ struct addrinfo hints, *res;
+ int gai_error;
+ u_char *ea;
+ char *host = argv[0], *eaddr = argv[1];
+
+ getsocket();
+ argc -= 2;
+ argv += 2;
+ sdl_m = blank_sdl;
+ sin_m = blank_sin;
+
+ bzero(&hints, sizeof(hints));
+ hints.ai_family = AF_INET6;
+ gai_error = getaddrinfo(host, NULL, &hints, &res);
+ if (gai_error) {
+ fprintf(stderr, "ndp: %s: %s\n", host,
+ gai_strerror(gai_error));
+ return 1;
+ }
+ sin->sin6_addr = ((struct sockaddr_in6 *)res->ai_addr)->sin6_addr;
+ sin->sin6_scope_id =
+ ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id;
+ ea = (u_char *)LLADDR(&sdl_m);
+ if (ndp_ether_aton(eaddr, ea) == 0)
+ sdl_m.sdl_alen = 6;
+ flags = expire_time = 0;
+ while (argc-- > 0) {
+ if (strncmp(argv[0], "temp", 4) == 0) {
+ struct timeval now;
+
+ gettimeofday(&now, 0);
+ expire_time = now.tv_sec + 20 * 60;
+ } else if (strncmp(argv[0], "proxy", 5) == 0)
+ flags |= RTF_ANNOUNCE;
+ argv++;
+ }
+ if (rtmsg(RTM_GET) < 0) {
+ errx(1, "RTM_GET(%s) failed", host);
+ /* NOTREACHED */
+ }
+ sin = (struct sockaddr_in6 *)(rtm + 1);
+ sdl = (struct sockaddr_dl *)(ALIGN(sin->sin6_len) + (char *)sin);
+ if (IN6_ARE_ADDR_EQUAL(&sin->sin6_addr, &sin_m.sin6_addr)) {
+ if (sdl->sdl_family == AF_LINK &&
+ !(rtm->rtm_flags & RTF_GATEWAY)) {
+ switch (sdl->sdl_type) {
+ case IFT_ETHER: case IFT_FDDI: case IFT_ISO88023:
+ case IFT_ISO88024: case IFT_ISO88025:
+ case IFT_L2VLAN: case IFT_BRIDGE:
+ goto overwrite;
+ }
+ }
+ fprintf(stderr, "set: cannot configure a new entry\n");
+ return 1;
+ }
+
+overwrite:
+ if (sdl->sdl_family != AF_LINK) {
+ printf("cannot intuit interface index and type for %s\n", host);
+ return (1);
+ }
+ sdl_m.sdl_type = sdl->sdl_type;
+ sdl_m.sdl_index = sdl->sdl_index;
+ return (rtmsg(RTM_ADD));
+}
+
+/*
+ * Display an individual neighbor cache entry
+ */
+static void
+get(char *host)
+{
+ struct sockaddr_in6 *sin = &sin_m;
+ struct addrinfo hints, *res;
+ int gai_error;
+
+ sin_m = blank_sin;
+ bzero(&hints, sizeof(hints));
+ hints.ai_family = AF_INET6;
+ gai_error = getaddrinfo(host, NULL, &hints, &res);
+ if (gai_error) {
+ fprintf(stderr, "ndp: %s: %s\n", host,
+ gai_strerror(gai_error));
+ return;
+ }
+ sin->sin6_addr = ((struct sockaddr_in6 *)res->ai_addr)->sin6_addr;
+ sin->sin6_scope_id =
+ ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id;
+ dump(sin, 0);
+ if (found_entry == 0) {
+ getnameinfo((struct sockaddr *)sin, sin->sin6_len, host_buf,
+ sizeof(host_buf), NULL ,0,
+ (nflag ? NI_NUMERICHOST : 0));
+ printf("%s (%s) -- no entry\n", host, host_buf);
+ exit(1);
+ }
+}
+
+/*
+ * Delete a neighbor cache entry
+ */
+static int
+delete(char *host)
+{
+ struct sockaddr_in6 *sin = &sin_m;
+ register struct rt_msghdr *rtm = &m_rtmsg.m_rtm;
+ register char *cp = m_rtmsg.m_space;
+ struct sockaddr_dl *sdl;
+ struct addrinfo hints, *res;
+ int gai_error;
+
+ getsocket();
+ sin_m = blank_sin;
+
+ bzero(&hints, sizeof(hints));
+ hints.ai_family = AF_INET6;
+ gai_error = getaddrinfo(host, NULL, &hints, &res);
+ if (gai_error) {
+ fprintf(stderr, "ndp: %s: %s\n", host,
+ gai_strerror(gai_error));
+ return 1;
+ }
+ sin->sin6_addr = ((struct sockaddr_in6 *)res->ai_addr)->sin6_addr;
+ sin->sin6_scope_id =
+ ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id;
+ if (rtmsg(RTM_GET) < 0) {
+ errx(1, "RTM_GET(%s) failed", host);
+ /* NOTREACHED */
+ }
+ sin = (struct sockaddr_in6 *)(rtm + 1);
+ sdl = (struct sockaddr_dl *)(ALIGN(sin->sin6_len) + (char *)sin);
+ if (IN6_ARE_ADDR_EQUAL(&sin->sin6_addr, &sin_m.sin6_addr)) {
+ if (sdl->sdl_family == AF_LINK &&
+ !(rtm->rtm_flags & RTF_GATEWAY)) {
+ goto delete;
+ }
+ fprintf(stderr, "delete: cannot delete non-NDP entry\n");
+ return 1;
+ }
+
+delete:
+ if (sdl->sdl_family != AF_LINK) {
+ printf("cannot locate %s\n", host);
+ return (1);
+ }
+ /*
+ * need to reinit the field because it has rt_key
+ * but we want the actual address
+ */
+ NEXTADDR(RTA_DST, sin_m);
+ rtm->rtm_flags |= RTF_LLDATA;
+ if (rtmsg(RTM_DELETE) == 0) {
+ getnameinfo((struct sockaddr *)sin,
+ sin->sin6_len, host_buf,
+ sizeof(host_buf), NULL, 0,
+ (nflag ? NI_NUMERICHOST : 0));
+ printf("%s (%s) deleted\n", host, host_buf);
+ }
+
+ return 0;
+}
+
+#define W_ADDR 36
+#define W_LL 17
+#define W_IF 6
+
+/*
+ * Dump the entire neighbor cache
+ */
+static void
+dump(struct sockaddr_in6 *addr, int cflag)
+{
+ int mib[6];
+ size_t needed;
+ char *lim, *buf, *next;
+ struct rt_msghdr *rtm;
+ struct sockaddr_in6 *sin;
+ struct sockaddr_dl *sdl;
+ extern int h_errno;
+ struct timeval now;
+ u_long expire;
+ int addrwidth;
+ int llwidth;
+ int ifwidth;
+ char flgbuf[8];
+ char *ifname;
+
+ /* Print header */
+ if (!tflag && !cflag)
+ printf("%-*.*s %-*.*s %*.*s %-9.9s %1s %5s\n",
+ W_ADDR, W_ADDR, "Neighbor", W_LL, W_LL, "Linklayer Address",
+ W_IF, W_IF, "Netif", "Expire", "S", "Flags");
+
+again:;
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = AF_INET6;
+ mib[4] = NET_RT_FLAGS;
+#ifdef RTF_LLINFO
+ mib[5] = RTF_LLINFO;
+#else
+ mib[5] = 0;
+#endif
+ if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0)
+ err(1, "sysctl(PF_ROUTE estimate)");
+ if (needed > 0) {
+ if ((buf = malloc(needed)) == NULL)
+ err(1, "malloc");
+ if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0)
+ err(1, "sysctl(PF_ROUTE, NET_RT_FLAGS)");
+ lim = buf + needed;
+ } else
+ buf = lim = NULL;
+
+ for (next = buf; next && next < lim; next += rtm->rtm_msglen) {
+ int isrouter = 0, prbs = 0;
+
+ rtm = (struct rt_msghdr *)next;
+ sin = (struct sockaddr_in6 *)(rtm + 1);
+ sdl = (struct sockaddr_dl *)((char *)sin + ALIGN(sin->sin6_len));
+
+ /*
+ * Some OSes can produce a route that has the LINK flag but
+ * has a non-AF_LINK gateway (e.g. fe80::xx%lo0 on FreeBSD
+ * and BSD/OS, where xx is not the interface identifier on
+ * lo0). Such routes entry would annoy getnbrinfo() below,
+ * so we skip them.
+ * XXX: such routes should have the GATEWAY flag, not the
+ * LINK flag. However, there is rotten routing software
+ * that advertises all routes that have the GATEWAY flag.
+ * Thus, KAME kernel intentionally does not set the LINK flag.
+ * What is to be fixed is not ndp, but such routing software
+ * (and the kernel workaround)...
+ */
+ if (sdl->sdl_family != AF_LINK)
+ continue;
+
+ if (!(rtm->rtm_flags & RTF_HOST))
+ continue;
+
+ if (addr) {
+ if (IN6_ARE_ADDR_EQUAL(&addr->sin6_addr,
+ &sin->sin6_addr) == 0 ||
+ addr->sin6_scope_id != sin->sin6_scope_id)
+ continue;
+ found_entry = 1;
+ } else if (IN6_IS_ADDR_MULTICAST(&sin->sin6_addr))
+ continue;
+ if (IN6_IS_ADDR_LINKLOCAL(&sin->sin6_addr) ||
+ IN6_IS_ADDR_MC_LINKLOCAL(&sin->sin6_addr)) {
+ /* XXX: should scope id be filled in the kernel? */
+ if (sin->sin6_scope_id == 0)
+ sin->sin6_scope_id = sdl->sdl_index;
+ }
+ getnameinfo((struct sockaddr *)sin, sin->sin6_len, host_buf,
+ sizeof(host_buf), NULL, 0, (nflag ? NI_NUMERICHOST : 0));
+ if (cflag) {
+#ifdef RTF_WASCLONED
+ if (rtm->rtm_flags & RTF_WASCLONED)
+ delete(host_buf);
+#elif defined(RTF_CLONED)
+ if (rtm->rtm_flags & RTF_CLONED)
+ delete(host_buf);
+#else
+ if (rtm->rtm_flags & RTF_PINNED)
+ continue;
+ delete(host_buf);
+#endif
+ continue;
+ }
+ gettimeofday(&now, 0);
+ if (tflag)
+ ts_print(&now);
+
+ addrwidth = strlen(host_buf);
+ if (addrwidth < W_ADDR)
+ addrwidth = W_ADDR;
+ llwidth = strlen(ether_str(sdl));
+ if (W_ADDR + W_LL - addrwidth > llwidth)
+ llwidth = W_ADDR + W_LL - addrwidth;
+ ifname = if_indextoname(sdl->sdl_index, ifix_buf);
+ if (!ifname)
+ ifname = "?";
+ ifwidth = strlen(ifname);
+ if (W_ADDR + W_LL + W_IF - addrwidth - llwidth > ifwidth)
+ ifwidth = W_ADDR + W_LL + W_IF - addrwidth - llwidth;
+
+ printf("%-*.*s %-*.*s %*.*s", addrwidth, addrwidth, host_buf,
+ llwidth, llwidth, ether_str(sdl), ifwidth, ifwidth, ifname);
+
+ /* Print neighbor discovery specific information */
+ expire = rtm->rtm_rmx.rmx_expire;
+ if (expire > now.tv_sec)
+ printf(" %-9.9s", sec2str(expire - now.tv_sec));
+ else if (expire == 0)
+ printf(" %-9.9s", "permanent");
+ else
+ printf(" %-9.9s", "expired");
+
+ switch (rtm->rtm_rmx.rmx_state) {
+ case ND6_LLINFO_NOSTATE:
+ printf(" N");
+ break;
+#ifdef ND6_LLINFO_WAITDELETE
+ case ND6_LLINFO_WAITDELETE:
+ printf(" W");
+ break;
+#endif
+ case ND6_LLINFO_INCOMPLETE:
+ printf(" I");
+ break;
+ case ND6_LLINFO_REACHABLE:
+ printf(" R");
+ break;
+ case ND6_LLINFO_STALE:
+ printf(" S");
+ break;
+ case ND6_LLINFO_DELAY:
+ printf(" D");
+ break;
+ case ND6_LLINFO_PROBE:
+ printf(" P");
+ break;
+ default:
+ printf(" ?");
+ break;
+ }
+
+ isrouter = rtm->rtm_flags & RTF_GATEWAY;
+ prbs = rtm->rtm_rmx.rmx_pksent;
+
+ /*
+ * other flags. R: router, P: proxy, W: ??
+ */
+ if ((rtm->rtm_addrs & RTA_NETMASK) == 0) {
+ snprintf(flgbuf, sizeof(flgbuf), "%s%s",
+ isrouter ? "R" : "",
+ (rtm->rtm_flags & RTF_ANNOUNCE) ? "p" : "");
+ } else {
+#if 0 /* W and P are mystery even for us */
+ sin = (struct sockaddr_in6 *)
+ (sdl->sdl_len + (char *)sdl);
+ snprintf(flgbuf, sizeof(flgbuf), "%s%s%s%s",
+ isrouter ? "R" : "",
+ !IN6_IS_ADDR_UNSPECIFIED(&sin->sin6_addr) ? "P" : "",
+ (sin->sin6_len != sizeof(struct sockaddr_in6)) ? "W" : "",
+ (rtm->rtm_flags & RTF_ANNOUNCE) ? "p" : "");
+#else
+ snprintf(flgbuf, sizeof(flgbuf), "%s%s",
+ isrouter ? "R" : "",
+ (rtm->rtm_flags & RTF_ANNOUNCE) ? "p" : "");
+#endif
+ }
+ printf(" %s", flgbuf);
+
+ if (prbs)
+ printf(" %d", prbs);
+
+ printf("\n");
+ }
+ if (buf != NULL)
+ free(buf);
+
+ if (repeat) {
+ printf("\n");
+ fflush(stdout);
+ sleep(repeat);
+ goto again;
+ }
+}
+
+static struct in6_nbrinfo *
+getnbrinfo(struct in6_addr *addr, int ifindex, int warning)
+{
+ static struct in6_nbrinfo nbi;
+ int s;
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0)
+ err(1, "socket");
+
+ bzero(&nbi, sizeof(nbi));
+ if_indextoname(ifindex, nbi.ifname);
+ nbi.addr = *addr;
+ if (ioctl(s, SIOCGNBRINFO_IN6, (caddr_t)&nbi) < 0) {
+ if (warning)
+ warn("ioctl(SIOCGNBRINFO_IN6)");
+ close(s);
+ return(NULL);
+ }
+
+ close(s);
+ return(&nbi);
+}
+
+static char *
+ether_str(struct sockaddr_dl *sdl)
+{
+ static char hbuf[NI_MAXHOST];
+
+ if (sdl->sdl_alen == ETHER_ADDR_LEN) {
+ strlcpy(hbuf, ether_ntoa((struct ether_addr *)LLADDR(sdl)),
+ sizeof(hbuf));
+ } else if (sdl->sdl_alen) {
+ int n = sdl->sdl_nlen > 0 ? sdl->sdl_nlen + 1 : 0;
+ snprintf(hbuf, sizeof(hbuf), "%s", link_ntoa(sdl) + n);
+ } else
+ snprintf(hbuf, sizeof(hbuf), "(incomplete)");
+
+ return(hbuf);
+}
+
+static int
+ndp_ether_aton(char *a, u_char *n)
+{
+ int i, o[6];
+
+ i = sscanf(a, "%x:%x:%x:%x:%x:%x", &o[0], &o[1], &o[2],
+ &o[3], &o[4], &o[5]);
+ if (i != 6) {
+ fprintf(stderr, "ndp: invalid Ethernet address '%s'\n", a);
+ return (1);
+ }
+ for (i = 0; i < 6; i++)
+ n[i] = o[i];
+ return (0);
+}
+
+static void
+usage()
+{
+ printf("usage: ndp [-nt] hostname\n");
+ printf(" ndp [-nt] -a | -c | -p | -r | -H | -P | -R\n");
+ printf(" ndp [-nt] -A wait\n");
+ printf(" ndp [-nt] -d hostname\n");
+ printf(" ndp [-nt] -f filename\n");
+ printf(" ndp [-nt] -i interface [flags...]\n");
+#ifdef SIOCSDEFIFACE_IN6
+ printf(" ndp [-nt] -I [interface|delete]\n");
+#endif
+ printf(" ndp [-nt] -s nodename etheraddr [temp] [proxy]\n");
+ exit(1);
+}
+
+static int
+rtmsg(int cmd)
+{
+ static int seq;
+ int rlen;
+ register struct rt_msghdr *rtm = &m_rtmsg.m_rtm;
+ register char *cp = m_rtmsg.m_space;
+ register int l;
+
+ errno = 0;
+ if (cmd == RTM_DELETE)
+ goto doit;
+ bzero((char *)&m_rtmsg, sizeof(m_rtmsg));
+ rtm->rtm_flags = flags;
+ rtm->rtm_version = RTM_VERSION;
+
+ switch (cmd) {
+ default:
+ fprintf(stderr, "ndp: internal wrong cmd\n");
+ exit(1);
+ case RTM_ADD:
+ rtm->rtm_addrs |= RTA_GATEWAY;
+ if (expire_time) {
+ rtm->rtm_rmx.rmx_expire = expire_time;
+ rtm->rtm_inits = RTV_EXPIRE;
+ }
+ rtm->rtm_flags |= (RTF_HOST | RTF_STATIC | RTF_LLDATA);
+#if 0 /* we don't support ipv6addr/128 type proxying */
+ if (rtm->rtm_flags & RTF_ANNOUNCE) {
+ rtm->rtm_flags &= ~RTF_HOST;
+ rtm->rtm_addrs |= RTA_NETMASK;
+ }
+#endif
+ /* FALLTHROUGH */
+ case RTM_GET:
+ rtm->rtm_addrs |= RTA_DST;
+ }
+
+ NEXTADDR(RTA_DST, sin_m);
+ NEXTADDR(RTA_GATEWAY, sdl_m);
+#if 0 /* we don't support ipv6addr/128 type proxying */
+ memset(&so_mask.sin6_addr, 0xff, sizeof(so_mask.sin6_addr));
+ NEXTADDR(RTA_NETMASK, so_mask);
+#endif
+
+ rtm->rtm_msglen = cp - (char *)&m_rtmsg;
+doit:
+ l = rtm->rtm_msglen;
+ rtm->rtm_seq = ++seq;
+ rtm->rtm_type = cmd;
+ if ((rlen = write(s, (char *)&m_rtmsg, l)) < 0) {
+ if (errno != ESRCH || cmd != RTM_DELETE) {
+ err(1, "writing to routing socket");
+ /* NOTREACHED */
+ }
+ }
+ do {
+ l = read(s, (char *)&m_rtmsg, sizeof(m_rtmsg));
+ } while (l > 0 && (rtm->rtm_seq != seq || rtm->rtm_pid != pid));
+ if (l < 0)
+ (void) fprintf(stderr, "ndp: read from routing socket: %s\n",
+ strerror(errno));
+ return (0);
+}
+
+static void
+ifinfo(char *ifname, int argc, char **argv)
+{
+ struct in6_ndireq nd;
+ int i, s;
+ u_int32_t newflags;
+#ifdef IPV6CTL_USETEMPADDR
+ u_int8_t nullbuf[8];
+#endif
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
+ err(1, "socket");
+ /* NOTREACHED */
+ }
+ bzero(&nd, sizeof(nd));
+ strlcpy(nd.ifname, ifname, sizeof(nd.ifname));
+ if (ioctl(s, SIOCGIFINFO_IN6, (caddr_t)&nd) < 0) {
+ err(1, "ioctl(SIOCGIFINFO_IN6)");
+ /* NOTREACHED */
+ }
+#define ND nd.ndi
+ newflags = ND.flags;
+ for (i = 0; i < argc; i++) {
+ int clear = 0;
+ char *cp = argv[i];
+
+ if (*cp == '-') {
+ clear = 1;
+ cp++;
+ }
+
+#define SETFLAG(s, f) \
+ do {\
+ if (strcmp(cp, (s)) == 0) {\
+ if (clear)\
+ newflags &= ~(f);\
+ else\
+ newflags |= (f);\
+ }\
+ } while (0)
+/*
+ * XXX: this macro is not 100% correct, in that it matches "nud" against
+ * "nudbogus". But we just let it go since this is minor.
+ */
+#define SETVALUE(f, v) \
+ do { \
+ char *valptr; \
+ unsigned long newval; \
+ v = 0; /* unspecified */ \
+ if (strncmp(cp, f, strlen(f)) == 0) { \
+ valptr = strchr(cp, '='); \
+ if (valptr == NULL) \
+ err(1, "syntax error in %s field", (f)); \
+ errno = 0; \
+ newval = strtoul(++valptr, NULL, 0); \
+ if (errno) \
+ err(1, "syntax error in %s's value", (f)); \
+ v = newval; \
+ } \
+ } while (0)
+
+ SETFLAG("disabled", ND6_IFF_IFDISABLED);
+ SETFLAG("nud", ND6_IFF_PERFORMNUD);
+#ifdef ND6_IFF_ACCEPT_RTADV
+ SETFLAG("accept_rtadv", ND6_IFF_ACCEPT_RTADV);
+#endif
+#ifdef ND6_IFF_AUTO_LINKLOCAL
+ SETFLAG("auto_linklocal", ND6_IFF_AUTO_LINKLOCAL);
+#endif
+#ifdef ND6_IFF_NO_PREFER_IFACE
+ SETFLAG("no_prefer_iface", ND6_IFF_NO_PREFER_IFACE);
+#endif
+ SETVALUE("basereachable", ND.basereachable);
+ SETVALUE("retrans", ND.retrans);
+ SETVALUE("curhlim", ND.chlim);
+
+ ND.flags = newflags;
+ if (ioctl(s, SIOCSIFINFO_IN6, (caddr_t)&nd) < 0) {
+ err(1, "ioctl(SIOCSIFINFO_IN6)");
+ /* NOTREACHED */
+ }
+#undef SETFLAG
+#undef SETVALUE
+ }
+
+ if (!ND.initialized) {
+ errx(1, "%s: not initialized yet", ifname);
+ /* NOTREACHED */
+ }
+
+ if (ioctl(s, SIOCGIFINFO_IN6, (caddr_t)&nd) < 0) {
+ err(1, "ioctl(SIOCGIFINFO_IN6)");
+ /* NOTREACHED */
+ }
+ printf("linkmtu=%d", ND.linkmtu);
+ printf(", maxmtu=%d", ND.maxmtu);
+ printf(", curhlim=%d", ND.chlim);
+ printf(", basereachable=%ds%dms",
+ ND.basereachable / 1000, ND.basereachable % 1000);
+ printf(", reachable=%ds", ND.reachable);
+ printf(", retrans=%ds%dms", ND.retrans / 1000, ND.retrans % 1000);
+#ifdef IPV6CTL_USETEMPADDR
+ memset(nullbuf, 0, sizeof(nullbuf));
+ if (memcmp(nullbuf, ND.randomid, sizeof(nullbuf)) != 0) {
+ int j;
+ u_int8_t *rbuf;
+
+ for (i = 0; i < 3; i++) {
+ switch (i) {
+ case 0:
+ printf("\nRandom seed(0): ");
+ rbuf = ND.randomseed0;
+ break;
+ case 1:
+ printf("\nRandom seed(1): ");
+ rbuf = ND.randomseed1;
+ break;
+ case 2:
+ printf("\nRandom ID: ");
+ rbuf = ND.randomid;
+ break;
+ default:
+ errx(1, "impossible case for tempaddr display");
+ }
+ for (j = 0; j < 8; j++)
+ printf("%02x", rbuf[j]);
+ }
+ }
+#endif
+ if (ND.flags) {
+ printf("\nFlags: ");
+#ifdef ND6_IFF_IFDISABLED
+ if ((ND.flags & ND6_IFF_IFDISABLED))
+ printf("disabled ");
+#endif
+ if ((ND.flags & ND6_IFF_PERFORMNUD))
+ printf("nud ");
+#ifdef ND6_IFF_ACCEPT_RTADV
+ if ((ND.flags & ND6_IFF_ACCEPT_RTADV))
+ printf("accept_rtadv ");
+#endif
+#ifdef ND6_IFF_AUTO_LINKLOCAL
+ if ((ND.flags & ND6_IFF_AUTO_LINKLOCAL))
+ printf("auto_linklocal ");
+#endif
+#ifdef ND6_IFF_NO_PREFER_IFACE
+ if ((ND.flags & ND6_IFF_NO_PREFER_IFACE))
+ printf("no_prefer_iface ");
+#endif
+ }
+ putc('\n', stdout);
+#undef ND
+
+ close(s);
+}
+
+#ifndef ND_RA_FLAG_RTPREF_MASK /* XXX: just for compilation on *BSD release */
+#define ND_RA_FLAG_RTPREF_MASK 0x18 /* 00011000 */
+#endif
+
+static void
+rtrlist()
+{
+ int mib[] = { CTL_NET, PF_INET6, IPPROTO_ICMPV6, ICMPV6CTL_ND6_DRLIST };
+ char *buf;
+ struct in6_defrouter *p, *ep;
+ size_t l;
+ struct timeval now;
+
+ if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), NULL, &l, NULL, 0) < 0) {
+ err(1, "sysctl(ICMPV6CTL_ND6_DRLIST)");
+ /*NOTREACHED*/
+ }
+ if (l == 0)
+ return;
+ buf = malloc(l);
+ if (!buf) {
+ err(1, "malloc");
+ /*NOTREACHED*/
+ }
+ if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), buf, &l, NULL, 0) < 0) {
+ err(1, "sysctl(ICMPV6CTL_ND6_DRLIST)");
+ /*NOTREACHED*/
+ }
+
+ ep = (struct in6_defrouter *)(buf + l);
+ for (p = (struct in6_defrouter *)buf; p < ep; p++) {
+ int rtpref;
+
+ if (getnameinfo((struct sockaddr *)&p->rtaddr,
+ p->rtaddr.sin6_len, host_buf, sizeof(host_buf), NULL, 0,
+ (nflag ? NI_NUMERICHOST : 0)) != 0)
+ strlcpy(host_buf, "?", sizeof(host_buf));
+
+ printf("%s if=%s", host_buf,
+ if_indextoname(p->if_index, ifix_buf));
+ printf(", flags=%s%s",
+ p->flags & ND_RA_FLAG_MANAGED ? "M" : "",
+ p->flags & ND_RA_FLAG_OTHER ? "O" : "");
+ rtpref = ((p->flags & ND_RA_FLAG_RTPREF_MASK) >> 3) & 0xff;
+ printf(", pref=%s", rtpref_str[rtpref]);
+
+ gettimeofday(&now, 0);
+ if (p->expire == 0)
+ printf(", expire=Never\n");
+ else
+ printf(", expire=%s\n",
+ sec2str(p->expire - now.tv_sec));
+ }
+ free(buf);
+}
+
+static void
+plist()
+{
+ int mib[] = { CTL_NET, PF_INET6, IPPROTO_ICMPV6, ICMPV6CTL_ND6_PRLIST };
+ char *buf;
+ struct in6_prefix *p, *ep, *n;
+ struct sockaddr_in6 *advrtr;
+ size_t l;
+ struct timeval now;
+ const int niflags = NI_NUMERICHOST;
+ int ninflags = nflag ? NI_NUMERICHOST : 0;
+ char namebuf[NI_MAXHOST];
+
+ if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), NULL, &l, NULL, 0) < 0) {
+ err(1, "sysctl(ICMPV6CTL_ND6_PRLIST)");
+ /*NOTREACHED*/
+ }
+ buf = malloc(l);
+ if (!buf) {
+ err(1, "malloc");
+ /*NOTREACHED*/
+ }
+ if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), buf, &l, NULL, 0) < 0) {
+ err(1, "sysctl(ICMPV6CTL_ND6_PRLIST)");
+ /*NOTREACHED*/
+ }
+
+ ep = (struct in6_prefix *)(buf + l);
+ for (p = (struct in6_prefix *)buf; p < ep; p = n) {
+ advrtr = (struct sockaddr_in6 *)(p + 1);
+ n = (struct in6_prefix *)&advrtr[p->advrtrs];
+
+ if (getnameinfo((struct sockaddr *)&p->prefix,
+ p->prefix.sin6_len, namebuf, sizeof(namebuf),
+ NULL, 0, niflags) != 0)
+ strlcpy(namebuf, "?", sizeof(namebuf));
+ printf("%s/%d if=%s\n", namebuf, p->prefixlen,
+ if_indextoname(p->if_index, ifix_buf));
+
+ gettimeofday(&now, 0);
+ /*
+ * meaning of fields, especially flags, is very different
+ * by origin. notify the difference to the users.
+ */
+ printf("flags=%s%s%s%s%s",
+ p->raflags.onlink ? "L" : "",
+ p->raflags.autonomous ? "A" : "",
+ (p->flags & NDPRF_ONLINK) != 0 ? "O" : "",
+ (p->flags & NDPRF_DETACHED) != 0 ? "D" : "",
+#ifdef NDPRF_HOME
+ (p->flags & NDPRF_HOME) != 0 ? "H" : ""
+#else
+ ""
+#endif
+ );
+ if (p->vltime == ND6_INFINITE_LIFETIME)
+ printf(" vltime=infinity");
+ else
+ printf(" vltime=%lu", (unsigned long)p->vltime);
+ if (p->pltime == ND6_INFINITE_LIFETIME)
+ printf(", pltime=infinity");
+ else
+ printf(", pltime=%lu", (unsigned long)p->pltime);
+ if (p->expire == 0)
+ printf(", expire=Never");
+ else if (p->expire >= now.tv_sec)
+ printf(", expire=%s",
+ sec2str(p->expire - now.tv_sec));
+ else
+ printf(", expired");
+ printf(", ref=%d", p->refcnt);
+ printf("\n");
+ /*
+ * "advertising router" list is meaningful only if the prefix
+ * information is from RA.
+ */
+ if (p->advrtrs) {
+ int j;
+ struct sockaddr_in6 *sin6;
+
+ sin6 = advrtr;
+ printf(" advertised by\n");
+ for (j = 0; j < p->advrtrs; j++) {
+ struct in6_nbrinfo *nbi;
+
+ if (getnameinfo((struct sockaddr *)sin6,
+ sin6->sin6_len, namebuf, sizeof(namebuf),
+ NULL, 0, ninflags) != 0)
+ strlcpy(namebuf, "?", sizeof(namebuf));
+ printf(" %s", namebuf);
+
+ nbi = getnbrinfo(&sin6->sin6_addr,
+ p->if_index, 0);
+ if (nbi) {
+ switch (nbi->state) {
+ case ND6_LLINFO_REACHABLE:
+ case ND6_LLINFO_STALE:
+ case ND6_LLINFO_DELAY:
+ case ND6_LLINFO_PROBE:
+ printf(" (reachable)\n");
+ break;
+ default:
+ printf(" (unreachable)\n");
+ }
+ } else
+ printf(" (no neighbor state)\n");
+ sin6++;
+ }
+ } else
+ printf(" No advertising router\n");
+ }
+ free(buf);
+}
+
+static void
+pfx_flush()
+{
+ char dummyif[IFNAMSIZ+8];
+ int s;
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0)
+ err(1, "socket");
+ strlcpy(dummyif, "lo0", sizeof(dummyif)); /* dummy */
+ if (ioctl(s, SIOCSPFXFLUSH_IN6, (caddr_t)&dummyif) < 0)
+ err(1, "ioctl(SIOCSPFXFLUSH_IN6)");
+}
+
+static void
+rtr_flush()
+{
+ char dummyif[IFNAMSIZ+8];
+ int s;
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0)
+ err(1, "socket");
+ strlcpy(dummyif, "lo0", sizeof(dummyif)); /* dummy */
+ if (ioctl(s, SIOCSRTRFLUSH_IN6, (caddr_t)&dummyif) < 0)
+ err(1, "ioctl(SIOCSRTRFLUSH_IN6)");
+
+ close(s);
+}
+
+static void
+harmonize_rtr()
+{
+ char dummyif[IFNAMSIZ+8];
+ int s;
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0)
+ err(1, "socket");
+ strlcpy(dummyif, "lo0", sizeof(dummyif)); /* dummy */
+ if (ioctl(s, SIOCSNDFLUSH_IN6, (caddr_t)&dummyif) < 0)
+ err(1, "ioctl(SIOCSNDFLUSH_IN6)");
+
+ close(s);
+}
+
+#ifdef SIOCSDEFIFACE_IN6 /* XXX: check SIOCGDEFIFACE_IN6 as well? */
+static void
+setdefif(char *ifname)
+{
+ struct in6_ndifreq ndifreq;
+ unsigned int ifindex;
+
+ if (strcasecmp(ifname, "delete") == 0)
+ ifindex = 0;
+ else {
+ if ((ifindex = if_nametoindex(ifname)) == 0)
+ err(1, "failed to resolve i/f index for %s", ifname);
+ }
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0)
+ err(1, "socket");
+
+ strlcpy(ndifreq.ifname, "lo0", sizeof(ndifreq.ifname)); /* dummy */
+ ndifreq.ifindex = ifindex;
+
+ if (ioctl(s, SIOCSDEFIFACE_IN6, (caddr_t)&ndifreq) < 0)
+ err(1, "ioctl(SIOCSDEFIFACE_IN6)");
+
+ close(s);
+}
+
+static void
+getdefif()
+{
+ struct in6_ndifreq ndifreq;
+ char ifname[IFNAMSIZ+8];
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0)
+ err(1, "socket");
+
+ memset(&ndifreq, 0, sizeof(ndifreq));
+ strlcpy(ndifreq.ifname, "lo0", sizeof(ndifreq.ifname)); /* dummy */
+
+ if (ioctl(s, SIOCGDEFIFACE_IN6, (caddr_t)&ndifreq) < 0)
+ err(1, "ioctl(SIOCGDEFIFACE_IN6)");
+
+ if (ndifreq.ifindex == 0)
+ printf("No default interface.\n");
+ else {
+ if ((if_indextoname(ndifreq.ifindex, ifname)) == NULL)
+ err(1, "failed to resolve ifname for index %lu",
+ ndifreq.ifindex);
+ printf("ND default interface = %s\n", ifname);
+ }
+
+ close(s);
+}
+#endif
+
+static char *
+sec2str(time_t total)
+{
+ static char result[256];
+ int days, hours, mins, secs;
+ int first = 1;
+ char *p = result;
+ char *ep = &result[sizeof(result)];
+ int n;
+
+ days = total / 3600 / 24;
+ hours = (total / 3600) % 24;
+ mins = (total / 60) % 60;
+ secs = total % 60;
+
+ if (days) {
+ first = 0;
+ n = snprintf(p, ep - p, "%dd", days);
+ if (n < 0 || n >= ep - p)
+ return "?";
+ p += n;
+ }
+ if (!first || hours) {
+ first = 0;
+ n = snprintf(p, ep - p, "%dh", hours);
+ if (n < 0 || n >= ep - p)
+ return "?";
+ p += n;
+ }
+ if (!first || mins) {
+ first = 0;
+ n = snprintf(p, ep - p, "%dm", mins);
+ if (n < 0 || n >= ep - p)
+ return "?";
+ p += n;
+ }
+ snprintf(p, ep - p, "%ds", secs);
+
+ return(result);
+}
+
+/*
+ * Print the timestamp
+ * from tcpdump/util.c
+ */
+static void
+ts_print(const struct timeval *tvp)
+{
+ int s;
+
+ /* Default */
+ s = (tvp->tv_sec + thiszone) % 86400;
+ (void)printf("%02d:%02d:%02d.%06u ",
+ s / 3600, (s % 3600) / 60, s % 60, (u_int32_t)tvp->tv_usec);
+}
+
+#undef NEXTADDR
diff --git a/usr.sbin/newsyslog/Makefile b/usr.sbin/newsyslog/Makefile
new file mode 100644
index 0000000..1c63e73
--- /dev/null
+++ b/usr.sbin/newsyslog/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= newsyslog
+MAN= newsyslog.8 newsyslog.conf.5
+SRCS= newsyslog.c ptimes.c
+
+.if ${MK_TESTS} != "no"
+SUBDIR+= tests
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/newsyslog/Makefile.depend b/usr.sbin/newsyslog/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/newsyslog/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/newsyslog/extern.h b/usr.sbin/newsyslog/extern.h
new file mode 100644
index 0000000..c350226
--- /dev/null
+++ b/usr.sbin/newsyslog/extern.h
@@ -0,0 +1,68 @@
+/*-
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * Copyright (c) 2003 - Garance Alistair Drosehn <gad@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.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the FreeBSD Project.
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * $FreeBSD$
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ */
+
+#include <sys/cdefs.h>
+#include <time.h>
+
+#define PTM_PARSE_ISO8601 0x0001 /* Parse ISO-standard format */
+#define PTM_PARSE_DWM 0x0002 /* Parse Day-Week-Month format */
+#define PTM_PARSE_MATCHDOM 0x0004 /* If the user specifies a day-of-month,
+ * then the result should be a month
+ * which actually has that day. Eg:
+ * the user requests "day 31" when
+ * the present month is February. */
+
+struct ptime_data;
+
+/* Some global variables from newsyslog.c which might be of interest */
+extern int dbg_at_times; /* cmdline debugging option */
+extern int noaction; /* command-line option */
+extern int verbose; /* command-line option */
+extern struct ptime_data *dbg_timenow;
+
+__BEGIN_DECLS
+struct ptime_data *ptime_init(const struct ptime_data *_optsrc);
+int ptime_adjust4dst(struct ptime_data *_ptime, const struct
+ ptime_data *_dstsrc);
+int ptime_free(struct ptime_data *_ptime);
+int ptime_relparse(struct ptime_data *_ptime, int _parseopts,
+ time_t _basetime, const char *_str);
+const char *ptimeget_ctime(const struct ptime_data *_ptime);
+double ptimeget_diff(const struct ptime_data *_minuend,
+ const struct ptime_data *_subtrahend);
+time_t ptimeget_secs(const struct ptime_data *_ptime);
+int ptimeset_nxtime(struct ptime_data *_ptime);
+int ptimeset_time(struct ptime_data *_ptime, time_t _secs);
+__END_DECLS
diff --git a/usr.sbin/newsyslog/newsyslog.8 b/usr.sbin/newsyslog/newsyslog.8
new file mode 100644
index 0000000..ba3db8a
--- /dev/null
+++ b/usr.sbin/newsyslog/newsyslog.8
@@ -0,0 +1,308 @@
+.\" This file contains changes from the Open Software Foundation.
+.\"
+.\" from: @(#)newsyslog.8
+.\" $FreeBSD$
+.\"
+.\" Copyright 1988, 1989 by the Massachusetts Institute of Technology
+.\"
+.\" Permission to use, copy, modify, and distribute this software
+.\" and its documentation for any purpose and without fee is
+.\" hereby granted, provided that the above copyright notice
+.\" appear in all copies and that both that copyright notice and
+.\" this permission notice appear in supporting documentation,
+.\" and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
+.\" used in advertising or publicity pertaining to distribution
+.\" of the software without specific, written prior permission.
+.\" M.I.T. and the M.I.T. S.I.P.B. make no representations about
+.\" the suitability of this software for any purpose. It is
+.\" provided "as is" without express or implied warranty.
+.\"
+.Dd September 23, 2014
+.Dt NEWSYSLOG 8
+.Os
+.Sh NAME
+.Nm newsyslog
+.Nd maintain system log files to manageable sizes
+.Sh SYNOPSIS
+.Nm
+.Op Fl CFNPnrsv
+.Op Fl a Ar directory
+.Op Fl d Ar directory
+.Op Fl f Ar config_file
+.Op Fl S Ar pidfile
+.Op Fl t Ar timefmt
+.Op Oo Fl R Ar tagname Oc Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility should be scheduled to run periodically by
+.Xr cron 8 .
+When it is executed it archives log files if necessary.
+If a log file
+is determined to require archiving,
+.Nm
+rearranges the files so that
+.Dq Va logfile
+is empty,
+.Dq Va logfile Ns Li \&.0
+has
+the last period's logs in it,
+.Dq Va logfile Ns Li \&.1
+has the next to last
+period's logs in it, and so on, up to a user-specified number of
+archived logs.
+It is also possible to let archived log filenames be created using the
+time the log file was archived instead of the sequential number using
+the
+.Fl t
+option.
+Optionally the archived logs can be compressed to save
+space.
+.Pp
+A log can be archived for three reasons:
+.Bl -enum -offset indent
+.It
+It is larger than the configured size (in kilobytes).
+.It
+A configured number of hours have elapsed since the log was last
+archived.
+.It
+This is the specific configured hour for rotation of the log.
+.El
+.Pp
+The granularity of
+.Nm
+is dependent on how often it is scheduled to run by
+.Xr cron 8 .
+Since the program is quite fast, it may be scheduled to run every hour
+without any ill effects,
+and mode three (above) assumes that this is so.
+.Sh OPTIONS
+The following options can be used with
+.Nm :
+.Bl -tag -width indent
+.It Fl f Ar config_file
+Instruct
+.Nm
+to use
+.Ar config_file
+instead of
+.Pa /etc/newsyslog.conf
+for its configuration file.
+.It Fl a Ar directory
+Specify a
+.Ar directory
+into which archived log files will be written.
+If a relative path is given,
+it is appended to the path of each log file
+and the resulting path is used as the directory
+into which the archived log for that log file will be written.
+If an absolute path is given,
+all archived logs are written into the given
+.Ar directory .
+If any component of the path
+.Ar directory
+does not exist,
+it will be created when
+.Nm
+is run.
+.It Fl d Ar directory
+Specify a
+.Ar directory
+which all log files will be relative to.
+To allow archiving of logs outside the root, the
+.Ar directory
+passed to the
+.Fl a
+option is unaffected.
+.It Fl v
+Place
+.Nm
+in verbose mode.
+In this mode it will print out each log and its
+reasons for either trimming that log or skipping it.
+.It Fl n
+Cause
+.Nm
+not to trim the logs, but to print out what it would do if this option
+were not specified. This option implies the
+.Fl r
+option.
+.It Fl r
+Remove the restriction that
+.Nm
+must be running as root.
+Of course,
+.Nm
+will not be able to send a HUP signal to
+.Xr syslogd 8
+so this option should only be used in debugging.
+.It Fl s
+Specify that
+.Nm
+should not send any signals to any daemon processes that it would
+normally signal when rotating a log file.
+For any log file which is rotated, this option will usually also
+mean the rotated log file will not be compressed if there is a
+daemon which would have been signalled without this option.
+However, this option is most likely to be useful when specified
+with the
+.Fl R
+option, and in that case the compression will be done.
+.It Fl t Ar timefmt
+If specified
+.Nm
+will create the
+.Dq rotated
+logfiles using the specified time format instead of the default
+sequential filenames.
+The filename used will be kept until it is deleted.
+The time format is described in the
+.Xr strftime 3
+manual page.
+If the
+.Ar timefmt
+argument is set to an empty string or the string
+.Dq DEFAULT ,
+the default built in time format
+is used.
+If the
+.Ar timefmt
+string is changed the old files created using the previous time format
+will not be automatically removed (unless the new format is very
+similar to the old format).
+This is also the case when changing from sequential filenames to time
+based file names, and the other way around.
+The time format should contain at least year, month, day, and hour to
+make sure rotating of old logfiles can select the correct logfiles.
+.It Fl C
+If specified once, then
+.Nm
+will create any log files which do not exist, and which have the
+.Sy C
+flag specified in their config file entry.
+If specified multiple times, then
+.Nm
+will create all log files which do not already exist.
+If log files are given on the command-line, then the
+.Fl C
+or
+.Fl CC
+will only apply to those specific log files.
+.It Fl F
+Force
+.Nm
+to trim the logs, even if the trim conditions have not been met.
+This
+option is useful for diagnosing system problems by providing you with
+fresh logs that contain only the problems.
+.It Fl N
+Do not perform any rotations.
+This option is intended to be used with the
+.Fl C
+or
+.Fl CC
+options when creating log files is the only objective.
+.It Fl P
+Prevent further action if we should send signal but the
+.Dq pidfile
+is empty or does not exist.
+.It Fl R Ar tagname
+Specify that
+.Nm
+should rotate a given list of files, even if trim conditions are not
+met for those files.
+The
+.Ar tagname
+is only used in the messages written to the log files which are
+rotated.
+This differs from the
+.Fl F
+option in that one or more log files must also be specified, so that
+.Nm
+will only operate on those specific files.
+This option is mainly intended for the daemons or programs which write
+some log files, and want to trigger a rotate based on their own criteria.
+With this option they can execute
+.Nm
+to trigger the rotate when they want it to happen, and still give the
+system administrator a way to specify the rules of rotation (such as how
+many backup copies are kept, and what kind of compression is done).
+When a daemon does execute
+.Nm
+with the
+.Fl R
+option, it should make sure all of the log files are closed before
+calling
+.Nm ,
+and then it should re-open the files after
+.Nm
+returns.
+Usually the calling process will also want to specify the
+.Fl s
+option, so
+.Nm
+will not send a signal to the very process which called it to force
+the rotate.
+Skipping the signal step will also mean that
+.Nm
+will return faster, since
+.Nm
+normally waits a few seconds after any signal that is sent.
+.It Fl S Ar pidfile
+Use
+.Ar pidfile
+as
+.Xr syslogd 8 Ns 's
+pidfile.
+.El
+.Pp
+If additional command line arguments are given,
+.Nm
+will only examine log files that match those arguments; otherwise, it
+will examine all files listed in the configuration file.
+.Sh FILES
+.Bl -tag -width /usr/local/etc/newsyslog.conf.d -compact
+.It Pa /etc/newsyslog.conf
+.Nm
+configuration file
+.It Pa /etc/newsyslog.conf.d
+Each file in this directory will be included by the default
+.Pa newsyslog.conf .
+.It Pa /usr/local/etc/newsyslog.conf.d
+Each file in this directory will be included by the default
+.Pa newsyslog.conf .
+.El
+.Sh COMPATIBILITY
+Previous versions of the
+.Nm
+utility used the dot (``.'') character to
+distinguish the group name.
+Beginning with
+.Fx 3.3 ,
+this has been changed to a colon (``:'') character so that user and group
+names may contain the dot character.
+The dot (``.'') character is still
+accepted for backwards compatibility.
+.Sh SEE ALSO
+.Xr bzip2 1 ,
+.Xr gzip 1 ,
+.Xr xz 1 ,
+.Xr syslog 3 ,
+.Xr newsyslog.conf 5 ,
+.Xr chown 8 ,
+.Xr syslogd 8
+.Sh HISTORY
+The
+.Nm
+utility originated from
+.Nx
+and first appeared in
+.Fx 2.2 .
+.Sh AUTHORS
+.An Theodore Ts'o ,
+MIT Project Athena
+.Pp
+Copyright 1987, Massachusetts Institute of Technology
+.Sh BUGS
+Does not yet automatically read the logs to find security breaches.
diff --git a/usr.sbin/newsyslog/newsyslog.c b/usr.sbin/newsyslog/newsyslog.c
new file mode 100644
index 0000000..9490cda
--- /dev/null
+++ b/usr.sbin/newsyslog/newsyslog.c
@@ -0,0 +1,2673 @@
+/*-
+ * ------+---------+---------+-------- + --------+---------+---------+---------*
+ * This file includes significant modifications done by:
+ * Copyright (c) 2003, 2004 - Garance Alistair Drosehn <gad@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.
+ *
+ * ------+---------+---------+-------- + --------+---------+---------+---------*
+ */
+
+/*
+ * This file contains changes from the Open Software Foundation.
+ */
+
+/*
+ * Copyright 1988, 1989 by the Massachusetts Institute of Technology
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
+ * used in advertising or publicity pertaining to distribution of the
+ * software without specific, written prior permission. M.I.T. and the M.I.T.
+ * S.I.P.B. make no representations about the suitability of this software
+ * for any purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ */
+
+/*
+ * newsyslog - roll over selected logs at the appropriate time, keeping the a
+ * specified number of backup files around.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#define OSF
+
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <glob.h>
+#include <grp.h>
+#include <paths.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <libgen.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "pathnames.h"
+#include "extern.h"
+
+/*
+ * Compression suffixes
+ */
+#ifndef COMPRESS_SUFFIX_GZ
+#define COMPRESS_SUFFIX_GZ ".gz"
+#endif
+
+#ifndef COMPRESS_SUFFIX_BZ2
+#define COMPRESS_SUFFIX_BZ2 ".bz2"
+#endif
+
+#ifndef COMPRESS_SUFFIX_XZ
+#define COMPRESS_SUFFIX_XZ ".xz"
+#endif
+
+#define COMPRESS_SUFFIX_MAXLEN MAX(MAX(sizeof(COMPRESS_SUFFIX_GZ),sizeof(COMPRESS_SUFFIX_BZ2)),sizeof(COMPRESS_SUFFIX_XZ))
+
+/*
+ * Compression types
+ */
+#define COMPRESS_TYPES 4 /* Number of supported compression types */
+
+#define COMPRESS_NONE 0
+#define COMPRESS_GZIP 1
+#define COMPRESS_BZIP2 2
+#define COMPRESS_XZ 3
+
+/*
+ * Bit-values for the 'flags' parsed from a config-file entry.
+ */
+#define CE_BINARY 0x0008 /* Logfile is in binary, do not add status */
+ /* messages to logfile(s) when rotating. */
+#define CE_NOSIGNAL 0x0010 /* There is no process to signal when */
+ /* trimming this file. */
+#define CE_TRIMAT 0x0020 /* trim file at a specific time. */
+#define CE_GLOB 0x0040 /* name of the log is file name pattern. */
+#define CE_SIGNALGROUP 0x0080 /* Signal a process-group instead of a single */
+ /* process when trimming this file. */
+#define CE_CREATE 0x0100 /* Create the log file if it does not exist. */
+#define CE_NODUMP 0x0200 /* Set 'nodump' on newly created log file. */
+#define CE_PID2CMD 0x0400 /* Replace PID file with a shell command.*/
+
+#define MIN_PID 5 /* Don't touch pids lower than this */
+#define MAX_PID 99999 /* was lower, see /usr/include/sys/proc.h */
+
+#define kbytes(size) (((size) + 1023) >> 10)
+
+#define DEFAULT_MARKER "<default>"
+#define DEBUG_MARKER "<debug>"
+#define INCLUDE_MARKER "<include>"
+#define DEFAULT_TIMEFNAME_FMT "%Y%m%dT%H%M%S"
+
+#define MAX_OLDLOGS 65536 /* Default maximum number of old logfiles */
+
+struct compress_types {
+ const char *flag; /* Flag in configuration file */
+ const char *suffix; /* Compression suffix */
+ const char *path; /* Path to compression program */
+};
+
+static const struct compress_types compress_type[COMPRESS_TYPES] = {
+ { "", "", "" }, /* no compression */
+ { "Z", COMPRESS_SUFFIX_GZ, _PATH_GZIP }, /* gzip compression */
+ { "J", COMPRESS_SUFFIX_BZ2, _PATH_BZIP2 }, /* bzip2 compression */
+ { "X", COMPRESS_SUFFIX_XZ, _PATH_XZ } /* xz compression */
+};
+
+struct conf_entry {
+ STAILQ_ENTRY(conf_entry) cf_nextp;
+ char *log; /* Name of the log */
+ char *pid_cmd_file; /* PID or command file */
+ char *r_reason; /* The reason this file is being rotated */
+ int firstcreate; /* Creating log for the first time (-C). */
+ int rotate; /* Non-zero if this file should be rotated */
+ int fsize; /* size found for the log file */
+ uid_t uid; /* Owner of log */
+ gid_t gid; /* Group of log */
+ int numlogs; /* Number of logs to keep */
+ int trsize; /* Size cutoff to trigger trimming the log */
+ int hours; /* Hours between log trimming */
+ struct ptime_data *trim_at; /* Specific time to do trimming */
+ unsigned int permissions; /* File permissions on the log */
+ int flags; /* CE_BINARY */
+ int compress; /* Compression */
+ int sig; /* Signal to send */
+ int def_cfg; /* Using the <default> rule for this file */
+};
+
+struct sigwork_entry {
+ SLIST_ENTRY(sigwork_entry) sw_nextp;
+ int sw_signum; /* the signal to send */
+ int sw_pidok; /* true if pid value is valid */
+ pid_t sw_pid; /* the process id from the PID file */
+ const char *sw_pidtype; /* "daemon" or "process group" */
+ int sw_runcmd; /* run command or send PID to signal */
+ char sw_fname[1]; /* file the PID was read from or shell cmd */
+};
+
+struct zipwork_entry {
+ SLIST_ENTRY(zipwork_entry) zw_nextp;
+ const struct conf_entry *zw_conf; /* for chown/perm/flag info */
+ const struct sigwork_entry *zw_swork; /* to know success of signal */
+ int zw_fsize; /* size of the file to compress */
+ char zw_fname[1]; /* the file to compress */
+};
+
+struct include_entry {
+ STAILQ_ENTRY(include_entry) inc_nextp;
+ const char *file; /* Name of file to process */
+};
+
+struct oldlog_entry {
+ char *fname; /* Filename of the log file */
+ time_t t; /* Parsed timestamp of the logfile */
+};
+
+typedef enum {
+ FREE_ENT, KEEP_ENT
+} fk_entry;
+
+STAILQ_HEAD(cflist, conf_entry);
+static SLIST_HEAD(swlisthead, sigwork_entry) swhead =
+ SLIST_HEAD_INITIALIZER(swhead);
+static SLIST_HEAD(zwlisthead, zipwork_entry) zwhead =
+ SLIST_HEAD_INITIALIZER(zwhead);
+STAILQ_HEAD(ilist, include_entry);
+
+int dbg_at_times; /* -D Show details of 'trim_at' code */
+
+static int archtodir = 0; /* Archive old logfiles to other directory */
+static int createlogs; /* Create (non-GLOB) logfiles which do not */
+ /* already exist. 1=='for entries with */
+ /* C flag', 2=='for all entries'. */
+int verbose = 0; /* Print out what's going on */
+static int needroot = 1; /* Root privs are necessary */
+int noaction = 0; /* Don't do anything, just show it */
+static int norotate = 0; /* Don't rotate */
+static int nosignal; /* Do not send any signals */
+static int enforcepid = 0; /* If PID file does not exist or empty, do nothing */
+static int force = 0; /* Force the trim no matter what */
+static int rotatereq = 0; /* -R = Always rotate the file(s) as given */
+ /* on the command (this also requires */
+ /* that a list of files *are* given on */
+ /* the run command). */
+static char *requestor; /* The name given on a -R request */
+static char *timefnamefmt = NULL;/* Use time based filenames instead of .0 */
+static char *archdirname; /* Directory path to old logfiles archive */
+static char *destdir = NULL; /* Directory to treat at root for logs */
+static const char *conf; /* Configuration file to use */
+
+struct ptime_data *dbg_timenow; /* A "timenow" value set via -D option */
+static struct ptime_data *timenow; /* The time to use for checking at-fields */
+
+#define DAYTIME_LEN 16
+static char daytime[DAYTIME_LEN];/* The current time in human readable form,
+ * used for rotation-tracking messages. */
+static char hostname[MAXHOSTNAMELEN]; /* hostname */
+
+static const char *path_syslogpid = _PATH_SYSLOGPID;
+
+static struct cflist *get_worklist(char **files);
+static void parse_file(FILE *cf, struct cflist *work_p, struct cflist *glob_p,
+ struct conf_entry *defconf_p, struct ilist *inclist);
+static void add_to_queue(const char *fname, struct ilist *inclist);
+static char *sob(char *p);
+static char *son(char *p);
+static int isnumberstr(const char *);
+static int isglobstr(const char *);
+static char *missing_field(char *p, char *errline);
+static void change_attrs(const char *, const struct conf_entry *);
+static const char *get_logfile_suffix(const char *logfile);
+static fk_entry do_entry(struct conf_entry *);
+static fk_entry do_rotate(const struct conf_entry *);
+static void do_sigwork(struct sigwork_entry *);
+static void do_zipwork(struct zipwork_entry *);
+static struct sigwork_entry *
+ save_sigwork(const struct conf_entry *);
+static struct zipwork_entry *
+ save_zipwork(const struct conf_entry *, const struct
+ sigwork_entry *, int, const char *);
+static void set_swpid(struct sigwork_entry *, const struct conf_entry *);
+static int sizefile(const char *);
+static void expand_globs(struct cflist *work_p, struct cflist *glob_p);
+static void free_clist(struct cflist *list);
+static void free_entry(struct conf_entry *ent);
+static struct conf_entry *init_entry(const char *fname,
+ struct conf_entry *src_entry);
+static void parse_args(int argc, char **argv);
+static int parse_doption(const char *doption);
+static void usage(void);
+static int log_trim(const char *logname, const struct conf_entry *log_ent);
+static int age_old_log(const char *file);
+static void savelog(char *from, char *to);
+static void createdir(const struct conf_entry *ent, char *dirpart);
+static void createlog(const struct conf_entry *ent);
+static int parse_signal(const char *str);
+
+/*
+ * All the following take a parameter of 'int', but expect values in the
+ * range of unsigned char. Define wrappers which take values of type 'char',
+ * whether signed or unsigned, and ensure they end up in the right range.
+ */
+#define isdigitch(Anychar) isdigit((u_char)(Anychar))
+#define isprintch(Anychar) isprint((u_char)(Anychar))
+#define isspacech(Anychar) isspace((u_char)(Anychar))
+#define tolowerch(Anychar) tolower((u_char)(Anychar))
+
+int
+main(int argc, char **argv)
+{
+ struct cflist *worklist;
+ struct conf_entry *p;
+ struct sigwork_entry *stmp;
+ struct zipwork_entry *ztmp;
+
+ SLIST_INIT(&swhead);
+ SLIST_INIT(&zwhead);
+
+ parse_args(argc, argv);
+ argc -= optind;
+ argv += optind;
+
+ if (needroot && getuid() && geteuid())
+ errx(1, "must have root privs");
+ worklist = get_worklist(argv);
+
+ /*
+ * Rotate all the files which need to be rotated. Note that
+ * some users have *hundreds* of entries in newsyslog.conf!
+ */
+ while (!STAILQ_EMPTY(worklist)) {
+ p = STAILQ_FIRST(worklist);
+ STAILQ_REMOVE_HEAD(worklist, cf_nextp);
+ if (do_entry(p) == FREE_ENT)
+ free_entry(p);
+ }
+
+ /*
+ * Send signals to any processes which need a signal to tell
+ * them to close and re-open the log file(s) we have rotated.
+ * Note that zipwork_entries include pointers to these
+ * sigwork_entry's, so we can not free the entries here.
+ */
+ if (!SLIST_EMPTY(&swhead)) {
+ if (noaction || verbose)
+ printf("Signal all daemon process(es)...\n");
+ SLIST_FOREACH(stmp, &swhead, sw_nextp)
+ do_sigwork(stmp);
+ if (noaction)
+ printf("\tsleep 10\n");
+ else {
+ if (verbose)
+ printf("Pause 10 seconds to allow daemon(s)"
+ " to close log file(s)\n");
+ sleep(10);
+ }
+ }
+ /*
+ * Compress all files that we're expected to compress, now
+ * that all processes should have closed the files which
+ * have been rotated.
+ */
+ if (!SLIST_EMPTY(&zwhead)) {
+ if (noaction || verbose)
+ printf("Compress all rotated log file(s)...\n");
+ while (!SLIST_EMPTY(&zwhead)) {
+ ztmp = SLIST_FIRST(&zwhead);
+ do_zipwork(ztmp);
+ SLIST_REMOVE_HEAD(&zwhead, zw_nextp);
+ free(ztmp);
+ }
+ }
+ /* Now free all the sigwork entries. */
+ while (!SLIST_EMPTY(&swhead)) {
+ stmp = SLIST_FIRST(&swhead);
+ SLIST_REMOVE_HEAD(&swhead, sw_nextp);
+ free(stmp);
+ }
+
+ while (wait(NULL) > 0 || errno == EINTR)
+ ;
+ return (0);
+}
+
+static struct conf_entry *
+init_entry(const char *fname, struct conf_entry *src_entry)
+{
+ struct conf_entry *tempwork;
+
+ if (verbose > 4)
+ printf("\t--> [creating entry for %s]\n", fname);
+
+ tempwork = malloc(sizeof(struct conf_entry));
+ if (tempwork == NULL)
+ err(1, "malloc of conf_entry for %s", fname);
+
+ if (destdir == NULL || fname[0] != '/')
+ tempwork->log = strdup(fname);
+ else
+ asprintf(&tempwork->log, "%s%s", destdir, fname);
+ if (tempwork->log == NULL)
+ err(1, "strdup for %s", fname);
+
+ if (src_entry != NULL) {
+ tempwork->pid_cmd_file = NULL;
+ if (src_entry->pid_cmd_file)
+ tempwork->pid_cmd_file = strdup(src_entry->pid_cmd_file);
+ tempwork->r_reason = NULL;
+ tempwork->firstcreate = 0;
+ tempwork->rotate = 0;
+ tempwork->fsize = -1;
+ tempwork->uid = src_entry->uid;
+ tempwork->gid = src_entry->gid;
+ tempwork->numlogs = src_entry->numlogs;
+ tempwork->trsize = src_entry->trsize;
+ tempwork->hours = src_entry->hours;
+ tempwork->trim_at = NULL;
+ if (src_entry->trim_at != NULL)
+ tempwork->trim_at = ptime_init(src_entry->trim_at);
+ tempwork->permissions = src_entry->permissions;
+ tempwork->flags = src_entry->flags;
+ tempwork->compress = src_entry->compress;
+ tempwork->sig = src_entry->sig;
+ tempwork->def_cfg = src_entry->def_cfg;
+ } else {
+ /* Initialize as a "do-nothing" entry */
+ tempwork->pid_cmd_file = NULL;
+ tempwork->r_reason = NULL;
+ tempwork->firstcreate = 0;
+ tempwork->rotate = 0;
+ tempwork->fsize = -1;
+ tempwork->uid = (uid_t)-1;
+ tempwork->gid = (gid_t)-1;
+ tempwork->numlogs = 1;
+ tempwork->trsize = -1;
+ tempwork->hours = -1;
+ tempwork->trim_at = NULL;
+ tempwork->permissions = 0;
+ tempwork->flags = 0;
+ tempwork->compress = COMPRESS_NONE;
+ tempwork->sig = SIGHUP;
+ tempwork->def_cfg = 0;
+ }
+
+ return (tempwork);
+}
+
+static void
+free_entry(struct conf_entry *ent)
+{
+
+ if (ent == NULL)
+ return;
+
+ if (ent->log != NULL) {
+ if (verbose > 4)
+ printf("\t--> [freeing entry for %s]\n", ent->log);
+ free(ent->log);
+ ent->log = NULL;
+ }
+
+ if (ent->pid_cmd_file != NULL) {
+ free(ent->pid_cmd_file);
+ ent->pid_cmd_file = NULL;
+ }
+
+ if (ent->r_reason != NULL) {
+ free(ent->r_reason);
+ ent->r_reason = NULL;
+ }
+
+ if (ent->trim_at != NULL) {
+ ptime_free(ent->trim_at);
+ ent->trim_at = NULL;
+ }
+
+ free(ent);
+}
+
+static void
+free_clist(struct cflist *list)
+{
+ struct conf_entry *ent;
+
+ while (!STAILQ_EMPTY(list)) {
+ ent = STAILQ_FIRST(list);
+ STAILQ_REMOVE_HEAD(list, cf_nextp);
+ free_entry(ent);
+ }
+
+ free(list);
+ list = NULL;
+}
+
+static fk_entry
+do_entry(struct conf_entry * ent)
+{
+#define REASON_MAX 80
+ int modtime;
+ fk_entry free_or_keep;
+ double diffsecs;
+ char temp_reason[REASON_MAX];
+ int oversized;
+
+ free_or_keep = FREE_ENT;
+ if (verbose)
+ printf("%s <%d%s>: ", ent->log, ent->numlogs,
+ compress_type[ent->compress].flag);
+ ent->fsize = sizefile(ent->log);
+ oversized = ((ent->trsize > 0) && (ent->fsize >= ent->trsize));
+ modtime = age_old_log(ent->log);
+ ent->rotate = 0;
+ ent->firstcreate = 0;
+ if (ent->fsize < 0) {
+ /*
+ * If either the C flag or the -C option was specified,
+ * and if we won't be creating the file, then have the
+ * verbose message include a hint as to why the file
+ * will not be created.
+ */
+ temp_reason[0] = '\0';
+ if (createlogs > 1)
+ ent->firstcreate = 1;
+ else if ((ent->flags & CE_CREATE) && createlogs)
+ ent->firstcreate = 1;
+ else if (ent->flags & CE_CREATE)
+ strlcpy(temp_reason, " (no -C option)", REASON_MAX);
+ else if (createlogs)
+ strlcpy(temp_reason, " (no C flag)", REASON_MAX);
+
+ if (ent->firstcreate) {
+ if (verbose)
+ printf("does not exist -> will create.\n");
+ createlog(ent);
+ } else if (verbose) {
+ printf("does not exist, skipped%s.\n", temp_reason);
+ }
+ } else {
+ if (ent->flags & CE_TRIMAT && !force && !rotatereq &&
+ !oversized) {
+ diffsecs = ptimeget_diff(timenow, ent->trim_at);
+ if (diffsecs < 0.0) {
+ /* trim_at is some time in the future. */
+ if (verbose) {
+ ptime_adjust4dst(ent->trim_at,
+ timenow);
+ printf("--> will trim at %s",
+ ptimeget_ctime(ent->trim_at));
+ }
+ return (free_or_keep);
+ } else if (diffsecs >= 3600.0) {
+ /*
+ * trim_at is more than an hour in the past,
+ * so find the next valid trim_at time, and
+ * tell the user what that will be.
+ */
+ if (verbose && dbg_at_times)
+ printf("\n\t--> prev trim at %s\t",
+ ptimeget_ctime(ent->trim_at));
+ if (verbose) {
+ ptimeset_nxtime(ent->trim_at);
+ printf("--> will trim at %s",
+ ptimeget_ctime(ent->trim_at));
+ }
+ return (free_or_keep);
+ } else if (verbose && noaction && dbg_at_times) {
+ /*
+ * If we are just debugging at-times, then
+ * a detailed message is helpful. Also
+ * skip "doing" any commands, since they
+ * would all be turned off by no-action.
+ */
+ printf("\n\t--> timematch at %s",
+ ptimeget_ctime(ent->trim_at));
+ return (free_or_keep);
+ } else if (verbose && ent->hours <= 0) {
+ printf("--> time is up\n");
+ }
+ }
+ if (verbose && (ent->trsize > 0))
+ printf("size (Kb): %d [%d] ", ent->fsize, ent->trsize);
+ if (verbose && (ent->hours > 0))
+ printf(" age (hr): %d [%d] ", modtime, ent->hours);
+
+ /*
+ * Figure out if this logfile needs to be rotated.
+ */
+ temp_reason[0] = '\0';
+ if (rotatereq) {
+ ent->rotate = 1;
+ snprintf(temp_reason, REASON_MAX, " due to -R from %s",
+ requestor);
+ } else if (force) {
+ ent->rotate = 1;
+ snprintf(temp_reason, REASON_MAX, " due to -F request");
+ } else if (oversized) {
+ ent->rotate = 1;
+ snprintf(temp_reason, REASON_MAX, " due to size>%dK",
+ ent->trsize);
+ } else if (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) {
+ ent->rotate = 1;
+ } else if ((ent->hours > 0) && ((modtime >= ent->hours) ||
+ (modtime < 0))) {
+ ent->rotate = 1;
+ }
+
+ /*
+ * If the file needs to be rotated, then rotate it.
+ */
+ if (ent->rotate && !norotate) {
+ if (temp_reason[0] != '\0')
+ ent->r_reason = strdup(temp_reason);
+ if (verbose)
+ printf("--> trimming log....\n");
+ if (noaction && !verbose)
+ printf("%s <%d%s>: trimming\n", ent->log,
+ ent->numlogs,
+ compress_type[ent->compress].flag);
+ free_or_keep = do_rotate(ent);
+ } else {
+ if (verbose)
+ printf("--> skipping\n");
+ }
+ }
+ return (free_or_keep);
+#undef REASON_MAX
+}
+
+static void
+parse_args(int argc, char **argv)
+{
+ int ch;
+ char *p;
+
+ timenow = ptime_init(NULL);
+ ptimeset_time(timenow, time(NULL));
+ strlcpy(daytime, ptimeget_ctime(timenow) + 4, DAYTIME_LEN);
+
+ /* Let's get our hostname */
+ (void)gethostname(hostname, sizeof(hostname));
+
+ /* Truncate domain */
+ if ((p = strchr(hostname, '.')) != NULL)
+ *p = '\0';
+
+ /* Parse command line options. */
+ while ((ch = getopt(argc, argv, "a:d:f:nrst:vCD:FNPR:S:")) != -1)
+ switch (ch) {
+ case 'a':
+ archtodir++;
+ archdirname = optarg;
+ break;
+ case 'd':
+ destdir = optarg;
+ break;
+ case 'f':
+ conf = optarg;
+ break;
+ case 'n':
+ noaction++;
+ /* FALLTHROUGH */
+ case 'r':
+ needroot = 0;
+ break;
+ case 's':
+ nosignal = 1;
+ break;
+ case 't':
+ if (optarg[0] == '\0' ||
+ strcmp(optarg, "DEFAULT") == 0)
+ timefnamefmt = strdup(DEFAULT_TIMEFNAME_FMT);
+ else
+ timefnamefmt = strdup(optarg);
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'C':
+ /* Useful for things like rc.diskless... */
+ createlogs++;
+ break;
+ case 'D':
+ /*
+ * Set some debugging option. The specific option
+ * depends on the value of optarg. These options
+ * may come and go without notice or documentation.
+ */
+ if (parse_doption(optarg))
+ break;
+ usage();
+ /* NOTREACHED */
+ case 'F':
+ force++;
+ break;
+ case 'N':
+ norotate++;
+ break;
+ case 'P':
+ enforcepid++;
+ break;
+ case 'R':
+ rotatereq++;
+ requestor = strdup(optarg);
+ break;
+ case 'S':
+ path_syslogpid = optarg;
+ break;
+ case 'm': /* Used by OpenBSD for "monitor mode" */
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+
+ if (force && norotate) {
+ warnx("Only one of -F and -N may be specified.");
+ usage();
+ /* NOTREACHED */
+ }
+
+ if (rotatereq) {
+ if (optind == argc) {
+ warnx("At least one filename must be given when -R is specified.");
+ usage();
+ /* NOTREACHED */
+ }
+ /* Make sure "requestor" value is safe for a syslog message. */
+ for (p = requestor; *p != '\0'; p++) {
+ if (!isprintch(*p) && (*p != '\t'))
+ *p = '.';
+ }
+ }
+
+ if (dbg_timenow) {
+ /*
+ * Note that the 'daytime' variable is not changed.
+ * That is only used in messages that track when a
+ * logfile is rotated, and if a file *is* rotated,
+ * then it will still rotated at the "real now" time.
+ */
+ ptime_free(timenow);
+ timenow = dbg_timenow;
+ fprintf(stderr, "Debug: Running as if TimeNow is %s",
+ ptimeget_ctime(dbg_timenow));
+ }
+
+}
+
+/*
+ * These debugging options are mainly meant for developer use, such
+ * as writing regression-tests. They would not be needed by users
+ * during normal operation of newsyslog...
+ */
+static int
+parse_doption(const char *doption)
+{
+ const char TN[] = "TN=";
+ int res;
+
+ if (strncmp(doption, TN, sizeof(TN) - 1) == 0) {
+ /*
+ * The "TimeNow" debugging option. This might be off
+ * by an hour when crossing a timezone change.
+ */
+ dbg_timenow = ptime_init(NULL);
+ res = ptime_relparse(dbg_timenow, PTM_PARSE_ISO8601,
+ time(NULL), doption + sizeof(TN) - 1);
+ if (res == -2) {
+ warnx("Non-existent time specified on -D %s", doption);
+ return (0); /* failure */
+ } else if (res < 0) {
+ warnx("Malformed time given on -D %s", doption);
+ return (0); /* failure */
+ }
+ return (1); /* successfully parsed */
+
+ }
+
+ if (strcmp(doption, "ats") == 0) {
+ dbg_at_times++;
+ return (1); /* successfully parsed */
+ }
+
+ /* XXX - This check could probably be dropped. */
+ if ((strcmp(doption, "neworder") == 0) || (strcmp(doption, "oldorder")
+ == 0)) {
+ warnx("NOTE: newsyslog always uses 'neworder'.");
+ return (1); /* successfully parsed */
+ }
+
+ warnx("Unknown -D (debug) option: '%s'", doption);
+ return (0); /* failure */
+}
+
+static void
+usage(void)
+{
+
+ fprintf(stderr,
+ "usage: newsyslog [-CFNPnrsv] [-a directory] [-d directory] [-f config_file]\n"
+ " [-S pidfile] [-t timefmt] [[-R tagname] file ...]\n");
+ exit(1);
+}
+
+/*
+ * Parse a configuration file and return a linked list of all the logs
+ * which should be processed.
+ */
+static struct cflist *
+get_worklist(char **files)
+{
+ FILE *f;
+ char **given;
+ struct cflist *cmdlist, *filelist, *globlist;
+ struct conf_entry *defconf, *dupent, *ent;
+ struct ilist inclist;
+ struct include_entry *inc;
+ int gmatch, fnres;
+
+ defconf = NULL;
+ STAILQ_INIT(&inclist);
+
+ filelist = malloc(sizeof(struct cflist));
+ if (filelist == NULL)
+ err(1, "malloc of filelist");
+ STAILQ_INIT(filelist);
+ globlist = malloc(sizeof(struct cflist));
+ if (globlist == NULL)
+ err(1, "malloc of globlist");
+ STAILQ_INIT(globlist);
+
+ inc = malloc(sizeof(struct include_entry));
+ if (inc == NULL)
+ err(1, "malloc of inc");
+ inc->file = conf;
+ if (inc->file == NULL)
+ inc->file = _PATH_CONF;
+ STAILQ_INSERT_TAIL(&inclist, inc, inc_nextp);
+
+ STAILQ_FOREACH(inc, &inclist, inc_nextp) {
+ if (strcmp(inc->file, "-") != 0)
+ f = fopen(inc->file, "r");
+ else {
+ f = stdin;
+ inc->file = "<stdin>";
+ }
+ if (!f)
+ err(1, "%s", inc->file);
+
+ if (verbose)
+ printf("Processing %s\n", inc->file);
+ parse_file(f, filelist, globlist, defconf, &inclist);
+ (void) fclose(f);
+ }
+
+ /*
+ * All config-file information has been read in and turned into
+ * a filelist and a globlist. If there were no specific files
+ * given on the run command, then the only thing left to do is to
+ * call a routine which finds all files matched by the globlist
+ * and adds them to the filelist. Then return the worklist.
+ */
+ if (*files == NULL) {
+ expand_globs(filelist, globlist);
+ free_clist(globlist);
+ if (defconf != NULL)
+ free_entry(defconf);
+ return (filelist);
+ /* NOTREACHED */
+ }
+
+ /*
+ * If newsyslog was given a specific list of files to process,
+ * it may be that some of those files were not listed in any
+ * config file. Those unlisted files should get the default
+ * rotation action. First, create the default-rotation action
+ * if none was found in a system config file.
+ */
+ if (defconf == NULL) {
+ defconf = init_entry(DEFAULT_MARKER, NULL);
+ defconf->numlogs = 3;
+ defconf->trsize = 50;
+ defconf->permissions = S_IRUSR|S_IWUSR;
+ }
+
+ /*
+ * If newsyslog was run with a list of specific filenames,
+ * then create a new worklist which has only those files in
+ * it, picking up the rotation-rules for those files from
+ * the original filelist.
+ *
+ * XXX - Note that this will copy multiple rules for a single
+ * logfile, if multiple entries are an exact match for
+ * that file. That matches the historic behavior, but do
+ * we want to continue to allow it? If so, it should
+ * probably be handled more intelligently.
+ */
+ cmdlist = malloc(sizeof(struct cflist));
+ if (cmdlist == NULL)
+ err(1, "malloc of cmdlist");
+ STAILQ_INIT(cmdlist);
+
+ for (given = files; *given; ++given) {
+ /*
+ * First try to find exact-matches for this given file.
+ */
+ gmatch = 0;
+ STAILQ_FOREACH(ent, filelist, cf_nextp) {
+ if (strcmp(ent->log, *given) == 0) {
+ gmatch++;
+ dupent = init_entry(*given, ent);
+ STAILQ_INSERT_TAIL(cmdlist, dupent, cf_nextp);
+ }
+ }
+ if (gmatch) {
+ if (verbose > 2)
+ printf("\t+ Matched entry %s\n", *given);
+ continue;
+ }
+
+ /*
+ * There was no exact-match for this given file, so look
+ * for a "glob" entry which does match.
+ */
+ gmatch = 0;
+ if (verbose > 2 && globlist != NULL)
+ printf("\t+ Checking globs for %s\n", *given);
+ STAILQ_FOREACH(ent, globlist, cf_nextp) {
+ fnres = fnmatch(ent->log, *given, FNM_PATHNAME);
+ if (verbose > 2)
+ printf("\t+ = %d for pattern %s\n", fnres,
+ ent->log);
+ if (fnres == 0) {
+ gmatch++;
+ dupent = init_entry(*given, ent);
+ /* This new entry is not a glob! */
+ dupent->flags &= ~CE_GLOB;
+ STAILQ_INSERT_TAIL(cmdlist, dupent, cf_nextp);
+ /* Only allow a match to one glob-entry */
+ break;
+ }
+ }
+ if (gmatch) {
+ if (verbose > 2)
+ printf("\t+ Matched %s via %s\n", *given,
+ ent->log);
+ continue;
+ }
+
+ /*
+ * This given file was not found in any config file, so
+ * add a worklist item based on the default entry.
+ */
+ if (verbose > 2)
+ printf("\t+ No entry matched %s (will use %s)\n",
+ *given, DEFAULT_MARKER);
+ dupent = init_entry(*given, defconf);
+ /* Mark that it was *not* found in a config file */
+ dupent->def_cfg = 1;
+ STAILQ_INSERT_TAIL(cmdlist, dupent, cf_nextp);
+ }
+
+ /*
+ * Free all the entries in the original work list, the list of
+ * glob entries, and the default entry.
+ */
+ free_clist(filelist);
+ free_clist(globlist);
+ free_entry(defconf);
+
+ /* And finally, return a worklist which matches the given files. */
+ return (cmdlist);
+}
+
+/*
+ * Expand the list of entries with filename patterns, and add all files
+ * which match those glob-entries onto the worklist.
+ */
+static void
+expand_globs(struct cflist *work_p, struct cflist *glob_p)
+{
+ int gmatch, gres;
+ size_t i;
+ char *mfname;
+ struct conf_entry *dupent, *ent, *globent;
+ glob_t pglob;
+ struct stat st_fm;
+
+ /*
+ * The worklist contains all fully-specified (non-GLOB) names.
+ *
+ * Now expand the list of filename-pattern (GLOB) entries into
+ * a second list, which (by definition) will only match files
+ * that already exist. Do not add a glob-related entry for any
+ * file which already exists in the fully-specified list.
+ */
+ STAILQ_FOREACH(globent, glob_p, cf_nextp) {
+ gres = glob(globent->log, GLOB_NOCHECK, NULL, &pglob);
+ if (gres != 0) {
+ warn("cannot expand pattern (%d): %s", gres,
+ globent->log);
+ continue;
+ }
+
+ if (verbose > 2)
+ printf("\t+ Expanding pattern %s\n", globent->log);
+ for (i = 0; i < pglob.gl_matchc; i++) {
+ mfname = pglob.gl_pathv[i];
+
+ /* See if this file already has a specific entry. */
+ gmatch = 0;
+ STAILQ_FOREACH(ent, work_p, cf_nextp) {
+ if (strcmp(mfname, ent->log) == 0) {
+ gmatch++;
+ break;
+ }
+ }
+ if (gmatch)
+ continue;
+
+ /* Make sure the named matched is a file. */
+ gres = lstat(mfname, &st_fm);
+ if (gres != 0) {
+ /* Error on a file that glob() matched?!? */
+ warn("Skipping %s - lstat() error", mfname);
+ continue;
+ }
+ if (!S_ISREG(st_fm.st_mode)) {
+ /* We only rotate files! */
+ if (verbose > 2)
+ printf("\t+ . skipping %s (!file)\n",
+ mfname);
+ continue;
+ }
+
+ if (verbose > 2)
+ printf("\t+ . add file %s\n", mfname);
+ dupent = init_entry(mfname, globent);
+ /* This new entry is not a glob! */
+ dupent->flags &= ~CE_GLOB;
+
+ /* Add to the worklist. */
+ STAILQ_INSERT_TAIL(work_p, dupent, cf_nextp);
+ }
+ globfree(&pglob);
+ if (verbose > 2)
+ printf("\t+ Done with pattern %s\n", globent->log);
+ }
+}
+
+/*
+ * Parse a configuration file and update a linked list of all the logs to
+ * process.
+ */
+static void
+parse_file(FILE *cf, struct cflist *work_p, struct cflist *glob_p,
+ struct conf_entry *defconf_p, struct ilist *inclist)
+{
+ char line[BUFSIZ], *parse, *q;
+ char *cp, *errline, *group;
+ struct conf_entry *working;
+ struct passwd *pwd;
+ struct group *grp;
+ glob_t pglob;
+ int eol, ptm_opts, res, special;
+ size_t i;
+
+ errline = NULL;
+ while (fgets(line, BUFSIZ, cf)) {
+ if ((line[0] == '\n') || (line[0] == '#') ||
+ (strlen(line) == 0))
+ continue;
+ if (errline != NULL)
+ free(errline);
+ errline = strdup(line);
+ for (cp = line + 1; *cp != '\0'; cp++) {
+ if (*cp != '#')
+ continue;
+ if (*(cp - 1) == '\\') {
+ strcpy(cp - 1, cp);
+ cp--;
+ continue;
+ }
+ *cp = '\0';
+ break;
+ }
+
+ q = parse = missing_field(sob(line), errline);
+ parse = son(line);
+ if (!*parse)
+ errx(1, "malformed line (missing fields):\n%s",
+ errline);
+ *parse = '\0';
+
+ /*
+ * Allow people to set debug options via the config file.
+ * (NOTE: debug options are undocumented, and may disappear
+ * at any time, etc).
+ */
+ if (strcasecmp(DEBUG_MARKER, q) == 0) {
+ q = parse = missing_field(sob(parse + 1), errline);
+ parse = son(parse);
+ if (!*parse)
+ warnx("debug line specifies no option:\n%s",
+ errline);
+ else {
+ *parse = '\0';
+ parse_doption(q);
+ }
+ continue;
+ } else if (strcasecmp(INCLUDE_MARKER, q) == 0) {
+ if (verbose)
+ printf("Found: %s", errline);
+ q = parse = missing_field(sob(parse + 1), errline);
+ parse = son(parse);
+ if (!*parse) {
+ warnx("include line missing argument:\n%s",
+ errline);
+ continue;
+ }
+
+ *parse = '\0';
+
+ if (isglobstr(q)) {
+ res = glob(q, GLOB_NOCHECK, NULL, &pglob);
+ if (res != 0) {
+ warn("cannot expand pattern (%d): %s",
+ res, q);
+ continue;
+ }
+
+ if (verbose > 2)
+ printf("\t+ Expanding pattern %s\n", q);
+
+ for (i = 0; i < pglob.gl_matchc; i++)
+ add_to_queue(pglob.gl_pathv[i],
+ inclist);
+ globfree(&pglob);
+ } else
+ add_to_queue(q, inclist);
+ continue;
+ }
+
+ special = 0;
+ working = init_entry(q, NULL);
+ if (strcasecmp(DEFAULT_MARKER, q) == 0) {
+ special = 1;
+ if (defconf_p != NULL) {
+ warnx("Ignoring duplicate entry for %s!", q);
+ free_entry(working);
+ continue;
+ }
+ defconf_p = working;
+ }
+
+ q = parse = missing_field(sob(parse + 1), errline);
+ parse = son(parse);
+ if (!*parse)
+ errx(1, "malformed line (missing fields):\n%s",
+ errline);
+ *parse = '\0';
+ if ((group = strchr(q, ':')) != NULL ||
+ (group = strrchr(q, '.')) != NULL) {
+ *group++ = '\0';
+ if (*q) {
+ if (!(isnumberstr(q))) {
+ if ((pwd = getpwnam(q)) == NULL)
+ errx(1,
+ "error in config file; unknown user:\n%s",
+ errline);
+ working->uid = pwd->pw_uid;
+ } else
+ working->uid = atoi(q);
+ } else
+ working->uid = (uid_t)-1;
+
+ q = group;
+ if (*q) {
+ if (!(isnumberstr(q))) {
+ if ((grp = getgrnam(q)) == NULL)
+ errx(1,
+ "error in config file; unknown group:\n%s",
+ errline);
+ working->gid = grp->gr_gid;
+ } else
+ working->gid = atoi(q);
+ } else
+ working->gid = (gid_t)-1;
+
+ q = parse = missing_field(sob(parse + 1), errline);
+ parse = son(parse);
+ if (!*parse)
+ errx(1, "malformed line (missing fields):\n%s",
+ errline);
+ *parse = '\0';
+ } else {
+ working->uid = (uid_t)-1;
+ working->gid = (gid_t)-1;
+ }
+
+ if (!sscanf(q, "%o", &working->permissions))
+ errx(1, "error in config file; bad permissions:\n%s",
+ errline);
+
+ q = parse = missing_field(sob(parse + 1), errline);
+ parse = son(parse);
+ if (!*parse)
+ errx(1, "malformed line (missing fields):\n%s",
+ errline);
+ *parse = '\0';
+ if (!sscanf(q, "%d", &working->numlogs) || working->numlogs < 0)
+ errx(1, "error in config file; bad value for count of logs to save:\n%s",
+ errline);
+
+ q = parse = missing_field(sob(parse + 1), errline);
+ parse = son(parse);
+ if (!*parse)
+ errx(1, "malformed line (missing fields):\n%s",
+ errline);
+ *parse = '\0';
+ if (isdigitch(*q))
+ working->trsize = atoi(q);
+ else if (strcmp(q, "*") == 0)
+ working->trsize = -1;
+ else {
+ warnx("Invalid value of '%s' for 'size' in line:\n%s",
+ q, errline);
+ working->trsize = -1;
+ }
+
+ working->flags = 0;
+ working->compress = COMPRESS_NONE;
+ q = parse = missing_field(sob(parse + 1), errline);
+ parse = son(parse);
+ eol = !*parse;
+ *parse = '\0';
+ {
+ char *ep;
+ u_long ul;
+
+ ul = strtoul(q, &ep, 10);
+ if (ep == q)
+ working->hours = 0;
+ else if (*ep == '*')
+ working->hours = -1;
+ else if (ul > INT_MAX)
+ errx(1, "interval is too large:\n%s", errline);
+ else
+ working->hours = ul;
+
+ if (*ep == '\0' || strcmp(ep, "*") == 0)
+ goto no_trimat;
+ if (*ep != '@' && *ep != '$')
+ errx(1, "malformed interval/at:\n%s", errline);
+
+ working->flags |= CE_TRIMAT;
+ working->trim_at = ptime_init(NULL);
+ ptm_opts = PTM_PARSE_ISO8601;
+ if (*ep == '$')
+ ptm_opts = PTM_PARSE_DWM;
+ ptm_opts |= PTM_PARSE_MATCHDOM;
+ res = ptime_relparse(working->trim_at, ptm_opts,
+ ptimeget_secs(timenow), ep + 1);
+ if (res == -2)
+ errx(1, "nonexistent time for 'at' value:\n%s",
+ errline);
+ else if (res < 0)
+ errx(1, "malformed 'at' value:\n%s", errline);
+ }
+no_trimat:
+
+ if (eol)
+ q = NULL;
+ else {
+ q = parse = sob(parse + 1); /* Optional field */
+ parse = son(parse);
+ if (!*parse)
+ eol = 1;
+ *parse = '\0';
+ }
+
+ for (; q && *q && !isspacech(*q); q++) {
+ switch (tolowerch(*q)) {
+ case 'b':
+ working->flags |= CE_BINARY;
+ break;
+ case 'c':
+ working->flags |= CE_CREATE;
+ break;
+ case 'd':
+ working->flags |= CE_NODUMP;
+ break;
+ case 'g':
+ working->flags |= CE_GLOB;
+ break;
+ case 'j':
+ working->compress = COMPRESS_BZIP2;
+ break;
+ case 'n':
+ working->flags |= CE_NOSIGNAL;
+ break;
+ case 'r':
+ working->flags |= CE_PID2CMD;
+ break;
+ case 'u':
+ working->flags |= CE_SIGNALGROUP;
+ break;
+ case 'w':
+ /* Depreciated flag - keep for compatibility purposes */
+ break;
+ case 'x':
+ working->compress = COMPRESS_XZ;
+ break;
+ case 'z':
+ working->compress = COMPRESS_GZIP;
+ break;
+ case '-':
+ break;
+ case 'f': /* Used by OpenBSD for "CE_FOLLOW" */
+ case 'm': /* Used by OpenBSD for "CE_MONITOR" */
+ case 'p': /* Used by NetBSD for "CE_PLAIN0" */
+ default:
+ errx(1, "illegal flag in config file -- %c",
+ *q);
+ }
+ }
+
+ if (eol)
+ q = NULL;
+ else {
+ q = parse = sob(parse + 1); /* Optional field */
+ parse = son(parse);
+ if (!*parse)
+ eol = 1;
+ *parse = '\0';
+ }
+
+ working->pid_cmd_file = NULL;
+ if (q && *q) {
+ if (*q == '/')
+ working->pid_cmd_file = strdup(q);
+ else if (isalnum(*q))
+ goto got_sig;
+ else {
+ errx(1,
+ "illegal pid file or signal in config file:\n%s",
+ errline);
+ }
+ }
+ if (eol)
+ q = NULL;
+ else {
+ q = parse = sob(parse + 1); /* Optional field */
+ *(parse = son(parse)) = '\0';
+ }
+
+ working->sig = SIGHUP;
+ if (q && *q) {
+got_sig:
+ working->sig = parse_signal(q);
+ if (working->sig < 1 || working->sig >= sys_nsig) {
+ errx(1,
+ "illegal signal in config file:\n%s",
+ errline);
+ }
+ }
+
+ /*
+ * Finish figuring out what pid-file to use (if any) in
+ * later processing if this logfile needs to be rotated.
+ */
+ if ((working->flags & CE_NOSIGNAL) == CE_NOSIGNAL) {
+ /*
+ * This config-entry specified 'n' for nosignal,
+ * see if it also specified an explicit pid_cmd_file.
+ * This would be a pretty pointless combination.
+ */
+ if (working->pid_cmd_file != NULL) {
+ warnx("Ignoring '%s' because flag 'n' was specified in line:\n%s",
+ working->pid_cmd_file, errline);
+ free(working->pid_cmd_file);
+ working->pid_cmd_file = NULL;
+ }
+ } else if (working->pid_cmd_file == NULL) {
+ /*
+ * This entry did not specify the 'n' flag, which
+ * means it should signal syslogd unless it had
+ * specified some other pid-file (and obviously the
+ * syslog pid-file will not be for a process-group).
+ * Also, we should only try to notify syslog if we
+ * are root.
+ */
+ if (working->flags & CE_SIGNALGROUP) {
+ warnx("Ignoring flag 'U' in line:\n%s",
+ errline);
+ working->flags &= ~CE_SIGNALGROUP;
+ }
+ if (needroot)
+ working->pid_cmd_file = strdup(path_syslogpid);
+ }
+
+ /*
+ * Add this entry to the appropriate list of entries, unless
+ * it was some kind of special entry (eg: <default>).
+ */
+ if (special) {
+ ; /* Do not add to any list */
+ } else if (working->flags & CE_GLOB) {
+ STAILQ_INSERT_TAIL(glob_p, working, cf_nextp);
+ } else {
+ STAILQ_INSERT_TAIL(work_p, working, cf_nextp);
+ }
+ }
+ if (errline != NULL)
+ free(errline);
+}
+
+static char *
+missing_field(char *p, char *errline)
+{
+
+ if (!p || !*p)
+ errx(1, "missing field in config file:\n%s", errline);
+ return (p);
+}
+
+/*
+ * In our sort we return it in the reverse of what qsort normally
+ * would do, as we want the newest files first. If we have two
+ * entries with the same time we don't really care about order.
+ *
+ * Support function for qsort() in delete_oldest_timelog().
+ */
+static int
+oldlog_entry_compare(const void *a, const void *b)
+{
+ const struct oldlog_entry *ola = a, *olb = b;
+
+ if (ola->t > olb->t)
+ return (-1);
+ else if (ola->t < olb->t)
+ return (1);
+ else
+ return (0);
+}
+
+/*
+ * Check whether the file corresponding to dp is an archive of the logfile
+ * logfname, based on the timefnamefmt format string. Return true and fill out
+ * tm if this is the case; otherwise return false.
+ */
+static int
+validate_old_timelog(int fd, const struct dirent *dp, const char *logfname,
+ struct tm *tm)
+{
+ struct stat sb;
+ size_t logfname_len;
+ char *s;
+ int c;
+
+ logfname_len = strlen(logfname);
+
+ if (dp->d_type != DT_REG) {
+ /*
+ * Some filesystems (e.g. NFS) don't fill out the d_type field
+ * and leave it set to DT_UNKNOWN; in this case we must obtain
+ * the file type ourselves.
+ */
+ if (dp->d_type != DT_UNKNOWN ||
+ fstatat(fd, dp->d_name, &sb, AT_SYMLINK_NOFOLLOW) != 0 ||
+ !S_ISREG(sb.st_mode))
+ return (0);
+ }
+ /* Ignore everything but files with our logfile prefix. */
+ if (strncmp(dp->d_name, logfname, logfname_len) != 0)
+ return (0);
+ /* Ignore the actual non-rotated logfile. */
+ if (dp->d_namlen == logfname_len)
+ return (0);
+
+ /*
+ * Make sure we created have found a logfile, so the
+ * postfix is valid, IE format is: '.<time>(.[bgx]z)?'.
+ */
+ if (dp->d_name[logfname_len] != '.') {
+ if (verbose)
+ printf("Ignoring %s which has unexpected "
+ "extension '%s'\n", dp->d_name,
+ &dp->d_name[logfname_len]);
+ return (0);
+ }
+ memset(tm, 0, sizeof(*tm));
+ if ((s = strptime(&dp->d_name[logfname_len + 1],
+ timefnamefmt, tm)) == NULL) {
+ /*
+ * We could special case "old" sequentially named logfiles here,
+ * but we do not as that would require special handling to
+ * decide which one was the oldest compared to "new" time based
+ * logfiles.
+ */
+ if (verbose)
+ printf("Ignoring %s which does not "
+ "match time format\n", dp->d_name);
+ return (0);
+ }
+
+ for (c = 0; c < COMPRESS_TYPES; c++)
+ if (strcmp(s, compress_type[c].suffix) == 0)
+ /* We're done. */
+ return (1);
+
+ if (verbose)
+ printf("Ignoring %s which has unexpected extension '%s'\n",
+ dp->d_name, s);
+
+ return (0);
+}
+
+/*
+ * Delete the oldest logfiles, when using time based filenames.
+ */
+static void
+delete_oldest_timelog(const struct conf_entry *ent, const char *archive_dir)
+{
+ char *logfname, *s, *dir, errbuf[80];
+ int dir_fd, i, logcnt, max_logcnt;
+ struct oldlog_entry *oldlogs;
+ struct dirent *dp;
+ const char *cdir;
+ struct tm tm;
+ DIR *dirp;
+
+ oldlogs = malloc(MAX_OLDLOGS * sizeof(struct oldlog_entry));
+ max_logcnt = MAX_OLDLOGS;
+ logcnt = 0;
+
+ if (archive_dir != NULL && archive_dir[0] != '\0')
+ cdir = archive_dir;
+ else
+ if ((cdir = dirname(ent->log)) == NULL)
+ err(1, "dirname()");
+ if ((dir = strdup(cdir)) == NULL)
+ err(1, "strdup()");
+
+ if ((s = basename(ent->log)) == NULL)
+ err(1, "basename()");
+ if ((logfname = strdup(s)) == NULL)
+ err(1, "strdup()");
+ if (strcmp(logfname, "/") == 0)
+ errx(1, "Invalid log filename - became '/'");
+
+ if (verbose > 2)
+ printf("Searching for old logs in %s\n", dir);
+
+ /* First we create a 'list' of all archived logfiles */
+ if ((dirp = opendir(dir)) == NULL)
+ err(1, "Cannot open log directory '%s'", dir);
+ dir_fd = dirfd(dirp);
+ while ((dp = readdir(dirp)) != NULL) {
+ if (validate_old_timelog(dir_fd, dp, logfname, &tm) == 0)
+ continue;
+
+ /*
+ * We should now have old an old rotated logfile, so
+ * add it to the 'list'.
+ */
+ if ((oldlogs[logcnt].t = timegm(&tm)) == -1)
+ err(1, "Could not convert time string to time value");
+ if ((oldlogs[logcnt].fname = strdup(dp->d_name)) == NULL)
+ err(1, "strdup()");
+ logcnt++;
+
+ /*
+ * It is very unlikely we ever run out of space in the
+ * logfile array from the default size, but lets
+ * handle it anyway...
+ */
+ if (logcnt >= max_logcnt) {
+ max_logcnt *= 4;
+ /* Detect integer overflow */
+ if (max_logcnt < logcnt)
+ errx(1, "Too many old logfiles found");
+ oldlogs = realloc(oldlogs,
+ max_logcnt * sizeof(struct oldlog_entry));
+ if (oldlogs == NULL)
+ err(1, "realloc()");
+ }
+ }
+
+ /* Second, if needed we delete oldest archived logfiles */
+ if (logcnt > 0 && logcnt >= ent->numlogs && ent->numlogs > 1) {
+ oldlogs = realloc(oldlogs, logcnt *
+ sizeof(struct oldlog_entry));
+ if (oldlogs == NULL)
+ err(1, "realloc()");
+
+ /*
+ * We now sort the logs in the order of newest to
+ * oldest. That way we can simply skip over the
+ * number of records we want to keep.
+ */
+ qsort(oldlogs, logcnt, sizeof(struct oldlog_entry),
+ oldlog_entry_compare);
+ for (i = ent->numlogs - 1; i < logcnt; i++) {
+ if (noaction)
+ printf("\trm -f %s/%s\n", dir,
+ oldlogs[i].fname);
+ else if (unlinkat(dir_fd, oldlogs[i].fname, 0) != 0) {
+ snprintf(errbuf, sizeof(errbuf),
+ "Could not delete old logfile '%s'",
+ oldlogs[i].fname);
+ perror(errbuf);
+ }
+ }
+ } else if (verbose > 1)
+ printf("No old logs to delete for logfile %s\n", ent->log);
+
+ /* Third, cleanup */
+ closedir(dirp);
+ for (i = 0; i < logcnt; i++) {
+ assert(oldlogs[i].fname != NULL);
+ free(oldlogs[i].fname);
+ }
+ free(oldlogs);
+ free(logfname);
+ free(dir);
+}
+
+/*
+ * Generate a log filename, when using classic filenames.
+ */
+static void
+gen_classiclog_fname(char *fname, size_t fname_sz, const char *archive_dir,
+ const char *namepart, int numlogs_c)
+{
+
+ if (archive_dir[0] != '\0')
+ (void) snprintf(fname, fname_sz, "%s/%s.%d", archive_dir,
+ namepart, numlogs_c);
+ else
+ (void) snprintf(fname, fname_sz, "%s.%d", namepart, numlogs_c);
+}
+
+/*
+ * Delete a rotated logfile, when using classic filenames.
+ */
+static void
+delete_classiclog(const char *archive_dir, const char *namepart, int numlog_c)
+{
+ char file1[MAXPATHLEN], zfile1[MAXPATHLEN];
+ int c;
+
+ gen_classiclog_fname(file1, sizeof(file1), archive_dir, namepart,
+ numlog_c);
+
+ for (c = 0; c < COMPRESS_TYPES; c++) {
+ (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
+ compress_type[c].suffix);
+ if (noaction)
+ printf("\trm -f %s\n", zfile1);
+ else
+ (void) unlink(zfile1);
+ }
+}
+
+/*
+ * Only add to the queue if the file hasn't already been added. This is
+ * done to prevent circular include loops.
+ */
+static void
+add_to_queue(const char *fname, struct ilist *inclist)
+{
+ struct include_entry *inc;
+
+ STAILQ_FOREACH(inc, inclist, inc_nextp) {
+ if (strcmp(fname, inc->file) == 0) {
+ warnx("duplicate include detected: %s", fname);
+ return;
+ }
+ }
+
+ inc = malloc(sizeof(struct include_entry));
+ if (inc == NULL)
+ err(1, "malloc of inc");
+ inc->file = strdup(fname);
+
+ if (verbose > 2)
+ printf("\t+ Adding %s to the processing queue.\n", fname);
+
+ STAILQ_INSERT_TAIL(inclist, inc, inc_nextp);
+}
+
+/*
+ * Search for logfile and return its compression suffix (if supported)
+ * The suffix detection is first-match in the order of compress_types
+ *
+ * Note: if logfile without suffix exists (uncompressed, COMPRESS_NONE)
+ * a zero-length string is returned
+ */
+static const char *
+get_logfile_suffix(const char *logfile)
+{
+ struct stat st;
+ char zfile[MAXPATHLEN];
+ int c;
+
+ for (c = 0; c < COMPRESS_TYPES; c++) {
+ (void) strlcpy(zfile, logfile, MAXPATHLEN);
+ (void) strlcat(zfile, compress_type[c].suffix, MAXPATHLEN);
+ if (lstat(zfile, &st) == 0)
+ return (compress_type[c].suffix);
+ }
+ return (NULL);
+}
+
+static fk_entry
+do_rotate(const struct conf_entry *ent)
+{
+ char dirpart[MAXPATHLEN], namepart[MAXPATHLEN];
+ char file1[MAXPATHLEN], file2[MAXPATHLEN];
+ char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN];
+ const char *logfile_suffix;
+ char datetimestr[30];
+ int flags, numlogs_c;
+ fk_entry free_or_keep;
+ struct sigwork_entry *swork;
+ struct stat st;
+ struct tm tm;
+ time_t now;
+
+ flags = ent->flags;
+ free_or_keep = FREE_ENT;
+
+ if (archtodir) {
+ char *p;
+
+ /* build complete name of archive directory into dirpart */
+ if (*archdirname == '/') { /* absolute */
+ strlcpy(dirpart, archdirname, sizeof(dirpart));
+ } else { /* relative */
+ /* get directory part of logfile */
+ strlcpy(dirpart, ent->log, sizeof(dirpart));
+ if ((p = strrchr(dirpart, '/')) == NULL)
+ dirpart[0] = '\0';
+ else
+ *(p + 1) = '\0';
+ strlcat(dirpart, archdirname, sizeof(dirpart));
+ }
+
+ /* check if archive directory exists, if not, create it */
+ if (lstat(dirpart, &st))
+ createdir(ent, dirpart);
+
+ /* get filename part of logfile */
+ if ((p = strrchr(ent->log, '/')) == NULL)
+ strlcpy(namepart, ent->log, sizeof(namepart));
+ else
+ strlcpy(namepart, p + 1, sizeof(namepart));
+ } else {
+ /*
+ * Tell utility functions we are not using an archive
+ * dir.
+ */
+ dirpart[0] = '\0';
+ strlcpy(namepart, ent->log, sizeof(namepart));
+ }
+
+ /* Delete old logs */
+ if (timefnamefmt != NULL)
+ delete_oldest_timelog(ent, dirpart);
+ else {
+ /*
+ * Handle cleaning up after legacy newsyslog where we
+ * kept ent->numlogs + 1 files. This code can go away
+ * at some point in the future.
+ */
+ delete_classiclog(dirpart, namepart, ent->numlogs);
+
+ if (ent->numlogs > 0)
+ delete_classiclog(dirpart, namepart, ent->numlogs - 1);
+
+ }
+
+ if (timefnamefmt != NULL) {
+ /* If time functions fails we can't really do any sensible */
+ if (time(&now) == (time_t)-1 ||
+ localtime_r(&now, &tm) == NULL)
+ bzero(&tm, sizeof(tm));
+
+ strftime(datetimestr, sizeof(datetimestr), timefnamefmt, &tm);
+ if (archtodir)
+ (void) snprintf(file1, sizeof(file1), "%s/%s.%s",
+ dirpart, namepart, datetimestr);
+ else
+ (void) snprintf(file1, sizeof(file1), "%s.%s",
+ ent->log, datetimestr);
+
+ /* Don't run the code to move down logs */
+ numlogs_c = -1;
+ } else {
+ gen_classiclog_fname(file1, sizeof(file1), dirpart, namepart,
+ ent->numlogs - 1);
+ numlogs_c = ent->numlogs - 2; /* copy for countdown */
+ }
+
+ /* Move down log files */
+ for (; numlogs_c >= 0; numlogs_c--) {
+ (void) strlcpy(file2, file1, sizeof(file2));
+
+ gen_classiclog_fname(file1, sizeof(file1), dirpart, namepart,
+ numlogs_c);
+
+ logfile_suffix = get_logfile_suffix(file1);
+ if (logfile_suffix == NULL)
+ continue;
+ (void) strlcpy(zfile1, file1, MAXPATHLEN);
+ (void) strlcpy(zfile2, file2, MAXPATHLEN);
+ (void) strlcat(zfile1, logfile_suffix, MAXPATHLEN);
+ (void) strlcat(zfile2, logfile_suffix, MAXPATHLEN);
+
+ if (noaction)
+ printf("\tmv %s %s\n", zfile1, zfile2);
+ else {
+ /* XXX - Ought to be checking for failure! */
+ (void)rename(zfile1, zfile2);
+ }
+ change_attrs(zfile2, ent);
+ }
+
+ if (ent->numlogs > 0) {
+ if (noaction) {
+ /*
+ * Note that savelog() may succeed with using link()
+ * for the archtodir case, but there is no good way
+ * of knowing if it will when doing "noaction", so
+ * here we claim that it will have to do a copy...
+ */
+ if (archtodir)
+ printf("\tcp %s %s\n", ent->log, file1);
+ else
+ printf("\tln %s %s\n", ent->log, file1);
+ printf("\ttouch %s\t\t"
+ "# Update mtime for 'when'-interval processing\n",
+ file1);
+ } else {
+ if (!(flags & CE_BINARY)) {
+ /* Report the trimming to the old log */
+ log_trim(ent->log, ent);
+ }
+ savelog(ent->log, file1);
+ /*
+ * Interval-based rotations are done using the mtime of
+ * the most recently archived log, so make sure it gets
+ * updated during a rotation.
+ */
+ utimes(file1, NULL);
+ }
+ change_attrs(file1, ent);
+ }
+
+ /* Create the new log file and move it into place */
+ if (noaction)
+ printf("Start new log...\n");
+ createlog(ent);
+
+ /*
+ * Save all signalling and file-compression to be done after log
+ * files from all entries have been rotated. This way any one
+ * process will not be sent the same signal multiple times when
+ * multiple log files had to be rotated.
+ */
+ swork = NULL;
+ if (ent->pid_cmd_file != NULL)
+ swork = save_sigwork(ent);
+ if (ent->numlogs > 0 && ent->compress > COMPRESS_NONE) {
+ /*
+ * The zipwork_entry will include a pointer to this
+ * conf_entry, so the conf_entry should not be freed.
+ */
+ free_or_keep = KEEP_ENT;
+ save_zipwork(ent, swork, ent->fsize, file1);
+ }
+
+ return (free_or_keep);
+}
+
+static void
+do_sigwork(struct sigwork_entry *swork)
+{
+ struct sigwork_entry *nextsig;
+ int kres, secs;
+ char *tmp;
+
+ if (swork->sw_runcmd == 0 && (!(swork->sw_pidok) || swork->sw_pid == 0))
+ return; /* no work to do... */
+
+ /*
+ * If nosignal (-s) was specified, then do not signal any process.
+ * Note that a nosignal request triggers a warning message if the
+ * rotated logfile needs to be compressed, *unless* -R was also
+ * specified. We assume that an `-sR' request came from a process
+ * which writes to the logfile, and as such, we assume that process
+ * has already made sure the logfile is not presently in use. This
+ * just sets swork->sw_pidok to a special value, and do_zipwork
+ * will print any necessary warning(s).
+ */
+ if (nosignal) {
+ if (!rotatereq)
+ swork->sw_pidok = -1;
+ return;
+ }
+
+ /*
+ * Compute the pause between consecutive signals. Use a longer
+ * sleep time if we will be sending two signals to the same
+ * daemon or process-group.
+ */
+ secs = 0;
+ nextsig = SLIST_NEXT(swork, sw_nextp);
+ if (nextsig != NULL) {
+ if (swork->sw_pid == nextsig->sw_pid)
+ secs = 10;
+ else
+ secs = 1;
+ }
+
+ if (noaction) {
+ if (swork->sw_runcmd)
+ printf("\tsh -c '%s %d'\n", swork->sw_fname,
+ swork->sw_signum);
+ else {
+ printf("\tkill -%d %d \t\t# %s\n", swork->sw_signum,
+ (int)swork->sw_pid, swork->sw_fname);
+ if (secs > 0)
+ printf("\tsleep %d\n", secs);
+ }
+ return;
+ }
+
+ if (swork->sw_runcmd) {
+ asprintf(&tmp, "%s %d", swork->sw_fname, swork->sw_signum);
+ if (tmp == NULL) {
+ warn("can't allocate memory to run %s",
+ swork->sw_fname);
+ return;
+ }
+ if (verbose)
+ printf("Run command: %s\n", tmp);
+ kres = system(tmp);
+ if (kres) {
+ warnx("%s: returned non-zero exit code: %d",
+ tmp, kres);
+ }
+ free(tmp);
+ return;
+ }
+
+ kres = kill(swork->sw_pid, swork->sw_signum);
+ if (kres != 0) {
+ /*
+ * Assume that "no such process" (ESRCH) is something
+ * to warn about, but is not an error. Presumably the
+ * process which writes to the rotated log file(s) is
+ * gone, in which case we should have no problem with
+ * compressing the rotated log file(s).
+ */
+ if (errno != ESRCH)
+ swork->sw_pidok = 0;
+ warn("can't notify %s, pid %d = %s", swork->sw_pidtype,
+ (int)swork->sw_pid, swork->sw_fname);
+ } else {
+ if (verbose)
+ printf("Notified %s pid %d = %s\n", swork->sw_pidtype,
+ (int)swork->sw_pid, swork->sw_fname);
+ if (secs > 0) {
+ if (verbose)
+ printf("Pause %d second(s) between signals\n",
+ secs);
+ sleep(secs);
+ }
+ }
+}
+
+static void
+do_zipwork(struct zipwork_entry *zwork)
+{
+ const char *pgm_name, *pgm_path;
+ int errsav, fcount, zstatus;
+ pid_t pidzip, wpid;
+ char zresult[MAXPATHLEN];
+ int c;
+
+ assert(zwork != NULL);
+ pgm_path = NULL;
+ strlcpy(zresult, zwork->zw_fname, sizeof(zresult));
+ if (zwork->zw_conf != NULL &&
+ zwork->zw_conf->compress > COMPRESS_NONE)
+ for (c = 1; c < COMPRESS_TYPES; c++) {
+ if (zwork->zw_conf->compress == c) {
+ pgm_path = compress_type[c].path;
+ (void) strlcat(zresult,
+ compress_type[c].suffix, sizeof(zresult));
+ break;
+ }
+ }
+ if (pgm_path == NULL) {
+ warnx("invalid entry for %s in do_zipwork", zwork->zw_fname);
+ return;
+ }
+ pgm_name = strrchr(pgm_path, '/');
+ if (pgm_name == NULL)
+ pgm_name = pgm_path;
+ else
+ pgm_name++;
+
+ if (zwork->zw_swork != NULL && zwork->zw_swork->sw_runcmd == 0 &&
+ zwork->zw_swork->sw_pidok <= 0) {
+ warnx(
+ "log %s not compressed because daemon(s) not notified",
+ zwork->zw_fname);
+ change_attrs(zwork->zw_fname, zwork->zw_conf);
+ return;
+ }
+
+ if (noaction) {
+ printf("\t%s %s\n", pgm_name, zwork->zw_fname);
+ change_attrs(zresult, zwork->zw_conf);
+ return;
+ }
+
+ fcount = 1;
+ pidzip = fork();
+ while (pidzip < 0) {
+ /*
+ * The fork failed. If the failure was due to a temporary
+ * problem, then wait a short time and try it again.
+ */
+ errsav = errno;
+ warn("fork() for `%s %s'", pgm_name, zwork->zw_fname);
+ if (errsav != EAGAIN || fcount > 5)
+ errx(1, "Exiting...");
+ sleep(fcount * 12);
+ fcount++;
+ pidzip = fork();
+ }
+ if (!pidzip) {
+ /* The child process executes the compression command */
+ execl(pgm_path, pgm_path, "-f", zwork->zw_fname, (char *)0);
+ err(1, "execl(`%s -f %s')", pgm_path, zwork->zw_fname);
+ }
+
+ wpid = waitpid(pidzip, &zstatus, 0);
+ if (wpid == -1) {
+ /* XXX - should this be a fatal error? */
+ warn("%s: waitpid(%d)", pgm_path, pidzip);
+ return;
+ }
+ if (!WIFEXITED(zstatus)) {
+ warnx("`%s -f %s' did not terminate normally", pgm_name,
+ zwork->zw_fname);
+ return;
+ }
+ if (WEXITSTATUS(zstatus)) {
+ warnx("`%s -f %s' terminated with a non-zero status (%d)",
+ pgm_name, zwork->zw_fname, WEXITSTATUS(zstatus));
+ return;
+ }
+
+ /* Compression was successful, set file attributes on the result. */
+ change_attrs(zresult, zwork->zw_conf);
+}
+
+/*
+ * Save information on any process we need to signal. Any single
+ * process may need to be sent different signal-values for different
+ * log files, but usually a single signal-value will cause the process
+ * to close and re-open all of it's log files.
+ */
+static struct sigwork_entry *
+save_sigwork(const struct conf_entry *ent)
+{
+ struct sigwork_entry *sprev, *stmp;
+ int ndiff;
+ size_t tmpsiz;
+
+ sprev = NULL;
+ ndiff = 1;
+ SLIST_FOREACH(stmp, &swhead, sw_nextp) {
+ ndiff = strcmp(ent->pid_cmd_file, stmp->sw_fname);
+ if (ndiff > 0)
+ break;
+ if (ndiff == 0) {
+ if (ent->sig == stmp->sw_signum)
+ break;
+ if (ent->sig > stmp->sw_signum) {
+ ndiff = 1;
+ break;
+ }
+ }
+ sprev = stmp;
+ }
+ if (stmp != NULL && ndiff == 0)
+ return (stmp);
+
+ tmpsiz = sizeof(struct sigwork_entry) + strlen(ent->pid_cmd_file) + 1;
+ stmp = malloc(tmpsiz);
+
+ stmp->sw_runcmd = 0;
+ /* If this is a command to run we just set the flag and run command */
+ if (ent->flags & CE_PID2CMD) {
+ stmp->sw_pid = -1;
+ stmp->sw_pidok = 0;
+ stmp->sw_runcmd = 1;
+ } else {
+ set_swpid(stmp, ent);
+ }
+ stmp->sw_signum = ent->sig;
+ strcpy(stmp->sw_fname, ent->pid_cmd_file);
+ if (sprev == NULL)
+ SLIST_INSERT_HEAD(&swhead, stmp, sw_nextp);
+ else
+ SLIST_INSERT_AFTER(sprev, stmp, sw_nextp);
+ return (stmp);
+}
+
+/*
+ * Save information on any file we need to compress. We may see the same
+ * file multiple times, so check the full list to avoid duplicates. The
+ * list itself is sorted smallest-to-largest, because that's the order we
+ * want to compress the files. If the partition is very low on disk space,
+ * then the smallest files are the most likely to compress, and compressing
+ * them first will free up more space for the larger files.
+ */
+static struct zipwork_entry *
+save_zipwork(const struct conf_entry *ent, const struct sigwork_entry *swork,
+ int zsize, const char *zipfname)
+{
+ struct zipwork_entry *zprev, *ztmp;
+ int ndiff;
+ size_t tmpsiz;
+
+ /* Compute the size if the caller did not know it. */
+ if (zsize < 0)
+ zsize = sizefile(zipfname);
+
+ zprev = NULL;
+ ndiff = 1;
+ SLIST_FOREACH(ztmp, &zwhead, zw_nextp) {
+ ndiff = strcmp(zipfname, ztmp->zw_fname);
+ if (ndiff == 0)
+ break;
+ if (zsize > ztmp->zw_fsize)
+ zprev = ztmp;
+ }
+ if (ztmp != NULL && ndiff == 0)
+ return (ztmp);
+
+ tmpsiz = sizeof(struct zipwork_entry) + strlen(zipfname) + 1;
+ ztmp = malloc(tmpsiz);
+ ztmp->zw_conf = ent;
+ ztmp->zw_swork = swork;
+ ztmp->zw_fsize = zsize;
+ strcpy(ztmp->zw_fname, zipfname);
+ if (zprev == NULL)
+ SLIST_INSERT_HEAD(&zwhead, ztmp, zw_nextp);
+ else
+ SLIST_INSERT_AFTER(zprev, ztmp, zw_nextp);
+ return (ztmp);
+}
+
+/* Send a signal to the pid specified by pidfile */
+static void
+set_swpid(struct sigwork_entry *swork, const struct conf_entry *ent)
+{
+ FILE *f;
+ long minok, maxok, rval;
+ char *endp, *linep, line[BUFSIZ];
+
+ minok = MIN_PID;
+ maxok = MAX_PID;
+ swork->sw_pidok = 0;
+ swork->sw_pid = 0;
+ swork->sw_pidtype = "daemon";
+ if (ent->flags & CE_SIGNALGROUP) {
+ /*
+ * If we are expected to signal a process-group when
+ * rotating this logfile, then the value read in should
+ * be the negative of a valid process ID.
+ */
+ minok = -MAX_PID;
+ maxok = -MIN_PID;
+ swork->sw_pidtype = "process-group";
+ }
+
+ f = fopen(ent->pid_cmd_file, "r");
+ if (f == NULL) {
+ if (errno == ENOENT && enforcepid == 0) {
+ /*
+ * Warn if the PID file doesn't exist, but do
+ * not consider it an error. Most likely it
+ * means the process has been terminated,
+ * so it should be safe to rotate any log
+ * files that the process would have been using.
+ */
+ swork->sw_pidok = 1;
+ warnx("pid file doesn't exist: %s", ent->pid_cmd_file);
+ } else
+ warn("can't open pid file: %s", ent->pid_cmd_file);
+ return;
+ }
+
+ if (fgets(line, BUFSIZ, f) == NULL) {
+ /*
+ * Warn if the PID file is empty, but do not consider
+ * it an error. Most likely it means the process has
+ * has terminated, so it should be safe to rotate any
+ * log files that the process would have been using.
+ */
+ if (feof(f) && enforcepid == 0) {
+ swork->sw_pidok = 1;
+ warnx("pid/cmd file is empty: %s", ent->pid_cmd_file);
+ } else
+ warn("can't read from pid file: %s", ent->pid_cmd_file);
+ (void)fclose(f);
+ return;
+ }
+ (void)fclose(f);
+
+ errno = 0;
+ linep = line;
+ while (*linep == ' ')
+ linep++;
+ rval = strtol(linep, &endp, 10);
+ if (*endp != '\0' && !isspacech(*endp)) {
+ warnx("pid file does not start with a valid number: %s",
+ ent->pid_cmd_file);
+ } else if (rval < minok || rval > maxok) {
+ warnx("bad value '%ld' for process number in %s",
+ rval, ent->pid_cmd_file);
+ if (verbose)
+ warnx("\t(expecting value between %ld and %ld)",
+ minok, maxok);
+ } else {
+ swork->sw_pidok = 1;
+ swork->sw_pid = rval;
+ }
+
+ return;
+}
+
+/* Log the fact that the logs were turned over */
+static int
+log_trim(const char *logname, const struct conf_entry *log_ent)
+{
+ FILE *f;
+ const char *xtra;
+
+ if ((f = fopen(logname, "a")) == NULL)
+ return (-1);
+ xtra = "";
+ if (log_ent->def_cfg)
+ xtra = " using <default> rule";
+ if (log_ent->firstcreate)
+ fprintf(f, "%s %s newsyslog[%d]: logfile first created%s\n",
+ daytime, hostname, (int) getpid(), xtra);
+ else if (log_ent->r_reason != NULL)
+ fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s%s\n",
+ daytime, hostname, (int) getpid(), log_ent->r_reason, xtra);
+ else
+ fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s\n",
+ daytime, hostname, (int) getpid(), xtra);
+ if (fclose(f) == EOF)
+ err(1, "log_trim: fclose");
+ return (0);
+}
+
+/* Return size in kilobytes of a file */
+static int
+sizefile(const char *file)
+{
+ struct stat sb;
+
+ if (stat(file, &sb) < 0)
+ return (-1);
+ return (kbytes(sb.st_size));
+}
+
+/*
+ * Return the mtime of the most recent archive of the logfile, using timestamp
+ * based filenames.
+ */
+static time_t
+mtime_old_timelog(const char *file)
+{
+ struct stat sb;
+ struct tm tm;
+ int dir_fd;
+ time_t t;
+ struct dirent *dp;
+ DIR *dirp;
+ char *s, *logfname, *dir;
+
+ t = -1;
+
+ if ((dir = dirname(file)) == NULL) {
+ warn("dirname() of '%s'", file);
+ return (t);
+ }
+ if ((s = basename(file)) == NULL) {
+ warn("basename() of '%s'", file);
+ return (t);
+ } else if (s[0] == '/') {
+ warnx("Invalid log filename '%s'", s);
+ return (t);
+ } else if ((logfname = strdup(s)) == NULL)
+ err(1, "strdup()");
+
+ if ((dirp = opendir(dir)) == NULL) {
+ warn("Cannot open log directory '%s'", dir);
+ return (t);
+ }
+ dir_fd = dirfd(dirp);
+ /* Open the archive dir and find the most recent archive of logfname. */
+ while ((dp = readdir(dirp)) != NULL) {
+ if (validate_old_timelog(dir_fd, dp, logfname, &tm) == 0)
+ continue;
+
+ if (fstatat(dir_fd, dp->d_name, &sb, AT_SYMLINK_NOFOLLOW) == -1) {
+ warn("Cannot stat '%s'", file);
+ continue;
+ }
+ if (t < sb.st_mtime)
+ t = sb.st_mtime;
+ }
+ closedir(dirp);
+
+ return (t);
+}
+
+/* Return the age in hours of the most recent archive of the logfile. */
+static int
+age_old_log(const char *file)
+{
+ struct stat sb;
+ const char *logfile_suffix;
+ char tmp[MAXPATHLEN + sizeof(".0") + COMPRESS_SUFFIX_MAXLEN + 1];
+ time_t mtime;
+
+ if (archtodir) {
+ char *p;
+
+ /* build name of archive directory into tmp */
+ if (*archdirname == '/') { /* absolute */
+ strlcpy(tmp, archdirname, sizeof(tmp));
+ } else { /* relative */
+ /* get directory part of logfile */
+ strlcpy(tmp, file, sizeof(tmp));
+ if ((p = strrchr(tmp, '/')) == NULL)
+ tmp[0] = '\0';
+ else
+ *(p + 1) = '\0';
+ strlcat(tmp, archdirname, sizeof(tmp));
+ }
+
+ strlcat(tmp, "/", sizeof(tmp));
+
+ /* get filename part of logfile */
+ if ((p = strrchr(file, '/')) == NULL)
+ strlcat(tmp, file, sizeof(tmp));
+ else
+ strlcat(tmp, p + 1, sizeof(tmp));
+ } else {
+ (void) strlcpy(tmp, file, sizeof(tmp));
+ }
+
+ if (timefnamefmt != NULL) {
+ mtime = mtime_old_timelog(tmp);
+ if (mtime == -1)
+ return (-1);
+ } else {
+ strlcat(tmp, ".0", sizeof(tmp));
+ logfile_suffix = get_logfile_suffix(tmp);
+ if (logfile_suffix == NULL)
+ return (-1);
+ (void) strlcat(tmp, logfile_suffix, sizeof(tmp));
+ if (stat(tmp, &sb) < 0)
+ return (-1);
+ mtime = sb.st_mtime;
+ }
+
+ return ((int)(ptimeget_secs(timenow) - mtime + 1800) / 3600);
+}
+
+/* Skip Over Blanks */
+static char *
+sob(char *p)
+{
+ while (p && *p && isspace(*p))
+ p++;
+ return (p);
+}
+
+/* Skip Over Non-Blanks */
+static char *
+son(char *p)
+{
+ while (p && *p && !isspace(*p))
+ p++;
+ return (p);
+}
+
+/* Check if string is actually a number */
+static int
+isnumberstr(const char *string)
+{
+ while (*string) {
+ if (!isdigitch(*string++))
+ return (0);
+ }
+ return (1);
+}
+
+/* Check if string contains a glob */
+static int
+isglobstr(const char *string)
+{
+ char chr;
+
+ while ((chr = *string++)) {
+ if (chr == '*' || chr == '?' || chr == '[')
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * Save the active log file under a new name. A link to the new name
+ * is the quick-and-easy way to do this. If that fails (which it will
+ * if the destination is on another partition), then make a copy of
+ * the file to the new location.
+ */
+static void
+savelog(char *from, char *to)
+{
+ FILE *src, *dst;
+ int c, res;
+
+ res = link(from, to);
+ if (res == 0)
+ return;
+
+ if ((src = fopen(from, "r")) == NULL)
+ err(1, "can't fopen %s for reading", from);
+ if ((dst = fopen(to, "w")) == NULL)
+ err(1, "can't fopen %s for writing", to);
+
+ while ((c = getc(src)) != EOF) {
+ if ((putc(c, dst)) == EOF)
+ err(1, "error writing to %s", to);
+ }
+
+ if (ferror(src))
+ err(1, "error reading from %s", from);
+ if ((fclose(src)) != 0)
+ err(1, "can't fclose %s", to);
+ if ((fclose(dst)) != 0)
+ err(1, "can't fclose %s", from);
+}
+
+/* create one or more directory components of a path */
+static void
+createdir(const struct conf_entry *ent, char *dirpart)
+{
+ int res;
+ char *s, *d;
+ char mkdirpath[MAXPATHLEN];
+ struct stat st;
+
+ s = dirpart;
+ d = mkdirpath;
+
+ for (;;) {
+ *d++ = *s++;
+ if (*s != '/' && *s != '\0')
+ continue;
+ *d = '\0';
+ res = lstat(mkdirpath, &st);
+ if (res != 0) {
+ if (noaction) {
+ printf("\tmkdir %s\n", mkdirpath);
+ } else {
+ res = mkdir(mkdirpath, 0755);
+ if (res != 0)
+ err(1, "Error on mkdir(\"%s\") for -a",
+ mkdirpath);
+ }
+ }
+ if (*s == '\0')
+ break;
+ }
+ if (verbose) {
+ if (ent->firstcreate)
+ printf("Created directory '%s' for new %s\n",
+ dirpart, ent->log);
+ else
+ printf("Created directory '%s' for -a\n", dirpart);
+ }
+}
+
+/*
+ * Create a new log file, destroying any currently-existing version
+ * of the log file in the process. If the caller wants a backup copy
+ * of the file to exist, they should call 'link(logfile,logbackup)'
+ * before calling this routine.
+ */
+void
+createlog(const struct conf_entry *ent)
+{
+ int fd, failed;
+ struct stat st;
+ char *realfile, *slash, tempfile[MAXPATHLEN];
+
+ fd = -1;
+ realfile = ent->log;
+
+ /*
+ * If this log file is being created for the first time (-C option),
+ * then it may also be true that the parent directory does not exist
+ * yet. Check, and create that directory if it is missing.
+ */
+ if (ent->firstcreate) {
+ strlcpy(tempfile, realfile, sizeof(tempfile));
+ slash = strrchr(tempfile, '/');
+ if (slash != NULL) {
+ *slash = '\0';
+ failed = stat(tempfile, &st);
+ if (failed && errno != ENOENT)
+ err(1, "Error on stat(%s)", tempfile);
+ if (failed)
+ createdir(ent, tempfile);
+ else if (!S_ISDIR(st.st_mode))
+ errx(1, "%s exists but is not a directory",
+ tempfile);
+ }
+ }
+
+ /*
+ * First create an unused filename, so it can be chown'ed and
+ * chmod'ed before it is moved into the real location. mkstemp
+ * will create the file mode=600 & owned by us. Note that all
+ * temp files will have a suffix of '.z<something>'.
+ */
+ strlcpy(tempfile, realfile, sizeof(tempfile));
+ strlcat(tempfile, ".zXXXXXX", sizeof(tempfile));
+ if (noaction)
+ printf("\tmktemp %s\n", tempfile);
+ else {
+ fd = mkstemp(tempfile);
+ if (fd < 0)
+ err(1, "can't mkstemp logfile %s", tempfile);
+
+ /*
+ * Add status message to what will become the new log file.
+ */
+ if (!(ent->flags & CE_BINARY)) {
+ if (log_trim(tempfile, ent))
+ err(1, "can't add status message to log");
+ }
+ }
+
+ /* Change the owner/group, if we are supposed to */
+ if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1) {
+ if (noaction)
+ printf("\tchown %u:%u %s\n", ent->uid, ent->gid,
+ tempfile);
+ else {
+ failed = fchown(fd, ent->uid, ent->gid);
+ if (failed)
+ err(1, "can't fchown temp file %s", tempfile);
+ }
+ }
+
+ /* Turn on NODUMP if it was requested in the config-file. */
+ if (ent->flags & CE_NODUMP) {
+ if (noaction)
+ printf("\tchflags nodump %s\n", tempfile);
+ else {
+ failed = fchflags(fd, UF_NODUMP);
+ if (failed) {
+ warn("log_trim: fchflags(NODUMP)");
+ }
+ }
+ }
+
+ /*
+ * Note that if the real logfile still exists, and if the call
+ * to rename() fails, then "neither the old file nor the new
+ * file shall be changed or created" (to quote the standard).
+ * If the call succeeds, then the file will be replaced without
+ * any window where some other process might find that the file
+ * did not exist.
+ * XXX - ? It may be that for some error conditions, we could
+ * retry by first removing the realfile and then renaming.
+ */
+ if (noaction) {
+ printf("\tchmod %o %s\n", ent->permissions, tempfile);
+ printf("\tmv %s %s\n", tempfile, realfile);
+ } else {
+ failed = fchmod(fd, ent->permissions);
+ if (failed)
+ err(1, "can't fchmod temp file '%s'", tempfile);
+ failed = rename(tempfile, realfile);
+ if (failed)
+ err(1, "can't mv %s to %s", tempfile, realfile);
+ }
+
+ if (fd >= 0)
+ close(fd);
+}
+
+/*
+ * Change the attributes of a given filename to what was specified in
+ * the newsyslog.conf entry. This routine is only called for files
+ * that newsyslog expects that it has created, and thus it is a fatal
+ * error if this routine finds that the file does not exist.
+ */
+static void
+change_attrs(const char *fname, const struct conf_entry *ent)
+{
+ int failed;
+
+ if (noaction) {
+ printf("\tchmod %o %s\n", ent->permissions, fname);
+
+ if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1)
+ printf("\tchown %u:%u %s\n",
+ ent->uid, ent->gid, fname);
+
+ if (ent->flags & CE_NODUMP)
+ printf("\tchflags nodump %s\n", fname);
+ return;
+ }
+
+ failed = chmod(fname, ent->permissions);
+ if (failed) {
+ if (errno != EPERM)
+ err(1, "chmod(%s) in change_attrs", fname);
+ warn("change_attrs couldn't chmod(%s)", fname);
+ }
+
+ if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1) {
+ failed = chown(fname, ent->uid, ent->gid);
+ if (failed)
+ warn("can't chown %s", fname);
+ }
+
+ if (ent->flags & CE_NODUMP) {
+ failed = chflags(fname, UF_NODUMP);
+ if (failed)
+ warn("can't chflags %s NODUMP", fname);
+ }
+}
+
+/*
+ * Parse a signal number or signal name. Returns the signal number parsed or -1
+ * on failure.
+ */
+static int
+parse_signal(const char *str)
+{
+ int sig, i;
+ const char *errstr;
+
+ sig = strtonum(str, 1, sys_nsig - 1, &errstr);
+
+ if (errstr == NULL)
+ return (sig);
+ if (strncasecmp(str, "SIG", 3) == 0)
+ str += 3;
+
+ for (i = 1; i < sys_nsig; i++) {
+ if (strcasecmp(str, sys_signame[i]) == 0)
+ return (i);
+ }
+
+ return (-1);
+}
diff --git a/usr.sbin/newsyslog/newsyslog.conf.5 b/usr.sbin/newsyslog/newsyslog.conf.5
new file mode 100644
index 0000000..0d28aab
--- /dev/null
+++ b/usr.sbin/newsyslog/newsyslog.conf.5
@@ -0,0 +1,394 @@
+.\" This file was split from the newsyslog(8) manual page by Tom Rhodes
+.\" and includes modifications as appropriate.
+.\" The original header is included below:
+.\"
+.\" This file contains changes from the Open Software Foundation.
+.\"
+.\" from: @(#)newsyslog.8
+.\" $FreeBSD$
+.\"
+.\" Copyright 1988, 1989 by the Massachusetts Institute of Technology
+.\"
+.\" Permission to use, copy, modify, and distribute this software
+.\" and its documentation for any purpose and without fee is
+.\" hereby granted, provided that the above copyright notice
+.\" appear in all copies and that both that copyright notice and
+.\" this permission notice appear in supporting documentation,
+.\" and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
+.\" used in advertising or publicity pertaining to distribution
+.\" of the software without specific, written prior permission.
+.\" M.I.T. and the M.I.T. S.I.P.B. make no representations about
+.\" the suitability of this software for any purpose. It is
+.\" provided "as is" without express or implied warranty.
+.\"
+.Dd October 24, 2015
+.Dt NEWSYSLOG.CONF 5
+.Os
+.Sh NAME
+.Nm newsyslog.conf
+.Nd
+.Xr newsyslog 8
+configuration file
+.Sh DESCRIPTION
+The
+.Nm
+file is used to set log file rotation configuration for the
+.Xr newsyslog 8
+utility.
+Configuration may designate that logs are rotated based on
+size, last rotation time, or time of day.
+The
+.Nm
+file can also be used to designate secure permissions to log
+files at rotation time.
+During initialization,
+.Xr newsyslog 8
+reads a configuration file,
+normally
+.Pa /etc/newsyslog.conf ,
+to determine which logs may potentially be rotated and archived.
+Each line has five mandatory fields and four optional fields,
+separated with whitespace.
+Blank lines or lines beginning with
+.Ql #
+are ignored.
+If
+.Ql #
+is placed in the middle of the line, the
+.Ql #
+character and the rest of the line after it is ignored.
+To prevent special meaning, the
+.Ql #
+character may be escaped with
+.Ql \e ;
+in this case preceding
+.Ql \e
+is removed and
+.Ql #
+is treated as an ordinary character.
+The fields of the configuration file are as follows:
+.Bl -tag -width indent
+.It Ar logfile_name
+Name of the system log file to be archived,
+or one of the literal strings
+.Dq Aq Li default ,
+or
+.Dq Aq Li include .
+The special default entry will only be used if a log file
+name is given as a command line argument to
+.Xr newsyslog 8 ,
+and if that log file name is not matched by any other
+line in the configuration file.
+The include entry is used to include other configuration
+files and supports globbing.
+.It Ar owner : Ns Ar group
+This optional field specifies the owner and group for the archive file.
+The
+.Ql \&:
+is essential regardless if the
+.Ar owner
+or
+.Ar group
+field is left blank or contains a value.
+The field may be numeric, or a name which is present in
+.Pa /etc/passwd
+or
+.Pa /etc/group .
+.It Ar mode
+Specify the file mode of the log file and archives.
+.It Ar count
+Specify the maximum number of archive files which may exist.
+This does not consider the current log file.
+.It Ar size
+When the size of the log file reaches
+.Ar size
+in kilobytes, the log file will be trimmed as described above.
+If this field contains an asterisk
+.Pq Ql * ,
+the log file will not be trimmed based on size.
+.It Ar when
+The
+.Ar when
+field may consist of an interval, a specific time, or both.
+If the
+.Ar when
+field contains an asterisk
+.Pq Ql * ,
+log rotation will solely depend on the contents of the
+.Ar size
+field.
+Otherwise, the
+.Ar when
+field consists of an optional interval in hours, usually followed
+by an
+.So Li \&@ Sc Ns No -sign
+and a time in restricted
+.Tn ISO 8601
+format.
+Additionally, the format may also be constructed with a
+.Ql $
+sign along with a rotation time specification of once
+a day, once a week, or once a month.
+.Pp
+Time based trimming happens only if
+.Xr newsyslog 8
+is run within one hour of the specified time.
+If an interval is specified, the log file will be trimmed if that many
+hours have passed since the last rotation.
+When both a time and an interval are
+specified then both conditions must be satisfied for the rotation to
+take place.
+.Pp
+There is no provision for the specification of a timezone.
+There is little point in specifying an explicit minutes or
+seconds component in the current implementation, since the only comparison is
+.Dq within the hour .
+.Pp
+.Sy ISO 8601 restricted time format :
+.Pp
+The lead-in character for a restricted
+.Tn ISO 8601
+time is an
+.Ql @
+sign.
+The particular format of the time in restricted
+.Tn ISO 8601
+is:
+.Sm off
+.Oo Oo Oo Oo Oo
+.Va cc Oc
+.Va yy Oc
+.Va mm Oc
+.Va dd Oc
+.Oo
+.Li T Oo
+.Va hh Oo
+.Va mm Oo
+.Va ss
+.Oc Oc Oc Oc Oc .
+.Sm on
+Optional date fields default to the appropriate component of the
+current date; optional time fields default to midnight; hence if today
+is January 22, 1999, the following date specifications are all
+equivalent:
+.Pp
+.Bl -item -compact -offset indent
+.It
+.Sq Li 19990122T000000
+.It
+.Sq Li 990122T000000
+.It
+.Sq Li 0122T000000
+.It
+.Sq Li 22T000000
+.It
+.Sq Li T000000
+.It
+.Sq Li T0000
+.It
+.Sq Li T00
+.It
+.Sq Li 22T
+.It
+.Sq Li T
+.It
+.Sq Li \&
+.El
+.Pp
+.Sy Day, week, and month time format:
+.Pp
+The lead-in character for day, week, and month specification is a
+.Ql $
+sign.
+The particular format of day, week, and month specification is:
+.Op Li D Ns Va hh ,
+.Op Li W Ns Va w Ns Op Li D Ns Va hh ,
+and
+.Op Li M Ns Va dd Ns Op Li D Ns Va hh ,
+respectively.
+Optional time fields default to midnight.
+The ranges for day and hour specifications are:
+.Pp
+.Bl -tag -width indent -offset indent -compact
+.It Ar hh
+hours, range 0..23
+.It Ar w
+day of week, range 0..6, 0 = Sunday
+.It Ar dd
+day of month, range 1..31, or one of the letters
+.Ql L
+or
+.Ql l
+to specify the last day of the month.
+.El
+.Pp
+Some examples:
+.Pp
+.Bl -tag -width indent -offset indent -compact
+.It Li $D0
+rotate every night at midnight
+(same as
+.Li @T00 )
+.It Li $D23
+rotate every day at 23:00
+(same as
+.Li @T23 )
+.It Li $W0D23
+rotate every week on Sunday at 23:00
+.It Li $W5D16
+rotate every week on Friday at 16:00
+.It Li $M1D0
+rotate at the first day of every month at midnight
+(i.e., the start of the day; same as
+.Li @01T00 )
+.It Li $M5D6
+rotate on every 5th day of month at 6:00
+(same as
+.Li @05T06 )
+.El
+.It Ar flags
+This optional field is made up of one or more characters
+that specify any special processing to be done for the log
+files matched by this line.
+The following are valid flags:
+.Bl -tag -width indent
+.It Cm B
+indicates that the log file is a binary file, or has some
+special format.
+Usually
+.Xr newsyslog 8
+inserts an
+.Tn ASCII
+message into a log file during rotation.
+This message is used to indicate
+when, and sometimes why the log file was rotated.
+If
+.Cm B
+is specified, then that informational message will not be
+inserted into the log file.
+.It Cm C
+indicates that the log file should be created if it does not
+already exist, and if the
+.Fl C
+option was also specified on the command line.
+.It Cm D
+indicates that
+.Xr newsyslog 8
+should set the
+.Dv UF_NODUMP
+flag when creating a new version of
+this log file.
+This option would affect how the
+.Xr dump 8
+command treats the log file when making a file system backup.
+.It Cm G
+indicates that the specified
+.Ar logfile_name
+is a shell pattern, and that
+.Xr newsyslog 8
+should archive all filenames matching that pattern using the
+other options on this line.
+See
+.Xr glob 3
+for details on syntax and matching rules.
+.It Cm J
+indicates that
+.Xr newsyslog 8
+should attempt to save disk space by compressing the rotated
+log file using
+.Xr bzip2 1 .
+.It Cm X
+indicates that
+.Xr newsyslog 8
+should attempt to save disk space by compressing the rotated
+log file using
+.Xr xz 1 .
+.It Cm N
+indicates that there is no process which needs to be signaled
+when this log file is rotated.
+.It Cm R
+if this flag is set the
+.Xr newsyslog 8
+will run shell command defined in
+.Ar path_to_pid_cmd_file
+after rotation instead of trying to send signal to a process id
+stored in the file.
+.It Cm U
+indicates that the file specified by
+.Ar path_to_pid_cmd_file
+will contain the ID for a process group instead of a process.
+This option also requires that the first line in that file
+be a negative value to distinguish it from a process ID.
+.It Cm Z
+indicates that
+.Xr newsyslog 8
+should attempt to save disk space by compressing the rotated
+log file using
+.Xr gzip 1 .
+.It Fl
+a minus sign will not cause any special processing, but it
+can be used as a placeholder to create a
+.Ar flags
+field when you need to specify any of the following fields.
+.El
+.It Ar path_to_pid_cmd_file
+This optional field specifies the file name containing a daemon's
+process ID or to find a group process ID if the
+.Cm U
+flag was specified.
+If this field is present, a
+.Ar signal
+is sent to the process ID contained in this file.
+If this field is not present and the
+.Cm N
+flag has not been specified, then a
+.Dv SIGHUP
+signal will be sent to
+.Xr syslogd 8
+or to the process id found in the file specified by
+.Xr newsyslog 8 Ns 's
+.Fl S
+switch.
+This field must start with
+.Ql /
+in order to be recognized properly.
+When used with the
+.Cm R
+flag, the file is treated as a path to a binary to be executed
+by the
+.Xr newsyslog 8
+after rotation instead of sending the signal out.
+.It Ar signal
+This optional field specifies the signal that will be sent to the daemon
+process (or to all processes in a process group, if the
+.Cm U
+flag was specified).
+If this field is not present, then a
+.Dv SIGHUP
+signal will be sent.
+Signal names
+must start with
+.Dq SIG
+and be the signal name, e.g.,
+.Dv SIGUSR1 .
+Alternatively,
+.Ar signal
+can be the signal number, e.g., 30 for
+.Dv SIGUSR1 .
+.El
+.Sh EXAMPLES
+The following is an example of the
+.Dq Aq Li include
+entry:
+.Dl "<include> /etc/newsyslog-local.conf"
+.Sh SEE ALSO
+.Xr bzip2 1 ,
+.Xr gzip 1 ,
+.Xr xz 1 ,
+.Xr syslog 3 ,
+.Xr chown 8 ,
+.Xr newsyslog 8 ,
+.Xr syslogd 8
+.Sh HISTORY
+This manual page first appeared in
+.Fx 4.10 .
diff --git a/usr.sbin/newsyslog/pathnames.h b/usr.sbin/newsyslog/pathnames.h
new file mode 100644
index 0000000..9c4f885
--- /dev/null
+++ b/usr.sbin/newsyslog/pathnames.h
@@ -0,0 +1,29 @@
+/*
+ * This file contains changes from the Open Software Foundation.
+ */
+
+/*
+
+Copyright 1988, 1989 by the Massachusetts Institute of Technology
+
+Permission to use, copy, modify, and distribute this software
+and its documentation for any purpose and without fee is
+hereby granted, provided that the above copyright notice
+appear in all copies and that both that copyright notice and
+this permission notice appear in supporting documentation,
+and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
+used in advertising or publicity pertaining to distribution
+of the software without specific, written prior permission.
+M.I.T. and the M.I.T. S.I.P.B. make no representations about
+the suitability of this software for any purpose. It is
+provided "as is" without express or implied warranty.
+
+ $FreeBSD$
+
+*/
+
+#define _PATH_CONF "/etc/newsyslog.conf"
+#define _PATH_SYSLOGPID _PATH_VARRUN "syslog.pid"
+#define _PATH_BZIP2 "/usr/bin/bzip2"
+#define _PATH_GZIP "/usr/bin/gzip"
+#define _PATH_XZ "/usr/bin/xz"
diff --git a/usr.sbin/newsyslog/ptimes.c b/usr.sbin/newsyslog/ptimes.c
new file mode 100644
index 0000000..474c69d
--- /dev/null
+++ b/usr.sbin/newsyslog/ptimes.c
@@ -0,0 +1,618 @@
+/*-
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * Initial version of parse8601 was originally added to newsyslog.c in
+ * FreeBSD on Jan 22, 1999 by Garrett Wollman <wollman@FreeBSD.org>.
+ * Initial version of parseDWM was originally added to newsyslog.c in
+ * FreeBSD on Apr 4, 2000 by Hellmuth Michaelis <hm@FreeBSD.org>.
+ *
+ * Copyright (c) 2003 - Garance Alistair Drosehn <gad@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.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the FreeBSD Project.
+ *
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ * This is intended to be a set of general-purpose routines to process times.
+ * Right now it probably still has a number of assumptions in it, such that
+ * it works fine for newsyslog but might not work for other uses.
+ * ------+---------+---------+---------+---------+---------+---------+---------*
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "extern.h"
+
+#define SECS_PER_HOUR 3600
+
+/*
+ * Bit-values which indicate which components of time were specified
+ * by the string given to parse8601 or parseDWM. These are needed to
+ * calculate what time-in-the-future will match that string.
+ */
+#define TSPEC_YEAR 0x0001
+#define TSPEC_MONTHOFYEAR 0x0002
+#define TSPEC_LDAYOFMONTH 0x0004
+#define TSPEC_DAYOFMONTH 0x0008
+#define TSPEC_DAYOFWEEK 0x0010
+#define TSPEC_HOUROFDAY 0x0020
+
+#define TNYET_ADJ4DST -10 /* DST has "not yet" been adjusted */
+
+struct ptime_data {
+ time_t basesecs; /* Base point for relative times */
+ time_t tsecs; /* Time in seconds */
+ struct tm basetm; /* Base Time expanded into fields */
+ struct tm tm; /* Time expanded into fields */
+ int did_adj4dst; /* Track calls to ptime_adjust4dst */
+ int parseopts; /* Options given for parsing */
+ int tmspec; /* Indicates which time fields had
+ * been specified by the user */
+};
+
+static int days_pmonth(int month, int year);
+static int parse8601(struct ptime_data *ptime, const char *str);
+static int parseDWM(struct ptime_data *ptime, const char *str);
+
+/*
+ * Simple routine to calculate the number of days in a given month.
+ */
+static int
+days_pmonth(int month, int year)
+{
+ static const int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31,
+ 30, 31, 30, 31};
+ int ndays;
+
+ ndays = mtab[month];
+
+ if (month == 1) {
+ /*
+ * We are usually called with a 'tm-year' value
+ * (ie, the value = the number of years past 1900).
+ */
+ if (year < 1900)
+ year += 1900;
+ if (year % 4 == 0) {
+ /*
+ * This is a leap year, as long as it is not a
+ * multiple of 100, or if it is a multiple of
+ * both 100 and 400.
+ */
+ if (year % 100 != 0)
+ ndays++; /* not multiple of 100 */
+ else if (year % 400 == 0)
+ ndays++; /* is multiple of 100 and 400 */
+ }
+ }
+ return (ndays);
+}
+
+/*-
+ * Parse a limited subset of ISO 8601. The specific format is as follows:
+ *
+ * [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter)
+ *
+ * We don't accept a timezone specification; missing fields (including timezone)
+ * are defaulted to the current date but time zero.
+ */
+static int
+parse8601(struct ptime_data *ptime, const char *s)
+{
+ char *t;
+ long l;
+ struct tm tm;
+
+ l = strtol(s, &t, 10);
+ if (l < 0 || l >= INT_MAX || (*t != '\0' && *t != 'T'))
+ return (-1);
+
+ /*
+ * Now t points either to the end of the string (if no time was
+ * provided) or to the letter `T' which separates date and time in
+ * ISO 8601. The pointer arithmetic is the same for either case.
+ */
+ tm = ptime->tm;
+ ptime->tmspec = TSPEC_HOUROFDAY;
+ switch (t - s) {
+ case 8:
+ tm.tm_year = ((l / 1000000) - 19) * 100;
+ l = l % 1000000;
+ case 6:
+ ptime->tmspec |= TSPEC_YEAR;
+ tm.tm_year -= tm.tm_year % 100;
+ tm.tm_year += l / 10000;
+ l = l % 10000;
+ case 4:
+ ptime->tmspec |= TSPEC_MONTHOFYEAR;
+ tm.tm_mon = (l / 100) - 1;
+ l = l % 100;
+ case 2:
+ ptime->tmspec |= TSPEC_DAYOFMONTH;
+ tm.tm_mday = l;
+ case 0:
+ break;
+ default:
+ return (-1);
+ }
+
+ /* sanity check */
+ if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12
+ || tm.tm_mday < 1 || tm.tm_mday > 31)
+ return (-1);
+
+ if (*t != '\0') {
+ s = ++t;
+ l = strtol(s, &t, 10);
+ if (l < 0 || l >= INT_MAX || (*t != '\0' && !isspace(*t)))
+ return (-1);
+
+ switch (t - s) {
+ case 6:
+ tm.tm_sec = l % 100;
+ l /= 100;
+ case 4:
+ tm.tm_min = l % 100;
+ l /= 100;
+ case 2:
+ ptime->tmspec |= TSPEC_HOUROFDAY;
+ tm.tm_hour = l;
+ case 0:
+ break;
+ default:
+ return (-1);
+ }
+
+ /* sanity check */
+ if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0
+ || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23)
+ return (-1);
+ }
+
+ ptime->tm = tm;
+ return (0);
+}
+
+/*-
+ * Parse a cyclic time specification, the format is as follows:
+ *
+ * [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]]
+ *
+ * to rotate a logfile cyclic at
+ *
+ * - every day (D) within a specific hour (hh) (hh = 0...23)
+ * - once a week (W) at a specific day (d) OR (d = 0..6, 0 = Sunday)
+ * - once a month (M) at a specific day (d) (d = 1..31,l|L)
+ *
+ * We don't accept a timezone specification; missing fields
+ * are defaulted to the current date but time zero.
+ */
+static int
+parseDWM(struct ptime_data *ptime, const char *s)
+{
+ int daysmon, Dseen, WMseen;
+ const char *endval;
+ char *tmp;
+ long l;
+ struct tm tm;
+
+ /* Save away the number of days in this month */
+ tm = ptime->tm;
+ daysmon = days_pmonth(tm.tm_mon, tm.tm_year);
+
+ WMseen = Dseen = 0;
+ ptime->tmspec = TSPEC_HOUROFDAY;
+ for (;;) {
+ endval = NULL;
+ switch (*s) {
+ case 'D':
+ if (Dseen)
+ return (-1);
+ Dseen++;
+ ptime->tmspec |= TSPEC_HOUROFDAY;
+ s++;
+ l = strtol(s, &tmp, 10);
+ if (l < 0 || l > 23)
+ return (-1);
+ endval = tmp;
+ tm.tm_hour = l;
+ break;
+
+ case 'W':
+ if (WMseen)
+ return (-1);
+ WMseen++;
+ ptime->tmspec |= TSPEC_DAYOFWEEK;
+ s++;
+ l = strtol(s, &tmp, 10);
+ if (l < 0 || l > 6)
+ return (-1);
+ endval = tmp;
+ if (l != tm.tm_wday) {
+ int save;
+
+ if (l < tm.tm_wday) {
+ save = 6 - tm.tm_wday;
+ save += (l + 1);
+ } else {
+ save = l - tm.tm_wday;
+ }
+
+ tm.tm_mday += save;
+
+ if (tm.tm_mday > daysmon) {
+ tm.tm_mon++;
+ tm.tm_mday = tm.tm_mday - daysmon;
+ }
+ }
+ break;
+
+ case 'M':
+ if (WMseen)
+ return (-1);
+ WMseen++;
+ ptime->tmspec |= TSPEC_DAYOFMONTH;
+ s++;
+ if (tolower(*s) == 'l') {
+ /* User wants the last day of the month. */
+ ptime->tmspec |= TSPEC_LDAYOFMONTH;
+ tm.tm_mday = daysmon;
+ endval = s + 1;
+ } else {
+ l = strtol(s, &tmp, 10);
+ if (l < 1 || l > 31)
+ return (-1);
+
+ if (l > daysmon)
+ return (-1);
+ endval = tmp;
+ tm.tm_mday = l;
+ }
+ break;
+
+ default:
+ return (-1);
+ break;
+ }
+
+ if (endval == NULL)
+ return (-1);
+ else if (*endval == '\0' || isspace(*endval))
+ break;
+ else
+ s = endval;
+ }
+
+ ptime->tm = tm;
+ return (0);
+}
+
+/*
+ * Initialize a new ptime-related data area.
+ */
+struct ptime_data *
+ptime_init(const struct ptime_data *optsrc)
+{
+ struct ptime_data *newdata;
+
+ newdata = malloc(sizeof(struct ptime_data));
+ if (optsrc != NULL) {
+ memcpy(newdata, optsrc, sizeof(struct ptime_data));
+ } else {
+ memset(newdata, '\0', sizeof(struct ptime_data));
+ newdata->did_adj4dst = TNYET_ADJ4DST;
+ }
+
+ return (newdata);
+}
+
+/*
+ * Adjust a given time if that time is in a different timezone than
+ * some other time.
+ */
+int
+ptime_adjust4dst(struct ptime_data *ptime, const struct ptime_data *dstsrc)
+{
+ struct ptime_data adjtime;
+
+ if (ptime == NULL)
+ return (-1);
+
+ /*
+ * Changes are not made to the given time until after all
+ * of the calculations have been successful.
+ */
+ adjtime = *ptime;
+
+ /* Check to see if this adjustment was already made */
+ if ((adjtime.did_adj4dst != TNYET_ADJ4DST) &&
+ (adjtime.did_adj4dst == dstsrc->tm.tm_isdst))
+ return (0); /* yes, so don't make it twice */
+
+ /* See if daylight-saving has changed between the two times. */
+ if (dstsrc->tm.tm_isdst != adjtime.tm.tm_isdst) {
+ if (adjtime.tm.tm_isdst == 1)
+ adjtime.tsecs -= SECS_PER_HOUR;
+ else if (adjtime.tm.tm_isdst == 0)
+ adjtime.tsecs += SECS_PER_HOUR;
+ adjtime.tm = *(localtime(&adjtime.tsecs));
+ /* Remember that this adjustment has been made */
+ adjtime.did_adj4dst = dstsrc->tm.tm_isdst;
+ /*
+ * XXX - Should probably check to see if changing the
+ * hour also changed the value of is_dst. What
+ * should we do in that case?
+ */
+ }
+
+ *ptime = adjtime;
+ return (0);
+}
+
+int
+ptime_relparse(struct ptime_data *ptime, int parseopts, time_t basetime,
+ const char *str)
+{
+ int dpm, pres;
+ struct tm temp_tm;
+
+ ptime->parseopts = parseopts;
+ ptime->basesecs = basetime;
+ ptime->basetm = *(localtime(&ptime->basesecs));
+ ptime->tm = ptime->basetm;
+ ptime->tm.tm_hour = ptime->tm.tm_min = ptime->tm.tm_sec = 0;
+
+ /*
+ * Call a routine which sets ptime.tm and ptime.tspecs based
+ * on the given string and parsing-options. Note that the
+ * routine should not call mktime to set ptime.tsecs.
+ */
+ if (parseopts & PTM_PARSE_DWM)
+ pres = parseDWM(ptime, str);
+ else
+ pres = parse8601(ptime, str);
+ if (pres < 0) {
+ ptime->tsecs = (time_t)pres;
+ return (pres);
+ }
+
+ /*
+ * Before calling mktime, check to see if we ended up with a
+ * "day-of-month" that does not exist in the selected month.
+ * If we did call mktime with that info, then mktime will
+ * make it look like the user specifically requested a day
+ * in the following month (eg: Feb 31 turns into Mar 3rd).
+ */
+ dpm = days_pmonth(ptime->tm.tm_mon, ptime->tm.tm_year);
+ if ((parseopts & PTM_PARSE_MATCHDOM) &&
+ (ptime->tmspec & TSPEC_DAYOFMONTH) &&
+ (ptime->tm.tm_mday> dpm)) {
+ /*
+ * ptime_nxtime() will want a ptime->tsecs value,
+ * but we need to avoid mktime resetting all the
+ * ptime->tm values.
+ */
+ if (verbose && dbg_at_times > 1)
+ fprintf(stderr,
+ "\t-- dom fixed: %4d/%02d/%02d %02d:%02d (%02d)",
+ ptime->tm.tm_year, ptime->tm.tm_mon,
+ ptime->tm.tm_mday, ptime->tm.tm_hour,
+ ptime->tm.tm_min, dpm);
+ temp_tm = ptime->tm;
+ ptime->tsecs = mktime(&temp_tm);
+ if (ptime->tsecs > (time_t)-1)
+ ptimeset_nxtime(ptime);
+ if (verbose && dbg_at_times > 1)
+ fprintf(stderr,
+ " to: %4d/%02d/%02d %02d:%02d\n",
+ ptime->tm.tm_year, ptime->tm.tm_mon,
+ ptime->tm.tm_mday, ptime->tm.tm_hour,
+ ptime->tm.tm_min);
+ }
+
+ /*
+ * Convert the ptime.tm into standard time_t seconds. Check
+ * for invalid times, which includes things like the hour lost
+ * when switching from "standard time" to "daylight saving".
+ */
+ ptime->tsecs = mktime(&ptime->tm);
+ if (ptime->tsecs == (time_t)-1) {
+ ptime->tsecs = (time_t)-2;
+ return (-2);
+ }
+
+ return (0);
+}
+
+int
+ptime_free(struct ptime_data *ptime)
+{
+
+ if (ptime == NULL)
+ return (-1);
+
+ free(ptime);
+ return (0);
+}
+
+/*
+ * Some trivial routines so ptime_data can remain a completely
+ * opaque type.
+ */
+const char *
+ptimeget_ctime(const struct ptime_data *ptime)
+{
+
+ if (ptime == NULL)
+ return ("Null time in ptimeget_ctime()\n");
+
+ return (ctime(&ptime->tsecs));
+}
+
+double
+ptimeget_diff(const struct ptime_data *minuend, const struct
+ ptime_data *subtrahend)
+{
+
+ /* Just like difftime(), we have no good error-return */
+ if (minuend == NULL || subtrahend == NULL)
+ return (0.0);
+
+ return (difftime(minuend->tsecs, subtrahend->tsecs));
+}
+
+time_t
+ptimeget_secs(const struct ptime_data *ptime)
+{
+
+ if (ptime == NULL)
+ return (-1);
+
+ return (ptime->tsecs);
+}
+
+/*
+ * Generate an approximate timestamp for the next event, based on
+ * what parts of time were specified by the original parameter to
+ * ptime_relparse(). The result may be -1 if there is no obvious
+ * "next time" which will work.
+ */
+int
+ptimeset_nxtime(struct ptime_data *ptime)
+{
+ int moredays, tdpm, tmon, tyear;
+ struct ptime_data nextmatch;
+
+ if (ptime == NULL)
+ return (-1);
+
+ /*
+ * Changes are not made to the given time until after all
+ * of the calculations have been successful.
+ */
+ nextmatch = *ptime;
+ /*
+ * If the user specified a year and we're already past that
+ * time, then there will never be another one!
+ */
+ if (ptime->tmspec & TSPEC_YEAR)
+ return (-1);
+
+ /*
+ * The caller gave us a time in the past. Calculate how much
+ * time is needed to go from that valid rotate time to the
+ * next valid rotate time. We only need to get to the nearest
+ * hour, because newsyslog is only run once per hour.
+ */
+ moredays = 0;
+ if (ptime->tmspec & TSPEC_MONTHOFYEAR) {
+ /* Special case: Feb 29th does not happen every year. */
+ if (ptime->tm.tm_mon == 1 && ptime->tm.tm_mday == 29) {
+ nextmatch.tm.tm_year += 4;
+ if (days_pmonth(1, nextmatch.tm.tm_year) < 29)
+ nextmatch.tm.tm_year += 4;
+ } else {
+ nextmatch.tm.tm_year += 1;
+ }
+ nextmatch.tm.tm_isdst = -1;
+ nextmatch.tsecs = mktime(&nextmatch.tm);
+
+ } else if (ptime->tmspec & TSPEC_LDAYOFMONTH) {
+ /*
+ * Need to get to the last day of next month. Origtm is
+ * already at the last day of this month, so just add to
+ * it number of days in the next month.
+ */
+ if (ptime->tm.tm_mon < 11)
+ moredays = days_pmonth(ptime->tm.tm_mon + 1,
+ ptime->tm.tm_year);
+ else
+ moredays = days_pmonth(0, ptime->tm.tm_year + 1);
+
+ } else if (ptime->tmspec & TSPEC_DAYOFMONTH) {
+ /* Jump to the same day in the next month */
+ moredays = days_pmonth(ptime->tm.tm_mon, ptime->tm.tm_year);
+ /*
+ * In some cases, the next month may not *have* the
+ * desired day-of-the-month. If that happens, then
+ * move to the next month that does have enough days.
+ */
+ tmon = ptime->tm.tm_mon;
+ tyear = ptime->tm.tm_year;
+ for (;;) {
+ if (tmon < 11)
+ tmon += 1;
+ else {
+ tmon = 0;
+ tyear += 1;
+ }
+ tdpm = days_pmonth(tmon, tyear);
+ if (tdpm >= ptime->tm.tm_mday)
+ break;
+ moredays += tdpm;
+ }
+
+ } else if (ptime->tmspec & TSPEC_DAYOFWEEK) {
+ moredays = 7;
+ } else if (ptime->tmspec & TSPEC_HOUROFDAY) {
+ moredays = 1;
+ }
+
+ if (moredays != 0) {
+ nextmatch.tsecs += SECS_PER_HOUR * 24 * moredays;
+ nextmatch.tm = *(localtime(&nextmatch.tsecs));
+ }
+
+ /*
+ * The new time will need to be adjusted if the setting of
+ * daylight-saving has changed between the two times.
+ */
+ ptime_adjust4dst(&nextmatch, ptime);
+
+ /* Everything worked. Update the given time and return. */
+ *ptime = nextmatch;
+ return (0);
+}
+
+int
+ptimeset_time(struct ptime_data *ptime, time_t secs)
+{
+
+ if (ptime == NULL)
+ return (-1);
+
+ ptime->tsecs = secs;
+ ptime->tm = *(localtime(&ptime->tsecs));
+ ptime->parseopts = 0;
+ /* ptime->tmspec = ? */
+ return (0);
+}
diff --git a/usr.sbin/newsyslog/tests/Makefile b/usr.sbin/newsyslog/tests/Makefile
new file mode 100644
index 0000000..802a44c
--- /dev/null
+++ b/usr.sbin/newsyslog/tests/Makefile
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+TAP_TESTS_SH= legacy_test
+
+.include <bsd.test.mk>
diff --git a/usr.sbin/newsyslog/tests/legacy_test.sh b/usr.sbin/newsyslog/tests/legacy_test.sh
new file mode 100644
index 0000000..ba10f2c
--- /dev/null
+++ b/usr.sbin/newsyslog/tests/legacy_test.sh
@@ -0,0 +1,444 @@
+#!/bin/sh
+
+# $FreeBSD$
+
+COUNT=0
+TMPDIR=$(pwd)/work
+if [ $? -ne 0 ]; then
+ echo "$0: Can't create temp dir, exiting..."
+ exit 1
+fi
+
+# Begin an individual test
+begin()
+{
+ COUNT=`expr $COUNT + 1`
+ OK=1
+ NAME="$1"
+}
+
+# End an individual test
+end()
+{
+ if [ $OK = 1 ]
+ then
+ printf 'ok '
+ else
+ printf 'not ok '
+ fi
+ echo "$COUNT - $NAME"
+}
+
+# Make a file that can later be verified
+mkf()
+{
+ CN=`basename $1`
+ echo "$CN-$CN" >$1
+}
+
+# Verify that the file specified is correct
+ckf()
+{
+ if [ -f $2 ] && echo "$1-$1" | diff - $2 >/dev/null
+ then
+ ok
+ else
+ notok
+ fi
+}
+
+# Check that a file exists
+ckfe()
+{
+ if [ -f $1 ]
+ then
+ ok
+ else
+ notok
+ fi
+}
+
+# Verify that the specified file does not exist
+# (is not there)
+cknt()
+{
+ if [ -r $1 ]
+ then
+ notok
+ else
+ ok
+ fi
+}
+
+# Check if a file is there, depending of if it's supposed to or not -
+# basically how many log files we are supposed to keep vs. how many we
+# actually keep.
+ckntfe()
+{
+ curcnt=$1
+ keepcnt=$2
+ f=$3
+
+ if [ $curcnt -le $keepcnt ]
+ then
+ #echo Assuming file there
+ ckfe $f
+ else
+ #echo Assuming file NOT there
+ cknt $f
+ fi
+}
+
+
+
+# A part of a test succeeds
+ok()
+{
+ :
+}
+
+# A part of a test fails
+notok()
+{
+ OK=0
+}
+
+# Verify that the exit code passed is for unsuccessful termination
+ckfail()
+{
+ if [ $1 -gt 0 ]
+ then
+ ok
+ else
+ notok
+ fi
+}
+
+# Verify that the exit code passed is for successful termination
+ckok()
+{
+ if [ $1 -eq 0 ]
+ then
+ ok
+ else
+ notok
+ fi
+}
+
+# Check that there are X files which match expr
+chkfcnt()
+{
+ cnt=$1; shift
+ if [ $cnt -eq `echo "$@" | wc -w` ]
+ then
+ ok
+ else
+ notok
+ fi
+}
+
+# Check that two strings are alike
+ckstr()
+{
+ if [ "$1" = "$2" ]
+ then
+ ok
+ else
+ notok
+ fi
+}
+
+tmpdir_create()
+{
+ mkdir -p ${TMPDIR}/log ${TMPDIR}/alog
+ cd ${TMPDIR}/log
+}
+
+tmpdir_clean()
+{
+ cd ${TMPDIR}
+ rm -rf "${TMPDIR}/log" "${TMPDIR}/alog" newsyslog.conf
+}
+
+run_newsyslog()
+{
+
+ newsyslog -f ../newsyslog.conf -F -r "$@"
+}
+
+tests_normal_rotate() {
+ ext="$1"
+ dir="$2"
+
+ if [ -n "$dir" ]; then
+ newsyslog_args=" -a ${dir}"
+ name_postfix="${ext} archive dir"
+ else
+ newsyslog_args=""
+ name_postfix="${ext}"
+ fi
+
+ tmpdir_create
+
+ begin "create file ${name_postfix}" -newdir
+ run_newsyslog -C
+ ckfe $LOGFNAME
+ cknt ${dir}${LOGFNAME}.0${ext}
+ end
+
+ begin "rotate normal 1 ${name_postfix}"
+ run_newsyslog $newsyslog_args
+ ckfe ${LOGFNAME}
+ ckfe ${dir}${LOGFNAME}.0${ext}
+ cknt ${dir}${LOGFNAME}.1${ext}
+ end
+
+ begin "rotate normal 2 ${name_postfix}"
+ run_newsyslog $newsyslog_args
+ ckfe ${LOGFNAME}
+ ckfe ${dir}${LOGFNAME}.0${ext}
+ ckfe ${dir}${LOGFNAME}.1${ext}
+ cknt ${dir}${LOGFNAME}.2${ext}
+ end
+
+ begin "rotate normal 3 ${name_postfix}"
+ run_newsyslog $newsyslog_args
+ ckfe ${LOGFNAME}
+ ckfe ${dir}${LOGFNAME}.0${ext}
+ ckfe ${dir}${LOGFNAME}.1${ext}
+ ckfe ${dir}${LOGFNAME}.2${ext}
+ cknt ${dir}${LOGFNAME}.3${ext}
+ end
+
+ begin "rotate normal 4 ${name_postfix}"
+ run_newsyslog $newsyslog_args
+ ckfe ${LOGFNAME}
+ ckfe ${dir}${LOGFNAME}.0${ext}
+ ckfe ${dir}${LOGFNAME}.1${ext}
+ ckfe ${dir}${LOGFNAME}.2${ext}
+ cknt ${dir}${LOGFNAME}.4${ext}
+ end
+
+ begin "rotate normal 5 ${name_postfix}"
+ run_newsyslog $newsyslog_args
+ ckfe ${LOGFNAME}
+ ckfe ${dir}${LOGFNAME}.0${ext}
+ ckfe ${dir}${LOGFNAME}.1${ext}
+ ckfe ${dir}${LOGFNAME}.2${ext}
+ cknt ${dir}${LOGFNAME}.4${ext}
+ end
+
+ # Wait a bit so we can see if the noaction test rotates files
+ sleep 1.1
+
+ begin "noaction ${name_postfix}"
+ ofiles=`ls -Tl ${dir}${LOGFNAME}.*${ext} | tr -d '\n'`
+ run_newsyslog ${newsyslog_args} -n >/dev/null
+ ckfe ${LOGFNAME}
+ ckstr "$ofiles" "`ls -lT ${dir}${LOGFNAME}.*${ext} | tr -d '\n'`"
+ end
+
+ tmpdir_clean
+}
+
+tests_normal_rotate_keepn() {
+ cnt="$1"
+ ext="$2"
+ dir="$3"
+
+ if [ -n "$dir" ]; then
+ newsyslog_args=" -a ${dir}"
+ name_postfix="${ext} archive dir"
+ else
+ newsyslog_args=""
+ name_postfix="${ext}"
+ fi
+
+ tmpdir_create
+
+ begin "create file ${name_postfix}" -newdir
+ run_newsyslog -C
+ ckfe $LOGFNAME
+ cknt ${dir}${LOGFNAME}.0${ext}
+ end
+
+ begin "rotate normal 1 cnt=$cnt ${name_postfix}"
+ run_newsyslog $newsyslog_args
+ ckfe ${LOGFNAME}
+ ckntfe 1 $cnt ${dir}${LOGFNAME}.0${ext}
+ cknt ${dir}${LOGFNAME}.1${ext}
+ end
+
+ begin "rotate normal 2 cnt=$cnt ${name_postfix}"
+ run_newsyslog $newsyslog_args
+ ckfe ${LOGFNAME}
+ ckntfe 1 $cnt ${dir}${LOGFNAME}.0${ext}
+ ckntfe 2 $cnt ${dir}${LOGFNAME}.1${ext}
+ cknt ${dir}${LOGFNAME}.2${ext}
+ end
+
+ begin "rotate normal 3 cnt=$cnt ${name_postfix}"
+ run_newsyslog $newsyslog_args
+ ckfe ${LOGFNAME}
+ ckntfe 1 $cnt ${dir}${LOGFNAME}.0${ext}
+ ckntfe 2 $cnt ${dir}${LOGFNAME}.1${ext}
+ ckntfe 3 $cnt ${dir}${LOGFNAME}.2${ext}
+ cknt ${dir}${LOGFNAME}.3${ext}
+ end
+
+ begin "rotate normal 3 cnt=$cnt ${name_postfix}"
+ run_newsyslog $newsyslog_args
+ ckfe ${LOGFNAME}
+ ckntfe 1 $cnt ${dir}${LOGFNAME}.0${ext}
+ ckntfe 2 $cnt ${dir}${LOGFNAME}.1${ext}
+ ckntfe 3 $cnt ${dir}${LOGFNAME}.2${ext}
+ ckntfe 4 $cnt ${dir}${LOGFNAME}.3${ext}
+ cknt ${dir}${LOGFNAME}.4${ext}
+ end
+
+ # Wait a bit so we can see if the noaction test rotates files
+ sleep 1.1
+
+ begin "noaction ${name_postfix}"
+ osum=`md5 ${dir}${LOGFNAME} | tr -d '\n'`
+ run_newsyslog ${newsyslog_args} -n >/dev/null
+ ckfe ${LOGFNAME}
+ ckstr "$osum" "`md5 ${dir}${LOGFNAME} | tr -d '\n'`"
+ end
+
+ tmpdir_clean
+}
+
+tests_time_rotate() {
+ ext="$1"
+ dir="$2"
+
+ if [ -n "$dir" ]; then
+ newsyslog_args="-t DEFAULT -a ${dir}"
+ name_postfix="${ext} archive dir"
+ else
+ newsyslog_args="-t DEFAULT"
+ name_postfix="${ext}"
+ fi
+
+ tmpdir_create
+
+ begin "create file ${name_postfix}" -newdir
+ run_newsyslog -C ${newsyslog_args}
+ ckfe ${LOGFNAME}
+ end
+
+ begin "rotate time 1 ${name_postfix}"
+ run_newsyslog ${newsyslog_args}
+ ckfe ${LOGFNAME}
+ chkfcnt 1 ${dir}${LOGFNAME}.*${ext}
+ end
+
+ sleep 1.1
+
+ begin "rotate time 2 ${name_postfix}"
+ run_newsyslog ${newsyslog_args}
+ ckfe ${LOGFNAME}
+ chkfcnt 2 ${dir}${LOGFNAME}.*${ext}
+ end
+
+ sleep 1.1
+
+ begin "rotate time 3 ${name_postfix}"
+ run_newsyslog ${newsyslog_args}
+ ckfe ${LOGFNAME}
+ chkfcnt 3 ${dir}${LOGFNAME}.*${ext}
+ end
+
+ sleep 1.1
+
+ begin "rotate time 4 ${name_postfix}"
+ run_newsyslog ${newsyslog_args}
+ ckfe ${LOGFNAME}
+ chkfcnt 3 ${dir}${LOGFNAME}.*${ext}
+ end
+
+ begin "noaction ${name_postfix}"
+ ofiles=`ls -1 ${dir}${LOGFNAME}.*${ext} | tr -d '\n'`
+ run_newsyslog ${newsyslog_args} -n >/dev/null
+ ckfe ${LOGFNAME}
+ ckstr "$ofiles" "`ls -1 ${dir}${LOGFNAME}.*${ext} | tr -d '\n'`"
+ end
+
+ tmpdir_clean
+}
+
+echo 1..126
+mkdir -p ${TMPDIR}
+cd ${TMPDIR}
+
+LOGFNAME=foo.log
+LOGFPATH=${TMPDIR}/log/${LOGFNAME}
+
+# Normal, no archive dir, keep X files
+echo "$LOGFPATH 640 0 * @T00 NC" > newsyslog.conf
+tests_normal_rotate_keepn 0
+
+echo "$LOGFPATH 640 1 * @T00 NC" > newsyslog.conf
+tests_normal_rotate_keepn 1
+
+echo "$LOGFPATH 640 2 * @T00 NC" > newsyslog.conf
+tests_normal_rotate_keepn 2
+
+echo "$LOGFPATH 640 3 * @T00 NC" > newsyslog.conf
+tests_normal_rotate_keepn 3
+
+# Normal, no archive dir, keep X files, gz
+echo "$LOGFPATH 640 0 * @T00 NCZ" > newsyslog.conf
+tests_normal_rotate_keepn 0 ".gz"
+
+echo "$LOGFPATH 640 1 * @T00 NCZ" > newsyslog.conf
+tests_normal_rotate_keepn 1 ".gz"
+
+echo "$LOGFPATH 640 2 * @T00 NCZ" > newsyslog.conf
+tests_normal_rotate_keepn 2 ".gz"
+
+echo "$LOGFPATH 640 3 * @T00 NCZ" > newsyslog.conf
+tests_normal_rotate_keepn 3 ".gz"
+
+# Normal, no archive dir
+echo "$LOGFPATH 640 3 * @T00 NC" > newsyslog.conf
+tests_normal_rotate
+
+echo "$LOGFPATH 640 3 * @T00 NCZ" > newsyslog.conf
+tests_normal_rotate ".gz"
+
+echo "$LOGFPATH 640 3 * @T00 NCJ" > newsyslog.conf
+tests_normal_rotate ".bz2"
+
+# Normal, archive dir
+echo "$LOGFPATH 640 3 * @T00 NC" > newsyslog.conf
+tests_normal_rotate "" "${TMPDIR}/alog/"
+
+echo "$LOGFPATH 640 3 * @T00 NCZ" > newsyslog.conf
+tests_normal_rotate ".gz" "${TMPDIR}/alog/"
+
+echo "$LOGFPATH 640 3 * @T00 NCJ" > newsyslog.conf
+tests_normal_rotate ".bz2" "${TMPDIR}/alog/"
+
+# Time based, no archive dir
+echo "$LOGFPATH 640 3 * @T00 NC" > newsyslog.conf
+tests_time_rotate
+
+echo "$LOGFPATH 640 3 * @T00 NCZ" > newsyslog.conf
+tests_time_rotate "gz" ""
+
+echo "$LOGFPATH 640 3 * @T00 NCJ" > newsyslog.conf
+tests_time_rotate "bz2" ""
+
+# Time based, archive dir
+echo "$LOGFPATH 640 3 * @T00 NC" > newsyslog.conf
+tests_time_rotate "" "${TMPDIR}/alog/"
+
+echo "$LOGFPATH 640 3 * @T00 NCZ" > newsyslog.conf
+tests_time_rotate "gz" "${TMPDIR}/alog/"
+
+echo "$LOGFPATH 640 3 * @T00 NCJ" > newsyslog.conf
+tests_time_rotate "bz2" "${TMPDIR}/alog/"
+
+rm -rf "${TMPDIR}"
diff --git a/usr.sbin/nfscbd/Makefile b/usr.sbin/nfscbd/Makefile
new file mode 100644
index 0000000..1e11e3a
--- /dev/null
+++ b/usr.sbin/nfscbd/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= nfscbd
+MAN= nfscbd.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/nfscbd/Makefile.depend b/usr.sbin/nfscbd/Makefile.depend
new file mode 100644
index 0000000..1061516
--- /dev/null
+++ b/usr.sbin/nfscbd/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/rpc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/nfscbd/nfscbd.8 b/usr.sbin/nfscbd/nfscbd.8
new file mode 100644
index 0000000..958e534
--- /dev/null
+++ b/usr.sbin/nfscbd/nfscbd.8
@@ -0,0 +1,86 @@
+.\" Copyright (c) 2009 Rick Macklem, University of Guelph
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 25, 2009
+.Dt NFSCBD 8
+.Os
+.Sh NAME
+.Nm nfscbd
+.Nd
+.Tn NFSv4
+client side callback daemon
+.Sh SYNOPSIS
+.Nm nfscbd
+.Op Fl p Ar port_number
+.Op Fl P Ar client_principal
+.Sh DESCRIPTION
+.Nm
+runs on a client using
+.Tn NFSv4
+to handle callback requests from the NFSv4 server.
+If no
+.Nm
+is running, NFSv4 mounts will still work, but the server will never issue
+Open Delegations to the client.
+.Pp
+One callback server and one master server
+are always started.
+.Pp
+The following options are available:
+.Bl -tag -width Ds
+.It Fl p Ar port_number
+Specifies what port# the callback server should use.
+.It Fl P Ar client_principal
+Specifies the host based principal name to be used as the target for
+callbacks over RPCSEC_GSS. For KerberosV, it must be in the client's
+default keytab file.
+This client_principal should be the same one specified by the
+.Cm gssname
+argument being used by nfsv4 mounts.
+If you do not specify this argument, callbacks will still work over AUTH_SYS,
+which is what many extant servers use even for RPCSEC_GSS mounts, as of 2009.
+.El
+.Pp
+For example,
+.Dq Li "nfscbd -p 7654 -P root"
+starts the daemon to handle callbacks on port# 7654 and is using the host based
+principal root@<client-host>.<dns-domain> as the callback target.
+.Pp
+.Nm
+listens for service requests at the port
+defined by NFSV4_CBPORT in /usr/include/fs/nfs/nfs.h, unless
+.Fl p
+has been specified.
+For more information on what callbacks and Open Delegations do, see
+.%T "Network File System (NFS) Version 4 Protocol" ,
+RFC3530.
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr nfsv4 4 ,
+.Xr mount_nfs 8
+.Sh HISTORY
+First introduced with the experimental nfs client for NFSv4 support in 2009.
diff --git a/usr.sbin/nfscbd/nfscbd.c b/usr.sbin/nfscbd/nfscbd.c
new file mode 100644
index 0000000..c9153b4
--- /dev/null
+++ b/usr.sbin/nfscbd/nfscbd.c
@@ -0,0 +1,373 @@
+/*-
+ * Copyright (c) 2009 Rick Macklem, University of Guelph
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/ioctl.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/stat.h>
+#include <sys/ucred.h>
+#include <sys/uio.h>
+#include <sys/vnode.h>
+#include <sys/wait.h>
+
+#include <nfs/nfssvc.h>
+
+#include <rpc/rpc.h>
+
+#include <fs/nfs/rpcv2.h>
+#include <fs/nfs/nfsproto.h>
+#include <fs/nfs/nfskpiport.h>
+#include <fs/nfs/nfs.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+/* Global defs */
+#ifdef DEBUG
+#define syslog(e, s) fprintf(stderr,(s))
+static int debug = 1;
+#else
+static int debug = 0;
+#endif
+
+static pid_t children;
+
+static void nonfs(int);
+static void reapchild(int);
+static void usage(void);
+static void cleanup(int);
+static void child_cleanup(int);
+static void nfscbd_exit(int);
+static void killchildren(void);
+
+/*
+ * Nfs callback server daemon.
+ *
+ * 1 - do file descriptor and signal cleanup
+ * 2 - fork the nfscbd(s)
+ * 4 - create callback server socket(s)
+ * 5 - set up server socket for rpc
+ *
+ * For connectionless protocols, just pass the socket into the kernel via.
+ * nfssvc().
+ * For connection based sockets, loop doing accepts. When you get a new
+ * socket from accept, pass the msgsock into the kernel via. nfssvc().
+ */
+int
+main(int argc, char *argv[])
+{
+ struct nfscbd_args nfscbdargs;
+ struct nfsd_nfscbd_args nfscbdargs2;
+ struct sockaddr_in inetaddr, inetpeer;
+ fd_set ready, sockbits;
+ int ch, connect_type_cnt, len, maxsock, msgsock, error;
+ int nfssvc_flag, on, sock, tcpsock, ret, mustfreeai = 0;
+ char *cp, princname[128];
+ char myname[MAXHOSTNAMELEN], *myfqdnname = NULL;
+ struct addrinfo *aip, hints;
+ pid_t pid;
+ short myport = NFSV4_CBPORT;
+
+ if (modfind("nfscl") < 0) {
+ /* Not present in kernel, try loading it */
+ if (kldload("nfscl") < 0 ||
+ modfind("nfscl") < 0)
+ errx(1, "nfscl is not available");
+ }
+ /*
+ * First, get our fully qualified host name, if possible.
+ */
+ if (gethostname(myname, MAXHOSTNAMELEN) >= 0) {
+ cp = strchr(myname, '.');
+ if (cp != NULL && *(cp + 1) != '\0') {
+ cp = myname;
+ } else {
+ /*
+ * No domain on myname, so try looking it up.
+ */
+ cp = NULL;
+ memset((void *)&hints, 0, sizeof (hints));
+ hints.ai_flags = AI_CANONNAME;
+ error = getaddrinfo(myname, NULL, &hints, &aip);
+ if (error == 0) {
+ if (aip->ai_canonname != NULL &&
+ (cp = strchr(aip->ai_canonname, '.')) != NULL
+ && *(cp + 1) != '\0') {
+ cp = aip->ai_canonname;
+ mustfreeai = 1;
+ } else {
+ freeaddrinfo(aip);
+ }
+ }
+ }
+ if (cp == NULL)
+ warnx("Can't get fully qualified host name");
+ myfqdnname = cp;
+ }
+
+ princname[0] = '\0';
+#define GETOPT "p:P:"
+#define USAGE "[ -p port_num ] [ -P client_principal ]"
+ while ((ch = getopt(argc, argv, GETOPT)) != -1)
+ switch (ch) {
+ case 'p':
+ myport = atoi(optarg);
+ if (myport < 1) {
+ warnx("port# non-positive, reset to %d",
+ NFSV4_CBPORT);
+ myport = NFSV4_CBPORT;
+ }
+ break;
+ case 'P':
+ cp = optarg;
+ if (cp != NULL && strlen(cp) > 0 &&
+ strlen(cp) < sizeof (princname)) {
+ if (strchr(cp, '@') == NULL &&
+ myfqdnname != NULL)
+ snprintf(princname, sizeof (princname),
+ "%s@%s", cp, myfqdnname);
+ else
+ strlcpy(princname, cp,
+ sizeof (princname));
+ } else {
+ warnx("client princ invalid. ignored\n");
+ }
+ break;
+ default:
+ case '?':
+ usage();
+ };
+ argv += optind;
+ argc -= optind;
+
+ if (argc > 0)
+ usage();
+
+ if (mustfreeai)
+ freeaddrinfo(aip);
+ nfscbdargs2.principal = (const char *)princname;
+ if (debug == 0) {
+ daemon(0, 0);
+ (void)signal(SIGTERM, SIG_IGN);
+ (void)signal(SIGHUP, SIG_IGN);
+ (void)signal(SIGINT, SIG_IGN);
+ (void)signal(SIGQUIT, SIG_IGN);
+ }
+ (void)signal(SIGSYS, nonfs);
+ (void)signal(SIGCHLD, reapchild);
+
+ openlog("nfscbd:", LOG_PID, LOG_DAEMON);
+
+ pid = fork();
+ if (pid < 0) {
+ syslog(LOG_ERR, "fork: %m");
+ nfscbd_exit(1);
+ } else if (pid > 0) {
+ children = pid;
+ } else {
+ (void)signal(SIGUSR1, child_cleanup);
+ setproctitle("server");
+ nfssvc_flag = NFSSVC_NFSCBD;
+ if (nfssvc(nfssvc_flag, &nfscbdargs2) < 0) {
+ syslog(LOG_ERR, "nfssvc: %m");
+ nfscbd_exit(1);
+ }
+ exit(0);
+ }
+ (void)signal(SIGUSR1, cleanup);
+
+ if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+ syslog(LOG_ERR, "can't create udp socket");
+ nfscbd_exit(1);
+ }
+ memset(&inetaddr, 0, sizeof inetaddr);
+ inetaddr.sin_family = AF_INET;
+ inetaddr.sin_addr.s_addr = INADDR_ANY;
+ inetaddr.sin_port = htons(myport);
+ inetaddr.sin_len = sizeof(inetaddr);
+ ret = bind(sock, (struct sockaddr *)&inetaddr, sizeof(inetaddr));
+ /* If bind() fails, this is a restart, so just skip UDP. */
+ if (ret == 0) {
+ len = sizeof(inetaddr);
+ if (getsockname(sock, (struct sockaddr *)&inetaddr, &len) < 0){
+ syslog(LOG_ERR, "can't get bound addr");
+ nfscbd_exit(1);
+ }
+ nfscbdargs.port = ntohs(inetaddr.sin_port);
+ if (nfscbdargs.port != myport) {
+ syslog(LOG_ERR, "BAD PORT#");
+ nfscbd_exit(1);
+ }
+ nfscbdargs.sock = sock;
+ nfscbdargs.name = NULL;
+ nfscbdargs.namelen = 0;
+ if (nfssvc(NFSSVC_CBADDSOCK, &nfscbdargs) < 0) {
+ syslog(LOG_ERR, "can't Add UDP socket");
+ nfscbd_exit(1);
+ }
+ }
+ (void)close(sock);
+
+ /* Now set up the master server socket waiting for tcp connections. */
+ on = 1;
+ FD_ZERO(&sockbits);
+ connect_type_cnt = 0;
+ if ((tcpsock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+ syslog(LOG_ERR, "can't create tcp socket");
+ nfscbd_exit(1);
+ }
+ if (setsockopt(tcpsock,
+ SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0)
+ syslog(LOG_ERR, "setsockopt SO_REUSEADDR: %m");
+ /* sin_port is already set */
+ inetaddr.sin_family = AF_INET;
+ inetaddr.sin_addr.s_addr = INADDR_ANY;
+ inetaddr.sin_port = htons(myport);
+ inetaddr.sin_len = sizeof(inetaddr);
+ if (bind(tcpsock,
+ (struct sockaddr *)&inetaddr, sizeof (inetaddr)) < 0) {
+ syslog(LOG_ERR, "can't bind tcp addr");
+ nfscbd_exit(1);
+ }
+ if (listen(tcpsock, 5) < 0) {
+ syslog(LOG_ERR, "listen failed");
+ nfscbd_exit(1);
+ }
+ FD_SET(tcpsock, &sockbits);
+ maxsock = tcpsock;
+ connect_type_cnt++;
+
+ setproctitle("master");
+
+ /*
+ * Loop forever accepting connections and passing the sockets
+ * into the kernel for the mounts.
+ */
+ for (;;) {
+ ready = sockbits;
+ if (connect_type_cnt > 1) {
+ if (select(maxsock + 1,
+ &ready, NULL, NULL, NULL) < 1) {
+ syslog(LOG_ERR, "select failed: %m");
+ nfscbd_exit(1);
+ }
+ }
+ if (FD_ISSET(tcpsock, &ready)) {
+ len = sizeof(inetpeer);
+ if ((msgsock = accept(tcpsock,
+ (struct sockaddr *)&inetpeer, &len)) < 0) {
+ syslog(LOG_ERR, "accept failed: %m");
+ nfscbd_exit(1);
+ }
+ memset(inetpeer.sin_zero, 0,
+ sizeof (inetpeer.sin_zero));
+ if (setsockopt(msgsock, SOL_SOCKET,
+ SO_KEEPALIVE, (char *)&on, sizeof(on)) < 0)
+ syslog(LOG_ERR,
+ "setsockopt SO_KEEPALIVE: %m");
+ nfscbdargs.sock = msgsock;
+ nfscbdargs.name = (caddr_t)&inetpeer;
+ nfscbdargs.namelen = sizeof(inetpeer);
+ nfssvc(NFSSVC_CBADDSOCK, &nfscbdargs);
+ (void)close(msgsock);
+ }
+ }
+}
+
+static void
+usage(void)
+{
+
+ errx(1, "usage: nfscbd %s", USAGE);
+}
+
+static void
+nonfs(int signo __unused)
+{
+ syslog(LOG_ERR, "missing system call: NFS not available");
+}
+
+static void
+reapchild(int signo __unused)
+{
+ pid_t pid;
+
+ while ((pid = wait3(NULL, WNOHANG, NULL)) > 0) {
+ if (pid == children)
+ children = -1;
+ }
+}
+
+static void
+killchildren(void)
+{
+
+ if (children > 0)
+ kill(children, SIGKILL);
+}
+
+/*
+ * Cleanup master after SIGUSR1.
+ */
+static void
+cleanup(int signo __unused)
+{
+ nfscbd_exit(0);
+}
+
+/*
+ * Cleanup child after SIGUSR1.
+ */
+static void
+child_cleanup(int signo __unused)
+{
+ exit(0);
+}
+
+static void
+nfscbd_exit(int status __unused)
+{
+ killchildren();
+ exit(status);
+}
diff --git a/usr.sbin/nfsd/Makefile b/usr.sbin/nfsd/Makefile
new file mode 100644
index 0000000..2905067
--- /dev/null
+++ b/usr.sbin/nfsd/Makefile
@@ -0,0 +1,7 @@
+# @(#)Makefile 8.1 (Berkeley) 6/5/93
+# $FreeBSD$
+
+PROG= nfsd
+MAN= nfsd.8 nfsv4.4 stablerestart.5
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/nfsd/Makefile.depend b/usr.sbin/nfsd/Makefile.depend
new file mode 100644
index 0000000..c0b7a14
--- /dev/null
+++ b/usr.sbin/nfsd/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/nfsd/nfsd.8 b/usr.sbin/nfsd/nfsd.8
new file mode 100644
index 0000000..d014a01
--- /dev/null
+++ b/usr.sbin/nfsd/nfsd.8
@@ -0,0 +1,240 @@
+.\" 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.
+.\"
+.\" @(#)nfsd.8 8.4 (Berkeley) 3/29/95
+.\" $FreeBSD$
+.\"
+.Dd April 25, 2015
+.Dt NFSD 8
+.Os
+.Sh NAME
+.Nm nfsd
+.Nd remote
+.Tn NFS
+server
+.Sh SYNOPSIS
+.Nm
+.Op Fl ardute
+.Op Fl n Ar num_servers
+.Op Fl h Ar bindip
+.Op Fl Fl maxthreads Ar max_threads
+.Op Fl Fl minthreads Ar min_threads
+.Sh DESCRIPTION
+The
+.Nm
+utility runs on a server machine to service
+.Tn NFS
+requests from client machines.
+At least one
+.Nm
+must be running for a machine to operate as a server.
+.Pp
+Unless otherwise specified, eight servers per CPU for
+.Tn UDP
+transport are started.
+.Pp
+The following options are available:
+.Bl -tag -width Ds
+.It Fl r
+Register the
+.Tn NFS
+service with
+.Xr rpcbind 8
+without creating any servers.
+This option can be used along with the
+.Fl u
+or
+.Fl t
+options to re-register NFS if the rpcbind server is restarted.
+.It Fl d
+Unregister the
+.Tn NFS
+service with
+.Xr rpcbind 8
+without creating any servers.
+.It Fl n Ar threads
+Specifies how many servers to create. This option is equivalent to specifying
+.Fl Fl maxthreads
+and
+.Fl Fl minthreads
+with their respective arguments to
+.Ar threads .
+.It Fl Fl maxthreads Ar threads
+Specifies the maximum servers that will be kept around to service requests.
+.It Fl Fl minthreads Ar threads
+Specifies the minimum servers that will be kept around to service requests.
+.It Fl h Ar bindip
+Specifies which IP address or hostname to bind to on the local host.
+This option is recommended when a host has multiple interfaces.
+Multiple
+.Fl h
+options may be specified.
+.It Fl a
+Specifies that nfsd should bind to the wildcard IP address.
+This is the default if no
+.Fl h
+options are given.
+It may also be specified in addition to any
+.Fl h
+options given.
+Note that NFS/UDP does not operate properly when
+bound to the wildcard IP address whether you use -a or do not use -h.
+.It Fl t
+Serve
+.Tn TCP NFS
+clients.
+.It Fl u
+Serve
+.Tn UDP NFS
+clients.
+.It Fl e
+Ignored; included for backward compatibility.
+.El
+.Pp
+For example,
+.Dq Li "nfsd -u -t -n 6"
+serves
+.Tn UDP
+and
+.Tn TCP
+transports using six daemons.
+.Pp
+A server should run enough daemons to handle
+the maximum level of concurrency from its clients,
+typically four to six.
+.Pp
+The
+.Nm
+utility listens for service requests at the port indicated in the
+.Tn NFS
+server specification; see
+.%T "Network File System Protocol Specification" ,
+RFC1094,
+.%T "NFS: Network File System Version 3 Protocol Specification" ,
+RFC1813 and
+.%T "Network File System (NFS) Version 4 Protocol" ,
+RFC3530.
+.Pp
+If
+.Nm
+detects that
+.Tn NFS
+is not loaded in the running kernel, it will attempt
+to load a loadable kernel module containing
+.Tn NFS
+support using
+.Xr kldload 2 .
+If this fails, or no
+.Tn NFS
+KLD is available,
+.Nm
+will exit with an error.
+.Pp
+If
+.Nm
+is to be run on a host with multiple interfaces or interface aliases, use
+of the
+.Fl h
+option is recommended.
+If you do not use the option NFS may not respond to
+UDP packets from the same IP address they were sent to.
+Use of this option
+is also recommended when securing NFS exports on a firewalling machine such
+that the NFS sockets can only be accessed by the inside interface.
+The
+.Nm ipfw
+utility
+would then be used to block nfs-related packets that come in on the outside
+interface.
+.Pp
+If the server has stopped servicing clients and has generated a console message
+like
+.Dq Li "nfsd server cache flooded..." ,
+the value for vfs.nfsd.tcphighwater needs to be increased.
+This should allow the server to again handle requests without a reboot.
+Also, you may want to consider decreasing the value for
+vfs.nfsd.tcpcachetimeo to several minutes (in seconds) instead of 12 hours
+when this occurs.
+.Pp
+Unfortunately making vfs.nfsd.tcphighwater too large can result in the mbuf
+limit being reached, as indicated by a console message
+like
+.Dq Li "kern.ipc.nmbufs limit reached" .
+If you cannot find values of the above
+.Nm sysctl
+values that work, you can disable the DRC cache for TCP by setting
+vfs.nfsd.cachetcp to 0.
+.Pp
+The
+.Nm
+utility has to be terminated with
+.Dv SIGUSR1
+and cannot be killed with
+.Dv SIGTERM
+or
+.Dv SIGQUIT .
+The
+.Nm
+utility needs to ignore these signals in order to stay alive as long
+as possible during a shutdown, otherwise loopback mounts will
+not be able to unmount.
+If you have to kill
+.Nm
+just do a
+.Dq Li "kill -USR1 <PID of master nfsd>"
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr nfsstat 1 ,
+.Xr kldload 2 ,
+.Xr nfssvc 2 ,
+.Xr nfsv4 4 ,
+.Xr exports 5 ,
+.Xr stablerestart 5 ,
+.Xr gssd 8 ,
+.Xr ipfw 8 ,
+.Xr mountd 8 ,
+.Xr nfsiod 8 ,
+.Xr nfsrevoke 8 ,
+.Xr nfsuserd 8 ,
+.Xr rpcbind 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Bx 4.4 .
+.Sh BUGS
+If
+.Nm
+is started when
+.Xr gssd 8
+is not running, it will service AUTH_SYS requests only. To fix the problem
+you must kill
+.Nm
+and then restart it, after the
+.Xr gssd 8
+is running.
diff --git a/usr.sbin/nfsd/nfsd.c b/usr.sbin/nfsd/nfsd.c
new file mode 100644
index 0000000..f58ed30
--- /dev/null
+++ b/usr.sbin/nfsd/nfsd.c
@@ -0,0 +1,1086 @@
+/*
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * 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.
+ */
+
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1989, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)nfsd.c 8.9 (Berkeley) 3/29/95";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/syslog.h>
+#include <sys/wait.h>
+#include <sys/mount.h>
+#include <sys/fcntl.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/ucred.h>
+
+#include <rpc/rpc.h>
+#include <rpc/pmap_clnt.h>
+#include <rpcsvc/nfs_prot.h>
+
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <nfsserver/nfs.h>
+#include <nfs/nfssvc.h>
+
+#include <err.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sysexits.h>
+
+#include <getopt.h>
+
+static int debug = 0;
+
+#define NFSD_STABLERESTART "/var/db/nfs-stablerestart"
+#define NFSD_STABLEBACKUP "/var/db/nfs-stablerestart.bak"
+#define MAXNFSDCNT 256
+#define DEFNFSDCNT 4
+static pid_t children[MAXNFSDCNT]; /* PIDs of children */
+static int nfsdcnt; /* number of children */
+static int nfsdcnt_set;
+static int minthreads;
+static int maxthreads;
+static int nfssvc_nfsd; /* Set to correct NFSSVC_xxx flag */
+static int stablefd = -1; /* Fd for the stable restart file */
+static int backupfd; /* Fd for the backup stable restart file */
+static const char *getopt_shortopts;
+static const char *getopt_usage;
+
+static int minthreads_set;
+static int maxthreads_set;
+
+static struct option longopts[] = {
+ { "debug", no_argument, &debug, 1 },
+ { "minthreads", required_argument, &minthreads_set, 1 },
+ { "maxthreads", required_argument, &maxthreads_set, 1 },
+ { NULL, 0, NULL, 0}
+};
+
+static void cleanup(int);
+static void child_cleanup(int);
+static void killchildren(void);
+static void nfsd_exit(int);
+static void nonfs(int);
+static void reapchild(int);
+static int setbindhost(struct addrinfo **ia, const char *bindhost,
+ struct addrinfo hints);
+static void start_server(int);
+static void unregistration(void);
+static void usage(void);
+static void open_stable(int *, int *);
+static void copy_stable(int, int);
+static void backup_stable(int);
+static void set_nfsdcnt(int);
+
+/*
+ * Nfs server daemon mostly just a user context for nfssvc()
+ *
+ * 1 - do file descriptor and signal cleanup
+ * 2 - fork the nfsd(s)
+ * 3 - create server socket(s)
+ * 4 - register socket with rpcbind
+ *
+ * For connectionless protocols, just pass the socket into the kernel via.
+ * nfssvc().
+ * For connection based sockets, loop doing accepts. When you get a new
+ * socket from accept, pass the msgsock into the kernel via. nfssvc().
+ * The arguments are:
+ * -r - reregister with rpcbind
+ * -d - unregister with rpcbind
+ * -t - support tcp nfs clients
+ * -u - support udp nfs clients
+ * -e - forces it to run a server that supports nfsv4
+ * followed by "n" which is the number of nfsds' to fork off
+ */
+int
+main(int argc, char **argv)
+{
+ struct nfsd_addsock_args addsockargs;
+ struct addrinfo *ai_udp, *ai_tcp, *ai_udp6, *ai_tcp6, hints;
+ struct netconfig *nconf_udp, *nconf_tcp, *nconf_udp6, *nconf_tcp6;
+ struct netbuf nb_udp, nb_tcp, nb_udp6, nb_tcp6;
+ struct sockaddr_in inetpeer;
+ struct sockaddr_in6 inet6peer;
+ fd_set ready, sockbits;
+ fd_set v4bits, v6bits;
+ int ch, connect_type_cnt, i, maxsock, msgsock;
+ socklen_t len;
+ int on = 1, unregister, reregister, sock;
+ int tcp6sock, ip6flag, tcpflag, tcpsock;
+ int udpflag, ecode, error, s;
+ int bindhostc, bindanyflag, rpcbreg, rpcbregcnt;
+ int nfssvc_addsock;
+ int longindex = 0;
+ const char *lopt;
+ char **bindhost = NULL;
+ pid_t pid;
+
+ nfsdcnt = DEFNFSDCNT;
+ unregister = reregister = tcpflag = maxsock = 0;
+ bindanyflag = udpflag = connect_type_cnt = bindhostc = 0;
+ getopt_shortopts = "ah:n:rdtue";
+ getopt_usage =
+ "usage:\n"
+ " nfsd [-ardtue] [-h bindip]\n"
+ " [-n numservers] [--minthreads #] [--maxthreads #]\n";
+ while ((ch = getopt_long(argc, argv, getopt_shortopts, longopts,
+ &longindex)) != -1)
+ switch (ch) {
+ case 'a':
+ bindanyflag = 1;
+ break;
+ case 'n':
+ set_nfsdcnt(atoi(optarg));
+ break;
+ case 'h':
+ bindhostc++;
+ bindhost = realloc(bindhost,sizeof(char *)*bindhostc);
+ if (bindhost == NULL)
+ errx(1, "Out of memory");
+ bindhost[bindhostc-1] = strdup(optarg);
+ if (bindhost[bindhostc-1] == NULL)
+ errx(1, "Out of memory");
+ break;
+ case 'r':
+ reregister = 1;
+ break;
+ case 'd':
+ unregister = 1;
+ break;
+ case 't':
+ tcpflag = 1;
+ break;
+ case 'u':
+ udpflag = 1;
+ break;
+ case 'e':
+ /* now a no-op, since this is the default */
+ break;
+ case 0:
+ lopt = longopts[longindex].name;
+ if (!strcmp(lopt, "minthreads")) {
+ minthreads = atoi(optarg);
+ } else if (!strcmp(lopt, "maxthreads")) {
+ maxthreads = atoi(optarg);
+ }
+ break;
+ default:
+ case '?':
+ usage();
+ };
+ if (!tcpflag && !udpflag)
+ udpflag = 1;
+ argv += optind;
+ argc -= optind;
+ if (minthreads_set && maxthreads_set && minthreads > maxthreads)
+ errx(EX_USAGE,
+ "error: minthreads(%d) can't be greater than "
+ "maxthreads(%d)", minthreads, maxthreads);
+
+ /*
+ * XXX
+ * Backward compatibility, trailing number is the count of daemons.
+ */
+ if (argc > 1)
+ usage();
+ if (argc == 1)
+ set_nfsdcnt(atoi(argv[0]));
+
+ /*
+ * Unless the "-o" option was specified, try and run "nfsd".
+ * If "-o" was specified, try and run "nfsserver".
+ */
+ if (modfind("nfsd") < 0) {
+ /* Not present in kernel, try loading it */
+ if (kldload("nfsd") < 0 || modfind("nfsd") < 0)
+ errx(1, "NFS server is not available");
+ }
+
+ ip6flag = 1;
+ s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ if (s == -1) {
+ if (errno != EPROTONOSUPPORT && errno != EAFNOSUPPORT)
+ err(1, "socket");
+ ip6flag = 0;
+ } else if (getnetconfigent("udp6") == NULL ||
+ getnetconfigent("tcp6") == NULL) {
+ ip6flag = 0;
+ }
+ if (s != -1)
+ close(s);
+
+ if (bindhostc == 0 || bindanyflag) {
+ bindhostc++;
+ bindhost = realloc(bindhost,sizeof(char *)*bindhostc);
+ if (bindhost == NULL)
+ errx(1, "Out of memory");
+ bindhost[bindhostc-1] = strdup("*");
+ if (bindhost[bindhostc-1] == NULL)
+ errx(1, "Out of memory");
+ }
+
+ if (unregister) {
+ unregistration();
+ exit (0);
+ }
+ if (reregister) {
+ if (udpflag) {
+ memset(&hints, 0, sizeof hints);
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ ecode = getaddrinfo(NULL, "nfs", &hints, &ai_udp);
+ if (ecode != 0)
+ err(1, "getaddrinfo udp: %s", gai_strerror(ecode));
+ nconf_udp = getnetconfigent("udp");
+ if (nconf_udp == NULL)
+ err(1, "getnetconfigent udp failed");
+ nb_udp.buf = ai_udp->ai_addr;
+ nb_udp.len = nb_udp.maxlen = ai_udp->ai_addrlen;
+ if ((!rpcb_set(NFS_PROGRAM, 2, nconf_udp, &nb_udp)) ||
+ (!rpcb_set(NFS_PROGRAM, 3, nconf_udp, &nb_udp)))
+ err(1, "rpcb_set udp failed");
+ freeaddrinfo(ai_udp);
+ }
+ if (udpflag && ip6flag) {
+ memset(&hints, 0, sizeof hints);
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ ecode = getaddrinfo(NULL, "nfs", &hints, &ai_udp6);
+ if (ecode != 0)
+ err(1, "getaddrinfo udp6: %s", gai_strerror(ecode));
+ nconf_udp6 = getnetconfigent("udp6");
+ if (nconf_udp6 == NULL)
+ err(1, "getnetconfigent udp6 failed");
+ nb_udp6.buf = ai_udp6->ai_addr;
+ nb_udp6.len = nb_udp6.maxlen = ai_udp6->ai_addrlen;
+ if ((!rpcb_set(NFS_PROGRAM, 2, nconf_udp6, &nb_udp6)) ||
+ (!rpcb_set(NFS_PROGRAM, 3, nconf_udp6, &nb_udp6)))
+ err(1, "rpcb_set udp6 failed");
+ freeaddrinfo(ai_udp6);
+ }
+ if (tcpflag) {
+ memset(&hints, 0, sizeof hints);
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+ ecode = getaddrinfo(NULL, "nfs", &hints, &ai_tcp);
+ if (ecode != 0)
+ err(1, "getaddrinfo tcp: %s", gai_strerror(ecode));
+ nconf_tcp = getnetconfigent("tcp");
+ if (nconf_tcp == NULL)
+ err(1, "getnetconfigent tcp failed");
+ nb_tcp.buf = ai_tcp->ai_addr;
+ nb_tcp.len = nb_tcp.maxlen = ai_tcp->ai_addrlen;
+ if ((!rpcb_set(NFS_PROGRAM, 2, nconf_tcp, &nb_tcp)) ||
+ (!rpcb_set(NFS_PROGRAM, 3, nconf_tcp, &nb_tcp)))
+ err(1, "rpcb_set tcp failed");
+ freeaddrinfo(ai_tcp);
+ }
+ if (tcpflag && ip6flag) {
+ memset(&hints, 0, sizeof hints);
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+ ecode = getaddrinfo(NULL, "nfs", &hints, &ai_tcp6);
+ if (ecode != 0)
+ err(1, "getaddrinfo tcp6: %s", gai_strerror(ecode));
+ nconf_tcp6 = getnetconfigent("tcp6");
+ if (nconf_tcp6 == NULL)
+ err(1, "getnetconfigent tcp6 failed");
+ nb_tcp6.buf = ai_tcp6->ai_addr;
+ nb_tcp6.len = nb_tcp6.maxlen = ai_tcp6->ai_addrlen;
+ if ((!rpcb_set(NFS_PROGRAM, 2, nconf_tcp6, &nb_tcp6)) ||
+ (!rpcb_set(NFS_PROGRAM, 3, nconf_tcp6, &nb_tcp6)))
+ err(1, "rpcb_set tcp6 failed");
+ freeaddrinfo(ai_tcp6);
+ }
+ exit (0);
+ }
+ if (debug == 0) {
+ daemon(0, 0);
+ (void)signal(SIGHUP, SIG_IGN);
+ (void)signal(SIGINT, SIG_IGN);
+ /*
+ * nfsd sits in the kernel most of the time. It needs
+ * to ignore SIGTERM/SIGQUIT in order to stay alive as long
+ * as possible during a shutdown, otherwise loopback
+ * mounts will not be able to unmount.
+ */
+ (void)signal(SIGTERM, SIG_IGN);
+ (void)signal(SIGQUIT, SIG_IGN);
+ }
+ (void)signal(SIGSYS, nonfs);
+ (void)signal(SIGCHLD, reapchild);
+ (void)signal(SIGUSR2, backup_stable);
+
+ openlog("nfsd", LOG_PID | (debug ? LOG_PERROR : 0), LOG_DAEMON);
+
+ /*
+ * For V4, we open the stablerestart file and call nfssvc()
+ * to get it loaded. This is done before the daemons do the
+ * regular nfssvc() call to service NFS requests.
+ * (This way the file remains open until the last nfsd is killed
+ * off.)
+ * It and the backup copy will be created as empty files
+ * the first time this nfsd is started and should never be
+ * deleted/replaced if at all possible. It should live on a
+ * local, non-volatile storage device that does not do hardware
+ * level write-back caching. (See SCSI doc for more information
+ * on how to prevent write-back caching on SCSI disks.)
+ */
+ open_stable(&stablefd, &backupfd);
+ if (stablefd < 0) {
+ syslog(LOG_ERR, "Can't open %s: %m\n", NFSD_STABLERESTART);
+ exit(1);
+ }
+ /* This system call will fail for old kernels, but that's ok. */
+ nfssvc(NFSSVC_BACKUPSTABLE, NULL);
+ if (nfssvc(NFSSVC_STABLERESTART, (caddr_t)&stablefd) < 0) {
+ syslog(LOG_ERR, "Can't read stable storage file: %m\n");
+ exit(1);
+ }
+ nfssvc_addsock = NFSSVC_NFSDADDSOCK;
+ nfssvc_nfsd = NFSSVC_NFSDNFSD;
+
+ if (tcpflag) {
+ /*
+ * For TCP mode, we fork once to start the first
+ * kernel nfsd thread. The kernel will add more
+ * threads as needed.
+ */
+ pid = fork();
+ if (pid == -1) {
+ syslog(LOG_ERR, "fork: %m");
+ nfsd_exit(1);
+ }
+ if (pid) {
+ children[0] = pid;
+ } else {
+ (void)signal(SIGUSR1, child_cleanup);
+ setproctitle("server");
+ start_server(0);
+ }
+ }
+
+ (void)signal(SIGUSR1, cleanup);
+ FD_ZERO(&v4bits);
+ FD_ZERO(&v6bits);
+ FD_ZERO(&sockbits);
+
+ rpcbregcnt = 0;
+ /* Set up the socket for udp and rpcb register it. */
+ if (udpflag) {
+ rpcbreg = 0;
+ for (i = 0; i < bindhostc; i++) {
+ memset(&hints, 0, sizeof hints);
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ if (setbindhost(&ai_udp, bindhost[i], hints) == 0) {
+ rpcbreg = 1;
+ rpcbregcnt++;
+ if ((sock = socket(ai_udp->ai_family,
+ ai_udp->ai_socktype,
+ ai_udp->ai_protocol)) < 0) {
+ syslog(LOG_ERR,
+ "can't create udp socket");
+ nfsd_exit(1);
+ }
+ if (bind(sock, ai_udp->ai_addr,
+ ai_udp->ai_addrlen) < 0) {
+ syslog(LOG_ERR,
+ "can't bind udp addr %s: %m",
+ bindhost[i]);
+ nfsd_exit(1);
+ }
+ freeaddrinfo(ai_udp);
+ addsockargs.sock = sock;
+ addsockargs.name = NULL;
+ addsockargs.namelen = 0;
+ if (nfssvc(nfssvc_addsock, &addsockargs) < 0) {
+ syslog(LOG_ERR, "can't Add UDP socket");
+ nfsd_exit(1);
+ }
+ (void)close(sock);
+ }
+ }
+ if (rpcbreg == 1) {
+ memset(&hints, 0, sizeof hints);
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ ecode = getaddrinfo(NULL, "nfs", &hints, &ai_udp);
+ if (ecode != 0) {
+ syslog(LOG_ERR, "getaddrinfo udp: %s",
+ gai_strerror(ecode));
+ nfsd_exit(1);
+ }
+ nconf_udp = getnetconfigent("udp");
+ if (nconf_udp == NULL)
+ err(1, "getnetconfigent udp failed");
+ nb_udp.buf = ai_udp->ai_addr;
+ nb_udp.len = nb_udp.maxlen = ai_udp->ai_addrlen;
+ if ((!rpcb_set(NFS_PROGRAM, 2, nconf_udp, &nb_udp)) ||
+ (!rpcb_set(NFS_PROGRAM, 3, nconf_udp, &nb_udp)))
+ err(1, "rpcb_set udp failed");
+ freeaddrinfo(ai_udp);
+ }
+ }
+
+ /* Set up the socket for udp6 and rpcb register it. */
+ if (udpflag && ip6flag) {
+ rpcbreg = 0;
+ for (i = 0; i < bindhostc; i++) {
+ memset(&hints, 0, sizeof hints);
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ if (setbindhost(&ai_udp6, bindhost[i], hints) == 0) {
+ rpcbreg = 1;
+ rpcbregcnt++;
+ if ((sock = socket(ai_udp6->ai_family,
+ ai_udp6->ai_socktype,
+ ai_udp6->ai_protocol)) < 0) {
+ syslog(LOG_ERR,
+ "can't create udp6 socket");
+ nfsd_exit(1);
+ }
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
+ &on, sizeof on) < 0) {
+ syslog(LOG_ERR,
+ "can't set v6-only binding for "
+ "udp6 socket: %m");
+ nfsd_exit(1);
+ }
+ if (bind(sock, ai_udp6->ai_addr,
+ ai_udp6->ai_addrlen) < 0) {
+ syslog(LOG_ERR,
+ "can't bind udp6 addr %s: %m",
+ bindhost[i]);
+ nfsd_exit(1);
+ }
+ freeaddrinfo(ai_udp6);
+ addsockargs.sock = sock;
+ addsockargs.name = NULL;
+ addsockargs.namelen = 0;
+ if (nfssvc(nfssvc_addsock, &addsockargs) < 0) {
+ syslog(LOG_ERR,
+ "can't add UDP6 socket");
+ nfsd_exit(1);
+ }
+ (void)close(sock);
+ }
+ }
+ if (rpcbreg == 1) {
+ memset(&hints, 0, sizeof hints);
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ ecode = getaddrinfo(NULL, "nfs", &hints, &ai_udp6);
+ if (ecode != 0) {
+ syslog(LOG_ERR, "getaddrinfo udp6: %s",
+ gai_strerror(ecode));
+ nfsd_exit(1);
+ }
+ nconf_udp6 = getnetconfigent("udp6");
+ if (nconf_udp6 == NULL)
+ err(1, "getnetconfigent udp6 failed");
+ nb_udp6.buf = ai_udp6->ai_addr;
+ nb_udp6.len = nb_udp6.maxlen = ai_udp6->ai_addrlen;
+ if ((!rpcb_set(NFS_PROGRAM, 2, nconf_udp6, &nb_udp6)) ||
+ (!rpcb_set(NFS_PROGRAM, 3, nconf_udp6, &nb_udp6)))
+ err(1, "rpcb_set udp6 failed");
+ freeaddrinfo(ai_udp6);
+ }
+ }
+
+ /* Set up the socket for tcp and rpcb register it. */
+ if (tcpflag) {
+ rpcbreg = 0;
+ for (i = 0; i < bindhostc; i++) {
+ memset(&hints, 0, sizeof hints);
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+ if (setbindhost(&ai_tcp, bindhost[i], hints) == 0) {
+ rpcbreg = 1;
+ rpcbregcnt++;
+ if ((tcpsock = socket(AF_INET, SOCK_STREAM,
+ 0)) < 0) {
+ syslog(LOG_ERR,
+ "can't create tcp socket");
+ nfsd_exit(1);
+ }
+ if (setsockopt(tcpsock, SOL_SOCKET,
+ SO_REUSEADDR,
+ (char *)&on, sizeof(on)) < 0)
+ syslog(LOG_ERR,
+ "setsockopt SO_REUSEADDR: %m");
+ if (bind(tcpsock, ai_tcp->ai_addr,
+ ai_tcp->ai_addrlen) < 0) {
+ syslog(LOG_ERR,
+ "can't bind tcp addr %s: %m",
+ bindhost[i]);
+ nfsd_exit(1);
+ }
+ if (listen(tcpsock, -1) < 0) {
+ syslog(LOG_ERR, "listen failed");
+ nfsd_exit(1);
+ }
+ freeaddrinfo(ai_tcp);
+ FD_SET(tcpsock, &sockbits);
+ FD_SET(tcpsock, &v4bits);
+ maxsock = tcpsock;
+ connect_type_cnt++;
+ }
+ }
+ if (rpcbreg == 1) {
+ memset(&hints, 0, sizeof hints);
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+ ecode = getaddrinfo(NULL, "nfs", &hints,
+ &ai_tcp);
+ if (ecode != 0) {
+ syslog(LOG_ERR, "getaddrinfo tcp: %s",
+ gai_strerror(ecode));
+ nfsd_exit(1);
+ }
+ nconf_tcp = getnetconfigent("tcp");
+ if (nconf_tcp == NULL)
+ err(1, "getnetconfigent tcp failed");
+ nb_tcp.buf = ai_tcp->ai_addr;
+ nb_tcp.len = nb_tcp.maxlen = ai_tcp->ai_addrlen;
+ if ((!rpcb_set(NFS_PROGRAM, 2, nconf_tcp,
+ &nb_tcp)) || (!rpcb_set(NFS_PROGRAM, 3,
+ nconf_tcp, &nb_tcp)))
+ err(1, "rpcb_set tcp failed");
+ freeaddrinfo(ai_tcp);
+ }
+ }
+
+ /* Set up the socket for tcp6 and rpcb register it. */
+ if (tcpflag && ip6flag) {
+ rpcbreg = 0;
+ for (i = 0; i < bindhostc; i++) {
+ memset(&hints, 0, sizeof hints);
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+ if (setbindhost(&ai_tcp6, bindhost[i], hints) == 0) {
+ rpcbreg = 1;
+ rpcbregcnt++;
+ if ((tcp6sock = socket(ai_tcp6->ai_family,
+ ai_tcp6->ai_socktype,
+ ai_tcp6->ai_protocol)) < 0) {
+ syslog(LOG_ERR,
+ "can't create tcp6 socket");
+ nfsd_exit(1);
+ }
+ if (setsockopt(tcp6sock, SOL_SOCKET,
+ SO_REUSEADDR,
+ (char *)&on, sizeof(on)) < 0)
+ syslog(LOG_ERR,
+ "setsockopt SO_REUSEADDR: %m");
+ if (setsockopt(tcp6sock, IPPROTO_IPV6,
+ IPV6_V6ONLY, &on, sizeof on) < 0) {
+ syslog(LOG_ERR,
+ "can't set v6-only binding for tcp6 "
+ "socket: %m");
+ nfsd_exit(1);
+ }
+ if (bind(tcp6sock, ai_tcp6->ai_addr,
+ ai_tcp6->ai_addrlen) < 0) {
+ syslog(LOG_ERR,
+ "can't bind tcp6 addr %s: %m",
+ bindhost[i]);
+ nfsd_exit(1);
+ }
+ if (listen(tcp6sock, -1) < 0) {
+ syslog(LOG_ERR, "listen failed");
+ nfsd_exit(1);
+ }
+ freeaddrinfo(ai_tcp6);
+ FD_SET(tcp6sock, &sockbits);
+ FD_SET(tcp6sock, &v6bits);
+ if (maxsock < tcp6sock)
+ maxsock = tcp6sock;
+ connect_type_cnt++;
+ }
+ }
+ if (rpcbreg == 1) {
+ memset(&hints, 0, sizeof hints);
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+ ecode = getaddrinfo(NULL, "nfs", &hints, &ai_tcp6);
+ if (ecode != 0) {
+ syslog(LOG_ERR, "getaddrinfo tcp6: %s",
+ gai_strerror(ecode));
+ nfsd_exit(1);
+ }
+ nconf_tcp6 = getnetconfigent("tcp6");
+ if (nconf_tcp6 == NULL)
+ err(1, "getnetconfigent tcp6 failed");
+ nb_tcp6.buf = ai_tcp6->ai_addr;
+ nb_tcp6.len = nb_tcp6.maxlen = ai_tcp6->ai_addrlen;
+ if ((!rpcb_set(NFS_PROGRAM, 2, nconf_tcp6, &nb_tcp6)) ||
+ (!rpcb_set(NFS_PROGRAM, 3, nconf_tcp6, &nb_tcp6)))
+ err(1, "rpcb_set tcp6 failed");
+ freeaddrinfo(ai_tcp6);
+ }
+ }
+
+ if (rpcbregcnt == 0) {
+ syslog(LOG_ERR, "rpcb_set() failed, nothing to do: %m");
+ nfsd_exit(1);
+ }
+
+ if (tcpflag && connect_type_cnt == 0) {
+ syslog(LOG_ERR, "tcp connects == 0, nothing to do: %m");
+ nfsd_exit(1);
+ }
+
+ setproctitle("master");
+ /*
+ * We always want a master to have a clean way to to shut nfsd down
+ * (with unregistration): if the master is killed, it unregisters and
+ * kills all children. If we run for UDP only (and so do not have to
+ * loop waiting waiting for accept), we instead make the parent
+ * a "server" too. start_server will not return.
+ */
+ if (!tcpflag)
+ start_server(1);
+
+ /*
+ * Loop forever accepting connections and passing the sockets
+ * into the kernel for the mounts.
+ */
+ for (;;) {
+ ready = sockbits;
+ if (connect_type_cnt > 1) {
+ if (select(maxsock + 1,
+ &ready, NULL, NULL, NULL) < 1) {
+ error = errno;
+ if (error == EINTR)
+ continue;
+ syslog(LOG_ERR, "select failed: %m");
+ nfsd_exit(1);
+ }
+ }
+ for (tcpsock = 0; tcpsock <= maxsock; tcpsock++) {
+ if (FD_ISSET(tcpsock, &ready)) {
+ if (FD_ISSET(tcpsock, &v4bits)) {
+ len = sizeof(inetpeer);
+ if ((msgsock = accept(tcpsock,
+ (struct sockaddr *)&inetpeer, &len)) < 0) {
+ error = errno;
+ syslog(LOG_ERR, "accept failed: %m");
+ if (error == ECONNABORTED ||
+ error == EINTR)
+ continue;
+ nfsd_exit(1);
+ }
+ memset(inetpeer.sin_zero, 0,
+ sizeof(inetpeer.sin_zero));
+ if (setsockopt(msgsock, SOL_SOCKET,
+ SO_KEEPALIVE, (char *)&on, sizeof(on)) < 0)
+ syslog(LOG_ERR,
+ "setsockopt SO_KEEPALIVE: %m");
+ addsockargs.sock = msgsock;
+ addsockargs.name = (caddr_t)&inetpeer;
+ addsockargs.namelen = len;
+ nfssvc(nfssvc_addsock, &addsockargs);
+ (void)close(msgsock);
+ } else if (FD_ISSET(tcpsock, &v6bits)) {
+ len = sizeof(inet6peer);
+ if ((msgsock = accept(tcpsock,
+ (struct sockaddr *)&inet6peer,
+ &len)) < 0) {
+ error = errno;
+ syslog(LOG_ERR,
+ "accept failed: %m");
+ if (error == ECONNABORTED ||
+ error == EINTR)
+ continue;
+ nfsd_exit(1);
+ }
+ if (setsockopt(msgsock, SOL_SOCKET,
+ SO_KEEPALIVE, (char *)&on,
+ sizeof(on)) < 0)
+ syslog(LOG_ERR, "setsockopt "
+ "SO_KEEPALIVE: %m");
+ addsockargs.sock = msgsock;
+ addsockargs.name = (caddr_t)&inet6peer;
+ addsockargs.namelen = len;
+ nfssvc(nfssvc_addsock, &addsockargs);
+ (void)close(msgsock);
+ }
+ }
+ }
+ }
+}
+
+static int
+setbindhost(struct addrinfo **ai, const char *bindhost, struct addrinfo hints)
+{
+ int ecode;
+ u_int32_t host_addr[4]; /* IPv4 or IPv6 */
+ const char *hostptr;
+
+ if (bindhost == NULL || strcmp("*", bindhost) == 0)
+ hostptr = NULL;
+ else
+ hostptr = bindhost;
+
+ if (hostptr != NULL) {
+ switch (hints.ai_family) {
+ case AF_INET:
+ if (inet_pton(AF_INET, hostptr, host_addr) == 1) {
+ hints.ai_flags = AI_NUMERICHOST;
+ } else {
+ if (inet_pton(AF_INET6, hostptr,
+ host_addr) == 1)
+ return (1);
+ }
+ break;
+ case AF_INET6:
+ if (inet_pton(AF_INET6, hostptr, host_addr) == 1) {
+ hints.ai_flags = AI_NUMERICHOST;
+ } else {
+ if (inet_pton(AF_INET, hostptr,
+ host_addr) == 1)
+ return (1);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ ecode = getaddrinfo(hostptr, "nfs", &hints, ai);
+ if (ecode != 0) {
+ syslog(LOG_ERR, "getaddrinfo %s: %s", bindhost,
+ gai_strerror(ecode));
+ return (1);
+ }
+ return (0);
+}
+
+static void
+set_nfsdcnt(int proposed)
+{
+
+ if (proposed < 1) {
+ warnx("nfsd count too low %d; reset to %d", proposed,
+ DEFNFSDCNT);
+ nfsdcnt = DEFNFSDCNT;
+ } else if (proposed > MAXNFSDCNT) {
+ warnx("nfsd count too high %d; truncated to %d", proposed,
+ MAXNFSDCNT);
+ nfsdcnt = MAXNFSDCNT;
+ } else
+ nfsdcnt = proposed;
+ nfsdcnt_set = 1;
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "%s", getopt_usage);
+ exit(1);
+}
+
+static void
+nonfs(__unused int signo)
+{
+ syslog(LOG_ERR, "missing system call: NFS not available");
+}
+
+static void
+reapchild(__unused int signo)
+{
+ pid_t pid;
+ int i;
+
+ while ((pid = wait3(NULL, WNOHANG, NULL)) > 0) {
+ for (i = 0; i < nfsdcnt; i++)
+ if (pid == children[i])
+ children[i] = -1;
+ }
+}
+
+static void
+unregistration(void)
+{
+ if ((!rpcb_unset(NFS_PROGRAM, 2, NULL)) ||
+ (!rpcb_unset(NFS_PROGRAM, 3, NULL)))
+ syslog(LOG_ERR, "rpcb_unset failed");
+}
+
+static void
+killchildren(void)
+{
+ int i;
+
+ for (i = 0; i < nfsdcnt; i++) {
+ if (children[i] > 0)
+ kill(children[i], SIGKILL);
+ }
+}
+
+/*
+ * Cleanup master after SIGUSR1.
+ */
+static void
+cleanup(__unused int signo)
+{
+ nfsd_exit(0);
+}
+
+/*
+ * Cleanup child after SIGUSR1.
+ */
+static void
+child_cleanup(__unused int signo)
+{
+ exit(0);
+}
+
+static void
+nfsd_exit(int status)
+{
+ killchildren();
+ unregistration();
+ exit(status);
+}
+
+static int
+get_tuned_nfsdcount(void)
+{
+ int ncpu, error, tuned_nfsdcnt;
+ size_t ncpu_size;
+
+ ncpu_size = sizeof(ncpu);
+ error = sysctlbyname("hw.ncpu", &ncpu, &ncpu_size, NULL, 0);
+ if (error) {
+ warnx("sysctlbyname(hw.ncpu) failed defaulting to %d nfs servers",
+ DEFNFSDCNT);
+ tuned_nfsdcnt = DEFNFSDCNT;
+ } else {
+ tuned_nfsdcnt = ncpu * 8;
+ }
+ return tuned_nfsdcnt;
+}
+
+static void
+start_server(int master)
+{
+ char principal[MAXHOSTNAMELEN + 5];
+ struct nfsd_nfsd_args nfsdargs;
+ int status, error;
+ char hostname[MAXHOSTNAMELEN + 1], *cp;
+ struct addrinfo *aip, hints;
+
+ status = 0;
+ gethostname(hostname, sizeof (hostname));
+ snprintf(principal, sizeof (principal), "nfs@%s", hostname);
+ if ((cp = strchr(hostname, '.')) == NULL ||
+ *(cp + 1) == '\0') {
+ /* If not fully qualified, try getaddrinfo() */
+ memset((void *)&hints, 0, sizeof (hints));
+ hints.ai_flags = AI_CANONNAME;
+ error = getaddrinfo(hostname, NULL, &hints, &aip);
+ if (error == 0) {
+ if (aip->ai_canonname != NULL &&
+ (cp = strchr(aip->ai_canonname, '.')) !=
+ NULL && *(cp + 1) != '\0')
+ snprintf(principal, sizeof (principal),
+ "nfs@%s", aip->ai_canonname);
+ freeaddrinfo(aip);
+ }
+ }
+ nfsdargs.principal = principal;
+
+ if (nfsdcnt_set)
+ nfsdargs.minthreads = nfsdargs.maxthreads = nfsdcnt;
+ else {
+ nfsdargs.minthreads = minthreads_set ? minthreads : get_tuned_nfsdcount();
+ nfsdargs.maxthreads = maxthreads_set ? maxthreads : nfsdargs.minthreads;
+ if (nfsdargs.maxthreads < nfsdargs.minthreads)
+ nfsdargs.maxthreads = nfsdargs.minthreads;
+ }
+ error = nfssvc(nfssvc_nfsd, &nfsdargs);
+ if (error < 0 && errno == EAUTH) {
+ /*
+ * This indicates that it could not register the
+ * rpcsec_gss credentials, usually because the
+ * gssd daemon isn't running.
+ * (only the experimental server with nfsv4)
+ */
+ syslog(LOG_ERR, "No gssd, using AUTH_SYS only");
+ principal[0] = '\0';
+ error = nfssvc(nfssvc_nfsd, &nfsdargs);
+ }
+ if (error < 0) {
+ syslog(LOG_ERR, "nfssvc: %m");
+ status = 1;
+ }
+ if (master)
+ nfsd_exit(status);
+ else
+ exit(status);
+}
+
+/*
+ * Open the stable restart file and return the file descriptor for it.
+ */
+static void
+open_stable(int *stable_fdp, int *backup_fdp)
+{
+ int stable_fd, backup_fd = -1, ret;
+ struct stat st, backup_st;
+
+ /* Open and stat the stable restart file. */
+ stable_fd = open(NFSD_STABLERESTART, O_RDWR, 0);
+ if (stable_fd < 0)
+ stable_fd = open(NFSD_STABLERESTART, O_RDWR | O_CREAT, 0600);
+ if (stable_fd >= 0) {
+ ret = fstat(stable_fd, &st);
+ if (ret < 0) {
+ close(stable_fd);
+ stable_fd = -1;
+ }
+ }
+
+ /* Open and stat the backup stable restart file. */
+ if (stable_fd >= 0) {
+ backup_fd = open(NFSD_STABLEBACKUP, O_RDWR, 0);
+ if (backup_fd < 0)
+ backup_fd = open(NFSD_STABLEBACKUP, O_RDWR | O_CREAT,
+ 0600);
+ if (backup_fd >= 0) {
+ ret = fstat(backup_fd, &backup_st);
+ if (ret < 0) {
+ close(backup_fd);
+ backup_fd = -1;
+ }
+ }
+ if (backup_fd < 0) {
+ close(stable_fd);
+ stable_fd = -1;
+ }
+ }
+
+ *stable_fdp = stable_fd;
+ *backup_fdp = backup_fd;
+ if (stable_fd < 0)
+ return;
+
+ /* Sync up the 2 files, as required. */
+ if (st.st_size > 0)
+ copy_stable(stable_fd, backup_fd);
+ else if (backup_st.st_size > 0)
+ copy_stable(backup_fd, stable_fd);
+}
+
+/*
+ * Copy the stable restart file to the backup or vice versa.
+ */
+static void
+copy_stable(int from_fd, int to_fd)
+{
+ int cnt, ret;
+ static char buf[1024];
+
+ ret = lseek(from_fd, (off_t)0, SEEK_SET);
+ if (ret >= 0)
+ ret = lseek(to_fd, (off_t)0, SEEK_SET);
+ if (ret >= 0)
+ ret = ftruncate(to_fd, (off_t)0);
+ if (ret >= 0)
+ do {
+ cnt = read(from_fd, buf, 1024);
+ if (cnt > 0)
+ ret = write(to_fd, buf, cnt);
+ else if (cnt < 0)
+ ret = cnt;
+ } while (cnt > 0 && ret >= 0);
+ if (ret >= 0)
+ ret = fsync(to_fd);
+ if (ret < 0)
+ syslog(LOG_ERR, "stable restart copy failure: %m");
+}
+
+/*
+ * Back up the stable restart file when indicated by the kernel.
+ */
+static void
+backup_stable(__unused int signo)
+{
+
+ if (stablefd >= 0)
+ copy_stable(stablefd, backupfd);
+}
+
diff --git a/usr.sbin/nfsd/nfsv4.4 b/usr.sbin/nfsd/nfsv4.4
new file mode 100644
index 0000000..8d9bc80
--- /dev/null
+++ b/usr.sbin/nfsd/nfsv4.4
@@ -0,0 +1,331 @@
+.\" Copyright (c) 2009 Rick Macklem, University of Guelph
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 1, 2013
+.Dt NFSV4 4
+.Os
+.Sh NAME
+.Nm NFSv4
+.Nd NFS Version 4 Protocol
+.Sh DESCRIPTION
+The NFS client and server provides support for the
+.Tn NFSv4
+specification; see
+.%T "Network File System (NFS) Version 4 Protocol RFC 3530" .
+The protocol is somewhat similar to NFS Version 3, but differs in significant
+ways.
+It uses a single compound RPC that concatenates operations to-gether.
+Each of these operations are similar to the RPCs of NFS Version 3.
+The operations in the compound are performed in order, until one of
+them fails (returns an error) and then the RPC terminates at that point.
+.Pp
+It has
+integrated locking support, which implies that the server is no longer
+stateless.
+As such, the
+.Nm
+server remains in recovery mode for a grace period (always greater than the
+lease duration the server uses) after a reboot.
+During this grace period, clients may recover state but not perform other
+open/lock state changing operations.
+To provide for correct recovery semantics, a small file described by
+.Xr stablerestart 5
+is used by the server during the recovery phase.
+If this file is missing or empty, there is a backup copy maintained by
+.Xr nfsd 8
+that will be used. If either file is missing, they will be
+created by the
+.Xr nfsd 8 .
+If both the file and the backup copy are empty,
+it will result in the server starting without providing a grace period
+for recovery.
+Note that recovery only occurs when the server
+machine is rebooted, not when the
+.Xr nfsd 8
+are just restarted.
+.Pp
+It provides several optional features not present in NFS Version 3:
+.sp
+.Bd -literal -offset indent -compact
+- NFS Version 4 ACLs
+- Referrals, which redirect subtrees to other servers
+ (not yet implemented)
+- Delegations, which allow a client to operate on a file locally
+.Ed
+.Pp
+The
+.Nm
+protocol does not use a separate mount protocol and assumes that the
+server provides a single file system tree structure, rooted at the point
+in the local file system tree specified by one or more
+.sp 1
+.Bd -literal -offset indent -compact
+V4: <rootdir> [-sec=secflavors] [host(s) or net]
+.Ed
+.sp 1
+line(s) in the
+.Xr exports 5
+file.
+(See
+.Xr exports 5
+for details.)
+The
+.Xr nfsd 8
+allows a limited subset of operations to be performed on non-exported subtrees
+of the local file system, so that traversal of the tree to the exported
+subtrees is possible.
+As such, the ``<rootdir>'' can be in a non-exported file system.
+The exception is ZFS, which checks exports and, as such, all ZFS file systems
+below the ``<rootdir>'' must be exported.
+However,
+the entire tree that is rooted at that point must be in local file systems
+that are of types that can be NFS exported.
+Since the
+.Nm
+file system is rooted at ``<rootdir>'', setting this to anything other
+than ``/'' will result in clients being required to use different mount
+paths for
+.Nm
+than for NFS Version 2 or 3.
+Unlike NFS Version 2 and 3, Version 4 allows a client mount to span across
+multiple server file systems, although not all clients are capable of doing
+this.
+.Pp
+.Nm
+uses names for users and groups instead of numbers.
+On the wire, they
+take the form:
+.sp
+.Bd -literal -offset indent -compact
+<user>@<dns.domain>
+.Ed
+.sp
+where ``<dns.domain>'' is not the same as the DNS domain used
+for host name lookups, but is usually set to the same string.
+Most systems set this ``<dns.domain>''
+to the domain name part of the machine's
+.Xr hostname 1
+by default.
+However, this can normally be overridden by a command line
+option or configuration file for the daemon used to do the name<->number
+mapping.
+Under FreeBSD, the mapping daemon is called
+.Xr nfsuserd 8
+and has a command line option that overrides the domain component of the
+machine's hostname.
+For use of
+.Nm ,
+either client or server, this daemon must be running.
+If this ``<dns.domain>'' is not set correctly or the daemon is not running, ``ls -l'' will typically
+report a lot of ``nobody'' and ``nogroup'' ownerships.
+.Pp
+Although uid/gid numbers are no longer used in the
+.Nm
+protocol, they will still be in the RPC authentication fields when
+using AUTH_SYS (sec=sys), which is the default.
+As such, in this case both the user/group name and number spaces must
+be consistent between the client and server.
+.Pp
+However, if you run
+.Nm
+with RPCSEC_GSS (sec=krb5, krb5i, krb5p), only names and KerberosV tickets
+will go on the wire.
+.Sh SERVER SETUP
+To set up the NFS server that supports
+.Nm ,
+you will need to either set the variables in
+.Xr rc.conf 5
+as follows:
+.sp
+.Bd -literal -offset indent -compact
+nfs_server_enable="YES"
+nfsv4_server_enable="YES"
+nfsuserd_enable="YES"
+.Ed
+.sp
+or start
+.Xr mountd 8
+and
+.Xr nfsd 8
+without the ``-o'' option, which would force use of the old server.
+The
+.Xr nfsuserd 8
+daemon must also be running.
+.Pp
+You will also need to add at least one ``V4:'' line to the
+.Xr exports 5
+file for
+.Nm
+to work.
+.Pp
+If the file systems you are exporting are only being accessed via
+.Nm
+there are a couple of
+.Xr sysctl 8
+variables that you can change, which might improve performance.
+.Bl -tag -width Ds
+.It Cm vfs.nfsd.issue_delegations
+when set non-zero, allows the server to issue Open Delegations to
+clients.
+These delegations permit the client to manipulate the file
+locally on the client.
+Unfortunately, at this time, client use of
+delegations is limited, so performance gains may not be observed.
+This can only be enabled when the file systems being exported to
+.Nm
+clients are not being accessed locally on the server and, if being
+accessed via NFS Version 2 or 3 clients, these clients cannot be
+using the NLM.
+.It Cm vfs.nfsd.enable_locallocks
+can be set to 0 to disable acquisition of local byte range locks.
+Disabling local locking can only be done if neither local accesses
+to the exported file systems nor the NLM is operating on them.
+.El
+.sp
+Note that Samba server access would be considered ``local access'' for the above
+discussion.
+.Pp
+To build a kernel with the NFS server that supports
+.Nm
+linked into it, the
+.sp
+.Bd -literal -offset indent -compact
+options NFSD
+.Ed
+.sp
+must be specified in the kernel's
+.Xr config 5
+file.
+.Sh CLIENT MOUNTS
+To do an
+.Nm
+mount, specify the ``nfsv4'' option on the
+.Xr mount_nfs 8
+command line.
+This will force use of the client that supports
+.Nm
+plus set ``tcp'' and
+.Nm .
+.Pp
+The
+.Xr nfsuserd 8
+must be running, as above.
+Also, since an
+.Nm
+mount uses the host uuid to identify the client uniquely to the server,
+you cannot safely do an
+.Nm
+mount when
+.sp
+.Bd -literal -offset indent -compact
+hostid_enable="NO"
+.Ed
+.sp
+is set in
+.Xr rc.conf 5 .
+.sp
+If the
+.Nm
+server that is being mounted on supports delegations, you can start the
+.Xr nfscbd 8
+daemon to handle client side callbacks.
+This will occur if
+.sp
+.Bd -literal -offset indent -compact
+nfsuserd_enable="YES"
+nfscbd_enable="YES"
+.Ed
+.sp
+are set in
+.Xr rc.conf 5 .
+.sp
+Without a functioning callback path, a server will never issue Delegations
+to a client.
+.sp
+By default, the callback address will be set to the IP address acquired via
+rtalloc() in the kernel and port# 7745.
+To override the default port#, a command line option for
+.Xr nfscbd 8
+can be used.
+.sp
+To get callbacks to work when behind a NAT gateway, a port for the callback
+service will need to be set up on the NAT gateway and then the address
+of the NAT gateway (host IP plus port#) will need to be set by assigning the
+.Xr sysctl 8
+variable vfs.nfs.callback_addr to a string of the form:
+.sp
+N.N.N.N.N.N
+.sp
+where the first 4 Ns are the host IP address and the last two are the
+port# in network byte order (all decimal #s in the range 0-255).
+.Pp
+To build a kernel with the client that supports
+.Nm
+linked into it, the option
+.sp
+.Bd -literal -offset indent -compact
+options NFSCL
+.Ed
+.sp
+must be specified in the kernel's
+.Xr config 5
+file.
+.Pp
+Options can be specified for the
+.Xr nfsuserd 8
+and
+.Xr nfscbd 8
+daemons at boot time via the ``nfsuserd_flags'' and ``nfscbd_flags''
+.Xr rc.conf 5
+variables.
+.Pp
+NFSv4 mount(s) against exported volume(s) on the same host are not recommended,
+since this can result in a hung NFS server.
+It occurs when an nfsd thread tries to do an NFSv4 VOP_RECLAIM()/Close RPC
+as part of acquiring a new vnode.
+If all other nfsd threads are blocked waiting for lock(s) held by this nfsd
+thread, then there isn't an nfsd thread to service the Close RPC.
+.Sh FILES
+.Bl -tag -width /var/db/nfs-stablerestart.bak -compact
+.It Pa /var/db/nfs-stablerestart
+NFS V4 stable restart file
+.It Pa /var/db/nfs-stablerestart.bak
+backup copy of the file
+.El
+.Sh SEE ALSO
+.Xr stablerestart 5 ,
+.Xr mountd 8 ,
+.Xr nfscbd 8 ,
+.Xr nfsd 8 ,
+.Xr nfsdumpstate 8 ,
+.Xr nfsrevoke 8 ,
+.Xr nfsuserd 8
+.Sh BUGS
+At this time, there is no recall of delegations for local file system
+operations.
+As such, delegations should only be enabled for file systems
+that are being used solely as NFS export volumes and are not being accessed
+via local system calls nor services such as Samba.
diff --git a/usr.sbin/nfsd/stablerestart.5 b/usr.sbin/nfsd/stablerestart.5
new file mode 100644
index 0000000..7096053
--- /dev/null
+++ b/usr.sbin/nfsd/stablerestart.5
@@ -0,0 +1,97 @@
+.\" Copyright (c) 2009 Rick Macklem, University of Guelph
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 10, 2011
+.Dt STABLERESTART 5
+.Os
+.Sh NAME
+.Nm nfs-stablerestart
+.Nd restart information for the
+.Tn NFSv4
+server
+.Sh SYNOPSIS
+.Nm nfs-stablerestart
+.Sh DESCRIPTION
+The
+.Nm
+file holds information that allows the
+.Tn NFSv4
+server to restart without always returning the NFSERR_NOGRACE error, as described in the
+.Tn NFSv4
+server specification; see
+.%T "Network File System (NFS) Version 4 Protocol RFC 3530, Section 8.6.3" .
+.Pp
+The first record in the file, as defined by struct nfsf_rec in
+/usr/include/fs/nfs/nfsrvstate.h, holds the lease duration of the
+last incarnation of the server and the number of boot times that follows.
+Following this are the number of previous boot times listed in the
+first record.
+The lease duration is used to set the grace period.
+The boot times
+are used to avoid the unlikely occurrence of a boot time being reused,
+due to a TOD clock going backwards. This record and the previous boot times with this boot time added is re-written at the
+end of the grace period.
+.Pp
+The rest of the file are appended records, as defined by
+struct nfst_rec in /usr/include/fs/nfs/nfsrvstate.h and are used
+represent one of two things. There are records which indicate that a
+client successfully acquired state and records that indicate a client's state was revoked.
+State revoke records indicate that state information
+for a client was discarded, due to lease expiry and an otherwise
+conflicting open or lock request being made by a different client.
+These records can be used
+to determine if clients might have done either of the
+edge conditions.
+.Pp
+If a client might have done either edge condition or this file is
+empty or corrupted, the server returns NFSERR_NOGRACE for any reclaim
+request from the client.
+.Pp
+For correct operation of the server, it must be ensured that the file
+is written to stable storage by the time a write op with IO_SYNC specified
+has returned. This might require hardware level caching to be disabled for
+a local disk drive that holds the file, or similar.
+.Sh FILES
+.Bl -tag -width /var/db/nfs-stablerestart.bak -compact
+.It Pa /var/db/nfs-stablerestart
+NFSv4 stable restart file
+.It Pa /var/db/nfs-stablerestart.bak
+backup copy of the file
+.El
+.Sh SEE ALSO
+.Xr nfsv4 4 ,
+.Xr nfsd 8
+.Sh BUGS
+If the file is empty, the NFSv4 server has no choice but to return
+NFSERR_NOGRACE for all reclaim requests. Although correct, this is
+a highly undesirable occurrence, so the file should not be lost if
+at all possible. The backup copy of the file is maintained
+and used by the
+.Xr nfsd 8
+to minimize the risk of this occurring.
+To move the file, you must edit
+the nfsd sources and recompile it. This was done to discourage
+accidental relocation of the file.
diff --git a/usr.sbin/nfsdumpstate/Makefile b/usr.sbin/nfsdumpstate/Makefile
new file mode 100644
index 0000000..938fb07
--- /dev/null
+++ b/usr.sbin/nfsdumpstate/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= nfsdumpstate
+MAN= nfsdumpstate.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/nfsdumpstate/Makefile.depend b/usr.sbin/nfsdumpstate/Makefile.depend
new file mode 100644
index 0000000..54c1f6f
--- /dev/null
+++ b/usr.sbin/nfsdumpstate/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/nfsdumpstate/nfsdumpstate.8 b/usr.sbin/nfsdumpstate/nfsdumpstate.8
new file mode 100644
index 0000000..1a9f110
--- /dev/null
+++ b/usr.sbin/nfsdumpstate/nfsdumpstate.8
@@ -0,0 +1,74 @@
+.\" Copyright (c) 2009 Rick Macklem, University of Guelph
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 25, 2009
+.Dt NFSDUMPSTATE 8
+.Os
+.Sh NAME
+.Nm nfsdumpstate
+.Nd display
+.Tn NFSv4
+open/lock state
+.Sh SYNOPSIS
+.Nm nfsdumpstate
+.Op Fl o
+.Op Fl l Ar filename
+.Sh DESCRIPTION
+.Nm
+displays open/lock state for the
+.Tn NFSv4
+client and server in the experimental nfs subsystem.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl o
+Displays a summary of Clients for NFSv4. Each line lists a Client with
+the ClientID being the last field of the line.
+.Pp
+The following are the client flag values displayed:
+.Bl -tag -compact -offset 2n -width MMMM
+.It NC
+Needs Confirmation
+.It CB
+Callbacks are enabled
+.It GSS
+Using RPCSEC_GSS
+.It REV
+Administratively Revoked, via nfsrevoke(8)
+.El
+.It Fl l Ar filename
+Displays a list of all NFSv4 Opens and Locks on the file specified by
+the
+.Ar filename .
+The ClientID is the last field of each line.
+.El
+.Sh SEE ALSO
+.Xr nfsv4 4 ,
+.Xr nfsrevoke 8
+.Sh HISTORY
+The
+.Nm
+utility was introduced with the NFSv4 experimental subsystem in 2009.
diff --git a/usr.sbin/nfsdumpstate/nfsdumpstate.c b/usr.sbin/nfsdumpstate/nfsdumpstate.c
new file mode 100644
index 0000000..3858177
--- /dev/null
+++ b/usr.sbin/nfsdumpstate/nfsdumpstate.c
@@ -0,0 +1,280 @@
+/*-
+ * Copyright (c) 2009 Rick Macklem, University of Guelph
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <sys/module.h>
+#include <sys/socket.h>
+
+#include <arpa/inet.h>
+
+#include <netinet/in.h>
+
+#include <nfs/nfssvc.h>
+
+#include <fs/nfs/rpcv2.h>
+#include <fs/nfs/nfsproto.h>
+#include <fs/nfs/nfskpiport.h>
+#include <fs/nfs/nfs.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define DUMPSIZE 10000
+
+static void dump_lockstate(char *);
+static void dump_openstate(void);
+static void usage(void);
+static char *open_flags(uint32_t);
+static char *deleg_flags(uint32_t);
+static char *lock_flags(uint32_t);
+static char *client_flags(uint32_t);
+
+static struct nfsd_dumpclients dp[DUMPSIZE];
+static struct nfsd_dumplocks lp[DUMPSIZE];
+static char flag_string[20];
+
+int
+main(int argc, char **argv)
+{
+ int ch, openstate;
+ char *lockfile;
+
+ if (modfind("nfsd") < 0)
+ errx(1, "nfsd not loaded - self terminating");
+ openstate = 0;
+ lockfile = NULL;
+ while ((ch = getopt(argc, argv, "ol:")) != -1)
+ switch (ch) {
+ case 'o':
+ openstate = 1;
+ break;
+ case 'l':
+ lockfile = optarg;
+ break;
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (openstate == 0 && lockfile == NULL)
+ openstate = 1;
+ else if (openstate != 0 && lockfile != NULL)
+ errx(1, "-o and -l cannot both be specified");
+
+ /*
+ * For -o, dump all open/lock state.
+ * For -l, dump lock state for that file.
+ */
+ if (openstate != 0)
+ dump_openstate();
+ else
+ dump_lockstate(lockfile);
+ exit(0);
+}
+
+static void
+usage(void)
+{
+
+ errx(1, "usage: nfsdumpstate [-o] [-l]");
+}
+
+/*
+ * Dump all open/lock state.
+ */
+static void
+dump_openstate(void)
+{
+ struct nfsd_dumplist dumplist;
+ int cnt, i;
+
+ dumplist.ndl_size = DUMPSIZE;
+ dumplist.ndl_list = (void *)dp;
+ if (nfssvc(NFSSVC_DUMPCLIENTS, &dumplist) < 0)
+ errx(1, "Can't perform dump clients syscall");
+
+ printf("%-13s %9.9s %9.9s %9.9s %9.9s %9.9s %9.9s %-15s %s\n",
+ "Flags", "OpenOwner", "Open", "LockOwner",
+ "Lock", "Deleg", "OldDeleg", "Clientaddr", "ClientID");
+ /*
+ * Loop through results, printing them out.
+ */
+ cnt = 0;
+ while (dp[cnt].ndcl_clid.nclid_idlen > 0 && cnt < DUMPSIZE) {
+ printf("%-13s ", client_flags(dp[cnt].ndcl_flags));
+ printf("%9d %9d %9d %9d %9d %9d ",
+ dp[cnt].ndcl_nopenowners,
+ dp[cnt].ndcl_nopens,
+ dp[cnt].ndcl_nlockowners,
+ dp[cnt].ndcl_nlocks,
+ dp[cnt].ndcl_ndelegs,
+ dp[cnt].ndcl_nolddelegs);
+ if (dp[cnt].ndcl_addrfam == AF_INET)
+ printf("%-15s ",
+ inet_ntoa(dp[cnt].ndcl_cbaddr.sin_addr));
+ for (i = 0; i < dp[cnt].ndcl_clid.nclid_idlen; i++)
+ printf("%02x", dp[cnt].ndcl_clid.nclid_id[i]);
+ printf("\n");
+ cnt++;
+ }
+}
+
+/*
+ * Dump the lock state for a file.
+ */
+static void
+dump_lockstate(char *fname)
+{
+ struct nfsd_dumplocklist dumplocklist;
+ int cnt, i;
+
+ dumplocklist.ndllck_size = DUMPSIZE;
+ dumplocklist.ndllck_list = (void *)lp;
+ dumplocklist.ndllck_fname = fname;
+ if (nfssvc(NFSSVC_DUMPLOCKS, &dumplocklist) < 0)
+ errx(1, "Can't dump locks for %s\n", fname);
+
+ printf("%-11s %-36s %-15s %s\n",
+ "Open/Lock",
+ " Stateid or Lock Range",
+ "Clientaddr",
+ "Owner and ClientID");
+ /*
+ * Loop through results, printing them out.
+ */
+ cnt = 0;
+ while (lp[cnt].ndlck_clid.nclid_idlen > 0 && cnt < DUMPSIZE) {
+ if (lp[cnt].ndlck_flags & NFSLCK_OPEN)
+ printf("%-11s %9d %08x %08x %08x ",
+ open_flags(lp[cnt].ndlck_flags),
+ lp[cnt].ndlck_stateid.seqid,
+ lp[cnt].ndlck_stateid.other[0],
+ lp[cnt].ndlck_stateid.other[1],
+ lp[cnt].ndlck_stateid.other[2]);
+ else if (lp[cnt].ndlck_flags & (NFSLCK_DELEGREAD |
+ NFSLCK_DELEGWRITE))
+ printf("%-11s %9d %08x %08x %08x ",
+ deleg_flags(lp[cnt].ndlck_flags),
+ lp[cnt].ndlck_stateid.seqid,
+ lp[cnt].ndlck_stateid.other[0],
+ lp[cnt].ndlck_stateid.other[1],
+ lp[cnt].ndlck_stateid.other[2]);
+ else
+ printf("%-11s %17jd %17jd ",
+ lock_flags(lp[cnt].ndlck_flags),
+ lp[cnt].ndlck_first,
+ lp[cnt].ndlck_end);
+ if (lp[cnt].ndlck_addrfam == AF_INET)
+ printf("%-15s ",
+ inet_ntoa(lp[cnt].ndlck_cbaddr.sin_addr));
+ else
+ printf("%-15s ", " ");
+ for (i = 0; i < lp[cnt].ndlck_owner.nclid_idlen; i++)
+ printf("%02x", lp[cnt].ndlck_owner.nclid_id[i]);
+ printf(" ");
+ for (i = 0; i < lp[cnt].ndlck_clid.nclid_idlen; i++)
+ printf("%02x", lp[cnt].ndlck_clid.nclid_id[i]);
+ printf("\n");
+ cnt++;
+ }
+}
+
+/*
+ * Parse the Open/Lock flag bits and create a string to be printed.
+ */
+static char *
+open_flags(uint32_t flags)
+{
+ int i, j;
+
+ strlcpy(flag_string, "Open ", sizeof (flag_string));
+ i = 5;
+ if (flags & NFSLCK_READACCESS)
+ flag_string[i++] = 'R';
+ if (flags & NFSLCK_WRITEACCESS)
+ flag_string[i++] = 'W';
+ flag_string[i++] = ' ';
+ flag_string[i++] = 'D';
+ flag_string[i] = 'N';
+ j = i;
+ if (flags & NFSLCK_READDENY)
+ flag_string[i++] = 'R';
+ if (flags & NFSLCK_WRITEDENY)
+ flag_string[i++] = 'W';
+ if (i == j)
+ i++;
+ flag_string[i] = '\0';
+ return (flag_string);
+}
+
+static char *
+deleg_flags(uint32_t flags)
+{
+
+ if (flags & NFSLCK_DELEGREAD)
+ strlcpy(flag_string, "Deleg R", sizeof (flag_string));
+ else
+ strlcpy(flag_string, "Deleg W", sizeof (flag_string));
+ return (flag_string);
+}
+
+static char *
+lock_flags(uint32_t flags)
+{
+
+ if (flags & NFSLCK_READ)
+ strlcpy(flag_string, "Lock R", sizeof (flag_string));
+ else
+ strlcpy(flag_string, "Lock W", sizeof (flag_string));
+ return (flag_string);
+}
+
+static char *
+client_flags(uint32_t flags)
+{
+
+ flag_string[0] = '\0';
+ if (flags & LCL_NEEDSCONFIRM)
+ strlcat(flag_string, "NC ", sizeof (flag_string));
+ if (flags & LCL_CALLBACKSON)
+ strlcat(flag_string, "CB ", sizeof (flag_string));
+ if (flags & LCL_GSS)
+ strlcat(flag_string, "GSS ", sizeof (flag_string));
+ if (flags & LCL_ADMINREVOKED)
+ strlcat(flag_string, "REV", sizeof (flag_string));
+ return (flag_string);
+}
diff --git a/usr.sbin/nfsrevoke/Makefile b/usr.sbin/nfsrevoke/Makefile
new file mode 100644
index 0000000..ab78aa3
--- /dev/null
+++ b/usr.sbin/nfsrevoke/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= nfsrevoke
+MAN= nfsrevoke.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/nfsrevoke/Makefile.depend b/usr.sbin/nfsrevoke/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/nfsrevoke/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/nfsrevoke/nfsrevoke.8 b/usr.sbin/nfsrevoke/nfsrevoke.8
new file mode 100644
index 0000000..00a8915
--- /dev/null
+++ b/usr.sbin/nfsrevoke/nfsrevoke.8
@@ -0,0 +1,64 @@
+.\" Copyright (c) 2009 Rick Macklem, University of Guelph
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 25, 2009
+.Dt NFSREVOKE 8
+.Os
+.Sh NAME
+.Nm nfsrevoke
+.Nd revoke
+.Tn NFS
+V4 client
+.Sh SYNOPSIS
+.Nm nfsrevoke
+.Ar ClientId
+.Sh DESCRIPTION
+.Nm
+This command is used by a system administrator to revoke a client's access
+to the NFS Version 4 server. All Open/Lock state held by the client will
+be released.
+After revocation, the client will no longer be able to use state on the server
+until it does a fresh SetClientID/SetClientIDConfirm operations sequence.
+THIS SHOULD BE DONE AS A LAST RESORT ONLY, when clients are holding state
+that must be released on the server.
+.Pp
+The
+.Ar ClientId
+argument is a hexadecimal string, which is the last field
+of the
+.Xr nfsdumpstate 8
+command's
+.Fl o
+and
+.Fl l
+options output.
+.Sh SEE ALSO
+.Xr nfsv4 4 ,
+.Xr nfsdumpstate 8
+.Sh HISTORY
+The
+.Nm
+command was introduced as a part of the experimental nfs server subsystem.
diff --git a/usr.sbin/nfsrevoke/nfsrevoke.c b/usr.sbin/nfsrevoke/nfsrevoke.c
new file mode 100644
index 0000000..85eb48b
--- /dev/null
+++ b/usr.sbin/nfsrevoke/nfsrevoke.c
@@ -0,0 +1,123 @@
+/*-
+ * Copyright (c) 2009 Rick Macklem, University of Guelph
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <sys/module.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/vnode.h>
+
+#include <netinet/in.h>
+
+#include <nfs/nfssvc.h>
+
+#include <fs/nfs/rpcv2.h>
+#include <fs/nfs/nfsproto.h>
+#include <fs/nfs/nfskpiport.h>
+#include <fs/nfs/nfs.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ char *cp;
+ u_char val;
+ int cnt, even;
+ struct nfsd_clid revoke_handle;
+
+ if (modfind("nfsd") < 0)
+ errx(1, "nfsd not loaded - self terminating");
+ if (argc != 2)
+ usage();
+ cnt = 0;
+ cp = argv[1];
+ if (strlen(cp) % 2)
+ even = 0;
+ else
+ even = 1;
+ val = 0;
+ while (*cp) {
+ if (*cp >= '0' && *cp <= '9')
+ val += (u_char)(*cp - '0');
+ else if (*cp >= 'A' && *cp <= 'F')
+ val += ((u_char)(*cp - 'A')) + 0xa;
+ else if (*cp >= 'a' && *cp <= 'f')
+ val += ((u_char)(*cp - 'a')) + 0xa;
+ else
+ errx(1, "Non hexadecimal digit in %s", argv[1]);
+ if (even) {
+ val <<= 4;
+ even = 0;
+ } else {
+ revoke_handle.nclid_id[cnt++] = val;
+ if (cnt > NFSV4_OPAQUELIMIT)
+ errx(1, "Clientid %s, loo long", argv[1]);
+ val = 0;
+ even = 1;
+ }
+ cp++;
+ }
+
+ /*
+ * Do the revocation system call.
+ */
+ revoke_handle.nclid_idlen = cnt;
+#ifdef DEBUG
+ printf("Idlen=%d\n", revoke_handle.nclid_idlen);
+ for (cnt = 0; cnt < revoke_handle.nclid_idlen; cnt++)
+ printf("%02x", revoke_handle.nclid_id[cnt]);
+ printf("\n");
+#else
+ if (nfssvc(NFSSVC_ADMINREVOKE, &revoke_handle) < 0)
+ err(1, "Admin revoke failed");
+#endif
+}
+
+static void
+usage(void)
+{
+
+ errx(1, "Usage: nfsrevoke <ClientID>");
+}
diff --git a/usr.sbin/nfsuserd/Makefile b/usr.sbin/nfsuserd/Makefile
new file mode 100644
index 0000000..6153449
--- /dev/null
+++ b/usr.sbin/nfsuserd/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= nfsuserd
+MAN= nfsuserd.8
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/nfsuserd/Makefile.depend b/usr.sbin/nfsuserd/Makefile.depend
new file mode 100644
index 0000000..1061516
--- /dev/null
+++ b/usr.sbin/nfsuserd/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/rpc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/nfsuserd/nfsuserd.8 b/usr.sbin/nfsuserd/nfsuserd.8
new file mode 100644
index 0000000..84f4a22
--- /dev/null
+++ b/usr.sbin/nfsuserd/nfsuserd.8
@@ -0,0 +1,128 @@
+.\" Copyright (c) 2009 Rick Macklem, University of Guelph
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 November 1, 2015
+.Dt NFSUSERD 8
+.Os
+.Sh NAME
+.Nm nfsuserd
+.Nd load user and group information into the kernel for
+.Tn NFSv4
+services plus support manage-gids for all NFS versions
+.Sh SYNOPSIS
+.Nm nfsuserd
+.Op Fl domain Ar domain_name
+.Op Fl usertimeout Ar minutes
+.Op Fl usermax Ar max_cache_size
+.Op Fl verbose
+.Op Fl force
+.Op Fl manage-gids
+.Op Ar num_servers
+.Sh DESCRIPTION
+.Nm
+loads user and group information into the kernel for NFSv4.
+It must be running for NFSv4 to function correctly, either client or server.
+It also provides support for manage-gids and must be running on the server if
+this is being used for any version of NFS.
+.Pp
+Upon startup, it loads the machines DNS domain name, plus timeout and
+cache size limit into the kernel. It then preloads the cache with group
+and user information, up to the cache size limit and forks off N children
+(default 4), that service requests from the kernel for cache misses. The
+master server is there for the sole purpose of killing off the slaves.
+To stop the nfsuserd, send a SIGUSR1 to the master server.
+.Pp
+The following options are available:
+.Bl -tag -width Ds
+.It Fl domain Ar domain_name
+This option allows you to override the default DNS domain name, which
+is acquired by taking either the suffix on the machine's hostname or,
+if that name is not a fully qualified host name, the canonical name as
+reported by
+.Xr getaddrinfo 3 .
+.It Fl usertimeout Ar minutes
+Overrides the default timeout for cache entries, in minutes. If the
+timeout is specified as 0, cache entries never time out. The longer the
+time out, the better the performance, but the longer it takes for replaced
+entries to be seen. If your user/group database management system almost
+never re-uses the same names or id numbers, a large timeout is recommended.
+The default is 1 minute.
+.It Fl usermax Ar max_cache_size
+Overrides the default upper bound on the cache size. The larger the cache,
+the more kernel memory is used, but the better the performance. If your
+system can afford the memory use, make this the sum of the number of
+entries in your group and password databases.
+The default is 200 entries.
+.It Fl verbose
+When set, the server logs a bunch of information to syslog.
+.It Fl force
+This flag option must be set to restart the daemon after it has gone away
+abnormally and refuses to start, because it thinks nfsuserd is already
+running.
+.It Fl manage-gids
+This flag enables manage-gids for the NFS server
+.Xr nfsd 8 .
+When this is enabled, all NFS requests using
+AUTH_SYS authentication take the uid from the RPC request
+and uses the group list for that uid provided by
+.Xr getgrouplist 3
+on the server instead of the list of groups provided in the RPC authenticator.
+This can be used to avoid the 16 group limit for AUTH_SYS.
+.It Ar num_servers
+Specifies how many servers to create (max 20).
+The default of 4 may be sufficient. You should run enough servers, so that
+.Xr ps 1
+shows almost no running time for one or two of the slaves after the system
+has been running for a long period. Running too few will have a major
+performance impact, whereas running too many will only tie up some resources,
+such as a process table entry and swap space.
+.El
+.Sh SEE ALSO
+.Xr getgrent 3 ,
+.Xr getgrouplist 3 ,
+.Xr getpwent 3 ,
+.Xr nfsv4 4 ,
+.Xr group 5 ,
+.Xr passwd 5 ,
+.Xr nfsd 8
+.Sh HISTORY
+The
+.Nm
+utility was introduced with the NFSv4 experimental subsystem in 2009.
+.Sh BUGS
+The
+.Nm
+use
+.Xr getgrent 3 ,
+.Xr getgrouplist 3
+and
+.Xr getpwent 3
+library calls to resolve requests and will hang if the servers handling
+those requests fail and the library functions don't return. See
+.Xr group 5
+and
+.Xr passwd 5
+for more information on how the databases are accessed.
diff --git a/usr.sbin/nfsuserd/nfsuserd.c b/usr.sbin/nfsuserd/nfsuserd.c
new file mode 100644
index 0000000..293da71
--- /dev/null
+++ b/usr.sbin/nfsuserd/nfsuserd.c
@@ -0,0 +1,725 @@
+/*-
+ * Copyright (c) 2009 Rick Macklem, University of Guelph
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/errno.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/time.h>
+#include <sys/ucred.h>
+#include <sys/vnode.h>
+#include <sys/wait.h>
+
+#include <nfs/nfssvc.h>
+
+#include <rpc/rpc.h>
+
+#include <fs/nfs/rpcv2.h>
+#include <fs/nfs/nfsproto.h>
+#include <fs/nfs/nfskpiport.h>
+#include <fs/nfs/nfs.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <grp.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+/*
+ * This program loads the password and group databases into the kernel
+ * for NFS V4.
+ */
+
+static void cleanup_term(int);
+static void usage(void);
+static void nfsuserdsrv(struct svc_req *, SVCXPRT *);
+static bool_t xdr_getid(XDR *, caddr_t);
+static bool_t xdr_getname(XDR *, caddr_t);
+static bool_t xdr_retval(XDR *, caddr_t);
+
+#define MAXNAME 1024
+#define MAXNFSUSERD 20
+#define DEFNFSUSERD 4
+#define MAXUSERMAX 100000
+#define MINUSERMAX 10
+#define DEFUSERMAX 200
+#define DEFUSERTIMEOUT (1 * 60)
+struct info {
+ long id;
+ long retval;
+ char name[MAXNAME + 1];
+};
+
+u_char *dnsname = "default.domain";
+u_char *defaultuser = "nobody";
+uid_t defaultuid = (uid_t)32767;
+u_char *defaultgroup = "nogroup";
+gid_t defaultgid = (gid_t)32767;
+int verbose = 0, im_a_slave = 0, nfsuserdcnt = -1, forcestart = 0;
+int defusertimeout = DEFUSERTIMEOUT, manage_gids = 0;
+pid_t slaves[MAXNFSUSERD];
+
+int
+main(int argc, char *argv[])
+{
+ int i, j;
+ int error, fnd_dup, len, mustfreeai = 0, start_uidpos;
+ struct nfsd_idargs nid;
+ struct passwd *pwd;
+ struct group *grp;
+ int sock, one = 1;
+ SVCXPRT *udptransp;
+ u_short portnum;
+ sigset_t signew;
+ char hostname[MAXHOSTNAMELEN + 1], *cp;
+ struct addrinfo *aip, hints;
+ static uid_t check_dups[MAXUSERMAX];
+ gid_t grps[NGROUPS];
+ int ngroup;
+
+ if (modfind("nfscommon") < 0) {
+ /* Not present in kernel, try loading it */
+ if (kldload("nfscommon") < 0 ||
+ modfind("nfscommon") < 0)
+ errx(1, "Experimental nfs subsystem is not available");
+ }
+
+ /*
+ * First, figure out what our domain name and Kerberos Realm
+ * seem to be. Command line args may override these later.
+ */
+ if (gethostname(hostname, MAXHOSTNAMELEN) == 0) {
+ if ((cp = strchr(hostname, '.')) != NULL &&
+ *(cp + 1) != '\0') {
+ dnsname = cp + 1;
+ } else {
+ memset((void *)&hints, 0, sizeof (hints));
+ hints.ai_flags = AI_CANONNAME;
+ error = getaddrinfo(hostname, NULL, &hints, &aip);
+ if (error == 0) {
+ if (aip->ai_canonname != NULL &&
+ (cp = strchr(aip->ai_canonname, '.')) != NULL
+ && *(cp + 1) != '\0') {
+ dnsname = cp + 1;
+ mustfreeai = 1;
+ } else {
+ freeaddrinfo(aip);
+ }
+ }
+ }
+ }
+ nid.nid_usermax = DEFUSERMAX;
+ nid.nid_usertimeout = defusertimeout;
+
+ argc--;
+ argv++;
+ while (argc >= 1) {
+ if (!strcmp(*argv, "-domain")) {
+ if (argc == 1)
+ usage();
+ argc--;
+ argv++;
+ strncpy(hostname, *argv, MAXHOSTNAMELEN);
+ hostname[MAXHOSTNAMELEN] = '\0';
+ dnsname = hostname;
+ } else if (!strcmp(*argv, "-verbose")) {
+ verbose = 1;
+ } else if (!strcmp(*argv, "-force")) {
+ forcestart = 1;
+ } else if (!strcmp(*argv, "-manage-gids")) {
+ manage_gids = 1;
+ } else if (!strcmp(*argv, "-usermax")) {
+ if (argc == 1)
+ usage();
+ argc--;
+ argv++;
+ i = atoi(*argv);
+ if (i < MINUSERMAX || i > MAXUSERMAX) {
+ fprintf(stderr,
+ "usermax %d out of range %d<->%d\n", i,
+ MINUSERMAX, MAXUSERMAX);
+ usage();
+ }
+ nid.nid_usermax = i;
+ } else if (!strcmp(*argv, "-usertimeout")) {
+ if (argc == 1)
+ usage();
+ argc--;
+ argv++;
+ i = atoi(*argv);
+ if (i < 0 || i > 100000) {
+ fprintf(stderr,
+ "usertimeout %d out of range 0<->100000\n",
+ i);
+ usage();
+ }
+ nid.nid_usertimeout = defusertimeout = i * 60;
+ } else if (nfsuserdcnt == -1) {
+ nfsuserdcnt = atoi(*argv);
+ if (nfsuserdcnt < 1)
+ usage();
+ if (nfsuserdcnt > MAXNFSUSERD) {
+ warnx("nfsuserd count %d; reset to %d",
+ nfsuserdcnt, DEFNFSUSERD);
+ nfsuserdcnt = DEFNFSUSERD;
+ }
+ } else {
+ usage();
+ }
+ argc--;
+ argv++;
+ }
+ if (nfsuserdcnt < 1)
+ nfsuserdcnt = DEFNFSUSERD;
+
+ /*
+ * Strip off leading and trailing '.'s in domain name and map
+ * alphabetics to lower case.
+ */
+ while (*dnsname == '.')
+ dnsname++;
+ if (*dnsname == '\0')
+ errx(1, "Domain name all '.'");
+ len = strlen(dnsname);
+ cp = dnsname + len - 1;
+ while (*cp == '.') {
+ *cp = '\0';
+ len--;
+ cp--;
+ }
+ for (i = 0; i < len; i++) {
+ if (!isascii(dnsname[i]))
+ errx(1, "Domain name has non-ascii char");
+ if (isupper(dnsname[i]))
+ dnsname[i] = tolower(dnsname[i]);
+ }
+
+ /*
+ * If the nfsuserd died off ungracefully, this is necessary to
+ * get them to start again.
+ */
+ if (forcestart && nfssvc(NFSSVC_NFSUSERDDELPORT, NULL) < 0)
+ errx(1, "Can't do nfssvc() to delete the port");
+
+ if (verbose)
+ fprintf(stderr,
+ "nfsuserd: domain=%s usermax=%d usertimeout=%d\n",
+ dnsname, nid.nid_usermax, nid.nid_usertimeout);
+
+ for (i = 0; i < nfsuserdcnt; i++)
+ slaves[i] = (pid_t)-1;
+
+ /*
+ * Set up the service port to accept requests via UDP from
+ * localhost (127.0.0.1).
+ */
+ if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
+ err(1, "cannot create udp socket");
+
+ /*
+ * Not sure what this does, so I'll leave it here for now.
+ */
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+
+ if ((udptransp = svcudp_create(sock)) == NULL)
+ err(1, "Can't set up socket");
+
+ /*
+ * By not specifying a protocol, it is linked into the
+ * dispatch queue, but not registered with portmapper,
+ * which is just what I want.
+ */
+ if (!svc_register(udptransp, RPCPROG_NFSUSERD, RPCNFSUSERD_VERS,
+ nfsuserdsrv, 0))
+ err(1, "Can't register nfsuserd");
+
+ /*
+ * Tell the kernel what my port# is.
+ */
+ portnum = htons(udptransp->xp_port);
+#ifdef DEBUG
+ printf("portnum=0x%x\n", portnum);
+#else
+ if (nfssvc(NFSSVC_NFSUSERDPORT, (caddr_t)&portnum) < 0) {
+ if (errno == EPERM) {
+ fprintf(stderr,
+ "Can't start nfsuserd when already running");
+ fprintf(stderr,
+ " If not running, use the -force option.\n");
+ } else {
+ fprintf(stderr, "Can't do nfssvc() to add port\n");
+ }
+ exit(1);
+ }
+#endif
+
+ pwd = getpwnam(defaultuser);
+ if (pwd)
+ nid.nid_uid = pwd->pw_uid;
+ else
+ nid.nid_uid = defaultuid;
+ grp = getgrnam(defaultgroup);
+ if (grp)
+ nid.nid_gid = grp->gr_gid;
+ else
+ nid.nid_gid = defaultgid;
+ nid.nid_name = dnsname;
+ nid.nid_namelen = strlen(nid.nid_name);
+ nid.nid_ngroup = 0;
+ nid.nid_grps = NULL;
+ nid.nid_flag = NFSID_INITIALIZE;
+#ifdef DEBUG
+ printf("Initialize uid=%d gid=%d dns=%s\n", nid.nid_uid, nid.nid_gid,
+ nid.nid_name);
+#else
+ error = nfssvc(NFSSVC_IDNAME | NFSSVC_NEWSTRUCT, &nid);
+ if (error)
+ errx(1, "Can't initialize nfs user/groups");
+#endif
+
+ i = 0;
+ /*
+ * Loop around adding all groups.
+ */
+ setgrent();
+ while (i < nid.nid_usermax && (grp = getgrent())) {
+ nid.nid_gid = grp->gr_gid;
+ nid.nid_name = grp->gr_name;
+ nid.nid_namelen = strlen(grp->gr_name);
+ nid.nid_ngroup = 0;
+ nid.nid_grps = NULL;
+ nid.nid_flag = NFSID_ADDGID;
+#ifdef DEBUG
+ printf("add gid=%d name=%s\n", nid.nid_gid, nid.nid_name);
+#else
+ error = nfssvc(NFSSVC_IDNAME | NFSSVC_NEWSTRUCT, &nid);
+ if (error)
+ errx(1, "Can't add group %s", grp->gr_name);
+#endif
+ i++;
+ }
+
+ /*
+ * Loop around adding all users.
+ */
+ start_uidpos = i;
+ setpwent();
+ while (i < nid.nid_usermax && (pwd = getpwent())) {
+ fnd_dup = 0;
+ /*
+ * Yes, this is inefficient, but it is only done once when
+ * the daemon is started and will run in a fraction of a second
+ * for nid_usermax at 10000. If nid_usermax is cranked up to
+ * 100000, it will take several seconds, depending on the CPU.
+ */
+ for (j = 0; j < (i - start_uidpos); j++)
+ if (check_dups[j] == pwd->pw_uid) {
+ /* Found another entry for uid, so skip it */
+ fnd_dup = 1;
+ break;
+ }
+ if (fnd_dup != 0)
+ continue;
+ check_dups[i - start_uidpos] = pwd->pw_uid;
+ nid.nid_uid = pwd->pw_uid;
+ nid.nid_name = pwd->pw_name;
+ nid.nid_namelen = strlen(pwd->pw_name);
+ if (manage_gids != 0) {
+ /* Get the group list for this user. */
+ ngroup = NGROUPS;
+ if (getgrouplist(pwd->pw_name, pwd->pw_gid, grps,
+ &ngroup) < 0)
+ syslog(LOG_ERR, "Group list too small");
+ nid.nid_ngroup = ngroup;
+ nid.nid_grps = grps;
+ } else {
+ nid.nid_ngroup = 0;
+ nid.nid_grps = NULL;
+ }
+ nid.nid_flag = NFSID_ADDUID;
+#ifdef DEBUG
+ printf("add uid=%d name=%s\n", nid.nid_uid, nid.nid_name);
+#else
+ error = nfssvc(NFSSVC_IDNAME | NFSSVC_NEWSTRUCT, &nid);
+ if (error)
+ errx(1, "Can't add user %s", pwd->pw_name);
+#endif
+ i++;
+ }
+
+ /*
+ * I should feel guilty for not calling this for all the above exit()
+ * upon error cases, but I don't.
+ */
+ if (mustfreeai)
+ freeaddrinfo(aip);
+
+#ifdef DEBUG
+ exit(0);
+#endif
+ /*
+ * Temporarily block SIGUSR1 and SIGCHLD, so slaves[] can't
+ * end up bogus.
+ */
+ sigemptyset(&signew);
+ sigaddset(&signew, SIGUSR1);
+ sigaddset(&signew, SIGCHLD);
+ sigprocmask(SIG_BLOCK, &signew, NULL);
+
+ daemon(0, 0);
+ (void)signal(SIGHUP, SIG_IGN);
+ (void)signal(SIGINT, SIG_IGN);
+ (void)signal(SIGQUIT, SIG_IGN);
+ (void)signal(SIGTERM, SIG_IGN);
+ (void)signal(SIGUSR1, cleanup_term);
+ (void)signal(SIGCHLD, cleanup_term);
+
+ openlog("nfsuserd:", LOG_PID, LOG_DAEMON);
+
+ /*
+ * Fork off the slave daemons that do the work. All the master
+ * does is kill them off and cleanup.
+ */
+ for (i = 0; i < nfsuserdcnt; i++) {
+ slaves[i] = fork();
+ if (slaves[i] == 0) {
+ im_a_slave = 1;
+ setproctitle("slave");
+ sigemptyset(&signew);
+ sigaddset(&signew, SIGUSR1);
+ sigprocmask(SIG_UNBLOCK, &signew, NULL);
+
+ /*
+ * and away we go.
+ */
+ svc_run();
+ syslog(LOG_ERR, "nfsuserd died: %m");
+ exit(1);
+ } else if (slaves[i] < 0) {
+ syslog(LOG_ERR, "fork: %m");
+ }
+ }
+
+ /*
+ * Just wait for SIGUSR1 or a child to die and then...
+ * As the Governor of California would say, "Terminate them".
+ */
+ setproctitle("master");
+ sigemptyset(&signew);
+ while (1)
+ sigsuspend(&signew);
+}
+
+/*
+ * The nfsuserd rpc service
+ */
+static void
+nfsuserdsrv(struct svc_req *rqstp, SVCXPRT *transp)
+{
+ struct passwd *pwd;
+ struct group *grp;
+ int error;
+ u_short sport;
+ struct info info;
+ struct nfsd_idargs nid;
+ u_int32_t saddr;
+ gid_t grps[NGROUPS];
+ int ngroup;
+
+ /*
+ * Only handle requests from 127.0.0.1 on a reserved port number.
+ * (Since a reserved port # at localhost implies a client with
+ * local root, there won't be a security breach. This is about
+ * the only case I can think of where a reserved port # means
+ * something.)
+ */
+ sport = ntohs(transp->xp_raddr.sin_port);
+ saddr = ntohl(transp->xp_raddr.sin_addr.s_addr);
+ if ((rqstp->rq_proc != NULLPROC && sport >= IPPORT_RESERVED) ||
+ saddr != 0x7f000001) {
+ syslog(LOG_ERR, "req from ip=0x%x port=%d\n", saddr, sport);
+ svcerr_weakauth(transp);
+ return;
+ }
+ switch (rqstp->rq_proc) {
+ case NULLPROC:
+ if (!svc_sendreply(transp, (xdrproc_t)xdr_void, NULL))
+ syslog(LOG_ERR, "Can't send reply");
+ return;
+ case RPCNFSUSERD_GETUID:
+ if (!svc_getargs(transp, (xdrproc_t)xdr_getid,
+ (caddr_t)&info)) {
+ svcerr_decode(transp);
+ return;
+ }
+ pwd = getpwuid((uid_t)info.id);
+ info.retval = 0;
+ if (pwd != NULL) {
+ nid.nid_usertimeout = defusertimeout;
+ nid.nid_uid = pwd->pw_uid;
+ nid.nid_name = pwd->pw_name;
+ if (manage_gids != 0) {
+ /* Get the group list for this user. */
+ ngroup = NGROUPS;
+ if (getgrouplist(pwd->pw_name, pwd->pw_gid,
+ grps, &ngroup) < 0)
+ syslog(LOG_ERR, "Group list too small");
+ nid.nid_ngroup = ngroup;
+ nid.nid_grps = grps;
+ } else {
+ nid.nid_ngroup = 0;
+ nid.nid_grps = NULL;
+ }
+ } else {
+ nid.nid_usertimeout = 5;
+ nid.nid_uid = (uid_t)info.id;
+ nid.nid_name = defaultuser;
+ nid.nid_ngroup = 0;
+ nid.nid_grps = NULL;
+ }
+ nid.nid_namelen = strlen(nid.nid_name);
+ nid.nid_flag = NFSID_ADDUID;
+ error = nfssvc(NFSSVC_IDNAME | NFSSVC_NEWSTRUCT, &nid);
+ if (error) {
+ info.retval = error;
+ syslog(LOG_ERR, "Can't add user %s\n", pwd->pw_name);
+ } else if (verbose) {
+ syslog(LOG_ERR,"Added uid=%d name=%s\n",
+ nid.nid_uid, nid.nid_name);
+ }
+ if (!svc_sendreply(transp, (xdrproc_t)xdr_retval,
+ (caddr_t)&info))
+ syslog(LOG_ERR, "Can't send reply");
+ return;
+ case RPCNFSUSERD_GETGID:
+ if (!svc_getargs(transp, (xdrproc_t)xdr_getid,
+ (caddr_t)&info)) {
+ svcerr_decode(transp);
+ return;
+ }
+ grp = getgrgid((gid_t)info.id);
+ info.retval = 0;
+ if (grp != NULL) {
+ nid.nid_usertimeout = defusertimeout;
+ nid.nid_gid = grp->gr_gid;
+ nid.nid_name = grp->gr_name;
+ } else {
+ nid.nid_usertimeout = 5;
+ nid.nid_gid = (gid_t)info.id;
+ nid.nid_name = defaultgroup;
+ }
+ nid.nid_namelen = strlen(nid.nid_name);
+ nid.nid_ngroup = 0;
+ nid.nid_grps = NULL;
+ nid.nid_flag = NFSID_ADDGID;
+ error = nfssvc(NFSSVC_IDNAME | NFSSVC_NEWSTRUCT, &nid);
+ if (error) {
+ info.retval = error;
+ syslog(LOG_ERR, "Can't add group %s\n",
+ grp->gr_name);
+ } else if (verbose) {
+ syslog(LOG_ERR,"Added gid=%d name=%s\n",
+ nid.nid_gid, nid.nid_name);
+ }
+ if (!svc_sendreply(transp, (xdrproc_t)xdr_retval,
+ (caddr_t)&info))
+ syslog(LOG_ERR, "Can't send reply");
+ return;
+ case RPCNFSUSERD_GETUSER:
+ if (!svc_getargs(transp, (xdrproc_t)xdr_getname,
+ (caddr_t)&info)) {
+ svcerr_decode(transp);
+ return;
+ }
+ pwd = getpwnam(info.name);
+ info.retval = 0;
+ if (pwd != NULL) {
+ nid.nid_usertimeout = defusertimeout;
+ nid.nid_uid = pwd->pw_uid;
+ nid.nid_name = pwd->pw_name;
+ } else {
+ nid.nid_usertimeout = 5;
+ nid.nid_uid = defaultuid;
+ nid.nid_name = info.name;
+ }
+ nid.nid_namelen = strlen(nid.nid_name);
+ nid.nid_ngroup = 0;
+ nid.nid_grps = NULL;
+ nid.nid_flag = NFSID_ADDUSERNAME;
+ error = nfssvc(NFSSVC_IDNAME | NFSSVC_NEWSTRUCT, &nid);
+ if (error) {
+ info.retval = error;
+ syslog(LOG_ERR, "Can't add user %s\n", pwd->pw_name);
+ } else if (verbose) {
+ syslog(LOG_ERR,"Added uid=%d name=%s\n",
+ nid.nid_uid, nid.nid_name);
+ }
+ if (!svc_sendreply(transp, (xdrproc_t)xdr_retval,
+ (caddr_t)&info))
+ syslog(LOG_ERR, "Can't send reply");
+ return;
+ case RPCNFSUSERD_GETGROUP:
+ if (!svc_getargs(transp, (xdrproc_t)xdr_getname,
+ (caddr_t)&info)) {
+ svcerr_decode(transp);
+ return;
+ }
+ grp = getgrnam(info.name);
+ info.retval = 0;
+ if (grp != NULL) {
+ nid.nid_usertimeout = defusertimeout;
+ nid.nid_gid = grp->gr_gid;
+ nid.nid_name = grp->gr_name;
+ } else {
+ nid.nid_usertimeout = 5;
+ nid.nid_gid = defaultgid;
+ nid.nid_name = info.name;
+ }
+ nid.nid_namelen = strlen(nid.nid_name);
+ nid.nid_ngroup = 0;
+ nid.nid_grps = NULL;
+ nid.nid_flag = NFSID_ADDGROUPNAME;
+ error = nfssvc(NFSSVC_IDNAME | NFSSVC_NEWSTRUCT, &nid);
+ if (error) {
+ info.retval = error;
+ syslog(LOG_ERR, "Can't add group %s\n",
+ grp->gr_name);
+ } else if (verbose) {
+ syslog(LOG_ERR,"Added gid=%d name=%s\n",
+ nid.nid_gid, nid.nid_name);
+ }
+ if (!svc_sendreply(transp, (xdrproc_t)xdr_retval,
+ (caddr_t)&info))
+ syslog(LOG_ERR, "Can't send reply");
+ return;
+ default:
+ svcerr_noproc(transp);
+ return;
+ };
+}
+
+/*
+ * Xdr routine to get an id number
+ */
+static bool_t
+xdr_getid(XDR *xdrsp, caddr_t cp)
+{
+ struct info *ifp = (struct info *)cp;
+
+ return (xdr_long(xdrsp, &ifp->id));
+}
+
+/*
+ * Xdr routine to get a user name
+ */
+static bool_t
+xdr_getname(XDR *xdrsp, caddr_t cp)
+{
+ struct info *ifp = (struct info *)cp;
+ long len;
+
+ if (!xdr_long(xdrsp, &len))
+ return (0);
+ if (len > MAXNAME)
+ return (0);
+ if (!xdr_opaque(xdrsp, ifp->name, len))
+ return (0);
+ ifp->name[len] = '\0';
+ return (1);
+}
+
+/*
+ * Xdr routine to return the value.
+ */
+static bool_t
+xdr_retval(XDR *xdrsp, caddr_t cp)
+{
+ struct info *ifp = (struct info *)cp;
+ long val;
+
+ val = ifp->retval;
+ return (xdr_long(xdrsp, &val));
+}
+
+/*
+ * cleanup_term() called via SIGUSR1.
+ */
+static void
+cleanup_term(int signo __unused)
+{
+ int i, cnt;
+
+ if (im_a_slave)
+ exit(0);
+
+ /*
+ * Ok, so I'm the master.
+ * As the Governor of California might say, "Terminate them".
+ */
+ cnt = 0;
+ for (i = 0; i < nfsuserdcnt; i++) {
+ if (slaves[i] != (pid_t)-1) {
+ cnt++;
+ kill(slaves[i], SIGUSR1);
+ }
+ }
+
+ /*
+ * and wait for them to die
+ */
+ for (i = 0; i < cnt; i++)
+ wait3(NULL, 0, NULL);
+
+ /*
+ * Finally, get rid of the socket
+ */
+ if (nfssvc(NFSSVC_NFSUSERDDELPORT, NULL) < 0) {
+ syslog(LOG_ERR, "Can't do nfssvc() to delete the port\n");
+ exit(1);
+ }
+ exit(0);
+}
+
+static void
+usage(void)
+{
+
+ errx(1,
+ "usage: nfsuserd [-usermax cache_size] [-usertimeout minutes] [-verbose] [-manage-gids] [-domain domain_name] [n]");
+}
diff --git a/usr.sbin/ngctl/Makefile b/usr.sbin/ngctl/Makefile
new file mode 100644
index 0000000..71b5d2b
--- /dev/null
+++ b/usr.sbin/ngctl/Makefile
@@ -0,0 +1,27 @@
+# $FreeBSD$
+# $Whistle: Makefile,v 1.3 1999/01/16 00:10:11 archie Exp $
+
+.include <src.opts.mk>
+
+PROG= ngctl
+MAN= ngctl.8
+SRCS= main.c mkpeer.c config.c connect.c dot.c name.c show.c list.c \
+ msg.c debug.c shutdown.c rmhook.c status.c types.c write.c
+WARNS?= 3
+
+.if defined(RELEASE_CRUNCH)
+NGCTL_NO_LIBEDIT=
+.endif
+
+.if ${MK_LIBTHR} == "no"
+NGCTL_NO_LIBEDIT=
+.endif
+
+LIBADD= netgraph
+
+.if !defined(NGCTL_NO_LIBEDIT)
+CFLAGS+= -DEDITLINE
+LIBADD+= edit pthread
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ngctl/Makefile.depend b/usr.sbin/ngctl/Makefile.depend
new file mode 100644
index 0000000..a0a9832
--- /dev/null
+++ b/usr.sbin/ngctl/Makefile.depend
@@ -0,0 +1,22 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libedit \
+ lib/libnetgraph \
+ lib/libthr \
+ lib/ncurses/ncursesw \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ngctl/config.c b/usr.sbin/ngctl/config.c
new file mode 100644
index 0000000..9b1a275
--- /dev/null
+++ b/usr.sbin/ngctl/config.c
@@ -0,0 +1,111 @@
+/*
+ * config.c
+ *
+ * Copyright (c) 1996-1999 Whistle Communications, Inc.
+ * All rights reserved.
+ *
+ * Subject to the following obligations and disclaimer of warranty, use and
+ * redistribution of this software, in source or object code forms, with or
+ * without modifications are expressly permitted by Whistle Communications;
+ * provided, however, that:
+ * 1. Any and all reproductions of the source or object code must include the
+ * copyright notice above and the following disclaimer of warranties; and
+ * 2. No rights are granted, in any manner or form, to use Whistle
+ * Communications, Inc. trademarks, including the mark "WHISTLE
+ * COMMUNICATIONS" on advertising, endorsements, or otherwise except as
+ * such appears in the above copyright notice or in the software.
+ *
+ * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
+ * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
+ * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
+ * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
+ * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
+ * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
+ * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
+ * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
+ * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
+ * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <err.h>
+#include <errno.h>
+#include <netgraph.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ngctl.h"
+
+#define NOCONFIG "<no config>"
+
+static int ConfigCmd(int ac, char **av);
+
+const struct ngcmd config_cmd = {
+ ConfigCmd,
+ "config <path> [arguments]",
+ "get or set configuration of node at <path>",
+ NULL,
+ { NULL }
+};
+
+static int
+ConfigCmd(int ac, char **av)
+{
+ u_char sbuf[sizeof(struct ng_mesg) + NG_TEXTRESPONSE];
+ struct ng_mesg *const resp = (struct ng_mesg *) sbuf;
+ char *const status = (char *) resp->data;
+ char *path;
+ char buf[NG_TEXTRESPONSE];
+ int nostat = 0, i;
+
+ /* Get arguments */
+ if (ac < 2)
+ return (CMDRTN_USAGE);
+ path = av[1];
+
+ *buf = '\0';
+ for (i = 2; i < ac; i++) {
+ if (i != 2)
+ strcat(buf, " ");
+ strcat(buf, av[i]);
+ }
+
+ /* Get node config summary */
+ if (*buf != '\0')
+ i = NgSendMsg(csock, path, NGM_GENERIC_COOKIE,
+ NGM_TEXT_CONFIG, buf, strlen(buf) + 1);
+ else
+ i = NgSendMsg(csock, path, NGM_GENERIC_COOKIE,
+ NGM_TEXT_CONFIG, NULL, 0);
+ if (i < 0) {
+ switch (errno) {
+ case EINVAL:
+ nostat = 1;
+ break;
+ default:
+ warn("send msg");
+ return (CMDRTN_ERROR);
+ }
+ } else {
+ if (NgRecvMsg(csock, resp, sizeof(sbuf), NULL) < 0
+ || (resp->header.flags & NGF_RESP) == 0)
+ nostat = 1;
+ }
+
+ /* Show it */
+ if (nostat)
+ printf("No config available for \"%s\"\n", path);
+ else
+ printf("Config for \"%s\":\n%s\n", path, status);
+ return (CMDRTN_OK);
+}
+
diff --git a/usr.sbin/ngctl/connect.c b/usr.sbin/ngctl/connect.c
new file mode 100644
index 0000000..cd5307e
--- /dev/null
+++ b/usr.sbin/ngctl/connect.c
@@ -0,0 +1,90 @@
+
+/*
+ * connect.c
+ *
+ * Copyright (c) 1996-1999 Whistle Communications, Inc.
+ * All rights reserved.
+ *
+ * Subject to the following obligations and disclaimer of warranty, use and
+ * redistribution of this software, in source or object code forms, with or
+ * without modifications are expressly permitted by Whistle Communications;
+ * provided, however, that:
+ * 1. Any and all reproductions of the source or object code must include the
+ * copyright notice above and the following disclaimer of warranties; and
+ * 2. No rights are granted, in any manner or form, to use Whistle
+ * Communications, Inc. trademarks, including the mark "WHISTLE
+ * COMMUNICATIONS" on advertising, endorsements, or otherwise except as
+ * such appears in the above copyright notice or in the software.
+ *
+ * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
+ * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
+ * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
+ * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
+ * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
+ * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
+ * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
+ * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
+ * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
+ * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <err.h>
+#include <netgraph.h>
+#include <stdio.h>
+
+#include "ngctl.h"
+
+static int ConnectCmd(int ac, char **av);
+
+const struct ngcmd connect_cmd = {
+ ConnectCmd,
+ "connect [path] <relpath> <hook> <peerhook>",
+ "Connects hook <peerhook> of the node at <relpath> to <hook>",
+ "The connect command creates a link between the two nodes at"
+ " \"path\" and \"relpath\" using hooks \"hook\" and \"peerhook\","
+ " respectively. The \"relpath\", if not absolute, is specified"
+ " relative to the node at \"path\"."
+ " If \"path\" is omitted then \".\" is assumed.",
+ { "join" }
+};
+
+static int
+ConnectCmd(int ac, char **av)
+{
+ struct ngm_connect con;
+ const char *path = ".";
+
+ /* Get arguments */
+ switch (ac) {
+ case 5:
+ path = av[1];
+ ac--;
+ av++;
+ /* FALLTHROUGH */
+ case 4:
+ snprintf(con.path, sizeof(con.path), "%s", av[1]);
+ snprintf(con.ourhook, sizeof(con.ourhook), "%s", av[2]);
+ snprintf(con.peerhook, sizeof(con.peerhook), "%s", av[3]);
+ break;
+ default:
+ return (CMDRTN_USAGE);
+ }
+
+ /* Send message */
+ if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE,
+ NGM_CONNECT, &con, sizeof(con)) < 0) {
+ warn("send msg");
+ return (CMDRTN_ERROR);
+ }
+ return (CMDRTN_OK);
+}
+
diff --git a/usr.sbin/ngctl/debug.c b/usr.sbin/ngctl/debug.c
new file mode 100644
index 0000000..d64e32d
--- /dev/null
+++ b/usr.sbin/ngctl/debug.c
@@ -0,0 +1,84 @@
+
+/*
+ * debug.c
+ *
+ * Copyright (c) 1996-1999 Whistle Communications, Inc.
+ * All rights reserved.
+ *
+ * Subject to the following obligations and disclaimer of warranty, use and
+ * redistribution of this software, in source or object code forms, with or
+ * without modifications are expressly permitted by Whistle Communications;
+ * provided, however, that:
+ * 1. Any and all reproductions of the source or object code must include the
+ * copyright notice above and the following disclaimer of warranties; and
+ * 2. No rights are granted, in any manner or form, to use Whistle
+ * Communications, Inc. trademarks, including the mark "WHISTLE
+ * COMMUNICATIONS" on advertising, endorsements, or otherwise except as
+ * such appears in the above copyright notice or in the software.
+ *
+ * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
+ * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
+ * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
+ * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
+ * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
+ * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
+ * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
+ * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
+ * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
+ * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <netgraph.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ngctl.h"
+
+static int DebugCmd(int ac, char **av);
+
+const struct ngcmd debug_cmd = {
+ DebugCmd,
+ "debug [level]",
+ "Get/set debugging verbosity level",
+ "Without any argument, this command displays the current"
+ " debugging verbosity level. If the argument is ``+'' or ``-''"
+ " the debug level is incremented or decremented; otherwise,"
+ " it must be an absolute numerical level.",
+ { NULL }
+};
+
+static int
+DebugCmd(int ac, char **av)
+{
+ int level;
+
+ /* Get arguments */
+ switch (ac) {
+ case 2:
+ if (!strcmp(av[1], "+"))
+ level = NgSetDebug(-1) + 1;
+ else if (!strcmp(av[1], "-"))
+ level = NgSetDebug(-1) - 1;
+ else if ((level = atoi(av[1])) < 0)
+ return (CMDRTN_USAGE);
+ NgSetDebug(level);
+ break;
+ case 1:
+ printf("Current debug level is %d\n", NgSetDebug(-1));
+ break;
+ default:
+ return (CMDRTN_USAGE);
+ }
+ return (CMDRTN_OK);
+}
+
diff --git a/usr.sbin/ngctl/dot.c b/usr.sbin/ngctl/dot.c
new file mode 100644
index 0000000..045101a
--- /dev/null
+++ b/usr.sbin/ngctl/dot.c
@@ -0,0 +1,200 @@
+
+/*
+ * dot.c
+ *
+ * Copyright (c) 2004 Brian Fundakowski Feldman
+ * Copyright (c) 1996-1999 Whistle Communications, Inc.
+ * All rights reserved.
+ *
+ * Subject to the following obligations and disclaimer of warranty, use and
+ * redistribution of this software, in source or object code forms, with or
+ * without modifications are expressly permitted by Whistle Communications;
+ * provided, however, that:
+ * 1. Any and all reproductions of the source or object code must include the
+ * copyright notice above and the following disclaimer of warranties; and
+ * 2. No rights are granted, in any manner or form, to use Whistle
+ * Communications, Inc. trademarks, including the mark "WHISTLE
+ * COMMUNICATIONS" on advertising, endorsements, or otherwise except as
+ * such appears in the above copyright notice or in the software.
+ *
+ * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
+ * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
+ * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
+ * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
+ * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
+ * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
+ * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
+ * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
+ * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
+ * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <err.h>
+#include <inttypes.h>
+#include <netgraph.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "ngctl.h"
+
+#define UNNAMED "\\<unnamed\\>"
+
+static int DotCmd(int ac, char **av);
+
+const struct ngcmd dot_cmd = {
+ DotCmd,
+ "dot [outputfile]",
+ "Produce a GraphViz (.dot) of the entire netgraph.",
+ "If no outputfile is specified, stdout will be assumed.",
+ { "graphviz", "confdot" }
+};
+
+static int
+DotCmd(int ac, char **av)
+{
+ struct ng_mesg *nlresp;
+ struct namelist *nlist;
+ FILE *f = stdout;
+ int ch;
+ u_int i;
+
+ /* Get options */
+ optind = 1;
+ while ((ch = getopt(ac, av, "")) != -1) {
+ switch (ch) {
+ case '?':
+ default:
+ return (CMDRTN_USAGE);
+ break;
+ }
+ }
+ ac -= optind;
+ av += optind;
+
+ /* Get arguments */
+ switch (ac) {
+ case 1:
+ f = fopen(av[0], "w");
+ if (f == NULL) {
+ warn("Could not open %s for writing", av[0]);
+ return (CMDRTN_ERROR);
+ }
+ case 0:
+ break;
+ default:
+ if (f != stdout)
+ (void)fclose(f);
+ return (CMDRTN_USAGE);
+ }
+
+ /* Get list of nodes */
+ if (NgSendMsg(csock, ".", NGM_GENERIC_COOKIE, NGM_LISTNODES, NULL,
+ 0) < 0) {
+ warn("send listnodes msg");
+ goto error;
+ }
+ if (NgAllocRecvMsg(csock, &nlresp, NULL) < 0) {
+ warn("recv listnodes msg");
+ goto error;
+ }
+
+ nlist = (struct namelist *)nlresp->data;
+ fprintf(f, "graph netgraph {\n");
+ /* TODO: implement rank = same or subgraphs at some point */
+ fprintf(f, "\tedge [ weight = 1.0 ];\n");
+ fprintf(f, "\tnode [ shape = record, fontsize = 12 ] {\n");
+ for (i = 0; i < nlist->numnames; i++)
+ fprintf(f, "\t\t\"%jx\" [ label = \"{%s:|{%s|[%jx]:}}\" ];\n",
+ (uintmax_t)nlist->nodeinfo[i].id,
+ nlist->nodeinfo[i].name[0] != '\0' ?
+ nlist->nodeinfo[i].name : UNNAMED,
+ nlist->nodeinfo[i].type, (uintmax_t)nlist->nodeinfo[i].id);
+ fprintf(f, "\t};\n");
+
+ fprintf(f, "\tsubgraph cluster_disconnected {\n");
+ fprintf(f, "\t\tbgcolor = pink;\n");
+ for (i = 0; i < nlist->numnames; i++)
+ if (nlist->nodeinfo[i].hooks == 0)
+ fprintf(f, "\t\t\"%jx\";\n",
+ (uintmax_t)nlist->nodeinfo[i].id);
+ fprintf(f, "\t};\n");
+
+ for (i = 0; i < nlist->numnames; i++) {
+ struct ng_mesg *hlresp;
+ struct hooklist *hlist;
+ struct nodeinfo *ninfo;
+ char path[NG_PATHSIZ];
+ u_int j;
+
+ (void)snprintf(path, sizeof(path), "[%jx]:",
+ (uintmax_t)nlist->nodeinfo[i].id);
+
+ /* Get node info and hook list */
+ if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE, NGM_LISTHOOKS,
+ NULL, 0) < 0) {
+ free(nlresp);
+ warn("send listhooks msg");
+ goto error;
+ }
+ if (NgAllocRecvMsg(csock, &hlresp, NULL) < 0) {
+ free(nlresp);
+ warn("recv listhooks msg");
+ goto error;
+ }
+
+ hlist = (struct hooklist *)hlresp->data;
+ ninfo = &hlist->nodeinfo;
+ if (ninfo->hooks == 0) {
+ free(hlresp);
+ continue;
+ }
+
+ fprintf(f, "\tnode [ shape = octagon, fontsize = 10 ] {\n");
+ for (j = 0; j < ninfo->hooks; j++)
+ fprintf(f, "\t\t\"%jx.%s\" [ label = \"%s\" ];\n",
+ (uintmax_t)nlist->nodeinfo[i].id,
+ hlist->link[j].ourhook, hlist->link[j].ourhook);
+ fprintf(f, "\t};\n");
+
+ fprintf(f, "\t{\n\t\tedge [ weight = 2.0, style = bold ];\n");
+ for (j = 0; j < ninfo->hooks; j++)
+ fprintf(f, "\t\t\"%jx\" -- \"%jx.%s\";\n",
+ (uintmax_t)nlist->nodeinfo[i].id,
+ (uintmax_t)nlist->nodeinfo[i].id,
+ hlist->link[j].ourhook);
+ fprintf(f, "\t};\n");
+
+ for (j = 0; j < ninfo->hooks; j++) {
+ /* Only print the edges going in one direction. */
+ if (hlist->link[j].nodeinfo.id > nlist->nodeinfo[i].id)
+ continue;
+ fprintf(f, "\t\"%jx.%s\" -- \"%jx.%s\";\n",
+ (uintmax_t)nlist->nodeinfo[i].id,
+ hlist->link[j].ourhook,
+ (uintmax_t)hlist->link[j].nodeinfo.id,
+ hlist->link[j].peerhook);
+ }
+ free(hlresp);
+ }
+
+ fprintf(f, "}\n");
+
+ free(nlresp);
+ if (f != stdout)
+ (void)fclose(f);
+ return (CMDRTN_OK);
+error:
+ if (f != stdout)
+ (void)fclose(f);
+ return (CMDRTN_ERROR);
+}
diff --git a/usr.sbin/ngctl/list.c b/usr.sbin/ngctl/list.c
new file mode 100644
index 0000000..da4174c
--- /dev/null
+++ b/usr.sbin/ngctl/list.c
@@ -0,0 +1,146 @@
+
+/*
+ * list.c
+ *
+ * Copyright (c) 1996-1999 Whistle Communications, Inc.
+ * All rights reserved.
+ *
+ * Subject to the following obligations and disclaimer of warranty, use and
+ * redistribution of this software, in source or object code forms, with or
+ * without modifications are expressly permitted by Whistle Communications;
+ * provided, however, that:
+ * 1. Any and all reproductions of the source or object code must include the
+ * copyright notice above and the following disclaimer of warranties; and
+ * 2. No rights are granted, in any manner or form, to use Whistle
+ * Communications, Inc. trademarks, including the mark "WHISTLE
+ * COMMUNICATIONS" on advertising, endorsements, or otherwise except as
+ * such appears in the above copyright notice or in the software.
+ *
+ * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
+ * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
+ * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
+ * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
+ * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
+ * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
+ * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
+ * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
+ * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
+ * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <err.h>
+#include <netgraph.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "ngctl.h"
+
+#define UNNAMED "<unnamed>"
+
+static int ListCmd(int ac, char **av);
+
+const struct ngcmd list_cmd = {
+ ListCmd,
+ "list [-ln]",
+ "Show information about all nodes",
+ "The list command shows information about every node that currently"
+ " exists in the netgraph system. The optional -n argument limits"
+ " this list to only those nodes with a global name assignment."
+ " The optional -l argument provides verbose output that includes"
+ " hook information as well.",
+ { "ls" }
+};
+
+static int
+ListCmd(int ac, char **av)
+{
+ struct ng_mesg *resp;
+ struct namelist *nlist;
+ struct nodeinfo *ninfo;
+ int list_hooks = 0;
+ int named_only = 0;
+ int ch, rtn = CMDRTN_OK;
+
+ /* Get options */
+ optind = 1;
+ while ((ch = getopt(ac, av, "ln")) != -1) {
+ switch (ch) {
+ case 'l':
+ list_hooks = 1;
+ break;
+ case 'n':
+ named_only = 1;
+ break;
+ case '?':
+ default:
+ return (CMDRTN_USAGE);
+ break;
+ }
+ }
+ ac -= optind;
+ av += optind;
+
+ /* Get arguments */
+ switch (ac) {
+ case 0:
+ break;
+ default:
+ return (CMDRTN_USAGE);
+ }
+
+ /* Get list of nodes */
+ if (NgSendMsg(csock, ".", NGM_GENERIC_COOKIE,
+ named_only ? NGM_LISTNAMES : NGM_LISTNODES, NULL, 0) < 0) {
+ warn("send msg");
+ return (CMDRTN_ERROR);
+ }
+ if (NgAllocRecvMsg(csock, &resp, NULL) < 0) {
+ warn("recv msg");
+ return (CMDRTN_ERROR);
+ }
+
+ /* Show each node */
+ nlist = (struct namelist *) resp->data;
+ printf("There are %d total %snodes:\n",
+ nlist->numnames, named_only ? "named " : "");
+ ninfo = nlist->nodeinfo;
+ if (list_hooks) {
+ char path[NG_PATHSIZ];
+ char *argv[2] = { "show", path };
+
+ while (nlist->numnames > 0) {
+ snprintf(path, sizeof(path),
+ "[%lx]:", (u_long)ninfo->id);
+ if ((rtn = (*show_cmd.func)(2, argv)) != CMDRTN_OK)
+ break;
+ ninfo++;
+ nlist->numnames--;
+ }
+ } else {
+ while (nlist->numnames > 0) {
+ if (!*ninfo->name)
+ snprintf(ninfo->name, sizeof(ninfo->name),
+ "%s", UNNAMED);
+ printf(" Name: %-15s Type: %-15s ID: %08x "
+ "Num hooks: %d\n",
+ ninfo->name, ninfo->type, ninfo->id, ninfo->hooks);
+ ninfo++;
+ nlist->numnames--;
+ }
+ }
+
+ /* Done */
+ free(resp);
+ return (rtn);
+}
+
diff --git a/usr.sbin/ngctl/main.c b/usr.sbin/ngctl/main.c
new file mode 100644
index 0000000..4b1cdab
--- /dev/null
+++ b/usr.sbin/ngctl/main.c
@@ -0,0 +1,665 @@
+
+/*
+ * main.c
+ *
+ * Copyright (c) 1996-1999 Whistle Communications, Inc.
+ * All rights reserved.
+ *
+ * Subject to the following obligations and disclaimer of warranty, use and
+ * redistribution of this software, in source or object code forms, with or
+ * without modifications are expressly permitted by Whistle Communications;
+ * provided, however, that:
+ * 1. Any and all reproductions of the source or object code must include the
+ * copyright notice above and the following disclaimer of warranties; and
+ * 2. No rights are granted, in any manner or form, to use Whistle
+ * Communications, Inc. trademarks, including the mark "WHISTLE
+ * COMMUNICATIONS" on advertising, endorsements, or otherwise except as
+ * such appears in the above copyright notice or in the software.
+ *
+ * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
+ * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
+ * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
+ * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
+ * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
+ * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
+ * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
+ * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
+ * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
+ * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * $Whistle: main.c,v 1.12 1999/11/29 19:17:46 archie Exp $
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#ifdef EDITLINE
+#include <signal.h>
+#include <histedit.h>
+#include <pthread.h>
+#endif
+
+#include <netgraph.h>
+
+#include "ngctl.h"
+
+#define PROMPT "+ "
+#define MAX_ARGS 512
+#define WHITESPACE " \t\r\n\v\f"
+#define DUMP_BYTES_PER_LINE 16
+
+/* Internal functions */
+static int ReadFile(FILE *fp);
+static void ReadSockets(fd_set *);
+static int DoParseCommand(const char *line);
+static int DoCommand(int ac, char **av);
+static int DoInteractive(void);
+static const struct ngcmd *FindCommand(const char *string);
+static int MatchCommand(const struct ngcmd *cmd, const char *s);
+static void Usage(const char *msg);
+static int ReadCmd(int ac, char **av);
+static int HelpCmd(int ac, char **av);
+static int QuitCmd(int ac, char **av);
+#ifdef EDITLINE
+static volatile sig_atomic_t unblock;
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+#endif
+
+/* List of commands */
+static const struct ngcmd *const cmds[] = {
+ &config_cmd,
+ &connect_cmd,
+ &debug_cmd,
+ &dot_cmd,
+ &help_cmd,
+ &list_cmd,
+ &mkpeer_cmd,
+ &msg_cmd,
+ &name_cmd,
+ &read_cmd,
+ &rmhook_cmd,
+ &show_cmd,
+ &shutdown_cmd,
+ &status_cmd,
+ &types_cmd,
+ &write_cmd,
+ &quit_cmd,
+ NULL
+};
+
+/* Commands defined in this file */
+const struct ngcmd read_cmd = {
+ ReadCmd,
+ "read <filename>",
+ "Read and execute commands from a file",
+ NULL,
+ { "source", "." }
+};
+const struct ngcmd help_cmd = {
+ HelpCmd,
+ "help [command]",
+ "Show command summary or get more help on a specific command",
+ NULL,
+ { "?" }
+};
+const struct ngcmd quit_cmd = {
+ QuitCmd,
+ "quit",
+ "Exit program",
+ NULL,
+ { "exit" }
+};
+
+/* Our control and data sockets */
+int csock, dsock;
+
+/*
+ * main()
+ */
+int
+main(int ac, char *av[])
+{
+ char name[NG_NODESIZ];
+ int interactive = isatty(0) && isatty(1);
+ FILE *fp = NULL;
+ int ch, rtn = 0;
+
+ /* Set default node name */
+ snprintf(name, sizeof(name), "ngctl%d", getpid());
+
+ /* Parse command line */
+ while ((ch = getopt(ac, av, "df:n:")) != -1) {
+ switch (ch) {
+ case 'd':
+ NgSetDebug(NgSetDebug(-1) + 1);
+ break;
+ case 'f':
+ if (strcmp(optarg, "-") == 0)
+ fp = stdin;
+ else if ((fp = fopen(optarg, "r")) == NULL)
+ err(EX_NOINPUT, "%s", optarg);
+ break;
+ case 'n':
+ snprintf(name, sizeof(name), "%s", optarg);
+ break;
+ case '?':
+ default:
+ Usage((char *)NULL);
+ break;
+ }
+ }
+ ac -= optind;
+ av += optind;
+
+ /* Create a new socket node */
+ if (NgMkSockNode(name, &csock, &dsock) < 0)
+ err(EX_OSERR, "can't create node");
+
+ /* Do commands as requested */
+ if (ac == 0) {
+ if (fp != NULL) {
+ rtn = ReadFile(fp);
+ } else if (interactive) {
+ rtn = DoInteractive();
+ } else
+ Usage("no command specified");
+ } else {
+ rtn = DoCommand(ac, av);
+ }
+
+ /* Convert command return code into system exit code */
+ switch (rtn) {
+ case CMDRTN_OK:
+ case CMDRTN_QUIT:
+ rtn = 0;
+ break;
+ case CMDRTN_USAGE:
+ rtn = EX_USAGE;
+ break;
+ case CMDRTN_ERROR:
+ rtn = EX_OSERR;
+ break;
+ }
+ return (rtn);
+}
+
+/*
+ * Process commands from a file
+ */
+static int
+ReadFile(FILE *fp)
+{
+ char line[LINE_MAX];
+ int num, rtn;
+
+ for (num = 1; fgets(line, sizeof(line), fp) != NULL; num++) {
+ if (*line == '#')
+ continue;
+ if ((rtn = DoParseCommand(line)) != 0) {
+ warnx("line %d: error in file", num);
+ return (rtn);
+ }
+ }
+ return (CMDRTN_OK);
+}
+
+#ifdef EDITLINE
+/* Signal handler for Monitor() thread. */
+static void
+Unblock(int signal __unused)
+{
+
+ unblock = 1;
+}
+
+/*
+ * Thread that monitors csock and dsock while main thread
+ * can be blocked in el_gets().
+ */
+static void *
+Monitor(void *v __unused)
+{
+ struct sigaction act;
+ const int maxfd = MAX(csock, dsock) + 1;
+
+ act.sa_handler = Unblock;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ sigaction(SIGUSR1, &act, NULL);
+
+ pthread_mutex_lock(&mutex);
+ for (;;) {
+ fd_set rfds;
+
+ /* See if any data or control messages are arriving. */
+ FD_ZERO(&rfds);
+ FD_SET(csock, &rfds);
+ FD_SET(dsock, &rfds);
+ unblock = 0;
+ if (select(maxfd, &rfds, NULL, NULL, NULL) <= 0) {
+ if (errno == EINTR) {
+ if (unblock == 1)
+ pthread_cond_wait(&cond, &mutex);
+ continue;
+ }
+ err(EX_OSERR, "select");
+ }
+ ReadSockets(&rfds);
+ }
+
+ return (NULL);
+}
+
+static char *
+Prompt(EditLine *el __unused)
+{
+
+ return (PROMPT);
+}
+
+/*
+ * Here we start a thread, that will monitor the netgraph
+ * sockets and catch any unexpected messages or data on them,
+ * that can arrive while user edits his/her commands.
+ *
+ * Whenever we expect data on netgraph sockets, we send signal
+ * to monitoring thread. The signal forces it to exit select()
+ * system call and sleep on condvar until we wake it. While
+ * monitoring thread sleeps, we can do our work with netgraph
+ * sockets.
+ */
+static int
+DoInteractive(void)
+{
+ pthread_t monitor;
+ EditLine *el;
+ History *hist;
+ HistEvent hev = { 0, "" };
+
+ (*help_cmd.func)(0, NULL);
+ pthread_create(&monitor, NULL, Monitor, NULL);
+ el = el_init(getprogname(), stdin, stdout, stderr);
+ if (el == NULL)
+ return (CMDRTN_ERROR);
+ el_set(el, EL_PROMPT, Prompt);
+ el_set(el, EL_SIGNAL, 1);
+ el_set(el, EL_EDITOR, "emacs");
+ hist = history_init();
+ if (hist == NULL)
+ return (CMDRTN_ERROR);
+ history(hist, &hev, H_SETSIZE, 100);
+ history(hist, &hev, H_SETUNIQUE, 1);
+ el_set(el, EL_HIST, history, (const char *)hist);
+ el_source(el, NULL);
+
+ for (;;) {
+ const char *buf;
+ int count;
+
+ if ((buf = el_gets(el, &count)) == NULL) {
+ printf("\n");
+ break;
+ }
+ history(hist, &hev, H_ENTER, buf);
+ pthread_kill(monitor, SIGUSR1);
+ pthread_mutex_lock(&mutex);
+ if (DoParseCommand(buf) == CMDRTN_QUIT) {
+ pthread_mutex_unlock(&mutex);
+ break;
+ }
+ pthread_cond_signal(&cond);
+ pthread_mutex_unlock(&mutex);
+ }
+
+ history_end(hist);
+ el_end(el);
+ pthread_cancel(monitor);
+
+ return (CMDRTN_QUIT);
+}
+
+#else /* !EDITLINE */
+
+/*
+ * Interactive mode w/o libedit functionality.
+ */
+static int
+DoInteractive(void)
+{
+ const int maxfd = MAX(csock, dsock) + 1;
+
+ (*help_cmd.func)(0, NULL);
+ while (1) {
+ struct timeval tv;
+ fd_set rfds;
+
+ /* See if any data or control messages are arriving */
+ FD_ZERO(&rfds);
+ FD_SET(csock, &rfds);
+ FD_SET(dsock, &rfds);
+ memset(&tv, 0, sizeof(tv));
+ if (select(maxfd, &rfds, NULL, NULL, &tv) <= 0) {
+
+ /* Issue prompt and wait for anything to happen */
+ printf("%s", PROMPT);
+ fflush(stdout);
+ FD_ZERO(&rfds);
+ FD_SET(0, &rfds);
+ FD_SET(csock, &rfds);
+ FD_SET(dsock, &rfds);
+ if (select(maxfd, &rfds, NULL, NULL, NULL) < 0)
+ err(EX_OSERR, "select");
+
+ /* If not user input, print a newline first */
+ if (!FD_ISSET(0, &rfds))
+ printf("\n");
+ }
+
+ ReadSockets(&rfds);
+
+ /* Get any user input */
+ if (FD_ISSET(0, &rfds)) {
+ char buf[LINE_MAX];
+
+ if (fgets(buf, sizeof(buf), stdin) == NULL) {
+ printf("\n");
+ break;
+ }
+ if (DoParseCommand(buf) == CMDRTN_QUIT)
+ break;
+ }
+ }
+ return (CMDRTN_QUIT);
+}
+#endif /* !EDITLINE */
+
+/*
+ * Read and process data on netgraph control and data sockets.
+ */
+static void
+ReadSockets(fd_set *rfds)
+{
+ /* Display any incoming control message. */
+ if (FD_ISSET(csock, rfds))
+ MsgRead();
+
+ /* Display any incoming data packet. */
+ if (FD_ISSET(dsock, rfds)) {
+ char hook[NG_HOOKSIZ];
+ u_char *buf;
+ int rl;
+
+ /* Read packet from socket. */
+ if ((rl = NgAllocRecvData(dsock, &buf, hook)) < 0)
+ err(EX_OSERR, "reading hook \"%s\"", hook);
+ if (rl == 0)
+ errx(EX_OSERR, "EOF from hook \"%s\"?", hook);
+
+ /* Write packet to stdout. */
+ printf("Rec'd data packet on hook \"%s\":\n", hook);
+ DumpAscii(buf, rl);
+ free(buf);
+ }
+}
+
+/*
+ * Parse a command line and execute the command
+ */
+static int
+DoParseCommand(const char *line)
+{
+ char *av[MAX_ARGS];
+ int ac;
+
+ /* Parse line */
+ for (ac = 0, av[0] = strtok((char *)line, WHITESPACE);
+ ac < MAX_ARGS - 1 && av[ac];
+ av[++ac] = strtok(NULL, WHITESPACE));
+
+ /* Do command */
+ return (DoCommand(ac, av));
+}
+
+/*
+ * Execute the command
+ */
+static int
+DoCommand(int ac, char **av)
+{
+ const struct ngcmd *cmd;
+ int rtn;
+
+ if (ac == 0 || *av[0] == 0)
+ return (CMDRTN_OK);
+ if ((cmd = FindCommand(av[0])) == NULL)
+ return (CMDRTN_ERROR);
+ if ((rtn = (*cmd->func)(ac, av)) == CMDRTN_USAGE)
+ warnx("usage: %s", cmd->cmd);
+ return (rtn);
+}
+
+/*
+ * Find a command
+ */
+static const struct ngcmd *
+FindCommand(const char *string)
+{
+ int k, found = -1;
+
+ for (k = 0; cmds[k] != NULL; k++) {
+ if (MatchCommand(cmds[k], string)) {
+ if (found != -1) {
+ warnx("\"%s\": ambiguous command", string);
+ return (NULL);
+ }
+ found = k;
+ }
+ }
+ if (found == -1) {
+ warnx("\"%s\": unknown command", string);
+ return (NULL);
+ }
+ return (cmds[found]);
+}
+
+/*
+ * See if string matches a prefix of "cmd" (or an alias) case insensitively
+ */
+static int
+MatchCommand(const struct ngcmd *cmd, const char *s)
+{
+ int a;
+
+ /* Try to match command, ignoring the usage stuff */
+ if (strlen(s) <= strcspn(cmd->cmd, WHITESPACE)) {
+ if (strncasecmp(s, cmd->cmd, strlen(s)) == 0)
+ return (1);
+ }
+
+ /* Try to match aliases */
+ for (a = 0; a < MAX_CMD_ALIAS && cmd->aliases[a] != NULL; a++) {
+ if (strlen(cmd->aliases[a]) >= strlen(s)) {
+ if (strncasecmp(s, cmd->aliases[a], strlen(s)) == 0)
+ return (1);
+ }
+ }
+
+ /* No match */
+ return (0);
+}
+
+/*
+ * ReadCmd()
+ */
+static int
+ReadCmd(int ac, char **av)
+{
+ FILE *fp;
+ int rtn;
+
+ /* Open file */
+ switch (ac) {
+ case 2:
+ if ((fp = fopen(av[1], "r")) == NULL) {
+ warn("%s", av[1]);
+ return (CMDRTN_ERROR);
+ }
+ break;
+ default:
+ return (CMDRTN_USAGE);
+ }
+
+ /* Process it */
+ rtn = ReadFile(fp);
+ fclose(fp);
+ return (rtn);
+}
+
+/*
+ * HelpCmd()
+ */
+static int
+HelpCmd(int ac, char **av)
+{
+ const struct ngcmd *cmd;
+ int k;
+
+ switch (ac) {
+ case 0:
+ case 1:
+ /* Show all commands */
+ printf("Available commands:\n");
+ for (k = 0; cmds[k] != NULL; k++) {
+ char *s, buf[100];
+
+ cmd = cmds[k];
+ snprintf(buf, sizeof(buf), "%s", cmd->cmd);
+ for (s = buf; *s != '\0' && !isspace(*s); s++);
+ *s = '\0';
+ printf(" %-10s %s\n", buf, cmd->desc);
+ }
+ return (CMDRTN_OK);
+ default:
+ /* Show help on a specific command */
+ if ((cmd = FindCommand(av[1])) != NULL) {
+ printf("usage: %s\n", cmd->cmd);
+ if (cmd->aliases[0] != NULL) {
+ int a = 0;
+
+ printf("Aliases: ");
+ while (1) {
+ printf("%s", cmd->aliases[a++]);
+ if (a == MAX_CMD_ALIAS
+ || cmd->aliases[a] == NULL) {
+ printf("\n");
+ break;
+ }
+ printf(", ");
+ }
+ }
+ printf("Summary: %s\n", cmd->desc);
+ if (cmd->help != NULL) {
+ const char *s;
+ char buf[65];
+ int tot, len, done;
+
+ printf("Description:\n");
+ for (s = cmd->help; *s != '\0'; s += len) {
+ while (isspace(*s))
+ s++;
+ tot = snprintf(buf,
+ sizeof(buf), "%s", s);
+ len = strlen(buf);
+ done = len == tot;
+ if (!done) {
+ while (len > 0
+ && !isspace(buf[len-1]))
+ buf[--len] = '\0';
+ }
+ printf(" %s\n", buf);
+ }
+ }
+ }
+ }
+ return (CMDRTN_OK);
+}
+
+/*
+ * QuitCmd()
+ */
+static int
+QuitCmd(int ac __unused, char **av __unused)
+{
+ return (CMDRTN_QUIT);
+}
+
+/*
+ * Dump data in hex and ASCII form
+ */
+void
+DumpAscii(const u_char *buf, int len)
+{
+ char ch, sbuf[100];
+ int k, count;
+
+ for (count = 0; count < len; count += DUMP_BYTES_PER_LINE) {
+ snprintf(sbuf, sizeof(sbuf), "%04x: ", count);
+ for (k = 0; k < DUMP_BYTES_PER_LINE; k++) {
+ if (count + k < len) {
+ snprintf(sbuf + strlen(sbuf),
+ sizeof(sbuf) - strlen(sbuf),
+ "%02x ", buf[count + k]);
+ } else {
+ snprintf(sbuf + strlen(sbuf),
+ sizeof(sbuf) - strlen(sbuf), " ");
+ }
+ }
+ snprintf(sbuf + strlen(sbuf), sizeof(sbuf) - strlen(sbuf), " ");
+ for (k = 0; k < DUMP_BYTES_PER_LINE; k++) {
+ if (count + k < len) {
+ ch = isprint(buf[count + k]) ?
+ buf[count + k] : '.';
+ snprintf(sbuf + strlen(sbuf),
+ sizeof(sbuf) - strlen(sbuf), "%c", ch);
+ } else {
+ snprintf(sbuf + strlen(sbuf),
+ sizeof(sbuf) - strlen(sbuf), " ");
+ }
+ }
+ printf("%s\n", sbuf);
+ }
+}
+
+/*
+ * Usage()
+ */
+static void
+Usage(const char *msg)
+{
+ if (msg)
+ warnx("%s", msg);
+ fprintf(stderr,
+ "usage: ngctl [-d] [-f file] [-n name] [command ...]\n");
+ exit(EX_USAGE);
+}
diff --git a/usr.sbin/ngctl/mkpeer.c b/usr.sbin/ngctl/mkpeer.c
new file mode 100644
index 0000000..1c6896b
--- /dev/null
+++ b/usr.sbin/ngctl/mkpeer.c
@@ -0,0 +1,90 @@
+
+/*
+ * mkpeer.c
+ *
+ * Copyright (c) 1996-1999 Whistle Communications, Inc.
+ * All rights reserved.
+ *
+ * Subject to the following obligations and disclaimer of warranty, use and
+ * redistribution of this software, in source or object code forms, with or
+ * without modifications are expressly permitted by Whistle Communications;
+ * provided, however, that:
+ * 1. Any and all reproductions of the source or object code must include the
+ * copyright notice above and the following disclaimer of warranties; and
+ * 2. No rights are granted, in any manner or form, to use Whistle
+ * Communications, Inc. trademarks, including the mark "WHISTLE
+ * COMMUNICATIONS" on advertising, endorsements, or otherwise except as
+ * such appears in the above copyright notice or in the software.
+ *
+ * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
+ * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
+ * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
+ * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
+ * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
+ * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
+ * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
+ * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
+ * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
+ * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <err.h>
+#include <netgraph.h>
+#include <stdio.h>
+
+#include "ngctl.h"
+
+static int MkPeerCmd(int ac, char **av);
+
+const struct ngcmd mkpeer_cmd = {
+ MkPeerCmd,
+ "mkpeer [path] <type> <hook> <peerhook>",
+ "Create and connect a new node to the node at \"path\"",
+ "The mkpeer command atomically creates a new node of type \"type\""
+ " and connects it to the node at \"path\". The hooks used for the"
+ " connection are \"hook\" on the original node and \"peerhook\""
+ " on the new node."
+ " If \"path\" is omitted then \".\" is assumed.",
+ { NULL }
+};
+
+static int
+MkPeerCmd(int ac, char **av)
+{
+ struct ngm_mkpeer mkp;
+ const char *path = ".";
+
+ /* Get arguments */
+ switch (ac) {
+ case 5:
+ path = av[1];
+ ac--;
+ av++;
+ /* FALLTHROUGH */
+ case 4:
+ snprintf(mkp.type, sizeof(mkp.type), "%s", av[1]);
+ snprintf(mkp.ourhook, sizeof(mkp.ourhook), "%s", av[2]);
+ snprintf(mkp.peerhook, sizeof(mkp.peerhook), "%s", av[3]);
+ break;
+ default:
+ return (CMDRTN_USAGE);
+ }
+
+ /* Send message */
+ if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE,
+ NGM_MKPEER, &mkp, sizeof(mkp)) < 0) {
+ warn("send msg");
+ return (CMDRTN_ERROR);
+ }
+ return (CMDRTN_OK);
+}
+
diff --git a/usr.sbin/ngctl/msg.c b/usr.sbin/ngctl/msg.c
new file mode 100644
index 0000000..38f0db7
--- /dev/null
+++ b/usr.sbin/ngctl/msg.c
@@ -0,0 +1,165 @@
+
+/*
+ * msg.c
+ *
+ * Copyright (c) 1999 Whistle Communications, Inc.
+ * All rights reserved.
+ *
+ * Subject to the following obligations and disclaimer of warranty, use and
+ * redistribution of this software, in source or object code forms, with or
+ * without modifications are expressly permitted by Whistle Communications;
+ * provided, however, that:
+ * 1. Any and all reproductions of the source or object code must include the
+ * copyright notice above and the following disclaimer of warranties; and
+ * 2. No rights are granted, in any manner or form, to use Whistle
+ * Communications, Inc. trademarks, including the mark "WHISTLE
+ * COMMUNICATIONS" on advertising, endorsements, or otherwise except as
+ * such appears in the above copyright notice or in the software.
+ *
+ * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
+ * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
+ * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
+ * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
+ * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
+ * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
+ * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
+ * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
+ * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
+ * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * $Whistle: msg.c,v 1.2 1999/11/29 23:38:35 archie Exp $
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <err.h>
+#include <netgraph.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "ngctl.h"
+
+static int MsgCmd(int ac, char **av);
+
+const struct ngcmd msg_cmd = {
+ MsgCmd,
+ "msg path command [args ... ]",
+ "Send a netgraph control message to the node at \"path\"",
+ "The msg command constructs a netgraph control message from the"
+ " command name and ASCII arguments (if any) and sends that message"
+ " to the node. It does this by first asking the node to convert"
+ " the ASCII message into binary format, and resending the result.",
+ { "cmd" }
+};
+
+static int
+MsgCmd(int ac, char **av)
+{
+ char *buf;
+ char *path, *cmdstr;
+ int i, len;
+
+ /* Get arguments */
+ if (ac < 3)
+ return (CMDRTN_USAGE);
+ path = av[1];
+ cmdstr = av[2];
+
+ /* Put command and arguments back together as one string */
+ for (len = 1, i = 3; i < ac; i++)
+ len += strlen(av[i]) + 1;
+ if ((buf = malloc(len)) == NULL) {
+ warn("malloc");
+ return (CMDRTN_ERROR);
+ }
+ for (*buf = '\0', i = 3; i < ac; i++) {
+ snprintf(buf + strlen(buf),
+ len - strlen(buf), " %s", av[i]);
+ }
+
+ /* Send it */
+ if (NgSendAsciiMsg(csock, path, "%s%s", cmdstr, buf) < 0) {
+ free(buf);
+ warn("send msg");
+ return (CMDRTN_ERROR);
+ }
+ free(buf);
+
+ /* See if a synchronous reply awaits */
+ {
+ struct timeval tv;
+ fd_set rfds;
+
+ FD_ZERO(&rfds);
+ FD_SET(csock, &rfds);
+ memset(&tv, 0, sizeof(tv));
+ switch (select(csock + 1, &rfds, NULL, NULL, &tv)) {
+ case -1:
+ err(EX_OSERR, "select");
+ case 0:
+ break;
+ default:
+ MsgRead();
+ break;
+ }
+ }
+
+ /* Done */
+ return (CMDRTN_OK);
+}
+
+/*
+ * Read and display the next incoming control message
+ */
+void
+MsgRead(void)
+{
+ struct ng_mesg *m, *m2;
+ struct ng_mesg *ascii;
+ char path[NG_PATHSIZ];
+
+ /* Get incoming message (in binary form) */
+ if (NgAllocRecvMsg(csock, &m, path) < 0) {
+ warn("recv incoming message");
+ return;
+ }
+
+ /* Ask originating node to convert message to ASCII */
+ if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE,
+ NGM_BINARY2ASCII, m, sizeof(*m) + m->header.arglen) < 0
+ || NgAllocRecvMsg(csock, &m2, NULL) < 0) {
+ printf("Rec'd %s %d from \"%s\":\n",
+ (m->header.flags & NGF_RESP) != 0 ? "response" : "command",
+ m->header.cmd, path);
+ if (m->header.arglen == 0)
+ printf("No arguments\n");
+ else
+ DumpAscii((const u_char *)m->data, m->header.arglen);
+ free(m);
+ return;
+ }
+
+ /* Display message in ASCII form */
+ free(m);
+ ascii = (struct ng_mesg *)m2->data;
+ printf("Rec'd %s \"%s\" (%d) from \"%s\":\n",
+ (ascii->header.flags & NGF_RESP) != 0 ? "response" : "command",
+ ascii->header.cmdstr, ascii->header.cmd, path);
+ if (*ascii->data != '\0')
+ printf("Args:\t%s\n", ascii->data);
+ else
+ printf("No arguments\n");
+ free(m2);
+}
+
diff --git a/usr.sbin/ngctl/name.c b/usr.sbin/ngctl/name.c
new file mode 100644
index 0000000..7149d35
--- /dev/null
+++ b/usr.sbin/ngctl/name.c
@@ -0,0 +1,81 @@
+
+/*
+ * name.c
+ *
+ * Copyright (c) 1996-1999 Whistle Communications, Inc.
+ * All rights reserved.
+ *
+ * Subject to the following obligations and disclaimer of warranty, use and
+ * redistribution of this software, in source or object code forms, with or
+ * without modifications are expressly permitted by Whistle Communications;
+ * provided, however, that:
+ * 1. Any and all reproductions of the source or object code must include the
+ * copyright notice above and the following disclaimer of warranties; and
+ * 2. No rights are granted, in any manner or form, to use Whistle
+ * Communications, Inc. trademarks, including the mark "WHISTLE
+ * COMMUNICATIONS" on advertising, endorsements, or otherwise except as
+ * such appears in the above copyright notice or in the software.
+ *
+ * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
+ * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
+ * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
+ * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
+ * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
+ * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
+ * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
+ * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
+ * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
+ * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <err.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <netgraph.h>
+
+#include "ngctl.h"
+
+static int NameCmd(int ac, char **av);
+
+const struct ngcmd name_cmd = {
+ NameCmd,
+ "name <path> <name>",
+ "Assign name <name> to the node at <path>",
+ NULL,
+ { NULL }
+};
+
+static int
+NameCmd(int ac, char **av)
+{
+ struct ngm_name name;
+ char *path;
+
+ /* Get arguments */
+ switch (ac) {
+ case 3:
+ path = av[1];
+ snprintf(name.name, sizeof(name.name), "%s", av[2]);
+ break;
+ default:
+ return (CMDRTN_USAGE);
+ }
+
+ /* Send message */
+ if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE,
+ NGM_NAME, &name, sizeof(name)) < 0) {
+ warn("send msg");
+ return (CMDRTN_ERROR);
+ }
+ return (CMDRTN_OK);
+}
+
diff --git a/usr.sbin/ngctl/ngctl.8 b/usr.sbin/ngctl/ngctl.8
new file mode 100644
index 0000000..8ba9f5d
--- /dev/null
+++ b/usr.sbin/ngctl/ngctl.8
@@ -0,0 +1,141 @@
+.\" Copyright (c) 1996-1999 Whistle Communications, Inc.
+.\" All rights reserved.
+.\"
+.\" Subject to the following obligations and disclaimer of warranty, use and
+.\" redistribution of this software, in source or object code forms, with or
+.\" without modifications are expressly permitted by Whistle Communications;
+.\" provided, however, that:
+.\" 1. Any and all reproductions of the source or object code must include the
+.\" copyright notice above and the following disclaimer of warranties; and
+.\" 2. No rights are granted, in any manner or form, to use Whistle
+.\" Communications, Inc. trademarks, including the mark "WHISTLE
+.\" COMMUNICATIONS" on advertising, endorsements, or otherwise except as
+.\" such appears in the above copyright notice or in the software.
+.\"
+.\" THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
+.\" TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
+.\" REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
+.\" INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
+.\" WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
+.\" REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
+.\" SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
+.\" IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
+.\" RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
+.\" WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+.\" PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
+.\" SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
+.\" OF SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\" $Whistle: ngctl.8,v 1.6 1999/01/20 03:19:44 archie Exp $
+.\"
+.Dd January 19, 1999
+.Dt NGCTL 8
+.Os
+.Sh NAME
+.Nm ngctl
+.Nd netgraph control utility
+.Sh SYNOPSIS
+.Nm
+.Op Fl d
+.Op Fl f Ar filename
+.Op Fl n Ar nodename
+.Op Ar command ...
+.Sh DESCRIPTION
+The
+.Nm
+utility creates a new netgraph node of type
+.Em socket
+which can be used to issue netgraph commands.
+If no
+.Fl f
+flag is given, no
+command is supplied on the command line, and standard input is a tty,
+.Nm
+will enter interactive mode.
+Otherwise
+.Nm
+will execute the supplied command(s) and exit immediately.
+.Pp
+Nodes can be created, removed, joined together, etc.
+.Tn ASCII
+formatted control messages can be sent to any node if that node
+supports binary/ASCII control message conversion.
+.Pp
+In interactive mode,
+.Nm
+will display any control messages and data packets received by the socket node.
+In the case of control messages, the message arguments are displayed in
+.Tn ASCII
+form if the originating node supports conversion.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl f Ar nodeinfo
+Read commands from the named file.
+A single dash represents the standard input.
+Blank lines and lines starting with a
+.Dq #
+are ignored.
+.It Fl n Ar nodename
+Assign
+.Em nodename
+to the newly created netgraph node.
+The default name is
+.Em ngctlXXX
+where XXX is the process ID number.
+.It Fl d
+Increase the debugging verbosity level.
+.El
+.Sh COMMANDS
+The currently supported commands in
+.Nm
+are:
+.Pp
+.Bd -literal -offset indent -compact
+config get or set configuration of node at <path>
+connect Connects hook <peerhook> of the node at <relpath> to <hook>
+debug Get/set debugging verbosity level
+dot Produce a GraphViz (.dot) of the entire netgraph.
+help Show command summary or get more help on a specific command
+list Show information about all nodes
+mkpeer Create and connect a new node to the node at "path"
+msg Send a netgraph control message to the node at "path"
+name Assign name <name> to the node at <path>
+read Read and execute commands from a file
+rmhook Disconnect hook "hook" of the node at "path"
+show Show information about the node at <path>
+shutdown Shutdown the node at <path>
+status Get human readable status information from the node at <path>
+types Show information about all installed node types
+write Send a data packet down the hook named by "hook".
+quit Exit program
+.Ed
+.Pp
+Some commands have aliases, e.g.,
+.Dq ls
+is the same as
+.Dq list .
+The
+.Dq help
+command displays the available
+commands, their usage and aliases, and a brief description.
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr netgraph 3 ,
+.Xr netgraph 4 ,
+.Xr nghook 8
+.Sh HISTORY
+The
+.Nm netgraph
+system was designed and first implemented at Whistle Communications, Inc.\& in
+a version of
+.Fx 2.2
+customized for the Whistle InterJet.
+.Sh AUTHORS
+.An Archie Cobbs Aq Mt archie@whistle.com
diff --git a/usr.sbin/ngctl/ngctl.h b/usr.sbin/ngctl/ngctl.h
new file mode 100644
index 0000000..4d8c2ad
--- /dev/null
+++ b/usr.sbin/ngctl/ngctl.h
@@ -0,0 +1,82 @@
+
+/*
+ * ngctl.h
+ *
+ * Copyright (c) 1996-1999 Whistle Communications, Inc.
+ * All rights reserved.
+ *
+ * Subject to the following obligations and disclaimer of warranty, use and
+ * redistribution of this software, in source or object code forms, with or
+ * without modifications are expressly permitted by Whistle Communications;
+ * provided, however, that:
+ * 1. Any and all reproductions of the source or object code must include the
+ * copyright notice above and the following disclaimer of warranties; and
+ * 2. No rights are granted, in any manner or form, to use Whistle
+ * Communications, Inc. trademarks, including the mark "WHISTLE
+ * COMMUNICATIONS" on advertising, endorsements, or otherwise except as
+ * such appears in the above copyright notice or in the software.
+ *
+ * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
+ * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
+ * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
+ * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
+ * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
+ * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
+ * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
+ * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
+ * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
+ * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#define MAX_CMD_ALIAS 8
+
+/* Command descriptors */
+struct ngcmd {
+ int (*func)(int ac, char **av); /* command function */
+ const char *cmd; /* command usage */
+ const char *desc; /* description */
+ const char *help; /* help text */
+ const char *aliases[MAX_CMD_ALIAS]; /* command aliases */
+};
+
+/* Command return values */
+#define CMDRTN_OK 0
+#define CMDRTN_USAGE 1
+#define CMDRTN_ERROR 2
+#define CMDRTN_QUIT 3
+
+/* Available commands */
+extern const struct ngcmd config_cmd;
+extern const struct ngcmd connect_cmd;
+extern const struct ngcmd debug_cmd;
+extern const struct ngcmd dot_cmd;
+extern const struct ngcmd help_cmd;
+extern const struct ngcmd list_cmd;
+extern const struct ngcmd mkpeer_cmd;
+extern const struct ngcmd msg_cmd;
+extern const struct ngcmd name_cmd;
+extern const struct ngcmd read_cmd;
+extern const struct ngcmd rmhook_cmd;
+extern const struct ngcmd show_cmd;
+extern const struct ngcmd shutdown_cmd;
+extern const struct ngcmd status_cmd;
+extern const struct ngcmd types_cmd;
+extern const struct ngcmd write_cmd;
+extern const struct ngcmd quit_cmd;
+
+/* Data and control sockets */
+extern int csock, dsock;
+
+/* Misc functions */
+extern void MsgRead(void);
+extern void DumpAscii(const u_char *buf, int len);
+
diff --git a/usr.sbin/ngctl/rmhook.c b/usr.sbin/ngctl/rmhook.c
new file mode 100644
index 0000000..9a47a9d
--- /dev/null
+++ b/usr.sbin/ngctl/rmhook.c
@@ -0,0 +1,86 @@
+
+/*
+ * rmhook.c
+ *
+ * Copyright (c) 1996-1999 Whistle Communications, Inc.
+ * All rights reserved.
+ *
+ * Subject to the following obligations and disclaimer of warranty, use and
+ * redistribution of this software, in source or object code forms, with or
+ * without modifications are expressly permitted by Whistle Communications;
+ * provided, however, that:
+ * 1. Any and all reproductions of the source or object code must include the
+ * copyright notice above and the following disclaimer of warranties; and
+ * 2. No rights are granted, in any manner or form, to use Whistle
+ * Communications, Inc. trademarks, including the mark "WHISTLE
+ * COMMUNICATIONS" on advertising, endorsements, or otherwise except as
+ * such appears in the above copyright notice or in the software.
+ *
+ * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
+ * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
+ * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
+ * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
+ * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
+ * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
+ * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
+ * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
+ * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
+ * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <err.h>
+#include <netgraph.h>
+#include <stdio.h>
+
+#include "ngctl.h"
+
+static int RmHookCmd(int ac, char **av);
+
+const struct ngcmd rmhook_cmd = {
+ RmHookCmd,
+ "rmhook [path] <hook>",
+ "Disconnect hook \"hook\" of the node at \"path\"",
+ "The rmhook command forces the node at \"path\" to break the link"
+ " formed by its hook \"hook\", if connected."
+ " If \"path\" is omitted then \".\" is assumed.",
+ { "disconnect" }
+};
+
+static int
+RmHookCmd(int ac, char **av)
+{
+ struct ngm_rmhook rmh;
+ const char *path = ".";
+
+ /* Get arguments */
+ switch (ac) {
+ case 3:
+ path = av[1];
+ ac--;
+ av++;
+ /* FALLTHROUGH */
+ case 2:
+ snprintf(rmh.ourhook, sizeof(rmh.ourhook), "%s", av[1]);
+ break;
+ default:
+ return (CMDRTN_USAGE);
+ }
+
+ /* Send message */
+ if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE,
+ NGM_RMHOOK, &rmh, sizeof(rmh)) < 0) {
+ warn("send msg");
+ return (CMDRTN_ERROR);
+ }
+ return (CMDRTN_OK);
+}
+
diff --git a/usr.sbin/ngctl/show.c b/usr.sbin/ngctl/show.c
new file mode 100644
index 0000000..26eea04
--- /dev/null
+++ b/usr.sbin/ngctl/show.c
@@ -0,0 +1,139 @@
+
+/*
+ * show.c
+ *
+ * Copyright (c) 1996-1999 Whistle Communications, Inc.
+ * All rights reserved.
+ *
+ * Subject to the following obligations and disclaimer of warranty, use and
+ * redistribution of this software, in source or object code forms, with or
+ * without modifications are expressly permitted by Whistle Communications;
+ * provided, however, that:
+ * 1. Any and all reproductions of the source or object code must include the
+ * copyright notice above and the following disclaimer of warranties; and
+ * 2. No rights are granted, in any manner or form, to use Whistle
+ * Communications, Inc. trademarks, including the mark "WHISTLE
+ * COMMUNICATIONS" on advertising, endorsements, or otherwise except as
+ * such appears in the above copyright notice or in the software.
+ *
+ * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
+ * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
+ * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
+ * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
+ * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
+ * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
+ * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
+ * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
+ * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
+ * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <err.h>
+#include <netgraph.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "ngctl.h"
+
+#define FMT " %-15s %-15s %-12s %-15s %-15s\n"
+#define UNNAMED "<unnamed>"
+#define NOSTATUS "<no status>"
+
+static int ShowCmd(int ac, char **av);
+
+const struct ngcmd show_cmd = {
+ ShowCmd,
+ "show [-n] <path>",
+ "Show information about the node at <path>",
+ "If the -n flag is given, hooks are not listed.",
+ { "inquire", "info" }
+};
+
+static int
+ShowCmd(int ac, char **av)
+{
+ char *path;
+ struct ng_mesg *resp;
+ struct hooklist *hlist;
+ struct nodeinfo *ninfo;
+ int ch, no_hooks = 0;
+
+ /* Get options */
+ optind = 1;
+ while ((ch = getopt(ac, av, "n")) != -1) {
+ switch (ch) {
+ case 'n':
+ no_hooks = 1;
+ break;
+ case '?':
+ default:
+ return (CMDRTN_USAGE);
+ break;
+ }
+ }
+ ac -= optind;
+ av += optind;
+
+ /* Get arguments */
+ switch (ac) {
+ case 1:
+ path = av[0];
+ break;
+ default:
+ return (CMDRTN_USAGE);
+ }
+
+ /* Get node info and hook list */
+ if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE,
+ NGM_LISTHOOKS, NULL, 0) < 0) {
+ warn("send msg");
+ return (CMDRTN_ERROR);
+ }
+ if (NgAllocRecvMsg(csock, &resp, NULL) < 0) {
+ warn("recv msg");
+ return (CMDRTN_ERROR);
+ }
+
+ /* Show node information */
+ hlist = (struct hooklist *) resp->data;
+ ninfo = &hlist->nodeinfo;
+ if (!*ninfo->name)
+ snprintf(ninfo->name, sizeof(ninfo->name), "%s", UNNAMED);
+ printf(" Name: %-15s Type: %-15s ID: %08x Num hooks: %d\n",
+ ninfo->name, ninfo->type, ninfo->id, ninfo->hooks);
+ if (!no_hooks && ninfo->hooks > 0) {
+ u_int k;
+
+ printf(FMT, "Local hook", "Peer name",
+ "Peer type", "Peer ID", "Peer hook");
+ printf(FMT, "----------", "---------",
+ "---------", "-------", "---------");
+ for (k = 0; k < ninfo->hooks; k++) {
+ struct linkinfo *const link = &hlist->link[k];
+ struct nodeinfo *const peer = &hlist->link[k].nodeinfo;
+ char idbuf[20];
+
+ if (!*peer->name) {
+ snprintf(peer->name, sizeof(peer->name),
+ "%s", UNNAMED);
+ }
+ snprintf(idbuf, sizeof(idbuf), "%08x", peer->id);
+ printf(FMT, link->ourhook, peer->name,
+ peer->type, idbuf, link->peerhook);
+ }
+ }
+ free(resp);
+ return (CMDRTN_OK);
+}
+
+
diff --git a/usr.sbin/ngctl/shutdown.c b/usr.sbin/ngctl/shutdown.c
new file mode 100644
index 0000000..b06beb5
--- /dev/null
+++ b/usr.sbin/ngctl/shutdown.c
@@ -0,0 +1,79 @@
+
+/*
+ * shutdown.c
+ *
+ * Copyright (c) 1996-1999 Whistle Communications, Inc.
+ * All rights reserved.
+ *
+ * Subject to the following obligations and disclaimer of warranty, use and
+ * redistribution of this software, in source or object code forms, with or
+ * without modifications are expressly permitted by Whistle Communications;
+ * provided, however, that:
+ * 1. Any and all reproductions of the source or object code must include the
+ * copyright notice above and the following disclaimer of warranties; and
+ * 2. No rights are granted, in any manner or form, to use Whistle
+ * Communications, Inc. trademarks, including the mark "WHISTLE
+ * COMMUNICATIONS" on advertising, endorsements, or otherwise except as
+ * such appears in the above copyright notice or in the software.
+ *
+ * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
+ * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
+ * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
+ * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
+ * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
+ * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
+ * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
+ * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
+ * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
+ * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <err.h>
+#include <netgraph.h>
+#include <unistd.h>
+
+#include "ngctl.h"
+
+static int ShutdownCmd(int ac, char **av);
+
+const struct ngcmd shutdown_cmd = {
+ ShutdownCmd,
+ "shutdown <path>",
+ "Shutdown the node at <path>",
+ NULL,
+ { "kill", "rmnode" }
+};
+
+static int
+ShutdownCmd(int ac, char **av)
+{
+ char *path;
+
+ /* Get arguments */
+ switch (ac) {
+ case 2:
+ path = av[1];
+ break;
+ default:
+ return (CMDRTN_USAGE);
+ }
+
+ /* Shutdown node */
+ if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE,
+ NGM_SHUTDOWN, NULL, 0) < 0) {
+ warn("shutdown");
+ return (CMDRTN_ERROR);
+ }
+ return (CMDRTN_OK);
+}
+
+
diff --git a/usr.sbin/ngctl/status.c b/usr.sbin/ngctl/status.c
new file mode 100644
index 0000000..7827c63
--- /dev/null
+++ b/usr.sbin/ngctl/status.c
@@ -0,0 +1,101 @@
+
+/*
+ * status.c
+ *
+ * Copyright (c) 1996-1999 Whistle Communications, Inc.
+ * All rights reserved.
+ *
+ * Subject to the following obligations and disclaimer of warranty, use and
+ * redistribution of this software, in source or object code forms, with or
+ * without modifications are expressly permitted by Whistle Communications;
+ * provided, however, that:
+ * 1. Any and all reproductions of the source or object code must include the
+ * copyright notice above and the following disclaimer of warranties; and
+ * 2. No rights are granted, in any manner or form, to use Whistle
+ * Communications, Inc. trademarks, including the mark "WHISTLE
+ * COMMUNICATIONS" on advertising, endorsements, or otherwise except as
+ * such appears in the above copyright notice or in the software.
+ *
+ * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
+ * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
+ * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
+ * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
+ * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
+ * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
+ * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
+ * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
+ * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
+ * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <err.h>
+#include <errno.h>
+#include <netgraph.h>
+#include <stdio.h>
+
+#include "ngctl.h"
+
+#define NOSTATUS "<no status>"
+
+static int StatusCmd(int ac, char **av);
+
+const struct ngcmd status_cmd = {
+ StatusCmd,
+ "status <path>",
+ "Get human readable status information from the node at <path>",
+ NULL,
+ { NULL }
+};
+
+static int
+StatusCmd(int ac, char **av)
+{
+ u_char sbuf[sizeof(struct ng_mesg) + NG_TEXTRESPONSE];
+ struct ng_mesg *const resp = (struct ng_mesg *) sbuf;
+ char *const status = (char *) resp->data;
+ char *path;
+ int nostat = 0;
+
+ /* Get arguments */
+ switch (ac) {
+ case 2:
+ path = av[1];
+ break;
+ default:
+ return (CMDRTN_USAGE);
+ }
+
+ /* Get node status summary */
+ if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE,
+ NGM_TEXT_STATUS, NULL, 0) < 0) {
+ switch (errno) {
+ case EINVAL:
+ nostat = 1;
+ break;
+ default:
+ warn("send msg");
+ return (CMDRTN_ERROR);
+ }
+ } else {
+ if (NgRecvMsg(csock, resp, sizeof(sbuf), NULL) < 0
+ || (resp->header.flags & NGF_RESP) == 0)
+ nostat = 1;
+ }
+
+ /* Show it */
+ if (nostat)
+ printf("No status available for \"%s\"\n", path);
+ else
+ printf("Status for \"%s\":\n%s\n", path, status);
+ return (CMDRTN_OK);
+}
+
diff --git a/usr.sbin/ngctl/types.c b/usr.sbin/ngctl/types.c
new file mode 100644
index 0000000..5ed4b53
--- /dev/null
+++ b/usr.sbin/ngctl/types.c
@@ -0,0 +1,101 @@
+
+/*
+ * types.c
+ *
+ * Copyright (c) 1996-1999 Whistle Communications, Inc.
+ * All rights reserved.
+ *
+ * Subject to the following obligations and disclaimer of warranty, use and
+ * redistribution of this software, in source or object code forms, with or
+ * without modifications are expressly permitted by Whistle Communications;
+ * provided, however, that:
+ * 1. Any and all reproductions of the source or object code must include the
+ * copyright notice above and the following disclaimer of warranties; and
+ * 2. No rights are granted, in any manner or form, to use Whistle
+ * Communications, Inc. trademarks, including the mark "WHISTLE
+ * COMMUNICATIONS" on advertising, endorsements, or otherwise except as
+ * such appears in the above copyright notice or in the software.
+ *
+ * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
+ * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
+ * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
+ * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
+ * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
+ * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
+ * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
+ * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
+ * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
+ * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <err.h>
+#include <netgraph.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "ngctl.h"
+
+static int TypesCmd(int ac, char **av);
+
+const struct ngcmd types_cmd = {
+ TypesCmd,
+ "types",
+ "Show information about all installed node types",
+ NULL,
+ { NULL }
+};
+
+static int
+TypesCmd(int ac, char **av __unused)
+{
+ struct ng_mesg *resp;
+ struct typelist *tlist;
+ int rtn = CMDRTN_OK;
+ u_int k;
+
+ /* Get arguments */
+ switch (ac) {
+ case 1:
+ break;
+ default:
+ return (CMDRTN_USAGE);
+ }
+
+ /* Get list of types */
+ if (NgSendMsg(csock, ".", NGM_GENERIC_COOKIE,
+ NGM_LISTTYPES, NULL, 0) < 0) {
+ warn("send msg");
+ return (CMDRTN_ERROR);
+ }
+ if (NgAllocRecvMsg(csock, &resp, NULL) < 0) {
+ warn("recv msg");
+ return (CMDRTN_ERROR);
+ }
+
+ /* Show each type */
+ tlist = (struct typelist *) resp->data;
+ printf("There are %d total types:\n", tlist->numtypes);
+ if (tlist->numtypes > 0) {
+ printf("%15s Number of living nodes\n", "Type name");
+ printf("%15s ----------------------\n", "---------");
+ }
+ for (k = 0; k < tlist->numtypes; k++) {
+ struct typeinfo *const ti = &tlist->typeinfo[k];
+ printf("%15s %5d\n", ti->type_name, ti->numnodes);
+ }
+
+ /* Done */
+ free(resp);
+ return (rtn);
+}
+
diff --git a/usr.sbin/ngctl/write.c b/usr.sbin/ngctl/write.c
new file mode 100644
index 0000000..4114211
--- /dev/null
+++ b/usr.sbin/ngctl/write.c
@@ -0,0 +1,121 @@
+
+/*
+ * write.c
+ *
+ * Copyright (c) 2002 Archie L. Cobbs
+ * All rights reserved.
+ *
+ * Subject to the following obligations and disclaimer of warranty, use and
+ * redistribution of this software, in source or object code forms, with or
+ * without modifications are expressly permitted by Archie L. Cobbs;
+ * provided, however, that:
+ * 1. Any and all reproductions of the source or object code must include the
+ * copyright notice above and the following disclaimer of warranties
+ *
+ * THIS SOFTWARE IS BEING PROVIDED BY ARCHIE L. COBBS AS IS", AND TO
+ * THE MAXIMUM EXTENT PERMITTED BY LAW, ARCHIE L. COBBS MAKES NO
+ * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
+ * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
+ * ARCHIE L. COBBS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
+ * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
+ * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
+ * IN NO EVENT SHALL ARCHIE L. COBBS BE LIABLE FOR ANY DAMAGES
+ * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
+ * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ARCHIE L. COBBS IS ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <netgraph/ng_socket.h>
+
+#include "ngctl.h"
+
+#define BUF_SIZE 8192
+
+static int WriteCmd(int ac, char **av);
+
+const struct ngcmd write_cmd = {
+ WriteCmd,
+ "write hook < -f file | byte ... >",
+ "Send a data packet down the hook named by \"hook\".",
+ "The data may be contained in a file, or may be described directly"
+ " on the command line by supplying a sequence of bytes.",
+ { "w" }
+};
+
+static int
+WriteCmd(int ac, char **av)
+{
+ u_int32_t sagbuf[64];
+ struct sockaddr_ng *sag = (struct sockaddr_ng *)sagbuf;
+ u_char buf[BUF_SIZE];
+ const char *hook;
+ FILE *fp;
+ u_int len;
+ int byte;
+ int i;
+
+ /* Get arguments */
+ if (ac < 3)
+ return (CMDRTN_USAGE);
+ hook = av[1];
+
+ /* Get data */
+ if (strcmp(av[2], "-f") == 0) {
+ if (ac != 4)
+ return (CMDRTN_USAGE);
+ if ((fp = fopen(av[3], "r")) == NULL) {
+ warn("can't read file \"%s\"", av[3]);
+ return (CMDRTN_ERROR);
+ }
+ if ((len = fread(buf, 1, sizeof(buf), fp)) == 0) {
+ if (ferror(fp))
+ warn("can't read file \"%s\"", av[3]);
+ else
+ warnx("file \"%s\" is empty", av[3]);
+ fclose(fp);
+ return (CMDRTN_ERROR);
+ }
+ fclose(fp);
+ } else {
+ for (i = 2, len = 0; i < ac && len < sizeof(buf); i++, len++) {
+ if (sscanf(av[i], "%i", &byte) != 1
+ || (byte < -128 || byte > 255)) {
+ warnx("invalid byte \"%s\"", av[i]);
+ return (CMDRTN_ERROR);
+ }
+ buf[len] = (u_char)byte;
+ }
+ if (len == 0)
+ return (CMDRTN_USAGE);
+ }
+
+ /* Send data */
+ sag->sg_len = 3 + strlen(hook);
+ sag->sg_family = AF_NETGRAPH;
+ strlcpy(sag->sg_data, hook, sizeof(sagbuf) - 2);
+ if (sendto(dsock, buf, len,
+ 0, (struct sockaddr *)sag, sag->sg_len) == -1) {
+ warn("writing to hook \"%s\"", hook);
+ return (CMDRTN_ERROR);
+ }
+
+ /* Done */
+ return (CMDRTN_OK);
+}
+
diff --git a/usr.sbin/nghook/Makefile b/usr.sbin/nghook/Makefile
new file mode 100644
index 0000000..8a63d82
--- /dev/null
+++ b/usr.sbin/nghook/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+# $Whistle: Makefile,v 1.4 1999/01/16 04:44:33 archie Exp $
+
+PROG= nghook
+MAN= nghook.8
+SRCS= main.c
+
+LIBADD= netgraph
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/nghook/Makefile.depend b/usr.sbin/nghook/Makefile.depend
new file mode 100644
index 0000000..92ae034
--- /dev/null
+++ b/usr.sbin/nghook/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libnetgraph \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/nghook/main.c b/usr.sbin/nghook/main.c
new file mode 100644
index 0000000..700ca5e
--- /dev/null
+++ b/usr.sbin/nghook/main.c
@@ -0,0 +1,309 @@
+/*
+ * main.c
+ *
+ * Copyright (c) 1996-1999 Whistle Communications, Inc.
+ * All rights reserved.
+ *
+ * Subject to the following obligations and disclaimer of warranty, use and
+ * redistribution of this software, in source or object code forms, with or
+ * without modifications are expressly permitted by Whistle Communications;
+ * provided, however, that:
+ * 1. Any and all reproductions of the source or object code must include the
+ * copyright notice above and the following disclaimer of warranties; and
+ * 2. No rights are granted, in any manner or form, to use Whistle
+ * Communications, Inc. trademarks, including the mark "WHISTLE
+ * COMMUNICATIONS" on advertising, endorsements, or otherwise except as
+ * such appears in the above copyright notice or in the software.
+ *
+ * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
+ * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
+ * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
+ * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
+ * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
+ * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
+ * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
+ * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
+ * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
+ * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * $Whistle: main.c,v 1.9 1999/01/20 00:26:26 archie Exp $
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <sysexits.h>
+#include <errno.h>
+#include <err.h>
+#include <stringlist.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+
+#include <netgraph.h>
+
+#define DEFAULT_HOOKNAME "debug"
+#define NG_SOCK_HOOK_NAME "hook"
+
+#define BUF_SIZE (64 * 1024)
+
+static void WriteAscii(u_char * buf, int len);
+static void Usage(void);
+static void send_msgs(int, const char *);
+
+static int outfd = STDOUT_FILENO;
+static int infd = STDIN_FILENO;
+
+static StringList *msgs;
+
+/*
+ * main()
+ */
+int
+main(int ac, char *av[])
+{
+ struct ngm_connect ngc;
+ const char *path = NULL;
+ const char *hook = DEFAULT_HOOKNAME;
+ int csock, dsock;
+ int asciiFlag = 0;
+ int loopFlag = 0;
+ int noInput = 0;
+ int execFlag = 0;
+ int ch;
+
+ if ((msgs = sl_init()) == NULL)
+ err(EX_OSERR, NULL);
+
+ /* Parse flags */
+ while ((ch = getopt(ac, av, "aedlm:nsS")) != -1) {
+ switch (ch) {
+ case 'a':
+ asciiFlag = 1;
+ break;
+ case 'd':
+ NgSetDebug(NgSetDebug(-1) + 1);
+ break;
+ case 'e':
+ execFlag = 1;
+ break;
+ case 'l':
+ loopFlag = 1;
+ break;
+ case 'n':
+ noInput = 1;
+ break;
+ case 'm':
+ if (sl_add(msgs, optarg) == -1)
+ err(EX_OSERR, NULL);
+ break;
+ case 's':
+ outfd = STDIN_FILENO;
+ break;
+ case 'S':
+ infd = STDOUT_FILENO;
+ break;
+ case '?':
+ default:
+ Usage();
+ }
+ }
+ ac -= optind;
+ av += optind;
+
+ if (execFlag) {
+ if (asciiFlag || loopFlag) {
+ fprintf(stderr, "conflicting options\n");
+ Usage();
+ }
+ if (ac < 3)
+ Usage();
+ path = av[0];
+ hook = av[1];
+ av += 2;
+ ac -= 2;
+ } else {
+ /* Get params */
+ switch (ac) {
+ case 2:
+ hook = av[1];
+ /* FALLTHROUGH */
+ case 1:
+ path = av[0];
+ break;
+ default:
+ Usage();
+ }
+ }
+
+ /* Get sockets */
+ if (NgMkSockNode(NULL, &csock, &dsock) < 0)
+ errx(EX_OSERR, "can't get sockets");
+
+ /* Connect socket node to specified node */
+ snprintf(ngc.path, sizeof(ngc.path), "%s", path);
+ snprintf(ngc.ourhook, sizeof(ngc.ourhook), NG_SOCK_HOOK_NAME);
+ snprintf(ngc.peerhook, sizeof(ngc.peerhook), "%s", hook);
+
+ if (NgSendMsg(csock, ".",
+ NGM_GENERIC_COOKIE, NGM_CONNECT, &ngc, sizeof(ngc)) < 0)
+ errx(EX_OSERR, "can't connect to node");
+
+ if (execFlag) {
+ /* move dsock to fd 0 and 1 */
+ (void)close(0);
+ (void)close(1);
+ if (!noInput)
+ (void)dup2(dsock, 0);
+ (void)dup2(dsock, 1);
+
+ send_msgs(csock, path);
+
+ /* try executing the program */
+ (void)execv(av[0], av);
+ err(EX_OSERR, "%s", av[0]);
+
+ } else
+ send_msgs(csock, path);
+
+ /* Close standard input if not reading from it */
+ if (noInput)
+ fclose(stdin);
+
+ /* Relay data */
+ while (1) {
+ fd_set rfds;
+
+ /* Setup bits */
+ FD_ZERO(&rfds);
+ if (!noInput)
+ FD_SET(infd, &rfds);
+ FD_SET(dsock, &rfds);
+
+ /* Wait for something to happen */
+ if (select(FD_SETSIZE, &rfds, NULL, NULL, NULL) < 0)
+ err(EX_OSERR, "select");
+
+ /* Check data from socket */
+ if (FD_ISSET(dsock, &rfds)) {
+ char buf[BUF_SIZE];
+ int rl, wl;
+
+ /* Read packet from socket */
+ if ((rl = NgRecvData(dsock,
+ buf, sizeof(buf), NULL)) < 0)
+ err(EX_OSERR, "read(hook)");
+ if (rl == 0)
+ errx(EX_OSERR, "read EOF from hook?!");
+
+ /* Write packet to stdout */
+ if (asciiFlag)
+ WriteAscii((u_char *) buf, rl);
+ else if ((wl = write(outfd, buf, rl)) != rl) {
+ if (wl < 0) {
+ err(EX_OSERR, "write(stdout)");
+ } else {
+ errx(EX_OSERR,
+ "stdout: read %d, wrote %d",
+ rl, wl);
+ }
+ }
+ /* Loopback */
+ if (loopFlag) {
+ if (NgSendData(dsock, NG_SOCK_HOOK_NAME, buf, rl) < 0)
+ err(EX_OSERR, "write(hook)");
+ }
+ }
+
+ /* Check data from stdin */
+ if (FD_ISSET(infd, &rfds)) {
+ char buf[BUF_SIZE];
+ int rl;
+
+ /* Read packet from stdin */
+ if ((rl = read(infd, buf, sizeof(buf))) < 0)
+ err(EX_OSERR, "read(stdin)");
+ if (rl == 0)
+ errx(EX_OSERR, "EOF(stdin)");
+
+ /* Write packet to socket */
+ if (NgSendData(dsock, NG_SOCK_HOOK_NAME, buf, rl) < 0)
+ err(EX_OSERR, "write(hook)");
+ }
+ }
+}
+
+/*
+ * Dump data in hex and ASCII form
+ */
+static void
+WriteAscii(u_char *buf, int len)
+{
+ char ch, sbuf[100];
+ int k, count;
+
+ for (count = 0; count < len; count += 16) {
+ snprintf(sbuf, sizeof(sbuf), "%04x: ", count);
+ for (k = 0; k < 16; k++)
+ if (count + k < len)
+ snprintf(sbuf + strlen(sbuf),
+ sizeof(sbuf) - strlen(sbuf),
+ "%02x ", buf[count + k]);
+ else
+ snprintf(sbuf + strlen(sbuf),
+ sizeof(sbuf) - strlen(sbuf), " ");
+ snprintf(sbuf + strlen(sbuf), sizeof(sbuf) - strlen(sbuf), " ");
+ for (k = 0; k < 16; k++)
+ if (count + k < len) {
+ ch = isprint(buf[count + k]) ?
+ buf[count + k] : '.';
+ snprintf(sbuf + strlen(sbuf),
+ sizeof(sbuf) - strlen(sbuf), "%c", ch);
+ } else
+ snprintf(sbuf + strlen(sbuf),
+ sizeof(sbuf) - strlen(sbuf), " ");
+ snprintf(sbuf + strlen(sbuf),
+ sizeof(sbuf) - strlen(sbuf), "\n");
+ (void) write(outfd, sbuf, strlen(sbuf));
+ }
+ ch = '\n';
+ write(outfd, &ch, 1);
+}
+
+/*
+ * Display usage and exit
+ */
+static void
+Usage(void)
+{
+ fprintf(stderr, "usage: nghook [-adlnsS] path [hookname]\n");
+ fprintf(stderr, " or: nghook -e [-n] [-m msg]* path hookname prog "
+ "[args...]\n");
+ exit(EX_USAGE);
+}
+
+/*
+ * Send the messages to the node
+ */
+static void
+send_msgs(int cs, const char *path)
+{
+ u_int i;
+
+ for (i = 0; i < msgs->sl_cur; i++)
+ if (NgSendAsciiMsg(cs, path, "%s", msgs->sl_str[i]) == -1)
+ err(EX_OSERR, "sending message '%s'", msgs->sl_str[i]);
+}
diff --git a/usr.sbin/nghook/nghook.8 b/usr.sbin/nghook/nghook.8
new file mode 100644
index 0000000..651a6fa
--- /dev/null
+++ b/usr.sbin/nghook/nghook.8
@@ -0,0 +1,145 @@
+.\" Copyright (c) 1996-1999 Whistle Communications, Inc.
+.\" All rights reserved.
+.\"
+.\" Subject to the following obligations and disclaimer of warranty, use and
+.\" redistribution of this software, in source or object code forms, with or
+.\" without modifications are expressly permitted by Whistle Communications;
+.\" provided, however, that:
+.\" 1. Any and all reproductions of the source or object code must include the
+.\" copyright notice above and the following disclaimer of warranties; and
+.\" 2. No rights are granted, in any manner or form, to use Whistle
+.\" Communications, Inc. trademarks, including the mark "WHISTLE
+.\" COMMUNICATIONS" on advertising, endorsements, or otherwise except as
+.\" such appears in the above copyright notice or in the software.
+.\"
+.\" THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
+.\" TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
+.\" REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
+.\" INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
+.\" WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
+.\" REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
+.\" SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
+.\" IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
+.\" RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
+.\" WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+.\" PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
+.\" SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
+.\" OF SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\" $Whistle: nghook.8,v 1.4 1999/01/20 03:19:45 archie Exp $
+.\"
+.Dd October 24, 2003
+.Dt NGHOOK 8
+.Os
+.Sh NAME
+.Nm nghook
+.Nd connect to a
+.Xr netgraph 4
+node
+.Sh SYNOPSIS
+.Nm
+.Op Fl adlnSs
+.Op Fl m Ar msg
+.Ar path
+.Op Ar hookname
+.Nm
+.Fl e
+.Op Fl n
+.Op Fl m Ar msg
+.Ar path
+.Ar hookname
+.Ar program
+.Op Ar args ...
+.Sh DESCRIPTION
+The
+.Nm
+utility creates a
+.Xr ng_socket 4
+socket type node and connects it to hook
+.Ar hookname
+of the node found at
+.Ar path .
+If
+.Ar hookname
+is omitted,
+.Dq Li debug
+is assumed.
+.Pp
+If the
+.Fl e
+option is given, the third argument is interpreted as the path to a program,
+and this program is executed with the remaining arguments as its arguments.
+Before executing, the program Netgraph messages (specified by the
+.Fl m
+option) are sent to the node.
+The program is executed with its standard input (unless closed by
+.Fl n )
+and output connected to the hook.
+.Pp
+If the
+.Fl e
+option is not given, all data written to standard input is sent
+to the node, and all data received from the node is relayed
+to standard output.
+Messages specified with
+.Fl m
+are sent to the node before the loop is entered.
+The
+.Nm
+utility exits when
+.Dv EOF
+is detected on standard input in this case.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl a
+Output each packet read in human-readable decoded
+.Tn ASCII
+form instead of raw binary.
+.It Fl d
+Increase the debugging verbosity level.
+.It Fl e
+Execute the program specified by the third argument.
+.It Fl l
+Loops all received data back to the hook in addition to writing it
+to standard output.
+.It Fl m Ar msg
+Before executing the program (in
+.Fl e
+mode) send the given ASCII control message to the node.
+This option may be given more than once.
+.It Fl n
+Do not attempt to read any data from standard input.
+The
+.Nm
+utility will continue reading from the node until stopped by a signal.
+.It Fl S
+Use file descriptor 0 for output instead of the default 1.
+.It Fl s
+Use file descriptor 1 for input instead of the default 0.
+.El
+.Sh SEE ALSO
+.Xr netgraph 3 ,
+.Xr netgraph 4 ,
+.Xr ngctl 8
+.Sh HISTORY
+The
+.Nm netgraph
+system was designed and first implemented at Whistle Communications, Inc.\&
+in a version of
+.Fx 2.2
+customized for the Whistle InterJet.
+.Sh AUTHORS
+.An Archie Cobbs Aq Mt archie@whistle.com
+.Sh BUGS
+Although all input is read in unbuffered mode,
+there is no way to control the packetization of the input.
+.Pp
+If the node sends a response to a message (specified by
+.Fl m ) ,
+this response is lost.
diff --git a/usr.sbin/nmtree/Makefile b/usr.sbin/nmtree/Makefile
new file mode 100644
index 0000000..03af6d6
--- /dev/null
+++ b/usr.sbin/nmtree/Makefile
@@ -0,0 +1,26 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+.PATH: ${.CURDIR}/../../contrib/mtree
+
+PROG= mtree
+MAN= mtree.5 mtree.8
+SRCS= compare.c crc.c create.c excludes.c getid.c misc.c mtree.c \
+ only.c spec.c specspec.c verify.c
+
+CFLAGS+= -I${.CURDIR}/../../contrib/mknod
+.PATH: ${.CURDIR}/../../contrib/mknod
+SRCS+= pack_dev.c
+
+CFLAGS+= -I${.CURDIR}/../../lib/libnetbsd
+LIBADD= netbsd md util
+
+LINKS= ${BINDIR}/mtree ${BINDIR}/nmtree
+MLINKS= mtree.8 nmtree.8
+
+.if ${MK_TESTS} != "no"
+SUBDIR+= tests
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/nmtree/Makefile.depend b/usr.sbin/nmtree/Makefile.depend
new file mode 100644
index 0000000..87dbfec
--- /dev/null
+++ b/usr.sbin/nmtree/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libmd \
+ lib/libnetbsd \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/nmtree/mtree.5 b/usr.sbin/nmtree/mtree.5
new file mode 100644
index 0000000..afcdae8
--- /dev/null
+++ b/usr.sbin/nmtree/mtree.5
@@ -0,0 +1,264 @@
+.\" Copyright (c) 1989, 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.
+.\"
+.\" From: @(#)mtree.8 8.2 (Berkeley) 12/11/93
+.\" $FreeBSD$
+.\"
+.Dd December 31, 2007
+.Dt MTREE 5
+.Os
+.Sh NAME
+.Nm mtree
+.Nd format of mtree dir hierarchy files
+.Sh DESCRIPTION
+The
+.Nm
+format is a textual format that describes a collection of filesystem objects.
+Such files are typically used to create or verify directory hierarchies.
+.Ss General Format
+An
+.Nm
+file consists of a series of lines, each providing information
+about a single filesystem object.
+Leading whitespace is always ignored.
+.Pp
+When encoding file or pathnames, any backslash character or
+character outside of the 95 printable ASCII characters must be
+encoded as a backslash followed by three
+octal digits.
+When reading mtree files, any appearance of a backslash
+followed by three octal digits should be converted into the
+corresponding character.
+.Pp
+Each line is interpreted independently as one of the following types:
+.Bl -tag -width Cm
+.It Signature
+The first line of any mtree file must begin with
+.Dq #mtree .
+If a file contains any full path entries, the first line should
+begin with
+.Dq #mtree v2.0 ,
+otherwise, the first line should begin with
+.Dq #mtree v1.0 .
+.It Blank
+Blank lines are ignored.
+.It Comment
+Lines beginning with
+.Cm #
+are ignored.
+.It Special
+Lines beginning with
+.Cm /
+are special commands that influence
+the interpretation of later lines.
+.It Relative
+If the first whitespace-delimited word has no
+.Cm /
+characters,
+it is the name of a file in the current directory.
+Any relative entry that describes a directory changes the
+current directory.
+.It dot-dot
+As a special case, a relative entry with the filename
+.Pa ..
+changes the current directory to the parent directory.
+Options on dot-dot entries are always ignored.
+.It Full
+If the first whitespace-delimited word has a
+.Cm /
+character after
+the first character, it is the pathname of a file relative to the
+starting directory.
+There can be multiple full entries describing the same file.
+.El
+.Pp
+Some tools that process
+.Nm
+files may require that multiple lines describing the same file
+occur consecutively.
+It is not permitted for the same file to be mentioned using
+both a relative and a full file specification.
+.Ss Special commands
+Two special commands are currently defined:
+.Bl -tag -width Cm
+.It Cm /set
+This command defines default values for one or more keywords.
+It is followed on the same line by one or more whitespace-separated
+keyword definitions.
+These definitions apply to all following files that do not specify
+a value for that keyword.
+.It Cm /unset
+This command removes any default value set by a previous
+.Cm /set
+command.
+It is followed on the same line by one or more keywords
+separated by whitespace.
+.El
+.Ss Keywords
+After the filename, a full or relative entry consists of zero
+or more whitespace-separated keyword definitions.
+Each such definitions consists of a key from the following
+list immediately followed by an '=' sign
+and a value.
+Software programs reading mtree files should warn about
+unrecognized keywords.
+.Pp
+Currently supported keywords are as follows:
+.Bl -tag -width Cm
+.It Cm cksum
+The checksum of the file using the default algorithm specified by
+the
+.Xr cksum 1
+utility.
+.It Cm contents
+The full pathname of a file whose contents should be
+compared to the contents of this file.
+.It Cm flags
+The file flags as a symbolic name.
+See
+.Xr chflags 1
+for information on these names.
+If no flags are to be set the string
+.Dq none
+may be used to override the current default.
+.It Cm ignore
+Ignore any file hierarchy below this file.
+.It Cm gid
+The file group as a numeric value.
+.It Cm gname
+The file group as a symbolic name.
+.It Cm md5
+The MD5 message digest of the file.
+.It Cm md5digest
+A synonym for
+.Cm md5 .
+.It Cm sha1
+The
+.Tn FIPS
+160-1
+.Pq Dq Tn SHA-1
+message digest of the file.
+.It Cm sha1digest
+A synonym for
+.Cm sha1 .
+.It Cm sha256
+The
+.Tn FIPS
+180-2
+.Pq Dq Tn SHA-256
+message digest of the file.
+.It Cm sha256digest
+A synonym for
+.Cm sha256 .
+.It Cm ripemd160digest
+The
+.Tn RIPEMD160
+message digest of the file.
+.It Cm rmd160
+A synonym for
+.Cm ripemd160digest .
+.It Cm rmd160digest
+A synonym for
+.Cm ripemd160digest .
+.It Cm mode
+The current file's permissions as a numeric (octal) or symbolic
+value.
+.It Cm nlink
+The number of hard links the file is expected to have.
+.It Cm nochange
+Make sure this file or directory exists but otherwise ignore all attributes.
+.It Cm uid
+The file owner as a numeric value.
+.It Cm uname
+The file owner as a symbolic name.
+.It Cm size
+The size, in bytes, of the file.
+.It Cm link
+The file the symbolic link is expected to reference.
+.It Cm time
+The last modification time of the file, in seconds and nanoseconds.
+The value should include a period character and exactly nine digits
+after the period.
+.It Cm type
+The type of the file; may be set to any one of the following:
+.Pp
+.Bl -tag -width Cm -compact
+.It Cm block
+block special device
+.It Cm char
+character special device
+.It Cm dir
+directory
+.It Cm fifo
+fifo
+.It Cm file
+regular file
+.It Cm link
+symbolic link
+.It Cm socket
+socket
+.El
+.El
+.Sh SEE ALSO
+.Xr cksum 1 ,
+.Xr find 1 ,
+.Xr mtree 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.3 Reno .
+The
+.Tn MD5
+digest capability was added in
+.Fx 2.1 ,
+in response to the widespread use of programs which can spoof
+.Xr cksum 1 .
+The
+.Tn SHA-1
+and
+.Tn RIPEMD160
+digests were added in
+.Fx 4.0 ,
+as new attacks have demonstrated weaknesses in
+.Tn MD5 .
+The
+.Tn SHA-256
+digest was added in
+.Fx 6.0 .
+Support for file flags was added in
+.Fx 4.0 ,
+and mostly comes from
+.Nx .
+The
+.Dq full
+entry format was added by
+.Nx .
+.Sh BUGS
+The requirement for a
+.Dq #mtree
+signature line is new and not yet widely implemented.
diff --git a/usr.sbin/nmtree/tests/Makefile b/usr.sbin/nmtree/tests/Makefile
new file mode 100644
index 0000000..0b63127
--- /dev/null
+++ b/usr.sbin/nmtree/tests/Makefile
@@ -0,0 +1,30 @@
+# $FreeBSD$
+
+TESTSRC= ${SRCTOP}/contrib/netbsd-tests/usr.sbin/mtree
+.PATH: ${TESTSRC}
+
+ATF_TESTS_SH= nmtree_test
+ATF_TESTS_SH_SRC_nmtree_test= t_mtree.sh
+
+FILESDIR= ${TESTSDIR}
+
+# NOTE: the output from FreeBSD's nmtree displays sha256digest instead of
+# sha256; we need to mangle the specfiles to reflect this.
+.for f in mtree_d_create.out netbsd6_d_create.out
+CLEANFILES+= $f $f.tmp
+FILES+= $f
+$f: ${TESTSRC}/$f
+ sed -e 's/sha256/sha256digest/g' < ${.ALLSRC} > ${.TARGET}.tmp
+ mv ${.TARGET}.tmp ${.TARGET}
+.endfor
+
+FILES+= d_convert.in
+FILES+= d_convert_C.out
+FILES+= d_convert_C_S.out
+FILES+= d_convert_D.out
+FILES+= d_convert_D_S.out
+FILES+= d_merge.in
+FILES+= d_merge_C_M.out
+FILES+= d_merge_C_M_S.out
+
+.include <bsd.test.mk>
diff --git a/usr.sbin/nologin/Makefile b/usr.sbin/nologin/Makefile
new file mode 100644
index 0000000..0b4c0db
--- /dev/null
+++ b/usr.sbin/nologin/Makefile
@@ -0,0 +1,16 @@
+# @(#)Makefile 8.2 (Berkeley) 4/22/94
+# $FreeBSD$
+
+PROG= nologin
+MAN= nologin.5 nologin.8
+
+SYMLINKS= ${BINDIR}/nologin /sbin/nologin
+
+# It is important that nologin be statically linked for security
+# reasons. A dynamic non-setuid binary can be linked against a trojan
+# libc by setting LD_LIBRARY_PATH appropriately. Both sshd(8) and
+# login(1) make it possible to log in with an unsanitized environment,
+# rendering a dynamic nologin binary virtually useless.
+NO_SHARED= YES
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/nologin/Makefile.depend b/usr.sbin/nologin/Makefile.depend
new file mode 100644
index 0000000..9cb890b
--- /dev/null
+++ b/usr.sbin/nologin/Makefile.depend
@@ -0,0 +1,17 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/nologin/nologin.5 b/usr.sbin/nologin/nologin.5
new file mode 100644
index 0000000..f8a21be
--- /dev/null
+++ b/usr.sbin/nologin/nologin.5
@@ -0,0 +1,96 @@
+.\" 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.
+.\"
+.\" @(#)nologin.8 8.1 (Berkeley) 6/19/93
+.\" $FreeBSD$
+.\"
+.Dd May 10, 2007
+.Dt NOLOGIN 5
+.Os
+.Sh NAME
+.Nm nologin
+.Nd disallow logins
+.Sh DESCRIPTION
+Programs such as
+.Xr login 1
+disallow logins if the
+.Nm
+file exists.
+The programs display the contents of
+.Nm
+to the user if possible and interrupt the login sequence.
+This makes it simple to temporarily prevent incoming logins systemwide.
+.Pp
+To disable logins on a per-account basis,
+investigate
+.Xr nologin 8 .
+.Sh SECURITY
+The
+.Nm
+file is ignored for user root by default.
+.Sh IMPLEMENTATION NOTES
+The
+.Nm
+feature is implemented through
+.Xr login.conf 5 ,
+which allows to change the pathname of the
+file and to extend the list of users
+exempt from temporary login restriction.
+.Pp
+PAM-aware programs can be selectively configured to respect
+.Nm
+using the
+.Xr pam_nologin 8
+module via
+.Xr pam.conf 5 .
+.Pp
+The
+.Nm
+file will be removed at system boot if it resides in
+.Pa /var/run
+and
+.Va cleanvar_enable
+is set to
+.Dq Li YES
+in
+.Xr rc.conf 5 ,
+which is default.
+Therefore system reboot can effectively re-enable logins.
+.Sh FILES
+.Bl -tag -width ".Pa /var/run/nologin" -compact
+.It Pa /var/run/nologin
+default location of
+.Nm
+.El
+.Sh SEE ALSO
+.Xr login 1 ,
+.Xr login.conf 5 ,
+.Xr pam.conf 5 ,
+.Xr rc.conf 5 ,
+.Xr nologin 8 ,
+.Xr pam_nologin 8 ,
+.Xr shutdown 8
diff --git a/usr.sbin/nologin/nologin.8 b/usr.sbin/nologin/nologin.8
new file mode 100644
index 0000000..d03aa33
--- /dev/null
+++ b/usr.sbin/nologin/nologin.8
@@ -0,0 +1,57 @@
+.\" 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.
+.\"
+.\" @(#)nologin.8 8.1 (Berkeley) 6/19/93
+.\" $FreeBSD$
+.\"
+.Dd June 19, 1993
+.Dt NOLOGIN 8
+.Os
+.Sh NAME
+.Nm nologin
+.Nd politely refuse a login
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+The
+.Nm
+utility displays a message that an account is not available and
+exits non-zero.
+It is intended as a replacement shell field for accounts that
+have been disabled.
+.Pp
+To disable all logins,
+investigate
+.Xr nologin 5 .
+.Sh SEE ALSO
+.Xr login 1 ,
+.Xr nologin 5
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.4 .
diff --git a/usr.sbin/nologin/nologin.c b/usr.sbin/nologin/nologin.c
new file mode 100644
index 0000000..658637e
--- /dev/null
+++ b/usr.sbin/nologin/nologin.c
@@ -0,0 +1,51 @@
+/*-
+ * Copyright (c) 2004 The FreeBSD 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.
+ *
+ * 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 <stdio.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#define MESSAGE "This account is currently not available.\n"
+
+int
+main(__unused int argc, __unused char *argv[])
+{
+ const char *user, *tt;
+
+ if ((tt = ttyname(0)) == NULL)
+ tt = "UNKNOWN";
+ if ((user = getlogin()) == NULL)
+ user = "UNKNOWN";
+ openlog("nologin", LOG_CONS, LOG_AUTH);
+ syslog(LOG_CRIT, "Attempted login by %s on %s", user, tt);
+ closelog();
+
+ printf("%s", MESSAGE);
+ return 1;
+}
diff --git a/usr.sbin/nscd/Makefile b/usr.sbin/nscd/Makefile
new file mode 100644
index 0000000..96a2e8a
--- /dev/null
+++ b/usr.sbin/nscd/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+PROG= nscd
+MAN= nscd.conf.5 nscd.8
+
+WARNS?= 3
+SRCS= agent.c nscd.c nscdcli.c cachelib.c cacheplcs.c debug.c log.c \
+ config.c query.c mp_ws_query.c mp_rs_query.c singletons.c protocol.c \
+ parser.c
+CFLAGS+= -DCONFIG_PATH="\"${PREFIX}/etc/nscd.conf\""
+
+LIBADD= util pthread
+
+.PATH: ${.CURDIR}/agents
+.include "${.CURDIR}/agents/Makefile.inc"
+.include <bsd.prog.mk>
diff --git a/usr.sbin/nscd/Makefile.depend b/usr.sbin/nscd/Makefile.depend
new file mode 100644
index 0000000..eec69a4
--- /dev/null
+++ b/usr.sbin/nscd/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libthr \
+ lib/libutil \
+ lib/msun \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/nscd/agent.c b/usr.sbin/nscd/agent.c
new file mode 100644
index 0000000..55b1565
--- /dev/null
+++ b/usr.sbin/nscd/agent.c
@@ -0,0 +1,127 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "agent.h"
+#include "debug.h"
+
+static int
+agent_cmp_func(const void *a1, const void *a2)
+{
+ struct agent const *ap1 = *((struct agent const **)a1);
+ struct agent const *ap2 = *((struct agent const **)a2);
+ int res;
+
+ res = strcmp(ap1->name, ap2->name);
+ if (res == 0) {
+ if (ap1->type == ap2->type)
+ res = 0;
+ else if (ap1->type < ap2->type)
+ res = -1;
+ else
+ res = 1;
+ }
+
+ return (res);
+}
+
+struct agent_table *
+init_agent_table(void)
+{
+ struct agent_table *retval;
+
+ TRACE_IN(init_agent_table);
+ retval = calloc(1, sizeof(*retval));
+ assert(retval != NULL);
+
+ TRACE_OUT(init_agent_table);
+ return (retval);
+}
+
+void
+register_agent(struct agent_table *at, struct agent *a)
+{
+ struct agent **new_agents;
+ size_t new_agents_num;
+
+ TRACE_IN(register_agent);
+ assert(at != NULL);
+ assert(a != NULL);
+ new_agents_num = at->agents_num + 1;
+ new_agents = malloc(sizeof(*new_agents) *
+ new_agents_num);
+ assert(new_agents != NULL);
+ memcpy(new_agents, at->agents, at->agents_num * sizeof(struct agent *));
+ new_agents[new_agents_num - 1] = a;
+ qsort(new_agents, new_agents_num, sizeof(struct agent *),
+ agent_cmp_func);
+
+ free(at->agents);
+ at->agents = new_agents;
+ at->agents_num = new_agents_num;
+ TRACE_OUT(register_agent);
+}
+
+struct agent *
+find_agent(struct agent_table *at, const char *name, enum agent_type type)
+{
+ struct agent **res;
+ struct agent model, *model_p;
+
+ TRACE_IN(find_agent);
+ model.name = (char *)name;
+ model.type = type;
+ model_p = &model;
+ res = bsearch(&model_p, at->agents, at->agents_num,
+ sizeof(struct agent *), agent_cmp_func);
+
+ TRACE_OUT(find_agent);
+ return ( res == NULL ? NULL : *res);
+}
+
+void
+destroy_agent_table(struct agent_table *at)
+{
+ size_t i;
+
+ TRACE_IN(destroy_agent_table);
+ assert(at != NULL);
+ for (i = 0; i < at->agents_num; ++i) {
+ free(at->agents[i]->name);
+ free(at->agents[i]);
+ }
+
+ free(at->agents);
+ free(at);
+ TRACE_OUT(destroy_agent_table);
+}
diff --git a/usr.sbin/nscd/agent.h b/usr.sbin/nscd/agent.h
new file mode 100644
index 0000000..57078c6
--- /dev/null
+++ b/usr.sbin/nscd/agent.h
@@ -0,0 +1,71 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 __NSCD_AGENT_H__
+#define __NSCD_AGENT_H__
+
+/*
+ * Agents are used to perform the actual lookups from the caching daemon.
+ * There are two types of daemons: for common requests and for multipart
+ * requests.
+ * All agents are stored in the agents table, which is the singleton.
+ */
+
+enum agent_type {
+ COMMON_AGENT = 0,
+ MULTIPART_AGENT = 1
+};
+
+struct agent {
+ char *name;
+ enum agent_type type;
+};
+
+struct common_agent {
+ struct agent parent;
+ int (*lookup_func)(const char *, size_t, char **, size_t *);
+};
+
+struct multipart_agent {
+ struct agent parent;
+ void *(*mp_init_func)(void);
+ int (*mp_lookup_func)(char **, size_t *, void *);
+ void (*mp_destroy_func)(void *);
+};
+
+struct agent_table {
+ struct agent **agents;
+ size_t agents_num;
+};
+
+struct agent_table *init_agent_table(void);
+void register_agent(struct agent_table *, struct agent *);
+struct agent *find_agent(struct agent_table *, const char *, enum agent_type);
+void destroy_agent_table(struct agent_table *);
+
+#endif
diff --git a/usr.sbin/nscd/agents/Makefile.inc b/usr.sbin/nscd/agents/Makefile.inc
new file mode 100644
index 0000000..1be32e1
--- /dev/null
+++ b/usr.sbin/nscd/agents/Makefile.inc
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+SRCS += passwd.c group.c services.c
diff --git a/usr.sbin/nscd/agents/group.c b/usr.sbin/nscd/agents/group.c
new file mode 100644
index 0000000..92be7ed
--- /dev/null
+++ b/usr.sbin/nscd/agents/group.c
@@ -0,0 +1,260 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <assert.h>
+#include <grp.h>
+#include <nsswitch.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../debug.h"
+#include "group.h"
+
+static int group_marshal_func(struct group *, char *, size_t *);
+static int group_lookup_func(const char *, size_t, char **, size_t *);
+static void *group_mp_init_func(void);
+static int group_mp_lookup_func(char **, size_t *, void *);
+static void group_mp_destroy_func(void *);
+
+static int
+group_marshal_func(struct group *grp, char *buffer, size_t *buffer_size)
+{
+ struct group new_grp;
+ size_t desired_size, size, mem_size;
+ char *p, **mem;
+
+ TRACE_IN(group_marshal_func);
+ desired_size = ALIGNBYTES + sizeof(struct group) + sizeof(char *);
+
+ if (grp->gr_name != NULL)
+ desired_size += strlen(grp->gr_name) + 1;
+ if (grp->gr_passwd != NULL)
+ desired_size += strlen(grp->gr_passwd) + 1;
+
+ if (grp->gr_mem != NULL) {
+ mem_size = 0;
+ for (mem = grp->gr_mem; *mem; ++mem) {
+ desired_size += strlen(*mem) + 1;
+ ++mem_size;
+ }
+
+ desired_size += ALIGNBYTES + (mem_size + 1) * sizeof(char *);
+ }
+
+ if ((desired_size > *buffer_size) || (buffer == NULL)) {
+ *buffer_size = desired_size;
+ TRACE_OUT(group_marshal_func);
+ return (NS_RETURN);
+ }
+
+ memcpy(&new_grp, grp, sizeof(struct group));
+ memset(buffer, 0, desired_size);
+
+ *buffer_size = desired_size;
+ p = buffer + sizeof(struct group) + sizeof(char *);
+ memcpy(buffer + sizeof(struct group), &p, sizeof(char *));
+ p = (char *)ALIGN(p);
+
+ if (new_grp.gr_name != NULL) {
+ size = strlen(new_grp.gr_name);
+ memcpy(p, new_grp.gr_name, size);
+ new_grp.gr_name = p;
+ p += size + 1;
+ }
+
+ if (new_grp.gr_passwd != NULL) {
+ size = strlen(new_grp.gr_passwd);
+ memcpy(p, new_grp.gr_passwd, size);
+ new_grp.gr_passwd = p;
+ p += size + 1;
+ }
+
+ if (new_grp.gr_mem != NULL) {
+ p = (char *)ALIGN(p);
+ memcpy(p, new_grp.gr_mem, sizeof(char *) * mem_size);
+ new_grp.gr_mem = (char **)p;
+ p += sizeof(char *) * (mem_size + 1);
+
+ for (mem = new_grp.gr_mem; *mem; ++mem) {
+ size = strlen(*mem);
+ memcpy(p, *mem, size);
+ *mem = p;
+ p += size + 1;
+ }
+ }
+
+ memcpy(buffer, &new_grp, sizeof(struct group));
+ TRACE_OUT(group_marshal_func);
+ return (NS_SUCCESS);
+}
+
+static int
+group_lookup_func(const char *key, size_t key_size, char **buffer,
+ size_t *buffer_size)
+{
+ enum nss_lookup_type lookup_type;
+ char *name;
+ size_t size;
+ gid_t gid;
+
+ struct group *result;
+
+ TRACE_IN(group_lookup_func);
+ assert(buffer != NULL);
+ assert(buffer_size != NULL);
+
+ if (key_size < sizeof(enum nss_lookup_type)) {
+ TRACE_OUT(group_lookup_func);
+ return (NS_UNAVAIL);
+ }
+ memcpy(&lookup_type, key, sizeof(enum nss_lookup_type));
+
+ switch (lookup_type) {
+ case nss_lt_name:
+ size = key_size - sizeof(enum nss_lookup_type) + 1;
+ name = calloc(1, size);
+ assert(name != NULL);
+ memcpy(name, key + sizeof(enum nss_lookup_type), size - 1);
+ break;
+ case nss_lt_id:
+ if (key_size < sizeof(enum nss_lookup_type) +
+ sizeof(gid_t)) {
+ TRACE_OUT(passwd_lookup_func);
+ return (NS_UNAVAIL);
+ }
+
+ memcpy(&gid, key + sizeof(enum nss_lookup_type), sizeof(gid_t));
+ break;
+ default:
+ TRACE_OUT(group_lookup_func);
+ return (NS_UNAVAIL);
+ }
+
+ switch (lookup_type) {
+ case nss_lt_name:
+ TRACE_STR(name);
+ result = getgrnam(name);
+ free(name);
+ break;
+ case nss_lt_id:
+ result = getgrgid(gid);
+ break;
+ default:
+ /* SHOULD NOT BE REACHED */
+ break;
+ }
+
+ if (result != NULL) {
+ group_marshal_func(result, NULL, buffer_size);
+ *buffer = malloc(*buffer_size);
+ assert(*buffer != NULL);
+ group_marshal_func(result, *buffer, buffer_size);
+ }
+
+ TRACE_OUT(group_lookup_func);
+ return (result == NULL ? NS_NOTFOUND : NS_SUCCESS);
+}
+
+static void *
+group_mp_init_func(void)
+{
+ TRACE_IN(group_mp_init_func);
+ setgrent();
+ TRACE_OUT(group_mp_init_func);
+
+ return (NULL);
+}
+
+static int
+group_mp_lookup_func(char **buffer, size_t *buffer_size, void *mdata)
+{
+ struct group *result;
+
+ TRACE_IN(group_mp_lookup_func);
+ result = getgrent();
+ if (result != NULL) {
+ group_marshal_func(result, NULL, buffer_size);
+ *buffer = malloc(*buffer_size);
+ assert(*buffer != NULL);
+ group_marshal_func(result, *buffer, buffer_size);
+ }
+
+ TRACE_OUT(group_mp_lookup_func);
+ return (result == NULL ? NS_NOTFOUND : NS_SUCCESS);
+}
+
+static void
+group_mp_destroy_func(void *mdata)
+{
+ TRACE_IN(group_mp_destroy_func);
+ TRACE_OUT(group_mp_destroy_func);
+}
+
+struct agent *
+init_group_agent(void)
+{
+ struct common_agent *retval;
+
+ TRACE_IN(init_group_agent);
+ retval = calloc(1, sizeof(*retval));
+ assert(retval != NULL);
+
+ retval->parent.name = strdup("group");
+ assert(retval->parent.name != NULL);
+
+ retval->parent.type = COMMON_AGENT;
+ retval->lookup_func = group_lookup_func;
+
+ TRACE_OUT(init_group_agent);
+ return ((struct agent *)retval);
+}
+
+struct agent *
+init_group_mp_agent(void)
+{
+ struct multipart_agent *retval;
+
+ TRACE_IN(init_group_mp_agent);
+ retval = calloc(1,
+ sizeof(*retval));
+ assert(retval != NULL);
+
+ retval->parent.name = strdup("group");
+ retval->parent.type = MULTIPART_AGENT;
+ retval->mp_init_func = group_mp_init_func;
+ retval->mp_lookup_func = group_mp_lookup_func;
+ retval->mp_destroy_func = group_mp_destroy_func;
+ assert(retval->parent.name != NULL);
+
+ TRACE_OUT(init_group_mp_agent);
+ return ((struct agent *)retval);
+}
diff --git a/usr.sbin/nscd/agents/group.h b/usr.sbin/nscd/agents/group.h
new file mode 100644
index 0000000..15e1097
--- /dev/null
+++ b/usr.sbin/nscd/agents/group.h
@@ -0,0 +1,32 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 "../agent.h"
+
+struct agent *init_group_agent(void);
+struct agent *init_group_mp_agent(void);
diff --git a/usr.sbin/nscd/agents/passwd.c b/usr.sbin/nscd/agents/passwd.c
new file mode 100644
index 0000000..247444f
--- /dev/null
+++ b/usr.sbin/nscd/agents/passwd.c
@@ -0,0 +1,268 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <assert.h>
+#include <nsswitch.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../debug.h"
+#include "passwd.h"
+
+static int passwd_marshal_func(struct passwd *, char *, size_t *);
+static int passwd_lookup_func(const char *, size_t, char **, size_t *);
+static void *passwd_mp_init_func(void);
+static int passwd_mp_lookup_func(char **, size_t *, void *);
+static void passwd_mp_destroy_func(void *mdata);
+
+static int
+passwd_marshal_func(struct passwd *pwd, char *buffer, size_t *buffer_size)
+{
+ char *p;
+ struct passwd new_pwd;
+ size_t desired_size, size;
+
+ TRACE_IN(passwd_marshal_func);
+ desired_size = sizeof(struct passwd) + sizeof(char *) +
+ strlen(pwd->pw_name) + 1;
+ if (pwd->pw_passwd != NULL)
+ desired_size += strlen(pwd->pw_passwd) + 1;
+ if (pwd->pw_class != NULL)
+ desired_size += strlen(pwd->pw_class) + 1;
+ if (pwd->pw_gecos != NULL)
+ desired_size += strlen(pwd->pw_gecos) + 1;
+ if (pwd->pw_dir != NULL)
+ desired_size += strlen(pwd->pw_dir) + 1;
+ if (pwd->pw_shell != NULL)
+ desired_size += strlen(pwd->pw_shell) + 1;
+
+ if ((*buffer_size < desired_size) || (buffer == NULL)) {
+ *buffer_size = desired_size;
+ TRACE_OUT(passwd_marshal_func);
+ return (NS_RETURN);
+ }
+
+ memcpy(&new_pwd, pwd, sizeof(struct passwd));
+ memset(buffer, 0, desired_size);
+
+ *buffer_size = desired_size;
+ p = buffer + sizeof(struct passwd) + sizeof(char *);
+ memcpy(buffer + sizeof(struct passwd), &p, sizeof(char *));
+
+ if (new_pwd.pw_name != NULL) {
+ size = strlen(new_pwd.pw_name);
+ memcpy(p, new_pwd.pw_name, size);
+ new_pwd.pw_name = p;
+ p += size + 1;
+ }
+
+ if (new_pwd.pw_passwd != NULL) {
+ size = strlen(new_pwd.pw_passwd);
+ memcpy(p, new_pwd.pw_passwd, size);
+ new_pwd.pw_passwd = p;
+ p += size + 1;
+ }
+
+ if (new_pwd.pw_class != NULL) {
+ size = strlen(new_pwd.pw_class);
+ memcpy(p, new_pwd.pw_class, size);
+ new_pwd.pw_class = p;
+ p += size + 1;
+ }
+
+ if (new_pwd.pw_gecos != NULL) {
+ size = strlen(new_pwd.pw_gecos);
+ memcpy(p, new_pwd.pw_gecos, size);
+ new_pwd.pw_gecos = p;
+ p += size + 1;
+ }
+
+ if (new_pwd.pw_dir != NULL) {
+ size = strlen(new_pwd.pw_dir);
+ memcpy(p, new_pwd.pw_dir, size);
+ new_pwd.pw_dir = p;
+ p += size + 1;
+ }
+
+ if (new_pwd.pw_shell != NULL) {
+ size = strlen(new_pwd.pw_shell);
+ memcpy(p, new_pwd.pw_shell, size);
+ new_pwd.pw_shell = p;
+ p += size + 1;
+ }
+
+ memcpy(buffer, &new_pwd, sizeof(struct passwd));
+ TRACE_OUT(passwd_marshal_func);
+ return (NS_SUCCESS);
+}
+
+static int
+passwd_lookup_func(const char *key, size_t key_size, char **buffer,
+ size_t *buffer_size)
+{
+ enum nss_lookup_type lookup_type;
+ char *login;
+ size_t size;
+ uid_t uid;
+
+ struct passwd *result;
+
+ TRACE_IN(passwd_lookup_func);
+ assert(buffer != NULL);
+ assert(buffer_size != NULL);
+
+ if (key_size < sizeof(enum nss_lookup_type)) {
+ TRACE_OUT(passwd_lookup_func);
+ return (NS_UNAVAIL);
+ }
+ memcpy(&lookup_type, key, sizeof(enum nss_lookup_type));
+
+ switch (lookup_type) {
+ case nss_lt_name:
+ size = key_size - sizeof(enum nss_lookup_type) + 1;
+ login = calloc(1, size);
+ assert(login != NULL);
+ memcpy(login, key + sizeof(enum nss_lookup_type), size - 1);
+ break;
+ case nss_lt_id:
+ if (key_size < sizeof(enum nss_lookup_type) +
+ sizeof(uid_t)) {
+ TRACE_OUT(passwd_lookup_func);
+ return (NS_UNAVAIL);
+ }
+
+ memcpy(&uid, key + sizeof(enum nss_lookup_type), sizeof(uid_t));
+ break;
+ default:
+ TRACE_OUT(passwd_lookup_func);
+ return (NS_UNAVAIL);
+ }
+
+ switch (lookup_type) {
+ case nss_lt_name:
+ result = getpwnam(login);
+ free(login);
+ break;
+ case nss_lt_id:
+ result = getpwuid(uid);
+ break;
+ default:
+ /* SHOULD NOT BE REACHED */
+ break;
+ }
+
+ if (result != NULL) {
+ passwd_marshal_func(result, NULL, buffer_size);
+ *buffer = malloc(*buffer_size);
+ assert(*buffer != NULL);
+ passwd_marshal_func(result, *buffer, buffer_size);
+ }
+
+ TRACE_OUT(passwd_lookup_func);
+ return (result == NULL ? NS_NOTFOUND : NS_SUCCESS);
+}
+
+static void *
+passwd_mp_init_func(void)
+{
+ TRACE_IN(passwd_mp_init_func);
+ setpwent();
+ TRACE_OUT(passwd_mp_init_func);
+
+ return (NULL);
+}
+
+static int
+passwd_mp_lookup_func(char **buffer, size_t *buffer_size, void *mdata)
+{
+ struct passwd *result;
+
+ TRACE_IN(passwd_mp_lookup_func);
+ result = getpwent();
+ if (result != NULL) {
+ passwd_marshal_func(result, NULL, buffer_size);
+ *buffer = malloc(*buffer_size);
+ assert(*buffer != NULL);
+ passwd_marshal_func(result, *buffer, buffer_size);
+ }
+
+ TRACE_OUT(passwd_mp_lookup_func);
+ return (result == NULL ? NS_NOTFOUND : NS_SUCCESS);
+}
+
+static void
+passwd_mp_destroy_func(void *mdata)
+{
+ TRACE_IN(passwd_mp_destroy_func);
+ TRACE_OUT(passwd_mp_destroy_func);
+}
+
+struct agent *
+init_passwd_agent(void)
+{
+ struct common_agent *retval;
+
+ TRACE_IN(init_passwd_agent);
+ retval = calloc(1, sizeof(*retval));
+ assert(retval != NULL);
+
+ retval->parent.name = strdup("passwd");
+ assert(retval->parent.name != NULL);
+
+ retval->parent.type = COMMON_AGENT;
+ retval->lookup_func = passwd_lookup_func;
+
+ TRACE_OUT(init_passwd_agent);
+ return ((struct agent *)retval);
+}
+
+struct agent *
+init_passwd_mp_agent(void)
+{
+ struct multipart_agent *retval;
+
+ TRACE_IN(init_passwd_mp_agent);
+ retval = calloc(1,
+ sizeof(*retval));
+ assert(retval != NULL);
+
+ retval->parent.name = strdup("passwd");
+ retval->parent.type = MULTIPART_AGENT;
+ retval->mp_init_func = passwd_mp_init_func;
+ retval->mp_lookup_func = passwd_mp_lookup_func;
+ retval->mp_destroy_func = passwd_mp_destroy_func;
+ assert(retval->parent.name != NULL);
+
+ TRACE_OUT(init_passwd_mp_agent);
+ return ((struct agent *)retval);
+}
diff --git a/usr.sbin/nscd/agents/passwd.h b/usr.sbin/nscd/agents/passwd.h
new file mode 100644
index 0000000..112423a
--- /dev/null
+++ b/usr.sbin/nscd/agents/passwd.h
@@ -0,0 +1,32 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 "../agent.h"
+
+struct agent *init_passwd_agent(void);
+struct agent *init_passwd_mp_agent(void);
diff --git a/usr.sbin/nscd/agents/services.c b/usr.sbin/nscd/agents/services.c
new file mode 100644
index 0000000..7b02e50
--- /dev/null
+++ b/usr.sbin/nscd/agents/services.c
@@ -0,0 +1,281 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <assert.h>
+#include <netdb.h>
+#include <nsswitch.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../debug.h"
+#include "services.h"
+
+static int services_marshal_func(struct servent *, char *, size_t *);
+static int services_lookup_func(const char *, size_t, char **, size_t *);
+static void *services_mp_init_func(void);
+static int services_mp_lookup_func(char **, size_t *, void *);
+static void services_mp_destroy_func(void *);
+
+static int
+services_marshal_func(struct servent *serv, char *buffer, size_t *buffer_size)
+{
+ struct servent new_serv;
+ size_t desired_size;
+ char **alias;
+ char *p;
+ size_t size;
+ size_t aliases_size;
+
+ TRACE_IN(services_marshal_func);
+ desired_size = ALIGNBYTES + sizeof(struct servent) + sizeof(char *);
+ if (serv->s_name != NULL)
+ desired_size += strlen(serv->s_name) + 1;
+ if (serv->s_proto != NULL)
+ desired_size += strlen(serv->s_proto) + 1;
+
+ aliases_size = 0;
+ if (serv->s_aliases != NULL) {
+ for (alias = serv->s_aliases; *alias; ++alias) {
+ desired_size += strlen(*alias) + 1;
+ ++aliases_size;
+ }
+
+ desired_size += ALIGNBYTES + sizeof(char *) *
+ (aliases_size + 1);
+ }
+
+ if ((*buffer_size < desired_size) || (buffer == NULL)) {
+ *buffer_size = desired_size;
+ TRACE_OUT(services_marshal_func);
+ return (NS_RETURN);
+ }
+
+ memcpy(&new_serv, serv, sizeof(struct servent));
+ memset(buffer, 0, desired_size);
+
+ *buffer_size = desired_size;
+ p = buffer + sizeof(struct servent) + sizeof(char *);
+ memcpy(buffer + sizeof(struct servent), &p, sizeof(char *));
+ p = (char *)ALIGN(p);
+
+ if (new_serv.s_name != NULL) {
+ size = strlen(new_serv.s_name);
+ memcpy(p, new_serv.s_name, size);
+ new_serv.s_name = p;
+ p += size + 1;
+ }
+
+ if (new_serv.s_proto != NULL) {
+ size = strlen(new_serv.s_proto);
+ memcpy(p, new_serv.s_proto, size);
+ new_serv.s_proto = p;
+ p += size + 1;
+ }
+
+ if (new_serv.s_aliases != NULL) {
+ p = (char *)ALIGN(p);
+ memcpy(p, new_serv.s_aliases, sizeof(char *) * aliases_size);
+ new_serv.s_aliases = (char **)p;
+ p += sizeof(char *) * (aliases_size + 1);
+
+ for (alias = new_serv.s_aliases; *alias; ++alias) {
+ size = strlen(*alias);
+ memcpy(p, *alias, size);
+ *alias = p;
+ p += size + 1;
+ }
+ }
+
+ memcpy(buffer, &new_serv, sizeof(struct servent));
+ TRACE_OUT(services_marshal_func);
+ return (NS_SUCCESS);
+}
+
+static int
+services_lookup_func(const char *key, size_t key_size, char **buffer,
+ size_t *buffer_size)
+{
+ enum nss_lookup_type lookup_type;
+ char *name = NULL;
+ char *proto = NULL;
+ size_t size, size2;
+ int port;
+
+ struct servent *result;
+
+ TRACE_IN(services_lookup_func);
+
+ assert(buffer != NULL);
+ assert(buffer_size != NULL);
+
+ if (key_size < sizeof(enum nss_lookup_type)) {
+ TRACE_OUT(passwd_lookup_func);
+ return (NS_UNAVAIL);
+ }
+ memcpy(&lookup_type, key, sizeof(enum nss_lookup_type));
+
+ switch (lookup_type) {
+ case nss_lt_name:
+ size = key_size - sizeof(enum nss_lookup_type);
+ name = calloc(1, size + 1);
+ assert(name != NULL);
+ memcpy(name, key + sizeof(enum nss_lookup_type), size);
+
+ size2 = strlen(name) + 1;
+
+ if (size2 < size)
+ proto = name + size2;
+ else
+ proto = NULL;
+ break;
+ case nss_lt_id:
+ if (key_size < sizeof(enum nss_lookup_type) +
+ sizeof(int)) {
+ TRACE_OUT(passwd_lookup_func);
+ return (NS_UNAVAIL);
+ }
+
+ memcpy(&port, key + sizeof(enum nss_lookup_type),
+ sizeof(int));
+
+ size = key_size - sizeof(enum nss_lookup_type) - sizeof(int);
+ if (size > 0) {
+ proto = calloc(1, size + 1);
+ assert(proto != NULL);
+ memcpy(proto, key + sizeof(enum nss_lookup_type) +
+ sizeof(int), size);
+ }
+ break;
+ default:
+ TRACE_OUT(passwd_lookup_func);
+ return (NS_UNAVAIL);
+ }
+
+ switch (lookup_type) {
+ case nss_lt_name:
+ result = getservbyname(name, proto);
+ free(name);
+ break;
+ case nss_lt_id:
+ result = getservbyport(port, proto);
+ free(proto);
+ break;
+ default:
+ /* SHOULD NOT BE REACHED */
+ break;
+ }
+
+ if (result != NULL) {
+ services_marshal_func(result, NULL, buffer_size);
+ *buffer = malloc(*buffer_size);
+ assert(*buffer != NULL);
+ services_marshal_func(result, *buffer, buffer_size);
+ }
+
+ TRACE_OUT(services_lookup_func);
+ return (result == NULL ? NS_NOTFOUND : NS_SUCCESS);
+}
+
+static void *
+services_mp_init_func(void)
+{
+ TRACE_IN(services_mp_init_func);
+ setservent(0);
+ TRACE_OUT(services_mp_init_func);
+
+ return (NULL);
+}
+
+static int
+services_mp_lookup_func(char **buffer, size_t *buffer_size, void *mdata)
+{
+ struct servent *result;
+
+ TRACE_IN(services_mp_lookup_func);
+ result = getservent();
+ if (result != NULL) {
+ services_marshal_func(result, NULL, buffer_size);
+ *buffer = malloc(*buffer_size);
+ assert(*buffer != NULL);
+ services_marshal_func(result, *buffer, buffer_size);
+ }
+
+ TRACE_OUT(services_mp_lookup_func);
+ return (result == NULL ? NS_NOTFOUND : NS_SUCCESS);
+}
+
+static void
+services_mp_destroy_func(void *mdata)
+{
+ TRACE_IN(services_mp_destroy_func);
+ TRACE_OUT(services_mp_destroy_func);
+}
+
+struct agent *
+init_services_agent(void)
+{
+ struct common_agent *retval;
+ TRACE_IN(init_services_agent);
+
+ retval = calloc(1, sizeof(*retval));
+ assert(retval != NULL);
+
+ retval->parent.name = strdup("services");
+ assert(retval->parent.name != NULL);
+
+ retval->parent.type = COMMON_AGENT;
+ retval->lookup_func = services_lookup_func;
+
+ TRACE_OUT(init_services_agent);
+ return ((struct agent *)retval);
+}
+
+struct agent *
+init_services_mp_agent(void)
+{
+ struct multipart_agent *retval;
+
+ TRACE_IN(init_services_mp_agent);
+ retval = calloc(1,
+ sizeof(*retval));
+ assert(retval != NULL);
+
+ retval->parent.name = strdup("services");
+ retval->parent.type = MULTIPART_AGENT;
+ retval->mp_init_func = services_mp_init_func;
+ retval->mp_lookup_func = services_mp_lookup_func;
+ retval->mp_destroy_func = services_mp_destroy_func;
+ assert(retval->parent.name != NULL);
+
+ TRACE_OUT(init_services_mp_agent);
+ return ((struct agent *)retval);
+}
diff --git a/usr.sbin/nscd/agents/services.h b/usr.sbin/nscd/agents/services.h
new file mode 100644
index 0000000..728fe99
--- /dev/null
+++ b/usr.sbin/nscd/agents/services.h
@@ -0,0 +1,32 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 "../agent.h"
+
+struct agent *init_services_agent(void);
+struct agent *init_services_mp_agent(void);
diff --git a/usr.sbin/nscd/cachelib.c b/usr.sbin/nscd/cachelib.c
new file mode 100644
index 0000000..ab95b29
--- /dev/null
+++ b/usr.sbin/nscd/cachelib.c
@@ -0,0 +1,1244 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/time.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "cachelib.h"
+#include "debug.h"
+
+#define INITIAL_ENTRIES_CAPACITY 32
+#define ENTRIES_CAPACITY_STEP 32
+
+#define STRING_SIMPLE_HASH_BODY(in_var, var, a, M) \
+ for ((var) = 0; *(in_var) != '\0'; ++(in_var)) \
+ (var) = ((a)*(var) + *(in_var)) % (M)
+
+#define STRING_SIMPLE_MP2_HASH_BODY(in_var, var, a, M) \
+ for ((var) = 0; *(in_var) != 0; ++(in_var)) \
+ (var) = ((a)*(var) + *(in_var)) & (M - 1)
+
+static int cache_elemsize_common_continue_func(struct cache_common_entry_ *,
+ struct cache_policy_item_ *);
+static int cache_lifetime_common_continue_func(struct cache_common_entry_ *,
+ struct cache_policy_item_ *);
+static void clear_cache_entry(struct cache_entry_ *);
+static void destroy_cache_entry(struct cache_entry_ *);
+static void destroy_cache_mp_read_session(struct cache_mp_read_session_ *);
+static void destroy_cache_mp_write_session(struct cache_mp_write_session_ *);
+static int entries_bsearch_cmp_func(const void *, const void *);
+static int entries_qsort_cmp_func(const void *, const void *);
+static struct cache_entry_ ** find_cache_entry_p(struct cache_ *,
+ const char *);
+static void flush_cache_entry(struct cache_entry_ *);
+static void flush_cache_policy(struct cache_common_entry_ *,
+ struct cache_policy_ *, struct cache_policy_ *,
+ int (*)(struct cache_common_entry_ *,
+ struct cache_policy_item_ *));
+static int ht_items_cmp_func(const void *, const void *);
+static int ht_items_fixed_size_left_cmp_func(const void *, const void *);
+static hashtable_index_t ht_item_hash_func(const void *, size_t);
+
+/*
+ * Hashing and comparing routines, that are used with the hash tables
+ */
+static int
+ht_items_cmp_func(const void *p1, const void *p2)
+{
+ struct cache_ht_item_data_ *hp1, *hp2;
+ size_t min_size;
+ int result;
+
+ hp1 = (struct cache_ht_item_data_ *)p1;
+ hp2 = (struct cache_ht_item_data_ *)p2;
+
+ assert(hp1->key != NULL);
+ assert(hp2->key != NULL);
+
+ if (hp1->key_size != hp2->key_size) {
+ min_size = (hp1->key_size < hp2->key_size) ? hp1->key_size :
+ hp2->key_size;
+ result = memcmp(hp1->key, hp2->key, min_size);
+
+ if (result == 0)
+ return ((hp1->key_size < hp2->key_size) ? -1 : 1);
+ else
+ return (result);
+ } else
+ return (memcmp(hp1->key, hp2->key, hp1->key_size));
+}
+
+static int
+ht_items_fixed_size_left_cmp_func(const void *p1, const void *p2)
+{
+ struct cache_ht_item_data_ *hp1, *hp2;
+ size_t min_size;
+ int result;
+
+ hp1 = (struct cache_ht_item_data_ *)p1;
+ hp2 = (struct cache_ht_item_data_ *)p2;
+
+ assert(hp1->key != NULL);
+ assert(hp2->key != NULL);
+
+ if (hp1->key_size != hp2->key_size) {
+ min_size = (hp1->key_size < hp2->key_size) ? hp1->key_size :
+ hp2->key_size;
+ result = memcmp(hp1->key, hp2->key, min_size);
+
+ if (result == 0)
+ if (min_size == hp1->key_size)
+ return (0);
+ else
+ return ((hp1->key_size < hp2->key_size) ? -1 : 1);
+ else
+ return (result);
+ } else
+ return (memcmp(hp1->key, hp2->key, hp1->key_size));
+}
+
+static hashtable_index_t
+ht_item_hash_func(const void *p, size_t cache_entries_size)
+{
+ struct cache_ht_item_data_ *hp;
+ size_t i;
+
+ hashtable_index_t retval;
+
+ hp = (struct cache_ht_item_data_ *)p;
+ assert(hp->key != NULL);
+
+ retval = 0;
+ for (i = 0; i < hp->key_size; ++i)
+ retval = (127 * retval + (unsigned char)hp->key[i]) %
+ cache_entries_size;
+
+ return retval;
+}
+
+HASHTABLE_PROTOTYPE(cache_ht_, cache_ht_item_, struct cache_ht_item_data_);
+HASHTABLE_GENERATE(cache_ht_, cache_ht_item_, struct cache_ht_item_data_, data,
+ ht_item_hash_func, ht_items_cmp_func);
+
+/*
+ * Routines to sort and search the entries by name
+ */
+static int
+entries_bsearch_cmp_func(const void *key, const void *ent)
+{
+
+ assert(key != NULL);
+ assert(ent != NULL);
+
+ return (strcmp((char const *)key,
+ (*(struct cache_entry_ const **)ent)->name));
+}
+
+static int
+entries_qsort_cmp_func(const void *e1, const void *e2)
+{
+
+ assert(e1 != NULL);
+ assert(e2 != NULL);
+
+ return (strcmp((*(struct cache_entry_ const **)e1)->name,
+ (*(struct cache_entry_ const **)e2)->name));
+}
+
+static struct cache_entry_ **
+find_cache_entry_p(struct cache_ *the_cache, const char *entry_name)
+{
+
+ return ((struct cache_entry_ **)(bsearch(entry_name, the_cache->entries,
+ the_cache->entries_size, sizeof(struct cache_entry_ *),
+ entries_bsearch_cmp_func)));
+}
+
+static void
+destroy_cache_mp_write_session(struct cache_mp_write_session_ *ws)
+{
+
+ struct cache_mp_data_item_ *data_item;
+
+ TRACE_IN(destroy_cache_mp_write_session);
+ assert(ws != NULL);
+ while (!TAILQ_EMPTY(&ws->items)) {
+ data_item = TAILQ_FIRST(&ws->items);
+ TAILQ_REMOVE(&ws->items, data_item, entries);
+ free(data_item->value);
+ free(data_item);
+ }
+
+ free(ws);
+ TRACE_OUT(destroy_cache_mp_write_session);
+}
+
+static void
+destroy_cache_mp_read_session(struct cache_mp_read_session_ *rs)
+{
+
+ TRACE_IN(destroy_cache_mp_read_session);
+ assert(rs != NULL);
+ free(rs);
+ TRACE_OUT(destroy_cache_mp_read_session);
+}
+
+static void
+destroy_cache_entry(struct cache_entry_ *entry)
+{
+ struct cache_common_entry_ *common_entry;
+ struct cache_mp_entry_ *mp_entry;
+ struct cache_mp_read_session_ *rs;
+ struct cache_mp_write_session_ *ws;
+ struct cache_ht_item_ *ht_item;
+ struct cache_ht_item_data_ *ht_item_data;
+
+ TRACE_IN(destroy_cache_entry);
+ assert(entry != NULL);
+
+ if (entry->params->entry_type == CET_COMMON) {
+ common_entry = (struct cache_common_entry_ *)entry;
+
+ HASHTABLE_FOREACH(&(common_entry->items), ht_item) {
+ HASHTABLE_ENTRY_FOREACH(ht_item, data, ht_item_data)
+ {
+ free(ht_item_data->key);
+ free(ht_item_data->value);
+ }
+ HASHTABLE_ENTRY_CLEAR(ht_item, data);
+ }
+
+ HASHTABLE_DESTROY(&(common_entry->items), data);
+
+ /* FIFO policy is always first */
+ destroy_cache_fifo_policy(common_entry->policies[0]);
+ switch (common_entry->common_params.policy) {
+ case CPT_LRU:
+ destroy_cache_lru_policy(common_entry->policies[1]);
+ break;
+ case CPT_LFU:
+ destroy_cache_lfu_policy(common_entry->policies[1]);
+ break;
+ default:
+ break;
+ }
+ free(common_entry->policies);
+ } else {
+ mp_entry = (struct cache_mp_entry_ *)entry;
+
+ while (!TAILQ_EMPTY(&mp_entry->ws_head)) {
+ ws = TAILQ_FIRST(&mp_entry->ws_head);
+ TAILQ_REMOVE(&mp_entry->ws_head, ws, entries);
+ destroy_cache_mp_write_session(ws);
+ }
+
+ while (!TAILQ_EMPTY(&mp_entry->rs_head)) {
+ rs = TAILQ_FIRST(&mp_entry->rs_head);
+ TAILQ_REMOVE(&mp_entry->rs_head, rs, entries);
+ destroy_cache_mp_read_session(rs);
+ }
+
+ if (mp_entry->completed_write_session != NULL)
+ destroy_cache_mp_write_session(
+ mp_entry->completed_write_session);
+
+ if (mp_entry->pending_write_session != NULL)
+ destroy_cache_mp_write_session(
+ mp_entry->pending_write_session);
+ }
+
+ free(entry->name);
+ free(entry);
+ TRACE_OUT(destroy_cache_entry);
+}
+
+static void
+clear_cache_entry(struct cache_entry_ *entry)
+{
+ struct cache_mp_entry_ *mp_entry;
+ struct cache_common_entry_ *common_entry;
+ struct cache_ht_item_ *ht_item;
+ struct cache_ht_item_data_ *ht_item_data;
+ struct cache_policy_ *policy;
+ struct cache_policy_item_ *item, *next_item;
+ size_t entry_size;
+ unsigned int i;
+
+ if (entry->params->entry_type == CET_COMMON) {
+ common_entry = (struct cache_common_entry_ *)entry;
+
+ entry_size = 0;
+ HASHTABLE_FOREACH(&(common_entry->items), ht_item) {
+ HASHTABLE_ENTRY_FOREACH(ht_item, data, ht_item_data)
+ {
+ free(ht_item_data->key);
+ free(ht_item_data->value);
+ }
+ entry_size += HASHTABLE_ENTRY_SIZE(ht_item, data);
+ HASHTABLE_ENTRY_CLEAR(ht_item, data);
+ }
+
+ common_entry->items_size -= entry_size;
+ for (i = 0; i < common_entry->policies_size; ++i) {
+ policy = common_entry->policies[i];
+
+ next_item = NULL;
+ item = policy->get_first_item_func(policy);
+ while (item != NULL) {
+ next_item = policy->get_next_item_func(policy,
+ item);
+ policy->remove_item_func(policy, item);
+ policy->destroy_item_func(item);
+ item = next_item;
+ }
+ }
+ } else {
+ mp_entry = (struct cache_mp_entry_ *)entry;
+
+ if (mp_entry->rs_size == 0) {
+ if (mp_entry->completed_write_session != NULL) {
+ destroy_cache_mp_write_session(
+ mp_entry->completed_write_session);
+ mp_entry->completed_write_session = NULL;
+ }
+
+ memset(&mp_entry->creation_time, 0,
+ sizeof(struct timeval));
+ memset(&mp_entry->last_request_time, 0,
+ sizeof(struct timeval));
+ }
+ }
+}
+
+/*
+ * When passed to the flush_cache_policy, ensures that all old elements are
+ * deleted.
+ */
+static int
+cache_lifetime_common_continue_func(struct cache_common_entry_ *entry,
+ struct cache_policy_item_ *item)
+{
+
+ return ((item->last_request_time.tv_sec - item->creation_time.tv_sec >
+ entry->common_params.max_lifetime.tv_sec) ? 1: 0);
+}
+
+/*
+ * When passed to the flush_cache_policy, ensures that all elements, that
+ * exceed the size limit, are deleted.
+ */
+static int
+cache_elemsize_common_continue_func(struct cache_common_entry_ *entry,
+ struct cache_policy_item_ *item)
+{
+
+ return ((entry->items_size > entry->common_params.satisf_elemsize) ? 1
+ : 0);
+}
+
+/*
+ * Removes the elements from the cache entry, while the continue_func returns 1.
+ */
+static void
+flush_cache_policy(struct cache_common_entry_ *entry,
+ struct cache_policy_ *policy,
+ struct cache_policy_ *connected_policy,
+ int (*continue_func)(struct cache_common_entry_ *,
+ struct cache_policy_item_ *))
+{
+ struct cache_policy_item_ *item, *next_item, *connected_item;
+ struct cache_ht_item_ *ht_item;
+ struct cache_ht_item_data_ *ht_item_data, ht_key;
+ hashtable_index_t hash;
+
+ assert(policy != NULL);
+
+ next_item = NULL;
+ item = policy->get_first_item_func(policy);
+ while ((item != NULL) && (continue_func(entry, item) == 1)) {
+ next_item = policy->get_next_item_func(policy, item);
+
+ connected_item = item->connected_item;
+ policy->remove_item_func(policy, item);
+
+ memset(&ht_key, 0, sizeof(struct cache_ht_item_data_));
+ ht_key.key = item->key;
+ ht_key.key_size = item->key_size;
+
+ hash = HASHTABLE_CALCULATE_HASH(cache_ht_, &entry->items,
+ &ht_key);
+ assert(hash < HASHTABLE_ENTRIES_COUNT(&entry->items));
+
+ ht_item = HASHTABLE_GET_ENTRY(&(entry->items), hash);
+ ht_item_data = HASHTABLE_ENTRY_FIND(cache_ht_, ht_item,
+ &ht_key);
+ assert(ht_item_data != NULL);
+ free(ht_item_data->key);
+ free(ht_item_data->value);
+ HASHTABLE_ENTRY_REMOVE(cache_ht_, ht_item, ht_item_data);
+ --entry->items_size;
+
+ policy->destroy_item_func(item);
+
+ if (connected_item != NULL) {
+ connected_policy->remove_item_func(connected_policy,
+ connected_item);
+ connected_policy->destroy_item_func(connected_item);
+ }
+
+ item = next_item;
+ }
+}
+
+static void
+flush_cache_entry(struct cache_entry_ *entry)
+{
+ struct cache_mp_entry_ *mp_entry;
+ struct cache_common_entry_ *common_entry;
+ struct cache_policy_ *policy, *connected_policy;
+
+ connected_policy = NULL;
+ if (entry->params->entry_type == CET_COMMON) {
+ common_entry = (struct cache_common_entry_ *)entry;
+ if ((common_entry->common_params.max_lifetime.tv_sec != 0) ||
+ (common_entry->common_params.max_lifetime.tv_usec != 0)) {
+
+ policy = common_entry->policies[0];
+ if (common_entry->policies_size > 1)
+ connected_policy = common_entry->policies[1];
+
+ flush_cache_policy(common_entry, policy,
+ connected_policy,
+ cache_lifetime_common_continue_func);
+ }
+
+
+ if ((common_entry->common_params.max_elemsize != 0) &&
+ common_entry->items_size >
+ common_entry->common_params.max_elemsize) {
+
+ if (common_entry->policies_size > 1) {
+ policy = common_entry->policies[1];
+ connected_policy = common_entry->policies[0];
+ } else {
+ policy = common_entry->policies[0];
+ connected_policy = NULL;
+ }
+
+ flush_cache_policy(common_entry, policy,
+ connected_policy,
+ cache_elemsize_common_continue_func);
+ }
+ } else {
+ mp_entry = (struct cache_mp_entry_ *)entry;
+
+ if ((mp_entry->mp_params.max_lifetime.tv_sec != 0)
+ || (mp_entry->mp_params.max_lifetime.tv_usec != 0)) {
+
+ if (mp_entry->last_request_time.tv_sec -
+ mp_entry->last_request_time.tv_sec >
+ mp_entry->mp_params.max_lifetime.tv_sec)
+ clear_cache_entry(entry);
+ }
+ }
+}
+
+struct cache_ *
+init_cache(struct cache_params const *params)
+{
+ struct cache_ *retval;
+
+ TRACE_IN(init_cache);
+ assert(params != NULL);
+
+ retval = calloc(1, sizeof(*retval));
+ assert(retval != NULL);
+
+ assert(params != NULL);
+ memcpy(&retval->params, params, sizeof(struct cache_params));
+
+ retval->entries = calloc(1,
+ sizeof(*retval->entries) * INITIAL_ENTRIES_CAPACITY);
+ assert(retval->entries != NULL);
+
+ retval->entries_capacity = INITIAL_ENTRIES_CAPACITY;
+ retval->entries_size = 0;
+
+ TRACE_OUT(init_cache);
+ return (retval);
+}
+
+void
+destroy_cache(struct cache_ *the_cache)
+{
+
+ TRACE_IN(destroy_cache);
+ assert(the_cache != NULL);
+
+ if (the_cache->entries != NULL) {
+ size_t i;
+ for (i = 0; i < the_cache->entries_size; ++i)
+ destroy_cache_entry(the_cache->entries[i]);
+
+ free(the_cache->entries);
+ }
+
+ free(the_cache);
+ TRACE_OUT(destroy_cache);
+}
+
+int
+register_cache_entry(struct cache_ *the_cache,
+ struct cache_entry_params const *params)
+{
+ int policies_size;
+ size_t entry_name_size;
+ struct cache_common_entry_ *new_common_entry;
+ struct cache_mp_entry_ *new_mp_entry;
+
+ TRACE_IN(register_cache_entry);
+ assert(the_cache != NULL);
+
+ if (find_cache_entry(the_cache, params->entry_name) != NULL) {
+ TRACE_OUT(register_cache_entry);
+ return (-1);
+ }
+
+ if (the_cache->entries_size == the_cache->entries_capacity) {
+ struct cache_entry_ **new_entries;
+ size_t new_capacity;
+
+ new_capacity = the_cache->entries_capacity +
+ ENTRIES_CAPACITY_STEP;
+ new_entries = calloc(1,
+ sizeof(*new_entries) * new_capacity);
+ assert(new_entries != NULL);
+
+ memcpy(new_entries, the_cache->entries,
+ sizeof(struct cache_entry_ *)
+ * the_cache->entries_size);
+
+ free(the_cache->entries);
+ the_cache->entries = new_entries;
+ }
+
+ entry_name_size = strlen(params->entry_name) + 1;
+ switch (params->entry_type)
+ {
+ case CET_COMMON:
+ new_common_entry = calloc(1,
+ sizeof(*new_common_entry));
+ assert(new_common_entry != NULL);
+
+ memcpy(&new_common_entry->common_params, params,
+ sizeof(struct common_cache_entry_params));
+ new_common_entry->params =
+ (struct cache_entry_params *)&new_common_entry->common_params;
+
+ new_common_entry->common_params.cep.entry_name = calloc(1,
+ entry_name_size);
+ assert(new_common_entry->common_params.cep.entry_name != NULL);
+ strlcpy(new_common_entry->common_params.cep.entry_name,
+ params->entry_name, entry_name_size);
+ new_common_entry->name =
+ new_common_entry->common_params.cep.entry_name;
+
+ HASHTABLE_INIT(&(new_common_entry->items),
+ struct cache_ht_item_data_, data,
+ new_common_entry->common_params.cache_entries_size);
+
+ if (new_common_entry->common_params.policy == CPT_FIFO)
+ policies_size = 1;
+ else
+ policies_size = 2;
+
+ new_common_entry->policies = calloc(1,
+ sizeof(*new_common_entry->policies) * policies_size);
+ assert(new_common_entry->policies != NULL);
+
+ new_common_entry->policies_size = policies_size;
+ new_common_entry->policies[0] = init_cache_fifo_policy();
+
+ if (policies_size > 1) {
+ switch (new_common_entry->common_params.policy) {
+ case CPT_LRU:
+ new_common_entry->policies[1] =
+ init_cache_lru_policy();
+ break;
+ case CPT_LFU:
+ new_common_entry->policies[1] =
+ init_cache_lfu_policy();
+ break;
+ default:
+ break;
+ }
+ }
+
+ new_common_entry->get_time_func =
+ the_cache->params.get_time_func;
+ the_cache->entries[the_cache->entries_size++] =
+ (struct cache_entry_ *)new_common_entry;
+ break;
+ case CET_MULTIPART:
+ new_mp_entry = calloc(1,
+ sizeof(*new_mp_entry));
+ assert(new_mp_entry != NULL);
+
+ memcpy(&new_mp_entry->mp_params, params,
+ sizeof(struct mp_cache_entry_params));
+ new_mp_entry->params =
+ (struct cache_entry_params *)&new_mp_entry->mp_params;
+
+ new_mp_entry->mp_params.cep.entry_name = calloc(1,
+ entry_name_size);
+ assert(new_mp_entry->mp_params.cep.entry_name != NULL);
+ strlcpy(new_mp_entry->mp_params.cep.entry_name, params->entry_name,
+ entry_name_size);
+ new_mp_entry->name = new_mp_entry->mp_params.cep.entry_name;
+
+ TAILQ_INIT(&new_mp_entry->ws_head);
+ TAILQ_INIT(&new_mp_entry->rs_head);
+
+ new_mp_entry->get_time_func = the_cache->params.get_time_func;
+ the_cache->entries[the_cache->entries_size++] =
+ (struct cache_entry_ *)new_mp_entry;
+ break;
+ }
+
+
+ qsort(the_cache->entries, the_cache->entries_size,
+ sizeof(struct cache_entry_ *), entries_qsort_cmp_func);
+
+ TRACE_OUT(register_cache_entry);
+ return (0);
+}
+
+int
+unregister_cache_entry(struct cache_ *the_cache, const char *entry_name)
+{
+ struct cache_entry_ **del_ent;
+
+ TRACE_IN(unregister_cache_entry);
+ assert(the_cache != NULL);
+
+ del_ent = find_cache_entry_p(the_cache, entry_name);
+ if (del_ent != NULL) {
+ destroy_cache_entry(*del_ent);
+ --the_cache->entries_size;
+
+ memmove(del_ent, del_ent + 1,
+ (&(the_cache->entries[--the_cache->entries_size]) -
+ del_ent) * sizeof(struct cache_entry_ *));
+
+ TRACE_OUT(unregister_cache_entry);
+ return (0);
+ } else {
+ TRACE_OUT(unregister_cache_entry);
+ return (-1);
+ }
+}
+
+struct cache_entry_ *
+find_cache_entry(struct cache_ *the_cache, const char *entry_name)
+{
+ struct cache_entry_ **result;
+
+ TRACE_IN(find_cache_entry);
+ result = find_cache_entry_p(the_cache, entry_name);
+
+ if (result == NULL) {
+ TRACE_OUT(find_cache_entry);
+ return (NULL);
+ } else {
+ TRACE_OUT(find_cache_entry);
+ return (*result);
+ }
+}
+
+/*
+ * Tries to read the element with the specified key from the cache. If the
+ * value_size is too small, it will be filled with the proper number, and
+ * the user will need to call cache_read again with the value buffer, that
+ * is large enough.
+ * Function returns 0 on success, -1 on error, and -2 if the value_size is too
+ * small.
+ */
+int
+cache_read(struct cache_entry_ *entry, const char *key, size_t key_size,
+ char *value, size_t *value_size)
+{
+ struct cache_common_entry_ *common_entry;
+ struct cache_ht_item_data_ item_data, *find_res;
+ struct cache_ht_item_ *item;
+ hashtable_index_t hash;
+ struct cache_policy_item_ *connected_item;
+
+ TRACE_IN(cache_read);
+ assert(entry != NULL);
+ assert(key != NULL);
+ assert(value_size != NULL);
+ assert(entry->params->entry_type == CET_COMMON);
+
+ common_entry = (struct cache_common_entry_ *)entry;
+
+ memset(&item_data, 0, sizeof(struct cache_ht_item_data_));
+ /* can't avoid the cast here */
+ item_data.key = (char *)key;
+ item_data.key_size = key_size;
+
+ hash = HASHTABLE_CALCULATE_HASH(cache_ht_, &common_entry->items,
+ &item_data);
+ assert(hash < HASHTABLE_ENTRIES_COUNT(&common_entry->items));
+
+ item = HASHTABLE_GET_ENTRY(&(common_entry->items), hash);
+ find_res = HASHTABLE_ENTRY_FIND(cache_ht_, item, &item_data);
+ if (find_res == NULL) {
+ TRACE_OUT(cache_read);
+ return (-1);
+ }
+ /* pretend that entry was not found if confidence is below threshold*/
+ if (find_res->confidence <
+ common_entry->common_params.confidence_threshold) {
+ TRACE_OUT(cache_read);
+ return (-1);
+ }
+
+ if ((common_entry->common_params.max_lifetime.tv_sec != 0) ||
+ (common_entry->common_params.max_lifetime.tv_usec != 0)) {
+
+ if (find_res->fifo_policy_item->last_request_time.tv_sec -
+ find_res->fifo_policy_item->creation_time.tv_sec >
+ common_entry->common_params.max_lifetime.tv_sec) {
+
+ free(find_res->key);
+ free(find_res->value);
+
+ connected_item =
+ find_res->fifo_policy_item->connected_item;
+ if (connected_item != NULL) {
+ common_entry->policies[1]->remove_item_func(
+ common_entry->policies[1],
+ connected_item);
+ common_entry->policies[1]->destroy_item_func(
+ connected_item);
+ }
+
+ common_entry->policies[0]->remove_item_func(
+ common_entry->policies[0],
+ find_res->fifo_policy_item);
+ common_entry->policies[0]->destroy_item_func(
+ find_res->fifo_policy_item);
+
+ HASHTABLE_ENTRY_REMOVE(cache_ht_, item, find_res);
+ --common_entry->items_size;
+ }
+ }
+
+ if ((*value_size < find_res->value_size) || (value == NULL)) {
+ *value_size = find_res->value_size;
+ TRACE_OUT(cache_read);
+ return (-2);
+ }
+
+ *value_size = find_res->value_size;
+ memcpy(value, find_res->value, find_res->value_size);
+
+ ++find_res->fifo_policy_item->request_count;
+ common_entry->get_time_func(
+ &find_res->fifo_policy_item->last_request_time);
+ common_entry->policies[0]->update_item_func(common_entry->policies[0],
+ find_res->fifo_policy_item);
+
+ if (find_res->fifo_policy_item->connected_item != NULL) {
+ connected_item = find_res->fifo_policy_item->connected_item;
+ memcpy(&connected_item->last_request_time,
+ &find_res->fifo_policy_item->last_request_time,
+ sizeof(struct timeval));
+ connected_item->request_count =
+ find_res->fifo_policy_item->request_count;
+
+ common_entry->policies[1]->update_item_func(
+ common_entry->policies[1], connected_item);
+ }
+
+ TRACE_OUT(cache_read);
+ return (0);
+}
+
+/*
+ * Writes the value with the specified key into the cache entry.
+ * Functions returns 0 on success, and -1 on error.
+ */
+int
+cache_write(struct cache_entry_ *entry, const char *key, size_t key_size,
+ char const *value, size_t value_size)
+{
+ struct cache_common_entry_ *common_entry;
+ struct cache_ht_item_data_ item_data, *find_res;
+ struct cache_ht_item_ *item;
+ hashtable_index_t hash;
+
+ struct cache_policy_ *policy, *connected_policy;
+ struct cache_policy_item_ *policy_item;
+ struct cache_policy_item_ *connected_policy_item;
+
+ TRACE_IN(cache_write);
+ assert(entry != NULL);
+ assert(key != NULL);
+ assert(value != NULL);
+ assert(entry->params->entry_type == CET_COMMON);
+
+ common_entry = (struct cache_common_entry_ *)entry;
+
+ memset(&item_data, 0, sizeof(struct cache_ht_item_data_));
+ /* can't avoid the cast here */
+ item_data.key = (char *)key;
+ item_data.key_size = key_size;
+
+ hash = HASHTABLE_CALCULATE_HASH(cache_ht_, &common_entry->items,
+ &item_data);
+ assert(hash < HASHTABLE_ENTRIES_COUNT(&common_entry->items));
+
+ item = HASHTABLE_GET_ENTRY(&(common_entry->items), hash);
+ find_res = HASHTABLE_ENTRY_FIND(cache_ht_, item, &item_data);
+ if (find_res != NULL) {
+ if (find_res->confidence < common_entry->common_params.confidence_threshold) {
+ /* duplicate entry is no error, if confidence is low */
+ if ((find_res->value_size == value_size) &&
+ (memcmp(find_res->value, value, value_size) == 0)) {
+ /* increase confidence on exact match (key and values) */
+ find_res->confidence++;
+ } else {
+ /* create new entry with low confidence, if value changed */
+ free(item_data.value);
+ item_data.value = malloc(value_size);
+ assert(item_data.value != NULL);
+ memcpy(item_data.value, value, value_size);
+ item_data.value_size = value_size;
+ find_res->confidence = 1;
+ }
+ TRACE_OUT(cache_write);
+ return (0);
+ }
+ TRACE_OUT(cache_write);
+ return (-1);
+ }
+
+ item_data.key = malloc(key_size);
+ memcpy(item_data.key, key, key_size);
+
+ item_data.value = malloc(value_size);
+ assert(item_data.value != NULL);
+
+ memcpy(item_data.value, value, value_size);
+ item_data.value_size = value_size;
+
+ item_data.confidence = 1;
+
+ policy_item = common_entry->policies[0]->create_item_func();
+ policy_item->key = item_data.key;
+ policy_item->key_size = item_data.key_size;
+ common_entry->get_time_func(&policy_item->creation_time);
+
+ if (common_entry->policies_size > 1) {
+ connected_policy_item =
+ common_entry->policies[1]->create_item_func();
+ memcpy(&connected_policy_item->creation_time,
+ &policy_item->creation_time,
+ sizeof(struct timeval));
+ connected_policy_item->key = policy_item->key;
+ connected_policy_item->key_size = policy_item->key_size;
+
+ connected_policy_item->connected_item = policy_item;
+ policy_item->connected_item = connected_policy_item;
+ }
+
+ item_data.fifo_policy_item = policy_item;
+
+ common_entry->policies[0]->add_item_func(common_entry->policies[0],
+ policy_item);
+ if (common_entry->policies_size > 1)
+ common_entry->policies[1]->add_item_func(
+ common_entry->policies[1], connected_policy_item);
+
+ HASHTABLE_ENTRY_STORE(cache_ht_, item, &item_data);
+ ++common_entry->items_size;
+
+ if ((common_entry->common_params.max_elemsize != 0) &&
+ (common_entry->items_size >
+ common_entry->common_params.max_elemsize)) {
+ if (common_entry->policies_size > 1) {
+ policy = common_entry->policies[1];
+ connected_policy = common_entry->policies[0];
+ } else {
+ policy = common_entry->policies[0];
+ connected_policy = NULL;
+ }
+
+ flush_cache_policy(common_entry, policy, connected_policy,
+ cache_elemsize_common_continue_func);
+ }
+
+ TRACE_OUT(cache_write);
+ return (0);
+}
+
+/*
+ * Initializes the write session for the specified multipart entry. This
+ * session then should be filled with data either committed or abandoned by
+ * using close_cache_mp_write_session or abandon_cache_mp_write_session
+ * respectively.
+ * Returns NULL on errors (when there are too many opened write sessions for
+ * the entry).
+ */
+struct cache_mp_write_session_ *
+open_cache_mp_write_session(struct cache_entry_ *entry)
+{
+ struct cache_mp_entry_ *mp_entry;
+ struct cache_mp_write_session_ *retval;
+
+ TRACE_IN(open_cache_mp_write_session);
+ assert(entry != NULL);
+ assert(entry->params->entry_type == CET_MULTIPART);
+ mp_entry = (struct cache_mp_entry_ *)entry;
+
+ if ((mp_entry->mp_params.max_sessions > 0) &&
+ (mp_entry->ws_size == mp_entry->mp_params.max_sessions)) {
+ TRACE_OUT(open_cache_mp_write_session);
+ return (NULL);
+ }
+
+ retval = calloc(1,
+ sizeof(*retval));
+ assert(retval != NULL);
+
+ TAILQ_INIT(&retval->items);
+ retval->parent_entry = mp_entry;
+
+ TAILQ_INSERT_HEAD(&mp_entry->ws_head, retval, entries);
+ ++mp_entry->ws_size;
+
+ TRACE_OUT(open_cache_mp_write_session);
+ return (retval);
+}
+
+/*
+ * Writes data to the specified session. Return 0 on success and -1 on errors
+ * (when write session size limit is exceeded).
+ */
+int
+cache_mp_write(struct cache_mp_write_session_ *ws, char *data,
+ size_t data_size)
+{
+ struct cache_mp_data_item_ *new_item;
+
+ TRACE_IN(cache_mp_write);
+ assert(ws != NULL);
+ assert(ws->parent_entry != NULL);
+ assert(ws->parent_entry->params->entry_type == CET_MULTIPART);
+
+ if ((ws->parent_entry->mp_params.max_elemsize > 0) &&
+ (ws->parent_entry->mp_params.max_elemsize == ws->items_size)) {
+ TRACE_OUT(cache_mp_write);
+ return (-1);
+ }
+
+ new_item = calloc(1,
+ sizeof(*new_item));
+ assert(new_item != NULL);
+
+ new_item->value = malloc(data_size);
+ assert(new_item->value != NULL);
+ memcpy(new_item->value, data, data_size);
+ new_item->value_size = data_size;
+
+ TAILQ_INSERT_TAIL(&ws->items, new_item, entries);
+ ++ws->items_size;
+
+ TRACE_OUT(cache_mp_write);
+ return (0);
+}
+
+/*
+ * Abandons the write session and frees all the connected resources.
+ */
+void
+abandon_cache_mp_write_session(struct cache_mp_write_session_ *ws)
+{
+
+ TRACE_IN(abandon_cache_mp_write_session);
+ assert(ws != NULL);
+ assert(ws->parent_entry != NULL);
+ assert(ws->parent_entry->params->entry_type == CET_MULTIPART);
+
+ TAILQ_REMOVE(&ws->parent_entry->ws_head, ws, entries);
+ --ws->parent_entry->ws_size;
+
+ destroy_cache_mp_write_session(ws);
+ TRACE_OUT(abandon_cache_mp_write_session);
+}
+
+/*
+ * Commits the session to the entry, for which it was created.
+ */
+void
+close_cache_mp_write_session(struct cache_mp_write_session_ *ws)
+{
+
+ TRACE_IN(close_cache_mp_write_session);
+ assert(ws != NULL);
+ assert(ws->parent_entry != NULL);
+ assert(ws->parent_entry->params->entry_type == CET_MULTIPART);
+
+ TAILQ_REMOVE(&ws->parent_entry->ws_head, ws, entries);
+ --ws->parent_entry->ws_size;
+
+ if (ws->parent_entry->completed_write_session == NULL) {
+ /*
+ * If there is no completed session yet, this will be the one
+ */
+ ws->parent_entry->get_time_func(
+ &ws->parent_entry->creation_time);
+ ws->parent_entry->completed_write_session = ws;
+ } else {
+ /*
+ * If there is a completed session, then we'll save our session
+ * as a pending session. If there is already a pending session,
+ * it would be destroyed.
+ */
+ if (ws->parent_entry->pending_write_session != NULL)
+ destroy_cache_mp_write_session(
+ ws->parent_entry->pending_write_session);
+
+ ws->parent_entry->pending_write_session = ws;
+ }
+ TRACE_OUT(close_cache_mp_write_session);
+}
+
+/*
+ * Opens read session for the specified entry. Returns NULL on errors (when
+ * there are no data in the entry, or the data are obsolete).
+ */
+struct cache_mp_read_session_ *
+open_cache_mp_read_session(struct cache_entry_ *entry)
+{
+ struct cache_mp_entry_ *mp_entry;
+ struct cache_mp_read_session_ *retval;
+
+ TRACE_IN(open_cache_mp_read_session);
+ assert(entry != NULL);
+ assert(entry->params->entry_type == CET_MULTIPART);
+ mp_entry = (struct cache_mp_entry_ *)entry;
+
+ if (mp_entry->completed_write_session == NULL) {
+ TRACE_OUT(open_cache_mp_read_session);
+ return (NULL);
+ }
+
+ if ((mp_entry->mp_params.max_lifetime.tv_sec != 0)
+ || (mp_entry->mp_params.max_lifetime.tv_usec != 0)) {
+ if (mp_entry->last_request_time.tv_sec -
+ mp_entry->last_request_time.tv_sec >
+ mp_entry->mp_params.max_lifetime.tv_sec) {
+ flush_cache_entry(entry);
+ TRACE_OUT(open_cache_mp_read_session);
+ return (NULL);
+ }
+ }
+
+ retval = calloc(1,
+ sizeof(*retval));
+ assert(retval != NULL);
+
+ retval->parent_entry = mp_entry;
+ retval->current_item = TAILQ_FIRST(
+ &mp_entry->completed_write_session->items);
+
+ TAILQ_INSERT_HEAD(&mp_entry->rs_head, retval, entries);
+ ++mp_entry->rs_size;
+
+ mp_entry->get_time_func(&mp_entry->last_request_time);
+ TRACE_OUT(open_cache_mp_read_session);
+ return (retval);
+}
+
+/*
+ * Reads the data from the read session - step by step.
+ * Returns 0 on success, -1 on error (when there are no more data), and -2 if
+ * the data_size is too small. In the last case, data_size would be filled
+ * the proper value.
+ */
+int
+cache_mp_read(struct cache_mp_read_session_ *rs, char *data, size_t *data_size)
+{
+
+ TRACE_IN(cache_mp_read);
+ assert(rs != NULL);
+
+ if (rs->current_item == NULL) {
+ TRACE_OUT(cache_mp_read);
+ return (-1);
+ }
+
+ if (rs->current_item->value_size > *data_size) {
+ *data_size = rs->current_item->value_size;
+ if (data == NULL) {
+ TRACE_OUT(cache_mp_read);
+ return (0);
+ }
+
+ TRACE_OUT(cache_mp_read);
+ return (-2);
+ }
+
+ *data_size = rs->current_item->value_size;
+ memcpy(data, rs->current_item->value, rs->current_item->value_size);
+ rs->current_item = TAILQ_NEXT(rs->current_item, entries);
+
+ TRACE_OUT(cache_mp_read);
+ return (0);
+}
+
+/*
+ * Closes the read session. If there are no more read sessions and there is
+ * a pending write session, it will be committed and old
+ * completed_write_session will be destroyed.
+ */
+void
+close_cache_mp_read_session(struct cache_mp_read_session_ *rs)
+{
+
+ TRACE_IN(close_cache_mp_read_session);
+ assert(rs != NULL);
+ assert(rs->parent_entry != NULL);
+
+ TAILQ_REMOVE(&rs->parent_entry->rs_head, rs, entries);
+ --rs->parent_entry->rs_size;
+
+ if ((rs->parent_entry->rs_size == 0) &&
+ (rs->parent_entry->pending_write_session != NULL)) {
+ destroy_cache_mp_write_session(
+ rs->parent_entry->completed_write_session);
+ rs->parent_entry->completed_write_session =
+ rs->parent_entry->pending_write_session;
+ rs->parent_entry->pending_write_session = NULL;
+ }
+
+ destroy_cache_mp_read_session(rs);
+ TRACE_OUT(close_cache_mp_read_session);
+}
+
+int
+transform_cache_entry(struct cache_entry_ *entry,
+ enum cache_transformation_t transformation)
+{
+
+ TRACE_IN(transform_cache_entry);
+ switch (transformation) {
+ case CTT_CLEAR:
+ clear_cache_entry(entry);
+ TRACE_OUT(transform_cache_entry);
+ return (0);
+ case CTT_FLUSH:
+ flush_cache_entry(entry);
+ TRACE_OUT(transform_cache_entry);
+ return (0);
+ default:
+ TRACE_OUT(transform_cache_entry);
+ return (-1);
+ }
+}
+
+int
+transform_cache_entry_part(struct cache_entry_ *entry,
+ enum cache_transformation_t transformation, const char *key_part,
+ size_t key_part_size, enum part_position_t part_position)
+{
+ struct cache_common_entry_ *common_entry;
+ struct cache_ht_item_ *ht_item;
+ struct cache_ht_item_data_ *ht_item_data, ht_key;
+
+ struct cache_policy_item_ *item, *connected_item;
+
+ TRACE_IN(transform_cache_entry_part);
+ if (entry->params->entry_type != CET_COMMON) {
+ TRACE_OUT(transform_cache_entry_part);
+ return (-1);
+ }
+
+ if (transformation != CTT_CLEAR) {
+ TRACE_OUT(transform_cache_entry_part);
+ return (-1);
+ }
+
+ memset(&ht_key, 0, sizeof(struct cache_ht_item_data_));
+ ht_key.key = (char *)key_part; /* can't avoid casting here */
+ ht_key.key_size = key_part_size;
+
+ common_entry = (struct cache_common_entry_ *)entry;
+ HASHTABLE_FOREACH(&(common_entry->items), ht_item) {
+ do {
+ ht_item_data = HASHTABLE_ENTRY_FIND_SPECIAL(cache_ht_,
+ ht_item, &ht_key,
+ ht_items_fixed_size_left_cmp_func);
+
+ if (ht_item_data != NULL) {
+ item = ht_item_data->fifo_policy_item;
+ connected_item = item->connected_item;
+
+ common_entry->policies[0]->remove_item_func(
+ common_entry->policies[0],
+ item);
+
+ free(ht_item_data->key);
+ free(ht_item_data->value);
+ HASHTABLE_ENTRY_REMOVE(cache_ht_, ht_item,
+ ht_item_data);
+ --common_entry->items_size;
+
+ common_entry->policies[0]->destroy_item_func(
+ item);
+ if (common_entry->policies_size == 2) {
+ common_entry->policies[1]->remove_item_func(
+ common_entry->policies[1],
+ connected_item);
+ common_entry->policies[1]->destroy_item_func(
+ connected_item);
+ }
+ }
+ } while (ht_item_data != NULL);
+ }
+
+ TRACE_OUT(transform_cache_entry_part);
+ return (0);
+}
diff --git a/usr.sbin/nscd/cachelib.h b/usr.sbin/nscd/cachelib.h
new file mode 100644
index 0000000..9df50e7
--- /dev/null
+++ b/usr.sbin/nscd/cachelib.h
@@ -0,0 +1,263 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 __NSCD_CACHELIB_H__
+#define __NSCD_CACHELIB_H__
+
+#include "hashtable.h"
+#include "cacheplcs.h"
+
+enum cache_entry_t {
+ CET_COMMON = 0, /* cache item is atomic */
+ CET_MULTIPART /* cache item is formed part by part */
+};
+
+enum cache_transformation_t {
+ CTT_FLUSH = 0, /* flush the cache - delete all obsolete items */
+ CTT_CLEAR = 1 /* delete all items in the cache */
+};
+
+/* cache deletion policy type enum */
+enum cache_policy_t {
+ CPT_FIFO = 0, /* first-in first-out */
+ CPT_LRU = 1, /* least recently used */
+ CPT_LFU = 2 /* least frequently used */
+};
+
+/* multipart sessions can be used for reading and writing */
+enum cache_mp_session_t {
+ CMPT_READ_SESSION,
+ CMPT_WRITE_SESSION
+};
+
+/*
+ * When doing partial transformations of entries (which are applied for
+ * elements with keys, that contain specified buffer in its left or
+ * right part), this enum will show the needed position of the key part.
+ */
+enum part_position_t {
+ KPPT_LEFT,
+ KPPT_RIGHT
+};
+
+/* num_levels attribute is obsolete, i think - user can always emulate it
+ * by using one entry.
+ * get_time_func is needed to have the clocks-independent counter
+ */
+struct cache_params {
+ void (*get_time_func)(struct timeval *);
+};
+
+/*
+ * base structure - normal_cache_entry_params and multipart_cache_entry_params
+ * are "inherited" from it
+ */
+struct cache_entry_params {
+ enum cache_entry_t entry_type;
+ char *entry_name;
+};
+
+/* params, used for most entries */
+struct common_cache_entry_params {
+ struct cache_entry_params cep;
+
+ size_t cache_entries_size;
+
+ size_t max_elemsize; /* if 0 then no check is made */
+ size_t satisf_elemsize; /* if entry size is exceeded,
+ * this number of elements will be left,
+ * others will be deleted */
+ int confidence_threshold; /* number matching replies required */
+ struct timeval max_lifetime; /* if 0 then no check is made */
+ enum cache_policy_t policy; /* policy used for transformations */
+};
+
+/* params, used for multipart entries */
+struct mp_cache_entry_params {
+ struct cache_entry_params cep;
+
+ /* unique fields */
+ size_t max_elemsize; /* if 0 then no check is made */
+ size_t max_sessions; /* maximum number of active sessions */
+
+ struct timeval max_lifetime; /* maximum elements lifetime */
+};
+
+struct cache_ht_item_data_ {
+ /* key is the bytes sequence only - not the null-terminated string */
+ char *key;
+ size_t key_size;
+
+ char *value;
+ size_t value_size;
+
+ struct cache_policy_item_ *fifo_policy_item;
+ int confidence; /* incremented for each verification */
+};
+
+struct cache_ht_item_ {
+ HASHTABLE_ENTRY_HEAD(ht_item_, struct cache_ht_item_data_) data;
+};
+
+struct cache_entry_ {
+ char *name;
+ struct cache_entry_params *params;
+};
+
+struct cache_common_entry_ {
+ char *name;
+ struct cache_entry_params *params;
+
+ struct common_cache_entry_params common_params;
+
+ HASHTABLE_HEAD(cache_ht_, cache_ht_item_) items;
+ size_t items_size;
+
+ /*
+ * Entry always has the FIFO policy, that is used to eliminate old
+ * elements (the ones, with lifetime more than max_lifetime). Besides,
+ * user can specify another policy to be applied, when there are too
+ * many elements in the entry. So policies_size can be 1 or 2.
+ */
+ struct cache_policy_ **policies;
+ size_t policies_size;
+
+ void (*get_time_func)(struct timeval *);
+};
+
+struct cache_mp_data_item_ {
+ char *value;
+ size_t value_size;
+
+ TAILQ_ENTRY(cache_mp_data_item_) entries;
+};
+
+struct cache_mp_write_session_ {
+ struct cache_mp_entry_ *parent_entry;
+
+ /*
+ * All items are accumulated in this queue. When the session is
+ * committed, they all will be copied to the multipart entry.
+ */
+ TAILQ_HEAD(cache_mp_data_item_head, cache_mp_data_item_) items;
+ size_t items_size;
+
+ TAILQ_ENTRY(cache_mp_write_session_) entries;
+};
+
+struct cache_mp_read_session_ {
+ struct cache_mp_entry_ *parent_entry;
+ struct cache_mp_data_item_ *current_item;
+
+ TAILQ_ENTRY(cache_mp_read_session_) entries;
+};
+
+struct cache_mp_entry_ {
+ char *name;
+ struct cache_entry_params *params;
+
+ struct mp_cache_entry_params mp_params;
+
+ /* All opened write sessions */
+ TAILQ_HEAD(write_sessions_head, cache_mp_write_session_) ws_head;
+ size_t ws_size;
+
+ /* All opened read sessions */
+ TAILQ_HEAD(read_sessions_head, cache_mp_read_session_) rs_head;
+ size_t rs_size;
+
+ /*
+ * completed_write_session is the committed write sessions. All read
+ * sessions use data from it. If the completed_write_session is out of
+ * date, but still in use by some of the read sessions, the newly
+ * committed write session is stored in the pending_write_session.
+ * In such a case, completed_write_session will be substituted with
+ * pending_write_session as soon as it won't be used by any of
+ * the read sessions.
+ */
+ struct cache_mp_write_session_ *completed_write_session;
+ struct cache_mp_write_session_ *pending_write_session;
+ struct timeval creation_time;
+ struct timeval last_request_time;
+
+ void (*get_time_func)(struct timeval *);
+};
+
+struct cache_ {
+ struct cache_params params;
+
+ struct cache_entry_ **entries;
+ size_t entries_capacity;
+ size_t entries_size;
+};
+
+/* simple abstractions - for not to write "struct" every time */
+typedef struct cache_ *cache;
+typedef struct cache_entry_ *cache_entry;
+typedef struct cache_mp_write_session_ *cache_mp_write_session;
+typedef struct cache_mp_read_session_ *cache_mp_read_session;
+
+#define INVALID_CACHE (NULL)
+#define INVALID_CACHE_ENTRY (NULL)
+#define INVALID_CACHE_MP_WRITE_SESSION (NULL)
+#define INVALID_CACHE_MP_READ_SESSION (NULL)
+
+/*
+ * NOTE: all cache operations are thread-unsafe. You must ensure thread-safety
+ * externally, by yourself.
+ */
+
+/* cache initialization/destruction routines */
+cache init_cache(struct cache_params const *);
+void destroy_cache(cache);
+
+/* cache entries manipulation routines */
+int register_cache_entry(cache, struct cache_entry_params const *);
+int unregister_cache_entry(cache, const char *);
+cache_entry find_cache_entry(cache, const char *);
+
+/* read/write operations used on common entries */
+int cache_read(cache_entry, const char *, size_t, char *, size_t *);
+int cache_write(cache_entry, const char *, size_t, char const *, size_t);
+
+/* read/write operations used on multipart entries */
+cache_mp_write_session open_cache_mp_write_session(cache_entry);
+int cache_mp_write(cache_mp_write_session, char *, size_t);
+void abandon_cache_mp_write_session(cache_mp_write_session);
+void close_cache_mp_write_session(cache_mp_write_session);
+
+cache_mp_read_session open_cache_mp_read_session(cache_entry);
+int cache_mp_read(cache_mp_read_session, char *, size_t *);
+void close_cache_mp_read_session(cache_mp_read_session);
+
+/* transformation routines */
+int transform_cache_entry(cache_entry, enum cache_transformation_t);
+int transform_cache_entry_part(cache_entry, enum cache_transformation_t,
+ const char *, size_t, enum part_position_t);
+
+#endif
diff --git a/usr.sbin/nscd/cacheplcs.c b/usr.sbin/nscd/cacheplcs.c
new file mode 100644
index 0000000..37cf765
--- /dev/null
+++ b/usr.sbin/nscd/cacheplcs.c
@@ -0,0 +1,590 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/time.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "cacheplcs.h"
+#include "debug.h"
+
+static void cache_fifo_policy_update_item(struct cache_policy_ *,
+ struct cache_policy_item_ *);
+static void cache_lfu_policy_add_item(struct cache_policy_ *,
+ struct cache_policy_item_ *);
+static struct cache_policy_item_ * cache_lfu_policy_create_item(void);
+static void cache_lfu_policy_destroy_item(struct cache_policy_item_ *);
+static struct cache_policy_item_ *cache_lfu_policy_get_first_item(
+ struct cache_policy_ *);
+static struct cache_policy_item_ *cache_lfu_policy_get_last_item(
+ struct cache_policy_ *);
+static struct cache_policy_item_ *cache_lfu_policy_get_next_item(
+ struct cache_policy_ *, struct cache_policy_item_ *);
+static struct cache_policy_item_ *cache_lfu_policy_get_prev_item(
+ struct cache_policy_ *, struct cache_policy_item_ *);
+static void cache_lfu_policy_remove_item(struct cache_policy_ *,
+ struct cache_policy_item_ *);
+static void cache_lfu_policy_update_item(struct cache_policy_ *,
+ struct cache_policy_item_ *);
+static void cache_lru_policy_update_item(struct cache_policy_ *,
+ struct cache_policy_item_ *);
+static void cache_queue_policy_add_item(struct cache_policy_ *,
+ struct cache_policy_item_ *);
+static struct cache_policy_item_ * cache_queue_policy_create_item(void);
+static void cache_queue_policy_destroy_item(struct cache_policy_item_ *);
+static struct cache_policy_item_ *cache_queue_policy_get_first_item(
+ struct cache_policy_ *);
+static struct cache_policy_item_ *cache_queue_policy_get_last_item(
+ struct cache_policy_ *);
+static struct cache_policy_item_ *cache_queue_policy_get_next_item(
+ struct cache_policy_ *, struct cache_policy_item_ *);
+static struct cache_policy_item_ *cache_queue_policy_get_prev_item(
+ struct cache_policy_ *, struct cache_policy_item_ *);
+static void cache_queue_policy_remove_item(struct cache_policy_ *,
+ struct cache_policy_item_ *);
+static void destroy_cache_queue_policy(struct cache_queue_policy_ *);
+static struct cache_queue_policy_ *init_cache_queue_policy(void);
+
+/*
+ * All cache_queue_policy_XXX functions below will be used to fill
+ * the cache_queue_policy structure. They implement the most functionality of
+ * LRU and FIFO policies. LRU and FIFO policies are actually the
+ * cache_queue_policy_ with cache_update_item function changed.
+ */
+static struct cache_policy_item_ *
+cache_queue_policy_create_item(void)
+{
+ struct cache_queue_policy_item_ *retval;
+
+ TRACE_IN(cache_queue_policy_create_item);
+ retval = calloc(1,
+ sizeof(*retval));
+ assert(retval != NULL);
+
+ TRACE_OUT(cache_queue_policy_create_item);
+ return ((struct cache_policy_item_ *)retval);
+}
+
+static void
+cache_queue_policy_destroy_item(struct cache_policy_item_ *item)
+{
+
+ TRACE_IN(cache_queue_policy_destroy_item);
+ assert(item != NULL);
+ free(item);
+ TRACE_OUT(cache_queue_policy_destroy_item);
+}
+
+static void
+cache_queue_policy_add_item(struct cache_policy_ *policy,
+ struct cache_policy_item_ *item)
+{
+ struct cache_queue_policy_ *queue_policy;
+ struct cache_queue_policy_item_ *queue_item;
+
+ TRACE_IN(cache_queue_policy_add_item);
+ queue_policy = (struct cache_queue_policy_ *)policy;
+ queue_item = (struct cache_queue_policy_item_ *)item;
+ TAILQ_INSERT_TAIL(&queue_policy->head, queue_item, entries);
+ TRACE_OUT(cache_queue_policy_add_item);
+}
+
+static void
+cache_queue_policy_remove_item(struct cache_policy_ *policy,
+ struct cache_policy_item_ *item)
+{
+ struct cache_queue_policy_ *queue_policy;
+ struct cache_queue_policy_item_ *queue_item;
+
+ TRACE_IN(cache_queue_policy_remove_item);
+ queue_policy = (struct cache_queue_policy_ *)policy;
+ queue_item = (struct cache_queue_policy_item_ *)item;
+ TAILQ_REMOVE(&queue_policy->head, queue_item, entries);
+ TRACE_OUT(cache_queue_policy_remove_item);
+}
+
+static struct cache_policy_item_ *
+cache_queue_policy_get_first_item(struct cache_policy_ *policy)
+{
+ struct cache_queue_policy_ *queue_policy;
+
+ TRACE_IN(cache_queue_policy_get_first_item);
+ queue_policy = (struct cache_queue_policy_ *)policy;
+ TRACE_OUT(cache_queue_policy_get_first_item);
+ return ((struct cache_policy_item_ *)TAILQ_FIRST(&queue_policy->head));
+}
+
+static struct cache_policy_item_ *
+cache_queue_policy_get_last_item(struct cache_policy_ *policy)
+{
+ struct cache_queue_policy_ *queue_policy;
+
+ TRACE_IN(cache_queue_policy_get_last_item);
+ queue_policy = (struct cache_queue_policy_ *)policy;
+ TRACE_OUT(cache_queue_policy_get_last_item);
+ return ((struct cache_policy_item_ *)TAILQ_LAST(&queue_policy->head,
+ cache_queue_policy_head_));
+}
+
+static struct cache_policy_item_ *
+cache_queue_policy_get_next_item(struct cache_policy_ *policy,
+ struct cache_policy_item_ *item)
+{
+ struct cache_queue_policy_ *queue_policy;
+ struct cache_queue_policy_item_ *queue_item;
+
+ TRACE_IN(cache_queue_policy_get_next_item);
+ queue_policy = (struct cache_queue_policy_ *)policy;
+ queue_item = (struct cache_queue_policy_item_ *)item;
+
+ TRACE_OUT(cache_queue_policy_get_next_item);
+ return ((struct cache_policy_item_ *)TAILQ_NEXT(queue_item, entries));
+}
+
+static struct cache_policy_item_ *
+cache_queue_policy_get_prev_item(struct cache_policy_ *policy,
+ struct cache_policy_item_ *item)
+{
+ struct cache_queue_policy_ *queue_policy;
+ struct cache_queue_policy_item_ *queue_item;
+
+ TRACE_IN(cache_queue_policy_get_prev_item);
+ queue_policy = (struct cache_queue_policy_ *)policy;
+ queue_item = (struct cache_queue_policy_item_ *)item;
+
+ TRACE_OUT(cache_queue_policy_get_prev_item);
+ return ((struct cache_policy_item_ *)TAILQ_PREV(queue_item,
+ cache_queue_policy_head_, entries));
+}
+
+/*
+ * Initializes cache_queue_policy_ by filling the structure with the functions
+ * pointers, defined above
+ */
+static struct cache_queue_policy_ *
+init_cache_queue_policy(void)
+{
+ struct cache_queue_policy_ *retval;
+
+ TRACE_IN(init_cache_queue_policy);
+ retval = calloc(1,
+ sizeof(*retval));
+ assert(retval != NULL);
+
+ retval->parent_data.create_item_func = cache_queue_policy_create_item;
+ retval->parent_data.destroy_item_func = cache_queue_policy_destroy_item;
+
+ retval->parent_data.add_item_func = cache_queue_policy_add_item;
+ retval->parent_data.remove_item_func = cache_queue_policy_remove_item;
+
+ retval->parent_data.get_first_item_func =
+ cache_queue_policy_get_first_item;
+ retval->parent_data.get_last_item_func =
+ cache_queue_policy_get_last_item;
+ retval->parent_data.get_next_item_func =
+ cache_queue_policy_get_next_item;
+ retval->parent_data.get_prev_item_func =
+ cache_queue_policy_get_prev_item;
+
+ TAILQ_INIT(&retval->head);
+ TRACE_OUT(init_cache_queue_policy);
+ return (retval);
+}
+
+static void
+destroy_cache_queue_policy(struct cache_queue_policy_ *queue_policy)
+{
+ struct cache_queue_policy_item_ *queue_item;
+
+ TRACE_IN(destroy_cache_queue_policy);
+ while (!TAILQ_EMPTY(&queue_policy->head)) {
+ queue_item = TAILQ_FIRST(&queue_policy->head);
+ TAILQ_REMOVE(&queue_policy->head, queue_item, entries);
+ cache_queue_policy_destroy_item(
+ (struct cache_policy_item_ *)queue_item);
+ }
+ free(queue_policy);
+ TRACE_OUT(destroy_cache_queue_policy);
+}
+
+/*
+ * Makes cache_queue_policy_ behave like FIFO policy - we don't do anything,
+ * when the cache element is updated. So it always stays in its initial
+ * position in the queue - that is exactly the FIFO functionality.
+ */
+static void
+cache_fifo_policy_update_item(struct cache_policy_ *policy,
+ struct cache_policy_item_ *item)
+{
+
+ TRACE_IN(cache_fifo_policy_update_item);
+ /* policy and item arguments are ignored */
+ TRACE_OUT(cache_fifo_policy_update_item);
+}
+
+struct cache_policy_ *
+init_cache_fifo_policy(void)
+{
+ struct cache_queue_policy_ *retval;
+
+ TRACE_IN(init_cache_fifo_policy);
+ retval = init_cache_queue_policy();
+ retval->parent_data.update_item_func = cache_fifo_policy_update_item;
+
+ TRACE_OUT(init_cache_fifo_policy);
+ return ((struct cache_policy_ *)retval);
+}
+
+void
+destroy_cache_fifo_policy(struct cache_policy_ *policy)
+{
+ struct cache_queue_policy_ *queue_policy;
+
+ TRACE_IN(destroy_cache_fifo_policy);
+ queue_policy = (struct cache_queue_policy_ *)policy;
+ destroy_cache_queue_policy(queue_policy);
+ TRACE_OUT(destroy_cache_fifo_policy);
+}
+
+/*
+ * Makes cache_queue_policy_ behave like LRU policy. On each update, cache
+ * element is moved to the end of the queue - so it would be deleted in last
+ * turn. That is exactly the LRU policy functionality.
+ */
+static void
+cache_lru_policy_update_item(struct cache_policy_ *policy,
+ struct cache_policy_item_ *item)
+{
+ struct cache_queue_policy_ *queue_policy;
+ struct cache_queue_policy_item_ *queue_item;
+
+ TRACE_IN(cache_lru_policy_update_item);
+ queue_policy = (struct cache_queue_policy_ *)policy;
+ queue_item = (struct cache_queue_policy_item_ *)item;
+
+ TAILQ_REMOVE(&queue_policy->head, queue_item, entries);
+ TAILQ_INSERT_TAIL(&queue_policy->head, queue_item, entries);
+ TRACE_OUT(cache_lru_policy_update_item);
+}
+
+struct cache_policy_ *
+init_cache_lru_policy(void)
+{
+ struct cache_queue_policy_ *retval;
+
+ TRACE_IN(init_cache_lru_policy);
+ retval = init_cache_queue_policy();
+ retval->parent_data.update_item_func = cache_lru_policy_update_item;
+
+ TRACE_OUT(init_cache_lru_policy);
+ return ((struct cache_policy_ *)retval);
+}
+
+void
+destroy_cache_lru_policy(struct cache_policy_ *policy)
+{
+ struct cache_queue_policy_ *queue_policy;
+
+ TRACE_IN(destroy_cache_lru_policy);
+ queue_policy = (struct cache_queue_policy_ *)policy;
+ destroy_cache_queue_policy(queue_policy);
+ TRACE_OUT(destroy_cache_lru_policy);
+}
+
+/*
+ * LFU (least frequently used) policy implementation differs much from the
+ * LRU and FIFO (both based on cache_queue_policy_). Almost all cache_policy_
+ * functions are implemented specifically for this policy. The idea of this
+ * policy is to represent frequency (real number) as the integer number and
+ * use it as the index in the array. Each array's element is
+ * the list of elements. For example, if we have the 100-elements
+ * array for this policy, the elements with frequency 0.1 (calls per-second)
+ * would be in 10th element of the array.
+ */
+static struct cache_policy_item_ *
+cache_lfu_policy_create_item(void)
+{
+ struct cache_lfu_policy_item_ *retval;
+
+ TRACE_IN(cache_lfu_policy_create_item);
+ retval = calloc(1,
+ sizeof(*retval));
+ assert(retval != NULL);
+
+ TRACE_OUT(cache_lfu_policy_create_item);
+ return ((struct cache_policy_item_ *)retval);
+}
+
+static void
+cache_lfu_policy_destroy_item(struct cache_policy_item_ *item)
+{
+
+ TRACE_IN(cache_lfu_policy_destroy_item);
+ assert(item != NULL);
+ free(item);
+ TRACE_OUT(cache_lfu_policy_destroy_item);
+}
+
+/*
+ * When placed in the LFU policy queue for the first time, the maximum
+ * frequency is assigned to the element
+ */
+static void
+cache_lfu_policy_add_item(struct cache_policy_ *policy,
+ struct cache_policy_item_ *item)
+{
+ struct cache_lfu_policy_ *lfu_policy;
+ struct cache_lfu_policy_item_ *lfu_item;
+
+ TRACE_IN(cache_lfu_policy_add_item);
+ lfu_policy = (struct cache_lfu_policy_ *)policy;
+ lfu_item = (struct cache_lfu_policy_item_ *)item;
+
+ lfu_item->frequency = CACHELIB_MAX_FREQUENCY - 1;
+ TAILQ_INSERT_HEAD(&(lfu_policy->groups[CACHELIB_MAX_FREQUENCY - 1]),
+ lfu_item, entries);
+ TRACE_OUT(cache_lfu_policy_add_item);
+}
+
+/*
+ * On each update the frequency of the element is recalculated and, if it
+ * changed, the element would be moved to the another place in the array.
+ */
+static void
+cache_lfu_policy_update_item(struct cache_policy_ *policy,
+ struct cache_policy_item_ *item)
+{
+ struct cache_lfu_policy_ *lfu_policy;
+ struct cache_lfu_policy_item_ *lfu_item;
+ int index;
+
+ TRACE_IN(cache_lfu_policy_update_item);
+ lfu_policy = (struct cache_lfu_policy_ *)policy;
+ lfu_item = (struct cache_lfu_policy_item_ *)item;
+
+ /*
+ * We calculate the square of the request_count to avoid grouping of
+ * all elements at the start of the array (for example, if array size is
+ * 100 and most of its elements has frequency below the 0.01, they
+ * all would be grouped in the first array's position). Other
+ * techniques should be used here later to ensure, that elements are
+ * equally distributed in the array and not grouped in its beginning.
+ */
+ if (lfu_item->parent_data.last_request_time.tv_sec !=
+ lfu_item->parent_data.creation_time.tv_sec) {
+ index = ((double)lfu_item->parent_data.request_count *
+ (double)lfu_item->parent_data.request_count /
+ (lfu_item->parent_data.last_request_time.tv_sec -
+ lfu_item->parent_data.creation_time.tv_sec + 1)) *
+ CACHELIB_MAX_FREQUENCY;
+ if (index >= CACHELIB_MAX_FREQUENCY)
+ index = CACHELIB_MAX_FREQUENCY - 1;
+ } else
+ index = CACHELIB_MAX_FREQUENCY - 1;
+
+ TAILQ_REMOVE(&(lfu_policy->groups[lfu_item->frequency]), lfu_item,
+ entries);
+ lfu_item->frequency = index;
+ TAILQ_INSERT_HEAD(&(lfu_policy->groups[index]), lfu_item, entries);
+
+ TRACE_OUT(cache_lfu_policy_update_item);
+}
+
+static void
+cache_lfu_policy_remove_item(struct cache_policy_ *policy,
+ struct cache_policy_item_ *item)
+{
+ struct cache_lfu_policy_ *lfu_policy;
+ struct cache_lfu_policy_item_ *lfu_item;
+
+ TRACE_IN(cache_lfu_policy_remove_item);
+ lfu_policy = (struct cache_lfu_policy_ *)policy;
+ lfu_item = (struct cache_lfu_policy_item_ *)item;
+
+ TAILQ_REMOVE(&(lfu_policy->groups[lfu_item->frequency]), lfu_item,
+ entries);
+ TRACE_OUT(cache_lfu_policy_remove_item);
+}
+
+static struct cache_policy_item_ *
+cache_lfu_policy_get_first_item(struct cache_policy_ *policy)
+{
+ struct cache_lfu_policy_ *lfu_policy;
+ struct cache_lfu_policy_item_ *lfu_item;
+ int i;
+
+ TRACE_IN(cache_lfu_policy_get_first_item);
+ lfu_item = NULL;
+ lfu_policy = (struct cache_lfu_policy_ *)policy;
+ for (i = 0; i < CACHELIB_MAX_FREQUENCY; ++i)
+ if (!TAILQ_EMPTY(&(lfu_policy->groups[i]))) {
+ lfu_item = TAILQ_FIRST(&(lfu_policy->groups[i]));
+ break;
+ }
+
+ TRACE_OUT(cache_lfu_policy_get_first_item);
+ return ((struct cache_policy_item_ *)lfu_item);
+}
+
+static struct cache_policy_item_ *
+cache_lfu_policy_get_last_item(struct cache_policy_ *policy)
+{
+ struct cache_lfu_policy_ *lfu_policy;
+ struct cache_lfu_policy_item_ *lfu_item;
+ int i;
+
+ TRACE_IN(cache_lfu_policy_get_last_item);
+ lfu_item = NULL;
+ lfu_policy = (struct cache_lfu_policy_ *)policy;
+ for (i = CACHELIB_MAX_FREQUENCY - 1; i >= 0; --i)
+ if (!TAILQ_EMPTY(&(lfu_policy->groups[i]))) {
+ lfu_item = TAILQ_LAST(&(lfu_policy->groups[i]),
+ cache_lfu_policy_group_);
+ break;
+ }
+
+ TRACE_OUT(cache_lfu_policy_get_last_item);
+ return ((struct cache_policy_item_ *)lfu_item);
+}
+
+static struct cache_policy_item_ *
+cache_lfu_policy_get_next_item(struct cache_policy_ *policy,
+ struct cache_policy_item_ *item)
+{
+ struct cache_lfu_policy_ *lfu_policy;
+ struct cache_lfu_policy_item_ *lfu_item;
+ int i;
+
+ TRACE_IN(cache_lfu_policy_get_next_item);
+ lfu_policy = (struct cache_lfu_policy_ *)policy;
+ lfu_item = TAILQ_NEXT((struct cache_lfu_policy_item_ *)item, entries);
+ if (lfu_item == NULL)
+ {
+ for (i = ((struct cache_lfu_policy_item_ *)item)->frequency + 1;
+ i < CACHELIB_MAX_FREQUENCY; ++i) {
+ if (!TAILQ_EMPTY(&(lfu_policy->groups[i]))) {
+ lfu_item = TAILQ_FIRST(&(lfu_policy->groups[i]));
+ break;
+ }
+ }
+ }
+
+ TRACE_OUT(cache_lfu_policy_get_next_item);
+ return ((struct cache_policy_item_ *)lfu_item);
+}
+
+static struct cache_policy_item_ *
+cache_lfu_policy_get_prev_item(struct cache_policy_ *policy,
+ struct cache_policy_item_ *item)
+{
+ struct cache_lfu_policy_ *lfu_policy;
+ struct cache_lfu_policy_item_ *lfu_item;
+ int i;
+
+ TRACE_IN(cache_lfu_policy_get_prev_item);
+ lfu_policy = (struct cache_lfu_policy_ *)policy;
+ lfu_item = TAILQ_PREV((struct cache_lfu_policy_item_ *)item,
+ cache_lfu_policy_group_, entries);
+ if (lfu_item == NULL)
+ {
+ for (i = ((struct cache_lfu_policy_item_ *)item)->frequency - 1;
+ i >= 0; --i)
+ if (!TAILQ_EMPTY(&(lfu_policy->groups[i]))) {
+ lfu_item = TAILQ_LAST(&(lfu_policy->groups[i]),
+ cache_lfu_policy_group_);
+ break;
+ }
+ }
+
+ TRACE_OUT(cache_lfu_policy_get_prev_item);
+ return ((struct cache_policy_item_ *)lfu_item);
+}
+
+/*
+ * Initializes the cache_policy_ structure by filling it with appropriate
+ * functions pointers
+ */
+struct cache_policy_ *
+init_cache_lfu_policy(void)
+{
+ int i;
+ struct cache_lfu_policy_ *retval;
+
+ TRACE_IN(init_cache_lfu_policy);
+ retval = calloc(1,
+ sizeof(*retval));
+ assert(retval != NULL);
+
+ retval->parent_data.create_item_func = cache_lfu_policy_create_item;
+ retval->parent_data.destroy_item_func = cache_lfu_policy_destroy_item;
+
+ retval->parent_data.add_item_func = cache_lfu_policy_add_item;
+ retval->parent_data.update_item_func = cache_lfu_policy_update_item;
+ retval->parent_data.remove_item_func = cache_lfu_policy_remove_item;
+
+ retval->parent_data.get_first_item_func =
+ cache_lfu_policy_get_first_item;
+ retval->parent_data.get_last_item_func =
+ cache_lfu_policy_get_last_item;
+ retval->parent_data.get_next_item_func =
+ cache_lfu_policy_get_next_item;
+ retval->parent_data.get_prev_item_func =
+ cache_lfu_policy_get_prev_item;
+
+ for (i = 0; i < CACHELIB_MAX_FREQUENCY; ++i)
+ TAILQ_INIT(&(retval->groups[i]));
+
+ TRACE_OUT(init_cache_lfu_policy);
+ return ((struct cache_policy_ *)retval);
+}
+
+void
+destroy_cache_lfu_policy(struct cache_policy_ *policy)
+{
+ int i;
+ struct cache_lfu_policy_ *lfu_policy;
+ struct cache_lfu_policy_item_ *lfu_item;
+
+ TRACE_IN(destroy_cache_lfu_policy);
+ lfu_policy = (struct cache_lfu_policy_ *)policy;
+ for (i = 0; i < CACHELIB_MAX_FREQUENCY; ++i) {
+ while (!TAILQ_EMPTY(&(lfu_policy->groups[i]))) {
+ lfu_item = TAILQ_FIRST(&(lfu_policy->groups[i]));
+ TAILQ_REMOVE(&(lfu_policy->groups[i]), lfu_item,
+ entries);
+ cache_lfu_policy_destroy_item(
+ (struct cache_policy_item_ *)lfu_item);
+ }
+ }
+ free(policy);
+ TRACE_OUT(destroy_cache_lfu_policy);
+}
diff --git a/usr.sbin/nscd/cacheplcs.h b/usr.sbin/nscd/cacheplcs.h
new file mode 100644
index 0000000..7d7e1da
--- /dev/null
+++ b/usr.sbin/nscd/cacheplcs.h
@@ -0,0 +1,129 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 __NSCD_CACHEPLCS_H__
+#define __NSCD_CACHEPLCS_H__
+
+#include <sys/queue.h>
+
+/* common policy definitions */
+#define CACHELIB_MAX_FREQUENCY 100
+
+/*
+ * cache_policy_item_ represents some abstract cache element in the policy
+ * queue. connected_item pointers to the corresponding cache_policy_item_ in
+ * another policy queue.
+ */
+struct cache_policy_item_ {
+ char *key;
+ size_t key_size;
+
+ size_t request_count;
+ struct timeval last_request_time;
+ struct timeval creation_time;
+
+ struct cache_policy_item_ *connected_item;
+};
+
+/*
+ * cache_policy_ represents an abstract policy queue. It can be customized by
+ * setting appropriate function pointers
+ */
+struct cache_policy_ {
+ struct cache_policy_item_* (*create_item_func)(void);
+ void (*destroy_item_func)(struct cache_policy_item_ *);
+
+ void (*add_item_func)(struct cache_policy_ *,
+ struct cache_policy_item_ *);
+ void (*remove_item_func)(struct cache_policy_ *,
+ struct cache_policy_item_ *);
+ void (*update_item_func)(struct cache_policy_ *,
+ struct cache_policy_item_ *);
+
+ struct cache_policy_item_ *(*get_first_item_func)(
+ struct cache_policy_ *);
+ struct cache_policy_item_ *(*get_last_item_func)(
+ struct cache_policy_ *);
+ struct cache_policy_item_ *(*get_next_item_func)(
+ struct cache_policy_ *, struct cache_policy_item_ *);
+ struct cache_policy_item_ *(*get_prev_item_func)(
+ struct cache_policy_ *, struct cache_policy_item_ *);
+};
+
+/*
+ * LFU cache policy item "inherited" from cache_policy_item_ structure
+ */
+struct cache_lfu_policy_item_ {
+ struct cache_policy_item_ parent_data;
+ int frequency;
+
+ TAILQ_ENTRY(cache_lfu_policy_item_) entries;
+};
+
+TAILQ_HEAD(cache_lfu_policy_group_, cache_lfu_policy_item_);
+
+/*
+ * LFU policy queue "inherited" from cache_policy_.
+ */
+struct cache_lfu_policy_ {
+ struct cache_policy_ parent_data;
+ struct cache_lfu_policy_group_ groups[CACHELIB_MAX_FREQUENCY];
+};
+
+/*
+ * LRU and FIFO policies item "inherited" from cache_policy_item_
+ */
+struct cache_queue_policy_item_ {
+ struct cache_policy_item_ parent_data;
+ TAILQ_ENTRY(cache_queue_policy_item_) entries;
+};
+
+/*
+ * LRU and FIFO policies "inherited" from cache_policy_
+ */
+struct cache_queue_policy_ {
+ struct cache_policy_ parent_data;
+ TAILQ_HEAD(cache_queue_policy_head_, cache_queue_policy_item_) head;
+};
+
+typedef struct cache_queue_policy_ cache_fifo_policy_;
+typedef struct cache_queue_policy_ cache_lru_policy_;
+
+/* fifo policy routines */
+struct cache_policy_ *init_cache_fifo_policy(void);
+void destroy_cache_fifo_policy(struct cache_policy_ *);
+
+/* lru policy routines */
+struct cache_policy_ *init_cache_lru_policy(void);
+void destroy_cache_lru_policy(struct cache_policy_ *);
+
+/* lfu policy routines */
+struct cache_policy_ *init_cache_lfu_policy(void);
+void destroy_cache_lfu_policy(struct cache_policy_ *);
+
+#endif
diff --git a/usr.sbin/nscd/config.c b/usr.sbin/nscd/config.c
new file mode 100644
index 0000000..f44e986
--- /dev/null
+++ b/usr.sbin/nscd/config.c
@@ -0,0 +1,588 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/stat.h>
+#include <sys/time.h>
+
+#include <assert.h>
+#include <math.h>
+#include <nsswitch.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "config.h"
+#include "debug.h"
+#include "log.h"
+
+/*
+ * Default entries, which always exist in the configuration
+ */
+const char *c_default_entries[6] = {
+ NSDB_PASSWD,
+ NSDB_GROUP,
+ NSDB_HOSTS,
+ NSDB_SERVICES,
+ NSDB_PROTOCOLS,
+ NSDB_RPC
+ };
+
+static int configuration_entry_cmp(const void *, const void *);
+static int configuration_entry_sort_cmp(const void *, const void *);
+static int configuration_entry_cache_mp_sort_cmp(const void *, const void *);
+static int configuration_entry_cache_mp_cmp(const void *, const void *);
+static int configuration_entry_cache_mp_part_cmp(const void *, const void *);
+static struct configuration_entry *create_configuration_entry(const char *,
+ struct timeval const *, struct timeval const *,
+ struct common_cache_entry_params const *,
+ struct common_cache_entry_params const *,
+ struct mp_cache_entry_params const *);
+
+static int
+configuration_entry_sort_cmp(const void *e1, const void *e2)
+{
+ return (strcmp((*((struct configuration_entry **)e1))->name,
+ (*((struct configuration_entry **)e2))->name
+ ));
+}
+
+static int
+configuration_entry_cmp(const void *e1, const void *e2)
+{
+ return (strcmp((const char *)e1,
+ (*((struct configuration_entry **)e2))->name
+ ));
+}
+
+static int
+configuration_entry_cache_mp_sort_cmp(const void *e1, const void *e2)
+{
+ return (strcmp((*((cache_entry *)e1))->params->entry_name,
+ (*((cache_entry *)e2))->params->entry_name
+ ));
+}
+
+static int
+configuration_entry_cache_mp_cmp(const void *e1, const void *e2)
+{
+ return (strcmp((const char *)e1,
+ (*((cache_entry *)e2))->params->entry_name
+ ));
+}
+
+static int
+configuration_entry_cache_mp_part_cmp(const void *e1, const void *e2)
+{
+ return (strncmp((const char *)e1,
+ (*((cache_entry *)e2))->params->entry_name,
+ strlen((const char *)e1)
+ ));
+}
+
+static struct configuration_entry *
+create_configuration_entry(const char *name,
+ struct timeval const *common_timeout,
+ struct timeval const *mp_timeout,
+ struct common_cache_entry_params const *positive_params,
+ struct common_cache_entry_params const *negative_params,
+ struct mp_cache_entry_params const *mp_params)
+{
+ struct configuration_entry *retval;
+ size_t size;
+ int res;
+
+ TRACE_IN(create_configuration_entry);
+ assert(name != NULL);
+ assert(positive_params != NULL);
+ assert(negative_params != NULL);
+ assert(mp_params != NULL);
+
+ retval = calloc(1,
+ sizeof(*retval));
+ assert(retval != NULL);
+
+ res = pthread_mutex_init(&retval->positive_cache_lock, NULL);
+ if (res != 0) {
+ free(retval);
+ LOG_ERR_2("create_configuration_entry",
+ "can't create positive cache lock");
+ TRACE_OUT(create_configuration_entry);
+ return (NULL);
+ }
+
+ res = pthread_mutex_init(&retval->negative_cache_lock, NULL);
+ if (res != 0) {
+ pthread_mutex_destroy(&retval->positive_cache_lock);
+ free(retval);
+ LOG_ERR_2("create_configuration_entry",
+ "can't create negative cache lock");
+ TRACE_OUT(create_configuration_entry);
+ return (NULL);
+ }
+
+ res = pthread_mutex_init(&retval->mp_cache_lock, NULL);
+ if (res != 0) {
+ pthread_mutex_destroy(&retval->positive_cache_lock);
+ pthread_mutex_destroy(&retval->negative_cache_lock);
+ free(retval);
+ LOG_ERR_2("create_configuration_entry",
+ "can't create negative cache lock");
+ TRACE_OUT(create_configuration_entry);
+ return (NULL);
+ }
+
+ memcpy(&retval->positive_cache_params, positive_params,
+ sizeof(struct common_cache_entry_params));
+ memcpy(&retval->negative_cache_params, negative_params,
+ sizeof(struct common_cache_entry_params));
+ memcpy(&retval->mp_cache_params, mp_params,
+ sizeof(struct mp_cache_entry_params));
+
+ size = strlen(name);
+ retval->name = calloc(1, size + 1);
+ assert(retval->name != NULL);
+ memcpy(retval->name, name, size);
+
+ memcpy(&retval->common_query_timeout, common_timeout,
+ sizeof(struct timeval));
+ memcpy(&retval->mp_query_timeout, mp_timeout,
+ sizeof(struct timeval));
+
+ asprintf(&retval->positive_cache_params.cep.entry_name, "%s+", name);
+ assert(retval->positive_cache_params.cep.entry_name != NULL);
+
+ asprintf(&retval->negative_cache_params.cep.entry_name, "%s-", name);
+ assert(retval->negative_cache_params.cep.entry_name != NULL);
+
+ asprintf(&retval->mp_cache_params.cep.entry_name, "%s*", name);
+ assert(retval->mp_cache_params.cep.entry_name != NULL);
+
+ TRACE_OUT(create_configuration_entry);
+ return (retval);
+}
+
+/*
+ * Creates configuration entry and fills it with default values
+ */
+struct configuration_entry *
+create_def_configuration_entry(const char *name)
+{
+ struct common_cache_entry_params positive_params, negative_params;
+ struct mp_cache_entry_params mp_params;
+ struct timeval default_common_timeout, default_mp_timeout;
+
+ struct configuration_entry *res = NULL;
+
+ TRACE_IN(create_def_configuration_entry);
+ memset(&positive_params, 0,
+ sizeof(struct common_cache_entry_params));
+ positive_params.cep.entry_type = CET_COMMON;
+ positive_params.cache_entries_size = DEFAULT_CACHE_HT_SIZE;
+ positive_params.max_elemsize = DEFAULT_POSITIVE_ELEMENTS_SIZE;
+ positive_params.satisf_elemsize = DEFAULT_POSITIVE_ELEMENTS_SIZE / 2;
+ positive_params.max_lifetime.tv_sec = DEFAULT_POSITIVE_LIFETIME;
+ positive_params.confidence_threshold = DEFAULT_POSITIVE_CONF_THRESH;
+ positive_params.policy = CPT_LRU;
+
+ memcpy(&negative_params, &positive_params,
+ sizeof(struct common_cache_entry_params));
+ negative_params.max_elemsize = DEFAULT_NEGATIVE_ELEMENTS_SIZE;
+ negative_params.satisf_elemsize = DEFAULT_NEGATIVE_ELEMENTS_SIZE / 2;
+ negative_params.max_lifetime.tv_sec = DEFAULT_NEGATIVE_LIFETIME;
+ negative_params.confidence_threshold = DEFAULT_NEGATIVE_CONF_THRESH;
+ negative_params.policy = CPT_FIFO;
+
+ memset(&default_common_timeout, 0, sizeof(struct timeval));
+ default_common_timeout.tv_sec = DEFAULT_COMMON_ENTRY_TIMEOUT;
+
+ memset(&default_mp_timeout, 0, sizeof(struct timeval));
+ default_mp_timeout.tv_sec = DEFAULT_MP_ENTRY_TIMEOUT;
+
+ memset(&mp_params, 0,
+ sizeof(struct mp_cache_entry_params));
+ mp_params.cep.entry_type = CET_MULTIPART;
+ mp_params.max_elemsize = DEFAULT_MULTIPART_ELEMENTS_SIZE;
+ mp_params.max_sessions = DEFAULT_MULITPART_SESSIONS_SIZE;
+ mp_params.max_lifetime.tv_sec = DEFAULT_MULITPART_LIFETIME;
+
+ res = create_configuration_entry(name, &default_common_timeout,
+ &default_mp_timeout, &positive_params, &negative_params,
+ &mp_params);
+
+ TRACE_OUT(create_def_configuration_entry);
+ return (res);
+}
+
+void
+destroy_configuration_entry(struct configuration_entry *entry)
+{
+ TRACE_IN(destroy_configuration_entry);
+ assert(entry != NULL);
+ pthread_mutex_destroy(&entry->positive_cache_lock);
+ pthread_mutex_destroy(&entry->negative_cache_lock);
+ pthread_mutex_destroy(&entry->mp_cache_lock);
+ free(entry->name);
+ free(entry->positive_cache_params.cep.entry_name);
+ free(entry->negative_cache_params.cep.entry_name);
+ free(entry->mp_cache_params.cep.entry_name);
+ free(entry->mp_cache_entries);
+ free(entry);
+ TRACE_OUT(destroy_configuration_entry);
+}
+
+int
+add_configuration_entry(struct configuration *config,
+ struct configuration_entry *entry)
+{
+ TRACE_IN(add_configuration_entry);
+ assert(entry != NULL);
+ assert(entry->name != NULL);
+ if (configuration_find_entry(config, entry->name) != NULL) {
+ TRACE_OUT(add_configuration_entry);
+ return (-1);
+ }
+
+ if (config->entries_size == config->entries_capacity) {
+ struct configuration_entry **new_entries;
+
+ config->entries_capacity *= 2;
+ new_entries = calloc(1,
+ sizeof(*new_entries) *
+ config->entries_capacity);
+ assert(new_entries != NULL);
+ memcpy(new_entries, config->entries,
+ sizeof(struct configuration_entry *) *
+ config->entries_size);
+
+ free(config->entries);
+ config->entries = new_entries;
+ }
+
+ config->entries[config->entries_size++] = entry;
+ qsort(config->entries, config->entries_size,
+ sizeof(struct configuration_entry *),
+ configuration_entry_sort_cmp);
+
+ TRACE_OUT(add_configuration_entry);
+ return (0);
+}
+
+size_t
+configuration_get_entries_size(struct configuration *config)
+{
+ TRACE_IN(configuration_get_entries_size);
+ assert(config != NULL);
+ TRACE_OUT(configuration_get_entries_size);
+ return (config->entries_size);
+}
+
+struct configuration_entry *
+configuration_get_entry(struct configuration *config, size_t index)
+{
+ TRACE_IN(configuration_get_entry);
+ assert(config != NULL);
+ assert(index < config->entries_size);
+ TRACE_OUT(configuration_get_entry);
+ return (config->entries[index]);
+}
+
+struct configuration_entry *
+configuration_find_entry(struct configuration *config,
+ const char *name)
+{
+ struct configuration_entry **retval;
+
+ TRACE_IN(configuration_find_entry);
+
+ retval = bsearch(name, config->entries, config->entries_size,
+ sizeof(struct configuration_entry *), configuration_entry_cmp);
+ TRACE_OUT(configuration_find_entry);
+
+ return ((retval != NULL) ? *retval : NULL);
+}
+
+/*
+ * All multipart cache entries are stored in the configuration_entry in the
+ * sorted array (sorted by names). The 3 functions below manage this array.
+ */
+
+int
+configuration_entry_add_mp_cache_entry(struct configuration_entry *config_entry,
+ cache_entry c_entry)
+{
+ cache_entry *new_mp_entries, *old_mp_entries;
+
+ TRACE_IN(configuration_entry_add_mp_cache_entry);
+ ++config_entry->mp_cache_entries_size;
+ new_mp_entries = malloc(sizeof(*new_mp_entries) *
+ config_entry->mp_cache_entries_size);
+ assert(new_mp_entries != NULL);
+ new_mp_entries[0] = c_entry;
+
+ if (config_entry->mp_cache_entries_size - 1 > 0) {
+ memcpy(new_mp_entries + 1,
+ config_entry->mp_cache_entries,
+ (config_entry->mp_cache_entries_size - 1) *
+ sizeof(cache_entry));
+ }
+
+ old_mp_entries = config_entry->mp_cache_entries;
+ config_entry->mp_cache_entries = new_mp_entries;
+ free(old_mp_entries);
+
+ qsort(config_entry->mp_cache_entries,
+ config_entry->mp_cache_entries_size,
+ sizeof(cache_entry),
+ configuration_entry_cache_mp_sort_cmp);
+
+ TRACE_OUT(configuration_entry_add_mp_cache_entry);
+ return (0);
+}
+
+cache_entry
+configuration_entry_find_mp_cache_entry(
+ struct configuration_entry *config_entry, const char *mp_name)
+{
+ cache_entry *result;
+
+ TRACE_IN(configuration_entry_find_mp_cache_entry);
+ result = bsearch(mp_name, config_entry->mp_cache_entries,
+ config_entry->mp_cache_entries_size,
+ sizeof(cache_entry), configuration_entry_cache_mp_cmp);
+
+ if (result == NULL) {
+ TRACE_OUT(configuration_entry_find_mp_cache_entry);
+ return (NULL);
+ } else {
+ TRACE_OUT(configuration_entry_find_mp_cache_entry);
+ return (*result);
+ }
+}
+
+/*
+ * Searches for all multipart entries with names starting with mp_name.
+ * Needed for cache flushing.
+ */
+int
+configuration_entry_find_mp_cache_entries(
+ struct configuration_entry *config_entry, const char *mp_name,
+ cache_entry **start, cache_entry **finish)
+{
+ cache_entry *result;
+
+ TRACE_IN(configuration_entry_find_mp_cache_entries);
+ result = bsearch(mp_name, config_entry->mp_cache_entries,
+ config_entry->mp_cache_entries_size,
+ sizeof(cache_entry), configuration_entry_cache_mp_part_cmp);
+
+ if (result == NULL) {
+ TRACE_OUT(configuration_entry_find_mp_cache_entries);
+ return (-1);
+ }
+
+ *start = result;
+ *finish = result + 1;
+
+ while (*start != config_entry->mp_cache_entries) {
+ if (configuration_entry_cache_mp_part_cmp(mp_name, *start - 1) == 0)
+ *start = *start - 1;
+ else
+ break;
+ }
+
+ while (*finish != config_entry->mp_cache_entries +
+ config_entry->mp_cache_entries_size) {
+
+ if (configuration_entry_cache_mp_part_cmp(
+ mp_name, *finish) == 0)
+ *finish = *finish + 1;
+ else
+ break;
+ }
+
+ TRACE_OUT(configuration_entry_find_mp_cache_entries);
+ return (0);
+}
+
+/*
+ * Configuration entry uses rwlock to handle access to its fields.
+ */
+void
+configuration_lock_rdlock(struct configuration *config)
+{
+ TRACE_IN(configuration_lock_rdlock);
+ pthread_rwlock_rdlock(&config->rwlock);
+ TRACE_OUT(configuration_lock_rdlock);
+}
+
+void
+configuration_lock_wrlock(struct configuration *config)
+{
+ TRACE_IN(configuration_lock_wrlock);
+ pthread_rwlock_wrlock(&config->rwlock);
+ TRACE_OUT(configuration_lock_wrlock);
+}
+
+void
+configuration_unlock(struct configuration *config)
+{
+ TRACE_IN(configuration_unlock);
+ pthread_rwlock_unlock(&config->rwlock);
+ TRACE_OUT(configuration_unlock);
+}
+
+/*
+ * Configuration entry uses 3 mutexes to handle cache operations. They are
+ * acquired by configuration_lock_entry and configuration_unlock_entry
+ * functions.
+ */
+void
+configuration_lock_entry(struct configuration_entry *entry,
+ enum config_entry_lock_type lock_type)
+{
+ TRACE_IN(configuration_lock_entry);
+ assert(entry != NULL);
+
+ switch (lock_type) {
+ case CELT_POSITIVE:
+ pthread_mutex_lock(&entry->positive_cache_lock);
+ break;
+ case CELT_NEGATIVE:
+ pthread_mutex_lock(&entry->negative_cache_lock);
+ break;
+ case CELT_MULTIPART:
+ pthread_mutex_lock(&entry->mp_cache_lock);
+ break;
+ default:
+ /* should be unreachable */
+ break;
+ }
+ TRACE_OUT(configuration_lock_entry);
+}
+
+void
+configuration_unlock_entry(struct configuration_entry *entry,
+ enum config_entry_lock_type lock_type)
+{
+ TRACE_IN(configuration_unlock_entry);
+ assert(entry != NULL);
+
+ switch (lock_type) {
+ case CELT_POSITIVE:
+ pthread_mutex_unlock(&entry->positive_cache_lock);
+ break;
+ case CELT_NEGATIVE:
+ pthread_mutex_unlock(&entry->negative_cache_lock);
+ break;
+ case CELT_MULTIPART:
+ pthread_mutex_unlock(&entry->mp_cache_lock);
+ break;
+ default:
+ /* should be unreachable */
+ break;
+ }
+ TRACE_OUT(configuration_unlock_entry);
+}
+
+struct configuration *
+init_configuration(void)
+{
+ struct configuration *retval;
+
+ TRACE_IN(init_configuration);
+ retval = calloc(1, sizeof(*retval));
+ assert(retval != NULL);
+
+ retval->entries_capacity = INITIAL_ENTRIES_CAPACITY;
+ retval->entries = calloc(1,
+ sizeof(*retval->entries) *
+ retval->entries_capacity);
+ assert(retval->entries != NULL);
+
+ pthread_rwlock_init(&retval->rwlock, NULL);
+
+ TRACE_OUT(init_configuration);
+ return (retval);
+}
+
+void
+fill_configuration_defaults(struct configuration *config)
+{
+ size_t len, i;
+
+ TRACE_IN(fill_configuration_defaults);
+ assert(config != NULL);
+
+ if (config->socket_path != NULL)
+ free(config->socket_path);
+
+ len = strlen(DEFAULT_SOCKET_PATH);
+ config->socket_path = calloc(1, len + 1);
+ assert(config->socket_path != NULL);
+ memcpy(config->socket_path, DEFAULT_SOCKET_PATH, len);
+
+ len = strlen(DEFAULT_PIDFILE_PATH);
+ config->pidfile_path = calloc(1, len + 1);
+ assert(config->pidfile_path != NULL);
+ memcpy(config->pidfile_path, DEFAULT_PIDFILE_PATH, len);
+
+ config->socket_mode = S_IFSOCK | S_IRUSR | S_IWUSR |
+ S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
+ config->force_unlink = 1;
+
+ config->query_timeout = DEFAULT_QUERY_TIMEOUT;
+ config->threads_num = DEFAULT_THREADS_NUM;
+
+ for (i = 0; i < config->entries_size; ++i)
+ destroy_configuration_entry(config->entries[i]);
+ config->entries_size = 0;
+
+ TRACE_OUT(fill_configuration_defaults);
+}
+
+void
+destroy_configuration(struct configuration *config)
+{
+ unsigned int i;
+
+ TRACE_IN(destroy_configuration);
+ assert(config != NULL);
+ free(config->pidfile_path);
+ free(config->socket_path);
+
+ for (i = 0; i < config->entries_size; ++i)
+ destroy_configuration_entry(config->entries[i]);
+ free(config->entries);
+
+ pthread_rwlock_destroy(&config->rwlock);
+ free(config);
+ TRACE_OUT(destroy_configuration);
+}
diff --git a/usr.sbin/nscd/config.h b/usr.sbin/nscd/config.h
new file mode 100644
index 0000000..29770e6
--- /dev/null
+++ b/usr.sbin/nscd/config.h
@@ -0,0 +1,150 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 __NSCD_CONFIG_H__
+#define __NSCD_CONFIG_H__
+
+#include "cachelib.h"
+
+#define DEFAULT_QUERY_TIMEOUT 8
+#define DEFAULT_THREADS_NUM 8
+
+#define DEFAULT_COMMON_ENTRY_TIMEOUT 10
+#define DEFAULT_MP_ENTRY_TIMEOUT 60
+#define DEFAULT_CACHE_HT_SIZE 257
+
+#define INITIAL_ENTRIES_CAPACITY 8
+#define DEFAULT_SOCKET_PATH "/var/run/nscd"
+#define DEFAULT_PIDFILE_PATH "/var/run/nscd.pid"
+
+#define DEFAULT_POSITIVE_ELEMENTS_SIZE (2048)
+#define DEFAULT_POSITIVE_LIFETIME (3600)
+#define DEFAULT_POSITIVE_CONF_THRESH (1)
+
+#define DEFAULT_NEGATIVE_ELEMENTS_SIZE (2048)
+#define DEFAULT_NEGATIVE_LIFETIME (60)
+#define DEFAULT_NEGATIVE_CONF_THRESH (1) /* (2) ??? */
+
+#define DEFAULT_MULTIPART_ELEMENTS_SIZE (1024 * 8)
+#define DEFAULT_MULITPART_SESSIONS_SIZE (1024)
+#define DEFAULT_MULITPART_LIFETIME (3600)
+
+extern const char *c_default_entries[6];
+
+/*
+ * Configuration entry represents the details of each cache entry in the
+ * config file (i.e. passwd or group). Its purpose also is to acquire locks
+ * of three different types (for usual read/write caching, for multipart
+ * caching and for caching of the negative results) for that cache entry.
+ */
+struct configuration_entry {
+ struct common_cache_entry_params positive_cache_params;
+ struct common_cache_entry_params negative_cache_params;
+ struct mp_cache_entry_params mp_cache_params;
+
+ /*
+ * configuration_entry holds pointers for all actual cache_entries,
+ * which are used for it. There is one for positive caching, one for
+ * for negative caching, and several (one per each euid/egid) for
+ * multipart caching.
+ */
+ cache_entry positive_cache_entry;
+ cache_entry negative_cache_entry;
+
+ cache_entry *mp_cache_entries;
+ size_t mp_cache_entries_size;
+
+ struct timeval common_query_timeout;
+ struct timeval mp_query_timeout;
+
+ char *name;
+ pthread_mutex_t positive_cache_lock;
+ pthread_mutex_t negative_cache_lock;
+ pthread_mutex_t mp_cache_lock;
+
+ int perform_actual_lookups;
+ int enabled;
+};
+
+/*
+ * Contains global configuration options and array of all configuration entries
+ */
+struct configuration {
+ char *pidfile_path;
+ char *socket_path;
+
+ struct configuration_entry **entries;
+ size_t entries_capacity;
+ size_t entries_size;
+
+ pthread_rwlock_t rwlock;
+
+ mode_t socket_mode;
+ int force_unlink;
+ int query_timeout;
+
+ int threads_num;
+};
+
+enum config_entry_lock_type {
+ CELT_POSITIVE,
+ CELT_NEGATIVE,
+ CELT_MULTIPART
+};
+
+struct configuration *init_configuration(void);
+void destroy_configuration(struct configuration *);
+void fill_configuration_defaults(struct configuration *);
+
+int add_configuration_entry(struct configuration *,
+ struct configuration_entry *);
+struct configuration_entry *create_def_configuration_entry(const char *);
+void destroy_configuration_entry(struct configuration_entry *);
+size_t configuration_get_entries_size(struct configuration *);
+struct configuration_entry *configuration_get_entry(struct configuration *,
+ size_t);
+struct configuration_entry *configuration_find_entry(struct configuration *,
+ const char *);
+
+int configuration_entry_add_mp_cache_entry(struct configuration_entry *,
+ cache_entry);
+cache_entry configuration_entry_find_mp_cache_entry(
+ struct configuration_entry *, const char *);
+int configuration_entry_find_mp_cache_entries(struct configuration_entry *,
+ const char *, cache_entry **, cache_entry **);
+
+void configuration_lock_rdlock(struct configuration *config);
+void configuration_lock_wrlock(struct configuration *config);
+void configuration_unlock(struct configuration *config);
+
+void configuration_lock_entry(struct configuration_entry *,
+ enum config_entry_lock_type);
+void configuration_unlock_entry(struct configuration_entry *,
+ enum config_entry_lock_type);
+
+#endif
diff --git a/usr.sbin/nscd/debug.c b/usr.sbin/nscd/debug.c
new file mode 100644
index 0000000..48130b4
--- /dev/null
+++ b/usr.sbin/nscd/debug.c
@@ -0,0 +1,150 @@
+/*-
+ * Copyright (c) 2004 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <stdio.h>
+
+#include "debug.h"
+
+static int trace_level = 0;
+static int trace_level_bk = 0;
+
+void
+nscd_trace_in(const char *s, const char *f, int l)
+{
+ int i;
+ if (trace_level < TRACE_WANTED)
+ {
+ for (i = 0; i < trace_level; ++i)
+ printf("\t");
+
+ printf("=> %s\n", s);
+ }
+
+ ++trace_level;
+}
+
+void
+nscd_trace_point(const char *f, int l)
+{
+ int i;
+
+ if (trace_level < TRACE_WANTED)
+ {
+ for (i = 0; i < trace_level - 1; ++i)
+ printf("\t");
+
+ printf("= %s: %d\n", f, l);
+ }
+}
+
+void
+nscd_trace_msg(const char *msg, const char *f, int l)
+{
+ int i;
+
+ if (trace_level < TRACE_WANTED)
+ {
+ for (i = 0; i < trace_level - 1; ++i)
+ printf("\t");
+
+ printf("= MSG %s, %s: %d\n", msg, f, l);
+ }
+}
+
+void
+nscd_trace_ptr(const char *desc, const void *p, const char *f, int l)
+{
+ int i;
+
+ if (trace_level < TRACE_WANTED)
+ {
+ for (i = 0; i < trace_level - 1; ++i)
+ printf("\t");
+
+ printf("= PTR %s: %p, %s: %d\n", desc, p, f, l);
+ }
+}
+
+void
+nscd_trace_int(const char *desc, int i, const char *f, int l)
+{
+ int j;
+
+ if (trace_level < TRACE_WANTED)
+ {
+ for (j = 0; j < trace_level - 1; ++j)
+ printf("\t");
+
+ printf("= INT %s: %i, %s: %d\n",desc, i, f, l);
+ }
+}
+
+void
+nscd_trace_str(const char *desc, const char *s, const char *f, int l)
+{
+ int i;
+
+ if (trace_level < TRACE_WANTED)
+ {
+ for (i = 0; i < trace_level - 1; ++i)
+ printf("\t");
+
+ printf("= STR %s: '%s', %s: %d\n", desc, s, f, l);
+ }
+}
+
+void
+nscd_trace_out(const char *s, const char *f, int l)
+{
+ int i;
+
+ --trace_level;
+ if (trace_level < TRACE_WANTED)
+ {
+ for (i = 0; i < trace_level; ++i)
+ printf("\t");
+
+ printf("<= %s\n", s);
+ }
+}
+
+void
+nscd_trace_on(void)
+{
+ trace_level = trace_level_bk;
+ trace_level_bk = 0;
+}
+
+void
+nscd_trace_off(void)
+{
+ trace_level_bk = trace_level;
+ trace_level = 1024;
+}
diff --git a/usr.sbin/nscd/debug.h b/usr.sbin/nscd/debug.h
new file mode 100644
index 0000000..e345637
--- /dev/null
+++ b/usr.sbin/nscd/debug.h
@@ -0,0 +1,67 @@
+/*-
+ * Copyright (c) 2004 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 __NSCD_DEBUG_H__
+#define __NSCD_DEBUG_H__
+
+#define TRACE_WANTED 32
+
+/* #ifndef NDEBUG */
+#if 0
+#define TRACE_IN(x) nscd_trace_in(#x, __FILE__, __LINE__)
+#define TRACE_POINT() nscd_trace_point(__FILE__, __LINE__)
+#define TRACE_MSG(x) nscd_trace_msg(x, __FILE__, __LINE__)
+#define TRACE_PTR(p) nscd_trace_ptr(#p, p, __FILE__, __LINE__)
+#define TRACE_INT(i) nscd_trace_int(#i, i, __FILE__, __LINE__)
+#define TRACE_STR(s) nscd_trace_str(#s, s, __FILE__, __LINE__)
+#define TRACE_OUT(x) nscd_trace_out(#x, __FILE__, __LINE__)
+#define TRACE_ON() nscd_trace_on()
+#define TRACE_OFF() nscd_trace_off()
+#else
+#define TRACE_IN(x) (void)0
+#define TRACE_POINT() (void)0
+#define TRACE_MSG(x) (void)0
+#define TRACE_PTR(p) (void)0
+#define TRACE_INT(i) (void)0
+#define TRACE_STR(s) (void)0
+#define TRACE_OUT(x) (void)0
+#define TRACE_ON() (void)0
+#define TRACE_OFF() (void)0
+#endif
+
+void nscd_trace_in(const char *, const char *, int);
+void nscd_trace_point(const char *, int);
+void nscd_trace_msg(const char *, const char *, int);
+void nscd_trace_ptr(const char *, const void *, const char *, int);
+void nscd_trace_int(const char *, int, const char *, int);
+void nscd_trace_str(const char *, const char *, const char *, int);
+void nscd_trace_out(const char *, const char *, int);
+void nscd_trace_on(void);
+void nscd_trace_off(void);
+
+#endif
diff --git a/usr.sbin/nscd/hashtable.h b/usr.sbin/nscd/hashtable.h
new file mode 100644
index 0000000..26e6eb9
--- /dev/null
+++ b/usr.sbin/nscd/hashtable.h
@@ -0,0 +1,221 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 __CACHELIB_HASHTABLE_H__
+#define __CACHELIB_HASHTABLE_H__
+
+#include <string.h>
+
+#define HASHTABLE_INITIAL_ENTRIES_CAPACITY 8
+typedef unsigned int hashtable_index_t;
+
+/*
+ * This file contains queue.h-like macro definitions for hash tables.
+ * Hash table is organized as an array of the specified size of the user
+ * defined (with HASTABLE_ENTRY_HEAD) structures. Each hash table
+ * entry (user defined structure) stores its elements in the sorted array.
+ * You can place elements into the hash table, retrieve elements with
+ * specified key, traverse through all elements, and delete them.
+ * New elements are placed into the hash table by using the compare and
+ * hashing functions, provided by the user.
+ */
+
+/*
+ * Defines the hash table entry structure, that uses specified type of
+ * elements.
+ */
+#define HASHTABLE_ENTRY_HEAD(name, type) struct name { \
+ type *values; \
+ size_t capacity; \
+ size_t size; \
+}
+
+/*
+ * Defines the hash table structure, which uses the specified type of entries.
+ * The only restriction for entries is that is that they should have the field,
+ * defined with HASHTABLE_ENTRY_HEAD macro.
+ */
+#define HASHTABLE_HEAD(name, entry) struct name { \
+ struct entry *entries; \
+ size_t entries_size; \
+}
+
+#define HASHTABLE_ENTRIES_COUNT(table) \
+ ((table)->entries_size)
+
+/*
+ * Unlike most of queue.h data types, hash tables can not be initialized
+ * statically - so there is no HASHTABLE_HEAD_INITIALIZED macro.
+ */
+#define HASHTABLE_INIT(table, type, field, _entries_size) \
+ do { \
+ hashtable_index_t var; \
+ (table)->entries = calloc(1, \
+ sizeof(*(table)->entries) * (_entries_size)); \
+ (table)->entries_size = (_entries_size); \
+ for (var = 0; var < HASHTABLE_ENTRIES_COUNT(table); ++var) {\
+ (table)->entries[var].field.capacity = \
+ HASHTABLE_INITIAL_ENTRIES_CAPACITY; \
+ (table)->entries[var].field.size = 0; \
+ (table)->entries[var].field.values = malloc( \
+ sizeof(type) * \
+ HASHTABLE_INITIAL_ENTRIES_CAPACITY); \
+ assert((table)->entries[var].field.values != NULL);\
+ } \
+ } while (0)
+
+/*
+ * All initialized hashtables should be destroyed with this macro.
+ */
+#define HASHTABLE_DESTROY(table, field) \
+ do { \
+ hashtable_index_t var; \
+ for (var = 0; var < HASHTABLE_ENTRIES_COUNT(table); ++var) {\
+ free((table)->entries[var].field.values); \
+ } \
+ } while (0)
+
+#define HASHTABLE_GET_ENTRY(table, hash) \
+ (&((table)->entries[hash]))
+
+/*
+ * Traverses through all hash table entries
+ */
+#define HASHTABLE_FOREACH(table, var) \
+ for ((var) = &((table)->entries[0]); \
+ (var) < &((table)->entries[HASHTABLE_ENTRIES_COUNT(table)]);\
+ ++(var))
+
+/*
+ * Traverses through all elements of the specified hash table entry
+ */
+#define HASHTABLE_ENTRY_FOREACH(entry, field, var) \
+ for ((var) = &((entry)->field.values[0]); \
+ (var) < &((entry)->field.values[(entry)->field.size]); \
+ ++(var))
+
+#define HASHTABLE_ENTRY_CLEAR(entry, field) \
+ ((entry)->field.size = 0)
+
+#define HASHTABLE_ENTRY_SIZE(entry, field) \
+ ((entry)->field.size)
+
+#define HASHTABLE_ENTRY_CAPACITY(entry, field) \
+ ((entry)->field.capacity)
+
+#define HASHTABLE_ENTRY_CAPACITY_INCREASE(entry, field, type) \
+ do { \
+ (entry)->field.capacity *= 2; \
+ (entry)->field.values = realloc((entry)->field.values, \
+ (entry)->field.capacity * sizeof(type)); \
+ } while (0)
+
+#define HASHTABLE_ENTRY_CAPACITY_DECREASE(entry, field, type) \
+ do { \
+ (entry)->field.capacity /= 2; \
+ (entry)->field.values = realloc((entry)->field.values, \
+ (entry)->field.capacity * sizeof(type)); \
+ } while (0)
+
+/*
+ * Generates prototypes for the hash table functions
+ */
+#define HASHTABLE_PROTOTYPE(name, entry_, type) \
+hashtable_index_t name##_CALCULATE_HASH(struct name *, type *); \
+void name##_ENTRY_STORE(struct entry_*, type *); \
+type *name##_ENTRY_FIND(struct entry_*, type *); \
+type *name##_ENTRY_FIND_SPECIAL(struct entry_ *, type *, \
+ int (*) (const void *, const void *)); \
+void name##_ENTRY_REMOVE(struct entry_*, type *);
+
+/*
+ * Generates implementations of the hash table functions
+ */
+#define HASHTABLE_GENERATE(name, entry_, type, field, HASH, CMP) \
+hashtable_index_t name##_CALCULATE_HASH(struct name *table, type *data) \
+{ \
+ \
+ return HASH(data, table->entries_size); \
+} \
+ \
+void name##_ENTRY_STORE(struct entry_ *the_entry, type *data) \
+{ \
+ \
+ if (the_entry->field.size == the_entry->field.capacity) \
+ HASHTABLE_ENTRY_CAPACITY_INCREASE(the_entry, field, type);\
+ \
+ memcpy(&(the_entry->field.values[the_entry->field.size++]), \
+ data, \
+ sizeof(type)); \
+ qsort(the_entry->field.values, the_entry->field.size, \
+ sizeof(type), CMP); \
+} \
+ \
+type *name##_ENTRY_FIND(struct entry_ *the_entry, type *key) \
+{ \
+ \
+ return ((type *)bsearch(key, the_entry->field.values, \
+ the_entry->field.size, sizeof(type), CMP)); \
+} \
+ \
+type *name##_ENTRY_FIND_SPECIAL(struct entry_ *the_entry, type *key, \
+ int (*compar) (const void *, const void *)) \
+{ \
+ return ((type *)bsearch(key, the_entry->field.values, \
+ the_entry->field.size, sizeof(type), compar)); \
+} \
+ \
+void name##_ENTRY_REMOVE(struct entry_ *the_entry, type *del_elm) \
+{ \
+ \
+ memmove(del_elm, del_elm + 1, \
+ (&the_entry->field.values[--the_entry->field.size] - del_elm) *\
+ sizeof(type)); \
+}
+
+/*
+ * Macro definitions below wrap the functions, generaed with
+ * HASHTABLE_GENERATE macro. You should use them and avoid using generated
+ * functions directly.
+ */
+#define HASHTABLE_CALCULATE_HASH(name, table, data) \
+ (name##_CALCULATE_HASH((table), data))
+
+#define HASHTABLE_ENTRY_STORE(name, entry, data) \
+ name##_ENTRY_STORE((entry), data)
+
+#define HASHTABLE_ENTRY_FIND(name, entry, key) \
+ (name##_ENTRY_FIND((entry), (key)))
+
+#define HASHTABLE_ENTRY_FIND_SPECIAL(name, entry, key, cmp) \
+ (name##_ENTRY_FIND_SPECIAL((entry), (key), (cmp)))
+
+#define HASHTABLE_ENTRY_REMOVE(name, entry, del_elm) \
+ name##_ENTRY_REMOVE((entry), (del_elm))
+
+#endif
diff --git a/usr.sbin/nscd/log.c b/usr.sbin/nscd/log.c
new file mode 100644
index 0000000..5f4c578
--- /dev/null
+++ b/usr.sbin/nscd/log.c
@@ -0,0 +1,79 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <assert.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syslog.h>
+
+#include "log.h"
+
+void
+__log_msg(int level, const char *sender, const char *message, ...)
+{
+ va_list ap;
+ char *fmessage;
+
+ fmessage = NULL;
+ va_start(ap, message);
+ vasprintf(&fmessage, message, ap);
+ va_end(ap);
+ assert(fmessage != NULL);
+
+ printf("M%d from %s: %s\n", level, sender, fmessage);
+#ifndef NO_SYSLOG
+ if (level == 0)
+ syslog(LOG_INFO, "nscd message (from %s): %s", sender,
+ fmessage);
+#endif
+ free(fmessage);
+}
+
+void
+__log_err(int level, const char *sender, const char *error, ...)
+{
+ va_list ap;
+ char *ferror;
+
+ ferror = NULL;
+ va_start(ap, error);
+ vasprintf(&ferror, error, ap);
+ va_end(ap);
+ assert(ferror != NULL);
+
+ printf("E%d from %s: %s\n", level, sender, ferror);
+
+#ifndef NO_SYSLOG
+ if (level == 0)
+ syslog(LOG_ERR, "nscd error (from %s): %s", sender, ferror);
+#endif
+ free(ferror);
+}
diff --git a/usr.sbin/nscd/log.h b/usr.sbin/nscd/log.h
new file mode 100644
index 0000000..296c98f
--- /dev/null
+++ b/usr.sbin/nscd/log.h
@@ -0,0 +1,43 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 __NSCD_LOG_H__
+#define __NSCD_LOG_H__
+
+#define LOG_MSG_1(sender, msg, ...) __log_msg(1, sender, msg, ##__VA_ARGS__)
+#define LOG_MSG_2(sender, msg, ...) __log_msg(2, sender, msg, ##__VA_ARGS__)
+#define LOG_MSG_3(sender, msg, ...) __log_msg(3, sedner, msg, ##__VA_ARGS__)
+
+#define LOG_ERR_1(sender, err, ...) __log_err(1, sender, err, ##__VA_ARGS__)
+#define LOG_ERR_2(sender, err, ...) __log_err(2, sender, err, ##__VA_ARGS__)
+#define LOG_ERR_3(sender, err, ...) __log_err(3, sender, err, ##__VA_ARGS__)
+
+void __log_msg(int, const char *, const char *, ...);
+void __log_err(int, const char *, const char *, ...);
+
+#endif
diff --git a/usr.sbin/nscd/mp_rs_query.c b/usr.sbin/nscd/mp_rs_query.c
new file mode 100644
index 0000000..4bec517
--- /dev/null
+++ b/usr.sbin/nscd/mp_rs_query.c
@@ -0,0 +1,537 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/event.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <nsswitch.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "cachelib.h"
+#include "config.h"
+#include "debug.h"
+#include "log.h"
+#include "query.h"
+#include "mp_rs_query.h"
+#include "mp_ws_query.h"
+#include "singletons.h"
+
+static int on_mp_read_session_close_notification(struct query_state *);
+static void on_mp_read_session_destroy(struct query_state *);
+static int on_mp_read_session_mapper(struct query_state *);
+/* int on_mp_read_session_request_read1(struct query_state *); */
+static int on_mp_read_session_request_read2(struct query_state *);
+static int on_mp_read_session_request_process(struct query_state *);
+static int on_mp_read_session_response_write1(struct query_state *);
+static int on_mp_read_session_read_request_process(struct query_state *);
+static int on_mp_read_session_read_response_write1(struct query_state *);
+static int on_mp_read_session_read_response_write2(struct query_state *);
+
+/*
+ * This function is used as the query_state's destroy_func to make the
+ * proper cleanup in case of errors.
+ */
+static void
+on_mp_read_session_destroy(struct query_state *qstate)
+{
+ TRACE_IN(on_mp_read_session_destroy);
+ finalize_comm_element(&qstate->request);
+ finalize_comm_element(&qstate->response);
+
+ if (qstate->mdata != NULL) {
+ configuration_lock_entry(qstate->config_entry, CELT_MULTIPART);
+ close_cache_mp_read_session(
+ (cache_mp_read_session)qstate->mdata);
+ configuration_unlock_entry(qstate->config_entry,
+ CELT_MULTIPART);
+ }
+ TRACE_OUT(on_mp_read_session_destroy);
+}
+
+/*
+ * The functions below are used to process multipart read session initiation
+ * requests.
+ * - on_mp_read_session_request_read1 and on_mp_read_session_request_read2 read
+ * the request itself
+ * - on_mp_read_session_request_process processes it
+ * - on_mp_read_session_response_write1 sends the response
+ */
+int
+on_mp_read_session_request_read1(struct query_state *qstate)
+{
+ struct cache_mp_read_session_request *c_mp_rs_request;
+ ssize_t result;
+
+ TRACE_IN(on_mp_read_session_request_read1);
+ if (qstate->kevent_watermark == 0)
+ qstate->kevent_watermark = sizeof(size_t);
+ else {
+ init_comm_element(&qstate->request,
+ CET_MP_READ_SESSION_REQUEST);
+ c_mp_rs_request = get_cache_mp_read_session_request(
+ &qstate->request);
+
+ result = qstate->read_func(qstate,
+ &c_mp_rs_request->entry_length, sizeof(size_t));
+
+ if (result != sizeof(size_t)) {
+ TRACE_OUT(on_mp_read_session_request_read1);
+ return (-1);
+ }
+
+ if (BUFSIZE_INVALID(c_mp_rs_request->entry_length)) {
+ TRACE_OUT(on_mp_read_session_request_read1);
+ return (-1);
+ }
+
+ c_mp_rs_request->entry = calloc(1,
+ c_mp_rs_request->entry_length + 1);
+ assert(c_mp_rs_request->entry != NULL);
+
+ qstate->kevent_watermark = c_mp_rs_request->entry_length;
+ qstate->process_func = on_mp_read_session_request_read2;
+ }
+ TRACE_OUT(on_mp_read_session_request_read1);
+ return (0);
+}
+
+static int
+on_mp_read_session_request_read2(struct query_state *qstate)
+{
+ struct cache_mp_read_session_request *c_mp_rs_request;
+ ssize_t result;
+
+ TRACE_IN(on_mp_read_session_request_read2);
+ c_mp_rs_request = get_cache_mp_read_session_request(&qstate->request);
+
+ result = qstate->read_func(qstate, c_mp_rs_request->entry,
+ c_mp_rs_request->entry_length);
+
+ if (result < 0 || (size_t)result != qstate->kevent_watermark) {
+ LOG_ERR_3("on_mp_read_session_request_read2",
+ "read failed");
+ TRACE_OUT(on_mp_read_session_request_read2);
+ return (-1);
+ }
+
+ qstate->kevent_watermark = 0;
+ qstate->process_func = on_mp_read_session_request_process;
+ TRACE_OUT(on_mp_read_session_request_read2);
+ return (0);
+}
+
+static int
+on_mp_read_session_request_process(struct query_state *qstate)
+{
+ struct cache_mp_read_session_request *c_mp_rs_request;
+ struct cache_mp_read_session_response *c_mp_rs_response;
+ cache_mp_read_session rs;
+ cache_entry c_entry;
+ char *dec_cache_entry_name;
+
+ char *buffer;
+ size_t buffer_size;
+ cache_mp_write_session ws;
+ struct agent *lookup_agent;
+ struct multipart_agent *mp_agent;
+ void *mdata;
+ int res;
+
+ TRACE_IN(on_mp_read_session_request_process);
+ init_comm_element(&qstate->response, CET_MP_READ_SESSION_RESPONSE);
+ c_mp_rs_response = get_cache_mp_read_session_response(
+ &qstate->response);
+ c_mp_rs_request = get_cache_mp_read_session_request(&qstate->request);
+
+ qstate->config_entry = configuration_find_entry(
+ s_configuration, c_mp_rs_request->entry);
+ if (qstate->config_entry == NULL) {
+ c_mp_rs_response->error_code = ENOENT;
+
+ LOG_ERR_2("read_session_request",
+ "can't find configuration entry '%s'."
+ " aborting request", c_mp_rs_request->entry);
+ goto fin;
+ }
+
+ if (qstate->config_entry->enabled == 0) {
+ c_mp_rs_response->error_code = EACCES;
+
+ LOG_ERR_2("read_session_request",
+ "configuration entry '%s' is disabled",
+ c_mp_rs_request->entry);
+ goto fin;
+ }
+
+ if (qstate->config_entry->perform_actual_lookups != 0)
+ dec_cache_entry_name = strdup(
+ qstate->config_entry->mp_cache_params.cep.entry_name);
+ else {
+#ifdef NS_NSCD_EID_CHECKING
+ if (check_query_eids(qstate) != 0) {
+ c_mp_rs_response->error_code = EPERM;
+ goto fin;
+ }
+#endif
+
+ asprintf(&dec_cache_entry_name, "%s%s", qstate->eid_str,
+ qstate->config_entry->mp_cache_params.cep.entry_name);
+ }
+
+ assert(dec_cache_entry_name != NULL);
+
+ configuration_lock_rdlock(s_configuration);
+ c_entry = find_cache_entry(s_cache, dec_cache_entry_name);
+ configuration_unlock(s_configuration);
+
+ if ((c_entry == INVALID_CACHE) &&
+ (qstate->config_entry->perform_actual_lookups != 0))
+ c_entry = register_new_mp_cache_entry(qstate,
+ dec_cache_entry_name);
+
+ free(dec_cache_entry_name);
+
+ if (c_entry != INVALID_CACHE_ENTRY) {
+ configuration_lock_entry(qstate->config_entry, CELT_MULTIPART);
+ rs = open_cache_mp_read_session(c_entry);
+ configuration_unlock_entry(qstate->config_entry,
+ CELT_MULTIPART);
+
+ if ((rs == INVALID_CACHE_MP_READ_SESSION) &&
+ (qstate->config_entry->perform_actual_lookups != 0)) {
+ lookup_agent = find_agent(s_agent_table,
+ c_mp_rs_request->entry, MULTIPART_AGENT);
+
+ if ((lookup_agent != NULL) &&
+ (lookup_agent->type == MULTIPART_AGENT)) {
+ mp_agent = (struct multipart_agent *)
+ lookup_agent;
+ mdata = mp_agent->mp_init_func();
+
+ /*
+ * Multipart agents read the whole snapshot
+ * of the data at one time.
+ */
+ configuration_lock_entry(qstate->config_entry,
+ CELT_MULTIPART);
+ ws = open_cache_mp_write_session(c_entry);
+ configuration_unlock_entry(qstate->config_entry,
+ CELT_MULTIPART);
+ if (ws != NULL) {
+ do {
+ buffer = NULL;
+ res = mp_agent->mp_lookup_func(&buffer,
+ &buffer_size,
+ mdata);
+
+ if ((res & NS_TERMINATE) &&
+ (buffer != NULL)) {
+ configuration_lock_entry(
+ qstate->config_entry,
+ CELT_MULTIPART);
+ if (cache_mp_write(ws, buffer,
+ buffer_size) != 0) {
+ abandon_cache_mp_write_session(ws);
+ ws = NULL;
+ }
+ configuration_unlock_entry(
+ qstate->config_entry,
+ CELT_MULTIPART);
+
+ free(buffer);
+ buffer = NULL;
+ } else {
+ configuration_lock_entry(
+ qstate->config_entry,
+ CELT_MULTIPART);
+ close_cache_mp_write_session(ws);
+ configuration_unlock_entry(
+ qstate->config_entry,
+ CELT_MULTIPART);
+
+ free(buffer);
+ buffer = NULL;
+ }
+ } while ((res & NS_TERMINATE) &&
+ (ws != NULL));
+ }
+
+ configuration_lock_entry(qstate->config_entry,
+ CELT_MULTIPART);
+ rs = open_cache_mp_read_session(c_entry);
+ configuration_unlock_entry(qstate->config_entry,
+ CELT_MULTIPART);
+ }
+ }
+
+ if (rs == INVALID_CACHE_MP_READ_SESSION)
+ c_mp_rs_response->error_code = -1;
+ else {
+ qstate->mdata = rs;
+ qstate->destroy_func = on_mp_read_session_destroy;
+
+ configuration_lock_entry(qstate->config_entry,
+ CELT_MULTIPART);
+ if ((qstate->config_entry->mp_query_timeout.tv_sec != 0) ||
+ (qstate->config_entry->mp_query_timeout.tv_usec != 0))
+ memcpy(&qstate->timeout,
+ &qstate->config_entry->mp_query_timeout,
+ sizeof(struct timeval));
+ configuration_unlock_entry(qstate->config_entry,
+ CELT_MULTIPART);
+ }
+ } else
+ c_mp_rs_response->error_code = -1;
+
+fin:
+ qstate->process_func = on_mp_read_session_response_write1;
+ qstate->kevent_watermark = sizeof(int);
+ qstate->kevent_filter = EVFILT_WRITE;
+
+ TRACE_OUT(on_mp_read_session_request_process);
+ return (0);
+}
+
+static int
+on_mp_read_session_response_write1(struct query_state *qstate)
+{
+ struct cache_mp_read_session_response *c_mp_rs_response;
+ ssize_t result;
+
+ TRACE_IN(on_mp_read_session_response_write1);
+ c_mp_rs_response = get_cache_mp_read_session_response(
+ &qstate->response);
+ result = qstate->write_func(qstate, &c_mp_rs_response->error_code,
+ sizeof(int));
+
+ if (result != sizeof(int)) {
+ LOG_ERR_3("on_mp_read_session_response_write1",
+ "write failed");
+ TRACE_OUT(on_mp_read_session_response_write1);
+ return (-1);
+ }
+
+ if (c_mp_rs_response->error_code == 0) {
+ qstate->kevent_watermark = sizeof(int);
+ qstate->process_func = on_mp_read_session_mapper;
+ qstate->kevent_filter = EVFILT_READ;
+ } else {
+ qstate->kevent_watermark = 0;
+ qstate->process_func = NULL;
+ }
+ TRACE_OUT(on_mp_read_session_response_write1);
+ return (0);
+}
+
+/*
+ * Mapper function is used to avoid multiple connections for each session
+ * write or read requests. After processing the request, it does not close
+ * the connection, but waits for the next request.
+ */
+static int
+on_mp_read_session_mapper(struct query_state *qstate)
+{
+ ssize_t result;
+ int elem_type;
+
+ TRACE_IN(on_mp_read_session_mapper);
+ if (qstate->kevent_watermark == 0) {
+ qstate->kevent_watermark = sizeof(int);
+ } else {
+ result = qstate->read_func(qstate, &elem_type, sizeof(int));
+ if (result != sizeof(int)) {
+ LOG_ERR_3("on_mp_read_session_mapper",
+ "read failed");
+ TRACE_OUT(on_mp_read_session_mapper);
+ return (-1);
+ }
+
+ switch (elem_type) {
+ case CET_MP_READ_SESSION_READ_REQUEST:
+ qstate->kevent_watermark = 0;
+ qstate->process_func =
+ on_mp_read_session_read_request_process;
+ break;
+ case CET_MP_READ_SESSION_CLOSE_NOTIFICATION:
+ qstate->kevent_watermark = 0;
+ qstate->process_func =
+ on_mp_read_session_close_notification;
+ break;
+ default:
+ qstate->kevent_watermark = 0;
+ qstate->process_func = NULL;
+ LOG_ERR_3("on_mp_read_session_mapper",
+ "unknown element type");
+ TRACE_OUT(on_mp_read_session_mapper);
+ return (-1);
+ }
+ }
+ TRACE_OUT(on_mp_read_session_mapper);
+ return (0);
+}
+
+/*
+ * The functions below are used to process multipart read sessions read
+ * requests. User doesn't have to pass any kind of data, besides the
+ * request identificator itself. So we don't need any XXX_read functions and
+ * start with the XXX_process function.
+ * - on_mp_read_session_read_request_process processes it
+ * - on_mp_read_session_read_response_write1 and
+ * on_mp_read_session_read_response_write2 sends the response
+ */
+static int
+on_mp_read_session_read_request_process(struct query_state *qstate)
+{
+ struct cache_mp_read_session_read_response *read_response;
+
+ TRACE_IN(on_mp_read_session_response_process);
+ init_comm_element(&qstate->response, CET_MP_READ_SESSION_READ_RESPONSE);
+ read_response = get_cache_mp_read_session_read_response(
+ &qstate->response);
+
+ configuration_lock_entry(qstate->config_entry, CELT_MULTIPART);
+ read_response->error_code = cache_mp_read(
+ (cache_mp_read_session)qstate->mdata, NULL,
+ &read_response->data_size);
+
+ if (read_response->error_code == 0) {
+ read_response->data = malloc(read_response->data_size);
+ assert(read_response != NULL);
+ read_response->error_code = cache_mp_read(
+ (cache_mp_read_session)qstate->mdata,
+ read_response->data,
+ &read_response->data_size);
+ }
+ configuration_unlock_entry(qstate->config_entry, CELT_MULTIPART);
+
+ if (read_response->error_code == 0)
+ qstate->kevent_watermark = sizeof(size_t) + sizeof(int);
+ else
+ qstate->kevent_watermark = sizeof(int);
+ qstate->process_func = on_mp_read_session_read_response_write1;
+ qstate->kevent_filter = EVFILT_WRITE;
+
+ TRACE_OUT(on_mp_read_session_response_process);
+ return (0);
+}
+
+static int
+on_mp_read_session_read_response_write1(struct query_state *qstate)
+{
+ struct cache_mp_read_session_read_response *read_response;
+ ssize_t result;
+
+ TRACE_IN(on_mp_read_session_read_response_write1);
+ read_response = get_cache_mp_read_session_read_response(
+ &qstate->response);
+
+ result = qstate->write_func(qstate, &read_response->error_code,
+ sizeof(int));
+ if (read_response->error_code == 0) {
+ result += qstate->write_func(qstate, &read_response->data_size,
+ sizeof(size_t));
+ if (result < 0 || (size_t)result != qstate->kevent_watermark) {
+ TRACE_OUT(on_mp_read_session_read_response_write1);
+ LOG_ERR_3("on_mp_read_session_read_response_write1",
+ "write failed");
+ return (-1);
+ }
+
+ qstate->kevent_watermark = read_response->data_size;
+ qstate->process_func = on_mp_read_session_read_response_write2;
+ } else {
+ if (result < 0 || (size_t)result != qstate->kevent_watermark) {
+ LOG_ERR_3("on_mp_read_session_read_response_write1",
+ "write failed");
+ TRACE_OUT(on_mp_read_session_read_response_write1);
+ return (-1);
+ }
+
+ qstate->kevent_watermark = 0;
+ qstate->process_func = NULL;
+ }
+
+ TRACE_OUT(on_mp_read_session_read_response_write1);
+ return (0);
+}
+
+static int
+on_mp_read_session_read_response_write2(struct query_state *qstate)
+{
+ struct cache_mp_read_session_read_response *read_response;
+ ssize_t result;
+
+ TRACE_IN(on_mp_read_session_read_response_write2);
+ read_response = get_cache_mp_read_session_read_response(
+ &qstate->response);
+ result = qstate->write_func(qstate, read_response->data,
+ read_response->data_size);
+ if (result < 0 || (size_t)result != qstate->kevent_watermark) {
+ LOG_ERR_3("on_mp_read_session_read_response_write2",
+ "write failed");
+ TRACE_OUT(on_mp_read_session_read_response_write2);
+ return (-1);
+ }
+
+ finalize_comm_element(&qstate->request);
+ finalize_comm_element(&qstate->response);
+
+ qstate->kevent_watermark = sizeof(int);
+ qstate->process_func = on_mp_read_session_mapper;
+ qstate->kevent_filter = EVFILT_READ;
+
+ TRACE_OUT(on_mp_read_session_read_response_write2);
+ return (0);
+}
+
+/*
+ * Handles session close notification by calling close_cache_mp_read_session
+ * function.
+ */
+static int
+on_mp_read_session_close_notification(struct query_state *qstate)
+{
+
+ TRACE_IN(on_mp_read_session_close_notification);
+ configuration_lock_entry(qstate->config_entry, CELT_MULTIPART);
+ close_cache_mp_read_session((cache_mp_read_session)qstate->mdata);
+ configuration_unlock_entry(qstate->config_entry, CELT_MULTIPART);
+ qstate->mdata = NULL;
+ qstate->kevent_watermark = 0;
+ qstate->process_func = NULL;
+ TRACE_OUT(on_mp_read_session_close_notification);
+ return (0);
+}
diff --git a/usr.sbin/nscd/mp_rs_query.h b/usr.sbin/nscd/mp_rs_query.h
new file mode 100644
index 0000000..f74200a
--- /dev/null
+++ b/usr.sbin/nscd/mp_rs_query.h
@@ -0,0 +1,34 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 __NSCD_MP_RS_QUERY_H__
+#define __NSCD_MP_RS_QUERY_H__
+
+int on_mp_read_session_request_read1(struct query_state *);
+
+#endif
diff --git a/usr.sbin/nscd/mp_ws_query.c b/usr.sbin/nscd/mp_ws_query.c
new file mode 100644
index 0000000..5e29748
--- /dev/null
+++ b/usr.sbin/nscd/mp_ws_query.c
@@ -0,0 +1,546 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/event.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "cachelib.h"
+#include "config.h"
+#include "debug.h"
+#include "log.h"
+#include "query.h"
+#include "mp_ws_query.h"
+#include "singletons.h"
+
+static int on_mp_write_session_abandon_notification(struct query_state *);
+static int on_mp_write_session_close_notification(struct query_state *);
+static void on_mp_write_session_destroy(struct query_state *);
+static int on_mp_write_session_mapper(struct query_state *);
+/* int on_mp_write_session_request_read1(struct query_state *); */
+static int on_mp_write_session_request_read2(struct query_state *);
+static int on_mp_write_session_request_process(struct query_state *);
+static int on_mp_write_session_response_write1(struct query_state *);
+static int on_mp_write_session_write_request_read1(struct query_state *);
+static int on_mp_write_session_write_request_read2(struct query_state *);
+static int on_mp_write_session_write_request_process(struct query_state *);
+static int on_mp_write_session_write_response_write1(struct query_state *);
+
+/*
+ * This function is used as the query_state's destroy_func to make the
+ * proper cleanup in case of errors.
+ */
+static void
+on_mp_write_session_destroy(struct query_state *qstate)
+{
+
+ TRACE_IN(on_mp_write_session_destroy);
+ finalize_comm_element(&qstate->request);
+ finalize_comm_element(&qstate->response);
+
+ if (qstate->mdata != NULL) {
+ configuration_lock_entry(qstate->config_entry, CELT_MULTIPART);
+ abandon_cache_mp_write_session(
+ (cache_mp_write_session)qstate->mdata);
+ configuration_unlock_entry(qstate->config_entry,
+ CELT_MULTIPART);
+ }
+ TRACE_OUT(on_mp_write_session_destroy);
+}
+
+/*
+ * The functions below are used to process multipart write session initiation
+ * requests.
+ * - on_mp_write_session_request_read1 and on_mp_write_session_request_read2
+ * read the request itself
+ * - on_mp_write_session_request_process processes it
+ * - on_mp_write_session_response_write1 sends the response
+ */
+int
+on_mp_write_session_request_read1(struct query_state *qstate)
+{
+ struct cache_mp_write_session_request *c_mp_ws_request;
+ ssize_t result;
+
+ TRACE_IN(on_mp_write_session_request_read1);
+ if (qstate->kevent_watermark == 0)
+ qstate->kevent_watermark = sizeof(size_t);
+ else {
+ init_comm_element(&qstate->request,
+ CET_MP_WRITE_SESSION_REQUEST);
+ c_mp_ws_request = get_cache_mp_write_session_request(
+ &qstate->request);
+
+ result = qstate->read_func(qstate,
+ &c_mp_ws_request->entry_length, sizeof(size_t));
+
+ if (result != sizeof(size_t)) {
+ LOG_ERR_3("on_mp_write_session_request_read1",
+ "read failed");
+ TRACE_OUT(on_mp_write_session_request_read1);
+ return (-1);
+ }
+
+ if (BUFSIZE_INVALID(c_mp_ws_request->entry_length)) {
+ LOG_ERR_3("on_mp_write_session_request_read1",
+ "invalid entry_length value");
+ TRACE_OUT(on_mp_write_session_request_read1);
+ return (-1);
+ }
+
+ c_mp_ws_request->entry = calloc(1,
+ c_mp_ws_request->entry_length + 1);
+ assert(c_mp_ws_request->entry != NULL);
+
+ qstate->kevent_watermark = c_mp_ws_request->entry_length;
+ qstate->process_func = on_mp_write_session_request_read2;
+ }
+ TRACE_OUT(on_mp_write_session_request_read1);
+ return (0);
+}
+
+static int
+on_mp_write_session_request_read2(struct query_state *qstate)
+{
+ struct cache_mp_write_session_request *c_mp_ws_request;
+ ssize_t result;
+
+ TRACE_IN(on_mp_write_session_request_read2);
+ c_mp_ws_request = get_cache_mp_write_session_request(&qstate->request);
+
+ result = qstate->read_func(qstate, c_mp_ws_request->entry,
+ c_mp_ws_request->entry_length);
+
+ if (result < 0 || (size_t)result != qstate->kevent_watermark) {
+ LOG_ERR_3("on_mp_write_session_request_read2",
+ "read failed");
+ TRACE_OUT(on_mp_write_session_request_read2);
+ return (-1);
+ }
+
+ qstate->kevent_watermark = 0;
+ qstate->process_func = on_mp_write_session_request_process;
+
+ TRACE_OUT(on_mp_write_session_request_read2);
+ return (0);
+}
+
+static int
+on_mp_write_session_request_process(struct query_state *qstate)
+{
+ struct cache_mp_write_session_request *c_mp_ws_request;
+ struct cache_mp_write_session_response *c_mp_ws_response;
+ cache_mp_write_session ws;
+ cache_entry c_entry;
+ char *dec_cache_entry_name;
+
+ TRACE_IN(on_mp_write_session_request_process);
+ init_comm_element(&qstate->response, CET_MP_WRITE_SESSION_RESPONSE);
+ c_mp_ws_response = get_cache_mp_write_session_response(
+ &qstate->response);
+ c_mp_ws_request = get_cache_mp_write_session_request(&qstate->request);
+
+ qstate->config_entry = configuration_find_entry(
+ s_configuration, c_mp_ws_request->entry);
+ if (qstate->config_entry == NULL) {
+ c_mp_ws_response->error_code = ENOENT;
+
+ LOG_ERR_2("write_session_request",
+ "can't find configuration entry '%s'. "
+ "aborting request", c_mp_ws_request->entry);
+ goto fin;
+ }
+
+ if (qstate->config_entry->enabled == 0) {
+ c_mp_ws_response->error_code = EACCES;
+
+ LOG_ERR_2("write_session_request",
+ "configuration entry '%s' is disabled",
+ c_mp_ws_request->entry);
+ goto fin;
+ }
+
+ if (qstate->config_entry->perform_actual_lookups != 0) {
+ c_mp_ws_response->error_code = EOPNOTSUPP;
+
+ LOG_ERR_2("write_session_request",
+ "entry '%s' performs lookups by itself: "
+ "can't write to it", c_mp_ws_request->entry);
+ goto fin;
+ } else {
+#ifdef NS_NSCD_EID_CHECKING
+ if (check_query_eids(qstate) != 0) {
+ c_mp_ws_response->error_code = EPERM;
+ goto fin;
+ }
+#endif
+ }
+
+ /*
+ * All multipart entries are separated by their name decorations.
+ * For one configuration entry there will be a lot of multipart
+ * cache entries - each with its own decorated name.
+ */
+ asprintf(&dec_cache_entry_name, "%s%s", qstate->eid_str,
+ qstate->config_entry->mp_cache_params.cep.entry_name);
+ assert(dec_cache_entry_name != NULL);
+
+ configuration_lock_rdlock(s_configuration);
+ c_entry = find_cache_entry(s_cache,
+ dec_cache_entry_name);
+ configuration_unlock(s_configuration);
+
+ if (c_entry == INVALID_CACHE_ENTRY)
+ c_entry = register_new_mp_cache_entry(qstate,
+ dec_cache_entry_name);
+
+ free(dec_cache_entry_name);
+
+ assert(c_entry != NULL);
+ configuration_lock_entry(qstate->config_entry, CELT_MULTIPART);
+ ws = open_cache_mp_write_session(c_entry);
+ if (ws == INVALID_CACHE_MP_WRITE_SESSION)
+ c_mp_ws_response->error_code = -1;
+ else {
+ qstate->mdata = ws;
+ qstate->destroy_func = on_mp_write_session_destroy;
+
+ if ((qstate->config_entry->mp_query_timeout.tv_sec != 0) ||
+ (qstate->config_entry->mp_query_timeout.tv_usec != 0))
+ memcpy(&qstate->timeout,
+ &qstate->config_entry->mp_query_timeout,
+ sizeof(struct timeval));
+ }
+ configuration_unlock_entry(qstate->config_entry, CELT_MULTIPART);
+
+fin:
+ qstate->process_func = on_mp_write_session_response_write1;
+ qstate->kevent_watermark = sizeof(int);
+ qstate->kevent_filter = EVFILT_WRITE;
+
+ TRACE_OUT(on_mp_write_session_request_process);
+ return (0);
+}
+
+static int
+on_mp_write_session_response_write1(struct query_state *qstate)
+{
+ struct cache_mp_write_session_response *c_mp_ws_response;
+ ssize_t result;
+
+ TRACE_IN(on_mp_write_session_response_write1);
+ c_mp_ws_response = get_cache_mp_write_session_response(
+ &qstate->response);
+ result = qstate->write_func(qstate, &c_mp_ws_response->error_code,
+ sizeof(int));
+ if (result != sizeof(int)) {
+ LOG_ERR_3("on_mp_write_session_response_write1",
+ "write failed");
+ TRACE_OUT(on_mp_write_session_response_write1);
+ return (-1);
+ }
+
+ if (c_mp_ws_response->error_code == 0) {
+ qstate->kevent_watermark = sizeof(int);
+ qstate->process_func = on_mp_write_session_mapper;
+ qstate->kevent_filter = EVFILT_READ;
+ } else {
+ qstate->kevent_watermark = 0;
+ qstate->process_func = NULL;
+ }
+ TRACE_OUT(on_mp_write_session_response_write1);
+ return (0);
+}
+
+/*
+ * Mapper function is used to avoid multiple connections for each session
+ * write or read requests. After processing the request, it does not close
+ * the connection, but waits for the next request.
+ */
+static int
+on_mp_write_session_mapper(struct query_state *qstate)
+{
+ ssize_t result;
+ int elem_type;
+
+ TRACE_IN(on_mp_write_session_mapper);
+ if (qstate->kevent_watermark == 0) {
+ qstate->kevent_watermark = sizeof(int);
+ } else {
+ result = qstate->read_func(qstate, &elem_type, sizeof(int));
+ if (result != sizeof(int)) {
+ LOG_ERR_3("on_mp_write_session_mapper",
+ "read failed");
+ TRACE_OUT(on_mp_write_session_mapper);
+ return (-1);
+ }
+
+ switch (elem_type) {
+ case CET_MP_WRITE_SESSION_WRITE_REQUEST:
+ qstate->kevent_watermark = sizeof(size_t);
+ qstate->process_func =
+ on_mp_write_session_write_request_read1;
+ break;
+ case CET_MP_WRITE_SESSION_ABANDON_NOTIFICATION:
+ qstate->kevent_watermark = 0;
+ qstate->process_func =
+ on_mp_write_session_abandon_notification;
+ break;
+ case CET_MP_WRITE_SESSION_CLOSE_NOTIFICATION:
+ qstate->kevent_watermark = 0;
+ qstate->process_func =
+ on_mp_write_session_close_notification;
+ break;
+ default:
+ qstate->kevent_watermark = 0;
+ qstate->process_func = NULL;
+ LOG_ERR_2("on_mp_write_session_mapper",
+ "unknown element type");
+ TRACE_OUT(on_mp_write_session_mapper);
+ return (-1);
+ }
+ }
+ TRACE_OUT(on_mp_write_session_mapper);
+ return (0);
+}
+
+/*
+ * The functions below are used to process multipart write sessions write
+ * requests.
+ * - on_mp_write_session_write_request_read1 and
+ * on_mp_write_session_write_request_read2 read the request itself
+ * - on_mp_write_session_write_request_process processes it
+ * - on_mp_write_session_write_response_write1 sends the response
+ */
+static int
+on_mp_write_session_write_request_read1(struct query_state *qstate)
+{
+ struct cache_mp_write_session_write_request *write_request;
+ ssize_t result;
+
+ TRACE_IN(on_mp_write_session_write_request_read1);
+ init_comm_element(&qstate->request,
+ CET_MP_WRITE_SESSION_WRITE_REQUEST);
+ write_request = get_cache_mp_write_session_write_request(
+ &qstate->request);
+
+ result = qstate->read_func(qstate, &write_request->data_size,
+ sizeof(size_t));
+
+ if (result != sizeof(size_t)) {
+ LOG_ERR_3("on_mp_write_session_write_request_read1",
+ "read failed");
+ TRACE_OUT(on_mp_write_session_write_request_read1);
+ return (-1);
+ }
+
+ if (BUFSIZE_INVALID(write_request->data_size)) {
+ LOG_ERR_3("on_mp_write_session_write_request_read1",
+ "invalid data_size value");
+ TRACE_OUT(on_mp_write_session_write_request_read1);
+ return (-1);
+ }
+
+ write_request->data = calloc(1, write_request->data_size);
+ assert(write_request->data != NULL);
+
+ qstate->kevent_watermark = write_request->data_size;
+ qstate->process_func = on_mp_write_session_write_request_read2;
+ TRACE_OUT(on_mp_write_session_write_request_read1);
+ return (0);
+}
+
+static int
+on_mp_write_session_write_request_read2(struct query_state *qstate)
+{
+ struct cache_mp_write_session_write_request *write_request;
+ ssize_t result;
+
+ TRACE_IN(on_mp_write_session_write_request_read2);
+ write_request = get_cache_mp_write_session_write_request(
+ &qstate->request);
+
+ result = qstate->read_func(qstate, write_request->data,
+ write_request->data_size);
+
+ if (result < 0 || (size_t)result != qstate->kevent_watermark) {
+ LOG_ERR_3("on_mp_write_session_write_request_read2",
+ "read failed");
+ TRACE_OUT(on_mp_write_session_write_request_read2);
+ return (-1);
+ }
+
+ qstate->kevent_watermark = 0;
+ qstate->process_func = on_mp_write_session_write_request_process;
+ TRACE_OUT(on_mp_write_session_write_request_read2);
+ return (0);
+}
+
+static int
+on_mp_write_session_write_request_process(struct query_state *qstate)
+{
+ struct cache_mp_write_session_write_request *write_request;
+ struct cache_mp_write_session_write_response *write_response;
+
+ TRACE_IN(on_mp_write_session_write_request_process);
+ init_comm_element(&qstate->response,
+ CET_MP_WRITE_SESSION_WRITE_RESPONSE);
+ write_response = get_cache_mp_write_session_write_response(
+ &qstate->response);
+ write_request = get_cache_mp_write_session_write_request(
+ &qstate->request);
+
+ configuration_lock_entry(qstate->config_entry, CELT_MULTIPART);
+ write_response->error_code = cache_mp_write(
+ (cache_mp_write_session)qstate->mdata,
+ write_request->data,
+ write_request->data_size);
+ configuration_unlock_entry(qstate->config_entry, CELT_MULTIPART);
+
+ qstate->kevent_watermark = sizeof(int);
+ qstate->process_func = on_mp_write_session_write_response_write1;
+ qstate->kevent_filter = EVFILT_WRITE;
+
+ TRACE_OUT(on_mp_write_session_write_request_process);
+ return (0);
+}
+
+static int
+on_mp_write_session_write_response_write1(struct query_state *qstate)
+{
+ struct cache_mp_write_session_write_response *write_response;
+ ssize_t result;
+
+ TRACE_IN(on_mp_write_session_write_response_write1);
+ write_response = get_cache_mp_write_session_write_response(
+ &qstate->response);
+ result = qstate->write_func(qstate, &write_response->error_code,
+ sizeof(int));
+ if (result != sizeof(int)) {
+ LOG_ERR_3("on_mp_write_session_write_response_write1",
+ "write failed");
+ TRACE_OUT(on_mp_write_session_write_response_write1);
+ return (-1);
+ }
+
+ if (write_response->error_code == 0) {
+ finalize_comm_element(&qstate->request);
+ finalize_comm_element(&qstate->response);
+
+ qstate->kevent_watermark = sizeof(int);
+ qstate->process_func = on_mp_write_session_mapper;
+ qstate->kevent_filter = EVFILT_READ;
+ } else {
+ qstate->kevent_watermark = 0;
+ qstate->process_func = 0;
+ }
+
+ TRACE_OUT(on_mp_write_session_write_response_write1);
+ return (0);
+}
+
+/*
+ * Handles abandon notifications. Destroys the session by calling the
+ * abandon_cache_mp_write_session.
+ */
+static int
+on_mp_write_session_abandon_notification(struct query_state *qstate)
+{
+ TRACE_IN(on_mp_write_session_abandon_notification);
+ configuration_lock_entry(qstate->config_entry, CELT_MULTIPART);
+ abandon_cache_mp_write_session((cache_mp_write_session)qstate->mdata);
+ configuration_unlock_entry(qstate->config_entry, CELT_MULTIPART);
+ qstate->mdata = INVALID_CACHE_MP_WRITE_SESSION;
+
+ qstate->kevent_watermark = 0;
+ qstate->process_func = NULL;
+ TRACE_OUT(on_mp_write_session_abandon_notification);
+ return (0);
+}
+
+/*
+ * Handles close notifications. Commits the session by calling
+ * the close_cache_mp_write_session.
+ */
+static int
+on_mp_write_session_close_notification(struct query_state *qstate)
+{
+ TRACE_IN(on_mp_write_session_close_notification);
+ configuration_lock_entry(qstate->config_entry, CELT_MULTIPART);
+ close_cache_mp_write_session((cache_mp_write_session)qstate->mdata);
+ configuration_unlock_entry(qstate->config_entry, CELT_MULTIPART);
+ qstate->mdata = INVALID_CACHE_MP_WRITE_SESSION;
+
+ qstate->kevent_watermark = 0;
+ qstate->process_func = NULL;
+ TRACE_OUT(on_mp_write_session_close_notification);
+ return (0);
+}
+
+cache_entry register_new_mp_cache_entry(struct query_state *qstate,
+ const char *dec_cache_entry_name)
+{
+ cache_entry c_entry;
+ char *en_bkp;
+
+ TRACE_IN(register_new_mp_cache_entry);
+ c_entry = INVALID_CACHE_ENTRY;
+ configuration_lock_entry(qstate->config_entry, CELT_MULTIPART);
+
+ configuration_lock_wrlock(s_configuration);
+ en_bkp = qstate->config_entry->mp_cache_params.cep.entry_name;
+ qstate->config_entry->mp_cache_params.cep.entry_name =
+ (char *)dec_cache_entry_name;
+ register_cache_entry(s_cache, (struct cache_entry_params *)
+ &qstate->config_entry->mp_cache_params);
+ qstate->config_entry->mp_cache_params.cep.entry_name = en_bkp;
+ configuration_unlock(s_configuration);
+
+ configuration_lock_rdlock(s_configuration);
+ c_entry = find_cache_entry(s_cache,
+ dec_cache_entry_name);
+ configuration_unlock(s_configuration);
+
+ configuration_entry_add_mp_cache_entry(qstate->config_entry,
+ c_entry);
+
+ configuration_unlock_entry(qstate->config_entry,
+ CELT_MULTIPART);
+
+ TRACE_OUT(register_new_mp_cache_entry);
+ return (c_entry);
+}
diff --git a/usr.sbin/nscd/mp_ws_query.h b/usr.sbin/nscd/mp_ws_query.h
new file mode 100644
index 0000000..3337ef6
--- /dev/null
+++ b/usr.sbin/nscd/mp_ws_query.h
@@ -0,0 +1,35 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 __NSCD_MP_WS_QUERY_H__
+#define __NSCD_MP_WS_QUERY_H__
+
+int on_mp_write_session_request_read1(struct query_state *);
+cache_entry register_new_mp_cache_entry(struct query_state *, const char *);
+
+#endif
diff --git a/usr.sbin/nscd/nscd.8 b/usr.sbin/nscd/nscd.8
new file mode 100644
index 0000000..f54c185
--- /dev/null
+++ b/usr.sbin/nscd/nscd.8
@@ -0,0 +1,165 @@
+.\" Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 20, 2005
+.Dt NSCD 8
+.Os
+.Sh NAME
+.Nm nscd
+.Nd "name service caching daemon"
+.Sh SYNOPSIS
+.Nm
+.Op Fl dnst
+.Op Fl i Ar cachename
+.Op Fl I Ar cachename
+.Sh DESCRIPTION
+The
+.Nm
+utility
+is the system caching daemon.
+It can cache almost all types of data and is basically intended to be used
+with the
+.Nm nsswitch
+subsystem.
+The cache is actually per-user.
+This means that each user can work only with the
+cached data that were cached by themselves, and cannot poison the
+cache of other users.
+The
+.Nm
+utility supports two types of caching:
+.Bl -tag -width ".Sy Type"
+.It Sy Type
+.Sy Description
+.It Common caching
+Each cached element is the key+value pair.
+This type of caching supports policies which are applied when maximum
+number of cached elements is exceeded.
+Three policies are available:
+.Cm FIFO
+(first in - first out),
+.Cm LRU
+(least recently used) and
+.Cm LFU
+(least frequently used).
+This type of caching is used with the
+.Fn getXXXbyname
+family of functions.
+.It Multipart caching
+Each cached element is the part of the elements sequence.
+This type of caching is intended to be used with the
+.Fn getXXXent
+family of functions.
+.El
+.Pp
+The
+.Nm
+utility is able not only to cache elements, but to perform the actual nsswitch
+lookups by itself.
+To enable this feature, use the
+.Va perform-actual-lookups
+parameter in
+.Xr nscd.conf 5 .
+.Pp
+The
+.Nm
+utility recognizes the following runtime options:
+.Bl -tag -width indent
+.\" .It Fl d
+.\" XXX Document me!
+.It Fl n
+Do not daemonize;
+.Nm
+will not fork or disconnect itself from the terminal.
+.It Fl s
+Single-threaded mode.
+Forces using only one thread for all processing purposes (it overrides
+the
+.Va threads
+parameter in the
+.Xr nscd.conf 5
+file).
+.It Fl t
+Trace mode.
+All trace messages will be written to stdout.
+This mode is usually used with
+.Fl n
+and
+.Fl s
+flags are used for debugging purposes.
+.It Fl i Ar cachename
+Invalidates personal cache.
+When specified,
+.Nm
+acts as the administration tool.
+It asks the already running
+.Nm
+to invalidate the specified part of the cache of the
+calling user.
+For example, sometimes you may want to invalidate your
+.Dq Li hosts
+cache.
+You can specify
+.Dq Li all
+as the
+.Ar cachename
+to invalidate your personal cache as a whole.
+You cannot use this option for the
+.Ar cachename
+for which the
+.Va perform-actual-lookups
+option is enabled.
+.It Fl I Ar cachename
+Invalidates the cache for every user.
+When specified,
+.Nm
+acts as the administration tool.
+It asks the already
+running
+.Nm
+to invalidate the specified part of the cache for
+every user.
+You can specify
+.Dq Li all
+as the
+.Ar cachename
+to invalidate the whole cache.
+Only the root can use this option.
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /etc/nscd.conf" -compact
+.It Pa /etc/nscd.conf
+The default configuration file.
+.El
+.Sh SEE ALSO
+.Xr nsdispatch 3 ,
+.Xr nscd.conf 5 ,
+.Xr nsswitch.conf 5
+.Sh AUTHORS
+.An Michael Bushkov Aq Mt bushman@FreeBSD.org
+.Sh BUGS
+Please send bug reports and suggestions to
+.Aq Mt bushman@FreeBSD.org .
diff --git a/usr.sbin/nscd/nscd.c b/usr.sbin/nscd/nscd.c
new file mode 100644
index 0000000..6b5af94
--- /dev/null
+++ b/usr.sbin/nscd/nscd.c
@@ -0,0 +1,870 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in thereg
+ * 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/event.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/un.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libutil.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "agents/passwd.h"
+#include "agents/group.h"
+#include "agents/services.h"
+#include "cachelib.h"
+#include "config.h"
+#include "debug.h"
+#include "log.h"
+#include "nscdcli.h"
+#include "parser.h"
+#include "query.h"
+#include "singletons.h"
+
+#ifndef CONFIG_PATH
+#define CONFIG_PATH "/etc/nscd.conf"
+#endif
+#define DEFAULT_CONFIG_PATH "nscd.conf"
+
+#define MAX_SOCKET_IO_SIZE 4096
+
+struct processing_thread_args {
+ cache the_cache;
+ struct configuration *the_configuration;
+ struct runtime_env *the_runtime_env;
+};
+
+static void accept_connection(struct kevent *, struct runtime_env *,
+ struct configuration *);
+static void destroy_cache_(cache);
+static void destroy_runtime_env(struct runtime_env *);
+static cache init_cache_(struct configuration *);
+static struct runtime_env *init_runtime_env(struct configuration *);
+static void processing_loop(cache, struct runtime_env *,
+ struct configuration *);
+static void process_socket_event(struct kevent *, struct runtime_env *,
+ struct configuration *);
+static void process_timer_event(struct kevent *, struct runtime_env *,
+ struct configuration *);
+static void *processing_thread(void *);
+static void usage(void);
+
+void get_time_func(struct timeval *);
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: nscd [-dnst] [-i cachename] [-I cachename]\n");
+ exit(1);
+}
+
+static cache
+init_cache_(struct configuration *config)
+{
+ struct cache_params params;
+ cache retval;
+
+ struct configuration_entry *config_entry;
+ size_t size, i;
+ int res;
+
+ TRACE_IN(init_cache_);
+
+ memset(&params, 0, sizeof(struct cache_params));
+ params.get_time_func = get_time_func;
+ retval = init_cache(&params);
+
+ size = configuration_get_entries_size(config);
+ for (i = 0; i < size; ++i) {
+ config_entry = configuration_get_entry(config, i);
+ /*
+ * We should register common entries now - multipart entries
+ * would be registered automatically during the queries.
+ */
+ res = register_cache_entry(retval, (struct cache_entry_params *)
+ &config_entry->positive_cache_params);
+ config_entry->positive_cache_entry = find_cache_entry(retval,
+ config_entry->positive_cache_params.cep.entry_name);
+ assert(config_entry->positive_cache_entry !=
+ INVALID_CACHE_ENTRY);
+
+ res = register_cache_entry(retval, (struct cache_entry_params *)
+ &config_entry->negative_cache_params);
+ config_entry->negative_cache_entry = find_cache_entry(retval,
+ config_entry->negative_cache_params.cep.entry_name);
+ assert(config_entry->negative_cache_entry !=
+ INVALID_CACHE_ENTRY);
+ }
+
+ LOG_MSG_2("cache", "cache was successfully initialized");
+ TRACE_OUT(init_cache_);
+ return (retval);
+}
+
+static void
+destroy_cache_(cache the_cache)
+{
+ TRACE_IN(destroy_cache_);
+ destroy_cache(the_cache);
+ TRACE_OUT(destroy_cache_);
+}
+
+/*
+ * Socket and kqueues are prepared here. We have one global queue for both
+ * socket and timers events.
+ */
+static struct runtime_env *
+init_runtime_env(struct configuration *config)
+{
+ int serv_addr_len;
+ struct sockaddr_un serv_addr;
+
+ struct kevent eventlist;
+ struct timespec timeout;
+
+ struct runtime_env *retval;
+
+ TRACE_IN(init_runtime_env);
+ retval = calloc(1, sizeof(*retval));
+ assert(retval != NULL);
+
+ retval->sockfd = socket(PF_LOCAL, SOCK_STREAM, 0);
+
+ if (config->force_unlink == 1)
+ unlink(config->socket_path);
+
+ memset(&serv_addr, 0, sizeof(struct sockaddr_un));
+ serv_addr.sun_family = PF_LOCAL;
+ strlcpy(serv_addr.sun_path, config->socket_path,
+ sizeof(serv_addr.sun_path));
+ serv_addr_len = sizeof(serv_addr.sun_family) +
+ strlen(serv_addr.sun_path) + 1;
+
+ if (bind(retval->sockfd, (struct sockaddr *)&serv_addr,
+ serv_addr_len) == -1) {
+ close(retval->sockfd);
+ free(retval);
+
+ LOG_ERR_2("runtime environment", "can't bind socket to path: "
+ "%s", config->socket_path);
+ TRACE_OUT(init_runtime_env);
+ return (NULL);
+ }
+ LOG_MSG_2("runtime environment", "using socket %s",
+ config->socket_path);
+
+ /*
+ * Here we're marking socket as non-blocking and setting its backlog
+ * to the maximum value
+ */
+ chmod(config->socket_path, config->socket_mode);
+ listen(retval->sockfd, -1);
+ fcntl(retval->sockfd, F_SETFL, O_NONBLOCK);
+
+ retval->queue = kqueue();
+ assert(retval->queue != -1);
+
+ EV_SET(&eventlist, retval->sockfd, EVFILT_READ, EV_ADD | EV_ONESHOT,
+ 0, 0, 0);
+ memset(&timeout, 0, sizeof(struct timespec));
+ kevent(retval->queue, &eventlist, 1, NULL, 0, &timeout);
+
+ LOG_MSG_2("runtime environment", "successfully initialized");
+ TRACE_OUT(init_runtime_env);
+ return (retval);
+}
+
+static void
+destroy_runtime_env(struct runtime_env *env)
+{
+ TRACE_IN(destroy_runtime_env);
+ close(env->queue);
+ close(env->sockfd);
+ free(env);
+ TRACE_OUT(destroy_runtime_env);
+}
+
+static void
+accept_connection(struct kevent *event_data, struct runtime_env *env,
+ struct configuration *config)
+{
+ struct kevent eventlist[2];
+ struct timespec timeout;
+ struct query_state *qstate;
+
+ int fd;
+ int res;
+
+ uid_t euid;
+ gid_t egid;
+
+ TRACE_IN(accept_connection);
+ fd = accept(event_data->ident, NULL, NULL);
+ if (fd == -1) {
+ LOG_ERR_2("accept_connection", "error %d during accept()",
+ errno);
+ TRACE_OUT(accept_connection);
+ return;
+ }
+
+ if (getpeereid(fd, &euid, &egid) != 0) {
+ LOG_ERR_2("accept_connection", "error %d during getpeereid()",
+ errno);
+ TRACE_OUT(accept_connection);
+ return;
+ }
+
+ qstate = init_query_state(fd, sizeof(int), euid, egid);
+ if (qstate == NULL) {
+ LOG_ERR_2("accept_connection", "can't init query_state");
+ TRACE_OUT(accept_connection);
+ return;
+ }
+
+ memset(&timeout, 0, sizeof(struct timespec));
+ EV_SET(&eventlist[0], fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT,
+ 0, qstate->timeout.tv_sec * 1000, qstate);
+ EV_SET(&eventlist[1], fd, EVFILT_READ, EV_ADD | EV_ONESHOT,
+ NOTE_LOWAT, qstate->kevent_watermark, qstate);
+ res = kevent(env->queue, eventlist, 2, NULL, 0, &timeout);
+ if (res < 0)
+ LOG_ERR_2("accept_connection", "kevent error");
+
+ TRACE_OUT(accept_connection);
+}
+
+static void
+process_socket_event(struct kevent *event_data, struct runtime_env *env,
+ struct configuration *config)
+{
+ struct kevent eventlist[2];
+ struct timeval query_timeout;
+ struct timespec kevent_timeout;
+ int nevents;
+ int eof_res, res;
+ ssize_t io_res;
+ struct query_state *qstate;
+
+ TRACE_IN(process_socket_event);
+ eof_res = event_data->flags & EV_EOF ? 1 : 0;
+ res = 0;
+
+ memset(&kevent_timeout, 0, sizeof(struct timespec));
+ EV_SET(&eventlist[0], event_data->ident, EVFILT_TIMER, EV_DELETE,
+ 0, 0, NULL);
+ nevents = kevent(env->queue, eventlist, 1, NULL, 0, &kevent_timeout);
+ if (nevents == -1) {
+ if (errno == ENOENT) {
+ /* the timer is already handling this event */
+ TRACE_OUT(process_socket_event);
+ return;
+ } else {
+ /* some other error happened */
+ LOG_ERR_2("process_socket_event", "kevent error, errno"
+ " is %d", errno);
+ TRACE_OUT(process_socket_event);
+ return;
+ }
+ }
+ qstate = (struct query_state *)event_data->udata;
+
+ /*
+ * If the buffer that is to be send/received is too large,
+ * we send it implicitly, by using query_io_buffer_read and
+ * query_io_buffer_write functions in the query_state. These functions
+ * use the temporary buffer, which is later send/received in parts.
+ * The code below implements buffer splitting/mergind for send/receive
+ * operations. It also does the actual socket IO operations.
+ */
+ if (((qstate->use_alternate_io == 0) &&
+ (qstate->kevent_watermark <= (size_t)event_data->data)) ||
+ ((qstate->use_alternate_io != 0) &&
+ (qstate->io_buffer_watermark <= (size_t)event_data->data))) {
+ if (qstate->use_alternate_io != 0) {
+ switch (qstate->io_buffer_filter) {
+ case EVFILT_READ:
+ io_res = query_socket_read(qstate,
+ qstate->io_buffer_p,
+ qstate->io_buffer_watermark);
+ if (io_res < 0) {
+ qstate->use_alternate_io = 0;
+ qstate->process_func = NULL;
+ } else {
+ qstate->io_buffer_p += io_res;
+ if (qstate->io_buffer_p ==
+ qstate->io_buffer +
+ qstate->io_buffer_size) {
+ qstate->io_buffer_p =
+ qstate->io_buffer;
+ qstate->use_alternate_io = 0;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (qstate->use_alternate_io == 0) {
+ do {
+ res = qstate->process_func(qstate);
+ } while ((qstate->kevent_watermark == 0) &&
+ (qstate->process_func != NULL) &&
+ (res == 0));
+
+ if (res != 0)
+ qstate->process_func = NULL;
+ }
+
+ if ((qstate->use_alternate_io != 0) &&
+ (qstate->io_buffer_filter == EVFILT_WRITE)) {
+ io_res = query_socket_write(qstate, qstate->io_buffer_p,
+ qstate->io_buffer_watermark);
+ if (io_res < 0) {
+ qstate->use_alternate_io = 0;
+ qstate->process_func = NULL;
+ } else
+ qstate->io_buffer_p += io_res;
+ }
+ } else {
+ /* assuming that socket was closed */
+ qstate->process_func = NULL;
+ qstate->use_alternate_io = 0;
+ }
+
+ if (((qstate->process_func == NULL) &&
+ (qstate->use_alternate_io == 0)) ||
+ (eof_res != 0) || (res != 0)) {
+ destroy_query_state(qstate);
+ close(event_data->ident);
+ TRACE_OUT(process_socket_event);
+ return;
+ }
+
+ /* updating the query_state lifetime variable */
+ get_time_func(&query_timeout);
+ query_timeout.tv_usec = 0;
+ query_timeout.tv_sec -= qstate->creation_time.tv_sec;
+ if (query_timeout.tv_sec > qstate->timeout.tv_sec)
+ query_timeout.tv_sec = 0;
+ else
+ query_timeout.tv_sec = qstate->timeout.tv_sec -
+ query_timeout.tv_sec;
+
+ if ((qstate->use_alternate_io != 0) && (qstate->io_buffer_p ==
+ qstate->io_buffer + qstate->io_buffer_size))
+ qstate->use_alternate_io = 0;
+
+ if (qstate->use_alternate_io == 0) {
+ /*
+ * If we must send/receive the large block of data,
+ * we should prepare the query_state's io_XXX fields.
+ * We should also substitute its write_func and read_func
+ * with the query_io_buffer_write and query_io_buffer_read,
+ * which will allow us to implicitly send/receive this large
+ * buffer later (in the subsequent calls to the
+ * process_socket_event).
+ */
+ if (qstate->kevent_watermark > MAX_SOCKET_IO_SIZE) {
+ if (qstate->io_buffer != NULL)
+ free(qstate->io_buffer);
+
+ qstate->io_buffer = calloc(1,
+ qstate->kevent_watermark);
+ assert(qstate->io_buffer != NULL);
+
+ qstate->io_buffer_p = qstate->io_buffer;
+ qstate->io_buffer_size = qstate->kevent_watermark;
+ qstate->io_buffer_filter = qstate->kevent_filter;
+
+ qstate->write_func = query_io_buffer_write;
+ qstate->read_func = query_io_buffer_read;
+
+ if (qstate->kevent_filter == EVFILT_READ)
+ qstate->use_alternate_io = 1;
+
+ qstate->io_buffer_watermark = MAX_SOCKET_IO_SIZE;
+ EV_SET(&eventlist[1], event_data->ident,
+ qstate->kevent_filter, EV_ADD | EV_ONESHOT,
+ NOTE_LOWAT, MAX_SOCKET_IO_SIZE, qstate);
+ } else {
+ EV_SET(&eventlist[1], event_data->ident,
+ qstate->kevent_filter, EV_ADD | EV_ONESHOT,
+ NOTE_LOWAT, qstate->kevent_watermark, qstate);
+ }
+ } else {
+ if (qstate->io_buffer + qstate->io_buffer_size -
+ qstate->io_buffer_p <
+ MAX_SOCKET_IO_SIZE) {
+ qstate->io_buffer_watermark = qstate->io_buffer +
+ qstate->io_buffer_size - qstate->io_buffer_p;
+ EV_SET(&eventlist[1], event_data->ident,
+ qstate->io_buffer_filter,
+ EV_ADD | EV_ONESHOT, NOTE_LOWAT,
+ qstate->io_buffer_watermark,
+ qstate);
+ } else {
+ qstate->io_buffer_watermark = MAX_SOCKET_IO_SIZE;
+ EV_SET(&eventlist[1], event_data->ident,
+ qstate->io_buffer_filter, EV_ADD | EV_ONESHOT,
+ NOTE_LOWAT, MAX_SOCKET_IO_SIZE, qstate);
+ }
+ }
+ EV_SET(&eventlist[0], event_data->ident, EVFILT_TIMER,
+ EV_ADD | EV_ONESHOT, 0, query_timeout.tv_sec * 1000, qstate);
+ kevent(env->queue, eventlist, 2, NULL, 0, &kevent_timeout);
+
+ TRACE_OUT(process_socket_event);
+}
+
+/*
+ * This routine is called if timer event has been signaled in the kqueue. It
+ * just closes the socket and destroys the query_state.
+ */
+static void
+process_timer_event(struct kevent *event_data, struct runtime_env *env,
+ struct configuration *config)
+{
+ struct query_state *qstate;
+
+ TRACE_IN(process_timer_event);
+ qstate = (struct query_state *)event_data->udata;
+ destroy_query_state(qstate);
+ close(event_data->ident);
+ TRACE_OUT(process_timer_event);
+}
+
+/*
+ * Processing loop is the basic processing routine, that forms a body of each
+ * procssing thread
+ */
+static void
+processing_loop(cache the_cache, struct runtime_env *env,
+ struct configuration *config)
+{
+ struct timespec timeout;
+ const int eventlist_size = 1;
+ struct kevent eventlist[eventlist_size];
+ int nevents, i;
+
+ TRACE_MSG("=> processing_loop");
+ memset(&timeout, 0, sizeof(struct timespec));
+ memset(&eventlist, 0, sizeof(struct kevent) * eventlist_size);
+
+ for (;;) {
+ nevents = kevent(env->queue, NULL, 0, eventlist,
+ eventlist_size, NULL);
+ /*
+ * we can only receive 1 event on success
+ */
+ if (nevents == 1) {
+ struct kevent *event_data;
+ event_data = &eventlist[0];
+
+ if ((int)event_data->ident == env->sockfd) {
+ for (i = 0; i < event_data->data; ++i)
+ accept_connection(event_data, env, config);
+
+ EV_SET(eventlist, s_runtime_env->sockfd,
+ EVFILT_READ, EV_ADD | EV_ONESHOT,
+ 0, 0, 0);
+ memset(&timeout, 0,
+ sizeof(struct timespec));
+ kevent(s_runtime_env->queue, eventlist,
+ 1, NULL, 0, &timeout);
+
+ } else {
+ switch (event_data->filter) {
+ case EVFILT_READ:
+ case EVFILT_WRITE:
+ process_socket_event(event_data,
+ env, config);
+ break;
+ case EVFILT_TIMER:
+ process_timer_event(event_data,
+ env, config);
+ break;
+ default:
+ break;
+ }
+ }
+ } else {
+ /* this branch shouldn't be currently executed */
+ }
+ }
+
+ TRACE_MSG("<= processing_loop");
+}
+
+/*
+ * Wrapper above the processing loop function. It sets the thread signal mask
+ * to avoid SIGPIPE signals (which can happen if the client works incorrectly).
+ */
+static void *
+processing_thread(void *data)
+{
+ struct processing_thread_args *args;
+ sigset_t new;
+
+ TRACE_MSG("=> processing_thread");
+ args = (struct processing_thread_args *)data;
+
+ sigemptyset(&new);
+ sigaddset(&new, SIGPIPE);
+ if (pthread_sigmask(SIG_BLOCK, &new, NULL) != 0)
+ LOG_ERR_1("processing thread",
+ "thread can't block the SIGPIPE signal");
+
+ processing_loop(args->the_cache, args->the_runtime_env,
+ args->the_configuration);
+ free(args);
+ TRACE_MSG("<= processing_thread");
+
+ return (NULL);
+}
+
+void
+get_time_func(struct timeval *time)
+{
+ struct timespec res;
+ memset(&res, 0, sizeof(struct timespec));
+ clock_gettime(CLOCK_MONOTONIC, &res);
+
+ time->tv_sec = res.tv_sec;
+ time->tv_usec = 0;
+}
+
+/*
+ * The idea of _nss_cache_cycle_prevention_function is that nsdispatch
+ * will search for this symbol in the executable. This symbol is the
+ * attribute of the caching daemon. So, if it exists, nsdispatch won't try
+ * to connect to the caching daemon and will just ignore the 'cache'
+ * source in the nsswitch.conf. This method helps to avoid cycles and
+ * organize self-performing requests.
+ *
+ * (not actually a function; it used to be, but it doesn't make any
+ * difference, as long as it has external linkage)
+ */
+void *_nss_cache_cycle_prevention_function;
+
+int
+main(int argc, char *argv[])
+{
+ struct processing_thread_args *thread_args;
+ pthread_t *threads;
+
+ struct pidfh *pidfile;
+ pid_t pid;
+
+ char const *config_file;
+ char const *error_str;
+ int error_line;
+ int i, res;
+
+ int trace_mode_enabled;
+ int force_single_threaded;
+ int do_not_daemonize;
+ int clear_user_cache_entries, clear_all_cache_entries;
+ char *user_config_entry_name, *global_config_entry_name;
+ int show_statistics;
+ int daemon_mode, interactive_mode;
+
+
+ /* by default all debug messages are omitted */
+ TRACE_OFF();
+
+ /* parsing command line arguments */
+ trace_mode_enabled = 0;
+ force_single_threaded = 0;
+ do_not_daemonize = 0;
+ clear_user_cache_entries = 0;
+ clear_all_cache_entries = 0;
+ show_statistics = 0;
+ user_config_entry_name = NULL;
+ global_config_entry_name = NULL;
+ while ((res = getopt(argc, argv, "nstdi:I:")) != -1) {
+ switch (res) {
+ case 'n':
+ do_not_daemonize = 1;
+ break;
+ case 's':
+ force_single_threaded = 1;
+ break;
+ case 't':
+ trace_mode_enabled = 1;
+ break;
+ case 'i':
+ clear_user_cache_entries = 1;
+ if (optarg != NULL)
+ if (strcmp(optarg, "all") != 0)
+ user_config_entry_name = strdup(optarg);
+ break;
+ case 'I':
+ clear_all_cache_entries = 1;
+ if (optarg != NULL)
+ if (strcmp(optarg, "all") != 0)
+ global_config_entry_name =
+ strdup(optarg);
+ break;
+ case 'd':
+ show_statistics = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ /* NOT REACHED */
+ }
+ }
+
+ daemon_mode = do_not_daemonize | force_single_threaded |
+ trace_mode_enabled;
+ interactive_mode = clear_user_cache_entries | clear_all_cache_entries |
+ show_statistics;
+
+ if ((daemon_mode != 0) && (interactive_mode != 0)) {
+ LOG_ERR_1("main", "daemon mode and interactive_mode arguments "
+ "can't be used together");
+ usage();
+ }
+
+ if (interactive_mode != 0) {
+ FILE *pidfin = fopen(DEFAULT_PIDFILE_PATH, "r");
+ char pidbuf[256];
+
+ struct nscd_connection_params connection_params;
+ nscd_connection connection;
+
+ int result;
+
+ if (pidfin == NULL)
+ errx(EXIT_FAILURE, "There is no daemon running.");
+
+ memset(pidbuf, 0, sizeof(pidbuf));
+ fread(pidbuf, sizeof(pidbuf) - 1, 1, pidfin);
+ fclose(pidfin);
+
+ if (ferror(pidfin) != 0)
+ errx(EXIT_FAILURE, "Can't read from pidfile.");
+
+ if (sscanf(pidbuf, "%d", &pid) != 1)
+ errx(EXIT_FAILURE, "Invalid pidfile.");
+ LOG_MSG_1("main", "daemon PID is %d", pid);
+
+
+ memset(&connection_params, 0,
+ sizeof(struct nscd_connection_params));
+ connection_params.socket_path = DEFAULT_SOCKET_PATH;
+ connection = open_nscd_connection__(&connection_params);
+ if (connection == INVALID_NSCD_CONNECTION)
+ errx(EXIT_FAILURE, "Can't connect to the daemon.");
+
+ if (clear_user_cache_entries != 0) {
+ result = nscd_transform__(connection,
+ user_config_entry_name, TT_USER);
+ if (result != 0)
+ LOG_MSG_1("main",
+ "user cache transformation failed");
+ else
+ LOG_MSG_1("main",
+ "user cache_transformation "
+ "succeeded");
+ }
+
+ if (clear_all_cache_entries != 0) {
+ if (geteuid() != 0)
+ errx(EXIT_FAILURE, "Only root can initiate "
+ "global cache transformation.");
+
+ result = nscd_transform__(connection,
+ global_config_entry_name, TT_ALL);
+ if (result != 0)
+ LOG_MSG_1("main",
+ "global cache transformation "
+ "failed");
+ else
+ LOG_MSG_1("main",
+ "global cache transformation "
+ "succeeded");
+ }
+
+ close_nscd_connection__(connection);
+
+ free(user_config_entry_name);
+ free(global_config_entry_name);
+ return (EXIT_SUCCESS);
+ }
+
+ pidfile = pidfile_open(DEFAULT_PIDFILE_PATH, 0644, &pid);
+ if (pidfile == NULL) {
+ if (errno == EEXIST)
+ errx(EXIT_FAILURE, "Daemon already running, pid: %d.",
+ pid);
+ warn("Cannot open or create pidfile");
+ }
+
+ if (trace_mode_enabled == 1)
+ TRACE_ON();
+
+ /* blocking the main thread from receiving SIGPIPE signal */
+ sigblock(sigmask(SIGPIPE));
+
+ /* daemonization */
+ if (do_not_daemonize == 0) {
+ res = daemon(0, trace_mode_enabled == 0 ? 0 : 1);
+ if (res != 0) {
+ LOG_ERR_1("main", "can't daemonize myself: %s",
+ strerror(errno));
+ pidfile_remove(pidfile);
+ goto fin;
+ } else
+ LOG_MSG_1("main", "successfully daemonized");
+ }
+
+ pidfile_write(pidfile);
+
+ s_agent_table = init_agent_table();
+ register_agent(s_agent_table, init_passwd_agent());
+ register_agent(s_agent_table, init_passwd_mp_agent());
+ register_agent(s_agent_table, init_group_agent());
+ register_agent(s_agent_table, init_group_mp_agent());
+ register_agent(s_agent_table, init_services_agent());
+ register_agent(s_agent_table, init_services_mp_agent());
+ LOG_MSG_1("main", "request agents registered successfully");
+
+ /*
+ * Hosts agent can't work properly until we have access to the
+ * appropriate dtab structures, which are used in nsdispatch
+ * calls
+ *
+ register_agent(s_agent_table, init_hosts_agent());
+ */
+
+ /* configuration initialization */
+ s_configuration = init_configuration();
+ fill_configuration_defaults(s_configuration);
+
+ error_str = NULL;
+ error_line = 0;
+ config_file = CONFIG_PATH;
+
+ res = parse_config_file(s_configuration, config_file, &error_str,
+ &error_line);
+ if ((res != 0) && (error_str == NULL)) {
+ config_file = DEFAULT_CONFIG_PATH;
+ res = parse_config_file(s_configuration, config_file,
+ &error_str, &error_line);
+ }
+
+ if (res != 0) {
+ if (error_str != NULL) {
+ LOG_ERR_1("main", "error in configuration file(%s, %d): %s\n",
+ config_file, error_line, error_str);
+ } else {
+ LOG_ERR_1("main", "no configuration file found "
+ "- was looking for %s and %s",
+ CONFIG_PATH, DEFAULT_CONFIG_PATH);
+ }
+ destroy_configuration(s_configuration);
+ return (-1);
+ }
+
+ if (force_single_threaded == 1)
+ s_configuration->threads_num = 1;
+
+ /* cache initialization */
+ s_cache = init_cache_(s_configuration);
+ if (s_cache == NULL) {
+ LOG_ERR_1("main", "can't initialize the cache");
+ destroy_configuration(s_configuration);
+ return (-1);
+ }
+
+ /* runtime environment initialization */
+ s_runtime_env = init_runtime_env(s_configuration);
+ if (s_runtime_env == NULL) {
+ LOG_ERR_1("main", "can't initialize the runtime environment");
+ destroy_configuration(s_configuration);
+ destroy_cache_(s_cache);
+ return (-1);
+ }
+
+ if (s_configuration->threads_num > 1) {
+ threads = calloc(1, sizeof(*threads) *
+ s_configuration->threads_num);
+ for (i = 0; i < s_configuration->threads_num; ++i) {
+ thread_args = malloc(
+ sizeof(*thread_args));
+ thread_args->the_cache = s_cache;
+ thread_args->the_runtime_env = s_runtime_env;
+ thread_args->the_configuration = s_configuration;
+
+ LOG_MSG_1("main", "thread #%d was successfully created",
+ i);
+ pthread_create(&threads[i], NULL, processing_thread,
+ thread_args);
+
+ thread_args = NULL;
+ }
+
+ for (i = 0; i < s_configuration->threads_num; ++i)
+ pthread_join(threads[i], NULL);
+ } else {
+ LOG_MSG_1("main", "working in single-threaded mode");
+ processing_loop(s_cache, s_runtime_env, s_configuration);
+ }
+
+fin:
+ /* runtime environment destruction */
+ destroy_runtime_env(s_runtime_env);
+
+ /* cache destruction */
+ destroy_cache_(s_cache);
+
+ /* configuration destruction */
+ destroy_configuration(s_configuration);
+
+ /* agents table destruction */
+ destroy_agent_table(s_agent_table);
+
+ pidfile_remove(pidfile);
+ return (EXIT_SUCCESS);
+}
diff --git a/usr.sbin/nscd/nscd.conf.5 b/usr.sbin/nscd/nscd.conf.5
new file mode 100644
index 0000000..eb28eca
--- /dev/null
+++ b/usr.sbin/nscd/nscd.conf.5
@@ -0,0 +1,158 @@
+.\" Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 30, 2006
+.Dt NSCD.CONF 5
+.Os
+.Sh NAME
+.Nm nscd.conf
+.Nd "nscd configuration file"
+.Sh DESCRIPTION
+The
+.Nm
+file
+is used by the
+.Xr nscd 8
+daemon and is read on its startup.
+Its syntax is mostly similar to the
+.Pa nscd.conf
+syntax in
+.Tn Linux
+and
+.Tn Solaris .
+It has some differences, though \[em] see them below.
+.Pp
+Each line specifies either an attribute and a
+.Ar value ,
+or an attribute, a
+.Ar cachename
+and a
+.Ar value .
+Usual cachenames are
+.Dq Li passwd ,
+.Dq Li group ,
+.Dq Li hosts ,
+.Dq Li services ,
+.Dq Li protocols
+and
+.Dq Li rpc .
+You can also use any other
+.Ar cachename
+(for example, if some third-party
+application uses nsswitch).
+.Bl -tag -width indent
+.It Va threads Op Ar value
+Number of threads, which would listen for connections and process requests.
+The minimum is 1.
+The default value is 8.
+.It Va enable-cache Oo Ar cachename Oc Op Cm yes | no
+Enables or disables the cache for specified
+.Ar cachename .
+.It Va positive-time-to-live Oo Ar cachename Oc Op Ar value
+Sets the TTL (time-to-live) for the specified cache in seconds.
+Larger values can increase system's performance, but they also can affect
+the cache coherence.
+The default value is 3600.
+.It Va positive-policy Oo Ar cachename Oc Op Cm fifo | lru | lfu
+The policy that is applied to erase some of the cache elements, when the
+size limit of the given
+.Ar cachename
+is exceeded.
+Possible policies are:
+.Cm fifo
+(first-in-first-out),
+.Cm lru
+(least-recently-used), and
+.Cm lfu
+(least-frequently-used).
+The default policy is
+.Cm lru .
+.It Va negative-time-to-live Oo Ar cachename Oc Op Ar value
+The TTL of the negative cached elements in seconds.
+The larger values can significantly increase system performance in some
+environments (when dealing with files with UIDs, which are not in system
+databases, for example).
+This number should be kept low to avoid the cache coherence problems.
+The default value is 60.
+.It Va negative-policy Oo Ar cachename Oc Op Cm fifo | lru | lfu
+The same as the positive-policy, but this one is applied to the negative
+elements of the given
+.Ar cachename .
+The default policy is fifo.
+.It Va negative-confidence-threshold Oo Ar cachename Oc Op Ar value
+The number of times a query must have failed before the cache accepts
+that the element can not be found.
+At the default value of 1 each negative query result is cached and
+immediately returned from the cache on further queries.
+Higher numbers cause queries to be retried at the configured data
+sources the given number of times, before the negative result is
+returned from the cache on further queries.
+This allows to probe for the existence of an entry, and then to create
+it if it did not exist, without the negative probe result preventing
+access to the new entry for the duration of the negative TTL.
+.It Va suggested-size Oo Ar cachename Oc Op Ar value
+This is the internal hash table size.
+The value should be a prime number for optimum performance.
+You should only change this value when the number of cached elements is
+significantly (5-10 times) greater than the default hash table size (257).
+.It Va keep-hot-count Oo Ar cachename Oc Op Ar value
+The size limit of the cache with the given
+.Ar cachename .
+When it is exceeded, the policy will be applied.
+The default value is 2048.
+.It Va perform-actual-lookups Oo Ar cachename Oc Op Cm yes | no
+If enabled, the
+.Xr nscd 8
+does not simply receive and cache the NSS-requests results, but performs
+all the lookups by itself and only returns the responses.
+If this feature is enabled, then for the given
+.Ar cachename
+.Xr nscd 8
+will act similarly to the NSCD.
+.Pp
+.Sy NOTE :
+this feature is currently experimental \[em] it supports only
+.Dq Li passwd ,
+.Dq Li group
+and
+.Dq Li services
+cachenames.
+.El
+.Sh NOTES
+You can use the
+.Ql #
+symbol at the beginning of the line for comments.
+.Sh FILES
+.Bl -tag -width ".Pa /etc/nscd.conf" -compact
+.It Pa /etc/nscd.conf
+.El
+.Sh SEE ALSO
+.Xr nscd 8
+.Sh AUTHORS
+.An Michael Bushkov Aq Mt bushman@FreeBSD.org
+.Sh BUGS
+Please send bug reports and suggestions to
+.Aq Mt bushman@FreeBSD.org .
diff --git a/usr.sbin/nscd/nscdcli.c b/usr.sbin/nscd/nscdcli.c
new file mode 100644
index 0000000..9b07201
--- /dev/null
+++ b/usr.sbin/nscd/nscdcli.c
@@ -0,0 +1,287 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/event.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "debug.h"
+#include "nscdcli.h"
+#include "protocol.h"
+
+#define DEFAULT_NSCD_IO_TIMEOUT 4
+
+static int safe_write(struct nscd_connection_ *, const void *, size_t);
+static int safe_read(struct nscd_connection_ *, void *, size_t);
+static int send_credentials(struct nscd_connection_ *, int);
+
+static int
+safe_write(struct nscd_connection_ *connection, const void *data,
+ size_t data_size)
+{
+ struct kevent eventlist;
+ int nevents;
+ size_t result;
+ ssize_t s_result;
+ struct timespec timeout;
+
+ if (data_size == 0)
+ return (0);
+
+ timeout.tv_sec = DEFAULT_NSCD_IO_TIMEOUT;
+ timeout.tv_nsec = 0;
+ result = 0;
+ do {
+ nevents = kevent(connection->write_queue, NULL, 0, &eventlist,
+ 1, &timeout);
+ if ((nevents == 1) && (eventlist.filter == EVFILT_WRITE)) {
+ s_result = write(connection->sockfd,
+ (char *)data + result,
+ (size_t)eventlist.data < data_size - result ?
+ (size_t)eventlist.data : data_size - result);
+ if (s_result == -1)
+ return (-1);
+ else
+ result += s_result;
+
+ if (eventlist.flags & EV_EOF)
+ return (result < data_size ? -1 : 0);
+ } else
+ return (-1);
+ } while (result < data_size);
+
+ return (0);
+}
+
+static int
+safe_read(struct nscd_connection_ *connection, void *data, size_t data_size)
+{
+ struct kevent eventlist;
+ size_t result;
+ ssize_t s_result;
+ struct timespec timeout;
+ int nevents;
+
+ if (data_size == 0)
+ return (0);
+
+ timeout.tv_sec = DEFAULT_NSCD_IO_TIMEOUT;
+ timeout.tv_nsec = 0;
+ result = 0;
+ do {
+ nevents = kevent(connection->read_queue, NULL, 0, &eventlist, 1,
+ &timeout);
+ if ((nevents == 1) && (eventlist.filter == EVFILT_READ)) {
+ s_result = read(connection->sockfd,
+ (char *)data + result,
+ (size_t)eventlist.data <= data_size - result ?
+ (size_t)eventlist.data : data_size - result);
+ if (s_result == -1)
+ return (-1);
+ else
+ result += s_result;
+
+ if (eventlist.flags & EV_EOF)
+ return (result < data_size ? -1 : 0);
+ } else
+ return (-1);
+ } while (result < data_size);
+
+ return (0);
+}
+
+static int
+send_credentials(struct nscd_connection_ *connection, int type)
+{
+ struct kevent eventlist;
+ int nevents;
+ ssize_t result;
+ int res;
+
+ struct msghdr cred_hdr;
+ struct iovec iov;
+
+ struct {
+ struct cmsghdr hdr;
+ struct cmsgcred creds;
+ } cmsg;
+
+ TRACE_IN(send_credentials);
+ memset(&cmsg, 0, sizeof(cmsg));
+ cmsg.hdr.cmsg_len = sizeof(cmsg);
+ cmsg.hdr.cmsg_level = SOL_SOCKET;
+ cmsg.hdr.cmsg_type = SCM_CREDS;
+
+ memset(&cred_hdr, 0, sizeof(struct msghdr));
+ cred_hdr.msg_iov = &iov;
+ cred_hdr.msg_iovlen = 1;
+ cred_hdr.msg_control = &cmsg;
+ cred_hdr.msg_controllen = sizeof(cmsg);
+
+ iov.iov_base = &type;
+ iov.iov_len = sizeof(int);
+
+ EV_SET(&eventlist, connection->sockfd, EVFILT_WRITE, EV_ADD,
+ NOTE_LOWAT, sizeof(int), NULL);
+ res = kevent(connection->write_queue, &eventlist, 1, NULL, 0, NULL);
+
+ nevents = kevent(connection->write_queue, NULL, 0, &eventlist, 1, NULL);
+ if ((nevents == 1) && (eventlist.filter == EVFILT_WRITE)) {
+ result = (sendmsg(connection->sockfd, &cred_hdr, 0) == -1) ? -1
+ : 0;
+ EV_SET(&eventlist, connection->sockfd, EVFILT_WRITE, EV_ADD,
+ 0, 0, NULL);
+ kevent(connection->write_queue, &eventlist, 1, NULL, 0, NULL);
+ TRACE_OUT(send_credentials);
+ return (result);
+ } else {
+ TRACE_OUT(send_credentials);
+ return (-1);
+ }
+}
+
+struct nscd_connection_ *
+open_nscd_connection__(struct nscd_connection_params const *params)
+{
+ struct nscd_connection_ *retval;
+ struct kevent eventlist;
+ struct sockaddr_un client_address;
+ int client_address_len, client_socket;
+ int res;
+
+ TRACE_IN(open_nscd_connection);
+ assert(params != NULL);
+
+ client_socket = socket(PF_LOCAL, SOCK_STREAM, 0);
+ client_address.sun_family = PF_LOCAL;
+ strlcpy(client_address.sun_path, params->socket_path,
+ sizeof(client_address.sun_path));
+ client_address_len = sizeof(client_address.sun_family) +
+ strlen(client_address.sun_path) + 1;
+
+ res = connect(client_socket, (struct sockaddr *)&client_address,
+ client_address_len);
+ if (res == -1) {
+ close(client_socket);
+ TRACE_OUT(open_nscd_connection);
+ return (NULL);
+ }
+ fcntl(client_socket, F_SETFL, O_NONBLOCK);
+
+ retval = calloc(1, sizeof(*retval));
+ assert(retval != NULL);
+
+ retval->sockfd = client_socket;
+
+ retval->write_queue = kqueue();
+ assert(retval->write_queue != -1);
+
+ EV_SET(&eventlist, retval->sockfd, EVFILT_WRITE, EV_ADD,
+ 0, 0, NULL);
+ res = kevent(retval->write_queue, &eventlist, 1, NULL, 0, NULL);
+
+ retval->read_queue = kqueue();
+ assert(retval->read_queue != -1);
+
+ EV_SET(&eventlist, retval->sockfd, EVFILT_READ, EV_ADD,
+ 0, 0, NULL);
+ res = kevent(retval->read_queue, &eventlist, 1, NULL, 0, NULL);
+
+ TRACE_OUT(open_nscd_connection);
+ return (retval);
+}
+
+void
+close_nscd_connection__(struct nscd_connection_ *connection)
+{
+
+ TRACE_IN(close_nscd_connection);
+ assert(connection != NULL);
+
+ close(connection->sockfd);
+ close(connection->read_queue);
+ close(connection->write_queue);
+ free(connection);
+ TRACE_OUT(close_nscd_connection);
+}
+
+int
+nscd_transform__(struct nscd_connection_ *connection,
+ const char *entry_name, int transformation_type)
+{
+ size_t name_size;
+ int error_code;
+ int result;
+
+ TRACE_IN(nscd_transform);
+
+ error_code = -1;
+ result = 0;
+ result = send_credentials(connection, CET_TRANSFORM_REQUEST);
+ if (result != 0)
+ goto fin;
+
+ if (entry_name != NULL)
+ name_size = strlen(entry_name);
+ else
+ name_size = 0;
+
+ result = safe_write(connection, &name_size, sizeof(size_t));
+ if (result != 0)
+ goto fin;
+
+ result = safe_write(connection, &transformation_type, sizeof(int));
+ if (result != 0)
+ goto fin;
+
+ if (entry_name != NULL) {
+ result = safe_write(connection, entry_name, name_size);
+ if (result != 0)
+ goto fin;
+ }
+
+ result = safe_read(connection, &error_code, sizeof(int));
+ if (result != 0)
+ error_code = -1;
+
+fin:
+ TRACE_OUT(nscd_transform);
+ return (error_code);
+}
diff --git a/usr.sbin/nscd/nscdcli.h b/usr.sbin/nscd/nscdcli.h
new file mode 100644
index 0000000..1eae4ef
--- /dev/null
+++ b/usr.sbin/nscd/nscdcli.h
@@ -0,0 +1,55 @@
+/*-
+ * Copyright (c) 2004 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 __NSCD_NSCDCLI_H__
+#define __NSCD_NSCDCLI_H__
+
+struct nscd_connection_params {
+ char *socket_path;
+ struct timeval timeout;
+};
+
+struct nscd_connection_ {
+ int sockfd;
+ int read_queue;
+ int write_queue;
+};
+
+/* simple abstractions for not to write "struct" every time */
+typedef struct nscd_connection_ *nscd_connection;
+typedef struct nscd_connection_ *nscd_mp_write_session;
+typedef struct nscd_connection_ *nscd_mp_read_session;
+
+#define INVALID_NSCD_CONNECTION (NULL)
+
+/* initialization/destruction routines */
+nscd_connection open_nscd_connection__(struct nscd_connection_params const *);
+void close_nscd_connection__(nscd_connection);
+int nscd_transform__(nscd_connection, const char *, int);
+
+#endif
diff --git a/usr.sbin/nscd/parser.c b/usr.sbin/nscd/parser.c
new file mode 100644
index 0000000..533dc79
--- /dev/null
+++ b/usr.sbin/nscd/parser.c
@@ -0,0 +1,522 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/time.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "config.h"
+#include "debug.h"
+#include "log.h"
+#include "parser.h"
+
+static void enable_cache(struct configuration *,const char *, int);
+static struct configuration_entry *find_create_entry(struct configuration *,
+ const char *);
+static int get_number(const char *, int, int);
+static enum cache_policy_t get_policy(const char *);
+static int get_yesno(const char *);
+static int check_cachename(const char *);
+static void check_files(struct configuration *, const char *, int);
+static void set_keep_hot_count(struct configuration *, const char *, int);
+static void set_negative_policy(struct configuration *, const char *,
+ enum cache_policy_t);
+static void set_negative_time_to_live(struct configuration *,
+ const char *, int);
+static void set_positive_policy(struct configuration *, const char *,
+ enum cache_policy_t);
+static void set_perform_actual_lookups(struct configuration *, const char *,
+ int);
+static void set_positive_time_to_live(struct configuration *,
+ const char *, int);
+static void set_suggested_size(struct configuration *, const char *,
+ int size);
+static void set_threads_num(struct configuration *, int);
+static int strbreak(char *, char **, int);
+
+static int
+strbreak(char *str, char **fields, int fields_size)
+{
+ char *c = str;
+ int i, num;
+
+ TRACE_IN(strbreak);
+ num = 0;
+ for (i = 0;
+ ((*fields =
+ strsep(i < fields_size ? &c : NULL, "\n\t ")) != NULL);
+ ++i)
+ if ((*(*fields)) != '\0') {
+ ++fields;
+ ++num;
+ }
+
+ TRACE_OUT(strbreak);
+ return (num);
+}
+
+/*
+ * Tries to find the configuration entry with the specified name. If search
+ * fails, the new entry with the default parameters will be created.
+ */
+static struct configuration_entry *
+find_create_entry(struct configuration *config,
+ const char *entry_name)
+{
+ struct configuration_entry *entry = NULL;
+ int res;
+
+ TRACE_IN(find_create_entry);
+ entry = configuration_find_entry(config, entry_name);
+ if (entry == NULL) {
+ entry = create_def_configuration_entry(entry_name);
+ assert( entry != NULL);
+ res = add_configuration_entry(config, entry);
+ assert(res == 0);
+ }
+
+ TRACE_OUT(find_create_entry);
+ return (entry);
+}
+
+/*
+ * The vast majority of the functions below corresponds to the particular
+ * keywords in the configuration file.
+ */
+static void
+enable_cache(struct configuration *config, const char *entry_name, int flag)
+{
+ struct configuration_entry *entry;
+
+ TRACE_IN(enable_cache);
+ entry = find_create_entry(config, entry_name);
+ entry->enabled = flag;
+ TRACE_OUT(enable_cache);
+}
+
+static void
+set_positive_time_to_live(struct configuration *config,
+ const char *entry_name, int ttl)
+{
+ struct configuration_entry *entry;
+ struct timeval lifetime;
+
+ TRACE_IN(set_positive_time_to_live);
+ assert(ttl >= 0);
+ assert(entry_name != NULL);
+ memset(&lifetime, 0, sizeof(struct timeval));
+ lifetime.tv_sec = ttl;
+
+ entry = find_create_entry(config, entry_name);
+ memcpy(&entry->positive_cache_params.max_lifetime,
+ &lifetime, sizeof(struct timeval));
+ memcpy(&entry->mp_cache_params.max_lifetime,
+ &lifetime, sizeof(struct timeval));
+
+ TRACE_OUT(set_positive_time_to_live);
+}
+
+static void
+set_negative_time_to_live(struct configuration *config,
+ const char *entry_name, int nttl)
+{
+ struct configuration_entry *entry;
+ struct timeval lifetime;
+
+ TRACE_IN(set_negative_time_to_live);
+ assert(nttl > 0);
+ assert(entry_name != NULL);
+ memset(&lifetime, 0, sizeof(struct timeval));
+ lifetime.tv_sec = nttl;
+
+ entry = find_create_entry(config, entry_name);
+ assert(entry != NULL);
+ memcpy(&entry->negative_cache_params.max_lifetime,
+ &lifetime, sizeof(struct timeval));
+
+ TRACE_OUT(set_negative_time_to_live);
+}
+
+static void
+set_positive_confidence_threshold(struct configuration *config,
+ const char *entry_name, int conf_thresh)
+{
+ struct configuration_entry *entry;
+
+ TRACE_IN(set_positive_conf_thresh);
+ assert(conf_thresh > 0);
+ assert(entry_name != NULL);
+
+ entry = find_create_entry(config, entry_name);
+ assert(entry != NULL);
+ entry->positive_cache_params.confidence_threshold = conf_thresh;
+
+ TRACE_OUT(set_positive_conf_thresh);
+}
+
+static void
+set_negative_confidence_threshold(struct configuration *config,
+ const char *entry_name, int conf_thresh)
+{
+ struct configuration_entry *entry;
+
+ TRACE_IN(set_negative_conf_thresh);
+ assert(conf_thresh > 0);
+ assert(entry_name != NULL);
+ entry = find_create_entry(config, entry_name);
+ assert(entry != NULL);
+ entry->negative_cache_params.confidence_threshold = conf_thresh;
+ TRACE_OUT(set_negative_conf_thresh);
+}
+
+/*
+ * Hot count is actually the elements size limit.
+ */
+static void
+set_keep_hot_count(struct configuration *config,
+ const char *entry_name, int count)
+{
+ struct configuration_entry *entry;
+
+ TRACE_IN(set_keep_hot_count);
+ assert(count >= 0);
+ assert(entry_name != NULL);
+
+ entry = find_create_entry(config, entry_name);
+ assert(entry != NULL);
+ entry->positive_cache_params.max_elemsize = count;
+
+ entry = find_create_entry(config, entry_name);
+ assert(entry != NULL);
+ entry->negative_cache_params.max_elemsize = count;
+
+ TRACE_OUT(set_keep_hot_count);
+}
+
+static void
+set_positive_policy(struct configuration *config,
+ const char *entry_name, enum cache_policy_t policy)
+{
+ struct configuration_entry *entry;
+
+ TRACE_IN(set_positive_policy);
+ assert(entry_name != NULL);
+
+ entry = find_create_entry(config, entry_name);
+ assert(entry != NULL);
+ entry->positive_cache_params.policy = policy;
+
+ TRACE_OUT(set_positive_policy);
+}
+
+static void
+set_negative_policy(struct configuration *config,
+ const char *entry_name, enum cache_policy_t policy)
+{
+ struct configuration_entry *entry;
+
+ TRACE_IN(set_negative_policy);
+ assert(entry_name != NULL);
+
+ entry = find_create_entry(config, entry_name);
+ assert(entry != NULL);
+ entry->negative_cache_params.policy = policy;
+
+ TRACE_OUT(set_negative_policy);
+}
+
+static void
+set_perform_actual_lookups(struct configuration *config,
+ const char *entry_name, int flag)
+{
+ struct configuration_entry *entry;
+
+ TRACE_IN(set_perform_actual_lookups);
+ assert(entry_name != NULL);
+
+ entry = find_create_entry(config, entry_name);
+ assert(entry != NULL);
+ entry->perform_actual_lookups = flag;
+
+ TRACE_OUT(set_perform_actual_lookups);
+}
+
+static void
+set_suggested_size(struct configuration *config,
+ const char *entry_name, int size)
+{
+ struct configuration_entry *entry;
+
+ TRACE_IN(set_suggested_size);
+ assert(config != NULL);
+ assert(entry_name != NULL);
+ assert(size > 0);
+
+ entry = find_create_entry(config, entry_name);
+ assert(entry != NULL);
+ entry->positive_cache_params.cache_entries_size = size;
+ entry->negative_cache_params.cache_entries_size = size;
+
+ TRACE_OUT(set_suggested_size);
+}
+
+static void
+check_files(struct configuration *config, const char *entry_name, int flag)
+{
+
+ TRACE_IN(check_files);
+ assert(entry_name != NULL);
+ TRACE_OUT(check_files);
+}
+
+static int
+get_yesno(const char *str)
+{
+
+ if (strcmp(str, "yes") == 0)
+ return (1);
+ else if (strcmp(str, "no") == 0)
+ return (0);
+ else
+ return (-1);
+}
+
+static int
+get_number(const char *str, int low, int max)
+{
+
+ char *end = NULL;
+ int res = 0;
+
+ if (str[0] == '\0')
+ return (-1);
+
+ res = strtol(str, &end, 10);
+ if (*end != '\0')
+ return (-1);
+ else
+ if (((res >= low) || (low == -1)) &&
+ ((res <= max) || (max == -1)))
+ return (res);
+ else
+ return (-2);
+}
+
+static enum cache_policy_t
+get_policy(const char *str)
+{
+
+ if (strcmp(str, "fifo") == 0)
+ return (CPT_FIFO);
+ else if (strcmp(str, "lru") == 0)
+ return (CPT_LRU);
+ else if (strcmp(str, "lfu") == 0)
+ return (CPT_LFU);
+
+ return (-1);
+}
+
+static int
+check_cachename(const char *str)
+{
+
+ assert(str != NULL);
+ return ((strlen(str) > 0) ? 0 : -1);
+}
+
+static void
+set_threads_num(struct configuration *config, int value)
+{
+
+ assert(config != NULL);
+ config->threads_num = value;
+}
+
+/*
+ * The main configuration routine. Its implementation is hugely inspired by the
+ * the same routine implementation in Solaris NSCD.
+ */
+int
+parse_config_file(struct configuration *config,
+ const char *fname, char const **error_str, int *error_line)
+{
+ FILE *fin;
+ char buffer[255];
+ char *fields[128];
+ int field_count, line_num, value;
+ int res;
+
+ TRACE_IN(parse_config_file);
+ assert(config != NULL);
+ assert(fname != NULL);
+
+ fin = fopen(fname, "r");
+ if (fin == NULL) {
+ TRACE_OUT(parse_config_file);
+ return (-1);
+ }
+
+ res = 0;
+ line_num = 0;
+ memset(buffer, 0, sizeof(buffer));
+ while ((res == 0) && (fgets(buffer, sizeof(buffer) - 1, fin) != NULL)) {
+ field_count = strbreak(buffer, fields, sizeof(fields));
+ ++line_num;
+
+ if (field_count == 0)
+ continue;
+
+ switch (fields[0][0]) {
+ case '#':
+ case '\0':
+ continue;
+ case 'e':
+ if ((field_count == 3) &&
+ (strcmp(fields[0], "enable-cache") == 0) &&
+ (check_cachename(fields[1]) == 0) &&
+ ((value = get_yesno(fields[2])) != -1)) {
+ enable_cache(config, fields[1], value);
+ continue;
+ }
+ break;
+ case 'd':
+ if ((field_count == 2) &&
+ (strcmp(fields[0], "debug-level") == 0) &&
+ ((value = get_number(fields[1], 0, 10)) != -1)) {
+ continue;
+ }
+ break;
+ case 'p':
+ if ((field_count == 3) &&
+ (strcmp(fields[0], "positive-time-to-live") == 0) &&
+ (check_cachename(fields[1]) == 0) &&
+ ((value = get_number(fields[2], 0, -1)) != -1)) {
+ set_positive_time_to_live(config,
+ fields[1], value);
+ continue;
+ } else if ((field_count == 3) &&
+ (strcmp(fields[0], "positive-confidence-threshold") == 0) &&
+ ((value = get_number(fields[2], 1, -1)) != -1)) {
+ set_positive_confidence_threshold(config,
+ fields[1], value);
+ continue;
+ } else if ((field_count == 3) &&
+ (strcmp(fields[0], "positive-policy") == 0) &&
+ (check_cachename(fields[1]) == 0) &&
+ ((value = get_policy(fields[2])) != -1)) {
+ set_positive_policy(config, fields[1], value);
+ continue;
+ } else if ((field_count == 3) &&
+ (strcmp(fields[0], "perform-actual-lookups") == 0) &&
+ (check_cachename(fields[1]) == 0) &&
+ ((value = get_yesno(fields[2])) != -1)) {
+ set_perform_actual_lookups(config, fields[1],
+ value);
+ continue;
+ }
+ break;
+ case 'n':
+ if ((field_count == 3) &&
+ (strcmp(fields[0], "negative-time-to-live") == 0) &&
+ (check_cachename(fields[1]) == 0) &&
+ ((value = get_number(fields[2], 0, -1)) != -1)) {
+ set_negative_time_to_live(config,
+ fields[1], value);
+ continue;
+ } else if ((field_count == 3) &&
+ (strcmp(fields[0], "negative-confidence-threshold") == 0) &&
+ ((value = get_number(fields[2], 1, -1)) != -1)) {
+ set_negative_confidence_threshold(config,
+ fields[1], value);
+ continue;
+ } else if ((field_count == 3) &&
+ (strcmp(fields[0], "negative-policy") == 0) &&
+ (check_cachename(fields[1]) == 0) &&
+ ((value = get_policy(fields[2])) != -1)) {
+ set_negative_policy(config,
+ fields[1], value);
+ continue;
+ }
+ break;
+ case 's':
+ if ((field_count == 3) &&
+ (strcmp(fields[0], "suggested-size") == 0) &&
+ (check_cachename(fields[1]) == 0) &&
+ ((value = get_number(fields[2], 1, -1)) != -1)) {
+ set_suggested_size(config, fields[1], value);
+ continue;
+ }
+ break;
+ case 't':
+ if ((field_count == 2) &&
+ (strcmp(fields[0], "threads") == 0) &&
+ ((value = get_number(fields[1], 1, -1)) != -1)) {
+ set_threads_num(config, value);
+ continue;
+ }
+ break;
+ case 'k':
+ if ((field_count == 3) &&
+ (strcmp(fields[0], "keep-hot-count") == 0) &&
+ (check_cachename(fields[1]) == 0) &&
+ ((value = get_number(fields[2], 0, -1)) != -1)) {
+ set_keep_hot_count(config,
+ fields[1], value);
+ continue;
+ }
+ break;
+ case 'c':
+ if ((field_count == 3) &&
+ (strcmp(fields[0], "check-files") == 0) &&
+ (check_cachename(fields[1]) == 0) &&
+ ((value = get_yesno(fields[2])) != -1)) {
+ check_files(config,
+ fields[1], value);
+ continue;
+ }
+ break;
+ default:
+ break;
+ }
+
+ LOG_ERR_2("config file parser", "error in file "
+ "%s on line %d", fname, line_num);
+ *error_str = "syntax error";
+ *error_line = line_num;
+ res = -1;
+ }
+ fclose(fin);
+
+ TRACE_OUT(parse_config_file);
+ return (res);
+}
diff --git a/usr.sbin/nscd/parser.h b/usr.sbin/nscd/parser.h
new file mode 100644
index 0000000..d4f3b22
--- /dev/null
+++ b/usr.sbin/nscd/parser.h
@@ -0,0 +1,35 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 __NSCD_PARSER_H__
+#define __NSCD_PARSER_H__
+
+int parse_config_file(struct configuration *,
+ const char *, char const **, int *);
+
+#endif
diff --git a/usr.sbin/nscd/protocol.c b/usr.sbin/nscd/protocol.c
new file mode 100644
index 0000000..3e40739
--- /dev/null
+++ b/usr.sbin/nscd/protocol.c
@@ -0,0 +1,551 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "debug.h"
+#include "log.h"
+#include "protocol.h"
+
+/*
+ * Initializes the comm_element with any given type of data
+ */
+void
+init_comm_element(struct comm_element *element, enum comm_element_t type)
+{
+
+ TRACE_IN(init_comm_element);
+ memset(element, 0, sizeof(struct comm_element));
+
+ switch (type) {
+ case CET_WRITE_REQUEST:
+ init_cache_write_request(&element->c_write_request);
+ break;
+ case CET_WRITE_RESPONSE:
+ init_cache_write_response(&element->c_write_response);
+ break;
+ case CET_READ_REQUEST:
+ init_cache_read_request(&element->c_read_request);
+ break;
+ case CET_READ_RESPONSE:
+ init_cache_read_response(&element->c_read_response);
+ break;
+ case CET_TRANSFORM_REQUEST:
+ init_cache_transform_request(&element->c_transform_request);
+ break;
+ case CET_TRANSFORM_RESPONSE:
+ init_cache_transform_response(&element->c_transform_response);
+ break;
+ case CET_MP_WRITE_SESSION_REQUEST:
+ init_cache_mp_write_session_request(&element->c_mp_ws_request);
+ break;
+ case CET_MP_WRITE_SESSION_RESPONSE:
+ init_cache_mp_write_session_response(&element->c_mp_ws_response);
+ break;
+ case CET_MP_WRITE_SESSION_WRITE_REQUEST:
+ init_cache_mp_write_session_write_request(
+ &element->c_mp_ws_write_request);
+ break;
+ case CET_MP_WRITE_SESSION_WRITE_RESPONSE:
+ init_cache_mp_write_session_write_response(
+ &element->c_mp_ws_write_response);
+ break;
+ case CET_MP_READ_SESSION_REQUEST:
+ init_cache_mp_read_session_request(&element->c_mp_rs_request);
+ break;
+ case CET_MP_READ_SESSION_RESPONSE:
+ init_cache_mp_read_session_response(&element->c_mp_rs_response);
+ break;
+ case CET_MP_READ_SESSION_READ_RESPONSE:
+ init_cache_mp_read_session_read_response(
+ &element->c_mp_rs_read_response);
+ break;
+ case CET_UNDEFINED:
+ break;
+ default:
+ LOG_ERR_2("init_comm_element", "invalid communication element");
+ TRACE_OUT(init_comm_element);
+ return;
+ }
+
+ element->type = type;
+ TRACE_OUT(init_comm_element);
+}
+
+void
+finalize_comm_element(struct comm_element *element)
+{
+
+ TRACE_IN(finalize_comm_element);
+ switch (element->type) {
+ case CET_WRITE_REQUEST:
+ finalize_cache_write_request(&element->c_write_request);
+ break;
+ case CET_WRITE_RESPONSE:
+ finalize_cache_write_response(&element->c_write_response);
+ break;
+ case CET_READ_REQUEST:
+ finalize_cache_read_request(&element->c_read_request);
+ break;
+ case CET_READ_RESPONSE:
+ finalize_cache_read_response(&element->c_read_response);
+ break;
+ case CET_TRANSFORM_REQUEST:
+ finalize_cache_transform_request(&element->c_transform_request);
+ break;
+ case CET_TRANSFORM_RESPONSE:
+ finalize_cache_transform_response(
+ &element->c_transform_response);
+ break;
+ case CET_MP_WRITE_SESSION_REQUEST:
+ finalize_cache_mp_write_session_request(
+ &element->c_mp_ws_request);
+ break;
+ case CET_MP_WRITE_SESSION_RESPONSE:
+ finalize_cache_mp_write_session_response(
+ &element->c_mp_ws_response);
+ break;
+ case CET_MP_WRITE_SESSION_WRITE_REQUEST:
+ finalize_cache_mp_write_session_write_request(
+ &element->c_mp_ws_write_request);
+ break;
+ case CET_MP_WRITE_SESSION_WRITE_RESPONSE:
+ finalize_cache_mp_write_session_write_response(
+ &element->c_mp_ws_write_response);
+ break;
+ case CET_MP_READ_SESSION_REQUEST:
+ finalize_cache_mp_read_session_request(
+ &element->c_mp_rs_request);
+ break;
+ case CET_MP_READ_SESSION_RESPONSE:
+ finalize_cache_mp_read_session_response(
+ &element->c_mp_rs_response);
+ break;
+ case CET_MP_READ_SESSION_READ_RESPONSE:
+ finalize_cache_mp_read_session_read_response(
+ &element->c_mp_rs_read_response);
+ break;
+ case CET_UNDEFINED:
+ break;
+ default:
+ break;
+ }
+
+ element->type = CET_UNDEFINED;
+ TRACE_OUT(finalize_comm_element);
+}
+
+void
+init_cache_write_request(struct cache_write_request *write_request)
+{
+
+ TRACE_IN(init_cache_write_request);
+ memset(write_request, 0, sizeof(struct cache_write_request));
+ TRACE_OUT(init_cache_write_request);
+}
+
+void
+finalize_cache_write_request(struct cache_write_request *write_request)
+{
+
+ TRACE_IN(finalize_cache_write_request);
+ free(write_request->entry);
+ free(write_request->cache_key);
+ free(write_request->data);
+ TRACE_OUT(finalize_cache_write_request);
+}
+
+struct cache_write_request *
+get_cache_write_request(struct comm_element *element)
+{
+
+ TRACE_IN(get_cache_write_request);
+ assert(element->type == CET_WRITE_REQUEST);
+ TRACE_OUT(get_cache_write_request);
+ return (&element->c_write_request);
+}
+
+void
+init_cache_write_response(struct cache_write_response *write_response)
+{
+
+ TRACE_IN(init_cache_write_response);
+ memset(write_response, 0, sizeof(struct cache_write_response));
+ TRACE_OUT(init_cache_write_response);
+}
+
+void
+finalize_cache_write_response(struct cache_write_response *write_response)
+{
+
+ TRACE_IN(finalize_cache_write_response);
+ TRACE_OUT(finalize_cache_write_response);
+}
+
+struct cache_write_response *
+get_cache_write_response(struct comm_element *element)
+{
+
+ TRACE_IN(get_cache_write_response);
+ assert(element->type == CET_WRITE_RESPONSE);
+ TRACE_OUT(get_cache_write_response);
+ return (&element->c_write_response);
+}
+
+void
+init_cache_read_request(struct cache_read_request *read_request)
+{
+
+ TRACE_IN(init_cache_read_request);
+ memset(read_request, 0, sizeof(struct cache_read_request));
+ TRACE_OUT(init_cache_read_request);
+}
+
+void
+finalize_cache_read_request(struct cache_read_request *read_request)
+{
+
+ TRACE_IN(finalize_cache_read_request);
+ free(read_request->entry);
+ free(read_request->cache_key);
+ TRACE_OUT(finalize_cache_read_request);
+}
+
+struct cache_read_request *
+get_cache_read_request(struct comm_element *element)
+{
+
+ TRACE_IN(get_cache_read_request);
+ assert(element->type == CET_READ_REQUEST);
+ TRACE_OUT(get_cache_read_request);
+ return (&element->c_read_request);
+}
+
+void
+init_cache_read_response(struct cache_read_response *read_response)
+{
+
+ TRACE_IN(init_cache_read_response);
+ memset(read_response, 0, sizeof(struct cache_read_response));
+ TRACE_OUT(init_cache_read_response);
+}
+
+void
+finalize_cache_read_response(struct cache_read_response *read_response)
+{
+
+ TRACE_IN(finalize_cache_read_response);
+ free(read_response->data);
+ TRACE_OUT(finalize_cache_read_response);
+}
+
+struct cache_read_response *
+get_cache_read_response(struct comm_element *element)
+{
+
+ TRACE_IN(get_cache_read_response);
+ assert(element->type == CET_READ_RESPONSE);
+ TRACE_OUT(get_cache_read_response);
+ return (&element->c_read_response);
+}
+
+void
+init_cache_transform_request(struct cache_transform_request *transform_request)
+{
+
+ TRACE_IN(init_cache_transform_request);
+ memset(transform_request, 0, sizeof(struct cache_transform_request));
+ TRACE_OUT(init_cache_transform_request);
+}
+
+void
+finalize_cache_transform_request(
+ struct cache_transform_request *transform_request)
+{
+
+ TRACE_IN(finalize_cache_transform_request);
+ free(transform_request->entry);
+ TRACE_OUT(finalize_cache_transform_request);
+}
+
+struct cache_transform_request *
+get_cache_transform_request(struct comm_element *element)
+{
+
+ TRACE_IN(get_cache_transform_request);
+ assert(element->type == CET_TRANSFORM_REQUEST);
+ TRACE_OUT(get_cache_transform_request);
+ return (&element->c_transform_request);
+}
+
+void
+init_cache_transform_response(
+ struct cache_transform_response *transform_response)
+{
+
+ TRACE_IN(init_cache_transform_request);
+ memset(transform_response, 0, sizeof(struct cache_transform_response));
+ TRACE_OUT(init_cache_transform_request);
+}
+
+void
+finalize_cache_transform_response(
+ struct cache_transform_response *transform_response)
+{
+
+ TRACE_IN(finalize_cache_transform_response);
+ TRACE_OUT(finalize_cache_transform_response);
+}
+
+struct cache_transform_response *
+get_cache_transform_response(struct comm_element *element)
+{
+
+ TRACE_IN(get_cache_transform_response);
+ assert(element->type == CET_TRANSFORM_RESPONSE);
+ TRACE_OUT(get_cache_transform_response);
+ return (&element->c_transform_response);
+}
+
+
+void
+init_cache_mp_write_session_request(
+ struct cache_mp_write_session_request *mp_ws_request)
+{
+
+ TRACE_IN(init_cache_mp_write_session_request);
+ memset(mp_ws_request, 0,
+ sizeof(struct cache_mp_write_session_request));
+ TRACE_OUT(init_cache_mp_write_session_request);
+}
+
+void
+finalize_cache_mp_write_session_request(
+ struct cache_mp_write_session_request *mp_ws_request)
+{
+
+ TRACE_IN(finalize_cache_mp_write_session_request);
+ free(mp_ws_request->entry);
+ TRACE_OUT(finalize_cache_mp_write_session_request);
+}
+
+struct cache_mp_write_session_request *
+get_cache_mp_write_session_request(struct comm_element *element)
+{
+
+ TRACE_IN(get_cache_mp_write_session_request);
+ assert(element->type == CET_MP_WRITE_SESSION_REQUEST);
+ TRACE_OUT(get_cache_mp_write_session_request);
+ return (&element->c_mp_ws_request);
+}
+
+void
+init_cache_mp_write_session_response(
+ struct cache_mp_write_session_response *mp_ws_response)
+{
+
+ TRACE_IN(init_cache_mp_write_session_response);
+ memset(mp_ws_response, 0,
+ sizeof(struct cache_mp_write_session_response));
+ TRACE_OUT(init_cache_mp_write_session_response);
+}
+
+void
+finalize_cache_mp_write_session_response(
+ struct cache_mp_write_session_response *mp_ws_response)
+{
+
+ TRACE_IN(finalize_cache_mp_write_session_response);
+ TRACE_OUT(finalize_cache_mp_write_session_response);
+}
+
+struct cache_mp_write_session_response *
+get_cache_mp_write_session_response(struct comm_element *element)
+{
+
+ TRACE_IN(get_cache_mp_write_session_response);
+ assert(element->type == CET_MP_WRITE_SESSION_RESPONSE);
+ TRACE_OUT(get_cache_mp_write_session_response);
+ return (&element->c_mp_ws_response);
+}
+
+void
+init_cache_mp_write_session_write_request(
+ struct cache_mp_write_session_write_request *mp_ws_write_request)
+{
+
+ TRACE_IN(init_cache_mp_write_session_write_request);
+ memset(mp_ws_write_request, 0,
+ sizeof(struct cache_mp_write_session_write_request));
+ TRACE_OUT(init_cache_mp_write_session_write_response);
+}
+
+void
+finalize_cache_mp_write_session_write_request(
+ struct cache_mp_write_session_write_request *mp_ws_write_request)
+{
+
+ TRACE_IN(finalize_cache_mp_write_session_write_request);
+ free(mp_ws_write_request->data);
+ TRACE_OUT(finalize_cache_mp_write_session_write_request);
+}
+
+struct cache_mp_write_session_write_request *
+get_cache_mp_write_session_write_request(struct comm_element *element)
+{
+
+ TRACE_IN(get_cache_mp_write_session_write_request);
+ assert(element->type == CET_MP_WRITE_SESSION_WRITE_REQUEST);
+ TRACE_OUT(get_cache_mp_write_session_write_request);
+ return (&element->c_mp_ws_write_request);
+}
+
+void
+init_cache_mp_write_session_write_response(
+ struct cache_mp_write_session_write_response *mp_ws_write_response)
+{
+
+ TRACE_IN(init_cache_mp_write_session_write_response);
+ memset(mp_ws_write_response, 0,
+ sizeof(struct cache_mp_write_session_write_response));
+ TRACE_OUT(init_cache_mp_write_session_write_response);
+}
+
+void
+finalize_cache_mp_write_session_write_response(
+ struct cache_mp_write_session_write_response *mp_ws_write_response)
+{
+
+ TRACE_IN(finalize_cache_mp_write_session_write_response);
+ TRACE_OUT(finalize_cache_mp_write_session_write_response);
+}
+
+struct cache_mp_write_session_write_response *
+get_cache_mp_write_session_write_response(struct comm_element *element)
+{
+
+ TRACE_IN(get_cache_mp_write_session_write_response);
+ assert(element->type == CET_MP_WRITE_SESSION_WRITE_RESPONSE);
+ TRACE_OUT(get_cache_mp_write_session_write_response);
+ return (&element->c_mp_ws_write_response);
+}
+
+void
+init_cache_mp_read_session_request(
+ struct cache_mp_read_session_request *mp_rs_request)
+{
+
+ TRACE_IN(init_cache_mp_read_session_request);
+ memset(mp_rs_request, 0, sizeof(struct cache_mp_read_session_request));
+ TRACE_OUT(init_cache_mp_read_session_request);
+}
+
+void
+finalize_cache_mp_read_session_request(
+ struct cache_mp_read_session_request *mp_rs_request)
+{
+
+ TRACE_IN(finalize_cache_mp_read_session_request);
+ free(mp_rs_request->entry);
+ TRACE_OUT(finalize_cache_mp_read_session_request);
+}
+
+struct cache_mp_read_session_request *
+get_cache_mp_read_session_request(struct comm_element *element)
+{
+
+ TRACE_IN(get_cache_mp_read_session_request);
+ assert(element->type == CET_MP_READ_SESSION_REQUEST);
+ TRACE_OUT(get_cache_mp_read_session_request);
+ return (&element->c_mp_rs_request);
+}
+
+void
+init_cache_mp_read_session_response(
+ struct cache_mp_read_session_response *mp_rs_response)
+{
+
+ TRACE_IN(init_cache_mp_read_session_response);
+ memset(mp_rs_response, 0,
+ sizeof(struct cache_mp_read_session_response));
+ TRACE_OUT(init_cache_mp_read_session_response);
+}
+
+void
+finalize_cache_mp_read_session_response(
+ struct cache_mp_read_session_response *mp_rs_response)
+{
+
+ TRACE_IN(finalize_cache_mp_read_session_response);
+ TRACE_OUT(finalize_cache_mp_read_session_response);
+}
+
+struct cache_mp_read_session_response *
+get_cache_mp_read_session_response(struct comm_element *element)
+{
+
+ TRACE_IN(get_cache_mp_read_session_response);
+ assert(element->type == CET_MP_READ_SESSION_RESPONSE);
+ TRACE_OUT(get_cache_mp_read_session_response);
+ return (&element->c_mp_rs_response);
+}
+
+void
+init_cache_mp_read_session_read_response(
+ struct cache_mp_read_session_read_response *mp_ws_read_response)
+{
+
+ TRACE_IN(init_cache_mp_read_session_read_response);
+ memset(mp_ws_read_response, 0,
+ sizeof(struct cache_mp_read_session_read_response));
+ TRACE_OUT(init_cache_mp_read_session_read_response);
+}
+
+void
+finalize_cache_mp_read_session_read_response(
+ struct cache_mp_read_session_read_response *mp_rs_read_response)
+{
+
+ TRACE_IN(finalize_cache_mp_read_session_read_response);
+ free(mp_rs_read_response->data);
+ TRACE_OUT(finalize_cache_mp_read_session_read_response);
+}
+
+struct cache_mp_read_session_read_response *
+get_cache_mp_read_session_read_response(struct comm_element *element)
+{
+
+ TRACE_IN(get_cache_mp_read_session_read_response);
+ assert(element->type == CET_MP_READ_SESSION_READ_RESPONSE);
+ TRACE_OUT(get_cache_mp_read_session_read_response);
+ return (&element->c_mp_rs_read_response);
+}
diff --git a/usr.sbin/nscd/protocol.h b/usr.sbin/nscd/protocol.h
new file mode 100644
index 0000000..270335c
--- /dev/null
+++ b/usr.sbin/nscd/protocol.h
@@ -0,0 +1,249 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 __NSCD_PROTOCOL_H__
+#define __NSCD_PROTOCOL_H__
+
+/* maximum buffer size to receive - larger buffers are not allowed */
+#define MAX_BUFFER_SIZE (1 << 20)
+
+/* buffer size correctness checking routine */
+#define BUFSIZE_CORRECT(x) (((x) > 0) && ((x) < MAX_BUFFER_SIZE))
+#define BUFSIZE_INVALID(x) (!BUFSIZE_CORRECT(x))
+
+/* structures below represent the data that are sent/received by the daemon */
+struct cache_write_request {
+ char *entry;
+ char *cache_key;
+ char *data;
+
+ size_t entry_length;
+ size_t cache_key_size;
+ size_t data_size;
+};
+
+struct cache_write_response {
+ int error_code;
+};
+
+struct cache_read_request {
+ char *entry;
+ char *cache_key;
+
+ size_t entry_length;
+ size_t cache_key_size;
+};
+
+struct cache_read_response {
+ char *data; // ignored if error_code is not 0
+ size_t data_size; // ignored if error_code is not 0
+
+ int error_code;
+};
+
+enum transformation_type {
+ TT_USER = 0, // transform only the entries of the caller
+ TT_ALL = 1 // transform all entries
+};
+
+struct cache_transform_request {
+ char *entry; // ignored if entry_length is 0
+ size_t entry_length;
+
+ int transformation_type;
+};
+
+struct cache_transform_response {
+ int error_code;
+};
+
+struct cache_mp_write_session_request {
+ char *entry;
+ size_t entry_length;
+};
+
+struct cache_mp_write_session_response {
+ int error_code;
+};
+
+struct cache_mp_write_session_write_request {
+ char *data;
+ size_t data_size;
+};
+
+struct cache_mp_write_session_write_response {
+ int error_code;
+};
+
+struct cache_mp_read_session_request {
+ char *entry;
+ size_t entry_length;
+};
+
+struct cache_mp_read_session_response {
+ int error_code;
+};
+
+struct cache_mp_read_session_read_response {
+ char *data;
+ size_t data_size;
+
+ int error_code;
+};
+
+
+enum comm_element_t {
+ CET_UNDEFINED = 0,
+ CET_WRITE_REQUEST = 1,
+ CET_WRITE_RESPONSE = 2,
+ CET_READ_REQUEST = 3,
+ CET_READ_RESPONSE = 4,
+ CET_TRANSFORM_REQUEST = 5,
+ CET_TRANSFORM_RESPONSE = 6,
+ CET_MP_WRITE_SESSION_REQUEST = 7,
+ CET_MP_WRITE_SESSION_RESPONSE = 8,
+ CET_MP_WRITE_SESSION_WRITE_REQUEST = 9,
+ CET_MP_WRITE_SESSION_WRITE_RESPONSE = 10,
+ CET_MP_WRITE_SESSION_CLOSE_NOTIFICATION = 11,
+ CET_MP_WRITE_SESSION_ABANDON_NOTIFICATION = 12,
+ CET_MP_READ_SESSION_REQUEST = 13,
+ CET_MP_READ_SESSION_RESPONSE = 14,
+ CET_MP_READ_SESSION_READ_REQUEST = 15,
+ CET_MP_READ_SESSION_READ_RESPONSE = 16,
+ CET_MP_READ_SESSION_CLOSE_NOTIFICATION = 17,
+ CET_MAX = 18
+};
+
+/*
+ * The comm_element is used as the holder of any known (defined above) data
+ * type that is to be sent/received.
+ */
+struct comm_element {
+ union {
+ struct cache_write_request c_write_request;
+ struct cache_write_response c_write_response;
+ struct cache_read_request c_read_request;
+ struct cache_read_response c_read_response;
+ struct cache_transform_request c_transform_request;
+ struct cache_transform_response c_transform_response;
+
+ struct cache_mp_write_session_request c_mp_ws_request;
+ struct cache_mp_write_session_response c_mp_ws_response;
+ struct cache_mp_write_session_write_request c_mp_ws_write_request;
+ struct cache_mp_write_session_write_response c_mp_ws_write_response;
+
+ struct cache_mp_read_session_request c_mp_rs_request;
+ struct cache_mp_read_session_response c_mp_rs_response;
+ struct cache_mp_read_session_read_response c_mp_rs_read_response;
+ } /* anonymous */;
+ enum comm_element_t type;
+};
+
+void init_comm_element(struct comm_element *, enum comm_element_t type);
+void finalize_comm_element(struct comm_element *);
+
+/*
+ * For each type of data, there is three functions (init/finalize/get), that
+ * used with comm_element structure
+ */
+void init_cache_write_request(struct cache_write_request *);
+void finalize_cache_write_request(struct cache_write_request *);
+struct cache_write_request *get_cache_write_request(struct comm_element *);
+
+void init_cache_write_response(struct cache_write_response *);
+void finalize_cache_write_response(struct cache_write_response *);
+struct cache_write_response *get_cache_write_response(struct comm_element *);
+
+void init_cache_read_request(struct cache_read_request *);
+void finalize_cache_read_request(struct cache_read_request *);
+struct cache_read_request *get_cache_read_request(struct comm_element *);
+
+void init_cache_read_response(struct cache_read_response *);
+void finalize_cache_read_response(struct cache_read_response *);
+struct cache_read_response *get_cache_read_response(struct comm_element *);
+
+void init_cache_transform_request(struct cache_transform_request *);
+void finalize_cache_transform_request(struct cache_transform_request *);
+struct cache_transform_request *get_cache_transform_request(
+ struct comm_element *);
+
+void init_cache_transform_response(struct cache_transform_response *);
+void finalize_cache_transform_response(struct cache_transform_response *);
+struct cache_transform_response *get_cache_transform_response(
+ struct comm_element *);
+
+void init_cache_mp_write_session_request(
+ struct cache_mp_write_session_request *);
+void finalize_cache_mp_write_session_request(
+ struct cache_mp_write_session_request *);
+struct cache_mp_write_session_request *
+ get_cache_mp_write_session_request(struct comm_element *);
+
+void init_cache_mp_write_session_response(
+ struct cache_mp_write_session_response *);
+void finalize_cache_mp_write_session_response(
+ struct cache_mp_write_session_response *);
+struct cache_mp_write_session_response *
+ get_cache_mp_write_session_response(struct comm_element *);
+
+void init_cache_mp_write_session_write_request(
+ struct cache_mp_write_session_write_request *);
+void finalize_cache_mp_write_session_write_request(
+ struct cache_mp_write_session_write_request *);
+struct cache_mp_write_session_write_request *
+ get_cache_mp_write_session_write_request(struct comm_element *);
+
+void init_cache_mp_write_session_write_response(
+ struct cache_mp_write_session_write_response *);
+void finalize_cache_mp_write_session_write_response(
+ struct cache_mp_write_session_write_response *);
+struct cache_mp_write_session_write_response *
+ get_cache_mp_write_session_write_response(struct comm_element *);
+
+void init_cache_mp_read_session_request(
+ struct cache_mp_read_session_request *);
+void finalize_cache_mp_read_session_request(
+ struct cache_mp_read_session_request *);
+struct cache_mp_read_session_request *get_cache_mp_read_session_request(
+ struct comm_element *);
+
+void init_cache_mp_read_session_response(
+ struct cache_mp_read_session_response *);
+void finalize_cache_mp_read_session_response(
+ struct cache_mp_read_session_response *);
+struct cache_mp_read_session_response *
+ get_cache_mp_read_session_response(struct comm_element *);
+
+void init_cache_mp_read_session_read_response(
+ struct cache_mp_read_session_read_response *);
+void finalize_cache_mp_read_session_read_response(
+ struct cache_mp_read_session_read_response *);
+struct cache_mp_read_session_read_response *
+ get_cache_mp_read_session_read_response(struct comm_element *);
+
+#endif
diff --git a/usr.sbin/nscd/query.c b/usr.sbin/nscd/query.c
new file mode 100644
index 0000000..270992e
--- /dev/null
+++ b/usr.sbin/nscd/query.c
@@ -0,0 +1,1277 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/event.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <nsswitch.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "debug.h"
+#include "query.h"
+#include "log.h"
+#include "mp_ws_query.h"
+#include "mp_rs_query.h"
+#include "singletons.h"
+
+static const char negative_data[1] = { 0 };
+
+extern void get_time_func(struct timeval *);
+
+static void clear_config_entry(struct configuration_entry *);
+static void clear_config_entry_part(struct configuration_entry *,
+ const char *, size_t);
+
+static int on_query_startup(struct query_state *);
+static void on_query_destroy(struct query_state *);
+
+static int on_read_request_read1(struct query_state *);
+static int on_read_request_read2(struct query_state *);
+static int on_read_request_process(struct query_state *);
+static int on_read_response_write1(struct query_state *);
+static int on_read_response_write2(struct query_state *);
+
+static int on_rw_mapper(struct query_state *);
+
+static int on_transform_request_read1(struct query_state *);
+static int on_transform_request_read2(struct query_state *);
+static int on_transform_request_process(struct query_state *);
+static int on_transform_response_write1(struct query_state *);
+
+static int on_write_request_read1(struct query_state *);
+static int on_write_request_read2(struct query_state *);
+static int on_negative_write_request_process(struct query_state *);
+static int on_write_request_process(struct query_state *);
+static int on_write_response_write1(struct query_state *);
+
+/*
+ * Clears the specified configuration entry (clears the cache for positive and
+ * and negative entries) and also for all multipart entries.
+ */
+static void
+clear_config_entry(struct configuration_entry *config_entry)
+{
+ size_t i;
+
+ TRACE_IN(clear_config_entry);
+ configuration_lock_entry(config_entry, CELT_POSITIVE);
+ if (config_entry->positive_cache_entry != NULL)
+ transform_cache_entry(
+ config_entry->positive_cache_entry,
+ CTT_CLEAR);
+ configuration_unlock_entry(config_entry, CELT_POSITIVE);
+
+ configuration_lock_entry(config_entry, CELT_NEGATIVE);
+ if (config_entry->negative_cache_entry != NULL)
+ transform_cache_entry(
+ config_entry->negative_cache_entry,
+ CTT_CLEAR);
+ configuration_unlock_entry(config_entry, CELT_NEGATIVE);
+
+ configuration_lock_entry(config_entry, CELT_MULTIPART);
+ for (i = 0; i < config_entry->mp_cache_entries_size; ++i)
+ transform_cache_entry(
+ config_entry->mp_cache_entries[i],
+ CTT_CLEAR);
+ configuration_unlock_entry(config_entry, CELT_MULTIPART);
+
+ TRACE_OUT(clear_config_entry);
+}
+
+/*
+ * Clears the specified configuration entry by deleting only the elements,
+ * that are owned by the user with specified eid_str.
+ */
+static void
+clear_config_entry_part(struct configuration_entry *config_entry,
+ const char *eid_str, size_t eid_str_length)
+{
+ cache_entry *start, *finish, *mp_entry;
+ TRACE_IN(clear_config_entry_part);
+ configuration_lock_entry(config_entry, CELT_POSITIVE);
+ if (config_entry->positive_cache_entry != NULL)
+ transform_cache_entry_part(
+ config_entry->positive_cache_entry,
+ CTT_CLEAR, eid_str, eid_str_length, KPPT_LEFT);
+ configuration_unlock_entry(config_entry, CELT_POSITIVE);
+
+ configuration_lock_entry(config_entry, CELT_NEGATIVE);
+ if (config_entry->negative_cache_entry != NULL)
+ transform_cache_entry_part(
+ config_entry->negative_cache_entry,
+ CTT_CLEAR, eid_str, eid_str_length, KPPT_LEFT);
+ configuration_unlock_entry(config_entry, CELT_NEGATIVE);
+
+ configuration_lock_entry(config_entry, CELT_MULTIPART);
+ if (configuration_entry_find_mp_cache_entries(config_entry,
+ eid_str, &start, &finish) == 0) {
+ for (mp_entry = start; mp_entry != finish; ++mp_entry)
+ transform_cache_entry(*mp_entry, CTT_CLEAR);
+ }
+ configuration_unlock_entry(config_entry, CELT_MULTIPART);
+
+ TRACE_OUT(clear_config_entry_part);
+}
+
+/*
+ * This function is assigned to the query_state structue on its creation.
+ * It's main purpose is to receive credentials from the client.
+ */
+static int
+on_query_startup(struct query_state *qstate)
+{
+ struct msghdr cred_hdr;
+ struct iovec iov;
+ struct cmsgcred *cred;
+ int elem_type;
+
+ struct {
+ struct cmsghdr hdr;
+ char cred[CMSG_SPACE(sizeof(struct cmsgcred))];
+ } cmsg;
+
+ TRACE_IN(on_query_startup);
+ assert(qstate != NULL);
+
+ memset(&cred_hdr, 0, sizeof(struct msghdr));
+ cred_hdr.msg_iov = &iov;
+ cred_hdr.msg_iovlen = 1;
+ cred_hdr.msg_control = (caddr_t)&cmsg;
+ cred_hdr.msg_controllen = CMSG_LEN(sizeof(struct cmsgcred));
+
+ memset(&iov, 0, sizeof(struct iovec));
+ iov.iov_base = &elem_type;
+ iov.iov_len = sizeof(int);
+
+ if (recvmsg(qstate->sockfd, &cred_hdr, 0) == -1) {
+ TRACE_OUT(on_query_startup);
+ return (-1);
+ }
+
+ if (cmsg.hdr.cmsg_len < CMSG_LEN(sizeof(struct cmsgcred))
+ || cmsg.hdr.cmsg_level != SOL_SOCKET
+ || cmsg.hdr.cmsg_type != SCM_CREDS) {
+ TRACE_OUT(on_query_startup);
+ return (-1);
+ }
+
+ cred = (struct cmsgcred *)CMSG_DATA(&cmsg);
+ qstate->uid = cred->cmcred_uid;
+ qstate->gid = cred->cmcred_gid;
+
+#if defined(NS_NSCD_EID_CHECKING) || defined(NS_STRICT_NSCD_EID_CHECKING)
+/*
+ * This check is probably a bit redundant - per-user cache is always separated
+ * by the euid/egid pair
+ */
+ if (check_query_eids(qstate) != 0) {
+#ifdef NS_STRICT_NSCD_EID_CHECKING
+ TRACE_OUT(on_query_startup);
+ return (-1);
+#else
+ if ((elem_type != CET_READ_REQUEST) &&
+ (elem_type != CET_MP_READ_SESSION_REQUEST) &&
+ (elem_type != CET_WRITE_REQUEST) &&
+ (elem_type != CET_MP_WRITE_SESSION_REQUEST)) {
+ TRACE_OUT(on_query_startup);
+ return (-1);
+ }
+#endif
+ }
+#endif
+
+ switch (elem_type) {
+ case CET_WRITE_REQUEST:
+ qstate->process_func = on_write_request_read1;
+ break;
+ case CET_READ_REQUEST:
+ qstate->process_func = on_read_request_read1;
+ break;
+ case CET_TRANSFORM_REQUEST:
+ qstate->process_func = on_transform_request_read1;
+ break;
+ case CET_MP_WRITE_SESSION_REQUEST:
+ qstate->process_func = on_mp_write_session_request_read1;
+ break;
+ case CET_MP_READ_SESSION_REQUEST:
+ qstate->process_func = on_mp_read_session_request_read1;
+ break;
+ default:
+ TRACE_OUT(on_query_startup);
+ return (-1);
+ }
+
+ qstate->kevent_watermark = 0;
+ TRACE_OUT(on_query_startup);
+ return (0);
+}
+
+/*
+ * on_rw_mapper is used to process multiple read/write requests during
+ * one connection session. It's never called in the beginning (on query_state
+ * creation) as it does not process the multipart requests and does not
+ * receive credentials
+ */
+static int
+on_rw_mapper(struct query_state *qstate)
+{
+ ssize_t result;
+ int elem_type;
+
+ TRACE_IN(on_rw_mapper);
+ if (qstate->kevent_watermark == 0) {
+ qstate->kevent_watermark = sizeof(int);
+ } else {
+ result = qstate->read_func(qstate, &elem_type, sizeof(int));
+ if (result != sizeof(int)) {
+ TRACE_OUT(on_rw_mapper);
+ return (-1);
+ }
+
+ switch (elem_type) {
+ case CET_WRITE_REQUEST:
+ qstate->kevent_watermark = sizeof(size_t);
+ qstate->process_func = on_write_request_read1;
+ break;
+ case CET_READ_REQUEST:
+ qstate->kevent_watermark = sizeof(size_t);
+ qstate->process_func = on_read_request_read1;
+ break;
+ default:
+ TRACE_OUT(on_rw_mapper);
+ return (-1);
+ break;
+ }
+ }
+ TRACE_OUT(on_rw_mapper);
+ return (0);
+}
+
+/*
+ * The default query_destroy function
+ */
+static void
+on_query_destroy(struct query_state *qstate)
+{
+
+ TRACE_IN(on_query_destroy);
+ finalize_comm_element(&qstate->response);
+ finalize_comm_element(&qstate->request);
+ TRACE_OUT(on_query_destroy);
+}
+
+/*
+ * The functions below are used to process write requests.
+ * - on_write_request_read1 and on_write_request_read2 read the request itself
+ * - on_write_request_process processes it (if the client requests to
+ * cache the negative result, the on_negative_write_request_process is used)
+ * - on_write_response_write1 sends the response
+ */
+static int
+on_write_request_read1(struct query_state *qstate)
+{
+ struct cache_write_request *write_request;
+ ssize_t result;
+
+ TRACE_IN(on_write_request_read1);
+ if (qstate->kevent_watermark == 0)
+ qstate->kevent_watermark = sizeof(size_t) * 3;
+ else {
+ init_comm_element(&qstate->request, CET_WRITE_REQUEST);
+ write_request = get_cache_write_request(&qstate->request);
+
+ result = qstate->read_func(qstate, &write_request->entry_length,
+ sizeof(size_t));
+ result += qstate->read_func(qstate,
+ &write_request->cache_key_size, sizeof(size_t));
+ result += qstate->read_func(qstate,
+ &write_request->data_size, sizeof(size_t));
+
+ if (result != sizeof(size_t) * 3) {
+ TRACE_OUT(on_write_request_read1);
+ return (-1);
+ }
+
+ if (BUFSIZE_INVALID(write_request->entry_length) ||
+ BUFSIZE_INVALID(write_request->cache_key_size) ||
+ (BUFSIZE_INVALID(write_request->data_size) &&
+ (write_request->data_size != 0))) {
+ TRACE_OUT(on_write_request_read1);
+ return (-1);
+ }
+
+ write_request->entry = calloc(1,
+ write_request->entry_length + 1);
+ assert(write_request->entry != NULL);
+
+ write_request->cache_key = calloc(1,
+ write_request->cache_key_size +
+ qstate->eid_str_length);
+ assert(write_request->cache_key != NULL);
+ memcpy(write_request->cache_key, qstate->eid_str,
+ qstate->eid_str_length);
+
+ if (write_request->data_size != 0) {
+ write_request->data = calloc(1,
+ write_request->data_size);
+ assert(write_request->data != NULL);
+ }
+
+ qstate->kevent_watermark = write_request->entry_length +
+ write_request->cache_key_size +
+ write_request->data_size;
+ qstate->process_func = on_write_request_read2;
+ }
+
+ TRACE_OUT(on_write_request_read1);
+ return (0);
+}
+
+static int
+on_write_request_read2(struct query_state *qstate)
+{
+ struct cache_write_request *write_request;
+ ssize_t result;
+
+ TRACE_IN(on_write_request_read2);
+ write_request = get_cache_write_request(&qstate->request);
+
+ result = qstate->read_func(qstate, write_request->entry,
+ write_request->entry_length);
+ result += qstate->read_func(qstate, write_request->cache_key +
+ qstate->eid_str_length, write_request->cache_key_size);
+ if (write_request->data_size != 0)
+ result += qstate->read_func(qstate, write_request->data,
+ write_request->data_size);
+
+ if (result != (ssize_t)qstate->kevent_watermark) {
+ TRACE_OUT(on_write_request_read2);
+ return (-1);
+ }
+ write_request->cache_key_size += qstate->eid_str_length;
+
+ qstate->kevent_watermark = 0;
+ if (write_request->data_size != 0)
+ qstate->process_func = on_write_request_process;
+ else
+ qstate->process_func = on_negative_write_request_process;
+ TRACE_OUT(on_write_request_read2);
+ return (0);
+}
+
+static int
+on_write_request_process(struct query_state *qstate)
+{
+ struct cache_write_request *write_request;
+ struct cache_write_response *write_response;
+ cache_entry c_entry;
+
+ TRACE_IN(on_write_request_process);
+ init_comm_element(&qstate->response, CET_WRITE_RESPONSE);
+ write_response = get_cache_write_response(&qstate->response);
+ write_request = get_cache_write_request(&qstate->request);
+
+ qstate->config_entry = configuration_find_entry(
+ s_configuration, write_request->entry);
+
+ if (qstate->config_entry == NULL) {
+ write_response->error_code = ENOENT;
+
+ LOG_ERR_2("write_request", "can't find configuration"
+ " entry '%s'. aborting request", write_request->entry);
+ goto fin;
+ }
+
+ if (qstate->config_entry->enabled == 0) {
+ write_response->error_code = EACCES;
+
+ LOG_ERR_2("write_request",
+ "configuration entry '%s' is disabled",
+ write_request->entry);
+ goto fin;
+ }
+
+ if (qstate->config_entry->perform_actual_lookups != 0) {
+ write_response->error_code = EOPNOTSUPP;
+
+ LOG_ERR_2("write_request",
+ "entry '%s' performs lookups by itself: "
+ "can't write to it", write_request->entry);
+ goto fin;
+ }
+
+ configuration_lock_rdlock(s_configuration);
+ c_entry = find_cache_entry(s_cache,
+ qstate->config_entry->positive_cache_params.cep.entry_name);
+ configuration_unlock(s_configuration);
+ if (c_entry != NULL) {
+ configuration_lock_entry(qstate->config_entry, CELT_POSITIVE);
+ qstate->config_entry->positive_cache_entry = c_entry;
+ write_response->error_code = cache_write(c_entry,
+ write_request->cache_key,
+ write_request->cache_key_size,
+ write_request->data,
+ write_request->data_size);
+ configuration_unlock_entry(qstate->config_entry, CELT_POSITIVE);
+
+ if ((qstate->config_entry->common_query_timeout.tv_sec != 0) ||
+ (qstate->config_entry->common_query_timeout.tv_usec != 0))
+ memcpy(&qstate->timeout,
+ &qstate->config_entry->common_query_timeout,
+ sizeof(struct timeval));
+
+ } else
+ write_response->error_code = -1;
+
+fin:
+ qstate->kevent_filter = EVFILT_WRITE;
+ qstate->kevent_watermark = sizeof(int);
+ qstate->process_func = on_write_response_write1;
+
+ TRACE_OUT(on_write_request_process);
+ return (0);
+}
+
+static int
+on_negative_write_request_process(struct query_state *qstate)
+{
+ struct cache_write_request *write_request;
+ struct cache_write_response *write_response;
+ cache_entry c_entry;
+
+ TRACE_IN(on_negative_write_request_process);
+ init_comm_element(&qstate->response, CET_WRITE_RESPONSE);
+ write_response = get_cache_write_response(&qstate->response);
+ write_request = get_cache_write_request(&qstate->request);
+
+ qstate->config_entry = configuration_find_entry (
+ s_configuration, write_request->entry);
+
+ if (qstate->config_entry == NULL) {
+ write_response->error_code = ENOENT;
+
+ LOG_ERR_2("negative_write_request",
+ "can't find configuration"
+ " entry '%s'. aborting request", write_request->entry);
+ goto fin;
+ }
+
+ if (qstate->config_entry->enabled == 0) {
+ write_response->error_code = EACCES;
+
+ LOG_ERR_2("negative_write_request",
+ "configuration entry '%s' is disabled",
+ write_request->entry);
+ goto fin;
+ }
+
+ if (qstate->config_entry->perform_actual_lookups != 0) {
+ write_response->error_code = EOPNOTSUPP;
+
+ LOG_ERR_2("negative_write_request",
+ "entry '%s' performs lookups by itself: "
+ "can't write to it", write_request->entry);
+ goto fin;
+ } else {
+#ifdef NS_NSCD_EID_CHECKING
+ if (check_query_eids(qstate) != 0) {
+ write_response->error_code = EPERM;
+ goto fin;
+ }
+#endif
+ }
+
+ configuration_lock_rdlock(s_configuration);
+ c_entry = find_cache_entry(s_cache,
+ qstate->config_entry->negative_cache_params.cep.entry_name);
+ configuration_unlock(s_configuration);
+ if (c_entry != NULL) {
+ configuration_lock_entry(qstate->config_entry, CELT_NEGATIVE);
+ qstate->config_entry->negative_cache_entry = c_entry;
+ write_response->error_code = cache_write(c_entry,
+ write_request->cache_key,
+ write_request->cache_key_size,
+ negative_data,
+ sizeof(negative_data));
+ configuration_unlock_entry(qstate->config_entry, CELT_NEGATIVE);
+
+ if ((qstate->config_entry->common_query_timeout.tv_sec != 0) ||
+ (qstate->config_entry->common_query_timeout.tv_usec != 0))
+ memcpy(&qstate->timeout,
+ &qstate->config_entry->common_query_timeout,
+ sizeof(struct timeval));
+ } else
+ write_response->error_code = -1;
+
+fin:
+ qstate->kevent_filter = EVFILT_WRITE;
+ qstate->kevent_watermark = sizeof(int);
+ qstate->process_func = on_write_response_write1;
+
+ TRACE_OUT(on_negative_write_request_process);
+ return (0);
+}
+
+static int
+on_write_response_write1(struct query_state *qstate)
+{
+ struct cache_write_response *write_response;
+ ssize_t result;
+
+ TRACE_IN(on_write_response_write1);
+ write_response = get_cache_write_response(&qstate->response);
+ result = qstate->write_func(qstate, &write_response->error_code,
+ sizeof(int));
+ if (result != sizeof(int)) {
+ TRACE_OUT(on_write_response_write1);
+ return (-1);
+ }
+
+ finalize_comm_element(&qstate->request);
+ finalize_comm_element(&qstate->response);
+
+ qstate->kevent_watermark = sizeof(int);
+ qstate->kevent_filter = EVFILT_READ;
+ qstate->process_func = on_rw_mapper;
+
+ TRACE_OUT(on_write_response_write1);
+ return (0);
+}
+
+/*
+ * The functions below are used to process read requests.
+ * - on_read_request_read1 and on_read_request_read2 read the request itself
+ * - on_read_request_process processes it
+ * - on_read_response_write1 and on_read_response_write2 send the response
+ */
+static int
+on_read_request_read1(struct query_state *qstate)
+{
+ struct cache_read_request *read_request;
+ ssize_t result;
+
+ TRACE_IN(on_read_request_read1);
+ if (qstate->kevent_watermark == 0)
+ qstate->kevent_watermark = sizeof(size_t) * 2;
+ else {
+ init_comm_element(&qstate->request, CET_READ_REQUEST);
+ read_request = get_cache_read_request(&qstate->request);
+
+ result = qstate->read_func(qstate,
+ &read_request->entry_length, sizeof(size_t));
+ result += qstate->read_func(qstate,
+ &read_request->cache_key_size, sizeof(size_t));
+
+ if (result != sizeof(size_t) * 2) {
+ TRACE_OUT(on_read_request_read1);
+ return (-1);
+ }
+
+ if (BUFSIZE_INVALID(read_request->entry_length) ||
+ BUFSIZE_INVALID(read_request->cache_key_size)) {
+ TRACE_OUT(on_read_request_read1);
+ return (-1);
+ }
+
+ read_request->entry = calloc(1,
+ read_request->entry_length + 1);
+ assert(read_request->entry != NULL);
+
+ read_request->cache_key = calloc(1,
+ read_request->cache_key_size +
+ qstate->eid_str_length);
+ assert(read_request->cache_key != NULL);
+ memcpy(read_request->cache_key, qstate->eid_str,
+ qstate->eid_str_length);
+
+ qstate->kevent_watermark = read_request->entry_length +
+ read_request->cache_key_size;
+ qstate->process_func = on_read_request_read2;
+ }
+
+ TRACE_OUT(on_read_request_read1);
+ return (0);
+}
+
+static int
+on_read_request_read2(struct query_state *qstate)
+{
+ struct cache_read_request *read_request;
+ ssize_t result;
+
+ TRACE_IN(on_read_request_read2);
+ read_request = get_cache_read_request(&qstate->request);
+
+ result = qstate->read_func(qstate, read_request->entry,
+ read_request->entry_length);
+ result += qstate->read_func(qstate,
+ read_request->cache_key + qstate->eid_str_length,
+ read_request->cache_key_size);
+
+ if (result != (ssize_t)qstate->kevent_watermark) {
+ TRACE_OUT(on_read_request_read2);
+ return (-1);
+ }
+ read_request->cache_key_size += qstate->eid_str_length;
+
+ qstate->kevent_watermark = 0;
+ qstate->process_func = on_read_request_process;
+
+ TRACE_OUT(on_read_request_read2);
+ return (0);
+}
+
+static int
+on_read_request_process(struct query_state *qstate)
+{
+ struct cache_read_request *read_request;
+ struct cache_read_response *read_response;
+ cache_entry c_entry, neg_c_entry;
+
+ struct agent *lookup_agent;
+ struct common_agent *c_agent;
+ int res;
+
+ TRACE_IN(on_read_request_process);
+ init_comm_element(&qstate->response, CET_READ_RESPONSE);
+ read_response = get_cache_read_response(&qstate->response);
+ read_request = get_cache_read_request(&qstate->request);
+
+ qstate->config_entry = configuration_find_entry(
+ s_configuration, read_request->entry);
+ if (qstate->config_entry == NULL) {
+ read_response->error_code = ENOENT;
+
+ LOG_ERR_2("read_request",
+ "can't find configuration "
+ "entry '%s'. aborting request", read_request->entry);
+ goto fin;
+ }
+
+ if (qstate->config_entry->enabled == 0) {
+ read_response->error_code = EACCES;
+
+ LOG_ERR_2("read_request",
+ "configuration entry '%s' is disabled",
+ read_request->entry);
+ goto fin;
+ }
+
+ /*
+ * if we perform lookups by ourselves, then we don't need to separate
+ * cache entries by euid and egid
+ */
+ if (qstate->config_entry->perform_actual_lookups != 0)
+ memset(read_request->cache_key, 0, qstate->eid_str_length);
+ else {
+#ifdef NS_NSCD_EID_CHECKING
+ if (check_query_eids(qstate) != 0) {
+ /* if the lookup is not self-performing, we check for clients euid/egid */
+ read_response->error_code = EPERM;
+ goto fin;
+ }
+#endif
+ }
+
+ configuration_lock_rdlock(s_configuration);
+ c_entry = find_cache_entry(s_cache,
+ qstate->config_entry->positive_cache_params.cep.entry_name);
+ neg_c_entry = find_cache_entry(s_cache,
+ qstate->config_entry->negative_cache_params.cep.entry_name);
+ configuration_unlock(s_configuration);
+ if ((c_entry != NULL) && (neg_c_entry != NULL)) {
+ configuration_lock_entry(qstate->config_entry, CELT_POSITIVE);
+ qstate->config_entry->positive_cache_entry = c_entry;
+ read_response->error_code = cache_read(c_entry,
+ read_request->cache_key,
+ read_request->cache_key_size, NULL,
+ &read_response->data_size);
+
+ if (read_response->error_code == -2) {
+ read_response->data = malloc(
+ read_response->data_size);
+ assert(read_response != NULL);
+ read_response->error_code = cache_read(c_entry,
+ read_request->cache_key,
+ read_request->cache_key_size,
+ read_response->data,
+ &read_response->data_size);
+ }
+ configuration_unlock_entry(qstate->config_entry, CELT_POSITIVE);
+
+ configuration_lock_entry(qstate->config_entry, CELT_NEGATIVE);
+ qstate->config_entry->negative_cache_entry = neg_c_entry;
+ if (read_response->error_code == -1) {
+ read_response->error_code = cache_read(neg_c_entry,
+ read_request->cache_key,
+ read_request->cache_key_size, NULL,
+ &read_response->data_size);
+
+ if (read_response->error_code == -2) {
+ read_response->error_code = 0;
+ read_response->data = NULL;
+ read_response->data_size = 0;
+ }
+ }
+ configuration_unlock_entry(qstate->config_entry, CELT_NEGATIVE);
+
+ if ((read_response->error_code == -1) &&
+ (qstate->config_entry->perform_actual_lookups != 0)) {
+ free(read_response->data);
+ read_response->data = NULL;
+ read_response->data_size = 0;
+
+ lookup_agent = find_agent(s_agent_table,
+ read_request->entry, COMMON_AGENT);
+
+ if ((lookup_agent != NULL) &&
+ (lookup_agent->type == COMMON_AGENT)) {
+ c_agent = (struct common_agent *)lookup_agent;
+ res = c_agent->lookup_func(
+ read_request->cache_key +
+ qstate->eid_str_length,
+ read_request->cache_key_size -
+ qstate->eid_str_length,
+ &read_response->data,
+ &read_response->data_size);
+
+ if (res == NS_SUCCESS) {
+ read_response->error_code = 0;
+ configuration_lock_entry(
+ qstate->config_entry,
+ CELT_POSITIVE);
+ cache_write(c_entry,
+ read_request->cache_key,
+ read_request->cache_key_size,
+ read_response->data,
+ read_response->data_size);
+ configuration_unlock_entry(
+ qstate->config_entry,
+ CELT_POSITIVE);
+ } else if ((res == NS_NOTFOUND) ||
+ (res == NS_RETURN)) {
+ configuration_lock_entry(
+ qstate->config_entry,
+ CELT_NEGATIVE);
+ cache_write(neg_c_entry,
+ read_request->cache_key,
+ read_request->cache_key_size,
+ negative_data,
+ sizeof(negative_data));
+ configuration_unlock_entry(
+ qstate->config_entry,
+ CELT_NEGATIVE);
+
+ read_response->error_code = 0;
+ read_response->data = NULL;
+ read_response->data_size = 0;
+ }
+ }
+ }
+
+ if ((qstate->config_entry->common_query_timeout.tv_sec != 0) ||
+ (qstate->config_entry->common_query_timeout.tv_usec != 0))
+ memcpy(&qstate->timeout,
+ &qstate->config_entry->common_query_timeout,
+ sizeof(struct timeval));
+ } else
+ read_response->error_code = -1;
+
+fin:
+ qstate->kevent_filter = EVFILT_WRITE;
+ if (read_response->error_code == 0)
+ qstate->kevent_watermark = sizeof(int) + sizeof(size_t);
+ else
+ qstate->kevent_watermark = sizeof(int);
+ qstate->process_func = on_read_response_write1;
+
+ TRACE_OUT(on_read_request_process);
+ return (0);
+}
+
+static int
+on_read_response_write1(struct query_state *qstate)
+{
+ struct cache_read_response *read_response;
+ ssize_t result;
+
+ TRACE_IN(on_read_response_write1);
+ read_response = get_cache_read_response(&qstate->response);
+
+ result = qstate->write_func(qstate, &read_response->error_code,
+ sizeof(int));
+
+ if (read_response->error_code == 0) {
+ result += qstate->write_func(qstate, &read_response->data_size,
+ sizeof(size_t));
+ if (result != (ssize_t)qstate->kevent_watermark) {
+ TRACE_OUT(on_read_response_write1);
+ return (-1);
+ }
+
+ qstate->kevent_watermark = read_response->data_size;
+ qstate->process_func = on_read_response_write2;
+ } else {
+ if (result != (ssize_t)qstate->kevent_watermark) {
+ TRACE_OUT(on_read_response_write1);
+ return (-1);
+ }
+
+ qstate->kevent_watermark = 0;
+ qstate->process_func = NULL;
+ }
+
+ TRACE_OUT(on_read_response_write1);
+ return (0);
+}
+
+static int
+on_read_response_write2(struct query_state *qstate)
+{
+ struct cache_read_response *read_response;
+ ssize_t result;
+
+ TRACE_IN(on_read_response_write2);
+ read_response = get_cache_read_response(&qstate->response);
+ if (read_response->data_size > 0) {
+ result = qstate->write_func(qstate, read_response->data,
+ read_response->data_size);
+ if (result != (ssize_t)qstate->kevent_watermark) {
+ TRACE_OUT(on_read_response_write2);
+ return (-1);
+ }
+ }
+
+ finalize_comm_element(&qstate->request);
+ finalize_comm_element(&qstate->response);
+
+ qstate->kevent_watermark = sizeof(int);
+ qstate->kevent_filter = EVFILT_READ;
+ qstate->process_func = on_rw_mapper;
+ TRACE_OUT(on_read_response_write2);
+ return (0);
+}
+
+/*
+ * The functions below are used to process write requests.
+ * - on_transform_request_read1 and on_transform_request_read2 read the
+ * request itself
+ * - on_transform_request_process processes it
+ * - on_transform_response_write1 sends the response
+ */
+static int
+on_transform_request_read1(struct query_state *qstate)
+{
+ struct cache_transform_request *transform_request;
+ ssize_t result;
+
+ TRACE_IN(on_transform_request_read1);
+ if (qstate->kevent_watermark == 0)
+ qstate->kevent_watermark = sizeof(size_t) + sizeof(int);
+ else {
+ init_comm_element(&qstate->request, CET_TRANSFORM_REQUEST);
+ transform_request =
+ get_cache_transform_request(&qstate->request);
+
+ result = qstate->read_func(qstate,
+ &transform_request->entry_length, sizeof(size_t));
+ result += qstate->read_func(qstate,
+ &transform_request->transformation_type, sizeof(int));
+
+ if (result != sizeof(size_t) + sizeof(int)) {
+ TRACE_OUT(on_transform_request_read1);
+ return (-1);
+ }
+
+ if ((transform_request->transformation_type != TT_USER) &&
+ (transform_request->transformation_type != TT_ALL)) {
+ TRACE_OUT(on_transform_request_read1);
+ return (-1);
+ }
+
+ if (transform_request->entry_length != 0) {
+ if (BUFSIZE_INVALID(transform_request->entry_length)) {
+ TRACE_OUT(on_transform_request_read1);
+ return (-1);
+ }
+
+ transform_request->entry = calloc(1,
+ transform_request->entry_length + 1);
+ assert(transform_request->entry != NULL);
+
+ qstate->process_func = on_transform_request_read2;
+ } else
+ qstate->process_func = on_transform_request_process;
+
+ qstate->kevent_watermark = transform_request->entry_length;
+ }
+
+ TRACE_OUT(on_transform_request_read1);
+ return (0);
+}
+
+static int
+on_transform_request_read2(struct query_state *qstate)
+{
+ struct cache_transform_request *transform_request;
+ ssize_t result;
+
+ TRACE_IN(on_transform_request_read2);
+ transform_request = get_cache_transform_request(&qstate->request);
+
+ result = qstate->read_func(qstate, transform_request->entry,
+ transform_request->entry_length);
+
+ if (result != (ssize_t)qstate->kevent_watermark) {
+ TRACE_OUT(on_transform_request_read2);
+ return (-1);
+ }
+
+ qstate->kevent_watermark = 0;
+ qstate->process_func = on_transform_request_process;
+
+ TRACE_OUT(on_transform_request_read2);
+ return (0);
+}
+
+static int
+on_transform_request_process(struct query_state *qstate)
+{
+ struct cache_transform_request *transform_request;
+ struct cache_transform_response *transform_response;
+ struct configuration_entry *config_entry;
+ size_t i, size;
+
+ TRACE_IN(on_transform_request_process);
+ init_comm_element(&qstate->response, CET_TRANSFORM_RESPONSE);
+ transform_response = get_cache_transform_response(&qstate->response);
+ transform_request = get_cache_transform_request(&qstate->request);
+
+ switch (transform_request->transformation_type) {
+ case TT_USER:
+ if (transform_request->entry == NULL) {
+ size = configuration_get_entries_size(s_configuration);
+ for (i = 0; i < size; ++i) {
+ config_entry = configuration_get_entry(
+ s_configuration, i);
+
+ if (config_entry->perform_actual_lookups == 0)
+ clear_config_entry_part(config_entry,
+ qstate->eid_str, qstate->eid_str_length);
+ }
+ } else {
+ qstate->config_entry = configuration_find_entry(
+ s_configuration, transform_request->entry);
+
+ if (qstate->config_entry == NULL) {
+ LOG_ERR_2("transform_request",
+ "can't find configuration"
+ " entry '%s'. aborting request",
+ transform_request->entry);
+ transform_response->error_code = -1;
+ goto fin;
+ }
+
+ if (qstate->config_entry->perform_actual_lookups != 0) {
+ LOG_ERR_2("transform_request",
+ "can't transform the cache entry %s"
+ ", because it ised for actual lookups",
+ transform_request->entry);
+ transform_response->error_code = -1;
+ goto fin;
+ }
+
+ clear_config_entry_part(qstate->config_entry,
+ qstate->eid_str, qstate->eid_str_length);
+ }
+ break;
+ case TT_ALL:
+ if (qstate->euid != 0)
+ transform_response->error_code = -1;
+ else {
+ if (transform_request->entry == NULL) {
+ size = configuration_get_entries_size(
+ s_configuration);
+ for (i = 0; i < size; ++i) {
+ clear_config_entry(
+ configuration_get_entry(
+ s_configuration, i));
+ }
+ } else {
+ qstate->config_entry = configuration_find_entry(
+ s_configuration,
+ transform_request->entry);
+
+ if (qstate->config_entry == NULL) {
+ LOG_ERR_2("transform_request",
+ "can't find configuration"
+ " entry '%s'. aborting request",
+ transform_request->entry);
+ transform_response->error_code = -1;
+ goto fin;
+ }
+
+ clear_config_entry(qstate->config_entry);
+ }
+ }
+ break;
+ default:
+ transform_response->error_code = -1;
+ }
+
+fin:
+ qstate->kevent_watermark = 0;
+ qstate->process_func = on_transform_response_write1;
+ TRACE_OUT(on_transform_request_process);
+ return (0);
+}
+
+static int
+on_transform_response_write1(struct query_state *qstate)
+{
+ struct cache_transform_response *transform_response;
+ ssize_t result;
+
+ TRACE_IN(on_transform_response_write1);
+ transform_response = get_cache_transform_response(&qstate->response);
+ result = qstate->write_func(qstate, &transform_response->error_code,
+ sizeof(int));
+ if (result != sizeof(int)) {
+ TRACE_OUT(on_transform_response_write1);
+ return (-1);
+ }
+
+ finalize_comm_element(&qstate->request);
+ finalize_comm_element(&qstate->response);
+
+ qstate->kevent_watermark = 0;
+ qstate->process_func = NULL;
+ TRACE_OUT(on_transform_response_write1);
+ return (0);
+}
+
+/*
+ * Checks if the client's euid and egid do not differ from its uid and gid.
+ * Returns 0 on success.
+ */
+int
+check_query_eids(struct query_state *qstate)
+{
+
+ return ((qstate->uid != qstate->euid) || (qstate->gid != qstate->egid) ? -1 : 0);
+}
+
+/*
+ * Uses the qstate fields to process an "alternate" read - when the buffer is
+ * too large to be received during one socket read operation
+ */
+ssize_t
+query_io_buffer_read(struct query_state *qstate, void *buf, size_t nbytes)
+{
+ size_t remaining;
+ ssize_t result;
+
+ TRACE_IN(query_io_buffer_read);
+ if ((qstate->io_buffer_size == 0) || (qstate->io_buffer == NULL))
+ return (-1);
+
+ assert(qstate->io_buffer_p <=
+ qstate->io_buffer + qstate->io_buffer_size);
+ remaining = qstate->io_buffer + qstate->io_buffer_size -
+ qstate->io_buffer_p;
+ if (nbytes < remaining)
+ result = nbytes;
+ else
+ result = remaining;
+
+ memcpy(buf, qstate->io_buffer_p, result);
+ qstate->io_buffer_p += result;
+
+ if (remaining == 0) {
+ free(qstate->io_buffer);
+ qstate->io_buffer = NULL;
+
+ qstate->write_func = query_socket_write;
+ qstate->read_func = query_socket_read;
+ }
+
+ TRACE_OUT(query_io_buffer_read);
+ return (result);
+}
+
+/*
+ * Uses the qstate fields to process an "alternate" write - when the buffer is
+ * too large to be sent during one socket write operation
+ */
+ssize_t
+query_io_buffer_write(struct query_state *qstate, const void *buf,
+ size_t nbytes)
+{
+ size_t remaining;
+ ssize_t result;
+
+ TRACE_IN(query_io_buffer_write);
+ if ((qstate->io_buffer_size == 0) || (qstate->io_buffer == NULL))
+ return (-1);
+
+ assert(qstate->io_buffer_p <=
+ qstate->io_buffer + qstate->io_buffer_size);
+ remaining = qstate->io_buffer + qstate->io_buffer_size -
+ qstate->io_buffer_p;
+ if (nbytes < remaining)
+ result = nbytes;
+ else
+ result = remaining;
+
+ memcpy(qstate->io_buffer_p, buf, result);
+ qstate->io_buffer_p += result;
+
+ if (remaining == 0) {
+ qstate->use_alternate_io = 1;
+ qstate->io_buffer_p = qstate->io_buffer;
+
+ qstate->write_func = query_socket_write;
+ qstate->read_func = query_socket_read;
+ }
+
+ TRACE_OUT(query_io_buffer_write);
+ return (result);
+}
+
+/*
+ * The default "read" function, which reads data directly from socket
+ */
+ssize_t
+query_socket_read(struct query_state *qstate, void *buf, size_t nbytes)
+{
+ ssize_t result;
+
+ TRACE_IN(query_socket_read);
+ if (qstate->socket_failed != 0) {
+ TRACE_OUT(query_socket_read);
+ return (-1);
+ }
+
+ result = read(qstate->sockfd, buf, nbytes);
+ if (result < 0 || (size_t)result < nbytes)
+ qstate->socket_failed = 1;
+
+ TRACE_OUT(query_socket_read);
+ return (result);
+}
+
+/*
+ * The default "write" function, which writes data directly to socket
+ */
+ssize_t
+query_socket_write(struct query_state *qstate, const void *buf, size_t nbytes)
+{
+ ssize_t result;
+
+ TRACE_IN(query_socket_write);
+ if (qstate->socket_failed != 0) {
+ TRACE_OUT(query_socket_write);
+ return (-1);
+ }
+
+ result = write(qstate->sockfd, buf, nbytes);
+ if (result < 0 || (size_t)result < nbytes)
+ qstate->socket_failed = 1;
+
+ TRACE_OUT(query_socket_write);
+ return (result);
+}
+
+/*
+ * Initializes the query_state structure by filling it with the default values.
+ */
+struct query_state *
+init_query_state(int sockfd, size_t kevent_watermark, uid_t euid, gid_t egid)
+{
+ struct query_state *retval;
+
+ TRACE_IN(init_query_state);
+ retval = calloc(1, sizeof(*retval));
+ assert(retval != NULL);
+
+ retval->sockfd = sockfd;
+ retval->kevent_filter = EVFILT_READ;
+ retval->kevent_watermark = kevent_watermark;
+
+ retval->euid = euid;
+ retval->egid = egid;
+ retval->uid = retval->gid = -1;
+
+ if (asprintf(&retval->eid_str, "%d_%d_", retval->euid,
+ retval->egid) == -1) {
+ free(retval);
+ return (NULL);
+ }
+ retval->eid_str_length = strlen(retval->eid_str);
+
+ init_comm_element(&retval->request, CET_UNDEFINED);
+ init_comm_element(&retval->response, CET_UNDEFINED);
+ retval->process_func = on_query_startup;
+ retval->destroy_func = on_query_destroy;
+
+ retval->write_func = query_socket_write;
+ retval->read_func = query_socket_read;
+
+ get_time_func(&retval->creation_time);
+ retval->timeout.tv_sec = s_configuration->query_timeout;
+ retval->timeout.tv_usec = 0;
+
+ TRACE_OUT(init_query_state);
+ return (retval);
+}
+
+void
+destroy_query_state(struct query_state *qstate)
+{
+
+ TRACE_IN(destroy_query_state);
+ if (qstate->eid_str != NULL)
+ free(qstate->eid_str);
+
+ if (qstate->io_buffer != NULL)
+ free(qstate->io_buffer);
+
+ qstate->destroy_func(qstate);
+ free(qstate);
+ TRACE_OUT(destroy_query_state);
+}
diff --git a/usr.sbin/nscd/query.h b/usr.sbin/nscd/query.h
new file mode 100644
index 0000000..f3df906
--- /dev/null
+++ b/usr.sbin/nscd/query.h
@@ -0,0 +1,104 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 __NSCD_QUERY_H__
+#define __NSCD_QUERY_H__
+
+#include "cachelib.h"
+#include "config.h"
+#include "protocol.h"
+
+struct query_state;
+struct configuration;
+struct configuration_entry;
+
+typedef int (*query_process_func)(struct query_state *);
+typedef void (*query_destroy_func)(struct query_state *);
+typedef ssize_t (*query_read_func)(struct query_state *, void *, size_t);
+typedef ssize_t (*query_write_func)(struct query_state *, const void *, size_t);
+
+/*
+ * The query state structure contains the information to process all types of
+ * requests and to send all types of responses.
+ */
+struct query_state {
+ struct timeval creation_time;
+ struct timeval timeout;
+
+ struct comm_element request;
+ struct comm_element response;
+ struct configuration_entry *config_entry;
+ void *mdata;
+
+ query_process_func process_func; /* called on each event */
+ query_destroy_func destroy_func; /* called on destroy */
+
+ /*
+ * By substituting these functions we can opaquely send and received
+ * very large buffers
+ */
+ query_write_func write_func; /* data write function */
+ query_read_func read_func; /* data read function */
+
+ char *eid_str; /* the user-identifying string (euid_egid_) */
+ size_t eid_str_length;
+
+ uid_t euid; /* euid of the caller, received via getpeereid */
+ uid_t uid; /* uid of the caller, received via credentials */
+ gid_t egid; /* egid of the caller, received via getpeereid */
+ gid_t gid; /* gid of the caller received via credentials */
+
+ size_t io_buffer_size;
+ size_t io_buffer_watermark;
+ size_t kevent_watermark; /* bytes to be sent/received */
+ int sockfd; /* the unix socket to read/write */
+ int kevent_filter; /* EVFILT_READ or EVFILT_WRITE */
+ int socket_failed; /* set to 1 if the socket doesn't work correctly */
+
+ /*
+ * These fields are used to opaquely proceed sending/receiving of
+ * the large buffers
+ */
+ char *io_buffer;
+ char *io_buffer_p;
+ int io_buffer_filter;
+ int use_alternate_io;
+};
+
+int check_query_eids(struct query_state *);
+
+ssize_t query_io_buffer_read(struct query_state *, void *, size_t);
+ssize_t query_io_buffer_write(struct query_state *, const void *, size_t);
+
+ssize_t query_socket_read(struct query_state *, void *, size_t);
+ssize_t query_socket_write(struct query_state *, const void *, size_t);
+
+struct query_state *init_query_state(int, size_t, uid_t, gid_t);
+void destroy_query_state(struct query_state *);
+
+#endif
diff --git a/usr.sbin/nscd/singletons.c b/usr.sbin/nscd/singletons.c
new file mode 100644
index 0000000..e557c20
--- /dev/null
+++ b/usr.sbin/nscd/singletons.c
@@ -0,0 +1,38 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/time.h>
+
+#include "singletons.h"
+
+struct configuration *s_configuration = NULL;
+cache s_cache = INVALID_CACHE;
+struct runtime_env *s_runtime_env = NULL;
+struct agent_table *s_agent_table = NULL;
diff --git a/usr.sbin/nscd/singletons.h b/usr.sbin/nscd/singletons.h
new file mode 100644
index 0000000..dfd891f
--- /dev/null
+++ b/usr.sbin/nscd/singletons.h
@@ -0,0 +1,47 @@
+/*-
+ * Copyright (c) 2005 Michael Bushkov <bushman@rsu.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 __NSCD_SINGLETONS_H__
+#define __NSCD_SINGLETONS_H__
+
+#include "cachelib.h"
+#include "config.h"
+#include "agent.h"
+
+struct runtime_env {
+ int queue;
+ int sockfd;
+ int finished; /* for future use */
+};
+
+extern struct configuration *s_configuration;
+extern cache s_cache;
+extern struct runtime_env *s_runtime_env;
+extern struct agent_table *s_agent_table;
+
+#endif
diff --git a/usr.sbin/ntp/Makefile b/usr.sbin/ntp/Makefile
new file mode 100644
index 0000000..ad5b523
--- /dev/null
+++ b/usr.sbin/ntp/Makefile
@@ -0,0 +1,18 @@
+# Makefile for ntpd.
+# $FreeBSD$
+
+SUBDIR= libopts libntp libntpevent libparse ntpd ntpdc ntpq ntpdate \
+ ntptime ntp-keygen sntp
+SUBDIR+= doc
+
+SUBDIR_DEPEND_ntpd= libntp libopts libparse
+SUBDIR_DEPEND_ntpdate= libntp
+SUBDIR_DEPEND_ntpdc= libntp libopts
+SUBDIR_DEPEND_ntpq= libntp libopts
+SUBDIR_DEPEND_ntptime= libntp
+SUBDIR_DEPEND_ntp-keygen= libntp libopts
+SUBDIR_DEPEND_sntp= libntp libntpevent libopts
+
+SUBDIR_PARALLEL=
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/ntp/Makefile.inc b/usr.sbin/ntp/Makefile.inc
new file mode 100644
index 0000000..274ec39
--- /dev/null
+++ b/usr.sbin/ntp/Makefile.inc
@@ -0,0 +1,19 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+DEFS_LOCAL= -DPARSE -DHAVE_CONFIG_H
+NTPDEFS= -DSYS_FREEBSD
+# CLOCKDEFS=
+# -DLOCAL_CLOCK -DPST -DWWVB -DAS2201 -DGOES -DGPSTM -DOMEGA \
+# -DLEITCH -DTRAK -DACTS -DATOM -DDATUM -DHEATH -DMSFEES \
+# -DMX4200 -DNMEA -DBOEDER
+CFLAGS+= ${NTPDEFS} ${DEFS_LOCAL} ${CLOCKDEFS}
+
+.if ${MK_OPENSSL} != "no" && !defined(RELEASE_CRUNCH)
+CFLAGS+= -DOPENSSL -DUSE_OPENSSL_CRYPTO_RAND -DAUTOKEY
+.endif
+
+WARNS?= 0
+
+.include "../Makefile.inc"
diff --git a/usr.sbin/ntp/config.h b/usr.sbin/ntp/config.h
new file mode 100644
index 0000000..73f83a3
--- /dev/null
+++ b/usr.sbin/ntp/config.h
@@ -0,0 +1,1808 @@
+/* config.h. Generated from config.h.in by configure. */
+/* config.h.in. Generated from configure.ac by autoheader. */
+/* $FreeBSD$ */
+
+/* Define if building universal (internal helper macro) */
+/* #undef AC_APPLE_UNIVERSAL_BUILD */
+
+/* Is adjtime() accurate? */
+/* #undef ADJTIME_IS_ACCURATE */
+
+/* Support NTP Autokey protocol? */
+/* #define AUTOKEY 1 */
+
+/* why not HAVE_P_S? */
+/* #undef CALL_PTHREAD_SETCONCURRENCY */
+
+/* ACTS modem service */
+#define CLOCK_ACTS 1
+
+/* Arbiter 1088A/B GPS receiver */
+#define CLOCK_ARBITER 1
+
+/* ARCRON support? */
+#define CLOCK_ARCRON_MSF 1
+
+/* Austron 2200A/2201A GPS receiver? */
+#define CLOCK_AS2201 1
+
+/* PPS interface? */
+#define CLOCK_ATOM 1
+
+/* Datum/Bancomm bc635/VME interface? */
+/* #undef CLOCK_BANC */
+
+/* Chronolog K-series WWVB receiver? */
+#define CLOCK_CHRONOLOG 1
+
+/* CHU modem/decoder */
+#define CLOCK_CHU 1
+
+/* Diems Computime Radio Clock? */
+/* #undef CLOCK_COMPUTIME */
+
+/* Datum Programmable Time System? */
+#define CLOCK_DATUM 1
+
+/* ELV/DCF7000 clock? */
+/* #undef CLOCK_DCF7000 */
+
+/* Dumb generic hh:mm:ss local clock? */
+#define CLOCK_DUMBCLOCK 1
+
+/* Forum Graphic GPS datating station driver? */
+#define CLOCK_FG 1
+
+/* GPSD JSON receiver */
+#define CLOCK_GPSDJSON 1
+
+/* TrueTime GPS receiver/VME interface? */
+/* #undef CLOCK_GPSVME */
+
+/* Heath GC-1000 WWV/WWVH receiver? */
+#define CLOCK_HEATH 1
+
+/* HOPF 6021 clock? */
+/* #undef CLOCK_HOPF6021 */
+
+/* HOPF PCI clock device? */
+#define CLOCK_HOPF_PCI 1
+
+/* HOPF serial clock device? */
+#define CLOCK_HOPF_SERIAL 1
+
+/* HP 58503A GPS receiver? */
+#define CLOCK_HPGPS 1
+
+/* IRIG audio decoder? */
+#define CLOCK_IRIG 1
+
+/* JJY receiver? */
+#define CLOCK_JJY 1
+
+/* Rockwell Jupiter GPS clock? */
+#define CLOCK_JUPITER 1
+
+/* Leitch CSD 5300 Master Clock System Driver? */
+#define CLOCK_LEITCH 1
+
+/* local clock reference? */
+#define CLOCK_LOCAL 1
+
+/* Meinberg clocks */
+#define CLOCK_MEINBERG 1
+
+/* Magnavox MX4200 GPS receiver */
+/* #undef CLOCK_MX4200 */
+
+/* NeoClock4X */
+#define CLOCK_NEOCLOCK4X 1
+
+/* NMEA GPS receiver */
+#define CLOCK_NMEA 1
+
+/* Motorola UT Oncore GPS */
+#define CLOCK_ONCORE 1
+
+/* Palisade clock */
+#define CLOCK_PALISADE 1
+
+/* PARSE driver interface */
+#define CLOCK_PARSE 1
+
+/* Conrad parallel port radio clock */
+#define CLOCK_PCF 1
+
+/* PCL 720 clock support */
+/* #undef CLOCK_PPS720 */
+
+/* PST/Traconex 1020 WWV/WWVH receiver */
+#define CLOCK_PST 1
+
+/* DCF77 raw time code */
+#define CLOCK_RAWDCF 1
+
+/* RCC 8000 clock */
+/* #undef CLOCK_RCC8000 */
+
+/* RIPE NCC Trimble clock */
+/* #undef CLOCK_RIPENCC */
+
+/* Schmid DCF77 clock */
+/* #undef CLOCK_SCHMID */
+
+/* SEL240X protocol */
+/* #undef CLOCK_SEL240X */
+
+/* clock thru shared memory */
+#define CLOCK_SHM 1
+
+/* Spectracom 8170/Netclock/2 WWVB receiver */
+#define CLOCK_SPECTRACOM 1
+
+/* KSI/Odetics TPRO/S GPS receiver/IRIG interface */
+/* #undef CLOCK_TPRO */
+
+/* Trimble GPS receiver/TAIP protocol */
+/* #undef CLOCK_TRIMTAIP */
+
+/* Trimble GPS receiver/TSIP protocol */
+/* #undef CLOCK_TRIMTSIP */
+
+/* Kinemetrics/TrueTime receivers */
+#define CLOCK_TRUETIME 1
+
+/* Spectracom TSYNC timing board */
+/* #undef CLOCK_TSYNCPCI */
+
+/* TrueTime 560 IRIG-B decoder? */
+/* #undef CLOCK_TT560 */
+
+/* Ultralink M320 WWVB receiver? */
+#define CLOCK_ULINK 1
+
+/* VARITEXT clock */
+/* #undef CLOCK_VARITEXT */
+
+/* WHARTON 400A Series clock */
+/* #undef CLOCK_WHARTON_400A */
+
+/* WWV audio driver */
+#define CLOCK_WWV 1
+
+/* Zyfer GPStarplus */
+#define CLOCK_ZYFER 1
+
+/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP
+ systems. This function is required for `alloca.c' support on those systems.
+ */
+/* #undef CRAY_STACKSEG_END */
+
+/* Define to 1 if using `alloca.c'. */
+/* #undef C_ALLOCA */
+
+/* Enable debugging code? */
+#define DEBUG 1
+
+/* Enable processing time debugging? */
+/* #undef DEBUG_TIMING */
+
+/* Declaration style */
+/* #undef DECL_ADJTIME_0 */
+
+/* Declaration style */
+/* #undef DECL_BCOPY_0 */
+
+/* Declaration style */
+/* #undef DECL_BZERO_0 */
+
+/* Declaration style */
+/* #undef DECL_CFSETISPEED_0 */
+
+/* Declare errno? */
+/* #undef DECL_ERRNO */
+
+/* Declaration style */
+/* #undef DECL_HSTRERROR_0 */
+
+/* Declare h_errno? */
+#define DECL_H_ERRNO 1
+
+/* Declaration style */
+/* #undef DECL_INET_NTOA_0 */
+
+/* Declaration style */
+/* #undef DECL_IOCTL_0 */
+
+/* Declaration style */
+/* #undef DECL_IPC_0 */
+
+/* Declaration style */
+/* #undef DECL_MEMMOVE_0 */
+
+/* Declaration style */
+/* #undef DECL_MKSTEMP_0 */
+
+/* Declaration style */
+/* #undef DECL_MKTEMP_0 */
+
+/* Declaration style */
+/* #undef DECL_NLIST_0 */
+
+/* Declaration style */
+/* #undef DECL_PLOCK_0 */
+
+/* Declaration style */
+/* #undef DECL_RENAME_0 */
+
+/* Declaration style */
+/* #undef DECL_SELECT_0 */
+
+/* Declaration style */
+/* #undef DECL_SETITIMER_0 */
+
+/* Declaration style */
+/* #undef DECL_SETPRIORITY_0 */
+
+/* Declaration style */
+/* #undef DECL_SETPRIORITY_1 */
+
+/* Declaration style */
+/* #undef DECL_SIGVEC_0 */
+
+/* Declaration style */
+/* #undef DECL_STDIO_0 */
+
+/* Declaration style */
+/* #undef DECL_STIME_0 */
+
+/* Declaration style */
+/* #undef DECL_STIME_1 */
+
+/* Declaration style */
+/* #undef DECL_STRERROR_0 */
+
+/* Declaration style */
+/* #undef DECL_STRTOL_0 */
+
+/* Declare syscall()? */
+/* #undef DECL_SYSCALL */
+
+/* Declaration style */
+/* #undef DECL_SYSLOG_0 */
+
+/* Declaration style */
+/* #undef DECL_TIMEOFDAY_0 */
+
+/* Declaration style */
+/* #undef DECL_TIME_0 */
+
+/* Declaration style */
+/* #undef DECL_TOLOWER_0 */
+
+/* Declaration style */
+/* #undef DECL_TOUPPER_0 */
+
+/* What is the fallback value for HZ? */
+#define DEFAULT_HZ 100
+
+/* Default number of megabytes for RLIMIT_MEMLOCK */
+#define DFLT_RLIMIT_MEMLOCK 32
+
+/* Default number of 4k pages for RLIMIT_STACK */
+#define DFLT_RLIMIT_STACK 50
+
+/* Directory separator character, usually / or \\ */
+#define DIR_SEP '/'
+
+/* use old autokey session key behavior? */
+/* #undef DISABLE_BUG1243_FIX */
+
+/* synch TODR hourly? */
+/* #undef DOSYNCTODR */
+
+/* The number of minutes in a DST adjustment */
+#define DSTMINUTES 60
+
+/* number of args to el_init() */
+#define EL_INIT_ARGS 4
+
+/* nls support in libopts */
+/* #undef ENABLE_NLS */
+
+/* force ntpdate to step the clock if !defined(STEP_SLEW) ? */
+/* #undef FORCE_NTPDATE_STEP */
+
+/* What is getsockname()'s socklen type? */
+#define GETSOCKNAME_SOCKLEN_TYPE socklen_t
+
+/* Do we have a routing socket (rt_msghdr or rtattr)? */
+#define HAS_ROUTING_SOCKET 1
+
+/* via __adjtimex */
+/* #undef HAVE_ADJTIMEX */
+
+/* Define to 1 if you have `alloca', as a function or macro. */
+#define HAVE_ALLOCA 1
+
+/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix).
+ */
+/* #undef HAVE_ALLOCA_H */
+
+/* Define to 1 if you have the `arc4random_buf' function. */
+#define HAVE_ARC4RANDOM_BUF 1
+
+/* Define to 1 if you have the <arpa/nameser.h> header file. */
+#define HAVE_ARPA_NAMESER_H 1
+
+/* Define to 1 if you have the `atomic_thread_fence' function. */
+/* #undef HAVE_ATOMIC_THREAD_FENCE */
+
+/* Do we have audio support? */
+#define HAVE_AUDIO /**/
+
+/* Define to 1 if you have the <bstring.h> header file. */
+/* #undef HAVE_BSTRING_H */
+
+/* Define to 1 if you have the `canonicalize_file_name' function. */
+/* #undef HAVE_CANONICALIZE_FILE_NAME */
+
+/* Define to 1 if you have the `chmod' function. */
+#define HAVE_CHMOD 1
+
+/* Do we have the CIOGETEV ioctl (SunOS, Linux)? */
+/* #undef HAVE_CIOGETEV */
+
+/* Define to 1 if you have the `clock_getres' function. */
+#define HAVE_CLOCK_GETRES 1
+
+/* Define to 1 if you have the `clock_gettime' function. */
+#define HAVE_CLOCK_GETTIME 1
+
+/* Define to 1 if you have the `clock_settime' function. */
+#define HAVE_CLOCK_SETTIME 1
+
+/* Define to 1 if you have the <cthreads.h> header file. */
+/* #undef HAVE_CTHREADS_H */
+
+/* Define to 1 if you have the `daemon' function. */
+#define HAVE_DAEMON 1
+
+/* Define to 1 if you have the declaration of `strerror_r', and to 0 if you
+ don't. */
+#define HAVE_DECL_STRERROR_R 1
+
+/* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'.
+ */
+#define HAVE_DIRENT_H 1
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Use Rendezvous/DNS-SD registration */
+/* #undef HAVE_DNSREGISTRATION */
+
+/* Define to 1 if you don't have `vprintf' but do have `_doprnt.' */
+/* #undef HAVE_DOPRNT */
+
+/* Can we drop root privileges? */
+/* #undef HAVE_DROPROOT */
+
+/* Define to 1 if you have the <errno.h> header file. */
+#define HAVE_ERRNO_H 1
+
+/* Define to 1 if you have the `EVP_MD_do_all_sorted' function. */
+#define HAVE_EVP_MD_DO_ALL_SORTED 1
+
+/* Define to 1 if you have the `fchmod' function. */
+#define HAVE_FCHMOD 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the `finite' function. */
+/* #undef HAVE_FINITE */
+
+/* Define to 1 if you have the `fnmatch' function. */
+#define HAVE_FNMATCH 1
+
+/* Define to 1 if you have the <fnmatch.h> header file. */
+#define HAVE_FNMATCH_H 1
+
+/* Define to 1 if you have the `fork' function. */
+#define HAVE_FORK 1
+
+/* Define to 1 if you have the `fstat' function. */
+#define HAVE_FSTAT 1
+
+/* Define to 1 if you have the `getbootfile' function. */
+#define HAVE_GETBOOTFILE 1
+
+/* Define to 1 if you have the `getclock' function. */
+/* #undef HAVE_GETCLOCK */
+
+/* Define to 1 if you have the `getdtablesize' function. */
+#define HAVE_GETDTABLESIZE 1
+
+/* Define to 1 if you have the `getifaddrs' function. */
+#define HAVE_GETIFADDRS 1
+
+/* Define to 1 if you have the `getpassphrase' function. */
+/* #undef HAVE_GETPASSPHRASE */
+
+/* Define to 1 if you have the `getrusage' function. */
+#define HAVE_GETRUSAGE 1
+
+/* Define to 1 if you have the `getuid' function. */
+#define HAVE_GETUID 1
+
+/* if you have GNU Pth */
+/* #undef HAVE_GNU_PTH */
+
+/* Define to 1 if you have the <histedit.h> header file. */
+#define HAVE_HISTEDIT_H 1
+
+/* Define to 1 if you have the <history.h> header file. */
+/* #undef HAVE_HISTORY_H */
+
+/* Obvious */
+#define HAVE_HZ_IN_STRUCT_CLOCKINFO 1
+
+/* Define to 1 if you have the <ieeefp.h> header file. */
+#define HAVE_IEEEFP_H 1
+
+/* have iflist_sysctl? */
+#define HAVE_IFLIST_SYSCTL 1
+
+/* Define to 1 if you have the `if_nametoindex' function. */
+#define HAVE_IF_NAMETOINDEX 1
+
+/* inline keyword or macro available */
+#define HAVE_INLINE 1
+
+/* Define to 1 if the system has the type `int16_t'. */
+#define HAVE_INT16_T 1
+
+/* Define to 1 if the system has the type `int32'. */
+/* #undef HAVE_INT32 */
+
+/* int32 type in DNS headers, not others. */
+/* #undef HAVE_INT32_ONLY_WITH_DNS */
+
+/* Define to 1 if the system has the type `int32_t'. */
+#define HAVE_INT32_T 1
+
+/* Define to 1 if the system has the type `int8_t'. */
+#define HAVE_INT8_T 1
+
+/* Define to 1 if the system has the type `intmax_t'. */
+/* #undef HAVE_INTMAX_T */
+
+/* Define to 1 if the system has the type `intptr_t'. */
+#define HAVE_INTPTR_T 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `isfinite' function. */
+#define HAVE_ISFINITE 1
+
+/* Define to 1 if you have the <kvm.h> header file. */
+#define HAVE_KVM_H 1
+
+/* Define to 1 if you have the `kvm_open' function. */
+/* #undef HAVE_KVM_OPEN */
+
+/* Define to 1 if you have the `gen' library (-lgen). */
+/* #undef HAVE_LIBGEN */
+
+/* Define to 1 if you have the <libgen.h> header file. */
+#define HAVE_LIBGEN_H 1
+
+/* Define to 1 if you have the `intl' library (-lintl). */
+/* #undef HAVE_LIBINTL */
+
+/* Define to 1 if you have the <libintl.h> header file. */
+/* #undef HAVE_LIBINTL_H */
+
+/* Define to 1 if you have the <libscf.h> header file. */
+/* #undef HAVE_LIBSCF_H */
+
+/* Define to 1 if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* using Linux pthread? */
+/* #undef HAVE_LINUXTHREADS */
+
+/* Do we have Linux capabilities? */
+/* #undef HAVE_LINUX_CAPABILITIES */
+
+/* Define to 1 if you have the <linux/if_addr.h> header file. */
+/* #undef HAVE_LINUX_IF_ADDR_H */
+
+/* if you have LinuxThreads */
+/* #undef HAVE_LINUX_THREADS */
+
+/* Define to 1 if you have the `localeconv' function. */
+/* #undef HAVE_LOCALECONV */
+
+/* Define to 1 if you have the <locale.h> header file. */
+/* #undef HAVE_LOCALE_H */
+
+/* Define to 1 if the system has the type `long double'. */
+/* #undef HAVE_LONG_DOUBLE */
+
+/* Define to 1 if the system has the type `long long'. */
+#define HAVE_LONG_LONG 1
+
+/* Define to 1 if the system has the type `long long int'. */
+/* #undef HAVE_LONG_LONG_INT */
+
+/* if you have SunOS LWP package */
+/* #undef HAVE_LWP */
+
+/* Define to 1 if you have the <lwp/lwp.h> header file. */
+/* #undef HAVE_LWP_LWP_H */
+
+/* Define to 1 if you have the <machine/inline.h> header file. */
+/* #undef HAVE_MACHINE_INLINE_H */
+
+/* Define to 1 if you have the <machine/soundcard.h> header file. */
+/* #undef HAVE_MACHINE_SOUNDCARD_H */
+
+/* define if you have Mach Cthreads */
+/* #undef HAVE_MACH_CTHREADS */
+
+/* Define to 1 if you have the <mach/cthreads.h> header file. */
+/* #undef HAVE_MACH_CTHREADS_H */
+
+/* Define to 1 if you have the <math.h> header file. */
+#define HAVE_MATH_H 1
+
+/* Define to 1 if you have the `MD5Init' function. */
+#define HAVE_MD5INIT 1
+
+/* Define to 1 if you have the <md5.h> header file. */
+#define HAVE_MD5_H 1
+
+/* Define to 1 if you have the `memlk' function. */
+/* #undef HAVE_MEMLK */
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `mkstemp' function. */
+#define HAVE_MKSTEMP 1
+
+/* Define to 1 if you have the `mktime' function. */
+#define HAVE_MKTIME 1
+
+/* Define to 1 if you have the `mlockall' function. */
+#define HAVE_MLOCKALL 1
+
+/* Define to 1 if you have the `mmap' function. */
+#define HAVE_MMAP 1
+
+/* Define to 1 if you have the `nanosleep' function. */
+#define HAVE_NANOSLEEP 1
+
+/* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */
+/* #undef HAVE_NDIR_H */
+
+/* Define to 1 if you have the <netdb.h> header file. */
+#define HAVE_NETDB_H 1
+
+/* Define to 1 if you have the <netinet/in.h> header file. */
+#define HAVE_NETINET_IN_H 1
+
+/* Define to 1 if you have the <netinet/in_system.h> header file. */
+/* #undef HAVE_NETINET_IN_SYSTEM_H */
+
+/* Define to 1 if you have the <netinet/in_systm.h> header file. */
+#define HAVE_NETINET_IN_SYSTM_H 1
+
+/* Define to 1 if you have the <netinet/in_var.h> header file. */
+#define HAVE_NETINET_IN_VAR_H 1
+
+/* Define to 1 if you have the <netinet/ip.h> header file. */
+#define HAVE_NETINET_IP_H 1
+
+/* NetInfo support? */
+/* #undef HAVE_NETINFO */
+
+/* Define to 1 if you have the <netinfo/ni.h> header file. */
+/* #undef HAVE_NETINFO_NI_H */
+
+/* Define to 1 if you have the <net/if6.h> header file. */
+/* #undef HAVE_NET_IF6_H */
+
+/* Define to 1 if you have the <net/if.h> header file. */
+#define HAVE_NET_IF_H 1
+
+/* Define to 1 if you have the <net/if_var.h> header file. */
+#define HAVE_NET_IF_VAR_H 1
+
+/* Define to 1 if you have the <net/route.h> header file. */
+#define HAVE_NET_ROUTE_H 1
+
+/* Define to 1 if you have the `nice' function. */
+#define HAVE_NICE 1
+
+/* Define to 1 if you have the <nlist.h> header file. */
+#define HAVE_NLIST_H 1
+
+/* via __adjtimex */
+#define HAVE_NTP_ADJTIME 1
+
+/* via __ntp_gettime */
+#define HAVE_NTP_GETTIME 1
+
+/* Do we want support for Samba's signing daemon? */
+#define HAVE_NTP_SIGND 1
+
+/* if you have NT Event Log */
+/* #undef HAVE_NT_EVENT_LOG */
+
+/* if you have NT Service Manager */
+/* #undef HAVE_NT_SERVICE_MANAGER */
+
+/* if you have NT Threads */
+/* #undef HAVE_NT_THREADS */
+
+/* Define to 1 if the system has the type `pid_t'. */
+#define HAVE_PID_T 1
+
+/* Define to 1 if you have the `plock' function. */
+/* #undef HAVE_PLOCK */
+
+/* Define to 1 if you have the <poll.h> header file. */
+#define HAVE_POLL_H 1
+
+/* Do we have the PPS API per the Draft RFC? */
+#define HAVE_PPSAPI 1
+
+/* Define to 1 if you have the <priv.h> header file. */
+/* #undef HAVE_PRIV_H */
+
+/* Define if you have POSIX threads libraries and header files. */
+/* #undef HAVE_PTHREAD */
+
+/* define to pthreads API spec revision */
+#define HAVE_PTHREADS 10
+
+/* Define to 1 if you have the `pthread_attr_getstacksize' function. */
+#define HAVE_PTHREAD_ATTR_GETSTACKSIZE 1
+
+/* Define to 1 if you have the `pthread_attr_setstacksize' function. */
+#define HAVE_PTHREAD_ATTR_SETSTACKSIZE 1
+
+/* define if you have pthread_detach function */
+#define HAVE_PTHREAD_DETACH 1
+
+/* Define to 1 if you have the `pthread_getconcurrency' function. */
+#define HAVE_PTHREAD_GETCONCURRENCY 1
+
+/* Define to 1 if you have the <pthread.h> header file. */
+#define HAVE_PTHREAD_H 1
+
+/* Define to 1 if you have the `pthread_kill' function. */
+#define HAVE_PTHREAD_KILL 1
+
+/* Define to 1 if you have the `pthread_kill_other_threads_np' function. */
+/* #undef HAVE_PTHREAD_KILL_OTHER_THREADS_NP */
+
+/* define if you have pthread_rwlock_destroy function */
+#define HAVE_PTHREAD_RWLOCK_DESTROY 1
+
+/* Define to 1 if you have the `pthread_setconcurrency' function. */
+#define HAVE_PTHREAD_SETCONCURRENCY 1
+
+/* Define to 1 if you have the `pthread_yield' function. */
+#define HAVE_PTHREAD_YIELD 1
+
+/* Define to 1 if you have the <pth.h> header file. */
+/* #undef HAVE_PTH_H */
+
+/* Define to 1 if the system has the type `ptrdiff_t'. */
+#define HAVE_PTRDIFF_T 1
+
+/* Define to 1 if you have the `pututline' function. */
+/* #undef HAVE_PUTUTLINE */
+
+/* Define to 1 if you have the `pututxline' function. */
+#define HAVE_PUTUTXLINE 1
+
+/* Define to 1 if you have the `RAND_bytes' function. */
+#define HAVE_RAND_BYTES 1
+
+/* Define to 1 if you have the `RAND_poll' function. */
+#define HAVE_RAND_POLL 1
+
+/* Define to 1 if you have the <readline.h> header file. */
+/* #undef HAVE_READLINE_H */
+
+/* Define if your readline library has \`add_history' */
+#define HAVE_READLINE_HISTORY 1
+
+/* Define to 1 if you have the <readline/history.h> header file. */
+#define HAVE_READLINE_HISTORY_H 1
+
+/* Define to 1 if you have the <readline/readline.h> header file. */
+#define HAVE_READLINE_READLINE_H 1
+
+/* Define to 1 if you have the `readlink' function. */
+#define HAVE_READLINK 1
+
+/* Define to 1 if you have the `recvmsg' function. */
+#define HAVE_RECVMSG 1
+
+/* Define to 1 if you have the <resolv.h> header file. */
+#define HAVE_RESOLV_H 1
+
+/* Define to 1 if you have the `res_init' function. */
+#define HAVE_RES_INIT 1
+
+/* Do we have Linux routing socket? */
+/* #undef HAVE_RTNETLINK */
+
+/* Define to 1 if you have the `rtprio' function. */
+#define HAVE_RTPRIO 1
+
+/* Define to 1 if you have the <runetype.h> header file. */
+#define HAVE_RUNETYPE_H 1
+
+/* Obvious */
+#define HAVE_SA_SIGACTION_IN_STRUCT_SIGACTION 1
+
+/* Define to 1 if you have the <sched.h> header file. */
+#define HAVE_SCHED_H 1
+
+/* Define to 1 if you have the `sched_setscheduler' function. */
+#define HAVE_SCHED_SETSCHEDULER 1
+
+/* Define to 1 if you have the `sched_yield' function. */
+#define HAVE_SCHED_YIELD 1
+
+/* Define to 1 if you have the <semaphore.h> header file. */
+#define HAVE_SEMAPHORE_H 1
+
+/* Define to 1 if you have the `sem_timedwait' function. */
+#define HAVE_SEM_TIMEDWAIT 1
+
+/* Define to 1 if you have the <setjmp.h> header file. */
+#define HAVE_SETJMP_H 1
+
+/* Define to 1 if you have the `setlinebuf' function. */
+#define HAVE_SETLINEBUF 1
+
+/* Define to 1 if you have the `setpgid' function. */
+#define HAVE_SETPGID 1
+
+/* define if setpgrp takes 0 arguments */
+/* #undef HAVE_SETPGRP_0 */
+
+/* Define to 1 if you have the `setpriority' function. */
+#define HAVE_SETPRIORITY 1
+
+/* Define to 1 if you have the `setrlimit' function. */
+#define HAVE_SETRLIMIT 1
+
+/* Define to 1 if you have the `setsid' function. */
+#define HAVE_SETSID 1
+
+/* Define to 1 if you have the `settimeofday' function. */
+#define HAVE_SETTIMEOFDAY 1
+
+/* Define to 1 if you have the `setvbuf' function. */
+#define HAVE_SETVBUF 1
+
+/* Define to 1 if you have the <sgtty.h> header file. */
+/* #undef HAVE_SGTTY_H */
+
+/* Define to 1 if you have the `sigaction' function. */
+#define HAVE_SIGACTION 1
+
+/* Can we use SIGIO for tcp and udp IO? */
+/* #undef HAVE_SIGNALED_IO */
+
+/* Define to 1 if you have the `sigset' function. */
+#define HAVE_SIGSET 1
+
+/* Define to 1 if you have the `sigvec' function. */
+#define HAVE_SIGVEC 1
+
+/* sigwait() available? */
+#define HAVE_SIGWAIT 1
+
+/* Define to 1 if the system has the type `size_t'. */
+#define HAVE_SIZE_T 1
+
+/* Define if C99-compliant `snprintf' is available. */
+#define HAVE_SNPRINTF 1
+
+/* Define to 1 if you have the `socketpair' function. */
+#define HAVE_SOCKETPAIR 1
+
+/* Are Solaris privileges available? */
+/* #undef HAVE_SOLARIS_PRIVS */
+
+/* Define to 1 if you have the <stdarg.h> header file. */
+#define HAVE_STDARG_H 1
+
+/* Define to 1 if you have the <stdatomic.h> header file. */
+#define HAVE_STDATOMIC_H 1
+
+/* Define to 1 if you have the <stdbool.h> header file. */
+#define HAVE_STDBOOL_H 1
+
+/* Define to 1 if you have the <stddef.h> header file. */
+/* #undef HAVE_STDDEF_H */
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the `stime' function. */
+/* #undef HAVE_STIME */
+
+/* Define to 1 if you have the `strchr' function. */
+#define HAVE_STRCHR 1
+
+/* Define to 1 if you have the `strdup' function. */
+#define HAVE_STRDUP 1
+
+/* Define to 1 if you have the `strerror' function. */
+#define HAVE_STRERROR 1
+
+/* Define to 1 if you have the `strerror_r' function. */
+#define HAVE_STRERROR_R 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strlcat' function. */
+#define HAVE_STRLCAT 1
+
+/* Define to 1 if you have the `strlcpy' function. */
+#define HAVE_STRLCPY 1
+
+/* Define to 1 if you have the <stropts.h> header file. */
+/* #undef HAVE_STROPTS_H */
+
+/* Define to 1 if you have the `strrchr' function. */
+#define HAVE_STRRCHR 1
+
+/* Define to 1 if you have the `strsignal' function. */
+#define HAVE_STRSIGNAL 1
+
+/* Define to 1 if you have the `strtoll' function. */
+#define HAVE_STRTOLL 1
+
+/* Define to 1 if `decimal_point' is a member of `struct lconv'. */
+/* #undef HAVE_STRUCT_LCONV_DECIMAL_POINT */
+
+/* Define to 1 if `thousands_sep' is a member of `struct lconv'. */
+/* #undef HAVE_STRUCT_LCONV_THOUSANDS_SEP */
+
+/* Do we have struct ntptimeval? */
+#define HAVE_STRUCT_NTPTIMEVAL 1
+
+/* Define to 1 if `time.tv_nsec' is a member of `struct ntptimeval'. */
+#define HAVE_STRUCT_NTPTIMEVAL_TIME_TV_NSEC 1
+
+/* Does a system header define struct ppsclockev? */
+/* #undef HAVE_STRUCT_PPSCLOCKEV */
+
+/* Do we have struct snd_size? */
+#define HAVE_STRUCT_SND_SIZE 1
+
+/* Does a system header define struct sockaddr_storage? */
+#define HAVE_STRUCT_SOCKADDR_STORAGE 1
+
+/* struct timespec declared? */
+#define HAVE_STRUCT_TIMESPEC 1
+
+/* Define to 1 if you have the <sun/audioio.h> header file. */
+/* #undef HAVE_SUN_AUDIOIO_H */
+
+/* Define to 1 if you have the <synch.h> header file. */
+/* #undef HAVE_SYNCH_H */
+
+/* Define to 1 if you have the `sysconf' function. */
+#define HAVE_SYSCONF 1
+
+/* Define to 1 if you have the <sysexits.h> header file. */
+#define HAVE_SYSEXITS_H 1
+
+/* */
+#define HAVE_SYSLOG_FACILITYNAMES 1
+
+/* Define to 1 if you have the <sys/audioio.h> header file. */
+/* #undef HAVE_SYS_AUDIOIO_H */
+
+/* Define to 1 if you have the <sys/capability.h> header file. */
+#define HAVE_SYS_CAPABILITY_H 1
+
+/* Define to 1 if you have the <sys/clockctl.h> header file. */
+/* #undef HAVE_SYS_CLOCKCTL_H */
+
+/* Define to 1 if you have the <sys/dir.h> header file, and it defines `DIR'.
+ */
+/* #undef HAVE_SYS_DIR_H */
+
+/* Define to 1 if you have the <sys/file.h> header file. */
+#define HAVE_SYS_FILE_H 1
+
+/* Define to 1 if you have the <sys/i8253.h> header file. */
+/* #undef HAVE_SYS_I8253_H */
+
+/* Define to 1 if you have the <sys/ioctl.h> header file. */
+#define HAVE_SYS_IOCTL_H 1
+
+/* Define to 1 if you have the <sys/ipc.h> header file. */
+#define HAVE_SYS_IPC_H 1
+
+/* Define to 1 if you have the <sys/limits.h> header file. */
+/* #undef HAVE_SYS_LIMITS_H */
+
+/* Define to 1 if you have the <sys/lock.h> header file. */
+#define HAVE_SYS_LOCK_H 1
+
+/* Define to 1 if you have the <sys/mman.h> header file. */
+#define HAVE_SYS_MMAN_H 1
+
+/* Define to 1 if you have the <sys/modem.h> header file. */
+/* #undef HAVE_SYS_MODEM_H */
+
+/* Define to 1 if you have the <sys/ndir.h> header file, and it defines `DIR'.
+ */
+/* #undef HAVE_SYS_NDIR_H */
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#define HAVE_SYS_PARAM_H 1
+
+/* Define to 1 if you have the <sys/pcl720.h> header file. */
+/* #undef HAVE_SYS_PCL720_H */
+
+/* Define to 1 if you have the <sys/poll.h> header file. */
+#define HAVE_SYS_POLL_H 1
+
+/* Define to 1 if you have the <sys/ppsclock.h> header file. */
+/* #undef HAVE_SYS_PPSCLOCK_H */
+
+/* Define to 1 if you have the <sys/ppstime.h> header file. */
+/* #undef HAVE_SYS_PPSTIME_H */
+
+/* Define to 1 if you have the <sys/prctl.h> header file. */
+/* #undef HAVE_SYS_PRCTL_H */
+
+/* Define to 1 if you have the <sys/procset.h> header file. */
+/* #undef HAVE_SYS_PROCSET_H */
+
+/* Define to 1 if you have the <sys/proc.h> header file. */
+#define HAVE_SYS_PROC_H 1
+
+/* Define to 1 if you have the <sys/resource.h> header file. */
+#define HAVE_SYS_RESOURCE_H 1
+
+/* Define to 1 if you have the <sys/sched.h> header file. */
+/* #undef HAVE_SYS_SCHED_H */
+
+/* Define to 1 if you have the <sys/select.h> header file. */
+#define HAVE_SYS_SELECT_H 1
+
+/* Define to 1 if you have the <sys/shm.h> header file. */
+#define HAVE_SYS_SHM_H 1
+
+/* Define to 1 if you have the <sys/signal.h> header file. */
+#define HAVE_SYS_SIGNAL_H 1
+
+/* Define to 1 if you have the <sys/socket.h> header file. */
+#define HAVE_SYS_SOCKET_H 1
+
+/* Define to 1 if you have the <sys/sockio.h> header file. */
+#define HAVE_SYS_SOCKIO_H 1
+
+/* Define to 1 if you have the <sys/soundcard.h> header file. */
+#define HAVE_SYS_SOUNDCARD_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/stream.h> header file. */
+/* #undef HAVE_SYS_STREAM_H */
+
+/* Define to 1 if you have the <sys/stropts.h> header file. */
+/* #undef HAVE_SYS_STROPTS_H */
+
+/* Define to 1 if you have the <sys/sysctl.h> header file. */
+#define HAVE_SYS_SYSCTL_H 1
+
+/* Define to 1 if you have the <sys/syssgi.h> header file. */
+/* #undef HAVE_SYS_SYSSGI_H */
+
+/* Define to 1 if you have the <sys/systune.h> header file. */
+/* #undef HAVE_SYS_SYSTUNE_H */
+
+/* Define to 1 if you have the <sys/termios.h> header file. */
+#define HAVE_SYS_TERMIOS_H 1
+
+/* Define to 1 if you have the <sys/timepps.h> header file. */
+#define HAVE_SYS_TIMEPPS_H 1
+
+/* Define to 1 if you have the <sys/timers.h> header file. */
+#define HAVE_SYS_TIMERS_H 1
+
+/* Define to 1 if you have the <sys/timex.h> header file. */
+#define HAVE_SYS_TIMEX_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/tpro.h> header file. */
+/* #undef HAVE_SYS_TPRO_H */
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Use sys/uio.h for struct iovec help */
+/* #undef HAVE_SYS_UIO_H */
+
+/* Define to 1 if you have the <sys/un.h> header file. */
+#define HAVE_SYS_UN_H 1
+
+/* Define to 1 if you have the <sys/var.h> header file. */
+/* #undef HAVE_SYS_VAR_H */
+
+/* Define to 1 if you have the <sys/wait.h> header file. */
+#define HAVE_SYS_WAIT_H 1
+
+/* Define to 1 if the system has the type `s_char'. */
+/* #undef HAVE_S_CHAR */
+
+/* Define to 1 if you have the <termios.h> header file. */
+#define HAVE_TERMIOS_H 1
+
+/* Define to 1 if you have the <termio.h> header file. */
+/* #undef HAVE_TERMIO_H */
+
+/* if you have Solaris LWP (thr) package */
+/* #undef HAVE_THR */
+
+/* Define to 1 if you have the <thread.h> header file. */
+/* #undef HAVE_THREAD_H */
+
+/* Define to 1 if you have the `thr_getconcurrency' function. */
+/* #undef HAVE_THR_GETCONCURRENCY */
+
+/* Define to 1 if you have the `thr_setconcurrency' function. */
+/* #undef HAVE_THR_SETCONCURRENCY */
+
+/* Define to 1 if you have the `thr_yield' function. */
+/* #undef HAVE_THR_YIELD */
+
+/* Obvious */
+#define HAVE_TICKADJ_IN_STRUCT_CLOCKINFO 1
+
+/* Define to 1 if you have the `timegm' function. */
+#define HAVE_TIMEGM 1
+
+/* Define to 1 if you have the <timepps.h> header file. */
+/* #undef HAVE_TIMEPPS_H */
+
+/* Define to 1 if you have the `timer_create' function. */
+/* #undef HAVE_TIMER_CREATE */
+
+/* Define to 1 if you have the <timex.h> header file. */
+/* #undef HAVE_TIMEX_H */
+
+/* Define to 1 if you have the <time.h> header file. */
+#define HAVE_TIME_H 1
+
+/* Do we have the TIOCGPPSEV ioctl (Solaris)? */
+/* #undef HAVE_TIOCGPPSEV */
+
+/* Do we have the TIOCSPPS ioctl (Solaris)? */
+/* #undef HAVE_TIOCSPPS */
+
+/* Do we have the TIO serial stuff? */
+/* #undef HAVE_TIO_SERIAL_STUFF */
+
+/* Define to 1 if the system has the type `uint16_t'. */
+#define HAVE_UINT16_T 1
+
+/* Define to 1 if the system has the type `uint32_t'. */
+#define HAVE_UINT32_T 1
+
+/* Define to 1 if the system has the type `uint8_t'. */
+#define HAVE_UINT8_T 1
+
+/* Define to 1 if the system has the type `uintmax_t'. */
+/* #undef HAVE_UINTMAX_T */
+
+/* Define to 1 if the system has the type `uintptr_t'. */
+#define HAVE_UINTPTR_T 1
+
+/* Define to 1 if the system has the type `uint_t'. */
+/* #undef HAVE_UINT_T */
+
+/* Define to 1 if you have the `umask' function. */
+#define HAVE_UMASK 1
+
+/* Define to 1 if you have the `uname' function. */
+#define HAVE_UNAME 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* deviant sigwait? */
+/* #undef HAVE_UNIXWARE_SIGWAIT */
+
+/* Define to 1 if the system has the type `unsigned long long int'. */
+#define HAVE_UNSIGNED_LONG_LONG_INT 1
+
+/* Define to 1 if you have the `updwtmp' function. */
+/* #undef HAVE_UPDWTMP */
+
+/* Define to 1 if you have the `updwtmpx' function. */
+/* #undef HAVE_UPDWTMPX */
+
+/* Define to 1 if you have the <utime.h> header file. */
+#define HAVE_UTIME_H 1
+
+/* Define to 1 if you have the <utmpx.h> header file. */
+#define HAVE_UTMPX_H 1
+
+/* Define to 1 if you have the <utmp.h> header file. */
+/* #undef HAVE_UTMP_H */
+
+/* Define to 1 if the system has the type `u_int32'. */
+/* #undef HAVE_U_INT32 */
+
+/* u_int32 type in DNS headers, not others. */
+/* #undef HAVE_U_INT32_ONLY_WITH_DNS */
+
+/* Define to 1 if you have the <values.h> header file. */
+/* #undef HAVE_VALUES_H */
+
+/* Define to 1 if you have the <varargs.h> header file. */
+/* #undef HAVE_VARARGS_H */
+
+/* Define to 1 if you have the `vfork' function. */
+#define HAVE_VFORK 1
+
+/* Define to 1 if you have the <vfork.h> header file. */
+/* #undef HAVE_VFORK_H */
+
+/* Define to 1 if you have the `vprintf' function. */
+#define HAVE_VPRINTF 1
+
+/* Define if C99-compliant `vsnprintf' is available. */
+#define HAVE_VSNPRINTF 1
+
+/* Define to 1 if you have the <wchar.h> header file. */
+#define HAVE_WCHAR_H 1
+
+/* Define to 1 if the system has the type `wchar_t'. */
+#define HAVE_WCHAR_T 1
+
+/* Define to 1 if the system has the type `wint_t'. */
+#define HAVE_WINT_T 1
+
+/* Define to 1 if `fork' works. */
+#define HAVE_WORKING_FORK 1
+
+/* Define to 1 if `vfork' works. */
+#define HAVE_WORKING_VFORK 1
+
+/* define if select implicitly yields */
+#define HAVE_YIELDING_SELECT 1
+
+/* Define to 1 if you have the `_exit' function. */
+#define HAVE__EXIT 1
+
+/* Define to 1 if you have the </sys/sync/queue.h> header file. */
+/* #undef HAVE__SYS_SYNC_QUEUE_H */
+
+/* Define to 1 if you have the </sys/sync/sema.h> header file. */
+/* #undef HAVE__SYS_SYNC_SEMA_H */
+
+/* Define to 1 if you have the `__adjtimex' function. */
+/* #undef HAVE___ADJTIMEX */
+
+/* defined if C compiler supports __attribute__((...)) */
+#define HAVE___ATTRIBUTE__ /**/
+
+
+ /* define away __attribute__() if unsupported */
+ #ifndef HAVE___ATTRIBUTE__
+ # define __attribute__(x) /* empty */
+ #endif
+ #define ISC_PLATFORM_NORETURN_PRE
+ #define ISC_PLATFORM_NORETURN_POST __attribute__((__noreturn__))
+
+
+
+/* Define to 1 if you have the `__ntp_gettime' function. */
+/* #undef HAVE___NTP_GETTIME */
+
+/* Define to 1 if you have the `__res_init' function. */
+/* #undef HAVE___RES_INIT */
+
+/* Does struct sockaddr_storage have __ss_family? */
+/* #undef HAVE___SS_FAMILY_IN_SS */
+
+
+ /* Handle sockaddr_storage.__ss_family */
+ #ifdef HAVE___SS_FAMILY_IN_SS
+ # define ss_family __ss_family
+ #endif /* HAVE___SS_FAMILY_IN_SS */
+
+
+
+/* Define to provide `rpl_snprintf' function. */
+/* #undef HW_WANT_RPL_SNPRINTF */
+
+/* Define to provide `rpl_vsnprintf' function. */
+/* #undef HW_WANT_RPL_VSNPRINTF */
+
+/* Retry queries on _any_ DNS error? */
+/* #undef IGNORE_DNS_ERRORS */
+
+/* Should we use the IRIG sawtooth filter? */
+/* #undef IRIG_SUCKS */
+
+/* Enclose PTHREAD_ONCE_INIT in extra braces? */
+/* #undef ISC_PLATFORM_BRACEPTHREADONCEINIT */
+
+/* Do we need to fix in6isaddr? */
+/* #undef ISC_PLATFORM_FIXIN6ISADDR */
+
+/* ISC: do we have if_nametoindex()? */
+#define ISC_PLATFORM_HAVEIFNAMETOINDEX 1
+
+/* have struct if_laddrconf? */
+/* #undef ISC_PLATFORM_HAVEIF_LADDRCONF */
+
+/* have struct if_laddrreq? */
+/* #undef ISC_PLATFORM_HAVEIF_LADDRREQ */
+
+/* have struct in6_pktinfo? */
+#define ISC_PLATFORM_HAVEIN6PKTINFO 1
+
+/* have IPv6? */
+#define ISC_PLATFORM_HAVEIPV6 1
+
+/* struct sockaddr has sa_len? */
+#define ISC_PLATFORM_HAVESALEN 1
+
+/* sin6_scope_id? */
+#define ISC_PLATFORM_HAVESCOPEID 1
+
+/* missing in6addr_any? */
+/* #undef ISC_PLATFORM_NEEDIN6ADDRANY */
+
+/* Do we need netinet6/in6.h? */
+/* #undef ISC_PLATFORM_NEEDNETINET6IN6H */
+
+/* ISC: provide inet_ntop() */
+/* #undef ISC_PLATFORM_NEEDNTOP */
+
+/* Declare in_port_t? */
+/* #undef ISC_PLATFORM_NEEDPORTT */
+
+/* ISC: provide inet_pton() */
+/* #undef ISC_PLATFORM_NEEDPTON */
+
+/* enable libisc thread support? */
+#define ISC_PLATFORM_USETHREADS 1
+
+/* Does the kernel have an FLL bug? */
+/* #undef KERNEL_FLL_BUG */
+
+/* Does the kernel support precision time discipline? */
+#define KERNEL_PLL 1
+
+/* Define to use libseccomp system call filtering. */
+/* #undef KERN_SECCOMP */
+
+/* What is (probably) the name of DOSYNCTODR in the kernel? */
+#define K_DOSYNCTODR_NAME "_dosynctodr"
+
+/* What is (probably) the name of NOPRINTF in the kernel? */
+#define K_NOPRINTF_NAME "_noprintf"
+
+/* What is the name of TICKADJ in the kernel? */
+#define K_TICKADJ_NAME "_tickadj"
+
+/* What is the name of TICK in the kernel? */
+#define K_TICK_NAME "_tick"
+
+/* define to 1 if library is thread safe */
+#define LDAP_API_FEATURE_X_OPENLDAP_THREAD_SAFE 1
+
+/* leap smear mechanism */
+/* #undef LEAP_SMEAR */
+
+/* Define to any value to include libseccomp sandboxing. */
+/* #undef LIBSECCOMP */
+
+/* Should we align with the NIST lockclock scheme? */
+/* #undef LOCKCLOCK */
+
+/* Define to the sub-directory in which libtool stores uninstalled libraries.
+ */
+#define LT_OBJDIR ".libs/"
+
+/* Does the target support multicast IP? */
+#define MCAST 1
+
+/* Should we recommend a minimum value for tickadj? */
+/* #undef MIN_REC_TICKADJ */
+
+/* Define to 1 if the compiler does not support C99's structure
+ initialization. */
+/* #undef MISSING_C99_STRUCT_INIT */
+
+/* Do we need HPUX adjtime() library support? */
+/* #undef NEED_HPUX_ADJTIME */
+
+/* Do we want the HPUX FindConfig()? */
+/* #undef NEED_HPUX_FINDCONFIG */
+
+/* We need to provide netsnmp_daemonize() */
+/* #undef NEED_NETSNMP_DAEMONIZE */
+
+/* pthread_init() required? */
+/* #undef NEED_PTHREAD_INIT */
+
+/* use PTHREAD_SCOPE_SYSTEM? */
+/* #undef NEED_PTHREAD_SCOPE_SYSTEM */
+
+/* Do we need the qnx adjtime call? */
+/* #undef NEED_QNX_ADJTIME */
+
+/* Do we need extra room for SO_RCVBUF? (HPUX < 8) */
+/* #undef NEED_RCVBUF_SLOP */
+
+/* Do we need an s_char typedef? */
+#define NEED_S_CHAR_TYPEDEF 1
+
+/* Might nlist() values require an extra level of indirection (AIX)? */
+/* #undef NLIST_EXTRA_INDIRECTION */
+
+/* does struct nlist use a name union? */
+/* #undef NLIST_NAME_UNION */
+
+/* nlist stuff */
+#define NLIST_STRUCT 1
+
+/* Should we NOT read /dev/kmem? */
+#define NOKMEM 1
+
+/* Define to 1 if your C compiler doesn't accept -c and -o together. */
+/* #undef NO_MINUS_C_MINUS_O */
+
+/* Should we avoid #warning on option name collisions? */
+/* #undef NO_OPTION_NAME_WARNINGS */
+
+/* Is there a problem using PARENB and IGNPAR? */
+/* #undef NO_PARENB_IGNPAR */
+
+/* define if you have (or want) no threads */
+/* #undef NO_THREADS */
+
+/* Default location of crypto key info */
+#define NTP_KEYSDIR "/etc/ntp"
+
+/* Path to sign daemon rendezvous socket */
+#define NTP_SIGND_PATH "/var/run/ntp_signd"
+
+/* Do we have ntp_{adj,get}time in libc? */
+#define NTP_SYSCALLS_LIBC 1
+
+/* Do we have ntp_{adj,get}time in the kernel? */
+/* #undef NTP_SYSCALLS_STD */
+
+/* Do we have support for SHMEM_STATUS? */
+#define ONCORE_SHMEM_STATUS 1
+
+/* Use OpenSSL? */
+/* #define OPENSSL */
+
+/* Should we open the broadcast socket? */
+#define OPEN_BCAST_SOCKET 1
+
+/* need to recreate sockets on changed routing? */
+/* #undef OS_MISSES_SPECIFIC_ROUTE_UPDATES */
+
+/* wildcard socket needs REUSEADDR to bind interface addresses */
+/* #undef OS_NEEDS_REUSEADDR_FOR_IFADDRBIND */
+
+/* Do we need to override the system's idea of HZ? */
+#define OVERRIDE_HZ 1
+
+/* Name of package */
+#define PACKAGE "ntp"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "http://bugs.ntp.org./"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "ntp"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "ntp 4.2.8p5"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "ntp"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL "http://www.ntp.org./"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "4.2.8p5"
+
+/* data dir */
+#define PERLLIBDIR "/usr/local/share/ntp/lib"
+
+/* define to a working POSIX compliant shell */
+#define POSIX_SHELL "/bin/sh"
+
+/* PARSE kernel PLL PPS support */
+/* #undef PPS_SYNC */
+
+/* Preset a value for 'tick'? */
+#define PRESET_TICK 1000000L/hz
+
+/* Preset a value for 'tickadj'? */
+#define PRESET_TICKADJ 500/hz
+
+/* Should we not IGNPAR (Linux)? */
+/* #undef RAWDCF_NO_IGNPAR */
+
+/* enable thread safety */
+#define REENTRANT 1
+
+/* Basic refclock support? */
+#define REFCLOCK 1
+
+/* Do we want the ReliantUNIX clock hacks? */
+/* #undef RELIANTUNIX_CLOCK */
+
+/* define if sched_yield yields the entire process */
+/* #undef REPLACE_BROKEN_YIELD */
+
+/* Define as the return type of signal handlers (`int' or `void'). */
+#define RETSIGTYPE void
+
+/* saveconfig mechanism */
+#define SAVECONFIG 1
+
+/* Do we want the SCO clock hacks? */
+/* #undef SCO5_CLOCK */
+
+/* The size of `char*', as computed by sizeof. */
+#ifdef __LP64__
+#define SIZEOF_CHARP 8
+#else
+#define SIZEOF_CHARP 4
+#endif
+
+/* The size of `int', as computed by sizeof. */
+#define SIZEOF_INT 4
+
+/* The size of `long', as computed by sizeof. */
+#ifdef __LP64__
+#define SIZEOF_LONG 8
+#else
+#define SIZEOF_LONG 4
+#endif
+
+/* The size of `long long', as computed by sizeof. */
+#define SIZEOF_LONG_LONG 8
+
+/* The size of `pthread_t', as computed by sizeof. */
+#define SIZEOF_PTHREAD_T 8
+
+/* The size of `short', as computed by sizeof. */
+#define SIZEOF_SHORT 2
+
+/* The size of `signed char', as computed by sizeof. */
+#define SIZEOF_SIGNED_CHAR 1
+
+/* The size of `time_t', as computed by sizeof. */
+#if defined(__i386__) || defined(__powerpc__)
+#define SIZEOF_TIME_T 4
+#else
+#define SIZEOF_TIME_T 8
+#endif
+
+/* Does SIOCGIFCONF return size in the buffer? */
+/* #undef SIZE_RETURNED_IN_BUFFER */
+
+/* Slew always? */
+/* #undef SLEWALWAYS */
+
+/* If using the C implementation of alloca, define if you know the
+ direction of stack growth for your system; otherwise it will be
+ automatically deduced at runtime.
+ STACK_DIRECTION > 0 => grows toward higher addresses
+ STACK_DIRECTION < 0 => grows toward lower addresses
+ STACK_DIRECTION = 0 => direction of growth unknown */
+/* #undef STACK_DIRECTION */
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Step, then slew the clock? */
+/* #undef STEP_SLEW */
+
+/* Define to 1 if strerror_r returns char *. */
+/* #undef STRERROR_R_CHAR_P */
+
+/* canonical system (cpu-vendor-os) of where we should run */
+#if defined(__alpha__)
+#define STR_SYSTEM "alpha-undermydesk-freebsd"
+#elif defined(__sparc64__)
+#define STR_SYSTEM "sparc64-undermydesk-freebsd"
+#elif defined(__amd64__)
+#define STR_SYSTEM "amd64-undermydesk-freebsd"
+#elif defined(__powerpc64__)
+#define STR_SYSTEM "powerpc64-undermydesk-freebsd"
+#elif defined(__powerpc__)
+#define STR_SYSTEM "powerpc-undermydesk-freebsd"
+#elif defined(__mips64)
+#define STR_SYSTEM "mips64-undermydesk-freebsd"
+#elif defined(__mips__)
+#define STR_SYSTEM "mips-undermydesk-freebsd"
+#elif defined(__aarch64__)
+#define STR_SYSTEM "arm64-undermydesk-freebsd"
+#elif defined(__arm__)
+#define STR_SYSTEM "arm-undermydesk-freebsd"
+#elif defined(__sparc64__)
+#define STR_SYSTEM "sparc64-undermydesk-freebsd"
+#elif defined(__sparc__)
+#define STR_SYSTEM "sparc-undermydesk-freebsd"
+#elif defined(__ia64__)
+#define STR_SYSTEM "ia64-undermydesk-freebsd"
+#else
+#define STR_SYSTEM "i386-undermydesk-freebsd"
+#endif
+
+/* Does Xettimeofday take 1 arg? */
+/* #undef SYSV_TIMEOFDAY */
+
+/* Do we need to #define _SVID3 when we #include <termios.h>? */
+/* #undef TERMIOS_NEEDS__SVID3 */
+
+/* enable thread safety */
+#define THREADSAFE 1
+
+/* enable thread safety */
+#define THREAD_SAFE 1
+
+/* Is K_TICKADJ_NAME in nanoseconds? */
+/* #undef TICKADJ_NANO */
+
+/* Is K_TICK_NAME in nanoseconds? */
+/* #undef TICK_NANO */
+
+/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
+#define TIME_WITH_SYS_TIME 1
+
+/* Define to 1 if your <sys/time.h> declares `struct tm'. */
+/* #undef TM_IN_SYS_TIME */
+
+/* Provide a typedef for uintptr_t? */
+#ifndef HAVE_UINTPTR_T
+typedef unsigned int uintptr_t;
+#define HAVE_UINTPTR_T 1
+#endif
+
+/* What type to use for setsockopt */
+#define TYPEOF_IP_MULTICAST_LOOP u_char
+
+/* Do we set process groups with -pid? */
+/* #undef UDP_BACKWARDS_SETOWN */
+
+/* Must we have a CTTY for fsetown? */
+#define USE_FSETOWNCTTY 1
+
+/* Use OpenSSL's crypto random functions */
+/* #define USE_OPENSSL_CRYPTO_RAND 1 */
+
+/* OK to use snprintb()? */
+/* #undef USE_SNPRINTB */
+
+/* Can we use SIGPOLL for tty IO? */
+/* #undef USE_TTY_SIGPOLL */
+
+/* Can we use SIGPOLL for UDP? */
+/* #undef USE_UDP_SIGPOLL */
+
+/* Version number of package */
+#define VERSION "4.2.8p5"
+
+/* vsnprintf expands "%m" to strerror(errno) */
+/* #undef VSNPRINTF_PERCENT_M */
+
+/* configure --enable-ipv6 */
+#define WANT_IPV6 1
+
+/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
+ significant byte first (like Motorola and SPARC, unlike Intel). */
+#if defined(__ARMEB__) || defined(__MIPSEB__) || defined(__powerpc__) || \
+ defined(__powerpc64__) || defined(__sparc64__)
+#define WORDS_BIGENDIAN 1
+#endif
+
+/* routine worker child proc uses to exit. */
+#define WORKER_CHILD_EXIT exit
+
+/* Define to 1 if on MINIX. */
+/* #undef _MINIX */
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+ this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* enable thread safety */
+#define _REENTRANT 1
+
+/* enable thread safety */
+#define _SGI_MP_SOURCE 1
+
+/* enable thread safety */
+#define _THREADSAFE 1
+
+/* enable thread safety */
+#define _THREAD_SAFE 1
+
+/* Define to 500 only on HP-UX. */
+/* #undef _XOPEN_SOURCE */
+
+/* Are we _special_? */
+/* #undef __APPLE_USE_RFC_3542 */
+
+/* Define to 1 if type `char' is unsigned and you are not using gcc. */
+#ifndef __CHAR_UNSIGNED__
+/* # undef __CHAR_UNSIGNED__ */
+#endif
+
+/* Enable extensions on AIX 3, Interix. */
+#ifndef _ALL_SOURCE
+# define _ALL_SOURCE 1
+#endif
+/* Enable GNU extensions on systems that have them. */
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE 1
+#endif
+/* Enable threading extensions on Solaris. */
+#ifndef _POSIX_PTHREAD_SEMANTICS
+# define _POSIX_PTHREAD_SEMANTICS 1
+#endif
+/* Enable extensions on HP NonStop. */
+#ifndef _TANDEM_SOURCE
+# define _TANDEM_SOURCE 1
+#endif
+/* Enable general extensions on Solaris. */
+#ifndef __EXTENSIONS__
+# define __EXTENSIONS__ 1
+#endif
+
+
+/* deviant */
+/* #undef adjtimex */
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef gid_t */
+
+/* Define to `__inline__' or `__inline' if that's what the C compiler
+ calls it, or to nothing if 'inline' is not supported under any name. */
+#ifndef __cplusplus
+/* #undef inline */
+#endif
+
+/* Define to the widest signed integer type if <stdint.h> and <inttypes.h> do
+ not define. */
+/* #undef intmax_t */
+
+/* deviant */
+/* #undef ntp_adjtime */
+
+/* deviant */
+/* #undef ntp_gettime */
+
+/* Define to `long int' if <sys/types.h> does not define. */
+/* #undef off_t */
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef pid_t */
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+/* #undef size_t */
+
+
+ #if !defined(_KERNEL) && !defined(PARSESTREAM)
+ /*
+ * stdio.h must be included after _GNU_SOURCE is defined
+ * but before #define snprintf rpl_snprintf
+ */
+ # include <stdio.h>
+ #endif
+
+
+/* Define to rpl_snprintf if the replacement function should be used. */
+/* #undef snprintf */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef uid_t */
+
+/* Define to the widest unsigned integer type if <stdint.h> and <inttypes.h>
+ do not define. */
+/* #undef uintmax_t */
+
+/* Define to the type of an unsigned integer type wide enough to hold a
+ pointer, if such a type exists, and if the system does not define it. */
+/* #undef uintptr_t */
+
+/* Define as `fork' if `vfork' does not work. */
+/* #undef vfork */
+
+/* Define to empty if the keyword `volatile' does not work. Warning: valid
+ code using `volatile' can become incorrect without. Disable with care. */
+/* #undef volatile */
+
+/* Define to rpl_vsnprintf if the replacement function should be used. */
+/* #undef vsnprintf */
+
+
+#ifndef MPINFOU_PREDECLARED
+# define MPINFOU_PREDECLARED
+typedef union mpinfou {
+ struct pdk_mpinfo *pdkptr;
+ struct mpinfo *pikptr;
+} mpinfou_t;
+#endif
+
+
+
+ #if !defined(_KERNEL) && !defined(PARSESTREAM)
+ # if defined(HW_WANT_RPL_VSNPRINTF)
+ # if defined(__cplusplus)
+ extern "C" {
+ # endif
+ # include <stdarg.h>
+ int rpl_vsnprintf(char *, size_t, const char *, va_list);
+ # if defined(__cplusplus)
+ }
+ # endif
+ # endif
+ # if defined(HW_WANT_RPL_SNPRINTF)
+ # if defined(__cplusplus)
+ extern "C" {
+ # endif
+ int rpl_snprintf(char *, size_t, const char *, ...);
+ # if defined(__cplusplus)
+ }
+ # endif
+ # endif
+ #endif /* !defined(_KERNEL) && !defined(PARSESTREAM) */
+
+/*
+ * FreeBSD specific: Explicitly specify date/time for reproducible build.
+ */
+#define MKREPRO_DATE "Jan 8 2016"
+#define MKREPRO_TIME "12:37:48"
diff --git a/usr.sbin/ntp/doc/Makefile b/usr.sbin/ntp/doc/Makefile
new file mode 100644
index 0000000..283d6ab
--- /dev/null
+++ b/usr.sbin/ntp/doc/Makefile
@@ -0,0 +1,34 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+SUBDIR= drivers hints icons pic scripts
+
+FILESDIR= ${SHAREDIR}/doc/ntp
+
+.if ${MK_HTML} != "no"
+FILES= access.html accopt.html assoc.html audio.html authentic.html \
+ authopt.html autokey.html bugs.html build.html clock.html \
+ clockopt.html cluster.html comdex.html config.html confopt.html \
+ copyright.html debug.html decode.html discipline.html discover.html \
+ extern.html filter.html hints.html history.html howto.html \
+ huffpuff.html index.html kern.html kernpps.html keygen.html leap.html \
+ miscopt.html monopt.html msyslog.html ntp-keygen.html ntp-wait.html \
+ ntp.conf.html ntp.keys.html ntp_conf.html ntpd.html ntpdate.html \
+ ntpdc.html ntpdsim.html ntpdsim_new.html ntpq.html ntpsnmpd.html \
+ ntptime.html ntptrace.html orphan.html parsedata.html \
+ parsenew.html poll.html pps.html prefer.html quick.html rate.html \
+ rdebug.html refclock.html release.html select.html sitemap.html \
+ sntp.html stats.html tickadj.html warp.html xleave.html
+.endif
+
+MAN= ntp.conf.5 ntp.keys.5
+MAN+= ntp-keygen.8 ntpd.8 ntpdate.8 ntpdc.8 ntpq.8 ntptime.8 sntp.8
+
+.PATH: ${.CURDIR}/../../../contrib/ntp/html \
+ ${.CURDIR}/../../../contrib/ntp/util \
+ ${.CURDIR}/../../../contrib/ntp/util \
+ ${.CURDIR}/../../../contrib/ntp/ntpd \
+ ${.CURDIR}/../../../contrib/ntp/ntpsnmpd
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ntp/doc/Makefile.depend b/usr.sbin/ntp/doc/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/ntp/doc/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ntp/doc/drivers/Makefile b/usr.sbin/ntp/doc/drivers/Makefile
new file mode 100644
index 0000000..7ae3cd2
--- /dev/null
+++ b/usr.sbin/ntp/doc/drivers/Makefile
@@ -0,0 +1,21 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+SUBDIR= icons scripts
+FILESDIR= ${SHAREDIR}/doc/ntp/drivers
+
+.if ${MK_HTML} != "no"
+FILES= driver1.html driver10.html driver11.html driver12.html driver16.html \
+ driver18.html driver19.html driver20.html driver22.html driver26.html \
+ driver27.html driver28.html driver29.html driver3.html driver30.html \
+ driver31.html driver32.html driver33.html driver34.html driver35.html \
+ driver36.html driver37.html driver38.html driver39.html driver4.html \
+ driver40.html driver42.html driver43.html driver44.html driver45.html \
+ driver46.html driver5.html driver6.html driver7.html driver8.html \
+ driver9.html mx4200data.html oncore-shmem.html tf582_4.html
+.endif
+
+.PATH: ${.CURDIR}/../../../../contrib/ntp/html/drivers
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ntp/doc/drivers/Makefile.depend b/usr.sbin/ntp/doc/drivers/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/ntp/doc/drivers/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ntp/doc/drivers/icons/Makefile b/usr.sbin/ntp/doc/drivers/icons/Makefile
new file mode 100644
index 0000000..e76e1fa
--- /dev/null
+++ b/usr.sbin/ntp/doc/drivers/icons/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+FILESDIR= ${SHAREDIR}/doc/ntp/drivers/icons
+
+.if ${MK_HTML} != "no"
+FILES= home.gif mail2.gif
+.endif
+
+.PATH: ${.CURDIR}/../../../../../contrib/ntp/html/drivers/icons
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ntp/doc/drivers/icons/Makefile.depend b/usr.sbin/ntp/doc/drivers/icons/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/ntp/doc/drivers/icons/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ntp/doc/drivers/scripts/Makefile b/usr.sbin/ntp/doc/drivers/scripts/Makefile
new file mode 100644
index 0000000..44ff1a6
--- /dev/null
+++ b/usr.sbin/ntp/doc/drivers/scripts/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+FILESDIR= ${SHAREDIR}/doc/ntp/drivers/scripts
+
+.if ${MK_HTML} != "no"
+FILES= footer.txt style.css
+.endif
+
+.PATH: ${.CURDIR}/../../../../../contrib/ntp/html/drivers/scripts
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ntp/doc/drivers/scripts/Makefile.depend b/usr.sbin/ntp/doc/drivers/scripts/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/ntp/doc/drivers/scripts/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ntp/doc/hints/Makefile b/usr.sbin/ntp/doc/hints/Makefile
new file mode 100644
index 0000000..fdbcea0
--- /dev/null
+++ b/usr.sbin/ntp/doc/hints/Makefile
@@ -0,0 +1,17 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+FILESDIR= ${SHAREDIR}/doc/ntp/hints
+
+.if ${MK_HTML} != "no"
+FILES= a-ux aix bsdi changes decosf1 decosf2 freebsd hpux linux mpeix \
+ notes-xntp-v3 parse refclocks rs6000 sco.html sgi \
+ solaris-dosynctodr.html solaris.html solaris.xtra.4023118 \
+ solaris.xtra.4095849 solaris.xtra.S99ntpd solaris.xtra.patchfreq \
+ sun4 svr4-dell svr4_package todo vxworks.html winnt.html
+.endif
+
+.PATH: ${.CURDIR}/../../../../contrib/ntp/html/hints
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ntp/doc/hints/Makefile.depend b/usr.sbin/ntp/doc/hints/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/ntp/doc/hints/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ntp/doc/icons/Makefile b/usr.sbin/ntp/doc/icons/Makefile
new file mode 100644
index 0000000..410a380
--- /dev/null
+++ b/usr.sbin/ntp/doc/icons/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+FILESDIR= ${SHAREDIR}/doc/ntp/icons
+
+.if ${MK_HTML} != "no"
+FILES= home.gif mail2.gif sitemap.png
+.endif
+
+.PATH: ${.CURDIR}/../../../../contrib/ntp/html/icons
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ntp/doc/icons/Makefile.depend b/usr.sbin/ntp/doc/icons/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/ntp/doc/icons/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ntp/doc/ntp-keygen.8 b/usr.sbin/ntp/doc/ntp-keygen.8
new file mode 100644
index 0000000..a0c0954
--- /dev/null
+++ b/usr.sbin/ntp/doc/ntp-keygen.8
@@ -0,0 +1,1073 @@
+.Dd January 7 2016
+.Dt NTP_KEYGEN 8 User Commands
+.Os
+.\" EDIT THIS FILE WITH CAUTION (ntp-keygen-opts.mdoc)
+.\"
+.\" $FreeBSD$
+.\"
+.\" It has been AutoGen-ed January 7, 2016 at 11:32:43 PM by AutoGen 5.18.5
+.\" From the definitions ntp-keygen-opts.def
+.\" and the template file agmdoc-cmd.tpl
+.Sh NAME
+.Nm ntp-keygen
+.Nd Create a NTP host key
+.Sh SYNOPSIS
+.Nm
+.\" Mixture of short (flag) options and long options
+.Op Fl flags
+.Op Fl flag Op Ar value
+.Op Fl \-option\-name Ns Oo Oo Ns "=| " Oc Ns Ar value Oc
+.Pp
+All arguments must be options.
+.Pp
+.Sh DESCRIPTION
+This program generates cryptographic data files used by the NTPv4
+authentication and identification schemes.
+It generates MD5 key files used in symmetric key cryptography.
+In addition, if the OpenSSL software library has been installed,
+it generates keys, certificate and identity files used in public key
+cryptography.
+These files are used for cookie encryption,
+digital signature and challenge/response identification algorithms
+compatible with the Internet standard security infrastructure.
+.Pp
+All files are in PEM\-encoded printable ASCII format,
+so they can be embedded as MIME attachments in mail to other sites
+and certificate authorities.
+By default, files are not encrypted.
+.Pp
+When used to generate message digest keys, the program produces a file
+containing ten pseudo\-random printable ASCII strings suitable for the
+MD5 message digest algorithm included in the distribution.
+If the OpenSSL library is installed, it produces an additional ten
+hex\-encoded random bit strings suitable for the SHA1 and other message
+digest algorithms.
+The message digest keys file must be distributed and stored
+using secure means beyond the scope of NTP itself.
+Besides the keys used for ordinary NTP associations, additional keys
+can be defined as passwords for the
+.Xr ntpq 8
+and
+.Xr ntpdc 8
+utility programs.
+.Pp
+The remaining generated files are compatible with other OpenSSL
+applications and other Public Key Infrastructure (PKI) resources.
+Certificates generated by this program are compatible with extant
+industry practice, although some users might find the interpretation of
+X509v3 extension fields somewhat liberal.
+However, the identity keys are probably not compatible with anything
+other than Autokey.
+.Pp
+Some files used by this program are encrypted using a private password.
+The
+.Fl p
+option specifies the password for local encrypted files and the
+.Fl q
+option the password for encrypted files sent to remote sites.
+If no password is specified, the host name returned by the Unix
+.Fn gethostname
+function, normally the DNS name of the host is used.
+.Pp
+The
+.Ar pw
+option of the
+.Ar crypto
+configuration command specifies the read
+password for previously encrypted local files.
+This must match the local password used by this program.
+If not specified, the host name is used.
+Thus, if files are generated by this program without password,
+they can be read back by
+.Ar ntpd
+without password but only on the same host.
+.Pp
+Normally, encrypted files for each host are generated by that host and
+used only by that host, although exceptions exist as noted later on
+this page.
+The symmetric keys file, normally called
+.Ar ntp.keys ,
+is usually installed in
+.Pa /etc .
+Other files and links are usually installed in
+.Pa /usr/local/etc ,
+which is normally in a shared filesystem in
+NFS\-mounted networks and cannot be changed by shared clients.
+The location of the keys directory can be changed by the
+.Ar keysdir
+configuration command in such cases.
+Normally, this is in
+.Pa /etc .
+.Pp
+This program directs commentary and error messages to the standard
+error stream
+.Ar stderr
+and remote files to the standard output stream
+.Ar stdout
+where they can be piped to other applications or redirected to files.
+The names used for generated files and links all begin with the
+string
+.Ar ntpkey
+and include the file type, generating host and filestamp,
+as described in the
+.Dq Cryptographic Data Files
+section below.
+.Ss Running the Program
+To test and gain experience with Autokey concepts, log in as root and
+change to the keys directory, usually
+.Pa /usr/local/etc
+When run for the first time, or if all files with names beginning with
+.Ar ntpkey
+have been removed, use the
+.Nm
+command without arguments to generate a
+default RSA host key and matching RSA\-MD5 certificate with expiration
+date one year hence.
+If run again without options, the program uses the
+existing keys and parameters and generates only a new certificate with
+new expiration date one year hence.
+.Pp
+Run the command on as many hosts as necessary.
+Designate one of them as the trusted host (TH) using
+.Nm
+with the
+.Fl T
+option and configure it to synchronize from reliable Internet servers.
+Then configure the other hosts to synchronize to the TH directly or
+indirectly.
+A certificate trail is created when Autokey asks the immediately
+ascendant host towards the TH to sign its certificate, which is then
+provided to the immediately descendant host on request.
+All group hosts should have acyclic certificate trails ending on the TH.
+.Pp
+The host key is used to encrypt the cookie when required and so must be
+RSA type.
+By default, the host key is also the sign key used to encrypt
+signatures.
+A different sign key can be assigned using the
+.Fl S
+option and this can be either RSA or DSA type.
+By default, the signature
+message digest type is MD5, but any combination of sign key type and
+message digest type supported by the OpenSSL library can be specified
+using the
+.Fl c
+option.
+The rules say cryptographic media should be generated with proventic
+filestamps, which means the host should already be synchronized before
+this program is run.
+This of course creates a chicken\-and\-egg problem
+when the host is started for the first time.
+Accordingly, the host time
+should be set by some other means, such as eyeball\-and\-wristwatch, at
+least so that the certificate lifetime is within the current year.
+After that and when the host is synchronized to a proventic source, the
+certificate should be re\-generated.
+.Pp
+Additional information on trusted groups and identity schemes is on the
+.Dq Autokey Public\-Key Authentication
+page.
+.Pp
+The
+.Xr ntpd 8
+configuration command
+.Ic crypto pw Ar password
+specifies the read password for previously encrypted files.
+The daemon expires on the spot if the password is missing
+or incorrect.
+For convenience, if a file has been previously encrypted,
+the default read password is the name of the host running
+the program.
+If the previous write password is specified as the host name,
+these files can be read by that host with no explicit password.
+.Pp
+File names begin with the prefix
+.Cm ntpkey_
+and end with the postfix
+.Ar _hostname.filestamp ,
+where
+.Ar hostname
+is the owner name, usually the string returned
+by the Unix gethostname() routine, and
+.Ar filestamp
+is the NTP seconds when the file was generated, in decimal digits.
+This both guarantees uniqueness and simplifies maintenance
+procedures, since all files can be quickly removed
+by a
+.Ic rm ntpkey\&*
+command or all files generated
+at a specific time can be removed by a
+.Ic rm
+.Ar \&*filestamp
+command.
+To further reduce the risk of misconfiguration,
+the first two lines of a file contain the file name
+and generation date and time as comments.
+.Pp
+All files are installed by default in the keys directory
+.Pa /usr/local/etc ,
+which is normally in a shared filesystem
+in NFS\-mounted networks.
+The actual location of the keys directory
+and each file can be overridden by configuration commands,
+but this is not recommended.
+Normally, the files for each host are generated by that host
+and used only by that host, although exceptions exist
+as noted later on this page.
+.Pp
+Normally, files containing private values,
+including the host key, sign key and identification parameters,
+are permitted root read/write\-only;
+while others containing public values are permitted world readable.
+Alternatively, files containing private values can be encrypted
+and these files permitted world readable,
+which simplifies maintenance in shared file systems.
+Since uniqueness is insured by the hostname and
+file name extensions, the files for a NFS server and
+dependent clients can all be installed in the same shared directory.
+.Pp
+The recommended practice is to keep the file name extensions
+when installing a file and to install a soft link
+from the generic names specified elsewhere on this page
+to the generated files.
+This allows new file generations to be activated simply
+by changing the link.
+If a link is present, ntpd follows it to the file name
+to extract the filestamp.
+If a link is not present,
+.Xr ntpd 8
+extracts the filestamp from the file itself.
+This allows clients to verify that the file and generation times
+are always current.
+The
+.Nm
+program uses the same timestamp extension for all files generated
+at one time, so each generation is distinct and can be readily
+recognized in monitoring data.
+.Ss Running the program
+The safest way to run the
+.Nm
+program is logged in directly as root.
+The recommended procedure is change to the keys directory,
+usually
+.Pa /usr/local/etc ,
+then run the program.
+When run for the first time,
+or if all
+.Cm ntpkey
+files have been removed,
+the program generates a RSA host key file and matching RSA\-MD5 certificate file,
+which is all that is necessary in many cases.
+The program also generates soft links from the generic names
+to the respective files.
+If run again, the program uses the same host key file,
+but generates a new certificate file and link.
+.Pp
+The host key is used to encrypt the cookie when required and so must be RSA type.
+By default, the host key is also the sign key used to encrypt signatures.
+When necessary, a different sign key can be specified and this can be
+either RSA or DSA type.
+By default, the message digest type is MD5, but any combination
+of sign key type and message digest type supported by the OpenSSL library
+can be specified, including those using the MD2, MD5, SHA, SHA1, MDC2
+and RIPE160 message digest algorithms.
+However, the scheme specified in the certificate must be compatible
+with the sign key.
+Certificates using any digest algorithm are compatible with RSA sign keys;
+however, only SHA and SHA1 certificates are compatible with DSA sign keys.
+.Pp
+Private/public key files and certificates are compatible with
+other OpenSSL applications and very likely other libraries as well.
+Certificates or certificate requests derived from them should be compatible
+with extant industry practice, although some users might find
+the interpretation of X509v3 extension fields somewhat liberal.
+However, the identification parameter files, although encoded
+as the other files, are probably not compatible with anything other than Autokey.
+.Pp
+Running the program as other than root and using the Unix
+.Ic su
+command
+to assume root may not work properly, since by default the OpenSSL library
+looks for the random seed file
+.Cm .rnd
+in the user home directory.
+However, there should be only one
+.Cm .rnd ,
+most conveniently
+in the root directory, so it is convenient to define the
+.Cm $RANDFILE
+environment variable used by the OpenSSL library as the path to
+.Cm /.rnd .
+.Pp
+Installing the keys as root might not work in NFS\-mounted
+shared file systems, as NFS clients may not be able to write
+to the shared keys directory, even as root.
+In this case, NFS clients can specify the files in another
+directory such as
+.Pa /etc
+using the
+.Ic keysdir
+command.
+There is no need for one client to read the keys and certificates
+of other clients or servers, as these data are obtained automatically
+by the Autokey protocol.
+.Pp
+Ordinarily, cryptographic files are generated by the host that uses them,
+but it is possible for a trusted agent (TA) to generate these files
+for other hosts; however, in such cases files should always be encrypted.
+The subject name and trusted name default to the hostname
+of the host generating the files, but can be changed by command line options.
+It is convenient to designate the owner name and trusted name
+as the subject and issuer fields, respectively, of the certificate.
+The owner name is also used for the host and sign key files,
+while the trusted name is used for the identity files.
+.Pp
+All files are installed by default in the keys directory
+.Pa /usr/local/etc ,
+which is normally in a shared filesystem
+in NFS\-mounted networks.
+The actual location of the keys directory
+and each file can be overridden by configuration commands,
+but this is not recommended.
+Normally, the files for each host are generated by that host
+and used only by that host, although exceptions exist
+as noted later on this page.
+.Pp
+Normally, files containing private values,
+including the host key, sign key and identification parameters,
+are permitted root read/write\-only;
+while others containing public values are permitted world readable.
+Alternatively, files containing private values can be encrypted
+and these files permitted world readable,
+which simplifies maintenance in shared file systems.
+Since uniqueness is insured by the hostname and
+file name extensions, the files for a NFS server and
+dependent clients can all be installed in the same shared directory.
+.Pp
+The recommended practice is to keep the file name extensions
+when installing a file and to install a soft link
+from the generic names specified elsewhere on this page
+to the generated files.
+This allows new file generations to be activated simply
+by changing the link.
+If a link is present, ntpd follows it to the file name
+to extract the filestamp.
+If a link is not present,
+.Xr ntpd 8
+extracts the filestamp from the file itself.
+This allows clients to verify that the file and generation times
+are always current.
+The
+.Nm
+program uses the same timestamp extension for all files generated
+at one time, so each generation is distinct and can be readily
+recognized in monitoring data.
+.Ss Running the program
+The safest way to run the
+.Nm
+program is logged in directly as root.
+The recommended procedure is change to the keys directory,
+usually
+.Pa /usr/local/etc ,
+then run the program.
+When run for the first time,
+or if all
+.Cm ntpkey
+files have been removed,
+the program generates a RSA host key file and matching RSA\-MD5 certificate file,
+which is all that is necessary in many cases.
+The program also generates soft links from the generic names
+to the respective files.
+If run again, the program uses the same host key file,
+but generates a new certificate file and link.
+.Pp
+The host key is used to encrypt the cookie when required and so must be RSA type.
+By default, the host key is also the sign key used to encrypt signatures.
+When necessary, a different sign key can be specified and this can be
+either RSA or DSA type.
+By default, the message digest type is MD5, but any combination
+of sign key type and message digest type supported by the OpenSSL library
+can be specified, including those using the MD2, MD5, SHA, SHA1, MDC2
+and RIPE160 message digest algorithms.
+However, the scheme specified in the certificate must be compatible
+with the sign key.
+Certificates using any digest algorithm are compatible with RSA sign keys;
+however, only SHA and SHA1 certificates are compatible with DSA sign keys.
+.Pp
+Private/public key files and certificates are compatible with
+other OpenSSL applications and very likely other libraries as well.
+Certificates or certificate requests derived from them should be compatible
+with extant industry practice, although some users might find
+the interpretation of X509v3 extension fields somewhat liberal.
+However, the identification parameter files, although encoded
+as the other files, are probably not compatible with anything other than Autokey.
+.Pp
+Running the program as other than root and using the Unix
+.Ic su
+command
+to assume root may not work properly, since by default the OpenSSL library
+looks for the random seed file
+.Cm .rnd
+in the user home directory.
+However, there should be only one
+.Cm .rnd ,
+most conveniently
+in the root directory, so it is convenient to define the
+.Cm $RANDFILE
+environment variable used by the OpenSSL library as the path to
+.Cm /.rnd .
+.Pp
+Installing the keys as root might not work in NFS\-mounted
+shared file systems, as NFS clients may not be able to write
+to the shared keys directory, even as root.
+In this case, NFS clients can specify the files in another
+directory such as
+.Pa /etc
+using the
+.Ic keysdir
+command.
+There is no need for one client to read the keys and certificates
+of other clients or servers, as these data are obtained automatically
+by the Autokey protocol.
+.Pp
+Ordinarily, cryptographic files are generated by the host that uses them,
+but it is possible for a trusted agent (TA) to generate these files
+for other hosts; however, in such cases files should always be encrypted.
+The subject name and trusted name default to the hostname
+of the host generating the files, but can be changed by command line options.
+It is convenient to designate the owner name and trusted name
+as the subject and issuer fields, respectively, of the certificate.
+The owner name is also used for the host and sign key files,
+while the trusted name is used for the identity files.
+seconds.
+seconds.
+s Trusted Hosts and Groups
+Each cryptographic configuration involves selection of a signature scheme
+and identification scheme, called a cryptotype,
+as explained in the
+.Sx Authentication Options
+section of
+.Xr ntp.conf 5 .
+The default cryptotype uses RSA encryption, MD5 message digest
+and TC identification.
+First, configure a NTP subnet including one or more low\-stratum
+trusted hosts from which all other hosts derive synchronization
+directly or indirectly.
+Trusted hosts have trusted certificates;
+all other hosts have nontrusted certificates.
+These hosts will automatically and dynamically build authoritative
+certificate trails to one or more trusted hosts.
+A trusted group is the set of all hosts that have, directly or indirectly,
+a certificate trail ending at a trusted host.
+The trail is defined by static configuration file entries
+or dynamic means described on the
+.Sx Automatic NTP Configuration Options
+section of
+.Xr ntp.conf 5 .
+.Pp
+On each trusted host as root, change to the keys directory.
+To insure a fresh fileset, remove all
+.Cm ntpkey
+files.
+Then run
+.Nm
+.Fl T
+to generate keys and a trusted certificate.
+On all other hosts do the same, but leave off the
+.Fl T
+flag to generate keys and nontrusted certificates.
+When complete, start the NTP daemons beginning at the lowest stratum
+and working up the tree.
+It may take some time for Autokey to instantiate the certificate trails
+throughout the subnet, but setting up the environment is completely automatic.
+.Pp
+If it is necessary to use a different sign key or different digest/signature
+scheme than the default, run
+.Nm
+with the
+.Fl S Ar type
+option, where
+.Ar type
+is either
+.Cm RSA
+or
+.Cm DSA .
+The most often need to do this is when a DSA\-signed certificate is used.
+If it is necessary to use a different certificate scheme than the default,
+run
+.Nm
+with the
+.Fl c Ar scheme
+option and selected
+.Ar scheme
+as needed.
+f
+.Nm
+is run again without these options, it generates a new certificate
+using the same scheme and sign key.
+.Pp
+After setting up the environment it is advisable to update certificates
+from time to time, if only to extend the validity interval.
+Simply run
+.Nm
+with the same flags as before to generate new certificates
+using existing keys.
+However, if the host or sign key is changed,
+.Xr ntpd 8
+should be restarted.
+When
+.Xr ntpd 8
+is restarted, it loads any new files and restarts the protocol.
+Other dependent hosts will continue as usual until signatures are refreshed,
+at which time the protocol is restarted.
+.Ss Identity Schemes
+As mentioned on the Autonomous Authentication page,
+the default TC identity scheme is vulnerable to a middleman attack.
+However, there are more secure identity schemes available,
+including PC, IFF, GQ and MV described on the
+.Qq Identification Schemes
+page
+(maybe available at
+.Li http://www.eecis.udel.edu/%7emills/keygen.html ) .
+These schemes are based on a TA, one or more trusted hosts
+and some number of nontrusted hosts.
+Trusted hosts prove identity using values provided by the TA,
+while the remaining hosts prove identity using values provided
+by a trusted host and certificate trails that end on that host.
+The name of a trusted host is also the name of its sugroup
+and also the subject and issuer name on its trusted certificate.
+The TA is not necessarily a trusted host in this sense, but often is.
+.Pp
+In some schemes there are separate keys for servers and clients.
+A server can also be a client of another server,
+but a client can never be a server for another client.
+In general, trusted hosts and nontrusted hosts that operate
+as both server and client have parameter files that contain
+both server and client keys.
+Hosts that operate
+only as clients have key files that contain only client keys.
+.Pp
+The PC scheme supports only one trusted host in the group.
+On trusted host alice run
+.Nm
+.Fl P
+.Fl p Ar password
+to generate the host key file
+.Pa ntpkey_RSAkey_ Ns Ar alice.filestamp
+and trusted private certificate file
+.Pa ntpkey_RSA\-MD5_cert_ Ns Ar alice.filestamp .
+Copy both files to all group hosts;
+they replace the files which would be generated in other schemes.
+On each host bob install a soft link from the generic name
+.Pa ntpkey_host_ Ns Ar bob
+to the host key file and soft link
+.Pa ntpkey_cert_ Ns Ar bob
+to the private certificate file.
+Note the generic links are on bob, but point to files generated
+by trusted host alice.
+In this scheme it is not possible to refresh
+either the keys or certificates without copying them
+to all other hosts in the group.
+.Pp
+For the IFF scheme proceed as in the TC scheme to generate keys
+and certificates for all group hosts, then for every trusted host in the group,
+generate the IFF parameter file.
+On trusted host alice run
+.Nm
+.Fl T
+.Fl I
+.Fl p Ar password
+to produce her parameter file
+.Pa ntpkey_IFFpar_ Ns Ar alice.filestamp ,
+which includes both server and client keys.
+Copy this file to all group hosts that operate as both servers
+and clients and install a soft link from the generic
+.Pa ntpkey_iff_ Ns Ar alice
+to this file.
+If there are no hosts restricted to operate only as clients,
+there is nothing further to do.
+As the IFF scheme is independent
+of keys and certificates, these files can be refreshed as needed.
+.Pp
+If a rogue client has the parameter file, it could masquerade
+as a legitimate server and present a middleman threat.
+To eliminate this threat, the client keys can be extracted
+from the parameter file and distributed to all restricted clients.
+After generating the parameter file, on alice run
+.Nm
+.Fl e
+and pipe the output to a file or mail program.
+Copy or mail this file to all restricted clients.
+On these clients install a soft link from the generic
+.Pa ntpkey_iff_ Ns Ar alice
+to this file.
+To further protect the integrity of the keys,
+each file can be encrypted with a secret password.
+.Pp
+For the GQ scheme proceed as in the TC scheme to generate keys
+and certificates for all group hosts, then for every trusted host
+in the group, generate the IFF parameter file.
+On trusted host alice run
+.Nm
+.Fl T
+.Fl G
+.Fl p Ar password
+to produce her parameter file
+.Pa ntpkey_GQpar_ Ns Ar alice.filestamp ,
+which includes both server and client keys.
+Copy this file to all group hosts and install a soft link
+from the generic
+.Pa ntpkey_gq_ Ns Ar alice
+to this file.
+In addition, on each host bob install a soft link
+from generic
+.Pa ntpkey_gq_ Ns Ar bob
+to this file.
+As the GQ scheme updates the GQ parameters file and certificate
+at the same time, keys and certificates can be regenerated as needed.
+.Pp
+For the MV scheme, proceed as in the TC scheme to generate keys
+and certificates for all group hosts.
+For illustration assume trish is the TA, alice one of several trusted hosts
+and bob one of her clients.
+On TA trish run
+.Nm
+.Fl V Ar n
+.Fl p Ar password ,
+where
+.Ar n
+is the number of revokable keys (typically 5) to produce
+the parameter file
+.Pa ntpkeys_MVpar_ Ns Ar trish.filestamp
+and client key files
+.Pa ntpkeys_MVkeyd_ Ns Ar trish.filestamp
+where
+.Ar d
+is the key number (0 \&<
+.Ar d
+\&<
+.Ar n ) .
+Copy the parameter file to alice and install a soft link
+from the generic
+.Pa ntpkey_mv_ Ns Ar alice
+to this file.
+Copy one of the client key files to alice for later distribution
+to her clients.
+It doesn't matter which client key file goes to alice,
+since they all work the same way.
+Alice copies the client key file to all of her cliens.
+On client bob install a soft link from generic
+.Pa ntpkey_mvkey_ Ns Ar bob
+to the client key file.
+As the MV scheme is independent of keys and certificates,
+these files can be refreshed as needed.
+.Ss Command Line Options
+.Bl -tag -width indent
+.It Fl c Ar scheme
+Select certificate message digest/signature encryption scheme.
+The
+.Ar scheme
+can be one of the following:
+. Cm RSA\-MD2 , RSA\-MD5 , RSA\-SHA , RSA\-SHA1 , RSA\-MDC2 , RSA\-RIPEMD160 , DSA\-SHA ,
+or
+.Cm DSA\-SHA1 .
+Note that RSA schemes must be used with a RSA sign key and DSA
+schemes must be used with a DSA sign key.
+The default without this option is
+.Cm RSA\-MD5 .
+.It Fl d
+Enable debugging.
+This option displays the cryptographic data produced in eye\-friendly billboards.
+.It Fl e
+Write the IFF client keys to the standard output.
+This is intended for automatic key distribution by mail.
+.It Fl G
+Generate parameters and keys for the GQ identification scheme,
+obsoleting any that may exist.
+.It Fl g
+Generate keys for the GQ identification scheme
+using the existing GQ parameters.
+If the GQ parameters do not yet exist, create them first.
+.It Fl H
+Generate new host keys, obsoleting any that may exist.
+.It Fl I
+Generate parameters for the IFF identification scheme,
+obsoleting any that may exist.
+.It Fl i Ar name
+Set the suject name to
+.Ar name .
+This is used as the subject field in certificates
+and in the file name for host and sign keys.
+.It Fl M
+Generate MD5 keys, obsoleting any that may exist.
+.It Fl P
+Generate a private certificate.
+By default, the program generates public certificates.
+.It Fl p Ar password
+Encrypt generated files containing private data with
+.Ar password
+and the DES\-CBC algorithm.
+.It Fl q
+Set the password for reading files to password.
+.It Fl S Oo Cm RSA | DSA Oc
+Generate a new sign key of the designated type,
+obsoleting any that may exist.
+By default, the program uses the host key as the sign key.
+.It Fl s Ar name
+Set the issuer name to
+.Ar name .
+This is used for the issuer field in certificates
+and in the file name for identity files.
+.It Fl T
+Generate a trusted certificate.
+By default, the program generates a non\-trusted certificate.
+.It Fl V Ar nkeys
+Generate parameters and keys for the Mu\-Varadharajan (MV) identification scheme.
+.El
+.Ss Random Seed File
+All cryptographically sound key generation schemes must have means
+to randomize the entropy seed used to initialize
+the internal pseudo\-random number generator used
+by the library routines.
+The OpenSSL library uses a designated random seed file for this purpose.
+The file must be available when starting the NTP daemon and
+.Nm
+program.
+If a site supports OpenSSL or its companion OpenSSH,
+it is very likely that means to do this are already available.
+.Pp
+It is important to understand that entropy must be evolved
+for each generation, for otherwise the random number sequence
+would be predictable.
+Various means dependent on external events, such as keystroke intervals,
+can be used to do this and some systems have built\-in entropy sources.
+Suitable means are described in the OpenSSL software documentation,
+but are outside the scope of this page.
+.Pp
+The entropy seed used by the OpenSSL library is contained in a file,
+usually called
+.Cm .rnd ,
+which must be available when starting the NTP daemon
+or the
+.Nm
+program.
+The NTP daemon will first look for the file
+using the path specified by the
+.Ic randfile
+subcommand of the
+.Ic crypto
+configuration command.
+If not specified in this way, or when starting the
+.Nm
+program,
+the OpenSSL library will look for the file using the path specified
+by the
+.Ev RANDFILE
+environment variable in the user home directory,
+whether root or some other user.
+If the
+.Ev RANDFILE
+environment variable is not present,
+the library will look for the
+.Cm .rnd
+file in the user home directory.
+If the file is not available or cannot be written,
+the daemon exits with a message to the system log and the program
+exits with a suitable error message.
+.Ss Cryptographic Data Files
+All other file formats begin with two lines.
+The first contains the file name, including the generated host name
+and filestamp.
+The second contains the datestamp in conventional Unix date format.
+Lines beginning with # are considered comments and ignored by the
+.Nm
+program and
+.Xr ntpd 8
+daemon.
+Cryptographic values are encoded first using ASN.1 rules,
+then encrypted if necessary, and finally written PEM\-encoded
+printable ASCII format preceded and followed by MIME content identifier lines.
+.Pp
+The format of the symmetric keys file is somewhat different
+than the other files in the interest of backward compatibility.
+Since DES\-CBC is deprecated in NTPv4, the only key format of interest
+is MD5 alphanumeric strings.
+Following hte heard the keys are
+entered one per line in the format
+.D1 Ar keyno type key
+where
+.Ar keyno
+is a positive integer in the range 1\-65,535,
+.Ar type
+is the string MD5 defining the key format and
+.Ar key
+is the key itself,
+which is a printable ASCII string 16 characters or less in length.
+Each character is chosen from the 93 printable characters
+in the range 0x21 through 0x7f excluding space and the
+.Ql #
+character.
+.Pp
+Note that the keys used by the
+.Xr ntpq 8
+and
+.Xr ntpdc 8
+programs
+are checked against passwords requested by the programs
+and entered by hand, so it is generally appropriate to specify these keys
+in human readable ASCII format.
+.Pp
+The
+.Nm
+program generates a MD5 symmetric keys file
+.Pa ntpkey_MD5key_ Ns Ar hostname.filestamp .
+Since the file contains private shared keys,
+it should be visible only to root and distributed by secure means
+to other subnet hosts.
+The NTP daemon loads the file
+.Pa ntp.keys ,
+so
+.Nm
+installs a soft link from this name to the generated file.
+Subsequently, similar soft links must be installed by manual
+or automated means on the other subnet hosts.
+While this file is not used with the Autokey Version 2 protocol,
+it is needed to authenticate some remote configuration commands
+used by the
+.Xr ntpq 8
+and
+.Xr ntpdc 8
+utilities.
+.Sh "OPTIONS"
+.Bl -tag
+.It Fl b Ar imbits , Fl \-imbits Ns = Ns Ar imbits
+identity modulus bits.
+This option takes an integer number as its argument.
+The value of
+.Ar imbits
+is constrained to being:
+.in +4
+.nf
+.na
+in the range 256 through 2048
+.fi
+.in -4
+.sp
+The number of bits in the identity modulus. The default is 256.
+.It Fl c Ar scheme , Fl \-certificate Ns = Ns Ar scheme
+certificate scheme.
+.sp
+scheme is one of
+RSA\-MD2, RSA\-MD5, RSA\-SHA, RSA\-SHA1, RSA\-MDC2, RSA\-RIPEMD160,
+DSA\-SHA, or DSA\-SHA1.
+.sp
+Select the certificate message digest/signature encryption scheme.
+Note that RSA schemes must be used with a RSA sign key and DSA
+schemes must be used with a DSA sign key. The default without
+this option is RSA\-MD5.
+.It Fl C Ar cipher , Fl \-cipher Ns = Ns Ar cipher
+privatekey cipher.
+.sp
+Select the cipher which is used to encrypt the files containing
+private keys. The default is three\-key triple DES in CBC mode,
+equivalent to "@code{\-C des\-ede3\-cbc". The openssl tool lists ciphers
+available in "\fBopenssl \-h\fP" output.
+.It Fl d , Fl \-debug\-level
+Increase debug verbosity level.
+This option may appear an unlimited number of times.
+.sp
+.It Fl D Ar number , Fl \-set\-debug\-level Ns = Ns Ar number
+Set the debug verbosity level.
+This option may appear an unlimited number of times.
+This option takes an integer number as its argument.
+.sp
+.It Fl e , Fl \-id\-key
+Write IFF or GQ identity keys.
+.sp
+Write the IFF or GQ client keys to the standard output. This is
+intended for automatic key distribution by mail.
+.It Fl G , Fl \-gq\-params
+Generate GQ parameters and keys.
+.sp
+Generate parameters and keys for the GQ identification scheme,
+obsoleting any that may exist.
+.It Fl H , Fl \-host\-key
+generate RSA host key.
+.sp
+Generate new host keys, obsoleting any that may exist.
+.It Fl I , Fl \-iffkey
+generate IFF parameters.
+.sp
+Generate parameters for the IFF identification scheme, obsoleting
+any that may exist.
+.It Fl i Ar group , Fl \-ident Ns = Ns Ar group
+set Autokey group name.
+.sp
+Set the optional Autokey group name to name. This is used in
+the file name of IFF, GQ, and MV client parameters files. In
+that role, the default is the host name if this option is not
+provided. The group name, if specified using \fB\-i/\-\-ident\fP or
+using \fB\-s/\-\-subject\-name\fP following an '\fB@\fP' character,
+is also a part of the self\-signed host certificate's subject and
+issuer names in the form \fBhost@group\fP and should match the
+\'\fBcrypto ident\fP' or '\fBserver ident\fP' configuration in
+\fBntpd\fP's configuration file.
+.It Fl l Ar lifetime , Fl \-lifetime Ns = Ns Ar lifetime
+set certificate lifetime.
+This option takes an integer number as its argument.
+.sp
+Set the certificate expiration to lifetime days from now.
+.It Fl M , Fl \-md5key
+generate MD5 keys.
+.sp
+Generate MD5 keys, obsoleting any that may exist.
+.It Fl m Ar modulus , Fl \-modulus Ns = Ns Ar modulus
+modulus.
+This option takes an integer number as its argument.
+The value of
+.Ar modulus
+is constrained to being:
+.in +4
+.nf
+.na
+in the range 256 through 2048
+.fi
+.in -4
+.sp
+The number of bits in the prime modulus. The default is 512.
+.It Fl P , Fl \-pvt\-cert
+generate PC private certificate.
+.sp
+Generate a private certificate. By default, the program generates
+public certificates.
+.It Fl p Ar passwd , Fl \-password Ns = Ns Ar passwd
+local private password.
+.sp
+Local files containing private data are encrypted with the
+DES\-CBC algorithm and the specified password. The same password
+must be specified to the local ntpd via the "crypto pw password"
+configuration command. The default password is the local
+hostname.
+.It Fl q Ar passwd , Fl \-export\-passwd Ns = Ns Ar passwd
+export IFF or GQ group keys with password.
+.sp
+Export IFF or GQ identity group keys to the standard output,
+encrypted with the DES\-CBC algorithm and the specified password.
+The same password must be specified to the remote ntpd via the
+"crypto pw password" configuration command. See also the option
+-\-id\-key (\-e) for unencrypted exports.
+.It Fl S Ar sign , Fl \-sign\-key Ns = Ns Ar sign
+generate sign key (RSA or DSA).
+.sp
+Generate a new sign key of the designated type, obsoleting any
+that may exist. By default, the program uses the host key as the
+sign key.
+.It Fl s Ar host@group , Fl \-subject\-name Ns = Ns Ar host@group
+set host and optionally group name.
+.sp
+Set the Autokey host name, and optionally, group name specified
+following an '\fB@\fP' character. The host name is used in the file
+name of generated host and signing certificates, without the
+group name. The host name, and if provided, group name are used
+in \fBhost@group\fP form for the host certificate's subject and issuer
+fields. Specifying '\fB\-s @group\fP' is allowed, and results in
+leaving the host name unchanged while appending \fB@group\fP to the
+subject and issuer fields, as with \fB\-i group\fP. The group name, or
+if not provided, the host name are also used in the file names
+of IFF, GQ, and MV client parameter files.
+.It Fl T , Fl \-trusted\-cert
+trusted certificate (TC scheme).
+.sp
+Generate a trusted certificate. By default, the program generates
+a non\-trusted certificate.
+.It Fl V Ar num , Fl \-mv\-params Ns = Ns Ar num
+generate <num> MV parameters.
+This option takes an integer number as its argument.
+.sp
+Generate parameters and keys for the Mu\-Varadharajan (MV)
+identification scheme.
+.It Fl v Ar num , Fl \-mv\-keys Ns = Ns Ar num
+update <num> MV keys.
+This option takes an integer number as its argument.
+.sp
+This option has not been fully documented.
+.It Fl \&? , Fl \-help
+Display usage information and exit.
+.It Fl \&! , Fl \-more\-help
+Pass the extended usage information through a pager.
+.It Fl > Oo Ar cfgfile Oc , Fl \-save\-opts Oo Ns = Ns Ar cfgfile Oc
+Save the option state to \fIcfgfile\fP. The default is the \fIlast\fP
+configuration file listed in the \fBOPTION PRESETS\fP section, below.
+The command will exit after updating the config file.
+.It Fl < Ar cfgfile , Fl \-load\-opts Ns = Ns Ar cfgfile , Fl \-no\-load\-opts
+Load options from \fIcfgfile\fP.
+The \fIno\-load\-opts\fP form will disable the loading
+of earlier config/rc/ini files. \fI\-\-no\-load\-opts\fP is handled early,
+out of order.
+.It Fl \-version Op Brq Ar v|c|n
+Output version of program and exit. The default mode is `v', a simple
+version. The `c' mode will print copyright information and `n' will
+print the full copyright notice.
+.El
+.Sh "OPTION PRESETS"
+Any option that is not marked as \fInot presettable\fP may be preset
+by loading values from configuration ("RC" or ".INI") file(s) and values from
+environment variables named:
+.nf
+ \fBNTP_KEYGEN_<option\-name>\fP or \fBNTP_KEYGEN\fP
+.fi
+.ad
+The environmental presets take precedence (are processed later than)
+the configuration files.
+The \fIhomerc\fP files are "\fI$HOME\fP", and "\fI.\fP".
+If any of these are directories, then the file \fI.ntprc\fP
+is searched for within those directories.
+.Sh USAGE
+The
+.Fl p Ar password
+option specifies the write password and
+.Fl q Ar password
+option the read password for previously encrypted files.
+The
+.Nm
+program prompts for the password if it reads an encrypted file
+and the password is missing or incorrect.
+If an encrypted file is read successfully and
+no write password is specified, the read password is used
+as the write password by default.
+.Sh "ENVIRONMENT"
+See \fBOPTION PRESETS\fP for configuration environment variables.
+.Sh "FILES"
+See \fBOPTION PRESETS\fP for configuration files.
+.Sh "EXIT STATUS"
+One of the following exit values will be returned:
+.Bl -tag
+.It 0 " (EXIT_SUCCESS)"
+Successful program execution.
+.It 1 " (EXIT_FAILURE)"
+The operation failed or the command syntax was not valid.
+.It 66 " (EX_NOINPUT)"
+A specified configuration file could not be loaded.
+.It 70 " (EX_SOFTWARE)"
+libopts had an internal operational error. Please report
+it to autogen\-users@lists.sourceforge.net. Thank you.
+.El
+.Sh "AUTHORS"
+The University of Delaware and Network Time Foundation
+.Sh "COPYRIGHT"
+Copyright (C) 1992\-2015 The University of Delaware and Network Time Foundation all rights reserved.
+This program is released under the terms of the NTP license, <http://ntp.org/license>.
+.Sh BUGS
+It can take quite a while to generate some cryptographic values,
+from one to several minutes with modern architectures
+such as UltraSPARC and up to tens of minutes to an hour
+with older architectures such as SPARC IPC.
+.Pp
+Please report bugs to http://bugs.ntp.org .
+.Pp
+Please send bug reports to: http://bugs.ntp.org, bugs@ntp.org
+.Sh NOTES
+Portions of this document came from FreeBSD.
+.Pp
+This manual page was \fIAutoGen\fP\-erated from the \fBntp\-keygen\fP
+option definitions.
diff --git a/usr.sbin/ntp/doc/ntp.conf.5 b/usr.sbin/ntp/doc/ntp.conf.5
new file mode 100644
index 0000000..3f075a1
--- /dev/null
+++ b/usr.sbin/ntp/doc/ntp.conf.5
@@ -0,0 +1,2858 @@
+.Dd January 7 2016
+.Dt NTP_CONF 5 File Formats
+.Os
+.\" EDIT THIS FILE WITH CAUTION (ntp.mdoc)
+.\"
+.\" $FreeBSD$
+.\"
+.\" It has been AutoGen-ed January 7, 2016 at 11:30:57 PM by AutoGen 5.18.5
+.\" From the definitions ntp.conf.def
+.\" and the template file agmdoc-cmd.tpl
+.Sh NAME
+.Nm ntp.conf
+.Nd Network Time Protocol (NTP) daemon configuration file format
+.Sh SYNOPSIS
+.Nm
+.Op Fl \-option\-name
+.Op Fl \-option\-name Ar value
+.Pp
+All arguments must be options.
+.Pp
+.Sh DESCRIPTION
+The
+.Nm
+configuration file is read at initial startup by the
+.Xr ntpd 8
+daemon in order to specify the synchronization sources,
+modes and other related information.
+Usually, it is installed in the
+.Pa /etc
+directory,
+but could be installed elsewhere
+(see the daemon's
+.Fl c
+command line option).
+.Pp
+The file format is similar to other
+.Ux
+configuration files.
+Comments begin with a
+.Ql #
+character and extend to the end of the line;
+blank lines are ignored.
+Configuration commands consist of an initial keyword
+followed by a list of arguments,
+some of which may be optional, separated by whitespace.
+Commands may not be continued over multiple lines.
+Arguments may be host names,
+host addresses written in numeric, dotted\-quad form,
+integers, floating point numbers (when specifying times in seconds)
+and text strings.
+.Pp
+The rest of this page describes the configuration and control options.
+The
+.Qq Notes on Configuring NTP and Setting up an NTP Subnet
+page
+(available as part of the HTML documentation
+provided in
+.Pa /usr/share/doc/ntp )
+contains an extended discussion of these options.
+In addition to the discussion of general
+.Sx Configuration Options ,
+there are sections describing the following supported functionality
+and the options used to control it:
+.Bl -bullet -offset indent
+.It
+.Sx Authentication Support
+.It
+.Sx Monitoring Support
+.It
+.Sx Access Control Support
+.It
+.Sx Automatic NTP Configuration Options
+.It
+.Sx Reference Clock Support
+.It
+.Sx Miscellaneous Options
+.El
+.Pp
+Following these is a section describing
+.Sx Miscellaneous Options .
+While there is a rich set of options available,
+the only required option is one or more
+.Ic pool ,
+.Ic server ,
+.Ic peer ,
+.Ic broadcast
+or
+.Ic manycastclient
+commands.
+.Sh Configuration Support
+Following is a description of the configuration commands in
+NTPv4.
+These commands have the same basic functions as in NTPv3 and
+in some cases new functions and new arguments.
+There are two
+classes of commands, configuration commands that configure a
+persistent association with a remote server or peer or reference
+clock, and auxiliary commands that specify environmental variables
+that control various related operations.
+.Ss Configuration Commands
+The various modes are determined by the command keyword and the
+type of the required IP address.
+Addresses are classed by type as
+(s) a remote server or peer (IPv4 class A, B and C), (b) the
+broadcast address of a local interface, (m) a multicast address (IPv4
+class D), or (r) a reference clock address (127.127.x.x).
+Note that
+only those options applicable to each command are listed below.
+Use
+of options not listed may not be caught as an error, but may result
+in some weird and even destructive behavior.
+.Pp
+If the Basic Socket Interface Extensions for IPv6 (RFC\-2553)
+is detected, support for the IPv6 address family is generated
+in addition to the default support of the IPv4 address family.
+In a few cases, including the reslist billboard generated
+by ntpdc, IPv6 addresses are automatically generated.
+IPv6 addresses can be identified by the presence of colons
+.Dq \&:
+in the address field.
+IPv6 addresses can be used almost everywhere where
+IPv4 addresses can be used,
+with the exception of reference clock addresses,
+which are always IPv4.
+.Pp
+Note that in contexts where a host name is expected, a
+.Fl 4
+qualifier preceding
+the host name forces DNS resolution to the IPv4 namespace,
+while a
+.Fl 6
+qualifier forces DNS resolution to the IPv6 namespace.
+See IPv6 references for the
+equivalent classes for that address family.
+.Bl -tag -width indent
+.It Xo Ic pool Ar address
+.Op Cm burst
+.Op Cm iburst
+.Op Cm version Ar version
+.Op Cm prefer
+.Op Cm minpoll Ar minpoll
+.Op Cm maxpoll Ar maxpoll
+.Xc
+.It Xo Ic server Ar address
+.Op Cm key Ar key \&| Cm autokey
+.Op Cm burst
+.Op Cm iburst
+.Op Cm version Ar version
+.Op Cm prefer
+.Op Cm minpoll Ar minpoll
+.Op Cm maxpoll Ar maxpoll
+.Xc
+.It Xo Ic peer Ar address
+.Op Cm key Ar key \&| Cm autokey
+.Op Cm version Ar version
+.Op Cm prefer
+.Op Cm minpoll Ar minpoll
+.Op Cm maxpoll Ar maxpoll
+.Xc
+.It Xo Ic broadcast Ar address
+.Op Cm key Ar key \&| Cm autokey
+.Op Cm version Ar version
+.Op Cm prefer
+.Op Cm minpoll Ar minpoll
+.Op Cm ttl Ar ttl
+.Xc
+.It Xo Ic manycastclient Ar address
+.Op Cm key Ar key \&| Cm autokey
+.Op Cm version Ar version
+.Op Cm prefer
+.Op Cm minpoll Ar minpoll
+.Op Cm maxpoll Ar maxpoll
+.Op Cm ttl Ar ttl
+.Xc
+.El
+.Pp
+These five commands specify the time server name or address to
+be used and the mode in which to operate.
+The
+.Ar address
+can be
+either a DNS name or an IP address in dotted\-quad notation.
+Additional information on association behavior can be found in the
+.Qq Association Management
+page
+(available as part of the HTML documentation
+provided in
+.Pa /usr/share/doc/ntp ) .
+.Bl -tag -width indent
+.It Ic pool
+For type s addresses, this command mobilizes a persistent
+client mode association with a number of remote servers.
+In this mode the local clock can synchronized to the
+remote server, but the remote server can never be synchronized to
+the local clock.
+.It Ic server
+For type s and r addresses, this command mobilizes a persistent
+client mode association with the specified remote server or local
+radio clock.
+In this mode the local clock can synchronized to the
+remote server, but the remote server can never be synchronized to
+the local clock.
+This command should
+.Em not
+be used for type
+b or m addresses.
+.It Ic peer
+For type s addresses (only), this command mobilizes a
+persistent symmetric\-active mode association with the specified
+remote peer.
+In this mode the local clock can be synchronized to
+the remote peer or the remote peer can be synchronized to the local
+clock.
+This is useful in a network of servers where, depending on
+various failure scenarios, either the local or remote peer may be
+the better source of time.
+This command should NOT be used for type
+b, m or r addresses.
+.It Ic broadcast
+For type b and m addresses (only), this
+command mobilizes a persistent broadcast mode association.
+Multiple
+commands can be used to specify multiple local broadcast interfaces
+(subnets) and/or multiple multicast groups.
+Note that local
+broadcast messages go only to the interface associated with the
+subnet specified, but multicast messages go to all interfaces.
+In broadcast mode the local server sends periodic broadcast
+messages to a client population at the
+.Ar address
+specified, which is usually the broadcast address on (one of) the
+local network(s) or a multicast address assigned to NTP.
+The IANA
+has assigned the multicast group address IPv4 224.0.1.1 and
+IPv6 ff05::101 (site local) exclusively to
+NTP, but other nonconflicting addresses can be used to contain the
+messages within administrative boundaries.
+Ordinarily, this
+specification applies only to the local server operating as a
+sender; for operation as a broadcast client, see the
+.Ic broadcastclient
+or
+.Ic multicastclient
+commands
+below.
+.It Ic manycastclient
+For type m addresses (only), this command mobilizes a
+manycast client mode association for the multicast address
+specified.
+In this case a specific address must be supplied which
+matches the address used on the
+.Ic manycastserver
+command for
+the designated manycast servers.
+The NTP multicast address
+224.0.1.1 assigned by the IANA should NOT be used, unless specific
+means are taken to avoid spraying large areas of the Internet with
+these messages and causing a possibly massive implosion of replies
+at the sender.
+The
+.Ic manycastserver
+command specifies that the local server
+is to operate in client mode with the remote servers that are
+discovered as the result of broadcast/multicast messages.
+The
+client broadcasts a request message to the group address associated
+with the specified
+.Ar address
+and specifically enabled
+servers respond to these messages.
+The client selects the servers
+providing the best time and continues as with the
+.Ic server
+command.
+The remaining servers are discarded as if never
+heard.
+.El
+.Pp
+Options:
+.Bl -tag -width indent
+.It Cm autokey
+All packets sent to and received from the server or peer are to
+include authentication fields encrypted using the autokey scheme
+described in
+.Sx Authentication Options .
+.It Cm burst
+when the server is reachable, send a burst of eight packets
+instead of the usual one.
+The packet spacing is normally 2 s;
+however, the spacing between the first and second packets
+can be changed with the calldelay command to allow
+additional time for a modem or ISDN call to complete.
+This is designed to improve timekeeping quality
+with the
+.Ic server
+command and s addresses.
+.It Cm iburst
+When the server is unreachable, send a burst of eight packets
+instead of the usual one.
+The packet spacing is normally 2 s;
+however, the spacing between the first two packets can be
+changed with the calldelay command to allow
+additional time for a modem or ISDN call to complete.
+This is designed to speed the initial synchronization
+acquisition with the
+.Ic server
+command and s addresses and when
+.Xr ntpd 8
+is started with the
+.Fl q
+option.
+.It Cm key Ar key
+All packets sent to and received from the server or peer are to
+include authentication fields encrypted using the specified
+.Ar key
+identifier with values from 1 to 65534, inclusive.
+The
+default is to include no encryption field.
+.It Cm minpoll Ar minpoll
+.It Cm maxpoll Ar maxpoll
+These options specify the minimum and maximum poll intervals
+for NTP messages, as a power of 2 in seconds
+The maximum poll
+interval defaults to 10 (1,024 s), but can be increased by the
+.Cm maxpoll
+option to an upper limit of 17 (36.4 h).
+The
+minimum poll interval defaults to 6 (64 s), but can be decreased by
+the
+.Cm minpoll
+option to a lower limit of 4 (16 s).
+.It Cm noselect
+Marks the server as unused, except for display purposes.
+The server is discarded by the selection algroithm.
+.It Cm prefer
+Marks the server as preferred.
+All other things being equal,
+this host will be chosen for synchronization among a set of
+correctly operating hosts.
+See the
+.Qq Mitigation Rules and the prefer Keyword
+page
+(available as part of the HTML documentation
+provided in
+.Pa /usr/share/doc/ntp )
+for further information.
+.It Cm ttl Ar ttl
+This option is used only with broadcast server and manycast
+client modes.
+It specifies the time\-to\-live
+.Ar ttl
+to
+use on broadcast server and multicast server and the maximum
+.Ar ttl
+for the expanding ring search with manycast
+client packets.
+Selection of the proper value, which defaults to
+127, is something of a black art and should be coordinated with the
+network administrator.
+.It Cm version Ar version
+Specifies the version number to be used for outgoing NTP
+packets.
+Versions 1\-4 are the choices, with version 4 the
+default.
+.El
+.Ss Auxiliary Commands
+.Bl -tag -width indent
+.It Ic broadcastclient
+This command enables reception of broadcast server messages to
+any local interface (type b) address.
+Upon receiving a message for
+the first time, the broadcast client measures the nominal server
+propagation delay using a brief client/server exchange with the
+server, then enters the broadcast client mode, in which it
+synchronizes to succeeding broadcast messages.
+Note that, in order
+to avoid accidental or malicious disruption in this mode, both the
+server and client should operate using symmetric\-key or public\-key
+authentication as described in
+.Sx Authentication Options .
+.It Ic manycastserver Ar address ...
+This command enables reception of manycast client messages to
+the multicast group address(es) (type m) specified.
+At least one
+address is required, but the NTP multicast address 224.0.1.1
+assigned by the IANA should NOT be used, unless specific means are
+taken to limit the span of the reply and avoid a possibly massive
+implosion at the original sender.
+Note that, in order to avoid
+accidental or malicious disruption in this mode, both the server
+and client should operate using symmetric\-key or public\-key
+authentication as described in
+.Sx Authentication Options .
+.It Ic multicastclient Ar address ...
+This command enables reception of multicast server messages to
+the multicast group address(es) (type m) specified.
+Upon receiving
+a message for the first time, the multicast client measures the
+nominal server propagation delay using a brief client/server
+exchange with the server, then enters the broadcast client mode, in
+which it synchronizes to succeeding multicast messages.
+Note that,
+in order to avoid accidental or malicious disruption in this mode,
+both the server and client should operate using symmetric\-key or
+public\-key authentication as described in
+.Sx Authentication Options .
+.It Ic mdnstries Ar number
+If we are participating in mDNS,
+after we have synched for the first time
+we attempt to register with the mDNS system.
+If that registration attempt fails,
+we try again at one minute intervals for up to
+.Ic mdnstries
+times.
+After all,
+.Ic ntpd
+may be starting before mDNS.
+The default value for
+.Ic mdnstries
+is 5.
+.El
+.Sh Authentication Support
+Authentication support allows the NTP client to verify that the
+server is in fact known and trusted and not an intruder intending
+accidentally or on purpose to masquerade as that server.
+The NTPv3
+specification RFC\-1305 defines a scheme which provides
+cryptographic authentication of received NTP packets.
+Originally,
+this was done using the Data Encryption Standard (DES) algorithm
+operating in Cipher Block Chaining (CBC) mode, commonly called
+DES\-CBC.
+Subsequently, this was replaced by the RSA Message Digest
+5 (MD5) algorithm using a private key, commonly called keyed\-MD5.
+Either algorithm computes a message digest, or one\-way hash, which
+can be used to verify the server has the correct private key and
+key identifier.
+.Pp
+NTPv4 retains the NTPv3 scheme, properly described as symmetric key
+cryptography and, in addition, provides a new Autokey scheme
+based on public key cryptography.
+Public key cryptography is generally considered more secure
+than symmetric key cryptography, since the security is based
+on a private value which is generated by each server and
+never revealed.
+With Autokey all key distribution and
+management functions involve only public values, which
+considerably simplifies key distribution and storage.
+Public key management is based on X.509 certificates,
+which can be provided by commercial services or
+produced by utility programs in the OpenSSL software library
+or the NTPv4 distribution.
+.Pp
+While the algorithms for symmetric key cryptography are
+included in the NTPv4 distribution, public key cryptography
+requires the OpenSSL software library to be installed
+before building the NTP distribution.
+Directions for doing that
+are on the Building and Installing the Distribution page.
+.Pp
+Authentication is configured separately for each association
+using the
+.Cm key
+or
+.Cm autokey
+subcommand on the
+.Ic peer ,
+.Ic server ,
+.Ic broadcast
+and
+.Ic manycastclient
+configuration commands as described in
+.Sx Configuration Options
+page.
+The authentication
+options described below specify the locations of the key files,
+if other than default, which symmetric keys are trusted
+and the interval between various operations, if other than default.
+.Pp
+Authentication is always enabled,
+although ineffective if not configured as
+described below.
+If a NTP packet arrives
+including a message authentication
+code (MAC), it is accepted only if it
+passes all cryptographic checks.
+The
+checks require correct key ID, key value
+and message digest.
+If the packet has
+been modified in any way or replayed
+by an intruder, it will fail one or more
+of these checks and be discarded.
+Furthermore, the Autokey scheme requires a
+preliminary protocol exchange to obtain
+the server certificate, verify its
+credentials and initialize the protocol
+.Pp
+The
+.Cm auth
+flag controls whether new associations or
+remote configuration commands require cryptographic authentication.
+This flag can be set or reset by the
+.Ic enable
+and
+.Ic disable
+commands and also by remote
+configuration commands sent by a
+.Xr ntpdc 8
+program running in
+another machine.
+If this flag is enabled, which is the default
+case, new broadcast client and symmetric passive associations and
+remote configuration commands must be cryptographically
+authenticated using either symmetric key or public key cryptography.
+If this
+flag is disabled, these operations are effective
+even if not cryptographic
+authenticated.
+It should be understood
+that operating with the
+.Ic auth
+flag disabled invites a significant vulnerability
+where a rogue hacker can
+masquerade as a falseticker and seriously
+disrupt system timekeeping.
+It is
+important to note that this flag has no purpose
+other than to allow or disallow
+a new association in response to new broadcast
+and symmetric active messages
+and remote configuration commands and, in particular,
+the flag has no effect on
+the authentication process itself.
+.Pp
+An attractive alternative where multicast support is available
+is manycast mode, in which clients periodically troll
+for servers as described in the
+.Sx Automatic NTP Configuration Options
+page.
+Either symmetric key or public key
+cryptographic authentication can be used in this mode.
+The principle advantage
+of manycast mode is that potential servers need not be
+configured in advance,
+since the client finds them during regular operation,
+and the configuration
+files for all clients can be identical.
+.Pp
+The security model and protocol schemes for
+both symmetric key and public key
+cryptography are summarized below;
+further details are in the briefings, papers
+and reports at the NTP project page linked from
+.Li http://www.ntp.org/ .
+.Ss Symmetric\-Key Cryptography
+The original RFC\-1305 specification allows any one of possibly
+65,534 keys, each distinguished by a 32\-bit key identifier, to
+authenticate an association.
+The servers and clients involved must
+agree on the key and key identifier to
+authenticate NTP packets.
+Keys and
+related information are specified in a key
+file, usually called
+.Pa ntp.keys ,
+which must be distributed and stored using
+secure means beyond the scope of the NTP protocol itself.
+Besides the keys used
+for ordinary NTP associations,
+additional keys can be used as passwords for the
+.Xr ntpq 8
+and
+.Xr ntpdc 8
+utility programs.
+.Pp
+When
+.Xr ntpd 8
+is first started, it reads the key file specified in the
+.Ic keys
+configuration command and installs the keys
+in the key cache.
+However,
+individual keys must be activated with the
+.Ic trusted
+command before use.
+This
+allows, for instance, the installation of possibly
+several batches of keys and
+then activating or deactivating each batch
+remotely using
+.Xr ntpdc 8 .
+This also provides a revocation capability that can be used
+if a key becomes compromised.
+The
+.Ic requestkey
+command selects the key used as the password for the
+.Xr ntpdc 8
+utility, while the
+.Ic controlkey
+command selects the key used as the password for the
+.Xr ntpq 8
+utility.
+.Ss Public Key Cryptography
+NTPv4 supports the original NTPv3 symmetric key scheme
+described in RFC\-1305 and in addition the Autokey protocol,
+which is based on public key cryptography.
+The Autokey Version 2 protocol described on the Autokey Protocol
+page verifies packet integrity using MD5 message digests
+and verifies the source with digital signatures and any of several
+digest/signature schemes.
+Optional identity schemes described on the Identity Schemes
+page and based on cryptographic challenge/response algorithms
+are also available.
+Using all of these schemes provides strong security against
+replay with or without modification, spoofing, masquerade
+and most forms of clogging attacks.
+.\" .Pp
+.\" The cryptographic means necessary for all Autokey operations
+.\" is provided by the OpenSSL software library.
+.\" This library is available from http://www.openssl.org/
+.\" and can be installed using the procedures outlined
+.\" in the Building and Installing the Distribution page.
+.\" Once installed,
+.\" the configure and build
+.\" process automatically detects the library and links
+.\" the library routines required.
+.Pp
+The Autokey protocol has several modes of operation
+corresponding to the various NTP modes supported.
+Most modes use a special cookie which can be
+computed independently by the client and server,
+but encrypted in transmission.
+All modes use in addition a variant of the S\-KEY scheme,
+in which a pseudo\-random key list is generated and used
+in reverse order.
+These schemes are described along with an executive summary,
+current status, briefing slides and reading list on the
+.Sx Autonomous Authentication
+page.
+.Pp
+The specific cryptographic environment used by Autokey servers
+and clients is determined by a set of files
+and soft links generated by the
+.Xr ntp\-keygen 1ntpkeygenmdoc
+program.
+This includes a required host key file,
+required certificate file and optional sign key file,
+leapsecond file and identity scheme files.
+The
+digest/signature scheme is specified in the X.509 certificate
+along with the matching sign key.
+There are several schemes
+available in the OpenSSL software library, each identified
+by a specific string such as
+.Cm md5WithRSAEncryption ,
+which stands for the MD5 message digest with RSA
+encryption scheme.
+The current NTP distribution supports
+all the schemes in the OpenSSL library, including
+those based on RSA and DSA digital signatures.
+.Pp
+NTP secure groups can be used to define cryptographic compartments
+and security hierarchies.
+It is important that every host
+in the group be able to construct a certificate trail to one
+or more trusted hosts in the same group.
+Each group
+host runs the Autokey protocol to obtain the certificates
+for all hosts along the trail to one or more trusted hosts.
+This requires the configuration file in all hosts to be
+engineered so that, even under anticipated failure conditions,
+the NTP subnet will form such that every group host can find
+a trail to at least one trusted host.
+.Ss Naming and Addressing
+It is important to note that Autokey does not use DNS to
+resolve addresses, since DNS can't be completely trusted
+until the name servers have synchronized clocks.
+The cryptographic name used by Autokey to bind the host identity
+credentials and cryptographic values must be independent
+of interface, network and any other naming convention.
+The name appears in the host certificate in either or both
+the subject and issuer fields, so protection against
+DNS compromise is essential.
+.Pp
+By convention, the name of an Autokey host is the name returned
+by the Unix
+.Xr gethostname 2
+system call or equivalent in other systems.
+By the system design
+model, there are no provisions to allow alternate names or aliases.
+However, this is not to say that DNS aliases, different names
+for each interface, etc., are constrained in any way.
+.Pp
+It is also important to note that Autokey verifies authenticity
+using the host name, network address and public keys,
+all of which are bound together by the protocol specifically
+to deflect masquerade attacks.
+For this reason Autokey
+includes the source and destinatino IP addresses in message digest
+computations and so the same addresses must be available
+at both the server and client.
+For this reason operation
+with network address translation schemes is not possible.
+This reflects the intended robust security model where government
+and corporate NTP servers are operated outside firewall perimeters.
+.Ss Operation
+A specific combination of authentication scheme (none,
+symmetric key, public key) and identity scheme is called
+a cryptotype, although not all combinations are compatible.
+There may be management configurations where the clients,
+servers and peers may not all support the same cryptotypes.
+A secure NTPv4 subnet can be configured in many ways while
+keeping in mind the principles explained above and
+in this section.
+Note however that some cryptotype
+combinations may successfully interoperate with each other,
+but may not represent good security practice.
+.Pp
+The cryptotype of an association is determined at the time
+of mobilization, either at configuration time or some time
+later when a message of appropriate cryptotype arrives.
+When mobilized by a
+.Ic server
+or
+.Ic peer
+configuration command and no
+.Ic key
+or
+.Ic autokey
+subcommands are present, the association is not
+authenticated; if the
+.Ic key
+subcommand is present, the association is authenticated
+using the symmetric key ID specified; if the
+.Ic autokey
+subcommand is present, the association is authenticated
+using Autokey.
+.Pp
+When multiple identity schemes are supported in the Autokey
+protocol, the first message exchange determines which one is used.
+The client request message contains bits corresponding
+to which schemes it has available.
+The server response message
+contains bits corresponding to which schemes it has available.
+Both server and client match the received bits with their own
+and select a common scheme.
+.Pp
+Following the principle that time is a public value,
+a server responds to any client packet that matches
+its cryptotype capabilities.
+Thus, a server receiving
+an unauthenticated packet will respond with an unauthenticated
+packet, while the same server receiving a packet of a cryptotype
+it supports will respond with packets of that cryptotype.
+However, unconfigured broadcast or manycast client
+associations or symmetric passive associations will not be
+mobilized unless the server supports a cryptotype compatible
+with the first packet received.
+By default, unauthenticated associations will not be mobilized
+unless overridden in a decidedly dangerous way.
+.Pp
+Some examples may help to reduce confusion.
+Client Alice has no specific cryptotype selected.
+Server Bob has both a symmetric key file and minimal Autokey files.
+Alice's unauthenticated messages arrive at Bob, who replies with
+unauthenticated messages.
+Cathy has a copy of Bob's symmetric
+key file and has selected key ID 4 in messages to Bob.
+Bob verifies the message with his key ID 4.
+If it's the
+same key and the message is verified, Bob sends Cathy a reply
+authenticated with that key.
+If verification fails,
+Bob sends Cathy a thing called a crypto\-NAK, which tells her
+something broke.
+She can see the evidence using the
+.Xr ntpq 8
+program.
+.Pp
+Denise has rolled her own host key and certificate.
+She also uses one of the identity schemes as Bob.
+She sends the first Autokey message to Bob and they
+both dance the protocol authentication and identity steps.
+If all comes out okay, Denise and Bob continue as described above.
+.Pp
+It should be clear from the above that Bob can support
+all the girls at the same time, as long as he has compatible
+authentication and identity credentials.
+Now, Bob can act just like the girls in his own choice of servers;
+he can run multiple configured associations with multiple different
+servers (or the same server, although that might not be useful).
+But, wise security policy might preclude some cryptotype
+combinations; for instance, running an identity scheme
+with one server and no authentication with another might not be wise.
+.Ss Key Management
+The cryptographic values used by the Autokey protocol are
+incorporated as a set of files generated by the
+.Xr ntp\-keygen 1ntpkeygenmdoc
+utility program, including symmetric key, host key and
+public certificate files, as well as sign key, identity parameters
+and leapseconds files.
+Alternatively, host and sign keys and
+certificate files can be generated by the OpenSSL utilities
+and certificates can be imported from public certificate
+authorities.
+Note that symmetric keys are necessary for the
+.Xr ntpq 8
+and
+.Xr ntpdc 8
+utility programs.
+The remaining files are necessary only for the
+Autokey protocol.
+.Pp
+Certificates imported from OpenSSL or public certificate
+authorities have certian limitations.
+The certificate should be in ASN.1 syntax, X.509 Version 3
+format and encoded in PEM, which is the same format
+used by OpenSSL.
+The overall length of the certificate encoded
+in ASN.1 must not exceed 1024 bytes.
+The subject distinguished
+name field (CN) is the fully qualified name of the host
+on which it is used; the remaining subject fields are ignored.
+The certificate extension fields must not contain either
+a subject key identifier or a issuer key identifier field;
+however, an extended key usage field for a trusted host must
+contain the value
+.Cm trustRoot ; .
+Other extension fields are ignored.
+.Ss Authentication Commands
+.Bl -tag -width indent
+.It Ic autokey Op Ar logsec
+Specifies the interval between regenerations of the session key
+list used with the Autokey protocol.
+Note that the size of the key
+list for each association depends on this interval and the current
+poll interval.
+The default value is 12 (4096 s or about 1.1 hours).
+For poll intervals above the specified interval, a session key list
+with a single entry will be regenerated for every message
+sent.
+.It Ic controlkey Ar key
+Specifies the key identifier to use with the
+.Xr ntpq 8
+utility, which uses the standard
+protocol defined in RFC\-1305.
+The
+.Ar key
+argument is
+the key identifier for a trusted key, where the value can be in the
+range 1 to 65,534, inclusive.
+.It Xo Ic crypto
+.Op Cm cert Ar file
+.Op Cm leap Ar file
+.Op Cm randfile Ar file
+.Op Cm host Ar file
+.Op Cm sign Ar file
+.Op Cm gq Ar file
+.Op Cm gqpar Ar file
+.Op Cm iffpar Ar file
+.Op Cm mvpar Ar file
+.Op Cm pw Ar password
+.Xc
+This command requires the OpenSSL library.
+It activates public key
+cryptography, selects the message digest and signature
+encryption scheme and loads the required private and public
+values described above.
+If one or more files are left unspecified,
+the default names are used as described above.
+Unless the complete path and name of the file are specified, the
+location of a file is relative to the keys directory specified
+in the
+.Ic keysdir
+command or default
+.Pa /usr/local/etc .
+Following are the subcommands:
+.Bl -tag -width indent
+.It Cm cert Ar file
+Specifies the location of the required host public certificate file.
+This overrides the link
+.Pa ntpkey_cert_ Ns Ar hostname
+in the keys directory.
+.It Cm gqpar Ar file
+Specifies the location of the optional GQ parameters file.
+This
+overrides the link
+.Pa ntpkey_gq_ Ns Ar hostname
+in the keys directory.
+.It Cm host Ar file
+Specifies the location of the required host key file.
+This overrides
+the link
+.Pa ntpkey_key_ Ns Ar hostname
+in the keys directory.
+.It Cm iffpar Ar file
+Specifies the location of the optional IFF parameters file.This
+overrides the link
+.Pa ntpkey_iff_ Ns Ar hostname
+in the keys directory.
+.It Cm leap Ar file
+Specifies the location of the optional leapsecond file.
+This overrides the link
+.Pa ntpkey_leap
+in the keys directory.
+.It Cm mvpar Ar file
+Specifies the location of the optional MV parameters file.
+This
+overrides the link
+.Pa ntpkey_mv_ Ns Ar hostname
+in the keys directory.
+.It Cm pw Ar password
+Specifies the password to decrypt files containing private keys and
+identity parameters.
+This is required only if these files have been
+encrypted.
+.It Cm randfile Ar file
+Specifies the location of the random seed file used by the OpenSSL
+library.
+The defaults are described in the main text above.
+.It Cm sign Ar file
+Specifies the location of the optional sign key file.
+This overrides
+the link
+.Pa ntpkey_sign_ Ns Ar hostname
+in the keys directory.
+If this file is
+not found, the host key is also the sign key.
+.El
+.It Ic keys Ar keyfile
+Specifies the complete path and location of the MD5 key file
+containing the keys and key identifiers used by
+.Xr ntpd 8 ,
+.Xr ntpq 8
+and
+.Xr ntpdc 8
+when operating with symmetric key cryptography.
+This is the same operation as the
+.Fl k
+command line option.
+.It Ic keysdir Ar path
+This command specifies the default directory path for
+cryptographic keys, parameters and certificates.
+The default is
+.Pa /usr/local/etc/ .
+.It Ic requestkey Ar key
+Specifies the key identifier to use with the
+.Xr ntpdc 8
+utility program, which uses a
+proprietary protocol specific to this implementation of
+.Xr ntpd 8 .
+The
+.Ar key
+argument is a key identifier
+for the trusted key, where the value can be in the range 1 to
+65,534, inclusive.
+.It Ic revoke Ar logsec
+Specifies the interval between re\-randomization of certain
+cryptographic values used by the Autokey scheme, as a power of 2 in
+seconds.
+These values need to be updated frequently in order to
+deflect brute\-force attacks on the algorithms of the scheme;
+however, updating some values is a relatively expensive operation.
+The default interval is 16 (65,536 s or about 18 hours).
+For poll
+intervals above the specified interval, the values will be updated
+for every message sent.
+.It Ic trustedkey Ar key ...
+Specifies the key identifiers which are trusted for the
+purposes of authenticating peers with symmetric key cryptography,
+as well as keys used by the
+.Xr ntpq 8
+and
+.Xr ntpdc 8
+programs.
+The authentication procedures require that both the local
+and remote servers share the same key and key identifier for this
+purpose, although different keys can be used with different
+servers.
+The
+.Ar key
+arguments are 32\-bit unsigned
+integers with values from 1 to 65,534.
+.El
+.Ss Error Codes
+The following error codes are reported via the NTP control
+and monitoring protocol trap mechanism.
+.Bl -tag -width indent
+.It 101
+.Pq bad field format or length
+The packet has invalid version, length or format.
+.It 102
+.Pq bad timestamp
+The packet timestamp is the same or older than the most recent received.
+This could be due to a replay or a server clock time step.
+.It 103
+.Pq bad filestamp
+The packet filestamp is the same or older than the most recent received.
+This could be due to a replay or a key file generation error.
+.It 104
+.Pq bad or missing public key
+The public key is missing, has incorrect format or is an unsupported type.
+.It 105
+.Pq unsupported digest type
+The server requires an unsupported digest/signature scheme.
+.It 106
+.Pq mismatched digest types
+Not used.
+.It 107
+.Pq bad signature length
+The signature length does not match the current public key.
+.It 108
+.Pq signature not verified
+The message fails the signature check.
+It could be bogus or signed by a
+different private key.
+.It 109
+.Pq certificate not verified
+The certificate is invalid or signed with the wrong key.
+.It 110
+.Pq certificate not verified
+The certificate is not yet valid or has expired or the signature could not
+be verified.
+.It 111
+.Pq bad or missing cookie
+The cookie is missing, corrupted or bogus.
+.It 112
+.Pq bad or missing leapseconds table
+The leapseconds table is missing, corrupted or bogus.
+.It 113
+.Pq bad or missing certificate
+The certificate is missing, corrupted or bogus.
+.It 114
+.Pq bad or missing identity
+The identity key is missing, corrupt or bogus.
+.El
+.Sh Monitoring Support
+.Xr ntpd 8
+includes a comprehensive monitoring facility suitable
+for continuous, long term recording of server and client
+timekeeping performance.
+See the
+.Ic statistics
+command below
+for a listing and example of each type of statistics currently
+supported.
+Statistic files are managed using file generation sets
+and scripts in the
+.Pa ./scripts
+directory of this distribution.
+Using
+these facilities and
+.Ux
+.Xr cron 8
+jobs, the data can be
+automatically summarized and archived for retrospective analysis.
+.Ss Monitoring Commands
+.Bl -tag -width indent
+.It Ic statistics Ar name ...
+Enables writing of statistics records.
+Currently, eight kinds of
+.Ar name
+statistics are supported.
+.Bl -tag -width indent
+.It Cm clockstats
+Enables recording of clock driver statistics information.
+Each update
+received from a clock driver appends a line of the following form to
+the file generation set named
+.Cm clockstats :
+.Bd -literal
+49213 525.624 127.127.4.1 93 226 00:08:29.606 D
+.Ed
+.Pp
+The first two fields show the date (Modified Julian Day) and time
+(seconds and fraction past UTC midnight).
+The next field shows the
+clock address in dotted\-quad notation.
+The final field shows the last
+timecode received from the clock in decoded ASCII format, where
+meaningful.
+In some clock drivers a good deal of additional information
+can be gathered and displayed as well.
+See information specific to each
+clock for further details.
+.It Cm cryptostats
+This option requires the OpenSSL cryptographic software library.
+It
+enables recording of cryptographic public key protocol information.
+Each message received by the protocol module appends a line of the
+following form to the file generation set named
+.Cm cryptostats :
+.Bd -literal
+49213 525.624 127.127.4.1 message
+.Ed
+.Pp
+The first two fields show the date (Modified Julian Day) and time
+(seconds and fraction past UTC midnight).
+The next field shows the peer
+address in dotted\-quad notation, The final message field includes the
+message type and certain ancillary information.
+See the
+.Sx Authentication Options
+section for further information.
+.It Cm loopstats
+Enables recording of loop filter statistics information.
+Each
+update of the local clock outputs a line of the following form to
+the file generation set named
+.Cm loopstats :
+.Bd -literal
+50935 75440.031 0.000006019 13.778190 0.000351733 0.0133806
+.Ed
+.Pp
+The first two fields show the date (Modified Julian Day) and
+time (seconds and fraction past UTC midnight).
+The next five fields
+show time offset (seconds), frequency offset (parts per million \-
+PPM), RMS jitter (seconds), Allan deviation (PPM) and clock
+discipline time constant.
+.It Cm peerstats
+Enables recording of peer statistics information.
+This includes
+statistics records of all peers of a NTP server and of special
+signals, where present and configured.
+Each valid update appends a
+line of the following form to the current element of a file
+generation set named
+.Cm peerstats :
+.Bd -literal
+48773 10847.650 127.127.4.1 9714 \-0.001605376 0.000000000 0.001424877 0.000958674
+.Ed
+.Pp
+The first two fields show the date (Modified Julian Day) and
+time (seconds and fraction past UTC midnight).
+The next two fields
+show the peer address in dotted\-quad notation and status,
+respectively.
+The status field is encoded in hex in the format
+described in Appendix A of the NTP specification RFC 1305.
+The final four fields show the offset,
+delay, dispersion and RMS jitter, all in seconds.
+.It Cm rawstats
+Enables recording of raw\-timestamp statistics information.
+This
+includes statistics records of all peers of a NTP server and of
+special signals, where present and configured.
+Each NTP message
+received from a peer or clock driver appends a line of the
+following form to the file generation set named
+.Cm rawstats :
+.Bd -literal
+50928 2132.543 128.4.1.1 128.4.1.20 3102453281.584327000 3102453281.58622800031 02453332.540806000 3102453332.541458000
+.Ed
+.Pp
+The first two fields show the date (Modified Julian Day) and
+time (seconds and fraction past UTC midnight).
+The next two fields
+show the remote peer or clock address followed by the local address
+in dotted\-quad notation.
+The final four fields show the originate,
+receive, transmit and final NTP timestamps in order.
+The timestamp
+values are as received and before processing by the various data
+smoothing and mitigation algorithms.
+.It Cm sysstats
+Enables recording of ntpd statistics counters on a periodic basis.
+Each
+hour a line of the following form is appended to the file generation
+set named
+.Cm sysstats :
+.Bd -literal
+50928 2132.543 36000 81965 0 9546 56 71793 512 540 10 147
+.Ed
+.Pp
+The first two fields show the date (Modified Julian Day) and time
+(seconds and fraction past UTC midnight).
+The remaining ten fields show
+the statistics counter values accumulated since the last generated
+line.
+.Bl -tag -width indent
+.It Time since restart Cm 36000
+Time in hours since the system was last rebooted.
+.It Packets received Cm 81965
+Total number of packets received.
+.It Packets processed Cm 0
+Number of packets received in response to previous packets sent
+.It Current version Cm 9546
+Number of packets matching the current NTP version.
+.It Previous version Cm 56
+Number of packets matching the previous NTP version.
+.It Bad version Cm 71793
+Number of packets matching neither NTP version.
+.It Access denied Cm 512
+Number of packets denied access for any reason.
+.It Bad length or format Cm 540
+Number of packets with invalid length, format or port number.
+.It Bad authentication Cm 10
+Number of packets not verified as authentic.
+.It Rate exceeded Cm 147
+Number of packets discarded due to rate limitation.
+.El
+.It Cm statsdir Ar directory_path
+Indicates the full path of a directory where statistics files
+should be created (see below).
+This keyword allows
+the (otherwise constant)
+.Cm filegen
+filename prefix to be modified for file generation sets, which
+is useful for handling statistics logs.
+.It Cm filegen Ar name Xo
+.Op Cm file Ar filename
+.Op Cm type Ar typename
+.Op Cm link | nolink
+.Op Cm enable | disable
+.Xc
+Configures setting of generation file set name.
+Generation
+file sets provide a means for handling files that are
+continuously growing during the lifetime of a server.
+Server statistics are a typical example for such files.
+Generation file sets provide access to a set of files used
+to store the actual data.
+At any time at most one element
+of the set is being written to.
+The type given specifies
+when and how data will be directed to a new element of the set.
+This way, information stored in elements of a file set
+that are currently unused are available for administrational
+operations without the risk of disturbing the operation of ntpd.
+(Most important: they can be removed to free space for new data
+produced.)
+.Pp
+Note that this command can be sent from the
+.Xr ntpdc 8
+program running at a remote location.
+.Bl -tag -width indent
+.It Cm name
+This is the type of the statistics records, as shown in the
+.Cm statistics
+command.
+.It Cm file Ar filename
+This is the file name for the statistics records.
+Filenames of set
+members are built from three concatenated elements
+.Ar Cm prefix ,
+.Ar Cm filename
+and
+.Ar Cm suffix :
+.Bl -tag -width indent
+.It Cm prefix
+This is a constant filename path.
+It is not subject to
+modifications via the
+.Ar filegen
+option.
+It is defined by the
+server, usually specified as a compile\-time constant.
+It may,
+however, be configurable for individual file generation sets
+via other commands.
+For example, the prefix used with
+.Ar loopstats
+and
+.Ar peerstats
+generation can be configured using the
+.Ar statsdir
+option explained above.
+.It Cm filename
+This string is directly concatenated to the prefix mentioned
+above (no intervening
+.Ql / ) .
+This can be modified using
+the file argument to the
+.Ar filegen
+statement.
+No
+.Pa ..
+elements are
+allowed in this component to prevent filenames referring to
+parts outside the filesystem hierarchy denoted by
+.Ar prefix .
+.It Cm suffix
+This part is reflects individual elements of a file set.
+It is
+generated according to the type of a file set.
+.El
+.It Cm type Ar typename
+A file generation set is characterized by its type.
+The following
+types are supported:
+.Bl -tag -width indent
+.It Cm none
+The file set is actually a single plain file.
+.It Cm pid
+One element of file set is used per incarnation of a ntpd
+server.
+This type does not perform any changes to file set
+members during runtime, however it provides an easy way of
+separating files belonging to different
+.Xr ntpd 8
+server incarnations.
+The set member filename is built by appending a
+.Ql \&.
+to concatenated
+.Ar prefix
+and
+.Ar filename
+strings, and
+appending the decimal representation of the process ID of the
+.Xr ntpd 8
+server process.
+.It Cm day
+One file generation set element is created per day.
+A day is
+defined as the period between 00:00 and 24:00 UTC.
+The file set
+member suffix consists of a
+.Ql \&.
+and a day specification in
+the form
+.Cm YYYYMMdd .
+.Cm YYYY
+is a 4\-digit year number (e.g., 1992).
+.Cm MM
+is a two digit month number.
+.Cm dd
+is a two digit day number.
+Thus, all information written at 10 December 1992 would end up
+in a file named
+.Ar prefix
+.Ar filename Ns .19921210 .
+.It Cm week
+Any file set member contains data related to a certain week of
+a year.
+The term week is defined by computing day\-of\-year
+modulo 7.
+Elements of such a file generation set are
+distinguished by appending the following suffix to the file set
+filename base: A dot, a 4\-digit year number, the letter
+.Cm W ,
+and a 2\-digit week number.
+For example, information from January,
+10th 1992 would end up in a file with suffix
+.No . Ns Ar 1992W1 .
+.It Cm month
+One generation file set element is generated per month.
+The
+file name suffix consists of a dot, a 4\-digit year number, and
+a 2\-digit month.
+.It Cm year
+One generation file element is generated per year.
+The filename
+suffix consists of a dot and a 4 digit year number.
+.It Cm age
+This type of file generation sets changes to a new element of
+the file set every 24 hours of server operation.
+The filename
+suffix consists of a dot, the letter
+.Cm a ,
+and an 8\-digit number.
+This number is taken to be the number of seconds the server is
+running at the start of the corresponding 24\-hour period.
+Information is only written to a file generation by specifying
+.Cm enable ;
+output is prevented by specifying
+.Cm disable .
+.El
+.It Cm link | nolink
+It is convenient to be able to access the current element of a file
+generation set by a fixed name.
+This feature is enabled by
+specifying
+.Cm link
+and disabled using
+.Cm nolink .
+If link is specified, a
+hard link from the current file set element to a file without
+suffix is created.
+When there is already a file with this name and
+the number of links of this file is one, it is renamed appending a
+dot, the letter
+.Cm C ,
+and the pid of the ntpd server process.
+When the
+number of links is greater than one, the file is unlinked.
+This
+allows the current file to be accessed by a constant name.
+.It Cm enable \&| Cm disable
+Enables or disables the recording function.
+.El
+.El
+.El
+.Sh Access Control Support
+The
+.Xr ntpd 8
+daemon implements a general purpose address/mask based restriction
+list.
+The list contains address/match entries sorted first
+by increasing address values and and then by increasing mask values.
+A match occurs when the bitwise AND of the mask and the packet
+source address is equal to the bitwise AND of the mask and
+address in the list.
+The list is searched in order with the
+last match found defining the restriction flags associated
+with the entry.
+Additional information and examples can be found in the
+.Qq Notes on Configuring NTP and Setting up a NTP Subnet
+page
+(available as part of the HTML documentation
+provided in
+.Pa /usr/share/doc/ntp ) .
+.Pp
+The restriction facility was implemented in conformance
+with the access policies for the original NSFnet backbone
+time servers.
+Later the facility was expanded to deflect
+cryptographic and clogging attacks.
+While this facility may
+be useful for keeping unwanted or broken or malicious clients
+from congesting innocent servers, it should not be considered
+an alternative to the NTP authentication facilities.
+Source address based restrictions are easily circumvented
+by a determined cracker.
+.Pp
+Clients can be denied service because they are explicitly
+included in the restrict list created by the restrict command
+or implicitly as the result of cryptographic or rate limit
+violations.
+Cryptographic violations include certificate
+or identity verification failure; rate limit violations generally
+result from defective NTP implementations that send packets
+at abusive rates.
+Some violations cause denied service
+only for the offending packet, others cause denied service
+for a timed period and others cause the denied service for
+an indefinate period.
+When a client or network is denied access
+for an indefinate period, the only way at present to remove
+the restrictions is by restarting the server.
+.Ss The Kiss\-of\-Death Packet
+Ordinarily, packets denied service are simply dropped with no
+further action except incrementing statistics counters.
+Sometimes a
+more proactive response is needed, such as a server message that
+explicitly requests the client to stop sending and leave a message
+for the system operator.
+A special packet format has been created
+for this purpose called the "kiss\-of\-death" (KoD) packet.
+KoD packets have the leap bits set unsynchronized and stratum set
+to zero and the reference identifier field set to a four\-byte
+ASCII code.
+If the
+.Cm noserve
+or
+.Cm notrust
+flag of the matching restrict list entry is set,
+the code is "DENY"; if the
+.Cm limited
+flag is set and the rate limit
+is exceeded, the code is "RATE".
+Finally, if a cryptographic violation occurs, the code is "CRYP".
+.Pp
+A client receiving a KoD performs a set of sanity checks to
+minimize security exposure, then updates the stratum and
+reference identifier peer variables, sets the access
+denied (TEST4) bit in the peer flash variable and sends
+a message to the log.
+As long as the TEST4 bit is set,
+the client will send no further packets to the server.
+The only way at present to recover from this condition is
+to restart the protocol at both the client and server.
+This
+happens automatically at the client when the association times out.
+It will happen at the server only if the server operator cooperates.
+.Ss Access Control Commands
+.Bl -tag -width indent
+.It Xo Ic discard
+.Op Cm average Ar avg
+.Op Cm minimum Ar min
+.Op Cm monitor Ar prob
+.Xc
+Set the parameters of the
+.Cm limited
+facility which protects the server from
+client abuse.
+The
+.Cm average
+subcommand specifies the minimum average packet
+spacing, while the
+.Cm minimum
+subcommand specifies the minimum packet spacing.
+Packets that violate these minima are discarded
+and a kiss\-o'\-death packet returned if enabled.
+The default
+minimum average and minimum are 5 and 2, respectively.
+The monitor subcommand specifies the probability of discard
+for packets that overflow the rate\-control window.
+.It Xo Ic restrict address
+.Op Cm mask Ar mask
+.Op Ar flag ...
+.Xc
+The
+.Ar address
+argument expressed in
+dotted\-quad form is the address of a host or network.
+Alternatively, the
+.Ar address
+argument can be a valid host DNS name.
+The
+.Ar mask
+argument expressed in dotted\-quad form defaults to
+.Cm 255.255.255.255 ,
+meaning that the
+.Ar address
+is treated as the address of an individual host.
+A default entry (address
+.Cm 0.0.0.0 ,
+mask
+.Cm 0.0.0.0 )
+is always included and is always the first entry in the list.
+Note that text string
+.Cm default ,
+with no mask option, may
+be used to indicate the default entry.
+In the current implementation,
+.Cm flag
+always
+restricts access, i.e., an entry with no flags indicates that free
+access to the server is to be given.
+The flags are not orthogonal,
+in that more restrictive flags will often make less restrictive
+ones redundant.
+The flags can generally be classed into two
+categories, those which restrict time service and those which
+restrict informational queries and attempts to do run\-time
+reconfiguration of the server.
+One or more of the following flags
+may be specified:
+.Bl -tag -width indent
+.It Cm ignore
+Deny packets of all kinds, including
+.Xr ntpq 8
+and
+.Xr ntpdc 8
+queries.
+.It Cm kod
+If this flag is set when an access violation occurs, a kiss\-o'\-death
+(KoD) packet is sent.
+KoD packets are rate limited to no more than one
+per second.
+If another KoD packet occurs within one second after the
+last one, the packet is dropped.
+.It Cm limited
+Deny service if the packet spacing violates the lower limits specified
+in the discard command.
+A history of clients is kept using the
+monitoring capability of
+.Xr ntpd 8 .
+Thus, monitoring is always active as
+long as there is a restriction entry with the
+.Cm limited
+flag.
+.It Cm lowpriotrap
+Declare traps set by matching hosts to be low priority.
+The
+number of traps a server can maintain is limited (the current limit
+is 3).
+Traps are usually assigned on a first come, first served
+basis, with later trap requestors being denied service.
+This flag
+modifies the assignment algorithm by allowing low priority traps to
+be overridden by later requests for normal priority traps.
+.It Cm nomodify
+Deny
+.Xr ntpq 8
+and
+.Xr ntpdc 8
+queries which attempt to modify the state of the
+server (i.e., run time reconfiguration).
+Queries which return
+information are permitted.
+.It Cm noquery
+Deny
+.Xr ntpq 8
+and
+.Xr ntpdc 8
+queries.
+Time service is not affected.
+.It Cm nopeer
+Deny packets which would result in mobilizing a new association.
+This
+includes broadcast and symmetric active packets when a configured
+association does not exist.
+It also includes
+.Cm pool
+associations, so if you want to use servers from a
+.Cm pool
+directive and also want to use
+.Cm nopeer
+by default, you'll want a
+.Cm "restrict source ..." line as well that does
+.It not
+include the
+.Cm nopeer
+directive.
+.It Cm noserve
+Deny all packets except
+.Xr ntpq 8
+and
+.Xr ntpdc 8
+queries.
+.It Cm notrap
+Decline to provide mode 6 control message trap service to matching
+hosts.
+The trap service is a subsystem of the ntpdq control message
+protocol which is intended for use by remote event logging programs.
+.It Cm notrust
+Deny service unless the packet is cryptographically authenticated.
+.It Cm ntpport
+This is actually a match algorithm modifier, rather than a
+restriction flag.
+Its presence causes the restriction entry to be
+matched only if the source port in the packet is the standard NTP
+UDP port (123).
+Both
+.Cm ntpport
+and
+.Cm non\-ntpport
+may
+be specified.
+The
+.Cm ntpport
+is considered more specific and
+is sorted later in the list.
+.It Cm version
+Deny packets that do not match the current NTP version.
+.El
+.Pp
+Default restriction list entries with the flags ignore, interface,
+ntpport, for each of the local host's interface addresses are
+inserted into the table at startup to prevent the server
+from attempting to synchronize to its own time.
+A default entry is also always present, though if it is
+otherwise unconfigured; no flags are associated
+with the default entry (i.e., everything besides your own
+NTP server is unrestricted).
+.El
+.Sh Automatic NTP Configuration Options
+.Ss Manycasting
+Manycasting is a automatic discovery and configuration paradigm
+new to NTPv4.
+It is intended as a means for a multicast client
+to troll the nearby network neighborhood to find cooperating
+manycast servers, validate them using cryptographic means
+and evaluate their time values with respect to other servers
+that might be lurking in the vicinity.
+The intended result is that each manycast client mobilizes
+client associations with some number of the "best"
+of the nearby manycast servers, yet automatically reconfigures
+to sustain this number of servers should one or another fail.
+.Pp
+Note that the manycasting paradigm does not coincide
+with the anycast paradigm described in RFC\-1546,
+which is designed to find a single server from a clique
+of servers providing the same service.
+The manycast paradigm is designed to find a plurality
+of redundant servers satisfying defined optimality criteria.
+.Pp
+Manycasting can be used with either symmetric key
+or public key cryptography.
+The public key infrastructure (PKI)
+offers the best protection against compromised keys
+and is generally considered stronger, at least with relatively
+large key sizes.
+It is implemented using the Autokey protocol and
+the OpenSSL cryptographic library available from
+.Li http://www.openssl.org/ .
+The library can also be used with other NTPv4 modes
+as well and is highly recommended, especially for broadcast modes.
+.Pp
+A persistent manycast client association is configured
+using the manycastclient command, which is similar to the
+server command but with a multicast (IPv4 class
+.Cm D
+or IPv6 prefix
+.Cm FF )
+group address.
+The IANA has designated IPv4 address 224.1.1.1
+and IPv6 address FF05::101 (site local) for NTP.
+When more servers are needed, it broadcasts manycast
+client messages to this address at the minimum feasible rate
+and minimum feasible time\-to\-live (TTL) hops, depending
+on how many servers have already been found.
+There can be as many manycast client associations
+as different group address, each one serving as a template
+for a future ephemeral unicast client/server association.
+.Pp
+Manycast servers configured with the
+.Ic manycastserver
+command listen on the specified group address for manycast
+client messages.
+Note the distinction between manycast client,
+which actively broadcasts messages, and manycast server,
+which passively responds to them.
+If a manycast server is
+in scope of the current TTL and is itself synchronized
+to a valid source and operating at a stratum level equal
+to or lower than the manycast client, it replies to the
+manycast client message with an ordinary unicast server message.
+.Pp
+The manycast client receiving this message mobilizes
+an ephemeral client/server association according to the
+matching manycast client template, but only if cryptographically
+authenticated and the server stratum is less than or equal
+to the client stratum.
+Authentication is explicitly required
+and either symmetric key or public key (Autokey) can be used.
+Then, the client polls the server at its unicast address
+in burst mode in order to reliably set the host clock
+and validate the source.
+This normally results
+in a volley of eight client/server at 2\-s intervals
+during which both the synchronization and cryptographic
+protocols run concurrently.
+Following the volley,
+the client runs the NTP intersection and clustering
+algorithms, which act to discard all but the "best"
+associations according to stratum and synchronization
+distance.
+The surviving associations then continue
+in ordinary client/server mode.
+.Pp
+The manycast client polling strategy is designed to reduce
+as much as possible the volume of manycast client messages
+and the effects of implosion due to near\-simultaneous
+arrival of manycast server messages.
+The strategy is determined by the
+.Ic manycastclient ,
+.Ic tos
+and
+.Ic ttl
+configuration commands.
+The manycast poll interval is
+normally eight times the system poll interval,
+which starts out at the
+.Cm minpoll
+value specified in the
+.Ic manycastclient ,
+command and, under normal circumstances, increments to the
+.Cm maxpolll
+value specified in this command.
+Initially, the TTL is
+set at the minimum hops specified by the ttl command.
+At each retransmission the TTL is increased until reaching
+the maximum hops specified by this command or a sufficient
+number client associations have been found.
+Further retransmissions use the same TTL.
+.Pp
+The quality and reliability of the suite of associations
+discovered by the manycast client is determined by the NTP
+mitigation algorithms and the
+.Cm minclock
+and
+.Cm minsane
+values specified in the
+.Ic tos
+configuration command.
+At least
+.Cm minsane
+candidate servers must be available and the mitigation
+algorithms produce at least
+.Cm minclock
+survivors in order to synchronize the clock.
+Byzantine agreement principles require at least four
+candidates in order to correctly discard a single falseticker.
+For legacy purposes,
+.Cm minsane
+defaults to 1 and
+.Cm minclock
+defaults to 3.
+For manycast service
+.Cm minsane
+should be explicitly set to 4, assuming at least that
+number of servers are available.
+.Pp
+If at least
+.Cm minclock
+servers are found, the manycast poll interval is immediately
+set to eight times
+.Cm maxpoll .
+If less than
+.Cm minclock
+servers are found when the TTL has reached the maximum hops,
+the manycast poll interval is doubled.
+For each transmission
+after that, the poll interval is doubled again until
+reaching the maximum of eight times
+.Cm maxpoll .
+Further transmissions use the same poll interval and
+TTL values.
+Note that while all this is going on,
+each client/server association found is operating normally
+it the system poll interval.
+.Pp
+Administratively scoped multicast boundaries are normally
+specified by the network router configuration and,
+in the case of IPv6, the link/site scope prefix.
+By default, the increment for TTL hops is 32 starting
+from 31; however, the
+.Ic ttl
+configuration command can be
+used to modify the values to match the scope rules.
+.Pp
+It is often useful to narrow the range of acceptable
+servers which can be found by manycast client associations.
+Because manycast servers respond only when the client
+stratum is equal to or greater than the server stratum,
+primary (stratum 1) servers fill find only primary servers
+in TTL range, which is probably the most common objective.
+However, unless configured otherwise, all manycast clients
+in TTL range will eventually find all primary servers
+in TTL range, which is probably not the most common
+objective in large networks.
+The
+.Ic tos
+command can be used to modify this behavior.
+Servers with stratum below
+.Cm floor
+or above
+.Cm ceiling
+specified in the
+.Ic tos
+command are strongly discouraged during the selection
+process; however, these servers may be temporally
+accepted if the number of servers within TTL range is
+less than
+.Cm minclock .
+.Pp
+The above actions occur for each manycast client message,
+which repeats at the designated poll interval.
+However, once the ephemeral client association is mobilized,
+subsequent manycast server replies are discarded,
+since that would result in a duplicate association.
+If during a poll interval the number of client associations
+falls below
+.Cm minclock ,
+all manycast client prototype associations are reset
+to the initial poll interval and TTL hops and operation
+resumes from the beginning.
+It is important to avoid
+frequent manycast client messages, since each one requires
+all manycast servers in TTL range to respond.
+The result could well be an implosion, either minor or major,
+depending on the number of servers in range.
+The recommended value for
+.Cm maxpoll
+is 12 (4,096 s).
+.Pp
+It is possible and frequently useful to configure a host
+as both manycast client and manycast server.
+A number of hosts configured this way and sharing a common
+group address will automatically organize themselves
+in an optimum configuration based on stratum and
+synchronization distance.
+For example, consider an NTP
+subnet of two primary servers and a hundred or more
+dependent clients.
+With two exceptions, all servers
+and clients have identical configuration files including both
+.Ic multicastclient
+and
+.Ic multicastserver
+commands using, for instance, multicast group address
+239.1.1.1.
+The only exception is that each primary server
+configuration file must include commands for the primary
+reference source such as a GPS receiver.
+.Pp
+The remaining configuration files for all secondary
+servers and clients have the same contents, except for the
+.Ic tos
+command, which is specific for each stratum level.
+For stratum 1 and stratum 2 servers, that command is
+not necessary.
+For stratum 3 and above servers the
+.Cm floor
+value is set to the intended stratum number.
+Thus, all stratum 3 configuration files are identical,
+all stratum 4 files are identical and so forth.
+.Pp
+Once operations have stabilized in this scenario,
+the primary servers will find the primary reference source
+and each other, since they both operate at the same
+stratum (1), but not with any secondary server or client,
+since these operate at a higher stratum.
+The secondary
+servers will find the servers at the same stratum level.
+If one of the primary servers loses its GPS receiver,
+it will continue to operate as a client and other clients
+will time out the corresponding association and
+re\-associate accordingly.
+.Pp
+Some administrators prefer to avoid running
+.Xr ntpd 8
+continuously and run either
+.Xr sntp 8
+or
+.Xr ntpd 8
+.Fl q
+as a cron job.
+In either case the servers must be
+configured in advance and the program fails if none are
+available when the cron job runs.
+A really slick
+application of manycast is with
+.Xr ntpd 8
+.Fl q .
+The program wakes up, scans the local landscape looking
+for the usual suspects, selects the best from among
+the rascals, sets the clock and then departs.
+Servers do not have to be configured in advance and
+all clients throughout the network can have the same
+configuration file.
+.Ss Manycast Interactions with Autokey
+Each time a manycast client sends a client mode packet
+to a multicast group address, all manycast servers
+in scope generate a reply including the host name
+and status word.
+The manycast clients then run
+the Autokey protocol, which collects and verifies
+all certificates involved.
+Following the burst interval
+all but three survivors are cast off,
+but the certificates remain in the local cache.
+It often happens that several complete signing trails
+from the client to the primary servers are collected in this way.
+.Pp
+About once an hour or less often if the poll interval
+exceeds this, the client regenerates the Autokey key list.
+This is in general transparent in client/server mode.
+However, about once per day the server private value
+used to generate cookies is refreshed along with all
+manycast client associations.
+In this case all
+cryptographic values including certificates is refreshed.
+If a new certificate has been generated since
+the last refresh epoch, it will automatically revoke
+all prior certificates that happen to be in the
+certificate cache.
+At the same time, the manycast
+scheme starts all over from the beginning and
+the expanding ring shrinks to the minimum and increments
+from there while collecting all servers in scope.
+.Ss Manycast Options
+.Bl -tag -width indent
+.It Xo Ic tos
+.Oo
+.Cm ceiling Ar ceiling |
+.Cm cohort { 0 | 1 } |
+.Cm floor Ar floor |
+.Cm minclock Ar minclock |
+.Cm minsane Ar minsane
+.Oc
+.Xc
+This command affects the clock selection and clustering
+algorithms.
+It can be used to select the quality and
+quantity of peers used to synchronize the system clock
+and is most useful in manycast mode.
+The variables operate
+as follows:
+.Bl -tag -width indent
+.It Cm ceiling Ar ceiling
+Peers with strata above
+.Cm ceiling
+will be discarded if there are at least
+.Cm minclock
+peers remaining.
+This value defaults to 15, but can be changed
+to any number from 1 to 15.
+.It Cm cohort Bro 0 | 1 Brc
+This is a binary flag which enables (0) or disables (1)
+manycast server replies to manycast clients with the same
+stratum level.
+This is useful to reduce implosions where
+large numbers of clients with the same stratum level
+are present.
+The default is to enable these replies.
+.It Cm floor Ar floor
+Peers with strata below
+.Cm floor
+will be discarded if there are at least
+.Cm minclock
+peers remaining.
+This value defaults to 1, but can be changed
+to any number from 1 to 15.
+.It Cm minclock Ar minclock
+The clustering algorithm repeatedly casts out outlier
+associations until no more than
+.Cm minclock
+associations remain.
+This value defaults to 3,
+but can be changed to any number from 1 to the number of
+configured sources.
+.It Cm minsane Ar minsane
+This is the minimum number of candidates available
+to the clock selection algorithm in order to produce
+one or more truechimers for the clustering algorithm.
+If fewer than this number are available, the clock is
+undisciplined and allowed to run free.
+The default is 1
+for legacy purposes.
+However, according to principles of
+Byzantine agreement,
+.Cm minsane
+should be at least 4 in order to detect and discard
+a single falseticker.
+.El
+.It Cm ttl Ar hop ...
+This command specifies a list of TTL values in increasing
+order, up to 8 values can be specified.
+In manycast mode these values are used in turn
+in an expanding\-ring search.
+The default is eight
+multiples of 32 starting at 31.
+.El
+.Sh Reference Clock Support
+The NTP Version 4 daemon supports some three dozen different radio,
+satellite and modem reference clocks plus a special pseudo\-clock
+used for backup or when no other clock source is available.
+Detailed descriptions of individual device drivers and options can
+be found in the
+.Qq Reference Clock Drivers
+page
+(available as part of the HTML documentation
+provided in
+.Pa /usr/share/doc/ntp ) .
+Additional information can be found in the pages linked
+there, including the
+.Qq Debugging Hints for Reference Clock Drivers
+and
+.Qq How To Write a Reference Clock Driver
+pages
+(available as part of the HTML documentation
+provided in
+.Pa /usr/share/doc/ntp ) .
+In addition, support for a PPS
+signal is available as described in the
+.Qq Pulse\-per\-second (PPS) Signal Interfacing
+page
+(available as part of the HTML documentation
+provided in
+.Pa /usr/share/doc/ntp ) .
+Many
+drivers support special line discipline/streams modules which can
+significantly improve the accuracy using the driver.
+These are
+described in the
+.Qq Line Disciplines and Streams Drivers
+page
+(available as part of the HTML documentation
+provided in
+.Pa /usr/share/doc/ntp ) .
+.Pp
+A reference clock will generally (though not always) be a radio
+timecode receiver which is synchronized to a source of standard
+time such as the services offered by the NRC in Canada and NIST and
+USNO in the US.
+The interface between the computer and the timecode
+receiver is device dependent, but is usually a serial port.
+A
+device driver specific to each reference clock must be selected and
+compiled in the distribution; however, most common radio, satellite
+and modem clocks are included by default.
+Note that an attempt to
+configure a reference clock when the driver has not been compiled
+or the hardware port has not been appropriately configured results
+in a scalding remark to the system log file, but is otherwise non
+hazardous.
+.Pp
+For the purposes of configuration,
+.Xr ntpd 8
+treats
+reference clocks in a manner analogous to normal NTP peers as much
+as possible.
+Reference clocks are identified by a syntactically
+correct but invalid IP address, in order to distinguish them from
+normal NTP peers.
+Reference clock addresses are of the form
+.Sm off
+.Li 127.127. Ar t . Ar u ,
+.Sm on
+where
+.Ar t
+is an integer
+denoting the clock type and
+.Ar u
+indicates the unit
+number in the range 0\-3.
+While it may seem overkill, it is in fact
+sometimes useful to configure multiple reference clocks of the same
+type, in which case the unit numbers must be unique.
+.Pp
+The
+.Ic server
+command is used to configure a reference
+clock, where the
+.Ar address
+argument in that command
+is the clock address.
+The
+.Cm key ,
+.Cm version
+and
+.Cm ttl
+options are not used for reference clock support.
+The
+.Cm mode
+option is added for reference clock support, as
+described below.
+The
+.Cm prefer
+option can be useful to
+persuade the server to cherish a reference clock with somewhat more
+enthusiasm than other reference clocks or peers.
+Further
+information on this option can be found in the
+.Qq Mitigation Rules and the prefer Keyword
+(available as part of the HTML documentation
+provided in
+.Pa /usr/share/doc/ntp )
+page.
+The
+.Cm minpoll
+and
+.Cm maxpoll
+options have
+meaning only for selected clock drivers.
+See the individual clock
+driver document pages for additional information.
+.Pp
+The
+.Ic fudge
+command is used to provide additional
+information for individual clock drivers and normally follows
+immediately after the
+.Ic server
+command.
+The
+.Ar address
+argument specifies the clock address.
+The
+.Cm refid
+and
+.Cm stratum
+options can be used to
+override the defaults for the device.
+There are two optional
+device\-dependent time offsets and four flags that can be included
+in the
+.Ic fudge
+command as well.
+.Pp
+The stratum number of a reference clock is by default zero.
+Since the
+.Xr ntpd 8
+daemon adds one to the stratum of each
+peer, a primary server ordinarily displays an external stratum of
+one.
+In order to provide engineered backups, it is often useful to
+specify the reference clock stratum as greater than zero.
+The
+.Cm stratum
+option is used for this purpose.
+Also, in cases
+involving both a reference clock and a pulse\-per\-second (PPS)
+discipline signal, it is useful to specify the reference clock
+identifier as other than the default, depending on the driver.
+The
+.Cm refid
+option is used for this purpose.
+Except where noted,
+these options apply to all clock drivers.
+.Ss Reference Clock Commands
+.Bl -tag -width indent
+.It Xo Ic server
+.Sm off
+.Li 127.127. Ar t . Ar u
+.Sm on
+.Op Cm prefer
+.Op Cm mode Ar int
+.Op Cm minpoll Ar int
+.Op Cm maxpoll Ar int
+.Xc
+This command can be used to configure reference clocks in
+special ways.
+The options are interpreted as follows:
+.Bl -tag -width indent
+.It Cm prefer
+Marks the reference clock as preferred.
+All other things being
+equal, this host will be chosen for synchronization among a set of
+correctly operating hosts.
+See the
+.Qq Mitigation Rules and the prefer Keyword
+page
+(available as part of the HTML documentation
+provided in
+.Pa /usr/share/doc/ntp )
+for further information.
+.It Cm mode Ar int
+Specifies a mode number which is interpreted in a
+device\-specific fashion.
+For instance, it selects a dialing
+protocol in the ACTS driver and a device subtype in the
+parse
+drivers.
+.It Cm minpoll Ar int
+.It Cm maxpoll Ar int
+These options specify the minimum and maximum polling interval
+for reference clock messages, as a power of 2 in seconds
+For
+most directly connected reference clocks, both
+.Cm minpoll
+and
+.Cm maxpoll
+default to 6 (64 s).
+For modem reference clocks,
+.Cm minpoll
+defaults to 10 (17.1 m) and
+.Cm maxpoll
+defaults to 14 (4.5 h).
+The allowable range is 4 (16 s) to 17 (36.4 h) inclusive.
+.El
+.It Xo Ic fudge
+.Sm off
+.Li 127.127. Ar t . Ar u
+.Sm on
+.Op Cm time1 Ar sec
+.Op Cm time2 Ar sec
+.Op Cm stratum Ar int
+.Op Cm refid Ar string
+.Op Cm mode Ar int
+.Op Cm flag1 Cm 0 \&| Cm 1
+.Op Cm flag2 Cm 0 \&| Cm 1
+.Op Cm flag3 Cm 0 \&| Cm 1
+.Op Cm flag4 Cm 0 \&| Cm 1
+.Xc
+This command can be used to configure reference clocks in
+special ways.
+It must immediately follow the
+.Ic server
+command which configures the driver.
+Note that the same capability
+is possible at run time using the
+.Xr ntpdc 8
+program.
+The options are interpreted as
+follows:
+.Bl -tag -width indent
+.It Cm time1 Ar sec
+Specifies a constant to be added to the time offset produced by
+the driver, a fixed\-point decimal number in seconds.
+This is used
+as a calibration constant to adjust the nominal time offset of a
+particular clock to agree with an external standard, such as a
+precision PPS signal.
+It also provides a way to correct a
+systematic error or bias due to serial port or operating system
+latencies, different cable lengths or receiver internal delay.
+The
+specified offset is in addition to the propagation delay provided
+by other means, such as internal DIPswitches.
+Where a calibration
+for an individual system and driver is available, an approximate
+correction is noted in the driver documentation pages.
+Note: in order to facilitate calibration when more than one
+radio clock or PPS signal is supported, a special calibration
+feature is available.
+It takes the form of an argument to the
+.Ic enable
+command described in
+.Sx Miscellaneous Options
+page and operates as described in the
+.Qq Reference Clock Drivers
+page
+(available as part of the HTML documentation
+provided in
+.Pa /usr/share/doc/ntp ) .
+.It Cm time2 Ar secs
+Specifies a fixed\-point decimal number in seconds, which is
+interpreted in a driver\-dependent way.
+See the descriptions of
+specific drivers in the
+.Qq Reference Clock Drivers
+page
+(available as part of the HTML documentation
+provided in
+.Pa /usr/share/doc/ntp ) .
+.It Cm stratum Ar int
+Specifies the stratum number assigned to the driver, an integer
+between 0 and 15.
+This number overrides the default stratum number
+ordinarily assigned by the driver itself, usually zero.
+.It Cm refid Ar string
+Specifies an ASCII string of from one to four characters which
+defines the reference identifier used by the driver.
+This string
+overrides the default identifier ordinarily assigned by the driver
+itself.
+.It Cm mode Ar int
+Specifies a mode number which is interpreted in a
+device\-specific fashion.
+For instance, it selects a dialing
+protocol in the ACTS driver and a device subtype in the
+parse
+drivers.
+.It Cm flag1 Cm 0 \&| Cm 1
+.It Cm flag2 Cm 0 \&| Cm 1
+.It Cm flag3 Cm 0 \&| Cm 1
+.It Cm flag4 Cm 0 \&| Cm 1
+These four flags are used for customizing the clock driver.
+The
+interpretation of these values, and whether they are used at all,
+is a function of the particular clock driver.
+However, by
+convention
+.Cm flag4
+is used to enable recording monitoring
+data to the
+.Cm clockstats
+file configured with the
+.Ic filegen
+command.
+Further information on the
+.Ic filegen
+command can be found in
+.Sx Monitoring Options .
+.El
+.El
+.Sh Miscellaneous Options
+.Bl -tag -width indent
+.It Ic broadcastdelay Ar seconds
+The broadcast and multicast modes require a special calibration
+to determine the network delay between the local and remote
+servers.
+Ordinarily, this is done automatically by the initial
+protocol exchanges between the client and server.
+In some cases,
+the calibration procedure may fail due to network or server access
+controls, for example.
+This command specifies the default delay to
+be used under these circumstances.
+Typically (for Ethernet), a
+number between 0.003 and 0.007 seconds is appropriate.
+The default
+when this command is not used is 0.004 seconds.
+.It Ic calldelay Ar delay
+This option controls the delay in seconds between the first and second
+packets sent in burst or iburst mode to allow additional time for a modem
+or ISDN call to complete.
+.It Ic driftfile Ar driftfile
+This command specifies the complete path and name of the file used to
+record the frequency of the local clock oscillator.
+This is the same
+operation as the
+.Fl f
+command line option.
+If the file exists, it is read at
+startup in order to set the initial frequency and then updated once per
+hour with the current frequency computed by the daemon.
+If the file name is
+specified, but the file itself does not exist, the starts with an initial
+frequency of zero and creates the file when writing it for the first time.
+If this command is not given, the daemon will always start with an initial
+frequency of zero.
+.Pp
+The file format consists of a single line containing a single
+floating point number, which records the frequency offset measured
+in parts\-per\-million (PPM).
+The file is updated by first writing
+the current drift value into a temporary file and then renaming
+this file to replace the old version.
+This implies that
+.Xr ntpd 8
+must have write permission for the directory the
+drift file is located in, and that file system links, symbolic or
+otherwise, should be avoided.
+.It Ic dscp Ar value
+This option specifies the Differentiated Services Control Point (DSCP) value,
+a 6\-bit code. The default value is 46, signifying Expedited Forwarding.
+.It Xo Ic enable
+.Oo
+.Cm auth | Cm bclient |
+.Cm calibrate | Cm kernel |
+.Cm mode7 | monitor |
+.Cm ntp | Cm stats
+.Oc
+.Xc
+.It Xo Ic disable
+.Oo
+.Cm auth | Cm bclient |
+.Cm calibrate | Cm kernel |
+.Cm mode7 | monitor |
+.Cm ntp | Cm stats
+.Oc
+.Xc
+Provides a way to enable or disable various server options.
+Flags not mentioned are unaffected.
+Note that all of these flags
+can be controlled remotely using the
+.Xr ntpdc 8
+utility program.
+.Bl -tag -width indent
+.It Cm auth
+Enables the server to synchronize with unconfigured peers only if the
+peer has been correctly authenticated using either public key or
+private key cryptography.
+The default for this flag is
+.Ic enable .
+.It Cm bclient
+Enables the server to listen for a message from a broadcast or
+multicast server, as in the
+.Ic multicastclient
+command with default
+address.
+The default for this flag is
+.Ic disable .
+.It Cm calibrate
+Enables the calibrate feature for reference clocks.
+The default for
+this flag is
+.Ic disable .
+.It Cm kernel
+Enables the kernel time discipline, if available.
+The default for this
+flag is
+.Ic enable
+if support is available, otherwise
+.Ic disable .
+.It Cm mode7
+Enables processing of NTP mode 7 implementation\-specific requests
+which are used by the deprecated
+.Xr ntpdc 8
+program.
+The default for this flag is disable.
+This flag is excluded from runtime configuration using
+.Xr ntpq 8 .
+The
+.Xr ntpq 8
+program provides the same capabilities as
+.Xr ntpdc 8
+using standard mode 6 requests.
+.It Cm monitor
+Enables the monitoring facility.
+See the
+.Xr ntpdc 8
+program
+and the
+.Ic monlist
+command or further information.
+The
+default for this flag is
+.Ic enable .
+.It Cm ntp
+Enables time and frequency discipline.
+In effect, this switch opens and
+closes the feedback loop, which is useful for testing.
+The default for
+this flag is
+.Ic enable .
+.It Cm stats
+Enables the statistics facility.
+See the
+.Sx Monitoring Options
+section for further information.
+The default for this flag is
+.Ic disable .
+.El
+.It Ic includefile Ar includefile
+This command allows additional configuration commands
+to be included from a separate file.
+Include files may
+be nested to a depth of five; upon reaching the end of any
+include file, command processing resumes in the previous
+configuration file.
+This option is useful for sites that run
+.Xr ntpd 8
+on multiple hosts, with (mostly) common options (e.g., a
+restriction list).
+.It Ic leapsmearinterval Ar seconds
+This EXPERIMENTAL option is only available if
+.Xr ntpd 8
+was built with the
+.Cm \-\-enable\-leap\-smear
+option to the
+.Cm configure
+script.
+It specifies the interval over which a leap second correction will be applied.
+Recommended values for this option are between
+7200 (2 hours) and 86400 (24 hours).
+.Sy DO NOT USE THIS OPTION ON PUBLIC\-ACCESS SERVERS!
+See http://bugs.ntp.org/2855 for more information.
+.It Ic logconfig Ar configkeyword
+This command controls the amount and type of output written to
+the system
+.Xr syslog 3
+facility or the alternate
+.Ic logfile
+log file.
+By default, all output is turned on.
+All
+.Ar configkeyword
+keywords can be prefixed with
+.Ql = ,
+.Ql +
+and
+.Ql \- ,
+where
+.Ql =
+sets the
+.Xr syslog 3
+priority mask,
+.Ql +
+adds and
+.Ql \-
+removes
+messages.
+.Xr syslog 3
+messages can be controlled in four
+classes
+.Po
+.Cm clock ,
+.Cm peer ,
+.Cm sys
+and
+.Cm sync
+.Pc .
+Within these classes four types of messages can be
+controlled: informational messages
+.Po
+.Cm info
+.Pc ,
+event messages
+.Po
+.Cm events
+.Pc ,
+statistics messages
+.Po
+.Cm statistics
+.Pc
+and
+status messages
+.Po
+.Cm status
+.Pc .
+.Pp
+Configuration keywords are formed by concatenating the message class with
+the event class.
+The
+.Cm all
+prefix can be used instead of a message class.
+A
+message class may also be followed by the
+.Cm all
+keyword to enable/disable all
+messages of the respective message class.Thus, a minimal log configuration
+could look like this:
+.Bd -literal
+logconfig =syncstatus +sysevents
+.Ed
+.Pp
+This would just list the synchronizations state of
+.Xr ntpd 8
+and the major system events.
+For a simple reference server, the
+following minimum message configuration could be useful:
+.Bd -literal
+logconfig =syncall +clockall
+.Ed
+.Pp
+This configuration will list all clock information and
+synchronization information.
+All other events and messages about
+peers, system events and so on is suppressed.
+.It Ic logfile Ar logfile
+This command specifies the location of an alternate log file to
+be used instead of the default system
+.Xr syslog 3
+facility.
+This is the same operation as the \-l command line option.
+.It Ic setvar Ar variable Op Cm default
+This command adds an additional system variable.
+These
+variables can be used to distribute additional information such as
+the access policy.
+If the variable of the form
+.Sm off
+.Va name = Ar value
+.Sm on
+is followed by the
+.Cm default
+keyword, the
+variable will be listed as part of the default system variables
+.Po
+.Xr ntpq 8
+.Ic rv
+command
+.Pc ) .
+These additional variables serve
+informational purposes only.
+They are not related to the protocol
+other that they can be listed.
+The known protocol variables will
+always override any variables defined via the
+.Ic setvar
+mechanism.
+There are three special variables that contain the names
+of all variable of the same group.
+The
+.Va sys_var_list
+holds
+the names of all system variables.
+The
+.Va peer_var_list
+holds
+the names of all peer variables and the
+.Va clock_var_list
+holds the names of the reference clock variables.
+.It Xo Ic tinker
+.Oo
+.Cm allan Ar allan |
+.Cm dispersion Ar dispersion |
+.Cm freq Ar freq |
+.Cm huffpuff Ar huffpuff |
+.Cm panic Ar panic |
+.Cm step Ar step |
+.Cm stepback Ar stepback |
+.Cm stepfwd Ar stepfwd |
+.Cm stepout Ar stepout
+.Oc
+.Xc
+This command can be used to alter several system variables in
+very exceptional circumstances.
+It should occur in the
+configuration file before any other configuration options.
+The
+default values of these variables have been carefully optimized for
+a wide range of network speeds and reliability expectations.
+In
+general, they interact in intricate ways that are hard to predict
+and some combinations can result in some very nasty behavior.
+Very
+rarely is it necessary to change the default values; but, some
+folks cannot resist twisting the knobs anyway and this command is
+for them.
+Emphasis added: twisters are on their own and can expect
+no help from the support group.
+.Pp
+The variables operate as follows:
+.Bl -tag -width indent
+.It Cm allan Ar allan
+The argument becomes the new value for the minimum Allan
+intercept, which is a parameter of the PLL/FLL clock discipline
+algorithm.
+The value in log2 seconds defaults to 7 (1024 s), which is also the lower
+limit.
+.It Cm dispersion Ar dispersion
+The argument becomes the new value for the dispersion increase rate,
+normally .000015 s/s.
+.It Cm freq Ar freq
+The argument becomes the initial value of the frequency offset in
+parts\-per\-million.
+This overrides the value in the frequency file, if
+present, and avoids the initial training state if it is not.
+.It Cm huffpuff Ar huffpuff
+The argument becomes the new value for the experimental
+huff\-n'\-puff filter span, which determines the most recent interval
+the algorithm will search for a minimum delay.
+The lower limit is
+900 s (15 m), but a more reasonable value is 7200 (2 hours).
+There
+is no default, since the filter is not enabled unless this command
+is given.
+.It Cm panic Ar panic
+The argument is the panic threshold, normally 1000 s.
+If set to zero,
+the panic sanity check is disabled and a clock offset of any value will
+be accepted.
+.It Cm step Ar step
+The argument is the step threshold, which by default is 0.128 s.
+It can
+be set to any positive number in seconds.
+If set to zero, step
+adjustments will never occur.
+Note: The kernel time discipline is
+disabled if the step threshold is set to zero or greater than the
+default.
+.It Cm stepback Ar stepback
+The argument is the step threshold for the backward direction,
+which by default is 0.128 s.
+It can
+be set to any positive number in seconds.
+If both the forward and backward step thresholds are set to zero, step
+adjustments will never occur.
+Note: The kernel time discipline is
+disabled if
+each direction of step threshold are either
+set to zero or greater than .5 second.
+.It Cm stepfwd Ar stepfwd
+As for stepback, but for the forward direction.
+.It Cm stepout Ar stepout
+The argument is the stepout timeout, which by default is 900 s.
+It can
+be set to any positive number in seconds.
+If set to zero, the stepout
+pulses will not be suppressed.
+.El
+.It Xo Ic rlimit
+.Oo
+.Cm memlock Ar Nmegabytes |
+.Cm stacksize Ar N4kPages
+.Cm filenum Ar Nfiledescriptors
+.Oc
+.Xc
+.Bl -tag -width indent
+.It Cm memlock Ar Nmegabytes
+Specify the number of megabytes of memory that should be
+allocated and locked.
+Probably only available under Linux, this option may be useful
+when dropping root (the
+.Fl i
+option).
+The default is 32 megabytes on non\-Linux machines, and \-1 under Linux.
+-1 means "do not lock the process into memory".
+0 means "lock whatever memory the process wants into memory".
+.It Cm stacksize Ar N4kPages
+Specifies the maximum size of the process stack on systems with the
+.Fn mlockall
+function.
+Defaults to 50 4k pages (200 4k pages in OpenBSD).
+.It Cm filenum Ar Nfiledescriptors
+Specifies the maximum number of file descriptors ntpd may have open at once. Defaults to the system default.
+.El
+.It Xo Ic trap Ar host_address
+.Op Cm port Ar port_number
+.Op Cm interface Ar interface_address
+.Xc
+This command configures a trap receiver at the given host
+address and port number for sending messages with the specified
+local interface address.
+If the port number is unspecified, a value
+of 18447 is used.
+If the interface address is not specified, the
+message is sent with a source address of the local interface the
+message is sent through.
+Note that on a multihomed host the
+interface used may vary from time to time with routing changes.
+.Pp
+The trap receiver will generally log event messages and other
+information from the server in a log file.
+While such monitor
+programs may also request their own trap dynamically, configuring a
+trap receiver will ensure that no messages are lost when the server
+is started.
+.It Cm hop Ar ...
+This command specifies a list of TTL values in increasing order, up to 8
+values can be specified.
+In manycast mode these values are used in turn in
+an expanding\-ring search.
+The default is eight multiples of 32 starting at
+31.
+.El
+.Sh "OPTIONS"
+.Bl -tag
+.It Fl \-help
+Display usage information and exit.
+.It Fl \-more\-help
+Pass the extended usage information through a pager.
+.It Fl \-version Op Brq Ar v|c|n
+Output version of program and exit. The default mode is `v', a simple
+version. The `c' mode will print copyright information and `n' will
+print the full copyright notice.
+.El
+.Sh "OPTION PRESETS"
+Any option that is not marked as \fInot presettable\fP may be preset
+by loading values from environment variables named:
+.nf
+ \fBNTP_CONF_<option\-name>\fP or \fBNTP_CONF\fP
+.fi
+.ad
+.Sh "ENVIRONMENT"
+See \fBOPTION PRESETS\fP for configuration environment variables.
+.Sh FILES
+.Bl -tag -width /etc/ntp.drift -compact
+.It Pa /etc/ntp.conf
+the default name of the configuration file
+.It Pa ntp.keys
+private MD5 keys
+.It Pa ntpkey
+RSA private key
+.It Pa ntpkey_ Ns Ar host
+RSA public key
+.It Pa ntp_dh
+Diffie\-Hellman agreement parameters
+.El
+.Sh "EXIT STATUS"
+One of the following exit values will be returned:
+.Bl -tag
+.It 0 " (EXIT_SUCCESS)"
+Successful program execution.
+.It 1 " (EXIT_FAILURE)"
+The operation failed or the command syntax was not valid.
+.It 70 " (EX_SOFTWARE)"
+libopts had an internal operational error. Please report
+it to autogen\-users@lists.sourceforge.net. Thank you.
+.El
+.Sh "SEE ALSO"
+.Xr ntpd 8 ,
+.Xr ntpdc 8 ,
+.Xr ntpq 8
+.Pp
+In addition to the manual pages provided,
+comprehensive documentation is available on the world wide web
+at
+.Li http://www.ntp.org/ .
+A snapshot of this documentation is available in HTML format in
+.Pa /usr/share/doc/ntp .
+.Rs
+.%A David L. Mills
+.%T Network Time Protocol (Version 4)
+.%O RFC5905
+.Re
+.Sh "AUTHORS"
+The University of Delaware and Network Time Foundation
+.Sh "COPYRIGHT"
+Copyright (C) 1992\-2015 The University of Delaware and Network Time Foundation all rights reserved.
+This program is released under the terms of the NTP license, <http://ntp.org/license>.
+.Sh BUGS
+The syntax checking is not picky; some combinations of
+ridiculous and even hilarious options and modes may not be
+detected.
+.Pp
+The
+.Pa ntpkey_ Ns Ar host
+files are really digital
+certificates.
+These should be obtained via secure directory
+services when they become universally available.
+.Pp
+Please send bug reports to: http://bugs.ntp.org, bugs@ntp.org
+.Sh NOTES
+This document was derived from FreeBSD.
+.Pp
+This manual page was \fIAutoGen\fP\-erated from the \fBntp.conf\fP
+option definitions.
diff --git a/usr.sbin/ntp/doc/ntp.keys.5 b/usr.sbin/ntp/doc/ntp.keys.5
new file mode 100644
index 0000000..04dfbcd
--- /dev/null
+++ b/usr.sbin/ntp/doc/ntp.keys.5
@@ -0,0 +1,160 @@
+.Dd January 7 2016
+.Dt NTP_KEYS 5 File Formats
+.Os SunOS 5.10
+.\" EDIT THIS FILE WITH CAUTION (ntp.mdoc)
+.\"
+.\" $FreeBSD$
+.\"
+.\" It has been AutoGen-ed January 7, 2016 at 11:31:00 PM by AutoGen 5.18.5
+.\" From the definitions ntp.keys.def
+.\" and the template file agmdoc-file.tpl
+.Sh NAME
+.Nm ntp.keys
+.Nd NTP symmetric key file format
+
+.Sh NAME
+.Nm ntp.keys
+.Nd NTP symmetric key file format
+.Sh SYNOPSIS
+.Nm
+.Op Fl \-option\-name
+.Op Fl \-option\-name Ar value
+.Pp
+All arguments must be options.
+.Pp
+.Sh DESCRIPTION
+This document describes the format of an NTP symmetric key file.
+For a description of the use of this type of file, see the
+.Qq Authentication Support
+section of the
+.Xr ntp.conf 5
+page.
+.Pp
+.Xr ntpd 8
+reads its keys from a file specified using the
+.Fl k
+command line option or the
+.Ic keys
+statement in the configuration file.
+While key number 0 is fixed by the NTP standard
+(as 56 zero bits)
+and may not be changed,
+one or more keys numbered between 1 and 65534
+may be arbitrarily set in the keys file.
+.Pp
+The key file uses the same comment conventions
+as the configuration file.
+Key entries use a fixed format of the form
+.Pp
+.D1 Ar keyno type key
+.Pp
+where
+.Ar keyno
+is a positive integer (between 1 and 65534),
+.Ar type
+is the message digest algorithm,
+and
+.Ar key
+is the key itself.
+.Pp
+The
+.Ar key
+may be given in a format
+controlled by the
+.Ar type
+field.
+The
+.Ar type
+.Li MD5
+is always supported.
+If
+.Li ntpd
+was built with the OpenSSL library
+then any digest library supported by that library may be specified.
+However, if compliance with FIPS 140\-2 is required the
+.Ar type
+must be either
+.Li SHA
+or
+.Li SHA1 .
+.Pp
+What follows are some key types, and corresponding formats:
+.Pp
+.Bl -tag -width RMD160 -compact
+.It Li MD5
+The key is 1 to 16 printable characters terminated by
+an EOL,
+whitespace,
+or
+a
+.Li #
+(which is the "start of comment" character).
+.Pp
+.It Li SHA
+.It Li SHA1
+.It Li RMD160
+The key is a hex\-encoded ASCII string of 40 characters,
+which is truncated as necessary.
+.El
+.Pp
+Note that the keys used by the
+.Xr ntpq 8
+and
+.Xr ntpdc 8
+programs are checked against passwords
+requested by the programs and entered by hand,
+so it is generally appropriate to specify these keys in ASCII format.
+.Sh "OPTIONS"
+.Bl -tag
+.It Fl \-help
+Display usage information and exit.
+.It Fl \-more\-help
+Pass the extended usage information through a pager.
+.It Fl \-version Op Brq Ar v|c|n
+Output version of program and exit. The default mode is `v', a simple
+version. The `c' mode will print copyright information and `n' will
+print the full copyright notice.
+.El
+.Sh "OPTION PRESETS"
+Any option that is not marked as \fInot presettable\fP may be preset
+by loading values from environment variables named:
+.nf
+ \fBNTP_KEYS_<option\-name>\fP or \fBNTP_KEYS\fP
+.fi
+.ad
+.Sh "ENVIRONMENT"
+See \fBOPTION PRESETS\fP for configuration environment variables.
+.Sh FILES
+.Bl -tag -width /etc/ntp.keys -compact
+.It Pa /etc/ntp.keys
+the default name of the configuration file
+.El
+.Sh "EXIT STATUS"
+One of the following exit values will be returned:
+.Bl -tag
+.It 0 " (EXIT_SUCCESS)"
+Successful program execution.
+.It 1 " (EXIT_FAILURE)"
+The operation failed or the command syntax was not valid.
+.It 70 " (EX_SOFTWARE)"
+libopts had an internal operational error. Please report
+it to autogen\-users@lists.sourceforge.net. Thank you.
+.El
+.Sh "SEE ALSO"
+.Xr ntp.conf 5 ,
+.Xr ntpd 8 ,
+.Xr ntpdate 8 ,
+.Xr ntpdc 8 ,
+.Xr sntp 8
+.Sh "AUTHORS"
+The University of Delaware and Network Time Foundation
+.Sh "COPYRIGHT"
+Copyright (C) 1992\-2015 The University of Delaware and Network Time Foundation all rights reserved.
+This program is released under the terms of the NTP license, <http://ntp.org/license>.
+.Sh "BUGS"
+Please send bug reports to: http://bugs.ntp.org, bugs@ntp.org
+.Sh NOTES
+This document was derived from FreeBSD.
+.Pp
+This manual page was \fIAutoGen\fP\-erated from the \fBntp.keys\fP
+option definitions.
diff --git a/usr.sbin/ntp/doc/ntpd.8 b/usr.sbin/ntp/doc/ntpd.8
new file mode 100644
index 0000000..70ab88e
--- /dev/null
+++ b/usr.sbin/ntp/doc/ntpd.8
@@ -0,0 +1,910 @@
+.Dd January 7 2016
+.Dt NTPD 8 User Commands
+.Os
+.\" EDIT THIS FILE WITH CAUTION (ntpd-opts.mdoc)
+.\"
+.\" $FreeBSD$
+.\"
+.\" It has been AutoGen-ed January 7, 2016 at 11:31:02 PM by AutoGen 5.18.5
+.\" From the definitions ntpd-opts.def
+.\" and the template file agmdoc-cmd.tpl
+.Sh NAME
+.Nm ntpd
+.Nd NTP daemon program
+.Sh SYNOPSIS
+.Nm
+.\" Mixture of short (flag) options and long options
+.Op Fl flags
+.Op Fl flag Op Ar value
+.Op Fl \-option\-name Ns Oo Oo Ns "=| " Oc Ns Ar value Oc
+[ <server1> ... <serverN> ]
+.Pp
+.Sh DESCRIPTION
+The
+.Nm
+utility is an operating system daemon which sets
+and maintains the system time of day in synchronism with Internet
+standard time servers.
+It is a complete implementation of the
+Network Time Protocol (NTP) version 4, as defined by RFC\-5905,
+but also retains compatibility with
+version 3, as defined by RFC\-1305, and versions 1
+and 2, as defined by RFC\-1059 and RFC\-1119, respectively.
+.Pp
+The
+.Nm
+utility does most computations in 64\-bit floating point
+arithmetic and does relatively clumsy 64\-bit fixed point operations
+only when necessary to preserve the ultimate precision, about 232
+picoseconds.
+While the ultimate precision is not achievable with
+ordinary workstations and networks of today, it may be required
+with future gigahertz CPU clocks and gigabit LANs.
+.Pp
+Ordinarily,
+.Nm
+reads the
+.Xr ntp.conf 5
+configuration file at startup time in order to determine the
+synchronization sources and operating modes.
+It is also possible to
+specify a working, although limited, configuration entirely on the
+command line, obviating the need for a configuration file.
+This may
+be particularly useful when the local host is to be configured as a
+broadcast/multicast client, with all peers being determined by
+listening to broadcasts at run time.
+.Pp
+If NetInfo support is built into
+.Nm ,
+then
+.Nm
+will attempt to read its configuration from the
+NetInfo if the default
+.Xr ntp.conf 5
+file cannot be read and no file is
+specified by the
+.Fl c
+option.
+.Pp
+Various internal
+.Nm
+variables can be displayed and
+configuration options altered while the
+.Nm
+is running
+using the
+.Xr ntpq 8
+and
+.Xr ntpdc 8
+utility programs.
+.Pp
+When
+.Nm
+starts it looks at the value of
+.Xr umask 2 ,
+and if zero
+.Nm
+will set the
+.Xr umask 2
+to 022.
+.Sh "OPTIONS"
+.Bl -tag
+.It Fl 4 , Fl \-ipv4
+Force IPv4 DNS name resolution.
+This option must not appear in combination with any of the following options:
+ipv6.
+.sp
+Force DNS resolution of following host names on the command line
+to the IPv4 namespace.
+.It Fl 6 , Fl \-ipv6
+Force IPv6 DNS name resolution.
+This option must not appear in combination with any of the following options:
+ipv4.
+.sp
+Force DNS resolution of following host names on the command line
+to the IPv6 namespace.
+.It Fl a , Fl \-authreq
+Require crypto authentication.
+This option must not appear in combination with any of the following options:
+authnoreq.
+.sp
+Require cryptographic authentication for broadcast client,
+multicast client and symmetric passive associations.
+This is the default.
+.It Fl A , Fl \-authnoreq
+Do not require crypto authentication.
+This option must not appear in combination with any of the following options:
+authreq.
+.sp
+Do not require cryptographic authentication for broadcast client,
+multicast client and symmetric passive associations.
+This is almost never a good idea.
+.It Fl b , Fl \-bcastsync
+Allow us to sync to broadcast servers.
+.sp
+.It Fl c Ar string , Fl \-configfile Ns = Ns Ar string
+configuration file name.
+.sp
+The name and path of the configuration file,
+\fI/etc/ntp.conf\fP
+by default.
+.It Fl d , Fl \-debug\-level
+Increase debug verbosity level.
+This option may appear an unlimited number of times.
+.sp
+.It Fl D Ar number , Fl \-set\-debug\-level Ns = Ns Ar number
+Set the debug verbosity level.
+This option may appear an unlimited number of times.
+This option takes an integer number as its argument.
+.sp
+.It Fl f Ar string , Fl \-driftfile Ns = Ns Ar string
+frequency drift file name.
+.sp
+The name and path of the frequency file,
+\fI/etc/ntp.drift\fP
+by default.
+This is the same operation as the
+\fBdriftfile\fP \fIdriftfile\fP
+configuration specification in the
+\fI/etc/ntp.conf\fP
+file.
+.It Fl g , Fl \-panicgate
+Allow the first adjustment to be Big.
+This option may appear an unlimited number of times.
+.sp
+Normally,
+\fBntpd\fP
+exits with a message to the system log if the offset exceeds the panic threshold, which is 1000 s by default. This option allows the time to be set to any value without restriction; however, this can happen only once. If the threshold is exceeded after that,
+\fBntpd\fP
+will exit with a message to the system log. This option can be used with the
+\fB\-q\fP
+and
+\fB\-x\fP
+options.
+See the
+\fBtinker\fP
+configuration file directive for other options.
+.It Fl G , Fl \-force\-step\-once
+Step any initial offset correction..
+.sp
+Normally,
+\fBntpd\fP
+steps the time if the time offset exceeds the step threshold,
+which is 128 ms by default, and otherwise slews the time.
+This option forces the initial offset correction to be stepped,
+so the highest time accuracy can be achieved quickly.
+However, this may also cause the time to be stepped back
+so this option must not be used if
+applications requiring monotonic time are running.
+See the \fBtinker\fP configuration file directive for other options.
+.It Fl i Ar string , Fl \-jaildir Ns = Ns Ar string
+Jail directory.
+.sp
+Chroot the server to the directory
+\fIjaildir\fP
+.
+This option also implies that the server attempts to drop root privileges at startup.
+You may need to also specify a
+\fB\-u\fP
+option.
+This option is only available if the OS supports adjusting the clock
+without full root privileges.
+This option is supported under NetBSD (configure with
+\fB\-\-enable\-clockctl\fP) or Linux (configure with
+\fB\-\-enable\-linuxcaps\fP) or Solaris (configure with \fB\-\-enable\-solarisprivs\fP).
+.It Fl I Ar iface , Fl \-interface Ns = Ns Ar iface
+Listen on an interface name or address.
+This option may appear an unlimited number of times.
+.sp
+Open the network address given, or all the addresses associated with the
+given interface name. This option may appear multiple times. This option
+also implies not opening other addresses, except wildcard and localhost.
+This option is deprecated. Please consider using the configuration file
+\fBinterface\fP command, which is more versatile.
+.It Fl k Ar string , Fl \-keyfile Ns = Ns Ar string
+path to symmetric keys.
+.sp
+Specify the name and path of the symmetric key file.
+\fI/etc/ntp.keys\fP
+is the default.
+This is the same operation as the
+\fBkeys\fP \fIkeyfile\fP
+configuration file directive.
+.It Fl l Ar string , Fl \-logfile Ns = Ns Ar string
+path to the log file.
+.sp
+Specify the name and path of the log file.
+The default is the system log file.
+This is the same operation as the
+\fBlogfile\fP \fIlogfile\fP
+configuration file directive.
+.It Fl L , Fl \-novirtualips
+Do not listen to virtual interfaces.
+.sp
+Do not listen to virtual interfaces, defined as those with
+names containing a colon. This option is deprecated. Please
+consider using the configuration file \fBinterface\fP command, which
+is more versatile.
+.It Fl M , Fl \-modifymmtimer
+Modify Multimedia Timer (Windows only).
+.sp
+Set the Windows Multimedia Timer to highest resolution. This
+ensures the resolution does not change while ntpd is running,
+avoiding timekeeping glitches associated with changes.
+.It Fl n , Fl \-nofork
+Do not fork.
+This option must not appear in combination with any of the following options:
+wait\-sync.
+.sp
+.It Fl N , Fl \-nice
+Run at high priority.
+.sp
+To the extent permitted by the operating system, run
+\fBntpd\fP
+at the highest priority.
+.It Fl p Ar string , Fl \-pidfile Ns = Ns Ar string
+path to the PID file.
+.sp
+Specify the name and path of the file used to record
+\fBntpd\fP's
+process ID.
+This is the same operation as the
+\fBpidfile\fP \fIpidfile\fP
+configuration file directive.
+.It Fl P Ar number , Fl \-priority Ns = Ns Ar number
+Process priority.
+This option takes an integer number as its argument.
+.sp
+To the extent permitted by the operating system, run
+\fBntpd\fP
+at the specified
+\fBsched_setscheduler(SCHED_FIFO)\fP
+priority.
+.It Fl q , Fl \-quit
+Set the time and quit.
+This option must not appear in combination with any of the following options:
+saveconfigquit, wait\-sync.
+.sp
+\fBntpd\fP
+will not daemonize and will exit after the clock is first
+synchronized. This behavior mimics that of the
+\fBntpdate\fP
+program, which will soon be replaced with a shell script.
+The
+\fB\-g\fP
+and
+\fB\-x\fP
+options can be used with this option.
+Note: The kernel time discipline is disabled with this option.
+.It Fl r Ar string , Fl \-propagationdelay Ns = Ns Ar string
+Broadcast/propagation delay.
+.sp
+Specify the default propagation delay from the broadcast/multicast server to this client. This is necessary only if the delay cannot be computed automatically by the protocol.
+.It Fl \-saveconfigquit Ns = Ns Ar string
+Save parsed configuration and quit.
+This option must not appear in combination with any of the following options:
+quit, wait\-sync.
+.sp
+Cause \fBntpd\fP to parse its startup configuration file and save an
+equivalent to the given filename and exit. This option was
+designed for automated testing.
+.It Fl s Ar string , Fl \-statsdir Ns = Ns Ar string
+Statistics file location.
+.sp
+Specify the directory path for files created by the statistics facility.
+This is the same operation as the
+\fBstatsdir\fP \fIstatsdir\fP
+configuration file directive.
+.It Fl t Ar tkey , Fl \-trustedkey Ns = Ns Ar tkey
+Trusted key number.
+This option may appear an unlimited number of times.
+.sp
+Add the specified key number to the trusted key list.
+.It Fl u Ar string , Fl \-user Ns = Ns Ar string
+Run as userid (or userid:groupid).
+.sp
+Specify a user, and optionally a group, to switch to.
+This option is only available if the OS supports adjusting the clock
+without full root privileges.
+This option is supported under NetBSD (configure with
+\fB\-\-enable\-clockctl\fP) or Linux (configure with
+\fB\-\-enable\-linuxcaps\fP) or Solaris (configure with \fB\-\-enable\-solarisprivs\fP).
+.It Fl U Ar number , Fl \-updateinterval Ns = Ns Ar number
+interval in seconds between scans for new or dropped interfaces.
+This option takes an integer number as its argument.
+.sp
+Give the time in seconds between two scans for new or dropped interfaces.
+For systems with routing socket support the scans will be performed shortly after the interface change
+has been detected by the system.
+Use 0 to disable scanning. 60 seconds is the minimum time between scans.
+.It Fl \-var Ns = Ns Ar nvar
+make ARG an ntp variable (RW).
+This option may appear an unlimited number of times.
+.sp
+.It Fl \-dvar Ns = Ns Ar ndvar
+make ARG an ntp variable (RW|DEF).
+This option may appear an unlimited number of times.
+.sp
+.It Fl w Ar number , Fl \-wait\-sync Ns = Ns Ar number
+Seconds to wait for first clock sync.
+This option must not appear in combination with any of the following options:
+nofork, quit, saveconfigquit.
+This option takes an integer number as its argument.
+.sp
+If greater than zero, alters \fBntpd\fP's behavior when forking to
+daemonize. Instead of exiting with status 0 immediately after
+the fork, the parent waits up to the specified number of
+seconds for the child to first synchronize the clock. The exit
+status is zero (success) if the clock was synchronized,
+otherwise it is \fBETIMEDOUT\fP.
+This provides the option for a script starting \fBntpd\fP to easily
+wait for the first set of the clock before proceeding.
+.It Fl x , Fl \-slew
+Slew up to 600 seconds.
+.sp
+Normally, the time is slewed if the offset is less than the step threshold, which is 128 ms by default, and stepped if above the threshold.
+This option sets the threshold to 600 s, which is well within the accuracy window to set the clock manually.
+Note: Since the slew rate of typical Unix kernels is limited to 0.5 ms/s, each second of adjustment requires an amortization interval of 2000 s.
+Thus, an adjustment as much as 600 s will take almost 14 days to complete.
+This option can be used with the
+\fB\-g\fP
+and
+\fB\-q\fP
+options.
+See the
+\fBtinker\fP
+configuration file directive for other options.
+Note: The kernel time discipline is disabled with this option.
+.It Fl \-usepcc
+Use CPU cycle counter (Windows only).
+.sp
+Attempt to substitute the CPU counter for \fBQueryPerformanceCounter\fP.
+The CPU counter and \fBQueryPerformanceCounter\fP are compared, and if
+they have the same frequency, the CPU counter (RDTSC on x86) is
+used directly, saving the overhead of a system call.
+.It Fl \-pccfreq Ns = Ns Ar string
+Force CPU cycle counter use (Windows only).
+.sp
+Force substitution the CPU counter for \fBQueryPerformanceCounter\fP.
+The CPU counter (RDTSC on x86) is used unconditionally with the
+given frequency (in Hz).
+.It Fl m , Fl \-mdns
+Register with mDNS as a NTP server.
+.sp
+Registers as an NTP server with the local mDNS server which allows
+the server to be discovered via mDNS client lookup.
+.It Fl \&? , Fl \-help
+Display usage information and exit.
+.It Fl \&! , Fl \-more\-help
+Pass the extended usage information through a pager.
+.It Fl \-version Op Brq Ar v|c|n
+Output version of program and exit. The default mode is `v', a simple
+version. The `c' mode will print copyright information and `n' will
+print the full copyright notice.
+.El
+.Sh "OPTION PRESETS"
+Any option that is not marked as \fInot presettable\fP may be preset
+by loading values from environment variables named:
+.nf
+ \fBNTPD_<option\-name>\fP or \fBNTPD\fP
+.fi
+.ad
+.Sh USAGE
+.Ss "How NTP Operates"
+The
+.Nm
+utility operates by exchanging messages with
+one or more configured servers over a range of designated poll intervals.
+When
+started, whether for the first or subsequent times, the program
+requires several exchanges from the majority of these servers so
+the signal processing and mitigation algorithms can accumulate and
+groom the data and set the clock.
+In order to protect the network
+from bursts, the initial poll interval for each server is delayed
+an interval randomized over a few seconds.
+At the default initial poll
+interval of 64s, several minutes can elapse before the clock is
+set.
+This initial delay to set the clock
+can be safely and dramatically reduced using the
+.Cm iburst
+keyword with the
+.Ic server
+configuration
+command, as described in
+.Xr ntp.conf 5 .
+.Pp
+Most operating systems and hardware of today incorporate a
+time\-of\-year (TOY) chip to maintain the time during periods when
+the power is off.
+When the machine is booted, the chip is used to
+initialize the operating system time.
+After the machine has
+synchronized to a NTP server, the operating system corrects the
+chip from time to time.
+In the default case, if
+.Nm
+detects that the time on the host
+is more than 1000s from the server time,
+.Nm
+assumes something must be terribly wrong and the only
+reliable action is for the operator to intervene and set the clock
+by hand.
+(Reasons for this include there is no TOY chip,
+or its battery is dead, or that the TOY chip is just of poor quality.)
+This causes
+.Nm
+to exit with a panic message to
+the system log.
+The
+.Fl g
+option overrides this check and the
+clock will be set to the server time regardless of the chip time
+(up to 68 years in the past or future \(em
+this is a limitation of the NTPv4 protocol).
+However, and to protect against broken hardware, such as when the
+CMOS battery fails or the clock counter becomes defective, once the
+clock has been set an error greater than 1000s will cause
+.Nm
+to exit anyway.
+.Pp
+Under ordinary conditions,
+.Nm
+adjusts the clock in
+small steps so that the timescale is effectively continuous and
+without discontinuities.
+Under conditions of extreme network
+congestion, the roundtrip delay jitter can exceed three seconds and
+the synchronization distance, which is equal to one\-half the
+roundtrip delay plus error budget terms, can become very large.
+The
+.Nm
+algorithms discard sample offsets exceeding 128 ms,
+unless the interval during which no sample offset is less than 128
+ms exceeds 900s.
+The first sample after that, no matter what the
+offset, steps the clock to the indicated time.
+In practice this
+reduces the false alarm rate where the clock is stepped in error to
+a vanishingly low incidence.
+.Pp
+As the result of this behavior, once the clock has been set it
+very rarely strays more than 128 ms even under extreme cases of
+network path congestion and jitter.
+Sometimes, in particular when
+.Nm
+is first started without a valid drift file
+on a system with a large intrinsic drift
+the error might grow to exceed 128 ms,
+which would cause the clock to be set backwards
+if the local clock time is more than 128 s
+in the future relative to the server.
+In some applications, this behavior may be unacceptable.
+There are several solutions, however.
+If the
+.Fl x
+option is included on the command line, the clock will
+never be stepped and only slew corrections will be used.
+But this choice comes with a cost that
+should be carefully explored before deciding to use
+the
+.Fl x
+option.
+The maximum slew rate possible is limited
+to 500 parts\-per\-million (PPM) as a consequence of the correctness
+principles on which the NTP protocol and algorithm design are
+based.
+As a result, the local clock can take a long time to
+converge to an acceptable offset, about 2,000 s for each second the
+clock is outside the acceptable range.
+During this interval the
+local clock will not be consistent with any other network clock and
+the system cannot be used for distributed applications that require
+correctly synchronized network time.
+.Pp
+In spite of the above precautions, sometimes when large
+frequency errors are present the resulting time offsets stray
+outside the 128\-ms range and an eventual step or slew time
+correction is required.
+If following such a correction the
+frequency error is so large that the first sample is outside the
+acceptable range,
+.Nm
+enters the same state as when the
+.Pa ntp.drift
+file is not present.
+The intent of this behavior
+is to quickly correct the frequency and restore operation to the
+normal tracking mode.
+In the most extreme cases
+(the host
+.Cm time.ien.it
+comes to mind), there may be occasional
+step/slew corrections and subsequent frequency corrections.
+It
+helps in these cases to use the
+.Cm burst
+keyword when
+configuring the server, but
+ONLY
+when you have permission to do so from the owner of the target host.
+.Pp
+Finally,
+in the past many startup scripts would run
+.Xr ntpdate 8
+or
+.Xr sntp 8
+to get the system clock close to correct before starting
+.Xr ntpd 8 ,
+but this was never more than a mediocre hack and is no longer needed.
+If you are following the instructions in
+.Sx "Starting NTP (Best Current Practice)"
+and you still need to set the system time before starting
+.Nm ,
+please open a bug report and document what is going on,
+and then look at using
+.Xr sntp 8
+if you really need to set the clock before starting
+.Nm .
+.Pp
+There is a way to start
+.Xr ntpd 8
+that often addresses all of the problems mentioned above.
+.Ss "Starting NTP (Best Current Practice)"
+First, use the
+.Cm iburst
+option on your
+.Cm server
+entries.
+.Pp
+If you can also keep a good
+.Pa ntp.drift
+file then
+.Xr ntpd 8
+will effectively "warm\-start" and your system's clock will
+be stable in under 11 seconds' time.
+.Pp
+As soon as possible in the startup sequence, start
+.Xr ntpd 8
+with at least the
+.Fl g
+and perhaps the
+.Fl N
+options.
+Then,
+start the rest of your "normal" processes.
+This will give
+.Xr ntpd 8
+as much time as possible to get the system's clock synchronized and stable.
+.Pp
+Finally,
+if you have processes like
+.Cm dovecot
+or database servers
+that require
+monotonically\-increasing time,
+run
+.Xr ntp\-wait 1ntp\-waitmdoc
+as late as possible in the boot sequence
+(perhaps with the
+.Fl v
+flag)
+and after
+.Xr ntp\-wait 1ntp\-waitmdoc
+exits successfully
+it is as safe as it will ever be to start any process that require
+stable time.
+.Ss "Frequency Discipline"
+The
+.Nm
+behavior at startup depends on whether the
+frequency file, usually
+.Pa ntp.drift ,
+exists.
+This file
+contains the latest estimate of clock frequency error.
+When the
+.Nm
+is started and the file does not exist, the
+.Nm
+enters a special mode designed to quickly adapt to
+the particular system clock oscillator time and frequency error.
+This takes approximately 15 minutes, after which the time and
+frequency are set to nominal values and the
+.Nm
+enters
+normal mode, where the time and frequency are continuously tracked
+relative to the server.
+After one hour the frequency file is
+created and the current frequency offset written to it.
+When the
+.Nm
+is started and the file does exist, the
+.Nm
+frequency is initialized from the file and enters normal mode
+immediately.
+After that the current frequency offset is written to
+the file at hourly intervals.
+.Ss "Operating Modes"
+The
+.Nm
+utility can operate in any of several modes, including
+symmetric active/passive, client/server broadcast/multicast and
+manycast, as described in the
+.Qq Association Management
+page
+(available as part of the HTML documentation
+provided in
+.Pa /usr/share/doc/ntp ) .
+It normally operates continuously while
+monitoring for small changes in frequency and trimming the clock
+for the ultimate precision.
+However, it can operate in a one\-time
+mode where the time is set from an external server and frequency is
+set from a previously recorded frequency file.
+A
+broadcast/multicast or manycast client can discover remote servers,
+compute server\-client propagation delay correction factors and
+configure itself automatically.
+This makes it possible to deploy a
+fleet of workstations without specifying configuration details
+specific to the local environment.
+.Pp
+By default,
+.Nm
+runs in continuous mode where each of
+possibly several external servers is polled at intervals determined
+by an intricate state machine.
+The state machine measures the
+incidental roundtrip delay jitter and oscillator frequency wander
+and determines the best poll interval using a heuristic algorithm.
+Ordinarily, and in most operating environments, the state machine
+will start with 64s intervals and eventually increase in steps to
+1024s.
+A small amount of random variation is introduced in order to
+avoid bunching at the servers.
+In addition, should a server become
+unreachable for some time, the poll interval is increased in steps
+to 1024s in order to reduce network overhead.
+.Pp
+In some cases it may not be practical for
+.Nm
+to run continuously.
+A common workaround has been to run the
+.Xr ntpdate 8
+or
+.Xr sntp 8
+programs from a
+.Xr cron 8
+job at designated
+times.
+However, these programs do not have the crafted signal
+processing, error checking or mitigation algorithms of
+.Nm .
+The
+.Fl q
+option is intended for this purpose.
+Setting this option will cause
+.Nm
+to exit just after
+setting the clock for the first time.
+The procedure for initially
+setting the clock is the same as in continuous mode; most
+applications will probably want to specify the
+.Cm iburst
+keyword with the
+.Ic server
+configuration command.
+With this
+keyword a volley of messages are exchanged to groom the data and
+the clock is set in about 10 s.
+If nothing is heard after a
+couple of minutes, the daemon times out and exits.
+After a suitable
+period of mourning, the
+.Xr ntpdate 8
+program will be
+retired.
+.Pp
+When kernel support is available to discipline the clock
+frequency, which is the case for stock Solaris, Tru64, Linux and
+.Fx ,
+a useful feature is available to discipline the clock
+frequency.
+First,
+.Nm
+is run in continuous mode with
+selected servers in order to measure and record the intrinsic clock
+frequency offset in the frequency file.
+It may take some hours for
+the frequency and offset to settle down.
+Then the
+.Nm
+is
+stopped and run in one\-time mode as required.
+At each startup, the
+frequency is read from the file and initializes the kernel
+frequency.
+.Ss "Poll Interval Control"
+This version of NTP includes an intricate state machine to
+reduce the network load while maintaining a quality of
+synchronization consistent with the observed jitter and wander.
+There are a number of ways to tailor the operation in order enhance
+accuracy by reducing the interval or to reduce network overhead by
+increasing it.
+However, the user is advised to carefully consider
+the consequences of changing the poll adjustment range from the
+default minimum of 64 s to the default maximum of 1,024 s.
+The
+default minimum can be changed with the
+.Ic tinker
+.Cm minpoll
+command to a value not less than 16 s.
+This value is used for all
+configured associations, unless overridden by the
+.Cm minpoll
+option on the configuration command.
+Note that most device drivers
+will not operate properly if the poll interval is less than 64 s
+and that the broadcast server and manycast client associations will
+also use the default, unless overridden.
+.Pp
+In some cases involving dial up or toll services, it may be
+useful to increase the minimum interval to a few tens of minutes
+and maximum interval to a day or so.
+Under normal operation
+conditions, once the clock discipline loop has stabilized the
+interval will be increased in steps from the minimum to the
+maximum.
+However, this assumes the intrinsic clock frequency error
+is small enough for the discipline loop correct it.
+The capture
+range of the loop is 500 PPM at an interval of 64s decreasing by a
+factor of two for each doubling of interval.
+At a minimum of 1,024
+s, for example, the capture range is only 31 PPM.
+If the intrinsic
+error is greater than this, the drift file
+.Pa ntp.drift
+will
+have to be specially tailored to reduce the residual error below
+this limit.
+Once this is done, the drift file is automatically
+updated once per hour and is available to initialize the frequency
+on subsequent daemon restarts.
+.Ss "The huff\-n'\-puff Filter"
+In scenarios where a considerable amount of data are to be
+downloaded or uploaded over telephone modems, timekeeping quality
+can be seriously degraded.
+This occurs because the differential
+delays on the two directions of transmission can be quite large.
+In
+many cases the apparent time errors are so large as to exceed the
+step threshold and a step correction can occur during and after the
+data transfer is in progress.
+.Pp
+The huff\-n'\-puff filter is designed to correct the apparent time
+offset in these cases.
+It depends on knowledge of the propagation
+delay when no other traffic is present.
+In common scenarios this
+occurs during other than work hours.
+The filter maintains a shift
+register that remembers the minimum delay over the most recent
+interval measured usually in hours.
+Under conditions of severe
+delay, the filter corrects the apparent offset using the sign of
+the offset and the difference between the apparent delay and
+minimum delay.
+The name of the filter reflects the negative (huff)
+and positive (puff) correction, which depends on the sign of the
+offset.
+.Pp
+The filter is activated by the
+.Ic tinker
+command and
+.Cm huffpuff
+keyword, as described in
+.Xr ntp.conf 5 .
+.Sh "ENVIRONMENT"
+See \fBOPTION PRESETS\fP for configuration environment variables.
+.Sh FILES
+.Bl -tag -width /etc/ntp.drift -compact
+.It Pa /etc/ntp.conf
+the default name of the configuration file
+.It Pa /etc/ntp.drift
+the default name of the drift file
+.It Pa /etc/ntp.keys
+the default name of the key file
+.El
+.Sh "EXIT STATUS"
+One of the following exit values will be returned:
+.Bl -tag
+.It 0 " (EXIT_SUCCESS)"
+Successful program execution.
+.It 1 " (EXIT_FAILURE)"
+The operation failed or the command syntax was not valid.
+.It 70 " (EX_SOFTWARE)"
+libopts had an internal operational error. Please report
+it to autogen\-users@lists.sourceforge.net. Thank you.
+.El
+.Sh "SEE ALSO"
+.Xr ntp.conf 5 ,
+.Xr ntpdate 8 ,
+.Xr ntpdc 8 ,
+.Xr ntpq 8 ,
+.Xr sntp 8
+.Pp
+In addition to the manual pages provided,
+comprehensive documentation is available on the world wide web
+at
+.Li http://www.ntp.org/ .
+A snapshot of this documentation is available in HTML format in
+.Pa /usr/share/doc/ntp .
+.Rs
+.%A David L. Mills
+.%T Network Time Protocol (Version 1)
+.%O RFC1059
+.Re
+.Rs
+.%A David L. Mills
+.%T Network Time Protocol (Version 2)
+.%O RFC1119
+.Re
+.Rs
+.%A David L. Mills
+.%T Network Time Protocol (Version 3)
+.%O RFC1305
+.Re
+.Rs
+.%A David L. Mills
+.%A J. Martin, Ed.
+.%A J. Burbank
+.%A W. Kasch
+.%T Network Time Protocol Version 4: Protocol and Algorithms Specification
+.%O RFC5905
+.Re
+.Rs
+.%A David L. Mills
+.%A B. Haberman, Ed.
+.%T Network Time Protocol Version 4: Autokey Specification
+.%O RFC5906
+.Re
+.Rs
+.%A H. Gerstung
+.%A C. Elliott
+.%A B. Haberman, Ed.
+.%T Definitions of Managed Objects for Network Time Protocol Version 4: (NTPv4)
+.%O RFC5907
+.Re
+.Rs
+.%A R. Gayraud
+.%A B. Lourdelet
+.%T Network Time Protocol (NTP) Server Option for DHCPv6
+.%O RFC5908
+.Re
+.Sh "AUTHORS"
+The University of Delaware and Network Time Foundation
+.Sh "COPYRIGHT"
+Copyright (C) 1992\-2015 The University of Delaware and Network Time Foundation all rights reserved.
+This program is released under the terms of the NTP license, <http://ntp.org/license>.
+.Sh BUGS
+The
+.Nm
+utility has gotten rather fat.
+While not huge, it has gotten
+larger than might be desirable for an elevated\-priority
+.Nm
+running on a workstation, particularly since many of
+the fancy features which consume the space were designed more with
+a busy primary server, rather than a high stratum workstation in
+mind.
+.Pp
+Please send bug reports to: http://bugs.ntp.org, bugs@ntp.org
+.Sh NOTES
+Portions of this document came from FreeBSD.
+.Pp
+This manual page was \fIAutoGen\fP\-erated from the \fBntpd\fP
+option definitions.
diff --git a/usr.sbin/ntp/doc/ntpdate.8 b/usr.sbin/ntp/doc/ntpdate.8
new file mode 100644
index 0000000..e5aaecd
--- /dev/null
+++ b/usr.sbin/ntp/doc/ntpdate.8
@@ -0,0 +1,279 @@
+.\"
+.\" $FreeBSD$
+.\"
+.Dd May 17, 2006
+.Dt NTPDATE 8
+.Os
+.Sh NAME
+.Nm ntpdate
+.Nd set the date and time via NTP
+.Sh SYNOPSIS
+.Nm
+.Op Fl 46bBdoqsuv
+.Op Fl a Ar key
+.Op Fl e Ar authdelay
+.Op Fl k Ar keyfile
+.Op Fl o Ar version
+.Op Fl p Ar samples
+.Op Fl t Ar timeout
+.Ar server ...
+.Sh DESCRIPTION
+.Em Note :
+The functionality of this program is now available
+in the
+.Xr ntpd 8
+program.
+See the
+.Fl q
+command line
+option in the
+.Xr ntpd 8
+page.
+After a suitable period of
+mourning, the
+.Nm
+utility is to be retired from this
+distribution.
+.Pp
+The
+.Nm
+utility sets the local date and time by polling the
+Network Time Protocol (NTP) server(s) given as the
+.Ar server
+arguments to determine the correct time.
+It must be run as root on
+the local host.
+A number of samples are obtained from each of the
+servers specified and a subset of the NTP clock filter and
+selection algorithms are applied to select the best of these.
+Note
+that the accuracy and reliability of
+.Nm
+depends on
+the number of servers, the number of polls each time it is run and
+the interval between runs.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl 4
+Force DNS resolution of following host names on the command line to the
+IPv4 namespace.
+.It Fl 6
+Force DNS resolution of following host names on the command line to the
+IPv6 namespace.
+.It Fl a Ar key
+Enable the authentication function and specify the key
+identifier to be used for authentication as the argument
+.Ar key .
+The keys and key identifiers must match
+in both the client and server key files.
+The default is to disable
+the authentication function.
+.It Fl B
+Force the time to always be slewed using the
+.Xr adjtime 2
+system
+call, even if the measured offset is greater than +-128 ms.
+The
+default is to step the time using
+.Xr settimeofday 2
+if the offset is
+greater than +-128 ms.
+Note that, if the offset is much greater
+than +-128 ms in this case, it can take a long time (hours) to
+slew the clock to the correct value.
+During this time, the host
+should not be used to synchronize clients.
+.It Fl b
+Force the time to be stepped using the
+.Xr settimeofday 2
+system
+call, rather than slewed (default) using the
+.Xr adjtime 2
+system call.
+This option should be used when called from a startup file at boot
+time.
+.It Fl d
+Enable the debugging mode, in which
+.Nm
+will go
+through all the steps, but not adjust the local clock.
+Information
+useful for general debugging will also be printed.
+.It Fl e Ar authdelay
+Specify the processing delay to perform an authentication
+function as the value
+.Ar authdelay ,
+in seconds and fraction
+(see
+.Xr ntpd 8
+for details).
+This number is usually small
+enough to be negligible for most purposes, though specifying a
+value may improve timekeeping on very slow CPU's.
+.It Fl k Ar keyfile
+Specify the path for the authentication key file as the string
+.Ar keyfile .
+The default is
+.Pa /etc/ntp.keys .
+This file
+should be in the format described in
+.Xr ntpd 8 .
+.It Fl o Ar version
+Specify the NTP version for outgoing packets as the integer
+.Ar version ,
+which can be 1 or 2.
+The default is 3.
+This allows
+.Nm
+to be used with older NTP versions.
+.It Fl p Ar samples
+Specify the number of samples to be acquired from each server
+as the integer
+.Ar samples ,
+with values from 1 to 8 inclusive.
+The default is 4.
+.It Fl q
+Query only - do not set the clock.
+.It Fl s
+Divert logging output from the standard output (default) to the
+system
+.Xr syslog 3
+facility.
+This is designed primarily for
+convenience of
+.Xr cron 8
+scripts.
+.It Fl t Ar timeout
+Specify the maximum time waiting for a server response as the
+value
+.Ar timeout ,
+in seconds and fraction.
+The value is
+rounded to a multiple of 0.2 seconds.
+The default is 1 second, a
+value suitable for polling across a LAN.
+.It Fl u
+Direct
+.Nm
+to use an unprivileged port for outgoing
+packets.
+This is most useful when behind a firewall that blocks
+incoming traffic to privileged ports, and you want to synchronise
+with hosts beyond the firewall.
+Note that the
+.Fl d
+option
+always uses unprivileged ports.
+.It Fl v
+Be verbose.
+This option will cause
+.Nm Ns 's
+version
+identification string to be logged.
+.El
+.Pp
+The
+.Nm
+utility can be run manually as necessary to set the
+host clock, or it can be run from the host startup script to set
+the clock at boot time.
+This is useful in some cases to set the
+clock initially before starting the NTP daemon
+.Xr ntpd 8 .
+It is
+also possible to run
+.Nm
+from a
+.Xr cron 8
+script.
+However, it is important to note that
+.Nm
+with
+contrived
+.Xr cron 8
+scripts is no substitute for the NTP
+daemon, which uses sophisticated algorithms to maximize accuracy
+and reliability while minimizing resource use.
+Finally, since
+.Nm
+does not discipline the host clock frequency as
+does
+.Xr ntpd 8 ,
+the accuracy using
+.Nm
+is
+limited.
+.Pp
+Time adjustments are made by
+.Nm
+in one of two
+ways.
+If
+.Nm
+determines the clock is in error more
+than 0.5 second it will simply step the time by calling the system
+.Xr settimeofday 2
+routine.
+If the error is less than 0.5
+seconds, it will slew the time by calling the system
+.Xr adjtime 2
+routine.
+The latter technique is less disruptive
+and more accurate when the error is small, and works quite well
+when
+.Nm
+is run by
+.Xr cron 8
+every hour or
+two.
+.Pp
+The
+.Nm
+utility will decline to set the date if an NTP server
+daemon (e.g.,
+.Xr ntpd 8 )
+is running on the same host.
+When
+running
+.Nm
+on a regular basis from
+.Xr cron 8
+as
+an alternative to running a daemon, doing so once every hour or two
+will result in precise enough timekeeping to avoid stepping the
+clock.
+.Pp
+Note that in contexts where a host name is expected, a
+.Fl 4
+qualifier preceding the host name forces DNS resolution to the
+IPv4 namespace, while a
+.Fl 6
+qualifier forces DNS resolution to the IPv6 namespace.
+.Pp
+If NetInfo support is compiled into
+.Nm ,
+then the
+.Cm server
+argument is optional if
+.Nm
+can find a
+time server in the NetInfo configuration for
+.Xr ntpd 8 .
+.Sh FILES
+.Bl -tag -width /etc/ntp.keys -compact
+.It Pa /etc/ntp.keys
+contains the encryption keys used by
+.Nm .
+.El
+.Sh SEE ALSO
+.Xr ntpd 8
+.Sh BUGS
+The slew adjustment is actually 50% larger than the measured
+offset, since this (it is argued) will tend to keep a badly
+drifting clock more accurate.
+This is probably not a good idea and
+may cause a troubling hunt for some values of the kernel variables
+.Va kern.clockrate.tick
+and
+.Va kern.clockrate.tickadj .
diff --git a/usr.sbin/ntp/doc/ntpdc.8 b/usr.sbin/ntp/doc/ntpdc.8
new file mode 100644
index 0000000..3561f2a
--- /dev/null
+++ b/usr.sbin/ntp/doc/ntpdc.8
@@ -0,0 +1,811 @@
+.Dd January 7 2016
+.Dt NTPDC 8 User Commands
+.Os
+.\" EDIT THIS FILE WITH CAUTION (ntpdc-opts.mdoc)
+.\"
+.\" $FreeBSD$
+.\"
+.\" It has been AutoGen-ed January 7, 2016 at 11:31:29 PM by AutoGen 5.18.5
+.\" From the definitions ntpdc-opts.def
+.\" and the template file agmdoc-cmd.tpl
+.Sh NAME
+.Nm ntpdc
+.Nd vendor-specific NTPD control program
+.Sh SYNOPSIS
+.Nm
+.\" Mixture of short (flag) options and long options
+.Op Fl flags
+.Op Fl flag Op Ar value
+.Op Fl \-option\-name Ns Oo Oo Ns "=| " Oc Ns Ar value Oc
+[ host ...]
+.Pp
+.Sh DESCRIPTION
+.Nm
+is deprecated.
+Please use
+.Xr ntpq 8 instead \- it can do everything
+.Nm
+used to do, and it does so using a much more sane interface.
+.Pp
+.Nm
+is a utility program used to query
+.Xr ntpd 8
+about its
+current state and to request changes in that state.
+It uses NTP mode 7 control message formats described in the source code.
+The program may
+be run either in interactive mode or controlled using command line
+arguments.
+Extensive state and statistics information is available
+through the
+.Nm
+interface.
+In addition, nearly all the
+configuration options which can be specified at startup using
+ntpd's configuration file may also be specified at run time using
+.Nm .
+.Sh "OPTIONS"
+.Bl -tag
+.It Fl 4 , Fl \-ipv4
+Force IPv4 DNS name resolution.
+This option must not appear in combination with any of the following options:
+ipv6.
+.sp
+Force DNS resolution of following host names on the command line
+to the IPv4 namespace.
+.It Fl 6 , Fl \-ipv6
+Force IPv6 DNS name resolution.
+This option must not appear in combination with any of the following options:
+ipv4.
+.sp
+Force DNS resolution of following host names on the command line
+to the IPv6 namespace.
+.It Fl c Ar cmd , Fl \-command Ns = Ns Ar cmd
+run a command and exit.
+This option may appear an unlimited number of times.
+.sp
+The following argument is interpreted as an interactive format command
+and is added to the list of commands to be executed on the specified
+host(s).
+.It Fl d , Fl \-debug\-level
+Increase debug verbosity level.
+This option may appear an unlimited number of times.
+.sp
+.It Fl D Ar number , Fl \-set\-debug\-level Ns = Ns Ar number
+Set the debug verbosity level.
+This option may appear an unlimited number of times.
+This option takes an integer number as its argument.
+.sp
+.It Fl i , Fl \-interactive
+Force ntpq to operate in interactive mode.
+This option must not appear in combination with any of the following options:
+command, listpeers, peers, showpeers.
+.sp
+Force ntpq to operate in interactive mode. Prompts will be written
+to the standard output and commands read from the standard input.
+.It Fl l , Fl \-listpeers
+Print a list of the peers.
+This option must not appear in combination with any of the following options:
+command.
+.sp
+Print a list of the peers known to the server as well as a summary of
+their state. This is equivalent to the 'listpeers' interactive command.
+.It Fl n , Fl \-numeric
+numeric host addresses.
+.sp
+Output all host addresses in dotted\-quad numeric format rather than
+converting to the canonical host names.
+.It Fl p , Fl \-peers
+Print a list of the peers.
+This option must not appear in combination with any of the following options:
+command.
+.sp
+Print a list of the peers known to the server as well as a summary
+of their state. This is equivalent to the 'peers' interactive command.
+.It Fl s , Fl \-showpeers
+Show a list of the peers.
+This option must not appear in combination with any of the following options:
+command.
+.sp
+Print a list of the peers known to the server as well as a summary
+of their state. This is equivalent to the 'dmpeers' interactive command.
+.It Fl \&? , Fl \-help
+Display usage information and exit.
+.It Fl \&! , Fl \-more\-help
+Pass the extended usage information through a pager.
+.It Fl > Oo Ar cfgfile Oc , Fl \-save\-opts Oo Ns = Ns Ar cfgfile Oc
+Save the option state to \fIcfgfile\fP. The default is the \fIlast\fP
+configuration file listed in the \fBOPTION PRESETS\fP section, below.
+The command will exit after updating the config file.
+.It Fl < Ar cfgfile , Fl \-load\-opts Ns = Ns Ar cfgfile , Fl \-no\-load\-opts
+Load options from \fIcfgfile\fP.
+The \fIno\-load\-opts\fP form will disable the loading
+of earlier config/rc/ini files. \fI\-\-no\-load\-opts\fP is handled early,
+out of order.
+.It Fl \-version Op Brq Ar v|c|n
+Output version of program and exit. The default mode is `v', a simple
+version. The `c' mode will print copyright information and `n' will
+print the full copyright notice.
+.El
+.Sh "OPTION PRESETS"
+Any option that is not marked as \fInot presettable\fP may be preset
+by loading values from configuration ("RC" or ".INI") file(s) and values from
+environment variables named:
+.nf
+ \fBNTPDC_<option\-name>\fP or \fBNTPDC\fP
+.fi
+.ad
+The environmental presets take precedence (are processed later than)
+the configuration files.
+The \fIhomerc\fP files are "\fI$HOME\fP", and "\fI.\fP".
+If any of these are directories, then the file \fI.ntprc\fP
+is searched for within those directories.
+.Sh USAGE
+If one or more request options are included on the command line
+when
+.Nm
+is executed, each of the requests will be sent
+to the NTP servers running on each of the hosts given as command
+line arguments, or on localhost by default.
+If no request options
+are given,
+.Nm
+will attempt to read commands from the
+standard input and execute these on the NTP server running on the
+first host given on the command line, again defaulting to localhost
+when no other host is specified.
+The
+.Nm
+utility will prompt for
+commands if the standard input is a terminal device.
+.Pp
+The
+.Nm
+utility uses NTP mode 7 packets to communicate with the
+NTP server, and hence can be used to query any compatible server on
+the network which permits it.
+Note that since NTP is a UDP protocol
+this communication will be somewhat unreliable, especially over
+large distances in terms of network topology.
+The
+.Nm
+utility makes
+no attempt to retransmit requests, and will time requests out if
+the remote host is not heard from within a suitable timeout
+time.
+.Pp
+The operation of
+.Nm
+are specific to the particular
+implementation of the
+.Xr ntpd 8
+daemon and can be expected to
+work only with this and maybe some previous versions of the daemon.
+Requests from a remote
+.Nm
+utility which affect the
+state of the local server must be authenticated, which requires
+both the remote program and local server share a common key and key
+identifier.
+.Pp
+Note that in contexts where a host name is expected, a
+.Fl 4
+qualifier preceding the host name forces DNS resolution to the IPv4 namespace,
+while a
+.Fl 6
+qualifier forces DNS resolution to the IPv6 namespace.
+Specifying a command line option other than
+.Fl i
+or
+.Fl n
+will cause the specified query (queries) to be sent to
+the indicated host(s) immediately.
+Otherwise,
+.Nm
+will
+attempt to read interactive format commands from the standard
+input.
+.Ss "Interactive Commands"
+Interactive format commands consist of a keyword followed by zero
+to four arguments.
+Only enough characters of the full keyword to
+uniquely identify the command need be typed.
+The output of a
+command is normally sent to the standard output, but optionally the
+output of individual commands may be sent to a file by appending a
+.Ql \&> ,
+followed by a file name, to the command line.
+.Pp
+A number of interactive format commands are executed entirely
+within the
+.Nm
+utility itself and do not result in NTP
+mode 7 requests being sent to a server.
+These are described
+following.
+.Bl -tag -width indent
+.It Ic \&? Ar command_keyword
+.It Ic help Ar command_keyword
+A
+.Sq Ic \&?
+will print a list of all the command
+keywords known to this incarnation of
+.Nm .
+A
+.Sq Ic \&?
+followed by a command keyword will print function and usage
+information about the command.
+This command is probably a better
+source of information about
+.Xr ntpq 8
+than this manual
+page.
+.It Ic delay Ar milliseconds
+Specify a time interval to be added to timestamps included in
+requests which require authentication.
+This is used to enable
+(unreliable) server reconfiguration over long delay network paths
+or between machines whose clocks are unsynchronized.
+Actually the
+server does not now require timestamps in authenticated requests,
+so this command may be obsolete.
+.It Ic host Ar hostname
+Set the host to which future queries will be sent.
+Hostname may
+be either a host name or a numeric address.
+.It Ic hostnames Op Cm yes | Cm no
+If
+.Cm yes
+is specified, host names are printed in
+information displays.
+If
+.Cm no
+is specified, numeric
+addresses are printed instead.
+The default is
+.Cm yes ,
+unless
+modified using the command line
+.Fl n
+switch.
+.It Ic keyid Ar keyid
+This command allows the specification of a key number to be
+used to authenticate configuration requests.
+This must correspond
+to a key number the server has been configured to use for this
+purpose.
+.It Ic quit
+Exit
+.Nm .
+.It Ic passwd
+This command prompts you to type in a password (which will not
+be echoed) which will be used to authenticate configuration
+requests.
+The password must correspond to the key configured for
+use by the NTP server for this purpose if such requests are to be
+successful.
+.It Ic timeout Ar milliseconds
+Specify a timeout period for responses to server queries.
+The
+default is about 8000 milliseconds.
+Note that since
+.Nm
+retries each query once after a timeout, the total waiting time for
+a timeout will be twice the timeout value set.
+.El
+.Ss "Control Message Commands"
+Query commands result in NTP mode 7 packets containing requests for
+information being sent to the server.
+These are read\-only commands
+in that they make no modification of the server configuration
+state.
+.Bl -tag -width indent
+.It Ic listpeers
+Obtains and prints a brief list of the peers for which the
+server is maintaining state.
+These should include all configured
+peer associations as well as those peers whose stratum is such that
+they are considered by the server to be possible future
+synchronization candidates.
+.It Ic peers
+Obtains a list of peers for which the server is maintaining
+state, along with a summary of that state.
+Summary information
+includes the address of the remote peer, the local interface
+address (0.0.0.0 if a local address has yet to be determined), the
+stratum of the remote peer (a stratum of 16 indicates the remote
+peer is unsynchronized), the polling interval, in seconds, the
+reachability register, in octal, and the current estimated delay,
+offset and dispersion of the peer, all in seconds.
+.Pp
+The character in the left margin indicates the mode this peer
+entry is operating in.
+A
+.Ql \&+
+denotes symmetric active, a
+.Ql \&\-
+indicates symmetric passive, a
+.Ql \&=
+means the
+remote server is being polled in client mode, a
+.Ql \&^
+indicates that the server is broadcasting to this address, a
+.Ql \&~
+denotes that the remote peer is sending broadcasts and a
+.Ql \&~
+denotes that the remote peer is sending broadcasts and a
+.Ql \&*
+marks the peer the server is currently synchronizing
+to.
+.Pp
+The contents of the host field may be one of four forms.
+It may
+be a host name, an IP address, a reference clock implementation
+name with its parameter or
+.Fn REFCLK "implementation_number" "parameter" .
+On
+.Ic hostnames
+.Cm no
+only IP\-addresses
+will be displayed.
+.It Ic dmpeers
+A slightly different peer summary list.
+Identical to the output
+of the
+.Ic peers
+command, except for the character in the
+leftmost column.
+Characters only appear beside peers which were
+included in the final stage of the clock selection algorithm.
+A
+.Ql \&.
+indicates that this peer was cast off in the falseticker
+detection, while a
+.Ql \&+
+indicates that the peer made it
+through.
+A
+.Ql \&*
+denotes the peer the server is currently
+synchronizing with.
+.It Ic showpeer Ar peer_address Oo Ar ... Oc
+Shows a detailed display of the current peer variables for one
+or more peers.
+Most of these values are described in the NTP
+Version 2 specification.
+.It Ic pstats Ar peer_address Oo Ar ... Oc
+Show per\-peer statistic counters associated with the specified
+peer(s).
+.It Ic clockstat Ar clock_peer_address Oo Ar ... Oc
+Obtain and print information concerning a peer clock.
+The
+values obtained provide information on the setting of fudge factors
+and other clock performance information.
+.It Ic kerninfo
+Obtain and print kernel phase\-lock loop operating parameters.
+This information is available only if the kernel has been specially
+modified for a precision timekeeping function.
+.It Ic loopinfo Op Cm oneline | Cm multiline
+Print the values of selected loop filter variables.
+The loop
+filter is the part of NTP which deals with adjusting the local
+system clock.
+The
+.Sq offset
+is the last offset given to the
+loop filter by the packet processing code.
+The
+.Sq frequency
+is the frequency error of the local clock in parts\-per\-million
+(ppm).
+The
+.Sq time_const
+controls the stiffness of the
+phase\-lock loop and thus the speed at which it can adapt to
+oscillator drift.
+The
+.Sq watchdog timer
+value is the number
+of seconds which have elapsed since the last sample offset was
+given to the loop filter.
+The
+.Cm oneline
+and
+.Cm multiline
+options specify the format in which this
+information is to be printed, with
+.Cm multiline
+as the
+default.
+.It Ic sysinfo
+Print a variety of system state variables, i.e., state related
+to the local server.
+All except the last four lines are described
+in the NTP Version 3 specification, RFC\-1305.
+.Pp
+The
+.Sq system flags
+show various system flags, some of
+which can be set and cleared by the
+.Ic enable
+and
+.Ic disable
+configuration commands, respectively.
+These are
+the
+.Cm auth ,
+.Cm bclient ,
+.Cm monitor ,
+.Cm pll ,
+.Cm pps
+and
+.Cm stats
+flags.
+See the
+.Xr ntpd 8
+documentation for the meaning of these flags.
+There
+are two additional flags which are read only, the
+.Cm kernel_pll
+and
+.Cm kernel_pps .
+These flags indicate
+the synchronization status when the precision time kernel
+modifications are in use.
+The
+.Sq kernel_pll
+indicates that
+the local clock is being disciplined by the kernel, while the
+.Sq kernel_pps
+indicates the kernel discipline is provided by the PPS
+signal.
+.Pp
+The
+.Sq stability
+is the residual frequency error remaining
+after the system frequency correction is applied and is intended for
+maintenance and debugging.
+In most architectures, this value will
+initially decrease from as high as 500 ppm to a nominal value in
+the range .01 to 0.1 ppm.
+If it remains high for some time after
+starting the daemon, something may be wrong with the local clock,
+or the value of the kernel variable
+.Va kern.clockrate.tick
+may be
+incorrect.
+.Pp
+The
+.Sq broadcastdelay
+shows the default broadcast delay,
+as set by the
+.Ic broadcastdelay
+configuration command.
+.Pp
+The
+.Sq authdelay
+shows the default authentication delay,
+as set by the
+.Ic authdelay
+configuration command.
+.It Ic sysstats
+Print statistics counters maintained in the protocol
+module.
+.It Ic memstats
+Print statistics counters related to memory allocation
+code.
+.It Ic iostats
+Print statistics counters maintained in the input\-output
+module.
+.It Ic timerstats
+Print statistics counters maintained in the timer/event queue
+support code.
+.It Ic reslist
+Obtain and print the server's restriction list.
+This list is
+(usually) printed in sorted order and may help to understand how
+the restrictions are applied.
+.It Ic monlist Op Ar version
+Obtain and print traffic counts collected and maintained by the
+monitor facility.
+The version number should not normally need to be
+specified.
+.It Ic clkbug Ar clock_peer_address Oo Ar ... Oc
+Obtain debugging information for a reference clock driver.
+This
+information is provided only by some clock drivers and is mostly
+undecodable without a copy of the driver source in hand.
+.El
+.Ss "Runtime Configuration Requests"
+All requests which cause state changes in the server are
+authenticated by the server using a configured NTP key (the
+facility can also be disabled by the server by not configuring a
+key).
+The key number and the corresponding key must also be made
+known to
+.Nm .
+This can be done using the
+.Ic keyid
+and
+.Ic passwd
+commands, the latter of which will prompt at the terminal for a
+password to use as the encryption key.
+You will also be prompted
+automatically for both the key number and password the first time a
+command which would result in an authenticated request to the
+server is given.
+Authentication not only provides verification that
+the requester has permission to make such changes, but also gives
+an extra degree of protection again transmission errors.
+.Pp
+Authenticated requests always include a timestamp in the packet
+data, which is included in the computation of the authentication
+code.
+This timestamp is compared by the server to its receive time
+stamp.
+If they differ by more than a small amount the request is
+rejected.
+This is done for two reasons.
+First, it makes simple
+replay attacks on the server, by someone who might be able to
+overhear traffic on your LAN, much more difficult.
+Second, it makes
+it more difficult to request configuration changes to your server
+from topologically remote hosts.
+While the reconfiguration facility
+will work well with a server on the local host, and may work
+adequately between time\-synchronized hosts on the same LAN, it will
+work very poorly for more distant hosts.
+As such, if reasonable
+passwords are chosen, care is taken in the distribution and
+protection of keys and appropriate source address restrictions are
+applied, the run time reconfiguration facility should provide an
+adequate level of security.
+.Pp
+The following commands all make authenticated requests.
+.Bl -tag -width indent
+.It Xo Ic addpeer Ar peer_address
+.Op Ar keyid
+.Op Ar version
+.Op Cm prefer
+.Xc
+Add a configured peer association at the given address and
+operating in symmetric active mode.
+Note that an existing
+association with the same peer may be deleted when this command is
+executed, or may simply be converted to conform to the new
+configuration, as appropriate.
+If the optional
+.Ar keyid
+is a
+nonzero integer, all outgoing packets to the remote server will
+have an authentication field attached encrypted with this key.
+If
+the value is 0 (or not given) no authentication will be done.
+The
+.Ar version
+can be 1, 2 or 3 and defaults to 3.
+The
+.Cm prefer
+keyword indicates a preferred peer (and thus will
+be used primarily for clock synchronisation if possible).
+The
+preferred peer also determines the validity of the PPS signal \- if
+the preferred peer is suitable for synchronisation so is the PPS
+signal.
+.It Xo Ic addserver Ar peer_address
+.Op Ar keyid
+.Op Ar version
+.Op Cm prefer
+.Xc
+Identical to the addpeer command, except that the operating
+mode is client.
+.It Xo Ic broadcast Ar peer_address
+.Op Ar keyid
+.Op Ar version
+.Op Cm prefer
+.Xc
+Identical to the addpeer command, except that the operating
+mode is broadcast.
+In this case a valid key identifier and key are
+required.
+The
+.Ar peer_address
+parameter can be the broadcast
+address of the local network or a multicast group address assigned
+to NTP.
+If a multicast address, a multicast\-capable kernel is
+required.
+.It Ic unconfig Ar peer_address Oo Ar ... Oc
+This command causes the configured bit to be removed from the
+specified peer(s).
+In many cases this will cause the peer
+association to be deleted.
+When appropriate, however, the
+association may persist in an unconfigured mode if the remote peer
+is willing to continue on in this fashion.
+.It Xo Ic fudge Ar peer_address
+.Op Cm time1
+.Op Cm time2
+.Op Ar stratum
+.Op Ar refid
+.Xc
+This command provides a way to set certain data for a reference
+clock.
+See the source listing for further information.
+.It Xo Ic enable
+.Oo
+.Cm auth | Cm bclient |
+.Cm calibrate | Cm kernel |
+.Cm monitor | Cm ntp |
+.Cm pps | Cm stats
+.Oc
+.Xc
+.It Xo Ic disable
+.Oo
+.Cm auth | Cm bclient |
+.Cm calibrate | Cm kernel |
+.Cm monitor | Cm ntp |
+.Cm pps | Cm stats
+.Oc
+.Xc
+These commands operate in the same way as the
+.Ic enable
+and
+.Ic disable
+configuration file commands of
+.Xr ntpd 8 .
+.Bl -tag -width indent
+.It Cm auth
+Enables the server to synchronize with unconfigured peers only
+if the peer has been correctly authenticated using either public key
+or private key cryptography.
+The default for this flag is enable.
+.It Cm bclient
+Enables the server to listen for a message from a broadcast or
+multicast server, as in the multicastclient command with
+default address.
+The default for this flag is disable.
+.It Cm calibrate
+Enables the calibrate feature for reference clocks.
+The default for this flag is disable.
+.It Cm kernel
+Enables the kernel time discipline, if available.
+The default for this flag is enable if support is available, otherwise disable.
+.It Cm monitor
+Enables the monitoring facility.
+See the documentation here about the
+.Cm monlist
+command or further information.
+The default for this flag is enable.
+.It Cm ntp
+Enables time and frequency discipline.
+In effect, this switch opens and closes the feedback loop,
+which is useful for testing.
+The default for this flag is enable.
+.It Cm pps
+Enables the pulse\-per\-second (PPS) signal when frequency
+and time is disciplined by the precision time kernel modifications.
+See the
+.Qq A Kernel Model for Precision Timekeeping
+(available as part of the HTML documentation
+provided in
+.Pa /usr/share/doc/ntp )
+page for further information.
+The default for this flag is disable.
+.It Cm stats
+Enables the statistics facility.
+See the
+.Sx Monitoring Options
+section of
+.Xr ntp.conf 5
+for further information.
+The default for this flag is disable.
+.El
+.It Xo Ic restrict Ar address Ar mask
+.Ar flag Oo Ar ... Oc
+.Xc
+This command operates in the same way as the
+.Ic restrict
+configuration file commands of
+.Xr ntpd 8 .
+.It Xo Ic unrestrict Ar address Ar mask
+.Ar flag Oo Ar ... Oc
+.Xc
+Unrestrict the matching entry from the restrict list.
+.It Xo Ic delrestrict Ar address Ar mask
+.Op Cm ntpport
+.Xc
+Delete the matching entry from the restrict list.
+.It Ic readkeys
+Causes the current set of authentication keys to be purged and
+a new set to be obtained by rereading the keys file (which must
+have been specified in the
+.Xr ntpd 8
+configuration file).
+This
+allows encryption keys to be changed without restarting the
+server.
+.It Ic trustedkey Ar keyid Oo Ar ... Oc
+.It Ic untrustedkey Ar keyid Oo Ar ... Oc
+These commands operate in the same way as the
+.Ic trustedkey
+and
+.Ic untrustedkey
+configuration file
+commands of
+.Xr ntpd 8 .
+.It Ic authinfo
+Returns information concerning the authentication module,
+including known keys and counts of encryptions and decryptions
+which have been done.
+.It Ic traps
+Display the traps set in the server.
+See the source listing for
+further information.
+.It Xo Ic addtrap Ar address
+.Op Ar port
+.Op Ar interface
+.Xc
+Set a trap for asynchronous messages.
+See the source listing
+for further information.
+.It Xo Ic clrtrap Ar address
+.Op Ar port
+.Op Ar interface
+.Xc
+Clear a trap for asynchronous messages.
+See the source listing
+for further information.
+.It Ic reset
+Clear the statistics counters in various modules of the server.
+See the source listing for further information.
+.El
+.Sh "ENVIRONMENT"
+See \fBOPTION PRESETS\fP for configuration environment variables.
+.Sh "FILES"
+See \fBOPTION PRESETS\fP for configuration files.
+.Sh "EXIT STATUS"
+One of the following exit values will be returned:
+.Bl -tag
+.It 0 " (EXIT_SUCCESS)"
+Successful program execution.
+.It 1 " (EXIT_FAILURE)"
+The operation failed or the command syntax was not valid.
+.It 66 " (EX_NOINPUT)"
+A specified configuration file could not be loaded.
+.It 70 " (EX_SOFTWARE)"
+libopts had an internal operational error. Please report
+it to autogen\-users@lists.sourceforge.net. Thank you.
+.El
+.Sh "SEE ALSO"
+.Xr ntp.conf 5 ,
+.Xr ntpd 8
+.Rs
+.%A David L. Mills
+.%T Network Time Protocol (Version 3)
+.%O RFC1305
+.Re
+.Sh AUTHORS
+The formatting directives in this document came from FreeBSD.
+.Sh "COPYRIGHT"
+Copyright (C) 1992\-2015 The University of Delaware and Network Time Foundation all rights reserved.
+This program is released under the terms of the NTP license, <http://ntp.org/license>.
+.Sh BUGS
+The
+.Nm
+utility is a crude hack.
+Much of the information it shows is
+deadly boring and could only be loved by its implementer.
+The
+program was designed so that new (and temporary) features were easy
+to hack in, at great expense to the program's ease of use.
+Despite
+this, the program is occasionally useful.
+.Pp
+Please report bugs to http://bugs.ntp.org .
+.Pp
+Please send bug reports to: http://bugs.ntp.org, bugs@ntp.org
+.Sh "NOTES"
+This manual page was \fIAutoGen\fP\-erated from the \fBntpdc\fP
+option definitions.
diff --git a/usr.sbin/ntp/doc/ntpq.8 b/usr.sbin/ntp/doc/ntpq.8
new file mode 100644
index 0000000..e71a84b
--- /dev/null
+++ b/usr.sbin/ntp/doc/ntpq.8
@@ -0,0 +1,966 @@
+.Dd January 7 2016
+.Dt NTPQ 8 User Commands
+.Os
+.\" EDIT THIS FILE WITH CAUTION (ntpq-opts.mdoc)
+.\"
+.\" $FreeBSD$
+.\"
+.\" It has been AutoGen-ed January 7, 2016 at 11:32:02 PM by AutoGen 5.18.5
+.\" From the definitions ntpq-opts.def
+.\" and the template file agmdoc-cmd.tpl
+.Sh NAME
+.Nm ntpq
+.Nd standard NTP query program
+.Sh SYNOPSIS
+.Nm
+.\" Mixture of short (flag) options and long options
+.Op Fl flags
+.Op Fl flag Op Ar value
+.Op Fl \-option\-name Ns Oo Oo Ns "=| " Oc Ns Ar value Oc
+[ host ...]
+.Pp
+.Sh DESCRIPTION
+The
+.Nm
+utility program is used to query NTP servers which
+implement the standard NTP mode 6 control message formats defined
+in Appendix B of the NTPv3 specification RFC1305, requesting
+information about current state and/or changes in that state.
+The same formats are used in NTPv4, although some of the
+variables have changed and new ones added. The description on this
+page is for the NTPv4 variables.
+The program may be run either in interactive mode or controlled using
+command line arguments.
+Requests to read and write arbitrary
+variables can be assembled, with raw and pretty\-printed output
+options being available.
+The
+.Nm
+utility can also obtain and print a
+list of peers in a common format by sending multiple queries to the
+server.
+If one or more request options is included on the command line
+when
+.Nm
+is executed, each of the requests will be sent
+to the NTP servers running on each of the hosts given as command
+line arguments, or on localhost by default.
+If no request options
+are given,
+.Nm
+will attempt to read commands from the
+standard input and execute these on the NTP server running on the
+first host given on the command line, again defaulting to localhost
+when no other host is specified.
+The
+.Nm
+utility will prompt for
+commands if the standard input is a terminal device.
+.Nm
+uses NTP mode 6 packets to communicate with the
+NTP server, and hence can be used to query any compatible server on
+the network which permits it.
+Note that since NTP is a UDP protocol
+this communication will be somewhat unreliable, especially over
+large distances in terms of network topology.
+The
+.Nm
+utility makes
+one attempt to retransmit requests, and will time requests out if
+the remote host is not heard from within a suitable timeout
+time.
+Specifying a
+command line option other than
+.Fl i
+or
+.Fl n
+will
+cause the specified query (queries) to be sent to the indicated
+host(s) immediately.
+Otherwise,
+.Nm
+will attempt to read
+interactive format commands from the standard input.
+.Ss "Internal Commands"
+Interactive format commands consist of a keyword followed by zero
+to four arguments.
+Only enough characters of the full keyword to
+uniquely identify the command need be typed.
+A
+number of interactive format commands are executed entirely within
+the
+.Nm
+utility itself and do not result in NTP mode 6
+requests being sent to a server.
+These are described following.
+.Bl -tag -width "? [command_keyword]" -compact -offset indent
+.It Ic ? Op Ar command_keyword
+.It Ic help Op Ar command_keyword
+A
+.Ql \&?
+by itself will print a list of all the command
+keywords known to this incarnation of
+.Nm .
+A
+.Ql \&?
+followed by a command keyword will print function and usage
+information about the command.
+This command is probably a better
+source of information about
+.Nm
+than this manual
+page.
+.It Ic addvars Ar variable_name Ns Xo Op Ic =value
+.Ic ...
+.Xc
+.It Ic rmvars Ar variable_name Ic ...
+.It Ic clearvars
+.It Ic showvars
+The data carried by NTP mode 6 messages consists of a list of
+items of the form
+.Ql variable_name=value ,
+where the
+.Ql =value
+is ignored, and can be omitted,
+in requests to the server to read variables.
+The
+.Nm
+utility maintains an internal list in which data to be included in control
+messages can be assembled, and sent using the
+.Ic readlist
+and
+.Ic writelist
+commands described below.
+The
+.Ic addvars
+command allows variables and their optional values to be added to
+the list.
+If more than one variable is to be added, the list should
+be comma\-separated and not contain white space.
+The
+.Ic rmvars
+command can be used to remove individual variables from the list,
+while the
+.Ic clearlist
+command removes all variables from the
+list.
+The
+.Ic showvars
+command displays the current list of optional variables.
+.It Ic authenticate Op yes | no
+Normally
+.Nm
+does not authenticate requests unless
+they are write requests.
+The command
+.Ql authenticate yes
+causes
+.Nm
+to send authentication with all requests it
+makes.
+Authenticated requests causes some servers to handle
+requests slightly differently, and can occasionally melt the CPU in
+fuzzballs if you turn authentication on before doing a
+.Ic peer
+display.
+The command
+.Ql authenticate
+causes
+.Nm
+to display whether or not
+.Nm
+is currently autheinticating requests.
+.It Ic cooked
+Causes output from query commands to be "cooked", so that
+variables which are recognized by
+.Nm
+will have their
+values reformatted for human consumption.
+Variables which
+.Nm
+thinks should have a decodable value but didn't are
+marked with a trailing
+.Ql \&? .
+.It Xo
+.Ic debug
+.Oo
+.Cm more |
+.Cm less |
+.Cm off
+.Oc
+.Xc
+With no argument, displays the current debug level.
+Otherwise, the debug level is changed to the indicated level.
+.It Ic delay Ar milliseconds
+Specify a time interval to be added to timestamps included in
+requests which require authentication.
+This is used to enable
+(unreliable) server reconfiguration over long delay network paths
+or between machines whose clocks are unsynchronized.
+Actually the
+server does not now require timestamps in authenticated requests,
+so this command may be obsolete.
+.It Ic exit
+Exit
+.Nm .
+.It Ic host Ar hostname
+Set the host to which future queries will be sent.
+.Ar hostname
+may be either a host name or a numeric address.
+.It Ic hostnames Op Cm yes | Cm no
+If
+.Cm yes
+is specified, host names are printed in
+information displays.
+If
+.Cm no
+is specified, numeric
+addresses are printed instead.
+The default is
+.Cm yes ,
+unless
+modified using the command line
+.Fl n
+switch.
+.It Ic keyid Ar keyid
+This command allows the specification of a key number to be
+used to authenticate configuration requests.
+This must correspond
+to the
+.Cm controlkey
+key number the server has been configured to use for this
+purpose.
+.It Ic keytype Xo Oo
+.Cm md5 |
+.Cm OpenSSLDigestType
+.Oc
+.Xc
+Specify the type of key to use for authenticating requests.
+.Cm md5
+is alway supported.
+If
+.Nm
+was built with OpenSSL support,
+any digest type supported by OpenSSL can also be provided.
+If no argument is given, the current
+.Ic keytype
+is displayed.
+.It Ic ntpversion Xo Oo
+.Cm 1 |
+.Cm 2 |
+.Cm 3 |
+.Cm 4
+.Oc
+.Xc
+Sets the NTP version number which
+.Nm
+claims in
+packets.
+Defaults to 3, and note that mode 6 control messages (and
+modes, for that matter) didn't exist in NTP version 1.
+There appear
+to be no servers left which demand version 1.
+With no argument, displays the current NTP version that will be used
+when communicating with servers.
+.It Ic passwd
+This command prompts you to type in a password (which will not
+be echoed) which will be used to authenticate configuration
+requests.
+The password must correspond to the key configured for
+use by the NTP server for this purpose if such requests are to be
+successful.
+.\" Not yet implemented.
+.\" .It Ic poll
+.\" .Op Ar n
+.\" .Op Ic verbose
+.\" Poll an NTP server in client mode
+.\" .Ar n
+.\" times.
+.It Ic quit
+Exit
+.Nm .
+.It Ic raw
+Causes all output from query commands is printed as received
+from the remote server.
+The only formating/interpretation done on
+the data is to transform nonascii data into a printable (but barely
+understandable) form.
+.It Ic timeout Ar milliseconds
+Specify a timeout period for responses to server queries.
+The
+default is about 5000 milliseconds.
+Note that since
+.Nm
+retries each query once after a timeout, the total waiting time for
+a timeout will be twice the timeout value set.
+.It Ic version
+Print the version of the
+.Nm
+program.
+.El
+.Ss "Control Message Commands"
+Association IDs are used to identify system, peer and clock variables.
+System variables are assigned an association ID of zero and system name space, while each association is assigned a nonzero association ID and peer namespace.
+Most control commands send a single mode\-6 message to the server and expect a single response message.
+The exceptions are the
+.Li peers
+command, which sends a series of messages,
+and the
+.Li mreadlist
+and
+.Li mreadvar
+commands, which iterate over a range of associations.
+.Bl -tag -width "something" -compact -offset indent
+.It Cm associations
+Display a list of mobilized associations in the form:
+.Dl ind assid status conf reach auth condition last_event cnt
+.Bl -column -offset indent ".Sy Variable" ".Sy Description"
+.It Sy String Ta Sy Description
+.It Li ind Ta index on this list
+.It Li assid Ta association ID
+.It Li status Ta peer status word
+.It Li conf Ta Li yes : persistent, Li no : ephemeral
+.It Li reach Ta Li yes : reachable, Li no : unreachable
+.It Li auth Ta Li ok , Li yes , Li bad and Li none
+.It Li condition Ta selection status (see the Li select field of the peer status word)
+.It Li last_event Ta event report (see the Li event field of the peer status word)
+.It Li cnt Ta event count (see the Li count field of the peer status word)
+.El
+.It Cm authinfo
+Display the authentication statistics.
+.It Cm clockvar Ar assocID Oo Ar name Ns Oo Cm = Ns Ar value Oc Oc Op ...
+.It Cm cv Ar assocID Oo Ar name Ns Oo Cm = Ns Ar value Oc Oc Op ...
+Display a list of clock variables for those associations supporting a reference clock.
+.It Cm :config Op ...
+Send the remainder of the command line, including whitespace, to the server as a run\-time configuration command in the same format as a line in the configuration file. This command is experimental until further notice and clarification. Authentication is of course required.
+.It Cm config\-from\-file Ar filename
+Send the each line of
+.Ar filename
+to the server as run\-time configuration commands in the same format as a line in the configuration file. This command is experimental until further notice and clarification. Authentication is required.
+.It Ic ifstats
+Display statistics for each local network address. Authentication is required.
+.It Ic iostats
+Display network and reference clock I/O statistics.
+.It Ic kerninfo
+Display kernel loop and PPS statistics. As with other ntpq output, times are in milliseconds. The precision value displayed is in milliseconds as well, unlike the precision system variable.
+.It Ic lassociations
+Perform the same function as the associations command, except display mobilized and unmobilized associations.
+.It Ic lopeers Xo
+.Oo Ic \-4 |
+.Ic \-6
+.Oc
+.Xc
+Obtain and print a list of all peers and clients showing
+.Ar dstadr
+(associated with any given IP version).
+.It Ic lpeers Xo
+.Oo Ic \-4 |
+.Ic \-6
+.Oc
+.Xc
+Print a peer spreadsheet for the appropriate IP version(s).
+.Ar dstadr
+(associated with any given IP version).
+.It Ic monstats
+Display monitor facility statistics.
+.It Ic mrulist Oo Ic limited | Ic kod | Ic mincount Ns = Ns Ar count | Ic laddr Ns = Ns Ar localaddr | Ic sort Ns = Ns Ar sortorder | Ic resany Ns = Ns Ar hexmask | Ic resall Ns = Ns Ar hexmask Oc
+Obtain and print traffic counts collected and maintained by the monitor facility.
+With the exception of
+.Cm sort Ns = Ns Ar sortorder ,
+the options filter the list returned by
+.Cm ntpd.
+The
+.Cm limited
+and
+.Cm kod
+options return only entries representing client addresses from which the last packet received triggered either discarding or a KoD response.
+The
+.Cm mincount Ns = Ns Ar count
+option filters entries representing less than
+.Ar count
+packets.
+The
+.Cm laddr Ns = Ns Ar localaddr
+option filters entries for packets received on any local address other than
+.Ar localaddr .
+.Cm resany Ns = Ns Ar hexmask
+and
+.Cm resall Ns = Ns Ar hexmask
+filter entries containing none or less than all, respectively, of the bits in
+.Ar hexmask ,
+which must begin with
+.Cm 0x .
+The
+.Ar sortorder
+defaults to
+.Cm lstint
+and may be any of
+.Cm addr ,
+.Cm count ,
+.Cm avgint ,
+.Cm lstint ,
+or any of those preceded by a minus sign (hyphen) to reverse the sort order.
+The output columns are:
+.Bl -tag -width "something" -compact -offset indent
+.It Column
+Description
+.It Ic lstint
+Interval in s between the receipt of the most recent packet from this address and the completion of the retrieval of the MRU list by
+.Nm .
+.It Ic avgint
+Average interval in s between packets from this address.
+.It Ic rstr
+Restriction flags associated with this address.
+Most are copied unchanged from the matching
+.Ic restrict
+command, however 0x400 (kod) and 0x20 (limited) flags are cleared unless the last packet from this address triggered a rate control response.
+.It Ic r
+Rate control indicator, either
+a period,
+.Ic L
+or
+.Ic K
+for no rate control response,
+rate limiting by discarding, or rate limiting with a KoD response, respectively.
+.It Ic m
+Packet mode.
+.It Ic v
+Packet version number.
+.It Ic count
+Packets received from this address.
+.It Ic rport
+Source port of last packet from this address.
+.It Ic remote address
+DNS name, numeric address, or address followed by
+claimed DNS name which could not be verified in parentheses.
+.El
+.It Ic mreadvar assocID assocID Oo Ar variable_name Ns Oo = Ns Ar value Oc Oc ...
+.It Ic mrv assocID assocID Oo Ar variable_name Ns Oo = Ns Ar value Oc Oc ...
+Perform the same function as the
+.Ic readvar
+command, except for a range of association IDs.
+This range is determined from the association list cached by the most recent
+.Ic associations
+command.
+.It Ic opeers Xo
+.Oo Ic \-4 |
+.Ic \-6
+.Oc
+.Xc
+Obtain and print the old\-style list of all peers and clients showing
+.Ar dstadr
+(associated with any given IP version),
+rather than the
+.Ar refid .
+.It Ic passociations
+Perform the same function as the
+.Ic associations
+command,
+except that it uses previously stored data rather than making a new query.
+.It Ic peers
+Display a list of peers in the form:
+.Dl [tally]remote refid st t when pool reach delay offset jitter
+.Bl -tag -width "something" -compact -offset indent
+.It Variable
+Description
+.It Ic [tally]
+single\-character code indicating current value of the
+.Ic select
+field of the
+.Lk decode.html#peer "peer status word"
+.It Ic remote
+host name (or IP number) of peer.
+The value displayed will be truncated to 15 characters unless the
+.Fl w
+flag is given, in which case the full value will be displayed
+on the first line,
+and the remaining data is displayed on the next line.
+.It Ic refid
+association ID or
+.Lk decode.html#kiss "'kiss code"
+.It Ic st
+stratum
+.It Ic t
+.Ic u :
+unicast or manycast client,
+.Ic b :
+broadcast or multicast client,
+.Ic l :
+local (reference clock),
+.Ic s :
+symmetric (peer),
+.Ic A :
+manycast server,
+.Ic B :
+broadcast server,
+.Ic M :
+multicast server
+.It Ic when
+sec/min/hr since last received packet
+.It Ic poll
+poll interval (log2 s)
+.It Ic reach
+reach shift register (octal)
+.It Ic delay
+roundtrip delay
+.It Ic offset
+offset of server relative to this host
+.It Ic jitter
+jitter
+.El
+.It Ic apeers
+Display a list of peers in the form:
+.Dl [tally]remote refid assid st t when pool reach delay offset jitter
+where the output is just like the
+.Ic peers
+command except that the
+.Ic refid
+is displayed in hex format and the association number is also displayed.
+.It Ic pstats Ar assocID
+Show the statistics for the peer with the given
+.Ar assocID .
+.It Ic readlist Ar assocID
+.It Ic rl Ar assocID
+Read the system or peer variables included in the variable list.
+.It Ic readvar Ar assocID Ar name Ns Oo Ns = Ns Ar value Oc Oo , ... Oc
+.It Ic rv Ar assocID Ar name Ns Oo Ns = Ns Ar value Oc Oo , ... Oc
+Display the specified variables.
+If
+.Ar assocID
+is zero, the variables are from the
+.Sx System Variables
+name space, otherwise they are from the
+.Sx Peer Variables
+name space.
+The
+.Ar assocID
+is required, as the same name can occur in both spaces.
+If no
+.Ar name
+is included, all operative variables in the name space are displayed.
+In this case only, if the
+.Ar assocID
+is omitted, it is assumed zero.
+Multiple names are specified with comma separators and without whitespace.
+Note that time values are represented in milliseconds
+and frequency values in parts\-per\-million (PPM).
+Some NTP timestamps are represented in the format
+YYYYMMDDTTTT ,
+where YYYY is the year,
+MM the month of year,
+DD the day of month and
+TTTT the time of day.
+.It Ic reslist
+Show the access control (restrict) list for
+.Nm .
+.It Ic saveconfig Ar filename
+Write the current configuration,
+including any runtime modifications given with
+.Ic :config
+or
+.Ic config\-from\-file ,
+to the ntpd host's file
+.Ar filename .
+This command will be rejected by the server unless
+.Lk miscopt.html#saveconfigdir "saveconfigdir"
+appears in the
+.Ic ntpd
+configuration file.
+.Ar filename
+can use
+.Xr strftime
+format specifies to substitute the current date and time, for example,
+.Ic q]saveconfig ntp\-%Y%m%d\-%H%M%S.confq] .
+The filename used is stored in system variable
+.Ic savedconfig .
+Authentication is required.
+.It Ic timerstats
+Display interval timer counters.
+.It Ic writelist Ar assocID
+Write the system or peer variables included in the variable list.
+.It Ic writevar Ar assocID Ar name Ns = Ns Ar value Op , ...
+Write the specified variables.
+If the
+.Ar assocID
+is zero, the variables are from the
+.Sx System Variables
+name space, otherwise they are from the
+.Sx Peer Variables
+name space.
+The
+.Ar assocID
+is required, as the same name can occur in both spaces.
+.It Ic sysinfo
+Display operational summary.
+.It Ic sysstats
+Print statistics counters maintained in the protocol module.
+.El
+.Ss Status Words and Kiss Codes
+The current state of the operating program is shown
+in a set of status words
+maintained by the system.
+Status information is also available on a per\-association basis.
+These words are displayed in the
+.Ic rv
+and
+.Ic as
+commands both in hexadecimal and in decoded short tip strings.
+The codes, tips and short explanations are documented on the
+.Lk decode.html "Event Messages and Status Words"
+page.
+The page also includes a list of system and peer messages,
+the code for the latest of which is included in the status word.
+.Pp
+Information resulting from protocol machine state transitions
+is displayed using an informal set of ASCII strings called
+.Lk decode.html#kiss "kiss codes" .
+The original purpose was for kiss\-o'\-death (KoD) packets
+sent by the server to advise the client of an unusual condition.
+They are now displayed, when appropriate,
+in the reference identifier field in various billboards.
+.Ss System Variables
+The following system variables appear in the
+.Ic rv
+billboard.
+Not all variables are displayed in some configurations.
+.Bl -tag -width "something" -compact -offset indent
+.It Variable
+Description
+.It Ic status
+.Lk decode.html#sys "system status word"
+.It Ic version
+NTP software version and build time
+.It Ic processor
+hardware platform and version
+.It Ic system
+operating system and version
+.It Ic leap
+leap warning indicator (0\-3)
+.It Ic stratum
+stratum (1\-15)
+.It Ic precision
+precision (log2 s)
+.It Ic rootdelay
+total roundtrip delay to the primary reference clock
+.It Ic rootdisp
+total dispersion to the primary reference clock
+.It Ic peer
+system peer association ID
+.It Ic tc
+time constant and poll exponent (log2 s) (3\-17)
+.It Ic mintc
+minimum time constant (log2 s) (3\-10)
+.It Ic clock
+date and time of day
+.It Ic refid
+reference ID or
+.Lk decode.html#kiss "kiss code"
+.It Ic reftime
+reference time
+.It Ic offset
+combined offset of server relative to this host
+.It Ic sys_jitter
+combined system jitter
+.It Ic frequency
+frequency offset (PPM) relative to hardware clock
+.It Ic clk_wander
+clock frequency wander (PPM)
+.It Ic clk_jitter
+clock jitter
+.It Ic tai
+TAI\-UTC offset (s)
+.It Ic leapsec
+NTP seconds when the next leap second is/was inserted
+.It Ic expire
+NTP seconds when the NIST leapseconds file expires
+.El
+The jitter and wander statistics are exponentially\-weighted RMS averages.
+The system jitter is defined in the NTPv4 specification;
+the clock jitter statistic is computed by the clock discipline module.
+.Pp
+When the NTPv4 daemon is compiled with the OpenSSL software library,
+additional system variables are displayed,
+including some or all of the following,
+depending on the particular Autokey dance:
+.Bl -tag -width "something" -compact -offset indent
+.It Variable
+Description
+.It Ic host
+Autokey host name for this host
+.It Ic ident
+Autokey group name for this host
+.It Ic flags
+host flags (see Autokey specification)
+.It Ic digest
+OpenSSL message digest algorithm
+.It Ic signature
+OpenSSL digest/signature scheme
+.It Ic update
+NTP seconds at last signature update
+.It Ic cert
+certificate subject, issuer and certificate flags
+.It Ic until
+NTP seconds when the certificate expires
+.El
+.Ss Peer Variables
+The following peer variables appear in the
+.Ic rv
+billboard for each association.
+Not all variables are displayed in some configurations.
+.Bl -tag -width "something" -compact -offset indent
+.It Variable
+Description
+.It Ic associd
+association ID
+.It Ic status
+.Lk decode.html#peer "peer status word"
+.It Ic srcadr
+source (remote) IP address
+.It Ic srcport
+source (remote) port
+.It Ic dstadr
+destination (local) IP address
+.It Ic dstport
+destination (local) port
+.It Ic leap
+leap indicator (0\-3)
+.It Ic stratum
+stratum (0\-15)
+.It Ic precision
+precision (log2 s)
+.It Ic rootdelay
+total roundtrip delay to the primary reference clock
+.It Ic rootdisp
+total root dispersion to the primary reference clock
+.It Ic refid
+reference ID or
+.Lk decode.html#kiss "kiss code"
+.It Ic reftime
+reference time
+.It Ic reach
+reach register (octal)
+.It Ic unreach
+unreach counter
+.It Ic hmode
+host mode (1\-6)
+.It Ic pmode
+peer mode (1\-5)
+.It Ic hpoll
+host poll exponent (log2 s) (3\-17)
+.It Ic ppoll
+peer poll exponent (log2 s) (3\-17)
+.It Ic headway
+headway (see
+.Lk rate.html "Rate Management and the Kiss\-o'\-Death Packet" )
+.It Ic flash
+.Lk decode.html#flash "flash status word"
+.It Ic offset
+filter offset
+.It Ic delay
+filter delay
+.It Ic dispersion
+filter dispersion
+.It Ic jitter
+filter jitter
+.It Ic ident
+Autokey group name for this association
+.It Ic bias
+unicast/broadcast bias
+.It Ic xleave
+interleave delay (see
+.Lk xleave.html "NTP Interleaved Modes" )
+.El
+The
+.Ic bias
+variable is calculated when the first broadcast packet is received
+after the calibration volley.
+It represents the offset of the broadcast subgraph relative to the unicast subgraph.
+The
+.Ic xleave
+variable appears only for the interleaved symmetric and interleaved modes.
+It represents the internal queuing, buffering and transmission delays
+for the preceding packet.
+.Pp
+When the NTPv4 daemon is compiled with the OpenSSL software library,
+additional peer variables are displayed, including the following:
+.Bl -tag -width "something" -compact -offset indent
+.It Variable
+Description
+.It Ic flags
+peer flags (see Autokey specification)
+.It Ic host
+Autokey server name
+.It Ic flags
+peer flags (see Autokey specification)
+.It Ic signature
+OpenSSL digest/signature scheme
+.It Ic initsequence
+initial key ID
+.It Ic initkey
+initial key index
+.It Ic timestamp
+Autokey signature timestamp
+.El
+.Ss Clock Variables
+The following clock variables appear in the
+.Ic cv
+billboard for each association with a reference clock.
+Not all variables are displayed in some configurations.
+.Bl -tag -width "something" -compact -offset indent
+.It Variable
+Description
+.It Ic associd
+association ID
+.It Ic status
+.Lk decode.html#clock "clock status word"
+.It Ic device
+device description
+.It Ic timecode
+ASCII time code string (specific to device)
+.It Ic poll
+poll messages sent
+.It Ic noreply
+no reply
+.It Ic badformat
+bad format
+.It Ic baddata
+bad date or time
+.It Ic fudgetime1
+fudge time 1
+.It Ic fudgetime2
+fudge time 2
+.It Ic stratum
+driver stratum
+.It Ic refid
+driver reference ID
+.It Ic flags
+driver flags
+.El
+.Sh "OPTIONS"
+.Bl -tag
+.It Fl 4 , Fl \-ipv4
+Force IPv4 DNS name resolution.
+This option must not appear in combination with any of the following options:
+ipv6.
+.sp
+Force DNS resolution of following host names on the command line
+to the IPv4 namespace.
+.It Fl 6 , Fl \-ipv6
+Force IPv6 DNS name resolution.
+This option must not appear in combination with any of the following options:
+ipv4.
+.sp
+Force DNS resolution of following host names on the command line
+to the IPv6 namespace.
+.It Fl c Ar cmd , Fl \-command Ns = Ns Ar cmd
+run a command and exit.
+This option may appear an unlimited number of times.
+.sp
+The following argument is interpreted as an interactive format command
+and is added to the list of commands to be executed on the specified
+host(s).
+.It Fl d , Fl \-debug\-level
+Increase debug verbosity level.
+This option may appear an unlimited number of times.
+.sp
+.It Fl D Ar number , Fl \-set\-debug\-level Ns = Ns Ar number
+Set the debug verbosity level.
+This option may appear an unlimited number of times.
+This option takes an integer number as its argument.
+.sp
+.It Fl i , Fl \-interactive
+Force ntpq to operate in interactive mode.
+This option must not appear in combination with any of the following options:
+command, peers.
+.sp
+Force \fBntpq\fP to operate in interactive mode.
+Prompts will be written to the standard output and
+commands read from the standard input.
+.It Fl n , Fl \-numeric
+numeric host addresses.
+.sp
+Output all host addresses in dotted\-quad numeric format rather than
+converting to the canonical host names.
+.It Fl \-old\-rv
+Always output status line with readvar.
+.sp
+By default, \fBntpq\fP now suppresses the \fBassocid=...\fP
+line that precedes the output of \fBreadvar\fP
+(alias \fBrv\fP) when a single variable is requested, such as
+\fBntpq \-c "rv 0 offset"\fP.
+This option causes \fBntpq\fP to include both lines of output
+for a single\-variable \fBreadvar\fP.
+Using an environment variable to
+preset this option in a script will enable both older and
+newer \fBntpq\fP to behave identically in this regard.
+.It Fl p , Fl \-peers
+Print a list of the peers.
+This option must not appear in combination with any of the following options:
+interactive.
+.sp
+Print a list of the peers known to the server as well as a summary
+of their state. This is equivalent to the 'peers' interactive command.
+.It Fl w , Fl \-wide
+Display the full 'remote' value.
+.sp
+Display the full value of the 'remote' value. If this requires
+more than 15 characters, display the full value, emit a newline,
+and continue the data display properly indented on the next line.
+.It Fl \&? , Fl \-help
+Display usage information and exit.
+.It Fl \&! , Fl \-more\-help
+Pass the extended usage information through a pager.
+.It Fl > Oo Ar cfgfile Oc , Fl \-save\-opts Oo Ns = Ns Ar cfgfile Oc
+Save the option state to \fIcfgfile\fP. The default is the \fIlast\fP
+configuration file listed in the \fBOPTION PRESETS\fP section, below.
+The command will exit after updating the config file.
+.It Fl < Ar cfgfile , Fl \-load\-opts Ns = Ns Ar cfgfile , Fl \-no\-load\-opts
+Load options from \fIcfgfile\fP.
+The \fIno\-load\-opts\fP form will disable the loading
+of earlier config/rc/ini files. \fI\-\-no\-load\-opts\fP is handled early,
+out of order.
+.It Fl \-version Op Brq Ar v|c|n
+Output version of program and exit. The default mode is `v', a simple
+version. The `c' mode will print copyright information and `n' will
+print the full copyright notice.
+.El
+.Sh "OPTION PRESETS"
+Any option that is not marked as \fInot presettable\fP may be preset
+by loading values from configuration ("RC" or ".INI") file(s) and values from
+environment variables named:
+.nf
+ \fBNTPQ_<option\-name>\fP or \fBNTPQ\fP
+.fi
+.ad
+The environmental presets take precedence (are processed later than)
+the configuration files.
+The \fIhomerc\fP files are "\fI$HOME\fP", and "\fI.\fP".
+If any of these are directories, then the file \fI.ntprc\fP
+is searched for within those directories.
+.Sh "ENVIRONMENT"
+See \fBOPTION PRESETS\fP for configuration environment variables.
+.Sh "FILES"
+See \fBOPTION PRESETS\fP for configuration files.
+.Sh "EXIT STATUS"
+One of the following exit values will be returned:
+.Bl -tag
+.It 0 " (EXIT_SUCCESS)"
+Successful program execution.
+.It 1 " (EXIT_FAILURE)"
+The operation failed or the command syntax was not valid.
+.It 66 " (EX_NOINPUT)"
+A specified configuration file could not be loaded.
+.It 70 " (EX_SOFTWARE)"
+libopts had an internal operational error. Please report
+it to autogen\-users@lists.sourceforge.net. Thank you.
+.El
+.Sh "AUTHORS"
+The University of Delaware and Network Time Foundation
+.Sh "COPYRIGHT"
+Copyright (C) 1992\-2015 The University of Delaware and Network Time Foundation all rights reserved.
+This program is released under the terms of the NTP license, <http://ntp.org/license>.
+.Sh "BUGS"
+Please send bug reports to: http://bugs.ntp.org, bugs@ntp.org
+.Sh "NOTES"
+This manual page was \fIAutoGen\fP\-erated from the \fBntpq\fP
+option definitions.
diff --git a/usr.sbin/ntp/doc/ntptime.8 b/usr.sbin/ntp/doc/ntptime.8
new file mode 100644
index 0000000..bb3b41a
--- /dev/null
+++ b/usr.sbin/ntp/doc/ntptime.8
@@ -0,0 +1,67 @@
+.\"
+.\" $FreeBSD$
+.\"
+.Dd April 27, 2015
+.Dt NTPTIME 8
+.Os
+.Sh NAME
+.Nm ntptime
+.Nd read kernel time variables
+.Sh SYNOPSIS
+.Nm
+.Op Fl chr
+.Op Fl e Ar est_error
+.Op Fl f Ar frequency
+.Op Fl m Ar max_error
+.Op Fl o Ar offset
+.Op Fl s Ar status
+.Op Fl t Ar time_constant
+.Sh DESCRIPTION
+The
+.Nm
+utility is useful only with special kernels
+described in the
+.Qo
+A Kernel Model for Precision Timekeeping
+.Qc
+page
+(available as part of the HTML documentation
+provided in
+.Pa /usr/share/doc/ntp ) .
+It reads and displays time-related kernel variables
+using the
+.Fn gettime
+and
+.Xr adjtime 2
+system calls if available.
+A similar display can be obtained using the
+.Xr ntpdc 8
+program's
+.Ic kerninfo
+command.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl c
+Display the execution time of
+.Nm
+itself.
+.It Fl e Ar est_error
+Specify estimated error, in microseconds.
+.It Fl f Ar frequency
+Specify frequency offset, in parts per million.
+.It Fl h
+Display help information.
+.It Fl m Ar max_error
+Specify max possible errors, in microseconds.
+.It Fl o Ar offset
+Specify clock offset, in microseconds.
+.It Fl r
+Display Unix and NTP times in raw format.
+.It Fl s Ar status
+.It Fl t Ar time_constant
+Specify time constant, an integer in the range 0-4.
+.El
+.Sh SEE ALSO
+.Xr adjtime 2 ,
+.Xr ntpdc 8
diff --git a/usr.sbin/ntp/doc/ntptrace.8 b/usr.sbin/ntp/doc/ntptrace.8
new file mode 100644
index 0000000..40cb719
--- /dev/null
+++ b/usr.sbin/ntp/doc/ntptrace.8
@@ -0,0 +1,93 @@
+.Dd February 4 2015
+.Dt NTPTRACE 8 User Commands
+.Os
+.\" EDIT THIS FILE WITH CAUTION (ntptrace-opts.mdoc)
+.\"
+.\" $FreeBSD$
+.\"
+.\" It has been AutoGen-ed February 4, 2015 at 02:37:48 AM by AutoGen 5.18.5pre4
+.\" From the definitions ntptrace-opts.def
+.\" and the template file agmdoc-cmd.tpl
+.Sh NAME
+.Nm ntptrace
+.Nd Trace peers of an NTP server
+.Sh SYNOPSIS
+.Nm
+.\" Mixture of short (flag) options and long options
+.Op Fl flags
+.Op Fl flag Op Ar value
+.Op Fl \-option\-name Ns Oo Oo Ns "=| " Oc Ns Ar value Oc
+[host]
+.Pp
+.Sh DESCRIPTION
+\fBntptrace\fP is a perl script that uses the ntpq utility program to follow
+the chain of NTP servers from a given host back to the primary time source. For
+ntptrace to work properly, each of these servers must implement the NTP Control
+and Monitoring Protocol specified in RFC 1305 and enable NTP Mode 6 packets.
+.sp
+If given no arguments, ntptrace starts with localhost. Here is an example of
+the output from ntptrace:
+.sp
+.Bd -literal -offset indent
+% ntptrace localhost: stratum 4, offset 0.0019529, synch distance 0.144135
+server2ozo.com: stratum 2, offset 0.0124263, synch distance 0.115784 usndh.edu:
+stratum 1, offset 0.0019298, synch distance 0.011993, refid 'WWVB'
+.Ed
+.sp
+On each line, the fields are (left to right): the host name, the host stratum,
+the time offset between that host and the local host (as measured by
+\fBntptrace\fP; this is why it is not always zero for "localhost"), the host
+synchronization distance, and (only for stratum\-1 servers) the reference clock
+ID. All times are given in seconds. Note that the stratum is the server hop
+count to the primary source, while the synchronization distance is the
+estimated error relative to the primary source. These terms are precisely
+defined in RFC\-1305.
+.Sh "OPTIONS"
+.Bl -tag
+.It Fl n , Fl \-numeric
+Print IP addresses instead of hostnames.
+.sp
+Output hosts as dotted\-quad numeric format rather than converting to
+the canonical host names.
+.It Fl m Ar number , Fl \-max\-hosts Ns = Ns Ar number
+Maximum number of peers to trace.
+This option takes an integer number as its argument.
+The default
+.Ar number
+for this option is:
+.ti +4
+ 99
+.sp
+This option has not been fully documented.
+.It Fl r Ar string , Fl \-host Ns = Ns Ar string
+Single remote host.
+The default
+.Ar string
+for this option is:
+.ti +4
+ 127.0.0.1
+.sp
+This option has not been fully documented.
+.It Fl \&? , Fl \-help
+Display usage information and exit.
+.It Fl \&! , Fl \-more\-help
+Pass the extended usage information through a pager.
+.It Fl v Op Brq Ar v|c|n Fl \-version Op Brq Ar v|c|n
+Output version of program and exit. The default mode is `v', a simple
+version. The `c' mode will print copyright information and `n' will
+print the full copyright notice.
+.El
+.Sh "EXIT STATUS"
+One of the following exit values will be returned:
+.Bl -tag
+.It 0 " (EXIT_SUCCESS)"
+Successful program execution.
+.It 1 " (EXIT_FAILURE)"
+The operation failed or the command syntax was not valid.
+.It 70 " (EX_SOFTWARE)"
+libopts had an internal operational error. Please report
+it to autogen\-users@lists.sourceforge.net. Thank you.
+.El
+.Sh "NOTES"
+This manual page was \fIAutoGen\fP\-erated from the \fBntptrace\fP
+option definitions.
diff --git a/usr.sbin/ntp/doc/pic/Makefile b/usr.sbin/ntp/doc/pic/Makefile
new file mode 100644
index 0000000..11bcab6
--- /dev/null
+++ b/usr.sbin/ntp/doc/pic/Makefile
@@ -0,0 +1,27 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+FILESDIR= ${SHAREDIR}/doc/ntp/pic
+
+.if ${MK_HTML} != "no"
+FILES= 9400n.jpg alice11.gif alice13.gif alice15.gif alice23.gif \
+ alice31.gif alice32.gif alice35.gif alice38.gif alice44.gif \
+ alice47.gif alice51.gif alice61.gif barnstable.gif beaver.gif \
+ boom3.gif boom3a.gif boom4.gif broad.gif bustardfly.gif c51.jpg \
+ description.jpg discipline.gif dogsnake.gif driver29.gif \
+ driver43_1.gif driver43_2.jpg fg6021.gif fg6039.jpg fig_3_1.gif \
+ flatheads.gif flt1.gif flt2.gif flt3.gif flt4.gif flt5.gif flt6.gif \
+ flt7.gif flt8.gif flt9.gif freq1211.gif gadget.jpg gps167.jpg \
+ group.gif hornraba.gif igclock.gif neoclock4x.gif offset1211.gif \
+ oncore_evalbig.gif oncore_remoteant.jpg oncore_utplusbig.gif oz2.gif \
+ panda.gif pd_om006.gif pd_om011.gif peer.gif pogo.gif pogo1a.gif \
+ pogo3a.gif pogo4.gif pogo5.gif pogo6.gif pogo7.gif pogo8.gif \
+ pzf509.jpg pzf511.jpg rabbit.gif radio2.jpg sheepb.jpg stack1a.jpg \
+ stats.gif sx5.gif thunderbolt.jpg time1.gif tonea.gif tribeb.gif \
+ wingdorothy.gif
+.endif
+
+.PATH: ${.CURDIR}/../../../../contrib/ntp/html/pic
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ntp/doc/pic/Makefile.depend b/usr.sbin/ntp/doc/pic/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/ntp/doc/pic/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ntp/doc/scripts/Makefile b/usr.sbin/ntp/doc/scripts/Makefile
new file mode 100644
index 0000000..13adda7
--- /dev/null
+++ b/usr.sbin/ntp/doc/scripts/Makefile
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+FILESDIR= ${SHAREDIR}/doc/ntp/scripts
+
+.if ${MK_HTML} != "no"
+FILES= accopt.txt audio.txt authopt.txt clockopt.txt command.txt config.txt \
+ confopt.txt external.txt footer.txt hand.txt install.txt manual.txt \
+ misc.txt miscopt.txt monopt.txt refclock.txt special.txt style.css
+.endif
+
+.PATH: ${.CURDIR}/../../../../contrib/ntp/html/scripts
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ntp/doc/scripts/Makefile.depend b/usr.sbin/ntp/doc/scripts/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/ntp/doc/scripts/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ntp/doc/sntp.8 b/usr.sbin/ntp/doc/sntp.8
new file mode 100644
index 0000000..0d2dd64
--- /dev/null
+++ b/usr.sbin/ntp/doc/sntp.8
@@ -0,0 +1,314 @@
+.Dd January 7 2016
+.Dt SNTP 8 User Commands
+.Os
+.\" EDIT THIS FILE WITH CAUTION (sntp-opts.mdoc)
+.\"
+.\" $FreeBSD$
+.\"
+.\" It has been AutoGen-ed January 7, 2016 at 11:23:27 PM by AutoGen 5.18.5
+.\" From the definitions sntp-opts.def
+.\" and the template file agmdoc-cmd.tpl
+.Sh NAME
+.Nm sntp
+.Nd standard Simple Network Time Protocol client program
+.Sh SYNOPSIS
+.Nm
+.\" Mixture of short (flag) options and long options
+.Op Fl flags
+.Op Fl flag Op Ar value
+.Op Fl \-option\-name Ns Oo Oo Ns "=| " Oc Ns Ar value Oc
+[ hostname\-or\-IP ...]
+.Pp
+.Sh DESCRIPTION
+.Nm
+can be used as an SNTP client to query a NTP or SNTP server and either display
+the time or set the local system's time (given suitable privilege). It can be
+run as an interactive command or from a
+.Ic cron
+job.
+NTP (the Network Time Protocol) and SNTP (the Simple Network Time Protocol)
+are defined and described by RFC 5905.
+.Pp
+The default is to write the estimated correct local date and time (i.e. not
+UTC) to the standard output in a format like:
+.Ic "'1996\-10\-15 20:17:25.123 (+0800) +4.567 +/\- 0.089 [host] IP sN'"
+where the
+.Ic "'(+0800)'"
+means that to get to UTC from the reported local time one must
+add 8 hours and 0 minutes,
+the
+.Ic "'+4.567'"
+indicates the local clock is 4.567 seconds behind the correct time
+(so 4.567 seconds must be added to the local clock to get it to be correct).
+Note that the number of decimals printed for this value will change
+based on the reported precision of the server.
+.Ic "'+/\- 0.089'"
+is the reported
+.Em synchronization distance
+(in seconds), which represents the maximum error due to all causes.
+If the server does not report valid data needed to calculate the
+synchronization distance, this will be reported as
+.Ic "'+/\- ?'" .
+If the
+.Em host
+is different from the
+.Em IP ,
+both will be displayed.
+Otherwise, only the
+.Em IP
+is displayed.
+Finally, the
+.Em stratum
+of the host is reported
+and the leap indicator is decoded and displayed.
+.Sh "OPTIONS"
+.Bl -tag
+.It Fl 4 , Fl \-ipv4
+Force IPv4 DNS name resolution.
+This option must not appear in combination with any of the following options:
+ipv6.
+.sp
+Force DNS resolution of the following host names on the command line
+to the IPv4 namespace.
+.It Fl 6 , Fl \-ipv6
+Force IPv6 DNS name resolution.
+This option must not appear in combination with any of the following options:
+ipv4.
+.sp
+Force DNS resolution of the following host names on the command line
+to the IPv6 namespace.
+.It Fl a Ar auth\-keynumber , Fl \-authentication Ns = Ns Ar auth\-keynumber
+Enable authentication with the key \fBauth\-keynumber\fP.
+This option takes an integer number as its argument.
+.sp
+Enable authentication using the key specified in this option's
+argument. The argument of this option is the \fBkeyid\fP, a
+number specified in the \fBkeyfile\fP as this key's identifier.
+See the \fBkeyfile\fP option (\fB\-k\fP) for more details.
+.It Fl b Ar broadcast\-address , Fl \-broadcast Ns = Ns Ar broadcast\-address
+Listen to the address specified for broadcast time sync.
+This option may appear an unlimited number of times.
+.sp
+If specified \fBsntp\fP will listen to the specified address
+for NTP broadcasts. The default maximum wait time
+can (and probably should) be modified with \fB\-t\fP.
+.It Fl c Ar host\-name , Fl \-concurrent Ns = Ns Ar host\-name
+Concurrently query all IPs returned for host\-name.
+This option may appear an unlimited number of times.
+.sp
+Requests from an NTP "client" to a "server" should never be sent
+more rapidly than one every 2 seconds. By default, any IPs returned
+as part of a DNS lookup are assumed to be for a single instance of
+\fBntpd\fP, and therefore \fBsntp\fP will send queries to these IPs
+one after another, with a 2\-second gap in between each query.
+.sp
+The \fB\-c\fP or \fB\-\-concurrent\fP flag says that any IPs
+returned for the DNS lookup of the supplied host\-name are on
+different machines, so we can send concurrent queries.
+.It Fl d , Fl \-debug\-level
+Increase debug verbosity level.
+This option may appear an unlimited number of times.
+.sp
+.It Fl D Ar number , Fl \-set\-debug\-level Ns = Ns Ar number
+Set the debug verbosity level.
+This option may appear an unlimited number of times.
+This option takes an integer number as its argument.
+.sp
+.It Fl g Ar milliseconds , Fl \-gap Ns = Ns Ar milliseconds
+The gap (in milliseconds) between time requests.
+This option takes an integer number as its argument.
+The default
+.Ar milliseconds
+for this option is:
+.ti +4
+ 50
+.sp
+Since we're only going to use the first valid response we get and
+there is benefit to specifying a good number of servers to query,
+separate the queries we send out by the specified number of
+milliseconds.
+.It Fl K Ar file\-name , Fl \-kod Ns = Ns Ar file\-name
+KoD history filename.
+The default
+.Ar file\-name
+for this option is:
+.ti +4
+ /var/db/ntp\-kod
+.sp
+Specifies the filename to be used for the persistent history of KoD
+responses received from servers. If the file does not exist, a
+warning message will be displayed. The file will not be created.
+.It Fl k Ar file\-name , Fl \-keyfile Ns = Ns Ar file\-name
+Look in this file for the key specified with \fB\-a\fP.
+.sp
+This option specifies the keyfile.
+\fBsntp\fP will search for the key specified with \fB\-a\fP
+\fIkeyno\fP in this file. See \fBntp.keys(5)\fP for more
+information.
+.It Fl l Ar file\-name , Fl \-logfile Ns = Ns Ar file\-name
+Log to specified logfile.
+.sp
+This option causes the client to write log messages to the specified
+\fIlogfile\fP.
+.It Fl M Ar number , Fl \-steplimit Ns = Ns Ar number
+Adjustments less than \fBsteplimit\fP msec will be slewed.
+This option takes an integer number as its argument.
+The value of
+.Ar number
+is constrained to being:
+.in +4
+.nf
+.na
+greater than or equal to 0
+.fi
+.in -4
+.sp
+If the time adjustment is less than \fIsteplimit\fP milliseconds,
+slew the amount using \fBadjtime(2)\fP. Otherwise, step the
+correction using \fBsettimeofday(2)\fP. The default value is 0,
+which means all adjustments will be stepped. This is a feature, as
+different situations demand different values.
+.It Fl o Ar number , Fl \-ntpversion Ns = Ns Ar number
+Send \fBint\fP as our NTP protocol version.
+This option takes an integer number as its argument.
+The value of
+.Ar number
+is constrained to being:
+.in +4
+.nf
+.na
+in the range 0 through 7
+.fi
+.in -4
+The default
+.Ar number
+for this option is:
+.ti +4
+ 4
+.sp
+When sending requests to a remote server, tell them we are running
+NTP protocol version \fIntpversion\fP .
+.It Fl r , Fl \-usereservedport
+Use the NTP Reserved Port (port 123).
+.sp
+Use port 123, which is reserved for NTP, for our network
+communications.
+.It Fl S , Fl \-step
+OK to 'step' the time with \fBsettimeofday(2)\fP.
+.sp
+.It Fl s , Fl \-slew
+OK to 'slew' the time with \fBadjtime(2)\fP.
+.sp
+.It Fl t Ar seconds , Fl \-timeout Ns = Ns Ar seconds
+The number of seconds to wait for responses.
+This option takes an integer number as its argument.
+The default
+.Ar seconds
+for this option is:
+.ti +4
+ 5
+.sp
+When waiting for a reply, \fBsntp\fP will wait the number
+of seconds specified before giving up. The default should be
+more than enough for a unicast response. If \fBsntp\fP is
+only waiting for a broadcast response a longer timeout is
+likely needed.
+.It Fl \-wait , " Fl \-no\-wait"
+Wait for pending replies (if not setting the time).
+The \fIno\-wait\fP form will disable the option.
+This option is enabled by default.
+.sp
+If we are not setting the time, wait for all pending responses.
+.It Fl \&? , Fl \-help
+Display usage information and exit.
+.It Fl \&! , Fl \-more\-help
+Pass the extended usage information through a pager.
+.It Fl > Oo Ar cfgfile Oc , Fl \-save\-opts Oo Ns = Ns Ar cfgfile Oc
+Save the option state to \fIcfgfile\fP. The default is the \fIlast\fP
+configuration file listed in the \fBOPTION PRESETS\fP section, below.
+The command will exit after updating the config file.
+.It Fl < Ar cfgfile , Fl \-load\-opts Ns = Ns Ar cfgfile , Fl \-no\-load\-opts
+Load options from \fIcfgfile\fP.
+The \fIno\-load\-opts\fP form will disable the loading
+of earlier config/rc/ini files. \fI\-\-no\-load\-opts\fP is handled early,
+out of order.
+.It Fl \-version Op Brq Ar v|c|n
+Output version of program and exit. The default mode is `v', a simple
+version. The `c' mode will print copyright information and `n' will
+print the full copyright notice.
+.El
+.Sh "OPTION PRESETS"
+Any option that is not marked as \fInot presettable\fP may be preset
+by loading values from configuration ("RC" or ".INI") file(s) and values from
+environment variables named:
+.nf
+ \fBSNTP_<option\-name>\fP or \fBSNTP\fP
+.fi
+.ad
+The environmental presets take precedence (are processed later than)
+the configuration files.
+The \fIhomerc\fP files are "\fI$HOME\fP", and "\fI.\fP".
+If any of these are directories, then the file \fI.ntprc\fP
+is searched for within those directories.
+.Sh USAGE
+.Bl -tag -width indent
+.It Li "sntp ntpserver.somewhere"
+is the simplest use of this program
+and can be run as an unprivileged command
+to check the current time and error in the local clock.
+.It Li "sntp \-Ss \-M 128 ntpserver.somewhere"
+With suitable privilege,
+run as a command
+or from a
+.Xr cron 8
+job,
+.Ic "sntp \-Ss \-M 128 ntpserver.somewhere"
+will request the time from the server,
+and if that server reports that it is synchronized
+then if the offset adjustment is less than 128 milliseconds
+the correction will be slewed,
+and if the correction is more than 128 milliseconds
+the correction will be stepped.
+.It Li "sntp \-S ntpserver.somewhere"
+With suitable privilege,
+run as a command
+or from a
+.Xr cron 8
+job,
+.Ic "sntp \-S ntpserver.somewhere"
+will set (step) the local clock from a synchronized specified server,
+like the (deprecated)
+.Xr ntpdate 8 ,
+or
+.Xr rdate 8
+commands.
+.El
+.Sh "ENVIRONMENT"
+See \fBOPTION PRESETS\fP for configuration environment variables.
+.Sh "FILES"
+See \fBOPTION PRESETS\fP for configuration files.
+.Sh "EXIT STATUS"
+One of the following exit values will be returned:
+.Bl -tag
+.It 0 " (EXIT_SUCCESS)"
+Successful program execution.
+.It 1 " (EXIT_FAILURE)"
+The operation failed or the command syntax was not valid.
+.It 66 " (EX_NOINPUT)"
+A specified configuration file could not be loaded.
+.It 70 " (EX_SOFTWARE)"
+libopts had an internal operational error. Please report
+it to autogen\-users@lists.sourceforge.net. Thank you.
+.El
+.Sh AUTHORS
+.An "Johannes Maximilian Kuehn"
+.An "Harlan Stenn"
+.An "Dave Hart"
+.Sh "COPYRIGHT"
+Copyright (C) 1992\-2015 The University of Delaware and Network Time Foundation all rights reserved.
+This program is released under the terms of the NTP license, <http://ntp.org/license>.
+.Sh "BUGS"
+Please send bug reports to: http://bugs.ntp.org, bugs@ntp.org
+.Sh "NOTES"
+This manual page was \fIAutoGen\fP\-erated from the \fBsntp\fP
+option definitions.
diff --git a/usr.sbin/ntp/libntp/Makefile b/usr.sbin/ntp/libntp/Makefile
new file mode 100644
index 0000000..1e48483
--- /dev/null
+++ b/usr.sbin/ntp/libntp/Makefile
@@ -0,0 +1,89 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../../contrib/ntp/libntp \
+ ${.CURDIR}/../../../contrib/ntp/lib/isc \
+ ${.CURDIR}/../../../contrib/ntp/lib/isc/nls \
+ ${.CURDIR}/../../../contrib/ntp/lib/isc/pthreads \
+ ${.CURDIR}/../../../contrib/ntp/lib/isc/unix \
+
+LIB= ntp
+INTERNALLIB=
+
+NTP_SRCS= systime.c a_md5encrypt.c adjtime.c atoint.c \
+ atolfp.c atouint.c audio.c authkeys.c \
+ authreadkeys.c authusekey.c bsd_strerror.c buftvtots.c \
+ caljulian.c caltontp.c calyearstart.c clocktime.c \
+ clocktypes.c decodenetnum.c dofptoa.c dolfptoa.c \
+ emalloc.c findconfig.c getopt.c hextoint.c \
+ hextolfp.c humandate.c icom.c iosignal.c \
+ lib_strbuf.c machines.c mktime.c modetoa.c \
+ mstolfp.c msyslog.c netof.c ntp_calendar.c \
+ ntp_crypto_rnd.c ntp_intres.c ntp_libopts.c \
+ ntp_lineedit.c ntp_random.c ntp_rfc2553.c ntp_worker.c \
+ numtoa.c numtohost.c octtoint.c prettydate.c \
+ recvbuff.c refidsmear.c \
+ refnumtoa.c snprintf.c socket.c \
+ socktoa.c socktohost.c ssl_init.c statestr.c \
+ strdup.c strl_obsd.c syssignal.c timetoa.c \
+ timevalops.c uglydate.c vint64ops.c work_fork.c \
+ work_thread.c ymd2yd.c
+
+ISC_PTHREADS_SRCS= condition.c \
+ thread.c \
+ mutex.c
+
+ISC_UNIX_SRCS= dir.c \
+ errno2result.c \
+ file.c \
+ interfaceiter.c \
+ net.c \
+ stdio.c \
+ stdtime.c \
+ strerror.c \
+ time.c
+
+ISC_NLS_SRCS= msgcat.c
+
+ISC_SRCS= assertions.c \
+ buffer.c \
+ backtrace-emptytbl.c \
+ backtrace.c \
+ error.c \
+ event.c \
+ inet_ntop.c \
+ inet_pton.c \
+ lib.c \
+ log.c \
+ md5.c \
+ netaddr.c \
+ netscope.c \
+ ondestroy.c \
+ random.c \
+ result.c \
+ task.c \
+ sha1.c \
+ sockaddr.c \
+ ${ISC_NLS_SRCS} \
+ ${ISC_PTHREADS_SRCS} \
+ ${ISC_UNIX_SRCS}
+
+SRCS= ${NTP_SRCS} ${ISC_SRCS} version.c
+
+CFLAGS+= -I${.CURDIR}/../../../contrib/ntp/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/unix/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/pthreads/include \
+ -I${.CURDIR}/../../../contrib/ntp/sntp/libopts \
+ -I${.CURDIR}/../../../lib/libc/${MACHINE_ARCH} \
+ -I${.CURDIR}/../../../lib/libedit/edit \
+ -I${.CURDIR}/../ \
+ -I${.CURDIR}/
+
+CFLAGS+= -DHAVE_BSD_NICE -DHAVE_STDINT_H
+
+CLEANFILES+= .version version.c
+
+version.c:
+ sh -e ${.CURDIR}/../scripts/mkver ntpd
+
+.include <bsd.lib.mk>
diff --git a/usr.sbin/ntp/libntp/Makefile.depend b/usr.sbin/ntp/libntp/Makefile.depend
new file mode 100644
index 0000000..aa72b94
--- /dev/null
+++ b/usr.sbin/ntp/libntp/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/libedit/edit/readline \
+ lib/msun \
+ secure/lib/libcrypto \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+version.o: version.c
+version.po: version.c
+.endif
diff --git a/usr.sbin/ntp/libntpevent/Makefile b/usr.sbin/ntp/libntpevent/Makefile
new file mode 100644
index 0000000..b912ed8
--- /dev/null
+++ b/usr.sbin/ntp/libntpevent/Makefile
@@ -0,0 +1,34 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../../contrib/ntp/sntp/libevent
+
+LIB= ntpevent
+INTERNALLIB=
+
+SRCS= buffer.c bufferevent.c bufferevent_filter.c bufferevent_openssl.c \
+ bufferevent_pair.c epoll.c evdns.c event.c event_tagging.c \
+ evmap.c evport.c evrpc.c evthread.c evthread_pthread.c evutil.c \
+ evutil_rand.c evutil_time.c http.c kqueue.c listener.c log.c poll.c \
+ select.c signal.c strlcpy.c
+
+.if ${MACHINE_ARCH} == "i386"
+NTP_ATOMIC=x86_32
+.elif ${MACHINE_ARCH} == "amd64"
+NTP_ATOMIC=x86_64
+.elif ${MACHINE_ARCH} == "ia64"
+NTP_ATOMIC=ia64
+.elif ${MACHINE_ARCH} == "powerpc64"
+NTP_ATOMIC=powerpc
+.elif ${MACHINE_ARCH} == "sparc64"
+NTP_ATOMIC=sparc64
+.else
+NTP_ATOMIC=noatomic
+.endif
+
+CFLAGS+= -I${.CURDIR}/../../../contrib/ntp/include \
+ -I${.CURDIR}/../../../contrib/ntp/sntp/libevent/include \
+ -I${.CURDIR}/
+
+CFLAGS+= -DHAVE_BSD_NICE -DHAVE_STDINT_H
+
+.include <bsd.lib.mk>
diff --git a/usr.sbin/ntp/libntpevent/Makefile.depend b/usr.sbin/ntp/libntpevent/Makefile.depend
new file mode 100644
index 0000000..39fece6
--- /dev/null
+++ b/usr.sbin/ntp/libntpevent/Makefile.depend
@@ -0,0 +1,16 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/arpa \
+ include/xlocale \
+ secure/lib/libcrypto \
+ secure/lib/libssl \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ntp/libntpevent/event2/event-config.h b/usr.sbin/ntp/libntpevent/event2/event-config.h
new file mode 100644
index 0000000..920b7ba
--- /dev/null
+++ b/usr.sbin/ntp/libntpevent/event2/event-config.h
@@ -0,0 +1,648 @@
+/* event2/event-config.h
+* $FreeBSD$
+*
+* This file was generated by autoconf when libevent was built, and post-
+* processed by Libevent so that its macros would have a uniform prefix.
+*
+* DO NOT EDIT THIS FILE.
+*
+* Do not rely on macros in this file existing in later versions.
+*/
+
+#ifndef EVENT2_EVENT_CONFIG_H_INCLUDED_
+#define EVENT2_EVENT_CONFIG_H_INCLUDED_
+/* config.h. Generated from config.h.in by configure. */
+/* config.h.in. Generated from configure.ac by autoheader. */
+
+/* Define if libevent should build without support for a debug mode */
+/* #undef EVENT__DISABLE_DEBUG_MODE */
+
+/* Define if libevent should not allow replacing the mm functions */
+/* #undef EVENT__DISABLE_MM_REPLACEMENT */
+
+/* Define if libevent should not be compiled with thread support */
+/* #undef EVENT__DISABLE_THREAD_SUPPORT */
+
+/* Define to 1 if you have the `accept4' function. */
+#define EVENT__HAVE_ACCEPT4 1
+
+/* Define to 1 if you have the `arc4random' function. */
+#define EVENT__HAVE_ARC4RANDOM 1
+
+/* Define to 1 if you have the `arc4random_buf' function. */
+#define EVENT__HAVE_ARC4RANDOM_BUF 1
+
+/* Define to 1 if you have the <arpa/inet.h> header file. */
+#define EVENT__HAVE_ARPA_INET_H 1
+
+/* Define to 1 if you have the `clock_gettime' function. */
+#define EVENT__HAVE_CLOCK_GETTIME 1
+
+/* Define to 1 if you have the <cthreads.h> header file. */
+/* #undef EVENT__HAVE_CTHREADS_H */
+
+/* Define to 1 if you have the declaration of `CTL_KERN', and to 0 if you
+ don't. */
+#define EVENT__HAVE_DECL_CTL_KERN 1
+
+/* Define to 1 if you have the declaration of `KERN_ARND', and to 0 if you
+ don't. */
+#define EVENT__HAVE_DECL_KERN_ARND 1
+
+/* Define to 1 if you have the declaration of `KERN_RANDOM', and to 0 if you
+ don't. */
+#define EVENT__HAVE_DECL_KERN_RANDOM 0
+
+/* Define to 1 if you have the declaration of `RANDOM_UUID', and to 0 if you
+ don't. */
+#define EVENT__HAVE_DECL_RANDOM_UUID 0
+
+/* Define if /dev/poll is available */
+/* #undef EVENT__HAVE_DEVPOLL */
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define EVENT__HAVE_DLFCN_H 1
+
+/* Define if your system supports the epoll system calls */
+/* #undef EVENT__HAVE_EPOLL */
+
+/* Define to 1 if you have the `epoll_create1' function. */
+/* #undef EVENT__HAVE_EPOLL_CREATE1 */
+
+/* Define to 1 if you have the `epoll_ctl' function. */
+/* #undef EVENT__HAVE_EPOLL_CTL */
+
+/* Define to 1 if you have the `eventfd' function. */
+/* #undef EVENT__HAVE_EVENTFD */
+
+/* Define if your system supports event ports */
+/* #undef EVENT__HAVE_EVENT_PORTS */
+
+/* Define to 1 if you have the `fcntl' function. */
+#define EVENT__HAVE_FCNTL 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define EVENT__HAVE_FCNTL_H 1
+
+/* Define to 1 if the system has the type `fd_mask'. */
+#define EVENT__HAVE_FD_MASK 1
+
+/* Do we have getaddrinfo()? */
+#define EVENT__HAVE_GETADDRINFO 1
+
+/* Define to 1 if you have the `getegid' function. */
+#define EVENT__HAVE_GETEGID 1
+
+/* Define to 1 if you have the `geteuid' function. */
+#define EVENT__HAVE_GETEUID 1
+
+/* Define this if you have any gethostbyname_r() */
+/* #undef EVENT__HAVE_GETHOSTBYNAME_R */
+
+/* Define this if gethostbyname_r takes 3 arguments */
+/* #undef EVENT__HAVE_GETHOSTBYNAME_R_3_ARG */
+
+/* Define this if gethostbyname_r takes 5 arguments */
+/* #undef EVENT__HAVE_GETHOSTBYNAME_R_5_ARG */
+
+/* Define this if gethostbyname_r takes 6 arguments */
+/* #undef EVENT__HAVE_GETHOSTBYNAME_R_6_ARG */
+
+/* Define to 1 if you have the `getifaddrs' function. */
+#define EVENT__HAVE_GETIFADDRS 1
+
+/* Define to 1 if you have the `getnameinfo' function. */
+#define EVENT__HAVE_GETNAMEINFO 1
+
+/* Define to 1 if you have the `getprotobynumber' function. */
+#define EVENT__HAVE_GETPROTOBYNUMBER 1
+
+/* Define to 1 if you have the `getservbyname' function. */
+/* #undef EVENT__HAVE_GETSERVBYNAME */
+
+/* Define to 1 if you have the `gettimeofday' function. */
+#define EVENT__HAVE_GETTIMEOFDAY 1
+
+/* if you have GNU Pth */
+/* #undef EVENT__HAVE_GNU_PTH */
+
+/* Define to 1 if you have the <ifaddrs.h> header file. */
+#define EVENT__HAVE_IFADDRS_H 1
+
+/* Define to 1 if you have the `inet_ntop' function. */
+#define EVENT__HAVE_INET_NTOP 1
+
+/* Define to 1 if you have the `inet_pton' function. */
+#define EVENT__HAVE_INET_PTON 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define EVENT__HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `issetugid' function. */
+#define EVENT__HAVE_ISSETUGID 1
+
+/* Define to 1 if you have the `kqueue' function. */
+#define EVENT__HAVE_KQUEUE 1
+
+/* Define if the system has zlib */
+#define EVENT__HAVE_LIBZ 1
+
+/* if you have LinuxThreads */
+/* #undef EVENT__HAVE_LINUX_THREADS */
+
+/* if you have SunOS LWP package */
+/* #undef EVENT__HAVE_LWP */
+
+/* Define to 1 if you have the <lwp/lwp.h> header file. */
+/* #undef EVENT__HAVE_LWP_LWP_H */
+
+/* Define to 1 if you have the `mach_absolute_time' function. */
+/* #undef EVENT__HAVE_MACH_ABSOLUTE_TIME */
+
+/* define if you have Mach Cthreads */
+/* #undef EVENT__HAVE_MACH_CTHREADS */
+
+/* Define to 1 if you have the <mach/cthreads.h> header file. */
+/* #undef EVENT__HAVE_MACH_CTHREADS_H */
+
+/* Define to 1 if you have the <mach/mach_time.h> header file. */
+/* #undef EVENT__HAVE_MACH_MACH_TIME_H */
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define EVENT__HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `mmap' function. */
+#define EVENT__HAVE_MMAP 1
+
+/* Define to 1 if you have the `nanosleep' function. */
+#define EVENT__HAVE_NANOSLEEP 1
+
+/* Define to 1 if you have the <netdb.h> header file. */
+#define EVENT__HAVE_NETDB_H 1
+
+/* Define to 1 if you have the <netinet/in6.h> header file. */
+/* #undef EVENT__HAVE_NETINET_IN6_H */
+
+/* Define to 1 if you have the <netinet/in.h> header file. */
+#define EVENT__HAVE_NETINET_IN_H 1
+
+/* Define to 1 if you have the <netinet/tcp.h> header file. */
+#define EVENT__HAVE_NETINET_TCP_H 1
+
+/* if you have NT Event Log */
+/* #undef EVENT__HAVE_NT_EVENT_LOG */
+
+/* if you have NT Service Manager */
+/* #undef EVENT__HAVE_NT_SERVICE_MANAGER */
+
+/* if you have NT Threads */
+/* #undef EVENT__HAVE_NT_THREADS */
+
+/* Define if the system has openssl */
+/* #undef EVENT__HAVE_OPENSSL */
+
+/* Define to 1 if you have the `pipe' function. */
+#define EVENT__HAVE_PIPE 1
+
+/* Define to 1 if you have the `pipe2' function. */
+#define EVENT__HAVE_PIPE2 1
+
+/* Define to 1 if you have the `poll' function. */
+#define EVENT__HAVE_POLL 1
+
+/* Define to 1 if you have the <poll.h> header file. */
+#define EVENT__HAVE_POLL_H 1
+
+/* Define to 1 if you have the `port_create' function. */
+/* #undef EVENT__HAVE_PORT_CREATE */
+
+/* Define to 1 if you have the <port.h> header file. */
+/* #undef EVENT__HAVE_PORT_H */
+
+/* Define if you have POSIX threads libraries and header files. */
+/* #undef EVENT__HAVE_PTHREAD */
+
+/* define to pthreads API spec revision */
+#define EVENT__HAVE_PTHREADS 10
+
+/* define if you have pthread_detach function */
+#define EVENT__HAVE_PTHREAD_DETACH 1
+
+/* Define to 1 if you have the `pthread_getconcurrency' function. */
+#define EVENT__HAVE_PTHREAD_GETCONCURRENCY 1
+
+/* Define to 1 if you have the <pthread.h> header file. */
+#define EVENT__HAVE_PTHREAD_H 1
+
+/* Define to 1 if you have the `pthread_kill' function. */
+#define EVENT__HAVE_PTHREAD_KILL 1
+
+/* Define to 1 if you have the `pthread_kill_other_threads_np' function. */
+/* #undef EVENT__HAVE_PTHREAD_KILL_OTHER_THREADS_NP */
+
+/* define if you have pthread_rwlock_destroy function */
+#define EVENT__HAVE_PTHREAD_RWLOCK_DESTROY 1
+
+/* Define to 1 if you have the `pthread_setconcurrency' function. */
+#define EVENT__HAVE_PTHREAD_SETCONCURRENCY 1
+
+/* Define to 1 if you have the `pthread_yield' function. */
+#define EVENT__HAVE_PTHREAD_YIELD 1
+
+/* Define to 1 if you have the <pth.h> header file. */
+/* #undef EVENT__HAVE_PTH_H */
+
+/* Define to 1 if you have the `putenv' function. */
+#define EVENT__HAVE_PUTENV 1
+
+/* Define to 1 if the system has the type `sa_family_t'. */
+#define EVENT__HAVE_SA_FAMILY_T 1
+
+/* Define to 1 if you have the <sched.h> header file. */
+#define EVENT__HAVE_SCHED_H 1
+
+/* Define to 1 if you have the `sched_yield' function. */
+#define EVENT__HAVE_SCHED_YIELD 1
+
+/* Define to 1 if you have the `select' function. */
+#define EVENT__HAVE_SELECT 1
+
+/* Define to 1 if you have the `sendfile' function. */
+#define EVENT__HAVE_SENDFILE 1
+
+/* Define to 1 if you have the `setenv' function. */
+#define EVENT__HAVE_SETENV 1
+
+/* Define if F_SETFD is defined in <fcntl.h> */
+#define EVENT__HAVE_SETFD 1
+
+/* Define to 1 if you have the `setrlimit' function. */
+#define EVENT__HAVE_SETRLIMIT 1
+
+/* Define to 1 if you have the `sigaction' function. */
+#define EVENT__HAVE_SIGACTION 1
+
+/* Define to 1 if you have the `signal' function. */
+#define EVENT__HAVE_SIGNAL 1
+
+/* Define to 1 if you have the `splice' function. */
+/* #undef EVENT__HAVE_SPLICE */
+
+/* Define to 1 if you have the <stdarg.h> header file. */
+#define EVENT__HAVE_STDARG_H 1
+
+/* Define to 1 if you have the <stddef.h> header file. */
+#define EVENT__HAVE_STDDEF_H 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define EVENT__HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define EVENT__HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define EVENT__HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define EVENT__HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strlcpy' function. */
+#define EVENT__HAVE_STRLCPY 1
+
+/* Define to 1 if you have the `strsep' function. */
+#define EVENT__HAVE_STRSEP 1
+
+/* Define to 1 if you have the `strtok_r' function. */
+#define EVENT__HAVE_STRTOK_R 1
+
+/* Define to 1 if you have the `strtoll' function. */
+#define EVENT__HAVE_STRTOLL 1
+
+/* Define to 1 if the system has the type `struct addrinfo'. */
+#define EVENT__HAVE_STRUCT_ADDRINFO 1
+
+/* Define to 1 if the system has the type `struct in6_addr'. */
+#define EVENT__HAVE_STRUCT_IN6_ADDR 1
+
+/* Define to 1 if `s6_addr16' is a member of `struct in6_addr'. */
+/* #undef EVENT__HAVE_STRUCT_IN6_ADDR_S6_ADDR16 */
+
+/* Define to 1 if `s6_addr32' is a member of `struct in6_addr'. */
+/* #undef EVENT__HAVE_STRUCT_IN6_ADDR_S6_ADDR32 */
+
+/* Define to 1 if the system has the type `struct sockaddr_in6'. */
+#define EVENT__HAVE_STRUCT_SOCKADDR_IN6 1
+
+/* Define to 1 if `sin6_len' is a member of `struct sockaddr_in6'. */
+#define EVENT__HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN 1
+
+/* Define to 1 if `sin_len' is a member of `struct sockaddr_in'. */
+#define EVENT__HAVE_STRUCT_SOCKADDR_IN_SIN_LEN 1
+
+/* Define to 1 if the system has the type `struct sockaddr_storage'. */
+#define EVENT__HAVE_STRUCT_SOCKADDR_STORAGE 1
+
+/* Define to 1 if `ss_family' is a member of `struct sockaddr_storage'. */
+#define EVENT__HAVE_STRUCT_SOCKADDR_STORAGE_SS_FAMILY 1
+
+/* Define to 1 if `__ss_family' is a member of `struct sockaddr_storage'. */
+/* #undef EVENT__HAVE_STRUCT_SOCKADDR_STORAGE___SS_FAMILY */
+
+/* Define to 1 if the system has the type `struct so_linger'. */
+/* #undef EVENT__HAVE_STRUCT_SO_LINGER */
+
+/* Define to 1 if you have the <synch.h> header file. */
+/* #undef EVENT__HAVE_SYNCH_H */
+
+/* Define to 1 if you have the `sysctl' function. */
+#define EVENT__HAVE_SYSCTL 1
+
+/* Define to 1 if you have the <sys/devpoll.h> header file. */
+/* #undef EVENT__HAVE_SYS_DEVPOLL_H */
+
+/* Define to 1 if you have the <sys/epoll.h> header file. */
+/* #undef EVENT__HAVE_SYS_EPOLL_H */
+
+/* Define to 1 if you have the <sys/eventfd.h> header file. */
+/* #undef EVENT__HAVE_SYS_EVENTFD_H */
+
+/* Define to 1 if you have the <sys/event.h> header file. */
+#define EVENT__HAVE_SYS_EVENT_H 1
+
+/* Define to 1 if you have the <sys/ioctl.h> header file. */
+#define EVENT__HAVE_SYS_IOCTL_H 1
+
+/* Define to 1 if you have the <sys/mman.h> header file. */
+#define EVENT__HAVE_SYS_MMAN_H 1
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#define EVENT__HAVE_SYS_PARAM_H 1
+
+/* Define to 1 if you have the <sys/queue.h> header file. */
+#define EVENT__HAVE_SYS_QUEUE_H 1
+
+/* Define to 1 if you have the <sys/resource.h> header file. */
+#define EVENT__HAVE_SYS_RESOURCE_H 1
+
+/* Define to 1 if you have the <sys/select.h> header file. */
+#define EVENT__HAVE_SYS_SELECT_H 1
+
+/* Define to 1 if you have the <sys/sendfile.h> header file. */
+/* #undef EVENT__HAVE_SYS_SENDFILE_H */
+
+/* Define to 1 if you have the <sys/socket.h> header file. */
+#define EVENT__HAVE_SYS_SOCKET_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define EVENT__HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/sysctl.h> header file. */
+#define EVENT__HAVE_SYS_SYSCTL_H 1
+
+/* Define to 1 if you have the <sys/timerfd.h> header file. */
+/* #undef EVENT__HAVE_SYS_TIMERFD_H */
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#define EVENT__HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define EVENT__HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <sys/uio.h> header file. */
+#define EVENT__HAVE_SYS_UIO_H 1
+
+/* Define to 1 if you have the <sys/wait.h> header file. */
+#define EVENT__HAVE_SYS_WAIT_H 1
+
+/* Define if TAILQ_FOREACH is defined in <sys/queue.h> */
+#define EVENT__HAVE_TAILQFOREACH 1
+
+/* if you have Solaris LWP (thr) package */
+/* #undef EVENT__HAVE_THR */
+
+/* Define to 1 if you have the <thread.h> header file. */
+/* #undef EVENT__HAVE_THREAD_H */
+
+/* Define to 1 if you have the `thr_getconcurrency' function. */
+/* #undef EVENT__HAVE_THR_GETCONCURRENCY */
+
+/* Define to 1 if you have the `thr_setconcurrency' function. */
+/* #undef EVENT__HAVE_THR_SETCONCURRENCY */
+
+/* Define to 1 if you have the `thr_yield' function. */
+/* #undef EVENT__HAVE_THR_YIELD */
+
+/* Define if timeradd is defined in <sys/time.h> */
+#define EVENT__HAVE_TIMERADD 1
+
+/* Define if timerclear is defined in <sys/time.h> */
+#define EVENT__HAVE_TIMERCLEAR 1
+
+/* Define if timercmp is defined in <sys/time.h> */
+#define EVENT__HAVE_TIMERCMP 1
+
+/* Define to 1 if you have the `timerfd_create' function. */
+/* #undef EVENT__HAVE_TIMERFD_CREATE */
+
+/* Define if timerisset is defined in <sys/time.h> */
+#define EVENT__HAVE_TIMERISSET 1
+
+/* Define to 1 if the system has the type `uint16_t'. */
+#define EVENT__HAVE_UINT16_T 1
+
+/* Define to 1 if the system has the type `uint32_t'. */
+#define EVENT__HAVE_UINT32_T 1
+
+/* Define to 1 if the system has the type `uint64_t'. */
+#define EVENT__HAVE_UINT64_T 1
+
+/* Define to 1 if the system has the type `uint8_t'. */
+#define EVENT__HAVE_UINT8_T 1
+
+/* Define to 1 if the system has the type `uintptr_t'. */
+#define EVENT__HAVE_UINTPTR_T 1
+
+/* Define to 1 if you have the `umask' function. */
+#define EVENT__HAVE_UMASK 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define EVENT__HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the `unsetenv' function. */
+#define EVENT__HAVE_UNSETENV 1
+
+/* Define to 1 if you have the `usleep' function. */
+#define EVENT__HAVE_USLEEP 1
+
+/* Define to 1 if you have the `vasprintf' function. */
+#define EVENT__HAVE_VASPRINTF 1
+
+/* Define if kqueue works correctly with pipes */
+#define EVENT__HAVE_WORKING_KQUEUE 1
+
+/* define if select implicitly yields */
+#define EVENT__HAVE_YIELDING_SELECT 1
+
+/* Define to 1 if you have the <zlib.h> header file. */
+#define EVENT__HAVE_ZLIB_H 1
+
+/* define to 1 if library is thread safe */
+#define EVENT__LDAP_API_FEATURE_X_OPENLDAP_THREAD_SAFE 1
+
+/* Define to the sub-directory in which libtool stores uninstalled libraries.
+ */
+#define EVENT__LT_OBJDIR ".libs/"
+
+/* Define to 1 if your C compiler doesn't accept -c and -o together. */
+/* #undef EVENT__NO_MINUS_C_MINUS_O */
+
+/* define if you have (or want) no threads */
+/* #undef EVENT__NO_THREADS */
+
+/* Numeric representation of the version */
+#define EVENT__NUMERIC_VERSION 0x02010301
+
+/* Name of package */
+#define EVENT__PACKAGE "libevent"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define EVENT__PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define EVENT__PACKAGE_NAME "libevent"
+
+/* Define to the full name and version of this package. */
+#define EVENT__PACKAGE_STRING "libevent 2.1.3-alpha-dev"
+
+/* Define to the one symbol short name of this package. */
+#define EVENT__PACKAGE_TARNAME "libevent"
+
+/* Define to the home page for this package. */
+#define EVENT__PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define EVENT__PACKAGE_VERSION "2.1.3-alpha-dev"
+
+/* enable thread safety */
+#define EVENT__REENTRANT 1
+
+/* define if sched_yield yields the entire process */
+/* #undef EVENT__REPLACE_BROKEN_YIELD */
+
+/* The size of `int', as computed by sizeof. */
+#define EVENT__SIZEOF_INT 4
+
+/* The size of `long', as computed by sizeof. */
+#define EVENT__SIZEOF_LONG 8
+
+/* The size of `long long', as computed by sizeof. */
+#define EVENT__SIZEOF_LONG_LONG 8
+
+/* The size of `off_t', as computed by sizeof. */
+#define EVENT__SIZEOF_OFF_T 8
+
+/* The size of `pthread_t', as computed by sizeof. */
+#define EVENT__SIZEOF_PTHREAD_T 8
+
+/* The size of `short', as computed by sizeof. */
+#define EVENT__SIZEOF_SHORT 2
+
+/* The size of `size_t', as computed by sizeof. */
+#define EVENT__SIZEOF_SIZE_T 8
+
+/* The size of `void *', as computed by sizeof. */
+#define EVENT__SIZEOF_VOID_P 8
+
+/* Define to 1 if you have the ANSI C header files. */
+#define EVENT__STDC_HEADERS 1
+
+/* enable thread safety */
+#define EVENT__THREADSAFE 1
+
+/* enable thread safety */
+#define EVENT__THREAD_SAFE 1
+
+/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
+#define EVENT__TIME_WITH_SYS_TIME 1
+
+/* Version number of package */
+#define EVENT__VERSION "2.1.3-alpha-dev"
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+/* #undef EVENT___FILE_OFFSET_BITS */
+
+/* Define for large files, on AIX-style hosts. */
+/* #undef EVENT___LARGE_FILES */
+
+/* Define to 1 if on MINIX. */
+/* #undef EVENT___MINIX */
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+ this defined. */
+/* #undef EVENT___POSIX_1_SOURCE */
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+/* #undef EVENT___POSIX_SOURCE */
+
+/* enable thread safety */
+#define EVENT___REENTRANT 1
+
+/* enable thread safety */
+#define EVENT___SGI_MP_SOURCE 1
+
+/* enable thread safety */
+#define EVENT___THREADSAFE 1
+
+/* enable thread safety */
+#define EVENT___THREAD_SAFE 1
+
+/* Define to 500 only on HP-UX. */
+/* #undef EVENT___XOPEN_SOURCE */
+
+/* Enable extensions on AIX 3, Interix. */
+#ifndef EVENT___ALL_SOURCE
+# define EVENT___ALL_SOURCE 1
+#endif
+/* Enable GNU extensions on systems that have them. */
+#ifndef EVENT___GNU_SOURCE
+# define EVENT___GNU_SOURCE 1
+#endif
+/* Enable threading extensions on Solaris. */
+#ifndef EVENT___POSIX_PTHREAD_SEMANTICS
+# define EVENT___POSIX_PTHREAD_SEMANTICS 1
+#endif
+/* Enable extensions on HP NonStop. */
+#ifndef EVENT___TANDEM_SOURCE
+# define EVENT___TANDEM_SOURCE 1
+#endif
+/* Enable general extensions on Solaris. */
+#ifndef EVENT____EXTENSIONS__
+# define EVENT____EXTENSIONS__ 1
+#endif
+
+
+/* Define to appropriate substitue if compiler doesnt have __func__ */
+/* #undef EVENT____func__ */
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef EVENT__const */
+
+/* Define to `__inline__' or `__inline' if that's what the C compiler
+ calls it, or to nothing if 'inline' is not supported under any name. */
+#ifndef EVENT____cplusplus
+/* #undef EVENT__inline */
+#endif
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef EVENT__pid_t */
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+/* #undef EVENT__size_t */
+
+/* Define to unsigned int if you dont have it */
+/* #undef EVENT__socklen_t */
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef EVENT__ssize_t */
+
+#endif /* event2/event-config.h */
diff --git a/usr.sbin/ntp/libopts/Makefile b/usr.sbin/ntp/libopts/Makefile
new file mode 100644
index 0000000..3c7eef7
--- /dev/null
+++ b/usr.sbin/ntp/libopts/Makefile
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../../contrib/ntp/sntp/libopts
+
+LIB= opts
+INTERNALLIB=
+
+SRCS= libopts.c
+
+CFLAGS+= -I${.CURDIR}/../../../contrib/ntp/include \
+ -I${.CURDIR}/../../../contrib/ntp/sntp/libopts \
+ -I${.CURDIR}/../
+
+.include <bsd.lib.mk>
diff --git a/usr.sbin/ntp/libopts/Makefile.depend b/usr.sbin/ntp/libopts/Makefile.depend
new file mode 100644
index 0000000..18be76b
--- /dev/null
+++ b/usr.sbin/ntp/libopts/Makefile.depend
@@ -0,0 +1,13 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ntp/libparse/Makefile b/usr.sbin/ntp/libparse/Makefile
new file mode 100644
index 0000000..e99e471
--- /dev/null
+++ b/usr.sbin/ntp/libparse/Makefile
@@ -0,0 +1,19 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../../contrib/ntp/libparse
+
+LIB= parse
+INTERNALLIB=
+
+SRCS= binio.c clk_computime.c clk_dcf7000.c clk_hopf6021.c \
+ clk_meinberg.c clk_rawdcf.c clk_rcc8000.c clk_schmid.c \
+ clk_sel240x.c clk_trimtaip.c clk_trimtsip.c clk_varitext.c \
+ clk_wharton.c data_mbg.c gpstolfp.c ieee754io.c \
+ info_trimble.c mfp_mul.c parse.c parse_conf.c \
+ trim_info.c
+
+CFLAGS+= -I${.CURDIR}/../../../contrib/ntp/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/unix/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/include -I${.CURDIR}/../
+
+.include <bsd.lib.mk>
diff --git a/usr.sbin/ntp/libparse/Makefile.depend b/usr.sbin/ntp/libparse/Makefile.depend
new file mode 100644
index 0000000..4bf31bd
--- /dev/null
+++ b/usr.sbin/ntp/libparse/Makefile.depend
@@ -0,0 +1,16 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/msun \
+ secure/lib/libcrypto \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ntp/ntp-keygen/Makefile b/usr.sbin/ntp/ntp-keygen/Makefile
new file mode 100644
index 0000000..d3f10f3
--- /dev/null
+++ b/usr.sbin/ntp/ntp-keygen/Makefile
@@ -0,0 +1,29 @@
+# $FreeBSD$
+
+MAN=
+
+.include <src.opts.mk>
+
+.PATH: ${.CURDIR}/../../../contrib/ntp/util \
+ ${.CURDIR}/../../../contrib/ntp/ntpd
+
+PROG= ntp-keygen
+SRCS= ntp-keygen.c ntp-keygen-opts.c
+
+CFLAGS+= -I${.CURDIR}/../../../contrib/ntp/include \
+ -I${.CURDIR}/../../../contrib/ntp/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/unix/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/pthreads/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/${NTP_ATOMIC}/include \
+ -I${.CURDIR}/../../../contrib/ntp/sntp/libopts \
+ -I${.CURDIR}/../../../lib/libc/${MACHINE_ARCH} \
+ -I${.CURDIR}/../
+
+LIBADD+= ntp opts pthread
+
+.if ${MK_OPENSSL} != "no"
+LIBADD+= crypto
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ntp/ntp-keygen/Makefile.depend b/usr.sbin/ntp/ntp-keygen/Makefile.depend
new file mode 100644
index 0000000..88a55c0
--- /dev/null
+++ b/usr.sbin/ntp/ntp-keygen/Makefile.depend
@@ -0,0 +1,24 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libthr \
+ lib/msun \
+ secure/lib/libcrypto \
+ usr.sbin/ntp/libntp \
+ usr.sbin/ntp/libopts \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ntp/ntpd/Makefile b/usr.sbin/ntp/ntpd/Makefile
new file mode 100644
index 0000000..905ff3c
--- /dev/null
+++ b/usr.sbin/ntp/ntpd/Makefile
@@ -0,0 +1,52 @@
+# $FreeBSD$
+
+MAN=
+
+.include <src.opts.mk>
+
+.PATH: ${.CURDIR}/../../../contrib/ntp/ntpd \
+ ${.OBJDIR}
+
+PROG= ntpd
+
+SRCS= cmd_args.c ntp_config.c ntp_control.c ntp_crypto.c ntp_filegen.c \
+ ntp_io.c ntp_leapsec.c ntp_loopfilter.c ntp_monitor.c ntp_parser.c \
+ ntp_peer.c ntp_proto.c ntp_refclock.c ntp_request.c ntp_restrict.c \
+ ntp_scanner.c ntp_signd.c ntp_timer.c ntp_util.c ntpd-opts.c ntpd.c \
+ rc_cmdlength.c \
+ refclock_acts.c refclock_arbiter.c refclock_arc.c refclock_as2201.c \
+ refclock_atom.c refclock_bancomm.c refclock_chronolog.c \
+ refclock_chu.c refclock_conf.c refclock_datum.c refclock_dumbclock.c \
+ refclock_fg.c refclock_gpsdjson.c refclock_gpsvme.c refclock_heath.c \
+ refclock_hopfpci.c refclock_hopfser.c refclock_hpgps.c \
+ refclock_irig.c refclock_jjy.c refclock_jupiter.c refclock_leitch.c \
+ refclock_local.c refclock_nmea.c refclock_neoclock4x.c \
+ refclock_oncore.c refclock_palisade.c \
+ refclock_parse.c refclock_pcf.c refclock_pst.c refclock_ripencc.c \
+ refclock_shm.c refclock_tpro.c refclock_true.c refclock_tsyncpci.c \
+ refclock_tt560.c refclock_ulink.c refclock_wwv.c refclock_wwvb.c \
+ refclock_zyfer.c version.c
+
+CFLAGS+= -I${.CURDIR}/../../../contrib/ntp/ntpd \
+ -I${.CURDIR}/../../../contrib/ntp/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/pthreads/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/unix/include \
+ -I${.CURDIR}/../../../contrib/ntp/sntp/libopts \
+ -I${.CURDIR}/../ \
+ -I${.CURDIR}
+
+LIBADD= parse ntp m opts pthread
+
+.if ${MK_OPENSSL} != "no"
+LIBADD+= crypto
+.else
+LIBADD+= md
+.endif
+
+CLEANFILES+= .version version.c
+
+version.c:
+ sh -e ${.CURDIR}/../scripts/mkver ntpd
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ntp/ntpd/Makefile.depend b/usr.sbin/ntp/ntpd/Makefile.depend
new file mode 100644
index 0000000..0fc69e4
--- /dev/null
+++ b/usr.sbin/ntp/ntpd/Makefile.depend
@@ -0,0 +1,27 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libthr \
+ lib/msun \
+ secure/lib/libcrypto \
+ usr.sbin/ntp/libntp \
+ usr.sbin/ntp/libopts \
+ usr.sbin/ntp/libparse \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+version.o: version.c
+version.po: version.c
+.endif
diff --git a/usr.sbin/ntp/ntpdate/Makefile b/usr.sbin/ntp/ntpdate/Makefile
new file mode 100644
index 0000000..10352e8
--- /dev/null
+++ b/usr.sbin/ntp/ntpdate/Makefile
@@ -0,0 +1,30 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+.PATH: ${.CURDIR}/../../../contrib/ntp/ntpdate
+
+PROG= ntpdate
+MAN=
+SRCS= ntpdate.c version.c
+
+CFLAGS+= -I${.CURDIR}/../../../contrib/ntp/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/include/ \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/unix/include/ \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/pthreads/include \
+ -I${.CURDIR}/../
+
+LIBADD= ntp m pthread
+
+.if ${MK_OPENSSL} != "no"
+LIBADD+= crypto
+.else
+LIBADD+= md
+.endif
+
+CLEANFILES+= .version version.c
+
+version.c:
+ sh -e ${.CURDIR}/../scripts/mkver ntpdate
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ntp/ntpdate/Makefile.depend b/usr.sbin/ntp/ntpdate/Makefile.depend
new file mode 100644
index 0000000..31c07ab
--- /dev/null
+++ b/usr.sbin/ntp/ntpdate/Makefile.depend
@@ -0,0 +1,25 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libthr \
+ lib/msun \
+ secure/lib/libcrypto \
+ usr.sbin/ntp/libntp \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+version.o: version.c
+version.po: version.c
+.endif
diff --git a/usr.sbin/ntp/ntpdc/Makefile b/usr.sbin/ntp/ntpdc/Makefile
new file mode 100644
index 0000000..d420eb5
--- /dev/null
+++ b/usr.sbin/ntp/ntpdc/Makefile
@@ -0,0 +1,36 @@
+# $FreeBSD$
+
+MAN=
+
+.include <src.opts.mk>
+.include <bsd.own.mk>
+
+.PATH: ${.CURDIR}/../../../contrib/ntp/ntpdc
+
+PROG= ntpdc
+SRCS= ntpdc.c ntpdc_ops.c ntpdc-opts.c version.c
+
+CFLAGS+= -I${.CURDIR}/../../../contrib/ntp/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/unix/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/pthreads/include \
+ -I${.CURDIR}/../../../contrib/ntp/sntp/libopts \
+ -I${.CURDIR}/../../../lib/libc/${MACHINE_ARCH} \
+ -I${.CURDIR}/../ -I${.CURDIR}
+
+LIBADD= edit ntp m opts pthread
+CFLAGS+= -DHAVE_LIBEDIT -DHAVE_READLINE_READLINE_H \
+ -I${DESTDIR}/${INCLUDEDIR}/edit
+
+.if ${MK_OPENSSL} != "no"
+LIBADD+= crypto
+.else
+LIBADD+= md
+.endif
+
+CLEANFILES+= .version version.c
+
+version.c:
+ sh -e ${.CURDIR}/../scripts/mkver ntpdc
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ntp/ntpdc/Makefile.depend b/usr.sbin/ntp/ntpdc/Makefile.depend
new file mode 100644
index 0000000..7531e73
--- /dev/null
+++ b/usr.sbin/ntp/ntpdc/Makefile.depend
@@ -0,0 +1,28 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libedit \
+ lib/libthr \
+ lib/msun \
+ lib/ncurses/ncursesw \
+ secure/lib/libcrypto \
+ usr.sbin/ntp/libntp \
+ usr.sbin/ntp/libopts \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+version.o: version.c
+version.po: version.c
+.endif
diff --git a/usr.sbin/ntp/ntpdc/nl.c b/usr.sbin/ntp/ntpdc/nl.c
new file mode 100644
index 0000000..045d000
--- /dev/null
+++ b/usr.sbin/ntp/ntpdc/nl.c
@@ -0,0 +1,895 @@
+/* $FreeBSD$ */
+ printf("sizeof(union req_data_u_tag) = %d\n",
+ (int) sizeof(union req_data_u_tag));
+ printf("offsetof(u32) = %d\n",
+ (int) offsetof(union req_data_u_tag, u32));
+ printf("offsetof(data) = %d\n",
+ (int) offsetof(union req_data_u_tag, data));
+ printf("\n");
+
+ printf("sizeof(struct req_pkt) = %d\n",
+ (int) sizeof(struct req_pkt));
+ printf("offsetof(rm_vn_mode) = %d\n",
+ (int) offsetof(struct req_pkt, rm_vn_mode));
+ printf("offsetof(auth_seq) = %d\n",
+ (int) offsetof(struct req_pkt, auth_seq));
+ printf("offsetof(implementation) = %d\n",
+ (int) offsetof(struct req_pkt, implementation));
+ printf("offsetof(request) = %d\n",
+ (int) offsetof(struct req_pkt, request));
+ printf("offsetof(err_nitems) = %d\n",
+ (int) offsetof(struct req_pkt, err_nitems));
+ printf("offsetof(mbz_itemsize) = %d\n",
+ (int) offsetof(struct req_pkt, mbz_itemsize));
+ printf("offsetof(u) = %d\n",
+ (int) offsetof(struct req_pkt, u));
+ printf("offsetof(tstamp) = %d\n",
+ (int) offsetof(struct req_pkt, tstamp));
+ printf("offsetof(keyid) = %d\n",
+ (int) offsetof(struct req_pkt, keyid));
+ printf("offsetof(mac) = %d\n",
+ (int) offsetof(struct req_pkt, mac));
+ printf("\n");
+
+ printf("sizeof(struct req_pkt_tail) = %d\n",
+ (int) sizeof(struct req_pkt_tail));
+ printf("offsetof(tstamp) = %d\n",
+ (int) offsetof(struct req_pkt_tail, tstamp));
+ printf("offsetof(keyid) = %d\n",
+ (int) offsetof(struct req_pkt_tail, keyid));
+ printf("offsetof(mac) = %d\n",
+ (int) offsetof(struct req_pkt_tail, mac));
+ printf("\n");
+
+ printf("sizeof(union resp_pkt_u_tag) = %d\n",
+ (int) sizeof(union resp_pkt_u_tag));
+ printf("offsetof(data) = %d\n",
+ (int) offsetof(union resp_pkt_u_tag, data));
+ printf("offsetof(u32) = %d\n",
+ (int) offsetof(union resp_pkt_u_tag, u32));
+ printf("\n");
+
+ printf("sizeof(struct resp_pkt) = %d\n",
+ (int) sizeof(struct resp_pkt));
+ printf("offsetof(rm_vn_mode) = %d\n",
+ (int) offsetof(struct resp_pkt, rm_vn_mode));
+ printf("offsetof(auth_seq) = %d\n",
+ (int) offsetof(struct resp_pkt, auth_seq));
+ printf("offsetof(implementation) = %d\n",
+ (int) offsetof(struct resp_pkt, implementation));
+ printf("offsetof(request) = %d\n",
+ (int) offsetof(struct resp_pkt, request));
+ printf("offsetof(err_nitems) = %d\n",
+ (int) offsetof(struct resp_pkt, err_nitems));
+ printf("offsetof(mbz_itemsize) = %d\n",
+ (int) offsetof(struct resp_pkt, mbz_itemsize));
+ printf("offsetof(u) = %d\n",
+ (int) offsetof(struct resp_pkt, u));
+ printf("\n");
+
+ printf("sizeof(struct info_peer_list) = %d\n",
+ (int) sizeof(struct info_peer_list));
+ printf("offsetof(addr) = %d\n",
+ (int) offsetof(struct info_peer_list, addr));
+ printf("offsetof(port) = %d\n",
+ (int) offsetof(struct info_peer_list, port));
+ printf("offsetof(hmode) = %d\n",
+ (int) offsetof(struct info_peer_list, hmode));
+ printf("offsetof(flags) = %d\n",
+ (int) offsetof(struct info_peer_list, flags));
+ printf("offsetof(v6_flag) = %d\n",
+ (int) offsetof(struct info_peer_list, v6_flag));
+ printf("offsetof(unused1) = %d\n",
+ (int) offsetof(struct info_peer_list, unused1));
+ printf("offsetof(addr6) = %d\n",
+ (int) offsetof(struct info_peer_list, addr6));
+ printf("\n");
+
+ printf("sizeof(struct info_peer_summary) = %d\n",
+ (int) sizeof(struct info_peer_summary));
+ printf("offsetof(dstadr) = %d\n",
+ (int) offsetof(struct info_peer_summary, dstadr));
+ printf("offsetof(srcadr) = %d\n",
+ (int) offsetof(struct info_peer_summary, srcadr));
+ printf("offsetof(srcport) = %d\n",
+ (int) offsetof(struct info_peer_summary, srcport));
+ printf("offsetof(stratum) = %d\n",
+ (int) offsetof(struct info_peer_summary, stratum));
+ printf("offsetof(hpoll) = %d\n",
+ (int) offsetof(struct info_peer_summary, hpoll));
+ printf("offsetof(ppoll) = %d\n",
+ (int) offsetof(struct info_peer_summary, ppoll));
+ printf("offsetof(reach) = %d\n",
+ (int) offsetof(struct info_peer_summary, reach));
+ printf("offsetof(flags) = %d\n",
+ (int) offsetof(struct info_peer_summary, flags));
+ printf("offsetof(hmode) = %d\n",
+ (int) offsetof(struct info_peer_summary, hmode));
+ printf("offsetof(delay) = %d\n",
+ (int) offsetof(struct info_peer_summary, delay));
+ printf("offsetof(offset) = %d\n",
+ (int) offsetof(struct info_peer_summary, offset));
+ printf("offsetof(dispersion) = %d\n",
+ (int) offsetof(struct info_peer_summary, dispersion));
+ printf("offsetof(v6_flag) = %d\n",
+ (int) offsetof(struct info_peer_summary, v6_flag));
+ printf("offsetof(unused1) = %d\n",
+ (int) offsetof(struct info_peer_summary, unused1));
+ printf("offsetof(dstadr6) = %d\n",
+ (int) offsetof(struct info_peer_summary, dstadr6));
+ printf("offsetof(srcadr6) = %d\n",
+ (int) offsetof(struct info_peer_summary, srcadr6));
+ printf("\n");
+
+ printf("sizeof(struct info_peer) = %d\n",
+ (int) sizeof(struct info_peer));
+ printf("offsetof(dstadr) = %d\n",
+ (int) offsetof(struct info_peer, dstadr));
+ printf("offsetof(srcadr) = %d\n",
+ (int) offsetof(struct info_peer, srcadr));
+ printf("offsetof(srcport) = %d\n",
+ (int) offsetof(struct info_peer, srcport));
+ printf("offsetof(flags) = %d\n",
+ (int) offsetof(struct info_peer, flags));
+ printf("offsetof(leap) = %d\n",
+ (int) offsetof(struct info_peer, leap));
+ printf("offsetof(hmode) = %d\n",
+ (int) offsetof(struct info_peer, hmode));
+ printf("offsetof(pmode) = %d\n",
+ (int) offsetof(struct info_peer, pmode));
+ printf("offsetof(stratum) = %d\n",
+ (int) offsetof(struct info_peer, stratum));
+ printf("offsetof(ppoll) = %d\n",
+ (int) offsetof(struct info_peer, ppoll));
+ printf("offsetof(hpoll) = %d\n",
+ (int) offsetof(struct info_peer, hpoll));
+ printf("offsetof(precision) = %d\n",
+ (int) offsetof(struct info_peer, precision));
+ printf("offsetof(version) = %d\n",
+ (int) offsetof(struct info_peer, version));
+ printf("offsetof(unused8) = %d\n",
+ (int) offsetof(struct info_peer, unused8));
+ printf("offsetof(reach) = %d\n",
+ (int) offsetof(struct info_peer, reach));
+ printf("offsetof(unreach) = %d\n",
+ (int) offsetof(struct info_peer, unreach));
+ printf("offsetof(flash) = %d\n",
+ (int) offsetof(struct info_peer, flash));
+ printf("offsetof(ttl) = %d\n",
+ (int) offsetof(struct info_peer, ttl));
+ printf("offsetof(flash2) = %d\n",
+ (int) offsetof(struct info_peer, flash2));
+ printf("offsetof(associd) = %d\n",
+ (int) offsetof(struct info_peer, associd));
+ printf("offsetof(keyid) = %d\n",
+ (int) offsetof(struct info_peer, keyid));
+ printf("offsetof(pkeyid) = %d\n",
+ (int) offsetof(struct info_peer, pkeyid));
+ printf("offsetof(refid) = %d\n",
+ (int) offsetof(struct info_peer, refid));
+ printf("offsetof(timer) = %d\n",
+ (int) offsetof(struct info_peer, timer));
+ printf("offsetof(rootdelay) = %d\n",
+ (int) offsetof(struct info_peer, rootdelay));
+ printf("offsetof(rootdispersion) = %d\n",
+ (int) offsetof(struct info_peer, rootdispersion));
+ printf("offsetof(reftime) = %d\n",
+ (int) offsetof(struct info_peer, reftime));
+ printf("offsetof(org) = %d\n",
+ (int) offsetof(struct info_peer, org));
+ printf("offsetof(rec) = %d\n",
+ (int) offsetof(struct info_peer, rec));
+ printf("offsetof(xmt) = %d\n",
+ (int) offsetof(struct info_peer, xmt));
+ printf("offsetof(filtdelay) = %d\n",
+ (int) offsetof(struct info_peer, filtdelay));
+ printf("offsetof(filtoffset) = %d\n",
+ (int) offsetof(struct info_peer, filtoffset));
+ printf("offsetof(order) = %d\n",
+ (int) offsetof(struct info_peer, order));
+ printf("offsetof(delay) = %d\n",
+ (int) offsetof(struct info_peer, delay));
+ printf("offsetof(dispersion) = %d\n",
+ (int) offsetof(struct info_peer, dispersion));
+ printf("offsetof(offset) = %d\n",
+ (int) offsetof(struct info_peer, offset));
+ printf("offsetof(selectdisp) = %d\n",
+ (int) offsetof(struct info_peer, selectdisp));
+ printf("offsetof(unused1) = %d\n",
+ (int) offsetof(struct info_peer, unused1));
+ printf("offsetof(unused2) = %d\n",
+ (int) offsetof(struct info_peer, unused2));
+ printf("offsetof(unused3) = %d\n",
+ (int) offsetof(struct info_peer, unused3));
+ printf("offsetof(unused4) = %d\n",
+ (int) offsetof(struct info_peer, unused4));
+ printf("offsetof(unused5) = %d\n",
+ (int) offsetof(struct info_peer, unused5));
+ printf("offsetof(unused6) = %d\n",
+ (int) offsetof(struct info_peer, unused6));
+ printf("offsetof(unused7) = %d\n",
+ (int) offsetof(struct info_peer, unused7));
+ printf("offsetof(estbdelay) = %d\n",
+ (int) offsetof(struct info_peer, estbdelay));
+ printf("offsetof(v6_flag) = %d\n",
+ (int) offsetof(struct info_peer, v6_flag));
+ printf("offsetof(unused9) = %d\n",
+ (int) offsetof(struct info_peer, unused9));
+ printf("offsetof(dstadr6) = %d\n",
+ (int) offsetof(struct info_peer, dstadr6));
+ printf("offsetof(srcadr6) = %d\n",
+ (int) offsetof(struct info_peer, srcadr6));
+ printf("\n");
+
+ printf("sizeof(struct info_peer_stats) = %d\n",
+ (int) sizeof(struct info_peer_stats));
+ printf("offsetof(dstadr) = %d\n",
+ (int) offsetof(struct info_peer_stats, dstadr));
+ printf("offsetof(srcadr) = %d\n",
+ (int) offsetof(struct info_peer_stats, srcadr));
+ printf("offsetof(srcport) = %d\n",
+ (int) offsetof(struct info_peer_stats, srcport));
+ printf("offsetof(flags) = %d\n",
+ (int) offsetof(struct info_peer_stats, flags));
+ printf("offsetof(timereset) = %d\n",
+ (int) offsetof(struct info_peer_stats, timereset));
+ printf("offsetof(timereceived) = %d\n",
+ (int) offsetof(struct info_peer_stats, timereceived));
+ printf("offsetof(timetosend) = %d\n",
+ (int) offsetof(struct info_peer_stats, timetosend));
+ printf("offsetof(timereachable) = %d\n",
+ (int) offsetof(struct info_peer_stats, timereachable));
+ printf("offsetof(sent) = %d\n",
+ (int) offsetof(struct info_peer_stats, sent));
+ printf("offsetof(unused1) = %d\n",
+ (int) offsetof(struct info_peer_stats, unused1));
+ printf("offsetof(processed) = %d\n",
+ (int) offsetof(struct info_peer_stats, processed));
+ printf("offsetof(unused2) = %d\n",
+ (int) offsetof(struct info_peer_stats, unused2));
+ printf("offsetof(badauth) = %d\n",
+ (int) offsetof(struct info_peer_stats, badauth));
+ printf("offsetof(bogusorg) = %d\n",
+ (int) offsetof(struct info_peer_stats, bogusorg));
+ printf("offsetof(oldpkt) = %d\n",
+ (int) offsetof(struct info_peer_stats, oldpkt));
+ printf("offsetof(unused3) = %d\n",
+ (int) offsetof(struct info_peer_stats, unused3));
+ printf("offsetof(unused4) = %d\n",
+ (int) offsetof(struct info_peer_stats, unused4));
+ printf("offsetof(seldisp) = %d\n",
+ (int) offsetof(struct info_peer_stats, seldisp));
+ printf("offsetof(selbroken) = %d\n",
+ (int) offsetof(struct info_peer_stats, selbroken));
+ printf("offsetof(unused5) = %d\n",
+ (int) offsetof(struct info_peer_stats, unused5));
+ printf("offsetof(candidate) = %d\n",
+ (int) offsetof(struct info_peer_stats, candidate));
+ printf("offsetof(unused6) = %d\n",
+ (int) offsetof(struct info_peer_stats, unused6));
+ printf("offsetof(unused7) = %d\n",
+ (int) offsetof(struct info_peer_stats, unused7));
+ printf("offsetof(unused8) = %d\n",
+ (int) offsetof(struct info_peer_stats, unused8));
+ printf("offsetof(v6_flag) = %d\n",
+ (int) offsetof(struct info_peer_stats, v6_flag));
+ printf("offsetof(unused9) = %d\n",
+ (int) offsetof(struct info_peer_stats, unused9));
+ printf("offsetof(dstadr6) = %d\n",
+ (int) offsetof(struct info_peer_stats, dstadr6));
+ printf("offsetof(srcadr6) = %d\n",
+ (int) offsetof(struct info_peer_stats, srcadr6));
+ printf("\n");
+
+ printf("sizeof(struct info_loop) = %d\n",
+ (int) sizeof(struct info_loop));
+ printf("offsetof(last_offset) = %d\n",
+ (int) offsetof(struct info_loop, last_offset));
+ printf("offsetof(drift_comp) = %d\n",
+ (int) offsetof(struct info_loop, drift_comp));
+ printf("offsetof(compliance) = %d\n",
+ (int) offsetof(struct info_loop, compliance));
+ printf("offsetof(watchdog_timer) = %d\n",
+ (int) offsetof(struct info_loop, watchdog_timer));
+ printf("\n");
+
+ printf("sizeof(struct info_sys) = %d\n",
+ (int) sizeof(struct info_sys));
+ printf("offsetof(peer) = %d\n",
+ (int) offsetof(struct info_sys, peer));
+ printf("offsetof(peer_mode) = %d\n",
+ (int) offsetof(struct info_sys, peer_mode));
+ printf("offsetof(leap) = %d\n",
+ (int) offsetof(struct info_sys, leap));
+ printf("offsetof(stratum) = %d\n",
+ (int) offsetof(struct info_sys, stratum));
+ printf("offsetof(precision) = %d\n",
+ (int) offsetof(struct info_sys, precision));
+ printf("offsetof(rootdelay) = %d\n",
+ (int) offsetof(struct info_sys, rootdelay));
+ printf("offsetof(rootdispersion) = %d\n",
+ (int) offsetof(struct info_sys, rootdispersion));
+ printf("offsetof(refid) = %d\n",
+ (int) offsetof(struct info_sys, refid));
+ printf("offsetof(reftime) = %d\n",
+ (int) offsetof(struct info_sys, reftime));
+ printf("offsetof(poll) = %d\n",
+ (int) offsetof(struct info_sys, poll));
+ printf("offsetof(flags) = %d\n",
+ (int) offsetof(struct info_sys, flags));
+ printf("offsetof(unused1) = %d\n",
+ (int) offsetof(struct info_sys, unused1));
+ printf("offsetof(unused2) = %d\n",
+ (int) offsetof(struct info_sys, unused2));
+ printf("offsetof(unused3) = %d\n",
+ (int) offsetof(struct info_sys, unused3));
+ printf("offsetof(bdelay) = %d\n",
+ (int) offsetof(struct info_sys, bdelay));
+ printf("offsetof(frequency) = %d\n",
+ (int) offsetof(struct info_sys, frequency));
+ printf("offsetof(authdelay) = %d\n",
+ (int) offsetof(struct info_sys, authdelay));
+ printf("offsetof(stability) = %d\n",
+ (int) offsetof(struct info_sys, stability));
+ printf("offsetof(v6_flag) = %d\n",
+ (int) offsetof(struct info_sys, v6_flag));
+ printf("offsetof(unused4) = %d\n",
+ (int) offsetof(struct info_sys, unused4));
+ printf("offsetof(peer6) = %d\n",
+ (int) offsetof(struct info_sys, peer6));
+ printf("\n");
+
+ printf("sizeof(struct info_sys_stats) = %d\n",
+ (int) sizeof(struct info_sys_stats));
+ printf("offsetof(timeup) = %d\n",
+ (int) offsetof(struct info_sys_stats, timeup));
+ printf("offsetof(timereset) = %d\n",
+ (int) offsetof(struct info_sys_stats, timereset));
+ printf("offsetof(denied) = %d\n",
+ (int) offsetof(struct info_sys_stats, denied));
+ printf("offsetof(oldversionpkt) = %d\n",
+ (int) offsetof(struct info_sys_stats, oldversionpkt));
+ printf("offsetof(newversionpkt) = %d\n",
+ (int) offsetof(struct info_sys_stats, newversionpkt));
+ printf("offsetof(unknownversion) = %d\n",
+ (int) offsetof(struct info_sys_stats, unknownversion));
+ printf("offsetof(badlength) = %d\n",
+ (int) offsetof(struct info_sys_stats, badlength));
+ printf("offsetof(processed) = %d\n",
+ (int) offsetof(struct info_sys_stats, processed));
+ printf("offsetof(badauth) = %d\n",
+ (int) offsetof(struct info_sys_stats, badauth));
+ printf("offsetof(received) = %d\n",
+ (int) offsetof(struct info_sys_stats, received));
+ printf("offsetof(limitrejected) = %d\n",
+ (int) offsetof(struct info_sys_stats, limitrejected));
+ printf("\n");
+
+ printf("sizeof(struct old_info_sys_stats) = %d\n",
+ (int) sizeof(struct old_info_sys_stats));
+ printf("offsetof(timeup) = %d\n",
+ (int) offsetof(struct old_info_sys_stats, timeup));
+ printf("offsetof(timereset) = %d\n",
+ (int) offsetof(struct old_info_sys_stats, timereset));
+ printf("offsetof(denied) = %d\n",
+ (int) offsetof(struct old_info_sys_stats, denied));
+ printf("offsetof(oldversionpkt) = %d\n",
+ (int) offsetof(struct old_info_sys_stats, oldversionpkt));
+ printf("offsetof(newversionpkt) = %d\n",
+ (int) offsetof(struct old_info_sys_stats, newversionpkt));
+ printf("offsetof(unknownversion) = %d\n",
+ (int) offsetof(struct old_info_sys_stats, unknownversion));
+ printf("offsetof(badlength) = %d\n",
+ (int) offsetof(struct old_info_sys_stats, badlength));
+ printf("offsetof(processed) = %d\n",
+ (int) offsetof(struct old_info_sys_stats, processed));
+ printf("offsetof(badauth) = %d\n",
+ (int) offsetof(struct old_info_sys_stats, badauth));
+ printf("offsetof(wanderhold) = %d\n",
+ (int) offsetof(struct old_info_sys_stats, wanderhold));
+ printf("\n");
+
+ printf("sizeof(struct info_mem_stats) = %d\n",
+ (int) sizeof(struct info_mem_stats));
+ printf("offsetof(timereset) = %d\n",
+ (int) offsetof(struct info_mem_stats, timereset));
+ printf("offsetof(totalpeermem) = %d\n",
+ (int) offsetof(struct info_mem_stats, totalpeermem));
+ printf("offsetof(freepeermem) = %d\n",
+ (int) offsetof(struct info_mem_stats, freepeermem));
+ printf("offsetof(findpeer_calls) = %d\n",
+ (int) offsetof(struct info_mem_stats, findpeer_calls));
+ printf("offsetof(allocations) = %d\n",
+ (int) offsetof(struct info_mem_stats, allocations));
+ printf("offsetof(demobilizations) = %d\n",
+ (int) offsetof(struct info_mem_stats, demobilizations));
+ printf("offsetof(hashcount) = %d\n",
+ (int) offsetof(struct info_mem_stats, hashcount));
+ printf("\n");
+
+ printf("sizeof(struct info_io_stats) = %d\n",
+ (int) sizeof(struct info_io_stats));
+ printf("offsetof(timereset) = %d\n",
+ (int) offsetof(struct info_io_stats, timereset));
+ printf("offsetof(totalrecvbufs) = %d\n",
+ (int) offsetof(struct info_io_stats, totalrecvbufs));
+ printf("offsetof(freerecvbufs) = %d\n",
+ (int) offsetof(struct info_io_stats, freerecvbufs));
+ printf("offsetof(fullrecvbufs) = %d\n",
+ (int) offsetof(struct info_io_stats, fullrecvbufs));
+ printf("offsetof(lowwater) = %d\n",
+ (int) offsetof(struct info_io_stats, lowwater));
+ printf("offsetof(dropped) = %d\n",
+ (int) offsetof(struct info_io_stats, dropped));
+ printf("offsetof(ignored) = %d\n",
+ (int) offsetof(struct info_io_stats, ignored));
+ printf("offsetof(received) = %d\n",
+ (int) offsetof(struct info_io_stats, received));
+ printf("offsetof(sent) = %d\n",
+ (int) offsetof(struct info_io_stats, sent));
+ printf("offsetof(notsent) = %d\n",
+ (int) offsetof(struct info_io_stats, notsent));
+ printf("offsetof(interrupts) = %d\n",
+ (int) offsetof(struct info_io_stats, interrupts));
+ printf("offsetof(int_received) = %d\n",
+ (int) offsetof(struct info_io_stats, int_received));
+ printf("\n");
+
+ printf("sizeof(struct info_timer_stats) = %d\n",
+ (int) sizeof(struct info_timer_stats));
+ printf("offsetof(timereset) = %d\n",
+ (int) offsetof(struct info_timer_stats, timereset));
+ printf("offsetof(alarms) = %d\n",
+ (int) offsetof(struct info_timer_stats, alarms));
+ printf("offsetof(overflows) = %d\n",
+ (int) offsetof(struct info_timer_stats, overflows));
+ printf("offsetof(xmtcalls) = %d\n",
+ (int) offsetof(struct info_timer_stats, xmtcalls));
+ printf("\n");
+
+ printf("sizeof(struct old_conf_peer) = %d\n",
+ (int) sizeof(struct old_conf_peer));
+ printf("offsetof(peeraddr) = %d\n",
+ (int) offsetof(struct old_conf_peer, peeraddr));
+ printf("offsetof(hmode) = %d\n",
+ (int) offsetof(struct old_conf_peer, hmode));
+ printf("offsetof(version) = %d\n",
+ (int) offsetof(struct old_conf_peer, version));
+ printf("offsetof(minpoll) = %d\n",
+ (int) offsetof(struct old_conf_peer, minpoll));
+ printf("offsetof(maxpoll) = %d\n",
+ (int) offsetof(struct old_conf_peer, maxpoll));
+ printf("offsetof(flags) = %d\n",
+ (int) offsetof(struct old_conf_peer, flags));
+ printf("offsetof(ttl) = %d\n",
+ (int) offsetof(struct old_conf_peer, ttl));
+ printf("offsetof(unused) = %d\n",
+ (int) offsetof(struct old_conf_peer, unused));
+ printf("offsetof(keyid) = %d\n",
+ (int) offsetof(struct old_conf_peer, keyid));
+ printf("\n");
+
+ printf("sizeof(struct conf_peer) = %d\n",
+ (int) sizeof(struct conf_peer));
+ printf("offsetof(peeraddr) = %d\n",
+ (int) offsetof(struct conf_peer, peeraddr));
+ printf("offsetof(hmode) = %d\n",
+ (int) offsetof(struct conf_peer, hmode));
+ printf("offsetof(version) = %d\n",
+ (int) offsetof(struct conf_peer, version));
+ printf("offsetof(minpoll) = %d\n",
+ (int) offsetof(struct conf_peer, minpoll));
+ printf("offsetof(maxpoll) = %d\n",
+ (int) offsetof(struct conf_peer, maxpoll));
+ printf("offsetof(flags) = %d\n",
+ (int) offsetof(struct conf_peer, flags));
+ printf("offsetof(ttl) = %d\n",
+ (int) offsetof(struct conf_peer, ttl));
+ printf("offsetof(unused1) = %d\n",
+ (int) offsetof(struct conf_peer, unused1));
+ printf("offsetof(keyid) = %d\n",
+ (int) offsetof(struct conf_peer, keyid));
+ printf("offsetof(keystr) = %d\n",
+ (int) offsetof(struct conf_peer, keystr));
+ printf("offsetof(v6_flag) = %d\n",
+ (int) offsetof(struct conf_peer, v6_flag));
+ printf("offsetof(unused2) = %d\n",
+ (int) offsetof(struct conf_peer, unused2));
+ printf("offsetof(peeraddr6) = %d\n",
+ (int) offsetof(struct conf_peer, peeraddr6));
+ printf("\n");
+
+ printf("sizeof(struct conf_unpeer) = %d\n",
+ (int) sizeof(struct conf_unpeer));
+ printf("offsetof(peeraddr) = %d\n",
+ (int) offsetof(struct conf_unpeer, peeraddr));
+ printf("offsetof(v6_flag) = %d\n",
+ (int) offsetof(struct conf_unpeer, v6_flag));
+ printf("offsetof(peeraddr6) = %d\n",
+ (int) offsetof(struct conf_unpeer, peeraddr6));
+ printf("\n");
+
+ printf("sizeof(struct conf_sys_flags) = %d\n",
+ (int) sizeof(struct conf_sys_flags));
+ printf("offsetof(flags) = %d\n",
+ (int) offsetof(struct conf_sys_flags, flags));
+ printf("\n");
+
+ printf("sizeof(struct info_restrict) = %d\n",
+ (int) sizeof(struct info_restrict));
+ printf("offsetof(addr) = %d\n",
+ (int) offsetof(struct info_restrict, addr));
+ printf("offsetof(mask) = %d\n",
+ (int) offsetof(struct info_restrict, mask));
+ printf("offsetof(count) = %d\n",
+ (int) offsetof(struct info_restrict, count));
+ printf("offsetof(flags) = %d\n",
+ (int) offsetof(struct info_restrict, flags));
+ printf("offsetof(mflags) = %d\n",
+ (int) offsetof(struct info_restrict, mflags));
+ printf("offsetof(v6_flag) = %d\n",
+ (int) offsetof(struct info_restrict, v6_flag));
+ printf("offsetof(unused1) = %d\n",
+ (int) offsetof(struct info_restrict, unused1));
+ printf("offsetof(addr6) = %d\n",
+ (int) offsetof(struct info_restrict, addr6));
+ printf("offsetof(mask6) = %d\n",
+ (int) offsetof(struct info_restrict, mask6));
+ printf("\n");
+
+ printf("sizeof(struct conf_restrict) = %d\n",
+ (int) sizeof(struct conf_restrict));
+ printf("offsetof(addr) = %d\n",
+ (int) offsetof(struct conf_restrict, addr));
+ printf("offsetof(mask) = %d\n",
+ (int) offsetof(struct conf_restrict, mask));
+ printf("offsetof(flags) = %d\n",
+ (int) offsetof(struct conf_restrict, flags));
+ printf("offsetof(mflags) = %d\n",
+ (int) offsetof(struct conf_restrict, mflags));
+ printf("offsetof(v6_flag) = %d\n",
+ (int) offsetof(struct conf_restrict, v6_flag));
+ printf("offsetof(addr6) = %d\n",
+ (int) offsetof(struct conf_restrict, addr6));
+ printf("offsetof(mask6) = %d\n",
+ (int) offsetof(struct conf_restrict, mask6));
+ printf("\n");
+
+ printf("sizeof(struct info_monitor_1) = %d\n",
+ (int) sizeof(struct info_monitor_1));
+ printf("offsetof(avg_int) = %d\n",
+ (int) offsetof(struct info_monitor_1, avg_int));
+ printf("offsetof(last_int) = %d\n",
+ (int) offsetof(struct info_monitor_1, last_int));
+ printf("offsetof(restr) = %d\n",
+ (int) offsetof(struct info_monitor_1, restr));
+ printf("offsetof(count) = %d\n",
+ (int) offsetof(struct info_monitor_1, count));
+ printf("offsetof(addr) = %d\n",
+ (int) offsetof(struct info_monitor_1, addr));
+ printf("offsetof(daddr) = %d\n",
+ (int) offsetof(struct info_monitor_1, daddr));
+ printf("offsetof(flags) = %d\n",
+ (int) offsetof(struct info_monitor_1, flags));
+ printf("offsetof(port) = %d\n",
+ (int) offsetof(struct info_monitor_1, port));
+ printf("offsetof(mode) = %d\n",
+ (int) offsetof(struct info_monitor_1, mode));
+ printf("offsetof(version) = %d\n",
+ (int) offsetof(struct info_monitor_1, version));
+ printf("offsetof(v6_flag) = %d\n",
+ (int) offsetof(struct info_monitor_1, v6_flag));
+ printf("offsetof(unused1) = %d\n",
+ (int) offsetof(struct info_monitor_1, unused1));
+ printf("offsetof(addr6) = %d\n",
+ (int) offsetof(struct info_monitor_1, addr6));
+ printf("offsetof(daddr6) = %d\n",
+ (int) offsetof(struct info_monitor_1, daddr6));
+ printf("\n");
+
+ printf("sizeof(struct info_monitor) = %d\n",
+ (int) sizeof(struct info_monitor));
+ printf("offsetof(avg_int) = %d\n",
+ (int) offsetof(struct info_monitor, avg_int));
+ printf("offsetof(last_int) = %d\n",
+ (int) offsetof(struct info_monitor, last_int));
+ printf("offsetof(restr) = %d\n",
+ (int) offsetof(struct info_monitor, restr));
+ printf("offsetof(count) = %d\n",
+ (int) offsetof(struct info_monitor, count));
+ printf("offsetof(addr) = %d\n",
+ (int) offsetof(struct info_monitor, addr));
+ printf("offsetof(port) = %d\n",
+ (int) offsetof(struct info_monitor, port));
+ printf("offsetof(mode) = %d\n",
+ (int) offsetof(struct info_monitor, mode));
+ printf("offsetof(version) = %d\n",
+ (int) offsetof(struct info_monitor, version));
+ printf("offsetof(v6_flag) = %d\n",
+ (int) offsetof(struct info_monitor, v6_flag));
+ printf("offsetof(unused1) = %d\n",
+ (int) offsetof(struct info_monitor, unused1));
+ printf("offsetof(addr6) = %d\n",
+ (int) offsetof(struct info_monitor, addr6));
+ printf("\n");
+
+ printf("sizeof(struct old_info_monitor) = %d\n",
+ (int) sizeof(struct old_info_monitor));
+ printf("offsetof(lasttime) = %d\n",
+ (int) offsetof(struct old_info_monitor, lasttime));
+ printf("offsetof(firsttime) = %d\n",
+ (int) offsetof(struct old_info_monitor, firsttime));
+ printf("offsetof(count) = %d\n",
+ (int) offsetof(struct old_info_monitor, count));
+ printf("offsetof(addr) = %d\n",
+ (int) offsetof(struct old_info_monitor, addr));
+ printf("offsetof(port) = %d\n",
+ (int) offsetof(struct old_info_monitor, port));
+ printf("offsetof(mode) = %d\n",
+ (int) offsetof(struct old_info_monitor, mode));
+ printf("offsetof(version) = %d\n",
+ (int) offsetof(struct old_info_monitor, version));
+ printf("offsetof(v6_flag) = %d\n",
+ (int) offsetof(struct old_info_monitor, v6_flag));
+ printf("offsetof(addr6) = %d\n",
+ (int) offsetof(struct old_info_monitor, addr6));
+ printf("\n");
+
+ printf("sizeof(struct reset_flags) = %d\n",
+ (int) sizeof(struct reset_flags));
+ printf("offsetof(flags) = %d\n",
+ (int) offsetof(struct reset_flags, flags));
+ printf("\n");
+
+ printf("sizeof(struct info_auth) = %d\n",
+ (int) sizeof(struct info_auth));
+ printf("offsetof(timereset) = %d\n",
+ (int) offsetof(struct info_auth, timereset));
+ printf("offsetof(numkeys) = %d\n",
+ (int) offsetof(struct info_auth, numkeys));
+ printf("offsetof(numfreekeys) = %d\n",
+ (int) offsetof(struct info_auth, numfreekeys));
+ printf("offsetof(keylookups) = %d\n",
+ (int) offsetof(struct info_auth, keylookups));
+ printf("offsetof(keynotfound) = %d\n",
+ (int) offsetof(struct info_auth, keynotfound));
+ printf("offsetof(encryptions) = %d\n",
+ (int) offsetof(struct info_auth, encryptions));
+ printf("offsetof(decryptions) = %d\n",
+ (int) offsetof(struct info_auth, decryptions));
+ printf("offsetof(expired) = %d\n",
+ (int) offsetof(struct info_auth, expired));
+ printf("offsetof(keyuncached) = %d\n",
+ (int) offsetof(struct info_auth, keyuncached));
+ printf("\n");
+
+ printf("sizeof(struct info_trap) = %d\n",
+ (int) sizeof(struct info_trap));
+ printf("offsetof(local_address) = %d\n",
+ (int) offsetof(struct info_trap, local_address));
+ printf("offsetof(trap_address) = %d\n",
+ (int) offsetof(struct info_trap, trap_address));
+ printf("offsetof(trap_port) = %d\n",
+ (int) offsetof(struct info_trap, trap_port));
+ printf("offsetof(sequence) = %d\n",
+ (int) offsetof(struct info_trap, sequence));
+ printf("offsetof(settime) = %d\n",
+ (int) offsetof(struct info_trap, settime));
+ printf("offsetof(origtime) = %d\n",
+ (int) offsetof(struct info_trap, origtime));
+ printf("offsetof(resets) = %d\n",
+ (int) offsetof(struct info_trap, resets));
+ printf("offsetof(flags) = %d\n",
+ (int) offsetof(struct info_trap, flags));
+ printf("offsetof(v6_flag) = %d\n",
+ (int) offsetof(struct info_trap, v6_flag));
+ printf("offsetof(local_address6) = %d\n",
+ (int) offsetof(struct info_trap, local_address6));
+ printf("offsetof(trap_address6) = %d\n",
+ (int) offsetof(struct info_trap, trap_address6));
+ printf("\n");
+
+ printf("sizeof(struct conf_trap) = %d\n",
+ (int) sizeof(struct conf_trap));
+ printf("offsetof(local_address) = %d\n",
+ (int) offsetof(struct conf_trap, local_address));
+ printf("offsetof(trap_address) = %d\n",
+ (int) offsetof(struct conf_trap, trap_address));
+ printf("offsetof(trap_port) = %d\n",
+ (int) offsetof(struct conf_trap, trap_port));
+ printf("offsetof(unused) = %d\n",
+ (int) offsetof(struct conf_trap, unused));
+ printf("offsetof(v6_flag) = %d\n",
+ (int) offsetof(struct conf_trap, v6_flag));
+ printf("offsetof(local_address6) = %d\n",
+ (int) offsetof(struct conf_trap, local_address6));
+ printf("offsetof(trap_address6) = %d\n",
+ (int) offsetof(struct conf_trap, trap_address6));
+ printf("\n");
+
+ printf("sizeof(struct info_control) = %d\n",
+ (int) sizeof(struct info_control));
+ printf("offsetof(ctltimereset) = %d\n",
+ (int) offsetof(struct info_control, ctltimereset));
+ printf("offsetof(numctlreq) = %d\n",
+ (int) offsetof(struct info_control, numctlreq));
+ printf("offsetof(numctlbadpkts) = %d\n",
+ (int) offsetof(struct info_control, numctlbadpkts));
+ printf("offsetof(numctlresponses) = %d\n",
+ (int) offsetof(struct info_control, numctlresponses));
+ printf("offsetof(numctlfrags) = %d\n",
+ (int) offsetof(struct info_control, numctlfrags));
+ printf("offsetof(numctlerrors) = %d\n",
+ (int) offsetof(struct info_control, numctlerrors));
+ printf("offsetof(numctltooshort) = %d\n",
+ (int) offsetof(struct info_control, numctltooshort));
+ printf("offsetof(numctlinputresp) = %d\n",
+ (int) offsetof(struct info_control, numctlinputresp));
+ printf("offsetof(numctlinputfrag) = %d\n",
+ (int) offsetof(struct info_control, numctlinputfrag));
+ printf("offsetof(numctlinputerr) = %d\n",
+ (int) offsetof(struct info_control, numctlinputerr));
+ printf("offsetof(numctlbadoffset) = %d\n",
+ (int) offsetof(struct info_control, numctlbadoffset));
+ printf("offsetof(numctlbadversion) = %d\n",
+ (int) offsetof(struct info_control, numctlbadversion));
+ printf("offsetof(numctldatatooshort) = %d\n",
+ (int) offsetof(struct info_control, numctldatatooshort));
+ printf("offsetof(numctlbadop) = %d\n",
+ (int) offsetof(struct info_control, numctlbadop));
+ printf("offsetof(numasyncmsgs) = %d\n",
+ (int) offsetof(struct info_control, numasyncmsgs));
+ printf("\n");
+
+ printf("sizeof(struct info_clock) = %d\n",
+ (int) sizeof(struct info_clock));
+ printf("offsetof(clockadr) = %d\n",
+ (int) offsetof(struct info_clock, clockadr));
+ printf("offsetof(type) = %d\n",
+ (int) offsetof(struct info_clock, type));
+ printf("offsetof(flags) = %d\n",
+ (int) offsetof(struct info_clock, flags));
+ printf("offsetof(lastevent) = %d\n",
+ (int) offsetof(struct info_clock, lastevent));
+ printf("offsetof(currentstatus) = %d\n",
+ (int) offsetof(struct info_clock, currentstatus));
+ printf("offsetof(polls) = %d\n",
+ (int) offsetof(struct info_clock, polls));
+ printf("offsetof(noresponse) = %d\n",
+ (int) offsetof(struct info_clock, noresponse));
+ printf("offsetof(badformat) = %d\n",
+ (int) offsetof(struct info_clock, badformat));
+ printf("offsetof(baddata) = %d\n",
+ (int) offsetof(struct info_clock, baddata));
+ printf("offsetof(timestarted) = %d\n",
+ (int) offsetof(struct info_clock, timestarted));
+ printf("offsetof(fudgetime1) = %d\n",
+ (int) offsetof(struct info_clock, fudgetime1));
+ printf("offsetof(fudgetime2) = %d\n",
+ (int) offsetof(struct info_clock, fudgetime2));
+ printf("offsetof(fudgeval1) = %d\n",
+ (int) offsetof(struct info_clock, fudgeval1));
+ printf("offsetof(fudgeval2) = %d\n",
+ (int) offsetof(struct info_clock, fudgeval2));
+ printf("\n");
+
+ printf("sizeof(struct conf_fudge) = %d\n",
+ (int) sizeof(struct conf_fudge));
+ printf("offsetof(clockadr) = %d\n",
+ (int) offsetof(struct conf_fudge, clockadr));
+ printf("offsetof(which) = %d\n",
+ (int) offsetof(struct conf_fudge, which));
+ printf("offsetof(fudgetime) = %d\n",
+ (int) offsetof(struct conf_fudge, fudgetime));
+ printf("offsetof(fudgeval_flags) = %d\n",
+ (int) offsetof(struct conf_fudge, fudgeval_flags));
+ printf("\n");
+
+ printf("sizeof(struct info_clkbug) = %d\n",
+ (int) sizeof(struct info_clkbug));
+ printf("offsetof(clockadr) = %d\n",
+ (int) offsetof(struct info_clkbug, clockadr));
+ printf("offsetof(nvalues) = %d\n",
+ (int) offsetof(struct info_clkbug, nvalues));
+ printf("offsetof(ntimes) = %d\n",
+ (int) offsetof(struct info_clkbug, ntimes));
+ printf("offsetof(svalues) = %d\n",
+ (int) offsetof(struct info_clkbug, svalues));
+ printf("offsetof(stimes) = %d\n",
+ (int) offsetof(struct info_clkbug, stimes));
+ printf("offsetof(values) = %d\n",
+ (int) offsetof(struct info_clkbug, values));
+ printf("offsetof(times) = %d\n",
+ (int) offsetof(struct info_clkbug, times));
+ printf("\n");
+
+ printf("sizeof(struct info_kernel) = %d\n",
+ (int) sizeof(struct info_kernel));
+ printf("offsetof(offset) = %d\n",
+ (int) offsetof(struct info_kernel, offset));
+ printf("offsetof(freq) = %d\n",
+ (int) offsetof(struct info_kernel, freq));
+ printf("offsetof(maxerror) = %d\n",
+ (int) offsetof(struct info_kernel, maxerror));
+ printf("offsetof(esterror) = %d\n",
+ (int) offsetof(struct info_kernel, esterror));
+ printf("offsetof(status) = %d\n",
+ (int) offsetof(struct info_kernel, status));
+ printf("offsetof(shift) = %d\n",
+ (int) offsetof(struct info_kernel, shift));
+ printf("offsetof(constant) = %d\n",
+ (int) offsetof(struct info_kernel, constant));
+ printf("offsetof(precision) = %d\n",
+ (int) offsetof(struct info_kernel, precision));
+ printf("offsetof(tolerance) = %d\n",
+ (int) offsetof(struct info_kernel, tolerance));
+ printf("offsetof(ppsfreq) = %d\n",
+ (int) offsetof(struct info_kernel, ppsfreq));
+ printf("offsetof(jitter) = %d\n",
+ (int) offsetof(struct info_kernel, jitter));
+ printf("offsetof(stabil) = %d\n",
+ (int) offsetof(struct info_kernel, stabil));
+ printf("offsetof(jitcnt) = %d\n",
+ (int) offsetof(struct info_kernel, jitcnt));
+ printf("offsetof(calcnt) = %d\n",
+ (int) offsetof(struct info_kernel, calcnt));
+ printf("offsetof(errcnt) = %d\n",
+ (int) offsetof(struct info_kernel, errcnt));
+ printf("offsetof(stbcnt) = %d\n",
+ (int) offsetof(struct info_kernel, stbcnt));
+ printf("\n");
+
+ printf("sizeof(struct info_if_stats) = %d\n",
+ (int) sizeof(struct info_if_stats));
+ printf("offsetof(unaddr) = %d\n",
+ (int) offsetof(struct info_if_stats, unaddr));
+ printf("offsetof(unbcast) = %d\n",
+ (int) offsetof(struct info_if_stats, unbcast));
+ printf("offsetof(unmask) = %d\n",
+ (int) offsetof(struct info_if_stats, unmask));
+ printf("offsetof(v6_flag) = %d\n",
+ (int) offsetof(struct info_if_stats, v6_flag));
+ printf("offsetof(name) = %d\n",
+ (int) offsetof(struct info_if_stats, name));
+ printf("offsetof(flags) = %d\n",
+ (int) offsetof(struct info_if_stats, flags));
+ printf("offsetof(last_ttl) = %d\n",
+ (int) offsetof(struct info_if_stats, last_ttl));
+ printf("offsetof(num_mcast) = %d\n",
+ (int) offsetof(struct info_if_stats, num_mcast));
+ printf("offsetof(received) = %d\n",
+ (int) offsetof(struct info_if_stats, received));
+ printf("offsetof(sent) = %d\n",
+ (int) offsetof(struct info_if_stats, sent));
+ printf("offsetof(notsent) = %d\n",
+ (int) offsetof(struct info_if_stats, notsent));
+ printf("offsetof(uptime) = %d\n",
+ (int) offsetof(struct info_if_stats, uptime));
+ printf("offsetof(scopeid) = %d\n",
+ (int) offsetof(struct info_if_stats, scopeid));
+ printf("offsetof(ifindex) = %d\n",
+ (int) offsetof(struct info_if_stats, ifindex));
+ printf("offsetof(ifnum) = %d\n",
+ (int) offsetof(struct info_if_stats, ifnum));
+ printf("offsetof(peercnt) = %d\n",
+ (int) offsetof(struct info_if_stats, peercnt));
+ printf("offsetof(family) = %d\n",
+ (int) offsetof(struct info_if_stats, family));
+ printf("offsetof(ignore_packets) = %d\n",
+ (int) offsetof(struct info_if_stats, ignore_packets));
+ printf("offsetof(action) = %d\n",
+ (int) offsetof(struct info_if_stats, action));
+ printf("offsetof(_filler0) = %d\n",
+ (int) offsetof(struct info_if_stats, _filler0));
+ printf("\n");
+
+ printf("sizeof(struct info_dns_assoc) = %d\n",
+ (int) sizeof(struct info_dns_assoc));
+ printf("offsetof(peeraddr) = %d\n",
+ (int) offsetof(struct info_dns_assoc, peeraddr));
+ printf("offsetof(associd) = %d\n",
+ (int) offsetof(struct info_dns_assoc, associd));
+ printf("offsetof(hostname) = %d\n",
+ (int) offsetof(struct info_dns_assoc, hostname));
+ printf("\n");
+
diff --git a/usr.sbin/ntp/ntpq/Makefile b/usr.sbin/ntp/ntpq/Makefile
new file mode 100644
index 0000000..f9901a0
--- /dev/null
+++ b/usr.sbin/ntp/ntpq/Makefile
@@ -0,0 +1,40 @@
+# $FreeBSD$
+
+MAN=
+
+.include <src.opts.mk>
+.include <bsd.own.mk>
+
+.PATH: ${.CURDIR}/../../../contrib/ntp/ntpq
+
+BINDIR= /usr/bin
+
+PROG= ntpq
+SRCS= ntpq.c ntpq-opts.c ntpq-subs.c version.c
+
+CFLAGS+= -I${.CURDIR}/../../../contrib/ntp/include \
+ -I${.CURDIR}/../../../contrib/ntp/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/unix/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/pthreads/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/${NTP_ATOMIC}/include \
+ -I${.CURDIR}/../../../contrib/ntp/sntp/libopts \
+ -I${.CURDIR}/../
+
+LIBADD+= edit ntp opts m pthread
+
+.if ${MK_OPENSSL} != "no"
+LIBADD+= crypto
+.else
+LIBADD+= md
+.endif
+
+CFLAGS+= -DHAVE_LIBEDIT -DHAVE_READLINE_READLINE_H \
+ -I${DESTDIR}/${INCLUDEDIR}/edit
+
+CLEANFILES+= .version version.c
+
+version.c:
+ sh -e ${.CURDIR}/../scripts/mkver ntpq
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ntp/ntpq/Makefile.depend b/usr.sbin/ntp/ntpq/Makefile.depend
new file mode 100644
index 0000000..7531e73
--- /dev/null
+++ b/usr.sbin/ntp/ntpq/Makefile.depend
@@ -0,0 +1,28 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libedit \
+ lib/libthr \
+ lib/msun \
+ lib/ncurses/ncursesw \
+ secure/lib/libcrypto \
+ usr.sbin/ntp/libntp \
+ usr.sbin/ntp/libopts \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+version.o: version.c
+version.po: version.c
+.endif
diff --git a/usr.sbin/ntp/ntptime/Makefile b/usr.sbin/ntp/ntptime/Makefile
new file mode 100644
index 0000000..ef02d0f
--- /dev/null
+++ b/usr.sbin/ntp/ntptime/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../../contrib/ntp/util
+
+PROG= ntptime
+MAN=
+
+CFLAGS+= -I${.CURDIR}/../../../contrib/ntp/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/include/ \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/unix/include/ \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/pthreads/include \
+ -I${.CURDIR}/../
+
+LIBADD= ntp pthread
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ntp/ntptime/Makefile.depend b/usr.sbin/ntp/ntptime/Makefile.depend
new file mode 100644
index 0000000..a231d4a
--- /dev/null
+++ b/usr.sbin/ntp/ntptime/Makefile.depend
@@ -0,0 +1,23 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libthr \
+ lib/msun \
+ secure/lib/libcrypto \
+ usr.sbin/ntp/libntp \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ntp/scripts/mkver b/usr.sbin/ntp/scripts/mkver
new file mode 100755
index 0000000..2bc36b5
--- /dev/null
+++ b/usr.sbin/ntp/scripts/mkver
@@ -0,0 +1,44 @@
+#!/bin/sh
+#
+# $FreeBSD$
+#
+PROG=${1-UNKNOWN}
+
+ConfStr="$PROG"
+
+ConfStr="$ConfStr 4.2.8p4"
+
+case "$CSET" in
+ '') ;;
+ *) ConfStr="$ConfStr@$CSET" ;;
+esac
+
+case "" in
+ '')
+ case "1" in
+ '') ;;
+ *) ConfStr="${ConfStr}-a" ;;
+ esac
+ ;;
+ *) ConfStr="${ConfStr}-r" ;;
+esac
+
+if [ ! -f .version ]; then
+ echo 0 > .version
+fi
+RUN="`cat .version`"
+RUN="`expr $RUN + 1`"
+echo $RUN > .version
+
+ConfStr="$ConfStr (${RUN})"
+
+echo "Version <${ConfStr}>";
+
+rm -f version.c
+cat > version.c << -EoF-
+/*
+ * version file for $PROG
+ */
+#include <config.h>
+const char * Version = "${ConfStr}";
+-EoF-
diff --git a/usr.sbin/ntp/scripts/ntptrace b/usr.sbin/ntp/scripts/ntptrace
new file mode 100644
index 0000000..8a895c4
--- /dev/null
+++ b/usr.sbin/ntp/scripts/ntptrace
@@ -0,0 +1,62 @@
+#! /usr/local/bin/perl -w
+#
+# $FreeBSD$
+
+# John Hay -- John.Hay@icomtek.csir.co.za / jhay@FreeBSD.org
+
+use Socket;
+use Getopt::Std;
+use vars qw($opt_n);
+
+$ntpq = "ntpq";
+
+getopts('n');
+
+$dodns = 1;
+$dodns = 0 if (defined($opt_n));
+
+$host = shift;
+$host ||= "127.0.0.1";
+
+for (;;) {
+ $stratum = 255;
+ $cmd = "$ntpq -n -c rv $host";
+ open(PH, $cmd . "|") || die "failed to start command $cmd: $!";
+ while (<PH>) {
+ $stratum = $1 if (/stratum=(\d+)/);
+ $peer = $1 if (/peer=(\d+)/);
+ # Very old servers report phase and not offset.
+ $offset = $1 if (/(?:offset|phase)=([^\s,]+)/);
+ $rootdelay = $1 if (/rootdelay=([^\s,]+)/);
+ $refid = $1 if (/refid=([^\s,]+)/);
+ }
+ close(PH) || die "$cmd failed";
+ last if ($stratum == 255);
+ $offset /= 1000;
+ $rootdelay /= 1000;
+ $dhost = $host;
+ # Only do lookups of IPv4 addresses. The standard lookup functions
+ # of perl only do IPv4 and I don't know if we should require extras.
+ if ($dodns && $host =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/) {
+ $iaddr = inet_aton($host);
+ $name = (gethostbyaddr($iaddr, AF_INET))[0];
+ $dhost = $name if (defined($name));
+ }
+ printf("%s: stratum %d, offset %f, root distance %f",
+ $dhost, $stratum, $offset, $rootdelay);
+ printf(", refid '%s'", $refid) if ($stratum == 1);
+ printf("\n");
+ last if ($stratum == 0 || $stratum == 1 || $stratum == 16);
+ last if ($refid =~ /^127\.127\.\d{1,3}\.\d{1,3}$/);
+
+ $cmd = "$ntpq -n -c \"pstat $peer\" $host";
+ open(PH, $cmd . "|") || die "failed to start command $cmd: $!";
+ $thost = "";
+ while (<PH>) {
+ $thost = $1, last if (/srcadr=(\S+),/);
+ }
+ close(PH) || die "$cmd failed";
+ last if ($thost eq "");
+ $host = $thost;
+}
+
diff --git a/usr.sbin/ntp/scripts/ntpver b/usr.sbin/ntp/scripts/ntpver
new file mode 100755
index 0000000..6dbc510
--- /dev/null
+++ b/usr.sbin/ntp/scripts/ntpver
@@ -0,0 +1,8 @@
+#!/bin/sh
+# $FreeBSD$
+# print version string of NTP daemon
+# Copyright (c) 1997 by Ulrich Windl
+# Modified 970318: Harlan Stenn: rewritten...
+# usage: ntpver hostname
+
+ntpq -c "rv 0 daemon_version" $* | awk '/daemon_version/ { print $2 }'
diff --git a/usr.sbin/ntp/sntp/Makefile b/usr.sbin/ntp/sntp/Makefile
new file mode 100644
index 0000000..ce5c01d
--- /dev/null
+++ b/usr.sbin/ntp/sntp/Makefile
@@ -0,0 +1,31 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+.PATH: ${.CURDIR}/../../../contrib/ntp/sntp
+
+PROG= sntp
+MK_MAN= no
+SRCS= crypto.c kod_management.c log.c main.c networking.c \
+ sntp-opts.c sntp.c utilities.c
+
+CFLAGS+= -I${.CURDIR}/../../../contrib/ntp/include \
+ -I${.CURDIR}/../../../contrib/ntp/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/unix/include \
+ -I${.CURDIR}/../../../contrib/ntp/lib/isc/pthreads/include \
+ -I${.CURDIR}/../../../contrib/ntp/sntp \
+ -I${.CURDIR}/../../../contrib/ntp/sntp/libopts \
+ -I${.CURDIR}/../../../contrib/ntp/sntp/libevent/include \
+ -I${.CURDIR}/../libntpevent \
+ -I${.CURDIR}/../
+
+LIBADD= m opts ntp ntpevent pthread
+
+.if ${MK_OPENSSL} != "no"
+LIBADD+= crypto
+.else
+LIBADD+= md
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ntp/sntp/Makefile.depend b/usr.sbin/ntp/sntp/Makefile.depend
new file mode 100644
index 0000000..9f44d40
--- /dev/null
+++ b/usr.sbin/ntp/sntp/Makefile.depend
@@ -0,0 +1,25 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libthr \
+ lib/msun \
+ secure/lib/libcrypto \
+ usr.sbin/ntp/libntp \
+ usr.sbin/ntp/libntpevent \
+ usr.sbin/ntp/libopts \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/nvram/Makefile b/usr.sbin/nvram/Makefile
new file mode 100644
index 0000000..2c6e43c
--- /dev/null
+++ b/usr.sbin/nvram/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= nvram
+MAN= nvram.8
+MANSUBDIR= /powerpc
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/nvram/nvram.8 b/usr.sbin/nvram/nvram.8
new file mode 100644
index 0000000..ffc005e
--- /dev/null
+++ b/usr.sbin/nvram/nvram.8
@@ -0,0 +1,118 @@
+.\"-
+.\" Copyright (c) 2006 Maxim Sobolev <sobomax@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 ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED 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 1, 2006
+.Dt NVRAM 8 powerpc
+.Os
+.Sh NAME
+.Nm nvram
+.Nd "display or modify contents of the EEPROM or NVRAM"
+.Sh SYNOPSIS
+.Nm
+.Fl p
+.Nm
+.Oo Fl d Ar name Oc Ar ...
+.Op Ar name Ns = Ns Ar value ...
+.Sh DESCRIPTION
+The
+.Nm
+utility provides an interface for displaying and changing the system's
+configuration variables contained in EEPROM or NVRAM.
+In the first synopsis form, all available configuration variables and their
+current values are printed.
+In the second form, the variable selected by
+.Ar name
+is either removed or its value is changed to the
+.Ar value
+following by
+.Ql =
+sign.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl d Ar name
+Delete the variable selected by
+.Ar name
+from the EEPROM or NVRAM.
+The
+.Fl d
+flag can be specified multiple times, in which case multiple variables
+will be removed.
+.It Fl p
+Print all available configuration variables and their current values.
+.El
+.Sh EXAMPLES
+Print all available configuration variables and their current values:
+.Pp
+.Dl "nvram -p"
+.Pp
+Remove the variable named
+.Va local-mac-address? :
+.Pp
+.Dl "nvram -d local-mac-address\e?"
+.Pp
+Set the value of the
+.Va local-mac-address?
+variable to
+.Dq Li true :
+.Pp
+.Dl "nvram local-mac-address\e?=true"
+.Pp
+Note that the
+.Ql \e
+in the above examples is used to keep the shell from interpreting the
+.Ql \&? .
+.Pp
+Remove variables named
+.Va foo
+and
+.Va bar
+and set variable named
+.Va baz
+to
+.Dq Li 100 :
+.Pp
+.Dl "nvram -d foo -d bar baz=100"
+.Sh SEE ALSO
+.Xr powermac_nvram 4 ,
+.Xr eeprom 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 7.0 .
+It is inspired by the
+Darwin/Mac OS X
+.Nm
+utility.
+.Sh AUTHORS
+.An Maxim Sobolev Aq Mt sobomax@FreeBSD.org
+.Sh BUGS
+Currently,
+.Nm
+only supports systems equipped with AMD flash and is only tested on Apple
+G4-based Mac Mini machines.
diff --git a/usr.sbin/nvram/nvram.c b/usr.sbin/nvram/nvram.c
new file mode 100644
index 0000000..52c1e80
--- /dev/null
+++ b/usr.sbin/nvram/nvram.c
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2006 Maxim Sobolev <sobomax@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 ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED 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$
+ */
+
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <dev/powermac_nvram/powermac_nvramvar.h>
+
+#define DEVICE_NAME (_PATH_DEV "powermac_nvram")
+
+static void usage(void);
+static int remove_var(uint8_t *, int, const char *);
+static int append_var(uint8_t *, int, const char *, const char *);
+
+struct deletelist {
+ char *name;
+ struct deletelist *next;
+ struct deletelist *last;
+};
+
+static union {
+ uint8_t buf[sizeof(struct chrp_header)];
+ struct chrp_header header;
+} conv;
+
+int
+main(int argc, char **argv)
+{
+ int opt, dump, fd, res, i, size;
+ uint8_t buf[NVRAM_SIZE], *cp, *common;
+ struct deletelist *dl;
+
+ dump = 0;
+ dl = NULL;
+
+ while((opt = getopt(argc, argv, "d:p")) != -1) {
+ switch(opt) {
+ case 'p':
+ dump = 1;
+ break;
+
+ case 'd':
+ if (dl == NULL) {
+ dl = malloc(sizeof(*dl));
+ if (dl == NULL)
+ err(1, "malloc");
+ bzero(dl, sizeof(*dl));
+ dl->last = dl;
+ } else {
+ dl->last->next = malloc(sizeof(*dl));
+ if (dl->last->next == NULL)
+ err(1, "malloc");
+ dl->last = dl->last->next;
+ bzero(dl->last, sizeof(*dl));
+ }
+ dl->last->name = optarg;
+ break;
+
+ default:
+ usage();
+ /* Not reached */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0 && dump == 0 && dl == NULL) {
+ usage();
+ /* Not reached */
+ }
+
+ fd = open(DEVICE_NAME, O_RDWR);
+ if (fd == -1)
+ err(1, DEVICE_NAME);
+ for (i = 0; i < (int)sizeof(buf);) {
+ res = read(fd, buf + i, sizeof(buf) - i);
+ if (res == -1 && errno != EINTR)
+ err(1, DEVICE_NAME);
+ if (res == 0)
+ break;
+ if (res > 0)
+ i += res;
+ }
+ if (i != sizeof(buf))
+ errx(1, "%s: short read", DEVICE_NAME);
+
+ /* Locate common block */
+ size = 0;
+ for (cp = buf; cp < buf + sizeof(buf); cp += size) {
+ memcpy(conv.buf, cp, sizeof(struct chrp_header));
+ size = conv.header.length * 0x10;
+ if (strncmp(conv.header.name, "common", 7) == 0)
+ break;
+ }
+ if (cp >= buf + sizeof(buf) || size <= (int)sizeof(struct chrp_header))
+ errx(1, "%s: no common block", DEVICE_NAME);
+ common = cp + sizeof(struct chrp_header);
+ size -= sizeof(struct chrp_header);
+
+ if (dump != 0) {
+ while (size > 0) {
+ i = strlen(common) + 1;
+ if (i == 1)
+ break;
+ printf("%s\n", common);
+ size -= i;
+ common += i;
+ }
+ exit(0);
+ }
+
+ for (;dl != NULL; dl = dl->next) {
+ if (remove_var(common, size, dl->name) == 0)
+ warnx("%s: no such variable", dl->name);
+ }
+
+ for (; argc > 0; argc--, argv++) {
+ cp = strchr(*argv, '=');
+ if (cp == NULL)
+ errx(1, "%s: invalid argument", *argv);
+ cp[0] = '\0';
+ cp++;
+ remove_var(common, size, *argv);
+ if (append_var(common, size, *argv, cp) == -1)
+ errx(1, "%s: error setting variable", *argv);
+ }
+
+ for (i = 0; i < (int)sizeof(buf);) {
+ res = write(fd, buf + i, sizeof(buf) - i);
+ if (res == -1 && errno != EINTR)
+ err(1, DEVICE_NAME);
+ if (res == 0)
+ break;
+ if (res > 0)
+ i += res;
+ }
+ if (i != sizeof(buf))
+ errx(1, "%s: short write", DEVICE_NAME);
+ if (close(fd) == -1)
+ err(1, DEVICE_NAME);
+
+ exit(0);
+}
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: nvram [-p] | [-d name ...] [name=value ...]\n");
+ exit(1);
+}
+
+static int
+remove_var(uint8_t *buf, int len, const char *var_name)
+{
+ int nremoved, i, name_len;
+
+ nremoved = 0;
+ name_len = strlen(var_name);
+ while (len > 0) {
+ i = strlen(buf) + 1;
+ if (i == 1)
+ break;
+ if (strncmp(buf, var_name, name_len) == 0 && buf[name_len] == '=') {
+ memmove(buf, buf + i, len - i);
+ memset(buf + len - i, '\0', i);
+ nremoved += 1;
+ continue;
+ }
+ len -= i;
+ buf += i;
+ }
+ return nremoved;
+}
+
+static int
+append_var(uint8_t *buf, int len, const char *var_name, const char *var_value)
+{
+ int i, append_len;
+
+ while (len > 0) {
+ i = strlen(buf) + 1;
+ if (i == 1)
+ break;
+ len -= i;
+ buf += i;
+ }
+ append_len = strlen(var_name) + strlen(var_value) + 2;
+ if (len < append_len)
+ return -1;
+ sprintf(buf, "%s=%s", var_name, var_value);
+ return 0;
+}
diff --git a/usr.sbin/ofwdump/Makefile b/usr.sbin/ofwdump/Makefile
new file mode 100644
index 0000000..ca4f486
--- /dev/null
+++ b/usr.sbin/ofwdump/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= ofwdump
+MAN= ofwdump.8
+SRCS= ofwdump.c ofw_util.c
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ofwdump/Makefile.depend b/usr.sbin/ofwdump/Makefile.depend
new file mode 100644
index 0000000..a5da8fd
--- /dev/null
+++ b/usr.sbin/ofwdump/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libc_nonshared \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ofwdump/ofw_util.c b/usr.sbin/ofwdump/ofw_util.c
new file mode 100644
index 0000000..71361d4
--- /dev/null
+++ b/usr.sbin/ofwdump/ofw_util.c
@@ -0,0 +1,230 @@
+/*-
+ * Copyright (c) 2002 by Thomas Moestl <tmm@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 ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED 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/ioctl.h>
+
+#include <dev/ofw/openfirmio.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 "pathnames.h"
+#include "ofw_util.h"
+
+#define OFW_IOCTL(fd, cmd, val) do { \
+ if (ioctl(fd, cmd, val) == -1) \
+ err(EX_IOERR, "ioctl(..., " #cmd ", ...) failed"); \
+} while (0)
+
+int
+ofw_open(int mode)
+{
+ int fd;
+
+ if ((fd = open(PATH_DEV_OPENFIRM, mode)) == -1)
+ err(EX_UNAVAILABLE, "could not open " PATH_DEV_OPENFIRM);
+ return (fd);
+}
+
+void
+ofw_close(int fd)
+{
+
+ close(fd);
+}
+
+phandle_t
+ofw_root(int fd)
+{
+
+ return (ofw_peer(fd, 0));
+}
+
+phandle_t
+ofw_optnode(int fd)
+{
+ phandle_t rv;
+
+ OFW_IOCTL(fd, OFIOCGETOPTNODE, &rv);
+ return (rv);
+}
+
+phandle_t
+ofw_peer(int fd, phandle_t node)
+{
+ phandle_t rv;
+
+ rv = node;
+ OFW_IOCTL(fd, OFIOCGETNEXT, &rv);
+ return (rv);
+}
+
+phandle_t
+ofw_child(int fd, phandle_t node)
+{
+ phandle_t rv;
+
+ rv = node;
+ OFW_IOCTL(fd, OFIOCGETCHILD, &rv);
+ return (rv);
+}
+
+phandle_t
+ofw_finddevice(int fd, const char *name)
+{
+ struct ofiocdesc d;
+
+ d.of_nodeid = 0;
+ d.of_namelen = strlen(name);
+ d.of_name = name;
+ d.of_buflen = 0;
+ d.of_buf = NULL;
+ if (ioctl(fd, OFIOCFINDDEVICE, &d) == -1) {
+ if (errno == ENOENT)
+ err(EX_UNAVAILABLE, "Node '%s' not found", name);
+ else
+ err(EX_IOERR,
+ "ioctl(..., OFIOCFINDDEVICE, ...) failed");
+ }
+ return (d.of_nodeid);
+}
+
+int
+ofw_firstprop(int fd, phandle_t node, char *buf, int buflen)
+{
+
+ return (ofw_nextprop(fd, node, NULL, buf, buflen));
+}
+
+int
+ofw_nextprop(int fd, phandle_t node, const char *prev, char *buf, int buflen)
+{
+ struct ofiocdesc d;
+
+ d.of_nodeid = node;
+ d.of_namelen = prev != NULL ? strlen(prev) : 0;
+ d.of_name = prev;
+ d.of_buflen = buflen;
+ d.of_buf = buf;
+ if (ioctl(fd, OFIOCNEXTPROP, &d) == -1) {
+ if (errno == ENOENT)
+ return (0);
+ else
+ err(EX_IOERR, "ioctl(..., OFIOCNEXTPROP, ...) failed");
+ }
+ return (d.of_buflen);
+}
+
+static void *
+ofw_malloc(int size)
+{
+ void *p;
+
+ if ((p = malloc(size)) == NULL)
+ err(EX_OSERR, "malloc() failed");
+ return (p);
+}
+
+int
+ofw_getprop(int fd, phandle_t node, const char *name, void *buf, int buflen)
+{
+ struct ofiocdesc d;
+
+ d.of_nodeid = node;
+ d.of_namelen = strlen(name);
+ d.of_name = name;
+ d.of_buflen = buflen;
+ d.of_buf = buf;
+ OFW_IOCTL(fd, OFIOCGET, &d);
+ return (d.of_buflen);
+}
+
+int
+ofw_setprop(int fd, phandle_t node, const char *name, const void *buf,
+ int buflen)
+{
+ struct ofiocdesc d;
+
+ d.of_nodeid = node;
+ d.of_namelen = strlen(name);
+ d.of_name = name;
+ d.of_buflen = buflen;
+ d.of_buf = ofw_malloc(buflen);
+ memcpy(d.of_buf, buf, buflen);
+ OFW_IOCTL(fd, OFIOCSET, &d);
+ free(d.of_buf);
+ return (d.of_buflen);
+}
+
+int
+ofw_getproplen(int fd, phandle_t node, const char *name)
+{
+ struct ofiocdesc d;
+
+ d.of_nodeid = node;
+ d.of_namelen = strlen(name);
+ d.of_name = name;
+ OFW_IOCTL(fd, OFIOCGETPROPLEN, &d);
+ return (d.of_buflen);
+}
+
+int
+ofw_getprop_alloc(int fd, phandle_t node, const char *name, void **buf,
+ int *buflen, int reserve)
+{
+ struct ofiocdesc d;
+ int len, rv;
+
+ do {
+ len = ofw_getproplen(fd, node, name);
+ if (len < 0)
+ return (len);
+ if (*buflen < len + reserve) {
+ if (*buf != NULL)
+ free(*buf);
+ *buflen = len + reserve + OFIOCMAXVALUE;
+ *buf = ofw_malloc(*buflen);
+ }
+ d.of_nodeid = node;
+ d.of_namelen = strlen(name);
+ d.of_name = name;
+ d.of_buflen = *buflen - reserve;
+ d.of_buf = *buf;
+ rv = ioctl(fd, OFIOCGET, &d);
+ } while (rv == -1 && errno == ENOMEM);
+ if (rv == -1)
+ err(EX_IOERR, "ioctl(..., OFIOCGET, ...) failed");
+ return (d.of_buflen);
+}
diff --git a/usr.sbin/ofwdump/ofw_util.h b/usr.sbin/ofwdump/ofw_util.h
new file mode 100644
index 0000000..af8f01f
--- /dev/null
+++ b/usr.sbin/ofwdump/ofw_util.h
@@ -0,0 +1,50 @@
+/*-
+ * Copyright (c) 2002 by Thomas Moestl <tmm@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 ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED 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 OFW_UTIL_H
+#define OFW_UTIL_H
+
+#include <dev/ofw/openfirm.h>
+
+int ofw_open(int);
+void ofw_close(int);
+
+phandle_t ofw_root(int);
+phandle_t ofw_optnode(int);
+phandle_t ofw_peer(int, phandle_t);
+phandle_t ofw_child(int, phandle_t);
+phandle_t ofw_finddevice(int, const char *);
+
+int ofw_firstprop(int, phandle_t, char *, int);
+int ofw_nextprop(int, phandle_t, const char *, char *, int);
+int ofw_getprop(int, phandle_t, const char *, void *, int);
+int ofw_setprop(int, phandle_t, const char *, const void *, int);
+int ofw_getproplen(int, phandle_t, const char *);
+int ofw_getprop_alloc(int, phandle_t, const char *, void **, int *,
+ int);
+
+#endif /* OFW_UTIL_H */
diff --git a/usr.sbin/ofwdump/ofwdump.8 b/usr.sbin/ofwdump/ofwdump.8
new file mode 100644
index 0000000..b21d878
--- /dev/null
+++ b/usr.sbin/ofwdump/ofwdump.8
@@ -0,0 +1,107 @@
+.\" Copyright (c) 2002 by Thomas Moestl <tmm@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 ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED 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 18, 2002
+.Dt OFWDUMP 8
+.Os
+.Sh NAME
+.Nm ofwdump
+.Nd examine the Open Firmware device tree
+.Sh SYNOPSIS
+.Nm
+.Fl a
+.Op Fl p | P Ar property
+.Op Fl R | S
+.Nm
+.Op Fl p | P Ar property
+.Op Fl r
+.Op Fl R | S
+.Op Fl -
+.Ar nodes
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to examine the Open Firmware device tree.
+In the first synopsis form, the complete device tree is printed; in the
+second form, only the selected
+.Ar nodes
+will be examined.
+.Pp
+The following options are available:
+.Bl -tag -width ".Fl P Ar property"
+.It Fl a
+Print the complete device tree.
+.It Fl p
+Print all available properties.
+.It Fl P Ar property
+Only print properties of the given name.
+.It Fl R
+Print properties in
+.Dq raw
+format, i.e., omit all headings and indentation and just write the
+property values unaltered to the standard output.
+This is intended to be used with the
+.Fl P
+option to extract the value of a single property.
+.It Fl S
+Print properties as strings; this is analogous to the
+.Fl R
+option, except that each property is only output to the first
+.Dv NUL
+character, and that newline is appended to each.
+.It Fl r
+Recursively print all children of the specified nodes.
+.El
+.Sh EXAMPLES
+Print the complete device tree:
+.Pp
+.Dl "ofwdump -a"
+.Pp
+Print the complete device subtree of the
+.Dq Li /pci
+node, including all available properties:
+.Pp
+.Dl "ofwdump -pr /pci"
+.Pp
+Print the
+.Dq Li compatible
+property of the
+.Dq Li /pci
+node as plain string:
+.Pp
+.Dl "ofwdump -P compatible -S /pci"
+.Sh SEE ALSO
+.Xr eeprom 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 5.0 .
+.Sh AUTHORS
+The
+.Nm
+utility
+was written by
+.An Thomas Moestl Aq Mt tmm@FreeBSD.org .
diff --git a/usr.sbin/ofwdump/ofwdump.c b/usr.sbin/ofwdump/ofwdump.c
new file mode 100644
index 0000000..9a356f4
--- /dev/null
+++ b/usr.sbin/ofwdump/ofwdump.c
@@ -0,0 +1,250 @@
+/*-
+ * Copyright (c) 2002 by Thomas Moestl <tmm@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 ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED 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 <dev/ofw/openfirm.h>
+#include <dev/ofw/openfirmio.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <vis.h>
+
+#include "ofw_util.h"
+
+/* Constants controlling the layout of the output. */
+#define LVLINDENT 2
+#define NAMEINDENT 2
+#define DUMPINDENT 4
+#define CHARSPERLINE 60
+#define BYTESPERLINE (CHARSPERLINE / 3)
+
+static void usage(void);
+static void ofw_indent(int);
+static void ofw_dump_properties(int, phandle_t, int, int, int);
+static void ofw_dump_property(int fd, phandle_t n, int level,
+ const char *prop, int raw, int str);
+static void ofw_dump(int, const char *, int, int, const char *, int, int);
+
+static void
+usage(void)
+{
+
+ fprintf(stderr,
+ "usage: ofwdump -a [-p | -P property] [-R | -S]\n"
+ " ofwdump [-p | -P property] [-r] [-R | -S] [--] nodes\n");
+ exit(EX_USAGE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int opt, i, fd;
+ int aflag, pflag, rflag, Rflag, Sflag;
+ char *Parg;
+
+ aflag = pflag = rflag = Rflag = Sflag = 0;
+ Parg = NULL;
+ while ((opt = getopt(argc, argv, "-aprP:RS")) != -1) {
+ if (opt == '-')
+ break;
+ switch (opt) {
+ case 'a':
+ aflag = 1;
+ rflag = 1;
+ break;
+ case 'p':
+ if (Parg != NULL)
+ usage();
+ pflag = 1;
+ break;
+ case 'r':
+ rflag = 1;
+ break;
+ case 'P':
+ if (pflag)
+ usage();
+ pflag = 1;
+ Parg = optarg;
+ break;
+ case 'R':
+ if (Sflag)
+ usage();
+ Rflag = 1;
+ break;
+ case 'S':
+ if (Rflag)
+ usage();
+ Sflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ fd = ofw_open(O_RDONLY);
+ if (aflag) {
+ if (argc != 0)
+ usage();
+ ofw_dump(fd, NULL, rflag, pflag, Parg, Rflag, Sflag);
+ } else {
+ /*
+ * For the sake of scripts, usage() is not called here if
+ * argc == 0.
+ */
+ for (i = 0; i < argc; i++)
+ ofw_dump(fd, argv[i], rflag, pflag, Parg, Rflag, Sflag);
+ }
+ ofw_close(fd);
+ return (EX_OK);
+}
+
+static void
+ofw_indent(int level)
+{
+ int i;
+
+ for (i = 0; i < level; i++)
+ putchar(' ');
+}
+
+static void
+ofw_dump_properties(int fd, phandle_t n, int level, int raw, int str)
+{
+ int nlen;
+ char prop[32];
+
+ for (nlen = ofw_firstprop(fd, n, prop, sizeof(prop)); nlen != 0;
+ nlen = ofw_nextprop(fd, n, prop, prop, sizeof(prop)))
+ ofw_dump_property(fd, n, level, prop, raw, str);
+}
+
+static void
+ofw_dump_property(int fd, phandle_t n, int level, const char *prop, int raw,
+ int str)
+{
+ static void *pbuf = NULL;
+ static char *visbuf = NULL;
+ static char printbuf[CHARSPERLINE + 1];
+ static int pblen = 0, vblen = 0;
+ int len, i, j, max, vlen;
+
+ len = ofw_getprop_alloc(fd, n, prop, &pbuf, &pblen, 1);
+ if (len < 0)
+ return;
+ if (raw)
+ write(STDOUT_FILENO, pbuf, len);
+ else if (str)
+ printf("%.*s\n", len, (char *)pbuf);
+ else {
+ ofw_indent(level * LVLINDENT + NAMEINDENT);
+ printf("%s:\n", prop);
+ /* Print in hex. */
+ for (i = 0; i < len; i += BYTESPERLINE) {
+ max = len - i;
+ max = max > BYTESPERLINE ? BYTESPERLINE : max;
+ ofw_indent(level * LVLINDENT + DUMPINDENT);
+ for (j = 0; j < max; j++)
+ printf("%02x ",
+ ((unsigned char *)pbuf)[i + j]);
+ printf("\n");
+ }
+ /*
+ * strvis() and print if it looks like it is
+ * zero-terminated.
+ */
+ if (((char *)pbuf)[len - 1] == '\0' &&
+ strlen(pbuf) == (unsigned)len - 1) {
+ if (vblen < (len - 1) * 4 + 1) {
+ if (visbuf != NULL)
+ free(visbuf);
+ vblen = (OFIOCMAXVALUE + len) * 4 + 1;
+ if ((visbuf = malloc(vblen)) == NULL)
+ err(EX_OSERR,
+ "malloc() failed");
+ }
+ vlen = strvis(visbuf, pbuf, VIS_TAB | VIS_NL);
+ for (i = 0; i < vlen; i += CHARSPERLINE) {
+ ofw_indent(level * LVLINDENT +
+ DUMPINDENT);
+ strlcpy(printbuf, &visbuf[i],
+ sizeof(printbuf));
+ printf("'%s'\n", printbuf);
+ }
+ }
+ }
+}
+
+static void
+ofw_dump_node(int fd, phandle_t n, int level, int rec, int prop,
+ const char *pmatch, int raw, int str)
+{
+ static void *nbuf = NULL;
+ static int nblen = 0;
+ int plen;
+ phandle_t c;
+
+ if (!(raw || str)) {
+ ofw_indent(level * LVLINDENT);
+ printf("Node %#lx", (unsigned long)n);
+ plen = ofw_getprop_alloc(fd, n, "name", &nbuf, &nblen, 1);
+ if (plen > 0)
+ printf(": %.*s\n", (int)plen, (char *)nbuf);
+ else
+ putchar('\n');
+ }
+ if (prop) {
+ if (pmatch)
+ ofw_dump_property(fd, n, level, pmatch, raw, str);
+ else
+ ofw_dump_properties(fd, n, level, raw, str);
+ }
+ if (rec) {
+ for (c = ofw_child(fd, n); c != 0; c = ofw_peer(fd, c)) {
+ ofw_dump_node(fd, c, level + 1, rec, prop, pmatch,
+ raw, str);
+ }
+ }
+}
+
+static void
+ofw_dump(int fd, const char *start, int rec, int prop, const char *pmatch,
+ int raw, int str)
+{
+ phandle_t n;
+
+ n = start == NULL ? ofw_root(fd) : ofw_finddevice(fd, start);
+ ofw_dump_node(fd, n, 0, rec, prop, pmatch, raw, str);
+}
diff --git a/usr.sbin/ofwdump/pathnames.h b/usr.sbin/ofwdump/pathnames.h
new file mode 100644
index 0000000..32f5fe6
--- /dev/null
+++ b/usr.sbin/ofwdump/pathnames.h
@@ -0,0 +1,30 @@
+/*-
+ * Copyright (c) 2002 by Thomas Moestl <tmm@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 ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED 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 <paths.h>
+
+#define PATH_DEV_OPENFIRM _PATH_DEV "openfirm"
diff --git a/usr.sbin/pc-sysinstall/Makefile b/usr.sbin/pc-sysinstall/Makefile
new file mode 100644
index 0000000..d079e16
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+SUBDIR=backend backend-partmanager backend-query conf doc examples
+SUBDIR+=pc-sysinstall
+SUBDIR_PARALLEL=
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/pc-sysinstall/Makefile.inc b/usr.sbin/pc-sysinstall/Makefile.inc
new file mode 100644
index 0000000..265f86d
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/Makefile.inc
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+.include "../Makefile.inc"
diff --git a/usr.sbin/pc-sysinstall/backend-partmanager/Makefile b/usr.sbin/pc-sysinstall/backend-partmanager/Makefile
new file mode 100644
index 0000000..6420e81
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-partmanager/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+FILES= create-part.sh delete-part.sh
+FILESMODE= ${BINMODE}
+FILESDIR=${SHAREDIR}/pc-sysinstall/backend-partmanager
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/pc-sysinstall/backend-partmanager/Makefile.depend b/usr.sbin/pc-sysinstall/backend-partmanager/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-partmanager/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/pc-sysinstall/backend-partmanager/create-part.sh b/usr.sbin/pc-sysinstall/backend-partmanager/create-part.sh
new file mode 100755
index 0000000..5fe3540
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-partmanager/create-part.sh
@@ -0,0 +1,103 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Create partitions on a target disk
+#############################
+
+. ${PROGDIR}/backend/functions.sh
+
+if [ -z "${1}" ] ; then
+ echo "Error: No disk specified!"
+ exit 1
+fi
+
+if [ -z "${2}" ] ; then
+ echo "Error: No size specified!"
+ exit 1
+fi
+
+if [ ! -e "/dev/${1}" ] ; then
+ echo "Error: Disk /dev/${1} does not exist!"
+ exit 1
+fi
+
+DISK="${1}"
+MB="${2}"
+TYPE="${3}"
+STARTBLOCK="${4}"
+
+TOTALBLOCKS="`expr $MB \* 2048`"
+
+# If no TYPE specified, default to MBR
+if [ -z "$TYPE" ] ; then TYPE="mbr" ; fi
+
+# Sanity check the gpart type
+case $TYPE in
+ apm|APM) ;;
+ bsd|BSD) ;;
+ ebr|EBR) ;;
+ pc98|pc98) ;;
+ gpt|GPT) ;;
+ mbr|MBR) ;;
+ vtoc8|VTOC8) ;;
+ *) echo "Error: Unknown gpart type: $TYPE" ; exit 1 ;;
+esac
+
+# Lets figure out what number this partition will be
+LASTSLICE="`gpart show $DISK | grep -v -e $DISK -e '\- free \-' -e '^$' | awk 'END {print $3}'`"
+if [ -z "${LASTSLICE}" ] ; then
+ LASTSLICE="1"
+else
+ LASTSLICE="`expr $LASTSLICE + 1`"
+fi
+
+SLICENUM="${LASTSLICE}"
+
+# Set a 4k Aligned start block if none specified
+if [ "${SLICENUM}" = "1" -a -z "$STARTBLOCK" ] ; then
+ STARTBLOCK="2016"
+fi
+
+
+# If this is an empty disk, see if we need to create a new scheme for it
+gpart show ${DISK} >/dev/null 2>/dev/null
+if [ $? -eq 0 -a "${SLICENUM}" = "1" ] ; then
+ if [ "${TYPE}" = "mbr" -o "${TYPE}" = "MBR" ] ; then
+ flags="-s ${TYPE} -f active"
+ else
+ flags="-s ${TYPE}"
+ fi
+ gpart create ${flags} ${DISK}
+fi
+
+# If we have a starting block, use it
+if [ -n "$STARTBLOCK" ] ; then
+ sBLOCK="-b $STARTBLOCK"
+fi
+
+gpart add ${sBLOCK} -s ${TOTALBLOCKS} -t freebsd -i ${SLICENUM} ${DISK}
+exit "$?"
diff --git a/usr.sbin/pc-sysinstall/backend-partmanager/delete-part.sh b/usr.sbin/pc-sysinstall/backend-partmanager/delete-part.sh
new file mode 100755
index 0000000..1db01ff
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-partmanager/delete-part.sh
@@ -0,0 +1,89 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Delete a specified partition, takes effect immediately
+########################################################
+
+. ${PROGDIR}/backend/functions.sh
+. ${PROGDIR}/backend/functions-disk.sh
+
+if [ -z "${1}" ]
+then
+ echo "Error: No partition specified!"
+ exit 1
+fi
+
+if [ ! -e "/dev/${1}" ]
+then
+ echo "Error: Partition /dev/${1} does not exist!"
+ exit 1
+fi
+
+PARTITION="${1}"
+
+# First lets figure out the partition number for the given device
+##################################################################
+
+# Get the number of characters in this dev
+CHARS="`echo $PARTITION | wc -c`"
+
+PARTINDEX=""
+
+# Lets read through backwards until we get the part number
+while
+z=1
+do
+ CHARS=$((CHARS-1))
+ LAST_CHAR=`echo "${PARTITION}" | cut -c $CHARS`
+ echo "${LAST_CHAR}" | grep -q "^[0-9]$" 2>/dev/null
+ if [ $? -eq 0 ] ; then
+ PARTINDEX="${LAST_CHAR}${PARTINDEX}"
+ else
+ break
+ fi
+done
+
+# Now get current disk we are working on
+CHARS=`expr $CHARS - 1`
+DISK="`echo $PARTITION | cut -c 1-${CHARS}`"
+
+# Make sure we have a valid disk name still
+if [ ! -e "/dev/${DISK}" ] ; then
+ echo "Error: Disk: ${DISK} doesn't exist!"
+ exit 1
+fi
+
+echo "Running: gpart delete -i ${PARTINDEX} ${DISK}"
+gpart delete -i ${PARTINDEX} ${DISK} >/dev/null 2>/dev/null
+
+# Check if this was the last partition and destroy the disk geom if so
+get_disk_partitions "${DISK}"
+if [ -z "${VAL}" ] ; then
+ gpart destroy ${DISK}
+fi
+
+exit "$?"
diff --git a/usr.sbin/pc-sysinstall/backend-query/Makefile b/usr.sbin/pc-sysinstall/backend-query/Makefile
new file mode 100644
index 0000000..e5541b7
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/Makefile
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+FILES= detect-laptop.sh detect-nics.sh detect-emulation.sh disk-info.sh \
+ disk-list.sh disk-part.sh enable-net.sh get-packages.sh list-config.sh \
+ list-components.sh list-mirrors.sh list-packages.sh list-rsync-backups.sh \
+ list-tzones.sh query-langs.sh send-logs.sh set-mirror.sh setup-ssh-keys.sh \
+ sys-mem.sh test-live.sh test-netup.sh update-part-list.sh xkeyboard-layouts.sh \
+ xkeyboard-models.sh xkeyboard-variants.sh
+FILESMODE= ${BINMODE}
+FILESDIR=${SHAREDIR}/pc-sysinstall/backend-query
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/pc-sysinstall/backend-query/Makefile.depend b/usr.sbin/pc-sysinstall/backend-query/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/pc-sysinstall/backend-query/detect-emulation.sh b/usr.sbin/pc-sysinstall/backend-query/detect-emulation.sh
new file mode 100755
index 0000000..5eefd26
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/detect-emulation.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+case "$(kenv smbios.system.product)" in
+VirtualBox)
+ echo "emulation: VIRTUALBOX"
+ exit 0
+ ;;
+VMware*)
+ echo "emulation: VMWARE"
+ exit 0
+ ;;
+*)
+ echo "emulation: NO"
+ exit 1
+ ;;
+esac
diff --git a/usr.sbin/pc-sysinstall/backend-query/detect-laptop.sh b/usr.sbin/pc-sysinstall/backend-query/detect-laptop.sh
new file mode 100755
index 0000000..d23a420
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/detect-laptop.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+if devinfo | grep -q acpi_acad0; then
+ echo "laptop: YES"
+else
+ echo "laptop: NO"
+fi
diff --git a/usr.sbin/pc-sysinstall/backend-query/detect-nics.sh b/usr.sbin/pc-sysinstall/backend-query/detect-nics.sh
new file mode 100755
index 0000000..2f7272a
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/detect-nics.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+for i in $(ifconfig -l); do
+ case "${i%%[0-9]*}" in
+ lo|fwe|fwip|plip|pfsync|pflog|tun)
+ continue
+ ;;
+ esac
+ IDENT=$(dmesg | sed -n "s/^$i: <\(.*\)>.*$/\1/p" | head -1)
+ echo "${i}: <$IDENT>"
+done
diff --git a/usr.sbin/pc-sysinstall/backend-query/disk-info.sh b/usr.sbin/pc-sysinstall/backend-query/disk-info.sh
new file mode 100755
index 0000000..adaeaa5
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/disk-info.sh
@@ -0,0 +1,60 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Query a disk for partitions and display them
+#############################################################################
+
+. ${PROGDIR}/backend/functions.sh
+. ${PROGDIR}/backend/functions-disk.sh
+
+DISK="${1}"
+
+[ -z "${DISK}" ] && { echo 'Error: No disk specified!'; exit 1; }
+[ ! -e "/dev/${DISK}" ] && \
+ { echo "Error: Disk /dev/${DISK} does not exist!"; exit 1; }
+
+get_disk_cyl "${DISK}"
+CYLS="${VAL}"
+
+get_disk_heads "${DISK}"
+HEADS="${VAL}"
+
+get_disk_sectors "${DISK}"
+SECS="${VAL}"
+
+# Now get the disks size in MB
+KB="`diskinfo -v ${1} | grep 'bytes' | cut -d '#' -f 1 | tr -s '\t' ' ' | tr -d ' '`"
+MB=$(convert_byte_to_megabyte ${KB})
+
+# Now get the Controller Type
+CTYPE="`dmesg | grep "^${1}:" | grep "B <" | cut -d '>' -f 2 | cut -d ' ' -f 3-10`"
+
+echo "cylinders=${CYLS}"
+echo "heads=${HEADS}"
+echo "sectors=${SECS}"
+echo "size=${MB}"
+echo "type=${CTYPE}"
diff --git a/usr.sbin/pc-sysinstall/backend-query/disk-list.sh b/usr.sbin/pc-sysinstall/backend-query/disk-list.sh
new file mode 100755
index 0000000..2616ef9
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/disk-list.sh
@@ -0,0 +1,112 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+ARGS=$1
+FLAGS_MD=""
+FLAGS_CD=""
+FLAGS_VERBOSE=""
+
+shift
+while [ -n "$1" ]
+do
+ case "$1" in
+ -m)
+ FLAGS_MD=1
+ ;;
+ -v)
+ FLAGS_VERBOSE=1
+ ;;
+ -c)
+ FLAGS_CD=1
+ ;;
+ esac
+ shift
+done
+
+# Create our device listing
+SYSDISK=$(sysctl -n kern.disks)
+if [ -n "${FLAGS_MD}" ]
+then
+ MDS=`mdconfig -l`
+ if [ -n "${MDS}" ]
+ then
+ SYSDISK="${SYSDISK} ${MDS}"
+ fi
+fi
+
+# Add any RAID devices
+if [ -d "/dev/raid" ] ; then
+ cd /dev/raid
+ for i in `ls`
+ do
+ SYSDISK="${SYSDISK} ${i}"
+ done
+fi
+
+# Now loop through these devices, and list the disk drives
+for i in ${SYSDISK}
+do
+
+ # Get the current device
+ DEV="${i}"
+
+ # Make sure we don't find any cd devices
+ if [ -z "${FLAGS_CD}" ]
+ then
+ case "${DEV}" in
+ acd[0-9]*|cd[0-9]*|scd[0-9]*) continue ;;
+ esac
+ fi
+
+ # Try and find some identification information with camcontrol
+ NEWLINE=$(camcontrol identify $DEV 2>/dev/null | sed -ne 's/^device model *//p')
+ if [ -z "$NEWLINE" ]; then
+ NEWLINE=" <Unknown Device>"
+ fi
+
+ if [ -n "${FLAGS_MD}" ] && echo "${DEV}" | grep -E '^md[0-9]+' >/dev/null 2>/dev/null
+ then
+ NEWLINE=" <Memory Disk>"
+ fi
+
+ if [ -n "${FLAGS_VERBOSE}" ]
+ then
+ :
+ fi
+
+ # Save the disk list
+ if [ ! -z "$DLIST" ]
+ then
+ DLIST="\n${DLIST}"
+ fi
+
+ DLIST="${DEV}:${NEWLINE}${DLIST}"
+
+done
+
+# Echo out the found line
+echo -e "$DLIST" | sort
diff --git a/usr.sbin/pc-sysinstall/backend-query/disk-part.sh b/usr.sbin/pc-sysinstall/backend-query/disk-part.sh
new file mode 100755
index 0000000..e5e7355
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/disk-part.sh
@@ -0,0 +1,111 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Query a disk for partitions and display them
+#############################
+
+. ${PROGDIR}/backend/functions.sh
+. ${PROGDIR}/backend/functions-disk.sh
+
+if [ -z "${1}" ]
+then
+ echo "Error: No disk specified!"
+ exit 1
+fi
+
+if [ ! -e "/dev/${1}" ]
+then
+ echo "Error: Disk /dev/${1} does not exist!"
+ exit 1
+fi
+
+DISK="${1}"
+
+# Now get the disks size in MB
+KB="`diskinfo -v ${1} | grep 'bytes' | cut -d '#' -f 1 | tr -s '\t' ' ' | tr -d ' '`"
+MB=$(convert_byte_to_megabyte ${KB})
+TOTALSIZE="$MB"
+TOTALB="`diskinfo -v ${1} | grep 'in sectors' | tr -s '\t' ' ' | cut -d ' ' -f 2`"
+
+gpart show ${1} >/dev/null 2>/dev/null
+if [ "$?" != "0" ] ; then
+ # No partitions on this disk, display entire disk size and exit
+ echo "${1}-freemb: ${TOTALSIZE}"
+ echo "${1}-freeblocks: ${TOTALB}"
+ exit
+fi
+
+# Display if this is GPT or MBR formatted
+TYPE=`gpart show ${1} | awk '/^=>/ { printf("%s",$5); }'`
+echo "${1}-format: $TYPE"
+
+# Set some search flags
+PART="0"
+EXTENDED="0"
+START="0"
+SIZEB="0"
+
+# Get a listing of partitions on this disk
+get_disk_partitions "${DISK}"
+PARTS="${VAL}"
+for curpart in $PARTS
+do
+
+ # First get the sysid / label for this partition
+ if [ "$TYPE" = "MBR" ] ; then
+ get_partition_sysid_mbr "${DISK}" "${curpart}"
+ echo "${curpart}-sysid: ${VAL}"
+ get_partition_label_mbr "${DISK}" "${curpart}"
+ echo "${curpart}-label: ${VAL}"
+ else
+ get_partition_label_gpt "${DISK}" "${curpart}"
+ echo "${curpart}-sysid: ${VAL}"
+ echo "${curpart}-label: ${VAL}"
+ fi
+
+ # Now get the startblock, blocksize and MB size of this partition
+
+ get_partition_startblock "${DISK}" "${curpart}"
+ START="${VAL}"
+ echo "${curpart}-blockstart: ${START}"
+
+ get_partition_blocksize "${DISK}" "${curpart}"
+ SIZEB="${VAL}"
+ echo "${curpart}-blocksize: ${SIZEB}"
+
+ SIZEMB=$(convert_blocks_to_megabyte ${SIZEB})
+ echo "${curpart}-sizemb: ${SIZEMB}"
+
+done
+
+
+# Now calculate any free space
+LASTB="`expr $SIZEB + $START`"
+FREEB="`expr $TOTALB - $LASTB`"
+FREEMB="`expr ${FREEB} / 2048`"
+echo "${1}-freemb: $FREEMB"
+echo "${1}-freeblocks: $FREEB"
diff --git a/usr.sbin/pc-sysinstall/backend-query/enable-net.sh b/usr.sbin/pc-sysinstall/backend-query/enable-net.sh
new file mode 100755
index 0000000..8cd72a1
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/enable-net.sh
@@ -0,0 +1,114 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, Inc. All rights reserved.
+# Copyright (c) 2011 The FreeBSD Foundation
+# All rights reserved.
+#
+# Portions of this software were developed by Bjoern Zeeb
+# 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$
+
+# Script which enables networking with specified options
+###########################################################################
+
+. ${PROGDIR}/backend/functions.sh
+. ${PROGDIR}/conf/pc-sysinstall.conf
+. ${BACKEND}/functions-networking.sh
+. ${BACKEND}/functions-parse.sh
+
+
+NIC="$1"
+IP="$2"
+NETMASK="$3"
+DNS="$4"
+GATEWAY="$5"
+MIRRORFETCH="$6"
+IPV6="$7"
+IPV6GATE="$8"
+IPV6DNS="$9"
+
+if [ -z "${NIC}" ]
+then
+ echo "ERROR: Usage enable-net <nic> <ip> <netmask> <dns> <gateway> <ipv6> " \
+ "<ipv6gateway> <ipv6dns>"
+ exit 150
+fi
+
+if [ "$NIC" = "AUTO-DHCP" ]
+then
+ enable_auto_dhcp
+elif [ "$NIC" = "IPv6-SLAAC" ]
+then
+ enable_auto_slaac
+ # In addition, if static values were defined, add them as well.
+ # We might not get DNS information from RAs, for example.
+ if [ -n "${IPV6}" ]; then
+ VAL=""
+ get_first_wired_nic
+ if [ -n "${VAL}" ]; then
+ ifconfig ${VAL} inet6 ${IPV6} alias
+ fi
+ fi
+ # Append only here.
+ if [ -n "${IPV6DNS}" ]; then
+ echo "nameserver ${IPV6DNS}" >>/etc/resolv.conf
+ fi
+ # Do not
+ if [ -n "${IPV6GATE}" ]; then
+ # Check if we have a default route already to not overwrite.
+ if ! route -n get -inet6 default > /dev/null 2>&1 ; then
+ route add -inet6 default ${IPV6GATE}
+ fi
+ fi
+else
+ echo "Enabling NIC: $NIC"
+ if [ -n "${IP}" ]; then
+ ifconfig ${NIC} inet ${IP} ${NETMASK}
+ fi
+ if [ -n "${IPV6}" ]; then
+ ifconfig ${NIC} inet6 ${IPV6} alias
+ fi
+
+ # Keep default from IPv4-only support times and clear the resolv.conf file.
+ : > /etc/resolv.conf
+ if [ -n "${DNS}" ]; then
+ echo "nameserver ${DNS}" >>/etc/resolv.conf
+ fi
+ if [ -n "${IPV6DNS}" ]; then
+ echo "nameserver ${IPV6DNS}" >>/etc/resolv.conf
+ fi
+
+ if [ -n "${GATE}" ]; then
+ route add -inet default ${GATE}
+ fi
+ if [ -n "${IPV6GATE}" ]; then
+ route add -inet6 default ${IPV6GATE}
+ fi
+fi
+
+case ${MIRRORFETCH} in
+ ON|on|yes|YES) fetch -o /tmp/mirrors-list.txt ${MIRRORLIST} >/dev/null 2>/dev/null;;
+ *) ;;
+esac
diff --git a/usr.sbin/pc-sysinstall/backend-query/get-packages.sh b/usr.sbin/pc-sysinstall/backend-query/get-packages.sh
new file mode 100755
index 0000000..6bdcd3b
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/get-packages.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Script which lists the available packages for this release
+###########################################################################
+
+. ${PROGDIR}/backend/functions.sh
+. ${PROGDIR}/backend/functions-packages.sh
+
+ID=`id -u`
+if [ "${ID}" -ne "0" ]
+then
+ echo "Error: must be root!"
+ exit 1
+fi
+
+if [ ! -f "${PKGDIR}/INDEX" ]
+then
+ get_package_index
+fi
+
+if [ -f "${PKGDIR}/INDEX" ]
+then
+ echo "${PKGDIR}/INDEX"
+ exit 0
+fi
+
+exit 1
diff --git a/usr.sbin/pc-sysinstall/backend-query/list-components.sh b/usr.sbin/pc-sysinstall/backend-query/list-components.sh
new file mode 100755
index 0000000..a7cde89
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/list-components.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Script which lists the available components for this release
+###########################################################################
+
+. ${PROGDIR}/backend/functions.sh
+
+echo "Available Components:"
+
+if [ -d "${COMPDIR}" ]
+then
+ cd ${COMPDIR}
+ for i in `ls -d *`
+ do
+ if [ -e "${i}/component.cfg" -a -e "${i}/install.sh" -a -e "${i}/distfiles" ]
+ then
+ NAME="`grep 'name:' ${i}/component.cfg | cut -d ':' -f 2`"
+ DESC="`grep 'description:' ${i}/component.cfg | cut -d ':' -f 2`"
+ TYPE="`grep 'type:' ${i}/component.cfg | cut -d ':' -f 2`"
+ echo " "
+ echo "name: ${i}"
+ echo "desc:${DESC}"
+ echo "type:${TYPE}"
+ if [ -e "${i}/component.png" ]
+ then
+ echo "icon: ${COMPDIR}/${i}/component.png"
+ fi
+ fi
+ done
+fi
diff --git a/usr.sbin/pc-sysinstall/backend-query/list-config.sh b/usr.sbin/pc-sysinstall/backend-query/list-config.sh
new file mode 100755
index 0000000..b7edda2
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/list-config.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+echo "branch=${FBSD_BRANCH}"
+echo "arch=${FBSD_ARCH}"
+exit 0
diff --git a/usr.sbin/pc-sysinstall/backend-query/list-mirrors.sh b/usr.sbin/pc-sysinstall/backend-query/list-mirrors.sh
new file mode 100755
index 0000000..0fe209d
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/list-mirrors.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+. ${PROGDIR}/backend/functions.sh
+. ${PROGDIR}/backend/functions-ftp.sh
+
+# Backend script which lists all the available ftp mirrors for front-ends to display
+COUNTRY="${1}"
+
+get_ftp_mirrors "${COUNTRY}"
+show_mirrors "${VAL}"
+
+exit 0
diff --git a/usr.sbin/pc-sysinstall/backend-query/list-packages.sh b/usr.sbin/pc-sysinstall/backend-query/list-packages.sh
new file mode 100755
index 0000000..45941ff
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/list-packages.sh
@@ -0,0 +1,86 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Script which lists the available packages for this release
+###########################################################################
+
+. ${PROGDIR}/backend/functions.sh
+. ${PROGDIR}/backend/functions-packages.sh
+
+PACKAGE_CATEGORY="${1}"
+PACKAGE_NAME="${2}"
+NARGS=0
+
+if [ ! -f "${PKGDIR}/INDEX" ]
+then
+ echo "Error: please fetch package index with get-packages!"
+ exit 1
+fi
+
+if [ ! -f "${PKGDIR}/INDEX.parsed" ]
+then
+ parse_package_index
+fi
+
+if [ -n "${PACKAGE_CATEGORY}" ]
+then
+ NARGS=$((NARGS+1))
+fi
+
+if [ -n "${PACKAGE_NAME}" ]
+then
+ NARGS=$((NARGS+1))
+fi
+
+if [ "${NARGS}" -eq "0" ]
+then
+ show_packages
+
+elif [ "${NARGS}" -eq "1" ]
+then
+
+ if [ "${PACKAGE_CATEGORY}" = "@INDEX@" ]
+ then
+ if [ -f "${PKGDIR}/INDEX" ]
+ then
+ echo "${PKGDIR}/INDEX"
+ exit 0
+ else
+ exit 1
+ fi
+
+ else
+ show_packages_by_category "${PACKAGE_CATEGORY}"
+ fi
+
+elif [ "${NARGS}" -eq "2" ]
+then
+ show_package_by_name "${PACKAGE_CATEGORY}" "${PACKAGE_NAME}"
+
+else
+ show_packages
+fi
diff --git a/usr.sbin/pc-sysinstall/backend-query/list-rsync-backups.sh b/usr.sbin/pc-sysinstall/backend-query/list-rsync-backups.sh
new file mode 100755
index 0000000..68ced7f
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/list-rsync-backups.sh
@@ -0,0 +1,70 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Script which lists the backups present on a server
+###########################################################################
+
+. ${PROGDIR}/backend/functions.sh
+
+SSHUSER=$1
+SSHHOST=$2
+SSHPORT=$3
+
+if [ -z "${SSHHOST}" -o -z "${SSHPORT}" ]
+then
+ echo "ERROR: Usage list-rsync-backups.sh <user> <host> <port>"
+ exit 150
+fi
+
+# Look for full-system backups, needs at minimum a kernel to be bootable
+FINDCMD="find . -type d -maxdepth 6 -name 'kernel' | grep '/boot/kernel'"
+
+# Get a listing of the number of full backups saved
+OLDBACKUPS=`ssh -o 'BatchMode=yes' -p ${SSHPORT} ${SSHUSER}@${SSHHOST} "${FINDCMD}"`
+if [ "$?" = "0" ]
+then
+ for i in ${OLDBACKUPS}
+ do
+ BACKPATH="`echo ${i} | sed 's|/boot/.*||g' | sed 's|^./||g'`"
+ if [ -z "${BACKLIST}" ]
+ then
+ BACKLIST="${BACKPATH}"
+ else
+ BACKLIST="${BACKLIST}:${BACKPATH}"
+ fi
+ done
+
+ if [ -z "${BACKLIST}" ]
+ then
+ echo "NONE"
+ else
+ echo "$BACKLIST"
+ fi
+
+else
+ echo "FAILED"
+fi
diff --git a/usr.sbin/pc-sysinstall/backend-query/list-tzones.sh b/usr.sbin/pc-sysinstall/backend-query/list-tzones.sh
new file mode 100755
index 0000000..c7009b0
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/list-tzones.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Backend script which lists all the available timezones for front-ends to display
+egrep -v '^#' /usr/share/zoneinfo/zone.tab |\
+ tr -s "\t" ":" |\
+ cut -d ":" -f 3-4 |\
+ sort
+
+exit 0
diff --git a/usr.sbin/pc-sysinstall/backend-query/query-langs.sh b/usr.sbin/pc-sysinstall/backend-query/query-langs.sh
new file mode 100755
index 0000000..2ce6066
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/query-langs.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+cat ${PROGDIR}/conf/avail-langs
+
+exit 0
diff --git a/usr.sbin/pc-sysinstall/backend-query/send-logs.sh b/usr.sbin/pc-sysinstall/backend-query/send-logs.sh
new file mode 100755
index 0000000..b81087c
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/send-logs.sh
@@ -0,0 +1,83 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Script which creates a gzipped log and optionally mails it to the specified address
+############################################################################
+
+. ${PROGDIR}/backend/functions.sh
+. ${PROGDIR}/conf/pc-sysinstall.conf
+. ${BACKEND}/functions-networking.sh
+. ${BACKEND}/functions-parse.sh
+
+# Bring up all NICS under DHCP
+enable_auto_dhcp
+
+MAILTO="$1"
+MAILRESULT="0"
+
+# Set the location of our compressed log
+TMPLOG="/tmp/pc-sysinstall.log"
+
+echo "# PC-SYSINSTALL LOG" >${TMPLOG}
+cat ${LOGOUT} >> ${TMPLOG}
+
+# Check if we have a GUI generated install cfg
+if [ -e "/tmp/sys-install.cfg" ]
+then
+ echo "" >>${TMPLOG}
+ echo "# PC-SYSINSTALL CFG " >>${TMPLOG}
+ cat /tmp/sys-install.cfg | grep -vE 'rootPass|userPass' >> ${TMPLOG}
+fi
+
+# Save dmesg output
+echo "" >>${TMPLOG}
+echo "# DMESG OUTPUT " >>${TMPLOG}
+dmesg >> ${TMPLOG}
+
+# Get gpart info on all disks
+for i in `pc-sysinstall disk-list | cut -d ':' -f 1`
+do
+ echo "" >>${TMPLOG}
+ echo "# DISK INFO $i " >>${TMPLOG}
+ ls /dev/${i}* >>${TMPLOG}
+ gpart show ${i} >> ${TMPLOG}
+done
+
+# Show Mounted volumes
+echo "" >>${TMPLOG}
+echo "# MOUNT OUTPUT " >>${TMPLOG}
+mount >> ${TMPLOG}
+
+echo "Log file saved to ${TMPLOG}"
+echo "Warning: This file will be lost once the system is rebooted."
+
+echo "Do you wish to view this logfile now? (Y/N)"
+read tmp
+if [ "$tmp" = "Y" -o "$tmp" = "y" ]
+then
+ more ${TMPLOG}
+fi
diff --git a/usr.sbin/pc-sysinstall/backend-query/set-mirror.sh b/usr.sbin/pc-sysinstall/backend-query/set-mirror.sh
new file mode 100755
index 0000000..23306bb
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/set-mirror.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXSystems, 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$
+
+. ${PROGDIR}/backend/functions.sh
+. ${PROGDIR}/backend/functions-ftp.sh
+
+MIRROR="${1}"
+
+if [ -z "${MIRROR}" ]
+then
+ echo "Error: No mirror specified!"
+ exit 1
+fi
+
+set_ftp_mirror "${MIRROR}"
+exit 0
diff --git a/usr.sbin/pc-sysinstall/backend-query/setup-ssh-keys.sh b/usr.sbin/pc-sysinstall/backend-query/setup-ssh-keys.sh
new file mode 100755
index 0000000..ac3d6ac
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/setup-ssh-keys.sh
@@ -0,0 +1,64 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Script which sets up password-less logins for ssh host
+###########################################################################
+
+. ${PROGDIR}/backend/functions.sh
+
+SSHUSER=$1
+SSHHOST=$2
+SSHPORT=$3
+
+if [ -z "${SSHUSER}" -o -z "${SSHHOST}" -o -z "${SSHPORT}" ]
+then
+ echo "ERROR: Usage setup-ssh-keys <user> <host> <port>"
+ exit 150
+fi
+
+cd ~
+
+echo "Preparing to setup SSH key authorization..."
+echo "When prompted, enter your password for ${SSHUSER}@${SSHHOST}"
+
+if [ ! -e ".ssh/id_rsa.pub" ]
+then
+ mkdir .ssh >/dev/null 2>/dev/null
+ ssh-keygen -q -t rsa -N '' -f .ssh/id_rsa
+ sleep 1
+fi
+
+if [ ! -e ".ssh/id_rsa.pub" ]
+then
+ echo "ERROR: Failed creating .ssh/id_rsa.pub"
+ exit 150
+fi
+
+# Get the .pub key
+PUBKEY="`cat .ssh/id_rsa.pub`"
+
+ssh -p ${SSHPORT} ${SSHUSER}@${SSHHOST} "mkdir .ssh ; echo $PUBKEY >> .ssh/authorized_keys; chmod 600 .ssh/authorized_keys ; echo $PUBKEY >> .ssh/authorized_keys2; chmod 600 .ssh/authorized_keys2"
diff --git a/usr.sbin/pc-sysinstall/backend-query/sys-mem.sh b/usr.sbin/pc-sysinstall/backend-query/sys-mem.sh
new file mode 100755
index 0000000..a726370
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/sys-mem.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+if smbios_mem=$(kenv -q smbios.memory.enabled); then
+ smbios_mem=$(expr $smbios_mem / 1024)
+else
+ smbios_mem=0
+fi
+realmem=$(expr $(sysctl -n hw.realmem) / 1048576)
+
+if [ $smbios_mem -gt $realmem ]; then
+ echo $smbios_mem
+else
+ echo $realmem
+fi
diff --git a/usr.sbin/pc-sysinstall/backend-query/test-live.sh b/usr.sbin/pc-sysinstall/backend-query/test-live.sh
new file mode 100755
index 0000000..86acc30
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/test-live.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Script which checks if we are running from install media, or real system
+#############################################################################
+
+dmesg | grep -q 'md0: Preloaded image' || { echo 'REAL-DISK'; exit 1; }
+
+echo 'INSTALL-MEDIA'
diff --git a/usr.sbin/pc-sysinstall/backend-query/test-netup.sh b/usr.sbin/pc-sysinstall/backend-query/test-netup.sh
new file mode 100755
index 0000000..e0a3eba
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/test-netup.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, Inc. All rights reserved.
+# Copyright (c) 2011 The FreeBSD Foundation
+# All rights reserved.
+#
+# Portions of this software were developed by Bjoern Zeeb
+# 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$
+
+
+# Script which tries to ping "home" to see if Internet connectivity is
+# available.
+#############################################################################
+
+rm ${TMPDIR}/.testftp >/dev/null 2>/dev/null
+
+ping -c 2 www.pcbsd.org >/dev/null 2>/dev/null
+if [ "$?" = "0" ]
+then
+ echo "ftp: Up"
+ exit 0
+fi
+
+ping6 -c 2 www.pcbsd.org >/dev/null 2>/dev/null
+if [ "$?" = "0" ]
+then
+ echo "ftp: Up"
+ exit 0
+fi
+
+ping -c 2 www.freebsd.org >/dev/null 2>/dev/null
+if [ "$?" = "0" ]
+then
+ echo "ftp: Up"
+ exit 0
+fi
+
+ping6 -c 2 www.freebsd.org >/dev/null 2>/dev/null
+if [ "$?" = "0" ]
+then
+ echo "ftp: Up"
+ exit 0
+fi
+
+echo "ftp: Down"
+exit 1
diff --git a/usr.sbin/pc-sysinstall/backend-query/update-part-list.sh b/usr.sbin/pc-sysinstall/backend-query/update-part-list.sh
new file mode 100755
index 0000000..298609b
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/update-part-list.sh
@@ -0,0 +1,99 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Need access to a some unmount functions
+. ${PROGDIR}/backend/functions-unmount.sh
+
+echo "Running: find-update-parts" >> ${LOGOUT}
+
+rm ${TMPDIR}/AvailUpgrades >/dev/null 2>/dev/null
+
+FSMNT="/mnt"
+
+# Get the freebsd version on this partition
+get_fbsd_ver()
+{
+ sFiles="/bin/sh /boot/kernel/kernel"
+ for file in $sFiles
+ do
+ if [ ! -e "${FSMNT}/$file" ] ; then continue ; fi
+
+ VER="`file ${FSMNT}/$file | grep 'for FreeBSD' | sed 's|for FreeBSD |;|g' | cut -d ';' -f 2 | cut -d ',' -f 1`"
+ if [ "$?" = "0" ] ; then
+ file ${FSMNT}/$file | grep '32-bit' >/dev/null 2>/dev/null
+ if [ "${?}" = "0" ] ; then
+ echo "${1}: FreeBSD ${VER} (32bit)"
+ else
+ echo "${1}: FreeBSD ${VER} (64bit)"
+ fi
+ fi
+ break
+ done
+
+}
+
+# Create our device listing
+SYSDISK="`sysctl kern.disks | cut -d ':' -f 2 | sed 's/^[ \t]*//'`"
+DEVS=""
+
+# Now loop through these devices, and list the disk drives
+for i in ${SYSDISK}
+do
+
+ # Get the current device
+ DEV="${i}"
+ # Make sure we don't find any cd devices
+ echo "${DEV}" | grep -e "^acd[0-9]" -e "^cd[0-9]" -e "^scd[0-9]" >/dev/null 2>/dev/null
+ if [ "$?" != "0" ] ; then
+ DEVS="${DEVS} `ls /dev/${i}*`"
+ fi
+
+done
+
+# Search for regular UFS / Geom Partitions to upgrade
+for i in $DEVS
+do
+ if [ ! -e "${i}a.journal" -a ! -e "${i}a" -a ! -e "${i}p2" -a ! -e "${i}p2.journal" ] ; then
+ continue
+ fi
+
+ if [ -e "${i}a.journal" ] ; then
+ _dsk="${i}a.journal"
+ elif [ -e "${i}a" ] ; then
+ _dsk="${i}a"
+ elif [ -e "${i}p2" ] ; then
+ _dsk="${i}p2"
+ elif [ -e "${i}p2.journal" ] ; then
+ _dsk="${i}p2.journal"
+ fi
+
+ mount -o ro ${_dsk} ${FSMNT} >>${LOGOUT} 2>>${LOGOUT}
+ if [ $? -eq 0 ] ; then
+ get_fbsd_ver "`echo ${_dsk} | sed 's|/dev/||g'`"
+ umount -f ${FSMNT} >/dev/null 2>/dev/null
+ fi
+done
diff --git a/usr.sbin/pc-sysinstall/backend-query/xkeyboard-layouts.sh b/usr.sbin/pc-sysinstall/backend-query/xkeyboard-layouts.sh
new file mode 100755
index 0000000..17821be
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/xkeyboard-layouts.sh
@@ -0,0 +1,67 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+FOUND="0"
+TMPLIST="/tmp/.xkeyList.$$"
+XLST="/usr/local/share/X11/xkb/rules/xorg.lst"
+
+if [ ! -e "${XLST}" ] ; then
+ exit 1
+fi
+
+# Lets parse the xorg.list file, and see what layouts are supported
+while read line
+do
+
+ if [ "$FOUND" = "1" -a ! -z "$line" ]
+ then
+ echo $line | grep '! ' >/dev/null 2>/dev/null
+ if [ "$?" = "0" ]
+ then
+ break
+ else
+ echo "$line" >> ${TMPLIST}
+ fi
+ fi
+
+ if [ "${FOUND}" = "0" ]
+ then
+ echo $line | grep '! layout' >/dev/null 2>/dev/null
+ if [ "$?" = "0" ]
+ then
+ FOUND="1"
+ fi
+ fi
+
+done < $XLST
+
+sort -b -d +1 $TMPLIST
+
+# Delete the tmp file
+rm $TMPLIST
+
+exit 0
diff --git a/usr.sbin/pc-sysinstall/backend-query/xkeyboard-models.sh b/usr.sbin/pc-sysinstall/backend-query/xkeyboard-models.sh
new file mode 100755
index 0000000..c89811e
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/xkeyboard-models.sh
@@ -0,0 +1,58 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+FOUND="0"
+
+# Lets parse the xorg.list file, and see what models are supported
+while read line
+do
+
+ if [ "$FOUND" = "1" -a ! -z "$line" ]
+ then
+ echo $line | grep '! ' >/dev/null 2>/dev/null
+ if [ "$?" = "0" ]
+ then
+ exit 0
+ else
+ model="`echo $line | sed 's|(|[|g'`"
+ model="`echo $model | sed 's|)|]|g'`"
+ echo "$model"
+ fi
+ fi
+
+ if [ "${FOUND}" = "0" ]
+ then
+ echo $line | grep '! model' >/dev/null 2>/dev/null
+ if [ "$?" = "0" ]
+ then
+ FOUND="1"
+ fi
+ fi
+
+done < /usr/local/share/X11/xkb/rules/xorg.lst
+
+exit 0
diff --git a/usr.sbin/pc-sysinstall/backend-query/xkeyboard-variants.sh b/usr.sbin/pc-sysinstall/backend-query/xkeyboard-variants.sh
new file mode 100755
index 0000000..c8c0aa5
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend-query/xkeyboard-variants.sh
@@ -0,0 +1,56 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+FOUND="0"
+
+# Lets parse the xorg.list file, and see what varients are supported
+while read line
+do
+
+ if [ "$FOUND" = "1" -a ! -z "$line" ]
+ then
+ echo $line | grep '! ' >/dev/null 2>/dev/null
+ if [ "$?" = "0" ]
+ then
+ exit 0
+ else
+ echo "$line"
+ fi
+ fi
+
+ if [ "${FOUND}" = "0" ]
+ then
+ echo $line | grep '! variant' >/dev/null 2>/dev/null
+ if [ "$?" = "0" ]
+ then
+ FOUND="1"
+ fi
+ fi
+
+done < /usr/local/share/X11/xkb/rules/xorg.lst
+
+exit 0
diff --git a/usr.sbin/pc-sysinstall/backend/Makefile b/usr.sbin/pc-sysinstall/backend/Makefile
new file mode 100644
index 0000000..3a7703a
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/Makefile
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+FILES= functions-bsdlabel.sh functions-cleanup.sh functions-disk.sh \
+ functions-extractimage.sh functions-ftp.sh functions-installcomponents.sh \
+ functions-installpackages.sh functions-localize.sh functions-mountdisk.sh \
+ functions-mountoptical.sh functions-networking.sh \
+ functions-newfs.sh functions-packages.sh functions-parse.sh \
+ functions-runcommands.sh functions-unmount.sh \
+ functions-upgrade.sh functions-users.sh \
+ functions.sh parseconfig.sh startautoinstall.sh installimage.sh
+FILESMODE= ${BINMODE}
+FILESDIR=${SHAREDIR}/pc-sysinstall/backend
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/pc-sysinstall/backend/Makefile.depend b/usr.sbin/pc-sysinstall/backend/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/pc-sysinstall/backend/functions-bsdlabel.sh b/usr.sbin/pc-sysinstall/backend/functions-bsdlabel.sh
new file mode 100755
index 0000000..37353d7
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/functions-bsdlabel.sh
@@ -0,0 +1,782 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Functions related to disk operations using bsdlabel
+
+# Check if we are are provided a geli password on the nextline of the config
+check_for_enc_pass()
+{
+ CURLINE="${1}"
+
+ get_next_cfg_line "${CFGF}" "${CURLINE}"
+ echo ${VAL} | grep -q "^encpass=" 2>/dev/null
+ if [ $? -eq 0 ] ; then
+ # Found a password, return it
+ get_value_from_string "${VAL}"
+ return
+ fi
+
+ export VAL=""
+ return
+};
+
+# On check on the disk-label line if we have any extra vars for this device
+get_fs_line_xvars()
+{
+ ACTIVEDEV="${1}"
+ LINE="${2}"
+
+ echo $LINE | cut -d ' ' -f 4 | grep -q '(' 2>/dev/null
+ if [ $? -ne 0 ] ; then return ; fi
+
+ # See if we are looking for ZFS specific options
+ echo $LINE | grep -q '^ZFS' 2>/dev/null
+ if [ $? -eq 0 ] ; then
+ ZTYPE="NONE"
+ ZFSVARS="`echo $LINE | cut -d ' ' -f 4-20 |cut -d '(' -f 2- | cut -d ')' -f 1 | xargs`"
+
+ echo $ZFSVARS | grep -qE "^(disk|file|mirror|raidz(1|2|3)?|spare|log|cache):" 2>/dev/null
+ if [ $? -eq 0 ] ; then
+ ZTYPE=`echo $ZFSVARS | cut -f1 -d:`
+ tmpVars=`echo $ZFSVARS | sed "s|$ZTYPE: ||g" | sed "s|$ZTYPE:||g"`
+ ZFSVARS=""
+ # make sure we have a '/dev' in front of the extra devices
+ for i in $tmpVars
+ do
+ echo $i | grep -q '/dev/'
+ if [ $? -ne 0 ] ; then
+ ZFSVARS="$ZFSVARS /dev/${i}"
+ else
+ ZFSVARS="$ZFSVARS $i"
+ fi
+ done
+ fi
+
+ # Return the ZFS options
+ if [ "${ZTYPE}" = "NONE" ] ; then
+ VAR="${ACTIVEDEV} ${ZFSVARS}"
+ else
+ VAR="${ZTYPE} ${ACTIVEDEV} ${ZFSVARS}"
+ fi
+ export VAR
+ return
+ fi # End of ZFS block
+
+ # See if we are looking for UFS specific newfs options
+ echo $LINE | grep -q '^UFS' 2>/dev/null
+ if [ $? -eq 0 ] ; then
+ FSVARS="`echo $LINE | cut -d '(' -f 2- | cut -d ')' -f 1 | xargs`"
+ VAR="${FSVARS}"
+ export VAR
+ return
+ fi
+
+ # If we got here, set VAR to empty and export
+ export VAR=""
+ return
+};
+
+# Init each zfs mirror disk with a boot sector so we can failover
+setup_zfs_mirror_parts()
+{
+ _nZFS=""
+
+ ZTYPE="`echo ${1} | awk '{print $1}'`"
+
+ # Using mirroring, setup boot partitions on each disk
+ _mirrline="`echo ${1} | sed 's|mirror ||g' | sed 's|raidz1 ||g' | sed 's|raidz2 ||g' | sed 's|raidz3 ||g' | sed 's|raidz ||g'`"
+ for _zvars in $_mirrline
+ do
+ echo "Looping through _zvars: $_zvars" >>${LOGOUT}
+ echo "$_zvars" | grep -q "${2}" 2>/dev/null
+ if [ $? -eq 0 ] ; then continue ; fi
+ if [ -z "$_zvars" ] ; then continue ; fi
+
+ is_disk "$_zvars" >/dev/null 2>/dev/null
+ if [ $? -eq 0 ] ; then
+ echo "Setting up ZFS disk $_zvars" >>${LOGOUT}
+ init_gpt_full_disk "$_zvars" >/dev/null 2>/dev/null
+ rc_halt "gpart add -a 4k -t freebsd-zfs ${_zvars}" >/dev/null 2>/dev/null
+ rc_halt "gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 ${_zvars}" >/dev/null 2>/dev/null
+ _nZFS="$_nZFS ${_zvars}p2"
+ else
+ _nZFS="$_nZFS ${_zvars}"
+ fi
+ done
+ echo "$ZTYPE $2 `echo $_nZFS | tr -s ' '`"
+} ;
+
+# Function which creates a unique label name for the specified mount
+gen_glabel_name()
+{
+ MOUNT="$1"
+ TYPE="$2"
+ NUM="0"
+ MAXNUM="20"
+
+ if [ "$TYPE" = "ZFS" ] ; then
+ NAME="zpool"
+ elif [ "$MOUNT" = "/" ] ; then
+ NAME="rootfs"
+ else
+ # If doing a swap partition, also rename it
+ if [ "${TYPE}" = "SWAP" ]
+ then
+ NAME="swap"
+ else
+ NAME="`echo $MOUNT | sed 's|/||g' | sed 's| ||g'`"
+ fi
+ fi
+
+ # Loop through and break when we find our first available label
+ while
+ Z=1
+ do
+ glabel status | grep -q "${NAME}${NUM}" 2>/dev/null
+ if [ $? -ne 0 ]
+ then
+ break
+ else
+ NUM=$((NUM+1))
+ fi
+
+ if [ $NUM -gt $MAXNUM ]
+ then
+ exit_err "Cannot allocate additional glabel name for $NAME"
+ break
+ fi
+ done
+
+
+ export VAL="${NAME}${NUM}"
+};
+
+# Function to determine the size we can safely use when 0 is specified
+get_autosize()
+{
+ # Disk tag to look for
+ dTag="$1"
+
+ # Total MB Avail
+ get_disk_mediasize_mb "$2"
+ local _aSize=$VAL
+
+ while read line
+ do
+ # Check for data on this slice
+ echo $line | grep -q "^${_dTag}-part=" 2>/dev/null
+ if [ $? -ne 0 ] ; then continue ; fi
+
+ get_value_from_string "${line}"
+ STRING="$VAL"
+
+ # Get the size of this partition
+ SIZE=`echo $STRING | tr -s '\t' ' ' | cut -d ' ' -f 2`
+ if [ $SIZE -eq 0 ] ; then continue ; fi
+ _aSize=`expr $_aSize - $SIZE`
+ done <${CFGF}
+
+ # Pad the size a bit
+ _aSize=`expr $_aSize - 2`
+
+ VAL="$_aSize"
+ export VAL
+};
+
+# Function to setup partitions using gpart
+setup_gpart_partitions()
+{
+ local _dTag="$1"
+ local _pDisk="$2"
+ local _wSlice="$3"
+ local _sNum="$4"
+ local _pType="$5"
+ FOUNDPARTS="1"
+ USEDAUTOSIZE=0
+
+ # Lets read in the config file now and setup our partitions
+ if [ "${_pType}" = "gpt" ] ; then
+ CURPART="2"
+ elif [ "${_pType}" = "apm" ] ; then
+ CURPART="3"
+ else
+ PARTLETTER="a"
+ CURPART="1"
+ if [ "${_pType}" = "mbr" ] ; then
+ rc_halt "gpart create -s BSD ${_wSlice}"
+ fi
+ fi
+
+ while read line
+ do
+ # Check for data on this slice
+ echo $line | grep -q "^${_dTag}-part=" 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ FOUNDPARTS="0"
+ # Found a slice- entry, lets get the slice info
+ get_value_from_string "${line}"
+ STRING="$VAL"
+
+ # We need to split up the string now, and pick out the variables
+ FS=`echo $STRING | tr -s '\t' ' ' | cut -d ' ' -f 1`
+ SIZE=`echo $STRING | tr -s '\t' ' ' | cut -d ' ' -f 2`
+ MNT=`echo $STRING | tr -s '\t' ' ' | cut -d ' ' -f 3`
+
+ # Check if we have a .eli extension on this FS
+ echo ${FS} | grep -q ".eli" 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ FS="`echo ${FS} | cut -d '.' -f 1`"
+ ENC="ON"
+ check_for_enc_pass "${line}"
+ if [ "${VAL}" != "" ] ; then
+ # We have a user supplied password, save it for later
+ ENCPASS="${VAL}"
+ fi
+ else
+ ENC="OFF"
+ fi
+
+ # Check if the user tried to setup / as an encrypted partition
+ check_for_mount "${MNT}" "/"
+ if [ $? -eq 0 -a "${ENC}" = "ON" ]
+ then
+ export USINGENCROOT="0"
+ fi
+
+ # Now check that these values are sane
+ case $FS in
+ UFS|UFS+S|UFS+J|UFS+SUJ|ZFS|SWAP) ;;
+ *) exit_err "ERROR: Invalid file system specified on $line" ;;
+ esac
+
+ # Check that we have a valid size number
+ expr $SIZE + 1 >/dev/null 2>/dev/null
+ if [ $? -ne 0 ]; then
+ exit_err "ERROR: The size specified on $line is invalid"
+ fi
+
+ # Check that the mount-point starts with /
+ echo "$MNT" | grep -qe "^/" -e "^none" 2>/dev/null
+ if [ $? -ne 0 ]; then
+ exit_err "ERROR: The mount-point specified on $line is invalid"
+ fi
+
+ if [ "$SIZE" = "0" ]
+ then
+ if [ $USEDAUTOSIZE -eq 1 ] ; then
+ exit_err "ERROR: You can not have two partitions with a size of 0 specified!"
+ fi
+ case ${_pType} in
+ gpt|apm) get_autosize "${_dTag}" "$_pDisk" ;;
+ *) get_autosize "${_dTag}" "$_wSlice" ;;
+ esac
+ SOUT="-s ${VAL}M"
+ USEDAUTOSIZE=1
+ else
+ SOUT="-s ${SIZE}M"
+ fi
+
+ # Check if we found a valid root partition
+ check_for_mount "${MNT}" "/"
+ if [ $? -eq 0 ] ; then
+ export FOUNDROOT="1"
+ if [ "${CURPART}" = "2" -a "$_pType" = "gpt" ] ; then
+ export FOUNDROOT="0"
+ fi
+ if [ "${CURPART}" = "3" -a "$_pType" = "apm" ] ; then
+ export FOUNDROOT="0"
+ fi
+ if [ "${CURPART}" = "1" -a "$_pType" = "mbr" ] ; then
+ export FOUNDROOT="0"
+ fi
+ if [ "${CURPART}" = "1" -a "$_pType" = "gptslice" ] ; then
+ export FOUNDROOT="0"
+ fi
+ fi
+
+ check_for_mount "${MNT}" "/boot"
+ if [ $? -eq 0 ] ; then
+ export USINGBOOTPART="0"
+ if [ "${CURPART}" != "2" -a "${_pType}" = "gpt" ] ; then
+ exit_err "/boot partition must be first partition"
+ fi
+ if [ "${CURPART}" != "3" -a "${_pType}" = "apm" ] ; then
+ exit_err "/boot partition must be first partition"
+ fi
+ if [ "${CURPART}" != "1" -a "${_pType}" = "mbr" ] ; then
+ exit_err "/boot partition must be first partition"
+ fi
+ if [ "${CURPART}" != "1" -a "${_pType}" = "gptslice" ] ; then
+ exit_err "/boot partition must be first partition"
+ fi
+
+ if [ "${FS}" != "UFS" -a "${FS}" != "UFS+S" -a "${FS}" != "UFS+J" -a "${FS}" != "UFS+SUJ" ] ; then
+ exit_err "/boot partition must be formatted with UFS"
+ fi
+ fi
+
+ # Generate a unique label name for this mount
+ gen_glabel_name "${MNT}" "${FS}"
+ PLABEL="${VAL}"
+
+ # Get any extra options for this fs / line
+ if [ "${_pType}" = "gpt" ] ; then
+ get_fs_line_xvars "${_pDisk}p${CURPART}" "${STRING}"
+ elif [ "${_pType}" = "apm" ] ; then
+ get_fs_line_xvars "${_pDisk}s${CURPART}" "${STRING}"
+ else
+ get_fs_line_xvars "${_wSlice}${PARTLETTER}" "${STRING}"
+ fi
+ XTRAOPTS="$VAR"
+
+ # Check if using zfs mirror
+ echo ${XTRAOPTS} | grep -q -e "mirror" -e "raidz"
+ if [ $? -eq 0 -a "$FS" = "ZFS" ] ; then
+ if [ "${_pType}" = "gpt" -o "${_pType}" = "gptslice" ] ; then
+ XTRAOPTS=$(setup_zfs_mirror_parts "$XTRAOPTS" "${_pDisk}p${CURPART}")
+ elif [ "${_pType}" = "apm" ] ; then
+ XTRAOPTS=$(setup_zfs_mirror_parts "$XTRAOPTS" "${_pDisk}s${CURPART}")
+ else
+ XTRAOPTS=$(setup_zfs_mirror_parts "$XTRAOPTS" "${_wSlice}${PARTLETTER}")
+ fi
+ fi
+
+ # Figure out the gpart type to use
+ case ${FS} in
+ ZFS) PARTYPE="freebsd-zfs" ;;
+ SWAP) PARTYPE="freebsd-swap" ;;
+ *) PARTYPE="freebsd-ufs" ;;
+ esac
+
+ # Create the partition
+ if [ "${_pType}" = "gpt" ] ; then
+ if [ "$CURPART" = "2" ] ; then
+ # If this is GPT, make sure first partition is aligned to 4k
+ sleep 2
+ rc_halt "gpart add -a 4k ${SOUT} -t ${PARTYPE} ${_pDisk}"
+ else
+ sleep 2
+ rc_halt "gpart add ${SOUT} -t ${PARTYPE} ${_pDisk}"
+ fi
+ elif [ "${_pType}" = "gptslice" ]; then
+ sleep 2
+ rc_halt "gpart add ${SOUT} -t ${PARTYPE} ${_wSlice}"
+ elif [ "${_pType}" = "apm" ]; then
+ sleep 2
+ rc_halt "gpart add ${SOUT} -t ${PARTYPE} ${_pDisk}"
+ else
+ sleep 2
+ rc_halt "gpart add ${SOUT} -t ${PARTYPE} -i ${CURPART} ${_wSlice}"
+ fi
+
+ # Check if this is a root / boot partition, and stamp the right loader
+ for TESTMNT in `echo ${MNT} | sed 's|,| |g'`
+ do
+ if [ "${TESTMNT}" = "/" -a -z "${BOOTTYPE}" ] ; then
+ BOOTTYPE="${PARTYPE}"
+ fi
+ if [ "${TESTMNT}" = "/boot" ] ; then
+ BOOTTYPE="${PARTYPE}"
+ fi
+ done
+
+ # Save this data to our partition config dir
+ if [ "${_pType}" = "gpt" ] ; then
+ _dFile="`echo $_pDisk | sed 's|/|-|g'`"
+ echo "${FS}#${MNT}#${ENC}#${PLABEL}#GPT#${XTRAOPTS}" >${PARTDIR}/${_dFile}p${CURPART}
+
+ # Clear out any headers
+ sleep 2
+ dd if=/dev/zero of=${_pDisk}p${CURPART} count=2048 2>/dev/null
+
+ # If we have a enc password, save it as well
+ if [ -n "${ENCPASS}" ] ; then
+ echo "${ENCPASS}" >${PARTDIR}-enc/${_dFile}p${CURPART}-encpass
+ fi
+ elif [ "${_pType}" = "apm" ] ; then
+ _dFile="`echo $_pDisk | sed 's|/|-|g'`"
+ echo "${FS}#${MNT}#${ENC}#${PLABEL}#GPT#${XTRAOPTS}" >${PARTDIR}/${_dFile}s${CURPART}
+
+ # Clear out any headers
+ sleep 2
+ dd if=/dev/zero of=${_pDisk}s${CURPART} count=2048 2>/dev/null
+
+ # If we have a enc password, save it as well
+ if [ -n "${ENCPASS}" ] ; then
+ echo "${ENCPASS}" >${PARTDIR}-enc/${_dFile}s${CURPART}-encpass
+ fi
+ else
+ # MBR Partition or GPT slice
+ _dFile="`echo $_wSlice | sed 's|/|-|g'`"
+ echo "${FS}#${MNT}#${ENC}#${PLABEL}#MBR#${XTRAOPTS}#${IMAGE}" >${PARTDIR}/${_dFile}${PARTLETTER}
+ # Clear out any headers
+ sleep 2
+ dd if=/dev/zero of=${_wSlice}${PARTLETTER} count=2048 2>/dev/null
+
+ # If we have a enc password, save it as well
+ if [ -n "${ENCPASS}" ] ; then
+ echo "${ENCPASS}" >${PARTDIR}-enc/${_dFile}${PARTLETTER}-encpass
+ fi
+ fi
+
+
+ # Increment our parts counter
+ if [ "$_pType" = "gpt" -o "$_pType" = "apm" ] ; then
+ CURPART=$((CURPART+1))
+ # If this is a gpt/apm partition,
+ # we can continue and skip the MBR part letter stuff
+ continue
+ else
+ CURPART=$((CURPART+1))
+ if [ "$CURPART" = "3" ] ; then CURPART="4" ; fi
+ fi
+
+
+ # This partition letter is used, get the next one
+ case ${PARTLETTER} in
+ a) PARTLETTER="b" ;;
+ b) PARTLETTER="d" ;;
+ d) PARTLETTER="e" ;;
+ e) PARTLETTER="f" ;;
+ f) PARTLETTER="g" ;;
+ g) PARTLETTER="h" ;;
+ h) PARTLETTER="ERR" ;;
+ *) exit_err "ERROR: bsdlabel only supports up to letter h for partitions." ;;
+ esac
+
+ fi # End of subsection locating a slice in config
+
+ echo $line | grep -q "^commitDiskLabel" 2>/dev/null
+ if [ $? -eq 0 -a "${FOUNDPARTS}" = "0" ]
+ then
+
+ # If this is the boot disk, stamp the right gptboot
+ if [ ! -z "${BOOTTYPE}" -a "$_pType" = "gpt" ] ; then
+ case ${BOOTTYPE} in
+ freebsd-ufs) rc_halt "gpart bootcode -p /boot/gptboot -i 1 ${_pDisk}" ;;
+ freebsd-zfs) rc_halt "gpart bootcode -p /boot/gptzfsboot -i 1 ${_pDisk}" ;;
+ esac
+ fi
+
+ # Make sure to stamp the MBR loader
+ if [ "$_pType" = "mbr" ] ; then
+ rc_halt "gpart bootcode -b /boot/boot ${_wSlice}"
+ fi
+
+ # Found our flag to commit this label setup, check that we found at least 1 partition
+ if [ "${CURPART}" = "1" ] ; then
+ exit_err "ERROR: commitDiskLabel was called without any partition entries for it!"
+ fi
+
+ break
+ fi
+ done <${CFGF}
+};
+
+# Reads through the config and sets up a BSDLabel for the given slice
+populate_disk_label()
+{
+ if [ -z "${1}" ]
+ then
+ exit_err "ERROR: populate_disk_label() called without argument!"
+ fi
+
+ # Set some vars from the given working slice
+ diskid="`echo $1 | cut -d ':' -f 1`"
+ disk="`echo $1 | cut -d ':' -f 1 | sed 's|-|/|g'`"
+ slicenum="`echo $1 | cut -d ':' -f 2`"
+ type="`echo $1 | cut -d ':' -f 3`"
+
+ # Set WRKSLICE based upon format we are using
+ if [ "$type" = "mbr" ] ; then
+ wrkslice="${diskid}s${slicenum}"
+ fi
+ if [ "$type" = "apm" ] ; then
+ wrkslice="${diskid}s${slicenum}"
+ fi
+ if [ "$type" = "gpt" -o "$type" = "gptslice" ] ; then
+ wrkslice="${diskid}p${slicenum}"
+ fi
+
+ if [ ! -e "${SLICECFGDIR}/${wrkslice}" ] ; then
+ exit_err "ERROR: Missing SLICETAG data. This shouldn't happen - please let the developers know"
+ fi
+
+ disktag="`cat ${SLICECFGDIR}/${wrkslice}`"
+ slicedev="`echo $wrkslice | sed 's|-|/|g'`"
+
+ # Setup the partitions with gpart
+ setup_gpart_partitions "${disktag}" "${disk}" "${slicedev}" "${slicenum}" "${type}"
+
+};
+
+# Function which reads in the disk slice config, and performs it
+setup_disk_label()
+{
+ # We are ready to start setting up the label, lets read the config and do the actions
+ # First confirm that we have a valid WORKINGSLICES
+ if [ -z "${WORKINGSLICES}" ]; then
+ exit_err "ERROR: No slices were setup! Please report this to the maintainers"
+ fi
+
+ # Check that the slices we have did indeed get setup and gpart worked
+ for i in $WORKINGSLICES
+ do
+ disk="`echo $i | cut -d '-' -f 1`"
+ pnum="`echo $i | cut -d '-' -f 2`"
+ type="`echo $i | cut -d '-' -f 3`"
+ if [ "$type" = "mbr" -a ! -e "${disk}s${pnum}" ] ; then
+ exit_err "ERROR: The partition ${i} doesn't exist! gpart failure!"
+ fi
+ if [ "$type" = "gpt" -a ! -e "${disk}p${pnum}" ] ; then
+ exit_err "ERROR: The partition ${i} doesn't exist! gpart failure!"
+ fi
+ if [ "$type" = "apm" -a ! -e "${disk}s${pnum}" ] ; then
+ exit_err "ERROR: The partition ${i} doesn't exist! gpart failure!"
+ fi
+ if [ "$type" = "gptslice" -a ! -e "${disk}p${pnum}" ] ; then
+ exit_err "ERROR: The partition ${i} doesn't exist! gpart failure!"
+ fi
+ done
+
+ # Setup some files which we'll be referring to
+ export LABELLIST="${TMPDIR}/workingLabels"
+ rm $LABELLIST >/dev/null 2>/dev/null
+
+ # Set our flag to determine if we've got a valid root partition in this setup
+ export FOUNDROOT="-1"
+
+ # Check if we are using a /boot partition
+ export USINGBOOTPART="1"
+
+ # Set encryption on root check
+ export USINGENCROOT="1"
+
+ # Make the tmp directory where we'll store FS info & mount-points
+ rm -rf ${PARTDIR} >/dev/null 2>/dev/null
+ mkdir -p ${PARTDIR} >/dev/null 2>/dev/null
+ rm -rf ${PARTDIR}-enc >/dev/null 2>/dev/null
+ mkdir -p ${PARTDIR}-enc >/dev/null 2>/dev/null
+
+ for i in $WORKINGSLICES
+ do
+ populate_disk_label "${i}"
+ done
+
+ # Check if we made a root partition
+ if [ "$FOUNDROOT" = "-1" ]
+ then
+ exit_err "ERROR: No root (/) partition specified!!"
+ fi
+
+ # Check if we made a root partition
+ if [ "$FOUNDROOT" = "1" -a "${USINGBOOTPART}" != "0" ]
+ then
+ exit_err "ERROR: (/) partition isn't first partition on disk!"
+ fi
+
+ if [ "${USINGENCROOT}" = "0" -a "${USINGBOOTPART}" != "0" ]
+ then
+ exit_err "ERROR: Can't encrypt (/) with no (/boot) partition!"
+ fi
+};
+
+check_fstab_mbr()
+{
+ local SLICE
+ local FSTAB
+
+ if [ -z "$2" ]
+ then
+ return 1
+ fi
+
+ SLICE="$1"
+ FSTAB="$2/etc/fstab"
+
+ if [ -f "${FSTAB}" ]
+ then
+ PARTLETTER=`echo "$SLICE" | sed -E 's|^.+([a-h])$|\1|'`
+
+ cat "${FSTAB}" | awk '{ print $2 }' | grep -qE '^/$' 2>&1
+ if [ $? -eq 0 ]
+ then
+ if [ "${PARTLETTER}" = "a" ]
+ then
+ FOUNDROOT="0"
+ else
+ FOUNDROOT="1"
+ fi
+
+ ROOTIMAGE="1"
+
+ export FOUNDROOT
+ export ROOTIMAGE
+ fi
+
+ cat "${FSTAB}" | awk '{ print $2 }' | grep -qE '^/boot$' 2>&1
+ if [ $? -eq 0 ]
+ then
+ if [ "${PARTLETTER}" = "a" ]
+ then
+ USINGBOOTPART="0"
+ else
+ exit_err "/boot partition must be first partition"
+ fi
+ export USINGBOOTPART
+ fi
+
+ return 0
+ fi
+
+ return 1
+};
+
+check_fstab_gpt()
+{
+ local SLICE
+ local FSTAB
+
+ if [ -z "$2" ]
+ then
+ return 1
+ fi
+
+ SLICE="$1"
+ FSTAB="$2/etc/fstab"
+
+ if [ -f "${FSTAB}" ]
+ then
+ PARTNUMBER=`echo "${SLICE}" | sed -E 's|^.+p([0-9]*)$|\1|'`
+
+ cat "${FSTAB}" | awk '{ print $2 }' | grep -qE '^/$' 2>&1
+ if [ $? -eq 0 ]
+ then
+ if [ "${PARTNUMBER}" = "2" ]
+ then
+ FOUNDROOT="0"
+ else
+ FOUNDROOT="1"
+ fi
+
+ ROOTIMAGE="1"
+
+ export FOUNDROOT
+ export ROOTIMAGE
+ fi
+
+ cat "${FSTAB}" | awk '{ print $2 }' | grep -qE '^/boot$' 2>&1
+ if [ $? -eq 0 ]
+ then
+ if [ "${PARTNUMBER}" = "2" ]
+ then
+ USINGBOOTPART="0"
+ else
+ exit_err "/boot partition must be first partition"
+ fi
+ export USINGBOOTPART
+ fi
+
+ return 0
+ fi
+
+
+ return 1
+};
+
+check_disk_layout()
+{
+ local SLICES
+ local TYPE
+ local DISK
+ local RES
+ local F
+
+ DISK="$1"
+ TYPE="MBR"
+
+ if [ -z "${DISK}" ]
+ then
+ return 1
+ fi
+
+ SLICES_MBR=`ls /dev/${DISK}s[1-4]*[a-h]* 2>/dev/null`
+ SLICES_GPT=`ls /dev/${DISK}p[0-9]* 2>/dev/null`
+ SLICES_SLICE=`ls /dev/${DISK}[a-h]* 2>/dev/null`
+
+ if [ -n "${SLICES_MBR}" ]
+ then
+ SLICES="${SLICES_MBR}"
+ TYPE="MBR"
+ RES=0
+ fi
+ if [ -n "${SLICES_GPT}" ]
+ then
+ SLICES="${SLICES_GPT}"
+ TYPE="GPT"
+ RES=0
+ fi
+ if [ -n "${SLICES_SLICE}" ]
+ then
+ SLICES="${SLICES_SLICE}"
+ TYPE="MBR"
+ RES=0
+ fi
+
+ for slice in ${SLICES}
+ do
+ F=1
+ mount ${slice} /mnt 2>/dev/null
+ if [ $? -ne 0 ]
+ then
+ continue
+ fi
+
+ if [ "${TYPE}" = "MBR" ]
+ then
+ check_fstab_mbr "${slice}" "/mnt"
+ F="$?"
+
+ elif [ "${TYPE}" = "GPT" ]
+ then
+ check_fstab_gpt "${slice}" "/mnt"
+ F="$?"
+ fi
+
+ if [ ${F} -eq 0 ]
+ then
+ umount /mnt
+ break
+ fi
+
+ umount /mnt
+ done
+
+ return ${RES}
+};
diff --git a/usr.sbin/pc-sysinstall/backend/functions-cleanup.sh b/usr.sbin/pc-sysinstall/backend/functions-cleanup.sh
new file mode 100755
index 0000000..787f891
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/functions-cleanup.sh
@@ -0,0 +1,411 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Functions which perform the final cleanup after an install
+
+# Finishes up with ZFS setup before unmounting
+zfs_cleanup_unmount()
+{
+ # Loop through our FS and see if we have any ZFS partitions to cleanup
+ for PART in `ls ${PARTDIR}`
+ do
+ PARTDEV=`echo $PART | sed 's|-|/|g'`
+ PARTFS="`cat ${PARTDIR}/${PART} | cut -d '#' -f 1`"
+ PARTMNT="`cat ${PARTDIR}/${PART} | cut -d '#' -f 2`"
+ ZPOOLNAME=$(get_zpool_name "${PARTDEV}")
+
+ if [ "$PARTFS" = "ZFS" ]
+ then
+ # Check if we have multiple zfs mounts specified
+ for ZMNT in `echo ${PARTMNT} | sed 's|,| |g'`
+ do
+ if [ "${ZMNT}" = "/" ]
+ then
+ # Make sure we haven't already added the zfs boot line when
+ # Creating a dedicated "/boot" partition
+ cat ${FSMNT}/boot/loader.conf 2>/dev/null | grep -q "vfs.root.mountfrom=" 2>/dev/null
+ if [ $? -ne 0 ] ; then
+ echo "vfs.root.mountfrom=\"zfs:${ZPOOLNAME}/ROOT/default\"" >> ${FSMNT}/boot/loader.conf
+ fi
+ export FOUNDZFSROOT="${ZPOOLNAME}"
+ fi
+ done
+ FOUNDZFS="1"
+ fi
+ done
+
+ if [ -n "${FOUNDZFS}" ]
+ then
+ # Check if we need to add our ZFS flags to rc.conf, src.conf and loader.conf
+ cat ${FSMNT}/boot/loader.conf 2>/dev/null | grep -q 'zfs_load="YES"' 2>/dev/null
+ if [ $? -ne 0 ]
+ then
+ echo 'zfs_load="YES"' >>${FSMNT}/boot/loader.conf
+ fi
+ cat ${FSMNT}/etc/rc.conf 2>/dev/null | grep -q 'zfs_enable="YES"' 2>/dev/null
+ if [ $? -ne 0 ]
+ then
+ echo 'zfs_enable="YES"' >>${FSMNT}/etc/rc.conf
+ fi
+
+ sleep 2
+ # Copy over any ZFS cache data
+ cp /boot/zfs/* ${FSMNT}/boot/zfs/
+
+ # Copy the hostid so that our zfs cache works
+ cp /etc/hostid ${FSMNT}/etc/hostid
+ fi
+
+ # Loop through our FS and see if we have any ZFS partitions to cleanup
+ for PART in `ls ${PARTDIR}`
+ do
+ PARTDEV=`echo $PART | sed 's|-|/|g'`
+ PARTFS="`cat ${PARTDIR}/${PART} | cut -d '#' -f 1`"
+ PARTMNT="`cat ${PARTDIR}/${PART} | cut -d '#' -f 2`"
+ PARTENC="`cat ${PARTDIR}/${PART} | cut -d '#' -f 3`"
+ ZPOOLNAME=$(get_zpool_name "${PARTDEV}")
+
+ if [ "$PARTFS" = "ZFS" ]
+ then
+
+ # Create a list of zpool names we can export
+ echo $ZPOOLEXPORTS | grep -q "$ZPOOLNAME "
+ if [ $? -ne 0 ] ; then
+ export ZPOOLEXPORTS="$ZPOOLNAME $ZPOOLEXPORTS"
+ fi
+
+ # Check if we have multiple zfs mounts specified
+ for ZMNT in `echo ${PARTMNT} | sed 's|,| |g'`
+ do
+ ZMNT="`echo $ZMNT | cut -d '(' -f 1`"
+ PARTMNTREV="${ZMNT} ${PARTMNTREV}"
+ done
+
+ for ZMNT in ${PARTMNTREV}
+ do
+ if [ "${ZMNT}" = "/" ] ; then continue ; fi
+ # Some ZFS like /swap aren't mounted, and dont need unmounting
+ mount | grep -q "${FSMNT}${ZMNT}"
+ if [ $? -eq 0 ] ; then
+ rc_halt "zfs unmount ${ZPOOLNAME}${ZMNT}"
+ rc_halt "zfs set mountpoint=${ZMNT} ${ZPOOLNAME}${ZMNT}"
+ fi
+ sleep 2
+ done
+ fi
+ done
+
+};
+
+# Function which performs the specific setup for using a /boot partition
+setup_dedicated_boot_part()
+{
+ ROOTFS="${1}"
+ ROOTFSTYPE="${2}"
+ BOOTFS="${3}"
+ BOOTMNT="${4}"
+
+ # Set the root mount in loader.conf
+ echo "vfs.root.mountfrom=\"${ROOTFSTYPE}:${ROOTFS}\"" >> ${FSMNT}/boot/loader.conf
+ rc_halt "mkdir -p ${FSMNT}/${BOOTMNT}/boot"
+ rc_halt "mv ${FSMNT}/boot/* ${FSMNT}${BOOTMNT}/boot/"
+ rc_halt "mv ${FSMNT}${BOOTMNT}/boot ${FSMNT}/boot/"
+ rc_halt "umount ${BOOTFS}"
+ rc_halt "mount ${BOOTFS} ${FSMNT}${BOOTMNT}"
+ rc_halt "rmdir ${FSMNT}/boot"
+
+ # Strip the '/' from BOOTMNT before making symlink
+ BOOTMNTNS="`echo ${BOOTMNT} | sed 's|/||g'`"
+ rc_halt "chroot ${FSMNT} ln -s ${BOOTMNTNS}/boot /boot"
+
+};
+
+# Function which creates the /etc/fstab for the installed system
+setup_fstab()
+{
+ FSTAB="${FSMNT}/etc/fstab"
+ rm ${FSTAB} >/dev/null 2>/dev/null
+
+ # Create the header
+ echo "# Device Mountpoint FStype Options Dump Pass" >> ${FSTAB}
+
+ # Loop through the partitions, and start creating /etc/fstab
+ for PART in `ls ${PARTDIR}`
+ do
+ PARTDEV=`echo $PART | sed 's|-|/|g'`
+ PARTFS="`cat ${PARTDIR}/${PART} | cut -d '#' -f 1`"
+ PARTMNT="`cat ${PARTDIR}/${PART} | cut -d '#' -f 2`"
+ PARTENC="`cat ${PARTDIR}/${PART} | cut -d '#' -f 3`"
+ PARTLABEL="`cat ${PARTDIR}/${PART} | cut -d '#' -f 4`"
+
+ # Unset EXT
+ EXT=""
+
+ # Set mount options for file-systems
+ case $PARTFS in
+ UFS+J) MNTOPTS="rw,noatime,async" ;;
+ SWAP) MNTOPTS="sw" ;;
+ *) MNTOPTS="rw,noatime" ;;
+ esac
+
+
+ # Figure out if we are using a glabel, or the raw name for this entry
+ if [ -n "${PARTLABEL}" ]
+ then
+ DEVICE="label/${PARTLABEL}"
+ else
+ # Check if using encryption
+ if [ "${PARTENC}" = "ON" ] ; then
+ EXT=".eli"
+ fi
+
+ if [ "${PARTFS}" = "UFS+J" ] ; then
+ EXT="${EXT}.journal"
+ fi
+ DEVICE="${PARTDEV}${EXT}"
+ fi
+
+
+ # Set our ROOTFSTYPE for loader.conf if necessary
+ check_for_mount "${PARTMNT}" "/"
+ if [ $? -eq 0 ] ; then
+ if [ "${PARTFS}" = "ZFS" ] ; then
+ ROOTFSTYPE="zfs"
+ ZPOOLNAME=$(get_zpool_name "${PARTDEV}")
+ ROOTFS="${ZPOOLNAME}/ROOT/default"
+ else
+ ROOTFS="${DEVICE}"
+ ROOTFSTYPE="ufs"
+ fi
+ fi
+
+ # Only create non-zfs partitions
+ if [ "${PARTFS}" != "ZFS" ]
+ then
+
+ # Make sure geom_journal is loaded
+ if [ "${PARTFS}" = "UFS+J" ] ; then
+ setup_gjournal
+ fi
+
+ # Save the BOOTFS for call at the end
+ if [ "${PARTMNT}" = "/boot" ] ; then
+ BOOTFS="${PARTDEV}${EXT}"
+ BOOTMNT="${BOOT_PART_MOUNT}"
+ PARTMNT="${BOOTMNT}"
+ fi
+
+ # Echo out the fstab entry now
+ if [ "${PARTFS}" = "SWAP" ]
+ then
+ echo "/dev/${DEVICE} none swap ${MNTOPTS} 0 0" >> ${FSTAB}
+ else
+ echo "/dev/${DEVICE} ${PARTMNT} ufs ${MNTOPTS} 1 1" >> ${FSTAB}
+ fi
+
+ fi # End of ZFS Check
+ done
+
+ # Setup some specific PC-BSD fstab options
+ if [ "$INSTALLTYPE" != "FreeBSD" ]
+ then
+ echo "procfs /proc procfs rw 0 0" >> ${FSTAB}
+ echo "linprocfs /compat/linux/proc linprocfs rw 0 0" >> ${FSTAB}
+ fi
+
+ # If we have a dedicated /boot, run the post-install setup of it now
+ if [ ! -z "${BOOTMNT}" ] ; then
+ setup_dedicated_boot_part "${ROOTFS}" "${ROOTFSTYPE}" "${BOOTFS}" "${BOOTMNT}"
+ fi
+
+};
+
+# Setup our disk mirroring with gmirror
+setup_gmirror()
+{
+ cat ${FSMNT}/boot/loader.conf 2>/dev/null | grep -q 'geom_mirror_load="YES"' 2>/dev/null
+ if [ $? -ne 0 ]
+ then
+ echo 'geom_mirror_load="YES"' >>${FSMNT}/boot/loader.conf
+ fi
+
+};
+
+# Function which saves geli keys and sets up loading of them at boot
+setup_geli_loading()
+{
+
+ # Make our keys dir
+ mkdir -p ${FSMNT}/boot/keys >/dev/null 2>/dev/null
+
+ cd ${GELIKEYDIR}
+ for KEYFILE in `ls`
+ do
+ # Figure out the partition name based on keyfile name removing .key
+ PART="`echo ${KEYFILE} | cut -d '.' -f 1`"
+ PARTDEV="`echo ${PART} | sed 's|-|/|g'`"
+ PARTNAME="`echo ${PART} | sed 's|-dev-||g'`"
+
+ rc_halt "geli configure -b ${PARTDEV}"
+
+ # If no passphrase, setup key files
+ if [ ! -e "${PARTDIR}-enc/${PART}-encpass" ] ; then
+ echo "geli_${PARTNAME}_keyfile0_load=\"YES\"" >> ${FSMNT}/boot/loader.conf
+ echo "geli_${PARTNAME}_keyfile0_type=\"${PARTNAME}:geli_keyfile0\"" >> ${FSMNT}/boot/loader.conf
+ echo "geli_${PARTNAME}_keyfile0_name=\"/boot/keys/${PARTNAME}.key\"" >> ${FSMNT}/boot/loader.conf
+
+ # Copy the key to the disk
+ rc_halt "cp ${GELIKEYDIR}/${KEYFILE} ${FSMNT}/boot/keys/${PARTNAME}.key"
+ fi
+
+ done
+
+ # Make sure we have geom_eli set to load at boot
+ cat ${FSMNT}/boot/loader.conf 2>/dev/null | grep -q 'geom_eli_load="YES"' 2>/dev/null
+ if [ $? -ne 0 ]
+ then
+ echo 'geom_eli_load="YES"' >>${FSMNT}/boot/loader.conf
+ fi
+
+};
+
+
+# Function to generate a random hostname if none was specified
+gen_hostname()
+{
+ RAND="`jot -r 1 1 9000`"
+
+ if [ "$INSTALLTYPE" = "FreeBSD" ]
+ then
+ VAL="freebsd-${RAND}"
+ else
+ VAL="pcbsd-${RAND}"
+ fi
+
+ export VAL
+
+};
+
+# Function which sets up the hostname for the system
+setup_hostname()
+{
+
+ get_value_from_cfg hostname
+ HOSTNAME="${VAL}"
+
+ # If we don't have a hostname, make one up
+ if [ -z "${HOSTNAME}" ]
+ then
+ gen_hostname
+ HOSTNAME="${VAL}"
+ fi
+
+ # Clean up any saved hostname
+ cat ${FSMNT}/etc/rc.conf | grep -v "hostname=" >${FSMNT}/etc/rc.conf.new
+ mv ${FSMNT}/etc/rc.conf.new ${FSMNT}/etc/rc.conf
+
+ # Set the hostname now
+ echo_log "Setting hostname: ${HOSTNAME}"
+ echo "hostname=\"${HOSTNAME}\"" >> ${FSMNT}/etc/rc.conf
+ sed -i -e "s|my.domain|${HOSTNAME} ${HOSTNAME}|g" ${FSMNT}/etc/hosts
+
+};
+
+
+# Check and make sure geom_journal is enabled on the system
+setup_gjournal()
+{
+
+ # Make sure we have geom_journal set to load at boot
+ cat ${FSMNT}/boot/loader.conf 2>/dev/null | grep -q 'geom_journal_load="YES"' 2>/dev/null
+ if [ $? -ne 0 ]
+ then
+ echo 'geom_journal_load="YES"' >>${FSMNT}/boot/loader.conf
+ fi
+
+};
+
+# Function which sets the root password from the install config
+set_root_pw()
+{
+ # Get the plaintext string
+ get_value_from_cfg_with_spaces rootPass
+ local PW="${VAL}"
+
+ # Get the encrypted string
+ get_value_from_cfg_with_spaces rootEncPass
+ local ENCPW="${VAL}"
+
+ # If we don't have a root pass, return
+ if [ -z "${PW}" -a -z "${ENCPW}" ] ; then return 0 ; fi
+
+ echo_log "Setting root password"
+
+ # Check if setting plaintext password
+ if [ -n "${PW}" ] ; then
+ echo "${PW}" > ${FSMNT}/.rootpw
+ run_chroot_cmd "cat /.rootpw | pw usermod root -h 0"
+ rc_halt "rm ${FSMNT}/.rootpw"
+ fi
+
+ # Check if setting encrypted password
+ if [ -n "${ENCPW}" ] ; then
+ echo "${ENCPW}" > ${FSMNT}/.rootpw
+ run_chroot_cmd "cat /.rootpw | pw usermod root -H 0"
+ rc_halt "rm ${FSMNT}/.rootpw"
+ fi
+
+};
+
+
+run_final_cleanup()
+{
+ # Check if we need to run any gmirror setup
+ ls ${MIRRORCFGDIR}/* >/dev/null 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ # Lets setup gmirror now
+ setup_gmirror
+ fi
+
+ # Check if we need to save any geli keys
+ ls ${GELIKEYDIR}/* >/dev/null 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ # Lets setup geli loading
+ setup_geli_loading
+ fi
+
+ # Set a hostname on the install system
+ setup_hostname
+
+ # Set the root_pw if it is specified
+ set_root_pw
+
+ # Generate the fstab for the installed system
+ setup_fstab
+};
diff --git a/usr.sbin/pc-sysinstall/backend/functions-disk.sh b/usr.sbin/pc-sysinstall/backend/functions-disk.sh
new file mode 100755
index 0000000..eac10d9
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/functions-disk.sh
@@ -0,0 +1,908 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Functions related to disk operations using gpart
+
+# See if device is a full disk or partition/slice
+is_disk()
+{
+ for _dsk in `sysctl -n kern.disks`
+ do
+ [ "$_dsk" = "${1}" ] && return 0
+ [ "/dev/$_dsk" = "${1}" ] && return 0
+ done
+
+ return 1
+}
+
+# Get a MBR partitions sysid
+get_partition_sysid_mbr()
+{
+ INPART="0"
+ DISK="$1"
+ PARTNUM=`echo ${2} | sed "s|${DISK}s||g"`
+ fdisk ${DISK} >${TMPDIR}/disk-${DISK} 2>/dev/null
+ while read i
+ do
+ echo "$i" | grep -q "The data for partition" 2>/dev/null
+ if [ $? -eq 0 ] ; then
+ INPART="0"
+ PART="`echo ${i} | cut -d ' ' -f 5`"
+ if [ "$PART" = "$PARTNUM" ] ; then
+ INPART="1"
+ fi
+ fi
+
+ # In the partition section
+ if [ "$INPART" = "1" ] ; then
+ echo "$i" | grep -q "^sysid" 2>/dev/null
+ if [ $? -eq 0 ] ; then
+ SYSID="`echo ${i} | tr -s '\t' ' ' | cut -d ' ' -f 2`"
+ break
+ fi
+
+ fi
+
+ done < ${TMPDIR}/disk-${DISK}
+ rm ${TMPDIR}/disk-${DISK}
+
+ export VAL="${SYSID}"
+};
+
+# Get the partitions MBR label
+get_partition_label_mbr()
+{
+ INPART="0"
+ DISK="$1"
+ PARTNUM=`echo ${2} | sed "s|${DISK}s||g"`
+ fdisk ${DISK} >${TMPDIR}/disk-${DISK} 2>/dev/null
+ while read i
+ do
+ echo "$i" | grep -q "The data for partition" 2>/dev/null
+ if [ $? -eq 0 ] ; then
+ INPART="0"
+ PART="`echo ${i} | cut -d ' ' -f 5`"
+ if [ "$PART" = "$PARTNUM" ] ; then
+ INPART="1"
+ fi
+ fi
+
+ # In the partition section
+ if [ "$INPART" = "1" ] ; then
+ echo "$i" | grep -q "^sysid" 2>/dev/null
+ if [ $? -eq 0 ] ; then
+ LABEL="`echo ${i} | tr -s '\t' ' ' | cut -d ',' -f 2-10`"
+ break
+ fi
+
+ fi
+
+ done < ${TMPDIR}/disk-${DISK}
+ rm ${TMPDIR}/disk-${DISK}
+
+ export VAL="${LABEL}"
+};
+
+# Get a GPT partitions label
+get_partition_label_gpt()
+{
+ DISK="${1}"
+ PARTNUM=`echo ${2} | sed "s|${DISK}p||g"`
+
+ gpart show ${DISK} >${TMPDIR}/disk-${DISK}
+ while read i
+ do
+ SLICE="`echo ${i} | grep -v ${DISK} | grep -v ' free ' |tr -s '\t' ' ' | cut -d ' ' -f 3`"
+ if [ "${SLICE}" = "${PARTNUM}" ] ; then
+ LABEL="`echo ${i} | grep -v ${DISK} | grep -v ' free ' |tr -s '\t' ' ' | cut -d ' ' -f 4`"
+ break
+ fi
+ done <${TMPDIR}/disk-${DISK}
+ rm ${TMPDIR}/disk-${DISK}
+
+ export VAL="${LABEL}"
+};
+
+# Get a partitions startblock
+get_partition_startblock()
+{
+ DISK="${1}"
+ PARTNUM=`echo ${2} | sed "s|${DISK}p||g" | sed "s|${DISK}s||g"`
+
+ gpart show ${DISK} >${TMPDIR}/disk-${DISK}
+ while read i
+ do
+ SLICE="`echo ${i} | grep -v ${DISK} | grep -v ' free ' |tr -s '\t' ' ' | cut -d ' ' -f 3`"
+ if [ "$SLICE" = "${PARTNUM}" ] ; then
+ SB="`echo ${i} | grep -v ${DISK} | grep -v ' free ' |tr -s '\t' ' ' | cut -d ' ' -f 1`"
+ break
+ fi
+ done <${TMPDIR}/disk-${DISK}
+ rm ${TMPDIR}/disk-${DISK}
+
+ export VAL="${SB}"
+};
+
+# Get a partitions blocksize
+get_partition_blocksize()
+{
+ DISK="${1}"
+ PARTNUM=`echo ${2} | sed "s|${DISK}p||g" | sed "s|${DISK}s||g"`
+
+ gpart show ${DISK} >${TMPDIR}/disk-${DISK}
+ while read i
+ do
+ SLICE="`echo ${i} | grep -v ${DISK} | grep -v ' free ' |tr -s '\t' ' ' | cut -d ' ' -f 3`"
+ if [ "$SLICE" = "${PARTNUM}" ] ; then
+ BS="`echo ${i} | grep -v ${DISK} | grep -v ' free ' |tr -s '\t' ' ' | cut -d ' ' -f 2`"
+ break
+ fi
+ done <${TMPDIR}/disk-${DISK}
+ rm ${TMPDIR}/disk-${DISK}
+
+ export VAL="${BS}"
+};
+
+# Function which returns the partitions on a target disk
+get_disk_partitions()
+{
+ gpart show ${1} >/dev/null 2>/dev/null
+ if [ $? -ne 0 ] ; then
+ export VAL=""
+ return
+ fi
+
+ type=`gpart show ${1} | awk '/^=>/ { printf("%s",$5); }'`
+
+ SLICES="`gpart show ${1} | grep -v ${1} | grep -v ' free ' |tr -s '\t' ' ' | cut -d ' ' -f 4 | sed '/^$/d'`"
+ for i in ${SLICES}
+ do
+ case $type in
+ MBR) name="${1}s${i}" ;;
+ GPT) name="${1}p${i}";;
+ *) name="${1}s${i}";;
+ esac
+ if [ -z "${RSLICES}" ]
+ then
+ RSLICES="${name}"
+ else
+ RSLICES="${RSLICES} ${name}"
+ fi
+ done
+
+ export VAL="${RSLICES}"
+};
+
+# Function which returns a target disks cylinders
+get_disk_cyl()
+{
+ cyl=`diskinfo -v ${1} | grep "# Cylinders" | tr -s ' ' | cut -f 2`
+ export VAL="${cyl}"
+};
+
+# Function which returns a target disks sectors
+get_disk_sectors()
+{
+ sec=`diskinfo -v ${1} | grep "# Sectors" | tr -s ' ' | cut -f 2`
+ export VAL="${sec}"
+};
+
+# Function which returns a target disks heads
+get_disk_heads()
+{
+ head=`diskinfo -v ${1} | grep "# Heads" | tr -s ' ' | cut -f 2`
+ export VAL="${head}"
+};
+
+# Function which returns a target disks mediasize in sectors
+get_disk_mediasize()
+{
+ mediasize=`diskinfo -v ${1} | grep "# mediasize in sectors" | tr -s ' ' | cut -f 2`
+ export VAL="${mediasize}"
+};
+
+# Function which returns a target disks mediasize in megabytes
+get_disk_mediasize_mb()
+{
+ mediasize=`diskinfo -v ${1} | grep "# mediasize in bytes" | tr -s ' ' | cut -f 2`
+ mediasize=`expr $mediasize / 1024`
+ mediasize=`expr $mediasize / 1024`
+ export VAL="${mediasize}"
+};
+
+# Function to delete all gparts before starting an install
+delete_all_gpart()
+{
+ echo_log "Deleting all gparts"
+ local DISK="$1"
+
+ # Check for any swaps to stop
+ for i in `swapctl -l | grep "$DISK" | awk '{print $1}'`
+ do
+ swapoff ${i} >/dev/null 2>/dev/null
+ done
+
+ # Delete the gparts now
+ for i in `gpart show ${DISK} 2>/dev/null | tr -s ' ' | cut -d ' ' -f 4`
+ do
+ if [ "/dev/${i}" != "${DISK}" -a "${i}" != "-" ] ; then
+ rc_nohalt "gpart delete -i ${i} ${DISK}"
+ fi
+ done
+
+ # Destroy the disk geom
+ rc_nohalt "gpart destroy ${DISK}"
+
+ # Make sure we clear any hidden gpt tables
+ clear_backup_gpt_table "${DISK}"
+
+ # Wipe out front of disk
+ rc_nohalt "dd if=/dev/zero of=${DISK} count=3000"
+
+};
+
+# Function to export all zpools before starting an install
+stop_all_zfs()
+{
+ local DISK="`echo ${1} | sed 's|/dev/||g'`"
+
+ # Export any zpools using this device so we can overwrite
+ for i in `zpool list -H -o name`
+ do
+ ztst=`zpool status ${i} | grep "ONLINE" | awk '{print $1}' | grep -q ${DISK}`
+ if [ "$ztst" = "$DISK" ] ; then
+ zpool export -f ${i}
+ fi
+ done
+};
+
+# Function which stops all gmirrors before doing any disk manipulation
+stop_all_gmirror()
+{
+ local DISK="`echo ${1} | sed 's|/dev/||g'`"
+ GPROV="`gmirror list | grep ". Name: mirror/" | cut -d '/' -f 2`"
+ for gprov in $GPROV
+ do
+ gmirror list | grep -q "Name: ${DISK}" 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ echo_log "Stopping mirror $gprov $DISK"
+ rc_nohalt "gmirror remove $gprov $DISK"
+ rc_nohalt "dd if=/dev/zero of=/dev/${DISK} count=4096"
+ fi
+ done
+};
+
+# Make sure we don't have any geli providers active on this disk
+stop_all_geli()
+{
+ local _geld="`echo ${1} | sed 's|/dev/||g'`"
+ cd /dev
+
+ for i in `ls ${_geld}*`
+ do
+ echo $i | grep -q '.eli' 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ echo_log "Detaching GELI on ${i}"
+ rc_halt "geli detach ${i}"
+ fi
+ done
+
+};
+
+# Function which reads in the disk slice config, and performs it
+setup_disk_slice()
+{
+
+ # Cleanup any slice / mirror dirs
+ rm -rf ${SLICECFGDIR} >/dev/null 2>/dev/null
+ mkdir ${SLICECFGDIR}
+ rm -rf ${MIRRORCFGDIR} >/dev/null 2>/dev/null
+ mkdir ${MIRRORCFGDIR}
+
+ # Start with disk0 and gm0
+ disknum="0"
+ gmnum="0"
+
+ # We are ready to start setting up the disks, lets read the config and do the actions
+ while read line
+ do
+ echo $line | grep -q "^disk${disknum}=" 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+
+ # Found a disk= entry, lets get the disk we are working on
+ get_value_from_string "${line}"
+ strip_white_space "$VAL"
+ DISK="$VAL"
+
+ echo "${DISK}" | grep -q '^/dev/'
+ if [ $? -ne 0 ] ; then DISK="/dev/$DISK" ; fi
+
+ # Before we go further, lets confirm this disk really exists
+ if [ ! -e "${DISK}" ] ; then
+ exit_err "ERROR: The disk ${DISK} does not exist!"
+ fi
+
+ # Make sure we stop any gmirrors on this disk
+ stop_all_gmirror ${DISK}
+
+ # Make sure we stop any geli stuff on this disk
+ stop_all_geli ${DISK}
+
+ # Make sure we don't have any zpools loaded
+ stop_all_zfs ${DISK}
+
+ fi
+
+ # Lets look if this device will be mirrored on another disk
+ echo $line | grep -q "^mirror=" 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+
+ # Found a disk= entry, lets get the disk we are working on
+ get_value_from_string "${line}"
+ strip_white_space "$VAL"
+ MIRRORDISK="$VAL"
+ echo "${MIRRORDISK}" | grep -q '^/dev/'
+ if [ $? -ne 0 ] ; then MIRRORDISK="/dev/$MIRRORDISK" ; fi
+
+ # Before we go further, lets confirm this disk really exists
+ if [ ! -e "${MIRRORDISK}" ]
+ then
+ exit_err "ERROR: The mirror disk ${MIRRORDISK} does not exist!"
+ fi
+
+ # Make sure we stop any gmirrors on this mirror disk
+ stop_all_gmirror ${MIRRORDISK}
+
+ # Make sure we stop any geli stuff on this mirror disk
+ stop_all_geli ${MIRRORDISK}
+
+ # Make sure we don't have any zpools mirror loaded
+ stop_all_zfs ${MIRRORDISK}
+
+ fi
+
+ # Lets see if we have been given a mirror balance choice
+ echo $line | grep -q "^mirrorbal=" 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+
+ # Found a disk= entry, lets get the disk we are working on
+ get_value_from_string "${line}"
+ strip_white_space "$VAL"
+ MIRRORBAL="$VAL"
+ fi
+
+ echo $line | grep -q "^partition=" 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ # Found a partition= entry, lets read / set it
+ get_value_from_string "${line}"
+ strip_white_space "$VAL"
+ PTYPE=`echo $VAL|tr A-Z a-z`
+
+ # We are using free space, figure out the slice number
+ if [ "${PTYPE}" = "free" ]
+ then
+ # Lets figure out what number this slice will be
+ LASTSLICE="`gpart show ${DISK} \
+ | grep -v ${DISK} \
+ | grep -v ' free' \
+ | tr -s '\t' ' ' \
+ | cut -d ' ' -f 4 \
+ | sed '/^$/d' \
+ | tail -n 1`"
+
+ if [ -z "${LASTSLICE}" ]
+ then
+ LASTSLICE="1"
+ else
+ LASTSLICE=$((LASTSLICE+1))
+ fi
+
+ if [ $LASTSLICE -gt 4 ]
+ then
+ exit_err "ERROR: BSD only supports primary partitions, and there are none available on $DISK"
+ fi
+
+ fi
+ fi
+
+ # Check if we have an image file defined
+ echo $line | grep -q "^image=" 2>/dev/null
+ if [ $? -eq 0 ] ; then
+ # Found an image= entry, lets read / set it
+ get_value_from_string "${line}"
+ strip_white_space "$VAL"
+ IMAGE="$VAL"
+ if [ ! -f "$IMAGE" ] ; then
+ exit_err "$IMAGE file does not exist"
+ fi
+ fi
+
+ # Check if we have a partscheme specified
+ echo $line | grep -q "^partscheme=" 2>/dev/null
+ if [ $? -eq 0 ] ; then
+ # Found a partscheme= entry, lets read / set it
+ get_value_from_string "${line}"
+ strip_white_space "$VAL"
+ PSCHEME="$VAL"
+ if [ "$PSCHEME" != "GPT" -a "$PSCHEME" != "MBR" ] ; then
+ exit_err "Unknown partition scheme: $PSCHEME"
+ fi
+ fi
+
+ echo $line | grep -q "^bootManager=" 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ # Found a bootManager= entry, lets read /set it
+ get_value_from_string "${line}"
+ strip_white_space "$VAL"
+ BMANAGER="$VAL"
+ fi
+
+ echo $line | grep -q "^commitDiskPart" 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ # Found our flag to commit this disk setup / lets do sanity check and do it
+ if [ ! -z "${DISK}" -a ! -z "${PTYPE}" ]
+ then
+ # Make sure we are only installing ppc to full disk
+ if [ `uname -m` = "powerpc" -o `uname -m` = "powerpc64" ]; then
+ if [ "$PTYPE" != "all" ] ; then
+ exit_err "powerpc can only be installed to a full disk"
+ fi
+ fi
+
+ case ${PTYPE} in
+ all)
+ # If we have a gmirror, lets set it up
+ if [ -n "$MIRRORDISK" ]; then
+ # Default to round-robin if the user didn't specify
+ if [ -z "$MIRRORBAL" ]; then MIRRORBAL="round-robin" ; fi
+
+ _mFile=`echo $DISK | sed 's|/|%|g'`
+ echo "$MIRRORDISK:$MIRRORBAL:gm${gmnum}" >${MIRRORCFGDIR}/$_mFile
+ init_gmirror "$gmnum" "$MIRRORBAL" "$DISK" "$MIRRORDISK"
+
+ # Reset DISK to the gmirror device
+ DISK="/dev/mirror/gm${gmnum}"
+ gmnum=$((gmknum+1))
+ fi
+
+ if [ "$PSCHEME" = "MBR" -o -z "$PSCHEME" ] ; then
+ PSCHEME="MBR"
+ tmpSLICE="${DISK}s1"
+ else
+ tmpSLICE="${DISK}p1"
+ fi
+
+ if [ `uname -m` = "powerpc" -o `uname -m` = "powerpc64" ]
+ then
+ PSCHEME="APM"
+ tmpSLICE="${DISK}s1"
+ fi
+
+ run_gpart_full "${DISK}" "${BMANAGER}" "${PSCHEME}"
+ ;;
+
+ s1|s2|s3|s4)
+ tmpSLICE="${DISK}${PTYPE}"
+ # Get the number of the slice we are working on
+ s="`echo ${PTYPE} | awk '{print substr($0,length,1)}'`"
+ run_gpart_slice "${DISK}" "${BMANAGER}" "${s}"
+ ;;
+
+ p1|p2|p3|p4|p5|p6|p7|p8|p9|p10|p11|p12|p13|p14|p15|p16|p17|p18|p19|p20)
+ tmpSLICE="${DISK}${PTYPE}"
+ # Get the number of the gpt partition we are working on
+ s="`echo ${PTYPE} | awk '{print substr($0,length,1)}'`"
+ run_gpart_gpt_part "${DISK}" "${BMANAGER}" "${s}"
+ ;;
+
+ free)
+ tmpSLICE="${DISK}s${LASTSLICE}"
+ run_gpart_free "${DISK}" "${LASTSLICE}" "${BMANAGER}"
+ ;;
+
+ image)
+ if [ -z "${IMAGE}" ]
+ then
+ exit_err "ERROR: partition type image specified with no image!"
+ fi
+ ;;
+
+ *) exit_err "ERROR: Unknown PTYPE: $PTYPE" ;;
+ esac
+
+
+ if [ -n "${IMAGE}" ]
+ then
+ local DEST
+
+ if [ -n "${tmpSLICE}" ]
+ then
+ DEST="${tmpSLICE}"
+ else
+ DEST="${DISK}"
+ fi
+
+ write_image "${IMAGE}" "${DEST}"
+ check_disk_layout "${DEST}"
+ fi
+
+ # Now save which disk<num> this is, so we can parse it later during slice partition setup
+ if [ -z "${IMAGE}" ]
+ then
+ _sFile=`echo $tmpSLICE | sed 's|/|-|g'`
+ echo "disk${disknum}" >${SLICECFGDIR}/$_sFile
+ fi
+
+ # Increment our disk counter to look for next disk and unset
+ unset BMANAGER PTYPE DISK MIRRORDISK MIRRORBAL PSCHEME IMAGE
+ disknum=$((disknum+1))
+ else
+ exit_err "ERROR: commitDiskPart was called without procceding disk<num>= and partition= entries!!!"
+ fi
+ fi
+
+ done <${CFGF}
+
+};
+
+
+# Init the gmirror device
+init_gmirror()
+{
+ local _mNum=$1
+ local _mBal=$2
+ local _mDisk=$3
+
+ # Create this mirror device
+ rc_halt "gmirror label -vb ${_mBal} gm${_mNum} ${_mDisk}"
+
+ sleep 3
+
+}
+
+# Stop all gjournals on disk / slice
+stop_gjournal()
+{
+ _gdsk="`echo $1 | sed 's|/dev/||g'`"
+ # Check if we need to shutdown any journals on this drive
+ ls /dev/${_gdsk}*.journal >/dev/null 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ cd /dev
+ for i in `ls ${_gdsk}*.journal`
+ do
+ rawjournal="`echo ${i} | cut -d '.' -f 1`"
+ gjournal stop -f ${rawjournal} >>${LOGOUT} 2>>${LOGOUT}
+ gjournal clear ${rawjournal} >>${LOGOUT} 2>>${LOGOUT}
+ done
+ fi
+} ;
+
+
+# Function to wipe the potential backup gpt table from a disk
+clear_backup_gpt_table()
+{
+ echo_log "Clearing gpt backup table location on disk"
+ rc_nohalt "dd if=/dev/zero of=${1} bs=1m count=1"
+ rc_nohalt "dd if=/dev/zero of=${1} bs=1m oseek=`diskinfo ${1} | awk '{print int($3 / (1024*1024)) - 4;}'`"
+} ;
+
+# Function which runs gpart and creates a single large APM partition scheme
+init_apm_full_disk()
+{
+ _intDISK=$1
+
+ # Set our sysctl so we can overwrite any geom using drives
+ sysctl kern.geom.debugflags=16 >>${LOGOUT} 2>>${LOGOUT}
+
+ # Stop any journaling
+ stop_gjournal "${_intDISK}"
+
+ # Remove any existing partitions
+ delete_all_gpart "${_intDISK}"
+
+ sleep 2
+
+ echo_log "Running gpart on ${_intDISK}"
+ rc_halt "gpart create -s APM ${_intDISK}"
+ rc_halt "gpart add -s 800k -t freebsd-boot ${_intDISK}"
+
+ echo_log "Stamping boot sector on ${_intDISK}"
+ rc_halt "gpart bootcode -p /boot/boot1.hfs -i 1 ${_intDISK}"
+
+}
+
+# Function which runs gpart and creates a single large GPT partition scheme
+init_gpt_full_disk()
+{
+ _intDISK=$1
+
+ # Set our sysctl so we can overwrite any geom using drives
+ sysctl kern.geom.debugflags=16 >>${LOGOUT} 2>>${LOGOUT}
+
+ # Stop any journaling
+ stop_gjournal "${_intDISK}"
+
+ # Remove any existing partitions
+ delete_all_gpart "${_intDISK}"
+
+ sleep 2
+
+ echo_log "Running gpart on ${_intDISK}"
+ rc_halt "gpart create -s GPT ${_intDISK}"
+ rc_halt "gpart add -b 34 -s 128 -t freebsd-boot ${_intDISK}"
+
+ echo_log "Stamping boot sector on ${_intDISK}"
+ rc_halt "gpart bootcode -b /boot/pmbr ${_intDISK}"
+
+}
+
+# Function which runs gpart and creates a single large MBR partition scheme
+init_mbr_full_disk()
+{
+ _intDISK=$1
+ _intBOOT=$2
+
+ startblock="2016"
+
+ # Set our sysctl so we can overwrite any geom using drives
+ sysctl kern.geom.debugflags=16 >>${LOGOUT} 2>>${LOGOUT}
+
+ # Stop any journaling
+ stop_gjournal "${_intDISK}"
+
+ # Remove any existing partitions
+ delete_all_gpart "${_intDISK}"
+
+ sleep 2
+
+ echo_log "Running gpart on ${_intDISK}"
+ rc_halt "gpart create -s mbr -f active ${_intDISK}"
+
+ # Install new partition setup
+ echo_log "Running gpart add on ${_intDISK}"
+ rc_halt "gpart add -a 4k -t freebsd -i 1 ${_intDISK}"
+ sleep 2
+
+ echo_log "Cleaning up ${_intDISK}s1"
+ rc_halt "dd if=/dev/zero of=${_intDISK}s1 count=1024"
+
+ # Make the partition active
+ rc_halt "gpart set -a active -i 1 ${_intDISK}"
+
+ if [ "$_intBOOT" = "bsd" ] ; then
+ echo_log "Stamping boot0 on ${_intDISK}"
+ rc_halt "gpart bootcode -b /boot/boot0 ${_intDISK}"
+ else
+ echo_log "Stamping boot1 on ${_intDISK}"
+ rc_halt "gpart bootcode -b /boot/boot1 ${_intDISK}"
+ fi
+
+}
+
+# Function which runs gpart and creates a single large slice
+run_gpart_full()
+{
+ DISK=$1
+ BOOT=$2
+ SCHEME=$3
+
+ if [ "$SCHEME" = "APM" ] ; then
+ init_apm_full_disk "$DISK"
+ slice=`echo "${DISK}:1:apm" | sed 's|/|-|g'`
+ elif [ "$SCHEME" = "MBR" ] ; then
+ init_mbr_full_disk "$DISK" "$BOOT"
+ slice=`echo "${DISK}:1:mbr" | sed 's|/|-|g'`
+ else
+ init_gpt_full_disk "$DISK"
+ slice=`echo "${DISK}:1:gpt" | sed 's|/|-|g'`
+ fi
+
+ # Lets save our slice, so we know what to look for in the config file later on
+ if [ -z "$WORKINGSLICES" ]
+ then
+ WORKINGSLICES="${slice}"
+ export WORKINGSLICES
+ else
+ WORKINGSLICES="${WORKINGSLICES} ${slice}"
+ export WORKINGSLICES
+ fi
+};
+
+# Function which runs gpart on a specified gpt partition
+run_gpart_gpt_part()
+{
+ DISK=$1
+
+ # Set the slice we will use later
+ slice="${1}p${3}"
+
+ # Set our sysctl so we can overwrite any geom using drives
+ sysctl kern.geom.debugflags=16 >>${LOGOUT} 2>>${LOGOUT}
+
+ # Get the number of the slice we are working on
+ slicenum="$3"
+
+ # Stop any journaling
+ stop_gjournal "${slice}"
+
+ # Make sure we have disabled swap on this drive
+ if [ -e "${slice}b" ]
+ then
+ swapoff ${slice}b >/dev/null 2>/dev/null
+ swapoff ${slice}b.eli >/dev/null 2>/dev/null
+ fi
+
+ # Modify partition type
+ echo_log "Running gpart modify on ${DISK}"
+ rc_halt "gpart modify -t freebsd -i ${slicenum} ${DISK}"
+ sleep 2
+
+ # Clean up old partition
+ echo_log "Cleaning up $slice"
+ rc_halt "dd if=/dev/zero of=${DISK}p${slicenum} count=1024"
+
+ sleep 4
+
+ # Init the MBR partition
+ rc_halt "gpart create -s BSD ${DISK}p${slicenum}"
+
+ # Stamp the bootloader
+ sleep 4
+ rc_halt "gpart bootcode -b /boot/boot ${DISK}p${slicenum}"
+
+ # Set the slice to the format we'll be using for gpart later
+ slice=`echo "${1}:${3}:gptslice" | sed 's|/|-|g'`
+
+ # Lets save our slice, so we know what to look for in the config file later on
+ if [ -z "$WORKINGSLICES" ]
+ then
+ WORKINGSLICES="${slice}"
+ export WORKINGSLICES
+ else
+ WORKINGSLICES="${WORKINGSLICES} ${slice}"
+ export WORKINGSLICES
+ fi
+};
+
+# Function which runs gpart on a specified s1-4 slice
+run_gpart_slice()
+{
+ DISK=$1
+ if [ -n "$2" ]
+ then
+ BMANAGER="$2"
+ fi
+
+ # Set the slice we will use later
+ slice="${1}s${3}"
+
+ # Set our sysctl so we can overwrite any geom using drives
+ sysctl kern.geom.debugflags=16 >>${LOGOUT} 2>>${LOGOUT}
+
+ # Get the number of the slice we are working on
+ slicenum="$3"
+
+ # Stop any journaling
+ stop_gjournal "${slice}"
+
+ # Make sure we have disabled swap on this drive
+ if [ -e "${slice}b" ]
+ then
+ swapoff ${slice}b >/dev/null 2>/dev/null
+ swapoff ${slice}b.eli >/dev/null 2>/dev/null
+ fi
+
+ # Modify partition type
+ echo_log "Running gpart modify on ${DISK}"
+ rc_halt "gpart modify -t freebsd -i ${slicenum} ${DISK}"
+ sleep 2
+
+ # Clean up old partition
+ echo_log "Cleaning up $slice"
+ rc_halt "dd if=/dev/zero of=${DISK}s${slicenum} count=1024"
+
+ sleep 1
+
+ if [ "${BMANAGER}" = "bsd" ]
+ then
+ echo_log "Stamping boot sector on ${DISK}"
+ rc_halt "gpart bootcode -b /boot/boot0 ${DISK}"
+ fi
+
+ # Set the slice to the format we'll be using for gpart later
+ slice=`echo "${1}:${3}:mbr" | sed 's|/|-|g'`
+
+ # Lets save our slice, so we know what to look for in the config file later on
+ if [ -z "$WORKINGSLICES" ]
+ then
+ WORKINGSLICES="${slice}"
+ export WORKINGSLICES
+ else
+ WORKINGSLICES="${WORKINGSLICES} ${slice}"
+ export WORKINGSLICES
+ fi
+};
+
+# Function which runs gpart and creates a new slice from free disk space
+run_gpart_free()
+{
+ DISK=$1
+ SLICENUM=$2
+ if [ -n "$3" ]
+ then
+ BMANAGER="$3"
+ fi
+
+ # Set our sysctl so we can overwrite any geom using drives
+ sysctl kern.geom.debugflags=16 >>${LOGOUT} 2>>${LOGOUT}
+
+ slice="${DISK}s${SLICENUM}"
+ slicenum="${SLICENUM}"
+
+ # Working on the first slice, make sure we have MBR setup
+ gpart show ${DISK} >/dev/null 2>/dev/null
+ if [ $? -ne 0 -a "$SLICENUM" = "1" ] ; then
+ echo_log "Initializing disk, no existing MBR setup"
+ rc_halt "gpart create -s mbr ${DISK}"
+ fi
+
+ # Install new partition setup
+ echo_log "Running gpart on ${DISK}"
+ rc_halt "gpart add -a 4k -t freebsd -i ${slicenum} ${DISK}"
+ sleep 2
+
+ echo_log "Cleaning up $slice"
+ rc_halt "dd if=/dev/zero of=${slice} count=1024"
+
+ sleep 1
+
+ if [ "${BMANAGER}" = "bsd" ]
+ then
+ echo_log "Stamping boot sector on ${DISK}"
+ rc_halt "gpart bootcode -b /boot/boot0 ${DISK}"
+ fi
+
+ slice=`echo "${DISK}:${SLICENUM}:mbr" | sed 's|/|-|g'`
+ # Lets save our slice, so we know what to look for in the config file later on
+ if [ -z "$WORKINGSLICES" ]
+ then
+ WORKINGSLICES="${slice}"
+ export WORKINGSLICES
+ else
+ WORKINGSLICES="${WORKINGSLICES} ${slice}"
+ export WORKINGSLICES
+ fi
+};
diff --git a/usr.sbin/pc-sysinstall/backend/functions-extractimage.sh b/usr.sbin/pc-sysinstall/backend/functions-extractimage.sh
new file mode 100755
index 0000000..a75fad7
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/functions-extractimage.sh
@@ -0,0 +1,552 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Functions which perform the extraction / installation of system to disk
+
+. ${BACKEND}/functions-mountoptical.sh
+
+# Performs the extraction of data to disk from FreeBSD dist files
+start_extract_dist()
+{
+ if [ -z "$1" ] ; then exit_err "Called dist extraction with no directory set!"; fi
+ if [ -z "$INSFILE" ]; then exit_err "Called extraction with no install file set!"; fi
+ local DDIR="$1"
+
+ # Check if we are doing an upgrade, and if so use our exclude list
+ if [ "${INSTALLMODE}" = "upgrade" ]; then
+ TAROPTS="-X ${PROGDIR}/conf/exclude-from-upgrade"
+ else
+ TAROPTS=""
+ fi
+
+ # Loop though and extract dist files
+ for di in $INSFILE
+ do
+ # Check the MANIFEST see if we have an archive size / count
+ if [ -e "${DDIR}/MANIFEST" ]; then
+ count=`grep "^${di}.txz" ${DDIR}/MANIFEST | awk '{print $3}'`
+ if [ ! -z "$count" ] ; then
+ echo "INSTALLCOUNT: $count"
+ fi
+ fi
+ echo_log "pc-sysinstall: Starting Extraction (${di})"
+ tar -xpv -C ${FSMNT} -f ${DDIR}/${di}.txz ${TAROPTS} >&1 2>&1
+ if [ $? -ne 0 ]; then
+ exit_err "ERROR: Failed extracting the dist file: $di"
+ fi
+ done
+
+ # Check if this was a FTP download and clean it up now
+ if [ "${INSTALLMEDIUM}" = "ftp" ]; then
+ echo_log "Cleaning up downloaded archives"
+ rm -rf ${DDIR}
+ fi
+
+ echo_log "pc-sysinstall: Extraction Finished"
+}
+
+# Performs the extraction of data to disk from a uzip or tar archive
+start_extract_uzip_tar()
+{
+ if [ -z "$INSFILE" ]; then
+ exit_err "ERROR: Called extraction with no install file set!"
+ fi
+
+ # Check if we have a .count file, and echo it out for a front-end to use in progress bars
+ if [ -e "${INSFILE}.count" ]; then
+ echo "INSTALLCOUNT: `cat ${INSFILE}.count`"
+ fi
+
+ # Check if we are doing an upgrade, and if so use our exclude list
+ if [ "${INSTALLMODE}" = "upgrade" ]; then
+ TAROPTS="-X ${PROGDIR}/conf/exclude-from-upgrade"
+ else
+ TAROPTS=""
+ fi
+
+ echo_log "pc-sysinstall: Starting Extraction"
+
+ case ${PACKAGETYPE} in
+ uzip)
+ if ! kldstat -v | grep -q "geom_uzip" ; then
+ exit_err "Kernel module geom_uzip not loaded"
+ fi
+
+ # Start by mounting the uzip image
+ MDDEVICE=`mdconfig -a -t vnode -o readonly -f ${INSFILE}`
+ mkdir -p ${FSMNT}.uzip
+ mount -r /dev/${MDDEVICE}.uzip ${FSMNT}.uzip
+ if [ $? -ne 0 ]
+ then
+ exit_err "ERROR: Failed mounting the ${INSFILE}"
+ fi
+ cd ${FSMNT}.uzip
+
+ # Copy over all the files now!
+ tar cvf - . 2>/dev/null | tar -xpv -C ${FSMNT} ${TAROPTS} -f - 2>&1 | tee -a ${FSMNT}/.tar-extract.log
+ if [ $? -ne 0 ]
+ then
+ cd /
+ echo "TAR failure occurred:" >>${LOGOUT}
+ cat ${FSMNT}/.tar-extract.log | grep "tar:" >>${LOGOUT}
+ umount ${FSMNT}.uzip
+ mdconfig -d -u ${MDDEVICE}
+ exit_err "ERROR: Failed extracting the tar image"
+ fi
+
+ # All finished, now lets umount and cleanup
+ cd /
+ umount ${FSMNT}.uzip
+ mdconfig -d -u ${MDDEVICE}
+ ;;
+ tar)
+ tar -xpv -C ${FSMNT} -f ${INSFILE} ${TAROPTS} >&1 2>&1
+ if [ $? -ne 0 ]; then
+ exit_err "ERROR: Failed extracting the tar image"
+ fi
+ ;;
+ esac
+
+ # Check if this was a FTP download and clean it up now
+ if [ "${INSTALLMEDIUM}" = "ftp" ]
+ then
+ echo_log "Cleaning up downloaded archive"
+ rm ${INSFILE}
+ rm ${INSFILE}.count >/dev/null 2>/dev/null
+ rm ${INSFILE}.md5 >/dev/null 2>/dev/null
+ fi
+
+ echo_log "pc-sysinstall: Extraction Finished"
+
+};
+
+# Performs the extraction of data to disk from a directory with split files
+start_extract_split()
+{
+ if [ -z "${INSDIR}" ]
+ then
+ exit_err "ERROR: Called extraction with no install directory set!"
+ fi
+
+ echo_log "pc-sysinstall: Starting Extraction"
+
+ # Used by install.sh
+ DESTDIR="${FSMNT}"
+ export DESTDIR
+
+ HERE=`pwd`
+ DIRS=`ls -d ${INSDIR}/*|grep -Ev '(uzip|kernels|src)'`
+ for dir in ${DIRS}
+ do
+ cd "${dir}"
+ if [ -f "install.sh" ]
+ then
+ echo_log "Extracting" `basename ${dir}`
+ echo "y" | sh install.sh >/dev/null
+ if [ $? -ne 0 ]
+ then
+ exit_err "ERROR: Failed extracting ${dir}"
+ fi
+ else
+ exit_err "ERROR: ${dir}/install.sh does not exist"
+ fi
+ done
+ cd "${HERE}"
+
+ KERNELS=`ls -d ${INSDIR}/*|grep kernels`
+ cd "${KERNELS}"
+ if [ -f "install.sh" ]
+ then
+ echo_log "Extracting" `basename ${KERNELS}`
+ echo "y" | sh install.sh generic >/dev/null
+ if [ $? -ne 0 ]
+ then
+ exit_err "ERROR: Failed extracting ${KERNELS}"
+ fi
+ rm -rf "${FSMNT}/boot/kernel"
+ mv "${FSMNT}/boot/GENERIC" "${FSMNT}/boot/kernel"
+ else
+ exit_err "ERROR: ${KERNELS}/install.sh does not exist"
+ fi
+ cd "${HERE}"
+
+ SOURCE=`ls -d ${INSDIR}/*|grep src`
+ cd "${SOURCE}"
+ if [ -f "install.sh" ]
+ then
+ echo_log "Extracting" `basename ${SOURCE}`
+ echo "y" | sh install.sh all >/dev/null
+ if [ $? -ne 0 ]
+ then
+ exit_err "ERROR: Failed extracting ${SOURCE}"
+ fi
+ else
+ exit_err "ERROR: ${SOURCE}/install.sh does not exist"
+ fi
+ cd "${HERE}"
+
+ echo_log "pc-sysinstall: Extraction Finished"
+};
+
+# Function which will attempt to fetch the dist file(s) before we start
+fetch_dist_file()
+{
+ get_value_from_cfg ftpPath
+ if [ -z "$VAL" ]
+ then
+ exit_err "ERROR: Install medium was set to ftp, but no ftpPath was provided!"
+ fi
+
+ FTPPATH="${VAL}"
+
+ # Check if we have a /usr partition to save the download
+ if [ -d "${FSMNT}/usr" ]
+ then
+ DLDIR="${FSMNT}/usr/.fetch.$$"
+ else
+ DLDIR="${FSMNT}/.fetch.$$"
+ fi
+ mkdir -p ${DLDIR}
+
+ # Do the fetch of the dist archive(s) now
+ for di in $INSFILE
+ do
+ fetch_file "${FTPPATH}/${di}.txz" "${DLDIR}/${di}.txz" "1"
+ done
+
+ # Check to see if there is a MANIFEST file for this install
+ fetch_file "${FTPPATH}/MANIFEST" "${DLDIR}/MANIFEST" "0"
+
+ export DLDIR
+};
+
+# Function which will attempt to fetch the install file before we start
+# the install
+fetch_install_file()
+{
+ get_value_from_cfg ftpPath
+ if [ -z "$VAL" ]
+ then
+ exit_err "ERROR: Install medium was set to ftp, but no ftpPath was provided!"
+ fi
+
+ FTPPATH="${VAL}"
+
+ # Check if we have a /usr partition to save the download
+ if [ -d "${FSMNT}/usr" ]
+ then
+ OUTFILE="${FSMNT}/usr/.fetch-${INSFILE}"
+ else
+ OUTFILE="${FSMNT}/.fetch-${INSFILE}"
+ fi
+
+ # Do the fetch of the archive now
+ fetch_file "${FTPPATH}/${INSFILE}" "${OUTFILE}" "1"
+
+ # Check to see if there is a .count file for this install
+ fetch_file "${FTPPATH}/${INSFILE}.count" "${OUTFILE}.count" "0"
+
+ # Check to see if there is a .md5 file for this install
+ fetch_file "${FTPPATH}/${INSFILE}.md5" "${OUTFILE}.md5" "0"
+
+ # Done fetching, now reset the INSFILE to our downloaded archived
+ export INSFILE="${OUTFILE}"
+
+};
+
+# Function which will download freebsd install files
+fetch_split_files()
+{
+ get_ftpHost
+ if [ -z "$VAL" ]
+ then
+ exit_err "ERROR: Install medium was set to ftp, but no ftpHost was provided!"
+ fi
+ FTPHOST="${VAL}"
+
+ get_ftpDir
+ if [ -z "$VAL" ]
+ then
+ exit_err "ERROR: Install medium was set to ftp, but no ftpDir was provided!"
+ fi
+ FTPDIR="${VAL}"
+
+ # Check if we have a /usr partition to save the download
+ if [ -d "${FSMNT}/usr" ]
+ then
+ OUTFILE="${FSMNT}/usr/.fetch-${INSFILE}"
+ else
+ OUTFILE="${FSMNT}/.fetch-${INSFILE}"
+ fi
+
+ DIRS="base catpages dict doc info manpages proflibs kernels src"
+ if [ "${FBSD_ARCH}" = "amd64" ]
+ then
+ DIRS="${DIRS} lib32"
+ fi
+
+ for d in ${DIRS}
+ do
+ mkdir -p "${OUTFILE}/${d}"
+ done
+
+
+ NETRC="${OUTFILE}/.netrc"
+ cat <<EOF >"${NETRC}"
+machine ${FTPHOST}
+login anonymous
+password anonymous
+macdef INSTALL
+bin
+prompt
+EOF
+
+ for d in ${DIRS}
+ do
+ cat <<EOF >>"${NETRC}"
+cd ${FTPDIR}/${d}
+lcd ${OUTFILE}/${d}
+mreget *
+EOF
+ done
+
+ cat <<EOF >>"${NETRC}"
+bye
+
+
+EOF
+
+ # Fetch the files via ftp
+ echo "$ INSTALL" | ftp -N "${NETRC}" "${FTPHOST}"
+
+ # Done fetching, now reset the INSFILE to our downloaded archived
+ export INSFILE="${OUTFILE}"
+}
+
+# Function which does the rsync download from the server specified in cfg
+start_rsync_copy()
+{
+ # Load our rsync config values
+ get_value_from_cfg rsyncPath
+ if [ -z "${VAL}" ]; then
+ exit_err "ERROR: rsyncPath is unset! Please check your config and try again."
+ fi
+ export RSYNCPATH="${VAL}"
+
+ get_value_from_cfg rsyncHost
+ if [ -z "${VAL}" ]; then
+ exit_err "ERROR: rsyncHost is unset! Please check your config and try again."
+ fi
+ export RSYNCHOST="${VAL}"
+
+ get_value_from_cfg rsyncUser
+ if [ -z "${VAL}" ]; then
+ exit_err "ERROR: rsyncUser is unset! Please check your config and try again."
+ fi
+ export RSYNCUSER="${VAL}"
+
+ get_value_from_cfg rsyncPort
+ if [ -z "${VAL}" ]; then
+ exit_err "ERROR: rsyncPort is unset! Please check your config and try again."
+ fi
+ export RSYNCPORT="${VAL}"
+
+ COUNT=1
+ while
+ z=1
+ do
+ if [ ${COUNT} -gt ${RSYNCTRIES} ]
+ then
+ exit_err "ERROR: Failed rsync command!"
+ break
+ fi
+
+ rsync -avvzHsR \
+ --rsync-path="rsync --fake-super" \
+ -e "ssh -p ${RSYNCPORT}" \
+ ${RSYNCUSER}@${RSYNCHOST}:${RSYNCPATH}/./ ${FSMNT}
+ if [ $? -ne 0 ]
+ then
+ echo "Rsync failed! Tries: ${COUNT}"
+ else
+ break
+ fi
+
+ COUNT=$((COUNT+1))
+ done
+
+};
+
+start_image_install()
+{
+ if [ -z "${IMAGE_FILE}" ]
+ then
+ exit_err "ERROR: installMedium set to image but no image file specified!"
+ fi
+
+ # We are ready to start mounting, lets read the config and do it
+ while read line
+ do
+ echo $line | grep -q "^disk0=" 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ # Found a disk= entry, lets get the disk we are working on
+ get_value_from_string "${line}"
+ strip_white_space "$VAL"
+ DISK="$VAL"
+ fi
+
+ echo $line | grep -q "^commitDiskPart" 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ # Found our flag to commit this disk setup / lets do sanity check and do it
+ if [ -n "${DISK}" ]
+ then
+
+ # Write the image
+ write_image "${IMAGE_FILE}" "${DISK}"
+
+ # Increment our disk counter to look for next disk and unset
+ unset DISK
+ break
+
+ else
+ exit_err "ERROR: commitDiskPart was called without procceding disk<num>= and partition= entries!!!"
+ fi
+ fi
+
+ done <${CFGF}
+};
+
+# Entrance function, which starts the installation process
+init_extraction()
+{
+ # Figure out what file we are using to install from via the config
+ get_value_from_cfg installFile
+
+ if [ -n "${VAL}" ]
+ then
+ export INSFILE="${VAL}"
+ else
+ # If no installFile specified, try our defaults
+ if [ "$INSTALLTYPE" = "FreeBSD" ]
+ then
+ case $PACKAGETYPE in
+ uzip) INSFILE="${FBSD_UZIP_FILE}" ;;
+ tar) INSFILE="${FBSD_TAR_FILE}" ;;
+ dist)
+ get_value_from_cfg_with_spaces distFiles
+ if [ -z "$VAL" ] ; then
+ exit_err "No dist files specified!"
+ fi
+ INSFILE="${VAL}"
+ ;;
+ split)
+ INSDIR="${FBSD_BRANCH_DIR}"
+
+ # This is to trick opt_mount into not failing
+ INSFILE="${INSDIR}"
+ ;;
+ esac
+ else
+ case $PACKAGETYPE in
+ uzip) INSFILE="${UZIP_FILE}" ;;
+ tar) INSFILE="${TAR_FILE}" ;;
+ dist)
+ get_value_from_cfg_with_spaces distFiles
+ if [ -z "$VAL" ] ; then
+ exit_err "No dist files specified!"
+ fi
+ INSFILE="${VAL}"
+ ;;
+ esac
+ fi
+ export INSFILE
+ fi
+
+ # Lets start by figuring out what medium we are using
+ case ${INSTALLMEDIUM} in
+ dvd|usb)
+ # Lets start by mounting the disk
+ opt_mount
+ if [ -n "${INSDIR}" ]
+ then
+ INSDIR="${CDMNT}/${INSDIR}" ; export INSDIR
+ start_extract_split
+
+ else
+ if [ "$PACKAGETYPE" = "dist" ] ; then
+ start_extract_dist "${CDMNT}/usr/freebsd-dist"
+ else
+ INSFILE="${CDMNT}/${INSFILE}" ; export INSFILE
+ start_extract_uzip_tar
+ fi
+ fi
+ ;;
+
+ ftp)
+ case $PACKAGETYPE in
+ split)
+ fetch_split_files
+
+ INSDIR="${INSFILE}" ; export INSDIR
+ start_extract_split
+ ;;
+ dist)
+ fetch_dist_file
+ start_extract_dist "$DLDIR"
+ ;;
+ *)
+ fetch_install_file
+ start_extract_uzip_tar
+ ;;
+ esac
+ ;;
+
+ sftp) ;;
+
+ rsync) start_rsync_copy ;;
+ image) start_image_install ;;
+ local)
+ get_value_from_cfg localPath
+ if [ -z "$VAL" ]
+ then
+ exit_err "Install medium was set to local, but no localPath was provided!"
+ fi
+ LOCALPATH=$VAL
+ if [ "$PACKAGETYPE" = "dist" ] ; then
+ INSFILE="${INSFILE}" ; export INSFILE
+ start_extract_dist "$LOCALPATH"
+ else
+ INSFILE="${LOCALPATH}/${INSFILE}" ; export INSFILE
+ start_extract_uzip_tar
+ fi
+ ;;
+ *) exit_err "ERROR: Unknown install medium" ;;
+ esac
+
+};
diff --git a/usr.sbin/pc-sysinstall/backend/functions-ftp.sh b/usr.sbin/pc-sysinstall/backend/functions-ftp.sh
new file mode 100755
index 0000000..e974f31
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/functions-ftp.sh
@@ -0,0 +1,414 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Functions which runs commands on the system
+
+. ${BACKEND}/functions.sh
+. ${BACKEND}/functions-parse.sh
+
+DEFAULT_FTP_SERVER="ftp.freebsd.org"
+
+MAIN_FTP_SERVERS="\
+Main Site: ftp.freebsd.org"
+
+IPV6_FTP_SERVERS="\
+IPv6 Main Site: ftp.freebsd.org|\
+IPv6 Ireland: ftp3.ie.freebsd.org|\
+IPv6 Israel: ftp.il.freebsd.org|\
+IPv6 Japan: ftp2.jp.freebsd.org|\
+IPv6 Sweden: ftp4.se.freebsd.org|\
+IPv6 USA: ftp4.us.freebsd.org|\
+IPv6 Turkey: ftp2.tr.freebsd.org"
+
+PRIMARY_FTP_SERVERS="\
+Primary: ftp1.freebsd.org|\
+Primary #2: ftp2.freebsd.org|\
+Primary #3: ftp3.freebsd.org|\
+Primary #4: ftp4.freebsd.org|\
+Primary #5: ftp5.freebsd.org|\
+Primary #6: ftp6.freebsd.org|\
+Primary #7: ftp7.freebsd.org|\
+Primary #8: ftp8.freebsd.org|\
+Primary #9: ftp9.freebsd.org|\
+Primary #10: ftp10.freebsd.org|\
+Primary #11: ftp11.freebsd.org|\
+Primary #12: ftp12.freebsd.org|\
+Primary #13: ftp13.freebsd.org|\
+Primary #14: ftp14.freebsd.org"
+
+ARGENTINA_FTP_SERVERS="\
+Argentina: ftp.ar.freebsd.org"
+
+AUSTRALIA_FTP_SERVERS="\
+Australia: ftp.au.freebsd.org|\
+Australia #2: ftp2.au.freebsd.org|\
+Australia #3: ftp3.au.freebsd.org"
+
+AUSTRIA_FTP_SERVERS="\
+Austria: ftp.at.freebsd.org|\
+Austria #2: ftp2.at.freebsd.org"
+
+BRAZIL_FTP_SERVERS="\
+Brazil: ftp.br.freebsd.org|\
+Brazil #2: ftp2.br.freebsd.org|\
+Brazil #3: ftp3.br.freebsd.org|\
+Brazil #4: ftp4.br.freebsd.org|\
+Brazil #5: ftp5.br.freebsd.org|\
+Brazil #6: ftp6.br.freebsd.org|\
+Brazil #7: ftp7.br.freebsd.org"
+
+CANADA_FTP_SERVERS="\
+Canada: ftp.ca.freebsd.org"
+
+CHINA_FTP_SERVERS="\
+China: ftp.cn.freebsd.org|\
+China #2: ftp2.cn.freebsd.org"
+
+CROATIA_FTP_SERVERS="\
+Croatia: ftp.hr.freebsd.org"
+
+CZECH_REPUBLIC_FTP_SERVERS="\
+Czech Republic: ftp.cz.freebsd.org"
+
+DENMARK_FTP_SERVERS="\
+Denmark: ftp.dk.freebsd.org|\
+Denmark #2: ftp2.dk.freebsd.org"
+
+ESTONIA_FTP_SERVERS="\
+Estonia: ftp.ee.freebsd.org"
+
+FINLAND_FTP_SERVERS="\
+Finland: ftp.fi.freebsd.org"
+
+FRANCE_FTP_SERVERS="\
+France: ftp.fr.freebsd.org|\
+France #2: ftp2.fr.freebsd.org|\
+France #3: ftp3.fr.freebsd.org|\
+France #5: ftp5.fr.freebsd.org|\
+France #6: ftp6.fr.freebsd.org|\
+France #8: ftp8.fr.freebsd.org"
+
+GERMANY_FTP_SERVERS="\
+Germany: ftp.de.freebsd.org|\
+Germany #2: ftp2.de.freebsd.org|\
+Germany #3: ftp3.de.freebsd.org|\
+Germany #4: ftp4.de.freebsd.org|\
+Germany #5: ftp5.de.freebsd.org|\
+Germany #6: ftp6.de.freebsd.org|\
+Germany #7: ftp7.de.freebsd.org|\
+Germany #8: ftp8.de.freebsd.org"
+
+GREECE_FTP_SERVERS="\
+Greece: ftp.gr.freebsd.org|\
+Greece #2: ftp2.gr.freebsd.org"
+
+HUNGARY_FTP_SERVERS="\
+Hungary: ftp.hu.freebsd.org"
+
+ICELAND_FTP_SERVERS="\
+Iceland: ftp.is.freebsd.org"
+
+IRELAND_FTP_SERVERS="\
+Ireland: ftp.ie.freebsd.org|\
+Ireland #2: ftp2.ie.freebsd.org|\
+Ireland #3: ftp3.ie.freebsd.org"
+
+ISRAEL_FTP_SERVERS="\
+Israel: ftp.il.freebsd.org"
+
+ITALY_FTP_SERVERS="\
+Italy: ftp.it.freebsd.org"
+
+JAPAN_FTP_SERVERS="\
+Japan: ftp.jp.freebsd.org|\
+Japan #2: ftp2.jp.freebsd.org|\
+Japan #3: ftp3.jp.freebsd.org|\
+Japan #4: ftp4.jp.freebsd.org|\
+Japan #5: ftp5.jp.freebsd.org|\
+Japan #6: ftp6.jp.freebsd.org|\
+Japan #7: ftp7.jp.freebsd.org|\
+Japan #8: ftp8.jp.freebsd.org|\
+Japan #9: ftp9.jp.freebsd.org"
+
+KOREA_FTP_SERVERS="\
+Korea: ftp.kr.freebsd.org|\
+Korea #2: ftp2.kr.freebsd.org"
+
+LITHUANIA_FTP_SERVERS="\
+Lithuania: ftp.lt.freebsd.org"
+
+NETHERLANDS_FTP_SERVERS="\
+Netherlands: ftp.nl.freebsd.org|\
+Netherlands #2: ftp2.nl.freebsd.org"
+
+NORWAY_FTP_SERVERS="\
+Norway: ftp.no.freebsd.org|\
+Norway #3: ftp3.no.freebsd.org"
+
+POLAND_FTP_SERVERS="\
+Poland: ftp.pl.freebsd.org|\
+Poland #2: ftp2.pl.freebsd.org|\
+Poland #5: ftp5.pl.freebsd.org"
+
+PORTUGAL_FTP_SERVERS="\
+Portugal: ftp.pt.freebsd.org|\
+Portugal #2: ftp2.pt.freebsd.org|\
+Portugal #4: ftp4.pt.freebsd.org"
+
+ROMANIA_FTP_SERVERS="\
+Romania: ftp.ro.freebsd.org"
+
+RUSSIA_FTP_SERVERS="\
+Russia: ftp.ru.freebsd.org|\
+Russia #2: ftp2.ru.freebsd.org|\
+Russia #3: ftp3.ru.freebsd.org|\
+Russia #4: ftp4.ru.freebsd.org"
+
+SINGAPORE_FTP_SERVERS="\
+Singapore: ftp.sg.freebsd.org"
+
+SLOVAK_REPUBLIC_FTP_SERVERS="\
+Slovak Republic: ftp.sk.freebsd.org"
+
+SLOVENIA_FTP_SERVERS="\
+Slovenia: ftp.si.freebsd.org|\
+Slovenia #2: ftp2.si.freebsd.org"
+
+SOUTH_AFRICA_FTP_SERVERS="\
+South Africa: ftp.za.freebsd.org|\
+South Africa #2: ftp2.za.freebsd.org|\
+South Africa #3: ftp3.za.freebsd.org|\
+South Africa #4: ftp4.za.freebsd.org"
+
+SPAIN_FTP_SERVERS="\
+Spain: ftp.es.freebsd.org|\
+Spain #2: ftp2.es.freebsd.org|\
+Spain #3: ftp3.es.freebsd.org"
+
+SWEDEN_FTP_SERVERS="\
+Sweden: ftp.se.freebsd.org|\
+Sweden #2: ftp2.se.freebsd.org|\
+Sweden #3: ftp3.se.freebsd.org|\
+Sweden #4: ftp4.se.freebsd.org|\
+Sweden #5: ftp5.se.freebsd.org"
+
+SWITZERLAND_FTP_SERVERS="\
+Switzerland: ftp.ch.freebsd.org|\
+Switzerland #2: ftp2.ch.freebsd.org"
+
+TAIWAN_FTP_SERVERS="\
+Taiwan: ftp.tw.freebsd.org|\
+Taiwan #2: ftp2.tw.freebsd.org|\
+Taiwan #3: ftp3.tw.freebsd.org|\
+Taiwan #4: ftp4.tw.freebsd.org|\
+Taiwan #6: ftp6.tw.freebsd.org|\
+Taiwan #11: ftp11.tw.freebsd.org"
+
+TURKEY_FTP_SERVERS="\
+Turkey: ftp.tr.freebsd.org|\
+Turkey #2: ftp2.tr.freebsd.org"
+
+UK_FTP_SERVERS="\
+UK: ftp.uk.freebsd.org|\
+UK #2: ftp2.uk.freebsd.org|\
+UK #3: ftp3.uk.freebsd.org|\
+UK #4: ftp4.uk.freebsd.org|\
+UK #5: ftp5.uk.freebsd.org|\
+UK #6: ftp6.uk.freebsd.org"
+
+UKRAINE_FTP_SERVERS="\
+Ukraine: ftp.ua.freebsd.org|\
+Ukraine #2: ftp2.ua.freebsd.org|\
+Ukraine #5: ftp5.ua.freebsd.org|\
+Ukraine #6: ftp6.ua.freebsd.org|\
+Ukraine #7: ftp7.ua.freebsd.org|\
+Ukraine #8: ftp8.ua.freebsd.org"
+
+USA_FTP_SERVERS="\
+USA #1: ftp1.us.freebsd.org|\
+USA #2: ftp2.us.freebsd.org|\
+USA #3: ftp3.us.freebsd.org|\
+USA #4: ftp4.us.freebsd.org|\
+USA #5: ftp5.us.freebsd.org|\
+USA #6: ftp6.us.freebsd.org|\
+USA #7: ftp7.us.freebsd.org|\
+USA #8: ftp8.us.freebsd.org|\
+USA #9: ftp9.us.freebsd.org|\
+USA #10: ftp10.us.freebsd.org|\
+USA #11: ftp11.us.freebsd.org|\
+USA #12: ftp12.us.freebsd.org|\
+USA #13: ftp13.us.freebsd.org|\
+USA #14: ftp14.us.freebsd.org|\
+USA #15: ftp15.us.freebsd.org"
+
+show_mirrors()
+{
+ MIRRORS="${1}"
+ if [ -n "${MIRRORS}" ]
+ then
+ SAVE_IFS="${IFS}"
+ IFS="|"
+ for m in ${MIRRORS}
+ do
+ echo "$m"
+ done
+ IFS="${SAVE_IFS}"
+ fi
+};
+
+set_ftp_mirror()
+{
+ MIRROR="${1}"
+ echo "${MIRROR}" > "${CONFDIR}/mirrors.conf"
+};
+
+get_ftp_mirror()
+{
+ MIRROR="${DEFAULT_FTP_SERVER}"
+ if [ -f "${CONFDIR}/mirrors.conf" ]
+ then
+ MIRROR=`cat "${CONFDIR}/mirrors.conf"`
+ fi
+
+ export VAL="${MIRROR}"
+};
+
+
+get_ftpHost()
+{
+ get_value_from_cfg ftpPath
+ ftpPath="$VAL"
+
+ ftpHost=`echo "${ftpPath}" | sed -E 's|^(ftp://)([^/]*)(.*)|\2|'`
+ export VAL="${ftpHost}"
+};
+
+get_ftpDir()
+{
+ get_value_from_cfg ftpPath
+ ftpPath="$VAL"
+
+ ftpDir=`echo "${ftpPath}" | sed -E 's|^(ftp://)([^/]*)(.*)|\3|'`
+ export VAL="${ftpDir}"
+};
+
+get_ftp_mirrors()
+{
+ COUNTRY="${1}"
+ if [ -n "$COUNTRY" ]
+ then
+ COUNTRY=`echo $COUNTRY|tr A-Z a-z`
+ case "${COUNTRY}" in
+ argentina*) VAL="${ARGENTINA_FTP_SERVERS}" ;;
+ australia*) VAL="${AUSTRALIA_FTP_SERVERS}" ;;
+ austria*) VAL="${AUSTRIA_FTP_SERVERS}" ;;
+ brazil*) VAL="${BRAZIL_FTP_SERVERS}" ;;
+ canada*) VAL="${CANADA_FTP_SERVERS}" ;;
+ china*) VAL="${CHINA_FTP_SERVERS}" ;;
+ croatia*) VAL="${CROATIA_FTP_SERVERS}" ;;
+ czech*) VAL="${CZECH_REPUBLIC_FTP_SERVERS}" ;;
+ denmark*) VAL="${DENMARK_FTP_SERVERS}" ;;
+ estonia*) VAL="${ESTONIA_FTP_SERVERS}" ;;
+ finland*) VAL="${FINLAND_FTP_SERVERS}" ;;
+ france*) VAL="${FRANCE_FTP_SERVERS}" ;;
+ germany*) VAL="${GERMANY_FTP_SERVERS}" ;;
+ greece*) VAL="${GREECE_FTP_SERVERS}" ;;
+ hungary*) VAL="${HUNGARY_FTP_SERVERS}" ;;
+ iceland*) VAL="${ICELAND_FTP_SERVERS}" ;;
+ ireland*) VAL="${IRELAND_FTP_SERVERS}" ;;
+ israel*) VAL="${ISRAEL_FTP_SERVERS}" ;;
+ italy*) VAL="${ITALY_FTP_SERVERS}" ;;
+ japan*) VAL="${JAPAN_FTP_SERVERS}" ;;
+ korea*) VAL="${KOREA_FTP_SERVERS}" ;;
+ lithuania*) VAL="${LITHUANIA_FTP_SERVERS}" ;;
+ netherlands*) VAL="${NETHERLANDS_FTP_SERVERS}" ;;
+ norway*) VAL="${NORWAY_FTP_SERVERS}" ;;
+ poland*) VAL="${POLAND_FTP_SERVERS}" ;;
+ portugal*) VAL="${PORTUGAL_FTP_SERVERS}" ;;
+ romania*) VAL="${ROMAINIA_FTP_SERVERS}" ;;
+ russia*) VAL="${RUSSIA_FTP_SERVERS}" ;;
+ singapore*) VAL="${SINGAPORE_FTP_SERVERS}" ;;
+ slovak*) VAL="${SLOVAK_REPUBLIC_FTP_SERVERS}" ;;
+ slovenia*) VAL="${SLOVENIA_FTP_SERVERS}" ;;
+ *africa*) VAL="${SOUTH_AFRICA_FTP_SERVERS}" ;;
+ spain*) VAL="${SPAIN_FTP_SERVERS}" ;;
+ sweden*) VAL="${SWEDEN_FTP_SERVERS}" ;;
+ switzerland*) VAL="${SWITZERLAND_FTP_SERVERS}" ;;
+ taiwan*) VAL="${TAIWAN_FTP_SERVERS}" ;;
+ turkey*) VAL="${TURKEY_FTP_SERVERS}" ;;
+ ukraine*) VAL="${UKRAINE_FTP_SERVERS}" ;;
+ uk*) VAL="${UK_FTP_SERVERS}" ;;
+ usa*) VAL="${USA_FTP_SERVERS}" ;;
+ esac
+ else
+ VAL="${MAIN_FTP_SERVERS}"
+ VAL="${VAL}|${IPV6_FTP_SERVERS}"
+ VAL="${VAL}|${PRIMARY_FTP_SERVERS}"
+ VAL="${VAL}|${ARGENTINA_FTP_SERVERS}"
+ VAL="${VAL}|${AUSTRALIA_FTP_SERVERS}"
+ VAL="${VAL}|${AUSTRIA_FTP_SERVERS}"
+ VAL="${VAL}|${BRAZIL_FTP_SERVERS}"
+ VAL="${VAL}|${CANADA_FTP_SERVERS}"
+ VAL="${VAL}|${CHINA_FTP_SERVERS}"
+ VAL="${VAL}|${CROATIA_FTP_SERVERS}"
+ VAL="${VAL}|${CZECH_REPUBLIC_FTP_SERVERS}"
+ VAL="${VAL}|${DENMARK_FTP_SERVERS}"
+ VAL="${VAL}|${ESTONIA_FTP_SERVERS}"
+ VAL="${VAL}|${FINLAND_FTP_SERVERS}"
+ VAL="${VAL}|${FRANCE_FTP_SERVERS}"
+ VAL="${VAL}|${GERMANY_FTP_SERVERS}"
+ VAL="${VAL}|${GREECE_FTP_SERVERS}"
+ VAL="${VAL}|${HUNGARY_FTP_SERVERS}"
+ VAL="${VAL}|${ICELAND_FTP_SERVERS}"
+ VAL="${VAL}|${IRELAND_FTP_SERVERS}"
+ VAL="${VAL}|${ISRAEL_FTP_SERVERS}"
+ VAL="${VAL}|${ITALY_FTP_SERVERS}"
+ VAL="${VAL}|${JAPAN_FTP_SERVERS}"
+ VAL="${VAL}|${KOREA_FTP_SERVERS}"
+ VAL="${VAL}|${LITHUANIA_FTP_SERVERS}"
+ VAL="${VAL}|${NETHERLANDS_FTP_SERVERS}"
+ VAL="${VAL}|${NORWAY_FTP_SERVERS}"
+ VAL="${VAL}|${POLAND_FTP_SERVERS}"
+ VAL="${VAL}|${PORTUGAL_FTP_SERVERS}"
+ VAL="${VAL}|${ROMANIA_FTP_SERVERS}"
+ VAL="${VAL}|${RUSSIA_FTP_SERVERS}"
+ VAL="${VAL}|${SINGAPORE_FTP_SERVERS}"
+ VAL="${VAL}|${SLOVAK_REPUBLIC_FTP_SERVERS}"
+ VAL="${VAL}|${SLOVENIA_FTP_SERVERS}"
+ VAL="${VAL}|${SOUTH_AFRICA_FTP_SERVERS}"
+ VAL="${VAL}|${SPAIN_FTP_SERVERS}"
+ VAL="${VAL}|${SWEDEN_FTP_SERVERS}"
+ VAL="${VAL}|${SWITZERLAND_FTP_SERVERS}"
+ VAL="${VAL}|${TAIWAN_FTP_SERVERS}"
+ VAL="${VAL}|${TURKEY_FTP_SERVERS}"
+ VAL="${VAL}|${UKRAINE_FTP_SERVERS}"
+ VAL="${VAL}|${UK_FTP_SERVERS}"
+ VAL="${VAL}|${USA_FTP_SERVERS}"
+ fi
+
+ export VAL
+};
diff --git a/usr.sbin/pc-sysinstall/backend/functions-installcomponents.sh b/usr.sbin/pc-sysinstall/backend/functions-installcomponents.sh
new file mode 100755
index 0000000..f388dd4
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/functions-installcomponents.sh
@@ -0,0 +1,177 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Functions which check and load any optional modules specified in the config
+
+. ${BACKEND}/functions.sh
+. ${BACKEND}/functions-parse.sh
+
+copy_component()
+{
+ COMPONENT="$1"
+ FAILED="0"
+ CFILES=""
+
+ # Check the type, and set the components subdir properly
+ TYPE="`grep 'type:' ${COMPDIR}/${COMPONENT}/component.cfg | cut -d ' ' -f 2`"
+ if [ "${TYPE}" = "PBI" ]
+ then
+ SUBDIR="PBI"
+ else
+ SUBDIR="components"
+ fi
+
+ # Lets start by downloading / copying the files this component needs
+ while read line
+ do
+ CFILE="`echo $line | cut -d ':' -f 1`"
+ CFILEMD5="`echo $line | cut -d ':' -f 2`"
+ CFILE2MD5="`echo $line | cut -d ':' -f 3`"
+
+ case ${INSTALLMEDIUM} in
+ dvd|usb)
+ # On both dvd / usb, we can just copy the file
+ cp ${CDMNT}/${COMPFILEDIR}/${SUBDIR}/${CFILE} \
+ ${FSMNT}/${COMPTMPDIR} >>${LOGOUT} 2>>${LOGOUT}
+ RESULT="$?"
+ ;;
+
+ ftp)
+ get_value_from_cfg ftpPath
+ if [ -z "$VAL" ]
+ then
+ exit_err "ERROR: Install medium was set to ftp, but no ftpPath was provided!"
+ fi
+ FTPPATH="${VAL}"
+
+ fetch_file "${FTPPATH}/${COMPFILEDIR}/${SUBDIR}/${CFILE}" "${FSMNT}/${COMPTMPDIR}/${CFILE}" "0"
+ RESULT="$?"
+ ;;
+ local)
+ get_value_from_cfg localPath
+ if [ -z "$VAL" ]; then
+ exit_err "Install medium was set to local, but no localPath was provided!"
+ fi
+ LOCALPATH=$VAL
+ cp ${LOCALPATH}/${COMPFILEDIR}/${SUBDIR}/${CFILE} \
+ ${FSMNT}/${COMPTMPDIR} >>${LOGOUT} 2>>${LOGOUT}
+ RESULT="$?"
+ ;;
+ esac
+
+ if [ "${RESULT}" != "0" ]
+ then
+ echo_log "WARNING: Failed to copy ${CFILE}"
+ FAILED="1"
+ else
+ # Now lets check the MD5 to confirm the file is valid
+ CHECKMD5=`md5 -q ${FSMNT}/${COMPTMPDIR}/${CFILE}`
+ if [ "${CHECKMD5}" != "${CFILEMD5}" -a "${CHECKMD5}" != "${CFILE2MD5}" ]
+ then
+ echo_log "WARNING: ${CFILE} failed md5 checksum"
+ FAILED="1"
+ else
+ if [ -z "${CFILES}" ]
+ then
+ CFILES="${CFILE}"
+ else
+ CFILES="${CFILES},${CFILE}"
+ fi
+ fi
+ fi
+
+
+ done < ${COMPDIR}/${COMPONENT}/distfiles
+
+ if [ "${FAILED}" = "0" ]
+ then
+ # Now install the component
+ run_component_install ${COMPONENT} ${CFILES}
+ fi
+
+};
+
+run_component_install()
+{
+ COMPONENT="$1"
+ CFILES="$1"
+
+ # Lets install this component now
+ # Start by making a wrapper script which sets the variables
+ # for the component to use
+ echo "#!/bin/sh
+COMPTMPDIR=\"${COMPTMPDIR}\"
+export COMPTMPDIR
+CFILE=\"${CFILE}\"
+export CFILE
+mount -t devfs devfs /dev
+
+sh ${COMPTMPDIR}/install.sh
+
+umount /dev
+" >${FSMNT}/.componentwrapper.sh
+ chmod 755 ${FSMNT}/.componentwrapper.sh
+
+ # Copy over the install script for this component
+ cp ${COMPDIR}/${COMPONENT}/install.sh ${FSMNT}/${COMPTMPDIR}/
+
+ echo_log "INSTALL COMPONENT: ${i}"
+ chroot ${FSMNT} /.componentwrapper.sh >>${LOGOUT} 2>>${LOGOUT}
+ rm ${FSMNT}/.componentwrapper.sh
+
+};
+
+# Check for any modules specified, and begin loading them
+install_components()
+{
+ # First, lets check and see if we even have any optional modules
+ get_value_from_cfg installComponents
+ if [ -n "${VAL}" ]
+ then
+ # Lets start by cleaning up the string and getting it ready to parse
+ strip_white_space ${VAL}
+ COMPONENTS=`echo ${VAL} | sed -e "s|,| |g"`
+ for i in $COMPONENTS
+ do
+ if [ ! -e "${COMPDIR}/${i}/install.sh" -o ! -e "${COMPDIR}/${i}/distfiles" ]
+ then
+ echo_log "WARNING: Component ${i} doesn't seem to exist"
+ else
+
+ # Make the tmpdir on the disk
+ mkdir -p ${FSMNT}/${COMPTMPDIR} >>${LOGOUT} 2>>${LOGOUT}
+
+ # Start by grabbing the component files
+ copy_component ${i}
+
+ # Remove the tmpdir now
+ rm -rf ${FSMNT}/${COMPTMPDIR} >>${LOGOUT} 2>>${LOGOUT}
+ fi
+ done
+ fi
+
+};
diff --git a/usr.sbin/pc-sysinstall/backend/functions-installpackages.sh b/usr.sbin/pc-sysinstall/backend/functions-installpackages.sh
new file mode 100755
index 0000000..c1a879a
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/functions-installpackages.sh
@@ -0,0 +1,188 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Functions which check and load any optional packages specified in the config
+
+. ${BACKEND}/functions.sh
+. ${BACKEND}/functions-parse.sh
+
+# Recursively determine all dependencies for this package
+determine_package_dependencies()
+{
+ local PKGNAME="${1}"
+ local DEPFILE="${2}"
+
+ grep -q "${PKGNAME}" "${DEPFILE}"
+ if [ $? -ne 0 ]
+ then
+ echo "${PKGNAME}" >> "${DEPFILE}"
+ get_package_dependencies "${PKGNAME}" "1"
+
+ local DEPS="${VAL}"
+ for d in ${DEPS}
+ do
+ determine_package_dependencies "${d}" "${DEPFILE}"
+ done
+ fi
+};
+
+# Fetch packages dependencies from a file
+fetch_package_dependencies()
+{
+ local DEPFILE
+ local DEPS
+ local SAVEDIR
+
+ DEPFILE="${1}"
+ DEPS=`cat "${DEPFILE}"`
+ SAVEDIR="${2}"
+
+ for d in ${DEPS}
+ do
+ get_package_short_name "${d}"
+ SNAME="${VAL}"
+
+ get_package_category "${SNAME}"
+ CATEGORY="${VAL}"
+
+ fetch_package "${CATEGORY}" "${d}" "${SAVEDIR}"
+ done
+};
+
+# Check for any packages specified, and begin loading them
+install_packages()
+{
+ echo "Checking for packages to install..."
+ sleep 2
+
+ # First, lets check and see if we even have any packages to install
+ get_value_from_cfg installPackages
+
+ # Nothing to do?
+ if [ -z "${VAL}" ]; then return; fi
+
+ echo "Installing packages..."
+ sleep 3
+
+ local PKGPTH
+
+ HERE=`pwd`
+ rc_halt "mkdir -p ${FSMNT}${PKGTMPDIR}"
+
+ # Determine the directory we will install packages from
+ get_package_location
+ rc_halt "cd ${PKGDLDIR}"
+
+ # Set the location of the INDEXFILE
+ INDEXFILE="${TMPDIR}/INDEX"
+
+ if [ ! -f "${INDEXFILE}" ]; then
+ get_package_index
+ fi
+
+ if [ ! -f "${TMPDIR}/INDEX.parsed" -a "$INSTALLMEDIUM" = "ftp" ]; then
+ parse_package_index
+ fi
+
+ # What extension are we using for pkgs?
+ PKGEXT="txz"
+ get_value_from_cfg pkgExt
+ if [ -n "${VAL}" ]; then
+ strip_white_space ${VAL}
+ PKGEXT="$VAL"
+ fi
+ export PKGEXT
+
+ # We dont want to be bothered with scripts asking questions
+ PACKAGE_BUILDING=yes
+ export PACKAGE_BUILDING
+
+ # Lets start by cleaning up the string and getting it ready to parse
+ get_value_from_cfg_with_spaces installPackages
+ PACKAGES="${VAL}"
+ echo_log "Packages to install: `echo $PACKAGES | wc -w | awk '{print $1}'`"
+ for i in $PACKAGES
+ do
+ if ! get_package_name "${i}"
+ then
+ echo_log "Unable to locate package ${i}"
+ continue
+ fi
+
+ PKGNAME="${VAL}"
+
+ # Fetch package + deps, but skip if installing from local media
+ if [ "${INSTALLMEDIUM}" = "ftp" ] ; then
+ DEPFILE="${FSMNT}/${PKGTMPDIR}/.${PKGNAME}.deps"
+ rc_nohalt "touch ${DEPFILE}"
+ determine_package_dependencies "${PKGNAME}" "${DEPFILE}"
+ fetch_package_dependencies "${DEPFILE}" "${FSMNT}/${PKGTMPDIR}"
+ fi
+
+ # Set package location
+ case "${INSTALLMEDIUM}" in
+ usb|dvd|local) PKGPTH="${PKGTMPDIR}/All/${PKGNAME}" ;;
+ *) PKGPTH="${PKGTMPDIR}/${PKGNAME}" ;;
+ esac
+
+ # See if we need to determine the package format we are working with
+ if [ -z "${PKGINFO}" ] ; then
+ tar tqf "${FSMNT}${PKGPTH}" '+MANIFEST' >/dev/null 2>/dev/null
+ if [ $? -ne 0 ] ; then
+ PKGADD="pkg_add -C ${FSMNT}"
+ PKGINFO="pkg_info"
+ else
+ PKGADD="pkg -c ${FSMNT} add"
+ PKGINFO="pkg info"
+ bootstrap_pkgng
+ fi
+ fi
+
+ # If the package is not already installed, install it!
+ if ! run_chroot_cmd "${PKGINFO} -e ${PKGNAME}" >/dev/null 2>/dev/null
+ then
+ echo_log "Installing package: ${PKGNAME}"
+ rc_nohalt "${PKGADD} ${PKGPTH}"
+ fi
+
+ if [ "${INSTALLMEDIUM}" = "ftp" ] ; then
+ rc_nohalt "rm ${DEPFILE}"
+ fi
+
+ done
+
+ echo_log "Package installation complete!"
+
+ # Cleanup after ourselves
+ rc_halt "cd ${HERE}"
+ if [ "${INSTALLMEDIUM}" = "ftp" ] ; then
+ rc_halt "rm -rf ${FSMNT}${PKGTMPDIR}" >/dev/null 2>/dev/null
+ else
+ rc_halt "umount ${FSMNT}${PKGTMPDIR}" >/dev/null 2>/dev/null
+ rc_halt "rmdir ${FSMNT}${PKGTMPDIR}" >/dev/null 2>/dev/null
+ fi
+};
diff --git a/usr.sbin/pc-sysinstall/backend/functions-localize.sh b/usr.sbin/pc-sysinstall/backend/functions-localize.sh
new file mode 100755
index 0000000..b92e710
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/functions-localize.sh
@@ -0,0 +1,541 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Functions which runs commands on the system
+
+. ${BACKEND}/functions.sh
+. ${BACKEND}/functions-parse.sh
+
+
+# Function which localizes a FreeBSD install
+localize_freebsd()
+{
+ sed -i.bak "s/lang=en_US/lang=${LOCALE}/g" ${FSMNT}/etc/login.conf
+ rm ${FSMNT}/etc/login.conf.bak
+};
+
+localize_x_desktops() {
+
+ # Check for and customize KDE lang
+ ##########################################################################
+
+ # Check if we can localize KDE via skel
+ if [ -e "${FSMNT}/usr/share/skel/.kde4/share/config/kdeglobals" ] ; then
+ sed -i '' "s/Country=us/Country=${COUNTRY}/g" ${FSMNT}/usr/share/skel/.kde4/share/config/kdeglobals
+ sed -i '' "s/Country=us/Country=${COUNTRY}/g" ${FSMNT}/root/.kde4/share/config/kdeglobals
+ sed -i '' "s/Language=en_US/Language=${SETLANG}:${LOCALE}/g" ${FSMNT}/usr/share/skel/.kde4/share/config/kdeglobals
+ fi
+
+ # Check if we have a KDE root config
+ if [ -e "${FSMNT}/root/.kde4/share/config/kdeglobals" ] ; then
+ sed -i '' "s/Language=en_US/Language=${SETLANG}:${LOCALE}/g" ${FSMNT}/root/.kde4/share/config/kdeglobals
+ fi
+
+ # Check for KDM
+ if [ -e "${FSMNT}/usr/local/kde4/share/config/kdm/kdmrc" ] ; then
+ sed -i '' "s/Language=en_US/Language=${LOCALE}.UTF-8/g" ${FSMNT}/usr/local/kde4/share/config/kdm/kdmrc
+ fi
+
+ # Check for and customize GNOME / GDM lang
+ ##########################################################################
+
+ # See if GDM is enabled and customize its lang
+ cat ${FSMNT}/etc/rc.conf 2>/dev/null | grep -q "gdm_enable=\"YES\"" 2>/dev/null
+ if [ "$?" = "0" ] ; then
+ echo "gdm_lang=\"${LOCALE}.UTF-8\"" >> ${FSMNT}/etc/rc.conf
+ fi
+
+};
+
+# Function which localizes a PC-BSD install
+localize_pcbsd()
+{
+ # Check if we have a localized splash screen and copy it
+ if [ -e "${FSMNT}/usr/local/share/pcbsd/splash-screens/loading-screen-${SETLANG}.pcx" ]
+ then
+ cp ${FSMNT}/usr/local/share/pcbsd/splash-screens/loading-screen-${SETLANG}.pcx ${FSMNT}/boot/loading-screen.pcx
+ fi
+
+};
+
+localize_x_keyboard()
+{
+ KEYMOD="$1"
+ KEYLAY="$2"
+ KEYVAR="$3"
+ COUNTRY="$4"
+ OPTION="grp:alt_shift_toggle"
+ SETXKBMAP=""
+
+ if [ "${COUNTRY}" = "NONE" -o "${COUNTRY}" = "us" -o "${COUNTRY}" = "C" ] ; then
+ #In this case we don't need any additional language
+ COUNTRY=""
+ OPTION=""
+ else
+ COUNTRY=",${COUNTRY}"
+ fi
+
+ if [ "${KEYMOD}" != "NONE" ]
+ then
+ SETXKBMAP="-model ${KEYMOD}"
+ KXMODEL="${KEYMOD}"
+ else
+ KXMODEL="pc104"
+ fi
+
+ if [ "${KEYLAY}" != "NONE" ]
+ then
+ localize_key_layout "$KEYLAY"
+ SETXKBMAP="${SETXKBMAP} -layout ${KEYLAY}"
+ KXLAYOUT="${KEYLAY}"
+ else
+ KXLAYOUT="us"
+ fi
+
+ if [ "${KEYVAR}" != "NONE" ]
+ then
+ SETXKBMAP="${SETXKBMAP} -variant ${KEYVAR}"
+ KXVAR="(${KEYVAR})"
+ else
+ KXVAR=""
+ fi
+
+ # Setup .xprofile with our setxkbmap call now
+ if [ ! -z "${SETXKBMAP}" ]
+ then
+ if [ ! -e "${FSMNT}/usr/share/skel/.xprofile" ]
+ then
+ echo "#!/bin/sh" >${FSMNT}/usr/share/skel/.xprofile
+ fi
+
+ # Save the keyboard layout for user / root X logins
+ echo "setxkbmap ${SETXKBMAP}" >>${FSMNT}/usr/share/skel/.xprofile
+ chmod 755 ${FSMNT}/usr/share/skel/.xprofile
+ cp ${FSMNT}/usr/share/skel/.xprofile ${FSMNT}/root/.xprofile
+
+ # Save it for KDM
+ if [ -e "${FSMNT}/usr/local/kde4/share/config/kdm/Xsetup" ] ; then
+ echo "setxkbmap ${SETXKBMAP}" >>${FSMNT}/usr/local/kde4/share/config/kdm/Xsetup
+ fi
+ fi
+
+ # Create the kxkbrc configuration using these options
+ if [ -d "${FSMNT}/usr/share/skel/.kde4/share/config" ] ; then
+ echo "[Layout]
+DisplayNames=${KXLAYOUT}${COUNTRY}
+IndicatorOnly=false
+LayoutList=${KXLAYOUT}${KXVAR}${COUNTRY}
+Model=${KXMODEL}
+Options=${OPTION}
+ResetOldOptions=true
+ShowFlag=true
+ShowSingle=false
+SwitchMode=WinClass
+Use=true " >${FSMNT}/usr/share/skel/.kde4/share/config/kxkbrc
+ fi
+
+};
+
+localize_key_layout()
+{
+
+ KEYLAYOUT="$1"
+
+ # Set the keylayout in rc.conf
+ case ${KEYLAYOUT} in
+ am) KEYLAYOUT_CONSOLE="hy.armscii-8" ;;
+ ca) KEYLAYOUT_CONSOLE="fr_CA.acc.iso" ;;
+ ch) KEYLAYOUT_CONSOLE="swissgerman.iso" ;;
+ cz) KEYLAYOUT_CONSOLE="cz.iso2" ;;
+ de) KEYLAYOUT_CONSOLE="german.iso" ;;
+ dk) KEYLAYOUT_CONSOLE="danish.iso" ;;
+ ee) KEYLAYOUT_CONSOLE="estonian.iso" ;;
+ es) KEYLAYOUT_CONSOLE="spanish.iso" ;;
+ fi) KEYLAYOUT_CONSOLE="finnish.iso" ;;
+ is) KEYLAYOUT_CONSOLE="icelandic.iso" ;;
+ jp) KEYLAYOUT_CONSOLE="jp.106" ;;
+ nl) KEYLAYOUT_CONSOLE="dutch.iso.acc" ;;
+ no) KEYLAYOUT_CONSOLE="norwegian.iso" ;;
+ pl) KEYLAYOUT_CONSOLE="pl_PL.ISO8859-2" ;;
+ ru) KEYLAYOUT_CONSOLE="ru.koi8-r" ;;
+ sk) KEYLAYOUT_CONSOLE="sk.iso2" ;;
+ se) KEYLAYOUT_CONSOLE="swedish.iso" ;;
+ tr) KEYLAYOUT_CONSOLE="tr.iso9.q" ;;
+ gb) KEYLAYOUT_CONSOLE="uk.iso" ;;
+ *) if [ ! -z "${KEYLAYOUT}" ]
+ then
+ KEYLAYOUT_CONSOLE="${KEYLAYOUT}.iso"
+ fi
+ ;;
+ esac
+
+ if [ -n "${KEYLAYOUT_CONSOLE}" ]
+ then
+ echo "keymap=\"${KEYLAYOUT_CONSOLE}\"" >>${FSMNT}/etc/rc.conf
+ fi
+
+};
+
+# Function which prunes other l10n files from the KDE install
+localize_prune_langs()
+{
+ get_value_from_cfg localizeLang
+ KEEPLANG="$VAL"
+ if [ -z "$KEEPLANG" ] ; then
+ KEEPLANG="en"
+ fi
+ export KEEPLANG
+
+ echo_log "Pruning other l10n files, keeping ${KEEPLANG}"
+
+ # Create the script to do uninstalls
+ echo '#!/bin/sh
+
+ for i in `pkg_info -xEI kde-l10n`
+ do
+ echo "$i" | grep "${KEEPLANG}-kde"
+ if [ $? -ne 0 ] ; then
+ pkg_delete ${i}
+ fi
+ done
+ ' > ${FSMNT}/.pruneLangs.sh
+
+ chmod 755 ${FSMNT}/.pruneLangs.sh
+ chroot ${FSMNT} /.pruneLangs.sh >/dev/null 2>/dev/null
+ rm ${FSMNT}/.pruneLangs.sh
+
+};
+
+# Function which sets COUNTRY SETLANG and LOCALE based upon $1
+localize_get_codes()
+{
+ TARGETLANG="${1}"
+ # Setup the presets for the specific lang
+ case $TARGETLANG in
+ af)
+ COUNTRY="C"
+ SETLANG="af"
+ LOCALE="af_ZA"
+ ;;
+ ar)
+ COUNTRY="C"
+ SETLANG="ar"
+ LOCALE="en_US"
+ ;;
+ az)
+ COUNTRY="C"
+ SETLANG="az"
+ LOCALE="en_US"
+ ;;
+ ca)
+ COUNTRY="es"
+ SETLANG="es:ca"
+ LOCALE="ca_ES"
+ ;;
+ be)
+ COUNTRY="be"
+ SETLANG="be"
+ LOCALE="be_BY"
+ ;;
+ bn)
+ COUNTRY="bn"
+ SETLANG="bn"
+ LOCALE="en_US"
+ ;;
+ bg)
+ COUNTRY="bg"
+ SETLANG="bg"
+ LOCALE="bg_BG"
+ ;;
+ cs)
+ COUNTRY="cz"
+ SETLANG="cs"
+ LOCALE="cs_CZ"
+ ;;
+ da)
+ COUNTRY="dk"
+ SETLANG="da"
+ LOCALE="da_DK"
+ ;;
+ de)
+ COUNTRY="de"
+ SETLANG="de"
+ LOCALE="de_DE"
+ ;;
+ en_GB)
+ COUNTRY="gb"
+ SETLANG="en_GB:cy"
+ LOCALE="en_GB"
+ ;;
+ el)
+ COUNTRY="gr"
+ SETLANG="el:gr"
+ LOCALE="el_GR"
+ ;;
+ es)
+ COUNTRY="es"
+ SETLANG="es"
+ LOCALE="es_ES"
+ ;;
+ es_LA)
+ COUNTRY="us"
+ SETLANG="es:en_US"
+ LOCALE="es_ES"
+ ;;
+ et)
+ COUNTRY="ee"
+ SETLANG="et"
+ LOCALE="et_EE"
+ ;;
+ fr)
+ COUNTRY="fr"
+ SETLANG="fr"
+ LOCALE="fr_FR"
+ ;;
+ he)
+ COUNTRY="il"
+ SETLANG="he:ar"
+ LOCALE="he_IL"
+ ;;
+ hr)
+ COUNTRY="hr"
+ SETLANG="hr"
+ LOCALE="hr_HR"
+ ;;
+ hu)
+ COUNTRY="hu"
+ SETLANG="hu"
+ LOCALE="hu_HU"
+ ;;
+ it)
+ COUNTRY="it"
+ SETLANG="it"
+ LOCALE="it_IT"
+ ;;
+ ja)
+ COUNTRY="jp"
+ SETLANG="ja"
+ LOCALE="ja_JP"
+ ;;
+ ko)
+ COUNTRY="kr"
+ SETLANG="ko"
+ LOCALE="ko_KR"
+ ;;
+ nl)
+ COUNTRY="nl"
+ SETLANG="nl"
+ LOCALE="nl_NL"
+ ;;
+ nn)
+ COUNTRY="no"
+ SETLANG="nn"
+ LOCALE="en_US"
+ ;;
+ pa)
+ COUNTRY="pa"
+ SETLANG="pa"
+ LOCALE="en_US"
+ ;;
+ pl)
+ COUNTRY="pl"
+ SETLANG="pl"
+ LOCALE="pl_PL"
+ ;;
+ pt)
+ COUNTRY="pt"
+ SETLANG="pt"
+ LOCALE="pt_PT"
+ ;;
+ pt_BR)
+ COUNTRY="br"
+ SETLANG="pt_BR"
+ LOCALE="pt_BR"
+ ;;
+ ru)
+ COUNTRY="ru"
+ SETLANG="ru"
+ LOCALE="ru_RU"
+ ;;
+ sl)
+ COUNTRY="si"
+ SETLANG="sl"
+ LOCALE="sl_SI"
+ ;;
+ sk)
+ COUNTRY="sk"
+ SETLANG="sk"
+ LOCALE="sk_SK"
+ ;;
+ sv)
+ COUNTRY="se"
+ SETLANG="sv"
+ LOCALE="sv_SE"
+ ;;
+ uk)
+ COUNTRY="ua"
+ SETLANG="uk"
+ LOCALE="uk_UA"
+ ;;
+ vi)
+ COUNTRY="vn"
+ SETLANG="vi"
+ LOCALE="en_US"
+ ;;
+ zh_CN)
+ COUNTRY="cn"
+ SETLANG="zh_CN"
+ LOCALE="zh_CN"
+ ;;
+ zh_TW)
+ COUNTRY="tw"
+ SETLANG="zh_TW"
+ LOCALE="zh_TW"
+ ;;
+ *)
+ COUNTRY="C"
+ SETLANG="${TARGETLANG}"
+ LOCALE="en_US"
+ ;;
+ esac
+
+ export COUNTRY SETLANG LOCALE
+
+};
+
+# Function which sets the timezone on the system
+set_timezone()
+{
+ TZONE="$1"
+ cp ${FSMNT}/usr/share/zoneinfo/${TZONE} ${FSMNT}/etc/localtime
+};
+
+# Function which enables / disables NTP
+set_ntp()
+{
+ ENABLED="$1"
+ if [ "$ENABLED" = "yes" -o "${ENABLED}" = "YES" ]
+ then
+ cat ${FSMNT}/etc/rc.conf 2>/dev/null | grep -q 'ntpd_enable="YES"' 2>/dev/null
+ if [ $? -ne 0 ]
+ then
+ echo 'ntpd_enable="YES"' >>${FSMNT}/etc/rc.conf
+ echo 'ntpd_sync_on_start="YES"' >>${FSMNT}/etc/rc.conf
+ fi
+ else
+ cat ${FSMNT}/etc/rc.conf 2>/dev/null | grep -q 'ntpd_enable="YES"' 2>/dev/null
+ if [ $? -ne 0 ]
+ then
+ sed -i.bak 's|ntpd_enable="YES"||g' ${FSMNT}/etc/rc.conf
+ fi
+ fi
+};
+
+# Starts checking for localization directives
+run_localize()
+{
+ KEYLAYOUT="NONE"
+ KEYMOD="NONE"
+ KEYVAR="NONE"
+
+ while read line
+ do
+ # Check if we need to do any localization
+ echo $line | grep -q "^localizeLang=" 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+
+ # Set our country / lang / locale variables
+ get_value_from_string "$line"
+ localize_get_codes ${VAL}
+
+ get_value_from_string "$line"
+ # If we are doing PC-BSD install, localize it as well as FreeBSD base
+ if [ "${INSTALLTYPE}" != "FreeBSD" ]
+ then
+ localize_pcbsd "$VAL"
+ fi
+
+ # Localize FreeBSD
+ localize_freebsd "$VAL"
+
+ # Localize any X pkgs
+ localize_x_desktops "$VAL"
+ fi
+
+ # Check if we need to do any keylayouts
+ echo $line | grep -q "^localizeKeyLayout=" 2>/dev/null
+ if [ $? -eq 0 ] ; then
+ get_value_from_string "$line"
+ KEYLAYOUT="$VAL"
+ fi
+
+ # Check if we need to do any key models
+ echo $line | grep -q "^localizeKeyModel=" 2>/dev/null
+ if [ $? -eq 0 ] ; then
+ get_value_from_string "$line"
+ KEYMOD="$VAL"
+ fi
+
+ # Check if we need to do any key variant
+ echo $line | grep -q "^localizeKeyVariant=" 2>/dev/null
+ if [ $? -eq 0 ] ; then
+ get_value_from_string "$line"
+ KEYVAR="$VAL"
+ fi
+
+
+ # Check if we need to set a timezone
+ echo $line | grep -q "^timeZone=" 2>/dev/null
+ if [ $? -eq 0 ] ; then
+ get_value_from_string "$line"
+ set_timezone "$VAL"
+ fi
+
+ # Check if we need to set a timezone
+ echo $line | grep -q "^enableNTP=" 2>/dev/null
+ if [ $? -eq 0 ] ; then
+ get_value_from_string "$line"
+ set_ntp "$VAL"
+ fi
+ done <${CFGF}
+
+ if [ "${INSTALLTYPE}" != "FreeBSD" ] ; then
+ # Do our X keyboard localization
+ localize_x_keyboard "${KEYMOD}" "${KEYLAYOUT}" "${KEYVAR}" "${COUNTRY}"
+ fi
+
+ # Check if we want to prunt any other KDE lang files to save some disk space
+ get_value_from_cfg localizePrune
+ if [ "${VAL}" = "yes" -o "${VAL}" = "YES" ] ; then
+ localize_prune_langs
+ fi
+
+ # Update the login.conf db, even if we didn't localize, its a good idea to make sure its up2date
+ run_chroot_cmd "/usr/bin/cap_mkdb /etc/login.conf" >/dev/null 2>/dev/null
+
+};
diff --git a/usr.sbin/pc-sysinstall/backend/functions-mountdisk.sh b/usr.sbin/pc-sysinstall/backend/functions-mountdisk.sh
new file mode 100755
index 0000000..e9eb148
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/functions-mountdisk.sh
@@ -0,0 +1,240 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Functions related mounting the newly formatted disk partitions
+
+# Mounts all the specified partition to the mount-point
+mount_partition()
+{
+ if [ -z "${1}" -o -z "${2}" -o -z "${3}" ]
+ then
+ exit_err "ERROR: Missing arguments for mount_partition"
+ fi
+
+ PART="${1}"
+ PARTFS="${2}"
+ MNTPOINT="${3}"
+ MNTFLAGS="${4}"
+
+ # Setup the MNTOPTS
+ if [ -z "${MNTOPTS}" ]
+ then
+ MNTFLAGS="-o rw"
+ else
+ MNTFLAGS="-o rw,${MNTFLAGS}"
+ fi
+
+
+ #We are on ZFS, lets setup this mount-point
+ if [ "${PARTFS}" = "ZFS" ]
+ then
+ ZPOOLNAME=$(get_zpool_name "${PART}")
+
+ # Check if we have multiple zfs mounts specified
+ for ZMNT in `echo ${MNTPOINT} | sed 's|,| |g'`
+ do
+ # Check for any ZFS specific mount options
+ ZMNTOPTS="`echo $ZMNT | cut -d '(' -f 2 | cut -d ')' -f 1`"
+ if [ "$ZMNTOPTS" = "$ZMNT" ] ; then ZMNTOPTS="" ; fi
+
+ # Reset ZMNT with options removed
+ ZMNT="`echo $ZMNT | cut -d '(' -f 1`"
+
+ # First make sure we create the mount point
+ if [ ! -d "${FSMNT}${ZMNT}" ] ; then
+ mkdir -p ${FSMNT}${ZMNT} >>${LOGOUT} 2>>${LOGOUT}
+ fi
+
+ # Check for any volsize args
+ zcopt=""
+ for ZOPT in `echo $ZMNTOPTS | sed 's/|/ /g'`
+ do
+ echo "$ZOPT" | grep -q volsize
+ if [ $? -eq 0 ] ; then
+ volsize=`echo $ZOPT | cut -d '=' -f 2`
+ zcopt="-V $volsize"
+ fi
+ done
+
+ if [ "${ZMNT}" = "/" ] ; then
+ # If creating ZFS / dataset, give it name that beadm works with
+ ZNAME="/ROOT/default"
+ ZMKMNT=""
+ echo_log "zfs create $zcopt -p ${ZPOOLNAME}/ROOT"
+ rc_halt "zfs create $zcopt -p ${ZPOOLNAME}/ROOT"
+ echo_log "zfs create $zcopt -p ${ZPOOLNAME}${ZNAME}"
+ rc_halt "zfs create $zcopt -p ${ZPOOLNAME}${ZNAME}"
+ else
+ ZNAME="${ZMNT}"
+ ZMKMNT="${ZMNT}"
+ echo_log "zfs create $zcopt -p ${ZPOOLNAME}${ZNAME}"
+ rc_halt "zfs create $zcopt -p ${ZPOOLNAME}${ZNAME}"
+ fi
+ sleep 2
+ if [ -z "$zcopt" ] ; then
+ rc_halt "zfs set mountpoint=${FSMNT}${ZMKMNT} ${ZPOOLNAME}${ZNAME}"
+ fi
+
+ # Do we need to make this / zfs dataset bootable?
+ if [ "$ZMNT" = "/" ] ; then
+ echo_log "Stamping ${ZPOOLNAME}/ROOT/default as bootfs"
+ rc_halt "zpool set bootfs=${ZPOOLNAME}/ROOT/default ${ZPOOLNAME}"
+ fi
+
+ # Do we need to make this /boot zfs dataset bootable?
+ if [ "$ZMNT" = "/boot" ] ; then
+ echo_log "Stamping ${ZPOOLNAME}${ZMNT} as bootfs"
+ rc_halt "zpool set bootfs=${ZPOOLNAME}${ZMNT} ${ZPOOLNAME}"
+ fi
+
+ # If no ZFS options, we can skip
+ if [ -z "$ZMNTOPTS" ] ; then continue ; fi
+
+ # Parse any ZFS options now
+ for ZOPT in `echo $ZMNTOPTS | sed 's/|/ /g'`
+ do
+ echo "$ZOPT" | grep -q volsize
+ if [ $? -eq 0 ] ; then continue ; fi
+ rc_halt "zfs set $ZOPT ${ZPOOLNAME}${ZNAME}"
+ done
+ done # End of adding ZFS mounts
+
+ else
+ # If we are not on ZFS, lets do the mount now
+ # First make sure we create the mount point
+ if [ ! -d "${FSMNT}${MNTPOINT}" ]
+ then
+ mkdir -p ${FSMNT}${MNTPOINT} >>${LOGOUT} 2>>${LOGOUT}
+ fi
+
+ echo_log "mount ${MNTFLAGS} ${PART} -> ${FSMNT}${MNTPOINT}"
+ sleep 2
+ rc_halt "mount ${MNTFLAGS} ${PART} ${FSMNT}${MNTPOINT}"
+ fi
+
+};
+
+# Mounts all the new file systems to prepare for installation
+mount_all_filesystems()
+{
+ # Make sure our mount point exists
+ mkdir -p ${FSMNT} >/dev/null 2>/dev/null
+
+ # First lets find and mount the / partition
+ #########################################################
+ for PART in `ls ${PARTDIR}`
+ do
+ PARTDEV=`echo $PART | sed 's|-|/|g'`
+ PARTFS="`cat ${PARTDIR}/${PART} | cut -d '#' -f 1`"
+ if [ ! -e "${PARTDEV}" -a "${PARTFS}" != "ZFS" ]
+ then
+ exit_err "ERROR: The partition ${PARTDEV} does not exist. Failure in bsdlabel?"
+ fi
+
+ PARTMNT="`cat ${PARTDIR}/${PART} | cut -d '#' -f 2`"
+ PARTENC="`cat ${PARTDIR}/${PART} | cut -d '#' -f 3`"
+
+ if [ "${PARTENC}" = "ON" ]
+ then
+ EXT=".eli"
+ else
+ EXT=""
+ fi
+
+ # Check for root partition for mounting, including ZFS "/,/usr" type
+ echo "$PARTMNT" | grep "/," >/dev/null
+ if [ "$?" = "0" -o "$PARTMNT" = "/" ]
+ then
+ case ${PARTFS} in
+ UFS) mount_partition ${PARTDEV}${EXT} ${PARTFS} ${PARTMNT} "noatime" ;;
+ UFS+S) mount_partition ${PARTDEV}${EXT} ${PARTFS} ${PARTMNT} "noatime" ;;
+ UFS+SUJ) mount_partition ${PARTDEV}${EXT} ${PARTFS} ${PARTMNT} "noatime" ;;
+ UFS+J) mount_partition ${PARTDEV}${EXT}.journal ${PARTFS} ${PARTMNT} "async,noatime" ;;
+ ZFS) mount_partition ${PARTDEV} ${PARTFS} ${PARTMNT} ;;
+ IMAGE) mount_partition ${PARTDEV} ${PARTFS} ${PARTMNT} ;;
+ *) exit_err "ERROR: Got unknown file-system type $PARTFS" ;;
+ esac
+ fi
+ done
+
+ # Now that we've mounted "/" lets do any other remaining mount-points
+ ##################################################################
+ for PART in `ls ${PARTDIR}`
+ do
+ PARTDEV=`echo $PART | sed 's|-|/|g'`
+ PARTFS="`cat ${PARTDIR}/${PART} | cut -d '#' -f 1`"
+ if [ ! -e "${PARTDEV}" -a "${PARTFS}" != "ZFS" ]
+ then
+ exit_err "ERROR: The partition ${PARTDEV} does not exist. Failure in bsdlabel?"
+ fi
+
+ PARTMNT="`cat ${PARTDIR}/${PART} | cut -d '#' -f 2`"
+ PARTENC="`cat ${PARTDIR}/${PART} | cut -d '#' -f 3`"
+
+ if [ "${PARTENC}" = "ON" ]
+ then
+ EXT=".eli"
+ else
+ EXT=""
+ fi
+
+ # Check if we've found "/" again, don't need to mount it twice
+ echo "$PARTMNT" | grep "/," >/dev/null
+ if [ "$?" != "0" -a "$PARTMNT" != "/" ]
+ then
+ case ${PARTFS} in
+ UFS) mount_partition ${PARTDEV}${EXT} ${PARTFS} ${PARTMNT} "noatime" ;;
+ UFS+S) mount_partition ${PARTDEV}${EXT} ${PARTFS} ${PARTMNT} "noatime" ;;
+ UFS+SUJ) mount_partition ${PARTDEV}${EXT} ${PARTFS} ${PARTMNT} "noatime" ;;
+ UFS+J) mount_partition ${PARTDEV}${EXT}.journal ${PARTFS} ${PARTMNT} "async,noatime" ;;
+ ZFS) mount_partition ${PARTDEV} ${PARTFS} ${PARTMNT} ;;
+ SWAP)
+ # Lets enable this swap now
+ if [ "$PARTENC" = "ON" ]
+ then
+ echo_log "Enabling encrypted swap on ${PARTDEV}"
+ rc_halt "geli onetime -d -e 3des ${PARTDEV}"
+ sleep 5
+ rc_halt "swapon ${PARTDEV}.eli"
+ else
+ echo_log "swapon ${PARTDEV}"
+ sleep 5
+ rc_halt "swapon ${PARTDEV}"
+ fi
+ ;;
+ IMAGE)
+ if [ ! -d "${PARTMNT}" ]
+ then
+ mkdir -p "${PARTMNT}"
+ fi
+ mount_partition ${PARTDEV} ${PARTFS} ${PARTMNT}
+ ;;
+ *) exit_err "ERROR: Got unknown file-system type $PARTFS" ;;
+ esac
+ fi
+ done
+};
diff --git a/usr.sbin/pc-sysinstall/backend/functions-mountoptical.sh b/usr.sbin/pc-sysinstall/backend/functions-mountoptical.sh
new file mode 100755
index 0000000..4a15b81
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/functions-mountoptical.sh
@@ -0,0 +1,152 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Functions which perform mounting / unmounting and switching of
+# optical / usb media
+
+. ${BACKEND}/functions.sh
+. ${BACKEND}/functions-parse.sh
+
+# Displays an optical failure message
+opt_fail()
+{
+ # If we got here, we must not have a DVD/USB we can find :(
+ get_value_from_cfg installInteractive
+ if [ "${VAL}" = "yes" ]
+ then
+ # We are running interactive, and didn't find a DVD, prompt user again
+ echo_log "DISK ERROR: Unable to find installation disk!"
+ echo_log "Please insert the installation disk and press enter."
+ read tmp
+ else
+ exit_err "ERROR: Unable to locate installation DVD/USB"
+ fi
+};
+
+# Performs the extraction of data to disk
+opt_mount()
+{
+ FOUND="0"
+
+ # Ensure we have a directory where its supposed to be
+ if [ ! -d "${CDMNT}" ]
+ then
+ mkdir -p ${CDMNT}
+ fi
+
+
+ # Start by checking if we already have a cd mounted at CDMNT
+ mount | grep -q "${CDMNT} " 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ if [ -e "${CDMNT}/${INSFILE}" ]
+ then
+ echo "MOUNTED" >${TMPDIR}/cdmnt
+ echo_log "FOUND DVD: MOUNTED"
+ FOUND="1"
+ return
+ fi
+
+ # failed to find optical disk
+ opt_fail
+ return
+ fi
+
+ # Setup our loop to search for installation media
+ while
+ z=1
+ do
+
+ # Loop though and look for an installation disk
+ for i in `ls -1 /dev/acd* /dev/cd* /dev/scd* /dev/rscd* 2>/dev/null`
+ do
+ # Find the CD Device
+ /sbin/mount_cd9660 $i ${CDMNT}
+
+ # Check the package type to see if we have our install data
+ if [ -e "${CDMNT}/${INSFILE}" ]
+ then
+ echo "${i}" >${TMPDIR}/cdmnt
+ echo_log "FOUND DVD: ${i}"
+ FOUND="1"
+ break
+ fi
+ /sbin/umount ${CDMNT} >/dev/null 2>/dev/null
+ done
+
+ # If no DVD found, try USB
+ if [ "$FOUND" != "1" ]
+ then
+ # Loop though and look for an installation disk
+ for i in `ls -1 /dev/da* 2>/dev/null`
+ do
+ # Check if we can mount this device UFS
+ /sbin/mount -r $i ${CDMNT}
+
+ # Check the package type to see if we have our install data
+ if [ -e "${CDMNT}/${INSFILE}" ]
+ then
+ echo "${i}" >${TMPDIR}/cdmnt
+ echo_log "FOUND USB: ${i}"
+ FOUND="1"
+ break
+ fi
+ /sbin/umount ${CDMNT} >/dev/null 2>/dev/null
+
+ # Also check if it is a FAT mount
+ /sbin/mount -r -t msdosfs $i ${CDMNT}
+
+ # Check the package type to see if we have our install data
+ if [ -e "${CDMNT}/${INSFILE}" ]
+ then
+ echo "${i}" >${TMPDIR}/cdmnt
+ echo_log "FOUND USB: ${i}"
+ FOUND="1"
+ break
+ fi
+ /sbin/umount ${CDMNT} >/dev/null 2>/dev/null
+ done
+ fi # End of USB Check
+
+
+ if [ "$FOUND" = "1" ]
+ then
+ break
+ fi
+
+ # Failed to find a disk, take action now
+ opt_fail
+
+ done
+
+};
+
+# Function to unmount optical media
+opt_umount()
+{
+ /sbin/umount ${CDMNT} >/dev/null 2>/dev/null
+};
diff --git a/usr.sbin/pc-sysinstall/backend/functions-networking.sh b/usr.sbin/pc-sysinstall/backend/functions-networking.sh
new file mode 100755
index 0000000..1aa08bd
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/functions-networking.sh
@@ -0,0 +1,500 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, Inc. All rights reserved.
+# Copyright (c) 2011 The FreeBSD Foundation
+# All rights reserved.
+#
+# Portions of this software were developed by Bjoern Zeeb
+# 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$
+
+# Functions which perform our networking setup
+
+# Function which creates a kde4 .desktop file for the PC-BSD net tray
+create_desktop_nettray()
+{
+ NIC="${1}"
+ echo "#!/usr/bin/env xdg-open
+[Desktop Entry]
+Exec=/usr/local/kde4/bin/pc-nettray ${NIC}
+Icon=network
+StartupNotify=false
+Type=Application" > ${FSMNT}/usr/share/skel/.kde4/Autostart/tray-${NIC}.desktop
+ chmod 744 ${FSMNT}/usr/share/skel/.kde4/Autostart/tray-${NIC}.desktop
+
+};
+
+# Function which checks is a nic is wifi or not
+check_is_wifi()
+{
+ NIC="$1"
+ ifconfig ${NIC} | grep -q "802.11" 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ return 0
+ else
+ return 1
+ fi
+};
+
+# Function to get the first available wired nic, used for setup
+get_first_wired_nic()
+{
+ rm ${TMPDIR}/.niclist >/dev/null 2>/dev/null
+ # start by getting a list of nics on this system
+ ${QUERYDIR}/detect-nics.sh > ${TMPDIR}/.niclist
+ if [ -e "${TMPDIR}/.niclist" ]
+ then
+ while read line
+ do
+ NIC="`echo $line | cut -d ':' -f 1`"
+ check_is_wifi ${NIC}
+ if [ $? -ne 0 ]
+ then
+ export VAL="${NIC}"
+ return
+ fi
+ done < ${TMPDIR}/.niclist
+ fi
+
+ export VAL=""
+ return
+};
+
+
+# Function which simply enables plain dhcp on all detected nics
+enable_dhcp_all()
+{
+ rm ${TMPDIR}/.niclist >/dev/null 2>/dev/null
+ # start by getting a list of nics on this system
+ ${QUERYDIR}/detect-nics.sh > ${TMPDIR}/.niclist
+ if [ -e "${TMPDIR}/.niclist" ]
+ then
+ echo "# Auto-Enabled NICs from pc-sysinstall" >>${FSMNT}/etc/rc.conf
+ WLANCOUNT="0"
+ while read line
+ do
+ NIC="`echo $line | cut -d ':' -f 1`"
+ DESC="`echo $line | cut -d ':' -f 2`"
+ echo_log "Setting $NIC to DHCP on the system."
+ check_is_wifi ${NIC}
+ if [ $? -eq 0 ]
+ then
+ # We have a wifi device, setup a wlan* entry for it
+ WLAN="wlan${WLANCOUNT}"
+ cat ${FSMNT}/etc/rc.conf | grep -q "wlans_${NIC}="
+ if [ $? -ne 0 ] ; then
+ echo "wlans_${NIC}=\"${WLAN}\"" >>${FSMNT}/etc/rc.conf
+ fi
+ echo "ifconfig_${WLAN}=\"DHCP\"" >>${FSMNT}/etc/rc.conf
+ CNIC="${WLAN}"
+ WLANCOUNT=$((WLANCOUNT+1))
+ else
+ echo "ifconfig_${NIC}=\"DHCP\"" >>${FSMNT}/etc/rc.conf
+ CNIC="${NIC}"
+ fi
+
+ done < ${TMPDIR}/.niclist
+ fi
+};
+
+
+# Function which detects available nics, and enables dhcp on them
+save_auto_dhcp()
+{
+ enable_dhcp_all
+};
+
+# Function which simply enables iPv6 SLAAC on all detected nics
+enable_slaac_all()
+{
+ rm ${TMPDIR}/.niclist >/dev/null 2>/dev/null
+ # start by getting a list of nics on this system
+ ${QUERYDIR}/detect-nics.sh > ${TMPDIR}/.niclist
+ if [ -e "${TMPDIR}/.niclist" ]
+ then
+ echo "# Auto-Enabled NICs from pc-sysinstall" >>${FSMNT}/etc/rc.conf
+ WLANCOUNT="0"
+ while read line
+ do
+ NIC="`echo $line | cut -d ':' -f 1`"
+ DESC="`echo $line | cut -d ':' -f 2`"
+ echo_log "Setting $NIC to accepting RAs on the system."
+ check_is_wifi ${NIC}
+ if [ $? -eq 0 ]
+ then
+ # We have a wifi device, setup a wlan* entry for it
+ # Given we cannot have DHCP and SLAAC the same time currently
+ # it's save to just duplicate.
+ WLAN="wlan${WLANCOUNT}"
+ cat ${FSMNT}/etc/rc.conf | grep -q "wlans_${NIC}="
+ if [ $? -ne 0 ] ; then
+ echo "wlans_${NIC}=\"${WLAN}\"" >>${FSMNT}/etc/rc.conf
+ fi
+ #echo "ifconfig_${NIC}=\"up\"" >>${FSMNT}/etc/rc.conf
+ echo "ifconfig_${WLAN}_ipv6=\"inet6 accept_rtadv\"" >>${FSMNT}/etc/rc.conf
+ CNIC="${WLAN}"
+ WLANCOUNT=$((WLANCOUNT+1))
+ else
+ #echo "ifconfig_${NIC}=\"up\"" >>${FSMNT}/etc/rc.conf
+ echo "ifconfig_${NIC}_ipv6=\"inet6 accept_rtadv\"" >>${FSMNT}/etc/rc.conf
+ CNIC="${NIC}"
+ fi
+
+ done < ${TMPDIR}/.niclist
+ fi
+
+ # Given we cannot yet rely on RAs to provide DNS information as much
+ # as we can in the DHCP world, we should append a given nameserver.
+ : > ${FSMNT}/etc/resolv.conf
+ get_value_from_cfg netSaveIPv6NameServer
+ NAMESERVER="${VAL}"
+ if [ -n "${NAMESERVER}" ]
+ then
+ echo "nameserver ${NAMESERVER}" >>${FSMNT}/etc/resolv.conf
+ fi
+
+};
+
+
+# Function which detects available nics, and enables IPv6 SLAAC on them
+save_auto_slaac()
+{
+ enable_slaac_all
+};
+
+
+# Function which saves a manual nic setup to the installed system
+save_manual_nic()
+{
+ # Get the target nic
+ NIC="$1"
+
+ get_value_from_cfg netSaveIP_${NIC}
+ NETIP="${VAL}"
+
+ if [ "$NETIP" = "DHCP" ]
+ then
+ echo_log "Setting $NIC to DHCP on the system."
+ echo "ifconfig_${NIC}=\"DHCP\"" >>${FSMNT}/etc/rc.conf
+ return 0
+ fi
+
+ # If we get here, we have a manual setup, lets do so now
+ IFARGS=""
+ IF6ARGS=""
+
+ # Set the manual IP
+ if [ -n "${NETIP}" ]
+ then
+ IFARGS="inet ${NETIP}"
+
+ # Check if we have a netmask to set
+ get_value_from_cfg netSaveMask_${NIC}
+ NETMASK="${VAL}"
+ if [ -n "${NETMASK}" ]
+ then
+ IFARGS="${IFARGS} netmask ${NETMASK}"
+ fi
+ fi
+
+ get_value_from_cfg netSaveIPv6_${NIC}
+ NETIP6="${VAL}"
+ if [ -n "${NETIP6}" ]
+ then
+ # Make sure we have one inet6 prefix.
+ IF6ARGS=`echo "${NETIP6}" | awk '{ if ("^inet6 ") { print $0; } else
+ { printf "inet6 %s", $0; } }'`
+ fi
+
+ echo "# Auto-Enabled NICs from pc-sysinstall" >>${FSMNT}/etc/rc.conf
+ if [ -n "${IFARGS}" ]
+ then
+ echo "ifconfig_${NIC}=\"${IFARGS}\"" >>${FSMNT}/etc/rc.conf
+ fi
+ if [ -n "${IF6ARGS}" ]
+ then
+ echo "ifconfig_${NIC}_ipv6=\"${IF6ARGS}\"" >>${FSMNT}/etc/rc.conf
+ fi
+
+};
+
+# Function which saves a manual gateway router setup to the installed system
+save_manual_router()
+{
+
+ # Check if we have a default router to set
+ get_value_from_cfg netSaveDefaultRouter
+ NETROUTE="${VAL}"
+ if [ -n "${NETROUTE}" ]
+ then
+ echo "defaultrouter=\"${NETROUTE}\"" >>${FSMNT}/etc/rc.conf
+ fi
+ get_value_from_cfg netSaveIPv6DefaultRouter
+ NETROUTE="${VAL}"
+ if [ -n "${NETROUTE}" ]
+ then
+ echo "ipv6_defaultrouter=\"${NETROUTE}\"" >>${FSMNT}/etc/rc.conf
+ fi
+
+};
+
+save_manual_nameserver()
+{
+ # Check if we have a nameserver to enable
+ : > ${FSMNT}/etc/resolv.conf
+ get_value_from_cfg_with_spaces netSaveNameServer
+ NAMESERVERLIST="${VAL}"
+ if [ ! -z "${NAMESERVERLIST}" ]
+ then
+ for NAMESERVER in ${NAMESERVERLIST}
+ do
+ echo "nameserver ${NAMESERVER}" >>${FSMNT}/etc/resolv.conf
+ done
+ fi
+
+ get_value_from_cfg_with_spaces netSaveIPv6NameServer
+ NAMESERVERLIST="${VAL}"
+ if [ ! -z "${NAMESERVERLIST}" ]
+ then
+ for NAMESERVER in ${NAMESERVERLIST}
+ do
+ echo "nameserver ${NAMESERVER}" >>${FSMNT}/etc/resolv.conf
+ done
+ fi
+
+};
+
+# Function which determines if a nic is active / up
+is_nic_active()
+{
+ ifconfig ${1} | grep -q "status: active" 2>/dev/null
+ if [ $? -eq 0 ] ; then
+ return 0
+ else
+ return 1
+ fi
+};
+
+
+# Function which detects available nics, and runs DHCP on them until
+# a success is found
+enable_auto_dhcp()
+{
+ # start by getting a list of nics on this system
+ ${QUERYDIR}/detect-nics.sh > ${TMPDIR}/.niclist
+ while read line
+ do
+ NIC="`echo $line | cut -d ':' -f 1`"
+ DESC="`echo $line | cut -d ':' -f 2`"
+
+ is_nic_active "${NIC}"
+ if [ $? -eq 0 ] ; then
+ echo_log "Trying DHCP on $NIC $DESC"
+ dhclient ${NIC} >/dev/null 2>/dev/null
+ if [ $? -eq 0 ] ; then
+ # Got a valid DHCP IP, we can return now
+ export WRKNIC="$NIC"
+ return 0
+ fi
+ fi
+ done < ${TMPDIR}/.niclist
+
+};
+
+# Function which detects available nics, and runs rtsol on them.
+enable_auto_slaac()
+{
+
+ # start by getting a list of nics on this system
+ ${QUERYDIR}/detect-nics.sh > ${TMPDIR}/.niclist
+ ALLNICS=""
+ while read line
+ do
+ NIC="`echo $line | cut -d ':' -f 1`"
+ DESC="`echo $line | cut -d ':' -f 2`"
+
+ is_nic_active "${NIC}"
+ if [ $? -eq 0 ] ; then
+ echo_log "Will try IPv6 SLAAC on $NIC $DESC"
+ ifconfig ${NIC} inet6 -ifdisabled accept_rtadv up
+ ALLNICS="${ALLNICS} ${NIC}"
+ fi
+ done < ${TMPDIR}/.niclist
+
+ # XXX once we support it in-tree call /sbin/resovconf here.
+ echo_log "Running rtsol on ${ALLNICS}"
+ rtsol -F ${ALLNICS} >/dev/null 2>/dev/null
+}
+
+# Get the mac address of a target NIC
+get_nic_mac()
+{
+ FOUNDMAC="`ifconfig ${1} | grep 'ether' | tr -d '\t' | cut -d ' ' -f 2`"
+ export FOUNDMAC
+}
+
+# Function which performs the manual setup of a target nic in the cfg
+enable_manual_nic()
+{
+ # Get the target nic
+ NIC="$1"
+
+ # Check that this NIC exists
+ rc_halt "ifconfig ${NIC}"
+
+ get_value_from_cfg netIP
+ NETIP="${VAL}"
+
+ if [ "$NETIP" = "DHCP" ]
+ then
+ echo_log "Enabling DHCP on $NIC"
+ rc_halt "dhclient ${NIC}"
+ return 0
+ fi
+
+ # If we get here, we have a manual setup, lets do so now
+
+ # IPv4:
+
+ # Set the manual IP
+ if [ -n "${NETIP}" ]
+ then
+ # Check if we have a netmask to set
+ get_value_from_cfg netMask
+ NETMASK="${VAL}"
+ if [ -n "${NETMASK}" ]
+ then
+ rc_halt "ifconfig inet ${NIC} netmask ${NETMASK}"
+ else
+ rc_halt "ifconfig inet ${NIC} ${NETIP}"
+ fi
+ fi
+
+ # Check if we have a default router to set
+ get_value_from_cfg netDefaultRouter
+ NETROUTE="${VAL}"
+ if [ -n "${NETROUTE}" ]
+ then
+ rc_halt "route add -inet default ${NETROUTE}"
+ fi
+
+ # IPv6:
+
+ # Set static IPv6 address
+ get_value_from_cfg netIPv6
+ NETIP="${VAL}"
+ if [ -n ${NETIP} ]
+ then
+ rc_halt "ifconfig inet6 ${NIC} ${NETIP} -ifdisabled up"
+ fi
+
+ # Default router
+ get_value_from_cfg netIPv6DefaultRouter
+ NETROUTE="${VAL}"
+ if [ -n "${NETROUTE}" ]
+ then
+ rc_halt "route add -inet6 default ${NETROUTE}"
+ fi
+
+ # Check if we have a nameserver to enable
+ : >/etc/resolv.conf
+ get_value_from_cfg netNameServer
+ NAMESERVER="${VAL}"
+ if [ -n "${NAMESERVER}" ]
+ then
+ echo "nameserver ${NAMESERVER}" >>/etc/resolv.conf
+ fi
+ get_value_from_cfg netIPv6NameServer
+ NAMESERVER="${VAL}"
+ if [ -n "${NAMESERVER}" ]
+ then
+ echo "nameserver ${NAMESERVER}" >>/etc/resolv.conf
+ fi
+
+};
+
+
+# Function which parses the cfg and enables networking per specified
+start_networking()
+{
+ # Check if we have any networking requested
+ get_value_from_cfg netDev
+ if [ -z "${VAL}" ]
+ then
+ return 0
+ fi
+
+ NETDEV="${VAL}"
+ if [ "$NETDEV" = "AUTO-DHCP" ]
+ then
+ enable_auto_dhcp
+ elif [ "$NETDEV" = "IPv6-SLAAC" ]
+ then
+ enable_auto_slaac
+ elif [ "$NETDEV" = "AUTO-DHCP-SLAAC" ]
+ then
+ enable_auto_dhcp
+ enable_auto_slaac
+ else
+ enable_manual_nic ${NETDEV}
+ fi
+
+};
+
+
+# Function which checks the cfg and enables the specified networking on
+# the installed system
+save_networking_install()
+{
+
+ # Check if we have any networking requested to save
+ get_value_from_cfg_with_spaces netSaveDev
+ if [ -z "${VAL}" ]
+ then
+ return 0
+ fi
+
+ NETDEVLIST="${VAL}"
+ if [ "$NETDEVLIST" = "AUTO-DHCP" ]
+ then
+ save_auto_dhcp
+ elif [ "$NETDEVLIST" = "IPv6-SLAAC" ]
+ then
+ save_auto_slaac
+ elif [ "$NETDEVLIST" = "AUTO-DHCP-SLAAC" ]
+ then
+ save_auto_dhcp
+ save_auto_slaac
+ else
+ for NETDEV in ${NETDEVLIST}
+ do
+ save_manual_nic ${NETDEV}
+ done
+ save_manual_router
+ save_manual_nameserver
+ fi
+
+};
diff --git a/usr.sbin/pc-sysinstall/backend/functions-newfs.sh b/usr.sbin/pc-sysinstall/backend/functions-newfs.sh
new file mode 100755
index 0000000..f8664f0
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/functions-newfs.sh
@@ -0,0 +1,262 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Functions related to disk operations using newfs
+
+
+# Function which performs the ZFS magic
+setup_zfs_filesystem()
+{
+ PART="$1"
+ PARTFS="$2"
+ PARTMNT="$3"
+ EXT="$4"
+ PARTGEOM="$5"
+ ZPOOLOPTS="$6"
+ ROOTSLICE="`echo ${PART} | rev | cut -b 2- | rev`"
+ ZPOOLNAME=$(get_zpool_name "${PART}")
+
+ # Sleep a few moments, let the disk catch its breath
+ sleep 5
+ sync
+
+ # Check if we have multiple zfs mounts specified
+ for i in `echo ${PARTMNT} | sed 's|,| |g'`
+ do
+ # Check if we ended up with needing a zfs bootable partition
+ if [ "${i}" = "/" -o "${i}" = "/boot" ]
+ then
+ if [ "$HAVEBOOT" = "YES" ] ; then continue ; fi
+ if [ "${PARTGEOM}" = "MBR" ] ; then
+ # Lets stamp the proper ZFS boot loader
+ echo_log "Setting up ZFS boot loader support"
+ rc_halt "dd if=/boot/zfsboot of=${ROOTSLICE} count=1"
+ rc_halt "dd if=/boot/zfsboot of=${PART}${EXT} skip=1 seek=1024"
+ fi
+ fi
+ done
+
+ # Check if we have some custom zpool arguments and use them if so
+ if [ ! -z "${ZPOOLOPTS}" ] ; then
+ # Sort through devices and run gnop on them
+ local gnopDev=""
+ local newOpts=""
+ for i in $ZPOOLOPTS
+ do
+ echo "$i" | grep -q '/dev/'
+ if [ $? -eq 0 ] ; then
+ rc_halt "gnop create -S 4096 ${i}"
+ gnopDev="$gnopDev $i"
+ newOpts="$newOpts ${i}.nop"
+ else
+ newOpts="$newOpts $i"
+ fi
+ done
+
+ echo_log "Creating zpool ${ZPOOLNAME} with $newOpts"
+ rc_halt "zpool create -m none -f ${ZPOOLNAME} ${newOpts}"
+
+ # Export the pool
+ rc_halt "zpool export ${ZPOOLNAME}"
+
+ # Destroy the gnop devices
+ for i in $gnopDev
+ do
+ rc_halt "gnop destroy ${i}.nop"
+ done
+
+ # And lastly re-import the pool
+ rc_halt "zpool import ${ZPOOLNAME}"
+ else
+ # Lets do our pseudo-4k drive
+ rc_halt "gnop create -S 4096 ${PART}${EXT}"
+
+ # No zpool options, create pool on single device
+ echo_log "Creating zpool ${ZPOOLNAME} on ${PART}${EXT}"
+ rc_halt "zpool create -m none -f ${ZPOOLNAME} ${PART}${EXT}.nop"
+
+ # Finish up the gnop 4k trickery
+ rc_halt "zpool export ${ZPOOLNAME}"
+ rc_halt "gnop destroy ${PART}${EXT}.nop"
+ rc_halt "zpool import ${ZPOOLNAME}"
+ fi
+
+ # Disable atime for this zfs partition, speed increase
+ rc_nohalt "zfs set atime=off ${ZPOOLNAME}"
+
+
+
+};
+
+# Runs newfs on all the partiions which we've setup with bsdlabel
+setup_filesystems()
+{
+
+ # Create the keydir
+ rm -rf ${GELIKEYDIR} >/dev/null 2>/dev/null
+ mkdir ${GELIKEYDIR}
+
+ # Lets go ahead and read through the saved partitions we created, and determine if we need to run
+ # newfs on any of them
+ for PART in `ls ${PARTDIR}`
+ do
+ PARTDEV="`echo $PART | sed 's|-|/|g'`"
+ PARTFS="`cat ${PARTDIR}/${PART} | cut -d '#' -f 1`"
+ PARTMNT="`cat ${PARTDIR}/${PART} | cut -d '#' -f 2`"
+ PARTENC="`cat ${PARTDIR}/${PART} | cut -d '#' -f 3`"
+ PARTLABEL="`cat ${PARTDIR}/${PART} | cut -d '#' -f 4`"
+ PARTGEOM="`cat ${PARTDIR}/${PART} | cut -d '#' -f 5`"
+ PARTXTRAOPTS="`cat ${PARTDIR}/${PART} | cut -d '#' -f 6`"
+ PARTIMAGE="`cat ${PARTDIR}/${PART} | cut -d '#' -f 7`"
+
+ if [ ! -e "${PARTDEV}" ] ; then
+ exit_err "ERROR: The partition ${PARTDEV} does not exist. Failure in bsdlabel?"
+ fi
+
+ # Make sure journaling isn't enabled on this device
+ if [ -e "${PARTDEV}.journal" ]
+ then
+ rc_nohalt "gjournal stop -f ${PARTDEV}.journal"
+ rc_nohalt "gjournal clear ${PARTDEV}"
+ fi
+
+ # Setup encryption if necessary
+ if [ "${PARTENC}" = "ON" -a "${PARTFS}" != "SWAP" ]
+ then
+ echo_log "Creating geli provider for ${PARTDEV}"
+
+ if [ -e "${PARTDIR}-enc/${PART}-encpass" ] ; then
+ # Using a passphrase
+ rc_halt "dd if=/dev/random of=${GELIKEYDIR}/${PART}.key bs=64 count=1"
+ rc_halt "geli init -J ${PARTDIR}-enc/${PART}-encpass ${PARTDEV}"
+ rc_halt "geli attach -j ${PARTDIR}-enc/${PART}-encpass ${PARTDEV}"
+ else
+ # No Encryption password, use key file
+ rc_halt "dd if=/dev/random of=${GELIKEYDIR}/${PART}.key bs=64 count=1"
+ rc_halt "geli init -b -s 4096 -P -K ${GELIKEYDIR}/${PART}.key ${PARTDEV}"
+ rc_halt "geli attach -p -k ${GELIKEYDIR}/${PART}.key ${PARTDEV}"
+
+ fi
+
+ EXT=".eli"
+ else
+ # No Encryption
+ EXT=""
+ fi
+
+ case ${PARTFS} in
+ UFS)
+ echo_log "NEWFS: ${PARTDEV} - ${PARTFS}"
+ sleep 2
+ rc_halt "newfs -t ${PARTXTRAOPTS} ${PARTDEV}${EXT}"
+ sleep 2
+ rc_halt "sync"
+ rc_halt "glabel label ${PARTLABEL} ${PARTDEV}${EXT}"
+ rc_halt "sync"
+
+ # Set flag that we've found a boot partition
+ if [ "$PARTMNT" = "/boot" -o "${PARTMNT}" = "/" ] ; then
+ HAVEBOOT="YES"
+ fi
+ sleep 2
+ ;;
+
+ UFS+S)
+ echo_log "NEWFS: ${PARTDEV} - ${PARTFS}"
+ sleep 2
+ rc_halt "newfs -t ${PARTXTRAOPTS} -U ${PARTDEV}${EXT}"
+ sleep 2
+ rc_halt "sync"
+ rc_halt "glabel label ${PARTLABEL} ${PARTDEV}${EXT}"
+ rc_halt "sync"
+ # Set flag that we've found a boot partition
+ if [ "$PARTMNT" = "/boot" -o "${PARTMNT}" = "/" ] ; then
+ HAVEBOOT="YES"
+ fi
+ sleep 2
+ ;;
+
+ UFS+SUJ)
+ echo_log "NEWFS: ${PARTDEV} - ${PARTFS}"
+ sleep 2
+ rc_halt "newfs -t ${PARTXTRAOPTS} -U ${PARTDEV}${EXT}"
+ sleep 2
+ rc_halt "sync"
+ rc_halt "tunefs -j enable ${PARTDEV}${EXT}"
+ sleep 2
+ rc_halt "sync"
+ rc_halt "glabel label ${PARTLABEL} ${PARTDEV}${EXT}"
+ rc_halt "sync"
+ # Set flag that we've found a boot partition
+ if [ "$PARTMNT" = "/boot" -o "${PARTMNT}" = "/" ] ; then
+ HAVEBOOT="YES"
+ fi
+ sleep 2
+ ;;
+
+
+ UFS+J)
+ echo_log "NEWFS: ${PARTDEV} - ${PARTFS}"
+ sleep 2
+ rc_halt "newfs ${PARTDEV}${EXT}"
+ sleep 2
+ rc_halt "gjournal label -f ${PARTDEV}${EXT}"
+ sleep 2
+ rc_halt "newfs ${PARTXTRAOPTS} -O 2 -J ${PARTDEV}${EXT}.journal"
+ sleep 2
+ rc_halt "sync"
+ rc_halt "glabel label ${PARTLABEL} ${PARTDEV}${EXT}.journal"
+ rc_halt "sync"
+ # Set flag that we've found a boot partition
+ if [ "$PARTMNT" = "/boot" -o "${PARTMNT}" = "/" ] ; then
+ HAVEBOOT="YES"
+ fi
+ sleep 2
+ ;;
+
+ ZFS)
+ echo_log "NEWFS: ${PARTDEV} - ${PARTFS}"
+ setup_zfs_filesystem "${PARTDEV}" "${PARTFS}" "${PARTMNT}" "${EXT}" "${PARTGEOM}" "${PARTXTRAOPTS}"
+ ;;
+
+ SWAP)
+ rc_halt "sync"
+ rc_halt "glabel label ${PARTLABEL} ${PARTDEV}${EXT}"
+ rc_halt "sync"
+ sleep 2
+ ;;
+
+ IMAGE)
+ write_image "${PARTIMAGE}" "${PARTDEV}"
+ sleep 2
+ ;;
+
+ *) exit_err "ERROR: Got unknown file-system type $PARTFS" ;;
+ esac
+
+ done
+};
diff --git a/usr.sbin/pc-sysinstall/backend/functions-packages.sh b/usr.sbin/pc-sysinstall/backend/functions-packages.sh
new file mode 100755
index 0000000..ee41928
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/functions-packages.sh
@@ -0,0 +1,411 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Functions which runs commands on the system
+
+. ${BACKEND}/functions.sh
+. ${BACKEND}/functions-parse.sh
+. ${BACKEND}/functions-ftp.sh
+
+
+get_package_index_by_ftp()
+{
+ local INDEX_FILE
+ local FTP_SERVER
+
+ FTP_SERVER="${1}"
+ INDEX_FILE="INDEX"
+ USE_BZIP2=0
+
+ if [ -f "/usr/bin/bzip2" ]
+ then
+ INDEX_FILE="${INDEX_FILE}.bz2"
+ USE_BZIP2=1
+ INDEX_PATH="${INDEXFILE}.bz2"
+ else
+ INDEX_PATH="${INDEXFILE}"
+ fi
+
+ fetch_file "${FTP_SERVER}/${INDEX_FILE}" "${INDEX_PATH}" "1"
+ if [ -f "${INDEX_PATH}" ] && [ "${USE_BZIP2}" -eq "1" ]
+ then
+ bzip2 -d "${INDEX_PATH}"
+ fi
+};
+
+get_package_index_by_fs()
+{
+ if [ "$INSTALLMEDIUM" = "local" ] ; then
+ INDEXFILE="${LOCALPATH}/packages/INDEX"
+ else
+ INDEXFILE="${CDMNT}/packages/INDEX"
+ fi
+};
+
+get_package_index_size()
+{
+ if [ -f "${INDEXFILE}" ]
+ then
+ SIZE=`ls -l ${INDEXFILE} | awk '{ print $5 }'`
+ else
+ get_ftp_mirror
+ FTPHOST="${VAL}"
+
+ FTPDIR="/pub/FreeBSD/releases/${FBSD_ARCH}/${FBSD_BRANCH}"
+ FTPPATH="ftp://${FTPHOST}${FTPDIR}/packages"
+
+ fetch -s "${FTPPATH}/INDEX.bz2"
+ fi
+};
+
+get_package_index()
+{
+ RES=0
+
+ if [ -z "${INSTALLMODE}" ]
+ then
+ get_ftp_mirror
+ FTPHOST="${VAL}"
+
+ FTPDIR="/pub/FreeBSD/releases/${FBSD_ARCH}/${FBSD_BRANCH}"
+ FTPPATH="ftp://${FTPHOST}${FTPDIR}/packages"
+
+ get_package_index_by_ftp "${FTPPATH}"
+
+ else
+
+ case "${INSTALLMEDIUM}" in
+ usb|dvd|local) get_package_index_by_fs ;;
+ ftp) get_value_from_cfg ftpHost
+ if [ -z "$VAL" ]; then
+ exit_err "ERROR: Install medium was set to ftp, but no ftpHost was provided!"
+ fi
+ FTPHOST="${VAL}"
+
+ get_value_from_cfg ftpDir
+ if [ -z "$VAL" ]; then
+ exit_err "ERROR: Install medium was set to ftp, but no ftpDir was provided!"
+ fi
+ FTPDIR="${VAL}"
+ FTPPATH="ftp://${FTPHOST}${FTPDIR}"
+ get_package_index_by_ftp "${FTPPATH}" ;;
+ sftp) ;;
+ *) RES=1 ;;
+ esac
+
+ fi
+
+ return ${RES}
+};
+
+parse_package_index()
+{
+ echo_log "Building package dep list.. Please wait.."
+ INDEX_FILE="${PKGDIR}/INDEX"
+
+ exec 3<&0
+ exec 0<"${INDEXFILE}"
+
+ while read -r line
+ do
+ PKGNAME=""
+ CATEGORY=""
+ PACKAGE=""
+ DESC=""
+ DEPS=""
+ i=0
+
+ SAVE_IFS="${IFS}"
+ IFS="|"
+
+ for part in ${line}
+ do
+ if [ ${i} -eq 0 ]
+ then
+ PKGNAME="${part}"
+
+ elif [ ${i} -eq 1 ]
+ then
+ PACKAGE=`basename "${part}"`
+
+ elif [ ${i} -eq 3 ]
+ then
+ DESC="${part}"
+
+ elif [ ${i} -eq 6 ]
+ then
+ CATEGORY=`echo "${part}" | cut -f1 -d' '`
+
+ elif [ ${i} -eq 8 ]
+ then
+ DEPS="${part}"
+ fi
+
+ i=$((i+1))
+ done
+
+ echo "${CATEGORY}|${PACKAGE}|${DESC}" >> "${INDEX_FILE}.parsed"
+ echo "${PACKAGE}|${PKGNAME}|${DEPS}" >> "${INDEX_FILE}.deps"
+
+ IFS="${SAVE_IFS}"
+ done
+
+ exec 0<&3
+};
+
+show_package_file()
+{
+ PKGFILE="${1}"
+
+ echo "Available Packages:"
+
+ exec 3<&0
+ exec 0<"${PKGFILE}"
+
+ while read -r line
+ do
+ CATEGORY=`echo "${line}" | cut -f1 -d'|'`
+ PACKAGE=`echo "${line}" | cut -f2 -d'|'`
+ DESC=`echo "${line}" | cut -f3 -d'|'`
+
+ echo "${CATEGORY}/${PACKAGE}:${DESC}"
+ done
+
+ exec 0<&3
+};
+
+show_packages_by_category()
+{
+ CATEGORY="${1}"
+ INDEX_FILE="${PKGDIR}/INDEX.parsed"
+ TMPFILE="/tmp/.pkg.cat"
+
+ grep "^${CATEGORY}|" "${INDEX_FILE}" > "${TMPFILE}"
+ show_package_file "${TMPFILE}"
+ rm "${TMPFILE}"
+};
+
+show_package_by_name()
+{
+ CATEGORY="${1}"
+ PACKAGE="${2}"
+ INDEX_FILE="${PKGDIR}/INDEX.parsed"
+ TMPFILE="/tmp/.pkg.cat.pak"
+
+ grep "^${CATEGORY}|${PACKAGE}" "${INDEX_FILE}" > "${TMPFILE}"
+ show_package_file "${TMPFILE}"
+ rm "${TMPFILE}"
+};
+
+show_packages()
+{
+ show_package_file "${PKGDIR}/INDEX.parsed"
+};
+
+get_package_dependencies()
+{
+ PACKAGE="${1}"
+ LONG="${2:-0}"
+ RES=0
+
+ INDEX_FILE="${PKGDIR}/INDEX.deps"
+ REGEX="^${PACKAGE}|"
+
+ if [ ${LONG} -ne 0 ]
+ then
+ REGEX="^.*|${PACKAGE}|"
+ fi
+
+ LINE=`grep "${REGEX}" "${INDEX_FILE}" 2>/dev/null`
+ DEPS=`echo "${LINE}"|cut -f3 -d'|'`
+
+ export VAL="${DEPS}"
+
+ if [ -z "${VAL}" ]
+ then
+ RES=1
+ fi
+
+ return ${RES}
+};
+
+get_package_name()
+{
+ PACKAGE="${1}"
+ RES=0
+ local PKGPTH
+
+ # If we are on a local medium, we can parse the Latest/ directory
+ if [ "${INSTALLMEDIUM}" != "ftp" ] ; then
+ case "${INSTALLMEDIUM}" in
+ usb|dvd) PKGPTH="${CDMNT}/packages" ;;
+ *) PKGPTH="${LOCALPATH}/packages" ;;
+ esac
+
+ # Check the /Latest dir for generic names, then look for specific version in All/
+ if [ -e "${PKGPTH}/Latest/${PACKAGE}.${PKGEXT}" ] ; then
+ NAME=`ls -al ${PKGPTH}/Latest/${PACKAGE}.${PKGEXT} 2>/dev/null | cut -d '>' -f 2 | rev | cut -f1 -d'/' | rev | tr -s ' '`
+ else
+ NAME=`ls -al ${PKGPTH}/All/${PACKAGE}.${PKGEXT} 2>/dev/null | cut -d '>' -f 2 | rev | cut -f1 -d'/' | rev | tr -s ' '`
+ fi
+ export VAL="${NAME}"
+ else
+ # Doing remote fetch, we we will look up, but some generic names like
+ # "perl" wont work, since we don't know the default version
+ INDEX_FILE="${PKGDIR}/INDEX.deps"
+ REGEX="^${PACKAGE}|"
+
+ LINE=`grep "${REGEX}" "${INDEX_FILE}" 2>/dev/null`
+ NAME=`echo "${LINE}"|cut -f2 -d'|'`
+
+ export VAL="${NAME}"
+ fi
+
+ if [ -z "${VAL}" ]
+ then
+ RES=1
+ fi
+ return ${RES}
+};
+
+get_package_short_name()
+{
+ PACKAGE="${1}"
+ RES=0
+
+ INDEX_FILE="${PKGDIR}/INDEX.deps"
+ REGEX="^.*|${PACKAGE}|"
+
+ LINE=`grep "${REGEX}" "${INDEX_FILE}" 2>/dev/null`
+ NAME=`echo "${LINE}"|cut -f1 -d'|'`
+
+ export VAL="${NAME}"
+
+ if [ -z "${VAL}" ]
+ then
+ RES=1
+ fi
+
+ return ${RES}
+};
+
+get_package_category()
+{
+ PACKAGE="${1}"
+ INDEX_FILE="${PKGDIR}/INDEX.parsed"
+ RES=0
+
+ LINE=`grep "|${PACKAGE}|" "${INDEX_FILE}" 2>/dev/null`
+ NAME=`echo "${LINE}"|cut -f1 -d'|'`
+
+ export VAL="${NAME}"
+
+ if [ -z "${VAL}" ]
+ then
+ RES=1
+ fi
+
+ return ${RES}
+};
+
+fetch_package_by_ftp()
+{
+ CATEGORY="${1}"
+ PACKAGE="${2}"
+ SAVEDIR="${3}"
+
+ get_value_from_cfg ftpHost
+ if [ -z "$VAL" ]
+ then
+ exit_err "ERROR: Install medium was set to ftp, but no ftpHost was provided!"
+ fi
+ FTPHOST="${VAL}"
+
+ get_value_from_cfg ftpDir
+ if [ -z "$VAL" ]
+ then
+ exit_err "ERROR: Install medium was set to ftp, but no ftpDir was provided!"
+ fi
+ FTPDIR="${VAL}"
+
+ PACKAGE="${PACKAGE}.${PKGEXT}"
+ FTP_SERVER="ftp://${FTPHOST}${FTPDIR}"
+
+ if [ ! -f "${SAVEDIR}/${PACKAGE}" ]
+ then
+ PKGPATH="${CATEGORY}/${PACKAGE}"
+ FTP_PATH="${FTP_HOST}/packages/${PKGPATH}"
+ fetch_file "${FTP_PATH}" "${SAVEDIR}/" "0"
+ fi
+};
+
+fetch_package()
+{
+ CATEGORY="${1}"
+ PACKAGE="${2}"
+ SAVEDIR="${3}"
+
+ # Fetch package, but skip if installing from local media
+ case "${INSTALLMEDIUM}" in
+ usb|dvd|local) return ;;
+ ftp) fetch_package_by_ftp "${CATEGORY}" "${PACKAGE}" "${SAVEDIR}" ;;
+ sftp) ;;
+ esac
+};
+
+bootstrap_pkgng()
+{
+ # Check if we need to boot-strap pkgng
+ if run_chroot_cmd "which pkg-static" >/dev/null 2>/dev/null
+ then
+ return
+ fi
+ local PKGPTH
+
+ # Ok, lets boot-strap this sucker
+ echo_log "Bootstraping pkgng.."
+ fetch_package "Latest" "pkg" "${PKGDLDIR}"
+
+ # Figure out real location of "pkg" package
+ case "${INSTALLMEDIUM}" in
+ usb|dvd|local) PKGPTH="${PKGTMPDIR}/Latest/pkg.${PKGEXT}" ;;
+ *) PKGPTH="${PKGTMPDIR}/pkg.${PKGEXT}" ;;
+ esac
+ rc_halt "pkg -c ${FSMNT} add ${PKGPTH}" ; run_chroot_cmd "pkg2ng"
+}
+
+get_package_location()
+{
+ case "${INSTALLMEDIUM}" in
+ usb|dvd) rc_halt "mount_nullfs ${CDMNT}/packages ${FSMNT}${PKGTMPDIR}"
+ PKGDLDIR="${FSMNT}${PKGTMPDIR}/All" ;;
+ local) rc_halt "mount_nullfs ${LOCALPATH}/packages ${FSMNT}${PKGTMPDIR}"
+ PKGDLDIR="${FSMNT}${PKGTMPDIR}/All" ;;
+ *) PKGDLDIR="${FSMNT}${PKGTMPDIR}" ;;
+ esac
+ export PKGDLDIR
+}
diff --git a/usr.sbin/pc-sysinstall/backend/functions-parse.sh b/usr.sbin/pc-sysinstall/backend/functions-parse.sh
new file mode 100755
index 0000000..fb7cdd2
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/functions-parse.sh
@@ -0,0 +1,229 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# functions.sh
+# Library of functions which pc-sysinstall may call upon for parsing the config
+
+# which gets the value of a setting in the provided line
+get_value_from_string()
+{
+ if [ -n "${1}" ]
+ then
+ export VAL="`echo ${1} | cut -d '=' -f 2-`"
+ else
+ echo "Error: Did we forgot to supply a string to parse?"
+ exit 1
+ fi
+};
+
+# Get the value from the cfg file including spaces
+get_value_from_cfg_with_spaces()
+{
+ if [ -n "${1}" ]
+ then
+ export VAL="`grep ^${1}= ${CFGF} | head -n 1 | cut -d '=' -f 2-`"
+ else
+ exit_err "Error: Did we forgot to supply a setting to grab?"
+ fi
+};
+
+
+# Get the value from the cfg file
+get_value_from_cfg()
+{
+ if [ -n "${1}" ]
+ then
+ export VAL=`grep "^${1}=" ${CFGF} | head -n 1 | cut -d '=' -f 2- | tr -d ' '`
+ else
+ exit_err "Error: Did we forgot to supply a setting to grab?"
+ fi
+};
+
+# Checks the value of a setting in the provided line with supplied possibilities
+# 1 = setting we are checking, 2 = list of valid values
+if_check_value_exists()
+{
+ if [ -n "${1}" -a -n "${2}" ]
+ then
+ # Get the first occurrence of the setting from the config, strip out whitespace
+
+ VAL=`grep "^${1}" ${CFGF} | head -n 1 | cut -d '=' -f 2- | tr -d ' '`
+ if [ -z "${VAL}" ]
+ then
+ # This value doesn't exist, lets return
+ return 0
+ fi
+
+
+ VALID="1"
+ for i in ${2}
+ do
+ VAL=`echo "$VAL"|tr A-Z a-z`
+ if [ "$VAL" = "${i}" ]
+ then
+ VALID="0"
+ fi
+ done
+ if [ "$VALID" = "1" ]
+ then
+ exit_err "Error: ${1} is set to unknown value $VAL"
+ fi
+ else
+ exit_err "Error: Did we forgot to supply a string to parse and setting to grab?"
+ fi
+};
+
+# Checks the value of a setting in the provided line with supplied possibilities
+# 1 = setting we are checking, 2 = list of valid values
+check_value()
+{
+ if [ -n "${1}" -a -n "${2}" ]
+ then
+ # Get the first occurrence of the setting from the config, strip out whitespace
+ VAL=`grep "^${1}" ${CFGF} | head -n 1 | cut -d '=' -f 2- | tr -d ' '`
+ VALID="1"
+ for i in ${2}
+ do
+ if [ "$VAL" = "${i}" ]
+ then
+ VALID="0"
+ fi
+ done
+ if [ "$VALID" = "1" ]
+ then
+ exit_err "Error: ${1} is set to unknown value $VAL"
+ fi
+ else
+ exit_err "Error: Did we forgot to supply a string to parse and setting to grab?"
+ fi
+};
+
+# Checks for the presence of the supplied arguments in the config file
+# 1 = values to confirm exist
+file_sanity_check()
+{
+ if [ -n "$CFGF" -a -n "$1" ]
+ then
+ for i in $1
+ do
+ grep -q "^${i}=" $CFGF 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ LN=`grep "^${i}=" ${CFGF} | head -n 1 | cut -d '=' -f 2- | tr -d ' '`
+ if [ -z "${LN}" ]
+ then
+ echo "Error: Config fails sanity test! ${i}= is empty"
+ exit 1
+ fi
+ else
+ echo "Error: Config fails sanity test! Missing ${i}="
+ exit 1
+ fi
+ done
+ else
+ echo "Error: Missing config file, and / or values to sanity check for!"
+ exit 1
+ fi
+};
+
+
+# Function which merges the contents of a new config into the specified old one
+# Only works with <val>= type configurations
+merge_config()
+{
+ OLDCFG="${1}"
+ NEWCFG="${2}"
+ FINALCFG="${3}"
+
+ # Copy our oldcfg to the new one, which will be used as basis
+ cp ${OLDCFG} ${FINALCFG}
+
+ # Remove blank lines from new file
+ cat ${NEWCFG} | sed '/^$/d' > ${FINALCFG}.tmp
+
+ # Set our marker if we've found any
+ FOUNDMERGE="NO"
+
+ while read newline
+ do
+ echo ${newline} | grep -q "^#" 2>/dev/null
+ if [ $? -ne 0 ] ; then
+ VAL="`echo ${newline} | cut -d '=' -f 1`"
+ cat ${OLDCFG} | grep -q ${VAL} 2>/dev/null
+ if [ $? -ne 0 ] ; then
+ if [ "${FOUNDMERGE}" = "NO" ] ; then
+ echo "" >> ${FINALCFG}
+ echo "# Auto-merged values from newer ${NEWCFG}" >> ${FINALCFG}
+ FOUNDMERGE="YES"
+ fi
+ echo "${newline}" >> ${FINALCFG}
+ fi
+ fi
+ done < ${FINALCFG}.tmp
+ rm ${FINALCFG}.tmp
+
+};
+
+# Loop to check for a specified mount-point in a list
+check_for_mount()
+{
+ MNTS="${1}"
+ FINDMNT="${2}"
+
+ # Check if we found a valid root partition
+ for CHECKMNT in `echo ${MNTS} | sed 's|,| |g'`
+ do
+ if [ "${CHECKMNT}" = "${FINDMNT}" ] ; then
+ return 0
+ fi
+ done
+
+ return 1
+};
+
+# Function which returns the next line in the specified config file
+get_next_cfg_line()
+{
+ CURFILE="$1"
+ CURLINE="$2"
+
+ FOUND="1"
+
+ while read line
+ do
+ if [ "$FOUND" = "0" ] ; then
+ export VAL="$line"
+ return
+ fi
+ if [ "$line" = "${CURLINE}" ] ; then
+ FOUND="0"
+ fi
+ done <${CURFILE}
+
+ # Got here, couldn't find this line or at end of file, set VAL to ""
+ export VAL=""
+};
diff --git a/usr.sbin/pc-sysinstall/backend/functions-runcommands.sh b/usr.sbin/pc-sysinstall/backend/functions-runcommands.sh
new file mode 100755
index 0000000..b364823
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/functions-runcommands.sh
@@ -0,0 +1,110 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Functions which runs commands on the system
+
+. ${BACKEND}/functions.sh
+. ${BACKEND}/functions-parse.sh
+
+run_chroot_cmd()
+{
+ CMD="$@"
+ echo_log "Running chroot command: ${CMD}"
+ echo "$CMD" >${FSMNT}/.runcmd.sh
+ chmod 755 ${FSMNT}/.runcmd.sh
+ chroot ${FSMNT} sh /.runcmd.sh
+ RES=$?
+
+ rm ${FSMNT}/.runcmd.sh
+ return ${RES}
+};
+
+run_chroot_script()
+{
+ SCRIPT="$@"
+ SBASE=`basename $SCRIPT`
+
+ cp ${SCRIPT} ${FSMNT}/.$SBASE
+ chmod 755 ${FSMNT}/.${SBASE}
+
+ echo_log "Running chroot script: ${SCRIPT}"
+ chroot ${FSMNT} /.${SBASE}
+ RES=$?
+
+ rm ${FSMNT}/.${SBASE}
+ return ${RES}
+};
+
+
+run_ext_cmd()
+{
+ CMD="$@"
+ # Make sure to export FSMNT, in case cmd needs it
+ export FSMNT
+ echo_log "Running external command: ${CMD}"
+ echo "${CMD}"> ${TMPDIR}/.runcmd.sh
+ chmod 755 ${TMPDIR}/.runcmd.sh
+ sh ${TMPDIR}/.runcmd.sh
+ RES=$?
+
+ rm ${TMPDIR}/.runcmd.sh
+ return ${RES}
+};
+
+
+# Starts the user setup
+run_commands()
+{
+ while read line
+ do
+ # Check if we need to run any chroot command
+ echo $line | grep -q ^runCommand= 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ get_value_from_string "$line"
+ run_chroot_cmd "$VAL"
+ fi
+
+ # Check if we need to run any chroot script
+ echo $line | grep -q ^runScript= 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ get_value_from_string "$line"
+ run_chroot_script "$VAL"
+ fi
+
+ # Check if we need to run any chroot command
+ echo $line | grep -q ^runExtCommand= 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ get_value_from_string "$line"
+ run_ext_cmd "$VAL"
+ fi
+
+ done <${CFGF}
+
+};
diff --git a/usr.sbin/pc-sysinstall/backend/functions-unmount.sh b/usr.sbin/pc-sysinstall/backend/functions-unmount.sh
new file mode 100755
index 0000000..a61a5e6
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/functions-unmount.sh
@@ -0,0 +1,210 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Functions which unmount all mounted disk filesystems
+
+# Unmount all mounted partitions under specified dir
+umount_all_dir()
+{
+ _udir="$1"
+ _umntdirs=`mount | sort -r | grep "on $_udir" | cut -d ' ' -f 3`
+ for _ud in $_umntdirs
+ do
+ umount -f ${_ud}
+ done
+}
+
+# Script that adds our gmirror devices for syncing
+start_gmirror_sync()
+{
+
+ cd ${MIRRORCFGDIR}
+ for DISK in `ls ${MIRRORCFGDIR}`
+ do
+ MIRRORDISK="`cat ${DISK} | cut -d ':' -f 1`"
+ MIRRORBAL="`cat ${DISK} | cut -d ':' -f 2`"
+ MIRRORNAME="`cat ${DISK} | cut -d ':' -f 3`"
+
+ # Start the mirroring service
+ rc_nohalt "gmirror forget ${MIRRORNAME}"
+ rc_halt "gmirror insert ${MIRRORNAME} ${MIRRORDISK}"
+
+ done
+
+};
+
+# Unmounts all our mounted file-systems
+unmount_all_filesystems()
+{
+ # Copy the logfile to disk before we unmount
+ cp ${LOGOUT} ${FSMNT}/root/pc-sysinstall.log
+ cd /
+
+ # Start by unmounting any ZFS partitions
+ zfs_cleanup_unmount
+
+ # Lets read our partition list, and unmount each
+ ##################################################################
+ for PART in `ls ${PARTDIR}`
+ do
+ PARTDEV=`echo $PART | sed 's|-|/|g'`
+ PARTFS="`cat ${PARTDIR}/${PART} | cut -d '#' -f 1`"
+ PARTMNT="`cat ${PARTDIR}/${PART} | cut -d '#' -f 2`"
+ PARTENC="`cat ${PARTDIR}/${PART} | cut -d '#' -f 3`"
+ PARTLABEL="`cat ${PARTDIR}/${PART} | cut -d '#' -f 4`"
+
+ if [ "${PARTENC}" = "ON" ]
+ then
+ EXT=".eli"
+ else
+ EXT=""
+ fi
+
+ if [ "${PARTFS}" = "SWAP" ]
+ then
+ rc_nohalt "swapoff ${PARTDEV}${EXT}"
+ fi
+
+ # Check if we've found "/", and unmount that last
+ if [ "$PARTMNT" != "/" -a "${PARTMNT}" != "none" -a "${PARTFS}" != "ZFS" ]
+ then
+ rc_halt "umount -f ${PARTDEV}${EXT}"
+
+ # Re-check if we are missing a label for this device and create it again if so
+ if [ ! -e "/dev/label/${PARTLABEL}" ]
+ then
+ case ${PARTFS} in
+ UFS) glabel label ${PARTLABEL} ${PARTDEV}${EXT} ;;
+ UFS+S) glabel label ${PARTLABEL} ${PARTDEV}${EXT} ;;
+ UFS+SUJ) glabel label ${PARTLABEL} ${PARTDEV}${EXT} ;;
+ UFS+J) glabel label ${PARTLABEL} ${PARTDEV}${EXT}.journal ;;
+ *) ;;
+ esac
+ fi
+ fi
+
+ # Check if we've found "/" and make sure the label exists
+ if [ "$PARTMNT" = "/" -a "${PARTFS}" != "ZFS" ]
+ then
+ if [ ! -e "/dev/label/${PARTLABEL}" ]
+ then
+ case ${PARTFS} in
+ UFS) ROOTRELABEL="glabel label ${PARTLABEL} ${PARTDEV}${EXT}" ;;
+ UFS+S) ROOTRELABEL="glabel label ${PARTLABEL} ${PARTDEV}${EXT}" ;;
+ UFS+SUJ) ROOTRELABEL="glabel label ${PARTLABEL} ${PARTDEV}${EXT}" ;;
+ UFS+J) ROOTRELABEL="glabel label ${PARTLABEL} ${PARTDEV}${EXT}.journal" ;;
+ *) ;;
+ esac
+ fi
+ fi
+ done
+
+ # Last lets the /mnt partition
+ #########################################################
+ rc_nohalt "umount -f ${FSMNT}"
+
+ # If are using a ZFS on "/" set it to legacy
+ if [ ! -z "${FOUNDZFSROOT}" ]
+ then
+ rc_halt "zfs set mountpoint=legacy ${FOUNDZFSROOT}"
+ fi
+
+ # If we need to relabel "/" do it now
+ if [ ! -z "${ROOTRELABEL}" ]
+ then
+ ${ROOTRELABEL}
+ fi
+
+ # Unmount our CDMNT
+ rc_nohalt "umount -f ${CDMNT}" >/dev/null 2>/dev/null
+
+ # Check if we need to run any gmirror syncing
+ ls ${MIRRORCFGDIR}/* >/dev/null 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ # Lets start syncing now
+ start_gmirror_sync
+ fi
+
+};
+
+# Unmounts any filesystems after a failure
+unmount_all_filesystems_failure()
+{
+ cd /
+
+ # if we did a fresh install, start unmounting
+ if [ "${INSTALLMODE}" = "fresh" ]
+ then
+
+ # Lets read our partition list, and unmount each
+ ##################################################################
+ if [ -d "${PARTDIR}" ]
+ then
+ for PART in `ls ${PARTDIR}`
+ do
+ PARTDEV=`echo $PART | sed 's|-|/|g'`
+ PARTFS="`cat ${PARTDIR}/${PART} | cut -d '#' -f 1`"
+ PARTMNT="`cat ${PARTDIR}/${PART} | cut -d '#' -f 2`"
+ PARTENC="`cat ${PARTDIR}/${PART} | cut -d '#' -f 3`"
+
+ if [ "${PARTFS}" = "SWAP" ]
+ then
+ if [ "${PARTENC}" = "ON" ]
+ then
+ swapoff ${PARTDEV}.eli >/dev/null 2>/dev/null
+ else
+ swapoff ${PARTDEV} >/dev/null 2>/dev/null
+ fi
+ fi
+
+ # Check if we've found "/" again, don't need to mount it twice
+ if [ "$PARTMNT" != "/" -a "${PARTMNT}" != "none" -a "${PARTFS}" != "ZFS" ]
+ then
+ umount -f ${PARTDEV} >/dev/null 2>/dev/null
+ umount -f ${FSMNT}${PARTMNT} >/dev/null 2>/dev/null
+ fi
+ done
+
+ # Last lets the /mnt partition
+ #########################################################
+ umount -f ${FSMNT} >/dev/null 2>/dev/null
+
+ fi
+ else
+ # We are doing a upgrade, try unmounting any of these filesystems
+ chroot ${FSMNT} /sbin/umount -a >/dev/null 2>/dev/null
+ umount -f ${FSMNT}/usr >/dev/null 2>/dev/null
+ umount -f ${FSMNT}/dev >/dev/null 2>/dev/null
+ umount -f ${FSMNT} >/dev/null 2>/dev/null
+ sh ${TMPDIR}/.upgrade-unmount >/dev/null 2>/dev/null
+ fi
+
+ # Unmount our CDMNT
+ umount ${CDMNT} >/dev/null 2>/dev/null
+
+};
diff --git a/usr.sbin/pc-sysinstall/backend/functions-upgrade.sh b/usr.sbin/pc-sysinstall/backend/functions-upgrade.sh
new file mode 100755
index 0000000..f191e6f
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/functions-upgrade.sh
@@ -0,0 +1,247 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Functions which perform the mounting / unmount for upgrades
+
+. ${PROGDIR}/backend/functions-unmount.sh
+
+mount_target_slice()
+{
+ MPART="${1}"
+
+ # Import any zpools
+ zpool import -o altroot=${FSMNT} -a
+ umount_all_dir "${FSMNT}"
+
+ # Set a variable of files we want to make backups of before doing upgrade
+ BKFILES="/etc/rc.conf /boot/loader.conf"
+
+ if [ -e "/dev/${MPART}" ] ; then
+ rc_nohalt "mount /dev/${MPART} ${FSMNT}"
+ if [ $? -ne 0 ] ; then
+ # Check if we have ZFS tank name
+ rc_halt "mount -t zfs ${MPART} ${FSMNT}"
+ fi
+ else
+ # Check if we have ZFS tank name
+ rc_halt "mount -t zfs ${MPART} ${FSMNT}"
+ fi
+
+ # Mount devfs in chroot
+ mount -t devfs devfs ${FSMNT}/dev
+
+ # Check if we have any ZFS partitions to mount
+ zfs mount -a
+
+ # Mount all the fstab goodies on disk
+ chroot ${FSMNT} /sbin/mount -a -t nolinprocfs >>${LOGOUT} 2>>${LOGOUT}
+ chroot ${FSMNT} umount /proc >/dev/null 2>/dev/null
+ chroot ${FSMNT} umount /compat/linux/proc >/dev/null 2>/dev/null
+
+ # Now before we start the upgrade, make sure we set our noschg flags
+ echo_log "Cleaning up old filesystem... Please wait..."
+ rc_halt "chflags -R noschg ${FSMNT}"
+
+ # Make backup copies of some files
+ for i in ${BKFILES}
+ do
+ cp ${FSMNT}${i} ${FSMNT}${i}.preUpgrade >/dev/null 2>/dev/null
+ done
+
+ # Remove some old dirs
+ rm -rf ${FSMNT}/etc/rc.d >/dev/null 2>/dev/null
+
+ # If we are doing PC-BSD install, lets cleanup old pkgs on disk
+ if [ "$INSTALLTYPE" != "FreeBSD" ]
+ then
+ echo_log "Removing old packages, this may take a while... Please wait..."
+ echo '#!/bin/sh
+for i in `pkg_info -aE`
+do
+ echo "Uninstalling package: ${i}"
+ pkg_delete -f ${i} >/dev/null 2>/dev/null
+done
+' >${FSMNT}/.cleanPkgs.sh
+ chmod 755 ${FSMNT}/.cleanPkgs.sh
+ chroot ${FSMNT} /.cleanPkgs.sh
+ rm ${FSMNT}/.cleanPkgs.sh
+ run_chroot_cmd "pkg_delete -f \*" >/dev/null 2>/dev/null
+ run_chroot_cmd "rm -rf /usr/PCBSD" >/dev/null 2>/dev/null
+ run_chroot_cmd "rm -rf /PCBSD" >/dev/null 2>/dev/null
+ run_chroot_cmd "rm -rf /var/db/pkgs" >/dev/null 2>/dev/null
+ run_chroot_cmd "rm -rf /usr/local32" >/dev/null 2>/dev/null
+ run_chroot_cmd "rm -rf /usr/sbin" >/dev/null 2>/dev/null
+ run_chroot_cmd "rm -rf /usr/lib" >/dev/null 2>/dev/null
+ run_chroot_cmd "rm -rf /usr/bin" >/dev/null 2>/dev/null
+ run_chroot_cmd "rm -rf /boot/kernel" >/dev/null 2>/dev/null
+ run_chroot_cmd "rm -rf /sbin" >/dev/null 2>/dev/null
+ run_chroot_cmd "rm -rf /bin" >/dev/null 2>/dev/null
+ run_chroot_cmd "rm -rf /lib" >/dev/null 2>/dev/null
+ run_chroot_cmd "rm -rf /libexec" >/dev/null 2>/dev/null
+ fi
+
+};
+
+# Mount the target upgrade partitions
+mount_upgrade()
+{
+
+ # Make sure we remove the old upgrade-mount script
+ rm -rf ${TMPDIR}/.upgrade-unmount >/dev/null 2>/dev/null
+
+ # We are ready to start mounting, lets read the config and do it
+ while read line
+ do
+ echo $line | grep -q "^disk0=" 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+
+ # Found a disk= entry, lets get the disk we are working on
+ get_value_from_string "${line}"
+ strip_white_space "$VAL"
+ DISK="$VAL"
+ fi
+
+ echo $line | grep -q "^commitDiskPart" 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ # Found our flag to commit this disk setup / lets do sanity check and do it
+ if [ -n "${DISK}" ]
+ then
+
+ # Start mounting this slice
+ mount_target_slice "${DISK}"
+
+ # Increment our disk counter to look for next disk and unset
+ unset DISK
+ break
+ else
+ exit_err "ERROR: commitDiskPart was called without procceding disk<num>= and partition= entries!!!"
+ fi
+ fi
+
+ done <${CFGF}
+
+};
+
+copy_skel_files_upgrade()
+{
+
+ # Now make sure we fix any user profile scripts, which cause problems from 7.x->8.x
+ echo '#!/bin/sh
+
+cd /home
+for i in `ls`
+do
+
+ # Backup the old profile dirs
+ if [ -d "${i}" ]
+ then
+ mv /home/${i}/.kde4 /home/${i}/.kde4.preUpgrade >/dev/null 2>/dev/null
+ mv /home/${i}/.kde /home/${i}/.kde.preUpgrade >/dev/null 2>/dev/null
+ mv /home/${i}/.fluxbox /home/${i}/.fluxbox.preUpgrade >/dev/null 2>/dev/null
+
+ # Copy over the skel directories
+ tar cv --exclude "./dot.*" -f - -C /usr/share/skel . 2>/dev/null | tar xvf - -C /home/${i} 2>/dev/null
+
+ for j in `ls /usr/share/skel/dot*`
+ do
+ dname=`echo ${j} | sed s/dot//`
+ cp /usr/share/skel/${j} /home/${i}/${dname}
+ done
+
+ chown -R ${i}:${i} /home/${i}
+ fi
+
+done
+' >${FSMNT}/.fixUserProfile.sh
+ chmod 755 ${FSMNT}/.fixUserProfile.sh
+ chroot ${FSMNT} /.fixUserProfile.sh >/dev/null 2>/dev/null
+ rm ${FSMNT}/.fixUserProfile.sh
+
+
+
+ # if the user wants to keep their original .kde4 profile
+ ###########################################################################
+ get_value_from_cfg "upgradeKeepDesktopProfile"
+ if [ "$VAL" = "YES" -o "$VAL" = "yes" ] ; then
+ echo '#!/bin/sh
+ cd /home
+for i in `ls`
+do
+ # Import the old config again
+ if [ -d "${i}/.kde4.preUpgrade" ]
+ then
+ # Copy over the skel directories
+ tar cv -f - -C /home/${i}/.kde4.preUpgrade . 2>/dev/null | tar xvf - -C /home/${i}/.kde4 2>/dev/null
+ chown -R ${i}:${i} /home/${i}/.kde4
+ fi
+done
+' >${FSMNT}/.fixUserProfile.sh
+ chmod 755 ${FSMNT}/.fixUserProfile.sh
+ chroot ${FSMNT} /.fixUserProfile.sh >/dev/null 2>/dev/null
+ rm ${FSMNT}/.fixUserProfile.sh
+
+ fi
+
+};
+
+# Function which merges some configuration files with the new defaults
+merge_old_configs()
+{
+
+ # Merge the loader.conf with old
+ cp ${FSMNT}/boot/loader.conf ${FSMNT}/boot/loader.conf.new
+ merge_config "${FSMNT}/boot/loader.conf.preUpgrade" "${FSMNT}/boot/loader.conf.new" "${FSMNT}/boot/loader.conf"
+ rm ${FSMNT}/boot/loader.conf.new
+
+ # Merge the rc.conf with old
+ cp ${FSMNT}/etc/rc.conf ${FSMNT}/etc/rc.conf.new
+ merge_config "${FSMNT}/etc/rc.conf.preUpgrade" "${FSMNT}/etc/rc.conf.new" "${FSMNT}/etc/rc.conf"
+ rm ${FSMNT}/etc/rc.conf.new
+
+};
+
+# Function which unmounts all the mounted file-systems
+unmount_upgrade()
+{
+
+ # If on PC-BSD, make sure we copy any fixed skel files
+ if [ "$INSTALLTYPE" != "FreeBSD" ] ; then
+ copy_skel_files_upgrade
+ fi
+
+ cd /
+
+ # Unmount FS
+ umount_all_dir "${FSMNT}"
+
+ # Run our saved unmount script for these file-systems
+ rc_nohalt "umount -f ${FSMNT}"
+
+ umount ${CDMNT}
+};
diff --git a/usr.sbin/pc-sysinstall/backend/functions-users.sh b/usr.sbin/pc-sysinstall/backend/functions-users.sh
new file mode 100755
index 0000000..bf1a72f
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/functions-users.sh
@@ -0,0 +1,186 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Functions which runs commands on the system
+
+. ${BACKEND}/functions.sh
+. ${BACKEND}/functions-parse.sh
+
+
+# Function which checks and sets up auto-login for a user if specified
+check_autologin()
+{
+ get_value_from_cfg autoLoginUser
+ if [ -n "${VAL}" -a "${INSTALLTYPE}" = "PCBSD" ]
+ then
+ AUTOU="${VAL}"
+ # Add the auto-login user line
+ sed -i.bak "s/AutoLoginUser=/AutoLoginUser=${AUTOU}/g" ${FSMNT}/usr/local/kde4/share/config/kdm/kdmrc
+
+ # Add the auto-login user line
+ sed -i.bak "s/AutoLoginEnable=false/AutoLoginEnable=true/g" ${FSMNT}/usr/local/kde4/share/config/kdm/kdmrc
+
+ fi
+};
+
+# Function which actually runs the adduser command on the filesystem
+add_user()
+{
+ ARGS="${1}"
+
+ if [ -e "${FSMNT}/.tmpPass" ]
+ then
+ # Add a user with a supplied password
+ run_chroot_cmd "cat /.tmpPass | pw useradd ${ARGS}"
+ rc_halt "rm ${FSMNT}/.tmpPass"
+ else
+ # Add a user with no password
+ run_chroot_cmd "cat /.tmpPass | pw useradd ${ARGS}"
+ fi
+
+};
+
+# Function which reads in the config, and adds any users specified
+setup_users()
+{
+
+ # We are ready to start setting up the users, lets read the config
+ while read line
+ do
+
+ echo $line | grep -q "^userName=" 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ get_value_from_string "${line}"
+ USERNAME="$VAL"
+ fi
+
+ echo $line | grep -q "^userComment=" 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ get_value_from_string "${line}"
+ USERCOMMENT="$VAL"
+ fi
+
+ echo $line | grep -q "^userPass=" 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ get_value_from_string "${line}"
+ USERPASS="$VAL"
+ fi
+
+ echo $line | grep -q "^userEncPass=" 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ get_value_from_string "${line}"
+ USERENCPASS="$VAL"
+ fi
+
+ echo $line | grep -q "^userShell=" 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ get_value_from_string "${line}"
+ strip_white_space "$VAL"
+ USERSHELL="$VAL"
+ fi
+
+ echo $line | grep -q "^userHome=" 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ get_value_from_string "${line}"
+ USERHOME="$VAL"
+ fi
+
+ echo $line | grep -q "^userGroups=" 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ get_value_from_string "${line}"
+ USERGROUPS="$VAL"
+ fi
+
+
+ echo $line | grep -q "^commitUser" 2>/dev/null
+ if [ $? -eq 0 ]
+ then
+ # Found our flag to commit this user, lets check and do it
+ if [ -n "${USERNAME}" ]
+ then
+
+ # Now add this user to the system, by building our args list
+ ARGS="-n ${USERNAME}"
+
+ if [ -n "${USERCOMMENT}" ]
+ then
+ ARGS="${ARGS} -c \"${USERCOMMENT}\""
+ fi
+
+ if [ -n "${USERPASS}" ]
+ then
+ ARGS="${ARGS} -h 0"
+ echo "${USERPASS}" >${FSMNT}/.tmpPass
+ elif [ -n "${USERENCPASS}" ]
+ then
+ ARGS="${ARGS} -H 0"
+ echo "${USERENCPASS}" >${FSMNT}/.tmpPass
+ else
+ ARGS="${ARGS} -h -"
+ rm ${FSMNT}/.tmpPass 2>/dev/null 2>/dev/null
+ fi
+
+ if [ -n "${USERSHELL}" ]
+ then
+ ARGS="${ARGS} -s \"${USERSHELL}\""
+ else
+ ARGS="${ARGS} -s \"/nonexistant\""
+ fi
+
+ if [ -n "${USERHOME}" ]
+ then
+ ARGS="${ARGS} -m -d \"${USERHOME}\""
+ fi
+
+ if [ -n "${USERGROUPS}" ]
+ then
+ ARGS="${ARGS} -G \"${USERGROUPS}\""
+ fi
+
+ add_user "${ARGS}"
+
+ # Unset our vars before looking for any more users
+ unset USERNAME USERCOMMENT USERPASS USERENCPASS USERSHELL USERHOME USERGROUPS
+ else
+ exit_err "ERROR: commitUser was called without any userName= entry!!!"
+ fi
+ fi
+
+ done <${CFGF}
+
+
+ # Check if we need to enable a user to auto-login to the desktop
+ check_autologin
+
+};
diff --git a/usr.sbin/pc-sysinstall/backend/functions.sh b/usr.sbin/pc-sysinstall/backend/functions.sh
new file mode 100755
index 0000000..a2d039b
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/functions.sh
@@ -0,0 +1,540 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# functions.sh
+# Library of functions which pc-sysinstall may call upon
+
+# Function which displays the help-index file
+display_help()
+{
+ if [ -e "${PROGDIR}/doc/help-index" ]
+ then
+ cat ${PROGDIR}/doc/help-index
+ else
+ echo "Error: ${PROGDIR}/doc/help-index not found"
+ exit 1
+ fi
+};
+
+# Function which displays the help for a specified command
+display_command_help()
+{
+ if [ -z "$1" ]
+ then
+ echo "Error: No command specified to display help for"
+ exit 1
+ fi
+
+ if [ -e "${PROGDIR}/doc/help-${1}" ]
+ then
+ cat ${PROGDIR}/doc/help-${1}
+ else
+ echo "Error: ${PROGDIR}/doc/help-${1} not found"
+ exit 1
+ fi
+};
+
+# Function to convert bytes to megabytes
+convert_byte_to_megabyte()
+{
+ if [ -z "${1}" ]
+ then
+ echo "Error: No bytes specified!"
+ exit 1
+ fi
+
+ expr -e ${1} / 1048576
+};
+
+# Function to convert blocks to megabytes
+convert_blocks_to_megabyte()
+{
+ if [ -z "${1}" ] ; then
+ echo "Error: No blocks specified!"
+ exit 1
+ fi
+
+ expr -e ${1} / 2048
+};
+
+# Takes $1 and strips the whitespace out of it, returns VAL
+strip_white_space()
+{
+ if [ -z "${1}" ]
+ then
+ echo "Error: No value setup to strip whitespace from!"
+
+ exit 1
+ fi
+
+ export VAL=`echo "$1" | tr -d ' '`
+};
+
+# Displays an error message and exits with error 1
+exit_err()
+{
+ # Echo the message for the users benefit
+ echo "EXITERROR: $1"
+
+ # Save this error to the log file
+ echo "EXITERROR: ${1}" >>$LOGOUT
+
+ # Check if we need to unmount any file-systems after this failure
+ unmount_all_filesystems_failure
+
+ echo "For more details see log file: $LOGOUT"
+
+ exit 1
+};
+
+# Run-command, don't halt if command exits with non-0
+rc_nohalt()
+{
+ CMD="$1"
+
+ if [ -z "${CMD}" ]
+ then
+ exit_err "Error: missing argument in rc_nohalt()"
+ fi
+
+ echo "Running: ${CMD}" >>${LOGOUT}
+ ${CMD} >>${LOGOUT} 2>>${LOGOUT}
+
+};
+
+# Run-command, halt if command exits with non-0
+rc_halt()
+{
+ CMD="$1"
+
+ if [ -z "${CMD}" ]
+ then
+ exit_err "Error: missing argument in rc_halt()"
+ fi
+
+ echo "Running: ${CMD}" >>${LOGOUT}
+ eval ${CMD} >>${LOGOUT} 2>>${LOGOUT}
+ STATUS="$?"
+ if [ "${STATUS}" != "0" ]
+ then
+ exit_err "Error ${STATUS}: ${CMD}"
+ fi
+};
+
+# Run-command w/echo to screen, halt if command exits with non-0
+rc_halt_echo()
+{
+ CMD="$1"
+
+ if [ -z "${CMD}" ]
+ then
+ exit_err "Error: missing argument in rc_halt_echo()"
+ fi
+
+ echo "Running: ${CMD}" >>${LOGOUT}
+ ${CMD} 2>&1 | tee -a ${LOGOUT}
+ STATUS="$?"
+ if [ "$STATUS" != "0" ]
+ then
+ exit_err "Error ${STATUS}: $CMD"
+ fi
+
+};
+
+# Run-command w/echo, don't halt if command exits with non-0
+rc_nohalt_echo()
+{
+ CMD="$1"
+
+ if [ -z "${CMD}" ]
+ then
+ exit_err "Error: missing argument in rc_nohalt_echo()"
+ fi
+
+ echo "Running: ${CMD}" >>${LOGOUT}
+ ${CMD} 2>&1 | tee -a ${LOGOUT}
+
+};
+
+# Echo to the screen and to the log
+echo_log()
+{
+ STR="$1"
+
+ if [ -z "${STR}" ]
+ then
+ exit_err "Error: missing argument in echo_log()"
+ fi
+
+ echo "${STR}" | tee -a ${LOGOUT}
+};
+
+# Make sure we have a numeric
+is_num()
+{
+ expr $1 + 1 2>/dev/null
+ return $?
+}
+
+# Function which uses "fetch" to download a file, and display a progress report
+fetch_file()
+{
+
+ FETCHFILE="$1"
+ FETCHOUTFILE="$2"
+ EXITFAILED="$3"
+
+ EXITFILE="${TMPDIR}/.fetchExit"
+
+ rm ${FETCHOUTFILE} 2>/dev/null >/dev/null
+
+ SIZE=$(( `fetch -s "${FETCHFILE}"` / 1024 ))
+ echo "FETCH: ${FETCHFILE}"
+ echo "FETCH: ${FETCHOUTFILE}" >>${LOGOUT}
+
+ ( fetch -o ${FETCHOUTFILE} "${FETCHFILE}" >/dev/null 2>/dev/null ; echo "$?" > ${EXITFILE} ) &
+ PID="$!"
+ while
+ z=1
+ do
+
+ if [ -e "${FETCHOUTFILE}" ]
+ then
+ DSIZE=`du -k ${FETCHOUTFILE} | tr -d '\t' | cut -d '/' -f 1`
+ if [ $(is_num "$DSIZE") ] ; then
+ if [ $SIZE -lt $DSIZE ] ; then DSIZE="$SIZE"; fi
+ echo "SIZE: ${SIZE} DOWNLOADED: ${DSIZE}"
+ echo "SIZE: ${SIZE} DOWNLOADED: ${DSIZE}" >>${LOGOUT}
+ fi
+ fi
+
+ # Check if the download is finished
+ ps -p ${PID} >/dev/null 2>/dev/null
+ if [ $? -ne 0 ]
+ then
+ break;
+ fi
+
+ sleep 2
+ done
+
+ echo "FETCHDONE"
+
+ EXIT="`cat ${EXITFILE}`"
+ if [ "${EXIT}" != "0" -a "$EXITFAILED" = "1" ]
+ then
+ exit_err "Error: Failed to download ${FETCHFILE}"
+ fi
+
+ return $EXIT
+
+};
+
+# Function to return a the zpool name for this device
+get_zpool_name()
+{
+ DEVICE="$1"
+
+ # Set the base name we use for zpools
+ BASENAME="tank"
+
+ if [ ! -d "${TMPDIR}/.zpools" ] ; then
+ mkdir -p ${TMPDIR}/.zpools
+ fi
+
+ if [ -e "${TMPDIR}/.zpools/${DEVICE}" ] ; then
+ cat ${TMPDIR}/.zpools/${DEVICE}
+ return 0
+ else
+ # Need to generate a zpool name for this device
+ NUM=`ls ${TMPDIR}/.zpools/ | wc -l | sed 's| ||g'`
+
+ # Is it used in another zpool?
+ while :
+ do
+ NEWNAME="${BASENAME}${NUM}"
+ zpool list | grep -qw "${NEWNAME}"
+ local chk1=$?
+ zpool import | grep -qw "${NEWNAME}"
+ local chk2=$?
+ if [ $chk1 -eq 1 -a $chk2 -eq 1 ] ; then break ; fi
+ NUM=$((NUM+1))
+ done
+
+ # Now save the new tank name
+ mkdir -p ${TMPDIR}/.zpools/`dirname $DEVICE`
+ echo "$NEWNAME" >${TMPDIR}/.zpools/${DEVICE}
+ echo "${NEWNAME}"
+ return 0
+ fi
+};
+
+iscompressed()
+{
+ local FILE
+ local RES
+
+ FILE="$1"
+ RES=1
+
+ if echo "${FILE}" | \
+ grep -qiE '\.(Z|lzo|lzw|lzma|gz|bz2|xz|zip)$' 2>&1
+ then
+ RES=0
+ fi
+
+ return ${RES}
+}
+
+get_compression_type()
+{
+ local FILE
+ local SUFFIX
+
+ FILE="$1"
+ SUFFIX=`echo "${FILE}" | sed -E 's|^(.+)\.(.+)$|\2|'`
+
+ VAL=""
+ SUFFIX=`echo "${SUFFIX}" | tr A-Z a-z`
+ case "${SUFFIX}" in
+ z) VAL="lzw" ;;
+ lzo) VAL="lzo" ;;
+ lzw) VAL="lzw" ;;
+ lzma) VAL="lzma" ;;
+ gz) VAL="gzip" ;;
+ bz2) VAL="bzip2" ;;
+ xz) VAL="xz" ;;
+ zip) VAL="zip" ;;
+ esac
+
+ export VAL
+}
+
+write_image()
+{
+ local DEVICE_FILE
+
+ IMAGE_FILE="$1"
+ DEVICE_FILE="$2"
+
+ if [ -z "${IMAGE_FILE}" ]
+ then
+ exit_err "ERROR: Image file not specified!"
+ fi
+
+ if [ -z "${DEVICE_FILE}" ]
+ then
+ exit_err "ERROR: Device file not specified!"
+ fi
+
+ if [ ! -f "${IMAGE_FILE}" ]
+ then
+ exit_err "ERROR: '${IMAGE_FILE}' does not exist!"
+ fi
+
+ DEVICE_FILE="${DEVICE_FILE#/dev/}"
+ DEVICE_FILE="/dev/${DEVICE_FILE}"
+
+ if [ ! -c "${DEVICE_FILE}" ]
+ then
+ exit_err "ERROR: '${DEVICE_FILE}' is not a character device!"
+ fi
+
+ if iscompressed "${IMAGE_FILE}"
+ then
+ local COMPRESSION
+
+ get_compression_type "${IMAGE_FILE}"
+ COMPRESSION="${VAL}"
+
+ case "${COMPRESSION}" in
+ lzw)
+ rc_halt "uncompress ${IMAGE_FILE} -c | dd of=${DEVICE_FILE}"
+ IMAGE_FILE="${IMAGE_FILE%.Z}"
+ ;;
+
+ lzo)
+ rc_halt "lzop -d $IMAGE_{FILE} -c | dd of=${DEVICE_FILE}"
+ IMAGE_FILE="${IMAGE_FILE%.lzo}"
+ ;;
+
+ lzma)
+ rc_halt "lzma -d ${IMAGE_FILE} -c | dd of=${DEVICE_FILE}"
+ IMAGE_FILE="${IMAGE_FILE%.lzma}"
+ ;;
+
+ gzip)
+ rc_halt "gunzip ${IMAGE_FILE} -c | dd of=${DEVICE_FILE}"
+ IMAGE_FILE="${IMAGE_FILE%.gz}"
+ ;;
+
+ bzip2)
+ rc_halt "bunzip2 ${IMAGE_FILE} -c | dd of=${DEVICE_FILE}"
+ IMAGE_FILE="${IMAGE_FILE%.bz2}"
+ ;;
+
+ xz)
+ rc_halt "xz -d ${IMAGE_FILE} -c | dd of=${DEVICE_FILE}"
+ IMAGE_FILE="${IMAGE_FILE%.xz}"
+ ;;
+
+ zip)
+ rc_halt "unzip ${IMAGE_FILE} -c | dd of=${DEVICE_FILE}"
+ IMAGE_FILE="${IMAGE_FILE%.zip}"
+ ;;
+
+ *)
+ exit_err "ERROR: ${COMPRESSION} compression is not supported"
+ ;;
+ esac
+
+ else
+ rc_halt "dd if=${IMAGE_FILE} of=${DEVICE_FILE}"
+
+ fi
+};
+
+# Setup and install on a new disk / partition
+install_fresh()
+{
+ # Lets start setting up the disk slices now
+ setup_disk_slice
+
+ if [ -z "${ROOTIMAGE}" ]
+ then
+
+ # Disk setup complete, now lets parse WORKINGSLICES and setup the bsdlabels
+ setup_disk_label
+
+ # Now we've setup the bsdlabels, lets go ahead and run newfs / zfs
+ # to setup the filesystems
+ setup_filesystems
+
+ # Lets mount the partitions now
+ mount_all_filesystems
+
+ # We are ready to begin extraction, lets start now
+ init_extraction
+
+ # Check if we have any optional modules to load
+ install_components
+
+ # Check if we have any packages to install
+ install_packages
+
+ # Do any localization in configuration
+ run_localize
+
+ # Save any networking config on the installed system
+ save_networking_install
+
+ # Now add any users
+ setup_users
+
+ # Do any last cleanup / setup before unmounting
+ run_final_cleanup
+
+ # Now run any commands specified
+ run_commands
+
+ # Unmount and finish up
+ unmount_all_filesystems
+ fi
+
+ echo_log "Installation finished!"
+};
+
+# Extract the system to a pre-mounted directory
+install_extractonly()
+{
+ # We are ready to begin extraction, lets start now
+ init_extraction
+
+ # Check if we have any optional modules to load
+ install_components
+
+ # Check if we have any packages to install
+ install_packages
+
+ # Do any localization in configuration
+ run_localize
+
+ # Save any networking config on the installed system
+ save_networking_install
+
+ # Now add any users
+ setup_users
+
+ # Now run any commands specified
+ run_commands
+
+ # Set a hostname on the install system
+ setup_hostname
+
+ # Set the root_pw if it is specified
+ set_root_pw
+
+ echo_log "Installation finished!"
+};
+
+install_image()
+{
+ # We are ready to begin extraction, lets start now
+ init_extraction
+
+ echo_log "Installation finished!"
+};
+
+install_upgrade()
+{
+ # We're going to do an upgrade, skip all the disk setup
+ # and start by mounting the target drive/slices
+ mount_upgrade
+
+ # Start the extraction process
+ init_extraction
+
+ # Do any localization in configuration
+ run_localize
+
+ # Now run any commands specified
+ run_commands
+
+ # Merge any old configuration files
+ merge_old_configs
+
+ # Check if we have any optional modules to load
+ install_components
+
+ # Check if we have any packages to install
+ install_packages
+
+ # All finished, unmount the file-systems
+ unmount_upgrade
+
+ echo_log "Upgrade finished!"
+};
diff --git a/usr.sbin/pc-sysinstall/backend/installimage.sh b/usr.sbin/pc-sysinstall/backend/installimage.sh
new file mode 100755
index 0000000..242ecfe
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/installimage.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Source our functions scripts
+. ${BACKEND}/functions.sh
+
+IMAGE_FILE="${1}"
+DEVICE_FILE="${2}"
+
+write_image "${IMAGE_FILE}" "${DEVICE_FILE}"
diff --git a/usr.sbin/pc-sysinstall/backend/parseconfig.sh b/usr.sbin/pc-sysinstall/backend/parseconfig.sh
new file mode 100755
index 0000000..f3d89fc
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/parseconfig.sh
@@ -0,0 +1,124 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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$
+
+# Main install configuration parsing script
+#
+
+# Source our functions scripts
+. ${BACKEND}/functions.sh
+. ${BACKEND}/functions-bsdlabel.sh
+. ${BACKEND}/functions-cleanup.sh
+. ${BACKEND}/functions-disk.sh
+. ${BACKEND}/functions-extractimage.sh
+. ${BACKEND}/functions-installcomponents.sh
+. ${BACKEND}/functions-installpackages.sh
+. ${BACKEND}/functions-localize.sh
+. ${BACKEND}/functions-mountdisk.sh
+. ${BACKEND}/functions-networking.sh
+. ${BACKEND}/functions-newfs.sh
+. ${BACKEND}/functions-packages.sh
+. ${BACKEND}/functions-parse.sh
+. ${BACKEND}/functions-runcommands.sh
+. ${BACKEND}/functions-ftp.sh
+. ${BACKEND}/functions-unmount.sh
+. ${BACKEND}/functions-upgrade.sh
+. ${BACKEND}/functions-users.sh
+
+# Check that the config file exists
+if [ ! -e "${1}" ]
+then
+ echo "ERROR: Install configuration $1 does not exist!"
+ exit 1
+fi
+
+# Set our config file variable
+CFGF="$1"
+
+# Resolve any relative pathing
+CFGF="`realpath ${CFGF}`"
+export CFGF
+
+# Start by doing a sanity check, which will catch any obvious mistakes in the config
+file_sanity_check "installMode installType installMedium packageType"
+
+# We passed the Sanity check, lets grab some of the universal config settings and store them
+check_value installMode "fresh upgrade extract"
+check_value installType "PCBSD FreeBSD"
+check_value installMedium "dvd usb ftp rsync image local"
+check_value packageType "uzip tar rsync split dist"
+if_check_value_exists mirrorbal "load prefer round-robin split"
+
+# We passed all sanity checks! Yay, lets start the install
+echo "File Sanity Check -> OK"
+
+# Lets load the various universal settings now
+get_value_from_cfg installMode
+export INSTALLMODE="${VAL}"
+
+get_value_from_cfg installType
+export INSTALLTYPE="${VAL}"
+
+get_value_from_cfg installMedium
+export INSTALLMEDIUM="${VAL}"
+
+get_value_from_cfg packageType
+export PACKAGETYPE="${VAL}"
+
+# Check if we are doing any networking setup
+start_networking
+
+# If we are not doing an upgrade, lets go ahead and setup the disk
+case "${INSTALLMODE}" in
+ fresh)
+ if [ "${INSTALLMEDIUM}" = "image" ]
+ then
+ install_image
+ else
+ install_fresh
+ fi
+ ;;
+
+ extract)
+ # Extracting only, make sure we have a valid target directory
+ get_value_from_cfg installLocation
+ export FSMNT="${VAL}"
+ if [ -z "$FSMNT" ] ; then exit_err "Missing installLocation=" ; fi
+ if [ ! -d "$FSMNT" ] ; then exit_err "No such directory: $FSMNT" ; fi
+
+ install_extractonly
+ ;;
+
+ upgrade)
+ install_upgrade
+ ;;
+
+ *)
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/usr.sbin/pc-sysinstall/backend/startautoinstall.sh b/usr.sbin/pc-sysinstall/backend/startautoinstall.sh
new file mode 100755
index 0000000..5e4dcfc
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/backend/startautoinstall.sh
@@ -0,0 +1,136 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010 iXsystems, 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.
+#
+# Script which reads the pc-autoinstall.conf directive, and begins the install
+#
+# $FreeBSD$
+
+# Source our functions scripts
+. ${BACKEND}/functions.sh
+. ${BACKEND}/functions-networking.sh
+. ${BACKEND}/functions-parse.sh
+
+# Check that the config file exists
+if [ ! -e "${1}" ]
+then
+ echo "ERROR: Install configuration $1 does not exist!"
+ exit 1
+fi
+
+# Set our config file variable
+CONF=${1}
+INSTALL_CFG="/tmp/pc-sysinstall.cfg"
+
+# Check if the config file is on disk as well
+PCCFG=`grep "pc_config:" ${CONF} | grep -v "^#" | sed "s|pc_config: ||g" | sed "s|pc_config:||g"`
+SHUTDOWN_CMD=`grep "shutdown_cmd:" ${CONF} | grep -v "^#" | sed "s|shutdown_cmd: ||g" | sed "s|shutdown_cmd:||g"`
+CONFIRM_INS=`grep "confirm_install:" ${CONF} | grep -v "^#" | sed "s|confirm_install: ||g" | sed "s|confirm_install:||g"`
+
+# Check that this isn't a http / ftp file we need to fetch later
+echo "${PCCFG}" | grep -q -e "^http" -e "^ftp" 2>/dev/null
+if [ $? -ne 0 ]
+then
+ # Copy over the install cfg file, if not done already
+ if [ ! -e "${INSTALL_CFG}" ]
+ then
+ cp ${PCCFG} ${INSTALL_CFG}
+ fi
+ # Make sure we have the file which was copied into /tmp previously
+ if [ ! -e "${INSTALL_CFG}" ]
+ then
+ echo "Error: ${INSTALL_CFG} is missing! Exiting in 10 seconds..."
+ sleep 10
+ exit 150
+ fi
+else
+ # We need to fetch a remote file, check and set any nic options before doing so
+ NICCFG=`grep "nic_config:" ${CONF} | grep -v "^#" | sed "s|nic_config: ||g" | sed "s|nic_config:||g"`
+ if [ "${NICCFG}" = "dhcp-all" -o "${NICCFG}" = "DHCP-ALL" ]
+ then
+ # Try to auto-enable dhcp on any nics we find
+ enable_auto_dhcp
+ else
+ echo "Running command \"ifconfig ${NICCFG}\""
+ ifconfig ${NICCFG}
+ WRKNIC="`echo ${NICCFG} | cut -d ' ' -f 1`"
+ NICDNS=`grep "nic_dns:" ${CONF} | grep -v "^#" | sed "s|nic_dns: ||g" | sed "s|nic_dns:||g"`
+ NICGATE=`grep "nic_gateway:" ${CONF} | grep -v "^#" | sed "s|nic_gateway: ||g" | sed "s|nic_gateway:||g"`
+
+ echo "nameserver ${NICDNS}" >/etc/resolv.conf
+
+ echo "Running command \"route add default ${NICGATE}\""
+ route add default ${NICGATE}
+ fi
+
+ get_nic_mac "$WRKNIC"
+ nic_mac="${FOUNDMAC}"
+
+ PCCFG=`echo ${PCCFG} | sed "s|%%NIC_MAC%%|${nic_mac}|g"`
+
+ # Now try to fetch the remove file
+ echo "Fetching cfg with: \"fetch -o ${INSTALL_CFG} ${PCCFG}\""
+ fetch -o "${INSTALL_CFG}" "${PCCFG}"
+ if [ $? -ne 0 ]
+ then
+ echo "ERROR: Failed to fetch ${PCCFG}, install aborted"
+ exit 150
+ fi
+
+fi
+
+# If we end up with a valid config, lets proccede
+if [ -e "${INSTALL_CFG}" ]
+then
+
+ if [ "${CONFIRM_INS}" != "no" -a "${CONFIRM_INS}" != "NO" ]
+ then
+ echo "Type in 'install' to begin automated installation. Warning: Data on target disks may be destroyed!"
+ read tmp
+ case $tmp in
+ install|INSTALL) ;;
+ *) echo "Install canceled!" ; exit 150 ;;
+ esac
+ fi
+
+ pc-sysinstall -c ${INSTALL_CFG}
+ if [ $? -eq 0 ]
+ then
+ if [ -n "$SHUTDOWN_CMD" ]
+ then
+ ${SHUTDOWN_CMD}
+ else
+ echo "SUCCESS: Installation finished! Press ENTER to reboot."
+ read tmp
+ shutdown -r now
+ fi
+ else
+ echo "ERROR: Installation failed, press ENTER to drop to shell."
+ read tmp
+ /bin/csh
+ fi
+else
+ echo "ERROR: Failed to get /tmp/pc-sysinstall.cfg for automated install..."
+ exit 150
+fi
diff --git a/usr.sbin/pc-sysinstall/conf/Makefile b/usr.sbin/pc-sysinstall/conf/Makefile
new file mode 100644
index 0000000..5ccd78f
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/conf/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+FILESGROUPS= CONF LICENSE
+CONF= exclude-from-upgrade pc-sysinstall.conf avail-langs
+CONFDIR= ${SHAREDIR}/pc-sysinstall/conf
+LICENSE= licenses/bsd-en.txt licenses/intel-en.txt licenses/nvidia-en.txt
+LICENSEDIR= ${SHAREDIR}/pc-sysinstall/conf/license
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/pc-sysinstall/conf/Makefile.depend b/usr.sbin/pc-sysinstall/conf/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/conf/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/pc-sysinstall/conf/avail-langs b/usr.sbin/pc-sysinstall/conf/avail-langs
new file mode 100644
index 0000000..d78e14a
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/conf/avail-langs
@@ -0,0 +1,20 @@
+en English
+bg Bulgarian
+ca Catalan
+zh_TW Chinese_(Taiwan)
+cs Czech
+nl Dutch
+fr French
+de German
+en_GB English_(UK)
+en_ZA English_(South Africa)
+it Italian
+ja Japanese
+pt_BR Portuguese_(Brazil)
+pl Polish
+pa Punjabi
+ru Russian
+sk Slovak
+sl Slovenian
+es Spanish
+uk Ukrainian
diff --git a/usr.sbin/pc-sysinstall/conf/exclude-from-upgrade b/usr.sbin/pc-sysinstall/conf/exclude-from-upgrade
new file mode 100644
index 0000000..b0529d4
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/conf/exclude-from-upgrade
@@ -0,0 +1,15 @@
+etc/fstab
+dev
+etc/passwd
+etc/pwd.db
+etc/group
+etc/master.passwd
+etc/spwd.db
+etc/hosts
+etc/resolv.conf
+etc/localtime
+etc/hosts
+etc/X11
+etc/nsmb.conf
+usr/Programs/.config/ProgList
+Programs/.config/ProgList
diff --git a/usr.sbin/pc-sysinstall/conf/licenses/bsd-en.txt b/usr.sbin/pc-sysinstall/conf/licenses/bsd-en.txt
new file mode 100644
index 0000000..a30b67e
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/conf/licenses/bsd-en.txt
@@ -0,0 +1,24 @@
+SECTION 1: BSD LICENSE
+--------------------------------------------------------------------------------
+Copyright (c) 1998, 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:
+
+ Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ Neither the name of the University of California, Berkeley 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 EXPR
+ESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO E
+VENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCI
+DENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ OR BUSINESS INTERRUPTION) HOWEVER CAUSED AN ON ANY THEORY OF LIABILITY, WHETHER
+ IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/usr.sbin/pc-sysinstall/conf/licenses/intel-en.txt b/usr.sbin/pc-sysinstall/conf/licenses/intel-en.txt
new file mode 100644
index 0000000..0858ff7
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/conf/licenses/intel-en.txt
@@ -0,0 +1,207 @@
+Section 2: Intel Firmware license
+--------------------------------------------------------------------------------
+ TERMS AND CONDITIONS
+ IMPORTANT - PLEASE READ BEFORE INSTALLING OR USING THIS INTEL(C) SOFTWARE
+
+Do not use or load this firmware (the "Software") until you have carefully read
+the following terms and conditions. By loading or using the Software, you agree
+to the terms of this Agreement. If you do not wish to so agree, do not install
+or use the Software.
+
+LICENSEES:
+
+Please note:
+
+* If you are an End-User, only Exhibit A, the SOFTWARE LICENSE AGREEMENT,
+ applies.
+* If you are an Original Equipment Manufacturer (OEM), Independent Hardware
+ Vendor (IHV), or Independent Software Vendor (ISV), this complete Agreement
+ applies
+
+--------------------------------------------------------------------------------
+
+For OEMs, IHVs, and ISVs:
+
+LICENSE. This Software is licensed for use only in conjunction with Intel
+component products. Use of the Software in conjunction with non-Intel component
+products is not licensed hereunder. Subject to the terms of this Agreement,
+Intel grants to you a nonexclusive, nontransferable, worldwide, fully paid-up
+license under Intel's copyrights to: (i) copy the Software internally for your
+own development and maintenance purposes; (ii) copy and distribute the Software
+to your end-users, but only under a license agreement with terms at least as
+restrictive as those contained in Intel's Final, Single User License Agreement,
+attached as Exhibit A; and (iii) modify, copy and distribute the end-user
+documentation which may accompany the Software, but only in association with
+the Software.
+
+If you are not the final manufacturer or vendor of a computer system or software
+program incorporating the Software, then you may transfer a copy of the
+Software, including any related documentation (modified or unmodified) to your
+recipient for use in accordance with the terms of this Agreement, provided such
+recipient agrees to be fully bound by the terms hereof. You shall not otherwise
+assign, sublicense, lease, or in any other way transfer or disclose Software to
+any third party. You may not, nor may you assist any other person or entity to
+modify, translate, convert to another programming language, decompile, reverse
+engineer, or disassemble any portion of the Software or otherwise attempt to
+derive source code from any object code modules of the Software or any internal
+data files generated by the Software. Your rights to redistribute the Software
+shall be contingent upon your installation of this Agreement in its entirety in
+the same directory as the Software.
+
+CONTRACTORS. For the purpose of this Agreement, and notwithstanding anything
+to the contrary hereunder, solely with respect to the requirements for
+compliance with the terms hereunder, any contractors or consultants that You
+use to perform the work or otherwise assist You in the development or products
+using this Software shall be deemed to be End Users and accordingly, upon
+receipt of the Software, shall be bound by the terms of Exhibit A, Software
+License Agreement. No additional agreement between You and such consultants or
+contractors is required under this Agreement to detail such compliance.
+
+TRADEMARKS. Except as expressly provided herein, you shall not use Intel's
+name in any publications, advertisements, or other announcements without
+Intel's prior written consent. You do not have any rights to use any Intel
+trademarks or logos.
+
+OWNERSHIP OF SOFTWARE AND COPYRIGHTS. Software and accompanying materials, if
+any, are owned by Intel or its suppliers and licensors and may be protected by
+copyright, trademark, patent and trade secret law and international treaties.
+Any rights, express or implied, in the intellectual property embodied in the
+foregoing, other than those specified in this Agreement, are reserved by Intel
+and its suppliers and licensors or otherwise as set forth in any applicable
+open source license agreement. You will keep the Software free of liens,
+attachments, and other encumbrances. You agree not to remove any proprietary
+notices and/or any labels from the Software and accompanying materials without
+prior written approval by Intel
+
+LIMITATION OF LIABILITY. IN NO EVENT SHALL INTEL OR ITS SUPPLIERS AND LICENSORS
+BE LIABLE FOR ANY DAMAGES WHATSOEVER FROM ANY CAUSE OF ACTION OF ANY KIND
+(INCLUDING, WITHOUT LIMITATION, LOST PROFITS, BUSINESS INTERRUPTION, OR LOST
+INFORMATION) ARISING OUT OF THE USE, MODIFICATION, OR INABILITY TO USE THE
+INTEL SOFTWARE, OR OTHERWISE, NOR FOR PUNITIVE, INCIDENTAL, CONSEQUENTIAL, OR
+SPECIAL DAMAGES OF ANY KIND, EVEN IF INTEL OR ITS SUPPLIERS AND LICENSORS HAS
+BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME JURISDICTIONS PROHIBIT
+EXCLUSION OR LIMITATION OF LIABILITY FOR IMPLIED WARRANTIES, CONSEQUENTIAL OR
+INCIDENTAL DAMAGES, SO CERTAIN LIMITATIONS MAY NOT APPLY. YOU MAY ALSO HAVE
+OTHER LEGAL RIGHTS THAT VARY BETWEEN JURISDICTIONS.
+
+EXCLUSION OF WARRANTIES. THE SOFTWARE IS PROVIDED "AS IS" AND POSSIBLY WITH
+FAULTS. UNLESS EXPRESSLY AGREED OTHERWISE, INTEL AND ITS SUPPLIERS AND
+LICENSORS DISCLAIM ANY AND ALL WARRANTIES AND GUARANTEES, EXPRESS, IMPLIED OR
+OTHERWISE, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+NONINFRINGEMENT, OR FITNESS FOR A PARTICULAR PURPOSE. Intel does not warrant
+or assume responsibility for the accuracy or completeness of any information,
+text, graphics, links or other items contained within the Software. You assume
+all liability, financial or otherwise, associated with Your use or disposition
+of the Software.
+
+APPLICABLE LAW. Claims arising under this Agreement shall be governed by the
+laws of State of California], excluding its principles of conflict of laws and
+the United Nations Convention on Contracts for the Sale of Goods.
+
+WAIVER AND AMENDMENT. No modification, amendment or waiver of any provision of
+this Agreement shall be effective unless in writing and signed by an officer of
+Intel. No failure or delay in exercising any right, power, or remedy under
+this Agreement shall operate as a waiver of any such right, power or remedy.
+Without limiting the foregoing, terms and conditions on any purchase orders or
+similar materials submitted by you to Intel, and any terms contained in Intel’s
+standard acknowledgment form that are in conflict with these terms, shall be of
+no force or effect.
+
+SEVERABILITY. If any provision of this Agreement is held by a court of
+competent jurisdiction to be contrary to law, such provision shall be changed
+and interpreted so as to best accomplish the objectives of the original
+provision to the fullest extent allowed by law and the remaining provisions of
+this Agreement shall remain in full force and effect.
+
+EXPORT RESTRICTIONS. Each party acknowledges that the Software is subject to
+applicable import and export regulations of the United States and of the
+countries in which each party transacts business, specifically including U.S.
+Export Administration Act and Export Administration Regulations. Each party
+shall comply with such laws and regulations, as well as all other laws and
+regulations applicable to the Software. Without limiting the generality of the
+foregoing, each party agrees that it will not export, re-export, transfer or
+divert any of the Software or the direct programs thereof to any restricted
+place or party in accordance with U.S. export regulations. Note that Software
+containing encryption may be subject to additional restrictions.
+
+GOVERNMENT RESTRICTED RIGHTS. The Software is provided with "RESTRICTED RIGHTS."
+Use, duplication, or disclosure by the Government is subject to restrictions as
+set forth in FAR52.227-14 and DFAR252.227-7013 et seq. or their successors. Use
+of the Software by the Government constitutes acknowledgment of Intel's
+proprietary rights therein. Contractor or Manufacturer is Intel Corporation,
+2200 Mission College Blvd., Santa Clara, CA 95052.
+
+TERMINATION OF THE AGREEMENT. Intel may terminate this Agreement if you violate
+its terms. Upon termination, you will immediately destroy the Software or
+return all copies of the Software to Intel.
+
+--------------------------------------------------------------------------------
+
+EXHIBIT "A"
+
+SOFTWARE LICENSE AGREEMENT (Final, Single User)
+
+IMPORTANT - READ BEFORE COPYING, INSTALLING OR USING.
+
+Do not use or load this firmware image (the "Software") until you have carefully
+read the following terms and conditions. By loading or using the Software, you
+agree to the terms of this Agreement. If you do not wish to so agree, do not
+install or use the Software.
+
+LICENSE. You may copy and use the Software, subject to these conditions:
+1. This Software is licensed for use only in conjunction with Intel component
+ products. Use of the Software in conjunction with non-Intel component
+ products is not licensed hereunder.
+2. You may not copy, modify, rent, sell, distribute or transfer any part of the
+ Software except as provided in this Agreement, and you agree to prevent
+ unauthorized copying of the Software.
+3. You may not reverse engineer, decompile, or disassemble the Software.
+4. You may not sublicense the Software.
+5. The Software may contain the software or other property of third party
+ suppliers.
+
+OWNERSHIP OF SOFTWARE AND COPYRIGHTS. Title to all copies of the Software
+remains with Intel or its suppliers. The Software is copyrighted and protected
+by the laws of the United States and other countries, and international treaty
+provisions. You may not remove any copyright notices from the Software. Intel
+may make changes to the Software, or items referenced therein, at any time
+without notice, but is not obligated to support or update the Software. Except
+as otherwise expressly provided, Intel grants no express or implied right under
+Intel patents, copyrights, trademarks, or other intellectual property rights.
+You may transfer the Software only if a copy of this license accompanies the
+Software and the recipient agrees to be fully bound by these terms.
+
+EXCLUSION OF OTHER WARRANTIES EXCEPT AS PROVIDED ABOVE, THE SOFTWARE IS PROVIDED
+"AS IS" WITHOUT ANY EXPRESS OR IMPLIED WARRANTY OF ANY KIND INCLUDING
+WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT, OR FITNESS FOR A PARTICULAR
+PURPOSE. Intel does not warrant or assume responsibility for the accuracy or
+completeness of any information, text, graphics, links or other items contained
+within the Software.
+
+LIMITATION OF LIABILITY. IN NO EVENT SHALL INTEL OR ITS SUPPLIERS BE LIABLE FOR
+ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, LOST PROFITS, BUSINESS
+INTERRUPTION, OR LOST INFORMATION) ARISING OUT OF THE USE OF OR INABILITY TO
+USE THE SOFTWARE, EVEN IF INTEL HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES. SOME JURISDICTIONS PROHIBIT EXCLUSION OR LIMITATION OF LIABILITY FOR
+IMPLIED WARRANTIES OR CONSEQUENTIAL OR INCIDENTAL DAMAGES, SO THE ABOVE
+LIMITATION MAY NOT APPLY TO YOU. YOU MAY ALSO HAVE OTHER LEGAL RIGHTS THAT VARY
+BETWEEN JURISDICTIONS.
+
+TERMINATION OF THIS AGREEMENT. Intel may terminate this Agreement at any time if
+you violate its terms. Upon termination, you will immediately destroy the
+Software.
+
+APPLICABLE LAWS. Claims arising under this Agreement shall be governed by the
+laws of California, excluding its principles of conflict of laws and the United
+Nations Convention on Contracts for the Sale of Goods. You may not export the
+Software in violation of applicable export laws and regulations. Intel is not
+obligated under any other agreements unless they are in writing and signed by
+an authorized representative
+of Intel.
+
+GOVERNMENT RESTRICTED RIGHTS. The Software is provided with "RESTRICTED RIGHTS."
+Use, duplication, or disclosure by the Government is subject to restrictions as
+set forth in FAR52.227-14 and DFAR252.227-7013 et seq. or their successors. Use
+of the Software by the Government constitutes acknowledgment of Intel's
+proprietary rights therein. Contractor or Manufacturer is Intel Corporation,
+2200 Mission College Blvd., Santa Clara, CA 95052.
diff --git a/usr.sbin/pc-sysinstall/conf/licenses/nvidia-en.txt b/usr.sbin/pc-sysinstall/conf/licenses/nvidia-en.txt
new file mode 100644
index 0000000..22c7906
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/conf/licenses/nvidia-en.txt
@@ -0,0 +1,53 @@
+Section 3: NVIDIA driver license agreement
+--------------------------------------------------------------------------------
+License For Customer Use of NVIDIA Software
+
+IMPORTANT NOTICE -- READ CAREFULLY: This License For Customer Use of NVIDIA Software ("LICENSE") is the agreement which governs use of the software of NVIDIA Corporation and its subsidiaries ("NVIDIA") downloadable herefrom, including computer software and associated printed materials ("SOFTWARE"). By downloading, installing, copying, or otherwise using the SOFTWARE, you agree to be bound by the terms of this LICENSE. If you do not agree to the terms of this LICENSE, do not download the SOFTWARE.
+
+RECITALS
+
+Use of NVIDIA's products requires three elements: the SOFTWARE, the hardware on a graphics controller board, and a personal computer. The SOFTWARE is protected by copyright laws and international copyright treaties, as well as other intellectual property laws and treaties. The SOFTWARE is not sold, and instead is only licensed for use, strictly in accordance with this document. The hardware is protected by various patents, and is sold, but this LICENSE does not cover that sale, since it may not necessarily be sold as a package with the SOFTWARE. This LICENSE sets forth the terms and conditions of the SOFTWARE LICENSE only.
+
+1. DEFINITIONS
+
+1.1 Customer. Customer means the entity or individual that downloads the SOFTWARE.
+
+2. GRANT OF LICENSE
+
+2.1 Rights and Limitations of Grant. NVIDIA hereby grants Customer the following non-exclusive, non-transferable right to use the SOFTWARE, with the following limitations:
+
+2.1.1 Rights. Customer may install and use one copy of the SOFTWARE on a single computer, and except for making one back-up copy of the Software, may not otherwise copy the SOFTWARE. This LICENSE of SOFTWARE may not be shared or used concurrently on different computers.
+
+2.1.2 Linux/FreeBSD Exception. Notwithstanding the foregoing terms of Section 2.1.1, SOFTWARE designed exclusively for use on the Linux or FreeBSD operating systems, or other operating systems derived from the source code to these operating systems, may be copied and redistributed, provided that the binary files thereof are not modified in any way (except for unzipping of compressed files).
+
+2.1.3 Limitations.
+
+No Reverse Engineering. Customer may not reverse engineer, decompile, or disassemble the SOFTWARE, nor attempt in any other manner to obtain the source code.
+
+No Separation of Components. The SOFTWARE is licensed as a single product. Its component parts may not be separated for use on more than one computer, nor otherwise used separately from the other parts.
+
+No Rental. Customer may not rent or lease the SOFTWARE to someone else.
+
+3. TERMINATION
+
+This LICENSE will automatically terminate if Customer fails to comply with any of the terms and conditions hereof. In such event, Customer must destroy all copies of the SOFTWARE and all of its component parts.
+
+Defensive Suspension. If Customer commences or participates in any legal proceeding against NVIDIA, then NVIDIA may, in its sole discretion, suspend or terminate all license grants and any other rights provided under this LICENSE during the pendency of such legal proceedings.
+
+4. COPYRIGHT
+
+All title and copyrights in and to the SOFTWARE (including but not limited to all images, photographs, animations, video, audio, music, text, and other information incorporated into the SOFTWARE), the accompanying printed materials, and any copies of the SOFTWARE, are owned by NVIDIA, or its suppliers. The SOFTWARE is protected by copyright laws and international treaty provisions. Accordingly, Customer is required to treat the SOFTWARE like any other copyrighted material, except as otherwise allowed pursuant to this LICENSE and that it may make one copy of the SOFTWARE solely for backup or archive purposes.
+
+5. APPLICABLE LAW
+
+This LICENSE shall be deemed to have been made in, and shall be construed pursuant to, the laws of the State of California. The United Nations Convention on Contracts for the International Sale of Goods is specifically disclaimed.
+
+6. DISCLAIMER OF WARRANTIES AND LIMITATION ON LIABILITY
+
+6.1 No Warranties. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS" AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+
+6.2 No Liability for Consequential Damages. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL NVIDIA OR ITS SUPPLIERS BE LIABLE FOR ANY SPECIAL, INCIDENTAL, INDIRECT, OR CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THE SOFTWARE, EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. MISCELLANEOUS
+
+If any provision of this LICENSE is inconsistent with, or cannot be fully enforced under, the law, such provision will be construed as limited to the extent necessary to be consistent with and fully enforceable under the law. This LICENSE is the final, complete and exclusive agreement between the parties relating to the subject matter hereof, and supersedes all prior or contemporaneous understandings and agreements relating to such subject matter, whether oral or written. This LICENSE may only be modified in writing signed by an authorized officer of NVIDIA. Customer agrees that it will not ship, transfer or export the SOFTWARE into any country, or use the SOFTWARE in any manner, prohibited by the United States Bureau of Export Administration or any export laws, restrictions or regulations.
diff --git a/usr.sbin/pc-sysinstall/conf/pc-sysinstall.conf b/usr.sbin/pc-sysinstall/conf/pc-sysinstall.conf
new file mode 100644
index 0000000..9c01e31
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/conf/pc-sysinstall.conf
@@ -0,0 +1,84 @@
+#!/bin/sh
+# $FreeBSD$
+# Configuration options for pc-sysinstall
+
+TMPDIR="/tmp/.pc-sysinstall"
+export TMPDIR
+
+# Create a fresh TMPDIR
+if [ -d "${TMPDIR}" -a "$TMPDIR" != '/' ]; then rm -rf ${TMPDIR}; fi
+mkdir -p ${TMPDIR}
+
+# Set our temp directory for storing partition information
+PARTDIR="${TMPDIR}/part-info"
+export PARTDIR
+
+# Set the SLICECFGDIR
+SLICECFGDIR="${TMPDIR}/.slice-cfg"
+export SLICECFGDIR
+
+# Set the MIRRORCFGDIR
+MIRRORCFGDIR="${TMPDIR}/.mirror-cfg"
+export MIRRORCFGDIR
+
+# Set the GELIKEYDIR
+GELIKEYDIR="${TMPDIR}/.geli-keys"
+export GELIKEYDIR
+
+# Set our log file
+LOGOUT="${TMPDIR}/pc-sysinstall.log"
+export LOGOUT
+
+# Set the number of rsync tries
+RSYNCTRIES="3"
+export RSYNCTRIES
+
+# Set our mount-points
+CDMNT=${CDMNT-/cdmnt-install}
+FSMNT=${FSMNT-/mnt}
+UZIP_DIR="/usr"
+BOOT_PART_MOUNT="/boot-mount"
+export FSMNT CDMNT UZIP_DIR BOOT_PART_MOUNT
+
+# Set the location of component files on DVD / usb / ftp
+# Relative to CDMNT or the FTP root
+COMPFILEDIR="extras/"
+export COMPFILEDIR
+
+# Set the component temp directory, which is relative to FSMNT
+COMPTMPDIR="/usr/.componenttmp"
+export COMPTMPDIR
+
+# set the package temp directory, which is relative to FSMNT
+PKGTMPDIR="/usr/.pkgtmp"
+export PKGTMPDIR
+
+# Variables to set the location of installation data
+UZIP_FILE="PCBSD.ufs.uzip"
+TAR_FILE="PCBSD.tbz"
+export UZIP_FILE TAR_FILE
+
+# Locations of FreeBSD only install files
+FBSD_UZIP_FILE="fbsd-release.ufs.uzip"
+FBSD_TAR_FILE="fbsd-release.tbz"
+FBSD_BRANCH="8.0-RELEASE"
+FBSD_BRANCH_DIR="${FBSD_BRANCH}"
+FBSD_ARCH=`uname -m`
+export FBSD_UZIP_FILE FBSD_TAR_FILE FBSD_BRANCH FBSD_BRANCH_DIR FBSD_ARCH
+
+# Location of image file
+IMAGE_FILE="/home/john/tmp/PCBSD8.1-x86-USB.img"
+export IMAGE_FILE
+
+# Our internet mirror listing file location
+NETSERVER="http://updates.pcbsd.org"
+ARCH="`uname -m`"
+
+# Check if we are running on a PC-BSD Disk
+if [ -e "/PCBSDVERSION" ] ; then
+ VERSION="`cat /PCBSDVERSION`"
+else
+ VERSION="UNKNOWN"
+fi
+
+MIRRORLIST="${NETSERVER}/mirrors-netinstall.php?ver=${VERSION}&arch=${ARCH}"
diff --git a/usr.sbin/pc-sysinstall/doc/Makefile b/usr.sbin/pc-sysinstall/doc/Makefile
new file mode 100644
index 0000000..682415c
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/doc/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+FILES= help-disk-list help-disk-size help-index help-start-autoinstall
+
+FILESDIR=${SHAREDIR}/pc-sysinstall/doc
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/pc-sysinstall/doc/Makefile.depend b/usr.sbin/pc-sysinstall/doc/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/doc/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/pc-sysinstall/doc/help-disk-list b/usr.sbin/pc-sysinstall/doc/help-disk-list
new file mode 100644
index 0000000..0d4bff9
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/doc/help-disk-list
@@ -0,0 +1 @@
+Holder
diff --git a/usr.sbin/pc-sysinstall/doc/help-disk-size b/usr.sbin/pc-sysinstall/doc/help-disk-size
new file mode 100644
index 0000000..0d4bff9
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/doc/help-disk-size
@@ -0,0 +1 @@
+Holder
diff --git a/usr.sbin/pc-sysinstall/doc/help-index b/usr.sbin/pc-sysinstall/doc/help-index
new file mode 100644
index 0000000..bad401f
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/doc/help-index
@@ -0,0 +1,100 @@
+pc-sysinstall Help Index
+-----------------------------------------------
+Help Commands
+
+ help
+ Display this index file
+
+ help <command>
+ Display the help data for the specified command
+
+System Query Commands
+ install-image <image> <device>
+ Installs an image file to a device file
+
+ disk-list
+ Provides a listing of the disk drives detected on this system
+
+ disk-part <disk>
+ Queries the specified disk and returns information about its partitions
+
+ disk-info <disk>
+ Returns information about the disks size, cyls, heads, and sectors
+
+ detect-laptop
+ Tests to see if this system is a laptop or desktop
+
+ detect-emulation
+ Tests to see if this system is actually running in an emulator such as VirtualBox
+
+ detect-nics
+ Returns a listing of the detected network cards on this system
+
+ list-config
+ Returns a listing of the pc-sysinstall configuration
+
+ list-components
+ Returns a listing of the available components which can be installed
+
+ list-mirrors [country]
+ Returns a listing of the available FTP mirrors
+
+ list-packages [category] [package]
+ Returns a listing of the available packages
+
+ list-rsync-backups <user> <host> <port>
+ Returns a listing of available rsync-backups on the target server in the life-preserver/ dir
+
+ list-tzones
+ Returns a listing of available timezones
+
+ query-langs
+ Return a list of languages that the installer supports
+
+ get-packages
+ Retrieves the list of packages from an FTP mirror
+
+ sys-mem
+ Return the size of installed system RAM in MegaBytes
+
+ set-mirror <mirror>
+ Set FTP mirror
+
+ test-netup
+ Test if an internet connection is available
+
+ update-part-list
+ Return a list of PC-BSD & FreeBSD installs on this system for updates
+
+ xkeyboard-layouts
+ Return a list of keyboard layouts that xorg supports
+
+ xkeyboard-models
+ Return a list of keyboard models that xorg supports
+
+ xkeyboard-variants
+ Return a list of keyboard variants that xorg supports
+
+Partition Management Commands
+
+ create-part <disk> <size>
+ Create a new MBR primary slice on the target <disk> using <size> MB
+
+ delete-part <partition>
+ Deletes the disk partition specified. If this is the last partition,
+ the disk partition layout will also be scrubbed, leaving a clean disk
+ ready for MBR or GPT file system layouts.
+
+
+Installation Commands
+
+ -c <cfg>
+ Begin a install / upgrade with the specified cfg file
+
+ start-autoinstall <conf>
+ Start an automated installation with the specified conf file
+ Normally only used by automated install scripts
+
+ setup-ssh-keys <user> <host> <port>
+ Setup SSH without a password for the target host and user and port
+ Use to prompt the user to log into a server before doing a rsync + ssh restore
diff --git a/usr.sbin/pc-sysinstall/doc/help-start-autoinstall b/usr.sbin/pc-sysinstall/doc/help-start-autoinstall
new file mode 100644
index 0000000..1cb1e6a
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/doc/help-start-autoinstall
@@ -0,0 +1,39 @@
+start-autoinstall - HELP
+-----------------------------------------------
+
+About:
+ start-autoinstall is used when performing automated installationsr. It is able
+ to configure networking with provided settings, and fetch an installation script from
+ http / ftp server. This allows media to be created which fetches dynamic configuration
+ options from a server-side supplier.
+
+Usage:
+ pc-sysinstall start-autoinstall <config>
+
+Config Syntax:
+
+ The configuration file for start-autoinstall can contain the following options:
+
+ pc_config: <value>
+ - Location of the pc-sysinstall installation configuration file, can be local
+ or start with http:// or ftp:// to fetch from a remote system.
+
+ shutdown_cmd: <value>
+ - Command to execute post-installation, such as "shutdown -p now" or other.
+
+ confirm_install: (YES/NO)
+ - Prompt on the console to begin installation. Defaults to YES.
+ Warning: Setting this to NO will start an installation as soon as start-autoinstall
+ is run. (I.E. after booting some install media) It may be dangerous if a disk is left
+ in a drive and the system is turned on!
+
+ nic_config: (DHCP-ALL / <cfg>)
+ - When set to DHCP-ALL, the software will attempt to get a network address from DHCP on
+ any / all detected NICS. If set to some other command, it will be used as an argument
+ to "ifconfig" to enable networking.
+
+ nic_dns: <value>
+ - Use the following DNS server for networking
+
+ nic_gateway: <gateway>
+ - Use the following default route / gateway for networking
diff --git a/usr.sbin/pc-sysinstall/examples/Makefile b/usr.sbin/pc-sysinstall/examples/Makefile
new file mode 100644
index 0000000..fb76fec
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/examples/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+FILES= README pc-autoinstall.conf pcinstall.cfg.fbsd-netinstall \
+ pcinstall.cfg.geli pcinstall.cfg.gmirror pcinstall.cfg.netinstall \
+ pcinstall.cfg.restore pcinstall.cfg.rsync pcinstall.cfg.upgrade \
+ pcinstall.cfg.zfs
+
+FILESDIR=${SHAREDIR}/examples/pc-sysinstall
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/pc-sysinstall/examples/Makefile.depend b/usr.sbin/pc-sysinstall/examples/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/examples/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/pc-sysinstall/examples/README b/usr.sbin/pc-sysinstall/examples/README
new file mode 100644
index 0000000..1e8d32d
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/examples/README
@@ -0,0 +1,403 @@
+pc-sysinstall README
+
+This file documents many of the variables used in pc-sysinstall
+config scripts
+#################################################################
+
+# hostname=
+
+Using hostname= will set the specified hostname on the
+installed system
+
+When hostname= is not present, pc-sysinstall will auto-generate
+a hostname such as freebsd-XXXX or pcbsd-XXXX
+
+# installMode=(fresh/upgrade/extract)
+
+Set the type of install we are doing.
+
+Fresh installs will format and mount the target disks before
+extracting the install images to the system. Using this mode
+it is also possible to do a system restore, by specifying a
+full system backup as the install source.
+
+Upgrades will mount the target disk, and extract the system
+archive to the disk, overwriting files on the disk.
+The conf/exclude-from-upgrade file can be used to specify
+additional files to exclude from overwriting during the
+install process.
+
+Extract will skip any disk setup, and perform an installation
+to the directory specified by "installLocation=". This location
+should be a directory with your pre-mounted file-systems ready
+for file extraction. When using the "extract" option, /etc/fstab
+on the installed system will *not* be automatically configured.
+
+# installLocation=
+
+Used only when installMode is set to extract.
+
+This is set to the location you want to extract your system to,
+and should already be mounted properly.
+
+# installInteractive=(yes or no)
+
+Set if the installer is running in interactive mode, and
+is able to prompt for input from the user, defaults to no
+
+
+########################################################################
+# NETWORK SETTINGS
+########################################################################
+
+# netDev=(AUTO-DHCP or <nic>)
+
+netDev specifies what type of networking to enable for the installer
+Can be set to AUTO-DHCP or to a network interface, such as et0
+
+When set to AUTO-DHCP, pc-sysinstall will probe for all network devices
+and attempt to set DHCP mode on each, until a working network connection
+is established
+
+If netDev= is set to a network interface such as et0, the following options
+will need to be set in order to enable the interface
+
+# netIP=(IP address such as 192.168.0.100)
+
+ Set netIP to an address that you wish to have the interface specified in
+ netDev set to
+ Only used when netDev is not set to AUTO-DHCP
+
+# netMask=(Netmask such as 255.255.255.0)
+
+Set netMask to the address you with to have the interface specified in
+netDev set to
+Only used when netDev is not set to AUTO-DHCP
+
+
+# netNameServer=(DNS Server such as 192.168.0.1)
+
+Set netNameServer to the DNS address you want to use during the install
+Only used when netDev is not set to AUTO-DHCP
+
+
+# netDefaultRouter=(192.168.0.1)
+
+Set netDefaultRouter to the gateway you wish to have the installer use
+Only used when netDev is not set to AUTO-DHCP
+
+
+
+# netSaveDev=(AUTO-DHCP or network interface)
+
+netSaveDev specifies what networking to enable on the installed system
+
+When set to AUTO-DHCP, pc-sysinstall will probe all network interfaces, and
+set them all to DHCP in the systems /etc/rc.conf file. Wireless devices will also
+have the corresponding wlan[0-9] device created.
+
+When set to a network interface, pc-sysinstall will set the target device with
+the settings specified by the variables below.
+
+# netSaveIP=192.168.0.49
+# netSaveMask=255.255.255.0
+# netSaveNameServer=208.67.222.222
+# netSaveDefaultRouter=192.168.0.1
+
+
+
+
+########################################################################
+# DISK SLICE SETTINGS
+########################################################################
+
+The following section specifies the target disk(s) to be used in the
+install or upgrade.
+
+# disk0=(disk device, such as ad0)
+
+The diskX= variable should be set to the target device for this drive, such
+as ad0, da0
+The first should begin with disk0=, and additional drives to disk1=, disk2
+if additional disks are to be setup.
+
+When doing an upgrade, the disk0= line should be set to the root device or
+root zpool of the target system to update. I.E:
+ # disk0=tank0
+ # disk0=ada0s1a
+
+
+# partition=(all, free, s1, s1, s3, s4, image)
+
+After setting disk[0-9], the partition= variable is used to specify which target
+partition we will be working with for this device.
+
+Setting this to "all" will setup the disk with a single FreeBSD slice as "s1"
+
+Setting this to "free" will allow pc-sysinstall to search for the first available
+primary slice with free space, and create the slice.
+
+Setting this to "s1, s2, s3 or s4" will use the specified MBR slice.
+
+Setting this to "image" will use an image to configure the disk.
+
+(This tag is unused for upgrades)
+
+# partscheme=(MBR/GPT)
+
+When performing a "full" disk (partition=all), the partscheme= variable is used
+to determine the partition scheme type gpart will be using on the disk. Valid
+choices are MBR or GPT.
+
+# mirror=(disk device such as ad1)
+
+Setting the mirror= variable will setup the target device as a gmirror
+of the diskX= device. The mirror device must be the same size or larger
+than the drive being mirrored.
+
+
+# mirrorbal=(load, prefer, round-robin, split)
+
+Allows the setting of the mirror balance method to be used, if not
+specified this defaults to "round-robin"
+
+# bootManager=(none, bsd)
+
+Setting this option will instruct pc-sysinstall to install the BSD boot Manager,
+or leave it empty
+
+# image=(/path/to/image/file) (/mountpoint)
+
+Setting this option will instruct pc-sysinstall to write the image file
+specified by the path to the disk.
+
+# commitDiskPart
+
+This command must be placed at the end of the diskX= section, before starting
+the listing of any additional diskX= directives.
+
+
+########################################################################
+# DISK PARTITION / MOUNT SETTINGS
+########################################################################
+
+The following settings specify the partitioning / mount points to setup
+on the target partition
+
+# disk0-part=UFS+S 500 / (-n -o time)
+# disk0-part=SWAP 2000 none
+# disk0-part=UFS.eli 500 /usr
+# encpass=mypass
+# disk0-part=UFS+J 500 /tmp
+# disk0-part=ZFS 0 /data,/storage (mirror: ad1)
+# commitDiskLabel
+
+The above values instructs pc-sysinstall which partitions / mounts
+to create on the target drive / slice, specified by "disk0".
+(disk0 will resolve to the drive / slice specified in the previous section)
+
+The notation is as follows:
+<File System Type> <Size> <Mountpoint>
+
+Available FileSystems:
+ UFS - Standard UFS2 FileSystem
+UFS+S - UFS2 + Softupdates enabled
+UFS+SUJ - UFS2 + Soft Updates + Journaling enabled
+UFS+J - UFS2 + Journaling through gjournal
+ ZFS - Z File System, pools / mounts created automatically
+ SWAP - BSD Swap space partition, mountpoint should be set to "none"
+
+Adding the ".eli" extension to any of the above file systems
+will enable disk encryption via geli
+(UFS.eli, UFS+S.eli, UFS+SUJ.eli, UFS+J.eli, ZFS.eli, SWAP.eli)
+
+If you with to use a passphrase with this encrypted partition, on the next line
+the flag "encpass=" should be entered:
+encpass=mypass
+
+All sizes are expressed in MegaBytes
+Specifying a size 0 instructs pc-sysinstall to use the rest of the
+available slice size, and should only be used for the last partition / mount
+
+When using "UFS" and its various types, it is possible to specify custom options
+for newfs using (). For examplei:
+disk0-part=UFS+SUJ 1000 / (-o time)
+In this case "-o time" would be passed to newfs when creating the "/" filesystem.
+
+
+When using "ZFS" specifically, it is possible to specify additional disks / partitions
+to include in the zpool. By using the syntax: (mirror: ad1,ad2) or (raidz: ad1,ad2), it is possible
+to include the disk "ad1" into the zpool for this partition, using the raidz / mirror methods.
+If you with to just include the disk into the pool in "basic" mode, then use (ad1,ad2) with no flags
+
+########################################################################
+# INSTALL OPTIONS / SOURCES
+########################################################################
+
+The following settings specify the type, locations and sources
+for this installation
+
+# installMedium=(dvd, usb, ftp, rsync, image)
+
+Set installMedium= to the source type we will be using for this install.
+
+Available Types:
+ dvd - Search for and mount the DVD which contains the install archive
+local - Pull files directly from a local directory
+ usb - Search for and mount the USB drive which contains the install archive
+ ftp - The install archive will be fetched from a FTP / HTTP server before install
+rsync - Pull the system data from a ssh + rsync server, specified with variables below
+image - Install system from an image
+
+# localPath=/usr/freebsd-dist
+
+Location of the directory we will be pulling installation files from
+
+# installType=(PCBSD, FreeBSD)
+
+Set the type of system we are installing, PCBSD or FreeBSD
+
+# installFile=fbsd-release.tbz
+
+The installer archive, if not using the defaults specified in conf/pc-sysinstall.conf
+
+# packageType=(tar, uzip, split, dist)
+
+The archive type we are extracting from when using dvd, usb or ftp
+
+# distFiles=base src kernel
+
+List of dist files to install when packageType=dist
+
+# ftpPath=ftp://ftp.pcbsd.org/pub/8.0/netinstall
+
+Location of the installer archive when using a installMedium=ftp
+
+# rsyncPath=life-preserver/back-2009-11-12T14_53_14
+
+The location of the rsync data on the remote server when using installMedium=rsync
+
+# rsyncUser=rsyncuser
+
+The username to use for the ssh server running rsync
+
+# rsyncHost=192.168.0.50
+
+The rsync / ssh server we wish to connect to
+
+# rsyncPort=22
+
+The port to use when connecting to a ssh + rsync server
+
+# installComponents=amarok,firefox,ports
+
+The specified components to install, view available with "./pc-sysinstall list-components"
+
+
+########################################################################
+# UPGRADE OPTIONS
+########################################################################
+
+Options specific to performing an upgrade
+
+# upgradeKeepDesktopProfile=(yes/no)
+
+This option allows you to specify if you wish to keep your existing users desktop
+profile data. The default is NO, and your existing profile will be moved to
+.kde4.preUpgrade automatically.
+
+########################################################################
+# USER OPTIONS
+########################################################################
+
+Options for setting up usernames and passwords on the installed system
+
+# rootPass=root
+
+Set the root password of the installed system to the specified plaintext string
+
+# rootEncPass=<encryptedstring>
+
+Set the root password of the installed system to the specified encrypted string
+
+The below variables are used to setup a user on the installed system
+Be sure to call commitUser after after adding these values, and before
+starting another user block
+
+# userName=kris
+# userComment=Kris Moore
+# userPass=mypass
+or
+# userEncPass=<encryptedstring>
+# userShell=/bin/csh
+# userHome=/home/kris
+# userGroups=wheel,operator
+# commitUser
+
+########################################################################
+# RUN COMMANDS
+########################################################################
+
+The following variables can be set to run commands post-installation,
+allowing the user to further tweak / modify the system
+
+# runCommand=
+
+Run the specified command within chroot of the installed system
+
+# runScript=
+
+runScript will copy the specified script into FSMNT, and run it in chroot of the system
+Useful when you have a 3rd party script on the DVD / USB, and you want to copy it into
+the installed system and run
+
+# runExtCommand=
+
+runExtCommand is used when you wish to run a command outside the chroot
+The variable $FSMNT is set to the mount-point of your installed system
+
+
+########################################################################
+# PC-BSD SPECIFIC OPTIONS
+########################################################################
+
+Options for time-zones and NTP on the installed system
+
+# timeZone=
+
+timeZone can be set to the zone file in /usr/share/zoneinfo/ that is to be used
+example: America/New_York
+
+# enableNTP= (yes / no)
+
+set enableNTP to yes or no to enable or disable the NTP service on the system
+
+
+########################################################################
+# PC-BSD SPECIFC OPTIONS
+########################################################################
+
+Options specific to installing PC-BSD, such as localization, and KDE settings
+
+# localizeLang=en
+
+localizeLang will set the system console and Desktop to the target language
+
+# localizeKeyLayout=en
+
+localizeKeyLayout updates the system's xorg config to set the keyboard layout
+
+# localizeKeyModel=pc104
+
+localizeKeyModel updates the system's xorg config to set the keyboard model
+
+# localizeKeyVariant=intl
+
+localizeKeyVariant is used to update the xorg config to set the keyboard variant
+
+# autoLoginUser=kris
+
+Setting autoLoginUser will enable the specified user to log into the desktop
+automatically without entering a password
+
+$FreeBSD$
diff --git a/usr.sbin/pc-sysinstall/examples/pc-autoinstall.conf b/usr.sbin/pc-sysinstall/examples/pc-autoinstall.conf
new file mode 100644
index 0000000..899d3b9
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/examples/pc-autoinstall.conf
@@ -0,0 +1,52 @@
+# pc-autoinstall.conf example
+# $FreeBSD$
+#
+# Usage: Modify these variables, and copy the file to
+# /boot/pc-autoinstall.conf on your PC-BSD installation medium
+#
+# The conf will then be read at bootup, and your automated
+# install will take place
+##################################################################
+
+# Where the pc-sysinstall main config is located
+# Can be either a file on the booted CD / DVD / USB media,
+# or a remote file on http / ftp
+#
+# The value %%NIC_MAC%% is special, and will be substituted with
+# the macaddress of the enabled NIC from DHCP or manually set
+# with 'nic_config:'
+##################################################################
+
+# Examples:
+# pc_config: ftp://192.168.0.2/cust-install.cfg
+# pc_config: http://192.168.0.2/cust-install.cfg
+# pc_config: http://192.168.0.2/%%NIC_MAC%%.cfg
+# pc_config: /boot/cust-install.cfg
+
+# Set this to yes if we should confirm before doing an install
+# This should normally be set to yes, otherwise booting the wrong
+# disk will result in a system wipe
+# confirm_install: no
+confirm_install: yes
+
+# Set the command to run post-install, usually best to run shutdown
+# but this can be replaced with any other command / script you wish
+# to execute post-install
+# shutdown_cmd: shutdown -p now
+
+# Options for the network setup, should the cfg need to be fetched
+# from a remote location, only necessary when using ftp or http
+##################################################################
+
+# Special option, will attempt dhcp on all found NICs
+# until the file can be fetched, or we run out of interfaces
+# nic_config: dhcp-all
+
+# Line to be passed to the "ifconfig" command to bring up an interface
+# nic_config: em0 192.168.0.101 255.255.255.0
+
+# DNS server to use
+# nic_dns: 192.168.0.1
+
+# Default router / gateway
+# nic_gateway: 192.168.0.1
diff --git a/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.fbsd-netinstall b/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.fbsd-netinstall
new file mode 100644
index 0000000..e57bad8
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.fbsd-netinstall
@@ -0,0 +1,71 @@
+# Sample configuration file for an installation using pc-sysinstall
+#$FreeBSD$
+
+installMode=fresh
+installInteractive=yes
+hostname=pcbsd8
+
+# Set the disk parameters
+disk0=ad0
+partition=all
+bootManager=none
+commitDiskPart
+
+# Setup the disk label
+# All sizes are expressed in MB
+# Avail FS Types, UFS, UFS+S, UFS+J, ZFS, SWAP
+disk0-part=UFS 1000 /
+disk0-part=SWAP 2000 none
+disk0-part=UFS 0 /usr
+# Size 0 means use the rest of the slice size
+# Do it now!
+commitDiskLabel
+
+netDev=AUTO-DHCP
+#netDev=nfe0
+#netIP=192.168.0.49
+#netMask=255.255.255.0
+#netNameServer=208.67.222.222
+#netDefaultRouter=192.168.0.1
+
+netSaveDev=AUTO-DHCP
+#netSaveDev=nfe0
+#netSaveIP=192.168.0.49
+#netSaveMask=255.255.255.0
+#netSaveNameServer=208.67.222.222
+#netSaveDefaultRouter=192.168.0.1
+
+# Set if we are installing via optical, USB, or FTP
+#installType=PCBSD
+installType=FreeBSD
+#installMedium=dvd
+installMedium=ftp
+
+ftpPath=ftp://192.168.0.2/netinstall
+
+#packageType=uzip
+packageType=tar
+
+# List our components to install
+installComponents=ports,src
+
+# Setup user "kris" to log into the desktop automatically
+autoLoginUser=kris
+
+# Set the root pass
+rootPass=root
+
+# Setup our users
+userName=kris
+userComment=Kris Moore
+userPass=kris
+userShell=/bin/csh
+userHome=/home/kris
+userGroups=wheel,operator
+commitUser
+
+# Options for localizing an install
+localizeLang="ru"
+localizeKeyLayout="ru"
+localizeKeyModel="pc104"
+localizeKeyVariant="intl"
diff --git a/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.geli b/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.geli
new file mode 100644
index 0000000..983dff7
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.geli
@@ -0,0 +1,50 @@
+# Auto-Generated pc-sysinstall configuration
+#$FreeBSD$
+
+installInteractive=no
+installMode=fresh
+installType=FreeBSD
+packageType=tar
+installMedium=dvd
+netSaveDev=AUTO-DHCP
+
+# Timezone
+timeZone=America/New_York
+enableNTP=yes
+
+# Keyboard Layout Options
+localizeKeyModel=pc104
+localizeKeyLayout=us
+
+# Disk Setup for ad0
+disk0=ad0
+partition=ALL
+bootManager=none
+commitDiskPart
+
+# Partition Setup for ad0(ALL)
+# All sizes are expressed in MB
+# Avail FS Types, UFS, UFS+S, UFS+J, ZFS, SWAP
+# UFS.eli, UFS+S.eli, UFS+J.eli, ZFS.eli, SWAP.eli
+disk0-part=UFS 500 /boot
+disk0-part=UFS.eli 500 /
+disk0-part=UFS.eli 500 /usr
+encpass=mypass
+commitDiskLabel
+
+# Optional Components
+installComponents=
+
+# Root Password
+rootPass=mypass
+
+# Users
+userName=kris
+userComment=Kris Moore
+userPass=mypass
+userShell=/bin/csh
+userHome=/home/kris
+userGroups=wheel,operator
+autoLoginUser=kris
+commitUser
+
diff --git a/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.gmirror b/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.gmirror
new file mode 100644
index 0000000..eec2831
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.gmirror
@@ -0,0 +1,45 @@
+# Sample configuration file for an installation using pc-sysinstall
+#$FreeBSD$
+
+installMode=fresh
+installInteractive=yes
+hostname=pcbsd8
+
+# Set the disk parameters
+disk0=ad0
+mirror=ad1
+mirrorbal=split
+partition=all
+bootManager=bsd
+commitDiskPart
+
+# Setup the disk label
+# All sizes are expressed in MB
+# Avail FS Types, UFS, UFS+S, UFS+J, ZFS, SWAP
+disk0-part=UFS+S 500 /
+disk0-part=SWAP 2000 none
+disk0-part=UFS+S 0 /usr
+# Size 0 means use the rest of the slice size
+# Do it now!
+commitDiskLabel
+
+# Set if we are installing via optical, USB, or FTP
+installType=FreeBSD
+installMedium=dvd
+
+#packageType=uzip
+packageType=tar
+#installComponents=ports,src
+
+# Run any commands post-install
+runCommand=echo 'root' | pw usermod root -h 0
+#runScript=/root/test.sh
+#runExtCommand=echo 'hey there'; touch $FSMNT/touched
+
+#autoLoginUser=kris
+
+# Options for localizing an install
+localizeLang="ru"
+localizeKeyLayout="ru"
+localizeKeyModel="pc104"
+localizeKeyVariant="intl"
diff --git a/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.netinstall b/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.netinstall
new file mode 100644
index 0000000..44a0c50
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.netinstall
@@ -0,0 +1,68 @@
+# Sample configuration file for an installation using pc-sysinstall
+#$FreeBSD$
+
+installMode=fresh
+installInteractive=yes
+hostname=pcbsd8
+
+# Set the disk parameters
+disk0=ad0
+partition=all
+bootManager=none
+commitDiskPart
+
+# Setup the disk label
+# All sizes are expressed in MB
+# Avail FS Types, UFS, UFS+S, UFS+J, ZFS, SWAP
+disk0-part=UFS 1000 /
+disk0-part=SWAP 2000 none
+disk0-part=UFS 0 /usr
+# Size 0 means use the rest of the slice size
+# Do it now!
+commitDiskLabel
+
+netDev=AUTO-DHCP
+#netDev=nfe0
+#netIP=192.168.0.49
+#netMask=255.255.255.0
+#netNameServer=208.67.222.222
+#netDefaultRouter=192.168.0.1
+
+netSaveDev=AUTO-DHCP
+#netSaveDev=nfe0
+#netSaveIP=192.168.0.49
+#netSaveMask=255.255.255.0
+#netSaveNameServer=208.67.222.222
+#netSaveDefaultRouter=192.168.0.1
+
+# Set if we are installing via optical, USB, or FTP
+#installType=PCBSD
+installType=FreeBSD
+#installMedium=dvd
+installMedium=ftp
+
+ftpPath=ftp://192.168.0.2/netinstall
+
+#packageType=uzip
+packageType=tar
+#installComponents=ports,src
+
+#autoLoginUser=kris
+
+# Set the root pass
+rootPass=root
+
+# Setup our users
+userName=kris
+userComment=Kris Moore
+userPass=kris
+userShell=/bin/csh
+userHome=/home/kris
+userGroups=wheel,operator
+commitUser
+
+# Options for localizing an install
+localizeLang="ru"
+localizeKeyLayout="ru"
+localizeKeyModel="pc104"
+localizeKeyVariant="intl"
diff --git a/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.restore b/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.restore
new file mode 100644
index 0000000..8dcc486
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.restore
@@ -0,0 +1,57 @@
+# Sample configuration file for an installation using pc-sysinstall
+#$FreeBSD$
+
+installMode=fresh
+installInteractive=no
+hostname=freebsd8
+
+# Set the disk parameters
+disk0=ad1
+partition=all
+bootManager=none
+commitDiskPart
+
+# Setup the disk label
+# All sizes are expressed in MB
+# Avail FS Types, UFS, UFS+S, UFS+J, ZFS, SWAP
+# UFS.eli, UFS+S.eli, UFS+J.eli, ZFS.eli, SWAP.eli
+disk0-part=UFS+S 500 /
+disk0-part=SWAP.eli 2000 none
+disk0-part=UFS+S 0 /usr
+# Size 0 means use the rest of the slice size
+# Do it now!
+commitDiskLabel
+
+# Set if we are installing via optical, USB, or FTP
+installType=FreeBSD
+installMedium=dvd
+installFile=freebsd-release.tbz
+
+#packageType=uzip
+packageType=tar
+#installComponents=ports,src
+
+# Run any commands post-install
+#runCommand=echo 'root' | pw usermod root -h 0
+#runScript=/root/test.sh
+#runExtCommand=echo 'hey there'; touch $FSMNT/touched
+
+# Set the root pass
+rootPass=root
+
+# Setup our users
+userName=kris
+userComment=Kris Moore
+userPass=kris
+userShell=/bin/csh
+userHome=/home/kris
+userGroups=wheel,operator
+commitUser
+
+#autoLoginUser=kris
+
+# Options for localizing an install
+localizeLang="ru"
+localizeKeyLayout="ru"
+localizeKeyModel="pc104"
+localizeKeyVariant="intl"
diff --git a/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.rsync b/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.rsync
new file mode 100644
index 0000000..d520d09
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.rsync
@@ -0,0 +1,45 @@
+# Sample configuration file for an installation using pc-sysinstall
+#$FreeBSD$
+
+installMode=fresh
+installInteractive=yes
+hostname=pcbsd8
+
+# Set the disk parameters
+disk0=ad0
+partition=all
+bootManager=none
+commitDiskPart
+
+# Setup the disk label
+# All sizes are expressed in MB
+# Avail FS Types, UFS, UFS+S, UFS+J, ZFS, SWAP
+disk0-part=UFS 2000 /
+disk0-part=SWAP 2000 none
+disk0-part=UFS 0 /usr
+# Size 0 means use the rest of the slice size
+# Do it now!
+commitDiskLabel
+
+netDev=AUTO-DHCP
+#netDev=nfe0
+#netIP=192.168.0.49
+#netMask=255.255.255.0
+#netNameServer=208.67.222.222
+#netDefaultRouter=192.168.0.1
+
+# Set if we are installing via optical, USB, or FTP
+#installType=PCBSD
+installType=FreeBSD
+#installMedium=dvd
+installMedium=rsync
+
+rsyncPath=life-preserver/back-2009-11-12T14_53_14
+rsyncUser=lifep
+rsyncHost=192.168.0.50
+rsyncPort=22
+
+#packageType=uzip
+packageType=tar
+#installComponents=ports,src
+
diff --git a/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.upgrade b/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.upgrade
new file mode 100644
index 0000000..f181d61
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.upgrade
@@ -0,0 +1,24 @@
+# Sample configuration file for an installation using pc-sysinstall
+#$FreeBSD$
+
+installMode=upgrade
+installInteractive=no
+hostname=freebsd8
+
+# Set the disk parameters
+disk0=ada0s1a
+bootManager=none
+commitDiskPart
+
+# Set if we are installing via optical, USB, or FTP
+installType=PCBSD
+installMedium=dvd
+
+packageType=uzip
+#installComponents=ports,src
+
+# Options for localizing an install
+localizeLang="ru"
+localizeKeyLayout="ru"
+localizeKeyModel="pc104"
+localizeKeyVariant="intl"
diff --git a/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.zfs b/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.zfs
new file mode 100644
index 0000000..2deb498
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/examples/pcinstall.cfg.zfs
@@ -0,0 +1,59 @@
+# Auto-Generated pc-sysinstall configuration
+#$FreeBSD$
+installInteractive=no
+installMode=fresh
+installType=FreeBSD
+packageType=tar
+installMedium=dvd
+netSaveDev=AUTO-DHCP
+
+# Timezone
+timeZone=America/New_York
+enableNTP=yes
+
+# Keyboard Layout Options
+localizeKeyModel=pc104
+localizeKeyLayout=us
+
+# Disk Setup for ad0
+disk0=ad0
+partition=ALL
+bootManager=none
+commitDiskPart
+
+# Partition Setup for ad0(ALL)
+# All sizes are expressed in MB
+# Avail FS Types, UFS, UFS+S, UFS+J, ZFS, SWAP
+# UFS.eli, UFS+S.eli, UFS+J.eli, ZFS.eli, SWAP.eli
+disk0-part=ZFS 0 /,/usr,/var,/data (mirror: ad1)
+commitDiskLabel
+
+# Disk Setup for ad3
+disk1=ad3
+partition=ALL
+bootManager=none
+commitDiskPart
+
+# Partition Setup for ad3(ALL)
+# All sizes are expressed in MB
+# Avail FS Types, UFS, UFS+S, UFS+J, ZFS, SWAP
+# UFS.eli, UFS+S.eli, UFS+J.eli, ZFS.eli, SWAP.eli
+disk1-part=SWAP 0 none
+commitDiskLabel
+
+# Optional Components
+installComponents=
+
+# Root Password
+rootPass=mypass
+
+# Users
+userName=kris
+userComment=Kris Moore
+userPass=mypass
+userShell=/bin/csh
+userHome=/home/kris
+userGroups=wheel,operator
+autoLoginUser=kris
+commitUser
+
diff --git a/usr.sbin/pc-sysinstall/pc-sysinstall/Makefile b/usr.sbin/pc-sysinstall/pc-sysinstall/Makefile
new file mode 100644
index 0000000..1e8c061c
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/pc-sysinstall/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+SCRIPTS=pc-sysinstall.sh
+MAN= pc-sysinstall.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/pc-sysinstall/pc-sysinstall/Makefile.depend b/usr.sbin/pc-sysinstall/pc-sysinstall/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/pc-sysinstall/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/pc-sysinstall/pc-sysinstall/pc-sysinstall.8 b/usr.sbin/pc-sysinstall/pc-sysinstall/pc-sysinstall.8
new file mode 100644
index 0000000..bcc1f30
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/pc-sysinstall/pc-sysinstall.8
@@ -0,0 +1,120 @@
+.\" Copyright (c) 2010
+.\" iXsystems, 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 Jordan Hubbard OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (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 24, 2010
+.Dt PC-SYSINSTALL 8
+.Os
+.Sh NAME
+.Nm pc-sysinstall
+.Nd System installer backend
+.Sh SYNOPSIS
+.Nm
+.Op Fl c Ar file
+.Op Ar command
+.Sh DESCRIPTION
+The
+.Nm
+utility is a hybrid backend for installing FreeBSD. When run in install mode
+it takes a configuration file and performs an installation according to the
+parameters specified in the configuration file. When called with one of
+the system query commands it provides information about the system to aid a
+front end in building an appropriate configuration file.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl c Ar file
+Perform an installation as directed by
+.Ar file.
+.El
+.Sh COMMANDS
+The
+.Cm command
+can be any one of the following:
+.Bl -tag -width indent
+.It help
+Display a list of all commands.
+.It help Ar command
+Display the help data for the specified command.
+.It disk-list
+Provide a listing of the storage devices detected on this system.
+.It disk-part Ar disk
+Queries the specified storage device and returns information about its
+partitions.
+.It disk-info Ar disk
+Returns information about a storage device's size, cylinders, heads, and
+sectors.
+.It detect-laptop
+Tests to see if this system is a laptop or desktop.
+.It detect-emulation
+Tests to see if this system is running in an emulator
+.It detect-nics
+Returns a listing of the detected network cards on this system.
+.It list-components
+Returns a listing of the available components which can be installed.
+.It list-rsync-backups Ar user Ar host Ar port
+Returns a listing of available rsync-backups on the target server in the
+life-preserver/ directory.
+.It list-tzones
+Returns a listing of available timezones.
+.It query-langs
+Returns a list of languages that the installer supports.
+.It sys-mem
+Returns the size of installed system RAM in MegaBytes.
+.It test-netup
+test if an internet connection is available.
+.It update-part-list
+Returns a list of PC-BSD and FreeBSD installs on this system for updates.
+.It xkeyboard-layouts
+Returns a list of keyboard layouts that xorg supports.
+.It xkeyboard-models
+Returns a list of keyboard models that xorg supports.
+.It xkeyboard-variants
+Returns a list of keyboard variants that xorg supports.
+.It create-part Ar disk Ar size
+Create a new MBR primary slice on the target disk using size MB.
+.It delete-part Ar partition
+Delete the disk partition specified. If this is the last partition, the
+disk partition layout will also be scrubbed, leaving a clean disk ready
+for MBR or GPT file system layouts.
+.It start-autoinstall Ar file
+Start an automated installation with the specified file. Normally only
+used by automated install scripts.
+.It setup-ssh-keys Ar user Ar host Ar port
+Setup SSH without a password for the target host, user, and port. Used to
+prompt the user to log into a server before doing a rsync + ssh restore.
+.El
+.Sh HISTORY
+This version of
+.Nm
+first appeared in
+.Fx 9.0 .
+.Sh AUTHORS
+.An Kris Moore Aq Mt kmoore@FreeBSD.org
+.Sh BUGS
+This utility was written to install PC-BSD and has seen limited use as an
+installer for FreeBSD. It's likely that usage to install FreeBSD will expose
+edge cases that PC-BSD doesn't, as well as generate feature requests based
+on unforeseen needs.
diff --git a/usr.sbin/pc-sysinstall/pc-sysinstall/pc-sysinstall.sh b/usr.sbin/pc-sysinstall/pc-sysinstall/pc-sysinstall.sh
new file mode 100755
index 0000000..2dc7093
--- /dev/null
+++ b/usr.sbin/pc-sysinstall/pc-sysinstall/pc-sysinstall.sh
@@ -0,0 +1,240 @@
+#!/bin/sh
+#####################################################################
+# Author: Kris Moore
+# License: BSD
+# Description: pc-sysinstall provides a backend for performing
+# system installations, as well as calls which a front-end can use
+# to retrive information about the system
+#####################################################################
+# Copyright 2010 iXsystems
+# All rights reserved
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted providing that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#####################################################################
+
+# User-editable configuration variables
+
+# Set this to the program location
+if [ -z "${PROGDIR}" ]
+then
+ PROGDIR="/usr/share/pc-sysinstall"
+ export PROGDIR
+fi
+
+# Set this to the components location
+COMPDIR="${PROGDIR}/components"
+export COMPDIR
+
+CONFDIR="${PROGDIR}/conf"
+export CONFDIR
+
+# Set this to the packages location
+PKGDIR="${CONFDIR}"
+export PKGDIR
+
+# End of user-editable configuration
+#####################################################################
+
+# Set our QUERYDIR
+QUERYDIR="${PROGDIR}/backend-query"
+export QUERYDIR
+
+# Set our BACKEND
+BACKEND="${PROGDIR}/backend"
+export BACKEND
+
+PARTMANAGERDIR="${PROGDIR}/backend-partmanager"
+export PARTMANAGERDIR
+
+# Start by sourcing our conf file
+if [ -e "${PROGDIR}/conf/pc-sysinstall.conf" ]
+then
+ . ${PROGDIR}/conf/pc-sysinstall.conf
+else
+ echo "ERROR: Could not find ${PROGDIR}/conf/pc-sysinstall.conf"
+ exit 1
+fi
+
+# Now source our functions.sh
+if [ -e "${PROGDIR}/backend/functions.sh" ]
+then
+ . ${PROGDIR}/backend/functions.sh
+else
+ echo "ERROR: Could not find ${PROGDIR}/backend/functions.sh"
+ exit 1
+fi
+
+
+# Check if we are called without any flags and display help
+if [ -z "${1}" ]
+then
+ # Display the help index
+ display_help
+ exit 0
+fi
+
+case $1 in
+ # The -c flag has been given, time to parse the script
+ -c)
+ if [ -z "${2}" ]
+ then
+ display_help
+ else
+ ${BACKEND}/parseconfig.sh ${2}
+ exit $?
+ fi
+ ;;
+
+ # The user requsted help
+ help)
+ if [ -z "${2}" ]
+ then
+ display_help
+ else
+ display_command_help ${2}
+ fi
+ ;;
+
+ # Install an image file to a device
+ install-image) ${BACKEND}/installimage.sh "${2}" "${3}"
+ ;;
+
+ # Parse an auto-install directive, and begin the installation
+ start-autoinstall) ${BACKEND}/startautoinstall.sh ${2}
+ ;;
+
+ # The user is wanting to create a new partition
+ create-part) ${PARTMANAGERDIR}/create-part.sh "${2}" "${3}" "${4}" "${5}"
+ ;;
+
+ # The user is wanting to delete an existing partition
+ delete-part) ${PARTMANAGERDIR}/delete-part.sh "${2}"
+ ;;
+
+ # The user is wanting to check if we are on a laptop or desktop
+ detect-laptop) ${QUERYDIR}/detect-laptop.sh
+ ;;
+
+ # The user is wanting to see what nics are available on the system
+ detect-nics) ${QUERYDIR}/detect-nics.sh
+ ;;
+
+ # The user is wanting to check if we are in emulation
+ detect-emulation) ${QUERYDIR}/detect-emulation.sh
+ ;;
+
+ # The user is wanting to query a disk's information
+ disk-info) ${QUERYDIR}/disk-info.sh ${2}
+ ;;
+
+ # The user is wanting to query which disks are available
+ disk-list) ${QUERYDIR}/disk-list.sh $*
+ ;;
+
+ # The user is wanting to query a disk's partitions
+ disk-part) ${QUERYDIR}/disk-part.sh ${2}
+ ;;
+
+ # Function allows the setting of networking by a calling front-end
+ enable-net) ${QUERYDIR}/enable-net.sh "${2}" "${3}" "${4}" "${5}" "${6}" "${7}"
+ ;;
+
+ # Function which lists components available
+ list-components) ${QUERYDIR}/list-components.sh
+ ;;
+
+ # Function which lists pc-sysinstall configuration
+ list-config) ${QUERYDIR}/list-config.sh
+ ;;
+
+ # Function which lists available FTP mirrors
+ list-mirrors) ${QUERYDIR}/list-mirrors.sh "${2}"
+ ;;
+
+ # Function which lists available packages
+ list-packages) ${QUERYDIR}/list-packages.sh "${2}" "${3}"
+ ;;
+
+ # Function which lists available backups on a rsync/ssh server
+ list-rsync-backups) ${QUERYDIR}/list-rsync-backups.sh "${2}" "${3}" "${4}"
+ ;;
+
+ # Function which lists timezones available
+ list-tzones) ${QUERYDIR}/list-tzones.sh
+ ;;
+
+ # Requested a list of languages this install will support
+ query-langs) ${QUERYDIR}/query-langs.sh
+ ;;
+
+ # Function which creates a error report, and mails it to the specified address
+ send-logs) ${QUERYDIR}/send-logs.sh ${2}
+ ;;
+
+ # Function to get package index
+ get-packages) ${QUERYDIR}/get-packages.sh "${2}"
+ ;;
+
+ # Function to set FTP mirror
+ set-mirror) ${QUERYDIR}/set-mirror.sh "${2}"
+ ;;
+
+ # Function which allows setting up of SSH keys
+ setup-ssh-keys) ${QUERYDIR}/setup-ssh-keys.sh "${2}" "${3}" "${4}"
+ ;;
+
+ # Function which lists the real memory of the system in MB
+ sys-mem) ${QUERYDIR}/sys-mem.sh
+ ;;
+
+ # Run script which determines if we are booted from install media, or on disk
+ test-live) ${QUERYDIR}/test-live.sh
+ ;;
+
+ # The user is wanting to test if the network is up and working
+ test-netup) ${QUERYDIR}/test-netup.sh
+ ;;
+
+ # The user is wanting to get a list of partitions available to be updated / repaired
+ update-part-list) ${QUERYDIR}/update-part-list.sh
+ ;;
+
+ # Requested a list of keyboard layouts that xorg supports
+ xkeyboard-layouts) ${QUERYDIR}/xkeyboard-layouts.sh
+ ;;
+
+ # Requested a list of keyboard models that xorg supports
+ xkeyboard-models) ${QUERYDIR}/xkeyboard-models.sh
+ ;;
+
+ # Requested a list of keyboard variants that xorg supports
+ xkeyboard-variants) ${QUERYDIR}/xkeyboard-variants.sh
+ ;;
+
+ *) echo "Unknown Command: ${1}"
+ exit 1 ;;
+esac
+
+# Exit with success if we made it to the end
+exit $?
diff --git a/usr.sbin/pciconf/Makefile b/usr.sbin/pciconf/Makefile
new file mode 100644
index 0000000..a839733
--- /dev/null
+++ b/usr.sbin/pciconf/Makefile
@@ -0,0 +1,10 @@
+# $ANA: Makefile,v 1.1.1.1 1996/09/25 21:12:57 wollman Exp $
+# $FreeBSD$
+
+PROG= pciconf
+SRCS= pciconf.c cap.c err.c
+MAN= pciconf.8
+
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/pciconf/Makefile.depend b/usr.sbin/pciconf/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/pciconf/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/pciconf/cap.c b/usr.sbin/pciconf/cap.c
new file mode 100644
index 0000000..9ab6dd1
--- /dev/null
+++ b/usr.sbin/pciconf/cap.c
@@ -0,0 +1,887 @@
+/*-
+ * Copyright (c) 2007 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@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.
+ * 3. 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 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 <err.h>
+#include <stdio.h>
+#include <strings.h>
+#include <sys/agpio.h>
+#include <sys/pciio.h>
+
+#include <dev/agp/agpreg.h>
+#include <dev/pci/pcireg.h>
+
+#include "pciconf.h"
+
+static void list_ecaps(int fd, struct pci_conf *p);
+
+static void
+cap_power(int fd, struct pci_conf *p, uint8_t ptr)
+{
+ uint16_t cap, status;
+
+ cap = read_config(fd, &p->pc_sel, ptr + PCIR_POWER_CAP, 2);
+ status = read_config(fd, &p->pc_sel, ptr + PCIR_POWER_STATUS, 2);
+ printf("powerspec %d supports D0%s%s D3 current D%d",
+ cap & PCIM_PCAP_SPEC,
+ cap & PCIM_PCAP_D1SUPP ? " D1" : "",
+ cap & PCIM_PCAP_D2SUPP ? " D2" : "",
+ status & PCIM_PSTAT_DMASK);
+}
+
+static void
+cap_agp(int fd, struct pci_conf *p, uint8_t ptr)
+{
+ uint32_t status, command;
+
+ status = read_config(fd, &p->pc_sel, ptr + AGP_STATUS, 4);
+ command = read_config(fd, &p->pc_sel, ptr + AGP_CAPID, 4);
+ printf("AGP ");
+ if (AGP_MODE_GET_MODE_3(status)) {
+ printf("v3 ");
+ if (AGP_MODE_GET_RATE(status) & AGP_MODE_V3_RATE_8x)
+ printf("8x ");
+ if (AGP_MODE_GET_RATE(status) & AGP_MODE_V3_RATE_4x)
+ printf("4x ");
+ } else {
+ if (AGP_MODE_GET_RATE(status) & AGP_MODE_V2_RATE_4x)
+ printf("4x ");
+ if (AGP_MODE_GET_RATE(status) & AGP_MODE_V2_RATE_2x)
+ printf("2x ");
+ if (AGP_MODE_GET_RATE(status) & AGP_MODE_V2_RATE_1x)
+ printf("1x ");
+ }
+ if (AGP_MODE_GET_SBA(status))
+ printf("SBA ");
+ if (AGP_MODE_GET_AGP(command)) {
+ printf("enabled at ");
+ if (AGP_MODE_GET_MODE_3(command)) {
+ printf("v3 ");
+ switch (AGP_MODE_GET_RATE(command)) {
+ case AGP_MODE_V3_RATE_8x:
+ printf("8x ");
+ break;
+ case AGP_MODE_V3_RATE_4x:
+ printf("4x ");
+ break;
+ }
+ } else
+ switch (AGP_MODE_GET_RATE(command)) {
+ case AGP_MODE_V2_RATE_4x:
+ printf("4x ");
+ break;
+ case AGP_MODE_V2_RATE_2x:
+ printf("2x ");
+ break;
+ case AGP_MODE_V2_RATE_1x:
+ printf("1x ");
+ break;
+ }
+ if (AGP_MODE_GET_SBA(command))
+ printf("SBA ");
+ } else
+ printf("disabled");
+}
+
+static void
+cap_vpd(int fd, struct pci_conf *p, uint8_t ptr)
+{
+
+ printf("VPD");
+}
+
+static void
+cap_msi(int fd, struct pci_conf *p, uint8_t ptr)
+{
+ uint16_t ctrl;
+ int msgnum;
+
+ ctrl = read_config(fd, &p->pc_sel, ptr + PCIR_MSI_CTRL, 2);
+ msgnum = 1 << ((ctrl & PCIM_MSICTRL_MMC_MASK) >> 1);
+ printf("MSI supports %d message%s%s%s ", msgnum,
+ (msgnum == 1) ? "" : "s",
+ (ctrl & PCIM_MSICTRL_64BIT) ? ", 64 bit" : "",
+ (ctrl & PCIM_MSICTRL_VECTOR) ? ", vector masks" : "");
+ if (ctrl & PCIM_MSICTRL_MSI_ENABLE) {
+ msgnum = 1 << ((ctrl & PCIM_MSICTRL_MME_MASK) >> 4);
+ printf("enabled with %d message%s", msgnum,
+ (msgnum == 1) ? "" : "s");
+ }
+}
+
+static void
+cap_pcix(int fd, struct pci_conf *p, uint8_t ptr)
+{
+ uint32_t status;
+ int comma, max_splits, max_burst_read;
+
+ status = read_config(fd, &p->pc_sel, ptr + PCIXR_STATUS, 4);
+ printf("PCI-X ");
+ if (status & PCIXM_STATUS_64BIT)
+ printf("64-bit ");
+ if ((p->pc_hdr & PCIM_HDRTYPE) == 1)
+ printf("bridge ");
+ if ((p->pc_hdr & PCIM_HDRTYPE) != 1 || (status & (PCIXM_STATUS_133CAP |
+ PCIXM_STATUS_266CAP | PCIXM_STATUS_533CAP)) != 0)
+ printf("supports");
+ comma = 0;
+ if (status & PCIXM_STATUS_133CAP) {
+ printf("%s 133MHz", comma ? "," : "");
+ comma = 1;
+ }
+ if (status & PCIXM_STATUS_266CAP) {
+ printf("%s 266MHz", comma ? "," : "");
+ comma = 1;
+ }
+ if (status & PCIXM_STATUS_533CAP) {
+ printf("%s 533MHz", comma ? "," : "");
+ comma = 1;
+ }
+ if ((p->pc_hdr & PCIM_HDRTYPE) == 1)
+ return;
+ switch (status & PCIXM_STATUS_MAX_READ) {
+ case PCIXM_STATUS_MAX_READ_512:
+ max_burst_read = 512;
+ break;
+ case PCIXM_STATUS_MAX_READ_1024:
+ max_burst_read = 1024;
+ break;
+ case PCIXM_STATUS_MAX_READ_2048:
+ max_burst_read = 2048;
+ break;
+ case PCIXM_STATUS_MAX_READ_4096:
+ max_burst_read = 4096;
+ break;
+ }
+ switch (status & PCIXM_STATUS_MAX_SPLITS) {
+ case PCIXM_STATUS_MAX_SPLITS_1:
+ max_splits = 1;
+ break;
+ case PCIXM_STATUS_MAX_SPLITS_2:
+ max_splits = 2;
+ break;
+ case PCIXM_STATUS_MAX_SPLITS_3:
+ max_splits = 3;
+ break;
+ case PCIXM_STATUS_MAX_SPLITS_4:
+ max_splits = 4;
+ break;
+ case PCIXM_STATUS_MAX_SPLITS_8:
+ max_splits = 8;
+ break;
+ case PCIXM_STATUS_MAX_SPLITS_12:
+ max_splits = 12;
+ break;
+ case PCIXM_STATUS_MAX_SPLITS_16:
+ max_splits = 16;
+ break;
+ case PCIXM_STATUS_MAX_SPLITS_32:
+ max_splits = 32;
+ break;
+ }
+ printf("%s %d burst read, %d split transaction%s", comma ? "," : "",
+ max_burst_read, max_splits, max_splits == 1 ? "" : "s");
+}
+
+static void
+cap_ht(int fd, struct pci_conf *p, uint8_t ptr)
+{
+ uint32_t reg;
+ uint16_t command;
+
+ command = read_config(fd, &p->pc_sel, ptr + PCIR_HT_COMMAND, 2);
+ printf("HT ");
+ if ((command & 0xe000) == PCIM_HTCAP_SLAVE)
+ printf("slave");
+ else if ((command & 0xe000) == PCIM_HTCAP_HOST)
+ printf("host");
+ else
+ switch (command & PCIM_HTCMD_CAP_MASK) {
+ case PCIM_HTCAP_SWITCH:
+ printf("switch");
+ break;
+ case PCIM_HTCAP_INTERRUPT:
+ printf("interrupt");
+ break;
+ case PCIM_HTCAP_REVISION_ID:
+ printf("revision ID");
+ break;
+ case PCIM_HTCAP_UNITID_CLUMPING:
+ printf("unit ID clumping");
+ break;
+ case PCIM_HTCAP_EXT_CONFIG_SPACE:
+ printf("extended config space");
+ break;
+ case PCIM_HTCAP_ADDRESS_MAPPING:
+ printf("address mapping");
+ break;
+ case PCIM_HTCAP_MSI_MAPPING:
+ printf("MSI %saddress window %s at 0x",
+ command & PCIM_HTCMD_MSI_FIXED ? "fixed " : "",
+ command & PCIM_HTCMD_MSI_ENABLE ? "enabled" :
+ "disabled");
+ if (command & PCIM_HTCMD_MSI_FIXED)
+ printf("fee00000");
+ else {
+ reg = read_config(fd, &p->pc_sel,
+ ptr + PCIR_HTMSI_ADDRESS_HI, 4);
+ if (reg != 0)
+ printf("%08x", reg);
+ reg = read_config(fd, &p->pc_sel,
+ ptr + PCIR_HTMSI_ADDRESS_LO, 4);
+ printf("%08x", reg);
+ }
+ break;
+ case PCIM_HTCAP_DIRECT_ROUTE:
+ printf("direct route");
+ break;
+ case PCIM_HTCAP_VCSET:
+ printf("VC set");
+ break;
+ case PCIM_HTCAP_RETRY_MODE:
+ printf("retry mode");
+ break;
+ case PCIM_HTCAP_X86_ENCODING:
+ printf("X86 encoding");
+ break;
+ case PCIM_HTCAP_GEN3:
+ printf("Gen3");
+ break;
+ case PCIM_HTCAP_FLE:
+ printf("function-level extension");
+ break;
+ case PCIM_HTCAP_PM:
+ printf("power management");
+ break;
+ case PCIM_HTCAP_HIGH_NODE_COUNT:
+ printf("high node count");
+ break;
+ default:
+ printf("unknown %02x", command);
+ break;
+ }
+}
+
+static void
+cap_vendor(int fd, struct pci_conf *p, uint8_t ptr)
+{
+ uint8_t length;
+
+ length = read_config(fd, &p->pc_sel, ptr + PCIR_VENDOR_LENGTH, 1);
+ printf("vendor (length %d)", length);
+ if (p->pc_vendor == 0x8086) {
+ /* Intel */
+ uint8_t version;
+
+ version = read_config(fd, &p->pc_sel, ptr + PCIR_VENDOR_DATA,
+ 1);
+ printf(" Intel cap %d version %d", version >> 4, version & 0xf);
+ if (version >> 4 == 1 && length == 12) {
+ /* Feature Detection */
+ uint32_t fvec;
+ int comma;
+
+ comma = 0;
+ fvec = read_config(fd, &p->pc_sel, ptr +
+ PCIR_VENDOR_DATA + 5, 4);
+ printf("\n\t\t features:");
+ if (fvec & (1 << 0)) {
+ printf(" AMT");
+ comma = 1;
+ }
+ fvec = read_config(fd, &p->pc_sel, ptr +
+ PCIR_VENDOR_DATA + 1, 4);
+ if (fvec & (1 << 21)) {
+ printf("%s Quick Resume", comma ? "," : "");
+ comma = 1;
+ }
+ if (fvec & (1 << 18)) {
+ printf("%s SATA RAID-5", comma ? "," : "");
+ comma = 1;
+ }
+ if (fvec & (1 << 9)) {
+ printf("%s Mobile", comma ? "," : "");
+ comma = 1;
+ }
+ if (fvec & (1 << 7)) {
+ printf("%s 6 PCI-e x1 slots", comma ? "," : "");
+ comma = 1;
+ } else {
+ printf("%s 4 PCI-e x1 slots", comma ? "," : "");
+ comma = 1;
+ }
+ if (fvec & (1 << 5)) {
+ printf("%s SATA RAID-0/1/10", comma ? "," : "");
+ comma = 1;
+ }
+ if (fvec & (1 << 3)) {
+ printf("%s SATA AHCI", comma ? "," : "");
+ comma = 1;
+ }
+ }
+ }
+}
+
+static void
+cap_debug(int fd, struct pci_conf *p, uint8_t ptr)
+{
+ uint16_t debug_port;
+
+ debug_port = read_config(fd, &p->pc_sel, ptr + PCIR_DEBUG_PORT, 2);
+ printf("EHCI Debug Port at offset 0x%x in map 0x%x", debug_port &
+ PCIM_DEBUG_PORT_OFFSET, PCIR_BAR(debug_port >> 13));
+}
+
+static void
+cap_subvendor(int fd, struct pci_conf *p, uint8_t ptr)
+{
+ uint32_t id;
+
+ id = read_config(fd, &p->pc_sel, ptr + PCIR_SUBVENDCAP_ID, 4);
+ printf("PCI Bridge card=0x%08x", id);
+}
+
+#define MAX_PAYLOAD(field) (128 << (field))
+
+static const char *
+link_speed_string(uint8_t speed)
+{
+
+ switch (speed) {
+ case 1:
+ return ("2.5");
+ case 2:
+ return ("5.0");
+ case 3:
+ return ("8.0");
+ default:
+ return ("undef");
+ }
+}
+
+static const char *
+aspm_string(uint8_t aspm)
+{
+
+ switch (aspm) {
+ case 1:
+ return ("L0s");
+ case 2:
+ return ("L1");
+ case 3:
+ return ("L0s/L1");
+ default:
+ return ("disabled");
+ }
+}
+
+static void
+cap_express(int fd, struct pci_conf *p, uint8_t ptr)
+{
+ uint32_t cap, cap2;
+ uint16_t ctl, flags, sta;
+
+ flags = read_config(fd, &p->pc_sel, ptr + PCIER_FLAGS, 2);
+ printf("PCI-Express %d ", flags & PCIEM_FLAGS_VERSION);
+ switch (flags & PCIEM_FLAGS_TYPE) {
+ case PCIEM_TYPE_ENDPOINT:
+ printf("endpoint");
+ break;
+ case PCIEM_TYPE_LEGACY_ENDPOINT:
+ printf("legacy endpoint");
+ break;
+ case PCIEM_TYPE_ROOT_PORT:
+ printf("root port");
+ break;
+ case PCIEM_TYPE_UPSTREAM_PORT:
+ printf("upstream port");
+ break;
+ case PCIEM_TYPE_DOWNSTREAM_PORT:
+ printf("downstream port");
+ break;
+ case PCIEM_TYPE_PCI_BRIDGE:
+ printf("PCI bridge");
+ break;
+ case PCIEM_TYPE_PCIE_BRIDGE:
+ printf("PCI to PCIe bridge");
+ break;
+ case PCIEM_TYPE_ROOT_INT_EP:
+ printf("root endpoint");
+ break;
+ case PCIEM_TYPE_ROOT_EC:
+ printf("event collector");
+ break;
+ default:
+ printf("type %d", (flags & PCIEM_FLAGS_TYPE) >> 4);
+ break;
+ }
+ if (flags & PCIEM_FLAGS_SLOT)
+ printf(" slot");
+ if (flags & PCIEM_FLAGS_IRQ)
+ printf(" IRQ %d", (flags & PCIEM_FLAGS_IRQ) >> 9);
+ cap = read_config(fd, &p->pc_sel, ptr + PCIER_DEVICE_CAP, 4);
+ cap2 = read_config(fd, &p->pc_sel, ptr + PCIER_DEVICE_CAP2, 4);
+ ctl = read_config(fd, &p->pc_sel, ptr + PCIER_DEVICE_CTL, 2);
+ printf(" max data %d(%d)",
+ MAX_PAYLOAD((ctl & PCIEM_CTL_MAX_PAYLOAD) >> 5),
+ MAX_PAYLOAD(cap & PCIEM_CAP_MAX_PAYLOAD));
+ if ((cap & PCIEM_CAP_FLR) != 0)
+ printf(" FLR");
+ if (ctl & PCIEM_CTL_RELAXED_ORD_ENABLE)
+ printf(" RO");
+ if (ctl & PCIEM_CTL_NOSNOOP_ENABLE)
+ printf(" NS");
+ cap = read_config(fd, &p->pc_sel, ptr + PCIER_LINK_CAP, 4);
+ sta = read_config(fd, &p->pc_sel, ptr + PCIER_LINK_STA, 2);
+ printf(" link x%d(x%d)", (sta & PCIEM_LINK_STA_WIDTH) >> 4,
+ (cap & PCIEM_LINK_CAP_MAX_WIDTH) >> 4);
+ if ((cap & (PCIEM_LINK_CAP_MAX_WIDTH | PCIEM_LINK_CAP_ASPM)) != 0)
+ printf("\n ");
+ if ((cap & PCIEM_LINK_CAP_MAX_WIDTH) != 0) {
+ printf(" speed %s(%s)", (sta & PCIEM_LINK_STA_WIDTH) == 0 ?
+ "0.0" : link_speed_string(sta & PCIEM_LINK_STA_SPEED),
+ link_speed_string(cap & PCIEM_LINK_CAP_MAX_SPEED));
+ }
+ if ((cap & PCIEM_LINK_CAP_ASPM) != 0) {
+ ctl = read_config(fd, &p->pc_sel, ptr + PCIER_LINK_CTL, 2);
+ printf(" ASPM %s(%s)", aspm_string(ctl & PCIEM_LINK_CTL_ASPMC),
+ aspm_string((cap & PCIEM_LINK_CAP_ASPM) >> 10));
+ }
+ if ((cap2 & PCIEM_CAP2_ARI) != 0) {
+ ctl = read_config(fd, &p->pc_sel, ptr + PCIER_DEVICE_CTL2, 4);
+ printf(" ARI %s",
+ (ctl & PCIEM_CTL2_ARI) ? "enabled" : "disabled");
+ }
+}
+
+static void
+cap_msix(int fd, struct pci_conf *p, uint8_t ptr)
+{
+ uint32_t pba_offset, table_offset, val;
+ int msgnum, pba_bar, table_bar;
+ uint16_t ctrl;
+
+ ctrl = read_config(fd, &p->pc_sel, ptr + PCIR_MSIX_CTRL, 2);
+ msgnum = (ctrl & PCIM_MSIXCTRL_TABLE_SIZE) + 1;
+
+ val = read_config(fd, &p->pc_sel, ptr + PCIR_MSIX_TABLE, 4);
+ table_bar = PCIR_BAR(val & PCIM_MSIX_BIR_MASK);
+ table_offset = val & ~PCIM_MSIX_BIR_MASK;
+
+ val = read_config(fd, &p->pc_sel, ptr + PCIR_MSIX_PBA, 4);
+ pba_bar = PCIR_BAR(val & PCIM_MSIX_BIR_MASK);
+ pba_offset = val & ~PCIM_MSIX_BIR_MASK;
+
+ printf("MSI-X supports %d message%s%s\n", msgnum,
+ (msgnum == 1) ? "" : "s",
+ (ctrl & PCIM_MSIXCTRL_MSIX_ENABLE) ? ", enabled" : "");
+
+ printf(" ");
+ printf("Table in map 0x%x[0x%x], PBA in map 0x%x[0x%x]",
+ table_bar, table_offset, pba_bar, pba_offset);
+}
+
+static void
+cap_sata(int fd, struct pci_conf *p, uint8_t ptr)
+{
+
+ printf("SATA Index-Data Pair");
+}
+
+static void
+cap_pciaf(int fd, struct pci_conf *p, uint8_t ptr)
+{
+ uint8_t cap;
+
+ cap = read_config(fd, &p->pc_sel, ptr + PCIR_PCIAF_CAP, 1);
+ printf("PCI Advanced Features:%s%s",
+ cap & PCIM_PCIAFCAP_FLR ? " FLR" : "",
+ cap & PCIM_PCIAFCAP_TP ? " TP" : "");
+}
+
+void
+list_caps(int fd, struct pci_conf *p)
+{
+ int express;
+ uint16_t sta;
+ uint8_t ptr, cap;
+
+ /* Are capabilities present for this device? */
+ sta = read_config(fd, &p->pc_sel, PCIR_STATUS, 2);
+ if (!(sta & PCIM_STATUS_CAPPRESENT))
+ return;
+
+ switch (p->pc_hdr & PCIM_HDRTYPE) {
+ case PCIM_HDRTYPE_NORMAL:
+ case PCIM_HDRTYPE_BRIDGE:
+ ptr = PCIR_CAP_PTR;
+ break;
+ case PCIM_HDRTYPE_CARDBUS:
+ ptr = PCIR_CAP_PTR_2;
+ break;
+ default:
+ errx(1, "list_caps: bad header type");
+ }
+
+ /* Walk the capability list. */
+ express = 0;
+ ptr = read_config(fd, &p->pc_sel, ptr, 1);
+ while (ptr != 0 && ptr != 0xff) {
+ cap = read_config(fd, &p->pc_sel, ptr + PCICAP_ID, 1);
+ printf(" cap %02x[%02x] = ", cap, ptr);
+ switch (cap) {
+ case PCIY_PMG:
+ cap_power(fd, p, ptr);
+ break;
+ case PCIY_AGP:
+ cap_agp(fd, p, ptr);
+ break;
+ case PCIY_VPD:
+ cap_vpd(fd, p, ptr);
+ break;
+ case PCIY_MSI:
+ cap_msi(fd, p, ptr);
+ break;
+ case PCIY_PCIX:
+ cap_pcix(fd, p, ptr);
+ break;
+ case PCIY_HT:
+ cap_ht(fd, p, ptr);
+ break;
+ case PCIY_VENDOR:
+ cap_vendor(fd, p, ptr);
+ break;
+ case PCIY_DEBUG:
+ cap_debug(fd, p, ptr);
+ break;
+ case PCIY_SUBVENDOR:
+ cap_subvendor(fd, p, ptr);
+ break;
+ case PCIY_EXPRESS:
+ express = 1;
+ cap_express(fd, p, ptr);
+ break;
+ case PCIY_MSIX:
+ cap_msix(fd, p, ptr);
+ break;
+ case PCIY_SATA:
+ cap_sata(fd, p, ptr);
+ break;
+ case PCIY_PCIAF:
+ cap_pciaf(fd, p, ptr);
+ break;
+ default:
+ printf("unknown");
+ break;
+ }
+ printf("\n");
+ ptr = read_config(fd, &p->pc_sel, ptr + PCICAP_NEXTPTR, 1);
+ }
+
+ if (express)
+ list_ecaps(fd, p);
+}
+
+/* From <sys/systm.h>. */
+static __inline 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 void
+ecap_aer(int fd, struct pci_conf *p, uint16_t ptr, uint8_t ver)
+{
+ uint32_t sta, mask;
+
+ printf("AER %d", ver);
+ if (ver < 1)
+ return;
+ sta = read_config(fd, &p->pc_sel, ptr + PCIR_AER_UC_STATUS, 4);
+ mask = read_config(fd, &p->pc_sel, ptr + PCIR_AER_UC_SEVERITY, 4);
+ printf(" %d fatal", bitcount32(sta & mask));
+ printf(" %d non-fatal", bitcount32(sta & ~mask));
+ sta = read_config(fd, &p->pc_sel, ptr + PCIR_AER_COR_STATUS, 4);
+ printf(" %d corrected\n", bitcount32(sta));
+}
+
+static void
+ecap_vc(int fd, struct pci_conf *p, uint16_t ptr, uint8_t ver)
+{
+ uint32_t cap1;
+
+ printf("VC %d", ver);
+ if (ver < 1)
+ return;
+ cap1 = read_config(fd, &p->pc_sel, ptr + PCIR_VC_CAP1, 4);
+ printf(" max VC%d", cap1 & PCIM_VC_CAP1_EXT_COUNT);
+ if ((cap1 & PCIM_VC_CAP1_LOWPRI_EXT_COUNT) != 0)
+ printf(" lowpri VC0-VC%d",
+ (cap1 & PCIM_VC_CAP1_LOWPRI_EXT_COUNT) >> 4);
+ printf("\n");
+}
+
+static void
+ecap_sernum(int fd, struct pci_conf *p, uint16_t ptr, uint8_t ver)
+{
+ uint32_t high, low;
+
+ printf("Serial %d", ver);
+ if (ver < 1)
+ return;
+ low = read_config(fd, &p->pc_sel, ptr + PCIR_SERIAL_LOW, 4);
+ high = read_config(fd, &p->pc_sel, ptr + PCIR_SERIAL_HIGH, 4);
+ printf(" %08x%08x\n", high, low);
+}
+
+static void
+ecap_vendor(int fd, struct pci_conf *p, uint16_t ptr, uint8_t ver)
+{
+ uint32_t val;
+
+ printf("Vendor %d", ver);
+ if (ver < 1)
+ return;
+ val = read_config(fd, &p->pc_sel, ptr + 4, 4);
+ printf(" ID %d\n", val & 0xffff);
+}
+
+static void
+ecap_sec_pcie(int fd, struct pci_conf *p, uint16_t ptr, uint8_t ver)
+{
+ uint32_t val;
+
+ printf("PCIe Sec %d", ver);
+ if (ver < 1)
+ return;
+ val = read_config(fd, &p->pc_sel, ptr + 8, 4);
+ printf(" lane errors %#x\n", val);
+}
+
+static const char *
+check_enabled(int value)
+{
+
+ return (value ? "enabled" : "disabled");
+}
+
+static void
+ecap_sriov(int fd, struct pci_conf *p, uint16_t ptr, uint8_t ver)
+{
+ const char *comma, *enabled;
+ uint16_t iov_ctl, total_vfs, num_vfs, vf_offset, vf_stride, vf_did;
+ uint32_t page_caps, page_size, page_shift, size;
+ int i;
+
+ printf("SR-IOV %d ", ver);
+
+ iov_ctl = read_config(fd, &p->pc_sel, ptr + PCIR_SRIOV_CTL, 2);
+ printf("IOV %s, Memory Space %s, ARI %s\n",
+ check_enabled(iov_ctl & PCIM_SRIOV_VF_EN),
+ check_enabled(iov_ctl & PCIM_SRIOV_VF_MSE),
+ check_enabled(iov_ctl & PCIM_SRIOV_ARI_EN));
+
+ total_vfs = read_config(fd, &p->pc_sel, ptr + PCIR_SRIOV_TOTAL_VFS, 2);
+ num_vfs = read_config(fd, &p->pc_sel, ptr + PCIR_SRIOV_NUM_VFS, 2);
+ printf(" ");
+ printf("%d VFs configured out of %d supported\n", num_vfs, total_vfs);
+
+ vf_offset = read_config(fd, &p->pc_sel, ptr + PCIR_SRIOV_VF_OFF, 2);
+ vf_stride = read_config(fd, &p->pc_sel, ptr + PCIR_SRIOV_VF_STRIDE, 2);
+ printf(" ");
+ printf("First VF RID Offset 0x%04x, VF RID Stride 0x%04x\n", vf_offset,
+ vf_stride);
+
+ vf_did = read_config(fd, &p->pc_sel, ptr + PCIR_SRIOV_VF_DID, 2);
+ printf(" VF Device ID 0x%04x\n", vf_did);
+
+ page_caps = read_config(fd, &p->pc_sel, ptr + PCIR_SRIOV_PAGE_CAP, 4);
+ page_size = read_config(fd, &p->pc_sel, ptr + PCIR_SRIOV_PAGE_SIZE, 4);
+ printf(" ");
+ printf("Page Sizes: ");
+ comma = "";
+ while (page_caps != 0) {
+ page_shift = ffs(page_caps) - 1;
+
+ if (page_caps & page_size)
+ enabled = " (enabled)";
+ else
+ enabled = "";
+
+ size = (1 << (page_shift + PCI_SRIOV_BASE_PAGE_SHIFT));
+ printf("%s%d%s", comma, size, enabled);
+ comma = ", ";
+
+ page_caps &= ~(1 << page_shift);
+ }
+ printf("\n");
+
+ for (i = 0; i <= PCIR_MAX_BAR_0; i++)
+ print_bar(fd, p, "iov bar ", ptr + PCIR_SRIOV_BAR(i));
+}
+
+struct {
+ uint16_t id;
+ const char *name;
+} ecap_names[] = {
+ { PCIZ_PWRBDGT, "Power Budgeting" },
+ { PCIZ_RCLINK_DCL, "Root Complex Link Declaration" },
+ { PCIZ_RCLINK_CTL, "Root Complex Internal Link Control" },
+ { PCIZ_RCEC_ASSOC, "Root Complex Event Collector ASsociation" },
+ { PCIZ_MFVC, "MFVC" },
+ { PCIZ_RCRB, "RCRB" },
+ { PCIZ_ACS, "ACS" },
+ { PCIZ_ARI, "ARI" },
+ { PCIZ_ATS, "ATS" },
+ { PCIZ_MULTICAST, "Multicast" },
+ { PCIZ_RESIZE_BAR, "Resizable BAR" },
+ { PCIZ_DPA, "DPA" },
+ { PCIZ_TPH_REQ, "TPH Requester" },
+ { PCIZ_LTR, "LTR" },
+ { 0, NULL }
+};
+
+static void
+list_ecaps(int fd, struct pci_conf *p)
+{
+ const char *name;
+ uint32_t ecap;
+ uint16_t ptr;
+ int i;
+
+ ptr = PCIR_EXTCAP;
+ ecap = read_config(fd, &p->pc_sel, ptr, 4);
+ if (ecap == 0xffffffff || ecap == 0)
+ return;
+ for (;;) {
+ printf(" ecap %04x[%03x] = ", PCI_EXTCAP_ID(ecap), ptr);
+ switch (PCI_EXTCAP_ID(ecap)) {
+ case PCIZ_AER:
+ ecap_aer(fd, p, ptr, PCI_EXTCAP_VER(ecap));
+ break;
+ case PCIZ_VC:
+ ecap_vc(fd, p, ptr, PCI_EXTCAP_VER(ecap));
+ break;
+ case PCIZ_SERNUM:
+ ecap_sernum(fd, p, ptr, PCI_EXTCAP_VER(ecap));
+ break;
+ case PCIZ_VENDOR:
+ ecap_vendor(fd, p, ptr, PCI_EXTCAP_VER(ecap));
+ break;
+ case PCIZ_SEC_PCIE:
+ ecap_sec_pcie(fd, p, ptr, PCI_EXTCAP_VER(ecap));
+ break;
+ case PCIZ_SRIOV:
+ ecap_sriov(fd, p, ptr, PCI_EXTCAP_VER(ecap));
+ break;
+ default:
+ name = "unknown";
+ for (i = 0; ecap_names[i].name != NULL; i++)
+ if (ecap_names[i].id == PCI_EXTCAP_ID(ecap)) {
+ name = ecap_names[i].name;
+ break;
+ }
+ printf("%s %d\n", name, PCI_EXTCAP_VER(ecap));
+ break;
+ }
+ ptr = PCI_EXTCAP_NEXTPTR(ecap);
+ if (ptr == 0)
+ break;
+ ecap = read_config(fd, &p->pc_sel, ptr, 4);
+ }
+}
+
+/* Find offset of a specific capability. Returns 0 on failure. */
+uint8_t
+pci_find_cap(int fd, struct pci_conf *p, uint8_t id)
+{
+ uint16_t sta;
+ uint8_t ptr, cap;
+
+ /* Are capabilities present for this device? */
+ sta = read_config(fd, &p->pc_sel, PCIR_STATUS, 2);
+ if (!(sta & PCIM_STATUS_CAPPRESENT))
+ return (0);
+
+ switch (p->pc_hdr & PCIM_HDRTYPE) {
+ case PCIM_HDRTYPE_NORMAL:
+ case PCIM_HDRTYPE_BRIDGE:
+ ptr = PCIR_CAP_PTR;
+ break;
+ case PCIM_HDRTYPE_CARDBUS:
+ ptr = PCIR_CAP_PTR_2;
+ break;
+ default:
+ return (0);
+ }
+
+ ptr = read_config(fd, &p->pc_sel, ptr, 1);
+ while (ptr != 0 && ptr != 0xff) {
+ cap = read_config(fd, &p->pc_sel, ptr + PCICAP_ID, 1);
+ if (cap == id)
+ return (ptr);
+ ptr = read_config(fd, &p->pc_sel, ptr + PCICAP_NEXTPTR, 1);
+ }
+ return (0);
+}
+
+/* Find offset of a specific extended capability. Returns 0 on failure. */
+uint16_t
+pcie_find_cap(int fd, struct pci_conf *p, uint16_t id)
+{
+ uint32_t ecap;
+ uint16_t ptr;
+
+ ptr = PCIR_EXTCAP;
+ ecap = read_config(fd, &p->pc_sel, ptr, 4);
+ if (ecap == 0xffffffff || ecap == 0)
+ return (0);
+ for (;;) {
+ if (PCI_EXTCAP_ID(ecap) == id)
+ return (ptr);
+ ptr = PCI_EXTCAP_NEXTPTR(ecap);
+ if (ptr == 0)
+ break;
+ ecap = read_config(fd, &p->pc_sel, ptr, 4);
+ }
+ return (0);
+}
diff --git a/usr.sbin/pciconf/err.c b/usr.sbin/pciconf/err.c
new file mode 100644
index 0000000..7a77903
--- /dev/null
+++ b/usr.sbin/pciconf/err.c
@@ -0,0 +1,173 @@
+/*-
+ * Copyright (c) 2012 Hudson River Trading LLC
+ * Written by: John H. 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.
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/pciio.h>
+
+#include <err.h>
+#include <stdio.h>
+
+#include <dev/pci/pcireg.h>
+
+#include "pciconf.h"
+
+struct bit_table {
+ uint32_t mask;
+ const char *desc;
+};
+
+/* Error indicators in the PCI status register (PCIR_STATUS). */
+static struct bit_table pci_status[] = {
+ { PCIM_STATUS_MDPERR, "Master Data Parity Error" },
+ { PCIM_STATUS_STABORT, "Sent Target-Abort" },
+ { PCIM_STATUS_RTABORT, "Received Target-Abort" },
+ { PCIM_STATUS_RMABORT, "Received Master-Abort" },
+ { PCIM_STATUS_SERR, "Signalled System Error" },
+ { PCIM_STATUS_PERR, "Detected Parity Error" },
+ { 0, NULL },
+};
+
+/* Valid error indicator bits in PCIR_STATUS. */
+#define PCI_ERRORS (PCIM_STATUS_MDPERR | PCIM_STATUS_STABORT | \
+ PCIM_STATUS_RTABORT | PCIM_STATUS_RMABORT | \
+ PCIM_STATUS_SERR | PCIM_STATUS_PERR)
+
+/* Error indicators in the PCI-Express device status register. */
+static struct bit_table pcie_device_status[] = {
+ { PCIEM_STA_CORRECTABLE_ERROR, "Correctable Error Detected" },
+ { PCIEM_STA_NON_FATAL_ERROR, "Non-Fatal Error Detected" },
+ { PCIEM_STA_FATAL_ERROR, "Fatal Error Detected" },
+ { PCIEM_STA_UNSUPPORTED_REQ, "Unsupported Request Detected" },
+ { 0, NULL },
+};
+
+/* Valid error indicator bits in the PCI-Express device status register. */
+#define PCIE_ERRORS (PCIEM_STA_CORRECTABLE_ERROR | \
+ PCIEM_STA_NON_FATAL_ERROR | \
+ PCIEM_STA_FATAL_ERROR | \
+ PCIEM_STA_UNSUPPORTED_REQ)
+
+/* AER Uncorrected errors. */
+static struct bit_table aer_uc[] = {
+ { PCIM_AER_UC_TRAINING_ERROR, "Link Training Error" },
+ { PCIM_AER_UC_DL_PROTOCOL_ERROR, "Data Link Protocol Error" },
+ { PCIM_AER_UC_SURPRISE_LINK_DOWN, "Surprise Link Down Error" },
+ { PCIM_AER_UC_POISONED_TLP, "Poisoned TLP" },
+ { PCIM_AER_UC_FC_PROTOCOL_ERROR, "Flow Control Protocol Error" },
+ { PCIM_AER_UC_COMPLETION_TIMEOUT, "Completion Timeout" },
+ { PCIM_AER_UC_COMPLETER_ABORT, "Completer Abort" },
+ { PCIM_AER_UC_UNEXPECTED_COMPLETION, "Unexpected Completion" },
+ { PCIM_AER_UC_RECEIVER_OVERFLOW, "Receiver Overflow Error" },
+ { PCIM_AER_UC_MALFORMED_TLP, "Malformed TLP" },
+ { PCIM_AER_UC_ECRC_ERROR, "ECRC Error" },
+ { PCIM_AER_UC_UNSUPPORTED_REQUEST, "Unsupported Request" },
+ { PCIM_AER_UC_ACS_VIOLATION, "ACS Violation" },
+ { PCIM_AER_UC_INTERNAL_ERROR, "Uncorrectable Internal Error" },
+ { PCIM_AER_UC_MC_BLOCKED_TLP, "MC Blocked TLP" },
+ { PCIM_AER_UC_ATOMIC_EGRESS_BLK, "AtomicOp Egress Blocked" },
+ { PCIM_AER_UC_TLP_PREFIX_BLOCKED, "TLP Prefix Blocked Error" },
+ { 0, NULL },
+};
+
+/* AER Corrected errors. */
+static struct bit_table aer_cor[] = {
+ { PCIM_AER_COR_RECEIVER_ERROR, "Receiver Error" },
+ { PCIM_AER_COR_BAD_TLP, "Bad TLP" },
+ { PCIM_AER_COR_BAD_DLLP, "Bad DLLP" },
+ { PCIM_AER_COR_REPLAY_ROLLOVER, "REPLAY_NUM Rollover" },
+ { PCIM_AER_COR_REPLAY_TIMEOUT, "Replay Timer Timeout" },
+ { PCIM_AER_COR_ADVISORY_NF_ERROR, "Advisory Non-Fatal Error" },
+ { PCIM_AER_COR_INTERNAL_ERROR, "Corrected Internal Error" },
+ { PCIM_AER_COR_HEADER_LOG_OVFLOW, "Header Log Overflow" },
+ { 0, NULL },
+};
+
+static void
+print_bits(const char *header, struct bit_table *table, uint32_t mask)
+{
+ int first;
+
+ first = 1;
+ for (; table->desc != NULL; table++)
+ if (mask & table->mask) {
+ if (first) {
+ printf("%14s = ", header);
+ first = 0;
+ } else
+ printf(" ");
+ printf("%s\n", table->desc);
+ mask &= ~table->mask;
+ }
+ if (mask != 0) {
+ if (first)
+ printf("%14s = ", header);
+ else
+ printf(" ");
+ printf("Unknown: 0x%08x\n", mask);
+ }
+}
+
+void
+list_errors(int fd, struct pci_conf *p)
+{
+ uint32_t mask, severity;
+ uint16_t sta, aer;
+ uint8_t pcie;
+
+ /* First check for standard PCI errors. */
+ sta = read_config(fd, &p->pc_sel, PCIR_STATUS, 2);
+ print_bits("PCI errors", pci_status, sta & PCI_ERRORS);
+
+ /* See if this is a PCI-express device. */
+ pcie = pci_find_cap(fd, p, PCIY_EXPRESS);
+ if (pcie == 0)
+ return;
+
+ /* Check for PCI-e errors. */
+ sta = read_config(fd, &p->pc_sel, pcie + PCIER_DEVICE_STA, 2);
+ print_bits("PCI-e errors", pcie_device_status, sta & PCIE_ERRORS);
+
+ /* See if this device supports AER. */
+ aer = pcie_find_cap(fd, p, PCIZ_AER);
+ if (aer == 0)
+ return;
+
+ /* Check for uncorrected errors. */
+ mask = read_config(fd, &p->pc_sel, aer + PCIR_AER_UC_STATUS, 4);
+ severity = read_config(fd, &p->pc_sel, aer + PCIR_AER_UC_SEVERITY, 4);
+ print_bits("Fatal", aer_uc, mask & severity);
+ print_bits("Non-fatal", aer_uc, mask & ~severity);
+
+ /* Check for corrected errors. */
+ mask = read_config(fd, &p->pc_sel, aer + PCIR_AER_COR_STATUS, 4);
+ print_bits("Corrected", aer_cor, mask);
+}
diff --git a/usr.sbin/pciconf/pathnames.h b/usr.sbin/pciconf/pathnames.h
new file mode 100644
index 0000000..61c0993
--- /dev/null
+++ b/usr.sbin/pciconf/pathnames.h
@@ -0,0 +1,4 @@
+/* $FreeBSD$ */
+#define _PATH_DEVPCI "/dev/pci"
+#define _PATH_PCIVDB "/usr/share/misc/pci_vendors"
+#define _PATH_LPCIVDB "/usr/local/share/pciids/pci.ids"
diff --git a/usr.sbin/pciconf/pciconf.8 b/usr.sbin/pciconf/pciconf.8
new file mode 100644
index 0000000..705b594
--- /dev/null
+++ b/usr.sbin/pciconf/pciconf.8
@@ -0,0 +1,370 @@
+.\" Copyright (c) 1997
+.\" Stefan Esser <se@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 November 23, 2015
+.Dt PCICONF 8
+.Os
+.Sh NAME
+.Nm pciconf
+.Nd diagnostic utility for the PCI bus
+.Sh SYNOPSIS
+.Nm
+.Fl l Oo Fl BbceVv Oc Op Ar device
+.Nm
+.Fl a Ar device
+.Nm
+.Fl r Oo Fl b | h Oc Ar device addr Ns Op : Ns Ar addr2
+.Nm
+.Fl w Oo Fl b | h Oc Ar device addr value
+.Sh DESCRIPTION
+The
+.Nm
+utility provides a command line interface to functionality provided by the
+.Xr pci 4
+.Xr ioctl 2
+interface.
+As such, some of the functions are only available to users with write
+access to
+.Pa /dev/pci ,
+normally only the super-user.
+.Pp
+With the
+.Fl l
+option,
+.Nm
+lists PCI devices in the following format:
+.Bd -literal
+foo0@pci0:0:4:0: class=0x010000 card=0x00000000 chip=0x000f1000 rev=0x01 \
+hdr=0x00
+bar0@pci0:0:5:0: class=0x000100 card=0x00000000 chip=0x88c15333 rev=0x00 \
+hdr=0x00
+none0@pci0:0:6:0: class=0x020000 card=0x00000000 chip=0x802910ec rev=0x00 \
+hdr=0x00
+.Ed
+.Pp
+The first column gives the
+driver name, unit number, and selector .
+If there is no driver attached to the
+.Tn PCI
+device in question, the driver name will be
+.Dq none .
+Unit numbers for detached devices start at zero and are incremented for
+each detached device that is encountered.
+The selector
+is in a form which may directly be used for the other forms of the command.
+The second column is the class code, with the class byte printed as two
+hex digits, followed by the sub-class and the interface bytes.
+The third column gives the contents of the subvendorid register, introduced
+in revision 2.1 of the
+.Tn PCI
+standard.
+Note that it will be 0 for older cards.
+The field consists of the card ID in the upper
+half and the card vendor ID in the lower half of the value.
+.Pp
+The fourth column contains the chip device ID, which identifies the chip
+this card is based on.
+It consists of two fields, identifying the chip and
+its vendor, as above.
+The fifth column prints the chip's revision.
+The sixth column describes the header type.
+Currently assigned header types include 0 for most devices,
+1 for
+.Tn PCI
+to
+.Tn PCI
+bridges, and 2 for
+.Tn PCI
+to
+.Tn CardBus
+bridges.
+If the most significant bit
+of the header type register is set for
+function 0 of a
+.Tn PCI
+device, it is a
+.Em multi-function
+device, which contains several (similar or independent) functions on
+one chip.
+.Pp
+If the
+.Fl B
+option is supplied,
+.Nm
+will list additional information for
+.Tn PCI
+to
+.Tn PCI
+and
+.Tn PCI
+to
+.Tn CardBus
+bridges,
+specifically the resource ranges decoded by the bridge for use by devices
+behind the bridge.
+Each bridge lists a range of bus numbers handled by the bridge and its
+downstream devices.
+Memory and I/O port decoding windows are enumerated via a line in the
+following format:
+.Bd -literal
+ window[1c] = type I/O Port, range 16, addr 0x5000-0x8fff, enabled
+.Ed
+.Pp
+The first value after the
+.Dq Li window
+prefix in the square brackets is the offset of the decoding window in
+config space in hexadecimal.
+The type of a window is one of
+.Dq Memory ,
+.Dq Prefetchable Memory ,
+or
+.Dq I/O Port .
+The range indicates the binary log of the maximum address the window decodes.
+The address field indicates the start and end addresses of the decoded range.
+Finally, the last flag indicates if the window is enabled or disabled.
+.Pp
+If the
+.Fl b
+option is supplied,
+.Nm
+will list any base address registers
+.Pq BARs
+that are assigned resources for each device.
+Each BAR will be enumerated via a line in the following format:
+.Bd -literal
+ bar [10] = type Memory, range 32, base 0xda060000, size 131072, enabled
+.Ed
+.Pp
+The first value after the
+.Dq Li bar
+prefix in the square brackets is the offset of the BAR in config space in
+hexadecimal.
+The type of a BAR is one of
+.Dq Memory ,
+.Dq Prefetchable Memory ,
+or
+.Dq I/O Port .
+The range indicates the binary log of the maximum address the BAR decodes.
+The base and size indicate the start and length of the BAR's address window,
+respectively.
+Finally, the last flag indicates if the BAR is enabled or disabled.
+.Pp
+If the
+.Fl c
+option is supplied,
+.Nm
+will list any capabilities supported by each device.
+Each capability is enumerated via a line in the following format:
+.Bd -literal
+ cap 10[40] = PCI-Express 1 root port
+.Ed
+.Pp
+The first value after the
+.Dq Li cap
+prefix is the capability ID in hexadecimal.
+The second value in the square brackets is the offset of the capability
+in config space in hexadecimal.
+The format of the text after the equals sign is capability-specific.
+.Pp
+Each extended capability is enumerated via a line in a similar format:
+.Bd -literal
+ecap 0002[100] = VC 1 max VC0
+.Ed
+.Pp
+The first value after the
+.Dq Li ecap
+prefix is the extended capability ID in hexadecimal.
+The second value in the square brackets is the offset of the extended
+capability in config space in hexadecimal.
+The format of the text after the equals sign is capability-specific.
+.Pp
+If the
+.Fl e
+option is supplied,
+.Nm
+will list any errors reported for this device in standard PCI error registers.
+Errors are checked for in the PCI status register,
+the PCI-express device status register,
+and the Advanced Error Reporting status registers.
+.Pp
+If the
+.Fl v
+option is supplied,
+.Nm
+will attempt to load the vendor/device information database, and print
+vendor, device, class and subclass identification strings for each device.
+.Pp
+If the
+.Fl V
+option is supplied,
+.Nm
+will list any vital product data
+.Pq VPD
+provided by each device.
+Each VPD keyword is enumerated via a line in the following format:
+.Bd -literal
+ VPD ro PN = '110114640C0 '
+.Ed
+.Pp
+The first string after the
+.Dq Li VPD
+prefix indicates if the keyword is read-only
+.Dq ro
+or read-write
+.Dq rw .
+The second string provides the keyword name.
+The text after the the equals sign lists the value of the keyword which is
+usually an ASCII string.
+.Pp
+If the optional
+.Ar device
+argument is given with the
+.Fl l
+flag,
+.Nm
+will only list details about a single device instead of all devices.
+.Pp
+All invocations of
+.Nm
+except for
+.Fl l
+require a
+.Ar device .
+The device can be identified either by a device name if the device is
+attached to a driver or by a selector.
+Selectors identify a PCI device by its address in PCI config space and
+can take one of the following forms:
+.Pp
+.Bl -bullet -offset indent -compact
+.It
+.Li pci Ns Va domain Ns \&: Ns Va bus Ns \&: Ns Va device Ns \&: \
+Ns Va function Ns
+.It
+.Li pci Ns Va bus Ns \&: Ns Va device Ns \&: Ns Va function Ns
+.It
+.Li pci Ns Va bus Ns \&: Ns Va device Ns
+.El
+.Pp
+In the case of an abridged form, omitted selector components are assumed to be 0.
+An optional leading device name followed by @ and an optional final colon
+will be ignored; this is so that the first column in the output of
+.Nm
+.Fl l
+can be used without modification.
+All numbers are base 10.
+.Pp
+With the
+.Fl a
+flag,
+.Nm
+determines whether any driver has been assigned to the device
+identified by
+.Ar selector .
+An exit status of zero indicates that the device has a driver;
+non-zero indicates that it does not.
+.Pp
+The
+.Fl r
+option reads a configuration space register at byte offset
+.Ar addr
+of device
+.Ar selector
+and prints out its value in hexadecimal.
+The optional second address
+.Ar addr2
+specifies a range to read.
+The
+.Fl w
+option writes the
+.Ar value
+into a configuration space register at byte offset
+.Ar addr
+of device
+.Ar selector .
+For both operations, the flags
+.Fl b
+and
+.Fl h
+select the width of the operation;
+.Fl b
+indicates a byte operation, and
+.Fl h
+indicates a halfword (two-byte) operation.
+The default is to read or
+write a longword (four bytes).
+.Sh ENVIRONMENT
+PCI vendor and device information is read from
+.Pa /usr/local/share/pciids/pci.ids .
+If that file is not present, it is read from
+.Pa /usr/share/misc/pci_vendors .
+This path can be overridden by setting the environment variable
+.Ev PCICONF_VENDOR_DATABASE .
+.Sh SEE ALSO
+.Xr ioctl 2 ,
+.\" .Xr pci 4 ,
+.Xr devinfo 8 ,
+.Xr kldload 8
+.Sh HISTORY
+The
+.Nm
+utility appeared first in
+.Fx 2.2 .
+The
+.Fl a
+option was added for
+.Tn PCI
+KLD support in
+.Fx 3.0 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility was written by
+.An Stefan Esser
+and
+.An Garrett Wollman .
+.Sh BUGS
+The
+.Fl b
+and
+.Fl h
+options are implemented in
+.Nm ,
+but not in the underlying
+.Xr ioctl 2 .
+.Pp
+It might be useful to give non-root users access to the
+.Fl a
+and
+.Fl r
+options.
+But only root will be able to execute a
+.Nm kldload
+to provide the device with a driver KLD, and reading of configuration space
+registers may cause a failure in badly designed
+.Tn PCI
+chips.
diff --git a/usr.sbin/pciconf/pciconf.c b/usr.sbin/pciconf/pciconf.c
new file mode 100644
index 0000000..194da6b
--- /dev/null
+++ b/usr.sbin/pciconf/pciconf.c
@@ -0,0 +1,1024 @@
+/*
+ * Copyright 1996 Massachusetts Institute of Technology
+ *
+ * Permission to use, copy, modify, and distribute this software and
+ * its documentation for any purpose and without fee is hereby
+ * granted, provided that both the above copyright notice and this
+ * permission notice appear in all copies, that both the above
+ * copyright notice and this permission notice appear in all
+ * supporting documentation, and that the name of M.I.T. not be used
+ * in advertising or publicity pertaining to distribution of the
+ * software without specific, written prior permission. M.I.T. makes
+ * no representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS
+ * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
+ * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (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/fcntl.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/pciio.h>
+#include <sys/queue.h>
+
+#include <dev/pci/pcireg.h>
+
+#include "pathnames.h"
+#include "pciconf.h"
+
+struct pci_device_info
+{
+ TAILQ_ENTRY(pci_device_info) link;
+ int id;
+ char *desc;
+};
+
+struct pci_vendor_info
+{
+ TAILQ_ENTRY(pci_vendor_info) link;
+ TAILQ_HEAD(,pci_device_info) devs;
+ int id;
+ char *desc;
+};
+
+TAILQ_HEAD(,pci_vendor_info) pci_vendors;
+
+static struct pcisel getsel(const char *str);
+static void list_bridge(int fd, struct pci_conf *p);
+static void list_bars(int fd, struct pci_conf *p);
+static void list_devs(const char *name, int verbose, int bars, int bridge,
+ int caps, int errors, int vpd);
+static void list_verbose(struct pci_conf *p);
+static void list_vpd(int fd, struct pci_conf *p);
+static const char *guess_class(struct pci_conf *p);
+static const char *guess_subclass(struct pci_conf *p);
+static int load_vendors(void);
+static void readit(const char *, const char *, int);
+static void writeit(const char *, const char *, const char *, int);
+static void chkattached(const char *);
+
+static int exitstatus = 0;
+
+static void
+usage(void)
+{
+ fprintf(stderr, "%s\n%s\n%s\n%s\n",
+ "usage: pciconf -l [-BbcevV] [device]",
+ " pciconf -a device",
+ " pciconf -r [-b | -h] device addr[:addr2]",
+ " pciconf -w [-b | -h] device addr value");
+ exit (1);
+}
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ int listmode, readmode, writemode, attachedmode;
+ int bars, bridge, caps, errors, verbose, vpd;
+ int byte, isshort;
+
+ listmode = readmode = writemode = attachedmode = 0;
+ bars = bridge = caps = errors = verbose = vpd = byte = isshort = 0;
+
+ while ((c = getopt(argc, argv, "aBbcehlrwVv")) != -1) {
+ switch(c) {
+ case 'a':
+ attachedmode = 1;
+ break;
+
+ case 'B':
+ bridge = 1;
+ break;
+
+ case 'b':
+ bars = 1;
+ byte = 1;
+ break;
+
+ case 'c':
+ caps = 1;
+ break;
+
+ case 'e':
+ errors = 1;
+ break;
+
+ case 'h':
+ isshort = 1;
+ break;
+
+ case 'l':
+ listmode = 1;
+ break;
+
+ case 'r':
+ readmode = 1;
+ break;
+
+ case 'w':
+ writemode = 1;
+ break;
+
+ case 'v':
+ verbose = 1;
+ break;
+
+ case 'V':
+ vpd = 1;
+ break;
+
+ default:
+ usage();
+ }
+ }
+
+ if ((listmode && optind >= argc + 1)
+ || (writemode && optind + 3 != argc)
+ || (readmode && optind + 2 != argc)
+ || (attachedmode && optind + 1 != argc))
+ usage();
+
+ if (listmode) {
+ list_devs(optind + 1 == argc ? argv[optind] : NULL, verbose,
+ bars, bridge, caps, errors, vpd);
+ } else if (attachedmode) {
+ chkattached(argv[optind]);
+ } else if (readmode) {
+ readit(argv[optind], argv[optind + 1],
+ byte ? 1 : isshort ? 2 : 4);
+ } else if (writemode) {
+ writeit(argv[optind], argv[optind + 1], argv[optind + 2],
+ byte ? 1 : isshort ? 2 : 4);
+ } else {
+ usage();
+ }
+
+ return exitstatus;
+}
+
+static void
+list_devs(const char *name, int verbose, int bars, int bridge, int caps,
+ int errors, int vpd)
+{
+ int fd;
+ struct pci_conf_io pc;
+ struct pci_conf conf[255], *p;
+ struct pci_match_conf patterns[1];
+ int none_count = 0;
+
+ if (verbose)
+ load_vendors();
+
+ fd = open(_PATH_DEVPCI, (bridge || caps || errors) ? O_RDWR : O_RDONLY,
+ 0);
+ if (fd < 0)
+ err(1, "%s", _PATH_DEVPCI);
+
+ bzero(&pc, sizeof(struct pci_conf_io));
+ pc.match_buf_len = sizeof(conf);
+ pc.matches = conf;
+ if (name != NULL) {
+ bzero(&patterns, sizeof(patterns));
+ patterns[0].pc_sel = getsel(name);
+ patterns[0].flags = PCI_GETCONF_MATCH_DOMAIN |
+ PCI_GETCONF_MATCH_BUS | PCI_GETCONF_MATCH_DEV |
+ PCI_GETCONF_MATCH_FUNC;
+ pc.num_patterns = 1;
+ pc.pat_buf_len = sizeof(patterns);
+ pc.patterns = patterns;
+ }
+
+ do {
+ if (ioctl(fd, PCIOCGETCONF, &pc) == -1)
+ err(1, "ioctl(PCIOCGETCONF)");
+
+ /*
+ * 255 entries should be more than enough for most people,
+ * but if someone has more devices, and then changes things
+ * around between ioctls, we'll do the cheesy thing and
+ * just bail. The alternative would be to go back to the
+ * beginning of the list, and print things twice, which may
+ * not be desirable.
+ */
+ if (pc.status == PCI_GETCONF_LIST_CHANGED) {
+ warnx("PCI device list changed, please try again");
+ exitstatus = 1;
+ close(fd);
+ return;
+ } else if (pc.status == PCI_GETCONF_ERROR) {
+ warnx("error returned from PCIOCGETCONF ioctl");
+ exitstatus = 1;
+ close(fd);
+ return;
+ }
+ for (p = conf; p < &conf[pc.num_matches]; p++) {
+ printf("%s%d@pci%d:%d:%d:%d:\tclass=0x%06x card=0x%08x "
+ "chip=0x%08x rev=0x%02x hdr=0x%02x\n",
+ *p->pd_name ? p->pd_name :
+ "none",
+ *p->pd_name ? (int)p->pd_unit :
+ none_count++, p->pc_sel.pc_domain,
+ p->pc_sel.pc_bus, p->pc_sel.pc_dev,
+ p->pc_sel.pc_func, (p->pc_class << 16) |
+ (p->pc_subclass << 8) | p->pc_progif,
+ (p->pc_subdevice << 16) | p->pc_subvendor,
+ (p->pc_device << 16) | p->pc_vendor,
+ p->pc_revid, p->pc_hdr);
+ if (verbose)
+ list_verbose(p);
+ if (bars)
+ list_bars(fd, p);
+ if (bridge)
+ list_bridge(fd, p);
+ if (caps)
+ list_caps(fd, p);
+ if (errors)
+ list_errors(fd, p);
+ if (vpd)
+ list_vpd(fd, p);
+ }
+ } while (pc.status == PCI_GETCONF_MORE_DEVS);
+
+ close(fd);
+}
+
+static void
+print_bus_range(int fd, struct pci_conf *p, int secreg, int subreg)
+{
+ uint8_t secbus, subbus;
+
+ secbus = read_config(fd, &p->pc_sel, secreg, 1);
+ subbus = read_config(fd, &p->pc_sel, subreg, 1);
+ printf(" bus range = %u-%u\n", secbus, subbus);
+}
+
+static void
+print_window(int reg, const char *type, int range, uint64_t base,
+ uint64_t limit)
+{
+
+ printf(" window[%02x] = type %s, range %2d, addr %#jx-%#jx, %s\n",
+ reg, type, range, (uintmax_t)base, (uintmax_t)limit,
+ base < limit ? "enabled" : "disabled");
+}
+
+static void
+print_special_decode(bool isa, bool vga, bool subtractive)
+{
+ bool comma;
+
+ if (isa || vga || subtractive) {
+ comma = false;
+ printf(" decode = ");
+ if (isa) {
+ printf("ISA");
+ comma = true;
+ }
+ if (vga) {
+ printf("%sVGA", comma ? ", " : "");
+ comma = true;
+ }
+ if (subtractive)
+ printf("%ssubtractive", comma ? ", " : "");
+ printf("\n");
+ }
+}
+
+static void
+print_bridge_windows(int fd, struct pci_conf *p)
+{
+ uint64_t base, limit;
+ uint32_t val;
+ uint16_t bctl;
+ bool subtractive;
+ int range;
+
+ /*
+ * XXX: This assumes that a window with a base and limit of 0
+ * is not implemented. In theory a window might be programmed
+ * at the smallest size with a base of 0, but those do not seem
+ * common in practice.
+ */
+ val = read_config(fd, &p->pc_sel, PCIR_IOBASEL_1, 1);
+ if (val != 0 || read_config(fd, &p->pc_sel, PCIR_IOLIMITL_1, 1) != 0) {
+ if ((val & PCIM_BRIO_MASK) == PCIM_BRIO_32) {
+ base = PCI_PPBIOBASE(
+ read_config(fd, &p->pc_sel, PCIR_IOBASEH_1, 2),
+ val);
+ limit = PCI_PPBIOLIMIT(
+ read_config(fd, &p->pc_sel, PCIR_IOLIMITH_1, 2),
+ read_config(fd, &p->pc_sel, PCIR_IOLIMITL_1, 1));
+ range = 32;
+ } else {
+ base = PCI_PPBIOBASE(0, val);
+ limit = PCI_PPBIOLIMIT(0,
+ read_config(fd, &p->pc_sel, PCIR_IOLIMITL_1, 1));
+ range = 16;
+ }
+ print_window(PCIR_IOBASEL_1, "I/O Port", range, base, limit);
+ }
+
+ base = PCI_PPBMEMBASE(0,
+ read_config(fd, &p->pc_sel, PCIR_MEMBASE_1, 2));
+ limit = PCI_PPBMEMLIMIT(0,
+ read_config(fd, &p->pc_sel, PCIR_MEMLIMIT_1, 2));
+ print_window(PCIR_MEMBASE_1, "Memory", 32, base, limit);
+
+ val = read_config(fd, &p->pc_sel, PCIR_PMBASEL_1, 2);
+ if (val != 0 || read_config(fd, &p->pc_sel, PCIR_PMLIMITL_1, 2) != 0) {
+ if ((val & PCIM_BRPM_MASK) == PCIM_BRPM_64) {
+ base = PCI_PPBMEMBASE(
+ read_config(fd, &p->pc_sel, PCIR_PMBASEH_1, 4),
+ val);
+ limit = PCI_PPBMEMLIMIT(
+ read_config(fd, &p->pc_sel, PCIR_PMLIMITH_1, 4),
+ read_config(fd, &p->pc_sel, PCIR_PMLIMITL_1, 2));
+ range = 64;
+ } else {
+ base = PCI_PPBMEMBASE(0, val);
+ limit = PCI_PPBMEMLIMIT(0,
+ read_config(fd, &p->pc_sel, PCIR_PMLIMITL_1, 2));
+ range = 32;
+ }
+ print_window(PCIR_PMBASEL_1, "Prefetchable Memory", range, base,
+ limit);
+ }
+
+ /*
+ * XXX: This list of bridges that are subtractive but do not set
+ * progif to indicate it is copied from pci_pci.c.
+ */
+ subtractive = p->pc_progif == PCIP_BRIDGE_PCI_SUBTRACTIVE;
+ switch (p->pc_device << 16 | p->pc_vendor) {
+ case 0xa002177d: /* Cavium ThunderX */
+ case 0x124b8086: /* Intel 82380FB Mobile */
+ case 0x060513d7: /* Toshiba ???? */
+ subtractive = true;
+ }
+ if (p->pc_vendor == 0x8086 && (p->pc_device & 0xff00) == 0x2400)
+ subtractive = true;
+
+ bctl = read_config(fd, &p->pc_sel, PCIR_BRIDGECTL_1, 2);
+ print_special_decode(bctl & PCIB_BCR_ISA_ENABLE,
+ bctl & PCIB_BCR_VGA_ENABLE, subtractive);
+}
+
+static void
+print_cardbus_mem_window(int fd, struct pci_conf *p, int basereg, int limitreg,
+ bool prefetch)
+{
+
+ print_window(basereg, prefetch ? "Prefetchable Memory" : "Memory", 32,
+ PCI_CBBMEMBASE(read_config(fd, &p->pc_sel, basereg, 4)),
+ PCI_CBBMEMLIMIT(read_config(fd, &p->pc_sel, limitreg, 4)));
+}
+
+static void
+print_cardbus_io_window(int fd, struct pci_conf *p, int basereg, int limitreg)
+{
+ uint32_t base, limit;
+ uint32_t val;
+ int range;
+
+ val = read_config(fd, &p->pc_sel, basereg, 2);
+ if ((val & PCIM_CBBIO_MASK) == PCIM_CBBIO_32) {
+ base = PCI_CBBIOBASE(read_config(fd, &p->pc_sel, basereg, 4));
+ limit = PCI_CBBIOBASE(read_config(fd, &p->pc_sel, limitreg, 4));
+ range = 32;
+ } else {
+ base = PCI_CBBIOBASE(val);
+ limit = PCI_CBBIOBASE(read_config(fd, &p->pc_sel, limitreg, 2));
+ range = 16;
+ }
+ print_window(basereg, "I/O Port", range, base, limit);
+}
+
+static void
+print_cardbus_windows(int fd, struct pci_conf *p)
+{
+ uint16_t bctl;
+
+ bctl = read_config(fd, &p->pc_sel, PCIR_BRIDGECTL_2, 2);
+ print_cardbus_mem_window(fd, p, PCIR_MEMBASE0_2, PCIR_MEMLIMIT0_2,
+ bctl & CBB_BCR_PREFETCH_0_ENABLE);
+ print_cardbus_mem_window(fd, p, PCIR_MEMBASE1_2, PCIR_MEMLIMIT1_2,
+ bctl & CBB_BCR_PREFETCH_1_ENABLE);
+ print_cardbus_io_window(fd, p, PCIR_IOBASE0_2, PCIR_IOLIMIT0_2);
+ print_cardbus_io_window(fd, p, PCIR_IOBASE1_2, PCIR_IOLIMIT1_2);
+ print_special_decode(bctl & CBB_BCR_ISA_ENABLE,
+ bctl & CBB_BCR_VGA_ENABLE, false);
+}
+
+static void
+list_bridge(int fd, struct pci_conf *p)
+{
+
+ switch (p->pc_hdr & PCIM_HDRTYPE) {
+ case PCIM_HDRTYPE_BRIDGE:
+ print_bus_range(fd, p, PCIR_SECBUS_1, PCIR_SUBBUS_1);
+ print_bridge_windows(fd, p);
+ break;
+ case PCIM_HDRTYPE_CARDBUS:
+ print_bus_range(fd, p, PCIR_SECBUS_2, PCIR_SUBBUS_2);
+ print_cardbus_windows(fd, p);
+ break;
+ }
+}
+
+static void
+list_bars(int fd, struct pci_conf *p)
+{
+ int i, max;
+
+ switch (p->pc_hdr & PCIM_HDRTYPE) {
+ case PCIM_HDRTYPE_NORMAL:
+ max = PCIR_MAX_BAR_0;
+ break;
+ case PCIM_HDRTYPE_BRIDGE:
+ max = PCIR_MAX_BAR_1;
+ break;
+ case PCIM_HDRTYPE_CARDBUS:
+ max = PCIR_MAX_BAR_2;
+ break;
+ default:
+ return;
+ }
+
+ for (i = 0; i <= max; i++)
+ print_bar(fd, p, "bar ", PCIR_BAR(i));
+}
+
+void
+print_bar(int fd, struct pci_conf *p, const char *label, uint16_t bar_offset)
+{
+ uint64_t base;
+ const char *type;
+ struct pci_bar_io bar;
+ int range;
+
+ bar.pbi_sel = p->pc_sel;
+ bar.pbi_reg = bar_offset;
+ if (ioctl(fd, PCIOCGETBAR, &bar) < 0)
+ return;
+ if (PCI_BAR_IO(bar.pbi_base)) {
+ type = "I/O Port";
+ range = 32;
+ base = bar.pbi_base & PCIM_BAR_IO_BASE;
+ } else {
+ if (bar.pbi_base & PCIM_BAR_MEM_PREFETCH)
+ type = "Prefetchable Memory";
+ else
+ type = "Memory";
+ switch (bar.pbi_base & PCIM_BAR_MEM_TYPE) {
+ case PCIM_BAR_MEM_32:
+ range = 32;
+ break;
+ case PCIM_BAR_MEM_1MB:
+ range = 20;
+ break;
+ case PCIM_BAR_MEM_64:
+ range = 64;
+ break;
+ default:
+ range = -1;
+ }
+ base = bar.pbi_base & ~((uint64_t)0xf);
+ }
+ printf(" %s[%02x] = type %s, range %2d, base %#jx, ",
+ label, bar_offset, type, range, (uintmax_t)base);
+ printf("size %ju, %s\n", (uintmax_t)bar.pbi_length,
+ bar.pbi_enabled ? "enabled" : "disabled");
+}
+
+static void
+list_verbose(struct pci_conf *p)
+{
+ struct pci_vendor_info *vi;
+ struct pci_device_info *di;
+ const char *dp;
+
+ TAILQ_FOREACH(vi, &pci_vendors, link) {
+ if (vi->id == p->pc_vendor) {
+ printf(" vendor = '%s'\n", vi->desc);
+ break;
+ }
+ }
+ if (vi == NULL) {
+ di = NULL;
+ } else {
+ TAILQ_FOREACH(di, &vi->devs, link) {
+ if (di->id == p->pc_device) {
+ printf(" device = '%s'\n", di->desc);
+ break;
+ }
+ }
+ }
+ if ((dp = guess_class(p)) != NULL)
+ printf(" class = %s\n", dp);
+ if ((dp = guess_subclass(p)) != NULL)
+ printf(" subclass = %s\n", dp);
+}
+
+static void
+list_vpd(int fd, struct pci_conf *p)
+{
+ struct pci_list_vpd_io list;
+ struct pci_vpd_element *vpd, *end;
+
+ list.plvi_sel = p->pc_sel;
+ list.plvi_len = 0;
+ list.plvi_data = NULL;
+ if (ioctl(fd, PCIOCLISTVPD, &list) < 0 || list.plvi_len == 0)
+ return;
+
+ list.plvi_data = malloc(list.plvi_len);
+ if (ioctl(fd, PCIOCLISTVPD, &list) < 0) {
+ free(list.plvi_data);
+ return;
+ }
+
+ vpd = list.plvi_data;
+ end = (struct pci_vpd_element *)((char *)vpd + list.plvi_len);
+ for (; vpd < end; vpd = PVE_NEXT(vpd)) {
+ if (vpd->pve_flags == PVE_FLAG_IDENT) {
+ printf(" VPD ident = '%.*s'\n",
+ (int)vpd->pve_datalen, vpd->pve_data);
+ continue;
+ }
+
+ /* Ignore the checksum keyword. */
+ if (!(vpd->pve_flags & PVE_FLAG_RW) &&
+ memcmp(vpd->pve_keyword, "RV", 2) == 0)
+ continue;
+
+ /* Ignore remaining read-write space. */
+ if (vpd->pve_flags & PVE_FLAG_RW &&
+ memcmp(vpd->pve_keyword, "RW", 2) == 0)
+ continue;
+
+ /* Handle extended capability keyword. */
+ if (!(vpd->pve_flags & PVE_FLAG_RW) &&
+ memcmp(vpd->pve_keyword, "CP", 2) == 0) {
+ printf(" VPD ro CP = ID %02x in map 0x%x[0x%x]\n",
+ (unsigned int)vpd->pve_data[0],
+ PCIR_BAR((unsigned int)vpd->pve_data[1]),
+ (unsigned int)vpd->pve_data[3] << 8 |
+ (unsigned int)vpd->pve_data[2]);
+ continue;
+ }
+
+ /* Remaining keywords should all have ASCII values. */
+ printf(" VPD %s %c%c = '%.*s'\n",
+ vpd->pve_flags & PVE_FLAG_RW ? "rw" : "ro",
+ vpd->pve_keyword[0], vpd->pve_keyword[1],
+ (int)vpd->pve_datalen, vpd->pve_data);
+ }
+ free(list.plvi_data);
+}
+
+/*
+ * This is a direct cut-and-paste from the table in sys/dev/pci/pci.c.
+ */
+static struct
+{
+ int class;
+ int subclass;
+ const char *desc;
+} pci_nomatch_tab[] = {
+ {PCIC_OLD, -1, "old"},
+ {PCIC_OLD, PCIS_OLD_NONVGA, "non-VGA display device"},
+ {PCIC_OLD, PCIS_OLD_VGA, "VGA-compatible display device"},
+ {PCIC_STORAGE, -1, "mass storage"},
+ {PCIC_STORAGE, PCIS_STORAGE_SCSI, "SCSI"},
+ {PCIC_STORAGE, PCIS_STORAGE_IDE, "ATA"},
+ {PCIC_STORAGE, PCIS_STORAGE_FLOPPY, "floppy disk"},
+ {PCIC_STORAGE, PCIS_STORAGE_IPI, "IPI"},
+ {PCIC_STORAGE, PCIS_STORAGE_RAID, "RAID"},
+ {PCIC_STORAGE, PCIS_STORAGE_ATA_ADMA, "ATA (ADMA)"},
+ {PCIC_STORAGE, PCIS_STORAGE_SATA, "SATA"},
+ {PCIC_STORAGE, PCIS_STORAGE_SAS, "SAS"},
+ {PCIC_STORAGE, PCIS_STORAGE_NVM, "NVM"},
+ {PCIC_NETWORK, -1, "network"},
+ {PCIC_NETWORK, PCIS_NETWORK_ETHERNET, "ethernet"},
+ {PCIC_NETWORK, PCIS_NETWORK_TOKENRING, "token ring"},
+ {PCIC_NETWORK, PCIS_NETWORK_FDDI, "fddi"},
+ {PCIC_NETWORK, PCIS_NETWORK_ATM, "ATM"},
+ {PCIC_NETWORK, PCIS_NETWORK_ISDN, "ISDN"},
+ {PCIC_DISPLAY, -1, "display"},
+ {PCIC_DISPLAY, PCIS_DISPLAY_VGA, "VGA"},
+ {PCIC_DISPLAY, PCIS_DISPLAY_XGA, "XGA"},
+ {PCIC_DISPLAY, PCIS_DISPLAY_3D, "3D"},
+ {PCIC_MULTIMEDIA, -1, "multimedia"},
+ {PCIC_MULTIMEDIA, PCIS_MULTIMEDIA_VIDEO, "video"},
+ {PCIC_MULTIMEDIA, PCIS_MULTIMEDIA_AUDIO, "audio"},
+ {PCIC_MULTIMEDIA, PCIS_MULTIMEDIA_TELE, "telephony"},
+ {PCIC_MULTIMEDIA, PCIS_MULTIMEDIA_HDA, "HDA"},
+ {PCIC_MEMORY, -1, "memory"},
+ {PCIC_MEMORY, PCIS_MEMORY_RAM, "RAM"},
+ {PCIC_MEMORY, PCIS_MEMORY_FLASH, "flash"},
+ {PCIC_BRIDGE, -1, "bridge"},
+ {PCIC_BRIDGE, PCIS_BRIDGE_HOST, "HOST-PCI"},
+ {PCIC_BRIDGE, PCIS_BRIDGE_ISA, "PCI-ISA"},
+ {PCIC_BRIDGE, PCIS_BRIDGE_EISA, "PCI-EISA"},
+ {PCIC_BRIDGE, PCIS_BRIDGE_MCA, "PCI-MCA"},
+ {PCIC_BRIDGE, PCIS_BRIDGE_PCI, "PCI-PCI"},
+ {PCIC_BRIDGE, PCIS_BRIDGE_PCMCIA, "PCI-PCMCIA"},
+ {PCIC_BRIDGE, PCIS_BRIDGE_NUBUS, "PCI-NuBus"},
+ {PCIC_BRIDGE, PCIS_BRIDGE_CARDBUS, "PCI-CardBus"},
+ {PCIC_BRIDGE, PCIS_BRIDGE_RACEWAY, "PCI-RACEway"},
+ {PCIC_SIMPLECOMM, -1, "simple comms"},
+ {PCIC_SIMPLECOMM, PCIS_SIMPLECOMM_UART, "UART"}, /* could detect 16550 */
+ {PCIC_SIMPLECOMM, PCIS_SIMPLECOMM_PAR, "parallel port"},
+ {PCIC_SIMPLECOMM, PCIS_SIMPLECOMM_MULSER, "multiport serial"},
+ {PCIC_SIMPLECOMM, PCIS_SIMPLECOMM_MODEM, "generic modem"},
+ {PCIC_BASEPERIPH, -1, "base peripheral"},
+ {PCIC_BASEPERIPH, PCIS_BASEPERIPH_PIC, "interrupt controller"},
+ {PCIC_BASEPERIPH, PCIS_BASEPERIPH_DMA, "DMA controller"},
+ {PCIC_BASEPERIPH, PCIS_BASEPERIPH_TIMER, "timer"},
+ {PCIC_BASEPERIPH, PCIS_BASEPERIPH_RTC, "realtime clock"},
+ {PCIC_BASEPERIPH, PCIS_BASEPERIPH_PCIHOT, "PCI hot-plug controller"},
+ {PCIC_BASEPERIPH, PCIS_BASEPERIPH_SDHC, "SD host controller"},
+ {PCIC_BASEPERIPH, PCIS_BASEPERIPH_IOMMU, "IOMMU"},
+ {PCIC_INPUTDEV, -1, "input device"},
+ {PCIC_INPUTDEV, PCIS_INPUTDEV_KEYBOARD, "keyboard"},
+ {PCIC_INPUTDEV, PCIS_INPUTDEV_DIGITIZER,"digitizer"},
+ {PCIC_INPUTDEV, PCIS_INPUTDEV_MOUSE, "mouse"},
+ {PCIC_INPUTDEV, PCIS_INPUTDEV_SCANNER, "scanner"},
+ {PCIC_INPUTDEV, PCIS_INPUTDEV_GAMEPORT, "gameport"},
+ {PCIC_DOCKING, -1, "docking station"},
+ {PCIC_PROCESSOR, -1, "processor"},
+ {PCIC_SERIALBUS, -1, "serial bus"},
+ {PCIC_SERIALBUS, PCIS_SERIALBUS_FW, "FireWire"},
+ {PCIC_SERIALBUS, PCIS_SERIALBUS_ACCESS, "AccessBus"},
+ {PCIC_SERIALBUS, PCIS_SERIALBUS_SSA, "SSA"},
+ {PCIC_SERIALBUS, PCIS_SERIALBUS_USB, "USB"},
+ {PCIC_SERIALBUS, PCIS_SERIALBUS_FC, "Fibre Channel"},
+ {PCIC_SERIALBUS, PCIS_SERIALBUS_SMBUS, "SMBus"},
+ {PCIC_WIRELESS, -1, "wireless controller"},
+ {PCIC_WIRELESS, PCIS_WIRELESS_IRDA, "iRDA"},
+ {PCIC_WIRELESS, PCIS_WIRELESS_IR, "IR"},
+ {PCIC_WIRELESS, PCIS_WIRELESS_RF, "RF"},
+ {PCIC_INTELLIIO, -1, "intelligent I/O controller"},
+ {PCIC_INTELLIIO, PCIS_INTELLIIO_I2O, "I2O"},
+ {PCIC_SATCOM, -1, "satellite communication"},
+ {PCIC_SATCOM, PCIS_SATCOM_TV, "sat TV"},
+ {PCIC_SATCOM, PCIS_SATCOM_AUDIO, "sat audio"},
+ {PCIC_SATCOM, PCIS_SATCOM_VOICE, "sat voice"},
+ {PCIC_SATCOM, PCIS_SATCOM_DATA, "sat data"},
+ {PCIC_CRYPTO, -1, "encrypt/decrypt"},
+ {PCIC_CRYPTO, PCIS_CRYPTO_NETCOMP, "network/computer crypto"},
+ {PCIC_CRYPTO, PCIS_CRYPTO_NETCOMP, "entertainment crypto"},
+ {PCIC_DASP, -1, "dasp"},
+ {PCIC_DASP, PCIS_DASP_DPIO, "DPIO module"},
+ {0, 0, NULL}
+};
+
+static const char *
+guess_class(struct pci_conf *p)
+{
+ int i;
+
+ for (i = 0; pci_nomatch_tab[i].desc != NULL; i++) {
+ if (pci_nomatch_tab[i].class == p->pc_class)
+ return(pci_nomatch_tab[i].desc);
+ }
+ return(NULL);
+}
+
+static const char *
+guess_subclass(struct pci_conf *p)
+{
+ int i;
+
+ for (i = 0; pci_nomatch_tab[i].desc != NULL; i++) {
+ if ((pci_nomatch_tab[i].class == p->pc_class) &&
+ (pci_nomatch_tab[i].subclass == p->pc_subclass))
+ return(pci_nomatch_tab[i].desc);
+ }
+ return(NULL);
+}
+
+static int
+load_vendors(void)
+{
+ const char *dbf;
+ FILE *db;
+ struct pci_vendor_info *cv;
+ struct pci_device_info *cd;
+ char buf[1024], str[1024];
+ char *ch;
+ int id, error;
+
+ /*
+ * Locate the database and initialise.
+ */
+ TAILQ_INIT(&pci_vendors);
+ if ((dbf = getenv("PCICONF_VENDOR_DATABASE")) == NULL)
+ dbf = _PATH_LPCIVDB;
+ if ((db = fopen(dbf, "r")) == NULL) {
+ dbf = _PATH_PCIVDB;
+ if ((db = fopen(dbf, "r")) == NULL)
+ return(1);
+ }
+ cv = NULL;
+ cd = NULL;
+ error = 0;
+
+ /*
+ * Scan input lines from the database
+ */
+ for (;;) {
+ if (fgets(buf, sizeof(buf), db) == NULL)
+ break;
+
+ if ((ch = strchr(buf, '#')) != NULL)
+ *ch = '\0';
+ ch = strchr(buf, '\0') - 1;
+ while (ch > buf && isspace(*ch))
+ *ch-- = '\0';
+ if (ch <= buf)
+ continue;
+
+ /* Can't handle subvendor / subdevice entries yet */
+ if (buf[0] == '\t' && buf[1] == '\t')
+ continue;
+
+ /* Check for vendor entry */
+ if (buf[0] != '\t' && sscanf(buf, "%04x %[^\n]", &id, str) == 2) {
+ if ((id == 0) || (strlen(str) < 1))
+ continue;
+ if ((cv = malloc(sizeof(struct pci_vendor_info))) == NULL) {
+ warn("allocating vendor entry");
+ error = 1;
+ break;
+ }
+ if ((cv->desc = strdup(str)) == NULL) {
+ free(cv);
+ warn("allocating vendor description");
+ error = 1;
+ break;
+ }
+ cv->id = id;
+ TAILQ_INIT(&cv->devs);
+ TAILQ_INSERT_TAIL(&pci_vendors, cv, link);
+ continue;
+ }
+
+ /* Check for device entry */
+ if (buf[0] == '\t' && sscanf(buf + 1, "%04x %[^\n]", &id, str) == 2) {
+ if ((id == 0) || (strlen(str) < 1))
+ continue;
+ if (cv == NULL) {
+ warnx("device entry with no vendor!");
+ continue;
+ }
+ if ((cd = malloc(sizeof(struct pci_device_info))) == NULL) {
+ warn("allocating device entry");
+ error = 1;
+ break;
+ }
+ if ((cd->desc = strdup(str)) == NULL) {
+ free(cd);
+ warn("allocating device description");
+ error = 1;
+ break;
+ }
+ cd->id = id;
+ TAILQ_INSERT_TAIL(&cv->devs, cd, link);
+ continue;
+ }
+
+ /* It's a comment or junk, ignore it */
+ }
+ if (ferror(db))
+ error = 1;
+ fclose(db);
+
+ return(error);
+}
+
+uint32_t
+read_config(int fd, struct pcisel *sel, long reg, int width)
+{
+ struct pci_io pi;
+
+ pi.pi_sel = *sel;
+ pi.pi_reg = reg;
+ pi.pi_width = width;
+
+ if (ioctl(fd, PCIOCREAD, &pi) < 0)
+ err(1, "ioctl(PCIOCREAD)");
+
+ return (pi.pi_data);
+}
+
+static struct pcisel
+getdevice(const char *name)
+{
+ struct pci_conf_io pc;
+ struct pci_conf conf[1];
+ struct pci_match_conf patterns[1];
+ char *cp;
+ int fd;
+
+ fd = open(_PATH_DEVPCI, O_RDONLY, 0);
+ if (fd < 0)
+ err(1, "%s", _PATH_DEVPCI);
+
+ bzero(&pc, sizeof(struct pci_conf_io));
+ pc.match_buf_len = sizeof(conf);
+ pc.matches = conf;
+
+ bzero(&patterns, sizeof(patterns));
+
+ /*
+ * The pattern structure requires the unit to be split out from
+ * the driver name. Walk backwards from the end of the name to
+ * find the start of the unit.
+ */
+ if (name[0] == '\0')
+ errx(1, "Empty device name");
+ cp = strchr(name, '\0');
+ assert(cp != NULL && cp != name);
+ cp--;
+ while (cp != name && isdigit(cp[-1]))
+ cp--;
+ if (cp == name || !isdigit(*cp))
+ errx(1, "Invalid device name");
+ if ((size_t)(cp - name) + 1 > sizeof(patterns[0].pd_name))
+ errx(1, "Device name is too long");
+ memcpy(patterns[0].pd_name, name, cp - name);
+ patterns[0].pd_unit = strtol(cp, &cp, 10);
+ assert(*cp == '\0');
+ patterns[0].flags = PCI_GETCONF_MATCH_NAME | PCI_GETCONF_MATCH_UNIT;
+ pc.num_patterns = 1;
+ pc.pat_buf_len = sizeof(patterns);
+ pc.patterns = patterns;
+
+ if (ioctl(fd, PCIOCGETCONF, &pc) == -1)
+ err(1, "ioctl(PCIOCGETCONF)");
+ if (pc.status != PCI_GETCONF_LAST_DEVICE &&
+ pc.status != PCI_GETCONF_MORE_DEVS)
+ errx(1, "error returned from PCIOCGETCONF ioctl");
+ close(fd);
+ if (pc.num_matches == 0)
+ errx(1, "Device not found");
+ return (conf[0].pc_sel);
+}
+
+static struct pcisel
+parsesel(const char *str)
+{
+ char *ep = strchr(str, '@');
+ char *epbase;
+ struct pcisel sel;
+ unsigned long selarr[4];
+ int i;
+
+ if (ep == NULL)
+ ep = (char *)str;
+ else
+ ep++;
+
+ epbase = ep;
+
+ if (strncmp(ep, "pci", 3) == 0) {
+ ep += 3;
+ i = 0;
+ do {
+ selarr[i++] = strtoul(ep, &ep, 10);
+ } while ((*ep == ':' || *ep == '.') && *++ep != '\0' && i < 4);
+
+ if (i > 2)
+ sel.pc_func = selarr[--i];
+ else
+ sel.pc_func = 0;
+ sel.pc_dev = selarr[--i];
+ sel.pc_bus = selarr[--i];
+ if (i > 0)
+ sel.pc_domain = selarr[--i];
+ else
+ sel.pc_domain = 0;
+ }
+ if (*ep != '\x0' || ep == epbase)
+ errx(1, "cannot parse selector %s", str);
+ return sel;
+}
+
+static struct pcisel
+getsel(const char *str)
+{
+
+ /*
+ * No device names contain colons and selectors always contain
+ * at least one colon.
+ */
+ if (strchr(str, ':') == NULL)
+ return (getdevice(str));
+ else
+ return (parsesel(str));
+}
+
+static void
+readone(int fd, struct pcisel *sel, long reg, int width)
+{
+
+ printf("%0*x", width*2, read_config(fd, sel, reg, width));
+}
+
+static void
+readit(const char *name, const char *reg, int width)
+{
+ long rstart;
+ long rend;
+ long r;
+ char *end;
+ int i;
+ int fd;
+ struct pcisel sel;
+
+ fd = open(_PATH_DEVPCI, O_RDWR, 0);
+ if (fd < 0)
+ err(1, "%s", _PATH_DEVPCI);
+
+ rend = rstart = strtol(reg, &end, 0);
+ if (end && *end == ':') {
+ end++;
+ rend = strtol(end, (char **) 0, 0);
+ }
+ sel = getsel(name);
+ for (i = 1, r = rstart; r <= rend; i++, r += width) {
+ readone(fd, &sel, r, width);
+ if (i && !(i % 8))
+ putchar(' ');
+ putchar(i % (16/width) ? ' ' : '\n');
+ }
+ if (i % (16/width) != 1)
+ putchar('\n');
+ close(fd);
+}
+
+static void
+writeit(const char *name, const char *reg, const char *data, int width)
+{
+ int fd;
+ struct pci_io pi;
+
+ pi.pi_sel = getsel(name);
+ pi.pi_reg = strtoul(reg, (char **)0, 0); /* XXX error check */
+ pi.pi_width = width;
+ pi.pi_data = strtoul(data, (char **)0, 0); /* XXX error check */
+
+ fd = open(_PATH_DEVPCI, O_RDWR, 0);
+ if (fd < 0)
+ err(1, "%s", _PATH_DEVPCI);
+
+ if (ioctl(fd, PCIOCWRITE, &pi) < 0)
+ err(1, "ioctl(PCIOCWRITE)");
+}
+
+static void
+chkattached(const char *name)
+{
+ int fd;
+ struct pci_io pi;
+
+ pi.pi_sel = getsel(name);
+
+ fd = open(_PATH_DEVPCI, O_RDWR, 0);
+ if (fd < 0)
+ err(1, "%s", _PATH_DEVPCI);
+
+ if (ioctl(fd, PCIOCATTACHED, &pi) < 0)
+ err(1, "ioctl(PCIOCATTACHED)");
+
+ exitstatus = pi.pi_data ? 0 : 2; /* exit(2), if NOT attached */
+ printf("%s: %s%s\n", name, pi.pi_data == 0 ? "not " : "", "attached");
+}
diff --git a/usr.sbin/pciconf/pciconf.h b/usr.sbin/pciconf/pciconf.h
new file mode 100644
index 0000000..734007e
--- /dev/null
+++ b/usr.sbin/pciconf/pciconf.h
@@ -0,0 +1,43 @@
+/*-
+ * Copyright (c) 2007 Yahoo!, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@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.
+ * 3. 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 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 __PCICONF_H__
+#define __PCICONF_H__
+
+void list_caps(int fd, struct pci_conf *p);
+void list_errors(int fd, struct pci_conf *p);
+uint8_t pci_find_cap(int fd, struct pci_conf *p, uint8_t id);
+uint16_t pcie_find_cap(int fd, struct pci_conf *p, uint16_t id);
+void print_bar(int fd, struct pci_conf *p, const char *label, uint16_t bar);
+uint32_t read_config(int fd, struct pcisel *sel, long reg, int width);
+
+#endif
diff --git a/usr.sbin/periodic/Makefile b/usr.sbin/periodic/Makefile
new file mode 100644
index 0000000..875d078
--- /dev/null
+++ b/usr.sbin/periodic/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+SCRIPTS=periodic.sh
+MAN= periodic.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/periodic/Makefile.depend b/usr.sbin/periodic/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/periodic/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/periodic/periodic.8 b/usr.sbin/periodic/periodic.8
new file mode 100644
index 0000000..175119d
--- /dev/null
+++ b/usr.sbin/periodic/periodic.8
@@ -0,0 +1,258 @@
+.\" 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$
+.\"
+.Dd August 30, 2007
+.Dt PERIODIC 8
+.Os
+.Sh NAME
+.Nm periodic
+.Nd run periodic system functions
+.Sh SYNOPSIS
+.Nm
+.Ar directory ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is intended to be called by
+.Xr cron 8
+to execute shell scripts
+located in the specified directory.
+.Pp
+One or more of the following arguments must be specified:
+.Bl -tag -width ".Pa monthly"
+.It Pa daily
+Perform the standard daily periodic executable run.
+This usually occurs early in the morning (local time).
+.It Pa weekly
+Perform the standard weekly periodic executable run.
+This usually occurs very early on Saturday mornings.
+.It Pa monthly
+Perform the standard monthly periodic executable run.
+This usually occurs on the first day of the month.
+.It Pa security
+Perform the standard daily security checks.
+This is usually spawned by the
+.Pa daily
+run.
+.It Ar path
+An arbitrary directory containing a set of executables to be run.
+.El
+.Pp
+If an argument is an absolute directory name it is used as is, otherwise
+it is searched for under
+.Pa /etc/periodic
+and any other directories specified by the
+.Va local_periodic
+setting in
+.Xr periodic.conf 5
+(see below).
+.Pp
+The
+.Nm
+utility will run each executable file in the directory or directories
+specified.
+If a file does not have the executable bit set, it is silently ignored.
+.Pp
+Each script is required to exit with one of the following values:
+.Bl -tag -width 4n
+.It 0
+The script has produced nothing notable in its output.
+The
+.Ao Ar basedir Ac Ns Va _show_success
+variable controls the masking of this output.
+.It 1
+The script has produced some notable information in its output.
+The
+.Ao Ar basedir Ac Ns Va _show_info
+variable controls the masking of this output.
+.It 2
+The script has produced some warnings due to invalid configuration settings.
+The
+.Ao Ar basedir Ac Ns Va _show_badconfig
+variable controls the masking of this output.
+.It >2
+The script has produced output that must not be masked.
+.El
+.Pp
+If the relevant variable (where
+.Aq Ar basedir
+is the base directory in which the script resides) is set to
+.Dq Li NO
+in
+.Pa periodic.conf ,
+.Nm
+will mask the script output.
+If the variable is not set to either
+.Dq Li YES
+or
+.Dq Li NO ,
+it will be given a default value as described in
+.Xr periodic.conf 5 .
+.Pp
+All remaining script output is delivered based on the value of the
+.Ao Ar basedir Ac Ns Va _output
+setting.
+.Pp
+If this is set to a path name (beginning with a
+.Ql /
+character), output is simply logged to that file.
+.Xr newsyslog 8
+knows about the files
+.Pa /var/log/daily.log , /var/log/weekly.log
+and
+.Pa /var/log/monthly.log ,
+and if they exist, it will rotate them at the appropriate times.
+These are therefore good values if you wish to log
+.Nm
+output.
+.Pp
+If the
+.Ao Ar basedir Ac Ns Va _output
+value does not begin with a
+.Ql /
+and is not empty, it is assumed to contain a list of email addresses, and
+the output is mailed to them.
+If
+.Ao Ar basedir Ac Ns Va _show_empty_output
+is set to
+.Dq Li NO ,
+then no mail will be sent if the output was empty.
+.Pp
+If
+.Ao Ar basedir Ac Ns Va _output
+is not set or is empty, output is sent to standard output.
+.Sh ENVIRONMENT
+The
+.Nm
+utility sets the
+.Ev PATH
+environment to include all standard system directories, but no additional
+directories, such as
+.Pa /usr/local/bin .
+If executables are added which depend upon other path components, each
+executable must be responsible for configuring its own appropriate environment.
+.Sh FILES
+.Bl -tag -width ".Pa /etc/defaults/periodic.conf"
+.It Pa /etc/crontab
+the
+.Nm
+utility is typically called via entries in the system default
+.Xr cron 8
+table
+.It Pa /etc/periodic
+the top level directory containing
+.Pa daily ,
+.Pa weekly ,
+and
+.Pa monthly
+subdirectories which contain standard system periodic executables
+.It Pa /etc/defaults/periodic.conf
+the
+.Pa periodic.conf
+system registry contains variables that control the behaviour of
+.Nm
+and the standard
+.Pa daily , weekly ,
+and
+.Pa monthly
+scripts
+.It Pa /etc/periodic.conf
+this file contains local overrides for the default
+.Nm
+configuration
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success and 1 if the command fails.
+.Sh EXAMPLES
+The system crontab should have entries for
+.Nm
+similar to the following example:
+.Bd -literal -offset indent
+# do daily/weekly/monthly maintenance
+0 2 * * * root periodic daily
+0 3 * * 6 root periodic weekly
+0 5 1 * * root periodic monthly
+.Ed
+.Pp
+The
+.Pa /etc/defaults/periodic.conf
+system registry will typically have a
+.Va local_periodic
+variable reading:
+.Pp
+.Dl local_periodic="/usr/local/etc/periodic"
+.Pp
+To log
+.Nm
+output instead of receiving it as email, add the following lines to
+.Pa /etc/periodic.conf :
+.Bd -literal -offset indent
+daily_output=/var/log/daily.log
+weekly_output=/var/log/weekly.log
+monthly_output=/var/log/monthly.log
+.Ed
+.Pp
+To only see important information from daily periodic jobs, add the
+following lines to
+.Pa /etc/periodic.conf :
+.Bd -literal -offset indent
+daily_show_success=NO
+daily_show_info=NO
+daily_show_badconfig=NO
+.Ed
+.Sh DIAGNOSTICS
+The command may fail for one of the following reasons:
+.Bl -diag
+.It usage: periodic <directory of files to execute>
+No directory path argument was passed to
+.Nm
+to specify where the script fragments reside.
+.It <directory> not found
+Self explanatory.
+.El
+.Sh SEE ALSO
+.Xr sh 1 ,
+.Xr crontab 5 ,
+.Xr periodic.conf 5 ,
+.Xr cron 8 ,
+.Xr newsyslog 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 3.0 .
+.Sh AUTHORS
+.An Paul Traina Aq Mt pst@FreeBSD.org
+.An Brian Somers Aq Mt brian@Awfulhak.org
+.Sh BUGS
+Since one specifies information about a directory using shell
+variables containing the string,
+.Aq Ar basedir ,
+.Aq Ar basedir
+must only contain characters that are valid within a
+.Xr sh 1
+variable name, alphanumerics and underscores, and the first character
+may not be numeric.
diff --git a/usr.sbin/periodic/periodic.sh b/usr.sbin/periodic/periodic.sh
new file mode 100644
index 0000000..c27aeee
--- /dev/null
+++ b/usr.sbin/periodic/periodic.sh
@@ -0,0 +1,143 @@
+#!/bin/sh -
+#
+# $FreeBSD$
+#
+# Run nightly periodic scripts
+#
+# usage: periodic { daily | weekly | monthly } - run standard periodic scripts
+# periodic /absolute/path/to/directory - run periodic scripts in dir
+#
+
+usage () {
+ echo "usage: $0 <directory of files to execute>" 1>&2
+ echo "or $0 { daily | weekly | monthly }" 1>&2
+ exit 1
+}
+
+output_pipe()
+{
+ # Where's our output going ?
+ eval output=\$${1##*/}_output
+ case "$output" in
+ /*) pipe="cat >>$output";;
+ "") pipe=cat;;
+ *) pipe="mail -E -s '$host ${2}${2:+ }${1##*/} run output' $output";;
+ esac
+ eval $pipe
+}
+
+if [ $# -lt 1 ] ; then
+ usage
+fi
+
+# If possible, check the global system configuration file,
+# to see if there are additional dirs to check
+if [ -r /etc/defaults/periodic.conf ]; then
+ . /etc/defaults/periodic.conf
+ source_periodic_confs
+fi
+
+host=`hostname`
+export host
+
+# If we were called normally, then create a lock file for each argument
+# in turn and reinvoke ourselves with the LOCKED argument. This prevents
+# very long running jobs from being overlapped by another run as this is
+# will lead the system running progressivly slower and more and more jobs
+# are run at once.
+if [ $1 != "LOCKED" ]; then
+ ret=0
+ for arg; do
+ lockfile=/var/run/periodic.${arg##*/}.lock
+ lockf -t 0 "${lockfile}" /bin/sh $0 LOCKED "$arg"
+ case $? in
+ 0) ;;
+ 73) #EX_CANTCREATE
+ echo "can't create ${lockfile}" | \
+ output_pipe $arg "$PERIODIC"
+ ret=1
+ ;;
+ 75) #EX_TEMPFAIL
+ echo "$host ${arg##*/} prior run still in progress" | \
+ output_pipe $arg "$PERIODIC"
+ ret=1
+ ;;
+ *)
+ ret=1
+ ;;
+ esac
+ done
+ exit $ret
+fi
+
+if [ $# -ne 2 ]; then
+ usage
+fi
+shift
+arg=$1
+
+tmp_output=`mktemp ${TMPDIR:-/tmp}/periodic.XXXXXXXXXX`
+context="$PERIODIC"
+export PERIODIC="$arg${PERIODIC:+ }${PERIODIC}"
+
+# Execute each executable file in the directory list. If the x bit is not
+# set, assume the user didn't really want us to muck with it (it's a
+# README file or has been disabled).
+
+success=YES info=YES badconfig=NO empty_output=YES # Defaults when ${run}_* aren't YES/NO
+for var in success info badconfig empty_output; do
+ case $(eval echo "\$${arg##*/}_show_$var") in
+ [Yy][Ee][Ss]) eval $var=YES;;
+ [Nn][Oo]) eval $var=NO;;
+ esac
+done
+
+case $arg in
+/*) if [ -d "$arg" ]; then
+ dirlist="$arg"
+ else
+ echo "$0: $arg not found" >&2
+ continue
+ fi
+ ;;
+*) dirlist=
+ for top in /etc/periodic ${local_periodic}; do
+ [ -d $top/$arg ] && dirlist="$dirlist $top/$arg"
+ done
+ ;;
+esac
+
+{
+ empty=TRUE
+ processed=0
+ for dir in $dirlist; do
+ for file in $dir/*; do
+ if [ -x $file -a ! -d $file ]; then
+ output=TRUE
+ processed=$(($processed + 1))
+ $file </dev/null >$tmp_output 2>&1
+ rc=$?
+ if [ -s $tmp_output ]; then
+ case $rc in
+ 0) [ $success = NO ] && output=FALSE;;
+ 1) [ $info = NO ] && output=FALSE;;
+ 2) [ $badconfig = NO ] && output=FALSE;;
+ esac
+ [ $output = TRUE ] && { cat $tmp_output; empty=FALSE; }
+ fi
+ cp /dev/null $tmp_output
+ fi
+ done
+ done
+ if [ $empty = TRUE ]; then
+ if [ $empty_output = TRUE ]; then
+ [ $processed = 1 ] && plural= || plural=s
+ echo "No output from the $processed file$plural processed"
+ fi
+ else
+ echo ""
+ echo "-- End of $arg output --"
+ fi
+} | output_pipe $arg "$context"
+
+rm -f $tmp_output
diff --git a/usr.sbin/pkg/Makefile b/usr.sbin/pkg/Makefile
new file mode 100644
index 0000000..d149fd3
--- /dev/null
+++ b/usr.sbin/pkg/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+PROG= pkg
+SRCS= pkg.c dns_utils.c config.c
+MAN= pkg.7
+
+CFLAGS+=-I${.CURDIR}/../../contrib/libucl/include
+.PATH: ${.CURDIR}/../../contrib/libucl/include
+LIBADD= archive fetch ucl sbuf crypto ssl
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/pkg/Makefile.depend b/usr.sbin/pkg/Makefile.depend
new file mode 100644
index 0000000..98b04b08
--- /dev/null
+++ b/usr.sbin/pkg/Makefile.depend
@@ -0,0 +1,31 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libarchive \
+ lib/libbz2 \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libexpat \
+ lib/libfetch \
+ lib/liblzma \
+ lib/libsbuf \
+ lib/libthr \
+ lib/libucl \
+ lib/libz \
+ lib/msun \
+ secure/lib/libcrypto \
+ secure/lib/libssl \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/pkg/config.c b/usr.sbin/pkg/config.c
new file mode 100644
index 0000000..b0271f5
--- /dev/null
+++ b/usr.sbin/pkg/config.c
@@ -0,0 +1,541 @@
+/*-
+ * Copyright (c) 2014 Baptiste Daroussin <bapt@FreeBSD.org>
+ * Copyright (c) 2013 Bryan Drewery <bdrewery@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/param.h>
+#include <sys/queue.h>
+#include <sys/sbuf.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <sys/sysctl.h>
+
+#include <assert.h>
+#include <dirent.h>
+#include <ucl.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <paths.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "config.h"
+
+#define roundup2(x, y) (((x)+((y)-1))&(~((y)-1))) /* if y is powers of two */
+
+struct config_value {
+ char *value;
+ STAILQ_ENTRY(config_value) next;
+};
+
+struct config_entry {
+ uint8_t type;
+ const char *key;
+ const char *val;
+ char *value;
+ STAILQ_HEAD(, config_value) *list;
+ bool envset;
+ bool main_only; /* Only set in pkg.conf. */
+};
+
+static struct config_entry c[] = {
+ [PACKAGESITE] = {
+ PKG_CONFIG_STRING,
+ "PACKAGESITE",
+ URL_SCHEME_PREFIX "http://pkg.FreeBSD.org/${ABI}/latest",
+ NULL,
+ NULL,
+ false,
+ false,
+ },
+ [ABI] = {
+ PKG_CONFIG_STRING,
+ "ABI",
+ NULL,
+ NULL,
+ NULL,
+ false,
+ true,
+ },
+ [MIRROR_TYPE] = {
+ PKG_CONFIG_STRING,
+ "MIRROR_TYPE",
+ "SRV",
+ NULL,
+ NULL,
+ false,
+ false,
+ },
+ [ASSUME_ALWAYS_YES] = {
+ PKG_CONFIG_BOOL,
+ "ASSUME_ALWAYS_YES",
+ "NO",
+ NULL,
+ NULL,
+ false,
+ true,
+ },
+ [SIGNATURE_TYPE] = {
+ PKG_CONFIG_STRING,
+ "SIGNATURE_TYPE",
+ NULL,
+ NULL,
+ NULL,
+ false,
+ false,
+ },
+ [FINGERPRINTS] = {
+ PKG_CONFIG_STRING,
+ "FINGERPRINTS",
+ NULL,
+ NULL,
+ NULL,
+ false,
+ false,
+ },
+ [REPOS_DIR] = {
+ PKG_CONFIG_LIST,
+ "REPOS_DIR",
+ NULL,
+ NULL,
+ NULL,
+ false,
+ true,
+ },
+ [PUBKEY] = {
+ PKG_CONFIG_STRING,
+ "PUBKEY",
+ NULL,
+ NULL,
+ NULL,
+ false,
+ false
+ }
+};
+
+static int
+pkg_get_myabi(char *dest, size_t sz)
+{
+ struct utsname uts;
+ char machine_arch[255];
+ size_t len;
+ int error;
+
+ error = uname(&uts);
+ if (error)
+ return (errno);
+
+ len = sizeof(machine_arch);
+ error = sysctlbyname("hw.machine_arch", machine_arch, &len, NULL, 0);
+ if (error)
+ return (errno);
+ machine_arch[len] = '\0';
+
+ /*
+ * Use __FreeBSD_version rather than kernel version (uts.release) for
+ * use in jails. This is equivalent to the value of uname -U.
+ */
+ snprintf(dest, sz, "%s:%d:%s", uts.sysname, __FreeBSD_version/100000,
+ machine_arch);
+
+ return (error);
+}
+
+static void
+subst_packagesite(const char *abi)
+{
+ struct sbuf *newval;
+ const char *variable_string;
+ const char *oldval;
+
+ if (c[PACKAGESITE].value != NULL)
+ oldval = c[PACKAGESITE].value;
+ else
+ oldval = c[PACKAGESITE].val;
+
+ if ((variable_string = strstr(oldval, "${ABI}")) == NULL)
+ return;
+
+ newval = sbuf_new_auto();
+ sbuf_bcat(newval, oldval, variable_string - oldval);
+ sbuf_cat(newval, abi);
+ sbuf_cat(newval, variable_string + strlen("${ABI}"));
+ sbuf_finish(newval);
+
+ free(c[PACKAGESITE].value);
+ c[PACKAGESITE].value = strdup(sbuf_data(newval));
+}
+
+static int
+boolstr_to_bool(const char *str)
+{
+ if (str != NULL && (strcasecmp(str, "true") == 0 ||
+ strcasecmp(str, "yes") == 0 || strcasecmp(str, "on") == 0 ||
+ str[0] == '1'))
+ return (true);
+
+ return (false);
+}
+
+static void
+config_parse(const ucl_object_t *obj, pkg_conf_file_t conftype)
+{
+ struct sbuf *buf = sbuf_new_auto();
+ const ucl_object_t *cur, *seq;
+ ucl_object_iter_t it = NULL, itseq = NULL;
+ struct config_entry *temp_config;
+ struct config_value *cv;
+ const char *key;
+ int i;
+ size_t j;
+
+ /* Temporary config for configs that may be disabled. */
+ temp_config = calloc(CONFIG_SIZE, sizeof(struct config_entry));
+
+ while ((cur = ucl_iterate_object(obj, &it, true))) {
+ key = ucl_object_key(cur);
+ if (key == NULL)
+ continue;
+ sbuf_clear(buf);
+
+ if (conftype == CONFFILE_PKG) {
+ for (j = 0; j < strlen(key); ++j)
+ sbuf_putc(buf, key[j]);
+ sbuf_finish(buf);
+ } else if (conftype == CONFFILE_REPO) {
+ if (strcasecmp(key, "url") == 0)
+ sbuf_cpy(buf, "PACKAGESITE");
+ else if (strcasecmp(key, "mirror_type") == 0)
+ sbuf_cpy(buf, "MIRROR_TYPE");
+ else if (strcasecmp(key, "signature_type") == 0)
+ sbuf_cpy(buf, "SIGNATURE_TYPE");
+ else if (strcasecmp(key, "fingerprints") == 0)
+ sbuf_cpy(buf, "FINGERPRINTS");
+ else if (strcasecmp(key, "pubkey") == 0)
+ sbuf_cpy(buf, "PUBKEY");
+ else if (strcasecmp(key, "enabled") == 0) {
+ if ((cur->type != UCL_BOOLEAN) ||
+ !ucl_object_toboolean(cur))
+ goto cleanup;
+ } else
+ continue;
+ sbuf_finish(buf);
+ }
+
+ for (i = 0; i < CONFIG_SIZE; i++) {
+ if (strcmp(sbuf_data(buf), c[i].key) == 0)
+ break;
+ }
+
+ /* Silently skip unknown keys to be future compatible. */
+ if (i == CONFIG_SIZE)
+ continue;
+
+ /* env has priority over config file */
+ if (c[i].envset)
+ continue;
+
+ /* Parse sequence value ["item1", "item2"] */
+ switch (c[i].type) {
+ case PKG_CONFIG_LIST:
+ if (cur->type != UCL_ARRAY) {
+ warnx("Skipping invalid array "
+ "value for %s.\n", c[i].key);
+ continue;
+ }
+ temp_config[i].list =
+ malloc(sizeof(*temp_config[i].list));
+ STAILQ_INIT(temp_config[i].list);
+
+ while ((seq = ucl_iterate_object(cur, &itseq, true))) {
+ if (seq->type != UCL_STRING)
+ continue;
+ cv = malloc(sizeof(struct config_value));
+ cv->value =
+ strdup(ucl_object_tostring(seq));
+ STAILQ_INSERT_TAIL(temp_config[i].list, cv,
+ next);
+ }
+ break;
+ case PKG_CONFIG_BOOL:
+ temp_config[i].value =
+ strdup(ucl_object_toboolean(cur) ? "yes" : "no");
+ break;
+ default:
+ /* Normal string value. */
+ temp_config[i].value = strdup(ucl_object_tostring(cur));
+ break;
+ }
+ }
+
+ /* Repo is enabled, copy over all settings from temp_config. */
+ for (i = 0; i < CONFIG_SIZE; i++) {
+ if (c[i].envset)
+ continue;
+ /* Prevent overriding ABI, ASSUME_ALWAYS_YES, etc. */
+ if (conftype != CONFFILE_PKG && c[i].main_only == true)
+ continue;
+ switch (c[i].type) {
+ case PKG_CONFIG_LIST:
+ c[i].list = temp_config[i].list;
+ break;
+ default:
+ c[i].value = temp_config[i].value;
+ break;
+ }
+ }
+
+cleanup:
+ free(temp_config);
+ sbuf_delete(buf);
+}
+
+/*-
+ * Parse new repo style configs in style:
+ * Name:
+ * URL:
+ * MIRROR_TYPE:
+ * etc...
+ */
+static void
+parse_repo_file(ucl_object_t *obj)
+{
+ ucl_object_iter_t it = NULL;
+ const ucl_object_t *cur;
+ const char *key;
+
+ while ((cur = ucl_iterate_object(obj, &it, true))) {
+ key = ucl_object_key(cur);
+
+ if (key == NULL)
+ continue;
+
+ if (cur->type != UCL_OBJECT)
+ continue;
+
+ config_parse(cur, CONFFILE_REPO);
+ }
+}
+
+
+static int
+read_conf_file(const char *confpath, pkg_conf_file_t conftype)
+{
+ struct ucl_parser *p;
+ ucl_object_t *obj = NULL;
+
+ p = ucl_parser_new(0);
+
+ if (!ucl_parser_add_file(p, confpath)) {
+ if (errno != ENOENT)
+ errx(EXIT_FAILURE, "Unable to parse configuration "
+ "file %s: %s", confpath, ucl_parser_get_error(p));
+ ucl_parser_free(p);
+ /* no configuration present */
+ return (1);
+ }
+
+ obj = ucl_parser_get_object(p);
+ if (obj->type != UCL_OBJECT)
+ warnx("Invalid configuration format, ignoring the "
+ "configuration file %s", confpath);
+ else {
+ if (conftype == CONFFILE_PKG)
+ config_parse(obj, conftype);
+ else if (conftype == CONFFILE_REPO)
+ parse_repo_file(obj);
+ }
+
+ ucl_object_unref(obj);
+ ucl_parser_free(p);
+
+ return (0);
+}
+
+static int
+load_repositories(const char *repodir)
+{
+ struct dirent *ent;
+ DIR *d;
+ char *p;
+ size_t n;
+ char path[MAXPATHLEN];
+ int ret;
+
+ ret = 0;
+
+ if ((d = opendir(repodir)) == NULL)
+ return (1);
+
+ while ((ent = readdir(d))) {
+ /* Trim out 'repos'. */
+ if ((n = strlen(ent->d_name)) <= 5)
+ continue;
+ p = &ent->d_name[n - 5];
+ if (strcmp(p, ".conf") == 0) {
+ snprintf(path, sizeof(path), "%s%s%s",
+ repodir,
+ repodir[strlen(repodir) - 1] == '/' ? "" : "/",
+ ent->d_name);
+ if (access(path, F_OK) == 0 &&
+ read_conf_file(path, CONFFILE_REPO)) {
+ ret = 1;
+ goto cleanup;
+ }
+ }
+ }
+
+cleanup:
+ closedir(d);
+
+ return (ret);
+}
+
+int
+config_init(void)
+{
+ char *val;
+ int i;
+ const char *localbase;
+ char *env_list_item;
+ char confpath[MAXPATHLEN];
+ struct config_value *cv;
+ char abi[BUFSIZ];
+
+ for (i = 0; i < CONFIG_SIZE; i++) {
+ val = getenv(c[i].key);
+ if (val != NULL) {
+ c[i].envset = true;
+ switch (c[i].type) {
+ case PKG_CONFIG_LIST:
+ /* Split up comma-separated items from env. */
+ c[i].list = malloc(sizeof(*c[i].list));
+ STAILQ_INIT(c[i].list);
+ for (env_list_item = strtok(val, ",");
+ env_list_item != NULL;
+ env_list_item = strtok(NULL, ",")) {
+ cv =
+ malloc(sizeof(struct config_value));
+ cv->value =
+ strdup(env_list_item);
+ STAILQ_INSERT_TAIL(c[i].list, cv,
+ next);
+ }
+ break;
+ default:
+ c[i].val = val;
+ break;
+ }
+ }
+ }
+
+ /* Read LOCALBASE/etc/pkg.conf first. */
+ localbase = getenv("LOCALBASE") ? getenv("LOCALBASE") : _LOCALBASE;
+ snprintf(confpath, sizeof(confpath), "%s/etc/pkg.conf",
+ localbase);
+
+ if (access(confpath, F_OK) == 0 && read_conf_file(confpath,
+ CONFFILE_PKG))
+ goto finalize;
+
+ /* Then read in all repos from REPOS_DIR list of directories. */
+ if (c[REPOS_DIR].list == NULL) {
+ c[REPOS_DIR].list = malloc(sizeof(*c[REPOS_DIR].list));
+ STAILQ_INIT(c[REPOS_DIR].list);
+ cv = malloc(sizeof(struct config_value));
+ cv->value = strdup("/etc/pkg");
+ STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next);
+ cv = malloc(sizeof(struct config_value));
+ if (asprintf(&cv->value, "%s/etc/pkg/repos", localbase) < 0)
+ goto finalize;
+ STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next);
+ }
+
+ STAILQ_FOREACH(cv, c[REPOS_DIR].list, next)
+ if (load_repositories(cv->value))
+ goto finalize;
+
+finalize:
+ if (c[ABI].val == NULL && c[ABI].value == NULL) {
+ if (pkg_get_myabi(abi, BUFSIZ) != 0)
+ errx(EXIT_FAILURE, "Failed to determine the system "
+ "ABI");
+ c[ABI].val = abi;
+ }
+
+ subst_packagesite(c[ABI].value != NULL ? c[ABI].value : c[ABI].val);
+
+ return (0);
+}
+
+int
+config_string(pkg_config_key k, const char **val)
+{
+ if (c[k].type != PKG_CONFIG_STRING)
+ return (-1);
+
+ if (c[k].value != NULL)
+ *val = c[k].value;
+ else
+ *val = c[k].val;
+
+ return (0);
+}
+
+int
+config_bool(pkg_config_key k, bool *val)
+{
+ const char *value;
+
+ if (c[k].type != PKG_CONFIG_BOOL)
+ return (-1);
+
+ *val = false;
+
+ if (c[k].value != NULL)
+ value = c[k].value;
+ else
+ value = c[k].val;
+
+ if (boolstr_to_bool(value))
+ *val = true;
+
+ return (0);
+}
+
+void
+config_finish(void) {
+ int i;
+
+ for (i = 0; i < CONFIG_SIZE; i++)
+ free(c[i].value);
+}
diff --git a/usr.sbin/pkg/config.h b/usr.sbin/pkg/config.h
new file mode 100644
index 0000000..4ff0a16
--- /dev/null
+++ b/usr.sbin/pkg/config.h
@@ -0,0 +1,63 @@
+/*-
+ * Copyright (c) 2013 Baptiste Daroussin <bapt@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 _PKG_CONFIG_H
+#define _PKG_CONFIG_H
+
+#define _LOCALBASE "/usr/local"
+#define URL_SCHEME_PREFIX "pkg+"
+
+typedef enum {
+ PACKAGESITE = 0,
+ ABI,
+ MIRROR_TYPE,
+ ASSUME_ALWAYS_YES,
+ SIGNATURE_TYPE,
+ FINGERPRINTS,
+ REPOS_DIR,
+ PUBKEY,
+ CONFIG_SIZE
+} pkg_config_key;
+
+typedef enum {
+ PKG_CONFIG_STRING=0,
+ PKG_CONFIG_BOOL,
+ PKG_CONFIG_LIST,
+} pkg_config_t;
+
+typedef enum {
+ CONFFILE_PKG=0,
+ CONFFILE_REPO,
+} pkg_conf_file_t;
+
+int config_init(void);
+void config_finish(void);
+int config_string(pkg_config_key, const char **);
+int config_bool(pkg_config_key, bool *);
+
+#endif
diff --git a/usr.sbin/pkg/dns_utils.c b/usr.sbin/pkg/dns_utils.c
new file mode 100644
index 0000000..a3c3a7e
--- /dev/null
+++ b/usr.sbin/pkg/dns_utils.c
@@ -0,0 +1,222 @@
+/*-
+ * Copyright (c) 2012-2013 Baptiste Daroussin <bapt@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,
+ * 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 <stdlib.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <resolv.h>
+
+#include "dns_utils.h"
+
+typedef union {
+ HEADER hdr;
+ unsigned char buf[1024];
+} dns_query;
+
+static int
+srv_priority_cmp(const void *a, const void *b)
+{
+ const struct dns_srvinfo *da, *db;
+ unsigned int r, l;
+
+ da = *(struct dns_srvinfo * const *)a;
+ db = *(struct dns_srvinfo * const *)b;
+
+ l = da->priority;
+ r = db->priority;
+
+ return ((l > r) - (l < r));
+}
+
+static int
+srv_final_cmp(const void *a, const void *b)
+{
+ const struct dns_srvinfo *da, *db;
+ unsigned int r, l, wr, wl;
+ int res;
+
+ da = *(struct dns_srvinfo * const *)a;
+ db = *(struct dns_srvinfo * const *)b;
+
+ l = da->priority;
+ r = db->priority;
+
+ res = ((l > r) - (l < r));
+
+ if (res == 0) {
+ wl = da->finalweight;
+ wr = db->finalweight;
+ res = ((wr > wl) - (wr < wl));
+ }
+
+ return (res);
+}
+
+static void
+compute_weight(struct dns_srvinfo **d, int first, int last)
+{
+ int i, j, totalweight;
+ int *chosen;
+
+ totalweight = 0;
+
+ for (i = 0; i <= last; i++)
+ totalweight += d[i]->weight;
+
+ if (totalweight == 0)
+ return;
+
+ chosen = malloc(sizeof(int) * (last - first + 1));
+
+ for (i = 0; i <= last; i++) {
+ for (;;) {
+ chosen[i] = random() % (d[i]->weight * 100 / totalweight);
+ for (j = 0; j < i; j++) {
+ if (chosen[i] == chosen[j])
+ break;
+ }
+ if (j == i) {
+ d[i]->finalweight = chosen[i];
+ break;
+ }
+ }
+ }
+
+ free(chosen);
+}
+
+struct dns_srvinfo *
+dns_getsrvinfo(const char *zone)
+{
+ struct dns_srvinfo **res, *first;
+ unsigned char *end, *p;
+ char host[MAXHOSTNAMELEN];
+ dns_query q;
+ int len, qdcount, ancount, n, i, f, l;
+ unsigned int type, class, ttl, priority, weight, port;
+
+ if ((len = res_query(zone, C_IN, T_SRV, q.buf, sizeof(q.buf))) == -1 ||
+ len < (int)sizeof(HEADER))
+ return (NULL);
+
+ qdcount = ntohs(q.hdr.qdcount);
+ ancount = ntohs(q.hdr.ancount);
+
+ end = q.buf + len;
+ p = q.buf + sizeof(HEADER);
+
+ while(qdcount > 0 && p < end) {
+ qdcount--;
+ if((len = dn_expand(q.buf, end, p, host, MAXHOSTNAMELEN)) < 0)
+ return (NULL);
+ p += len + NS_QFIXEDSZ;
+ }
+
+ res = calloc(ancount, sizeof(struct dns_srvinfo *));
+ if (res == NULL)
+ return (NULL);
+
+ n = 0;
+ while (ancount > 0 && p < end) {
+ ancount--;
+ len = dn_expand(q.buf, end, p, host, MAXHOSTNAMELEN);
+ if (len < 0) {
+ for (i = 0; i < n; i++)
+ free(res[i]);
+ free(res);
+ return NULL;
+ }
+
+ p += len;
+
+ NS_GET16(type, p);
+ NS_GET16(class, p);
+ NS_GET32(ttl, p);
+ NS_GET16(len, p);
+
+ if (type != T_SRV) {
+ p += len;
+ continue;
+ }
+
+ NS_GET16(priority, p);
+ NS_GET16(weight, p);
+ NS_GET16(port, p);
+
+ len = dn_expand(q.buf, end, p, host, MAXHOSTNAMELEN);
+ if (len < 0) {
+ for (i = 0; i < n; i++)
+ free(res[i]);
+ free(res);
+ return (NULL);
+ }
+
+ res[n] = malloc(sizeof(struct dns_srvinfo));
+ if (res[n] == NULL) {
+ for (i = 0; i < n; i++)
+ free(res[i]);
+ free(res);
+ return (NULL);
+ }
+ res[n]->type = type;
+ res[n]->class = class;
+ res[n]->ttl = ttl;
+ res[n]->priority = priority;
+ res[n]->weight = weight;
+ res[n]->port = port;
+ res[n]->next = NULL;
+ strlcpy(res[n]->host, host, MAXHOSTNAMELEN);
+
+ p += len;
+ n++;
+ }
+
+ qsort(res, n, sizeof(res[0]), srv_priority_cmp);
+
+ priority = f = l = 0;
+ for (i = 0; i < n; i++) {
+ if (res[i]->priority != priority) {
+ if (f != l)
+ compute_weight(res, f, l);
+ f = i;
+ priority = res[i]->priority;
+ }
+ l = i;
+ }
+
+ qsort(res, n, sizeof(res[0]), srv_final_cmp);
+
+ for (i = 0; i < n - 1; i++)
+ res[i]->next = res[i + 1];
+
+ first = res[0];
+ free(res);
+
+ return (first);
+}
diff --git a/usr.sbin/pkg/dns_utils.h b/usr.sbin/pkg/dns_utils.h
new file mode 100644
index 0000000..b1418bb
--- /dev/null
+++ b/usr.sbin/pkg/dns_utils.h
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 2012 Baptiste Daroussin <bapt@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 DNS_UTILS_H
+#define DNS_UTILS_H
+struct dns_srvinfo {
+ unsigned int type;
+ unsigned int class;
+ unsigned int ttl;
+ unsigned int priority;
+ unsigned int weight;
+ unsigned int port;
+ unsigned int finalweight;
+ char host[MAXHOSTNAMELEN];
+ struct dns_srvinfo *next;
+};
+
+struct dns_srvinfo *
+ dns_getsrvinfo(const char *zone);
+
+#endif
diff --git a/usr.sbin/pkg/pkg.7 b/usr.sbin/pkg/pkg.7
new file mode 100644
index 0000000..342c934
--- /dev/null
+++ b/usr.sbin/pkg/pkg.7
@@ -0,0 +1,281 @@
+.\" Copyright (c) 2013 Bryan Drewery <bdrewery@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 December 12, 2013
+.Dt PKG 7
+.Os
+.Sh NAME
+.Nm pkg
+.Nd a utility for manipulating packages
+.Sh SYNOPSIS
+.Nm
+.Ao Ar command Ac
+.Nm
+add
+.Op Fl f
+.Ao Pa pkg.txz Ac
+.Nm
+.Fl N
+.Nm
+bootstrap
+.Op Fl f
+.Sh DESCRIPTION
+.Nm
+is the package management tool.
+It is used to manage local packages installed from
+.Xr ports 7
+and install/upgrade packages from remote repositories.
+.Pp
+To avoid backwards incompatibility issues, the actual
+.Xr pkg 8
+tool is not installed in the base system.
+The first time invoked,
+.Nm
+will bootstrap the real
+.Xr pkg 8
+from a remote repository.
+.Bl -tag -width "pkg bootstrap"
+.It Nm Ao Ar command Ac
+If
+.Xr pkg 8
+is not installed yet, it will be fetched, have its signature verified,
+installed, and then have the original command forwarded to it.
+If already installed, the command requested will be forwarded to the real
+.Xr pkg 8 .
+.It Nm Li add Oo Fl f Oc Ao Pa pkg.txz Ac
+Install
+.Xr pkg 8
+from a local package instead of fetching from remote.
+If a
+.Pa pkg.txz.sig
+file exists and
+signature checking is enabled, then the signature will be verified
+before installing the package.
+If the
+.Fl f
+flag is specified, then
+.Xr pkg 8
+will be installed regardless if it is already installed.
+.It Nm Fl N
+Do not bootstrap, just determine if
+.Xr pkg 8
+is actually installed or not.
+Returns 0 and the number of packages installed
+if it is, otherwise 1.
+.It Nm Li bootstrap Op Fl f
+Attempt to bootstrap and do not forward anything to
+.Xr pkg 8
+after it is installed.
+If the
+.Fl f
+flag is specified, then
+.Xr pkg 8
+will be fetched and installed regardless if it is already installed.
+.El
+.Sh CONFIGURATION
+Configuration varies in whether it is in a repository configuration file
+or the global configuration file.
+.Pp
+Repository configuration can be stored in
+.Pa /etc/pkg/FreeBSD.conf
+in the following format:
+.Bd -literal -offset indent
+FreeBSD: {
+ url: "pkg+http://pkg.FreeBSD.org/${ABI}/latest",
+ mirror_type: "srv",
+ signature_type: "none",
+ fingerprints: "/usr/share/keys/pkg",
+ enabled: yes
+}
+.Ed
+.Bl -tag -width signature_type -compact
+.It url
+Refer to
+.Dv PACKAGESITE
+in
+.Sx ENVIRONMENT
+.It mirror_type
+Refer to
+.Dv MIRROR_TYPE
+in
+.Sx ENVIRONMENT
+.It signature_type
+Refer to
+.Dv SIGNATURE_TYPE
+in
+.Sx ENVIRONMENT
+.It fingerprints
+Refer to
+.Dv FINGERPRINTS
+in
+.Sx ENVIRONMENT
+.It enabled
+Defines whether this repository should be used or not.
+Valid values are
+.Dv yes ,
+.Dv true ,
+.Dv 1 ,
+.Dv no ,
+.Dv false ,
+.Dv 0 .
+.El
+.Pp
+Global configuration can be stored in
+.Pa /usr/local/etc/pkg.conf
+in the following format:
+.Bd -literal -offset indent
+PACKAGESITE: "pkg+http://pkg.FreeBSD.org/${ABI}/latest",
+MIRROR_TYPE: "srv",
+SIGNATURE_TYPE: "none",
+FINGERPRINTS: "/usr/share/keys/pkg",
+ASSUME_ALWAYS_YES: "yes"
+REPOS_DIR: ["/etc/pkg", "/usr/local/etc/pkg/repos"]
+.Ed
+.Pp
+Reference
+.Sx ENVIRONMENT
+for each variable.
+.Sh ENVIRONMENT
+The following environment variables can be set to override the settings
+from the
+.Pa pkg.conf
+file used.
+.Bl -tag -width "ASSUME_ALWAYS_YES"
+.It Ev MIRROR_TYPE
+This defines which mirror type should be used.
+Valid values are
+.Dv SRV ,
+.Dv HTTP ,
+.Dv NONE .
+.It Ev ABI
+This defines the ABI for the package to be installed.
+Default ABI is determined from
+.Pa /bin/sh .
+.It Ev ASSUME_ALWAYS_YES
+If set, no confirmation will be asked when bootstrapping
+.Xr pkg 8 .
+.It Ev SIGNATURE_TYPE
+If set to
+.Dv FINGERPRINTS
+then a signature will be required and validated against known
+certificate fingerprints when bootstrapping
+.Xr pkg 8 .
+.It Ev FINGERPRINTS
+If
+.Sy SIGNATURE_TYPE
+is set to
+.Dv FINGERPRINTS
+this value should be set to the directory path where known fingerprints are
+located.
+.It Ev PACKAGESITE
+The URL that
+.Xr pkg 8
+and other packages
+will be fetched from.
+.It Ev REPOS_DIR
+Comma-separated list of directories that should be searched for repository
+configuration files.
+.El
+.Sh FILES
+Configuration is read from the files in the listed order.
+This path can be changed by setting
+.Sy REPOS_DIR .
+The last enabled repository is the one used for bootstrapping
+.Xr pkg 8 .
+.Bl -tag -width "/usr/local/etc/pkg/repos/*.conf"
+.It Pa /usr/local/etc/pkg.conf
+.It Pa /etc/pkg/FreeBSD.conf
+.It Pa /usr/local/etc/pkg/repos/*.conf
+.El
+.Sh EXAMPLES
+Some examples are listed here.
+The full list of available commands are available in
+.Xr pkg 8
+once it is bootstrapped.
+.Pp
+Search for a package:
+.Dl $ pkg search perl
+.Pp
+Install a package:
+.Dl % pkg install perl
+.Pp
+List installed packages:
+.Dl $ pkg info
+.Pp
+Upgrade from remote repository:
+.Dl % pkg upgrade
+.Pp
+List non-automatic packages:
+.Dl $ pkg query -e '%a = 0' %o
+.Pp
+List automatic packages:
+.Dl $ pkg query -e '%a = 1' %o
+.Pp
+Delete an installed package:
+.Dl % pkg delete perl
+.Pp
+Remove unneeded dependencies:
+.Dl % pkg autoremove
+.Pp
+Change a package from automatic to non-automatic, which will prevent
+.Ic autoremove
+from removing it:
+.Dl % pkg set -A 0 perl
+.Pp
+Change a package from non-automatic to automatic, which will make
+.Ic autoremove
+allow it be removed once nothing depends on it:
+.Dl % pkg set -A 1 perl
+.Pp
+Create package file from an installed package:
+.Dl % pkg create -o /usr/ports/packages/All perl
+.Pp
+Determine which package installed a file:
+.Dl $ pkg which /usr/local/bin/perl
+.Pp
+Audit installed packages for security advisories:
+.Dl $ pkg audit
+.Pp
+Check installed packages for checksum mismatches:
+.Dl # pkg check -s -a
+.Pp
+Check for missing dependencies:
+.Dl # pkg check -d -a
+.Sh SEE ALSO
+.Xr ports 7 ,
+.Xr pkg 8
+.Sh HISTORY
+The
+.Nm
+command first appeared in
+.Fx 9.1 .
+It became the default package tool in
+.Fx 10.0 ,
+replacing the
+pkg_install suite of tools
+.Xr pkg_add 1 ,
+.Xr pkg_info 1 and
+.Xr pkg_create 1 .
diff --git a/usr.sbin/pkg/pkg.c b/usr.sbin/pkg/pkg.c
new file mode 100644
index 0000000..bd3076c
--- /dev/null
+++ b/usr.sbin/pkg/pkg.c
@@ -0,0 +1,1109 @@
+/*-
+ * Copyright (c) 2012-2014 Baptiste Daroussin <bapt@FreeBSD.org>
+ * Copyright (c) 2013 Bryan Drewery <bdrewery@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/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/sbuf.h>
+#include <sys/wait.h>
+
+#define _WITH_GETLINE
+#include <archive.h>
+#include <archive_entry.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fetch.h>
+#include <paths.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ucl.h>
+
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+
+#include "dns_utils.h"
+#include "config.h"
+
+struct sig_cert {
+ char *name;
+ unsigned char *sig;
+ int siglen;
+ unsigned char *cert;
+ int certlen;
+ bool trusted;
+};
+
+struct pubkey {
+ unsigned char *sig;
+ int siglen;
+};
+
+typedef enum {
+ HASH_UNKNOWN,
+ HASH_SHA256,
+} hash_t;
+
+struct fingerprint {
+ hash_t type;
+ char *name;
+ char hash[BUFSIZ];
+ STAILQ_ENTRY(fingerprint) next;
+};
+
+STAILQ_HEAD(fingerprint_list, fingerprint);
+
+static int
+extract_pkg_static(int fd, char *p, int sz)
+{
+ struct archive *a;
+ struct archive_entry *ae;
+ char *end;
+ int ret, r;
+
+ ret = -1;
+ a = archive_read_new();
+ if (a == NULL) {
+ warn("archive_read_new");
+ return (ret);
+ }
+ archive_read_support_filter_all(a);
+ archive_read_support_format_tar(a);
+
+ if (lseek(fd, 0, 0) == -1) {
+ warn("lseek");
+ goto cleanup;
+ }
+
+ if (archive_read_open_fd(a, fd, 4096) != ARCHIVE_OK) {
+ warnx("archive_read_open_fd: %s", archive_error_string(a));
+ goto cleanup;
+ }
+
+ ae = NULL;
+ while ((r = archive_read_next_header(a, &ae)) == ARCHIVE_OK) {
+ end = strrchr(archive_entry_pathname(ae), '/');
+ if (end == NULL)
+ continue;
+
+ if (strcmp(end, "/pkg-static") == 0) {
+ r = archive_read_extract(a, ae,
+ ARCHIVE_EXTRACT_OWNER | ARCHIVE_EXTRACT_PERM |
+ ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_ACL |
+ ARCHIVE_EXTRACT_FFLAGS | ARCHIVE_EXTRACT_XATTR);
+ strlcpy(p, archive_entry_pathname(ae), sz);
+ break;
+ }
+ }
+
+ if (r == ARCHIVE_OK)
+ ret = 0;
+ else
+ warnx("failed to extract pkg-static: %s",
+ archive_error_string(a));
+
+cleanup:
+ archive_read_free(a);
+ return (ret);
+
+}
+
+static int
+install_pkg_static(const char *path, const char *pkgpath, bool force)
+{
+ int pstat;
+ pid_t pid;
+
+ switch ((pid = fork())) {
+ case -1:
+ return (-1);
+ case 0:
+ if (force)
+ execl(path, "pkg-static", "add", "-f", pkgpath,
+ (char *)NULL);
+ else
+ execl(path, "pkg-static", "add", pkgpath,
+ (char *)NULL);
+ _exit(1);
+ default:
+ break;
+ }
+
+ while (waitpid(pid, &pstat, 0) == -1)
+ if (errno != EINTR)
+ return (-1);
+
+ if (WEXITSTATUS(pstat))
+ return (WEXITSTATUS(pstat));
+ else if (WIFSIGNALED(pstat))
+ return (128 & (WTERMSIG(pstat)));
+ return (pstat);
+}
+
+static int
+fetch_to_fd(const char *url, char *path)
+{
+ struct url *u;
+ struct dns_srvinfo *mirrors, *current;
+ struct url_stat st;
+ FILE *remote;
+ /* To store _https._tcp. + hostname + \0 */
+ int fd;
+ int retry, max_retry;
+ ssize_t r;
+ char buf[10240];
+ char zone[MAXHOSTNAMELEN + 13];
+ static const char *mirror_type = NULL;
+
+ max_retry = 3;
+ current = mirrors = NULL;
+ remote = NULL;
+
+ if (mirror_type == NULL && config_string(MIRROR_TYPE, &mirror_type)
+ != 0) {
+ warnx("No MIRROR_TYPE defined");
+ return (-1);
+ }
+
+ if ((fd = mkstemp(path)) == -1) {
+ warn("mkstemp()");
+ return (-1);
+ }
+
+ retry = max_retry;
+
+ if ((u = fetchParseURL(url)) == NULL) {
+ warn("fetchParseURL('%s')", url);
+ return (-1);
+ }
+
+ while (remote == NULL) {
+ if (retry == max_retry) {
+ if (strcmp(u->scheme, "file") != 0 &&
+ strcasecmp(mirror_type, "srv") == 0) {
+ snprintf(zone, sizeof(zone),
+ "_%s._tcp.%s", u->scheme, u->host);
+ mirrors = dns_getsrvinfo(zone);
+ current = mirrors;
+ }
+ }
+
+ if (mirrors != NULL) {
+ strlcpy(u->host, current->host, sizeof(u->host));
+ u->port = current->port;
+ }
+
+ remote = fetchXGet(u, &st, "");
+ if (remote == NULL) {
+ --retry;
+ if (retry <= 0)
+ goto fetchfail;
+ if (mirrors == NULL) {
+ sleep(1);
+ } else {
+ current = current->next;
+ if (current == NULL)
+ current = mirrors;
+ }
+ }
+ }
+
+ while ((r = fread(buf, 1, sizeof(buf), remote)) > 0) {
+ if (write(fd, buf, r) != r) {
+ warn("write()");
+ goto fetchfail;
+ }
+ }
+
+ if (r != 0) {
+ warn("An error occurred while fetching pkg(8)");
+ goto fetchfail;
+ }
+
+ if (ferror(remote))
+ goto fetchfail;
+
+ goto cleanup;
+
+fetchfail:
+ if (fd != -1) {
+ close(fd);
+ fd = -1;
+ unlink(path);
+ }
+
+cleanup:
+ if (remote != NULL)
+ fclose(remote);
+
+ return fd;
+}
+
+static struct fingerprint *
+parse_fingerprint(ucl_object_t *obj)
+{
+ const ucl_object_t *cur;
+ ucl_object_iter_t it = NULL;
+ const char *function, *fp, *key;
+ struct fingerprint *f;
+ hash_t fct = HASH_UNKNOWN;
+
+ function = fp = NULL;
+
+ while ((cur = ucl_iterate_object(obj, &it, true))) {
+ key = ucl_object_key(cur);
+ if (cur->type != UCL_STRING)
+ continue;
+ if (strcasecmp(key, "function") == 0) {
+ function = ucl_object_tostring(cur);
+ continue;
+ }
+ if (strcasecmp(key, "fingerprint") == 0) {
+ fp = ucl_object_tostring(cur);
+ continue;
+ }
+ }
+
+ if (fp == NULL || function == NULL)
+ return (NULL);
+
+ if (strcasecmp(function, "sha256") == 0)
+ fct = HASH_SHA256;
+
+ if (fct == HASH_UNKNOWN) {
+ warnx("Unsupported hashing function: %s", function);
+ return (NULL);
+ }
+
+ f = calloc(1, sizeof(struct fingerprint));
+ f->type = fct;
+ strlcpy(f->hash, fp, sizeof(f->hash));
+
+ return (f);
+}
+
+static void
+free_fingerprint_list(struct fingerprint_list* list)
+{
+ struct fingerprint *fingerprint, *tmp;
+
+ STAILQ_FOREACH_SAFE(fingerprint, list, next, tmp) {
+ free(fingerprint->name);
+ free(fingerprint);
+ }
+ free(list);
+}
+
+static struct fingerprint *
+load_fingerprint(const char *dir, const char *filename)
+{
+ ucl_object_t *obj = NULL;
+ struct ucl_parser *p = NULL;
+ struct fingerprint *f;
+ char path[MAXPATHLEN];
+
+ f = NULL;
+
+ snprintf(path, MAXPATHLEN, "%s/%s", dir, filename);
+
+ p = ucl_parser_new(0);
+ if (!ucl_parser_add_file(p, path)) {
+ warnx("%s: %s", path, ucl_parser_get_error(p));
+ ucl_parser_free(p);
+ return (NULL);
+ }
+
+ obj = ucl_parser_get_object(p);
+
+ if (obj->type == UCL_OBJECT)
+ f = parse_fingerprint(obj);
+
+ if (f != NULL)
+ f->name = strdup(filename);
+
+ ucl_object_unref(obj);
+ ucl_parser_free(p);
+
+ return (f);
+}
+
+static struct fingerprint_list *
+load_fingerprints(const char *path, int *count)
+{
+ DIR *d;
+ struct dirent *ent;
+ struct fingerprint *finger;
+ struct fingerprint_list *fingerprints;
+
+ *count = 0;
+
+ fingerprints = calloc(1, sizeof(struct fingerprint_list));
+ if (fingerprints == NULL)
+ return (NULL);
+ STAILQ_INIT(fingerprints);
+
+ if ((d = opendir(path)) == NULL) {
+ free(fingerprints);
+
+ return (NULL);
+ }
+
+ while ((ent = readdir(d))) {
+ if (strcmp(ent->d_name, ".") == 0 ||
+ strcmp(ent->d_name, "..") == 0)
+ continue;
+ finger = load_fingerprint(path, ent->d_name);
+ if (finger != NULL) {
+ STAILQ_INSERT_TAIL(fingerprints, finger, next);
+ ++(*count);
+ }
+ }
+
+ closedir(d);
+
+ return (fingerprints);
+}
+
+static void
+sha256_hash(unsigned char hash[SHA256_DIGEST_LENGTH],
+ char out[SHA256_DIGEST_LENGTH * 2 + 1])
+{
+ int i;
+
+ for (i = 0; i < SHA256_DIGEST_LENGTH; i++)
+ sprintf(out + (i * 2), "%02x", hash[i]);
+
+ out[SHA256_DIGEST_LENGTH * 2] = '\0';
+}
+
+static void
+sha256_buf(char *buf, size_t len, char out[SHA256_DIGEST_LENGTH * 2 + 1])
+{
+ unsigned char hash[SHA256_DIGEST_LENGTH];
+ SHA256_CTX sha256;
+
+ out[0] = '\0';
+
+ SHA256_Init(&sha256);
+ SHA256_Update(&sha256, buf, len);
+ SHA256_Final(hash, &sha256);
+ sha256_hash(hash, out);
+}
+
+static int
+sha256_fd(int fd, char out[SHA256_DIGEST_LENGTH * 2 + 1])
+{
+ int my_fd;
+ FILE *fp;
+ char buffer[BUFSIZ];
+ unsigned char hash[SHA256_DIGEST_LENGTH];
+ size_t r;
+ int ret;
+ SHA256_CTX sha256;
+
+ my_fd = -1;
+ fp = NULL;
+ r = 0;
+ ret = 1;
+
+ out[0] = '\0';
+
+ /* Duplicate the fd so that fclose(3) does not close it. */
+ if ((my_fd = dup(fd)) == -1) {
+ warnx("dup");
+ goto cleanup;
+ }
+
+ if ((fp = fdopen(my_fd, "rb")) == NULL) {
+ warnx("fdopen");
+ goto cleanup;
+ }
+
+ SHA256_Init(&sha256);
+
+ while ((r = fread(buffer, 1, BUFSIZ, fp)) > 0)
+ SHA256_Update(&sha256, buffer, r);
+
+ if (ferror(fp) != 0) {
+ warnx("fread");
+ goto cleanup;
+ }
+
+ SHA256_Final(hash, &sha256);
+ sha256_hash(hash, out);
+ ret = 0;
+
+cleanup:
+ if (fp != NULL)
+ fclose(fp);
+ else if (my_fd != -1)
+ close(my_fd);
+ (void)lseek(fd, 0, SEEK_SET);
+
+ return (ret);
+}
+
+static EVP_PKEY *
+load_public_key_file(const char *file)
+{
+ EVP_PKEY *pkey;
+ BIO *bp;
+ char errbuf[1024];
+
+ bp = BIO_new_file(file, "r");
+ if (!bp)
+ errx(EXIT_FAILURE, "Unable to read %s", file);
+
+ if ((pkey = PEM_read_bio_PUBKEY(bp, NULL, NULL, NULL)) == NULL)
+ warnx("ici: %s", ERR_error_string(ERR_get_error(), errbuf));
+
+ BIO_free(bp);
+
+ return (pkey);
+}
+
+static EVP_PKEY *
+load_public_key_buf(const unsigned char *cert, int certlen)
+{
+ EVP_PKEY *pkey;
+ BIO *bp;
+ char errbuf[1024];
+
+ bp = BIO_new_mem_buf(__DECONST(void *, cert), certlen);
+
+ if ((pkey = PEM_read_bio_PUBKEY(bp, NULL, NULL, NULL)) == NULL)
+ warnx("%s", ERR_error_string(ERR_get_error(), errbuf));
+
+ BIO_free(bp);
+
+ return (pkey);
+}
+
+static bool
+rsa_verify_cert(int fd, const char *sigfile, const unsigned char *key,
+ int keylen, unsigned char *sig, int siglen)
+{
+ EVP_MD_CTX *mdctx;
+ EVP_PKEY *pkey;
+ char sha256[(SHA256_DIGEST_LENGTH * 2) + 2];
+ char errbuf[1024];
+ bool ret;
+
+ pkey = NULL;
+ mdctx = NULL;
+ ret = false;
+
+ SSL_load_error_strings();
+
+ /* Compute SHA256 of the package. */
+ if (lseek(fd, 0, 0) == -1) {
+ warn("lseek");
+ goto cleanup;
+ }
+ if ((sha256_fd(fd, sha256)) == -1) {
+ warnx("Error creating SHA256 hash for package");
+ goto cleanup;
+ }
+
+ if (sigfile != NULL) {
+ if ((pkey = load_public_key_file(sigfile)) == NULL) {
+ warnx("Error reading public key");
+ goto cleanup;
+ }
+ } else {
+ if ((pkey = load_public_key_buf(key, keylen)) == NULL) {
+ warnx("Error reading public key");
+ goto cleanup;
+ }
+ }
+
+ /* Verify signature of the SHA256(pkg) is valid. */
+ if ((mdctx = EVP_MD_CTX_create()) == NULL) {
+ warnx("%s", ERR_error_string(ERR_get_error(), errbuf));
+ goto error;
+ }
+
+ if (EVP_DigestVerifyInit(mdctx, NULL, EVP_sha256(), NULL, pkey) != 1) {
+ warnx("%s", ERR_error_string(ERR_get_error(), errbuf));
+ goto error;
+ }
+ if (EVP_DigestVerifyUpdate(mdctx, sha256, strlen(sha256)) != 1) {
+ warnx("%s", ERR_error_string(ERR_get_error(), errbuf));
+ goto error;
+ }
+
+ if (EVP_DigestVerifyFinal(mdctx, sig, siglen) != 1) {
+ warnx("%s", ERR_error_string(ERR_get_error(), errbuf));
+ goto error;
+ }
+
+ ret = true;
+ printf("done\n");
+ goto cleanup;
+
+error:
+ printf("failed\n");
+
+cleanup:
+ if (pkey)
+ EVP_PKEY_free(pkey);
+ if (mdctx)
+ EVP_MD_CTX_destroy(mdctx);
+ ERR_free_strings();
+
+ return (ret);
+}
+
+static struct pubkey *
+read_pubkey(int fd)
+{
+ struct pubkey *pk;
+ struct sbuf *sig;
+ char buf[4096];
+ int r;
+
+ if (lseek(fd, 0, 0) == -1) {
+ warn("lseek");
+ return (NULL);
+ }
+
+ sig = sbuf_new_auto();
+
+ while ((r = read(fd, buf, sizeof(buf))) >0) {
+ sbuf_bcat(sig, buf, r);
+ }
+
+ sbuf_finish(sig);
+ pk = calloc(1, sizeof(struct pubkey));
+ pk->siglen = sbuf_len(sig);
+ pk->sig = calloc(1, pk->siglen);
+ memcpy(pk->sig, sbuf_data(sig), pk->siglen);
+ sbuf_delete(sig);
+
+ return (pk);
+}
+
+static struct sig_cert *
+parse_cert(int fd) {
+ int my_fd;
+ struct sig_cert *sc;
+ FILE *fp;
+ struct sbuf *buf, *sig, *cert;
+ char *line;
+ size_t linecap;
+ ssize_t linelen;
+
+ buf = NULL;
+ my_fd = -1;
+ sc = NULL;
+ line = NULL;
+ linecap = 0;
+
+ if (lseek(fd, 0, 0) == -1) {
+ warn("lseek");
+ return (NULL);
+ }
+
+ /* Duplicate the fd so that fclose(3) does not close it. */
+ if ((my_fd = dup(fd)) == -1) {
+ warnx("dup");
+ return (NULL);
+ }
+
+ if ((fp = fdopen(my_fd, "rb")) == NULL) {
+ warn("fdopen");
+ close(my_fd);
+ return (NULL);
+ }
+
+ sig = sbuf_new_auto();
+ cert = sbuf_new_auto();
+
+ while ((linelen = getline(&line, &linecap, fp)) > 0) {
+ if (strcmp(line, "SIGNATURE\n") == 0) {
+ buf = sig;
+ continue;
+ } else if (strcmp(line, "CERT\n") == 0) {
+ buf = cert;
+ continue;
+ } else if (strcmp(line, "END\n") == 0) {
+ break;
+ }
+ if (buf != NULL)
+ sbuf_bcat(buf, line, linelen);
+ }
+
+ fclose(fp);
+
+ /* Trim out unrelated trailing newline */
+ sbuf_setpos(sig, sbuf_len(sig) - 1);
+
+ sbuf_finish(sig);
+ sbuf_finish(cert);
+
+ sc = calloc(1, sizeof(struct sig_cert));
+ sc->siglen = sbuf_len(sig);
+ sc->sig = calloc(1, sc->siglen);
+ memcpy(sc->sig, sbuf_data(sig), sc->siglen);
+
+ sc->certlen = sbuf_len(cert);
+ sc->cert = strdup(sbuf_data(cert));
+
+ sbuf_delete(sig);
+ sbuf_delete(cert);
+
+ return (sc);
+}
+
+static bool
+verify_pubsignature(int fd_pkg, int fd_sig)
+{
+ struct pubkey *pk;
+ const char *pubkey;
+ bool ret;
+
+ pk = NULL;
+ pubkey = NULL;
+ ret = false;
+ if (config_string(PUBKEY, &pubkey) != 0) {
+ warnx("No CONFIG_PUBKEY defined");
+ goto cleanup;
+ }
+
+ if ((pk = read_pubkey(fd_sig)) == NULL) {
+ warnx("Error reading signature");
+ goto cleanup;
+ }
+
+ /* Verify the signature. */
+ printf("Verifying signature with public key %s... ", pubkey);
+ if (rsa_verify_cert(fd_pkg, pubkey, NULL, 0, pk->sig,
+ pk->siglen) == false) {
+ fprintf(stderr, "Signature is not valid\n");
+ goto cleanup;
+ }
+
+ ret = true;
+
+cleanup:
+ if (pk) {
+ free(pk->sig);
+ free(pk);
+ }
+
+ return (ret);
+}
+
+static bool
+verify_signature(int fd_pkg, int fd_sig)
+{
+ struct fingerprint_list *trusted, *revoked;
+ struct fingerprint *fingerprint;
+ struct sig_cert *sc;
+ bool ret;
+ int trusted_count, revoked_count;
+ const char *fingerprints;
+ char path[MAXPATHLEN];
+ char hash[SHA256_DIGEST_LENGTH * 2 + 1];
+
+ sc = NULL;
+ trusted = revoked = NULL;
+ ret = false;
+
+ /* Read and parse fingerprints. */
+ if (config_string(FINGERPRINTS, &fingerprints) != 0) {
+ warnx("No CONFIG_FINGERPRINTS defined");
+ goto cleanup;
+ }
+
+ snprintf(path, MAXPATHLEN, "%s/trusted", fingerprints);
+ if ((trusted = load_fingerprints(path, &trusted_count)) == NULL) {
+ warnx("Error loading trusted certificates");
+ goto cleanup;
+ }
+
+ if (trusted_count == 0 || trusted == NULL) {
+ fprintf(stderr, "No trusted certificates found.\n");
+ goto cleanup;
+ }
+
+ snprintf(path, MAXPATHLEN, "%s/revoked", fingerprints);
+ if ((revoked = load_fingerprints(path, &revoked_count)) == NULL) {
+ warnx("Error loading revoked certificates");
+ goto cleanup;
+ }
+
+ /* Read certificate and signature in. */
+ if ((sc = parse_cert(fd_sig)) == NULL) {
+ warnx("Error parsing certificate");
+ goto cleanup;
+ }
+ /* Explicitly mark as non-trusted until proven otherwise. */
+ sc->trusted = false;
+
+ /* Parse signature and pubkey out of the certificate */
+ sha256_buf(sc->cert, sc->certlen, hash);
+
+ /* Check if this hash is revoked */
+ if (revoked != NULL) {
+ STAILQ_FOREACH(fingerprint, revoked, next) {
+ if (strcasecmp(fingerprint->hash, hash) == 0) {
+ fprintf(stderr, "The package was signed with "
+ "revoked certificate %s\n",
+ fingerprint->name);
+ goto cleanup;
+ }
+ }
+ }
+
+ STAILQ_FOREACH(fingerprint, trusted, next) {
+ if (strcasecmp(fingerprint->hash, hash) == 0) {
+ sc->trusted = true;
+ sc->name = strdup(fingerprint->name);
+ break;
+ }
+ }
+
+ if (sc->trusted == false) {
+ fprintf(stderr, "No trusted fingerprint found matching "
+ "package's certificate\n");
+ goto cleanup;
+ }
+
+ /* Verify the signature. */
+ printf("Verifying signature with trusted certificate %s... ", sc->name);
+ if (rsa_verify_cert(fd_pkg, NULL, sc->cert, sc->certlen, sc->sig,
+ sc->siglen) == false) {
+ fprintf(stderr, "Signature is not valid\n");
+ goto cleanup;
+ }
+
+ ret = true;
+
+cleanup:
+ if (trusted)
+ free_fingerprint_list(trusted);
+ if (revoked)
+ free_fingerprint_list(revoked);
+ if (sc) {
+ free(sc->cert);
+ free(sc->sig);
+ free(sc->name);
+ free(sc);
+ }
+
+ return (ret);
+}
+
+static int
+bootstrap_pkg(bool force)
+{
+ int fd_pkg, fd_sig;
+ int ret;
+ char url[MAXPATHLEN];
+ char tmppkg[MAXPATHLEN];
+ char tmpsig[MAXPATHLEN];
+ const char *packagesite;
+ const char *signature_type;
+ char pkgstatic[MAXPATHLEN];
+
+ fd_sig = -1;
+ ret = -1;
+
+ if (config_string(PACKAGESITE, &packagesite) != 0) {
+ warnx("No PACKAGESITE defined");
+ return (-1);
+ }
+
+ if (config_string(SIGNATURE_TYPE, &signature_type) != 0) {
+ warnx("Error looking up SIGNATURE_TYPE");
+ return (-1);
+ }
+
+ printf("Bootstrapping pkg from %s, please wait...\n", packagesite);
+
+ /* Support pkg+http:// for PACKAGESITE which is the new format
+ in 1.2 to avoid confusion on why http://pkg.FreeBSD.org has
+ no A record. */
+ if (strncmp(URL_SCHEME_PREFIX, packagesite,
+ strlen(URL_SCHEME_PREFIX)) == 0)
+ packagesite += strlen(URL_SCHEME_PREFIX);
+ snprintf(url, MAXPATHLEN, "%s/Latest/pkg.txz", packagesite);
+
+ snprintf(tmppkg, MAXPATHLEN, "%s/pkg.txz.XXXXXX",
+ getenv("TMPDIR") ? getenv("TMPDIR") : _PATH_TMP);
+
+ if ((fd_pkg = fetch_to_fd(url, tmppkg)) == -1)
+ goto fetchfail;
+
+ if (signature_type != NULL &&
+ strcasecmp(signature_type, "NONE") != 0) {
+ if (strcasecmp(signature_type, "FINGERPRINTS") == 0) {
+
+ snprintf(tmpsig, MAXPATHLEN, "%s/pkg.txz.sig.XXXXXX",
+ getenv("TMPDIR") ? getenv("TMPDIR") : _PATH_TMP);
+ snprintf(url, MAXPATHLEN, "%s/Latest/pkg.txz.sig",
+ packagesite);
+
+ if ((fd_sig = fetch_to_fd(url, tmpsig)) == -1) {
+ fprintf(stderr, "Signature for pkg not "
+ "available.\n");
+ goto fetchfail;
+ }
+
+ if (verify_signature(fd_pkg, fd_sig) == false)
+ goto cleanup;
+ } else if (strcasecmp(signature_type, "PUBKEY") == 0) {
+
+ snprintf(tmpsig, MAXPATHLEN,
+ "%s/pkg.txz.pubkeysig.XXXXXX",
+ getenv("TMPDIR") ? getenv("TMPDIR") : _PATH_TMP);
+ snprintf(url, MAXPATHLEN, "%s/Latest/pkg.txz.pubkeysig",
+ packagesite);
+
+ if ((fd_sig = fetch_to_fd(url, tmpsig)) == -1) {
+ fprintf(stderr, "Signature for pkg not "
+ "available.\n");
+ goto fetchfail;
+ }
+
+ if (verify_pubsignature(fd_pkg, fd_sig) == false)
+ goto cleanup;
+ } else {
+ warnx("Signature type %s is not supported for "
+ "bootstrapping.", signature_type);
+ goto cleanup;
+ }
+ }
+
+ if ((ret = extract_pkg_static(fd_pkg, pkgstatic, MAXPATHLEN)) == 0)
+ ret = install_pkg_static(pkgstatic, tmppkg, force);
+
+ goto cleanup;
+
+fetchfail:
+ warnx("Error fetching %s: %s", url, fetchLastErrString);
+ fprintf(stderr, "A pre-built version of pkg could not be found for "
+ "your system.\n");
+ fprintf(stderr, "Consider changing PACKAGESITE or installing it from "
+ "ports: 'ports-mgmt/pkg'.\n");
+
+cleanup:
+ if (fd_sig != -1) {
+ close(fd_sig);
+ unlink(tmpsig);
+ }
+
+ if (fd_pkg != -1) {
+ close(fd_pkg);
+ unlink(tmppkg);
+ }
+
+ return (ret);
+}
+
+static const char confirmation_message[] =
+"The package management tool is not yet installed on your system.\n"
+"Do you want to fetch and install it now? [y/N]: ";
+
+static const char non_interactive_message[] =
+"The package management tool is not yet installed on your system.\n"
+"Please set ASSUME_ALWAYS_YES=yes environment variable to be able to bootstrap "
+"in non-interactive (stdin not being a tty)\n";
+
+static int
+pkg_query_yes_no(void)
+{
+ int ret, c;
+
+ c = getchar();
+
+ if (c == 'y' || c == 'Y')
+ ret = 1;
+ else
+ ret = 0;
+
+ while (c != '\n' && c != EOF)
+ c = getchar();
+
+ return (ret);
+}
+
+static int
+bootstrap_pkg_local(const char *pkgpath, bool force)
+{
+ char path[MAXPATHLEN];
+ char pkgstatic[MAXPATHLEN];
+ const char *signature_type;
+ int fd_pkg, fd_sig, ret;
+
+ fd_sig = -1;
+ ret = -1;
+
+ fd_pkg = open(pkgpath, O_RDONLY);
+ if (fd_pkg == -1)
+ err(EXIT_FAILURE, "Unable to open %s", pkgpath);
+
+ if (config_string(SIGNATURE_TYPE, &signature_type) != 0) {
+ warnx("Error looking up SIGNATURE_TYPE");
+ goto cleanup;
+ }
+ if (signature_type != NULL &&
+ strcasecmp(signature_type, "NONE") != 0) {
+ if (strcasecmp(signature_type, "FINGERPRINTS") == 0) {
+
+ snprintf(path, sizeof(path), "%s.sig", pkgpath);
+
+ if ((fd_sig = open(path, O_RDONLY)) == -1) {
+ fprintf(stderr, "Signature for pkg not "
+ "available.\n");
+ goto cleanup;
+ }
+
+ if (verify_signature(fd_pkg, fd_sig) == false)
+ goto cleanup;
+
+ } else if (strcasecmp(signature_type, "PUBKEY") == 0) {
+
+ snprintf(path, sizeof(path), "%s.pubkeysig", pkgpath);
+
+ if ((fd_sig = open(path, O_RDONLY)) == -1) {
+ fprintf(stderr, "Signature for pkg not "
+ "available.\n");
+ goto cleanup;
+ }
+
+ if (verify_pubsignature(fd_pkg, fd_sig) == false)
+ goto cleanup;
+
+ } else {
+ warnx("Signature type %s is not supported for "
+ "bootstrapping.", signature_type);
+ goto cleanup;
+ }
+ }
+
+ if ((ret = extract_pkg_static(fd_pkg, pkgstatic, MAXPATHLEN)) == 0)
+ ret = install_pkg_static(pkgstatic, pkgpath, force);
+
+cleanup:
+ close(fd_pkg);
+ if (fd_sig != -1)
+ close(fd_sig);
+
+ return (ret);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char pkgpath[MAXPATHLEN];
+ const char *pkgarg;
+ bool bootstrap_only, force, yes;
+
+ bootstrap_only = false;
+ force = false;
+ pkgarg = NULL;
+ yes = false;
+
+ snprintf(pkgpath, MAXPATHLEN, "%s/sbin/pkg",
+ getenv("LOCALBASE") ? getenv("LOCALBASE") : _LOCALBASE);
+
+ if (argc > 1 && strcmp(argv[1], "bootstrap") == 0) {
+ bootstrap_only = true;
+ if (argc == 3 && strcmp(argv[2], "-f") == 0)
+ force = true;
+ }
+
+ if ((bootstrap_only && force) || access(pkgpath, X_OK) == -1) {
+ /*
+ * To allow 'pkg -N' to be used as a reliable test for whether
+ * a system is configured to use pkg, don't bootstrap pkg
+ * when that argument is given as argv[1].
+ */
+ if (argv[1] != NULL && strcmp(argv[1], "-N") == 0)
+ errx(EXIT_FAILURE, "pkg is not installed");
+
+ config_init();
+
+ if (argc > 1 && strcmp(argv[1], "add") == 0) {
+ if (argc > 2 && strcmp(argv[2], "-f") == 0) {
+ force = true;
+ pkgarg = argv[3];
+ } else
+ pkgarg = argv[2];
+ if (pkgarg == NULL) {
+ fprintf(stderr, "Path to pkg.txz required\n");
+ exit(EXIT_FAILURE);
+ }
+ if (access(pkgarg, R_OK) == -1) {
+ fprintf(stderr, "No such file: %s\n", pkgarg);
+ exit(EXIT_FAILURE);
+ }
+ if (bootstrap_pkg_local(pkgarg, force) != 0)
+ exit(EXIT_FAILURE);
+ exit(EXIT_SUCCESS);
+ }
+ /*
+ * Do not ask for confirmation if either of stdin or stdout is
+ * not tty. Check the environment to see if user has answer
+ * tucked in there already.
+ */
+ config_bool(ASSUME_ALWAYS_YES, &yes);
+ if (!yes) {
+ if (!isatty(fileno(stdin))) {
+ fprintf(stderr, non_interactive_message);
+ exit(EXIT_FAILURE);
+ }
+
+ printf("%s", confirmation_message);
+ if (pkg_query_yes_no() == 0)
+ exit(EXIT_FAILURE);
+ }
+ if (bootstrap_pkg(force) != 0)
+ exit(EXIT_FAILURE);
+ config_finish();
+
+ if (bootstrap_only)
+ exit(EXIT_SUCCESS);
+ } else if (bootstrap_only) {
+ printf("pkg already bootstrapped at %s\n", pkgpath);
+ exit(EXIT_SUCCESS);
+ }
+
+ execv(pkgpath, argv);
+
+ /* NOT REACHED */
+ return (EXIT_FAILURE);
+}
diff --git a/usr.sbin/pmcannotate/Makefile b/usr.sbin/pmcannotate/Makefile
new file mode 100644
index 0000000..cff1efb
--- /dev/null
+++ b/usr.sbin/pmcannotate/Makefile
@@ -0,0 +1,10 @@
+#
+# $FreeBSD$
+#
+
+PROG= pmcannotate
+MAN= pmcannotate.8
+
+SRCS= pmcannotate.c
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/pmcannotate/Makefile.depend b/usr.sbin/pmcannotate/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/pmcannotate/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/pmcannotate/pmcannotate.8 b/usr.sbin/pmcannotate/pmcannotate.8
new file mode 100644
index 0000000..08967d9
--- /dev/null
+++ b/usr.sbin/pmcannotate/pmcannotate.8
@@ -0,0 +1,109 @@
+.\" Copyright (c) 2008 Nokia Corporation
+.\" All rights reserved.
+.\"
+.\" This software was developed by Attilio Rao for the IPSO project under
+.\" contract to Nokia Corporation.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 November 20, 2008
+.Dt PMCANNOTATE 8
+.Os
+.Sh NAME
+.Nm pmcannotate
+.Nd "sources printout with inlined profiling"
+.Sh SYNOPSIS
+.Nm
+.Op Fl a
+.Op Fl h
+.Op Fl k Ar pathname
+.Op Fl l Ar level
+.Ar pmcout.out binaryobj
+.Sh DESCRIPTION
+The
+.Nm
+utility can produce both C sources or assembly sources of a program with
+a line-by-line based profiling.
+The profiling information is retrieved through a
+.Xr pmcstat 8
+raw output while the program operations are retrieved through the
+.Xr objdump 1
+tool.
+.Pp
+When calling
+.Nm
+the raw output is passed through the
+.Ar pmcout.out
+argument, while the program is passed through the
+.Ar binaryobj
+argument.
+.Pp
+As long as
+.Nm
+relies on
+.Xr objdump 1
+and
+.Xr pmcstat 8
+to work, it will fail if one of them is not available.
+.Sh OPTIONS
+The following options are available:
+.Bl -tag -width indent
+.It Fl a
+Shows the program profiling inlined in the assembly code only.
+No C information involving C sources is provided.
+.It Fl h
+Prints out information about the usage of the tool.
+.It Fl l Ar level
+Changes the lower bound (expressed in percentage) for traced functions
+that will be printed out in the report.
+The default value is 0.5%.
+.It Fl k Ar kerneldir
+Set the pathname of the kernel directory to argument
+.Ar kerneldir .
+This directory specifies where
+.Nm
+should look for the kernel and its modules.
+The default is
+.Pa /boot/kernel .
+.El
+.Sh LIMITATIONS
+As long as
+.Nm
+relies on the
+.Xr objdump 1
+utility to retrieve the C code, the program needs to be compiled with
+debugging options.
+Sometimes, in particular with heavy optimization levels, the
+.Xr objdump 1
+utility embeds the code of inlining functions directly in the callers,
+making an output difficult to read.
+The x86 version reports the sampling from pmcstat collecting the following
+instruction in regard of the interrupted one.
+This means that the samples may be attributed to the line below the one
+of interest.
+.Sh SEE ALSO
+.Xr objdump 1 ,
+.Xr pmcstat 8
+.Sh AUTHORS
+.An Attilio Rao Aq Mt attilio@FreeBSD.org
diff --git a/usr.sbin/pmcannotate/pmcannotate.c b/usr.sbin/pmcannotate/pmcannotate.c
new file mode 100644
index 0000000..b22d0dc
--- /dev/null
+++ b/usr.sbin/pmcannotate/pmcannotate.c
@@ -0,0 +1,803 @@
+/*-
+ * Copyright (c) 2008 Nokia Corporation
+ * All rights reserved.
+ *
+ * This software was developed by Attilio Rao for the IPSO project under
+ * contract to Nokia Corporation.
+ *
+ * Redistribution and use in source and binary forms, 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/queue.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <unistd.h>
+
+/* NB: Make sure FNBUFF is as large as LNBUFF, otherwise it could overflow */
+#define FNBUFF 512
+#define LNBUFF 512
+
+#define TMPPATH "/tmp/pmcannotate.XXXXXX"
+
+#define FATAL(ptr, x ...) do { \
+ fqueue_deleteall(); \
+ general_deleteall(); \
+ if ((ptr) != NULL) \
+ perror(ptr); \
+ fprintf(stderr, ##x); \
+ remove(tbfl); \
+ remove(tofl); \
+ exit(EXIT_FAILURE); \
+} while (0)
+
+#define PERCSAMP(x) ((x) * 100 / totalsamples)
+
+struct entry {
+ TAILQ_ENTRY(entry) en_iter;
+ char *en_name;
+ uintptr_t en_pc;
+ uintptr_t en_ostart;
+ uintptr_t en_oend;
+ u_int en_nsamples;
+};
+
+struct aggent {
+ TAILQ_ENTRY(aggent) ag_fiter;
+ long ag_offset;
+ uintptr_t ag_ostart;
+ uintptr_t ag_oend;
+ char *ag_name;
+ u_int ag_nsamples;
+};
+
+static struct aggent *agg_create(const char *name, u_int nsamples,
+ uintptr_t start, uintptr_t end);
+static void agg_destroy(struct aggent *agg) __unused;
+static void asmparse(FILE *fp);
+static int cparse(FILE *fp);
+static void entry_acqref(struct entry *entry);
+static struct entry *entry_create(const char *name, uintptr_t pc,
+ uintptr_t start, uintptr_t end);
+static void entry_destroy(struct entry *entry) __unused;
+static void fqueue_compact(float th);
+static void fqueue_deleteall(void);
+static struct aggent *fqueue_findent_by_name(const char *name);
+static int fqueue_getall(const char *bin, char *temp, int asmf);
+static int fqueue_insertent(struct entry *entry);
+static int fqueue_insertgen(void);
+static void general_deleteall(void);
+static struct entry *general_findent(uintptr_t pc);
+static void general_insertent(struct entry *entry);
+static void general_printasm(FILE *fp, struct aggent *agg);
+static int general_printc(FILE *fp, struct aggent *agg);
+static int printblock(FILE *fp, struct aggent *agg);
+static void usage(const char *progname) __dead2;
+
+static TAILQ_HEAD(, entry) mainlst = TAILQ_HEAD_INITIALIZER(mainlst);
+static TAILQ_HEAD(, aggent) fqueue = TAILQ_HEAD_INITIALIZER(fqueue);
+
+/*
+ * Use a float value in order to automatically promote operations
+ * to return a float value rather than use casts.
+ */
+static float totalsamples;
+
+/*
+ * Identifies a string cointaining objdump's assembly printout.
+ */
+static inline int
+isasminline(const char *str)
+{
+ void *ptr;
+ int nbytes;
+
+ if (sscanf(str, " %p%n", &ptr, &nbytes) != 1)
+ return (0);
+ if (str[nbytes] != ':' || isspace(str[nbytes + 1]) == 0)
+ return (0);
+ return (1);
+}
+
+/*
+ * Identifies a string containing objdump's assembly printout
+ * for a new function.
+ */
+static inline int
+newfunction(const char *str)
+{
+ char fname[FNBUFF];
+ void *ptr;
+ int nbytes;
+
+ if (isspace(str[0]))
+ return (0);
+ if (sscanf(str, "%p <%[^>:]>:%n", &ptr, fname, &nbytes) != 2)
+ return (0);
+ return (nbytes);
+}
+
+/*
+ * Create a new first-level aggregation object for a specified
+ * function.
+ */
+static struct aggent *
+agg_create(const char *name, u_int nsamples, uintptr_t start, uintptr_t end)
+{
+ struct aggent *agg;
+
+ agg = calloc(1, sizeof(struct aggent));
+ if (agg == NULL)
+ return (NULL);
+ agg->ag_name = strdup(name);
+ if (agg->ag_name == NULL) {
+ free(agg);
+ return (NULL);
+ }
+ agg->ag_nsamples = nsamples;
+ agg->ag_ostart = start;
+ agg->ag_oend = end;
+ return (agg);
+}
+
+/*
+ * Destroy a first-level aggregation object for a specified
+ * function.
+ */
+static void
+agg_destroy(struct aggent *agg)
+{
+
+ free(agg->ag_name);
+ free(agg);
+}
+
+/*
+ * Analyze the "objdump -d" output, locate functions and start
+ * printing out the assembly functions content.
+ * We do not use newfunction() because we actually need the
+ * function name in available form, but the heurstic used is
+ * the same.
+ */
+static void
+asmparse(FILE *fp)
+{
+ char buffer[LNBUFF], fname[FNBUFF];
+ struct aggent *agg;
+ void *ptr;
+
+ while (fgets(buffer, LNBUFF, fp) != NULL) {
+ if (isspace(buffer[0]))
+ continue;
+ if (sscanf(buffer, "%p <%[^>:]>:", &ptr, fname) != 2)
+ continue;
+ agg = fqueue_findent_by_name(fname);
+ if (agg == NULL)
+ continue;
+ agg->ag_offset = ftell(fp);
+ }
+
+ TAILQ_FOREACH(agg, &fqueue, ag_fiter) {
+ if (fseek(fp, agg->ag_offset, SEEK_SET) == -1)
+ return;
+ printf("Profile trace for function: %s() [%.2f%%]\n",
+ agg->ag_name, PERCSAMP(agg->ag_nsamples));
+ general_printasm(fp, agg);
+ printf("\n");
+ }
+}
+
+/*
+ * Analyze the "objdump -S" output, locate functions and start
+ * printing out the C functions content.
+ * We do not use newfunction() because we actually need the
+ * function name in available form, but the heurstic used is
+ * the same.
+ * In order to maintain the printout sorted, on the first pass it
+ * simply stores the file offsets in order to fastly moved later
+ * (when the file is hot-cached also) when the real printout will
+ * happen.
+ */
+static int
+cparse(FILE *fp)
+{
+ char buffer[LNBUFF], fname[FNBUFF];
+ struct aggent *agg;
+ void *ptr;
+
+ while (fgets(buffer, LNBUFF, fp) != NULL) {
+ if (isspace(buffer[0]))
+ continue;
+ if (sscanf(buffer, "%p <%[^>:]>:", &ptr, fname) != 2)
+ continue;
+ agg = fqueue_findent_by_name(fname);
+ if (agg == NULL)
+ continue;
+ agg->ag_offset = ftell(fp);
+ }
+
+ TAILQ_FOREACH(agg, &fqueue, ag_fiter) {
+ if (fseek(fp, agg->ag_offset, SEEK_SET) == -1)
+ return (-1);
+ printf("Profile trace for function: %s() [%.2f%%]\n",
+ agg->ag_name, PERCSAMP(agg->ag_nsamples));
+ if (general_printc(fp, agg) == -1)
+ return (-1);
+ printf("\n");
+ }
+ return (0);
+}
+
+/*
+ * Bump the number of samples for any raw entry.
+ */
+static void
+entry_acqref(struct entry *entry)
+{
+
+ entry->en_nsamples++;
+}
+
+/*
+ * Create a new raw entry object for a specified function.
+ */
+static struct entry *
+entry_create(const char *name, uintptr_t pc, uintptr_t start, uintptr_t end)
+{
+ struct entry *obj;
+
+ obj = calloc(1, sizeof(struct entry));
+ if (obj == NULL)
+ return (NULL);
+ obj->en_name = strdup(name);
+ if (obj->en_name == NULL) {
+ free(obj);
+ return (NULL);
+ }
+ obj->en_pc = pc;
+ obj->en_ostart = start;
+ obj->en_oend = end;
+ obj->en_nsamples = 1;
+ return (obj);
+}
+
+/*
+ * Destroy a raw entry object for a specified function.
+ */
+static void
+entry_destroy(struct entry *entry)
+{
+
+ free(entry->en_name);
+ free(entry);
+}
+
+/*
+ * Specify a lower bound in percentage and drop from the
+ * first-level aggregation queue all the objects with a
+ * smaller impact.
+ */
+static void
+fqueue_compact(float th)
+{
+ u_int thi;
+ struct aggent *agg, *tmpagg;
+
+ if (totalsamples == 0)
+ return;
+
+ /* Revert the percentage calculation. */
+ thi = th * totalsamples / 100;
+ TAILQ_FOREACH_SAFE(agg, &fqueue, ag_fiter, tmpagg)
+ if (agg->ag_nsamples < thi)
+ TAILQ_REMOVE(&fqueue, agg, ag_fiter);
+}
+
+/*
+ * Flush the first-level aggregates queue.
+ */
+static void
+fqueue_deleteall(void)
+{
+ struct aggent *agg;
+
+ while (TAILQ_EMPTY(&fqueue) == 0) {
+ agg = TAILQ_FIRST(&fqueue);
+ TAILQ_REMOVE(&fqueue, agg, ag_fiter);
+ }
+}
+
+/*
+ * Insert a raw entry into the aggregations queue.
+ * If the respective first-level aggregation object
+ * does not exist create it and maintain it sorted
+ * in respect of the number of samples.
+ */
+static int
+fqueue_insertent(struct entry *entry)
+{
+ struct aggent *obj, *tmp;
+ int found;
+
+ found = 0;
+ TAILQ_FOREACH(obj, &fqueue, ag_fiter)
+ if (!strcmp(obj->ag_name, entry->en_name)) {
+ found = 1;
+ obj->ag_nsamples += entry->en_nsamples;
+ break;
+ }
+
+ /*
+ * If the first-level aggregation object already exists,
+ * just aggregate the samples and, if needed, resort
+ * it.
+ */
+ if (found) {
+ TAILQ_REMOVE(&fqueue, obj, ag_fiter);
+ found = 0;
+ TAILQ_FOREACH(tmp, &fqueue, ag_fiter)
+ if (obj->ag_nsamples > tmp->ag_nsamples) {
+ found = 1;
+ break;
+ }
+ if (found)
+ TAILQ_INSERT_BEFORE(tmp, obj, ag_fiter);
+ else
+ TAILQ_INSERT_TAIL(&fqueue, obj, ag_fiter);
+ return (0);
+ }
+
+ /*
+ * If the first-level aggregation object does not
+ * exist, create it and put in the sorted queue.
+ * If this is the first object, we need to set the
+ * head of the queue.
+ */
+ obj = agg_create(entry->en_name, entry->en_nsamples, entry->en_ostart,
+ entry->en_oend);
+ if (obj == NULL)
+ return (-1);
+ if (TAILQ_EMPTY(&fqueue) != 0) {
+ TAILQ_INSERT_HEAD(&fqueue, obj, ag_fiter);
+ return (0);
+ }
+ TAILQ_FOREACH(tmp, &fqueue, ag_fiter)
+ if (obj->ag_nsamples > tmp->ag_nsamples) {
+ found = 1;
+ break;
+ }
+ if (found)
+ TAILQ_INSERT_BEFORE(tmp, obj, ag_fiter);
+ else
+ TAILQ_INSERT_TAIL(&fqueue, obj, ag_fiter);
+ return (0);
+}
+
+/*
+ * Lookup a first-level aggregation object by name.
+ */
+static struct aggent *
+fqueue_findent_by_name(const char *name)
+{
+ struct aggent *obj;
+
+ TAILQ_FOREACH(obj, &fqueue, ag_fiter)
+ if (!strcmp(obj->ag_name, name))
+ return (obj);
+ return (NULL);
+}
+
+/*
+ * Return the number of object in the first-level aggregations queue.
+ */
+static int
+fqueue_getall(const char *bin, char *temp, int asmf)
+{
+ char tmpf[MAXPATHLEN * 2 + 50];
+ struct aggent *agg;
+ uintptr_t start, end;
+
+ if (mkstemp(temp) == -1)
+ return (-1);
+ TAILQ_FOREACH(agg, &fqueue, ag_fiter) {
+ bzero(tmpf, sizeof(tmpf));
+ start = agg->ag_ostart;
+ end = agg->ag_oend;
+
+ /*
+ * Fix-up the end address in order to show it in the objdump's
+ * trace.
+ */
+ end++;
+ if (asmf)
+ snprintf(tmpf, sizeof(tmpf),
+ "objdump --start-address=%p "
+ "--stop-address=%p -d %s >> %s", (void *)start,
+ (void *)end, bin, temp);
+ else
+ snprintf(tmpf, sizeof(tmpf),
+ "objdump --start-address=%p "
+ "--stop-address=%p -S %s >> %s", (void *)start,
+ (void *)end, bin, temp);
+ if (system(tmpf) != 0)
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * Insert all the raw entries present in the general queue
+ * into the first-level aggregations queue.
+ */
+static int
+fqueue_insertgen(void)
+{
+ struct entry *obj;
+
+ TAILQ_FOREACH(obj, &mainlst, en_iter)
+ if (fqueue_insertent(obj) == -1)
+ return (-1);
+ return (0);
+}
+
+/*
+ * Flush the raw entries general queue.
+ */
+static void
+general_deleteall(void)
+{
+ struct entry *obj;
+
+ while (TAILQ_EMPTY(&mainlst) == 0) {
+ obj = TAILQ_FIRST(&mainlst);
+ TAILQ_REMOVE(&mainlst, obj, en_iter);
+ }
+}
+
+/*
+ * Lookup a raw entry by the PC.
+ */
+static struct entry *
+general_findent(uintptr_t pc)
+{
+ struct entry *obj;
+
+ TAILQ_FOREACH(obj, &mainlst, en_iter)
+ if (obj->en_pc == pc)
+ return (obj);
+ return (NULL);
+}
+
+/*
+ * Insert a new raw entry in the general queue.
+ */
+static void
+general_insertent(struct entry *entry)
+{
+
+ TAILQ_INSERT_TAIL(&mainlst, entry, en_iter);
+}
+
+/*
+ * Printout the body of an "objdump -d" assembly function.
+ * It does simply stops when a new function is encountered,
+ * bringing back the file position in order to not mess up
+ * subsequent analysis.
+ * C lines and others not recognized are simply skipped.
+ */
+static void
+general_printasm(FILE *fp, struct aggent *agg)
+{
+ char buffer[LNBUFF];
+ struct entry *obj;
+ int nbytes;
+ void *ptr;
+
+ while (fgets(buffer, LNBUFF, fp) != NULL) {
+ if ((nbytes = newfunction(buffer)) != 0) {
+ fseek(fp, nbytes * -1, SEEK_CUR);
+ break;
+ }
+ if (!isasminline(buffer))
+ continue;
+ if (sscanf(buffer, " %p:", &ptr) != 1)
+ continue;
+ obj = general_findent((uintptr_t)ptr);
+ if (obj == NULL)
+ printf("\t| %s", buffer);
+ else
+ printf("%.2f%%\t| %s",
+ (float)obj->en_nsamples * 100 / agg->ag_nsamples,
+ buffer);
+ }
+}
+
+/*
+ * Printout the body of an "objdump -S" function.
+ * It does simply stops when a new function is encountered,
+ * bringing back the file position in order to not mess up
+ * subsequent analysis.
+ * It expect from the starting to the end to find, always, valid blocks
+ * (see below for an explanation of the "block" concept).
+ */
+static int
+general_printc(FILE *fp, struct aggent *agg)
+{
+ char buffer[LNBUFF];
+
+ while (fgets(buffer, LNBUFF, fp) != NULL) {
+ fseek(fp, strlen(buffer) * -1, SEEK_CUR);
+ if (newfunction(buffer) != 0)
+ break;
+ if (printblock(fp, agg) == -1)
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * Printout a single block inside an "objdump -S" function.
+ * The block is composed of a first part in C and subsequent translation
+ * in assembly.
+ * This code also operates a second-level aggregation packing together
+ * samples relative to PCs into a (lower bottom) block with their
+ * C (higher half) counterpart.
+ */
+static int
+printblock(FILE *fp, struct aggent *agg)
+{
+ char buffer[LNBUFF];
+ long lstart;
+ struct entry *obj;
+ u_int tnsamples;
+ int done, nbytes, sentinel;
+ void *ptr;
+
+ /*
+ * We expect the first thing of the block is C code, so simply give
+ * up if asm line is found.
+ */
+ lstart = ftell(fp);
+ sentinel = 0;
+ for (;;) {
+ if (fgets(buffer, LNBUFF, fp) == NULL)
+ return (0);
+ if (isasminline(buffer) != 0)
+ break;
+ sentinel = 1;
+ nbytes = newfunction(buffer);
+ if (nbytes != 0) {
+ if (fseek(fp, nbytes * -1, SEEK_CUR) == -1)
+ return (-1);
+ return (0);
+ }
+ }
+
+ /*
+ * If the sentinel is not set, it means it did not match any
+ * "high half" for this code so simply give up.
+ * Operates the second-level aggregation.
+ */
+ tnsamples = 0;
+ do {
+ if (sentinel == 0)
+ return (-1);
+ if (sscanf(buffer, " %p:", &ptr) != 1)
+ return (-1);
+ obj = general_findent((uintptr_t)ptr);
+ if (obj != NULL)
+ tnsamples += obj->en_nsamples;
+ } while (fgets(buffer, LNBUFF, fp) != NULL && isasminline(buffer) != 0);
+
+ /* Rewind to the start of the block in order to start the printout. */
+ if (fseek(fp, lstart, SEEK_SET) == -1)
+ return (-1);
+
+ /* Again the high half of the block rappresenting the C part. */
+ done = 0;
+ while (fgets(buffer, LNBUFF, fp) != NULL && isasminline(buffer) == 0) {
+ if (tnsamples == 0 || done != 0)
+ printf("\t| %s", buffer);
+ else {
+ done = 1;
+ printf("%.2f%%\t| %s",
+ (float)tnsamples * 100 / agg->ag_nsamples, buffer);
+ }
+ }
+
+ /*
+ * Again the low half of the block rappresenting the asm
+ * translation part.
+ */
+ for (;;) {
+ if (fgets(buffer, LNBUFF, fp) == NULL)
+ return (0);
+ if (isasminline(buffer) == 0)
+ break;
+ nbytes = newfunction(buffer);
+ if (nbytes != 0) {
+ if (fseek(fp, nbytes * -1, SEEK_CUR) == -1)
+ return (-1);
+ return (0);
+ }
+ }
+ if (fseek(fp, strlen(buffer) * -1, SEEK_CUR) == -1)
+ return (-1);
+ return (0);
+}
+
+/*
+ * Helper printout functions.
+ */
+static void
+usage(const char *progname)
+{
+
+ fprintf(stderr,
+ "usage: %s [-a] [-h] [-k kfile] [-l lb] pmcraw.out binary\n",
+ progname);
+ exit(EXIT_SUCCESS);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char buffer[LNBUFF], fname[FNBUFF], tbfl[] = TMPPATH, tofl[] = TMPPATH;
+ char tmpf[MAXPATHLEN * 2 + 50];
+ float limit;
+ char *bin, *exec, *kfile, *ofile;
+ struct entry *obj;
+ FILE *gfp, *bfp;
+ void *ptr, *hstart, *hend;
+ uintptr_t tmppc, ostart, oend;
+ int cget, asmsrc;
+
+ exec = argv[0];
+ ofile = NULL;
+ bin = NULL;
+ kfile = NULL;
+ asmsrc = 0;
+ limit = 0.5;
+ while ((cget = getopt(argc, argv, "ahl:k:")) != -1)
+ switch(cget) {
+ case 'a':
+ asmsrc = 1;
+ break;
+ case 'k':
+ kfile = optarg;
+ break;
+ case 'l':
+ limit = (float)atof(optarg);
+ break;
+ case 'h':
+ case '?':
+ default:
+ usage(exec);
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 2)
+ usage(exec);
+ ofile = argv[0];
+ bin = argv[1];
+
+ if (access(bin, R_OK | F_OK) == -1)
+ FATAL(exec, "%s: Impossible to locate the binary file\n",
+ exec);
+ if (access(ofile, R_OK | F_OK) == -1)
+ FATAL(exec, "%s: Impossible to locate the pmcstat file\n",
+ exec);
+ if (kfile != NULL && access(kfile, R_OK | F_OK) == -1)
+ FATAL(exec, "%s: Impossible to locate the kernel file\n",
+ exec);
+
+ bzero(tmpf, sizeof(tmpf));
+ if (mkstemp(tofl) == -1)
+ FATAL(exec, "%s: Impossible to create the tmp file\n",
+ exec);
+ if (kfile != NULL)
+ snprintf(tmpf, sizeof(tmpf), "pmcstat -k %s -R %s -m %s",
+ kfile, ofile, tofl);
+ else
+ snprintf(tmpf, sizeof(tmpf), "pmcstat -R %s -m %s", ofile,
+ tofl);
+ if (system(tmpf) != 0)
+ FATAL(exec, "%s: Impossible to create the tmp file\n",
+ exec);
+
+ gfp = fopen(tofl, "r");
+ if (gfp == NULL)
+ FATAL(exec, "%s: Impossible to open the map file\n",
+ exec);
+
+ /*
+ * Make the collection of raw entries from a pmcstat mapped file.
+ * The heuristic here wants strings in the form:
+ * "addr funcname startfaddr endfaddr".
+ */
+ while (fgets(buffer, LNBUFF, gfp) != NULL) {
+ if (isspace(buffer[0]))
+ continue;
+ if (sscanf(buffer, "%p %s %p %p\n", &ptr, fname,
+ &hstart, &hend) != 4)
+ FATAL(NULL,
+ "%s: Invalid scan of function in the map file\n",
+ exec);
+ ostart = (uintptr_t)hstart;
+ oend = (uintptr_t)hend;
+ tmppc = (uintptr_t)ptr;
+ totalsamples++;
+ obj = general_findent(tmppc);
+ if (obj != NULL) {
+ entry_acqref(obj);
+ continue;
+ }
+ obj = entry_create(fname, tmppc, ostart, oend);
+ if (obj == NULL)
+ FATAL(exec,
+ "%s: Impossible to create a new object\n", exec);
+ general_insertent(obj);
+ }
+ if (fclose(gfp) == EOF)
+ FATAL(exec, "%s: Impossible to close the filedesc\n",
+ exec);
+ if (remove(tofl) == -1)
+ FATAL(exec, "%s: Impossible to remove the tmpfile\n",
+ exec);
+
+ /*
+ * Remove the loose end objects and feed the first-level aggregation
+ * queue.
+ */
+ if (fqueue_insertgen() == -1)
+ FATAL(exec, "%s: Impossible to generate an analysis\n",
+ exec);
+ fqueue_compact(limit);
+ if (fqueue_getall(bin, tbfl, asmsrc) == -1)
+ FATAL(exec, "%s: Impossible to create the tmp file\n",
+ exec);
+
+ bfp = fopen(tbfl, "r");
+ if (bfp == NULL)
+ FATAL(exec, "%s: Impossible to open the binary file\n",
+ exec);
+
+ if (asmsrc != 0)
+ asmparse(bfp);
+ else if (cparse(bfp) == -1)
+ FATAL(NULL, "%s: Invalid format for the C file\n", exec);
+ if (fclose(bfp) == EOF)
+ FATAL(exec, "%s: Impossible to close the filedesc\n",
+ exec);
+ if (remove(tbfl) == -1)
+ FATAL(exec, "%s: Impossible to remove the tmpfile\n",
+ exec);
+ return (0);
+}
diff --git a/usr.sbin/pmccontrol/Makefile b/usr.sbin/pmccontrol/Makefile
new file mode 100644
index 0000000..1940b1f
--- /dev/null
+++ b/usr.sbin/pmccontrol/Makefile
@@ -0,0 +1,12 @@
+#
+# $FreeBSD$
+#
+
+PROG= pmccontrol
+MAN= pmccontrol.8
+
+LIBADD+= pmc
+
+SRCS= pmccontrol.c
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/pmccontrol/Makefile.depend b/usr.sbin/pmccontrol/Makefile.depend
new file mode 100644
index 0000000..d29a358
--- /dev/null
+++ b/usr.sbin/pmccontrol/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libpmc \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/pmccontrol/pmccontrol.8 b/usr.sbin/pmccontrol/pmccontrol.8
new file mode 100644
index 0000000..a5b6baa
--- /dev/null
+++ b/usr.sbin/pmccontrol/pmccontrol.8
@@ -0,0 +1,127 @@
+.\" Copyright (c) 2003,2008 Joseph Koshy. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 Joseph Koshy ``as is'' and
+.\" any express or implied warranties, including, but not limited to, the
+.\" implied warranties of merchantability and fitness for a particular purpose
+.\" are disclaimed. in no event shall Joseph Koshy be liable
+.\" for any direct, indirect, incidental, special, exemplary, or consequential
+.\" damages (including, but not limited to, procurement of substitute goods
+.\" or services; loss of use, data, or profits; or business interruption)
+.\" however caused and on any theory of liability, whether in contract, strict
+.\" liability, or tort (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 November 9, 2008
+.Dt PMCCONTROL 8
+.Os
+.Sh NAME
+.Nm pmccontrol
+.Nd "control hardware performance monitoring counters"
+.Sh SYNOPSIS
+.Nm
+.Oo Fl c Ar cpu | Fl d Ar pmc | Fl e Ar pmc Oc ...
+.Nm
+.Fl l
+.Nm
+.Fl L
+.Nm
+.Fl s
+.Sh DESCRIPTION
+The
+.Nm
+utility controls the operation of the system's hardware performance
+monitoring counters.
+.Sh OPTIONS
+The
+.Nm
+utility processes options in command line order, so later options modify
+the effect of earlier ones.
+The following options are available:
+.Bl -tag -width indent
+.It Fl c Ar cpu
+Subsequent enable and disable options affect the CPU
+denoted by argument
+.Ar cpu .
+The argument
+.Ar cpu
+is a number denoting a CPU in the system, or
+.Dq Li * ,
+denoting all unhalted CPUs in the system.
+.It Fl d Ar pmc
+Disable PMC number
+.Ar pmc
+on the CPU specified by
+.Fl c ,
+preventing it from being used till subsequently re-enabled.
+The argument
+.Ar pmc
+is a number denoting a specific PMC, or
+.Dq Li *
+denoting all the PMCs on the specified CPU.
+.Pp
+Only idle PMCs may be disabled.
+.\" XXX this probably needs to be fixed.
+.It Fl e Ar pmc
+Enable PMC number
+.Ar pmc ,
+on the CPU specified by
+.Fl c ,
+allowing it to be used in the future.
+The argument
+.Ar pmc
+is a number denoting a specific PMC, or
+.Dq Li *
+denoting all the PMCs on the specified CPU.
+If PMC
+.Ar pmc
+is already enabled, this option has no effect.
+.It Fl l
+List available hardware performance counters and their current
+disposition.
+.It Fl L
+List available hardware performance counter classes and their
+supported event names.
+.It Fl s
+Print driver statistics maintained by
+.Xr hwpmc 4 .
+.El
+.Sh EXAMPLES
+To disable all PMCs on all CPUs, use the command:
+.Dl "pmccontrol -d*"
+.Pp
+To enable all PMCs on all CPUs, use:
+.Dl "pmccontrol -e*"
+.Pp
+To disable PMCs 0 and 1 on CPU 2, use:
+.Dl "pmccontrol -c2 -d0 -d1"
+.Pp
+To disable PMC 0 of CPU 0 only, and enable all other PMCS on all other
+CPUs, use:
+.Dl "pmccontrol -c* -e* -c0 -d0"
+.Sh DIAGNOSTICS
+.Ex -std
+.Sh SEE ALSO
+.Xr pmc 3 ,
+.Xr pmclog 3 ,
+.Xr hwpmc 4 ,
+.Xr pmcstat 8 ,
+.Xr sysctl 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 6.0 .
+.Sh AUTHORS
+.An Joseph Koshy Aq Mt jkoshy@FreeBSD.org
diff --git a/usr.sbin/pmccontrol/pmccontrol.c b/usr.sbin/pmccontrol/pmccontrol.c
new file mode 100644
index 0000000..c1e2acd
--- /dev/null
+++ b/usr.sbin/pmccontrol/pmccontrol.c
@@ -0,0 +1,487 @@
+/*-
+ * Copyright (c) 2003,2004 Joseph Koshy
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/queue.h>
+#include <sys/cpuset.h>
+#include <sys/sysctl.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <pmc.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+/* Compile time defaults */
+
+#define PMCC_PRINT_USAGE 0
+#define PMCC_PRINT_EVENTS 1
+#define PMCC_LIST_STATE 2
+#define PMCC_ENABLE_DISABLE 3
+#define PMCC_SHOW_STATISTICS 4
+
+#define PMCC_CPU_ALL -1
+#define PMCC_CPU_WILDCARD '*'
+
+#define PMCC_PMC_ALL -1
+#define PMCC_PMC_WILDCARD '*'
+
+#define PMCC_OP_IGNORE 0
+#define PMCC_OP_DISABLE 1
+#define PMCC_OP_ENABLE 2
+
+#define PMCC_PROGRAM_NAME "pmccontrol"
+
+static STAILQ_HEAD(pmcc_op_list, pmcc_op) head = STAILQ_HEAD_INITIALIZER(head);
+
+struct pmcc_op {
+ char op_cpu;
+ char op_pmc;
+ char op_op;
+ STAILQ_ENTRY(pmcc_op) op_next;
+};
+
+/* Function Prototypes */
+#if DEBUG
+static void pmcc_init_debug(void);
+#endif
+
+static int pmcc_do_list_state(void);
+static int pmcc_do_enable_disable(struct pmcc_op_list *);
+static int pmcc_do_list_events(void);
+
+/* Globals */
+
+static char usage_message[] =
+ "Usage:\n"
+ " " PMCC_PROGRAM_NAME " -L\n"
+ " " PMCC_PROGRAM_NAME " -l\n"
+ " " PMCC_PROGRAM_NAME " -s\n"
+ " " PMCC_PROGRAM_NAME " [-e pmc | -d pmc | -c cpu] ...";
+
+#if DEBUG
+static FILE *debug_stream = NULL;
+#endif
+
+#if DEBUG
+#define DEBUG_MSG(...) \
+ (void) fprintf(debug_stream, "[pmccontrol] " __VA_ARGS__);
+#else
+#define DEBUG_MSG(m) /* */
+#endif /* !DEBUG */
+
+#if DEBUG
+/* log debug messages to a separate file */
+static void
+pmcc_init_debug(void)
+{
+ char *fn;
+
+ fn = getenv("PMCCONTROL_DEBUG");
+ if (fn != NULL)
+ {
+ debug_stream = fopen(fn, "w");
+ if (debug_stream == NULL)
+ debug_stream = stderr;
+ } else
+ debug_stream = stderr;
+}
+#endif
+
+static int
+pmcc_do_enable_disable(struct pmcc_op_list *op_list)
+{
+ int c, error, i, j, ncpu, npmc, t;
+ struct pmcc_op *np;
+ unsigned char *map;
+ unsigned char op;
+ int cpu, pmc;
+
+ if ((ncpu = pmc_ncpu()) < 0)
+ err(EX_OSERR, "Unable to determine the number of cpus");
+
+ /* Determine the maximum number of PMCs in any CPU. */
+ npmc = 0;
+ for (c = 0; c < ncpu; c++) {
+ if ((t = pmc_npmc(c)) < 0)
+ err(EX_OSERR,
+ "Unable to determine the number of PMCs in CPU %d",
+ c);
+ npmc = t > npmc ? t : npmc;
+ }
+
+ if (npmc == 0)
+ errx(EX_CONFIG, "No PMCs found");
+
+ if ((map = calloc(npmc, ncpu)) == NULL)
+ err(EX_SOFTWARE, "Out of memory");
+
+ error = 0;
+ STAILQ_FOREACH(np, op_list, op_next) {
+
+ cpu = np->op_cpu;
+ pmc = np->op_pmc;
+ op = np->op_op;
+
+ if (cpu >= ncpu)
+ errx(EX_DATAERR, "CPU id too large: \"%d\"", cpu);
+
+ if (pmc >= npmc)
+ errx(EX_DATAERR, "PMC id too large: \"%d\"", pmc);
+
+#define MARKMAP(M,C,P,V) do { \
+ *((M) + (C)*npmc + (P)) = (V); \
+} while (0)
+
+#define SET_PMCS(C,P,V) do { \
+ if ((P) == PMCC_PMC_ALL) { \
+ for (j = 0; j < npmc; j++) \
+ MARKMAP(map, (C), j, (V)); \
+ } else \
+ MARKMAP(map, (C), (P), (V)); \
+} while (0)
+
+#define MAP(M,C,P) (*((M) + (C)*npmc + (P)))
+
+ if (cpu == PMCC_CPU_ALL)
+ for (i = 0; i < ncpu; i++) {
+ SET_PMCS(i, pmc, op);
+ }
+ else
+ SET_PMCS(cpu, pmc, op);
+ }
+
+ /* Configure PMCS */
+ for (i = 0; i < ncpu; i++)
+ for (j = 0; j < npmc; j++) {
+ unsigned char b;
+
+ b = MAP(map, i, j);
+
+ error = 0;
+
+ if (b == PMCC_OP_ENABLE)
+ error = pmc_enable(i, j);
+ else if (b == PMCC_OP_DISABLE)
+ error = pmc_disable(i, j);
+
+ if (error < 0)
+ err(EX_OSERR, "%s of PMC %d on CPU %d failed",
+ b == PMCC_OP_ENABLE ? "Enable" : "Disable",
+ j, i);
+ }
+
+ return error;
+}
+
+static int
+pmcc_do_list_state(void)
+{
+ cpuset_t logical_cpus_mask;
+ long cpusetsize;
+ size_t setsize;
+ int c, cpu, n, npmc, ncpu;
+ struct pmc_info *pd;
+ struct pmc_pmcinfo *pi;
+ const struct pmc_cpuinfo *pc;
+
+ if (pmc_cpuinfo(&pc) != 0)
+ err(EX_OSERR, "Unable to determine CPU information");
+
+ printf("%d %s CPUs present, with %d PMCs per CPU\n", pc->pm_ncpu,
+ pmc_name_of_cputype(pc->pm_cputype),
+ pc->pm_npmc);
+
+ /* Determine the set of logical CPUs. */
+ cpusetsize = sysconf(_SC_CPUSET_SIZE);
+ if (cpusetsize == -1 || (u_long)cpusetsize > sizeof(cpuset_t))
+ err(EX_OSERR, "Cannot determine which CPUs are logical");
+ CPU_ZERO(&logical_cpus_mask);
+ setsize = (size_t)cpusetsize;
+ if (sysctlbyname("machdep.logical_cpus_mask", &logical_cpus_mask,
+ &setsize, NULL, 0) < 0)
+ CPU_ZERO(&logical_cpus_mask);
+
+ ncpu = pc->pm_ncpu;
+
+ for (c = cpu = 0; cpu < ncpu; cpu++) {
+#if defined(__i386__) || defined(__amd64__)
+ if (pc->pm_cputype == PMC_CPU_INTEL_PIV &&
+ CPU_ISSET(cpu, &logical_cpus_mask))
+ continue; /* skip P4-style 'logical' cpus */
+#endif
+ if (pmc_pmcinfo(cpu, &pi) < 0) {
+ if (errno == ENXIO)
+ continue;
+ err(EX_OSERR, "Unable to get PMC status for CPU %d",
+ cpu);
+ }
+
+ printf("#CPU %d:\n", c++);
+ npmc = pmc_npmc(cpu);
+ printf("#N NAME CLASS STATE ROW-DISP\n");
+
+ for (n = 0; n < npmc; n++) {
+ pd = &pi->pm_pmcs[n];
+
+ printf(" %-2d %-16s %-6s %-8s %-10s",
+ n,
+ pd->pm_name,
+ pmc_name_of_class(pd->pm_class),
+ pd->pm_enabled ? "ENABLED" : "DISABLED",
+ pmc_name_of_disposition(pd->pm_rowdisp));
+
+ if (pd->pm_ownerpid != -1) {
+ printf(" (pid %d)", pd->pm_ownerpid);
+ printf(" %-32s",
+ pmc_name_of_event(pd->pm_event));
+ if (PMC_IS_SAMPLING_MODE(pd->pm_mode))
+ printf(" (reload count %jd)",
+ pd->pm_reloadcount);
+ }
+ printf("\n");
+ }
+ free(pi);
+ }
+ return 0;
+}
+
+static int
+pmcc_do_list_events(void)
+{
+ enum pmc_class c;
+ unsigned int i, j, nevents;
+ const char **eventnamelist;
+ const struct pmc_cpuinfo *ci;
+
+ if (pmc_cpuinfo(&ci) != 0)
+ err(EX_OSERR, "Unable to determine CPU information");
+
+ eventnamelist = NULL;
+
+ for (i = 0; i < ci->pm_nclass; i++) {
+ c = ci->pm_classes[i].pm_class;
+
+ printf("%s\n", pmc_name_of_class(c));
+ if (pmc_event_names_of_class(c, &eventnamelist, &nevents) < 0)
+ err(EX_OSERR,
+"ERROR: Cannot find information for event class \"%s\"",
+ pmc_name_of_class(c));
+
+ for (j = 0; j < nevents; j++)
+ printf("\t%s\n", eventnamelist[j]);
+
+ free(eventnamelist);
+ }
+ return 0;
+}
+
+static int
+pmcc_show_statistics(void)
+{
+
+ struct pmc_driverstats gms;
+
+ if (pmc_get_driver_stats(&gms) < 0)
+ err(EX_OSERR, "ERROR: cannot retrieve driver statistics");
+
+ /*
+ * Print statistics.
+ */
+
+#define PRINT(N,V) (void) printf("%-40s %d\n", (N), gms.pm_##V)
+ PRINT("interrupts processed:", intr_processed);
+ PRINT("non-PMC interrupts:", intr_ignored);
+ PRINT("sampling stalls due to space shortages:", intr_bufferfull);
+ PRINT("system calls:", syscalls);
+ PRINT("system calls with errors:", syscall_errors);
+ PRINT("buffer requests:", buffer_requests);
+ PRINT("buffer requests failed:", buffer_requests_failed);
+ PRINT("sampling log sweeps:", log_sweeps);
+
+ return 0;
+}
+
+/*
+ * Main
+ */
+
+int
+main(int argc, char **argv)
+{
+ int error, command, currentcpu, option, pmc;
+ char *dummy;
+ struct pmcc_op *p;
+
+#if DEBUG
+ pmcc_init_debug();
+#endif
+
+ /* parse args */
+
+ currentcpu = PMCC_CPU_ALL;
+ command = PMCC_PRINT_USAGE;
+ error = 0;
+
+ STAILQ_INIT(&head);
+
+ while ((option = getopt(argc, argv, ":c:d:e:lLs")) != -1)
+ switch (option) {
+ case 'L':
+ if (command != PMCC_PRINT_USAGE) {
+ error = 1;
+ break;
+ }
+ command = PMCC_PRINT_EVENTS;
+ break;
+
+ case 'c':
+ if (command != PMCC_PRINT_USAGE &&
+ command != PMCC_ENABLE_DISABLE) {
+ error = 1;
+ break;
+ }
+ command = PMCC_ENABLE_DISABLE;
+
+ if (*optarg == PMCC_CPU_WILDCARD)
+ currentcpu = PMCC_CPU_ALL;
+ else {
+ currentcpu = strtoul(optarg, &dummy, 0);
+ if (*dummy != '\0' || currentcpu < 0)
+ errx(EX_DATAERR,
+ "\"%s\" is not a valid CPU id",
+ optarg);
+ }
+ break;
+
+ case 'd':
+ case 'e':
+ if (command != PMCC_PRINT_USAGE &&
+ command != PMCC_ENABLE_DISABLE) {
+ error = 1;
+ break;
+ }
+ command = PMCC_ENABLE_DISABLE;
+
+ if (*optarg == PMCC_PMC_WILDCARD)
+ pmc = PMCC_PMC_ALL;
+ else {
+ pmc = strtoul(optarg, &dummy, 0);
+ if (*dummy != '\0' || pmc < 0)
+ errx(EX_DATAERR,
+ "\"%s\" is not a valid PMC id",
+ optarg);
+ }
+
+ if ((p = malloc(sizeof(*p))) == NULL)
+ err(EX_SOFTWARE, "Out of memory");
+
+ p->op_cpu = currentcpu;
+ p->op_pmc = pmc;
+ p->op_op = option == 'd' ? PMCC_OP_DISABLE :
+ PMCC_OP_ENABLE;
+
+ STAILQ_INSERT_TAIL(&head, p, op_next);
+ break;
+
+ case 'l':
+ if (command != PMCC_PRINT_USAGE) {
+ error = 1;
+ break;
+ }
+ command = PMCC_LIST_STATE;
+ break;
+
+ case 's':
+ if (command != PMCC_PRINT_USAGE) {
+ error = 1;
+ break;
+ }
+ command = PMCC_SHOW_STATISTICS;
+ break;
+
+ case ':':
+ errx(EX_USAGE,
+ "Missing argument to option '-%c'", optopt);
+ break;
+
+ case '?':
+ warnx("Unrecognized option \"-%c\"", optopt);
+ errx(EX_USAGE, "%s", usage_message);
+ break;
+
+ default:
+ error = 1;
+ break;
+
+ }
+
+ if (command == PMCC_PRINT_USAGE)
+ (void) errx(EX_USAGE, "%s", usage_message);
+
+ if (error)
+ exit(EX_USAGE);
+
+ if (pmc_init() < 0)
+ err(EX_UNAVAILABLE,
+ "Initialization of the pmc(3) library failed");
+
+ switch (command) {
+ case PMCC_LIST_STATE:
+ error = pmcc_do_list_state();
+ break;
+ case PMCC_PRINT_EVENTS:
+ error = pmcc_do_list_events();
+ break;
+ case PMCC_SHOW_STATISTICS:
+ error = pmcc_show_statistics();
+ break;
+ case PMCC_ENABLE_DISABLE:
+ if (STAILQ_EMPTY(&head))
+ errx(EX_USAGE,
+ "No PMCs specified to enable or disable");
+ error = pmcc_do_enable_disable(&head);
+ break;
+ default:
+ assert(0);
+
+ }
+
+ if (error != 0)
+ err(EX_OSERR, "Command failed");
+ exit(0);
+}
diff --git a/usr.sbin/pmcstat/Makefile b/usr.sbin/pmcstat/Makefile
new file mode 100644
index 0000000..dc5a30a
--- /dev/null
+++ b/usr.sbin/pmcstat/Makefile
@@ -0,0 +1,14 @@
+#
+# $FreeBSD$
+#
+
+PROG= pmcstat
+MAN= pmcstat.8
+
+LIBADD= elf kvm pmc m ncursesw
+
+SRCS= pmcstat.c pmcstat.h pmcstat_log.c \
+pmcpl_callgraph.c pmcpl_gprof.c pmcpl_annotate.c \
+pmcpl_annotate_cg.c pmcpl_calltree.c
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/pmcstat/Makefile.depend b/usr.sbin/pmcstat/Makefile.depend
new file mode 100644
index 0000000..a3f7225
--- /dev/null
+++ b/usr.sbin/pmcstat/Makefile.depend
@@ -0,0 +1,23 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libelf \
+ lib/libkvm \
+ lib/libpmc \
+ lib/msun \
+ lib/ncurses/ncursesw \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/pmcstat/pmcpl_annotate.c b/usr.sbin/pmcstat/pmcpl_annotate.c
new file mode 100644
index 0000000..802983c
--- /dev/null
+++ b/usr.sbin/pmcstat/pmcpl_annotate.c
@@ -0,0 +1,111 @@
+/*-
+ * Copyright (c) 2005-2007, Joseph Koshy
+ * Copyright (c) 2007 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * Portions of this software were developed by A. Joseph Koshy under
+ * sponsorship from the FreeBSD Foundation and Google, 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.
+ *
+ * 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.
+ */
+
+/*
+ * Transform a hwpmc(4) log into human readable form, and into
+ * gprof(1) compatible profiles.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/endian.h>
+#include <sys/gmon.h>
+#include <sys/imgact_aout.h>
+#include <sys/imgact_elf.h>
+#include <sys/mman.h>
+#include <sys/pmc.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <netinet/in.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <gelf.h>
+#include <libgen.h>
+#include <limits.h>
+#include <netdb.h>
+#include <pmc.h>
+#include <pmclog.h>
+#include <sysexits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "pmcstat.h"
+#include "pmcstat_log.h"
+#include "pmcpl_annotate.h"
+
+/*
+ * Record a callchain.
+ */
+
+void
+pmcpl_annotate_process(struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr,
+ uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu)
+{
+ struct pmcstat_pcmap *map;
+ struct pmcstat_symbol *sym;
+ uintfptr_t newpc;
+ struct pmcstat_image *image;
+
+ (void) pmcr; (void) nsamples; (void) usermode; (void) cpu;
+
+ map = pmcstat_process_find_map(usermode ? pp : pmcstat_kernproc, cc[0]);
+ if (map == NULL) {
+ /* Unknown offset. */
+ pmcstat_stats.ps_samples_unknown_offset++;
+ return;
+ }
+
+ assert(cc[0] >= map->ppm_lowpc && cc[0] < map->ppm_highpc);
+
+ image = map->ppm_image;
+ newpc = cc[0] - (map->ppm_lowpc +
+ (image->pi_vaddr - image->pi_start));
+ sym = pmcstat_symbol_search(image, newpc);
+ if (sym == NULL)
+ return;
+
+ fprintf(args.pa_graphfile, "%p %s 0x%jx 0x%jx\n",
+ (void *)cc[0],
+ pmcstat_string_unintern(sym->ps_name),
+ (uintmax_t)(sym->ps_start +
+ image->pi_vaddr), (uintmax_t)(sym->ps_end +
+ image->pi_vaddr));
+}
diff --git a/usr.sbin/pmcstat/pmcpl_annotate.h b/usr.sbin/pmcstat/pmcpl_annotate.h
new file mode 100644
index 0000000..482bcd4
--- /dev/null
+++ b/usr.sbin/pmcstat/pmcpl_annotate.h
@@ -0,0 +1,41 @@
+/*-
+ * Copyright (c) 2005-2007, Joseph Koshy
+ * Copyright (c) 2007 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * Portions of this software were developed by A. Joseph Koshy under
+ * sponsorship from the FreeBSD Foundation and Google, 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.
+ *
+ * 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 _PMCSTAT_PL_ANNOTATE_H_
+#define _PMCSTAT_PL_ANNOTATE_H_
+
+/* Function prototypes */
+void pmcpl_annotate_process(
+ struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr,
+ uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu);
+
+#endif /* _PMCSTAT_PL_ANNOTATE_H_ */
diff --git a/usr.sbin/pmcstat/pmcpl_annotate_cg.c b/usr.sbin/pmcstat/pmcpl_annotate_cg.c
new file mode 100644
index 0000000..e90bda1
--- /dev/null
+++ b/usr.sbin/pmcstat/pmcpl_annotate_cg.c
@@ -0,0 +1,127 @@
+/*-
+ * Copyright (c) 2005-2007, Joseph Koshy
+ * Copyright (c) 2007 The FreeBSD Foundation
+ * Copyright (c) 2014, Adrian Chadd, Netflix Inc.
+ * All rights reserved.
+ *
+ * Portions of this software were developed by A. Joseph Koshy under
+ * sponsorship from the FreeBSD Foundation and Google, 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.
+ *
+ * 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.
+ */
+
+/*
+ * Transform a hwpmc(4) log into human readable form, and into
+ * gprof(1) compatible profiles.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/endian.h>
+#include <sys/gmon.h>
+#include <sys/imgact_aout.h>
+#include <sys/imgact_elf.h>
+#include <sys/mman.h>
+#include <sys/pmc.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <netinet/in.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <gelf.h>
+#include <libgen.h>
+#include <limits.h>
+#include <netdb.h>
+#include <pmc.h>
+#include <pmclog.h>
+#include <sysexits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "pmcstat.h"
+#include "pmcstat_log.h"
+#include "pmcpl_annotate_cg.h"
+
+/*
+ * Record a callchain.
+ */
+
+void
+pmcpl_annotate_cg_process(struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr,
+ uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu)
+{
+ struct pmcstat_pcmap *map;
+ struct pmcstat_symbol *sym;
+ uintfptr_t newpc;
+ struct pmcstat_image *image;
+ int i;
+ char filename[PATH_MAX], funcname[PATH_MAX];
+ unsigned sline;
+
+ (void) pmcr; (void) nsamples; (void) usermode; (void) cpu;
+
+ for (i = 0; i < (int) nsamples; i++) {
+ map = NULL;
+ sym = NULL;
+ image = NULL;
+ filename[0] = '\0';
+ funcname[0] = '\0';
+ sline = 0;
+
+ map = pmcstat_process_find_map(usermode ? pp : pmcstat_kernproc, cc[i]);
+ if (map != NULL) {
+ assert(cc[i] >= map->ppm_lowpc && cc[i] < map->ppm_highpc);
+ image = map->ppm_image;
+ newpc = cc[i] - (map->ppm_lowpc +
+ (image->pi_vaddr - image->pi_start));
+ sym = pmcstat_symbol_search(image, newpc);
+ }
+
+ if (map != NULL && image != NULL && sym != NULL) {
+ (void) pmcstat_image_addr2line(image, cc[i],
+ filename, sizeof(filename), &sline, funcname, sizeof(funcname));
+ }
+
+ if (map != NULL && sym != NULL) {
+ fprintf(args.pa_graphfile, "%p %s %s:%d\n",
+ (void *)cc[i],
+ funcname,
+ filename,
+ sline);
+ } else {
+ fprintf(args.pa_graphfile, "%p <unknown> ??:0\n",
+ (void *) cc[i]);
+ }
+ }
+ fprintf(args.pa_graphfile, "--\n");
+}
diff --git a/usr.sbin/pmcstat/pmcpl_annotate_cg.h b/usr.sbin/pmcstat/pmcpl_annotate_cg.h
new file mode 100644
index 0000000..bd655f7
--- /dev/null
+++ b/usr.sbin/pmcstat/pmcpl_annotate_cg.h
@@ -0,0 +1,42 @@
+/*-
+ * Copyright (c) 2005-2007, Joseph Koshy
+ * Copyright (c) 2007 The FreeBSD Foundation
+ * Copyright (c) 2014, Adrian Chadd, Netflix Inc.
+ * All rights reserved.
+ *
+ * Portions of this software were developed by A. Joseph Koshy under
+ * sponsorship from the FreeBSD Foundation and Google, 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.
+ *
+ * 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 _PMCSTAT_PL_ANNOTATE_CG_H_
+#define _PMCSTAT_PL_ANNOTATE_CG_H_
+
+/* Function prototypes */
+void pmcpl_annotate_cg_process(
+ struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr,
+ uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu);
+
+#endif /* _PMCSTAT_PL_ANNOTATE_CG_H_ */
diff --git a/usr.sbin/pmcstat/pmcpl_callgraph.c b/usr.sbin/pmcstat/pmcpl_callgraph.c
new file mode 100644
index 0000000..33998b5
--- /dev/null
+++ b/usr.sbin/pmcstat/pmcpl_callgraph.c
@@ -0,0 +1,687 @@
+/*-
+ * Copyright (c) 2005-2007, Joseph Koshy
+ * Copyright (c) 2007 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * Portions of this software were developed by A. Joseph Koshy under
+ * sponsorship from the FreeBSD Foundation and Google, 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.
+ *
+ * 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.
+ */
+
+/*
+ * Transform a hwpmc(4) log into human readable form, and into
+ * gprof(1) compatible profiles.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/endian.h>
+#include <sys/gmon.h>
+#include <sys/imgact_aout.h>
+#include <sys/imgact_elf.h>
+#include <sys/mman.h>
+#include <sys/pmc.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <netinet/in.h>
+
+#include <assert.h>
+#include <curses.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <gelf.h>
+#include <libgen.h>
+#include <limits.h>
+#include <netdb.h>
+#include <pmc.h>
+#include <pmclog.h>
+#include <sysexits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "pmcstat.h"
+#include "pmcstat_log.h"
+#include "pmcstat_top.h"
+#include "pmcpl_callgraph.h"
+
+/* Get the sample value in percent related to nsamples. */
+#define PMCPL_CG_COUNTP(a) \
+ ((a)->pcg_count * 100.0 / nsamples)
+
+/*
+ * The toplevel CG nodes (i.e., with rank == 0) are placed in a hash table.
+ */
+
+struct pmcstat_cgnode_hash_list pmcstat_cgnode_hash[PMCSTAT_NHASH];
+int pmcstat_cgnode_hash_count;
+
+static pmcstat_interned_string pmcstat_previous_filename_printed;
+
+static struct pmcstat_cgnode *
+pmcstat_cgnode_allocate(struct pmcstat_image *image, uintfptr_t pc)
+{
+ struct pmcstat_cgnode *cg;
+
+ if ((cg = malloc(sizeof(*cg))) == NULL)
+ err(EX_OSERR, "ERROR: Cannot allocate callgraph node");
+
+ cg->pcg_image = image;
+ cg->pcg_func = pc;
+
+ cg->pcg_count = 0;
+ cg->pcg_nchildren = 0;
+ LIST_INIT(&cg->pcg_children);
+
+ return (cg);
+}
+
+/*
+ * Free a node and its children.
+ */
+static void
+pmcstat_cgnode_free(struct pmcstat_cgnode *cg)
+{
+ struct pmcstat_cgnode *cgc, *cgtmp;
+
+ LIST_FOREACH_SAFE(cgc, &cg->pcg_children, pcg_sibling, cgtmp)
+ pmcstat_cgnode_free(cgc);
+ free(cg);
+}
+
+/*
+ * Look for a callgraph node associated with pmc `pmcid' in the global
+ * hash table that corresponds to the given `pc' value in the process
+ * `pp'.
+ */
+static struct pmcstat_cgnode *
+pmcstat_cgnode_hash_lookup_pc(struct pmcstat_process *pp, pmc_id_t pmcid,
+ uintfptr_t pc, int usermode)
+{
+ struct pmcstat_pcmap *ppm;
+ struct pmcstat_symbol *sym;
+ struct pmcstat_image *image;
+ struct pmcstat_cgnode *cg;
+ struct pmcstat_cgnode_hash *h;
+ uintfptr_t loadaddress;
+ unsigned int i, hash;
+
+ ppm = pmcstat_process_find_map(usermode ? pp : pmcstat_kernproc, pc);
+ if (ppm == NULL)
+ return (NULL);
+
+ image = ppm->ppm_image;
+
+ loadaddress = ppm->ppm_lowpc + image->pi_vaddr - image->pi_start;
+ pc -= loadaddress; /* Convert to an offset in the image. */
+
+ /*
+ * Try determine the function at this offset. If we can't
+ * find a function round leave the `pc' value alone.
+ */
+ if ((sym = pmcstat_symbol_search(image, pc)) != NULL)
+ pc = sym->ps_start;
+ else
+ pmcstat_stats.ps_samples_unknown_function++;
+
+ for (hash = i = 0; i < sizeof(uintfptr_t); i++)
+ hash += (pc >> i) & 0xFF;
+
+ hash &= PMCSTAT_HASH_MASK;
+
+ cg = NULL;
+ LIST_FOREACH(h, &pmcstat_cgnode_hash[hash], pch_next)
+ {
+ if (h->pch_pmcid != pmcid)
+ continue;
+
+ cg = h->pch_cgnode;
+
+ assert(cg != NULL);
+
+ if (cg->pcg_image == image && cg->pcg_func == pc)
+ return (cg);
+ }
+
+ /*
+ * We haven't seen this (pmcid, pc) tuple yet, so allocate a
+ * new callgraph node and a new hash table entry for it.
+ */
+ cg = pmcstat_cgnode_allocate(image, pc);
+ if ((h = malloc(sizeof(*h))) == NULL)
+ err(EX_OSERR, "ERROR: Could not allocate callgraph node");
+
+ h->pch_pmcid = pmcid;
+ h->pch_cgnode = cg;
+ LIST_INSERT_HEAD(&pmcstat_cgnode_hash[hash], h, pch_next);
+
+ pmcstat_cgnode_hash_count++;
+
+ return (cg);
+}
+
+/*
+ * Compare two callgraph nodes for sorting.
+ */
+static int
+pmcstat_cgnode_compare(const void *a, const void *b)
+{
+ const struct pmcstat_cgnode *const *pcg1, *const *pcg2, *cg1, *cg2;
+
+ pcg1 = (const struct pmcstat_cgnode *const *) a;
+ cg1 = *pcg1;
+ pcg2 = (const struct pmcstat_cgnode *const *) b;
+ cg2 = *pcg2;
+
+ /* Sort in reverse order */
+ if (cg1->pcg_count < cg2->pcg_count)
+ return (1);
+ if (cg1->pcg_count > cg2->pcg_count)
+ return (-1);
+ return (0);
+}
+
+/*
+ * Find (allocating if a needed) a callgraph node in the given
+ * parent with the same (image, pcoffset) pair.
+ */
+
+static struct pmcstat_cgnode *
+pmcstat_cgnode_find(struct pmcstat_cgnode *parent, struct pmcstat_image *image,
+ uintfptr_t pcoffset)
+{
+ struct pmcstat_cgnode *child;
+
+ LIST_FOREACH(child, &parent->pcg_children, pcg_sibling) {
+ if (child->pcg_image == image &&
+ child->pcg_func == pcoffset)
+ return (child);
+ }
+
+ /*
+ * Allocate a new structure.
+ */
+
+ child = pmcstat_cgnode_allocate(image, pcoffset);
+
+ /*
+ * Link it into the parent.
+ */
+ LIST_INSERT_HEAD(&parent->pcg_children, child, pcg_sibling);
+ parent->pcg_nchildren++;
+
+ return (child);
+}
+
+/*
+ * Print one callgraph node. The output format is:
+ *
+ * indentation %(parent's samples) #nsamples function@object
+ */
+static void
+pmcstat_cgnode_print(struct pmcstat_cgnode *cg, int depth, uint32_t total)
+{
+ uint32_t n;
+ const char *space;
+ struct pmcstat_symbol *sym;
+ struct pmcstat_cgnode **sortbuffer, **cgn, *pcg;
+
+ space = " ";
+
+ if (depth > 0)
+ (void) fprintf(args.pa_graphfile, "%*s", depth, space);
+
+ if (cg->pcg_count == total)
+ (void) fprintf(args.pa_graphfile, "100.0%% ");
+ else
+ (void) fprintf(args.pa_graphfile, "%05.2f%% ",
+ 100.0 * cg->pcg_count / total);
+
+ n = fprintf(args.pa_graphfile, " [%u] ", cg->pcg_count);
+
+ /* #samples is a 12 character wide field. */
+ if (n < 12)
+ (void) fprintf(args.pa_graphfile, "%*s", 12 - n, space);
+
+ if (depth > 0)
+ (void) fprintf(args.pa_graphfile, "%*s", depth, space);
+
+ sym = pmcstat_symbol_search(cg->pcg_image, cg->pcg_func);
+ if (sym)
+ (void) fprintf(args.pa_graphfile, "%s",
+ pmcstat_string_unintern(sym->ps_name));
+ else
+ (void) fprintf(args.pa_graphfile, "%p",
+ (void *) (cg->pcg_image->pi_vaddr + cg->pcg_func));
+
+ if (pmcstat_previous_filename_printed !=
+ cg->pcg_image->pi_fullpath) {
+ pmcstat_previous_filename_printed = cg->pcg_image->pi_fullpath;
+ (void) fprintf(args.pa_graphfile, " @ %s\n",
+ pmcstat_string_unintern(
+ pmcstat_previous_filename_printed));
+ } else
+ (void) fprintf(args.pa_graphfile, "\n");
+
+ if (cg->pcg_nchildren == 0)
+ return;
+
+ if ((sortbuffer = (struct pmcstat_cgnode **)
+ malloc(sizeof(struct pmcstat_cgnode *) *
+ cg->pcg_nchildren)) == NULL)
+ err(EX_OSERR, "ERROR: Cannot print callgraph");
+ cgn = sortbuffer;
+
+ LIST_FOREACH(pcg, &cg->pcg_children, pcg_sibling)
+ *cgn++ = pcg;
+
+ assert(cgn - sortbuffer == (int) cg->pcg_nchildren);
+
+ qsort(sortbuffer, cg->pcg_nchildren, sizeof(struct pmcstat_cgnode *),
+ pmcstat_cgnode_compare);
+
+ for (cgn = sortbuffer, n = 0; n < cg->pcg_nchildren; n++, cgn++)
+ pmcstat_cgnode_print(*cgn, depth+1, cg->pcg_count);
+
+ free(sortbuffer);
+}
+
+/*
+ * Record a callchain.
+ */
+
+void
+pmcpl_cg_process(struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr,
+ uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu)
+{
+ uintfptr_t pc, loadaddress;
+ uint32_t n;
+ struct pmcstat_image *image;
+ struct pmcstat_pcmap *ppm;
+ struct pmcstat_symbol *sym;
+ struct pmcstat_cgnode *parent, *child;
+ struct pmcstat_process *km;
+ pmc_id_t pmcid;
+
+ (void) cpu;
+
+ /*
+ * Find the callgraph node recorded in the global hash table
+ * for this (pmcid, pc).
+ */
+
+ pc = cc[0];
+ pmcid = pmcr->pr_pmcid;
+ parent = pmcstat_cgnode_hash_lookup_pc(pp, pmcid, pc, usermode);
+ if (parent == NULL) {
+ pmcstat_stats.ps_callchain_dubious_frames++;
+ pmcr->pr_dubious_frames++;
+ return;
+ }
+
+ parent->pcg_count++;
+
+ /*
+ * For each return address in the call chain record, subject
+ * to the maximum depth desired.
+ * - Find the image associated with the sample. Stop if there
+ * there is no valid image at that address.
+ * - Find the function that overlaps the return address.
+ * - If found: use the start address of the function.
+ * If not found (say an object's symbol table is not present or
+ * is incomplete), round down to th gprof bucket granularity.
+ * - Convert return virtual address to an offset in the image.
+ * - Look for a child with the same {offset,image} tuple,
+ * inserting one if needed.
+ * - Increment the count of occurrences of the child.
+ */
+ km = pmcstat_kernproc;
+
+ for (n = 1; n < (uint32_t) args.pa_graphdepth && n < nsamples; n++,
+ parent = child) {
+ pc = cc[n];
+
+ ppm = pmcstat_process_find_map(usermode ? pp : km, pc);
+ if (ppm == NULL) {
+ /* Detect full frame capture (kernel + user). */
+ if (!usermode) {
+ ppm = pmcstat_process_find_map(pp, pc);
+ if (ppm != NULL)
+ km = pp;
+ }
+ }
+ if (ppm == NULL)
+ return;
+
+ image = ppm->ppm_image;
+ loadaddress = ppm->ppm_lowpc + image->pi_vaddr -
+ image->pi_start;
+ pc -= loadaddress;
+
+ if ((sym = pmcstat_symbol_search(image, pc)) != NULL)
+ pc = sym->ps_start;
+
+ child = pmcstat_cgnode_find(parent, image, pc);
+ child->pcg_count++;
+ }
+}
+
+/*
+ * Printing a callgraph for a PMC.
+ */
+static void
+pmcstat_callgraph_print_for_pmcid(struct pmcstat_pmcrecord *pmcr)
+{
+ int n, nentries;
+ uint32_t nsamples;
+ pmc_id_t pmcid;
+ struct pmcstat_cgnode **sortbuffer, **cgn;
+ struct pmcstat_cgnode_hash *pch;
+
+ /*
+ * We pull out all callgraph nodes in the top-level hash table
+ * with a matching PMC id. We then sort these based on the
+ * frequency of occurrence. Each callgraph node is then
+ * printed.
+ */
+
+ nsamples = 0;
+ pmcid = pmcr->pr_pmcid;
+ if ((sortbuffer = (struct pmcstat_cgnode **)
+ malloc(sizeof(struct pmcstat_cgnode *) *
+ pmcstat_cgnode_hash_count)) == NULL)
+ err(EX_OSERR, "ERROR: Cannot sort callgraph");
+ cgn = sortbuffer;
+
+ for (n = 0; n < PMCSTAT_NHASH; n++)
+ LIST_FOREACH(pch, &pmcstat_cgnode_hash[n], pch_next)
+ if (pch->pch_pmcid == pmcid) {
+ nsamples += pch->pch_cgnode->pcg_count;
+ *cgn++ = pch->pch_cgnode;
+ }
+
+ nentries = cgn - sortbuffer;
+ assert(nentries <= pmcstat_cgnode_hash_count);
+
+ if (nentries == 0) {
+ free(sortbuffer);
+ return;
+ }
+
+ qsort(sortbuffer, nentries, sizeof(struct pmcstat_cgnode *),
+ pmcstat_cgnode_compare);
+
+ (void) fprintf(args.pa_graphfile,
+ "@ %s [%u samples]\n\n",
+ pmcstat_string_unintern(pmcr->pr_pmcname),
+ nsamples);
+
+ for (cgn = sortbuffer, n = 0; n < nentries; n++, cgn++) {
+ pmcstat_previous_filename_printed = NULL;
+ pmcstat_cgnode_print(*cgn, 0, nsamples);
+ (void) fprintf(args.pa_graphfile, "\n");
+ }
+
+ free(sortbuffer);
+}
+
+/*
+ * Print out callgraphs.
+ */
+
+static void
+pmcstat_callgraph_print(void)
+{
+ struct pmcstat_pmcrecord *pmcr;
+
+ LIST_FOREACH(pmcr, &pmcstat_pmcs, pr_next)
+ pmcstat_callgraph_print_for_pmcid(pmcr);
+}
+
+static void
+pmcstat_cgnode_topprint(struct pmcstat_cgnode *cg,
+ int depth, uint32_t nsamples)
+{
+ int v_attrs, vs_len, ns_len, width, len, n, nchildren;
+ float v;
+ char ns[30], vs[10];
+ struct pmcstat_symbol *sym;
+ struct pmcstat_cgnode **sortbuffer, **cgn, *pcg;
+
+ (void) depth;
+
+ /* Format value. */
+ v = PMCPL_CG_COUNTP(cg);
+ snprintf(vs, sizeof(vs), "%.1f", v);
+ v_attrs = PMCSTAT_ATTRPERCENT(v);
+
+ /* Format name. */
+ sym = pmcstat_symbol_search(cg->pcg_image, cg->pcg_func);
+ if (sym != NULL) {
+ snprintf(ns, sizeof(ns), "%s",
+ pmcstat_string_unintern(sym->ps_name));
+ } else
+ snprintf(ns, sizeof(ns), "%p",
+ (void *)cg->pcg_func);
+
+ PMCSTAT_ATTRON(v_attrs);
+ PMCSTAT_PRINTW("%5.5s", vs);
+ PMCSTAT_ATTROFF(v_attrs);
+ PMCSTAT_PRINTW(" %-10.10s %-20.20s",
+ pmcstat_string_unintern(cg->pcg_image->pi_name),
+ ns);
+
+ nchildren = cg->pcg_nchildren;
+ if (nchildren == 0) {
+ PMCSTAT_PRINTW("\n");
+ return;
+ }
+
+ width = pmcstat_displaywidth - 40;
+
+ if ((sortbuffer = (struct pmcstat_cgnode **)
+ malloc(sizeof(struct pmcstat_cgnode *) *
+ nchildren)) == NULL)
+ err(EX_OSERR, "ERROR: Cannot print callgraph");
+ cgn = sortbuffer;
+
+ LIST_FOREACH(pcg, &cg->pcg_children, pcg_sibling)
+ *cgn++ = pcg;
+
+ assert(cgn - sortbuffer == (int)nchildren);
+
+ qsort(sortbuffer, nchildren, sizeof(struct pmcstat_cgnode *),
+ pmcstat_cgnode_compare);
+
+ /* Count how many callers. */
+ for (cgn = sortbuffer, n = 0; n < nchildren; n++, cgn++) {
+ pcg = *cgn;
+
+ v = PMCPL_CG_COUNTP(pcg);
+ if (v < pmcstat_threshold)
+ break;
+ }
+ nchildren = n;
+
+ for (cgn = sortbuffer, n = 0; n < nchildren; n++, cgn++) {
+ pcg = *cgn;
+
+ /* Format value. */
+ if (nchildren > 1) {
+ v = PMCPL_CG_COUNTP(pcg);
+ vs_len = snprintf(vs, sizeof(vs), ":%.1f", v);
+ v_attrs = PMCSTAT_ATTRPERCENT(v);
+ } else
+ vs_len = 0;
+
+ /* Format name. */
+ sym = pmcstat_symbol_search(pcg->pcg_image, pcg->pcg_func);
+ if (sym != NULL) {
+ ns_len = snprintf(ns, sizeof(ns), "%s",
+ pmcstat_string_unintern(sym->ps_name));
+ } else
+ ns_len = snprintf(ns, sizeof(ns), "%p",
+ (void *)pcg->pcg_func);
+
+ len = ns_len + vs_len + 1;
+ if (width - len < 0) {
+ PMCSTAT_PRINTW(" ...");
+ break;
+ }
+ width -= len;
+
+ PMCSTAT_PRINTW(" %s", ns);
+ if (nchildren > 1) {
+ PMCSTAT_ATTRON(v_attrs);
+ PMCSTAT_PRINTW("%s", vs);
+ PMCSTAT_ATTROFF(v_attrs);
+ }
+ }
+ PMCSTAT_PRINTW("\n");
+ free(sortbuffer);
+}
+
+/*
+ * Top mode display.
+ */
+
+void
+pmcpl_cg_topdisplay(void)
+{
+ int n, nentries;
+ uint32_t nsamples;
+ struct pmcstat_cgnode **sortbuffer, **cgn;
+ struct pmcstat_cgnode_hash *pch;
+ struct pmcstat_pmcrecord *pmcr;
+
+ pmcr = pmcstat_pmcindex_to_pmcr(pmcstat_pmcinfilter);
+ if (!pmcr)
+ err(EX_SOFTWARE, "ERROR: invalid pmcindex");
+
+ /*
+ * We pull out all callgraph nodes in the top-level hash table
+ * with a matching PMC index. We then sort these based on the
+ * frequency of occurrence. Each callgraph node is then
+ * printed.
+ */
+
+ nsamples = 0;
+
+ if ((sortbuffer = (struct pmcstat_cgnode **)
+ malloc(sizeof(struct pmcstat_cgnode *) *
+ pmcstat_cgnode_hash_count)) == NULL)
+ err(EX_OSERR, "ERROR: Cannot sort callgraph");
+ cgn = sortbuffer;
+
+ for (n = 0; n < PMCSTAT_NHASH; n++)
+ LIST_FOREACH(pch, &pmcstat_cgnode_hash[n], pch_next)
+ if (pmcr == NULL || pch->pch_pmcid == pmcr->pr_pmcid) {
+ nsamples += pch->pch_cgnode->pcg_count;
+ *cgn++ = pch->pch_cgnode;
+ }
+
+ nentries = cgn - sortbuffer;
+ assert(nentries <= pmcstat_cgnode_hash_count);
+
+ if (nentries == 0) {
+ free(sortbuffer);
+ return;
+ }
+
+ qsort(sortbuffer, nentries, sizeof(struct pmcstat_cgnode *),
+ pmcstat_cgnode_compare);
+
+ PMCSTAT_PRINTW("%5.5s %-10.10s %-20.20s %s\n",
+ "%SAMP", "IMAGE", "FUNCTION", "CALLERS");
+
+ nentries = min(pmcstat_displayheight - 2, nentries);
+
+ for (cgn = sortbuffer, n = 0; n < nentries; n++, cgn++) {
+ if (PMCPL_CG_COUNTP(*cgn) < pmcstat_threshold)
+ break;
+ pmcstat_cgnode_topprint(*cgn, 0, nsamples);
+ }
+
+ free(sortbuffer);
+}
+
+/*
+ * Handle top mode keypress.
+ */
+
+int
+pmcpl_cg_topkeypress(int c, WINDOW *w)
+{
+
+ (void) c; (void) w;
+
+ return 0;
+}
+
+int
+pmcpl_cg_init(void)
+{
+ int i;
+
+ pmcstat_cgnode_hash_count = 0;
+ pmcstat_previous_filename_printed = NULL;
+
+ for (i = 0; i < PMCSTAT_NHASH; i++) {
+ LIST_INIT(&pmcstat_cgnode_hash[i]);
+ }
+
+ return (0);
+}
+
+void
+pmcpl_cg_shutdown(FILE *mf)
+{
+ int i;
+ struct pmcstat_cgnode_hash *pch, *pchtmp;
+
+ (void) mf;
+
+ if (args.pa_flags & FLAG_DO_CALLGRAPHS)
+ pmcstat_callgraph_print();
+
+ /*
+ * Free memory.
+ */
+ for (i = 0; i < PMCSTAT_NHASH; i++) {
+ LIST_FOREACH_SAFE(pch, &pmcstat_cgnode_hash[i], pch_next,
+ pchtmp) {
+ pmcstat_cgnode_free(pch->pch_cgnode);
+ LIST_REMOVE(pch, pch_next);
+ free(pch);
+ }
+ }
+}
+
diff --git a/usr.sbin/pmcstat/pmcpl_callgraph.h b/usr.sbin/pmcstat/pmcpl_callgraph.h
new file mode 100644
index 0000000..aaf0e1b
--- /dev/null
+++ b/usr.sbin/pmcstat/pmcpl_callgraph.h
@@ -0,0 +1,67 @@
+/*-
+ * Copyright (c) 2005-2007, Joseph Koshy
+ * Copyright (c) 2007 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * Portions of this software were developed by A. Joseph Koshy under
+ * sponsorship from the FreeBSD Foundation and Google, 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.
+ *
+ * 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 _PMCSTAT_PL_CALLGRAPH_H_
+#define _PMCSTAT_PL_CALLGRAPH_H_
+
+/*
+ * Each call graph node is tracked by a pmcstat_cgnode struct.
+ */
+
+struct pmcstat_cgnode {
+ struct pmcstat_image *pcg_image;
+ uintfptr_t pcg_func;
+ uint32_t pcg_count;
+ uint32_t pcg_nchildren;
+ LIST_ENTRY(pmcstat_cgnode) pcg_sibling;
+ LIST_HEAD(,pmcstat_cgnode) pcg_children;
+};
+
+struct pmcstat_cgnode_hash {
+ struct pmcstat_cgnode *pch_cgnode;
+ pmc_id_t pch_pmcid;
+ LIST_ENTRY(pmcstat_cgnode_hash) pch_next;
+};
+extern LIST_HEAD(pmcstat_cgnode_hash_list, pmcstat_cgnode_hash) pmcstat_cgnode_hash[PMCSTAT_NHASH];
+extern int pmcstat_cgnode_hash_count;
+
+/* Function prototypes */
+int pmcpl_cg_init(void);
+void pmcpl_cg_shutdown(FILE *mf);
+void pmcpl_cg_process(
+ struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr,
+ uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu);
+int pmcpl_cg_topkeypress(int c, WINDOW *w);
+void pmcpl_cg_topdisplay(void);
+void pmcpl_cg_configure(char *opt);
+
+#endif /* _PMCSTAT_PL_CALLGRAPH_H_ */
diff --git a/usr.sbin/pmcstat/pmcpl_calltree.c b/usr.sbin/pmcstat/pmcpl_calltree.c
new file mode 100644
index 0000000..3d0127d
--- /dev/null
+++ b/usr.sbin/pmcstat/pmcpl_calltree.c
@@ -0,0 +1,1197 @@
+/*-
+ * Copyright (c) 2012, Fabien Thomas
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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.
+ */
+
+/*
+ * Process hwpmc(4) samples as calltree.
+ *
+ * Output file format compatible with Kcachegrind (kdesdk).
+ * Handle top mode with a sorted tree display.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/endian.h>
+#include <sys/queue.h>
+
+#include <assert.h>
+#include <curses.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pmc.h>
+#include <pmclog.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sysexits.h>
+
+#include "pmcstat.h"
+#include "pmcstat_log.h"
+#include "pmcstat_top.h"
+#include "pmcpl_calltree.h"
+
+#define PMCPL_CT_GROWSIZE 4
+
+static int pmcstat_skiplink = 0;
+
+struct pmcpl_ct_node;
+
+/* Get the sample value for PMC a. */
+#define PMCPL_CT_SAMPLE(a, b) \
+ ((a) < (b)->npmcs ? (b)->sb[a] : 0)
+
+/* Get the sample value in percent related to rsamples. */
+#define PMCPL_CT_SAMPLEP(a, b) \
+ (PMCPL_CT_SAMPLE(a, b) * 100.0 / rsamples->sb[a])
+
+struct pmcpl_ct_sample {
+ int npmcs; /* Max pmc index available. */
+ unsigned *sb; /* Sample buffer for 0..npmcs. */
+};
+
+struct pmcpl_ct_arc {
+ struct pmcpl_ct_sample pcta_samples;
+ struct pmcpl_ct_sample pcta_callid;
+ unsigned pcta_call;
+ struct pmcpl_ct_node *pcta_child;
+};
+
+struct pmcpl_ct_instr {
+ uintfptr_t pctf_func;
+ struct pmcpl_ct_sample pctf_samples;
+};
+
+/*
+ * Each calltree node is tracked by a pmcpl_ct_node struct.
+ */
+struct pmcpl_ct_node {
+ struct pmcstat_image *pct_image;
+ uintfptr_t pct_func;
+
+ struct pmcstat_symbol *pct_sym;
+ pmcstat_interned_string pct_ifl;
+ pmcstat_interned_string pct_ifn;
+
+ struct pmcpl_ct_sample pct_samples;
+
+ int pct_narc;
+ int pct_arc_c;
+ struct pmcpl_ct_arc *pct_arc;
+
+ /* TODO: optimize for large number of items. */
+ int pct_ninstr;
+ int pct_instr_c;
+ struct pmcpl_ct_instr *pct_instr;
+
+#define PMCPL_PCT_ADDR 0
+#define PMCPL_PCT_NAME 1
+ char pct_type;
+#define PMCPL_PCT_WHITE 0
+#define PMCPL_PCT_GREY 1
+#define PMCPL_PCT_BLACK 2
+ char pct_color;
+};
+
+struct pmcpl_ct_node_hash {
+ struct pmcpl_ct_node *pch_ctnode;
+ STAILQ_ENTRY(pmcpl_ct_node_hash) pch_next;
+};
+
+static struct pmcpl_ct_sample pmcpl_ct_callid;
+
+#define PMCPL_CT_MAXCOL PMC_CALLCHAIN_DEPTH_MAX
+#define PMCPL_CT_MAXLINE 1024 /* TODO: dynamic. */
+
+struct pmcpl_ct_line {
+ unsigned ln_sum;
+ unsigned ln_index;
+};
+
+static struct pmcpl_ct_line pmcpl_ct_topmax[PMCPL_CT_MAXLINE+1];
+static struct pmcpl_ct_node
+ *pmcpl_ct_topscreen[PMCPL_CT_MAXCOL+1][PMCPL_CT_MAXLINE+1];
+
+/*
+ * All nodes indexed by function/image name are placed in a hash table.
+ */
+static STAILQ_HEAD(,pmcpl_ct_node_hash) pmcpl_ct_node_hash[PMCSTAT_NHASH];
+
+/*
+ * Root node for the graph.
+ */
+static struct pmcpl_ct_node *pmcpl_ct_root;
+
+/*
+ * Prototypes
+ */
+
+/*
+ * Initialize a samples.
+ */
+
+static void
+pmcpl_ct_samples_init(struct pmcpl_ct_sample *samples)
+{
+
+ samples->npmcs = 0;
+ samples->sb = NULL;
+}
+
+/*
+ * Free a samples.
+ */
+
+static void
+pmcpl_ct_samples_free(struct pmcpl_ct_sample *samples)
+{
+
+ samples->npmcs = 0;
+ free(samples->sb);
+ samples->sb = NULL;
+}
+
+/*
+ * Grow a sample block to store pmcstat_npmcs PMCs.
+ */
+
+static void
+pmcpl_ct_samples_grow(struct pmcpl_ct_sample *samples)
+{
+ int npmcs;
+
+ /* Enough storage. */
+ if (pmcstat_npmcs <= samples->npmcs)
+ return;
+
+ npmcs = samples->npmcs +
+ max(pmcstat_npmcs - samples->npmcs, PMCPL_CT_GROWSIZE);
+ samples->sb = realloc(samples->sb, npmcs * sizeof(unsigned));
+ if (samples->sb == NULL)
+ errx(EX_SOFTWARE, "ERROR: out of memory");
+ bzero((char *)samples->sb + samples->npmcs * sizeof(unsigned),
+ (npmcs - samples->npmcs) * sizeof(unsigned));
+ samples->npmcs = npmcs;
+}
+
+/*
+ * Compute the sum of all root arcs.
+ */
+
+static void
+pmcpl_ct_samples_root(struct pmcpl_ct_sample *samples)
+{
+ int i, pmcin;
+
+ pmcpl_ct_samples_init(samples);
+ pmcpl_ct_samples_grow(samples);
+
+ for (i = 0; i < pmcpl_ct_root->pct_narc; i++)
+ for (pmcin = 0; pmcin < pmcstat_npmcs; pmcin++)
+ samples->sb[pmcin] += PMCPL_CT_SAMPLE(pmcin,
+ &pmcpl_ct_root->pct_arc[i].pcta_samples);
+}
+
+/*
+ * Grow the arc table.
+ */
+
+static void
+pmcpl_ct_arc_grow(int cursize, int *maxsize, struct pmcpl_ct_arc **items)
+{
+ int nmaxsize;
+
+ if (cursize < *maxsize)
+ return;
+
+ nmaxsize = *maxsize + max(cursize + 1 - *maxsize, PMCPL_CT_GROWSIZE);
+ *items = realloc(*items, nmaxsize * sizeof(struct pmcpl_ct_arc));
+ if (*items == NULL)
+ errx(EX_SOFTWARE, "ERROR: out of memory");
+ bzero((char *)*items + *maxsize * sizeof(struct pmcpl_ct_arc),
+ (nmaxsize - *maxsize) * sizeof(struct pmcpl_ct_arc));
+ *maxsize = nmaxsize;
+}
+
+/*
+ * Grow the instr table.
+ */
+
+static void
+pmcpl_ct_instr_grow(int cursize, int *maxsize, struct pmcpl_ct_instr **items)
+{
+ int nmaxsize;
+
+ if (cursize < *maxsize)
+ return;
+
+ nmaxsize = *maxsize + max(cursize + 1 - *maxsize, PMCPL_CT_GROWSIZE);
+ *items = realloc(*items, nmaxsize * sizeof(struct pmcpl_ct_instr));
+ if (*items == NULL)
+ errx(EX_SOFTWARE, "ERROR: out of memory");
+ bzero((char *)*items + *maxsize * sizeof(struct pmcpl_ct_instr),
+ (nmaxsize - *maxsize) * sizeof(struct pmcpl_ct_instr));
+ *maxsize = nmaxsize;
+}
+
+/*
+ * Add a new instruction sample to given node.
+ */
+
+static void
+pmcpl_ct_instr_add(struct pmcpl_ct_node *ct, int pmcin,
+ uintfptr_t pc, unsigned v)
+{
+ int i;
+ struct pmcpl_ct_instr *in;
+
+ for (i = 0; i<ct->pct_ninstr; i++) {
+ if (ct->pct_instr[i].pctf_func == pc) {
+ in = &ct->pct_instr[i];
+ pmcpl_ct_samples_grow(&in->pctf_samples);
+ in->pctf_samples.sb[pmcin] += v;
+ return;
+ }
+ }
+
+ pmcpl_ct_instr_grow(ct->pct_ninstr, &ct->pct_instr_c, &ct->pct_instr);
+ in = &ct->pct_instr[ct->pct_ninstr];
+ in->pctf_func = pc;
+ pmcpl_ct_samples_init(&in->pctf_samples);
+ pmcpl_ct_samples_grow(&in->pctf_samples);
+ in->pctf_samples.sb[pmcin] = v;
+ ct->pct_ninstr++;
+}
+
+/*
+ * Allocate a new node.
+ */
+
+static struct pmcpl_ct_node *
+pmcpl_ct_node_allocate(void)
+{
+ struct pmcpl_ct_node *ct;
+
+ if ((ct = malloc(sizeof(*ct))) == NULL)
+ err(EX_OSERR, "ERROR: Cannot allocate callgraph node");
+
+ pmcpl_ct_samples_init(&ct->pct_samples);
+
+ ct->pct_sym = NULL;
+ ct->pct_image = NULL;
+ ct->pct_func = 0;
+
+ ct->pct_narc = 0;
+ ct->pct_arc_c = 0;
+ ct->pct_arc = NULL;
+
+ ct->pct_ninstr = 0;
+ ct->pct_instr_c = 0;
+ ct->pct_instr = NULL;
+
+ ct->pct_color = PMCPL_PCT_WHITE;
+
+ return (ct);
+}
+
+/*
+ * Free a node.
+ */
+
+static void
+pmcpl_ct_node_free(struct pmcpl_ct_node *ct)
+{
+ int i;
+
+ for (i = 0; i < ct->pct_narc; i++) {
+ pmcpl_ct_samples_free(&ct->pct_arc[i].pcta_samples);
+ pmcpl_ct_samples_free(&ct->pct_arc[i].pcta_callid);
+ }
+
+ pmcpl_ct_samples_free(&ct->pct_samples);
+ free(ct->pct_arc);
+ free(ct->pct_instr);
+ free(ct);
+}
+
+/*
+ * Clear the graph tag on each node.
+ */
+static void
+pmcpl_ct_node_cleartag(void)
+{
+ int i;
+ struct pmcpl_ct_node_hash *pch;
+
+ for (i = 0; i < PMCSTAT_NHASH; i++)
+ STAILQ_FOREACH(pch, &pmcpl_ct_node_hash[i], pch_next)
+ pch->pch_ctnode->pct_color = PMCPL_PCT_WHITE;
+
+ pmcpl_ct_root->pct_color = PMCPL_PCT_WHITE;
+}
+
+/*
+ * Print the callchain line by line with maximum cost at top.
+ */
+
+static int
+pmcpl_ct_node_dumptop(int pmcin, struct pmcpl_ct_node *ct,
+ struct pmcpl_ct_sample *rsamples, int x, int *y)
+{
+ int i, terminal;
+ struct pmcpl_ct_arc *arc;
+
+ if (ct->pct_color == PMCPL_PCT_GREY)
+ return 0;
+
+ if (x >= PMCPL_CT_MAXCOL) {
+ pmcpl_ct_topscreen[x][*y] = NULL;
+ return 1;
+ }
+ pmcpl_ct_topscreen[x][*y] = ct;
+
+ /*
+ * Check if this is a terminal node.
+ * We need to check that some samples exist
+ * for at least one arc for that PMC.
+ */
+ terminal = 1;
+ for (i = 0; i < ct->pct_narc; i++) {
+ arc = &ct->pct_arc[i];
+ if (arc->pcta_child->pct_color != PMCPL_PCT_GREY &&
+ PMCPL_CT_SAMPLE(pmcin,
+ &arc->pcta_samples) != 0 &&
+ PMCPL_CT_SAMPLEP(pmcin,
+ &arc->pcta_samples) > pmcstat_threshold) {
+ terminal = 0;
+ break;
+ }
+ }
+
+ if (ct->pct_narc == 0 || terminal) {
+ pmcpl_ct_topscreen[x+1][*y] = NULL;
+ if (*y >= PMCPL_CT_MAXLINE)
+ return 1;
+ *y = *y + 1;
+ for (i=0; i < x; i++)
+ pmcpl_ct_topscreen[i][*y] =
+ pmcpl_ct_topscreen[i][*y - 1];
+ return 0;
+ }
+
+ ct->pct_color = PMCPL_PCT_GREY;
+ for (i = 0; i < ct->pct_narc; i++) {
+ if (PMCPL_CT_SAMPLE(pmcin,
+ &ct->pct_arc[i].pcta_samples) == 0)
+ continue;
+ if (PMCPL_CT_SAMPLEP(pmcin,
+ &ct->pct_arc[i].pcta_samples) > pmcstat_threshold) {
+ if (pmcpl_ct_node_dumptop(pmcin,
+ ct->pct_arc[i].pcta_child,
+ rsamples, x+1, y)) {
+ ct->pct_color = PMCPL_PCT_BLACK;
+ return 1;
+ }
+ }
+ }
+ ct->pct_color = PMCPL_PCT_BLACK;
+
+ return 0;
+}
+
+/*
+ * Compare two top line by sum.
+ */
+static int
+pmcpl_ct_line_compare(const void *a, const void *b)
+{
+ const struct pmcpl_ct_line *ct1, *ct2;
+
+ ct1 = (const struct pmcpl_ct_line *) a;
+ ct2 = (const struct pmcpl_ct_line *) b;
+
+ /* Sort in reverse order */
+ if (ct1->ln_sum < ct2->ln_sum)
+ return (1);
+ if (ct1->ln_sum > ct2->ln_sum)
+ return (-1);
+ return (0);
+}
+
+/*
+ * Format and display given PMC index.
+ */
+
+static void
+pmcpl_ct_node_printtop(struct pmcpl_ct_sample *rsamples, int pmcin, int maxy)
+{
+#undef TS
+#undef TSI
+#define TS(x, y) (pmcpl_ct_topscreen[x][y])
+#define TSI(x, y) (pmcpl_ct_topscreen[x][pmcpl_ct_topmax[y].ln_index])
+
+ int v_attrs, ns_len, vs_len, is_len, width, indentwidth, x, y;
+ float v;
+ char ns[30], vs[10], is[20];
+ struct pmcpl_ct_node *ct;
+ const char *space = " ";
+
+ /*
+ * Sort by line cost.
+ */
+ for (y = 0; ; y++) {
+ ct = TS(1, y);
+ if (ct == NULL)
+ break;
+
+ pmcpl_ct_topmax[y].ln_sum = 0;
+ pmcpl_ct_topmax[y].ln_index = y;
+ for (x = 1; TS(x, y) != NULL; x++) {
+ pmcpl_ct_topmax[y].ln_sum +=
+ PMCPL_CT_SAMPLE(pmcin, &TS(x, y)->pct_samples);
+ }
+ }
+ qsort(pmcpl_ct_topmax, y, sizeof(pmcpl_ct_topmax[0]),
+ pmcpl_ct_line_compare);
+ pmcpl_ct_topmax[y].ln_index = y;
+
+ for (y = 0; y < maxy; y++) {
+ ct = TSI(1, y);
+ if (ct == NULL)
+ break;
+
+ if (y > 0)
+ PMCSTAT_PRINTW("\n");
+
+ /* Output sum. */
+ v = pmcpl_ct_topmax[y].ln_sum * 100.0 /
+ rsamples->sb[pmcin];
+ snprintf(vs, sizeof(vs), "%.1f", v);
+ v_attrs = PMCSTAT_ATTRPERCENT(v);
+ PMCSTAT_ATTRON(v_attrs);
+ PMCSTAT_PRINTW("%5.5s ", vs);
+ PMCSTAT_ATTROFF(v_attrs);
+
+ width = indentwidth = 5 + 1;
+
+ for (x = 1; (ct = TSI(x, y)) != NULL; x++) {
+
+ vs[0] = '\0'; vs_len = 0;
+ is[0] = '\0'; is_len = 0;
+
+ /* Format value. */
+ v = PMCPL_CT_SAMPLEP(pmcin, &ct->pct_samples);
+ if (v > pmcstat_threshold)
+ vs_len = snprintf(vs, sizeof(vs),
+ "(%.1f%%)", v);
+ v_attrs = PMCSTAT_ATTRPERCENT(v);
+
+ if (pmcstat_skiplink && v <= pmcstat_threshold) {
+ strlcpy(ns, ".", sizeof(ns));
+ ns_len = 1;
+ } else {
+ if (ct->pct_sym != NULL) {
+ ns_len = snprintf(ns, sizeof(ns), "%s",
+ pmcstat_string_unintern(ct->pct_sym->ps_name));
+ } else
+ ns_len = snprintf(ns, sizeof(ns), "%p",
+ (void *)ct->pct_func);
+
+ /* Format image. */
+ if (x == 1 ||
+ TSI(x-1, y)->pct_image != ct->pct_image)
+ is_len = snprintf(is, sizeof(is), "@%s",
+ pmcstat_string_unintern(ct->pct_image->pi_name));
+
+ /* Check for line wrap. */
+ width += ns_len + is_len + vs_len + 1;
+ }
+ if (width >= pmcstat_displaywidth) {
+ maxy--;
+ if (y >= maxy)
+ break;
+ PMCSTAT_PRINTW("\n%*s", indentwidth, space);
+ width = indentwidth + ns_len + is_len + vs_len;
+ }
+
+ PMCSTAT_ATTRON(v_attrs);
+ PMCSTAT_PRINTW("%s%s%s ", ns, is, vs);
+ PMCSTAT_ATTROFF(v_attrs);
+ }
+ }
+}
+
+/*
+ * Output top mode snapshot.
+ */
+
+void
+pmcpl_ct_topdisplay(void)
+{
+ int y;
+ struct pmcpl_ct_sample r, *rsamples;
+
+ rsamples = &r;
+ pmcpl_ct_samples_root(rsamples);
+ pmcpl_ct_node_cleartag();
+
+ PMCSTAT_PRINTW("%5.5s %s\n", "%SAMP", "CALLTREE");
+
+ y = 0;
+ if (pmcpl_ct_node_dumptop(pmcstat_pmcinfilter,
+ pmcpl_ct_root, rsamples, 0, &y))
+ PMCSTAT_PRINTW("...\n");
+ pmcpl_ct_topscreen[1][y] = NULL;
+
+ pmcpl_ct_node_printtop(rsamples,
+ pmcstat_pmcinfilter, pmcstat_displayheight - 2);
+
+ pmcpl_ct_samples_free(rsamples);
+}
+
+/*
+ * Handle top mode keypress.
+ */
+
+int
+pmcpl_ct_topkeypress(int c, WINDOW *w)
+{
+
+ switch (c) {
+ case 'f':
+ pmcstat_skiplink = !pmcstat_skiplink;
+ wprintw(w, "skip empty link %s",
+ pmcstat_skiplink ? "on" : "off");
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * Look for a callgraph node associated with pmc `pmcid' in the global
+ * hash table that corresponds to the given `pc' value in the process map
+ * `ppm'.
+ */
+
+static void
+pmcpl_ct_node_update(struct pmcpl_ct_node *parent,
+ struct pmcpl_ct_node *child, int pmcin, unsigned v, int cd)
+{
+ struct pmcpl_ct_arc *arc;
+ int i;
+
+ assert(parent != NULL);
+
+ /*
+ * Find related arc in parent node and
+ * increment the sample count.
+ */
+ for (i = 0; i < parent->pct_narc; i++) {
+ if (parent->pct_arc[i].pcta_child == child) {
+ arc = &parent->pct_arc[i];
+ pmcpl_ct_samples_grow(&arc->pcta_samples);
+ arc->pcta_samples.sb[pmcin] += v;
+ /* Estimate call count. */
+ if (cd) {
+ pmcpl_ct_samples_grow(&arc->pcta_callid);
+ if (pmcpl_ct_callid.sb[pmcin] -
+ arc->pcta_callid.sb[pmcin] > 1)
+ arc->pcta_call++;
+ arc->pcta_callid.sb[pmcin] =
+ pmcpl_ct_callid.sb[pmcin];
+ }
+ return;
+ }
+ }
+
+ /*
+ * No arc found for us, add ourself to the parent.
+ */
+ pmcpl_ct_arc_grow(parent->pct_narc,
+ &parent->pct_arc_c, &parent->pct_arc);
+ arc = &parent->pct_arc[parent->pct_narc];
+ pmcpl_ct_samples_grow(&arc->pcta_samples);
+ arc->pcta_samples.sb[pmcin] = v;
+ arc->pcta_call = 1;
+ if (cd) {
+ pmcpl_ct_samples_grow(&arc->pcta_callid);
+ arc->pcta_callid.sb[pmcin] = pmcpl_ct_callid.sb[pmcin];
+ }
+ arc->pcta_child = child;
+ parent->pct_narc++;
+}
+
+/*
+ * Lookup by image/pc.
+ */
+
+static struct pmcpl_ct_node *
+pmcpl_ct_node_hash_lookup(struct pmcstat_image *image, uintfptr_t pc,
+ struct pmcstat_symbol *sym, char *fl, char *fn)
+{
+ int i;
+ unsigned int hash;
+ struct pmcpl_ct_node *ct;
+ struct pmcpl_ct_node_hash *h;
+ pmcstat_interned_string ifl, ifn;
+
+ if (fn != NULL) {
+ ifl = pmcstat_string_intern(fl);
+ ifn = pmcstat_string_intern(fn);
+ } else {
+ ifl = 0;
+ ifn = 0;
+ }
+
+ for (hash = i = 0; i < (int)sizeof(uintfptr_t); i++)
+ hash += (pc >> i) & 0xFF;
+
+ hash &= PMCSTAT_HASH_MASK;
+
+ STAILQ_FOREACH(h, &pmcpl_ct_node_hash[hash], pch_next) {
+ ct = h->pch_ctnode;
+
+ assert(ct != NULL);
+
+ if (ct->pct_image == image && ct->pct_func == pc) {
+ if (fn == NULL)
+ return (ct);
+ if (ct->pct_type == PMCPL_PCT_NAME &&
+ ct->pct_ifl == ifl && ct->pct_ifn == ifn)
+ return (ct);
+ }
+ }
+
+ /*
+ * We haven't seen this (pmcid, pc) tuple yet, so allocate a
+ * new callgraph node and a new hash table entry for it.
+ */
+ ct = pmcpl_ct_node_allocate();
+ if ((h = malloc(sizeof(*h))) == NULL)
+ err(EX_OSERR, "ERROR: Could not allocate callgraph node");
+
+ if (fn != NULL) {
+ ct->pct_type = PMCPL_PCT_NAME;
+ ct->pct_ifl = ifl;
+ ct->pct_ifn = ifn;
+ } else
+ ct->pct_type = PMCPL_PCT_ADDR;
+ ct->pct_image = image;
+ ct->pct_func = pc;
+ ct->pct_sym = sym;
+
+ h->pch_ctnode = ct;
+ STAILQ_INSERT_HEAD(&pmcpl_ct_node_hash[hash], h, pch_next);
+ return (ct);
+}
+
+/*
+ * Record a callchain.
+ */
+
+void
+pmcpl_ct_process(struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr,
+ uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu)
+{
+ int i, n, pmcin;
+ uintfptr_t pc, loadaddress;
+ struct pmcstat_image *image;
+ struct pmcstat_symbol *sym;
+ struct pmcstat_pcmap *ppm[PMC_CALLCHAIN_DEPTH_MAX];
+ struct pmcstat_process *km;
+ struct pmcpl_ct_node *ct;
+ struct pmcpl_ct_node *ctl[PMC_CALLCHAIN_DEPTH_MAX+1];
+
+ (void) cpu;
+
+ assert(nsamples>0 && nsamples<=PMC_CALLCHAIN_DEPTH_MAX);
+
+ /* Get the PMC index. */
+ pmcin = pmcr->pr_pmcin;
+
+ /*
+ * Validate mapping for the callchain.
+ * Go from bottom to first invalid entry.
+ */
+ km = pmcstat_kernproc;
+ for (n = 0; n < (int)nsamples; n++) {
+ ppm[n] = pmcstat_process_find_map(usermode ?
+ pp : km, cc[n]);
+ if (ppm[n] == NULL) {
+ /* Detect full frame capture (kernel + user). */
+ if (!usermode) {
+ ppm[n] = pmcstat_process_find_map(pp, cc[n]);
+ if (ppm[n] != NULL)
+ km = pp;
+ }
+ }
+ if (ppm[n] == NULL)
+ break;
+ }
+ if (n-- == 0) {
+ pmcstat_stats.ps_callchain_dubious_frames++;
+ pmcr->pr_dubious_frames++;
+ return;
+ }
+
+ /* Increase the call generation counter. */
+ pmcpl_ct_samples_grow(&pmcpl_ct_callid);
+ pmcpl_ct_callid.sb[pmcin]++;
+
+ /*
+ * Build node list.
+ */
+ ctl[0] = pmcpl_ct_root;
+ for (i = 1; n >= 0; n--) {
+ image = ppm[n]->ppm_image;
+ loadaddress = ppm[n]->ppm_lowpc +
+ image->pi_vaddr - image->pi_start;
+ /* Convert to an offset in the image. */
+ pc = cc[n] - loadaddress;
+ /*
+ * Try determine the function at this offset. If we can't
+ * find a function round leave the `pc' value alone.
+ */
+ if ((sym = pmcstat_symbol_search(image, pc)) != NULL)
+ pc = sym->ps_start;
+ else
+ pmcstat_stats.ps_samples_unknown_function++;
+
+ ct = pmcpl_ct_node_hash_lookup(image, pc, sym, NULL, NULL);
+ if (ct == NULL) {
+ pmcstat_stats.ps_callchain_dubious_frames++;
+ continue;
+ }
+ ctl[i++] = ct;
+ }
+ /* No valid node found. */
+ if (i == 1)
+ return;
+ n = i;
+
+ ct = ctl[0];
+ for (i = 1; i < n; i++)
+ pmcpl_ct_node_update(ctl[i-1], ctl[i], pmcin, 1, 1);
+
+ /*
+ * Increment the sample count for this PMC.
+ */
+ pmcpl_ct_samples_grow(&ctl[n-1]->pct_samples);
+ ctl[n-1]->pct_samples.sb[pmcin]++;
+
+ /* Update per instruction sample if required. */
+ if (args.pa_ctdumpinstr)
+ pmcpl_ct_instr_add(ctl[n-1], pmcin, cc[0] -
+ (ppm[0]->ppm_lowpc + ppm[0]->ppm_image->pi_vaddr -
+ ppm[0]->ppm_image->pi_start), 1);
+}
+
+/*
+ * Print node child cost.
+ */
+
+static void
+pmcpl_ct_node_printchild(struct pmcpl_ct_node *ct, uintfptr_t paddr,
+ int pline)
+{
+ int i, j, line;
+ uintfptr_t addr;
+ struct pmcpl_ct_node *child;
+ char sourcefile[PATH_MAX];
+ char funcname[PATH_MAX];
+
+ /*
+ * Child cost.
+ * TODO: attach child cost to the real position in the funtion.
+ * TODO: cfn=<fn> / call <ncall> addr(<fn>) / addr(call <fn>) <arccost>
+ */
+ for (i=0 ; i<ct->pct_narc; i++) {
+ child = ct->pct_arc[i].pcta_child;
+ /* Object binary. */
+ fprintf(args.pa_graphfile, "cob=%s\n",
+ pmcstat_string_unintern(child->pct_image->pi_fullpath));
+ /* Child function name. */
+ addr = child->pct_image->pi_vaddr + child->pct_func;
+ line = 0;
+ /* Child function source file. */
+ if (child->pct_type == PMCPL_PCT_NAME) {
+ fprintf(args.pa_graphfile, "cfi=%s\ncfn=%s\n",
+ pmcstat_string_unintern(child->pct_ifl),
+ pmcstat_string_unintern(child->pct_ifn));
+ } else if (pmcstat_image_addr2line(child->pct_image, addr,
+ sourcefile, sizeof(sourcefile), &line,
+ funcname, sizeof(funcname))) {
+ fprintf(args.pa_graphfile, "cfi=%s\ncfn=%s\n",
+ sourcefile, funcname);
+ } else {
+ if (child->pct_sym != NULL)
+ fprintf(args.pa_graphfile,
+ "cfi=???\ncfn=%s\n",
+ pmcstat_string_unintern(
+ child->pct_sym->ps_name));
+ else
+ fprintf(args.pa_graphfile,
+ "cfi=???\ncfn=%p\n", (void *)addr);
+ }
+
+ /* Child function address, line and call count. */
+ fprintf(args.pa_graphfile, "calls=%u %p %u\n",
+ ct->pct_arc[i].pcta_call, (void *)addr, line);
+
+ /*
+ * Call address, line, sample.
+ * TODO: Associate call address to the right location.
+ */
+ fprintf(args.pa_graphfile, "%p %u", (void *)paddr, pline);
+ for (j = 0; j<pmcstat_npmcs; j++)
+ fprintf(args.pa_graphfile, " %u",
+ PMCPL_CT_SAMPLE(j, &ct->pct_arc[i].pcta_samples));
+ fprintf(args.pa_graphfile, "\n");
+ }
+}
+
+/*
+ * Print node self cost.
+ */
+
+static void
+pmcpl_ct_node_printself(struct pmcpl_ct_node *ct)
+{
+ int i, j, fline, line;
+ uintfptr_t faddr, addr;
+ char sourcefile[PATH_MAX];
+ char funcname[PATH_MAX];
+
+ /*
+ * Object binary.
+ */
+ fprintf(args.pa_graphfile, "ob=%s\n",
+ pmcstat_string_unintern(ct->pct_image->pi_fullpath));
+
+ /*
+ * Function name.
+ */
+ faddr = ct->pct_image->pi_vaddr + ct->pct_func;
+ fline = 0;
+ if (ct->pct_type == PMCPL_PCT_NAME) {
+ fprintf(args.pa_graphfile, "fl=%s\nfn=%s\n",
+ pmcstat_string_unintern(ct->pct_ifl),
+ pmcstat_string_unintern(ct->pct_ifn));
+ } else if (pmcstat_image_addr2line(ct->pct_image, faddr,
+ sourcefile, sizeof(sourcefile), &fline,
+ funcname, sizeof(funcname))) {
+ fprintf(args.pa_graphfile, "fl=%s\nfn=%s\n",
+ sourcefile, funcname);
+ } else {
+ if (ct->pct_sym != NULL)
+ fprintf(args.pa_graphfile, "fl=???\nfn=%s\n",
+ pmcstat_string_unintern(ct->pct_sym->ps_name));
+ else
+ fprintf(args.pa_graphfile, "fl=???\nfn=%p\n",
+ (void *)(ct->pct_image->pi_vaddr + ct->pct_func));
+ }
+
+ /*
+ * Self cost.
+ */
+ if (ct->pct_ninstr > 0) {
+ /*
+ * Per location cost.
+ */
+ for (i = 0; i < ct->pct_ninstr; i++) {
+ addr = ct->pct_image->pi_vaddr +
+ ct->pct_instr[i].pctf_func;
+ line = 0;
+ pmcstat_image_addr2line(ct->pct_image, addr,
+ sourcefile, sizeof(sourcefile), &line,
+ funcname, sizeof(funcname));
+ fprintf(args.pa_graphfile, "%p %u",
+ (void *)addr, line);
+ for (j = 0; j<pmcstat_npmcs; j++)
+ fprintf(args.pa_graphfile, " %u",
+ PMCPL_CT_SAMPLE(j,
+ &ct->pct_instr[i].pctf_samples));
+ fprintf(args.pa_graphfile, "\n");
+ }
+ } else {
+ /* Global cost function cost. */
+ fprintf(args.pa_graphfile, "%p %u", (void *)faddr, fline);
+ for (i = 0; i<pmcstat_npmcs ; i++)
+ fprintf(args.pa_graphfile, " %u",
+ PMCPL_CT_SAMPLE(i, &ct->pct_samples));
+ fprintf(args.pa_graphfile, "\n");
+ }
+
+ pmcpl_ct_node_printchild(ct, faddr, fline);
+}
+
+static void
+pmcpl_ct_printnode(struct pmcpl_ct_node *ct)
+{
+ int i;
+
+ if (ct == pmcpl_ct_root) {
+ fprintf(args.pa_graphfile, "fn=root\n");
+ fprintf(args.pa_graphfile, "0x0 1");
+ for (i = 0; i<pmcstat_npmcs ; i++)
+ fprintf(args.pa_graphfile, " 0");
+ fprintf(args.pa_graphfile, "\n");
+ pmcpl_ct_node_printchild(ct, 0, 0);
+ } else
+ pmcpl_ct_node_printself(ct);
+}
+
+/*
+ * Breadth first traversal.
+ */
+
+static void
+pmcpl_ct_bfs(struct pmcpl_ct_node *ct)
+{
+ int i;
+ struct pmcpl_ct_node_hash *pch, *pchc;
+ struct pmcpl_ct_node *child;
+ STAILQ_HEAD(,pmcpl_ct_node_hash) q;
+
+ STAILQ_INIT(&q);
+ if ((pch = malloc(sizeof(*pch))) == NULL)
+ err(EX_OSERR, "ERROR: Cannot allocate queue");
+ pch->pch_ctnode = ct;
+ STAILQ_INSERT_TAIL(&q, pch, pch_next);
+ ct->pct_color = PMCPL_PCT_BLACK;
+
+ while (!STAILQ_EMPTY(&q)) {
+ pch = STAILQ_FIRST(&q);
+ STAILQ_REMOVE_HEAD(&q, pch_next);
+ pmcpl_ct_printnode(pch->pch_ctnode);
+ for (i = 0; i<pch->pch_ctnode->pct_narc; i++) {
+ child = pch->pch_ctnode->pct_arc[i].pcta_child;
+ if (child->pct_color == PMCPL_PCT_WHITE) {
+ child->pct_color = PMCPL_PCT_BLACK;
+ if ((pchc = malloc(sizeof(*pchc))) == NULL)
+ err(EX_OSERR,
+ "ERROR: Cannot allocate queue");
+ pchc->pch_ctnode = child;
+ STAILQ_INSERT_TAIL(&q, pchc, pch_next);
+ }
+ }
+ free(pch);
+ }
+}
+
+/*
+ * Detect and fix inlined location.
+ */
+
+static void
+_pmcpl_ct_expand_inline(struct pmcpl_ct_node *ct)
+{
+ int i, j;
+ unsigned fline, line, v;
+ uintfptr_t faddr, addr, pc;
+ char sourcefile[PATH_MAX];
+ char ffuncname[PATH_MAX], funcname[PATH_MAX];
+ char buffer[PATH_MAX];
+ struct pmcpl_ct_node *child;
+
+ /*
+ * Resolve parent and compare to each instr location.
+ */
+ faddr = ct->pct_image->pi_vaddr + ct->pct_func;
+ fline = 0;
+ if (!pmcstat_image_addr2line(ct->pct_image, faddr,
+ sourcefile, sizeof(sourcefile), &fline,
+ ffuncname, sizeof(ffuncname)))
+ return;
+
+ for (i = 0; i < ct->pct_ninstr; i++) {
+ addr = ct->pct_image->pi_vaddr +
+ ct->pct_instr[i].pctf_func;
+ line = 0;
+ if (!pmcstat_image_addr2line(ct->pct_image, addr,
+ sourcefile, sizeof(sourcefile), &line,
+ funcname, sizeof(funcname)))
+ continue;
+
+ if (strcmp(funcname, ffuncname) == 0)
+ continue;
+
+ /*
+ * - Lookup/create inline node by function name.
+ * - Move instr PMCs to the inline node.
+ * - Link nodes.
+ * The lookup create a specific node per image/pc.
+ */
+ if (args.pa_verbosity >= 2)
+ fprintf(args.pa_printfile,
+ "WARNING: inlined function at %p %s in %s\n",
+ (void *)addr, funcname, ffuncname);
+
+ snprintf(buffer, sizeof(buffer), "%s@%s",
+ funcname, ffuncname);
+ child = pmcpl_ct_node_hash_lookup(ct->pct_image,
+ ct->pct_func, ct->pct_sym, sourcefile, buffer);
+ assert(child != NULL);
+ pc = ct->pct_instr[i].pctf_func;
+ for (j = 0; j<pmcstat_npmcs; j++) {
+ v = PMCPL_CT_SAMPLE(j,
+ &ct->pct_instr[i].pctf_samples);
+ if (v == 0)
+ continue;
+ pmcpl_ct_instr_add(child, j, pc, v);
+ pmcpl_ct_node_update(ct, child, j, v, 0);
+ if (j < ct->pct_samples.npmcs)
+ ct->pct_samples.sb[j] -=
+ ct->pct_instr[i].pctf_samples.sb[j];
+ ct->pct_instr[i].pctf_samples.sb[j] = 0;
+ }
+ }
+}
+
+static void
+pmcpl_ct_expand_inline(void)
+{
+ int i;
+ struct pmcpl_ct_node_hash *pch;
+
+ if (!args.pa_ctdumpinstr)
+ return;
+
+ for (i = 0; i < PMCSTAT_NHASH; i++)
+ STAILQ_FOREACH(pch, &pmcpl_ct_node_hash[i], pch_next)
+ if (pch->pch_ctnode->pct_type == PMCPL_PCT_ADDR)
+ _pmcpl_ct_expand_inline(pch->pch_ctnode);
+}
+
+/*
+ * Clean the PMC name for Kcachegrind formula
+ */
+
+static void
+pmcpl_ct_fixup_pmcname(char *s)
+{
+ char *p;
+
+ for (p = s; *p; p++)
+ if (!isalnum(*p))
+ *p = '_';
+}
+
+/*
+ * Print a calltree (KCachegrind) for all PMCs.
+ */
+
+static void
+pmcpl_ct_print(void)
+{
+ int i;
+ char name[40];
+ struct pmcpl_ct_sample rsamples;
+
+ pmcpl_ct_samples_root(&rsamples);
+ pmcpl_ct_expand_inline();
+
+ fprintf(args.pa_graphfile,
+ "version: 1\n"
+ "creator: pmcstat\n"
+ "positions: instr line\n"
+ "events:");
+ for (i=0; i<pmcstat_npmcs; i++) {
+ snprintf(name, sizeof(name), "%s_%d",
+ pmcstat_pmcindex_to_name(i), i);
+ pmcpl_ct_fixup_pmcname(name);
+ fprintf(args.pa_graphfile, " %s", name);
+ }
+ fprintf(args.pa_graphfile, "\nsummary:");
+ for (i=0; i<pmcstat_npmcs ; i++)
+ fprintf(args.pa_graphfile, " %u",
+ PMCPL_CT_SAMPLE(i, &rsamples));
+ fprintf(args.pa_graphfile, "\n");
+ pmcpl_ct_bfs(pmcpl_ct_root);
+ pmcpl_ct_samples_free(&rsamples);
+}
+
+int
+pmcpl_ct_configure(char *opt)
+{
+
+ if (strncmp(opt, "skiplink=", 9) == 0) {
+ pmcstat_skiplink = atoi(opt+9);
+ } else
+ return (0);
+
+ return (1);
+}
+
+int
+pmcpl_ct_init(void)
+{
+ int i;
+
+ pmcpl_ct_root = pmcpl_ct_node_allocate();
+
+ for (i = 0; i < PMCSTAT_NHASH; i++)
+ STAILQ_INIT(&pmcpl_ct_node_hash[i]);
+
+ pmcpl_ct_samples_init(&pmcpl_ct_callid);
+
+ return (0);
+}
+
+void
+pmcpl_ct_shutdown(FILE *mf)
+{
+ int i;
+ struct pmcpl_ct_node_hash *pch, *pchtmp;
+
+ (void) mf;
+
+ if (args.pa_flags & FLAG_DO_CALLGRAPHS)
+ pmcpl_ct_print();
+
+ /*
+ * Free memory.
+ */
+
+ for (i = 0; i < PMCSTAT_NHASH; i++) {
+ STAILQ_FOREACH_SAFE(pch, &pmcpl_ct_node_hash[i], pch_next,
+ pchtmp) {
+ pmcpl_ct_node_free(pch->pch_ctnode);
+ free(pch);
+ }
+ }
+
+ pmcpl_ct_node_free(pmcpl_ct_root);
+ pmcpl_ct_root = NULL;
+
+ pmcpl_ct_samples_free(&pmcpl_ct_callid);
+}
+
diff --git a/usr.sbin/pmcstat/pmcpl_calltree.h b/usr.sbin/pmcstat/pmcpl_calltree.h
new file mode 100644
index 0000000..f54957f
--- /dev/null
+++ b/usr.sbin/pmcstat/pmcpl_calltree.h
@@ -0,0 +1,42 @@
+/*-
+ * Copyright (c) 2009, Fabien Thomas
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 _PMCSTAT_PL_CALLTREE_H_
+#define _PMCSTAT_PL_CALLTREE_H_
+
+/* Function prototypes */
+int pmcpl_ct_init(void);
+void pmcpl_ct_shutdown(FILE *mf);
+void pmcpl_ct_process(
+ struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr,
+ uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu);
+int pmcpl_ct_topkeypress(int c, WINDOW *w);
+void pmcpl_ct_topdisplay(void);
+int pmcpl_ct_configure(char *opt);
+
+#endif /* _PMCSTAT_PL_CALLTREE_H_ */
diff --git a/usr.sbin/pmcstat/pmcpl_gprof.c b/usr.sbin/pmcstat/pmcpl_gprof.c
new file mode 100644
index 0000000..5fc9b41
--- /dev/null
+++ b/usr.sbin/pmcstat/pmcpl_gprof.c
@@ -0,0 +1,566 @@
+/*-
+ * Copyright (c) 2005-2007, Joseph Koshy
+ * Copyright (c) 2007 The FreeBSD Foundation
+ * Copyright (c) 2009, Fabien Thomas
+ * All rights reserved.
+ *
+ * Portions of this software were developed by A. Joseph Koshy under
+ * sponsorship from the FreeBSD Foundation and Google, 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.
+ *
+ * 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.
+ */
+
+/*
+ * Transform a hwpmc(4) log into human readable form, and into
+ * gprof(1) compatible profiles.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/endian.h>
+#include <sys/gmon.h>
+#include <sys/imgact_aout.h>
+#include <sys/imgact_elf.h>
+#include <sys/mman.h>
+#include <sys/pmc.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <netinet/in.h>
+
+#include <assert.h>
+#include <curses.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <gelf.h>
+#include <libgen.h>
+#include <limits.h>
+#include <netdb.h>
+#include <pmc.h>
+#include <pmclog.h>
+#include <sysexits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "pmcstat.h"
+#include "pmcstat_log.h"
+#include "pmcpl_callgraph.h"
+#include "pmcpl_gprof.h"
+
+typedef uint64_t WIDEHISTCOUNTER;
+
+#define WIDEHISTCOUNTER_MAX UINT64_MAX
+#define HISTCOUNTER_MAX USHRT_MAX
+#define WIDEHISTCOUNTER_GMONTYPE ((int) 64)
+#define HISTCOUNTER_GMONTYPE ((int) 0)
+static int hc_sz=0;
+
+/*
+ * struct pmcstat_gmonfile tracks a given 'gmon.out' file. These
+ * files are mmap()'ed in as needed.
+ */
+
+struct pmcstat_gmonfile {
+ LIST_ENTRY(pmcstat_gmonfile) pgf_next; /* list of entries */
+ int pgf_overflow; /* whether a count overflowed */
+ pmc_id_t pgf_pmcid; /* id of the associated pmc */
+ size_t pgf_nbuckets; /* #buckets in this gmon.out */
+ unsigned int pgf_nsamples; /* #samples in this gmon.out */
+ pmcstat_interned_string pgf_name; /* pathname of gmon.out file */
+ size_t pgf_ndatabytes; /* number of bytes mapped */
+ void *pgf_gmondata; /* pointer to mmap'ed data */
+ FILE *pgf_file; /* used when writing gmon arcs */
+};
+
+/*
+ * Prototypes
+ */
+
+static void pmcstat_gmon_create_file(struct pmcstat_gmonfile *_pgf,
+ struct pmcstat_image *_image);
+static pmcstat_interned_string pmcstat_gmon_create_name(const char *_sd,
+ struct pmcstat_image *_img, pmc_id_t _pmcid);
+static void pmcstat_gmon_map_file(struct pmcstat_gmonfile *_pgf);
+static void pmcstat_gmon_unmap_file(struct pmcstat_gmonfile *_pgf);
+
+static struct pmcstat_gmonfile *pmcstat_image_find_gmonfile(struct
+ pmcstat_image *_i, pmc_id_t _id);
+
+/*
+ * Create a gmon.out file and size it.
+ */
+
+static void
+pmcstat_gmon_create_file(struct pmcstat_gmonfile *pgf,
+ struct pmcstat_image *image)
+{
+ int fd;
+ size_t count;
+ struct gmonhdr gm;
+ const char *pathname;
+ char buffer[DEFAULT_BUFFER_SIZE];
+
+ pathname = pmcstat_string_unintern(pgf->pgf_name);
+ if ((fd = open(pathname, O_RDWR|O_NOFOLLOW|O_CREAT,
+ S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) < 0)
+ err(EX_OSERR, "ERROR: Cannot open \"%s\"", pathname);
+
+ gm.lpc = image->pi_start;
+ gm.hpc = image->pi_end;
+ gm.ncnt = (pgf->pgf_nbuckets * hc_sz) + sizeof(struct gmonhdr);
+ gm.version = GMONVERSION;
+ gm.profrate = 0; /* use ticks */
+ if (args.pa_flags & FLAG_DO_WIDE_GPROF_HC)
+ gm.histcounter_type = WIDEHISTCOUNTER_GMONTYPE;
+ else
+ gm.histcounter_type = HISTCOUNTER_GMONTYPE;
+ gm.spare[0] = gm.spare[1] = 0;
+
+ /* Write out the gmon header */
+ if (write(fd, &gm, sizeof(gm)) < 0)
+ goto error;
+
+ /* Zero fill the samples[] array */
+ (void) memset(buffer, 0, sizeof(buffer));
+
+ count = pgf->pgf_ndatabytes - sizeof(struct gmonhdr);
+ while (count > sizeof(buffer)) {
+ if (write(fd, &buffer, sizeof(buffer)) < 0)
+ goto error;
+ count -= sizeof(buffer);
+ }
+
+ if (write(fd, &buffer, count) < 0)
+ goto error;
+
+ (void) close(fd);
+
+ return;
+
+ error:
+ err(EX_OSERR, "ERROR: Cannot write \"%s\"", pathname);
+}
+
+/*
+ * Determine the full pathname of a gmon.out file for a given
+ * (image,pmcid) combination. Return the interned string.
+ */
+
+pmcstat_interned_string
+pmcstat_gmon_create_name(const char *samplesdir, struct pmcstat_image *image,
+ pmc_id_t pmcid)
+{
+ const char *pmcname;
+ char fullpath[PATH_MAX];
+
+ pmcname = pmcstat_pmcid_to_name(pmcid);
+ if (!pmcname)
+ err(EX_SOFTWARE, "ERROR: cannot find pmcid");
+
+ (void) snprintf(fullpath, sizeof(fullpath),
+ "%s/%s/%s", samplesdir, pmcname,
+ pmcstat_string_unintern(image->pi_samplename));
+
+ return (pmcstat_string_intern(fullpath));
+}
+
+
+/*
+ * Mmap in a gmon.out file for processing.
+ */
+
+static void
+pmcstat_gmon_map_file(struct pmcstat_gmonfile *pgf)
+{
+ int fd;
+ const char *pathname;
+
+ pathname = pmcstat_string_unintern(pgf->pgf_name);
+
+ /* the gmon.out file must already exist */
+ if ((fd = open(pathname, O_RDWR | O_NOFOLLOW, 0)) < 0)
+ err(EX_OSERR, "ERROR: cannot open \"%s\"", pathname);
+
+ pgf->pgf_gmondata = mmap(NULL, pgf->pgf_ndatabytes,
+ PROT_READ|PROT_WRITE, MAP_NOSYNC|MAP_SHARED, fd, 0);
+
+ if (pgf->pgf_gmondata == MAP_FAILED)
+ err(EX_OSERR, "ERROR: cannot map \"%s\"", pathname);
+
+ (void) close(fd);
+}
+
+/*
+ * Unmap a gmon.out file after sync'ing its data to disk.
+ */
+
+static void
+pmcstat_gmon_unmap_file(struct pmcstat_gmonfile *pgf)
+{
+ (void) msync(pgf->pgf_gmondata, pgf->pgf_ndatabytes,
+ MS_SYNC);
+ (void) munmap(pgf->pgf_gmondata, pgf->pgf_ndatabytes);
+ pgf->pgf_gmondata = NULL;
+}
+
+static void
+pmcstat_gmon_append_arc(struct pmcstat_image *image, pmc_id_t pmcid,
+ uintptr_t rawfrom, uintptr_t rawto, uint32_t count)
+{
+ struct rawarc arc; /* from <sys/gmon.h> */
+ const char *pathname;
+ struct pmcstat_gmonfile *pgf;
+
+ if ((pgf = pmcstat_image_find_gmonfile(image, pmcid)) == NULL)
+ return;
+
+ if (pgf->pgf_file == NULL) {
+ pathname = pmcstat_string_unintern(pgf->pgf_name);
+ if ((pgf->pgf_file = fopen(pathname, "a")) == NULL)
+ return;
+ }
+
+ arc.raw_frompc = rawfrom + image->pi_vaddr;
+ arc.raw_selfpc = rawto + image->pi_vaddr;
+ arc.raw_count = count;
+
+ (void) fwrite(&arc, sizeof(arc), 1, pgf->pgf_file);
+
+}
+
+static struct pmcstat_gmonfile *
+pmcstat_image_find_gmonfile(struct pmcstat_image *image, pmc_id_t pmcid)
+{
+ struct pmcstat_gmonfile *pgf;
+ LIST_FOREACH(pgf, &image->pi_gmlist, pgf_next)
+ if (pgf->pgf_pmcid == pmcid)
+ return (pgf);
+ return (NULL);
+}
+
+static void
+pmcstat_cgnode_do_gmon_arcs(struct pmcstat_cgnode *cg, pmc_id_t pmcid)
+{
+ struct pmcstat_cgnode *cgc;
+
+ /*
+ * Look for child nodes that belong to the same image.
+ */
+
+ LIST_FOREACH(cgc, &cg->pcg_children, pcg_sibling) {
+ if (cgc->pcg_image == cg->pcg_image)
+ pmcstat_gmon_append_arc(cg->pcg_image, pmcid,
+ cgc->pcg_func, cg->pcg_func, cgc->pcg_count);
+ if (cgc->pcg_nchildren > 0)
+ pmcstat_cgnode_do_gmon_arcs(cgc, pmcid);
+ }
+}
+
+static void
+pmcstat_callgraph_do_gmon_arcs_for_pmcid(pmc_id_t pmcid)
+{
+ int n;
+ struct pmcstat_cgnode_hash *pch;
+
+ for (n = 0; n < PMCSTAT_NHASH; n++)
+ LIST_FOREACH(pch, &pmcstat_cgnode_hash[n], pch_next)
+ if (pch->pch_pmcid == pmcid &&
+ pch->pch_cgnode->pcg_nchildren > 1)
+ pmcstat_cgnode_do_gmon_arcs(pch->pch_cgnode,
+ pmcid);
+}
+
+
+static void
+pmcstat_callgraph_do_gmon_arcs(void)
+{
+ struct pmcstat_pmcrecord *pmcr;
+
+ LIST_FOREACH(pmcr, &pmcstat_pmcs, pr_next)
+ pmcstat_callgraph_do_gmon_arcs_for_pmcid(pmcr->pr_pmcid);
+}
+
+void
+pmcpl_gmon_initimage(struct pmcstat_image *pi)
+{
+ int count, nlen;
+ char *sn;
+ char name[NAME_MAX];
+
+ /*
+ * Look for a suitable name for the sample files associated
+ * with this image: if `basename(path)`+".gmon" is available,
+ * we use that, otherwise we try iterating through
+ * `basename(path)`+ "~" + NNN + ".gmon" till we get a free
+ * entry.
+ */
+ if ((sn = basename(pmcstat_string_unintern(pi->pi_execpath))) == NULL)
+ err(EX_OSERR, "ERROR: Cannot process \"%s\"",
+ pmcstat_string_unintern(pi->pi_execpath));
+
+ nlen = strlen(sn);
+ nlen = min(nlen, (int) (sizeof(name) - sizeof(".gmon")));
+
+ snprintf(name, sizeof(name), "%.*s.gmon", nlen, sn);
+
+ /* try use the unabridged name first */
+ if (pmcstat_string_lookup(name) == NULL)
+ pi->pi_samplename = pmcstat_string_intern(name);
+ else {
+ /*
+ * Otherwise use a prefix from the original name and
+ * up to 3 digits.
+ */
+ nlen = strlen(sn);
+ nlen = min(nlen, (int) (sizeof(name)-sizeof("~NNN.gmon")));
+ count = 0;
+ do {
+ if (++count > 999)
+ errx(EX_CANTCREAT,
+ "ERROR: cannot create a gmon file for"
+ " \"%s\"", name);
+ snprintf(name, sizeof(name), "%.*s~%3.3d.gmon",
+ nlen, sn, count);
+ if (pmcstat_string_lookup(name) == NULL) {
+ pi->pi_samplename =
+ pmcstat_string_intern(name);
+ count = 0;
+ }
+ } while (count > 0);
+ }
+
+ LIST_INIT(&pi->pi_gmlist);
+}
+
+void
+pmcpl_gmon_shutdownimage(struct pmcstat_image *pi)
+{
+ struct pmcstat_gmonfile *pgf, *pgftmp;
+
+ LIST_FOREACH_SAFE(pgf, &pi->pi_gmlist, pgf_next, pgftmp) {
+ if (pgf->pgf_file)
+ (void) fclose(pgf->pgf_file);
+ LIST_REMOVE(pgf, pgf_next);
+ free(pgf);
+ }
+}
+
+void
+pmcpl_gmon_newpmc(pmcstat_interned_string ps, struct pmcstat_pmcrecord *pr)
+{
+ struct stat st;
+ char fullpath[PATH_MAX];
+
+ (void) pr;
+
+ /*
+ * Create the appropriate directory to hold gmon.out files.
+ */
+
+ (void) snprintf(fullpath, sizeof(fullpath), "%s/%s", args.pa_samplesdir,
+ pmcstat_string_unintern(ps));
+
+ /* If the path name exists, it should be a directory */
+ if (stat(fullpath, &st) == 0 && S_ISDIR(st.st_mode))
+ return;
+
+ if (mkdir(fullpath, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) < 0)
+ err(EX_OSERR, "ERROR: Cannot create directory \"%s\"",
+ fullpath);
+}
+
+/*
+ * Increment the bucket in the gmon.out file corresponding to 'pmcid'
+ * and 'pc'.
+ */
+
+void
+pmcpl_gmon_process(struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr,
+ uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu)
+{
+ struct pmcstat_pcmap *map;
+ struct pmcstat_image *image;
+ struct pmcstat_gmonfile *pgf;
+ uintfptr_t bucket;
+ HISTCOUNTER *hc;
+ WIDEHISTCOUNTER *whc;
+ pmc_id_t pmcid;
+
+ (void) nsamples; (void) usermode; (void) cpu;
+
+ map = pmcstat_process_find_map(usermode ? pp : pmcstat_kernproc, cc[0]);
+ if (map == NULL) {
+ /* Unknown offset. */
+ pmcstat_stats.ps_samples_unknown_offset++;
+ return;
+ }
+
+ assert(cc[0] >= map->ppm_lowpc && cc[0] < map->ppm_highpc);
+
+ image = map->ppm_image;
+ pmcid = pmcr->pr_pmcid;
+
+ /*
+ * If this is the first time we are seeing a sample for
+ * this executable image, try determine its parameters.
+ */
+ if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN)
+ pmcstat_image_determine_type(image);
+
+ assert(image->pi_type != PMCSTAT_IMAGE_UNKNOWN);
+
+ /* Ignore samples in images that we know nothing about. */
+ if (image->pi_type == PMCSTAT_IMAGE_INDETERMINABLE) {
+ pmcstat_stats.ps_samples_indeterminable++;
+ return;
+ }
+
+ /*
+ * Find the gmon file corresponding to 'pmcid', creating it if
+ * needed.
+ */
+ pgf = pmcstat_image_find_gmonfile(image, pmcid);
+ if (pgf == NULL) {
+ if (hc_sz == 0) {
+ /* Determine the correct histcounter size. */
+ if (args.pa_flags & FLAG_DO_WIDE_GPROF_HC)
+ hc_sz = sizeof(WIDEHISTCOUNTER);
+ else
+ hc_sz = sizeof(HISTCOUNTER);
+ }
+
+ if ((pgf = calloc(1, sizeof(*pgf))) == NULL)
+ err(EX_OSERR, "ERROR:");
+
+ pgf->pgf_gmondata = NULL; /* mark as unmapped */
+ pgf->pgf_name = pmcstat_gmon_create_name(args.pa_samplesdir,
+ image, pmcid);
+ pgf->pgf_pmcid = pmcid;
+ assert(image->pi_end > image->pi_start);
+ pgf->pgf_nbuckets = (image->pi_end - image->pi_start) /
+ FUNCTION_ALIGNMENT; /* see <machine/profile.h> */
+ pgf->pgf_ndatabytes = sizeof(struct gmonhdr) +
+ pgf->pgf_nbuckets * hc_sz;
+ pgf->pgf_nsamples = 0;
+ pgf->pgf_file = NULL;
+
+ pmcstat_gmon_create_file(pgf, image);
+
+ LIST_INSERT_HEAD(&image->pi_gmlist, pgf, pgf_next);
+ }
+
+ /*
+ * Map the gmon file in if needed. It may have been mapped
+ * out under memory pressure.
+ */
+ if (pgf->pgf_gmondata == NULL)
+ pmcstat_gmon_map_file(pgf);
+
+ assert(pgf->pgf_gmondata != NULL);
+
+ /*
+ *
+ */
+
+ bucket = (cc[0] - map->ppm_lowpc) / FUNCTION_ALIGNMENT;
+
+ assert(bucket < pgf->pgf_nbuckets);
+
+ if (args.pa_flags & FLAG_DO_WIDE_GPROF_HC) {
+ whc = (WIDEHISTCOUNTER *) ((uintptr_t) pgf->pgf_gmondata +
+ sizeof(struct gmonhdr));
+
+ /* saturating add */
+ if (whc[bucket] < WIDEHISTCOUNTER_MAX)
+ whc[bucket]++;
+ else /* mark that an overflow occurred */
+ pgf->pgf_overflow = 1;
+ } else {
+ hc = (HISTCOUNTER *) ((uintptr_t) pgf->pgf_gmondata +
+ sizeof(struct gmonhdr));
+
+ /* saturating add */
+ if (hc[bucket] < HISTCOUNTER_MAX)
+ hc[bucket]++;
+ else /* mark that an overflow occurred */
+ pgf->pgf_overflow = 1;
+ }
+
+ pgf->pgf_nsamples++;
+}
+
+/*
+ * Shutdown module.
+ */
+
+void
+pmcpl_gmon_shutdown(FILE *mf)
+{
+ int i;
+ struct pmcstat_gmonfile *pgf;
+ struct pmcstat_image *pi;
+
+ /*
+ * Sync back all gprof flat profile data.
+ */
+ for (i = 0; i < PMCSTAT_NHASH; i++) {
+ LIST_FOREACH(pi, &pmcstat_image_hash[i], pi_next) {
+ if (mf)
+ (void) fprintf(mf, " \"%s\" => \"%s\"",
+ pmcstat_string_unintern(pi->pi_execpath),
+ pmcstat_string_unintern(
+ pi->pi_samplename));
+
+ /* flush gmon.out data to disk */
+ LIST_FOREACH(pgf, &pi->pi_gmlist, pgf_next) {
+ pmcstat_gmon_unmap_file(pgf);
+ if (mf)
+ (void) fprintf(mf, " %s/%d",
+ pmcstat_pmcid_to_name(
+ pgf->pgf_pmcid),
+ pgf->pgf_nsamples);
+ if (pgf->pgf_overflow && args.pa_verbosity >= 1)
+ warnx(
+"WARNING: profile \"%s\" overflowed.",
+ pmcstat_string_unintern(
+ pgf->pgf_name));
+ }
+
+ if (mf)
+ (void) fprintf(mf, "\n");
+ }
+ }
+
+ /*
+ * Compute arcs and add these to the gprof files.
+ */
+ if (args.pa_flags & FLAG_DO_GPROF && args.pa_graphdepth > 1)
+ pmcstat_callgraph_do_gmon_arcs();
+}
diff --git a/usr.sbin/pmcstat/pmcpl_gprof.h b/usr.sbin/pmcstat/pmcpl_gprof.h
new file mode 100644
index 0000000..069082f
--- /dev/null
+++ b/usr.sbin/pmcstat/pmcpl_gprof.h
@@ -0,0 +1,47 @@
+/*-
+ * Copyright (c) 2005-2007, Joseph Koshy
+ * Copyright (c) 2007 The FreeBSD Foundation
+ * Copyright (c) 2009, Fabien Thomas
+ * All rights reserved.
+ *
+ * Portions of this software were developed by A. Joseph Koshy under
+ * sponsorship from the FreeBSD Foundation and Google, 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.
+ *
+ * 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 _PMCSTAT_PL_GPROF_H_
+#define _PMCSTAT_PL_GPROF_H_
+
+/* Function prototypes */
+void pmcpl_gmon_shutdown(FILE *mf);
+void pmcpl_gmon_process(
+ struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr,
+ uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu);
+void pmcpl_gmon_initimage(struct pmcstat_image *pi);
+void pmcpl_gmon_shutdownimage(struct pmcstat_image *pi);
+void pmcpl_gmon_newpmc(pmcstat_interned_string ps,
+ struct pmcstat_pmcrecord *pr);
+
+#endif /* _PMCSTAT_PL_GPROF_H_ */
diff --git a/usr.sbin/pmcstat/pmcstat.8 b/usr.sbin/pmcstat/pmcstat.8
new file mode 100644
index 0000000..bc4bb74
--- /dev/null
+++ b/usr.sbin/pmcstat/pmcstat.8
@@ -0,0 +1,500 @@
+.\" Copyright (c) 2003-2008 Joseph Koshy
+.\" Copyright (c) 2007 The FreeBSD Foundation
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 Joseph Koshy ``as is'' and
+.\" any express or implied warranties, including, but not limited to, the
+.\" implied warranties of merchantability and fitness for a particular purpose
+.\" are disclaimed. in no event shall Joseph Koshy be liable
+.\" for any direct, indirect, incidental, special, exemplary, or consequential
+.\" damages (including, but not limited to, procurement of substitute goods
+.\" or services; loss of use, data, or profits; or business interruption)
+.\" however caused and on any theory of liability, whether in contract, strict
+.\" liability, or tort (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 November 18, 2015
+.Dt PMCSTAT 8
+.Os
+.Sh NAME
+.Nm pmcstat
+.Nd "performance measurement with performance monitoring hardware"
+.Sh SYNOPSIS
+.Nm
+.Op Fl C
+.Op Fl D Ar pathname
+.Op Fl E
+.Op Fl F Ar pathname
+.Op Fl G Ar pathname
+.Op Fl M Ar mapfilename
+.Op Fl N
+.Op Fl O Ar logfilename
+.Op Fl P Ar event-spec
+.Op Fl R Ar logfilename
+.Op Fl S Ar event-spec
+.Op Fl T
+.Op Fl W
+.Op Fl a Ar pathname
+.Op Fl c Ar cpu-spec
+.Op Fl d
+.Op Fl e
+.Op Fl f Ar pluginopt
+.Op Fl g
+.Op Fl k Ar kerneldir
+.Op Fl l Ar secs
+.Op Fl m Ar pathname
+.Op Fl n Ar rate
+.Op Fl o Ar outputfile
+.Op Fl p Ar event-spec
+.Op Fl q
+.Op Fl r Ar fsroot
+.Op Fl s Ar event-spec
+.Op Fl t Ar process-spec
+.Op Fl v
+.Op Fl w Ar secs
+.Op Fl z Ar graphdepth
+.Op Ar command Op Ar args
+.Sh DESCRIPTION
+The
+.Nm
+utility measures system performance using the facilities provided by
+.Xr hwpmc 4 .
+.Pp
+The
+.Nm
+utility can measure both hardware events seen by the system as a
+whole, and those seen when a specified set of processes are executing
+on the system's CPUs.
+If a specific set of processes is being targeted (for example,
+if the
+.Fl t Ar process-spec
+option is specified, or if a command line is specified using
+.Ar command ) ,
+then measurement occurs till
+.Ar command
+exits, or till all target processes specified by the
+.Fl t Ar process-spec
+options exit, or till the
+.Nm
+utility is interrupted by the user.
+If a specific set of processes is not targeted for measurement, then
+.Nm
+will perform system-wide measurements till interrupted by the
+user.
+.Pp
+A given invocation of
+.Nm
+can mix allocations of system-mode and process-mode PMCs, of both
+counting and sampling flavors.
+The values of all counting PMCs are printed in human readable form
+at regular intervals by
+.Nm .
+The output of sampling PMCs may be configured to go to a log file for
+subsequent offline analysis, or, at the expense of greater
+overhead, may be configured to be printed in text form on the fly.
+.Pp
+Hardware events to measure are specified to
+.Nm
+using event specifier strings
+.Ar event-spec .
+The syntax of these event specifiers is machine dependent and is
+documented in
+.Xr pmc 3 .
+.Pp
+A process-mode PMC may be configured to be inheritable by the target
+process' current and future children.
+.Sh OPTIONS
+The following options are available:
+.Bl -tag -width indent
+.It Fl C
+Toggle between showing cumulative or incremental counts for
+subsequent counting mode PMCs specified on the command line.
+The default is to show incremental counts.
+.It Fl D Ar pathname
+Create files with per-program samples in the directory named
+by
+.Ar pathname .
+The default is to create these files in the current directory.
+.It Fl E
+Toggle showing per-process counts at the time a tracked process
+exits for subsequent process-mode PMCs specified on the command line.
+This option is useful for mapping the performance characteristics of a
+complex pipeline of processes when used in conjunction with the
+.Fl d
+option.
+The default is to not to enable per-process tracking.
+.It Fl F Ar pathname
+Print calltree (Kcachegrind) information to file
+.Ar pathname .
+If argument
+.Ar pathname
+is a
+.Dq Li -
+this information is sent to the output file specified by the
+.Fl o
+option.
+.It Fl G Ar pathname
+Print callchain information to file
+.Ar pathname .
+If argument
+.Ar pathname
+is a
+.Dq Li -
+this information is sent to the output file specified by the
+.Fl o
+option.
+.It Fl M Ar mapfilename
+Write the mapping between executable objects encountered in the event
+log and the abbreviated pathnames used for
+.Xr gprof 1
+profiles to file
+.Ar mapfilename .
+If this option is not specified, mapping information is not written.
+Argument
+.Ar mapfilename
+may be a
+.Dq Li -
+in which case this mapping information is sent to the output
+file configured by the
+.Fl o
+option.
+.It Fl N
+Toggle capturing callchain information for subsequent sampling PMCs.
+The default is for sampling PMCs to capture callchain information.
+.It Fl O Ar logfilename
+Send logging output to file
+.Ar logfilename .
+If
+.Ar logfilename
+is of the form
+.Ar hostname Ns : Ns Ar port ,
+where
+.Ar hostname
+does not start with a
+.Ql \&.
+or a
+.Ql / ,
+then
+.Nm
+will open a network socket to host
+.Ar hostname
+on port
+.Ar port .
+.Pp
+If the
+.Fl O
+option is not specified and one of the logging options is requested,
+then
+.Nm
+will print a textual form of the logged events to the configured
+output file.
+.It Fl P Ar event-spec
+Allocate a process mode sampling PMC measuring hardware events
+specified in
+.Ar event-spec .
+.It Fl R Ar logfilename
+Perform offline analysis using sampling data in file
+.Ar logfilename .
+.It Fl S Ar event-spec
+Allocate a system mode sampling PMC measuring hardware events
+specified in
+.Ar event-spec .
+.It Fl T
+Use a top like mode for sampling PMCs. The following hotkeys
+can be used: 'c+a' switch to accumulative mode, 'c+d' switch
+to delta mode, 'm' merge PMCs, 'n' change view, 'p' show next
+PMC, ' ' pause, 'q' quit. calltree only: 'f' cost under threshold
+is seen as a dot.
+.It Fl W
+Toggle logging the incremental counts seen by the threads of a
+tracked process each time they are scheduled on a CPU.
+This is an experimental feature intended to help analyse the
+dynamic behaviour of processes in the system.
+It may incur substantial overhead if enabled.
+The default is for this feature to be disabled.
+.It Fl a Ar pathname
+Perform a symbol and file:line lookup for each address in each
+callgraph and save the output to
+.Ar pathname .
+Unlike
+.Fl m
+that only resolves the first symbol in the graph, this resolves
+every node in the callgraph, or prints out addresses if no
+lookup information is available.
+This option requires the
+.Fl R
+option to read in samples that were previously collected and
+saved with the
+.Fl O
+option.
+.It Fl c Ar cpu-spec
+Set the cpus for subsequent system mode PMCs specified on the
+command line to
+.Ar cpu-spec .
+Argument
+.Ar cpu-spec
+is a comma separated list of CPU numbers, or the literal
+.Sq *
+denoting all available CPUs.
+The default is to allocate system mode PMCs on all available
+CPUs.
+.It Fl d
+Toggle between process mode PMCs measuring events for the target
+process' current and future children or only measuring events for
+the target process.
+The default is to measure events for the target process alone.
+(it has to be passed in the command line prior to
+.Fl p ,
+.Fl s ,
+.Fl P ,
+or
+.Fl S ) .
+.It Fl e
+Specify that the gprof profile files will use a wide history counter.
+These files are produced in a format compatible with
+.Xr gprof 1 .
+However, other tools that cannot fully parse a BSD-style
+gmon header might be unable to correctly parse these files.
+.It Fl f Ar pluginopt
+Pass option string to the active plugin.
+.br
+threshold=<float> do not display cost under specified value (Top).
+.br
+skiplink=0|1 replace node with cost under threshold by a dot (Top).
+.It Fl g
+Produce profiles in a format compatible with
+.Xr gprof 1 .
+A separate profile file is generated for each executable object
+encountered.
+Profile files are placed in sub-directories named by their PMC
+event name.
+.It Fl k Ar kerneldir
+Set the pathname of the kernel directory to argument
+.Ar kerneldir .
+This directory specifies where
+.Nm
+should look for the kernel and its modules.
+The default is to use the path of the running kernel obtained from the
+.Va kern.bootfile
+sysctl.
+.It Fl l Ar secs
+Set system-wide performance measurement duration for
+.Ar secs
+seconds.
+The argument
+.Ar secs
+may be a fractional value.
+.It Fl m Ar pathname
+Print the sampled PCs with the name, the start and ending addresses
+of the function within they live.
+The
+.Ar pathname
+argument is mandatory and indicates where the information will be stored.
+If argument
+.Ar pathname
+is a
+.Dq Li -
+this information is sent to the output file specified by the
+.Fl o
+option.
+This option requires the
+.Fl R
+option to read in samples that were previously collected and
+saved with the
+.Fl O
+option.
+.It Fl n Ar rate
+Set the default sampling rate for subsequent sampling mode
+PMCs specified on the command line.
+The default is to configure PMCs to sample the CPU's instruction
+pointer every 65536 events.
+.It Fl o Ar outputfile
+Send counter readings and textual representations of logged data
+to file
+.Ar outputfile .
+The default is to send output to
+.Pa stderr
+when collecting live data and to
+.Pa stdout
+when processing a pre-existing logfile.
+.It Fl p Ar event-spec
+Allocate a process mode counting PMC measuring hardware events
+specified in
+.Ar event-spec .
+.It Fl q
+Decrease verbosity.
+.It Fl r Ar fsroot
+Set the top of the filesystem hierarchy under which executables
+are located to argument
+.Ar fsroot .
+The default is
+.Pa / .
+.It Fl s Ar event-spec
+Allocate a system mode counting PMC measuring hardware events
+specified in
+.Ar event-spec .
+.It Fl t Ar process-spec
+Attach process mode PMCs to the processes named by argument
+.Ar process-spec .
+Argument
+.Ar process-spec
+may be a non-negative integer denoting a specific process id, or a
+regular expression for selecting processes based on their command names.
+.It Fl v
+Increase verbosity.
+.It Fl w Ar secs
+Print the values of all counting mode PMCs or sampling mode PMCs
+for top mode every
+.Ar secs
+seconds.
+The argument
+.Ar secs
+may be a fractional value.
+The default interval is 5 seconds.
+.It Fl z Ar graphdepth
+When printing system-wide callgraphs, limit callgraphs to the depth
+specified by argument
+.Ar graphdepth .
+.El
+.Pp
+If
+.Ar command
+is specified, it is executed using
+.Xr execvp 3 .
+.Sh EXAMPLES
+To perform system-wide statistical sampling on an AMD Athlon CPU with
+samples taken every 32768 instruction retirals and data being sampled
+to file
+.Pa sample.stat ,
+use:
+.Dl "pmcstat -O sample.stat -n 32768 -S k7-retired-instructions"
+.Pp
+To execute
+.Nm firefox
+and measure the number of data cache misses suffered
+by it and its children every 12 seconds on an AMD Athlon, use:
+.Dl "pmcstat -d -w 12 -p k7-dc-misses firefox"
+.Pp
+To measure instructions retired for all processes named
+.Dq emacs
+use:
+.Dl "pmcstat -t '^emacs$' -p instructions"
+.Pp
+To measure instructions retired for processes named
+.Dq emacs
+for a period of 10 seconds use:
+.Dl "pmcstat -t '^emacs$' -p instructions sleep 10"
+.Pp
+To count instruction tlb-misses on CPUs 0 and 2 on a Intel
+Pentium Pro/Pentium III SMP system use:
+.Dl "pmcstat -c 0,2 -s p6-itlb-miss"
+.Pp
+To collect profiling information for a specific process with pid 1234
+based on instruction cache misses seen by it use:
+.Dl "pmcstat -P ic-misses -t 1234 -O /tmp/sample.out"
+.Pp
+To perform system-wide sampling on all configured processors
+based on processor instructions retired use:
+.Dl "pmcstat -S instructions -O /tmp/sample.out"
+If callgraph capture is not desired use:
+.Dl "pmcstat -N -S instructions -O /tmp/sample.out"
+.Pp
+To send the generated event log to a remote machine use:
+.Dl "pmcstat -S instructions -O remotehost:port"
+On the remote machine, the sample log can be collected using
+.Xr nc 1 :
+.Dl "nc -l remotehost port > /tmp/sample.out"
+.Pp
+To generate
+.Xr gprof 1
+compatible profiles from a sample file use:
+.Dl "pmcstat -R /tmp/sample.out -g"
+.Pp
+To print a system-wide profile with callgraphs to file
+.Pa "foo.graph"
+use:
+.Dl "pmcstat -R /tmp/sample.out -G foo.graph"
+.Sh DIAGNOSTICS
+If option
+.Fl v
+is specified,
+.Nm
+may issue the following diagnostic messages:
+.Bl -diag
+.It "#callchain/dubious-frames"
+The number of callchain records that had an
+.Dq impossible
+value for a return address.
+.It "#exec handling errors"
+The number of
+.Xr exec 2
+events in the log file that named executables that could not be
+analyzed.
+.It "#exec/elf"
+The number of
+.Xr exec 2
+events that named ELF executables.
+.It "#exec/unknown"
+The number of
+.Xr exec 2
+events that named executables with unrecognized formats.
+.It "#samples/total"
+The total number of samples in the log file.
+.It "#samples/unclaimed"
+The number of samples that could not be correlated to a known
+executable object (i.e., to an executable, shared library, the
+kernel or the runtime loader).
+.It "#samples/unknown-object"
+The number of samples that were associated with an executable
+with an unrecognized object format.
+.El
+.Pp
+.Ex -std
+.Sh COMPATIBILITY
+Due to the limitations of the
+.Pa gmon.out
+file format,
+.Xr gprof 1
+compatible profiles generated by the
+.Fl g
+option do not contain information about calls that cross executable
+boundaries.
+The generated
+.Pa gmon.out
+files are also only meaningful for native executables.
+.Sh SEE ALSO
+.Xr gprof 1 ,
+.Xr nc 1 ,
+.Xr execvp 3 ,
+.Xr pmc 3 ,
+.Xr pmclog 3 ,
+.Xr hwpmc 4 ,
+.Xr pmccontrol 8 ,
+.Xr sysctl 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 6.0 .
+It is
+.Ud
+.Sh AUTHORS
+.An Joseph Koshy Aq Mt jkoshy@FreeBSD.org
+.Sh BUGS
+The
+.Nm
+utility cannot yet analyse
+.Xr hwpmc 4
+logs generated by non-native architectures.
diff --git a/usr.sbin/pmcstat/pmcstat.c b/usr.sbin/pmcstat/pmcstat.c
new file mode 100644
index 0000000..81b0cd0
--- /dev/null
+++ b/usr.sbin/pmcstat/pmcstat.c
@@ -0,0 +1,1537 @@
+/*-
+ * Copyright (c) 2003-2008, Joseph Koshy
+ * Copyright (c) 2007 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * Portions of this software were developed by A. Joseph Koshy under
+ * sponsorship from the FreeBSD Foundation and Google, 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.
+ *
+ * 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/cpuset.h>
+#include <sys/event.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/ttycom.h>
+#include <sys/user.h>
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <curses.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <kvm.h>
+#include <libgen.h>
+#include <limits.h>
+#include <math.h>
+#include <pmc.h>
+#include <pmclog.h>
+#include <regex.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "pmcstat.h"
+
+/*
+ * A given invocation of pmcstat(8) can manage multiple PMCs of both
+ * the system-wide and per-process variety. Each of these could be in
+ * 'counting mode' or in 'sampling mode'.
+ *
+ * For 'counting mode' PMCs, pmcstat(8) will periodically issue a
+ * pmc_read() at the configured time interval and print out the value
+ * of the requested PMCs.
+ *
+ * For 'sampling mode' PMCs it can log to a file for offline analysis,
+ * or can analyse sampling data "on the fly", either by converting
+ * samples to printed textual form or by creating gprof(1) compatible
+ * profiles, one per program executed. When creating gprof(1)
+ * profiles it can optionally merge entries from multiple processes
+ * for a given executable into a single profile file.
+ *
+ * pmcstat(8) can also execute a command line and attach PMCs to the
+ * resulting child process. The protocol used is as follows:
+ *
+ * - parent creates a socketpair for two way communication and
+ * fork()s.
+ * - subsequently:
+ *
+ * /Parent/ /Child/
+ *
+ * - Wait for childs token.
+ * - Sends token.
+ * - Awaits signal to start.
+ * - Attaches PMCs to the child's pid
+ * and starts them. Sets up
+ * monitoring for the child.
+ * - Signals child to start.
+ * - Receives signal, attempts exec().
+ *
+ * After this point normal processing can happen.
+ */
+
+/* Globals */
+
+int pmcstat_displayheight = DEFAULT_DISPLAY_HEIGHT;
+int pmcstat_displaywidth = DEFAULT_DISPLAY_WIDTH;
+static int pmcstat_sockpair[NSOCKPAIRFD];
+static int pmcstat_kq;
+static kvm_t *pmcstat_kvm;
+static struct kinfo_proc *pmcstat_plist;
+struct pmcstat_args args;
+
+static void
+pmcstat_clone_event_descriptor(struct pmcstat_ev *ev, const cpuset_t *cpumask)
+{
+ int cpu;
+ struct pmcstat_ev *ev_clone;
+
+ for (cpu = 0; cpu < CPU_SETSIZE; cpu++) {
+ if (!CPU_ISSET(cpu, cpumask))
+ continue;
+
+ if ((ev_clone = malloc(sizeof(*ev_clone))) == NULL)
+ errx(EX_SOFTWARE, "ERROR: Out of memory");
+ (void) memset(ev_clone, 0, sizeof(*ev_clone));
+
+ ev_clone->ev_count = ev->ev_count;
+ ev_clone->ev_cpu = cpu;
+ ev_clone->ev_cumulative = ev->ev_cumulative;
+ ev_clone->ev_flags = ev->ev_flags;
+ ev_clone->ev_mode = ev->ev_mode;
+ ev_clone->ev_name = strdup(ev->ev_name);
+ ev_clone->ev_pmcid = ev->ev_pmcid;
+ ev_clone->ev_saved = ev->ev_saved;
+ ev_clone->ev_spec = strdup(ev->ev_spec);
+
+ STAILQ_INSERT_TAIL(&args.pa_events, ev_clone, ev_next);
+ }
+}
+
+static void
+pmcstat_get_cpumask(const char *cpuspec, cpuset_t *cpumask)
+{
+ int cpu;
+ const char *s;
+ char *end;
+
+ CPU_ZERO(cpumask);
+ s = cpuspec;
+
+ do {
+ cpu = strtol(s, &end, 0);
+ if (cpu < 0 || end == s)
+ errx(EX_USAGE,
+ "ERROR: Illegal CPU specification \"%s\".",
+ cpuspec);
+ CPU_SET(cpu, cpumask);
+ s = end + strspn(end, ", \t");
+ } while (*s);
+ assert(!CPU_EMPTY(cpumask));
+}
+
+void
+pmcstat_attach_pmcs(void)
+{
+ struct pmcstat_ev *ev;
+ struct pmcstat_target *pt;
+ int count;
+
+ /* Attach all process PMCs to target processes. */
+ count = 0;
+ STAILQ_FOREACH(ev, &args.pa_events, ev_next) {
+ if (PMC_IS_SYSTEM_MODE(ev->ev_mode))
+ continue;
+ SLIST_FOREACH(pt, &args.pa_targets, pt_next)
+ if (pmc_attach(ev->ev_pmcid, pt->pt_pid) == 0)
+ count++;
+ else if (errno != ESRCH)
+ err(EX_OSERR,
+"ERROR: cannot attach pmc \"%s\" to process %d",
+ ev->ev_name, (int)pt->pt_pid);
+ }
+
+ if (count == 0)
+ errx(EX_DATAERR, "ERROR: No processes were attached to.");
+}
+
+
+void
+pmcstat_cleanup(void)
+{
+ struct pmcstat_ev *ev, *tmp;
+
+ /* release allocated PMCs. */
+ STAILQ_FOREACH_SAFE(ev, &args.pa_events, ev_next, tmp)
+ if (ev->ev_pmcid != PMC_ID_INVALID) {
+ if (pmc_stop(ev->ev_pmcid) < 0)
+ err(EX_OSERR, "ERROR: cannot stop pmc 0x%x \"%s\"",
+ ev->ev_pmcid, ev->ev_name);
+ if (pmc_release(ev->ev_pmcid) < 0)
+ err(EX_OSERR, "ERROR: cannot release pmc 0x%x \"%s\"",
+ ev->ev_pmcid, ev->ev_name);
+ free(ev->ev_name);
+ free(ev->ev_spec);
+ STAILQ_REMOVE(&args.pa_events, ev, pmcstat_ev, ev_next);
+ free(ev);
+ }
+
+ /* de-configure the log file if present. */
+ if (args.pa_flags & (FLAG_HAS_PIPE | FLAG_HAS_OUTPUT_LOGFILE))
+ (void) pmc_configure_logfile(-1);
+
+ if (args.pa_logparser) {
+ pmclog_close(args.pa_logparser);
+ args.pa_logparser = NULL;
+ }
+
+ pmcstat_shutdown_logging();
+}
+
+void
+pmcstat_create_process(void)
+{
+ char token;
+ pid_t pid;
+ struct kevent kev;
+ struct pmcstat_target *pt;
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, pmcstat_sockpair) < 0)
+ err(EX_OSERR, "ERROR: cannot create socket pair");
+
+ switch (pid = fork()) {
+ case -1:
+ err(EX_OSERR, "ERROR: cannot fork");
+ /*NOTREACHED*/
+
+ case 0: /* child */
+ (void) close(pmcstat_sockpair[PARENTSOCKET]);
+
+ /* Write a token to tell our parent we've started executing. */
+ if (write(pmcstat_sockpair[CHILDSOCKET], "+", 1) != 1)
+ err(EX_OSERR, "ERROR (child): cannot write token");
+
+ /* Wait for our parent to signal us to start. */
+ if (read(pmcstat_sockpair[CHILDSOCKET], &token, 1) < 0)
+ err(EX_OSERR, "ERROR (child): cannot read token");
+ (void) close(pmcstat_sockpair[CHILDSOCKET]);
+
+ /* exec() the program requested */
+ execvp(*args.pa_argv, args.pa_argv);
+ /* and if that fails, notify the parent */
+ kill(getppid(), SIGCHLD);
+ err(EX_OSERR, "ERROR: execvp \"%s\" failed", *args.pa_argv);
+ /*NOTREACHED*/
+
+ default: /* parent */
+ (void) close(pmcstat_sockpair[CHILDSOCKET]);
+ break;
+ }
+
+ /* Ask to be notified via a kevent when the target process exits. */
+ EV_SET(&kev, pid, EVFILT_PROC, EV_ADD|EV_ONESHOT, NOTE_EXIT, 0,
+ NULL);
+ if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0)
+ err(EX_OSERR, "ERROR: cannot monitor child process %d", pid);
+
+ if ((pt = malloc(sizeof(*pt))) == NULL)
+ errx(EX_SOFTWARE, "ERROR: Out of memory.");
+
+ pt->pt_pid = pid;
+ SLIST_INSERT_HEAD(&args.pa_targets, pt, pt_next);
+
+ /* Wait for the child to signal that its ready to go. */
+ if (read(pmcstat_sockpair[PARENTSOCKET], &token, 1) < 0)
+ err(EX_OSERR, "ERROR (parent): cannot read token");
+
+ return;
+}
+
+void
+pmcstat_find_targets(const char *spec)
+{
+ int n, nproc, pid, rv;
+ struct pmcstat_target *pt;
+ char errbuf[_POSIX2_LINE_MAX], *end;
+ static struct kinfo_proc *kp;
+ regex_t reg;
+ regmatch_t regmatch;
+
+ /* First check if we've been given a process id. */
+ pid = strtol(spec, &end, 0);
+ if (end != spec && pid >= 0) {
+ if ((pt = malloc(sizeof(*pt))) == NULL)
+ goto outofmemory;
+ pt->pt_pid = pid;
+ SLIST_INSERT_HEAD(&args.pa_targets, pt, pt_next);
+ return;
+ }
+
+ /* Otherwise treat arg as a regular expression naming processes. */
+ if (pmcstat_kvm == NULL) {
+ if ((pmcstat_kvm = kvm_openfiles(NULL, "/dev/null", NULL, 0,
+ errbuf)) == NULL)
+ err(EX_OSERR, "ERROR: Cannot open kernel \"%s\"",
+ errbuf);
+ if ((pmcstat_plist = kvm_getprocs(pmcstat_kvm, KERN_PROC_PROC,
+ 0, &nproc)) == NULL)
+ err(EX_OSERR, "ERROR: Cannot get process list: %s",
+ kvm_geterr(pmcstat_kvm));
+ } else
+ nproc = 0;
+
+ if ((rv = regcomp(&reg, spec, REG_EXTENDED|REG_NOSUB)) != 0) {
+ regerror(rv, &reg, errbuf, sizeof(errbuf));
+ err(EX_DATAERR, "ERROR: Failed to compile regex \"%s\": %s",
+ spec, errbuf);
+ }
+
+ for (n = 0, kp = pmcstat_plist; n < nproc; n++, kp++) {
+ if ((rv = regexec(&reg, kp->ki_comm, 1, &regmatch, 0)) == 0) {
+ if ((pt = malloc(sizeof(*pt))) == NULL)
+ goto outofmemory;
+ pt->pt_pid = kp->ki_pid;
+ SLIST_INSERT_HEAD(&args.pa_targets, pt, pt_next);
+ } else if (rv != REG_NOMATCH) {
+ regerror(rv, &reg, errbuf, sizeof(errbuf));
+ errx(EX_SOFTWARE, "ERROR: Regex evalation failed: %s",
+ errbuf);
+ }
+ }
+
+ regfree(&reg);
+
+ return;
+
+ outofmemory:
+ errx(EX_SOFTWARE, "Out of memory.");
+ /*NOTREACHED*/
+}
+
+void
+pmcstat_kill_process(void)
+{
+ struct pmcstat_target *pt;
+
+ assert(args.pa_flags & FLAG_HAS_COMMANDLINE);
+
+ /*
+ * If a command line was specified, it would be the very first
+ * in the list, before any other processes specified by -t.
+ */
+ pt = SLIST_FIRST(&args.pa_targets);
+ assert(pt != NULL);
+
+ if (kill(pt->pt_pid, SIGINT) != 0)
+ err(EX_OSERR, "ERROR: cannot signal child process");
+}
+
+void
+pmcstat_start_pmcs(void)
+{
+ struct pmcstat_ev *ev;
+
+ STAILQ_FOREACH(ev, &args.pa_events, ev_next) {
+
+ assert(ev->ev_pmcid != PMC_ID_INVALID);
+
+ if (pmc_start(ev->ev_pmcid) < 0) {
+ warn("ERROR: Cannot start pmc 0x%x \"%s\"",
+ ev->ev_pmcid, ev->ev_name);
+ pmcstat_cleanup();
+ exit(EX_OSERR);
+ }
+ }
+
+}
+
+void
+pmcstat_print_headers(void)
+{
+ struct pmcstat_ev *ev;
+ int c, w;
+
+ (void) fprintf(args.pa_printfile, PRINT_HEADER_PREFIX);
+
+ STAILQ_FOREACH(ev, &args.pa_events, ev_next) {
+ if (PMC_IS_SAMPLING_MODE(ev->ev_mode))
+ continue;
+
+ c = PMC_IS_SYSTEM_MODE(ev->ev_mode) ? 's' : 'p';
+
+ if (ev->ev_fieldskip != 0)
+ (void) fprintf(args.pa_printfile, "%*s",
+ ev->ev_fieldskip, "");
+ w = ev->ev_fieldwidth - ev->ev_fieldskip - 2;
+
+ if (c == 's')
+ (void) fprintf(args.pa_printfile, "s/%02d/%-*s ",
+ ev->ev_cpu, w-3, ev->ev_name);
+ else
+ (void) fprintf(args.pa_printfile, "p/%*s ", w,
+ ev->ev_name);
+ }
+
+ (void) fflush(args.pa_printfile);
+}
+
+void
+pmcstat_print_counters(void)
+{
+ int extra_width;
+ struct pmcstat_ev *ev;
+ pmc_value_t value;
+
+ extra_width = sizeof(PRINT_HEADER_PREFIX) - 1;
+
+ STAILQ_FOREACH(ev, &args.pa_events, ev_next) {
+
+ /* skip sampling mode counters */
+ if (PMC_IS_SAMPLING_MODE(ev->ev_mode))
+ continue;
+
+ if (pmc_read(ev->ev_pmcid, &value) < 0)
+ err(EX_OSERR, "ERROR: Cannot read pmc \"%s\"",
+ ev->ev_name);
+
+ (void) fprintf(args.pa_printfile, "%*ju ",
+ ev->ev_fieldwidth + extra_width,
+ (uintmax_t) ev->ev_cumulative ? value :
+ (value - ev->ev_saved));
+
+ if (ev->ev_cumulative == 0)
+ ev->ev_saved = value;
+ extra_width = 0;
+ }
+
+ (void) fflush(args.pa_printfile);
+}
+
+/*
+ * Print output
+ */
+
+void
+pmcstat_print_pmcs(void)
+{
+ static int linecount = 0;
+
+ /* check if we need to print a header line */
+ if (++linecount > pmcstat_displayheight) {
+ (void) fprintf(args.pa_printfile, "\n");
+ linecount = 1;
+ }
+ if (linecount == 1)
+ pmcstat_print_headers();
+ (void) fprintf(args.pa_printfile, "\n");
+
+ pmcstat_print_counters();
+
+ return;
+}
+
+/*
+ * Do process profiling
+ *
+ * If a pid was specified, attach each allocated PMC to the target
+ * process. Otherwise, fork a child and attach the PMCs to the child,
+ * and have the child exec() the target program.
+ */
+
+void
+pmcstat_start_process(void)
+{
+ /* Signal the child to proceed. */
+ if (write(pmcstat_sockpair[PARENTSOCKET], "!", 1) != 1)
+ err(EX_OSERR, "ERROR (parent): write of token failed");
+
+ (void) close(pmcstat_sockpair[PARENTSOCKET]);
+}
+
+void
+pmcstat_show_usage(void)
+{
+ errx(EX_USAGE,
+ "[options] [commandline]\n"
+ "\t Measure process and/or system performance using hardware\n"
+ "\t performance monitoring counters.\n"
+ "\t Options include:\n"
+ "\t -C\t\t (toggle) show cumulative counts\n"
+ "\t -D path\t create profiles in directory \"path\"\n"
+ "\t -E\t\t (toggle) show counts at process exit\n"
+ "\t -F file\t write a system-wide callgraph (Kcachegrind format)"
+ " to \"file\"\n"
+ "\t -G file\t write a system-wide callgraph to \"file\"\n"
+ "\t -M file\t print executable/gmon file map to \"file\"\n"
+ "\t -N\t\t (toggle) capture callchains\n"
+ "\t -O file\t send log output to \"file\"\n"
+ "\t -P spec\t allocate a process-private sampling PMC\n"
+ "\t -R file\t read events from \"file\"\n"
+ "\t -S spec\t allocate a system-wide sampling PMC\n"
+ "\t -T\t\t start in top mode\n"
+ "\t -W\t\t (toggle) show counts per context switch\n"
+ "\t -a file\t print sampled PCs and callgraph to \"file\"\n"
+ "\t -c cpu-list\t set cpus for subsequent system-wide PMCs\n"
+ "\t -d\t\t (toggle) track descendants\n"
+ "\t -e\t\t use wide history counter for gprof(1) output\n"
+ "\t -f spec\t pass \"spec\" to as plugin option\n"
+ "\t -g\t\t produce gprof(1) compatible profiles\n"
+ "\t -k dir\t\t set the path to the kernel\n"
+ "\t -l secs\t set duration time\n"
+ "\t -m file\t print sampled PCs to \"file\"\n"
+ "\t -n rate\t set sampling rate\n"
+ "\t -o file\t send print output to \"file\"\n"
+ "\t -p spec\t allocate a process-private counting PMC\n"
+ "\t -q\t\t suppress verbosity\n"
+ "\t -r fsroot\t specify FS root directory\n"
+ "\t -s spec\t allocate a system-wide counting PMC\n"
+ "\t -t process-spec attach to running processes matching "
+ "\"process-spec\"\n"
+ "\t -v\t\t increase verbosity\n"
+ "\t -w secs\t set printing time interval\n"
+ "\t -z depth\t limit callchain display depth"
+ );
+}
+
+/*
+ * At exit handler for top mode
+ */
+
+void
+pmcstat_topexit(void)
+{
+ if (!args.pa_toptty)
+ return;
+
+ /*
+ * Shutdown ncurses.
+ */
+ clrtoeol();
+ refresh();
+ endwin();
+}
+
+/*
+ * Main
+ */
+
+int
+main(int argc, char **argv)
+{
+ cpuset_t cpumask, rootmask;
+ double interval;
+ double duration;
+ int option, npmc;
+ int c, check_driver_stats, current_sampling_count;
+ int do_callchain, do_descendants, do_logproccsw, do_logprocexit;
+ int do_print, do_read;
+ size_t len;
+ int graphdepth;
+ int pipefd[2], rfd;
+ int use_cumulative_counts;
+ short cf, cb;
+ char *end, *tmp;
+ const char *errmsg, *graphfilename;
+ enum pmcstat_state runstate;
+ struct pmc_driverstats ds_start, ds_end;
+ struct pmcstat_ev *ev;
+ struct sigaction sa;
+ struct kevent kev;
+ struct winsize ws;
+ struct stat sb;
+ char buffer[PATH_MAX];
+
+ check_driver_stats = 0;
+ current_sampling_count = DEFAULT_SAMPLE_COUNT;
+ do_callchain = 1;
+ do_descendants = 0;
+ do_logproccsw = 0;
+ do_logprocexit = 0;
+ use_cumulative_counts = 0;
+ graphfilename = "-";
+ args.pa_required = 0;
+ args.pa_flags = 0;
+ args.pa_verbosity = 1;
+ args.pa_logfd = -1;
+ args.pa_fsroot = "";
+ args.pa_samplesdir = ".";
+ args.pa_printfile = stderr;
+ args.pa_graphdepth = DEFAULT_CALLGRAPH_DEPTH;
+ args.pa_graphfile = NULL;
+ args.pa_interval = DEFAULT_WAIT_INTERVAL;
+ args.pa_mapfilename = NULL;
+ args.pa_inputpath = NULL;
+ args.pa_outputpath = NULL;
+ args.pa_pplugin = PMCSTAT_PL_NONE;
+ args.pa_plugin = PMCSTAT_PL_NONE;
+ args.pa_ctdumpinstr = 1;
+ args.pa_topmode = PMCSTAT_TOP_DELTA;
+ args.pa_toptty = 0;
+ args.pa_topcolor = 0;
+ args.pa_mergepmc = 0;
+ args.pa_duration = 0.0;
+ STAILQ_INIT(&args.pa_events);
+ SLIST_INIT(&args.pa_targets);
+ bzero(&ds_start, sizeof(ds_start));
+ bzero(&ds_end, sizeof(ds_end));
+ ev = NULL;
+ CPU_ZERO(&cpumask);
+
+ /* Default to using the running system kernel. */
+ len = 0;
+ if (sysctlbyname("kern.bootfile", NULL, &len, NULL, 0) == -1)
+ err(EX_OSERR, "ERROR: Cannot determine path of running kernel");
+ args.pa_kernel = malloc(len + 1);
+ if (sysctlbyname("kern.bootfile", args.pa_kernel, &len, NULL, 0) == -1)
+ err(EX_OSERR, "ERROR: Cannot determine path of running kernel");
+
+ /*
+ * The initial CPU mask specifies the root mask of this process
+ * which is usually all CPUs in the system.
+ */
+ if (cpuset_getaffinity(CPU_LEVEL_ROOT, CPU_WHICH_PID, -1,
+ sizeof(rootmask), &rootmask) == -1)
+ err(EX_OSERR, "ERROR: Cannot determine the root set of CPUs");
+ CPU_COPY(&rootmask, &cpumask);
+
+ while ((option = getopt(argc, argv,
+ "CD:EF:G:M:NO:P:R:S:TWa:c:def:gk:l:m:n:o:p:qr:s:t:vw:z:")) != -1)
+ switch (option) {
+ case 'a': /* Annotate + callgraph */
+ args.pa_flags |= FLAG_DO_ANNOTATE;
+ args.pa_plugin = PMCSTAT_PL_ANNOTATE_CG;
+ graphfilename = optarg;
+ break;
+
+ case 'C': /* cumulative values */
+ use_cumulative_counts = !use_cumulative_counts;
+ args.pa_required |= FLAG_HAS_COUNTING_PMCS;
+ break;
+
+ case 'c': /* CPU */
+ if (optarg[0] == '*' && optarg[1] == '\0')
+ CPU_COPY(&rootmask, &cpumask);
+ else
+ pmcstat_get_cpumask(optarg, &cpumask);
+
+ args.pa_flags |= FLAGS_HAS_CPUMASK;
+ args.pa_required |= FLAG_HAS_SYSTEM_PMCS;
+ break;
+
+ case 'D':
+ if (stat(optarg, &sb) < 0)
+ err(EX_OSERR, "ERROR: Cannot stat \"%s\"",
+ optarg);
+ if (!S_ISDIR(sb.st_mode))
+ errx(EX_USAGE,
+ "ERROR: \"%s\" is not a directory.",
+ optarg);
+ args.pa_samplesdir = optarg;
+ args.pa_flags |= FLAG_HAS_SAMPLESDIR;
+ args.pa_required |= FLAG_DO_GPROF;
+ break;
+
+ case 'd': /* toggle descendents */
+ do_descendants = !do_descendants;
+ args.pa_required |= FLAG_HAS_PROCESS_PMCS;
+ break;
+
+ case 'e': /* wide gprof metrics */
+ args.pa_flags |= FLAG_DO_WIDE_GPROF_HC;
+ break;
+
+ case 'F': /* produce a system-wide calltree */
+ args.pa_flags |= FLAG_DO_CALLGRAPHS;
+ args.pa_plugin = PMCSTAT_PL_CALLTREE;
+ graphfilename = optarg;
+ break;
+
+ case 'f': /* plugins options */
+ if (args.pa_plugin == PMCSTAT_PL_NONE)
+ err(EX_USAGE, "ERROR: Need -g/-G/-m/-T.");
+ pmcstat_pluginconfigure_log(optarg);
+ break;
+
+ case 'G': /* produce a system-wide callgraph */
+ args.pa_flags |= FLAG_DO_CALLGRAPHS;
+ args.pa_plugin = PMCSTAT_PL_CALLGRAPH;
+ graphfilename = optarg;
+ break;
+
+ case 'g': /* produce gprof compatible profiles */
+ args.pa_flags |= FLAG_DO_GPROF;
+ args.pa_pplugin = PMCSTAT_PL_CALLGRAPH;
+ args.pa_plugin = PMCSTAT_PL_GPROF;
+ break;
+
+ case 'k': /* pathname to the kernel */
+ free(args.pa_kernel);
+ args.pa_kernel = strdup(optarg);
+ args.pa_required |= FLAG_DO_ANALYSIS;
+ args.pa_flags |= FLAG_HAS_KERNELPATH;
+ break;
+
+ case 'l': /* time duration in seconds */
+ duration = strtod(optarg, &end);
+ if (*end != '\0' || duration <= 0)
+ errx(EX_USAGE, "ERROR: Illegal duration time "
+ "value \"%s\".", optarg);
+ args.pa_flags |= FLAG_HAS_DURATION;
+ args.pa_duration = duration;
+ break;
+
+ case 'm':
+ args.pa_flags |= FLAG_DO_ANNOTATE;
+ args.pa_plugin = PMCSTAT_PL_ANNOTATE;
+ graphfilename = optarg;
+ break;
+
+ case 'E': /* log process exit */
+ do_logprocexit = !do_logprocexit;
+ args.pa_required |= (FLAG_HAS_PROCESS_PMCS |
+ FLAG_HAS_COUNTING_PMCS | FLAG_HAS_OUTPUT_LOGFILE);
+ break;
+
+ case 'M': /* mapfile */
+ args.pa_mapfilename = optarg;
+ break;
+
+ case 'N':
+ do_callchain = !do_callchain;
+ args.pa_required |= FLAG_HAS_SAMPLING_PMCS;
+ break;
+
+ case 'p': /* process virtual counting PMC */
+ case 's': /* system-wide counting PMC */
+ case 'P': /* process virtual sampling PMC */
+ case 'S': /* system-wide sampling PMC */
+ if ((ev = malloc(sizeof(*ev))) == NULL)
+ errx(EX_SOFTWARE, "ERROR: Out of memory.");
+
+ switch (option) {
+ case 'p': ev->ev_mode = PMC_MODE_TC; break;
+ case 's': ev->ev_mode = PMC_MODE_SC; break;
+ case 'P': ev->ev_mode = PMC_MODE_TS; break;
+ case 'S': ev->ev_mode = PMC_MODE_SS; break;
+ }
+
+ if (option == 'P' || option == 'p') {
+ args.pa_flags |= FLAG_HAS_PROCESS_PMCS;
+ args.pa_required |= (FLAG_HAS_COMMANDLINE |
+ FLAG_HAS_TARGET);
+ }
+
+ if (option == 'P' || option == 'S') {
+ args.pa_flags |= FLAG_HAS_SAMPLING_PMCS;
+ args.pa_required |= (FLAG_HAS_PIPE |
+ FLAG_HAS_OUTPUT_LOGFILE);
+ }
+
+ if (option == 'p' || option == 's')
+ args.pa_flags |= FLAG_HAS_COUNTING_PMCS;
+
+ if (option == 's' || option == 'S')
+ args.pa_flags |= FLAG_HAS_SYSTEM_PMCS;
+
+ ev->ev_spec = strdup(optarg);
+
+ if (option == 'S' || option == 'P')
+ ev->ev_count = current_sampling_count;
+ else
+ ev->ev_count = -1;
+
+ if (option == 'S' || option == 's')
+ ev->ev_cpu = CPU_FFS(&cpumask) - 1;
+ else
+ ev->ev_cpu = PMC_CPU_ANY;
+
+ ev->ev_flags = 0;
+ if (do_callchain)
+ ev->ev_flags |= PMC_F_CALLCHAIN;
+ if (do_descendants)
+ ev->ev_flags |= PMC_F_DESCENDANTS;
+ if (do_logprocexit)
+ ev->ev_flags |= PMC_F_LOG_PROCEXIT;
+ if (do_logproccsw)
+ ev->ev_flags |= PMC_F_LOG_PROCCSW;
+
+ ev->ev_cumulative = use_cumulative_counts;
+
+ ev->ev_saved = 0LL;
+ ev->ev_pmcid = PMC_ID_INVALID;
+
+ /* extract event name */
+ c = strcspn(optarg, ", \t");
+ ev->ev_name = malloc(c + 1);
+ (void) strncpy(ev->ev_name, optarg, c);
+ *(ev->ev_name + c) = '\0';
+
+ STAILQ_INSERT_TAIL(&args.pa_events, ev, ev_next);
+
+ if (option == 's' || option == 'S') {
+ CPU_CLR(ev->ev_cpu, &cpumask);
+ pmcstat_clone_event_descriptor(ev, &cpumask);
+ CPU_SET(ev->ev_cpu, &cpumask);
+ }
+
+ break;
+
+ case 'n': /* sampling count */
+ current_sampling_count = strtol(optarg, &end, 0);
+ if (*end != '\0' || current_sampling_count <= 0)
+ errx(EX_USAGE,
+ "ERROR: Illegal count value \"%s\".",
+ optarg);
+ args.pa_required |= FLAG_HAS_SAMPLING_PMCS;
+ break;
+
+ case 'o': /* outputfile */
+ if (args.pa_printfile != NULL &&
+ args.pa_printfile != stdout &&
+ args.pa_printfile != stderr)
+ (void) fclose(args.pa_printfile);
+ if ((args.pa_printfile = fopen(optarg, "w")) == NULL)
+ errx(EX_OSERR,
+ "ERROR: cannot open \"%s\" for writing.",
+ optarg);
+ args.pa_flags |= FLAG_DO_PRINT;
+ break;
+
+ case 'O': /* sampling output */
+ if (args.pa_outputpath)
+ errx(EX_USAGE,
+"ERROR: option -O may only be specified once.");
+ args.pa_outputpath = optarg;
+ args.pa_flags |= FLAG_HAS_OUTPUT_LOGFILE;
+ break;
+
+ case 'q': /* quiet mode */
+ args.pa_verbosity = 0;
+ break;
+
+ case 'r': /* root FS path */
+ args.pa_fsroot = optarg;
+ break;
+
+ case 'R': /* read an existing log file */
+ if (args.pa_inputpath != NULL)
+ errx(EX_USAGE,
+"ERROR: option -R may only be specified once.");
+ args.pa_inputpath = optarg;
+ if (args.pa_printfile == stderr)
+ args.pa_printfile = stdout;
+ args.pa_flags |= FLAG_READ_LOGFILE;
+ break;
+
+ case 't': /* target pid or process name */
+ pmcstat_find_targets(optarg);
+
+ args.pa_flags |= FLAG_HAS_TARGET;
+ args.pa_required |= FLAG_HAS_PROCESS_PMCS;
+ break;
+
+ case 'T': /* top mode */
+ args.pa_flags |= FLAG_DO_TOP;
+ args.pa_plugin = PMCSTAT_PL_CALLGRAPH;
+ args.pa_ctdumpinstr = 0;
+ args.pa_mergepmc = 1;
+ if (args.pa_printfile == stderr)
+ args.pa_printfile = stdout;
+ break;
+
+ case 'v': /* verbose */
+ args.pa_verbosity++;
+ break;
+
+ case 'w': /* wait interval */
+ interval = strtod(optarg, &end);
+ if (*end != '\0' || interval <= 0)
+ errx(EX_USAGE,
+"ERROR: Illegal wait interval value \"%s\".",
+ optarg);
+ args.pa_flags |= FLAG_HAS_WAIT_INTERVAL;
+ args.pa_interval = interval;
+ break;
+
+ case 'W': /* toggle LOG_CSW */
+ do_logproccsw = !do_logproccsw;
+ args.pa_required |= (FLAG_HAS_PROCESS_PMCS |
+ FLAG_HAS_COUNTING_PMCS | FLAG_HAS_OUTPUT_LOGFILE);
+ break;
+
+ case 'z':
+ graphdepth = strtod(optarg, &end);
+ if (*end != '\0' || graphdepth <= 0)
+ errx(EX_USAGE,
+ "ERROR: Illegal callchain depth \"%s\".",
+ optarg);
+ args.pa_graphdepth = graphdepth;
+ args.pa_required |= FLAG_DO_CALLGRAPHS;
+ break;
+
+ case '?':
+ default:
+ pmcstat_show_usage();
+ break;
+
+ }
+
+ args.pa_argc = (argc -= optind);
+ args.pa_argv = (argv += optind);
+
+ /* If we read from logfile and no specified CPU mask use
+ * the maximum CPU count.
+ */
+ if ((args.pa_flags & FLAG_READ_LOGFILE) &&
+ (args.pa_flags & FLAGS_HAS_CPUMASK) == 0)
+ CPU_FILL(&cpumask);
+
+ args.pa_cpumask = cpumask; /* For selecting CPUs using -R. */
+
+ if (argc) /* command line present */
+ args.pa_flags |= FLAG_HAS_COMMANDLINE;
+
+ if (args.pa_flags & (FLAG_DO_GPROF | FLAG_DO_CALLGRAPHS |
+ FLAG_DO_ANNOTATE | FLAG_DO_TOP))
+ args.pa_flags |= FLAG_DO_ANALYSIS;
+
+ /*
+ * Check invocation syntax.
+ */
+
+ /* disallow -O and -R together */
+ if (args.pa_outputpath && args.pa_inputpath)
+ errx(EX_USAGE,
+ "ERROR: options -O and -R are mutually exclusive.");
+
+ /* disallow -T and -l together */
+ if ((args.pa_flags & FLAG_HAS_DURATION) &&
+ (args.pa_flags & FLAG_DO_TOP))
+ errx(EX_USAGE, "ERROR: options -T and -l are mutually "
+ "exclusive.");
+
+ /* -a and -m require -R */
+ if (args.pa_flags & FLAG_DO_ANNOTATE && args.pa_inputpath == NULL)
+ errx(EX_USAGE, "ERROR: option %s requires an input file",
+ args.pa_plugin == PMCSTAT_PL_ANNOTATE ? "-m" : "-a");
+
+ /* -m option is not allowed combined with -g or -G. */
+ if (args.pa_flags & FLAG_DO_ANNOTATE &&
+ args.pa_flags & (FLAG_DO_GPROF | FLAG_DO_CALLGRAPHS))
+ errx(EX_USAGE,
+ "ERROR: option -m and -g | -G are mutually exclusive");
+
+ if (args.pa_flags & FLAG_READ_LOGFILE) {
+ errmsg = NULL;
+ if (args.pa_flags & FLAG_HAS_COMMANDLINE)
+ errmsg = "a command line specification";
+ else if (args.pa_flags & FLAG_HAS_TARGET)
+ errmsg = "option -t";
+ else if (!STAILQ_EMPTY(&args.pa_events))
+ errmsg = "a PMC event specification";
+ if (errmsg)
+ errx(EX_USAGE,
+ "ERROR: option -R may not be used with %s.",
+ errmsg);
+ } else if (STAILQ_EMPTY(&args.pa_events))
+ /* All other uses require a PMC spec. */
+ pmcstat_show_usage();
+
+ /* check for -t pid without a process PMC spec */
+ if ((args.pa_required & FLAG_HAS_TARGET) &&
+ (args.pa_flags & FLAG_HAS_PROCESS_PMCS) == 0)
+ errx(EX_USAGE,
+"ERROR: option -t requires a process mode PMC to be specified."
+ );
+
+ /* check for process-mode options without a command or -t pid */
+ if ((args.pa_required & FLAG_HAS_PROCESS_PMCS) &&
+ (args.pa_flags & (FLAG_HAS_COMMANDLINE | FLAG_HAS_TARGET)) == 0)
+ errx(EX_USAGE,
+"ERROR: options -d, -E, -p, -P, and -W require a command line or target process."
+ );
+
+ /* check for -p | -P without a target process of some sort */
+ if ((args.pa_required & (FLAG_HAS_COMMANDLINE | FLAG_HAS_TARGET)) &&
+ (args.pa_flags & (FLAG_HAS_COMMANDLINE | FLAG_HAS_TARGET)) == 0)
+ errx(EX_USAGE,
+"ERROR: options -P and -p require a target process or a command line."
+ );
+
+ /* check for process-mode options without a process-mode PMC */
+ if ((args.pa_required & FLAG_HAS_PROCESS_PMCS) &&
+ (args.pa_flags & FLAG_HAS_PROCESS_PMCS) == 0)
+ errx(EX_USAGE,
+"ERROR: options -d, -E, and -W require a process mode PMC to be specified."
+ );
+
+ /* check for -c cpu with no system mode PMCs or logfile. */
+ if ((args.pa_required & FLAG_HAS_SYSTEM_PMCS) &&
+ (args.pa_flags & FLAG_HAS_SYSTEM_PMCS) == 0 &&
+ (args.pa_flags & FLAG_READ_LOGFILE) == 0)
+ errx(EX_USAGE,
+"ERROR: option -c requires at least one system mode PMC to be specified."
+ );
+
+ /* check for counting mode options without a counting PMC */
+ if ((args.pa_required & FLAG_HAS_COUNTING_PMCS) &&
+ (args.pa_flags & FLAG_HAS_COUNTING_PMCS) == 0)
+ errx(EX_USAGE,
+"ERROR: options -C, -W and -o require at least one counting mode PMC to be specified."
+ );
+
+ /* check for sampling mode options without a sampling PMC spec */
+ if ((args.pa_required & FLAG_HAS_SAMPLING_PMCS) &&
+ (args.pa_flags & FLAG_HAS_SAMPLING_PMCS) == 0)
+ errx(EX_USAGE,
+"ERROR: options -N, -n and -O require at least one sampling mode PMC to be specified."
+ );
+
+ /* check if -g/-G/-m/-T are being used correctly */
+ if ((args.pa_flags & FLAG_DO_ANALYSIS) &&
+ !(args.pa_flags & (FLAG_HAS_SAMPLING_PMCS|FLAG_READ_LOGFILE)))
+ errx(EX_USAGE,
+"ERROR: options -g/-G/-m/-T require sampling PMCs or -R to be specified."
+ );
+
+ /* check if -e was specified without -g */
+ if ((args.pa_flags & FLAG_DO_WIDE_GPROF_HC) &&
+ !(args.pa_flags & FLAG_DO_GPROF))
+ errx(EX_USAGE,
+"ERROR: option -e requires gprof mode to be specified."
+ );
+
+ /* check if -O was spuriously specified */
+ if ((args.pa_flags & FLAG_HAS_OUTPUT_LOGFILE) &&
+ (args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) == 0)
+ errx(EX_USAGE,
+"ERROR: option -O is used only with options -E, -P, -S and -W."
+ );
+
+ /* -k kernel path require -g/-G/-m/-T or -R */
+ if ((args.pa_flags & FLAG_HAS_KERNELPATH) &&
+ (args.pa_flags & FLAG_DO_ANALYSIS) == 0 &&
+ (args.pa_flags & FLAG_READ_LOGFILE) == 0)
+ errx(EX_USAGE, "ERROR: option -k is only used with -g/-R/-m/-T.");
+
+ /* -D only applies to gprof output mode (-g) */
+ if ((args.pa_flags & FLAG_HAS_SAMPLESDIR) &&
+ (args.pa_flags & FLAG_DO_GPROF) == 0)
+ errx(EX_USAGE, "ERROR: option -D is only used with -g.");
+
+ /* -M mapfile requires -g or -R */
+ if (args.pa_mapfilename != NULL &&
+ (args.pa_flags & FLAG_DO_GPROF) == 0 &&
+ (args.pa_flags & FLAG_READ_LOGFILE) == 0)
+ errx(EX_USAGE, "ERROR: option -M is only used with -g/-R.");
+
+ /*
+ * Disallow textual output of sampling PMCs if counting PMCs
+ * have also been asked for, mostly because the combined output
+ * is difficult to make sense of.
+ */
+ if ((args.pa_flags & FLAG_HAS_COUNTING_PMCS) &&
+ (args.pa_flags & FLAG_HAS_SAMPLING_PMCS) &&
+ ((args.pa_flags & FLAG_HAS_OUTPUT_LOGFILE) == 0))
+ errx(EX_USAGE,
+"ERROR: option -O is required if counting and sampling PMCs are specified together."
+ );
+
+ /*
+ * Check if 'kerneldir' refers to a file rather than a
+ * directory. If so, use `dirname path` to determine the
+ * kernel directory.
+ */
+ (void) snprintf(buffer, sizeof(buffer), "%s%s", args.pa_fsroot,
+ args.pa_kernel);
+ if (stat(buffer, &sb) < 0)
+ err(EX_OSERR, "ERROR: Cannot locate kernel \"%s\"",
+ buffer);
+ if (!S_ISREG(sb.st_mode) && !S_ISDIR(sb.st_mode))
+ errx(EX_USAGE, "ERROR: \"%s\": Unsupported file type.",
+ buffer);
+ if (!S_ISDIR(sb.st_mode)) {
+ tmp = args.pa_kernel;
+ args.pa_kernel = strdup(dirname(args.pa_kernel));
+ free(tmp);
+ (void) snprintf(buffer, sizeof(buffer), "%s%s",
+ args.pa_fsroot, args.pa_kernel);
+ if (stat(buffer, &sb) < 0)
+ err(EX_OSERR, "ERROR: Cannot stat \"%s\"",
+ buffer);
+ if (!S_ISDIR(sb.st_mode))
+ errx(EX_USAGE,
+ "ERROR: \"%s\" is not a directory.",
+ buffer);
+ }
+
+ /*
+ * If we have a callgraph be created, select the outputfile.
+ */
+ if (args.pa_flags & FLAG_DO_CALLGRAPHS) {
+ if (strcmp(graphfilename, "-") == 0)
+ args.pa_graphfile = args.pa_printfile;
+ else {
+ args.pa_graphfile = fopen(graphfilename, "w");
+ if (args.pa_graphfile == NULL)
+ err(EX_OSERR,
+ "ERROR: cannot open \"%s\" for writing",
+ graphfilename);
+ }
+ }
+ if (args.pa_flags & FLAG_DO_ANNOTATE) {
+ args.pa_graphfile = fopen(graphfilename, "w");
+ if (args.pa_graphfile == NULL)
+ err(EX_OSERR, "ERROR: cannot open \"%s\" for writing",
+ graphfilename);
+ }
+
+ /* if we've been asked to process a log file, skip init */
+ if ((args.pa_flags & FLAG_READ_LOGFILE) == 0) {
+ if (pmc_init() < 0)
+ err(EX_UNAVAILABLE,
+ "ERROR: Initialization of the pmc(3) library failed"
+ );
+
+ if ((npmc = pmc_npmc(0)) < 0) /* assume all CPUs are identical */
+ err(EX_OSERR,
+"ERROR: Cannot determine the number of PMCs on CPU %d",
+ 0);
+ }
+
+ /* Allocate a kqueue */
+ if ((pmcstat_kq = kqueue()) < 0)
+ err(EX_OSERR, "ERROR: Cannot allocate kqueue");
+
+ /* Setup the logfile as the source. */
+ if (args.pa_flags & FLAG_READ_LOGFILE) {
+ /*
+ * Print the log in textual form if we haven't been
+ * asked to generate profiling information.
+ */
+ if ((args.pa_flags & FLAG_DO_ANALYSIS) == 0)
+ args.pa_flags |= FLAG_DO_PRINT;
+
+ pmcstat_initialize_logging();
+ rfd = pmcstat_open_log(args.pa_inputpath,
+ PMCSTAT_OPEN_FOR_READ);
+ if ((args.pa_logparser = pmclog_open(rfd)) == NULL)
+ err(EX_OSERR, "ERROR: Cannot create parser");
+ if (fcntl(rfd, F_SETFL, O_NONBLOCK) < 0)
+ err(EX_OSERR, "ERROR: fcntl(2) failed");
+ EV_SET(&kev, rfd, EVFILT_READ, EV_ADD,
+ 0, 0, NULL);
+ if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0)
+ err(EX_OSERR, "ERROR: Cannot register kevent");
+ }
+ /*
+ * Configure the specified log file or setup a default log
+ * consumer via a pipe.
+ */
+ if (args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) {
+ if (args.pa_outputpath)
+ args.pa_logfd = pmcstat_open_log(args.pa_outputpath,
+ PMCSTAT_OPEN_FOR_WRITE);
+ else {
+ /*
+ * process the log on the fly by reading it in
+ * through a pipe.
+ */
+ if (pipe(pipefd) < 0)
+ err(EX_OSERR, "ERROR: pipe(2) failed");
+
+ if (fcntl(pipefd[READPIPEFD], F_SETFL, O_NONBLOCK) < 0)
+ err(EX_OSERR, "ERROR: fcntl(2) failed");
+
+ EV_SET(&kev, pipefd[READPIPEFD], EVFILT_READ, EV_ADD,
+ 0, 0, NULL);
+
+ if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0)
+ err(EX_OSERR, "ERROR: Cannot register kevent");
+
+ args.pa_logfd = pipefd[WRITEPIPEFD];
+
+ args.pa_flags |= FLAG_HAS_PIPE;
+ if ((args.pa_flags & FLAG_DO_TOP) == 0)
+ args.pa_flags |= FLAG_DO_PRINT;
+ args.pa_logparser = pmclog_open(pipefd[READPIPEFD]);
+ }
+
+ if (pmc_configure_logfile(args.pa_logfd) < 0)
+ err(EX_OSERR, "ERROR: Cannot configure log file");
+ }
+
+ /* remember to check for driver errors if we are sampling or logging */
+ check_driver_stats = (args.pa_flags & FLAG_HAS_SAMPLING_PMCS) ||
+ (args.pa_flags & FLAG_HAS_OUTPUT_LOGFILE);
+
+ /*
+ if (args.pa_flags & FLAG_READ_LOGFILE) {
+ * Allocate PMCs.
+ */
+
+ STAILQ_FOREACH(ev, &args.pa_events, ev_next) {
+ if (pmc_allocate(ev->ev_spec, ev->ev_mode,
+ ev->ev_flags, ev->ev_cpu, &ev->ev_pmcid) < 0)
+ err(EX_OSERR,
+"ERROR: Cannot allocate %s-mode pmc with specification \"%s\"",
+ PMC_IS_SYSTEM_MODE(ev->ev_mode) ?
+ "system" : "process", ev->ev_spec);
+
+ if (PMC_IS_SAMPLING_MODE(ev->ev_mode) &&
+ pmc_set(ev->ev_pmcid, ev->ev_count) < 0)
+ err(EX_OSERR,
+ "ERROR: Cannot set sampling count for PMC \"%s\"",
+ ev->ev_name);
+ }
+
+ /* compute printout widths */
+ STAILQ_FOREACH(ev, &args.pa_events, ev_next) {
+ int counter_width;
+ int display_width;
+ int header_width;
+
+ (void) pmc_width(ev->ev_pmcid, &counter_width);
+ header_width = strlen(ev->ev_name) + 2; /* prefix '%c/' */
+ display_width = (int) floor(counter_width / 3.32193) + 1;
+
+ if (PMC_IS_SYSTEM_MODE(ev->ev_mode))
+ header_width += 3; /* 2 digit CPU number + '/' */
+
+ if (header_width > display_width) {
+ ev->ev_fieldskip = 0;
+ ev->ev_fieldwidth = header_width;
+ } else {
+ ev->ev_fieldskip = display_width -
+ header_width;
+ ev->ev_fieldwidth = display_width;
+ }
+ }
+
+ /*
+ * If our output is being set to a terminal, register a handler
+ * for window size changes.
+ */
+
+ if (isatty(fileno(args.pa_printfile))) {
+
+ if (ioctl(fileno(args.pa_printfile), TIOCGWINSZ, &ws) < 0)
+ err(EX_OSERR, "ERROR: Cannot determine window size");
+
+ pmcstat_displayheight = ws.ws_row - 1;
+ pmcstat_displaywidth = ws.ws_col - 1;
+
+ EV_SET(&kev, SIGWINCH, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
+
+ if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0)
+ err(EX_OSERR,
+ "ERROR: Cannot register kevent for SIGWINCH");
+
+ args.pa_toptty = 1;
+ }
+
+ /*
+ * Listen to key input in top mode.
+ */
+ if (args.pa_flags & FLAG_DO_TOP) {
+ EV_SET(&kev, fileno(stdin), EVFILT_READ, EV_ADD, 0, 0, NULL);
+ if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0)
+ err(EX_OSERR, "ERROR: Cannot register kevent");
+ }
+
+ EV_SET(&kev, SIGINT, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
+ if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0)
+ err(EX_OSERR, "ERROR: Cannot register kevent for SIGINT");
+
+ EV_SET(&kev, SIGIO, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
+ if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0)
+ err(EX_OSERR, "ERROR: Cannot register kevent for SIGIO");
+
+ /*
+ * An exec() failure of a forked child is signalled by the
+ * child sending the parent a SIGCHLD. We don't register an
+ * actual signal handler for SIGCHLD, but instead use our
+ * kqueue to pick up the signal.
+ */
+ EV_SET(&kev, SIGCHLD, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
+ if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0)
+ err(EX_OSERR, "ERROR: Cannot register kevent for SIGCHLD");
+
+ /*
+ * Setup a timer if we have counting mode PMCs needing to be printed or
+ * top mode plugin is active.
+ */
+ if (((args.pa_flags & FLAG_HAS_COUNTING_PMCS) &&
+ (args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) == 0) ||
+ (args.pa_flags & FLAG_DO_TOP)) {
+ EV_SET(&kev, 0, EVFILT_TIMER, EV_ADD, 0,
+ args.pa_interval * 1000, NULL);
+
+ if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0)
+ err(EX_OSERR,
+ "ERROR: Cannot register kevent for timer");
+ }
+
+ /*
+ * Setup a duration timer if we have sampling mode PMCs and
+ * a duration time is set
+ */
+ if ((args.pa_flags & FLAG_HAS_SAMPLING_PMCS) &&
+ (args.pa_flags & FLAG_HAS_DURATION)) {
+ EV_SET(&kev, 0, EVFILT_TIMER, EV_ADD, 0,
+ args.pa_duration * 1000, NULL);
+
+ if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0)
+ err(EX_OSERR, "ERROR: Cannot register kevent for "
+ "time duration");
+ }
+
+ /* attach PMCs to the target process, starting it if specified */
+ if (args.pa_flags & FLAG_HAS_COMMANDLINE)
+ pmcstat_create_process();
+
+ if (check_driver_stats && pmc_get_driver_stats(&ds_start) < 0)
+ err(EX_OSERR, "ERROR: Cannot retrieve driver statistics");
+
+ /* Attach process pmcs to the target process. */
+ if (args.pa_flags & (FLAG_HAS_TARGET | FLAG_HAS_COMMANDLINE)) {
+ if (SLIST_EMPTY(&args.pa_targets))
+ errx(EX_DATAERR,
+ "ERROR: No matching target processes.");
+ if (args.pa_flags & FLAG_HAS_PROCESS_PMCS)
+ pmcstat_attach_pmcs();
+
+ if (pmcstat_kvm) {
+ kvm_close(pmcstat_kvm);
+ pmcstat_kvm = NULL;
+ }
+ }
+
+ /* start the pmcs */
+ pmcstat_start_pmcs();
+
+ /* start the (commandline) process if needed */
+ if (args.pa_flags & FLAG_HAS_COMMANDLINE)
+ pmcstat_start_process();
+
+ /* initialize logging */
+ pmcstat_initialize_logging();
+
+ /* Handle SIGINT using the kqueue loop */
+ sa.sa_handler = SIG_IGN;
+ sa.sa_flags = 0;
+ (void) sigemptyset(&sa.sa_mask);
+
+ if (sigaction(SIGINT, &sa, NULL) < 0)
+ err(EX_OSERR, "ERROR: Cannot install signal handler");
+
+ /*
+ * Setup the top mode display.
+ */
+ if (args.pa_flags & FLAG_DO_TOP) {
+ args.pa_flags &= ~FLAG_DO_PRINT;
+
+ if (args.pa_toptty) {
+ /*
+ * Init ncurses.
+ */
+ initscr();
+ if(has_colors() == TRUE) {
+ args.pa_topcolor = 1;
+ start_color();
+ use_default_colors();
+ pair_content(0, &cf, &cb);
+ init_pair(1, COLOR_RED, cb);
+ init_pair(2, COLOR_YELLOW, cb);
+ init_pair(3, COLOR_GREEN, cb);
+ }
+ cbreak();
+ noecho();
+ nonl();
+ nodelay(stdscr, 1);
+ intrflush(stdscr, FALSE);
+ keypad(stdscr, TRUE);
+ clear();
+ /* Get terminal width / height with ncurses. */
+ getmaxyx(stdscr,
+ pmcstat_displayheight, pmcstat_displaywidth);
+ pmcstat_displayheight--; pmcstat_displaywidth--;
+ atexit(pmcstat_topexit);
+ }
+ }
+
+ /*
+ * loop till either the target process (if any) exits, or we
+ * are killed by a SIGINT or we reached the time duration.
+ */
+ runstate = PMCSTAT_RUNNING;
+ do_print = do_read = 0;
+ do {
+ if ((c = kevent(pmcstat_kq, NULL, 0, &kev, 1, NULL)) <= 0) {
+ if (errno != EINTR)
+ err(EX_OSERR, "ERROR: kevent failed");
+ else
+ continue;
+ }
+
+ if (kev.flags & EV_ERROR)
+ errc(EX_OSERR, kev.data, "ERROR: kevent failed");
+
+ switch (kev.filter) {
+ case EVFILT_PROC: /* target has exited */
+ runstate = pmcstat_close_log();
+ do_print = 1;
+ break;
+
+ case EVFILT_READ: /* log file data is present */
+ if (kev.ident == (unsigned)fileno(stdin) &&
+ (args.pa_flags & FLAG_DO_TOP)) {
+ if (pmcstat_keypress_log())
+ runstate = pmcstat_close_log();
+ } else {
+ do_read = 0;
+ runstate = pmcstat_process_log();
+ }
+ break;
+
+ case EVFILT_SIGNAL:
+ if (kev.ident == SIGCHLD) {
+ /*
+ * The child process sends us a
+ * SIGCHLD if its exec() failed. We
+ * wait for it to exit and then exit
+ * ourselves.
+ */
+ (void) wait(&c);
+ runstate = PMCSTAT_FINISHED;
+ } else if (kev.ident == SIGIO) {
+ /*
+ * We get a SIGIO if a PMC loses all
+ * of its targets, or if logfile
+ * writes encounter an error.
+ */
+ runstate = pmcstat_close_log();
+ do_print = 1; /* print PMCs at exit */
+ } else if (kev.ident == SIGINT) {
+ /* Kill the child process if we started it */
+ if (args.pa_flags & FLAG_HAS_COMMANDLINE)
+ pmcstat_kill_process();
+ runstate = pmcstat_close_log();
+ } else if (kev.ident == SIGWINCH) {
+ if (ioctl(fileno(args.pa_printfile),
+ TIOCGWINSZ, &ws) < 0)
+ err(EX_OSERR,
+ "ERROR: Cannot determine window size");
+ pmcstat_displayheight = ws.ws_row - 1;
+ pmcstat_displaywidth = ws.ws_col - 1;
+ } else
+ assert(0);
+
+ break;
+
+ case EVFILT_TIMER:
+ /* time duration reached, exit */
+ if (args.pa_flags & FLAG_HAS_DURATION) {
+ runstate = PMCSTAT_FINISHED;
+ break;
+ }
+ /* print out counting PMCs */
+ if ((args.pa_flags & FLAG_DO_TOP) &&
+ pmc_flush_logfile() == 0)
+ do_read = 1;
+ do_print = 1;
+ break;
+
+ }
+
+ if (do_print && !do_read) {
+ if ((args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) == 0) {
+ pmcstat_print_pmcs();
+ if (runstate == PMCSTAT_FINISHED &&
+ /* final newline */
+ (args.pa_flags & FLAG_DO_PRINT) == 0)
+ (void) fprintf(args.pa_printfile, "\n");
+ }
+ if (args.pa_flags & FLAG_DO_TOP)
+ pmcstat_display_log();
+ do_print = 0;
+ }
+
+ } while (runstate != PMCSTAT_FINISHED);
+
+ if ((args.pa_flags & FLAG_DO_TOP) && args.pa_toptty) {
+ pmcstat_topexit();
+ args.pa_toptty = 0;
+ }
+
+ /* flush any pending log entries */
+ if (args.pa_flags & (FLAG_HAS_OUTPUT_LOGFILE | FLAG_HAS_PIPE))
+ pmc_close_logfile();
+
+ pmcstat_cleanup();
+
+ free(args.pa_kernel);
+
+ /* check if the driver lost any samples or events */
+ if (check_driver_stats) {
+ if (pmc_get_driver_stats(&ds_end) < 0)
+ err(EX_OSERR,
+ "ERROR: Cannot retrieve driver statistics");
+ if (ds_start.pm_intr_bufferfull != ds_end.pm_intr_bufferfull &&
+ args.pa_verbosity > 0)
+ warnx(
+"WARNING: sampling was paused at least %u time%s.\n"
+"Please consider tuning the \"kern.hwpmc.nsamples\" tunable.",
+ ds_end.pm_intr_bufferfull -
+ ds_start.pm_intr_bufferfull,
+ ((ds_end.pm_intr_bufferfull -
+ ds_start.pm_intr_bufferfull) != 1) ? "s" : ""
+ );
+ if (ds_start.pm_buffer_requests_failed !=
+ ds_end.pm_buffer_requests_failed &&
+ args.pa_verbosity > 0)
+ warnx(
+"WARNING: at least %u event%s were discarded while running.\n"
+"Please consider tuning the \"kern.hwpmc.nbuffers\" tunable.",
+ ds_end.pm_buffer_requests_failed -
+ ds_start.pm_buffer_requests_failed,
+ ((ds_end.pm_buffer_requests_failed -
+ ds_start.pm_buffer_requests_failed) != 1) ? "s" : ""
+ );
+ }
+
+ exit(EX_OK);
+}
diff --git a/usr.sbin/pmcstat/pmcstat.h b/usr.sbin/pmcstat/pmcstat.h
new file mode 100644
index 0000000..5b1d3d9
--- /dev/null
+++ b/usr.sbin/pmcstat/pmcstat.h
@@ -0,0 +1,187 @@
+/*-
+ * Copyright (c) 2005-2007, Joseph Koshy
+ * Copyright (c) 2007 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * Portions of this software were developed by A. Joseph Koshy under
+ * sponsorship from the FreeBSD Foundation and Google, 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.
+ *
+ * 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 _PMCSTAT_H_
+#define _PMCSTAT_H_
+
+#include <sys/_cpuset.h>
+
+#define FLAG_HAS_TARGET 0x00000001 /* process target */
+#define FLAG_HAS_WAIT_INTERVAL 0x00000002 /* -w secs */
+#define FLAG_HAS_OUTPUT_LOGFILE 0x00000004 /* -O file or pipe */
+#define FLAG_HAS_COMMANDLINE 0x00000008 /* command */
+#define FLAG_HAS_SAMPLING_PMCS 0x00000010 /* -S or -P */
+#define FLAG_HAS_COUNTING_PMCS 0x00000020 /* -s or -p */
+#define FLAG_HAS_PROCESS_PMCS 0x00000040 /* -P or -p */
+#define FLAG_HAS_SYSTEM_PMCS 0x00000080 /* -S or -s */
+#define FLAG_HAS_PIPE 0x00000100 /* implicit log */
+#define FLAG_READ_LOGFILE 0x00000200 /* -R file */
+#define FLAG_DO_GPROF 0x00000400 /* -g */
+#define FLAG_HAS_SAMPLESDIR 0x00000800 /* -D dir */
+#define FLAG_HAS_KERNELPATH 0x00001000 /* -k kernel */
+#define FLAG_DO_PRINT 0x00002000 /* -o */
+#define FLAG_DO_CALLGRAPHS 0x00004000 /* -G or -F */
+#define FLAG_DO_ANNOTATE 0x00008000 /* -m */
+#define FLAG_DO_TOP 0x00010000 /* -T */
+#define FLAG_DO_ANALYSIS 0x00020000 /* -g or -G or -m or -T */
+#define FLAGS_HAS_CPUMASK 0x00040000 /* -c */
+#define FLAG_HAS_DURATION 0x00080000 /* -l secs */
+#define FLAG_DO_WIDE_GPROF_HC 0x00100000 /* -e */
+
+#define DEFAULT_SAMPLE_COUNT 65536
+#define DEFAULT_WAIT_INTERVAL 5.0
+#define DEFAULT_DISPLAY_HEIGHT 256 /* file virtual height */
+#define DEFAULT_DISPLAY_WIDTH 1024 /* file virtual width */
+#define DEFAULT_BUFFER_SIZE 4096
+#define DEFAULT_CALLGRAPH_DEPTH 16
+
+#define PRINT_HEADER_PREFIX "# "
+#define READPIPEFD 0
+#define WRITEPIPEFD 1
+#define NPIPEFD 2
+
+#define NSOCKPAIRFD 2
+#define PARENTSOCKET 0
+#define CHILDSOCKET 1
+
+#define PMCSTAT_OPEN_FOR_READ 0
+#define PMCSTAT_OPEN_FOR_WRITE 1
+#define PMCSTAT_DEFAULT_NW_HOST "localhost"
+#define PMCSTAT_DEFAULT_NW_PORT "9000"
+#define PMCSTAT_NHASH 256
+#define PMCSTAT_HASH_MASK 0xFF
+
+#define PMCSTAT_LDD_COMMAND "/usr/bin/ldd"
+
+#define PMCSTAT_PRINT_ENTRY(T,...) do { \
+ (void) fprintf(args.pa_printfile, "%-9s", T); \
+ (void) fprintf(args.pa_printfile, " " __VA_ARGS__); \
+ (void) fprintf(args.pa_printfile, "\n"); \
+ } while (0)
+
+#define PMCSTAT_PL_NONE 0
+#define PMCSTAT_PL_CALLGRAPH 1
+#define PMCSTAT_PL_GPROF 2
+#define PMCSTAT_PL_ANNOTATE 3
+#define PMCSTAT_PL_CALLTREE 4
+#define PMCSTAT_PL_ANNOTATE_CG 5
+
+#define PMCSTAT_TOP_DELTA 0
+#define PMCSTAT_TOP_ACCUM 1
+
+#define min(A,B) ((A) < (B) ? (A) : (B))
+#define max(A,B) ((A) > (B) ? (A) : (B))
+
+enum pmcstat_state {
+ PMCSTAT_FINISHED = 0,
+ PMCSTAT_EXITING = 1,
+ PMCSTAT_RUNNING = 2
+};
+
+struct pmcstat_ev {
+ STAILQ_ENTRY(pmcstat_ev) ev_next;
+ int ev_count; /* associated count if in sampling mode */
+ uint32_t ev_cpu; /* cpus for this event */
+ int ev_cumulative; /* show cumulative counts */
+ int ev_flags; /* PMC_F_* */
+ int ev_fieldskip; /* #leading spaces */
+ int ev_fieldwidth; /* print width */
+ enum pmc_mode ev_mode; /* desired mode */
+ char *ev_name; /* (derived) event name */
+ pmc_id_t ev_pmcid; /* allocated ID */
+ pmc_value_t ev_saved; /* for incremental counts */
+ char *ev_spec; /* event specification */
+};
+
+struct pmcstat_target {
+ SLIST_ENTRY(pmcstat_target) pt_next;
+ pid_t pt_pid;
+};
+
+struct pmcstat_args {
+ int pa_flags; /* argument flags */
+ int pa_required; /* required features */
+ int pa_pplugin; /* pre-processing plugin */
+ int pa_plugin; /* analysis plugin */
+ int pa_verbosity; /* verbosity level */
+ FILE *pa_printfile; /* where to send printed output */
+ int pa_logfd; /* output log file */
+ char *pa_inputpath; /* path to input log */
+ char *pa_outputpath; /* path to output log */
+ void *pa_logparser; /* log file parser */
+ const char *pa_fsroot; /* FS root where executables reside */
+ char *pa_kernel; /* pathname of the kernel */
+ const char *pa_samplesdir; /* directory for profile files */
+ const char *pa_mapfilename;/* mapfile name */
+ FILE *pa_graphfile; /* where to send the callgraph */
+ int pa_graphdepth; /* print depth for callgraphs */
+ double pa_interval; /* printing interval in seconds */
+ cpuset_t pa_cpumask; /* filter for CPUs analysed */
+ int pa_ctdumpinstr; /* dump instructions with calltree */
+ int pa_topmode; /* delta or accumulative */
+ int pa_toptty; /* output to tty or file */
+ int pa_topcolor; /* terminal support color */
+ int pa_mergepmc; /* merge PMC with same name */
+ double pa_duration; /* time duration */
+ int pa_argc;
+ char **pa_argv;
+ STAILQ_HEAD(, pmcstat_ev) pa_events;
+ SLIST_HEAD(, pmcstat_target) pa_targets;
+};
+
+extern int pmcstat_displayheight; /* current terminal height */
+extern int pmcstat_displaywidth; /* current terminal width */
+extern struct pmcstat_args args; /* command line args */
+
+/* Function prototypes */
+void pmcstat_attach_pmcs(void);
+void pmcstat_cleanup(void);
+int pmcstat_close_log(void);
+void pmcstat_create_process(void);
+void pmcstat_find_targets(const char *_arg);
+void pmcstat_initialize_logging(void);
+void pmcstat_kill_process(void);
+int pmcstat_open_log(const char *_p, int _mode);
+void pmcstat_print_counters(void);
+void pmcstat_print_headers(void);
+void pmcstat_print_pmcs(void);
+void pmcstat_show_usage(void);
+void pmcstat_shutdown_logging(void);
+void pmcstat_start_pmcs(void);
+void pmcstat_start_process(void);
+int pmcstat_process_log(void);
+int pmcstat_keypress_log(void);
+void pmcstat_display_log(void);
+void pmcstat_pluginconfigure_log(char *_opt);
+void pmcstat_topexit(void);
+
+#endif /* _PMCSTAT_H_ */
diff --git a/usr.sbin/pmcstat/pmcstat_log.c b/usr.sbin/pmcstat/pmcstat_log.c
new file mode 100644
index 0000000..ea9b547
--- /dev/null
+++ b/usr.sbin/pmcstat/pmcstat_log.c
@@ -0,0 +1,2238 @@
+/*-
+ * Copyright (c) 2005-2007, Joseph Koshy
+ * Copyright (c) 2007 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * Portions of this software were developed by A. Joseph Koshy under
+ * sponsorship from the FreeBSD Foundation and Google, 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.
+ *
+ * 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.
+ */
+
+/*
+ * Transform a hwpmc(4) log into human readable form, and into
+ * gprof(1) compatible profiles.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/endian.h>
+#include <sys/cpuset.h>
+#include <sys/gmon.h>
+#include <sys/imgact_aout.h>
+#include <sys/imgact_elf.h>
+#include <sys/mman.h>
+#include <sys/pmc.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <netinet/in.h>
+
+#include <assert.h>
+#include <curses.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <gelf.h>
+#include <libgen.h>
+#include <limits.h>
+#include <netdb.h>
+#include <pmc.h>
+#include <pmclog.h>
+#include <sysexits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "pmcstat.h"
+#include "pmcstat_log.h"
+#include "pmcstat_top.h"
+
+#define PMCSTAT_ALLOCATE 1
+
+/*
+ * PUBLIC INTERFACES
+ *
+ * pmcstat_initialize_logging() initialize this module, called first
+ * pmcstat_shutdown_logging() orderly shutdown, called last
+ * pmcstat_open_log() open an eventlog for processing
+ * pmcstat_process_log() print/convert an event log
+ * pmcstat_display_log() top mode display for the log
+ * pmcstat_close_log() finish processing an event log
+ *
+ * IMPLEMENTATION NOTES
+ *
+ * We correlate each 'callchain' or 'sample' entry seen in the event
+ * log back to an executable object in the system. Executable objects
+ * include:
+ * - program executables,
+ * - shared libraries loaded by the runtime loader,
+ * - dlopen()'ed objects loaded by the program,
+ * - the runtime loader itself,
+ * - the kernel and kernel modules.
+ *
+ * Each process that we know about is treated as a set of regions that
+ * map to executable objects. Processes are described by
+ * 'pmcstat_process' structures. Executable objects are tracked by
+ * 'pmcstat_image' structures. The kernel and kernel modules are
+ * common to all processes (they reside at the same virtual addresses
+ * for all processes). Individual processes can have their text
+ * segments and shared libraries loaded at process-specific locations.
+ *
+ * A given executable object can be in use by multiple processes
+ * (e.g., libc.so) and loaded at a different address in each.
+ * pmcstat_pcmap structures track per-image mappings.
+ *
+ * The sample log could have samples from multiple PMCs; we
+ * generate one 'gmon.out' profile per PMC.
+ *
+ * IMPLEMENTATION OF GMON OUTPUT
+ *
+ * Each executable object gets one 'gmon.out' profile, per PMC in
+ * use. Creation of 'gmon.out' profiles is done lazily. The
+ * 'gmon.out' profiles generated for a given sampling PMC are
+ * aggregates of all the samples for that particular executable
+ * object.
+ *
+ * IMPLEMENTATION OF SYSTEM-WIDE CALLGRAPH OUTPUT
+ *
+ * Each active pmcid has its own callgraph structure, described by a
+ * 'struct pmcstat_callgraph'. Given a process id and a list of pc
+ * values, we map each pc value to a tuple (image, symbol), where
+ * 'image' denotes an executable object and 'symbol' is the closest
+ * symbol that precedes the pc value. Each pc value in the list is
+ * also given a 'rank' that reflects its depth in the call stack.
+ */
+
+struct pmcstat_pmcs pmcstat_pmcs = LIST_HEAD_INITIALIZER(pmcstat_pmcs);
+
+/*
+ * All image descriptors are kept in a hash table.
+ */
+struct pmcstat_image_hash_list pmcstat_image_hash[PMCSTAT_NHASH];
+
+/*
+ * All process descriptors are kept in a hash table.
+ */
+struct pmcstat_process_hash_list pmcstat_process_hash[PMCSTAT_NHASH];
+
+struct pmcstat_stats pmcstat_stats; /* statistics */
+static int ps_samples_period; /* samples count between top refresh. */
+
+struct pmcstat_process *pmcstat_kernproc; /* kernel 'process' */
+
+#include "pmcpl_gprof.h"
+#include "pmcpl_callgraph.h"
+#include "pmcpl_annotate.h"
+#include "pmcpl_annotate_cg.h"
+#include "pmcpl_calltree.h"
+
+static struct pmc_plugins {
+ const char *pl_name; /* name */
+
+ /* configure */
+ int (*pl_configure)(char *opt);
+
+ /* init and shutdown */
+ int (*pl_init)(void);
+ void (*pl_shutdown)(FILE *mf);
+
+ /* sample processing */
+ void (*pl_process)(struct pmcstat_process *pp,
+ struct pmcstat_pmcrecord *pmcr, uint32_t nsamples,
+ uintfptr_t *cc, int usermode, uint32_t cpu);
+
+ /* image */
+ void (*pl_initimage)(struct pmcstat_image *pi);
+ void (*pl_shutdownimage)(struct pmcstat_image *pi);
+
+ /* pmc */
+ void (*pl_newpmc)(pmcstat_interned_string ps,
+ struct pmcstat_pmcrecord *pr);
+
+ /* top display */
+ void (*pl_topdisplay)(void);
+
+ /* top keypress */
+ int (*pl_topkeypress)(int c, WINDOW *w);
+
+} plugins[] = {
+ {
+ .pl_name = "none",
+ },
+ {
+ .pl_name = "callgraph",
+ .pl_init = pmcpl_cg_init,
+ .pl_shutdown = pmcpl_cg_shutdown,
+ .pl_process = pmcpl_cg_process,
+ .pl_topkeypress = pmcpl_cg_topkeypress,
+ .pl_topdisplay = pmcpl_cg_topdisplay
+ },
+ {
+ .pl_name = "gprof",
+ .pl_shutdown = pmcpl_gmon_shutdown,
+ .pl_process = pmcpl_gmon_process,
+ .pl_initimage = pmcpl_gmon_initimage,
+ .pl_shutdownimage = pmcpl_gmon_shutdownimage,
+ .pl_newpmc = pmcpl_gmon_newpmc
+ },
+ {
+ .pl_name = "annotate",
+ .pl_process = pmcpl_annotate_process
+ },
+ {
+ .pl_name = "calltree",
+ .pl_configure = pmcpl_ct_configure,
+ .pl_init = pmcpl_ct_init,
+ .pl_shutdown = pmcpl_ct_shutdown,
+ .pl_process = pmcpl_ct_process,
+ .pl_topkeypress = pmcpl_ct_topkeypress,
+ .pl_topdisplay = pmcpl_ct_topdisplay
+ },
+ {
+ .pl_name = "annotate_cg",
+ .pl_process = pmcpl_annotate_cg_process
+ },
+
+ {
+ .pl_name = NULL
+ }
+};
+
+static int pmcstat_mergepmc;
+
+int pmcstat_pmcinfilter = 0; /* PMC filter for top mode. */
+float pmcstat_threshold = 0.5; /* Cost filter for top mode. */
+
+/*
+ * Prototypes
+ */
+
+static struct pmcstat_image *pmcstat_image_from_path(pmcstat_interned_string
+ _path, int _iskernelmodule);
+static void pmcstat_image_get_aout_params(struct pmcstat_image *_image);
+static void pmcstat_image_get_elf_params(struct pmcstat_image *_image);
+static void pmcstat_image_link(struct pmcstat_process *_pp,
+ struct pmcstat_image *_i, uintfptr_t _lpc);
+
+static void pmcstat_pmcid_add(pmc_id_t _pmcid,
+ pmcstat_interned_string _name);
+
+static void pmcstat_process_aout_exec(struct pmcstat_process *_pp,
+ struct pmcstat_image *_image, uintfptr_t _entryaddr);
+static void pmcstat_process_elf_exec(struct pmcstat_process *_pp,
+ struct pmcstat_image *_image, uintfptr_t _entryaddr);
+static void pmcstat_process_exec(struct pmcstat_process *_pp,
+ pmcstat_interned_string _path, uintfptr_t _entryaddr);
+static struct pmcstat_process *pmcstat_process_lookup(pid_t _pid,
+ int _allocate);
+static int pmcstat_string_compute_hash(const char *_string);
+static void pmcstat_string_initialize(void);
+static int pmcstat_string_lookup_hash(pmcstat_interned_string _is);
+static void pmcstat_string_shutdown(void);
+static void pmcstat_stats_reset(int _reset_global);
+
+/*
+ * A simple implementation of interned strings. Each interned string
+ * is assigned a unique address, so that subsequent string compares
+ * can be done by a simple pointer comparison instead of using
+ * strcmp(). This speeds up hash table lookups and saves memory if
+ * duplicate strings are the norm.
+ */
+struct pmcstat_string {
+ LIST_ENTRY(pmcstat_string) ps_next; /* hash link */
+ int ps_len;
+ int ps_hash;
+ char *ps_string;
+};
+
+static LIST_HEAD(,pmcstat_string) pmcstat_string_hash[PMCSTAT_NHASH];
+
+/*
+ * PMC count.
+ */
+int pmcstat_npmcs;
+
+/*
+ * PMC Top mode pause state.
+ */
+static int pmcstat_pause;
+
+static void
+pmcstat_stats_reset(int reset_global)
+{
+ struct pmcstat_pmcrecord *pr;
+
+ /* Flush PMCs stats. */
+ LIST_FOREACH(pr, &pmcstat_pmcs, pr_next) {
+ pr->pr_samples = 0;
+ pr->pr_dubious_frames = 0;
+ }
+ ps_samples_period = 0;
+
+ /* Flush global stats. */
+ if (reset_global)
+ bzero(&pmcstat_stats, sizeof(struct pmcstat_stats));
+}
+
+/*
+ * Compute a 'hash' value for a string.
+ */
+
+static int
+pmcstat_string_compute_hash(const char *s)
+{
+ unsigned hash;
+
+ for (hash = 2166136261; *s; s++)
+ hash = (hash ^ *s) * 16777619;
+
+ return (hash & PMCSTAT_HASH_MASK);
+}
+
+/*
+ * Intern a copy of string 's', and return a pointer to the
+ * interned structure.
+ */
+
+pmcstat_interned_string
+pmcstat_string_intern(const char *s)
+{
+ struct pmcstat_string *ps;
+ const struct pmcstat_string *cps;
+ int hash, len;
+
+ if ((cps = pmcstat_string_lookup(s)) != NULL)
+ return (cps);
+
+ hash = pmcstat_string_compute_hash(s);
+ len = strlen(s);
+
+ if ((ps = malloc(sizeof(*ps))) == NULL)
+ err(EX_OSERR, "ERROR: Could not intern string");
+ ps->ps_len = len;
+ ps->ps_hash = hash;
+ ps->ps_string = strdup(s);
+ LIST_INSERT_HEAD(&pmcstat_string_hash[hash], ps, ps_next);
+ return ((pmcstat_interned_string) ps);
+}
+
+const char *
+pmcstat_string_unintern(pmcstat_interned_string str)
+{
+ const char *s;
+
+ s = ((const struct pmcstat_string *) str)->ps_string;
+ return (s);
+}
+
+pmcstat_interned_string
+pmcstat_string_lookup(const char *s)
+{
+ struct pmcstat_string *ps;
+ int hash, len;
+
+ hash = pmcstat_string_compute_hash(s);
+ len = strlen(s);
+
+ LIST_FOREACH(ps, &pmcstat_string_hash[hash], ps_next)
+ if (ps->ps_len == len && ps->ps_hash == hash &&
+ strcmp(ps->ps_string, s) == 0)
+ return (ps);
+ return (NULL);
+}
+
+static int
+pmcstat_string_lookup_hash(pmcstat_interned_string s)
+{
+ const struct pmcstat_string *ps;
+
+ ps = (const struct pmcstat_string *) s;
+ return (ps->ps_hash);
+}
+
+/*
+ * Initialize the string interning facility.
+ */
+
+static void
+pmcstat_string_initialize(void)
+{
+ int i;
+
+ for (i = 0; i < PMCSTAT_NHASH; i++)
+ LIST_INIT(&pmcstat_string_hash[i]);
+}
+
+/*
+ * Destroy the string table, free'ing up space.
+ */
+
+static void
+pmcstat_string_shutdown(void)
+{
+ int i;
+ struct pmcstat_string *ps, *pstmp;
+
+ for (i = 0; i < PMCSTAT_NHASH; i++)
+ LIST_FOREACH_SAFE(ps, &pmcstat_string_hash[i], ps_next,
+ pstmp) {
+ LIST_REMOVE(ps, ps_next);
+ free(ps->ps_string);
+ free(ps);
+ }
+}
+
+/*
+ * Determine whether a given executable image is an A.OUT object, and
+ * if so, fill in its parameters from the text file.
+ * Sets image->pi_type.
+ */
+
+static void
+pmcstat_image_get_aout_params(struct pmcstat_image *image)
+{
+ int fd;
+ ssize_t nbytes;
+ struct exec ex;
+ const char *path;
+ char buffer[PATH_MAX];
+
+ path = pmcstat_string_unintern(image->pi_execpath);
+ assert(path != NULL);
+
+ if (image->pi_iskernelmodule)
+ errx(EX_SOFTWARE,
+ "ERROR: a.out kernel modules are unsupported \"%s\"", path);
+
+ (void) snprintf(buffer, sizeof(buffer), "%s%s",
+ args.pa_fsroot, path);
+
+ if ((fd = open(buffer, O_RDONLY, 0)) < 0 ||
+ (nbytes = read(fd, &ex, sizeof(ex))) < 0) {
+ if (args.pa_verbosity >= 2)
+ warn("WARNING: Cannot determine type of \"%s\"",
+ path);
+ image->pi_type = PMCSTAT_IMAGE_INDETERMINABLE;
+ if (fd != -1)
+ (void) close(fd);
+ return;
+ }
+
+ (void) close(fd);
+
+ if ((unsigned) nbytes != sizeof(ex) ||
+ N_BADMAG(ex))
+ return;
+
+ image->pi_type = PMCSTAT_IMAGE_AOUT;
+
+ /* TODO: the rest of a.out processing */
+
+ return;
+}
+
+/*
+ * Helper function.
+ */
+
+static int
+pmcstat_symbol_compare(const void *a, const void *b)
+{
+ const struct pmcstat_symbol *sym1, *sym2;
+
+ sym1 = (const struct pmcstat_symbol *) a;
+ sym2 = (const struct pmcstat_symbol *) b;
+
+ if (sym1->ps_end <= sym2->ps_start)
+ return (-1);
+ if (sym1->ps_start >= sym2->ps_end)
+ return (1);
+ return (0);
+}
+
+/*
+ * Map an address to a symbol in an image.
+ */
+
+struct pmcstat_symbol *
+pmcstat_symbol_search(struct pmcstat_image *image, uintfptr_t addr)
+{
+ struct pmcstat_symbol sym;
+
+ if (image->pi_symbols == NULL)
+ return (NULL);
+
+ sym.ps_name = NULL;
+ sym.ps_start = addr;
+ sym.ps_end = addr + 1;
+
+ return (bsearch((void *) &sym, image->pi_symbols,
+ image->pi_symcount, sizeof(struct pmcstat_symbol),
+ pmcstat_symbol_compare));
+}
+
+/*
+ * Add the list of symbols in the given section to the list associated
+ * with the object.
+ */
+static void
+pmcstat_image_add_symbols(struct pmcstat_image *image, Elf *e,
+ Elf_Scn *scn, GElf_Shdr *sh)
+{
+ int firsttime;
+ size_t n, newsyms, nshsyms, nfuncsyms;
+ struct pmcstat_symbol *symptr;
+ char *fnname;
+ GElf_Sym sym;
+ Elf_Data *data;
+
+ if ((data = elf_getdata(scn, NULL)) == NULL)
+ return;
+
+ /*
+ * Determine the number of functions named in this
+ * section.
+ */
+
+ nshsyms = sh->sh_size / sh->sh_entsize;
+ for (n = nfuncsyms = 0; n < nshsyms; n++) {
+ if (gelf_getsym(data, (int) n, &sym) != &sym)
+ return;
+ if (GELF_ST_TYPE(sym.st_info) == STT_FUNC)
+ nfuncsyms++;
+ }
+
+ if (nfuncsyms == 0)
+ return;
+
+ /*
+ * Allocate space for the new entries.
+ */
+ firsttime = image->pi_symbols == NULL;
+ symptr = realloc(image->pi_symbols,
+ sizeof(*symptr) * (image->pi_symcount + nfuncsyms));
+ if (symptr == image->pi_symbols) /* realloc() failed. */
+ return;
+ image->pi_symbols = symptr;
+
+ /*
+ * Append new symbols to the end of the current table.
+ */
+ symptr += image->pi_symcount;
+
+ for (n = newsyms = 0; n < nshsyms; n++) {
+ if (gelf_getsym(data, (int) n, &sym) != &sym)
+ return;
+ if (GELF_ST_TYPE(sym.st_info) != STT_FUNC)
+ continue;
+ if (sym.st_shndx == STN_UNDEF)
+ continue;
+
+ if (!firsttime && pmcstat_symbol_search(image, sym.st_value))
+ continue; /* We've seen this symbol already. */
+
+ if ((fnname = elf_strptr(e, sh->sh_link, sym.st_name))
+ == NULL)
+ continue;
+#ifdef __arm__
+ /* Remove spurious ARM function name. */
+ if (fnname[0] == '$' &&
+ (fnname[1] == 'a' || fnname[1] == 't' ||
+ fnname[1] == 'd') &&
+ fnname[2] == '\0')
+ continue;
+#endif
+
+ symptr->ps_name = pmcstat_string_intern(fnname);
+ symptr->ps_start = sym.st_value - image->pi_vaddr;
+ symptr->ps_end = symptr->ps_start + sym.st_size;
+ symptr++;
+
+ newsyms++;
+ }
+
+ image->pi_symcount += newsyms;
+ if (image->pi_symcount == 0)
+ return;
+
+ assert(newsyms <= nfuncsyms);
+
+ /*
+ * Return space to the system if there were duplicates.
+ */
+ if (newsyms < nfuncsyms)
+ image->pi_symbols = realloc(image->pi_symbols,
+ sizeof(*symptr) * image->pi_symcount);
+
+ /*
+ * Keep the list of symbols sorted.
+ */
+ qsort(image->pi_symbols, image->pi_symcount, sizeof(*symptr),
+ pmcstat_symbol_compare);
+
+ /*
+ * Deal with function symbols that have a size of 'zero' by
+ * making them extend to the next higher address. These
+ * symbols are usually defined in assembly code.
+ */
+ for (symptr = image->pi_symbols;
+ symptr < image->pi_symbols + (image->pi_symcount - 1);
+ symptr++)
+ if (symptr->ps_start == symptr->ps_end)
+ symptr->ps_end = (symptr+1)->ps_start;
+}
+
+/*
+ * Examine an ELF file to determine the size of its text segment.
+ * Sets image->pi_type if anything conclusive can be determined about
+ * this image.
+ */
+
+static void
+pmcstat_image_get_elf_params(struct pmcstat_image *image)
+{
+ int fd;
+ size_t i, nph, nsh;
+ const char *path, *elfbase;
+ char *p, *endp;
+ uintfptr_t minva, maxva;
+ Elf *e;
+ Elf_Scn *scn;
+ GElf_Ehdr eh;
+ GElf_Phdr ph;
+ GElf_Shdr sh;
+ enum pmcstat_image_type image_type;
+ char buffer[PATH_MAX];
+
+ assert(image->pi_type == PMCSTAT_IMAGE_UNKNOWN);
+
+ image->pi_start = minva = ~(uintfptr_t) 0;
+ image->pi_end = maxva = (uintfptr_t) 0;
+ image->pi_type = image_type = PMCSTAT_IMAGE_INDETERMINABLE;
+ image->pi_isdynamic = 0;
+ image->pi_dynlinkerpath = NULL;
+ image->pi_vaddr = 0;
+
+ path = pmcstat_string_unintern(image->pi_execpath);
+ assert(path != NULL);
+
+ /*
+ * Look for kernel modules under FSROOT/KERNELPATH/NAME,
+ * and user mode executable objects under FSROOT/PATHNAME.
+ */
+ if (image->pi_iskernelmodule)
+ (void) snprintf(buffer, sizeof(buffer), "%s%s/%s",
+ args.pa_fsroot, args.pa_kernel, path);
+ else
+ (void) snprintf(buffer, sizeof(buffer), "%s%s",
+ args.pa_fsroot, path);
+
+ e = NULL;
+ if ((fd = open(buffer, O_RDONLY, 0)) < 0 ||
+ (e = elf_begin(fd, ELF_C_READ, NULL)) == NULL ||
+ (elf_kind(e) != ELF_K_ELF)) {
+ if (args.pa_verbosity >= 2)
+ warnx("WARNING: Cannot determine the type of \"%s\".",
+ buffer);
+ goto done;
+ }
+
+ if (gelf_getehdr(e, &eh) != &eh) {
+ warnx(
+ "WARNING: Cannot retrieve the ELF Header for \"%s\": %s.",
+ buffer, elf_errmsg(-1));
+ goto done;
+ }
+
+ if (eh.e_type != ET_EXEC && eh.e_type != ET_DYN &&
+ !(image->pi_iskernelmodule && eh.e_type == ET_REL)) {
+ warnx("WARNING: \"%s\" is of an unsupported ELF type.",
+ buffer);
+ goto done;
+ }
+
+ image_type = eh.e_ident[EI_CLASS] == ELFCLASS32 ?
+ PMCSTAT_IMAGE_ELF32 : PMCSTAT_IMAGE_ELF64;
+
+ /*
+ * Determine the virtual address where an executable would be
+ * loaded. Additionally, for dynamically linked executables,
+ * save the pathname to the runtime linker.
+ */
+ if (eh.e_type == ET_EXEC) {
+ if (elf_getphnum(e, &nph) == 0) {
+ warnx(
+"WARNING: Could not determine the number of program headers in \"%s\": %s.",
+ buffer,
+ elf_errmsg(-1));
+ goto done;
+ }
+ for (i = 0; i < eh.e_phnum; i++) {
+ if (gelf_getphdr(e, i, &ph) != &ph) {
+ warnx(
+"WARNING: Retrieval of PHDR entry #%ju in \"%s\" failed: %s.",
+ (uintmax_t) i, buffer, elf_errmsg(-1));
+ goto done;
+ }
+ switch (ph.p_type) {
+ case PT_DYNAMIC:
+ image->pi_isdynamic = 1;
+ break;
+ case PT_INTERP:
+ if ((elfbase = elf_rawfile(e, NULL)) == NULL) {
+ warnx(
+"WARNING: Cannot retrieve the interpreter for \"%s\": %s.",
+ buffer, elf_errmsg(-1));
+ goto done;
+ }
+ image->pi_dynlinkerpath =
+ pmcstat_string_intern(elfbase +
+ ph.p_offset);
+ break;
+ case PT_LOAD:
+ if ((ph.p_flags & PF_X) != 0 &&
+ (ph.p_offset & (-ph.p_align)) == 0)
+ image->pi_vaddr = ph.p_vaddr & (-ph.p_align);
+ break;
+ }
+ }
+ }
+
+ /*
+ * Get the min and max VA associated with this ELF object.
+ */
+ if (elf_getshnum(e, &nsh) == 0) {
+ warnx(
+"WARNING: Could not determine the number of sections for \"%s\": %s.",
+ buffer, elf_errmsg(-1));
+ goto done;
+ }
+
+ for (i = 0; i < nsh; i++) {
+ if ((scn = elf_getscn(e, i)) == NULL ||
+ gelf_getshdr(scn, &sh) != &sh) {
+ warnx(
+"WARNING: Could not retrieve section header #%ju in \"%s\": %s.",
+ (uintmax_t) i, buffer, elf_errmsg(-1));
+ goto done;
+ }
+ if (sh.sh_flags & SHF_EXECINSTR) {
+ minva = min(minva, sh.sh_addr);
+ maxva = max(maxva, sh.sh_addr + sh.sh_size);
+ }
+ if (sh.sh_type == SHT_SYMTAB || sh.sh_type == SHT_DYNSYM)
+ pmcstat_image_add_symbols(image, e, scn, &sh);
+ }
+
+ image->pi_start = minva;
+ image->pi_end = maxva;
+ image->pi_type = image_type;
+ image->pi_fullpath = pmcstat_string_intern(buffer);
+
+ /* Build display name
+ */
+ endp = buffer;
+ for (p = buffer; *p; p++)
+ if (*p == '/')
+ endp = p+1;
+ image->pi_name = pmcstat_string_intern(endp);
+
+ done:
+ (void) elf_end(e);
+ if (fd >= 0)
+ (void) close(fd);
+ return;
+}
+
+/*
+ * Given an image descriptor, determine whether it is an ELF, or AOUT.
+ * If no handler claims the image, set its type to 'INDETERMINABLE'.
+ */
+
+void
+pmcstat_image_determine_type(struct pmcstat_image *image)
+{
+ assert(image->pi_type == PMCSTAT_IMAGE_UNKNOWN);
+
+ /* Try each kind of handler in turn */
+ if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN)
+ pmcstat_image_get_elf_params(image);
+ if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN)
+ pmcstat_image_get_aout_params(image);
+
+ /*
+ * Otherwise, remember that we tried to determine
+ * the object's type and had failed.
+ */
+ if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN)
+ image->pi_type = PMCSTAT_IMAGE_INDETERMINABLE;
+}
+
+/*
+ * Locate an image descriptor given an interned path, adding a fresh
+ * descriptor to the cache if necessary. This function also finds a
+ * suitable name for this image's sample file.
+ *
+ * We defer filling in the file format specific parts of the image
+ * structure till the time we actually see a sample that would fall
+ * into this image.
+ */
+
+static struct pmcstat_image *
+pmcstat_image_from_path(pmcstat_interned_string internedpath,
+ int iskernelmodule)
+{
+ int hash;
+ struct pmcstat_image *pi;
+
+ hash = pmcstat_string_lookup_hash(internedpath);
+
+ /* First, look for an existing entry. */
+ LIST_FOREACH(pi, &pmcstat_image_hash[hash], pi_next)
+ if (pi->pi_execpath == internedpath &&
+ pi->pi_iskernelmodule == iskernelmodule)
+ return (pi);
+
+ /*
+ * Allocate a new entry and place it at the head of the hash
+ * and LRU lists.
+ */
+ pi = malloc(sizeof(*pi));
+ if (pi == NULL)
+ return (NULL);
+
+ pi->pi_type = PMCSTAT_IMAGE_UNKNOWN;
+ pi->pi_execpath = internedpath;
+ pi->pi_start = ~0;
+ pi->pi_end = 0;
+ pi->pi_entry = 0;
+ pi->pi_vaddr = 0;
+ pi->pi_isdynamic = 0;
+ pi->pi_iskernelmodule = iskernelmodule;
+ pi->pi_dynlinkerpath = NULL;
+ pi->pi_symbols = NULL;
+ pi->pi_symcount = 0;
+ pi->pi_addr2line = NULL;
+
+ if (plugins[args.pa_pplugin].pl_initimage != NULL)
+ plugins[args.pa_pplugin].pl_initimage(pi);
+ if (plugins[args.pa_plugin].pl_initimage != NULL)
+ plugins[args.pa_plugin].pl_initimage(pi);
+
+ LIST_INSERT_HEAD(&pmcstat_image_hash[hash], pi, pi_next);
+
+ return (pi);
+}
+
+/*
+ * Record the fact that PC values from 'start' to 'end' come from
+ * image 'image'.
+ */
+
+static void
+pmcstat_image_link(struct pmcstat_process *pp, struct pmcstat_image *image,
+ uintfptr_t start)
+{
+ struct pmcstat_pcmap *pcm, *pcmnew;
+ uintfptr_t offset;
+
+ assert(image->pi_type != PMCSTAT_IMAGE_UNKNOWN &&
+ image->pi_type != PMCSTAT_IMAGE_INDETERMINABLE);
+
+ if ((pcmnew = malloc(sizeof(*pcmnew))) == NULL)
+ err(EX_OSERR, "ERROR: Cannot create a map entry");
+
+ /*
+ * Adjust the map entry to only cover the text portion
+ * of the object.
+ */
+
+ offset = start - image->pi_vaddr;
+ pcmnew->ppm_lowpc = image->pi_start + offset;
+ pcmnew->ppm_highpc = image->pi_end + offset;
+ pcmnew->ppm_image = image;
+
+ assert(pcmnew->ppm_lowpc < pcmnew->ppm_highpc);
+
+ /* Overlapped mmap()'s are assumed to never occur. */
+ TAILQ_FOREACH(pcm, &pp->pp_map, ppm_next)
+ if (pcm->ppm_lowpc >= pcmnew->ppm_highpc)
+ break;
+
+ if (pcm == NULL)
+ TAILQ_INSERT_TAIL(&pp->pp_map, pcmnew, ppm_next);
+ else
+ TAILQ_INSERT_BEFORE(pcm, pcmnew, ppm_next);
+}
+
+/*
+ * Unmap images in the range [start..end) associated with process
+ * 'pp'.
+ */
+
+static void
+pmcstat_image_unmap(struct pmcstat_process *pp, uintfptr_t start,
+ uintfptr_t end)
+{
+ struct pmcstat_pcmap *pcm, *pcmtmp, *pcmnew;
+
+ assert(pp != NULL);
+ assert(start < end);
+
+ /*
+ * Cases:
+ * - we could have the range completely in the middle of an
+ * existing pcmap; in this case we have to split the pcmap
+ * structure into two (i.e., generate a 'hole').
+ * - we could have the range covering multiple pcmaps; these
+ * will have to be removed.
+ * - we could have either 'start' or 'end' falling in the
+ * middle of a pcmap; in this case shorten the entry.
+ */
+ TAILQ_FOREACH_SAFE(pcm, &pp->pp_map, ppm_next, pcmtmp) {
+ assert(pcm->ppm_lowpc < pcm->ppm_highpc);
+ if (pcm->ppm_highpc <= start)
+ continue;
+ if (pcm->ppm_lowpc >= end)
+ return;
+ if (pcm->ppm_lowpc >= start && pcm->ppm_highpc <= end) {
+ /*
+ * The current pcmap is completely inside the
+ * unmapped range: remove it entirely.
+ */
+ TAILQ_REMOVE(&pp->pp_map, pcm, ppm_next);
+ free(pcm);
+ } else if (pcm->ppm_lowpc < start && pcm->ppm_highpc > end) {
+ /*
+ * Split this pcmap into two; curtail the
+ * current map to end at [start-1], and start
+ * the new one at [end].
+ */
+ if ((pcmnew = malloc(sizeof(*pcmnew))) == NULL)
+ err(EX_OSERR,
+ "ERROR: Cannot split a map entry");
+
+ pcmnew->ppm_image = pcm->ppm_image;
+
+ pcmnew->ppm_lowpc = end;
+ pcmnew->ppm_highpc = pcm->ppm_highpc;
+
+ pcm->ppm_highpc = start;
+
+ TAILQ_INSERT_AFTER(&pp->pp_map, pcm, pcmnew, ppm_next);
+
+ return;
+ } else if (pcm->ppm_lowpc < start && pcm->ppm_highpc <= end)
+ pcm->ppm_highpc = start;
+ else if (pcm->ppm_lowpc >= start && pcm->ppm_highpc > end)
+ pcm->ppm_lowpc = end;
+ else
+ assert(0);
+ }
+}
+
+/*
+ * Resolve file name and line number for the given address.
+ */
+int
+pmcstat_image_addr2line(struct pmcstat_image *image, uintfptr_t addr,
+ char *sourcefile, size_t sourcefile_len, unsigned *sourceline,
+ char *funcname, size_t funcname_len)
+{
+ static int addr2line_warn = 0;
+
+ char *sep, cmdline[PATH_MAX], imagepath[PATH_MAX];
+ unsigned l;
+ int fd;
+
+ if (image->pi_addr2line == NULL) {
+ /* Try default debug file location. */
+ snprintf(imagepath, sizeof(imagepath),
+ "/usr/lib/debug/%s%s.debug",
+ args.pa_fsroot,
+ pmcstat_string_unintern(image->pi_fullpath));
+ fd = open(imagepath, O_RDONLY);
+ if (fd < 0) {
+ /* Old kernel symbol path. */
+ snprintf(imagepath, sizeof(imagepath), "%s%s.symbols",
+ args.pa_fsroot,
+ pmcstat_string_unintern(image->pi_fullpath));
+ fd = open(imagepath, O_RDONLY);
+ if (fd < 0) {
+ snprintf(imagepath, sizeof(imagepath), "%s%s",
+ args.pa_fsroot,
+ pmcstat_string_unintern(
+ image->pi_fullpath));
+ }
+ }
+ if (fd >= 0)
+ close(fd);
+ /*
+ * New addr2line support recursive inline function with -i
+ * but the format does not add a marker when no more entries
+ * are available.
+ */
+ snprintf(cmdline, sizeof(cmdline), "addr2line -Cfe \"%s\"",
+ imagepath);
+ image->pi_addr2line = popen(cmdline, "r+");
+ if (image->pi_addr2line == NULL) {
+ if (!addr2line_warn) {
+ addr2line_warn = 1;
+ warnx(
+"WARNING: addr2line is needed for source code information."
+ );
+ }
+ return (0);
+ }
+ }
+
+ if (feof(image->pi_addr2line) || ferror(image->pi_addr2line)) {
+ warnx("WARNING: addr2line pipe error");
+ pclose(image->pi_addr2line);
+ image->pi_addr2line = NULL;
+ return (0);
+ }
+
+ fprintf(image->pi_addr2line, "%p\n", (void *)addr);
+
+ if (fgets(funcname, funcname_len, image->pi_addr2line) == NULL) {
+ warnx("WARNING: addr2line function name read error");
+ return (0);
+ }
+ sep = strchr(funcname, '\n');
+ if (sep != NULL)
+ *sep = '\0';
+
+ if (fgets(sourcefile, sourcefile_len, image->pi_addr2line) == NULL) {
+ warnx("WARNING: addr2line source file read error");
+ return (0);
+ }
+ sep = strchr(sourcefile, ':');
+ if (sep == NULL) {
+ warnx("WARNING: addr2line source line separator missing");
+ return (0);
+ }
+ *sep = '\0';
+ l = atoi(sep+1);
+ if (l == 0)
+ return (0);
+ *sourceline = l;
+ return (1);
+}
+
+/*
+ * Add a {pmcid,name} mapping.
+ */
+
+static void
+pmcstat_pmcid_add(pmc_id_t pmcid, pmcstat_interned_string ps)
+{
+ struct pmcstat_pmcrecord *pr, *prm;
+
+ /* Replace an existing name for the PMC. */
+ prm = NULL;
+ LIST_FOREACH(pr, &pmcstat_pmcs, pr_next)
+ if (pr->pr_pmcid == pmcid) {
+ pr->pr_pmcname = ps;
+ return;
+ } else if (pr->pr_pmcname == ps)
+ prm = pr;
+
+ /*
+ * Otherwise, allocate a new descriptor and call the
+ * plugins hook.
+ */
+ if ((pr = malloc(sizeof(*pr))) == NULL)
+ err(EX_OSERR, "ERROR: Cannot allocate pmc record");
+
+ pr->pr_pmcid = pmcid;
+ pr->pr_pmcname = ps;
+ pr->pr_pmcin = pmcstat_npmcs++;
+ pr->pr_samples = 0;
+ pr->pr_dubious_frames = 0;
+ pr->pr_merge = prm == NULL ? pr : prm;
+
+ LIST_INSERT_HEAD(&pmcstat_pmcs, pr, pr_next);
+
+ if (plugins[args.pa_pplugin].pl_newpmc != NULL)
+ plugins[args.pa_pplugin].pl_newpmc(ps, pr);
+ if (plugins[args.pa_plugin].pl_newpmc != NULL)
+ plugins[args.pa_plugin].pl_newpmc(ps, pr);
+}
+
+/*
+ * Given a pmcid in use, find its human-readable name.
+ */
+
+const char *
+pmcstat_pmcid_to_name(pmc_id_t pmcid)
+{
+ struct pmcstat_pmcrecord *pr;
+
+ LIST_FOREACH(pr, &pmcstat_pmcs, pr_next)
+ if (pr->pr_pmcid == pmcid)
+ return (pmcstat_string_unintern(pr->pr_pmcname));
+
+ return NULL;
+}
+
+/*
+ * Convert PMC index to name.
+ */
+
+const char *
+pmcstat_pmcindex_to_name(int pmcin)
+{
+ struct pmcstat_pmcrecord *pr;
+
+ LIST_FOREACH(pr, &pmcstat_pmcs, pr_next)
+ if (pr->pr_pmcin == pmcin)
+ return pmcstat_string_unintern(pr->pr_pmcname);
+
+ return NULL;
+}
+
+/*
+ * Return PMC record with given index.
+ */
+
+struct pmcstat_pmcrecord *
+pmcstat_pmcindex_to_pmcr(int pmcin)
+{
+ struct pmcstat_pmcrecord *pr;
+
+ LIST_FOREACH(pr, &pmcstat_pmcs, pr_next)
+ if (pr->pr_pmcin == pmcin)
+ return pr;
+
+ return NULL;
+}
+
+/*
+ * Get PMC record by id, apply merge policy.
+ */
+
+static struct pmcstat_pmcrecord *
+pmcstat_lookup_pmcid(pmc_id_t pmcid)
+{
+ struct pmcstat_pmcrecord *pr;
+
+ LIST_FOREACH(pr, &pmcstat_pmcs, pr_next) {
+ if (pr->pr_pmcid == pmcid) {
+ if (pmcstat_mergepmc)
+ return pr->pr_merge;
+ return pr;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Associate an AOUT image with a process.
+ */
+
+static void
+pmcstat_process_aout_exec(struct pmcstat_process *pp,
+ struct pmcstat_image *image, uintfptr_t entryaddr)
+{
+ (void) pp;
+ (void) image;
+ (void) entryaddr;
+ /* TODO Implement a.out handling */
+}
+
+/*
+ * Associate an ELF image with a process.
+ */
+
+static void
+pmcstat_process_elf_exec(struct pmcstat_process *pp,
+ struct pmcstat_image *image, uintfptr_t entryaddr)
+{
+ uintmax_t libstart;
+ struct pmcstat_image *rtldimage;
+
+ assert(image->pi_type == PMCSTAT_IMAGE_ELF32 ||
+ image->pi_type == PMCSTAT_IMAGE_ELF64);
+
+ /* Create a map entry for the base executable. */
+ pmcstat_image_link(pp, image, image->pi_vaddr);
+
+ /*
+ * For dynamically linked executables we need to determine
+ * where the dynamic linker was mapped to for this process,
+ * Subsequent executable objects that are mapped in by the
+ * dynamic linker will be tracked by log events of type
+ * PMCLOG_TYPE_MAP_IN.
+ */
+
+ if (image->pi_isdynamic) {
+
+ /*
+ * The runtime loader gets loaded just after the maximum
+ * possible heap address. Like so:
+ *
+ * [ TEXT DATA BSS HEAP -->*RTLD SHLIBS <--STACK]
+ * ^ ^
+ * 0 VM_MAXUSER_ADDRESS
+
+ *
+ * The exact address where the loader gets mapped in
+ * will vary according to the size of the executable
+ * and the limits on the size of the process'es data
+ * segment at the time of exec(). The entry address
+ * recorded at process exec time corresponds to the
+ * 'start' address inside the dynamic linker. From
+ * this we can figure out the address where the
+ * runtime loader's file object had been mapped to.
+ */
+ rtldimage = pmcstat_image_from_path(image->pi_dynlinkerpath, 0);
+ if (rtldimage == NULL) {
+ warnx("WARNING: Cannot find image for \"%s\".",
+ pmcstat_string_unintern(image->pi_dynlinkerpath));
+ pmcstat_stats.ps_exec_errors++;
+ return;
+ }
+
+ if (rtldimage->pi_type == PMCSTAT_IMAGE_UNKNOWN)
+ pmcstat_image_get_elf_params(rtldimage);
+
+ if (rtldimage->pi_type != PMCSTAT_IMAGE_ELF32 &&
+ rtldimage->pi_type != PMCSTAT_IMAGE_ELF64) {
+ warnx("WARNING: rtld not an ELF object \"%s\".",
+ pmcstat_string_unintern(image->pi_dynlinkerpath));
+ return;
+ }
+
+ libstart = entryaddr - rtldimage->pi_entry;
+ pmcstat_image_link(pp, rtldimage, libstart);
+ }
+}
+
+/*
+ * Find the process descriptor corresponding to a PID. If 'allocate'
+ * is zero, we return a NULL if a pid descriptor could not be found or
+ * a process descriptor process. If 'allocate' is non-zero, then we
+ * will attempt to allocate a fresh process descriptor. Zombie
+ * process descriptors are only removed if a fresh allocation for the
+ * same PID is requested.
+ */
+
+static struct pmcstat_process *
+pmcstat_process_lookup(pid_t pid, int allocate)
+{
+ uint32_t hash;
+ struct pmcstat_pcmap *ppm, *ppmtmp;
+ struct pmcstat_process *pp, *pptmp;
+
+ hash = (uint32_t) pid & PMCSTAT_HASH_MASK; /* simplicity wins */
+
+ LIST_FOREACH_SAFE(pp, &pmcstat_process_hash[hash], pp_next, pptmp)
+ if (pp->pp_pid == pid) {
+ /* Found a descriptor, check and process zombies */
+ if (allocate && pp->pp_isactive == 0) {
+ /* remove maps */
+ TAILQ_FOREACH_SAFE(ppm, &pp->pp_map, ppm_next,
+ ppmtmp) {
+ TAILQ_REMOVE(&pp->pp_map, ppm,
+ ppm_next);
+ free(ppm);
+ }
+ /* remove process entry */
+ LIST_REMOVE(pp, pp_next);
+ free(pp);
+ break;
+ }
+ return (pp);
+ }
+
+ if (!allocate)
+ return (NULL);
+
+ if ((pp = malloc(sizeof(*pp))) == NULL)
+ err(EX_OSERR, "ERROR: Cannot allocate pid descriptor");
+
+ pp->pp_pid = pid;
+ pp->pp_isactive = 1;
+
+ TAILQ_INIT(&pp->pp_map);
+
+ LIST_INSERT_HEAD(&pmcstat_process_hash[hash], pp, pp_next);
+ return (pp);
+}
+
+/*
+ * Associate an image and a process.
+ */
+
+static void
+pmcstat_process_exec(struct pmcstat_process *pp,
+ pmcstat_interned_string path, uintfptr_t entryaddr)
+{
+ struct pmcstat_image *image;
+
+ if ((image = pmcstat_image_from_path(path, 0)) == NULL) {
+ pmcstat_stats.ps_exec_errors++;
+ return;
+ }
+
+ if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN)
+ pmcstat_image_determine_type(image);
+
+ assert(image->pi_type != PMCSTAT_IMAGE_UNKNOWN);
+
+ switch (image->pi_type) {
+ case PMCSTAT_IMAGE_ELF32:
+ case PMCSTAT_IMAGE_ELF64:
+ pmcstat_stats.ps_exec_elf++;
+ pmcstat_process_elf_exec(pp, image, entryaddr);
+ break;
+
+ case PMCSTAT_IMAGE_AOUT:
+ pmcstat_stats.ps_exec_aout++;
+ pmcstat_process_aout_exec(pp, image, entryaddr);
+ break;
+
+ case PMCSTAT_IMAGE_INDETERMINABLE:
+ pmcstat_stats.ps_exec_indeterminable++;
+ break;
+
+ default:
+ err(EX_SOFTWARE,
+ "ERROR: Unsupported executable type for \"%s\"",
+ pmcstat_string_unintern(path));
+ }
+}
+
+
+/*
+ * Find the map entry associated with process 'p' at PC value 'pc'.
+ */
+
+struct pmcstat_pcmap *
+pmcstat_process_find_map(struct pmcstat_process *p, uintfptr_t pc)
+{
+ struct pmcstat_pcmap *ppm;
+
+ TAILQ_FOREACH(ppm, &p->pp_map, ppm_next) {
+ if (pc >= ppm->ppm_lowpc && pc < ppm->ppm_highpc)
+ return (ppm);
+ if (pc < ppm->ppm_lowpc)
+ return (NULL);
+ }
+
+ return (NULL);
+}
+
+/*
+ * Convert a hwpmc(4) log to profile information. A system-wide
+ * callgraph is generated if FLAG_DO_CALLGRAPHS is set. gmon.out
+ * files usable by gprof(1) are created if FLAG_DO_GPROF is set.
+ */
+static int
+pmcstat_analyze_log(void)
+{
+ uint32_t cpu, cpuflags;
+ uintfptr_t pc;
+ pid_t pid;
+ struct pmcstat_image *image;
+ struct pmcstat_process *pp, *ppnew;
+ struct pmcstat_pcmap *ppm, *ppmtmp;
+ struct pmclog_ev ev;
+ struct pmcstat_pmcrecord *pmcr;
+ pmcstat_interned_string image_path;
+
+ assert(args.pa_flags & FLAG_DO_ANALYSIS);
+
+ if (elf_version(EV_CURRENT) == EV_NONE)
+ err(EX_UNAVAILABLE, "Elf library intialization failed");
+
+ while (pmclog_read(args.pa_logparser, &ev) == 0) {
+ assert(ev.pl_state == PMCLOG_OK);
+
+ switch (ev.pl_type) {
+ case PMCLOG_TYPE_INITIALIZE:
+ if ((ev.pl_u.pl_i.pl_version & 0xFF000000) !=
+ PMC_VERSION_MAJOR << 24 && args.pa_verbosity > 0)
+ warnx(
+"WARNING: Log version 0x%x does not match compiled version 0x%x.",
+ ev.pl_u.pl_i.pl_version, PMC_VERSION_MAJOR);
+ break;
+
+ case PMCLOG_TYPE_MAP_IN:
+ /*
+ * Introduce an address range mapping for a
+ * userland process or the kernel (pid == -1).
+ *
+ * We always allocate a process descriptor so
+ * that subsequent samples seen for this
+ * address range are mapped to the current
+ * object being mapped in.
+ */
+ pid = ev.pl_u.pl_mi.pl_pid;
+ if (pid == -1)
+ pp = pmcstat_kernproc;
+ else
+ pp = pmcstat_process_lookup(pid,
+ PMCSTAT_ALLOCATE);
+
+ assert(pp != NULL);
+
+ image_path = pmcstat_string_intern(ev.pl_u.pl_mi.
+ pl_pathname);
+ image = pmcstat_image_from_path(image_path, pid == -1);
+ if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN)
+ pmcstat_image_determine_type(image);
+ if (image->pi_type != PMCSTAT_IMAGE_INDETERMINABLE)
+ pmcstat_image_link(pp, image,
+ ev.pl_u.pl_mi.pl_start);
+ break;
+
+ case PMCLOG_TYPE_MAP_OUT:
+ /*
+ * Remove an address map.
+ */
+ pid = ev.pl_u.pl_mo.pl_pid;
+ if (pid == -1)
+ pp = pmcstat_kernproc;
+ else
+ pp = pmcstat_process_lookup(pid, 0);
+
+ if (pp == NULL) /* unknown process */
+ break;
+
+ pmcstat_image_unmap(pp, ev.pl_u.pl_mo.pl_start,
+ ev.pl_u.pl_mo.pl_end);
+ break;
+
+ case PMCLOG_TYPE_PCSAMPLE:
+ /*
+ * Note: the `PCSAMPLE' log entry is not
+ * generated by hpwmc(4) after version 2.
+ */
+
+ /*
+ * We bring in the gmon file for the image
+ * currently associated with the PMC & pid
+ * pair and increment the appropriate entry
+ * bin inside this.
+ */
+ pmcstat_stats.ps_samples_total++;
+ ps_samples_period++;
+
+ pc = ev.pl_u.pl_s.pl_pc;
+ pp = pmcstat_process_lookup(ev.pl_u.pl_s.pl_pid,
+ PMCSTAT_ALLOCATE);
+
+ /* Get PMC record. */
+ pmcr = pmcstat_lookup_pmcid(ev.pl_u.pl_s.pl_pmcid);
+ assert(pmcr != NULL);
+ pmcr->pr_samples++;
+
+ /*
+ * Call the plugins processing
+ * TODO: move pmcstat_process_find_map inside plugins
+ */
+
+ if (plugins[args.pa_pplugin].pl_process != NULL)
+ plugins[args.pa_pplugin].pl_process(
+ pp, pmcr, 1, &pc,
+ pmcstat_process_find_map(pp, pc) != NULL, 0);
+ plugins[args.pa_plugin].pl_process(
+ pp, pmcr, 1, &pc,
+ pmcstat_process_find_map(pp, pc) != NULL, 0);
+ break;
+
+ case PMCLOG_TYPE_CALLCHAIN:
+ pmcstat_stats.ps_samples_total++;
+ ps_samples_period++;
+
+ cpuflags = ev.pl_u.pl_cc.pl_cpuflags;
+ cpu = PMC_CALLCHAIN_CPUFLAGS_TO_CPU(cpuflags);
+
+ /* Filter on the CPU id. */
+ if (!CPU_ISSET(cpu, &(args.pa_cpumask))) {
+ pmcstat_stats.ps_samples_skipped++;
+ break;
+ }
+
+ pp = pmcstat_process_lookup(ev.pl_u.pl_cc.pl_pid,
+ PMCSTAT_ALLOCATE);
+
+ /* Get PMC record. */
+ pmcr = pmcstat_lookup_pmcid(ev.pl_u.pl_cc.pl_pmcid);
+ assert(pmcr != NULL);
+ pmcr->pr_samples++;
+
+ /*
+ * Call the plugins processing
+ */
+
+ if (plugins[args.pa_pplugin].pl_process != NULL)
+ plugins[args.pa_pplugin].pl_process(
+ pp, pmcr,
+ ev.pl_u.pl_cc.pl_npc,
+ ev.pl_u.pl_cc.pl_pc,
+ PMC_CALLCHAIN_CPUFLAGS_TO_USERMODE(cpuflags),
+ cpu);
+ plugins[args.pa_plugin].pl_process(
+ pp, pmcr,
+ ev.pl_u.pl_cc.pl_npc,
+ ev.pl_u.pl_cc.pl_pc,
+ PMC_CALLCHAIN_CPUFLAGS_TO_USERMODE(cpuflags),
+ cpu);
+ break;
+
+ case PMCLOG_TYPE_PMCALLOCATE:
+ /*
+ * Record the association pmc id between this
+ * PMC and its name.
+ */
+ pmcstat_pmcid_add(ev.pl_u.pl_a.pl_pmcid,
+ pmcstat_string_intern(ev.pl_u.pl_a.pl_evname));
+ break;
+
+ case PMCLOG_TYPE_PMCALLOCATEDYN:
+ /*
+ * Record the association pmc id between this
+ * PMC and its name.
+ */
+ pmcstat_pmcid_add(ev.pl_u.pl_ad.pl_pmcid,
+ pmcstat_string_intern(ev.pl_u.pl_ad.pl_evname));
+ break;
+
+ case PMCLOG_TYPE_PROCEXEC:
+
+ /*
+ * Change the executable image associated with
+ * a process.
+ */
+ pp = pmcstat_process_lookup(ev.pl_u.pl_x.pl_pid,
+ PMCSTAT_ALLOCATE);
+
+ /* delete the current process map */
+ TAILQ_FOREACH_SAFE(ppm, &pp->pp_map, ppm_next, ppmtmp) {
+ TAILQ_REMOVE(&pp->pp_map, ppm, ppm_next);
+ free(ppm);
+ }
+
+ /*
+ * Associate this process image.
+ */
+ image_path = pmcstat_string_intern(
+ ev.pl_u.pl_x.pl_pathname);
+ assert(image_path != NULL);
+ pmcstat_process_exec(pp, image_path,
+ ev.pl_u.pl_x.pl_entryaddr);
+ break;
+
+ case PMCLOG_TYPE_PROCEXIT:
+
+ /*
+ * Due to the way the log is generated, the
+ * last few samples corresponding to a process
+ * may appear in the log after the process
+ * exit event is recorded. Thus we keep the
+ * process' descriptor and associated data
+ * structures around, but mark the process as
+ * having exited.
+ */
+ pp = pmcstat_process_lookup(ev.pl_u.pl_e.pl_pid, 0);
+ if (pp == NULL)
+ break;
+ pp->pp_isactive = 0; /* mark as a zombie */
+ break;
+
+ case PMCLOG_TYPE_SYSEXIT:
+ pp = pmcstat_process_lookup(ev.pl_u.pl_se.pl_pid, 0);
+ if (pp == NULL)
+ break;
+ pp->pp_isactive = 0; /* make a zombie */
+ break;
+
+ case PMCLOG_TYPE_PROCFORK:
+
+ /*
+ * Allocate a process descriptor for the new
+ * (child) process.
+ */
+ ppnew =
+ pmcstat_process_lookup(ev.pl_u.pl_f.pl_newpid,
+ PMCSTAT_ALLOCATE);
+
+ /*
+ * If we had been tracking the parent, clone
+ * its address maps.
+ */
+ pp = pmcstat_process_lookup(ev.pl_u.pl_f.pl_oldpid, 0);
+ if (pp == NULL)
+ break;
+ TAILQ_FOREACH(ppm, &pp->pp_map, ppm_next)
+ pmcstat_image_link(ppnew, ppm->ppm_image,
+ ppm->ppm_lowpc);
+ break;
+
+ default: /* other types of entries are not relevant */
+ break;
+ }
+ }
+
+ if (ev.pl_state == PMCLOG_EOF)
+ return (PMCSTAT_FINISHED);
+ else if (ev.pl_state == PMCLOG_REQUIRE_DATA)
+ return (PMCSTAT_RUNNING);
+
+ err(EX_DATAERR,
+ "ERROR: event parsing failed (record %jd, offset 0x%jx)",
+ (uintmax_t) ev.pl_count + 1, ev.pl_offset);
+}
+
+/*
+ * Print log entries as text.
+ */
+
+static int
+pmcstat_print_log(void)
+{
+ struct pmclog_ev ev;
+ uint32_t npc;
+
+ while (pmclog_read(args.pa_logparser, &ev) == 0) {
+ assert(ev.pl_state == PMCLOG_OK);
+ switch (ev.pl_type) {
+ case PMCLOG_TYPE_CALLCHAIN:
+ PMCSTAT_PRINT_ENTRY("callchain",
+ "%d 0x%x %d %d %c", ev.pl_u.pl_cc.pl_pid,
+ ev.pl_u.pl_cc.pl_pmcid,
+ PMC_CALLCHAIN_CPUFLAGS_TO_CPU(ev.pl_u.pl_cc. \
+ pl_cpuflags), ev.pl_u.pl_cc.pl_npc,
+ PMC_CALLCHAIN_CPUFLAGS_TO_USERMODE(ev.pl_u.pl_cc.\
+ pl_cpuflags) ? 'u' : 's');
+ for (npc = 0; npc < ev.pl_u.pl_cc.pl_npc; npc++)
+ PMCSTAT_PRINT_ENTRY("...", "%p",
+ (void *) ev.pl_u.pl_cc.pl_pc[npc]);
+ break;
+ case PMCLOG_TYPE_CLOSELOG:
+ PMCSTAT_PRINT_ENTRY("closelog",);
+ break;
+ case PMCLOG_TYPE_DROPNOTIFY:
+ PMCSTAT_PRINT_ENTRY("drop",);
+ break;
+ case PMCLOG_TYPE_INITIALIZE:
+ PMCSTAT_PRINT_ENTRY("initlog","0x%x \"%s\"",
+ ev.pl_u.pl_i.pl_version,
+ pmc_name_of_cputype(ev.pl_u.pl_i.pl_arch));
+ if ((ev.pl_u.pl_i.pl_version & 0xFF000000) !=
+ PMC_VERSION_MAJOR << 24 && args.pa_verbosity > 0)
+ warnx(
+"WARNING: Log version 0x%x != expected version 0x%x.",
+ ev.pl_u.pl_i.pl_version, PMC_VERSION);
+ break;
+ case PMCLOG_TYPE_MAP_IN:
+ PMCSTAT_PRINT_ENTRY("map-in","%d %p \"%s\"",
+ ev.pl_u.pl_mi.pl_pid,
+ (void *) ev.pl_u.pl_mi.pl_start,
+ ev.pl_u.pl_mi.pl_pathname);
+ break;
+ case PMCLOG_TYPE_MAP_OUT:
+ PMCSTAT_PRINT_ENTRY("map-out","%d %p %p",
+ ev.pl_u.pl_mo.pl_pid,
+ (void *) ev.pl_u.pl_mo.pl_start,
+ (void *) ev.pl_u.pl_mo.pl_end);
+ break;
+ case PMCLOG_TYPE_PCSAMPLE:
+ PMCSTAT_PRINT_ENTRY("sample","0x%x %d %p %c",
+ ev.pl_u.pl_s.pl_pmcid,
+ ev.pl_u.pl_s.pl_pid,
+ (void *) ev.pl_u.pl_s.pl_pc,
+ ev.pl_u.pl_s.pl_usermode ? 'u' : 's');
+ break;
+ case PMCLOG_TYPE_PMCALLOCATE:
+ PMCSTAT_PRINT_ENTRY("allocate","0x%x \"%s\" 0x%x",
+ ev.pl_u.pl_a.pl_pmcid,
+ ev.pl_u.pl_a.pl_evname,
+ ev.pl_u.pl_a.pl_flags);
+ break;
+ case PMCLOG_TYPE_PMCALLOCATEDYN:
+ PMCSTAT_PRINT_ENTRY("allocatedyn","0x%x \"%s\" 0x%x",
+ ev.pl_u.pl_ad.pl_pmcid,
+ ev.pl_u.pl_ad.pl_evname,
+ ev.pl_u.pl_ad.pl_flags);
+ break;
+ case PMCLOG_TYPE_PMCATTACH:
+ PMCSTAT_PRINT_ENTRY("attach","0x%x %d \"%s\"",
+ ev.pl_u.pl_t.pl_pmcid,
+ ev.pl_u.pl_t.pl_pid,
+ ev.pl_u.pl_t.pl_pathname);
+ break;
+ case PMCLOG_TYPE_PMCDETACH:
+ PMCSTAT_PRINT_ENTRY("detach","0x%x %d",
+ ev.pl_u.pl_d.pl_pmcid,
+ ev.pl_u.pl_d.pl_pid);
+ break;
+ case PMCLOG_TYPE_PROCCSW:
+ PMCSTAT_PRINT_ENTRY("cswval","0x%x %d %jd",
+ ev.pl_u.pl_c.pl_pmcid,
+ ev.pl_u.pl_c.pl_pid,
+ ev.pl_u.pl_c.pl_value);
+ break;
+ case PMCLOG_TYPE_PROCEXEC:
+ PMCSTAT_PRINT_ENTRY("exec","0x%x %d %p \"%s\"",
+ ev.pl_u.pl_x.pl_pmcid,
+ ev.pl_u.pl_x.pl_pid,
+ (void *) ev.pl_u.pl_x.pl_entryaddr,
+ ev.pl_u.pl_x.pl_pathname);
+ break;
+ case PMCLOG_TYPE_PROCEXIT:
+ PMCSTAT_PRINT_ENTRY("exitval","0x%x %d %jd",
+ ev.pl_u.pl_e.pl_pmcid,
+ ev.pl_u.pl_e.pl_pid,
+ ev.pl_u.pl_e.pl_value);
+ break;
+ case PMCLOG_TYPE_PROCFORK:
+ PMCSTAT_PRINT_ENTRY("fork","%d %d",
+ ev.pl_u.pl_f.pl_oldpid,
+ ev.pl_u.pl_f.pl_newpid);
+ break;
+ case PMCLOG_TYPE_USERDATA:
+ PMCSTAT_PRINT_ENTRY("userdata","0x%x",
+ ev.pl_u.pl_u.pl_userdata);
+ break;
+ case PMCLOG_TYPE_SYSEXIT:
+ PMCSTAT_PRINT_ENTRY("exit","%d",
+ ev.pl_u.pl_se.pl_pid);
+ break;
+ default:
+ fprintf(args.pa_printfile, "unknown event (type %d).\n",
+ ev.pl_type);
+ }
+ }
+
+ if (ev.pl_state == PMCLOG_EOF)
+ return (PMCSTAT_FINISHED);
+ else if (ev.pl_state == PMCLOG_REQUIRE_DATA)
+ return (PMCSTAT_RUNNING);
+
+ errx(EX_DATAERR,
+ "ERROR: event parsing failed (record %jd, offset 0x%jx).",
+ (uintmax_t) ev.pl_count + 1, ev.pl_offset);
+ /*NOTREACHED*/
+}
+
+/*
+ * Public Interfaces.
+ */
+
+/*
+ * Close a logfile, after first flushing all in-module queued data.
+ */
+
+int
+pmcstat_close_log(void)
+{
+ /* If a local logfile is configured ask the kernel to stop
+ * and flush data. Kernel will close the file when data is flushed
+ * so keep the status to EXITING.
+ */
+ if (args.pa_logfd != -1) {
+ if (pmc_close_logfile() < 0)
+ err(EX_OSERR, "ERROR: logging failed");
+ }
+
+ return (args.pa_flags & FLAG_HAS_PIPE ? PMCSTAT_EXITING :
+ PMCSTAT_FINISHED);
+}
+
+
+
+/*
+ * Open a log file, for reading or writing.
+ *
+ * The function returns the fd of a successfully opened log or -1 in
+ * case of failure.
+ */
+
+int
+pmcstat_open_log(const char *path, int mode)
+{
+ int error, fd, cfd;
+ size_t hlen;
+ const char *p, *errstr;
+ struct addrinfo hints, *res, *res0;
+ char hostname[MAXHOSTNAMELEN];
+
+ errstr = NULL;
+ fd = -1;
+
+ /*
+ * If 'path' is "-" then open one of stdin or stdout depending
+ * on the value of 'mode'.
+ *
+ * If 'path' contains a ':' and does not start with a '/' or '.',
+ * and is being opened for writing, treat it as a "host:port"
+ * specification and open a network socket.
+ *
+ * Otherwise, treat 'path' as a file name and open that.
+ */
+ if (path[0] == '-' && path[1] == '\0')
+ fd = (mode == PMCSTAT_OPEN_FOR_READ) ? 0 : 1;
+ else if (path[0] != '/' &&
+ path[0] != '.' && strchr(path, ':') != NULL) {
+
+ p = strrchr(path, ':');
+ hlen = p - path;
+ if (p == path || hlen >= sizeof(hostname)) {
+ errstr = strerror(EINVAL);
+ goto done;
+ }
+
+ assert(hlen < sizeof(hostname));
+ (void) strncpy(hostname, path, hlen);
+ hostname[hlen] = '\0';
+
+ (void) memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ if ((error = getaddrinfo(hostname, p+1, &hints, &res0)) != 0) {
+ errstr = gai_strerror(error);
+ goto done;
+ }
+
+ fd = -1;
+ for (res = res0; res; res = res->ai_next) {
+ if ((fd = socket(res->ai_family, res->ai_socktype,
+ res->ai_protocol)) < 0) {
+ errstr = strerror(errno);
+ continue;
+ }
+ if (mode == PMCSTAT_OPEN_FOR_READ) {
+ if (bind(fd, res->ai_addr, res->ai_addrlen) < 0) {
+ errstr = strerror(errno);
+ (void) close(fd);
+ fd = -1;
+ continue;
+ }
+ listen(fd, 1);
+ cfd = accept(fd, NULL, NULL);
+ (void) close(fd);
+ if (cfd < 0) {
+ errstr = strerror(errno);
+ fd = -1;
+ break;
+ }
+ fd = cfd;
+ } else {
+ if (connect(fd, res->ai_addr, res->ai_addrlen) < 0) {
+ errstr = strerror(errno);
+ (void) close(fd);
+ fd = -1;
+ continue;
+ }
+ }
+ errstr = NULL;
+ break;
+ }
+ freeaddrinfo(res0);
+
+ } else if ((fd = open(path, mode == PMCSTAT_OPEN_FOR_READ ?
+ O_RDONLY : (O_WRONLY|O_CREAT|O_TRUNC),
+ S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) < 0)
+ errstr = strerror(errno);
+
+ done:
+ if (errstr)
+ errx(EX_OSERR, "ERROR: Cannot open \"%s\" for %s: %s.", path,
+ (mode == PMCSTAT_OPEN_FOR_READ ? "reading" : "writing"),
+ errstr);
+
+ return (fd);
+}
+
+/*
+ * Process a log file in offline analysis mode.
+ */
+
+int
+pmcstat_process_log(void)
+{
+
+ /*
+ * If analysis has not been asked for, just print the log to
+ * the current output file.
+ */
+ if (args.pa_flags & FLAG_DO_PRINT)
+ return (pmcstat_print_log());
+ else
+ return (pmcstat_analyze_log());
+}
+
+/*
+ * Refresh top display.
+ */
+
+static void
+pmcstat_refresh_top(void)
+{
+ int v_attrs;
+ float v;
+ char pmcname[40];
+ struct pmcstat_pmcrecord *pmcpr;
+
+ /* If in pause mode do not refresh display. */
+ if (pmcstat_pause)
+ return;
+
+ /* Wait until PMC pop in the log. */
+ pmcpr = pmcstat_pmcindex_to_pmcr(pmcstat_pmcinfilter);
+ if (pmcpr == NULL)
+ return;
+
+ /* Format PMC name. */
+ if (pmcstat_mergepmc)
+ snprintf(pmcname, sizeof(pmcname), "[%s]",
+ pmcstat_string_unintern(pmcpr->pr_pmcname));
+ else
+ snprintf(pmcname, sizeof(pmcname), "%s.%d",
+ pmcstat_string_unintern(pmcpr->pr_pmcname),
+ pmcstat_pmcinfilter);
+
+ /* Format samples count. */
+ if (ps_samples_period > 0)
+ v = (pmcpr->pr_samples * 100.0) / ps_samples_period;
+ else
+ v = 0.;
+ v_attrs = PMCSTAT_ATTRPERCENT(v);
+
+ PMCSTAT_PRINTBEGIN();
+ PMCSTAT_PRINTW("PMC: %s Samples: %u ",
+ pmcname,
+ pmcpr->pr_samples);
+ PMCSTAT_ATTRON(v_attrs);
+ PMCSTAT_PRINTW("(%.1f%%) ", v);
+ PMCSTAT_ATTROFF(v_attrs);
+ PMCSTAT_PRINTW(", %u unresolved\n\n",
+ pmcpr->pr_dubious_frames);
+ if (plugins[args.pa_plugin].pl_topdisplay != NULL)
+ plugins[args.pa_plugin].pl_topdisplay();
+ PMCSTAT_PRINTEND();
+}
+
+/*
+ * Find the next pmc index to display.
+ */
+
+static void
+pmcstat_changefilter(void)
+{
+ int pmcin;
+ struct pmcstat_pmcrecord *pmcr;
+
+ /*
+ * Find the next merge target.
+ */
+ if (pmcstat_mergepmc) {
+ pmcin = pmcstat_pmcinfilter;
+
+ do {
+ pmcr = pmcstat_pmcindex_to_pmcr(pmcstat_pmcinfilter);
+ if (pmcr == NULL || pmcr == pmcr->pr_merge)
+ break;
+
+ pmcstat_pmcinfilter++;
+ if (pmcstat_pmcinfilter >= pmcstat_npmcs)
+ pmcstat_pmcinfilter = 0;
+
+ } while (pmcstat_pmcinfilter != pmcin);
+ }
+}
+
+/*
+ * Top mode keypress.
+ */
+
+int
+pmcstat_keypress_log(void)
+{
+ int c, ret = 0;
+ WINDOW *w;
+
+ w = newwin(1, 0, 1, 0);
+ c = wgetch(w);
+ wprintw(w, "Key: %c => ", c);
+ switch (c) {
+ case 'c':
+ wprintw(w, "enter mode 'd' or 'a' => ");
+ c = wgetch(w);
+ if (c == 'd') {
+ args.pa_topmode = PMCSTAT_TOP_DELTA;
+ wprintw(w, "switching to delta mode");
+ } else {
+ args.pa_topmode = PMCSTAT_TOP_ACCUM;
+ wprintw(w, "switching to accumulation mode");
+ }
+ break;
+ case 'm':
+ pmcstat_mergepmc = !pmcstat_mergepmc;
+ /*
+ * Changing merge state require data reset.
+ */
+ if (plugins[args.pa_plugin].pl_shutdown != NULL)
+ plugins[args.pa_plugin].pl_shutdown(NULL);
+ pmcstat_stats_reset(0);
+ if (plugins[args.pa_plugin].pl_init != NULL)
+ plugins[args.pa_plugin].pl_init();
+
+ /* Update filter to be on a merge target. */
+ pmcstat_changefilter();
+ wprintw(w, "merge PMC %s", pmcstat_mergepmc ? "on" : "off");
+ break;
+ case 'n':
+ /* Close current plugin. */
+ if (plugins[args.pa_plugin].pl_shutdown != NULL)
+ plugins[args.pa_plugin].pl_shutdown(NULL);
+
+ /* Find next top display available. */
+ do {
+ args.pa_plugin++;
+ if (plugins[args.pa_plugin].pl_name == NULL)
+ args.pa_plugin = 0;
+ } while (plugins[args.pa_plugin].pl_topdisplay == NULL);
+
+ /* Open new plugin. */
+ pmcstat_stats_reset(0);
+ if (plugins[args.pa_plugin].pl_init != NULL)
+ plugins[args.pa_plugin].pl_init();
+ wprintw(w, "switching to plugin %s",
+ plugins[args.pa_plugin].pl_name);
+ break;
+ case 'p':
+ pmcstat_pmcinfilter++;
+ if (pmcstat_pmcinfilter >= pmcstat_npmcs)
+ pmcstat_pmcinfilter = 0;
+ pmcstat_changefilter();
+ wprintw(w, "switching to PMC %s.%d",
+ pmcstat_pmcindex_to_name(pmcstat_pmcinfilter),
+ pmcstat_pmcinfilter);
+ break;
+ case ' ':
+ pmcstat_pause = !pmcstat_pause;
+ if (pmcstat_pause)
+ wprintw(w, "pause => press space again to continue");
+ break;
+ case 'q':
+ wprintw(w, "exiting...");
+ ret = 1;
+ break;
+ default:
+ if (plugins[args.pa_plugin].pl_topkeypress != NULL)
+ if (plugins[args.pa_plugin].pl_topkeypress(c, w))
+ ret = 1;
+ }
+
+ wrefresh(w);
+ delwin(w);
+ return ret;
+}
+
+
+/*
+ * Top mode display.
+ */
+
+void
+pmcstat_display_log(void)
+{
+
+ pmcstat_refresh_top();
+
+ /* Reset everythings if delta mode. */
+ if (args.pa_topmode == PMCSTAT_TOP_DELTA) {
+ if (plugins[args.pa_plugin].pl_shutdown != NULL)
+ plugins[args.pa_plugin].pl_shutdown(NULL);
+ pmcstat_stats_reset(0);
+ if (plugins[args.pa_plugin].pl_init != NULL)
+ plugins[args.pa_plugin].pl_init();
+ }
+
+}
+
+/*
+ * Configure a plugins.
+ */
+
+void
+pmcstat_pluginconfigure_log(char *opt)
+{
+
+ if (strncmp(opt, "threshold=", 10) == 0) {
+ pmcstat_threshold = atof(opt+10);
+ } else {
+ if (plugins[args.pa_plugin].pl_configure != NULL) {
+ if (!plugins[args.pa_plugin].pl_configure(opt))
+ err(EX_USAGE,
+ "ERROR: unknown option <%s>.", opt);
+ }
+ }
+}
+
+/*
+ * Initialize module.
+ */
+
+void
+pmcstat_initialize_logging(void)
+{
+ int i;
+
+ /* use a convenient format for 'ldd' output */
+ if (setenv("LD_TRACE_LOADED_OBJECTS_FMT1","%o \"%p\" %x\n",1) != 0)
+ err(EX_OSERR, "ERROR: Cannot setenv");
+
+ /* Initialize hash tables */
+ pmcstat_string_initialize();
+ for (i = 0; i < PMCSTAT_NHASH; i++) {
+ LIST_INIT(&pmcstat_image_hash[i]);
+ LIST_INIT(&pmcstat_process_hash[i]);
+ }
+
+ /*
+ * Create a fake 'process' entry for the kernel with pid -1.
+ * hwpmc(4) will subsequently inform us about where the kernel
+ * and any loaded kernel modules are mapped.
+ */
+ if ((pmcstat_kernproc = pmcstat_process_lookup((pid_t) -1,
+ PMCSTAT_ALLOCATE)) == NULL)
+ err(EX_OSERR, "ERROR: Cannot initialize logging");
+
+ /* PMC count. */
+ pmcstat_npmcs = 0;
+
+ /* Merge PMC with same name. */
+ pmcstat_mergepmc = args.pa_mergepmc;
+
+ /*
+ * Initialize plugins
+ */
+
+ if (plugins[args.pa_pplugin].pl_init != NULL)
+ plugins[args.pa_pplugin].pl_init();
+ if (plugins[args.pa_plugin].pl_init != NULL)
+ plugins[args.pa_plugin].pl_init();
+}
+
+/*
+ * Shutdown module.
+ */
+
+void
+pmcstat_shutdown_logging(void)
+{
+ int i;
+ FILE *mf;
+ struct pmcstat_image *pi, *pitmp;
+ struct pmcstat_process *pp, *pptmp;
+ struct pmcstat_pcmap *ppm, *ppmtmp;
+
+ /* determine where to send the map file */
+ mf = NULL;
+ if (args.pa_mapfilename != NULL)
+ mf = (strcmp(args.pa_mapfilename, "-") == 0) ?
+ args.pa_printfile : fopen(args.pa_mapfilename, "w");
+
+ if (mf == NULL && args.pa_flags & FLAG_DO_GPROF &&
+ args.pa_verbosity >= 2)
+ mf = args.pa_printfile;
+
+ if (mf)
+ (void) fprintf(mf, "MAP:\n");
+
+ /*
+ * Shutdown the plugins
+ */
+
+ if (plugins[args.pa_plugin].pl_shutdown != NULL)
+ plugins[args.pa_plugin].pl_shutdown(mf);
+ if (plugins[args.pa_pplugin].pl_shutdown != NULL)
+ plugins[args.pa_pplugin].pl_shutdown(mf);
+
+ for (i = 0; i < PMCSTAT_NHASH; i++) {
+ LIST_FOREACH_SAFE(pi, &pmcstat_image_hash[i], pi_next,
+ pitmp) {
+ if (plugins[args.pa_plugin].pl_shutdownimage != NULL)
+ plugins[args.pa_plugin].pl_shutdownimage(pi);
+ if (plugins[args.pa_pplugin].pl_shutdownimage != NULL)
+ plugins[args.pa_pplugin].pl_shutdownimage(pi);
+
+ free(pi->pi_symbols);
+ if (pi->pi_addr2line != NULL)
+ pclose(pi->pi_addr2line);
+ LIST_REMOVE(pi, pi_next);
+ free(pi);
+ }
+
+ LIST_FOREACH_SAFE(pp, &pmcstat_process_hash[i], pp_next,
+ pptmp) {
+ TAILQ_FOREACH_SAFE(ppm, &pp->pp_map, ppm_next, ppmtmp) {
+ TAILQ_REMOVE(&pp->pp_map, ppm, ppm_next);
+ free(ppm);
+ }
+ LIST_REMOVE(pp, pp_next);
+ free(pp);
+ }
+ }
+
+ pmcstat_string_shutdown();
+
+ /*
+ * Print errors unless -q was specified. Print all statistics
+ * if verbosity > 1.
+ */
+#define PRINT(N,V) do { \
+ if (pmcstat_stats.ps_##V || args.pa_verbosity >= 2) \
+ (void) fprintf(args.pa_printfile, " %-40s %d\n",\
+ N, pmcstat_stats.ps_##V); \
+ } while (0)
+
+ if (args.pa_verbosity >= 1 && (args.pa_flags & FLAG_DO_ANALYSIS)) {
+ (void) fprintf(args.pa_printfile, "CONVERSION STATISTICS:\n");
+ PRINT("#exec/a.out", exec_aout);
+ PRINT("#exec/elf", exec_elf);
+ PRINT("#exec/unknown", exec_indeterminable);
+ PRINT("#exec handling errors", exec_errors);
+ PRINT("#samples/total", samples_total);
+ PRINT("#samples/unclaimed", samples_unknown_offset);
+ PRINT("#samples/unknown-object", samples_indeterminable);
+ PRINT("#samples/unknown-function", samples_unknown_function);
+ PRINT("#callchain/dubious-frames", callchain_dubious_frames);
+ }
+
+ if (mf)
+ (void) fclose(mf);
+}
diff --git a/usr.sbin/pmcstat/pmcstat_log.h b/usr.sbin/pmcstat/pmcstat_log.h
new file mode 100644
index 0000000..b4ced4d
--- /dev/null
+++ b/usr.sbin/pmcstat/pmcstat_log.h
@@ -0,0 +1,199 @@
+/*-
+ * Copyright (c) 2005-2007, Joseph Koshy
+ * Copyright (c) 2007 The FreeBSD Foundation
+ * Copyright (c) 2009, Fabien Thomas
+ * All rights reserved.
+ *
+ * Portions of this software were developed by A. Joseph Koshy under
+ * sponsorship from the FreeBSD Foundation and Google, 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.
+ *
+ * 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 _PMCSTAT_LOG_H_
+#define _PMCSTAT_LOG_H_
+
+typedef const void *pmcstat_interned_string;
+
+/*
+ * A 'pmcstat_process' structure models processes. Each process is
+ * associated with a set of pmcstat_pcmap structures that map
+ * addresses inside it to executable objects. This set is implemented
+ * as a list, kept sorted in ascending order of mapped addresses.
+ *
+ * 'pp_pid' holds the pid of the process. When a process exits, the
+ * 'pp_isactive' field is set to zero, but the process structure is
+ * not immediately reclaimed because there may still be samples in the
+ * log for this process.
+ */
+
+struct pmcstat_process {
+ LIST_ENTRY(pmcstat_process) pp_next; /* hash-next */
+ pid_t pp_pid; /* associated pid */
+ int pp_isactive; /* whether active */
+ uintfptr_t pp_entryaddr; /* entry address */
+ TAILQ_HEAD(,pmcstat_pcmap) pp_map; /* address range map */
+};
+extern LIST_HEAD(pmcstat_process_hash_list, pmcstat_process) pmcstat_process_hash[PMCSTAT_NHASH];
+
+/*
+ * A 'pmcstat_image' structure describes an executable program on
+ * disk. 'pi_execpath' is a cookie representing the pathname of
+ * the executable. 'pi_start' and 'pi_end' are the least and greatest
+ * virtual addresses for the text segments in the executable.
+ * 'pi_gmonlist' contains a linked list of gmon.out files associated
+ * with this image.
+ */
+
+enum pmcstat_image_type {
+ PMCSTAT_IMAGE_UNKNOWN = 0, /* never looked at the image */
+ PMCSTAT_IMAGE_INDETERMINABLE, /* can't tell what the image is */
+ PMCSTAT_IMAGE_ELF32, /* ELF 32 bit object */
+ PMCSTAT_IMAGE_ELF64, /* ELF 64 bit object */
+ PMCSTAT_IMAGE_AOUT /* AOUT object */
+};
+
+struct pmcstat_image {
+ LIST_ENTRY(pmcstat_image) pi_next; /* hash link */
+ TAILQ_ENTRY(pmcstat_image) pi_lru; /* LRU list */
+ pmcstat_interned_string pi_execpath; /* cookie */
+ pmcstat_interned_string pi_samplename; /* sample path name */
+ pmcstat_interned_string pi_fullpath; /* path to FS object */
+ pmcstat_interned_string pi_name; /* display name */
+
+ enum pmcstat_image_type pi_type; /* executable type */
+
+ /*
+ * Executables have pi_start and pi_end; these are zero
+ * for shared libraries.
+ */
+ uintfptr_t pi_start; /* start address (inclusive) */
+ uintfptr_t pi_end; /* end address (exclusive) */
+ uintfptr_t pi_entry; /* entry address */
+ uintfptr_t pi_vaddr; /* virtual address where loaded */
+ int pi_isdynamic; /* whether a dynamic object */
+ int pi_iskernelmodule;
+ pmcstat_interned_string pi_dynlinkerpath; /* path in .interp */
+
+ /* All symbols associated with this object. */
+ struct pmcstat_symbol *pi_symbols;
+ size_t pi_symcount;
+
+ /* Handle to addr2line for this image. */
+ FILE *pi_addr2line;
+
+ /*
+ * Plugins private data
+ */
+
+ /* gprof:
+ * An image can be associated with one or more gmon.out files;
+ * one per PMC.
+ */
+ LIST_HEAD(,pmcstat_gmonfile) pi_gmlist;
+};
+extern LIST_HEAD(pmcstat_image_hash_list, pmcstat_image) pmcstat_image_hash[PMCSTAT_NHASH];
+
+/*
+ * A 'pmcstat_pcmap' structure maps a virtual address range to an
+ * underlying 'pmcstat_image' descriptor.
+ */
+struct pmcstat_pcmap {
+ TAILQ_ENTRY(pmcstat_pcmap) ppm_next;
+ uintfptr_t ppm_lowpc;
+ uintfptr_t ppm_highpc;
+ struct pmcstat_image *ppm_image;
+};
+
+/*
+ * Each function symbol tracked by pmcstat(8).
+ */
+
+struct pmcstat_symbol {
+ pmcstat_interned_string ps_name;
+ uint64_t ps_start;
+ uint64_t ps_end;
+};
+
+/*
+ * 'pmcstat_pmcrecord' is a mapping from PMC ids to human-readable
+ * names.
+ */
+
+struct pmcstat_pmcrecord {
+ LIST_ENTRY(pmcstat_pmcrecord) pr_next;
+ pmc_id_t pr_pmcid;
+ int pr_pmcin;
+ pmcstat_interned_string pr_pmcname;
+ int pr_samples;
+ int pr_dubious_frames;
+ struct pmcstat_pmcrecord *pr_merge;
+};
+extern LIST_HEAD(pmcstat_pmcs, pmcstat_pmcrecord) pmcstat_pmcs; /* PMC list */
+
+/*
+ * Misc. statistics
+ */
+struct pmcstat_stats {
+ int ps_exec_aout; /* # a.out executables seen */
+ int ps_exec_elf; /* # elf executables seen */
+ int ps_exec_errors; /* # errors processing executables */
+ int ps_exec_indeterminable; /* # unknown executables seen */
+ int ps_samples_total; /* total number of samples processed */
+ int ps_samples_skipped; /* #samples filtered out for any reason */
+ int ps_samples_unknown_offset; /* #samples of rank 0 not in a map */
+ int ps_samples_indeterminable; /* #samples in indeterminable images */
+ int ps_samples_unknown_function;/* #samples with unknown function at offset */
+ int ps_callchain_dubious_frames;/* #dubious frame pointers seen */
+};
+extern struct pmcstat_stats pmcstat_stats; /* statistics */
+
+extern struct pmcstat_process *pmcstat_kernproc; /* kernel 'process' */
+
+extern int pmcstat_npmcs; /* PMC count. */
+
+/*
+ * Top mode global options.
+ */
+extern float pmcstat_threshold; /* Threshold to filter node. */
+extern int pmcstat_pmcinfilter; /* PMC index displayed. */
+
+/* Function prototypes */
+const char *pmcstat_pmcid_to_name(pmc_id_t _pmcid);
+const char *pmcstat_pmcindex_to_name(int pmcin);
+struct pmcstat_pmcrecord *pmcstat_pmcindex_to_pmcr(int pmcin);
+struct pmcstat_pcmap *pmcstat_process_find_map(struct pmcstat_process *_p,
+ uintfptr_t _pc);
+struct pmcstat_symbol *pmcstat_symbol_search(struct pmcstat_image *image,
+ uintfptr_t addr);
+const char *pmcstat_string_unintern(pmcstat_interned_string _is);
+pmcstat_interned_string pmcstat_string_intern(const char *_s);
+void pmcstat_image_determine_type(struct pmcstat_image *_image);
+pmcstat_interned_string pmcstat_string_lookup(const char *_s);
+int pmcstat_image_addr2line(struct pmcstat_image *image, uintfptr_t addr,
+ char *sourcefile, size_t sourcefile_len, unsigned *sourceline,
+ char *funcname, size_t funcname_len);
+
+#endif /* _PMCSTAT_LOG_H_ */
+
diff --git a/usr.sbin/pmcstat/pmcstat_top.h b/usr.sbin/pmcstat/pmcstat_top.h
new file mode 100644
index 0000000..281410b
--- /dev/null
+++ b/usr.sbin/pmcstat/pmcstat_top.h
@@ -0,0 +1,75 @@
+/*-
+ * Copyright (c) 2009, Fabien Thomas
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 _PMCSTAT_TOP_H_
+#define _PMCSTAT_TOP_H_
+
+/* Return the ncurses attributes for the given value. */
+#define PMCSTAT_ATTRPERCENT(b) \
+ ((b) > 10.0 ? (args.pa_topcolor ? COLOR_PAIR(1) : A_BOLD) : \
+ ((b) > 5.0 ? (args.pa_topcolor ? COLOR_PAIR(2) : 0) : \
+ ((b) > 2.5 ? (args.pa_topcolor ? COLOR_PAIR(3) : 0) : 0)))
+
+/* Print to the default ncurse windows if on a terminal or to the file. */
+#define PMCSTAT_PRINTW(...) do { \
+ if (args.pa_toptty) \
+ printw(__VA_ARGS__); \
+ else \
+ fprintf(args.pa_printfile, __VA_ARGS__);\
+} while (0)
+
+/* If ncurses mode active set attributes. */
+#define PMCSTAT_ATTRON(b) do { \
+ if (args.pa_toptty) \
+ attron(b); \
+} while (0)
+
+/* If ncurses mode active unset attributes. */
+#define PMCSTAT_ATTROFF(b) do { \
+ if (args.pa_toptty) \
+ attroff(b); \
+} while (0)
+
+/* Erase screen and set cursor to top left. */
+#define PMCSTAT_PRINTBEGIN() do { \
+ if (args.pa_toptty) \
+ clear(); \
+} while (0)
+
+/* Flush buffer to backend. */
+#define PMCSTAT_PRINTEND() do { \
+ if (!args.pa_toptty) { \
+ PMCSTAT_PRINTW("---\n"); \
+ fflush(args.pa_printfile); \
+ } else \
+ refresh(); \
+} while (0)
+
+/* Function prototypes */
+
+#endif /* _PMCSTAT_TOP_H_ */
diff --git a/usr.sbin/pmcstudy/Makefile b/usr.sbin/pmcstudy/Makefile
new file mode 100644
index 0000000..5ff3ba7
--- /dev/null
+++ b/usr.sbin/pmcstudy/Makefile
@@ -0,0 +1,12 @@
+# @(#)Makefile 8.1 (Berkeley) 6/9/93
+# $FreeBSD$
+
+PROG= pmcstudy
+MAN= pmcstudy.8
+SRCS= pmcstudy.c eval_expr.c
+CFLAGS+= -Wall -Werror
+
+BINDIR= /usr/bin
+
+.include <bsd.prog.mk>
+
diff --git a/usr.sbin/pmcstudy/eval_expr.c b/usr.sbin/pmcstudy/eval_expr.c
new file mode 100644
index 0000000..d8999a9
--- /dev/null
+++ b/usr.sbin/pmcstudy/eval_expr.c
@@ -0,0 +1,717 @@
+/*-
+ * Copyright (c) 2015 Netflix 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,
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <strings.h>
+#include <ctype.h>
+#include "eval_expr.h"
+__FBSDID("$FreeBSD$");
+
+static struct expression *
+alloc_and_hook_expr(struct expression **exp_p, struct expression **last_p)
+{
+ struct expression *ex, *at;
+
+ ex = malloc(sizeof(struct expression));
+ if (ex == NULL) {
+ printf("Out of memory in exp allocation\n");
+ exit(-2);
+ }
+ memset(ex, 0, sizeof(struct expression));
+ if (*exp_p == NULL) {
+ *exp_p = ex;
+ }
+ at = *last_p;
+ if (at == NULL) {
+ /* First one, its last */
+ *last_p = ex;
+ } else {
+ /* Chain it to the end and update last */
+ at->next = ex;
+ ex->prev = at;
+ *last_p = ex;
+ }
+ return (ex);
+}
+
+
+static int
+validate_expr(struct expression *exp, int val1_is_set, int op_is_set, int val2_is_set,
+ int *op_cnt)
+{
+ int val1, op, val2;
+ int open_cnt;
+ val1 = op = val2 = 0;
+ if (val1_is_set) {
+ val1 = 1;
+ }
+ if (op_is_set) {
+ op = 1;
+ }
+ if (val2_is_set) {
+ val2 = 1;
+ }
+ open_cnt = *op_cnt;
+ if (exp == NULL) {
+ /* End of the road */
+ if (val1 && op && val2 && (open_cnt == 0)) {
+ return(0);
+ } else {
+ return(1);
+ }
+ }
+ switch(exp->type) {
+ case TYPE_OP_PLUS:
+ case TYPE_OP_MINUS:
+ case TYPE_OP_MULT:
+ case TYPE_OP_DIVIDE:
+ if (val1 && op && val2) {
+ /* We are at x + y +
+ * collapse back to val/op
+ */
+ val1 = 1;
+ op = 1;
+ val2 = 0;
+ } else if ((op == 0) && (val1)) {
+ op = 1;
+ } else {
+ printf("Op but no val1 set\n");
+ return(-1);
+ }
+ break;
+ case TYPE_PARN_OPEN:
+ if (exp->next == NULL) {
+ printf("NULL after open paren\n");
+ exit(-1);
+ }
+ if ((exp->next->type == TYPE_OP_PLUS) ||
+ (exp->next->type == TYPE_OP_MINUS) ||
+ (exp->next->type == TYPE_OP_DIVIDE) ||
+ (exp->next->type == TYPE_OP_MULT)) {
+ printf("'( OP' -- not allowed\n");
+ return(-1);
+ }
+ if (val1 && (op == 0)) {
+ printf("'Val (' -- not allowed\n");
+ return(-1);
+ }
+ if (val1 && op && val2) {
+ printf("'Val OP Val (' -- not allowed\n");
+ return(-1);
+ }
+ open_cnt++;
+ *op_cnt = open_cnt;
+ if (val1) {
+ if (validate_expr(exp->next, 0, 0, 0, op_cnt) == 0) {
+ val2 = 1;
+ } else {
+ return(-1);
+ }
+ } else {
+ return(validate_expr(exp->next, 0, 0, 0, op_cnt));
+ }
+ break;
+ case TYPE_PARN_CLOSE:
+ open_cnt--;
+ *op_cnt = open_cnt;
+ if (val1 && op && val2) {
+ return(0);
+ } else {
+ printf("Found close paren and not complete\n");
+ return(-1);
+ }
+ break;
+ case TYPE_VALUE_CON:
+ case TYPE_VALUE_PMC:
+ if (val1 == 0) {
+ val1 = 1;
+ } else if (val1 && op) {
+ val2 = 1;
+ } else {
+ printf("val1 set, val2 about to be set op empty\n");
+ return(-1);
+ }
+ break;
+ default:
+ printf("unknown type %d\n", exp->type);
+ exit(-5);
+ break;
+ }
+ return(validate_expr(exp->next, val1, op, val2, op_cnt));
+}
+
+void
+print_exp(struct expression *exp)
+{
+ if (exp == NULL) {
+ printf("\n");
+ return;
+ }
+ switch(exp->type) {
+ case TYPE_OP_PLUS:
+ printf(" + ");
+ break;
+ case TYPE_OP_MINUS:
+ printf(" - ");
+ break;
+ case TYPE_OP_MULT:
+ printf(" * ");
+ break;
+ case TYPE_OP_DIVIDE:
+ printf(" / ");
+ break;
+ case TYPE_PARN_OPEN:
+ printf(" ( ");
+ break;
+ case TYPE_PARN_CLOSE:
+ printf(" ) ");
+ break;
+ case TYPE_VALUE_CON:
+ printf("%f", exp->value);
+ break;
+ case TYPE_VALUE_PMC:
+ printf("%s", exp->name);
+ break;
+ default:
+ printf("Unknown op %d\n", exp->type);
+ break;
+ }
+ print_exp(exp->next);
+}
+
+static void
+walk_back_and_insert_paren(struct expression **beg, struct expression *frm)
+{
+ struct expression *at, *ex;
+
+ /* Setup our new open paren */
+ ex = malloc(sizeof(struct expression));
+ if (ex == NULL) {
+ printf("Out of memory in exp allocation\n");
+ exit(-2);
+ }
+ memset(ex, 0, sizeof(struct expression));
+ ex->type = TYPE_PARN_OPEN;
+ /* Now lets place it */
+ at = frm->prev;
+ if (at == *beg) {
+ /* We are inserting at the head of the list */
+ in_beg:
+ ex->next = at;
+ at->prev = ex;
+ *beg = ex;
+ return;
+ } else if ((at->type == TYPE_VALUE_CON) ||
+ (at->type == TYPE_VALUE_PMC)) {
+ /* Simple case we have a value in the previous position */
+ in_mid:
+ ex->prev = at->prev;
+ ex->prev->next = ex;
+ ex->next = at;
+ at->prev = ex;
+ return;
+ } else if (at->type == TYPE_PARN_CLOSE) {
+ /* Skip through until we reach beg or all ( closes */
+ int par_cnt=1;
+
+ at = at->prev;
+ while(par_cnt) {
+ if (at->type == TYPE_PARN_CLOSE) {
+ par_cnt++;
+ } else if (at->type == TYPE_PARN_OPEN) {
+ par_cnt--;
+ if (par_cnt == 0) {
+ break;
+ }
+ }
+ at = at->prev;
+ }
+ if (at == *beg) {
+ /* At beginning we insert */
+ goto in_beg;
+ } else {
+ goto in_mid;
+ }
+ } else {
+ printf("%s:Unexpected type:%d?\n",
+ __FUNCTION__, at->type);
+ exit(-1);
+ }
+}
+
+static void
+walk_fwd_and_insert_paren(struct expression *frm, struct expression **added)
+{
+ struct expression *at, *ex;
+ /* Setup our new close paren */
+ ex = malloc(sizeof(struct expression));
+ if (ex == NULL) {
+ printf("Out of memory in exp allocation\n");
+ exit(-2);
+ }
+ memset(ex, 0, sizeof(struct expression));
+ ex->type = TYPE_PARN_CLOSE;
+ *added = ex;
+ /* Now lets place it */
+ at = frm->next;
+ if ((at->type == TYPE_VALUE_CON) ||
+ (at->type == TYPE_VALUE_PMC)) {
+ /* Simple case we have a value in the previous position */
+ insertit:
+ ex->next = at->next;
+ ex->prev = at;
+ at->next = ex;
+ return;
+ } else if (at->type == TYPE_PARN_OPEN) {
+ int par_cnt=1;
+ at = at->next;
+ while(par_cnt) {
+ if (at->type == TYPE_PARN_OPEN) {
+ par_cnt++;
+ } else if (at->type == TYPE_PARN_CLOSE) {
+ par_cnt--;
+ if (par_cnt == 0) {
+ break;
+ }
+ }
+ at = at->next;
+ }
+ goto insertit;
+ } else {
+ printf("%s:Unexpected type:%d?\n",
+ __FUNCTION__,
+ at->type);
+ exit(-1);
+ }
+}
+
+
+static void
+add_precendence(struct expression **beg, struct expression *start, struct expression *end)
+{
+ /*
+ * Between start and end add () around any * or /. This
+ * is quite tricky since if there is a () set inside the
+ * list we need to skip over everything in the ()'s considering
+ * that just a value.
+ */
+ struct expression *at, *newone;
+ int open_cnt;
+
+ at = start;
+ open_cnt = 0;
+ while(at != end) {
+ if (at->type == TYPE_PARN_OPEN) {
+ open_cnt++;
+ }
+ if (at->type == TYPE_PARN_CLOSE) {
+ open_cnt--;
+ }
+ if (open_cnt == 0) {
+ if ((at->type == TYPE_OP_MULT) ||
+ (at->type == TYPE_OP_DIVIDE)) {
+ walk_back_and_insert_paren(beg, at);
+ walk_fwd_and_insert_paren(at, &newone);
+ at = newone->next;
+ continue;
+ }
+ }
+ at = at->next;
+ }
+
+}
+
+static void
+set_math_precidence(struct expression **beg, struct expression *exp, struct expression **stopped)
+{
+ struct expression *at, *start, *end;
+ int cnt_lower, cnt_upper;
+ /*
+ * Walk through and set any math precedence to
+ * get proper precedence we insert () around * / over + -
+ */
+ end = NULL;
+ start = at = exp;
+ cnt_lower = cnt_upper = 0;
+ while(at) {
+ if (at->type == TYPE_PARN_CLOSE) {
+ /* Done with that paren */
+ if (stopped) {
+ *stopped = at;
+ }
+ if (cnt_lower && cnt_upper) {
+ /* We have a mixed set ... add precedence between start/end */
+ add_precendence(beg, start, end);
+ }
+ return;
+ }
+ if (at->type == TYPE_PARN_OPEN) {
+ set_math_precidence(beg, at->next, &end);
+ at = end;
+ continue;
+ } else if ((at->type == TYPE_OP_PLUS) ||
+ (at->type == TYPE_OP_MINUS)) {
+ cnt_lower++;
+ } else if ((at->type == TYPE_OP_DIVIDE) ||
+ (at->type == TYPE_OP_MULT)) {
+ cnt_upper++;
+ }
+ at = at->next;
+ }
+ if (cnt_lower && cnt_upper) {
+ add_precendence(beg, start, NULL);
+ }
+}
+
+extern char **valid_pmcs;
+extern int valid_pmc_cnt;
+
+static void
+pmc_name_set(struct expression *at)
+{
+ int i, idx, fnd;
+
+ if (at->name[0] == '%') {
+ /* Special number after $ gives index */
+ idx = strtol(&at->name[1], NULL, 0);
+ if (idx >= valid_pmc_cnt) {
+ printf("Unknown PMC %s -- largest we have is $%d -- can't run your expression\n",
+ at->name, valid_pmc_cnt);
+ exit(-1);
+ }
+ strcpy(at->name, valid_pmcs[idx]);
+ } else {
+ for(i=0, fnd=0; i<valid_pmc_cnt; i++) {
+ if (strcmp(valid_pmcs[i], at->name) == 0) {
+ fnd = 1;
+ break;
+ }
+ }
+ if (!fnd) {
+ printf("PMC %s does not exist on this machine -- can't run your expression\n",
+ at->name);
+ exit(-1);
+ }
+ }
+}
+
+struct expression *
+parse_expression(char *str)
+{
+ struct expression *exp=NULL, *last=NULL, *at;
+ int open_par, close_par;
+ int op_cnt=0;
+ size_t siz, i, x;
+ /*
+ * Walk through a string expression and convert
+ * it to a linked list of actions. We do this by:
+ * a) Counting the open/close paren's, there must
+ * be a matching number.
+ * b) If we have balanced paren's then create a linked list
+ * of the operators, then we validate that expression further.
+ * c) Validating that we have:
+ * val OP val <or>
+ * val OP ( <and>
+ * inside every paran you have a:
+ * val OP val <or>
+ * val OP ( <recursively>
+ * d) A final optional step (not implemented yet) would be
+ * to insert the mathematical precedence paran's. For
+ * the start we will just do the left to right evaluation and
+ * then later we can add this guy to add paran's to make it
+ * mathimatically correct... i.e instead of 1 + 2 * 3 we
+ * would translate it into 1 + ( 2 * 3).
+ */
+ open_par = close_par = 0;
+ siz = strlen(str);
+ /* No trailing newline please */
+ if (str[(siz-1)] == '\n') {
+ str[(siz-1)] = 0;
+ siz--;
+ }
+ for(i=0; i<siz; i++) {
+ if (str[i] == '(') {
+ open_par++;
+ } else if (str[i] == ')') {
+ close_par++;
+ }
+ }
+ if (open_par != close_par) {
+ printf("Invalid expression '%s' %d open paren's and %d close?\n",
+ str, open_par, close_par);
+ exit(-1);
+ }
+ for(i=0; i<siz; i++) {
+ if (str[i] == '(') {
+ at = alloc_and_hook_expr(&exp, &last);
+ at->type = TYPE_PARN_OPEN;
+ } else if (str[i] == ')') {
+ at = alloc_and_hook_expr(&exp, &last);
+ at->type = TYPE_PARN_CLOSE;
+ } else if (str[i] == ' ') {
+ /* Extra blank */
+ continue;
+ } else if (str[i] == '\t') {
+ /* Extra tab */
+ continue;
+ } else if (str[i] == '+') {
+ at = alloc_and_hook_expr(&exp, &last);
+ at->type = TYPE_OP_PLUS;
+ } else if (str[i] == '-') {
+ at = alloc_and_hook_expr(&exp, &last);
+ at->type = TYPE_OP_MINUS;
+ } else if (str[i] == '/') {
+ at = alloc_and_hook_expr(&exp, &last);
+ at->type = TYPE_OP_DIVIDE;
+ } else if (str[i] == '*') {
+ at = alloc_and_hook_expr(&exp, &last);
+ at->type = TYPE_OP_MULT;
+ } else {
+ /* Its a value or PMC constant */
+ at = alloc_and_hook_expr(&exp, &last);
+ if (isdigit(str[i]) || (str[i] == '.')) {
+ at->type = TYPE_VALUE_CON;
+ } else {
+ at->type = TYPE_VALUE_PMC;
+ }
+ x = 0;
+ while ((str[i] != ' ') &&
+ (str[i] != '\t') &&
+ (str[i] != 0) &&
+ (str[i] != ')') &&
+ (str[i] != '(')) {
+ /* We collect the constant until a space or tab */
+ at->name[x] = str[i];
+ i++;
+ x++;
+ if (x >=(sizeof(at->name)-1)) {
+ printf("Value/Constant too long %d max:%d\n",
+ (int)x, (int)(sizeof(at->name)-1));
+ exit(-3);
+ }
+ }
+ if (str[i] != 0) {
+ /* Need to back up and see the last char since
+ * the for will increment the loop.
+ */
+ i--;
+ }
+ /* Now we have pulled the string, set it up */
+ if (at->type == TYPE_VALUE_CON) {
+ at->state = STATE_FILLED;
+ at->value = strtod(at->name, NULL);
+ } else {
+ pmc_name_set(at);
+ }
+ }
+ }
+ /* Now lets validate its a workable expression */
+ if (validate_expr(exp, 0, 0, 0, &op_cnt)) {
+ printf("Invalid expression\n");
+ exit(-4);
+ }
+ set_math_precidence(&exp, exp, NULL);
+ return (exp);
+}
+
+
+
+static struct expression *
+gather_exp_to_paren_close(struct expression *exp, double *val_fill)
+{
+ /*
+ * I have been given ( ???
+ * so I could see either
+ * (
+ * or
+ * Val Op
+ *
+ */
+ struct expression *lastproc;
+ double val;
+
+ if (exp->type == TYPE_PARN_OPEN) {
+ lastproc = gather_exp_to_paren_close(exp->next, &val);
+ *val_fill = val;
+ } else {
+ *val_fill = run_expr(exp, 0, &lastproc);
+ }
+ return(lastproc);
+}
+
+
+double
+run_expr(struct expression *exp, int initial_call, struct expression **lastone)
+{
+ /*
+ * We expect to find either
+ * a) A Open Paren
+ * or
+ * b) Val-> Op -> Val
+ * or
+ * c) Val-> Op -> Open Paren
+ */
+ double val1, val2, res;
+ struct expression *op, *other_half, *rest;
+
+ if (exp->type == TYPE_PARN_OPEN) {
+ op = gather_exp_to_paren_close(exp->next, &val1);
+ } else if(exp->type == TYPE_VALUE_CON) {
+ val1 = exp->value;
+ op = exp->next;
+ } else if (exp->type == TYPE_VALUE_PMC) {
+ val1 = exp->value;
+ op = exp->next;
+ } else {
+ printf("Illegal value in %s huh?\n", __FUNCTION__);
+ exit(-1);
+ }
+ if (op == NULL) {
+ return (val1);
+ }
+more_to_do:
+ other_half = op->next;
+ if (other_half->type == TYPE_PARN_OPEN) {
+ rest = gather_exp_to_paren_close(other_half->next, &val2);
+ } else if(other_half->type == TYPE_VALUE_CON) {
+ val2 = other_half->value;
+ rest = other_half->next;
+ } else if (other_half->type == TYPE_VALUE_PMC) {
+ val2 = other_half->value;
+ rest = other_half->next;
+ } else {
+ printf("Illegal2 value in %s huh?\n", __FUNCTION__);
+ exit(-1);
+ }
+ switch(op->type) {
+ case TYPE_OP_PLUS:
+ res = val1 + val2;
+ break;
+ case TYPE_OP_MINUS:
+ res = val1 - val2;
+ break;
+ case TYPE_OP_MULT:
+ res = val1 * val2;
+ break;
+ case TYPE_OP_DIVIDE:
+ if (val2 != 0.0)
+ res = val1 / val2;
+ else {
+ printf("Division by zero averted\n");
+ res = 1.0;
+ }
+ break;
+ default:
+ printf("Op is not an operator -- its %d\n",
+ op->type);
+ exit(-1);
+ break;
+ }
+ if (rest == NULL) {
+ if (lastone) {
+ *lastone = NULL;
+ }
+ return (res);
+ }
+ if ((rest->type == TYPE_PARN_CLOSE) && (initial_call == 0)) {
+ if (lastone) {
+ *lastone = rest->next;
+ }
+ return(res);
+ }
+ /* There is more, as in
+ * a + b + c
+ * where we just did a + b
+ * so now it becomes val1 is set to res and
+ * we need to proceed with the rest of it.
+ */
+ val1 = res;
+ op = rest;
+ if ((op->type != TYPE_OP_PLUS) &&
+ (op->type != TYPE_OP_MULT) &&
+ (op->type != TYPE_OP_MINUS) &&
+ (op->type != TYPE_OP_DIVIDE)) {
+ printf("%s ending on type:%d not an op??\n", __FUNCTION__, op->type);
+ return(res);
+ }
+ if (op)
+ goto more_to_do;
+ return (res);
+}
+
+#ifdef STAND_ALONE_TESTING
+
+static double
+calc_expr(struct expression *exp)
+{
+ struct expression *at;
+ double xx;
+
+ /* First clear PMC's setting */
+ for(at = exp; at != NULL; at = at->next) {
+ if (at->type == TYPE_VALUE_PMC) {
+ at->state = STATE_UNSET;
+ }
+ }
+ /* Now for all pmc's make up values .. here is where I would pull them */
+ for(at = exp; at != NULL; at = at->next) {
+ if (at->type == TYPE_VALUE_PMC) {
+ at->value = (random() * 1.0);
+ at->state = STATE_FILLED;
+ if (at->value == 0.0) {
+ /* So we don't have div by 0 */
+ at->value = 1.0;
+ }
+ }
+ }
+ /* Now lets calculate the expression */
+ print_exp(exp);
+ xx = run_expr(exp, 1, NULL);
+ printf("Answer is %f\n", xx);
+ return(xx);
+}
+
+
+int
+main(int argc, char **argv)
+{
+ struct expression *exp;
+ if (argc < 2) {
+ printf("Use %s expression\n", argv[0]);
+ return(-1);
+ }
+ exp = parse_expression(argv[1]);
+ printf("Now the calc\n");
+ calc_expr(exp);
+ return(0);
+}
+
+#endif
diff --git a/usr.sbin/pmcstudy/eval_expr.h b/usr.sbin/pmcstudy/eval_expr.h
new file mode 100644
index 0000000..f095513
--- /dev/null
+++ b/usr.sbin/pmcstudy/eval_expr.h
@@ -0,0 +1,58 @@
+#ifndef __eval_expr_h__
+#define __eval_expr_h__
+/*-
+ * Copyright (c) 2015 Netflix 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,
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must 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.
+ */
+__FBSDID("$FreeBSD$");
+
+enum exptype {
+ TYPE_OP_PLUS,
+ TYPE_OP_MINUS,
+ TYPE_OP_MULT,
+ TYPE_OP_DIVIDE,
+ TYPE_PARN_OPEN,
+ TYPE_PARN_CLOSE,
+ TYPE_VALUE_CON,
+ TYPE_VALUE_PMC
+};
+
+#define STATE_UNSET 0 /* We have no setting yet in value */
+#define STATE_FILLED 1 /* We have filled in value */
+
+struct expression {
+ struct expression *next; /* Next in expression. */
+ struct expression *prev; /* Prev in expression. */
+ double value; /* If there is a value to set */
+ enum exptype type; /* What is it */
+ uint8_t state; /* Current state if value type */
+ char name[252]; /* If a PMC whats the name, con value*/
+};
+
+struct expression *parse_expression(char *str);
+double run_expr(struct expression *exp, int initial_call, struct expression **lastone);
+void print_exp(struct expression *exp);
+#endif
diff --git a/usr.sbin/pmcstudy/pmcstudy.8 b/usr.sbin/pmcstudy/pmcstudy.8
new file mode 100644
index 0000000..7c03897
--- /dev/null
+++ b/usr.sbin/pmcstudy/pmcstudy.8
@@ -0,0 +1,145 @@
+.\" Copyright (c) 2015
+.\" Netflix 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.
+.\"
+.\" 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 Mar 26, 2015
+.Dt PMCSTUDY 8
+.Os
+.Sh NAME
+.Nm pmcstudy
+.Nd Perform various studies on a system's overall PMCs.
+.Sh SYNOPSIS
+.Nm
+.Oo Fl i Ar inputfile | Fl A | Fl T | Fl v | Fl m Ar max | Fl e exp | Fl Ar E | Fl h | fl H Oc
+.Nm
+.Fl i Ar inputfile
+.Nm
+.Fl v
+.Nm
+.Fl m Ar max
+.Nm
+.Fl e Ar exp-name
+.Nm
+.Fl E Ar your-expr
+.Nm
+.Fl h
+.Nm
+.Fl H
+.Nm
+.Fl T
+.Sh DESCRIPTION
+The
+.Nm
+program is designed to run various tests against your systems
+performance.
+There are roughly 20-22 canned tests that setup specific
+PMCs and then run various formulas on the output information.
+These formulas can be found in Intel documentation "Using Intel Vtune
+amplifier xe on NNN Generation Intel Core Processors".
+The NNN is either
+2nd, 3rd, 4th or 5th generation i.e., Sandy Bridge, Ivy Bridge, Haswell and Broadwell.
+Currently the program only works on these four Intel processor types.
+.Sh OPTIONS
+The following options are available:
+.Bl -tag -width indent
+.It Fl i Ar filename
+If this option is supplied, instead of running a
+.Xr pmcstat 8
+command to collect the current running information the filename will be read
+in as input instead.
+.It Fl H
+This option will display the complete list of canned formulas that can be run including
+their names which can be input to the
+.Fl e
+option.
+.It Fl e Ar name
+Execute the canned test
+.Ar name
+on the running kernel.
+.It Fl h
+If you add this option to the
+.Fl e
+option the test will not execute but instead give you a small description
+of the test that would run.
+.It Fl T
+This option will execute a test of every PMC to validate that they are working
+on your system.
+If a PMC does not show up in this test chances
+are the kernel
+.Xr hwpmc 4
+driver needs updating with new PMC information.
+.It Fl m Ar num
+This option can restrict the number of one second samples that will
+be collected by your system when running a test (it bounds the
+time the test will run).
+Without this option the test will run
+for 1024 seconds or until the user types ctrl-c.
+.It Fl v
+The verbose option adds debugging output to the command.
+.It Fl E Ar expression
+This option can be used by those that have their own ideas
+on what formulas they want to run.
+The expression given to the
+.Fl E
+option is a "formula".
+The formula can declare directly the PMCs by name
+or you can use an abbreviation %NNN.
+To find out the abbreviations
+on your system you may run the
+.Fl L
+option.
+An example of a formula of your own might be
+.Fl E
+"FP_ASSIST.ANY / INST_RETIRED.ANY_P" or using the abbreviations on a
+Haswell machine you would type
+.Fl E
+" %176 / %150".
+You must have spaces between each entry and
+you may use parentheses to prioritize the operators.
+Add (+), Subtract (-),
+Divide (/) and Multiplication (*) are supported.
+You may also introduce
+constant numbers.
+For example you can do a standard efficency
+test like
+.Fl E
+"UOPS_RETIRED.RETIRE_SLOTS / (4 * CPU_CLK_UNHALTED.THREAD_P)".
+.It Fl L
+This option will list all known PMCs and their abbreviation (%NNN).
+.It Fl A
+Run all canned tests.
+.El
+.Sh SEE ALSO
+.Xr pmc 3 ,
+.Xr pmclog 3 ,
+.Xr hwpmc 4 ,
+.Xr pmcstat 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 11.0.
+.Sh AUTHORS
+.An Randall Stewart Aq Mt rrs@FreeBSD.org
diff --git a/usr.sbin/pmcstudy/pmcstudy.c b/usr.sbin/pmcstudy/pmcstudy.c
new file mode 100644
index 0000000..16c9f51
--- /dev/null
+++ b/usr.sbin/pmcstudy/pmcstudy.c
@@ -0,0 +1,2954 @@
+/*-
+ * Copyright (c) 2014, 2015 Netflix 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,
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/errno.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <getopt.h>
+#include "eval_expr.h"
+__FBSDID("$FreeBSD$");
+
+static int max_pmc_counters = 1;
+static int run_all = 0;
+
+#define MAX_COUNTER_SLOTS 1024
+#define MAX_NLEN 64
+#define MAX_CPU 64
+static int verbose = 0;
+
+extern char **environ;
+extern struct expression *master_exp;
+struct expression *master_exp=NULL;
+
+#define PMC_INITIAL_ALLOC 512
+extern char **valid_pmcs;
+char **valid_pmcs = NULL;
+extern int valid_pmc_cnt;
+int valid_pmc_cnt=0;
+extern int pmc_allocated_cnt;
+int pmc_allocated_cnt=0;
+
+/*
+ * The following two varients on popen and pclose with
+ * the cavet that they get you the PID so that you
+ * can supply it to pclose so it can send a SIGTERM
+ * to the process.
+ */
+static FILE *
+my_popen(const char *command, const char *dir, pid_t *p_pid)
+{
+ FILE *io_out, *io_in;
+ int pdesin[2], pdesout[2];
+ char *argv[4];
+ pid_t pid;
+ char cmd[4];
+ char cmd2[1024];
+ char arg1[4];
+
+ if ((strcmp(dir, "r") != 0) &&
+ (strcmp(dir, "w") != 0)) {
+ errno = EINVAL;
+ return(NULL);
+ }
+ if (pipe(pdesin) < 0)
+ return (NULL);
+
+ if (pipe(pdesout) < 0) {
+ (void)close(pdesin[0]);
+ (void)close(pdesin[1]);
+ return (NULL);
+ }
+ strcpy(cmd, "sh");
+ strcpy(arg1, "-c");
+ strcpy(cmd2, command);
+ argv[0] = cmd;
+ argv[1] = arg1;
+ argv[2] = cmd2;
+ argv[3] = NULL;
+
+ switch (pid = fork()) {
+ case -1: /* Error. */
+ (void)close(pdesin[0]);
+ (void)close(pdesin[1]);
+ (void)close(pdesout[0]);
+ (void)close(pdesout[1]);
+ return (NULL);
+ /* NOTREACHED */
+ case 0: /* Child. */
+ /* Close out un-used sides */
+ (void)close(pdesin[1]);
+ (void)close(pdesout[0]);
+ /* Now prepare the stdin of the process */
+ close(0);
+ (void)dup(pdesin[0]);
+ (void)close(pdesin[0]);
+ /* Now prepare the stdout of the process */
+ close(1);
+ (void)dup(pdesout[1]);
+ /* And lets do stderr just in case */
+ close(2);
+ (void)dup(pdesout[1]);
+ (void)close(pdesout[1]);
+ /* Now run it */
+ execve("/bin/sh", argv, environ);
+ exit(127);
+ /* NOTREACHED */
+ }
+ /* Parent; assume fdopen can't fail. */
+ /* Store the pid */
+ *p_pid = pid;
+ if (strcmp(dir, "r") != 0) {
+ io_out = fdopen(pdesin[1], "w");
+ (void)close(pdesin[0]);
+ (void)close(pdesout[0]);
+ (void)close(pdesout[1]);
+ return(io_out);
+ } else {
+ /* Prepare the input stream */
+ io_in = fdopen(pdesout[0], "r");
+ (void)close(pdesout[1]);
+ (void)close(pdesin[0]);
+ (void)close(pdesin[1]);
+ return (io_in);
+ }
+}
+
+/*
+ * pclose --
+ * Pclose returns -1 if stream is not associated with a `popened' command,
+ * if already `pclosed', or waitpid returns an error.
+ */
+static void
+my_pclose(FILE *io, pid_t the_pid)
+{
+ int pstat;
+ pid_t pid;
+
+ /*
+ * Find the appropriate file pointer and remove it from the list.
+ */
+ (void)fclose(io);
+ /* Die if you are not dead! */
+ kill(the_pid, SIGTERM);
+ do {
+ pid = wait4(the_pid, &pstat, 0, (struct rusage *)0);
+ } while (pid == -1 && errno == EINTR);
+}
+
+struct counters {
+ struct counters *next_cpu;
+ char counter_name[MAX_NLEN]; /* Name of counter */
+ int cpu; /* CPU we are on */
+ int pos; /* Index we are filling to. */
+ uint64_t vals[MAX_COUNTER_SLOTS]; /* Last 64 entries */
+ uint64_t sum; /* Summary of entries */
+};
+
+extern struct counters *glob_cpu[MAX_CPU];
+struct counters *glob_cpu[MAX_CPU];
+
+extern struct counters *cnts;
+struct counters *cnts=NULL;
+
+extern int ncnts;
+int ncnts=0;
+
+extern int (*expression)(struct counters *, int);
+int (*expression)(struct counters *, int);
+
+static const char *threshold=NULL;
+static const char *command;
+
+struct cpu_entry {
+ const char *name;
+ const char *thresh;
+ const char *command;
+ int (*func)(struct counters *, int);
+ int counters_required;
+};
+
+struct cpu_type {
+ char cputype[32];
+ int number;
+ struct cpu_entry *ents;
+ void (*explain)(const char *name);
+};
+extern struct cpu_type the_cpu;
+struct cpu_type the_cpu;
+
+static void
+explain_name_sb(const char *name)
+{
+ const char *mythresh;
+ if (strcmp(name, "allocstall1") == 0) {
+ printf("Examine PARTIAL_RAT_STALLS.SLOW_LEA_WINDOW / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh > .05";
+ } else if (strcmp(name, "allocstall2") == 0) {
+ printf("Examine PARTIAL_RAT_STALLS.FLAGS_MERGE_UOP_CYCLES/CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh > .05";
+ } else if (strcmp(name, "br_miss") == 0) {
+ printf("Examine (20 * BR_MISP_RETIRED.ALL_BRANCHES)/CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .2";
+ } else if (strcmp(name, "splitload") == 0) {
+ printf("Examine MEM_UOPS_RETIRED.SPLIT_LOADS * 5) / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .1";
+ } else if (strcmp(name, "splitstore") == 0) {
+ printf("Examine MEM_UOPS_RETIRED.SPLIT_STORES / MEM_UOPS_RETIRED.ALL_STORES\n");
+ mythresh = "thresh >= .01";
+ } else if (strcmp(name, "contested") == 0) {
+ printf("Examine (MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HITM * 60) / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .05";
+ } else if (strcmp(name, "blockstorefwd") == 0) {
+ printf("Examine (LD_BLOCKS_STORE_FORWARD * 13) / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .05";
+ } else if (strcmp(name, "cache2") == 0) {
+ printf("Examine ((MEM_LOAD_RETIRED.L3_HIT * 26) + \n");
+ printf(" (MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HIT * 43) + \n");
+ printf(" (MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HITM * 60)) / CPU_CLK_UNHALTED.THREAD_P\n");
+ printf("**Note we have it labeled MEM_LOAD_UOPS_RETIRED.LLC_HIT not MEM_LOAD_RETIRED.L3_HIT\n");
+ mythresh = "thresh >= .2";
+ } else if (strcmp(name, "cache1") == 0) {
+ printf("Examine (MEM_LOAD_UOPS_MISC_RETIRED.LLC_MISS * 180) / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .2";
+ } else if (strcmp(name, "dtlbmissload") == 0) {
+ printf("Examine (((DTLB_LOAD_MISSES.STLB_HIT * 7) + DTLB_LOAD_MISSES.WALK_DURATION)\n");
+ printf(" / CPU_CLK_UNHALTED.THREAD_P)\n");
+ mythresh = "thresh >= .1";
+ } else if (strcmp(name, "frontendstall") == 0) {
+ printf("Examine IDQ_UOPS_NOT_DELIVERED.CORE / (CPU_CLK_UNHALTED.THREAD_P * 4)\n");
+ mythresh = "thresh >= .15";
+ } else if (strcmp(name, "clears") == 0) {
+ printf("Examine ((MACHINE_CLEARS.MEMORY_ORDERING + \n");
+ printf(" MACHINE_CLEARS.SMC + \n");
+ printf(" MACHINE_CLEARS.MASKMOV ) * 100 ) / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .02";
+ } else if (strcmp(name, "microassist") == 0) {
+ printf("Examine IDQ.MS_CYCLES / (CPU_CLK_UNHALTED.THREAD_P * 4)\n");
+ printf("***We use IDQ.MS_UOPS,cmask=1 to get cycles\n");
+ mythresh = "thresh >= .05";
+ } else if (strcmp(name, "aliasing_4k") == 0) {
+ printf("Examine (LD_BLOCKS_PARTIAL.ADDRESS_ALIAS * 5) / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .1";
+ } else if (strcmp(name, "fpassist") == 0) {
+ printf("Examine FP_ASSIST.ANY/INST_RETIRED.ANY_P\n");
+ mythresh = "look for a excessive value";
+ } else if (strcmp(name, "otherassistavx") == 0) {
+ printf("Examine (OTHER_ASSISTS.AVX_TO_SSE * 75)/CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "look for a excessive value";
+ } else if (strcmp(name, "otherassistsse") == 0) {
+ printf("Examine (OTHER_ASSISTS.SSE_TO_AVX * 75)/CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "look for a excessive value";
+ } else if (strcmp(name, "eff1") == 0) {
+ printf("Examine (UOPS_RETIRED.RETIRE_SLOTS)/(4 *CPU_CLK_UNHALTED.THREAD_P)\n");
+ mythresh = "thresh < .9";
+ } else if (strcmp(name, "eff2") == 0) {
+ printf("Examine CPU_CLK_UNHALTED.THREAD_P/INST_RETIRED.ANY_P\n");
+ mythresh = "thresh > 1.0";
+ } else if (strcmp(name, "dtlbmissstore") == 0) {
+ printf("Examine (((DTLB_STORE_MISSES.STLB_HIT * 7) + DTLB_STORE_MISSES.WALK_DURATION)\n");
+ printf(" / CPU_CLK_UNHALTED.THREAD_P)\n");
+ mythresh = "thresh >= .05";
+ } else {
+ printf("Unknown name:%s\n", name);
+ mythresh = "unknown entry";
+ }
+ printf("If the value printed is %s we may have the ability to improve performance\n", mythresh);
+}
+
+static void
+explain_name_ib(const char *name)
+{
+ const char *mythresh;
+ if (strcmp(name, "br_miss") == 0) {
+ printf("Examine ((BR_MISP_RETIRED.ALL_BRANCHES /(BR_MISP_RETIRED.ALL_BRANCHES +\n");
+ printf(" MACHINE_CLEAR.COUNT) * ((UOPS_ISSUED.ANY - UOPS_RETIRED.RETIRE_SLOTS + 4 * INT_MISC.RECOVERY_CYCLES)\n");
+ printf("/ (4 * CPU_CLK_UNHALTED.THREAD))))\n");
+ mythresh = "thresh >= .2";
+ } else if (strcmp(name, "eff1") == 0) {
+ printf("Examine (UOPS_RETIRED.RETIRE_SLOTS)/(4 *CPU_CLK_UNHALTED.THREAD_P)\n");
+ mythresh = "thresh < .9";
+ } else if (strcmp(name, "eff2") == 0) {
+ printf("Examine CPU_CLK_UNHALTED.THREAD_P/INST_RETIRED.ANY_P\n");
+ mythresh = "thresh > 1.0";
+ } else if (strcmp(name, "cache1") == 0) {
+ printf("Examine (MEM_LOAD_UOPS_LLC_MISS_RETIRED.LOCAL_DRAM * 180) / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .2";
+ } else if (strcmp(name, "cache2") == 0) {
+ printf("Examine (MEM_LOAD_UOPS_RETIRED.LLC_HIT / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .2";
+ } else if (strcmp(name, "itlbmiss") == 0) {
+ printf("Examine ITLB_MISSES.WALK_DURATION / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh > .05";
+ } else if (strcmp(name, "icachemiss") == 0) {
+ printf("Examine (ICACHE.IFETCH_STALL - ITLB_MISSES.WALK_DURATION)/ CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh > .05";
+ } else if (strcmp(name, "lcpstall") == 0) {
+ printf("Examine ILD_STALL.LCP/CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh > .05";
+ } else if (strcmp(name, "datashare") == 0) {
+ printf("Examine (MEM_LOAD_UOPS_L3_HIT_RETIRED.XSNP_HIT * 43)/CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh > .05";
+ } else if (strcmp(name, "blockstorefwd") == 0) {
+ printf("Examine (LD_BLOCKS_STORE_FORWARD * 13) / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .05";
+ } else if (strcmp(name, "splitload") == 0) {
+ printf("Examine ((L1D_PEND_MISS.PENDING / MEM_LOAD_UOPS_RETIRED.L1_MISS) *\n");
+ printf(" LD_BLOCKS.NO_SR)/CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .1";
+ } else if (strcmp(name, "splitstore") == 0) {
+ printf("Examine MEM_UOPS_RETIRED.SPLIT_STORES / MEM_UOPS_RETIRED.ALL_STORES\n");
+ mythresh = "thresh >= .01";
+ } else if (strcmp(name, "aliasing_4k") == 0) {
+ printf("Examine (LD_BLOCKS_PARTIAL.ADDRESS_ALIAS * 5) / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .1";
+ } else if (strcmp(name, "dtlbmissload") == 0) {
+ printf("Examine (((DTLB_LOAD_MISSES.STLB_HIT * 7) + DTLB_LOAD_MISSES.WALK_DURATION)\n");
+ printf(" / CPU_CLK_UNHALTED.THREAD_P)\n");
+ mythresh = "thresh >= .1";
+ } else if (strcmp(name, "dtlbmissstore") == 0) {
+ printf("Examine (((DTLB_STORE_MISSES.STLB_HIT * 7) + DTLB_STORE_MISSES.WALK_DURATION)\n");
+ printf(" / CPU_CLK_UNHALTED.THREAD_P)\n");
+ mythresh = "thresh >= .05";
+ } else if (strcmp(name, "contested") == 0) {
+ printf("Examine (MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HITM * 60) / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .05";
+ } else if (strcmp(name, "clears") == 0) {
+ printf("Examine ((MACHINE_CLEARS.MEMORY_ORDERING + \n");
+ printf(" MACHINE_CLEARS.SMC + \n");
+ printf(" MACHINE_CLEARS.MASKMOV ) * 100 ) / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .02";
+ } else if (strcmp(name, "microassist") == 0) {
+ printf("Examine IDQ.MS_CYCLES / (4 * CPU_CLK_UNHALTED.THREAD_P)\n");
+ printf("***We use IDQ.MS_UOPS,cmask=1 to get cycles\n");
+ mythresh = "thresh >= .05";
+ } else if (strcmp(name, "fpassist") == 0) {
+ printf("Examine FP_ASSIST.ANY/INST_RETIRED.ANY_P\n");
+ mythresh = "look for a excessive value";
+ } else if (strcmp(name, "otherassistavx") == 0) {
+ printf("Examine (OTHER_ASSISTS.AVX_TO_SSE * 75)/CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "look for a excessive value";
+ } else if (strcmp(name, "otherassistsse") == 0) {
+ printf("Examine (OTHER_ASSISTS.SSE_TO_AVX * 75)/CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "look for a excessive value";
+ } else {
+ printf("Unknown name:%s\n", name);
+ mythresh = "unknown entry";
+ }
+ printf("If the value printed is %s we may have the ability to improve performance\n", mythresh);
+}
+
+
+static void
+explain_name_has(const char *name)
+{
+ const char *mythresh;
+ if (strcmp(name, "eff1") == 0) {
+ printf("Examine (UOPS_RETIRED.RETIRE_SLOTS)/(4 *CPU_CLK_UNHALTED.THREAD_P)\n");
+ mythresh = "thresh < .75";
+ } else if (strcmp(name, "eff2") == 0) {
+ printf("Examine CPU_CLK_UNHALTED.THREAD_P/INST_RETIRED.ANY_P\n");
+ mythresh = "thresh > 1.0";
+ } else if (strcmp(name, "itlbmiss") == 0) {
+ printf("Examine ITLB_MISSES.WALK_DURATION / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh > .05";
+ } else if (strcmp(name, "icachemiss") == 0) {
+ printf("Examine (36 * ICACHE.MISSES)/ CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh > .05";
+ } else if (strcmp(name, "lcpstall") == 0) {
+ printf("Examine ILD_STALL.LCP/CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh > .05";
+ } else if (strcmp(name, "cache1") == 0) {
+ printf("Examine (MEM_LOAD_UOPS_LLC_MISS_RETIRED.LOCAL_DRAM * 180) / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .2";
+ } else if (strcmp(name, "cache2") == 0) {
+ printf("Examine ((MEM_LOAD_UOPS_RETIRED.LLC_HIT * 36) + \n");
+ printf(" (MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HIT * 72) + \n");
+ printf(" (MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HITM * 84))\n");
+ printf(" / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .2";
+ } else if (strcmp(name, "contested") == 0) {
+ printf("Examine (MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HITM * 84) / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .05";
+ } else if (strcmp(name, "datashare") == 0) {
+ printf("Examine (MEM_LOAD_UOPS_L3_HIT_RETIRED.XSNP_HIT * 72)/CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh > .05";
+ } else if (strcmp(name, "blockstorefwd") == 0) {
+ printf("Examine (LD_BLOCKS_STORE_FORWARD * 13) / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .05";
+ } else if (strcmp(name, "splitload") == 0) {
+ printf("Examine (MEM_UOPS_RETIRED.SPLIT_LOADS * 5) / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .1";
+ } else if (strcmp(name, "splitstore") == 0) {
+ printf("Examine MEM_UOPS_RETIRED.SPLIT_STORES / MEM_UOPS_RETIRED.ALL_STORES\n");
+ mythresh = "thresh >= .01";
+ } else if (strcmp(name, "aliasing_4k") == 0) {
+ printf("Examine (LD_BLOCKS_PARTIAL.ADDRESS_ALIAS * 5) / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .1";
+ } else if (strcmp(name, "dtlbmissload") == 0) {
+ printf("Examine (((DTLB_LOAD_MISSES.STLB_HIT * 7) + DTLB_LOAD_MISSES.WALK_DURATION)\n");
+ printf(" / CPU_CLK_UNHALTED.THREAD_P)\n");
+ mythresh = "thresh >= .1";
+ } else if (strcmp(name, "br_miss") == 0) {
+ printf("Examine (20 * BR_MISP_RETIRED.ALL_BRANCHES)/CPU_CLK_UNHALTED.THREAD\n");
+ mythresh = "thresh >= .2";
+ } else if (strcmp(name, "clears") == 0) {
+ printf("Examine ((MACHINE_CLEARS.MEMORY_ORDERING + \n");
+ printf(" MACHINE_CLEARS.SMC + \n");
+ printf(" MACHINE_CLEARS.MASKMOV ) * 100 ) / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .02";
+ } else if (strcmp(name, "microassist") == 0) {
+ printf("Examine IDQ.MS_CYCLES / (4 * CPU_CLK_UNHALTED.THREAD_P)\n");
+ printf("***We use IDQ.MS_UOPS,cmask=1 to get cycles\n");
+ mythresh = "thresh >= .05";
+ } else if (strcmp(name, "fpassist") == 0) {
+ printf("Examine FP_ASSIST.ANY/INST_RETIRED.ANY_P\n");
+ mythresh = "look for a excessive value";
+ } else if (strcmp(name, "otherassistavx") == 0) {
+ printf("Examine (OTHER_ASSISTS.AVX_TO_SSE * 75)/CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "look for a excessive value";
+ } else if (strcmp(name, "otherassistsse") == 0) {
+ printf("Examine (OTHER_ASSISTS.SSE_TO_AVX * 75)/CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "look for a excessive value";
+ } else {
+ printf("Unknown name:%s\n", name);
+ mythresh = "unknown entry";
+ }
+ printf("If the value printed is %s we may have the ability to improve performance\n", mythresh);
+}
+
+
+
+static struct counters *
+find_counter(struct counters *base, const char *name)
+{
+ struct counters *at;
+ int len;
+
+ at = base;
+ len = strlen(name);
+ while(at) {
+ if (strncmp(at->counter_name, name, len) == 0) {
+ return(at);
+ }
+ at = at->next_cpu;
+ }
+ printf("Can't find counter %s\n", name);
+ printf("We have:\n");
+ at = base;
+ while(at) {
+ printf("- %s\n", at->counter_name);
+ at = at->next_cpu;
+ }
+ exit(-1);
+}
+
+static int
+allocstall1(struct counters *cpu, int pos)
+{
+/* 1 - PARTIAL_RAT_STALLS.SLOW_LEA_WINDOW/CPU_CLK_UNHALTED.THREAD_P (thresh > .05)*/
+ int ret;
+ struct counters *partial;
+ struct counters *unhalt;
+ double un, par, res;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ partial = find_counter(cpu, "PARTIAL_RAT_STALLS.SLOW_LEA_WINDOW");
+ if (pos != -1) {
+ par = partial->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ par = partial->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = par/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+static int
+allocstall2(struct counters *cpu, int pos)
+{
+/* 2 - PARTIAL_RAT_STALLS.FLAGS_MERGE_UOP_CYCLES/CPU_CLK_UNHALTED.THREAD_P (thresh >.05) */
+ int ret;
+ struct counters *partial;
+ struct counters *unhalt;
+ double un, par, res;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ partial = find_counter(cpu, "PARTIAL_RAT_STALLS.FLAGS_MERGE_UOP");
+ if (pos != -1) {
+ par = partial->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ par = partial->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = par/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+static int
+br_mispredict(struct counters *cpu, int pos)
+{
+ struct counters *brctr;
+ struct counters *unhalt;
+ int ret;
+/* 3 - (20 * BR_MISP_RETIRED.ALL_BRANCHES)/CPU_CLK_UNHALTED.THREAD_P (thresh >= .2) */
+ double br, un, con, res;
+ con = 20.0;
+
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ brctr = find_counter(cpu, "BR_MISP_RETIRED.ALL_BRANCHES");
+ if (pos != -1) {
+ br = brctr->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ br = brctr->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = (con * br)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+static int
+br_mispredictib(struct counters *cpu, int pos)
+{
+ struct counters *brctr;
+ struct counters *unhalt;
+ struct counters *clear, *clear2, *clear3;
+ struct counters *uops;
+ struct counters *recv;
+ struct counters *iss;
+/* "pmcstat -s CPU_CLK_UNHALTED.THREAD_P -s BR_MISP_RETIRED.ALL_BRANCHES -s MACHINE_CLEARS.MEMORY_ORDERING -s MACHINE_CLEARS.SMC -s MACHINE_CLEARS.MASKMOV -s UOPS_ISSUED.ANY -s UOPS_RETIRED.RETIRE_SLOTS -s INT_MISC.RECOVERY_CYCLES -w 1",*/
+ int ret;
+ /*
+ * (BR_MISP_RETIRED.ALL_BRANCHES /
+ * (BR_MISP_RETIRED.ALL_BRANCHES +
+ * MACHINE_CLEAR.COUNT) *
+ * ((UOPS_ISSUED.ANY - UOPS_RETIRED.RETIRE_SLOTS + 4 * INT_MISC.RECOVERY_CYCLES) / (4 * CPU_CLK_UNHALTED.THREAD)))
+ *
+ */
+ double br, cl, cl2, cl3, uo, re, un, con, res, is;
+ con = 4.0;
+
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ brctr = find_counter(cpu, "BR_MISP_RETIRED.ALL_BRANCHES");
+ clear = find_counter(cpu, "MACHINE_CLEARS.MEMORY_ORDERING");
+ clear2 = find_counter(cpu, "MACHINE_CLEARS.SMC");
+ clear3 = find_counter(cpu, "MACHINE_CLEARS.MASKMOV");
+ uops = find_counter(cpu, "UOPS_RETIRED.RETIRE_SLOTS");
+ iss = find_counter(cpu, "UOPS_ISSUED.ANY");
+ recv = find_counter(cpu, "INT_MISC.RECOVERY_CYCLES");
+ if (pos != -1) {
+ br = brctr->vals[pos] * 1.0;
+ cl = clear->vals[pos] * 1.0;
+ cl2 = clear2->vals[pos] * 1.0;
+ cl3 = clear3->vals[pos] * 1.0;
+ uo = uops->vals[pos] * 1.0;
+ re = recv->vals[pos] * 1.0;
+ is = iss->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ br = brctr->sum * 1.0;
+ cl = clear->sum * 1.0;
+ cl2 = clear2->sum * 1.0;
+ cl3 = clear3->sum * 1.0;
+ uo = uops->sum * 1.0;
+ re = recv->sum * 1.0;
+ is = iss->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = (br/(br + cl + cl2 + cl3) * ((is - uo + con * re) / (con * un)));
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+
+static int
+br_mispredict_broad(struct counters *cpu, int pos)
+{
+ struct counters *brctr;
+ struct counters *unhalt;
+ struct counters *clear;
+ struct counters *uops;
+ struct counters *uops_ret;
+ struct counters *recv;
+ int ret;
+ double br, cl, uo, uo_r, re, con, un, res;
+
+ con = 4.0;
+
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ brctr = find_counter(cpu, "BR_MISP_RETIRED.ALL_BRANCHES");
+ clear = find_counter(cpu, "MACHINE_CLEARS.CYCLES");
+ uops = find_counter(cpu, "UOPS_ISSUED.ANY");
+ uops_ret = find_counter(cpu, "UOPS_RETIRED.RETIRE_SLOTS");
+ recv = find_counter(cpu, "INT_MISC.RECOVERY_CYCLES");
+
+ if (pos != -1) {
+ un = unhalt->vals[pos] * 1.0;
+ br = brctr->vals[pos] * 1.0;
+ cl = clear->vals[pos] * 1.0;
+ uo = uops->vals[pos] * 1.0;
+ uo_r = uops_ret->vals[pos] * 1.0;
+ re = recv->vals[pos] * 1.0;
+ } else {
+ un = unhalt->sum * 1.0;
+ br = brctr->sum * 1.0;
+ cl = clear->sum * 1.0;
+ uo = uops->sum * 1.0;
+ uo_r = uops_ret->sum * 1.0;
+ re = recv->sum * 1.0;
+ }
+ res = br / (br + cl) * (uo - uo_r + con * re) / (un * con);
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+static int
+splitloadib(struct counters *cpu, int pos)
+{
+ int ret;
+ struct counters *mem;
+ struct counters *l1d, *ldblock;
+ struct counters *unhalt;
+ double un, memd, res, l1, ldb;
+ /*
+ * ((L1D_PEND_MISS.PENDING / MEM_LOAD_UOPS_RETIRED.L1_MISS) * LD_BLOCKS.NO_SR) / CPU_CLK_UNHALTED.THREAD_P
+ * "pmcstat -s CPU_CLK_UNHALTED.THREAD_P -s L1D_PEND_MISS.PENDING -s MEM_LOAD_UOPS_RETIRED.L1_MISS -s LD_BLOCKS.NO_SR -w 1",
+ */
+
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ mem = find_counter(cpu, "MEM_LOAD_UOPS_RETIRED.L1_MISS");
+ l1d = find_counter(cpu, "L1D_PEND_MISS.PENDING");
+ ldblock = find_counter(cpu, "LD_BLOCKS.NO_SR");
+ if (pos != -1) {
+ memd = mem->vals[pos] * 1.0;
+ l1 = l1d->vals[pos] * 1.0;
+ ldb = ldblock->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ memd = mem->sum * 1.0;
+ l1 = l1d->sum * 1.0;
+ ldb = ldblock->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = ((l1 / memd) * ldb)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+
+static int
+splitload(struct counters *cpu, int pos)
+{
+ int ret;
+ struct counters *mem;
+ struct counters *unhalt;
+ double con, un, memd, res;
+/* 4 - (MEM_UOPS_RETIRED.SPLIT_LOADS * 5) / CPU_CLK_UNHALTED.THREAD_P (thresh >= .1)*/
+
+ con = 5.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ mem = find_counter(cpu, "MEM_UOPS_RETIRED.SPLIT_LOADS");
+ if (pos != -1) {
+ memd = mem->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ memd = mem->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = (memd * con)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+
+static int
+splitload_sb(struct counters *cpu, int pos)
+{
+ int ret;
+ struct counters *mem;
+ struct counters *unhalt;
+ double con, un, memd, res;
+/* 4 - (MEM_UOP_RETIRED.SPLIT_LOADS * 5) / CPU_CLK_UNHALTED.THREAD_P (thresh >= .1)*/
+
+ con = 5.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ mem = find_counter(cpu, "MEM_UOP_RETIRED.SPLIT_LOADS");
+ if (pos != -1) {
+ memd = mem->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ memd = mem->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = (memd * con)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+
+static int
+splitstore_sb(struct counters *cpu, int pos)
+{
+ /* 5 - MEM_UOP_RETIRED.SPLIT_STORES / MEM_UOP_RETIRED.ALL_STORES (thresh > 0.01) */
+ int ret;
+ struct counters *mem_split;
+ struct counters *mem_stores;
+ double memsplit, memstore, res;
+ mem_split = find_counter(cpu, "MEM_UOP_RETIRED.SPLIT_STORES");
+ mem_stores = find_counter(cpu, "MEM_UOP_RETIRED.ALL_STORES");
+ if (pos != -1) {
+ memsplit = mem_split->vals[pos] * 1.0;
+ memstore = mem_stores->vals[pos] * 1.0;
+ } else {
+ memsplit = mem_split->sum * 1.0;
+ memstore = mem_stores->sum * 1.0;
+ }
+ res = memsplit/memstore;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+
+
+static int
+splitstore(struct counters *cpu, int pos)
+{
+ /* 5 - MEM_UOPS_RETIRED.SPLIT_STORES / MEM_UOPS_RETIRED.ALL_STORES (thresh > 0.01) */
+ int ret;
+ struct counters *mem_split;
+ struct counters *mem_stores;
+ double memsplit, memstore, res;
+ mem_split = find_counter(cpu, "MEM_UOPS_RETIRED.SPLIT_STORES");
+ mem_stores = find_counter(cpu, "MEM_UOPS_RETIRED.ALL_STORES");
+ if (pos != -1) {
+ memsplit = mem_split->vals[pos] * 1.0;
+ memstore = mem_stores->vals[pos] * 1.0;
+ } else {
+ memsplit = mem_split->sum * 1.0;
+ memstore = mem_stores->sum * 1.0;
+ }
+ res = memsplit/memstore;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+
+static int
+contested(struct counters *cpu, int pos)
+{
+ /* 6 - (MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HITM * 60) / CPU_CLK_UNHALTED.THREAD_P (thresh >.05) */
+ int ret;
+ struct counters *mem;
+ struct counters *unhalt;
+ double con, un, memd, res;
+
+ con = 60.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ mem = find_counter(cpu, "MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HITM");
+ if (pos != -1) {
+ memd = mem->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ memd = mem->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = (memd * con)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+static int
+contested_has(struct counters *cpu, int pos)
+{
+ /* 6 - (MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HITM * 84) / CPU_CLK_UNHALTED.THREAD_P (thresh >.05) */
+ int ret;
+ struct counters *mem;
+ struct counters *unhalt;
+ double con, un, memd, res;
+
+ con = 84.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ mem = find_counter(cpu, "MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HITM");
+ if (pos != -1) {
+ memd = mem->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ memd = mem->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = (memd * con)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+static int
+contestedbroad(struct counters *cpu, int pos)
+{
+ /* 6 - (MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HITM * 84) / CPU_CLK_UNHALTED.THREAD_P (thresh >.05) */
+ int ret;
+ struct counters *mem;
+ struct counters *mem2;
+ struct counters *unhalt;
+ double con, un, memd, memtoo, res;
+
+ con = 84.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ mem = find_counter(cpu, "MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HITM");
+ mem2 = find_counter(cpu,"MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_MISS");
+
+ if (pos != -1) {
+ memd = mem->vals[pos] * 1.0;
+ memtoo = mem2->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ memd = mem->sum * 1.0;
+ memtoo = mem2->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = ((memd * con) + memtoo)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+
+static int
+blockstoreforward(struct counters *cpu, int pos)
+{
+ /* 7 - (LD_BLOCKS_STORE_FORWARD * 13) / CPU_CLK_UNHALTED.THREAD_P (thresh >= .05)*/
+ int ret;
+ struct counters *ldb;
+ struct counters *unhalt;
+ double con, un, ld, res;
+
+ con = 13.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ ldb = find_counter(cpu, "LD_BLOCKS_STORE_FORWARD");
+ if (pos != -1) {
+ ld = ldb->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ ld = ldb->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = (ld * con)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+static int
+cache2(struct counters *cpu, int pos)
+{
+ /* ** Suspect ***
+ * 8 - ((MEM_LOAD_RETIRED.L3_HIT * 26) + (MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HIT * 43) +
+ * (MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HITM * 60)) / CPU_CLK_UNHALTED.THREAD_P (thresh >.2)
+ */
+ int ret;
+ struct counters *mem1, *mem2, *mem3;
+ struct counters *unhalt;
+ double con1, con2, con3, un, me_1, me_2, me_3, res;
+
+ con1 = 26.0;
+ con2 = 43.0;
+ con3 = 60.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+/* Call for MEM_LOAD_RETIRED.L3_HIT possibly MEM_LOAD_UOPS_RETIRED.LLC_HIT ?*/
+ mem1 = find_counter(cpu, "MEM_LOAD_UOPS_RETIRED.LLC_HIT");
+ mem2 = find_counter(cpu, "MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HIT");
+ mem3 = find_counter(cpu, "MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HITM");
+ if (pos != -1) {
+ me_1 = mem1->vals[pos] * 1.0;
+ me_2 = mem2->vals[pos] * 1.0;
+ me_3 = mem3->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ me_1 = mem1->sum * 1.0;
+ me_2 = mem2->sum * 1.0;
+ me_3 = mem3->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = ((me_1 * con1) + (me_2 * con2) + (me_3 * con3))/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+static int
+datasharing(struct counters *cpu, int pos)
+{
+ /*
+ * (MEM_LOAD_UOPS_L3_HIT_RETIRED.XSNP_HIT * 43)/ CPU_CLK_UNHALTED.THREAD_P (thresh >.2)
+ */
+ int ret;
+ struct counters *mem;
+ struct counters *unhalt;
+ double con, res, me, un;
+
+ con = 43.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ mem = find_counter(cpu, "MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HIT");
+ if (pos != -1) {
+ me = mem->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ me = mem->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = (me * con)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+
+}
+
+
+static int
+datasharing_has(struct counters *cpu, int pos)
+{
+ /*
+ * (MEM_LOAD_UOPS_L3_HIT_RETIRED.XSNP_HIT * 43)/ CPU_CLK_UNHALTED.THREAD_P (thresh >.2)
+ */
+ int ret;
+ struct counters *mem;
+ struct counters *unhalt;
+ double con, res, me, un;
+
+ con = 72.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ mem = find_counter(cpu, "MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HIT");
+ if (pos != -1) {
+ me = mem->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ me = mem->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = (me * con)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+
+}
+
+
+static int
+cache2ib(struct counters *cpu, int pos)
+{
+ /*
+ * (29 * MEM_LOAD_UOPS_RETIRED.LLC_HIT / CPU_CLK_UNHALTED.THREAD_P (thresh >.2)
+ */
+ int ret;
+ struct counters *mem;
+ struct counters *unhalt;
+ double con, un, me, res;
+
+ con = 29.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ mem = find_counter(cpu, "MEM_LOAD_UOPS_RETIRED.LLC_HIT");
+ if (pos != -1) {
+ me = mem->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ me = mem->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = (con * me)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+static int
+cache2has(struct counters *cpu, int pos)
+{
+ /*
+ * Examine ((MEM_LOAD_UOPS_RETIRED.LLC_HIT * 36) + \
+ * (MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HIT * 72) +
+ * (MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HITM * 84))
+ * / CPU_CLK_UNHALTED.THREAD_P
+ */
+ int ret;
+ struct counters *mem1, *mem2, *mem3;
+ struct counters *unhalt;
+ double con1, con2, con3, un, me1, me2, me3, res;
+
+ con1 = 36.0;
+ con2 = 72.0;
+ con3 = 84.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ mem1 = find_counter(cpu, "MEM_LOAD_UOPS_RETIRED.LLC_HIT");
+ mem2 = find_counter(cpu, "MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HIT");
+ mem3 = find_counter(cpu, "MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HITM");
+ if (pos != -1) {
+ me1 = mem1->vals[pos] * 1.0;
+ me2 = mem2->vals[pos] * 1.0;
+ me3 = mem3->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ me1 = mem1->sum * 1.0;
+ me2 = mem2->sum * 1.0;
+ me3 = mem3->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = ((me1 * con1) + (me2 * con2) + (me3 * con3))/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+
+static int
+cache2broad(struct counters *cpu, int pos)
+{
+ /*
+ * (29 * MEM_LOAD_UOPS_RETIRED.LLC_HIT / CPU_CLK_UNHALTED.THREAD_P (thresh >.2)
+ */
+ int ret;
+ struct counters *mem;
+ struct counters *unhalt;
+ double con, un, me, res;
+
+ con = 36.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ mem = find_counter(cpu, "MEM_LOAD_UOPS_RETIRED.L3_HIT");
+ if (pos != -1) {
+ me = mem->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ me = mem->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = (con * me)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+
+static int
+cache1(struct counters *cpu, int pos)
+{
+ /* 9 - (MEM_LOAD_UOPS_MISC_RETIRED.LLC_MISS * 180) / CPU_CLK_UNHALTED.THREAD_P (thresh >= .2) */
+ int ret;
+ struct counters *mem;
+ struct counters *unhalt;
+ double con, un, me, res;
+
+ con = 180.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ mem = find_counter(cpu, "MEM_LOAD_UOPS_MISC_RETIRED.LLC_MISS");
+ if (pos != -1) {
+ me = mem->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ me = mem->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = (me * con)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+static int
+cache1ib(struct counters *cpu, int pos)
+{
+ /* 9 - (MEM_LOAD_UOPS_L3_MISS_RETIRED.LCOAL_DRAM * 180) / CPU_CLK_UNHALTED.THREAD_P (thresh >= .2) */
+ int ret;
+ struct counters *mem;
+ struct counters *unhalt;
+ double con, un, me, res;
+
+ con = 180.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ mem = find_counter(cpu, "MEM_LOAD_UOPS_LLC_MISS_RETIRED.LOCAL_DRAM");
+ if (pos != -1) {
+ me = mem->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ me = mem->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = (me * con)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+
+static int
+cache1broad(struct counters *cpu, int pos)
+{
+ /* 9 - (MEM_LOAD_UOPS_L3_MISS_RETIRED.LCOAL_DRAM * 180) / CPU_CLK_UNHALTED.THREAD_P (thresh >= .2) */
+ int ret;
+ struct counters *mem;
+ struct counters *unhalt;
+ double con, un, me, res;
+
+ con = 180.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ mem = find_counter(cpu, "MEM_LOAD_UOPS_RETIRED.L3_MISS");
+ if (pos != -1) {
+ me = mem->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ me = mem->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = (me * con)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+
+static int
+dtlb_missload(struct counters *cpu, int pos)
+{
+ /* 10 - ((DTLB_LOAD_MISSES.STLB_HIT * 7) + DTLB_LOAD_MISSES.WALK_DURATION) / CPU_CLK_UNHALTED.THREAD_P (t >=.1) */
+ int ret;
+ struct counters *dtlb_m, *dtlb_d;
+ struct counters *unhalt;
+ double con, un, d1, d2, res;
+
+ con = 7.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ dtlb_m = find_counter(cpu, "DTLB_LOAD_MISSES.STLB_HIT");
+ dtlb_d = find_counter(cpu, "DTLB_LOAD_MISSES.WALK_DURATION");
+ if (pos != -1) {
+ d1 = dtlb_m->vals[pos] * 1.0;
+ d2 = dtlb_d->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ d1 = dtlb_m->sum * 1.0;
+ d2 = dtlb_d->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = ((d1 * con) + d2)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+static int
+dtlb_missstore(struct counters *cpu, int pos)
+{
+ /*
+ * ((DTLB_STORE_MISSES.STLB_HIT * 7) + DTLB_STORE_MISSES.WALK_DURATION) /
+ * CPU_CLK_UNHALTED.THREAD_P (t >= .1)
+ */
+ int ret;
+ struct counters *dtsb_m, *dtsb_d;
+ struct counters *unhalt;
+ double con, un, d1, d2, res;
+
+ con = 7.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ dtsb_m = find_counter(cpu, "DTLB_STORE_MISSES.STLB_HIT");
+ dtsb_d = find_counter(cpu, "DTLB_STORE_MISSES.WALK_DURATION");
+ if (pos != -1) {
+ d1 = dtsb_m->vals[pos] * 1.0;
+ d2 = dtsb_d->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ d1 = dtsb_m->sum * 1.0;
+ d2 = dtsb_d->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = ((d1 * con) + d2)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+static int
+itlb_miss(struct counters *cpu, int pos)
+{
+ /* ITLB_MISSES.WALK_DURATION / CPU_CLK_UNTHREAD_P IB */
+ int ret;
+ struct counters *itlb;
+ struct counters *unhalt;
+ double un, d1, res;
+
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ itlb = find_counter(cpu, "ITLB_MISSES.WALK_DURATION");
+ if (pos != -1) {
+ d1 = itlb->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ d1 = itlb->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = d1/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+
+static int
+itlb_miss_broad(struct counters *cpu, int pos)
+{
+ /* (7 * ITLB_MISSES.STLB_HIT_4K + ITLB_MISSES.WALK_DURATION) / CPU_CLK_UNTHREAD_P */
+ int ret;
+ struct counters *itlb;
+ struct counters *unhalt;
+ struct counters *four_k;
+ double un, d1, res, k;
+
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ itlb = find_counter(cpu, "ITLB_MISSES.WALK_DURATION");
+ four_k = find_counter(cpu, "ITLB_MISSES.STLB_HIT_4K");
+ if (pos != -1) {
+ d1 = itlb->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ k = four_k->vals[pos] * 1.0;
+ } else {
+ d1 = itlb->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ k = four_k->sum * 1.0;
+ }
+ res = (7.0 * k + d1)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+
+static int
+icache_miss(struct counters *cpu, int pos)
+{
+ /* (ICACHE.IFETCH_STALL - ITLB_MISSES.WALK_DURATION) / CPU_CLK_UNHALTED.THREAD_P IB */
+
+ int ret;
+ struct counters *itlb, *icache;
+ struct counters *unhalt;
+ double un, d1, ic, res;
+
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ itlb = find_counter(cpu, "ITLB_MISSES.WALK_DURATION");
+ icache = find_counter(cpu, "ICACHE.IFETCH_STALL");
+ if (pos != -1) {
+ d1 = itlb->vals[pos] * 1.0;
+ ic = icache->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ d1 = itlb->sum * 1.0;
+ ic = icache->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = (ic-d1)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+
+}
+
+static int
+icache_miss_has(struct counters *cpu, int pos)
+{
+ /* (36 * ICACHE.MISSES) / CPU_CLK_UNHALTED.THREAD_P */
+
+ int ret;
+ struct counters *icache;
+ struct counters *unhalt;
+ double un, con, ic, res;
+
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ icache = find_counter(cpu, "ICACHE.MISSES");
+ con = 36.0;
+ if (pos != -1) {
+ ic = icache->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ ic = icache->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = (con * ic)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+
+}
+
+static int
+lcp_stall(struct counters *cpu, int pos)
+{
+ /* ILD_STALL.LCP/CPU_CLK_UNHALTED.THREAD_P IB */
+ int ret;
+ struct counters *ild;
+ struct counters *unhalt;
+ double un, d1, res;
+
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ ild = find_counter(cpu, "ILD_STALL.LCP");
+ if (pos != -1) {
+ d1 = ild->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ d1 = ild->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = d1/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+
+}
+
+
+static int
+frontendstall(struct counters *cpu, int pos)
+{
+ /* 12 - IDQ_UOPS_NOT_DELIVERED.CORE / (CPU_CLK_UNHALTED.THREAD_P * 4) (thresh >= .15) */
+ int ret;
+ struct counters *idq;
+ struct counters *unhalt;
+ double con, un, id, res;
+
+ con = 4.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ idq = find_counter(cpu, "IDQ_UOPS_NOT_DELIVERED.CORE");
+ if (pos != -1) {
+ id = idq->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ id = idq->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = id/(un * con);
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+static int
+clears(struct counters *cpu, int pos)
+{
+ /* 13 - ((MACHINE_CLEARS.MEMORY_ORDERING + MACHINE_CLEARS.SMC + MACHINE_CLEARS.MASKMOV ) * 100 )
+ * / CPU_CLK_UNHALTED.THREAD_P (thresh >= .02)*/
+
+ int ret;
+ struct counters *clr1, *clr2, *clr3;
+ struct counters *unhalt;
+ double con, un, cl1, cl2, cl3, res;
+
+ con = 100.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ clr1 = find_counter(cpu, "MACHINE_CLEARS.MEMORY_ORDERING");
+ clr2 = find_counter(cpu, "MACHINE_CLEARS.SMC");
+ clr3 = find_counter(cpu, "MACHINE_CLEARS.MASKMOV");
+
+ if (pos != -1) {
+ cl1 = clr1->vals[pos] * 1.0;
+ cl2 = clr2->vals[pos] * 1.0;
+ cl3 = clr3->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ cl1 = clr1->sum * 1.0;
+ cl2 = clr2->sum * 1.0;
+ cl3 = clr3->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = ((cl1 + cl2 + cl3) * con)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+
+
+static int
+clears_broad(struct counters *cpu, int pos)
+{
+ int ret;
+ struct counters *clr1, *clr2, *clr3, *cyc;
+ struct counters *unhalt;
+ double con, un, cl1, cl2, cl3, cy, res;
+
+ con = 100.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ clr1 = find_counter(cpu, "MACHINE_CLEARS.MEMORY_ORDERING");
+ clr2 = find_counter(cpu, "MACHINE_CLEARS.SMC");
+ clr3 = find_counter(cpu, "MACHINE_CLEARS.MASKMOV");
+ cyc = find_counter(cpu, "MACHINE_CLEARS.CYCLES");
+ if (pos != -1) {
+ cl1 = clr1->vals[pos] * 1.0;
+ cl2 = clr2->vals[pos] * 1.0;
+ cl3 = clr3->vals[pos] * 1.0;
+ cy = cyc->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ cl1 = clr1->sum * 1.0;
+ cl2 = clr2->sum * 1.0;
+ cl3 = clr3->sum * 1.0;
+ cy = cyc->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ /* Formula not listed but extrapulated to add the cy ?? */
+ res = ((cl1 + cl2 + cl3 + cy) * con)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+
+
+
+
+static int
+microassist(struct counters *cpu, int pos)
+{
+ /* 14 - IDQ.MS_CYCLES / CPU_CLK_UNHALTED.THREAD_P (thresh > .05) */
+ int ret;
+ struct counters *idq;
+ struct counters *unhalt;
+ double un, id, res, con;
+
+ con = 4.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ idq = find_counter(cpu, "IDQ.MS_UOPS");
+ if (pos != -1) {
+ id = idq->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ id = idq->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = id/(un * con);
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+
+static int
+microassist_broad(struct counters *cpu, int pos)
+{
+ int ret;
+ struct counters *idq;
+ struct counters *unhalt;
+ struct counters *uopiss;
+ struct counters *uopret;
+ double un, id, res, con, uoi, uor;
+
+ con = 4.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ idq = find_counter(cpu, "IDQ.MS_UOPS");
+ uopiss = find_counter(cpu, "UOPS_ISSUED.ANY");
+ uopret = find_counter(cpu, "UOPS_RETIRED.RETIRE_SLOTS");
+ if (pos != -1) {
+ id = idq->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ uoi = uopiss->vals[pos] * 1.0;
+ uor = uopret->vals[pos] * 1.0;
+ } else {
+ id = idq->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ uoi = uopiss->sum * 1.0;
+ uor = uopret->sum * 1.0;
+ }
+ res = (uor/uoi) * (id/(un * con));
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+
+static int
+aliasing(struct counters *cpu, int pos)
+{
+ /* 15 - (LD_BLOCKS_PARTIAL.ADDRESS_ALIAS * 5) / CPU_CLK_UNHALTED.THREAD_P (thresh > .1) */
+ int ret;
+ struct counters *ld;
+ struct counters *unhalt;
+ double un, lds, con, res;
+
+ con = 5.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ ld = find_counter(cpu, "LD_BLOCKS_PARTIAL.ADDRESS_ALIAS");
+ if (pos != -1) {
+ lds = ld->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ lds = ld->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = (lds * con)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+static int
+aliasing_broad(struct counters *cpu, int pos)
+{
+ /* 15 - (LD_BLOCKS_PARTIAL.ADDRESS_ALIAS * 5) / CPU_CLK_UNHALTED.THREAD_P (thresh > .1) */
+ int ret;
+ struct counters *ld;
+ struct counters *unhalt;
+ double un, lds, con, res;
+
+ con = 7.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ ld = find_counter(cpu, "LD_BLOCKS_PARTIAL.ADDRESS_ALIAS");
+ if (pos != -1) {
+ lds = ld->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ lds = ld->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = (lds * con)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+
+static int
+fpassists(struct counters *cpu, int pos)
+{
+ /* 16 - FP_ASSIST.ANY/INST_RETIRED.ANY_P */
+ int ret;
+ struct counters *fp;
+ struct counters *inst;
+ double un, fpd, res;
+
+ inst = find_counter(cpu, "INST_RETIRED.ANY_P");
+ fp = find_counter(cpu, "FP_ASSIST.ANY");
+ if (pos != -1) {
+ fpd = fp->vals[pos] * 1.0;
+ un = inst->vals[pos] * 1.0;
+ } else {
+ fpd = fp->sum * 1.0;
+ un = inst->sum * 1.0;
+ }
+ res = fpd/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+static int
+otherassistavx(struct counters *cpu, int pos)
+{
+ /* 17 - (OTHER_ASSISTS.AVX_TO_SSE * 75)/CPU_CLK_UNHALTED.THREAD_P thresh .1*/
+ int ret;
+ struct counters *oth;
+ struct counters *unhalt;
+ double un, ot, con, res;
+
+ con = 75.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ oth = find_counter(cpu, "OTHER_ASSISTS.AVX_TO_SSE");
+ if (pos != -1) {
+ ot = oth->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ ot = oth->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = (ot * con)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+static int
+otherassistsse(struct counters *cpu, int pos)
+{
+
+ int ret;
+ struct counters *oth;
+ struct counters *unhalt;
+ double un, ot, con, res;
+
+ /* 18 (OTHER_ASSISTS.SSE_TO_AVX * 75)/CPU_CLK_UNHALTED.THREAD_P thresh .1*/
+ con = 75.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ oth = find_counter(cpu, "OTHER_ASSISTS.SSE_TO_AVX");
+ if (pos != -1) {
+ ot = oth->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ ot = oth->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = (ot * con)/un;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+static int
+efficiency1(struct counters *cpu, int pos)
+{
+
+ int ret;
+ struct counters *uops;
+ struct counters *unhalt;
+ double un, ot, con, res;
+
+ /* 19 (UOPS_RETIRED.RETIRE_SLOTS/(4*CPU_CLK_UNHALTED.THREAD_P) look if thresh < .9*/
+ con = 4.0;
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ uops = find_counter(cpu, "UOPS_RETIRED.RETIRE_SLOTS");
+ if (pos != -1) {
+ ot = uops->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ ot = uops->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = ot/(con * un);
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+static int
+efficiency2(struct counters *cpu, int pos)
+{
+
+ int ret;
+ struct counters *uops;
+ struct counters *unhalt;
+ double un, ot, res;
+
+ /* 20 - CPU_CLK_UNHALTED.THREAD_P/INST_RETIRED.ANY_P good if > 1. (comp factor)*/
+ unhalt = find_counter(cpu, "CPU_CLK_UNHALTED.THREAD_P");
+ uops = find_counter(cpu, "INST_RETIRED.ANY_P");
+ if (pos != -1) {
+ ot = uops->vals[pos] * 1.0;
+ un = unhalt->vals[pos] * 1.0;
+ } else {
+ ot = uops->sum * 1.0;
+ un = unhalt->sum * 1.0;
+ }
+ res = un/ot;
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+#define SANDY_BRIDGE_COUNT 20
+static struct cpu_entry sandy_bridge[SANDY_BRIDGE_COUNT] = {
+/*01*/ { "allocstall1", "thresh > .05",
+ "pmcstat -s CPU_CLK_UNHALTED.THREAD_P -s PARTIAL_RAT_STALLS.SLOW_LEA_WINDOW -w 1",
+ allocstall1, 2 },
+/* -- not defined for SB right (partial-rat_stalls) 02*/
+ { "allocstall2", "thresh > .05",
+ "pmcstat -s CPU_CLK_UNHALTED.THREAD_P -s PARTIAL_RAT_STALLS.FLAGS_MERGE_UOP -w 1",
+ allocstall2, 2 },
+/*03*/ { "br_miss", "thresh >= .2",
+ "pmcstat -s CPU_CLK_UNHALTED.THREAD_P -s BR_MISP_RETIRED.ALL_BRANCHES -w 1",
+ br_mispredict, 2 },
+/*04*/ { "splitload", "thresh >= .1",
+ "pmcstat -s CPU_CLK_UNHALTED.THREAD_P -s MEM_UOP_RETIRED.SPLIT_LOADS -w 1",
+ splitload_sb, 2 },
+/* 05*/ { "splitstore", "thresh >= .01",
+ "pmcstat -s MEM_UOP_RETIRED.SPLIT_STORES -s MEM_UOP_RETIRED.ALL_STORES -w 1",
+ splitstore_sb, 2 },
+/*06*/ { "contested", "thresh >= .05",
+ "pmcstat -s MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HITM -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ contested, 2 },
+/*07*/ { "blockstorefwd", "thresh >= .05",
+ "pmcstat -s LD_BLOCKS_STORE_FORWARD -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ blockstoreforward, 2 },
+/*08*/ { "cache2", "thresh >= .2",
+ "pmcstat -s MEM_LOAD_UOPS_RETIRED.LLC_HIT -s MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HIT -s MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HITM -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ cache2, 4 },
+/*09*/ { "cache1", "thresh >= .2",
+ "pmcstat -s MEM_LOAD_UOPS_MISC_RETIRED.LLC_MISS -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ cache1, 2 },
+/*10*/ { "dtlbmissload", "thresh >= .1",
+ "pmcstat -s DTLB_LOAD_MISSES.STLB_HIT -s DTLB_LOAD_MISSES.WALK_DURATION -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ dtlb_missload, 3 },
+/*11*/ { "dtlbmissstore", "thresh >= .05",
+ "pmcstat -s DTLB_STORE_MISSES.STLB_HIT -s DTLB_STORE_MISSES.WALK_DURATION -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ dtlb_missstore, 3 },
+/*12*/ { "frontendstall", "thresh >= .15",
+ "pmcstat -s IDQ_UOPS_NOT_DELIVERED.CORE -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ frontendstall, 2 },
+/*13*/ { "clears", "thresh >= .02",
+ "pmcstat -s MACHINE_CLEARS.MEMORY_ORDERING -s MACHINE_CLEARS.SMC -s MACHINE_CLEARS.MASKMOV -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ clears, 4 },
+/*14*/ { "microassist", "thresh >= .05",
+ "pmcstat -s IDQ.MS_UOPS,cmask=1 -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ microassist, 2 },
+/*15*/ { "aliasing_4k", "thresh >= .1",
+ "pmcstat -s LD_BLOCKS_PARTIAL.ADDRESS_ALIAS -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ aliasing, 2 },
+/*16*/ { "fpassist", "look for a excessive value",
+ "pmcstat -s FP_ASSIST.ANY -s INST_RETIRED.ANY_P -w 1",
+ fpassists, 2 },
+/*17*/ { "otherassistavx", "look for a excessive value",
+ "pmcstat -s OTHER_ASSISTS.AVX_TO_SSE -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ otherassistavx, 2},
+/*18*/ { "otherassistsse", "look for a excessive value",
+ "pmcstat -s OTHER_ASSISTS.SSE_TO_AVX -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ otherassistsse, 2 },
+/*19*/ { "eff1", "thresh < .9",
+ "pmcstat -s UOPS_RETIRED.RETIRE_SLOTS -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ efficiency1, 2 },
+/*20*/ { "eff2", "thresh > 1.0",
+ "pmcstat -s INST_RETIRED.ANY_P -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ efficiency2, 2 },
+};
+
+
+#define IVY_BRIDGE_COUNT 21
+static struct cpu_entry ivy_bridge[IVY_BRIDGE_COUNT] = {
+/*1*/ { "eff1", "thresh < .75",
+ "pmcstat -s UOPS_RETIRED.RETIRE_SLOTS -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ efficiency1, 2 },
+/*2*/ { "eff2", "thresh > 1.0",
+ "pmcstat -s INST_RETIRED.ANY_P -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ efficiency2, 2 },
+/*3*/ { "itlbmiss", "thresh > .05",
+ "pmcstat -s ITLB_MISSES.WALK_DURATION -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ itlb_miss, 2 },
+/*4*/ { "icachemiss", "thresh > .05",
+ "pmcstat -s ICACHE.IFETCH_STALL -s ITLB_MISSES.WALK_DURATION -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ icache_miss, 3 },
+/*5*/ { "lcpstall", "thresh > .05",
+ "pmcstat -s ILD_STALL.LCP -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ lcp_stall, 2 },
+/*6*/ { "cache1", "thresh >= .2",
+ "pmcstat -s MEM_LOAD_UOPS_LLC_MISS_RETIRED.LOCAL_DRAM -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ cache1ib, 2 },
+/*7*/ { "cache2", "thresh >= .2",
+ "pmcstat -s MEM_LOAD_UOPS_RETIRED.LLC_HIT -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ cache2ib, 2 },
+/*8*/ { "contested", "thresh >= .05",
+ "pmcstat -s MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HITM -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ contested, 2 },
+/*9*/ { "datashare", "thresh >= .05",
+ "pmcstat -s MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HIT -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ datasharing, 2 },
+/*10*/ { "blockstorefwd", "thresh >= .05",
+ "pmcstat -s LD_BLOCKS_STORE_FORWARD -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ blockstoreforward, 2 },
+/*11*/ { "splitload", "thresh >= .1",
+ "pmcstat -s CPU_CLK_UNHALTED.THREAD_P -s L1D_PEND_MISS.PENDING -s MEM_LOAD_UOPS_RETIRED.L1_MISS -s LD_BLOCKS.NO_SR -w 1",
+ splitloadib, 4 },
+/*12*/ { "splitstore", "thresh >= .01",
+ "pmcstat -s MEM_UOPS_RETIRED.SPLIT_STORES -s MEM_UOPS_RETIRED.ALL_STORES -w 1",
+ splitstore, 2 },
+/*13*/ { "aliasing_4k", "thresh >= .1",
+ "pmcstat -s LD_BLOCKS_PARTIAL.ADDRESS_ALIAS -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ aliasing, 2 },
+/*14*/ { "dtlbmissload", "thresh >= .1",
+ "pmcstat -s DTLB_LOAD_MISSES.STLB_HIT -s DTLB_LOAD_MISSES.WALK_DURATION -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ dtlb_missload , 3},
+/*15*/ { "dtlbmissstore", "thresh >= .05",
+ "pmcstat -s DTLB_STORE_MISSES.STLB_HIT -s DTLB_STORE_MISSES.WALK_DURATION -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ dtlb_missstore, 3 },
+/*16*/ { "br_miss", "thresh >= .2",
+ "pmcstat -s CPU_CLK_UNHALTED.THREAD_P -s BR_MISP_RETIRED.ALL_BRANCHES -s MACHINE_CLEARS.MEMORY_ORDERING -s MACHINE_CLEARS.SMC -s MACHINE_CLEARS.MASKMOV -s UOPS_ISSUED.ANY -s UOPS_RETIRED.RETIRE_SLOTS -s INT_MISC.RECOVERY_CYCLES -w 1",
+ br_mispredictib, 8 },
+/*17*/ { "clears", "thresh >= .02",
+ "pmcstat -s MACHINE_CLEARS.MEMORY_ORDERING -s MACHINE_CLEARS.SMC -s MACHINE_CLEARS.MASKMOV -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ clears, 4 },
+/*18*/ { "microassist", "thresh >= .05",
+ "pmcstat -s IDQ.MS_UOPS,cmask=1 -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ microassist, 2 },
+/*19*/ { "fpassist", "look for a excessive value",
+ "pmcstat -s FP_ASSIST.ANY -s INST_RETIRED.ANY_P -w 1",
+ fpassists, 2 },
+/*20*/ { "otherassistavx", "look for a excessive value",
+ "pmcstat -s OTHER_ASSISTS.AVX_TO_SSE -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ otherassistavx , 2},
+/*21*/ { "otherassistsse", "look for a excessive value",
+ "pmcstat -s OTHER_ASSISTS.SSE_TO_AVX -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ otherassistsse, 2 },
+};
+
+#define HASWELL_COUNT 20
+static struct cpu_entry haswell[HASWELL_COUNT] = {
+/*1*/ { "eff1", "thresh < .75",
+ "pmcstat -s UOPS_RETIRED.RETIRE_SLOTS -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ efficiency1, 2 },
+/*2*/ { "eff2", "thresh > 1.0",
+ "pmcstat -s INST_RETIRED.ANY_P -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ efficiency2, 2 },
+/*3*/ { "itlbmiss", "thresh > .05",
+ "pmcstat -s ITLB_MISSES.WALK_DURATION -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ itlb_miss, 2 },
+/*4*/ { "icachemiss", "thresh > .05",
+ "pmcstat -s ICACHE.MISSES -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ icache_miss_has, 2 },
+/*5*/ { "lcpstall", "thresh > .05",
+ "pmcstat -s ILD_STALL.LCP -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ lcp_stall, 2 },
+/*6*/ { "cache1", "thresh >= .2",
+ "pmcstat -s MEM_LOAD_UOPS_LLC_MISS_RETIRED.LOCAL_DRAM -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ cache1ib, 2 },
+/*7*/ { "cache2", "thresh >= .2",
+ "pmcstat -s MEM_LOAD_UOPS_RETIRED.LLC_HIT -s MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HIT -s MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HITM -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ cache2has, 4 },
+/*8*/ { "contested", "thresh >= .05",
+ "pmcstat -s MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HITM -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ contested_has, 2 },
+/*9*/ { "datashare", "thresh >= .05",
+ "pmcstat -s MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HIT -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ datasharing_has, 2 },
+/*10*/ { "blockstorefwd", "thresh >= .05",
+ "pmcstat -s LD_BLOCKS_STORE_FORWARD -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ blockstoreforward, 2 },
+/*11*/ { "splitload", "thresh >= .1",
+ "pmcstat -s CPU_CLK_UNHALTED.THREAD_P -s MEM_UOPS_RETIRED.SPLIT_LOADS -w 1",
+ splitload , 2},
+/*12*/ { "splitstore", "thresh >= .01",
+ "pmcstat -s MEM_UOPS_RETIRED.SPLIT_STORES -s MEM_UOPS_RETIRED.ALL_STORES -w 1",
+ splitstore, 2 },
+/*13*/ { "aliasing_4k", "thresh >= .1",
+ "pmcstat -s LD_BLOCKS_PARTIAL.ADDRESS_ALIAS -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ aliasing, 2 },
+/*14*/ { "dtlbmissload", "thresh >= .1",
+ "pmcstat -s DTLB_LOAD_MISSES.STLB_HIT -s DTLB_LOAD_MISSES.WALK_DURATION -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ dtlb_missload, 3 },
+/*15*/ { "br_miss", "thresh >= .2",
+ "pmcstat -s CPU_CLK_UNHALTED.THREAD_P -s BR_MISP_RETIRED.ALL_BRANCHES -w 1",
+ br_mispredict, 2 },
+/*16*/ { "clears", "thresh >= .02",
+ "pmcstat -s MACHINE_CLEARS.MEMORY_ORDERING -s MACHINE_CLEARS.SMC -s MACHINE_CLEARS.MASKMOV -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ clears, 4 },
+/*17*/ { "microassist", "thresh >= .05",
+ "pmcstat -s IDQ.MS_UOPS,cmask=1 -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ microassist, 2 },
+/*18*/ { "fpassist", "look for a excessive value",
+ "pmcstat -s FP_ASSIST.ANY -s INST_RETIRED.ANY_P -w 1",
+ fpassists, 2 },
+/*19*/ { "otherassistavx", "look for a excessive value",
+ "pmcstat -s OTHER_ASSISTS.AVX_TO_SSE -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ otherassistavx, 2 },
+/*20*/ { "otherassistsse", "look for a excessive value",
+ "pmcstat -s OTHER_ASSISTS.SSE_TO_AVX -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ otherassistsse, 2 },
+};
+
+
+static void
+explain_name_broad(const char *name)
+{
+ const char *mythresh;
+ if (strcmp(name, "eff1") == 0) {
+ printf("Examine (UOPS_RETIRED.RETIRE_SLOTS)/(4 *CPU_CLK_UNHALTED.THREAD_P)\n");
+ mythresh = "thresh < .75";
+ } else if (strcmp(name, "eff2") == 0) {
+ printf("Examine CPU_CLK_UNHALTED.THREAD_P/INST_RETIRED.ANY_P\n");
+ mythresh = "thresh > 1.0";
+ } else if (strcmp(name, "itlbmiss") == 0) {
+ printf("Examine (7 * ITLB_MISSES_STLB_HIT_4K + ITLB_MISSES.WALK_DURATION)/ CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh > .05";
+ } else if (strcmp(name, "icachemiss") == 0) {
+ printf("Examine ( 36.0 * ICACHE.MISSES)/ CPU_CLK_UNHALTED.THREAD_P ??? may not be right \n");
+ mythresh = "thresh > .05";
+ } else if (strcmp(name, "lcpstall") == 0) {
+ printf("Examine ILD_STALL.LCP/CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh > .05";
+ } else if (strcmp(name, "cache1") == 0) {
+ printf("Examine (MEM_LOAD_UOPS_LLC_MISS_RETIRED.LOCAL_DRAM * 180) / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .1";
+ } else if (strcmp(name, "cache2") == 0) {
+ printf("Examine (36.0 * MEM_LOAD_UOPS_RETIRED.L3_HIT / CPU_CLK_UNHALTED.THREAD_P)\n");
+ mythresh = "thresh >= .2";
+ } else if (strcmp(name, "contested") == 0) {
+ printf("Examine ((MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HITM * 84) + MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_MISS)/ CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .05";
+ } else if (strcmp(name, "datashare") == 0) {
+ printf("Examine (MEM_LOAD_UOPS_L3_HIT_RETIRED.XSNP_HIT * 72)/CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh > .05";
+ } else if (strcmp(name, "blockstorefwd") == 0) {
+ printf("Examine (LD_BLOCKS_STORE_FORWARD * 13) / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .05";
+ } else if (strcmp(name, "aliasing_4k") == 0) {
+ printf("Examine (LD_BLOCKS_PARTIAL.ADDRESS_ALIAS * 7) / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .1";
+ } else if (strcmp(name, "dtlbmissload") == 0) {
+ printf("Examine (((DTLB_LOAD_MISSES.STLB_HIT * 7) + DTLB_LOAD_MISSES.WALK_DURATION)\n");
+ printf(" / CPU_CLK_UNHALTED.THREAD_P)\n");
+ mythresh = "thresh >= .1";
+
+ } else if (strcmp(name, "br_miss") == 0) {
+ printf("Examine BR_MISP_RETIRED.ALL_BRANCHS_PS / (BR_MISP_RETIED.ALL_BRANCHES_PS + MACHINE_CLEARS.COUNT) *\n");
+ printf(" (UOPS_ISSUEDF.ANY - UOPS_RETIRED.RETIRE_SLOTS + 4 * INT_MISC.RECOVERY_CYCLES) /\n");
+ printf("CPU_CLK_UNHALTED.THREAD * 4)\n");
+ mythresh = "thresh >= .2";
+ } else if (strcmp(name, "clears") == 0) {
+ printf("Examine ((MACHINE_CLEARS.MEMORY_ORDERING + \n");
+ printf(" MACHINE_CLEARS.SMC + \n");
+ printf(" MACHINE_CLEARS.MASKMOV ) * 100 ) / CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "thresh >= .02";
+ } else if (strcmp(name, "fpassist") == 0) {
+ printf("Examine FP_ASSIST.ANY/INST_RETIRED.ANY_P\n");
+ mythresh = "look for a excessive value";
+ } else if (strcmp(name, "otherassistavx") == 0) {
+ printf("Examine (OTHER_ASSISTS.AVX_TO_SSE * 75)/CPU_CLK_UNHALTED.THREAD_P\n");
+ mythresh = "look for a excessive value";
+ } else if (strcmp(name, "microassist") == 0) {
+ printf("Examine (UOPS_RETIRED.RETIRE_SLOTS/UOPS_ISSUED.ANY) * (IDQ.MS_CYCLES / (4 * CPU_CLK_UNHALTED.THREAD_P)\n");
+ printf("***We use IDQ.MS_UOPS,cmask=1 to get cycles\n");
+ mythresh = "thresh >= .05";
+ } else {
+ printf("Unknown name:%s\n", name);
+ mythresh = "unknown entry";
+ }
+ printf("If the value printed is %s we may have the ability to improve performance\n", mythresh);
+}
+
+
+#define BROADWELL_COUNT 17
+static struct cpu_entry broadwell[BROADWELL_COUNT] = {
+/*1*/ { "eff1", "thresh < .75",
+ "pmcstat -s UOPS_RETIRED.RETIRE_SLOTS -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ efficiency1, 2 },
+/*2*/ { "eff2", "thresh > 1.0",
+ "pmcstat -s INST_RETIRED.ANY_P -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ efficiency2, 2 },
+/*3*/ { "itlbmiss", "thresh > .05",
+ "pmcstat -s ITLB_MISSES.WALK_DURATION -s CPU_CLK_UNHALTED.THREAD_P -s ITLB_MISSES.STLB_HIT_4K -w 1",
+ itlb_miss_broad, 3 },
+/*4*/ { "icachemiss", "thresh > .05",
+ "pmcstat -s ICACHE.MISSES -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ icache_miss_has, 2 },
+/*5*/ { "lcpstall", "thresh > .05",
+ "pmcstat -s ILD_STALL.LCP -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ lcp_stall, 2 },
+/*6*/ { "cache1", "thresh >= .1",
+ "pmcstat -s MEM_LOAD_UOPS_RETIRED.L3_MISS -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ cache1broad, 2 },
+/*7*/ { "cache2", "thresh >= .2",
+ "pmcstat -s MEM_LOAD_UOPS_RETIRED.L3_HIT -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ cache2broad, 2 },
+/*8*/ { "contested", "thresh >= .05",
+ "pmcstat -s MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HITM -s CPU_CLK_UNHALTED.THREAD_P -s MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_MISS -w 1",
+ contestedbroad, 2 },
+/*9*/ { "datashare", "thresh >= .05",
+ "pmcstat -s MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HIT -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ datasharing_has, 2 },
+/*10*/ { "blockstorefwd", "thresh >= .05",
+ "pmcstat -s LD_BLOCKS_STORE_FORWARD -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ blockstoreforward, 2 },
+/*11*/ { "aliasing_4k", "thresh >= .1",
+ "pmcstat -s LD_BLOCKS_PARTIAL.ADDRESS_ALIAS -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ aliasing_broad, 2 },
+/*12*/ { "dtlbmissload", "thresh >= .1",
+ "pmcstat -s DTLB_LOAD_MISSES.STLB_HIT_4K -s DTLB_LOAD_MISSES.WALK_DURATION -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ dtlb_missload, 3 },
+/*13*/ { "br_miss", "thresh >= .2",
+ "pmcstat -s CPU_CLK_UNHALTED.THREAD_P -s BR_MISP_RETIRED.ALL_BRANCHES -s MACHINE_CLEARS.CYCLES -s UOPS_ISSUED.ANY -s UOPS_RETIRED.RETIRE_SLOTS -s INT_MISC.RECOVERY_CYCLES -w 1",
+ br_mispredict_broad, 7 },
+/*14*/ { "clears", "thresh >= .02",
+ "pmcstat -s MACHINE_CLEARS.CYCLES -s MACHINE_CLEARS.MEMORY_ORDERING -s MACHINE_CLEARS.SMC -s MACHINE_CLEARS.MASKMOV -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ clears_broad, 5 },
+/*15*/ { "fpassist", "look for a excessive value",
+ "pmcstat -s FP_ASSIST.ANY -s INST_RETIRED.ANY_P -w 1",
+ fpassists, 2 },
+/*16*/ { "otherassistavx", "look for a excessive value",
+ "pmcstat -s OTHER_ASSISTS.AVX_TO_SSE -s CPU_CLK_UNHALTED.THREAD_P -w 1",
+ otherassistavx, 2 },
+/*17*/ { "microassist", "thresh >= .2",
+ "pmcstat -s IDQ.MS_UOPS,cmask=1 -s CPU_CLK_UNHALTED.THREAD_P -s UOPS_ISSUED.ANY -s UOPS_RETIRED.RETIRE_SLOTS -w 1",
+ microassist_broad, 4 },
+};
+
+
+static void
+set_sandybridge(void)
+{
+ strcpy(the_cpu.cputype, "SandyBridge PMC");
+ the_cpu.number = SANDY_BRIDGE_COUNT;
+ the_cpu.ents = sandy_bridge;
+ the_cpu.explain = explain_name_sb;
+}
+
+static void
+set_ivybridge(void)
+{
+ strcpy(the_cpu.cputype, "IvyBridge PMC");
+ the_cpu.number = IVY_BRIDGE_COUNT;
+ the_cpu.ents = ivy_bridge;
+ the_cpu.explain = explain_name_ib;
+}
+
+
+static void
+set_haswell(void)
+{
+ strcpy(the_cpu.cputype, "HASWELL PMC");
+ the_cpu.number = HASWELL_COUNT;
+ the_cpu.ents = haswell;
+ the_cpu.explain = explain_name_has;
+}
+
+
+static void
+set_broadwell(void)
+{
+ strcpy(the_cpu.cputype, "HASWELL PMC");
+ the_cpu.number = BROADWELL_COUNT;
+ the_cpu.ents = broadwell;
+ the_cpu.explain = explain_name_broad;
+}
+
+
+static int
+set_expression(const char *name)
+{
+ int found = 0, i;
+ for(i=0 ; i< the_cpu.number; i++) {
+ if (strcmp(name, the_cpu.ents[i].name) == 0) {
+ found = 1;
+ expression = the_cpu.ents[i].func;
+ command = the_cpu.ents[i].command;
+ threshold = the_cpu.ents[i].thresh;
+ if (the_cpu.ents[i].counters_required > max_pmc_counters) {
+ printf("Test %s requires that the CPU have %d counters and this CPU has only %d\n",
+ the_cpu.ents[i].name,
+ the_cpu.ents[i].counters_required, max_pmc_counters);
+ printf("Sorry this test can not be run\n");
+ if (run_all == 0) {
+ exit(-1);
+ } else {
+ return(-1);
+ }
+ }
+ break;
+ }
+ }
+ if (!found) {
+ printf("For CPU type %s we have no expression:%s\n",
+ the_cpu.cputype, name);
+ exit(-1);
+ }
+ return(0);
+}
+
+
+
+
+
+static int
+validate_expression(char *name)
+{
+ int i, found;
+
+ found = 0;
+ for(i=0 ; i< the_cpu.number; i++) {
+ if (strcmp(name, the_cpu.ents[i].name) == 0) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ return(-1);
+ }
+ return (0);
+}
+
+static void
+do_expression(struct counters *cpu, int pos)
+{
+ if (expression == NULL)
+ return;
+ (*expression)(cpu, pos);
+}
+
+static void
+process_header(int idx, char *p)
+{
+ struct counters *up;
+ int i, len, nlen;
+ /*
+ * Given header element idx, at p in
+ * form 's/NN/nameof'
+ * process the entry to pull out the name and
+ * the CPU number.
+ */
+ if (strncmp(p, "s/", 2)) {
+ printf("Check -- invalid header no s/ in %s\n",
+ p);
+ return;
+ }
+ up = &cnts[idx];
+ up->cpu = strtol(&p[2], NULL, 10);
+ len = strlen(p);
+ for (i=2; i<len; i++) {
+ if (p[i] == '/') {
+ nlen = strlen(&p[(i+1)]);
+ if (nlen < (MAX_NLEN-1)) {
+ strcpy(up->counter_name, &p[(i+1)]);
+ } else {
+ strncpy(up->counter_name, &p[(i+1)], (MAX_NLEN-1));
+ }
+ }
+ }
+}
+
+static void
+build_counters_from_header(FILE *io)
+{
+ char buffer[8192], *p;
+ int i, len, cnt;
+ size_t mlen;
+
+ /* We have a new start, lets
+ * setup our headers and cpus.
+ */
+ if (fgets(buffer, sizeof(buffer), io) == NULL) {
+ printf("First line can't be read from file err:%d\n", errno);
+ return;
+ }
+ /*
+ * Ok output is an array of counters. Once
+ * we start to read the values in we must
+ * put them in there slot to match there CPU and
+ * counter being updated. We create a mass array
+ * of the counters, filling in the CPU and
+ * counter name.
+ */
+ /* How many do we get? */
+ len = strlen(buffer);
+ for (i=0, cnt=0; i<len; i++) {
+ if (strncmp(&buffer[i], "s/", 2) == 0) {
+ cnt++;
+ for(;i<len;i++) {
+ if (buffer[i] == ' ')
+ break;
+ }
+ }
+ }
+ mlen = sizeof(struct counters) * cnt;
+ cnts = malloc(mlen);
+ ncnts = cnt;
+ if (cnts == NULL) {
+ printf("No memory err:%d\n", errno);
+ return;
+ }
+ memset(cnts, 0, mlen);
+ for (i=0, cnt=0; i<len; i++) {
+ if (strncmp(&buffer[i], "s/", 2) == 0) {
+ p = &buffer[i];
+ for(;i<len;i++) {
+ if (buffer[i] == ' ') {
+ buffer[i] = 0;
+ break;
+ }
+ }
+ process_header(cnt, p);
+ cnt++;
+ }
+ }
+ if (verbose)
+ printf("We have %d entries\n", cnt);
+}
+extern int max_to_collect;
+int max_to_collect = MAX_COUNTER_SLOTS;
+
+static int
+read_a_line(FILE *io)
+{
+ char buffer[8192], *p, *stop;
+ int pos, i;
+
+ if (fgets(buffer, sizeof(buffer), io) == NULL) {
+ return(0);
+ }
+ p = buffer;
+ for (i=0; i<ncnts; i++) {
+ pos = cnts[i].pos;
+ cnts[i].vals[pos] = strtol(p, &stop, 0);
+ cnts[i].pos++;
+ cnts[i].sum += cnts[i].vals[pos];
+ p = stop;
+ }
+ return (1);
+}
+
+extern int cpu_count_out;
+int cpu_count_out=0;
+
+static void
+print_header(void)
+{
+ int i, cnt, printed_cnt;
+
+ printf("*********************************\n");
+ for(i=0, cnt=0; i<MAX_CPU; i++) {
+ if (glob_cpu[i]) {
+ cnt++;
+ }
+ }
+ cpu_count_out = cnt;
+ for(i=0, printed_cnt=0; i<MAX_CPU; i++) {
+ if (glob_cpu[i]) {
+ printf("CPU%d", i);
+ printed_cnt++;
+ }
+ if (printed_cnt == cnt) {
+ printf("\n");
+ break;
+ } else {
+ printf("\t");
+ }
+ }
+}
+
+static void
+lace_cpus_together(void)
+{
+ int i, j, lace_cpu;
+ struct counters *cpat, *at;
+
+ for(i=0; i<ncnts; i++) {
+ cpat = &cnts[i];
+ if (cpat->next_cpu) {
+ /* Already laced in */
+ continue;
+ }
+ lace_cpu = cpat->cpu;
+ if (lace_cpu >= MAX_CPU) {
+ printf("CPU %d to big\n", lace_cpu);
+ continue;
+ }
+ if (glob_cpu[lace_cpu] == NULL) {
+ glob_cpu[lace_cpu] = cpat;
+ } else {
+ /* Already processed this cpu */
+ continue;
+ }
+ /* Ok look forward for cpu->cpu and link in */
+ for(j=(i+1); j<ncnts; j++) {
+ at = &cnts[j];
+ if (at->next_cpu) {
+ continue;
+ }
+ if (at->cpu == lace_cpu) {
+ /* Found one */
+ cpat->next_cpu = at;
+ cpat = at;
+ }
+ }
+ }
+}
+
+
+static void
+process_file(char *filename)
+{
+ FILE *io;
+ int i;
+ int line_at, not_done;
+ pid_t pid_of_command=0;
+
+ if (filename == NULL) {
+ io = my_popen(command, "r", &pid_of_command);
+ } else {
+ io = fopen(filename, "r");
+ if (io == NULL) {
+ printf("Can't process file %s err:%d\n",
+ filename, errno);
+ return;
+ }
+ }
+ build_counters_from_header(io);
+ if (cnts == NULL) {
+ /* Nothing we can do */
+ printf("Nothing to do -- no counters built\n");
+ if (io) {
+ fclose(io);
+ }
+ return;
+ }
+ lace_cpus_together();
+ print_header();
+ if (verbose) {
+ for (i=0; i<ncnts; i++) {
+ printf("Counter:%s cpu:%d index:%d\n",
+ cnts[i].counter_name,
+ cnts[i].cpu, i);
+ }
+ }
+ line_at = 0;
+ not_done = 1;
+ while(not_done) {
+ if (read_a_line(io)) {
+ line_at++;
+ } else {
+ break;
+ }
+ if (line_at >= max_to_collect) {
+ not_done = 0;
+ }
+ if (filename == NULL) {
+ int cnt;
+ /* For the ones we dynamically open we print now */
+ for(i=0, cnt=0; i<MAX_CPU; i++) {
+ do_expression(glob_cpu[i], (line_at-1));
+ cnt++;
+ if (cnt == cpu_count_out) {
+ printf("\n");
+ break;
+ } else {
+ printf("\t");
+ }
+ }
+ }
+ }
+ if (filename) {
+ fclose(io);
+ } else {
+ my_pclose(io, pid_of_command);
+ }
+}
+#if defined(__amd64__)
+#define cpuid(in,a,b,c,d)\
+ asm("cpuid": "=a" (a), "=b" (b), "=c" (c), "=d" (d) : "a" (in));
+
+static __inline void
+do_cpuid(u_int ax, u_int cx, u_int *p)
+{
+ __asm __volatile("cpuid"
+ : "=a" (p[0]), "=b" (p[1]), "=c" (p[2]), "=d" (p[3])
+ : "0" (ax), "c" (cx) );
+}
+
+#else
+#define cpuid(in, a, b, c, d)
+static __inline void
+do_cpuid(u_int ax, u_int cx, u_int *p)
+{
+}
+
+#endif
+
+static void
+get_cpuid_set(void)
+{
+ unsigned long eax, ebx, ecx, edx;
+ int model;
+ pid_t pid_of_command=0;
+ size_t sz, len;
+ FILE *io;
+ char linebuf[1024], *str;
+ u_int reg[4];
+
+ eax = ebx = ecx = edx = 0;
+
+ cpuid(0, eax, ebx, ecx, edx);
+ if (ebx == 0x68747541) {
+ printf("AMD processors are not supported by this program\n");
+ printf("Sorry\n");
+ exit(0);
+ } else if (ebx == 0x6972794) {
+ printf("Cyrix processors are not supported by this program\n");
+ printf("Sorry\n");
+ exit(0);
+ } else if (ebx == 0x756e6547) {
+ printf("Genuine Intel\n");
+ } else {
+ printf("Unknown processor type 0x%lx Only Intel AMD64 types are supported by this routine!\n", ebx);
+ exit(0);
+ }
+ cpuid(1, eax, ebx, ecx, edx);
+ model = (((eax & 0xF0000) >> 12) | ((eax & 0xF0) >> 4));
+ printf("CPU model is 0x%x id:0x%lx\n", model, eax);
+ switch (eax & 0xF00) {
+ case 0x500: /* Pentium family processors */
+ printf("Intel Pentium P5\n");
+ goto not_supported;
+ break;
+ case 0x600: /* Pentium Pro, Celeron, Pentium II & III */
+ switch (model) {
+ case 0x1:
+ printf("Intel Pentium P6\n");
+ goto not_supported;
+ break;
+ case 0x3:
+ case 0x5:
+ printf("Intel PII\n");
+ goto not_supported;
+ break;
+ case 0x6: case 0x16:
+ printf("Intel CL\n");
+ goto not_supported;
+ break;
+ case 0x7: case 0x8: case 0xA: case 0xB:
+ printf("Intel PIII\n");
+ goto not_supported;
+ break;
+ case 0x9: case 0xD:
+ printf("Intel PM\n");
+ goto not_supported;
+ break;
+ case 0xE:
+ printf("Intel CORE\n");
+ goto not_supported;
+ break;
+ case 0xF:
+ printf("Intel CORE2\n");
+ goto not_supported;
+ break;
+ case 0x17:
+ printf("Intel CORE2EXTREME\n");
+ goto not_supported;
+ break;
+ case 0x1C: /* Per Intel document 320047-002. */
+ printf("Intel ATOM\n");
+ goto not_supported;
+ break;
+ case 0x1A:
+ case 0x1E: /*
+ * Per Intel document 253669-032 9/2009,
+ * pages A-2 and A-57
+ */
+ case 0x1F: /*
+ * Per Intel document 253669-032 9/2009,
+ * pages A-2 and A-57
+ */
+ printf("Intel COREI7\n");
+ goto not_supported;
+ break;
+ case 0x2E:
+ printf("Intel NEHALEM\n");
+ goto not_supported;
+ break;
+ case 0x25: /* Per Intel document 253669-033US 12/2009. */
+ case 0x2C: /* Per Intel document 253669-033US 12/2009. */
+ printf("Intel WESTMERE\n");
+ goto not_supported;
+ break;
+ case 0x2F: /* Westmere-EX, seen in wild */
+ printf("Intel WESTMERE\n");
+ goto not_supported;
+ break;
+ case 0x2A: /* Per Intel document 253669-039US 05/2011. */
+ printf("Intel SANDYBRIDGE\n");
+ set_sandybridge();
+ break;
+ case 0x2D: /* Per Intel document 253669-044US 08/2012. */
+ printf("Intel SANDYBRIDGE_XEON\n");
+ set_sandybridge();
+ break;
+ case 0x3A: /* Per Intel document 253669-043US 05/2012. */
+ printf("Intel IVYBRIDGE\n");
+ set_ivybridge();
+ break;
+ case 0x3E: /* Per Intel document 325462-045US 01/2013. */
+ printf("Intel IVYBRIDGE_XEON\n");
+ set_ivybridge();
+ break;
+ case 0x3F: /* Per Intel document 325462-045US 09/2014. */
+ printf("Intel HASWELL (Xeon)\n");
+ set_haswell();
+ break;
+ case 0x3C: /* Per Intel document 325462-045US 01/2013. */
+ case 0x45:
+ case 0x46:
+ printf("Intel HASWELL\n");
+ set_haswell();
+ break;
+
+ case 0x4e:
+ case 0x5e:
+ printf("Intel SKY-LAKE\n");
+ goto not_supported;
+ break;
+ case 0x3D:
+ case 0x47:
+ printf("Intel BROADWELL\n");
+ set_broadwell();
+ break;
+ case 0x4f:
+ case 0x56:
+ printf("Intel BROADWEL (Xeon)\n");
+ set_broadwell();
+ break;
+
+ case 0x4D:
+ /* Per Intel document 330061-001 01/2014. */
+ printf("Intel ATOM_SILVERMONT\n");
+ goto not_supported;
+ break;
+ default:
+ printf("Intel model 0x%x is not known -- sorry\n",
+ model);
+ goto not_supported;
+ break;
+ }
+ break;
+ case 0xF00: /* P4 */
+ printf("Intel unknown model %d\n", model);
+ goto not_supported;
+ break;
+ }
+ do_cpuid(0xa, 0, reg);
+ max_pmc_counters = (reg[3] & 0x0000000f) + 1;
+ printf("We have %d PMC counters to work with\n", max_pmc_counters);
+ /* Ok lets load the list of all known PMC's */
+ io = my_popen("/usr/sbin/pmccontrol -L", "r", &pid_of_command);
+ if (valid_pmcs == NULL) {
+ /* Likely */
+ pmc_allocated_cnt = PMC_INITIAL_ALLOC;
+ sz = sizeof(char *) * pmc_allocated_cnt;
+ valid_pmcs = malloc(sz);
+ if (valid_pmcs == NULL) {
+ printf("No memory allocation fails at startup?\n");
+ exit(-1);
+ }
+ memset(valid_pmcs, 0, sz);
+ }
+
+ while (fgets(linebuf, sizeof(linebuf), io) != NULL) {
+ if (linebuf[0] != '\t') {
+ /* sometimes headers ;-) */
+ continue;
+ }
+ len = strlen(linebuf);
+ if (linebuf[(len-1)] == '\n') {
+ /* Likely */
+ linebuf[(len-1)] = 0;
+ }
+ str = &linebuf[1];
+ len = strlen(str) + 1;
+ valid_pmcs[valid_pmc_cnt] = malloc(len);
+ if (valid_pmcs[valid_pmc_cnt] == NULL) {
+ printf("No memory2 allocation fails at startup?\n");
+ exit(-1);
+ }
+ memset(valid_pmcs[valid_pmc_cnt], 0, len);
+ strcpy(valid_pmcs[valid_pmc_cnt], str);
+ valid_pmc_cnt++;
+ if (valid_pmc_cnt >= pmc_allocated_cnt) {
+ /* Got to expand -- unlikely */
+ char **more;
+
+ sz = sizeof(char *) * (pmc_allocated_cnt * 2);
+ more = malloc(sz);
+ if (more == NULL) {
+ printf("No memory3 allocation fails at startup?\n");
+ exit(-1);
+ }
+ memset(more, 0, sz);
+ memcpy(more, valid_pmcs, sz);
+ pmc_allocated_cnt *= 2;
+ free(valid_pmcs);
+ valid_pmcs = more;
+ }
+ }
+ my_pclose(io, pid_of_command);
+ return;
+not_supported:
+ printf("Not supported\n");
+ exit(-1);
+}
+
+static void
+explain_all(void)
+{
+ int i;
+ printf("For CPU's of type %s the following expressions are available:\n",the_cpu.cputype);
+ printf("-------------------------------------------------------------\n");
+ for(i=0; i<the_cpu.number; i++){
+ printf("For -e %s ", the_cpu.ents[i].name);
+ (*the_cpu.explain)(the_cpu.ents[i].name);
+ printf("----------------------------\n");
+ }
+}
+
+static void
+test_for_a_pmc(const char *pmc, int out_so_far)
+{
+ FILE *io;
+ pid_t pid_of_command=0;
+ char my_command[1024];
+ char line[1024];
+ char resp[1024];
+ int len, llen, i;
+
+ if (out_so_far < 50) {
+ len = 50 - out_so_far;
+ for(i=0; i<len; i++) {
+ printf(" ");
+ }
+ }
+ sprintf(my_command, "/usr/sbin/pmcstat -w .25 -c 0 -s %s", pmc);
+ io = my_popen(my_command, "r", &pid_of_command);
+ if (io == NULL) {
+ printf("Failed -- popen fails\n");
+ return;
+ }
+ /* Setup what we expect */
+ len = sprintf(resp, "%s", pmc);
+ if (fgets(line, sizeof(line), io) == NULL) {
+ printf("Failed -- no output from pmstat\n");
+ goto out;
+ }
+ llen = strlen(line);
+ if (line[(llen-1)] == '\n') {
+ line[(llen-1)] = 0;
+ llen--;
+ }
+ for(i=2; i<(llen-len); i++) {
+ if (strncmp(&line[i], "ERROR", 5) == 0) {
+ printf("Failed %s\n", line);
+ goto out;
+ } else if (strncmp(&line[i], resp, len) == 0) {
+ int j, k;
+
+ if (fgets(line, sizeof(line), io) == NULL) {
+ printf("Failed -- no second output from pmstat\n");
+ goto out;
+ }
+ len = strlen(line);
+ for (j=0; j<len; j++) {
+ if (line[j] == ' ') {
+ j++;
+ } else {
+ break;
+ }
+ }
+ printf("Pass");
+ len = strlen(&line[j]);
+ if (len < 20) {
+ for(k=0; k<(20-len); k++) {
+ printf(" ");
+ }
+ }
+ if (len) {
+ printf("%s", &line[j]);
+ } else {
+ printf("\n");
+ }
+ goto out;
+ }
+ }
+ printf("Failed -- '%s' not '%s'\n", line, resp);
+out:
+ my_pclose(io, pid_of_command);
+
+}
+
+static int
+add_it_to(char **vars, int cur_cnt, char *name)
+{
+ int i;
+ size_t len;
+ for(i=0; i<cur_cnt; i++) {
+ if (strcmp(vars[i], name) == 0) {
+ /* Already have */
+ return(0);
+ }
+ }
+ if (vars[cur_cnt] != NULL) {
+ printf("Cur_cnt:%d filled with %s??\n",
+ cur_cnt, vars[cur_cnt]);
+ exit(-1);
+ }
+ /* Ok its new */
+ len = strlen(name) + 1;
+ vars[cur_cnt] = malloc(len);
+ if (vars[cur_cnt] == NULL) {
+ printf("No memory %s\n", __FUNCTION__);
+ exit(-1);
+ }
+ memset(vars[cur_cnt], 0, len);
+ strcpy(vars[cur_cnt], name);
+ return(1);
+}
+
+static char *
+build_command_for_exp(struct expression *exp)
+{
+ /*
+ * Build the pmcstat command to handle
+ * the passed in expression.
+ * /usr/sbin/pmcstat -w 1 -s NNN -s QQQ
+ * where NNN and QQQ represent the PMC's in the expression
+ * uniquely..
+ */
+ char forming[1024];
+ int cnt_pmc, alloced_pmcs, i;
+ struct expression *at;
+ char **vars, *cmd;
+ size_t mal;
+
+ alloced_pmcs = cnt_pmc = 0;
+ /* first how many do we have */
+ at = exp;
+ while (at) {
+ if (at->type == TYPE_VALUE_PMC) {
+ cnt_pmc++;
+ }
+ at = at->next;
+ }
+ if (cnt_pmc == 0) {
+ printf("No PMC's in your expression -- nothing to do!!\n");
+ exit(0);
+ }
+ mal = cnt_pmc * sizeof(char *);
+ vars = malloc(mal);
+ if (vars == NULL) {
+ printf("No memory\n");
+ exit(-1);
+ }
+ memset(vars, 0, mal);
+ at = exp;
+ while (at) {
+ if (at->type == TYPE_VALUE_PMC) {
+ if(add_it_to(vars, alloced_pmcs, at->name)) {
+ alloced_pmcs++;
+ }
+ }
+ at = at->next;
+ }
+ /* Now we have a unique list in vars so create our command */
+ mal = 23; /* "/usr/sbin/pmcstat -w 1" + \0 */
+ for(i=0; i<alloced_pmcs; i++) {
+ mal += strlen(vars[i]) + 4; /* var + " -s " */
+ }
+ cmd = malloc((mal+2));
+ if (cmd == NULL) {
+ printf("%s out of mem\n", __FUNCTION__);
+ exit(-1);
+ }
+ memset(cmd, 0, (mal+2));
+ strcpy(cmd, "/usr/sbin/pmcstat -w 1");
+ at = exp;
+ for(i=0; i<alloced_pmcs; i++) {
+ sprintf(forming, " -s %s", vars[i]);
+ strcat(cmd, forming);
+ free(vars[i]);
+ vars[i] = NULL;
+ }
+ free(vars);
+ return(cmd);
+}
+
+static int
+user_expr(struct counters *cpu, int pos)
+{
+ int ret;
+ double res;
+ struct counters *var;
+ struct expression *at;
+
+ at = master_exp;
+ while (at) {
+ if (at->type == TYPE_VALUE_PMC) {
+ var = find_counter(cpu, at->name);
+ if (var == NULL) {
+ printf("%s:Can't find counter %s?\n", __FUNCTION__, at->name);
+ exit(-1);
+ }
+ if (pos != -1) {
+ at->value = var->vals[pos] * 1.0;
+ } else {
+ at->value = var->sum * 1.0;
+ }
+ }
+ at = at->next;
+ }
+ res = run_expr(master_exp, 1, NULL);
+ ret = printf("%1.3f", res);
+ return(ret);
+}
+
+
+static void
+set_manual_exp(struct expression *exp)
+{
+ expression = user_expr;
+ command = build_command_for_exp(exp);
+ threshold = "User defined threshold";
+}
+
+static void
+run_tests(void)
+{
+ int i, lenout;
+ printf("Running tests on %d PMC's this may take some time\n", valid_pmc_cnt);
+ printf("------------------------------------------------------------------------\n");
+ for(i=0; i<valid_pmc_cnt; i++) {
+ lenout = printf("%s", valid_pmcs[i]);
+ fflush(stdout);
+ test_for_a_pmc(valid_pmcs[i], lenout);
+ }
+}
+static void
+list_all(void)
+{
+ int i, cnt, j;
+ printf("PMC Abbreviation\n");
+ printf("--------------------------------------------------------------\n");
+ for(i=0; i<valid_pmc_cnt; i++) {
+ cnt = printf("%s", valid_pmcs[i]);
+ for(j=cnt; j<52; j++) {
+ printf(" ");
+ }
+ printf("%%%d\n", i);
+ }
+}
+
+
+int
+main(int argc, char **argv)
+{
+ int i, j, cnt;
+ char *filename=NULL;
+ const char *name=NULL;
+ int help_only = 0;
+ int test_mode = 0;
+ int test_at = 0;
+
+ get_cpuid_set();
+ memset(glob_cpu, 0, sizeof(glob_cpu));
+ while ((i = getopt(argc, argv, "ALHhvm:i:?e:TE:")) != -1) {
+ switch (i) {
+ case 'A':
+ run_all = 1;
+ break;
+ case 'L':
+ list_all();
+ return(0);
+ case 'H':
+ printf("**********************************\n");
+ explain_all();
+ printf("**********************************\n");
+ return(0);
+ break;
+ case 'T':
+ test_mode = 1;
+ break;
+ case 'E':
+ master_exp = parse_expression(optarg);
+ if (master_exp) {
+ set_manual_exp(master_exp);
+ }
+ break;
+ case 'e':
+ if (validate_expression(optarg)) {
+ printf("Unknown expression %s\n", optarg);
+ return(0);
+ }
+ name = optarg;
+ set_expression(optarg);
+ break;
+ case 'm':
+ max_to_collect = strtol(optarg, NULL, 0);
+ if (max_to_collect > MAX_COUNTER_SLOTS) {
+ /* You can't collect more than max in array */
+ max_to_collect = MAX_COUNTER_SLOTS;
+ }
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'h':
+ help_only = 1;
+ break;
+ case 'i':
+ filename = optarg;
+ break;
+ case '?':
+ default:
+ use:
+ printf("Use %s [ -i inputfile -v -m max_to_collect -e expr -E -h -? -H]\n",
+ argv[0]);
+ printf("-i inputfile -- use source as inputfile not stdin (if stdin collect)\n");
+ printf("-v -- verbose dump debug type things -- you don't want this\n");
+ printf("-m N -- maximum to collect is N measurments\n");
+ printf("-e expr-name -- Do expression expr-name\n");
+ printf("-E 'your expression' -- Do your expression\n");
+ printf("-h -- Don't do the expression I put in -e xxx just explain what it does and exit\n");
+ printf("-H -- Don't run anything, just explain all canned expressions\n");
+ printf("-T -- Test all PMC's defined by this processor\n");
+ printf("-A -- Run all canned tests\n");
+ return(0);
+ break;
+ };
+ }
+ if ((run_all == 0) && (name == NULL) && (filename == NULL) &&
+ (test_mode == 0) && (master_exp == NULL)) {
+ printf("Without setting an expression we cannot dynamically gather information\n");
+ printf("you must supply a filename (and you probably want verbosity)\n");
+ goto use;
+ }
+ if (run_all && max_to_collect > 10) {
+ max_to_collect = 3;
+ }
+ if (test_mode) {
+ run_tests();
+ return(0);
+ }
+ printf("*********************************\n");
+ if ((master_exp == NULL) && name) {
+ (*the_cpu.explain)(name);
+ } else if (master_exp) {
+ printf("Examine your expression ");
+ print_exp(master_exp);
+ printf("User defined threshold\n");
+ }
+ if (help_only) {
+ return(0);
+ }
+ if (run_all) {
+ more:
+ name = the_cpu.ents[test_at].name;
+ printf("***Test %s (threshold %s)****\n", name, the_cpu.ents[test_at].thresh);
+ test_at++;
+ if (set_expression(name) == -1) {
+ if (test_at >= the_cpu.number) {
+ goto done;
+ } else
+ goto more;
+ }
+
+ }
+ process_file(filename);
+ if (verbose >= 2) {
+ for (i=0; i<ncnts; i++) {
+ printf("Counter:%s cpu:%d index:%d\n",
+ cnts[i].counter_name,
+ cnts[i].cpu, i);
+ for(j=0; j<cnts[i].pos; j++) {
+ printf(" val - %ld\n", (long int)cnts[i].vals[j]);
+ }
+ printf(" sum - %ld\n", (long int)cnts[i].sum);
+ }
+ }
+ if (expression == NULL) {
+ return(0);
+ }
+ if (max_to_collect > 1) {
+ for(i=0, cnt=0; i<MAX_CPU; i++) {
+ if (glob_cpu[i]) {
+ do_expression(glob_cpu[i], -1);
+ cnt++;
+ if (cnt == cpu_count_out) {
+ printf("\n");
+ break;
+ } else {
+ printf("\t");
+ }
+ }
+ }
+ }
+ if (run_all && (test_at < the_cpu.number)) {
+ memset(glob_cpu, 0, sizeof(glob_cpu));
+ ncnts = 0;
+ printf("*********************************\n");
+ goto more;
+ } else if (run_all) {
+ done:
+ printf("*********************************\n");
+ }
+ return(0);
+}
diff --git a/usr.sbin/pnpinfo/Makefile b/usr.sbin/pnpinfo/Makefile
new file mode 100644
index 0000000..164ef37
--- /dev/null
+++ b/usr.sbin/pnpinfo/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../contrib/pnpinfo
+
+PROG= pnpinfo
+MAN= pnpinfo.8
+
+CFLAGS+= -I${.CURDIR}/../../sys
+
+.if ${MACHINE} == "pc98"
+CFLAGS+= -DPC98
+.endif
+
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/pnpinfo/Makefile.depend b/usr.sbin/pnpinfo/Makefile.depend
new file mode 100644
index 0000000..79eb58b
--- /dev/null
+++ b/usr.sbin/pnpinfo/Makefile.depend
@@ -0,0 +1,16 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/portsnap/Makefile b/usr.sbin/portsnap/Makefile
new file mode 100644
index 0000000..0f77bcb
--- /dev/null
+++ b/usr.sbin/portsnap/Makefile
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+SUBDIR= portsnap make_index phttpget
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/portsnap/Makefile.inc b/usr.sbin/portsnap/Makefile.inc
new file mode 100644
index 0000000..5f6527e
--- /dev/null
+++ b/usr.sbin/portsnap/Makefile.inc
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+LIBEXECDIR?= /usr/libexec
+
+.include "../Makefile.inc"
diff --git a/usr.sbin/portsnap/make_index/Makefile b/usr.sbin/portsnap/make_index/Makefile
new file mode 100644
index 0000000..92e9145
--- /dev/null
+++ b/usr.sbin/portsnap/make_index/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= make_index
+MAN=
+
+BINDIR= ${LIBEXECDIR}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/portsnap/make_index/Makefile.depend b/usr.sbin/portsnap/make_index/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/portsnap/make_index/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/portsnap/make_index/make_index.c b/usr.sbin/portsnap/make_index/make_index.c
new file mode 100644
index 0000000..c15ce10
--- /dev/null
+++ b/usr.sbin/portsnap/make_index/make_index.c
@@ -0,0 +1,513 @@
+/*-
+ * Copyright 2005 Colin Percival
+ * All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted providing that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct port;
+
+typedef union {
+ char * name;
+ struct port * p;
+} DEP;
+
+typedef struct port {
+ char * pkgname;
+ char * portdir;
+ char * prefix;
+ char * comment;
+ char * pkgdescr;
+ char * maintainer;
+ char * categories;
+ size_t n_edep;
+ DEP * edep;
+ size_t n_pdep;
+ DEP * pdep;
+ size_t n_fdep;
+ DEP * fdep;
+ size_t n_bdep;
+ DEP * bdep;
+ size_t n_rdep;
+ DEP * rdep;
+ char * www;
+ int recursed;
+} PORT;
+
+static void usage(void);
+static char * strdup2(const char *str);
+static DEP * makelist(char * str, size_t * n);
+static PORT * portify(char * line);
+static int portcompare(char * a, char * b);
+static void heapifyports(PORT **pp, size_t size, size_t pos);
+static PORT * findport(PORT ** pp, size_t st, size_t en, char * name, char * from);
+static void translateport(PORT ** pp, size_t pplen, PORT * p);
+static DEP * recurse_one(DEP * d, size_t * nd);
+static void recurse(PORT * p);
+static void heapifypkgs(DEP * d, size_t size, size_t pos);
+static void sortpkgs(DEP * d, size_t nd);
+static void printport(PORT * p);
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: make_index file\n");
+ exit(1);
+ /* NOTREACHED */
+}
+
+static char *
+strdup2(const char *str)
+{
+ char * r;
+
+ r = strdup(str);
+ if (r == NULL)
+ err(1, "strdup");
+ return r;
+}
+
+/* Take a space-separated list and return an array of (char *) */
+static DEP *
+makelist(char * str, size_t * n)
+{
+ DEP * d;
+ size_t i;
+
+ /* No depends at all? */
+ if (str[0] == 0) {
+ *n = 0;
+ return NULL;
+ }
+
+ /* Count the number of fields */
+ *n = 1;
+ for (i = 0; str[i] != 0; i++)
+ if (str[i] == ' ')
+ (*n)++;
+
+ /* Allocate and fill an array */
+ d = malloc(*n * sizeof(DEP));
+ if (d == NULL)
+ err(1, "malloc(DEP)");
+ for (i = 0; i < *n; i++) {
+ d[i].name = strdup2(strsep(&str, " "));
+
+ /* Strip trailing slashes */
+ if (d[i].name[strlen(d[i].name) - 1] == '/')
+ d[i].name[strlen(d[i].name) - 1] = 0;
+ }
+
+ return d;
+}
+
+/* Take a port's describe line and split it into fields */
+static PORT *
+portify(char * line)
+{
+ PORT * p;
+ size_t i, n;
+
+ /* Verify that line has the right number of fields */
+ for (n = i = 0; line[i] != 0; i++)
+ if (line[i] == '|')
+ n++;
+ if (n != 12)
+ errx(1, "Port describe line is corrupt:\n%s\n", line);
+
+ p = malloc(sizeof(PORT));
+ if (p == NULL)
+ err(1, "malloc(PORT)");
+
+ p->pkgname = strdup2(strsep(&line, "|"));
+ p->portdir = strdup2(strsep(&line, "|"));
+ p->prefix = strdup2(strsep(&line, "|"));
+ p->comment = strdup2(strsep(&line, "|"));
+ p->pkgdescr = strdup2(strsep(&line, "|"));
+ p->maintainer = strdup2(strsep(&line, "|"));
+ p->categories = strdup2(strsep(&line, "|"));
+ p->edep = makelist(strsep(&line, "|"), &p->n_edep);
+ p->pdep = makelist(strsep(&line, "|"), &p->n_pdep);
+ p->fdep = makelist(strsep(&line, "|"), &p->n_fdep);
+ p->bdep = makelist(strsep(&line, "|"), &p->n_bdep);
+ p->rdep = makelist(strsep(&line, "|"), &p->n_rdep);
+ p->www = strdup2(strsep(&line, "|"));
+
+ p->recursed = 0;
+
+ /*
+ * line will now be equal to NULL -- we counted the field
+ * separators at the top of the function.
+ */
+
+ return p;
+}
+
+/* Returns -1, 0, or 1 based on a comparison of the portdir strings */
+static int
+portcompare(char * a, char * b)
+{
+ size_t i;
+
+ /* Find first non-matching position */
+ for (i = 0; ; i++) {
+ if (a[i] != b[i])
+ break;
+ if (a[i] == 0) /* End of strings */
+ return 0;
+ }
+
+ /* One string is a prefix of the other */
+ if (a[i] == 0)
+ return -1;
+ if (b[i] == 0)
+ return 1;
+
+ /* One string has a category which is a prefix of the other */
+ if (a[i] == '/')
+ return -1;
+ if (b[i] == '/')
+ return 1;
+
+ /* The two strings are simply different */
+ if (a[i] < b[i])
+ return -1;
+ else
+ return 1;
+}
+
+/* Heapify (PORT *) number pos in a pseudo-heap pp[0]..pp[size - 1] */
+static void
+heapifyports(PORT **pp, size_t size, size_t pos)
+{
+ size_t i = pos;
+ PORT * tmp;
+
+top:
+ /* Find the largest value out of {pos, 2*pos+1, 2*pos+2} */
+ if ((2 * pos + 1 < size) &&
+ (portcompare(pp[i]->portdir, pp[2 * pos + 1]->portdir) < 0))
+ i = 2 * pos + 1;
+ if ((2 * pos + 2 < size) &&
+ (portcompare(pp[i]->portdir, pp[2 * pos + 2]->portdir) < 0))
+ i = 2 * pos + 2;
+
+ /* If necessary, swap elements and iterate down the tree. */
+ if (i != pos) {
+ tmp = pp[pos];
+ pp[pos] = pp[i];
+ pp[i] = tmp;
+ pos = i;
+ goto top;
+ }
+}
+
+/* Translate a port directory name into a (PORT *), and free the name */
+static PORT *
+findport(PORT ** pp, size_t st, size_t en, char * name, char * from)
+{
+ size_t mid;
+ int r;
+
+ if (st == en)
+ errx(1, "%s: no entry for %s", from, name);
+
+ mid = (st + en) / 2;
+ r = portcompare(pp[mid]->portdir, name);
+
+ if (r == 0) {
+ free(name);
+ return pp[mid];
+ } else if (r < 0)
+ return findport(pp, mid + 1, en, name, from);
+ else
+ return findport(pp, st, mid, name, from);
+}
+
+/* Translate all depends from names into PORT *s */
+static void
+translateport(PORT ** pp, size_t pplen, PORT * p)
+{
+ size_t i;
+
+ for (i = 0; i < p->n_edep; i++)
+ p->edep[i].p = findport(pp, 0, pplen, p->edep[i].name, p->portdir);
+ for (i = 0; i < p->n_pdep; i++)
+ p->pdep[i].p = findport(pp, 0, pplen, p->pdep[i].name, p->portdir);
+ for (i = 0; i < p->n_fdep; i++)
+ p->fdep[i].p = findport(pp, 0, pplen, p->fdep[i].name, p->portdir);
+ for (i = 0; i < p->n_bdep; i++)
+ p->bdep[i].p = findport(pp, 0, pplen, p->bdep[i].name, p->portdir);
+ for (i = 0; i < p->n_rdep; i++)
+ p->rdep[i].p = findport(pp, 0, pplen, p->rdep[i].name, p->portdir);
+}
+
+/* Recurse on one specific depends list */
+static DEP *
+recurse_one(DEP * d, size_t * nd)
+{
+ size_t i, j, k, n, N;
+
+ N = n = *nd;
+ for (i = 0; i < n; i++) {
+ recurse(d[i].p);
+ for (j = 0; j < d[i].p->n_rdep; j++) {
+ for (k = 0; k < N; k++) {
+ if (d[i].p->rdep[j].p == d[k].p)
+ break;
+ }
+ if (k == N) {
+ N++;
+ if (N >= *nd) {
+ *nd += *nd;
+ d = realloc(d, *nd * sizeof(DEP));
+ if (d == NULL)
+ err(1, "realloc(d)");
+ }
+ d[k].p = d[i].p->rdep[j].p;
+ }
+ }
+ }
+ *nd = N;
+
+ return d;
+}
+
+/* Recurse on the depends lists */
+static void
+recurse(PORT * p)
+{
+ switch (p->recursed) {
+ case 0:
+ /* First time we've seen this port */
+ p->recursed = 1;
+ break;
+ case 1:
+ /* We're in the middle of recursing this port */
+ errx(1, "Circular dependency loop found: %s"
+ " depends upon itself.\n", p->pkgname);
+ case 2:
+ /* This port has already been recursed */
+ return;
+ }
+
+ p->edep = recurse_one(p->edep, &p->n_edep);
+ p->pdep = recurse_one(p->pdep, &p->n_pdep);
+ p->fdep = recurse_one(p->fdep, &p->n_fdep);
+ p->bdep = recurse_one(p->bdep, &p->n_bdep);
+ p->rdep = recurse_one(p->rdep, &p->n_rdep);
+
+ /* Finished recursing on this port */
+ p->recursed = 2;
+}
+
+/* Heapify an element in a package list */
+static void
+heapifypkgs(DEP * d, size_t size, size_t pos)
+{
+ size_t i = pos;
+ PORT * tmp;
+
+top:
+ /* Find the largest value out of {pos, 2*pos+1, 2*pos+2} */
+ if ((2 * pos + 1 < size) &&
+ (strcmp(d[i].p->pkgname, d[2 * pos + 1].p->pkgname) < 0))
+ i = 2 * pos + 1;
+ if ((2 * pos + 2 < size) &&
+ (strcmp(d[i].p->pkgname, d[2 * pos + 2].p->pkgname) < 0))
+ i = 2 * pos + 2;
+
+ /* If necessary, swap elements and iterate down the tree. */
+ if (i != pos) {
+ tmp = d[pos].p;
+ d[pos].p = d[i].p;
+ d[i].p = tmp;
+ pos = i;
+ goto top;
+ }
+}
+
+/* Sort a list of dependent packages in alphabetical order */
+static void
+sortpkgs(DEP * d, size_t nd)
+{
+ size_t i;
+ PORT * tmp;
+
+ if (nd == 0)
+ return;
+
+ for (i = nd; i > 0; i--)
+ heapifypkgs(d, nd, i - 1); /* Build a heap */
+ for (i = nd - 1; i > 0; i--) {
+ tmp = d[0].p; /* Extract elements */
+ d[0].p = d[i].p;
+ d[i].p = tmp;
+ heapifypkgs(d, i, 0); /* And re-heapify */
+ }
+}
+
+/* Output an index line for the given port. */
+static void
+printport(PORT * p)
+{
+ size_t i;
+
+ sortpkgs(p->edep, p->n_edep);
+ sortpkgs(p->pdep, p->n_pdep);
+ sortpkgs(p->fdep, p->n_fdep);
+ sortpkgs(p->bdep, p->n_bdep);
+ sortpkgs(p->rdep, p->n_rdep);
+
+ printf("%s|%s|%s|%s|%s|%s|%s|",
+ p->pkgname, p->portdir, p->prefix, p->comment, p->pkgdescr,
+ p->maintainer, p->categories);
+ for (i = 0; i < p->n_bdep; i++)
+ printf("%s%s", i ? " " : "", p->bdep[i].p->pkgname);
+ printf("|");
+ for (i = 0; i < p->n_rdep; i++)
+ printf("%s%s", i ? " " : "", p->rdep[i].p->pkgname);
+ printf("|");
+ printf("%s|", p->www);
+ for (i = 0; i < p->n_edep; i++)
+ printf("%s%s", i ? " " : "", p->edep[i].p->pkgname);
+ printf("|");
+ for (i = 0; i < p->n_pdep; i++)
+ printf("%s%s", i ? " " : "", p->pdep[i].p->pkgname);
+ printf("|");
+ for (i = 0; i < p->n_fdep; i++)
+ printf("%s%s", i ? " " : "", p->fdep[i].p->pkgname);
+ printf("\n");
+}
+
+/*
+ * Algorithm:
+ * 1. Suck in all the data, splitting into fields.
+ * 1a. If there are no ports, there is no INDEX.
+ * 2. Sort the ports according to port directory.
+ * 3. Using a binary search, translate each dependency from a
+ * port directory name into a pointer to a port.
+ * 4. Recursively follow dependencies, expanding the lists of
+ * pointers as needed (using realloc).
+ * 5. Iterate through the ports, printing them out (remembering
+ * to list the dependent ports in alphabetical order).
+ */
+
+int
+main(int argc, char *argv[])
+{
+ FILE * f;
+ char * line;
+ size_t linelen;
+ PORT ** pp; /* Array of pointers to PORTs */
+ PORT * tmp;
+ size_t pplen; /* Allocated size of array */
+ size_t i;
+
+ if (argc != 2)
+ usage();
+ if ((f = fopen(argv[1], "r")) == NULL)
+ err(1, "fopen(%s)", argv[1]);
+
+ pplen = 1024;
+ if ((pp = malloc(pplen * sizeof(PORT *))) == NULL)
+ err(1, "malloc(pp)");
+
+ /*
+ * 1. Suck in all the data, splitting into fields.
+ */
+ for(i = 0; (line = fgetln(f, &linelen)) != NULL; i++) {
+ if (line[linelen - 1] != '\n')
+ errx(1, "Unterminated line encountered");
+ line[linelen - 1] = 0;
+
+ /* Enlarge array if needed */
+ if (i >= pplen) {
+ pplen *= 2;
+ if ((pp = realloc(pp, pplen * sizeof(PORT *))) == NULL)
+ err(1, "realloc(pp)");
+ }
+
+ pp[i] = portify(line);
+ }
+ /* Reallocate to the correct size */
+ pplen = i;
+ if ((pp = realloc(pp, pplen * sizeof(PORT *))) == NULL)
+ err(1, "realloc(pp)");
+
+ /* Make sure we actually reached the EOF */
+ if (!feof(f))
+ err(1, "fgetln(%s)", argv[1]);
+ /* Close the describes file */
+ if (fclose(f) != 0)
+ err(1, "fclose(%s)", argv[1]);
+
+ /*
+ * 1a. If there are no ports, there is no INDEX.
+ */
+ if (pplen == 0)
+ return 0;
+
+ /*
+ * 2. Sort the ports according to port directory.
+ */
+ for (i = pplen; i > 0; i--)
+ heapifyports(pp, pplen, i - 1); /* Build a heap */
+ for (i = pplen - 1; i > 0; i--) {
+ tmp = pp[0]; /* Extract elements */
+ pp[0] = pp[i];
+ pp[i] = tmp;
+ heapifyports(pp, i, 0); /* And re-heapify */
+ }
+
+ /*
+ * 3. Using a binary search, translate each dependency from a
+ * port directory name into a pointer to a port.
+ */
+ for (i = 0; i < pplen; i++)
+ translateport(pp, pplen, pp[i]);
+
+ /*
+ * 4. Recursively follow dependencies, expanding the lists of
+ * pointers as needed (using realloc).
+ */
+ for (i = 0; i < pplen; i++)
+ recurse(pp[i]);
+
+ /*
+ * 5. Iterate through the ports, printing them out (remembering
+ * to list the dependent ports in alphabetical order).
+ */
+ for (i = 0; i < pplen; i++)
+ printport(pp[i]);
+
+ return 0;
+}
diff --git a/usr.sbin/portsnap/phttpget/Makefile b/usr.sbin/portsnap/phttpget/Makefile
new file mode 100644
index 0000000..51c5b99
--- /dev/null
+++ b/usr.sbin/portsnap/phttpget/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= phttpget
+MAN= phttpget.8
+
+BINDIR= ${LIBEXECDIR}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/portsnap/phttpget/Makefile.depend b/usr.sbin/portsnap/phttpget/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/portsnap/phttpget/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/portsnap/phttpget/phttpget.8 b/usr.sbin/portsnap/phttpget/phttpget.8
new file mode 100644
index 0000000..f0c8210
--- /dev/null
+++ b/usr.sbin/portsnap/phttpget/phttpget.8
@@ -0,0 +1,88 @@
+.\"-
+.\" Copyright (c) 2015 Xin Li <delphij@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 January 3, 2015
+.Dt PHTTPGET 8
+.Os
+.Sh NAME
+.Nm phttpget
+.Nd retrieve multiple files via pipelined HTTP
+.Sh SYNOPSIS
+.Nm
+.Ar server
+.Ar file ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is a minimalist pipelined HTTP client,
+which is used to retrieve multiple
+.Ar file Ns s
+from one
+.Ar server ,
+and saves the downloaded files in the current working directory,
+using the last portion of their download path as file names.
+.Pp
+By making several "in flight" HTTP requests,
+it can dramatically increase performance when a large number of
+small files need to be downloaded.
+.Pp
+The
+.Xr freebsd-update 8
+and
+.Xr portnap 8
+tools use
+.Nm
+to download binary patch files.
+.Sh ENVIRONMENT
+.Bl -tag -width HTTP_PROXY_AUTH
+.It Ev HTTP_PROXY
+URL of the proxy to use for HTTP requests.
+.It Ev HTTP_PROXY_AUTH
+Authorization parameters for the HTTP proxy.
+.It Ev HTTP_USER_AGENT
+The User-Agent string to use for HTTP requests.
+The default is
+.Dq phttpget/0.1 .
+.It Ev HTTP_TIMEOUT
+Timeout for HTTP request in seconds.
+.El
+.Sh SEE ALSO
+.Xr fetch 1 ,
+.Xr freebsd-update 8 ,
+.Xr portsnap 8
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility was written by
+.An Colin Percival Aq Mt cperciva@FreeBSD.org
+for use with
+.Xr portsnap 8
+and later with
+.Xr freebsd-update 8 .
+This manual page was written by
+.An Xin LI Aq Mt delphij@FreeBSD.org .
diff --git a/usr.sbin/portsnap/phttpget/phttpget.c b/usr.sbin/portsnap/phttpget/phttpget.c
new file mode 100644
index 0000000..1bf6d9d
--- /dev/null
+++ b/usr.sbin/portsnap/phttpget/phttpget.c
@@ -0,0 +1,730 @@
+/*-
+ * Copyright 2005 Colin Percival
+ * All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted providing that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/time.h>
+#include <sys/socket.h>
+
+#include <ctype.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 <sysexits.h>
+#include <unistd.h>
+
+static const char * env_HTTP_PROXY;
+static char * env_HTTP_PROXY_AUTH;
+static const char * env_HTTP_USER_AGENT;
+static char * env_HTTP_TIMEOUT;
+static const char * proxyport;
+static char * proxyauth;
+
+static struct timeval timo = { 15, 0};
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: phttpget server [file ...]\n");
+ exit(EX_USAGE);
+}
+
+/*
+ * Base64 encode a string; the string returned, if non-NULL, is
+ * allocated using malloc() and must be freed by the caller.
+ */
+static char *
+b64enc(const char *ptext)
+{
+ static const char base64[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789+/";
+ const char *pt;
+ char *ctext, *pc;
+ size_t ptlen, ctlen;
+ uint32_t t;
+ unsigned int j;
+
+ /*
+ * Encoded length is 4 characters per 3-byte block or partial
+ * block of plaintext, plus one byte for the terminating NUL
+ */
+ ptlen = strlen(ptext);
+ if (ptlen > ((SIZE_MAX - 1) / 4) * 3 - 2)
+ return NULL; /* Possible integer overflow */
+ ctlen = 4 * ((ptlen + 2) / 3) + 1;
+ if ((ctext = malloc(ctlen)) == NULL)
+ return NULL;
+ ctext[ctlen - 1] = 0;
+
+ /*
+ * Scan through ptext, reading up to 3 bytes from ptext and
+ * writing 4 bytes to ctext, until we run out of input.
+ */
+ for (pt = ptext, pc = ctext; ptlen; ptlen -= 3, pc += 4) {
+ /* Read 3 bytes */
+ for (t = j = 0; j < 3; j++) {
+ t <<= 8;
+ if (j < ptlen)
+ t += *pt++;
+ }
+
+ /* Write 4 bytes */
+ for (j = 0; j < 4; j++) {
+ if (j <= ptlen + 1)
+ pc[j] = base64[(t >> 18) & 0x3f];
+ else
+ pc[j] = '=';
+ t <<= 6;
+ }
+
+ /* If we're done, exit the loop */
+ if (ptlen <= 3)
+ break;
+ }
+
+ return (ctext);
+}
+
+static void
+readenv(void)
+{
+ char *proxy_auth_userpass, *proxy_auth_userpass64, *p;
+ char *proxy_auth_user = NULL;
+ char *proxy_auth_pass = NULL;
+ long http_timeout;
+
+ env_HTTP_PROXY = getenv("HTTP_PROXY");
+ if (env_HTTP_PROXY == NULL)
+ env_HTTP_PROXY = getenv("http_proxy");
+ if (env_HTTP_PROXY != NULL) {
+ if (strncmp(env_HTTP_PROXY, "http://", 7) == 0)
+ env_HTTP_PROXY += 7;
+ p = strchr(env_HTTP_PROXY, '/');
+ if (p != NULL)
+ *p = 0;
+ p = strchr(env_HTTP_PROXY, ':');
+ if (p != NULL) {
+ *p = 0;
+ proxyport = p + 1;
+ } else
+ proxyport = "3128";
+ }
+
+ env_HTTP_PROXY_AUTH = getenv("HTTP_PROXY_AUTH");
+ if ((env_HTTP_PROXY != NULL) &&
+ (env_HTTP_PROXY_AUTH != NULL) &&
+ (strncasecmp(env_HTTP_PROXY_AUTH, "basic:" , 6) == 0)) {
+ /* Ignore authentication scheme */
+ (void) strsep(&env_HTTP_PROXY_AUTH, ":");
+
+ /* Ignore realm */
+ (void) strsep(&env_HTTP_PROXY_AUTH, ":");
+
+ /* Obtain username and password */
+ proxy_auth_user = strsep(&env_HTTP_PROXY_AUTH, ":");
+ proxy_auth_pass = env_HTTP_PROXY_AUTH;
+ }
+
+ if ((proxy_auth_user != NULL) && (proxy_auth_pass != NULL)) {
+ asprintf(&proxy_auth_userpass, "%s:%s",
+ proxy_auth_user, proxy_auth_pass);
+ if (proxy_auth_userpass == NULL)
+ err(1, "asprintf");
+
+ proxy_auth_userpass64 = b64enc(proxy_auth_userpass);
+ if (proxy_auth_userpass64 == NULL)
+ err(1, "malloc");
+
+ asprintf(&proxyauth, "Proxy-Authorization: Basic %s\r\n",
+ proxy_auth_userpass64);
+ if (proxyauth == NULL)
+ err(1, "asprintf");
+
+ free(proxy_auth_userpass);
+ free(proxy_auth_userpass64);
+ } else
+ proxyauth = NULL;
+
+ env_HTTP_USER_AGENT = getenv("HTTP_USER_AGENT");
+ if (env_HTTP_USER_AGENT == NULL)
+ env_HTTP_USER_AGENT = "phttpget/0.1";
+
+ env_HTTP_TIMEOUT = getenv("HTTP_TIMEOUT");
+ if (env_HTTP_TIMEOUT != NULL) {
+ http_timeout = strtol(env_HTTP_TIMEOUT, &p, 10);
+ if ((*env_HTTP_TIMEOUT == '\0') || (*p != '\0') ||
+ (http_timeout < 0))
+ warnx("HTTP_TIMEOUT (%s) is not a positive integer",
+ env_HTTP_TIMEOUT);
+ else
+ timo.tv_sec = http_timeout;
+ }
+}
+
+static int
+makerequest(char ** buf, char * path, char * server, int connclose)
+{
+ int buflen;
+
+ buflen = asprintf(buf,
+ "GET %s%s/%s HTTP/1.1\r\n"
+ "Host: %s\r\n"
+ "User-Agent: %s\r\n"
+ "%s"
+ "%s"
+ "\r\n",
+ env_HTTP_PROXY ? "http://" : "",
+ env_HTTP_PROXY ? server : "",
+ path, server, env_HTTP_USER_AGENT,
+ proxyauth ? proxyauth : "",
+ connclose ? "Connection: Close\r\n" : "Connection: Keep-Alive\r\n");
+ if (buflen == -1)
+ err(1, "asprintf");
+ return(buflen);
+}
+
+static int
+readln(int sd, char * resbuf, int * resbuflen, int * resbufpos)
+{
+ ssize_t len;
+
+ while (strnstr(resbuf + *resbufpos, "\r\n",
+ *resbuflen - *resbufpos) == NULL) {
+ /* Move buffered data to the start of the buffer */
+ if (*resbufpos != 0) {
+ memmove(resbuf, resbuf + *resbufpos,
+ *resbuflen - *resbufpos);
+ *resbuflen -= *resbufpos;
+ *resbufpos = 0;
+ }
+
+ /* If the buffer is full, complain */
+ if (*resbuflen == BUFSIZ)
+ return -1;
+
+ /* Read more data into the buffer */
+ len = recv(sd, resbuf + *resbuflen, BUFSIZ - *resbuflen, 0);
+ if ((len == 0) ||
+ ((len == -1) && (errno != EINTR)))
+ return -1;
+
+ if (len != -1)
+ *resbuflen += len;
+ }
+
+ return 0;
+}
+
+static int
+copybytes(int sd, int fd, off_t copylen, char * resbuf, int * resbuflen,
+ int * resbufpos)
+{
+ ssize_t len;
+
+ while (copylen) {
+ /* Write data from resbuf to fd */
+ len = *resbuflen - *resbufpos;
+ if (copylen < len)
+ len = copylen;
+ if (len > 0) {
+ if (fd != -1)
+ len = write(fd, resbuf + *resbufpos, len);
+ if (len == -1)
+ err(1, "write");
+ *resbufpos += len;
+ copylen -= len;
+ continue;
+ }
+
+ /* Read more data into buffer */
+ len = recv(sd, resbuf, BUFSIZ, 0);
+ if (len == -1) {
+ if (errno == EINTR)
+ continue;
+ return -1;
+ } else if (len == 0) {
+ return -2;
+ } else {
+ *resbuflen = len;
+ *resbufpos = 0;
+ }
+ }
+
+ return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct addrinfo hints; /* Hints to getaddrinfo */
+ struct addrinfo *res; /* Pointer to server address being used */
+ struct addrinfo *res0; /* Pointer to server addresses */
+ char * resbuf = NULL; /* Response buffer */
+ int resbufpos = 0; /* Response buffer position */
+ int resbuflen = 0; /* Response buffer length */
+ char * eolp; /* Pointer to "\r\n" within resbuf */
+ char * hln; /* Pointer within header line */
+ char * servername; /* Name of server */
+ char * fname = NULL; /* Name of downloaded file */
+ char * reqbuf = NULL; /* Request buffer */
+ int reqbufpos = 0; /* Request buffer position */
+ int reqbuflen = 0; /* Request buffer length */
+ ssize_t len; /* Length sent or received */
+ int nreq = 0; /* Number of next request to send */
+ int nres = 0; /* Number of next reply to receive */
+ int pipelined = 0; /* != 0 if connection in pipelined mode. */
+ int keepalive; /* != 0 if HTTP/1.0 keep-alive rcvd. */
+ int sd = -1; /* Socket descriptor */
+ int sdflags = 0; /* Flags on the socket sd */
+ int fd = -1; /* Descriptor for download target file */
+ int error; /* Error code */
+ int statuscode; /* HTTP Status code */
+ off_t contentlength; /* Value from Content-Length header */
+ int chunked; /* != if transfer-encoding is chunked */
+ off_t clen; /* Chunk length */
+ int firstreq = 0; /* # of first request for this connection */
+ int val; /* Value used for setsockopt call */
+
+ /* Check that the arguments are sensible */
+ if (argc < 2)
+ usage();
+
+ /* Read important environment variables */
+ readenv();
+
+ /* Get server name and adjust arg[cv] to point at file names */
+ servername = argv[1];
+ argv += 2;
+ argc -= 2;
+
+ /* Allocate response buffer */
+ resbuf = malloc(BUFSIZ);
+ if (resbuf == NULL)
+ err(1, "malloc");
+
+ /* Look up server */
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ error = getaddrinfo(env_HTTP_PROXY ? env_HTTP_PROXY : servername,
+ env_HTTP_PROXY ? proxyport : "http", &hints, &res0);
+ if (error)
+ errx(1, "host = %s, port = %s: %s",
+ env_HTTP_PROXY ? env_HTTP_PROXY : servername,
+ env_HTTP_PROXY ? proxyport : "http",
+ gai_strerror(error));
+ if (res0 == NULL)
+ errx(1, "could not look up %s", servername);
+ res = res0;
+
+ /* Do the fetching */
+ while (nres < argc) {
+ /* Make sure we have a connected socket */
+ for (; sd == -1; res = res->ai_next) {
+ /* No addresses left to try :-( */
+ if (res == NULL)
+ errx(1, "Could not connect to %s", servername);
+
+ /* Create a socket... */
+ sd = socket(res->ai_family, res->ai_socktype,
+ res->ai_protocol);
+ if (sd == -1)
+ continue;
+
+ /* ... set 15-second timeouts ... */
+ setsockopt(sd, SOL_SOCKET, SO_SNDTIMEO,
+ (void *)&timo, (socklen_t)sizeof(timo));
+ setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO,
+ (void *)&timo, (socklen_t)sizeof(timo));
+
+ /* ... disable SIGPIPE generation ... */
+ val = 1;
+ setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE,
+ (void *)&val, sizeof(int));
+
+ /* ... and connect to the server. */
+ if(connect(sd, res->ai_addr, res->ai_addrlen)) {
+ close(sd);
+ sd = -1;
+ continue;
+ }
+
+ firstreq = nres;
+ }
+
+ /*
+ * If in pipelined HTTP mode, put socket into non-blocking
+ * mode, since we're probably going to want to try to send
+ * several HTTP requests.
+ */
+ if (pipelined) {
+ sdflags = fcntl(sd, F_GETFL);
+ if (fcntl(sd, F_SETFL, sdflags | O_NONBLOCK) == -1)
+ err(1, "fcntl");
+ }
+
+ /* Construct requests and/or send them without blocking */
+ while ((nreq < argc) && ((reqbuf == NULL) || pipelined)) {
+ /* If not in the middle of a request, make one */
+ if (reqbuf == NULL) {
+ reqbuflen = makerequest(&reqbuf, argv[nreq],
+ servername, (nreq == argc - 1));
+ reqbufpos = 0;
+ }
+
+ /* If in pipelined mode, try to send the request */
+ if (pipelined) {
+ while (reqbufpos < reqbuflen) {
+ len = send(sd, reqbuf + reqbufpos,
+ reqbuflen - reqbufpos, 0);
+ if (len == -1)
+ break;
+ reqbufpos += len;
+ }
+ if (reqbufpos < reqbuflen) {
+ if (errno != EAGAIN)
+ goto conndied;
+ break;
+ } else {
+ free(reqbuf);
+ reqbuf = NULL;
+ nreq++;
+ }
+ }
+ }
+
+ /* Put connection back into blocking mode */
+ if (pipelined) {
+ if (fcntl(sd, F_SETFL, sdflags) == -1)
+ err(1, "fcntl");
+ }
+
+ /* Do we need to blocking-send a request? */
+ if (nres == nreq) {
+ while (reqbufpos < reqbuflen) {
+ len = send(sd, reqbuf + reqbufpos,
+ reqbuflen - reqbufpos, 0);
+ if (len == -1)
+ goto conndied;
+ reqbufpos += len;
+ }
+ free(reqbuf);
+ reqbuf = NULL;
+ nreq++;
+ }
+
+ /* Scan through the response processing headers. */
+ statuscode = 0;
+ contentlength = -1;
+ chunked = 0;
+ keepalive = 0;
+ do {
+ /* Get a header line */
+ error = readln(sd, resbuf, &resbuflen, &resbufpos);
+ if (error)
+ goto conndied;
+ hln = resbuf + resbufpos;
+ eolp = strnstr(hln, "\r\n", resbuflen - resbufpos);
+ resbufpos = (eolp - resbuf) + 2;
+ *eolp = '\0';
+
+ /* Make sure it doesn't contain a NUL character */
+ if (strchr(hln, '\0') != eolp)
+ goto conndied;
+
+ if (statuscode == 0) {
+ /* The first line MUST be HTTP/1.x xxx ... */
+ if ((strncmp(hln, "HTTP/1.", 7) != 0) ||
+ ! isdigit(hln[7]))
+ goto conndied;
+
+ /*
+ * If the minor version number isn't zero,
+ * then we can assume that pipelining our
+ * requests is OK -- as long as we don't
+ * see a "Connection: close" line later
+ * and we either have a Content-Length or
+ * Transfer-Encoding: chunked header to
+ * tell us the length.
+ */
+ if (hln[7] != '0')
+ pipelined = 1;
+
+ /* Skip over the minor version number */
+ hln = strchr(hln + 7, ' ');
+ if (hln == NULL)
+ goto conndied;
+ else
+ hln++;
+
+ /* Read the status code */
+ while (isdigit(*hln)) {
+ statuscode = statuscode * 10 +
+ *hln - '0';
+ hln++;
+ }
+
+ if (statuscode < 100 || statuscode > 599)
+ goto conndied;
+
+ /* Ignore the rest of the line */
+ continue;
+ }
+
+ /*
+ * Check for "Connection: close" or
+ * "Connection: Keep-Alive" header
+ */
+ if (strncasecmp(hln, "Connection:", 11) == 0) {
+ hln += 11;
+ if (strcasestr(hln, "close") != NULL)
+ pipelined = 0;
+ if (strcasestr(hln, "Keep-Alive") != NULL)
+ keepalive = 1;
+
+ /* Next header... */
+ continue;
+ }
+
+ /* Check for "Content-Length:" header */
+ if (strncasecmp(hln, "Content-Length:", 15) == 0) {
+ hln += 15;
+ contentlength = 0;
+
+ /* Find the start of the length */
+ while (!isdigit(*hln) && (*hln != '\0'))
+ hln++;
+
+ /* Compute the length */
+ while (isdigit(*hln)) {
+ if (contentlength >= OFF_MAX / 10) {
+ /* Nasty people... */
+ goto conndied;
+ }
+ contentlength = contentlength * 10 +
+ *hln - '0';
+ hln++;
+ }
+
+ /* Next header... */
+ continue;
+ }
+
+ /* Check for "Transfer-Encoding: chunked" header */
+ if (strncasecmp(hln, "Transfer-Encoding:", 18) == 0) {
+ hln += 18;
+ if (strcasestr(hln, "chunked") != NULL)
+ chunked = 1;
+
+ /* Next header... */
+ continue;
+ }
+
+ /* We blithely ignore any other header lines */
+
+ /* No more header lines */
+ if (strlen(hln) == 0) {
+ /*
+ * If the status code was 1xx, then there will
+ * be a real header later. Servers may emit
+ * 1xx header blocks at will, but since we
+ * don't expect one, we should just ignore it.
+ */
+ if (100 <= statuscode && statuscode <= 199) {
+ statuscode = 0;
+ continue;
+ }
+
+ /* End of header; message body follows */
+ break;
+ }
+ } while (1);
+
+ /* No message body for 204 or 304 */
+ if (statuscode == 204 || statuscode == 304) {
+ nres++;
+ continue;
+ }
+
+ /*
+ * There should be a message body coming, but we only want
+ * to send it to a file if the status code is 200
+ */
+ if (statuscode == 200) {
+ /* Generate a file name for the download */
+ fname = strrchr(argv[nres], '/');
+ if (fname == NULL)
+ fname = argv[nres];
+ else
+ fname++;
+ if (strlen(fname) == 0)
+ errx(1, "Cannot obtain file name from %s\n",
+ argv[nres]);
+
+ fd = open(fname, O_CREAT | O_TRUNC | O_WRONLY, 0644);
+ if (fd == -1)
+ errx(1, "open(%s)", fname);
+ };
+
+ /* Read the message and send data to fd if appropriate */
+ if (chunked) {
+ /* Handle a chunked-encoded entity */
+
+ /* Read chunks */
+ do {
+ error = readln(sd, resbuf, &resbuflen,
+ &resbufpos);
+ if (error)
+ goto conndied;
+ hln = resbuf + resbufpos;
+ eolp = strstr(hln, "\r\n");
+ resbufpos = (eolp - resbuf) + 2;
+
+ clen = 0;
+ while (isxdigit(*hln)) {
+ if (clen >= OFF_MAX / 16) {
+ /* Nasty people... */
+ goto conndied;
+ }
+ if (isdigit(*hln))
+ clen = clen * 16 + *hln - '0';
+ else
+ clen = clen * 16 + 10 +
+ tolower(*hln) - 'a';
+ hln++;
+ }
+
+ error = copybytes(sd, fd, clen, resbuf,
+ &resbuflen, &resbufpos);
+ if (error) {
+ goto conndied;
+ }
+ } while (clen != 0);
+
+ /* Read trailer and final CRLF */
+ do {
+ error = readln(sd, resbuf, &resbuflen,
+ &resbufpos);
+ if (error)
+ goto conndied;
+ hln = resbuf + resbufpos;
+ eolp = strstr(hln, "\r\n");
+ resbufpos = (eolp - resbuf) + 2;
+ } while (hln != eolp);
+ } else if (contentlength != -1) {
+ error = copybytes(sd, fd, contentlength, resbuf,
+ &resbuflen, &resbufpos);
+ if (error)
+ goto conndied;
+ } else {
+ /*
+ * Not chunked, and no content length header.
+ * Read everything until the server closes the
+ * socket.
+ */
+ error = copybytes(sd, fd, OFF_MAX, resbuf,
+ &resbuflen, &resbufpos);
+ if (error == -1)
+ goto conndied;
+ pipelined = 0;
+ }
+
+ if (fd != -1) {
+ close(fd);
+ fd = -1;
+ }
+
+ fprintf(stderr, "http://%s/%s: %d ", servername, argv[nres],
+ statuscode);
+ if (statuscode == 200)
+ fprintf(stderr, "OK\n");
+ else if (statuscode < 300)
+ fprintf(stderr, "Successful (ignored)\n");
+ else if (statuscode < 400)
+ fprintf(stderr, "Redirection (ignored)\n");
+ else
+ fprintf(stderr, "Error (ignored)\n");
+
+ /* We've finished this file! */
+ nres++;
+
+ /*
+ * If necessary, clean up this connection so that we
+ * can start a new one.
+ */
+ if (pipelined == 0 && keepalive == 0)
+ goto cleanupconn;
+ continue;
+
+conndied:
+ /*
+ * Something went wrong -- our connection died, the server
+ * sent us garbage, etc. If this happened on the first
+ * request we sent over this connection, give up. Otherwise,
+ * close this connection, open a new one, and reissue the
+ * request.
+ */
+ if (nres == firstreq)
+ errx(1, "Connection failure");
+
+cleanupconn:
+ /*
+ * Clean up our connection and keep on going
+ */
+ shutdown(sd, SHUT_RDWR);
+ close(sd);
+ sd = -1;
+ if (fd != -1) {
+ close(fd);
+ fd = -1;
+ }
+ if (reqbuf != NULL) {
+ free(reqbuf);
+ reqbuf = NULL;
+ }
+ nreq = nres;
+ res = res0;
+ pipelined = 0;
+ resbufpos = resbuflen = 0;
+ continue;
+ }
+
+ free(resbuf);
+ freeaddrinfo(res0);
+
+ return 0;
+}
diff --git a/usr.sbin/portsnap/portsnap/Makefile b/usr.sbin/portsnap/portsnap/Makefile
new file mode 100644
index 0000000..e8f42b8
--- /dev/null
+++ b/usr.sbin/portsnap/portsnap/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+SCRIPTS=portsnap.sh
+MAN= portsnap.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/portsnap/portsnap/Makefile.depend b/usr.sbin/portsnap/portsnap/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/portsnap/portsnap/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/portsnap/portsnap/portsnap.8 b/usr.sbin/portsnap/portsnap/portsnap.8
new file mode 100644
index 0000000..db215f1
--- /dev/null
+++ b/usr.sbin/portsnap/portsnap/portsnap.8
@@ -0,0 +1,277 @@
+.\"-
+.\" Copyright 2004-2005 Colin Percival
+.\" All rights reserved
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted providing that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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$
+.\"
+.Dd October 22, 2013
+.Dt PORTSNAP 8
+.Os FreeBSD
+.Sh NAME
+.Nm portsnap
+.Nd fetch and extract compressed snapshots of the ports tree
+.Sh SYNOPSIS
+.Nm
+.Op Fl I
+.Op Fl d Ar workdir
+.Op Fl f Ar conffile
+.Op Fl k Ar KEY
+.Op Fl l Ar descfile
+.Op Fl p Ar portsdir
+.Op Fl s Ar server
+.Cm command ...
+.Op Ar path
+.Sh DESCRIPTION
+The
+.Nm
+tool is used to fetch and update compressed snapshots
+of the
+.Fx
+ports tree, and extract and update an
+uncompressed ports tree.
+.Pp
+In a normal update operation,
+.Nm
+will routinely restore modified files to their unmodified state and
+delete unrecognized local files.
+.Sh OPTIONS
+The following options are supported:
+.Bl -tag -width "-f conffile"
+.It Fl d Ar workdir
+Store working files (e.g.\& downloaded updates) in
+.Ar workdir .
+(default:
+.Pa /var/db/portsnap ,
+or as given in the configuration file.)
+.It Fl f Ar conffile
+Read the configuration from
+.Ar conffile .
+(default:
+.Pa /etc/portsnap.conf )
+.It Fl I
+For the
+.Cm update
+command, update INDEX files, but not the rest of the ports tree.
+.It Fl k Ar KEY
+Expect a public key with given SHA256 hash.
+(default: read value from configuration file.)
+.It Fl l Ar descfile
+Merge the specified local describes file into the INDEX files being
+built.
+The
+.Ar descfile
+should be generated by running
+.Cm make describe
+in each of the local port directories.
+.It Fl p Ar portsdir
+When extracting or updating an uncompressed snapshot,
+operate on the directory
+.Ar portsdir .
+(default:
+.Pa /usr/ports/ ,
+or as given in the configuration file.)
+.It Fl s Ar server
+Fetch files from the specified server or server pool.
+(default: portsnap.FreeBSD.org, or as given in the
+configuration file.)
+.It path
+For
+.Cm extract
+command only, operate only on parts of the ports tree starting with
+.Ar path .
+(e.g.\&
+.Nm
+.Cm extract
+.Ar sysutils/port
+would extract sysutils/portsman, sysutils/portsnap,
+sysutils/portupgrade, etc.)
+.It Fl Fl interactive
+override auto-detection of calling process.
+Only use this when calling portsnap from an
+.Sy interactive, non-terminal application.
+(Cron jobs are particularly bad since they cause
+load spikes on the Portsnap mirrors.)
+.El
+.Sh COMMANDS
+The
+.Cm command
+can be any one of the following:
+.Bl -tag -width "-f conffile"
+.It fetch
+Fetch a compressed snapshot of the ports tree, or update
+the existing snapshot.
+This command should only be used interactively; for
+non-interactive use, you should use the
+.Cm cron
+command.
+.It cron
+Sleep a random amount of time between 1 and 3600 seconds,
+then operate as if the
+.Cm fetch
+command was specified.
+As the name suggests, this command is designed for running
+from
+.Xr cron 8 ;
+the random delay serves to minimize the probability that
+a large number of machines will simultaneously attempt to
+fetch updates.
+.It extract
+Extract a ports tree, replacing existing files and directories.
+NOTE: This will remove anything occupying the location where
+files or directories are being extracted; in particular, any
+changes made locally to the ports tree (for example, adding new
+patches) will be silently obliterated.
+.Pp
+Only run this command to initialize your portsnap-maintained
+ports tree for the first time, if you wish to start over with
+a clean, completely unmodified tree, or if you wish to extract
+a specific part of the tree (using the
+.Ar path
+option).
+.It update
+Update a ports tree extracted using the
+.Cm extract
+command.
+You must run this command to apply changes to your ports tree
+after downloading updates via the
+.Cm fetch
+or
+.Cm cron
+commands.
+Again, note that in the parts of the ports tree which are being
+updated, any local changes or additions will be removed.
+.It auto
+Run
+.Cm fetch
+or
+.Cm cron
+depending on whether stdin is a terminal; then run
+.Cm update
+or
+.Cm extract
+depending on whether
+.Ar portsdir
+exists.
+.El
+.Sh TIPS
+.Bl -bullet
+.It
+If your clock is set to local time, adding the line
+.Pp
+.Dl 0 3 * * * root /usr/sbin/portsnap cron
+.Pp
+to
+.Pa /etc/crontab
+is a good way to make sure you always have
+an up-to-date snapshot of the ports tree available which
+can quickly be extracted into
+.Pa /usr/ports .
+If your clock is set to UTC, please pick a random time other
+than 3AM, to avoid overly imposing an uneven load on the
+server(s) hosting the snapshots.
+.Pp
+Note that running
+.Nm
+.Cm cron
+or
+.Nm
+.Cm fetch
+does not apply the changes that were received: they only download
+them.
+To apply the changes, you must follow these commands with
+.Nm
+.Cm update .
+The
+.Nm
+.Cm update
+command is normally run by hand at a time when you are sure that
+no one is manually working in the ports tree.
+.It
+Running
+.Nm
+.Cm update
+from
+.Xr cron 8
+is a bad idea -- if you are ever installing or updating a
+port at the time the cron job runs, you will probably end up
+in a mess when
+.Nm
+updates or removes files which are being used by the port
+build.
+However, running
+.Nm
+.Fl I
+.Cm update
+is probably safe, and can be used together with
+.Xr portversion 1
+to identify installed software which is out of date.
+.It
+If you wish to use
+.Nm
+to keep a large number of machines up to date, you may wish
+to set up a caching HTTP proxy.
+Since
+.Nm
+uses
+.Xr fetch 1
+to download updates, setting the
+.Ev HTTP_PROXY
+environment variable will direct it to fetch updates from
+the given proxy.
+This is much more efficient than
+.Em mirroring
+the files on the portsnap server, since the vast majority
+of files are not needed by any particular client.
+.El
+.Sh PRIVACY NOTICE
+As an unavoidable part of its operation, a machine running
+.Nm
+will make its public IP address and the list of files it fetches
+available to the server from which it fetches updates.
+Using these it may be possible to recognize a machine over an extended
+period of time, determine when it is updated, and identify which
+portions of the FreeBSD ports tree, if any, are being ignored using
+"REFUSE" directives in
+.Pa portsnap.conf .
+In addition, the FreeBSD release level is transmitted to the server.
+.Pp
+Statistical data generated from information collected in this manner
+may be published, but only in aggregate and after anonymizing the
+individual systems.
+.Sh FILES
+.Bl -tag -width "/etc/portsnap.conf"
+.It Pa /etc/portsnap.conf
+Default location of the portsnap configuration file.
+.It Pa /var/db/portsnap
+Default location where compressed snapshots are stored.
+.It Pa /usr/ports
+Default location where the ports tree is extracted.
+.El
+.Sh SEE ALSO
+.Xr fetch 1 ,
+.Xr sha256 1 ,
+.Xr fetch 3 ,
+.Xr portsnap.conf 5
+.Sh AUTHORS
+.An Colin Percival Aq Mt cperciva@FreeBSD.org
diff --git a/usr.sbin/portsnap/portsnap/portsnap.sh b/usr.sbin/portsnap/portsnap/portsnap.sh
new file mode 100644
index 0000000..cba06d2
--- /dev/null
+++ b/usr.sbin/portsnap/portsnap/portsnap.sh
@@ -0,0 +1,1139 @@
+#!/bin/sh
+
+#-
+# Copyright 2004-2005 Colin Percival
+# All rights reserved
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted providing that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+
+#### Usage function -- called from command-line handling code.
+
+# Usage instructions. Options not listed:
+# --debug -- don't filter output from utilities
+# --no-stats -- don't show progress statistics while fetching files
+usage() {
+ cat <<EOF
+usage: `basename $0` [options] command ... [path]
+
+Options:
+ -d workdir -- Store working files in workdir
+ (default: /var/db/portsnap/)
+ -f conffile -- Read configuration options from conffile
+ (default: /etc/portsnap.conf)
+ -I -- Update INDEX only. (update command only)
+ -k KEY -- Trust an RSA key with SHA256 hash of KEY
+ -l descfile -- Merge the specified local describes file into the INDEX.
+ -p portsdir -- Location of uncompressed ports tree
+ (default: /usr/ports/)
+ -s server -- Server from which to fetch updates.
+ (default: portsnap.FreeBSD.org)
+ --interactive -- interactive: override auto-detection of calling process
+ (use this when calling portsnap from an interactive, non-
+ terminal application AND NEVER ELSE).
+ path -- Extract only parts of the tree starting with the given
+ string. (extract command only)
+Commands:
+ fetch -- Fetch a compressed snapshot of the ports tree,
+ or update an existing snapshot.
+ cron -- Sleep rand(3600) seconds, and then fetch updates.
+ extract -- Extract snapshot of ports tree, replacing existing
+ files and directories.
+ update -- Update ports tree to match current snapshot, replacing
+ files and directories which have changed.
+ auto -- Fetch updates, and either extract a new ports tree or
+ update an existing tree.
+EOF
+ exit 0
+}
+
+#### Parameter handling functions.
+
+# Initialize parameters to null, just in case they're
+# set in the environment.
+init_params() {
+ KEYPRINT=""
+ EXTRACTPATH=""
+ WORKDIR=""
+ PORTSDIR=""
+ CONFFILE=""
+ COMMAND=""
+ COMMANDS=""
+ QUIETREDIR=""
+ QUIETFLAG=""
+ STATSREDIR=""
+ XARGST=""
+ NDEBUG=""
+ DDSTATS=""
+ INDEXONLY=""
+ SERVERNAME=""
+ REFUSE=""
+ LOCALDESC=""
+ INTERACTIVE=""
+}
+
+# Parse the command line
+parse_cmdline() {
+ while [ $# -gt 0 ]; do
+ case "$1" in
+ -d)
+ if [ $# -eq 1 ]; then usage; fi
+ if [ ! -z "${WORKDIR}" ]; then usage; fi
+ shift; WORKDIR="$1"
+ ;;
+ --debug)
+ QUIETREDIR="/dev/stderr"
+ STATSREDIR="/dev/stderr"
+ QUIETFLAG=" "
+ NDEBUG=" "
+ XARGST="-t"
+ DDSTATS=".."
+ ;;
+ --interactive)
+ INTERACTIVE="YES"
+ ;;
+ -f)
+ if [ $# -eq 1 ]; then usage; fi
+ if [ ! -z "${CONFFILE}" ]; then usage; fi
+ shift; CONFFILE="$1"
+ ;;
+ -h | --help | help)
+ usage
+ ;;
+ -I)
+ INDEXONLY="YES"
+ ;;
+ -k)
+ if [ $# -eq 1 ]; then usage; fi
+ if [ ! -z "${KEYPRINT}" ]; then usage; fi
+ shift; KEYPRINT="$1"
+ ;;
+ -l)
+ if [ $# -eq 1 ]; then usage; fi
+ if [ ! -z "${LOCALDESC}" ]; then usage; fi
+ shift; LOCALDESC="$1"
+ ;;
+ --no-stats)
+ if [ -z "${STATSREDIR}" ]; then
+ STATSREDIR="/dev/null"
+ DDSTATS=".. "
+ fi
+ ;;
+ -p)
+ if [ $# -eq 1 ]; then usage; fi
+ if [ ! -z "${PORTSDIR}" ]; then usage; fi
+ shift; PORTSDIR="$1"
+ ;;
+ -s)
+ if [ $# -eq 1 ]; then usage; fi
+ if [ ! -z "${SERVERNAME}" ]; then usage; fi
+ shift; SERVERNAME="$1"
+ ;;
+ cron | extract | fetch | update | auto)
+ COMMANDS="${COMMANDS} $1"
+ ;;
+ up)
+ COMMANDS="${COMMANDS} update"
+ ;;
+ alfred)
+ COMMANDS="${COMMANDS} auto"
+ ;;
+ *)
+ if [ $# -gt 1 ]; then usage; fi
+ if echo ${COMMANDS} | grep -vq extract; then
+ usage
+ fi
+ EXTRACTPATH="$1"
+ ;;
+ esac
+ shift
+ done
+
+ if [ -z "${COMMANDS}" ]; then
+ usage
+ fi
+}
+
+# If CONFFILE was specified at the command-line, make
+# sure that it exists and is readable.
+sanity_conffile() {
+ if [ ! -z "${CONFFILE}" ] && [ ! -r "${CONFFILE}" ]; then
+ echo -n "File does not exist "
+ echo -n "or is not readable: "
+ echo ${CONFFILE}
+ exit 1
+ fi
+}
+
+# If a configuration file hasn't been specified, use
+# the default value (/etc/portsnap.conf)
+default_conffile() {
+ if [ -z "${CONFFILE}" ]; then
+ CONFFILE="/etc/portsnap.conf"
+ fi
+}
+
+# Read {KEYPRINT, SERVERNAME, WORKDIR, PORTSDIR} from the configuration
+# file if they haven't already been set. If the configuration
+# file doesn't exist, do nothing.
+# Also read REFUSE (which cannot be set via the command line) if it is
+# present in the configuration file.
+parse_conffile() {
+ if [ -r "${CONFFILE}" ]; then
+ for X in KEYPRINT WORKDIR PORTSDIR SERVERNAME; do
+ eval _=\$${X}
+ if [ -z "${_}" ]; then
+ eval ${X}=`grep "^${X}=" "${CONFFILE}" |
+ cut -f 2- -d '=' | tail -1`
+ fi
+ done
+
+ if grep -qE "^REFUSE[[:space:]]" ${CONFFILE}; then
+ REFUSE="^(`
+ grep -E "^REFUSE[[:space:]]" "${CONFFILE}" |
+ cut -c 7- | xargs echo | tr ' ' '|'
+ `)"
+ fi
+
+ if grep -qE "^INDEX[[:space:]]" ${CONFFILE}; then
+ INDEXPAIRS="`
+ grep -E "^INDEX[[:space:]]" "${CONFFILE}" |
+ cut -c 7- | tr ' ' '|' | xargs echo`"
+ fi
+ fi
+}
+
+# If parameters have not been set, use default values
+default_params() {
+ _QUIETREDIR="/dev/null"
+ _QUIETFLAG="-q"
+ _STATSREDIR="/dev/stdout"
+ _WORKDIR="/var/db/portsnap"
+ _PORTSDIR="/usr/ports"
+ _NDEBUG="-n"
+ _LOCALDESC="/dev/null"
+ for X in QUIETREDIR QUIETFLAG STATSREDIR WORKDIR PORTSDIR \
+ NDEBUG LOCALDESC; do
+ eval _=\$${X}
+ eval __=\$_${X}
+ if [ -z "${_}" ]; then
+ eval ${X}=${__}
+ fi
+ done
+ if [ -z "${INTERACTIVE}" ]; then
+ if [ -t 0 ]; then
+ INTERACTIVE="YES"
+ else
+ INTERACTIVE="NO"
+ fi
+ fi
+}
+
+# Perform sanity checks and set some final parameters
+# in preparation for fetching files. Also chdir into
+# the working directory.
+fetch_check_params() {
+ export HTTP_USER_AGENT="portsnap (${COMMAND}, `uname -r`)"
+
+ _SERVERNAME_z=\
+"SERVERNAME must be given via command line or configuration file."
+ _KEYPRINT_z="Key must be given via -k option or configuration file."
+ _KEYPRINT_bad="Invalid key fingerprint: "
+ _WORKDIR_bad="Directory does not exist or is not writable: "
+
+ if [ -z "${SERVERNAME}" ]; then
+ echo -n "`basename $0`: "
+ echo "${_SERVERNAME_z}"
+ exit 1
+ fi
+ if [ -z "${KEYPRINT}" ]; then
+ echo -n "`basename $0`: "
+ echo "${_KEYPRINT_z}"
+ exit 1
+ fi
+ if ! echo "${KEYPRINT}" | grep -qE "^[0-9a-f]{64}$"; then
+ echo -n "`basename $0`: "
+ echo -n "${_KEYPRINT_bad}"
+ echo ${KEYPRINT}
+ exit 1
+ fi
+ if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
+ echo -n "`basename $0`: "
+ echo -n "${_WORKDIR_bad}"
+ echo ${WORKDIR}
+ exit 1
+ fi
+ cd ${WORKDIR} || exit 1
+
+ BSPATCH=/usr/bin/bspatch
+ SHA256=/sbin/sha256
+ PHTTPGET=/usr/libexec/phttpget
+}
+
+# Perform sanity checks and set some final parameters
+# in preparation for extracting or updating ${PORTSDIR}
+# Complain if ${PORTSDIR} exists but is not writable,
+# but don't complain if ${PORTSDIR} doesn't exist.
+extract_check_params() {
+ _WORKDIR_bad="Directory does not exist: "
+ _PORTSDIR_bad="Directory is not writable: "
+
+ if ! [ -d "${WORKDIR}" ]; then
+ echo -n "`basename $0`: "
+ echo -n "${_WORKDIR_bad}"
+ echo ${WORKDIR}
+ exit 1
+ fi
+ if [ -d "${PORTSDIR}" ] && ! [ -w "${PORTSDIR}" ]; then
+ echo -n "`basename $0`: "
+ echo -n "${_PORTSDIR_bad}"
+ echo ${PORTSDIR}
+ exit 1
+ fi
+
+ if ! [ -d "${WORKDIR}/files" -a -r "${WORKDIR}/tag" \
+ -a -r "${WORKDIR}/INDEX" -a -r "${WORKDIR}/tINDEX" ]; then
+ echo "No snapshot available. Try running"
+ echo "# `basename $0` fetch"
+ exit 1
+ fi
+
+ MKINDEX=/usr/libexec/make_index
+}
+
+# Perform sanity checks and set some final parameters
+# in preparation for updating ${PORTSDIR}
+update_check_params() {
+ extract_check_params
+
+ if ! [ -r ${PORTSDIR}/.portsnap.INDEX ]; then
+ echo "${PORTSDIR} was not created by portsnap."
+ echo -n "You must run '`basename $0` extract' before "
+ echo "running '`basename $0` update'."
+ exit 1
+ fi
+
+}
+
+#### Core functionality -- the actual work gets done here
+
+# Use an SRV query to pick a server. If the SRV query doesn't provide
+# a useful answer, use the server name specified by the user.
+# Put another way... look up _http._tcp.${SERVERNAME} and pick a server
+# from that; or if no servers are returned, use ${SERVERNAME}.
+# This allows a user to specify "portsnap.freebsd.org" (in which case
+# portsnap will select one of the mirrors) or "portsnap5.tld.freebsd.org"
+# (in which case portsnap will use that particular server, since there
+# won't be an SRV entry for that name).
+#
+# We ignore the Port field, since we are always going to use port 80.
+
+# Fetch the mirror list, but do not pick a mirror yet. Returns 1 if
+# no mirrors are available for any reason.
+fetch_pick_server_init() {
+ : > serverlist_tried
+
+# Check that host(1) exists (i.e., that the system wasn't built with the
+# WITHOUT_BIND set) and don't try to find a mirror if it doesn't exist.
+ if ! which -s host; then
+ : > serverlist_full
+ return 1
+ fi
+
+ echo -n "Looking up ${SERVERNAME} mirrors... "
+
+# Issue the SRV query and pull out the Priority, Weight, and Target fields.
+# BIND 9 prints "$name has SRV record ..." while BIND 8 prints
+# "$name server selection ..."; we allow either format.
+ MLIST="_http._tcp.${SERVERNAME}"
+ host -t srv "${MLIST}" |
+ sed -nE "s/${MLIST} (has SRV record|server selection) //Ip" |
+ cut -f 1,2,4 -d ' ' |
+ sed -e 's/\.$//' |
+ sort > serverlist_full
+
+# If no records, give up -- we'll just use the server name we were given.
+ if [ `wc -l < serverlist_full` -eq 0 ]; then
+ echo "none found."
+ return 1
+ fi
+
+# Report how many mirrors we found.
+ echo `wc -l < serverlist_full` "mirrors found."
+
+# Generate a random seed for use in picking mirrors. If HTTP_PROXY
+# is set, this will be used to generate the seed; otherwise, the seed
+# will be random.
+ if [ -n "${HTTP_PROXY}${http_proxy}" ]; then
+ RANDVALUE=`sha256 -qs "${HTTP_PROXY}${http_proxy}" |
+ tr -d 'a-f' |
+ cut -c 1-9`
+ else
+ RANDVALUE=`jot -r 1 0 999999999`
+ fi
+}
+
+# Pick a mirror. Returns 1 if we have run out of mirrors to try.
+fetch_pick_server() {
+# Generate a list of not-yet-tried mirrors
+ sort serverlist_tried |
+ comm -23 serverlist_full - > serverlist
+
+# Have we run out of mirrors?
+ if [ `wc -l < serverlist` -eq 0 ]; then
+ echo "No mirrors remaining, giving up."
+ return 1
+ fi
+
+# Find the highest priority level (lowest numeric value).
+ SRV_PRIORITY=`cut -f 1 -d ' ' serverlist | sort -n | head -1`
+
+# Add up the weights of the response lines at that priority level.
+ SRV_WSUM=0;
+ while read X; do
+ case "$X" in
+ ${SRV_PRIORITY}\ *)
+ SRV_W=`echo $X | cut -f 2 -d ' '`
+ SRV_WSUM=$(($SRV_WSUM + $SRV_W))
+ ;;
+ esac
+ done < serverlist
+
+# If all the weights are 0, pretend that they are all 1 instead.
+ if [ ${SRV_WSUM} -eq 0 ]; then
+ SRV_WSUM=`grep -E "^${SRV_PRIORITY} " serverlist | wc -l`
+ SRV_W_ADD=1
+ else
+ SRV_W_ADD=0
+ fi
+
+# Pick a value between 0 and the sum of the weights - 1
+ SRV_RND=`expr ${RANDVALUE} % ${SRV_WSUM}`
+
+# Read through the list of mirrors and set SERVERNAME. Write the line
+# corresponding to the mirror we selected into serverlist_tried so that
+# we won't try it again.
+ while read X; do
+ case "$X" in
+ ${SRV_PRIORITY}\ *)
+ SRV_W=`echo $X | cut -f 2 -d ' '`
+ SRV_W=$(($SRV_W + $SRV_W_ADD))
+ if [ $SRV_RND -lt $SRV_W ]; then
+ SERVERNAME=`echo $X | cut -f 3 -d ' '`
+ echo "$X" >> serverlist_tried
+ break
+ else
+ SRV_RND=$(($SRV_RND - $SRV_W))
+ fi
+ ;;
+ esac
+ done < serverlist
+}
+
+# Check that we have a public key with an appropriate hash, or
+# fetch the key if it doesn't exist. Returns 1 if the key has
+# not yet been fetched.
+fetch_key() {
+ if [ -r pub.ssl ] && [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then
+ return 0
+ fi
+
+ echo -n "Fetching public key from ${SERVERNAME}... "
+ rm -f pub.ssl
+ fetch ${QUIETFLAG} http://${SERVERNAME}/pub.ssl \
+ 2>${QUIETREDIR} || true
+ if ! [ -r pub.ssl ]; then
+ echo "failed."
+ return 1
+ fi
+ if ! [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then
+ echo "key has incorrect hash."
+ rm -f pub.ssl
+ return 1
+ fi
+ echo "done."
+}
+
+# Fetch a snapshot tag
+fetch_tag() {
+ rm -f snapshot.ssl tag.new
+
+ echo ${NDEBUG} "Fetching snapshot tag from ${SERVERNAME}... "
+ fetch ${QUIETFLAG} http://${SERVERNAME}/$1.ssl \
+ 2>${QUIETREDIR} || true
+ if ! [ -r $1.ssl ]; then
+ echo "failed."
+ return 1
+ fi
+
+ openssl rsautl -pubin -inkey pub.ssl -verify \
+ < $1.ssl > tag.new 2>${QUIETREDIR} || true
+ rm $1.ssl
+
+ if ! [ `wc -l < tag.new` = 1 ] ||
+ ! grep -qE "^portsnap\|[0-9]{10}\|[0-9a-f]{64}" tag.new; then
+ echo "invalid snapshot tag."
+ return 1
+ fi
+
+ echo "done."
+
+ SNAPSHOTDATE=`cut -f 2 -d '|' < tag.new`
+ SNAPSHOTHASH=`cut -f 3 -d '|' < tag.new`
+}
+
+# Sanity-check the date on a snapshot tag
+fetch_snapshot_tagsanity() {
+ if [ `date "+%s"` -gt `expr ${SNAPSHOTDATE} + 31536000` ]; then
+ echo "Snapshot appears to be more than a year old!"
+ echo "(Is the system clock correct?)"
+ echo "Cowardly refusing to proceed any further."
+ return 1
+ fi
+ if [ `date "+%s"` -lt `expr ${SNAPSHOTDATE} - 86400` ]; then
+ echo -n "Snapshot appears to have been created more than "
+ echo "one day into the future!"
+ echo "(Is the system clock correct?)"
+ echo "Cowardly refusing to proceed any further."
+ return 1
+ fi
+}
+
+# Sanity-check the date on a snapshot update tag
+fetch_update_tagsanity() {
+ fetch_snapshot_tagsanity || return 1
+
+ if [ ${OLDSNAPSHOTDATE} -gt ${SNAPSHOTDATE} ]; then
+ echo -n "Latest snapshot on server is "
+ echo "older than what we already have!"
+ echo -n "Cowardly refusing to downgrade from "
+ date -r ${OLDSNAPSHOTDATE}
+ echo "to `date -r ${SNAPSHOTDATE}`."
+ return 1
+ fi
+}
+
+# Compare old and new tags; return 1 if update is unnecessary
+fetch_update_neededp() {
+ if [ ${OLDSNAPSHOTDATE} -eq ${SNAPSHOTDATE} ]; then
+ echo -n "Latest snapshot on server matches "
+ echo "what we already have."
+ echo "No updates needed."
+ rm tag.new
+ return 1
+ fi
+ if [ ${OLDSNAPSHOTHASH} = ${SNAPSHOTHASH} ]; then
+ echo -n "Ports tree hasn't changed since "
+ echo "last snapshot."
+ echo "No updates needed."
+ rm tag.new
+ return 1
+ fi
+
+ return 0
+}
+
+# Fetch snapshot metadata file
+fetch_metadata() {
+ rm -f ${SNAPSHOTHASH} tINDEX.new
+
+ echo ${NDEBUG} "Fetching snapshot metadata... "
+ fetch ${QUIETFLAG} http://${SERVERNAME}/t/${SNAPSHOTHASH} \
+ 2>${QUIETREDIR} || return
+ if [ "`${SHA256} -q ${SNAPSHOTHASH}`" != ${SNAPSHOTHASH} ]; then
+ echo "snapshot metadata corrupt."
+ return 1
+ fi
+ mv ${SNAPSHOTHASH} tINDEX.new
+ echo "done."
+}
+
+# Warn user about bogus metadata
+fetch_metadata_freakout() {
+ echo
+ echo "Portsnap metadata is correctly signed, but contains"
+ echo "at least one line which appears bogus."
+ echo "Cowardly refusing to proceed any further."
+}
+
+# Sanity-check a snapshot metadata file
+fetch_metadata_sanity() {
+ if grep -qvE "^[0-9A-Z.]+\|[0-9a-f]{64}$" tINDEX.new; then
+ fetch_metadata_freakout
+ return 1
+ fi
+ if [ `look INDEX tINDEX.new | wc -l` != 1 ]; then
+ echo
+ echo "Portsnap metadata appears bogus."
+ echo "Cowardly refusing to proceed any further."
+ return 1
+ fi
+}
+
+# Take a list of ${oldhash}|${newhash} and output a list of needed patches
+fetch_make_patchlist() {
+ local IFS='|'
+ echo "" 1>${QUIETREDIR}
+ grep -vE "^([0-9a-f]{64})\|\1$" |
+ while read X Y; do
+ printf "Processing: $X $Y ...\r" 1>${QUIETREDIR}
+ if [ -f "files/${Y}.gz" -o ! -f "files/${X}.gz" ]; then continue; fi
+ echo "${X}|${Y}"
+ done
+ echo "" 1>${QUIETREDIR}
+}
+
+# Print user-friendly progress statistics
+fetch_progress() {
+ LNC=0
+ while read x; do
+ LNC=$(($LNC + 1))
+ if [ $(($LNC % 10)) = 0 ]; then
+ echo -n $LNC
+ elif [ $(($LNC % 2)) = 0 ]; then
+ echo -n .
+ fi
+ done
+ echo -n " "
+}
+
+pct_fmt()
+{
+ printf " \r"
+ printf "($1/$2) %02.2f%% " `echo "scale=4;$LNC / $TOTAL * 100"|bc`
+}
+
+fetch_progress_percent() {
+ TOTAL=$1
+ LNC=0
+ pct_fmt $LNC $TOTAL
+ while read x; do
+ LNC=$(($LNC + 1))
+ if [ $(($LNC % 100)) = 0 ]; then
+ pct_fmt $LNC $TOTAL
+ elif [ $(($LNC % 10)) = 0 ]; then
+ echo -n .
+ fi
+ done
+ pct_fmt $LNC $TOTAL
+ echo " done. "
+}
+
+# Sanity-check an index file
+fetch_index_sanity() {
+ if grep -qvE "^[-_+./@0-9A-Za-z]+\|[0-9a-f]{64}$" INDEX.new ||
+ fgrep -q "./" INDEX.new; then
+ fetch_metadata_freakout
+ return 1
+ fi
+}
+
+# Verify a list of files
+fetch_snapshot_verify() {
+ while read F; do
+ if [ "`gunzip -c snap/${F} | ${SHA256} -q`" != ${F} ]; then
+ echo "snapshot corrupt."
+ return 1
+ fi
+ done
+ return 0
+}
+
+# Fetch a snapshot tarball, extract, and verify.
+fetch_snapshot() {
+ while ! fetch_tag snapshot; do
+ fetch_pick_server || return 1
+ done
+ fetch_snapshot_tagsanity || return 1
+ fetch_metadata || return 1
+ fetch_metadata_sanity || return 1
+
+ rm -rf snap/
+
+# Don't ask fetch(1) to be quiet -- downloading a snapshot of ~ 35MB will
+# probably take a while, so the progrees reports that fetch(1) generates
+# will be useful for keeping the users' attention from drifting.
+ echo "Fetching snapshot generated at `date -r ${SNAPSHOTDATE}`:"
+ fetch -r http://${SERVERNAME}/s/${SNAPSHOTHASH}.tgz || return 1
+
+ echo -n "Extracting snapshot... "
+ tar -xz --numeric-owner -f ${SNAPSHOTHASH}.tgz snap/ || return 1
+ rm ${SNAPSHOTHASH}.tgz
+ echo "done."
+
+ echo -n "Verifying snapshot integrity... "
+# Verify the metadata files
+ cut -f 2 -d '|' tINDEX.new | fetch_snapshot_verify || return 1
+# Extract the index
+ rm -f INDEX.new
+ gunzip -c snap/`look INDEX tINDEX.new |
+ cut -f 2 -d '|'`.gz > INDEX.new
+ fetch_index_sanity || return 1
+# Verify the snapshot contents
+ cut -f 2 -d '|' INDEX.new | fetch_snapshot_verify || return 1
+ echo "done."
+
+# Move files into their proper locations
+ rm -f tag INDEX tINDEX
+ rm -rf files
+ mv tag.new tag
+ mv tINDEX.new tINDEX
+ mv INDEX.new INDEX
+ mv snap/ files/
+
+ return 0
+}
+
+# Update a compressed snapshot
+fetch_update() {
+ rm -f patchlist diff OLD NEW filelist INDEX.new
+
+ OLDSNAPSHOTDATE=`cut -f 2 -d '|' < tag`
+ OLDSNAPSHOTHASH=`cut -f 3 -d '|' < tag`
+
+ while ! fetch_tag latest; do
+ fetch_pick_server || return 1
+ done
+ fetch_update_tagsanity || return 1
+ fetch_update_neededp || return 0
+ fetch_metadata || return 1
+ fetch_metadata_sanity || return 1
+
+ echo -n "Updating from `date -r ${OLDSNAPSHOTDATE}` "
+ echo "to `date -r ${SNAPSHOTDATE}`."
+
+# Generate a list of wanted metadata patches
+ join -t '|' -o 1.2,2.2 tINDEX tINDEX.new |
+ fetch_make_patchlist > patchlist
+
+# Attempt to fetch metadata patches
+ echo -n "Fetching `wc -l < patchlist | tr -d ' '` "
+ echo ${NDEBUG} "metadata patches.${DDSTATS}"
+ tr '|' '-' < patchlist |
+ lam -s "tp/" - -s ".gz" |
+ xargs ${XARGST} ${PHTTPGET} ${SERVERNAME} \
+ 2>${STATSREDIR} | fetch_progress
+ echo "done."
+
+# Attempt to apply metadata patches
+ echo -n "Applying metadata patches... "
+ local oldifs="$IFS" IFS='|'
+ while read X Y; do
+ if [ ! -f "${X}-${Y}.gz" ]; then continue; fi
+ gunzip -c < ${X}-${Y}.gz > diff
+ gunzip -c < files/${X}.gz > OLD
+ cut -c 2- diff | join -t '|' -v 2 - OLD > ptmp
+ grep '^\+' diff | cut -c 2- |
+ sort -k 1,1 -t '|' -m - ptmp > NEW
+ if [ `${SHA256} -q NEW` = ${Y} ]; then
+ mv NEW files/${Y}
+ gzip -n files/${Y}
+ fi
+ rm -f diff OLD NEW ${X}-${Y}.gz ptmp
+ done < patchlist 2>${QUIETREDIR}
+ IFS="$oldifs"
+ echo "done."
+
+# Update metadata without patches
+ join -t '|' -v 2 tINDEX tINDEX.new |
+ cut -f 2 -d '|' /dev/stdin patchlist |
+ while read Y; do
+ if [ ! -f "files/${Y}.gz" ]; then
+ echo ${Y};
+ fi
+ done > filelist
+ echo -n "Fetching `wc -l < filelist | tr -d ' '` "
+ echo ${NDEBUG} "metadata files... "
+ lam -s "f/" - -s ".gz" < filelist |
+ xargs ${XARGST} ${PHTTPGET} ${SERVERNAME} \
+ 2>${QUIETREDIR}
+
+ while read Y; do
+ echo -n "Verifying ${Y}... " 1>${QUIETREDIR}
+ if [ `gunzip -c < ${Y}.gz | ${SHA256} -q` = ${Y} ]; then
+ mv ${Y}.gz files/${Y}.gz
+ else
+ echo "metadata is corrupt."
+ return 1
+ fi
+ echo "ok." 1>${QUIETREDIR}
+ done < filelist
+ echo "done."
+
+# Extract the index
+ echo -n "Extracting index... " 1>${QUIETREDIR}
+ gunzip -c files/`look INDEX tINDEX.new |
+ cut -f 2 -d '|'`.gz > INDEX.new
+ fetch_index_sanity || return 1
+
+# If we have decided to refuse certain updates, construct a hybrid index which
+# is equal to the old index for parts of the tree which we don't want to
+# update, and equal to the new index for parts of the tree which gets updates.
+# This means that we should always have a "complete snapshot" of the ports
+# tree -- with the caveat that it isn't actually a snapshot.
+ if [ ! -z "${REFUSE}" ]; then
+ echo "Refusing to download updates for ${REFUSE}" \
+ >${QUIETREDIR}
+
+ grep -Ev "${REFUSE}" INDEX.new > INDEX.tmp
+ grep -E "${REFUSE}" INDEX |
+ sort -m -k 1,1 -t '|' - INDEX.tmp > INDEX.new
+ rm -f INDEX.tmp
+ fi
+
+# Generate a list of wanted ports patches
+ echo -n "Generating list of wanted patches..." 1>${QUIETREDIR}
+ join -t '|' -o 1.2,2.2 INDEX INDEX.new |
+ fetch_make_patchlist > patchlist
+ echo " done." 1>${QUIETREDIR}
+
+# Attempt to fetch ports patches
+ patchcnt=`wc -l < patchlist | tr -d ' '`
+ echo -n "Fetching $patchcnt "
+ echo ${NDEBUG} "patches.${DDSTATS}"
+ echo " "
+ tr '|' '-' < patchlist | lam -s "bp/" - |
+ xargs ${XARGST} ${PHTTPGET} ${SERVERNAME} \
+ 2>${STATSREDIR} | fetch_progress_percent $patchcnt
+ echo "done."
+
+# Attempt to apply ports patches
+ PATCHCNT=`wc -l patchlist`
+ echo "Applying patches... "
+ local oldifs="$IFS" IFS='|'
+ I=0
+ while read X Y; do
+ I=$(($I + 1))
+ F="${X}-${Y}"
+ if [ ! -f "${F}" ]; then
+ printf " Skipping ${F} (${I} of ${PATCHCNT}).\r"
+ continue;
+ fi
+ echo " Processing ${F}..." 1>${QUIETREDIR}
+ gunzip -c < files/${X}.gz > OLD
+ ${BSPATCH} OLD NEW ${X}-${Y}
+ if [ `${SHA256} -q NEW` = ${Y} ]; then
+ mv NEW files/${Y}
+ gzip -n files/${Y}
+ fi
+ rm -f diff OLD NEW ${X}-${Y}
+ done < patchlist 2>${QUIETREDIR}
+ IFS="$oldifs"
+ echo "done."
+
+# Update ports without patches
+ join -t '|' -v 2 INDEX INDEX.new |
+ cut -f 2 -d '|' /dev/stdin patchlist |
+ while read Y; do
+ if [ ! -f "files/${Y}.gz" ]; then
+ echo ${Y};
+ fi
+ done > filelist
+ echo -n "Fetching `wc -l < filelist | tr -d ' '` "
+ echo ${NDEBUG} "new ports or files... "
+ lam -s "f/" - -s ".gz" < filelist |
+ xargs ${XARGST} ${PHTTPGET} ${SERVERNAME} \
+ 2>${QUIETREDIR}
+
+ I=0
+ while read Y; do
+ I=$(($I + 1))
+ printf " Processing ${Y} (${I} of ${PATCHCNT}).\r" 1>${QUIETREDIR}
+ if [ `gunzip -c < ${Y}.gz | ${SHA256} -q` = ${Y} ]; then
+ mv ${Y}.gz files/${Y}.gz
+ else
+ echo "snapshot is corrupt."
+ return 1
+ fi
+ done < filelist
+ echo "done."
+
+# Remove files which are no longer needed
+ cut -f 2 -d '|' tINDEX INDEX | sort -u > oldfiles
+ cut -f 2 -d '|' tINDEX.new INDEX.new | sort -u | comm -13 - oldfiles |
+ lam -s "files/" - -s ".gz" | xargs rm -f
+ rm patchlist filelist oldfiles
+
+# We're done!
+ mv INDEX.new INDEX
+ mv tINDEX.new tINDEX
+ mv tag.new tag
+
+ return 0
+}
+
+# Do the actual work involved in "fetch" / "cron".
+fetch_run() {
+ fetch_pick_server_init && fetch_pick_server
+
+ while ! fetch_key; do
+ fetch_pick_server || return 1
+ done
+
+ if ! [ -d files -a -r tag -a -r INDEX -a -r tINDEX ]; then
+ fetch_snapshot || return 1
+ fi
+ fetch_update || return 1
+}
+
+# Build a ports INDEX file
+extract_make_index() {
+ if ! look $1 ${WORKDIR}/tINDEX > /dev/null; then
+ echo -n "$1 not provided by portsnap server; "
+ echo "$2 not being generated."
+ else
+ gunzip -c "${WORKDIR}/files/`look $1 ${WORKDIR}/tINDEX |
+ cut -f 2 -d '|'`.gz" |
+ cat - ${LOCALDESC} |
+ ${MKINDEX} /dev/stdin > ${PORTSDIR}/$2
+ fi
+}
+
+# Create INDEX, INDEX-5, INDEX-6
+extract_indices() {
+ echo -n "Building new INDEX files... "
+ for PAIR in ${INDEXPAIRS}; do
+ INDEXFILE=`echo ${PAIR} | cut -f 1 -d '|'`
+ DESCRIBEFILE=`echo ${PAIR} | cut -f 2 -d '|'`
+ extract_make_index ${DESCRIBEFILE} ${INDEXFILE} || return 1
+ done
+ echo "done."
+}
+
+# Create .portsnap.INDEX; if we are REFUSEing to touch certain directories,
+# merge the values from any exiting .portsnap.INDEX file.
+extract_metadata() {
+ if [ -z "${REFUSE}" ]; then
+ sort ${WORKDIR}/INDEX > ${PORTSDIR}/.portsnap.INDEX
+ elif [ -f ${PORTSDIR}/.portsnap.INDEX ]; then
+ grep -E "${REFUSE}" ${PORTSDIR}/.portsnap.INDEX \
+ > ${PORTSDIR}/.portsnap.INDEX.tmp
+ grep -vE "${REFUSE}" ${WORKDIR}/INDEX | sort |
+ sort -m - ${PORTSDIR}/.portsnap.INDEX.tmp \
+ > ${PORTSDIR}/.portsnap.INDEX
+ rm -f ${PORTSDIR}/.portsnap.INDEX.tmp
+ else
+ grep -vE "${REFUSE}" ${WORKDIR}/INDEX | sort \
+ > ${PORTSDIR}/.portsnap.INDEX
+ fi
+}
+
+# Do the actual work involved in "extract"
+extract_run() {
+ local oldifs="$IFS" IFS='|'
+ mkdir -p ${PORTSDIR} || return 1
+
+ if !
+ if ! [ -z "${EXTRACTPATH}" ]; then
+ grep "^${EXTRACTPATH}" ${WORKDIR}/INDEX
+ elif ! [ -z "${REFUSE}" ]; then
+ grep -vE "${REFUSE}" ${WORKDIR}/INDEX
+ else
+ cat ${WORKDIR}/INDEX
+ fi | while read FILE HASH; do
+ echo ${PORTSDIR}/${FILE}
+ if ! [ -s "${WORKDIR}/files/${HASH}.gz" ]; then
+ echo "files/${HASH}.gz not found -- snapshot corrupt."
+ return 1
+ fi
+ case ${FILE} in
+ */)
+ rm -rf ${PORTSDIR}/${FILE%/}
+ mkdir -p ${PORTSDIR}/${FILE}
+ tar -xz --numeric-owner -f ${WORKDIR}/files/${HASH}.gz \
+ -C ${PORTSDIR}/${FILE}
+ ;;
+ *)
+ rm -f ${PORTSDIR}/${FILE}
+ tar -xz --numeric-owner -f ${WORKDIR}/files/${HASH}.gz \
+ -C ${PORTSDIR} ${FILE}
+ ;;
+ esac
+ done; then
+ return 1
+ fi
+ if [ ! -z "${EXTRACTPATH}" ]; then
+ return 0;
+ fi
+
+ IFS="$oldifs"
+
+ extract_metadata
+ extract_indices
+}
+
+update_run_extract() {
+ local IFS='|'
+
+# Install new files
+ echo "Extracting new files:"
+ if !
+ if ! [ -z "${REFUSE}" ]; then
+ grep -vE "${REFUSE}" ${WORKDIR}/INDEX | sort
+ else
+ sort ${WORKDIR}/INDEX
+ fi |
+ comm -13 ${PORTSDIR}/.portsnap.INDEX - |
+ while read FILE HASH; do
+ echo ${PORTSDIR}/${FILE}
+ if ! [ -s "${WORKDIR}/files/${HASH}.gz" ]; then
+ echo "files/${HASH}.gz not found -- snapshot corrupt."
+ return 1
+ fi
+ case ${FILE} in
+ */)
+ mkdir -p ${PORTSDIR}/${FILE}
+ tar -xz --numeric-owner -f ${WORKDIR}/files/${HASH}.gz \
+ -C ${PORTSDIR}/${FILE}
+ ;;
+ *)
+ tar -xz --numeric-owner -f ${WORKDIR}/files/${HASH}.gz \
+ -C ${PORTSDIR} ${FILE}
+ ;;
+ esac
+ done; then
+ return 1
+ fi
+}
+
+# Do the actual work involved in "update"
+update_run() {
+ if ! [ -z "${INDEXONLY}" ]; then
+ extract_indices >/dev/null || return 1
+ return 0
+ fi
+
+ if sort ${WORKDIR}/INDEX |
+ cmp -s ${PORTSDIR}/.portsnap.INDEX -; then
+ echo "Ports tree is already up to date."
+ return 0
+ fi
+
+# If we are REFUSEing to touch certain directories, don't remove files
+# from those directories (even if they are out of date)
+ echo -n "Removing old files and directories... "
+ if ! [ -z "${REFUSE}" ]; then
+ sort ${WORKDIR}/INDEX |
+ comm -23 ${PORTSDIR}/.portsnap.INDEX - | cut -f 1 -d '|' |
+ grep -vE "${REFUSE}" |
+ lam -s "${PORTSDIR}/" - |
+ sed -e 's|/$||' | xargs rm -rf
+ else
+ sort ${WORKDIR}/INDEX |
+ comm -23 ${PORTSDIR}/.portsnap.INDEX - | cut -f 1 -d '|' |
+ lam -s "${PORTSDIR}/" - |
+ sed -e 's|/$||' | xargs rm -rf
+ fi
+ echo "done."
+
+ update_run_extract || return 1
+ extract_metadata
+ extract_indices
+}
+
+#### Main functions -- call parameter-handling and core functions
+
+# Using the command line, configuration file, and defaults,
+# set all the parameters which are needed later.
+get_params() {
+ init_params
+ parse_cmdline $@
+ sanity_conffile
+ default_conffile
+ parse_conffile
+ default_params
+}
+
+# Fetch command. Make sure that we're being called
+# interactively, then run fetch_check_params and fetch_run
+cmd_fetch() {
+ if [ "${INTERACTIVE}" != "YES" ]; then
+ echo -n "`basename $0` fetch should not "
+ echo "be run non-interactively."
+ echo "Run `basename $0` cron instead"
+ exit 1
+ fi
+ fetch_check_params
+ fetch_run || exit 1
+}
+
+# Cron command. Make sure the parameters are sensible; wait
+# rand(3600) seconds; then fetch updates. While fetching updates,
+# send output to a temporary file; only print that file if the
+# fetching failed.
+cmd_cron() {
+ fetch_check_params
+ sleep `jot -r 1 0 3600`
+
+ TMPFILE=`mktemp /tmp/portsnap.XXXXXX` || exit 1
+ if ! fetch_run >> ${TMPFILE}; then
+ cat ${TMPFILE}
+ rm ${TMPFILE}
+ exit 1
+ fi
+
+ rm ${TMPFILE}
+}
+
+# Extract command. Make sure the parameters are sensible,
+# then extract the ports tree (or part thereof).
+cmd_extract() {
+ extract_check_params
+ extract_run || exit 1
+}
+
+# Update command. Make sure the parameters are sensible,
+# then update the ports tree.
+cmd_update() {
+ update_check_params
+ update_run || exit 1
+}
+
+# Auto command. Run 'fetch' or 'cron' depending on
+# whether stdin is a terminal; then run 'update' or
+# 'extract' depending on whether ${PORTSDIR} exists.
+cmd_auto() {
+ if [ "${INTERACTIVE}" = "YES" ]; then
+ cmd_fetch
+ else
+ cmd_cron
+ fi
+ if [ -r ${PORTSDIR}/.portsnap.INDEX ]; then
+ cmd_update
+ else
+ cmd_extract
+ fi
+}
+
+#### Entry point
+
+# Make sure we find utilities from the base system
+export PATH=/sbin:/bin:/usr/sbin:/usr/bin:${PATH}
+
+# Set LC_ALL in order to avoid problems with character ranges like [A-Z].
+export LC_ALL=C
+
+get_params $@
+for COMMAND in ${COMMANDS}; do
+ cmd_${COMMAND}
+done
diff --git a/usr.sbin/powerd/Makefile b/usr.sbin/powerd/Makefile
new file mode 100644
index 0000000..4434dcf
--- /dev/null
+++ b/usr.sbin/powerd/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= powerd
+MAN= powerd.8
+
+LIBADD= util
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/powerd/Makefile.depend b/usr.sbin/powerd/Makefile.depend
new file mode 100644
index 0000000..58f9a33
--- /dev/null
+++ b/usr.sbin/powerd/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/powerd/powerd.8 b/usr.sbin/powerd/powerd.8
new file mode 100644
index 0000000..853282c
--- /dev/null
+++ b/usr.sbin/powerd/powerd.8
@@ -0,0 +1,163 @@
+.\" Copyright (c) 2005 Nate Lawson
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 July 4, 2013
+.Dt POWERD 8
+.Os
+.Sh NAME
+.Nm powerd
+.Nd "system power control utility"
+.Sh SYNOPSIS
+.Nm
+.Op Fl a Ar mode
+.Op Fl b Ar mode
+.Op Fl i Ar percent
+.Op Fl m Ar freq
+.Op Fl M Ar freq
+.Op Fl n Ar mode
+.Op Fl p Ar ival
+.Op Fl P Ar pidfile
+.Op Fl r Ar percent
+.Op Fl v
+.Sh DESCRIPTION
+The
+.Nm
+utility monitors the system state and sets various power control options
+accordingly.
+It offers power-saving modes that can be
+individually selected for operation on AC power or batteries.
+.Bl -tag -width ".Ar hiadaptive"
+.It Ar maximum
+Choose the highest performance values.
+May be abbreviated as
+.Ar max .
+.It Ar minimum
+Choose the lowest performance values to get the most power savings.
+May be abbreviated as
+.Ar min .
+.It Ar adaptive
+Attempt to strike a balance by degrading performance when the system
+appears idle and increasing it when the system is busy.
+It offers a good balance between a small performance loss for greatly
+increased power savings.
+May be abbreviated as
+.Ar adp .
+.It Ar hiadaptive
+Like
+.Ar adaptive
+mode, but tuned for systems where performance and interactivity are
+more important than power consumption.
+It increases frequency faster, reduces frequency less aggressively, and
+will maintain full frequency for longer.
+May be abbreviated as
+.Ar hadp .
+.El
+.Pp
+The default mode is
+.Ar adaptive
+for battery power and
+.Ar hiadaptive
+for the rest.
+.Pp
+.Nm
+recognizes these runtime options:
+.Bl -tag -width ".Fl r Ar percent"
+.It Fl a Ar mode
+Selects the
+.Ar mode
+to use while on AC power.
+.It Fl b Ar mode
+Selects the
+.Ar mode
+to use while on battery power.
+.It Fl i Ar percent
+Specifies the CPU load percent level when adaptive
+mode should begin to degrade performance to save power.
+The default is 50% or lower.
+.It Fl m Ar freq
+Specifies the minimum frequency to throttle down to.
+.It Fl M Ar freq
+Specifies the maximum frequency to throttle up to.
+.It Fl n Ar mode
+Selects the
+.Ar mode
+to use normally when the AC line state is unknown.
+.It Fl p Ar ival
+Specifies a different polling interval (in milliseconds) for AC line state
+and system idle levels.
+The default is 250 ms.
+.It Fl P Ar pidfile
+Specifies an alternative file in which the process ID should be stored.
+The default is
+.Pa /var/run/powerd.pid .
+.It Fl r Ar percent
+Specifies the CPU load percent level where adaptive
+mode should consider the CPU running and increase performance.
+The default is 75% or higher.
+.It Fl v
+Verbose mode.
+Messages about power changes will be printed to stdout and
+.Nm
+will operate in the foreground.
+.El
+.Sh SEE ALSO
+.Xr acpi 4 ,
+.Xr apm 4 ,
+.Xr cpufreq 4
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 6.0 .
+.Sh AUTHORS
+.An -nosplit
+.An Colin Percival
+first wrote
+.Nm estctrl ,
+the utility that
+.Nm
+is based on.
+.An Nate Lawson
+then updated it for
+.Xr cpufreq 4 ,
+added features, and wrote this manual page.
+.Sh BUGS
+The
+.Nm
+utility should also power down idle disks and other components besides the CPU.
+.Pp
+If
+.Nm
+is used with
+.Pa power_profile ,
+they may override each other.
+.Pp
+The
+.Nm
+utility
+should probably use the
+.Xr devctl 4
+interface instead of polling for AC line state.
diff --git a/usr.sbin/powerd/powerd.c b/usr.sbin/powerd/powerd.c
new file mode 100644
index 0000000..367a31f
--- /dev/null
+++ b/usr.sbin/powerd/powerd.c
@@ -0,0 +1,800 @@
+/*-
+ * Copyright (c) 2004 Colin Percival
+ * Copyright (c) 2005 Nate Lawson
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted providing that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/sysctl.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/un.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libutil.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#ifdef __i386__
+#define USE_APM
+#endif
+
+#ifdef USE_APM
+#include <machine/apm_bios.h>
+#endif
+
+#define DEFAULT_ACTIVE_PERCENT 75
+#define DEFAULT_IDLE_PERCENT 50
+#define DEFAULT_POLL_INTERVAL 250 /* Poll interval in milliseconds */
+
+typedef enum {
+ MODE_MIN,
+ MODE_ADAPTIVE,
+ MODE_HIADAPTIVE,
+ MODE_MAX,
+} modes_t;
+
+typedef enum {
+ SRC_AC,
+ SRC_BATTERY,
+ SRC_UNKNOWN,
+} power_src_t;
+
+static const char *modes[] = {
+ "AC",
+ "battery",
+ "unknown"
+};
+
+#define ACPIAC "hw.acpi.acline"
+#define PMUAC "dev.pmu.0.acline"
+#define APMDEV "/dev/apm"
+#define DEVDPIPE "/var/run/devd.pipe"
+#define DEVCTL_MAXBUF 1024
+
+static int read_usage_times(int *load);
+static int read_freqs(int *numfreqs, int **freqs, int **power,
+ int minfreq, int maxfreq);
+static int set_freq(int freq);
+static void acline_init(void);
+static void acline_read(void);
+static int devd_init(void);
+static void devd_close(void);
+static void handle_sigs(int sig);
+static void parse_mode(char *arg, int *mode, int ch);
+static void usage(void);
+
+/* Sysctl data structures. */
+static int cp_times_mib[2];
+static int freq_mib[4];
+static int levels_mib[4];
+static int acline_mib[4];
+static size_t acline_mib_len;
+
+/* Configuration */
+static int cpu_running_mark;
+static int cpu_idle_mark;
+static int poll_ival;
+static int vflag;
+
+static volatile sig_atomic_t exit_requested;
+static power_src_t acline_status;
+static enum {
+ ac_none,
+ ac_sysctl,
+ ac_acpi_devd,
+#ifdef USE_APM
+ ac_apm,
+#endif
+} acline_mode;
+#ifdef USE_APM
+static int apm_fd = -1;
+#endif
+static int devd_pipe = -1;
+
+#define DEVD_RETRY_INTERVAL 60 /* seconds */
+static struct timeval tried_devd;
+
+/*
+ * This function returns summary load of all CPUs. It was made so
+ * intentionally to not reduce performance in scenarios when several
+ * threads are processing requests as a pipeline -- running one at
+ * a time on different CPUs and waiting for each other.
+ */
+static int
+read_usage_times(int *load)
+{
+ static long *cp_times = NULL, *cp_times_old = NULL;
+ static int ncpus = 0;
+ size_t cp_times_len;
+ int error, cpu, i, total;
+
+ if (cp_times == NULL) {
+ cp_times_len = 0;
+ error = sysctl(cp_times_mib, 2, NULL, &cp_times_len, NULL, 0);
+ if (error)
+ return (error);
+ if ((cp_times = malloc(cp_times_len)) == NULL)
+ return (errno);
+ if ((cp_times_old = malloc(cp_times_len)) == NULL) {
+ free(cp_times);
+ cp_times = NULL;
+ return (errno);
+ }
+ ncpus = cp_times_len / (sizeof(long) * CPUSTATES);
+ }
+
+ cp_times_len = sizeof(long) * CPUSTATES * ncpus;
+ error = sysctl(cp_times_mib, 2, cp_times, &cp_times_len, NULL, 0);
+ if (error)
+ return (error);
+
+ if (load) {
+ *load = 0;
+ for (cpu = 0; cpu < ncpus; cpu++) {
+ total = 0;
+ for (i = 0; i < CPUSTATES; i++) {
+ total += cp_times[cpu * CPUSTATES + i] -
+ cp_times_old[cpu * CPUSTATES + i];
+ }
+ if (total == 0)
+ continue;
+ *load += 100 - (cp_times[cpu * CPUSTATES + CP_IDLE] -
+ cp_times_old[cpu * CPUSTATES + CP_IDLE]) * 100 / total;
+ }
+ }
+
+ memcpy(cp_times_old, cp_times, cp_times_len);
+
+ return (0);
+}
+
+static int
+read_freqs(int *numfreqs, int **freqs, int **power, int minfreq, int maxfreq)
+{
+ char *freqstr, *p, *q;
+ int i, j;
+ size_t len = 0;
+
+ if (sysctl(levels_mib, 4, NULL, &len, NULL, 0))
+ return (-1);
+ if ((freqstr = malloc(len)) == NULL)
+ return (-1);
+ if (sysctl(levels_mib, 4, freqstr, &len, NULL, 0))
+ return (-1);
+
+ *numfreqs = 1;
+ for (p = freqstr; *p != '\0'; p++)
+ if (*p == ' ')
+ (*numfreqs)++;
+
+ if ((*freqs = malloc(*numfreqs * sizeof(int))) == NULL) {
+ free(freqstr);
+ return (-1);
+ }
+ if ((*power = malloc(*numfreqs * sizeof(int))) == NULL) {
+ free(freqstr);
+ free(*freqs);
+ return (-1);
+ }
+ for (i = 0, j = 0, p = freqstr; i < *numfreqs; i++) {
+ q = strchr(p, ' ');
+ if (q != NULL)
+ *q = '\0';
+ if (sscanf(p, "%d/%d", &(*freqs)[j], &(*power)[i]) != 2) {
+ free(freqstr);
+ free(*freqs);
+ free(*power);
+ return (-1);
+ }
+ if (((*freqs)[j] >= minfreq || minfreq == -1) &&
+ ((*freqs)[j] <= maxfreq || maxfreq == -1))
+ j++;
+ p = q + 1;
+ }
+
+ *numfreqs = j;
+ if ((*freqs = realloc(*freqs, *numfreqs * sizeof(int))) == NULL) {
+ free(freqstr);
+ free(*freqs);
+ free(*power);
+ return (-1);
+ }
+
+ free(freqstr);
+ return (0);
+}
+
+static int
+get_freq(void)
+{
+ size_t len;
+ int curfreq;
+
+ len = sizeof(curfreq);
+ if (sysctl(freq_mib, 4, &curfreq, &len, NULL, 0) != 0) {
+ if (vflag)
+ warn("error reading current CPU frequency");
+ curfreq = 0;
+ }
+ return (curfreq);
+}
+
+static int
+set_freq(int freq)
+{
+
+ if (sysctl(freq_mib, 4, NULL, NULL, &freq, sizeof(freq))) {
+ if (errno != EPERM)
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+get_freq_id(int freq, int *freqs, int numfreqs)
+{
+ int i = 1;
+
+ while (i < numfreqs) {
+ if (freqs[i] < freq)
+ break;
+ i++;
+ }
+ return (i - 1);
+}
+
+/*
+ * Try to use ACPI to find the AC line status. If this fails, fall back
+ * to APM. If nothing succeeds, we'll just run in default mode.
+ */
+static void
+acline_init(void)
+{
+ acline_mib_len = 4;
+ acline_status = SRC_UNKNOWN;
+
+ if (sysctlnametomib(ACPIAC, acline_mib, &acline_mib_len) == 0) {
+ acline_mode = ac_sysctl;
+ if (vflag)
+ warnx("using sysctl for AC line status");
+#if __powerpc__
+ } else if (sysctlnametomib(PMUAC, acline_mib, &acline_mib_len) == 0) {
+ acline_mode = ac_sysctl;
+ if (vflag)
+ warnx("using sysctl for AC line status");
+#endif
+#ifdef USE_APM
+ } else if ((apm_fd = open(APMDEV, O_RDONLY)) >= 0) {
+ if (vflag)
+ warnx("using APM for AC line status");
+ acline_mode = ac_apm;
+#endif
+ } else {
+ warnx("unable to determine AC line status");
+ acline_mode = ac_none;
+ }
+}
+
+static void
+acline_read(void)
+{
+ if (acline_mode == ac_acpi_devd) {
+ char buf[DEVCTL_MAXBUF], *ptr;
+ ssize_t rlen;
+ int notify;
+
+ rlen = read(devd_pipe, buf, sizeof(buf));
+ if (rlen == 0 || (rlen < 0 && errno != EWOULDBLOCK)) {
+ if (vflag)
+ warnx("lost devd connection, switching to sysctl");
+ devd_close();
+ acline_mode = ac_sysctl;
+ /* FALLTHROUGH */
+ }
+ if (rlen > 0 &&
+ (ptr = strstr(buf, "system=ACPI")) != NULL &&
+ (ptr = strstr(ptr, "subsystem=ACAD")) != NULL &&
+ (ptr = strstr(ptr, "notify=")) != NULL &&
+ sscanf(ptr, "notify=%x", &notify) == 1)
+ acline_status = (notify ? SRC_AC : SRC_BATTERY);
+ }
+ if (acline_mode == ac_sysctl) {
+ int acline;
+ size_t len;
+
+ len = sizeof(acline);
+ if (sysctl(acline_mib, acline_mib_len, &acline, &len,
+ NULL, 0) == 0)
+ acline_status = (acline ? SRC_AC : SRC_BATTERY);
+ else
+ acline_status = SRC_UNKNOWN;
+ }
+#ifdef USE_APM
+ if (acline_mode == ac_apm) {
+ struct apm_info info;
+
+ if (ioctl(apm_fd, APMIO_GETINFO, &info) == 0) {
+ acline_status = (info.ai_acline ? SRC_AC : SRC_BATTERY);
+ } else {
+ close(apm_fd);
+ apm_fd = -1;
+ acline_mode = ac_none;
+ acline_status = SRC_UNKNOWN;
+ }
+ }
+#endif
+ /* try to (re)connect to devd */
+ if (acline_mode == ac_sysctl) {
+ struct timeval now;
+
+ gettimeofday(&now, NULL);
+ if (now.tv_sec > tried_devd.tv_sec + DEVD_RETRY_INTERVAL) {
+ if (devd_init() >= 0) {
+ if (vflag)
+ warnx("using devd for AC line status");
+ acline_mode = ac_acpi_devd;
+ }
+ tried_devd = now;
+ }
+ }
+}
+
+static int
+devd_init(void)
+{
+ struct sockaddr_un devd_addr;
+
+ bzero(&devd_addr, sizeof(devd_addr));
+ if ((devd_pipe = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) {
+ if (vflag)
+ warn("%s(): socket()", __func__);
+ return (-1);
+ }
+
+ devd_addr.sun_family = PF_LOCAL;
+ strlcpy(devd_addr.sun_path, DEVDPIPE, sizeof(devd_addr.sun_path));
+ if (connect(devd_pipe, (struct sockaddr *)&devd_addr,
+ sizeof(devd_addr)) == -1) {
+ if (vflag)
+ warn("%s(): connect()", __func__);
+ close(devd_pipe);
+ devd_pipe = -1;
+ return (-1);
+ }
+
+ if (fcntl(devd_pipe, F_SETFL, O_NONBLOCK) == -1) {
+ if (vflag)
+ warn("%s(): fcntl()", __func__);
+ close(devd_pipe);
+ return (-1);
+ }
+
+ return (devd_pipe);
+}
+
+static void
+devd_close(void)
+{
+
+ close(devd_pipe);
+ devd_pipe = -1;
+}
+
+static void
+parse_mode(char *arg, int *mode, int ch)
+{
+
+ if (strcmp(arg, "minimum") == 0 || strcmp(arg, "min") == 0)
+ *mode = MODE_MIN;
+ else if (strcmp(arg, "maximum") == 0 || strcmp(arg, "max") == 0)
+ *mode = MODE_MAX;
+ else if (strcmp(arg, "adaptive") == 0 || strcmp(arg, "adp") == 0)
+ *mode = MODE_ADAPTIVE;
+ else if (strcmp(arg, "hiadaptive") == 0 || strcmp(arg, "hadp") == 0)
+ *mode = MODE_HIADAPTIVE;
+ else
+ errx(1, "bad option: -%c %s", (char)ch, optarg);
+}
+
+static void
+handle_sigs(int __unused sig)
+{
+
+ exit_requested = 1;
+}
+
+static void
+usage(void)
+{
+
+ fprintf(stderr,
+"usage: powerd [-v] [-a mode] [-b mode] [-i %%] [-m freq] [-M freq] [-n mode] [-p ival] [-r %%] [-P pidfile]\n");
+ exit(1);
+}
+
+int
+main(int argc, char * argv[])
+{
+ struct timeval timeout;
+ fd_set fdset;
+ int nfds;
+ struct pidfh *pfh = NULL;
+ const char *pidfile = NULL;
+ int freq, curfreq, initfreq, *freqs, i, j, *mwatts, numfreqs, load;
+ int minfreq = -1, maxfreq = -1;
+ int ch, mode, mode_ac, mode_battery, mode_none, idle, to;
+ uint64_t mjoules_used;
+ size_t len;
+
+ /* Default mode for all AC states is adaptive. */
+ mode_ac = mode_none = MODE_HIADAPTIVE;
+ mode_battery = MODE_ADAPTIVE;
+ cpu_running_mark = DEFAULT_ACTIVE_PERCENT;
+ cpu_idle_mark = DEFAULT_IDLE_PERCENT;
+ poll_ival = DEFAULT_POLL_INTERVAL;
+ mjoules_used = 0;
+ vflag = 0;
+
+ /* User must be root to control frequencies. */
+ if (geteuid() != 0)
+ errx(1, "must be root to run");
+
+ while ((ch = getopt(argc, argv, "a:b:i:m:M:n:p:P:r:v")) != -1)
+ switch (ch) {
+ case 'a':
+ parse_mode(optarg, &mode_ac, ch);
+ break;
+ case 'b':
+ parse_mode(optarg, &mode_battery, ch);
+ break;
+ case 'i':
+ cpu_idle_mark = atoi(optarg);
+ if (cpu_idle_mark < 0 || cpu_idle_mark > 100) {
+ warnx("%d is not a valid percent",
+ cpu_idle_mark);
+ usage();
+ }
+ break;
+ case 'm':
+ minfreq = atoi(optarg);
+ if (minfreq < 0) {
+ warnx("%d is not a valid CPU frequency",
+ minfreq);
+ usage();
+ }
+ break;
+ case 'M':
+ maxfreq = atoi(optarg);
+ if (maxfreq < 0) {
+ warnx("%d is not a valid CPU frequency",
+ maxfreq);
+ usage();
+ }
+ break;
+ case 'n':
+ parse_mode(optarg, &mode_none, ch);
+ break;
+ case 'p':
+ poll_ival = atoi(optarg);
+ if (poll_ival < 5) {
+ warnx("poll interval is in units of ms");
+ usage();
+ }
+ break;
+ case 'P':
+ pidfile = optarg;
+ break;
+ case 'r':
+ cpu_running_mark = atoi(optarg);
+ if (cpu_running_mark <= 0 || cpu_running_mark > 100) {
+ warnx("%d is not a valid percent",
+ cpu_running_mark);
+ usage();
+ }
+ break;
+ case 'v':
+ vflag = 1;
+ break;
+ default:
+ usage();
+ }
+
+ mode = mode_none;
+
+ /* Poll interval is in units of ms. */
+ poll_ival *= 1000;
+
+ /* Look up various sysctl MIBs. */
+ len = 2;
+ if (sysctlnametomib("kern.cp_times", cp_times_mib, &len))
+ err(1, "lookup kern.cp_times");
+ len = 4;
+ if (sysctlnametomib("dev.cpu.0.freq", freq_mib, &len))
+ err(EX_UNAVAILABLE, "no cpufreq(4) support -- aborting");
+ len = 4;
+ if (sysctlnametomib("dev.cpu.0.freq_levels", levels_mib, &len))
+ err(1, "lookup freq_levels");
+
+ /* Check if we can read the load and supported freqs. */
+ if (read_usage_times(NULL))
+ err(1, "read_usage_times");
+ if (read_freqs(&numfreqs, &freqs, &mwatts, minfreq, maxfreq))
+ err(1, "error reading supported CPU frequencies");
+ if (numfreqs == 0)
+ errx(1, "no CPU frequencies in user-specified range");
+
+ /* Run in the background unless in verbose mode. */
+ if (!vflag) {
+ pid_t otherpid;
+
+ pfh = pidfile_open(pidfile, 0600, &otherpid);
+ if (pfh == NULL) {
+ if (errno == EEXIST) {
+ errx(1, "powerd already running, pid: %d",
+ otherpid);
+ }
+ warn("cannot open pid file");
+ }
+ if (daemon(0, 0) != 0) {
+ warn("cannot enter daemon mode, exiting");
+ pidfile_remove(pfh);
+ exit(EXIT_FAILURE);
+
+ }
+ pidfile_write(pfh);
+ }
+
+ /* Decide whether to use ACPI or APM to read the AC line status. */
+ acline_init();
+
+ /*
+ * Exit cleanly on signals.
+ */
+ signal(SIGINT, handle_sigs);
+ signal(SIGTERM, handle_sigs);
+
+ freq = initfreq = curfreq = get_freq();
+ i = get_freq_id(curfreq, freqs, numfreqs);
+ if (freq < 1)
+ freq = 1;
+
+ /*
+ * If we are in adaptive mode and the current frequency is outside the
+ * user-defined range, adjust it to be within the user-defined range.
+ */
+ acline_read();
+ if (acline_status > SRC_UNKNOWN)
+ errx(1, "invalid AC line status %d", acline_status);
+ if ((acline_status == SRC_AC &&
+ (mode_ac == MODE_ADAPTIVE || mode_ac == MODE_HIADAPTIVE)) ||
+ (acline_status == SRC_BATTERY &&
+ (mode_battery == MODE_ADAPTIVE || mode_battery == MODE_HIADAPTIVE)) ||
+ (acline_status == SRC_UNKNOWN &&
+ (mode_none == MODE_ADAPTIVE || mode_none == MODE_HIADAPTIVE))) {
+ /* Read the current frequency. */
+ len = sizeof(curfreq);
+ if (sysctl(freq_mib, 4, &curfreq, &len, NULL, 0) != 0) {
+ if (vflag)
+ warn("error reading current CPU frequency");
+ }
+ if (curfreq < freqs[numfreqs - 1]) {
+ if (vflag) {
+ printf("CPU frequency is below user-defined "
+ "minimum; changing frequency to %d "
+ "MHz\n", freqs[numfreqs - 1]);
+ }
+ if (set_freq(freqs[numfreqs - 1]) != 0) {
+ warn("error setting CPU freq %d",
+ freqs[numfreqs - 1]);
+ }
+ } else if (curfreq > freqs[0]) {
+ if (vflag) {
+ printf("CPU frequency is above user-defined "
+ "maximum; changing frequency to %d "
+ "MHz\n", freqs[0]);
+ }
+ if (set_freq(freqs[0]) != 0) {
+ warn("error setting CPU freq %d",
+ freqs[0]);
+ }
+ }
+ }
+
+ idle = 0;
+ /* Main loop. */
+ for (;;) {
+ FD_ZERO(&fdset);
+ if (devd_pipe >= 0) {
+ FD_SET(devd_pipe, &fdset);
+ nfds = devd_pipe + 1;
+ } else {
+ nfds = 0;
+ }
+ if (mode == MODE_HIADAPTIVE || idle < 120)
+ to = poll_ival;
+ else if (idle < 360)
+ to = poll_ival * 2;
+ else
+ to = poll_ival * 4;
+ timeout.tv_sec = to / 1000000;
+ timeout.tv_usec = to % 1000000;
+ select(nfds, &fdset, NULL, &fdset, &timeout);
+
+ /* If the user requested we quit, print some statistics. */
+ if (exit_requested) {
+ if (vflag && mjoules_used != 0)
+ printf("total joules used: %u.%03u\n",
+ (u_int)(mjoules_used / 1000),
+ (int)mjoules_used % 1000);
+ break;
+ }
+
+ /* Read the current AC status and record the mode. */
+ acline_read();
+ switch (acline_status) {
+ case SRC_AC:
+ mode = mode_ac;
+ break;
+ case SRC_BATTERY:
+ mode = mode_battery;
+ break;
+ case SRC_UNKNOWN:
+ mode = mode_none;
+ break;
+ default:
+ errx(1, "invalid AC line status %d", acline_status);
+ }
+
+ /* Read the current frequency. */
+ if (idle % 32 == 0) {
+ if ((curfreq = get_freq()) == 0)
+ continue;
+ i = get_freq_id(curfreq, freqs, numfreqs);
+ }
+ idle++;
+ if (vflag) {
+ /* Keep a sum of all power actually used. */
+ if (mwatts[i] != -1)
+ mjoules_used +=
+ (mwatts[i] * (poll_ival / 1000)) / 1000;
+ }
+
+ /* Always switch to the lowest frequency in min mode. */
+ if (mode == MODE_MIN) {
+ freq = freqs[numfreqs - 1];
+ if (curfreq != freq) {
+ if (vflag) {
+ printf("now operating on %s power; "
+ "changing frequency to %d MHz\n",
+ modes[acline_status], freq);
+ }
+ idle = 0;
+ if (set_freq(freq) != 0) {
+ warn("error setting CPU freq %d",
+ freq);
+ continue;
+ }
+ }
+ continue;
+ }
+
+ /* Always switch to the highest frequency in max mode. */
+ if (mode == MODE_MAX) {
+ freq = freqs[0];
+ if (curfreq != freq) {
+ if (vflag) {
+ printf("now operating on %s power; "
+ "changing frequency to %d MHz\n",
+ modes[acline_status], freq);
+ }
+ idle = 0;
+ if (set_freq(freq) != 0) {
+ warn("error setting CPU freq %d",
+ freq);
+ continue;
+ }
+ }
+ continue;
+ }
+
+ /* Adaptive mode; get the current CPU usage times. */
+ if (read_usage_times(&load)) {
+ if (vflag)
+ warn("read_usage_times() failed");
+ continue;
+ }
+
+ if (mode == MODE_ADAPTIVE) {
+ if (load > cpu_running_mark) {
+ if (load > 95 || load > cpu_running_mark * 2)
+ freq *= 2;
+ else
+ freq = freq * load / cpu_running_mark;
+ if (freq > freqs[0])
+ freq = freqs[0];
+ } else if (load < cpu_idle_mark &&
+ curfreq * load < freqs[get_freq_id(
+ freq * 7 / 8, freqs, numfreqs)] *
+ cpu_running_mark) {
+ freq = freq * 7 / 8;
+ if (freq < freqs[numfreqs - 1])
+ freq = freqs[numfreqs - 1];
+ }
+ } else { /* MODE_HIADAPTIVE */
+ if (load > cpu_running_mark / 2) {
+ if (load > 95 || load > cpu_running_mark)
+ freq *= 4;
+ else
+ freq = freq * load * 2 / cpu_running_mark;
+ if (freq > freqs[0] * 2)
+ freq = freqs[0] * 2;
+ } else if (load < cpu_idle_mark / 2 &&
+ curfreq * load < freqs[get_freq_id(
+ freq * 31 / 32, freqs, numfreqs)] *
+ cpu_running_mark / 2) {
+ freq = freq * 31 / 32;
+ if (freq < freqs[numfreqs - 1])
+ freq = freqs[numfreqs - 1];
+ }
+ }
+ if (vflag) {
+ printf("load %3d%%, current freq %4d MHz (%2d), wanted freq %4d MHz\n",
+ load, curfreq, i, freq);
+ }
+ j = get_freq_id(freq, freqs, numfreqs);
+ if (i != j) {
+ if (vflag) {
+ printf("changing clock"
+ " speed from %d MHz to %d MHz\n",
+ freqs[i], freqs[j]);
+ }
+ idle = 0;
+ if (set_freq(freqs[j]))
+ warn("error setting CPU frequency %d",
+ freqs[j]);
+ }
+ }
+ if (set_freq(initfreq))
+ warn("error setting CPU frequency %d", initfreq);
+ free(freqs);
+ free(mwatts);
+ devd_close();
+ if (!vflag)
+ pidfile_remove(pfh);
+
+ exit(0);
+}
diff --git a/usr.sbin/ppp/Makefile b/usr.sbin/ppp/Makefile
new file mode 100644
index 0000000..1dcd3b5
--- /dev/null
+++ b/usr.sbin/ppp/Makefile
@@ -0,0 +1,114 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= ppp
+MAN= ppp.8
+SRCS= acf.c arp.c async.c auth.c bundle.c cbcp.c ccp.c chap.c chat.c \
+ command.c datalink.c deflate.c defs.c exec.c filter.c fsm.c hdlc.c \
+ iface.c ip.c ipcp.c ipv6cp.c iplist.c lcp.c link.c log.c lqr.c main.c \
+ mbuf.c mp.c ncp.c ncpaddr.c pap.c physical.c pred.c probe.c prompt.c \
+ proto.c route.c server.c sig.c slcompress.c sync.c systems.c tcp.c \
+ tcpmss.c throughput.c timer.c tty.c tun.c udp.c vjcomp.c
+WARNS?= 3
+.if defined(RELEASE_CRUNCH)
+CFLAGS+=-DRELEASE_CRUNCH
+PPP_NO_ATM=
+PPP_NO_DES=
+PPP_NO_KLDLOAD=
+PPP_NO_NAT=
+PPP_NO_PAM=
+PPP_NO_RADIUS=
+PPP_NO_SUID=
+.endif
+CONFS= ppp.conf
+CONFSDIR= ${CONFDIR}/ppp
+CONFSMODE= 600
+
+.if ${MK_ATM} == "no"
+PPP_NO_ATM=
+.endif
+.if ${MK_NETGRAPH} == "no"
+PPP_NO_NETGRAPH=
+.endif
+.if ${MK_PAM_SUPPORT} == "no"
+PPP_NO_PAM=
+.endif
+.if ${MK_RADIUS_SUPPORT} == "no"
+PPP_NO_RADIUS=
+.endif
+
+.if defined(PPP_NO_SUID)
+BINMODE=554
+.else
+BINMODE=4554
+BINOWN= root
+.endif
+BINGRP= network
+M4FLAGS=
+
+LIBADD= md util z
+
+.if defined(PPP_CONFDIR) && !empty(PPP_CONFDIR)
+CFLAGS+=-DPPP_CONFDIR=\"${PPP_CONFDIR}\"
+.endif
+
+.if defined(PPP_NO_KLDLOAD)
+CFLAGS+=-DNOKLDLOAD
+.endif
+
+.if ${MK_INET6_SUPPORT} == "no"
+CFLAGS+=-DNOINET6
+.endif
+
+.if defined(PPP_NO_NAT)
+CFLAGS+=-DNONAT
+.else
+SRCS+= nat_cmd.c
+LIBADD+= alias
+.endif
+
+.if defined(PPP_NO_ATM)
+CFLAGS+=-DNOATM
+.else
+SRCS+= atm.c
+.endif
+
+.if defined(PPP_NO_SUID)
+CFLAGS+=-DNOSUID
+.else
+SRCS+= id.c
+.endif
+
+.if ${MK_OPENSSL} == "no" || defined(PPP_NO_DES)
+CFLAGS+=-DNODES
+.else
+SRCS+= chap_ms.c mppe.c
+LIBADD+= crypto
+.endif
+
+.if defined(PPP_NO_RADIUS)
+CFLAGS+=-DNORADIUS
+.else
+SRCS+= radius.c
+LIBADD+= radius
+.endif
+
+.if defined(PPP_NO_NETGRAPH)
+CFLAGS+=-DNONETGRAPH
+.else
+SRCS+= ether.c
+LIBADD+= netgraph
+.if defined(EXPERIMENTAL_NETGRAPH)
+CFLAGS+=-DEXPERIMENTAL_NETGRAPH
+SRCS+= netgraph.c
+.endif
+.endif
+
+.if defined(PPP_NO_PAM)
+CFLAGS+=-DNOPAM
+.else
+LIBADD+= pam
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ppp/Makefile.depend b/usr.sbin/ppp/Makefile.depend
new file mode 100644
index 0000000..250763c
--- /dev/null
+++ b/usr.sbin/ppp/Makefile.depend
@@ -0,0 +1,27 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libalias/libalias \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libmd \
+ lib/libnetgraph \
+ lib/libpam/libpam \
+ lib/libradius \
+ lib/libutil \
+ lib/libz \
+ secure/lib/libcrypto \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ppp/README.changes b/usr.sbin/ppp/README.changes
new file mode 100644
index 0000000..4ed3da6
--- /dev/null
+++ b/usr.sbin/ppp/README.changes
@@ -0,0 +1,140 @@
+Copyright (c) 2001 Brian Somers <brian@Awfulhak.org>
+ based on work by Eivind Eklund <perhaps@yes.no>,
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must 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$
+
+This file summarises changes made to ppp that effect
+its configuration.
+
+It does not describe new features, rather it attempts
+to answer any `this used to work, why doesn't it now?'
+questions.
+
+o The `set debug' command was replaced with `set log'.
+o The `set log LCP' command was split into LCP, IPCP and CCP logs.
+o Syslogd is used for logging. /etc/syslog.conf must be updated.
+o LQR is disabled by default.
+o Openmode is active by default.
+o Users must be a member of group `network' for ppp access. Furthermore,
+ they must be `allow'ed to run ppp via the `allow' command in the
+ configuration file.
+ For a brief period, ppp could only be run as root.
+o No diagnostic socket is created by default. The `set server' command
+ must be used.
+o The diagnostic socket password must be specified *only* on the `set
+ server' command line.
+o When `set server' is used to re-select a diagnostic port, all existing
+ diagnostic connections are dropped.
+o pppd-deflate is now called deflate24.
+o Filter IPs of 0.0.0.0 have a default width of 0, not 32.
+o Errors in `add' and `delete' are logged as warnings rather than being
+ written to the TCP/IP log.
+o Any number of diagnostic prompts are allowed, and they are allowed in
+ interactive mode.
+o The default `device' is cuau1, then cuau0
+o A password of "*" in ppp.secret causes a passwd database lookup in
+ pap mode.
+o The value of the CONNECT environment variable is logged in the
+ utmp host field in -direct mode.
+o Out-of-sequence FSM packets (IPCP/LCP/CCP) are dropped by default.
+o Reconnect values are used after an LQR timeout.
+o ^C works on the parent in -background mode.
+o The dial/call/open command works asynchronously. As a result, prompts
+ do not lose control while dialing.
+o The `display' command has been removed. All information is available
+ with the appropriate `show' command.
+o Msext does not need to be enabled/disabled. Setting the NBNS (set nbns)
+ will auto enable it. The DNS side may be enabled/disabled, and if
+ enabled without a `set dns' (was `set ns') will use values from
+ /etc/resolv.conf.
+o Filters are now called `allow', `dial', `in' and `out'. `set
+ ifilter ...' becomes `set filter in ...' etc.
+o Authname and Authkey may only be `set' in phase DEAD.
+o Set encrypt is no longer necessary. Ppp will respond to M$CHAP
+ servers correctly if it's built with DES.
+o Throughput statistics are enabled by default.
+o `Set stopped' only has two parameters. It's no longer possible to
+ have an IPCP stopped timer.
+o `Set timeout' only has one or two parameters. Use `set lqrperiod' and
+ `set {lcp,ccp,ipcp,chap,pap}retry' for the other timers. These timeout
+ values can be seen using the relevant show commands.
+o `set loopback' is now `enable/disable loopback'.
+o `show auto', `show loopback' and `show mtu' are all part of `show bundle'.
+o `show mru' is part of `show lcp'
+o `show msext' and `show vj' are part of `show ipcp'
+o `show reconnect' and `show redial' are part of `show link'
+o A signal 15 (TERM) will now shut down the link gracefully.
+o A signal 2 (HUP) will drop all links immediately.
+o Signal 30 (USR1) is now ignored.
+o Add & delete commands are not necessary in ppp.linkup if they are
+ `sticky routes' (ie, contain MYADDR or HISADDR).
+o LINK and CARRIER logging are no longer available.
+o Timer based DEBUG messages are now logged in the new TIMER log.
+o Ppp can use tun devices > tun255.
+o Protocol-compressed packets are accepted even if they were denied
+ at LCP negotiation time.
+o Passwords aren't logged when logging the ``set server'' line.
+o Command line options only need enough characters to uniquely identify
+ them. -a == -auto, -dd == -ddial etc. -interactive is also allowed.
+o If you don't like seeing additional interface aliases when running in
+ -auto -alias mode, add ``iface clear'' to your ppp.linkdown file -
+ check the sample file.
+o Ppp waits for 1 second before checking whether the device supports
+ carrier. This is controllable with ``set cd''.
+o Random dial timeouts are now between 1 and 30 seconds inclusive rather
+ than between 0 and 29.
+o Ppp now accepts M$CHAP (as well as normal CHAP) by default. If this
+ is not required, you must ``deny chap05 chap80''.
+o The ``set device'' command now expects each device to be specified as an
+ argument rather than concatentating all arguments and splitting based
+ on commas and spaces.
+o The ``show modem'' command is deprecated and has been changed to
+ ``show physical''.
+o The words ``host'' and ``port'' are no longer accepted by the ``set filter''
+ command. Removing them should yield the same results as before.
+o The ``set weight'' command has been deprecated. The ``set bandwidth''
+ command should now be used instead.
+o The ``set autoload'' command syntax and implementation have changed as the
+ old implementation was mis-designed and dysfunctional.
+o Ppp now waits either the full ``set cd'' time or until carrier is detected
+ before running the login script (whichever comes first).
+o The -alias flag has been deprecated. The -nat flag should be used instead.
+o Unbalanced quotes in commands are now warned about and the entire command
+ is ignored.
+o It is now only necessary to escape the `-' character in chat scripts twice.
+ See the example files for details.
+o Environment variables and ~ are expanded on in commands
+o ``nat pptp'' is no longer necessary as this is now done transparently
+o The ``!'' at the start of chat scripts and authkey can be made literal
+ (rather than meaning execute) by doubling it to ``!!''.
+o MP autoload throughput measurements are now based on the maximum of input
+ and output averages rather than on the total.
+o When only one link is open in MP mode, MP link level compression is not
+ open and the peer MRU >= the peer MRRU, ppp sends outbound traffic as
+ PROTO_IP traffic rather than PROTO_MP.
+o MSCHAPv2 is now accepted by default. If you don't wish to negotiate
+ this, you must explicitly deny it.
+o MPPE is enabled and accepted by default (although deflate and predictor1
+ are preferred.
diff --git a/usr.sbin/ppp/README.nat b/usr.sbin/ppp/README.nat
new file mode 100644
index 0000000..9ff240a9
--- /dev/null
+++ b/usr.sbin/ppp/README.nat
@@ -0,0 +1,378 @@
+Copyright (c) 2001 Charles Mott <cm@linktel.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 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$
+
+User PPP NAT (Packet Aliasing)
+
+
+
+0. Contents
+ 1. Background
+ 2. Setup
+ 3. New commands in ppp
+ 4. Future Work
+ 5. Authors / Acknowledgements
+ 6. Revision History for Aliasing Code
+
+
+
+1. Background
+
+User mode ppp has embedded NAT (Network Address Translation) code.
+Enabling this, either by the "-nat" command line option or the
+"nat enable yes" command in a ppp.conf file, makes the ppp host
+automatically NAT IP packets forwarded from a local network, making
+them appear to come from the ppp host machine. Incoming packets
+from the outside world are then appropriately de-NAT'd.
+
+The process of NAT'ing involves both the IP address and the TCP or UDP
+port numbers. ICMP echo and timestamp packets are natted by their id
+numbers. ICMP error messages can be properly directed by examining the
+fragment of the offending packet which is contained in the body of the
+message.
+
+This software was specifically meant to support users who have
+unregistered, private address IP networks (e.g. 192.168.0.x or 10.0.0.x
+addresses). The ppp host can act as a gateway for these networks, and
+computers on the local area net will have some degree of Internet access
+without the need for a registered IP address. Additionally, there will
+be no need for an Internet service provider to maintain routing tables
+for the local area network.
+
+A disadvantage of NAT is that machines on the local network,
+behind the ppp host, are not visible from the outside world. They can
+establish TCP connections and make UDP inquiries (such as domain name
+service requests) but the connections seem to come from the ppp host
+itself. There is, in effect, a partial firewall. Of course, if this is
+what you want, the disadvantage becomes an advantage.
+
+A second disadvantage is that "IP encoding" protocols, which send IP
+address or port information within the data stream, are not supported
+for the cases where exception code exists. This implementation has
+workarounds for FTP and IRC DCC, the most well known of the IP encoding
+protocols. This frees users from depending on using the ftp passive
+mode and avoiding IRC DCC sends, as is sometimes the case with other
+masquerading solutions.
+
+The implementation supports all standard, non-encoding TCP and UDP protocols.
+Examples of these protocols are http, gopher and telnet. The standard UDP
+mode of Real-Audio is not presently supported, but the TCP mode does work
+correctly.
+
+The NAT code also handles many ICMP messages. In particular,
+ping and traceroute are supported.
+
+
+
+2. Packet Aliasing Setup
+
+It is recommended that users first verify correct ppp operation without
+NAT enabled. This will confirm that the ppp.conf file is
+properly set up and that there are no ppp problems. Then start ppp with
+the "-nat" option on the command line. The user should verify that
+the ppp host can correctly connect to the Internet in NAT
+mode. Finally, check that machines on the private network can access
+the Internet.
+
+The NAT software handles all packets, whether they come from
+the host or another computer on the local area network. Thus, a correctly
+operating ppp host indicates that the software should work properly for
+other computers on the private network.
+
+If the ppp host can access the Internet, but other computers on the local
+network cannot, check that IP forwarding is enabled on the ppp host. Also,
+verify that the other computers use this machine as a gateway. Of course,
+you should also verify that machines within the local area network
+communicate properly. A common error is inconsistent subnet addresses
+and masks.
+
+
+
+3. New commands in ppp
+
+In order to control NAT behaviour in a simple manner (no need for
+recompilation), a new command has been added to ppp: nat. This
+is in addition to the -nat command line option. System managers and
+more experienced users may prefer to use the ppp command syntax
+within the ppp.conf file. The nat command also allows NAT
+behaviour to be more precisely specified.
+
+The decision to add a command instead of extending 'set' or 'option' was
+to make obvious that these options only work when NAT is enabled.
+
+The syntax for 'nat' is
+
+ ppp> nat option [yes|no]
+
+where option is given by one of the following templates.
+
+
+ - nat enable [yes|no] (default no)
+
+Enable NAT functionality. If disabled, no other NAT
+options will have any effect. You should usually enable NAT
+before routing any packets over the link; good points are in the
+initial script or right before adding a route. If you do not always
+want NAT, consider using the -nat option to ppp instead of this
+command.
+
+
+ - nat deny_incoming [yes|no] (default yes)
+
+Set to "yes" to disable all incoming connections. This just drops
+connections to, for example, ftp, telnet or web servers. The NAT
+mechanism prevents these connections. Technically, this option denies
+all incoming TCP and UDP requests, making the NAT software a
+fairly efficient one-way firewall. The default is no, which will allow
+all incoming connections to telnetd, ftpd, etc.
+
+
+ - nat log [yes|no]
+
+Controls logging of NAT link creation to "/var/log/alias.log" - this
+is usually only useful if debugging a setup, to see if the bug is in
+the PPP NATing. The debugging information is fairly limited, listing
+the number of NAT links open for different protocols.
+
+
+ - nat same_ports [yes|no] (default yes)
+
+When a connection is being established going through the NAT
+routines, it will normally have its port number changed to allow the
+NAT code to track it. If same_ports is enabled, the NAT
+software attempts to keep the connection's source port unchanged.
+This will allow rsh, RPC and other specialised protocols to work
+_most of the time_, at least on the host machine. Please, do not
+report this being unstable as a bug - it is a result of the way
+NAT has to work. TCP/IP was intended to have one IP address
+per machine.
+
+
+ - nat use_sockets [yes|no] (default yes)
+
+This is a fairly obscure option. For the most part, the NAT
+software does not have to allocate system sockets when it chooses a
+NAT port number. Under very specific circumstances, FTP data
+connections (which don't know the remote port number, though it is
+usually 20) and IRC DCC send (which doesn't know either the address or
+the port from which the connection will come), there can potentially be
+some interference with an open server socket having the same port number
+on the ppp host machine. This possibility for interference only exists
+until the TCP connection has been acknowledged on both sides. The safe
+option is yes, though fewer system resources are consumed by specifying
+no.
+
+
+ - nat unregistered_only [yes|no] (default no)
+
+NAT normally remaps all packets coming from the local area
+network to the ppp host machine address. Set this option to only map
+addresses from the following standard ranges for private, unregistered
+addresses:
+
+ 10.0.0.0 -> 10.255.255.255
+ 172.16.0.0 -> 172.31.255.255
+ 192.168.0.0 -> 192.168.255.255 */
+
+In the instance that there is a subnet of public addresses and another
+subnet of private addresses being routed by the ppp host, then only the
+packets on the private subnet will be NAT'd.
+
+
+- nat port <proto> <local addr>:<port> <nat port>
+
+This command allows incoming traffic to <nat port> on the host
+machine to be redirected to a specific machine and port on the
+local area network. One example of this would be:
+
+ nat port tcp 192.168.0.4:telnet 8066
+
+All traffic to port 8066 of the ppp host would then be sent to
+the telnet port (23) of machine 192.168.0.4. Port numbers
+can either be designated numerically or by symbolic names
+listed in /etc/services. Similarly, addresses can be either
+in dotted quad notation or in /etc/hosts.
+
+
+- nat addr <local addr> <public addr>
+
+This command allows traffic for a public IP address to be
+redirected to a machine on the local network. This function
+is known as "static NAT". An address assignment of 0 refers
+to the default address of the ppp host. Normally static
+NAT is useful if your ISP has allocated a small block of
+IP addresses to the user, but it can even be used in the
+case of a single, dynamically allocated IP address:
+
+ nat addr 10.0.0.8 0
+
+The above command would redirect all incoming traffic to
+machine 10.0.0.8.
+
+If several address NATs specify the same public address
+as follows
+
+ nat addr 192.168.0.2 public_addr
+ nat addr 192.168.0.3 public_addr
+ nat addr 192.168.0.4 public_addr
+
+then incoming traffic will be directed to the last
+translated local address (192.168.0.4), but outgoing
+traffic to the first two addresses will still be NAT'd
+to the specified public address.
+
+
+
+4. Future Work
+
+What is called NAT here has been variously called masquerading, packet
+aliasing and transparent proxying by others. It is an extremely useful
+function to many users, but it is also necessarily imperfect. The
+occasional IP-encoding protocols always need workarounds (hacks).
+Users who are interested in supporting new IP-encoding protocols
+can follow the examples of alias_ftp.c and alias_irc.c.
+
+ICMP error messages are currently handled only in the incoming direction.
+A handler needs to be added to correctly NAT outgoing error messages.
+
+IRC and FTP exception handling make reasonable, though not strictly correct
+assumptions, about how IP encoded messages will appear in the control
+stream. Programmers may wish to consider how to make this process more
+robust.
+
+The NAT engine (alias.c, alias_db.c, alias_ftp.c, alias_irc.c
+and alias_util.c) runs in user space, and is intended to be both portable
+and reusable for interfaces other than ppp. To access the basic engine
+only requires four simple function calls (initialisation, communication of
+host address, outgoing NAT and incoming de-NATing).
+
+
+
+5. Authors / Acknowledgements
+
+Charles Mott (cm@linktel.net) <versions 1.0 - 1.8, 2.0, 2.1>
+Eivind Eklund (perhaps@yes.no) <versions 1.8b - 1.9, new ppp commands>
+
+Listed below, in chronological order, are individuals who have provided
+valuable comments and/or debugging assistance.
+
+ Gary Roberts
+ Tom Torrance
+ Reto Burkhalter
+ Martin Renters
+ Brian Somers
+ Paul Traina
+ Ari Suutari
+ J. Fortes
+ Andrzej Bialeki
+
+
+
+6. Revision History for Aliasing Code
+
+Version 1.0: August 11, 1996 (cjm)
+
+Version 1.1: August 20, 1996 (cjm)
+ PPP host accepts incoming connections for ports 0 to 1023.
+
+Version 1.2: September 7, 1996 (cjm)
+ Fragment handling error in alias_db.c corrected.
+
+Version 1.3: September 15, 1996 (cjm)
+ - Generalised mechanism for handling incoming connections
+ (no more 0 to 1023 restriction).
+ - Increased ICMP support (will handle traceroute now).
+ - Improved TCP close connection logic.
+
+Version 1.4: September 16, 1996
+ Can't remember (this version only lasted a day -- cjm).
+
+Version 1.5: September 17, 1996 (cjm)
+ Corrected error in handling incoming UDP packets
+ with zero checksum.
+
+Version 1.6: September 18, 1996
+ Simplified ICMP data storage. Will now handle
+ tracert from Win95 as well as FreeBSD traceroute.
+
+Version 1.7: January 9, 1997 (cjm)
+ - Reduced malloc() activity for ICMP echo and
+ timestamp requests.
+ - Added handling for out-of-order IP fragments.
+ - Switched to differential checksum computation
+ for IP headers (TCP, UDP and ICMP checksums
+ were already differential).
+ - Accepts FTP data connections from other than
+ port 20. This allows one ftp connections
+ from two hosts which are both running packet
+ aliasing.
+
+Version 1.8: January 14, 1997 (cjm)
+ - Fixed data type error in function StartPoint()
+ in alias_db.c (this bug did not exist before v1.7)
+
+Version 1.8b: January 16, 1997 (Eivind Eklund <perhaps@yes.no>)
+ - Upgraded base PPP version to be the source code from
+ FreeBSD 2.1.6, with additional security patches. This
+ version should still be possible to run on 2.1.5, though -
+ I've run it with a 2.1.5 kernel without problems.
+ (Update done with the permission of cjm)
+
+Version 1.9: February 1, 1997 (Eivind Eklund <perhaps@yes.no>)
+ - Added support for IRC DCC (ee)
+ - Changed the aliasing routines to use ANSI style throughout -
+ minor API changes for integration with other programs than PPP (ee)
+ - Changed the build process, making all options switchable
+ from the Makefile (ee)
+ - Fixed minor security hole in alias_ftp.c for other applications
+ of the aliasing software. Hole could _not_ manifest in
+ PPP+pktAlias, but could potentially manifest in other
+ applications of the aliasing. (ee)
+ - Connections initiated from packet aliasing host machine will
+ not have their port number aliased unless it conflicts with
+ an aliasing port already being used. (There is an option to
+ disable this for debugging) (cjm)
+ - Sockets will be allocated in cases where there might be
+ port interference with the host machine. This can be disabled
+ in cases where the ppp host will be acting purely as a
+ masquerading router and not generate any traffic of its own.
+ (cjm)
+
+Version 2.0: March, 1997 (cjm)
+ - Incoming packets which are not recognised by the packet
+ aliasing engine are now completely dropped in ip.c.
+ - Aliasing links are cleared when a host interface address
+ changes (due to re-dial and dynamic address allocation).
+ - PacketAliasPermanentLink() API added.
+ - Option for only aliasing private, unregistered IP addresses
+ added.
+ - Substantial rework to the aliasing lookup engine.
+
+Version 2.1: May, 1997 (cjm)
+ - Continuing rework to the aliasing lookup engine to support
+ multiple incoming addresses and static NAT.
+ - Now supports outgoing as well as incoming ICMP error messages/
+ - PPP commands to support address and port redirection.
+
diff --git a/usr.sbin/ppp/acf.c b/usr.sbin/ppp/acf.c
new file mode 100644
index 0000000..6c3ec05
--- /dev/null
+++ b/usr.sbin/ppp/acf.c
@@ -0,0 +1,116 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.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/types.h>
+
+#include <stdio.h>
+#include <termios.h>
+
+#include "defs.h"
+#include "layer.h"
+#include "timer.h"
+#include "fsm.h"
+#include "log.h"
+#include "mbuf.h"
+#include "acf.h"
+#include "proto.h"
+#include "throughput.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "descriptor.h"
+#include "async.h"
+#include "physical.h"
+
+int
+acf_WrapperOctets(struct lcp *lcp, u_short proto)
+{
+ return (proto == PROTO_LCP || lcp->his_acfcomp == 0) ? 2 : 0;
+}
+
+static struct mbuf *
+acf_LayerPush(struct bundle *b __unused, struct link *l, struct mbuf *bp,
+ int pri __unused, u_short *proto)
+{
+ const u_char cp[2] = { HDLC_ADDR, HDLC_UI };
+
+ if (*proto == PROTO_LCP || l->lcp.his_acfcomp == 0) {
+ bp = m_prepend(bp, cp, 2, 0);
+ m_settype(bp, MB_ACFOUT);
+ }
+
+ return bp;
+}
+
+static struct mbuf *
+acf_LayerPull(struct bundle *b __unused, struct link *l, struct mbuf *bp,
+ u_short *proto __unused)
+{
+ struct physical *p = link2physical(l);
+ u_char cp[2];
+
+ if (!p) {
+ log_Printf(LogERROR, "Can't Pull an acf packet from a logical link\n");
+ return bp;
+ }
+
+ if (mbuf_View(bp, cp, 2) == 2) {
+ if (!p->link.lcp.want_acfcomp) {
+ /* We expect the packet not to be compressed */
+ bp = mbuf_Read(bp, cp, 2);
+ if (cp[0] != HDLC_ADDR) {
+ p->hdlc.lqm.ifInErrors++;
+ p->hdlc.stats.badaddr++;
+ log_Printf(LogDEBUG, "acf_LayerPull: addr 0x%02x\n", cp[0]);
+ m_freem(bp);
+ return NULL;
+ }
+ if (cp[1] != HDLC_UI) {
+ p->hdlc.lqm.ifInErrors++;
+ p->hdlc.stats.badcommand++;
+ log_Printf(LogDEBUG, "acf_LayerPull: control 0x%02x\n", cp[1]);
+ m_freem(bp);
+ return NULL;
+ }
+ m_settype(bp, MB_ACFIN);
+ } else if (cp[0] == HDLC_ADDR && cp[1] == HDLC_UI) {
+ /*
+ * We can receive compressed packets, but the peer still sends
+ * uncompressed packets (or maybe this is a PROTO_LCP packet) !
+ */
+ bp = mbuf_Read(bp, cp, 2);
+ m_settype(bp, MB_ACFIN);
+ }
+ }
+
+ return bp;
+}
+
+struct layer acflayer = { LAYER_ACF, "acf", acf_LayerPush, acf_LayerPull };
diff --git a/usr.sbin/ppp/acf.h b/usr.sbin/ppp/acf.h
new file mode 100644
index 0000000..e32adbc
--- /dev/null
+++ b/usr.sbin/ppp/acf.h
@@ -0,0 +1,33 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.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$
+ */
+
+struct lcp;
+
+extern int acf_WrapperOctets(struct lcp *, u_short);
+
+extern struct layer acflayer;
diff --git a/usr.sbin/ppp/arp.c b/usr.sbin/ppp/arp.c
new file mode 100644
index 0000000..b38b97a
--- /dev/null
+++ b/usr.sbin/ppp/arp.c
@@ -0,0 +1,316 @@
+/*
+ * sys-bsd.c - System-dependent procedures for setting up
+ * PPP interfaces on bsd-4.4-ish systems (including 386BSD, NetBSD, etc.)
+ *
+ * Copyright (c) 1989 Carnegie Mellon University.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms 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 Carnegie Mellon University. The name of the
+ * University 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 MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * $FreeBSD$
+ *
+ */
+
+/*
+ * TODO:
+ */
+
+#include <sys/param.h>
+#include <sys/socket.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 <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/sysctl.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "mbuf.h"
+#include "log.h"
+#include "id.h"
+#include "timer.h"
+#include "fsm.h"
+#include "defs.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "ipv6cp.h"
+#include "descriptor.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "mp.h"
+#include "ncp.h"
+#include "filter.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "bundle.h"
+#include "iface.h"
+#include "arp.h"
+
+/*
+ * SET_SA_FAMILY - set the sa_family field of a struct sockaddr,
+ * if it exists.
+ */
+#define SET_SA_FAMILY(addr, family) \
+ memset((char *) &(addr), '\0', sizeof(addr)); \
+ addr.sa_family = (family); \
+ addr.sa_len = sizeof(addr);
+
+
+#if RTM_VERSION >= 3
+
+/*
+ * arp_SetProxy - Make a proxy ARP entry for the peer.
+ */
+static struct {
+ struct rt_msghdr hdr;
+ struct sockaddr_in dst;
+ struct sockaddr_dl hwa;
+ char extra[128];
+} arpmsg;
+
+static int
+arp_ProxySub(struct bundle *bundle, struct in_addr addr, int add)
+{
+ int routes;
+
+ /*
+ * Get the hardware address of an interface on the same subnet as our local
+ * address.
+ */
+
+ memset(&arpmsg, 0, sizeof arpmsg);
+ if (!arp_EtherAddr(addr, &arpmsg.hwa, 0)) {
+ log_Printf(LogWARN, "%s: Cannot determine ethernet address for proxy ARP\n",
+ inet_ntoa(addr));
+ return 0;
+ }
+ routes = ID0socket(PF_ROUTE, SOCK_RAW, AF_INET);
+ if (routes < 0) {
+ log_Printf(LogERROR, "arp_SetProxy: opening routing socket: %s\n",
+ strerror(errno));
+ return 0;
+ }
+ arpmsg.hdr.rtm_type = add ? RTM_ADD : RTM_DELETE;
+ arpmsg.hdr.rtm_flags = RTF_ANNOUNCE | RTF_HOST | RTF_STATIC | RTF_LLDATA;
+ arpmsg.hdr.rtm_version = RTM_VERSION;
+ arpmsg.hdr.rtm_seq = ++bundle->routing_seq;
+ arpmsg.hdr.rtm_addrs = RTA_DST | RTA_GATEWAY;
+ arpmsg.hdr.rtm_inits = RTV_EXPIRE;
+ arpmsg.dst.sin_len = sizeof(struct sockaddr_in);
+ arpmsg.dst.sin_family = AF_INET;
+ arpmsg.dst.sin_addr.s_addr = addr.s_addr;
+
+ arpmsg.hdr.rtm_msglen = (char *) &arpmsg.hwa - (char *) &arpmsg
+ + arpmsg.hwa.sdl_len;
+
+
+ if (ID0write(routes, &arpmsg, arpmsg.hdr.rtm_msglen) < 0 &&
+ !(!add && errno == ESRCH)) {
+ log_Printf(LogERROR, "%s proxy arp entry %s: %s\n",
+ add ? "Add" : "Delete", inet_ntoa(addr), strerror(errno));
+ close(routes);
+ return 0;
+ }
+ close(routes);
+ return 1;
+}
+
+int
+arp_SetProxy(struct bundle *bundle, struct in_addr addr)
+{
+ return (arp_ProxySub(bundle, addr, 1));
+}
+
+/*
+ * arp_ClearProxy - Delete the proxy ARP entry for the peer.
+ */
+int
+arp_ClearProxy(struct bundle *bundle, struct in_addr addr)
+{
+ return (arp_ProxySub(bundle, addr, 0));
+}
+
+#else /* RTM_VERSION */
+
+/*
+ * arp_SetProxy - Make a proxy ARP entry for the peer.
+ */
+int
+arp_SetProxy(struct bundle *bundle, struct in_addr addr, int s)
+{
+ struct arpreq arpreq;
+ struct {
+ struct sockaddr_dl sdl;
+ char space[128];
+ } dls;
+
+ memset(&arpreq, '\0', sizeof arpreq);
+
+ /*
+ * Get the hardware address of an interface on the same subnet as our local
+ * address.
+ */
+ if (!arp_EtherAddr(addr, &dls.sdl, 1)) {
+ log_Printf(LOG_PHASE_BIT, "Cannot determine ethernet address for "
+ "proxy ARP\n");
+ return 0;
+ }
+ arpreq.arp_ha.sa_len = sizeof(struct sockaddr);
+ arpreq.arp_ha.sa_family = AF_UNSPEC;
+ memcpy(arpreq.arp_ha.sa_data, LLADDR(&dls.sdl), dls.sdl.sdl_alen);
+ SET_SA_FAMILY(arpreq.arp_pa, AF_INET);
+ ((struct sockaddr_in *)&arpreq.arp_pa)->sin_addr.s_addr = addr.s_addr;
+ arpreq.arp_flags = ATF_PERM | ATF_PUBL;
+ if (ID0ioctl(s, SIOCSARP, (caddr_t) & arpreq) < 0) {
+ log_Printf(LogERROR, "arp_SetProxy: ioctl(SIOCSARP): %s\n",
+ strerror(errno));
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * arp_ClearProxy - Delete the proxy ARP entry for the peer.
+ */
+int
+arp_ClearProxy(struct bundle *bundle, struct in_addr addr, int s)
+{
+ struct arpreq arpreq;
+
+ memset(&arpreq, '\0', sizeof arpreq);
+ SET_SA_FAMILY(arpreq.arp_pa, AF_INET);
+ ((struct sockaddr_in *)&arpreq.arp_pa)->sin_addr.s_addr = addr.s_addr;
+ if (ID0ioctl(s, SIOCDARP, (caddr_t) & arpreq) < 0) {
+ log_Printf(LogERROR, "arp_ClearProxy: ioctl(SIOCDARP): %s\n",
+ strerror(errno));
+ return 0;
+ }
+ return 1;
+}
+
+#endif /* RTM_VERSION */
+
+
+/*
+ * arp_EtherAddr - get the hardware address of an interface on the
+ * the same subnet as ipaddr.
+ */
+
+int
+arp_EtherAddr(struct in_addr ipaddr, struct sockaddr_dl *hwaddr,
+ int verbose)
+{
+ int mib[6], skip;
+ size_t needed;
+ char *buf, *ptr, *end;
+ struct if_msghdr *ifm;
+ struct ifa_msghdr *ifam;
+ struct sockaddr_dl *dl;
+ struct sockaddr *sa[RTAX_MAX];
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = 0;
+ mib[4] = NET_RT_IFLIST;
+ mib[5] = 0;
+
+ if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) {
+ log_Printf(LogERROR, "arp_EtherAddr: sysctl: estimate: %s\n",
+ strerror(errno));
+ return 0;
+ }
+
+ if ((buf = malloc(needed)) == NULL)
+ return 0;
+
+ if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) {
+ free(buf);
+ return 0;
+ }
+ end = buf + needed;
+
+ ptr = buf;
+ while (ptr < end) {
+ ifm = (struct if_msghdr *)ptr; /* On if_msghdr */
+ if (ifm->ifm_type != RTM_IFINFO)
+ break;
+ dl = (struct sockaddr_dl *)(ifm + 1); /* Single _dl at end */
+ skip = (ifm->ifm_flags & (IFF_UP | IFF_BROADCAST | IFF_POINTOPOINT |
+ IFF_NOARP | IFF_LOOPBACK)) != (IFF_UP | IFF_BROADCAST);
+ ptr += ifm->ifm_msglen; /* First ifa_msghdr */
+ while (ptr < end) {
+ ifam = (struct ifa_msghdr *)ptr; /* Next ifa_msghdr (alias) */
+ if (ifam->ifam_type != RTM_NEWADDR) /* finished ? */
+ break;
+ ptr += ifam->ifam_msglen;
+ if (skip || (ifam->ifam_addrs & (RTA_NETMASK|RTA_IFA)) !=
+ (RTA_NETMASK|RTA_IFA))
+ continue;
+ /* Found a candidate. Do the addresses match ? */
+ if (log_IsKept(LogDEBUG) &&
+ ptr == (char *)ifm + ifm->ifm_msglen + ifam->ifam_msglen)
+ log_Printf(LogDEBUG, "%.*s interface is a candidate for proxy\n",
+ dl->sdl_nlen, dl->sdl_data);
+
+ iface_ParseHdr(ifam, sa);
+
+ if (sa[RTAX_IFA]->sa_family == AF_INET) {
+ struct sockaddr_in *ifa, *netmask;
+
+ ifa = (struct sockaddr_in *)sa[RTAX_IFA];
+ netmask = (struct sockaddr_in *)sa[RTAX_NETMASK];
+
+ if (log_IsKept(LogDEBUG)) {
+ char a[16];
+
+ strncpy(a, inet_ntoa(netmask->sin_addr), sizeof a - 1);
+ a[sizeof a - 1] = '\0';
+ log_Printf(LogDEBUG, "Check addr %s, mask %s\n",
+ inet_ntoa(ifa->sin_addr), a);
+ }
+
+ if ((ifa->sin_addr.s_addr & netmask->sin_addr.s_addr) ==
+ (ipaddr.s_addr & netmask->sin_addr.s_addr)) {
+ log_Printf(verbose ? LogPHASE : LogDEBUG,
+ "Found interface %.*s for %s\n", dl->sdl_nlen,
+ dl->sdl_data, inet_ntoa(ipaddr));
+ memcpy(hwaddr, dl, dl->sdl_len);
+ free(buf);
+ return 1;
+ }
+ }
+ }
+ }
+ free(buf);
+
+ return 0;
+}
diff --git a/usr.sbin/ppp/arp.h b/usr.sbin/ppp/arp.h
new file mode 100644
index 0000000..81e3fa6
--- /dev/null
+++ b/usr.sbin/ppp/arp.h
@@ -0,0 +1,36 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+struct sockaddr_dl;
+struct bundle;
+
+extern int arp_ClearProxy(struct bundle *, struct in_addr);
+extern int arp_SetProxy(struct bundle *, struct in_addr);
+extern int arp_EtherAddr(struct in_addr, struct sockaddr_dl *, int);
diff --git a/usr.sbin/ppp/async.c b/usr.sbin/ppp/async.c
new file mode 100644
index 0000000..a2a8e04
--- /dev/null
+++ b/usr.sbin/ppp/async.c
@@ -0,0 +1,220 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <string.h>
+#include <termios.h>
+
+#include "layer.h"
+#include "mbuf.h"
+#include "log.h"
+#include "defs.h"
+#include "timer.h"
+#include "fsm.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "proto.h"
+#include "async.h"
+#include "throughput.h"
+#include "ccp.h"
+#include "link.h"
+#include "descriptor.h"
+#include "physical.h"
+
+#define MODE_HUNT 0x01
+#define MODE_ESC 0x02
+
+void
+async_Init(struct async *async)
+{
+ async_Setup(async);
+ memset(async->cfg.EscMap, '\0', sizeof async->cfg.EscMap);
+}
+
+void
+async_Setup(struct async *async)
+{
+ async->mode = MODE_HUNT;
+ async->length = 0;
+ async->my_accmap = async->his_accmap = 0xffffffff;
+}
+
+void
+async_SetLinkParams(struct async *async, u_int32_t mymap, u_int32_t hismap)
+{
+ async->my_accmap = mymap;
+ async->his_accmap = hismap | mymap;
+}
+
+/*
+ * Encode into async HDLC byte code
+ */
+static void
+async_Encode(struct async *async, u_char **cp, u_char c, int proto)
+{
+ u_char *wp;
+
+ wp = *cp;
+ if ((c < 0x20 && (proto == PROTO_LCP || (async->his_accmap & (1 << c))))
+ || (c == HDLC_ESC) || (c == HDLC_SYN)) {
+ *wp++ = HDLC_ESC;
+ c ^= HDLC_XOR;
+ }
+ if (async->cfg.EscMap[32] && async->cfg.EscMap[c >> 3] & (1 << (c & 7))) {
+ *wp++ = HDLC_ESC;
+ c ^= HDLC_XOR;
+ }
+ *wp++ = c;
+ *cp = wp;
+}
+
+static struct mbuf *
+async_LayerPush(struct bundle *b __unused, struct link *l, struct mbuf *bp,
+ int pri __unused, u_short *proto)
+{
+ struct physical *p = link2physical(l);
+ u_char *cp, *sp, *ep;
+ struct mbuf *wp;
+ size_t oldcnt;
+ size_t cnt;
+
+ if (!p || m_length(bp) > HDLCSIZE) {
+ m_freem(bp);
+ return NULL;
+ }
+
+ oldcnt = m_length(bp);
+
+ cp = p->async.xbuff;
+ ep = cp + HDLCSIZE - 10;
+ wp = bp;
+ *cp++ = HDLC_SYN;
+ while (wp) {
+ sp = MBUF_CTOP(wp);
+ for (cnt = wp->m_len; cnt > 0; cnt--) {
+ async_Encode(&p->async, &cp, *sp++, *proto);
+ if (cp >= ep) {
+ m_freem(bp);
+ return NULL;
+ }
+ }
+ wp = wp->m_next;
+ }
+ *cp++ = HDLC_SYN;
+
+ cnt = cp - p->async.xbuff;
+ m_freem(bp);
+ bp = m_get(cnt, MB_ASYNCOUT);
+ memcpy(MBUF_CTOP(bp), p->async.xbuff, cnt);
+ bp->priv = cnt - oldcnt;
+ log_DumpBp(LogASYNC, "Write", bp);
+
+ return bp;
+}
+
+static struct mbuf *
+async_Decode(struct async *async, u_char c)
+{
+ struct mbuf *bp;
+
+ if ((async->mode & MODE_HUNT) && c != HDLC_SYN)
+ return NULL;
+
+ switch (c) {
+ case HDLC_SYN:
+ async->mode &= ~MODE_HUNT;
+ if (async->length) { /* packet is ready. */
+ bp = m_get(async->length, MB_ASYNCIN);
+ mbuf_Write(bp, async->hbuff, async->length);
+ async->length = 0;
+ return bp;
+ }
+ break;
+ case HDLC_ESC:
+ if (!(async->mode & MODE_ESC)) {
+ async->mode |= MODE_ESC;
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ if (async->length >= HDLCSIZE) {
+ /* packet is too large, discard it */
+ log_Printf(LogWARN, "Packet too large (%d), discarding.\n",
+ async->length);
+ async->length = 0;
+ async->mode = MODE_HUNT;
+ break;
+ }
+ if (async->mode & MODE_ESC) {
+ c ^= HDLC_XOR;
+ async->mode &= ~MODE_ESC;
+ }
+ async->hbuff[async->length++] = c;
+ break;
+ }
+ return NULL;
+}
+
+static struct mbuf *
+async_LayerPull(struct bundle *b __unused, struct link *l, struct mbuf *bp,
+ u_short *proto __unused)
+{
+ struct mbuf *nbp, **last;
+ struct physical *p = link2physical(l);
+ u_char *ch;
+ size_t cnt;
+
+ if (!p) {
+ log_Printf(LogERROR, "Can't Pull an async packet from a logical link\n");
+ return bp;
+ }
+
+ last = &nbp;
+
+ log_DumpBp(LogASYNC, "Read", bp);
+ while (bp) {
+ ch = MBUF_CTOP(bp);
+ for (cnt = bp->m_len; cnt; cnt--) {
+ *last = async_Decode(&p->async, *ch++);
+ if (*last != NULL)
+ last = &(*last)->m_nextpkt;
+ }
+ bp = m_free(bp);
+ }
+
+ return nbp;
+}
+
+struct layer asynclayer =
+ { LAYER_ASYNC, "async", async_LayerPush, async_LayerPull };
diff --git a/usr.sbin/ppp/async.h b/usr.sbin/ppp/async.h
new file mode 100644
index 0000000..309f597
--- /dev/null
+++ b/usr.sbin/ppp/async.h
@@ -0,0 +1,53 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.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$
+ */
+
+#define HDLCSIZE (MAX_MRU*2+6)
+
+struct async {
+ int mode;
+ int length;
+ u_char hbuff[HDLCSIZE]; /* recv buffer */
+ u_char xbuff[HDLCSIZE]; /* xmit buffer */
+ u_int32_t my_accmap;
+ u_int32_t his_accmap;
+
+ struct {
+ u_char EscMap[33];
+ } cfg;
+};
+
+struct lcp;
+struct mbuf;
+struct physical;
+struct bundle;
+
+extern void async_Init(struct async *);
+extern void async_Setup(struct async *);
+extern void async_SetLinkParams(struct async *, u_int32_t, u_int32_t);
+
+extern struct layer asynclayer;
diff --git a/usr.sbin/ppp/atm.c b/usr.sbin/ppp/atm.c
new file mode 100644
index 0000000..6e0ce75
--- /dev/null
+++ b/usr.sbin/ppp/atm.c
@@ -0,0 +1,237 @@
+/*-
+ * Copyright (c) 2000 Jakob Stoklund Olesen <stoklund@taxidriver.dk>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/socket.h>
+#include <net/if.h>
+#include <netnatm/natm.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <sys/uio.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "fsm.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "async.h"
+#include "descriptor.h"
+#include "physical.h"
+#include "main.h"
+#include "atm.h"
+
+/* String identifying PPPoA */
+#define PPPOA "PPPoA"
+#define PPPOA_LEN (sizeof(PPPOA) - 1)
+
+struct atmdevice {
+ struct device dev; /* What struct physical knows about */
+};
+
+#define device2atm(d) ((d)->type == ATM_DEVICE ? (struct atmdevice *)d : NULL)
+
+unsigned
+atm_DeviceSize(void)
+{
+ return sizeof(struct atmdevice);
+}
+
+static ssize_t
+atm_Sendto(struct physical *p, const void *v, size_t n)
+{
+ ssize_t ret = write(p->fd, v, n);
+ if (ret < 0) {
+ log_Printf(LogDEBUG, "atm_Sendto(%ld): %s\n", (long)n, strerror(errno));
+ return ret;
+ }
+ return ret;
+}
+
+static ssize_t
+atm_Recvfrom(struct physical *p, void *v, size_t n)
+{
+ ssize_t ret = read(p->fd, (char*)v, n);
+ if (ret < 0) {
+ log_Printf(LogDEBUG, "atm_Recvfrom(%ld): %s\n", (long)n, strerror(errno));
+ return ret;
+ }
+ return ret;
+}
+
+static void
+atm_Free(struct physical *p)
+{
+ struct atmdevice *dev = device2atm(p->handler);
+
+ free(dev);
+}
+
+static void
+atm_device2iov(struct device *d, struct iovec *iov, int *niov,
+ int maxiov __unused, int *auxfd __unused, int *nauxfd __unused)
+{
+ int sz = physical_MaxDeviceSize();
+
+ iov[*niov].iov_base = realloc(d, sz);
+ if (iov[*niov].iov_base == NULL) {
+ log_Printf(LogALERT, "Failed to allocate memory: %d\n", sz);
+ AbortProgram(EX_OSERR);
+ }
+ iov[*niov].iov_len = sz;
+ (*niov)++;
+}
+
+static const struct device baseatmdevice = {
+ ATM_DEVICE,
+ "atm",
+ 0,
+ { CD_NOTREQUIRED, 0 },
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ atm_Free,
+ atm_Recvfrom,
+ atm_Sendto,
+ atm_device2iov,
+ NULL,
+ NULL,
+ NULL
+};
+
+struct device *
+atm_iov2device(int type, struct physical *p, struct iovec *iov, int *niov,
+ int maxiov __unused, int *auxfd __unused, int *nauxfd __unused)
+{
+ if (type == ATM_DEVICE) {
+ struct atmdevice *dev = (struct atmdevice *)iov[(*niov)++].iov_base;
+
+ dev = realloc(dev, sizeof *dev); /* Reduce to the correct size */
+ if (dev == NULL) {
+ log_Printf(LogALERT, "Failed to allocate memory: %d\n",
+ (int)(sizeof *dev));
+ AbortProgram(EX_OSERR);
+ }
+
+ /* Refresh function pointers etc */
+ memcpy(&dev->dev, &baseatmdevice, sizeof dev->dev);
+
+ physical_SetupStack(p, dev->dev.name, PHYSICAL_FORCE_SYNCNOACF);
+ return &dev->dev;
+ }
+
+ return NULL;
+}
+
+static struct atmdevice *
+atm_CreateDevice(struct physical *p, const char *iface, unsigned vpi,
+ unsigned vci)
+{
+ struct atmdevice *dev;
+ struct sockaddr_natm sock;
+
+ if ((dev = calloc(1, sizeof *dev)) == NULL) {
+ log_Printf(LogWARN, "%s: Cannot allocate an atm device: %s\n",
+ p->link.name, strerror(errno));
+ return NULL;
+ }
+
+ sock.snatm_len = sizeof sock;
+ sock.snatm_family = AF_NATM;
+ strncpy(sock.snatm_if, iface, IFNAMSIZ);
+ sock.snatm_vpi = vpi;
+ sock.snatm_vci = vci;
+
+ log_Printf(LogPHASE, "%s: Connecting to %s:%u.%u\n", p->link.name,
+ iface, vpi, vci);
+
+ p->fd = socket(PF_NATM, SOCK_DGRAM, PROTO_NATMAAL5);
+ if (p->fd >= 0) {
+ log_Printf(LogDEBUG, "%s: Opened atm socket %s\n", p->link.name,
+ p->name.full);
+ if (connect(p->fd, (struct sockaddr *)&sock, sizeof sock) == 0)
+ return dev;
+ else
+ log_Printf(LogWARN, "%s: connect: %s\n", p->name.full, strerror(errno));
+ } else
+ log_Printf(LogWARN, "%s: socket: %s\n", p->name.full, strerror(errno));
+
+ close(p->fd);
+ p->fd = -1;
+ free(dev);
+
+ return NULL;
+}
+
+struct device *
+atm_Create(struct physical *p)
+{
+ struct atmdevice *dev;
+
+ dev = NULL;
+ if (p->fd < 0 && !strncasecmp(p->name.full, PPPOA, PPPOA_LEN)
+ && p->name.full[PPPOA_LEN] == ':') {
+ char iface[25];
+ unsigned vci, vpi;
+
+ if (sscanf(p->name.full + PPPOA_LEN + 1, "%25[A-Za-z0-9]:%u.%u", iface,
+ &vpi, &vci) != 3) {
+ log_Printf(LogWARN, "Malformed ATM device name \'%s\', "
+ "PPPoA:if:vpi.vci expected\n", p->name.full);
+ return NULL;
+ }
+
+ dev = atm_CreateDevice(p, iface, vpi, vci);
+ }
+
+ if (dev) {
+ memcpy(&dev->dev, &baseatmdevice, sizeof dev->dev);
+ physical_SetupStack(p, dev->dev.name, PHYSICAL_FORCE_SYNCNOACF);
+ if (p->cfg.cd.necessity != CD_DEFAULT)
+ log_Printf(LogWARN, "Carrier settings ignored\n");
+ return &dev->dev;
+ }
+
+ return NULL;
+}
diff --git a/usr.sbin/ppp/atm.h b/usr.sbin/ppp/atm.h
new file mode 100644
index 0000000..ccfad8d
--- /dev/null
+++ b/usr.sbin/ppp/atm.h
@@ -0,0 +1,35 @@
+/*-
+ * Copyright (c) 2000 Jakob Stoklund Olesen <stoklund@taxidriver.dk>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+struct physical;
+struct device;
+
+extern struct device *atm_Create(struct physical *);
+extern struct device *atm_iov2device(int, struct physical *,
+ struct iovec *, int *, int, int *, int *);
+extern unsigned atm_DeviceSize(void);
diff --git a/usr.sbin/ppp/auth.c b/usr.sbin/ppp/auth.c
new file mode 100644
index 0000000..fbfc929
--- /dev/null
+++ b/usr.sbin/ppp/auth.c
@@ -0,0 +1,481 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#ifndef NOPAM
+#include <security/pam_appl.h>
+#ifdef OPENPAM
+#include <security/openpam.h>
+#endif
+#endif /* !NOPAM */
+
+#include "layer.h"
+#include "mbuf.h"
+#include "defs.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "auth.h"
+#include "systems.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "descriptor.h"
+#include "chat.h"
+#include "proto.h"
+#include "filter.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "cbcp.h"
+#include "chap.h"
+#include "async.h"
+#include "physical.h"
+#include "datalink.h"
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+
+const char *
+Auth2Nam(u_short auth, u_char type)
+{
+ static char chap[10];
+
+ switch (auth) {
+ case PROTO_PAP:
+ return "PAP";
+ case PROTO_CHAP:
+ snprintf(chap, sizeof chap, "CHAP 0x%02x", type);
+ return chap;
+ case 0:
+ return "none";
+ }
+ return "unknown";
+}
+
+#if !defined(NOPAM) && !defined(OPENPAM)
+static int
+pam_conv(int n, const struct pam_message **msg, struct pam_response **resp,
+ void *data)
+{
+
+ if (n != 1 || msg[0]->msg_style != PAM_PROMPT_ECHO_OFF)
+ return (PAM_CONV_ERR);
+ if ((*resp = malloc(sizeof(struct pam_response))) == NULL)
+ return (PAM_CONV_ERR);
+ (*resp)[0].resp = strdup((const char *)data);
+ (*resp)[0].resp_retcode = 0;
+
+ return ((*resp)[0].resp != NULL ? PAM_SUCCESS : PAM_CONV_ERR);
+}
+#endif /* !defined(NOPAM) && !defined(OPENPAM) */
+
+static int
+auth_CheckPasswd(const char *name, const char *data, const char *key)
+{
+ if (!strcmp(data, "*")) {
+#ifdef NOPAM
+ /* Then look up the real password database */
+ struct passwd *pw;
+ int result;
+ char *cryptpw;
+
+ cryptpw = crypt(key, pw->pw_passwd);
+ result = (pw = getpwnam(name)) &&
+ (cryptpw == NULL || !strcmp(cryptpw, pw->pw_passwd));
+ endpwent();
+ return result;
+#else /* !NOPAM */
+ /* Then consult with PAM. */
+ pam_handle_t *pamh;
+ int status;
+
+ struct pam_conv pamc = {
+#ifdef OPENPAM
+ &openpam_nullconv, NULL
+#else
+ &pam_conv, key
+#endif
+ };
+
+ if (pam_start("ppp", name, &pamc, &pamh) != PAM_SUCCESS)
+ return (0);
+#ifdef OPENPAM
+ if ((status = pam_set_item(pamh, PAM_AUTHTOK, key)) == PAM_SUCCESS)
+#endif
+ status = pam_authenticate(pamh, 0);
+ pam_end(pamh, status);
+ return (status == PAM_SUCCESS);
+#endif /* !NOPAM */
+ }
+
+ return !strcmp(data, key);
+}
+
+int
+auth_SetPhoneList(const char *name, char *phone, int phonelen)
+{
+ FILE *fp;
+ int n, lineno;
+ char *vector[6], buff[LINE_LEN];
+ const char *slash;
+
+ fp = OpenSecret(SECRETFILE);
+ if (fp != NULL) {
+again:
+ lineno = 0;
+ while (fgets(buff, sizeof buff, fp)) {
+ lineno++;
+ if (buff[0] == '#')
+ continue;
+ buff[strlen(buff) - 1] = '\0';
+ memset(vector, '\0', sizeof vector);
+ if ((n = MakeArgs(buff, vector, VECSIZE(vector), PARSE_REDUCE)) < 0)
+ log_Printf(LogWARN, "%s: %d: Invalid line\n", SECRETFILE, lineno);
+ if (n < 5)
+ continue;
+ if (strcmp(vector[0], name) == 0) {
+ CloseSecret(fp);
+ if (*vector[4] == '\0')
+ return 0;
+ strncpy(phone, vector[4], phonelen - 1);
+ phone[phonelen - 1] = '\0';
+ return 1; /* Valid */
+ }
+ }
+
+ if ((slash = strrchr(name, '\\')) != NULL && slash[1]) {
+ /* Look for the name without the leading domain */
+ name = slash + 1;
+ rewind(fp);
+ goto again;
+ }
+
+ CloseSecret(fp);
+ }
+ *phone = '\0';
+ return 0;
+}
+
+int
+auth_Select(struct bundle *bundle, const char *name)
+{
+ FILE *fp;
+ int n, lineno;
+ char *vector[5], buff[LINE_LEN];
+ const char *slash;
+
+ if (*name == '\0') {
+ ipcp_Setup(&bundle->ncp.ipcp, INADDR_NONE);
+ return 1;
+ }
+
+#ifndef NORADIUS
+ if (bundle->radius.valid && bundle->radius.ip.s_addr != INADDR_NONE &&
+ bundle->radius.ip.s_addr != RADIUS_INADDR_POOL) {
+ /* We've got a radius IP - it overrides everything */
+ if (!ipcp_UseHisIPaddr(bundle, bundle->radius.ip))
+ return 0;
+ ipcp_Setup(&bundle->ncp.ipcp, bundle->radius.mask.s_addr);
+ /* Continue with ppp.secret in case we've got a new label */
+ }
+#endif
+
+ fp = OpenSecret(SECRETFILE);
+ if (fp != NULL) {
+again:
+ lineno = 0;
+ while (fgets(buff, sizeof buff, fp)) {
+ lineno++;
+ if (buff[0] == '#')
+ continue;
+ buff[strlen(buff) - 1] = '\0';
+ memset(vector, '\0', sizeof vector);
+ if ((n = MakeArgs(buff, vector, VECSIZE(vector), PARSE_REDUCE)) < 0)
+ log_Printf(LogWARN, "%s: %d: Invalid line\n", SECRETFILE, lineno);
+ if (n < 2)
+ continue;
+ if (strcmp(vector[0], name) == 0) {
+ CloseSecret(fp);
+#ifndef NORADIUS
+ if (!bundle->radius.valid || bundle->radius.ip.s_addr == INADDR_NONE) {
+#endif
+ if (n > 2 && *vector[2] && strcmp(vector[2], "*") &&
+ !ipcp_UseHisaddr(bundle, vector[2], 1))
+ return 0;
+ ipcp_Setup(&bundle->ncp.ipcp, INADDR_NONE);
+#ifndef NORADIUS
+ }
+#endif
+ if (n > 3 && *vector[3] && strcmp(vector[3], "*"))
+ bundle_SetLabel(bundle, vector[3]);
+ return 1; /* Valid */
+ }
+ }
+
+ if ((slash = strrchr(name, '\\')) != NULL && slash[1]) {
+ /* Look for the name without the leading domain */
+ name = slash + 1;
+ rewind(fp);
+ goto again;
+ }
+
+ CloseSecret(fp);
+ }
+
+#ifndef NOPASSWDAUTH
+ /* Let 'em in anyway - they must have been in the passwd file */
+ ipcp_Setup(&bundle->ncp.ipcp, INADDR_NONE);
+ return 1;
+#else
+#ifndef NORADIUS
+ if (bundle->radius.valid)
+ return 1;
+#endif
+
+ /* Disappeared from ppp.secret ??? */
+ return 0;
+#endif
+}
+
+int
+auth_Validate(struct bundle *bundle, const char *name, const char *key)
+{
+ /* Used by PAP routines */
+
+ FILE *fp;
+ int n, lineno;
+ char *vector[5], buff[LINE_LEN];
+ const char *slash;
+
+ fp = OpenSecret(SECRETFILE);
+again:
+ lineno = 0;
+ if (fp != NULL) {
+ while (fgets(buff, sizeof buff, fp)) {
+ lineno++;
+ if (buff[0] == '#')
+ continue;
+ buff[strlen(buff) - 1] = 0;
+ memset(vector, '\0', sizeof vector);
+ if ((n = MakeArgs(buff, vector, VECSIZE(vector), PARSE_REDUCE)) < 0)
+ log_Printf(LogWARN, "%s: %d: Invalid line\n", SECRETFILE, lineno);
+ if (n < 2)
+ continue;
+ if (strcmp(vector[0], name) == 0) {
+ CloseSecret(fp);
+ return auth_CheckPasswd(name, vector[1], key);
+ }
+ }
+ }
+
+ if ((slash = strrchr(name, '\\')) != NULL && slash[1]) {
+ /* Look for the name without the leading domain */
+ name = slash + 1;
+ if (fp != NULL) {
+ rewind(fp);
+ goto again;
+ }
+ }
+
+ if (fp != NULL)
+ CloseSecret(fp);
+
+#ifndef NOPASSWDAUTH
+ if (Enabled(bundle, OPT_PASSWDAUTH))
+ return auth_CheckPasswd(name, "*", key);
+#endif
+
+ return 0; /* Invalid */
+}
+
+char *
+auth_GetSecret(const char *name, size_t len)
+{
+ /* Used by CHAP routines */
+
+ FILE *fp;
+ int n, lineno;
+ char *vector[5];
+ const char *slash;
+ static char buff[LINE_LEN]; /* vector[] will point here when returned */
+
+ fp = OpenSecret(SECRETFILE);
+ if (fp == NULL)
+ return (NULL);
+
+again:
+ lineno = 0;
+ while (fgets(buff, sizeof buff, fp)) {
+ lineno++;
+ if (buff[0] == '#')
+ continue;
+ n = strlen(buff) - 1;
+ if (buff[n] == '\n')
+ buff[n] = '\0'; /* Trim the '\n' */
+ memset(vector, '\0', sizeof vector);
+ if ((n = MakeArgs(buff, vector, VECSIZE(vector), PARSE_REDUCE)) < 0)
+ log_Printf(LogWARN, "%s: %d: Invalid line\n", SECRETFILE, lineno);
+ if (n < 2)
+ continue;
+ if (strlen(vector[0]) == len && strncmp(vector[0], name, len) == 0) {
+ CloseSecret(fp);
+ return vector[1];
+ }
+ }
+
+ if ((slash = strrchr(name, '\\')) != NULL && slash[1]) {
+ /* Go back and look for the name without the leading domain */
+ len -= slash - name + 1;
+ name = slash + 1;
+ rewind(fp);
+ goto again;
+ }
+
+ CloseSecret(fp);
+ return (NULL); /* Invalid */
+}
+
+static void
+AuthTimeout(void *vauthp)
+{
+ struct authinfo *authp = (struct authinfo *)vauthp;
+
+ timer_Stop(&authp->authtimer);
+ if (--authp->retry > 0) {
+ authp->id++;
+ (*authp->fn.req)(authp);
+ timer_Start(&authp->authtimer);
+ } else {
+ log_Printf(LogPHASE, "Auth: No response from server\n");
+ datalink_AuthNotOk(authp->physical->dl);
+ }
+}
+
+void
+auth_Init(struct authinfo *authp, struct physical *p, auth_func req,
+ auth_func success, auth_func failure)
+{
+ memset(authp, '\0', sizeof(struct authinfo));
+ authp->cfg.fsm.timeout = DEF_FSMRETRY;
+ authp->cfg.fsm.maxreq = DEF_FSMAUTHTRIES;
+ authp->cfg.fsm.maxtrm = 0; /* not used */
+ authp->fn.req = req;
+ authp->fn.success = success;
+ authp->fn.failure = failure;
+ authp->physical = p;
+}
+
+void
+auth_StartReq(struct authinfo *authp)
+{
+ timer_Stop(&authp->authtimer);
+ authp->authtimer.func = AuthTimeout;
+ authp->authtimer.name = "auth";
+ authp->authtimer.load = authp->cfg.fsm.timeout * SECTICKS;
+ authp->authtimer.arg = (void *)authp;
+ authp->retry = authp->cfg.fsm.maxreq;
+ authp->id = 1;
+ (*authp->fn.req)(authp);
+ timer_Start(&authp->authtimer);
+}
+
+void
+auth_StopTimer(struct authinfo *authp)
+{
+ timer_Stop(&authp->authtimer);
+}
+
+struct mbuf *
+auth_ReadHeader(struct authinfo *authp, struct mbuf *bp)
+{
+ size_t len;
+
+ len = m_length(bp);
+ if (len >= sizeof authp->in.hdr) {
+ bp = mbuf_Read(bp, (u_char *)&authp->in.hdr, sizeof authp->in.hdr);
+ if (len >= ntohs(authp->in.hdr.length))
+ return bp;
+ authp->in.hdr.length = htons(0);
+ log_Printf(LogWARN, "auth_ReadHeader: Short packet (%u > %zu) !\n",
+ ntohs(authp->in.hdr.length), len);
+ } else {
+ authp->in.hdr.length = htons(0);
+ log_Printf(LogWARN, "auth_ReadHeader: Short packet header (%u > %zu) !\n",
+ (int)(sizeof authp->in.hdr), len);
+ }
+
+ m_freem(bp);
+ return NULL;
+}
+
+struct mbuf *
+auth_ReadName(struct authinfo *authp, struct mbuf *bp, size_t len)
+{
+ if (len > sizeof authp->in.name - 1)
+ log_Printf(LogWARN, "auth_ReadName: Name too long (%zu) !\n", len);
+ else {
+ size_t mlen = m_length(bp);
+
+ if (len > mlen)
+ log_Printf(LogWARN, "auth_ReadName: Short packet (%zu > %zu) !\n",
+ len, mlen);
+ else {
+ bp = mbuf_Read(bp, (u_char *)authp->in.name, len);
+ authp->in.name[len] = '\0';
+ return bp;
+ }
+ }
+
+ *authp->in.name = '\0';
+ m_freem(bp);
+ return NULL;
+}
diff --git a/usr.sbin/ppp/auth.h b/usr.sbin/ppp/auth.h
new file mode 100644
index 0000000..3e1047d
--- /dev/null
+++ b/usr.sbin/ppp/auth.h
@@ -0,0 +1,68 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+struct physical;
+struct bundle;
+struct authinfo;
+typedef void (*auth_func)(struct authinfo *);
+
+struct authinfo {
+ struct {
+ auth_func req;
+ auth_func success;
+ auth_func failure;
+ } fn;
+ struct {
+ struct fsmheader hdr;
+ char name[AUTHLEN];
+ } in;
+ struct pppTimer authtimer;
+ int retry;
+ int id;
+ struct physical *physical;
+ struct {
+ struct fsm_retry fsm; /* How often/frequently to resend requests */
+ } cfg;
+};
+
+#define auth_Failure(a) (*(a)->fn.failure)(a)
+#define auth_Success(a) (*(a)->fn.success)(a)
+
+extern const char *Auth2Nam(u_short, u_char);
+extern void auth_Init(struct authinfo *, struct physical *,
+ auth_func, auth_func, auth_func);
+extern void auth_StopTimer(struct authinfo *);
+extern void auth_StartReq(struct authinfo *);
+extern int auth_Validate(struct bundle *, const char *, const char *);
+extern char *auth_GetSecret(const char *, size_t);
+extern int auth_SetPhoneList(const char *, char *, int);
+extern int auth_Select(struct bundle *, const char *);
+extern struct mbuf *auth_ReadHeader(struct authinfo *, struct mbuf *);
+extern struct mbuf *auth_ReadName(struct authinfo *, struct mbuf *, size_t);
diff --git a/usr.sbin/ppp/bundle.c b/usr.sbin/ppp/bundle.c
new file mode 100644
index 0000000..c12209e
--- /dev/null
+++ b/usr.sbin/ppp/bundle.c
@@ -0,0 +1,2019 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.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/param.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <net/if_tun.h> /* For TUNS* ioctls */
+#include <net/route.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#ifdef __OpenBSD__
+#include <util.h>
+#else
+#include <libutil.h>
+#endif
+#include <paths.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "id.h"
+#include "timer.h"
+#include "fsm.h"
+#include "iplist.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ip.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "route.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "async.h"
+#include "physical.h"
+#include "auth.h"
+#include "proto.h"
+#include "chap.h"
+#include "tun.h"
+#include "prompt.h"
+#include "chat.h"
+#include "cbcp.h"
+#include "datalink.h"
+#include "iface.h"
+#include "server.h"
+#include "probe.h"
+#ifndef NODES
+#include "mppe.h"
+#endif
+
+#define SCATTER_SEGMENTS 7 /* version, datalink, name, physical,
+ throughput, throughput, device */
+
+#define SEND_MAXFD 3 /* Max file descriptors passed through
+ the local domain socket */
+
+static int bundle_RemainingIdleTime(struct bundle *);
+
+static const char * const PhaseNames[] = {
+ "Dead", "Establish", "Authenticate", "Network", "Terminate"
+};
+
+const char *
+bundle_PhaseName(struct bundle *bundle)
+{
+ return bundle->phase <= PHASE_TERMINATE ?
+ PhaseNames[bundle->phase] : "unknown";
+}
+
+void
+bundle_NewPhase(struct bundle *bundle, u_int new)
+{
+ if (new == bundle->phase)
+ return;
+
+ if (new <= PHASE_TERMINATE)
+ log_Printf(LogPHASE, "bundle: %s\n", PhaseNames[new]);
+
+ switch (new) {
+ case PHASE_DEAD:
+ bundle->phase = new;
+#ifndef NODES
+ MPPE_MasterKeyValid = 0;
+#endif
+ log_DisplayPrompts();
+ break;
+
+ case PHASE_ESTABLISH:
+ bundle->phase = new;
+ break;
+
+ case PHASE_AUTHENTICATE:
+ bundle->phase = new;
+ log_DisplayPrompts();
+ break;
+
+ case PHASE_NETWORK:
+ if (ncp_fsmStart(&bundle->ncp, bundle)) {
+ bundle->phase = new;
+ log_DisplayPrompts();
+ } else {
+ log_Printf(LogPHASE, "bundle: All NCPs are disabled\n");
+ bundle_Close(bundle, NULL, CLOSE_STAYDOWN);
+ }
+ break;
+
+ case PHASE_TERMINATE:
+ bundle->phase = new;
+ mp_Down(&bundle->ncp.mp);
+ log_DisplayPrompts();
+ break;
+ }
+}
+
+static void
+bundle_LayerStart(void *v __unused, struct fsm *fp __unused)
+{
+ /* The given FSM is about to start up ! */
+}
+
+
+void
+bundle_Notify(struct bundle *bundle, char c)
+{
+ if (bundle->notify.fd != -1) {
+ int ret;
+
+ ret = write(bundle->notify.fd, &c, 1);
+ if (c != EX_REDIAL && c != EX_RECONNECT) {
+ if (ret == 1)
+ log_Printf(LogCHAT, "Parent notified of %s\n",
+ c == EX_NORMAL ? "success" : "failure");
+ else
+ log_Printf(LogERROR, "Failed to notify parent of success\n");
+ close(bundle->notify.fd);
+ bundle->notify.fd = -1;
+ } else if (ret == 1)
+ log_Printf(LogCHAT, "Parent notified of %s\n", ex_desc(c));
+ else
+ log_Printf(LogERROR, "Failed to notify parent of %s\n", ex_desc(c));
+ }
+}
+
+static void
+bundle_ClearQueues(void *v)
+{
+ struct bundle *bundle = (struct bundle *)v;
+ struct datalink *dl;
+
+ log_Printf(LogPHASE, "Clearing choked output queue\n");
+ timer_Stop(&bundle->choked.timer);
+
+ /*
+ * Emergency time:
+ *
+ * We've had a full queue for PACKET_DEL_SECS seconds without being
+ * able to get rid of any of the packets. We've probably given up
+ * on the redials at this point, and the queued data has almost
+ * definitely been timed out by the layer above. As this is preventing
+ * us from reading the TUN_NAME device (we don't want to buffer stuff
+ * indefinitely), we may as well nuke this data and start with a clean
+ * slate !
+ *
+ * Unfortunately, this has the side effect of shafting any compression
+ * dictionaries in use (causing the relevant RESET_REQ/RESET_ACK).
+ */
+
+ ncp_DeleteQueues(&bundle->ncp);
+ for (dl = bundle->links; dl; dl = dl->next)
+ physical_DeleteQueue(dl->physical);
+}
+
+static void
+bundle_LinkAdded(struct bundle *bundle, struct datalink *dl)
+{
+ bundle->phys_type.all |= dl->physical->type;
+ if (dl->state == DATALINK_OPEN)
+ bundle->phys_type.open |= dl->physical->type;
+
+#ifndef NORADIUS
+ if ((bundle->phys_type.open & (PHYS_DEDICATED|PHYS_DDIAL))
+ != bundle->phys_type.open && bundle->session.timer.state == TIMER_STOPPED)
+ if (bundle->radius.sessiontime)
+ bundle_StartSessionTimer(bundle, 0);
+#endif
+
+ if ((bundle->phys_type.open & (PHYS_DEDICATED|PHYS_DDIAL))
+ != bundle->phys_type.open && bundle->idle.timer.state == TIMER_STOPPED)
+ /* We may need to start our idle timer */
+ bundle_StartIdleTimer(bundle, 0);
+}
+
+void
+bundle_LinksRemoved(struct bundle *bundle)
+{
+ struct datalink *dl;
+
+ bundle->phys_type.all = bundle->phys_type.open = 0;
+ for (dl = bundle->links; dl; dl = dl->next)
+ bundle_LinkAdded(bundle, dl);
+
+ bundle_CalculateBandwidth(bundle);
+ mp_CheckAutoloadTimer(&bundle->ncp.mp);
+
+ if ((bundle->phys_type.open & (PHYS_DEDICATED|PHYS_DDIAL))
+ == bundle->phys_type.open) {
+#ifndef NORADIUS
+ if (bundle->radius.sessiontime)
+ bundle_StopSessionTimer(bundle);
+#endif
+ bundle_StopIdleTimer(bundle);
+ }
+}
+
+static void
+bundle_LayerUp(void *v, struct fsm *fp)
+{
+ /*
+ * The given fsm is now up
+ * If it's an LCP, adjust our phys_mode.open value and check the
+ * autoload timer.
+ * If it's the first NCP, calculate our bandwidth
+ * If it's the first NCP, set our ``upat'' time
+ * If it's the first NCP, start the idle timer.
+ * If it's an NCP, tell our -background parent to go away.
+ * If it's the first NCP, start the autoload timer
+ */
+ struct bundle *bundle = (struct bundle *)v;
+
+ if (fp->proto == PROTO_LCP) {
+ struct physical *p = link2physical(fp->link);
+
+ bundle_LinkAdded(bundle, p->dl);
+ mp_CheckAutoloadTimer(&bundle->ncp.mp);
+ } else if (isncp(fp->proto)) {
+ if (ncp_LayersOpen(&fp->bundle->ncp) == 1) {
+ bundle_CalculateBandwidth(fp->bundle);
+ time(&bundle->upat);
+#ifndef NORADIUS
+ if (bundle->radius.sessiontime)
+ bundle_StartSessionTimer(bundle, 0);
+#endif
+ bundle_StartIdleTimer(bundle, 0);
+ mp_CheckAutoloadTimer(&fp->bundle->ncp.mp);
+ }
+ bundle_Notify(bundle, EX_NORMAL);
+ } else if (fp->proto == PROTO_CCP)
+ bundle_CalculateBandwidth(fp->bundle); /* Against ccp_MTUOverhead */
+}
+
+static void
+bundle_LayerDown(void *v, struct fsm *fp)
+{
+ /*
+ * The given FSM has been told to come down.
+ * If it's our last NCP, stop the idle timer.
+ * If it's our last NCP, clear our ``upat'' value.
+ * If it's our last NCP, stop the autoload timer
+ * If it's an LCP, adjust our phys_type.open value and any timers.
+ * If it's an LCP and we're in multilink mode, adjust our tun
+ * If it's the last LCP, down all NCPs
+ * speed and make sure our minimum sequence number is adjusted.
+ */
+
+ struct bundle *bundle = (struct bundle *)v;
+
+ if (isncp(fp->proto)) {
+ if (ncp_LayersOpen(&fp->bundle->ncp) == 0) {
+#ifndef NORADIUS
+ if (bundle->radius.sessiontime)
+ bundle_StopSessionTimer(bundle);
+#endif
+ bundle_StopIdleTimer(bundle);
+ bundle->upat = 0;
+ mp_StopAutoloadTimer(&bundle->ncp.mp);
+ }
+ } else if (fp->proto == PROTO_LCP) {
+ struct datalink *dl;
+ struct datalink *lost;
+ int others_active;
+
+ bundle_LinksRemoved(bundle); /* adjust timers & phys_type values */
+
+ lost = NULL;
+ others_active = 0;
+ for (dl = bundle->links; dl; dl = dl->next) {
+ if (fp == &dl->physical->link.lcp.fsm)
+ lost = dl;
+ else if (dl->state != DATALINK_CLOSED && dl->state != DATALINK_HANGUP)
+ others_active++;
+ }
+
+ if (bundle->ncp.mp.active) {
+ bundle_CalculateBandwidth(bundle);
+
+ if (lost)
+ mp_LinkLost(&bundle->ncp.mp, lost);
+ else
+ log_Printf(LogALERT, "Oops, lost an unrecognised datalink (%s) !\n",
+ fp->link->name);
+ }
+
+ if (!others_active) {
+ /* Down the NCPs. We don't expect to get fsm_Close()d ourself ! */
+ ncp2initial(&bundle->ncp);
+ mp_Down(&bundle->ncp.mp);
+ }
+ }
+}
+
+static void
+bundle_LayerFinish(void *v, struct fsm *fp)
+{
+ /* The given fsm is now down (fp cannot be NULL)
+ *
+ * If it's the last NCP, fsm_Close all LCPs
+ * If it's the last NCP, bring any MP layer down
+ */
+
+ struct bundle *bundle = (struct bundle *)v;
+ struct datalink *dl;
+
+ if (isncp(fp->proto) && !ncp_LayersUnfinished(&bundle->ncp)) {
+ if (bundle_Phase(bundle) != PHASE_DEAD)
+ bundle_NewPhase(bundle, PHASE_TERMINATE);
+ for (dl = bundle->links; dl; dl = dl->next)
+ if (dl->state == DATALINK_OPEN)
+ datalink_Close(dl, CLOSE_STAYDOWN);
+ fsm2initial(fp);
+ mp_Down(&bundle->ncp.mp);
+ }
+}
+
+void
+bundle_Close(struct bundle *bundle, const char *name, int how)
+{
+ /*
+ * Please close the given datalink.
+ * If name == NULL or name is the last datalink, fsm_Close all NCPs
+ * (except our MP)
+ * If it isn't the last datalink, just Close that datalink.
+ */
+
+ struct datalink *dl, *this_dl;
+ int others_active;
+
+ others_active = 0;
+ this_dl = NULL;
+
+ for (dl = bundle->links; dl; dl = dl->next) {
+ if (name && !strcasecmp(name, dl->name))
+ this_dl = dl;
+ if (name == NULL || this_dl == dl) {
+ switch (how) {
+ case CLOSE_LCP:
+ datalink_DontHangup(dl);
+ break;
+ case CLOSE_STAYDOWN:
+ datalink_StayDown(dl);
+ break;
+ }
+ } else if (dl->state != DATALINK_CLOSED && dl->state != DATALINK_HANGUP)
+ others_active++;
+ }
+
+ if (name && this_dl == NULL) {
+ log_Printf(LogWARN, "%s: Invalid datalink name\n", name);
+ return;
+ }
+
+ if (!others_active) {
+#ifndef NORADIUS
+ if (bundle->radius.sessiontime)
+ bundle_StopSessionTimer(bundle);
+#endif
+ bundle_StopIdleTimer(bundle);
+ if (ncp_LayersUnfinished(&bundle->ncp))
+ ncp_Close(&bundle->ncp);
+ else {
+ ncp2initial(&bundle->ncp);
+ mp_Down(&bundle->ncp.mp);
+ for (dl = bundle->links; dl; dl = dl->next)
+ datalink_Close(dl, how);
+ }
+ } else if (this_dl && this_dl->state != DATALINK_CLOSED &&
+ this_dl->state != DATALINK_HANGUP)
+ datalink_Close(this_dl, how);
+}
+
+void
+bundle_Down(struct bundle *bundle, int how)
+{
+ struct datalink *dl;
+
+ for (dl = bundle->links; dl; dl = dl->next)
+ datalink_Down(dl, how);
+}
+
+static int
+bundle_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e, int *n)
+{
+ struct bundle *bundle = descriptor2bundle(d);
+ struct datalink *dl;
+ int result, nlinks;
+ u_short ifqueue;
+ size_t queued;
+
+ result = 0;
+
+ /* If there are aren't many packets queued, look for some more. */
+ for (nlinks = 0, dl = bundle->links; dl; dl = dl->next)
+ nlinks++;
+
+ if (nlinks) {
+ queued = r ? ncp_FillPhysicalQueues(&bundle->ncp, bundle) :
+ ncp_QueueLen(&bundle->ncp);
+
+ if (r && (bundle->phase == PHASE_NETWORK ||
+ bundle->phys_type.all & PHYS_AUTO)) {
+ /* enough surplus so that we can tell if we're getting swamped */
+ ifqueue = nlinks > bundle->cfg.ifqueue ? nlinks : bundle->cfg.ifqueue;
+ if (queued < ifqueue) {
+ /* Not enough - select() for more */
+ if (bundle->choked.timer.state == TIMER_RUNNING)
+ timer_Stop(&bundle->choked.timer); /* Not needed any more */
+ FD_SET(bundle->dev.fd, r);
+ if (*n < bundle->dev.fd + 1)
+ *n = bundle->dev.fd + 1;
+ log_Printf(LogTIMER, "%s: fdset(r) %d\n", TUN_NAME, bundle->dev.fd);
+ result++;
+ } else if (bundle->choked.timer.state == TIMER_STOPPED) {
+ bundle->choked.timer.func = bundle_ClearQueues;
+ bundle->choked.timer.name = "output choke";
+ bundle->choked.timer.load = bundle->cfg.choked.timeout * SECTICKS;
+ bundle->choked.timer.arg = bundle;
+ timer_Start(&bundle->choked.timer);
+ }
+ }
+ }
+
+#ifndef NORADIUS
+ result += descriptor_UpdateSet(&bundle->radius.desc, r, w, e, n);
+#endif
+
+ /* Which links need a select() ? */
+ for (dl = bundle->links; dl; dl = dl->next)
+ result += descriptor_UpdateSet(&dl->desc, r, w, e, n);
+
+ /*
+ * This *MUST* be called after the datalink UpdateSet()s as it
+ * might be ``holding'' one of the datalinks (death-row) and
+ * wants to be able to de-select() it from the descriptor set.
+ */
+ result += descriptor_UpdateSet(&bundle->ncp.mp.server.desc, r, w, e, n);
+
+ return result;
+}
+
+static int
+bundle_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+ struct bundle *bundle = descriptor2bundle(d);
+ struct datalink *dl;
+
+ for (dl = bundle->links; dl; dl = dl->next)
+ if (descriptor_IsSet(&dl->desc, fdset))
+ return 1;
+
+#ifndef NORADIUS
+ if (descriptor_IsSet(&bundle->radius.desc, fdset))
+ return 1;
+#endif
+
+ if (descriptor_IsSet(&bundle->ncp.mp.server.desc, fdset))
+ return 1;
+
+ return FD_ISSET(bundle->dev.fd, fdset);
+}
+
+static void
+bundle_DescriptorRead(struct fdescriptor *d __unused, struct bundle *bundle,
+ const fd_set *fdset)
+{
+ struct datalink *dl;
+ unsigned secs;
+ u_int32_t af;
+
+ if (descriptor_IsSet(&bundle->ncp.mp.server.desc, fdset))
+ descriptor_Read(&bundle->ncp.mp.server.desc, bundle, fdset);
+
+ for (dl = bundle->links; dl; dl = dl->next)
+ if (descriptor_IsSet(&dl->desc, fdset))
+ descriptor_Read(&dl->desc, bundle, fdset);
+
+#ifndef NORADIUS
+ if (descriptor_IsSet(&bundle->radius.desc, fdset))
+ descriptor_Read(&bundle->radius.desc, bundle, fdset);
+#endif
+
+ if (FD_ISSET(bundle->dev.fd, fdset)) {
+ struct tun_data tun;
+ int n, pri;
+ u_char *data;
+ size_t sz;
+
+ if (bundle->dev.header) {
+ data = (u_char *)&tun;
+ sz = sizeof tun;
+ } else {
+ data = tun.data;
+ sz = sizeof tun.data;
+ }
+
+ /* something to read from tun */
+
+ n = read(bundle->dev.fd, data, sz);
+ if (n < 0) {
+ log_Printf(LogWARN, "%s: read: %s\n", bundle->dev.Name, strerror(errno));
+ return;
+ }
+
+ if (bundle->dev.header) {
+ n -= sz - sizeof tun.data;
+ if (n <= 0) {
+ log_Printf(LogERROR, "%s: read: Got only %d bytes of data !\n",
+ bundle->dev.Name, n);
+ return;
+ }
+ af = ntohl(tun.header.family);
+#ifndef NOINET6
+ if (af != AF_INET && af != AF_INET6)
+#else
+ if (af != AF_INET)
+#endif
+ /* XXX: Should be maintaining drop/family counts ! */
+ return;
+ } else
+ af = AF_INET;
+
+ if (af == AF_INET && ((struct ip *)tun.data)->ip_dst.s_addr ==
+ bundle->ncp.ipcp.my_ip.s_addr) {
+ /* we've been asked to send something addressed *to* us :( */
+ if (Enabled(bundle, OPT_LOOPBACK)) {
+ pri = PacketCheck(bundle, af, tun.data, n, &bundle->filter.in,
+ NULL, NULL);
+ if (pri >= 0) {
+ n += sz - sizeof tun.data;
+ write(bundle->dev.fd, data, n);
+ log_Printf(LogDEBUG, "Looped back packet addressed to myself\n");
+ }
+ return;
+ } else
+ log_Printf(LogDEBUG, "Oops - forwarding packet addressed to myself\n");
+ }
+
+ /*
+ * Process on-demand dialup. Output packets are queued within the tunnel
+ * device until the appropriate NCP is opened.
+ */
+
+ if (bundle_Phase(bundle) == PHASE_DEAD) {
+ /*
+ * Note, we must be in AUTO mode :-/ otherwise our interface should
+ * *not* be UP and we can't receive data
+ */
+ pri = PacketCheck(bundle, af, tun.data, n, &bundle->filter.dial,
+ NULL, NULL);
+ if (pri >= 0)
+ bundle_Open(bundle, NULL, PHYS_AUTO, 0);
+ else
+ /*
+ * Drop the packet. If we were to queue it, we'd just end up with
+ * a pile of timed-out data in our output queue by the time we get
+ * around to actually dialing. We'd also prematurely reach the
+ * threshold at which we stop select()ing to read() the tun
+ * device - breaking auto-dial.
+ */
+ return;
+ }
+
+ secs = 0;
+ pri = PacketCheck(bundle, af, tun.data, n, &bundle->filter.out,
+ NULL, &secs);
+ if (pri >= 0) {
+ /* Prepend the number of seconds timeout given in the filter */
+ tun.header.timeout = secs;
+ ncp_Enqueue(&bundle->ncp, af, pri, (char *)&tun, n + sizeof tun.header);
+ }
+ }
+}
+
+static int
+bundle_DescriptorWrite(struct fdescriptor *d __unused, struct bundle *bundle,
+ const fd_set *fdset)
+{
+ struct datalink *dl;
+ int result = 0;
+
+ /* This is not actually necessary as struct mpserver doesn't Write() */
+ if (descriptor_IsSet(&bundle->ncp.mp.server.desc, fdset))
+ if (descriptor_Write(&bundle->ncp.mp.server.desc, bundle, fdset) == 1)
+ result++;
+
+ for (dl = bundle->links; dl; dl = dl->next)
+ if (descriptor_IsSet(&dl->desc, fdset))
+ switch (descriptor_Write(&dl->desc, bundle, fdset)) {
+ case -1:
+ datalink_ComeDown(dl, CLOSE_NORMAL);
+ break;
+ case 1:
+ result++;
+ }
+
+ return result;
+}
+
+void
+bundle_LockTun(struct bundle *bundle)
+{
+ FILE *lockfile;
+ char pidfile[PATH_MAX];
+
+ snprintf(pidfile, sizeof pidfile, "%stun%d.pid", _PATH_VARRUN, bundle->unit);
+ lockfile = ID0fopen(pidfile, "w");
+ if (lockfile != NULL) {
+ fprintf(lockfile, "%d\n", (int)getpid());
+ fclose(lockfile);
+ }
+#ifndef RELEASE_CRUNCH
+ else
+ log_Printf(LogERROR, "Warning: Can't create %s: %s\n",
+ pidfile, strerror(errno));
+#endif
+}
+
+static void
+bundle_UnlockTun(struct bundle *bundle)
+{
+ char pidfile[PATH_MAX];
+
+ snprintf(pidfile, sizeof pidfile, "%stun%d.pid", _PATH_VARRUN, bundle->unit);
+ ID0unlink(pidfile);
+}
+
+struct bundle *
+bundle_Create(const char *prefix, int type, int unit)
+{
+ static struct bundle bundle; /* there can be only one */
+ int enoentcount, err, minunit, maxunit;
+ const char *ifname;
+#if defined(__FreeBSD__) && !defined(NOKLDLOAD)
+ int kldtried;
+#endif
+#if defined(TUNSIFMODE) || defined(TUNSLMODE) || defined(TUNSIFHEAD)
+ int iff;
+#endif
+
+ if (bundle.iface != NULL) { /* Already allocated ! */
+ log_Printf(LogALERT, "bundle_Create: There's only one BUNDLE !\n");
+ return NULL;
+ }
+
+ if (unit == -1) {
+ minunit = 0;
+ maxunit = -1;
+ } else {
+ minunit = unit;
+ maxunit = unit + 1;
+ }
+ err = ENOENT;
+ enoentcount = 0;
+#if defined(__FreeBSD__) && !defined(NOKLDLOAD)
+ kldtried = 0;
+#endif
+ for (bundle.unit = minunit; bundle.unit != maxunit; bundle.unit++) {
+ snprintf(bundle.dev.Name, sizeof bundle.dev.Name, "%s%d",
+ prefix, bundle.unit);
+ bundle.dev.fd = ID0open(bundle.dev.Name, O_RDWR);
+ if (bundle.dev.fd >= 0)
+ break;
+ else if (errno == ENXIO || errno == ENOENT) {
+#if defined(__FreeBSD__) && !defined(NOKLDLOAD)
+ if (bundle.unit == minunit && !kldtried++) {
+ /*
+ * Attempt to load the tunnel interface KLD if it isn't loaded
+ * already.
+ */
+ if (loadmodules(LOAD_VERBOSLY, "if_tun", NULL))
+ bundle.unit--;
+ continue;
+ }
+#endif
+ if (errno != ENOENT || ++enoentcount > 2) {
+ err = errno;
+ break;
+ }
+ } else
+ err = errno;
+ }
+
+ if (bundle.dev.fd < 0) {
+ if (unit == -1)
+ log_Printf(LogWARN, "No available tunnel devices found (%s)\n",
+ strerror(err));
+ else
+ log_Printf(LogWARN, "%s%d: %s\n", prefix, unit, strerror(err));
+ return NULL;
+ }
+
+ log_SetTun(bundle.unit, NULL);
+
+ ifname = strrchr(bundle.dev.Name, '/');
+ if (ifname == NULL)
+ ifname = bundle.dev.Name;
+ else
+ ifname++;
+
+ bundle.iface = iface_Create(ifname);
+ if (bundle.iface == NULL) {
+ close(bundle.dev.fd);
+ return NULL;
+ }
+
+#ifdef TUNSIFMODE
+ /* Make sure we're POINTOPOINT & IFF_MULTICAST */
+ iff = IFF_POINTOPOINT | IFF_MULTICAST;
+ if (ID0ioctl(bundle.dev.fd, TUNSIFMODE, &iff) < 0)
+ log_Printf(LogERROR, "bundle_Create: ioctl(TUNSIFMODE): %s\n",
+ strerror(errno));
+#endif
+
+#ifdef TUNSLMODE
+ /* Make sure we're not prepending sockaddrs */
+ iff = 0;
+ if (ID0ioctl(bundle.dev.fd, TUNSLMODE, &iff) < 0)
+ log_Printf(LogERROR, "bundle_Create: ioctl(TUNSLMODE): %s\n",
+ strerror(errno));
+#endif
+
+#ifdef TUNSIFHEAD
+ /* We want the address family please ! */
+ iff = 1;
+ if (ID0ioctl(bundle.dev.fd, TUNSIFHEAD, &iff) < 0) {
+ log_Printf(LogERROR, "bundle_Create: ioctl(TUNSIFHEAD): %s\n",
+ strerror(errno));
+ bundle.dev.header = 0;
+ } else
+ bundle.dev.header = 1;
+#else
+#ifdef __OpenBSD__
+ /* Always present for OpenBSD */
+ bundle.dev.header = 1;
+#else
+ /*
+ * If TUNSIFHEAD isn't available and we're not OpenBSD, assume
+ * everything's AF_INET (hopefully the tun device won't pass us
+ * anything else !).
+ */
+ bundle.dev.header = 0;
+#endif
+#endif
+
+ log_Printf(LogPHASE, "Using interface: %s\n", ifname);
+
+ bundle.bandwidth = 0;
+ bundle.routing_seq = 0;
+ bundle.phase = PHASE_DEAD;
+ bundle.CleaningUp = 0;
+ bundle.NatEnabled = 0;
+
+ bundle.fsm.LayerStart = bundle_LayerStart;
+ bundle.fsm.LayerUp = bundle_LayerUp;
+ bundle.fsm.LayerDown = bundle_LayerDown;
+ bundle.fsm.LayerFinish = bundle_LayerFinish;
+ bundle.fsm.object = &bundle;
+
+ bundle.cfg.idle.timeout = NCP_IDLE_TIMEOUT;
+ bundle.cfg.idle.min_timeout = 0;
+ *bundle.cfg.auth.name = '\0';
+ *bundle.cfg.auth.key = '\0';
+ bundle.cfg.optmask = (1ull << OPT_IDCHECK) | (1ull << OPT_LOOPBACK) |
+ (1ull << OPT_SROUTES) | (1ull << OPT_TCPMSSFIXUP) |
+ (1ull << OPT_THROUGHPUT) | (1ull << OPT_UTMP) |
+ (1ull << OPT_NAS_IP_ADDRESS) |
+ (1ull << OPT_NAS_IDENTIFIER);
+#ifndef NOINET6
+ opt_enable(&bundle, OPT_IPCP);
+ if (probe.ipv6_available)
+ opt_enable(&bundle, OPT_IPV6CP);
+#endif
+ *bundle.cfg.label = '\0';
+ bundle.cfg.ifqueue = DEF_IFQUEUE;
+ bundle.cfg.choked.timeout = CHOKED_TIMEOUT;
+ bundle.phys_type.all = type;
+ bundle.phys_type.open = 0;
+ bundle.upat = 0;
+
+ bundle.links = datalink_Create("deflink", &bundle, type);
+ if (bundle.links == NULL) {
+ log_Printf(LogALERT, "Cannot create data link: %s\n", strerror(errno));
+ iface_Free(bundle.iface);
+ bundle.iface = NULL;
+ close(bundle.dev.fd);
+ return NULL;
+ }
+
+ bundle.desc.type = BUNDLE_DESCRIPTOR;
+ bundle.desc.UpdateSet = bundle_UpdateSet;
+ bundle.desc.IsSet = bundle_IsSet;
+ bundle.desc.Read = bundle_DescriptorRead;
+ bundle.desc.Write = bundle_DescriptorWrite;
+
+ ncp_Init(&bundle.ncp, &bundle);
+
+ memset(&bundle.filter, '\0', sizeof bundle.filter);
+ bundle.filter.in.fragok = bundle.filter.in.logok = 1;
+ bundle.filter.in.name = "IN";
+ bundle.filter.out.fragok = bundle.filter.out.logok = 1;
+ bundle.filter.out.name = "OUT";
+ bundle.filter.dial.name = "DIAL";
+ bundle.filter.dial.logok = 1;
+ bundle.filter.alive.name = "ALIVE";
+ bundle.filter.alive.logok = 1;
+ {
+ int i;
+ for (i = 0; i < MAXFILTERS; i++) {
+ bundle.filter.in.rule[i].f_action = A_NONE;
+ bundle.filter.out.rule[i].f_action = A_NONE;
+ bundle.filter.dial.rule[i].f_action = A_NONE;
+ bundle.filter.alive.rule[i].f_action = A_NONE;
+ }
+ }
+ memset(&bundle.idle.timer, '\0', sizeof bundle.idle.timer);
+ bundle.idle.done = 0;
+ bundle.notify.fd = -1;
+ memset(&bundle.choked.timer, '\0', sizeof bundle.choked.timer);
+#ifndef NORADIUS
+ radius_Init(&bundle.radius);
+#endif
+
+ /* Clean out any leftover crud */
+ iface_Clear(bundle.iface, &bundle.ncp, 0, IFACE_CLEAR_ALL);
+
+ bundle_LockTun(&bundle);
+
+ return &bundle;
+}
+
+static void
+bundle_DownInterface(struct bundle *bundle)
+{
+ route_IfDelete(bundle, 1);
+ iface_ClearFlags(bundle->iface->name, IFF_UP);
+}
+
+void
+bundle_Destroy(struct bundle *bundle)
+{
+ struct datalink *dl;
+
+ /*
+ * Clean up the interface. We don't really need to do the timer_Stop()s,
+ * mp_Down(), iface_Clear() and bundle_DownInterface() unless we're getting
+ * out under exceptional conditions such as a descriptor exception.
+ */
+ timer_Stop(&bundle->idle.timer);
+ timer_Stop(&bundle->choked.timer);
+ mp_Down(&bundle->ncp.mp);
+ iface_Clear(bundle->iface, &bundle->ncp, 0, IFACE_CLEAR_ALL);
+ bundle_DownInterface(bundle);
+
+#ifndef NORADIUS
+ /* Tell the radius server the bad news */
+ radius_Destroy(&bundle->radius);
+#endif
+
+ /* Again, these are all DATALINK_CLOSED unless we're abending */
+ dl = bundle->links;
+ while (dl)
+ dl = datalink_Destroy(dl);
+
+ ncp_Destroy(&bundle->ncp);
+
+ close(bundle->dev.fd);
+ bundle_UnlockTun(bundle);
+
+ /* In case we never made PHASE_NETWORK */
+ bundle_Notify(bundle, EX_ERRDEAD);
+
+ iface_Destroy(bundle->iface);
+ bundle->iface = NULL;
+}
+
+void
+bundle_LinkClosed(struct bundle *bundle, struct datalink *dl)
+{
+ /*
+ * Our datalink has closed.
+ * CleanDatalinks() (called from DoLoop()) will remove closed
+ * BACKGROUND, FOREGROUND and DIRECT links.
+ * If it's the last data link, enter phase DEAD.
+ *
+ * NOTE: dl may not be in our list (bundle_SendDatalink()) !
+ */
+
+ struct datalink *odl;
+ int other_links;
+
+ log_SetTtyCommandMode(dl);
+
+ other_links = 0;
+ for (odl = bundle->links; odl; odl = odl->next)
+ if (odl != dl && odl->state != DATALINK_CLOSED)
+ other_links++;
+
+ if (!other_links) {
+ if (dl->physical->type != PHYS_AUTO) /* Not in -auto mode */
+ bundle_DownInterface(bundle);
+ ncp2initial(&bundle->ncp);
+ mp_Down(&bundle->ncp.mp);
+ bundle_NewPhase(bundle, PHASE_DEAD);
+#ifndef NORADIUS
+ if (bundle->radius.sessiontime)
+ bundle_StopSessionTimer(bundle);
+#endif
+ bundle_StopIdleTimer(bundle);
+ }
+}
+
+void
+bundle_Open(struct bundle *bundle, const char *name, int mask, int force)
+{
+ /*
+ * Please open the given datalink, or all if name == NULL
+ */
+ struct datalink *dl;
+
+ for (dl = bundle->links; dl; dl = dl->next)
+ if (name == NULL || !strcasecmp(dl->name, name)) {
+ if ((mask & dl->physical->type) &&
+ (dl->state == DATALINK_CLOSED ||
+ (force && dl->state == DATALINK_OPENING &&
+ dl->dial.timer.state == TIMER_RUNNING) ||
+ dl->state == DATALINK_READY)) {
+ timer_Stop(&dl->dial.timer); /* We're finished with this */
+ datalink_Up(dl, 1, 1);
+ if (mask & PHYS_AUTO)
+ break; /* Only one AUTO link at a time */
+ }
+ if (name != NULL)
+ break;
+ }
+}
+
+struct datalink *
+bundle2datalink(struct bundle *bundle, const char *name)
+{
+ struct datalink *dl;
+
+ if (name != NULL) {
+ for (dl = bundle->links; dl; dl = dl->next)
+ if (!strcasecmp(dl->name, name))
+ return dl;
+ } else if (bundle->links && !bundle->links->next)
+ return bundle->links;
+
+ return NULL;
+}
+
+int
+bundle_ShowLinks(struct cmdargs const *arg)
+{
+ struct datalink *dl;
+ struct pppThroughput *t;
+ unsigned long long octets;
+ int secs;
+
+ for (dl = arg->bundle->links; dl; dl = dl->next) {
+ octets = MAX(dl->physical->link.stats.total.in.OctetsPerSecond,
+ dl->physical->link.stats.total.out.OctetsPerSecond);
+
+ prompt_Printf(arg->prompt, "Name: %s [%s, %s]",
+ dl->name, mode2Nam(dl->physical->type), datalink_State(dl));
+ if (dl->physical->link.stats.total.rolling && dl->state == DATALINK_OPEN)
+ prompt_Printf(arg->prompt, " bandwidth %d, %llu bps (%llu bytes/sec)",
+ dl->mp.bandwidth ? dl->mp.bandwidth :
+ physical_GetSpeed(dl->physical),
+ octets * 8, octets);
+ prompt_Printf(arg->prompt, "\n");
+ }
+
+ t = &arg->bundle->ncp.mp.link.stats.total;
+ octets = MAX(t->in.OctetsPerSecond, t->out.OctetsPerSecond);
+ secs = t->downtime ? 0 : throughput_uptime(t);
+ if (secs > t->SamplePeriod)
+ secs = t->SamplePeriod;
+ if (secs)
+ prompt_Printf(arg->prompt, "Currently averaging %llu bps (%llu bytes/sec)"
+ " over the last %d secs\n", octets * 8, octets, secs);
+
+ return 0;
+}
+
+static const char *
+optval(struct bundle *bundle, int opt)
+{
+ return Enabled(bundle, opt) ? "enabled" : "disabled";
+}
+
+int
+bundle_ShowStatus(struct cmdargs const *arg)
+{
+ int remaining;
+
+ prompt_Printf(arg->prompt, "Phase %s\n", bundle_PhaseName(arg->bundle));
+ prompt_Printf(arg->prompt, " Device: %s\n", arg->bundle->dev.Name);
+ prompt_Printf(arg->prompt, " Interface: %s @ %lubps",
+ arg->bundle->iface->name, arg->bundle->bandwidth);
+
+ if (arg->bundle->upat) {
+ int secs = bundle_Uptime(arg->bundle);
+
+ prompt_Printf(arg->prompt, ", up time %d:%02d:%02d", secs / 3600,
+ (secs / 60) % 60, secs % 60);
+ }
+ prompt_Printf(arg->prompt, "\n Queued: %lu of %u\n",
+ (unsigned long)ncp_QueueLen(&arg->bundle->ncp),
+ arg->bundle->cfg.ifqueue);
+
+ prompt_Printf(arg->prompt, "\nDefaults:\n");
+ prompt_Printf(arg->prompt, " Label: %s\n",
+ arg->bundle->cfg.label);
+ prompt_Printf(arg->prompt, " Auth name: %s\n",
+ arg->bundle->cfg.auth.name);
+ prompt_Printf(arg->prompt, " Diagnostic socket: ");
+ if (*server.cfg.sockname != '\0') {
+ prompt_Printf(arg->prompt, "%s", server.cfg.sockname);
+ if (server.cfg.mask != (mode_t)-1)
+ prompt_Printf(arg->prompt, ", mask 0%03o", (int)server.cfg.mask);
+ prompt_Printf(arg->prompt, "%s\n", server.fd == -1 ? " (not open)" : "");
+ } else if (server.cfg.port != 0)
+ prompt_Printf(arg->prompt, "TCP port %d%s\n", server.cfg.port,
+ server.fd == -1 ? " (not open)" : "");
+ else
+ prompt_Printf(arg->prompt, "none\n");
+
+ prompt_Printf(arg->prompt, " Choked Timer: %us\n",
+ arg->bundle->cfg.choked.timeout);
+
+#ifndef NORADIUS
+ radius_Show(&arg->bundle->radius, arg->prompt);
+#endif
+
+ prompt_Printf(arg->prompt, " Idle Timer: ");
+ if (arg->bundle->cfg.idle.timeout) {
+ prompt_Printf(arg->prompt, "%us", arg->bundle->cfg.idle.timeout);
+ if (arg->bundle->cfg.idle.min_timeout)
+ prompt_Printf(arg->prompt, ", min %us",
+ arg->bundle->cfg.idle.min_timeout);
+ remaining = bundle_RemainingIdleTime(arg->bundle);
+ if (remaining != -1)
+ prompt_Printf(arg->prompt, " (%ds remaining)", remaining);
+ prompt_Printf(arg->prompt, "\n");
+ } else
+ prompt_Printf(arg->prompt, "disabled\n");
+
+ prompt_Printf(arg->prompt, " Filter Decap: %-20.20s",
+ optval(arg->bundle, OPT_FILTERDECAP));
+ prompt_Printf(arg->prompt, " ID check: %s\n",
+ optval(arg->bundle, OPT_IDCHECK));
+ prompt_Printf(arg->prompt, " Iface-Alias: %-20.20s",
+ optval(arg->bundle, OPT_IFACEALIAS));
+#ifndef NOINET6
+ prompt_Printf(arg->prompt, " IPCP: %s\n",
+ optval(arg->bundle, OPT_IPCP));
+ prompt_Printf(arg->prompt, " IPV6CP: %-20.20s",
+ optval(arg->bundle, OPT_IPV6CP));
+#endif
+ prompt_Printf(arg->prompt, " Keep-Session: %s\n",
+ optval(arg->bundle, OPT_KEEPSESSION));
+ prompt_Printf(arg->prompt, " Loopback: %-20.20s",
+ optval(arg->bundle, OPT_LOOPBACK));
+ prompt_Printf(arg->prompt, " PasswdAuth: %s\n",
+ optval(arg->bundle, OPT_PASSWDAUTH));
+ prompt_Printf(arg->prompt, " Proxy: %-20.20s",
+ optval(arg->bundle, OPT_PROXY));
+ prompt_Printf(arg->prompt, " Proxyall: %s\n",
+ optval(arg->bundle, OPT_PROXYALL));
+ prompt_Printf(arg->prompt, " Sticky Routes: %-20.20s",
+ optval(arg->bundle, OPT_SROUTES));
+ prompt_Printf(arg->prompt, " TCPMSS Fixup: %s\n",
+ optval(arg->bundle, OPT_TCPMSSFIXUP));
+ prompt_Printf(arg->prompt, " Throughput: %-20.20s",
+ optval(arg->bundle, OPT_THROUGHPUT));
+ prompt_Printf(arg->prompt, " Utmp Logging: %s\n",
+ optval(arg->bundle, OPT_UTMP));
+ prompt_Printf(arg->prompt, " NAS-IP-Address: %-20.20s",
+ optval(arg->bundle, OPT_NAS_IP_ADDRESS));
+ prompt_Printf(arg->prompt, " NAS-Identifier: %s\n",
+ optval(arg->bundle, OPT_NAS_IDENTIFIER));
+
+ return 0;
+}
+
+static void
+bundle_IdleTimeout(void *v)
+{
+ struct bundle *bundle = (struct bundle *)v;
+
+ log_Printf(LogPHASE, "Idle timer expired\n");
+ bundle_StopIdleTimer(bundle);
+ bundle_Close(bundle, NULL, CLOSE_STAYDOWN);
+}
+
+/*
+ * Start Idle timer. If timeout is reached, we call bundle_Close() to
+ * close LCP and link.
+ */
+void
+bundle_StartIdleTimer(struct bundle *bundle, unsigned secs)
+{
+ timer_Stop(&bundle->idle.timer);
+ if ((bundle->phys_type.open & (PHYS_DEDICATED|PHYS_DDIAL)) !=
+ bundle->phys_type.open && bundle->cfg.idle.timeout) {
+ time_t now = time(NULL);
+
+ if (secs == 0)
+ secs = bundle->cfg.idle.timeout;
+
+ /* We want at least `secs' */
+ if (bundle->cfg.idle.min_timeout > secs && bundle->upat) {
+ unsigned up = now - bundle->upat;
+
+ if (bundle->cfg.idle.min_timeout > up &&
+ bundle->cfg.idle.min_timeout - up > (long long)secs)
+ /* Only increase from the current `remaining' value */
+ secs = bundle->cfg.idle.min_timeout - up;
+ }
+ bundle->idle.timer.func = bundle_IdleTimeout;
+ bundle->idle.timer.name = "idle";
+ bundle->idle.timer.load = secs * SECTICKS;
+ bundle->idle.timer.arg = bundle;
+ timer_Start(&bundle->idle.timer);
+ bundle->idle.done = now + secs;
+ }
+}
+
+void
+bundle_SetIdleTimer(struct bundle *bundle, unsigned timeout,
+ unsigned min_timeout)
+{
+ bundle->cfg.idle.timeout = timeout;
+ bundle->cfg.idle.min_timeout = min_timeout;
+ if (ncp_LayersOpen(&bundle->ncp))
+ bundle_StartIdleTimer(bundle, 0);
+}
+
+void
+bundle_StopIdleTimer(struct bundle *bundle)
+{
+ timer_Stop(&bundle->idle.timer);
+ bundle->idle.done = 0;
+}
+
+static int
+bundle_RemainingIdleTime(struct bundle *bundle)
+{
+ if (bundle->idle.done)
+ return bundle->idle.done - time(NULL);
+ return -1;
+}
+
+#ifndef NORADIUS
+
+static void
+bundle_SessionTimeout(void *v)
+{
+ struct bundle *bundle = (struct bundle *)v;
+
+ log_Printf(LogPHASE, "Session-Timeout timer expired\n");
+ bundle_StopSessionTimer(bundle);
+ bundle_Close(bundle, NULL, CLOSE_STAYDOWN);
+}
+
+void
+bundle_StartSessionTimer(struct bundle *bundle, unsigned secs)
+{
+ timer_Stop(&bundle->session.timer);
+ if ((bundle->phys_type.open & (PHYS_DEDICATED|PHYS_DDIAL)) !=
+ bundle->phys_type.open && bundle->radius.sessiontime) {
+ time_t now = time(NULL);
+
+ if (secs == 0)
+ secs = bundle->radius.sessiontime;
+
+ bundle->session.timer.func = bundle_SessionTimeout;
+ bundle->session.timer.name = "session";
+ bundle->session.timer.load = secs * SECTICKS;
+ bundle->session.timer.arg = bundle;
+ timer_Start(&bundle->session.timer);
+ bundle->session.done = now + secs;
+ }
+}
+
+void
+bundle_StopSessionTimer(struct bundle *bundle)
+{
+ timer_Stop(&bundle->session.timer);
+ bundle->session.done = 0;
+}
+
+#endif
+
+int
+bundle_IsDead(struct bundle *bundle)
+{
+ return !bundle->links || (bundle->phase == PHASE_DEAD && bundle->CleaningUp);
+}
+
+static struct datalink *
+bundle_DatalinkLinkout(struct bundle *bundle, struct datalink *dl)
+{
+ struct datalink **dlp;
+
+ for (dlp = &bundle->links; *dlp; dlp = &(*dlp)->next)
+ if (*dlp == dl) {
+ *dlp = dl->next;
+ dl->next = NULL;
+ bundle_LinksRemoved(bundle);
+ return dl;
+ }
+
+ return NULL;
+}
+
+static void
+bundle_DatalinkLinkin(struct bundle *bundle, struct datalink *dl)
+{
+ struct datalink **dlp = &bundle->links;
+
+ while (*dlp)
+ dlp = &(*dlp)->next;
+
+ *dlp = dl;
+ dl->next = NULL;
+
+ bundle_LinkAdded(bundle, dl);
+ mp_CheckAutoloadTimer(&bundle->ncp.mp);
+}
+
+void
+bundle_CleanDatalinks(struct bundle *bundle)
+{
+ struct datalink **dlp = &bundle->links;
+ int found = 0;
+
+ while (*dlp)
+ if ((*dlp)->state == DATALINK_CLOSED &&
+ (*dlp)->physical->type &
+ (PHYS_DIRECT|PHYS_BACKGROUND|PHYS_FOREGROUND)) {
+ *dlp = datalink_Destroy(*dlp);
+ found++;
+ } else
+ dlp = &(*dlp)->next;
+
+ if (found)
+ bundle_LinksRemoved(bundle);
+}
+
+int
+bundle_DatalinkClone(struct bundle *bundle, struct datalink *dl,
+ const char *name)
+{
+ if (bundle2datalink(bundle, name)) {
+ log_Printf(LogWARN, "Clone: %s: name already exists\n", name);
+ return 0;
+ }
+
+ bundle_DatalinkLinkin(bundle, datalink_Clone(dl, name));
+ return 1;
+}
+
+void
+bundle_DatalinkRemove(struct bundle *bundle, struct datalink *dl)
+{
+ dl = bundle_DatalinkLinkout(bundle, dl);
+ if (dl)
+ datalink_Destroy(dl);
+}
+
+void
+bundle_SetLabel(struct bundle *bundle, const char *label)
+{
+ if (label)
+ strncpy(bundle->cfg.label, label, sizeof bundle->cfg.label - 1);
+ else
+ *bundle->cfg.label = '\0';
+}
+
+const char *
+bundle_GetLabel(struct bundle *bundle)
+{
+ return *bundle->cfg.label ? bundle->cfg.label : NULL;
+}
+
+int
+bundle_LinkSize()
+{
+ struct iovec iov[SCATTER_SEGMENTS];
+ int niov, expect, f;
+
+ iov[0].iov_len = strlen(Version) + 1;
+ iov[0].iov_base = NULL;
+ niov = 1;
+ if (datalink2iov(NULL, iov, &niov, SCATTER_SEGMENTS, NULL, NULL) == -1) {
+ log_Printf(LogERROR, "Cannot determine space required for link\n");
+ return 0;
+ }
+
+ for (f = expect = 0; f < niov; f++)
+ expect += iov[f].iov_len;
+
+ return expect;
+}
+
+void
+bundle_ReceiveDatalink(struct bundle *bundle, int s)
+{
+ char cmsgbuf[sizeof(struct cmsghdr) + sizeof(int) * SEND_MAXFD];
+ int niov, expect, f, *fd, nfd, onfd;
+ ssize_t got;
+ struct iovec iov[SCATTER_SEGMENTS];
+ struct cmsghdr *cmsg;
+ struct msghdr msg;
+ struct datalink *dl;
+ pid_t pid;
+
+ log_Printf(LogPHASE, "Receiving datalink\n");
+
+ /*
+ * Create our scatter/gather array - passing NULL gets the space
+ * allocation requirement rather than actually flattening the
+ * structures.
+ */
+ iov[0].iov_len = strlen(Version) + 1;
+ iov[0].iov_base = NULL;
+ niov = 1;
+ if (datalink2iov(NULL, iov, &niov, SCATTER_SEGMENTS, NULL, NULL) == -1) {
+ log_Printf(LogERROR, "Cannot determine space required for link\n");
+ return;
+ }
+
+ /* Allocate the scatter/gather array for recvmsg() */
+ for (f = expect = 0; f < niov; f++) {
+ if ((iov[f].iov_base = malloc(iov[f].iov_len)) == NULL) {
+ log_Printf(LogERROR, "Cannot allocate space to receive link\n");
+ return;
+ }
+ if (f)
+ expect += iov[f].iov_len;
+ }
+
+ /* Set up our message */
+ cmsg = (struct cmsghdr *)cmsgbuf;
+ cmsg->cmsg_len = sizeof cmsgbuf;
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = 0;
+
+ memset(&msg, '\0', sizeof msg);
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1; /* Only send the version at the first pass */
+ msg.msg_control = cmsgbuf;
+ msg.msg_controllen = sizeof cmsgbuf;
+
+ log_Printf(LogDEBUG, "Expecting %u scatter/gather bytes\n",
+ (unsigned)iov[0].iov_len);
+
+ if ((got = recvmsg(s, &msg, MSG_WAITALL)) != (ssize_t)iov[0].iov_len) {
+ if (got == -1)
+ log_Printf(LogERROR, "Failed recvmsg: %s\n", strerror(errno));
+ else
+ log_Printf(LogERROR, "Failed recvmsg: Got %zd, not %u\n",
+ got, (unsigned)iov[0].iov_len);
+ while (niov--)
+ free(iov[niov].iov_base);
+ return;
+ }
+
+ if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) {
+ log_Printf(LogERROR, "Recvmsg: no descriptors received !\n");
+ while (niov--)
+ free(iov[niov].iov_base);
+ return;
+ }
+
+ fd = (int *)CMSG_DATA(cmsg);
+ nfd = ((caddr_t)cmsg + cmsg->cmsg_len - (caddr_t)fd) / sizeof(int);
+
+ if (nfd < 2) {
+ log_Printf(LogERROR, "Recvmsg: %d descriptor%s received (too few) !\n",
+ nfd, nfd == 1 ? "" : "s");
+ while (nfd--)
+ close(fd[nfd]);
+ while (niov--)
+ free(iov[niov].iov_base);
+ return;
+ }
+
+ /*
+ * We've successfully received two or more open file descriptors
+ * through our socket, plus a version string. Make sure it's the
+ * correct version, and drop the connection if it's not.
+ */
+ if (strncmp(Version, iov[0].iov_base, iov[0].iov_len)) {
+ log_Printf(LogWARN, "Cannot receive datalink, incorrect version"
+ " (\"%.*s\", not \"%s\")\n", (int)iov[0].iov_len,
+ (char *)iov[0].iov_base, Version);
+ while (nfd--)
+ close(fd[nfd]);
+ while (niov--)
+ free(iov[niov].iov_base);
+ return;
+ }
+
+ /*
+ * Everything looks good. Send the other side our process id so that
+ * they can transfer lock ownership, and wait for them to send the
+ * actual link data.
+ */
+ pid = getpid();
+ if ((got = write(fd[1], &pid, sizeof pid)) != sizeof pid) {
+ if (got == -1)
+ log_Printf(LogERROR, "Failed write: %s\n", strerror(errno));
+ else
+ log_Printf(LogERROR, "Failed write: Got %zd, not %d\n", got,
+ (int)(sizeof pid));
+ while (nfd--)
+ close(fd[nfd]);
+ while (niov--)
+ free(iov[niov].iov_base);
+ return;
+ }
+
+ if ((got = readv(fd[1], iov + 1, niov - 1)) != expect) {
+ if (got == -1)
+ log_Printf(LogERROR, "Failed write: %s\n", strerror(errno));
+ else
+ log_Printf(LogERROR, "Failed write: Got %zd, not %d\n", got, expect);
+ while (nfd--)
+ close(fd[nfd]);
+ while (niov--)
+ free(iov[niov].iov_base);
+ return;
+ }
+ close(fd[1]);
+
+ onfd = nfd; /* We've got this many in our array */
+ nfd -= 2; /* Don't include p->fd and our reply descriptor */
+ niov = 1; /* Skip the version id */
+ dl = iov2datalink(bundle, iov, &niov, sizeof iov / sizeof *iov, fd[0],
+ fd + 2, &nfd);
+ if (dl) {
+
+ if (nfd) {
+ log_Printf(LogERROR, "bundle_ReceiveDatalink: Failed to handle %d "
+ "auxiliary file descriptors (%d remain)\n", onfd, nfd);
+ datalink_Destroy(dl);
+ while (nfd--)
+ close(fd[onfd--]);
+ close(fd[0]);
+ } else {
+ bundle_DatalinkLinkin(bundle, dl);
+ datalink_AuthOk(dl);
+ bundle_CalculateBandwidth(dl->bundle);
+ }
+ } else {
+ while (nfd--)
+ close(fd[onfd--]);
+ close(fd[0]);
+ close(fd[1]);
+ }
+
+ free(iov[0].iov_base);
+}
+
+void
+bundle_SendDatalink(struct datalink *dl, int s, struct sockaddr_un *sun)
+{
+ char cmsgbuf[CMSG_SPACE(sizeof(int) * SEND_MAXFD)];
+ const char *constlock;
+ char *lock;
+ struct cmsghdr *cmsg;
+ struct msghdr msg;
+ struct iovec iov[SCATTER_SEGMENTS];
+ int niov, f, expect, newsid, fd[SEND_MAXFD], nfd, reply[2];
+ ssize_t got;
+ pid_t newpid;
+
+ log_Printf(LogPHASE, "Transmitting datalink %s\n", dl->name);
+
+ /* Record the base device name for a lock transfer later */
+ constlock = physical_LockedDevice(dl->physical);
+ if (constlock) {
+ lock = alloca(strlen(constlock) + 1);
+ strcpy(lock, constlock);
+ } else
+ lock = NULL;
+
+ bundle_LinkClosed(dl->bundle, dl);
+ bundle_DatalinkLinkout(dl->bundle, dl);
+
+ /* Build our scatter/gather array */
+ iov[0].iov_len = strlen(Version) + 1;
+ iov[0].iov_base = strdup(Version);
+ niov = 1;
+ nfd = 0;
+
+ fd[0] = datalink2iov(dl, iov, &niov, SCATTER_SEGMENTS, fd + 2, &nfd);
+
+ if (fd[0] != -1 && socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, reply) != -1) {
+ /*
+ * fd[1] is used to get the peer process id back, then to confirm that
+ * we've transferred any device locks to that process id.
+ */
+ fd[1] = reply[1];
+
+ nfd += 2; /* Include fd[0] and fd[1] */
+ memset(&msg, '\0', sizeof msg);
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ /*
+ * Only send the version to start... We used to send the whole lot, but
+ * this caused problems with our RECVBUF size as a single link is about
+ * 22k ! This way, we should bump into no limits.
+ */
+ msg.msg_iovlen = 1;
+ msg.msg_iov = iov;
+ msg.msg_control = cmsgbuf;
+ msg.msg_controllen = CMSG_SPACE(sizeof(int) * nfd);
+ msg.msg_flags = 0;
+
+ cmsg = (struct cmsghdr *)cmsgbuf;
+ cmsg->cmsg_len = msg.msg_controllen;
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+
+ for (f = 0; f < nfd; f++)
+ *((int *)CMSG_DATA(cmsg) + f) = fd[f];
+
+ for (f = 1, expect = 0; f < niov; f++)
+ expect += iov[f].iov_len;
+
+ if (setsockopt(reply[0], SOL_SOCKET, SO_SNDBUF, &expect, sizeof(int)) == -1)
+ log_Printf(LogERROR, "setsockopt(SO_RCVBUF, %d): %s\n", expect,
+ strerror(errno));
+ if (setsockopt(reply[1], SOL_SOCKET, SO_RCVBUF, &expect, sizeof(int)) == -1)
+ log_Printf(LogERROR, "setsockopt(SO_RCVBUF, %d): %s\n", expect,
+ strerror(errno));
+
+ log_Printf(LogDEBUG, "Sending %d descriptor%s and %u bytes in scatter"
+ "/gather array\n", nfd, nfd == 1 ? "" : "s",
+ (unsigned)iov[0].iov_len);
+
+ if ((got = sendmsg(s, &msg, 0)) == -1)
+ log_Printf(LogERROR, "Failed sendmsg: %s: %s\n",
+ sun->sun_path, strerror(errno));
+ else if (got != (ssize_t)iov[0].iov_len)
+ log_Printf(LogERROR, "%s: Failed initial sendmsg: Only sent %zd of %u\n",
+ sun->sun_path, got, (unsigned)iov[0].iov_len);
+ else {
+ /* We must get the ACK before closing the descriptor ! */
+ int res;
+
+ if ((got = read(reply[0], &newpid, sizeof newpid)) == sizeof newpid) {
+ log_Printf(LogDEBUG, "Received confirmation from pid %ld\n",
+ (long)newpid);
+ if (lock && (res = ID0uu_lock_txfr(lock, newpid)) != UU_LOCK_OK)
+ log_Printf(LogERROR, "uu_lock_txfr: %s\n", uu_lockerr(res));
+
+ log_Printf(LogDEBUG, "Transmitting link (%d bytes)\n", expect);
+ if ((got = writev(reply[0], iov + 1, niov - 1)) != expect) {
+ if (got == -1)
+ log_Printf(LogERROR, "%s: Failed writev: %s\n",
+ sun->sun_path, strerror(errno));
+ else
+ log_Printf(LogERROR, "%s: Failed writev: Wrote %zd of %d\n",
+ sun->sun_path, got, expect);
+ }
+ } else if (got == -1)
+ log_Printf(LogERROR, "%s: Failed socketpair read: %s\n",
+ sun->sun_path, strerror(errno));
+ else
+ log_Printf(LogERROR, "%s: Failed socketpair read: Got %zd of %d\n",
+ sun->sun_path, got, (int)(sizeof newpid));
+ }
+
+ close(reply[0]);
+ close(reply[1]);
+
+ newsid = Enabled(dl->bundle, OPT_KEEPSESSION) ||
+ tcgetpgrp(fd[0]) == getpgrp();
+ while (nfd)
+ close(fd[--nfd]);
+ if (newsid)
+ bundle_setsid(dl->bundle, got != -1);
+ }
+ close(s);
+
+ while (niov--)
+ free(iov[niov].iov_base);
+}
+
+int
+bundle_RenameDatalink(struct bundle *bundle, struct datalink *ndl,
+ const char *name)
+{
+ struct datalink *dl;
+
+ if (!strcasecmp(ndl->name, name))
+ return 1;
+
+ for (dl = bundle->links; dl; dl = dl->next)
+ if (!strcasecmp(dl->name, name))
+ return 0;
+
+ datalink_Rename(ndl, name);
+ return 1;
+}
+
+int
+bundle_SetMode(struct bundle *bundle, struct datalink *dl, int mode)
+{
+ int omode;
+
+ omode = dl->physical->type;
+ if (omode == mode)
+ return 1;
+
+ if (mode == PHYS_AUTO && !(bundle->phys_type.all & PHYS_AUTO))
+ /* First auto link */
+ if (bundle->ncp.ipcp.peer_ip.s_addr == INADDR_ANY) {
+ log_Printf(LogWARN, "You must `set ifaddr' or `open' before"
+ " changing mode to %s\n", mode2Nam(mode));
+ return 0;
+ }
+
+ if (!datalink_SetMode(dl, mode))
+ return 0;
+
+ if (mode == PHYS_AUTO && !(bundle->phys_type.all & PHYS_AUTO) &&
+ bundle->phase != PHASE_NETWORK)
+ /* First auto link, we need an interface */
+ ipcp_InterfaceUp(&bundle->ncp.ipcp);
+
+ /* Regenerate phys_type and adjust idle timer */
+ bundle_LinksRemoved(bundle);
+
+ return 1;
+}
+
+void
+bundle_setsid(struct bundle *bundle, int holdsession)
+{
+ /*
+ * Lose the current session. This means getting rid of our pid
+ * too so that the tty device will really go away, and any getty
+ * etc will be allowed to restart.
+ */
+ pid_t pid, orig;
+ int fds[2];
+ char done;
+ struct datalink *dl;
+
+ if (!holdsession && bundle_IsDead(bundle)) {
+ /*
+ * No need to lose our session after all... we're going away anyway
+ *
+ * We should really stop the timer and pause if holdsession is set and
+ * the bundle's dead, but that leaves other resources lying about :-(
+ */
+ return;
+ }
+
+ orig = getpid();
+ if (pipe(fds) == -1) {
+ log_Printf(LogERROR, "pipe: %s\n", strerror(errno));
+ return;
+ }
+ switch ((pid = fork())) {
+ case -1:
+ log_Printf(LogERROR, "fork: %s\n", strerror(errno));
+ close(fds[0]);
+ close(fds[1]);
+ return;
+ case 0:
+ close(fds[1]);
+ read(fds[0], &done, 1); /* uu_locks are mine ! */
+ close(fds[0]);
+ if (pipe(fds) == -1) {
+ log_Printf(LogERROR, "pipe(2): %s\n", strerror(errno));
+ return;
+ }
+ switch ((pid = fork())) {
+ case -1:
+ log_Printf(LogERROR, "fork(2): %s\n", strerror(errno));
+ close(fds[0]);
+ close(fds[1]);
+ return;
+ case 0:
+ close(fds[1]);
+ bundle_LockTun(bundle); /* update pid */
+ read(fds[0], &done, 1); /* uu_locks are mine ! */
+ close(fds[0]);
+ setsid();
+ bundle_ChangedPID(bundle);
+ log_Printf(LogDEBUG, "%ld -> %ld: %s session control\n",
+ (long)orig, (long)getpid(),
+ holdsession ? "Passed" : "Dropped");
+ timer_InitService(0); /* Start the Timer Service */
+ break;
+ default:
+ close(fds[0]);
+ /* Give away all our physical locks (to the final process) */
+ for (dl = bundle->links; dl; dl = dl->next)
+ if (dl->state != DATALINK_CLOSED)
+ physical_ChangedPid(dl->physical, pid);
+ write(fds[1], "!", 1); /* done */
+ close(fds[1]);
+ _exit(0);
+ break;
+ }
+ break;
+ default:
+ close(fds[0]);
+ /* Give away all our physical locks (to the intermediate process) */
+ for (dl = bundle->links; dl; dl = dl->next)
+ if (dl->state != DATALINK_CLOSED)
+ physical_ChangedPid(dl->physical, pid);
+ write(fds[1], "!", 1); /* done */
+ close(fds[1]);
+ if (holdsession) {
+ int fd, status;
+
+ timer_TermService();
+ signal(SIGPIPE, SIG_DFL);
+ signal(SIGALRM, SIG_DFL);
+ signal(SIGHUP, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGINT, SIG_DFL);
+ signal(SIGQUIT, SIG_DFL);
+ for (fd = getdtablesize(); fd >= 0; fd--)
+ close(fd);
+ /*
+ * Reap the intermediate process. As we're not exiting but the
+ * intermediate is, we don't want it to become defunct.
+ */
+ waitpid(pid, &status, 0);
+ /* Tweak our process arguments.... */
+ SetTitle("session owner");
+#ifndef NOSUID
+ setuid(ID0realuid());
+#endif
+ /*
+ * Hang around for a HUP. This should happen as soon as the
+ * ppp that we passed our ctty descriptor to closes it.
+ * NOTE: If this process dies, the passed descriptor becomes
+ * invalid and will give a select() error by setting one
+ * of the error fds, aborting the other ppp. We don't
+ * want that to happen !
+ */
+ pause();
+ }
+ _exit(0);
+ break;
+ }
+}
+
+unsigned
+bundle_HighestState(struct bundle *bundle)
+{
+ struct datalink *dl;
+ unsigned result = DATALINK_CLOSED;
+
+ for (dl = bundle->links; dl; dl = dl->next)
+ if (result < dl->state)
+ result = dl->state;
+
+ return result;
+}
+
+int
+bundle_Exception(struct bundle *bundle, int fd)
+{
+ struct datalink *dl;
+
+ for (dl = bundle->links; dl; dl = dl->next)
+ if (dl->physical->fd == fd) {
+ datalink_Down(dl, CLOSE_NORMAL);
+ return 1;
+ }
+
+ return 0;
+}
+
+void
+bundle_AdjustFilters(struct bundle *bundle, struct ncpaddr *local,
+ struct ncpaddr *remote)
+{
+ filter_AdjustAddr(&bundle->filter.in, local, remote, NULL);
+ filter_AdjustAddr(&bundle->filter.out, local, remote, NULL);
+ filter_AdjustAddr(&bundle->filter.dial, local, remote, NULL);
+ filter_AdjustAddr(&bundle->filter.alive, local, remote, NULL);
+}
+
+void
+bundle_AdjustDNS(struct bundle *bundle)
+{
+ struct in_addr *dns = bundle->ncp.ipcp.ns.dns;
+
+ filter_AdjustAddr(&bundle->filter.in, NULL, NULL, dns);
+ filter_AdjustAddr(&bundle->filter.out, NULL, NULL, dns);
+ filter_AdjustAddr(&bundle->filter.dial, NULL, NULL, dns);
+ filter_AdjustAddr(&bundle->filter.alive, NULL, NULL, dns);
+}
+
+void
+bundle_CalculateBandwidth(struct bundle *bundle)
+{
+ struct datalink *dl;
+ int sp, overhead, maxoverhead;
+
+ bundle->bandwidth = 0;
+ bundle->iface->mtu = 0;
+ maxoverhead = 0;
+
+ for (dl = bundle->links; dl; dl = dl->next) {
+ overhead = ccp_MTUOverhead(&dl->physical->link.ccp);
+ if (maxoverhead < overhead)
+ maxoverhead = overhead;
+ if (dl->state == DATALINK_OPEN) {
+ if ((sp = dl->mp.bandwidth) == 0 &&
+ (sp = physical_GetSpeed(dl->physical)) == 0)
+ log_Printf(LogDEBUG, "%s: %s: Cannot determine bandwidth\n",
+ dl->name, dl->physical->name.full);
+ else
+ bundle->bandwidth += sp;
+ if (!bundle->ncp.mp.active) {
+ bundle->iface->mtu = dl->physical->link.lcp.his_mru;
+ break;
+ }
+ }
+ }
+
+ if (bundle->bandwidth == 0)
+ bundle->bandwidth = 115200; /* Shrug */
+
+ if (bundle->ncp.mp.active) {
+ bundle->iface->mtu = bundle->ncp.mp.peer_mrru;
+ overhead = ccp_MTUOverhead(&bundle->ncp.mp.link.ccp);
+ if (maxoverhead < overhead)
+ maxoverhead = overhead;
+ } else if (!bundle->iface->mtu)
+ bundle->iface->mtu = DEF_MRU;
+
+#ifndef NORADIUS
+ if (bundle->radius.valid && bundle->radius.mtu &&
+ bundle->radius.mtu < bundle->iface->mtu) {
+ log_Printf(LogLCP, "Reducing MTU to radius value %lu\n",
+ bundle->radius.mtu);
+ bundle->iface->mtu = bundle->radius.mtu;
+ }
+#endif
+
+ if (maxoverhead) {
+ log_Printf(LogLCP, "Reducing MTU from %lu to %lu (CCP requirement)\n",
+ bundle->iface->mtu, bundle->iface->mtu - maxoverhead);
+ bundle->iface->mtu -= maxoverhead;
+ }
+
+ tun_configure(bundle);
+
+ route_UpdateMTU(bundle);
+}
+
+void
+bundle_AutoAdjust(struct bundle *bundle, int percent, int what)
+{
+ struct datalink *dl, *choice, *otherlinkup;
+
+ choice = otherlinkup = NULL;
+ for (dl = bundle->links; dl; dl = dl->next)
+ if (dl->physical->type == PHYS_AUTO) {
+ if (dl->state == DATALINK_OPEN) {
+ if (what == AUTO_DOWN) {
+ if (choice)
+ otherlinkup = choice;
+ choice = dl;
+ }
+ } else if (dl->state == DATALINK_CLOSED) {
+ if (what == AUTO_UP) {
+ choice = dl;
+ break;
+ }
+ } else {
+ /* An auto link in an intermediate state - forget it for the moment */
+ choice = NULL;
+ break;
+ }
+ } else if (dl->state == DATALINK_OPEN && what == AUTO_DOWN)
+ otherlinkup = dl;
+
+ if (choice) {
+ if (what == AUTO_UP) {
+ log_Printf(LogPHASE, "%d%% saturation -> Opening link ``%s''\n",
+ percent, choice->name);
+ datalink_Up(choice, 1, 1);
+ mp_CheckAutoloadTimer(&bundle->ncp.mp);
+ } else if (otherlinkup) { /* Only bring the second-last link down */
+ log_Printf(LogPHASE, "%d%% saturation -> Closing link ``%s''\n",
+ percent, choice->name);
+ datalink_Close(choice, CLOSE_STAYDOWN);
+ mp_CheckAutoloadTimer(&bundle->ncp.mp);
+ }
+ }
+}
+
+int
+bundle_WantAutoloadTimer(struct bundle *bundle)
+{
+ struct datalink *dl;
+ int autolink, opened;
+
+ if (bundle->phase == PHASE_NETWORK) {
+ for (autolink = opened = 0, dl = bundle->links; dl; dl = dl->next)
+ if (dl->physical->type == PHYS_AUTO) {
+ if (++autolink == 2 || (autolink == 1 && opened))
+ /* Two auto links or one auto and one open in NETWORK phase */
+ return 1;
+ } else if (dl->state == DATALINK_OPEN) {
+ opened++;
+ if (autolink)
+ /* One auto and one open link in NETWORK phase */
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void
+bundle_ChangedPID(struct bundle *bundle)
+{
+#ifdef TUNSIFPID
+ ioctl(bundle->dev.fd, TUNSIFPID, 0);
+#endif
+}
+
+int
+bundle_Uptime(struct bundle *bundle)
+{
+ if (bundle->upat)
+ return time(NULL) - bundle->upat;
+
+ return 0;
+}
diff --git a/usr.sbin/ppp/bundle.h b/usr.sbin/ppp/bundle.h
new file mode 100644
index 0000000..e2f9e7f
--- /dev/null
+++ b/usr.sbin/ppp/bundle.h
@@ -0,0 +1,216 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.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$
+ */
+
+#define PHASE_DEAD 0 /* Link is dead */
+#define PHASE_ESTABLISH 1 /* Establishing link */
+#define PHASE_AUTHENTICATE 2 /* Being authenticated */
+#define PHASE_NETWORK 3 /* We're alive ! */
+#define PHASE_TERMINATE 4 /* Terminating link */
+
+/* cfg.opt bit settings */
+#define OPT_FILTERDECAP 1
+#define OPT_FORCE_SCRIPTS 2 /* force chat scripts */
+#define OPT_IDCHECK 3
+#define OPT_IFACEALIAS 4
+#ifndef NOINET6
+#define OPT_IPCP 5
+#define OPT_IPV6CP 6
+#endif
+#define OPT_KEEPSESSION 7
+#define OPT_LOOPBACK 8
+#define OPT_NAS_IP_ADDRESS 9
+#define OPT_NAS_IDENTIFIER 10
+#define OPT_PASSWDAUTH 11
+#define OPT_PROXY 12
+#define OPT_PROXYALL 13
+#define OPT_SROUTES 14
+#define OPT_TCPMSSFIXUP 15
+#define OPT_THROUGHPUT 16
+#define OPT_UTMP 17
+#define OPT_MAX 17
+
+#define MAX_ENDDISC_CLASS 5
+
+#define Enabled(b, o) ((b)->cfg.optmask & (1ull << (o)))
+#define opt_enable(b, o) ((b)->cfg.optmask |= (1ull << (o)))
+#define opt_disable(b, o) ((b)->cfg.optmask &= ~(1ull << (o)))
+
+/* AutoAdjust() values */
+#define AUTO_UP 1
+#define AUTO_DOWN 2
+
+struct sockaddr_un;
+struct datalink;
+struct physical;
+struct link;
+struct server;
+struct prompt;
+struct iface;
+
+struct bundle {
+ struct fdescriptor desc; /* really all our datalinks */
+ int unit; /* The device/interface unit number */
+
+ struct {
+ char Name[20]; /* The /dev/XXXX name */
+ int fd; /* The /dev/XXXX descriptor */
+ unsigned header : 1; /* Family header sent & received ? */
+ } dev;
+
+ u_long bandwidth; /* struct tuninfo speed */
+ struct iface *iface; /* Interface information */
+
+ int routing_seq; /* The current routing sequence number */
+ u_int phase; /* Curent phase */
+
+ struct {
+ int all; /* Union of all physical::type's */
+ int open; /* Union of all open physical::type's */
+ } phys_type;
+
+ unsigned CleaningUp : 1; /* Going to exit.... */
+ unsigned NatEnabled : 1; /* Are we using libalias ? */
+
+ struct fsm_parent fsm; /* Our callback functions */
+ struct datalink *links; /* Our data links */
+
+ time_t upat; /* When the link came up */
+
+ struct {
+ struct {
+ unsigned timeout; /* NCP Idle timeout value */
+ unsigned min_timeout; /* Don't idle out before this */
+ } idle;
+ struct {
+ char name[AUTHLEN]; /* PAP/CHAP system name */
+ char key[AUTHLEN]; /* PAP/CHAP key */
+ } auth;
+ unsigned long long optmask; /* Uses OPT_ bits from above */
+ char label[50]; /* last thing `load'ed */
+ u_short ifqueue; /* Interface queue size */
+
+ struct {
+ unsigned timeout; /* How long to leave the output queue choked */
+ } choked;
+ } cfg;
+
+ struct ncp ncp;
+
+ struct {
+ struct filter in; /* incoming packet filter */
+ struct filter out; /* outgoing packet filter */
+ struct filter dial; /* dial-out packet filter */
+ struct filter alive; /* keep-alive packet filter */
+ } filter;
+
+ struct {
+ struct pppTimer timer; /* timeout after cfg.idle_timeout */
+ time_t done;
+ } idle;
+
+#ifndef NORADIUS
+ struct {
+ struct pppTimer timer;
+ time_t done;
+ } session;
+#endif
+
+ struct {
+ int fd; /* write status here */
+ } notify;
+
+ struct {
+ struct pppTimer timer; /* choked output queue timer */
+ } choked;
+
+#ifndef NORADIUS
+ struct radius radius; /* Info retrieved from radius server */
+ struct radacct radacct;
+#ifndef NOINET6
+ struct radacct radacct6;
+#endif
+#endif
+};
+
+#define descriptor2bundle(d) \
+ ((d)->type == BUNDLE_DESCRIPTOR ? (struct bundle *)(d) : NULL)
+
+extern struct bundle *bundle_Create(const char *, int, int);
+extern void bundle_Destroy(struct bundle *);
+extern const char *bundle_PhaseName(struct bundle *);
+#define bundle_Phase(b) ((b)->phase)
+extern void bundle_NewPhase(struct bundle *, u_int);
+extern void bundle_LinksRemoved(struct bundle *);
+extern void bundle_Close(struct bundle *, const char *, int);
+extern void bundle_Down(struct bundle *, int);
+extern void bundle_Open(struct bundle *, const char *, int, int);
+extern void bundle_LinkClosed(struct bundle *, struct datalink *);
+
+extern int bundle_ShowLinks(struct cmdargs const *);
+extern int bundle_ShowStatus(struct cmdargs const *);
+extern void bundle_StartIdleTimer(struct bundle *, unsigned secs);
+extern void bundle_SetIdleTimer(struct bundle *, unsigned, unsigned);
+extern void bundle_StopIdleTimer(struct bundle *);
+extern int bundle_IsDead(struct bundle *);
+extern struct datalink *bundle2datalink(struct bundle *, const char *);
+
+#ifndef NORADIUS
+extern void bundle_StartSessionTimer(struct bundle *, unsigned secs);
+extern void bundle_StopSessionTimer(struct bundle *);
+#endif
+
+extern void bundle_RegisterDescriptor(struct bundle *, struct fdescriptor *);
+extern void bundle_UnRegisterDescriptor(struct bundle *, struct fdescriptor *);
+
+extern void bundle_SetTtyCommandMode(struct bundle *, struct datalink *);
+
+extern int bundle_DatalinkClone(struct bundle *, struct datalink *,
+ const char *);
+extern void bundle_DatalinkRemove(struct bundle *, struct datalink *);
+extern void bundle_CleanDatalinks(struct bundle *);
+extern void bundle_SetLabel(struct bundle *, const char *);
+extern const char *bundle_GetLabel(struct bundle *);
+extern void bundle_SendDatalink(struct datalink *, int, struct sockaddr_un *);
+extern int bundle_LinkSize(void);
+extern void bundle_ReceiveDatalink(struct bundle *, int);
+extern int bundle_SetMode(struct bundle *, struct datalink *, int);
+extern int bundle_RenameDatalink(struct bundle *, struct datalink *,
+ const char *);
+extern void bundle_setsid(struct bundle *, int);
+extern void bundle_LockTun(struct bundle *);
+extern unsigned bundle_HighestState(struct bundle *);
+extern int bundle_Exception(struct bundle *, int);
+extern void bundle_AdjustFilters(struct bundle *, struct ncpaddr *,
+ struct ncpaddr *);
+extern void bundle_AdjustDNS(struct bundle *);
+extern void bundle_CalculateBandwidth(struct bundle *);
+extern void bundle_AutoAdjust(struct bundle *, int, int);
+extern int bundle_WantAutoloadTimer(struct bundle *);
+extern void bundle_ChangedPID(struct bundle *);
+extern void bundle_Notify(struct bundle *, char);
+extern int bundle_Uptime(struct bundle *);
diff --git a/usr.sbin/ppp/cbcp.c b/usr.sbin/ppp/cbcp.c
new file mode 100644
index 0000000..23366c8
--- /dev/null
+++ b/usr.sbin/ppp/cbcp.c
@@ -0,0 +1,763 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.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/param.h>
+
+#ifdef __FreeBSD__
+#include <netinet/in.h>
+#endif
+#include <sys/un.h>
+
+#include <string.h>
+#include <termios.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "log.h"
+#include "timer.h"
+#include "descriptor.h"
+#include "lqr.h"
+#include "mbuf.h"
+#include "fsm.h"
+#include "throughput.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "async.h"
+#include "physical.h"
+#include "proto.h"
+#include "cbcp.h"
+#include "mp.h"
+#include "chat.h"
+#include "auth.h"
+#include "chap.h"
+#include "datalink.h"
+
+void
+cbcp_Init(struct cbcp *cbcp, struct physical *p)
+{
+ cbcp->required = 0;
+ cbcp->fsm.state = CBCP_CLOSED;
+ cbcp->fsm.id = 0;
+ cbcp->fsm.delay = 0;
+ *cbcp->fsm.phone = '\0';
+ memset(&cbcp->fsm.timer, '\0', sizeof cbcp->fsm.timer);
+ cbcp->p = p;
+}
+
+static void cbcp_SendReq(struct cbcp *);
+static void cbcp_SendResponse(struct cbcp *);
+static void cbcp_SendAck(struct cbcp *);
+
+static void
+cbcp_Timeout(void *v)
+{
+ struct cbcp *cbcp = (struct cbcp *)v;
+
+ timer_Stop(&cbcp->fsm.timer);
+ if (cbcp->fsm.restart) {
+ switch (cbcp->fsm.state) {
+ case CBCP_CLOSED:
+ case CBCP_STOPPED:
+ log_Printf(LogCBCP, "%s: Urk - unexpected CBCP timeout !\n",
+ cbcp->p->dl->name);
+ break;
+
+ case CBCP_REQSENT:
+ cbcp_SendReq(cbcp);
+ break;
+ case CBCP_RESPSENT:
+ cbcp_SendResponse(cbcp);
+ break;
+ case CBCP_ACKSENT:
+ cbcp_SendAck(cbcp);
+ break;
+ }
+ } else {
+ const char *missed;
+
+ switch (cbcp->fsm.state) {
+ case CBCP_STOPPED:
+ missed = "REQ";
+ break;
+ case CBCP_REQSENT:
+ missed = "RESPONSE";
+ break;
+ case CBCP_RESPSENT:
+ missed = "ACK";
+ break;
+ case CBCP_ACKSENT:
+ missed = "Terminate REQ";
+ break;
+ default:
+ log_Printf(LogCBCP, "%s: Urk - unexpected CBCP timeout !\n",
+ cbcp->p->dl->name);
+ missed = NULL;
+ break;
+ }
+ if (missed)
+ log_Printf(LogCBCP, "%s: Timeout waiting for peer %s\n",
+ cbcp->p->dl->name, missed);
+ datalink_CBCPFailed(cbcp->p->dl);
+ }
+}
+
+static void
+cbcp_StartTimer(struct cbcp *cbcp, int timeout)
+{
+ timer_Stop(&cbcp->fsm.timer);
+ cbcp->fsm.timer.func = cbcp_Timeout;
+ cbcp->fsm.timer.name = "cbcp";
+ cbcp->fsm.timer.load = timeout * SECTICKS;
+ cbcp->fsm.timer.arg = cbcp;
+ timer_Start(&cbcp->fsm.timer);
+}
+
+#define CBCP_CLOSED (0) /* Not in use */
+#define CBCP_STOPPED (1) /* Waiting for a REQ */
+#define CBCP_REQSENT (2) /* Waiting for a RESP */
+#define CBCP_RESPSENT (3) /* Waiting for an ACK */
+#define CBCP_ACKSENT (4) /* Waiting for an LCP Term REQ */
+
+static const char * const cbcpname[] = {
+ "closed", "stopped", "req-sent", "resp-sent", "ack-sent"
+};
+
+static const char *
+cbcpstate(unsigned s)
+{
+ if (s < sizeof cbcpname / sizeof cbcpname[0])
+ return cbcpname[s];
+ return HexStr(s, NULL, 0);
+}
+
+static void
+cbcp_NewPhase(struct cbcp *cbcp, int new)
+{
+ if (cbcp->fsm.state != new) {
+ log_Printf(LogCBCP, "%s: State change %s --> %s\n", cbcp->p->dl->name,
+ cbcpstate(cbcp->fsm.state), cbcpstate(new));
+ cbcp->fsm.state = new;
+ }
+}
+
+struct cbcp_header {
+ u_char code;
+ u_char id;
+ u_int16_t length; /* Network byte order */
+};
+
+
+/* cbcp_header::code values */
+#define CBCP_REQ (1)
+#define CBCP_RESPONSE (2)
+#define CBCP_ACK (3)
+
+struct cbcp_data {
+ u_char type;
+ u_char length;
+ u_char delay;
+ char addr_start[253]; /* max cbcp_data length 255 + 1 for NULL */
+};
+
+/* cbcp_data::type values */
+#define CBCP_NONUM (1)
+#define CBCP_CLIENTNUM (2)
+#define CBCP_SERVERNUM (3)
+#define CBCP_LISTNUM (4)
+
+static void
+cbcp_Output(struct cbcp *cbcp, u_char code, struct cbcp_data *data)
+{
+ struct cbcp_header *head;
+ struct mbuf *bp;
+
+ bp = m_get(sizeof *head + data->length, MB_CBCPOUT);
+ head = (struct cbcp_header *)MBUF_CTOP(bp);
+ head->code = code;
+ head->id = cbcp->fsm.id;
+ head->length = htons(sizeof *head + data->length);
+ memcpy(MBUF_CTOP(bp) + sizeof *head, data, data->length);
+ log_DumpBp(LogDEBUG, "cbcp_Output", bp);
+ link_PushPacket(&cbcp->p->link, bp, cbcp->p->dl->bundle,
+ LINK_QUEUES(&cbcp->p->link) - 1, PROTO_CBCP);
+}
+
+static const char *
+cbcp_data_Type(unsigned type)
+{
+ static const char * const types[] = {
+ "No callback", "User-spec", "Server-spec", "list"
+ };
+
+ if (type < 1 || type > sizeof types / sizeof types[0])
+ return HexStr(type, NULL, 0);
+ return types[type-1];
+}
+
+struct cbcp_addr {
+ u_char type;
+ char addr[sizeof ((struct cbcp_data *)0)->addr_start - 1]; /* ASCIIZ */
+};
+
+/* cbcp_data::type values */
+#define CBCP_ADDR_PSTN (1)
+
+static void
+cbcp_data_Show(struct cbcp_data *data)
+{
+ struct cbcp_addr *addr;
+ char *end;
+
+ addr = (struct cbcp_addr *)data->addr_start;
+ end = (char *)data + data->length;
+ *end = '\0';
+
+ log_Printf(LogCBCP, " TYPE %s\n", cbcp_data_Type(data->type));
+ if ((char *)&data->delay < end) {
+ log_Printf(LogCBCP, " DELAY %d\n", data->delay);
+ while (addr->addr < end) {
+ if (addr->type == CBCP_ADDR_PSTN)
+ log_Printf(LogCBCP, " ADDR %s\n", addr->addr);
+ else
+ log_Printf(LogCBCP, " ADDR type %d ??\n", (int)addr->type);
+ addr = (struct cbcp_addr *)(addr->addr + strlen(addr->addr) + 1);
+ }
+ }
+}
+
+static void
+cbcp_SendReq(struct cbcp *cbcp)
+{
+ struct cbcp_data data;
+ struct cbcp_addr *addr;
+ char list[sizeof cbcp->fsm.phone], *next;
+ int len, max;
+
+ /* Only callees send REQs */
+
+ log_Printf(LogCBCP, "%s: SendReq(%d) state = %s\n", cbcp->p->dl->name,
+ cbcp->fsm.id, cbcpstate(cbcp->fsm.state));
+ data.type = cbcp->fsm.type;
+ data.delay = 0;
+ strncpy(list, cbcp->fsm.phone, sizeof list - 1);
+ list[sizeof list - 1] = '\0';
+
+ switch (data.type) {
+ case CBCP_CLIENTNUM:
+ addr = (struct cbcp_addr *)data.addr_start;
+ addr->type = CBCP_ADDR_PSTN;
+ *addr->addr = '\0';
+ data.length = addr->addr - (char *)&data;
+ break;
+
+ case CBCP_LISTNUM:
+ addr = (struct cbcp_addr *)data.addr_start;
+ for (next = strtok(list, ","); next; next = strtok(NULL, ",")) {
+ len = strlen(next);
+ max = data.addr_start + sizeof data.addr_start - addr->addr - 1;
+ if (len <= max) {
+ addr->type = CBCP_ADDR_PSTN;
+ strncpy(addr->addr, next, sizeof addr->addr - 1);
+ addr->addr[sizeof addr->addr - 1] = '\0';
+ addr = (struct cbcp_addr *)((char *)addr + len + 2);
+ } else
+ log_Printf(LogWARN, "CBCP ADDR \"%s\" skipped - packet too large\n",
+ next);
+ }
+ data.length = (char *)addr - (char *)&data;
+ break;
+
+ case CBCP_SERVERNUM:
+ data.length = data.addr_start - (char *)&data;
+ break;
+
+ default:
+ data.length = (char *)&data.delay - (char *)&data;
+ break;
+ }
+
+ cbcp_data_Show(&data);
+ cbcp_Output(cbcp, CBCP_REQ, &data);
+ cbcp->fsm.restart--;
+ cbcp_StartTimer(cbcp, cbcp->fsm.delay);
+ cbcp_NewPhase(cbcp, CBCP_REQSENT); /* Wait for a RESPONSE */
+}
+
+void
+cbcp_Up(struct cbcp *cbcp)
+{
+ struct lcp *lcp = &cbcp->p->link.lcp;
+
+ cbcp->fsm.delay = cbcp->p->dl->cfg.cbcp.delay;
+ if (*cbcp->p->dl->peer.authname == '\0' ||
+ !auth_SetPhoneList(cbcp->p->dl->peer.authname, cbcp->fsm.phone,
+ sizeof cbcp->fsm.phone)) {
+ strncpy(cbcp->fsm.phone, cbcp->p->dl->cfg.cbcp.phone,
+ sizeof cbcp->fsm.phone - 1);
+ cbcp->fsm.phone[sizeof cbcp->fsm.phone - 1] = '\0';
+ }
+
+ if (lcp->want_callback.opmask) {
+ if (*cbcp->fsm.phone == '\0')
+ cbcp->fsm.type = CBCP_NONUM;
+ else if (!strcmp(cbcp->fsm.phone, "*")) {
+ cbcp->fsm.type = CBCP_SERVERNUM;
+ *cbcp->fsm.phone = '\0';
+ } else
+ cbcp->fsm.type = CBCP_CLIENTNUM;
+ cbcp_NewPhase(cbcp, CBCP_STOPPED); /* Wait for a REQ */
+ cbcp_StartTimer(cbcp, cbcp->fsm.delay * DEF_FSMTRIES);
+ } else {
+ if (*cbcp->fsm.phone == '\0')
+ cbcp->fsm.type = CBCP_NONUM;
+ else if (!strcmp(cbcp->fsm.phone, "*")) {
+ cbcp->fsm.type = CBCP_CLIENTNUM;
+ *cbcp->fsm.phone = '\0';
+ } else if (strchr(cbcp->fsm.phone, ','))
+ cbcp->fsm.type = CBCP_LISTNUM;
+ else
+ cbcp->fsm.type = CBCP_SERVERNUM;
+ cbcp->fsm.restart = DEF_FSMTRIES;
+ cbcp_SendReq(cbcp);
+ }
+}
+
+static int
+cbcp_AdjustResponse(struct cbcp *cbcp, struct cbcp_data *data)
+{
+ /*
+ * We've received a REQ (data). Adjust our response (cbcp->fsm.*)
+ * so that we (hopefully) agree with the peer
+ */
+ struct cbcp_addr *addr;
+
+ switch (data->type) {
+ case CBCP_NONUM:
+ if (cbcp->p->dl->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_NONE))
+ /*
+ * if ``none'' is a configured callback possibility
+ * (ie, ``set callback cbcp none''), go along with the callees
+ * request
+ */
+ cbcp->fsm.type = CBCP_NONUM;
+
+ /*
+ * Otherwise, we send our desired response anyway. This seems to be
+ * what Win95 does - although I can't find this behaviour documented
+ * in the CBCP spec....
+ */
+
+ return 1;
+
+ case CBCP_CLIENTNUM:
+ if (cbcp->fsm.type == CBCP_CLIENTNUM) {
+ char *ptr;
+
+ if (data->length > data->addr_start - (char *)data) {
+ /*
+ * The peer has given us an address type spec - make sure we
+ * understand !
+ */
+ addr = (struct cbcp_addr *)data->addr_start;
+ if (addr->type != CBCP_ADDR_PSTN) {
+ log_Printf(LogPHASE, "CBCP: Unrecognised address type %d !\n",
+ (int)addr->type);
+ return 0;
+ }
+ }
+ /* we accept the REQ even if the peer didn't specify an addr->type */
+ ptr = strchr(cbcp->fsm.phone, ',');
+ if (ptr)
+ *ptr = '\0'; /* Just use the first number in our list */
+ return 1;
+ }
+ log_Printf(LogPHASE, "CBCP: no number to pass to the peer !\n");
+ return 0;
+
+ case CBCP_SERVERNUM:
+ if (cbcp->fsm.type == CBCP_SERVERNUM) {
+ *cbcp->fsm.phone = '\0';
+ return 1;
+ }
+ if (data->length > data->addr_start - (char *)data) {
+ /*
+ * This violates the spec, but if the peer has told us the
+ * number it wants to call back, take advantage of this fact
+ * and allow things to proceed if we've specified the same
+ * number
+ */
+ addr = (struct cbcp_addr *)data->addr_start;
+ if (addr->type != CBCP_ADDR_PSTN) {
+ log_Printf(LogPHASE, "CBCP: Unrecognised address type %d !\n",
+ (int)addr->type);
+ return 0;
+ } else if (cbcp->fsm.type == CBCP_CLIENTNUM) {
+ /*
+ * If the peer's insisting on deciding the number, make sure
+ * it's one of the ones in our list. If it is, let the peer
+ * think it's in control :-)
+ */
+ char list[sizeof cbcp->fsm.phone], *next;
+
+ strncpy(list, cbcp->fsm.phone, sizeof list - 1);
+ list[sizeof list - 1] = '\0';
+ for (next = strtok(list, ","); next; next = strtok(NULL, ","))
+ if (!strcmp(next, addr->addr)) {
+ cbcp->fsm.type = CBCP_SERVERNUM;
+ strcpy(cbcp->fsm.phone, next);
+ return 1;
+ }
+ }
+ }
+ log_Printf(LogPHASE, "CBCP: Peer won't allow local decision !\n");
+ return 0;
+
+ case CBCP_LISTNUM:
+ if (cbcp->fsm.type == CBCP_CLIENTNUM || cbcp->fsm.type == CBCP_LISTNUM) {
+ /*
+ * Search through ``data''s addresses and see if cbcp->fsm.phone
+ * contains any of them
+ */
+ char list[sizeof cbcp->fsm.phone], *next, *end;
+
+ addr = (struct cbcp_addr *)data->addr_start;
+ end = (char *)data + data->length;
+
+ while (addr->addr < end) {
+ if (addr->type == CBCP_ADDR_PSTN) {
+ strncpy(list, cbcp->fsm.phone, sizeof list - 1);
+ list[sizeof list - 1] = '\0';
+ for (next = strtok(list, ","); next; next = strtok(NULL, ","))
+ if (!strcmp(next, addr->addr)) {
+ cbcp->fsm.type = CBCP_LISTNUM;
+ strcpy(cbcp->fsm.phone, next);
+ return 1;
+ }
+ } else
+ log_Printf(LogCBCP, "Warning: Unrecognised address type %d !\n",
+ (int)addr->type);
+ addr = (struct cbcp_addr *)(addr->addr + strlen(addr->addr) + 1);
+ }
+ }
+ log_Printf(LogPHASE, "CBCP: no good number to pass to the peer !\n");
+ return 0;
+ }
+
+ log_Printf(LogCBCP, "Unrecognised REQ type %d !\n", (int)data->type);
+ return 0;
+}
+
+static void
+cbcp_SendResponse(struct cbcp *cbcp)
+{
+ struct cbcp_data data;
+ struct cbcp_addr *addr;
+
+ /* Only callers send RESPONSEs */
+
+ log_Printf(LogCBCP, "%s: SendResponse(%d) state = %s\n", cbcp->p->dl->name,
+ cbcp->fsm.id, cbcpstate(cbcp->fsm.state));
+
+ data.type = cbcp->fsm.type;
+ data.delay = cbcp->fsm.delay;
+ addr = (struct cbcp_addr *)data.addr_start;
+ if (data.type == CBCP_NONUM)
+ data.length = (char *)&data.delay - (char *)&data;
+ else if (*cbcp->fsm.phone) {
+ addr->type = CBCP_ADDR_PSTN;
+ strncpy(addr->addr, cbcp->fsm.phone, sizeof addr->addr - 1);
+ addr->addr[sizeof addr->addr - 1] = '\0';
+ data.length = (addr->addr + strlen(addr->addr) + 1) - (char *)&data;
+ } else
+ data.length = data.addr_start - (char *)&data;
+
+ cbcp_data_Show(&data);
+ cbcp_Output(cbcp, CBCP_RESPONSE, &data);
+ cbcp->fsm.restart--;
+ cbcp_StartTimer(cbcp, cbcp->fsm.delay);
+ cbcp_NewPhase(cbcp, CBCP_RESPSENT); /* Wait for an ACK */
+}
+
+/* What to do after checking an incoming response */
+#define CBCP_ACTION_DOWN (0)
+#define CBCP_ACTION_REQ (1)
+#define CBCP_ACTION_ACK (2)
+
+static int
+cbcp_CheckResponse(struct cbcp *cbcp, struct cbcp_data *data)
+{
+ /*
+ * We've received a RESPONSE (data). Check if it agrees with
+ * our REQ (cbcp->fsm)
+ */
+ struct cbcp_addr *addr;
+
+ addr = (struct cbcp_addr *)data->addr_start;
+
+ if (data->type == cbcp->fsm.type) {
+ switch (cbcp->fsm.type) {
+ case CBCP_NONUM:
+ return CBCP_ACTION_ACK;
+
+ case CBCP_CLIENTNUM:
+ if ((char *)data + data->length <= addr->addr)
+ log_Printf(LogPHASE, "CBCP: peer didn't respond with a number !\n");
+ else if (addr->type != CBCP_ADDR_PSTN)
+ log_Printf(LogPHASE, "CBCP: Unrecognised address type %d !\n",
+ addr->type);
+ else {
+ strncpy(cbcp->fsm.phone, addr->addr, sizeof cbcp->fsm.phone - 1);
+ cbcp->fsm.phone[sizeof cbcp->fsm.phone - 1] = '\0';
+ cbcp->fsm.delay = data->delay;
+ return CBCP_ACTION_ACK;
+ }
+ return CBCP_ACTION_DOWN;
+
+ case CBCP_SERVERNUM:
+ cbcp->fsm.delay = data->delay;
+ return CBCP_ACTION_ACK;
+
+ case CBCP_LISTNUM:
+ if ((char *)data + data->length <= addr->addr)
+ log_Printf(LogPHASE, "CBCP: peer didn't respond with a number !\n");
+ else if (addr->type != CBCP_ADDR_PSTN)
+ log_Printf(LogPHASE, "CBCP: Unrecognised address type %d !\n",
+ addr->type);
+ else {
+ char list[sizeof cbcp->fsm.phone], *next;
+
+ strncpy(list, cbcp->fsm.phone, sizeof list - 1);
+ list[sizeof list - 1] = '\0';
+ for (next = strtok(list, ","); next; next = strtok(NULL, ","))
+ if (!strcmp(addr->addr, next)) {
+ strcpy(cbcp->fsm.phone, next);
+ cbcp->fsm.delay = data->delay;
+ return CBCP_ACTION_ACK;
+ }
+ log_Printf(LogPHASE, "CBCP: peer didn't respond with a "
+ "valid number !\n");
+ }
+ return CBCP_ACTION_DOWN;
+ }
+ log_Printf(LogPHASE, "Internal CBCP error - agreed on %d !\n",
+ (int)cbcp->fsm.type);
+ return CBCP_ACTION_DOWN;
+ } else if (data->type == CBCP_NONUM && cbcp->fsm.type == CBCP_CLIENTNUM) {
+ /*
+ * Client doesn't want CBCP after all....
+ * We only allow this when ``set cbcp *'' has been specified.
+ */
+ cbcp->fsm.type = CBCP_NONUM;
+ return CBCP_ACTION_ACK;
+ }
+ log_Printf(LogCBCP, "Invalid peer RESPONSE\n");
+ return CBCP_ACTION_REQ;
+}
+
+static void
+cbcp_SendAck(struct cbcp *cbcp)
+{
+ struct cbcp_data data;
+ struct cbcp_addr *addr;
+
+ /* Only callees send ACKs */
+
+ log_Printf(LogCBCP, "%s: SendAck(%d) state = %s\n", cbcp->p->dl->name,
+ cbcp->fsm.id, cbcpstate(cbcp->fsm.state));
+
+ data.type = cbcp->fsm.type;
+ switch (data.type) {
+ case CBCP_NONUM:
+ data.length = (char *)&data.delay - (char *)&data;
+ break;
+ case CBCP_CLIENTNUM:
+ addr = (struct cbcp_addr *)data.addr_start;
+ addr->type = CBCP_ADDR_PSTN;
+ strncpy(addr->addr, cbcp->fsm.phone, sizeof addr->addr - 1);
+ addr->addr[sizeof addr->addr - 1] = '\0';
+ data.delay = cbcp->fsm.delay;
+ data.length = addr->addr + strlen(addr->addr) + 1 - (char *)&data;
+ break;
+ default:
+ data.delay = cbcp->fsm.delay;
+ data.length = data.addr_start - (char *)&data;
+ break;
+ }
+
+ cbcp_data_Show(&data);
+ cbcp_Output(cbcp, CBCP_ACK, &data);
+ cbcp->fsm.restart--;
+ cbcp_StartTimer(cbcp, cbcp->fsm.delay);
+ cbcp_NewPhase(cbcp, CBCP_ACKSENT); /* Wait for an ACK */
+}
+
+extern struct mbuf *
+cbcp_Input(struct bundle *bundle __unused, struct link *l, struct mbuf *bp)
+{
+ struct physical *p = link2physical(l);
+ struct cbcp_header *head;
+ struct cbcp_data *data;
+ struct cbcp *cbcp = &p->dl->cbcp;
+ size_t len;
+
+ if (p == NULL) {
+ log_Printf(LogERROR, "cbcp_Input: Not a physical link - dropped\n");
+ m_freem(bp);
+ return NULL;
+ }
+
+ bp = m_pullup(bp);
+ len = m_length(bp);
+ if (len < sizeof(struct cbcp_header)) {
+ m_freem(bp);
+ return NULL;
+ }
+ head = (struct cbcp_header *)MBUF_CTOP(bp);
+ if (ntohs(head->length) != len) {
+ log_Printf(LogWARN, "Corrupt CBCP packet (code %d, length %u not %zu)"
+ " - ignored\n", head->code, ntohs(head->length), len);
+ m_freem(bp);
+ return NULL;
+ }
+ m_settype(bp, MB_CBCPIN);
+
+ /* XXX check the id */
+
+ bp->m_offset += sizeof(struct cbcp_header);
+ bp->m_len -= sizeof(struct cbcp_header);
+ data = (struct cbcp_data *)MBUF_CTOP(bp);
+
+ switch (head->code) {
+ case CBCP_REQ:
+ log_Printf(LogCBCP, "%s: RecvReq(%d) state = %s\n",
+ p->dl->name, head->id, cbcpstate(cbcp->fsm.state));
+ cbcp_data_Show(data);
+ if (cbcp->fsm.state == CBCP_STOPPED || cbcp->fsm.state == CBCP_RESPSENT) {
+ timer_Stop(&cbcp->fsm.timer);
+ if (cbcp_AdjustResponse(cbcp, data)) {
+ cbcp->fsm.restart = DEF_FSMTRIES;
+ cbcp->fsm.id = head->id;
+ cbcp_SendResponse(cbcp);
+ } else
+ datalink_CBCPFailed(cbcp->p->dl);
+ } else
+ log_Printf(LogCBCP, "%s: unexpected REQ dropped\n", p->dl->name);
+ break;
+
+ case CBCP_RESPONSE:
+ log_Printf(LogCBCP, "%s: RecvResponse(%d) state = %s\n",
+ p->dl->name, head->id, cbcpstate(cbcp->fsm.state));
+ cbcp_data_Show(data);
+ if (cbcp->fsm.id != head->id) {
+ log_Printf(LogCBCP, "Warning: Expected id was %d, not %d\n",
+ cbcp->fsm.id, head->id);
+ cbcp->fsm.id = head->id;
+ }
+ if (cbcp->fsm.state == CBCP_REQSENT || cbcp->fsm.state == CBCP_ACKSENT) {
+ timer_Stop(&cbcp->fsm.timer);
+ switch (cbcp_CheckResponse(cbcp, data)) {
+ case CBCP_ACTION_REQ:
+ cbcp_SendReq(cbcp);
+ break;
+
+ case CBCP_ACTION_ACK:
+ cbcp->fsm.restart = DEF_FSMTRIES;
+ cbcp_SendAck(cbcp);
+ if (cbcp->fsm.type == CBCP_NONUM) {
+ /*
+ * Don't change state in case the peer doesn't get our ACK,
+ * just bring the layer up.
+ */
+ timer_Stop(&cbcp->fsm.timer);
+ datalink_NCPUp(cbcp->p->dl);
+ }
+ break;
+
+ default:
+ datalink_CBCPFailed(cbcp->p->dl);
+ break;
+ }
+ } else
+ log_Printf(LogCBCP, "%s: unexpected RESPONSE dropped\n", p->dl->name);
+ break;
+
+ case CBCP_ACK:
+ log_Printf(LogCBCP, "%s: RecvAck(%d) state = %s\n",
+ p->dl->name, head->id, cbcpstate(cbcp->fsm.state));
+ cbcp_data_Show(data);
+ if (cbcp->fsm.id != head->id) {
+ log_Printf(LogCBCP, "Warning: Expected id was %d, not %d\n",
+ cbcp->fsm.id, head->id);
+ cbcp->fsm.id = head->id;
+ }
+ if (cbcp->fsm.type == CBCP_NONUM) {
+ /*
+ * Don't change state in case the peer doesn't get our ACK,
+ * just bring the layer up.
+ */
+ timer_Stop(&cbcp->fsm.timer);
+ datalink_NCPUp(cbcp->p->dl);
+ } else if (cbcp->fsm.state == CBCP_RESPSENT) {
+ timer_Stop(&cbcp->fsm.timer);
+ datalink_CBCPComplete(cbcp->p->dl);
+ log_Printf(LogPHASE, "%s: CBCP: Peer will dial back\n", p->dl->name);
+ } else
+ log_Printf(LogCBCP, "%s: unexpected ACK dropped\n", p->dl->name);
+ break;
+
+ default:
+ log_Printf(LogWARN, "Unrecognised CBCP packet (code %d, length %zd)\n",
+ head->code, len);
+ break;
+ }
+
+ m_freem(bp);
+ return NULL;
+}
+
+void
+cbcp_Down(struct cbcp *cbcp)
+{
+ timer_Stop(&cbcp->fsm.timer);
+ cbcp_NewPhase(cbcp, CBCP_CLOSED);
+ cbcp->required = 0;
+}
+
+void
+cbcp_ReceiveTerminateReq(struct physical *p)
+{
+ if (p->dl->cbcp.fsm.state == CBCP_ACKSENT) {
+ /* Don't change our state in case the peer doesn't get the ACK */
+ p->dl->cbcp.required = 1;
+ log_Printf(LogPHASE, "%s: CBCP: Will dial back on %s\n", p->dl->name,
+ p->dl->cbcp.fsm.phone);
+ } else
+ cbcp_NewPhase(&p->dl->cbcp, CBCP_CLOSED);
+}
diff --git a/usr.sbin/ppp/cbcp.h b/usr.sbin/ppp/cbcp.h
new file mode 100644
index 0000000..46bf274
--- /dev/null
+++ b/usr.sbin/ppp/cbcp.h
@@ -0,0 +1,65 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.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$
+ */
+
+struct mbuf;
+struct physical;
+struct datalink;
+
+/* fsm states */
+#define CBCP_CLOSED (0) /* Not in use */
+#define CBCP_STOPPED (1) /* Waiting for a REQ */
+#define CBCP_REQSENT (2) /* Waiting for a RESP */
+#define CBCP_RESPSENT (3) /* Waiting for an ACK */
+#define CBCP_ACKSENT (4) /* Waiting for an LCP Term REQ */
+
+struct cbcpcfg {
+ u_char delay;
+ char phone[SCRIPT_LEN];
+ long fsmretry;
+};
+
+struct cbcp {
+ unsigned required : 1; /* Are we gonna call back ? */
+ struct physical *p; /* On this physical link */
+ struct {
+ u_char type; /* cbcp_data::type (none/me/him/list) */
+ u_char delay; /* How long to delay */
+ char phone[SCRIPT_LEN]; /* What to dial */
+
+ int state; /* Our FSM state */
+ u_char id; /* Our FSM ID */
+ u_char restart; /* FSM Send again ? */
+ struct pppTimer timer; /* Resend last option */
+ } fsm;
+};
+
+extern void cbcp_Init(struct cbcp *, struct physical *);
+extern void cbcp_Up(struct cbcp *);
+extern struct mbuf *cbcp_Input(struct bundle *, struct link *, struct mbuf *);
+extern void cbcp_Down(struct cbcp *);
+extern void cbcp_ReceiveTerminateReq(struct physical *);
diff --git a/usr.sbin/ppp/ccp.c b/usr.sbin/ppp/ccp.c
new file mode 100644
index 0000000..f5bbf0c
--- /dev/null
+++ b/usr.sbin/ppp/ccp.c
@@ -0,0 +1,826 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h> /* memcpy() on some archs */
+#include <termios.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "proto.h"
+#include "pred.h"
+#include "deflate.h"
+#include "throughput.h"
+#include "iplist.h"
+#include "slcompress.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "prompt.h"
+#include "link.h"
+#include "mp.h"
+#include "async.h"
+#include "physical.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#ifndef NODES
+#include "mppe.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+
+static void CcpSendConfigReq(struct fsm *);
+static void CcpSentTerminateReq(struct fsm *);
+static void CcpSendTerminateAck(struct fsm *, u_char);
+static void CcpDecodeConfig(struct fsm *, u_char *, u_char *, int,
+ struct fsm_decode *);
+static void CcpLayerStart(struct fsm *);
+static void CcpLayerFinish(struct fsm *);
+static int CcpLayerUp(struct fsm *);
+static void CcpLayerDown(struct fsm *);
+static void CcpInitRestartCounter(struct fsm *, int);
+static int CcpRecvResetReq(struct fsm *);
+static void CcpRecvResetAck(struct fsm *, u_char);
+
+static struct fsm_callbacks ccp_Callbacks = {
+ CcpLayerUp,
+ CcpLayerDown,
+ CcpLayerStart,
+ CcpLayerFinish,
+ CcpInitRestartCounter,
+ CcpSendConfigReq,
+ CcpSentTerminateReq,
+ CcpSendTerminateAck,
+ CcpDecodeConfig,
+ CcpRecvResetReq,
+ CcpRecvResetAck
+};
+
+static const char * const ccp_TimerNames[] =
+ {"CCP restart", "CCP openmode", "CCP stopped"};
+
+static const char *
+protoname(int proto)
+{
+ static char const * const cftypes[] = {
+ /* Check out the latest ``Compression Control Protocol'' rfc (1962) */
+ "OUI", /* 0: OUI */
+ "PRED1", /* 1: Predictor type 1 */
+ "PRED2", /* 2: Predictor type 2 */
+ "PUDDLE", /* 3: Puddle Jumber */
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ "HWPPC", /* 16: Hewlett-Packard PPC */
+ "STAC", /* 17: Stac Electronics LZS (rfc1974) */
+ "MPPE", /* 18: Microsoft PPC (rfc2118) and */
+ /* Microsoft PPE (draft-ietf-pppext-mppe) */
+ "GAND", /* 19: Gandalf FZA (rfc1993) */
+ "V42BIS", /* 20: ARG->DATA.42bis compression */
+ "BSD", /* 21: BSD LZW Compress */
+ NULL,
+ "LZS-DCP", /* 23: LZS-DCP Compression Protocol (rfc1967) */
+ "MAGNALINK/DEFLATE",/* 24: Magnalink Variable Resource (rfc1975) */
+ /* 24: Deflate (according to pppd-2.3.*) */
+ "DCE", /* 25: Data Circuit-Terminating Equip (rfc1976) */
+ "DEFLATE", /* 26: Deflate (rfc1979) */
+ };
+
+ if (proto < 0 || (unsigned)proto > sizeof cftypes / sizeof *cftypes ||
+ cftypes[proto] == NULL) {
+ if (proto == -1)
+ return "none";
+ return HexStr(proto, NULL, 0);
+ }
+
+ return cftypes[proto];
+}
+
+/* We support these algorithms, and Req them in the given order */
+static const struct ccp_algorithm * const algorithm[] = {
+ &DeflateAlgorithm,
+ &Pred1Algorithm,
+ &PppdDeflateAlgorithm
+#ifndef NODES
+ , &MPPEAlgorithm
+#endif
+};
+
+#define NALGORITHMS (sizeof algorithm/sizeof algorithm[0])
+
+int
+ccp_ReportStatus(struct cmdargs const *arg)
+{
+ struct ccp_opt **o;
+ struct link *l;
+ struct ccp *ccp;
+ int f;
+
+ l = command_ChooseLink(arg);
+ ccp = &l->ccp;
+
+ prompt_Printf(arg->prompt, "%s: %s [%s]\n", l->name, ccp->fsm.name,
+ State2Nam(ccp->fsm.state));
+ if (ccp->fsm.state == ST_OPENED) {
+ prompt_Printf(arg->prompt, " My protocol = %s, His protocol = %s\n",
+ protoname(ccp->my_proto), protoname(ccp->his_proto));
+ prompt_Printf(arg->prompt, " Output: %ld --> %ld, Input: %ld --> %ld\n",
+ ccp->uncompout, ccp->compout,
+ ccp->compin, ccp->uncompin);
+ }
+
+ if (ccp->in.algorithm != -1)
+ prompt_Printf(arg->prompt, "\n Input Options: %s\n",
+ (*algorithm[ccp->in.algorithm]->Disp)(&ccp->in.opt));
+
+ if (ccp->out.algorithm != -1) {
+ o = &ccp->out.opt;
+ for (f = 0; f < ccp->out.algorithm; f++)
+ if (IsEnabled(ccp->cfg.neg[algorithm[f]->Neg]))
+ o = &(*o)->next;
+ prompt_Printf(arg->prompt, " Output Options: %s\n",
+ (*algorithm[ccp->out.algorithm]->Disp)(&(*o)->val));
+ }
+
+ prompt_Printf(arg->prompt, "\n Defaults: ");
+ prompt_Printf(arg->prompt, "FSM retry = %us, max %u Config"
+ " REQ%s, %u Term REQ%s\n", ccp->cfg.fsm.timeout,
+ ccp->cfg.fsm.maxreq, ccp->cfg.fsm.maxreq == 1 ? "" : "s",
+ ccp->cfg.fsm.maxtrm, ccp->cfg.fsm.maxtrm == 1 ? "" : "s");
+ prompt_Printf(arg->prompt, " deflate windows: ");
+ prompt_Printf(arg->prompt, "incoming = %d, ", ccp->cfg.deflate.in.winsize);
+ prompt_Printf(arg->prompt, "outgoing = %d\n", ccp->cfg.deflate.out.winsize);
+#ifndef NODES
+ prompt_Printf(arg->prompt, " MPPE: ");
+ if (ccp->cfg.mppe.keybits)
+ prompt_Printf(arg->prompt, "%d bits, ", ccp->cfg.mppe.keybits);
+ else
+ prompt_Printf(arg->prompt, "any bits, ");
+ switch (ccp->cfg.mppe.state) {
+ case MPPE_STATEFUL:
+ prompt_Printf(arg->prompt, "stateful");
+ break;
+ case MPPE_STATELESS:
+ prompt_Printf(arg->prompt, "stateless");
+ break;
+ case MPPE_ANYSTATE:
+ prompt_Printf(arg->prompt, "any state");
+ break;
+ }
+ prompt_Printf(arg->prompt, "%s\n",
+ ccp->cfg.mppe.required ? ", required" : "");
+#endif
+
+ prompt_Printf(arg->prompt, "\n DEFLATE: %s\n",
+ command_ShowNegval(ccp->cfg.neg[CCP_NEG_DEFLATE]));
+ prompt_Printf(arg->prompt, " PREDICTOR1: %s\n",
+ command_ShowNegval(ccp->cfg.neg[CCP_NEG_PRED1]));
+ prompt_Printf(arg->prompt, " DEFLATE24: %s\n",
+ command_ShowNegval(ccp->cfg.neg[CCP_NEG_DEFLATE24]));
+#ifndef NODES
+ prompt_Printf(arg->prompt, " MPPE: %s\n",
+ command_ShowNegval(ccp->cfg.neg[CCP_NEG_MPPE]));
+#endif
+ return 0;
+}
+
+void
+ccp_SetupCallbacks(struct ccp *ccp)
+{
+ ccp->fsm.fn = &ccp_Callbacks;
+ ccp->fsm.FsmTimer.name = ccp_TimerNames[0];
+ ccp->fsm.OpenTimer.name = ccp_TimerNames[1];
+ ccp->fsm.StoppedTimer.name = ccp_TimerNames[2];
+}
+
+void
+ccp_Init(struct ccp *ccp, struct bundle *bundle, struct link *l,
+ const struct fsm_parent *parent)
+{
+ /* Initialise ourselves */
+
+ fsm_Init(&ccp->fsm, "CCP", PROTO_CCP, 1, CCP_MAXCODE, LogCCP,
+ bundle, l, parent, &ccp_Callbacks, ccp_TimerNames);
+
+ ccp->cfg.deflate.in.winsize = 0;
+ ccp->cfg.deflate.out.winsize = 15;
+ ccp->cfg.fsm.timeout = DEF_FSMRETRY;
+ ccp->cfg.fsm.maxreq = DEF_FSMTRIES;
+ ccp->cfg.fsm.maxtrm = DEF_FSMTRIES;
+ ccp->cfg.neg[CCP_NEG_DEFLATE] = NEG_ENABLED|NEG_ACCEPTED;
+ ccp->cfg.neg[CCP_NEG_PRED1] = NEG_ENABLED|NEG_ACCEPTED;
+ ccp->cfg.neg[CCP_NEG_DEFLATE24] = 0;
+#ifndef NODES
+ ccp->cfg.mppe.keybits = 0;
+ ccp->cfg.mppe.state = MPPE_ANYSTATE;
+ ccp->cfg.mppe.required = 0;
+ ccp->cfg.neg[CCP_NEG_MPPE] = NEG_ENABLED|NEG_ACCEPTED;
+#endif
+
+ ccp_Setup(ccp);
+}
+
+void
+ccp_Setup(struct ccp *ccp)
+{
+ /* Set ourselves up for a startup */
+ ccp->fsm.open_mode = 0;
+ ccp->his_proto = ccp->my_proto = -1;
+ ccp->reset_sent = ccp->last_reset = -1;
+ ccp->in.algorithm = ccp->out.algorithm = -1;
+ ccp->in.state = ccp->out.state = NULL;
+ ccp->in.opt.hdr.id = -1;
+ ccp->out.opt = NULL;
+ ccp->his_reject = ccp->my_reject = 0;
+ ccp->uncompout = ccp->compout = 0;
+ ccp->uncompin = ccp->compin = 0;
+}
+
+/*
+ * Is ccp *REQUIRED* ?
+ * We ask each of the configured ccp protocols if they're required and
+ * return TRUE if they are.
+ *
+ * It's not possible for the peer to reject a required ccp protocol
+ * without our state machine bringing the supporting lcp layer down.
+ *
+ * If ccp is required but not open, the NCP layer should not push
+ * any data into the link.
+ */
+int
+ccp_Required(struct ccp *ccp)
+{
+ unsigned f;
+
+ for (f = 0; f < NALGORITHMS; f++)
+ if (IsEnabled(ccp->cfg.neg[algorithm[f]->Neg]) &&
+ (*algorithm[f]->Required)(&ccp->fsm))
+ return 1;
+
+ return 0;
+}
+
+/*
+ * Report whether it's possible to increase a packet's size after
+ * compression (and by how much).
+ */
+int
+ccp_MTUOverhead(struct ccp *ccp)
+{
+ if (ccp->fsm.state == ST_OPENED && ccp->out.algorithm >= 0)
+ return algorithm[ccp->out.algorithm]->o.MTUOverhead;
+
+ return 0;
+}
+
+static void
+CcpInitRestartCounter(struct fsm *fp, int what)
+{
+ /* Set fsm timer load */
+ struct ccp *ccp = fsm2ccp(fp);
+
+ fp->FsmTimer.load = ccp->cfg.fsm.timeout * SECTICKS;
+ switch (what) {
+ case FSM_REQ_TIMER:
+ fp->restart = ccp->cfg.fsm.maxreq;
+ break;
+ case FSM_TRM_TIMER:
+ fp->restart = ccp->cfg.fsm.maxtrm;
+ break;
+ default:
+ fp->restart = 1;
+ break;
+ }
+}
+
+static void
+CcpSendConfigReq(struct fsm *fp)
+{
+ /* Send config REQ please */
+ struct ccp *ccp = fsm2ccp(fp);
+ struct ccp_opt **o;
+ u_char *cp, buff[100];
+ unsigned f;
+ int alloc;
+
+ cp = buff;
+ o = &ccp->out.opt;
+ alloc = ccp->his_reject == 0 && ccp->out.opt == NULL;
+ ccp->my_proto = -1;
+ ccp->out.algorithm = -1;
+ for (f = 0; f < NALGORITHMS; f++)
+ if (IsEnabled(ccp->cfg.neg[algorithm[f]->Neg]) &&
+ !REJECTED(ccp, algorithm[f]->id) &&
+ (*algorithm[f]->Usable)(fp)) {
+
+ if (!alloc)
+ for (o = &ccp->out.opt; *o != NULL; o = &(*o)->next)
+ if ((*o)->val.hdr.id == algorithm[f]->id && (*o)->algorithm == (int)f)
+ break;
+
+ if (alloc || *o == NULL) {
+ if ((*o = (struct ccp_opt *)malloc(sizeof(struct ccp_opt))) == NULL) {
+ log_Printf(LogERROR, "%s: Not enough memory for CCP REQ !\n",
+ fp->link->name);
+ break;
+ }
+ (*o)->val.hdr.id = algorithm[f]->id;
+ (*o)->val.hdr.len = 2;
+ (*o)->next = NULL;
+ (*o)->algorithm = f;
+ (*algorithm[f]->o.OptInit)(fp->bundle, &(*o)->val, &ccp->cfg);
+ }
+
+ if (cp + (*o)->val.hdr.len > buff + sizeof buff) {
+ log_Printf(LogERROR, "%s: CCP REQ buffer overrun !\n", fp->link->name);
+ break;
+ }
+ memcpy(cp, &(*o)->val, (*o)->val.hdr.len);
+ cp += (*o)->val.hdr.len;
+
+ ccp->my_proto = (*o)->val.hdr.id;
+ ccp->out.algorithm = f;
+
+ if (alloc)
+ o = &(*o)->next;
+ }
+
+ fsm_Output(fp, CODE_CONFIGREQ, fp->reqid, buff, cp - buff, MB_CCPOUT);
+}
+
+void
+ccp_SendResetReq(struct fsm *fp)
+{
+ /* We can't read our input - ask peer to reset */
+ struct ccp *ccp = fsm2ccp(fp);
+
+ ccp->reset_sent = fp->reqid;
+ ccp->last_reset = -1;
+ fsm_Output(fp, CODE_RESETREQ, fp->reqid, NULL, 0, MB_CCPOUT);
+}
+
+static void
+CcpSentTerminateReq(struct fsm *fp __unused)
+{
+ /* Term REQ just sent by FSM */
+}
+
+static void
+CcpSendTerminateAck(struct fsm *fp, u_char id)
+{
+ /* Send Term ACK please */
+ fsm_Output(fp, CODE_TERMACK, id, NULL, 0, MB_CCPOUT);
+}
+
+static int
+CcpRecvResetReq(struct fsm *fp)
+{
+ /* Got a reset REQ, reset outgoing dictionary */
+ struct ccp *ccp = fsm2ccp(fp);
+ if (ccp->out.state == NULL)
+ return 1;
+ return (*algorithm[ccp->out.algorithm]->o.Reset)(ccp->out.state);
+}
+
+static void
+CcpLayerStart(struct fsm *fp)
+{
+ /* We're about to start up ! */
+ struct ccp *ccp = fsm2ccp(fp);
+
+ log_Printf(LogCCP, "%s: LayerStart.\n", fp->link->name);
+ fp->more.reqs = fp->more.naks = fp->more.rejs = ccp->cfg.fsm.maxreq * 3;
+}
+
+static void
+CcpLayerDown(struct fsm *fp)
+{
+ /* About to come down */
+ struct ccp *ccp = fsm2ccp(fp);
+ struct ccp_opt *next;
+
+ log_Printf(LogCCP, "%s: LayerDown.\n", fp->link->name);
+ if (ccp->in.state != NULL) {
+ (*algorithm[ccp->in.algorithm]->i.Term)(ccp->in.state);
+ ccp->in.state = NULL;
+ ccp->in.algorithm = -1;
+ }
+ if (ccp->out.state != NULL) {
+ (*algorithm[ccp->out.algorithm]->o.Term)(ccp->out.state);
+ ccp->out.state = NULL;
+ ccp->out.algorithm = -1;
+ }
+ ccp->his_reject = ccp->my_reject = 0;
+
+ while (ccp->out.opt) {
+ next = ccp->out.opt->next;
+ free(ccp->out.opt);
+ ccp->out.opt = next;
+ }
+ ccp_Setup(ccp);
+}
+
+static void
+CcpLayerFinish(struct fsm *fp)
+{
+ /* We're now down */
+ struct ccp *ccp = fsm2ccp(fp);
+ struct ccp_opt *next;
+
+ log_Printf(LogCCP, "%s: LayerFinish.\n", fp->link->name);
+
+ /*
+ * Nuke options that may be left over from sending a REQ but never
+ * coming up.
+ */
+ while (ccp->out.opt) {
+ next = ccp->out.opt->next;
+ free(ccp->out.opt);
+ ccp->out.opt = next;
+ }
+
+ if (ccp_Required(ccp)) {
+ if (fp->link->lcp.fsm.state == ST_OPENED)
+ log_Printf(LogLCP, "%s: Closing due to CCP completion\n", fp->link->name);
+ fsm_Close(&fp->link->lcp.fsm);
+ }
+}
+
+/* Called when CCP has reached the OPEN state */
+static int
+CcpLayerUp(struct fsm *fp)
+{
+ /* We're now up */
+ struct ccp *ccp = fsm2ccp(fp);
+ struct ccp_opt **o;
+ unsigned f, fail;
+
+ for (f = fail = 0; f < NALGORITHMS; f++)
+ if (IsEnabled(ccp->cfg.neg[algorithm[f]->Neg]) &&
+ (*algorithm[f]->Required)(&ccp->fsm) &&
+ (ccp->in.algorithm != (int)f || ccp->out.algorithm != (int)f)) {
+ /* Blow it all away - we haven't negotiated a required algorithm */
+ log_Printf(LogWARN, "%s: Failed to negotiate (required) %s\n",
+ fp->link->name, protoname(algorithm[f]->id));
+ fail = 1;
+ }
+
+ if (fail) {
+ ccp->his_proto = ccp->my_proto = -1;
+ fsm_Close(fp);
+ fsm_Close(&fp->link->lcp.fsm);
+ return 0;
+ }
+
+ log_Printf(LogCCP, "%s: LayerUp.\n", fp->link->name);
+
+ if (ccp->in.state == NULL && ccp->in.algorithm >= 0 &&
+ ccp->in.algorithm < (int)NALGORITHMS) {
+ ccp->in.state = (*algorithm[ccp->in.algorithm]->i.Init)
+ (fp->bundle, &ccp->in.opt);
+ if (ccp->in.state == NULL) {
+ log_Printf(LogERROR, "%s: %s (in) initialisation failure\n",
+ fp->link->name, protoname(ccp->his_proto));
+ ccp->his_proto = ccp->my_proto = -1;
+ fsm_Close(fp);
+ return 0;
+ }
+ }
+
+ o = &ccp->out.opt;
+ if (ccp->out.algorithm > 0)
+ for (f = 0; f < (unsigned)ccp->out.algorithm; f++)
+ if (IsEnabled(ccp->cfg.neg[algorithm[f]->Neg]))
+ o = &(*o)->next;
+
+ if (ccp->out.state == NULL && ccp->out.algorithm >= 0 &&
+ ccp->out.algorithm < (int)NALGORITHMS) {
+ ccp->out.state = (*algorithm[ccp->out.algorithm]->o.Init)
+ (fp->bundle, &(*o)->val);
+ if (ccp->out.state == NULL) {
+ log_Printf(LogERROR, "%s: %s (out) initialisation failure\n",
+ fp->link->name, protoname(ccp->my_proto));
+ ccp->his_proto = ccp->my_proto = -1;
+ fsm_Close(fp);
+ return 0;
+ }
+ }
+
+ fp->more.reqs = fp->more.naks = fp->more.rejs = ccp->cfg.fsm.maxreq * 3;
+
+ log_Printf(LogCCP, "%s: Out = %s[%d], In = %s[%d]\n",
+ fp->link->name, protoname(ccp->my_proto), ccp->my_proto,
+ protoname(ccp->his_proto), ccp->his_proto);
+
+ return 1;
+}
+
+static void
+CcpDecodeConfig(struct fsm *fp, u_char *cp, u_char *end, int mode_type,
+ struct fsm_decode *dec)
+{
+ /* Deal with incoming data */
+ struct ccp *ccp = fsm2ccp(fp);
+ int f;
+ const char *disp;
+ struct fsm_opt *opt;
+
+ if (mode_type == MODE_REQ)
+ ccp->in.algorithm = -1; /* In case we've received two REQs in a row */
+
+ while (end >= cp + sizeof(opt->hdr)) {
+ if ((opt = fsm_readopt(&cp)) == NULL)
+ break;
+
+ for (f = NALGORITHMS-1; f > -1; f--)
+ if (algorithm[f]->id == opt->hdr.id)
+ break;
+
+ disp = f == -1 ? "" : (*algorithm[f]->Disp)(opt);
+ if (disp == NULL)
+ disp = "";
+
+ log_Printf(LogCCP, " %s[%d] %s\n", protoname(opt->hdr.id),
+ opt->hdr.len, disp);
+
+ if (f == -1) {
+ /* Don't understand that :-( */
+ if (mode_type == MODE_REQ) {
+ ccp->my_reject |= (1 << opt->hdr.id);
+ fsm_rej(dec, opt);
+ }
+ } else {
+ struct ccp_opt *o;
+
+ switch (mode_type) {
+ case MODE_REQ:
+ if (IsAccepted(ccp->cfg.neg[algorithm[f]->Neg]) &&
+ (*algorithm[f]->Usable)(fp) &&
+ ccp->in.algorithm == -1) {
+ memcpy(&ccp->in.opt, opt, opt->hdr.len);
+ switch ((*algorithm[f]->i.Set)(fp->bundle, &ccp->in.opt, &ccp->cfg)) {
+ case MODE_REJ:
+ fsm_rej(dec, &ccp->in.opt);
+ break;
+ case MODE_NAK:
+ fsm_nak(dec, &ccp->in.opt);
+ break;
+ case MODE_ACK:
+ fsm_ack(dec, &ccp->in.opt);
+ ccp->his_proto = opt->hdr.id;
+ ccp->in.algorithm = (int)f; /* This one'll do :-) */
+ break;
+ }
+ } else {
+ fsm_rej(dec, opt);
+ }
+ break;
+ case MODE_NAK:
+ for (o = ccp->out.opt; o != NULL; o = o->next)
+ if (o->val.hdr.id == opt->hdr.id)
+ break;
+ if (o == NULL)
+ log_Printf(LogCCP, "%s: Warning: Ignoring peer NAK of unsent"
+ " option\n", fp->link->name);
+ else {
+ memcpy(&o->val, opt, opt->hdr.len);
+ if ((*algorithm[f]->o.Set)(fp->bundle, &o->val, &ccp->cfg) ==
+ MODE_ACK)
+ ccp->my_proto = algorithm[f]->id;
+ else {
+ ccp->his_reject |= (1 << opt->hdr.id);
+ ccp->my_proto = -1;
+ if (algorithm[f]->Required(fp)) {
+ log_Printf(LogWARN, "%s: Cannot understand peers (required)"
+ " %s negotiation\n", fp->link->name,
+ protoname(algorithm[f]->id));
+ fsm_Close(&fp->link->lcp.fsm);
+ }
+ }
+ }
+ break;
+ case MODE_REJ:
+ ccp->his_reject |= (1 << opt->hdr.id);
+ ccp->my_proto = -1;
+ if (algorithm[f]->Required(fp)) {
+ log_Printf(LogWARN, "%s: Peer rejected (required) %s negotiation\n",
+ fp->link->name, protoname(algorithm[f]->id));
+ fsm_Close(&fp->link->lcp.fsm);
+ }
+ break;
+ }
+ }
+ }
+
+ if (mode_type != MODE_NOP) {
+ fsm_opt_normalise(dec);
+ if (dec->rejend != dec->rej || dec->nakend != dec->nak) {
+ if (ccp->in.state == NULL) {
+ ccp->his_proto = -1;
+ ccp->in.algorithm = -1;
+ }
+ }
+ }
+}
+
+extern struct mbuf *
+ccp_Input(struct bundle *bundle, struct link *l, struct mbuf *bp)
+{
+ /* Got PROTO_CCP from link */
+ m_settype(bp, MB_CCPIN);
+ if (bundle_Phase(bundle) == PHASE_NETWORK)
+ fsm_Input(&l->ccp.fsm, bp);
+ else {
+ if (bundle_Phase(bundle) < PHASE_NETWORK)
+ log_Printf(LogCCP, "%s: Error: Unexpected CCP in phase %s (ignored)\n",
+ l->ccp.fsm.link->name, bundle_PhaseName(bundle));
+ m_freem(bp);
+ }
+ return NULL;
+}
+
+static void
+CcpRecvResetAck(struct fsm *fp, u_char id)
+{
+ /* Got a reset ACK, reset incoming dictionary */
+ struct ccp *ccp = fsm2ccp(fp);
+
+ if (ccp->reset_sent != -1) {
+ if (id != ccp->reset_sent) {
+ log_Printf(LogCCP, "%s: Incorrect ResetAck (id %d, not %d)"
+ " ignored\n", fp->link->name, id, ccp->reset_sent);
+ return;
+ }
+ /* Whaddaya know - a correct reset ack */
+ } else if (id == ccp->last_reset)
+ log_Printf(LogCCP, "%s: Duplicate ResetAck (resetting again)\n",
+ fp->link->name);
+ else {
+ log_Printf(LogCCP, "%s: Unexpected ResetAck (id %d) ignored\n",
+ fp->link->name, id);
+ return;
+ }
+
+ ccp->last_reset = ccp->reset_sent;
+ ccp->reset_sent = -1;
+ if (ccp->in.state != NULL)
+ (*algorithm[ccp->in.algorithm]->i.Reset)(ccp->in.state);
+}
+
+static struct mbuf *
+ccp_LayerPush(struct bundle *b __unused, struct link *l, struct mbuf *bp,
+ int pri, u_short *proto)
+{
+ if (PROTO_COMPRESSIBLE(*proto)) {
+ if (l->ccp.fsm.state != ST_OPENED) {
+ if (ccp_Required(&l->ccp)) {
+ /* The NCP layer shouldn't have let this happen ! */
+ log_Printf(LogERROR, "%s: Unexpected attempt to use an unopened and"
+ " required CCP layer\n", l->name);
+ m_freem(bp);
+ bp = NULL;
+ }
+ } else if (l->ccp.out.state != NULL) {
+ bp = (*algorithm[l->ccp.out.algorithm]->o.Write)
+ (l->ccp.out.state, &l->ccp, l, pri, proto, bp);
+ switch (*proto) {
+ case PROTO_ICOMPD:
+ m_settype(bp, MB_ICOMPDOUT);
+ break;
+ case PROTO_COMPD:
+ m_settype(bp, MB_COMPDOUT);
+ break;
+ }
+ }
+ }
+
+ return bp;
+}
+
+static struct mbuf *
+ccp_LayerPull(struct bundle *b __unused, struct link *l, struct mbuf *bp,
+ u_short *proto)
+{
+ /*
+ * If proto isn't PROTO_[I]COMPD, we still want to pass it to the
+ * decompression routines so that the dictionary's updated
+ */
+ if (l->ccp.fsm.state == ST_OPENED) {
+ if (*proto == PROTO_COMPD || *proto == PROTO_ICOMPD) {
+ /* Decompress incoming data */
+ if (l->ccp.reset_sent != -1)
+ /* Send another REQ and put the packet in the bit bucket */
+ fsm_Output(&l->ccp.fsm, CODE_RESETREQ, l->ccp.reset_sent, NULL, 0,
+ MB_CCPOUT);
+ else if (l->ccp.in.state != NULL) {
+ bp = (*algorithm[l->ccp.in.algorithm]->i.Read)
+ (l->ccp.in.state, &l->ccp, proto, bp);
+ switch (*proto) {
+ case PROTO_ICOMPD:
+ m_settype(bp, MB_ICOMPDIN);
+ break;
+ case PROTO_COMPD:
+ m_settype(bp, MB_COMPDIN);
+ break;
+ }
+ return bp;
+ }
+ m_freem(bp);
+ bp = NULL;
+ } else if (PROTO_COMPRESSIBLE(*proto) && l->ccp.in.state != NULL) {
+ /* Add incoming Network Layer traffic to our dictionary */
+ (*algorithm[l->ccp.in.algorithm]->i.DictSetup)
+ (l->ccp.in.state, &l->ccp, *proto, bp);
+ }
+ }
+
+ return bp;
+}
+
+u_short
+ccp_Proto(struct ccp *ccp)
+{
+ return !link2physical(ccp->fsm.link) || !ccp->fsm.bundle->ncp.mp.active ?
+ PROTO_COMPD : PROTO_ICOMPD;
+}
+
+int
+ccp_SetOpenMode(struct ccp *ccp)
+{
+ int f;
+
+ for (f = 0; f < CCP_NEG_TOTAL; f++)
+ if (IsEnabled(ccp->cfg.neg[f])) {
+ ccp->fsm.open_mode = 0;
+ return 1;
+ }
+
+ ccp->fsm.open_mode = OPEN_PASSIVE; /* Go straight to ST_STOPPED ? */
+
+ for (f = 0; f < CCP_NEG_TOTAL; f++)
+ if (IsAccepted(ccp->cfg.neg[f]))
+ return 1;
+
+ return 0; /* No CCP at all */
+}
+
+int
+ccp_DefaultUsable(struct fsm *fp __unused)
+{
+ return 1;
+}
+
+int
+ccp_DefaultRequired(struct fsm *fp __unused)
+{
+ return 0;
+}
+
+struct layer ccplayer = { LAYER_CCP, "ccp", ccp_LayerPush, ccp_LayerPull };
diff --git a/usr.sbin/ppp/ccp.h b/usr.sbin/ppp/ccp.h
new file mode 100644
index 0000000..bc867b6
--- /dev/null
+++ b/usr.sbin/ppp/ccp.h
@@ -0,0 +1,165 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+#define CCP_MAXCODE CODE_RESETACK
+
+#define TY_OUI 0 /* OUI */
+#define TY_PRED1 1 /* Predictor type 1 */
+#define TY_PRED2 2 /* Predictor type 2 */
+#define TY_PUDDLE 3 /* Puddle Jumper */
+#define TY_HWPPC 16 /* Hewlett-Packard PPC */
+#define TY_STAC 17 /* Stac Electronics LZS */
+#define TY_MSPPC 18 /* Microsoft PPC */
+#define TY_MPPE 18 /* Microsoft PPE */
+#define TY_GAND 19 /* Gandalf FZA */
+#define TY_V42BIS 20 /* V.42bis compression */
+#define TY_BSD 21 /* BSD LZW Compress */
+#define TY_PPPD_DEFLATE 24 /* Deflate (gzip) - (mis) numbered by pppd */
+#define TY_DEFLATE 26 /* Deflate (gzip) - rfc 1979 */
+
+#define CCP_NEG_DEFLATE 0
+#define CCP_NEG_PRED1 1
+#define CCP_NEG_DEFLATE24 2
+#ifndef NODES
+#define CCP_NEG_MPPE 3
+#define CCP_NEG_TOTAL 4
+#else
+#define CCP_NEG_TOTAL 3
+#endif
+
+#ifndef NODES
+enum mppe_negstate {
+ MPPE_ANYSTATE,
+ MPPE_STATELESS,
+ MPPE_STATEFUL
+};
+#endif
+
+struct mbuf;
+struct link;
+
+struct ccp_config {
+ struct {
+ struct {
+ int winsize;
+ } in, out;
+ } deflate;
+#ifndef NODES
+ struct {
+ int keybits;
+ enum mppe_negstate state;
+ unsigned required : 1;
+ } mppe;
+#endif
+ struct fsm_retry fsm; /* How often/frequently to resend requests */
+ unsigned neg[CCP_NEG_TOTAL];
+};
+
+struct ccp_opt {
+ struct ccp_opt *next;
+ int algorithm;
+ struct fsm_opt val;
+};
+
+struct ccp {
+ struct fsm fsm; /* The finite state machine */
+
+ int his_proto; /* peer's compression protocol */
+ int my_proto; /* our compression protocol */
+
+ int reset_sent; /* If != -1, ignore compressed 'till ack */
+ int last_reset; /* We can receive more (dups) w/ this id */
+
+ struct {
+ int algorithm; /* Algorithm in use */
+ void *state; /* Returned by implementations Init() */
+ struct fsm_opt opt; /* Set by implementation's OptInit() */
+ } in;
+
+ struct {
+ int algorithm; /* Algorithm in use */
+ void *state; /* Returned by implementations Init() */
+ struct ccp_opt *opt; /* Set by implementation's OptInit() */
+ } out;
+
+ u_int32_t his_reject; /* Request codes rejected by peer */
+ u_int32_t my_reject; /* Request codes I have rejected */
+
+ u_long uncompout, compout; /* Outgoing bytes before/after compression */
+ u_long uncompin, compin; /* Incoming bytes after/before decompression */
+
+ struct ccp_config cfg;
+};
+
+#define fsm2ccp(fp) (fp->proto == PROTO_CCP ? (struct ccp *)fp : NULL)
+
+struct ccp_algorithm {
+ int id;
+ int Neg; /* ccp_config neg array item */
+ const char *(*Disp)(struct fsm_opt *); /* Use result immediately ! */
+ int (*Usable)(struct fsm *); /* Ok to negotiate ? */
+ int (*Required)(struct fsm *); /* Must negotiate ? */
+ struct {
+ int (*Set)(struct bundle *, struct fsm_opt *, const struct ccp_config *);
+ void *(*Init)(struct bundle *, struct fsm_opt *);
+ void (*Term)(void *);
+ void (*Reset)(void *);
+ struct mbuf *(*Read)(void *, struct ccp *, u_short *, struct mbuf *);
+ void (*DictSetup)(void *, struct ccp *, u_short, struct mbuf *);
+ } i;
+ struct {
+ int MTUOverhead;
+ void (*OptInit)(struct bundle *, struct fsm_opt *,
+ const struct ccp_config *);
+ int (*Set)(struct bundle *, struct fsm_opt *, const struct ccp_config *);
+ void *(*Init)(struct bundle *, struct fsm_opt *);
+ void (*Term)(void *);
+ int (*Reset)(void *);
+ struct mbuf *(*Write)(void *, struct ccp *, struct link *, int, u_short *,
+ struct mbuf *);
+ } o;
+};
+
+extern void ccp_Init(struct ccp *, struct bundle *, struct link *,
+ const struct fsm_parent *);
+extern void ccp_Setup(struct ccp *);
+extern int ccp_Required(struct ccp *);
+extern int ccp_MTUOverhead(struct ccp *);
+
+extern void ccp_SendResetReq(struct fsm *);
+extern struct mbuf *ccp_Input(struct bundle *, struct link *, struct mbuf *);
+extern int ccp_ReportStatus(struct cmdargs const *);
+extern u_short ccp_Proto(struct ccp *);
+extern void ccp_SetupCallbacks(struct ccp *);
+extern int ccp_SetOpenMode(struct ccp *);
+extern int ccp_DefaultUsable(struct fsm *);
+extern int ccp_DefaultRequired(struct fsm *);
+
+extern struct layer ccplayer;
diff --git a/usr.sbin/ppp/chap.c b/usr.sbin/ppp/chap.c
new file mode 100644
index 0000000..75de650
--- /dev/null
+++ b/usr.sbin/ppp/chap.c
@@ -0,0 +1,972 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#ifndef NODES
+#include <md4.h>
+#endif
+#include <md5.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "mbuf.h"
+#include "log.h"
+#include "defs.h"
+#include "timer.h"
+#include "fsm.h"
+#include "proto.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "auth.h"
+#include "async.h"
+#include "throughput.h"
+#include "descriptor.h"
+#include "chap.h"
+#include "iplist.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "ccp.h"
+#include "link.h"
+#include "physical.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "chat.h"
+#include "cbcp.h"
+#include "command.h"
+#include "datalink.h"
+#ifndef NODES
+#include "chap_ms.h"
+#include "mppe.h"
+#endif
+#include "id.h"
+
+static const char * const chapcodes[] = {
+ "???", "CHALLENGE", "RESPONSE", "SUCCESS", "FAILURE"
+};
+#define MAXCHAPCODE (sizeof chapcodes / sizeof chapcodes[0] - 1)
+
+static void
+ChapOutput(struct physical *physical, u_int code, u_int id,
+ const u_char *ptr, int count, const char *text)
+{
+ int plen;
+ struct fsmheader lh;
+ struct mbuf *bp;
+
+ plen = sizeof(struct fsmheader) + count;
+ lh.code = code;
+ lh.id = id;
+ lh.length = htons(plen);
+ bp = m_get(plen, MB_CHAPOUT);
+ memcpy(MBUF_CTOP(bp), &lh, sizeof(struct fsmheader));
+ if (count)
+ memcpy(MBUF_CTOP(bp) + sizeof(struct fsmheader), ptr, count);
+ log_DumpBp(LogDEBUG, "ChapOutput", bp);
+ if (text == NULL)
+ log_Printf(LogPHASE, "Chap Output: %s\n", chapcodes[code]);
+ else
+ log_Printf(LogPHASE, "Chap Output: %s (%s)\n", chapcodes[code], text);
+ link_PushPacket(&physical->link, bp, physical->dl->bundle,
+ LINK_QUEUES(&physical->link) - 1, PROTO_CHAP);
+}
+
+static char *
+chap_BuildAnswer(char *name, char *key, u_char id, char *challenge
+#ifndef NODES
+ , u_char type, char *peerchallenge, char *authresponse,
+ int lanman
+#endif
+ )
+{
+ char *result, *digest;
+ size_t nlen, klen;
+
+ nlen = strlen(name);
+ klen = strlen(key);
+
+#ifndef NODES
+ if (type == 0x80) {
+ char expkey[AUTHLEN << 2];
+ MD4_CTX MD4context;
+ size_t f;
+
+ if ((result = malloc(1 + nlen + MS_CHAP_RESPONSE_LEN)) == NULL)
+ return result;
+
+ digest = result; /* the response */
+ *digest++ = MS_CHAP_RESPONSE_LEN; /* 49 */
+ memcpy(digest + MS_CHAP_RESPONSE_LEN, name, nlen);
+ if (lanman) {
+ memset(digest + 24, '\0', 25);
+ mschap_LANMan(digest, challenge + 1, key); /* LANMan response */
+ } else {
+ memset(digest, '\0', 25);
+ digest += 24;
+
+ for (f = 0; f < klen; f++) {
+ expkey[2*f] = key[f];
+ expkey[2*f+1] = '\0';
+ }
+ /*
+ * -----------
+ * expkey = | k\0e\0y\0 |
+ * -----------
+ */
+ MD4Init(&MD4context);
+ MD4Update(&MD4context, expkey, klen << 1);
+ MD4Final(digest, &MD4context);
+
+ /*
+ * ---- -------- ---------------- ------- ------
+ * result = | 49 | LANMan | 16 byte digest | 9 * ? | name |
+ * ---- -------- ---------------- ------- ------
+ */
+ mschap_NT(digest, challenge + 1);
+ }
+ /*
+ * ---- -------- ------------- ----- ------
+ * | | struct MS_ChapResponse24 | |
+ * result = | 49 | LANMan | NT digest | 0/1 | name |
+ * ---- -------- ------------- ----- ------
+ * where only one of LANMan & NT digest are set.
+ */
+ } else if (type == 0x81) {
+ char expkey[AUTHLEN << 2];
+ char pwdhash[CHAP81_HASH_LEN];
+ char pwdhashhash[CHAP81_HASH_LEN];
+ char *ntresponse;
+ size_t f;
+
+ if ((result = malloc(1 + nlen + CHAP81_RESPONSE_LEN)) == NULL)
+ return result;
+
+ memset(result, 0, 1 + nlen + CHAP81_RESPONSE_LEN);
+
+ digest = result;
+ *digest++ = CHAP81_RESPONSE_LEN; /* value size */
+
+ /* Copy our challenge */
+ memcpy(digest, peerchallenge + 1, CHAP81_CHALLENGE_LEN);
+
+ /* Expand password to Unicode XXX */
+ for (f = 0; f < klen; f++) {
+ expkey[2*f] = key[f];
+ expkey[2*f+1] = '\0';
+ }
+
+ ntresponse = digest + CHAP81_NTRESPONSE_OFF;
+
+ /* Get some needed hashes */
+ NtPasswordHash(expkey, klen * 2, pwdhash);
+ HashNtPasswordHash(pwdhash, pwdhashhash);
+
+ /* Generate NTRESPONSE to respond on challenge call */
+ GenerateNTResponse(challenge + 1, peerchallenge + 1, name,
+ expkey, klen * 2, ntresponse);
+
+ /* Generate MPPE MASTERKEY */
+ GetMasterKey(pwdhashhash, ntresponse, MPPE_MasterKey); /* XXX Global ! */
+
+ /* Generate AUTHRESPONSE to verify on auth success */
+ GenerateAuthenticatorResponse(expkey, klen * 2, ntresponse,
+ peerchallenge + 1, challenge + 1, name,
+ authresponse);
+
+ authresponse[CHAP81_AUTHRESPONSE_LEN] = 0;
+
+ memcpy(digest + CHAP81_RESPONSE_LEN, name, nlen);
+ } else
+#endif
+ if ((result = malloc(nlen + 17)) != NULL) {
+ /* Normal MD5 stuff */
+ MD5_CTX MD5context;
+
+ digest = result;
+ *digest++ = 16; /* value size */
+
+ MD5Init(&MD5context);
+ MD5Update(&MD5context, &id, 1);
+ MD5Update(&MD5context, key, klen);
+ MD5Update(&MD5context, challenge + 1, *challenge);
+ MD5Final(digest, &MD5context);
+
+ memcpy(digest + 16, name, nlen);
+ /*
+ * ---- -------- ------
+ * result = | 16 | digest | name |
+ * ---- -------- ------
+ */
+ }
+
+ return result;
+}
+
+static void
+chap_StartChild(struct chap *chap, char *prog, const char *name)
+{
+ char *argv[MAXARGS], *nargv[MAXARGS];
+ int argc, fd;
+ int in[2], out[2];
+ pid_t pid;
+
+ if (chap->child.fd != -1) {
+ log_Printf(LogWARN, "Chap: %s: Program already running\n", prog);
+ return;
+ }
+
+ if (pipe(in) == -1) {
+ log_Printf(LogERROR, "Chap: pipe: %s\n", strerror(errno));
+ return;
+ }
+
+ if (pipe(out) == -1) {
+ log_Printf(LogERROR, "Chap: pipe: %s\n", strerror(errno));
+ close(in[0]);
+ close(in[1]);
+ return;
+ }
+
+ pid = getpid();
+ switch ((chap->child.pid = fork())) {
+ case -1:
+ log_Printf(LogERROR, "Chap: fork: %s\n", strerror(errno));
+ close(in[0]);
+ close(in[1]);
+ close(out[0]);
+ close(out[1]);
+ chap->child.pid = 0;
+ return;
+
+ case 0:
+ timer_TermService();
+
+ if ((argc = command_Interpret(prog, strlen(prog), argv)) <= 0) {
+ if (argc < 0) {
+ log_Printf(LogWARN, "CHAP: Invalid command syntax\n");
+ _exit(255);
+ }
+ _exit(0);
+ }
+
+ close(in[1]);
+ close(out[0]);
+ if (out[1] == STDIN_FILENO)
+ out[1] = dup(out[1]);
+ dup2(in[0], STDIN_FILENO);
+ dup2(out[1], STDOUT_FILENO);
+ close(STDERR_FILENO);
+ if (open(_PATH_DEVNULL, O_RDWR) != STDERR_FILENO) {
+ log_Printf(LogALERT, "Chap: Failed to open %s: %s\n",
+ _PATH_DEVNULL, strerror(errno));
+ exit(1);
+ }
+ for (fd = getdtablesize(); fd > STDERR_FILENO; fd--)
+ fcntl(fd, F_SETFD, 1);
+#ifndef NOSUID
+ setuid(ID0realuid());
+#endif
+ command_Expand(nargv, argc, (char const *const *)argv,
+ chap->auth.physical->dl->bundle, 0, pid);
+ execvp(nargv[0], nargv);
+ printf("exec() of %s failed: %s\n", nargv[0], strerror(errno));
+ _exit(255);
+
+ default:
+ close(in[0]);
+ close(out[1]);
+ chap->child.fd = out[0];
+ chap->child.buf.len = 0;
+ write(in[1], chap->auth.in.name, strlen(chap->auth.in.name));
+ write(in[1], "\n", 1);
+ write(in[1], chap->challenge.peer + 1, *chap->challenge.peer);
+ write(in[1], "\n", 1);
+ write(in[1], name, strlen(name));
+ write(in[1], "\n", 1);
+ close(in[1]);
+ break;
+ }
+}
+
+static void
+chap_Cleanup(struct chap *chap, int sig)
+{
+ if (chap->child.pid) {
+ int status;
+
+ close(chap->child.fd);
+ chap->child.fd = -1;
+ if (sig)
+ kill(chap->child.pid, SIGTERM);
+ chap->child.pid = 0;
+ chap->child.buf.len = 0;
+
+ if (wait(&status) == -1)
+ log_Printf(LogERROR, "Chap: wait: %s\n", strerror(errno));
+ else if (WIFSIGNALED(status))
+ log_Printf(LogWARN, "Chap: Child received signal %d\n", WTERMSIG(status));
+ else if (WIFEXITED(status) && WEXITSTATUS(status))
+ log_Printf(LogERROR, "Chap: Child exited %d\n", WEXITSTATUS(status));
+ }
+ *chap->challenge.local = *chap->challenge.peer = '\0';
+#ifndef NODES
+ chap->peertries = 0;
+#endif
+}
+
+static void
+chap_Respond(struct chap *chap, char *name, char *key
+#ifndef NODES
+ , u_char type, int lm
+#endif
+ )
+{
+ u_char *ans;
+
+ ans = chap_BuildAnswer(name, key, chap->auth.id, chap->challenge.peer
+#ifndef NODES
+ , type, chap->challenge.local, chap->authresponse, lm
+#endif
+ );
+
+ if (ans) {
+ ChapOutput(chap->auth.physical, CHAP_RESPONSE, chap->auth.id,
+ ans, *ans + 1 + strlen(name), name);
+#ifndef NODES
+ chap->NTRespSent = !lm;
+ MPPE_IsServer = 0; /* XXX Global ! */
+#endif
+ free(ans);
+ } else
+ ChapOutput(chap->auth.physical, CHAP_FAILURE, chap->auth.id,
+ "Out of memory!", 14, NULL);
+}
+
+static int
+chap_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w __unused,
+ fd_set *e __unused, int *n)
+{
+ struct chap *chap = descriptor2chap(d);
+
+ if (r && chap && chap->child.fd != -1) {
+ FD_SET(chap->child.fd, r);
+ if (*n < chap->child.fd + 1)
+ *n = chap->child.fd + 1;
+ log_Printf(LogTIMER, "Chap: fdset(r) %d\n", chap->child.fd);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int
+chap_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+ struct chap *chap = descriptor2chap(d);
+
+ return chap && chap->child.fd != -1 && FD_ISSET(chap->child.fd, fdset);
+}
+
+static void
+chap_Read(struct fdescriptor *d, struct bundle *bundle __unused,
+ const fd_set *fdset __unused)
+{
+ struct chap *chap = descriptor2chap(d);
+ int got;
+
+ got = read(chap->child.fd, chap->child.buf.ptr + chap->child.buf.len,
+ sizeof chap->child.buf.ptr - chap->child.buf.len - 1);
+ if (got == -1) {
+ log_Printf(LogERROR, "Chap: Read: %s\n", strerror(errno));
+ chap_Cleanup(chap, SIGTERM);
+ } else if (got == 0) {
+ log_Printf(LogWARN, "Chap: Read: Child terminated connection\n");
+ chap_Cleanup(chap, SIGTERM);
+ } else {
+ char *name, *key, *end;
+
+ chap->child.buf.len += got;
+ chap->child.buf.ptr[chap->child.buf.len] = '\0';
+ name = chap->child.buf.ptr;
+ name += strspn(name, " \t");
+ if ((key = strchr(name, '\n')) == NULL)
+ end = NULL;
+ else
+ end = strchr(++key, '\n');
+
+ if (end == NULL) {
+ if (chap->child.buf.len == sizeof chap->child.buf.ptr - 1) {
+ log_Printf(LogWARN, "Chap: Read: Input buffer overflow\n");
+ chap_Cleanup(chap, SIGTERM);
+ }
+ } else {
+#ifndef NODES
+ int lanman = chap->auth.physical->link.lcp.his_authtype == 0x80 &&
+ ((chap->NTRespSent &&
+ IsAccepted(chap->auth.physical->link.lcp.cfg.chap80lm)) ||
+ !IsAccepted(chap->auth.physical->link.lcp.cfg.chap80nt));
+#endif
+
+ while (end >= name && strchr(" \t\r\n", *end))
+ *end-- = '\0';
+ end = key - 1;
+ while (end >= name && strchr(" \t\r\n", *end))
+ *end-- = '\0';
+ key += strspn(key, " \t");
+
+ chap_Respond(chap, name, key
+#ifndef NODES
+ , chap->auth.physical->link.lcp.his_authtype, lanman
+#endif
+ );
+ chap_Cleanup(chap, 0);
+ }
+ }
+}
+
+static int
+chap_Write(struct fdescriptor *d __unused, struct bundle *bundle __unused,
+ const fd_set *fdset __unused)
+{
+ /* We never want to write here ! */
+ log_Printf(LogALERT, "chap_Write: Internal error: Bad call !\n");
+ return 0;
+}
+
+static void
+chap_ChallengeInit(struct authinfo *authp)
+{
+ struct chap *chap = auth2chap(authp);
+ int len, i;
+ char *cp;
+
+ len = strlen(authp->physical->dl->bundle->cfg.auth.name);
+
+ if (!*chap->challenge.local) {
+ randinit();
+ cp = chap->challenge.local;
+
+#ifndef NORADIUS
+ if (*authp->physical->dl->bundle->radius.cfg.file) {
+ /* For radius, our challenge is 16 readable NUL terminated bytes :*/
+ *cp++ = 16;
+ for (i = 0; i < 16; i++)
+ *cp++ = (random() % 10) + '0';
+ } else
+#endif
+ {
+#ifndef NODES
+ if (authp->physical->link.lcp.want_authtype == 0x80)
+ *cp++ = 8; /* MS does 8 byte callenges :-/ */
+ else if (authp->physical->link.lcp.want_authtype == 0x81)
+ *cp++ = 16; /* MS-CHAP-V2 does 16 bytes challenges */
+ else
+#endif
+ *cp++ = random() % (CHAPCHALLENGELEN-16) + 16;
+ for (i = 0; i < *chap->challenge.local; i++)
+ *cp++ = random() & 0xff;
+ }
+ memcpy(cp, authp->physical->dl->bundle->cfg.auth.name, len);
+ }
+}
+
+static void
+chap_Challenge(struct authinfo *authp)
+{
+ struct chap *chap = auth2chap(authp);
+ int len;
+
+ log_Printf(LogDEBUG, "CHAP%02X: Challenge\n",
+ authp->physical->link.lcp.want_authtype);
+
+ len = strlen(authp->physical->dl->bundle->cfg.auth.name);
+
+ /* Generate new local challenge value */
+ if (!*chap->challenge.local)
+ chap_ChallengeInit(authp);
+
+#ifndef NODES
+ if (authp->physical->link.lcp.want_authtype == 0x81)
+ ChapOutput(authp->physical, CHAP_CHALLENGE, authp->id,
+ chap->challenge.local, 1 + *chap->challenge.local, NULL);
+ else
+#endif
+ ChapOutput(authp->physical, CHAP_CHALLENGE, authp->id,
+ chap->challenge.local, 1 + *chap->challenge.local + len, NULL);
+}
+
+static void
+chap_Success(struct authinfo *authp)
+{
+ struct bundle *bundle = authp->physical->dl->bundle;
+ const char *msg;
+
+ datalink_GotAuthname(authp->physical->dl, authp->in.name);
+#ifndef NODES
+ if (authp->physical->link.lcp.want_authtype == 0x81) {
+#ifndef NORADIUS
+ if (*bundle->radius.cfg.file && bundle->radius.msrepstr)
+ msg = bundle->radius.msrepstr;
+ else
+#endif
+ msg = auth2chap(authp)->authresponse;
+ MPPE_MasterKeyValid = 1; /* XXX Global ! */
+ } else
+#endif
+#ifndef NORADIUS
+ if (*bundle->radius.cfg.file && bundle->radius.repstr)
+ msg = bundle->radius.repstr;
+ else
+#endif
+ msg = "Welcome!!";
+
+ ChapOutput(authp->physical, CHAP_SUCCESS, authp->id, msg, strlen(msg),
+ NULL);
+
+ authp->physical->link.lcp.auth_ineed = 0;
+ if (Enabled(bundle, OPT_UTMP))
+ physical_Login(authp->physical, authp->in.name);
+
+ if (authp->physical->link.lcp.auth_iwait == 0)
+ /*
+ * Either I didn't need to authenticate, or I've already been
+ * told that I got the answer right.
+ */
+ datalink_AuthOk(authp->physical->dl);
+}
+
+static void
+chap_Failure(struct authinfo *authp)
+{
+#ifndef NODES
+ char buf[1024], *ptr;
+#endif
+ const char *msg;
+
+#ifndef NORADIUS
+ struct bundle *bundle = authp->physical->link.lcp.fsm.bundle;
+ if (*bundle->radius.cfg.file && bundle->radius.errstr)
+ msg = bundle->radius.errstr;
+ else
+#endif
+#ifndef NODES
+ if (authp->physical->link.lcp.want_authtype == 0x80) {
+ sprintf(buf, "E=691 R=1 M=Invalid!");
+ msg = buf;
+ } else if (authp->physical->link.lcp.want_authtype == 0x81) {
+ int i;
+
+ ptr = buf;
+ ptr += sprintf(buf, "E=691 R=0 C=");
+ for (i=0; i<16; i++)
+ ptr += sprintf(ptr, "%02X", *(auth2chap(authp)->challenge.local+1+i));
+
+ sprintf(ptr, " V=3 M=Invalid!");
+ msg = buf;
+ } else
+#endif
+ msg = "Invalid!!";
+
+ ChapOutput(authp->physical, CHAP_FAILURE, authp->id, msg, strlen(msg) + 1,
+ NULL);
+ datalink_AuthNotOk(authp->physical->dl);
+}
+
+static int
+chap_Cmp(char *myans, int mylen, char *hisans, int hislen
+#ifndef NODES
+ , u_char type, int lm
+#endif
+ )
+{
+ int off;
+
+ if (mylen != hislen)
+ return 0;
+
+ off = 0;
+
+#ifndef NODES
+ if (type == 0x80) {
+ off = lm ? 0 : 24;
+ mylen = 24;
+ }
+#endif
+
+ for (; mylen; off++, mylen--)
+ if (toupper(myans[off]) != toupper(hisans[off]))
+ return 0;
+
+ return 1;
+}
+
+#ifndef NODES
+static int
+chap_HaveAnotherGo(struct chap *chap)
+{
+ if (++chap->peertries < 3) {
+ /* Give the peer another shot */
+ *chap->challenge.local = '\0';
+ chap_Challenge(&chap->auth);
+ return 1;
+ }
+
+ return 0;
+}
+#endif
+
+void
+chap_Init(struct chap *chap, struct physical *p)
+{
+ chap->desc.type = CHAP_DESCRIPTOR;
+ chap->desc.UpdateSet = chap_UpdateSet;
+ chap->desc.IsSet = chap_IsSet;
+ chap->desc.Read = chap_Read;
+ chap->desc.Write = chap_Write;
+ chap->child.pid = 0;
+ chap->child.fd = -1;
+ auth_Init(&chap->auth, p, chap_Challenge, chap_Success, chap_Failure);
+ *chap->challenge.local = *chap->challenge.peer = '\0';
+#ifndef NODES
+ chap->NTRespSent = 0;
+ chap->peertries = 0;
+#endif
+}
+
+void
+chap_ReInit(struct chap *chap)
+{
+ chap_Cleanup(chap, SIGTERM);
+}
+
+struct mbuf *
+chap_Input(struct bundle *bundle, struct link *l, struct mbuf *bp)
+{
+ struct physical *p = link2physical(l);
+ struct chap *chap = &p->dl->chap;
+ char *name, *key, *ans;
+ int len;
+ size_t nlen;
+ u_char alen;
+#ifndef NODES
+ int lanman;
+#endif
+
+ if (p == NULL) {
+ log_Printf(LogERROR, "chap_Input: Not a physical link - dropped\n");
+ m_freem(bp);
+ return NULL;
+ }
+
+ if (bundle_Phase(bundle) != PHASE_NETWORK &&
+ bundle_Phase(bundle) != PHASE_AUTHENTICATE) {
+ log_Printf(LogPHASE, "Unexpected chap input - dropped !\n");
+ m_freem(bp);
+ return NULL;
+ }
+
+ m_settype(bp, MB_CHAPIN);
+ if ((bp = auth_ReadHeader(&chap->auth, bp)) == NULL &&
+ ntohs(chap->auth.in.hdr.length) == 0)
+ log_Printf(LogWARN, "Chap Input: Truncated header !\n");
+ else if (chap->auth.in.hdr.code == 0 || chap->auth.in.hdr.code > MAXCHAPCODE)
+ log_Printf(LogPHASE, "Chap Input: %d: Bad CHAP code !\n",
+ chap->auth.in.hdr.code);
+ else {
+ len = m_length(bp);
+ ans = NULL;
+
+ if (chap->auth.in.hdr.code != CHAP_CHALLENGE &&
+ chap->auth.id != chap->auth.in.hdr.id &&
+ Enabled(bundle, OPT_IDCHECK)) {
+ /* Wrong conversation dude ! */
+ log_Printf(LogPHASE, "Chap Input: %s dropped (got id %d, not %d)\n",
+ chapcodes[chap->auth.in.hdr.code], chap->auth.in.hdr.id,
+ chap->auth.id);
+ m_freem(bp);
+ return NULL;
+ }
+ chap->auth.id = chap->auth.in.hdr.id; /* We respond with this id */
+
+#ifndef NODES
+ lanman = 0;
+#endif
+ switch (chap->auth.in.hdr.code) {
+ case CHAP_CHALLENGE:
+ bp = mbuf_Read(bp, &alen, 1);
+ len -= alen + 1;
+ if (len < 0) {
+ log_Printf(LogERROR, "Chap Input: Truncated challenge !\n");
+ m_freem(bp);
+ return NULL;
+ }
+ *chap->challenge.peer = alen;
+ bp = mbuf_Read(bp, chap->challenge.peer + 1, alen);
+ bp = auth_ReadName(&chap->auth, bp, len);
+#ifndef NODES
+ lanman = p->link.lcp.his_authtype == 0x80 &&
+ ((chap->NTRespSent && IsAccepted(p->link.lcp.cfg.chap80lm)) ||
+ !IsAccepted(p->link.lcp.cfg.chap80nt));
+
+ /* Generate local challenge value */
+ chap_ChallengeInit(&chap->auth);
+#endif
+ break;
+
+ case CHAP_RESPONSE:
+ auth_StopTimer(&chap->auth);
+ bp = mbuf_Read(bp, &alen, 1);
+ len -= alen + 1;
+ if (len < 0) {
+ log_Printf(LogERROR, "Chap Input: Truncated response !\n");
+ m_freem(bp);
+ return NULL;
+ }
+ if ((ans = malloc(alen + 1)) == NULL) {
+ log_Printf(LogERROR, "Chap Input: Out of memory !\n");
+ m_freem(bp);
+ return NULL;
+ }
+ *ans = chap->auth.id;
+ bp = mbuf_Read(bp, ans + 1, alen);
+ bp = auth_ReadName(&chap->auth, bp, len);
+#ifndef NODES
+ lanman = p->link.lcp.want_authtype == 0x80 &&
+ alen == 49 && ans[alen] == 0;
+#endif
+ break;
+
+ case CHAP_SUCCESS:
+ case CHAP_FAILURE:
+ /* chap->auth.in.name is already set up at CHALLENGE time */
+ if ((ans = malloc(len + 1)) == NULL) {
+ log_Printf(LogERROR, "Chap Input: Out of memory !\n");
+ m_freem(bp);
+ return NULL;
+ }
+ bp = mbuf_Read(bp, ans, len);
+ ans[len] = '\0';
+ break;
+ }
+
+ switch (chap->auth.in.hdr.code) {
+ case CHAP_CHALLENGE:
+ case CHAP_RESPONSE:
+ if (*chap->auth.in.name)
+ log_Printf(LogPHASE, "Chap Input: %s (%d bytes from %s%s)\n",
+ chapcodes[chap->auth.in.hdr.code], alen,
+ chap->auth.in.name,
+#ifndef NODES
+ lanman && chap->auth.in.hdr.code == CHAP_RESPONSE ?
+ " - lanman" :
+#endif
+ "");
+ else
+ log_Printf(LogPHASE, "Chap Input: %s (%d bytes%s)\n",
+ chapcodes[chap->auth.in.hdr.code], alen,
+#ifndef NODES
+ lanman && chap->auth.in.hdr.code == CHAP_RESPONSE ?
+ " - lanman" :
+#endif
+ "");
+ break;
+
+ case CHAP_SUCCESS:
+ case CHAP_FAILURE:
+ if (*ans)
+ log_Printf(LogPHASE, "Chap Input: %s (%s)\n",
+ chapcodes[chap->auth.in.hdr.code], ans);
+ else
+ log_Printf(LogPHASE, "Chap Input: %s\n",
+ chapcodes[chap->auth.in.hdr.code]);
+ break;
+ }
+
+ switch (chap->auth.in.hdr.code) {
+ case CHAP_CHALLENGE:
+ if (*bundle->cfg.auth.key == '!' && bundle->cfg.auth.key[1] != '!')
+ chap_StartChild(chap, bundle->cfg.auth.key + 1,
+ bundle->cfg.auth.name);
+ else
+ chap_Respond(chap, bundle->cfg.auth.name, bundle->cfg.auth.key +
+ (*bundle->cfg.auth.key == '!' ? 1 : 0)
+
+#ifndef NODES
+ , p->link.lcp.his_authtype, lanman
+#endif
+ );
+ break;
+
+ case CHAP_RESPONSE:
+ name = chap->auth.in.name;
+ nlen = strlen(name);
+#ifndef NODES
+ if (p->link.lcp.want_authtype == 0x81) {
+ struct MSCHAPv2_resp *resp = (struct MSCHAPv2_resp *)(ans + 1);
+
+ chap->challenge.peer[0] = sizeof resp->PeerChallenge;
+ memcpy(chap->challenge.peer + 1, resp->PeerChallenge,
+ sizeof resp->PeerChallenge);
+ }
+#endif
+
+#ifndef NORADIUS
+ if (*bundle->radius.cfg.file) {
+ if (!radius_Authenticate(&bundle->radius, &chap->auth,
+ chap->auth.in.name, ans, alen + 1,
+ chap->challenge.local + 1,
+ *chap->challenge.local))
+ chap_Failure(&chap->auth);
+ } else
+#endif
+ {
+ if (p->link.lcp.want_authtype == 0x81 && ans[alen] != '\0' &&
+ alen == sizeof(struct MSCHAPv2_resp)) {
+ struct MSCHAPv2_resp *resp = (struct MSCHAPv2_resp *)(ans + 1);
+
+ log_Printf(LogWARN, "%s: Compensating for corrupt (Win98/WinME?) "
+ "CHAP81 RESPONSE\n", l->name);
+ resp->Flags = '\0'; /* rfc2759 says it *MUST* be zero */
+ }
+ key = auth_GetSecret(name, nlen);
+ if (key) {
+#ifndef NODES
+ if (p->link.lcp.want_authtype == 0x80 &&
+ lanman && !IsEnabled(p->link.lcp.cfg.chap80lm)) {
+ log_Printf(LogPHASE, "Auth failure: LANMan not enabled\n");
+ if (chap_HaveAnotherGo(chap))
+ break;
+ key = NULL;
+ } else if (p->link.lcp.want_authtype == 0x80 &&
+ !lanman && !IsEnabled(p->link.lcp.cfg.chap80nt)) {
+ log_Printf(LogPHASE, "Auth failure: mschap not enabled\n");
+ if (chap_HaveAnotherGo(chap))
+ break;
+ key = NULL;
+ } else if (p->link.lcp.want_authtype == 0x81 &&
+ !IsEnabled(p->link.lcp.cfg.chap81)) {
+ log_Printf(LogPHASE, "Auth failure: CHAP81 not enabled\n");
+ key = NULL;
+ } else
+#endif
+ {
+ char *myans = chap_BuildAnswer(name, key, chap->auth.id,
+ chap->challenge.local
+#ifndef NODES
+ , p->link.lcp.want_authtype,
+ chap->challenge.peer,
+ chap->authresponse, lanman);
+ MPPE_IsServer = 1; /* XXX Global ! */
+#else
+ );
+#endif
+ if (myans == NULL)
+ key = NULL;
+ else {
+ if (!chap_Cmp(myans + 1, *myans, ans + 1, alen
+#ifndef NODES
+ , p->link.lcp.want_authtype, lanman
+#endif
+ ))
+ key = NULL;
+ free(myans);
+ }
+ }
+ }
+
+ if (key)
+ chap_Success(&chap->auth);
+ else
+ chap_Failure(&chap->auth);
+ }
+
+ break;
+
+ case CHAP_SUCCESS:
+ if (p->link.lcp.auth_iwait == PROTO_CHAP) {
+ p->link.lcp.auth_iwait = 0;
+ if (p->link.lcp.auth_ineed == 0) {
+#ifndef NODES
+ if (p->link.lcp.his_authtype == 0x81) {
+ if (strncasecmp(ans, chap->authresponse, 42)) {
+ datalink_AuthNotOk(p->dl);
+ log_Printf(LogWARN, "CHAP81: AuthenticatorResponse: (%.42s)"
+ " != ans: (%.42s)\n", chap->authresponse, ans);
+
+ } else {
+ /* Successful login */
+ MPPE_MasterKeyValid = 1; /* XXX Global ! */
+ datalink_AuthOk(p->dl);
+ }
+ } else
+#endif
+ /*
+ * We've succeeded in our ``login''
+ * If we're not expecting the peer to authenticate (or he already
+ * has), proceed to network phase.
+ */
+ datalink_AuthOk(p->dl);
+ }
+ }
+ break;
+
+ case CHAP_FAILURE:
+ datalink_AuthNotOk(p->dl);
+ break;
+ }
+ free(ans);
+ }
+
+ m_freem(bp);
+ return NULL;
+}
diff --git a/usr.sbin/ppp/chap.h b/usr.sbin/ppp/chap.h
new file mode 100644
index 0000000..08c6865
--- /dev/null
+++ b/usr.sbin/ppp/chap.h
@@ -0,0 +1,75 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+struct mbuf;
+struct physical;
+
+#define CHAP_CHALLENGE 1
+#define CHAP_RESPONSE 2
+#define CHAP_SUCCESS 3
+#define CHAP_FAILURE 4
+
+struct chap {
+ struct fdescriptor desc;
+ struct {
+ pid_t pid;
+ int fd;
+ struct {
+ char ptr[AUTHLEN * 2 + 3]; /* Allow for \r\n at the end (- NUL) */
+ int len;
+ } buf;
+ } child;
+ struct authinfo auth;
+ struct {
+ u_char local[CHAPCHALLENGELEN + AUTHLEN]; /* I invented this one */
+ u_char peer[CHAPCHALLENGELEN + AUTHLEN]; /* Peer gave us this one */
+ } challenge;
+#ifndef NODES
+ unsigned NTRespSent : 1; /* Our last response */
+ int peertries;
+ u_char authresponse[CHAPAUTHRESPONSELEN]; /* CHAP 81 response */
+#endif
+};
+
+#define descriptor2chap(d) \
+ ((d)->type == CHAP_DESCRIPTOR ? (struct chap *)(d) : NULL)
+#define auth2chap(a) \
+ ((struct chap *)((char *)a - (uintptr_t)&((struct chap *)0)->auth))
+
+struct MSCHAPv2_resp { /* rfc2759 */
+ char PeerChallenge[16];
+ char Reserved[8];
+ char NTResponse[24];
+ char Flags;
+};
+
+extern void chap_Init(struct chap *, struct physical *);
+extern void chap_ReInit(struct chap *);
+extern struct mbuf *chap_Input(struct bundle *, struct link *, struct mbuf *);
diff --git a/usr.sbin/ppp/chap_ms.c b/usr.sbin/ppp/chap_ms.c
new file mode 100644
index 0000000..24e959c
--- /dev/null
+++ b/usr.sbin/ppp/chap_ms.c
@@ -0,0 +1,415 @@
+/*-
+ * Copyright (c) 1997 Gabor Kincses <gabor@acm.org>
+ * 1997 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Eric Rosenquist
+ * Strata Software Limited.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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>
+#ifdef __FreeBSD__
+#include <openssl/des.h>
+#include <sha.h>
+#else
+#include <sys/types.h>
+#include <stdlib.h>
+#ifdef __NetBSD__
+#include <openssl/des.h>
+#else
+#include <des.h>
+#endif
+#include <openssl/sha.h>
+#endif
+#include <md4.h>
+#include <string.h>
+
+#include "chap_ms.h"
+
+/*
+ * Documentation & specifications:
+ *
+ * MS-CHAP (CHAP80) rfc2433
+ * MS-CHAP-V2 (CHAP81) rfc2759
+ * MPPE key management draft-ietf-pppext-mppe-keys-02.txt
+ */
+
+static char SHA1_Pad1[40] =
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static char SHA1_Pad2[40] =
+ {0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2,
+ 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2,
+ 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2,
+ 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2};
+
+/* unused, for documentation only */
+/* only NTResp is filled in for FreeBSD */
+struct MS_ChapResponse {
+ u_char LANManResp[24];
+ u_char NTResp[24];
+ u_char UseNT; /* If 1, ignore the LANMan response field */
+};
+
+static u_char
+Get7Bits(u_char *input, int startBit)
+{
+ register unsigned int word;
+
+ word = (unsigned)input[startBit / 8] << 8;
+ word |= (unsigned)input[startBit / 8 + 1];
+
+ word >>= 15 - (startBit % 8 + 7);
+
+ return word & 0xFE;
+}
+
+/* IN 56 bit DES key missing parity bits
+ OUT 64 bit DES key with parity bits added */
+static void
+MakeKey(u_char *key, u_char *des_key)
+{
+ des_key[0] = Get7Bits(key, 0);
+ des_key[1] = Get7Bits(key, 7);
+ des_key[2] = Get7Bits(key, 14);
+ des_key[3] = Get7Bits(key, 21);
+ des_key[4] = Get7Bits(key, 28);
+ des_key[5] = Get7Bits(key, 35);
+ des_key[6] = Get7Bits(key, 42);
+ des_key[7] = Get7Bits(key, 49);
+
+ des_set_odd_parity((des_cblock *)des_key);
+}
+
+static void /* IN 8 octets IN 7 octest OUT 8 octets */
+DesEncrypt(u_char *clear, u_char *key, u_char *cipher)
+{
+ des_cblock des_key;
+ des_key_schedule key_schedule;
+
+ MakeKey(key, des_key);
+ des_set_key(&des_key, key_schedule);
+ des_ecb_encrypt((des_cblock *)clear, (des_cblock *)cipher, key_schedule, 1);
+}
+
+static void /* IN 8 octets IN 16 octets OUT 24 octets */
+ChallengeResponse(u_char *challenge, u_char *pwHash, u_char *response)
+{
+ char ZPasswordHash[21];
+
+ memset(ZPasswordHash, '\0', sizeof ZPasswordHash);
+ memcpy(ZPasswordHash, pwHash, 16);
+
+ DesEncrypt(challenge, ZPasswordHash + 0, response + 0);
+ DesEncrypt(challenge, ZPasswordHash + 7, response + 8);
+ DesEncrypt(challenge, ZPasswordHash + 14, response + 16);
+}
+
+void
+NtPasswordHash(char *key, int keylen, char *hash)
+{
+ MD4_CTX MD4context;
+
+ MD4Init(&MD4context);
+ MD4Update(&MD4context, key, keylen);
+ MD4Final(hash, &MD4context);
+}
+
+void
+HashNtPasswordHash(char *hash, char *hashhash)
+{
+ MD4_CTX MD4context;
+
+ MD4Init(&MD4context);
+ MD4Update(&MD4context, hash, 16);
+ MD4Final(hashhash, &MD4context);
+}
+
+static void
+ChallengeHash(char *PeerChallenge, char *AuthenticatorChallenge,
+ char *UserName, char *Challenge)
+{
+ SHA_CTX Context;
+ char Digest[SHA_DIGEST_LENGTH];
+ char *Name;
+
+ Name = strrchr(UserName, '\\');
+ if(NULL == Name)
+ Name = UserName;
+ else
+ Name++;
+
+ SHA1_Init(&Context);
+
+ SHA1_Update(&Context, PeerChallenge, 16);
+ SHA1_Update(&Context, AuthenticatorChallenge, 16);
+ SHA1_Update(&Context, Name, strlen(Name));
+
+ SHA1_Final(Digest, &Context);
+ memcpy(Challenge, Digest, 8);
+}
+
+void
+GenerateNTResponse(char *AuthenticatorChallenge, char *PeerChallenge,
+ char *UserName, char *Password,
+ int PasswordLen, char *Response)
+{
+ char Challenge[8];
+ char PasswordHash[16];
+
+ ChallengeHash(PeerChallenge, AuthenticatorChallenge, UserName, Challenge);
+ NtPasswordHash(Password, PasswordLen, PasswordHash);
+ ChallengeResponse(Challenge, PasswordHash, Response);
+}
+
+#ifndef __FreeBSD__
+#define LENGTH 20
+static char *
+SHA1_End(SHA_CTX *ctx, char *buf)
+{
+ int i;
+ unsigned char digest[LENGTH];
+ static const char hex[]="0123456789abcdef";
+
+ if (!buf)
+ buf = malloc(2*LENGTH + 1);
+ if (!buf)
+ return 0;
+ SHA1_Final(digest, ctx);
+ for (i = 0; i < LENGTH; i++) {
+ buf[i+i] = hex[digest[i] >> 4];
+ buf[i+i+1] = hex[digest[i] & 0x0f];
+ }
+ buf[i+i] = '\0';
+ return buf;
+}
+#endif
+
+void
+GenerateAuthenticatorResponse(char *Password, int PasswordLen,
+ char *NTResponse, char *PeerChallenge,
+ char *AuthenticatorChallenge, char *UserName,
+ char *AuthenticatorResponse)
+{
+ SHA_CTX Context;
+ char PasswordHash[16];
+ char PasswordHashHash[16];
+ char Challenge[8];
+ u_char Digest[SHA_DIGEST_LENGTH];
+ int i;
+
+ /*
+ * "Magic" constants used in response generation
+ */
+ char Magic1[39] =
+ {0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
+ 0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
+ 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
+ 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74};
+
+
+ char Magic2[41] =
+ {0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
+ 0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
+ 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
+ 0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
+ 0x6E};
+ /*
+ * Hash the password with MD4
+ */
+ NtPasswordHash(Password, PasswordLen, PasswordHash);
+ /*
+ * Now hash the hash
+ */
+ HashNtPasswordHash(PasswordHash, PasswordHashHash);
+
+ SHA1_Init(&Context);
+ SHA1_Update(&Context, PasswordHashHash, 16);
+ SHA1_Update(&Context, NTResponse, 24);
+ SHA1_Update(&Context, Magic1, 39);
+ SHA1_Final(Digest, &Context);
+ ChallengeHash(PeerChallenge, AuthenticatorChallenge, UserName, Challenge);
+ SHA1_Init(&Context);
+ SHA1_Update(&Context, Digest, 20);
+ SHA1_Update(&Context, Challenge, 8);
+ SHA1_Update(&Context, Magic2, 41);
+
+ /*
+ * Encode the value of 'Digest' as "S=" followed by
+ * 40 ASCII hexadecimal digits and return it in
+ * AuthenticatorResponse.
+ * For example,
+ * "S=0123456789ABCDEF0123456789ABCDEF01234567"
+ */
+ AuthenticatorResponse[0] = 'S';
+ AuthenticatorResponse[1] = '=';
+ SHA1_End(&Context, AuthenticatorResponse + 2);
+ for (i=2; i<42; i++)
+ AuthenticatorResponse[i] = toupper(AuthenticatorResponse[i]);
+
+}
+
+void
+GetMasterKey(char *PasswordHashHash, char *NTResponse, char *MasterKey)
+{
+ char Digest[SHA_DIGEST_LENGTH];
+ SHA_CTX Context;
+ static char Magic1[27] =
+ {0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
+ 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
+ 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79};
+
+ SHA1_Init(&Context);
+ SHA1_Update(&Context, PasswordHashHash, 16);
+ SHA1_Update(&Context, NTResponse, 24);
+ SHA1_Update(&Context, Magic1, 27);
+ SHA1_Final(Digest, &Context);
+ memcpy(MasterKey, Digest, 16);
+}
+
+void
+GetAsymetricStartKey(char *MasterKey, char *SessionKey, int SessionKeyLength,
+ int IsSend, int IsServer)
+{
+ char Digest[SHA_DIGEST_LENGTH];
+ SHA_CTX Context;
+ char *s;
+
+ static char Magic2[84] =
+ {0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
+ 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
+ 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
+ 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
+ 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
+ 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
+ 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
+ 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
+ 0x6b, 0x65, 0x79, 0x2e};
+
+ static char Magic3[84] =
+ {0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
+ 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
+ 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
+ 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
+ 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
+ 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
+ 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
+ 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
+ 0x6b, 0x65, 0x79, 0x2e};
+
+ if (IsSend) {
+ if (IsServer) {
+ s = Magic3;
+ } else {
+ s = Magic2;
+ }
+ } else {
+ if (IsServer) {
+ s = Magic2;
+ } else {
+ s = Magic3;
+ }
+ }
+
+ SHA1_Init(&Context);
+ SHA1_Update(&Context, MasterKey, 16);
+ SHA1_Update(&Context, SHA1_Pad1, 40);
+ SHA1_Update(&Context, s, 84);
+ SHA1_Update(&Context, SHA1_Pad2, 40);
+ SHA1_Final(Digest, &Context);
+
+ memcpy(SessionKey, Digest, SessionKeyLength);
+}
+
+void
+GetNewKeyFromSHA(char *StartKey, char *SessionKey, long SessionKeyLength,
+ char *InterimKey)
+{
+ SHA_CTX Context;
+ char Digest[SHA_DIGEST_LENGTH];
+
+ SHA1_Init(&Context);
+ SHA1_Update(&Context, StartKey, SessionKeyLength);
+ SHA1_Update(&Context, SHA1_Pad1, 40);
+ SHA1_Update(&Context, SessionKey, SessionKeyLength);
+ SHA1_Update(&Context, SHA1_Pad2, 40);
+ SHA1_Final(Digest, &Context);
+
+ memcpy(InterimKey, Digest, SessionKeyLength);
+}
+
+#if 0
+static void
+Get_Key(char *InitialSessionKey, char *CurrentSessionKey,
+ int LengthOfDesiredKey)
+{
+ SHA_CTX Context;
+ char Digest[SHA_DIGEST_LENGTH];
+
+ SHA1_Init(&Context);
+ SHA1_Update(&Context, InitialSessionKey, LengthOfDesiredKey);
+ SHA1_Update(&Context, SHA1_Pad1, 40);
+ SHA1_Update(&Context, CurrentSessionKey, LengthOfDesiredKey);
+ SHA1_Update(&Context, SHA1_Pad2, 40);
+ SHA1_Final(Digest, &Context);
+
+ memcpy(CurrentSessionKey, Digest, LengthOfDesiredKey);
+}
+#endif
+
+/* passwordHash 16-bytes MD4 hashed password
+ challenge 8-bytes peer CHAP challenge
+ since passwordHash is in a 24-byte buffer, response is written in there */
+void
+mschap_NT(char *passwordHash, char *challenge)
+{
+ u_char response[24];
+
+ ChallengeResponse(challenge, passwordHash, response);
+ memcpy(passwordHash, response, 24);
+ passwordHash[24] = 1; /* NT-style response */
+}
+
+void
+mschap_LANMan(char *digest, char *challenge, char *secret)
+{
+ static u_char salt[] = "KGS!@#$%"; /* RASAPI32.dll */
+ char SECRET[14], *ptr, *end;
+ u_char hash[16];
+
+ end = SECRET + sizeof SECRET;
+ for (ptr = SECRET; *secret && ptr < end; ptr++, secret++)
+ *ptr = toupper(*secret);
+ if (ptr < end)
+ memset(ptr, '\0', end - ptr);
+
+ DesEncrypt(salt, SECRET, hash);
+ DesEncrypt(salt, SECRET + 7, hash + 8);
+
+ ChallengeResponse(challenge, hash, digest);
+}
diff --git a/usr.sbin/ppp/chap_ms.h b/usr.sbin/ppp/chap_ms.h
new file mode 100644
index 0000000..8a69f93
--- /dev/null
+++ b/usr.sbin/ppp/chap_ms.h
@@ -0,0 +1,52 @@
+/*-
+ * Copyright (c) 1997 Gabor Kincses <gabor@acm.org>
+ * 1997 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Eric Rosenquist
+ * Strata Software Limited.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+/* Max # of (Unicode) chars in an NT password */
+#define MAX_NT_PASSWORD 256
+
+/* Don't rely on sizeof(MS_ChapResponse) in case of struct padding */
+#define MS_CHAP_RESPONSE_LEN 49
+#define CHAP81_RESPONSE_LEN 49
+#define CHAP81_NTRESPONSE_LEN 24
+#define CHAP81_NTRESPONSE_OFF 24
+#define CHAP81_HASH_LEN 16
+#define CHAP81_AUTHRESPONSE_LEN 42
+#define CHAP81_CHALLENGE_LEN 16
+
+extern void mschap_NT(char *, char *);
+extern void mschap_LANMan(char *, char *, char *);
+extern void GenerateNTResponse(char *, char *, char *, char *, int , char *);
+extern void HashNtPasswordHash(char *, char *);
+extern void NtPasswordHash(char *, int, char *);
+extern void GenerateAuthenticatorResponse(char *, int, char *, char *, char *, char *, char *);
+extern void GetAsymetricStartKey(char *, char *, int, int, int);
+extern void GetMasterKey(char *, char *, char *);
+extern void GetNewKeyFromSHA(char *, char *, long, char *);
diff --git a/usr.sbin/ppp/chat.c b/usr.sbin/ppp/chat.c
new file mode 100644
index 0000000..daa52cd
--- /dev/null
+++ b/usr.sbin/ppp/chat.c
@@ -0,0 +1,802 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.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/param.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "mbuf.h"
+#include "log.h"
+#include "defs.h"
+#include "timer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "fsm.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "async.h"
+#include "descriptor.h"
+#include "physical.h"
+#include "chat.h"
+#include "mp.h"
+#include "auth.h"
+#include "chap.h"
+#include "slcompress.h"
+#include "iplist.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "cbcp.h"
+#include "command.h"
+#include "datalink.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "id.h"
+
+#define BUFLEFT(c) (sizeof (c)->buf - ((c)->bufend - (c)->buf))
+
+static void ExecStr(struct physical *, char *, char *, int);
+static char *ExpandString(struct chat *, const char *, char *, int, int);
+
+static void
+chat_PauseTimer(void *v)
+{
+ struct chat *c = (struct chat *)v;
+ timer_Stop(&c->pause);
+ c->pause.load = 0;
+}
+
+static void
+chat_Pause(struct chat *c, u_long load)
+{
+ timer_Stop(&c->pause);
+ c->pause.load += load;
+ c->pause.func = chat_PauseTimer;
+ c->pause.name = "chat pause";
+ c->pause.arg = c;
+ timer_Start(&c->pause);
+}
+
+static void
+chat_TimeoutTimer(void *v)
+{
+ struct chat *c = (struct chat *)v;
+ timer_Stop(&c->timeout);
+ c->TimedOut = 1;
+}
+
+static void
+chat_SetTimeout(struct chat *c)
+{
+ timer_Stop(&c->timeout);
+ if (c->TimeoutSec > 0) {
+ c->timeout.load = SECTICKS * c->TimeoutSec;
+ c->timeout.func = chat_TimeoutTimer;
+ c->timeout.name = "chat timeout";
+ c->timeout.arg = c;
+ timer_Start(&c->timeout);
+ }
+}
+
+static char *
+chat_NextChar(char *ptr, char ch)
+{
+ for (; *ptr; ptr++)
+ if (*ptr == ch)
+ return ptr;
+ else if (*ptr == '\\')
+ if (*++ptr == '\0')
+ return NULL;
+
+ return NULL;
+}
+
+static int
+chat_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e, int *n)
+{
+ struct chat *c = descriptor2chat(d);
+ int special, gotabort, gottimeout, needcr;
+ int TimedOut = c->TimedOut;
+ static char arg_term; /* An empty string */
+
+ if (c->pause.state == TIMER_RUNNING)
+ return 0;
+
+ if (TimedOut) {
+ log_Printf(LogCHAT, "Expect timeout\n");
+ if (c->nargptr == NULL)
+ c->state = CHAT_FAILED;
+ else {
+ /* c->state = CHAT_EXPECT; */
+ c->argptr = &arg_term;
+ /*
+ We have to clear the input buffer, because it contains output
+ from the previous (timed out) command.
+ */
+ c->bufstart = c->bufend;
+ }
+ c->TimedOut = 0;
+ }
+
+ if (c->state != CHAT_EXPECT && c->state != CHAT_SEND)
+ return 0;
+
+ gottimeout = gotabort = 0;
+
+ if (c->arg < c->argc && (c->arg < 0 || *c->argptr == '\0')) {
+ /* Go get the next string */
+ if (c->arg < 0 || c->state == CHAT_SEND)
+ c->state = CHAT_EXPECT;
+ else
+ c->state = CHAT_SEND;
+
+ special = 1;
+ while (special && (c->nargptr || c->arg < c->argc - 1)) {
+ if (c->arg < 0 || (!TimedOut && c->state == CHAT_SEND))
+ c->nargptr = NULL;
+
+ if (c->nargptr != NULL) {
+ /* We're doing expect-send-expect.... */
+ c->argptr = c->nargptr;
+ /* Put the '-' back in case we ever want to rerun our script */
+ c->nargptr[-1] = '-';
+ c->nargptr = chat_NextChar(c->nargptr, '-');
+ if (c->nargptr != NULL)
+ *c->nargptr++ = '\0';
+ } else {
+ int minus;
+
+ if ((c->argptr = c->argv[++c->arg]) == NULL) {
+ /* End of script - all ok */
+ c->state = CHAT_DONE;
+ return 0;
+ }
+
+ if (c->state == CHAT_EXPECT) {
+ /* Look for expect-send-expect sequence */
+ c->nargptr = c->argptr;
+ minus = 0;
+ while ((c->nargptr = chat_NextChar(c->nargptr, '-'))) {
+ c->nargptr++;
+ minus++;
+ }
+
+ if (minus % 2)
+ log_Printf(LogWARN, "chat_UpdateSet: \"%s\": Uneven number of"
+ " '-' chars, all ignored\n", c->argptr);
+ else if (minus) {
+ c->nargptr = chat_NextChar(c->argptr, '-');
+ *c->nargptr++ = '\0';
+ }
+ }
+ }
+
+ /*
+ * c->argptr now temporarily points into c->script (via c->argv)
+ * If it's an expect-send-expect sequence, we've just got the correct
+ * portion of that sequence.
+ */
+
+ needcr = c->state == CHAT_SEND &&
+ (*c->argptr != '!' || c->argptr[1] == '!');
+
+ /* We leave room for a potential HDLC header in the target string */
+ ExpandString(c, c->argptr, c->exp + 2, sizeof c->exp - 2, needcr);
+
+ /*
+ * Now read our string. If it's not a special string, we unset
+ * ``special'' to break out of the loop.
+ */
+ if (gotabort) {
+ if (c->abort.num < MAXABORTS) {
+ int len, i;
+
+ len = strlen(c->exp+2);
+ for (i = 0; i < c->abort.num; i++)
+ if (len > c->abort.string[i].len) {
+ int last;
+
+ for (last = c->abort.num; last > i; last--) {
+ c->abort.string[last].data = c->abort.string[last-1].data;
+ c->abort.string[last].len = c->abort.string[last-1].len;
+ }
+ break;
+ }
+ c->abort.string[i].len = len;
+ if ((c->abort.string[i].data = (char *)malloc(len+1)) != NULL) {
+ memcpy(c->abort.string[i].data, c->exp+2, len+1);
+ c->abort.num++;
+ }
+ } else
+ log_Printf(LogERROR, "chat_UpdateSet: too many abort strings\n");
+ gotabort = 0;
+ } else if (gottimeout) {
+ c->TimeoutSec = atoi(c->exp + 2);
+ if (c->TimeoutSec <= 0)
+ c->TimeoutSec = 30;
+ gottimeout = 0;
+ } else if (c->nargptr == NULL && !strcmp(c->exp+2, "ABORT"))
+ gotabort = 1;
+ else if (c->nargptr == NULL && !strcmp(c->exp+2, "TIMEOUT"))
+ gottimeout = 1;
+ else {
+ if (c->exp[2] == '!' && c->exp[3] != '!')
+ ExecStr(c->physical, c->exp + 3, c->exp + 3, sizeof c->exp - 3);
+
+ if (c->exp[2] == '\0') {
+ /* Empty string, reparse (this may be better as a `goto start') */
+ c->argptr = &arg_term;
+ return chat_UpdateSet(d, r, w, e, n);
+ }
+
+ special = 0;
+ }
+ }
+
+ if (special) {
+ if (gottimeout)
+ log_Printf(LogWARN, "chat_UpdateSet: TIMEOUT: Argument expected\n");
+ else if (gotabort)
+ log_Printf(LogWARN, "chat_UpdateSet: ABORT: Argument expected\n");
+
+ /* End of script - all ok */
+ c->state = CHAT_DONE;
+ return 0;
+ }
+
+ /* set c->argptr to point in the right place */
+ c->argptr = c->exp + (c->exp[2] == '!' ? 3 : 2);
+ c->arglen = strlen(c->argptr);
+
+ if (c->state == CHAT_EXPECT) {
+ /* We must check to see if the string's already been found ! */
+ char *begin, *end;
+
+ end = c->bufend - c->arglen + 1;
+ if (end < c->bufstart)
+ end = c->bufstart;
+ for (begin = c->bufstart; begin < end; begin++)
+ if (!strncmp(begin, c->argptr, c->arglen)) {
+ c->bufstart = begin + c->arglen;
+ c->argptr += c->arglen;
+ c->arglen = 0;
+ /* Continue - we've already read our expect string */
+ return chat_UpdateSet(d, r, w, e, n);
+ }
+
+ log_Printf(LogCHAT, "Expect(%d): %s\n", c->TimeoutSec, c->argptr);
+ chat_SetTimeout(c);
+ }
+ }
+
+ /*
+ * We now have c->argptr pointing at what we want to expect/send and
+ * c->state saying what we want to do... we now know what to put in
+ * the fd_set :-)
+ */
+
+ if (c->state == CHAT_EXPECT)
+ return physical_doUpdateSet(&c->physical->desc, r, NULL, e, n, 1);
+ else
+ return physical_doUpdateSet(&c->physical->desc, NULL, w, e, n, 1);
+}
+
+static int
+chat_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+ struct chat *c = descriptor2chat(d);
+ return c->argptr && physical_IsSet(&c->physical->desc, fdset);
+}
+
+static void
+chat_UpdateLog(struct chat *c, int in)
+{
+ if (log_IsKept(LogCHAT) || log_IsKept(LogCONNECT)) {
+ /*
+ * If a linefeed appears in the last `in' characters of `c's input
+ * buffer, output from there, all the way back to the last linefeed.
+ * This is called for every read of `in' bytes.
+ */
+ char *ptr, *end, *stop, ch;
+ int level;
+
+ level = log_IsKept(LogCHAT) ? LogCHAT : LogCONNECT;
+ if (in == -1)
+ end = ptr = c->bufend;
+ else {
+ ptr = c->bufend - in;
+ for (end = c->bufend - 1; end >= ptr; end--)
+ if (*end == '\n')
+ break;
+ }
+
+ if (end >= ptr) {
+ for (ptr = c->bufend - (in == -1 ? 1 : in + 1); ptr >= c->bufstart; ptr--)
+ if (*ptr == '\n')
+ break;
+ ptr++;
+ stop = NULL;
+ while (stop < end) {
+ if ((stop = memchr(ptr, '\n', end - ptr)) == NULL)
+ stop = end;
+ ch = *stop;
+ *stop = '\0';
+ if (level == LogCHAT || strstr(ptr, "CONNECT"))
+ log_Printf(level, "Received: %s\n", ptr);
+ *stop = ch;
+ ptr = stop + 1;
+ }
+ }
+ }
+}
+
+static void
+chat_Read(struct fdescriptor *d, struct bundle *bundle __unused,
+ const fd_set *fdset __unused)
+{
+ struct chat *c = descriptor2chat(d);
+
+ if (c->state == CHAT_EXPECT) {
+ ssize_t in;
+ char *abegin, *ebegin, *begin, *aend, *eend, *end;
+ int n;
+
+ /*
+ * XXX - should this read only 1 byte to guarantee that we don't
+ * swallow any ppp talk from the peer ?
+ */
+ in = BUFLEFT(c);
+ if (in > (ssize_t)sizeof c->buf / 2)
+ in = sizeof c->buf / 2;
+
+ in = physical_Read(c->physical, c->bufend, in);
+ if (in <= 0)
+ return;
+
+ /* `begin' and `end' delimit where we're going to strncmp() from */
+ ebegin = c->bufend - c->arglen + 1;
+ eend = ebegin + in;
+ if (ebegin < c->bufstart)
+ ebegin = c->bufstart;
+
+ if (c->abort.num) {
+ abegin = c->bufend - c->abort.string[0].len + 1;
+ aend = c->bufend - c->abort.string[c->abort.num-1].len + in + 1;
+ if (abegin < c->bufstart)
+ abegin = c->bufstart;
+ } else {
+ abegin = ebegin;
+ aend = eend;
+ }
+ begin = abegin < ebegin ? abegin : ebegin;
+ end = aend < eend ? eend : aend;
+
+ c->bufend += in;
+
+ chat_UpdateLog(c, in);
+
+ if (c->bufend > c->buf + sizeof c->buf / 2) {
+ /* Shuffle our receive buffer back a bit */
+ int chop;
+
+ for (chop = begin - c->buf; chop; chop--)
+ if (c->buf[chop] == '\n')
+ /* found some already-logged garbage to remove :-) */
+ break;
+
+ if (!chop)
+ chop = begin - c->buf;
+
+ if (chop) {
+ char *from, *to;
+
+ to = c->buf;
+ from = to + chop;
+ while (from < c->bufend)
+ *to++ = *from++;
+ c->bufstart -= chop;
+ c->bufend -= chop;
+ begin -= chop;
+ end -= chop;
+ abegin -= chop;
+ aend -= chop;
+ ebegin -= chop;
+ eend -= chop;
+ }
+ }
+
+ for (; begin < end; begin++)
+ if (begin >= ebegin && begin < eend &&
+ !strncmp(begin, c->argptr, c->arglen)) {
+ /* Got it ! */
+ timer_Stop(&c->timeout);
+ if (memchr(begin + c->arglen - 1, '\n',
+ c->bufend - begin - c->arglen + 1) == NULL) {
+ /* force it into the log */
+ end = c->bufend;
+ c->bufend = begin + c->arglen;
+ chat_UpdateLog(c, -1);
+ c->bufend = end;
+ }
+ c->bufstart = begin + c->arglen;
+ c->argptr += c->arglen;
+ c->arglen = 0;
+ break;
+ } else if (begin >= abegin && begin < aend) {
+ for (n = c->abort.num - 1; n >= 0; n--) {
+ if (begin + c->abort.string[n].len > c->bufend)
+ break;
+ if (!strncmp(begin, c->abort.string[n].data,
+ c->abort.string[n].len)) {
+ if (memchr(begin + c->abort.string[n].len - 1, '\n',
+ c->bufend - begin - c->abort.string[n].len + 1) == NULL) {
+ /* force it into the log */
+ end = c->bufend;
+ c->bufend = begin + c->abort.string[n].len;
+ chat_UpdateLog(c, -1);
+ c->bufend = end;
+ }
+ c->bufstart = begin + c->abort.string[n].len;
+ c->state = CHAT_FAILED;
+ return;
+ }
+ }
+ }
+ }
+}
+
+static int
+chat_Write(struct fdescriptor *d, struct bundle *bundle __unused,
+ const fd_set *fdset __unused)
+{
+ struct chat *c = descriptor2chat(d);
+ int result = 0;
+
+ if (c->state == CHAT_SEND) {
+ int wrote;
+
+ if (strstr(c->argv[c->arg], "\\P")) /* Don't log the password */
+ log_Printf(LogCHAT, "Send: %s\n", c->argv[c->arg]);
+ else {
+ int sz;
+
+ sz = c->arglen - 1;
+ while (sz >= 0 && c->argptr[sz] == '\n')
+ sz--;
+ log_Printf(LogCHAT, "Send: %.*s\n", sz + 1, c->argptr);
+ }
+
+ if (physical_IsSync(c->physical)) {
+ /*
+ * XXX: Fix me
+ * This data should be stuffed down through the link layers
+ */
+ /* There's always room for the HDLC header */
+ c->argptr -= 2;
+ c->arglen += 2;
+ memcpy(c->argptr, "\377\003", 2); /* Prepend HDLC header */
+ }
+
+ wrote = physical_Write(c->physical, c->argptr, c->arglen);
+ result = wrote > 0 ? 1 : 0;
+ if (wrote == -1) {
+ if (errno != EINTR) {
+ log_Printf(LogWARN, "chat_Write: %s\n", strerror(errno));
+ result = -1;
+ }
+ if (physical_IsSync(c->physical)) {
+ c->argptr += 2;
+ c->arglen -= 2;
+ }
+ } else if (wrote < 2 && physical_IsSync(c->physical)) {
+ /* Oops - didn't even write our HDLC header ! */
+ c->argptr += 2;
+ c->arglen -= 2;
+ } else {
+ c->argptr += wrote;
+ c->arglen -= wrote;
+ }
+ }
+
+ return result;
+}
+
+void
+chat_Init(struct chat *c, struct physical *p)
+{
+ c->desc.type = CHAT_DESCRIPTOR;
+ c->desc.UpdateSet = chat_UpdateSet;
+ c->desc.IsSet = chat_IsSet;
+ c->desc.Read = chat_Read;
+ c->desc.Write = chat_Write;
+ c->physical = p;
+ *c->script = '\0';
+ c->argc = 0;
+ c->arg = -1;
+ c->argptr = NULL;
+ c->nargptr = NULL;
+ c->bufstart = c->bufend = c->buf;
+
+ memset(&c->pause, '\0', sizeof c->pause);
+ memset(&c->timeout, '\0', sizeof c->timeout);
+}
+
+int
+chat_Setup(struct chat *c, const char *data, const char *phone)
+{
+ c->state = CHAT_EXPECT;
+
+ if (data == NULL) {
+ *c->script = '\0';
+ c->argc = 0;
+ } else {
+ strncpy(c->script, data, sizeof c->script - 1);
+ c->script[sizeof c->script - 1] = '\0';
+ c->argc = MakeArgs(c->script, c->argv, VECSIZE(c->argv), PARSE_NOHASH);
+ }
+
+ c->arg = -1;
+ c->argptr = NULL;
+ c->nargptr = NULL;
+
+ c->TimeoutSec = 30;
+ c->TimedOut = 0;
+ c->phone = phone;
+ c->abort.num = 0;
+
+ timer_Stop(&c->pause);
+ timer_Stop(&c->timeout);
+
+ return c->argc >= 0;
+}
+
+void
+chat_Finish(struct chat *c)
+{
+ timer_Stop(&c->pause);
+ timer_Stop(&c->timeout);
+ while (c->abort.num)
+ free(c->abort.string[--c->abort.num].data);
+ c->abort.num = 0;
+}
+
+void
+chat_Destroy(struct chat *c)
+{
+ chat_Finish(c);
+}
+
+/*
+ * \c don't add a cr
+ * \d Sleep a little (delay 2 seconds
+ * \n Line feed character
+ * \P Auth Key password
+ * \p pause 0.25 sec
+ * \r Carrige return character
+ * \s Space character
+ * \T Telephone number(s) (defined via `set phone')
+ * \t Tab character
+ * \U Auth User
+ */
+static char *
+ExpandString(struct chat *c, const char *str, char *result, int reslen, int cr)
+{
+ int len;
+
+ result[--reslen] = '\0';
+ while (*str && reslen > 0) {
+ switch (*str) {
+ case '\\':
+ str++;
+ switch (*str) {
+ case 'c':
+ cr = 0;
+ break;
+ case 'd': /* Delay 2 seconds */
+ chat_Pause(c, 2 * SECTICKS);
+ break;
+ case 'p':
+ chat_Pause(c, SECTICKS / 4);
+ break; /* Delay 0.25 seconds */
+ case 'n':
+ *result++ = '\n';
+ reslen--;
+ break;
+ case 'r':
+ *result++ = '\r';
+ reslen--;
+ break;
+ case 's':
+ *result++ = ' ';
+ reslen--;
+ break;
+ case 't':
+ *result++ = '\t';
+ reslen--;
+ break;
+ case 'P':
+ strncpy(result, c->physical->dl->bundle->cfg.auth.key, reslen);
+ len = strlen(result);
+ reslen -= len;
+ result += len;
+ break;
+ case 'T':
+ if (c->phone) {
+ strncpy(result, c->phone, reslen);
+ len = strlen(result);
+ reslen -= len;
+ result += len;
+ }
+ break;
+ case 'U':
+ strncpy(result, c->physical->dl->bundle->cfg.auth.name, reslen);
+ len = strlen(result);
+ reslen -= len;
+ result += len;
+ break;
+ default:
+ reslen--;
+ *result++ = *str;
+ break;
+ }
+ if (*str)
+ str++;
+ break;
+ case '^':
+ str++;
+ if (*str) {
+ *result++ = *str++ & 0x1f;
+ reslen--;
+ }
+ break;
+ default:
+ *result++ = *str++;
+ reslen--;
+ break;
+ }
+ }
+ if (--reslen > 0) {
+ if (cr)
+ *result++ = '\r';
+ }
+ if (--reslen > 0)
+ *result++ = '\0';
+ return (result);
+}
+
+static void
+ExecStr(struct physical *physical, char *command, char *out, int olen)
+{
+ pid_t pid;
+ int fids[2];
+ char *argv[MAXARGS], *vector[MAXARGS], *startout, *endout;
+ int stat, nb, argc, i;
+
+ log_Printf(LogCHAT, "Exec: %s\n", command);
+ if ((argc = MakeArgs(command, vector, VECSIZE(vector),
+ PARSE_REDUCE|PARSE_NOHASH)) <= 0) {
+ if (argc < 0)
+ log_Printf(LogWARN, "Syntax error in exec command\n");
+ *out = '\0';
+ return;
+ }
+
+ if (pipe(fids) < 0) {
+ log_Printf(LogCHAT, "Unable to create pipe in ExecStr: %s\n",
+ strerror(errno));
+ *out = '\0';
+ return;
+ }
+ if ((pid = fork()) == 0) {
+ command_Expand(argv, argc, (char const *const *)vector,
+ physical->dl->bundle, 0, getpid());
+ close(fids[0]);
+ timer_TermService();
+ if (fids[1] == STDIN_FILENO)
+ fids[1] = dup(fids[1]);
+ dup2(physical->fd, STDIN_FILENO);
+ dup2(fids[1], STDERR_FILENO);
+ dup2(STDIN_FILENO, STDOUT_FILENO);
+ close(3);
+ if (open(_PATH_TTY, O_RDWR) != 3)
+ open(_PATH_DEVNULL, O_RDWR); /* Leave it closed if it fails... */
+ for (i = getdtablesize(); i > 3; i--)
+ fcntl(i, F_SETFD, 1);
+#ifndef NOSUID
+ setuid(ID0realuid());
+#endif
+ execvp(argv[0], argv);
+ fprintf(stderr, "execvp: %s: %s\n", argv[0], strerror(errno));
+ _exit(127);
+ } else {
+ char *name = strdup(vector[0]);
+
+ close(fids[1]);
+ endout = out + olen - 1;
+ startout = out;
+ while (out < endout) {
+ nb = read(fids[0], out, 1);
+ if (nb <= 0)
+ break;
+ out++;
+ }
+ *out = '\0';
+ close(fids[0]);
+ close(fids[1]);
+ waitpid(pid, &stat, WNOHANG);
+ if (WIFSIGNALED(stat)) {
+ log_Printf(LogWARN, "%s: signal %d\n", name, WTERMSIG(stat));
+ free(name);
+ *out = '\0';
+ return;
+ } else if (WIFEXITED(stat)) {
+ switch (WEXITSTATUS(stat)) {
+ case 0:
+ free(name);
+ break;
+ case 127:
+ log_Printf(LogWARN, "%s: %s\n", name, startout);
+ free(name);
+ *out = '\0';
+ return;
+ break;
+ default:
+ log_Printf(LogWARN, "%s: exit %d\n", name, WEXITSTATUS(stat));
+ free(name);
+ *out = '\0';
+ return;
+ break;
+ }
+ } else {
+ log_Printf(LogWARN, "%s: Unexpected exit result\n", name);
+ free(name);
+ *out = '\0';
+ return;
+ }
+ }
+}
diff --git a/usr.sbin/ppp/chat.h b/usr.sbin/ppp/chat.h
new file mode 100644
index 0000000..91fc7b4
--- /dev/null
+++ b/usr.sbin/ppp/chat.h
@@ -0,0 +1,82 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.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$
+ */
+
+#define CHAT_EXPECT 0
+#define CHAT_SEND 1
+#define CHAT_DONE 2
+#define CHAT_FAILED 3
+
+#define MAXABORTS 50
+
+struct physical;
+
+struct chat {
+ struct fdescriptor desc;
+ struct physical *physical;
+
+ int state; /* Our CHAT_* status */
+
+ char script[LINE_LEN]; /* Our arg buffer */
+ char *argv[MAXARGS]; /* Our arguments (pointing to script) */
+ int argc; /* Number of argv's */
+
+ int arg; /* Our current arg number */
+ char exp[LINE_LEN]; /* Our translated current argument */
+ char *argptr; /* Our current arg pointer */
+ int arglen; /* The length of argptr */
+ char *nargptr; /* Our next for expect-send-expect */
+
+ char buf[LINE_LEN*2]; /* Our input */
+ char *bufstart; /* start of relevant data */
+ char *bufend; /* end of relevant data */
+
+ int TimeoutSec; /* Expect timeout value */
+ int TimedOut; /* We timed out */
+
+ const char *phone; /* Our phone number */
+
+ struct {
+ struct {
+ char *data; /* Abort the dial if we get one */
+ int len;
+ } string[MAXABORTS];
+ int num; /* How many AbortStrings */
+ } abort;
+
+ struct pppTimer pause; /* Inactivity timer */
+ struct pppTimer timeout; /* TimeoutSec timer */
+};
+
+#define descriptor2chat(d) \
+ ((d)->type == CHAT_DESCRIPTOR ? (struct chat *)(d) : NULL)
+#define VECSIZE(v) (sizeof(v) / sizeof(v[0]))
+
+extern void chat_Init(struct chat *, struct physical *);
+extern int chat_Setup(struct chat *, const char *, const char *);
+extern void chat_Finish(struct chat *);
+extern void chat_Destroy(struct chat *);
diff --git a/usr.sbin/ppp/command.c b/usr.sbin/ppp/command.c
new file mode 100644
index 0000000..e90d96b
--- /dev/null
+++ b/usr.sbin/ppp/command.c
@@ -0,0 +1,3332 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <net/route.h>
+#include <netdb.h>
+#include <sys/un.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <unistd.h>
+
+#ifndef NONAT
+#ifdef LOCALNAT
+#include "alias.h"
+#else
+#include <alias.h>
+#endif
+#endif
+
+#include "layer.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#ifndef NONAT
+#include "nat_cmd.h"
+#endif
+#include "systems.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "main.h"
+#include "route.h"
+#include "ccp.h"
+#include "auth.h"
+#include "async.h"
+#include "link.h"
+#include "physical.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "server.h"
+#include "prompt.h"
+#include "chat.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+#include "iface.h"
+#include "id.h"
+#include "probe.h"
+
+/* ``set'' values */
+#define VAR_AUTHKEY 0
+#define VAR_DIAL 1
+#define VAR_LOGIN 2
+#define VAR_AUTHNAME 3
+#define VAR_AUTOLOAD 4
+#define VAR_WINSIZE 5
+#define VAR_DEVICE 6
+#define VAR_ACCMAP 7
+#define VAR_MRRU 8
+#define VAR_MRU 9
+#define VAR_MTU 10
+#define VAR_OPENMODE 11
+#define VAR_PHONE 12
+#define VAR_HANGUP 13
+#define VAR_IDLETIMEOUT 14
+#define VAR_LQRPERIOD 15
+#define VAR_LCPRETRY 16
+#define VAR_CHAPRETRY 17
+#define VAR_PAPRETRY 18
+#define VAR_CCPRETRY 19
+#define VAR_IPCPRETRY 20
+#define VAR_DNS 21
+#define VAR_NBNS 22
+#define VAR_MODE 23
+#define VAR_CALLBACK 24
+#define VAR_CBCP 25
+#define VAR_CHOKED 26
+#define VAR_SENDPIPE 27
+#define VAR_RECVPIPE 28
+#define VAR_RADIUS 29
+#define VAR_CD 30
+#define VAR_PARITY 31
+#define VAR_CRTSCTS 32
+#define VAR_URGENTPORTS 33
+#define VAR_LOGOUT 34
+#define VAR_IFQUEUE 35
+#define VAR_MPPE 36
+#define VAR_IPV6CPRETRY 37
+#define VAR_RAD_ALIVE 38
+#define VAR_PPPOE 39
+#define VAR_PORT_ID 40
+
+/* ``accept|deny|disable|enable'' masks */
+#define NEG_HISMASK (1)
+#define NEG_MYMASK (2)
+
+/* ``accept|deny|disable|enable'' values */
+#define NEG_ACFCOMP 40
+#define NEG_CHAP05 41
+#define NEG_CHAP80 42
+#define NEG_CHAP80LM 43
+#define NEG_DEFLATE 44
+#define NEG_DNS 45
+#define NEG_ECHO 46
+#define NEG_ENDDISC 47
+#define NEG_LQR 48
+#define NEG_PAP 49
+#define NEG_PPPDDEFLATE 50
+#define NEG_PRED1 51
+#define NEG_PROTOCOMP 52
+#define NEG_SHORTSEQ 53
+#define NEG_VJCOMP 54
+#define NEG_MPPE 55
+#define NEG_CHAP81 56
+
+const char Version[] = "3.4.2";
+
+static int ShowCommand(struct cmdargs const *);
+static int TerminalCommand(struct cmdargs const *);
+static int QuitCommand(struct cmdargs const *);
+static int OpenCommand(struct cmdargs const *);
+static int CloseCommand(struct cmdargs const *);
+static int DownCommand(struct cmdargs const *);
+static int SetCommand(struct cmdargs const *);
+static int LinkCommand(struct cmdargs const *);
+static int AddCommand(struct cmdargs const *);
+static int DeleteCommand(struct cmdargs const *);
+static int NegotiateCommand(struct cmdargs const *);
+static int ClearCommand(struct cmdargs const *);
+static int RunListCommand(struct cmdargs const *);
+static int IfaceNameCommand(struct cmdargs const *arg);
+static int IfaceAddCommand(struct cmdargs const *);
+static int IfaceDeleteCommand(struct cmdargs const *);
+static int IfaceClearCommand(struct cmdargs const *);
+static int SetProcTitle(struct cmdargs const *);
+#ifndef NONAT
+static int NatEnable(struct cmdargs const *);
+static int NatOption(struct cmdargs const *);
+#endif
+
+extern struct libalias *la;
+
+static const char *
+showcx(struct cmdtab const *cmd)
+{
+ if (cmd->lauth & LOCAL_CX)
+ return "(c)";
+ else if (cmd->lauth & LOCAL_CX_OPT)
+ return "(o)";
+
+ return "";
+}
+
+static int
+HelpCommand(struct cmdargs const *arg)
+{
+ struct cmdtab const *cmd;
+ int n, cmax, dmax, cols, cxlen;
+ const char *cx;
+
+ if (!arg->prompt) {
+ log_Printf(LogWARN, "help: Cannot help without a prompt\n");
+ return 0;
+ }
+
+ if (arg->argc > arg->argn) {
+ for (cmd = arg->cmdtab; cmd->name || cmd->alias; cmd++)
+ if ((cmd->lauth & arg->prompt->auth) &&
+ ((cmd->name && !strcasecmp(cmd->name, arg->argv[arg->argn])) ||
+ (cmd->alias && !strcasecmp(cmd->alias, arg->argv[arg->argn])))) {
+ prompt_Printf(arg->prompt, "%s %s\n", cmd->syntax, showcx(cmd));
+ return 0;
+ }
+ return -1;
+ }
+
+ cmax = dmax = 0;
+ for (cmd = arg->cmdtab; cmd->func; cmd++)
+ if (cmd->name && (cmd->lauth & arg->prompt->auth)) {
+ if ((n = strlen(cmd->name) + strlen(showcx(cmd))) > cmax)
+ cmax = n;
+ if ((n = strlen(cmd->helpmes)) > dmax)
+ dmax = n;
+ }
+
+ cols = 80 / (dmax + cmax + 3);
+ n = 0;
+ prompt_Printf(arg->prompt, "(o) = Optional context,"
+ " (c) = Context required\n");
+ for (cmd = arg->cmdtab; cmd->func; cmd++)
+ if (cmd->name && (cmd->lauth & arg->prompt->auth)) {
+ cx = showcx(cmd);
+ cxlen = cmax - strlen(cmd->name);
+ if (n % cols != 0)
+ prompt_Printf(arg->prompt, " ");
+ prompt_Printf(arg->prompt, "%s%-*.*s: %-*.*s",
+ cmd->name, cxlen, cxlen, cx, dmax, dmax, cmd->helpmes);
+ if (++n % cols == 0)
+ prompt_Printf(arg->prompt, "\n");
+ }
+ if (n % cols != 0)
+ prompt_Printf(arg->prompt, "\n");
+
+ return 0;
+}
+
+static int
+IdentCommand(struct cmdargs const *arg)
+{
+ Concatinate(arg->cx->physical->link.lcp.cfg.ident,
+ sizeof arg->cx->physical->link.lcp.cfg.ident,
+ arg->argc - arg->argn, arg->argv + arg->argn);
+ return 0;
+}
+
+static int
+SendIdentification(struct cmdargs const *arg)
+{
+ if (arg->cx->state < DATALINK_LCP) {
+ log_Printf(LogWARN, "sendident: link has not reached LCP\n");
+ return 2;
+ }
+ return lcp_SendIdentification(&arg->cx->physical->link.lcp) ? 0 : 1;
+}
+
+static int
+CloneCommand(struct cmdargs const *arg)
+{
+ char namelist[LINE_LEN];
+ char *name;
+ int f;
+
+ if (arg->argc == arg->argn)
+ return -1;
+
+ namelist[sizeof namelist - 1] = '\0';
+ for (f = arg->argn; f < arg->argc; f++) {
+ strncpy(namelist, arg->argv[f], sizeof namelist - 1);
+ for(name = strtok(namelist, ", "); name; name = strtok(NULL,", "))
+ bundle_DatalinkClone(arg->bundle, arg->cx, name);
+ }
+
+ return 0;
+}
+
+static int
+RemoveCommand(struct cmdargs const *arg)
+{
+ if (arg->argc != arg->argn)
+ return -1;
+
+ if (arg->cx->state != DATALINK_CLOSED) {
+ log_Printf(LogWARN, "remove: Cannot delete links that aren't closed\n");
+ return 2;
+ }
+
+ bundle_DatalinkRemove(arg->bundle, arg->cx);
+ return 0;
+}
+
+static int
+RenameCommand(struct cmdargs const *arg)
+{
+ if (arg->argc != arg->argn + 1)
+ return -1;
+
+ if (bundle_RenameDatalink(arg->bundle, arg->cx, arg->argv[arg->argn]))
+ return 0;
+
+ log_Printf(LogWARN, "%s -> %s: target name already exists\n",
+ arg->cx->name, arg->argv[arg->argn]);
+ return 1;
+}
+
+static int
+LoadCommand(struct cmdargs const *arg)
+{
+ const char *err;
+ int n, mode;
+
+ mode = arg->bundle->phys_type.all;
+
+ if (arg->argn < arg->argc) {
+ for (n = arg->argn; n < arg->argc; n++)
+ if ((err = system_IsValid(arg->argv[n], arg->prompt, mode)) != NULL) {
+ log_Printf(LogWARN, "%s: %s\n", arg->argv[n], err);
+ return 1;
+ }
+
+ for (n = arg->argn; n < arg->argc; n++) {
+ bundle_SetLabel(arg->bundle, arg->argv[arg->argc - 1]);
+ system_Select(arg->bundle, arg->argv[n], CONFFILE, arg->prompt, arg->cx);
+ }
+ bundle_SetLabel(arg->bundle, arg->argv[arg->argc - 1]);
+ } else if ((err = system_IsValid("default", arg->prompt, mode)) != NULL) {
+ log_Printf(LogWARN, "default: %s\n", err);
+ return 1;
+ } else {
+ bundle_SetLabel(arg->bundle, "default");
+ system_Select(arg->bundle, "default", CONFFILE, arg->prompt, arg->cx);
+ bundle_SetLabel(arg->bundle, "default");
+ }
+
+ return 0;
+}
+
+static int
+LogCommand(struct cmdargs const *arg)
+{
+ char buf[LINE_LEN];
+
+ if (arg->argn < arg->argc) {
+ char *argv[MAXARGS];
+ int argc = arg->argc - arg->argn;
+
+ if (argc >= (int)(sizeof argv / sizeof argv[0])) {
+ argc = sizeof argv / sizeof argv[0] - 1;
+ log_Printf(LogWARN, "Truncating log command to %d args\n", argc);
+ }
+ command_Expand(argv, argc, arg->argv + arg->argn, arg->bundle, 1, getpid());
+ Concatinate(buf, sizeof buf, argc, (const char *const *)argv);
+ log_Printf(LogLOG, "%s\n", buf);
+ command_Free(argc, argv);
+ return 0;
+ }
+
+ return -1;
+}
+
+static int
+SaveCommand(struct cmdargs const *arg __unused)
+{
+ log_Printf(LogWARN, "save command is not yet implemented.\n");
+ return 1;
+}
+
+static int
+DialCommand(struct cmdargs const *arg)
+{
+ int res;
+
+ if ((arg->cx && !(arg->cx->physical->type & (PHYS_INTERACTIVE|PHYS_AUTO)))
+ || (!arg->cx &&
+ (arg->bundle->phys_type.all & ~(PHYS_INTERACTIVE|PHYS_AUTO)))) {
+ log_Printf(LogWARN, "Manual dial is only available for auto and"
+ " interactive links\n");
+ return 1;
+ }
+
+ if (arg->argc > arg->argn && (res = LoadCommand(arg)) != 0)
+ return res;
+
+ bundle_Open(arg->bundle, arg->cx ? arg->cx->name : NULL, PHYS_ALL, 1);
+
+ return 0;
+}
+
+#define isinword(ch) (isalnum(ch) || (ch) == '_')
+
+static char *
+strstrword(char *big, const char *little)
+{
+ /* Get the first occurrence of the word ``little'' in ``big'' */
+ char *pos;
+ int len;
+
+ pos = big;
+ len = strlen(little);
+
+ while ((pos = strstr(pos, little)) != NULL)
+ if ((pos != big && isinword(pos[-1])) || isinword(pos[len]))
+ pos++;
+ else if (pos != big && pos[-1] == '\\')
+ memmove(pos - 1, pos, strlen(pos) + 1);
+ else
+ break;
+
+ return pos;
+}
+
+static char *
+subst(char *tgt, const char *oldstr, const char *newstr)
+{
+ /* tgt is a malloc()d area... realloc() as necessary */
+ char *word, *ntgt;
+ int ltgt, loldstr, lnewstr, pos;
+
+ if ((word = strstrword(tgt, oldstr)) == NULL)
+ return tgt;
+
+ ltgt = strlen(tgt) + 1;
+ loldstr = strlen(oldstr);
+ lnewstr = strlen(newstr);
+ do {
+ pos = word - tgt;
+ if (loldstr > lnewstr)
+ bcopy(word + loldstr, word + lnewstr, ltgt - pos - loldstr);
+ if (loldstr != lnewstr) {
+ ntgt = realloc(tgt, ltgt += lnewstr - loldstr);
+ if (ntgt == NULL)
+ break; /* Oh wonderful ! */
+ word = ntgt + pos;
+ tgt = ntgt;
+ }
+ if (lnewstr > loldstr)
+ bcopy(word + loldstr, word + lnewstr, ltgt - pos - lnewstr);
+ bcopy(newstr, word, lnewstr);
+ } while ((word = strstrword(word, oldstr)));
+
+ return tgt;
+}
+
+static char *
+substip(char *tgt, const char *oldstr, struct in_addr ip)
+{
+ return subst(tgt, oldstr, inet_ntoa(ip));
+}
+
+static char *
+substlong(char *tgt, const char *oldstr, long l)
+{
+ char buf[23];
+
+ snprintf(buf, sizeof buf, "%ld", l);
+
+ return subst(tgt, oldstr, buf);
+}
+
+static char *
+substull(char *tgt, const char *oldstr, unsigned long long ull)
+{
+ char buf[21];
+
+ snprintf(buf, sizeof buf, "%llu", ull);
+
+ return subst(tgt, oldstr, buf);
+}
+
+
+#ifndef NOINET6
+static char *
+substipv6(char *tgt, const char *oldstr, const struct ncpaddr *ip)
+{
+ return subst(tgt, oldstr, ncpaddr_ntoa(ip));
+}
+
+#ifndef NORADIUS
+static char *
+substipv6prefix(char *tgt, const char *oldstr, const uint8_t *ipv6prefix)
+{
+ uint8_t ipv6addr[INET6_ADDRSTRLEN];
+ uint8_t prefix[INET6_ADDRSTRLEN + sizeof("/128") - 1];
+
+ if (ipv6prefix) {
+ inet_ntop(AF_INET6, &ipv6prefix[2], ipv6addr, sizeof(ipv6addr));
+ snprintf(prefix, sizeof(prefix), "%s/%d", ipv6addr, ipv6prefix[1]);
+ } else
+ prefix[0] = '\0';
+ return subst(tgt, oldstr, prefix);
+}
+#endif
+#endif
+
+void
+command_Expand(char **nargv, int argc, char const *const *oargv,
+ struct bundle *bundle, int inc0, pid_t pid)
+{
+ int arg, secs;
+ char uptime[20];
+ unsigned long long oin, oout, pin, pout;
+
+ if (inc0)
+ arg = 0; /* Start at arg 0 */
+ else {
+ nargv[0] = strdup(oargv[0]);
+ arg = 1;
+ }
+
+ secs = bundle_Uptime(bundle);
+ snprintf(uptime, sizeof uptime, "%d:%02d:%02d",
+ secs / 3600, (secs / 60) % 60, secs % 60);
+ oin = bundle->ncp.ipcp.throughput.OctetsIn;
+ oout = bundle->ncp.ipcp.throughput.OctetsOut;
+ pin = bundle->ncp.ipcp.throughput.PacketsIn;
+ pout = bundle->ncp.ipcp.throughput.PacketsOut;
+#ifndef NOINET6
+ oin += bundle->ncp.ipv6cp.throughput.OctetsIn;
+ oout += bundle->ncp.ipv6cp.throughput.OctetsOut;
+ pin += bundle->ncp.ipv6cp.throughput.PacketsIn;
+ pout += bundle->ncp.ipv6cp.throughput.PacketsOut;
+#endif
+
+ for (; arg < argc; arg++) {
+ nargv[arg] = strdup(oargv[arg]);
+ nargv[arg] = subst(nargv[arg], "AUTHNAME", bundle->cfg.auth.name);
+ nargv[arg] = substip(nargv[arg], "DNS0", bundle->ncp.ipcp.ns.dns[0]);
+ nargv[arg] = substip(nargv[arg], "DNS1", bundle->ncp.ipcp.ns.dns[1]);
+ nargv[arg] = subst(nargv[arg], "ENDDISC",
+ mp_Enddisc(bundle->ncp.mp.cfg.enddisc.class,
+ bundle->ncp.mp.cfg.enddisc.address,
+ bundle->ncp.mp.cfg.enddisc.len));
+ nargv[arg] = substip(nargv[arg], "HISADDR", bundle->ncp.ipcp.peer_ip);
+#ifndef NOINET6
+ nargv[arg] = substipv6(nargv[arg], "HISADDR6", &bundle->ncp.ipv6cp.hisaddr);
+#endif
+ nargv[arg] = subst(nargv[arg], "INTERFACE", bundle->iface->name);
+ nargv[arg] = substull(nargv[arg], "IPOCTETSIN",
+ bundle->ncp.ipcp.throughput.OctetsIn);
+ nargv[arg] = substull(nargv[arg], "IPOCTETSOUT",
+ bundle->ncp.ipcp.throughput.OctetsOut);
+ nargv[arg] = substull(nargv[arg], "IPPACKETSIN",
+ bundle->ncp.ipcp.throughput.PacketsIn);
+ nargv[arg] = substull(nargv[arg], "IPPACKETSOUT",
+ bundle->ncp.ipcp.throughput.PacketsOut);
+#ifndef NOINET6
+ nargv[arg] = substull(nargv[arg], "IPV6OCTETSIN",
+ bundle->ncp.ipv6cp.throughput.OctetsIn);
+ nargv[arg] = substull(nargv[arg], "IPV6OCTETSOUT",
+ bundle->ncp.ipv6cp.throughput.OctetsOut);
+ nargv[arg] = substull(nargv[arg], "IPV6PACKETSIN",
+ bundle->ncp.ipv6cp.throughput.PacketsIn);
+ nargv[arg] = substull(nargv[arg], "IPV6PACKETSOUT",
+ bundle->ncp.ipv6cp.throughput.PacketsOut);
+#endif
+ nargv[arg] = subst(nargv[arg], "LABEL", bundle_GetLabel(bundle));
+ nargv[arg] = substip(nargv[arg], "MYADDR", bundle->ncp.ipcp.my_ip);
+#ifndef NOINET6
+ nargv[arg] = substipv6(nargv[arg], "MYADDR6", &bundle->ncp.ipv6cp.myaddr);
+#ifndef NORADIUS
+ nargv[arg] = substipv6prefix(nargv[arg], "IPV6PREFIX",
+ bundle->radius.ipv6prefix);
+#endif
+#endif
+ nargv[arg] = substull(nargv[arg], "OCTETSIN", oin);
+ nargv[arg] = substull(nargv[arg], "OCTETSOUT", oout);
+ nargv[arg] = substull(nargv[arg], "PACKETSIN", pin);
+ nargv[arg] = substull(nargv[arg], "PACKETSOUT", pout);
+ nargv[arg] = subst(nargv[arg], "PEER_ENDDISC",
+ mp_Enddisc(bundle->ncp.mp.peer.enddisc.class,
+ bundle->ncp.mp.peer.enddisc.address,
+ bundle->ncp.mp.peer.enddisc.len));
+ nargv[arg] = substlong(nargv[arg], "PROCESSID", pid);
+ if (server.cfg.port)
+ nargv[arg] = substlong(nargv[arg], "SOCKNAME", server.cfg.port);
+ else
+ nargv[arg] = subst(nargv[arg], "SOCKNAME", server.cfg.sockname);
+ nargv[arg] = subst(nargv[arg], "UPTIME", uptime);
+ nargv[arg] = subst(nargv[arg], "USER", bundle->ncp.mp.peer.authname);
+ nargv[arg] = subst(nargv[arg], "VERSION", Version);
+ }
+ nargv[arg] = NULL;
+}
+
+void
+command_Free(int argc, char **argv)
+{
+ while (argc) {
+ free(*argv);
+ argc--;
+ argv++;
+ }
+}
+
+static int
+ShellCommand(struct cmdargs const *arg, int bg)
+{
+ const char *shell;
+ pid_t shpid, pid;
+
+#ifdef SHELL_ONLY_INTERACTIVELY
+ /* we're only allowed to shell when we run ppp interactively */
+ if (arg->prompt && arg->prompt->owner) {
+ log_Printf(LogWARN, "Can't start a shell from a network connection\n");
+ return 1;
+ }
+#endif
+
+ if (arg->argc == arg->argn) {
+ if (!arg->prompt) {
+ log_Printf(LogWARN, "Can't start an interactive shell from"
+ " a config file\n");
+ return 1;
+ } else if (arg->prompt->owner) {
+ log_Printf(LogWARN, "Can't start an interactive shell from"
+ " a socket connection\n");
+ return 1;
+ } else if (bg) {
+ log_Printf(LogWARN, "Can only start an interactive shell in"
+ " the foreground mode\n");
+ return 1;
+ }
+ }
+
+ pid = getpid();
+ if ((shpid = fork()) == 0) {
+ int i, fd;
+
+ if ((shell = getenv("SHELL")) == 0)
+ shell = _PATH_BSHELL;
+
+ timer_TermService();
+
+ if (arg->prompt)
+ fd = arg->prompt->fd_out;
+ else if ((fd = open(_PATH_DEVNULL, O_RDWR)) == -1) {
+ log_Printf(LogALERT, "Failed to open %s: %s\n",
+ _PATH_DEVNULL, strerror(errno));
+ exit(1);
+ }
+ dup2(fd, STDIN_FILENO);
+ dup2(fd, STDOUT_FILENO);
+ dup2(fd, STDERR_FILENO);
+ for (i = getdtablesize(); i > STDERR_FILENO; i--)
+ fcntl(i, F_SETFD, 1);
+
+#ifndef NOSUID
+ setuid(ID0realuid());
+#endif
+ if (arg->argc > arg->argn) {
+ /* substitute pseudo args */
+ char *argv[MAXARGS];
+ int argc = arg->argc - arg->argn;
+
+ if (argc >= (int)(sizeof argv / sizeof argv[0])) {
+ argc = sizeof argv / sizeof argv[0] - 1;
+ log_Printf(LogWARN, "Truncating shell command to %d args\n", argc);
+ }
+ command_Expand(argv, argc, arg->argv + arg->argn, arg->bundle, 0, pid);
+ if (bg) {
+ pid_t p;
+
+ p = getpid();
+ if (daemon(1, 1) == -1) {
+ log_Printf(LogERROR, "%ld: daemon: %s\n", (long)p, strerror(errno));
+ exit(1);
+ }
+ } else if (arg->prompt)
+ printf("ppp: Pausing until %s finishes\n", arg->argv[arg->argn]);
+ execvp(argv[0], argv);
+ } else {
+ if (arg->prompt)
+ printf("ppp: Pausing until %s finishes\n", shell);
+ prompt_TtyOldMode(arg->prompt);
+ execl(shell, shell, (char *)NULL);
+ }
+
+ log_Printf(LogWARN, "exec() of %s failed: %s\n",
+ arg->argc > arg->argn ? arg->argv[arg->argn] : shell,
+ strerror(errno));
+ _exit(255);
+ }
+
+ if (shpid == (pid_t)-1)
+ log_Printf(LogERROR, "Fork failed: %s\n", strerror(errno));
+ else {
+ int status;
+ waitpid(shpid, &status, 0);
+ }
+
+ if (arg->prompt && !arg->prompt->owner)
+ prompt_TtyCommandMode(arg->prompt);
+
+ return 0;
+}
+
+static int
+BgShellCommand(struct cmdargs const *arg)
+{
+ if (arg->argc == arg->argn)
+ return -1;
+ return ShellCommand(arg, 1);
+}
+
+static int
+FgShellCommand(struct cmdargs const *arg)
+{
+ return ShellCommand(arg, 0);
+}
+
+static int
+ResolvCommand(struct cmdargs const *arg)
+{
+ if (arg->argc == arg->argn + 1) {
+ if (!strcasecmp(arg->argv[arg->argn], "reload"))
+ ipcp_LoadDNS(&arg->bundle->ncp.ipcp);
+ else if (!strcasecmp(arg->argv[arg->argn], "restore"))
+ ipcp_RestoreDNS(&arg->bundle->ncp.ipcp);
+ else if (!strcasecmp(arg->argv[arg->argn], "rewrite"))
+ ipcp_WriteDNS(&arg->bundle->ncp.ipcp);
+ else if (!strcasecmp(arg->argv[arg->argn], "readonly"))
+ arg->bundle->ncp.ipcp.ns.writable = 0;
+ else if (!strcasecmp(arg->argv[arg->argn], "writable"))
+ arg->bundle->ncp.ipcp.ns.writable = 1;
+ else
+ return -1;
+
+ return 0;
+ }
+
+ return -1;
+}
+
+#ifndef NONAT
+static struct cmdtab const NatCommands[] =
+{
+ {"addr", NULL, nat_RedirectAddr, LOCAL_AUTH,
+ "static address translation", "nat addr [addr_local addr_alias]", NULL},
+ {"deny_incoming", NULL, NatOption, LOCAL_AUTH,
+ "stop incoming connections", "nat deny_incoming yes|no",
+ (const void *) PKT_ALIAS_DENY_INCOMING},
+ {"enable", NULL, NatEnable, LOCAL_AUTH,
+ "enable NAT", "nat enable yes|no", NULL},
+ {"log", NULL, NatOption, LOCAL_AUTH,
+ "log NAT link creation", "nat log yes|no",
+ (const void *) PKT_ALIAS_LOG},
+ {"port", NULL, nat_RedirectPort, LOCAL_AUTH, "port redirection",
+ "nat port proto localaddr:port[-port] aliasport[-aliasport]", NULL},
+ {"proto", NULL, nat_RedirectProto, LOCAL_AUTH, "protocol redirection",
+ "nat proto proto localIP [publicIP [remoteIP]]", NULL},
+ {"proxy", NULL, nat_ProxyRule, LOCAL_AUTH,
+ "proxy control", "nat proxy server host[:port] ...", NULL},
+#ifndef NO_FW_PUNCH
+ {"punch_fw", NULL, nat_PunchFW, LOCAL_AUTH,
+ "firewall control", "nat punch_fw [base count]", NULL},
+#endif
+ {"skinny_port", NULL, nat_SkinnyPort, LOCAL_AUTH,
+ "TCP port used by Skinny Station protocol", "nat skinny_port [port]", NULL},
+ {"same_ports", NULL, NatOption, LOCAL_AUTH,
+ "try to leave port numbers unchanged", "nat same_ports yes|no",
+ (const void *) PKT_ALIAS_SAME_PORTS},
+ {"target", NULL, nat_SetTarget, LOCAL_AUTH,
+ "Default address for incoming connections", "nat target addr", NULL},
+ {"unregistered_only", NULL, NatOption, LOCAL_AUTH,
+ "translate unregistered (private) IP address space only",
+ "nat unregistered_only yes|no",
+ (const void *) PKT_ALIAS_UNREGISTERED_ONLY},
+ {"use_sockets", NULL, NatOption, LOCAL_AUTH,
+ "allocate host sockets", "nat use_sockets yes|no",
+ (const void *) PKT_ALIAS_USE_SOCKETS},
+ {"help", "?", HelpCommand, LOCAL_AUTH | LOCAL_NO_AUTH,
+ "Display this message", "nat help|? [command]", NatCommands},
+ {NULL, NULL, NULL, 0, NULL, NULL, NULL},
+};
+#endif
+
+static struct cmdtab const AllowCommands[] = {
+ {"modes", "mode", AllowModes, LOCAL_AUTH,
+ "Only allow certain ppp modes", "allow modes mode...", NULL},
+ {"users", "user", AllowUsers, LOCAL_AUTH,
+ "Only allow ppp access to certain users", "allow users logname...", NULL},
+ {"help", "?", HelpCommand, LOCAL_AUTH | LOCAL_NO_AUTH,
+ "Display this message", "allow help|? [command]", AllowCommands},
+ {NULL, NULL, NULL, 0, NULL, NULL, NULL},
+};
+
+static struct cmdtab const IfaceCommands[] =
+{
+ {"add", NULL, IfaceAddCommand, LOCAL_AUTH,
+ "Add iface address", "iface add addr[/bits| mask] peer", NULL},
+ {NULL, "add!", IfaceAddCommand, LOCAL_AUTH,
+ "Add or change an iface address", "iface add! addr[/bits| mask] peer",
+ (void *)1},
+ {"clear", NULL, IfaceClearCommand, LOCAL_AUTH,
+ "Clear iface address(es)", "iface clear [INET | INET6]", NULL},
+ {"delete", "rm", IfaceDeleteCommand, LOCAL_AUTH,
+ "Delete iface address", "iface delete addr", NULL},
+ {NULL, "rm!", IfaceDeleteCommand, LOCAL_AUTH,
+ "Delete iface address", "iface delete addr", (void *)1},
+ {NULL, "delete!", IfaceDeleteCommand, LOCAL_AUTH,
+ "Delete iface address", "iface delete addr", (void *)1},
+ {"name", NULL, IfaceNameCommand, LOCAL_AUTH,
+ "Set iface name", "iface name name", NULL},
+ {"description", NULL, iface_Descr, LOCAL_AUTH,
+ "Set iface description", "iface description text", NULL},
+ {"show", NULL, iface_Show, LOCAL_AUTH,
+ "Show iface address(es)", "iface show", NULL},
+ {"help", "?", HelpCommand, LOCAL_AUTH | LOCAL_NO_AUTH,
+ "Display this message", "nat help|? [command]", IfaceCommands},
+ {NULL, NULL, NULL, 0, NULL, NULL, NULL},
+};
+
+static struct cmdtab const Commands[] = {
+ {"accept", NULL, NegotiateCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+ "accept option request", "accept option ..", NULL},
+ {"add", NULL, AddCommand, LOCAL_AUTH,
+ "add route", "add dest mask gateway", NULL},
+ {NULL, "add!", AddCommand, LOCAL_AUTH,
+ "add or change route", "add! dest mask gateway", (void *)1},
+ {"allow", "auth", RunListCommand, LOCAL_AUTH,
+ "Allow ppp access", "allow users|modes ....", AllowCommands},
+ {"bg", "!bg", BgShellCommand, LOCAL_AUTH,
+ "Run a background command", "[!]bg command", NULL},
+ {"clear", NULL, ClearCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+ "Clear throughput statistics",
+ "clear ipcp|ipv6cp|physical [current|overall|peak]...", NULL},
+ {"clone", NULL, CloneCommand, LOCAL_AUTH | LOCAL_CX,
+ "Clone a link", "clone newname...", NULL},
+ {"close", NULL, CloseCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+ "Close an FSM", "close [lcp|ccp]", NULL},
+ {"delete", NULL, DeleteCommand, LOCAL_AUTH,
+ "delete route", "delete dest", NULL},
+ {NULL, "delete!", DeleteCommand, LOCAL_AUTH,
+ "delete a route if it exists", "delete! dest", (void *)1},
+ {"deny", NULL, NegotiateCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+ "Deny option request", "deny option ..", NULL},
+ {"dial", "call", DialCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+ "Dial and login", "dial|call [system ...]", NULL},
+ {"disable", NULL, NegotiateCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+ "Disable option", "disable option ..", NULL},
+ {"down", NULL, DownCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+ "Generate a down event", "down [ccp|lcp]", NULL},
+ {"enable", NULL, NegotiateCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+ "Enable option", "enable option ..", NULL},
+ {"ident", NULL, IdentCommand, LOCAL_AUTH | LOCAL_CX,
+ "Set the link identity", "ident text...", NULL},
+ {"iface", "interface", RunListCommand, LOCAL_AUTH,
+ "interface control", "iface option ...", IfaceCommands},
+ {"link", "datalink", LinkCommand, LOCAL_AUTH,
+ "Link specific commands", "link name command ...", NULL},
+ {"load", NULL, LoadCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+ "Load settings", "load [system ...]", NULL},
+ {"log", NULL, LogCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+ "log information", "log word ...", NULL},
+#ifndef NONAT
+ {"nat", "alias", RunListCommand, LOCAL_AUTH,
+ "NAT control", "nat option yes|no", NatCommands},
+#endif
+ {"open", NULL, OpenCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+ "Open an FSM", "open! [lcp|ccp|ipcp]", (void *)1},
+ {"passwd", NULL, PasswdCommand, LOCAL_NO_AUTH,
+ "Password for manipulation", "passwd LocalPassword", NULL},
+ {"quit", "bye", QuitCommand, LOCAL_AUTH | LOCAL_NO_AUTH,
+ "Quit PPP program", "quit|bye [all]", NULL},
+ {"remove", "rm", RemoveCommand, LOCAL_AUTH | LOCAL_CX,
+ "Remove a link", "remove", NULL},
+ {"rename", "mv", RenameCommand, LOCAL_AUTH | LOCAL_CX,
+ "Rename a link", "rename name", NULL},
+ {"resolv", NULL, ResolvCommand, LOCAL_AUTH,
+ "Manipulate resolv.conf", "resolv readonly|reload|restore|rewrite|writable",
+ NULL},
+ {"save", NULL, SaveCommand, LOCAL_AUTH,
+ "Save settings", "save", NULL},
+ {"sendident", NULL, SendIdentification, LOCAL_AUTH | LOCAL_CX,
+ "Transmit the link identity", "sendident", NULL},
+ {"set", "setup", SetCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+ "Set parameters", "set[up] var value", NULL},
+ {"shell", "!", FgShellCommand, LOCAL_AUTH,
+ "Run a subshell", "shell|! [sh command]", NULL},
+ {"show", NULL, ShowCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+ "Show status and stats", "show var", NULL},
+ {"term", NULL, TerminalCommand, LOCAL_AUTH | LOCAL_CX,
+ "Enter terminal mode", "term", NULL},
+ {"help", "?", HelpCommand, LOCAL_AUTH | LOCAL_NO_AUTH,
+ "Display this message", "help|? [command]", Commands},
+ {NULL, NULL, NULL, 0, NULL, NULL, NULL},
+};
+
+static int
+ShowEscape(struct cmdargs const *arg)
+{
+ if (arg->cx->physical->async.cfg.EscMap[32]) {
+ int code, bit;
+ const char *sep = "";
+
+ for (code = 0; code < 32; code++)
+ if (arg->cx->physical->async.cfg.EscMap[code])
+ for (bit = 0; bit < 8; bit++)
+ if (arg->cx->physical->async.cfg.EscMap[code] & (1 << bit)) {
+ prompt_Printf(arg->prompt, "%s0x%02x", sep, (code << 3) + bit);
+ sep = ", ";
+ }
+ prompt_Printf(arg->prompt, "\n");
+ }
+ return 0;
+}
+
+static int
+ShowTimerList(struct cmdargs const *arg)
+{
+ timer_Show(0, arg->prompt);
+ return 0;
+}
+
+static int
+ShowStopped(struct cmdargs const *arg)
+{
+ prompt_Printf(arg->prompt, " Stopped Timer: LCP: ");
+ if (!arg->cx->physical->link.lcp.fsm.StoppedTimer.load)
+ prompt_Printf(arg->prompt, "Disabled");
+ else
+ prompt_Printf(arg->prompt, "%ld secs",
+ arg->cx->physical->link.lcp.fsm.StoppedTimer.load / SECTICKS);
+
+ prompt_Printf(arg->prompt, ", CCP: ");
+ if (!arg->cx->physical->link.ccp.fsm.StoppedTimer.load)
+ prompt_Printf(arg->prompt, "Disabled");
+ else
+ prompt_Printf(arg->prompt, "%ld secs",
+ arg->cx->physical->link.ccp.fsm.StoppedTimer.load / SECTICKS);
+
+ prompt_Printf(arg->prompt, "\n");
+
+ return 0;
+}
+
+static int
+ShowVersion(struct cmdargs const *arg)
+{
+ prompt_Printf(arg->prompt, "PPP Version %s\n", Version);
+ return 0;
+}
+
+static int
+ShowProtocolStats(struct cmdargs const *arg)
+{
+ struct link *l = command_ChooseLink(arg);
+
+ prompt_Printf(arg->prompt, "%s:\n", l->name);
+ link_ReportProtocolStatus(l, arg->prompt);
+ return 0;
+}
+
+static struct cmdtab const ShowCommands[] = {
+ {"bundle", NULL, bundle_ShowStatus, LOCAL_AUTH,
+ "bundle details", "show bundle", NULL},
+ {"ccp", NULL, ccp_ReportStatus, LOCAL_AUTH | LOCAL_CX_OPT,
+ "CCP status", "show cpp", NULL},
+ {"compress", NULL, sl_Show, LOCAL_AUTH,
+ "VJ compression stats", "show compress", NULL},
+ {"escape", NULL, ShowEscape, LOCAL_AUTH | LOCAL_CX,
+ "escape characters", "show escape", NULL},
+ {"filter", NULL, filter_Show, LOCAL_AUTH,
+ "packet filters", "show filter [in|out|dial|alive]", NULL},
+ {"hdlc", NULL, hdlc_ReportStatus, LOCAL_AUTH | LOCAL_CX,
+ "HDLC errors", "show hdlc", NULL},
+ {"iface", "interface", iface_Show, LOCAL_AUTH,
+ "Interface status", "show iface", NULL},
+ {"ipcp", NULL, ipcp_Show, LOCAL_AUTH,
+ "IPCP status", "show ipcp", NULL},
+#ifndef NOINET6
+ {"ipv6cp", NULL, ipv6cp_Show, LOCAL_AUTH,
+ "IPV6CP status", "show ipv6cp", NULL},
+#endif
+ {"layers", NULL, link_ShowLayers, LOCAL_AUTH | LOCAL_CX_OPT,
+ "Protocol layers", "show layers", NULL},
+ {"lcp", NULL, lcp_ReportStatus, LOCAL_AUTH | LOCAL_CX,
+ "LCP status", "show lcp", NULL},
+ {"link", "datalink", datalink_Show, LOCAL_AUTH | LOCAL_CX,
+ "(high-level) link info", "show link", NULL},
+ {"links", NULL, bundle_ShowLinks, LOCAL_AUTH,
+ "available link names", "show links", NULL},
+ {"log", NULL, log_ShowLevel, LOCAL_AUTH,
+ "log levels", "show log", NULL},
+ {"mem", NULL, mbuf_Show, LOCAL_AUTH,
+ "mbuf allocations", "show mem", NULL},
+ {"ncp", NULL, ncp_Show, LOCAL_AUTH,
+ "NCP status", "show ncp", NULL},
+ {"physical", NULL, physical_ShowStatus, LOCAL_AUTH | LOCAL_CX,
+ "(low-level) link info", "show physical", NULL},
+ {"mp", "multilink", mp_ShowStatus, LOCAL_AUTH,
+ "multilink setup", "show mp", NULL},
+ {"proto", NULL, ShowProtocolStats, LOCAL_AUTH | LOCAL_CX_OPT,
+ "protocol summary", "show proto", NULL},
+ {"route", NULL, route_Show, LOCAL_AUTH,
+ "routing table", "show route", NULL},
+ {"stopped", NULL, ShowStopped, LOCAL_AUTH | LOCAL_CX,
+ "STOPPED timeout", "show stopped", NULL},
+ {"timers", NULL, ShowTimerList, LOCAL_AUTH,
+ "alarm timers", "show timers", NULL},
+ {"version", NULL, ShowVersion, LOCAL_NO_AUTH | LOCAL_AUTH,
+ "version string", "show version", NULL},
+ {"who", NULL, log_ShowWho, LOCAL_AUTH,
+ "client list", "show who", NULL},
+ {"help", "?", HelpCommand, LOCAL_NO_AUTH | LOCAL_AUTH,
+ "Display this message", "show help|? [command]", ShowCommands},
+ {NULL, NULL, NULL, 0, NULL, NULL, NULL},
+};
+
+static struct cmdtab const *
+FindCommand(struct cmdtab const *cmds, const char *str, int *pmatch)
+{
+ int nmatch;
+ int len;
+ struct cmdtab const *found;
+
+ found = NULL;
+ len = strlen(str);
+ nmatch = 0;
+ while (cmds->func) {
+ if (cmds->name && strncasecmp(str, cmds->name, len) == 0) {
+ if (cmds->name[len] == '\0') {
+ *pmatch = 1;
+ return cmds;
+ }
+ nmatch++;
+ found = cmds;
+ } else if (cmds->alias && strncasecmp(str, cmds->alias, len) == 0) {
+ if (cmds->alias[len] == '\0') {
+ *pmatch = 1;
+ return cmds;
+ }
+ nmatch++;
+ found = cmds;
+ }
+ cmds++;
+ }
+ *pmatch = nmatch;
+ return found;
+}
+
+static const char *
+mkPrefix(int argc, char const *const *argv, char *tgt, int sz)
+{
+ int f, tlen, len;
+
+ tlen = 0;
+ for (f = 0; f < argc && tlen < sz - 2; f++) {
+ if (f)
+ tgt[tlen++] = ' ';
+ len = strlen(argv[f]);
+ if (len > sz - tlen - 1)
+ len = sz - tlen - 1;
+ strncpy(tgt+tlen, argv[f], len);
+ tlen += len;
+ }
+ tgt[tlen] = '\0';
+ return tgt;
+}
+
+static int
+FindExec(struct bundle *bundle, struct cmdtab const *cmds, int argc, int argn,
+ char const *const *argv, struct prompt *prompt, struct datalink *cx)
+{
+ struct cmdtab const *cmd;
+ int val = 1;
+ int nmatch;
+ struct cmdargs arg;
+ char prefix[100];
+
+ cmd = FindCommand(cmds, argv[argn], &nmatch);
+ if (nmatch > 1)
+ log_Printf(LogWARN, "%s: Ambiguous command\n",
+ mkPrefix(argn+1, argv, prefix, sizeof prefix));
+ else if (cmd && (!prompt || (cmd->lauth & prompt->auth))) {
+ if ((cmd->lauth & LOCAL_CX) && !cx)
+ /* We've got no context, but we require it */
+ cx = bundle2datalink(bundle, NULL);
+
+ if ((cmd->lauth & LOCAL_CX) && !cx)
+ log_Printf(LogWARN, "%s: No context (use the `link' command)\n",
+ mkPrefix(argn+1, argv, prefix, sizeof prefix));
+ else {
+ if (cx && !(cmd->lauth & (LOCAL_CX|LOCAL_CX_OPT))) {
+ log_Printf(LogWARN, "%s: Redundant context (%s) ignored\n",
+ mkPrefix(argn+1, argv, prefix, sizeof prefix), cx->name);
+ cx = NULL;
+ }
+ arg.cmdtab = cmds;
+ arg.cmd = cmd;
+ arg.argc = argc;
+ arg.argn = argn+1;
+ arg.argv = argv;
+ arg.bundle = bundle;
+ arg.cx = cx;
+ arg.prompt = prompt;
+ val = (*cmd->func) (&arg);
+ }
+ } else
+ log_Printf(LogWARN, "%s: Invalid command\n",
+ mkPrefix(argn+1, argv, prefix, sizeof prefix));
+
+ if (val == -1)
+ log_Printf(LogWARN, "usage: %s\n", cmd->syntax);
+ else if (val)
+ log_Printf(LogWARN, "%s: Failed %d\n",
+ mkPrefix(argn+1, argv, prefix, sizeof prefix), val);
+
+ return val;
+}
+
+int
+command_Expand_Interpret(char *buff, int nb, char *argv[MAXARGS], int offset)
+{
+ char buff2[LINE_LEN-offset];
+
+ InterpretArg(buff, buff2);
+ strncpy(buff, buff2, LINE_LEN - offset - 1);
+ buff[LINE_LEN - offset - 1] = '\0';
+
+ return command_Interpret(buff, nb, argv);
+}
+
+int
+command_Interpret(char *buff, int nb, char *argv[MAXARGS])
+{
+ char *cp;
+
+ if (nb > 0) {
+ cp = buff + strcspn(buff, "\r\n");
+ if (cp)
+ *cp = '\0';
+ return MakeArgs(buff, argv, MAXARGS, PARSE_REDUCE);
+ }
+ return 0;
+}
+
+static int
+arghidden(char const *const *argv, int n)
+{
+ /* Is arg n of the given command to be hidden from the log ? */
+
+ /* set authkey xxxxx */
+ /* set key xxxxx */
+ if (n == 2 && !strncasecmp(argv[0], "se", 2) &&
+ (!strncasecmp(argv[1], "authk", 5) || !strncasecmp(argv[1], "ke", 2)))
+ return 1;
+
+ /* passwd xxxxx */
+ if (n == 1 && !strncasecmp(argv[0], "p", 1))
+ return 1;
+
+ /* set server port xxxxx .... */
+ if (n == 3 && !strncasecmp(argv[0], "se", 2) &&
+ !strncasecmp(argv[1], "se", 2))
+ return 1;
+
+ return 0;
+}
+
+void
+command_Run(struct bundle *bundle, int argc, char const *const *argv,
+ struct prompt *prompt, const char *label, struct datalink *cx)
+{
+ if (argc > 0) {
+ if (log_IsKept(LogCOMMAND)) {
+ char buf[LINE_LEN];
+ int f;
+ size_t n;
+
+ if (label) {
+ strncpy(buf, label, sizeof buf - 3);
+ buf[sizeof buf - 3] = '\0';
+ strcat(buf, ": ");
+ n = strlen(buf);
+ } else {
+ *buf = '\0';
+ n = 0;
+ }
+ buf[sizeof buf - 1] = '\0'; /* In case we run out of room in buf */
+
+ for (f = 0; f < argc; f++) {
+ if (n < sizeof buf - 1 && f)
+ buf[n++] = ' ';
+ if (arghidden(argv, f))
+ strncpy(buf+n, "********", sizeof buf - n - 1);
+ else
+ strncpy(buf+n, argv[f], sizeof buf - n - 1);
+ n += strlen(buf+n);
+ }
+ log_Printf(LogCOMMAND, "%s\n", buf);
+ }
+ FindExec(bundle, Commands, argc, 0, argv, prompt, cx);
+ }
+}
+
+int
+command_Decode(struct bundle *bundle, char *buff, int nb, struct prompt *prompt,
+ const char *label)
+{
+ int argc;
+ char *argv[MAXARGS];
+
+ if ((argc = command_Expand_Interpret(buff, nb, argv, 0)) < 0)
+ return 0;
+
+ command_Run(bundle, argc, (char const *const *)argv, prompt, label, NULL);
+ return 1;
+}
+
+static int
+ShowCommand(struct cmdargs const *arg)
+{
+ if (!arg->prompt)
+ log_Printf(LogWARN, "show: Cannot show without a prompt\n");
+ else if (arg->argc > arg->argn)
+ FindExec(arg->bundle, ShowCommands, arg->argc, arg->argn, arg->argv,
+ arg->prompt, arg->cx);
+ else
+ prompt_Printf(arg->prompt, "Use ``show ?'' to get a list.\n");
+
+ return 0;
+}
+
+static int
+TerminalCommand(struct cmdargs const *arg)
+{
+ if (!arg->prompt) {
+ log_Printf(LogWARN, "term: Need a prompt\n");
+ return 1;
+ }
+
+ if (arg->cx->physical->link.lcp.fsm.state > ST_CLOSED) {
+ prompt_Printf(arg->prompt, "LCP state is [%s]\n",
+ State2Nam(arg->cx->physical->link.lcp.fsm.state));
+ return 1;
+ }
+
+ datalink_Up(arg->cx, 0, 0);
+ prompt_TtyTermMode(arg->prompt, arg->cx);
+ return 0;
+}
+
+static int
+QuitCommand(struct cmdargs const *arg)
+{
+ if (!arg->prompt || prompt_IsController(arg->prompt) ||
+ (arg->argc > arg->argn && !strcasecmp(arg->argv[arg->argn], "all") &&
+ (arg->prompt->auth & LOCAL_AUTH)))
+ Cleanup();
+ if (arg->prompt)
+ prompt_Destroy(arg->prompt, 1);
+
+ return 0;
+}
+
+static int
+OpenCommand(struct cmdargs const *arg)
+{
+ if (arg->argc == arg->argn)
+ bundle_Open(arg->bundle, arg->cx ? arg->cx->name : NULL, PHYS_ALL, 1);
+ else if (arg->argc == arg->argn + 1) {
+ if (!strcasecmp(arg->argv[arg->argn], "lcp")) {
+ struct datalink *cx = arg->cx ?
+ arg->cx : bundle2datalink(arg->bundle, NULL);
+ if (cx) {
+ if (cx->physical->link.lcp.fsm.state == ST_OPENED)
+ fsm_Reopen(&cx->physical->link.lcp.fsm);
+ else
+ bundle_Open(arg->bundle, cx->name, PHYS_ALL, 1);
+ } else
+ log_Printf(LogWARN, "open lcp: You must specify a link\n");
+ } else if (!strcasecmp(arg->argv[arg->argn], "ccp")) {
+ struct fsm *fp;
+
+ fp = &command_ChooseLink(arg)->ccp.fsm;
+ if (fp->link->lcp.fsm.state != ST_OPENED)
+ log_Printf(LogWARN, "open: LCP must be open before opening CCP\n");
+ else if (fp->state == ST_OPENED)
+ fsm_Reopen(fp);
+ else {
+ fp->open_mode = 0; /* Not passive any more */
+ if (fp->state == ST_STOPPED) {
+ fsm_Down(fp);
+ fsm_Up(fp);
+ } else {
+ fsm_Up(fp);
+ fsm_Open(fp);
+ }
+ }
+ } else if (!strcasecmp(arg->argv[arg->argn], "ipcp")) {
+ if (arg->cx)
+ log_Printf(LogWARN, "open ipcp: You need not specify a link\n");
+ if (arg->bundle->ncp.ipcp.fsm.state == ST_OPENED)
+ fsm_Reopen(&arg->bundle->ncp.ipcp.fsm);
+ else
+ bundle_Open(arg->bundle, NULL, PHYS_ALL, 1);
+ } else
+ return -1;
+ } else
+ return -1;
+
+ return 0;
+}
+
+static int
+CloseCommand(struct cmdargs const *arg)
+{
+ if (arg->argc == arg->argn)
+ bundle_Close(arg->bundle, arg->cx ? arg->cx->name : NULL, CLOSE_STAYDOWN);
+ else if (arg->argc == arg->argn + 1) {
+ if (!strcasecmp(arg->argv[arg->argn], "lcp"))
+ bundle_Close(arg->bundle, arg->cx ? arg->cx->name : NULL, CLOSE_LCP);
+ else if (!strcasecmp(arg->argv[arg->argn], "ccp") ||
+ !strcasecmp(arg->argv[arg->argn], "ccp!")) {
+ struct fsm *fp;
+
+ fp = &command_ChooseLink(arg)->ccp.fsm;
+ if (fp->state == ST_OPENED) {
+ fsm_Close(fp);
+ if (arg->argv[arg->argn][3] == '!')
+ fp->open_mode = 0; /* Stay ST_CLOSED */
+ else
+ fp->open_mode = OPEN_PASSIVE; /* Wait for the peer to start */
+ }
+ } else
+ return -1;
+ } else
+ return -1;
+
+ return 0;
+}
+
+static int
+DownCommand(struct cmdargs const *arg)
+{
+ if (arg->argc == arg->argn) {
+ if (arg->cx)
+ datalink_Down(arg->cx, CLOSE_STAYDOWN);
+ else
+ bundle_Down(arg->bundle, CLOSE_STAYDOWN);
+ } else if (arg->argc == arg->argn + 1) {
+ if (!strcasecmp(arg->argv[arg->argn], "lcp")) {
+ if (arg->cx)
+ datalink_Down(arg->cx, CLOSE_LCP);
+ else
+ bundle_Down(arg->bundle, CLOSE_LCP);
+ } else if (!strcasecmp(arg->argv[arg->argn], "ccp")) {
+ struct fsm *fp = arg->cx ? &arg->cx->physical->link.ccp.fsm :
+ &arg->bundle->ncp.mp.link.ccp.fsm;
+ fsm2initial(fp);
+ } else
+ return -1;
+ } else
+ return -1;
+
+ return 0;
+}
+
+static int
+SetModemSpeed(struct cmdargs const *arg)
+{
+ long speed;
+ char *end;
+
+ if (arg->argc > arg->argn && *arg->argv[arg->argn]) {
+ if (arg->argc > arg->argn+1) {
+ log_Printf(LogWARN, "SetModemSpeed: Too many arguments\n");
+ return -1;
+ }
+ if (strcasecmp(arg->argv[arg->argn], "sync") == 0) {
+ physical_SetSync(arg->cx->physical);
+ return 0;
+ }
+ end = NULL;
+ speed = strtol(arg->argv[arg->argn], &end, 10);
+ if (*end || speed < 0) {
+ log_Printf(LogWARN, "SetModemSpeed: Bad argument \"%s\"",
+ arg->argv[arg->argn]);
+ return -1;
+ }
+ if (physical_SetSpeed(arg->cx->physical, speed))
+ return 0;
+ log_Printf(LogWARN, "%s: Invalid speed\n", arg->argv[arg->argn]);
+ } else
+ log_Printf(LogWARN, "SetModemSpeed: No speed specified\n");
+
+ return -1;
+}
+
+static int
+SetStoppedTimeout(struct cmdargs const *arg)
+{
+ struct link *l = &arg->cx->physical->link;
+
+ l->lcp.fsm.StoppedTimer.load = 0;
+ l->ccp.fsm.StoppedTimer.load = 0;
+ if (arg->argc <= arg->argn+2) {
+ if (arg->argc > arg->argn) {
+ l->lcp.fsm.StoppedTimer.load = atoi(arg->argv[arg->argn]) * SECTICKS;
+ if (arg->argc > arg->argn+1)
+ l->ccp.fsm.StoppedTimer.load = atoi(arg->argv[arg->argn+1]) * SECTICKS;
+ }
+ return 0;
+ }
+ return -1;
+}
+
+static int
+SetServer(struct cmdargs const *arg)
+{
+ int res = -1;
+
+ if (arg->argc > arg->argn && arg->argc < arg->argn+4) {
+ const char *port, *passwd, *mask;
+ size_t mlen;
+
+ /* What's what ? */
+ port = arg->argv[arg->argn];
+ if (arg->argc == arg->argn + 2) {
+ passwd = arg->argv[arg->argn+1];
+ mask = NULL;
+ } else if (arg->argc == arg->argn + 3) {
+ passwd = arg->argv[arg->argn+1];
+ mask = arg->argv[arg->argn+2];
+ mlen = strlen(mask);
+ if (mlen == 0 || mlen > 4 || strspn(mask, "01234567") != mlen ||
+ (mlen == 4 && *mask != '0')) {
+ log_Printf(LogWARN, "%s %s: %s: Invalid mask\n",
+ arg->argv[arg->argn - 2], arg->argv[arg->argn - 1], mask);
+ return -1;
+ }
+ } else if (arg->argc != arg->argn + 1)
+ return -1;
+ else if (strcasecmp(port, "none") == 0) {
+ if (server_Clear(arg->bundle))
+ log_Printf(LogPHASE, "Disabled server socket\n");
+ return 0;
+ } else if (strcasecmp(port, "open") == 0) {
+ switch (server_Reopen(arg->bundle)) {
+ case SERVER_OK:
+ return 0;
+ case SERVER_FAILED:
+ log_Printf(LogWARN, "Failed to reopen server port\n");
+ return 1;
+ case SERVER_UNSET:
+ log_Printf(LogWARN, "Cannot reopen unset server socket\n");
+ return 1;
+ default:
+ break;
+ }
+ return -1;
+ } else if (strcasecmp(port, "closed") == 0) {
+ if (server_Close(arg->bundle))
+ log_Printf(LogPHASE, "Closed server socket\n");
+ else
+ log_Printf(LogWARN, "Server socket not open\n");
+
+ return 0;
+ } else
+ return -1;
+
+ strncpy(server.cfg.passwd, passwd, sizeof server.cfg.passwd - 1);
+ server.cfg.passwd[sizeof server.cfg.passwd - 1] = '\0';
+
+ if (*port == '/') {
+ mode_t imask;
+ char *ptr, name[LINE_LEN + 12];
+
+ if (mask == NULL)
+ imask = (mode_t)-1;
+ else for (imask = mlen = 0; mask[mlen]; mlen++)
+ imask = (imask * 8) + mask[mlen] - '0';
+
+ ptr = strstr(port, "%d");
+ if (ptr) {
+ snprintf(name, sizeof name, "%.*s%d%s",
+ (int)(ptr - port), port, arg->bundle->unit, ptr + 2);
+ port = name;
+ }
+ res = server_LocalOpen(arg->bundle, port, imask);
+ } else {
+ int iport, add = 0;
+
+ if (mask != NULL)
+ return -1;
+
+ if (*port == '+') {
+ port++;
+ add = 1;
+ }
+ if (strspn(port, "0123456789") != strlen(port)) {
+ struct servent *s;
+
+ if ((s = getservbyname(port, "tcp")) == NULL) {
+ iport = 0;
+ log_Printf(LogWARN, "%s: Invalid port or service\n", port);
+ } else
+ iport = ntohs(s->s_port);
+ } else
+ iport = atoi(port);
+
+ if (iport) {
+ if (add)
+ iport += arg->bundle->unit;
+ res = server_TcpOpen(arg->bundle, iport);
+ } else
+ res = -1;
+ }
+ }
+
+ return res;
+}
+
+static int
+SetEscape(struct cmdargs const *arg)
+{
+ int code;
+ int argc = arg->argc - arg->argn;
+ char const *const *argv = arg->argv + arg->argn;
+
+ for (code = 0; code < 33; code++)
+ arg->cx->physical->async.cfg.EscMap[code] = 0;
+
+ while (argc-- > 0) {
+ sscanf(*argv++, "%x", &code);
+ code &= 0xff;
+ arg->cx->physical->async.cfg.EscMap[code >> 3] |= (1 << (code & 7));
+ arg->cx->physical->async.cfg.EscMap[32] = 1;
+ }
+ return 0;
+}
+
+static int
+SetInterfaceAddr(struct cmdargs const *arg)
+{
+ struct ncp *ncp = &arg->bundle->ncp;
+ struct ncpaddr ncpaddr;
+ const char *hisaddr;
+
+ if (arg->argc > arg->argn + 4)
+ return -1;
+
+ hisaddr = NULL;
+ memset(&ncp->ipcp.cfg.my_range, '\0', sizeof ncp->ipcp.cfg.my_range);
+ memset(&ncp->ipcp.cfg.peer_range, '\0', sizeof ncp->ipcp.cfg.peer_range);
+ ncp->ipcp.cfg.HaveTriggerAddress = 0;
+ ncp->ipcp.cfg.netmask.s_addr = INADDR_ANY;
+ iplist_reset(&ncp->ipcp.cfg.peer_list);
+
+ if (arg->argc > arg->argn) {
+ if (!ncprange_aton(&ncp->ipcp.cfg.my_range, ncp, arg->argv[arg->argn]))
+ return 1;
+ if (arg->argc > arg->argn+1) {
+ hisaddr = arg->argv[arg->argn+1];
+ if (arg->argc > arg->argn+2) {
+ ncp->ipcp.ifmask = ncp->ipcp.cfg.netmask =
+ GetIpAddr(arg->argv[arg->argn+2]);
+ if (arg->argc > arg->argn+3) {
+ ncp->ipcp.cfg.TriggerAddress = GetIpAddr(arg->argv[arg->argn+3]);
+ ncp->ipcp.cfg.HaveTriggerAddress = 1;
+ }
+ }
+ }
+ }
+
+ /* 0.0.0.0 means any address (0 bits) */
+ ncprange_getaddr(&ncp->ipcp.cfg.my_range, &ncpaddr);
+ ncpaddr_getip4(&ncpaddr, &ncp->ipcp.my_ip);
+ if (ncp->ipcp.my_ip.s_addr == INADDR_ANY)
+ ncprange_setwidth(&ncp->ipcp.cfg.my_range, 0);
+ bundle_AdjustFilters(arg->bundle, &ncpaddr, NULL);
+
+ if (hisaddr && !ipcp_UseHisaddr(arg->bundle, hisaddr,
+ arg->bundle->phys_type.all & PHYS_AUTO))
+ return 4;
+
+ return 0;
+}
+
+static int
+SetRetry(int argc, char const *const *argv, u_int *timeout, u_int *maxreq,
+ u_int *maxtrm, int def)
+{
+ if (argc == 0) {
+ *timeout = DEF_FSMRETRY;
+ *maxreq = def;
+ if (maxtrm != NULL)
+ *maxtrm = def;
+ } else {
+ long l = atol(argv[0]);
+
+ if (l < MIN_FSMRETRY) {
+ log_Printf(LogWARN, "%ld: Invalid FSM retry period - min %d\n",
+ l, MIN_FSMRETRY);
+ return 1;
+ } else
+ *timeout = l;
+
+ if (argc > 1) {
+ l = atol(argv[1]);
+ if (l < 1) {
+ log_Printf(LogWARN, "%ld: Invalid FSM REQ tries - changed to 1\n", l);
+ l = 1;
+ }
+ *maxreq = l;
+
+ if (argc > 2 && maxtrm != NULL) {
+ l = atol(argv[2]);
+ if (l < 1) {
+ log_Printf(LogWARN, "%ld: Invalid FSM TRM tries - changed to 1\n", l);
+ l = 1;
+ }
+ *maxtrm = l;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int
+SetVariable(struct cmdargs const *arg)
+{
+ long long_val, param = (long)arg->cmd->args;
+ int mode, dummyint, f, first, res;
+ u_short *change;
+ const char *argp;
+ struct datalink *cx = arg->cx; /* LOCAL_CX uses this */
+ struct link *l = command_ChooseLink(arg); /* LOCAL_CX_OPT uses this */
+ struct in_addr *ipaddr;
+ struct ncpaddr ncpaddr[2];
+
+ if (arg->argc > arg->argn)
+ argp = arg->argv[arg->argn];
+ else
+ argp = "";
+
+ res = 0;
+
+ if ((arg->cmd->lauth & LOCAL_CX) && !cx) {
+ log_Printf(LogWARN, "set %s: No context (use the `link' command)\n",
+ arg->cmd->name);
+ return 1;
+ } else if (cx && !(arg->cmd->lauth & (LOCAL_CX|LOCAL_CX_OPT))) {
+ log_Printf(LogWARN, "set %s: Redundant context (%s) ignored\n",
+ arg->cmd->name, cx->name);
+ cx = NULL;
+ }
+
+ switch (param) {
+ case VAR_AUTHKEY:
+ strncpy(arg->bundle->cfg.auth.key, argp,
+ sizeof arg->bundle->cfg.auth.key - 1);
+ arg->bundle->cfg.auth.key[sizeof arg->bundle->cfg.auth.key - 1] = '\0';
+ break;
+
+ case VAR_AUTHNAME:
+ switch (bundle_Phase(arg->bundle)) {
+ default:
+ log_Printf(LogWARN, "Altering authname while at phase %s\n",
+ bundle_PhaseName(arg->bundle));
+ /* drop through */
+ case PHASE_DEAD:
+ case PHASE_ESTABLISH:
+ strncpy(arg->bundle->cfg.auth.name, argp,
+ sizeof arg->bundle->cfg.auth.name - 1);
+ arg->bundle->cfg.auth.name[sizeof arg->bundle->cfg.auth.name-1] = '\0';
+ break;
+ }
+ break;
+
+ case VAR_AUTOLOAD:
+ if (arg->argc == arg->argn + 3) {
+ int v1, v2, v3;
+ char *end;
+
+ v1 = strtol(arg->argv[arg->argn], &end, 0);
+ if (v1 < 0 || *end) {
+ log_Printf(LogWARN, "autoload: %s: Invalid min percentage\n",
+ arg->argv[arg->argn]);
+ res = 1;
+ break;
+ }
+
+ v2 = strtol(arg->argv[arg->argn + 1], &end, 0);
+ if (v2 < 0 || *end) {
+ log_Printf(LogWARN, "autoload: %s: Invalid max percentage\n",
+ arg->argv[arg->argn + 1]);
+ res = 1;
+ break;
+ }
+ if (v2 < v1) {
+ v3 = v1;
+ v1 = v2;
+ v2 = v3;
+ }
+
+ v3 = strtol(arg->argv[arg->argn + 2], &end, 0);
+ if (v3 <= 0 || *end) {
+ log_Printf(LogWARN, "autoload: %s: Invalid throughput period\n",
+ arg->argv[arg->argn + 2]);
+ res = 1;
+ break;
+ }
+
+ arg->bundle->ncp.mp.cfg.autoload.min = v1;
+ arg->bundle->ncp.mp.cfg.autoload.max = v2;
+ arg->bundle->ncp.mp.cfg.autoload.period = v3;
+ mp_RestartAutoloadTimer(&arg->bundle->ncp.mp);
+ } else {
+ log_Printf(LogWARN, "Set autoload requires three arguments\n");
+ res = 1;
+ }
+ break;
+
+ case VAR_DIAL:
+ strncpy(cx->cfg.script.dial, argp, sizeof cx->cfg.script.dial - 1);
+ cx->cfg.script.dial[sizeof cx->cfg.script.dial - 1] = '\0';
+ break;
+
+ case VAR_LOGIN:
+ strncpy(cx->cfg.script.login, argp, sizeof cx->cfg.script.login - 1);
+ cx->cfg.script.login[sizeof cx->cfg.script.login - 1] = '\0';
+ break;
+
+ case VAR_WINSIZE:
+ if (arg->argc > arg->argn) {
+ l->ccp.cfg.deflate.out.winsize = atoi(arg->argv[arg->argn]);
+ if (l->ccp.cfg.deflate.out.winsize < 8 ||
+ l->ccp.cfg.deflate.out.winsize > 15) {
+ log_Printf(LogWARN, "%d: Invalid outgoing window size\n",
+ l->ccp.cfg.deflate.out.winsize);
+ l->ccp.cfg.deflate.out.winsize = 15;
+ }
+ if (arg->argc > arg->argn+1) {
+ l->ccp.cfg.deflate.in.winsize = atoi(arg->argv[arg->argn+1]);
+ if (l->ccp.cfg.deflate.in.winsize < 8 ||
+ l->ccp.cfg.deflate.in.winsize > 15) {
+ log_Printf(LogWARN, "%d: Invalid incoming window size\n",
+ l->ccp.cfg.deflate.in.winsize);
+ l->ccp.cfg.deflate.in.winsize = 15;
+ }
+ } else
+ l->ccp.cfg.deflate.in.winsize = 0;
+ } else {
+ log_Printf(LogWARN, "No window size specified\n");
+ res = 1;
+ }
+ break;
+
+#ifndef NODES
+ case VAR_MPPE:
+ if (arg->argc > arg->argn + 2) {
+ res = -1;
+ break;
+ }
+
+ if (arg->argc == arg->argn) {
+ l->ccp.cfg.mppe.keybits = 0;
+ l->ccp.cfg.mppe.state = MPPE_ANYSTATE;
+ l->ccp.cfg.mppe.required = 0;
+ break;
+ }
+
+ if (!strcmp(argp, "*"))
+ long_val = 0;
+ else {
+ long_val = atol(argp);
+ if (long_val != 40 && long_val != 56 && long_val != 128) {
+ log_Printf(LogWARN, "%s: Invalid bits value\n", argp);
+ res = -1;
+ break;
+ }
+ }
+
+ if (arg->argc == arg->argn + 2) {
+ if (!strcmp(arg->argv[arg->argn + 1], "*"))
+ l->ccp.cfg.mppe.state = MPPE_ANYSTATE;
+ else if (!strcasecmp(arg->argv[arg->argn + 1], "stateless"))
+ l->ccp.cfg.mppe.state = MPPE_STATELESS;
+ else if (!strcasecmp(arg->argv[arg->argn + 1], "stateful"))
+ l->ccp.cfg.mppe.state = MPPE_STATEFUL;
+ else {
+ log_Printf(LogWARN, "%s: Invalid state value\n",
+ arg->argv[arg->argn + 1]);
+ res = -1;
+ break;
+ }
+ } else
+ l->ccp.cfg.mppe.state = MPPE_ANYSTATE;
+ l->ccp.cfg.mppe.keybits = long_val;
+ l->ccp.cfg.mppe.required = 1;
+ break;
+#endif
+
+ case VAR_DEVICE:
+ physical_SetDeviceList(cx->physical, arg->argc - arg->argn,
+ arg->argv + arg->argn);
+ break;
+
+ case VAR_ACCMAP:
+ if (arg->argc > arg->argn) {
+ u_long ulong_val;
+ sscanf(argp, "%lx", &ulong_val);
+ cx->physical->link.lcp.cfg.accmap = (u_int32_t)ulong_val;
+ } else {
+ log_Printf(LogWARN, "No accmap specified\n");
+ res = 1;
+ }
+ break;
+
+ case VAR_MODE:
+ mode = Nam2mode(argp);
+ if (mode == PHYS_NONE || mode == PHYS_ALL) {
+ log_Printf(LogWARN, "%s: Invalid mode\n", argp);
+ res = -1;
+ break;
+ }
+ bundle_SetMode(arg->bundle, cx, mode);
+ break;
+
+ case VAR_MRRU:
+ switch (bundle_Phase(arg->bundle)) {
+ case PHASE_DEAD:
+ break;
+ case PHASE_ESTABLISH:
+ /* Make sure none of our links are DATALINK_LCP or greater */
+ if (bundle_HighestState(arg->bundle) >= DATALINK_LCP) {
+ log_Printf(LogWARN, "mrru: Only changeable before LCP negotiations\n");
+ res = 1;
+ break;
+ }
+ break;
+ default:
+ log_Printf(LogWARN, "mrru: Only changeable at phase DEAD/ESTABLISH\n");
+ res = 1;
+ break;
+ }
+ if (res != 0)
+ break;
+ long_val = atol(argp);
+ if (long_val && long_val < MIN_MRU) {
+ log_Printf(LogWARN, "MRRU %ld: too small - min %d\n", long_val, MIN_MRU);
+ res = 1;
+ break;
+ } else if (long_val > MAX_MRU) {
+ log_Printf(LogWARN, "MRRU %ld: too big - max %d\n", long_val, MAX_MRU);
+ res = 1;
+ break;
+ } else
+ arg->bundle->ncp.mp.cfg.mrru = long_val;
+ break;
+
+ case VAR_MRU:
+ long_val = 0; /* silence gcc */
+ change = NULL; /* silence gcc */
+ switch(arg->argc - arg->argn) {
+ case 1:
+ if (argp[strspn(argp, "0123456789")] != '\0') {
+ res = -1;
+ break;
+ }
+ /*FALLTHRU*/
+ case 0:
+ long_val = atol(argp);
+ change = &l->lcp.cfg.mru;
+ if (long_val > l->lcp.cfg.max_mru) {
+ log_Printf(LogWARN, "MRU %ld: too large - max set to %d\n", long_val,
+ l->lcp.cfg.max_mru);
+ res = 1;
+ break;
+ }
+ break;
+ case 2:
+ if (strcasecmp(argp, "max") && strcasecmp(argp, "maximum")) {
+ res = -1;
+ break;
+ }
+ long_val = atol(arg->argv[arg->argn + 1]);
+ change = &l->lcp.cfg.max_mru;
+ if (long_val > MAX_MRU) {
+ log_Printf(LogWARN, "MRU %ld: too large - maximum is %d\n", long_val,
+ MAX_MRU);
+ res = 1;
+ break;
+ }
+ break;
+ default:
+ res = -1;
+ break;
+ }
+ if (res != 0)
+ break;
+
+ if (long_val == 0)
+ *change = 0;
+ else if (long_val < MIN_MRU) {
+ log_Printf(LogWARN, "MRU %ld: too small - min %d\n", long_val, MIN_MRU);
+ res = 1;
+ break;
+ } else if (long_val > MAX_MRU) {
+ log_Printf(LogWARN, "MRU %ld: too big - max %d\n", long_val, MAX_MRU);
+ res = 1;
+ break;
+ } else
+ *change = long_val;
+ if (l->lcp.cfg.mru > *change)
+ l->lcp.cfg.mru = *change;
+ break;
+
+ case VAR_MTU:
+ long_val = 0; /* silence gcc */
+ change = NULL; /* silence gcc */
+ switch(arg->argc - arg->argn) {
+ case 1:
+ if (argp[strspn(argp, "0123456789")] != '\0') {
+ res = -1;
+ break;
+ }
+ /*FALLTHRU*/
+ case 0:
+ long_val = atol(argp);
+ change = &l->lcp.cfg.mtu;
+ if (long_val > l->lcp.cfg.max_mtu) {
+ log_Printf(LogWARN, "MTU %ld: too large - max set to %d\n", long_val,
+ l->lcp.cfg.max_mtu);
+ res = 1;
+ break;
+ }
+ break;
+ case 2:
+ if (strcasecmp(argp, "max") && strcasecmp(argp, "maximum")) {
+ res = -1;
+ break;
+ }
+ long_val = atol(arg->argv[arg->argn + 1]);
+ change = &l->lcp.cfg.max_mtu;
+ if (long_val > MAX_MTU) {
+ log_Printf(LogWARN, "MTU %ld: too large - maximum is %d\n", long_val,
+ MAX_MTU);
+ res = 1;
+ break;
+ }
+ break;
+ default:
+ res = -1;
+ break;
+ }
+
+ if (res != 0)
+ break;
+
+ if (long_val && long_val < MIN_MTU) {
+ log_Printf(LogWARN, "MTU %ld: too small - min %d\n", long_val, MIN_MTU);
+ res = 1;
+ break;
+ } else if (long_val > MAX_MTU) {
+ log_Printf(LogWARN, "MTU %ld: too big - max %d\n", long_val, MAX_MTU);
+ res = 1;
+ break;
+ } else
+ *change = long_val;
+ if (l->lcp.cfg.mtu > *change)
+ l->lcp.cfg.mtu = *change;
+ break;
+
+ case VAR_OPENMODE:
+ if (strcasecmp(argp, "active") == 0)
+ cx->physical->link.lcp.cfg.openmode = arg->argc > arg->argn+1 ?
+ atoi(arg->argv[arg->argn+1]) : 1;
+ else if (strcasecmp(argp, "passive") == 0)
+ cx->physical->link.lcp.cfg.openmode = OPEN_PASSIVE;
+ else {
+ log_Printf(LogWARN, "%s: Invalid openmode\n", argp);
+ res = 1;
+ }
+ break;
+
+ case VAR_PHONE:
+ strncpy(cx->cfg.phone.list, argp, sizeof cx->cfg.phone.list - 1);
+ cx->cfg.phone.list[sizeof cx->cfg.phone.list - 1] = '\0';
+ cx->phone.alt = cx->phone.next = NULL;
+ break;
+
+ case VAR_HANGUP:
+ strncpy(cx->cfg.script.hangup, argp, sizeof cx->cfg.script.hangup - 1);
+ cx->cfg.script.hangup[sizeof cx->cfg.script.hangup - 1] = '\0';
+ break;
+
+ case VAR_IFQUEUE:
+ long_val = atol(argp);
+ arg->bundle->cfg.ifqueue = long_val < 0 ? 0 : long_val;
+ break;
+
+ case VAR_LOGOUT:
+ strncpy(cx->cfg.script.logout, argp, sizeof cx->cfg.script.logout - 1);
+ cx->cfg.script.logout[sizeof cx->cfg.script.logout - 1] = '\0';
+ break;
+
+ case VAR_IDLETIMEOUT:
+ if (arg->argc > arg->argn+2) {
+ log_Printf(LogWARN, "Too many idle timeout values\n");
+ res = 1;
+ } else if (arg->argc == arg->argn) {
+ log_Printf(LogWARN, "Too few idle timeout values\n");
+ res = 1;
+ } else {
+ unsigned long timeout, min;
+
+ timeout = strtoul(argp, NULL, 10);
+ min = arg->bundle->cfg.idle.min_timeout;
+ if (arg->argc == arg->argn + 2)
+ min = strtoul(arg->argv[arg->argn + 1], NULL, 10);
+ bundle_SetIdleTimer(arg->bundle, timeout, min);
+ }
+ break;
+
+#ifndef NORADIUS
+ case VAR_RAD_ALIVE:
+ if (arg->argc > arg->argn + 2) {
+ log_Printf(LogWARN, "Too many RADIUS alive interval values\n");
+ res = 1;
+ } else if (arg->argc == arg->argn) {
+ log_Printf(LogWARN, "Too few RADIUS alive interval values\n");
+ res = 1;
+ } else {
+ arg->bundle->radius.alive.interval = atoi(argp);
+ if (arg->bundle->radius.alive.interval && !*arg->bundle->radius.cfg.file) {
+ log_Printf(LogWARN, "rad_alive requires radius to be configured\n");
+ res = 1;
+ } else if (arg->bundle->ncp.ipcp.fsm.state == ST_OPENED) {
+ if (arg->bundle->radius.alive.interval)
+ radius_StartTimer(arg->bundle);
+ else
+ radius_StopTimer(&arg->bundle->radius);
+ }
+ }
+ break;
+#endif
+
+ case VAR_LQRPERIOD:
+ long_val = atol(argp);
+ if (long_val < MIN_LQRPERIOD) {
+ log_Printf(LogWARN, "%ld: Invalid lqr period - min %d\n",
+ long_val, MIN_LQRPERIOD);
+ res = 1;
+ } else
+ l->lcp.cfg.lqrperiod = long_val;
+ break;
+
+ case VAR_LCPRETRY:
+ res = SetRetry(arg->argc - arg->argn, arg->argv + arg->argn,
+ &cx->physical->link.lcp.cfg.fsm.timeout,
+ &cx->physical->link.lcp.cfg.fsm.maxreq,
+ &cx->physical->link.lcp.cfg.fsm.maxtrm, DEF_FSMTRIES);
+ break;
+
+ case VAR_CHAPRETRY:
+ res = SetRetry(arg->argc - arg->argn, arg->argv + arg->argn,
+ &cx->chap.auth.cfg.fsm.timeout,
+ &cx->chap.auth.cfg.fsm.maxreq, NULL, DEF_FSMAUTHTRIES);
+ break;
+
+ case VAR_PAPRETRY:
+ res = SetRetry(arg->argc - arg->argn, arg->argv + arg->argn,
+ &cx->pap.cfg.fsm.timeout, &cx->pap.cfg.fsm.maxreq,
+ NULL, DEF_FSMAUTHTRIES);
+ break;
+
+ case VAR_CCPRETRY:
+ res = SetRetry(arg->argc - arg->argn, arg->argv + arg->argn,
+ &l->ccp.cfg.fsm.timeout, &l->ccp.cfg.fsm.maxreq,
+ &l->ccp.cfg.fsm.maxtrm, DEF_FSMTRIES);
+ break;
+
+ case VAR_IPCPRETRY:
+ res = SetRetry(arg->argc - arg->argn, arg->argv + arg->argn,
+ &arg->bundle->ncp.ipcp.cfg.fsm.timeout,
+ &arg->bundle->ncp.ipcp.cfg.fsm.maxreq,
+ &arg->bundle->ncp.ipcp.cfg.fsm.maxtrm, DEF_FSMTRIES);
+ break;
+
+#ifndef NOINET6
+ case VAR_IPV6CPRETRY:
+ res = SetRetry(arg->argc - arg->argn, arg->argv + arg->argn,
+ &arg->bundle->ncp.ipv6cp.cfg.fsm.timeout,
+ &arg->bundle->ncp.ipv6cp.cfg.fsm.maxreq,
+ &arg->bundle->ncp.ipv6cp.cfg.fsm.maxtrm, DEF_FSMTRIES);
+ break;
+#endif
+
+ case VAR_NBNS:
+ case VAR_DNS:
+ if (param == VAR_DNS) {
+ ipaddr = arg->bundle->ncp.ipcp.cfg.ns.dns;
+ ipaddr[0].s_addr = ipaddr[1].s_addr = INADDR_NONE;
+ } else {
+ ipaddr = arg->bundle->ncp.ipcp.cfg.ns.nbns;
+ ipaddr[0].s_addr = ipaddr[1].s_addr = INADDR_ANY;
+ }
+
+ if (arg->argc > arg->argn) {
+ ncpaddr_aton(ncpaddr, &arg->bundle->ncp, arg->argv[arg->argn]);
+ if (!ncpaddr_getip4(ncpaddr, ipaddr))
+ return -1;
+ if (arg->argc > arg->argn+1) {
+ ncpaddr_aton(ncpaddr + 1, &arg->bundle->ncp, arg->argv[arg->argn + 1]);
+ if (!ncpaddr_getip4(ncpaddr + 1, ipaddr + 1))
+ return -1;
+ }
+
+ if (ipaddr[0].s_addr == INADDR_ANY) {
+ ipaddr[0] = ipaddr[1];
+ ipaddr[1].s_addr = INADDR_ANY;
+ }
+ if (ipaddr[0].s_addr == INADDR_NONE) {
+ ipaddr[0] = ipaddr[1];
+ ipaddr[1].s_addr = INADDR_NONE;
+ }
+ }
+ break;
+
+ case VAR_CALLBACK:
+ cx->cfg.callback.opmask = 0;
+ for (dummyint = arg->argn; dummyint < arg->argc; dummyint++) {
+ if (!strcasecmp(arg->argv[dummyint], "auth"))
+ cx->cfg.callback.opmask |= CALLBACK_BIT(CALLBACK_AUTH);
+ else if (!strcasecmp(arg->argv[dummyint], "cbcp"))
+ cx->cfg.callback.opmask |= CALLBACK_BIT(CALLBACK_CBCP);
+ else if (!strcasecmp(arg->argv[dummyint], "e.164")) {
+ if (dummyint == arg->argc - 1)
+ log_Printf(LogWARN, "No E.164 arg (E.164 ignored) !\n");
+ else {
+ cx->cfg.callback.opmask |= CALLBACK_BIT(CALLBACK_E164);
+ strncpy(cx->cfg.callback.msg, arg->argv[++dummyint],
+ sizeof cx->cfg.callback.msg - 1);
+ cx->cfg.callback.msg[sizeof cx->cfg.callback.msg - 1] = '\0';
+ }
+ } else if (!strcasecmp(arg->argv[dummyint], "none"))
+ cx->cfg.callback.opmask |= CALLBACK_BIT(CALLBACK_NONE);
+ else {
+ res = -1;
+ break;
+ }
+ }
+ if (cx->cfg.callback.opmask == CALLBACK_BIT(CALLBACK_NONE))
+ cx->cfg.callback.opmask = 0;
+ break;
+
+ case VAR_CBCP:
+ cx->cfg.cbcp.delay = 0;
+ *cx->cfg.cbcp.phone = '\0';
+ cx->cfg.cbcp.fsmretry = DEF_FSMRETRY;
+ if (arg->argc > arg->argn) {
+ strncpy(cx->cfg.cbcp.phone, arg->argv[arg->argn],
+ sizeof cx->cfg.cbcp.phone - 1);
+ cx->cfg.cbcp.phone[sizeof cx->cfg.cbcp.phone - 1] = '\0';
+ if (arg->argc > arg->argn + 1) {
+ cx->cfg.cbcp.delay = atoi(arg->argv[arg->argn + 1]);
+ if (arg->argc > arg->argn + 2) {
+ long_val = atol(arg->argv[arg->argn + 2]);
+ if (long_val < MIN_FSMRETRY)
+ log_Printf(LogWARN, "%ld: Invalid CBCP FSM retry period - min %d\n",
+ long_val, MIN_FSMRETRY);
+ else
+ cx->cfg.cbcp.fsmretry = long_val;
+ }
+ }
+ }
+ break;
+
+ case VAR_CHOKED:
+ arg->bundle->cfg.choked.timeout = atoi(argp);
+ if (arg->bundle->cfg.choked.timeout <= 0)
+ arg->bundle->cfg.choked.timeout = CHOKED_TIMEOUT;
+ break;
+
+ case VAR_SENDPIPE:
+ long_val = atol(argp);
+ arg->bundle->ncp.cfg.sendpipe = long_val;
+ break;
+
+ case VAR_RECVPIPE:
+ long_val = atol(argp);
+ arg->bundle->ncp.cfg.recvpipe = long_val;
+ break;
+
+#ifndef NORADIUS
+ case VAR_RADIUS:
+ if (!*argp)
+ *arg->bundle->radius.cfg.file = '\0';
+ else if (access(argp, R_OK)) {
+ log_Printf(LogWARN, "%s: %s\n", argp, strerror(errno));
+ res = 1;
+ break;
+ } else {
+ strncpy(arg->bundle->radius.cfg.file, argp,
+ sizeof arg->bundle->radius.cfg.file - 1);
+ arg->bundle->radius.cfg.file
+ [sizeof arg->bundle->radius.cfg.file - 1] = '\0';
+ }
+ break;
+#endif
+
+ case VAR_CD:
+ if (*argp) {
+ if (strcasecmp(argp, "off")) {
+ long_val = atol(argp);
+ if (long_val < 0)
+ long_val = 0;
+ cx->physical->cfg.cd.delay = long_val;
+ cx->physical->cfg.cd.necessity = argp[strlen(argp)-1] == '!' ?
+ CD_REQUIRED : CD_VARIABLE;
+ } else
+ cx->physical->cfg.cd.necessity = CD_NOTREQUIRED;
+ } else {
+ cx->physical->cfg.cd.delay = 0;
+ cx->physical->cfg.cd.necessity = CD_DEFAULT;
+ }
+ break;
+
+ case VAR_PARITY:
+ if (arg->argc == arg->argn + 1)
+ res = physical_SetParity(arg->cx->physical, argp);
+ else {
+ log_Printf(LogWARN, "Parity value must be odd, even or none\n");
+ res = 1;
+ }
+ break;
+
+ case VAR_CRTSCTS:
+ if (strcasecmp(argp, "on") == 0)
+ physical_SetRtsCts(arg->cx->physical, 1);
+ else if (strcasecmp(argp, "off") == 0)
+ physical_SetRtsCts(arg->cx->physical, 0);
+ else {
+ log_Printf(LogWARN, "RTS/CTS value must be on or off\n");
+ res = 1;
+ }
+ break;
+
+ case VAR_URGENTPORTS:
+ if (arg->argn == arg->argc) {
+ ncp_SetUrgentTOS(&arg->bundle->ncp);
+ ncp_ClearUrgentTcpPorts(&arg->bundle->ncp);
+ ncp_ClearUrgentUdpPorts(&arg->bundle->ncp);
+ } else if (!strcasecmp(arg->argv[arg->argn], "udp")) {
+ ncp_SetUrgentTOS(&arg->bundle->ncp);
+ if (arg->argn == arg->argc - 1)
+ ncp_ClearUrgentUdpPorts(&arg->bundle->ncp);
+ else for (f = arg->argn + 1; f < arg->argc; f++)
+ if (*arg->argv[f] == '+')
+ ncp_AddUrgentUdpPort(&arg->bundle->ncp, atoi(arg->argv[f] + 1));
+ else if (*arg->argv[f] == '-')
+ ncp_RemoveUrgentUdpPort(&arg->bundle->ncp, atoi(arg->argv[f] + 1));
+ else {
+ if (f == arg->argn)
+ ncp_ClearUrgentUdpPorts(&arg->bundle->ncp);
+ ncp_AddUrgentUdpPort(&arg->bundle->ncp, atoi(arg->argv[f]));
+ }
+ } else if (arg->argn == arg->argc - 1 &&
+ !strcasecmp(arg->argv[arg->argn], "none")) {
+ ncp_ClearUrgentTcpPorts(&arg->bundle->ncp);
+ ncp_ClearUrgentUdpPorts(&arg->bundle->ncp);
+ ncp_ClearUrgentTOS(&arg->bundle->ncp);
+ } else {
+ ncp_SetUrgentTOS(&arg->bundle->ncp);
+ first = arg->argn;
+ if (!strcasecmp(arg->argv[first], "tcp") && ++first == arg->argc)
+ ncp_ClearUrgentTcpPorts(&arg->bundle->ncp);
+
+ for (f = first; f < arg->argc; f++)
+ if (*arg->argv[f] == '+')
+ ncp_AddUrgentTcpPort(&arg->bundle->ncp, atoi(arg->argv[f] + 1));
+ else if (*arg->argv[f] == '-')
+ ncp_RemoveUrgentTcpPort(&arg->bundle->ncp, atoi(arg->argv[f] + 1));
+ else {
+ if (f == first)
+ ncp_ClearUrgentTcpPorts(&arg->bundle->ncp);
+ ncp_AddUrgentTcpPort(&arg->bundle->ncp, atoi(arg->argv[f]));
+ }
+ }
+ break;
+
+ case VAR_PPPOE:
+ if (strcasecmp(argp, "3Com") == 0)
+ physical_SetPPPoEnonstandard(arg->cx->physical, 1);
+ else if (strcasecmp(argp, "standard") == 0)
+ physical_SetPPPoEnonstandard(arg->cx->physical, 0);
+ else {
+ log_Printf(LogWARN, "PPPoE standard value must be \"standard\" or \"3Com\"\n");
+ res = 1;
+ }
+ break;
+
+#ifndef NORADIUS
+ case VAR_PORT_ID:
+ if (strcasecmp(argp, "default") == 0)
+ arg->bundle->radius.port_id_type = RPI_DEFAULT;
+ else if (strcasecmp(argp, "pid") == 0)
+ arg->bundle->radius.port_id_type = RPI_PID;
+ else if (strcasecmp(argp, "ifnum") == 0)
+ arg->bundle->radius.port_id_type = RPI_IFNUM;
+ else if (strcasecmp(argp, "tunnum") == 0)
+ arg->bundle->radius.port_id_type = RPI_TUNNUM;
+ else {
+ log_Printf(LogWARN,
+ "RADIUS port id must be one of \"default\", \"pid\", \"ifnum\" or \"tunnum\"\n");
+ res = 1;
+ }
+
+ if (arg->bundle->radius.port_id_type && !*arg->bundle->radius.cfg.file) {
+ log_Printf(LogWARN, "rad_port_id requires radius to be configured\n");
+ res = 1;
+ }
+
+ break;
+#endif
+ }
+
+ return res;
+}
+
+static struct cmdtab const SetCommands[] = {
+ {"accmap", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX,
+ "accmap value", "set accmap hex-value", (const void *)VAR_ACCMAP},
+ {"authkey", "key", SetVariable, LOCAL_AUTH,
+ "authentication key", "set authkey|key key", (const void *)VAR_AUTHKEY},
+ {"authname", NULL, SetVariable, LOCAL_AUTH,
+ "authentication name", "set authname name", (const void *)VAR_AUTHNAME},
+ {"autoload", NULL, SetVariable, LOCAL_AUTH,
+ "auto link [de]activation", "set autoload maxtime maxload mintime minload",
+ (const void *)VAR_AUTOLOAD},
+ {"bandwidth", NULL, mp_SetDatalinkBandwidth, LOCAL_AUTH | LOCAL_CX,
+ "datalink bandwidth", "set bandwidth value", NULL},
+ {"callback", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX,
+ "callback control", "set callback [none|auth|cbcp|"
+ "E.164 *|number[,number]...]...", (const void *)VAR_CALLBACK},
+ {"cbcp", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX,
+ "CBCP control", "set cbcp [*|phone[,phone...] [delay [timeout]]]",
+ (const void *)VAR_CBCP},
+ {"ccpretry", "ccpretries", SetVariable, LOCAL_AUTH | LOCAL_CX_OPT,
+ "CCP retries", "set ccpretry value [attempts]", (const void *)VAR_CCPRETRY},
+ {"cd", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, "Carrier delay requirement",
+ "set cd value[!]", (const void *)VAR_CD},
+ {"chapretry", "chapretries", SetVariable, LOCAL_AUTH | LOCAL_CX,
+ "CHAP retries", "set chapretry value [attempts]",
+ (const void *)VAR_CHAPRETRY},
+ {"choked", NULL, SetVariable, LOCAL_AUTH,
+ "choked timeout", "set choked [secs]", (const void *)VAR_CHOKED},
+ {"ctsrts", "crtscts", SetVariable, LOCAL_AUTH | LOCAL_CX,
+ "Use hardware flow control", "set ctsrts [on|off]",
+ (const char *)VAR_CRTSCTS},
+ {"deflate", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX_OPT,
+ "deflate window sizes", "set deflate out-winsize in-winsize",
+ (const void *) VAR_WINSIZE},
+#ifndef NODES
+ {"mppe", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX_OPT,
+ "MPPE key size and state", "set mppe [40|56|128|* [stateful|stateless|*]]",
+ (const void *) VAR_MPPE},
+#endif
+ {"device", "line", SetVariable, LOCAL_AUTH | LOCAL_CX,
+ "physical device name", "set device|line device-name[,device-name]",
+ (const void *) VAR_DEVICE},
+ {"dial", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX,
+ "dialing script", "set dial chat-script", (const void *) VAR_DIAL},
+ {"dns", NULL, SetVariable, LOCAL_AUTH, "Domain Name Server",
+ "set dns pri-addr [sec-addr]", (const void *)VAR_DNS},
+ {"enddisc", NULL, mp_SetEnddisc, LOCAL_AUTH,
+ "Endpoint Discriminator", "set enddisc [IP|magic|label|psn value]", NULL},
+ {"escape", NULL, SetEscape, LOCAL_AUTH | LOCAL_CX,
+ "escape characters", "set escape hex-digit ...", NULL},
+ {"filter", NULL, filter_Set, LOCAL_AUTH,
+ "packet filters", "set filter alive|dial|in|out rule-no permit|deny "
+ "[src_addr[/width]] [dst_addr[/width]] [proto "
+ "[src [lt|eq|gt port]] [dst [lt|eq|gt port]] [estab] [syn] [finrst]]", NULL},
+ {"hangup", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX,
+ "hangup script", "set hangup chat-script", (const void *) VAR_HANGUP},
+ {"ifaddr", NULL, SetInterfaceAddr, LOCAL_AUTH, "destination address",
+ "set ifaddr [src-addr [dst-addr [netmask [trg-addr]]]]", NULL},
+ {"ifqueue", NULL, SetVariable, LOCAL_AUTH, "interface queue",
+ "set ifqueue packets", (const void *)VAR_IFQUEUE},
+ {"ipcpretry", "ipcpretries", SetVariable, LOCAL_AUTH, "IPCP retries",
+ "set ipcpretry value [attempts]", (const void *)VAR_IPCPRETRY},
+ {"ipv6cpretry", "ipv6cpretries", SetVariable, LOCAL_AUTH, "IPV6CP retries",
+ "set ipv6cpretry value [attempts]", (const void *)VAR_IPV6CPRETRY},
+ {"lcpretry", "lcpretries", SetVariable, LOCAL_AUTH | LOCAL_CX, "LCP retries",
+ "set lcpretry value [attempts]", (const void *)VAR_LCPRETRY},
+ {"log", NULL, log_SetLevel, LOCAL_AUTH, "log level",
+ "set log [local] [+|-]all|async|cbcp|ccp|chat|command|connect|debug|dns|hdlc|"
+ "id0|ipcp|lcp|lqm|phase|physical|radius|sync|tcp/ip|timer|tun...", NULL},
+ {"login", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX,
+ "login script", "set login chat-script", (const void *) VAR_LOGIN},
+ {"logout", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX,
+ "logout script", "set logout chat-script", (const void *) VAR_LOGOUT},
+ {"lqrperiod", "echoperiod", SetVariable, LOCAL_AUTH | LOCAL_CX_OPT,
+ "LQR period", "set lqr/echo period value", (const void *)VAR_LQRPERIOD},
+ {"mode", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, "mode value",
+ "set mode interactive|auto|ddial|background", (const void *)VAR_MODE},
+ {"mrru", NULL, SetVariable, LOCAL_AUTH, "MRRU value",
+ "set mrru value", (const void *)VAR_MRRU},
+ {"mru", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX,
+ "MRU value", "set mru [max[imum]] [value]", (const void *)VAR_MRU},
+ {"mtu", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX,
+ "interface MTU value", "set mtu [max[imum]] [value]", (const void *)VAR_MTU},
+ {"nbns", NULL, SetVariable, LOCAL_AUTH, "NetBIOS Name Server",
+ "set nbns pri-addr [sec-addr]", (const void *)VAR_NBNS},
+ {"openmode", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, "open mode",
+ "set openmode active|passive [secs]", (const void *)VAR_OPENMODE},
+ {"papretry", "papretries", SetVariable, LOCAL_AUTH | LOCAL_CX, "PAP retries",
+ "set papretry value [attempts]", (const void *)VAR_PAPRETRY},
+ {"parity", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, "serial parity",
+ "set parity [odd|even|none]", (const void *)VAR_PARITY},
+ {"phone", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, "telephone number(s)",
+ "set phone phone1[:phone2[...]]", (const void *)VAR_PHONE},
+ {"proctitle", "title", SetProcTitle, LOCAL_AUTH,
+ "Process title", "set proctitle [value]", NULL},
+#ifndef NORADIUS
+ {"radius", NULL, SetVariable, LOCAL_AUTH,
+ "RADIUS Config", "set radius cfgfile", (const void *)VAR_RADIUS},
+ {"rad_alive", NULL, SetVariable, LOCAL_AUTH,
+ "Raduis alive interval", "set rad_alive value",
+ (const void *)VAR_RAD_ALIVE},
+ {"rad_port_id", NULL, SetVariable, LOCAL_AUTH,
+ "NAS-Port-Id", "set rad_port_id [default|pid|ifnum|tunnum]", (const void *)VAR_PORT_ID},
+#endif
+ {"reconnect", NULL, datalink_SetReconnect, LOCAL_AUTH | LOCAL_CX,
+ "Reconnect timeout", "set reconnect value ntries", NULL},
+ {"recvpipe", NULL, SetVariable, LOCAL_AUTH,
+ "RECVPIPE value", "set recvpipe value", (const void *)VAR_RECVPIPE},
+ {"redial", NULL, datalink_SetRedial, LOCAL_AUTH | LOCAL_CX,
+ "Redial timeout", "set redial secs[+inc[-incmax]][.next] [attempts]", NULL},
+ {"sendpipe", NULL, SetVariable, LOCAL_AUTH,
+ "SENDPIPE value", "set sendpipe value", (const void *)VAR_SENDPIPE},
+ {"server", "socket", SetServer, LOCAL_AUTH, "diagnostic port",
+ "set server|socket TcpPort|LocalName|none|open|closed [password [mask]]",
+ NULL},
+ {"speed", NULL, SetModemSpeed, LOCAL_AUTH | LOCAL_CX,
+ "physical speed", "set speed value|sync", NULL},
+ {"stopped", NULL, SetStoppedTimeout, LOCAL_AUTH | LOCAL_CX,
+ "STOPPED timeouts", "set stopped [LCPseconds [CCPseconds]]", NULL},
+ {"timeout", NULL, SetVariable, LOCAL_AUTH, "Idle timeout",
+ "set timeout idletime", (const void *)VAR_IDLETIMEOUT},
+ {"urgent", NULL, SetVariable, LOCAL_AUTH, "urgent ports",
+ "set urgent [tcp|udp] [+|-]port...", (const void *)VAR_URGENTPORTS},
+ {"vj", NULL, ipcp_vjset, LOCAL_AUTH,
+ "vj values", "set vj slots|slotcomp [value]", NULL},
+ {"help", "?", HelpCommand, LOCAL_AUTH | LOCAL_NO_AUTH,
+ "Display this message", "set help|? [command]", SetCommands},
+ {"pppoe", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX,
+ "Connect using standard/3Com mode", "set pppoe [standard|3Com]",
+ (const char *)VAR_PPPOE},
+ {NULL, NULL, NULL, 0, NULL, NULL, NULL},
+};
+
+static int
+SetCommand(struct cmdargs const *arg)
+{
+ if (arg->argc > arg->argn)
+ FindExec(arg->bundle, SetCommands, arg->argc, arg->argn, arg->argv,
+ arg->prompt, arg->cx);
+ else if (arg->prompt)
+ prompt_Printf(arg->prompt, "Use `set ?' to get a list or `set ? <var>' for"
+ " syntax help.\n");
+ else
+ log_Printf(LogWARN, "set command must have arguments\n");
+
+ return 0;
+}
+
+static int
+AddCommand(struct cmdargs const *arg)
+{
+ struct ncpaddr gw;
+ struct ncprange dest;
+ struct in_addr host;
+#ifndef NOINET6
+ struct in6_addr host6;
+#endif
+ int dest_default, gw_arg, addrs;
+
+ if (arg->argc != arg->argn+3 && arg->argc != arg->argn+2)
+ return -1;
+
+ addrs = 0;
+ dest_default = 0;
+ if (arg->argc == arg->argn + 2) {
+ if (!strcasecmp(arg->argv[arg->argn], "default"))
+ dest_default = 1;
+ else {
+ if (!ncprange_aton(&dest, &arg->bundle->ncp, arg->argv[arg->argn]))
+ return -1;
+ if (!strncasecmp(arg->argv[arg->argn], "MYADDR", 6))
+ addrs = ROUTE_DSTMYADDR;
+ else if (!strncasecmp(arg->argv[arg->argn], "MYADDR6", 7))
+ addrs = ROUTE_DSTMYADDR6;
+ else if (!strncasecmp(arg->argv[arg->argn], "HISADDR", 7))
+ addrs = ROUTE_DSTHISADDR;
+ else if (!strncasecmp(arg->argv[arg->argn], "HISADDR6", 8))
+ addrs = ROUTE_DSTHISADDR6;
+ else if (!strncasecmp(arg->argv[arg->argn], "DNS0", 4))
+ addrs = ROUTE_DSTDNS0;
+ else if (!strncasecmp(arg->argv[arg->argn], "DNS1", 4))
+ addrs = ROUTE_DSTDNS1;
+ }
+ gw_arg = 1;
+ } else {
+ if (strcasecmp(arg->argv[arg->argn], "MYADDR") == 0) {
+ addrs = ROUTE_DSTMYADDR;
+ host = arg->bundle->ncp.ipcp.my_ip;
+ } else if (strcasecmp(arg->argv[arg->argn], "HISADDR") == 0) {
+ addrs = ROUTE_DSTHISADDR;
+ host = arg->bundle->ncp.ipcp.peer_ip;
+ } else if (strcasecmp(arg->argv[arg->argn], "DNS0") == 0) {
+ addrs = ROUTE_DSTDNS0;
+ host = arg->bundle->ncp.ipcp.ns.dns[0];
+ } else if (strcasecmp(arg->argv[arg->argn], "DNS1") == 0) {
+ addrs = ROUTE_DSTDNS1;
+ host = arg->bundle->ncp.ipcp.ns.dns[1];
+ } else {
+ host = GetIpAddr(arg->argv[arg->argn]);
+ if (host.s_addr == INADDR_NONE) {
+ log_Printf(LogWARN, "%s: Invalid destination address\n",
+ arg->argv[arg->argn]);
+ return -1;
+ }
+ }
+ ncprange_setip4(&dest, host, GetIpAddr(arg->argv[arg->argn + 1]));
+ gw_arg = 2;
+ }
+
+ if (strcasecmp(arg->argv[arg->argn + gw_arg], "HISADDR") == 0) {
+ ncpaddr_setip4(&gw, arg->bundle->ncp.ipcp.peer_ip);
+ addrs |= ROUTE_GWHISADDR;
+#ifndef NOINET6
+ } else if (strcasecmp(arg->argv[arg->argn + gw_arg], "HISADDR6") == 0) {
+ if (!ncpaddr_getip6(&arg->bundle->ncp.ipv6cp.hisaddr, &host6))
+ memset(&host6, '\0', sizeof host6);
+ ncpaddr_setip6(&gw, &host6);
+ addrs |= ROUTE_GWHISADDR6;
+#endif
+ } else {
+ if (!ncpaddr_aton(&gw, &arg->bundle->ncp, arg->argv[arg->argn + gw_arg])) {
+ log_Printf(LogWARN, "%s: Invalid gateway address\n",
+ arg->argv[arg->argn + gw_arg]);
+ return -1;
+ }
+ }
+
+ if (dest_default)
+ ncprange_setdefault(&dest, ncpaddr_family(&gw));
+
+ if (rt_Set(arg->bundle, RTM_ADD, &dest, &gw, arg->cmd->args ? 1 : 0,
+ ((addrs & ROUTE_GWHISADDR) || (addrs & ROUTE_GWHISADDR6)) ? 1 : 0)
+ && addrs != ROUTE_STATIC)
+ route_Add(&arg->bundle->ncp.route, addrs, &dest, &gw);
+
+ return 0;
+}
+
+static int
+DeleteCommand(struct cmdargs const *arg)
+{
+ struct ncprange dest;
+ int addrs;
+
+ if (arg->argc == arg->argn+1) {
+ if(strcasecmp(arg->argv[arg->argn], "all") == 0) {
+ route_IfDelete(arg->bundle, 0);
+ route_DeleteAll(&arg->bundle->ncp.route);
+ } else {
+ addrs = 0;
+ if (strcasecmp(arg->argv[arg->argn], "MYADDR") == 0) {
+ ncprange_setip4host(&dest, arg->bundle->ncp.ipcp.my_ip);
+ addrs = ROUTE_DSTMYADDR;
+#ifndef NOINET6
+ } else if (strcasecmp(arg->argv[arg->argn], "MYADDR6") == 0) {
+ ncprange_sethost(&dest, &arg->bundle->ncp.ipv6cp.myaddr);
+ addrs = ROUTE_DSTMYADDR6;
+#endif
+ } else if (strcasecmp(arg->argv[arg->argn], "HISADDR") == 0) {
+ ncprange_setip4host(&dest, arg->bundle->ncp.ipcp.peer_ip);
+ addrs = ROUTE_DSTHISADDR;
+#ifndef NOINET6
+ } else if (strcasecmp(arg->argv[arg->argn], "HISADDR6") == 0) {
+ ncprange_sethost(&dest, &arg->bundle->ncp.ipv6cp.hisaddr);
+ addrs = ROUTE_DSTHISADDR6;
+#endif
+ } else if (strcasecmp(arg->argv[arg->argn], "DNS0") == 0) {
+ ncprange_setip4host(&dest, arg->bundle->ncp.ipcp.ns.dns[0]);
+ addrs = ROUTE_DSTDNS0;
+ } else if (strcasecmp(arg->argv[arg->argn], "DNS1") == 0) {
+ ncprange_setip4host(&dest, arg->bundle->ncp.ipcp.ns.dns[1]);
+ addrs = ROUTE_DSTDNS1;
+ } else {
+ ncprange_aton(&dest, &arg->bundle->ncp, arg->argv[arg->argn]);
+ addrs = ROUTE_STATIC;
+ }
+ rt_Set(arg->bundle, RTM_DELETE, &dest, NULL, arg->cmd->args ? 1 : 0, 0);
+ route_Delete(&arg->bundle->ncp.route, addrs, &dest);
+ }
+ } else
+ return -1;
+
+ return 0;
+}
+
+#ifndef NONAT
+static int
+NatEnable(struct cmdargs const *arg)
+{
+ if (arg->argc == arg->argn+1) {
+ if (strcasecmp(arg->argv[arg->argn], "yes") == 0) {
+ if (!arg->bundle->NatEnabled) {
+ if (arg->bundle->ncp.ipcp.fsm.state == ST_OPENED)
+ LibAliasSetAddress(la, arg->bundle->ncp.ipcp.my_ip);
+ arg->bundle->NatEnabled = 1;
+ }
+ return 0;
+ } else if (strcasecmp(arg->argv[arg->argn], "no") == 0) {
+ arg->bundle->NatEnabled = 0;
+ opt_disable(arg->bundle, OPT_IFACEALIAS);
+ /* Don't iface_Clear() - there may be manually configured addresses */
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+
+static int
+NatOption(struct cmdargs const *arg)
+{
+ long param = (long)arg->cmd->args;
+
+ if (arg->argc == arg->argn+1) {
+ if (strcasecmp(arg->argv[arg->argn], "yes") == 0) {
+ if (arg->bundle->NatEnabled) {
+ LibAliasSetMode(la, param, param);
+ return 0;
+ }
+ log_Printf(LogWARN, "nat not enabled\n");
+ } else if (strcmp(arg->argv[arg->argn], "no") == 0) {
+ if (arg->bundle->NatEnabled) {
+ LibAliasSetMode(la, 0, param);
+ return 0;
+ }
+ log_Printf(LogWARN, "nat not enabled\n");
+ }
+ }
+ return -1;
+}
+#endif /* #ifndef NONAT */
+
+static int
+LinkCommand(struct cmdargs const *arg)
+{
+ if (arg->argc > arg->argn+1) {
+ char namelist[LINE_LEN];
+ struct datalink *cx;
+ char *name;
+ int result = 0;
+
+ if (!strcmp(arg->argv[arg->argn], "*")) {
+ struct datalink *dl;
+
+ cx = arg->bundle->links;
+ while (cx) {
+ /* Watch it, the command could be a ``remove'' */
+ dl = cx->next;
+ FindExec(arg->bundle, Commands, arg->argc, arg->argn+1, arg->argv,
+ arg->prompt, cx);
+ for (cx = arg->bundle->links; cx; cx = cx->next)
+ if (cx == dl)
+ break; /* Pointer's still valid ! */
+ }
+ } else {
+ strncpy(namelist, arg->argv[arg->argn], sizeof namelist - 1);
+ namelist[sizeof namelist - 1] = '\0';
+ for(name = strtok(namelist, ", "); name; name = strtok(NULL,", "))
+ if (!bundle2datalink(arg->bundle, name)) {
+ log_Printf(LogWARN, "link: %s: Invalid link name\n", name);
+ return 1;
+ }
+
+ strncpy(namelist, arg->argv[arg->argn], sizeof namelist - 1);
+ namelist[sizeof namelist - 1] = '\0';
+ for(name = strtok(namelist, ", "); name; name = strtok(NULL,", ")) {
+ cx = bundle2datalink(arg->bundle, name);
+ if (cx)
+ FindExec(arg->bundle, Commands, arg->argc, arg->argn+1, arg->argv,
+ arg->prompt, cx);
+ else {
+ log_Printf(LogWARN, "link: %s: Invalidated link name !\n", name);
+ result++;
+ }
+ }
+ }
+ return result;
+ }
+
+ log_Printf(LogWARN, "usage: %s\n", arg->cmd->syntax);
+ return 2;
+}
+
+struct link *
+command_ChooseLink(struct cmdargs const *arg)
+{
+ if (arg->cx)
+ return &arg->cx->physical->link;
+ else if (!arg->bundle->ncp.mp.cfg.mrru) {
+ struct datalink *dl = bundle2datalink(arg->bundle, NULL);
+ if (dl)
+ return &dl->physical->link;
+ }
+ return &arg->bundle->ncp.mp.link;
+}
+
+static const char *
+ident_cmd(const char *cmd, unsigned *keep, unsigned *add)
+{
+ const char *result;
+
+ switch (*cmd) {
+ case 'A':
+ case 'a':
+ result = "accept";
+ *keep = NEG_MYMASK;
+ *add = NEG_ACCEPTED;
+ break;
+ case 'D':
+ case 'd':
+ switch (cmd[1]) {
+ case 'E':
+ case 'e':
+ result = "deny";
+ *keep = NEG_MYMASK;
+ *add = 0;
+ break;
+ case 'I':
+ case 'i':
+ result = "disable";
+ *keep = NEG_HISMASK;
+ *add = 0;
+ break;
+ default:
+ return NULL;
+ }
+ break;
+ case 'E':
+ case 'e':
+ result = "enable";
+ *keep = NEG_HISMASK;
+ *add = NEG_ENABLED;
+ break;
+ default:
+ return NULL;
+ }
+
+ return result;
+}
+
+static int
+OptSet(struct cmdargs const *arg)
+{
+ int opt = (int)(long)arg->cmd->args;
+ unsigned keep; /* Keep this opt */
+ unsigned add; /* Add this opt */
+
+ if (ident_cmd(arg->argv[arg->argn - 2], &keep, &add) == NULL)
+ return 1;
+
+#ifndef NOINET6
+ if (add == NEG_ENABLED && opt == OPT_IPV6CP && !probe.ipv6_available) {
+ log_Printf(LogWARN, "IPv6 is not available on this machine\n");
+ return 1;
+ }
+#endif
+ if (!add && ((opt == OPT_NAS_IP_ADDRESS &&
+ !Enabled(arg->bundle, OPT_NAS_IDENTIFIER)) ||
+ (opt == OPT_NAS_IDENTIFIER &&
+ !Enabled(arg->bundle, OPT_NAS_IP_ADDRESS)))) {
+ log_Printf(LogWARN,
+ "Cannot disable both NAS-IP-Address and NAS-Identifier\n");
+ return 1;
+ }
+
+ if (add)
+ opt_enable(arg->bundle, opt);
+ else
+ opt_disable(arg->bundle, opt);
+
+ return 0;
+}
+
+static int
+IfaceAliasOptSet(struct cmdargs const *arg)
+{
+ unsigned long long save = arg->bundle->cfg.optmask;
+ int result = OptSet(arg);
+
+ if (result == 0)
+ if (Enabled(arg->bundle, OPT_IFACEALIAS) && !arg->bundle->NatEnabled) {
+ arg->bundle->cfg.optmask = save;
+ log_Printf(LogWARN, "Cannot enable iface-alias without NAT\n");
+ result = 2;
+ }
+
+ return result;
+}
+
+static int
+NegotiateSet(struct cmdargs const *arg)
+{
+ long param = (long)arg->cmd->args;
+ struct link *l = command_ChooseLink(arg); /* LOCAL_CX_OPT uses this */
+ struct datalink *cx = arg->cx; /* LOCAL_CX uses this */
+ const char *cmd;
+ unsigned keep; /* Keep these bits */
+ unsigned add; /* Add these bits */
+
+ if ((cmd = ident_cmd(arg->argv[arg->argn-2], &keep, &add)) == NULL)
+ return 1;
+
+ if ((arg->cmd->lauth & LOCAL_CX) && !cx) {
+ log_Printf(LogWARN, "%s %s: No context (use the `link' command)\n",
+ cmd, arg->cmd->name);
+ return 2;
+ } else if (cx && !(arg->cmd->lauth & (LOCAL_CX|LOCAL_CX_OPT))) {
+ log_Printf(LogWARN, "%s %s: Redundant context (%s) ignored\n",
+ cmd, arg->cmd->name, cx->name);
+ cx = NULL;
+ }
+
+ switch (param) {
+ case NEG_ACFCOMP:
+ cx->physical->link.lcp.cfg.acfcomp &= keep;
+ cx->physical->link.lcp.cfg.acfcomp |= add;
+ break;
+ case NEG_CHAP05:
+ cx->physical->link.lcp.cfg.chap05 &= keep;
+ cx->physical->link.lcp.cfg.chap05 |= add;
+ break;
+#ifndef NODES
+ case NEG_CHAP80:
+ cx->physical->link.lcp.cfg.chap80nt &= keep;
+ cx->physical->link.lcp.cfg.chap80nt |= add;
+ break;
+ case NEG_CHAP80LM:
+ cx->physical->link.lcp.cfg.chap80lm &= keep;
+ cx->physical->link.lcp.cfg.chap80lm |= add;
+ break;
+ case NEG_CHAP81:
+ cx->physical->link.lcp.cfg.chap81 &= keep;
+ cx->physical->link.lcp.cfg.chap81 |= add;
+ break;
+ case NEG_MPPE:
+ l->ccp.cfg.neg[CCP_NEG_MPPE] &= keep;
+ l->ccp.cfg.neg[CCP_NEG_MPPE] |= add;
+ break;
+#endif
+ case NEG_DEFLATE:
+ l->ccp.cfg.neg[CCP_NEG_DEFLATE] &= keep;
+ l->ccp.cfg.neg[CCP_NEG_DEFLATE] |= add;
+ break;
+ case NEG_DNS:
+ arg->bundle->ncp.ipcp.cfg.ns.dns_neg &= keep;
+ arg->bundle->ncp.ipcp.cfg.ns.dns_neg |= add;
+ break;
+ case NEG_ECHO: /* probably misplaced in this function ! */
+ if (cx->physical->link.lcp.cfg.echo && !add) {
+ cx->physical->link.lcp.cfg.echo = 0;
+ cx->physical->hdlc.lqm.method &= ~LQM_ECHO;
+ if (cx->physical->hdlc.lqm.method & LQM_ECHO &&
+ !cx->physical->link.lcp.want_lqrperiod &&
+ cx->physical->hdlc.lqm.timer.load) {
+ cx->physical->hdlc.lqm.timer.load = 0;
+ lqr_StopTimer(cx->physical);
+ }
+ } else if (!cx->physical->link.lcp.cfg.echo && add) {
+ cx->physical->link.lcp.cfg.echo = 1;
+ cx->physical->hdlc.lqm.method |= LQM_ECHO;
+ cx->physical->hdlc.lqm.timer.load =
+ cx->physical->link.lcp.cfg.lqrperiod * SECTICKS;
+ if (cx->physical->link.lcp.fsm.state == ST_OPENED)
+ (*cx->physical->hdlc.lqm.timer.func)(&cx->physical->link.lcp);
+ }
+ break;
+ case NEG_ENDDISC:
+ arg->bundle->ncp.mp.cfg.negenddisc &= keep;
+ arg->bundle->ncp.mp.cfg.negenddisc |= add;
+ break;
+ case NEG_LQR:
+ cx->physical->link.lcp.cfg.lqr &= keep;
+ cx->physical->link.lcp.cfg.lqr |= add;
+ break;
+ case NEG_PAP:
+ cx->physical->link.lcp.cfg.pap &= keep;
+ cx->physical->link.lcp.cfg.pap |= add;
+ break;
+ case NEG_PPPDDEFLATE:
+ l->ccp.cfg.neg[CCP_NEG_DEFLATE24] &= keep;
+ l->ccp.cfg.neg[CCP_NEG_DEFLATE24] |= add;
+ break;
+ case NEG_PRED1:
+ l->ccp.cfg.neg[CCP_NEG_PRED1] &= keep;
+ l->ccp.cfg.neg[CCP_NEG_PRED1] |= add;
+ break;
+ case NEG_PROTOCOMP:
+ cx->physical->link.lcp.cfg.protocomp &= keep;
+ cx->physical->link.lcp.cfg.protocomp |= add;
+ break;
+ case NEG_SHORTSEQ:
+ switch (bundle_Phase(arg->bundle)) {
+ case PHASE_DEAD:
+ break;
+ case PHASE_ESTABLISH:
+ /* Make sure none of our links are DATALINK_LCP or greater */
+ if (bundle_HighestState(arg->bundle) >= DATALINK_LCP) {
+ log_Printf(LogWARN, "shortseq: Only changeable before"
+ " LCP negotiations\n");
+ return 1;
+ }
+ break;
+ default:
+ log_Printf(LogWARN, "shortseq: Only changeable at phase"
+ " DEAD/ESTABLISH\n");
+ return 1;
+ }
+ arg->bundle->ncp.mp.cfg.shortseq &= keep;
+ arg->bundle->ncp.mp.cfg.shortseq |= add;
+ break;
+ case NEG_VJCOMP:
+ arg->bundle->ncp.ipcp.cfg.vj.neg &= keep;
+ arg->bundle->ncp.ipcp.cfg.vj.neg |= add;
+ break;
+ }
+
+ return 0;
+}
+
+static struct cmdtab const NegotiateCommands[] = {
+ {"echo", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX, "Send echo requests",
+ "disable|enable", (const void *)NEG_ECHO},
+ {"filter-decapsulation", NULL, OptSet, LOCAL_AUTH,
+ "filter on PPPoUDP payloads", "disable|enable",
+ (const void *)OPT_FILTERDECAP},
+ {"force-scripts", NULL, OptSet, LOCAL_AUTH,
+ "Force execution of the configured chat scripts", "disable|enable",
+ (const void *)OPT_FORCE_SCRIPTS},
+ {"idcheck", NULL, OptSet, LOCAL_AUTH, "Check FSM reply ids",
+ "disable|enable", (const void *)OPT_IDCHECK},
+ {"iface-alias", NULL, IfaceAliasOptSet, LOCAL_AUTH,
+ "retain interface addresses", "disable|enable",
+ (const void *)OPT_IFACEALIAS},
+#ifndef NOINET6
+ {"ipcp", NULL, OptSet, LOCAL_AUTH, "IP Network Control Protocol",
+ "disable|enable", (const void *)OPT_IPCP},
+ {"ipv6cp", NULL, OptSet, LOCAL_AUTH, "IPv6 Network Control Protocol",
+ "disable|enable", (const void *)OPT_IPV6CP},
+#endif
+ {"keep-session", NULL, OptSet, LOCAL_AUTH, "Retain device session leader",
+ "disable|enable", (const void *)OPT_KEEPSESSION},
+ {"loopback", NULL, OptSet, LOCAL_AUTH, "Loop packets for local iface",
+ "disable|enable", (const void *)OPT_LOOPBACK},
+ {"nas-ip-address", NULL, OptSet, LOCAL_AUTH, "Send NAS-IP-Address to RADIUS",
+ "disable|enable", (const void *)OPT_NAS_IP_ADDRESS},
+ {"nas-identifier", NULL, OptSet, LOCAL_AUTH, "Send NAS-Identifier to RADIUS",
+ "disable|enable", (const void *)OPT_NAS_IDENTIFIER},
+ {"passwdauth", NULL, OptSet, LOCAL_AUTH, "Use passwd file",
+ "disable|enable", (const void *)OPT_PASSWDAUTH},
+ {"proxy", NULL, OptSet, LOCAL_AUTH, "Create a proxy ARP entry",
+ "disable|enable", (const void *)OPT_PROXY},
+ {"proxyall", NULL, OptSet, LOCAL_AUTH, "Proxy ARP for all remote hosts",
+ "disable|enable", (const void *)OPT_PROXYALL},
+ {"sroutes", NULL, OptSet, LOCAL_AUTH, "Use sticky routes",
+ "disable|enable", (const void *)OPT_SROUTES},
+ {"tcpmssfixup", "mssfixup", OptSet, LOCAL_AUTH, "Modify MSS options",
+ "disable|enable", (const void *)OPT_TCPMSSFIXUP},
+ {"throughput", NULL, OptSet, LOCAL_AUTH, "Rolling throughput",
+ "disable|enable", (const void *)OPT_THROUGHPUT},
+ {"utmp", NULL, OptSet, LOCAL_AUTH, "Log connections in utmp",
+ "disable|enable", (const void *)OPT_UTMP},
+
+#ifndef NOINET6
+#define NEG_OPT_MAX 17 /* accept/deny allowed below and not above */
+#else
+#define NEG_OPT_MAX 15
+#endif
+
+ {"acfcomp", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX,
+ "Address & Control field compression", "accept|deny|disable|enable",
+ (const void *)NEG_ACFCOMP},
+ {"chap", "chap05", NegotiateSet, LOCAL_AUTH | LOCAL_CX,
+ "Challenge Handshake Authentication Protocol", "accept|deny|disable|enable",
+ (const void *)NEG_CHAP05},
+#ifndef NODES
+ {"mschap", "chap80nt", NegotiateSet, LOCAL_AUTH | LOCAL_CX,
+ "Microsoft (NT) CHAP", "accept|deny|disable|enable",
+ (const void *)NEG_CHAP80},
+ {"LANMan", "chap80lm", NegotiateSet, LOCAL_AUTH | LOCAL_CX,
+ "Microsoft (NT) CHAP", "accept|deny|disable|enable",
+ (const void *)NEG_CHAP80LM},
+ {"mschapv2", "chap81", NegotiateSet, LOCAL_AUTH | LOCAL_CX,
+ "Microsoft CHAP v2", "accept|deny|disable|enable",
+ (const void *)NEG_CHAP81},
+ {"mppe", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX_OPT,
+ "MPPE encryption", "accept|deny|disable|enable",
+ (const void *)NEG_MPPE},
+#endif
+ {"deflate", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX_OPT,
+ "Deflate compression", "accept|deny|disable|enable",
+ (const void *)NEG_DEFLATE},
+ {"deflate24", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX_OPT,
+ "Deflate (type 24) compression", "accept|deny|disable|enable",
+ (const void *)NEG_PPPDDEFLATE},
+ {"dns", NULL, NegotiateSet, LOCAL_AUTH,
+ "DNS specification", "accept|deny|disable|enable", (const void *)NEG_DNS},
+ {"enddisc", NULL, NegotiateSet, LOCAL_AUTH, "ENDDISC negotiation",
+ "accept|deny|disable|enable", (const void *)NEG_ENDDISC},
+ {"lqr", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX,
+ "Link Quality Reports", "accept|deny|disable|enable",
+ (const void *)NEG_LQR},
+ {"pap", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX,
+ "Password Authentication protocol", "accept|deny|disable|enable",
+ (const void *)NEG_PAP},
+ {"pred1", "predictor1", NegotiateSet, LOCAL_AUTH | LOCAL_CX_OPT,
+ "Predictor 1 compression", "accept|deny|disable|enable",
+ (const void *)NEG_PRED1},
+ {"protocomp", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX,
+ "Protocol field compression", "accept|deny|disable|enable",
+ (const void *)NEG_PROTOCOMP},
+ {"shortseq", NULL, NegotiateSet, LOCAL_AUTH,
+ "MP Short Sequence Numbers", "accept|deny|disable|enable",
+ (const void *)NEG_SHORTSEQ},
+ {"vjcomp", NULL, NegotiateSet, LOCAL_AUTH,
+ "Van Jacobson header compression", "accept|deny|disable|enable",
+ (const void *)NEG_VJCOMP},
+ {"help", "?", HelpCommand, LOCAL_AUTH | LOCAL_NO_AUTH,
+ "Display this message", "accept|deny|disable|enable help|? [value]",
+ NegotiateCommands},
+ {NULL, NULL, NULL, 0, NULL, NULL, NULL},
+};
+
+static int
+NegotiateCommand(struct cmdargs const *arg)
+{
+ if (arg->argc > arg->argn) {
+ char const *argv[3];
+ unsigned keep, add;
+ int n;
+
+ if ((argv[0] = ident_cmd(arg->argv[arg->argn-1], &keep, &add)) == NULL)
+ return -1;
+ argv[2] = NULL;
+
+ for (n = arg->argn; n < arg->argc; n++) {
+ argv[1] = arg->argv[n];
+ FindExec(arg->bundle, NegotiateCommands + (keep == NEG_HISMASK ?
+ 0 : NEG_OPT_MAX), 2, 1, argv, arg->prompt, arg->cx);
+ }
+ } else if (arg->prompt)
+ prompt_Printf(arg->prompt, "Use `%s ?' to get a list.\n",
+ arg->argv[arg->argn-1]);
+ else
+ log_Printf(LogWARN, "%s command must have arguments\n",
+ arg->argv[arg->argn] );
+
+ return 0;
+}
+
+const char *
+command_ShowNegval(unsigned val)
+{
+ switch (val&3) {
+ case 1: return "disabled & accepted";
+ case 2: return "enabled & denied";
+ case 3: return "enabled & accepted";
+ }
+ return "disabled & denied";
+}
+
+static int
+ClearCommand(struct cmdargs const *arg)
+{
+ struct pppThroughput *t;
+ struct datalink *cx;
+ int i, clear_type;
+
+ if (arg->argc < arg->argn + 1)
+ return -1;
+
+ if (strcasecmp(arg->argv[arg->argn], "physical") == 0) {
+ cx = arg->cx;
+ if (!cx)
+ cx = bundle2datalink(arg->bundle, NULL);
+ if (!cx) {
+ log_Printf(LogWARN, "A link must be specified for ``clear physical''\n");
+ return 1;
+ }
+ t = &cx->physical->link.stats.total;
+ } else if (strcasecmp(arg->argv[arg->argn], "ipcp") == 0)
+ t = &arg->bundle->ncp.ipcp.throughput;
+#ifndef NOINET6
+ else if (strcasecmp(arg->argv[arg->argn], "ipv6cp") == 0)
+ t = &arg->bundle->ncp.ipv6cp.throughput;
+#endif
+ else
+ return -1;
+
+ if (arg->argc > arg->argn + 1) {
+ clear_type = 0;
+ for (i = arg->argn + 1; i < arg->argc; i++)
+ if (strcasecmp(arg->argv[i], "overall") == 0)
+ clear_type |= THROUGHPUT_OVERALL;
+ else if (strcasecmp(arg->argv[i], "current") == 0)
+ clear_type |= THROUGHPUT_CURRENT;
+ else if (strcasecmp(arg->argv[i], "peak") == 0)
+ clear_type |= THROUGHPUT_PEAK;
+ else
+ return -1;
+ } else
+ clear_type = THROUGHPUT_ALL;
+
+ throughput_clear(t, clear_type, arg->prompt);
+ return 0;
+}
+
+static int
+RunListCommand(struct cmdargs const *arg)
+{
+ const char *cmd = arg->argc ? arg->argv[arg->argc - 1] : "???";
+
+#ifndef NONAT
+ if (arg->cmd->args == NatCommands &&
+ tolower(*arg->argv[arg->argn - 1]) == 'a') {
+ if (arg->prompt)
+ prompt_Printf(arg->prompt, "The alias command is deprecated\n");
+ else
+ log_Printf(LogWARN, "The alias command is deprecated\n");
+ }
+#endif
+
+ if (arg->argc > arg->argn)
+ FindExec(arg->bundle, arg->cmd->args, arg->argc, arg->argn, arg->argv,
+ arg->prompt, arg->cx);
+ else if (arg->prompt)
+ prompt_Printf(arg->prompt, "Use `%s help' to get a list or `%s help"
+ " <option>' for syntax help.\n", cmd, cmd);
+ else
+ log_Printf(LogWARN, "%s command must have arguments\n", cmd);
+
+ return 0;
+}
+
+static int
+IfaceNameCommand(struct cmdargs const *arg)
+{
+ int n = arg->argn;
+
+ if (arg->argc != n + 1)
+ return -1;
+
+ if (!iface_Name(arg->bundle->iface, arg->argv[n]))
+ return 1;
+
+ log_SetTun(arg->bundle->unit, arg->bundle->iface->name);
+ return 0;
+}
+
+static int
+IfaceAddCommand(struct cmdargs const *arg)
+{
+ struct ncpaddr peer, addr;
+ struct ncprange ifa;
+ struct in_addr mask;
+ int n, how;
+
+ if (arg->argc == arg->argn + 1) {
+ if (!ncprange_aton(&ifa, NULL, arg->argv[arg->argn]))
+ return -1;
+ ncpaddr_init(&peer);
+ } else {
+ if (arg->argc == arg->argn + 2) {
+ if (!ncprange_aton(&ifa, NULL, arg->argv[arg->argn]))
+ return -1;
+ n = 1;
+ } else if (arg->argc == arg->argn + 3) {
+ if (!ncpaddr_aton(&addr, NULL, arg->argv[arg->argn]))
+ return -1;
+ if (ncpaddr_family(&addr) != AF_INET)
+ return -1;
+ ncprange_sethost(&ifa, &addr);
+ if (!ncpaddr_aton(&addr, NULL, arg->argv[arg->argn + 1]))
+ return -1;
+ if (!ncpaddr_getip4(&addr, &mask))
+ return -1;
+ if (!ncprange_setip4mask(&ifa, mask))
+ return -1;
+ n = 2;
+ } else
+ return -1;
+
+ if (!ncpaddr_aton(&peer, NULL, arg->argv[arg->argn + n]))
+ return -1;
+
+ if (ncprange_family(&ifa) != ncpaddr_family(&peer)) {
+ log_Printf(LogWARN, "IfaceAddCommand: src and dst address families"
+ " differ\n");
+ return -1;
+ }
+ }
+
+ how = IFACE_ADD_LAST;
+ if (arg->cmd->args)
+ how |= IFACE_FORCE_ADD;
+
+ return !iface_Add(arg->bundle->iface, &arg->bundle->ncp, &ifa, &peer, how);
+}
+
+static int
+IfaceDeleteCommand(struct cmdargs const *arg)
+{
+ struct ncpaddr ifa;
+ struct in_addr ifa4;
+ int ok;
+
+ if (arg->argc != arg->argn + 1)
+ return -1;
+
+ if (!ncpaddr_aton(&ifa, NULL, arg->argv[arg->argn]))
+ return -1;
+
+ if (arg->bundle->ncp.ipcp.fsm.state == ST_OPENED &&
+ ncpaddr_getip4(&ifa, &ifa4) &&
+ arg->bundle->ncp.ipcp.my_ip.s_addr == ifa4.s_addr) {
+ log_Printf(LogWARN, "%s: Cannot remove active interface address\n",
+ ncpaddr_ntoa(&ifa));
+ return 1;
+ }
+
+ ok = iface_Delete(arg->bundle->iface, &arg->bundle->ncp, &ifa);
+ if (!ok) {
+ if (arg->cmd->args)
+ ok = 1;
+ else if (arg->prompt)
+ prompt_Printf(arg->prompt, "%s: No such interface address\n",
+ ncpaddr_ntoa(&ifa));
+ else
+ log_Printf(LogWARN, "%s: No such interface address\n",
+ ncpaddr_ntoa(&ifa));
+ }
+
+ return !ok;
+}
+
+static int
+IfaceClearCommand(struct cmdargs const *arg)
+{
+ int family, how;
+
+ family = 0;
+ if (arg->argc == arg->argn + 1) {
+ if (strcasecmp(arg->argv[arg->argn], "inet") == 0)
+ family = AF_INET;
+#ifndef NOINET6
+ else if (strcasecmp(arg->argv[arg->argn], "inet6") == 0)
+ family = AF_INET6;
+#endif
+ else
+ return -1;
+ } else if (arg->argc != arg->argn)
+ return -1;
+
+ how = arg->bundle->ncp.ipcp.fsm.state == ST_OPENED ||
+ arg->bundle->phys_type.all & PHYS_AUTO ?
+ IFACE_CLEAR_ALIASES : IFACE_CLEAR_ALL;
+ iface_Clear(arg->bundle->iface, &arg->bundle->ncp, family, how);
+
+ return 0;
+}
+
+static int
+SetProcTitle(struct cmdargs const *arg)
+{
+ static char title[LINE_LEN];
+ char *argv[MAXARGS];
+ int argc = arg->argc - arg->argn;
+
+ if (arg->argc <= arg->argn) {
+ SetTitle(NULL);
+ return 0;
+ }
+
+ if ((unsigned)argc >= sizeof argv / sizeof argv[0]) {
+ argc = sizeof argv / sizeof argv[0] - 1;
+ log_Printf(LogWARN, "Truncating proc title to %d args\n", argc);
+ }
+ command_Expand(argv, argc, arg->argv + arg->argn, arg->bundle, 1, getpid());
+ Concatinate(title, sizeof title, argc, (const char *const *)argv);
+ SetTitle(title);
+ command_Free(argc, argv);
+
+ return 0;
+}
diff --git a/usr.sbin/ppp/command.h b/usr.sbin/ppp/command.h
new file mode 100644
index 0000000..8eb1e38
--- /dev/null
+++ b/usr.sbin/ppp/command.h
@@ -0,0 +1,75 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+struct cmdtab;
+struct bundle;
+struct datalink;
+struct prompt;
+
+struct cmdargs {
+ struct cmdtab const *cmdtab; /* The entire command table */
+ struct cmdtab const *cmd; /* This command entry */
+ int argc; /* Number of arguments (excluding cmd */
+ int argn; /* Argument to start processing from */
+ char const *const *argv; /* Arguments */
+ struct bundle *bundle; /* Our bundle */
+ struct datalink *cx; /* Our context */
+ struct prompt *prompt; /* Who executed us */
+};
+
+struct cmdtab {
+ const char *name;
+ const char *alias;
+ int (*func) (struct cmdargs const *);
+ u_char lauth;
+ const char *helpmes;
+ const char *syntax;
+ const void *args;
+};
+
+#define NEG_ACCEPTED (1)
+#define NEG_ENABLED (2)
+#define IsAccepted(x) ((x) & NEG_ACCEPTED)
+#define IsEnabled(x) ((x) & NEG_ENABLED)
+
+extern const char Version[];
+
+extern void command_Expand(char **, int, char const *const *, struct bundle *,
+ int, pid_t);
+extern void command_Free(int, char **);
+extern int command_Expand_Interpret(char *, int, char *vector[MAXARGS], int);
+extern int command_Interpret(char *, int, char *vector[MAXARGS]);
+extern void command_Run(struct bundle *, int, char const *const *,
+ struct prompt *, const char *, struct datalink *);
+extern int command_Decode(struct bundle *, char *, int, struct prompt *,
+ const char *);
+extern struct link *command_ChooseLink(struct cmdargs const *);
+extern const char *command_ShowNegval(unsigned);
+
diff --git a/usr.sbin/ppp/datalink.c b/usr.sbin/ppp/datalink.c
new file mode 100644
index 0000000..d47b11a
--- /dev/null
+++ b/usr.sbin/ppp/datalink.c
@@ -0,0 +1,1478 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.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/param.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+#include <termios.h>
+
+#include "layer.h"
+#include "mbuf.h"
+#include "log.h"
+#include "defs.h"
+#include "timer.h"
+#include "fsm.h"
+#include "descriptor.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "async.h"
+#include "throughput.h"
+#include "ccp.h"
+#include "link.h"
+#include "physical.h"
+#include "iplist.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "chat.h"
+#include "auth.h"
+#include "prompt.h"
+#include "proto.h"
+#include "pap.h"
+#include "chap.h"
+#include "command.h"
+#include "cbcp.h"
+#include "datalink.h"
+
+static void datalink_LoginDone(struct datalink *);
+static void datalink_NewState(struct datalink *, unsigned);
+static char *datalink_NextName(struct datalink *);
+
+static void
+datalink_OpenTimeout(void *v)
+{
+ struct datalink *dl = (struct datalink *)v;
+
+ timer_Stop(&dl->dial.timer);
+ if (dl->state == DATALINK_OPENING)
+ log_Printf(LogCHAT, "%s: Redial timer expired.\n", dl->name);
+}
+
+static int
+datalink_StartDialTimer(struct datalink *dl, int Timeout)
+{
+ int result = Timeout;
+
+ timer_Stop(&dl->dial.timer);
+ if (Timeout < 0)
+ result = (random() % DIAL_TIMEOUT) + 1;
+ dl->dial.timer.load = result ? result * SECTICKS : 1;
+ dl->dial.timer.func = datalink_OpenTimeout;
+ dl->dial.timer.name = "dial";
+ dl->dial.timer.arg = dl;
+ timer_Start(&dl->dial.timer);
+ if (dl->state == DATALINK_OPENING)
+ log_Printf(LogPHASE, "%s: Enter pause (%d) for redialing.\n",
+ dl->name, result);
+ return result;
+}
+
+static void
+datalink_HangupDone(struct datalink *dl)
+{
+ if (dl->physical->type == PHYS_DEDICATED && !dl->bundle->CleaningUp &&
+ dl->physical->fd != -1) {
+ /* Don't close our device if the link is dedicated */
+ datalink_LoginDone(dl);
+ return;
+ }
+
+ chat_Finish(&dl->chat);
+ physical_Close(dl->physical);
+ dl->phone.chosen = "N/A";
+
+ if (dl->cbcp.required) {
+ log_Printf(LogPHASE, "Call peer back on %s\n", dl->cbcp.fsm.phone);
+ dl->cfg.callback.opmask = 0;
+ strncpy(dl->cfg.phone.list, dl->cbcp.fsm.phone,
+ sizeof dl->cfg.phone.list - 1);
+ dl->cfg.phone.list[sizeof dl->cfg.phone.list - 1] = '\0';
+ dl->phone.alt = dl->phone.next = NULL;
+ dl->reconnect_tries = dl->cfg.reconnect.max;
+ dl->dial.tries = dl->cfg.dial.max;
+ dl->dial.incs = 0;
+ dl->script.run = 1;
+ dl->script.packetmode = 1;
+ if (!physical_SetMode(dl->physical, PHYS_BACKGROUND))
+ log_Printf(LogERROR, "Oops - can't change mode to BACKGROUND (gulp) !\n");
+ bundle_LinksRemoved(dl->bundle);
+ /* if dial.timeout is < 0 (random), we don't override fsm.delay */
+ if (dl->cbcp.fsm.delay < dl->cfg.dial.timeout)
+ dl->cbcp.fsm.delay = dl->cfg.dial.timeout;
+ datalink_StartDialTimer(dl, dl->cbcp.fsm.delay);
+ cbcp_Down(&dl->cbcp);
+ datalink_NewState(dl, DATALINK_OPENING);
+ if (bundle_Phase(dl->bundle) == PHASE_DEAD ||
+ bundle_Phase(dl->bundle) == PHASE_TERMINATE)
+ bundle_NewPhase(dl->bundle, PHASE_ESTABLISH);
+ } else if (dl->bundle->CleaningUp ||
+ (dl->physical->type == PHYS_DIRECT) ||
+ ((!dl->dial.tries || (dl->dial.tries < 0 && !dl->reconnect_tries)) &&
+ !(dl->physical->type & (PHYS_DDIAL|PHYS_DEDICATED)))) {
+ datalink_NewState(dl, DATALINK_CLOSED);
+ dl->dial.tries = -1;
+ dl->dial.incs = 0;
+ dl->reconnect_tries = 0;
+ bundle_LinkClosed(dl->bundle, dl);
+ if (!dl->bundle->CleaningUp &&
+ !(dl->physical->type & (PHYS_DIRECT|PHYS_BACKGROUND|PHYS_FOREGROUND)))
+ datalink_StartDialTimer(dl, datalink_GetDialTimeout(dl));
+ } else {
+ datalink_NewState(dl, DATALINK_OPENING);
+ if (bundle_Phase(dl->bundle) == PHASE_DEAD ||
+ bundle_Phase(dl->bundle) == PHASE_TERMINATE)
+ bundle_NewPhase(dl->bundle, PHASE_ESTABLISH);
+ if (dl->dial.tries < 0) {
+ datalink_StartDialTimer(dl, dl->cfg.reconnect.timeout);
+ dl->dial.tries = dl->cfg.dial.max;
+ dl->dial.incs = 0;
+ dl->reconnect_tries--;
+ log_Printf(LogCHAT, "%s: Reconnect try %d of %d\n",
+ dl->name, dl->cfg.reconnect.max - dl->reconnect_tries,
+ dl->cfg.reconnect.max);
+ bundle_Notify(dl->bundle, EX_RECONNECT);
+ } else {
+ if (dl->phone.next == NULL)
+ datalink_StartDialTimer(dl, datalink_GetDialTimeout(dl));
+ else
+ datalink_StartDialTimer(dl, dl->cfg.dial.next_timeout);
+ bundle_Notify(dl->bundle, EX_REDIAL);
+ }
+ }
+}
+
+const char *
+datalink_ChoosePhoneNumber(struct datalink *dl)
+{
+ char *phone;
+
+ if (dl->phone.alt == NULL) {
+ if (dl->phone.next == NULL) {
+ strncpy(dl->phone.list, dl->cfg.phone.list, sizeof dl->phone.list - 1);
+ dl->phone.list[sizeof dl->phone.list - 1] = '\0';
+ if (*dl->phone.list == '\0')
+ return "";
+ dl->phone.next = dl->phone.list;
+ }
+ dl->phone.alt = strsep(&dl->phone.next, ":");
+ }
+ phone = strsep(&dl->phone.alt, "|");
+ dl->phone.chosen = *phone ? phone : "[NONE]";
+ if (*phone)
+ log_Printf(LogCHAT, "Phone: %s\n", phone);
+ return phone;
+}
+
+static void
+datalink_LoginDone(struct datalink *dl)
+{
+ chat_Finish(&dl->chat);
+
+ if (!dl->script.packetmode) {
+ dl->dial.tries = -1;
+ dl->dial.incs = 0;
+ datalink_NewState(dl, DATALINK_READY);
+ } else if (!physical_Raw(dl->physical)) {
+ dl->dial.tries = 0;
+ log_Printf(LogWARN, "datalink_LoginDone: Not connected.\n");
+ if (dl->script.run) {
+ datalink_NewState(dl, DATALINK_LOGOUT);
+ if (!chat_Setup(&dl->chat, dl->cfg.script.logout, NULL))
+ log_Printf(LogWARN, "Invalid logout script\n");
+ } else {
+ physical_StopDeviceTimer(dl->physical);
+ if (dl->physical->type == PHYS_DEDICATED)
+ /* force a redial timeout */
+ physical_Close(dl->physical);
+ datalink_HangupDone(dl);
+ }
+ } else {
+ dl->dial.tries = -1;
+ dl->dial.incs = 0;
+
+ hdlc_Init(&dl->physical->hdlc, &dl->physical->link.lcp);
+ async_Setup(&dl->physical->async);
+
+ lcp_Setup(&dl->physical->link.lcp, dl->state == DATALINK_READY ?
+ 0 : dl->physical->link.lcp.cfg.openmode);
+ ccp_Setup(&dl->physical->link.ccp);
+
+ datalink_NewState(dl, DATALINK_LCP);
+ fsm_Up(&dl->physical->link.lcp.fsm);
+ fsm_Open(&dl->physical->link.lcp.fsm);
+ }
+}
+
+static int
+datalink_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e,
+ int *n)
+{
+ struct datalink *dl = descriptor2datalink(d);
+ int result;
+
+ result = 0;
+ switch (dl->state) {
+ case DATALINK_CLOSED:
+ if ((dl->physical->type & (PHYS_DIRECT|PHYS_DEDICATED|PHYS_BACKGROUND|
+ PHYS_FOREGROUND|PHYS_DDIAL)) &&
+ !dl->bundle->CleaningUp)
+ /*
+ * Our first time in - DEDICATED & DDIAL never come down, and
+ * DIRECT, FOREGROUND & BACKGROUND get deleted when they enter
+ * DATALINK_CLOSED. Go to DATALINK_OPENING via datalink_Up()
+ * and fall through.
+ */
+ datalink_Up(dl, 1, 1);
+ else
+ break;
+ /* FALLTHROUGH */
+
+ case DATALINK_OPENING:
+ if (dl->dial.timer.state != TIMER_RUNNING) {
+ if (--dl->dial.tries < 0)
+ dl->dial.tries = 0;
+ if (physical_Open(dl->physical) >= 0) {
+ log_WritePrompts(dl, "%s: Entering terminal mode on %s\r\n"
+ "Type `~?' for help\r\n", dl->name,
+ dl->physical->name.full);
+ if (dl->script.run) {
+ datalink_NewState(dl, DATALINK_DIAL);
+ if (!chat_Setup(&dl->chat, dl->cfg.script.dial,
+ *dl->cfg.script.dial ?
+ datalink_ChoosePhoneNumber(dl) : ""))
+ log_Printf(LogWARN, "Invalid dial script\n");
+ if (!(dl->physical->type & (PHYS_DDIAL|PHYS_DEDICATED)) &&
+ dl->cfg.dial.max)
+ log_Printf(LogCHAT, "%s: Dial attempt %u of %d\n",
+ dl->name, dl->cfg.dial.max - dl->dial.tries,
+ dl->cfg.dial.max);
+ } else
+ datalink_NewState(dl, DATALINK_CARRIER);
+ return datalink_UpdateSet(d, r, w, e, n);
+ } else {
+ if (!(dl->physical->type & (PHYS_DDIAL|PHYS_DEDICATED)) &&
+ dl->cfg.dial.max)
+ log_Printf(LogCHAT, "Failed to open device (attempt %u of %d)\n",
+ dl->cfg.dial.max - dl->dial.tries, dl->cfg.dial.max);
+ else
+ log_Printf(LogCHAT, "Failed to open device\n");
+
+ if (dl->bundle->CleaningUp ||
+ (!(dl->physical->type & (PHYS_DDIAL|PHYS_DEDICATED)) &&
+ dl->cfg.dial.max && dl->dial.tries == 0)) {
+ datalink_NewState(dl, DATALINK_CLOSED);
+ dl->reconnect_tries = 0;
+ dl->dial.tries = -1;
+ log_WritePrompts(dl, "Failed to open %s\n",
+ dl->physical->name.full);
+ bundle_LinkClosed(dl->bundle, dl);
+ }
+ if (!dl->bundle->CleaningUp) {
+ int timeout;
+
+ timeout = datalink_StartDialTimer(dl, datalink_GetDialTimeout(dl));
+ bundle_Notify(dl->bundle, EX_REDIAL);
+ log_WritePrompts(dl, "Failed to open %s, pause %d seconds\n",
+ dl->physical->name.full, timeout);
+ }
+ }
+ }
+ break;
+
+ case DATALINK_CARRIER:
+ /* Wait for carrier on the device */
+ switch (physical_AwaitCarrier(dl->physical)) {
+ case CARRIER_PENDING:
+ log_Printf(LogDEBUG, "Waiting for carrier\n");
+ return 0; /* A device timer is running to wake us up again */
+
+ case CARRIER_OK:
+ if (dl->script.run) {
+ datalink_NewState(dl, DATALINK_LOGIN);
+ if (!chat_Setup(&dl->chat, dl->cfg.script.login, NULL))
+ log_Printf(LogWARN, "Invalid login script\n");
+ } else
+ datalink_LoginDone(dl);
+ return datalink_UpdateSet(d, r, w, e, n);
+
+ case CARRIER_LOST:
+ physical_Offline(dl->physical); /* Is this required ? */
+ if (dl->script.run) {
+ datalink_NewState(dl, DATALINK_HANGUP);
+ if (!chat_Setup(&dl->chat, dl->cfg.script.hangup, NULL))
+ log_Printf(LogWARN, "Invalid hangup script\n");
+ return datalink_UpdateSet(d, r, w, e, n);
+ } else {
+ datalink_HangupDone(dl);
+ return 0; /* Maybe bundle_CleanDatalinks() has something to do */
+ }
+ }
+
+ case DATALINK_HANGUP:
+ case DATALINK_DIAL:
+ case DATALINK_LOGOUT:
+ case DATALINK_LOGIN:
+ result = descriptor_UpdateSet(&dl->chat.desc, r, w, e, n);
+ switch (dl->chat.state) {
+ case CHAT_DONE:
+ /* script succeeded */
+ switch(dl->state) {
+ case DATALINK_HANGUP:
+ datalink_HangupDone(dl);
+ break;
+ case DATALINK_DIAL:
+ datalink_NewState(dl, DATALINK_CARRIER);
+ return datalink_UpdateSet(d, r, w, e, n);
+ case DATALINK_LOGOUT:
+ datalink_NewState(dl, DATALINK_HANGUP);
+ physical_Offline(dl->physical);
+ if (!chat_Setup(&dl->chat, dl->cfg.script.hangup, NULL))
+ log_Printf(LogWARN, "Invalid hangup script\n");
+ return datalink_UpdateSet(d, r, w, e, n);
+ case DATALINK_LOGIN:
+ dl->phone.alt = NULL;
+ datalink_LoginDone(dl);
+ return datalink_UpdateSet(d, r, w, e, n);
+ }
+ break;
+ case CHAT_FAILED:
+ /* Going down - script failed */
+ log_Printf(LogWARN, "Chat script failed\n");
+ switch(dl->state) {
+ case DATALINK_HANGUP:
+ datalink_HangupDone(dl);
+ break;
+ case DATALINK_DIAL:
+ case DATALINK_LOGOUT:
+ case DATALINK_LOGIN:
+ datalink_NewState(dl, DATALINK_HANGUP);
+ physical_Offline(dl->physical);
+ if (!chat_Setup(&dl->chat, dl->cfg.script.hangup, NULL))
+ log_Printf(LogWARN, "Invalid hangup script\n");
+ return datalink_UpdateSet(d, r, w, e, n);
+ }
+ break;
+ }
+ break;
+
+ case DATALINK_READY:
+ case DATALINK_LCP:
+ case DATALINK_AUTH:
+ case DATALINK_CBCP:
+ case DATALINK_OPEN:
+ result = descriptor_UpdateSet(&dl->chap.desc, r, w, e, n) +
+ descriptor_UpdateSet(&dl->physical->desc, r, w, e, n);
+ break;
+ }
+ return result;
+}
+
+int
+datalink_RemoveFromSet(struct datalink *dl, fd_set *r, fd_set *w, fd_set *e)
+{
+ return physical_RemoveFromSet(dl->physical, r, w, e);
+}
+
+static int
+datalink_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+ struct datalink *dl = descriptor2datalink(d);
+
+ switch (dl->state) {
+ case DATALINK_CLOSED:
+ case DATALINK_OPENING:
+ break;
+
+ case DATALINK_HANGUP:
+ case DATALINK_DIAL:
+ case DATALINK_LOGOUT:
+ case DATALINK_LOGIN:
+ return descriptor_IsSet(&dl->chat.desc, fdset);
+
+ case DATALINK_READY:
+ case DATALINK_LCP:
+ case DATALINK_AUTH:
+ case DATALINK_CBCP:
+ case DATALINK_OPEN:
+ return descriptor_IsSet(&dl->chap.desc, fdset) ? 1 :
+ descriptor_IsSet(&dl->physical->desc, fdset);
+ }
+ return 0;
+}
+
+static void
+datalink_Read(struct fdescriptor *d, struct bundle *bundle, const fd_set *fdset)
+{
+ struct datalink *dl = descriptor2datalink(d);
+
+ switch (dl->state) {
+ case DATALINK_CLOSED:
+ case DATALINK_OPENING:
+ break;
+
+ case DATALINK_HANGUP:
+ case DATALINK_DIAL:
+ case DATALINK_LOGOUT:
+ case DATALINK_LOGIN:
+ descriptor_Read(&dl->chat.desc, bundle, fdset);
+ break;
+
+ case DATALINK_READY:
+ case DATALINK_LCP:
+ case DATALINK_AUTH:
+ case DATALINK_CBCP:
+ case DATALINK_OPEN:
+ if (descriptor_IsSet(&dl->chap.desc, fdset))
+ descriptor_Read(&dl->chap.desc, bundle, fdset);
+ if (descriptor_IsSet(&dl->physical->desc, fdset))
+ descriptor_Read(&dl->physical->desc, bundle, fdset);
+ break;
+ }
+}
+
+static int
+datalink_Write(struct fdescriptor *d, struct bundle *bundle,
+ const fd_set *fdset)
+{
+ struct datalink *dl = descriptor2datalink(d);
+ int result = 0;
+
+ switch (dl->state) {
+ case DATALINK_CLOSED:
+ case DATALINK_OPENING:
+ break;
+
+ case DATALINK_HANGUP:
+ case DATALINK_DIAL:
+ case DATALINK_LOGOUT:
+ case DATALINK_LOGIN:
+ if ((result = descriptor_Write(&dl->chat.desc, bundle, fdset)) == -1) {
+ datalink_ComeDown(dl, CLOSE_NORMAL);
+ result = 0;
+ }
+ break;
+
+ case DATALINK_READY:
+ case DATALINK_LCP:
+ case DATALINK_AUTH:
+ case DATALINK_CBCP:
+ case DATALINK_OPEN:
+ if (descriptor_IsSet(&dl->chap.desc, fdset))
+ switch (descriptor_Write(&dl->chap.desc, bundle, fdset)) {
+ case -1:
+ datalink_ComeDown(dl, CLOSE_NORMAL);
+ break;
+ case 1:
+ result++;
+ }
+ if (descriptor_IsSet(&dl->physical->desc, fdset))
+ switch (descriptor_Write(&dl->physical->desc, bundle, fdset)) {
+ case -1:
+ datalink_ComeDown(dl, CLOSE_NORMAL);
+ break;
+ case 1:
+ result++;
+ }
+ break;
+ }
+
+ return result;
+}
+
+void
+datalink_ComeDown(struct datalink *dl, int how)
+{
+ int stayonline;
+
+ if (how == CLOSE_LCP)
+ datalink_DontHangup(dl);
+ else if (how == CLOSE_STAYDOWN)
+ datalink_StayDown(dl);
+
+ stayonline = dl->stayonline;
+ dl->stayonline = 0;
+
+ if (dl->state >= DATALINK_READY && stayonline) {
+ physical_StopDeviceTimer(dl->physical);
+ datalink_NewState(dl, DATALINK_READY);
+ } else if (dl->state != DATALINK_CLOSED && dl->state != DATALINK_HANGUP) {
+ physical_Offline(dl->physical);
+ if (dl->script.run && dl->state != DATALINK_OPENING) {
+ if (dl->state == DATALINK_LOGOUT) {
+ datalink_NewState(dl, DATALINK_HANGUP);
+ if (!chat_Setup(&dl->chat, dl->cfg.script.hangup, NULL))
+ log_Printf(LogWARN, "Invalid hangup script\n");
+ } else {
+ datalink_NewState(dl, DATALINK_LOGOUT);
+ if (!chat_Setup(&dl->chat, dl->cfg.script.logout, NULL))
+ log_Printf(LogWARN, "Invalid logout script\n");
+ }
+ } else
+ datalink_HangupDone(dl);
+ }
+}
+
+static void
+datalink_LayerStart(void *v, struct fsm *fp)
+{
+ /* The given FSM is about to start up ! */
+ struct datalink *dl = (struct datalink *)v;
+
+ if (fp->proto == PROTO_LCP)
+ (*dl->parent->LayerStart)(dl->parent->object, fp);
+}
+
+static void
+datalink_LayerUp(void *v, struct fsm *fp)
+{
+ /* The given fsm is now up */
+ struct datalink *dl = (struct datalink *)v;
+ struct lcp *lcp = &dl->physical->link.lcp;
+
+ if (fp->proto == PROTO_LCP) {
+ datalink_GotAuthname(dl, "");
+ lcp->auth_ineed = lcp->want_auth;
+ lcp->auth_iwait = lcp->his_auth;
+ if (lcp->his_auth || lcp->want_auth) {
+ if (bundle_Phase(dl->bundle) != PHASE_NETWORK)
+ bundle_NewPhase(dl->bundle, PHASE_AUTHENTICATE);
+ log_Printf(LogPHASE, "%s: his = %s, mine = %s\n", dl->name,
+ Auth2Nam(lcp->his_auth, lcp->his_authtype),
+ Auth2Nam(lcp->want_auth, lcp->want_authtype));
+ if (lcp->his_auth == PROTO_PAP)
+ auth_StartReq(&dl->pap);
+ if (lcp->want_auth == PROTO_CHAP)
+ auth_StartReq(&dl->chap.auth);
+ } else
+ datalink_AuthOk(dl);
+ } else if (fp->proto == PROTO_CCP)
+ (*dl->parent->LayerUp)(dl->parent->object, &dl->physical->link.ccp.fsm);
+}
+
+static void
+datalink_AuthReInit(struct datalink *dl)
+{
+ auth_StopTimer(&dl->pap);
+ auth_StopTimer(&dl->chap.auth);
+ chap_ReInit(&dl->chap);
+}
+
+void
+datalink_GotAuthname(struct datalink *dl, const char *name)
+{
+ strncpy(dl->peer.authname, name, sizeof dl->peer.authname - 1);
+ dl->peer.authname[sizeof dl->peer.authname - 1] = '\0';
+}
+
+void
+datalink_NCPUp(struct datalink *dl)
+{
+ int ccpok = ccp_SetOpenMode(&dl->physical->link.ccp);
+
+ if (dl->physical->link.lcp.want_mrru && dl->physical->link.lcp.his_mrru) {
+ /* we've authenticated in multilink mode ! */
+ switch (mp_Up(&dl->bundle->ncp.mp, dl)) {
+ case MP_LINKSENT:
+ /* We've handed the link off to another ppp (well, we will soon) ! */
+ return;
+ case MP_UP:
+ /* First link in the bundle */
+ auth_Select(dl->bundle, dl->peer.authname);
+ bundle_CalculateBandwidth(dl->bundle);
+ /* FALLTHROUGH */
+ case MP_ADDED:
+ /* We're in multilink mode ! */
+ dl->physical->link.ccp.fsm.open_mode = OPEN_PASSIVE; /* override */
+ bundle_CalculateBandwidth(dl->bundle);
+ break;
+ case MP_FAILED:
+ datalink_AuthNotOk(dl);
+ return;
+ }
+ } else if (bundle_Phase(dl->bundle) == PHASE_NETWORK) {
+ log_Printf(LogPHASE, "%s: Already in NETWORK phase\n", dl->name);
+ datalink_NewState(dl, DATALINK_OPEN);
+ bundle_CalculateBandwidth(dl->bundle);
+ (*dl->parent->LayerUp)(dl->parent->object, &dl->physical->link.lcp.fsm);
+ return;
+ } else {
+ dl->bundle->ncp.mp.peer = dl->peer;
+ ncp_SetLink(&dl->bundle->ncp, &dl->physical->link);
+ auth_Select(dl->bundle, dl->peer.authname);
+ }
+
+ if (ccpok) {
+ fsm_Up(&dl->physical->link.ccp.fsm);
+ fsm_Open(&dl->physical->link.ccp.fsm);
+ }
+ datalink_NewState(dl, DATALINK_OPEN);
+ bundle_NewPhase(dl->bundle, PHASE_NETWORK);
+ (*dl->parent->LayerUp)(dl->parent->object, &dl->physical->link.lcp.fsm);
+}
+
+void
+datalink_CBCPComplete(struct datalink *dl)
+{
+ datalink_NewState(dl, DATALINK_LCP);
+ datalink_AuthReInit(dl);
+ fsm_Close(&dl->physical->link.lcp.fsm);
+}
+
+void
+datalink_CBCPFailed(struct datalink *dl)
+{
+ cbcp_Down(&dl->cbcp);
+ datalink_CBCPComplete(dl);
+}
+
+void
+datalink_AuthOk(struct datalink *dl)
+{
+ if ((dl->physical->link.lcp.his_callback.opmask &
+ CALLBACK_BIT(CALLBACK_CBCP) ||
+ dl->physical->link.lcp.want_callback.opmask &
+ CALLBACK_BIT(CALLBACK_CBCP)) &&
+ !(dl->physical->link.lcp.want_callback.opmask &
+ CALLBACK_BIT(CALLBACK_AUTH))) {
+ /* We must have agreed CBCP if AUTH isn't there any more */
+ datalink_NewState(dl, DATALINK_CBCP);
+ cbcp_Up(&dl->cbcp);
+ } else if (dl->physical->link.lcp.want_callback.opmask) {
+ /* It's not CBCP */
+ log_Printf(LogPHASE, "%s: Shutdown and await peer callback\n", dl->name);
+ datalink_NewState(dl, DATALINK_LCP);
+ datalink_AuthReInit(dl);
+ fsm_Close(&dl->physical->link.lcp.fsm);
+ } else
+ switch (dl->physical->link.lcp.his_callback.opmask) {
+ case 0:
+ datalink_NCPUp(dl);
+ break;
+
+ case CALLBACK_BIT(CALLBACK_AUTH):
+ auth_SetPhoneList(dl->peer.authname, dl->cbcp.fsm.phone,
+ sizeof dl->cbcp.fsm.phone);
+ if (*dl->cbcp.fsm.phone == '\0' || !strcmp(dl->cbcp.fsm.phone, "*")) {
+ log_Printf(LogPHASE, "%s: %s cannot be called back\n", dl->name,
+ dl->peer.authname);
+ *dl->cbcp.fsm.phone = '\0';
+ } else {
+ char *ptr = strchr(dl->cbcp.fsm.phone, ',');
+ if (ptr)
+ *ptr = '\0'; /* Call back on the first number */
+ log_Printf(LogPHASE, "%s: Calling peer back on %s\n", dl->name,
+ dl->cbcp.fsm.phone);
+ dl->cbcp.required = 1;
+ }
+ dl->cbcp.fsm.delay = 0;
+ datalink_NewState(dl, DATALINK_LCP);
+ datalink_AuthReInit(dl);
+ fsm_Close(&dl->physical->link.lcp.fsm);
+ break;
+
+ case CALLBACK_BIT(CALLBACK_E164):
+ strncpy(dl->cbcp.fsm.phone, dl->physical->link.lcp.his_callback.msg,
+ sizeof dl->cbcp.fsm.phone - 1);
+ dl->cbcp.fsm.phone[sizeof dl->cbcp.fsm.phone - 1] = '\0';
+ log_Printf(LogPHASE, "%s: Calling peer back on %s\n", dl->name,
+ dl->cbcp.fsm.phone);
+ dl->cbcp.required = 1;
+ dl->cbcp.fsm.delay = 0;
+ datalink_NewState(dl, DATALINK_LCP);
+ datalink_AuthReInit(dl);
+ fsm_Close(&dl->physical->link.lcp.fsm);
+ break;
+
+ default:
+ log_Printf(LogPHASE, "%s: Oops - Should have NAK'd peer callback !\n",
+ dl->name);
+ datalink_NewState(dl, DATALINK_LCP);
+ datalink_AuthReInit(dl);
+ fsm_Close(&dl->physical->link.lcp.fsm);
+ break;
+ }
+}
+
+void
+datalink_AuthNotOk(struct datalink *dl)
+{
+ datalink_NewState(dl, DATALINK_LCP);
+ datalink_AuthReInit(dl);
+ fsm_Close(&dl->physical->link.lcp.fsm);
+}
+
+static void
+datalink_LayerDown(void *v, struct fsm *fp)
+{
+ /* The given FSM has been told to come down */
+ struct datalink *dl = (struct datalink *)v;
+
+ if (fp->proto == PROTO_LCP) {
+ switch (dl->state) {
+ case DATALINK_OPEN:
+ peerid_Init(&dl->peer);
+ fsm2initial(&dl->physical->link.ccp.fsm);
+ datalink_NewState(dl, DATALINK_LCP); /* before parent TLD */
+ (*dl->parent->LayerDown)(dl->parent->object, fp);
+ /* FALLTHROUGH (just in case) */
+
+ case DATALINK_CBCP:
+ if (!dl->cbcp.required)
+ cbcp_Down(&dl->cbcp);
+ /* FALLTHROUGH (just in case) */
+
+ case DATALINK_AUTH:
+ timer_Stop(&dl->pap.authtimer);
+ timer_Stop(&dl->chap.auth.authtimer);
+ }
+ datalink_NewState(dl, DATALINK_LCP);
+ datalink_AuthReInit(dl);
+ }
+}
+
+static void
+datalink_LayerFinish(void *v, struct fsm *fp)
+{
+ /* The given fsm is now down */
+ struct datalink *dl = (struct datalink *)v;
+
+ if (fp->proto == PROTO_LCP) {
+ fsm2initial(fp);
+ (*dl->parent->LayerFinish)(dl->parent->object, fp);
+ datalink_ComeDown(dl, CLOSE_NORMAL);
+ } else if (fp->state == ST_CLOSED && fp->open_mode == OPEN_PASSIVE)
+ fsm_Open(fp); /* CCP goes to ST_STOPPED */
+}
+
+struct datalink *
+datalink_Create(const char *name, struct bundle *bundle, int type)
+{
+ struct datalink *dl;
+
+ dl = (struct datalink *)malloc(sizeof(struct datalink));
+ if (dl == NULL)
+ return dl;
+
+ dl->desc.type = DATALINK_DESCRIPTOR;
+ dl->desc.UpdateSet = datalink_UpdateSet;
+ dl->desc.IsSet = datalink_IsSet;
+ dl->desc.Read = datalink_Read;
+ dl->desc.Write = datalink_Write;
+
+ dl->state = DATALINK_CLOSED;
+
+ *dl->cfg.script.dial = '\0';
+ *dl->cfg.script.login = '\0';
+ *dl->cfg.script.logout = '\0';
+ *dl->cfg.script.hangup = '\0';
+ *dl->cfg.phone.list = '\0';
+ *dl->phone.list = '\0';
+ dl->phone.next = NULL;
+ dl->phone.alt = NULL;
+ dl->phone.chosen = "N/A";
+ dl->stayonline = 0;
+ dl->script.run = 1;
+ dl->script.packetmode = 1;
+ mp_linkInit(&dl->mp);
+
+ dl->bundle = bundle;
+ dl->next = NULL;
+
+ memset(&dl->dial.timer, '\0', sizeof dl->dial.timer);
+
+ dl->dial.tries = 0;
+ dl->cfg.dial.max = 1;
+ dl->cfg.dial.next_timeout = DIAL_NEXT_TIMEOUT;
+ dl->cfg.dial.timeout = DIAL_TIMEOUT;
+ dl->cfg.dial.inc = 0;
+ dl->cfg.dial.maxinc = 10;
+
+ dl->reconnect_tries = 0;
+ dl->cfg.reconnect.max = 0;
+ dl->cfg.reconnect.timeout = RECONNECT_TIMEOUT;
+
+ dl->cfg.callback.opmask = 0;
+ dl->cfg.cbcp.delay = 0;
+ *dl->cfg.cbcp.phone = '\0';
+ dl->cfg.cbcp.fsmretry = DEF_FSMRETRY;
+
+ dl->name = strdup(name);
+ peerid_Init(&dl->peer);
+ dl->parent = &bundle->fsm;
+ dl->fsmp.LayerStart = datalink_LayerStart;
+ dl->fsmp.LayerUp = datalink_LayerUp;
+ dl->fsmp.LayerDown = datalink_LayerDown;
+ dl->fsmp.LayerFinish = datalink_LayerFinish;
+ dl->fsmp.object = dl;
+
+ if ((dl->physical = physical_Create(dl, type)) == NULL) {
+ free(dl->name);
+ free(dl);
+ return NULL;
+ }
+
+ pap_Init(&dl->pap, dl->physical);
+ chap_Init(&dl->chap, dl->physical);
+ cbcp_Init(&dl->cbcp, dl->physical);
+
+ memset(&dl->chat, '\0', sizeof dl->chat); /* Force buf{start,end} reset */
+ chat_Init(&dl->chat, dl->physical);
+
+ log_Printf(LogPHASE, "%s: Created in %s state\n",
+ dl->name, datalink_State(dl));
+
+ return dl;
+}
+
+struct datalink *
+datalink_Clone(struct datalink *odl, const char *name)
+{
+ struct datalink *dl;
+
+ dl = (struct datalink *)malloc(sizeof(struct datalink));
+ if (dl == NULL)
+ return dl;
+
+ dl->desc.type = DATALINK_DESCRIPTOR;
+ dl->desc.UpdateSet = datalink_UpdateSet;
+ dl->desc.IsSet = datalink_IsSet;
+ dl->desc.Read = datalink_Read;
+ dl->desc.Write = datalink_Write;
+
+ dl->state = DATALINK_CLOSED;
+
+ memcpy(&dl->cfg, &odl->cfg, sizeof dl->cfg);
+ mp_linkInit(&dl->mp);
+ *dl->phone.list = '\0';
+ dl->phone.next = NULL;
+ dl->phone.alt = NULL;
+ dl->phone.chosen = "N/A";
+ dl->bundle = odl->bundle;
+ dl->next = NULL;
+ memset(&dl->dial.timer, '\0', sizeof dl->dial.timer);
+ dl->dial.tries = 0;
+ dl->reconnect_tries = 0;
+ dl->name = strdup(name);
+ peerid_Init(&dl->peer);
+ dl->parent = odl->parent;
+ memcpy(&dl->fsmp, &odl->fsmp, sizeof dl->fsmp);
+ dl->fsmp.object = dl;
+
+ if ((dl->physical = physical_Create(dl, PHYS_INTERACTIVE)) == NULL) {
+ free(dl->name);
+ free(dl);
+ return NULL;
+ }
+ pap_Init(&dl->pap, dl->physical);
+ dl->pap.cfg = odl->pap.cfg;
+
+ chap_Init(&dl->chap, dl->physical);
+ dl->chap.auth.cfg = odl->chap.auth.cfg;
+
+ memcpy(&dl->physical->cfg, &odl->physical->cfg, sizeof dl->physical->cfg);
+ memcpy(&dl->physical->link.lcp.cfg, &odl->physical->link.lcp.cfg,
+ sizeof dl->physical->link.lcp.cfg);
+ memcpy(&dl->physical->link.ccp.cfg, &odl->physical->link.ccp.cfg,
+ sizeof dl->physical->link.ccp.cfg);
+ memcpy(&dl->physical->async.cfg, &odl->physical->async.cfg,
+ sizeof dl->physical->async.cfg);
+
+ cbcp_Init(&dl->cbcp, dl->physical);
+
+ memset(&dl->chat, '\0', sizeof dl->chat); /* Force buf{start,end} reset */
+ chat_Init(&dl->chat, dl->physical);
+
+ log_Printf(LogPHASE, "%s: Cloned in %s state\n",
+ dl->name, datalink_State(dl));
+
+ return dl;
+}
+
+struct datalink *
+datalink_Destroy(struct datalink *dl)
+{
+ struct datalink *result;
+
+ if (dl->state != DATALINK_CLOSED) {
+ log_Printf(LogERROR, "Oops, destroying a datalink in state %s\n",
+ datalink_State(dl));
+ switch (dl->state) {
+ case DATALINK_HANGUP:
+ case DATALINK_DIAL:
+ case DATALINK_LOGIN:
+ chat_Finish(&dl->chat); /* Gotta blat the timers ! */
+ break;
+ }
+ }
+
+ chat_Destroy(&dl->chat);
+ timer_Stop(&dl->dial.timer);
+ result = dl->next;
+ physical_Destroy(dl->physical);
+ free(dl->name);
+ free(dl);
+
+ return result;
+}
+
+void
+datalink_Up(struct datalink *dl, int runscripts, int packetmode)
+{
+ if (!Enabled(dl->bundle, OPT_FORCE_SCRIPTS) &&
+ (dl->physical->type & (PHYS_DIRECT|PHYS_DEDICATED)))
+ /* Ignore scripts */
+ runscripts = 0;
+
+ switch (dl->state) {
+ case DATALINK_CLOSED:
+ if (bundle_Phase(dl->bundle) == PHASE_DEAD ||
+ bundle_Phase(dl->bundle) == PHASE_TERMINATE)
+ bundle_NewPhase(dl->bundle, PHASE_ESTABLISH);
+ datalink_NewState(dl, DATALINK_OPENING);
+ dl->reconnect_tries =
+ dl->physical->type == PHYS_DIRECT ? 0 : dl->cfg.reconnect.max;
+ dl->dial.tries = dl->cfg.dial.max;
+ dl->script.run = runscripts;
+ dl->script.packetmode = packetmode;
+ break;
+
+ case DATALINK_OPENING:
+ if (!dl->script.run && runscripts)
+ dl->script.run = 1;
+ /* FALLTHROUGH */
+
+ case DATALINK_DIAL:
+ case DATALINK_LOGIN:
+ case DATALINK_READY:
+ if (!dl->script.packetmode && packetmode) {
+ dl->script.packetmode = 1;
+ if (dl->state == DATALINK_READY) {
+ dl->script.run = 0;
+ datalink_NewState(dl, DATALINK_CARRIER);
+ }
+ }
+ break;
+ }
+}
+
+void
+datalink_Close(struct datalink *dl, int how)
+{
+ /* Please close */
+ switch (dl->state) {
+ case DATALINK_OPEN:
+ peerid_Init(&dl->peer);
+ fsm2initial(&dl->physical->link.ccp.fsm);
+ /* FALLTHROUGH */
+
+ case DATALINK_CBCP:
+ case DATALINK_AUTH:
+ case DATALINK_LCP:
+ datalink_AuthReInit(dl);
+ if (how == CLOSE_LCP)
+ datalink_DontHangup(dl);
+ else if (how == CLOSE_STAYDOWN)
+ datalink_StayDown(dl);
+ fsm_Close(&dl->physical->link.lcp.fsm);
+ break;
+
+ default:
+ datalink_ComeDown(dl, how);
+ }
+}
+
+void
+datalink_Down(struct datalink *dl, int how)
+{
+ /* Carrier is lost */
+ switch (dl->state) {
+ case DATALINK_OPEN:
+ peerid_Init(&dl->peer);
+ fsm2initial(&dl->physical->link.ccp.fsm);
+ /* FALLTHROUGH */
+
+ case DATALINK_CBCP:
+ case DATALINK_AUTH:
+ case DATALINK_LCP:
+ fsm2initial(&dl->physical->link.lcp.fsm);
+ if (dl->state == DATALINK_OPENING)
+ return; /* we're doing a callback... */
+ /* FALLTHROUGH */
+
+ default:
+ datalink_ComeDown(dl, how);
+ }
+}
+
+void
+datalink_StayDown(struct datalink *dl)
+{
+ dl->dial.tries = -1;
+ dl->reconnect_tries = 0;
+ dl->stayonline = 0;
+}
+
+void
+datalink_DontHangup(struct datalink *dl)
+{
+ dl->dial.tries = -1;
+ dl->reconnect_tries = 0;
+ dl->stayonline = dl->state >= DATALINK_LCP ? 1 : 0;
+}
+
+int
+datalink_Show(struct cmdargs const *arg)
+{
+ prompt_Printf(arg->prompt, "Name: %s\n", arg->cx->name);
+ prompt_Printf(arg->prompt, " State: %s\n",
+ datalink_State(arg->cx));
+ prompt_Printf(arg->prompt, " Peer name: ");
+ if (*arg->cx->peer.authname)
+ prompt_Printf(arg->prompt, "%s\n", arg->cx->peer.authname);
+ else if (arg->cx->state == DATALINK_OPEN)
+ prompt_Printf(arg->prompt, "None requested\n");
+ else
+ prompt_Printf(arg->prompt, "N/A\n");
+ prompt_Printf(arg->prompt, " Discriminator: %s\n",
+ mp_Enddisc(arg->cx->peer.enddisc.class,
+ arg->cx->peer.enddisc.address,
+ arg->cx->peer.enddisc.len));
+
+ prompt_Printf(arg->prompt, "\nDefaults:\n");
+ prompt_Printf(arg->prompt, " Phone List: %s\n",
+ arg->cx->cfg.phone.list);
+ if (arg->cx->cfg.dial.max)
+ prompt_Printf(arg->prompt, " Dial tries: %d, delay ",
+ arg->cx->cfg.dial.max);
+ else
+ prompt_Printf(arg->prompt, " Dial tries: infinite, delay ");
+ if (arg->cx->cfg.dial.next_timeout >= 0)
+ prompt_Printf(arg->prompt, "%ds/", arg->cx->cfg.dial.next_timeout);
+ else
+ prompt_Printf(arg->prompt, "random/");
+ if (arg->cx->cfg.dial.timeout >= 0)
+ prompt_Printf(arg->prompt, "%ds\n", arg->cx->cfg.dial.timeout);
+ else
+ prompt_Printf(arg->prompt, "random\n");
+ prompt_Printf(arg->prompt, " Reconnect tries: %d, delay ",
+ arg->cx->cfg.reconnect.max);
+ if (arg->cx->cfg.reconnect.timeout > 0)
+ prompt_Printf(arg->prompt, "%ds\n", arg->cx->cfg.reconnect.timeout);
+ else
+ prompt_Printf(arg->prompt, "random\n");
+ prompt_Printf(arg->prompt, " Callback %s ", arg->cx->physical->type ==
+ PHYS_DIRECT ? "accepted: " : "requested:");
+ if (!arg->cx->cfg.callback.opmask)
+ prompt_Printf(arg->prompt, "none\n");
+ else {
+ int comma = 0;
+
+ if (arg->cx->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_NONE)) {
+ prompt_Printf(arg->prompt, "none");
+ comma = 1;
+ }
+ if (arg->cx->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_AUTH)) {
+ prompt_Printf(arg->prompt, "%sauth", comma ? ", " : "");
+ comma = 1;
+ }
+ if (arg->cx->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_E164)) {
+ prompt_Printf(arg->prompt, "%sE.164", comma ? ", " : "");
+ if (arg->cx->physical->type != PHYS_DIRECT)
+ prompt_Printf(arg->prompt, " (%s)", arg->cx->cfg.callback.msg);
+ comma = 1;
+ }
+ if (arg->cx->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_CBCP)) {
+ prompt_Printf(arg->prompt, "%scbcp\n", comma ? ", " : "");
+ prompt_Printf(arg->prompt, " CBCP: delay: %ds\n",
+ arg->cx->cfg.cbcp.delay);
+ prompt_Printf(arg->prompt, " phone: ");
+ if (!strcmp(arg->cx->cfg.cbcp.phone, "*")) {
+ if (arg->cx->physical->type & PHYS_DIRECT)
+ prompt_Printf(arg->prompt, "Caller decides\n");
+ else
+ prompt_Printf(arg->prompt, "Dialback server decides\n");
+ } else
+ prompt_Printf(arg->prompt, "%s\n", arg->cx->cfg.cbcp.phone);
+ prompt_Printf(arg->prompt, " timeout: %lds\n",
+ arg->cx->cfg.cbcp.fsmretry);
+ } else
+ prompt_Printf(arg->prompt, "\n");
+ }
+
+ prompt_Printf(arg->prompt, " Dial Script: %s\n",
+ arg->cx->cfg.script.dial);
+ prompt_Printf(arg->prompt, " Login Script: %s\n",
+ arg->cx->cfg.script.login);
+ prompt_Printf(arg->prompt, " Logout Script: %s\n",
+ arg->cx->cfg.script.logout);
+ prompt_Printf(arg->prompt, " Hangup Script: %s\n",
+ arg->cx->cfg.script.hangup);
+ return 0;
+}
+
+int
+datalink_SetReconnect(struct cmdargs const *arg)
+{
+ if (arg->argc == arg->argn+2) {
+ arg->cx->cfg.reconnect.timeout = atoi(arg->argv[arg->argn]);
+ arg->cx->cfg.reconnect.max = atoi(arg->argv[arg->argn+1]);
+ return 0;
+ }
+ return -1;
+}
+
+int
+datalink_SetRedial(struct cmdargs const *arg)
+{
+ const char *sep, *osep;
+ int timeout, inc, maxinc, tries;
+
+ if (arg->argc == arg->argn+1 || arg->argc == arg->argn+2) {
+ if (strncasecmp(arg->argv[arg->argn], "random", 6) == 0 &&
+ (arg->argv[arg->argn][6] == '\0' || arg->argv[arg->argn][6] == '.')) {
+ arg->cx->cfg.dial.timeout = -1;
+ randinit();
+ } else {
+ timeout = atoi(arg->argv[arg->argn]);
+
+ if (timeout >= 0)
+ arg->cx->cfg.dial.timeout = timeout;
+ else {
+ log_Printf(LogWARN, "Invalid redial timeout\n");
+ return -1;
+ }
+ }
+
+ sep = strchr(arg->argv[arg->argn], '+');
+ if (sep) {
+ inc = atoi(++sep);
+ osep = sep;
+ if (inc >= 0)
+ arg->cx->cfg.dial.inc = inc;
+ else {
+ log_Printf(LogWARN, "Invalid timeout increment\n");
+ return -1;
+ }
+ sep = strchr(sep, '-');
+ if (sep) {
+ maxinc = atoi(++sep);
+ if (maxinc >= 0)
+ arg->cx->cfg.dial.maxinc = maxinc;
+ else {
+ log_Printf(LogWARN, "Invalid maximum timeout increments\n");
+ return -1;
+ }
+ } else {
+ /* Default timeout increment */
+ arg->cx->cfg.dial.maxinc = 10;
+ sep = osep;
+ }
+ } else {
+ /* Default timeout increment & max increment */
+ arg->cx->cfg.dial.inc = 0;
+ arg->cx->cfg.dial.maxinc = 10;
+ sep = arg->argv[arg->argn];
+ }
+
+ sep = strchr(sep, '.');
+ if (sep) {
+ if (strcasecmp(++sep, "random") == 0) {
+ arg->cx->cfg.dial.next_timeout = -1;
+ randinit();
+ } else {
+ timeout = atoi(sep);
+ if (timeout >= 0)
+ arg->cx->cfg.dial.next_timeout = timeout;
+ else {
+ log_Printf(LogWARN, "Invalid next redial timeout\n");
+ return -1;
+ }
+ }
+ } else
+ /* Default next timeout */
+ arg->cx->cfg.dial.next_timeout = DIAL_NEXT_TIMEOUT;
+
+ if (arg->argc == arg->argn+2) {
+ tries = atoi(arg->argv[arg->argn+1]);
+
+ if (tries >= 0) {
+ arg->cx->cfg.dial.max = tries;
+ } else {
+ log_Printf(LogWARN, "Invalid retry value\n");
+ return 1;
+ }
+ }
+ return 0;
+ }
+
+ return -1;
+}
+
+static const char * const states[] = {
+ "closed",
+ "opening",
+ "hangup",
+ "dial",
+ "carrier",
+ "logout",
+ "login",
+ "ready",
+ "lcp",
+ "auth",
+ "cbcp",
+ "open"
+};
+
+const char *
+datalink_State(struct datalink *dl)
+{
+ if (dl->state >= sizeof states / sizeof states[0])
+ return "unknown";
+ return states[dl->state];
+}
+
+static void
+datalink_NewState(struct datalink *dl, unsigned state)
+{
+ if (state != dl->state) {
+ if (state < sizeof states / sizeof states[0]) {
+ log_Printf(LogPHASE, "%s: %s -> %s\n", dl->name, datalink_State(dl),
+ states[state]);
+ dl->state = state;
+ } else
+ log_Printf(LogERROR, "%s: Can't enter state %d !\n", dl->name, state);
+ }
+}
+
+struct datalink *
+iov2datalink(struct bundle *bundle, struct iovec *iov, int *niov, int maxiov,
+ int fd, int *auxfd, int *nauxfd)
+{
+ struct datalink *dl, *cdl;
+ struct fsm_retry copy;
+ char *oname, *pname;
+
+ dl = (struct datalink *)iov[(*niov)++].iov_base;
+ dl->name = iov[*niov].iov_base;
+
+ if (dl->name[DATALINK_MAXNAME-1]) {
+ dl->name[DATALINK_MAXNAME-1] = '\0';
+ if (strlen(dl->name) == DATALINK_MAXNAME - 1)
+ log_Printf(LogWARN, "Datalink name truncated to \"%s\"\n", dl->name);
+ }
+
+ /* Make sure the name is unique ! */
+ oname = NULL;
+ do {
+ for (cdl = bundle->links; cdl; cdl = cdl->next)
+ if (!strcasecmp(dl->name, cdl->name)) {
+ if ((pname = datalink_NextName(dl)) == NULL) {
+ for ((*niov)--; *niov < maxiov; (*niov)++)
+ free(iov[*niov].iov_base);
+ return NULL;
+ } else if (oname)
+ free(pname);
+ else
+ oname = pname;
+ break; /* Keep renaming 'till we have no conflicts */
+ }
+ } while (cdl);
+
+ if (oname) {
+ log_Printf(LogPHASE, "Rename link %s to %s\n", oname, dl->name);
+ free(oname);
+ } else {
+ dl->name = strdup(dl->name);
+ free(iov[*niov].iov_base);
+ }
+ (*niov)++;
+
+ dl->desc.type = DATALINK_DESCRIPTOR;
+ dl->desc.UpdateSet = datalink_UpdateSet;
+ dl->desc.IsSet = datalink_IsSet;
+ dl->desc.Read = datalink_Read;
+ dl->desc.Write = datalink_Write;
+
+ mp_linkInit(&dl->mp);
+ *dl->phone.list = '\0';
+ dl->phone.next = NULL;
+ dl->phone.alt = NULL;
+ dl->phone.chosen = "N/A";
+
+ dl->bundle = bundle;
+ dl->next = NULL;
+ memset(&dl->dial.timer, '\0', sizeof dl->dial.timer);
+ dl->dial.tries = 0;
+ dl->reconnect_tries = 0;
+ dl->parent = &bundle->fsm;
+ dl->fsmp.LayerStart = datalink_LayerStart;
+ dl->fsmp.LayerUp = datalink_LayerUp;
+ dl->fsmp.LayerDown = datalink_LayerDown;
+ dl->fsmp.LayerFinish = datalink_LayerFinish;
+ dl->fsmp.object = dl;
+
+ dl->physical = iov2physical(dl, iov, niov, maxiov, fd, auxfd, nauxfd);
+
+ if (!dl->physical) {
+ free(dl->name);
+ free(dl);
+ dl = NULL;
+ } else {
+ copy = dl->pap.cfg.fsm;
+ pap_Init(&dl->pap, dl->physical);
+ dl->pap.cfg.fsm = copy;
+
+ copy = dl->chap.auth.cfg.fsm;
+ chap_Init(&dl->chap, dl->physical);
+ dl->chap.auth.cfg.fsm = copy;
+
+ cbcp_Init(&dl->cbcp, dl->physical);
+
+ memset(&dl->chat, '\0', sizeof dl->chat); /* Force buf{start,end} reset */
+ chat_Init(&dl->chat, dl->physical);
+
+ log_Printf(LogPHASE, "%s: Transferred in %s state\n",
+ dl->name, datalink_State(dl));
+ }
+
+ return dl;
+}
+
+int
+datalink2iov(struct datalink *dl, struct iovec *iov, int *niov, int maxiov,
+ int *auxfd, int *nauxfd)
+{
+ /* If `dl' is NULL, we're allocating before a Fromiov() */
+ int link_fd;
+
+ if (dl) {
+ timer_Stop(&dl->dial.timer);
+ /* The following is purely for the sake of paranoia */
+ cbcp_Down(&dl->cbcp);
+ timer_Stop(&dl->pap.authtimer);
+ timer_Stop(&dl->chap.auth.authtimer);
+ }
+
+ if (*niov >= maxiov - 1) {
+ log_Printf(LogERROR, "Toiov: No room for datalink !\n");
+ if (dl) {
+ free(dl->name);
+ free(dl);
+ }
+ return -1;
+ }
+
+ iov[*niov].iov_base = (void *)dl;
+ iov[(*niov)++].iov_len = sizeof *dl;
+ iov[*niov].iov_base = dl ? realloc(dl->name, DATALINK_MAXNAME) : NULL;
+ iov[(*niov)++].iov_len = DATALINK_MAXNAME;
+
+ link_fd = physical2iov(dl ? dl->physical : NULL, iov, niov, maxiov, auxfd,
+ nauxfd);
+
+ if (link_fd == -1 && dl) {
+ free(dl->name);
+ free(dl);
+ }
+
+ return link_fd;
+}
+
+void
+datalink_Rename(struct datalink *dl, const char *name)
+{
+ free(dl->name);
+ dl->physical->link.name = dl->name = strdup(name);
+}
+
+static char *
+datalink_NextName(struct datalink *dl)
+{
+ int f, n;
+ char *name, *oname;
+
+ n = strlen(dl->name);
+ if ((name = (char *)malloc(n+3)) == NULL) {
+ log_Printf(LogERROR, "datalink_NextName: Out of memory !\n");
+ return NULL;
+ }
+ for (f = n - 1; f >= 0; f--)
+ if (!isdigit(dl->name[f]))
+ break;
+ n = sprintf(name, "%.*s-", dl->name[f] == '-' ? f : f + 1, dl->name);
+ sprintf(name + n, "%d", atoi(dl->name + f + 1) + 1);
+ oname = dl->name;
+ dl->name = name;
+ /* our physical link name isn't updated (it probably isn't created yet) */
+ return oname;
+}
+
+int
+datalink_SetMode(struct datalink *dl, int mode)
+{
+ if (!physical_SetMode(dl->physical, mode))
+ return 0;
+ if (dl->physical->type & (PHYS_DIRECT|PHYS_DEDICATED))
+ dl->script.run = 0;
+ if (dl->physical->type == PHYS_DIRECT)
+ dl->reconnect_tries = 0;
+ if (mode & (PHYS_DDIAL|PHYS_BACKGROUND|PHYS_FOREGROUND) &&
+ dl->state <= DATALINK_READY)
+ datalink_Up(dl, 1, 1);
+ return 1;
+}
+
+int
+datalink_GetDialTimeout(struct datalink *dl)
+{
+ int result = dl->cfg.dial.timeout + dl->dial.incs * dl->cfg.dial.inc;
+
+ if (dl->dial.incs < dl->cfg.dial.maxinc)
+ dl->dial.incs++;
+
+ return result;
+}
diff --git a/usr.sbin/ppp/datalink.h b/usr.sbin/ppp/datalink.h
new file mode 100644
index 0000000..6fd7e9b
--- /dev/null
+++ b/usr.sbin/ppp/datalink.h
@@ -0,0 +1,156 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.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$
+ */
+
+#define DATALINK_CLOSED (0)
+#define DATALINK_OPENING (1)
+#define DATALINK_HANGUP (2)
+#define DATALINK_DIAL (3)
+#define DATALINK_CARRIER (4)
+#define DATALINK_LOGOUT (5)
+#define DATALINK_LOGIN (6)
+#define DATALINK_READY (7)
+#define DATALINK_LCP (8)
+#define DATALINK_AUTH (9)
+#define DATALINK_CBCP (10)
+#define DATALINK_OPEN (11)
+
+#define DATALINK_MAXNAME (20) /* Maximum datalink::name length */
+
+/* How to close the link */
+#define CLOSE_NORMAL 0
+#define CLOSE_STAYDOWN 1
+#define CLOSE_LCP 2
+
+struct iovec;
+struct prompt;
+struct physical;
+struct bundle;
+
+struct datalink {
+ struct fdescriptor desc; /* We play either a physical or a chat */
+ unsigned state; /* Our DATALINK_* state */
+ struct physical *physical; /* Our link */
+
+ struct chat chat; /* For bringing the link up & down */
+
+ unsigned stayonline : 1; /* stay online when LCP is closed ? */
+ struct {
+ unsigned run : 1; /* run scripts ? */
+ unsigned packetmode : 1; /* Go into packet mode after login ? */
+ } script;
+
+ struct {
+ struct {
+ char dial[SCRIPT_LEN];
+ char login[SCRIPT_LEN];
+ char logout[SCRIPT_LEN];
+ char hangup[SCRIPT_LEN];
+ } script; /* various chat scripts */
+ struct {
+ char list[SCRIPT_LEN]; /* Telephone Numbers */
+ } phone;
+ struct {
+ int max; /* initially try again this number of times */
+ int next_timeout; /* Redial next timeout value */
+ int inc; /* Increment timeout by `inc' each time read */
+ int maxinc; /* Maximum number of increments */
+ int timeout; /* Redial timeout value (end of phone list) */
+ } dial;
+ struct {
+ int max; /* initially try again this number of times */
+ int timeout; /* Timeout before reconnect on carrier loss */
+ } reconnect;
+ struct callback callback; /* Direction depends on physical type */
+ struct cbcpcfg cbcp; /* Direction depends on phys type & callback */
+ } cfg; /* All our config data is in here */
+
+ struct {
+ char list[SCRIPT_LEN]; /* copy of cfg.list for strsep() */
+ char *next; /* Next phone from the list */
+ char *alt; /* Alternate (after fail) phone from the list */
+ const char *chosen; /* Chosen phone number after DIAL */
+ } phone;
+
+ struct cbcp cbcp;
+
+ struct {
+ struct pppTimer timer; /* For timing between close & open */
+ int tries; /* currently try again this number of times */
+ int incs; /* # times our timeout has been incremented */
+ } dial;
+
+ unsigned reconnect_tries; /* currently try again this number of times */
+
+ char *name; /* Our name */
+
+ struct peerid peer; /* Peer identification */
+
+ struct fsm_parent fsmp; /* Our callback functions */
+ const struct fsm_parent *parent; /* Our parent */
+
+ struct authinfo pap; /* Authentication using pap */
+ struct chap chap; /* Authentication using chap */
+
+ struct mp_link mp; /* multilink data */
+
+ struct bundle *bundle; /* for the moment */
+ struct datalink *next; /* Next in the list */
+};
+
+#define descriptor2datalink(d) \
+ ((d)->type == DATALINK_DESCRIPTOR ? (struct datalink *)(d) : NULL)
+
+extern struct datalink *datalink_Create(const char *name, struct bundle *, int);
+extern struct datalink *datalink_Clone(struct datalink *, const char *);
+extern struct datalink *iov2datalink(struct bundle *, struct iovec *, int *,
+ int, int, int *, int *);
+extern int datalink2iov(struct datalink *, struct iovec *, int *, int, int *,
+ int *);
+extern struct datalink *datalink_Destroy(struct datalink *);
+extern void datalink_GotAuthname(struct datalink *, const char *);
+extern void datalink_Up(struct datalink *, int, int);
+extern void datalink_Close(struct datalink *, int);
+extern void datalink_Down(struct datalink *, int);
+extern void datalink_StayDown(struct datalink *);
+extern void datalink_DontHangup(struct datalink *);
+extern void datalink_AuthOk(struct datalink *);
+extern void datalink_AuthNotOk(struct datalink *);
+extern void datalink_NCPUp(struct datalink *);
+extern void datalink_CBCPComplete(struct datalink *);
+extern void datalink_CBCPFailed(struct datalink *);
+extern int datalink_Show(struct cmdargs const *);
+extern int datalink_SetRedial(struct cmdargs const *);
+extern int datalink_SetReconnect(struct cmdargs const *);
+extern const char *datalink_State(struct datalink *);
+extern void datalink_Rename(struct datalink *, const char *);
+extern int datalink_RemoveFromSet(struct datalink *, fd_set *, fd_set *,
+ fd_set *);
+extern int datalink_SetMode(struct datalink *, int);
+extern int datalink_GetDialTimeout(struct datalink *);
+extern const char *datalink_ChoosePhoneNumber(struct datalink *);
+extern void datalink_ComeDown(struct datalink *, int);
diff --git a/usr.sbin/ppp/deflate.c b/usr.sbin/ppp/deflate.c
new file mode 100644
index 0000000..57c9004
--- /dev/null
+++ b/usr.sbin/ppp/deflate.c
@@ -0,0 +1,601 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.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/types.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <zlib.h>
+
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "ccp.h"
+#include "deflate.h"
+
+/* Our state */
+struct deflate_state {
+ u_short seqno;
+ int uncomp_rec;
+ int winsize;
+ z_stream cx;
+};
+
+static char garbage[10];
+static u_char EMPTY_BLOCK[4] = { 0x00, 0x00, 0xff, 0xff };
+
+#define DEFLATE_CHUNK_LEN (1536 - sizeof(struct mbuf))
+
+static int
+DeflateResetOutput(void *v)
+{
+ struct deflate_state *state = (struct deflate_state *)v;
+
+ state->seqno = 0;
+ state->uncomp_rec = 0;
+ deflateReset(&state->cx);
+ log_Printf(LogCCP, "Deflate: Output channel reset\n");
+
+ return 1; /* Ask FSM to ACK */
+}
+
+static struct mbuf *
+DeflateOutput(void *v, struct ccp *ccp, struct link *l __unused,
+ int pri __unused, u_short *proto, struct mbuf *mp)
+{
+ struct deflate_state *state = (struct deflate_state *)v;
+ u_char *wp, *rp;
+ int olen, ilen, len, res, flush;
+ struct mbuf *mo_head, *mo, *mi_head, *mi;
+
+ ilen = m_length(mp);
+ log_Printf(LogDEBUG, "DeflateOutput: Proto %02x (%d bytes)\n", *proto, ilen);
+ log_DumpBp(LogDEBUG, "DeflateOutput: Compress packet:", mp);
+
+ /* Stuff the protocol in front of the input */
+ mi_head = mi = m_get(2, MB_CCPOUT);
+ mi->m_next = mp;
+ rp = MBUF_CTOP(mi);
+ if (*proto < 0x100) { /* Compress the protocol */
+ rp[0] = *proto & 0377;
+ mi->m_len = 1;
+ } else { /* Don't compress the protocol */
+ rp[0] = *proto >> 8;
+ rp[1] = *proto & 0377;
+ mi->m_len = 2;
+ }
+
+ /* Allocate the initial output mbuf */
+ mo_head = mo = m_get(DEFLATE_CHUNK_LEN, MB_CCPOUT);
+ mo->m_len = 2;
+ wp = MBUF_CTOP(mo);
+ *wp++ = state->seqno >> 8;
+ *wp++ = state->seqno & 0377;
+ log_Printf(LogDEBUG, "DeflateOutput: Seq %d\n", state->seqno);
+ state->seqno++;
+
+ /* Set up the deflation context */
+ state->cx.next_out = wp;
+ state->cx.avail_out = DEFLATE_CHUNK_LEN - 2;
+ state->cx.next_in = MBUF_CTOP(mi);
+ state->cx.avail_in = mi->m_len;
+ flush = Z_NO_FLUSH;
+
+ olen = 0;
+ while (1) {
+ if ((res = deflate(&state->cx, flush)) != Z_OK) {
+ if (res == Z_STREAM_END)
+ break; /* Done */
+ log_Printf(LogWARN, "DeflateOutput: deflate returned %d (%s)\n",
+ res, state->cx.msg ? state->cx.msg : "");
+ m_freem(mo_head);
+ m_free(mi_head);
+ state->seqno--;
+ return mp; /* Our dictionary's probably dead now :-( */
+ }
+
+ if (flush == Z_SYNC_FLUSH && state->cx.avail_out != 0)
+ break;
+
+ if (state->cx.avail_in == 0 && mi->m_next != NULL) {
+ mi = mi->m_next;
+ state->cx.next_in = MBUF_CTOP(mi);
+ state->cx.avail_in = mi->m_len;
+ if (mi->m_next == NULL)
+ flush = Z_SYNC_FLUSH;
+ }
+
+ if (state->cx.avail_out == 0) {
+ mo->m_next = m_get(DEFLATE_CHUNK_LEN, MB_CCPOUT);
+ olen += (mo->m_len = DEFLATE_CHUNK_LEN);
+ mo = mo->m_next;
+ mo->m_len = 0;
+ state->cx.next_out = MBUF_CTOP(mo);
+ state->cx.avail_out = DEFLATE_CHUNK_LEN;
+ }
+ }
+
+ olen += (mo->m_len = DEFLATE_CHUNK_LEN - state->cx.avail_out);
+ olen -= 4; /* exclude the trailing EMPTY_BLOCK */
+
+ /*
+ * If the output packet (including seqno and excluding the EMPTY_BLOCK)
+ * got bigger, send the original.
+ */
+ if (olen >= ilen) {
+ m_freem(mo_head);
+ m_free(mi_head);
+ log_Printf(LogDEBUG, "DeflateOutput: %d => %d: Uncompressible (0x%04x)\n",
+ ilen, olen, *proto);
+ ccp->uncompout += ilen;
+ ccp->compout += ilen; /* We measure this stuff too */
+ return mp;
+ }
+
+ m_freem(mi_head);
+
+ /*
+ * Lose the last four bytes of our output.
+ * XXX: We should probably assert that these are the same as the
+ * contents of EMPTY_BLOCK.
+ */
+ mo = mo_head;
+ for (len = mo->m_len; len < olen; mo = mo->m_next, len += mo->m_len)
+ ;
+ mo->m_len -= len - olen;
+ if (mo->m_next != NULL) {
+ m_freem(mo->m_next);
+ mo->m_next = NULL;
+ }
+
+ ccp->uncompout += ilen;
+ ccp->compout += olen;
+
+ log_Printf(LogDEBUG, "DeflateOutput: %d => %d bytes, proto 0x%04x\n",
+ ilen, olen, *proto);
+
+ *proto = ccp_Proto(ccp);
+ return mo_head;
+}
+
+static void
+DeflateResetInput(void *v)
+{
+ struct deflate_state *state = (struct deflate_state *)v;
+
+ state->seqno = 0;
+ state->uncomp_rec = 0;
+ inflateReset(&state->cx);
+ log_Printf(LogCCP, "Deflate: Input channel reset\n");
+}
+
+static struct mbuf *
+DeflateInput(void *v, struct ccp *ccp, u_short *proto, struct mbuf *mi)
+{
+ struct deflate_state *state = (struct deflate_state *)v;
+ struct mbuf *mo, *mo_head, *mi_head;
+ u_char *wp;
+ int ilen, olen;
+ int seq, flush, res, first;
+ u_char hdr[2];
+
+ log_DumpBp(LogDEBUG, "DeflateInput: Decompress packet:", mi);
+ mi_head = mi = mbuf_Read(mi, hdr, 2);
+ ilen = 2;
+
+ /* Check the sequence number. */
+ seq = (hdr[0] << 8) + hdr[1];
+ log_Printf(LogDEBUG, "DeflateInput: Seq %d\n", seq);
+ if (seq != state->seqno) {
+ if (seq <= state->uncomp_rec)
+ /*
+ * So the peer's started at zero again - fine ! If we're wrong,
+ * inflate() will fail. This is better than getting into a loop
+ * trying to get a ResetReq to a busy sender.
+ */
+ state->seqno = seq;
+ else {
+ log_Printf(LogCCP, "DeflateInput: Seq error: Got %d, expected %d\n",
+ seq, state->seqno);
+ m_freem(mi_head);
+ ccp_SendResetReq(&ccp->fsm);
+ return NULL;
+ }
+ }
+ state->seqno++;
+ state->uncomp_rec = 0;
+
+ /* Allocate an output mbuf */
+ mo_head = mo = m_get(DEFLATE_CHUNK_LEN, MB_CCPIN);
+
+ /* Our proto starts with 0 if it's compressed */
+ wp = MBUF_CTOP(mo);
+ wp[0] = '\0';
+
+ /*
+ * We set avail_out to 1 initially so we can look at the first
+ * byte of the output and decide whether we have a compressed
+ * proto field.
+ */
+ state->cx.next_in = MBUF_CTOP(mi);
+ state->cx.avail_in = mi->m_len;
+ state->cx.next_out = wp + 1;
+ state->cx.avail_out = 1;
+ ilen += mi->m_len;
+
+ flush = mi->m_next ? Z_NO_FLUSH : Z_SYNC_FLUSH;
+ first = 1;
+ olen = 0;
+
+ while (1) {
+ if ((res = inflate(&state->cx, flush)) != Z_OK) {
+ if (res == Z_STREAM_END)
+ break; /* Done */
+ log_Printf(LogCCP, "DeflateInput: inflate returned %d (%s)\n",
+ res, state->cx.msg ? state->cx.msg : "");
+ m_freem(mo_head);
+ m_freem(mi);
+ ccp_SendResetReq(&ccp->fsm);
+ return NULL;
+ }
+
+ if (flush == Z_SYNC_FLUSH && state->cx.avail_out != 0)
+ break;
+
+ if (state->cx.avail_in == 0 && mi && (mi = m_free(mi)) != NULL) {
+ /* underflow */
+ state->cx.next_in = MBUF_CTOP(mi);
+ ilen += (state->cx.avail_in = mi->m_len);
+ if (mi->m_next == NULL)
+ flush = Z_SYNC_FLUSH;
+ }
+
+ if (state->cx.avail_out == 0) {
+ /* overflow */
+ if (first) {
+ if (!(wp[1] & 1)) {
+ /* 2 byte proto, shuffle it back in output */
+ wp[0] = wp[1];
+ state->cx.next_out--;
+ state->cx.avail_out = DEFLATE_CHUNK_LEN-1;
+ } else
+ state->cx.avail_out = DEFLATE_CHUNK_LEN-2;
+ first = 0;
+ } else {
+ olen += (mo->m_len = DEFLATE_CHUNK_LEN);
+ mo->m_next = m_get(DEFLATE_CHUNK_LEN, MB_CCPIN);
+ mo = mo->m_next;
+ state->cx.next_out = MBUF_CTOP(mo);
+ state->cx.avail_out = DEFLATE_CHUNK_LEN;
+ }
+ }
+ }
+
+ if (mi != NULL)
+ m_freem(mi);
+
+ if (first) {
+ log_Printf(LogCCP, "DeflateInput: Length error\n");
+ m_freem(mo_head);
+ ccp_SendResetReq(&ccp->fsm);
+ return NULL;
+ }
+
+ olen += (mo->m_len = DEFLATE_CHUNK_LEN - state->cx.avail_out);
+
+ *proto = ((u_short)wp[0] << 8) | wp[1];
+ mo_head->m_offset += 2;
+ mo_head->m_len -= 2;
+ olen -= 2;
+
+ ccp->compin += ilen;
+ ccp->uncompin += olen;
+
+ log_Printf(LogDEBUG, "DeflateInput: %d => %d bytes, proto 0x%04x\n",
+ ilen, olen, *proto);
+
+ /*
+ * Simulate an EMPTY_BLOCK so that our dictionary stays in sync.
+ * The peer will have silently removed this!
+ */
+ state->cx.next_out = garbage;
+ state->cx.avail_out = sizeof garbage;
+ state->cx.next_in = EMPTY_BLOCK;
+ state->cx.avail_in = sizeof EMPTY_BLOCK;
+ inflate(&state->cx, Z_SYNC_FLUSH);
+
+ return mo_head;
+}
+
+static void
+DeflateDictSetup(void *v, struct ccp *ccp, u_short proto, struct mbuf *mi)
+{
+ struct deflate_state *state = (struct deflate_state *)v;
+ int res, flush, expect_error;
+ u_char *rp;
+ struct mbuf *mi_head;
+ short len;
+
+ log_Printf(LogDEBUG, "DeflateDictSetup: Got seq %d\n", state->seqno);
+
+ /*
+ * Stuff an ``uncompressed data'' block header followed by the
+ * protocol in front of the input
+ */
+ mi_head = m_get(7, MB_CCPOUT);
+ mi_head->m_next = mi;
+ len = m_length(mi);
+ mi = mi_head;
+ rp = MBUF_CTOP(mi);
+ if (proto < 0x100) { /* Compress the protocol */
+ rp[5] = proto & 0377;
+ mi->m_len = 6;
+ len++;
+ } else { /* Don't compress the protocol */
+ rp[5] = proto >> 8;
+ rp[6] = proto & 0377;
+ mi->m_len = 7;
+ len += 2;
+ }
+ rp[0] = 0x80; /* BITS: 100xxxxx */
+ rp[1] = len & 0377; /* The length */
+ rp[2] = len >> 8;
+ rp[3] = (~len) & 0377; /* One's compliment of the length */
+ rp[4] = (~len) >> 8;
+
+ state->cx.next_in = rp;
+ state->cx.avail_in = mi->m_len;
+ state->cx.next_out = garbage;
+ state->cx.avail_out = sizeof garbage;
+ flush = Z_NO_FLUSH;
+ expect_error = 0;
+
+ while (1) {
+ if ((res = inflate(&state->cx, flush)) != Z_OK) {
+ if (res == Z_STREAM_END)
+ break; /* Done */
+ if (expect_error && res == Z_BUF_ERROR)
+ break;
+ log_Printf(LogCCP, "DeflateDictSetup: inflate returned %d (%s)\n",
+ res, state->cx.msg ? state->cx.msg : "");
+ log_Printf(LogCCP, "DeflateDictSetup: avail_in %d, avail_out %d\n",
+ state->cx.avail_in, state->cx.avail_out);
+ ccp_SendResetReq(&ccp->fsm);
+ m_free(mi_head); /* lose our allocated ``head'' buf */
+ return;
+ }
+
+ if (flush == Z_SYNC_FLUSH && state->cx.avail_out != 0)
+ break;
+
+ if (state->cx.avail_in == 0 && mi && (mi = mi->m_next) != NULL) {
+ /* underflow */
+ state->cx.next_in = MBUF_CTOP(mi);
+ state->cx.avail_in = mi->m_len;
+ if (mi->m_next == NULL)
+ flush = Z_SYNC_FLUSH;
+ }
+
+ if (state->cx.avail_out == 0) {
+ if (state->cx.avail_in == 0)
+ /*
+ * This seems to be a bug in libz ! If inflate() finished
+ * with 0 avail_in and 0 avail_out *and* this is the end of
+ * our input *and* inflate() *has* actually written all the
+ * output it's going to, it *doesn't* return Z_STREAM_END !
+ * When we subsequently call it with no more input, it gives
+ * us Z_BUF_ERROR :-( It seems pretty safe to ignore this
+ * error (the dictionary seems to stay in sync). In the worst
+ * case, we'll drop the next compressed packet and do a
+ * CcpReset() then.
+ */
+ expect_error = 1;
+ /* overflow */
+ state->cx.next_out = garbage;
+ state->cx.avail_out = sizeof garbage;
+ }
+ }
+
+ ccp->compin += len;
+ ccp->uncompin += len;
+
+ state->seqno++;
+ state->uncomp_rec++;
+ m_free(mi_head); /* lose our allocated ``head'' buf */
+}
+
+static const char *
+DeflateDispOpts(struct fsm_opt *o)
+{
+ static char disp[7]; /* Must be used immediately */
+
+ sprintf(disp, "win %d", (o->data[0]>>4) + 8);
+ return disp;
+}
+
+static void
+DeflateInitOptsOutput(struct bundle *bundle __unused, struct fsm_opt *o,
+ const struct ccp_config *cfg)
+{
+ o->hdr.len = 4;
+ o->data[0] = ((cfg->deflate.out.winsize - 8) << 4) + 8;
+ o->data[1] = '\0';
+}
+
+static int
+DeflateSetOptsOutput(struct bundle *bundle __unused, struct fsm_opt *o,
+ const struct ccp_config *cfg __unused)
+{
+ if (o->hdr.len != 4 || (o->data[0] & 15) != 8 || o->data[1] != '\0')
+ return MODE_REJ;
+
+ if ((o->data[0] >> 4) + 8 > 15) {
+ o->data[0] = ((15 - 8) << 4) + 8;
+ return MODE_NAK;
+ }
+
+ return MODE_ACK;
+}
+
+static int
+DeflateSetOptsInput(struct bundle *bundle __unused, struct fsm_opt *o,
+ const struct ccp_config *cfg)
+{
+ int want;
+
+ if (o->hdr.len != 4 || (o->data[0] & 15) != 8 || o->data[1] != '\0')
+ return MODE_REJ;
+
+ want = (o->data[0] >> 4) + 8;
+ if (cfg->deflate.in.winsize == 0) {
+ if (want < 8 || want > 15) {
+ o->data[0] = ((15 - 8) << 4) + 8;
+ }
+ } else if (want != cfg->deflate.in.winsize) {
+ o->data[0] = ((cfg->deflate.in.winsize - 8) << 4) + 8;
+ return MODE_NAK;
+ }
+
+ return MODE_ACK;
+}
+
+static void *
+DeflateInitInput(struct bundle *bundle __unused, struct fsm_opt *o)
+{
+ struct deflate_state *state;
+
+ state = (struct deflate_state *)malloc(sizeof(struct deflate_state));
+ if (state != NULL) {
+ state->winsize = (o->data[0] >> 4) + 8;
+ state->cx.zalloc = NULL;
+ state->cx.opaque = NULL;
+ state->cx.zfree = NULL;
+ state->cx.next_out = NULL;
+ if (inflateInit2(&state->cx, -state->winsize) == Z_OK)
+ DeflateResetInput(state);
+ else {
+ free(state);
+ state = NULL;
+ }
+ }
+
+ return state;
+}
+
+static void *
+DeflateInitOutput(struct bundle *bundle __unused, struct fsm_opt *o)
+{
+ struct deflate_state *state;
+
+ state = (struct deflate_state *)malloc(sizeof(struct deflate_state));
+ if (state != NULL) {
+ state->winsize = (o->data[0] >> 4) + 8;
+ state->cx.zalloc = NULL;
+ state->cx.opaque = NULL;
+ state->cx.zfree = NULL;
+ state->cx.next_in = NULL;
+ if (deflateInit2(&state->cx, Z_DEFAULT_COMPRESSION, 8,
+ -state->winsize, 8, Z_DEFAULT_STRATEGY) == Z_OK)
+ DeflateResetOutput(state);
+ else {
+ free(state);
+ state = NULL;
+ }
+ }
+
+ return state;
+}
+
+static void
+DeflateTermInput(void *v)
+{
+ struct deflate_state *state = (struct deflate_state *)v;
+
+ inflateEnd(&state->cx);
+ free(state);
+}
+
+static void
+DeflateTermOutput(void *v)
+{
+ struct deflate_state *state = (struct deflate_state *)v;
+
+ deflateEnd(&state->cx);
+ free(state);
+}
+
+const struct ccp_algorithm PppdDeflateAlgorithm = {
+ TY_PPPD_DEFLATE, /* Older versions of pppd expected this ``type'' */
+ CCP_NEG_DEFLATE24,
+ DeflateDispOpts,
+ ccp_DefaultUsable,
+ ccp_DefaultRequired,
+ {
+ DeflateSetOptsInput,
+ DeflateInitInput,
+ DeflateTermInput,
+ DeflateResetInput,
+ DeflateInput,
+ DeflateDictSetup
+ },
+ {
+ 0,
+ DeflateInitOptsOutput,
+ DeflateSetOptsOutput,
+ DeflateInitOutput,
+ DeflateTermOutput,
+ DeflateResetOutput,
+ DeflateOutput
+ },
+};
+
+const struct ccp_algorithm DeflateAlgorithm = {
+ TY_DEFLATE, /* rfc 1979 */
+ CCP_NEG_DEFLATE,
+ DeflateDispOpts,
+ ccp_DefaultUsable,
+ ccp_DefaultRequired,
+ {
+ DeflateSetOptsInput,
+ DeflateInitInput,
+ DeflateTermInput,
+ DeflateResetInput,
+ DeflateInput,
+ DeflateDictSetup
+ },
+ {
+ 0,
+ DeflateInitOptsOutput,
+ DeflateSetOptsOutput,
+ DeflateInitOutput,
+ DeflateTermOutput,
+ DeflateResetOutput,
+ DeflateOutput
+ },
+};
diff --git a/usr.sbin/ppp/deflate.h b/usr.sbin/ppp/deflate.h
new file mode 100644
index 0000000..a478b24
--- /dev/null
+++ b/usr.sbin/ppp/deflate.h
@@ -0,0 +1,30 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.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$
+ */
+
+extern const struct ccp_algorithm PppdDeflateAlgorithm;
+extern const struct ccp_algorithm DeflateAlgorithm;
diff --git a/usr.sbin/ppp/defs.c b/usr.sbin/ppp/defs.c
new file mode 100644
index 0000000..856c200
--- /dev/null
+++ b/usr.sbin/ppp/defs.c
@@ -0,0 +1,441 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.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/param.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#if defined(__FreeBSD__) && !defined(NOKLDLOAD)
+#include <sys/module.h>
+#endif
+#include <termios.h>
+#ifndef __FreeBSD__
+#include <time.h>
+#endif
+#include <unistd.h>
+
+#if defined(__FreeBSD__) && !defined(NOKLDLOAD)
+#include "id.h"
+#include "log.h"
+#endif
+#include "defs.h"
+
+#define issep(c) ((c) == '\t' || (c) == ' ')
+
+#ifdef __NetBSD__
+void
+randinit()
+{
+ srandom((time(NULL)^getpid())+random());
+}
+#endif
+
+ssize_t
+fullread(int fd, void *v, size_t n)
+{
+ size_t got, total;
+
+ for (total = 0; total < n; total += got)
+ switch ((got = read(fd, (char *)v + total, n - total))) {
+ case 0:
+ return total;
+ case -1:
+ if (errno == EINTR)
+ got = 0;
+ else
+ return -1;
+ }
+ return total;
+}
+
+static struct {
+ int mode;
+ const char *name;
+} modes[] = {
+ { PHYS_INTERACTIVE, "interactive" },
+ { PHYS_AUTO, "auto" },
+ { PHYS_DIRECT, "direct" },
+ { PHYS_DEDICATED, "dedicated" },
+ { PHYS_DDIAL, "ddial" },
+ { PHYS_BACKGROUND, "background" },
+ { PHYS_FOREGROUND, "foreground" },
+ { PHYS_ALL, "*" },
+ { 0, 0 }
+};
+
+const char *
+mode2Nam(int mode)
+{
+ int m;
+
+ for (m = 0; modes[m].mode; m++)
+ if (modes[m].mode == mode)
+ return modes[m].name;
+
+ return "unknown";
+}
+
+int
+Nam2mode(const char *name)
+{
+ int m, got, len;
+
+ len = strlen(name);
+ got = -1;
+ for (m = 0; modes[m].mode; m++)
+ if (!strncasecmp(name, modes[m].name, len)) {
+ if (modes[m].name[len] == '\0')
+ return modes[m].mode;
+ if (got != -1)
+ return 0;
+ got = m;
+ }
+
+ return got == -1 ? 0 : modes[got].mode;
+}
+
+struct in_addr
+GetIpAddr(const char *cp)
+{
+ struct in_addr ipaddr;
+
+ if (!strcasecmp(cp, "default"))
+ ipaddr.s_addr = INADDR_ANY;
+ else if (inet_aton(cp, &ipaddr) == 0) {
+ const char *ptr;
+
+ /* Any illegal characters ? */
+ for (ptr = cp; *ptr != '\0'; ptr++)
+ if (!isalnum(*ptr) && strchr("-.", *ptr) == NULL)
+ break;
+
+ if (*ptr == '\0') {
+ struct hostent *hp;
+
+ hp = gethostbyname(cp);
+ if (hp && hp->h_addrtype == AF_INET)
+ memcpy(&ipaddr, hp->h_addr, hp->h_length);
+ else
+ ipaddr.s_addr = INADDR_NONE;
+ } else
+ ipaddr.s_addr = INADDR_NONE;
+ }
+
+ return ipaddr;
+}
+
+static const struct speeds {
+ unsigned nspeed;
+ speed_t speed;
+} speeds[] = {
+#ifdef B50
+ { 50, B50, },
+#endif
+#ifdef B75
+ { 75, B75, },
+#endif
+#ifdef B110
+ { 110, B110, },
+#endif
+#ifdef B134
+ { 134, B134, },
+#endif
+#ifdef B150
+ { 150, B150, },
+#endif
+#ifdef B200
+ { 200, B200, },
+#endif
+#ifdef B300
+ { 300, B300, },
+#endif
+#ifdef B600
+ { 600, B600, },
+#endif
+#ifdef B1200
+ { 1200, B1200, },
+#endif
+#ifdef B1800
+ { 1800, B1800, },
+#endif
+#ifdef B2400
+ { 2400, B2400, },
+#endif
+#ifdef B4800
+ { 4800, B4800, },
+#endif
+#ifdef B9600
+ { 9600, B9600, },
+#endif
+#ifdef B19200
+ { 19200, B19200, },
+#endif
+#ifdef B38400
+ { 38400, B38400, },
+#endif
+#ifndef _POSIX_SOURCE
+#ifdef B7200
+ { 7200, B7200, },
+#endif
+#ifdef B14400
+ { 14400, B14400, },
+#endif
+#ifdef B28800
+ { 28800, B28800, },
+#endif
+#ifdef B57600
+ { 57600, B57600, },
+#endif
+#ifdef B76800
+ { 76800, B76800, },
+#endif
+#ifdef B115200
+ { 115200, B115200, },
+#endif
+#ifdef B230400
+ { 230400, B230400, },
+#endif
+#ifdef B460800
+ { 460800, B460800, },
+#endif
+#ifdef B921600
+ { 921600, B921600, },
+#endif
+#ifdef EXTA
+ { 19200, EXTA, },
+#endif
+#ifdef EXTB
+ { 38400, EXTB, },
+#endif
+#endif /* _POSIX_SOURCE */
+ { 0, 0 }
+};
+
+unsigned
+SpeedToUnsigned(speed_t speed)
+{
+ const struct speeds *sp;
+
+ for (sp = speeds; sp->nspeed; sp++) {
+ if (sp->speed == speed) {
+ return sp->nspeed;
+ }
+ }
+ return 0;
+}
+
+speed_t
+UnsignedToSpeed(unsigned nspeed)
+{
+ const struct speeds *sp;
+
+ for (sp = speeds; sp->nspeed; sp++) {
+ if (sp->nspeed == nspeed) {
+ return sp->speed;
+ }
+ }
+ return B0;
+}
+
+char *
+findblank(char *p, int flags)
+{
+ int instring;
+
+ instring = 0;
+ while (*p) {
+ if (*p == '\\') {
+ if (flags & PARSE_REDUCE) {
+ memmove(p, p + 1, strlen(p));
+ if (!*p)
+ break;
+ } else
+ p++;
+ } else if (*p == '"') {
+ memmove(p, p + 1, strlen(p));
+ instring = !instring;
+ continue;
+ } else if (!instring && (issep(*p) ||
+ (*p == '#' && !(flags & PARSE_NOHASH))))
+ return p;
+ p++;
+ }
+
+ return instring ? NULL : p;
+}
+
+int
+MakeArgs(char *script, char **pvect, int maxargs, int flags)
+{
+ int nargs;
+
+ nargs = 0;
+ while (*script) {
+ script += strspn(script, " \t");
+ if (*script == '#' && !(flags & PARSE_NOHASH)) {
+ *script = '\0';
+ break;
+ }
+ if (*script) {
+ if (nargs >= maxargs - 1)
+ break;
+ *pvect++ = script;
+ nargs++;
+ script = findblank(script, flags);
+ if (script == NULL)
+ return -1;
+ else if (!(flags & PARSE_NOHASH) && *script == '#')
+ *script = '\0';
+ else if (*script)
+ *script++ = '\0';
+ }
+ }
+ *pvect = NULL;
+ return nargs;
+}
+
+const char *
+NumStr(long val, char *buf, size_t sz)
+{
+ static char result[23]; /* handles 64 bit numbers */
+
+ if (buf == NULL || sz == 0) {
+ buf = result;
+ sz = sizeof result;
+ }
+ snprintf(buf, sz, "<%ld>", val);
+ return buf;
+}
+
+const char *
+HexStr(long val, char *buf, size_t sz)
+{
+ static char result[21]; /* handles 64 bit numbers */
+
+ if (buf == NULL || sz == 0) {
+ buf = result;
+ sz = sizeof result;
+ }
+ snprintf(buf, sz, "<0x%lx>", val);
+ return buf;
+}
+
+const char *
+ex_desc(int ex)
+{
+ static char num[12]; /* Used immediately if returned */
+ static const char * const desc[] = {
+ "normal", "start", "sock", "modem", "dial", "dead", "done",
+ "reboot", "errdead", "hangup", "term", "nodial", "nologin",
+ "redial", "reconnect"
+ };
+
+ if (ex >= 0 && ex < (int)(sizeof desc / sizeof *desc))
+ return desc[ex];
+ snprintf(num, sizeof num, "%d", ex);
+ return num;
+}
+
+void
+SetTitle(const char *title)
+{
+ if (title == NULL)
+ setproctitle(NULL);
+ else if (title[0] == '-' && title[1] != '\0')
+ setproctitle("-%s", title + 1);
+ else
+ setproctitle("%s", title);
+}
+
+fd_set *
+mkfdset()
+{
+ return (fd_set *)malloc(howmany(getdtablesize(), NFDBITS) * sizeof (fd_mask));
+}
+
+void
+zerofdset(fd_set *s)
+{
+ memset(s, '\0', howmany(getdtablesize(), NFDBITS) * sizeof (fd_mask));
+}
+
+void
+Concatinate(char *buf, size_t sz, int argc, const char *const *argv)
+{
+ int i, n;
+ unsigned pos;
+
+ *buf = '\0';
+ for (pos = i = 0; i < argc; i++) {
+ n = snprintf(buf + pos, sz - pos, "%s%s", i ? " " : "", argv[i]);
+ if (n < 0) {
+ buf[pos] = '\0';
+ break;
+ }
+ if ((pos += n) >= sz)
+ break;
+ }
+}
+
+#if defined(__FreeBSD__) && !defined(NOKLDLOAD)
+int
+loadmodules(int how, const char *module, ...)
+{
+ int loaded = 0;
+ va_list ap;
+
+ va_start(ap, module);
+ while (module != NULL) {
+ if (modfind(module) == -1) {
+ if (ID0kldload(module) == -1) {
+ if (how == LOAD_VERBOSLY)
+ log_Printf(LogWARN, "%s: Cannot load module\n", module);
+ } else
+ loaded++;
+ }
+ module = va_arg(ap, const char *);
+ }
+ va_end(ap);
+ return loaded;
+}
+#else
+int
+loadmodules(int how __unused, const char *module __unused, ...)
+{
+ return 0;
+}
+#endif
diff --git a/usr.sbin/ppp/defs.h b/usr.sbin/ppp/defs.h
new file mode 100644
index 0000000..d320a53
--- /dev/null
+++ b/usr.sbin/ppp/defs.h
@@ -0,0 +1,142 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 the following definitions for your machine environment */
+#ifdef __FreeBSD__
+# define MODEM_LIST "/dev/cuau1\0/dev/cuau0" /* name of tty device */
+#else
+# ifdef __OpenBSD__
+# define MODEM_LIST "/dev/cua01\0/dev/cua00" /* name of tty device */
+# else
+# define MODEM_LIST "/dev/tty01\0/dev/tty00" /* name of tty device */
+# endif
+#endif
+#define NMODEMS 2
+
+#ifndef PPP_CONFDIR
+#define PPP_CONFDIR "/etc/ppp"
+#endif
+
+#define TUN_NAME "tun"
+#define TUN_PREFIX (_PATH_DEV TUN_NAME) /* /dev/tun */
+
+#define MODEM_SPEED B38400 /* tty speed */
+#define SERVER_PORT 3000 /* Base server port no. */
+#define MODEM_CTSRTS 1 /* Default (true): use CTS/RTS signals */
+#define RECONNECT_TIMEOUT 3 /* Default timer for carrier loss */
+#define DIAL_TIMEOUT 30 /* Default and Max random time to redial */
+#define DIAL_NEXT_TIMEOUT 3 /* Default Hold time to next number redial */
+#define SCRIPT_LEN 512 /* Size of login/dial/hangup scripts */
+#define LINE_LEN SCRIPT_LEN /* Size of lines */
+#define DEVICE_LEN SCRIPT_LEN /* Size of individual devices */
+#define AUTHLEN 100 /* Size of authname/authkey */
+#define CHAPDIGESTLEN 100 /* Maximum chap digest */
+#define CHAPCHALLENGELEN 48 /* Maximum chap challenge */
+#define CHAPAUTHRESPONSELEN 48 /* Maximum chap authresponse (chap81) */
+#define MAXARGS 40 /* How many args per config line */
+#define NCP_IDLE_TIMEOUT 180 /* Drop all links */
+#define CHOKED_TIMEOUT 120 /* Delete queued packets w/ blocked tun */
+
+#define MIN_LQRPERIOD 1 /* Minimum LQR frequency */
+#define DEF_LQRPERIOD 30 /* Default LQR frequency */
+#define MIN_FSMRETRY 1 /* Minimum FSM retry frequency */
+#define DEF_FSMRETRY 3 /* FSM retry frequency */
+#define DEF_FSMTRIES 5 /* Default max retries */
+#define DEF_FSMAUTHTRIES 3 /* Default max auth retries */
+#define DEF_IFQUEUE 30 /* Default interface queue size */
+
+#define CONFFILE "ppp.conf"
+#define LINKUPFILE "ppp.linkup"
+#define LINKDOWNFILE "ppp.linkdown"
+#define SECRETFILE "ppp.secret"
+
+#define EX_SIG -1
+#define EX_NORMAL 0
+#define EX_START 1
+#define EX_SOCK 2
+#define EX_MODEM 3
+#define EX_DIAL 4
+#define EX_DEAD 5
+#define EX_DONE 6
+#define EX_REBOOT 7
+#define EX_ERRDEAD 8
+#define EX_HANGUP 9
+#define EX_TERM 10
+#define EX_NODIAL 11
+#define EX_NOLOGIN 12
+/* return values for -background mode, not really exits */
+#define EX_REDIAL 13
+#define EX_RECONNECT 14
+
+/* physical::type values (OR'd in bundle::phys_type) */
+#define PHYS_NONE 0
+#define PHYS_INTERACTIVE 1 /* Manual link */
+#define PHYS_AUTO 2 /* Dial-on-demand link */
+#define PHYS_DIRECT 4 /* Incoming link, deleted when closed */
+#define PHYS_DEDICATED 8 /* Dedicated link */
+#define PHYS_DDIAL 16 /* Dial immediately, stay connected */
+#define PHYS_BACKGROUND 32 /* Dial immediately, deleted when closed */
+#define PHYS_FOREGROUND 64 /* Pseudo mode, same as background */
+#define PHYS_ALL 127
+
+/* flags passed to findblank() and MakeArgs() */
+#define PARSE_NORMAL 0
+#define PARSE_REDUCE 1
+#define PARSE_NOHASH 2
+
+/* flags passed to loadmodules */
+#define LOAD_QUIETLY 1
+#define LOAD_VERBOSLY 2
+
+#define ROUNDUP(x) ((x) ? (1 + (((x) - 1) | (sizeof(long) - 1))) : sizeof(long))
+
+#ifdef __NetBSD__
+extern void randinit(void);
+#else
+#define random arc4random
+#define randinit()
+#endif
+
+extern ssize_t fullread(int, void *, size_t);
+extern const char *mode2Nam(int);
+extern int Nam2mode(const char *);
+extern struct in_addr GetIpAddr(const char *);
+extern unsigned SpeedToUnsigned(speed_t);
+extern speed_t UnsignedToSpeed(unsigned);
+extern char *findblank(char *, int);
+extern int MakeArgs(char *, char **, int, int);
+extern const char *NumStr(long, char *, size_t);
+extern const char *HexStr(long, char *, size_t);
+extern const char *ex_desc(int);
+extern void SetTitle(const char *);
+extern fd_set *mkfdset(void);
+extern void zerofdset(fd_set *);
+extern void Concatinate(char *, size_t, int, const char *const *);
+extern int loadmodules(int, const char *, ...);
diff --git a/usr.sbin/ppp/descriptor.h b/usr.sbin/ppp/descriptor.h
new file mode 100644
index 0000000..a3c1b10
--- /dev/null
+++ b/usr.sbin/ppp/descriptor.h
@@ -0,0 +1,53 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.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$
+ */
+
+#define PHYSICAL_DESCRIPTOR (1)
+#define SERVER_DESCRIPTOR (2)
+#define PROMPT_DESCRIPTOR (3)
+#define CHAT_DESCRIPTOR (4)
+#define DATALINK_DESCRIPTOR (5)
+#define BUNDLE_DESCRIPTOR (6)
+#define MPSERVER_DESCRIPTOR (7)
+#define RADIUS_DESCRIPTOR (8)
+#define CHAP_DESCRIPTOR (9)
+
+struct bundle;
+
+struct fdescriptor {
+ int type;
+
+ int (*UpdateSet)(struct fdescriptor *, fd_set *, fd_set *, fd_set *, int *);
+ int (*IsSet)(struct fdescriptor *, const fd_set *);
+ void (*Read)(struct fdescriptor *, struct bundle *, const fd_set *);
+ int (*Write)(struct fdescriptor *, struct bundle *, const fd_set *);
+};
+
+#define descriptor_UpdateSet(d, r, w, e, n) ((*(d)->UpdateSet)(d, r, w, e, n))
+#define descriptor_IsSet(d, s) ((*(d)->IsSet)(d, s))
+#define descriptor_Read(d, b, f) ((*(d)->Read)(d, b, f))
+#define descriptor_Write(d, b, f) ((*(d)->Write)(d, b, f))
diff --git a/usr.sbin/ppp/ether.c b/usr.sbin/ppp/ether.c
new file mode 100644
index 0000000..a86f3bd
--- /dev/null
+++ b/usr.sbin/ppp/ether.c
@@ -0,0 +1,737 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.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/param.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netgraph.h>
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/route.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netgraph/ng_ether.h>
+#include <netgraph/ng_message.h>
+#include <netgraph/ng_pppoe.h>
+#include <netgraph/ng_socket.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <termios.h>
+#include <sys/time.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "fsm.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "async.h"
+#include "descriptor.h"
+#include "physical.h"
+#include "main.h"
+#include "mp.h"
+#include "chat.h"
+#include "auth.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+#include "slcompress.h"
+#include "iplist.h"
+#include "ncpaddr.h"
+#include "ip.h"
+#include "ipcp.h"
+#include "filter.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "id.h"
+#include "iface.h"
+#include "route.h"
+#include "ether.h"
+
+
+#define PPPOE_NODE_TYPE_LEN (sizeof NG_PPPOE_NODE_TYPE - 1) /* "PPPoE" */
+
+struct etherdevice {
+ struct device dev; /* What struct physical knows about */
+ int cs; /* Control socket */
+ int connected; /* Are we connected yet ? */
+ int timeout; /* Seconds attempting to connect */
+ char hook[sizeof TUN_NAME + 11]; /* Our socket node hook */
+ u_int32_t slot; /* ifindex << 24 | unit */
+};
+
+#define device2ether(d) \
+ ((d)->type == ETHER_DEVICE ? (struct etherdevice *)d : NULL)
+
+unsigned
+ether_DeviceSize(void)
+{
+ return sizeof(struct etherdevice);
+}
+
+static ssize_t
+ether_Write(struct physical *p, const void *v, size_t n)
+{
+ struct etherdevice *dev = device2ether(p->handler);
+
+ return NgSendData(p->fd, dev->hook, v, n) == -1 ? -1 : (ssize_t)n;
+}
+
+static ssize_t
+ether_Read(struct physical *p, void *v, size_t n)
+{
+ char hook[sizeof TUN_NAME + 11];
+
+ return NgRecvData(p->fd, v, n, hook);
+}
+
+static int
+ether_RemoveFromSet(struct physical *p, fd_set *r, fd_set *w, fd_set *e)
+{
+ struct etherdevice *dev = device2ether(p->handler);
+ int result;
+
+ if (r && dev->cs >= 0 && FD_ISSET(dev->cs, r)) {
+ FD_CLR(dev->cs, r);
+ log_Printf(LogTIMER, "%s: fdunset(ctrl) %d\n", p->link.name, dev->cs);
+ result = 1;
+ } else
+ result = 0;
+
+ /* Careful... physical_RemoveFromSet() called us ! */
+
+ p->handler->removefromset = NULL;
+ result += physical_RemoveFromSet(p, r, w, e);
+ p->handler->removefromset = ether_RemoveFromSet;
+
+ return result;
+}
+
+static void
+ether_Free(struct physical *p)
+{
+ struct etherdevice *dev = device2ether(p->handler);
+
+ physical_SetDescriptor(p);
+ if (dev->cs != -1)
+ close(dev->cs);
+ free(dev);
+}
+
+static const char *
+ether_OpenInfo(struct physical *p)
+{
+ struct etherdevice *dev = device2ether(p->handler);
+
+ switch (dev->connected) {
+ case CARRIER_PENDING:
+ return "negotiating";
+ case CARRIER_OK:
+ return "established";
+ }
+
+ return "disconnected";
+}
+
+static int
+ether_Slot(struct physical *p)
+{
+ struct etherdevice *dev = device2ether(p->handler);
+
+ return dev->slot;
+}
+
+
+static void
+ether_device2iov(struct device *d, struct iovec *iov, int *niov,
+ int maxiov __unused, int *auxfd, int *nauxfd)
+{
+ struct etherdevice *dev;
+ int sz = physical_MaxDeviceSize();
+
+ iov[*niov].iov_base = d = realloc(d, sz);
+ if (d == NULL) {
+ log_Printf(LogALERT, "Failed to allocate memory: %d\n", sz);
+ AbortProgram(EX_OSERR);
+ }
+ iov[*niov].iov_len = sz;
+ (*niov)++;
+
+ dev = device2ether(d);
+ if (dev->cs >= 0) {
+ *auxfd = dev->cs;
+ (*nauxfd)++;
+ }
+}
+
+static void
+ether_MessageIn(struct etherdevice *dev)
+{
+ char msgbuf[sizeof(struct ng_mesg) + sizeof(struct ngpppoe_sts)];
+ struct ng_mesg *rep = (struct ng_mesg *)msgbuf;
+ struct ngpppoe_sts *sts = (struct ngpppoe_sts *)(msgbuf + sizeof *rep);
+ char *end, unknown[14], sessionid[5];
+ const char *msg;
+ struct timeval t;
+ fd_set *r;
+ u_long slot;
+ int asciilen, ret;
+
+ if (dev->cs < 0)
+ return;
+
+ if ((r = mkfdset()) == NULL) {
+ log_Printf(LogERROR, "DoLoop: Cannot create fd_set\n");
+ return;
+ }
+
+ while (1) {
+ zerofdset(r);
+ FD_SET(dev->cs, r);
+ t.tv_sec = t.tv_usec = 0;
+ ret = select(dev->cs + 1, r, NULL, NULL, &t);
+
+ if (ret <= 0)
+ break;
+
+ if (NgRecvMsg(dev->cs, rep, sizeof msgbuf, NULL) <= 0)
+ break;
+
+ if (rep->header.version != NG_VERSION) {
+ log_Printf(LogWARN, "%ld: Unexpected netgraph version, expected %ld\n",
+ (long)rep->header.version, (long)NG_VERSION);
+ break;
+ }
+
+ if (rep->header.typecookie != NGM_PPPOE_COOKIE) {
+ log_Printf(LogWARN, "%ld: Unexpected netgraph cookie, expected %ld\n",
+ (long)rep->header.typecookie, (long)NGM_PPPOE_COOKIE);
+ break;
+ }
+
+ asciilen = 0;
+ switch (rep->header.cmd) {
+ case NGM_PPPOE_SET_FLAG: msg = "SET_FLAG"; break;
+ case NGM_PPPOE_CONNECT: msg = "CONNECT"; break;
+ case NGM_PPPOE_LISTEN: msg = "LISTEN"; break;
+ case NGM_PPPOE_OFFER: msg = "OFFER"; break;
+ case NGM_PPPOE_SUCCESS: msg = "SUCCESS"; break;
+ case NGM_PPPOE_FAIL: msg = "FAIL"; break;
+ case NGM_PPPOE_CLOSE: msg = "CLOSE"; break;
+ case NGM_PPPOE_GET_STATUS: msg = "GET_STATUS"; break;
+ case NGM_PPPOE_ACNAME:
+ msg = "ACNAME";
+ if (setenv("ACNAME", sts->hook, 1) != 0)
+ log_Printf(LogWARN, "setenv: cannot set ACNAME=%s: %m", sts->hook);
+ asciilen = rep->header.arglen;
+ break;
+ case NGM_PPPOE_SESSIONID:
+ msg = "SESSIONID";
+ snprintf(sessionid, sizeof sessionid, "%04x", *(u_int16_t *)sts);
+ if (setenv("SESSIONID", sessionid, 1) != 0)
+ syslog(LOG_WARNING, "setenv: cannot set SESSIONID=%s: %m",
+ sessionid);
+ /* Use this in preference to our interface index */
+ slot = strtoul(sessionid, &end, 16);
+ if (end != sessionid && *end == '\0')
+ dev->slot = slot;
+ break;
+ default:
+ snprintf(unknown, sizeof unknown, "<%d>", (int)rep->header.cmd);
+ msg = unknown;
+ break;
+ }
+
+ if (asciilen)
+ log_Printf(LogPHASE, "Received NGM_PPPOE_%s (hook \"%.*s\")\n",
+ msg, asciilen, sts->hook);
+ else
+ log_Printf(LogPHASE, "Received NGM_PPPOE_%s\n", msg);
+
+ switch (rep->header.cmd) {
+ case NGM_PPPOE_SUCCESS:
+ dev->connected = CARRIER_OK;
+ break;
+ case NGM_PPPOE_FAIL:
+ case NGM_PPPOE_CLOSE:
+ dev->connected = CARRIER_LOST;
+ break;
+ }
+ }
+ free(r);
+}
+
+static int
+ether_AwaitCarrier(struct physical *p)
+{
+ struct etherdevice *dev = device2ether(p->handler);
+
+ if (dev->connected != CARRIER_OK && !dev->timeout--)
+ dev->connected = CARRIER_LOST;
+ else if (dev->connected == CARRIER_PENDING)
+ ether_MessageIn(dev);
+
+ return dev->connected;
+}
+
+static const struct device baseetherdevice = {
+ ETHER_DEVICE,
+ "ether",
+ 1492,
+ { CD_REQUIRED, DEF_ETHERCDDELAY },
+ ether_AwaitCarrier,
+ ether_RemoveFromSet,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ ether_Free,
+ ether_Read,
+ ether_Write,
+ ether_device2iov,
+ NULL,
+ ether_OpenInfo,
+ ether_Slot
+};
+
+struct device *
+ether_iov2device(int type, struct physical *p, struct iovec *iov, int *niov,
+ int maxiov __unused, int *auxfd, int *nauxfd)
+{
+ if (type == ETHER_DEVICE) {
+ struct etherdevice *dev = (struct etherdevice *)iov[(*niov)++].iov_base;
+
+ dev = realloc(dev, sizeof *dev); /* Reduce to the correct size */
+ if (dev == NULL) {
+ log_Printf(LogALERT, "Failed to allocate memory: %d\n",
+ (int)(sizeof *dev));
+ AbortProgram(EX_OSERR);
+ }
+
+ if (*nauxfd) {
+ dev->cs = *auxfd;
+ (*nauxfd)--;
+ } else
+ dev->cs = -1;
+
+ /* Refresh function pointers etc */
+ memcpy(&dev->dev, &baseetherdevice, sizeof dev->dev);
+
+ physical_SetupStack(p, dev->dev.name, PHYSICAL_FORCE_SYNCNOACF);
+ return &dev->dev;
+ }
+
+ return NULL;
+}
+
+static int
+ether_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e, int *n)
+{
+ struct physical *p = descriptor2physical(d);
+ struct etherdevice *dev = device2ether(p->handler);
+ int result;
+
+ if (r && dev->cs >= 0) {
+ FD_SET(dev->cs, r);
+ log_Printf(LogTIMER, "%s(ctrl): fdset(r) %d\n", p->link.name, dev->cs);
+ result = 1;
+ } else
+ result = 0;
+
+ result += physical_doUpdateSet(d, r, w, e, n, 0);
+
+ return result;
+}
+
+static int
+ether_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+ struct physical *p = descriptor2physical(d);
+ struct etherdevice *dev = device2ether(p->handler);
+ int result;
+
+ result = dev->cs >= 0 && FD_ISSET(dev->cs, fdset);
+ result += physical_IsSet(d, fdset);
+
+ return result;
+}
+
+static void
+ether_DescriptorRead(struct fdescriptor *d, struct bundle *bundle,
+ const fd_set *fdset)
+{
+ struct physical *p = descriptor2physical(d);
+ struct etherdevice *dev = device2ether(p->handler);
+
+ if (dev->cs >= 0 && FD_ISSET(dev->cs, fdset)) {
+ ether_MessageIn(dev);
+ if (dev->connected == CARRIER_LOST) {
+ log_Printf(LogPHASE, "%s: Device disconnected\n", p->link.name);
+ datalink_Down(p->dl, CLOSE_NORMAL);
+ return;
+ }
+ }
+
+ if (physical_IsSet(d, fdset))
+ physical_DescriptorRead(d, bundle, fdset);
+}
+
+static struct device *
+ether_Abandon(struct etherdevice *dev, struct physical *p)
+{
+ /* Abandon our node construction */
+ close(dev->cs);
+ close(p->fd);
+ p->fd = -2; /* Nobody else need try.. */
+ free(dev);
+
+ return NULL;
+}
+
+struct device *
+ether_Create(struct physical *p)
+{
+ u_char rbuf[2048];
+ struct etherdevice *dev;
+ struct ng_mesg *resp;
+ const struct hooklist *hlist;
+ const struct nodeinfo *ninfo;
+ char *path, *sessionid;
+ const char *mode;
+ size_t ifacelen;
+ unsigned f;
+
+ dev = NULL;
+ path = NULL;
+ ifacelen = 0;
+ if (p->fd < 0 && !strncasecmp(p->name.full, NG_PPPOE_NODE_TYPE,
+ PPPOE_NODE_TYPE_LEN) &&
+ p->name.full[PPPOE_NODE_TYPE_LEN] == ':') {
+ const struct linkinfo *nlink;
+ struct ngpppoe_init_data *data;
+ struct ngm_mkpeer mkp;
+ struct ngm_connect ngc;
+ const char *iface, *provider;
+ char etherid[12];
+ int providerlen;
+ char connectpath[sizeof dev->hook + 2]; /* .:<hook> */
+
+ p->fd--; /* We own the device - change fd */
+
+ loadmodules(LOAD_VERBOSLY, "netgraph", "ng_ether", "ng_pppoe", "ng_socket",
+ NULL);
+
+ if ((dev = malloc(sizeof *dev)) == NULL)
+ return NULL;
+
+ iface = p->name.full + PPPOE_NODE_TYPE_LEN + 1;
+
+ provider = strchr(iface, ':');
+ if (provider) {
+ ifacelen = provider - iface;
+ provider++;
+ providerlen = strlen(provider);
+ } else {
+ ifacelen = strlen(iface);
+ provider = "";
+ providerlen = 0;
+ }
+
+ /*
+ * We're going to do this (where tunN is our tunnel device):
+ *
+ * .---------.
+ * | ether |
+ * | <iface> | dev->cs
+ * `---------' |
+ * (orphan) p->fd |
+ * | | |
+ * | | |
+ * (ethernet) | |
+ * .---------. .-----------.
+ * | pppoe | | socket |
+ * | <iface> |(tunN)<---->(tunN)| <unnamed> |
+ * `--------- `-----------'
+ * (tunX)
+ * ^
+ * |
+ * `--->(tunX)
+ */
+
+ /* Create a socket node */
+ if (ID0NgMkSockNode(NULL, &dev->cs, &p->fd) == -1) {
+ log_Printf(LogWARN, "Cannot create netgraph socket node: %s\n",
+ strerror(errno));
+ free(dev);
+ p->fd = -2;
+ return NULL;
+ }
+
+ /*
+ * Ask for a list of hooks attached to the "ether" node. This node should
+ * magically exist as a way of hooking stuff onto an ethernet device
+ */
+ path = (char *)alloca(ifacelen + 2);
+ sprintf(path, "%.*s:", (int)ifacelen, iface);
+ if (NgSendMsg(dev->cs, path, NGM_GENERIC_COOKIE, NGM_LISTHOOKS,
+ NULL, 0) < 0) {
+ log_Printf(LogWARN, "%s Cannot send a netgraph message: %s\n",
+ path, strerror(errno));
+ return ether_Abandon(dev, p);
+ }
+
+ /* Get our list back */
+ resp = (struct ng_mesg *)rbuf;
+ if (NgRecvMsg(dev->cs, resp, sizeof rbuf, NULL) <= 0) {
+ log_Printf(LogWARN, "Cannot get netgraph response: %s\n",
+ strerror(errno));
+ return ether_Abandon(dev, p);
+ }
+
+ hlist = (const struct hooklist *)resp->data;
+ ninfo = &hlist->nodeinfo;
+
+ /* Make sure we've got the right type of node */
+ if (strncmp(ninfo->type, NG_ETHER_NODE_TYPE,
+ sizeof NG_ETHER_NODE_TYPE - 1)) {
+ log_Printf(LogWARN, "%s Unexpected node type ``%s'' (wanted ``"
+ NG_ETHER_NODE_TYPE "'')\n", path, ninfo->type);
+ return ether_Abandon(dev, p);
+ }
+
+ log_Printf(LogDEBUG, "List of netgraph node ``%s'' (id %x) hooks:\n",
+ path, ninfo->id);
+
+ /* look for a hook already attached. */
+ for (f = 0; f < ninfo->hooks; f++) {
+ nlink = &hlist->link[f];
+
+ log_Printf(LogDEBUG, " Found %s -> %s\n", nlink->ourhook,
+ nlink->peerhook);
+
+ if (!strcmp(nlink->ourhook, NG_ETHER_HOOK_ORPHAN) ||
+ !strcmp(nlink->ourhook, NG_ETHER_HOOK_DIVERT)) {
+ /*
+ * Something is using the data coming out of this ``ether'' node.
+ * If it's a PPPoE node, we use that node, otherwise we complain that
+ * someone else is using the node.
+ */
+ if (!strcmp(nlink->nodeinfo.type, NG_PPPOE_NODE_TYPE))
+ /* Use this PPPoE node ! */
+ snprintf(ngc.path, sizeof ngc.path, "[%x]:", nlink->nodeinfo.id);
+ else {
+ log_Printf(LogWARN, "%s Node type ``%s'' is currently active\n",
+ path, nlink->nodeinfo.type);
+ return ether_Abandon(dev, p);
+ }
+ break;
+ }
+ }
+
+ if (f == ninfo->hooks) {
+ /*
+ * Create a new ``PPPoE'' node connected to the ``ether'' node using
+ * the ``orphan'' and ``ethernet'' hooks
+ */
+ snprintf(mkp.type, sizeof mkp.type, "%s", NG_PPPOE_NODE_TYPE);
+ snprintf(mkp.ourhook, sizeof mkp.ourhook, "%s", NG_ETHER_HOOK_ORPHAN);
+ snprintf(mkp.peerhook, sizeof mkp.peerhook, "%s", NG_PPPOE_HOOK_ETHERNET);
+ snprintf(etherid, sizeof etherid, "[%x]:", ninfo->id);
+
+ log_Printf(LogDEBUG, "Creating PPPoE netgraph node %s%s -> %s\n",
+ etherid, mkp.ourhook, mkp.peerhook);
+
+ if (NgSendMsg(dev->cs, etherid, NGM_GENERIC_COOKIE,
+ NGM_MKPEER, &mkp, sizeof mkp) < 0) {
+ log_Printf(LogWARN, "%s Cannot create PPPoE netgraph node: %s\n",
+ etherid, strerror(errno));
+ return ether_Abandon(dev, p);
+ }
+
+ snprintf(ngc.path, sizeof ngc.path, "%s%s", path, NG_ETHER_HOOK_ORPHAN);
+ }
+
+ snprintf(dev->hook, sizeof dev->hook, "%s%d",
+ TUN_NAME, p->dl->bundle->unit);
+
+ /*
+ * Connect the PPPoE node to our socket node.
+ * ngc.path has already been set up
+ */
+ snprintf(ngc.ourhook, sizeof ngc.ourhook, "%s", dev->hook);
+ memcpy(ngc.peerhook, ngc.ourhook, sizeof ngc.peerhook);
+
+ log_Printf(LogDEBUG, "Connecting netgraph socket .:%s -> %s:%s\n",
+ ngc.ourhook, ngc.path, ngc.peerhook);
+ if (NgSendMsg(dev->cs, ".:", NGM_GENERIC_COOKIE,
+ NGM_CONNECT, &ngc, sizeof ngc) < 0) {
+ log_Printf(LogWARN, "Cannot connect PPPoE and socket netgraph "
+ "nodes: %s\n", strerror(errno));
+ return ether_Abandon(dev, p);
+ }
+
+ /* Bring the Ethernet interface up */
+ path[ifacelen] = '\0'; /* Remove the trailing ':' */
+ if (!iface_SetFlags(path, IFF_UP))
+ log_Printf(LogWARN, "%s: Failed to set the IFF_UP flag on %s\n",
+ p->link.name, path);
+
+ snprintf(connectpath, sizeof connectpath, ".:%s", dev->hook);
+
+ /* Configure node to 3Com mode if needed */
+ if (p->cfg.pppoe_configured) {
+ mode = p->cfg.nonstandard_pppoe ? NG_PPPOE_NONSTANDARD : NG_PPPOE_STANDARD;
+ if (NgSendMsg(dev->cs, connectpath, NGM_PPPOE_COOKIE,
+ NGM_PPPOE_SETMODE, mode, strlen(mode) + 1) == -1) {
+ log_Printf(LogWARN, "``%s'': Cannot configure netgraph node: %s\n",
+ connectpath, strerror(errno));
+ return ether_Abandon(dev, p);
+ }
+ }
+
+ /* And finally, request a connection to the given provider */
+
+ data = (struct ngpppoe_init_data *)alloca(sizeof *data + providerlen);
+ snprintf(data->hook, sizeof data->hook, "%s", dev->hook);
+ memcpy(data->data, provider, providerlen);
+ data->data_len = providerlen;
+
+ log_Printf(LogDEBUG, "Sending PPPOE_CONNECT to %s\n", connectpath);
+ if (NgSendMsg(dev->cs, connectpath, NGM_PPPOE_COOKIE,
+ NGM_PPPOE_CONNECT, data, sizeof *data + providerlen) == -1) {
+ log_Printf(LogWARN, "``%s'': Cannot start netgraph node: %s\n",
+ connectpath, strerror(errno));
+ return ether_Abandon(dev, p);
+ }
+
+ /* Hook things up so that we monitor dev->cs */
+ p->desc.UpdateSet = ether_UpdateSet;
+ p->desc.IsSet = ether_IsSet;
+ p->desc.Read = ether_DescriptorRead;
+
+ memcpy(&dev->dev, &baseetherdevice, sizeof dev->dev);
+ switch (p->cfg.cd.necessity) {
+ case CD_VARIABLE:
+ dev->dev.cd.delay = p->cfg.cd.delay;
+ break;
+ case CD_REQUIRED:
+ dev->dev.cd = p->cfg.cd;
+ break;
+ case CD_NOTREQUIRED:
+ log_Printf(LogWARN, "%s: Carrier must be set, using ``set cd %d!''\n",
+ p->link.name, dev->dev.cd.delay);
+ case CD_DEFAULT:
+ break;
+ }
+
+ dev->timeout = dev->dev.cd.delay;
+ dev->connected = CARRIER_PENDING;
+ /* This will be overridden by our session id - if provided by netgraph */
+ dev->slot = GetIfIndex(path);
+ } else {
+ /* See if we're a netgraph socket */
+ struct stat st;
+
+ if (fstat(p->fd, &st) != -1 && (st.st_mode & S_IFSOCK)) {
+ struct sockaddr_storage ssock;
+ struct sockaddr *sock = (struct sockaddr *)&ssock;
+ int sz;
+
+ sz = sizeof ssock;
+ if (getsockname(p->fd, sock, &sz) == -1) {
+ log_Printf(LogPHASE, "%s: Link is a closed socket !\n", p->link.name);
+ close(p->fd);
+ p->fd = -1;
+ return NULL;
+ }
+
+ if (sock->sa_family == AF_NETGRAPH) {
+ /*
+ * It's a netgraph node... We can't determine hook names etc, so we
+ * stay pretty impartial....
+ */
+ log_Printf(LogPHASE, "%s: Link is a netgraph node\n", p->link.name);
+
+ if ((dev = malloc(sizeof *dev)) == NULL) {
+ log_Printf(LogWARN, "%s: Cannot allocate an ether device: %s\n",
+ p->link.name, strerror(errno));
+ return NULL;
+ }
+
+ memcpy(&dev->dev, &baseetherdevice, sizeof dev->dev);
+ dev->cs = -1;
+ dev->timeout = 0;
+ dev->connected = CARRIER_OK;
+ *dev->hook = '\0';
+
+ /*
+ * If we're being envoked from pppoed(8), we may have a SESSIONID
+ * set in the environment. If so, use it as the slot
+ */
+ if ((sessionid = getenv("SESSIONID")) != NULL) {
+ char *end;
+ u_long slot;
+
+ slot = strtoul(sessionid, &end, 16);
+ dev->slot = end != sessionid && *end == '\0' ? slot : 0;
+ } else
+ dev->slot = 0;
+ }
+ }
+ }
+
+ if (dev) {
+ physical_SetupStack(p, dev->dev.name, PHYSICAL_FORCE_SYNCNOACF);
+ return &dev->dev;
+ }
+
+ return NULL;
+}
diff --git a/usr.sbin/ppp/ether.h b/usr.sbin/ppp/ether.h
new file mode 100644
index 0000000..94511bd
--- /dev/null
+++ b/usr.sbin/ppp/ether.h
@@ -0,0 +1,37 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.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$
+ */
+
+struct physical;
+struct device;
+
+#define DEF_ETHERCDDELAY 5 /* Default ``set cd'' value */
+
+extern struct device *ether_Create(struct physical *);
+extern struct device *ether_iov2device(int, struct physical *, struct iovec *,
+ int *, int, int *, int *);
+extern unsigned ether_DeviceSize(void);
diff --git a/usr.sbin/ppp/exec.c b/usr.sbin/ppp/exec.c
new file mode 100644
index 0000000..a53db37
--- /dev/null
+++ b/usr.sbin/ppp/exec.c
@@ -0,0 +1,410 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.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/param.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "fsm.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "async.h"
+#include "descriptor.h"
+#include "physical.h"
+#include "mp.h"
+#include "chat.h"
+#include "command.h"
+#include "auth.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+#include "id.h"
+#include "main.h"
+#include "exec.h"
+
+
+struct execdevice {
+ struct device dev; /* What struct physical knows about */
+ int fd_out; /* output descriptor */
+};
+
+#define device2exec(d) ((d)->type == EXEC_DEVICE ? (struct execdevice *)d : NULL)
+
+unsigned
+exec_DeviceSize(void)
+{
+ return sizeof(struct execdevice);
+}
+
+static void
+exec_Free(struct physical *p)
+{
+ struct execdevice *dev = device2exec(p->handler);
+
+ if (dev->fd_out != -1)
+ close(dev->fd_out);
+ free(dev);
+}
+
+static void
+exec_device2iov(struct device *d, struct iovec *iov, int *niov,
+ int maxiov __unused, int *auxfd, int *nauxfd)
+{
+ struct execdevice *dev;
+ int sz = physical_MaxDeviceSize();
+
+ iov[*niov].iov_base = d = realloc(d, sz);
+ if (d == NULL) {
+ log_Printf(LogALERT, "Failed to allocate memory: %d\n", sz);
+ AbortProgram(EX_OSERR);
+ }
+ iov[*niov].iov_len = sz;
+ (*niov)++;
+
+ dev = device2exec(d);
+ if (dev->fd_out >= 0) {
+ *auxfd = dev->fd_out;
+ (*nauxfd)++;
+ }
+}
+
+static int
+exec_RemoveFromSet(struct physical *p, fd_set *r, fd_set *w, fd_set *e)
+{
+ struct execdevice *dev = device2exec(p->handler);
+ int sets;
+
+ p->handler->removefromset = NULL;
+ sets = physical_RemoveFromSet(p, r, w, e);
+ p->handler->removefromset = exec_RemoveFromSet;
+
+ if (dev->fd_out >= 0) {
+ if (w && FD_ISSET(dev->fd_out, w)) {
+ FD_CLR(dev->fd_out, w);
+ log_Printf(LogTIMER, "%s: fdunset(w) %d\n", p->link.name, dev->fd_out);
+ sets++;
+ }
+ if (e && FD_ISSET(dev->fd_out, e)) {
+ FD_CLR(dev->fd_out, e);
+ log_Printf(LogTIMER, "%s: fdunset(e) %d\n", p->link.name, dev->fd_out);
+ sets++;
+ }
+ }
+
+ return sets;
+}
+
+static ssize_t
+exec_Write(struct physical *p, const void *v, size_t n)
+{
+ struct execdevice *dev = device2exec(p->handler);
+ int fd = dev->fd_out == -1 ? p->fd : dev->fd_out;
+
+ return write(fd, v, n);
+}
+
+static struct device baseexecdevice = {
+ EXEC_DEVICE,
+ "exec",
+ 0,
+ { CD_NOTREQUIRED, 0 },
+ NULL,
+ exec_RemoveFromSet,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ exec_Free,
+ NULL,
+ exec_Write,
+ exec_device2iov,
+ NULL,
+ NULL,
+ NULL
+};
+
+struct device *
+exec_iov2device(int type, struct physical *p, struct iovec *iov,
+ int *niov, int maxiov __unused, int *auxfd, int *nauxfd)
+{
+ if (type == EXEC_DEVICE) {
+ struct execdevice *dev = (struct execdevice *)iov[(*niov)++].iov_base;
+
+ dev = realloc(dev, sizeof *dev); /* Reduce to the correct size */
+ if (dev == NULL) {
+ log_Printf(LogALERT, "Failed to allocate memory: %d\n",
+ (int)(sizeof *dev));
+ AbortProgram(EX_OSERR);
+ }
+
+ if (*nauxfd) {
+ dev->fd_out = *auxfd;
+ (*nauxfd)--;
+ } else
+ dev->fd_out = -1;
+
+ /* Refresh function pointers etc */
+ memcpy(&dev->dev, &baseexecdevice, sizeof dev->dev);
+
+ physical_SetupStack(p, dev->dev.name, PHYSICAL_NOFORCE);
+ return &dev->dev;
+ }
+
+ return NULL;
+}
+
+static int
+exec_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e, int *n)
+{
+ struct physical *p = descriptor2physical(d);
+ struct execdevice *dev = device2exec(p->handler);
+ int result = 0;
+
+ if (w && dev->fd_out >= 0) {
+ FD_SET(dev->fd_out, w);
+ log_Printf(LogTIMER, "%s: fdset(w) %d\n", p->link.name, dev->fd_out);
+ result++;
+ w = NULL;
+ }
+
+ if (e && dev->fd_out >= 0) {
+ FD_SET(dev->fd_out, e);
+ log_Printf(LogTIMER, "%s: fdset(e) %d\n", p->link.name, dev->fd_out);
+ result++;
+ }
+
+ if (result && *n <= dev->fd_out)
+ *n = dev->fd_out + 1;
+
+ return result + physical_doUpdateSet(d, r, w, e, n, 0);
+}
+
+static int
+exec_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+ struct physical *p = descriptor2physical(d);
+ struct execdevice *dev = device2exec(p->handler);
+ int result = dev->fd_out >= 0 && FD_ISSET(dev->fd_out, fdset);
+ result += physical_IsSet(d, fdset);
+
+ return result;
+}
+
+struct device *
+exec_Create(struct physical *p)
+{
+ struct execdevice *dev;
+
+ dev = NULL;
+ if (p->fd < 0) {
+ if (*p->name.full == '!') {
+ int fids[2], type;
+
+ if ((dev = malloc(sizeof *dev)) == NULL) {
+ log_Printf(LogWARN, "%s: Cannot allocate an exec device: %s\n",
+ p->link.name, strerror(errno));
+ return NULL;
+ }
+ dev->fd_out = -1;
+
+ p->fd--; /* We own the device but maybe can't use it - change fd */
+ type = physical_IsSync(p) ? SOCK_DGRAM : SOCK_STREAM;
+
+ if (socketpair(AF_UNIX, type, PF_UNSPEC, fids) < 0) {
+ log_Printf(LogPHASE, "Unable to create pipe for line exec: %s\n",
+ strerror(errno));
+ free(dev);
+ dev = NULL;
+ } else {
+ static int child_status; /* This variable is abused ! */
+ int stat, argc, i, ret, wret, pidpipe[2];
+ pid_t pid, realpid;
+ char *argv[MAXARGS];
+
+ stat = fcntl(fids[0], F_GETFL, 0);
+ if (stat > 0) {
+ stat |= O_NONBLOCK;
+ fcntl(fids[0], F_SETFL, stat);
+ }
+ realpid = getpid();
+ if (pipe(pidpipe) == -1) {
+ log_Printf(LogPHASE, "Unable to pipe for line exec: %s\n",
+ strerror(errno));
+ close(fids[1]);
+ close(fids[0]);
+ free(dev);
+ dev = NULL;
+ } else switch ((pid = fork())) {
+ case -1:
+ log_Printf(LogPHASE, "Unable to fork for line exec: %s\n",
+ strerror(errno));
+ close(pidpipe[0]);
+ close(pidpipe[1]);
+ close(fids[1]);
+ close(fids[0]);
+ break;
+
+ case 0:
+ close(pidpipe[0]);
+ close(fids[0]);
+ timer_TermService();
+ #ifndef NOSUID
+ setuid(ID0realuid());
+ #endif
+
+ child_status = 0;
+ switch ((pid = vfork())) {
+ case 0:
+ close(pidpipe[1]);
+ break;
+
+ case -1:
+ ret = errno;
+ log_Printf(LogPHASE, "Unable to vfork to drop parent: %s\n",
+ strerror(errno));
+ close(pidpipe[1]);
+ _exit(ret);
+
+ default:
+ write(pidpipe[1], &pid, sizeof pid);
+ close(pidpipe[1]);
+ _exit(child_status); /* The error from exec() ! */
+ }
+
+ log_Printf(LogDEBUG, "Exec'ing ``%s''\n", p->name.base);
+
+ if ((argc = MakeArgs(p->name.base, argv, VECSIZE(argv),
+ PARSE_REDUCE|PARSE_NOHASH)) < 0) {
+ log_Printf(LogWARN, "Syntax error in exec command\n");
+ _exit(ESRCH);
+ }
+
+ command_Expand(argv, argc, (char const *const *)argv,
+ p->dl->bundle, 0, realpid);
+
+ dup2(fids[1], STDIN_FILENO);
+ dup2(fids[1], STDOUT_FILENO);
+ dup2(fids[1], STDERR_FILENO);
+ for (i = getdtablesize(); i > STDERR_FILENO; i--)
+ fcntl(i, F_SETFD, 1);
+
+ execvp(*argv, argv);
+ child_status = errno; /* Only works for vfork() */
+ printf("execvp failed: %s: %s\r\n", *argv, strerror(child_status));
+ _exit(child_status);
+ break;
+
+ default:
+ close(pidpipe[1]);
+ close(fids[1]);
+ if (read(pidpipe[0], &p->session_owner, sizeof p->session_owner) !=
+ sizeof p->session_owner)
+ p->session_owner = (pid_t)-1;
+ close(pidpipe[0]);
+ while ((wret = waitpid(pid, &stat, 0)) == -1 && errno == EINTR)
+ ;
+ if (wret == -1) {
+ log_Printf(LogWARN, "Waiting for child process: %s\n",
+ strerror(errno));
+ close(fids[0]);
+ p->session_owner = (pid_t)-1;
+ break;
+ } else if (WIFSIGNALED(stat)) {
+ log_Printf(LogWARN, "Child process received sig %d !\n",
+ WTERMSIG(stat));
+ close(fids[0]);
+ p->session_owner = (pid_t)-1;
+ break;
+ } else if (WIFSTOPPED(stat)) {
+ log_Printf(LogWARN, "Child process received stop sig %d !\n",
+ WSTOPSIG(stat));
+ /* I guess that's ok.... */
+ } else if ((ret = WEXITSTATUS(stat))) {
+ log_Printf(LogWARN, "Cannot exec \"%s\": %s\n", p->name.base,
+ strerror(ret));
+ close(fids[0]);
+ p->session_owner = (pid_t)-1;
+ break;
+ }
+ p->fd = fids[0];
+ log_Printf(LogDEBUG, "Using descriptor %d for child\n", p->fd);
+ }
+ }
+ }
+ } else {
+ struct stat st;
+
+ if (fstat(p->fd, &st) != -1 && (st.st_mode & S_IFIFO)) {
+ if ((dev = malloc(sizeof *dev)) == NULL)
+ log_Printf(LogWARN, "%s: Cannot allocate an exec device: %s\n",
+ p->link.name, strerror(errno));
+ else if (p->fd == STDIN_FILENO) {
+ log_Printf(LogPHASE, "%s: Using stdin/stdout to communicate with "
+ "parent (pipe mode)\n", p->link.name);
+ dev->fd_out = dup(STDOUT_FILENO);
+
+ /* Hook things up so that we monitor dev->fd_out */
+ p->desc.UpdateSet = exec_UpdateSet;
+ p->desc.IsSet = exec_IsSet;
+ } else
+ dev->fd_out = -1;
+ }
+ }
+
+ if (dev) {
+ memcpy(&dev->dev, &baseexecdevice, sizeof dev->dev);
+ physical_SetupStack(p, dev->dev.name, PHYSICAL_NOFORCE);
+ if (p->cfg.cd.necessity != CD_DEFAULT)
+ log_Printf(LogWARN, "Carrier settings ignored\n");
+ return &dev->dev;
+ }
+
+ return NULL;
+}
diff --git a/usr.sbin/ppp/exec.h b/usr.sbin/ppp/exec.h
new file mode 100644
index 0000000..32bd748
--- /dev/null
+++ b/usr.sbin/ppp/exec.h
@@ -0,0 +1,35 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.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$
+ */
+
+struct physical;
+struct device;
+
+extern struct device *exec_Create(struct physical *);
+extern struct device *exec_iov2device(int, struct physical *,
+ struct iovec *, int *, int, int *, int *);
+extern unsigned exec_DeviceSize(void);
diff --git a/usr.sbin/ppp/filter.c b/usr.sbin/ppp/filter.c
new file mode 100644
index 0000000..f59848e
--- /dev/null
+++ b/usr.sbin/ppp/filter.c
@@ -0,0 +1,604 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <netinet/in.h>
+#include <netdb.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "iplist.h"
+#include "timer.h"
+#include "throughput.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "fsm.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "prompt.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+
+static unsigned filter_Nam2Op(const char *);
+
+static int
+ParsePort(const char *service, const char *proto)
+{
+ struct servent *servent;
+ char *cp;
+ int port;
+
+ servent = getservbyname(service, proto);
+ if (servent != 0)
+ return ntohs(servent->s_port);
+
+ port = strtol(service, &cp, 0);
+ if (cp == service) {
+ log_Printf(LogWARN, "ParsePort: %s is not a port name or number.\n",
+ service);
+ return 0;
+ }
+ return port;
+}
+
+/*
+ * ICMP Syntax: src eq icmp_message_type
+ */
+static int
+ParseIcmp(int argc, char const *const *argv, struct filterent *tgt)
+{
+ int type;
+ char *cp;
+
+ switch (argc) {
+ case 0:
+ /* permit/deny all ICMP types */
+ tgt->f_srcop = tgt->f_dstop = OP_NONE;
+ break;
+
+ case 3:
+ if (!strcmp(*argv, "src") && !strcmp(argv[1], "eq")) {
+ type = strtol(argv[2], &cp, 0);
+ if (cp == argv[2]) {
+ log_Printf(LogWARN, "ParseIcmp: type is expected.\n");
+ return 0;
+ }
+ tgt->f_srcop = OP_EQ;
+ tgt->f_srcport = type;
+ tgt->f_dstop = OP_NONE;
+ }
+ break;
+
+ default:
+ log_Printf(LogWARN, "ParseIcmp: bad icmp syntax.\n");
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * UDP Syntax: [src op port] [dst op port]
+ */
+static int
+ParseUdpOrTcp(int argc, char const *const *argv, const struct protoent *pe,
+ struct filterent *tgt)
+{
+ tgt->f_srcop = tgt->f_dstop = OP_NONE;
+ tgt->f_estab = tgt->f_syn = tgt->f_finrst = 0;
+
+ if (argc >= 3 && !strcmp(*argv, "src")) {
+ tgt->f_srcop = filter_Nam2Op(argv[1]);
+ if (tgt->f_srcop == OP_NONE) {
+ log_Printf(LogWARN, "ParseUdpOrTcp: bad operator\n");
+ return 0;
+ }
+ if (pe == NULL)
+ return 0;
+ tgt->f_srcport = ParsePort(argv[2], pe->p_name);
+ if (tgt->f_srcport == 0)
+ return 0;
+ argc -= 3;
+ argv += 3;
+ }
+
+ if (argc >= 3 && !strcmp(argv[0], "dst")) {
+ tgt->f_dstop = filter_Nam2Op(argv[1]);
+ if (tgt->f_dstop == OP_NONE) {
+ log_Printf(LogWARN, "ParseUdpOrTcp: bad operator\n");
+ return 0;
+ }
+ if (pe == NULL)
+ return 0;
+ tgt->f_dstport = ParsePort(argv[2], pe->p_name);
+ if (tgt->f_dstport == 0)
+ return 0;
+ argc -= 3;
+ argv += 3;
+ }
+
+ if (pe && pe->p_proto == IPPROTO_TCP) {
+ for (; argc > 0; argc--, argv++)
+ if (!strcmp(*argv, "estab"))
+ tgt->f_estab = 1;
+ else if (!strcmp(*argv, "syn"))
+ tgt->f_syn = 1;
+ else if (!strcmp(*argv, "finrst"))
+ tgt->f_finrst = 1;
+ else
+ break;
+ }
+
+ if (argc > 0) {
+ log_Printf(LogWARN, "ParseUdpOrTcp: bad src/dst port syntax: %s\n", *argv);
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
+ParseGeneric(int argc, struct filterent *tgt)
+{
+ /*
+ * Filter currently is a catch-all. Requests are either permitted or
+ * dropped.
+ */
+ if (argc != 0) {
+ log_Printf(LogWARN, "ParseGeneric: Too many parameters\n");
+ return 0;
+ } else
+ tgt->f_srcop = tgt->f_dstop = OP_NONE;
+
+ return 1;
+}
+
+static unsigned
+addrtype(const char *addr)
+{
+ if (!strncasecmp(addr, "MYADDR", 6) && (addr[6] == '\0' || addr[6] == '/'))
+ return T_MYADDR;
+ if (!strncasecmp(addr, "MYADDR6", 7) && (addr[7] == '\0' || addr[7] == '/'))
+ return T_MYADDR6;
+ if (!strncasecmp(addr, "HISADDR", 7) && (addr[7] == '\0' || addr[7] == '/'))
+ return T_HISADDR;
+ if (!strncasecmp(addr, "HISADDR6", 8) && (addr[8] == '\0' || addr[8] == '/'))
+ return T_HISADDR6;
+ if (!strncasecmp(addr, "DNS0", 4) && (addr[4] == '\0' || addr[4] == '/'))
+ return T_DNS0;
+ if (!strncasecmp(addr, "DNS1", 4) && (addr[4] == '\0' || addr[4] == '/'))
+ return T_DNS1;
+
+ return T_ADDR;
+}
+
+static const char *
+addrstr(struct ncprange *addr, unsigned type)
+{
+ switch (type) {
+ case T_MYADDR:
+ return "MYADDR";
+ case T_HISADDR:
+ return "HISADDR";
+ case T_DNS0:
+ return "DNS0";
+ case T_DNS1:
+ return "DNS1";
+ }
+ return ncprange_ntoa(addr);
+}
+
+static int
+filter_Parse(struct ncp *ncp, int argc, char const *const *argv,
+ struct filterent *ofp)
+{
+ struct filterent fe;
+ struct protoent *pe;
+ char *wp;
+ int action, family, ruleno, val, width;
+
+ ruleno = strtol(*argv, &wp, 0);
+ if (*argv == wp || ruleno >= MAXFILTERS) {
+ log_Printf(LogWARN, "Parse: invalid filter number.\n");
+ return 0;
+ }
+ if (ruleno < 0) {
+ for (ruleno = 0; ruleno < MAXFILTERS; ruleno++) {
+ ofp->f_action = A_NONE;
+ ofp++;
+ }
+ log_Printf(LogWARN, "Parse: filter cleared.\n");
+ return 1;
+ }
+ ofp += ruleno;
+
+ if (--argc == 0) {
+ log_Printf(LogWARN, "Parse: missing action.\n");
+ return 0;
+ }
+ argv++;
+
+ memset(&fe, '\0', sizeof fe);
+
+ val = strtol(*argv, &wp, 0);
+ if (!*wp && val >= 0 && val < MAXFILTERS) {
+ if (val <= ruleno) {
+ log_Printf(LogWARN, "Parse: Can only jump forward from rule %d\n",
+ ruleno);
+ return 0;
+ }
+ action = val;
+ } else if (!strcmp(*argv, "permit")) {
+ action = A_PERMIT;
+ } else if (!strcmp(*argv, "deny")) {
+ action = A_DENY;
+ } else if (!strcmp(*argv, "clear")) {
+ ofp->f_action = A_NONE;
+ return 1;
+ } else {
+ log_Printf(LogWARN, "Parse: %s: bad action\n", *argv);
+ return 0;
+ }
+ fe.f_action = action;
+
+ argc--;
+ argv++;
+
+ if (argc && argv[0][0] == '!' && !argv[0][1]) {
+ fe.f_invert = 1;
+ argc--;
+ argv++;
+ }
+
+ ncprange_init(&fe.f_src);
+ ncprange_init(&fe.f_dst);
+
+ if (argc == 0)
+ pe = NULL;
+ else if ((pe = getprotobyname(*argv)) == NULL && strcmp(*argv, "all") != 0) {
+ if (argc < 2) {
+ log_Printf(LogWARN, "Parse: Protocol or address pair expected\n");
+ return 0;
+ } else if (strcasecmp(*argv, "any") == 0 ||
+ ncprange_aton(&fe.f_src, ncp, *argv)) {
+ family = ncprange_family(&fe.f_src);
+ if (!ncprange_getwidth(&fe.f_src, &width))
+ width = 0;
+ if (width == 0)
+ ncprange_init(&fe.f_src);
+ fe.f_srctype = addrtype(*argv);
+ argc--;
+ argv++;
+
+ if (strcasecmp(*argv, "any") == 0 ||
+ ncprange_aton(&fe.f_dst, ncp, *argv)) {
+ if (ncprange_family(&fe.f_dst) != AF_UNSPEC &&
+ ncprange_family(&fe.f_src) != AF_UNSPEC &&
+ family != ncprange_family(&fe.f_dst)) {
+ log_Printf(LogWARN, "Parse: src and dst address families differ\n");
+ return 0;
+ }
+ if (!ncprange_getwidth(&fe.f_dst, &width))
+ width = 0;
+ if (width == 0)
+ ncprange_init(&fe.f_dst);
+ fe.f_dsttype = addrtype(*argv);
+ argc--;
+ argv++;
+ } else {
+ log_Printf(LogWARN, "Parse: Protocol or address pair expected\n");
+ return 0;
+ }
+
+ if (argc) {
+ if ((pe = getprotobyname(*argv)) == NULL && strcmp(*argv, "all") != 0) {
+ log_Printf(LogWARN, "Parse: %s: Protocol expected\n", *argv);
+ return 0;
+ } else {
+ argc--;
+ argv++;
+ }
+ }
+ } else {
+ log_Printf(LogWARN, "Parse: Protocol or address pair expected\n");
+ return 0;
+ }
+ } else {
+ argc--;
+ argv++;
+ }
+
+ if (argc >= 2 && strcmp(*argv, "timeout") == 0) {
+ fe.timeout = strtoul(argv[1], NULL, 10);
+ argc -= 2;
+ argv += 2;
+ }
+
+ val = 1;
+ fe.f_proto = (pe == NULL) ? 0 : pe->p_proto;
+
+ switch (fe.f_proto) {
+ case IPPROTO_TCP:
+ case IPPROTO_UDP:
+ case IPPROTO_IPIP:
+#ifndef NOINET6
+ case IPPROTO_IPV6:
+#endif
+ val = ParseUdpOrTcp(argc, argv, pe, &fe);
+ break;
+ case IPPROTO_ICMP:
+#ifndef NOINET6
+ case IPPROTO_ICMPV6:
+#endif
+ val = ParseIcmp(argc, argv, &fe);
+ break;
+ default:
+ val = ParseGeneric(argc, &fe);
+ break;
+ }
+
+ log_Printf(LogDEBUG, "Parse: Src: %s\n", ncprange_ntoa(&fe.f_src));
+ log_Printf(LogDEBUG, "Parse: Dst: %s\n", ncprange_ntoa(&fe.f_dst));
+ log_Printf(LogDEBUG, "Parse: Proto: %d\n", fe.f_proto);
+
+ log_Printf(LogDEBUG, "Parse: src: %s (%d)\n",
+ filter_Op2Nam(fe.f_srcop), fe.f_srcport);
+ log_Printf(LogDEBUG, "Parse: dst: %s (%d)\n",
+ filter_Op2Nam(fe.f_dstop), fe.f_dstport);
+ log_Printf(LogDEBUG, "Parse: estab: %u\n", fe.f_estab);
+ log_Printf(LogDEBUG, "Parse: syn: %u\n", fe.f_syn);
+ log_Printf(LogDEBUG, "Parse: finrst: %u\n", fe.f_finrst);
+
+ if (val)
+ *ofp = fe;
+
+ return val;
+}
+
+int
+filter_Set(struct cmdargs const *arg)
+{
+ struct filter *filter;
+
+ if (arg->argc < arg->argn+2)
+ return -1;
+
+ if (!strcmp(arg->argv[arg->argn], "in"))
+ filter = &arg->bundle->filter.in;
+ else if (!strcmp(arg->argv[arg->argn], "out"))
+ filter = &arg->bundle->filter.out;
+ else if (!strcmp(arg->argv[arg->argn], "dial"))
+ filter = &arg->bundle->filter.dial;
+ else if (!strcmp(arg->argv[arg->argn], "alive"))
+ filter = &arg->bundle->filter.alive;
+ else {
+ log_Printf(LogWARN, "filter_Set: %s: Invalid filter name.\n",
+ arg->argv[arg->argn]);
+ return -1;
+ }
+
+ filter_Parse(&arg->bundle->ncp, arg->argc - arg->argn - 1,
+ arg->argv + arg->argn + 1, filter->rule);
+ return 0;
+}
+
+const char *
+filter_Action2Nam(unsigned act)
+{
+ static const char * const actname[] = { " none ", "permit ", " deny " };
+ static char buf[8];
+
+ if (act < MAXFILTERS) {
+ snprintf(buf, sizeof buf, "%6d ", act);
+ return buf;
+ } else if (act >= A_NONE && act < A_NONE + sizeof(actname)/sizeof(char *))
+ return actname[act - A_NONE];
+ else
+ return "?????? ";
+}
+
+static void
+doShowFilter(struct filterent *fp, struct prompt *prompt)
+{
+ struct protoent *pe;
+ int n;
+
+ for (n = 0; n < MAXFILTERS; n++, fp++) {
+ if (fp->f_action != A_NONE) {
+ prompt_Printf(prompt, " %2d %s", n, filter_Action2Nam(fp->f_action));
+ prompt_Printf(prompt, "%c ", fp->f_invert ? '!' : ' ');
+
+ if (ncprange_isset(&fp->f_src))
+ prompt_Printf(prompt, "%s ", addrstr(&fp->f_src, fp->f_srctype));
+ else
+ prompt_Printf(prompt, "any ");
+
+ if (ncprange_isset(&fp->f_dst))
+ prompt_Printf(prompt, "%s ", addrstr(&fp->f_dst, fp->f_dsttype));
+ else
+ prompt_Printf(prompt, "any ");
+
+ if (fp->f_proto) {
+ if ((pe = getprotobynumber(fp->f_proto)) == NULL)
+ prompt_Printf(prompt, "P:%d", fp->f_proto);
+ else
+ prompt_Printf(prompt, "%s", pe->p_name);
+
+ if (fp->f_srcop)
+ prompt_Printf(prompt, " src %s %d", filter_Op2Nam(fp->f_srcop),
+ fp->f_srcport);
+ if (fp->f_dstop)
+ prompt_Printf(prompt, " dst %s %d", filter_Op2Nam(fp->f_dstop),
+ fp->f_dstport);
+ if (fp->f_estab)
+ prompt_Printf(prompt, " estab");
+ if (fp->f_syn)
+ prompt_Printf(prompt, " syn");
+ if (fp->f_finrst)
+ prompt_Printf(prompt, " finrst");
+ } else
+ prompt_Printf(prompt, "all");
+ if (fp->timeout != 0)
+ prompt_Printf(prompt, " timeout %u", fp->timeout);
+ prompt_Printf(prompt, "\n");
+ }
+ }
+}
+
+int
+filter_Show(struct cmdargs const *arg)
+{
+ if (arg->argc > arg->argn+1)
+ return -1;
+
+ if (arg->argc == arg->argn+1) {
+ struct filter *filter;
+
+ if (!strcmp(arg->argv[arg->argn], "in"))
+ filter = &arg->bundle->filter.in;
+ else if (!strcmp(arg->argv[arg->argn], "out"))
+ filter = &arg->bundle->filter.out;
+ else if (!strcmp(arg->argv[arg->argn], "dial"))
+ filter = &arg->bundle->filter.dial;
+ else if (!strcmp(arg->argv[arg->argn], "alive"))
+ filter = &arg->bundle->filter.alive;
+ else
+ return -1;
+ doShowFilter(filter->rule, arg->prompt);
+ } else {
+ struct filter *filter[4];
+ int f;
+
+ filter[0] = &arg->bundle->filter.in;
+ filter[1] = &arg->bundle->filter.out;
+ filter[2] = &arg->bundle->filter.dial;
+ filter[3] = &arg->bundle->filter.alive;
+ for (f = 0; f < 4; f++) {
+ if (f)
+ prompt_Printf(arg->prompt, "\n");
+ prompt_Printf(arg->prompt, "%s:\n", filter[f]->name);
+ doShowFilter(filter[f]->rule, arg->prompt);
+ }
+ }
+
+ return 0;
+}
+
+static const char * const opname[] = {"none", "eq", "gt", "lt"};
+
+const char *
+filter_Op2Nam(unsigned op)
+{
+ if (op >= sizeof opname / sizeof opname[0])
+ return "unknown";
+ return opname[op];
+
+}
+
+static unsigned
+filter_Nam2Op(const char *cp)
+{
+ unsigned op;
+
+ for (op = sizeof opname / sizeof opname[0] - 1; op; op--)
+ if (!strcasecmp(cp, opname[op]))
+ break;
+
+ return op;
+}
+
+void
+filter_AdjustAddr(struct filter *filter, struct ncpaddr *local,
+ struct ncpaddr *remote, struct in_addr *dns)
+{
+ struct filterent *fp;
+ int n;
+
+ for (fp = filter->rule, n = 0; n < MAXFILTERS; fp++, n++)
+ if (fp->f_action != A_NONE) {
+ if (local) {
+ if (fp->f_srctype == T_MYADDR && ncpaddr_family(local) == AF_INET)
+ ncprange_sethost(&fp->f_src, local);
+ if (fp->f_dsttype == T_MYADDR && ncpaddr_family(local) == AF_INET)
+ ncprange_sethost(&fp->f_dst, local);
+#ifndef NOINET6
+ if (fp->f_srctype == T_MYADDR6 && ncpaddr_family(local) == AF_INET6)
+ ncprange_sethost(&fp->f_src, local);
+ if (fp->f_dsttype == T_MYADDR6 && ncpaddr_family(local) == AF_INET6)
+ ncprange_sethost(&fp->f_dst, local);
+#endif
+ }
+ if (remote) {
+ if (fp->f_srctype == T_HISADDR && ncpaddr_family(remote) == AF_INET)
+ ncprange_sethost(&fp->f_src, remote);
+ if (fp->f_dsttype == T_HISADDR && ncpaddr_family(remote) == AF_INET)
+ ncprange_sethost(&fp->f_dst, remote);
+#ifndef NOINET6
+ if (fp->f_srctype == T_HISADDR6 && ncpaddr_family(remote) == AF_INET6)
+ ncprange_sethost(&fp->f_src, remote);
+ if (fp->f_dsttype == T_HISADDR6 && ncpaddr_family(remote) == AF_INET6)
+ ncprange_sethost(&fp->f_dst, remote);
+#endif
+ }
+ if (dns) {
+ if (fp->f_srctype == T_DNS0)
+ ncprange_setip4host(&fp->f_src, dns[0]);
+ if (fp->f_dsttype == T_DNS0)
+ ncprange_setip4host(&fp->f_dst, dns[0]);
+ if (fp->f_srctype == T_DNS1)
+ ncprange_setip4host(&fp->f_src, dns[1]);
+ if (fp->f_dsttype == T_DNS1)
+ ncprange_setip4host(&fp->f_dst, dns[1]);
+ }
+ }
+}
diff --git a/usr.sbin/ppp/filter.h b/usr.sbin/ppp/filter.h
new file mode 100644
index 0000000..ce67420
--- /dev/null
+++ b/usr.sbin/ppp/filter.h
@@ -0,0 +1,101 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+/* Operations - f_srcop, f_dstop */
+#define OP_NONE 0
+#define OP_EQ 1
+#define OP_GT 2
+#define OP_LT 3
+
+/* srctype or dsttype */
+#define T_ADDR 0
+#define T_MYADDR 1
+#define T_MYADDR6 2
+#define T_HISADDR 3
+#define T_HISADDR6 4
+#define T_DNS0 5
+#define T_DNS1 6
+
+/*
+ * There's a struct filterent for each possible filter rule. The
+ * layout is designed to minimise size (there are 4 * MAXFILTERS of
+ * them) - which is also conveniently a power of 2 (32 bytes) on
+ * architectures where sizeof(int)==4 (this makes indexing faster).
+ *
+ * Note that there are four free bits in the initial word for future
+ * extensions.
+ */
+struct filterent {
+ int f_proto; /* Protocol: getprotoby*() */
+ unsigned f_action : 8; /* Filtering action: goto or A_... */
+ unsigned f_srcop : 2; /* Source port operation: OP_... */
+ unsigned f_dstop : 2; /* Destination port operation: OP_... */
+ unsigned f_srctype : 3; /* T_ value of src */
+ unsigned f_dsttype : 3; /* T_ value of dst */
+ unsigned f_estab : 1; /* Check TCP ACK bit */
+ unsigned f_syn : 1; /* Check TCP SYN bit */
+ unsigned f_finrst : 1; /* Check TCP FIN/RST bits */
+ unsigned f_invert : 1; /* true to complement match */
+ struct ncprange f_src; /* Source address and mask */
+ struct ncprange f_dst; /* Destination address and mask */
+ u_short f_srcport; /* Source port, compared with f_srcop */
+ u_short f_dstport; /* Destination port, compared with f_dstop */
+ unsigned timeout; /* Keep alive value for passed packet */
+};
+
+#define MAXFILTERS 40 /* in each filter set */
+
+/* f_action values [0..MAXFILTERS) specify the next filter rule, others are: */
+#define A_NONE (MAXFILTERS)
+#define A_PERMIT (A_NONE+1)
+#define A_DENY (A_PERMIT+1)
+
+struct filter {
+ struct filterent rule[MAXFILTERS]; /* incoming packet filter */
+ const char *name;
+ unsigned fragok : 1;
+ unsigned logok : 1;
+};
+
+/* Which filter set */
+#define FL_IN 0
+#define FL_OUT 1
+#define FL_DIAL 2
+#define FL_KEEP 3
+
+struct ipcp;
+struct cmdargs;
+
+extern int filter_Show(struct cmdargs const *);
+extern int filter_Set(struct cmdargs const *);
+extern const char * filter_Action2Nam(unsigned);
+extern const char *filter_Op2Nam(unsigned);
+extern void filter_AdjustAddr(struct filter *, struct ncpaddr *,
+ struct ncpaddr *, struct in_addr *);
diff --git a/usr.sbin/ppp/fsm.c b/usr.sbin/ppp/fsm.c
new file mode 100644
index 0000000..78530b9
--- /dev/null
+++ b/usr.sbin/ppp/fsm.c
@@ -0,0 +1,1213 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <string.h>
+#include <termios.h>
+
+#include "layer.h"
+#include "ua.h"
+#include "mbuf.h"
+#include "log.h"
+#include "defs.h"
+#include "timer.h"
+#include "fsm.h"
+#include "iplist.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "async.h"
+#include "physical.h"
+#include "proto.h"
+
+static void FsmSendConfigReq(struct fsm *);
+static void FsmSendTerminateReq(struct fsm *);
+static void FsmInitRestartCounter(struct fsm *, int);
+
+typedef void (recvfn)(struct fsm *, struct fsmheader *, struct mbuf *);
+static recvfn FsmRecvConfigReq, FsmRecvConfigAck, FsmRecvConfigNak,
+ FsmRecvConfigRej, FsmRecvTermReq, FsmRecvTermAck,
+ FsmRecvCodeRej, FsmRecvProtoRej, FsmRecvEchoReq,
+ FsmRecvEchoRep, FsmRecvDiscReq, FsmRecvIdent,
+ FsmRecvTimeRemain, FsmRecvResetReq, FsmRecvResetAck;
+
+static const struct fsmcodedesc {
+ recvfn *recv;
+ unsigned check_reqid : 1;
+ unsigned inc_reqid : 1;
+ const char *name;
+} FsmCodes[] = {
+ { FsmRecvConfigReq, 0, 0, "ConfigReq" },
+ { FsmRecvConfigAck, 1, 1, "ConfigAck" },
+ { FsmRecvConfigNak, 1, 1, "ConfigNak" },
+ { FsmRecvConfigRej, 1, 1, "ConfigRej" },
+ { FsmRecvTermReq, 0, 0, "TerminateReq" },
+ { FsmRecvTermAck, 1, 1, "TerminateAck" },
+ { FsmRecvCodeRej, 0, 0, "CodeRej" },
+ { FsmRecvProtoRej, 0, 0, "ProtocolRej" },
+ { FsmRecvEchoReq, 0, 0, "EchoRequest" },
+ { FsmRecvEchoRep, 0, 0, "EchoReply" },
+ { FsmRecvDiscReq, 0, 0, "DiscardReq" },
+ { FsmRecvIdent, 0, 1, "Ident" },
+ { FsmRecvTimeRemain,0, 0, "TimeRemain" },
+ { FsmRecvResetReq, 0, 0, "ResetReq" },
+ { FsmRecvResetAck, 0, 1, "ResetAck" }
+};
+
+static const char *
+Code2Nam(u_int code)
+{
+ if (code == 0 || code > sizeof FsmCodes / sizeof FsmCodes[0])
+ return "Unknown";
+ return FsmCodes[code-1].name;
+}
+
+const char *
+State2Nam(u_int state)
+{
+ static const char * const StateNames[] = {
+ "Initial", "Starting", "Closed", "Stopped", "Closing", "Stopping",
+ "Req-Sent", "Ack-Rcvd", "Ack-Sent", "Opened",
+ };
+
+ if (state >= sizeof StateNames / sizeof StateNames[0])
+ return "unknown";
+ return StateNames[state];
+}
+
+static void
+StoppedTimeout(void *v)
+{
+ struct fsm *fp = (struct fsm *)v;
+
+ log_Printf(fp->LogLevel, "%s: Stopped timer expired\n", fp->link->name);
+ if (fp->OpenTimer.state == TIMER_RUNNING) {
+ log_Printf(LogWARN, "%s: %s: aborting open delay due to stopped timer\n",
+ fp->link->name, fp->name);
+ timer_Stop(&fp->OpenTimer);
+ }
+ if (fp->state == ST_STOPPED)
+ fsm2initial(fp);
+}
+
+void
+fsm_Init(struct fsm *fp, const char *name, u_short proto, int mincode,
+ int maxcode, int LogLevel, struct bundle *bundle,
+ struct link *l, const struct fsm_parent *parent,
+ struct fsm_callbacks *fn, const char * const timer_names[3])
+{
+ fp->name = name;
+ fp->proto = proto;
+ fp->min_code = mincode;
+ fp->max_code = maxcode;
+ fp->state = fp->min_code > CODE_TERMACK ? ST_OPENED : ST_INITIAL;
+ fp->reqid = 1;
+ fp->restart = 1;
+ fp->more.reqs = fp->more.naks = fp->more.rejs = 3;
+ memset(&fp->FsmTimer, '\0', sizeof fp->FsmTimer);
+ memset(&fp->OpenTimer, '\0', sizeof fp->OpenTimer);
+ memset(&fp->StoppedTimer, '\0', sizeof fp->StoppedTimer);
+ fp->LogLevel = LogLevel;
+ fp->link = l;
+ fp->bundle = bundle;
+ fp->parent = parent;
+ fp->fn = fn;
+ fp->FsmTimer.name = timer_names[0];
+ fp->OpenTimer.name = timer_names[1];
+ fp->StoppedTimer.name = timer_names[2];
+}
+
+static void
+NewState(struct fsm *fp, int new)
+{
+ log_Printf(fp->LogLevel, "%s: State change %s --> %s\n",
+ fp->link->name, State2Nam(fp->state), State2Nam(new));
+ if (fp->state == ST_STOPPED && fp->StoppedTimer.state == TIMER_RUNNING)
+ timer_Stop(&fp->StoppedTimer);
+ fp->state = new;
+ if ((new >= ST_INITIAL && new <= ST_STOPPED) || (new == ST_OPENED)) {
+ timer_Stop(&fp->FsmTimer);
+ if (new == ST_STOPPED && fp->StoppedTimer.load) {
+ timer_Stop(&fp->StoppedTimer);
+ fp->StoppedTimer.func = StoppedTimeout;
+ fp->StoppedTimer.arg = (void *) fp;
+ timer_Start(&fp->StoppedTimer);
+ }
+ }
+}
+
+void
+fsm_Output(struct fsm *fp, u_int code, u_int id, u_char *ptr, unsigned count,
+ int mtype)
+{
+ int plen;
+ struct fsmheader lh;
+ struct mbuf *bp;
+
+ if (log_IsKept(fp->LogLevel)) {
+ log_Printf(fp->LogLevel, "%s: Send%s(%d) state = %s\n",
+ fp->link->name, Code2Nam(code), id, State2Nam(fp->state));
+ switch (code) {
+ case CODE_CONFIGREQ:
+ case CODE_CONFIGACK:
+ case CODE_CONFIGREJ:
+ case CODE_CONFIGNAK:
+ (*fp->fn->DecodeConfig)(fp, ptr, ptr + count, MODE_NOP, NULL);
+ if (count < sizeof(struct fsm_opt_hdr))
+ log_Printf(fp->LogLevel, " [EMPTY]\n");
+ break;
+ }
+ }
+
+ plen = sizeof(struct fsmheader) + count;
+ lh.code = code;
+ lh.id = id;
+ lh.length = htons(plen);
+ bp = m_get(plen, mtype);
+ memcpy(MBUF_CTOP(bp), &lh, sizeof(struct fsmheader));
+ if (count)
+ memcpy(MBUF_CTOP(bp) + sizeof(struct fsmheader), ptr, count);
+ log_DumpBp(LogDEBUG, "fsm_Output", bp);
+ link_PushPacket(fp->link, bp, fp->bundle, LINK_QUEUES(fp->link) - 1,
+ fp->proto);
+
+ if (code == CODE_CONFIGREJ)
+ lcp_SendIdentification(&fp->link->lcp);
+}
+
+static void
+FsmOpenNow(void *v)
+{
+ struct fsm *fp = (struct fsm *)v;
+
+ timer_Stop(&fp->OpenTimer);
+ if (fp->state <= ST_STOPPED) {
+ if (fp->state != ST_STARTING) {
+ /*
+ * In practice, we're only here in ST_STOPPED (when delaying the
+ * first config request) or ST_CLOSED (when openmode == 0).
+ *
+ * The ST_STOPPED bit is breaking the RFC already :-(
+ *
+ * According to the RFC (1661) state transition table, a TLS isn't
+ * required for an Open event when state == Closed, but the RFC
+ * must be wrong as TLS hasn't yet been called (since the last TLF)
+ * ie, Initial gets an `Up' event, Closing gets a RTA etc.
+ */
+ (*fp->fn->LayerStart)(fp);
+ (*fp->parent->LayerStart)(fp->parent->object, fp);
+ }
+ FsmInitRestartCounter(fp, FSM_REQ_TIMER);
+ FsmSendConfigReq(fp);
+ NewState(fp, ST_REQSENT);
+ }
+}
+
+void
+fsm_Open(struct fsm *fp)
+{
+ switch (fp->state) {
+ case ST_INITIAL:
+ NewState(fp, ST_STARTING);
+ (*fp->fn->LayerStart)(fp);
+ (*fp->parent->LayerStart)(fp->parent->object, fp);
+ break;
+ case ST_CLOSED:
+ if (fp->open_mode == OPEN_PASSIVE) {
+ NewState(fp, ST_STOPPED); /* XXX: This is a hack ! */
+ } else if (fp->open_mode > 0) {
+ if (fp->open_mode > 1)
+ log_Printf(LogPHASE, "%s: Entering STOPPED state for %d seconds\n",
+ fp->link->name, fp->open_mode);
+ NewState(fp, ST_STOPPED); /* XXX: This is a not-so-bad hack ! */
+ timer_Stop(&fp->OpenTimer);
+ fp->OpenTimer.load = fp->open_mode * SECTICKS;
+ fp->OpenTimer.func = FsmOpenNow;
+ fp->OpenTimer.arg = (void *)fp;
+ timer_Start(&fp->OpenTimer);
+ } else
+ FsmOpenNow(fp);
+ break;
+ case ST_STOPPED: /* XXX: restart option */
+ case ST_REQSENT:
+ case ST_ACKRCVD:
+ case ST_ACKSENT:
+ case ST_OPENED: /* XXX: restart option */
+ break;
+ case ST_CLOSING: /* XXX: restart option */
+ case ST_STOPPING: /* XXX: restart option */
+ NewState(fp, ST_STOPPING);
+ break;
+ }
+}
+
+void
+fsm_Up(struct fsm *fp)
+{
+ switch (fp->state) {
+ case ST_INITIAL:
+ log_Printf(fp->LogLevel, "FSM: Using \"%s\" as a transport\n",
+ fp->link->name);
+ NewState(fp, ST_CLOSED);
+ break;
+ case ST_STARTING:
+ FsmInitRestartCounter(fp, FSM_REQ_TIMER);
+ FsmSendConfigReq(fp);
+ NewState(fp, ST_REQSENT);
+ break;
+ default:
+ log_Printf(fp->LogLevel, "%s: Oops, Up at %s\n",
+ fp->link->name, State2Nam(fp->state));
+ break;
+ }
+}
+
+void
+fsm_Down(struct fsm *fp)
+{
+ switch (fp->state) {
+ case ST_CLOSED:
+ NewState(fp, ST_INITIAL);
+ break;
+ case ST_CLOSING:
+ /* This TLF contradicts the RFC (1661), which ``misses it out'' ! */
+ (*fp->fn->LayerFinish)(fp);
+ NewState(fp, ST_INITIAL);
+ (*fp->parent->LayerFinish)(fp->parent->object, fp);
+ break;
+ case ST_STOPPED:
+ NewState(fp, ST_STARTING);
+ (*fp->fn->LayerStart)(fp);
+ (*fp->parent->LayerStart)(fp->parent->object, fp);
+ break;
+ case ST_STOPPING:
+ case ST_REQSENT:
+ case ST_ACKRCVD:
+ case ST_ACKSENT:
+ NewState(fp, ST_STARTING);
+ break;
+ case ST_OPENED:
+ (*fp->fn->LayerDown)(fp);
+ NewState(fp, ST_STARTING);
+ (*fp->parent->LayerDown)(fp->parent->object, fp);
+ break;
+ }
+}
+
+void
+fsm_Close(struct fsm *fp)
+{
+ switch (fp->state) {
+ case ST_STARTING:
+ (*fp->fn->LayerFinish)(fp);
+ NewState(fp, ST_INITIAL);
+ (*fp->parent->LayerFinish)(fp->parent->object, fp);
+ break;
+ case ST_STOPPED:
+ NewState(fp, ST_CLOSED);
+ break;
+ case ST_STOPPING:
+ NewState(fp, ST_CLOSING);
+ break;
+ case ST_OPENED:
+ (*fp->fn->LayerDown)(fp);
+ if (fp->state == ST_OPENED) {
+ FsmInitRestartCounter(fp, FSM_TRM_TIMER);
+ FsmSendTerminateReq(fp);
+ NewState(fp, ST_CLOSING);
+ (*fp->parent->LayerDown)(fp->parent->object, fp);
+ }
+ break;
+ case ST_REQSENT:
+ case ST_ACKRCVD:
+ case ST_ACKSENT:
+ FsmInitRestartCounter(fp, FSM_TRM_TIMER);
+ FsmSendTerminateReq(fp);
+ NewState(fp, ST_CLOSING);
+ break;
+ }
+}
+
+/*
+ * Send functions
+ */
+static void
+FsmSendConfigReq(struct fsm *fp)
+{
+ if (fp->more.reqs-- > 0 && fp->restart-- > 0) {
+ (*fp->fn->SendConfigReq)(fp);
+ timer_Start(&fp->FsmTimer); /* Start restart timer */
+ } else {
+ if (fp->more.reqs < 0)
+ log_Printf(LogPHASE, "%s: Too many %s REQs sent - abandoning "
+ "negotiation\n", fp->link->name, fp->name);
+ lcp_SendIdentification(&fp->link->lcp);
+ fsm_Close(fp);
+ }
+}
+
+static void
+FsmSendTerminateReq(struct fsm *fp)
+{
+ fsm_Output(fp, CODE_TERMREQ, fp->reqid, NULL, 0, MB_UNKNOWN);
+ (*fp->fn->SentTerminateReq)(fp);
+ timer_Start(&fp->FsmTimer); /* Start restart timer */
+ fp->restart--; /* Decrement restart counter */
+}
+
+/*
+ * Timeout actions
+ */
+static void
+FsmTimeout(void *v)
+{
+ struct fsm *fp = (struct fsm *)v;
+
+ if (fp->restart) {
+ switch (fp->state) {
+ case ST_CLOSING:
+ case ST_STOPPING:
+ FsmSendTerminateReq(fp);
+ break;
+ case ST_REQSENT:
+ case ST_ACKSENT:
+ FsmSendConfigReq(fp);
+ break;
+ case ST_ACKRCVD:
+ FsmSendConfigReq(fp);
+ NewState(fp, ST_REQSENT);
+ break;
+ }
+ timer_Start(&fp->FsmTimer);
+ } else {
+ switch (fp->state) {
+ case ST_CLOSING:
+ (*fp->fn->LayerFinish)(fp);
+ NewState(fp, ST_CLOSED);
+ (*fp->parent->LayerFinish)(fp->parent->object, fp);
+ break;
+ case ST_STOPPING:
+ (*fp->fn->LayerFinish)(fp);
+ NewState(fp, ST_STOPPED);
+ (*fp->parent->LayerFinish)(fp->parent->object, fp);
+ break;
+ case ST_REQSENT: /* XXX: 3p */
+ case ST_ACKSENT:
+ case ST_ACKRCVD:
+ (*fp->fn->LayerFinish)(fp);
+ NewState(fp, ST_STOPPED);
+ (*fp->parent->LayerFinish)(fp->parent->object, fp);
+ break;
+ }
+ }
+}
+
+static void
+FsmInitRestartCounter(struct fsm *fp, int what)
+{
+ timer_Stop(&fp->FsmTimer);
+ fp->FsmTimer.func = FsmTimeout;
+ fp->FsmTimer.arg = (void *)fp;
+ (*fp->fn->InitRestartCounter)(fp, what);
+}
+
+/*
+ * Actions when receive packets
+ */
+static void
+FsmRecvConfigReq(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp)
+/* RCR */
+{
+ struct fsm_decode dec;
+ int plen, flen;
+ int ackaction = 0;
+ u_char *cp;
+
+ bp = m_pullup(bp);
+ plen = m_length(bp);
+ flen = ntohs(lhp->length) - sizeof *lhp;
+ if (plen < flen) {
+ log_Printf(LogWARN, "%s: FsmRecvConfigReq: plen (%d) < flen (%d)\n",
+ fp->link->name, plen, flen);
+ m_freem(bp);
+ return;
+ }
+
+ /* Some things must be done before we Decode the packet */
+ switch (fp->state) {
+ case ST_OPENED:
+ (*fp->fn->LayerDown)(fp);
+ }
+
+ dec.ackend = dec.ack;
+ dec.nakend = dec.nak;
+ dec.rejend = dec.rej;
+ cp = MBUF_CTOP(bp);
+ (*fp->fn->DecodeConfig)(fp, cp, cp + flen, MODE_REQ, &dec);
+ if (flen < (int)sizeof(struct fsm_opt_hdr))
+ log_Printf(fp->LogLevel, " [EMPTY]\n");
+
+ if (dec.nakend == dec.nak && dec.rejend == dec.rej)
+ ackaction = 1;
+
+ /* Check and process easy case */
+ switch (fp->state) {
+ case ST_INITIAL:
+ if (fp->proto == PROTO_CCP && fp->link->lcp.fsm.state == ST_OPENED) {
+ /*
+ * ccp_SetOpenMode() leaves us in initial if we're disabling
+ * & denying everything.
+ */
+ bp = m_prepend(bp, lhp, sizeof *lhp, 2);
+ bp = proto_Prepend(bp, fp->proto, 0, 0);
+ bp = m_pullup(bp);
+ lcp_SendProtoRej(&fp->link->lcp, MBUF_CTOP(bp), bp->m_len);
+ m_freem(bp);
+ return;
+ }
+ /* Drop through */
+ case ST_STARTING:
+ log_Printf(fp->LogLevel, "%s: Oops, RCR in %s.\n",
+ fp->link->name, State2Nam(fp->state));
+ m_freem(bp);
+ return;
+ case ST_CLOSED:
+ (*fp->fn->SendTerminateAck)(fp, lhp->id);
+ m_freem(bp);
+ return;
+ case ST_CLOSING:
+ log_Printf(fp->LogLevel, "%s: Error: Got ConfigReq while state = %s\n",
+ fp->link->name, State2Nam(fp->state));
+ case ST_STOPPING:
+ m_freem(bp);
+ return;
+ case ST_STOPPED:
+ FsmInitRestartCounter(fp, FSM_REQ_TIMER);
+ /* Drop through */
+ case ST_OPENED:
+ FsmSendConfigReq(fp);
+ break;
+ }
+
+ if (dec.rejend != dec.rej)
+ fsm_Output(fp, CODE_CONFIGREJ, lhp->id, dec.rej, dec.rejend - dec.rej,
+ MB_UNKNOWN);
+ if (dec.nakend != dec.nak)
+ fsm_Output(fp, CODE_CONFIGNAK, lhp->id, dec.nak, dec.nakend - dec.nak,
+ MB_UNKNOWN);
+ if (ackaction)
+ fsm_Output(fp, CODE_CONFIGACK, lhp->id, dec.ack, dec.ackend - dec.ack,
+ MB_UNKNOWN);
+
+ switch (fp->state) {
+ case ST_STOPPED:
+ /*
+ * According to the RFC (1661) state transition table, a TLS isn't
+ * required for a RCR when state == ST_STOPPED, but the RFC
+ * must be wrong as TLS hasn't yet been called (since the last TLF)
+ */
+ (*fp->fn->LayerStart)(fp);
+ (*fp->parent->LayerStart)(fp->parent->object, fp);
+ /* FALLTHROUGH */
+
+ case ST_OPENED:
+ if (ackaction)
+ NewState(fp, ST_ACKSENT);
+ else
+ NewState(fp, ST_REQSENT);
+ (*fp->parent->LayerDown)(fp->parent->object, fp);
+ break;
+ case ST_REQSENT:
+ if (ackaction)
+ NewState(fp, ST_ACKSENT);
+ break;
+ case ST_ACKRCVD:
+ if (ackaction) {
+ NewState(fp, ST_OPENED);
+ if ((*fp->fn->LayerUp)(fp))
+ (*fp->parent->LayerUp)(fp->parent->object, fp);
+ else {
+ (*fp->fn->LayerDown)(fp);
+ FsmInitRestartCounter(fp, FSM_TRM_TIMER);
+ FsmSendTerminateReq(fp);
+ NewState(fp, ST_CLOSING);
+ lcp_SendIdentification(&fp->link->lcp);
+ }
+ }
+ break;
+ case ST_ACKSENT:
+ if (!ackaction)
+ NewState(fp, ST_REQSENT);
+ break;
+ }
+ m_freem(bp);
+
+ if (dec.rejend != dec.rej && --fp->more.rejs <= 0) {
+ log_Printf(LogPHASE, "%s: Too many %s REJs sent - abandoning negotiation\n",
+ fp->link->name, fp->name);
+ lcp_SendIdentification(&fp->link->lcp);
+ fsm_Close(fp);
+ }
+
+ if (dec.nakend != dec.nak && --fp->more.naks <= 0) {
+ log_Printf(LogPHASE, "%s: Too many %s NAKs sent - abandoning negotiation\n",
+ fp->link->name, fp->name);
+ lcp_SendIdentification(&fp->link->lcp);
+ fsm_Close(fp);
+ }
+}
+
+static void
+FsmRecvConfigAck(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp)
+/* RCA */
+{
+ struct fsm_decode dec;
+ int plen, flen;
+ u_char *cp;
+
+ plen = m_length(bp);
+ flen = ntohs(lhp->length) - sizeof *lhp;
+ if (plen < flen) {
+ m_freem(bp);
+ return;
+ }
+
+ bp = m_pullup(bp);
+ dec.ackend = dec.ack;
+ dec.nakend = dec.nak;
+ dec.rejend = dec.rej;
+ cp = MBUF_CTOP(bp);
+ (*fp->fn->DecodeConfig)(fp, cp, cp + flen, MODE_ACK, &dec);
+ if (flen < (int)sizeof(struct fsm_opt_hdr))
+ log_Printf(fp->LogLevel, " [EMPTY]\n");
+
+ switch (fp->state) {
+ case ST_CLOSED:
+ case ST_STOPPED:
+ (*fp->fn->SendTerminateAck)(fp, lhp->id);
+ break;
+ case ST_CLOSING:
+ case ST_STOPPING:
+ break;
+ case ST_REQSENT:
+ FsmInitRestartCounter(fp, FSM_REQ_TIMER);
+ NewState(fp, ST_ACKRCVD);
+ break;
+ case ST_ACKRCVD:
+ FsmSendConfigReq(fp);
+ NewState(fp, ST_REQSENT);
+ break;
+ case ST_ACKSENT:
+ FsmInitRestartCounter(fp, FSM_REQ_TIMER);
+ NewState(fp, ST_OPENED);
+ if ((*fp->fn->LayerUp)(fp))
+ (*fp->parent->LayerUp)(fp->parent->object, fp);
+ else {
+ (*fp->fn->LayerDown)(fp);
+ FsmInitRestartCounter(fp, FSM_TRM_TIMER);
+ FsmSendTerminateReq(fp);
+ NewState(fp, ST_CLOSING);
+ lcp_SendIdentification(&fp->link->lcp);
+ }
+ break;
+ case ST_OPENED:
+ (*fp->fn->LayerDown)(fp);
+ FsmSendConfigReq(fp);
+ NewState(fp, ST_REQSENT);
+ (*fp->parent->LayerDown)(fp->parent->object, fp);
+ break;
+ }
+ m_freem(bp);
+}
+
+static void
+FsmRecvConfigNak(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp)
+/* RCN */
+{
+ struct fsm_decode dec;
+ int plen, flen;
+ u_char *cp;
+
+ plen = m_length(bp);
+ flen = ntohs(lhp->length) - sizeof *lhp;
+ if (plen < flen) {
+ m_freem(bp);
+ return;
+ }
+
+ /*
+ * Check and process easy case
+ */
+ switch (fp->state) {
+ case ST_INITIAL:
+ case ST_STARTING:
+ log_Printf(fp->LogLevel, "%s: Oops, RCN in %s.\n",
+ fp->link->name, State2Nam(fp->state));
+ m_freem(bp);
+ return;
+ case ST_CLOSED:
+ case ST_STOPPED:
+ (*fp->fn->SendTerminateAck)(fp, lhp->id);
+ m_freem(bp);
+ return;
+ case ST_CLOSING:
+ case ST_STOPPING:
+ m_freem(bp);
+ return;
+ }
+
+ bp = m_pullup(bp);
+ dec.ackend = dec.ack;
+ dec.nakend = dec.nak;
+ dec.rejend = dec.rej;
+ cp = MBUF_CTOP(bp);
+ (*fp->fn->DecodeConfig)(fp, cp, cp + flen, MODE_NAK, &dec);
+ if (flen < (int)sizeof(struct fsm_opt_hdr))
+ log_Printf(fp->LogLevel, " [EMPTY]\n");
+
+ switch (fp->state) {
+ case ST_REQSENT:
+ case ST_ACKSENT:
+ FsmInitRestartCounter(fp, FSM_REQ_TIMER);
+ FsmSendConfigReq(fp);
+ break;
+ case ST_OPENED:
+ (*fp->fn->LayerDown)(fp);
+ FsmSendConfigReq(fp);
+ NewState(fp, ST_REQSENT);
+ (*fp->parent->LayerDown)(fp->parent->object, fp);
+ break;
+ case ST_ACKRCVD:
+ FsmSendConfigReq(fp);
+ NewState(fp, ST_REQSENT);
+ break;
+ }
+
+ m_freem(bp);
+}
+
+static void
+FsmRecvTermReq(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp)
+/* RTR */
+{
+ switch (fp->state) {
+ case ST_INITIAL:
+ case ST_STARTING:
+ log_Printf(fp->LogLevel, "%s: Oops, RTR in %s\n",
+ fp->link->name, State2Nam(fp->state));
+ break;
+ case ST_CLOSED:
+ case ST_STOPPED:
+ case ST_CLOSING:
+ case ST_STOPPING:
+ case ST_REQSENT:
+ (*fp->fn->SendTerminateAck)(fp, lhp->id);
+ break;
+ case ST_ACKRCVD:
+ case ST_ACKSENT:
+ (*fp->fn->SendTerminateAck)(fp, lhp->id);
+ NewState(fp, ST_REQSENT);
+ break;
+ case ST_OPENED:
+ (*fp->fn->LayerDown)(fp);
+ (*fp->fn->SendTerminateAck)(fp, lhp->id);
+ FsmInitRestartCounter(fp, FSM_TRM_TIMER);
+ timer_Start(&fp->FsmTimer); /* Start restart timer */
+ fp->restart = 0;
+ NewState(fp, ST_STOPPING);
+ (*fp->parent->LayerDown)(fp->parent->object, fp);
+ /* A delayed ST_STOPPED is now scheduled */
+ break;
+ }
+ m_freem(bp);
+}
+
+static void
+FsmRecvTermAck(struct fsm *fp, struct fsmheader *lhp __unused, struct mbuf *bp)
+/* RTA */
+{
+ switch (fp->state) {
+ case ST_CLOSING:
+ (*fp->fn->LayerFinish)(fp);
+ NewState(fp, ST_CLOSED);
+ (*fp->parent->LayerFinish)(fp->parent->object, fp);
+ break;
+ case ST_STOPPING:
+ (*fp->fn->LayerFinish)(fp);
+ NewState(fp, ST_STOPPED);
+ (*fp->parent->LayerFinish)(fp->parent->object, fp);
+ break;
+ case ST_ACKRCVD:
+ NewState(fp, ST_REQSENT);
+ break;
+ case ST_OPENED:
+ (*fp->fn->LayerDown)(fp);
+ FsmSendConfigReq(fp);
+ NewState(fp, ST_REQSENT);
+ (*fp->parent->LayerDown)(fp->parent->object, fp);
+ break;
+ }
+ m_freem(bp);
+}
+
+static void
+FsmRecvConfigRej(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp)
+/* RCJ */
+{
+ struct fsm_decode dec;
+ size_t plen;
+ int flen;
+ u_char *cp;
+
+ plen = m_length(bp);
+ flen = ntohs(lhp->length) - sizeof *lhp;
+ if ((int)plen < flen) {
+ m_freem(bp);
+ return;
+ }
+
+ lcp_SendIdentification(&fp->link->lcp);
+
+ /*
+ * Check and process easy case
+ */
+ switch (fp->state) {
+ case ST_INITIAL:
+ case ST_STARTING:
+ log_Printf(fp->LogLevel, "%s: Oops, RCJ in %s.\n",
+ fp->link->name, State2Nam(fp->state));
+ m_freem(bp);
+ return;
+ case ST_CLOSED:
+ case ST_STOPPED:
+ (*fp->fn->SendTerminateAck)(fp, lhp->id);
+ m_freem(bp);
+ return;
+ case ST_CLOSING:
+ case ST_STOPPING:
+ m_freem(bp);
+ return;
+ }
+
+ bp = m_pullup(bp);
+ dec.ackend = dec.ack;
+ dec.nakend = dec.nak;
+ dec.rejend = dec.rej;
+ cp = MBUF_CTOP(bp);
+ (*fp->fn->DecodeConfig)(fp, cp, cp + flen, MODE_REJ, &dec);
+ if (flen < (int)sizeof(struct fsm_opt_hdr))
+ log_Printf(fp->LogLevel, " [EMPTY]\n");
+
+ switch (fp->state) {
+ case ST_REQSENT:
+ case ST_ACKSENT:
+ FsmInitRestartCounter(fp, FSM_REQ_TIMER);
+ FsmSendConfigReq(fp);
+ break;
+ case ST_OPENED:
+ (*fp->fn->LayerDown)(fp);
+ FsmSendConfigReq(fp);
+ NewState(fp, ST_REQSENT);
+ (*fp->parent->LayerDown)(fp->parent->object, fp);
+ break;
+ case ST_ACKRCVD:
+ FsmSendConfigReq(fp);
+ NewState(fp, ST_REQSENT);
+ break;
+ }
+ m_freem(bp);
+}
+
+static void
+FsmRecvCodeRej(struct fsm *fp __unused, struct fsmheader *lhp __unused,
+ struct mbuf *bp)
+{
+ m_freem(bp);
+}
+
+static void
+FsmRecvProtoRej(struct fsm *fp, struct fsmheader *lhp __unused, struct mbuf *bp)
+{
+ struct physical *p = link2physical(fp->link);
+ u_short proto;
+
+ if (m_length(bp) < 2) {
+ m_freem(bp);
+ return;
+ }
+ bp = mbuf_Read(bp, &proto, 2);
+ proto = ntohs(proto);
+ log_Printf(fp->LogLevel, "%s: -- Protocol 0x%04x (%s) was rejected!\n",
+ fp->link->name, proto, hdlc_Protocol2Nam(proto));
+
+ switch (proto) {
+ case PROTO_LQR:
+ if (p)
+ lqr_Stop(p, LQM_LQR);
+ else
+ log_Printf(LogERROR, "%s: FsmRecvProtoRej: Not a physical link !\n",
+ fp->link->name);
+ break;
+ case PROTO_CCP:
+ if (fp->proto == PROTO_LCP) {
+ fp = &fp->link->ccp.fsm;
+ /* Despite the RFC (1661), don't do an out-of-place TLF */
+ /* (*fp->fn->LayerFinish)(fp); */
+ switch (fp->state) {
+ case ST_CLOSED:
+ case ST_CLOSING:
+ NewState(fp, ST_CLOSED);
+ break;
+ default:
+ NewState(fp, ST_STOPPED);
+ break;
+ }
+ /* See above */
+ /* (*fp->parent->LayerFinish)(fp->parent->object, fp); */
+ }
+ break;
+ case PROTO_IPCP:
+ if (fp->proto == PROTO_LCP) {
+ log_Printf(LogPHASE, "%s: IPCP protocol reject closes IPCP !\n",
+ fp->link->name);
+ fsm_Close(&fp->bundle->ncp.ipcp.fsm);
+ }
+ break;
+#ifndef NOINET6
+ case PROTO_IPV6CP:
+ if (fp->proto == PROTO_LCP) {
+ log_Printf(LogPHASE, "%s: IPV6CP protocol reject closes IPV6CP !\n",
+ fp->link->name);
+ fsm_Close(&fp->bundle->ncp.ipv6cp.fsm);
+ }
+ break;
+#endif
+ case PROTO_MP:
+ if (fp->proto == PROTO_LCP) {
+ struct lcp *lcp = fsm2lcp(fp);
+
+ if (lcp->want_mrru && lcp->his_mrru) {
+ log_Printf(LogPHASE, "%s: MP protocol reject is fatal !\n",
+ fp->link->name);
+ fsm_Close(fp);
+ }
+ }
+ break;
+ }
+ m_freem(bp);
+}
+
+static void
+FsmRecvEchoReq(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp)
+{
+ struct lcp *lcp = fsm2lcp(fp);
+ u_char *cp;
+ u_int32_t magic;
+
+ bp = m_pullup(bp);
+ m_settype(bp, MB_ECHOIN);
+
+ if (lcp && ntohs(lhp->length) - sizeof *lhp >= 4) {
+ cp = MBUF_CTOP(bp);
+ ua_ntohl(cp, &magic);
+ if (magic != lcp->his_magic) {
+ log_Printf(fp->LogLevel, "%s: RecvEchoReq: magic 0x%08lx is wrong,"
+ " expecting 0x%08lx\n", fp->link->name, (u_long)magic,
+ (u_long)lcp->his_magic);
+ /* XXX: We should send terminate request */
+ }
+ if (fp->state == ST_OPENED) {
+ ua_htonl(&lcp->want_magic, cp); /* local magic */
+ fsm_Output(fp, CODE_ECHOREP, lhp->id, cp,
+ ntohs(lhp->length) - sizeof *lhp, MB_ECHOOUT);
+ }
+ }
+ m_freem(bp);
+}
+
+static void
+FsmRecvEchoRep(struct fsm *fp, struct fsmheader *lhp __unused, struct mbuf *bp)
+{
+ if (fsm2lcp(fp))
+ bp = lqr_RecvEcho(fp, bp);
+
+ m_freem(bp);
+}
+
+static void
+FsmRecvDiscReq(struct fsm *fp __unused, struct fsmheader *lhp __unused,
+ struct mbuf *bp)
+{
+ m_freem(bp);
+}
+
+static void
+FsmRecvIdent(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp)
+{
+ u_int32_t magic;
+ u_short len;
+ u_char *cp;
+
+ len = ntohs(lhp->length) - sizeof *lhp;
+ if (len >= 4) {
+ bp = m_pullup(m_append(bp, "", 1));
+ cp = MBUF_CTOP(bp);
+ ua_ntohl(cp, &magic);
+ if (magic != fp->link->lcp.his_magic)
+ log_Printf(fp->LogLevel, "%s: RecvIdent: magic 0x%08lx is wrong,"
+ " expecting 0x%08lx\n", fp->link->name, (u_long)magic,
+ (u_long)fp->link->lcp.his_magic);
+ cp[len] = '\0';
+ lcp_RecvIdentification(&fp->link->lcp, cp + 4);
+ }
+ m_freem(bp);
+}
+
+static void
+FsmRecvTimeRemain(struct fsm *fp __unused, struct fsmheader *lhp __unused,
+ struct mbuf *bp)
+{
+ m_freem(bp);
+}
+
+static void
+FsmRecvResetReq(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp)
+{
+ if ((*fp->fn->RecvResetReq)(fp)) {
+ /*
+ * All sendable compressed packets are queued in the first (lowest
+ * priority) modem output queue.... dump 'em to the priority queue
+ * so that they arrive at the peer before our ResetAck.
+ */
+ link_SequenceQueue(fp->link);
+ fsm_Output(fp, CODE_RESETACK, lhp->id, NULL, 0, MB_CCPOUT);
+ }
+ m_freem(bp);
+}
+
+static void
+FsmRecvResetAck(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp)
+{
+ (*fp->fn->RecvResetAck)(fp, lhp->id);
+ m_freem(bp);
+}
+
+void
+fsm_Input(struct fsm *fp, struct mbuf *bp)
+{
+ size_t len;
+ struct fsmheader lh;
+ const struct fsmcodedesc *codep;
+
+ len = m_length(bp);
+ if (len < sizeof(struct fsmheader)) {
+ m_freem(bp);
+ return;
+ }
+ bp = mbuf_Read(bp, &lh, sizeof lh);
+
+ if (ntohs(lh.length) > len) {
+ log_Printf(LogWARN, "%s: Oops: Got %zu bytes but %d byte payload "
+ "- dropped\n", fp->link->name, len, (int)ntohs(lh.length));
+ m_freem(bp);
+ return;
+ }
+
+ if (lh.code < fp->min_code || lh.code > fp->max_code ||
+ lh.code > sizeof FsmCodes / sizeof *FsmCodes) {
+ /*
+ * Use a private id. This is really a response-type packet, but we
+ * MUST send a unique id for each REQ....
+ */
+ static u_char id;
+
+ bp = m_prepend(bp, &lh, sizeof lh, 0);
+ bp = m_pullup(bp);
+ fsm_Output(fp, CODE_CODEREJ, id++, MBUF_CTOP(bp), bp->m_len, MB_UNKNOWN);
+ m_freem(bp);
+ return;
+ }
+
+ codep = FsmCodes + lh.code - 1;
+ if (lh.id != fp->reqid && codep->check_reqid &&
+ Enabled(fp->bundle, OPT_IDCHECK)) {
+ log_Printf(fp->LogLevel, "%s: Recv%s(%d), dropped (expected %d)\n",
+ fp->link->name, codep->name, lh.id, fp->reqid);
+ return;
+ }
+
+ log_Printf(fp->LogLevel, "%s: Recv%s(%d) state = %s\n",
+ fp->link->name, codep->name, lh.id, State2Nam(fp->state));
+
+ if (codep->inc_reqid && (lh.id == fp->reqid ||
+ (!Enabled(fp->bundle, OPT_IDCHECK) && codep->check_reqid)))
+ fp->reqid++; /* That's the end of that ``exchange''.... */
+
+ (*codep->recv)(fp, &lh, bp);
+}
+
+int
+fsm_NullRecvResetReq(struct fsm *fp)
+{
+ log_Printf(fp->LogLevel, "%s: Oops - received unexpected reset req\n",
+ fp->link->name);
+ return 1;
+}
+
+void
+fsm_NullRecvResetAck(struct fsm *fp, u_char id __unused)
+{
+ log_Printf(fp->LogLevel, "%s: Oops - received unexpected reset ack\n",
+ fp->link->name);
+}
+
+void
+fsm_Reopen(struct fsm *fp)
+{
+ if (fp->state == ST_OPENED) {
+ (*fp->fn->LayerDown)(fp);
+ FsmInitRestartCounter(fp, FSM_REQ_TIMER);
+ FsmSendConfigReq(fp);
+ NewState(fp, ST_REQSENT);
+ (*fp->parent->LayerDown)(fp->parent->object, fp);
+ }
+}
+
+void
+fsm2initial(struct fsm *fp)
+{
+ timer_Stop(&fp->FsmTimer);
+ timer_Stop(&fp->OpenTimer);
+ timer_Stop(&fp->StoppedTimer);
+ if (fp->state == ST_STOPPED)
+ fsm_Close(fp);
+ if (fp->state > ST_INITIAL)
+ fsm_Down(fp);
+ if (fp->state > ST_INITIAL)
+ fsm_Close(fp);
+}
+
+struct fsm_opt *
+fsm_readopt(u_char **cp)
+{
+ struct fsm_opt *o = (struct fsm_opt *)*cp;
+
+ if (o->hdr.len < sizeof(struct fsm_opt_hdr)) {
+ log_Printf(LogERROR, "Bad option length %d (out of phase?)\n", o->hdr.len);
+ return NULL;
+ }
+
+ *cp += o->hdr.len;
+
+ if (o->hdr.len > sizeof(struct fsm_opt)) {
+ log_Printf(LogERROR, "Warning: Truncating option length from %d to %d\n",
+ o->hdr.len, (int)sizeof(struct fsm_opt));
+ o->hdr.len = sizeof(struct fsm_opt);
+ }
+
+ return o;
+}
+
+static int
+fsm_opt(u_char *opt, int optlen, const struct fsm_opt *o)
+{
+ unsigned cplen = o->hdr.len;
+
+ if (optlen < (int)sizeof(struct fsm_opt_hdr))
+ optlen = 0;
+
+ if ((int)cplen > optlen) {
+ log_Printf(LogERROR, "Can't REJ length %d - trunating to %d\n",
+ cplen, optlen);
+ cplen = optlen;
+ }
+ memcpy(opt, o, cplen);
+ if (cplen)
+ opt[1] = cplen;
+
+ return cplen;
+}
+
+void
+fsm_rej(struct fsm_decode *dec, const struct fsm_opt *o)
+{
+ if (!dec)
+ return;
+ dec->rejend += fsm_opt(dec->rejend, FSM_OPTLEN - (dec->rejend - dec->rej), o);
+}
+
+void
+fsm_ack(struct fsm_decode *dec, const struct fsm_opt *o)
+{
+ if (!dec)
+ return;
+ dec->ackend += fsm_opt(dec->ackend, FSM_OPTLEN - (dec->ackend - dec->ack), o);
+}
+
+void
+fsm_nak(struct fsm_decode *dec, const struct fsm_opt *o)
+{
+ if (!dec)
+ return;
+ dec->nakend += fsm_opt(dec->nakend, FSM_OPTLEN - (dec->nakend - dec->nak), o);
+}
+
+void
+fsm_opt_normalise(struct fsm_decode *dec)
+{
+ if (dec->rejend != dec->rej) {
+ /* rejects are preferred */
+ dec->ackend = dec->ack;
+ dec->nakend = dec->nak;
+ } else if (dec->nakend != dec->nak)
+ /* then NAKs */
+ dec->ackend = dec->ack;
+}
diff --git a/usr.sbin/ppp/fsm.h b/usr.sbin/ppp/fsm.h
new file mode 100644
index 0000000..d233899
--- /dev/null
+++ b/usr.sbin/ppp/fsm.h
@@ -0,0 +1,201 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+/*
+ * State of machine
+ */
+#define ST_INITIAL 0
+#define ST_STARTING 1
+#define ST_CLOSED 2
+#define ST_STOPPED 3
+#define ST_CLOSING 4
+#define ST_STOPPING 5
+#define ST_REQSENT 6
+#define ST_ACKRCVD 7
+#define ST_ACKSENT 8
+#define ST_OPENED 9
+
+#define ST_MAX 10
+#define ST_UNDEF -1
+
+#define MODE_REQ 0
+#define MODE_NAK 1
+#define MODE_REJ 2
+#define MODE_NOP 3
+#define MODE_ACK 4 /* pseudo mode for ccp negotiations */
+
+#define OPEN_PASSIVE -1
+
+#define FSM_REQ_TIMER 1
+#define FSM_TRM_TIMER 2
+
+#define FSM_OPTLEN 100
+
+struct fsm;
+
+struct fsm_retry {
+ u_int timeout; /* FSM retry frequency */
+ u_int maxreq; /* Max Config REQ retries */
+ u_int maxtrm; /* Max Term REQ retries */
+};
+
+struct fsm_decode {
+ u_char ack[FSM_OPTLEN], *ackend;
+ u_char nak[FSM_OPTLEN], *nakend;
+ u_char rej[FSM_OPTLEN], *rejend;
+};
+
+struct fsm_callbacks {
+ int (*LayerUp)(struct fsm *); /* Layer is now up (tlu) */
+ void (*LayerDown)(struct fsm *); /* About to come down (tld) */
+ void (*LayerStart)(struct fsm *); /* Layer about to start (tls) */
+ void (*LayerFinish)(struct fsm *); /* Layer now down (tlf) */
+ void (*InitRestartCounter)(struct fsm *, int);/* Set fsm timer load */
+ void (*SendConfigReq)(struct fsm *); /* Send REQ please */
+ void (*SentTerminateReq)(struct fsm *); /* Term REQ just sent */
+ void (*SendTerminateAck)(struct fsm *, u_char); /* Send Term ACK please */
+ void (*DecodeConfig)(struct fsm *, u_char *, u_char *, int,
+ struct fsm_decode *); /* Deal with incoming data */
+ int (*RecvResetReq)(struct fsm *fp); /* Reset output */
+ void (*RecvResetAck)(struct fsm *fp, u_char); /* Reset input */
+};
+
+struct fsm_parent {
+ void (*LayerStart) (void *, struct fsm *); /* tls */
+ void (*LayerUp) (void *, struct fsm *); /* tlu */
+ void (*LayerDown) (void *, struct fsm *); /* tld */
+ void (*LayerFinish) (void *, struct fsm *); /* tlf */
+ void *object;
+};
+
+struct link;
+struct bundle;
+
+struct fsm {
+ const char *name; /* Name of protocol */
+ u_short proto; /* Protocol number */
+ u_short min_code;
+ u_short max_code;
+ int open_mode; /* Delay before config REQ (-1 forever) */
+ unsigned state; /* State of the machine */
+ u_char reqid; /* Next request id */
+ int restart; /* Restart counter value */
+
+ struct {
+ int reqs; /* Max config REQs before a close() */
+ int naks; /* Max config NAKs before a close() */
+ int rejs; /* Max config REJs before a close() */
+ } more;
+
+ struct pppTimer FsmTimer; /* Restart Timer */
+ struct pppTimer OpenTimer; /* Delay before opening */
+
+ /*
+ * This timer times the ST_STOPPED state out after the given value
+ * (specified via "set stopped ..."). Although this isn't specified in the
+ * rfc, the rfc *does* say that "the application may use higher level
+ * timers to avoid deadlock". The StoppedTimer takes effect when the other
+ * side ABENDs rather than going into ST_ACKSENT (and sending the ACK),
+ * causing ppp to time out and drop into ST_STOPPED. At this point,
+ * nothing will change this state :-(
+ */
+ struct pppTimer StoppedTimer;
+ int LogLevel;
+
+ /* The link layer active with this FSM (may be our bundle below) */
+ struct link *link;
+
+ /* Our high-level link */
+ struct bundle *bundle;
+
+ const struct fsm_parent *parent;
+ const struct fsm_callbacks *fn;
+};
+
+struct fsmheader {
+ u_char code; /* Request code */
+ u_char id; /* Identification */
+ u_short length; /* Length of packet */
+};
+
+#define CODE_CONFIGREQ 1
+#define CODE_CONFIGACK 2
+#define CODE_CONFIGNAK 3
+#define CODE_CONFIGREJ 4
+#define CODE_TERMREQ 5
+#define CODE_TERMACK 6
+#define CODE_CODEREJ 7
+#define CODE_PROTOREJ 8
+#define CODE_ECHOREQ 9 /* Used in LCP */
+#define CODE_ECHOREP 10 /* Used in LCP */
+#define CODE_DISCREQ 11
+#define CODE_IDENT 12 /* Used in LCP Extension */
+#define CODE_TIMEREM 13 /* Used in LCP Extension */
+#define CODE_RESETREQ 14 /* Used in CCP */
+#define CODE_RESETACK 15 /* Used in CCP */
+
+struct fsm_opt_hdr {
+ u_char id;
+ u_char len;
+} __packed;
+
+#define MAX_FSM_OPT_LEN 52
+struct fsm_opt {
+ struct fsm_opt_hdr hdr;
+ u_char data[MAX_FSM_OPT_LEN-2];
+};
+
+#define INC_FSM_OPT(ty, length, o) \
+ do { \
+ (o)->hdr.id = (ty); \
+ (o)->hdr.len = (length); \
+ (o) = (struct fsm_opt *)((u_char *)(o) + (length)); \
+ } while (0)
+
+
+extern void fsm_Init(struct fsm *, const char *, u_short, int, int, int,
+ struct bundle *, struct link *, const struct fsm_parent *,
+ struct fsm_callbacks *, const char * const [3]);
+extern void fsm_Output(struct fsm *, u_int, u_int, u_char *, unsigned, int);
+extern void fsm_Open(struct fsm *);
+extern void fsm_Up(struct fsm *);
+extern void fsm_Down(struct fsm *);
+extern void fsm_Input(struct fsm *, struct mbuf *);
+extern void fsm_Close(struct fsm *);
+extern int fsm_NullRecvResetReq(struct fsm *);
+extern void fsm_NullRecvResetAck(struct fsm *, u_char);
+extern void fsm_Reopen(struct fsm *);
+extern void fsm2initial(struct fsm *);
+extern const char *State2Nam(u_int);
+extern struct fsm_opt *fsm_readopt(u_char **);
+extern void fsm_rej(struct fsm_decode *, const struct fsm_opt *);
+extern void fsm_ack(struct fsm_decode *, const struct fsm_opt *);
+extern void fsm_nak(struct fsm_decode *, const struct fsm_opt *);
+extern void fsm_opt_normalise(struct fsm_decode *);
diff --git a/usr.sbin/ppp/hdlc.c b/usr.sbin/ppp/hdlc.c
new file mode 100644
index 0000000..517c3c5
--- /dev/null
+++ b/usr.sbin/ppp/hdlc.c
@@ -0,0 +1,438 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/un.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <termios.h>
+
+#include "defs.h"
+#include "layer.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "auth.h"
+#include "lcp.h"
+#include "async.h"
+#include "ccp.h"
+#include "link.h"
+#include "descriptor.h"
+#include "chap.h"
+#include "physical.h"
+#include "prompt.h"
+#include "chat.h"
+#include "mp.h"
+#include "cbcp.h"
+#include "datalink.h"
+
+static u_int16_t const fcstab[256] = {
+ /* 00 */ 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
+ /* 08 */ 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
+ /* 10 */ 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
+ /* 18 */ 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
+ /* 20 */ 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
+ /* 28 */ 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
+ /* 30 */ 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
+ /* 38 */ 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
+ /* 40 */ 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
+ /* 48 */ 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
+ /* 50 */ 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
+ /* 58 */ 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
+ /* 60 */ 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
+ /* 68 */ 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
+ /* 70 */ 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
+ /* 78 */ 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
+ /* 80 */ 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
+ /* 88 */ 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
+ /* 90 */ 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
+ /* 98 */ 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
+ /* a0 */ 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
+ /* a8 */ 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
+ /* b0 */ 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
+ /* b8 */ 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
+ /* c0 */ 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
+ /* c8 */ 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
+ /* d0 */ 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
+ /* d8 */ 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
+ /* e0 */ 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
+ /* e8 */ 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
+ /* f0 */ 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
+ /* f8 */ 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
+};
+
+void
+hdlc_Init(struct hdlc *hdlc, struct lcp *lcp)
+{
+ memset(hdlc, '\0', sizeof(struct hdlc));
+ hdlc->lqm.owner = lcp;
+}
+
+/*
+ * HDLC FCS computation. Read RFC 1171 Appendix B and CCITT X.25 section
+ * 2.27 for further details.
+ */
+u_short
+hdlc_Fcs(u_char *cp, size_t len)
+{
+ u_short fcs = INITFCS;
+
+ while (len--)
+ fcs = (fcs >> 8) ^ fcstab[(fcs ^ *cp++) & 0xff];
+
+ return fcs;
+}
+
+static inline u_short
+HdlcFcsBuf(u_short fcs, struct mbuf *m)
+{
+ int len;
+ u_char *pos, *end;
+
+ len = m_length(m);
+ pos = MBUF_CTOP(m);
+ end = pos + m->m_len;
+ while (len--) {
+ fcs = (fcs >> 8) ^ fcstab[(fcs ^ *pos++) & 0xff];
+ if (pos == end && len) {
+ m = m->m_next;
+ pos = MBUF_CTOP(m);
+ end = pos + m->m_len;
+ }
+ }
+ return (fcs);
+}
+
+static struct mbuf *
+hdlc_LayerPush(struct bundle *bundle __unused, struct link *l __unused,
+ struct mbuf *bp, int pri __unused, u_short *proto __unused)
+{
+ struct mbuf *last;
+ u_char *cp;
+ u_short fcs;
+
+ m_settype(bp, MB_HDLCOUT);
+ fcs = HdlcFcsBuf(INITFCS, bp);
+ fcs = ~fcs;
+
+ for (last = bp; last->m_next; last = last->m_next)
+ ;
+
+ if (last->m_size - last->m_offset - last->m_len >= 2) {
+ cp = MBUF_CTOP(last) + last->m_len;
+ last->m_len += 2;
+ } else {
+ struct mbuf *tail = m_get(2, MB_HDLCOUT);
+ last->m_next = tail;
+ cp = MBUF_CTOP(tail);
+ }
+
+ *cp++ = fcs & 0377; /* Low byte first (nothing like consistency) */
+ *cp++ = fcs >> 8;
+
+ log_DumpBp(LogHDLC, "hdlc_Output", bp);
+
+ return bp;
+}
+
+/* Check out the latest ``Assigned numbers'' rfc (rfc1700.txt) */
+static struct {
+ u_short from;
+ u_short to;
+ const char *name;
+} protocols[] = {
+ { 0x0001, 0x0001, "Padding Protocol" },
+ { 0x0003, 0x001f, "reserved (transparency inefficient)" },
+ { 0x0021, 0x0021, "Internet Protocol" },
+ { 0x0023, 0x0023, "OSI Network Layer" },
+ { 0x0025, 0x0025, "Xerox NS IDP" },
+ { 0x0027, 0x0027, "DECnet Phase IV" },
+ { 0x0029, 0x0029, "Appletalk" },
+ { 0x002b, 0x002b, "Novell IPX" },
+ { 0x002d, 0x002d, "Van Jacobson Compressed TCP/IP" },
+ { 0x002f, 0x002f, "Van Jacobson Uncompressed TCP/IP" },
+ { 0x0031, 0x0031, "Bridging PDU" },
+ { 0x0033, 0x0033, "Stream Protocol (ST-II)" },
+ { 0x0035, 0x0035, "Banyan Vines" },
+ { 0x0037, 0x0037, "reserved (until 1993)" },
+ { 0x0039, 0x0039, "AppleTalk EDDP" },
+ { 0x003b, 0x003b, "AppleTalk SmartBuffered" },
+ { 0x003d, 0x003d, "Multi-Link" },
+ { 0x003f, 0x003f, "NETBIOS Framing" },
+ { 0x0041, 0x0041, "Cisco Systems" },
+ { 0x0043, 0x0043, "Ascom Timeplex" },
+ { 0x0045, 0x0045, "Fujitsu Link Backup and Load Balancing (LBLB)" },
+ { 0x0047, 0x0047, "DCA Remote Lan" },
+ { 0x0049, 0x0049, "Serial Data Transport Protocol (PPP-SDTP)" },
+ { 0x004b, 0x004b, "SNA over 802.2" },
+ { 0x004d, 0x004d, "SNA" },
+ { 0x004f, 0x004f, "IP6 Header Compression" },
+ { 0x0051, 0x0051, "KNX Bridging Data" },
+ { 0x0053, 0x0053, "Encryption" },
+ { 0x0055, 0x0055, "Individual Link Encryption" },
+ { 0x0057, 0x0057, "Internet Protocol V6" },
+ { 0x006f, 0x006f, "Stampede Bridging" },
+ { 0x0071, 0x0071, "BAP Bandwidth Allocation Protocol" },
+ { 0x0073, 0x0073, "MP+ Protocol" },
+ { 0x007d, 0x007d, "reserved (Control Escape)" },
+ { 0x007f, 0x007f, "reserved (compression inefficient)" },
+ { 0x00cf, 0x00cf, "reserved (PPP NLPID)" },
+ { 0x00fb, 0x00fb, "compression on single link in multilink group" },
+ { 0x00fd, 0x00fd, "1st choice compression" },
+ { 0x00ff, 0x00ff, "reserved (compression inefficient)" },
+ { 0x0200, 0x02ff, "(compression inefficient)" },
+ { 0x0201, 0x0201, "802.1d Hello Packets" },
+ { 0x0203, 0x0203, "IBM Source Routing BPDU" },
+ { 0x0205, 0x0205, "DEC LANBridge100 Spanning Tree" },
+ { 0x0207, 0x0207, "Cisco Discovery Protocol" },
+ { 0x0209, 0x0209, "Netcs Twin Routing" },
+ { 0x0231, 0x0231, "Luxcom" },
+ { 0x0233, 0x0233, "Sigma Network Systems" },
+ { 0x0235, 0x0235, "Apple Client Server Protocol" },
+ { 0x1e00, 0x1eff, "(compression inefficient)" },
+ { 0x4001, 0x4001, "Cray Communications Control Protocol" },
+ { 0x4003, 0x4003, "CDPD Mobile Network Registration Protocol" },
+ { 0x4021, 0x4021, "Stacker LZS" },
+ { 0x8001, 0x801f, "Not Used - reserved" },
+ { 0x8021, 0x8021, "Internet Protocol Control Protocol" },
+ { 0x8023, 0x8023, "OSI Network Layer Control Protocol" },
+ { 0x8025, 0x8025, "Xerox NS IDP Control Protocol" },
+ { 0x8027, 0x8027, "DECnet Phase IV Control Protocol" },
+ { 0x8029, 0x8029, "Appletalk Control Protocol" },
+ { 0x802b, 0x802b, "Novell IPX Control Protocol" },
+ { 0x802d, 0x802d, "reserved" },
+ { 0x802f, 0x802f, "reserved" },
+ { 0x8031, 0x8031, "Bridging NCP" },
+ { 0x8033, 0x8033, "Stream Protocol Control Protocol" },
+ { 0x8035, 0x8035, "Banyan Vines Control Protocol" },
+ { 0x8037, 0x8037, "reserved till 1993" },
+ { 0x8039, 0x8039, "reserved" },
+ { 0x803b, 0x803b, "reserved" },
+ { 0x803d, 0x803d, "Multi-Link Control Protocol" },
+ { 0x803f, 0x803f, "NETBIOS Framing Control Protocol" },
+ { 0x8041, 0x8041, "Cisco Systems Control Protocol" },
+ { 0x8043, 0x8043, "Ascom Timeplex" },
+ { 0x8045, 0x8045, "Fujitsu LBLB Control Protocol" },
+ { 0x8047, 0x8047, "DCA Remote Lan Network Control Protocol (RLNCP)" },
+ { 0x8049, 0x8049, "Serial Data Control Protocol (PPP-SDCP)" },
+ { 0x804b, 0x804b, "SNA over 802.2 Control Protocol" },
+ { 0x804d, 0x804d, "SNA Control Protocol" },
+ { 0x804f, 0x804f, "IP6 Header Compression Control Protocol" },
+ { 0x8051, 0x8051, "KNX Bridging Control Protocol" },
+ { 0x8053, 0x8053, "Encryption Control Protocol" },
+ { 0x8055, 0x8055, "Individual Link Encryption Control Protocol" },
+ { 0x8057, 0x8057, "Internet Protocol V6 Control Protocol" },
+ { 0x806f, 0x806f, "Stampede Bridging Control Protocol" },
+ { 0x8073, 0x8073, "MP+ Control Protocol" },
+ { 0x8071, 0x8071, "BACP Bandwidth Allocation Control Protocol" },
+ { 0x807d, 0x807d, "Not Used - reserved" },
+ { 0x80cf, 0x80cf, "Not Used - reserved" },
+ { 0x80fb, 0x80fb, "compression on single link in multilink group control" },
+ { 0x80fd, 0x80fd, "Compression Control Protocol" },
+ { 0x80ff, 0x80ff, "Not Used - reserved" },
+ { 0x8207, 0x8207, "Cisco Discovery Protocol Control" },
+ { 0x8209, 0x8209, "Netcs Twin Routing" },
+ { 0x8235, 0x8235, "Apple Client Server Protocol Control" },
+ { 0xc021, 0xc021, "Link Control Protocol" },
+ { 0xc023, 0xc023, "Password Authentication Protocol" },
+ { 0xc025, 0xc025, "Link Quality Report" },
+ { 0xc027, 0xc027, "Shiva Password Authentication Protocol" },
+ { 0xc029, 0xc029, "CallBack Control Protocol (CBCP)" },
+ { 0xc081, 0xc081, "Container Control Protocol" },
+ { 0xc223, 0xc223, "Challenge Handshake Authentication Protocol" },
+ { 0xc225, 0xc225, "RSA Authentication Protocol" },
+ { 0xc227, 0xc227, "Extensible Authentication Protocol" },
+ { 0xc26f, 0xc26f, "Stampede Bridging Authorization Protocol" },
+ { 0xc281, 0xc281, "Proprietary Authentication Protocol" },
+ { 0xc283, 0xc283, "Proprietary Authentication Protocol" },
+ { 0xc481, 0xc481, "Proprietary Node ID Authentication Protocol" }
+};
+
+#define NPROTOCOLS (sizeof protocols/sizeof protocols[0])
+
+const char *
+hdlc_Protocol2Nam(u_short proto)
+{
+ unsigned f;
+
+ for (f = 0; f < NPROTOCOLS; f++)
+ if (proto >= protocols[f].from && proto <= protocols[f].to)
+ return protocols[f].name;
+ else if (proto < protocols[f].from)
+ break;
+ return "unrecognised protocol";
+}
+
+static struct mbuf *
+hdlc_LayerPull(struct bundle *b __unused, struct link *l, struct mbuf *bp,
+ u_short *proto __unused)
+{
+ struct physical *p = link2physical(l);
+ u_short fcs;
+ int len;
+
+ if (!p) {
+ log_Printf(LogERROR, "Can't Pull a hdlc packet from a logical link\n");
+ return bp;
+ }
+
+ log_DumpBp(LogHDLC, "hdlc_LayerPull:", bp);
+
+ bp = m_pullup(bp);
+ len = m_length(bp);
+ fcs = hdlc_Fcs(MBUF_CTOP(bp), len);
+
+ log_Printf(LogDEBUG, "%s: hdlc_LayerPull: fcs = %04x (%s)\n",
+ p->link.name, fcs, (fcs == GOODFCS) ? "good" : "BAD!");
+
+ p->hdlc.lqm.ifInOctets += len + 1; /* plus 1 flag octet! */
+
+ if (fcs != GOODFCS) {
+ p->hdlc.lqm.ifInErrors++;
+ p->hdlc.stats.badfcs++;
+ m_freem(bp);
+ return NULL;
+ }
+
+ /* Either done here or by the sync layer */
+ p->hdlc.lqm.lqr.InGoodOctets += len + 1; /* plus 1 flag octet! */
+ p->hdlc.lqm.ifInUniPackets++;
+
+ if (len < 4) { /* rfc1662 section 4.3 */
+ m_freem(bp);
+ bp = NULL;
+ }
+
+ bp = m_adj(bp, -2); /* discard the FCS */
+ m_settype(bp, MB_HDLCIN);
+
+ return bp;
+}
+
+/* Detect a HDLC frame */
+
+static const struct frameheader {
+ const u_char *data;
+ int len;
+} FrameHeaders[] = {
+ { "\176\377\003\300\041", 5 },
+ { "\176\377\175\043\300\041", 6 },
+ { "\176\177\175\043\100\041", 6 },
+ { "\176\175\337\175\043\300\041", 7 },
+ { "\176\175\137\175\043\100\041", 7 },
+ { NULL, 0 }
+};
+
+int
+hdlc_Detect(u_char const **cp, unsigned n, int issync)
+{
+ const struct frameheader *fh;
+ const u_char *h;
+ size_t len, cmp;
+
+ while (n) {
+ for (fh = FrameHeaders; fh->len; fh++) {
+ h = issync ? fh->data + 1 : fh->data;
+ len = issync ? fh->len - 1 : fh->len;
+ cmp = n >= len ? len : n;
+ if (memcmp(*cp, h, cmp) == 0)
+ return cmp == len;
+ }
+ n--;
+ (*cp)++;
+ }
+
+ return 0;
+}
+
+int
+hdlc_ReportStatus(struct cmdargs const *arg)
+{
+ struct hdlc *hdlc = &arg->cx->physical->hdlc;
+
+ prompt_Printf(arg->prompt, "%s HDLC level errors:\n", arg->cx->name);
+ prompt_Printf(arg->prompt, " Bad Frame Check Sequence fields: %u\n",
+ hdlc->stats.badfcs);
+ prompt_Printf(arg->prompt, " Bad address (!= 0x%02x) fields: %u\n",
+ HDLC_ADDR, hdlc->stats.badaddr);
+ prompt_Printf(arg->prompt, " Bad command (!= 0x%02x) fields: %u\n",
+ HDLC_UI, hdlc->stats.badcommand);
+ prompt_Printf(arg->prompt, " Unrecognised protocol fields: %u\n",
+ hdlc->stats.unknownproto);
+ return 0;
+}
+
+static void
+hdlc_ReportTime(void *v)
+{
+ /* Moan about HDLC errors */
+ struct hdlc *hdlc = (struct hdlc *)v;
+
+ timer_Stop(&hdlc->ReportTimer);
+
+ if (memcmp(&hdlc->laststats, &hdlc->stats, sizeof hdlc->stats)) {
+ log_Printf(LogPHASE,
+ "%s: HDLC errors -> FCS: %u, ADDR: %u, COMD: %u, PROTO: %u\n",
+ hdlc->lqm.owner->fsm.link->name,
+ hdlc->stats.badfcs - hdlc->laststats.badfcs,
+ hdlc->stats.badaddr - hdlc->laststats.badaddr,
+ hdlc->stats.badcommand - hdlc->laststats.badcommand,
+ hdlc->stats.unknownproto - hdlc->laststats.unknownproto);
+ hdlc->laststats = hdlc->stats;
+ }
+
+ timer_Start(&hdlc->ReportTimer);
+}
+
+void
+hdlc_StartTimer(struct hdlc *hdlc)
+{
+ timer_Stop(&hdlc->ReportTimer);
+ hdlc->ReportTimer.load = 60 * SECTICKS;
+ hdlc->ReportTimer.arg = hdlc;
+ hdlc->ReportTimer.func = hdlc_ReportTime;
+ hdlc->ReportTimer.name = "hdlc";
+ timer_Start(&hdlc->ReportTimer);
+}
+
+void
+hdlc_StopTimer(struct hdlc *hdlc)
+{
+ timer_Stop(&hdlc->ReportTimer);
+}
+
+struct layer hdlclayer = { LAYER_HDLC, "hdlc", hdlc_LayerPush, hdlc_LayerPull };
diff --git a/usr.sbin/ppp/hdlc.h b/usr.sbin/ppp/hdlc.h
new file mode 100644
index 0000000..b9211a6
--- /dev/null
+++ b/usr.sbin/ppp/hdlc.h
@@ -0,0 +1,116 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+/*
+ * Definition for Async HDLC
+ */
+#define HDLC_SYN 0x7e /* SYNC character */
+#define HDLC_ESC 0x7d /* Escape character */
+#define HDLC_XOR 0x20 /* Modifier value */
+
+#define HDLC_ADDR 0xff
+#define HDLC_UI 0x03
+/*
+ * Definition for HDLC Frame Check Sequence
+ */
+#define INITFCS 0xffff /* Initial value for FCS computation */
+#define GOODFCS 0xf0b8 /* Good FCS value */
+
+#define DEF_MRU 1500
+#define MAX_MRU 2048
+#define MIN_MRU 296
+
+#define DEF_MTU 0 /* whatever peer says */
+#define MAX_MTU 2048
+#define MIN_MTU 296
+
+struct physical;
+struct link;
+struct lcp;
+struct bundle;
+struct mbuf;
+struct cmdargs;
+
+struct hdlc {
+ struct pppTimer ReportTimer;
+
+ struct {
+ int badfcs;
+ int badaddr;
+ int badcommand;
+ int unknownproto;
+ } laststats, stats;
+
+ struct {
+ struct lcp *owner; /* parent LCP */
+ struct pppTimer timer; /* When to send */
+ int method; /* bit-mask for LQM_* from lqr.h */
+
+ u_int32_t ifOutUniPackets; /* Packets sent by me */
+ u_int32_t ifOutOctets; /* Octets sent by me */
+ u_int32_t ifInUniPackets; /* Packets received from peer */
+ u_int32_t ifInDiscards; /* Discards */
+ u_int32_t ifInErrors; /* Errors */
+ u_int32_t ifInOctets; /* Octets received from peer (unused) */
+
+ struct {
+ u_int32_t InGoodOctets; /* Good octets received from peer */
+ u_int32_t OutLQRs; /* LQRs sent by me */
+ u_int32_t InLQRs; /* LQRs received from peer */
+
+ struct lqrsavedata Save; /* Our last LQR */
+ struct lqrsavedata prevSave; /* Our last-but-one LQR (analysis) */
+
+ struct lqrdata peer; /* Last LQR from peer */
+ int peer_timeout; /* peers max lqr timeout */
+ int resent; /* Resent last packet `resent' times */
+ } lqr;
+
+ struct {
+ u_int32_t seq_sent; /* last echo sent */
+ u_int32_t seq_recv; /* last echo received */
+ } echo;
+ } lqm;
+};
+
+
+extern void hdlc_Init(struct hdlc *, struct lcp *);
+extern void hdlc_StartTimer(struct hdlc *);
+extern void hdlc_StopTimer(struct hdlc *);
+extern int hdlc_ReportStatus(struct cmdargs const *);
+extern const char *hdlc_Protocol2Nam(u_short);
+extern void hdlc_DecodePacket(struct bundle *, u_short, struct mbuf *,
+ struct link *);
+
+extern u_short hdlc_Fcs(u_char *, size_t);
+extern int hdlc_Detect(u_char const **, unsigned, int);
+#define hdlc_WrapperOctets() (2)
+
+extern struct layer hdlclayer;
diff --git a/usr.sbin/ppp/i4b.h b/usr.sbin/ppp/i4b.h
new file mode 100644
index 0000000..3545c84
--- /dev/null
+++ b/usr.sbin/ppp/i4b.h
@@ -0,0 +1,37 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.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$
+ */
+
+struct physical;
+struct device;
+
+#define DEF_I4BCDDELAY 6 /* Default ``set cd'' value */
+
+extern struct device *i4b_Create(struct physical *);
+extern struct device *i4b_iov2device(int, struct physical *,
+ struct iovec *, int *, int, int *, int *);
+extern unsigned i4b_DeviceSize(void);
diff --git a/usr.sbin/ppp/id.c b/usr.sbin/ppp/id.c
new file mode 100644
index 0000000..ec66574
--- /dev/null
+++ b/usr.sbin/ppp/id.c
@@ -0,0 +1,292 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.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/param.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#ifndef NONETGRAPH
+#include <netgraph.h>
+#endif
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <sysexits.h>
+#if defined(__FreeBSD__) && !defined(NOKLDLOAD)
+#include <sys/linker.h>
+#endif
+#include <unistd.h>
+#ifdef __OpenBSD__
+#include <util.h>
+#else
+#include <libutil.h>
+#endif
+#include <utmpx.h>
+
+#include "log.h"
+#include "main.h"
+#include "id.h"
+
+static int uid;
+static int euid;
+
+void
+ID0init()
+{
+ uid = getuid();
+ euid = geteuid();
+}
+
+static void
+ID0setuser(void)
+{
+ if (seteuid(uid) == -1) {
+ log_Printf(LogERROR, "ID0setuser: Unable to seteuid!\n");
+ AbortProgram(EX_NOPERM);
+ }
+}
+
+uid_t
+ID0realuid()
+{
+ return uid;
+}
+
+static void
+ID0set0(void)
+{
+ if (seteuid(euid) == -1) {
+ log_Printf(LogERROR, "ID0set0: Unable to seteuid!\n");
+ AbortProgram(EX_NOPERM);
+ }
+}
+
+int
+ID0ioctl(int fd, unsigned long req, void *arg)
+{
+ int ret;
+
+ ID0set0();
+ ret = ioctl(fd, req, arg);
+ log_Printf(LogID0, "%d = ioctl(%d, %lu, %p)\n", ret, fd, req, arg);
+ ID0setuser();
+ return ret;
+}
+
+int
+ID0unlink(const char *name)
+{
+ int ret;
+
+ ID0set0();
+ ret = unlink(name);
+ log_Printf(LogID0, "%d = unlink(\"%s\")\n", ret, name);
+ ID0setuser();
+ return ret;
+}
+
+int
+ID0socket(int domain, int type, int protocol)
+{
+ int ret;
+
+ ID0set0();
+ ret = socket(domain, type, protocol);
+ log_Printf(LogID0, "%d = socket(%d, %d, %d)\n", ret, domain, type, protocol);
+ ID0setuser();
+ return ret;
+}
+
+FILE *
+ID0fopen(const char *path, const char *mode)
+{
+ FILE *ret;
+
+ ID0set0();
+ ret = fopen(path, mode);
+ log_Printf(LogID0, "%p = fopen(\"%s\", \"%s\")\n", ret, path, mode);
+ ID0setuser();
+ return ret;
+}
+
+int
+ID0open(const char *path, int flags, ...)
+{
+ int ret;
+ va_list ap;
+
+ va_start(ap, flags);
+ ID0set0();
+ ret = open(path, flags, va_arg(ap, int));
+ log_Printf(LogID0, "%d = open(\"%s\", %d)\n", ret, path, flags);
+ ID0setuser();
+ va_end(ap);
+ return ret;
+}
+
+int
+ID0write(int fd, const void *data, size_t len)
+{
+ int ret;
+
+ ID0set0();
+ ret = write(fd, data, len);
+ log_Printf(LogID0, "%d = write(%d, data, %ld)\n", ret, fd, (long)len);
+ ID0setuser();
+ return ret;
+}
+
+int
+ID0uu_lock(const char *basettyname)
+{
+ int ret;
+
+ ID0set0();
+ ret = uu_lock(basettyname);
+ log_Printf(LogID0, "%d = uu_lock(\"%s\")\n", ret, basettyname);
+ ID0setuser();
+ return ret;
+}
+
+int
+ID0uu_lock_txfr(const char *basettyname, pid_t newpid)
+{
+ int ret;
+
+ ID0set0();
+ ret = uu_lock_txfr(basettyname, newpid);
+ log_Printf(LogID0, "%d = uu_lock_txfr(\"%s\", %ld)\n", ret, basettyname,
+ (long)newpid);
+ ID0setuser();
+ return ret;
+}
+
+int
+ID0uu_unlock(const char *basettyname)
+{
+ int ret;
+
+ ID0set0();
+ ret = uu_unlock(basettyname);
+ log_Printf(LogID0, "%d = uu_unlock(\"%s\")\n", ret, basettyname);
+ ID0setuser();
+ return ret;
+}
+
+void
+ID0login(const struct utmpx *ut)
+{
+ ID0set0();
+ pututxline(ut);
+ log_Printf(LogID0, "pututxline(\"%.*s\", \"%.*s\", \"%.*s\", \"%.*s\")\n",
+ (int)sizeof ut->ut_id, ut->ut_id,
+ (int)sizeof ut->ut_user, ut->ut_user,
+ (int)sizeof ut->ut_line, ut->ut_line,
+ (int)sizeof ut->ut_host, ut->ut_host);
+ ID0setuser();
+}
+
+void
+ID0logout(const struct utmpx *ut)
+{
+ ID0set0();
+ pututxline(ut);
+ log_Printf(LogID0, "pututxline(\"%.*s\")\n",
+ (int)sizeof ut->ut_id, ut->ut_id);
+ ID0setuser();
+}
+
+int
+ID0bind_un(int s, const struct sockaddr_un *name)
+{
+ int result;
+
+ ID0set0();
+ result = bind(s, (const struct sockaddr *)name, sizeof *name);
+ log_Printf(LogID0, "%d = bind(%d, \"%s\", %d)\n",
+ result, s, name->sun_path, (int)sizeof(*name));
+ ID0setuser();
+ return result;
+}
+
+int
+ID0connect_un(int s, const struct sockaddr_un *name)
+{
+ int result;
+
+ ID0set0();
+ result = connect(s, (const struct sockaddr *)name, sizeof *name);
+ log_Printf(LogID0, "%d = connect(%d, \"%s\", %d)\n",
+ result, s, name->sun_path, (int)sizeof(*name));
+ ID0setuser();
+ return result;
+}
+
+int
+ID0kill(pid_t pid, int sig)
+{
+ int result;
+
+ ID0set0();
+ result = kill(pid, sig);
+ log_Printf(LogID0, "%d = kill(%ld, %d)\n", result, (long)pid, sig);
+ ID0setuser();
+ return result;
+}
+
+#if defined(__FreeBSD__) && !defined(NOKLDLOAD)
+int
+ID0kldload(const char *dev)
+{
+ int result;
+
+ ID0set0();
+ result = kldload(dev);
+ log_Printf(LogID0, "%d = kldload(\"%s\")\n", result, dev);
+ ID0setuser();
+ return result;
+}
+#endif
+
+#ifndef NONETGRAPH
+int
+ID0NgMkSockNode(const char *name, int *cs, int *ds)
+{
+ int result;
+
+ ID0set0();
+ result = NgMkSockNode(name, cs, ds);
+ log_Printf(LogID0, "%d = NgMkSockNode(\"%s\", &cs, &ds)\n",
+ result, name ? name : "");
+ ID0setuser();
+ return result;
+}
+#endif
diff --git a/usr.sbin/ppp/id.h b/usr.sbin/ppp/id.h
new file mode 100644
index 0000000..2357dc7
--- /dev/null
+++ b/usr.sbin/ppp/id.h
@@ -0,0 +1,81 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.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 NOSUID
+struct utmpx;
+struct sockaddr_un;
+
+extern void ID0init(void);
+extern uid_t ID0realuid(void);
+extern int ID0ioctl(int, unsigned long, void *);
+extern int ID0unlink(const char *);
+extern int ID0socket(int, int, int);
+extern FILE *ID0fopen(const char *, const char *);
+extern int ID0open(const char *, int, ...);
+extern int ID0write(int, const void *, size_t);
+extern int ID0uu_lock(const char *);
+extern int ID0uu_lock_txfr(const char *, pid_t);
+extern int ID0uu_unlock(const char *);
+extern void ID0login(const struct utmpx *);
+extern void ID0logout(const struct utmpx *);
+extern int ID0bind_un(int, const struct sockaddr_un *);
+extern int ID0connect_un(int, const struct sockaddr_un *);
+extern int ID0kill(pid_t, int);
+#if defined(__FreeBSD__) && !defined(NOKLDLOAD)
+extern int ID0kldload(const char *);
+#endif
+#ifndef NONETGRAPH
+extern int ID0NgMkSockNode(const char *, int *, int *);
+#endif
+#else /* NOSUID */
+#define ID0init()
+#define ID0realuid() (0)
+#define ID0ioctl ioctl
+#define ID0unlink unlink
+#define ID0socket socket
+#define ID0fopen fopen
+#define ID0open open
+#define ID0write write
+#define ID0uu_lock uu_lock
+#define ID0uu_lock_txfr uu_lock_txfr
+#define ID0uu_unlock uu_unlock
+#define ID0login pututxline
+#define ID0logout pututxline
+#define ID0bind_un(s, n) bind(s, (const struct sockaddr *)(n), sizeof *(n))
+#define ID0connect_un(s, n) \
+ connect(s, (const struct sockaddr *)(n), sizeof *(n))
+#define ID0kill kill
+#if defined(__FreeBSD__) && !defined(NOKLDLOAD)
+#include <sys/param.h>
+#include <sys/linker.h>
+#define ID0kldload kldload
+#endif
+#ifndef NONETGRAPH
+#define ID0NgMkSockNode NgMkSockNode
+#endif
+#endif
diff --git a/usr.sbin/ppp/iface.c b/usr.sbin/ppp/iface.c
new file mode 100644
index 0000000..5f17769
--- /dev/null
+++ b/usr.sbin/ppp/iface.c
@@ -0,0 +1,836 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.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/param.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/route.h>
+#include <netinet/in_systm.h>
+#include <netinet/in_var.h>
+#include <netinet/ip.h>
+#ifndef NOINET6
+#include <netinet6/nd6.h>
+#endif
+#include <sys/un.h>
+
+#include <errno.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/sysctl.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "id.h"
+#include "timer.h"
+#include "fsm.h"
+#include "iplist.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "descriptor.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "prompt.h"
+#include "iface.h"
+
+#define IN6MASK128 {{{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }}}
+static const struct in6_addr in6mask128 = IN6MASK128;
+
+
+struct iface *
+iface_Create(const char *name)
+{
+ int mib[6], maxtries, err;
+ size_t needed, namelen;
+ char *buf, *ptr, *end;
+ struct if_msghdr *ifm;
+ struct ifa_msghdr *ifam;
+ struct sockaddr_dl *dl;
+ struct sockaddr *sa[RTAX_MAX];
+ struct iface *iface;
+ struct iface_addr *addr;
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = 0;
+ mib[4] = NET_RT_IFLIST;
+ mib[5] = 0;
+
+ maxtries = 20;
+ err = 0;
+ do {
+ if (maxtries-- == 0 || (err && err != ENOMEM)) {
+ fprintf(stderr, "iface_Create: sysctl: %s\n", strerror(err));
+ return NULL;
+ }
+
+ if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) {
+ fprintf(stderr, "iface_Create: sysctl: estimate: %s\n",
+ strerror(errno));
+ return NULL;
+ }
+
+ if ((buf = (char *)malloc(needed)) == NULL) {
+ fprintf(stderr, "iface_Create: malloc failed: %s\n", strerror(errno));
+ return NULL;
+ }
+
+ if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) {
+ err = errno;
+ free(buf);
+ buf = NULL;
+ }
+ } while (buf == NULL);
+
+ ptr = buf;
+ end = buf + needed;
+ iface = NULL;
+ namelen = strlen(name);
+
+ while (ptr < end && iface == NULL) {
+ ifm = (struct if_msghdr *)ptr; /* On if_msghdr */
+ if (ifm->ifm_type != RTM_IFINFO)
+ break;
+ dl = (struct sockaddr_dl *)(ifm + 1); /* Single _dl at end */
+ if (dl->sdl_nlen == namelen && !strncmp(name, dl->sdl_data, namelen)) {
+ iface = (struct iface *)malloc(sizeof *iface);
+ if (iface == NULL) {
+ fprintf(stderr, "iface_Create: malloc: %s\n", strerror(errno));
+ return NULL;
+ }
+ iface->name = strdup(name);
+ iface->descr = NULL;
+ iface->index = ifm->ifm_index;
+ iface->flags = ifm->ifm_flags;
+ iface->mtu = 0;
+ iface->addrs = 0;
+ iface->addr = NULL;
+ }
+ ptr += ifm->ifm_msglen; /* First ifa_msghdr */
+ for (; ptr < end; ptr += ifam->ifam_msglen) {
+ ifam = (struct ifa_msghdr *)ptr; /* Next if address */
+
+ if (ifam->ifam_type != RTM_NEWADDR) /* finished this if */
+ break;
+
+ if (iface != NULL && ifam->ifam_addrs & RTA_IFA) {
+ /* Found a configured interface ! */
+ iface_ParseHdr(ifam, sa);
+
+ if (sa[RTAX_IFA] && (sa[RTAX_IFA]->sa_family == AF_INET
+#ifndef NOINET6
+ || sa[RTAX_IFA]->sa_family == AF_INET6
+#endif
+ )) {
+ /* Record the address */
+
+ addr = (struct iface_addr *)
+ realloc(iface->addr, (iface->addrs + 1) * sizeof iface->addr[0]);
+ if (addr == NULL)
+ break;
+ iface->addr = addr;
+
+ addr += iface->addrs;
+ iface->addrs++;
+
+ ncprange_setsa(&addr->ifa, sa[RTAX_IFA], sa[RTAX_NETMASK]);
+ if (sa[RTAX_BRD])
+ ncpaddr_setsa(&addr->peer, sa[RTAX_BRD]);
+ else
+ ncpaddr_init(&addr->peer);
+ }
+ }
+ }
+ }
+
+ free(buf);
+
+ return iface;
+}
+
+static int
+iface_addr_Zap(const char *name, struct iface_addr *addr, int s)
+{
+ struct ifaliasreq ifra;
+#ifndef NOINET6
+ struct in6_aliasreq ifra6;
+#endif
+ struct sockaddr_in *me4, *msk4, *peer4;
+ struct sockaddr_storage ssme, sspeer, ssmsk;
+ int res;
+
+ ncprange_getsa(&addr->ifa, &ssme, &ssmsk);
+ ncpaddr_getsa(&addr->peer, &sspeer);
+ res = 0;
+
+ switch (ncprange_family(&addr->ifa)) {
+ case AF_INET:
+ memset(&ifra, '\0', sizeof ifra);
+ strncpy(ifra.ifra_name, name, sizeof ifra.ifra_name - 1);
+
+ me4 = (struct sockaddr_in *)&ifra.ifra_addr;
+ memcpy(me4, &ssme, sizeof *me4);
+
+ msk4 = (struct sockaddr_in *)&ifra.ifra_mask;
+ memcpy(msk4, &ssmsk, sizeof *msk4);
+
+ peer4 = (struct sockaddr_in *)&ifra.ifra_broadaddr;
+ if (ncpaddr_family(&addr->peer) == AF_UNSPEC) {
+ peer4->sin_family = AF_INET;
+ peer4->sin_len = sizeof(*peer4);
+ peer4->sin_addr.s_addr = INADDR_NONE;
+ } else
+ memcpy(peer4, &sspeer, sizeof *peer4);
+
+ res = ID0ioctl(s, SIOCDIFADDR, &ifra);
+ if (log_IsKept(LogDEBUG)) {
+ char buf[100];
+
+ snprintf(buf, sizeof buf, "%s", ncprange_ntoa(&addr->ifa));
+ log_Printf(LogWARN, "%s: DIFADDR %s -> %s returns %d\n",
+ ifra.ifra_name, buf, ncpaddr_ntoa(&addr->peer), res);
+ }
+ break;
+
+#ifndef NOINET6
+ case AF_INET6:
+ memset(&ifra6, '\0', sizeof ifra6);
+ strncpy(ifra6.ifra_name, name, sizeof ifra6.ifra_name - 1);
+
+ memcpy(&ifra6.ifra_addr, &ssme, sizeof ifra6.ifra_addr);
+ memcpy(&ifra6.ifra_prefixmask, &ssmsk, sizeof ifra6.ifra_prefixmask);
+ ifra6.ifra_prefixmask.sin6_family = AF_UNSPEC;
+ if (ncpaddr_family(&addr->peer) == AF_UNSPEC)
+ ifra6.ifra_dstaddr.sin6_family = AF_UNSPEC;
+ else
+ memcpy(&ifra6.ifra_dstaddr, &sspeer, sizeof ifra6.ifra_dstaddr);
+ ifra6.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME;
+ ifra6.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME;
+
+ res = ID0ioctl(s, SIOCDIFADDR_IN6, &ifra6);
+ break;
+#endif
+ }
+
+ if (res == -1) {
+ char dst[40];
+ const char *end =
+#ifndef NOINET6
+ ncprange_family(&addr->ifa) == AF_INET6 ? "_IN6" :
+#endif
+ "";
+
+ if (ncpaddr_family(&addr->peer) == AF_UNSPEC)
+ log_Printf(LogWARN, "iface rm: ioctl(SIOCDIFADDR%s, %s): %s\n",
+ end, ncprange_ntoa(&addr->ifa), strerror(errno));
+ else {
+ snprintf(dst, sizeof dst, "%s", ncpaddr_ntoa(&addr->peer));
+ log_Printf(LogWARN, "iface rm: ioctl(SIOCDIFADDR%s, %s -> %s): %s\n",
+ end, ncprange_ntoa(&addr->ifa), dst, strerror(errno));
+ }
+ }
+
+ return res != -1;
+}
+
+static int
+iface_addr_Add(const char *name, struct iface_addr *addr, int s)
+{
+ struct ifaliasreq ifra;
+#ifndef NOINET6
+ struct in6_aliasreq ifra6;
+#endif
+ struct sockaddr_in *me4, *msk4, *peer4;
+ struct sockaddr_storage ssme, sspeer, ssmsk;
+ int res;
+
+ ncprange_getsa(&addr->ifa, &ssme, &ssmsk);
+ ncpaddr_getsa(&addr->peer, &sspeer);
+ res = 0;
+
+ switch (ncprange_family(&addr->ifa)) {
+ case AF_INET:
+ memset(&ifra, '\0', sizeof ifra);
+ strncpy(ifra.ifra_name, name, sizeof ifra.ifra_name - 1);
+
+ me4 = (struct sockaddr_in *)&ifra.ifra_addr;
+ memcpy(me4, &ssme, sizeof *me4);
+
+ msk4 = (struct sockaddr_in *)&ifra.ifra_mask;
+ memcpy(msk4, &ssmsk, sizeof *msk4);
+
+ peer4 = (struct sockaddr_in *)&ifra.ifra_broadaddr;
+ if (ncpaddr_family(&addr->peer) == AF_UNSPEC) {
+ peer4->sin_family = AF_INET;
+ peer4->sin_len = sizeof(*peer4);
+ peer4->sin_addr.s_addr = INADDR_NONE;
+ } else
+ memcpy(peer4, &sspeer, sizeof *peer4);
+
+ res = ID0ioctl(s, SIOCAIFADDR, &ifra);
+ if (log_IsKept(LogDEBUG)) {
+ char buf[100];
+
+ snprintf(buf, sizeof buf, "%s", ncprange_ntoa(&addr->ifa));
+ log_Printf(LogWARN, "%s: AIFADDR %s -> %s returns %d\n",
+ ifra.ifra_name, buf, ncpaddr_ntoa(&addr->peer), res);
+ }
+ break;
+
+#ifndef NOINET6
+ case AF_INET6:
+ memset(&ifra6, '\0', sizeof ifra6);
+ strncpy(ifra6.ifra_name, name, sizeof ifra6.ifra_name - 1);
+
+ memcpy(&ifra6.ifra_addr, &ssme, sizeof ifra6.ifra_addr);
+ memcpy(&ifra6.ifra_prefixmask, &ssmsk, sizeof ifra6.ifra_prefixmask);
+ if (ncpaddr_family(&addr->peer) == AF_UNSPEC)
+ ifra6.ifra_dstaddr.sin6_family = AF_UNSPEC;
+ else if (memcmp(&((struct sockaddr_in6 *)&ssmsk)->sin6_addr, &in6mask128,
+ sizeof in6mask128) == 0)
+ memcpy(&ifra6.ifra_dstaddr, &sspeer, sizeof ifra6.ifra_dstaddr);
+ ifra6.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME;
+ ifra6.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME;
+
+ res = ID0ioctl(s, SIOCAIFADDR_IN6, &ifra6);
+ break;
+#endif
+ }
+
+ if (res == -1) {
+ char dst[40];
+ const char *end =
+#ifndef NOINET6
+ ncprange_family(&addr->ifa) == AF_INET6 ? "_IN6" :
+#endif
+ "";
+
+ if (ncpaddr_family(&addr->peer) == AF_UNSPEC)
+ log_Printf(LogWARN, "iface add: ioctl(SIOCAIFADDR%s, %s): %s\n",
+ end, ncprange_ntoa(&addr->ifa), strerror(errno));
+ else {
+ snprintf(dst, sizeof dst, "%s", ncpaddr_ntoa(&addr->peer));
+ log_Printf(LogWARN, "iface add: ioctl(SIOCAIFADDR%s, %s -> %s): %s\n",
+ end, ncprange_ntoa(&addr->ifa), dst, strerror(errno));
+ }
+ }
+
+ return res != -1;
+}
+
+int
+iface_Name(struct iface *iface, const char *name)
+{
+ struct ifreq ifr;
+ int s;
+ char *newname;
+
+ if ((newname = strdup(name)) == NULL) {
+ log_Printf(LogWARN, "iface name: strdup failed: %s\n", strerror(errno));
+ return 0;
+ }
+
+ if ((s = ID0socket(PF_INET, SOCK_DGRAM, 0)) == -1) {
+ log_Printf(LogERROR, "iface name: socket(): %s\n", strerror(errno));
+ free(newname);
+ return 0;
+ }
+
+ strlcpy(ifr.ifr_name, iface->name, sizeof(ifr.ifr_name));
+ ifr.ifr_data = newname;
+ if (ID0ioctl(s, SIOCSIFNAME, (caddr_t)&ifr) < 0) {
+ log_Printf(LogWARN, "iface name: ioctl(SIOCSIFNAME, %s -> %s): %s\n",
+ name, newname, strerror(errno));
+ free(newname);
+ return 0;
+ }
+
+ free(iface->name);
+ iface->name = newname;
+
+ return 1;
+}
+
+int
+iface_Descr(struct cmdargs const *arg)
+{
+ struct ifreq ifr;
+ struct iface *iface;
+ size_t sz, len;
+ int s, n, ifdescr_maxlen;
+ char *descr;
+
+ sz = sizeof(int);
+ if (sysctlbyname("net.ifdescr_maxlen", &ifdescr_maxlen, &sz, NULL, 0) < 0) {
+ log_Printf(LogERROR, "iface descr: sysctl failed: %s\n", strerror(errno));
+ return 1;
+ }
+
+ if (ifdescr_maxlen < 1) {
+ log_Printf(LogERROR, "iface descr: sysctl net.ifdescr_maxlen < 1\n");
+ return 1;
+ }
+
+ sz = sizeof(char) * ifdescr_maxlen;
+ if ((descr = malloc(sz)) == NULL) {
+ log_Printf(LogERROR, "iface descr: malloc failed: %s\n", strerror(errno));
+ return 1;
+ }
+
+ *descr = '\0';
+ n = arg->argn;
+ while (n < arg->argc) {
+ if (n > arg->argn && (len = strlcat(descr, " ", sz)) >= sz)
+ break;
+ if ((len = strlcat(descr, arg->argv[n], sz)) >= sz)
+ break;
+ ++n;
+ }
+ if (len >= sz) {
+ log_Printf(LogERROR, "iface descr: description exceeds maximum (%d)\n",
+ ifdescr_maxlen-1);
+ free(descr);
+ return 1;
+ }
+
+ if ((s = ID0socket(PF_INET, SOCK_DGRAM, 0)) == -1) {
+ log_Printf(LogERROR, "iface descr: socket(): %s\n", strerror(errno));
+ free(descr);
+ return 1;
+ }
+
+ iface = arg->bundle->iface;
+ strlcpy(ifr.ifr_name, iface->name, sizeof(ifr.ifr_name));
+ ifr.ifr_buffer.length = strlen(descr) + 1;
+ ifr.ifr_buffer.buffer = descr;
+ if (ID0ioctl(s, SIOCSIFDESCR, (caddr_t)&ifr) < 0) {
+ log_Printf(LogWARN, "iface descr: ioctl(SIOCSIFDESCR, %s): %s\n",
+ descr, strerror(errno));
+ free(descr);
+ return 1;
+ }
+
+ free(iface->descr);
+ iface->descr = descr;
+
+ return 0;
+}
+
+void
+iface_Clear(struct iface *iface, struct ncp *ncp, int family, int how)
+{
+ int af, inskip, in6skip, s4 = -1, s6 = -1, *s;
+ unsigned n;
+
+ if (iface->addrs) {
+ inskip = in6skip = how == IFACE_CLEAR_ALL ? 0 : 1;
+
+ for (n = 0; n < iface->addrs; n++) {
+ af = ncprange_family(&iface->addr[n].ifa);
+ if (family == 0 || family == af) {
+ if (!iface->addr[n].system && (how & IFACE_SYSTEM))
+ continue;
+ switch (af) {
+ case AF_INET:
+ if (inskip) {
+ inskip = 0;
+ continue;
+ }
+ s = &s4;
+ break;
+
+#ifndef NOINET6
+ case AF_INET6:
+ if (in6skip) {
+ in6skip = 0;
+ continue;
+ }
+ s = &s6;
+ break;
+#endif
+ default:
+ continue;
+ }
+
+ if (*s == -1 && (*s = ID0socket(af, SOCK_DGRAM, 0)) == -1)
+ log_Printf(LogERROR, "iface_Clear: socket(): %s\n", strerror(errno));
+ else if (iface_addr_Zap(iface->name, iface->addr + n, *s)) {
+ ncp_IfaceAddrDeleted(ncp, iface->addr + n);
+ bcopy(iface->addr + n + 1, iface->addr + n,
+ (iface->addrs - n - 1) * sizeof *iface->addr);
+ iface->addrs--;
+ n--;
+ }
+ }
+ }
+
+ /* Don't bother realloc()ing - we have little to gain */
+
+ if (s4)
+ close(s4);
+ if (s6)
+ close(s6);
+ }
+}
+
+int
+iface_Add(struct iface *iface, struct ncp *ncp, const struct ncprange *ifa,
+ const struct ncpaddr *peer, int how)
+{
+ int af, removed, s;
+ unsigned n;
+ struct ncpaddr ncplocal;
+ struct iface_addr *addr, newaddr;
+
+ af = ncprange_family(ifa);
+ if ((s = ID0socket(af, SOCK_DGRAM, 0)) == -1) {
+ log_Printf(LogERROR, "iface_Add: socket(): %s\n", strerror(errno));
+ return 0;
+ }
+ ncprange_getaddr(ifa, &ncplocal);
+
+ for (n = 0; n < iface->addrs; n++) {
+ if (ncprange_contains(&iface->addr[n].ifa, &ncplocal) ||
+ ncpaddr_equal(&iface->addr[n].peer, peer)) {
+ /* Replace this sockaddr */
+ if (!(how & IFACE_FORCE_ADD)) {
+ close(s);
+ return 0; /* errno = EEXIST; */
+ }
+
+ if (ncprange_equal(&iface->addr[n].ifa, ifa) &&
+ ncpaddr_equal(&iface->addr[n].peer, peer)) {
+ close(s);
+ ncp_IfaceAddrAdded(ncp, iface->addr + n);
+ return 1; /* Already there */
+ }
+
+ removed = iface_addr_Zap(iface->name, iface->addr + n, s);
+ if (removed)
+ ncp_IfaceAddrDeleted(ncp, iface->addr + n);
+ ncprange_copy(&iface->addr[n].ifa, ifa);
+ ncpaddr_copy(&iface->addr[n].peer, peer);
+ if (!iface_addr_Add(iface->name, iface->addr + n, s)) {
+ if (removed) {
+ bcopy(iface->addr + n + 1, iface->addr + n,
+ (iface->addrs - n - 1) * sizeof *iface->addr);
+ iface->addrs--;
+ n--;
+ }
+ close(s);
+ return 0;
+ }
+ close(s);
+ ncp_IfaceAddrAdded(ncp, iface->addr + n);
+ return 1;
+ }
+ }
+
+ addr = (struct iface_addr *)realloc
+ (iface->addr, (iface->addrs + 1) * sizeof iface->addr[0]);
+ if (addr == NULL) {
+ log_Printf(LogERROR, "iface_inAdd: realloc: %s\n", strerror(errno));
+ close(s);
+ return 0;
+ }
+ iface->addr = addr;
+
+ ncprange_copy(&newaddr.ifa, ifa);
+ ncpaddr_copy(&newaddr.peer, peer);
+ newaddr.system = !!(how & IFACE_SYSTEM);
+ if (!iface_addr_Add(iface->name, &newaddr, s)) {
+ close(s);
+ return 0;
+ }
+
+ if (how & IFACE_ADD_FIRST) {
+ /* Stuff it at the start of our list */
+ n = 0;
+ bcopy(iface->addr, iface->addr + 1, iface->addrs * sizeof *iface->addr);
+ } else
+ n = iface->addrs;
+
+ iface->addrs++;
+ memcpy(iface->addr + n, &newaddr, sizeof(*iface->addr));
+
+ close(s);
+ ncp_IfaceAddrAdded(ncp, iface->addr + n);
+
+ return 1;
+}
+
+int
+iface_Delete(struct iface *iface, struct ncp *ncp, const struct ncpaddr *del)
+{
+ struct ncpaddr found;
+ unsigned n;
+ int res, s;
+
+ if ((s = ID0socket(ncpaddr_family(del), SOCK_DGRAM, 0)) == -1) {
+ log_Printf(LogERROR, "iface_Delete: socket(): %s\n", strerror(errno));
+ return 0;
+ }
+
+ for (n = res = 0; n < iface->addrs; n++) {
+ ncprange_getaddr(&iface->addr[n].ifa, &found);
+ if (ncpaddr_equal(&found, del)) {
+ if (iface_addr_Zap(iface->name, iface->addr + n, s)) {
+ ncp_IfaceAddrDeleted(ncp, iface->addr + n);
+ bcopy(iface->addr + n + 1, iface->addr + n,
+ (iface->addrs - n - 1) * sizeof *iface->addr);
+ iface->addrs--;
+ res = 1;
+ }
+ break;
+ }
+ }
+
+ close(s);
+
+ return res;
+}
+
+#define IFACE_ADDFLAGS 1
+#define IFACE_DELFLAGS 2
+
+static int
+iface_ChangeFlags(const char *ifname, int flags, int how)
+{
+ struct ifreq ifrq;
+ int s, new_flags;
+
+ s = ID0socket(PF_INET, SOCK_DGRAM, 0);
+ if (s < 0) {
+ log_Printf(LogERROR, "iface_ChangeFlags: socket: %s\n", strerror(errno));
+ return 0;
+ }
+
+ memset(&ifrq, '\0', sizeof ifrq);
+ strncpy(ifrq.ifr_name, ifname, sizeof ifrq.ifr_name - 1);
+ ifrq.ifr_name[sizeof ifrq.ifr_name - 1] = '\0';
+ if (ID0ioctl(s, SIOCGIFFLAGS, &ifrq) < 0) {
+ log_Printf(LogERROR, "iface_ChangeFlags: ioctl(SIOCGIFFLAGS): %s\n",
+ strerror(errno));
+ close(s);
+ return 0;
+ }
+#ifdef __FreeBSD__
+ new_flags = (ifrq.ifr_flags & 0xffff) | (ifrq.ifr_flagshigh << 16);
+#else
+ new_flags = ifrq.ifr_flags & 0xffff;
+#endif
+
+ if (how == IFACE_ADDFLAGS)
+ new_flags |= flags;
+ else
+ new_flags &= ~flags;
+ ifrq.ifr_flags = new_flags & 0xffff;
+#ifdef __FreeBSD__
+ ifrq.ifr_flagshigh = new_flags >> 16;
+#endif
+
+ if (ID0ioctl(s, SIOCSIFFLAGS, &ifrq) < 0) {
+ log_Printf(LogERROR, "iface_ChangeFlags: ioctl(SIOCSIFFLAGS): %s\n",
+ strerror(errno));
+ close(s);
+ return 0;
+ }
+ close(s);
+
+ return 1; /* Success */
+}
+
+int
+iface_SetFlags(const char *ifname, int flags)
+{
+ return iface_ChangeFlags(ifname, flags, IFACE_ADDFLAGS);
+}
+
+int
+iface_ClearFlags(const char *ifname, int flags)
+{
+ return iface_ChangeFlags(ifname, flags, IFACE_DELFLAGS);
+}
+
+void
+iface_Free(struct iface *iface)
+{
+ free(iface->name);
+ free(iface->descr);
+ free(iface->addr);
+ free(iface);
+}
+
+void
+iface_Destroy(struct iface *iface)
+{
+ struct ifreq ifr;
+ int s;
+
+ if (iface != NULL) {
+ if ((s = ID0socket(PF_INET, SOCK_DGRAM, 0)) == -1) {
+ log_Printf(LogERROR, "iface_Destroy: socket(): %s\n", strerror(errno));
+ } else {
+ strlcpy(ifr.ifr_name, iface->name, sizeof(ifr.ifr_name));
+ if (ID0ioctl(s, SIOCIFDESTROY, (caddr_t)&ifr) < 0)
+ log_Printf(LogWARN, "iface_Destroy: ioctl(SIOCIFDESTROY, %s): %s\n",
+ iface->name, strerror(errno));
+ }
+ iface_Free(iface);
+ }
+}
+
+#define if_entry(x) { IFF_##x, #x }
+
+struct {
+ int flag;
+ const char *value;
+} if_flags[] = {
+ if_entry(UP),
+ if_entry(BROADCAST),
+ if_entry(DEBUG),
+ if_entry(LOOPBACK),
+ if_entry(POINTOPOINT),
+ if_entry(RUNNING),
+ if_entry(NOARP),
+ if_entry(PROMISC),
+ if_entry(ALLMULTI),
+ if_entry(OACTIVE),
+ if_entry(SIMPLEX),
+ if_entry(LINK0),
+ if_entry(LINK1),
+ if_entry(LINK2),
+ if_entry(MULTICAST),
+ { 0, "???" }
+};
+
+int
+iface_Show(struct cmdargs const *arg)
+{
+ struct ncpaddr ncpaddr;
+ struct iface *iface = arg->bundle->iface, *current;
+ unsigned f;
+ int flags;
+#ifndef NOINET6
+ int scopeid, width;
+#endif
+ struct in_addr mask;
+
+ current = iface_Create(iface->name);
+ flags = iface->flags = current->flags;
+ iface_Free(current);
+
+ prompt_Printf(arg->prompt, "%s (idx %d) <", iface->name, iface->index);
+ for (f = 0; f < sizeof if_flags / sizeof if_flags[0]; f++)
+ if ((if_flags[f].flag & flags)) {
+ prompt_Printf(arg->prompt, "%s%s", flags == iface->flags ? "" : ",",
+ if_flags[f].value);
+ flags &= ~if_flags[f].flag;
+ }
+
+#if 0
+ if (flags)
+ prompt_Printf(arg->prompt, "%s0x%x", flags == iface->flags ? "" : ",",
+ flags);
+#endif
+
+ prompt_Printf(arg->prompt, "> mtu %lu has %d address%s:\n", iface->mtu,
+ iface->addrs, iface->addrs == 1 ? "" : "es");
+
+ for (f = 0; f < iface->addrs; f++) {
+ ncprange_getaddr(&iface->addr[f].ifa, &ncpaddr);
+ switch (ncprange_family(&iface->addr[f].ifa)) {
+ case AF_INET:
+ prompt_Printf(arg->prompt, " inet %s --> ", ncpaddr_ntoa(&ncpaddr));
+ if (ncpaddr_family(&iface->addr[f].peer) == AF_UNSPEC)
+ prompt_Printf(arg->prompt, "255.255.255.255");
+ else
+ prompt_Printf(arg->prompt, "%s", ncpaddr_ntoa(&iface->addr[f].peer));
+ ncprange_getip4mask(&iface->addr[f].ifa, &mask);
+ prompt_Printf(arg->prompt, " netmask 0x%08lx", (long)ntohl(mask.s_addr));
+ break;
+
+#ifndef NOINET6
+ case AF_INET6:
+ prompt_Printf(arg->prompt, " inet6 %s", ncpaddr_ntoa(&ncpaddr));
+ if (ncpaddr_family(&iface->addr[f].peer) != AF_UNSPEC)
+ prompt_Printf(arg->prompt, " --> %s",
+ ncpaddr_ntoa(&iface->addr[f].peer));
+ ncprange_getwidth(&iface->addr[f].ifa, &width);
+ if (ncpaddr_family(&iface->addr[f].peer) == AF_UNSPEC)
+ prompt_Printf(arg->prompt, " prefixlen %d", width);
+ if ((scopeid = ncprange_scopeid(&iface->addr[f].ifa)) != -1)
+ prompt_Printf(arg->prompt, " scopeid 0x%x", (unsigned)scopeid);
+ break;
+#endif
+ }
+ prompt_Printf(arg->prompt, "\n");
+ }
+
+ return 0;
+}
+
+void
+iface_ParseHdr(struct ifa_msghdr *ifam, struct sockaddr *sa[RTAX_MAX])
+{
+ char *wp;
+ int rtax;
+
+ wp = (char *)(ifam + 1);
+
+ for (rtax = 0; rtax < RTAX_MAX; rtax++)
+ if (ifam->ifam_addrs & (1 << rtax)) {
+ sa[rtax] = (struct sockaddr *)wp;
+ wp += ROUNDUP(sa[rtax]->sa_len);
+ } else
+ sa[rtax] = NULL;
+}
diff --git a/usr.sbin/ppp/iface.h b/usr.sbin/ppp/iface.h
new file mode 100644
index 0000000..ea3e06d
--- /dev/null
+++ b/usr.sbin/ppp/iface.h
@@ -0,0 +1,69 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.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$
+ */
+
+struct ifa_msghdr;
+
+struct iface_addr {
+ unsigned system : 1; /* System alias ? */
+ struct ncprange ifa; /* local address/mask */
+ struct ncpaddr peer; /* peer address */
+};
+
+struct iface {
+ char *name; /* Interface name (malloc'd) */
+ char *descr; /* Interface description (malloc'd) */
+ int index; /* Interface index */
+ int flags; /* Interface flags (IFF_*) */
+ unsigned long mtu; /* struct tuninfo MTU */
+
+ unsigned addrs; /* How many in_addr's */
+ struct iface_addr *addr; /* Array of addresses (malloc'd) */
+};
+
+#define IFACE_CLEAR_ALL 0 /* Nuke 'em all */
+#define IFACE_CLEAR_ALIASES 1 /* Leave the NCP address */
+
+#define IFACE_ADD_LAST 0 /* Just another alias */
+#define IFACE_ADD_FIRST 1 /* The IPCP address */
+#define IFACE_FORCE_ADD 2 /* OR'd with IFACE_ADD_{FIRST,LAST} */
+
+#define IFACE_SYSTEM 4 /* Set/clear SYSTEM entries */
+
+extern struct iface *iface_Create(const char *name);
+extern void iface_Clear(struct iface *, struct ncp *, int, int);
+extern int iface_Name(struct iface *, const char *);
+extern int iface_Descr(struct cmdargs const *);
+extern int iface_Add(struct iface *, struct ncp *, const struct ncprange *,
+ const struct ncpaddr *, int);
+extern int iface_Delete(struct iface *, struct ncp *, const struct ncpaddr *);
+extern int iface_Show(struct cmdargs const *);
+extern int iface_SetFlags(const char *, int);
+extern int iface_ClearFlags(const char *, int);
+extern void iface_Free(struct iface *);
+extern void iface_Destroy(struct iface *);
+extern void iface_ParseHdr(struct ifa_msghdr *, struct sockaddr *[RTAX_MAX]);
diff --git a/usr.sbin/ppp/ip.c b/usr.sbin/ppp/ip.c
new file mode 100644
index 0000000..5c25a09
--- /dev/null
+++ b/usr.sbin/ppp/ip.c
@@ -0,0 +1,993 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/socket.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#ifndef NOINET6
+#include <netinet/icmp6.h>
+#include <netinet/ip6.h>
+#endif
+#include <netinet/ip_icmp.h>
+#include <netinet/udp.h>
+#include <netinet/tcp.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "proto.h"
+#include "mbuf.h"
+#include "log.h"
+#include "defs.h"
+#include "timer.h"
+#include "fsm.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "iplist.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ip.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "tun.h"
+
+
+#define OPCODE_QUERY 0
+#define OPCODE_IQUERY 1
+#define OPCODE_STATUS 2
+
+struct dns_header {
+ u_short id;
+ unsigned qr : 1;
+ unsigned opcode : 4;
+ unsigned aa : 1;
+ unsigned tc : 1;
+ unsigned rd : 1;
+ unsigned ra : 1;
+ unsigned z : 3;
+ unsigned rcode : 4;
+ u_short qdcount;
+ u_short ancount;
+ u_short nscount;
+ u_short arcount;
+};
+
+static const char *
+dns_Qclass2Txt(u_short qclass)
+{
+ static char failure[6];
+ struct {
+ u_short id;
+ const char *txt;
+ } qtxt[] = {
+ /* rfc1035 */
+ { 1, "IN" }, { 2, "CS" }, { 3, "CH" }, { 4, "HS" }, { 255, "*" }
+ };
+ unsigned f;
+
+ for (f = 0; f < sizeof qtxt / sizeof *qtxt; f++)
+ if (qtxt[f].id == qclass)
+ return qtxt[f].txt;
+
+ return HexStr(qclass, failure, sizeof failure);
+}
+
+static const char *
+dns_Qtype2Txt(u_short qtype)
+{
+ static char failure[6];
+ struct {
+ u_short id;
+ const char *txt;
+ } qtxt[] = {
+ /* rfc1035/rfc1700 */
+ { 1, "A" }, { 2, "NS" }, { 3, "MD" }, { 4, "MF" }, { 5, "CNAME" },
+ { 6, "SOA" }, { 7, "MB" }, { 8, "MG" }, { 9, "MR" }, { 10, "NULL" },
+ { 11, "WKS" }, { 12, "PTR" }, { 13, "HINFO" }, { 14, "MINFO" },
+ { 15, "MX" }, { 16, "TXT" }, { 17, "RP" }, { 18, "AFSDB" },
+ { 19, "X25" }, { 20, "ISDN" }, { 21, "RT" }, { 22, "NSAP" },
+ { 23, "NSAP-PTR" }, { 24, "SIG" }, { 25, "KEY" }, { 26, "PX" },
+ { 27, "GPOS" }, { 28, "AAAA" }, { 252, "AXFR" }, { 253, "MAILB" },
+ { 254, "MAILA" }, { 255, "*" }
+ };
+ unsigned f;
+
+ for (f = 0; f < sizeof qtxt / sizeof *qtxt; f++)
+ if (qtxt[f].id == qtype)
+ return qtxt[f].txt;
+
+ return HexStr(qtype, failure, sizeof failure);
+}
+
+static __inline int
+PortMatch(int op, u_short pport, u_short rport)
+{
+ switch (op) {
+ case OP_EQ:
+ return pport == rport;
+ case OP_GT:
+ return pport > rport;
+ case OP_LT:
+ return pport < rport;
+ default:
+ return 0;
+ }
+}
+
+/*
+ * Return a text string representing the cproto protocol number.
+ *
+ * The purpose of this routine is calculate this result, for
+ * the many times it is needed in FilterCheck, only on demand
+ * (i.e. when the corresponding logging functions are invoked).
+ *
+ * This optimization saves, over the previous implementation, which
+ * calculated prototxt at the beginning of FilterCheck, an
+ * open/read/close system call sequence per packet, approximately
+ * halving the ppp system overhead and reducing the overall (u + s)
+ * time by 38%.
+ *
+ * The caching performed here is just a side effect.
+ */
+static const char *
+prototxt(int cproto)
+{
+ static int oproto = -1;
+ static char protobuff[16] = "-1";
+ struct protoent *pe;
+
+ if (cproto == oproto)
+ return protobuff;
+ if ((pe = getprotobynumber(cproto)) == NULL)
+ snprintf(protobuff, sizeof protobuff, "%d", cproto);
+ else
+ snprintf(protobuff, sizeof protobuff, "%s", pe->p_name);
+ oproto = cproto;
+ return (protobuff);
+}
+
+/*
+ * Check a packet against the given filter
+ * Returns 0 to accept the packet, non-zero to drop the packet.
+ * If psecs is not NULL, populate it with the timeout associated
+ * with the filter rule matched.
+ *
+ * If filtering is enabled, the initial fragment of a datagram must
+ * contain the complete protocol header, and subsequent fragments
+ * must not attempt to over-write it.
+ *
+ * One (and only one) of pip or pip6 must be set.
+ */
+int
+FilterCheck(const unsigned char *packet,
+#ifdef NOINET6
+ u_int32_t family __unused,
+#else
+ u_int32_t family,
+#endif
+ const struct filter *filter, unsigned *psecs)
+{
+ int gotinfo; /* true if IP payload decoded */
+ int cproto; /* IPPROTO_* protocol number if (gotinfo) */
+ int estab, syn, finrst; /* TCP state flags if (gotinfo) */
+ u_short sport, dport; /* src, dest port from packet if (gotinfo) */
+ int n; /* filter rule to process */
+ int len; /* bytes used in dbuff */
+ int didname; /* true if filter header printed */
+ int match; /* true if condition matched */
+ int mindata; /* minimum data size or zero */
+ const struct filterent *fp = filter->rule;
+ char dbuff[100], dstip[16];
+ struct ncpaddr srcaddr, dstaddr;
+ const char *payload; /* IP payload */
+ int datalen; /* IP datagram length */
+
+ if (fp->f_action == A_NONE)
+ return 0; /* No rule is given. Permit this packet */
+
+#ifndef NOINET6
+ if (family == AF_INET6) {
+ const struct ip6_hdr *pip6 = (const struct ip6_hdr *)packet;
+
+ ncpaddr_setip6(&srcaddr, &pip6->ip6_src);
+ ncpaddr_setip6(&dstaddr, &pip6->ip6_dst);
+ datalen = ntohs(pip6->ip6_plen);
+ payload = packet + sizeof *pip6;
+ cproto = pip6->ip6_nxt;
+ } else
+#endif
+ {
+ /*
+ * Deny any packet fragment that tries to over-write the header.
+ * Since we no longer have the real header available, punt on the
+ * largest normal header - 20 bytes for TCP without options, rounded
+ * up to the next possible fragment boundary. Since the smallest
+ * `legal' MTU is 576, and the smallest recommended MTU is 296, any
+ * fragmentation within this range is dubious at best
+ */
+ const struct ip *pip = (const struct ip *)packet;
+
+ len = ntohs(pip->ip_off) & IP_OFFMASK; /* fragment offset */
+ if (len > 0) { /* Not first fragment within datagram */
+ if (len < (24 >> 3)) { /* don't allow fragment to over-write header */
+ log_Printf(LogFILTER, " error: illegal header\n");
+ return 1;
+ }
+ /* permit fragments on in and out filter */
+ if (!filter->fragok) {
+ log_Printf(LogFILTER, " error: illegal fragmentation\n");
+ return 1;
+ } else
+ return 0;
+ }
+
+ ncpaddr_setip4(&srcaddr, pip->ip_src);
+ ncpaddr_setip4(&dstaddr, pip->ip_dst);
+ datalen = ntohs(pip->ip_len) - (pip->ip_hl << 2);
+ payload = packet + (pip->ip_hl << 2);
+ cproto = pip->ip_p;
+ }
+
+
+ gotinfo = estab = syn = finrst = didname = 0;
+ sport = dport = 0;
+
+ for (n = 0; n < MAXFILTERS; ) {
+ if (fp->f_action == A_NONE) {
+ n++;
+ fp++;
+ continue;
+ }
+
+ if (!didname) {
+ log_Printf(LogDEBUG, "%s filter:\n", filter->name);
+ didname = 1;
+ }
+
+ match = 0;
+
+ if ((ncprange_family(&fp->f_src) == AF_UNSPEC ||
+ ncprange_contains(&fp->f_src, &srcaddr)) &&
+ (ncprange_family(&fp->f_dst) == AF_UNSPEC ||
+ ncprange_contains(&fp->f_dst, &dstaddr))) {
+ if (fp->f_proto != 0) {
+ if (!gotinfo) {
+ const struct tcphdr *th;
+ const struct udphdr *uh;
+ const struct icmp *ih;
+#ifndef NOINET6
+ const struct icmp6_hdr *ih6;
+#endif
+ mindata = 0;
+ sport = dport = 0;
+ estab = syn = finrst = -1;
+
+ switch (cproto) {
+ case IPPROTO_ICMP:
+ mindata = 8; /* ICMP must be at least 8 octets */
+ ih = (const struct icmp *)payload;
+ sport = ih->icmp_type;
+ if (log_IsKept(LogDEBUG))
+ snprintf(dbuff, sizeof dbuff, "sport = %d", sport);
+ break;
+
+#ifndef NOINET6
+ case IPPROTO_ICMPV6:
+ mindata = 8; /* ICMP must be at least 8 octets */
+ ih6 = (const struct icmp6_hdr *)payload;
+ sport = ih6->icmp6_type;
+ if (log_IsKept(LogDEBUG))
+ snprintf(dbuff, sizeof dbuff, "sport = %d", sport);
+ break;
+#endif
+
+ case IPPROTO_IGMP:
+ mindata = 8; /* IGMP uses 8-octet messages */
+ break;
+
+#ifdef IPPROTO_GRE
+ case IPPROTO_GRE:
+ mindata = 2; /* GRE uses 2-octet+ messages */
+ break;
+#endif
+#ifdef IPPROTO_OSPFIGP
+ case IPPROTO_OSPFIGP:
+ mindata = 8; /* IGMP uses 8-octet messages */
+ break;
+#endif
+#ifndef NOINET6
+ case IPPROTO_IPV6:
+ mindata = 20; /* RFC2893 Section 3.5: 5 * 32bit words */
+ break;
+#endif
+
+ case IPPROTO_UDP:
+ mindata = 8; /* UDP header is 8 octets */
+ uh = (const struct udphdr *)payload;
+ sport = ntohs(uh->uh_sport);
+ dport = ntohs(uh->uh_dport);
+ if (log_IsKept(LogDEBUG))
+ snprintf(dbuff, sizeof dbuff, "sport = %d, dport = %d",
+ sport, dport);
+ break;
+
+ case IPPROTO_TCP:
+ th = (const struct tcphdr *)payload;
+ /*
+ * TCP headers are variable length. The following code
+ * ensures that the TCP header length isn't de-referenced if
+ * the datagram is too short
+ */
+ if (datalen < 20 || datalen < (th->th_off << 2)) {
+ log_Printf(LogFILTER, " error: TCP header incorrect\n");
+ return 1;
+ }
+ sport = ntohs(th->th_sport);
+ dport = ntohs(th->th_dport);
+ estab = (th->th_flags & TH_ACK);
+ syn = (th->th_flags & TH_SYN);
+ finrst = (th->th_flags & (TH_FIN|TH_RST));
+ if (log_IsKept(LogDEBUG)) {
+ if (!estab)
+ snprintf(dbuff, sizeof dbuff,
+ "flags = %02x, sport = %d, dport = %d",
+ th->th_flags, sport, dport);
+ else
+ *dbuff = '\0';
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (datalen < mindata) {
+ log_Printf(LogFILTER, " error: proto %s must be at least"
+ " %d octets\n", prototxt(cproto), mindata);
+ return 1;
+ }
+
+ if (log_IsKept(LogDEBUG)) {
+ if (estab != -1) {
+ len = strlen(dbuff);
+ snprintf(dbuff + len, sizeof dbuff - len,
+ ", estab = %d, syn = %d, finrst = %d",
+ estab, syn, finrst);
+ }
+ log_Printf(LogDEBUG, " Filter: proto = %s, %s\n",
+ prototxt(cproto), dbuff);
+ }
+ gotinfo = 1;
+ }
+
+ if (log_IsKept(LogDEBUG)) {
+ if (fp->f_srcop != OP_NONE) {
+ snprintf(dbuff, sizeof dbuff, ", src %s %d",
+ filter_Op2Nam(fp->f_srcop), fp->f_srcport);
+ len = strlen(dbuff);
+ } else
+ len = 0;
+ if (fp->f_dstop != OP_NONE) {
+ snprintf(dbuff + len, sizeof dbuff - len,
+ ", dst %s %d", filter_Op2Nam(fp->f_dstop),
+ fp->f_dstport);
+ } else if (!len)
+ *dbuff = '\0';
+
+ log_Printf(LogDEBUG, " rule = %d: Address match, "
+ "check against proto %d%s, action = %s\n",
+ n, fp->f_proto, dbuff, filter_Action2Nam(fp->f_action));
+ }
+
+ if (cproto == fp->f_proto) {
+ if ((fp->f_srcop == OP_NONE ||
+ PortMatch(fp->f_srcop, sport, fp->f_srcport)) &&
+ (fp->f_dstop == OP_NONE ||
+ PortMatch(fp->f_dstop, dport, fp->f_dstport)) &&
+ (fp->f_estab == 0 || estab) &&
+ (fp->f_syn == 0 || syn) &&
+ (fp->f_finrst == 0 || finrst)) {
+ match = 1;
+ }
+ }
+ } else {
+ /* Address is matched and no protocol specified. Make a decision. */
+ log_Printf(LogDEBUG, " rule = %d: Address match, action = %s\n", n,
+ filter_Action2Nam(fp->f_action));
+ match = 1;
+ }
+ } else
+ log_Printf(LogDEBUG, " rule = %d: Address mismatch\n", n);
+
+ if (match != fp->f_invert) {
+ /* Take specified action */
+ if (fp->f_action < A_NONE)
+ fp = &filter->rule[n = fp->f_action];
+ else {
+ if (fp->f_action == A_PERMIT) {
+ if (psecs != NULL)
+ *psecs = fp->timeout;
+ if (strcmp(filter->name, "DIAL") == 0) {
+ /* If dial filter then even print out accept packets */
+ if (log_IsKept(LogFILTER)) {
+ snprintf(dstip, sizeof dstip, "%s", ncpaddr_ntoa(&dstaddr));
+ log_Printf(LogFILTER, "%sbound rule = %d accept %s "
+ "src = %s:%d dst = %s:%d\n", filter->name, n,
+ prototxt(cproto), ncpaddr_ntoa(&srcaddr), sport,
+ dstip, dport);
+ }
+ }
+ return 0;
+ } else {
+ if (log_IsKept(LogFILTER)) {
+ snprintf(dstip, sizeof dstip, "%s", ncpaddr_ntoa(&dstaddr));
+ log_Printf(LogFILTER,
+ "%sbound rule = %d deny %s src = %s/%d dst = %s/%d\n",
+ filter->name, n, prototxt(cproto),
+ ncpaddr_ntoa(&srcaddr), sport, dstip, dport);
+ }
+ return 1;
+ } /* Explicit match. Deny this packet */
+ }
+ } else {
+ n++;
+ fp++;
+ }
+ }
+
+ if (log_IsKept(LogFILTER)) {
+ snprintf(dstip, sizeof dstip, "%s", ncpaddr_ntoa(&dstaddr));
+ log_Printf(LogFILTER,
+ "%sbound rule = implicit deny %s src = %s/%d dst = %s/%d\n",
+ filter->name, prototxt(cproto), ncpaddr_ntoa(&srcaddr),
+ sport, dstip, dport);
+ }
+
+ return 1; /* No rule matched, deny this packet */
+}
+
+static void
+ip_LogDNS(const struct udphdr *uh, const char *direction)
+{
+ struct dns_header header;
+ const u_short *pktptr;
+ const u_char *ptr;
+ u_short *hptr, tmp;
+ unsigned len;
+
+ ptr = (const char *)uh + sizeof *uh;
+ len = ntohs(uh->uh_ulen) - sizeof *uh;
+ if (len < sizeof header + 5) /* rfc1024 */
+ return;
+
+ pktptr = (const u_short *)ptr;
+ hptr = (u_short *)&header;
+ ptr += sizeof header;
+ len -= sizeof header;
+
+ while (pktptr < (const u_short *)ptr) {
+ *hptr++ = ntohs(*pktptr); /* Careful of macro side-effects ! */
+ pktptr++;
+ }
+
+ if (header.opcode == OPCODE_QUERY && header.qr == 0) {
+ /* rfc1035 */
+ char namewithdot[MAXHOSTNAMELEN + 1], *n;
+ const char *qtype, *qclass;
+ const u_char *end;
+
+ n = namewithdot;
+ end = ptr + len - 4;
+ if (end - ptr >= (int)sizeof namewithdot)
+ end = ptr + sizeof namewithdot - 1;
+ while (ptr < end) {
+ len = *ptr++;
+ if ((int)len > end - ptr)
+ len = end - ptr;
+ if (n != namewithdot)
+ *n++ = '.';
+ memcpy(n, ptr, len);
+ ptr += len;
+ n += len;
+ }
+ *n = '\0';
+
+ if (log_IsKept(LogDNS)) {
+ memcpy(&tmp, end, sizeof tmp);
+ qtype = dns_Qtype2Txt(ntohs(tmp));
+ memcpy(&tmp, end + 2, sizeof tmp);
+ qclass = dns_Qclass2Txt(ntohs(tmp));
+
+ log_Printf(LogDNS, "%sbound query %s %s %s\n",
+ direction, qclass, qtype, namewithdot);
+ }
+ }
+}
+
+/*
+ * Check if the given packet matches the given filter.
+ * One of pip or pip6 must be set.
+ */
+int
+PacketCheck(struct bundle *bundle, u_int32_t family,
+ const unsigned char *packet, int nb, struct filter *filter,
+ const char *prefix, unsigned *psecs)
+{
+ char logbuf[200];
+ static const char *const TcpFlags[] = {
+ "FIN", "SYN", "RST", "PSH", "ACK", "URG"
+ };
+ const struct tcphdr *th;
+ const struct udphdr *uh;
+ const struct icmp *icmph;
+#ifndef NOINET6
+ const struct icmp6_hdr *icmp6h;
+#endif
+ const unsigned char *payload;
+ struct ncpaddr srcaddr, dstaddr;
+ int cproto, mask, len, n, pri, logit, result, datalen, frag;
+ unsigned loglen;
+ u_char tos;
+
+ logit = (log_IsKept(LogTCPIP) || log_IsKept(LogDNS)) &&
+ (!filter || filter->logok);
+ loglen = 0;
+ pri = 0;
+
+#ifndef NOINET6
+ if (family == AF_INET6) {
+ const struct ip6_hdr *pip6 = (const struct ip6_hdr *)packet;
+
+ ncpaddr_setip6(&srcaddr, &pip6->ip6_src);
+ ncpaddr_setip6(&dstaddr, &pip6->ip6_dst);
+ datalen = ntohs(pip6->ip6_plen);
+ payload = packet + sizeof *pip6;
+ cproto = pip6->ip6_nxt;
+ tos = 0; /* XXX: pip6->ip6_vfc >> 4 ? */
+ frag = 0; /* XXX: ??? */
+ } else
+#endif
+ {
+ const struct ip *pip = (const struct ip *)packet;
+
+ ncpaddr_setip4(&srcaddr, pip->ip_src);
+ ncpaddr_setip4(&dstaddr, pip->ip_dst);
+ datalen = ntohs(pip->ip_len) - (pip->ip_hl << 2);
+ payload = packet + (pip->ip_hl << 2);
+ cproto = pip->ip_p;
+ tos = pip->ip_tos;
+ frag = ntohs(pip->ip_off) & IP_OFFMASK;
+ }
+
+ uh = NULL;
+
+ if (logit && loglen < sizeof logbuf) {
+ if (prefix)
+ snprintf(logbuf + loglen, sizeof logbuf - loglen, "%s", prefix);
+ else if (filter)
+ snprintf(logbuf + loglen, sizeof logbuf - loglen, "%s ", filter->name);
+ else
+ snprintf(logbuf + loglen, sizeof logbuf - loglen, " ");
+ loglen += strlen(logbuf + loglen);
+ }
+
+ switch (cproto) {
+ case IPPROTO_ICMP:
+ if (logit && loglen < sizeof logbuf) {
+ len = datalen - sizeof *icmph;
+ icmph = (const struct icmp *)payload;
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ "ICMP: %s:%d ---> ", ncpaddr_ntoa(&srcaddr), icmph->icmp_type);
+ loglen += strlen(logbuf + loglen);
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ "%s (%d/%d)", ncpaddr_ntoa(&dstaddr), len, nb);
+ loglen += strlen(logbuf + loglen);
+ }
+ break;
+
+#ifndef NOINET6
+ case IPPROTO_ICMPV6:
+ if (logit && loglen < sizeof logbuf) {
+ len = datalen - sizeof *icmp6h;
+ icmp6h = (const struct icmp6_hdr *)payload;
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ "ICMP: %s:%d ---> ", ncpaddr_ntoa(&srcaddr), icmp6h->icmp6_type);
+ loglen += strlen(logbuf + loglen);
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ "%s (%d/%d)", ncpaddr_ntoa(&dstaddr), len, nb);
+ loglen += strlen(logbuf + loglen);
+ }
+ break;
+#endif
+
+ case IPPROTO_UDP:
+ uh = (const struct udphdr *)payload;
+ if (tos == IPTOS_LOWDELAY && bundle->ncp.cfg.urgent.tos)
+ pri++;
+
+ if (!frag && ncp_IsUrgentUdpPort(&bundle->ncp, ntohs(uh->uh_sport),
+ ntohs(uh->uh_dport)))
+ pri++;
+
+ if (logit && loglen < sizeof logbuf) {
+ len = datalen - sizeof *uh;
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ "UDP: %s:%d ---> ", ncpaddr_ntoa(&srcaddr), ntohs(uh->uh_sport));
+ loglen += strlen(logbuf + loglen);
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ "%s:%d (%d/%d)", ncpaddr_ntoa(&dstaddr), ntohs(uh->uh_dport),
+ len, nb);
+ loglen += strlen(logbuf + loglen);
+ }
+
+ if (Enabled(bundle, OPT_FILTERDECAP) &&
+ payload[sizeof *uh] == HDLC_ADDR &&
+ payload[sizeof *uh + 1] == HDLC_UI) {
+ u_short proto;
+ const char *type;
+
+ memcpy(&proto, payload + sizeof *uh + 2, sizeof proto);
+ type = NULL;
+
+ switch (ntohs(proto)) {
+ case PROTO_IP:
+ snprintf(logbuf + loglen, sizeof logbuf - loglen, " contains ");
+ result = PacketCheck(bundle, AF_INET, payload + sizeof *uh + 4,
+ nb - (payload - packet) - sizeof *uh - 4, filter,
+ logbuf, psecs);
+ if (result != -2)
+ return result;
+ type = "IP";
+ break;
+
+ case PROTO_VJUNCOMP: type = "compressed VJ"; break;
+ case PROTO_VJCOMP: type = "uncompressed VJ"; break;
+ case PROTO_MP: type = "Multi-link"; break;
+ case PROTO_ICOMPD: type = "Individual link CCP"; break;
+ case PROTO_COMPD: type = "CCP"; break;
+ case PROTO_IPCP: type = "IPCP"; break;
+ case PROTO_LCP: type = "LCP"; break;
+ case PROTO_PAP: type = "PAP"; break;
+ case PROTO_CBCP: type = "CBCP"; break;
+ case PROTO_LQR: type = "LQR"; break;
+ case PROTO_CHAP: type = "CHAP"; break;
+ }
+ if (type) {
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ " - %s data", type);
+ loglen += strlen(logbuf + loglen);
+ }
+ }
+
+ break;
+
+#ifdef IPPROTO_GRE
+ case IPPROTO_GRE:
+ if (logit && loglen < sizeof logbuf) {
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ "GRE: %s ---> ", ncpaddr_ntoa(&srcaddr));
+ loglen += strlen(logbuf + loglen);
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ "%s (%d/%d)", ncpaddr_ntoa(&dstaddr), datalen, nb);
+ loglen += strlen(logbuf + loglen);
+ }
+ break;
+#endif
+
+#ifdef IPPROTO_OSPFIGP
+ case IPPROTO_OSPFIGP:
+ if (logit && loglen < sizeof logbuf) {
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ "OSPF: %s ---> ", ncpaddr_ntoa(&srcaddr));
+ loglen += strlen(logbuf + loglen);
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ "%s (%d/%d)", ncpaddr_ntoa(&dstaddr), datalen, nb);
+ loglen += strlen(logbuf + loglen);
+ }
+ break;
+#endif
+
+#ifndef NOINET6
+ case IPPROTO_IPV6:
+ if (logit && loglen < sizeof logbuf) {
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ "IPv6: %s ---> ", ncpaddr_ntoa(&srcaddr));
+ loglen += strlen(logbuf + loglen);
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ "%s (%d/%d)", ncpaddr_ntoa(&dstaddr), datalen, nb);
+ loglen += strlen(logbuf + loglen);
+ }
+
+ if (Enabled(bundle, OPT_FILTERDECAP)) {
+ snprintf(logbuf + loglen, sizeof logbuf - loglen, " contains ");
+ result = PacketCheck(bundle, AF_INET6, payload, nb - (payload - packet),
+ filter, logbuf, psecs);
+ if (result != -2)
+ return result;
+ }
+ break;
+#endif
+
+ case IPPROTO_IPIP:
+ if (logit && loglen < sizeof logbuf) {
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ "IPIP: %s ---> ", ncpaddr_ntoa(&srcaddr));
+ loglen += strlen(logbuf + loglen);
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ "%s", ncpaddr_ntoa(&dstaddr));
+ loglen += strlen(logbuf + loglen);
+ }
+
+ if (Enabled(bundle, OPT_FILTERDECAP) &&
+ ((const struct ip *)payload)->ip_v == 4) {
+ snprintf(logbuf + loglen, sizeof logbuf - loglen, " contains ");
+ result = PacketCheck(bundle, AF_INET, payload, nb - (payload - packet),
+ filter, logbuf, psecs);
+ loglen += strlen(logbuf + loglen);
+ if (result != -2)
+ return result;
+ }
+ break;
+
+ case IPPROTO_ESP:
+ if (logit && loglen < sizeof logbuf) {
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ "ESP: %s ---> ", ncpaddr_ntoa(&srcaddr));
+ loglen += strlen(logbuf + loglen);
+ snprintf(logbuf + loglen, sizeof logbuf - loglen, "%s, spi %p",
+ ncpaddr_ntoa(&dstaddr), payload);
+ loglen += strlen(logbuf + loglen);
+ }
+ break;
+
+ case IPPROTO_AH:
+ if (logit && loglen < sizeof logbuf) {
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ "AH: %s ---> ", ncpaddr_ntoa(&srcaddr));
+ loglen += strlen(logbuf + loglen);
+ snprintf(logbuf + loglen, sizeof logbuf - loglen, "%s, spi %p",
+ ncpaddr_ntoa(&dstaddr), payload + sizeof(u_int32_t));
+ loglen += strlen(logbuf + loglen);
+ }
+ break;
+
+ case IPPROTO_IGMP:
+ if (logit && loglen < sizeof logbuf) {
+ uh = (const struct udphdr *)payload;
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ "IGMP: %s:%d ---> ", ncpaddr_ntoa(&srcaddr),
+ ntohs(uh->uh_sport));
+ loglen += strlen(logbuf + loglen);
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ "%s:%d", ncpaddr_ntoa(&dstaddr), ntohs(uh->uh_dport));
+ loglen += strlen(logbuf + loglen);
+ }
+ break;
+
+ case IPPROTO_TCP:
+ th = (const struct tcphdr *)payload;
+ if (tos == IPTOS_LOWDELAY && bundle->ncp.cfg.urgent.tos)
+ pri++;
+
+ if (!frag && ncp_IsUrgentTcpPort(&bundle->ncp, ntohs(th->th_sport),
+ ntohs(th->th_dport)))
+ pri++;
+
+ if (logit && loglen < sizeof logbuf) {
+ len = datalen - (th->th_off << 2);
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ "TCP: %s:%d ---> ", ncpaddr_ntoa(&srcaddr), ntohs(th->th_sport));
+ loglen += strlen(logbuf + loglen);
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ "%s:%d", ncpaddr_ntoa(&dstaddr), ntohs(th->th_dport));
+ loglen += strlen(logbuf + loglen);
+ n = 0;
+ for (mask = TH_FIN; mask != 0x40; mask <<= 1) {
+ if (th->th_flags & mask) {
+ snprintf(logbuf + loglen, sizeof logbuf - loglen, " %s", TcpFlags[n]);
+ loglen += strlen(logbuf + loglen);
+ }
+ n++;
+ }
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ " seq:%lx ack:%lx (%d/%d)",
+ (u_long)ntohl(th->th_seq), (u_long)ntohl(th->th_ack), len, nb);
+ loglen += strlen(logbuf + loglen);
+ if ((th->th_flags & TH_SYN) && nb > 40) {
+ const u_short *sp;
+
+ sp = (const u_short *)(payload + 20);
+ if (ntohs(sp[0]) == 0x0204) {
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ " MSS = %d", ntohs(sp[1]));
+ loglen += strlen(logbuf + loglen);
+ }
+ }
+ }
+ break;
+
+ default:
+ if (prefix)
+ return -2;
+
+ if (logit && loglen < sizeof logbuf) {
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ "<%d>: %s ---> ", cproto, ncpaddr_ntoa(&srcaddr));
+ loglen += strlen(logbuf + loglen);
+ snprintf(logbuf + loglen, sizeof logbuf - loglen,
+ "%s (%d)", ncpaddr_ntoa(&dstaddr), nb);
+ loglen += strlen(logbuf + loglen);
+ }
+ break;
+ }
+
+ if (filter && FilterCheck(packet, family, filter, psecs)) {
+ if (logit)
+ log_Printf(LogTCPIP, "%s - BLOCKED\n", logbuf);
+ result = -1;
+ } else {
+ /* Check Keep Alive filter */
+ if (logit && log_IsKept(LogTCPIP)) {
+ unsigned alivesecs;
+
+ alivesecs = 0;
+ if (filter &&
+ FilterCheck(packet, family, &bundle->filter.alive, &alivesecs))
+ log_Printf(LogTCPIP, "%s - NO KEEPALIVE\n", logbuf);
+ else if (psecs != NULL) {
+ if(*psecs == 0)
+ *psecs = alivesecs;
+ if (*psecs) {
+ if (*psecs != alivesecs)
+ log_Printf(LogTCPIP, "%s - (timeout = %d / ALIVE = %d secs)\n",
+ logbuf, *psecs, alivesecs);
+ else
+ log_Printf(LogTCPIP, "%s - (timeout = %d secs)\n", logbuf, *psecs);
+ } else
+ log_Printf(LogTCPIP, "%s\n", logbuf);
+ }
+ }
+ result = pri;
+ }
+
+ if (filter && uh && ntohs(uh->uh_dport) == 53 && log_IsKept(LogDNS))
+ ip_LogDNS(uh, filter->name);
+
+ return result;
+}
+
+static size_t
+ip_Input(struct bundle *bundle, struct link *l, struct mbuf *bp, u_int32_t af)
+{
+ ssize_t nw;
+ size_t nb;
+ struct tun_data tun;
+ char *data;
+ unsigned secs, alivesecs;
+
+ nb = m_length(bp);
+ if (nb > sizeof tun.data) {
+ log_Printf(LogWARN, "ip_Input: %s: Packet too large (got %zd, max %d)\n",
+ l->name, nb, (int)(sizeof tun.data));
+ m_freem(bp);
+ return 0;
+ }
+ mbuf_Read(bp, tun.data, nb);
+
+ secs = 0;
+ if (PacketCheck(bundle, af, tun.data, nb, &bundle->filter.in,
+ NULL, &secs) < 0)
+ return 0;
+
+ alivesecs = 0;
+ if (!FilterCheck(tun.data, af, &bundle->filter.alive, &alivesecs)) {
+ if (secs == 0)
+ secs = alivesecs;
+ bundle_StartIdleTimer(bundle, secs);
+ }
+
+ if (bundle->dev.header) {
+ tun.header.family = htonl(af);
+ nb += sizeof tun - sizeof tun.data;
+ data = (char *)&tun;
+ } else
+ data = tun.data;
+
+ nw = write(bundle->dev.fd, data, nb);
+ if (nw != (ssize_t)nb) {
+ if (nw == -1)
+ log_Printf(LogERROR, "ip_Input: %s: wrote %zd, got %s\n",
+ l->name, nb, strerror(errno));
+ else
+ log_Printf(LogERROR, "ip_Input: %s: wrote %zd, got %zd\n", l->name, nb,
+ nw);
+ }
+
+ return nb;
+}
+
+struct mbuf *
+ipv4_Input(struct bundle *bundle, struct link *l, struct mbuf *bp)
+{
+ int nb;
+
+ if (bundle->ncp.ipcp.fsm.state != ST_OPENED) {
+ log_Printf(LogWARN, "ipv4_Input: IPCP not open - packet dropped\n");
+ m_freem(bp);
+ return NULL;
+ }
+
+ m_settype(bp, MB_IPIN);
+
+ nb = ip_Input(bundle, l, bp, AF_INET);
+ ipcp_AddInOctets(&bundle->ncp.ipcp, nb);
+
+ return NULL;
+}
+
+#ifndef NOINET6
+struct mbuf *
+ipv6_Input(struct bundle *bundle, struct link *l, struct mbuf *bp)
+{
+ int nb;
+
+ if (bundle->ncp.ipv6cp.fsm.state != ST_OPENED) {
+ log_Printf(LogWARN, "ipv6_Input: IPV6CP not open - packet dropped\n");
+ m_freem(bp);
+ return NULL;
+ }
+
+ m_settype(bp, MB_IPV6IN);
+
+ nb = ip_Input(bundle, l, bp, AF_INET6);
+ ipv6cp_AddInOctets(&bundle->ncp.ipv6cp, nb);
+
+ return NULL;
+}
+#endif
diff --git a/usr.sbin/ppp/ip.h b/usr.sbin/ppp/ip.h
new file mode 100644
index 0000000..a4c4179
--- /dev/null
+++ b/usr.sbin/ppp/ip.h
@@ -0,0 +1,44 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+struct mbuf;
+struct filter;
+struct link;
+struct bundle;
+
+extern int ip_PushPacket(struct link *, struct bundle *);
+extern int PacketCheck(struct bundle *, u_int32_t, const unsigned char *, int,
+ struct filter *, const char *, unsigned *secs);
+extern int FilterCheck(const unsigned char *, u_int32_t, const struct filter *,
+ unsigned *);
+extern struct mbuf *ipv4_Input(struct bundle *, struct link *, struct mbuf *);
+#ifndef NOINET6
+extern struct mbuf *ipv6_Input(struct bundle *, struct link *, struct mbuf *);
+#endif
diff --git a/usr.sbin/ppp/ipcp.c b/usr.sbin/ppp/ipcp.c
new file mode 100644
index 0000000..dbb1e79
--- /dev/null
+++ b/usr.sbin/ppp/ipcp.c
@@ -0,0 +1,1482 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <net/route.h>
+#include <netdb.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <resolv.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <termios.h>
+#include <unistd.h>
+
+#ifndef NONAT
+#ifdef LOCALNAT
+#include "alias.h"
+#else
+#include <alias.h>
+#endif
+#endif
+
+#include "layer.h"
+#include "ua.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "proto.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ncpaddr.h"
+#include "ip.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "vjcomp.h"
+#include "async.h"
+#include "ccp.h"
+#include "link.h"
+#include "physical.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "id.h"
+#include "arp.h"
+#include "systems.h"
+#include "prompt.h"
+#include "route.h"
+#include "iface.h"
+
+#undef REJECTED
+#define REJECTED(p, x) ((p)->peer_reject & (1<<(x)))
+#define issep(ch) ((ch) == ' ' || (ch) == '\t')
+#define isip(ch) (((ch) >= '0' && (ch) <= '9') || (ch) == '.')
+
+struct compreq {
+ u_short proto;
+ u_char slots;
+ u_char compcid;
+};
+
+static int IpcpLayerUp(struct fsm *);
+static void IpcpLayerDown(struct fsm *);
+static void IpcpLayerStart(struct fsm *);
+static void IpcpLayerFinish(struct fsm *);
+static void IpcpInitRestartCounter(struct fsm *, int);
+static void IpcpSendConfigReq(struct fsm *);
+static void IpcpSentTerminateReq(struct fsm *);
+static void IpcpSendTerminateAck(struct fsm *, u_char);
+static void IpcpDecodeConfig(struct fsm *, u_char *, u_char *, int,
+ struct fsm_decode *);
+
+extern struct libalias *la;
+
+static struct fsm_callbacks ipcp_Callbacks = {
+ IpcpLayerUp,
+ IpcpLayerDown,
+ IpcpLayerStart,
+ IpcpLayerFinish,
+ IpcpInitRestartCounter,
+ IpcpSendConfigReq,
+ IpcpSentTerminateReq,
+ IpcpSendTerminateAck,
+ IpcpDecodeConfig,
+ fsm_NullRecvResetReq,
+ fsm_NullRecvResetAck
+};
+
+static const char *
+protoname(int proto)
+{
+ static struct {
+ int id;
+ const char *txt;
+ } cftypes[] = {
+ /* Check out the latest ``Assigned numbers'' rfc (rfc1700.txt) */
+ { 1, "IPADDRS" }, /* IP-Addresses */ /* deprecated */
+ { 2, "COMPPROTO" }, /* IP-Compression-Protocol */
+ { 3, "IPADDR" }, /* IP-Address */
+ { 129, "PRIDNS" }, /* 129: Primary DNS Server Address */
+ { 130, "PRINBNS" }, /* 130: Primary NBNS Server Address */
+ { 131, "SECDNS" }, /* 131: Secondary DNS Server Address */
+ { 132, "SECNBNS" } /* 132: Secondary NBNS Server Address */
+ };
+ unsigned f;
+
+ for (f = 0; f < sizeof cftypes / sizeof *cftypes; f++)
+ if (cftypes[f].id == proto)
+ return cftypes[f].txt;
+
+ return NumStr(proto, NULL, 0);
+}
+
+void
+ipcp_AddInOctets(struct ipcp *ipcp, int n)
+{
+ throughput_addin(&ipcp->throughput, n);
+}
+
+void
+ipcp_AddOutOctets(struct ipcp *ipcp, int n)
+{
+ throughput_addout(&ipcp->throughput, n);
+}
+
+void
+ipcp_LoadDNS(struct ipcp *ipcp)
+{
+ int fd;
+
+ ipcp->ns.dns[0].s_addr = ipcp->ns.dns[1].s_addr = INADDR_NONE;
+
+ if (ipcp->ns.resolv != NULL) {
+ free(ipcp->ns.resolv);
+ ipcp->ns.resolv = NULL;
+ }
+ if (ipcp->ns.resolv_nons != NULL) {
+ free(ipcp->ns.resolv_nons);
+ ipcp->ns.resolv_nons = NULL;
+ }
+ ipcp->ns.resolver = 0;
+
+ if ((fd = open(_PATH_RESCONF, O_RDONLY)) != -1) {
+ struct stat st;
+
+ if (fstat(fd, &st) == 0) {
+ ssize_t got;
+
+ /*
+ * Note, ns.resolv and ns.resolv_nons are assumed to always point to
+ * buffers of the same size! See the strcpy() below.
+ */
+ if ((ipcp->ns.resolv_nons = (char *)malloc(st.st_size + 1)) == NULL)
+ log_Printf(LogERROR, "Failed to malloc %lu for %s: %s\n",
+ (unsigned long)st.st_size, _PATH_RESCONF, strerror(errno));
+ else if ((ipcp->ns.resolv = (char *)malloc(st.st_size + 1)) == NULL) {
+ log_Printf(LogERROR, "Failed(2) to malloc %lu for %s: %s\n",
+ (unsigned long)st.st_size, _PATH_RESCONF, strerror(errno));
+ free(ipcp->ns.resolv_nons);
+ ipcp->ns.resolv_nons = NULL;
+ } else if ((got = read(fd, ipcp->ns.resolv, st.st_size)) != st.st_size) {
+ if (got == -1)
+ log_Printf(LogERROR, "Failed to read %s: %s\n",
+ _PATH_RESCONF, strerror(errno));
+ else
+ log_Printf(LogERROR, "Failed to read %s, got %lu not %lu\n",
+ _PATH_RESCONF, (unsigned long)got,
+ (unsigned long)st.st_size);
+ free(ipcp->ns.resolv_nons);
+ ipcp->ns.resolv_nons = NULL;
+ free(ipcp->ns.resolv);
+ ipcp->ns.resolv = NULL;
+ } else {
+ char *cp, *cp_nons, *ncp, ch;
+ int n;
+
+ ipcp->ns.resolv[st.st_size] = '\0';
+ ipcp->ns.resolver = 1;
+
+ cp_nons = ipcp->ns.resolv_nons;
+ cp = ipcp->ns.resolv;
+ n = 0;
+
+ while ((ncp = strstr(cp, "nameserver")) != NULL) {
+ if (ncp != cp) {
+ memcpy(cp_nons, cp, ncp - cp);
+ cp_nons += ncp - cp;
+ }
+ if ((ncp != cp && ncp[-1] != '\n') || !issep(ncp[10])) {
+ memcpy(cp_nons, ncp, 9);
+ cp_nons += 9;
+ cp = ncp + 9; /* Can't match "nameserver" at cp... */
+ continue;
+ }
+
+ for (cp = ncp + 11; issep(*cp); cp++) /* Skip whitespace */
+ ;
+
+ for (ncp = cp; isip(*ncp); ncp++) /* Jump over IP */
+ ;
+
+ ch = *ncp;
+ *ncp = '\0';
+ if (n < 2 && inet_aton(cp, ipcp->ns.dns))
+ n++;
+ *ncp = ch;
+
+ if ((cp = strchr(ncp, '\n')) == NULL) /* Point at next line */
+ cp = ncp + strlen(ncp);
+ else
+ cp++;
+ }
+ /*
+ * Note, cp_nons and cp always point to buffers of the same size, so
+ * strcpy is ok!
+ */
+ strcpy(cp_nons, cp); /* Copy the end - including the NUL */
+ cp_nons += strlen(cp_nons) - 1;
+ while (cp_nons >= ipcp->ns.resolv_nons && *cp_nons == '\n')
+ *cp_nons-- = '\0';
+ if (n == 2 && ipcp->ns.dns[0].s_addr == INADDR_ANY) {
+ ipcp->ns.dns[0].s_addr = ipcp->ns.dns[1].s_addr;
+ ipcp->ns.dns[1].s_addr = INADDR_ANY;
+ }
+ bundle_AdjustDNS(ipcp->fsm.bundle);
+ }
+ } else
+ log_Printf(LogERROR, "Failed to stat opened %s: %s\n",
+ _PATH_RESCONF, strerror(errno));
+
+ close(fd);
+ }
+}
+
+int
+ipcp_WriteDNS(struct ipcp *ipcp)
+{
+ const char *paddr;
+ mode_t mask;
+ FILE *fp;
+
+ if (ipcp->ns.dns[0].s_addr == INADDR_ANY &&
+ ipcp->ns.dns[1].s_addr == INADDR_ANY) {
+ log_Printf(LogIPCP, "%s not modified: All nameservers NAKd\n",
+ _PATH_RESCONF);
+ return 0;
+ }
+
+ if (ipcp->ns.dns[0].s_addr == INADDR_ANY) {
+ ipcp->ns.dns[0].s_addr = ipcp->ns.dns[1].s_addr;
+ ipcp->ns.dns[1].s_addr = INADDR_ANY;
+ }
+
+ mask = umask(022);
+ if ((fp = ID0fopen(_PATH_RESCONF, "w")) != NULL) {
+ umask(mask);
+ if (ipcp->ns.resolv_nons)
+ fputs(ipcp->ns.resolv_nons, fp);
+ paddr = inet_ntoa(ipcp->ns.dns[0]);
+ log_Printf(LogIPCP, "Primary nameserver set to %s\n", paddr);
+ fprintf(fp, "\nnameserver %s\n", paddr);
+ if (ipcp->ns.dns[1].s_addr != INADDR_ANY &&
+ ipcp->ns.dns[1].s_addr != INADDR_NONE &&
+ ipcp->ns.dns[1].s_addr != ipcp->ns.dns[0].s_addr) {
+ paddr = inet_ntoa(ipcp->ns.dns[1]);
+ log_Printf(LogIPCP, "Secondary nameserver set to %s\n", paddr);
+ fprintf(fp, "nameserver %s\n", paddr);
+ }
+ if (fclose(fp) == EOF) {
+ log_Printf(LogERROR, "write(): Failed updating %s: %s\n", _PATH_RESCONF,
+ strerror(errno));
+ return 0;
+ }
+ } else {
+ umask(mask);
+ log_Printf(LogERROR,"fopen(\"%s\", \"w\") failed: %s\n", _PATH_RESCONF,
+ strerror(errno));
+ }
+
+ return 1;
+}
+
+void
+ipcp_RestoreDNS(struct ipcp *ipcp)
+{
+ if (ipcp->ns.resolver) {
+ ssize_t got, len;
+ int fd;
+
+ if ((fd = ID0open(_PATH_RESCONF, O_WRONLY|O_TRUNC, 0644)) != -1) {
+ len = strlen(ipcp->ns.resolv);
+ if ((got = write(fd, ipcp->ns.resolv, len)) != len) {
+ if (got == -1)
+ log_Printf(LogERROR, "Failed rewriting %s: write: %s\n",
+ _PATH_RESCONF, strerror(errno));
+ else
+ log_Printf(LogERROR, "Failed rewriting %s: wrote %ld of %ld\n",
+ _PATH_RESCONF, (long)got, (long)len);
+ }
+ close(fd);
+ } else
+ log_Printf(LogERROR, "Failed rewriting %s: open: %s\n", _PATH_RESCONF,
+ strerror(errno));
+ } else if (remove(_PATH_RESCONF) == -1)
+ log_Printf(LogERROR, "Failed removing %s: %s\n", _PATH_RESCONF,
+ strerror(errno));
+
+}
+
+int
+ipcp_Show(struct cmdargs const *arg)
+{
+ struct ipcp *ipcp = &arg->bundle->ncp.ipcp;
+
+ prompt_Printf(arg->prompt, "%s [%s]\n", ipcp->fsm.name,
+ State2Nam(ipcp->fsm.state));
+ if (ipcp->fsm.state == ST_OPENED) {
+ prompt_Printf(arg->prompt, " His side: %s, %s\n",
+ inet_ntoa(ipcp->peer_ip), vj2asc(ipcp->peer_compproto));
+ prompt_Printf(arg->prompt, " My side: %s, %s\n",
+ inet_ntoa(ipcp->my_ip), vj2asc(ipcp->my_compproto));
+ prompt_Printf(arg->prompt, " Queued packets: %lu\n",
+ (unsigned long)ipcp_QueueLen(ipcp));
+ }
+
+ prompt_Printf(arg->prompt, "\nDefaults:\n");
+ prompt_Printf(arg->prompt, " FSM retry = %us, max %u Config"
+ " REQ%s, %u Term REQ%s\n", ipcp->cfg.fsm.timeout,
+ ipcp->cfg.fsm.maxreq, ipcp->cfg.fsm.maxreq == 1 ? "" : "s",
+ ipcp->cfg.fsm.maxtrm, ipcp->cfg.fsm.maxtrm == 1 ? "" : "s");
+ prompt_Printf(arg->prompt, " My Address: %s\n",
+ ncprange_ntoa(&ipcp->cfg.my_range));
+ if (ipcp->cfg.HaveTriggerAddress)
+ prompt_Printf(arg->prompt, " Trigger address: %s\n",
+ inet_ntoa(ipcp->cfg.TriggerAddress));
+
+ prompt_Printf(arg->prompt, " VJ compression: %s (%d slots %s slot "
+ "compression)\n", command_ShowNegval(ipcp->cfg.vj.neg),
+ ipcp->cfg.vj.slots, ipcp->cfg.vj.slotcomp ? "with" : "without");
+
+ if (iplist_isvalid(&ipcp->cfg.peer_list))
+ prompt_Printf(arg->prompt, " His Address: %s\n",
+ ipcp->cfg.peer_list.src);
+ else
+ prompt_Printf(arg->prompt, " His Address: %s\n",
+ ncprange_ntoa(&ipcp->cfg.peer_range));
+
+ prompt_Printf(arg->prompt, " DNS: %s",
+ ipcp->cfg.ns.dns[0].s_addr == INADDR_NONE ?
+ "none" : inet_ntoa(ipcp->cfg.ns.dns[0]));
+ if (ipcp->cfg.ns.dns[1].s_addr != INADDR_NONE)
+ prompt_Printf(arg->prompt, ", %s",
+ inet_ntoa(ipcp->cfg.ns.dns[1]));
+ prompt_Printf(arg->prompt, ", %s\n",
+ command_ShowNegval(ipcp->cfg.ns.dns_neg));
+ prompt_Printf(arg->prompt, " Resolver DNS: %s",
+ ipcp->ns.dns[0].s_addr == INADDR_NONE ?
+ "none" : inet_ntoa(ipcp->ns.dns[0]));
+ if (ipcp->ns.dns[1].s_addr != INADDR_NONE &&
+ ipcp->ns.dns[1].s_addr != ipcp->ns.dns[0].s_addr)
+ prompt_Printf(arg->prompt, ", %s",
+ inet_ntoa(ipcp->ns.dns[1]));
+ prompt_Printf(arg->prompt, "\n NetBIOS NS: %s, ",
+ inet_ntoa(ipcp->cfg.ns.nbns[0]));
+ prompt_Printf(arg->prompt, "%s\n\n",
+ inet_ntoa(ipcp->cfg.ns.nbns[1]));
+
+ throughput_disp(&ipcp->throughput, arg->prompt);
+
+ return 0;
+}
+
+int
+ipcp_vjset(struct cmdargs const *arg)
+{
+ if (arg->argc != arg->argn+2)
+ return -1;
+ if (!strcasecmp(arg->argv[arg->argn], "slots")) {
+ int slots;
+
+ slots = atoi(arg->argv[arg->argn+1]);
+ if (slots < 4 || slots > 16)
+ return 1;
+ arg->bundle->ncp.ipcp.cfg.vj.slots = slots;
+ return 0;
+ } else if (!strcasecmp(arg->argv[arg->argn], "slotcomp")) {
+ if (!strcasecmp(arg->argv[arg->argn+1], "on"))
+ arg->bundle->ncp.ipcp.cfg.vj.slotcomp = 1;
+ else if (!strcasecmp(arg->argv[arg->argn+1], "off"))
+ arg->bundle->ncp.ipcp.cfg.vj.slotcomp = 0;
+ else
+ return 2;
+ return 0;
+ }
+ return -1;
+}
+
+void
+ipcp_Init(struct ipcp *ipcp, struct bundle *bundle, struct link *l,
+ const struct fsm_parent *parent)
+{
+ struct hostent *hp;
+ struct in_addr host;
+ char name[MAXHOSTNAMELEN];
+ static const char * const timer_names[] =
+ {"IPCP restart", "IPCP openmode", "IPCP stopped"};
+
+ fsm_Init(&ipcp->fsm, "IPCP", PROTO_IPCP, 1, IPCP_MAXCODE, LogIPCP,
+ bundle, l, parent, &ipcp_Callbacks, timer_names);
+
+ ipcp->cfg.vj.slots = DEF_VJ_STATES;
+ ipcp->cfg.vj.slotcomp = 1;
+ memset(&ipcp->cfg.my_range, '\0', sizeof ipcp->cfg.my_range);
+
+ host.s_addr = htonl(INADDR_LOOPBACK);
+ ipcp->cfg.netmask.s_addr = INADDR_ANY;
+ if (gethostname(name, sizeof name) == 0) {
+ hp = gethostbyname(name);
+ if (hp && hp->h_addrtype == AF_INET && hp->h_length == sizeof host.s_addr)
+ memcpy(&host.s_addr, hp->h_addr, sizeof host.s_addr);
+ }
+ ncprange_setip4(&ipcp->cfg.my_range, host, ipcp->cfg.netmask);
+ ncprange_setip4(&ipcp->cfg.peer_range, ipcp->cfg.netmask, ipcp->cfg.netmask);
+
+ iplist_setsrc(&ipcp->cfg.peer_list, "");
+ ipcp->cfg.HaveTriggerAddress = 0;
+
+ ipcp->cfg.ns.dns[0].s_addr = INADDR_NONE;
+ ipcp->cfg.ns.dns[1].s_addr = INADDR_NONE;
+ ipcp->cfg.ns.dns_neg = 0;
+ ipcp->cfg.ns.nbns[0].s_addr = INADDR_ANY;
+ ipcp->cfg.ns.nbns[1].s_addr = INADDR_ANY;
+
+ ipcp->cfg.fsm.timeout = DEF_FSMRETRY;
+ ipcp->cfg.fsm.maxreq = DEF_FSMTRIES;
+ ipcp->cfg.fsm.maxtrm = DEF_FSMTRIES;
+ ipcp->cfg.vj.neg = NEG_ENABLED|NEG_ACCEPTED;
+
+ memset(&ipcp->vj, '\0', sizeof ipcp->vj);
+
+ ipcp->ns.resolv = NULL;
+ ipcp->ns.resolv_nons = NULL;
+ ipcp->ns.writable = 1;
+ ipcp_LoadDNS(ipcp);
+
+ throughput_init(&ipcp->throughput, SAMPLE_PERIOD);
+ memset(ipcp->Queue, '\0', sizeof ipcp->Queue);
+ ipcp_Setup(ipcp, INADDR_NONE);
+}
+
+void
+ipcp_Destroy(struct ipcp *ipcp)
+{
+ throughput_destroy(&ipcp->throughput);
+
+ if (ipcp->ns.resolv != NULL) {
+ free(ipcp->ns.resolv);
+ ipcp->ns.resolv = NULL;
+ }
+ if (ipcp->ns.resolv_nons != NULL) {
+ free(ipcp->ns.resolv_nons);
+ ipcp->ns.resolv_nons = NULL;
+ }
+}
+
+void
+ipcp_SetLink(struct ipcp *ipcp, struct link *l)
+{
+ ipcp->fsm.link = l;
+}
+
+void
+ipcp_Setup(struct ipcp *ipcp, u_int32_t mask)
+{
+ struct iface *iface = ipcp->fsm.bundle->iface;
+ struct ncpaddr ipaddr;
+ struct in_addr peer;
+ int pos;
+ unsigned n;
+
+ ipcp->fsm.open_mode = 0;
+ ipcp->ifmask.s_addr = mask == INADDR_NONE ? ipcp->cfg.netmask.s_addr : mask;
+
+ if (iplist_isvalid(&ipcp->cfg.peer_list)) {
+ /* Try to give the peer a previously configured IP address */
+ for (n = 0; n < iface->addrs; n++) {
+ if (!ncpaddr_getip4(&iface->addr[n].peer, &peer))
+ continue;
+ if ((pos = iplist_ip2pos(&ipcp->cfg.peer_list, peer)) != -1) {
+ ncpaddr_setip4(&ipaddr, iplist_setcurpos(&ipcp->cfg.peer_list, pos));
+ break;
+ }
+ }
+ if (n == iface->addrs)
+ /* Ok, so none of 'em fit.... pick a random one */
+ ncpaddr_setip4(&ipaddr, iplist_setrandpos(&ipcp->cfg.peer_list));
+
+ ncprange_sethost(&ipcp->cfg.peer_range, &ipaddr);
+ }
+
+ ipcp->heis1172 = 0;
+ ipcp->peer_req = 0;
+ ncprange_getip4addr(&ipcp->cfg.peer_range, &ipcp->peer_ip);
+ ipcp->peer_compproto = 0;
+
+ if (ipcp->cfg.HaveTriggerAddress) {
+ /*
+ * Some implementations of PPP require that we send a
+ * *special* value as our address, even though the rfc specifies
+ * full negotiation (e.g. "0.0.0.0" or Not "0.0.0.0").
+ */
+ ipcp->my_ip = ipcp->cfg.TriggerAddress;
+ log_Printf(LogIPCP, "Using trigger address %s\n",
+ inet_ntoa(ipcp->cfg.TriggerAddress));
+ } else {
+ /*
+ * Otherwise, if we've used an IP number before and it's still within
+ * the network specified on the ``set ifaddr'' line, we really
+ * want to keep that IP number so that we can keep any existing
+ * connections that are bound to that IP.
+ */
+ for (n = 0; n < iface->addrs; n++) {
+ ncprange_getaddr(&iface->addr[n].ifa, &ipaddr);
+ if (ncprange_contains(&ipcp->cfg.my_range, &ipaddr)) {
+ ncpaddr_getip4(&ipaddr, &ipcp->my_ip);
+ break;
+ }
+ }
+ if (n == iface->addrs)
+ ncprange_getip4addr(&ipcp->cfg.my_range, &ipcp->my_ip);
+ }
+
+ if (IsEnabled(ipcp->cfg.vj.neg)
+#ifndef NORADIUS
+ || (ipcp->fsm.bundle->radius.valid && ipcp->fsm.bundle->radius.vj)
+#endif
+ )
+ ipcp->my_compproto = (PROTO_VJCOMP << 16) +
+ ((ipcp->cfg.vj.slots - 1) << 8) +
+ ipcp->cfg.vj.slotcomp;
+ else
+ ipcp->my_compproto = 0;
+ sl_compress_init(&ipcp->vj.cslc, ipcp->cfg.vj.slots - 1);
+
+ ipcp->peer_reject = 0;
+ ipcp->my_reject = 0;
+
+ /* Copy startup values into ipcp->ns.dns */
+ if (ipcp->cfg.ns.dns[0].s_addr != INADDR_NONE)
+ memcpy(ipcp->ns.dns, ipcp->cfg.ns.dns, sizeof ipcp->ns.dns);
+}
+
+static int
+numaddresses(struct in_addr mask)
+{
+ u_int32_t bit, haddr;
+ int n;
+
+ haddr = ntohl(mask.s_addr);
+ bit = 1;
+ n = 1;
+
+ do {
+ if (!(haddr & bit))
+ n <<= 1;
+ } while (bit <<= 1);
+
+ return n;
+}
+
+static int
+ipcp_proxyarp(struct ipcp *ipcp,
+ int (*proxyfun)(struct bundle *, struct in_addr),
+ const struct iface_addr *addr)
+{
+ struct bundle *bundle = ipcp->fsm.bundle;
+ struct in_addr peer, mask, ip;
+ int n, ret;
+
+ if (!ncpaddr_getip4(&addr->peer, &peer)) {
+ log_Printf(LogERROR, "Oops, ipcp_proxyarp() called with unexpected addr\n");
+ return 0;
+ }
+
+ ret = 0;
+
+ if (Enabled(bundle, OPT_PROXYALL)) {
+ ncprange_getip4mask(&addr->ifa, &mask);
+ if ((n = numaddresses(mask)) > 256) {
+ log_Printf(LogWARN, "%s: Too many addresses for proxyall\n",
+ ncprange_ntoa(&addr->ifa));
+ return 0;
+ }
+ ip.s_addr = peer.s_addr & mask.s_addr;
+ if (n >= 4) {
+ ip.s_addr = htonl(ntohl(ip.s_addr) + 1);
+ n -= 2;
+ }
+ while (n) {
+ if (!((ip.s_addr ^ peer.s_addr) & mask.s_addr)) {
+ if (!(ret = (*proxyfun)(bundle, ip)))
+ break;
+ n--;
+ }
+ ip.s_addr = htonl(ntohl(ip.s_addr) + 1);
+ }
+ ret = !n;
+ } else if (Enabled(bundle, OPT_PROXY))
+ ret = (*proxyfun)(bundle, peer);
+
+ return ret;
+}
+
+static int
+ipcp_SetIPaddress(struct ipcp *ipcp, struct in_addr myaddr,
+ struct in_addr hisaddr)
+{
+ struct bundle *bundle = ipcp->fsm.bundle;
+ struct ncpaddr myncpaddr, hisncpaddr;
+ struct ncprange myrange;
+ struct in_addr mask;
+ struct sockaddr_storage ssdst, ssgw, ssmask;
+ struct sockaddr *sadst, *sagw, *samask;
+
+ sadst = (struct sockaddr *)&ssdst;
+ sagw = (struct sockaddr *)&ssgw;
+ samask = (struct sockaddr *)&ssmask;
+
+ ncpaddr_setip4(&hisncpaddr, hisaddr);
+ ncpaddr_setip4(&myncpaddr, myaddr);
+ ncprange_sethost(&myrange, &myncpaddr);
+
+ mask = addr2mask(myaddr);
+
+ if (ipcp->ifmask.s_addr != INADDR_ANY &&
+ (ipcp->ifmask.s_addr & mask.s_addr) == mask.s_addr)
+ ncprange_setip4mask(&myrange, ipcp->ifmask);
+
+ if (!iface_Add(bundle->iface, &bundle->ncp, &myrange, &hisncpaddr,
+ IFACE_ADD_FIRST|IFACE_FORCE_ADD|IFACE_SYSTEM))
+ return 0;
+
+ if (!Enabled(bundle, OPT_IFACEALIAS))
+ iface_Clear(bundle->iface, &bundle->ncp, AF_INET,
+ IFACE_CLEAR_ALIASES|IFACE_SYSTEM);
+
+ if (bundle->ncp.cfg.sendpipe > 0 || bundle->ncp.cfg.recvpipe > 0) {
+ ncprange_getsa(&myrange, &ssgw, &ssmask);
+ ncpaddr_getsa(&hisncpaddr, &ssdst);
+ rt_Update(bundle, sadst, sagw, samask, NULL, NULL);
+ }
+
+ if (Enabled(bundle, OPT_SROUTES))
+ route_Change(bundle, bundle->ncp.route, &myncpaddr, &hisncpaddr);
+
+#ifndef NORADIUS
+ if (bundle->radius.valid)
+ route_Change(bundle, bundle->radius.routes, &myncpaddr, &hisncpaddr);
+#endif
+
+ return 1; /* Ok */
+}
+
+static struct in_addr
+ChooseHisAddr(struct bundle *bundle, struct in_addr gw)
+{
+ struct in_addr try;
+ u_long f;
+
+ for (f = 0; f < bundle->ncp.ipcp.cfg.peer_list.nItems; f++) {
+ try = iplist_next(&bundle->ncp.ipcp.cfg.peer_list);
+ log_Printf(LogDEBUG, "ChooseHisAddr: Check item %ld (%s)\n",
+ f, inet_ntoa(try));
+ if (ipcp_SetIPaddress(&bundle->ncp.ipcp, gw, try)) {
+ log_Printf(LogIPCP, "Selected IP address %s\n", inet_ntoa(try));
+ break;
+ }
+ }
+
+ if (f == bundle->ncp.ipcp.cfg.peer_list.nItems) {
+ log_Printf(LogDEBUG, "ChooseHisAddr: All addresses in use !\n");
+ try.s_addr = INADDR_ANY;
+ }
+
+ return try;
+}
+
+static void
+IpcpInitRestartCounter(struct fsm *fp, int what)
+{
+ /* Set fsm timer load */
+ struct ipcp *ipcp = fsm2ipcp(fp);
+
+ fp->FsmTimer.load = ipcp->cfg.fsm.timeout * SECTICKS;
+ switch (what) {
+ case FSM_REQ_TIMER:
+ fp->restart = ipcp->cfg.fsm.maxreq;
+ break;
+ case FSM_TRM_TIMER:
+ fp->restart = ipcp->cfg.fsm.maxtrm;
+ break;
+ default:
+ fp->restart = 1;
+ break;
+ }
+}
+
+static void
+IpcpSendConfigReq(struct fsm *fp)
+{
+ /* Send config REQ please */
+ struct physical *p = link2physical(fp->link);
+ struct ipcp *ipcp = fsm2ipcp(fp);
+ u_char buff[MAX_FSM_OPT_LEN];
+ struct fsm_opt *o;
+
+ o = (struct fsm_opt *)buff;
+
+ if ((p && !physical_IsSync(p)) || !REJECTED(ipcp, TY_IPADDR)) {
+ memcpy(o->data, &ipcp->my_ip.s_addr, 4);
+ INC_FSM_OPT(TY_IPADDR, 6, o);
+ }
+
+ if (ipcp->my_compproto && !REJECTED(ipcp, TY_COMPPROTO)) {
+ if (ipcp->heis1172) {
+ u_int16_t proto = PROTO_VJCOMP;
+
+ ua_htons(&proto, o->data);
+ INC_FSM_OPT(TY_COMPPROTO, 4, o);
+ } else {
+ struct compreq req;
+
+ req.proto = htons(ipcp->my_compproto >> 16);
+ req.slots = (ipcp->my_compproto >> 8) & 255;
+ req.compcid = ipcp->my_compproto & 1;
+ memcpy(o->data, &req, 4);
+ INC_FSM_OPT(TY_COMPPROTO, 6, o);
+ }
+ }
+
+ if (IsEnabled(ipcp->cfg.ns.dns_neg)) {
+ if (!REJECTED(ipcp, TY_PRIMARY_DNS - TY_ADJUST_NS)) {
+ memcpy(o->data, &ipcp->ns.dns[0].s_addr, 4);
+ INC_FSM_OPT(TY_PRIMARY_DNS, 6, o);
+ }
+
+ if (!REJECTED(ipcp, TY_SECONDARY_DNS - TY_ADJUST_NS)) {
+ memcpy(o->data, &ipcp->ns.dns[1].s_addr, 4);
+ INC_FSM_OPT(TY_SECONDARY_DNS, 6, o);
+ }
+ }
+
+ fsm_Output(fp, CODE_CONFIGREQ, fp->reqid, buff, (u_char *)o - buff,
+ MB_IPCPOUT);
+}
+
+static void
+IpcpSentTerminateReq(struct fsm *fp __unused)
+{
+ /* Term REQ just sent by FSM */
+}
+
+static void
+IpcpSendTerminateAck(struct fsm *fp, u_char id)
+{
+ /* Send Term ACK please */
+ fsm_Output(fp, CODE_TERMACK, id, NULL, 0, MB_IPCPOUT);
+}
+
+static void
+IpcpLayerStart(struct fsm *fp)
+{
+ /* We're about to start up ! */
+ struct ipcp *ipcp = fsm2ipcp(fp);
+
+ log_Printf(LogIPCP, "%s: LayerStart.\n", fp->link->name);
+ throughput_start(&ipcp->throughput, "IPCP throughput",
+ Enabled(fp->bundle, OPT_THROUGHPUT));
+ fp->more.reqs = fp->more.naks = fp->more.rejs = ipcp->cfg.fsm.maxreq * 3;
+ ipcp->peer_req = 0;
+}
+
+static void
+IpcpLayerFinish(struct fsm *fp)
+{
+ /* We're now down */
+ struct ipcp *ipcp = fsm2ipcp(fp);
+
+ log_Printf(LogIPCP, "%s: LayerFinish.\n", fp->link->name);
+ throughput_stop(&ipcp->throughput);
+ throughput_log(&ipcp->throughput, LogIPCP, NULL);
+}
+
+/*
+ * Called from iface_Add() via ncp_IfaceAddrAdded()
+ */
+void
+ipcp_IfaceAddrAdded(struct ipcp *ipcp, const struct iface_addr *addr)
+{
+ struct bundle *bundle = ipcp->fsm.bundle;
+
+ if (Enabled(bundle, OPT_PROXY) || Enabled(bundle, OPT_PROXYALL))
+ ipcp_proxyarp(ipcp, arp_SetProxy, addr);
+}
+
+/*
+ * Called from iface_Clear() and iface_Delete() via ncp_IfaceAddrDeleted()
+ */
+void
+ipcp_IfaceAddrDeleted(struct ipcp *ipcp, const struct iface_addr *addr)
+{
+ struct bundle *bundle = ipcp->fsm.bundle;
+
+ if (Enabled(bundle, OPT_PROXY) || Enabled(bundle, OPT_PROXYALL))
+ ipcp_proxyarp(ipcp, arp_ClearProxy, addr);
+}
+
+static void
+IpcpLayerDown(struct fsm *fp)
+{
+ /* About to come down */
+ struct ipcp *ipcp = fsm2ipcp(fp);
+ static int recursing;
+ char addr[16];
+
+ if (!recursing++) {
+ snprintf(addr, sizeof addr, "%s", inet_ntoa(ipcp->my_ip));
+ log_Printf(LogIPCP, "%s: LayerDown: %s\n", fp->link->name, addr);
+
+#ifndef NORADIUS
+ radius_Flush(&fp->bundle->radius);
+ radius_Account(&fp->bundle->radius, &fp->bundle->radacct,
+ fp->bundle->links, RAD_STOP, &ipcp->throughput);
+
+ if (*fp->bundle->radius.cfg.file && fp->bundle->radius.filterid)
+ system_Select(fp->bundle, fp->bundle->radius.filterid, LINKDOWNFILE,
+ NULL, NULL);
+ radius_StopTimer(&fp->bundle->radius);
+#endif
+
+ /*
+ * XXX this stuff should really live in the FSM. Our config should
+ * associate executable sections in files with events.
+ */
+ if (system_Select(fp->bundle, addr, LINKDOWNFILE, NULL, NULL) < 0) {
+ if (bundle_GetLabel(fp->bundle)) {
+ if (system_Select(fp->bundle, bundle_GetLabel(fp->bundle),
+ LINKDOWNFILE, NULL, NULL) < 0)
+ system_Select(fp->bundle, "MYADDR", LINKDOWNFILE, NULL, NULL);
+ } else
+ system_Select(fp->bundle, "MYADDR", LINKDOWNFILE, NULL, NULL);
+ }
+
+ ipcp_Setup(ipcp, INADDR_NONE);
+ }
+ recursing--;
+}
+
+int
+ipcp_InterfaceUp(struct ipcp *ipcp)
+{
+ if (!ipcp_SetIPaddress(ipcp, ipcp->my_ip, ipcp->peer_ip)) {
+ log_Printf(LogERROR, "ipcp_InterfaceUp: unable to set ip address\n");
+ return 0;
+ }
+
+ if (!iface_SetFlags(ipcp->fsm.bundle->iface->name, IFF_UP)) {
+ log_Printf(LogERROR, "ipcp_InterfaceUp: Can't set the IFF_UP flag on %s\n",
+ ipcp->fsm.bundle->iface->name);
+ return 0;
+ }
+
+#ifndef NONAT
+ if (ipcp->fsm.bundle->NatEnabled)
+ LibAliasSetAddress(la, ipcp->my_ip);
+#endif
+
+ return 1;
+}
+
+static int
+IpcpLayerUp(struct fsm *fp)
+{
+ /* We're now up */
+ struct ipcp *ipcp = fsm2ipcp(fp);
+ char tbuff[16];
+
+ log_Printf(LogIPCP, "%s: LayerUp.\n", fp->link->name);
+ snprintf(tbuff, sizeof tbuff, "%s", inet_ntoa(ipcp->my_ip));
+ log_Printf(LogIPCP, "myaddr %s hisaddr = %s\n",
+ tbuff, inet_ntoa(ipcp->peer_ip));
+
+ if (ipcp->peer_compproto >> 16 == PROTO_VJCOMP)
+ sl_compress_init(&ipcp->vj.cslc, (ipcp->peer_compproto >> 8) & 255);
+
+ if (!ipcp_InterfaceUp(ipcp))
+ return 0;
+
+#ifndef NORADIUS
+ radius_Account_Set_Ip(&fp->bundle->radacct, &ipcp->peer_ip, &ipcp->ifmask);
+ radius_Account(&fp->bundle->radius, &fp->bundle->radacct, fp->bundle->links,
+ RAD_START, &ipcp->throughput);
+
+ if (*fp->bundle->radius.cfg.file && fp->bundle->radius.filterid)
+ system_Select(fp->bundle, fp->bundle->radius.filterid, LINKUPFILE,
+ NULL, NULL);
+ radius_StartTimer(fp->bundle);
+#endif
+
+ /*
+ * XXX this stuff should really live in the FSM. Our config should
+ * associate executable sections in files with events.
+ */
+ if (system_Select(fp->bundle, tbuff, LINKUPFILE, NULL, NULL) < 0) {
+ if (bundle_GetLabel(fp->bundle)) {
+ if (system_Select(fp->bundle, bundle_GetLabel(fp->bundle),
+ LINKUPFILE, NULL, NULL) < 0)
+ system_Select(fp->bundle, "MYADDR", LINKUPFILE, NULL, NULL);
+ } else
+ system_Select(fp->bundle, "MYADDR", LINKUPFILE, NULL, NULL);
+ }
+
+ fp->more.reqs = fp->more.naks = fp->more.rejs = ipcp->cfg.fsm.maxreq * 3;
+ log_DisplayPrompts();
+
+ return 1;
+}
+
+static void
+ipcp_ValidateReq(struct ipcp *ipcp, struct in_addr ip, struct fsm_decode *dec)
+{
+ struct bundle *bundle = ipcp->fsm.bundle;
+ struct iface *iface = bundle->iface;
+ struct in_addr myaddr, peer;
+ unsigned n;
+
+ if (iplist_isvalid(&ipcp->cfg.peer_list)) {
+ ncprange_getip4addr(&ipcp->cfg.my_range, &myaddr);
+ if (ip.s_addr == INADDR_ANY ||
+ iplist_ip2pos(&ipcp->cfg.peer_list, ip) < 0 ||
+ !ipcp_SetIPaddress(ipcp, myaddr, ip)) {
+ log_Printf(LogIPCP, "%s: Address invalid or already in use\n",
+ inet_ntoa(ip));
+ /*
+ * If we've already had a valid address configured for the peer,
+ * try NAKing with that so that we don't have to upset things
+ * too much.
+ */
+ for (n = 0; n < iface->addrs; n++) {
+ if (!ncpaddr_getip4(&iface->addr[n].peer, &peer))
+ continue;
+ if (iplist_ip2pos(&ipcp->cfg.peer_list, peer) >= 0) {
+ ipcp->peer_ip = peer;
+ break;
+ }
+ }
+
+ if (n == iface->addrs) {
+ /* Just pick an IP number from our list */
+ ipcp->peer_ip = ChooseHisAddr(bundle, myaddr);
+ }
+
+ if (ipcp->peer_ip.s_addr == INADDR_ANY) {
+ *dec->rejend++ = TY_IPADDR;
+ *dec->rejend++ = 6;
+ memcpy(dec->rejend, &ip.s_addr, 4);
+ dec->rejend += 4;
+ } else {
+ *dec->nakend++ = TY_IPADDR;
+ *dec->nakend++ = 6;
+ memcpy(dec->nakend, &ipcp->peer_ip.s_addr, 4);
+ dec->nakend += 4;
+ }
+ return;
+ }
+ } else if (ip.s_addr == INADDR_ANY ||
+ !ncprange_containsip4(&ipcp->cfg.peer_range, ip)) {
+ /*
+ * If the destination address is not acceptable, NAK with what we
+ * want to use.
+ */
+ *dec->nakend++ = TY_IPADDR;
+ *dec->nakend++ = 6;
+ for (n = 0; n < iface->addrs; n++)
+ if (ncprange_contains(&ipcp->cfg.peer_range, &iface->addr[n].peer)) {
+ /* We prefer the already-configured address */
+ ncpaddr_getip4addr(&iface->addr[n].peer, (u_int32_t *)dec->nakend);
+ break;
+ }
+
+ if (n == iface->addrs)
+ memcpy(dec->nakend, &ipcp->peer_ip.s_addr, 4);
+
+ dec->nakend += 4;
+ return;
+ }
+
+ ipcp->peer_ip = ip;
+ *dec->ackend++ = TY_IPADDR;
+ *dec->ackend++ = 6;
+ memcpy(dec->ackend, &ip.s_addr, 4);
+ dec->ackend += 4;
+}
+
+static void
+IpcpDecodeConfig(struct fsm *fp, u_char *cp, u_char *end, int mode_type,
+ struct fsm_decode *dec)
+{
+ /* Deal with incoming PROTO_IPCP */
+ struct ncpaddr ncpaddr;
+ struct ipcp *ipcp = fsm2ipcp(fp);
+ int gotdnsnak;
+ u_int32_t compproto;
+ struct compreq pcomp;
+ struct in_addr ipaddr, dstipaddr, have_ip;
+ char tbuff[100], tbuff2[100];
+ struct fsm_opt *opt, nak;
+
+ gotdnsnak = 0;
+
+ while (end - cp >= (int)sizeof(opt->hdr)) {
+ if ((opt = fsm_readopt(&cp)) == NULL)
+ break;
+
+ snprintf(tbuff, sizeof tbuff, " %s[%d]", protoname(opt->hdr.id),
+ opt->hdr.len);
+
+ switch (opt->hdr.id) {
+ case TY_IPADDR: /* RFC1332 */
+ memcpy(&ipaddr.s_addr, opt->data, 4);
+ log_Printf(LogIPCP, "%s %s\n", tbuff, inet_ntoa(ipaddr));
+
+ switch (mode_type) {
+ case MODE_REQ:
+ ipcp->peer_req = 1;
+ ipcp_ValidateReq(ipcp, ipaddr, dec);
+ break;
+
+ case MODE_NAK:
+ if (ncprange_containsip4(&ipcp->cfg.my_range, ipaddr)) {
+ /* Use address suggested by peer */
+ snprintf(tbuff2, sizeof tbuff2, "%s changing address: %s ", tbuff,
+ inet_ntoa(ipcp->my_ip));
+ log_Printf(LogIPCP, "%s --> %s\n", tbuff2, inet_ntoa(ipaddr));
+ ipcp->my_ip = ipaddr;
+ ncpaddr_setip4(&ncpaddr, ipcp->my_ip);
+ bundle_AdjustFilters(fp->bundle, &ncpaddr, NULL);
+ } else {
+ log_Printf(log_IsKept(LogIPCP) ? LogIPCP : LogPHASE,
+ "%s: Unacceptable address!\n", inet_ntoa(ipaddr));
+ fsm_Close(&ipcp->fsm);
+ }
+ break;
+
+ case MODE_REJ:
+ ipcp->peer_reject |= (1 << opt->hdr.id);
+ break;
+ }
+ break;
+
+ case TY_COMPPROTO:
+ memcpy(&pcomp, opt->data, sizeof pcomp);
+ compproto = (ntohs(pcomp.proto) << 16) + ((int)pcomp.slots << 8) +
+ pcomp.compcid;
+ log_Printf(LogIPCP, "%s %s\n", tbuff, vj2asc(compproto));
+
+ switch (mode_type) {
+ case MODE_REQ:
+ if (!IsAccepted(ipcp->cfg.vj.neg))
+ fsm_rej(dec, opt);
+ else {
+ switch (opt->hdr.len) {
+ case 4: /* RFC1172 */
+ if (ntohs(pcomp.proto) == PROTO_VJCOMP) {
+ log_Printf(LogWARN, "Peer is speaking RFC1172 compression "
+ "protocol !\n");
+ ipcp->heis1172 = 1;
+ ipcp->peer_compproto = compproto;
+ fsm_ack(dec, opt);
+ } else {
+ pcomp.proto = htons(PROTO_VJCOMP);
+ nak.hdr.id = TY_COMPPROTO;
+ nak.hdr.len = 4;
+ memcpy(nak.data, &pcomp, 2);
+ fsm_nak(dec, &nak);
+ }
+ break;
+ case 6: /* RFC1332 */
+ if (ntohs(pcomp.proto) == PROTO_VJCOMP) {
+ /* We know pcomp.slots' max value == MAX_VJ_STATES */
+ if (pcomp.slots >= MIN_VJ_STATES) {
+ /* Ok, we can do that */
+ ipcp->peer_compproto = compproto;
+ ipcp->heis1172 = 0;
+ fsm_ack(dec, opt);
+ } else {
+ /* Get as close as we can to what he wants */
+ ipcp->heis1172 = 0;
+ pcomp.slots = MIN_VJ_STATES;
+ nak.hdr.id = TY_COMPPROTO;
+ nak.hdr.len = 4;
+ memcpy(nak.data, &pcomp, 2);
+ fsm_nak(dec, &nak);
+ }
+ } else {
+ /* What we really want */
+ pcomp.proto = htons(PROTO_VJCOMP);
+ pcomp.slots = DEF_VJ_STATES;
+ pcomp.compcid = 1;
+ nak.hdr.id = TY_COMPPROTO;
+ nak.hdr.len = 6;
+ memcpy(nak.data, &pcomp, sizeof pcomp);
+ fsm_nak(dec, &nak);
+ }
+ break;
+ default:
+ fsm_rej(dec, opt);
+ break;
+ }
+ }
+ break;
+
+ case MODE_NAK:
+ if (ntohs(pcomp.proto) == PROTO_VJCOMP) {
+ /* We know pcomp.slots' max value == MAX_VJ_STATES */
+ if (pcomp.slots < MIN_VJ_STATES)
+ pcomp.slots = MIN_VJ_STATES;
+ compproto = (ntohs(pcomp.proto) << 16) + (pcomp.slots << 8) +
+ pcomp.compcid;
+ } else
+ compproto = 0;
+ log_Printf(LogIPCP, "%s changing compproto: %08x --> %08x\n",
+ tbuff, ipcp->my_compproto, compproto);
+ ipcp->my_compproto = compproto;
+ break;
+
+ case MODE_REJ:
+ ipcp->peer_reject |= (1 << opt->hdr.id);
+ break;
+ }
+ break;
+
+ case TY_IPADDRS: /* RFC1172 */
+ memcpy(&ipaddr.s_addr, opt->data, 4);
+ memcpy(&dstipaddr.s_addr, opt->data + 4, 4);
+ snprintf(tbuff2, sizeof tbuff2, "%s %s,", tbuff, inet_ntoa(ipaddr));
+ log_Printf(LogIPCP, "%s %s\n", tbuff2, inet_ntoa(dstipaddr));
+
+ switch (mode_type) {
+ case MODE_REQ:
+ fsm_rej(dec, opt);
+ break;
+
+ case MODE_NAK:
+ case MODE_REJ:
+ break;
+ }
+ break;
+
+ case TY_PRIMARY_DNS: /* DNS negotiation (rfc1877) */
+ case TY_SECONDARY_DNS:
+ memcpy(&ipaddr.s_addr, opt->data, 4);
+ log_Printf(LogIPCP, "%s %s\n", tbuff, inet_ntoa(ipaddr));
+
+ switch (mode_type) {
+ case MODE_REQ:
+ if (!IsAccepted(ipcp->cfg.ns.dns_neg)) {
+ ipcp->my_reject |= (1 << (opt->hdr.id - TY_ADJUST_NS));
+ fsm_rej(dec, opt);
+ break;
+ }
+ have_ip = ipcp->ns.dns[opt->hdr.id == TY_PRIMARY_DNS ? 0 : 1];
+
+ if (opt->hdr.id == TY_PRIMARY_DNS && ipaddr.s_addr != have_ip.s_addr &&
+ ipaddr.s_addr == ipcp->ns.dns[1].s_addr) {
+ /* Swap 'em 'round */
+ ipcp->ns.dns[0] = ipcp->ns.dns[1];
+ ipcp->ns.dns[1] = have_ip;
+ have_ip = ipcp->ns.dns[0];
+ }
+
+ if (ipaddr.s_addr != have_ip.s_addr) {
+ /*
+ * The client has got the DNS stuff wrong (first request) so
+ * we'll tell 'em how it is
+ */
+ nak.hdr.id = opt->hdr.id;
+ nak.hdr.len = 6;
+ memcpy(nak.data, &have_ip.s_addr, 4);
+ fsm_nak(dec, &nak);
+ } else {
+ /*
+ * Otherwise they have it right (this time) so we send an ack packet
+ * back confirming it... end of story
+ */
+ fsm_ack(dec, opt);
+ }
+ break;
+
+ case MODE_NAK:
+ if (IsEnabled(ipcp->cfg.ns.dns_neg)) {
+ gotdnsnak = 1;
+ memcpy(&ipcp->ns.dns[opt->hdr.id == TY_PRIMARY_DNS ? 0 : 1].s_addr,
+ opt->data, 4);
+ }
+ break;
+
+ case MODE_REJ: /* Can't do much, stop asking */
+ ipcp->peer_reject |= (1 << (opt->hdr.id - TY_ADJUST_NS));
+ break;
+ }
+ break;
+
+ case TY_PRIMARY_NBNS: /* M$ NetBIOS nameserver hack (rfc1877) */
+ case TY_SECONDARY_NBNS:
+ memcpy(&ipaddr.s_addr, opt->data, 4);
+ log_Printf(LogIPCP, "%s %s\n", tbuff, inet_ntoa(ipaddr));
+
+ switch (mode_type) {
+ case MODE_REQ:
+ have_ip.s_addr =
+ ipcp->cfg.ns.nbns[opt->hdr.id == TY_PRIMARY_NBNS ? 0 : 1].s_addr;
+
+ if (have_ip.s_addr == INADDR_ANY) {
+ log_Printf(LogIPCP, "NBNS REQ - rejected - nbns not set\n");
+ ipcp->my_reject |= (1 << (opt->hdr.id - TY_ADJUST_NS));
+ fsm_rej(dec, opt);
+ break;
+ }
+
+ if (ipaddr.s_addr != have_ip.s_addr) {
+ nak.hdr.id = opt->hdr.id;
+ nak.hdr.len = 6;
+ memcpy(nak.data, &have_ip.s_addr, 4);
+ fsm_nak(dec, &nak);
+ } else
+ fsm_ack(dec, opt);
+ break;
+
+ case MODE_NAK:
+ log_Printf(LogIPCP, "MS NBNS req %d - NAK??\n", opt->hdr.id);
+ break;
+
+ case MODE_REJ:
+ log_Printf(LogIPCP, "MS NBNS req %d - REJ??\n", opt->hdr.id);
+ break;
+ }
+ break;
+
+ default:
+ if (mode_type != MODE_NOP) {
+ ipcp->my_reject |= (1 << opt->hdr.id);
+ fsm_rej(dec, opt);
+ }
+ break;
+ }
+ }
+
+ if (gotdnsnak) {
+ if (ipcp->ns.writable) {
+ log_Printf(LogDEBUG, "Updating resolver\n");
+ if (!ipcp_WriteDNS(ipcp)) {
+ ipcp->peer_reject |= (1 << (TY_PRIMARY_DNS - TY_ADJUST_NS));
+ ipcp->peer_reject |= (1 << (TY_SECONDARY_DNS - TY_ADJUST_NS));
+ } else
+ bundle_AdjustDNS(fp->bundle);
+ } else {
+ log_Printf(LogDEBUG, "Not updating resolver (readonly)\n");
+ bundle_AdjustDNS(fp->bundle);
+ }
+ }
+
+ if (mode_type != MODE_NOP) {
+ if (mode_type == MODE_REQ && !ipcp->peer_req) {
+ if (dec->rejend == dec->rej && dec->nakend == dec->nak) {
+ /*
+ * Pretend the peer has requested an IP.
+ * We do this to ensure that we only send one NAK if the only
+ * reason for the NAK is because the peer isn't sending a
+ * TY_IPADDR REQ. This stops us from repeatedly trying to tell
+ * the peer that we have to have an IP address on their end.
+ */
+ ipcp->peer_req = 1;
+ }
+ ipaddr.s_addr = INADDR_ANY;
+ ipcp_ValidateReq(ipcp, ipaddr, dec);
+ }
+ fsm_opt_normalise(dec);
+ }
+}
+
+extern struct mbuf *
+ipcp_Input(struct bundle *bundle, struct link *l, struct mbuf *bp)
+{
+ /* Got PROTO_IPCP from link */
+ m_settype(bp, MB_IPCPIN);
+ if (bundle_Phase(bundle) == PHASE_NETWORK)
+ fsm_Input(&bundle->ncp.ipcp.fsm, bp);
+ else {
+ if (bundle_Phase(bundle) < PHASE_NETWORK)
+ log_Printf(LogIPCP, "%s: Error: Unexpected IPCP in phase %s (ignored)\n",
+ l->name, bundle_PhaseName(bundle));
+ m_freem(bp);
+ }
+ return NULL;
+}
+
+int
+ipcp_UseHisIPaddr(struct bundle *bundle, struct in_addr hisaddr)
+{
+ struct ipcp *ipcp = &bundle->ncp.ipcp;
+ struct in_addr myaddr;
+
+ memset(&ipcp->cfg.peer_range, '\0', sizeof ipcp->cfg.peer_range);
+ iplist_reset(&ipcp->cfg.peer_list);
+ ipcp->peer_ip = hisaddr;
+ ncprange_setip4host(&ipcp->cfg.peer_range, hisaddr);
+ ncprange_getip4addr(&ipcp->cfg.my_range, &myaddr);
+
+ return ipcp_SetIPaddress(ipcp, myaddr, hisaddr);
+}
+
+int
+ipcp_UseHisaddr(struct bundle *bundle, const char *hisaddr, int setaddr)
+{
+ struct in_addr myaddr;
+ struct ncp *ncp = &bundle->ncp;
+ struct ipcp *ipcp = &ncp->ipcp;
+ struct ncpaddr ncpaddr;
+
+ /* Use `hisaddr' for the peers address (set iface if `setaddr') */
+ memset(&ipcp->cfg.peer_range, '\0', sizeof ipcp->cfg.peer_range);
+ iplist_reset(&ipcp->cfg.peer_list);
+ if (strpbrk(hisaddr, ",-")) {
+ iplist_setsrc(&ipcp->cfg.peer_list, hisaddr);
+ if (iplist_isvalid(&ipcp->cfg.peer_list)) {
+ iplist_setrandpos(&ipcp->cfg.peer_list);
+ ipcp->peer_ip = ChooseHisAddr(bundle, ipcp->my_ip);
+ if (ipcp->peer_ip.s_addr == INADDR_ANY) {
+ log_Printf(LogWARN, "%s: None available !\n", ipcp->cfg.peer_list.src);
+ return 0;
+ }
+ ncprange_setip4host(&ipcp->cfg.peer_range, ipcp->peer_ip);
+ } else {
+ log_Printf(LogWARN, "%s: Invalid range !\n", hisaddr);
+ return 0;
+ }
+ } else if (ncprange_aton(&ipcp->cfg.peer_range, ncp, hisaddr) != 0) {
+ if (ncprange_family(&ipcp->cfg.my_range) != AF_INET) {
+ log_Printf(LogWARN, "%s: Not an AF_INET address !\n", hisaddr);
+ return 0;
+ }
+ ncprange_getip4addr(&ipcp->cfg.my_range, &myaddr);
+ ncprange_getip4addr(&ipcp->cfg.peer_range, &ipcp->peer_ip);
+
+ if (setaddr && !ipcp_SetIPaddress(ipcp, myaddr, ipcp->peer_ip))
+ return 0;
+ } else
+ return 0;
+
+ ncpaddr_setip4(&ncpaddr, ipcp->peer_ip);
+ bundle_AdjustFilters(bundle, NULL, &ncpaddr);
+
+ return 1; /* Ok */
+}
+
+struct in_addr
+addr2mask(struct in_addr addr)
+{
+ u_int32_t haddr = ntohl(addr.s_addr);
+
+ haddr = IN_CLASSA(haddr) ? IN_CLASSA_NET :
+ IN_CLASSB(haddr) ? IN_CLASSB_NET :
+ IN_CLASSC_NET;
+ addr.s_addr = htonl(haddr);
+
+ return addr;
+}
+
+size_t
+ipcp_QueueLen(struct ipcp *ipcp)
+{
+ struct mqueue *q;
+ size_t result;
+
+ result = 0;
+ for (q = ipcp->Queue; q < ipcp->Queue + IPCP_QUEUES(ipcp); q++)
+ result += q->len;
+
+ return result;
+}
+
+int
+ipcp_PushPacket(struct ipcp *ipcp, struct link *l)
+{
+ struct bundle *bundle = ipcp->fsm.bundle;
+ struct mqueue *queue;
+ struct mbuf *bp;
+ int m_len;
+ u_int32_t secs = 0;
+ unsigned alivesecs = 0;
+
+ if (ipcp->fsm.state != ST_OPENED)
+ return 0;
+
+ /*
+ * If ccp is not open but is required, do nothing.
+ */
+ if (l->ccp.fsm.state != ST_OPENED && ccp_Required(&l->ccp)) {
+ log_Printf(LogPHASE, "%s: Not transmitting... waiting for CCP\n", l->name);
+ return 0;
+ }
+
+ queue = ipcp->Queue + IPCP_QUEUES(ipcp) - 1;
+ do {
+ if (queue->top) {
+ bp = m_dequeue(queue);
+ bp = mbuf_Read(bp, &secs, sizeof secs);
+ bp = m_pullup(bp);
+ m_len = m_length(bp);
+ if (!FilterCheck(MBUF_CTOP(bp), AF_INET, &bundle->filter.alive,
+ &alivesecs)) {
+ if (secs == 0)
+ secs = alivesecs;
+ bundle_StartIdleTimer(bundle, secs);
+ }
+ link_PushPacket(l, bp, bundle, 0, PROTO_IP);
+ ipcp_AddOutOctets(ipcp, m_len);
+ return 1;
+ }
+ } while (queue-- != ipcp->Queue);
+
+ return 0;
+}
diff --git a/usr.sbin/ppp/ipcp.h b/usr.sbin/ppp/ipcp.h
new file mode 100644
index 0000000..dc1ed9e
--- /dev/null
+++ b/usr.sbin/ppp/ipcp.h
@@ -0,0 +1,132 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+#define IPCP_MAXCODE CODE_CODEREJ
+
+#define TY_IPADDRS 1
+#define TY_COMPPROTO 2
+#define TY_IPADDR 3
+
+/* Domain NameServer and NetBIOS NameServer options */
+
+#define TY_PRIMARY_DNS 129
+#define TY_PRIMARY_NBNS 130
+#define TY_SECONDARY_DNS 131
+#define TY_SECONDARY_NBNS 132
+#define TY_ADJUST_NS 119 /* subtract from NS val for REJECT bit */
+
+struct ipcp {
+ struct fsm fsm; /* The finite state machine */
+
+ struct {
+ struct {
+ int slots; /* Maximum VJ slots */
+ unsigned slotcomp : 1; /* Slot compression */
+ unsigned neg : 2; /* VJ negotiation */
+ } vj;
+
+ struct ncprange my_range; /* MYADDR spec */
+ struct in_addr netmask; /* Iface netmask (unused by most OSs) */
+ struct ncprange peer_range; /* HISADDR spec */
+ struct iplist peer_list; /* Ranges of HISADDR values */
+
+ struct in_addr TriggerAddress; /* Address to suggest in REQ */
+ unsigned HaveTriggerAddress : 1; /* Trigger address specified */
+
+ struct {
+ struct in_addr dns[2]; /* DNS addresses offered */
+ unsigned dns_neg : 2; /* dns negotiation */
+ struct in_addr nbns[2]; /* NetBIOS NS addresses offered */
+ } ns;
+
+ struct fsm_retry fsm; /* frequency to resend requests */
+ } cfg;
+
+ struct {
+ struct slcompress cslc; /* VJ state */
+ struct slstat slstat; /* VJ statistics */
+ } vj;
+
+ struct {
+ unsigned resolver : 1; /* Found resolv.conf ? */
+ unsigned writable : 1; /* Can write resolv.conf ? */
+ struct in_addr dns[2]; /* Current DNS addresses */
+ char *resolv; /* Contents of resolv.conf */
+ char *resolv_nons; /* Contents of resolv.conf without ns */
+ } ns;
+
+ unsigned heis1172 : 1; /* True if he is speaking rfc1172 */
+
+ unsigned peer_req : 1; /* Any TY_IPADDR REQs from the peer ? */
+ struct in_addr peer_ip; /* IP address he's willing to use */
+ u_int32_t peer_compproto; /* VJ params he's willing to use */
+
+ struct in_addr ifmask; /* Interface netmask */
+
+ struct in_addr my_ip; /* IP address I'm willing to use */
+ u_int32_t my_compproto; /* VJ params I'm willing to use */
+
+ u_int32_t peer_reject; /* Request codes rejected by peer */
+ u_int32_t my_reject; /* Request codes I have rejected */
+
+ struct pppThroughput throughput; /* throughput statistics */
+ struct mqueue Queue[3]; /* Output packet queues */
+};
+
+#define fsm2ipcp(fp) (fp->proto == PROTO_IPCP ? (struct ipcp *)fp : NULL)
+#define IPCP_QUEUES(ipcp) (sizeof ipcp->Queue / sizeof ipcp->Queue[0])
+
+struct bundle;
+struct link;
+struct cmdargs;
+struct iface_addr;
+
+extern void ipcp_Init(struct ipcp *, struct bundle *, struct link *,
+ const struct fsm_parent *);
+extern void ipcp_Destroy(struct ipcp *);
+extern void ipcp_Setup(struct ipcp *, u_int32_t);
+extern void ipcp_SetLink(struct ipcp *, struct link *);
+
+extern int ipcp_Show(struct cmdargs const *);
+extern struct mbuf *ipcp_Input(struct bundle *, struct link *, struct mbuf *);
+extern void ipcp_AddInOctets(struct ipcp *, int);
+extern void ipcp_AddOutOctets(struct ipcp *, int);
+extern int ipcp_UseHisIPaddr(struct bundle *, struct in_addr);
+extern int ipcp_UseHisaddr(struct bundle *, const char *, int);
+extern int ipcp_vjset(struct cmdargs const *);
+extern void ipcp_IfaceAddrAdded(struct ipcp *, const struct iface_addr *);
+extern void ipcp_IfaceAddrDeleted(struct ipcp *, const struct iface_addr *);
+extern int ipcp_InterfaceUp(struct ipcp *);
+extern struct in_addr addr2mask(struct in_addr);
+extern int ipcp_WriteDNS(struct ipcp *);
+extern void ipcp_RestoreDNS(struct ipcp *);
+extern void ipcp_LoadDNS(struct ipcp *);
+extern size_t ipcp_QueueLen(struct ipcp *);
+extern int ipcp_PushPacket(struct ipcp *, struct link *);
diff --git a/usr.sbin/ppp/iplist.c b/usr.sbin/ppp/iplist.c
new file mode 100644
index 0000000..a7fae6e
--- /dev/null
+++ b/usr.sbin/ppp/iplist.c
@@ -0,0 +1,225 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.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/types.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+
+#include "log.h"
+#include "defs.h"
+#include "iplist.h"
+
+static int
+do_inet_aton(const char *start, const char *end, struct in_addr *ip)
+{
+ char ipstr[16];
+
+ if (end - start > 15) {
+ log_Printf(LogWARN, "%.*s: Invalid IP address\n", (int)(end-start), start);
+ return 0;
+ }
+ strncpy(ipstr, start, end-start);
+ ipstr[end-start] = '\0';
+ return inet_aton(ipstr, ip);
+}
+
+static void
+iplist_first(struct iplist *list)
+{
+ list->cur.pos = -1;
+}
+
+static int
+iplist_setrange(struct iplist *list, char *range)
+{
+ char *ptr, *to;
+
+ if ((ptr = strpbrk(range, ",-")) == NULL) {
+ if (!inet_aton(range, &list->cur.ip))
+ return 0;
+ list->cur.lstart = ntohl(list->cur.ip.s_addr);
+ list->cur.nItems = 1;
+ } else {
+ if (!do_inet_aton(range, ptr, &list->cur.ip))
+ return 0;
+ if (*ptr == ',') {
+ list->cur.lstart = ntohl(list->cur.ip.s_addr);
+ list->cur.nItems = 1;
+ } else {
+ struct in_addr endip;
+
+ to = ptr+1;
+ if ((ptr = strpbrk(to, ",-")) == NULL)
+ ptr = to + strlen(to);
+ if (*to == '-')
+ return 0;
+ if (!do_inet_aton(to, ptr, &endip))
+ return 0;
+ list->cur.lstart = ntohl(list->cur.ip.s_addr);
+ list->cur.nItems = ntohl(endip.s_addr) - list->cur.lstart + 1;
+ if (list->cur.nItems < 1)
+ return 0;
+ }
+ }
+ list->cur.srcitem = 0;
+ list->cur.srcptr = range;
+ return 1;
+}
+
+static int
+iplist_nextrange(struct iplist *list)
+{
+ char *ptr, *to, *end;
+
+ ptr = list->cur.srcptr;
+ if (ptr != NULL && (ptr = strchr(ptr, ',')) != NULL)
+ ptr++;
+ else
+ ptr = list->src;
+
+ while (*ptr != '\0' && !iplist_setrange(list, ptr)) {
+ if ((end = strchr(ptr, ',')) == NULL)
+ end = ptr + strlen(ptr);
+ if (end == ptr)
+ return 0;
+ log_Printf(LogWARN, "%.*s: Invalid IP range (skipping)\n",
+ (int)(end - ptr), ptr);
+ to = ptr;
+ do
+ *to = *end++;
+ while (*to++ != '\0');
+ if (*ptr == '\0')
+ ptr = list->src;
+ }
+
+ return 1;
+}
+
+struct in_addr
+iplist_next(struct iplist *list)
+{
+ if (list->cur.pos == -1) {
+ list->cur.srcptr = NULL;
+ if (!iplist_nextrange(list)) {
+ list->cur.ip.s_addr = INADDR_ANY;
+ return list->cur.ip;
+ }
+ } else if (++list->cur.srcitem == list->cur.nItems) {
+ if (!iplist_nextrange(list)) {
+ list->cur.ip.s_addr = INADDR_ANY;
+ list->cur.pos = -1;
+ return list->cur.ip;
+ }
+ } else
+ list->cur.ip.s_addr = htonl(list->cur.lstart + list->cur.srcitem);
+ list->cur.pos++;
+
+ return list->cur.ip;
+}
+
+int
+iplist_setsrc(struct iplist *list, const char *src)
+{
+ strncpy(list->src, src, sizeof list->src - 1);
+ list->src[sizeof list->src - 1] = '\0';
+ list->cur.srcptr = list->src;
+ do {
+ if (iplist_nextrange(list))
+ list->nItems += list->cur.nItems;
+ else
+ return 0;
+ } while (list->cur.srcptr != list->src);
+ return 1;
+}
+
+void
+iplist_reset(struct iplist *list)
+{
+ list->src[0] = '\0';
+ list->nItems = 0;
+ list->cur.pos = -1;
+}
+
+struct in_addr
+iplist_setcurpos(struct iplist *list, long pos)
+{
+ if (pos < 0 || (unsigned)pos >= list->nItems) {
+ list->cur.pos = -1;
+ list->cur.ip.s_addr = INADDR_ANY;
+ return list->cur.ip;
+ }
+
+ list->cur.srcptr = NULL;
+ list->cur.pos = 0;
+ while (1) {
+ iplist_nextrange(list);
+ if (pos < (int)list->cur.nItems) {
+ if (pos) {
+ list->cur.srcitem = pos;
+ list->cur.pos += pos;
+ list->cur.ip.s_addr = htonl(list->cur.lstart + list->cur.srcitem);
+ }
+ break;
+ }
+ pos -= list->cur.nItems;
+ list->cur.pos += list->cur.nItems;
+ }
+
+ return list->cur.ip;
+}
+
+struct in_addr
+iplist_setrandpos(struct iplist *list)
+{
+ randinit();
+ return iplist_setcurpos(list, random() % list->nItems);
+}
+
+int
+iplist_ip2pos(struct iplist *list, struct in_addr ip)
+{
+ struct iplist_cur cur;
+ u_long f;
+ int result;
+
+ result = -1;
+ memcpy(&cur, &list->cur, sizeof cur);
+
+ for (iplist_first(list), f = 0; f < list->nItems; f++)
+ if (iplist_next(list).s_addr == ip.s_addr) {
+ result = list->cur.pos;
+ break;
+ }
+
+ memcpy(&list->cur, &cur, sizeof list->cur);
+ return result;
+}
diff --git a/usr.sbin/ppp/iplist.h b/usr.sbin/ppp/iplist.h
new file mode 100644
index 0000000..5805a2c
--- /dev/null
+++ b/usr.sbin/ppp/iplist.h
@@ -0,0 +1,51 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.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$
+ */
+
+struct iplist_cur {
+ struct in_addr ip;
+ int pos;
+ char *srcptr;
+ u_long srcitem;
+ u_int32_t lstart;
+ u_long nItems;
+};
+
+struct iplist {
+ struct iplist_cur cur;
+ u_long nItems;
+ char src[LINE_LEN];
+};
+
+extern int iplist_setsrc(struct iplist *, const char *);
+extern void iplist_reset(struct iplist *);
+extern struct in_addr iplist_setcurpos(struct iplist *, long);
+extern struct in_addr iplist_setrandpos(struct iplist *);
+extern int iplist_ip2pos(struct iplist *, struct in_addr);
+extern struct in_addr iplist_next(struct iplist *);
+
+#define iplist_isvalid(x) ((x)->src[0] != '\0')
diff --git a/usr.sbin/ppp/ipv6cp.c b/usr.sbin/ppp/ipv6cp.c
new file mode 100644
index 0000000..9088680
--- /dev/null
+++ b/usr.sbin/ppp/ipv6cp.c
@@ -0,0 +1,786 @@
+/*-
+ * Copyright (c) 2001 Brian Somers <brian@Awfulhak.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/param.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <net/route.h>
+#include <net/if.h>
+#include <net/if_types.h>
+#include <net/if_dl.h>
+#include <sys/un.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <ifaddrs.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "mbuf.h"
+#include "timer.h"
+#include "fsm.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ncpaddr.h"
+#include "ip.h"
+#include "ipcp.h"
+#include "ipv6cp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "ccp.h"
+#include "link.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ncp.h"
+#include "bundle.h"
+#include "route.h"
+#include "iface.h"
+#include "log.h"
+#include "proto.h"
+#include "command.h"
+#include "prompt.h"
+#include "async.h"
+#include "physical.h"
+#include "probe.h"
+#include "systems.h"
+
+
+#ifndef NOINET6
+#define IN6ADDR_LINKLOCAL_MCAST_INIT \
+ {{{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }}}
+static const struct in6_addr in6addr_linklocal_mcast =
+ IN6ADDR_LINKLOCAL_MCAST_INIT;
+
+static int ipv6cp_LayerUp(struct fsm *);
+static void ipv6cp_LayerDown(struct fsm *);
+static void ipv6cp_LayerStart(struct fsm *);
+static void ipv6cp_LayerFinish(struct fsm *);
+static void ipv6cp_InitRestartCounter(struct fsm *, int);
+static void ipv6cp_SendConfigReq(struct fsm *);
+static void ipv6cp_SentTerminateReq(struct fsm *);
+static void ipv6cp_SendTerminateAck(struct fsm *, u_char);
+static void ipv6cp_DecodeConfig(struct fsm *, u_char *, u_char *, int,
+ struct fsm_decode *);
+
+static struct fsm_callbacks ipv6cp_Callbacks = {
+ ipv6cp_LayerUp,
+ ipv6cp_LayerDown,
+ ipv6cp_LayerStart,
+ ipv6cp_LayerFinish,
+ ipv6cp_InitRestartCounter,
+ ipv6cp_SendConfigReq,
+ ipv6cp_SentTerminateReq,
+ ipv6cp_SendTerminateAck,
+ ipv6cp_DecodeConfig,
+ fsm_NullRecvResetReq,
+ fsm_NullRecvResetAck
+};
+
+static void
+SetInterfaceID(u_char *ifid, int userandom)
+{
+ struct ifaddrs *ifa, *ifap = NULL;
+ struct sockaddr_dl *sdl;
+ const u_long i32_max = 0xffffffff;
+ u_long r1, r2;
+
+ /* configure an interface ID based on Section 4.1 of RFC 2472 */
+ memset(ifid, 0, IPV6CP_IFIDLEN);
+
+ /*
+ * 1) If an IEEE global identifier (EUI-48 or EUI-64) is
+ * available anywhere on the node, it should be used to construct
+ * the tentative Interface-Identifier due to its uniqueness
+ * properties.
+ */
+ if (userandom)
+ goto randomid;
+ if (getifaddrs(&ifap) < 0)
+ goto randomid;
+
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ char *cp;
+
+ if (ifa->ifa_addr->sa_family != AF_LINK)
+ continue;
+
+ sdl = (struct sockaddr_dl *)ifa->ifa_addr;
+ if (sdl->sdl_alen < 6)
+ continue;
+ /* we're only interested in IEEE hardware addresses */
+ switch(sdl->sdl_type) {
+ case IFT_ETHER:
+ case IFT_FDDI:
+ case IFT_L2VLAN:
+ /* XXX need more cases? */
+ break;
+ default:
+ continue;
+ }
+
+ cp = (char *)(sdl->sdl_data + sdl->sdl_nlen);
+ ifid[0] = cp[0];
+ ifid[0] ^= 0x02; /* reverse the u/l bit*/
+ ifid[1] = cp[1];
+ ifid[2] = cp[2];
+ ifid[3] = 0xff;
+ ifid[4] = 0xfe;
+ ifid[5] = cp[3];
+ ifid[6] = cp[4];
+ ifid[7] = cp[5];
+
+ freeifaddrs(ifap);
+ return;
+ }
+
+ freeifaddrs(ifap);
+
+ /*
+ * 2) If an IEEE global identifier is not available a different source
+ * of uniqueness should be used.
+ * XXX: we skip this case.
+ */
+
+ /*
+ * 3) If a good source of uniqueness cannot be found, it is
+ * recommended that a random number be generated. In this case the
+ * "u" bit of the interface identifier MUST be set to zero (0).
+ */
+ randomid:
+ randinit();
+ r1 = (((u_long)random()) % i32_max) + 1;
+ r2 = (((u_long)random()) % i32_max) + 1;
+ memcpy(ifid, &r1, sizeof(r1));
+ memcpy(ifid + 4, &r2, sizeof(r2));
+ ifid[0] &= 0xfd;
+ return;
+}
+
+static int
+ipcp_SetIPv6address(struct ipv6cp *ipv6cp, u_char *myifid, u_char *hisifid)
+{
+ struct bundle *bundle = ipv6cp->fsm.bundle;
+ struct in6_addr myaddr, hisaddr;
+ struct ncprange myrange, range;
+ struct ncpaddr addr;
+ struct sockaddr_storage ssdst, ssgw, ssmask;
+ struct sockaddr *sadst, *sagw, *samask;
+
+ sadst = (struct sockaddr *)&ssdst;
+ sagw = (struct sockaddr *)&ssgw;
+ samask = (struct sockaddr *)&ssmask;
+
+ memset(&myaddr, '\0', sizeof myaddr);
+ memset(&hisaddr, '\0', sizeof hisaddr);
+
+ myaddr.s6_addr[0] = 0xfe;
+ myaddr.s6_addr[1] = 0x80;
+ memcpy(&myaddr.s6_addr[8], myifid, IPV6CP_IFIDLEN);
+#if 0
+ myaddr.s6_addr[8] |= 0x02; /* set 'universal' bit */
+#endif
+
+ hisaddr.s6_addr[0] = 0xfe;
+ hisaddr.s6_addr[1] = 0x80;
+ memcpy(&hisaddr.s6_addr[8], hisifid, IPV6CP_IFIDLEN);
+#if 0
+ hisaddr.s6_addr[8] |= 0x02; /* set 'universal' bit */
+#endif
+
+ ncpaddr_setip6(&ipv6cp->myaddr, &myaddr);
+ ncpaddr_setip6(&ipv6cp->hisaddr, &hisaddr);
+ ncprange_set(&myrange, &ipv6cp->myaddr, 64);
+
+ if (!iface_Add(bundle->iface, &bundle->ncp, &myrange, &ipv6cp->hisaddr,
+ IFACE_ADD_FIRST|IFACE_FORCE_ADD|IFACE_SYSTEM))
+ return 0;
+
+ if (!Enabled(bundle, OPT_IFACEALIAS))
+ iface_Clear(bundle->iface, &bundle->ncp, AF_INET6,
+ IFACE_CLEAR_ALIASES|IFACE_SYSTEM);
+
+ ncpaddr_setip6(&addr, &in6addr_linklocal_mcast);
+ ncprange_set(&range, &addr, 32);
+ rt_Set(bundle, RTM_ADD, &range, &ipv6cp->myaddr, 1, 0);
+
+ if (bundle->ncp.cfg.sendpipe > 0 || bundle->ncp.cfg.recvpipe > 0) {
+ ncprange_getsa(&myrange, &ssgw, &ssmask);
+ if (ncpaddr_isset(&ipv6cp->hisaddr))
+ ncpaddr_getsa(&ipv6cp->hisaddr, &ssdst);
+ else
+ sadst = NULL;
+ rt_Update(bundle, sadst, sagw, samask, NULL, NULL);
+ }
+
+ if (Enabled(bundle, OPT_SROUTES))
+ route_Change(bundle, bundle->ncp.route, &ipv6cp->myaddr, &ipv6cp->hisaddr);
+
+#ifndef NORADIUS
+ if (bundle->radius.valid)
+ route_Change(bundle, bundle->radius.ipv6routes, &ipv6cp->myaddr,
+ &ipv6cp->hisaddr);
+#endif
+
+ return 1; /* Ok */
+}
+
+void
+ipv6cp_Init(struct ipv6cp *ipv6cp, struct bundle *bundle, struct link *l,
+ const struct fsm_parent *parent)
+{
+ static const char * const timer_names[] =
+ {"IPV6CP restart", "IPV6CP openmode", "IPV6CP stopped"};
+ int n;
+
+ fsm_Init(&ipv6cp->fsm, "IPV6CP", PROTO_IPV6CP, 1, IPV6CP_MAXCODE, LogIPV6CP,
+ bundle, l, parent, &ipv6cp_Callbacks, timer_names);
+
+ ipv6cp->cfg.fsm.timeout = DEF_FSMRETRY;
+ ipv6cp->cfg.fsm.maxreq = DEF_FSMTRIES;
+ ipv6cp->cfg.fsm.maxtrm = DEF_FSMTRIES;
+
+ SetInterfaceID(ipv6cp->my_ifid, 0);
+ do {
+ SetInterfaceID(ipv6cp->his_ifid, 1);
+ } while (memcmp(ipv6cp->his_ifid, ipv6cp->my_ifid, IPV6CP_IFIDLEN) == 0);
+
+ if (probe.ipv6_available) {
+ n = 100;
+ while (n &&
+ !ipcp_SetIPv6address(ipv6cp, ipv6cp->my_ifid, ipv6cp->his_ifid)) {
+ do {
+ n--;
+ SetInterfaceID(ipv6cp->my_ifid, 1);
+ } while (n
+ && memcmp(ipv6cp->his_ifid, ipv6cp->my_ifid, IPV6CP_IFIDLEN) == 0);
+ }
+ }
+
+ throughput_init(&ipv6cp->throughput, SAMPLE_PERIOD);
+ memset(ipv6cp->Queue, '\0', sizeof ipv6cp->Queue);
+ ipv6cp_Setup(ipv6cp);
+}
+
+void
+ipv6cp_Destroy(struct ipv6cp *ipv6cp)
+{
+ throughput_destroy(&ipv6cp->throughput);
+}
+
+void
+ipv6cp_Setup(struct ipv6cp *ipv6cp)
+{
+ ncpaddr_init(&ipv6cp->myaddr);
+ ncpaddr_init(&ipv6cp->hisaddr);
+
+ ipv6cp->his_reject = 0;
+ ipv6cp->my_reject = 0;
+}
+
+void
+ipv6cp_SetLink(struct ipv6cp *ipv6cp, struct link *l)
+{
+ ipv6cp->fsm.link = l;
+}
+
+int
+ipv6cp_Show(struct cmdargs const *arg)
+{
+ struct ipv6cp *ipv6cp = &arg->bundle->ncp.ipv6cp;
+
+ prompt_Printf(arg->prompt, "%s [%s]\n", ipv6cp->fsm.name,
+ State2Nam(ipv6cp->fsm.state));
+ if (ipv6cp->fsm.state == ST_OPENED) {
+ prompt_Printf(arg->prompt, " His side: %s\n",
+ ncpaddr_ntoa(&ipv6cp->hisaddr));
+ prompt_Printf(arg->prompt, " My side: %s\n",
+ ncpaddr_ntoa(&ipv6cp->myaddr));
+ prompt_Printf(arg->prompt, " Queued packets: %lu\n",
+ (unsigned long)ipv6cp_QueueLen(ipv6cp));
+ }
+
+ prompt_Printf(arg->prompt, "\nDefaults:\n");
+ prompt_Printf(arg->prompt, " FSM retry = %us, max %u Config"
+ " REQ%s, %u Term REQ%s\n\n", ipv6cp->cfg.fsm.timeout,
+ ipv6cp->cfg.fsm.maxreq, ipv6cp->cfg.fsm.maxreq == 1 ? "" : "s",
+ ipv6cp->cfg.fsm.maxtrm, ipv6cp->cfg.fsm.maxtrm == 1 ? "" : "s");
+
+ throughput_disp(&ipv6cp->throughput, arg->prompt);
+
+ return 0;
+}
+
+struct mbuf *
+ipv6cp_Input(struct bundle *bundle, struct link *l, struct mbuf *bp)
+{
+ /* Got PROTO_IPV6CP from link */
+ m_settype(bp, MB_IPV6CPIN);
+ if (bundle_Phase(bundle) == PHASE_NETWORK)
+ fsm_Input(&bundle->ncp.ipv6cp.fsm, bp);
+ else {
+ if (bundle_Phase(bundle) < PHASE_NETWORK)
+ log_Printf(LogIPV6CP, "%s: Error: Unexpected IPV6CP in phase %s"
+ " (ignored)\n", l->name, bundle_PhaseName(bundle));
+ m_freem(bp);
+ }
+ return NULL;
+}
+
+void
+ipv6cp_AddInOctets(struct ipv6cp *ipv6cp, int n)
+{
+ throughput_addin(&ipv6cp->throughput, n);
+}
+
+void
+ipv6cp_AddOutOctets(struct ipv6cp *ipv6cp, int n)
+{
+ throughput_addout(&ipv6cp->throughput, n);
+}
+
+void
+ipv6cp_IfaceAddrAdded(struct ipv6cp *ipv6cp __unused,
+ const struct iface_addr *addr __unused)
+{
+}
+
+void
+ipv6cp_IfaceAddrDeleted(struct ipv6cp *ipv6cp __unused,
+ const struct iface_addr *addr __unused)
+{
+}
+
+int
+ipv6cp_InterfaceUp(struct ipv6cp *ipv6cp)
+{
+ if (!ipcp_SetIPv6address(ipv6cp, ipv6cp->my_ifid, ipv6cp->his_ifid)) {
+ log_Printf(LogERROR, "ipv6cp_InterfaceUp: unable to set ipv6 address\n");
+ return 0;
+ }
+
+ if (!iface_SetFlags(ipv6cp->fsm.bundle->iface->name, IFF_UP)) {
+ log_Printf(LogERROR, "ipv6cp_InterfaceUp: Can't set the IFF_UP"
+ " flag on %s\n", ipv6cp->fsm.bundle->iface->name);
+ return 0;
+ }
+
+ return 1;
+}
+
+size_t
+ipv6cp_QueueLen(struct ipv6cp *ipv6cp)
+{
+ struct mqueue *q;
+ size_t result;
+
+ result = 0;
+ for (q = ipv6cp->Queue; q < ipv6cp->Queue + IPV6CP_QUEUES(ipv6cp); q++)
+ result += q->len;
+
+ return result;
+}
+
+int
+ipv6cp_PushPacket(struct ipv6cp *ipv6cp, struct link *l)
+{
+ struct bundle *bundle = ipv6cp->fsm.bundle;
+ struct mqueue *queue;
+ struct mbuf *bp;
+ int m_len;
+ u_int32_t secs = 0;
+ unsigned alivesecs = 0;
+
+ if (ipv6cp->fsm.state != ST_OPENED)
+ return 0;
+
+ /*
+ * If ccp is not open but is required, do nothing.
+ */
+ if (l->ccp.fsm.state != ST_OPENED && ccp_Required(&l->ccp)) {
+ log_Printf(LogPHASE, "%s: Not transmitting... waiting for CCP\n", l->name);
+ return 0;
+ }
+
+ queue = ipv6cp->Queue + IPV6CP_QUEUES(ipv6cp) - 1;
+ do {
+ if (queue->top) {
+ bp = m_dequeue(queue);
+ bp = mbuf_Read(bp, &secs, sizeof secs);
+ bp = m_pullup(bp);
+ m_len = m_length(bp);
+ if (!FilterCheck(MBUF_CTOP(bp), AF_INET6, &bundle->filter.alive,
+ &alivesecs)) {
+ if (secs == 0)
+ secs = alivesecs;
+ bundle_StartIdleTimer(bundle, secs);
+ }
+ link_PushPacket(l, bp, bundle, 0, PROTO_IPV6);
+ ipv6cp_AddOutOctets(ipv6cp, m_len);
+ return 1;
+ }
+ } while (queue-- != ipv6cp->Queue);
+
+ return 0;
+}
+
+static int
+ipv6cp_LayerUp(struct fsm *fp)
+{
+ /* We're now up */
+ struct ipv6cp *ipv6cp = fsm2ipv6cp(fp);
+ char tbuff[40];
+
+ log_Printf(LogIPV6CP, "%s: LayerUp.\n", fp->link->name);
+ if (!ipv6cp_InterfaceUp(ipv6cp))
+ return 0;
+
+ snprintf(tbuff, sizeof tbuff, "%s", ncpaddr_ntoa(&ipv6cp->myaddr));
+ log_Printf(LogIPV6CP, "myaddr %s hisaddr = %s\n",
+ tbuff, ncpaddr_ntoa(&ipv6cp->hisaddr));
+
+#ifndef NORADIUS
+ radius_Account_Set_Ipv6(&fp->bundle->radacct6, ipv6cp->his_ifid);
+ radius_Account(&fp->bundle->radius, &fp->bundle->radacct6,
+ fp->bundle->links, RAD_START, &ipv6cp->throughput);
+
+ /*
+ * XXX: Avoid duplicate evaluation of filterid between IPCP and
+ * IPV6CP. When IPCP is enabled and rejected, filterid is not
+ * evaluated.
+ */
+ if (!Enabled(fp->bundle, OPT_IPCP)) {
+ if (*fp->bundle->radius.cfg.file && fp->bundle->radius.filterid)
+ system_Select(fp->bundle, fp->bundle->radius.filterid, LINKUPFILE,
+ NULL, NULL);
+ }
+#endif
+
+ /*
+ * XXX this stuff should really live in the FSM. Our config should
+ * associate executable sections in files with events.
+ */
+ if (system_Select(fp->bundle, tbuff, LINKUPFILE, NULL, NULL) < 0) {
+ /*
+ * XXX: Avoid duplicate evaluation of label between IPCP and
+ * IPV6CP. When IPCP is enabled and rejected, label is not
+ * evaluated.
+ */
+ if (bundle_GetLabel(fp->bundle) && !Enabled(fp->bundle, OPT_IPCP)) {
+ if (system_Select(fp->bundle, bundle_GetLabel(fp->bundle),
+ LINKUPFILE, NULL, NULL) < 0)
+ system_Select(fp->bundle, "MYADDR6", LINKUPFILE, NULL, NULL);
+ } else
+ system_Select(fp->bundle, "MYADDR6", LINKUPFILE, NULL, NULL);
+ }
+
+ fp->more.reqs = fp->more.naks = fp->more.rejs = ipv6cp->cfg.fsm.maxreq * 3;
+ log_DisplayPrompts();
+
+ return 1;
+}
+
+static void
+ipv6cp_LayerDown(struct fsm *fp)
+{
+ /* About to come down */
+ struct ipv6cp *ipv6cp = fsm2ipv6cp(fp);
+ static int recursing;
+ char addr[40];
+
+ if (!recursing++) {
+ snprintf(addr, sizeof addr, "%s", ncpaddr_ntoa(&ipv6cp->myaddr));
+ log_Printf(LogIPV6CP, "%s: LayerDown: %s\n", fp->link->name, addr);
+
+#ifndef NORADIUS
+ radius_Flush(&fp->bundle->radius);
+ radius_Account(&fp->bundle->radius, &fp->bundle->radacct6,
+ fp->bundle->links, RAD_STOP, &ipv6cp->throughput);
+
+ /*
+ * XXX: Avoid duplicate evaluation of filterid between IPCP and
+ * IPV6CP. When IPCP is enabled and rejected, filterid is not
+ * evaluated.
+ */
+ if (!Enabled(fp->bundle, OPT_IPCP)) {
+ if (*fp->bundle->radius.cfg.file && fp->bundle->radius.filterid)
+ system_Select(fp->bundle, fp->bundle->radius.filterid, LINKDOWNFILE,
+ NULL, NULL);
+ }
+#endif
+
+ /*
+ * XXX this stuff should really live in the FSM. Our config should
+ * associate executable sections in files with events.
+ */
+ if (system_Select(fp->bundle, addr, LINKDOWNFILE, NULL, NULL) < 0) {
+ /*
+ * XXX: Avoid duplicate evaluation of label between IPCP and
+ * IPV6CP. When IPCP is enabled and rejected, label is not
+ * evaluated.
+ */
+ if (bundle_GetLabel(fp->bundle) && !Enabled(fp->bundle, OPT_IPCP)) {
+ if (system_Select(fp->bundle, bundle_GetLabel(fp->bundle),
+ LINKDOWNFILE, NULL, NULL) < 0)
+ system_Select(fp->bundle, "MYADDR6", LINKDOWNFILE, NULL, NULL);
+ } else
+ system_Select(fp->bundle, "MYADDR6", LINKDOWNFILE, NULL, NULL);
+ }
+
+ ipv6cp_Setup(ipv6cp);
+ }
+ recursing--;
+}
+
+static void
+ipv6cp_LayerStart(struct fsm *fp)
+{
+ /* We're about to start up ! */
+ struct ipv6cp *ipv6cp = fsm2ipv6cp(fp);
+
+ log_Printf(LogIPV6CP, "%s: LayerStart.\n", fp->link->name);
+ throughput_start(&ipv6cp->throughput, "IPV6CP throughput",
+ Enabled(fp->bundle, OPT_THROUGHPUT));
+ fp->more.reqs = fp->more.naks = fp->more.rejs = ipv6cp->cfg.fsm.maxreq * 3;
+ ipv6cp->peer_tokenreq = 0;
+}
+
+static void
+ipv6cp_LayerFinish(struct fsm *fp)
+{
+ /* We're now down */
+ struct ipv6cp *ipv6cp = fsm2ipv6cp(fp);
+
+ log_Printf(LogIPV6CP, "%s: LayerFinish.\n", fp->link->name);
+ throughput_stop(&ipv6cp->throughput);
+ throughput_log(&ipv6cp->throughput, LogIPV6CP, NULL);
+}
+
+static void
+ipv6cp_InitRestartCounter(struct fsm *fp, int what)
+{
+ /* Set fsm timer load */
+ struct ipv6cp *ipv6cp = fsm2ipv6cp(fp);
+
+ fp->FsmTimer.load = ipv6cp->cfg.fsm.timeout * SECTICKS;
+ switch (what) {
+ case FSM_REQ_TIMER:
+ fp->restart = ipv6cp->cfg.fsm.maxreq;
+ break;
+ case FSM_TRM_TIMER:
+ fp->restart = ipv6cp->cfg.fsm.maxtrm;
+ break;
+ default:
+ fp->restart = 1;
+ break;
+ }
+}
+
+static void
+ipv6cp_SendConfigReq(struct fsm *fp)
+{
+ /* Send config REQ please */
+ struct physical *p = link2physical(fp->link);
+ struct ipv6cp *ipv6cp = fsm2ipv6cp(fp);
+ u_char buff[IPV6CP_IFIDLEN+2];
+ struct fsm_opt *o;
+
+ o = (struct fsm_opt *)buff;
+
+ if ((p && !physical_IsSync(p)) || !REJECTED(ipv6cp, TY_TOKEN)) {
+ memcpy(o->data, ipv6cp->my_ifid, IPV6CP_IFIDLEN);
+ INC_FSM_OPT(TY_TOKEN, IPV6CP_IFIDLEN + 2, o);
+ }
+
+ fsm_Output(fp, CODE_CONFIGREQ, fp->reqid, buff, (u_char *)o - buff,
+ MB_IPV6CPOUT);
+}
+
+static void
+ipv6cp_SentTerminateReq(struct fsm *fp __unused)
+{
+ /* Term REQ just sent by FSM */
+}
+
+static void
+ipv6cp_SendTerminateAck(struct fsm *fp, u_char id)
+{
+ /* Send Term ACK please */
+ fsm_Output(fp, CODE_TERMACK, id, NULL, 0, MB_IPV6CPOUT);
+}
+
+static const char *
+protoname(unsigned proto)
+{
+ static const char *cftypes[] = { "IFACEID", "COMPPROTO" };
+
+ if (proto > 0 && proto <= sizeof cftypes / sizeof *cftypes)
+ return cftypes[proto - 1];
+
+ return NumStr(proto, NULL, 0);
+}
+
+static void
+ipv6cp_ValidateInterfaceID(struct ipv6cp *ipv6cp, u_char *ifid,
+ struct fsm_decode *dec)
+{
+ struct fsm_opt opt;
+ u_char zero[IPV6CP_IFIDLEN];
+
+ memset(zero, 0, IPV6CP_IFIDLEN);
+
+ if (memcmp(ifid, zero, IPV6CP_IFIDLEN) != 0
+ && memcmp(ifid, ipv6cp->my_ifid, IPV6CP_IFIDLEN) != 0)
+ memcpy(ipv6cp->his_ifid, ifid, IPV6CP_IFIDLEN);
+
+ opt.hdr.id = TY_TOKEN;
+ opt.hdr.len = IPV6CP_IFIDLEN + 2;
+ memcpy(opt.data, &ipv6cp->his_ifid, IPV6CP_IFIDLEN);
+ if (memcmp(ifid, ipv6cp->his_ifid, IPV6CP_IFIDLEN) == 0)
+ fsm_ack(dec, &opt);
+ else
+ fsm_nak(dec, &opt);
+}
+
+static void
+ipv6cp_DecodeConfig(struct fsm *fp, u_char *cp, u_char *end, int mode_type,
+ struct fsm_decode *dec)
+{
+ /* Deal with incoming PROTO_IPV6CP */
+ struct ipv6cp *ipv6cp = fsm2ipv6cp(fp);
+ int n;
+ char tbuff[100];
+ u_char ifid[IPV6CP_IFIDLEN], zero[IPV6CP_IFIDLEN];
+ struct fsm_opt *opt;
+
+ memset(zero, 0, IPV6CP_IFIDLEN);
+
+ while (end - cp >= (int)sizeof(opt->hdr)) {
+ if ((opt = fsm_readopt(&cp)) == NULL)
+ break;
+
+ snprintf(tbuff, sizeof tbuff, " %s[%d]", protoname(opt->hdr.id),
+ opt->hdr.len);
+
+ switch (opt->hdr.id) {
+ case TY_TOKEN:
+ memcpy(ifid, opt->data, IPV6CP_IFIDLEN);
+ log_Printf(LogIPV6CP, "%s 0x%02x%02x%02x%02x%02x%02x%02x%02x\n", tbuff,
+ ifid[0], ifid[1], ifid[2], ifid[3], ifid[4], ifid[5], ifid[6], ifid[7]);
+
+ switch (mode_type) {
+ case MODE_REQ:
+ ipv6cp->peer_tokenreq = 1;
+ ipv6cp_ValidateInterfaceID(ipv6cp, ifid, dec);
+ break;
+
+ case MODE_NAK:
+ if (memcmp(ifid, zero, IPV6CP_IFIDLEN) == 0) {
+ log_Printf(log_IsKept(LogIPV6CP) ? LogIPV6CP : LogPHASE,
+ "0x0000000000000000: Unacceptable IntefaceID!\n");
+ fsm_Close(&ipv6cp->fsm);
+ } else if (memcmp(ifid, ipv6cp->his_ifid, IPV6CP_IFIDLEN) == 0) {
+ log_Printf(log_IsKept(LogIPV6CP) ? LogIPV6CP : LogPHASE,
+ "0x%02x%02x%02x%02x%02x%02x%02x%02x: "
+ "Unacceptable IntefaceID!\n",
+ ifid[0], ifid[1], ifid[2], ifid[3],
+ ifid[4], ifid[5], ifid[6], ifid[7]);
+ } else if (memcmp(ifid, ipv6cp->my_ifid, IPV6CP_IFIDLEN) != 0) {
+ n = 100;
+ while (n && !ipcp_SetIPv6address(ipv6cp, ifid, ipv6cp->his_ifid)) {
+ do {
+ n--;
+ SetInterfaceID(ifid, 1);
+ } while (n && memcmp(ifid, ipv6cp->his_ifid, IPV6CP_IFIDLEN) == 0);
+ }
+
+ if (n == 0) {
+ log_Printf(log_IsKept(LogIPV6CP) ? LogIPV6CP : LogPHASE,
+ "0x0000000000000000: Unacceptable IntefaceID!\n");
+ fsm_Close(&ipv6cp->fsm);
+ } else {
+ log_Printf(LogIPV6CP, "%s changing IntefaceID: "
+ "0x%02x%02x%02x%02x%02x%02x%02x%02x "
+ "--> 0x%02x%02x%02x%02x%02x%02x%02x%02x\n", tbuff,
+ ipv6cp->my_ifid[0], ipv6cp->my_ifid[1],
+ ipv6cp->my_ifid[2], ipv6cp->my_ifid[3],
+ ipv6cp->my_ifid[4], ipv6cp->my_ifid[5],
+ ipv6cp->my_ifid[6], ipv6cp->my_ifid[7],
+ ifid[0], ifid[1], ifid[2], ifid[3],
+ ifid[4], ifid[5], ifid[6], ifid[7]);
+ memcpy(ipv6cp->my_ifid, ifid, IPV6CP_IFIDLEN);
+ bundle_AdjustFilters(fp->bundle, &ipv6cp->myaddr, NULL);
+ }
+ }
+ break;
+
+ case MODE_REJ:
+ ipv6cp->his_reject |= (1 << opt->hdr.id);
+ break;
+ }
+ break;
+
+ default:
+ if (mode_type != MODE_NOP) {
+ ipv6cp->my_reject |= (1 << opt->hdr.id);
+ fsm_rej(dec, opt);
+ }
+ break;
+ }
+ }
+
+ if (mode_type != MODE_NOP) {
+ if (mode_type == MODE_REQ && !ipv6cp->peer_tokenreq) {
+ if (dec->rejend == dec->rej && dec->nakend == dec->nak) {
+ /*
+ * Pretend the peer has requested a TOKEN.
+ * We do this to ensure that we only send one NAK if the only
+ * reason for the NAK is because the peer isn't sending a
+ * TY_TOKEN REQ. This stops us from repeatedly trying to tell
+ * the peer that we have to have an IP address on their end.
+ */
+ ipv6cp->peer_tokenreq = 1;
+ }
+ memset(ifid, 0, IPV6CP_IFIDLEN);
+ ipv6cp_ValidateInterfaceID(ipv6cp, ifid, dec);
+ }
+ fsm_opt_normalise(dec);
+ }
+}
+#endif
diff --git a/usr.sbin/ppp/ipv6cp.h b/usr.sbin/ppp/ipv6cp.h
new file mode 100644
index 0000000..53f7153
--- /dev/null
+++ b/usr.sbin/ppp/ipv6cp.h
@@ -0,0 +1,83 @@
+/*-
+ * Copyright (c) 2001 Brian Somers <brian@Awfulhak.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 NOINET6
+#define IPV6CP_MAXCODE CODE_CODEREJ
+
+#define TY_TOKEN 1
+#define TY_COMPPROTO 2
+
+#define IPV6CP_IFIDLEN 8 /* RFC2472 */
+
+struct ipv6cp {
+ struct fsm fsm; /* The finite state machine */
+
+ struct {
+ struct fsm_retry fsm; /* frequency to resend requests */
+ } cfg;
+
+ unsigned peer_tokenreq : 1; /* Any TY_TOKEN REQs from the peer ? */
+
+ u_char my_ifid[IPV6CP_IFIDLEN]; /* Local Interface Identifier */
+ u_char his_ifid[IPV6CP_IFIDLEN]; /* Peer Interface Identifier */
+
+ struct ncpaddr myaddr; /* Local address */
+ struct ncpaddr hisaddr; /* Peer address */
+
+ u_int32_t his_reject; /* Request codes rejected by peer */
+ u_int32_t my_reject; /* Request codes I have rejected */
+
+ struct pppThroughput throughput; /* throughput statistics */
+ struct mqueue Queue[2]; /* Output packet queues */
+};
+
+#define fsm2ipv6cp(fp) (fp->proto == PROTO_IPV6CP ? (struct ipv6cp *)fp : NULL)
+#define IPV6CP_QUEUES(ipv6cp) (sizeof ipv6cp->Queue / sizeof ipv6cp->Queue[0])
+
+struct bundle;
+struct link;
+struct cmdargs;
+struct iface_addr;
+
+extern void ipv6cp_Init(struct ipv6cp *, struct bundle *, struct link *,
+ const struct fsm_parent *);
+extern void ipv6cp_Destroy(struct ipv6cp *);
+extern void ipv6cp_Setup(struct ipv6cp *);
+extern void ipv6cp_SetLink(struct ipv6cp *, struct link *);
+
+extern int ipv6cp_Show(struct cmdargs const *);
+extern struct mbuf *ipv6cp_Input(struct bundle *, struct link *, struct mbuf *);
+extern void ipv6cp_AddInOctets(struct ipv6cp *, int);
+extern void ipv6cp_AddOutOctets(struct ipv6cp *, int);
+
+extern void ipv6cp_IfaceAddrAdded(struct ipv6cp *, const struct iface_addr *);
+extern void ipv6cp_IfaceAddrDeleted(struct ipv6cp *, const struct iface_addr *);
+extern int ipv6cp_InterfaceUp(struct ipv6cp *);
+extern size_t ipv6cp_QueueLen(struct ipv6cp *);
+extern int ipv6cp_PushPacket(struct ipv6cp *, struct link *);
+#endif
diff --git a/usr.sbin/ppp/layer.h b/usr.sbin/ppp/layer.h
new file mode 100644
index 0000000..4ee59a1
--- /dev/null
+++ b/usr.sbin/ppp/layer.h
@@ -0,0 +1,52 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.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$
+ */
+
+#define LAYER_ASYNC 2
+#define LAYER_SYNC 3
+#define LAYER_HDLC 4
+#define LAYER_ACF 5
+#define LAYER_PROTO 6
+#define LAYER_LQR 7
+#define LAYER_CCP 8
+#define LAYER_VJ 9
+#define LAYER_NAT 10
+
+#define LAYER_MAX 10 /* How many layers we can handle on a link */
+
+struct mbuf;
+struct link;
+struct bundle;
+
+struct layer {
+ int type;
+ const char *name;
+ struct mbuf *(*push)(struct bundle *, struct link *, struct mbuf *,
+ int pri, u_short *proto);
+ struct mbuf *(*pull)(struct bundle *, struct link *, struct mbuf *,
+ u_short *);
+};
diff --git a/usr.sbin/ppp/lcp.c b/usr.sbin/ppp/lcp.c
new file mode 100644
index 0000000..cf75718
--- /dev/null
+++ b/usr.sbin/ppp/lcp.c
@@ -0,0 +1,1305 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "ua.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "proto.h"
+#include "descriptor.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "async.h"
+#include "link.h"
+#include "physical.h"
+#include "prompt.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "mp.h"
+#include "chat.h"
+#include "auth.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+
+/* for received LQRs */
+struct lqrreq {
+ struct fsm_opt_hdr hdr;
+ u_short proto; /* Quality protocol */
+ u_int32_t period; /* Reporting interval */
+};
+
+static int LcpLayerUp(struct fsm *);
+static void LcpLayerDown(struct fsm *);
+static void LcpLayerStart(struct fsm *);
+static void LcpLayerFinish(struct fsm *);
+static void LcpInitRestartCounter(struct fsm *, int);
+static void LcpSendConfigReq(struct fsm *);
+static void LcpSentTerminateReq(struct fsm *);
+static void LcpSendTerminateAck(struct fsm *, u_char);
+static void LcpDecodeConfig(struct fsm *, u_char *, u_char *, int,
+ struct fsm_decode *);
+
+static struct fsm_callbacks lcp_Callbacks = {
+ LcpLayerUp,
+ LcpLayerDown,
+ LcpLayerStart,
+ LcpLayerFinish,
+ LcpInitRestartCounter,
+ LcpSendConfigReq,
+ LcpSentTerminateReq,
+ LcpSendTerminateAck,
+ LcpDecodeConfig,
+ fsm_NullRecvResetReq,
+ fsm_NullRecvResetAck
+};
+
+static const char * const lcp_TimerNames[] =
+ {"LCP restart", "LCP openmode", "LCP stopped"};
+
+static const char *
+protoname(unsigned proto)
+{
+ static const char * const cftypes[] = {
+ /* Check out the latest ``Assigned numbers'' rfc (1700) */
+ NULL,
+ "MRU", /* 1: Maximum-Receive-Unit */
+ "ACCMAP", /* 2: Async-Control-Character-Map */
+ "AUTHPROTO", /* 3: Authentication-Protocol */
+ "QUALPROTO", /* 4: Quality-Protocol */
+ "MAGICNUM", /* 5: Magic-Number */
+ "RESERVED", /* 6: RESERVED */
+ "PROTOCOMP", /* 7: Protocol-Field-Compression */
+ "ACFCOMP", /* 8: Address-and-Control-Field-Compression */
+ "FCSALT", /* 9: FCS-Alternatives */
+ "SDP", /* 10: Self-Describing-Pad */
+ "NUMMODE", /* 11: Numbered-Mode */
+ "MULTIPROC", /* 12: Multi-Link-Procedure */
+ "CALLBACK", /* 13: Callback */
+ "CONTIME", /* 14: Connect-Time */
+ "COMPFRAME", /* 15: Compound-Frames */
+ "NDE", /* 16: Nominal-Data-Encapsulation */
+ "MRRU", /* 17: Multilink-MRRU */
+ "SHORTSEQ", /* 18: Multilink-Short-Sequence-Number-Header */
+ "ENDDISC", /* 19: Multilink-Endpoint-Discriminator */
+ "PROPRIETRY", /* 20: Proprietary */
+ "DCEID", /* 21: DCE-Identifier */
+ "MULTIPP", /* 22: Multi-Link-Plus-Procedure */
+ "LDBACP", /* 23: Link Discriminator for BACP */
+ };
+
+ if (proto > sizeof cftypes / sizeof *cftypes || cftypes[proto] == NULL)
+ return HexStr(proto, NULL, 0);
+
+ return cftypes[proto];
+}
+
+int
+lcp_ReportStatus(struct cmdargs const *arg)
+{
+ struct link *l;
+ struct lcp *lcp;
+
+ l = command_ChooseLink(arg);
+ lcp = &l->lcp;
+
+ prompt_Printf(arg->prompt, "%s: %s [%s]\n", l->name, lcp->fsm.name,
+ State2Nam(lcp->fsm.state));
+ prompt_Printf(arg->prompt,
+ " his side: MRU %d, ACCMAP %08lx, PROTOCOMP %s, ACFCOMP %s,\n"
+ " MAGIC %08lx, MRRU %u, SHORTSEQ %s, REJECT %04x\n",
+ lcp->his_mru, (u_long)lcp->his_accmap,
+ lcp->his_protocomp ? "on" : "off",
+ lcp->his_acfcomp ? "on" : "off",
+ (u_long)lcp->his_magic, lcp->his_mrru,
+ lcp->his_shortseq ? "on" : "off", lcp->his_reject);
+ prompt_Printf(arg->prompt,
+ " my side: MRU %d, ACCMAP %08lx, PROTOCOMP %s, ACFCOMP %s,\n"
+ " MAGIC %08lx, MRRU %u, SHORTSEQ %s, REJECT %04x\n",
+ lcp->want_mru, (u_long)lcp->want_accmap,
+ lcp->want_protocomp ? "on" : "off",
+ lcp->want_acfcomp ? "on" : "off",
+ (u_long)lcp->want_magic, lcp->want_mrru,
+ lcp->want_shortseq ? "on" : "off", lcp->my_reject);
+
+ if (lcp->cfg.mru)
+ prompt_Printf(arg->prompt, "\n Defaults: MRU = %d (max %d), ",
+ lcp->cfg.mru, lcp->cfg.max_mru);
+ else
+ prompt_Printf(arg->prompt, "\n Defaults: MRU = any (max %d), ",
+ lcp->cfg.max_mru);
+ if (lcp->cfg.mtu)
+ prompt_Printf(arg->prompt, "MTU = %d (max %d), ",
+ lcp->cfg.mtu, lcp->cfg.max_mtu);
+ else
+ prompt_Printf(arg->prompt, "MTU = any (max %d), ", lcp->cfg.max_mtu);
+ prompt_Printf(arg->prompt, "ACCMAP = %08lx\n", (u_long)lcp->cfg.accmap);
+ prompt_Printf(arg->prompt, " LQR period = %us, ",
+ lcp->cfg.lqrperiod);
+ prompt_Printf(arg->prompt, "Open Mode = %s",
+ lcp->cfg.openmode == OPEN_PASSIVE ? "passive" : "active");
+ if (lcp->cfg.openmode > 0)
+ prompt_Printf(arg->prompt, " (delay %ds)", lcp->cfg.openmode);
+ prompt_Printf(arg->prompt, "\n FSM retry = %us, max %u Config"
+ " REQ%s, %u Term REQ%s\n", lcp->cfg.fsm.timeout,
+ lcp->cfg.fsm.maxreq, lcp->cfg.fsm.maxreq == 1 ? "" : "s",
+ lcp->cfg.fsm.maxtrm, lcp->cfg.fsm.maxtrm == 1 ? "" : "s");
+ prompt_Printf(arg->prompt, " Ident: %s\n", lcp->cfg.ident);
+ prompt_Printf(arg->prompt, "\n Negotiation:\n");
+ prompt_Printf(arg->prompt, " ACFCOMP = %s\n",
+ command_ShowNegval(lcp->cfg.acfcomp));
+ prompt_Printf(arg->prompt, " CHAP = %s\n",
+ command_ShowNegval(lcp->cfg.chap05));
+#ifndef NODES
+ prompt_Printf(arg->prompt, " CHAP80 = %s\n",
+ command_ShowNegval(lcp->cfg.chap80nt));
+ prompt_Printf(arg->prompt, " LANMan = %s\n",
+ command_ShowNegval(lcp->cfg.chap80lm));
+ prompt_Printf(arg->prompt, " CHAP81 = %s\n",
+ command_ShowNegval(lcp->cfg.chap81));
+#endif
+ prompt_Printf(arg->prompt, " LQR = %s\n",
+ command_ShowNegval(lcp->cfg.lqr));
+ prompt_Printf(arg->prompt, " LCP ECHO = %s\n",
+ lcp->cfg.echo ? "enabled" : "disabled");
+ prompt_Printf(arg->prompt, " PAP = %s\n",
+ command_ShowNegval(lcp->cfg.pap));
+ prompt_Printf(arg->prompt, " PROTOCOMP = %s\n",
+ command_ShowNegval(lcp->cfg.protocomp));
+
+ return 0;
+}
+
+static u_int32_t
+GenerateMagic(void)
+{
+ /* Generate random number which will be used as magic number */
+ randinit();
+ return random();
+}
+
+void
+lcp_SetupCallbacks(struct lcp *lcp)
+{
+ lcp->fsm.fn = &lcp_Callbacks;
+ lcp->fsm.FsmTimer.name = lcp_TimerNames[0];
+ lcp->fsm.OpenTimer.name = lcp_TimerNames[1];
+ lcp->fsm.StoppedTimer.name = lcp_TimerNames[2];
+}
+
+void
+lcp_Init(struct lcp *lcp, struct bundle *bundle, struct link *l,
+ const struct fsm_parent *parent)
+{
+ /* Initialise ourselves */
+ int mincode = parent ? 1 : LCP_MINMPCODE;
+
+ fsm_Init(&lcp->fsm, "LCP", PROTO_LCP, mincode, LCP_MAXCODE, LogLCP,
+ bundle, l, parent, &lcp_Callbacks, lcp_TimerNames);
+
+ lcp->cfg.mru = 0;
+ lcp->cfg.max_mru = MAX_MRU;
+ lcp->cfg.mtu = 0;
+ lcp->cfg.max_mtu = MAX_MTU;
+ lcp->cfg.accmap = 0;
+ lcp->cfg.openmode = 1;
+ lcp->cfg.lqrperiod = DEF_LQRPERIOD;
+ lcp->cfg.fsm.timeout = DEF_FSMRETRY;
+ lcp->cfg.fsm.maxreq = DEF_FSMTRIES;
+ lcp->cfg.fsm.maxtrm = DEF_FSMTRIES;
+
+ lcp->cfg.acfcomp = NEG_ENABLED|NEG_ACCEPTED;
+ lcp->cfg.chap05 = NEG_ACCEPTED;
+#ifndef NODES
+ lcp->cfg.chap80nt = NEG_ACCEPTED;
+ lcp->cfg.chap80lm = 0;
+ lcp->cfg.chap81 = NEG_ACCEPTED;
+#endif
+ lcp->cfg.lqr = NEG_ACCEPTED;
+ lcp->cfg.echo = 0;
+ lcp->cfg.pap = NEG_ACCEPTED;
+ lcp->cfg.protocomp = NEG_ENABLED|NEG_ACCEPTED;
+ *lcp->cfg.ident = '\0';
+
+ lcp_Setup(lcp, lcp->cfg.openmode);
+}
+
+void
+lcp_Setup(struct lcp *lcp, int openmode)
+{
+ struct physical *p = link2physical(lcp->fsm.link);
+
+ lcp->fsm.open_mode = openmode;
+
+ lcp->his_mru = DEF_MRU;
+ lcp->his_mrru = 0;
+ lcp->his_magic = 0;
+ lcp->his_lqrperiod = 0;
+ lcp->his_acfcomp = 0;
+ lcp->his_auth = 0;
+ lcp->his_authtype = 0;
+ lcp->his_callback.opmask = 0;
+ lcp->his_shortseq = 0;
+ lcp->mru_req = 0;
+
+ if ((lcp->want_mru = lcp->cfg.mru) == 0)
+ lcp->want_mru = DEF_MRU;
+ lcp->want_mrru = lcp->fsm.bundle->ncp.mp.cfg.mrru;
+ lcp->want_shortseq = IsEnabled(lcp->fsm.bundle->ncp.mp.cfg.shortseq) ? 1 : 0;
+ lcp->want_acfcomp = IsEnabled(lcp->cfg.acfcomp) ? 1 : 0;
+
+ if (lcp->fsm.parent) {
+ lcp->his_accmap = 0xffffffff;
+ lcp->want_accmap = lcp->cfg.accmap;
+ lcp->his_protocomp = 0;
+ lcp->want_protocomp = IsEnabled(lcp->cfg.protocomp) ? 1 : 0;
+ lcp->want_magic = GenerateMagic();
+
+ if (IsEnabled(lcp->cfg.chap05)) {
+ lcp->want_auth = PROTO_CHAP;
+ lcp->want_authtype = 0x05;
+#ifndef NODES
+ } else if (IsEnabled(lcp->cfg.chap80nt) ||
+ IsEnabled(lcp->cfg.chap80lm)) {
+ lcp->want_auth = PROTO_CHAP;
+ lcp->want_authtype = 0x80;
+ } else if (IsEnabled(lcp->cfg.chap81)) {
+ lcp->want_auth = PROTO_CHAP;
+ lcp->want_authtype = 0x81;
+#endif
+ } else if (IsEnabled(lcp->cfg.pap)) {
+ lcp->want_auth = PROTO_PAP;
+ lcp->want_authtype = 0;
+ } else {
+ lcp->want_auth = 0;
+ lcp->want_authtype = 0;
+ }
+
+ if (p->type != PHYS_DIRECT)
+ memcpy(&lcp->want_callback, &p->dl->cfg.callback,
+ sizeof(struct callback));
+ else
+ lcp->want_callback.opmask = 0;
+ lcp->want_lqrperiod = IsEnabled(lcp->cfg.lqr) ?
+ lcp->cfg.lqrperiod * 100 : 0;
+ } else {
+ lcp->his_accmap = lcp->want_accmap = 0;
+ lcp->his_protocomp = lcp->want_protocomp = 1;
+ lcp->want_magic = 0;
+ lcp->want_auth = 0;
+ lcp->want_authtype = 0;
+ lcp->want_callback.opmask = 0;
+ lcp->want_lqrperiod = 0;
+ }
+
+ lcp->his_reject = lcp->my_reject = 0;
+ lcp->auth_iwait = lcp->auth_ineed = 0;
+ lcp->LcpFailedMagic = 0;
+}
+
+static void
+LcpInitRestartCounter(struct fsm *fp, int what)
+{
+ /* Set fsm timer load */
+ struct lcp *lcp = fsm2lcp(fp);
+
+ fp->FsmTimer.load = lcp->cfg.fsm.timeout * SECTICKS;
+ switch (what) {
+ case FSM_REQ_TIMER:
+ fp->restart = lcp->cfg.fsm.maxreq;
+ break;
+ case FSM_TRM_TIMER:
+ fp->restart = lcp->cfg.fsm.maxtrm;
+ break;
+ default:
+ fp->restart = 1;
+ break;
+ }
+}
+
+static void
+LcpSendConfigReq(struct fsm *fp)
+{
+ /* Send config REQ please */
+ struct physical *p = link2physical(fp->link);
+ struct lcp *lcp = fsm2lcp(fp);
+ u_char buff[200];
+ struct fsm_opt *o;
+ struct mp *mp;
+ u_int16_t proto;
+ u_short maxmru;
+
+ if (!p) {
+ log_Printf(LogERROR, "%s: LcpSendConfigReq: Not a physical link !\n",
+ fp->link->name);
+ return;
+ }
+
+ o = (struct fsm_opt *)buff;
+ if (!physical_IsSync(p)) {
+ if (lcp->want_acfcomp && !REJECTED(lcp, TY_ACFCOMP))
+ INC_FSM_OPT(TY_ACFCOMP, 2, o);
+
+ if (lcp->want_protocomp && !REJECTED(lcp, TY_PROTOCOMP))
+ INC_FSM_OPT(TY_PROTOCOMP, 2, o);
+
+ if (!REJECTED(lcp, TY_ACCMAP)) {
+ ua_htonl(&lcp->want_accmap, o->data);
+ INC_FSM_OPT(TY_ACCMAP, 6, o);
+ }
+ }
+
+ maxmru = p ? physical_DeviceMTU(p) : 0;
+ if (lcp->cfg.max_mru && (!maxmru || maxmru > lcp->cfg.max_mru))
+ maxmru = lcp->cfg.max_mru;
+ if (maxmru && lcp->want_mru > maxmru) {
+ log_Printf(LogWARN, "%s: Reducing configured MRU from %u to %u\n",
+ fp->link->name, lcp->want_mru, maxmru);
+ lcp->want_mru = maxmru;
+ }
+ if (!REJECTED(lcp, TY_MRU)) {
+ ua_htons(&lcp->want_mru, o->data);
+ INC_FSM_OPT(TY_MRU, 4, o);
+ }
+
+ if (lcp->want_magic && !REJECTED(lcp, TY_MAGICNUM)) {
+ ua_htonl(&lcp->want_magic, o->data);
+ INC_FSM_OPT(TY_MAGICNUM, 6, o);
+ }
+
+ if (lcp->want_lqrperiod && !REJECTED(lcp, TY_QUALPROTO)) {
+ proto = PROTO_LQR;
+ ua_htons(&proto, o->data);
+ ua_htonl(&lcp->want_lqrperiod, o->data + 2);
+ INC_FSM_OPT(TY_QUALPROTO, 8, o);
+ }
+
+ switch (lcp->want_auth) {
+ case PROTO_PAP:
+ proto = PROTO_PAP;
+ ua_htons(&proto, o->data);
+ INC_FSM_OPT(TY_AUTHPROTO, 4, o);
+ break;
+
+ case PROTO_CHAP:
+ proto = PROTO_CHAP;
+ ua_htons(&proto, o->data);
+ o->data[2] = lcp->want_authtype;
+ INC_FSM_OPT(TY_AUTHPROTO, 5, o);
+ break;
+ }
+
+ if (!REJECTED(lcp, TY_CALLBACK)) {
+ if (lcp->want_callback.opmask & CALLBACK_BIT(CALLBACK_AUTH)) {
+ *o->data = CALLBACK_AUTH;
+ INC_FSM_OPT(TY_CALLBACK, 3, o);
+ } else if (lcp->want_callback.opmask & CALLBACK_BIT(CALLBACK_CBCP)) {
+ *o->data = CALLBACK_CBCP;
+ INC_FSM_OPT(TY_CALLBACK, 3, o);
+ } else if (lcp->want_callback.opmask & CALLBACK_BIT(CALLBACK_E164)) {
+ size_t sz = strlen(lcp->want_callback.msg);
+
+ if (sz > sizeof o->data - 1) {
+ sz = sizeof o->data - 1;
+ log_Printf(LogWARN, "Truncating E164 data to %zu octets (oops!)\n",
+ sz);
+ }
+ *o->data = CALLBACK_E164;
+ memcpy(o->data + 1, lcp->want_callback.msg, sz);
+ INC_FSM_OPT(TY_CALLBACK, sz + 3, o);
+ }
+ }
+
+ if (lcp->want_mrru && !REJECTED(lcp, TY_MRRU)) {
+ ua_htons(&lcp->want_mrru, o->data);
+ INC_FSM_OPT(TY_MRRU, 4, o);
+
+ if (lcp->want_shortseq && !REJECTED(lcp, TY_SHORTSEQ))
+ INC_FSM_OPT(TY_SHORTSEQ, 2, o);
+ }
+
+ mp = &lcp->fsm.bundle->ncp.mp;
+ if (mp->cfg.enddisc.class != 0 && IsEnabled(mp->cfg.negenddisc) &&
+ !REJECTED(lcp, TY_ENDDISC)) {
+ *o->data = mp->cfg.enddisc.class;
+ memcpy(o->data+1, mp->cfg.enddisc.address, mp->cfg.enddisc.len);
+ INC_FSM_OPT(TY_ENDDISC, mp->cfg.enddisc.len + 3, o);
+ }
+
+ fsm_Output(fp, CODE_CONFIGREQ, fp->reqid, buff, (u_char *)o - buff,
+ MB_LCPOUT);
+}
+
+void
+lcp_SendProtoRej(struct lcp *lcp, u_char *option, int count)
+{
+ /* Don't understand `option' */
+ fsm_Output(&lcp->fsm, CODE_PROTOREJ, lcp->fsm.reqid, option, count,
+ MB_LCPOUT);
+}
+
+int
+lcp_SendIdentification(struct lcp *lcp)
+{
+ static u_char id; /* Use a private id */
+ u_char msg[DEF_MRU - 3];
+ const char *argv[2];
+ char *exp[2];
+
+ if (*lcp->cfg.ident == '\0')
+ return 0;
+
+ argv[0] = lcp->cfg.ident;
+ argv[1] = NULL;
+
+ command_Expand(exp, 1, argv, lcp->fsm.bundle, 1, getpid());
+
+ ua_htonl(&lcp->want_magic, msg);
+ strncpy(msg + 4, exp[0], sizeof msg - 5);
+ msg[sizeof msg - 1] = '\0';
+
+ fsm_Output(&lcp->fsm, CODE_IDENT, id++, msg, 4 + strlen(msg + 4), MB_LCPOUT);
+ log_Printf(LogLCP, " MAGICNUM %08x\n", lcp->want_magic);
+ log_Printf(LogLCP, " TEXT %s\n", msg + 4);
+
+ command_Free(1, exp);
+ return 1;
+}
+
+void
+lcp_RecvIdentification(struct lcp *lcp, char *data)
+{
+ log_Printf(LogLCP, " MAGICNUM %08x\n", lcp->his_magic);
+ log_Printf(LogLCP, " TEXT %s\n", data);
+}
+
+static void
+LcpSentTerminateReq(struct fsm *fp __unused)
+{
+ /* Term REQ just sent by FSM */
+}
+
+static void
+LcpSendTerminateAck(struct fsm *fp, u_char id)
+{
+ /* Send Term ACK please */
+ struct physical *p = link2physical(fp->link);
+
+ if (p && p->dl->state == DATALINK_CBCP)
+ cbcp_ReceiveTerminateReq(p);
+
+ fsm_Output(fp, CODE_TERMACK, id, NULL, 0, MB_LCPOUT);
+}
+
+static void
+LcpLayerStart(struct fsm *fp)
+{
+ /* We're about to start up ! */
+ struct lcp *lcp = fsm2lcp(fp);
+
+ log_Printf(LogLCP, "%s: LayerStart\n", fp->link->name);
+ lcp->LcpFailedMagic = 0;
+ fp->more.reqs = fp->more.naks = fp->more.rejs = lcp->cfg.fsm.maxreq * 3;
+ lcp->mru_req = 0;
+}
+
+static void
+LcpLayerFinish(struct fsm *fp)
+{
+ /* We're now down */
+ log_Printf(LogLCP, "%s: LayerFinish\n", fp->link->name);
+}
+
+static int
+LcpLayerUp(struct fsm *fp)
+{
+ /* We're now up */
+ struct physical *p = link2physical(fp->link);
+ struct lcp *lcp = fsm2lcp(fp);
+
+ log_Printf(LogLCP, "%s: LayerUp\n", fp->link->name);
+ physical_SetAsyncParams(p, lcp->want_accmap, lcp->his_accmap);
+ lqr_Start(lcp);
+ hdlc_StartTimer(&p->hdlc);
+ fp->more.reqs = fp->more.naks = fp->more.rejs = lcp->cfg.fsm.maxreq * 3;
+
+ lcp_SendIdentification(lcp);
+
+ return 1;
+}
+
+static void
+LcpLayerDown(struct fsm *fp)
+{
+ /* About to come down */
+ struct physical *p = link2physical(fp->link);
+
+ log_Printf(LogLCP, "%s: LayerDown\n", fp->link->name);
+ hdlc_StopTimer(&p->hdlc);
+ lqr_StopTimer(p);
+ lcp_Setup(fsm2lcp(fp), 0);
+}
+
+static int
+E164ok(struct callback *cb, char *req, int sz)
+{
+ char list[sizeof cb->msg], *next;
+ int len;
+
+ if (!strcmp(cb->msg, "*"))
+ return 1;
+
+ strncpy(list, cb->msg, sizeof list - 1);
+ list[sizeof list - 1] = '\0';
+ for (next = strtok(list, ","); next; next = strtok(NULL, ",")) {
+ len = strlen(next);
+ if (sz == len && !memcmp(list, req, sz))
+ return 1;
+ }
+ return 0;
+}
+
+static int
+lcp_auth_nak(struct lcp *lcp, struct fsm_decode *dec)
+{
+ struct fsm_opt nak;
+
+ nak.hdr.id = TY_AUTHPROTO;
+
+ if (IsAccepted(lcp->cfg.pap)) {
+ nak.hdr.len = 4;
+ nak.data[0] = (unsigned char)(PROTO_PAP >> 8);
+ nak.data[1] = (unsigned char)PROTO_PAP;
+ fsm_nak(dec, &nak);
+ return 1;
+ }
+
+ nak.hdr.len = 5;
+ nak.data[0] = (unsigned char)(PROTO_CHAP >> 8);
+ nak.data[1] = (unsigned char)PROTO_CHAP;
+
+ if (IsAccepted(lcp->cfg.chap05)) {
+ nak.data[2] = 0x05;
+ fsm_nak(dec, &nak);
+#ifndef NODES
+ } else if (IsAccepted(lcp->cfg.chap80nt) ||
+ IsAccepted(lcp->cfg.chap80lm)) {
+ nak.data[2] = 0x80;
+ fsm_nak(dec, &nak);
+ } else if (IsAccepted(lcp->cfg.chap81)) {
+ nak.data[2] = 0x81;
+ fsm_nak(dec, &nak);
+#endif
+ } else {
+ return 0;
+ }
+
+ return 1;
+}
+
+static void
+LcpDecodeConfig(struct fsm *fp, u_char *cp, u_char *end, int mode_type,
+ struct fsm_decode *dec)
+{
+ /* Deal with incoming PROTO_LCP */
+ struct lcp *lcp = fsm2lcp(fp);
+ int pos, op, callback_req, chap_type;
+ size_t sz;
+ u_int32_t magic, accmap;
+ u_short mru, phmtu, maxmtu, maxmru, wantmtu, wantmru, proto;
+ struct lqrreq req;
+ char request[20], desc[22];
+ struct mp *mp;
+ struct physical *p = link2physical(fp->link);
+ struct fsm_opt *opt, nak;
+
+ sz = 0;
+ op = callback_req = 0;
+
+ while (end - cp >= (int)sizeof(opt->hdr)) {
+ if ((opt = fsm_readopt(&cp)) == NULL)
+ break;
+
+ snprintf(request, sizeof request, " %s[%d]", protoname(opt->hdr.id),
+ opt->hdr.len);
+
+ switch (opt->hdr.id) {
+ case TY_MRRU:
+ mp = &lcp->fsm.bundle->ncp.mp;
+ ua_ntohs(opt->data, &mru);
+ log_Printf(LogLCP, "%s %u\n", request, mru);
+
+ switch (mode_type) {
+ case MODE_REQ:
+ if (mp->cfg.mrru) {
+ if (REJECTED(lcp, TY_MRRU))
+ /* Ignore his previous reject so that we REQ next time */
+ lcp->his_reject &= ~(1 << opt->hdr.id);
+
+ if (mru > MAX_MRU) {
+ /* Push him down to MAX_MRU */
+ lcp->his_mrru = MAX_MRU;
+ nak.hdr.id = TY_MRRU;
+ nak.hdr.len = 4;
+ ua_htons(&lcp->his_mrru, nak.data);
+ fsm_nak(dec, &nak);
+ } else if (mru < MIN_MRU) {
+ /* Push him up to MIN_MRU */
+ lcp->his_mrru = MIN_MRU;
+ nak.hdr.id = TY_MRRU;
+ nak.hdr.len = 4;
+ ua_htons(&lcp->his_mrru, nak.data);
+ fsm_nak(dec, &nak);
+ } else {
+ lcp->his_mrru = mru;
+ fsm_ack(dec, opt);
+ }
+ break;
+ } else {
+ fsm_rej(dec, opt);
+ lcp->my_reject |= (1 << opt->hdr.id);
+ }
+ break;
+ case MODE_NAK:
+ if (mp->cfg.mrru) {
+ if (REJECTED(lcp, TY_MRRU))
+ /* Must have changed his mind ! */
+ lcp->his_reject &= ~(1 << opt->hdr.id);
+
+ if (mru > MAX_MRU)
+ lcp->want_mrru = MAX_MRU;
+ else if (mru < MIN_MRU)
+ lcp->want_mrru = MIN_MRU;
+ else
+ lcp->want_mrru = mru;
+ }
+ /* else we honour our config and don't send the suggested REQ */
+ break;
+ case MODE_REJ:
+ lcp->his_reject |= (1 << opt->hdr.id);
+ lcp->want_mrru = 0; /* Ah well, no multilink :-( */
+ break;
+ }
+ break;
+
+ case TY_MRU:
+ lcp->mru_req = 1;
+ ua_ntohs(opt->data, &mru);
+ log_Printf(LogLCP, "%s %d\n", request, mru);
+
+ switch (mode_type) {
+ case MODE_REQ:
+ maxmtu = p ? physical_DeviceMTU(p) : 0;
+ if (lcp->cfg.max_mtu && (!maxmtu || maxmtu > lcp->cfg.max_mtu))
+ maxmtu = lcp->cfg.max_mtu;
+ wantmtu = lcp->cfg.mtu;
+ if (maxmtu && wantmtu > maxmtu) {
+ log_Printf(LogWARN, "%s: Reducing configured MTU from %u to %u\n",
+ fp->link->name, wantmtu, maxmtu);
+ wantmtu = maxmtu;
+ }
+
+ if (maxmtu && mru > maxmtu) {
+ lcp->his_mru = maxmtu;
+ nak.hdr.id = TY_MRU;
+ nak.hdr.len = 4;
+ ua_htons(&lcp->his_mru, nak.data);
+ fsm_nak(dec, &nak);
+ } else if (wantmtu && mru < wantmtu) {
+ /* Push him up to MTU or MIN_MRU */
+ lcp->his_mru = wantmtu;
+ nak.hdr.id = TY_MRU;
+ nak.hdr.len = 4;
+ ua_htons(&lcp->his_mru, nak.data);
+ fsm_nak(dec, &nak);
+ } else {
+ lcp->his_mru = mru;
+ fsm_ack(dec, opt);
+ }
+ break;
+ case MODE_NAK:
+ maxmru = p ? physical_DeviceMTU(p) : 0;
+ if (lcp->cfg.max_mru && (!maxmru || maxmru > lcp->cfg.max_mru))
+ maxmru = lcp->cfg.max_mru;
+ wantmru = lcp->cfg.mru > maxmru ? maxmru : lcp->cfg.mru;
+
+ if (wantmru && mru > wantmru)
+ lcp->want_mru = wantmru;
+ else if (mru > maxmru)
+ lcp->want_mru = maxmru;
+ else if (mru < MIN_MRU)
+ lcp->want_mru = MIN_MRU;
+ else
+ lcp->want_mru = mru;
+ break;
+ case MODE_REJ:
+ lcp->his_reject |= (1 << opt->hdr.id);
+ /* Set the MTU to what we want anyway - the peer won't care! */
+ if (lcp->his_mru > lcp->want_mru)
+ lcp->his_mru = lcp->want_mru;
+ break;
+ }
+ break;
+
+ case TY_ACCMAP:
+ ua_ntohl(opt->data, &accmap);
+ log_Printf(LogLCP, "%s 0x%08lx\n", request, (u_long)accmap);
+
+ switch (mode_type) {
+ case MODE_REQ:
+ lcp->his_accmap = accmap;
+ fsm_ack(dec, opt);
+ break;
+ case MODE_NAK:
+ lcp->want_accmap = accmap;
+ break;
+ case MODE_REJ:
+ lcp->his_reject |= (1 << opt->hdr.id);
+ break;
+ }
+ break;
+
+ case TY_AUTHPROTO:
+ ua_ntohs(opt->data, &proto);
+ chap_type = opt->hdr.len == 5 ? opt->data[2] : 0;
+
+ log_Printf(LogLCP, "%s 0x%04x (%s)\n", request, proto,
+ Auth2Nam(proto, chap_type));
+
+ switch (mode_type) {
+ case MODE_REQ:
+ switch (proto) {
+ case PROTO_PAP:
+ if (opt->hdr.len == 4 && IsAccepted(lcp->cfg.pap)) {
+ lcp->his_auth = proto;
+ lcp->his_authtype = 0;
+ fsm_ack(dec, opt);
+ } else if (!lcp_auth_nak(lcp, dec)) {
+ lcp->my_reject |= (1 << opt->hdr.id);
+ fsm_rej(dec, opt);
+ }
+ break;
+
+ case PROTO_CHAP:
+ if ((chap_type == 0x05 && IsAccepted(lcp->cfg.chap05))
+#ifndef NODES
+ || (chap_type == 0x80 && (IsAccepted(lcp->cfg.chap80nt) ||
+ (IsAccepted(lcp->cfg.chap80lm))))
+ || (chap_type == 0x81 && IsAccepted(lcp->cfg.chap81))
+#endif
+ ) {
+ lcp->his_auth = proto;
+ lcp->his_authtype = chap_type;
+ fsm_ack(dec, opt);
+ } else {
+#ifdef NODES
+ if (chap_type == 0x80) {
+ log_Printf(LogWARN, "CHAP 0x80 not available without DES\n");
+ } else if (chap_type == 0x81) {
+ log_Printf(LogWARN, "CHAP 0x81 not available without DES\n");
+ } else
+#endif
+ if (chap_type != 0x05)
+ log_Printf(LogWARN, "%s not supported\n",
+ Auth2Nam(PROTO_CHAP, chap_type));
+
+ if (!lcp_auth_nak(lcp, dec)) {
+ lcp->my_reject |= (1 << opt->hdr.id);
+ fsm_rej(dec, opt);
+ }
+ }
+ break;
+
+ default:
+ log_Printf(LogLCP, "%s 0x%04x - not recognised\n",
+ request, proto);
+ if (!lcp_auth_nak(lcp, dec)) {
+ lcp->my_reject |= (1 << opt->hdr.id);
+ fsm_rej(dec, opt);
+ }
+ break;
+ }
+ break;
+
+ case MODE_NAK:
+ switch (proto) {
+ case PROTO_PAP:
+ if (IsEnabled(lcp->cfg.pap)) {
+ lcp->want_auth = PROTO_PAP;
+ lcp->want_authtype = 0;
+ } else {
+ log_Printf(LogLCP, "Peer will only send PAP (not enabled)\n");
+ lcp->his_reject |= (1 << opt->hdr.id);
+ }
+ break;
+ case PROTO_CHAP:
+ if (chap_type == 0x05 && IsEnabled(lcp->cfg.chap05)) {
+ lcp->want_auth = PROTO_CHAP;
+ lcp->want_authtype = 0x05;
+#ifndef NODES
+ } else if (chap_type == 0x80 && (IsEnabled(lcp->cfg.chap80nt) ||
+ IsEnabled(lcp->cfg.chap80lm))) {
+ lcp->want_auth = PROTO_CHAP;
+ lcp->want_authtype = 0x80;
+ } else if (chap_type == 0x81 && IsEnabled(lcp->cfg.chap81)) {
+ lcp->want_auth = PROTO_CHAP;
+ lcp->want_authtype = 0x81;
+#endif
+ } else {
+#ifdef NODES
+ if (chap_type == 0x80) {
+ log_Printf(LogLCP, "Peer will only send MSCHAP (not available"
+ " without DES)\n");
+ } else if (chap_type == 0x81) {
+ log_Printf(LogLCP, "Peer will only send MSCHAPV2 (not available"
+ " without DES)\n");
+ } else
+#endif
+ log_Printf(LogLCP, "Peer will only send %s (not %s)\n",
+ Auth2Nam(PROTO_CHAP, chap_type),
+#ifndef NODES
+ (chap_type == 0x80 || chap_type == 0x81) ? "configured" :
+#endif
+ "supported");
+ lcp->his_reject |= (1 << opt->hdr.id);
+ }
+ break;
+ default:
+ /* We've been NAK'd with something we don't understand :-( */
+ lcp->his_reject |= (1 << opt->hdr.id);
+ break;
+ }
+ break;
+
+ case MODE_REJ:
+ lcp->his_reject |= (1 << opt->hdr.id);
+ break;
+ }
+ break;
+
+ case TY_QUALPROTO:
+ memcpy(&req, opt, sizeof req);
+ log_Printf(LogLCP, "%s proto %x, interval %lums\n",
+ request, ntohs(req.proto), (u_long)ntohl(req.period) * 10);
+ switch (mode_type) {
+ case MODE_REQ:
+ if (ntohs(req.proto) != PROTO_LQR || !IsAccepted(lcp->cfg.lqr)) {
+ fsm_rej(dec, opt);
+ lcp->my_reject |= (1 << opt->hdr.id);
+ } else {
+ lcp->his_lqrperiod = ntohl(req.period);
+ if (lcp->his_lqrperiod < MIN_LQRPERIOD * 100)
+ lcp->his_lqrperiod = MIN_LQRPERIOD * 100;
+ req.period = htonl(lcp->his_lqrperiod);
+ fsm_ack(dec, opt);
+ }
+ break;
+ case MODE_NAK:
+ lcp->want_lqrperiod = ntohl(req.period);
+ break;
+ case MODE_REJ:
+ lcp->his_reject |= (1 << opt->hdr.id);
+ break;
+ }
+ break;
+
+ case TY_MAGICNUM:
+ ua_ntohl(opt->data, &magic);
+ log_Printf(LogLCP, "%s 0x%08lx\n", request, (u_long)magic);
+
+ switch (mode_type) {
+ case MODE_REQ:
+ if (lcp->want_magic) {
+ /* Validate magic number */
+ if (magic == lcp->want_magic) {
+ sigset_t emptyset;
+
+ log_Printf(LogLCP, "Magic is same (%08lx) - %d times\n",
+ (u_long)magic, ++lcp->LcpFailedMagic);
+ lcp->want_magic = GenerateMagic();
+ fsm_nak(dec, opt);
+ ualarm(TICKUNIT * (4 + 4 * lcp->LcpFailedMagic), 0);
+ sigemptyset(&emptyset);
+ sigsuspend(&emptyset);
+ } else {
+ lcp->his_magic = magic;
+ lcp->LcpFailedMagic = 0;
+ fsm_ack(dec, opt);
+ }
+ } else {
+ lcp->my_reject |= (1 << opt->hdr.id);
+ fsm_rej(dec, opt);
+ }
+ break;
+ case MODE_NAK:
+ log_Printf(LogLCP, " Magic 0x%08lx is NAKed!\n", (u_long)magic);
+ lcp->want_magic = GenerateMagic();
+ break;
+ case MODE_REJ:
+ log_Printf(LogLCP, " Magic 0x%08x is REJected!\n", magic);
+ lcp->want_magic = 0;
+ lcp->his_reject |= (1 << opt->hdr.id);
+ break;
+ }
+ break;
+
+ case TY_PROTOCOMP:
+ log_Printf(LogLCP, "%s\n", request);
+
+ switch (mode_type) {
+ case MODE_REQ:
+ if (IsAccepted(lcp->cfg.protocomp)) {
+ lcp->his_protocomp = 1;
+ fsm_ack(dec, opt);
+ } else {
+#ifdef OLDMST
+ /* MorningStar before v1.3 needs NAK */
+ fsm_nak(dec, opt);
+#else
+ fsm_rej(dec, opt);
+ lcp->my_reject |= (1 << opt->hdr.id);
+#endif
+ }
+ break;
+ case MODE_NAK:
+ case MODE_REJ:
+ lcp->want_protocomp = 0;
+ lcp->his_reject |= (1 << opt->hdr.id);
+ break;
+ }
+ break;
+
+ case TY_ACFCOMP:
+ log_Printf(LogLCP, "%s\n", request);
+ switch (mode_type) {
+ case MODE_REQ:
+ if (IsAccepted(lcp->cfg.acfcomp)) {
+ lcp->his_acfcomp = 1;
+ fsm_ack(dec, opt);
+ } else {
+#ifdef OLDMST
+ /* MorningStar before v1.3 needs NAK */
+ fsm_nak(dec, opt);
+#else
+ fsm_rej(dec, opt);
+ lcp->my_reject |= (1 << opt->hdr.id);
+#endif
+ }
+ break;
+ case MODE_NAK:
+ case MODE_REJ:
+ lcp->want_acfcomp = 0;
+ lcp->his_reject |= (1 << opt->hdr.id);
+ break;
+ }
+ break;
+
+ case TY_SDP:
+ log_Printf(LogLCP, "%s\n", request);
+ switch (mode_type) {
+ case MODE_REQ:
+ case MODE_NAK:
+ case MODE_REJ:
+ break;
+ }
+ break;
+
+ case TY_CALLBACK:
+ if (opt->hdr.len == 2) {
+ op = CALLBACK_NONE;
+ sz = 0;
+ } else {
+ op = (int)opt->data[0];
+ sz = opt->hdr.len - 3;
+ }
+ switch (op) {
+ case CALLBACK_AUTH:
+ log_Printf(LogLCP, "%s Auth\n", request);
+ break;
+ case CALLBACK_DIALSTRING:
+ log_Printf(LogLCP, "%s Dialstring %.*s\n", request, (int)sz,
+ opt->data + 1);
+ break;
+ case CALLBACK_LOCATION:
+ log_Printf(LogLCP, "%s Location %.*s\n", request, (int)sz,
+ opt->data + 1);
+ break;
+ case CALLBACK_E164:
+ log_Printf(LogLCP, "%s E.164 (%.*s)\n", request, (int)sz,
+ opt->data + 1);
+ break;
+ case CALLBACK_NAME:
+ log_Printf(LogLCP, "%s Name %.*s\n", request, (int)sz,
+ opt->data + 1);
+ break;
+ case CALLBACK_CBCP:
+ log_Printf(LogLCP, "%s CBCP\n", request);
+ break;
+ default:
+ log_Printf(LogLCP, "%s ???\n", request);
+ break;
+ }
+
+ switch (mode_type) {
+ case MODE_REQ:
+ callback_req = 1;
+ if (p->type != PHYS_DIRECT) {
+ fsm_rej(dec, opt);
+ lcp->my_reject |= (1 << opt->hdr.id);
+ }
+ nak.hdr.id = opt->hdr.id;
+ nak.hdr.len = 3;
+ if ((p->dl->cfg.callback.opmask & CALLBACK_BIT(op)) &&
+ (op != CALLBACK_AUTH || p->link.lcp.want_auth) &&
+ (op != CALLBACK_E164 ||
+ E164ok(&p->dl->cfg.callback, opt->data + 1, sz))) {
+ lcp->his_callback.opmask = CALLBACK_BIT(op);
+ if (sz > sizeof lcp->his_callback.msg - 1) {
+ sz = sizeof lcp->his_callback.msg - 1;
+ log_Printf(LogWARN, "Truncating option arg to %zu octets\n", sz);
+ }
+ memcpy(lcp->his_callback.msg, opt->data + 1, sz);
+ lcp->his_callback.msg[sz] = '\0';
+ fsm_ack(dec, opt);
+ } else if ((p->dl->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_AUTH)) &&
+ p->link.lcp.auth_ineed) {
+ nak.data[0] = CALLBACK_AUTH;
+ fsm_nak(dec, &nak);
+ } else if (p->dl->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_CBCP)) {
+ nak.data[0] = CALLBACK_CBCP;
+ fsm_nak(dec, &nak);
+ } else if (p->dl->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_E164)) {
+ nak.data[0] = CALLBACK_E164;
+ fsm_nak(dec, &nak);
+ } else if (p->dl->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_AUTH)) {
+ log_Printf(LogWARN, "Cannot insist on auth callback without"
+ " PAP or CHAP enabled !\n");
+ nak.data[0] = 2;
+ fsm_nak(dec, &nak);
+ } else {
+ lcp->my_reject |= (1 << opt->hdr.id);
+ fsm_rej(dec, opt);
+ }
+ break;
+ case MODE_NAK:
+ /* We don't do what he NAKs with, we do things in our preferred order */
+ if (lcp->want_callback.opmask & CALLBACK_BIT(CALLBACK_AUTH))
+ lcp->want_callback.opmask &= ~CALLBACK_BIT(CALLBACK_AUTH);
+ else if (lcp->want_callback.opmask & CALLBACK_BIT(CALLBACK_CBCP))
+ lcp->want_callback.opmask &= ~CALLBACK_BIT(CALLBACK_CBCP);
+ else if (lcp->want_callback.opmask & CALLBACK_BIT(CALLBACK_E164))
+ lcp->want_callback.opmask &= ~CALLBACK_BIT(CALLBACK_E164);
+ if (lcp->want_callback.opmask == CALLBACK_BIT(CALLBACK_NONE)) {
+ log_Printf(LogPHASE, "Peer NAKd all callbacks, trying none\n");
+ lcp->want_callback.opmask = 0;
+ } else if (!lcp->want_callback.opmask) {
+ log_Printf(LogPHASE, "Peer NAKd last configured callback\n");
+ fsm_Close(&lcp->fsm);
+ }
+ break;
+ case MODE_REJ:
+ if (lcp->want_callback.opmask & CALLBACK_BIT(CALLBACK_NONE)) {
+ lcp->his_reject |= (1 << opt->hdr.id);
+ lcp->want_callback.opmask = 0;
+ } else {
+ log_Printf(LogPHASE, "Peer rejected *required* callback\n");
+ fsm_Close(&lcp->fsm);
+ }
+ break;
+ }
+ break;
+
+ case TY_SHORTSEQ:
+ mp = &lcp->fsm.bundle->ncp.mp;
+ log_Printf(LogLCP, "%s\n", request);
+
+ switch (mode_type) {
+ case MODE_REQ:
+ if (lcp->want_mrru && IsAccepted(mp->cfg.shortseq)) {
+ lcp->his_shortseq = 1;
+ fsm_ack(dec, opt);
+ } else {
+ fsm_rej(dec, opt);
+ lcp->my_reject |= (1 << opt->hdr.id);
+ }
+ break;
+ case MODE_NAK:
+ /*
+ * He's trying to get us to ask for short sequence numbers.
+ * We ignore the NAK and honour our configuration file instead.
+ */
+ break;
+ case MODE_REJ:
+ lcp->his_reject |= (1 << opt->hdr.id);
+ lcp->want_shortseq = 0; /* For when we hit MP */
+ break;
+ }
+ break;
+
+ case TY_ENDDISC:
+ mp = &lcp->fsm.bundle->ncp.mp;
+ log_Printf(LogLCP, "%s %s\n", request,
+ mp_Enddisc(opt->data[0], opt->data + 1, opt->hdr.len - 3));
+ switch (mode_type) {
+ case MODE_REQ:
+ if (!p) {
+ log_Printf(LogLCP, " ENDDISC rejected - not a physical link\n");
+ fsm_rej(dec, opt);
+ lcp->my_reject |= (1 << opt->hdr.id);
+ } else if (!IsAccepted(mp->cfg.negenddisc)) {
+ lcp->my_reject |= (1 << opt->hdr.id);
+ fsm_rej(dec, opt);
+ } else if (opt->hdr.len < sizeof p->dl->peer.enddisc.address + 3 &&
+ opt->data[0] <= MAX_ENDDISC_CLASS) {
+ p->dl->peer.enddisc.class = opt->data[0];
+ p->dl->peer.enddisc.len = opt->hdr.len - 3;
+ memcpy(p->dl->peer.enddisc.address, opt->data + 1, opt->hdr.len - 3);
+ p->dl->peer.enddisc.address[opt->hdr.len - 3] = '\0';
+ /* XXX: If mp->active, compare and NAK with mp->peer ? */
+ fsm_ack(dec, opt);
+ } else {
+ if (opt->data[0] > MAX_ENDDISC_CLASS)
+ log_Printf(LogLCP, " ENDDISC rejected - unrecognised class %d\n",
+ opt->data[0]);
+ else
+ log_Printf(LogLCP, " ENDDISC rejected - local max length is %ld\n",
+ (long)(sizeof p->dl->peer.enddisc.address - 1));
+ fsm_rej(dec, opt);
+ lcp->my_reject |= (1 << opt->hdr.id);
+ }
+ break;
+
+ case MODE_NAK: /* Treat this as a REJ, we don't vary our disc (yet) */
+ case MODE_REJ:
+ lcp->his_reject |= (1 << opt->hdr.id);
+ break;
+ }
+ break;
+
+ default:
+ sz = (sizeof desc - 2) / 2;
+ if (sz + 2 > opt->hdr.len)
+ sz = opt->hdr.len - 2;
+ pos = 0;
+ desc[0] = sz ? ' ' : '\0';
+ for (pos = 0; sz--; pos++)
+ sprintf(desc+(pos<<1)+1, "%02x", opt->data[pos]);
+
+ log_Printf(LogLCP, "%s%s\n", request, desc);
+
+ if (mode_type == MODE_REQ) {
+ fsm_rej(dec, opt);
+ lcp->my_reject |= (1 << opt->hdr.id);
+ }
+ break;
+ }
+ }
+
+ if (mode_type != MODE_NOP) {
+ if (mode_type == MODE_REQ && p && p->type == PHYS_DIRECT &&
+ p->dl->cfg.callback.opmask && !callback_req &&
+ !(p->dl->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_NONE))) {
+ /* We *REQUIRE* that the peer requests callback */
+ nak.hdr.id = TY_CALLBACK;
+ nak.hdr.len = 3;
+ if ((p->dl->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_AUTH)) &&
+ p->link.lcp.want_auth)
+ nak.data[0] = CALLBACK_AUTH;
+ else if (p->dl->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_CBCP))
+ nak.data[0] = CALLBACK_CBCP;
+ else if (p->dl->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_E164))
+ nak.data[0] = CALLBACK_E164;
+ else {
+ log_Printf(LogWARN, "Cannot insist on auth callback without"
+ " PAP or CHAP enabled !\n");
+ nak.hdr.len = 2; /* XXX: Silly ! */
+ }
+ fsm_nak(dec, &nak);
+ }
+ if (mode_type == MODE_REQ && !lcp->mru_req) {
+ mru = DEF_MRU;
+ phmtu = p ? physical_DeviceMTU(p) : 0;
+ if (phmtu && mru > phmtu)
+ mru = phmtu;
+ if (mru > lcp->cfg.max_mtu)
+ mru = lcp->cfg.max_mtu;
+ if (mru < DEF_MRU) {
+ /* Don't let the peer use the default MRU */
+ lcp->his_mru = lcp->cfg.mtu && lcp->cfg.mtu < mru ? lcp->cfg.mtu : mru;
+ nak.hdr.id = TY_MRU;
+ nak.hdr.len = 4;
+ ua_htons(&lcp->his_mru, nak.data);
+ fsm_nak(dec, &nak);
+ lcp->mru_req = 1; /* Don't keep NAK'ing this */
+ }
+ }
+ fsm_opt_normalise(dec);
+ }
+}
+
+extern struct mbuf *
+lcp_Input(struct bundle *bundle __unused, struct link *l, struct mbuf *bp)
+{
+ /* Got PROTO_LCP from link */
+ m_settype(bp, MB_LCPIN);
+ fsm_Input(&l->lcp.fsm, bp);
+ return NULL;
+}
diff --git a/usr.sbin/ppp/lcp.h b/usr.sbin/ppp/lcp.h
new file mode 100644
index 0000000..e0382a8
--- /dev/null
+++ b/usr.sbin/ppp/lcp.h
@@ -0,0 +1,143 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+/* callback::opmask values */
+#define CALLBACK_AUTH (0)
+#define CALLBACK_DIALSTRING (1) /* Don't do this */
+#define CALLBACK_LOCATION (2) /* Don't do this */
+#define CALLBACK_E164 (3)
+#define CALLBACK_NAME (4) /* Don't do this */
+#define CALLBACK_CBCP (6)
+#define CALLBACK_NONE (14) /* No callback is ok */
+
+#define CALLBACK_BIT(n) ((n) < 0 ? 0 : 1 << (n))
+
+struct callback {
+ int opmask; /* want these types of callback */
+ char msg[SCRIPT_LEN]; /* with this data (E.164) */
+};
+
+#define REJECTED(p, x) ((p)->his_reject & (1<<(x)))
+
+struct lcp {
+ struct fsm fsm; /* The finite state machine */
+ u_int16_t his_mru; /* Peers maximum packet size */
+ u_int16_t his_mrru; /* Peers maximum reassembled packet size (MP) */
+ u_int32_t his_accmap; /* Peeers async char control map */
+ u_int32_t his_magic; /* Peers magic number */
+ u_int32_t his_lqrperiod; /* Peers LQR frequency (100ths of seconds) */
+ u_short his_auth; /* Peer wants this type of authentication */
+ u_char his_authtype; /* Fifth octet of REQ/NAK/REJ */
+ struct callback his_callback; /* Peer wants callback ? */
+ unsigned his_shortseq : 1; /* Peer would like only 12bit seqs (MP) */
+ unsigned his_protocomp : 1; /* Does peer do Protocol field compression */
+ unsigned his_acfcomp : 1; /* Does peer do addr & cntrl fld compression */
+ unsigned mru_req : 1; /* Has the peer requested an MRU */
+
+ u_short want_mru; /* Our maximum packet size */
+ u_short want_mrru; /* Our maximum reassembled packet size (MP) */
+ u_int32_t want_accmap; /* Our async char control map */
+ u_int32_t want_magic; /* Our magic number */
+ u_int32_t want_lqrperiod; /* Our LQR frequency (100ths of seconds) */
+ u_short want_auth; /* We want this type of authentication */
+ u_char want_authtype; /* Fifth octet of REQ/NAK/REJ */
+ struct callback want_callback;/* We want callback ? */
+ unsigned want_shortseq : 1; /* I'd like only 12bit seqs (MP) */
+ unsigned want_protocomp : 1; /* Do we do protocol field compression */
+ unsigned want_acfcomp : 1; /* Do we do addr & cntrl fld compression */
+
+ u_int32_t his_reject; /* Request codes rejected by peer */
+ u_int32_t my_reject; /* Request codes I have rejected */
+
+ u_short auth_iwait; /* I must authenticate to the peer */
+ u_short auth_ineed; /* I require that the peer authenticates */
+
+ int LcpFailedMagic; /* Number of `magic is same' errors */
+
+ struct {
+ u_short mru; /* Preferred MRU value */
+ u_short max_mru; /* Preferred MRU value */
+ u_short mtu; /* Preferred MTU */
+ u_short max_mtu; /* Preferred MTU */
+ u_int32_t accmap; /* Initial ACCMAP value */
+ int openmode; /* when to start CFG REQs */
+ u_int32_t lqrperiod; /* LQR frequency (seconds) */
+ struct fsm_retry fsm; /* How often/frequently to resend requests */
+ unsigned acfcomp : 2; /* Address & Control Field Compression neg */
+ unsigned chap05 : 2; /* Challenge Handshake Authentication proto */
+#ifndef NODES
+ unsigned chap80nt : 2; /* Microsoft (NT) CHAP */
+ unsigned chap80lm : 2; /* Microsoft (LANMan) CHAP */
+ unsigned chap81 : 2; /* Microsoft CHAP v2 */
+#endif
+ unsigned lqr : 2; /* Link Quality Report */
+ unsigned echo : 1; /* Send echo Requests */
+ unsigned pap : 2; /* Password Authentication protocol */
+ unsigned protocomp : 2; /* Protocol field compression */
+ char ident[DEF_MRU - 7]; /* SendIdentification() data */
+ } cfg;
+};
+
+#define LCP_MAXCODE CODE_IDENT
+#define LCP_MINMPCODE CODE_CODEREJ
+
+#define TY_MRU 1 /* Maximum-Receive-Unit */
+#define TY_ACCMAP 2 /* Async-Control-Character-Map */
+#define TY_AUTHPROTO 3 /* Authentication-Protocol */
+#define TY_QUALPROTO 4 /* Quality-Protocol */
+#define TY_MAGICNUM 5 /* Magic-Number */
+#define TY_RESERVED 6 /* RESERVED */
+#define TY_PROTOCOMP 7 /* Protocol-Field-Compression */
+#define TY_ACFCOMP 8 /* Address-and-Control-Field-Compression */
+#define TY_FCSALT 9 /* FCS-Alternatives */
+#define TY_SDP 10 /* Self-Describing-Padding */
+#define TY_CALLBACK 13 /* Callback */
+#define TY_CFRAMES 15 /* Compound-frames */
+#define TY_MRRU 17 /* Max Reconstructed Receive Unit (MP) */
+#define TY_SHORTSEQ 18 /* Want short seqs (12bit) please (see mp.h) */
+#define TY_ENDDISC 19 /* Endpoint discriminator */
+
+struct mbuf;
+struct link;
+struct bundle;
+struct cmdargs;
+
+#define fsm2lcp(fp) (fp->proto == PROTO_LCP ? (struct lcp *)fp : NULL)
+
+extern void lcp_Init(struct lcp *, struct bundle *, struct link *,
+ const struct fsm_parent *);
+extern void lcp_Setup(struct lcp *, int);
+
+extern void lcp_SendProtoRej(struct lcp *, u_char *, int);
+extern int lcp_SendIdentification(struct lcp *);
+extern void lcp_RecvIdentification(struct lcp *, char *);
+extern int lcp_ReportStatus(struct cmdargs const *);
+extern struct mbuf *lcp_Input(struct bundle *, struct link *, struct mbuf *);
+extern void lcp_SetupCallbacks(struct lcp *);
diff --git a/usr.sbin/ppp/link.c b/usr.sbin/ppp/link.c
new file mode 100644
index 0000000..dc5507d
--- /dev/null
+++ b/usr.sbin/ppp/link.c
@@ -0,0 +1,412 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.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/types.h>
+#include <netinet/in_systm.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <termios.h>
+
+#include "defs.h"
+#include "layer.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "proto.h"
+#include "fsm.h"
+#include "descriptor.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "prompt.h"
+#include "async.h"
+#include "physical.h"
+#include "mp.h"
+#include "iplist.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ip.h"
+#include "ipcp.h"
+#include "ipv6cp.h"
+#include "auth.h"
+#include "pap.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "command.h"
+
+static void Despatch(struct bundle *, struct link *, struct mbuf *, u_short);
+
+static inline void
+link_AddInOctets(struct link *l, int n)
+{
+ if (l->stats.gather) {
+ throughput_addin(&l->stats.total, n);
+ if (l->stats.parent)
+ throughput_addin(l->stats.parent, n);
+ }
+}
+
+static inline void
+link_AddOutOctets(struct link *l, int n)
+{
+ if (l->stats.gather) {
+ throughput_addout(&l->stats.total, n);
+ if (l->stats.parent)
+ throughput_addout(l->stats.parent, n);
+ }
+}
+
+void
+link_SequenceQueue(struct link *l)
+{
+ struct mqueue *queue, *highest;
+
+ log_Printf(LogDEBUG, "link_SequenceQueue\n");
+
+ highest = LINK_HIGHQ(l);
+ for (queue = l->Queue; queue < highest; queue++)
+ while (queue->len)
+ m_enqueue(highest, m_dequeue(queue));
+}
+
+void
+link_DeleteQueue(struct link *l)
+{
+ struct mqueue *queue, *highest;
+
+ highest = LINK_HIGHQ(l);
+ for (queue = l->Queue; queue <= highest; queue++)
+ while (queue->top)
+ m_freem(m_dequeue(queue));
+}
+
+size_t
+link_QueueLen(struct link *l)
+{
+ unsigned i;
+ size_t len;
+
+ for (i = 0, len = 0; i < LINK_QUEUES(l); i++)
+ len += l->Queue[i].len;
+
+ return len;
+}
+
+size_t
+link_QueueBytes(struct link *l)
+{
+ unsigned i;
+ size_t len, bytes;
+ struct mbuf *m;
+
+ bytes = 0;
+ for (i = 0, len = 0; i < LINK_QUEUES(l); i++) {
+ len = l->Queue[i].len;
+ m = l->Queue[i].top;
+ while (len--) {
+ bytes += m_length(m);
+ m = m->m_nextpkt;
+ }
+ }
+
+ return bytes;
+}
+
+void
+link_PendingLowPriorityData(struct link *l, size_t *pkts, size_t *octets)
+{
+ struct mqueue *queue, *highest;
+ struct mbuf *m;
+ size_t len;
+
+ /*
+ * This is all rfc1989 stuff... because our LQR packet is going to bypass
+ * everything that's not in the highest priority queue, we must be able to
+ * subtract that data from our outgoing packet/octet counts. However,
+ * we've already async-encoded our data at this point, but the async
+ * encodings MUSTn't be a part of the LQR-reported payload :( So, we have
+ * the async layer record how much it's padded the packet in the mbuf's
+ * priv field, and when we calculate our outgoing LQR values we subtract
+ * this value for each packet from the octet count sent.
+ */
+
+ highest = LINK_HIGHQ(l);
+ *pkts = *octets = 0;
+ for (queue = l->Queue; queue < highest; queue++) {
+ len = queue->len;
+ *pkts += len;
+ for (m = queue->top; len--; m = m->m_nextpkt)
+ *octets += m_length(m) - m->priv;
+ }
+}
+
+struct mbuf *
+link_Dequeue(struct link *l)
+{
+ int pri;
+ struct mbuf *bp;
+
+ for (bp = NULL, pri = LINK_QUEUES(l) - 1; pri >= 0; pri--)
+ if (l->Queue[pri].len) {
+ bp = m_dequeue(l->Queue + pri);
+ log_Printf(LogDEBUG, "link_Dequeue: Dequeued from queue %d,"
+ " containing %lu more packets\n", pri,
+ (u_long)l->Queue[pri].len);
+ break;
+ }
+
+ return bp;
+}
+
+static struct protostatheader {
+ u_short number;
+ const char *name;
+} ProtocolStat[NPROTOSTAT] = {
+ { PROTO_IP, "IP" },
+ { PROTO_VJUNCOMP, "VJ_UNCOMP" },
+ { PROTO_VJCOMP, "VJ_COMP" },
+ { PROTO_COMPD, "COMPD" },
+ { PROTO_ICOMPD, "ICOMPD" },
+ { PROTO_LCP, "LCP" },
+ { PROTO_IPCP, "IPCP" },
+ { PROTO_CCP, "CCP" },
+ { PROTO_PAP, "PAP" },
+ { PROTO_LQR, "LQR" },
+ { PROTO_CHAP, "CHAP" },
+ { PROTO_MP, "MULTILINK" },
+ { 0, "Others" }
+};
+
+void
+link_ProtocolRecord(struct link *l, u_short proto, int type)
+{
+ int i;
+
+ for (i = 0; i < NPROTOSTAT; i++)
+ if (ProtocolStat[i].number == proto)
+ break;
+
+ if (type == PROTO_IN)
+ l->proto_in[i]++;
+ else
+ l->proto_out[i]++;
+}
+
+void
+link_ReportProtocolStatus(struct link *l, struct prompt *prompt)
+{
+ int i;
+
+ prompt_Printf(prompt, " Protocol in out "
+ "Protocol in out\n");
+ for (i = 0; i < NPROTOSTAT; i++) {
+ prompt_Printf(prompt, " %-9s: %8lu, %8lu",
+ ProtocolStat[i].name, l->proto_in[i], l->proto_out[i]);
+ if ((i % 2) == 0)
+ prompt_Printf(prompt, "\n");
+ }
+ if (!(i % 2))
+ prompt_Printf(prompt, "\n");
+}
+
+void
+link_PushPacket(struct link *l, struct mbuf *bp, struct bundle *b, int pri,
+ u_short proto)
+{
+ int layer;
+
+ /*
+ * When we ``push'' a packet into the link, it gets processed by the
+ * ``push'' function in each layer starting at the top.
+ * We never expect the result of a ``push'' to be more than one
+ * packet (as we do with ``pull''s).
+ */
+
+ if(pri < 0 || (unsigned)pri >= LINK_QUEUES(l))
+ pri = 0;
+
+ bp->priv = 0; /* Adjusted by the async layer ! */
+ for (layer = l->nlayers; layer && bp; layer--)
+ if (l->layer[layer - 1]->push != NULL)
+ bp = (*l->layer[layer - 1]->push)(b, l, bp, pri, &proto);
+
+ if (bp) {
+ link_AddOutOctets(l, m_length(bp));
+ log_Printf(LogDEBUG, "link_PushPacket: Transmit proto 0x%04x\n", proto);
+ m_enqueue(l->Queue + pri, m_pullup(bp));
+ }
+}
+
+void
+link_PullPacket(struct link *l, char *buf, size_t len, struct bundle *b)
+{
+ struct mbuf *bp, *lbp[LAYER_MAX], *next;
+ u_short lproto[LAYER_MAX], proto;
+ int layer;
+
+ /*
+ * When we ``pull'' a packet from the link, it gets processed by the
+ * ``pull'' function in each layer starting at the bottom.
+ * Each ``pull'' may produce multiple packets, chained together using
+ * bp->m_nextpkt.
+ * Each packet that results from each pull has to be pulled through
+ * all of the higher layers before the next resulting packet is pulled
+ * through anything; this ensures that packets that depend on the
+ * fsm state resulting from the receipt of the previous packet aren't
+ * surprised.
+ */
+
+ link_AddInOctets(l, len);
+
+ memset(lbp, '\0', sizeof lbp);
+ lbp[0] = m_get(len, MB_UNKNOWN);
+ memcpy(MBUF_CTOP(lbp[0]), buf, len);
+ lproto[0] = 0;
+ layer = 0;
+
+ while (layer || lbp[layer]) {
+ if (lbp[layer] == NULL) {
+ layer--;
+ continue;
+ }
+ bp = lbp[layer];
+ lbp[layer] = bp->m_nextpkt;
+ bp->m_nextpkt = NULL;
+ proto = lproto[layer];
+
+ if (l->layer[layer]->pull != NULL)
+ bp = (*l->layer[layer]->pull)(b, l, bp, &proto);
+
+ if (layer == l->nlayers - 1) {
+ /* We've just done the top layer, despatch the packet(s) */
+ while (bp) {
+ next = bp->m_nextpkt;
+ bp->m_nextpkt = NULL;
+ log_Printf(LogDEBUG, "link_PullPacket: Despatch proto 0x%04x\n", proto);
+ Despatch(b, l, bp, proto);
+ bp = next;
+ }
+ } else {
+ lbp[++layer] = bp;
+ lproto[layer] = proto;
+ }
+ }
+}
+
+int
+link_Stack(struct link *l, struct layer *layer)
+{
+ if (l->nlayers == sizeof l->layer / sizeof l->layer[0]) {
+ log_Printf(LogERROR, "%s: Oops, cannot stack a %s layer...\n",
+ l->name, layer->name);
+ return 0;
+ }
+ l->layer[l->nlayers++] = layer;
+ return 1;
+}
+
+void
+link_EmptyStack(struct link *l)
+{
+ l->nlayers = 0;
+}
+
+static const struct {
+ u_short proto;
+ struct mbuf *(*fn)(struct bundle *, struct link *, struct mbuf *);
+} despatcher[] = {
+ { PROTO_IP, ipv4_Input },
+#ifndef NOINET6
+ { PROTO_IPV6, ipv6_Input },
+#endif
+ { PROTO_MP, mp_Input },
+ { PROTO_LCP, lcp_Input },
+ { PROTO_IPCP, ipcp_Input },
+#ifndef NOINET6
+ { PROTO_IPV6CP, ipv6cp_Input },
+#endif
+ { PROTO_PAP, pap_Input },
+ { PROTO_CHAP, chap_Input },
+ { PROTO_CCP, ccp_Input },
+ { PROTO_LQR, lqr_Input },
+ { PROTO_CBCP, cbcp_Input }
+};
+
+#define DSIZE (sizeof despatcher / sizeof despatcher[0])
+
+static void
+Despatch(struct bundle *bundle, struct link *l, struct mbuf *bp, u_short proto)
+{
+ unsigned f;
+
+ for (f = 0; f < DSIZE; f++)
+ if (despatcher[f].proto == proto) {
+ bp = (*despatcher[f].fn)(bundle, l, bp);
+ break;
+ }
+
+ if (bp) {
+ struct physical *p = link2physical(l);
+
+ log_Printf(LogPHASE, "%s protocol 0x%04x (%s)\n",
+ f == DSIZE ? "Unknown" : "Unexpected", proto,
+ hdlc_Protocol2Nam(proto));
+ bp = m_pullup(proto_Prepend(bp, proto, 0, 0));
+ lcp_SendProtoRej(&l->lcp, MBUF_CTOP(bp), bp->m_len);
+ if (p) {
+ p->hdlc.lqm.ifInDiscards++;
+ p->hdlc.stats.unknownproto++;
+ }
+ m_freem(bp);
+ }
+}
+
+int
+link_ShowLayers(struct cmdargs const *arg)
+{
+ struct link *l = command_ChooseLink(arg);
+ int layer;
+
+ for (layer = l->nlayers; layer; layer--)
+ prompt_Printf(arg->prompt, "%s%s", layer == l->nlayers ? "" : ", ",
+ l->layer[layer - 1]->name);
+ if (l->nlayers)
+ prompt_Printf(arg->prompt, "\n");
+
+ return 0;
+}
diff --git a/usr.sbin/ppp/link.h b/usr.sbin/ppp/link.h
new file mode 100644
index 0000000..822474b
--- /dev/null
+++ b/usr.sbin/ppp/link.h
@@ -0,0 +1,81 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.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$
+ *
+ */
+
+
+#define PHYSICAL_LINK 1
+#define LOGICAL_LINK 2
+
+#define NPROTOSTAT 13
+
+struct bundle;
+struct prompt;
+struct cmdargs;
+
+struct link {
+ int type; /* _LINK type */
+ const char *name; /* Points to datalink::name */
+ int len; /* full size of parent struct */
+ struct {
+ unsigned gather : 1; /* Gather statistics ourself ? */
+ struct pppThroughput total; /* Link throughput statistics */
+ struct pppThroughput *parent; /* MP link throughput statistics */
+ } stats;
+ struct mqueue Queue[2]; /* Our output queue of mbufs */
+
+ u_long proto_in[NPROTOSTAT]; /* outgoing protocol stats */
+ u_long proto_out[NPROTOSTAT]; /* incoming protocol stats */
+
+ struct lcp lcp; /* Our line control FSM */
+ struct ccp ccp; /* Our compression FSM */
+
+ struct layer const *layer[LAYER_MAX]; /* i/o layers */
+ int nlayers;
+};
+
+#define LINK_QUEUES(link) (sizeof (link)->Queue / sizeof (link)->Queue[0])
+#define LINK_HIGHQ(link) ((link)->Queue + LINK_QUEUES(link) - 1)
+
+extern void link_SequenceQueue(struct link *);
+extern void link_DeleteQueue(struct link *);
+extern size_t link_QueueLen(struct link *);
+extern size_t link_QueueBytes(struct link *);
+extern void link_PendingLowPriorityData(struct link *, size_t *, size_t *);
+extern struct mbuf *link_Dequeue(struct link *);
+
+extern void link_PushPacket(struct link *, struct mbuf *, struct bundle *,
+ int, u_short);
+extern void link_PullPacket(struct link *, char *, size_t, struct bundle *);
+extern int link_Stack(struct link *, struct layer *);
+extern void link_EmptyStack(struct link *);
+
+#define PROTO_IN 1 /* third arg to link_ProtocolRecord */
+#define PROTO_OUT 2
+extern void link_ProtocolRecord(struct link *, u_short, int);
+extern void link_ReportProtocolStatus(struct link *, struct prompt *);
+extern int link_ShowLayers(struct cmdargs const *);
diff --git a/usr.sbin/ppp/log.c b/usr.sbin/ppp/log.c
new file mode 100644
index 0000000..76043b4
--- /dev/null
+++ b/usr.sbin/ppp/log.c
@@ -0,0 +1,532 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.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/types.h>
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <termios.h>
+
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "descriptor.h"
+#include "prompt.h"
+
+static const char *const LogNames[] = {
+ "Async",
+ "CBCP",
+ "CCP",
+ "Chat",
+ "Command",
+ "Connect",
+ "Debug",
+ "DNS",
+ "Filter", /* Log discarded packets */
+ "HDLC",
+ "ID0",
+ "IPCP",
+ "IPV6CP",
+ "LCP",
+ "LQM",
+ "Phase",
+ "Physical",
+ "Radius",
+ "Sync",
+ "TCP/IP",
+ "Timer",
+ "Tun",
+ "Warning",
+ "Error",
+ "Alert"
+};
+
+#define MSK(n) (1<<((n)-1))
+
+static u_long LogMask = MSK(LogPHASE);
+static u_long LogMaskLocal = MSK(LogERROR) | MSK(LogALERT) | MSK(LogWARN);
+static int LogTunno = -1;
+static const char *LogIfaceName;
+static struct prompt *promptlist; /* Where to log local stuff */
+struct prompt *log_PromptContext;
+int log_PromptListChanged;
+
+struct prompt *
+log_PromptList()
+{
+ return promptlist;
+}
+
+void
+log_RegisterPrompt(struct prompt *prompt)
+{
+ prompt->next = promptlist;
+ promptlist = prompt;
+ prompt->active = 1;
+ log_DiscardAllLocal(&prompt->logmask);
+}
+
+void
+log_ActivatePrompt(struct prompt *prompt)
+{
+ prompt->active = 1;
+ LogMaskLocal |= prompt->logmask;
+}
+
+static void
+LogSetMaskLocal(void)
+{
+ struct prompt *p;
+
+ LogMaskLocal = MSK(LogERROR) | MSK(LogALERT) | MSK(LogWARN);
+ for (p = promptlist; p; p = p->next)
+ LogMaskLocal |= p->logmask;
+}
+
+void
+log_DeactivatePrompt(struct prompt *prompt)
+{
+ if (prompt->active) {
+ prompt->active = 0;
+ LogSetMaskLocal();
+ }
+}
+
+void
+log_UnRegisterPrompt(struct prompt *prompt)
+{
+ if (prompt) {
+ struct prompt **p;
+
+ for (p = &promptlist; *p; p = &(*p)->next)
+ if (*p == prompt) {
+ *p = prompt->next;
+ prompt->next = NULL;
+ break;
+ }
+ LogSetMaskLocal();
+ log_PromptListChanged++;
+ }
+}
+
+void
+log_DestroyPrompts(struct server *s)
+{
+ struct prompt *p, *pn, *pl;
+
+ p = promptlist;
+ pl = NULL;
+ while (p) {
+ pn = p->next;
+ if (s && p->owner == s) {
+ if (pl)
+ pl->next = p->next;
+ else
+ promptlist = p->next;
+ p->next = NULL;
+ prompt_Destroy(p, 1);
+ } else
+ pl = p;
+ p = pn;
+ }
+}
+
+void
+log_DisplayPrompts()
+{
+ struct prompt *p;
+
+ for (p = promptlist; p; p = p->next)
+ prompt_Required(p);
+}
+
+void
+log_WritePrompts(struct datalink *dl, const char *fmt,...)
+{
+ va_list ap;
+ struct prompt *p;
+
+ va_start(ap, fmt);
+ for (p = promptlist; p; p = p->next)
+ if (prompt_IsTermMode(p, dl))
+ prompt_vPrintf(p, fmt, ap);
+ va_end(ap);
+}
+
+void
+log_SetTtyCommandMode(struct datalink *dl)
+{
+ struct prompt *p;
+
+ for (p = promptlist; p; p = p->next)
+ if (prompt_IsTermMode(p, dl))
+ prompt_TtyCommandMode(p);
+}
+
+static int
+syslogLevel(int lev)
+{
+ switch (lev) {
+ case LogLOG:
+ return LOG_INFO;
+ case LogDEBUG:
+ case LogTIMER:
+ return LOG_DEBUG;
+ case LogWARN:
+ return LOG_WARNING;
+ case LogERROR:
+ return LOG_ERR;
+ case LogALERT:
+ return LOG_ALERT;
+ }
+ return lev >= LogMIN && lev <= LogMAX ? LOG_INFO : 0;
+}
+
+const char *
+log_Name(int id)
+{
+ if (id == LogLOG)
+ return "LOG";
+ return id < LogMIN || id > LogMAX ? "Unknown" : LogNames[id - 1];
+}
+
+void
+log_Keep(int id)
+{
+ if (id >= LogMIN && id <= LogMAXCONF)
+ LogMask |= MSK(id);
+}
+
+void
+log_KeepLocal(int id, u_long *mask)
+{
+ if (id >= LogMIN && id <= LogMAXCONF) {
+ LogMaskLocal |= MSK(id);
+ *mask |= MSK(id);
+ }
+}
+
+void
+log_Discard(int id)
+{
+ if (id >= LogMIN && id <= LogMAXCONF)
+ LogMask &= ~MSK(id);
+}
+
+void
+log_DiscardLocal(int id, u_long *mask)
+{
+ if (id >= LogMIN && id <= LogMAXCONF) {
+ *mask &= ~MSK(id);
+ LogSetMaskLocal();
+ }
+}
+
+void
+log_DiscardAll()
+{
+ LogMask = 0;
+}
+
+void
+log_DiscardAllLocal(u_long *mask)
+{
+ *mask = MSK(LogERROR) | MSK(LogALERT) | MSK(LogWARN);
+ LogSetMaskLocal();
+}
+
+int
+log_IsKept(int id)
+{
+ if (id == LogLOG)
+ return LOG_KEPT_SYSLOG;
+ if (id < LogMIN || id > LogMAX)
+ return 0;
+ if (id > LogMAXCONF)
+ return LOG_KEPT_LOCAL | LOG_KEPT_SYSLOG;
+
+ return ((LogMaskLocal & MSK(id)) ? LOG_KEPT_LOCAL : 0) |
+ ((LogMask & MSK(id)) ? LOG_KEPT_SYSLOG : 0);
+}
+
+int
+log_IsKeptLocal(int id, u_long mask)
+{
+ if (id < LogMIN || id > LogMAX)
+ return 0;
+ if (id > LogMAXCONF)
+ return LOG_KEPT_LOCAL | LOG_KEPT_SYSLOG;
+
+ return ((mask & MSK(id)) ? LOG_KEPT_LOCAL : 0) |
+ ((LogMask & MSK(id)) ? LOG_KEPT_SYSLOG : 0);
+}
+
+void
+log_Open(const char *Name)
+{
+ openlog(Name, LOG_PID, LOG_DAEMON);
+}
+
+void
+log_SetTun(int tunno, const char *ifaceName)
+{
+ LogTunno = tunno;
+ LogIfaceName = ifaceName;
+}
+
+void
+log_Close()
+{
+ closelog();
+ LogTunno = -1;
+ LogIfaceName = NULL;
+}
+
+void
+log_Printf(int lev, const char *fmt,...)
+{
+ va_list ap;
+ struct prompt *prompt;
+
+ if (log_IsKept(lev)) {
+ char nfmt[200];
+
+ va_start(ap, fmt);
+ if (promptlist && (log_IsKept(lev) & LOG_KEPT_LOCAL)) {
+ if ((log_IsKept(LogTUN) & LOG_KEPT_LOCAL) && LogTunno != -1) {
+ if (LogIfaceName)
+ snprintf(nfmt, sizeof nfmt, "%s%d(%s): %s: %s", TUN_NAME,
+ LogTunno, LogIfaceName, log_Name(lev), fmt);
+ else
+ snprintf(nfmt, sizeof nfmt, "%s%d: %s: %s", TUN_NAME,
+ LogTunno, log_Name(lev), fmt);
+ } else
+ snprintf(nfmt, sizeof nfmt, "%s: %s", log_Name(lev), fmt);
+
+ if (log_PromptContext && lev == LogWARN)
+ /* Warnings just go to the current prompt */
+ prompt_vPrintf(log_PromptContext, nfmt, ap);
+ else for (prompt = promptlist; prompt; prompt = prompt->next)
+ if (lev > LogMAXCONF || (prompt->logmask & MSK(lev)))
+ prompt_vPrintf(prompt, nfmt, ap);
+ }
+ va_end(ap);
+
+ va_start(ap, fmt);
+ if ((log_IsKept(lev) & LOG_KEPT_SYSLOG) &&
+ (lev != LogWARN || !log_PromptContext)) {
+ if ((log_IsKept(LogTUN) & LOG_KEPT_SYSLOG) && LogTunno != -1) {
+ if (LogIfaceName)
+ snprintf(nfmt, sizeof nfmt, "%s%d(%s): %s: %s", TUN_NAME,
+ LogTunno, LogIfaceName, log_Name(lev), fmt);
+ else
+ snprintf(nfmt, sizeof nfmt, "%s%d: %s: %s", TUN_NAME,
+ LogTunno, log_Name(lev), fmt);
+ } else
+ snprintf(nfmt, sizeof nfmt, "%s: %s", log_Name(lev), fmt);
+ vsyslog(syslogLevel(lev), nfmt, ap);
+ }
+ va_end(ap);
+ }
+}
+
+void
+log_DumpBp(int lev, const char *hdr, const struct mbuf *bp)
+{
+ if (log_IsKept(lev)) {
+ char buf[68];
+ char *b, *c;
+ const u_char *ptr;
+ int f;
+
+ if (hdr && *hdr)
+ log_Printf(lev, "%s\n", hdr);
+
+ b = buf;
+ c = b + 50;
+ do {
+ f = bp->m_len;
+ ptr = CONST_MBUF_CTOP(bp);
+ while (f--) {
+ sprintf(b, " %02x", (int) *ptr);
+ *c++ = isprint(*ptr) ? *ptr : '.';
+ ptr++;
+ b += 3;
+ if (b == buf + 48) {
+ memset(b, ' ', 2);
+ *c = '\0';
+ log_Printf(lev, "%s\n", buf);
+ b = buf;
+ c = b + 50;
+ }
+ }
+ } while ((bp = bp->m_next) != NULL);
+
+ if (b > buf) {
+ memset(b, ' ', 50 - (b - buf));
+ *c = '\0';
+ log_Printf(lev, "%s\n", buf);
+ }
+ }
+}
+
+void
+log_DumpBuff(int lev, const char *hdr, const u_char *ptr, int n)
+{
+ if (log_IsKept(lev)) {
+ char buf[68];
+ char *b, *c;
+
+ if (hdr && *hdr)
+ log_Printf(lev, "%s\n", hdr);
+ while (n > 0) {
+ b = buf;
+ c = b + 50;
+ for (b = buf; b != buf + 48 && n--; b += 3, ptr++) {
+ sprintf(b, " %02x", (int) *ptr);
+ *c++ = isprint(*ptr) ? *ptr : '.';
+ }
+ memset(b, ' ', 50 - (b - buf));
+ *c = '\0';
+ log_Printf(lev, "%s\n", buf);
+ }
+ }
+}
+
+int
+log_ShowLevel(struct cmdargs const *arg)
+{
+ int i;
+
+ prompt_Printf(arg->prompt, "Log: ");
+ for (i = LogMIN; i <= LogMAX; i++)
+ if (log_IsKept(i) & LOG_KEPT_SYSLOG)
+ prompt_Printf(arg->prompt, " %s", log_Name(i));
+
+ prompt_Printf(arg->prompt, "\nLocal:");
+ for (i = LogMIN; i <= LogMAX; i++)
+ if (log_IsKeptLocal(i, arg->prompt->logmask) & LOG_KEPT_LOCAL)
+ prompt_Printf(arg->prompt, " %s", log_Name(i));
+
+ prompt_Printf(arg->prompt, "\n");
+
+ return 0;
+}
+
+int
+log_SetLevel(struct cmdargs const *arg)
+{
+ int i, res, argc, local;
+ char const *const *argv, *argp;
+
+ argc = arg->argc - arg->argn;
+ argv = arg->argv + arg->argn;
+ res = 0;
+
+ if (argc == 0 || strcasecmp(argv[0], "local"))
+ local = 0;
+ else {
+ if (arg->prompt == NULL) {
+ log_Printf(LogWARN, "set log local: Only available on the"
+ " command line\n");
+ return 1;
+ }
+ argc--;
+ argv++;
+ local = 1;
+ }
+
+ if (argc == 0 || (argv[0][0] != '+' && argv[0][0] != '-')) {
+ if (local)
+ log_DiscardAllLocal(&arg->prompt->logmask);
+ else
+ log_DiscardAll();
+ }
+
+ while (argc--) {
+ argp = **argv == '+' || **argv == '-' ? *argv + 1 : *argv;
+ /* Special case 'all' */
+ if (strcasecmp(argp, "all") == 0) {
+ if (**argv == '-') {
+ if (local)
+ for (i = LogMIN; i <= LogMAX; i++)
+ log_DiscardLocal(i, &arg->prompt->logmask);
+ else
+ for (i = LogMIN; i <= LogMAX; i++)
+ log_Discard(i);
+ } else if (local)
+ for (i = LogMIN; i <= LogMAX; i++)
+ log_KeepLocal(i, &arg->prompt->logmask);
+ else
+ for (i = LogMIN; i <= LogMAX; i++)
+ log_Keep(i);
+ argv++;
+ continue;
+ }
+ for (i = LogMIN; i <= LogMAX; i++)
+ if (strcasecmp(argp, log_Name(i)) == 0) {
+ if (**argv == '-') {
+ if (local)
+ log_DiscardLocal(i, &arg->prompt->logmask);
+ else
+ log_Discard(i);
+ } else if (local)
+ log_KeepLocal(i, &arg->prompt->logmask);
+ else
+ log_Keep(i);
+ break;
+ }
+ if (i > LogMAX) {
+ log_Printf(LogWARN, "%s: Invalid log value\n", argp);
+ res = -1;
+ }
+ argv++;
+ }
+ return res;
+}
+
+int
+log_ShowWho(struct cmdargs const *arg)
+{
+ struct prompt *p;
+
+ for (p = promptlist; p; p = p->next) {
+ prompt_Printf(arg->prompt, "%s (%s)", p->src.type, p->src.from);
+ if (p == arg->prompt)
+ prompt_Printf(arg->prompt, " *");
+ if (!p->active)
+ prompt_Printf(arg->prompt, " ^Z");
+ prompt_Printf(arg->prompt, "\n");
+ }
+
+ return 0;
+}
diff --git a/usr.sbin/ppp/log.h b/usr.sbin/ppp/log.h
new file mode 100644
index 0000000..139863d
--- /dev/null
+++ b/usr.sbin/ppp/log.h
@@ -0,0 +1,105 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.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$
+ */
+
+#define LogLOG (0)
+#define LogMIN (1)
+#define LogASYNC (1) /* syslog(LOG_INFO, ....) */
+#define LogCBCP (2)
+#define LogCCP (3)
+#define LogCHAT (4)
+#define LogCOMMAND (5)
+#define LogCONNECT (6)
+#define LogDEBUG (7) /* syslog(LOG_DEBUG, ....) */
+#define LogDNS (8)
+#define LogFILTER (9)
+#define LogHDLC (10)
+#define LogID0 (11)
+#define LogIPCP (12)
+#define LogIPV6CP (13)
+#define LogLCP (14)
+#define LogLQM (15)
+#define LogPHASE (16)
+#define LogPHYSICAL (17) /* syslog(LOG_INFO, ....) */
+#define LogRADIUS (18) /* syslog(LOG_INFO, ....) */
+#define LogSYNC (19) /* syslog(LOG_INFO, ....) */
+#define LogTCPIP (20)
+#define LogTIMER (21) /* syslog(LOG_DEBUG, ....) */
+#define LogTUN (22) /* If set, tun%d is output with each message */
+#define LogWARN (23) /* Sent to VarTerm else syslog(LOG_WARNING, ) */
+#define LogERROR (24) /* syslog(LOG_ERR, ....), + sent to VarTerm */
+#define LogALERT (25) /* syslog(LOG_ALERT, ....) */
+
+#define LogMAXCONF (22)
+#define LogMAX (25)
+
+struct mbuf;
+struct cmdargs;
+struct prompt;
+struct server;
+struct datalink;
+
+/* The first int arg for all of the following is one of the above values */
+extern const char *log_Name(int);
+extern void log_Keep(int);
+extern void log_KeepLocal(int, u_long *);
+extern void log_Discard(int);
+extern void log_DiscardLocal(int, u_long *);
+extern void log_DiscardAll(void);
+extern void log_DiscardAllLocal(u_long *);
+#define LOG_KEPT_SYSLOG (1) /* Results of log_IsKept() */
+#define LOG_KEPT_LOCAL (2) /* Results of log_IsKept() */
+extern int log_IsKept(int);
+extern int log_IsKeptLocal(int, u_long);
+extern void log_Open(const char *);
+extern void log_SetTun(int, const char *);
+extern void log_Close(void);
+#ifdef __GNUC__
+extern void log_Printf(int, const char *,...)
+ __attribute__ ((format (printf, 2, 3)));
+extern void log_WritePrompts(struct datalink *, const char *, ...)
+ __attribute__ ((format (printf, 2, 3)));
+#else
+extern void log_Printf(int, const char *,...);
+extern void log_WritePrompts(struct datalink *, const char *, ...);
+#endif
+extern void log_DumpBp(int, const char *, const struct mbuf *);
+extern void log_DumpBuff(int, const char *, const u_char *, int);
+extern int log_ShowLevel(struct cmdargs const *);
+extern int log_SetLevel(struct cmdargs const *);
+extern int log_ShowWho(struct cmdargs const *);
+
+extern struct prompt *log_PromptContext;
+extern int log_PromptListChanged;
+extern void log_RegisterPrompt(struct prompt *);
+extern void log_UnRegisterPrompt(struct prompt *);
+extern void log_DestroyPrompts(struct server *);
+extern void log_DisplayPrompts(void);
+extern void log_ActivatePrompt(struct prompt *);
+extern void log_DeactivatePrompt(struct prompt *);
+extern void log_SetTtyCommandMode(struct datalink *);
+extern struct prompt *log_PromptList(void);
diff --git a/usr.sbin/ppp/lqr.c b/usr.sbin/ppp/lqr.c
new file mode 100644
index 0000000..7ce1513
--- /dev/null
+++ b/usr.sbin/ppp/lqr.c
@@ -0,0 +1,532 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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>
+
+#ifdef __FreeBSD__
+#include <netinet/in.h>
+#endif
+#include <sys/un.h>
+
+#include <string.h>
+#include <termios.h>
+
+#include "layer.h"
+#include "mbuf.h"
+#include "log.h"
+#include "defs.h"
+#include "timer.h"
+#include "fsm.h"
+#include "acf.h"
+#include "proto.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "async.h"
+#include "throughput.h"
+#include "ccp.h"
+#include "link.h"
+#include "descriptor.h"
+#include "physical.h"
+#include "mp.h"
+#include "chat.h"
+#include "auth.h"
+#include "chap.h"
+#include "command.h"
+#include "cbcp.h"
+#include "datalink.h"
+
+struct echolqr {
+ u_int32_t magic;
+ u_int32_t signature;
+ u_int32_t sequence;
+};
+
+#define SIGNATURE 0x594e4f54
+
+static void
+SendEchoReq(struct lcp *lcp)
+{
+ struct hdlc *hdlc = &link2physical(lcp->fsm.link)->hdlc;
+ struct echolqr echo;
+
+ echo.magic = htonl(lcp->want_magic);
+ echo.signature = htonl(SIGNATURE);
+ echo.sequence = htonl(hdlc->lqm.echo.seq_sent);
+ fsm_Output(&lcp->fsm, CODE_ECHOREQ, hdlc->lqm.echo.seq_sent++,
+ (u_char *)&echo, sizeof echo, MB_ECHOOUT);
+}
+
+struct mbuf *
+lqr_RecvEcho(struct fsm *fp, struct mbuf *bp)
+{
+ struct hdlc *hdlc = &link2physical(fp->link)->hdlc;
+ struct lcp *lcp = fsm2lcp(fp);
+ struct echolqr lqr;
+
+ if (m_length(bp) >= sizeof lqr) {
+ m_freem(mbuf_Read(bp, &lqr, sizeof lqr));
+ bp = NULL;
+ lqr.magic = ntohl(lqr.magic);
+ lqr.signature = ntohl(lqr.signature);
+ lqr.sequence = ntohl(lqr.sequence);
+
+ /* Tolerate echo replies with either magic number */
+ if (lqr.magic != 0 && lqr.magic != lcp->his_magic &&
+ lqr.magic != lcp->want_magic) {
+ log_Printf(LogWARN, "%s: lqr_RecvEcho: Bad magic: expected 0x%08x,"
+ " got 0x%08x\n", fp->link->name, lcp->his_magic, lqr.magic);
+ /*
+ * XXX: We should send a terminate request. But poor implementations may
+ * die as a result.
+ */
+ }
+ if (lqr.signature == SIGNATURE
+ || lqr.signature == lcp->want_magic) { /* some implementations return the wrong magic */
+ /* careful not to update lqm.echo.seq_recv with older values */
+ if ((hdlc->lqm.echo.seq_recv > (u_int32_t)0 - 5 && lqr.sequence < 5) ||
+ (hdlc->lqm.echo.seq_recv <= (u_int32_t)0 - 5 &&
+ lqr.sequence > hdlc->lqm.echo.seq_recv))
+ hdlc->lqm.echo.seq_recv = lqr.sequence;
+ } else
+ log_Printf(LogWARN, "lqr_RecvEcho: Got sig 0x%08lx, not 0x%08lx !\n",
+ (u_long)lqr.signature, (u_long)SIGNATURE);
+ } else
+ log_Printf(LogWARN, "lqr_RecvEcho: Got packet size %zd, expecting %ld !\n",
+ m_length(bp), (long)sizeof(struct echolqr));
+ return bp;
+}
+
+void
+lqr_ChangeOrder(struct lqrdata *src, struct lqrdata *dst)
+{
+ u_int32_t *sp, *dp;
+ unsigned n;
+
+ sp = (u_int32_t *) src;
+ dp = (u_int32_t *) dst;
+ for (n = 0; n < sizeof(struct lqrdata) / sizeof(u_int32_t); n++, sp++, dp++)
+ *dp = ntohl(*sp);
+}
+
+static void
+SendLqrData(struct lcp *lcp)
+{
+ struct mbuf *bp;
+ int extra;
+
+ extra = proto_WrapperOctets(lcp, PROTO_LQR) +
+ acf_WrapperOctets(lcp, PROTO_LQR);
+ bp = m_get(sizeof(struct lqrdata) + extra, MB_LQROUT);
+ bp->m_len -= extra;
+ bp->m_offset += extra;
+
+ /*
+ * Send on the highest priority queue. We send garbage - the real data
+ * is written by lqr_LayerPush() where we know how to fill in all the
+ * fields. Note, lqr_LayerPush() ``knows'' that we're pushing onto the
+ * highest priority queue, and factors out packet & octet values from
+ * other queues!
+ */
+ link_PushPacket(lcp->fsm.link, bp, lcp->fsm.bundle,
+ LINK_QUEUES(lcp->fsm.link) - 1, PROTO_LQR);
+}
+
+static void
+SendLqrReport(void *v)
+{
+ struct lcp *lcp = (struct lcp *)v;
+ struct physical *p = link2physical(lcp->fsm.link);
+
+ timer_Stop(&p->hdlc.lqm.timer);
+
+ if (p->hdlc.lqm.method & LQM_LQR) {
+ if (p->hdlc.lqm.lqr.resent > 5) {
+ /* XXX: Should implement LQM strategy */
+ log_Printf(LogPHASE, "%s: ** Too many LQR packets lost **\n",
+ lcp->fsm.link->name);
+ log_Printf(LogLQM, "%s: Too many LQR packets lost\n",
+ lcp->fsm.link->name);
+ p->hdlc.lqm.method = 0;
+ datalink_Down(p->dl, CLOSE_NORMAL);
+ } else {
+ SendLqrData(lcp);
+ p->hdlc.lqm.lqr.resent++;
+ }
+ } else if (p->hdlc.lqm.method & LQM_ECHO) {
+ if ((p->hdlc.lqm.echo.seq_sent > 5 &&
+ p->hdlc.lqm.echo.seq_sent - 5 > p->hdlc.lqm.echo.seq_recv) ||
+ (p->hdlc.lqm.echo.seq_sent <= 5 &&
+ p->hdlc.lqm.echo.seq_sent > p->hdlc.lqm.echo.seq_recv + 5)) {
+ log_Printf(LogPHASE, "%s: ** Too many LCP ECHO packets lost **\n",
+ lcp->fsm.link->name);
+ log_Printf(LogLQM, "%s: Too many LCP ECHO packets lost\n",
+ lcp->fsm.link->name);
+ p->hdlc.lqm.method = 0;
+ datalink_Down(p->dl, CLOSE_NORMAL);
+ } else
+ SendEchoReq(lcp);
+ }
+ if (p->hdlc.lqm.method && p->hdlc.lqm.timer.load)
+ timer_Start(&p->hdlc.lqm.timer);
+}
+
+struct mbuf *
+lqr_Input(struct bundle *bundle __unused, struct link *l, struct mbuf *bp)
+{
+ struct physical *p = link2physical(l);
+ struct lcp *lcp = p->hdlc.lqm.owner;
+ int len;
+
+ if (p == NULL) {
+ log_Printf(LogERROR, "lqr_Input: Not a physical link - dropped\n");
+ m_freem(bp);
+ return NULL;
+ }
+
+ len = m_length(bp);
+ if (len != sizeof(struct lqrdata))
+ log_Printf(LogWARN, "lqr_Input: Got packet size %d, expecting %ld !\n",
+ len, (long)sizeof(struct lqrdata));
+ else if (!IsAccepted(l->lcp.cfg.lqr) && !(p->hdlc.lqm.method & LQM_LQR)) {
+ bp = m_pullup(proto_Prepend(bp, PROTO_LQR, 0, 0));
+ lcp_SendProtoRej(lcp, MBUF_CTOP(bp), bp->m_len);
+ } else {
+ struct lqrdata *lqr;
+
+ bp = m_pullup(bp);
+ lqr = (struct lqrdata *)MBUF_CTOP(bp);
+ if (ntohl(lqr->MagicNumber) != lcp->his_magic)
+ log_Printf(LogWARN, "lqr_Input: magic 0x%08lx is wrong,"
+ " expecting 0x%08lx\n",
+ (u_long)ntohl(lqr->MagicNumber), (u_long)lcp->his_magic);
+ else {
+ struct lqrdata lastlqr;
+
+ memcpy(&lastlqr, &p->hdlc.lqm.lqr.peer, sizeof lastlqr);
+ lqr_ChangeOrder(lqr, &p->hdlc.lqm.lqr.peer);
+ lqr_Dump(l->name, "Input", &p->hdlc.lqm.lqr.peer);
+ /* we have received an LQR from our peer */
+ p->hdlc.lqm.lqr.resent = 0;
+
+ /* Snapshot our state when the LQR packet was received */
+ memcpy(&p->hdlc.lqm.lqr.prevSave, &p->hdlc.lqm.lqr.Save,
+ sizeof p->hdlc.lqm.lqr.prevSave);
+ p->hdlc.lqm.lqr.Save.InLQRs = ++p->hdlc.lqm.lqr.InLQRs;
+ p->hdlc.lqm.lqr.Save.InPackets = p->hdlc.lqm.ifInUniPackets;
+ p->hdlc.lqm.lqr.Save.InDiscards = p->hdlc.lqm.ifInDiscards;
+ p->hdlc.lqm.lqr.Save.InErrors = p->hdlc.lqm.ifInErrors;
+ p->hdlc.lqm.lqr.Save.InOctets = p->hdlc.lqm.lqr.InGoodOctets;
+
+ lqr_Analyse(&p->hdlc, &lastlqr, &p->hdlc.lqm.lqr.peer);
+
+ /*
+ * Generate an LQR response if we're not running an LQR timer OR
+ * two successive LQR's PeerInLQRs are the same.
+ */
+ if (p->hdlc.lqm.timer.load == 0 || !(p->hdlc.lqm.method & LQM_LQR) ||
+ (lastlqr.PeerInLQRs &&
+ lastlqr.PeerInLQRs == p->hdlc.lqm.lqr.peer.PeerInLQRs))
+ SendLqrData(lcp);
+ }
+ }
+ m_freem(bp);
+ return NULL;
+}
+
+/*
+ * When LCP is reached to opened state, We'll start LQM activity.
+ */
+static void
+lqr_Setup(struct lcp *lcp)
+{
+ struct physical *physical = link2physical(lcp->fsm.link);
+ int period;
+
+ physical->hdlc.lqm.lqr.resent = 0;
+ physical->hdlc.lqm.echo.seq_sent = 0;
+ physical->hdlc.lqm.echo.seq_recv = 0;
+ memset(&physical->hdlc.lqm.lqr.peer, '\0',
+ sizeof physical->hdlc.lqm.lqr.peer);
+
+ physical->hdlc.lqm.method = lcp->cfg.echo ? LQM_ECHO : 0;
+ if (IsEnabled(lcp->cfg.lqr) && !REJECTED(lcp, TY_QUALPROTO))
+ physical->hdlc.lqm.method |= LQM_LQR;
+ timer_Stop(&physical->hdlc.lqm.timer);
+
+ physical->hdlc.lqm.lqr.peer_timeout = lcp->his_lqrperiod;
+ if (lcp->his_lqrperiod)
+ log_Printf(LogLQM, "%s: Expecting LQR every %d.%02d secs\n",
+ physical->link.name, lcp->his_lqrperiod / 100,
+ lcp->his_lqrperiod % 100);
+
+ period = lcp->want_lqrperiod ?
+ lcp->want_lqrperiod : lcp->cfg.lqrperiod * 100;
+ physical->hdlc.lqm.timer.func = SendLqrReport;
+ physical->hdlc.lqm.timer.name = "lqm";
+ physical->hdlc.lqm.timer.arg = lcp;
+
+ if (lcp->want_lqrperiod || physical->hdlc.lqm.method & LQM_ECHO) {
+ log_Printf(LogLQM, "%s: Will send %s every %d.%02d secs\n",
+ physical->link.name, lcp->want_lqrperiod ? "LQR" : "LCP ECHO",
+ period / 100, period % 100);
+ physical->hdlc.lqm.timer.load = period * SECTICKS / 100;
+ } else {
+ physical->hdlc.lqm.timer.load = 0;
+ if (!lcp->his_lqrperiod)
+ log_Printf(LogLQM, "%s: LQR/LCP ECHO not negotiated\n",
+ physical->link.name);
+ }
+}
+
+void
+lqr_Start(struct lcp *lcp)
+{
+ struct physical *p = link2physical(lcp->fsm.link);
+
+ lqr_Setup(lcp);
+ if (p->hdlc.lqm.timer.load)
+ SendLqrReport(lcp);
+}
+
+void
+lqr_reStart(struct lcp *lcp)
+{
+ struct physical *p = link2physical(lcp->fsm.link);
+
+ lqr_Setup(lcp);
+ if (p->hdlc.lqm.timer.load)
+ timer_Start(&p->hdlc.lqm.timer);
+}
+
+void
+lqr_StopTimer(struct physical *physical)
+{
+ timer_Stop(&physical->hdlc.lqm.timer);
+}
+
+void
+lqr_Stop(struct physical *physical, int method)
+{
+ if (method == LQM_LQR)
+ log_Printf(LogLQM, "%s: Stop sending LQR, Use LCP ECHO instead.\n",
+ physical->link.name);
+ if (method == LQM_ECHO)
+ log_Printf(LogLQM, "%s: Stop sending LCP ECHO.\n",
+ physical->link.name);
+ physical->hdlc.lqm.method &= ~method;
+ if (physical->hdlc.lqm.method)
+ SendLqrReport(physical->hdlc.lqm.owner);
+ else
+ timer_Stop(&physical->hdlc.lqm.timer);
+}
+
+void
+lqr_Dump(const char *link, const char *message, const struct lqrdata *lqr)
+{
+ if (log_IsKept(LogLQM)) {
+ log_Printf(LogLQM, "%s: %s:\n", link, message);
+ log_Printf(LogLQM, " Magic: %08x LastOutLQRs: %08x\n",
+ lqr->MagicNumber, lqr->LastOutLQRs);
+ log_Printf(LogLQM, " LastOutPackets: %08x LastOutOctets: %08x\n",
+ lqr->LastOutPackets, lqr->LastOutOctets);
+ log_Printf(LogLQM, " PeerInLQRs: %08x PeerInPackets: %08x\n",
+ lqr->PeerInLQRs, lqr->PeerInPackets);
+ log_Printf(LogLQM, " PeerInDiscards: %08x PeerInErrors: %08x\n",
+ lqr->PeerInDiscards, lqr->PeerInErrors);
+ log_Printf(LogLQM, " PeerInOctets: %08x PeerOutLQRs: %08x\n",
+ lqr->PeerInOctets, lqr->PeerOutLQRs);
+ log_Printf(LogLQM, " PeerOutPackets: %08x PeerOutOctets: %08x\n",
+ lqr->PeerOutPackets, lqr->PeerOutOctets);
+ }
+}
+
+void
+lqr_Analyse(const struct hdlc *hdlc, const struct lqrdata *oldlqr,
+ const struct lqrdata *newlqr)
+{
+ u_int32_t LQRs, transitLQRs, pkts, octets, disc, err;
+
+ if (!newlqr->PeerInLQRs) /* No analysis possible yet! */
+ return;
+
+ log_Printf(LogLQM, "Analysis:\n");
+
+ LQRs = (newlqr->LastOutLQRs - oldlqr->LastOutLQRs) -
+ (newlqr->PeerInLQRs - oldlqr->PeerInLQRs);
+ transitLQRs = hdlc->lqm.lqr.OutLQRs - newlqr->LastOutLQRs;
+ pkts = (newlqr->LastOutPackets - oldlqr->LastOutPackets) -
+ (newlqr->PeerInPackets - oldlqr->PeerInPackets);
+ octets = (newlqr->LastOutOctets - oldlqr->LastOutOctets) -
+ (newlqr->PeerInOctets - oldlqr->PeerInOctets);
+ log_Printf(LogLQM, " Outbound lossage: %d LQR%s (%d en route), %d packet%s,"
+ " %d octet%s\n", (int)LQRs, LQRs == 1 ? "" : "s", (int)transitLQRs,
+ (int)pkts, pkts == 1 ? "" : "s",
+ (int)octets, octets == 1 ? "" : "s");
+
+ pkts = (newlqr->PeerOutPackets - oldlqr->PeerOutPackets) -
+ (hdlc->lqm.lqr.Save.InPackets - hdlc->lqm.lqr.prevSave.InPackets);
+ octets = (newlqr->PeerOutOctets - oldlqr->PeerOutOctets) -
+ (hdlc->lqm.lqr.Save.InOctets - hdlc->lqm.lqr.prevSave.InOctets);
+ log_Printf(LogLQM, " Inbound lossage: %d packet%s, %d octet%s\n",
+ (int)pkts, pkts == 1 ? "" : "s",
+ (int)octets, octets == 1 ? "" : "s");
+
+ disc = newlqr->PeerInDiscards - oldlqr->PeerInDiscards;
+ err = newlqr->PeerInErrors - oldlqr->PeerInErrors;
+ if (disc && err)
+ log_Printf(LogLQM, " Likely due to both peer congestion"
+ " and physical errors\n");
+ else if (disc)
+ log_Printf(LogLQM, " Likely due to peer congestion\n");
+ else if (err)
+ log_Printf(LogLQM, " Likely due to physical errors\n");
+ else if (pkts)
+ log_Printf(LogLQM, " Likely due to transport "
+ "congestion\n");
+}
+
+static struct mbuf *
+lqr_LayerPush(struct bundle *b __unused, struct link *l, struct mbuf *bp,
+ int pri __unused, u_short *proto)
+{
+ struct physical *p = link2physical(l);
+ int len, layer;
+
+ if (!p) {
+ /* Oops - can't happen :-] */
+ m_freem(bp);
+ return NULL;
+ }
+
+ bp = m_pullup(bp);
+ len = m_length(bp);
+
+ /*-
+ * From rfc1989:
+ *
+ * All octets which are included in the FCS calculation MUST be counted,
+ * including the packet header, the information field, and any padding.
+ * The FCS octets MUST also be counted, and one flag octet per frame
+ * MUST be counted. All other octets (such as additional flag
+ * sequences, and escape bits or octets) MUST NOT be counted.
+ *
+ * As we're stacked higher than the HDLC layer (otherwise HDLC wouldn't be
+ * able to calculate the FCS), we must not forget about these additional
+ * bytes when we're asynchronous.
+ *
+ * We're also expecting to be stacked *before* the likes of the proto and
+ * acf layers (to avoid alignment issues), so deal with this too.
+ */
+
+ p->hdlc.lqm.ifOutUniPackets++;
+ p->hdlc.lqm.ifOutOctets += len + 1; /* plus 1 flag octet! */
+ for (layer = 0; layer < l->nlayers; layer++)
+ switch (l->layer[layer]->type) {
+ case LAYER_ACF:
+ p->hdlc.lqm.ifOutOctets += acf_WrapperOctets(&l->lcp, *proto);
+ break;
+ case LAYER_ASYNC:
+ /* Not included - see rfc1989 */
+ break;
+ case LAYER_HDLC:
+ p->hdlc.lqm.ifOutOctets += hdlc_WrapperOctets();
+ break;
+ case LAYER_LQR:
+ layer = l->nlayers;
+ break;
+ case LAYER_PROTO:
+ p->hdlc.lqm.ifOutOctets += proto_WrapperOctets(&l->lcp, *proto);
+ break;
+ case LAYER_SYNC:
+ /* Nothing to add on */
+ break;
+ default:
+ log_Printf(LogWARN, "Oops, don't know how to do octets for %s layer\n",
+ l->layer[layer]->name);
+ break;
+ }
+
+ if (*proto == PROTO_LQR) {
+ /* Overwrite the entire packet (created in SendLqrData()) */
+ struct lqrdata lqr;
+ size_t pending_pkts, pending_octets;
+
+ p->hdlc.lqm.lqr.OutLQRs++;
+
+ /*
+ * We need to compensate for the fact that we're pushing our data
+ * onto the highest priority queue by factoring out packet & octet
+ * values from other queues!
+ */
+ link_PendingLowPriorityData(l, &pending_pkts, &pending_octets);
+
+ memset(&lqr, '\0', sizeof lqr);
+ lqr.MagicNumber = p->link.lcp.want_magic;
+ lqr.LastOutLQRs = p->hdlc.lqm.lqr.peer.PeerOutLQRs;
+ lqr.LastOutPackets = p->hdlc.lqm.lqr.peer.PeerOutPackets;
+ lqr.LastOutOctets = p->hdlc.lqm.lqr.peer.PeerOutOctets;
+ lqr.PeerInLQRs = p->hdlc.lqm.lqr.Save.InLQRs;
+ lqr.PeerInPackets = p->hdlc.lqm.lqr.Save.InPackets;
+ lqr.PeerInDiscards = p->hdlc.lqm.lqr.Save.InDiscards;
+ lqr.PeerInErrors = p->hdlc.lqm.lqr.Save.InErrors;
+ lqr.PeerInOctets = p->hdlc.lqm.lqr.Save.InOctets;
+ lqr.PeerOutLQRs = p->hdlc.lqm.lqr.OutLQRs;
+ lqr.PeerOutPackets = p->hdlc.lqm.ifOutUniPackets - pending_pkts;
+ /* Don't forget our ``flag'' octets.... */
+ lqr.PeerOutOctets = p->hdlc.lqm.ifOutOctets - pending_octets - pending_pkts;
+ lqr_Dump(l->name, "Output", &lqr);
+ lqr_ChangeOrder(&lqr, (struct lqrdata *)MBUF_CTOP(bp));
+ }
+
+ return bp;
+}
+
+static struct mbuf *
+lqr_LayerPull(struct bundle *b __unused, struct link *l __unused,
+ struct mbuf *bp, u_short *proto)
+{
+ /*
+ * This is the ``Rx'' process from rfc1989, although a part of it is
+ * actually performed by sync_LayerPull() & hdlc_LayerPull() so that
+ * our octet counts are correct.
+ */
+
+ if (*proto == PROTO_LQR)
+ m_settype(bp, MB_LQRIN);
+ return bp;
+}
+
+/*
+ * Statistics for pulled packets are recorded either in hdlc_PullPacket()
+ * or sync_PullPacket()
+ */
+
+struct layer lqrlayer = { LAYER_LQR, "lqr", lqr_LayerPush, lqr_LayerPull };
diff --git a/usr.sbin/ppp/lqr.h b/usr.sbin/ppp/lqr.h
new file mode 100644
index 0000000..13d378f
--- /dev/null
+++ b/usr.sbin/ppp/lqr.h
@@ -0,0 +1,82 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+/*
+ * Structure of LQR packet defined in RFC1989
+ */
+struct lqrdata {
+ u_int32_t MagicNumber;
+ u_int32_t LastOutLQRs; /* most recently received PeerOutLQRs */
+ u_int32_t LastOutPackets; /* most recently received PeerOutPackets */
+ u_int32_t LastOutOctets; /* most recently received PeerOutOctets */
+ u_int32_t PeerInLQRs; /* Peers SaveInLQRs */
+ u_int32_t PeerInPackets; /* Peers SaveInPackets */
+ u_int32_t PeerInDiscards; /* Peers SaveInDiscards */
+ u_int32_t PeerInErrors; /* Peers SaveInErrors */
+ u_int32_t PeerInOctets; /* Peers SaveInOctets */
+ u_int32_t PeerOutLQRs; /* Peers OutLQRs (hdlc.h) */
+ u_int32_t PeerOutPackets; /* Peers OutPackets (hdlc.h) */
+ u_int32_t PeerOutOctets; /* Peers OutOctets (hdlc.h) */
+};
+
+struct lqrsavedata { /* Saved on receipt of an LQR */
+ u_int32_t InLQRs; /* From ifInLQRs */
+ u_int32_t InPackets; /* From ifInPackets */
+ u_int32_t InDiscards; /* From ifInDiscards */
+ u_int32_t InErrors; /* From ifInErrors */
+ u_int32_t InOctets; /* From InGoodOctets ! */
+};
+
+/*
+ * We support LQR and ECHO as LQM method
+ */
+#define LQM_LQR 1
+#define LQM_ECHO 2
+
+struct mbuf;
+struct physical;
+struct lcp;
+struct fsm;
+struct hdlc;
+struct link;
+struct bundle;
+
+extern void lqr_Dump(const char *, const char *, const struct lqrdata *);
+extern void lqr_Analyse(const struct hdlc *, const struct lqrdata *,
+ const struct lqrdata *);
+extern void lqr_ChangeOrder(struct lqrdata *, struct lqrdata *);
+extern void lqr_Start(struct lcp *);
+extern void lqr_reStart(struct lcp *);
+extern void lqr_Stop(struct physical *, int);
+extern void lqr_StopTimer(struct physical *);
+extern struct mbuf *lqr_RecvEcho(struct fsm *, struct mbuf *);
+extern struct mbuf *lqr_Input(struct bundle *, struct link *, struct mbuf *);
+
+extern struct layer lqrlayer;
diff --git a/usr.sbin/ppp/main.c b/usr.sbin/ppp/main.c
new file mode 100644
index 0000000..de867ea
--- /dev/null
+++ b/usr.sbin/ppp/main.c
@@ -0,0 +1,680 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/un.h>
+#include <sys/socket.h>
+#include <net/route.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <termios.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#ifndef NONAT
+#ifdef LOCALNAT
+#include "alias.h"
+#else
+#include <alias.h>
+#endif
+#endif
+
+#include "layer.h"
+#include "probe.h"
+#include "mbuf.h"
+#include "log.h"
+#include "defs.h"
+#include "id.h"
+#include "timer.h"
+#include "fsm.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "link.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "auth.h"
+#include "systems.h"
+#include "sig.h"
+#include "main.h"
+#include "server.h"
+#include "prompt.h"
+#include "chat.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+#include "iface.h"
+
+#ifndef O_NONBLOCK
+#ifdef O_NDELAY
+#define O_NONBLOCK O_NDELAY
+#endif
+#endif
+
+static void DoLoop(struct bundle *);
+static void TerminalStop(int);
+
+static struct bundle *SignalBundle;
+static struct prompt *SignalPrompt;
+struct libalias *la;
+
+void
+Cleanup()
+{
+ SignalBundle->CleaningUp = 1;
+ bundle_Close(SignalBundle, NULL, CLOSE_STAYDOWN);
+}
+
+void
+AbortProgram(int excode)
+{
+ if (SignalBundle)
+ server_Close(SignalBundle);
+ log_Printf(LogPHASE, "PPP Terminated (%s).\n", ex_desc(excode));
+ if (SignalBundle) {
+ bundle_Close(SignalBundle, NULL, CLOSE_STAYDOWN);
+ bundle_Destroy(SignalBundle);
+ }
+ log_Close();
+ exit(excode);
+}
+
+static void
+CloseConnection(int signo)
+{
+ /* NOTE, these are manual, we've done a setsid() */
+ sig_signal(SIGINT, SIG_IGN);
+ log_Printf(LogPHASE, "Caught signal %d, abort connection(s)\n", signo);
+ bundle_Down(SignalBundle, CLOSE_STAYDOWN);
+ sig_signal(SIGINT, CloseConnection);
+}
+
+static void
+CloseSession(int signo)
+{
+ log_Printf(LogPHASE, "Signal %d, terminate.\n", signo);
+ Cleanup();
+}
+
+static pid_t BGPid = 0;
+
+static void
+KillChild(int signo)
+{
+ signal(signo, SIG_IGN);
+ log_Printf(LogPHASE, "Parent: Signal %d\n", signo);
+ kill(BGPid, SIGINT);
+}
+
+static void
+TerminalCont(int signo __unused)
+{
+ signal(SIGCONT, SIG_DFL);
+ prompt_Continue(SignalPrompt);
+}
+
+static void
+TerminalStop(int signo __unused)
+{
+ prompt_Suspend(SignalPrompt);
+ signal(SIGCONT, TerminalCont);
+ raise(SIGSTOP);
+}
+
+static void
+BringDownServer(int signo __unused)
+{
+ /* Drops all child prompts too ! */
+ if (server_Close(SignalBundle))
+ log_Printf(LogPHASE, "Closed server socket\n");
+}
+
+static void
+RestartServer(int signo __unused)
+{
+ /* Drops all child prompts and re-opens the socket */
+ server_Reopen(SignalBundle);
+}
+
+static void
+Usage(void)
+{
+ fprintf(stderr, "usage: ppp [-auto | -foreground | -background | -direct |"
+ " -dedicated | -ddial | -interactive]"
+#ifndef NONAT
+ " [-nat]"
+#endif
+ " [-quiet] [-unit N] [system ...]\n");
+ exit(EX_START);
+}
+
+struct switches {
+ unsigned nat : 1;
+ unsigned fg : 1;
+ unsigned quiet : 1;
+ int mode;
+ int unit;
+};
+
+static int
+ProcessArgs(int argc, char **argv, struct switches *sw)
+{
+ int optc, newmode, arg;
+ char *cp;
+
+ optc = 0;
+ memset(sw, '\0', sizeof *sw);
+ sw->mode = PHYS_INTERACTIVE;
+ sw->unit = -1;
+
+ for (arg = 1; arg < argc && *argv[arg] == '-'; arg++, optc++) {
+ cp = argv[arg] + 1;
+ newmode = Nam2mode(cp);
+ switch (newmode) {
+ case PHYS_NONE:
+ if (strcmp(cp, "nat") == 0) {
+#ifdef NONAT
+ log_Printf(LogWARN, "%s ignored: NAT is compiled out\n", argv[arg]);
+#else
+ sw->nat = 1;
+#endif
+ optc--; /* this option isn't exclusive */
+ } else if (strcmp(cp, "alias") == 0) {
+#ifdef NONAT
+ log_Printf(LogWARN, "%s ignored: NAT is compiled out\n", argv[arg]);
+ fprintf(stderr, "%s ignored: NAT is compiled out\n", argv[arg]);
+#else
+ log_Printf(LogWARN, "%s is deprecated\n", argv[arg]);
+ fprintf(stderr, "%s is deprecated\n", argv[arg]);
+ sw->nat = 1;
+#endif
+ optc--; /* this option isn't exclusive */
+ } else if (strncmp(cp, "unit", 4) == 0) {
+ optc--; /* this option isn't exclusive */
+ if (cp[4] == '\0') {
+ optc--; /* nor is the argument */
+ if (++arg == argc) {
+ fprintf(stderr, "-unit: Expected unit number\n");
+ Usage();
+ } else
+ sw->unit = atoi(argv[arg]);
+ } else
+ sw->unit = atoi(cp + 4);
+ } else if (strcmp(cp, "quiet") == 0) {
+ sw->quiet = 1;
+ optc--; /* this option isn't exclusive */
+ } else
+ Usage();
+ break;
+
+ case PHYS_ALL:
+ Usage();
+ break;
+
+ default:
+ sw->mode = newmode;
+ if (newmode == PHYS_FOREGROUND)
+ sw->fg = 1;
+ }
+ }
+
+ if (optc > 1) {
+ fprintf(stderr, "You may specify only one mode.\n");
+ exit(EX_START);
+ }
+
+ if (sw->mode == PHYS_AUTO && arg == argc) {
+ fprintf(stderr, "A system must be specified in auto mode.\n");
+ exit(EX_START);
+ }
+
+ return arg; /* Don't SetLabel yet ! */
+}
+
+static void
+CheckLabel(const char *label, struct prompt *prompt, int mode)
+{
+ const char *err;
+
+ if ((err = system_IsValid(label, prompt, mode)) != NULL) {
+ fprintf(stderr, "%s: %s\n", label, err);
+ if (mode == PHYS_DIRECT)
+ log_Printf(LogWARN, "Label %s rejected -direct connection: %s\n",
+ label, err);
+ log_Close();
+ exit(1);
+ }
+}
+
+
+int
+main(int argc, char **argv)
+{
+ char *name;
+ const char *lastlabel;
+ int arg, holdfd[3], label;
+ unsigned f;
+ struct bundle *bundle;
+ struct prompt *prompt;
+ struct switches sw;
+
+ probe_Init();
+
+ /*
+ * We open 3 descriptors to ensure that STDIN_FILENO, STDOUT_FILENO and
+ * STDERR_FILENO are always open. These are closed before DoLoop(),
+ * but *after* we've avoided the possibility of erroneously closing
+ * an important descriptor with close(STD{IN,OUT,ERR}_FILENO).
+ */
+ if ((holdfd[0] = open(_PATH_DEVNULL, O_RDWR)) == -1) {
+ fprintf(stderr, "Cannot open %s !\n", _PATH_DEVNULL);
+ return 2;
+ }
+ for (f = 1; f < sizeof holdfd / sizeof *holdfd; f++)
+ holdfd[f] = dup(holdfd[0]);
+
+ name = strrchr(argv[0], '/');
+ log_Open(name ? name + 1 : argv[0]);
+
+#ifndef NONAT
+ la = LibAliasInit(NULL);
+#endif
+ label = ProcessArgs(argc, argv, &sw);
+
+ /*
+ * A FreeBSD & OpenBSD hack to dodge a bug in the tty driver that drops
+ * output occasionally.... I must find the real reason some time. To
+ * display the dodgy behaviour, comment out this bit, make yourself a large
+ * routing table and then run ppp in interactive mode. The `show route'
+ * command will drop chunks of data !!!
+ */
+ if (sw.mode == PHYS_INTERACTIVE) {
+ close(STDIN_FILENO);
+ if (open(_PATH_TTY, O_RDONLY) != STDIN_FILENO) {
+ fprintf(stderr, "Cannot open %s for input !\n", _PATH_TTY);
+ return 2;
+ }
+ }
+
+ /* Allow output for the moment (except in direct mode) */
+ if (sw.mode == PHYS_DIRECT)
+ prompt = NULL;
+ else
+ SignalPrompt = prompt = prompt_Create(NULL, NULL, PROMPT_STD);
+
+ ID0init();
+ if (ID0realuid() != 0) {
+ char conf[200], *ptr;
+
+ snprintf(conf, sizeof conf, "%s/%s", PPP_CONFDIR, CONFFILE);
+ do {
+ struct stat sb;
+
+ if (stat(conf, &sb) == 0 && sb.st_mode & S_IWOTH) {
+ log_Printf(LogALERT, "ppp: Access violation: Please protect %s\n",
+ conf);
+ return -1;
+ }
+ ptr = conf + strlen(conf)-2;
+ while (ptr > conf && *ptr != '/')
+ *ptr-- = '\0';
+ } while (ptr >= conf);
+ }
+
+ if (label < argc)
+ for (arg = label; arg < argc; arg++)
+ CheckLabel(argv[arg], prompt, sw.mode);
+ else
+ CheckLabel("default", prompt, sw.mode);
+
+ if (!sw.quiet)
+ prompt_Printf(prompt, "Working in %s mode\n", mode2Nam(sw.mode));
+
+ if ((bundle = bundle_Create(TUN_PREFIX, sw.mode, sw.unit)) == NULL)
+ return EX_START;
+
+ /* NOTE: We may now have changed argv[1] via a ``set proctitle'' */
+
+ SignalBundle = bundle;
+ bundle->NatEnabled = sw.nat;
+ if (sw.nat)
+ opt_enable(bundle, OPT_IFACEALIAS);
+
+ if (system_Select(bundle, "default", CONFFILE, prompt, NULL) < 0)
+ prompt_Printf(prompt, "Warning: No default entry found in config file.\n");
+
+ sig_signal(SIGHUP, CloseSession);
+ sig_signal(SIGTERM, CloseSession);
+ sig_signal(SIGINT, CloseConnection);
+ sig_signal(SIGQUIT, CloseSession);
+ sig_signal(SIGALRM, SIG_IGN);
+ signal(SIGPIPE, SIG_IGN);
+
+ if (sw.mode == PHYS_INTERACTIVE)
+ sig_signal(SIGTSTP, TerminalStop);
+
+ sig_signal(SIGUSR1, RestartServer);
+ sig_signal(SIGUSR2, BringDownServer);
+
+ lastlabel = argv[argc - 1];
+ for (arg = label; arg < argc; arg++) {
+ /* In case we use LABEL or ``set enddisc label'' */
+ bundle_SetLabel(bundle, lastlabel);
+ system_Select(bundle, argv[arg], CONFFILE, prompt, NULL);
+ }
+
+ if (label < argc)
+ /* In case the last label did a ``load'' */
+ bundle_SetLabel(bundle, lastlabel);
+
+ if (sw.mode == PHYS_AUTO &&
+ ncprange_family(&bundle->ncp.ipcp.cfg.peer_range) == AF_UNSPEC) {
+ prompt_Printf(prompt, "You must ``set ifaddr'' with a peer address "
+ "in auto mode.\n");
+ AbortProgram(EX_START);
+ }
+
+ if (prompt) {
+ prompt->bundle = bundle; /* couldn't do it earlier */
+ if (!sw.quiet)
+ prompt_Printf(prompt, "Using interface: %s\n", bundle->iface->name);
+ }
+
+ if (sw.mode != PHYS_INTERACTIVE) {
+ if (sw.mode != PHYS_DIRECT) {
+ if (!sw.fg) {
+ int bgpipe[2];
+ pid_t bgpid;
+
+ if (sw.mode == PHYS_BACKGROUND && pipe(bgpipe)) {
+ log_Printf(LogERROR, "pipe: %s\n", strerror(errno));
+ AbortProgram(EX_SOCK);
+ }
+
+ bgpid = fork();
+ if (bgpid == -1) {
+ log_Printf(LogERROR, "fork: %s\n", strerror(errno));
+ AbortProgram(EX_SOCK);
+ }
+
+ if (bgpid) {
+ char c = EX_NORMAL;
+ int ret;
+
+ if (sw.mode == PHYS_BACKGROUND) {
+ close(bgpipe[1]);
+ BGPid = bgpid;
+ /* If we get a signal, kill the child */
+ signal(SIGHUP, KillChild);
+ signal(SIGTERM, KillChild);
+ signal(SIGINT, KillChild);
+ signal(SIGQUIT, KillChild);
+
+ /* Wait for our child to close its pipe before we exit */
+ while ((ret = read(bgpipe[0], &c, 1)) == 1) {
+ switch (c) {
+ case EX_NORMAL:
+ if (!sw.quiet) {
+ prompt_Printf(prompt, "PPP enabled\n");
+ log_Printf(LogPHASE, "Parent: PPP enabled\n");
+ }
+ break;
+ case EX_REDIAL:
+ if (!sw.quiet)
+ prompt_Printf(prompt, "Attempting redial\n");
+ continue;
+ case EX_RECONNECT:
+ if (!sw.quiet)
+ prompt_Printf(prompt, "Attempting reconnect\n");
+ continue;
+ default:
+ prompt_Printf(prompt, "Child failed (%s)\n",
+ ex_desc((int)c));
+ log_Printf(LogPHASE, "Parent: Child failed (%s)\n",
+ ex_desc((int) c));
+ }
+ break;
+ }
+ if (ret != 1) {
+ prompt_Printf(prompt, "Child exit, no status.\n");
+ log_Printf(LogPHASE, "Parent: Child exit, no status.\n");
+ }
+ close(bgpipe[0]);
+ }
+ return c;
+ } else if (sw.mode == PHYS_BACKGROUND) {
+ close(bgpipe[0]);
+ bundle->notify.fd = bgpipe[1];
+ }
+
+ bundle_ChangedPID(bundle);
+ bundle_LockTun(bundle); /* we have a new pid */
+ }
+
+ /* -auto, -dedicated, -ddial, -foreground & -background */
+ prompt_Destroy(prompt, 0);
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+ close(STDIN_FILENO);
+ if (!sw.fg)
+ setsid();
+ } else {
+ /*
+ * -direct - STDIN_FILENO gets used by physical_Open. STDOUT_FILENO
+ * *may* get used in exec/pipe mode.
+ */
+ prompt_TtyInit(NULL);
+ close(STDERR_FILENO);
+ }
+ } else {
+ /* -interactive */
+ close(STDERR_FILENO);
+ prompt_TtyInit(prompt);
+ prompt_TtyCommandMode(prompt);
+ prompt_Required(prompt);
+ }
+
+ /* We can get rid of these now */
+ for (f = 0; f < sizeof holdfd / sizeof *holdfd; f++)
+ close(holdfd[f]);
+
+ log_Printf(LogPHASE, "PPP Started (%s mode).\n", mode2Nam(sw.mode));
+ DoLoop(bundle);
+ AbortProgram(EX_NORMAL);
+
+ return EX_NORMAL;
+}
+
+static void
+DoLoop(struct bundle *bundle)
+{
+ fd_set *rfds, *wfds, *efds;
+ int i, nfds, nothing_done;
+
+ if ((rfds = mkfdset()) == NULL) {
+ log_Printf(LogERROR, "DoLoop: Cannot create fd_set\n");
+ return;
+ }
+
+ if ((wfds = mkfdset()) == NULL) {
+ log_Printf(LogERROR, "DoLoop: Cannot create fd_set\n");
+ free(rfds);
+ return;
+ }
+
+ if ((efds = mkfdset()) == NULL) {
+ log_Printf(LogERROR, "DoLoop: Cannot create fd_set\n");
+ free(rfds);
+ free(wfds);
+ return;
+ }
+
+ for (; !bundle_IsDead(bundle); bundle_CleanDatalinks(bundle)) {
+ nfds = 0;
+ zerofdset(rfds);
+ zerofdset(wfds);
+ zerofdset(efds);
+
+ /* All our datalinks, the tun device and the MP socket */
+ descriptor_UpdateSet(&bundle->desc, rfds, wfds, efds, &nfds);
+
+ /* All our prompts and the diagnostic socket */
+ descriptor_UpdateSet(&server.desc, rfds, NULL, NULL, &nfds);
+
+ bundle_CleanDatalinks(bundle);
+ if (bundle_IsDead(bundle))
+ /* Don't select - we'll be here forever */
+ break;
+
+ /*
+ * It's possible that we've had a signal since we last checked. If
+ * we don't check again before calling select(), we may end up stuck
+ * after having missed the event.... sig_Handle() tries to be as
+ * quick as possible if nothing is likely to have happened.
+ * This is only really likely if we block in open(... O_NONBLOCK)
+ * which will happen with a misconfigured device.
+ */
+ if (sig_Handle())
+ continue;
+
+ i = select(nfds, rfds, wfds, efds, NULL);
+
+ if (i < 0 && errno != EINTR) {
+ log_Printf(LogERROR, "DoLoop: select(): %s\n", strerror(errno));
+ if (log_IsKept(LogTIMER)) {
+ struct timeval t;
+
+ for (i = 0; i <= nfds; i++) {
+ if (FD_ISSET(i, rfds)) {
+ log_Printf(LogTIMER, "Read set contains %d\n", i);
+ FD_CLR(i, rfds);
+ t.tv_sec = t.tv_usec = 0;
+ if (select(nfds, rfds, wfds, efds, &t) != -1) {
+ log_Printf(LogTIMER, "The culprit !\n");
+ break;
+ }
+ }
+ if (FD_ISSET(i, wfds)) {
+ log_Printf(LogTIMER, "Write set contains %d\n", i);
+ FD_CLR(i, wfds);
+ t.tv_sec = t.tv_usec = 0;
+ if (select(nfds, rfds, wfds, efds, &t) != -1) {
+ log_Printf(LogTIMER, "The culprit !\n");
+ break;
+ }
+ }
+ if (FD_ISSET(i, efds)) {
+ log_Printf(LogTIMER, "Error set contains %d\n", i);
+ FD_CLR(i, efds);
+ t.tv_sec = t.tv_usec = 0;
+ if (select(nfds, rfds, wfds, efds, &t) != -1) {
+ log_Printf(LogTIMER, "The culprit !\n");
+ break;
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ log_Printf(LogTIMER, "Select returns %d\n", i);
+
+ sig_Handle();
+
+ if (i <= 0)
+ continue;
+
+ for (i = 0; i <= nfds; i++)
+ if (FD_ISSET(i, efds)) {
+ log_Printf(LogPHASE, "Exception detected on descriptor %d\n", i);
+ /* We deal gracefully with link descriptor exceptions */
+ if (!bundle_Exception(bundle, i)) {
+ log_Printf(LogERROR, "Exception cannot be handled !\n");
+ break;
+ }
+ }
+
+ if (i <= nfds)
+ break;
+
+ nothing_done = 1;
+
+ if (descriptor_IsSet(&server.desc, rfds)) {
+ descriptor_Read(&server.desc, bundle, rfds);
+ nothing_done = 0;
+ }
+
+ if (descriptor_IsSet(&bundle->desc, rfds)) {
+ descriptor_Read(&bundle->desc, bundle, rfds);
+ nothing_done = 0;
+ }
+
+ if (descriptor_IsSet(&bundle->desc, wfds))
+ if (descriptor_Write(&bundle->desc, bundle, wfds) <= 0 && nothing_done) {
+ /*
+ * This is disastrous. The OS has told us that something is
+ * writable, and all our write()s have failed. Rather than
+ * going back immediately to do our UpdateSet()s and select(),
+ * we sleep for a bit to avoid gobbling up all cpu time.
+ */
+ struct timeval t;
+
+ t.tv_sec = 0;
+ t.tv_usec = 100000;
+ select(0, NULL, NULL, NULL, &t);
+ }
+ }
+
+ log_Printf(LogDEBUG, "DoLoop done.\n");
+}
diff --git a/usr.sbin/ppp/main.h b/usr.sbin/ppp/main.h
new file mode 100644
index 0000000..0c7399a
--- /dev/null
+++ b/usr.sbin/ppp/main.h
@@ -0,0 +1,32 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+extern void Cleanup(void);
+extern void AbortProgram(int);
diff --git a/usr.sbin/ppp/mbuf.c b/usr.sbin/ppp/mbuf.c
new file mode 100644
index 0000000..4773224
--- /dev/null
+++ b/usr.sbin/ppp/mbuf.c
@@ -0,0 +1,440 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <termios.h>
+
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "descriptor.h"
+#include "prompt.h"
+#include "main.h"
+
+#define BUCKET_CHUNK 20
+#define BUCKET_HASH 256
+
+struct mbucket;
+
+struct mfree {
+ struct mbucket *next;
+ size_t count;
+};
+
+static struct mbucket {
+ union {
+ struct mbuf m;
+ struct mfree f;
+ } u;
+} *bucket[(M_MAXLEN + sizeof(struct mbuf)) / BUCKET_HASH];
+
+#define M_BINDEX(sz) (((sz) + sizeof(struct mbuf) - 1) / BUCKET_HASH)
+#define M_BUCKET(sz) (bucket + M_BINDEX(sz))
+#define M_ROUNDUP(sz) ((M_BINDEX(sz) + 1) * BUCKET_HASH)
+
+static struct memmap {
+ struct mbuf *queue;
+ size_t fragments;
+ size_t octets;
+} MemMap[MB_MAX + 1];
+
+static unsigned long long mbuf_Mallocs, mbuf_Frees;
+
+size_t
+m_length(struct mbuf *bp)
+{
+ size_t len;
+
+ for (len = 0; bp; bp = bp->m_next)
+ len += bp->m_len;
+ return len;
+}
+
+static const char *
+mbuftype(int type)
+{
+ static const char * const mbufdesc[MB_MAX] = {
+ "ip in", "ip out", "ipv6 in", "ipv6 out", "nat in", "nat out",
+ "mp in", "mp out", "vj in", "vj out", "icompd in", "icompd out",
+ "compd in", "compd out", "lqr in", "lqr out", "echo in", "echo out",
+ "proto in", "proto out", "acf in", "acf out", "sync in", "sync out",
+ "hdlc in", "hdlc out", "async in", "async out", "cbcp in", "cbcp out",
+ "chap in", "chap out", "pap in", "pap out", "ccp in", "ccp out",
+ "ipcp in", "ipcp out", "ipv6cp in", "ipv6cp out", "lcp in", "lcp out"
+ };
+
+ return type < 0 || type >= MB_MAX ? "unknown" : mbufdesc[type];
+}
+
+struct mbuf *
+m_get(size_t m_len, int type)
+{
+ struct mbucket **mb;
+ struct mbuf *bp;
+ size_t size;
+
+ if (type > MB_MAX) {
+ log_Printf(LogERROR, "Bad mbuf type %d\n", type);
+ type = MB_UNKNOWN;
+ }
+
+ if (m_len > M_MAXLEN || m_len == 0) {
+ log_Printf(LogERROR, "Request for mbuf size %lu (\"%s\") denied !\n",
+ (u_long)m_len, mbuftype(type));
+ AbortProgram(EX_OSERR);
+ }
+
+ mb = M_BUCKET(m_len);
+ size = M_ROUNDUP(m_len);
+
+ if (*mb) {
+ /* We've got some free blocks of the right size */
+ bp = &(*mb)->u.m;
+ if (--(*mb)->u.f.count == 0)
+ *mb = (*mb)->u.f.next;
+ else {
+ ((struct mbucket *)((char *)*mb + size))->u.f.count = (*mb)->u.f.count;
+ *mb = (struct mbucket *)((char *)*mb + size);
+ (*mb)->u.f.next = NULL;
+ }
+ } else {
+ /*
+ * Allocate another chunk of mbufs, use the first and put the rest on
+ * the free list
+ */
+ *mb = (struct mbucket *)malloc(BUCKET_CHUNK * size);
+ if (*mb == NULL) {
+ log_Printf(LogALERT, "Failed to allocate memory (%lu)\n",
+ (unsigned long)BUCKET_CHUNK * size);
+ AbortProgram(EX_OSERR);
+ }
+ bp = &(*mb)->u.m;
+ *mb = (struct mbucket *)((char *)*mb + size);
+ (*mb)->u.f.count = BUCKET_CHUNK - 1;
+ (*mb)->u.f.next = NULL;
+ }
+
+ mbuf_Mallocs++;
+
+ memset(bp, '\0', sizeof(struct mbuf));
+ bp->m_size = size - sizeof *bp;
+ bp->m_len = m_len;
+ bp->m_type = type;
+
+ MemMap[type].fragments++;
+ MemMap[type].octets += bp->m_size;
+
+ return bp;
+}
+
+struct mbuf *
+m_free(struct mbuf *bp)
+{
+ struct mbucket **mb, *f;
+ struct mbuf *nbp;
+
+ if ((f = (struct mbucket *)bp) != NULL) {
+ MemMap[bp->m_type].fragments--;
+ MemMap[bp->m_type].octets -= bp->m_size;
+
+ nbp = bp->m_next;
+ mb = M_BUCKET(bp->m_size);
+ f->u.f.next = *mb;
+ f->u.f.count = 1;
+ *mb = f;
+
+ mbuf_Frees++;
+ bp = nbp;
+ }
+
+ return bp;
+}
+
+void
+m_freem(struct mbuf *bp)
+{
+ while (bp)
+ bp = m_free(bp);
+}
+
+struct mbuf *
+mbuf_Read(struct mbuf *bp, void *v, size_t len)
+{
+ int nb;
+ u_char *ptr = v;
+
+ while (bp && len > 0) {
+ if (len > bp->m_len)
+ nb = bp->m_len;
+ else
+ nb = len;
+ if (nb) {
+ memcpy(ptr, MBUF_CTOP(bp), nb);
+ ptr += nb;
+ bp->m_len -= nb;
+ len -= nb;
+ bp->m_offset += nb;
+ }
+ if (bp->m_len == 0)
+ bp = m_free(bp);
+ }
+
+ while (bp && bp->m_len == 0)
+ bp = m_free(bp);
+
+ return bp;
+}
+
+size_t
+mbuf_View(struct mbuf *bp, void *v, size_t len)
+{
+ size_t nb, l = len;
+ u_char *ptr = v;
+
+ while (bp && l > 0) {
+ if (l > bp->m_len)
+ nb = bp->m_len;
+ else
+ nb = l;
+ memcpy(ptr, MBUF_CTOP(bp), nb);
+ ptr += nb;
+ l -= nb;
+ bp = bp->m_next;
+ }
+
+ return len - l;
+}
+
+struct mbuf *
+m_prepend(struct mbuf *bp, const void *ptr, size_t len, u_short extra)
+{
+ struct mbuf *head;
+
+ if (bp && bp->m_offset) {
+ if (bp->m_offset >= len) {
+ bp->m_offset -= len;
+ bp->m_len += len;
+ if (ptr)
+ memcpy(MBUF_CTOP(bp), ptr, len);
+ return bp;
+ }
+ len -= bp->m_offset;
+ if (ptr)
+ memcpy(bp + 1, (const char *)ptr + len, bp->m_offset);
+ bp->m_len += bp->m_offset;
+ bp->m_offset = 0;
+ }
+
+ head = m_get(len + extra, bp ? bp->m_type : MB_UNKNOWN);
+ head->m_offset = extra;
+ head->m_len -= extra;
+ if (ptr)
+ memcpy(MBUF_CTOP(head), ptr, len);
+ head->m_next = bp;
+
+ return head;
+}
+
+struct mbuf *
+m_adj(struct mbuf *bp, ssize_t n)
+{
+ if (n > 0) {
+ while (bp) {
+ if ((size_t)n < bp->m_len) {
+ bp->m_len = n;
+ bp->m_offset += n;
+ return bp;
+ }
+ n -= bp->m_len;
+ bp = m_free(bp);
+ }
+ } else {
+ if ((n = m_length(bp) + n) <= 0) {
+ m_freem(bp);
+ return NULL;
+ }
+ for (; bp; bp = bp->m_next, n -= bp->m_len)
+ if ((size_t)n < bp->m_len) {
+ bp->m_len = n;
+ m_freem(bp->m_next);
+ bp->m_next = NULL;
+ break;
+ }
+ }
+
+ return bp;
+}
+
+void
+mbuf_Write(struct mbuf *bp, const void *ptr, size_t m_len)
+{
+ size_t plen;
+ int nb;
+
+ plen = m_length(bp);
+ if (plen < m_len)
+ m_len = plen;
+
+ while (m_len > 0) {
+ nb = (m_len < bp->m_len) ? m_len : bp->m_len;
+ memcpy(MBUF_CTOP(bp), ptr, nb);
+ m_len -= bp->m_len;
+ bp = bp->m_next;
+ }
+}
+
+int
+mbuf_Show(struct cmdargs const *arg)
+{
+ int i;
+
+ prompt_Printf(arg->prompt, "Fragments (octets) in use:\n");
+ for (i = 0; i < MB_MAX; i += 2)
+ prompt_Printf(arg->prompt, "%10.10s: %04lu (%06lu)\t"
+ "%10.10s: %04lu (%06lu)\n",
+ mbuftype(i), (u_long)MemMap[i].fragments,
+ (u_long)MemMap[i].octets, mbuftype(i+1),
+ (u_long)MemMap[i+1].fragments, (u_long)MemMap[i+1].octets);
+
+ if (i == MB_MAX)
+ prompt_Printf(arg->prompt, "%10.10s: %04lu (%06lu)\n",
+ mbuftype(i), (u_long)MemMap[i].fragments,
+ (u_long)MemMap[i].octets);
+
+ prompt_Printf(arg->prompt, "Mallocs: %llu, Frees: %llu\n",
+ mbuf_Mallocs, mbuf_Frees);
+
+ return 0;
+}
+
+struct mbuf *
+m_dequeue(struct mqueue *q)
+{
+ struct mbuf *bp;
+
+ log_Printf(LogDEBUG, "m_dequeue: queue len = %lu\n", (u_long)q->len);
+ bp = q->top;
+ if (bp) {
+ q->top = q->top->m_nextpkt;
+ q->len--;
+ if (q->top == NULL) {
+ q->last = q->top;
+ if (q->len)
+ log_Printf(LogERROR, "m_dequeue: Not zero (%lu)!!!\n",
+ (u_long)q->len);
+ }
+ bp->m_nextpkt = NULL;
+ }
+
+ return bp;
+}
+
+void
+m_enqueue(struct mqueue *queue, struct mbuf *bp)
+{
+ if (bp != NULL) {
+ if (queue->last) {
+ queue->last->m_nextpkt = bp;
+ queue->last = bp;
+ } else
+ queue->last = queue->top = bp;
+ queue->len++;
+ log_Printf(LogDEBUG, "m_enqueue: len = %lu\n", (unsigned long)queue->len);
+ }
+}
+
+struct mbuf *
+m_pullup(struct mbuf *bp)
+{
+ /* Put it all in one contigous (aligned) mbuf */
+
+ if (bp != NULL) {
+ if (bp->m_next != NULL) {
+ struct mbuf *nbp;
+ u_char *cp;
+
+ nbp = m_get(m_length(bp), bp->m_type);
+
+ for (cp = MBUF_CTOP(nbp); bp; bp = m_free(bp)) {
+ memcpy(cp, MBUF_CTOP(bp), bp->m_len);
+ cp += bp->m_len;
+ }
+ bp = nbp;
+ }
+#ifndef __i386__ /* Do any other archs not care about alignment ? */
+ else if ((bp->m_offset & (sizeof(long) - 1)) != 0) {
+ bcopy(MBUF_CTOP(bp), bp + 1, bp->m_len);
+ bp->m_offset = 0;
+ }
+#endif
+ }
+
+ return bp;
+}
+
+void
+m_settype(struct mbuf *bp, int type)
+{
+ for (; bp; bp = bp->m_next)
+ if (type != bp->m_type) {
+ MemMap[bp->m_type].fragments--;
+ MemMap[bp->m_type].octets -= bp->m_size;
+ bp->m_type = type;
+ MemMap[type].fragments++;
+ MemMap[type].octets += bp->m_size;
+ }
+}
+
+struct mbuf *
+m_append(struct mbuf *bp, const void *v, size_t sz)
+{
+ struct mbuf *m = bp;
+
+ if (m) {
+ while (m->m_next)
+ m = m->m_next;
+ if (m->m_size - m->m_len >= sz) {
+ if (v)
+ memcpy((char *)(m + 1) + m->m_len, v, sz);
+ m->m_len += sz;
+ } else
+ m->m_next = m_prepend(NULL, v, sz, 0);
+ } else
+ bp = m_prepend(NULL, v, sz, 0);
+
+ return bp;
+}
diff --git a/usr.sbin/ppp/mbuf.h b/usr.sbin/ppp/mbuf.h
new file mode 100644
index 0000000..980d649
--- /dev/null
+++ b/usr.sbin/ppp/mbuf.h
@@ -0,0 +1,119 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+struct mbuf {
+ size_t m_size; /* size allocated (excluding header) */
+ u_short m_offset; /* offset from header end to start position */
+ size_t m_len; /* available byte count in buffer */
+ short m_type; /* MB_* below */
+ struct mbuf *m_next; /* link to next mbuf */
+ struct mbuf *m_nextpkt; /* link to next packet */
+ unsigned long priv; /* private data - holds HDLC escape count */
+ /* buffer space is malloc()d directly after the header */
+};
+
+struct mqueue {
+ struct mbuf *top;
+ struct mbuf *last;
+ size_t len;
+};
+
+#define MBUF_CTOP(bp) \
+ ((bp) ? (u_char *)((bp)+1) + (bp)->m_offset : (u_char *)bp)
+
+#define CONST_MBUF_CTOP(bp) \
+ ((bp) ? (const u_char *)((bp)+1) + (bp)->m_offset : (const u_char *)bp)
+
+#define MB_IPIN 0
+#define MB_IPOUT 1
+#define MB_IPV6IN 2
+#define MB_IPV6OUT 3
+#define MB_NATIN 4
+#define MB_NATOUT 5
+#define MB_MPIN 6
+#define MB_MPOUT 7
+#define MB_VJIN 8
+#define MB_VJOUT 9
+#define MB_ICOMPDIN 10
+#define MB_ICOMPDOUT 11
+#define MB_COMPDIN 12
+#define MB_COMPDOUT 13
+#define MB_LQRIN 14
+#define MB_LQROUT 15
+#define MB_ECHOIN 16
+#define MB_ECHOOUT 17
+#define MB_PROTOIN 18
+#define MB_PROTOOUT 19
+#define MB_ACFIN 20
+#define MB_ACFOUT 21
+#define MB_SYNCIN 22
+#define MB_SYNCOUT 23
+#define MB_HDLCIN 24
+#define MB_HDLCOUT 25
+#define MB_ASYNCIN 26
+#define MB_ASYNCOUT 27
+#define MB_CBCPIN 28
+#define MB_CBCPOUT 29
+#define MB_CHAPIN 30
+#define MB_CHAPOUT 31
+#define MB_PAPIN 32
+#define MB_PAPOUT 33
+#define MB_CCPIN 34
+#define MB_CCPOUT 35
+#define MB_IPCPIN 36
+#define MB_IPCPOUT 37
+#define MB_IPV6CPIN 38
+#define MB_IPV6CPOUT 39
+#define MB_LCPIN 40
+#define MB_LCPOUT 41
+#define MB_UNKNOWN 42
+#define MB_MAX MB_UNKNOWN
+
+#define M_MAXLEN (4352 - sizeof(struct mbuf)) /* > HDLCSIZE */
+
+struct cmdargs;
+
+extern size_t m_length(struct mbuf *);
+extern struct mbuf *m_get(size_t, int);
+extern struct mbuf *m_free(struct mbuf *);
+extern void m_freem(struct mbuf *);
+extern void mbuf_Write(struct mbuf *, const void *, size_t);
+extern struct mbuf *mbuf_Read(struct mbuf *, void *, size_t);
+extern size_t mbuf_View(struct mbuf *, void *, size_t);
+extern struct mbuf *m_prepend(struct mbuf *, const void *, size_t, u_short);
+extern struct mbuf *m_adj(struct mbuf *, ssize_t);
+extern struct mbuf *m_pullup(struct mbuf *);
+extern void m_settype(struct mbuf *, int);
+extern struct mbuf *m_append(struct mbuf *, const void *, size_t);
+
+extern int mbuf_Show(struct cmdargs const *);
+
+extern void m_enqueue(struct mqueue *, struct mbuf *);
+extern struct mbuf *m_dequeue(struct mqueue *);
diff --git a/usr.sbin/ppp/mp.c b/usr.sbin/ppp/mp.c
new file mode 100644
index 0000000..b2cb46c
--- /dev/null
+++ b/usr.sbin/ppp/mp.c
@@ -0,0 +1,1209 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.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/param.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <net/if_dl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <paths.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#ifndef NONAT
+#include "nat_cmd.h"
+#endif
+#include "vjcomp.h"
+#include "ua.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "auth.h"
+#include "lcp.h"
+#include "async.h"
+#include "ccp.h"
+#include "link.h"
+#include "descriptor.h"
+#include "physical.h"
+#include "chat.h"
+#include "proto.h"
+#include "filter.h"
+#include "mp.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "prompt.h"
+#include "id.h"
+#include "arp.h"
+
+void
+peerid_Init(struct peerid *peer)
+{
+ peer->enddisc.class = 0;
+ *peer->enddisc.address = '\0';
+ peer->enddisc.len = 0;
+ *peer->authname = '\0';
+}
+
+int
+peerid_Equal(const struct peerid *p1, const struct peerid *p2)
+{
+ return !strcmp(p1->authname, p2->authname) &&
+ p1->enddisc.class == p2->enddisc.class &&
+ p1->enddisc.len == p2->enddisc.len &&
+ !memcmp(p1->enddisc.address, p2->enddisc.address, p1->enddisc.len);
+}
+
+static u_int32_t
+inc_seq(unsigned is12bit, u_int32_t seq)
+{
+ seq++;
+ if (is12bit) {
+ if (seq & 0xfffff000)
+ seq = 0;
+ } else if (seq & 0xff000000)
+ seq = 0;
+ return seq;
+}
+
+static int
+isbefore(unsigned is12bit, u_int32_t seq1, u_int32_t seq2)
+{
+ u_int32_t max = (is12bit ? 0xfff : 0xffffff) - 0x200;
+
+ if (seq1 > max) {
+ if (seq2 < 0x200 || seq2 > seq1)
+ return 1;
+ } else if ((seq1 > 0x200 || seq2 <= max) && seq1 < seq2)
+ return 1;
+
+ return 0;
+}
+
+static int
+mp_ReadHeader(struct mp *mp, struct mbuf *m, struct mp_header *header)
+{
+ if (mp->local_is12bit) {
+ u_int16_t val;
+
+ ua_ntohs(MBUF_CTOP(m), &val);
+ if (val & 0x3000) {
+ log_Printf(LogWARN, "Oops - MP header without required zero bits\n");
+ return 0;
+ }
+ header->begin = val & 0x8000 ? 1 : 0;
+ header->end = val & 0x4000 ? 1 : 0;
+ header->seq = val & 0x0fff;
+ return 2;
+ } else {
+ ua_ntohl(MBUF_CTOP(m), &header->seq);
+ if (header->seq & 0x3f000000) {
+ log_Printf(LogWARN, "Oops - MP header without required zero bits\n");
+ return 0;
+ }
+ header->begin = header->seq & 0x80000000 ? 1 : 0;
+ header->end = header->seq & 0x40000000 ? 1 : 0;
+ header->seq &= 0x00ffffff;
+ return 4;
+ }
+}
+
+static void
+mp_LayerStart(void *v __unused, struct fsm *fp __unused)
+{
+ /* The given FSM (ccp) is about to start up ! */
+}
+
+static void
+mp_LayerUp(void *v __unused, struct fsm *fp)
+{
+ /* The given fsm (ccp) is now up */
+
+ bundle_CalculateBandwidth(fp->bundle); /* Against ccp_MTUOverhead */
+}
+
+static void
+mp_LayerDown(void *v __unused, struct fsm *fp __unused)
+{
+ /* The given FSM (ccp) has been told to come down */
+}
+
+static void
+mp_LayerFinish(void *v __unused, struct fsm *fp)
+{
+ /* The given fsm (ccp) is now down */
+ if (fp->state == ST_CLOSED && fp->open_mode == OPEN_PASSIVE)
+ fsm_Open(fp); /* CCP goes to ST_STOPPED */
+}
+
+static void
+mp_UpDown(void *v)
+{
+ struct mp *mp = (struct mp *)v;
+ int percent;
+
+ percent = MAX(mp->link.stats.total.in.OctetsPerSecond,
+ mp->link.stats.total.out.OctetsPerSecond) * 800 /
+ mp->bundle->bandwidth;
+ if (percent >= mp->cfg.autoload.max) {
+ log_Printf(LogDEBUG, "%d%% saturation - bring a link up ?\n", percent);
+ bundle_AutoAdjust(mp->bundle, percent, AUTO_UP);
+ } else if (percent <= mp->cfg.autoload.min) {
+ log_Printf(LogDEBUG, "%d%% saturation - bring a link down ?\n", percent);
+ bundle_AutoAdjust(mp->bundle, percent, AUTO_DOWN);
+ }
+}
+
+void
+mp_StopAutoloadTimer(struct mp *mp)
+{
+ throughput_stop(&mp->link.stats.total);
+}
+
+void
+mp_CheckAutoloadTimer(struct mp *mp)
+{
+ if (mp->link.stats.total.SamplePeriod != mp->cfg.autoload.period) {
+ throughput_destroy(&mp->link.stats.total);
+ throughput_init(&mp->link.stats.total, mp->cfg.autoload.period);
+ throughput_callback(&mp->link.stats.total, mp_UpDown, mp);
+ }
+
+ if (bundle_WantAutoloadTimer(mp->bundle))
+ throughput_start(&mp->link.stats.total, "MP throughput", 1);
+ else
+ mp_StopAutoloadTimer(mp);
+}
+
+void
+mp_RestartAutoloadTimer(struct mp *mp)
+{
+ if (mp->link.stats.total.SamplePeriod != mp->cfg.autoload.period)
+ mp_CheckAutoloadTimer(mp);
+ else
+ throughput_clear(&mp->link.stats.total, THROUGHPUT_OVERALL, NULL);
+}
+
+void
+mp_Init(struct mp *mp, struct bundle *bundle)
+{
+ mp->peer_is12bit = mp->local_is12bit = 0;
+ mp->peer_mrru = mp->local_mrru = 0;
+
+ peerid_Init(&mp->peer);
+
+ mp->out.seq = 0;
+ mp->out.link = 0;
+ mp->out.af = AF_INET;
+ mp->seq.min_in = 0;
+ mp->seq.next_in = 0;
+ mp->inbufs = NULL;
+ mp->bundle = bundle;
+
+ mp->link.type = LOGICAL_LINK;
+ mp->link.name = "mp";
+ mp->link.len = sizeof *mp;
+
+ mp->cfg.autoload.period = SAMPLE_PERIOD;
+ mp->cfg.autoload.min = mp->cfg.autoload.max = 0;
+ throughput_init(&mp->link.stats.total, mp->cfg.autoload.period);
+ throughput_callback(&mp->link.stats.total, mp_UpDown, mp);
+ mp->link.stats.parent = NULL;
+ mp->link.stats.gather = 0; /* Let the physical links gather stats */
+ memset(mp->link.Queue, '\0', sizeof mp->link.Queue);
+ memset(mp->link.proto_in, '\0', sizeof mp->link.proto_in);
+ memset(mp->link.proto_out, '\0', sizeof mp->link.proto_out);
+
+ mp->fsmp.LayerStart = mp_LayerStart;
+ mp->fsmp.LayerUp = mp_LayerUp;
+ mp->fsmp.LayerDown = mp_LayerDown;
+ mp->fsmp.LayerFinish = mp_LayerFinish;
+ mp->fsmp.object = mp;
+
+ mpserver_Init(&mp->server);
+
+ mp->cfg.mrru = 0;
+ mp->cfg.shortseq = NEG_ENABLED|NEG_ACCEPTED;
+ mp->cfg.negenddisc = NEG_ENABLED|NEG_ACCEPTED;
+ mp->cfg.enddisc.class = 0;
+ *mp->cfg.enddisc.address = '\0';
+ mp->cfg.enddisc.len = 0;
+
+ lcp_Init(&mp->link.lcp, mp->bundle, &mp->link, NULL);
+ ccp_Init(&mp->link.ccp, mp->bundle, &mp->link, &mp->fsmp);
+
+ link_EmptyStack(&mp->link);
+ link_Stack(&mp->link, &protolayer);
+ link_Stack(&mp->link, &ccplayer);
+ link_Stack(&mp->link, &vjlayer);
+#ifndef NONAT
+ link_Stack(&mp->link, &natlayer);
+#endif
+}
+
+int
+mp_Up(struct mp *mp, struct datalink *dl)
+{
+ struct lcp *lcp = &dl->physical->link.lcp;
+
+ if (mp->active) {
+ /* We're adding a link - do a last validation on our parameters */
+ if (!peerid_Equal(&dl->peer, &mp->peer)) {
+ log_Printf(LogPHASE, "%s: Inappropriate peer !\n", dl->name);
+ log_Printf(LogPHASE, " Attached to peer %s/%s\n", mp->peer.authname,
+ mp_Enddisc(mp->peer.enddisc.class, mp->peer.enddisc.address,
+ mp->peer.enddisc.len));
+ log_Printf(LogPHASE, " New link is peer %s/%s\n", dl->peer.authname,
+ mp_Enddisc(dl->peer.enddisc.class, dl->peer.enddisc.address,
+ dl->peer.enddisc.len));
+ return MP_FAILED;
+ }
+ if (mp->local_mrru != lcp->want_mrru ||
+ mp->peer_mrru != lcp->his_mrru ||
+ mp->local_is12bit != lcp->want_shortseq ||
+ mp->peer_is12bit != lcp->his_shortseq) {
+ log_Printf(LogPHASE, "%s: Invalid MRRU/SHORTSEQ MP parameters !\n",
+ dl->name);
+ return MP_FAILED;
+ }
+ return MP_ADDED;
+ } else {
+ /* First link in multilink mode */
+
+ mp->local_mrru = lcp->want_mrru;
+ mp->peer_mrru = lcp->his_mrru;
+ mp->local_is12bit = lcp->want_shortseq;
+ mp->peer_is12bit = lcp->his_shortseq;
+ mp->peer = dl->peer;
+
+ throughput_destroy(&mp->link.stats.total);
+ throughput_init(&mp->link.stats.total, mp->cfg.autoload.period);
+ throughput_callback(&mp->link.stats.total, mp_UpDown, mp);
+ memset(mp->link.Queue, '\0', sizeof mp->link.Queue);
+ memset(mp->link.proto_in, '\0', sizeof mp->link.proto_in);
+ memset(mp->link.proto_out, '\0', sizeof mp->link.proto_out);
+
+ /* Tell the link who it belongs to */
+ dl->physical->link.stats.parent = &mp->link.stats.total;
+
+ mp->out.seq = 0;
+ mp->out.link = 0;
+ mp->out.af = AF_INET;
+ mp->seq.min_in = 0;
+ mp->seq.next_in = 0;
+
+ /*
+ * Now we create our server socket.
+ * If it already exists, join it. Otherwise, create and own it
+ */
+ switch (mpserver_Open(&mp->server, &mp->peer)) {
+ case MPSERVER_CONNECTED:
+ log_Printf(LogPHASE, "mp: Transfer link on %s\n",
+ mp->server.socket.sun_path);
+ mp->server.send.dl = dl; /* Defer 'till it's safe to send */
+ return MP_LINKSENT;
+ case MPSERVER_FAILED:
+ return MP_FAILED;
+ case MPSERVER_LISTENING:
+ log_Printf(LogPHASE, "mp: Listening on %s\n", mp->server.socket.sun_path);
+ log_Printf(LogPHASE, " First link: %s\n", dl->name);
+
+ /* Re-point our NCP layers at our MP link */
+ ncp_SetLink(&mp->bundle->ncp, &mp->link);
+
+ /* Our lcp's already up 'cos of the NULL parent */
+ if (ccp_SetOpenMode(&mp->link.ccp)) {
+ fsm_Up(&mp->link.ccp.fsm);
+ fsm_Open(&mp->link.ccp.fsm);
+ }
+
+ mp->active = 1;
+ break;
+ }
+ }
+
+ return MP_UP;
+}
+
+void
+mp_Down(struct mp *mp)
+{
+ if (mp->active) {
+ struct mbuf *next;
+
+ /* Stop that ! */
+ mp_StopAutoloadTimer(mp);
+
+ /* Don't want any more of these */
+ mpserver_Close(&mp->server);
+
+ /* CCP goes down with a bang */
+ fsm2initial(&mp->link.ccp.fsm);
+
+ /* Received fragments go in the bit-bucket */
+ while (mp->inbufs) {
+ next = mp->inbufs->m_nextpkt;
+ m_freem(mp->inbufs);
+ mp->inbufs = next;
+ }
+
+ peerid_Init(&mp->peer);
+ mp->active = 0;
+ }
+}
+
+void
+mp_linkInit(struct mp_link *mplink)
+{
+ mplink->seq = 0;
+ mplink->bandwidth = 0;
+}
+
+static void
+mp_Assemble(struct mp *mp, struct mbuf *m, struct physical *p)
+{
+ struct mp_header mh, h;
+ struct mbuf *q, *last;
+ u_int32_t seq;
+
+ /*
+ * When `m' and `p' are NULL, it means our oldest link has gone down.
+ * We want to determine a new min, and process any intermediate stuff
+ * as normal
+ */
+
+ if (m && mp_ReadHeader(mp, m, &mh) == 0) {
+ m_freem(m);
+ return;
+ }
+
+ if (p) {
+ seq = p->dl->mp.seq;
+ p->dl->mp.seq = mh.seq;
+ } else
+ seq = mp->seq.min_in;
+
+ if (mp->seq.min_in == seq) {
+ /*
+ * We've received new data on the link that has our min (oldest) seq.
+ * Figure out which link now has the smallest (oldest) seq.
+ */
+ struct datalink *dl;
+
+ mp->seq.min_in = (u_int32_t)-1;
+ for (dl = mp->bundle->links; dl; dl = dl->next)
+ if (dl->state == DATALINK_OPEN &&
+ (mp->seq.min_in == (u_int32_t)-1 ||
+ isbefore(mp->local_is12bit, dl->mp.seq, mp->seq.min_in)))
+ mp->seq.min_in = dl->mp.seq;
+ }
+
+ /*
+ * Now process as many of our fragments as we can, adding our new
+ * fragment in as we go, and ordering with the oldest at the top of
+ * the queue.
+ */
+
+ last = NULL;
+ seq = mp->seq.next_in;
+ q = mp->inbufs;
+ while (q || m) {
+ if (!q) {
+ if (last)
+ last->m_nextpkt = m;
+ else
+ mp->inbufs = m;
+ q = m;
+ m = NULL;
+ h = mh;
+ } else {
+ mp_ReadHeader(mp, q, &h);
+
+ if (m && isbefore(mp->local_is12bit, mh.seq, h.seq)) {
+ /* Our received fragment fits in before this one, so link it in */
+ if (last)
+ last->m_nextpkt = m;
+ else
+ mp->inbufs = m;
+ m->m_nextpkt = q;
+ q = m;
+ h = mh;
+ m = NULL;
+ }
+ }
+
+ if (h.seq != seq) {
+ /* we're missing something :-( */
+ if (isbefore(mp->local_is12bit, seq, mp->seq.min_in)) {
+ /* we're never gonna get it */
+ struct mbuf *next;
+
+ /* Zap all older fragments */
+ while (mp->inbufs != q) {
+ log_Printf(LogDEBUG, "Drop frag\n");
+ next = mp->inbufs->m_nextpkt;
+ m_freem(mp->inbufs);
+ mp->inbufs = next;
+ }
+
+ /*
+ * Zap everything until the next `end' fragment OR just before
+ * the next `begin' fragment OR 'till seq.min_in - whichever
+ * comes first.
+ */
+ do {
+ mp_ReadHeader(mp, mp->inbufs, &h);
+ if (h.begin) {
+ /* We might be able to process this ! */
+ h.seq--; /* We're gonna look for fragment with h.seq+1 */
+ break;
+ }
+ next = mp->inbufs->m_nextpkt;
+ log_Printf(LogDEBUG, "Drop frag %u\n", h.seq);
+ m_freem(mp->inbufs);
+ mp->inbufs = next;
+ } while (mp->inbufs && (isbefore(mp->local_is12bit, mp->seq.min_in,
+ h.seq) || h.end));
+
+ /*
+ * Continue processing things from here.
+ * This deals with the possibility that we received a fragment
+ * on the slowest link that invalidates some of our data (because
+ * of the hole at `q'), but where there are subsequent `whole'
+ * packets that have already been received.
+ */
+
+ mp->seq.next_in = seq = inc_seq(mp->local_is12bit, h.seq);
+ last = NULL;
+ q = mp->inbufs;
+ } else
+ /* we may still receive the missing fragment */
+ break;
+ } else if (h.end) {
+ /* We've got something, reassemble */
+ struct mbuf **frag = &q;
+ int len;
+ long long first = -1;
+
+ do {
+ *frag = mp->inbufs;
+ mp->inbufs = mp->inbufs->m_nextpkt;
+ len = mp_ReadHeader(mp, *frag, &h);
+ if (first == -1)
+ first = h.seq;
+ if (frag == &q && !h.begin) {
+ log_Printf(LogWARN, "Oops - MP frag %lu should have a begin flag\n",
+ (u_long)h.seq);
+ m_freem(q);
+ q = NULL;
+ } else if (frag != &q && h.begin) {
+ log_Printf(LogWARN, "Oops - MP frag %lu should have an end flag\n",
+ (u_long)h.seq - 1);
+ /*
+ * Stuff our fragment back at the front of the queue and zap
+ * our half-assembled packet.
+ */
+ (*frag)->m_nextpkt = mp->inbufs;
+ mp->inbufs = *frag;
+ *frag = NULL;
+ m_freem(q);
+ q = NULL;
+ frag = &q;
+ h.end = 0; /* just in case it's a whole packet */
+ } else {
+ (*frag)->m_offset += len;
+ (*frag)->m_len -= len;
+ (*frag)->m_nextpkt = NULL;
+ do
+ frag = &(*frag)->m_next;
+ while (*frag != NULL);
+ }
+ } while (!h.end);
+
+ if (q) {
+ q = m_pullup(q);
+ log_Printf(LogDEBUG, "MP: Reassembled frags %lu-%lu, length %zd\n",
+ (u_long)first, (u_long)h.seq, m_length(q));
+ link_PullPacket(&mp->link, MBUF_CTOP(q), q->m_len, mp->bundle);
+ m_freem(q);
+ }
+
+ mp->seq.next_in = seq = inc_seq(mp->local_is12bit, h.seq);
+ last = NULL;
+ q = mp->inbufs;
+ } else {
+ /* Look for the next fragment */
+ seq = inc_seq(mp->local_is12bit, seq);
+ last = q;
+ q = q->m_nextpkt;
+ }
+ }
+
+ if (m) {
+ /* We still have to find a home for our new fragment */
+ last = NULL;
+ for (q = mp->inbufs; q; last = q, q = q->m_nextpkt) {
+ mp_ReadHeader(mp, q, &h);
+ if (isbefore(mp->local_is12bit, mh.seq, h.seq))
+ break;
+ }
+ /* Our received fragment fits in here */
+ if (last)
+ last->m_nextpkt = m;
+ else
+ mp->inbufs = m;
+ m->m_nextpkt = q;
+ }
+}
+
+struct mbuf *
+mp_Input(struct bundle *bundle, struct link *l, struct mbuf *bp)
+{
+ struct physical *p = link2physical(l);
+
+ if (!bundle->ncp.mp.active)
+ /* Let someone else deal with it ! */
+ return bp;
+
+ if (p == NULL) {
+ log_Printf(LogWARN, "DecodePacket: Can't do MP inside MP !\n");
+ m_freem(bp);
+ } else {
+ m_settype(bp, MB_MPIN);
+ mp_Assemble(&bundle->ncp.mp, bp, p);
+ }
+
+ return NULL;
+}
+
+static void
+mp_Output(struct mp *mp, struct bundle *bundle, struct link *l,
+ struct mbuf *m, u_int32_t begin, u_int32_t end)
+{
+ char prepend[4];
+
+ /* Stuff an MP header on the front of our packet and send it */
+
+ if (mp->peer_is12bit) {
+ u_int16_t val;
+
+ val = (begin << 15) | (end << 14) | (u_int16_t)mp->out.seq;
+ ua_htons(&val, prepend);
+ m = m_prepend(m, prepend, 2, 0);
+ } else {
+ u_int32_t val;
+
+ val = (begin << 31) | (end << 30) | (u_int32_t)mp->out.seq;
+ ua_htonl(&val, prepend);
+ m = m_prepend(m, prepend, 4, 0);
+ }
+ if (log_IsKept(LogDEBUG))
+ log_Printf(LogDEBUG, "MP[frag %d]: Send %zd bytes on link `%s'\n",
+ mp->out.seq, m_length(m), l->name);
+ mp->out.seq = inc_seq(mp->peer_is12bit, mp->out.seq);
+
+ if (l->ccp.fsm.state != ST_OPENED && ccp_Required(&l->ccp)) {
+ log_Printf(LogPHASE, "%s: Not transmitting... waiting for CCP\n", l->name);
+ return;
+ }
+
+ link_PushPacket(l, m, bundle, LINK_QUEUES(l) - 1, PROTO_MP);
+}
+
+int
+mp_FillPhysicalQueues(struct bundle *bundle)
+{
+ struct mp *mp = &bundle->ncp.mp;
+ struct datalink *dl, *fdl;
+ size_t total, add, len;
+ int thislink, nlinks, nopenlinks, sendasip;
+ u_int32_t begin, end;
+ struct mbuf *m, *mo;
+ struct link *bestlink;
+
+ thislink = nlinks = nopenlinks = 0;
+ for (fdl = NULL, dl = bundle->links; dl; dl = dl->next) {
+ /* Include non-open links here as mp->out.link will stay more correct */
+ if (!fdl) {
+ if (thislink == mp->out.link)
+ fdl = dl;
+ else
+ thislink++;
+ }
+ nlinks++;
+ if (dl->state == DATALINK_OPEN)
+ nopenlinks++;
+ }
+
+ if (!fdl) {
+ fdl = bundle->links;
+ if (!fdl)
+ return 0;
+ thislink = 0;
+ }
+
+ total = 0;
+ for (dl = fdl; nlinks > 0; dl = dl->next, nlinks--, thislink++) {
+ if (!dl) {
+ dl = bundle->links;
+ thislink = 0;
+ }
+
+ if (dl->state != DATALINK_OPEN)
+ continue;
+
+ if (dl->physical->out)
+ /* this link has suffered a short write. Let it continue */
+ continue;
+
+ add = link_QueueLen(&dl->physical->link);
+ if (add) {
+ /* this link has got stuff already queued. Let it continue */
+ total += add;
+ continue;
+ }
+
+ if (!mp_QueueLen(mp)) {
+ int mrutoosmall;
+
+ /*
+ * If there's only a single open link in our bundle and we haven't got
+ * MP level link compression, queue outbound traffic directly via that
+ * link's protocol stack rather than using the MP link. This results
+ * in the outbound traffic going out as PROTO_IP or PROTO_IPV6 rather
+ * than PROTO_MP.
+ */
+
+ mrutoosmall = 0;
+ sendasip = nopenlinks < 2;
+ if (sendasip) {
+ if (dl->physical->link.lcp.his_mru < mp->peer_mrru) {
+ /*
+ * Actually, forget it. This test is done against the MRRU rather
+ * than the packet size so that we don't end up sending some data
+ * in MP fragments and some data in PROTO_IP packets. That's just
+ * too likely to upset some ppp implementations.
+ */
+ mrutoosmall = 1;
+ sendasip = 0;
+ }
+ }
+
+ bestlink = sendasip ? &dl->physical->link : &mp->link;
+ if (!ncp_PushPacket(&bundle->ncp, &mp->out.af, bestlink))
+ break; /* Nothing else to send */
+
+ if (mrutoosmall)
+ log_Printf(LogDEBUG, "Don't send data as PROTO_IP, MRU < MRRU\n");
+ else if (sendasip)
+ log_Printf(LogDEBUG, "Sending data as PROTO_IP, not PROTO_MP\n");
+
+ if (sendasip) {
+ add = link_QueueLen(&dl->physical->link);
+ if (add) {
+ /* this link has got stuff already queued. Let it continue */
+ total += add;
+ continue;
+ }
+ }
+ }
+
+ m = link_Dequeue(&mp->link);
+ if (m) {
+ len = m_length(m);
+ begin = 1;
+ end = 0;
+
+ while (!end) {
+ if (dl->state == DATALINK_OPEN) {
+ /* Write at most his_mru bytes to the physical link */
+ if (len <= dl->physical->link.lcp.his_mru) {
+ mo = m;
+ end = 1;
+ m_settype(mo, MB_MPOUT);
+ } else {
+ /* It's > his_mru, chop the packet (`m') into bits */
+ mo = m_get(dl->physical->link.lcp.his_mru, MB_MPOUT);
+ len -= mo->m_len;
+ m = mbuf_Read(m, MBUF_CTOP(mo), mo->m_len);
+ }
+ mp_Output(mp, bundle, &dl->physical->link, mo, begin, end);
+ begin = 0;
+ }
+
+ if (!end) {
+ nlinks--;
+ dl = dl->next;
+ if (!dl) {
+ dl = bundle->links;
+ thislink = 0;
+ } else
+ thislink++;
+ }
+ }
+ }
+ }
+ mp->out.link = thislink; /* Start here next time */
+
+ return total;
+}
+
+int
+mp_SetDatalinkBandwidth(struct cmdargs const *arg)
+{
+ int val;
+
+ if (arg->argc != arg->argn+1)
+ return -1;
+
+ val = atoi(arg->argv[arg->argn]);
+ if (val <= 0) {
+ log_Printf(LogWARN, "The link bandwidth must be greater than zero\n");
+ return 1;
+ }
+ arg->cx->mp.bandwidth = val;
+
+ if (arg->cx->state == DATALINK_OPEN)
+ bundle_CalculateBandwidth(arg->bundle);
+
+ return 0;
+}
+
+int
+mp_ShowStatus(struct cmdargs const *arg)
+{
+ struct mp *mp = &arg->bundle->ncp.mp;
+
+ prompt_Printf(arg->prompt, "Multilink is %sactive\n", mp->active ? "" : "in");
+ if (mp->active) {
+ struct mbuf *m, *lm;
+ int bufs = 0;
+
+ lm = NULL;
+ prompt_Printf(arg->prompt, "Socket: %s\n",
+ mp->server.socket.sun_path);
+ for (m = mp->inbufs; m; m = m->m_nextpkt) {
+ bufs++;
+ lm = m;
+ }
+ prompt_Printf(arg->prompt, "Pending frags: %d", bufs);
+ if (bufs) {
+ struct mp_header mh;
+ unsigned long first, last;
+
+ first = mp_ReadHeader(mp, mp->inbufs, &mh) ? mh.seq : 0;
+ last = mp_ReadHeader(mp, lm, &mh) ? mh.seq : 0;
+ prompt_Printf(arg->prompt, " (Have %lu - %lu, want %lu, lowest %lu)\n",
+ first, last, (unsigned long)mp->seq.next_in,
+ (unsigned long)mp->seq.min_in);
+ prompt_Printf(arg->prompt, " First has %sbegin bit and "
+ "%send bit", mh.begin ? "" : "no ", mh.end ? "" : "no ");
+ }
+ prompt_Printf(arg->prompt, "\n");
+ }
+
+ prompt_Printf(arg->prompt, "\nMy Side:\n");
+ if (mp->active) {
+ prompt_Printf(arg->prompt, " Output SEQ: %u\n", mp->out.seq);
+ prompt_Printf(arg->prompt, " MRRU: %u\n", mp->local_mrru);
+ prompt_Printf(arg->prompt, " Short Seq: %s\n",
+ mp->local_is12bit ? "on" : "off");
+ }
+ prompt_Printf(arg->prompt, " Discriminator: %s\n",
+ mp_Enddisc(mp->cfg.enddisc.class, mp->cfg.enddisc.address,
+ mp->cfg.enddisc.len));
+
+ prompt_Printf(arg->prompt, "\nHis Side:\n");
+ if (mp->active) {
+ prompt_Printf(arg->prompt, " Auth Name: %s\n", mp->peer.authname);
+ prompt_Printf(arg->prompt, " Input SEQ: %u\n", mp->seq.next_in);
+ prompt_Printf(arg->prompt, " MRRU: %u\n", mp->peer_mrru);
+ prompt_Printf(arg->prompt, " Short Seq: %s\n",
+ mp->peer_is12bit ? "on" : "off");
+ }
+ prompt_Printf(arg->prompt, " Discriminator: %s\n",
+ mp_Enddisc(mp->peer.enddisc.class, mp->peer.enddisc.address,
+ mp->peer.enddisc.len));
+
+ prompt_Printf(arg->prompt, "\nDefaults:\n");
+
+ prompt_Printf(arg->prompt, " MRRU: ");
+ if (mp->cfg.mrru)
+ prompt_Printf(arg->prompt, "%d (multilink enabled)\n", mp->cfg.mrru);
+ else
+ prompt_Printf(arg->prompt, "disabled\n");
+ prompt_Printf(arg->prompt, " Short Seq: %s\n",
+ command_ShowNegval(mp->cfg.shortseq));
+ prompt_Printf(arg->prompt, " Discriminator: %s\n",
+ command_ShowNegval(mp->cfg.negenddisc));
+ prompt_Printf(arg->prompt, " AutoLoad: min %d%%, max %d%%,"
+ " period %d secs\n", mp->cfg.autoload.min,
+ mp->cfg.autoload.max, mp->cfg.autoload.period);
+
+ return 0;
+}
+
+const char *
+mp_Enddisc(u_char c, const char *address, size_t len)
+{
+ static char result[100]; /* Used immediately after it's returned */
+ unsigned f, header;
+
+ switch (c) {
+ case ENDDISC_NULL:
+ sprintf(result, "Null Class");
+ break;
+
+ case ENDDISC_LOCAL:
+ snprintf(result, sizeof result, "Local Addr: %.*s", (int)len,
+ address);
+ break;
+
+ case ENDDISC_IP:
+ if (len == 4)
+ snprintf(result, sizeof result, "IP %s",
+ inet_ntoa(*(const struct in_addr *)address));
+ else
+ sprintf(result, "IP[%zd] ???", len);
+ break;
+
+ case ENDDISC_MAC:
+ if (len == 6) {
+ const u_char *m = (const u_char *)address;
+ snprintf(result, sizeof result, "MAC %02x:%02x:%02x:%02x:%02x:%02x",
+ m[0], m[1], m[2], m[3], m[4], m[5]);
+ } else
+ sprintf(result, "MAC[%zd] ???", len);
+ break;
+
+ case ENDDISC_MAGIC:
+ sprintf(result, "Magic: 0x");
+ header = strlen(result);
+ if (len + header + 1 > sizeof result)
+ len = sizeof result - header - 1;
+ for (f = 0; f < len; f++)
+ sprintf(result + header + 2 * f, "%02x", address[f]);
+ break;
+
+ case ENDDISC_PSN:
+ snprintf(result, sizeof result, "PSN: %.*s", (int)len, address);
+ break;
+
+ default:
+ sprintf(result, "%d: ", (int)c);
+ header = strlen(result);
+ if (len + header + 1 > sizeof result)
+ len = sizeof result - header - 1;
+ for (f = 0; f < len; f++)
+ sprintf(result + header + 2 * f, "%02x", address[f]);
+ break;
+ }
+ return result;
+}
+
+int
+mp_SetEnddisc(struct cmdargs const *arg)
+{
+ struct mp *mp = &arg->bundle->ncp.mp;
+ struct in_addr addr;
+
+ switch (bundle_Phase(arg->bundle)) {
+ case PHASE_DEAD:
+ break;
+ case PHASE_ESTABLISH:
+ /* Make sure none of our links are DATALINK_LCP or greater */
+ if (bundle_HighestState(arg->bundle) >= DATALINK_LCP) {
+ log_Printf(LogWARN, "enddisc: Only changeable before"
+ " LCP negotiations\n");
+ return 1;
+ }
+ break;
+ default:
+ log_Printf(LogWARN, "enddisc: Only changeable at phase DEAD/ESTABLISH\n");
+ return 1;
+ }
+
+ if (arg->argc == arg->argn) {
+ mp->cfg.enddisc.class = 0;
+ *mp->cfg.enddisc.address = '\0';
+ mp->cfg.enddisc.len = 0;
+ } else if (arg->argc > arg->argn) {
+ if (!strcasecmp(arg->argv[arg->argn], "label")) {
+ mp->cfg.enddisc.class = ENDDISC_LOCAL;
+ strcpy(mp->cfg.enddisc.address, arg->bundle->cfg.label);
+ mp->cfg.enddisc.len = strlen(mp->cfg.enddisc.address);
+ } else if (!strcasecmp(arg->argv[arg->argn], "ip")) {
+ if (arg->bundle->ncp.ipcp.my_ip.s_addr == INADDR_ANY)
+ ncprange_getip4addr(&arg->bundle->ncp.ipcp.cfg.my_range, &addr);
+ else
+ addr = arg->bundle->ncp.ipcp.my_ip;
+ memcpy(mp->cfg.enddisc.address, &addr.s_addr, sizeof addr.s_addr);
+ mp->cfg.enddisc.class = ENDDISC_IP;
+ mp->cfg.enddisc.len = sizeof arg->bundle->ncp.ipcp.my_ip.s_addr;
+ } else if (!strcasecmp(arg->argv[arg->argn], "mac")) {
+ struct sockaddr_dl hwaddr;
+
+ if (arg->bundle->ncp.ipcp.my_ip.s_addr == INADDR_ANY)
+ ncprange_getip4addr(&arg->bundle->ncp.ipcp.cfg.my_range, &addr);
+ else
+ addr = arg->bundle->ncp.ipcp.my_ip;
+
+ if (arp_EtherAddr(addr, &hwaddr, 1)) {
+ mp->cfg.enddisc.class = ENDDISC_MAC;
+ memcpy(mp->cfg.enddisc.address, hwaddr.sdl_data + hwaddr.sdl_nlen,
+ hwaddr.sdl_alen);
+ mp->cfg.enddisc.len = hwaddr.sdl_alen;
+ } else {
+ log_Printf(LogWARN, "set enddisc: Can't locate MAC address for %s\n",
+ inet_ntoa(addr));
+ return 4;
+ }
+ } else if (!strcasecmp(arg->argv[arg->argn], "magic")) {
+ int f;
+
+ randinit();
+ for (f = 0; f < 20; f += sizeof(long))
+ *(long *)(mp->cfg.enddisc.address + f) = random();
+ mp->cfg.enddisc.class = ENDDISC_MAGIC;
+ mp->cfg.enddisc.len = 20;
+ } else if (!strcasecmp(arg->argv[arg->argn], "psn")) {
+ if (arg->argc > arg->argn+1) {
+ mp->cfg.enddisc.class = ENDDISC_PSN;
+ strcpy(mp->cfg.enddisc.address, arg->argv[arg->argn+1]);
+ mp->cfg.enddisc.len = strlen(mp->cfg.enddisc.address);
+ } else {
+ log_Printf(LogWARN, "PSN endpoint requires additional data\n");
+ return 5;
+ }
+ } else {
+ log_Printf(LogWARN, "%s: Unrecognised endpoint type\n",
+ arg->argv[arg->argn]);
+ return 6;
+ }
+ }
+
+ return 0;
+}
+
+static int
+mpserver_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e,
+ int *n)
+{
+ struct mpserver *s = descriptor2mpserver(d);
+ int result;
+
+ result = 0;
+ if (s->send.dl != NULL) {
+ /* We've connect()ed */
+ if (!link_QueueLen(&s->send.dl->physical->link) &&
+ !s->send.dl->physical->out) {
+ /* Only send if we've transmitted all our data (i.e. the ConfigAck) */
+ result -= datalink_RemoveFromSet(s->send.dl, r, w, e);
+ bundle_SendDatalink(s->send.dl, s->fd, &s->socket);
+ s->send.dl = NULL;
+ s->fd = -1;
+ } else
+ /* Never read from a datalink that's on death row ! */
+ result -= datalink_RemoveFromSet(s->send.dl, r, NULL, NULL);
+ } else if (r && s->fd >= 0) {
+ if (*n < s->fd + 1)
+ *n = s->fd + 1;
+ FD_SET(s->fd, r);
+ log_Printf(LogTIMER, "mp: fdset(r) %d\n", s->fd);
+ result++;
+ }
+ return result;
+}
+
+static int
+mpserver_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+ struct mpserver *s = descriptor2mpserver(d);
+ return s->fd >= 0 && FD_ISSET(s->fd, fdset);
+}
+
+static void
+mpserver_Read(struct fdescriptor *d, struct bundle *bundle,
+ const fd_set *fdset __unused)
+{
+ struct mpserver *s = descriptor2mpserver(d);
+
+ bundle_ReceiveDatalink(bundle, s->fd);
+}
+
+static int
+mpserver_Write(struct fdescriptor *d __unused, struct bundle *bundle __unused,
+ const fd_set *fdset __unused)
+{
+ /* We never want to write here ! */
+ log_Printf(LogALERT, "mpserver_Write: Internal error: Bad call !\n");
+ return 0;
+}
+
+void
+mpserver_Init(struct mpserver *s)
+{
+ s->desc.type = MPSERVER_DESCRIPTOR;
+ s->desc.UpdateSet = mpserver_UpdateSet;
+ s->desc.IsSet = mpserver_IsSet;
+ s->desc.Read = mpserver_Read;
+ s->desc.Write = mpserver_Write;
+ s->send.dl = NULL;
+ s->fd = -1;
+ memset(&s->socket, '\0', sizeof s->socket);
+}
+
+int
+mpserver_Open(struct mpserver *s, struct peerid *peer)
+{
+ int f, l;
+ mode_t mask;
+
+ if (s->fd != -1) {
+ log_Printf(LogALERT, "Internal error ! mpserver already open\n");
+ mpserver_Close(s);
+ }
+
+ l = snprintf(s->socket.sun_path, sizeof s->socket.sun_path, "%sppp-%s-%02x-",
+ _PATH_VARRUN, peer->authname, peer->enddisc.class);
+ if (l < 0) {
+ log_Printf(LogERROR, "mpserver: snprintf(): %s\n", strerror(errno));
+ return MPSERVER_FAILED;
+ }
+
+ for (f = 0;
+ f < peer->enddisc.len && (size_t)l < sizeof s->socket.sun_path - 2;
+ f++) {
+ snprintf(s->socket.sun_path + l, sizeof s->socket.sun_path - l,
+ "%02x", *(u_char *)(peer->enddisc.address+f));
+ l += 2;
+ }
+
+ s->socket.sun_family = AF_LOCAL;
+ s->socket.sun_len = sizeof s->socket;
+ s->fd = ID0socket(PF_LOCAL, SOCK_DGRAM, 0);
+ if (s->fd < 0) {
+ log_Printf(LogERROR, "mpserver: socket(): %s\n", strerror(errno));
+ return MPSERVER_FAILED;
+ }
+
+ setsockopt(s->fd, SOL_SOCKET, SO_REUSEADDR, (struct sockaddr *)&s->socket,
+ sizeof s->socket);
+ mask = umask(0177);
+
+ /*
+ * Try to bind the socket. If we succeed we play server, if we fail
+ * we connect() and hand the link off.
+ */
+
+ if (ID0bind_un(s->fd, &s->socket) < 0) {
+ if (errno != EADDRINUSE) {
+ log_Printf(LogPHASE, "mpserver: can't create bundle socket %s (%s)\n",
+ s->socket.sun_path, strerror(errno));
+ umask(mask);
+ close(s->fd);
+ s->fd = -1;
+ return MPSERVER_FAILED;
+ }
+
+ /* So we're the sender */
+ umask(mask);
+ if (ID0connect_un(s->fd, &s->socket) < 0) {
+ log_Printf(LogPHASE, "mpserver: can't connect to bundle socket %s (%s)\n",
+ s->socket.sun_path, strerror(errno));
+ if (errno == ECONNREFUSED)
+ log_Printf(LogPHASE, " The previous server died badly !\n");
+ close(s->fd);
+ s->fd = -1;
+ return MPSERVER_FAILED;
+ }
+
+ /* Donate our link to the other guy */
+ return MPSERVER_CONNECTED;
+ }
+
+ return MPSERVER_LISTENING;
+}
+
+void
+mpserver_Close(struct mpserver *s)
+{
+ if (s->send.dl != NULL) {
+ bundle_SendDatalink(s->send.dl, s->fd, &s->socket);
+ s->send.dl = NULL;
+ s->fd = -1;
+ } else if (s->fd >= 0) {
+ close(s->fd);
+ if (ID0unlink(s->socket.sun_path) == -1)
+ log_Printf(LogERROR, "%s: Failed to remove: %s\n", s->socket.sun_path,
+ strerror(errno));
+ memset(&s->socket, '\0', sizeof s->socket);
+ s->fd = -1;
+ }
+}
+
+void
+mp_LinkLost(struct mp *mp, struct datalink *dl)
+{
+ if (mp->seq.min_in == dl->mp.seq)
+ /* We've lost the link that's holding everything up ! */
+ mp_Assemble(mp, NULL, NULL);
+}
+
+size_t
+mp_QueueLen(struct mp *mp)
+{
+ return link_QueueLen(&mp->link);
+}
diff --git a/usr.sbin/ppp/mp.h b/usr.sbin/ppp/mp.h
new file mode 100644
index 0000000..7cf5a8e
--- /dev/null
+++ b/usr.sbin/ppp/mp.h
@@ -0,0 +1,146 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.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$
+ */
+
+struct mbuf;
+struct physical;
+struct bundle;
+struct cmdargs;
+struct datalink;
+
+#define ENDDISC_NULL 0
+#define ENDDISC_LOCAL 1
+#define ENDDISC_IP 2
+#define ENDDISC_MAC 3
+#define ENDDISC_MAGIC 4
+#define ENDDISC_PSN 5
+
+#define MP_LINKSENT 0 /* We attached the link to another ppp */
+#define MP_UP 1 /* We've started MP */
+#define MP_ADDED 2 /* We've added the link to our MP */
+#define MP_FAILED 3 /* No go */
+
+#define MPSERVER_CONNECTED 0
+#define MPSERVER_LISTENING 1
+#define MPSERVER_FAILED 2
+
+struct enddisc {
+ u_char class;
+ char address[50];
+ int len;
+};
+
+struct peerid {
+ struct enddisc enddisc; /* Peers endpoint discriminator */
+ char authname[AUTHLEN]; /* Peers name (authenticated) */
+};
+
+struct mpserver {
+ struct fdescriptor desc;
+ int fd; /* listen()ing or connect()ing here */
+ struct sockaddr_un socket; /* On this socket */
+
+ struct {
+ struct datalink *dl; /* Send this datalink */
+ } send; /* (in UpdateSet()) */
+};
+
+struct mp {
+ struct link link;
+
+ unsigned active : 1;
+ unsigned peer_is12bit : 1; /* 12/24bit seq nos */
+ unsigned local_is12bit : 1;
+ u_short peer_mrru;
+ u_short local_mrru;
+
+ struct peerid peer; /* Who are we talking to */
+ struct mpserver server; /* Our ``sharing'' socket */
+
+ struct {
+ u_int32_t seq; /* next outgoing seq */
+ int link; /* Next link to send on */
+ int af; /* Next address family to send */
+ } out;
+
+ struct {
+ u_int32_t min_in; /* minimum received incoming seq */
+ u_int32_t next_in; /* next incoming seq to process */
+ } seq;
+
+ struct {
+ u_short mrru; /* Max Reconstructed Receive Unit */
+ unsigned shortseq : 2; /* I want short Sequence Numbers */
+ unsigned negenddisc : 2; /* I want an endpoint discriminator */
+ struct enddisc enddisc; /* endpoint discriminator */
+ struct {
+ int min; /* Lowest percent of bundle->bandwidth */
+ int max; /* Highest percent of bundle->bandwidth out */
+ int period; /* link->throughput sample period */
+ } autoload;
+ } cfg;
+
+ struct mbuf *inbufs; /* Received fragments */
+ struct fsm_parent fsmp; /* Our callback functions */
+ struct bundle *bundle; /* Parent */
+};
+
+struct mp_link {
+ u_int32_t seq; /* 12 or 24 bit incoming seq */
+ unsigned bandwidth; /* Our link bandwidth (or zero) */
+};
+
+struct mp_header {
+ unsigned begin : 1;
+ unsigned end : 1;
+ u_int32_t seq;
+};
+
+#define descriptor2mpserver(d) \
+ ((d)->type == MPSERVER_DESCRIPTOR ? (struct mpserver *)(d) : NULL)
+#define mpserver_IsOpen(s) ((s)->fd != -1)
+
+extern void peerid_Init(struct peerid *);
+extern int peerid_Equal(const struct peerid *, const struct peerid *);
+extern void mpserver_Init(struct mpserver *);
+extern int mpserver_Open(struct mpserver *, struct peerid *);
+extern void mpserver_Close(struct mpserver *);
+extern void mp_Init(struct mp *, struct bundle *);
+extern void mp_linkInit(struct mp_link *);
+extern int mp_Up(struct mp *, struct datalink *);
+extern void mp_Down(struct mp *);
+extern struct mbuf *mp_Input(struct bundle *, struct link *, struct mbuf *);
+extern int mp_FillPhysicalQueues(struct bundle *);
+extern int mp_SetDatalinkBandwidth(struct cmdargs const *);
+extern int mp_ShowStatus(struct cmdargs const *);
+extern const char *mp_Enddisc(u_char, const char *, size_t);
+extern int mp_SetEnddisc(struct cmdargs const *);
+extern void mp_LinkLost(struct mp *, struct datalink *);
+extern void mp_RestartAutoloadTimer(struct mp *);
+extern void mp_CheckAutoloadTimer(struct mp *);
+extern void mp_StopAutoloadTimer(struct mp *);
+extern size_t mp_QueueLen(struct mp *);
diff --git a/usr.sbin/ppp/mppe.c b/usr.sbin/ppp/mppe.c
new file mode 100644
index 0000000..f1fadc3
--- /dev/null
+++ b/usr.sbin/ppp/mppe.c
@@ -0,0 +1,817 @@
+/*-
+ * Copyright (c) 2000 Semen Ustimenko <semenu@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/param.h>
+
+#include <sys/socket.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <sys/un.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <openssl/rc4.h>
+
+#include "defs.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "throughput.h"
+#include "layer.h"
+#include "link.h"
+#include "chap_ms.h"
+#include "proto.h"
+#include "mppe.h"
+#include "ua.h"
+#include "descriptor.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ncpaddr.h"
+#include "iplist.h"
+#include "slcompress.h"
+#include "ipcp.h"
+#include "ipv6cp.h"
+#include "filter.h"
+#include "mp.h"
+#include "ncp.h"
+#include "bundle.h"
+
+/*
+ * Documentation:
+ *
+ * draft-ietf-pppext-mppe-04.txt
+ * draft-ietf-pppext-mppe-keys-02.txt
+ */
+
+#define MPPE_OPT_STATELESS 0x1000000
+#define MPPE_OPT_COMPRESSED 0x01
+#define MPPE_OPT_40BIT 0x20
+#define MPPE_OPT_56BIT 0x80
+#define MPPE_OPT_128BIT 0x40
+#define MPPE_OPT_BITMASK 0xe0
+#define MPPE_OPT_MASK (MPPE_OPT_STATELESS | MPPE_OPT_BITMASK)
+
+#define MPPE_FLUSHED 0x8000
+#define MPPE_ENCRYPTED 0x1000
+#define MPPE_HEADER_BITMASK 0xf000
+#define MPPE_HEADER_FLAG 0x00ff
+#define MPPE_HEADER_FLAGMASK 0x00ff
+#define MPPE_HEADER_FLAGSHIFT 8
+#define MPPE_HEADER_STATEFUL_KEYCHANGES 16
+
+struct mppe_state {
+ unsigned stateless : 1;
+ unsigned flushnext : 1;
+ unsigned flushrequired : 1;
+ int cohnum;
+ unsigned keylen; /* 8 or 16 bytes */
+ int keybits; /* 40, 56 or 128 bits */
+ char sesskey[MPPE_KEY_LEN];
+ char mastkey[MPPE_KEY_LEN];
+ RC4_KEY rc4key;
+};
+
+int MPPE_MasterKeyValid = 0;
+int MPPE_IsServer = 0;
+char MPPE_MasterKey[MPPE_KEY_LEN];
+
+/*
+ * The peer has missed a packet. Mark the next output frame to be FLUSHED
+ */
+static int
+MPPEResetOutput(void *v)
+{
+ struct mppe_state *mop = (struct mppe_state *)v;
+
+ if (mop->stateless)
+ log_Printf(LogCCP, "MPPE: Unexpected output channel reset\n");
+ else {
+ log_Printf(LogCCP, "MPPE: Output channel reset\n");
+ mop->flushnext = 1;
+ }
+
+ return 0; /* Ask FSM not to ACK */
+}
+
+static void
+MPPEReduceSessionKey(struct mppe_state *mp)
+{
+ switch(mp->keybits) {
+ case 40:
+ mp->sesskey[2] = 0x9e;
+ mp->sesskey[1] = 0x26;
+ case 56:
+ mp->sesskey[0] = 0xd1;
+ case 128:
+ break;
+ }
+}
+
+static void
+MPPEKeyChange(struct mppe_state *mp)
+{
+ char InterimKey[MPPE_KEY_LEN];
+ RC4_KEY RC4Key;
+
+ GetNewKeyFromSHA(mp->mastkey, mp->sesskey, mp->keylen, InterimKey);
+ RC4_set_key(&RC4Key, mp->keylen, InterimKey);
+ RC4(&RC4Key, mp->keylen, InterimKey, mp->sesskey);
+
+ MPPEReduceSessionKey(mp);
+}
+
+static struct mbuf *
+MPPEOutput(void *v, struct ccp *ccp, struct link *l __unused, int pri __unused,
+ u_short *proto, struct mbuf *mp)
+{
+ struct mppe_state *mop = (struct mppe_state *)v;
+ struct mbuf *mo;
+ u_short nproto, prefix;
+ int dictinit, ilen, len;
+ char *rp;
+
+ ilen = m_length(mp);
+ dictinit = 0;
+
+ log_Printf(LogDEBUG, "MPPE: Output: Proto %02x (%d bytes)\n", *proto, ilen);
+ if (*proto < 0x21 || *proto > 0xFA) {
+ log_Printf(LogDEBUG, "MPPE: Output: Not encrypting\n");
+ ccp->compout += ilen;
+ ccp->uncompout += ilen;
+ return mp;
+ }
+
+ log_DumpBp(LogDEBUG, "MPPE: Output: Encrypt packet:", mp);
+
+ /* Get mbuf for prefixes */
+ mo = m_get(4, MB_CCPOUT);
+ mo->m_next = mp;
+
+ rp = MBUF_CTOP(mo);
+ prefix = MPPE_ENCRYPTED | mop->cohnum;
+
+ if (mop->stateless ||
+ (mop->cohnum & MPPE_HEADER_FLAGMASK) == MPPE_HEADER_FLAG) {
+ /* Change our key */
+ log_Printf(LogDEBUG, "MPPEOutput: Key changed [%d]\n", mop->cohnum);
+ MPPEKeyChange(mop);
+ dictinit = 1;
+ }
+
+ if (mop->stateless || mop->flushnext) {
+ prefix |= MPPE_FLUSHED;
+ dictinit = 1;
+ mop->flushnext = 0;
+ }
+
+ if (dictinit) {
+ /* Initialise our dictionary */
+ log_Printf(LogDEBUG, "MPPEOutput: Dictionary initialised [%d]\n",
+ mop->cohnum);
+ RC4_set_key(&mop->rc4key, mop->keylen, mop->sesskey);
+ }
+
+ /* Set MPPE packet prefix */
+ ua_htons(&prefix, rp);
+
+ /* Save encrypted protocol number */
+ nproto = htons(*proto);
+ RC4(&mop->rc4key, 2, (char *)&nproto, rp + 2);
+
+ /* Encrypt main packet */
+ rp = MBUF_CTOP(mp);
+ RC4(&mop->rc4key, ilen, rp, rp);
+
+ mop->cohnum++;
+ mop->cohnum &= ~MPPE_HEADER_BITMASK;
+
+ /* Set the protocol number */
+ *proto = ccp_Proto(ccp);
+ len = m_length(mo);
+ ccp->uncompout += ilen;
+ ccp->compout += len;
+
+ log_Printf(LogDEBUG, "MPPE: Output: Encrypted: Proto %02x (%d bytes)\n",
+ *proto, len);
+
+ return mo;
+}
+
+static void
+MPPEResetInput(void *v __unused)
+{
+ log_Printf(LogCCP, "MPPE: Unexpected input channel ack\n");
+}
+
+static struct mbuf *
+MPPEInput(void *v, struct ccp *ccp, u_short *proto, struct mbuf *mp)
+{
+ struct mppe_state *mip = (struct mppe_state *)v;
+ u_short prefix;
+ char *rp;
+ int dictinit, flushed, ilen, len, n;
+
+ ilen = m_length(mp);
+ dictinit = 0;
+ ccp->compin += ilen;
+
+ log_Printf(LogDEBUG, "MPPE: Input: Proto %02x (%d bytes)\n", *proto, ilen);
+ log_DumpBp(LogDEBUG, "MPPE: Input: Packet:", mp);
+
+ mp = mbuf_Read(mp, &prefix, 2);
+ prefix = ntohs(prefix);
+ flushed = prefix & MPPE_FLUSHED;
+ prefix &= ~flushed;
+ if ((prefix & MPPE_HEADER_BITMASK) != MPPE_ENCRYPTED) {
+ log_Printf(LogERROR, "MPPE: Input: Invalid packet (flags = 0x%x)\n",
+ (prefix & MPPE_HEADER_BITMASK) | flushed);
+ m_freem(mp);
+ return NULL;
+ }
+
+ prefix &= ~MPPE_HEADER_BITMASK;
+
+ if (!flushed && mip->stateless) {
+ log_Printf(LogCCP, "MPPEInput: Packet without MPPE_FLUSHED set"
+ " in stateless mode\n");
+ flushed = MPPE_FLUSHED;
+ /* Should we really continue ? */
+ }
+
+ if (mip->stateless) {
+ /* Change our key for each missed packet in stateless mode */
+ while (prefix != mip->cohnum) {
+ log_Printf(LogDEBUG, "MPPEInput: Key changed [%u]\n", prefix);
+ MPPEKeyChange(mip);
+ /*
+ * mip->cohnum contains what we received last time in stateless
+ * mode.
+ */
+ mip->cohnum++;
+ mip->cohnum &= ~MPPE_HEADER_BITMASK;
+ }
+ dictinit = 1;
+ } else {
+ if (flushed) {
+ /*
+ * We can always process a flushed packet.
+ * Catch up on any outstanding key changes.
+ */
+ n = (prefix >> MPPE_HEADER_FLAGSHIFT) -
+ (mip->cohnum >> MPPE_HEADER_FLAGSHIFT);
+ if (n < 0)
+ n += MPPE_HEADER_STATEFUL_KEYCHANGES;
+ while (n--) {
+ log_Printf(LogDEBUG, "MPPEInput: Key changed during catchup [%u]\n",
+ prefix);
+ MPPEKeyChange(mip);
+ }
+ mip->flushrequired = 0;
+ mip->cohnum = prefix;
+ dictinit = 1;
+ }
+
+ if (mip->flushrequired) {
+ /*
+ * Perhaps we should be lenient if
+ * (prefix & MPPE_HEADER_FLAGMASK) == MPPE_HEADER_FLAG
+ * The spec says that we shouldn't be though....
+ */
+ log_Printf(LogDEBUG, "MPPE: Not flushed - discarded\n");
+ fsm_Output(&ccp->fsm, CODE_RESETREQ, ccp->fsm.reqid++, NULL, 0,
+ MB_CCPOUT);
+ m_freem(mp);
+ return NULL;
+ }
+
+ if (prefix != mip->cohnum) {
+ /*
+ * We're in stateful mode and didn't receive the expected
+ * packet. Send a reset request, but don't tell the CCP layer
+ * about it as we don't expect to receive a Reset ACK !
+ * Guess what... M$ invented this !
+ */
+ log_Printf(LogCCP, "MPPE: Input: Got seq %u, not %u\n",
+ prefix, mip->cohnum);
+ fsm_Output(&ccp->fsm, CODE_RESETREQ, ccp->fsm.reqid++, NULL, 0,
+ MB_CCPOUT);
+ mip->flushrequired = 1;
+ m_freem(mp);
+ return NULL;
+ }
+
+ if ((prefix & MPPE_HEADER_FLAGMASK) == MPPE_HEADER_FLAG) {
+ log_Printf(LogDEBUG, "MPPEInput: Key changed [%u]\n", prefix);
+ MPPEKeyChange(mip);
+ dictinit = 1;
+ } else if (flushed)
+ dictinit = 1;
+
+ /*
+ * mip->cohnum contains what we expect to receive next time in stateful
+ * mode.
+ */
+ mip->cohnum++;
+ mip->cohnum &= ~MPPE_HEADER_BITMASK;
+ }
+
+ if (dictinit) {
+ log_Printf(LogDEBUG, "MPPEInput: Dictionary initialised [%u]\n", prefix);
+ RC4_set_key(&mip->rc4key, mip->keylen, mip->sesskey);
+ }
+
+ mp = mbuf_Read(mp, proto, 2);
+ RC4(&mip->rc4key, 2, (char *)proto, (char *)proto);
+ *proto = ntohs(*proto);
+
+ rp = MBUF_CTOP(mp);
+ len = m_length(mp);
+ RC4(&mip->rc4key, len, rp, rp);
+
+ log_Printf(LogDEBUG, "MPPEInput: Decrypted: Proto %02x (%d bytes)\n",
+ *proto, len);
+ log_DumpBp(LogDEBUG, "MPPEInput: Decrypted: Packet:", mp);
+
+ ccp->uncompin += len;
+
+ return mp;
+}
+
+static void
+MPPEDictSetup(void *v __unused, struct ccp *ccp __unused,
+ u_short proto __unused, struct mbuf *mp __unused)
+{
+ /* Nothing to see here */
+}
+
+static const char *
+MPPEDispOpts(struct fsm_opt *o)
+{
+ static char buf[70];
+ u_int32_t val;
+ char ch;
+ int len, n;
+
+ ua_ntohl(o->data, &val);
+ len = 0;
+ if ((n = snprintf(buf, sizeof buf, "value 0x%08x ", (unsigned)val)) > 0)
+ len += n;
+ if (!(val & MPPE_OPT_BITMASK)) {
+ if ((n = snprintf(buf + len, sizeof buf - len, "(0")) > 0)
+ len += n;
+ } else {
+ ch = '(';
+ if (val & MPPE_OPT_128BIT) {
+ if ((n = snprintf(buf + len, sizeof buf - len, "%c128", ch)) > 0)
+ len += n;
+ ch = '/';
+ }
+ if (val & MPPE_OPT_56BIT) {
+ if ((n = snprintf(buf + len, sizeof buf - len, "%c56", ch)) > 0)
+ len += n;
+ ch = '/';
+ }
+ if (val & MPPE_OPT_40BIT) {
+ if ((n = snprintf(buf + len, sizeof buf - len, "%c40", ch)) > 0)
+ len += n;
+ ch = '/';
+ }
+ }
+
+ if ((n = snprintf(buf + len, sizeof buf - len, " bits, state%s",
+ (val & MPPE_OPT_STATELESS) ? "less" : "ful")) > 0)
+ len += n;
+
+ if (val & MPPE_OPT_COMPRESSED) {
+ if ((n = snprintf(buf + len, sizeof buf - len, ", compressed")) > 0)
+ len += n;
+ }
+
+ snprintf(buf + len, sizeof buf - len, ")");
+
+ return buf;
+}
+
+static int
+MPPEUsable(struct fsm *fp)
+{
+ int ok;
+#ifndef NORADIUS
+ struct radius *r = &fp->bundle->radius;
+
+ /*
+ * If the radius server gave us RAD_MICROSOFT_MS_MPPE_ENCRYPTION_TYPES,
+ * use that instead of our configuration value.
+ */
+ if (*r->cfg.file) {
+ ok = r->mppe.sendkeylen && r->mppe.recvkeylen;
+ if (!ok)
+ log_Printf(LogCCP, "MPPE: Not permitted by RADIUS server\n");
+ } else
+#endif
+ {
+ struct lcp *lcp = &fp->link->lcp;
+ ok = (lcp->want_auth == PROTO_CHAP && lcp->want_authtype == 0x81) ||
+ (lcp->his_auth == PROTO_CHAP && lcp->his_authtype == 0x81);
+ if (!ok)
+ log_Printf(LogCCP, "MPPE: Not usable without CHAP81\n");
+ }
+
+ return ok;
+}
+
+static int
+MPPERequired(struct fsm *fp)
+{
+#ifndef NORADIUS
+ /*
+ * If the radius server gave us RAD_MICROSOFT_MS_MPPE_ENCRYPTION_POLICY,
+ * use that instead of our configuration value.
+ */
+ if (*fp->bundle->radius.cfg.file && fp->bundle->radius.mppe.policy)
+ return fp->bundle->radius.mppe.policy == MPPE_POLICY_REQUIRED ? 1 : 0;
+#endif
+
+ return fp->link->ccp.cfg.mppe.required;
+}
+
+static u_int32_t
+MPPE_ConfigVal(struct bundle *bundle __unused, const struct ccp_config *cfg)
+{
+ u_int32_t val;
+
+ val = cfg->mppe.state == MPPE_STATELESS ? MPPE_OPT_STATELESS : 0;
+#ifndef NORADIUS
+ /*
+ * If the radius server gave us RAD_MICROSOFT_MS_MPPE_ENCRYPTION_TYPES,
+ * use that instead of our configuration value.
+ */
+ if (*bundle->radius.cfg.file && bundle->radius.mppe.types) {
+ if (bundle->radius.mppe.types & MPPE_TYPE_40BIT)
+ val |= MPPE_OPT_40BIT;
+ if (bundle->radius.mppe.types & MPPE_TYPE_128BIT)
+ val |= MPPE_OPT_128BIT;
+ } else
+#endif
+ switch(cfg->mppe.keybits) {
+ case 128:
+ val |= MPPE_OPT_128BIT;
+ break;
+ case 56:
+ val |= MPPE_OPT_56BIT;
+ break;
+ case 40:
+ val |= MPPE_OPT_40BIT;
+ break;
+ case 0:
+ val |= MPPE_OPT_128BIT | MPPE_OPT_56BIT | MPPE_OPT_40BIT;
+ break;
+ }
+
+ return val;
+}
+
+/*
+ * What options should we use for our first configure request
+ */
+static void
+MPPEInitOptsOutput(struct bundle *bundle, struct fsm_opt *o,
+ const struct ccp_config *cfg)
+{
+ u_int32_t mval;
+
+ o->hdr.len = 6;
+
+ if (!MPPE_MasterKeyValid) {
+ log_Printf(LogCCP, "MPPE: MasterKey is invalid,"
+ " MPPE is available only with CHAP81 authentication\n");
+ mval = 0;
+ ua_htonl(&mval, o->data);
+ return;
+ }
+
+
+ mval = MPPE_ConfigVal(bundle, cfg);
+ ua_htonl(&mval, o->data);
+}
+
+/*
+ * Our CCP request was NAK'd with the given options
+ */
+static int
+MPPESetOptsOutput(struct bundle *bundle, struct fsm_opt *o,
+ const struct ccp_config *cfg)
+{
+ u_int32_t mval, peer;
+
+ ua_ntohl(o->data, &peer);
+
+ if (!MPPE_MasterKeyValid)
+ /* Treat their NAK as a REJ */
+ return MODE_NAK;
+
+ mval = MPPE_ConfigVal(bundle, cfg);
+
+ /*
+ * If we haven't been configured with a specific number of keybits, allow
+ * whatever the peer asks for.
+ */
+ if (!cfg->mppe.keybits) {
+ mval &= ~MPPE_OPT_BITMASK;
+ mval |= (peer & MPPE_OPT_BITMASK);
+ if (!(mval & MPPE_OPT_BITMASK))
+ mval |= MPPE_OPT_128BIT;
+ }
+
+ /* Adjust our statelessness */
+ if (cfg->mppe.state == MPPE_ANYSTATE) {
+ mval &= ~MPPE_OPT_STATELESS;
+ mval |= (peer & MPPE_OPT_STATELESS);
+ }
+
+ ua_htonl(&mval, o->data);
+
+ return MODE_ACK;
+}
+
+/*
+ * The peer has requested the given options
+ */
+static int
+MPPESetOptsInput(struct bundle *bundle, struct fsm_opt *o,
+ const struct ccp_config *cfg)
+{
+ u_int32_t mval, peer;
+ int res = MODE_ACK;
+
+ ua_ntohl(o->data, &peer);
+ if (!MPPE_MasterKeyValid) {
+ if (peer != 0) {
+ peer = 0;
+ ua_htonl(&peer, o->data);
+ return MODE_NAK;
+ } else
+ return MODE_ACK;
+ }
+
+ mval = MPPE_ConfigVal(bundle, cfg);
+
+ if (peer & ~MPPE_OPT_MASK)
+ /* He's asking for bits we don't know about */
+ res = MODE_NAK;
+
+ if (peer & MPPE_OPT_STATELESS) {
+ if (cfg->mppe.state == MPPE_STATEFUL)
+ /* Peer can't have stateless */
+ res = MODE_NAK;
+ else
+ /* Peer wants stateless, that's ok */
+ mval |= MPPE_OPT_STATELESS;
+ } else {
+ if (cfg->mppe.state == MPPE_STATELESS)
+ /* Peer must have stateless */
+ res = MODE_NAK;
+ else
+ /* Peer doesn't want stateless, that's ok */
+ mval &= ~MPPE_OPT_STATELESS;
+ }
+
+ /* If we've got a configured number of keybits - the peer must use that */
+ if (cfg->mppe.keybits) {
+ ua_htonl(&mval, o->data);
+ return peer == mval ? res : MODE_NAK;
+ }
+
+ /* If a specific number of bits hasn't been requested, we'll need to NAK */
+ switch (peer & MPPE_OPT_BITMASK) {
+ case MPPE_OPT_128BIT:
+ case MPPE_OPT_56BIT:
+ case MPPE_OPT_40BIT:
+ break;
+ default:
+ res = MODE_NAK;
+ }
+
+ /* Suggest the best number of bits */
+ mval &= ~MPPE_OPT_BITMASK;
+ if (peer & MPPE_OPT_128BIT)
+ mval |= MPPE_OPT_128BIT;
+ else if (peer & MPPE_OPT_56BIT)
+ mval |= MPPE_OPT_56BIT;
+ else if (peer & MPPE_OPT_40BIT)
+ mval |= MPPE_OPT_40BIT;
+ else
+ mval |= MPPE_OPT_128BIT;
+ ua_htonl(&mval, o->data);
+
+ return res;
+}
+
+static struct mppe_state *
+MPPE_InitState(struct fsm_opt *o)
+{
+ struct mppe_state *mp;
+ u_int32_t val;
+
+ if ((mp = calloc(1, sizeof *mp)) != NULL) {
+ ua_ntohl(o->data, &val);
+
+ switch (val & MPPE_OPT_BITMASK) {
+ case MPPE_OPT_128BIT:
+ mp->keylen = 16;
+ mp->keybits = 128;
+ break;
+ case MPPE_OPT_56BIT:
+ mp->keylen = 8;
+ mp->keybits = 56;
+ break;
+ case MPPE_OPT_40BIT:
+ mp->keylen = 8;
+ mp->keybits = 40;
+ break;
+ default:
+ log_Printf(LogWARN, "Unexpected MPPE options 0x%08x\n", val);
+ free(mp);
+ return NULL;
+ }
+
+ mp->stateless = !!(val & MPPE_OPT_STATELESS);
+ }
+
+ return mp;
+}
+
+static void *
+MPPEInitInput(struct bundle *bundle __unused, struct fsm_opt *o)
+{
+ struct mppe_state *mip;
+
+ if (!MPPE_MasterKeyValid) {
+ log_Printf(LogWARN, "MPPE: Cannot initialise without CHAP81\n");
+ return NULL;
+ }
+
+ if ((mip = MPPE_InitState(o)) == NULL) {
+ log_Printf(LogWARN, "MPPEInput: Cannot initialise - unexpected options\n");
+ return NULL;
+ }
+
+ log_Printf(LogDEBUG, "MPPE: InitInput: %d-bits\n", mip->keybits);
+
+#ifndef NORADIUS
+ if (*bundle->radius.cfg.file && bundle->radius.mppe.recvkey) {
+ if (mip->keylen > bundle->radius.mppe.recvkeylen)
+ mip->keylen = bundle->radius.mppe.recvkeylen;
+ if (mip->keylen > sizeof mip->mastkey)
+ mip->keylen = sizeof mip->mastkey;
+ memcpy(mip->mastkey, bundle->radius.mppe.recvkey, mip->keylen);
+ } else
+#endif
+ GetAsymetricStartKey(MPPE_MasterKey, mip->mastkey, mip->keylen, 0,
+ MPPE_IsServer);
+
+ GetNewKeyFromSHA(mip->mastkey, mip->mastkey, mip->keylen, mip->sesskey);
+
+ MPPEReduceSessionKey(mip);
+
+ log_Printf(LogCCP, "MPPE: Input channel initiated\n");
+
+ if (!mip->stateless) {
+ /*
+ * We need to initialise our dictionary here as the first packet we
+ * receive is unlikely to have the FLUSHED bit set.
+ */
+ log_Printf(LogDEBUG, "MPPEInitInput: Dictionary initialised [%d]\n",
+ mip->cohnum);
+ RC4_set_key(&mip->rc4key, mip->keylen, mip->sesskey);
+ } else {
+ /*
+ * We do the first key change here as the first packet is expected
+ * to have a sequence number of 0 and we'll therefore not expect
+ * to have to change the key at that point.
+ */
+ log_Printf(LogDEBUG, "MPPEInitInput: Key changed [%d]\n", mip->cohnum);
+ MPPEKeyChange(mip);
+ }
+
+ return mip;
+}
+
+static void *
+MPPEInitOutput(struct bundle *bundle __unused, struct fsm_opt *o)
+{
+ struct mppe_state *mop;
+
+ if (!MPPE_MasterKeyValid) {
+ log_Printf(LogWARN, "MPPE: Cannot initialise without CHAP81\n");
+ return NULL;
+ }
+
+ if ((mop = MPPE_InitState(o)) == NULL) {
+ log_Printf(LogWARN, "MPPEOutput: Cannot initialise - unexpected options\n");
+ return NULL;
+ }
+
+ log_Printf(LogDEBUG, "MPPE: InitOutput: %d-bits\n", mop->keybits);
+
+#ifndef NORADIUS
+ if (*bundle->radius.cfg.file && bundle->radius.mppe.sendkey) {
+ if (mop->keylen > bundle->radius.mppe.sendkeylen)
+ mop->keylen = bundle->radius.mppe.sendkeylen;
+ if (mop->keylen > sizeof mop->mastkey)
+ mop->keylen = sizeof mop->mastkey;
+ memcpy(mop->mastkey, bundle->radius.mppe.sendkey, mop->keylen);
+ } else
+#endif
+ GetAsymetricStartKey(MPPE_MasterKey, mop->mastkey, mop->keylen, 1,
+ MPPE_IsServer);
+
+ GetNewKeyFromSHA(mop->mastkey, mop->mastkey, mop->keylen, mop->sesskey);
+
+ MPPEReduceSessionKey(mop);
+
+ log_Printf(LogCCP, "MPPE: Output channel initiated\n");
+
+ if (!mop->stateless) {
+ /*
+ * We need to initialise our dictionary now as the first packet we
+ * send won't have the FLUSHED bit set.
+ */
+ log_Printf(LogDEBUG, "MPPEInitOutput: Dictionary initialised [%d]\n",
+ mop->cohnum);
+ RC4_set_key(&mop->rc4key, mop->keylen, mop->sesskey);
+ }
+
+ return mop;
+}
+
+static void
+MPPETermInput(void *v)
+{
+ free(v);
+}
+
+static void
+MPPETermOutput(void *v)
+{
+ free(v);
+}
+
+const struct ccp_algorithm MPPEAlgorithm = {
+ TY_MPPE,
+ CCP_NEG_MPPE,
+ MPPEDispOpts,
+ MPPEUsable,
+ MPPERequired,
+ {
+ MPPESetOptsInput,
+ MPPEInitInput,
+ MPPETermInput,
+ MPPEResetInput,
+ MPPEInput,
+ MPPEDictSetup
+ },
+ {
+ 2,
+ MPPEInitOptsOutput,
+ MPPESetOptsOutput,
+ MPPEInitOutput,
+ MPPETermOutput,
+ MPPEResetOutput,
+ MPPEOutput
+ },
+};
diff --git a/usr.sbin/ppp/mppe.h b/usr.sbin/ppp/mppe.h
new file mode 100644
index 0000000..c70a609
--- /dev/null
+++ b/usr.sbin/ppp/mppe.h
@@ -0,0 +1,33 @@
+/*-
+ * Copyright (c) 2000 Semen Ustimenko <semenu@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$
+ */
+
+#define MPPE_KEY_LEN 16
+extern const struct ccp_algorithm MPPEAlgorithm;
+extern int MPPE_MasterKeyValid;
+extern int MPPE_IsServer;
+extern char MPPE_MasterKey[];
diff --git a/usr.sbin/ppp/nat_cmd.c b/usr.sbin/ppp/nat_cmd.c
new file mode 100644
index 0000000..accb149
--- /dev/null
+++ b/usr.sbin/ppp/nat_cmd.c
@@ -0,0 +1,601 @@
+/*-
+ * Copyright (c) 2001 Charles Mott <cm@linktel.net>
+ * Brian Somers <brian@Awfulhak.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/param.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+
+#ifdef LOCALNAT
+#include "alias.h"
+#else
+#include <alias.h>
+#endif
+
+#include "layer.h"
+#include "proto.h"
+#include "defs.h"
+#include "command.h"
+#include "log.h"
+#include "nat_cmd.h"
+#include "descriptor.h"
+#include "prompt.h"
+#include "timer.h"
+#include "fsm.h"
+#include "slcompress.h"
+#include "throughput.h"
+#include "iplist.h"
+#include "mbuf.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "ncpaddr.h"
+#include "ip.h"
+#include "ipcp.h"
+#include "ipv6cp.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "mp.h"
+#include "filter.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ncp.h"
+#include "bundle.h"
+
+
+#define NAT_EXTRABUF (13)
+
+static int StrToAddr(const char *, struct in_addr *);
+static int StrToPortRange(const char *, u_short *, u_short *, const char *);
+static int StrToAddrAndPort(const char *, struct in_addr *, u_short *,
+ u_short *, const char *);
+
+extern struct libalias *la;
+
+static void
+lowhigh(u_short *a, u_short *b)
+{
+ if (a > b) {
+ u_short c;
+
+ c = *b;
+ *b = *a;
+ *a = c;
+ }
+}
+
+int
+nat_RedirectPort(struct cmdargs const *arg)
+{
+ if (!arg->bundle->NatEnabled) {
+ prompt_Printf(arg->prompt, "Alias not enabled\n");
+ return 1;
+ } else if (arg->argc == arg->argn + 3 || arg->argc == arg->argn + 4) {
+ char proto_constant;
+ const char *proto;
+ struct in_addr localaddr;
+ u_short hlocalport, llocalport;
+ struct in_addr aliasaddr;
+ u_short haliasport, laliasport;
+ struct in_addr remoteaddr;
+ u_short hremoteport, lremoteport;
+ struct alias_link *link;
+ int error;
+
+ proto = arg->argv[arg->argn];
+ if (strcmp(proto, "tcp") == 0) {
+ proto_constant = IPPROTO_TCP;
+ } else if (strcmp(proto, "udp") == 0) {
+ proto_constant = IPPROTO_UDP;
+ } else {
+ prompt_Printf(arg->prompt, "port redirect: protocol must be"
+ " tcp or udp\n");
+ return -1;
+ }
+
+ error = StrToAddrAndPort(arg->argv[arg->argn+1], &localaddr, &llocalport,
+ &hlocalport, proto);
+ if (error) {
+ prompt_Printf(arg->prompt, "nat port: error reading localaddr:port\n");
+ return -1;
+ }
+
+ error = StrToPortRange(arg->argv[arg->argn+2], &laliasport, &haliasport,
+ proto);
+ if (error) {
+ prompt_Printf(arg->prompt, "nat port: error reading alias port\n");
+ return -1;
+ }
+ aliasaddr.s_addr = INADDR_ANY;
+
+ if (arg->argc == arg->argn + 4) {
+ error = StrToAddrAndPort(arg->argv[arg->argn+3], &remoteaddr,
+ &lremoteport, &hremoteport, proto);
+ if (error) {
+ prompt_Printf(arg->prompt, "nat port: error reading "
+ "remoteaddr:port\n");
+ return -1;
+ }
+ } else {
+ remoteaddr.s_addr = INADDR_ANY;
+ lremoteport = hremoteport = 0;
+ }
+
+ lowhigh(&llocalport, &hlocalport);
+ lowhigh(&laliasport, &haliasport);
+ lowhigh(&lremoteport, &hremoteport);
+
+ if (haliasport - laliasport != hlocalport - llocalport) {
+ prompt_Printf(arg->prompt, "nat port: local & alias port ranges "
+ "are not equal\n");
+ return -1;
+ }
+
+ if (hremoteport && hremoteport - lremoteport != hlocalport - llocalport) {
+ prompt_Printf(arg->prompt, "nat port: local & remote port ranges "
+ "are not equal\n");
+ return -1;
+ }
+
+ do {
+ link = LibAliasRedirectPort(la, localaddr, htons(llocalport),
+ remoteaddr, htons(lremoteport),
+ aliasaddr, htons(laliasport),
+ proto_constant);
+
+ if (link == NULL) {
+ prompt_Printf(arg->prompt, "nat port: %d: error %d\n", laliasport,
+ error);
+ return 1;
+ }
+ llocalport++;
+ if (hremoteport)
+ lremoteport++;
+ } while (laliasport++ < haliasport);
+
+ return 0;
+ }
+
+ return -1;
+}
+
+
+int
+nat_RedirectAddr(struct cmdargs const *arg)
+{
+ if (!arg->bundle->NatEnabled) {
+ prompt_Printf(arg->prompt, "nat not enabled\n");
+ return 1;
+ } else if (arg->argc == arg->argn+2) {
+ int error;
+ struct in_addr localaddr, aliasaddr;
+ struct alias_link *link;
+
+ error = StrToAddr(arg->argv[arg->argn], &localaddr);
+ if (error) {
+ prompt_Printf(arg->prompt, "address redirect: invalid local address\n");
+ return 1;
+ }
+ error = StrToAddr(arg->argv[arg->argn+1], &aliasaddr);
+ if (error) {
+ prompt_Printf(arg->prompt, "address redirect: invalid alias address\n");
+ prompt_Printf(arg->prompt, "usage: nat %s %s\n", arg->cmd->name,
+ arg->cmd->syntax);
+ return 1;
+ }
+ link = LibAliasRedirectAddr(la, localaddr, aliasaddr);
+ if (link == NULL) {
+ prompt_Printf(arg->prompt, "address redirect: packet aliasing"
+ " engine error\n");
+ prompt_Printf(arg->prompt, "usage: nat %s %s\n", arg->cmd->name,
+ arg->cmd->syntax);
+ }
+ } else
+ return -1;
+
+ return 0;
+}
+
+
+int
+nat_RedirectProto(struct cmdargs const *arg)
+{
+ if (!arg->bundle->NatEnabled) {
+ prompt_Printf(arg->prompt, "nat not enabled\n");
+ return 1;
+ } else if (arg->argc >= arg->argn + 2 && arg->argc <= arg->argn + 4) {
+ struct in_addr localIP, publicIP, remoteIP;
+ struct alias_link *link;
+ struct protoent *pe;
+ int error;
+ unsigned len;
+
+ len = strlen(arg->argv[arg->argn]);
+ if (len == 0) {
+ prompt_Printf(arg->prompt, "proto redirect: invalid protocol\n");
+ return 1;
+ }
+ if (strspn(arg->argv[arg->argn], "01234567") == len)
+ pe = getprotobynumber(atoi(arg->argv[arg->argn]));
+ else
+ pe = getprotobyname(arg->argv[arg->argn]);
+ if (pe == NULL) {
+ prompt_Printf(arg->prompt, "proto redirect: invalid protocol\n");
+ return 1;
+ }
+
+ error = StrToAddr(arg->argv[arg->argn + 1], &localIP);
+ if (error) {
+ prompt_Printf(arg->prompt, "proto redirect: invalid src address\n");
+ return 1;
+ }
+
+ if (arg->argc >= arg->argn + 3) {
+ error = StrToAddr(arg->argv[arg->argn + 2], &publicIP);
+ if (error) {
+ prompt_Printf(arg->prompt, "proto redirect: invalid alias address\n");
+ prompt_Printf(arg->prompt, "usage: nat %s %s\n", arg->cmd->name,
+ arg->cmd->syntax);
+ return 1;
+ }
+ } else
+ publicIP.s_addr = INADDR_ANY;
+
+ if (arg->argc == arg->argn + 4) {
+ error = StrToAddr(arg->argv[arg->argn + 2], &remoteIP);
+ if (error) {
+ prompt_Printf(arg->prompt, "proto redirect: invalid dst address\n");
+ prompt_Printf(arg->prompt, "usage: nat %s %s\n", arg->cmd->name,
+ arg->cmd->syntax);
+ return 1;
+ }
+ } else
+ remoteIP.s_addr = INADDR_ANY;
+
+ link = LibAliasRedirectProto(la, localIP, remoteIP, publicIP, pe->p_proto);
+ if (link == NULL) {
+ prompt_Printf(arg->prompt, "proto redirect: packet aliasing"
+ " engine error\n");
+ prompt_Printf(arg->prompt, "usage: nat %s %s\n", arg->cmd->name,
+ arg->cmd->syntax);
+ }
+ } else
+ return -1;
+
+ return 0;
+}
+
+
+static int
+StrToAddr(const char *str, struct in_addr *addr)
+{
+ struct hostent *hp;
+
+ if (inet_aton(str, addr))
+ return 0;
+
+ hp = gethostbyname(str);
+ if (!hp) {
+ log_Printf(LogWARN, "StrToAddr: Unknown host %s.\n", str);
+ return -1;
+ }
+ *addr = *((struct in_addr *) hp->h_addr);
+ return 0;
+}
+
+
+static int
+StrToPort(const char *str, u_short *port, const char *proto)
+{
+ struct servent *sp;
+ char *end;
+
+ *port = strtol(str, &end, 10);
+ if (*end != '\0') {
+ sp = getservbyname(str, proto);
+ if (sp == NULL) {
+ log_Printf(LogWARN, "StrToAddr: Unknown port or service %s/%s.\n",
+ str, proto);
+ return -1;
+ }
+ *port = ntohs(sp->s_port);
+ }
+
+ return 0;
+}
+
+static int
+StrToPortRange(const char *str, u_short *low, u_short *high, const char *proto)
+{
+ char *minus;
+ int res;
+
+ minus = strchr(str, '-');
+ if (minus)
+ *minus = '\0'; /* Cheat the const-ness ! */
+
+ res = StrToPort(str, low, proto);
+
+ if (minus)
+ *minus = '-'; /* Cheat the const-ness ! */
+
+ if (res == 0) {
+ if (minus)
+ res = StrToPort(minus + 1, high, proto);
+ else
+ *high = *low;
+ }
+
+ return res;
+}
+
+static int
+StrToAddrAndPort(const char *str, struct in_addr *addr, u_short *low,
+ u_short *high, const char *proto)
+{
+ char *colon;
+ int res;
+
+ colon = strchr(str, ':');
+ if (!colon) {
+ log_Printf(LogWARN, "StrToAddrAndPort: %s is missing port number.\n", str);
+ return -1;
+ }
+
+ *colon = '\0'; /* Cheat the const-ness ! */
+ res = StrToAddr(str, addr);
+ *colon = ':'; /* Cheat the const-ness ! */
+ if (res != 0)
+ return -1;
+
+ return StrToPortRange(colon + 1, low, high, proto);
+}
+
+int
+nat_ProxyRule(struct cmdargs const *arg)
+{
+ char cmd[LINE_LEN];
+ int f, pos;
+ size_t len;
+
+ if (arg->argn >= arg->argc)
+ return -1;
+
+ for (f = arg->argn, pos = 0; f < arg->argc; f++) {
+ len = strlen(arg->argv[f]);
+ if (sizeof cmd - pos < len + (len ? 1 : 0))
+ break;
+ if (len)
+ cmd[pos++] = ' ';
+ strcpy(cmd + pos, arg->argv[f]);
+ pos += len;
+ }
+
+ return LibAliasProxyRule(la, cmd);
+}
+
+int
+nat_SetTarget(struct cmdargs const *arg)
+{
+ struct in_addr addr;
+
+ if (arg->argc == arg->argn) {
+ addr.s_addr = INADDR_ANY;
+ LibAliasSetTarget(la, addr);
+ return 0;
+ }
+
+ if (arg->argc != arg->argn + 1)
+ return -1;
+
+ if (!strcasecmp(arg->argv[arg->argn], "MYADDR")) {
+ addr.s_addr = INADDR_ANY;
+ LibAliasSetTarget(la, addr);
+ return 0;
+ }
+
+ addr = GetIpAddr(arg->argv[arg->argn]);
+ if (addr.s_addr == INADDR_NONE) {
+ log_Printf(LogWARN, "%s: invalid address\n", arg->argv[arg->argn]);
+ return 1;
+ }
+
+ LibAliasSetTarget(la, addr);
+ return 0;
+}
+
+#ifndef NO_FW_PUNCH
+int
+nat_PunchFW(struct cmdargs const *arg)
+{
+ char *end;
+ long base, count;
+
+ if (arg->argc == arg->argn) {
+ LibAliasSetMode(la, 0, PKT_ALIAS_PUNCH_FW);
+ return 0;
+ }
+
+ if (arg->argc != arg->argn + 2)
+ return -1;
+
+ base = strtol(arg->argv[arg->argn], &end, 10);
+ if (*end != '\0' || base < 0)
+ return -1;
+
+ count = strtol(arg->argv[arg->argn + 1], &end, 10);
+ if (*end != '\0' || count < 0)
+ return -1;
+
+ LibAliasSetFWBase(la, base, count);
+ LibAliasSetMode(la, PKT_ALIAS_PUNCH_FW, PKT_ALIAS_PUNCH_FW);
+
+ return 0;
+}
+#endif
+
+int
+nat_SkinnyPort(struct cmdargs const *arg)
+{
+ char *end;
+ long port;
+
+ if (arg->argc == arg->argn) {
+ LibAliasSetSkinnyPort(la, 0);
+ return 0;
+ }
+
+ if (arg->argc != arg->argn + 1)
+ return -1;
+
+ port = strtol(arg->argv[arg->argn], &end, 10);
+ if (*end != '\0' || port < 0)
+ return -1;
+
+ LibAliasSetSkinnyPort(la, port);
+
+ return 0;
+}
+
+static struct mbuf *
+nat_LayerPush(struct bundle *bundle, struct link *l __unused, struct mbuf *bp,
+ int pri __unused, u_short *proto)
+{
+ if (!bundle->NatEnabled || *proto != PROTO_IP)
+ return bp;
+
+ log_Printf(LogDEBUG, "nat_LayerPush: PROTO_IP -> PROTO_IP\n");
+ m_settype(bp, MB_NATOUT);
+ /* Ensure there's a bit of extra buffer for the NAT code... */
+ bp = m_pullup(m_append(bp, NULL, NAT_EXTRABUF));
+ LibAliasOut(la, MBUF_CTOP(bp), bp->m_len);
+ bp->m_len = ntohs(((struct ip *)MBUF_CTOP(bp))->ip_len);
+
+ return bp;
+}
+
+static struct mbuf *
+nat_LayerPull(struct bundle *bundle, struct link *l __unused, struct mbuf *bp,
+ u_short *proto)
+{
+ static int gfrags;
+ int ret, len, nfrags;
+ struct mbuf **last;
+ char *fptr;
+
+ if (!bundle->NatEnabled || *proto != PROTO_IP)
+ return bp;
+
+ log_Printf(LogDEBUG, "nat_LayerPull: PROTO_IP -> PROTO_IP\n");
+ m_settype(bp, MB_NATIN);
+ /* Ensure there's a bit of extra buffer for the NAT code... */
+ bp = m_pullup(m_append(bp, NULL, NAT_EXTRABUF));
+ ret = LibAliasIn(la, MBUF_CTOP(bp), bp->m_len);
+
+ bp->m_len = ntohs(((struct ip *)MBUF_CTOP(bp))->ip_len);
+ if (bp->m_len > MAX_MRU) {
+ log_Printf(LogWARN, "nat_LayerPull: Problem with IP header length (%lu)\n",
+ (unsigned long)bp->m_len);
+ m_freem(bp);
+ return NULL;
+ }
+
+ switch (ret) {
+ case PKT_ALIAS_OK:
+ break;
+
+ case PKT_ALIAS_UNRESOLVED_FRAGMENT:
+ /* Save the data for later */
+ if ((fptr = malloc(bp->m_len)) == NULL) {
+ log_Printf(LogWARN, "nat_LayerPull: Dropped unresolved fragment -"
+ " out of memory!\n");
+ m_freem(bp);
+ bp = NULL;
+ } else {
+ bp = mbuf_Read(bp, fptr, bp->m_len);
+ LibAliasSaveFragment(la, fptr);
+ log_Printf(LogDEBUG, "Store another frag (%lu) - now %d\n",
+ (unsigned long)((struct ip *)fptr)->ip_id, ++gfrags);
+ }
+ break;
+
+ case PKT_ALIAS_FOUND_HEADER_FRAGMENT:
+ /* Fetch all the saved fragments and chain them on the end of `bp' */
+ last = &bp->m_nextpkt;
+ nfrags = 0;
+ while ((fptr = LibAliasGetFragment(la, MBUF_CTOP(bp))) != NULL) {
+ nfrags++;
+ LibAliasFragmentIn(la, MBUF_CTOP(bp), fptr);
+ len = ntohs(((struct ip *)fptr)->ip_len);
+ *last = m_get(len, MB_NATIN);
+ memcpy(MBUF_CTOP(*last), fptr, len);
+ free(fptr);
+ last = &(*last)->m_nextpkt;
+ }
+ gfrags -= nfrags;
+ log_Printf(LogDEBUG, "Found a frag header (%lu) - plus %d more frags (no"
+ "w %d)\n", (unsigned long)((struct ip *)MBUF_CTOP(bp))->ip_id,
+ nfrags, gfrags);
+ break;
+
+ case PKT_ALIAS_IGNORED:
+ if (LibAliasSetMode(la, 0, 0) & PKT_ALIAS_DENY_INCOMING) {
+ log_Printf(LogTCPIP, "NAT engine denied data:\n");
+ m_freem(bp);
+ bp = NULL;
+ } else if (log_IsKept(LogTCPIP)) {
+ log_Printf(LogTCPIP, "NAT engine ignored data:\n");
+ PacketCheck(bundle, AF_INET, MBUF_CTOP(bp), bp->m_len, NULL,
+ NULL, NULL);
+ }
+ break;
+
+ default:
+ log_Printf(LogWARN, "nat_LayerPull: Dropped a packet (%d)....\n", ret);
+ m_freem(bp);
+ bp = NULL;
+ break;
+ }
+
+ return bp;
+}
+
+struct layer natlayer =
+ { LAYER_NAT, "nat", nat_LayerPush, nat_LayerPull };
diff --git a/usr.sbin/ppp/nat_cmd.h b/usr.sbin/ppp/nat_cmd.h
new file mode 100644
index 0000000..f4c3655
--- /dev/null
+++ b/usr.sbin/ppp/nat_cmd.h
@@ -0,0 +1,42 @@
+/*-
+ * Copyright (c) 2001 Charles Mott <cm@linktel.net>
+ * Brian Somers <brian@Awfulhak.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$
+ */
+
+struct cmdargs;
+
+extern int nat_RedirectPort(struct cmdargs const *);
+extern int nat_RedirectAddr(struct cmdargs const *);
+extern int nat_RedirectProto(struct cmdargs const *);
+extern int nat_ProxyRule(struct cmdargs const *);
+extern int nat_SetTarget(struct cmdargs const *);
+#ifndef NO_FW_PUNCH
+extern int nat_PunchFW(struct cmdargs const *);
+#endif
+extern int nat_SkinnyPort(struct cmdargs const *);
+
+extern struct layer natlayer;
diff --git a/usr.sbin/ppp/ncp.c b/usr.sbin/ppp/ncp.c
new file mode 100644
index 0000000..1c12fc5
--- /dev/null
+++ b/usr.sbin/ppp/ncp.c
@@ -0,0 +1,547 @@
+/*-
+ * Copyright (c) 2001 Brian Somers <brian@Awfulhak.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/param.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <net/route.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <resolv.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "async.h"
+#include "ccp.h"
+#include "link.h"
+#include "physical.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "prompt.h"
+#include "route.h"
+#include "iface.h"
+#include "chat.h"
+#include "auth.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+
+
+static u_short default_urgent_tcp_ports[] = {
+ 21, /* ftp */
+ 22, /* ssh */
+ 23, /* telnet */
+ 513, /* login */
+ 514, /* shell */
+ 543, /* klogin */
+ 544 /* kshell */
+};
+
+#define NDEFTCPPORTS \
+ (sizeof default_urgent_tcp_ports / sizeof default_urgent_tcp_ports[0])
+
+void
+ncp_Init(struct ncp *ncp, struct bundle *bundle)
+{
+ ncp->afq = AF_INET;
+ ncp->route = NULL;
+
+ ncp->cfg.urgent.tcp.port = (u_short *)malloc(NDEFTCPPORTS * sizeof(u_short));
+ if (ncp->cfg.urgent.tcp.port == NULL) {
+ log_Printf(LogERROR, "ncp_Init: Out of memory allocating urgent ports\n");
+ ncp->cfg.urgent.tcp.nports = ncp->cfg.urgent.tcp.maxports = 0;
+ } else {
+ ncp->cfg.urgent.tcp.nports = ncp->cfg.urgent.tcp.maxports = NDEFTCPPORTS;
+ memcpy(ncp->cfg.urgent.tcp.port, default_urgent_tcp_ports,
+ NDEFTCPPORTS * sizeof(u_short));
+ }
+ ncp->cfg.urgent.tos = 1;
+
+ ncp->cfg.urgent.udp.nports = ncp->cfg.urgent.udp.maxports = 0;
+ ncp->cfg.urgent.udp.port = NULL;
+
+ mp_Init(&ncp->mp, bundle);
+
+ /* Send over the first physical link by default */
+ ipcp_Init(&ncp->ipcp, bundle, &bundle->links->physical->link,
+ &bundle->fsm);
+#ifndef NOINET6
+ ipv6cp_Init(&ncp->ipv6cp, bundle, &bundle->links->physical->link,
+ &bundle->fsm);
+#endif
+}
+
+void
+ncp_Destroy(struct ncp *ncp)
+{
+ ipcp_Destroy(&ncp->ipcp);
+#ifndef NOINET6
+ ipv6cp_Destroy(&ncp->ipv6cp);
+#endif
+
+ if (ncp->cfg.urgent.tcp.maxports) {
+ ncp->cfg.urgent.tcp.nports = ncp->cfg.urgent.tcp.maxports = 0;
+ free(ncp->cfg.urgent.tcp.port);
+ ncp->cfg.urgent.tcp.port = NULL;
+ }
+ if (ncp->cfg.urgent.udp.maxports) {
+ ncp->cfg.urgent.udp.nports = ncp->cfg.urgent.udp.maxports = 0;
+ free(ncp->cfg.urgent.udp.port);
+ ncp->cfg.urgent.udp.port = NULL;
+ }
+}
+
+int
+ncp_fsmStart(struct ncp *ncp,
+#ifdef NOINET6
+ struct bundle *bundle __unused
+#else
+ struct bundle *bundle
+#endif
+ )
+{
+ int res = 0;
+
+#ifndef NOINET6
+ if (Enabled(bundle, OPT_IPCP)) {
+#endif
+ fsm_Up(&ncp->ipcp.fsm);
+ fsm_Open(&ncp->ipcp.fsm);
+ res++;
+#ifndef NOINET6
+ }
+
+ if (Enabled(bundle, OPT_IPV6CP)) {
+ fsm_Up(&ncp->ipv6cp.fsm);
+ fsm_Open(&ncp->ipv6cp.fsm);
+ res++;
+ }
+#endif
+
+ return res;
+}
+
+void
+ncp_IfaceAddrAdded(struct ncp *ncp, const struct iface_addr *addr)
+{
+ switch (ncprange_family(&addr->ifa)) {
+ case AF_INET:
+ ipcp_IfaceAddrAdded(&ncp->ipcp, addr);
+ break;
+#ifndef NOINET6
+ case AF_INET6:
+ ipv6cp_IfaceAddrAdded(&ncp->ipv6cp, addr);
+ break;
+#endif
+ }
+}
+
+void
+ncp_IfaceAddrDeleted(struct ncp *ncp, const struct iface_addr *addr)
+{
+ if (ncprange_family(&addr->ifa) == AF_INET)
+ ipcp_IfaceAddrDeleted(&ncp->ipcp, addr);
+}
+
+void
+ncp_SetLink(struct ncp *ncp, struct link *l)
+{
+ ipcp_SetLink(&ncp->ipcp, l);
+#ifndef NOINET6
+ ipv6cp_SetLink(&ncp->ipv6cp, l);
+#endif
+}
+
+/*
+ * Enqueue a packet of the given address family. Nothing will make it
+ * down to the physical link level 'till ncp_FillPhysicalQueues() is used.
+ */
+void
+ncp_Enqueue(struct ncp *ncp, int af, unsigned pri, char *ptr, int count)
+{
+#ifndef NOINET6
+ struct ipv6cp *ipv6cp = &ncp->ipv6cp;
+#endif
+ struct ipcp *ipcp = &ncp->ipcp;
+ struct mbuf *bp;
+
+ /*
+ * We allocate an extra 6 bytes, four at the front and two at the end.
+ * This is an optimisation so that we need to do less work in
+ * m_prepend() in acf_LayerPush() and proto_LayerPush() and
+ * appending in hdlc_LayerPush().
+ */
+
+ switch (af) {
+ case AF_INET:
+ if (pri >= IPCP_QUEUES(ipcp)) {
+ log_Printf(LogERROR, "Can't store in ip queue %u\n", pri);
+ break;
+ }
+
+ bp = m_get(count + 6, MB_IPOUT);
+ bp->m_offset += 4;
+ bp->m_len -= 6;
+ memcpy(MBUF_CTOP(bp), ptr, count);
+ m_enqueue(ipcp->Queue + pri, bp);
+ break;
+
+#ifndef NOINET6
+ case AF_INET6:
+ if (pri >= IPV6CP_QUEUES(ipcp)) {
+ log_Printf(LogERROR, "Can't store in ipv6 queue %u\n", pri);
+ break;
+ }
+
+ bp = m_get(count + 6, MB_IPOUT);
+ bp->m_offset += 4;
+ bp->m_len -= 6;
+ memcpy(MBUF_CTOP(bp), ptr, count);
+ m_enqueue(ipv6cp->Queue + pri, bp);
+ break;
+#endif
+
+ default:
+ log_Printf(LogERROR, "Can't enqueue protocol family %d\n", af);
+ }
+}
+
+/*
+ * How many packets are queued to go out ?
+ */
+size_t
+ncp_QueueLen(struct ncp *ncp)
+{
+ size_t result;
+
+ result = ipcp_QueueLen(&ncp->ipcp);
+#ifndef NOINET6
+ result += ipv6cp_QueueLen(&ncp->ipv6cp);
+#endif
+ result += mp_QueueLen(&ncp->mp); /* Usually empty */
+
+ return result;
+}
+
+/*
+ * Ditch all queued packets. This is usually done after our choked timer
+ * has fired - which happens because we couldn't send any traffic over
+ * any links for some time.
+ */
+void
+ncp_DeleteQueues(struct ncp *ncp)
+{
+#ifndef NOINET6
+ struct ipv6cp *ipv6cp = &ncp->ipv6cp;
+#endif
+ struct ipcp *ipcp = &ncp->ipcp;
+ struct mp *mp = &ncp->mp;
+ struct mqueue *q;
+
+ for (q = ipcp->Queue; q < ipcp->Queue + IPCP_QUEUES(ipcp); q++)
+ while (q->top)
+ m_freem(m_dequeue(q));
+
+#ifndef NOINET6
+ for (q = ipv6cp->Queue; q < ipv6cp->Queue + IPV6CP_QUEUES(ipv6cp); q++)
+ while (q->top)
+ m_freem(m_dequeue(q));
+#endif
+
+ link_DeleteQueue(&mp->link); /* Usually empty anyway */
+}
+
+/*
+ * Arrange that each of our links has at least one packet. We keep the
+ * number of packets queued at the link level to a minimum so that the
+ * loss of a link in multi-link mode results in the minimum number of
+ * dropped packets.
+ */
+size_t
+ncp_FillPhysicalQueues(struct ncp *ncp, struct bundle *bundle)
+{
+ size_t total;
+
+ if (bundle->ncp.mp.active)
+ total = mp_FillPhysicalQueues(bundle);
+ else {
+ struct datalink *dl;
+ size_t add;
+
+ for (total = 0, dl = bundle->links; dl; dl = dl->next)
+ if (dl->state == DATALINK_OPEN) {
+ add = link_QueueLen(&dl->physical->link);
+ if (add == 0 && dl->physical->out == NULL)
+ add = ncp_PushPacket(ncp, &ncp->afq, &dl->physical->link);
+ total += add;
+ }
+ }
+
+ return total + ncp_QueueLen(&bundle->ncp);
+}
+
+/*
+ * Push a packet into the given link. ``af'' is used as a persistent record
+ * of what is to be pushed next, coming either from mp->out or ncp->afq.
+ */
+int
+ncp_PushPacket(struct ncp *ncp __unused,
+#ifdef NOINET6
+ int *af __unused,
+#else
+ int *af,
+#endif
+ struct link *l)
+{
+ struct bundle *bundle = l->lcp.fsm.bundle;
+ int res;
+
+#ifndef NOINET6
+ if (*af == AF_INET) {
+ if ((res = ipcp_PushPacket(&bundle->ncp.ipcp, l)))
+ *af = AF_INET6;
+ else
+ res = ipv6cp_PushPacket(&bundle->ncp.ipv6cp, l);
+ } else {
+ if ((res = ipv6cp_PushPacket(&bundle->ncp.ipv6cp, l)))
+ *af = AF_INET;
+ else
+ res = ipcp_PushPacket(&bundle->ncp.ipcp, l);
+ }
+#else
+ res = ipcp_PushPacket(&bundle->ncp.ipcp, l);
+#endif
+
+ return res;
+}
+
+int
+ncp_IsUrgentPort(struct port_range *range, u_short src, u_short dst)
+{
+ unsigned f;
+
+ for (f = 0; f < range->nports; f++)
+ if (range->port[f] == src || range->port[f] == dst)
+ return 1;
+
+ return 0;
+}
+
+void
+ncp_AddUrgentPort(struct port_range *range, u_short port)
+{
+ u_short *newport;
+ unsigned p;
+
+ if (range->nports == range->maxports) {
+ range->maxports += 10;
+ newport = (u_short *)realloc(range->port,
+ range->maxports * sizeof(u_short));
+ if (newport == NULL) {
+ log_Printf(LogERROR, "ncp_AddUrgentPort: realloc: %s\n",
+ strerror(errno));
+ range->maxports -= 10;
+ return;
+ }
+ range->port = newport;
+ }
+
+ for (p = 0; p < range->nports; p++)
+ if (range->port[p] == port) {
+ log_Printf(LogWARN, "%u: Port already set to urgent\n", port);
+ break;
+ } else if (range->port[p] > port) {
+ memmove(range->port + p + 1, range->port + p,
+ (range->nports - p) * sizeof(u_short));
+ range->port[p] = port;
+ range->nports++;
+ break;
+ }
+
+ if (p == range->nports)
+ range->port[range->nports++] = port;
+}
+
+void
+ncp_RemoveUrgentPort(struct port_range *range, u_short port)
+{
+ unsigned p;
+
+ for (p = 0; p < range->nports; p++)
+ if (range->port[p] == port) {
+ if (p + 1 != range->nports)
+ memmove(range->port + p, range->port + p + 1,
+ (range->nports - p - 1) * sizeof(u_short));
+ range->nports--;
+ return;
+ }
+
+ if (p == range->nports)
+ log_Printf(LogWARN, "%u: Port not set to urgent\n", port);
+}
+
+void
+ncp_ClearUrgentPorts(struct port_range *range)
+{
+ range->nports = 0;
+}
+
+int
+ncp_Show(struct cmdargs const *arg)
+{
+ struct ncp *ncp = &arg->bundle->ncp;
+ unsigned p;
+
+#ifndef NOINET6
+ prompt_Printf(arg->prompt, "Next queued AF: %s\n",
+ ncp->afq == AF_INET6 ? "inet6" : "inet");
+#endif
+
+ if (ncp->route) {
+ prompt_Printf(arg->prompt, "\n");
+ route_ShowSticky(arg->prompt, ncp->route, "Sticky routes", 1);
+ }
+
+ prompt_Printf(arg->prompt, "\nDefaults:\n");
+ prompt_Printf(arg->prompt, " sendpipe: ");
+ if (ncp->cfg.sendpipe > 0)
+ prompt_Printf(arg->prompt, "%-20ld\n", ncp->cfg.sendpipe);
+ else
+ prompt_Printf(arg->prompt, "unspecified\n");
+ prompt_Printf(arg->prompt, " recvpipe: ");
+ if (ncp->cfg.recvpipe > 0)
+ prompt_Printf(arg->prompt, "%ld\n", ncp->cfg.recvpipe);
+ else
+ prompt_Printf(arg->prompt, "unspecified\n");
+
+ prompt_Printf(arg->prompt, "\n Urgent ports\n");
+ prompt_Printf(arg->prompt, " TCP: ");
+ if (ncp->cfg.urgent.tcp.nports == 0)
+ prompt_Printf(arg->prompt, "none");
+ else
+ for (p = 0; p < ncp->cfg.urgent.tcp.nports; p++) {
+ if (p)
+ prompt_Printf(arg->prompt, ", ");
+ prompt_Printf(arg->prompt, "%u", ncp->cfg.urgent.tcp.port[p]);
+ }
+
+ prompt_Printf(arg->prompt, "\n UDP: ");
+ if (ncp->cfg.urgent.udp.nports == 0)
+ prompt_Printf(arg->prompt, "none");
+ else
+ for (p = 0; p < ncp->cfg.urgent.udp.nports; p++) {
+ if (p)
+ prompt_Printf(arg->prompt, ", ");
+ prompt_Printf(arg->prompt, "%u", ncp->cfg.urgent.udp.port[p]);
+ }
+ prompt_Printf(arg->prompt, "\n TOS: %s\n\n",
+ ncp->cfg.urgent.tos ? "yes" : "no");
+
+ return 0;
+}
+
+int
+ncp_LayersOpen(struct ncp *ncp)
+{
+ int n;
+
+ n = !!(ncp->ipcp.fsm.state == ST_OPENED);
+#ifndef NOINET6
+ n += !!(ncp->ipv6cp.fsm.state == ST_OPENED);
+#endif
+
+ return n;
+}
+
+int
+ncp_LayersUnfinished(struct ncp *ncp)
+{
+ int n = 0;
+
+ if (ncp->ipcp.fsm.state > ST_CLOSED ||
+ ncp->ipcp.fsm.state == ST_STARTING)
+ n++;
+
+#ifndef NOINET6
+ if (ncp->ipv6cp.fsm.state > ST_CLOSED ||
+ ncp->ipv6cp.fsm.state == ST_STARTING)
+ n++;
+#endif
+
+ return n;
+}
+
+void
+ncp_Close(struct ncp *ncp)
+{
+ if (ncp->ipcp.fsm.state > ST_CLOSED ||
+ ncp->ipcp.fsm.state == ST_STARTING)
+ fsm_Close(&ncp->ipcp.fsm);
+
+#ifndef NOINET6
+ if (ncp->ipv6cp.fsm.state > ST_CLOSED ||
+ ncp->ipv6cp.fsm.state == ST_STARTING)
+ fsm_Close(&ncp->ipv6cp.fsm);
+#endif
+}
+
+void
+ncp2initial(struct ncp *ncp)
+{
+ fsm2initial(&ncp->ipcp.fsm);
+#ifndef NOINET6
+ fsm2initial(&ncp->ipv6cp.fsm);
+#endif
+}
diff --git a/usr.sbin/ppp/ncp.h b/usr.sbin/ppp/ncp.h
new file mode 100644
index 0000000..99580cf
--- /dev/null
+++ b/usr.sbin/ppp/ncp.h
@@ -0,0 +1,101 @@
+/*-
+ * Copyright (c) 2001 Brian Somers <brian@Awfulhak.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$
+ */
+
+struct port_range {
+ unsigned nports; /* How many ports */
+ unsigned maxports; /* How many allocated (malloc) ports */
+ u_short *port; /* The actual ports */
+};
+
+struct ncp {
+ struct {
+ u_long sendpipe; /* route sendpipe size */
+ u_long recvpipe; /* route recvpipe size */
+
+ struct {
+ struct port_range tcp, udp; /* The range of urgent ports */
+ unsigned tos : 1; /* Urgent IPTOS_LOWDELAY packets ? */
+ } urgent;
+ } cfg;
+
+ int afq; /* Next address family to queue */
+
+ struct sticky_route *route; /* List of dynamic routes */
+
+ struct ipcp ipcp; /* Our IPCP FSM */
+#ifndef NOINET6
+ struct ipv6cp ipv6cp; /* Our IPV6CP FSM */
+#endif
+ struct mp mp; /* Our MP */
+};
+
+extern void ncp_Init(struct ncp *, struct bundle *);
+extern void ncp_Destroy(struct ncp *);
+extern int ncp_fsmStart(struct ncp *, struct bundle *);
+extern void ncp_IfaceAddrAdded(struct ncp *, const struct iface_addr *);
+extern void ncp_IfaceAddrDeleted(struct ncp *, const struct iface_addr *);
+extern void ncp_SetLink(struct ncp *, struct link *);
+extern void ncp_Enqueue(struct ncp *, int, unsigned, char *, int);
+extern void ncp_DeleteQueues(struct ncp *);
+extern size_t ncp_QueueLen(struct ncp *);
+extern size_t ncp_FillPhysicalQueues(struct ncp *, struct bundle *);
+extern int ncp_PushPacket(struct ncp *, int *, struct link *);
+extern int ncp_IsUrgentPort(struct port_range *, u_short, u_short);
+extern void ncp_AddUrgentPort(struct port_range *, u_short);
+extern void ncp_RemoveUrgentPort(struct port_range *, u_short);
+extern void ncp_ClearUrgentPorts(struct port_range *);
+extern int ncp_Show(struct cmdargs const *);
+extern int ncp_LayersOpen(struct ncp *);
+extern int ncp_LayersUnfinished(struct ncp *);
+extern void ncp_Close(struct ncp *);
+extern void ncp2initial(struct ncp *);
+
+#define ncp_IsUrgentTcpPort(ncp, p1, p2) \
+ ncp_IsUrgentPort(&(ncp)->cfg.urgent.tcp, p1, p2)
+#define ncp_IsUrgentUdpPort(ncp, p1, p2) \
+ ncp_IsUrgentPort(&(ncp)->cfg.urgent.udp, p1, p2)
+#define ncp_AddUrgentTcpPort(ncp, p) \
+ ncp_AddUrgentPort(&(ncp)->cfg.urgent.tcp, p)
+#define ncp_AddUrgentUdpPort(ncp, p) \
+ ncp_AddUrgentPort(&(ncp)->cfg.urgent.udp, p)
+#define ncp_RemoveUrgentTcpPort(ncp, p) \
+ ncp_RemoveUrgentPort(&(ncp)->cfg.urgent.tcp, p)
+#define ncp_RemoveUrgentUdpPort(ncp, p) \
+ ncp_RemoveUrgentPort(&(ncp)->cfg.urgent.udp, p)
+#define ncp_ClearUrgentTcpPorts(ncp) \
+ ncp_ClearUrgentPorts(&(ncp)->cfg.urgent.tcp)
+#define ncp_ClearUrgentUdpPorts(ncp) \
+ ncp_ClearUrgentPorts(&(ncp)->cfg.urgent.udp)
+#define ncp_ClearUrgentTOS(ncp) (ncp)->cfg.urgent.tos = 0;
+#define ncp_SetUrgentTOS(ncp) (ncp)->cfg.urgent.tos = 1;
+
+#ifndef NOINET6
+#define isncp(proto) ((proto) == PROTO_IPCP || (proto) == PROTO_IPV6CP)
+#else
+#define isncp(proto) ((proto) == PROTO_IPCP)
+#endif
diff --git a/usr.sbin/ppp/ncpaddr.c b/usr.sbin/ppp/ncpaddr.c
new file mode 100644
index 0000000..0b16999
--- /dev/null
+++ b/usr.sbin/ppp/ncpaddr.c
@@ -0,0 +1,1010 @@
+/*-
+ * Copyright (c) 2001 Brian Somers <brian@Awfulhak.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/types.h>
+#include <sys/socket.h>
+#ifdef __OpenBSD__
+#include <net/if_types.h>
+#include <net/route.h>
+#endif
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+
+#include "log.h"
+#include "ncpaddr.h"
+#include "timer.h"
+#include "fsm.h"
+#include "defs.h"
+#include "slcompress.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "mbuf.h"
+#include "ipcp.h"
+#include "descriptor.h"
+#include "layer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "mp.h"
+#include "ipv6cp.h"
+#include "ncp.h"
+
+
+#define ncprange_ip4addr u.ip4.ipaddr
+#define ncprange_ip4mask u.ip4.mask
+#define ncprange_ip4width u.ip4.width
+#define ncpaddr_ip4addr u.ip4addr
+#ifndef NOINET6
+#define ncprange_ip6addr u.ip6.ipaddr
+#define ncprange_ip6width u.ip6.width
+#define ncpaddr_ip6addr u.ip6addr
+#endif
+
+#define NCP_ASCIIBUFFERSIZE 52
+
+static struct in_addr
+bits2mask4(int bits)
+{
+ struct in_addr result;
+ u_int32_t bit = 0x80000000;
+
+ result.s_addr = 0;
+
+ while (bits) {
+ result.s_addr |= bit;
+ bit >>= 1;
+ bits--;
+ }
+
+ result.s_addr = htonl(result.s_addr);
+ return result;
+}
+
+static int
+mask42bits(struct in_addr mask)
+{
+ u_int32_t msk = ntohl(mask.s_addr);
+ u_int32_t tst;
+ int ret;
+
+ for (ret = 32, tst = 1; tst; ret--, tst <<= 1)
+ if (msk & tst)
+ break;
+
+ for (tst <<= 1; tst; tst <<= 1)
+ if (!(msk & tst))
+ break;
+
+ return tst ? -1 : ret;
+}
+
+#ifndef NOINET6
+static struct in6_addr
+bits2mask6(int bits)
+{
+ struct in6_addr result;
+ u_int32_t bit = 0x80;
+ u_char *c = result.s6_addr;
+
+ memset(&result, '\0', sizeof result);
+
+ while (bits) {
+ if (bit == 0) {
+ bit = 0x80;
+ c++;
+ }
+ *c |= bit;
+ bit >>= 1;
+ bits--;
+ }
+
+ return result;
+}
+
+static int
+mask62bits(const struct in6_addr *mask)
+{
+ const u_char masks[] = { 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe };
+ const u_char *c, *p, *end;
+ int masklen, m;
+
+ p = (const u_char *)mask;
+ for (masklen = 0, end = p + 16; p < end && *p == 0xff; p++)
+ masklen += 8;
+
+ if (p < end) {
+ for (c = masks, m = 0; c < masks + sizeof masks; c++, m++)
+ if (*c == *p) {
+ masklen += m;
+ break;
+ }
+ }
+
+ return masklen;
+}
+
+#if 0
+static void
+adjust_linklocal(struct sockaddr_in6 *sin6)
+{
+ /* XXX: ?????!?!?!!!!! This is horrible ! */
+ /*
+ * The kernel does not understand sin6_scope_id for routing at this moment.
+ * We should rather keep the embedded ID.
+ * jinmei@kame.net, 20011026
+ */
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) ||
+ IN6_IS_ADDR_MC_LINKLOCAL(&sin6->sin6_addr)) {
+ sin6->sin6_scope_id =
+ ntohs(*(u_short *)&sin6->sin6_addr.s6_addr[2]);
+ *(u_short *)&sin6->sin6_addr.s6_addr[2] = 0;
+ }
+}
+#endif
+#endif
+
+void
+ncpaddr_init(struct ncpaddr *addr)
+{
+ addr->ncpaddr_family = AF_UNSPEC;
+}
+
+int
+ncpaddr_isset(const struct ncpaddr *addr)
+{
+ return addr->ncpaddr_family != AF_UNSPEC;
+}
+
+int
+ncpaddr_isdefault(const struct ncpaddr *addr)
+{
+ switch (addr->ncpaddr_family) {
+ case AF_INET:
+ if (addr->ncpaddr_ip4addr.s_addr == INADDR_ANY)
+ return 1;
+ break;
+
+#ifndef NOINET6
+ case AF_INET6:
+ if (IN6_IS_ADDR_UNSPECIFIED(&addr->ncpaddr_ip6addr))
+ return 1;
+ break;
+#endif
+ }
+
+ return 0;
+}
+
+int
+ncpaddr_equal(const struct ncpaddr *addr, const struct ncpaddr *cmp)
+{
+ if (addr->ncpaddr_family != cmp->ncpaddr_family)
+ return 0;
+
+ switch (addr->ncpaddr_family) {
+ case AF_INET:
+ return addr->ncpaddr_ip4addr.s_addr == cmp->ncpaddr_ip4addr.s_addr;
+
+#ifndef NOINET6
+ case AF_INET6:
+ return !memcmp(&addr->ncpaddr_ip6addr, &cmp->ncpaddr_ip6addr,
+ sizeof addr->ncpaddr_ip6addr);
+#endif
+
+ case AF_UNSPEC:
+ return 1;
+ }
+
+ return 0;
+}
+
+void
+ncpaddr_copy(struct ncpaddr *addr, const struct ncpaddr *from)
+{
+ switch (from->ncpaddr_family) {
+ case AF_INET:
+ addr->ncpaddr_family = AF_INET;
+ addr->ncpaddr_ip4addr = from->ncpaddr_ip4addr;
+ break;
+#ifndef NOINET6
+ case AF_INET6:
+ addr->ncpaddr_family = AF_INET6;
+ addr->ncpaddr_ip6addr = from->ncpaddr_ip6addr;
+ break;
+#endif
+ default:
+ addr->ncpaddr_family = AF_UNSPEC;
+ }
+}
+
+void
+ncpaddr_setip4addr(struct ncpaddr *addr, u_int32_t ip)
+{
+ addr->ncpaddr_family = AF_INET;
+ addr->ncpaddr_ip4addr.s_addr = ip;
+}
+
+int
+ncpaddr_getip4addr(const struct ncpaddr *addr, u_int32_t *ip)
+{
+ if (addr->ncpaddr_family != AF_INET)
+ return 0;
+ *ip = addr->ncpaddr_ip4addr.s_addr;
+ return 1;
+}
+
+void
+ncpaddr_setip4(struct ncpaddr *addr, struct in_addr ip)
+{
+ addr->ncpaddr_family = AF_INET;
+ addr->ncpaddr_ip4addr = ip;
+}
+
+int
+ncpaddr_getip4(const struct ncpaddr *addr, struct in_addr *ip)
+{
+ if (addr->ncpaddr_family != AF_INET)
+ return 0;
+ *ip = addr->ncpaddr_ip4addr;
+ return 1;
+}
+
+#ifndef NOINET6
+void
+ncpaddr_setip6(struct ncpaddr *addr, const struct in6_addr *ip6)
+{
+ addr->ncpaddr_family = AF_INET6;
+ addr->ncpaddr_ip6addr = *ip6;
+}
+
+int
+ncpaddr_getip6(const struct ncpaddr *addr, struct in6_addr *ip6)
+{
+ if (addr->ncpaddr_family != AF_INET6)
+ return 0;
+ *ip6 = addr->ncpaddr_ip6addr;
+ return 1;
+}
+#endif
+
+void
+ncpaddr_getsa(const struct ncpaddr *addr, struct sockaddr_storage *host)
+{
+ struct sockaddr_in *host4 = (struct sockaddr_in *)host;
+#ifndef NOINET6
+ struct sockaddr_in6 *host6 = (struct sockaddr_in6 *)host;
+#endif
+
+ memset(host, '\0', sizeof(*host));
+
+ switch (addr->ncpaddr_family) {
+ case AF_INET:
+ host4->sin_family = AF_INET;
+ host4->sin_len = sizeof(*host4);
+ host4->sin_addr = addr->ncpaddr_ip4addr;
+ break;
+
+#ifndef NOINET6
+ case AF_INET6:
+ host6->sin6_family = AF_INET6;
+ host6->sin6_len = sizeof(*host6);
+ host6->sin6_addr = addr->ncpaddr_ip6addr;
+ break;
+#endif
+
+ default:
+ host->ss_family = AF_UNSPEC;
+ break;
+ }
+}
+
+void
+ncpaddr_setsa(struct ncpaddr *addr, const struct sockaddr *host)
+{
+ const struct sockaddr_in *host4 = (const struct sockaddr_in *)host;
+#ifndef NOINET6
+ const struct sockaddr_in6 *host6 = (const struct sockaddr_in6 *)host;
+#endif
+
+ switch (host->sa_family) {
+ case AF_INET:
+ addr->ncpaddr_family = AF_INET;
+ addr->ncpaddr_ip4addr = host4->sin_addr;
+ break;
+
+#ifndef NOINET6
+ case AF_INET6:
+ if (IN6_IS_ADDR_V4MAPPED(&host6->sin6_addr)) {
+ addr->ncpaddr_family = AF_INET;
+ addr->ncpaddr_ip4addr.s_addr =
+ *(const u_int32_t *)(host6->sin6_addr.s6_addr + 12);
+ } else {
+ addr->ncpaddr_family = AF_INET6;
+ addr->ncpaddr_ip6addr = host6->sin6_addr;
+ }
+ break;
+#endif
+
+ default:
+ addr->ncpaddr_family = AF_UNSPEC;
+ }
+}
+
+static char *
+ncpaddr_ntowa(const struct ncpaddr *addr)
+{
+ static char res[NCP_ASCIIBUFFERSIZE];
+#ifndef NOINET6
+ struct sockaddr_in6 sin6;
+#endif
+
+ switch (addr->ncpaddr_family) {
+ case AF_INET:
+ snprintf(res, sizeof res, "%s", inet_ntoa(addr->ncpaddr_ip4addr));
+ return res;
+
+#ifndef NOINET6
+ case AF_INET6:
+ memset(&sin6, '\0', sizeof(sin6));
+ sin6.sin6_len = sizeof(sin6);
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_addr = addr->ncpaddr_ip6addr;
+#if 0
+ adjust_linklocal(&sin6);
+#endif
+ if (getnameinfo((struct sockaddr *)&sin6, sizeof sin6, res, sizeof(res),
+ NULL, 0, NI_NUMERICHOST) != 0)
+ break;
+
+ return res;
+#endif
+ }
+
+ snprintf(res, sizeof res, "<AF_UNSPEC>");
+ return res;
+}
+
+const char *
+ncpaddr_ntoa(const struct ncpaddr *addr)
+{
+ return ncpaddr_ntowa(addr);
+}
+
+
+int
+ncpaddr_aton(struct ncpaddr *addr, struct ncp *ncp, const char *data)
+{
+ struct ncprange range;
+
+ if (!ncprange_aton(&range, ncp, data))
+ return 0;
+
+ if (range.ncprange_family == AF_INET && range.ncprange_ip4width != 32 &&
+ range.ncprange_ip4addr.s_addr != INADDR_ANY) {
+ log_Printf(LogWARN, "ncpaddr_aton: %s: Only 32 bits allowed\n", data);
+ return 0;
+ }
+
+#ifndef NOINET6
+ if (range.ncprange_family == AF_INET6 && range.ncprange_ip6width != 128 &&
+ !IN6_IS_ADDR_UNSPECIFIED(&range.ncprange_ip6addr)) {
+ log_Printf(LogWARN, "ncpaddr_aton: %s: Only 128 bits allowed\n", data);
+ return 0;
+ }
+#endif
+
+ switch (range.ncprange_family) {
+ case AF_INET:
+ addr->ncpaddr_family = range.ncprange_family;
+ addr->ncpaddr_ip4addr = range.ncprange_ip4addr;
+ return 1;
+
+#ifndef NOINET6
+ case AF_INET6:
+ addr->ncpaddr_family = range.ncprange_family;
+ addr->ncpaddr_ip6addr = range.ncprange_ip6addr;
+ return 1;
+#endif
+ }
+
+ return 0;
+}
+
+void
+ncprange_init(struct ncprange *range)
+{
+ range->ncprange_family = AF_UNSPEC;
+}
+
+int
+ncprange_isset(const struct ncprange *range)
+{
+ return range->ncprange_family != AF_UNSPEC;
+}
+
+int
+ncprange_equal(const struct ncprange *range, const struct ncprange *cmp)
+{
+ if (range->ncprange_family != cmp->ncprange_family)
+ return 0;
+
+ switch (range->ncprange_family) {
+ case AF_INET:
+ if (range->ncprange_ip4addr.s_addr != cmp->ncprange_ip4addr.s_addr)
+ return 0;
+ return range->ncprange_ip4mask.s_addr == cmp->ncprange_ip4mask.s_addr;
+
+#ifndef NOINET6
+ case AF_INET6:
+ if (range->ncprange_ip6width != cmp->ncprange_ip6width)
+ return 0;
+ return !memcmp(&range->ncprange_ip6addr, &cmp->ncprange_ip6addr,
+ sizeof range->ncprange_ip6addr);
+#endif
+
+ case AF_UNSPEC:
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+ncprange_isdefault(const struct ncprange *range)
+{
+ switch (range->ncprange_family) {
+ case AF_INET:
+ if (range->ncprange_ip4addr.s_addr == INADDR_ANY)
+ return 1;
+ break;
+
+#ifndef NOINET6
+ case AF_INET6:
+ if (range->ncprange_ip6width == 0 &&
+ IN6_IS_ADDR_UNSPECIFIED(&range->ncprange_ip6addr))
+ return 1;
+ break;
+#endif
+ }
+
+ return 0;
+}
+
+void
+ncprange_setdefault(struct ncprange *range, int af)
+{
+ memset(range, '\0', sizeof *range);
+ range->ncprange_family = af;
+}
+
+int
+ncprange_contains(const struct ncprange *range, const struct ncpaddr *addr)
+{
+#ifndef NOINET6
+ const u_char masks[] = { 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
+ const u_char *addrp, *rangep;
+ int bits;
+#endif
+
+ if (range->ncprange_family != addr->ncpaddr_family)
+ return 0;
+
+ switch (range->ncprange_family) {
+ case AF_INET:
+ return !((addr->ncpaddr_ip4addr.s_addr ^ range->ncprange_ip4addr.s_addr) &
+ range->ncprange_ip4mask.s_addr);
+
+#ifndef NOINET6
+ case AF_INET6:
+ rangep = (const u_char *)range->ncprange_ip6addr.s6_addr;
+ addrp = (const u_char *)addr->ncpaddr_ip6addr.s6_addr;
+
+ for (bits = range->ncprange_ip6width; bits > 0; bits -= 8)
+ if ((*addrp++ ^ *rangep++) & masks[bits > 7 ? 7 : bits - 1])
+ return 0;
+
+ return 1;
+#endif
+ }
+
+ return 0;
+}
+
+int
+ncprange_containsip4(const struct ncprange *range, struct in_addr addr)
+{
+ switch (range->ncprange_family) {
+ case AF_INET:
+ return !((addr.s_addr ^ range->ncprange_ip4addr.s_addr) &
+ range->ncprange_ip4mask.s_addr);
+ }
+
+ return 0;
+}
+
+void
+ncprange_copy(struct ncprange *range, const struct ncprange *from)
+{
+ switch (from->ncprange_family) {
+ case AF_INET:
+ range->ncprange_family = AF_INET;
+ range->ncprange_ip4addr = from->ncprange_ip4addr;
+ range->ncprange_ip4mask = from->ncprange_ip4mask;
+ range->ncprange_ip4width = from->ncprange_ip4width;
+ break;
+
+#ifndef NOINET6
+ case AF_INET6:
+ range->ncprange_family = AF_INET6;
+ range->ncprange_ip6addr = from->ncprange_ip6addr;
+ range->ncprange_ip6width = from->ncprange_ip6width;
+ break;
+#endif
+
+ default:
+ range->ncprange_family = AF_UNSPEC;
+ }
+}
+
+void
+ncprange_set(struct ncprange *range, const struct ncpaddr *addr, int width)
+{
+ ncprange_sethost(range, addr);
+ ncprange_setwidth(range, width);
+}
+
+void
+ncprange_sethost(struct ncprange *range, const struct ncpaddr *from)
+{
+ switch (from->ncpaddr_family) {
+ case AF_INET:
+ range->ncprange_family = AF_INET;
+ range->ncprange_ip4addr = from->ncpaddr_ip4addr;
+ if (from->ncpaddr_ip4addr.s_addr == INADDR_ANY) {
+ range->ncprange_ip4mask.s_addr = INADDR_ANY;
+ range->ncprange_ip4width = 0;
+ } else {
+ range->ncprange_ip4mask.s_addr = INADDR_BROADCAST;
+ range->ncprange_ip4width = 32;
+ }
+ break;
+
+#ifndef NOINET6
+ case AF_INET6:
+ range->ncprange_family = AF_INET6;
+ range->ncprange_ip6addr = from->ncpaddr_ip6addr;
+ range->ncprange_ip6width = 128;
+ break;
+#endif
+
+ default:
+ range->ncprange_family = AF_UNSPEC;
+ }
+}
+
+int
+ncprange_ishost(const struct ncprange *range)
+{
+ switch (range->ncprange_family) {
+ case AF_INET:
+ return range->ncprange_ip4width == 32;
+#ifndef NOINET6
+ case AF_INET6:
+ return range->ncprange_ip6width == 128;
+#endif
+ }
+
+ return (0);
+}
+
+int
+ncprange_setwidth(struct ncprange *range, int width)
+{
+ switch (range->ncprange_family) {
+ case AF_INET:
+ if (width < 0 || width > 32)
+ break;
+ range->ncprange_ip4width = width;
+ range->ncprange_ip4mask = bits2mask4(width);
+ break;
+
+#ifndef NOINET6
+ case AF_INET6:
+ if (width < 0 || width > 128)
+ break;
+ range->ncprange_ip6width = width;
+ break;
+#endif
+
+ case AF_UNSPEC:
+ return 1;
+ }
+
+ return 0;
+}
+
+void
+ncprange_setip4host(struct ncprange *range, struct in_addr from)
+{
+ range->ncprange_family = AF_INET;
+ range->ncprange_ip4addr = from;
+ if (from.s_addr == INADDR_ANY) {
+ range->ncprange_ip4mask.s_addr = INADDR_ANY;
+ range->ncprange_ip4width = 0;
+ } else {
+ range->ncprange_ip4mask.s_addr = INADDR_BROADCAST;
+ range->ncprange_ip4width = 32;
+ }
+}
+
+void
+ncprange_setip4(struct ncprange *range, struct in_addr from, struct in_addr msk)
+{
+ range->ncprange_family = AF_INET;
+ range->ncprange_ip4addr = from;
+ range->ncprange_ip4mask = msk;
+ range->ncprange_ip4width = mask42bits(msk);
+}
+
+
+int
+ncprange_setip4mask(struct ncprange *range, struct in_addr mask)
+{
+ if (range->ncprange_family != AF_INET)
+ return 0;
+ range->ncprange_ip4mask = mask;
+ range->ncprange_ip4width = mask42bits(mask);
+ return 1;
+}
+
+void
+ncprange_setsa(struct ncprange *range, const struct sockaddr *host,
+ const struct sockaddr *mask)
+{
+ const struct sockaddr_in *host4 = (const struct sockaddr_in *)host;
+ const struct sockaddr_in *mask4 = (const struct sockaddr_in *)mask;
+#ifndef NOINET6
+ const struct sockaddr_in6 *host6 = (const struct sockaddr_in6 *)host;
+ const struct sockaddr_in6 *mask6 = (const struct sockaddr_in6 *)mask;
+#endif
+
+ switch (host->sa_family) {
+ case AF_INET:
+ range->ncprange_family = AF_INET;
+ range->ncprange_ip4addr = host4->sin_addr;
+ if (host4->sin_addr.s_addr == INADDR_ANY) {
+ range->ncprange_ip4mask.s_addr = INADDR_ANY;
+ range->ncprange_ip4width = 0;
+ } else if (mask4 && mask4->sin_family == AF_INET) {
+ range->ncprange_ip4mask.s_addr = mask4->sin_addr.s_addr;
+ range->ncprange_ip4width = mask42bits(mask4->sin_addr);
+ } else {
+ range->ncprange_ip4mask.s_addr = INADDR_BROADCAST;
+ range->ncprange_ip4width = 32;
+ }
+ break;
+
+#ifndef NOINET6
+ case AF_INET6:
+ range->ncprange_family = AF_INET6;
+ range->ncprange_ip6addr = host6->sin6_addr;
+ if (IN6_IS_ADDR_UNSPECIFIED(&host6->sin6_addr))
+ range->ncprange_ip6width = 0;
+ else
+ range->ncprange_ip6width = mask6 ? mask62bits(&mask6->sin6_addr) : 128;
+ break;
+#endif
+
+ default:
+ range->ncprange_family = AF_UNSPEC;
+ }
+}
+
+void
+ncprange_getsa(const struct ncprange *range, struct sockaddr_storage *host,
+ struct sockaddr_storage *mask)
+{
+ struct sockaddr_in *host4 = (struct sockaddr_in *)host;
+ struct sockaddr_in *mask4 = (struct sockaddr_in *)mask;
+#ifndef NOINET6
+ struct sockaddr_in6 *host6 = (struct sockaddr_in6 *)host;
+ struct sockaddr_in6 *mask6 = (struct sockaddr_in6 *)mask;
+#endif
+
+ memset(host, '\0', sizeof(*host));
+ if (mask)
+ memset(mask, '\0', sizeof(*mask));
+
+ switch (range->ncprange_family) {
+ case AF_INET:
+ host4->sin_family = AF_INET;
+ host4->sin_len = sizeof(*host4);
+ host4->sin_addr = range->ncprange_ip4addr;
+ if (mask4) {
+ mask4->sin_family = AF_INET;
+ mask4->sin_len = sizeof(*host4);
+ mask4->sin_addr = range->ncprange_ip4mask;
+ }
+ break;
+
+#ifndef NOINET6
+ case AF_INET6:
+ host6->sin6_family = AF_INET6;
+ host6->sin6_len = sizeof(*host6);
+ host6->sin6_addr = range->ncprange_ip6addr;
+ if (mask6) {
+ mask6->sin6_family = AF_INET6;
+ mask6->sin6_len = sizeof(*host6);
+ mask6->sin6_addr = bits2mask6(range->ncprange_ip6width);
+ }
+ break;
+#endif
+
+ default:
+ host->ss_family = AF_UNSPEC;
+ if (mask)
+ mask->ss_family = AF_UNSPEC;
+ break;
+ }
+}
+
+int
+ncprange_getaddr(const struct ncprange *range, struct ncpaddr *addr)
+{
+ switch (range->ncprange_family) {
+ case AF_INET:
+ addr->ncpaddr_family = AF_INET;
+ addr->ncpaddr_ip4addr = range->ncprange_ip4addr;
+ return 1;
+#ifndef NOINET6
+ case AF_INET6:
+ addr->ncpaddr_family = AF_INET6;
+ addr->ncpaddr_ip6addr = range->ncprange_ip6addr;
+ return 1;
+#endif
+ }
+
+ return 0;
+}
+
+int
+ncprange_getip4addr(const struct ncprange *range, struct in_addr *addr)
+{
+ if (range->ncprange_family != AF_INET)
+ return 0;
+
+ *addr = range->ncprange_ip4addr;
+ return 1;
+}
+
+int
+ncprange_getip4mask(const struct ncprange *range, struct in_addr *mask)
+{
+ switch (range->ncprange_family) {
+ case AF_INET:
+ *mask = range->ncprange_ip4mask;
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+ncprange_getwidth(const struct ncprange *range, int *width)
+{
+ switch (range->ncprange_family) {
+ case AF_INET:
+ *width = range->ncprange_ip4width;
+ return 1;
+#ifndef NOINET6
+ case AF_INET6:
+ *width = range->ncprange_ip6width;
+ return 1;
+#endif
+ }
+
+ return 0;
+}
+
+const char *
+ncprange_ntoa(const struct ncprange *range)
+{
+ char *res;
+ struct ncpaddr addr;
+ int len;
+
+ if (!ncprange_getaddr(range, &addr))
+ return "<AF_UNSPEC>";
+
+ res = ncpaddr_ntowa(&addr);
+ len = strlen(res);
+ if (len >= NCP_ASCIIBUFFERSIZE - 1)
+ return res;
+
+ switch (range->ncprange_family) {
+ case AF_INET:
+ if (range->ncprange_ip4width == -1) {
+ /* A non-contiguous mask */
+ for (; len >= 3; res[len -= 2] = '\0')
+ if (strcmp(res + len - 2, ".0"))
+ break;
+ snprintf(res + len, sizeof res - len, "&0x%08lx",
+ (unsigned long)ntohl(range->ncprange_ip4mask.s_addr));
+ } else if (range->ncprange_ip4width < 32)
+ snprintf(res + len, sizeof res - len, "/%d", range->ncprange_ip4width);
+
+ return res;
+
+#ifndef NOINET6
+ case AF_INET6:
+ if (range->ncprange_ip6width != 128)
+ snprintf(res + len, sizeof res - len, "/%d", range->ncprange_ip6width);
+
+ return res;
+#endif
+ }
+
+ return "<AF_UNSPEC>";
+}
+
+#ifndef NOINET6
+int
+ncprange_scopeid(const struct ncprange *range)
+{
+ const struct in6_addr *sin6;
+ int scopeid = -1;
+
+ if (range->ncprange_family == AF_INET6) {
+ sin6 = &range->ncprange_ip6addr;
+ if (IN6_IS_ADDR_LINKLOCAL(sin6) || IN6_IS_ADDR_MC_LINKLOCAL(sin6))
+ if ((scopeid = ntohs(*(const u_short *)&sin6->s6_addr[2])) == 0)
+ scopeid = -1;
+ }
+
+ return scopeid;
+}
+#endif
+
+int
+ncprange_aton(struct ncprange *range, struct ncp *ncp, const char *data)
+{
+ int bits, len;
+ char *wp;
+ const char *cp;
+ char *s;
+
+ len = strcspn(data, "/");
+
+ if (ncp && strncasecmp(data, "HISADDR", len) == 0) {
+ range->ncprange_family = AF_INET;
+ range->ncprange_ip4addr = ncp->ipcp.peer_ip;
+ range->ncprange_ip4mask.s_addr = INADDR_BROADCAST;
+ range->ncprange_ip4width = 32;
+ return 1;
+#ifndef NOINET6
+ } else if (ncp && strncasecmp(data, "HISADDR6", len) == 0) {
+ range->ncprange_family = AF_INET6;
+ range->ncprange_ip6addr = ncp->ipv6cp.hisaddr.ncpaddr_ip6addr;
+ range->ncprange_ip6width = 128;
+ return 1;
+#endif
+ } else if (ncp && strncasecmp(data, "MYADDR", len) == 0) {
+ range->ncprange_family = AF_INET;
+ range->ncprange_ip4addr = ncp->ipcp.my_ip;
+ range->ncprange_ip4mask.s_addr = INADDR_BROADCAST;
+ range->ncprange_ip4width = 32;
+ return 1;
+#ifndef NOINET6
+ } else if (ncp && strncasecmp(data, "MYADDR6", len) == 0) {
+ range->ncprange_family = AF_INET6;
+ range->ncprange_ip6addr = ncp->ipv6cp.myaddr.ncpaddr_ip6addr;
+ range->ncprange_ip6width = 128;
+ return 1;
+#endif
+ } else if (ncp && strncasecmp(data, "DNS0", len) == 0) {
+ range->ncprange_family = AF_INET;
+ range->ncprange_ip4addr = ncp->ipcp.ns.dns[0];
+ range->ncprange_ip4mask.s_addr = INADDR_BROADCAST;
+ range->ncprange_ip4width = 32;
+ return 1;
+ } else if (ncp && strncasecmp(data, "DNS1", len) == 0) {
+ range->ncprange_family = AF_INET;
+ range->ncprange_ip4addr = ncp->ipcp.ns.dns[1];
+ range->ncprange_ip4mask.s_addr = INADDR_BROADCAST;
+ range->ncprange_ip4width = 32;
+ return 1;
+ }
+
+ s = (char *)alloca(len + 1);
+ strncpy(s, data, len);
+ s[len] = '\0';
+ bits = -1;
+
+ if (data[len] != '\0') {
+ bits = strtol(data + len + 1, &wp, 0);
+ if (*wp || wp == data + len + 1 || bits < 0 || bits > 128) {
+ log_Printf(LogWARN, "ncprange_aton: bad mask width.\n");
+ return 0;
+ }
+ }
+
+ if ((cp = strchr(data, ':')) == NULL) {
+ range->ncprange_family = AF_INET;
+
+ range->ncprange_ip4addr = GetIpAddr(s);
+
+ if (range->ncprange_ip4addr.s_addr == INADDR_NONE) {
+ log_Printf(LogWARN, "ncprange_aton: %s: Bad address\n", s);
+ return 0;
+ }
+
+ if (range->ncprange_ip4addr.s_addr == INADDR_ANY) {
+ range->ncprange_ip4mask.s_addr = INADDR_ANY;
+ range->ncprange_ip4width = 0;
+ } else if (bits == -1) {
+ range->ncprange_ip4mask.s_addr = INADDR_BROADCAST;
+ range->ncprange_ip4width = 32;
+ } else if (bits > 32) {
+ log_Printf(LogWARN, "ncprange_aton: bad mask width.\n");
+ return 0;
+ } else {
+ range->ncprange_ip4mask = bits2mask4(bits);
+ range->ncprange_ip4width = bits;
+ }
+
+ return 1;
+#ifndef NOINET6
+ } else if (strchr(cp + 1, ':') != NULL) {
+ range->ncprange_family = AF_INET6;
+
+ if (inet_pton(AF_INET6, s, &range->ncprange_ip6addr) != 1) {
+ log_Printf(LogWARN, "ncprange_aton: %s: Bad address\n", s);
+ return 0;
+ }
+
+ if (IN6_IS_ADDR_UNSPECIFIED(&range->ncprange_ip6addr))
+ range->ncprange_ip6width = 0;
+ else
+ range->ncprange_ip6width = (bits == -1) ? 128 : bits;
+ return 1;
+#endif
+ }
+
+ return 0;
+}
diff --git a/usr.sbin/ppp/ncpaddr.h b/usr.sbin/ppp/ncpaddr.h
new file mode 100644
index 0000000..8c6b886
--- /dev/null
+++ b/usr.sbin/ppp/ncpaddr.h
@@ -0,0 +1,109 @@
+/*-
+ * Copyright (c) 2001 Brian Somers <brian@Awfulhak.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$
+ */
+
+/*
+ * These structures should be treated as opaque.
+ */
+struct ncprange {
+ sa_family_t ncprange_family;
+ union {
+ struct {
+ struct in_addr ipaddr;
+ struct in_addr mask;
+ int width;
+ } ip4;
+#ifndef NOINET6
+ struct {
+ struct in6_addr ipaddr;
+ int width;
+ } ip6;
+#endif
+ } u;
+};
+
+struct ncpaddr {
+ sa_family_t ncpaddr_family;
+ union {
+ struct in_addr ip4addr;
+#ifndef NOINET6
+ struct in6_addr ip6addr;
+#endif
+ } u;
+};
+
+struct ncp;
+
+extern void ncpaddr_init(struct ncpaddr *);
+extern int ncpaddr_isset(const struct ncpaddr *);
+extern int ncpaddr_isdefault(const struct ncpaddr *);
+extern int ncpaddr_equal(const struct ncpaddr *, const struct ncpaddr *);
+extern void ncpaddr_copy(struct ncpaddr *, const struct ncpaddr *);
+extern void ncpaddr_setip4addr(struct ncpaddr *, u_int32_t);
+extern int ncpaddr_getip4addr(const struct ncpaddr *, u_int32_t *);
+extern void ncpaddr_setip4(struct ncpaddr *, struct in_addr);
+extern int ncpaddr_getip4(const struct ncpaddr *, struct in_addr *);
+#ifndef NOINET6
+extern void ncpaddr_setip6(struct ncpaddr *, const struct in6_addr *);
+extern int ncpaddr_getip6(const struct ncpaddr *, struct in6_addr *);
+#endif
+extern void ncpaddr_getsa(const struct ncpaddr *, struct sockaddr_storage *);
+extern void ncpaddr_setsa(struct ncpaddr *, const struct sockaddr *);
+extern const char *ncpaddr_ntoa(const struct ncpaddr *);
+extern int ncpaddr_aton(struct ncpaddr *, struct ncp *, const char *);
+
+extern void ncprange_init(struct ncprange *);
+extern int ncprange_isset(const struct ncprange *);
+extern int ncprange_equal(const struct ncprange *, const struct ncprange *);
+extern int ncprange_isdefault(const struct ncprange *);
+extern void ncprange_setdefault(struct ncprange *, int);
+extern int ncprange_contains(const struct ncprange *, const struct ncpaddr *);
+extern int ncprange_containsip4(const struct ncprange *, struct in_addr);
+extern void ncprange_copy(struct ncprange *, const struct ncprange *);
+extern void ncprange_set(struct ncprange *, const struct ncpaddr *, int);
+extern void ncprange_sethost(struct ncprange *, const struct ncpaddr *);
+extern int ncprange_ishost(const struct ncprange *);
+extern int ncprange_setwidth(struct ncprange *, int);
+extern void ncprange_setip4(struct ncprange *, struct in_addr, struct in_addr);
+extern void ncprange_setip4host(struct ncprange *, struct in_addr);
+extern int ncprange_setip4mask(struct ncprange *, struct in_addr);
+extern void ncprange_setsa(struct ncprange *, const struct sockaddr *,
+ const struct sockaddr *);
+extern void ncprange_getsa(const struct ncprange *, struct sockaddr_storage *,
+ struct sockaddr_storage *);
+extern int ncprange_getaddr(const struct ncprange *, struct ncpaddr *);
+extern int ncprange_getip4addr(const struct ncprange *, struct in_addr *);
+extern int ncprange_getip4mask(const struct ncprange *, struct in_addr *);
+extern int ncprange_getwidth(const struct ncprange *, int *);
+extern const char *ncprange_ntoa(const struct ncprange *);
+#ifndef NOINET6
+extern int ncprange_scopeid(const struct ncprange *);
+#endif
+extern int ncprange_aton(struct ncprange *, struct ncp *, const char *);
+
+#define ncpaddr_family(a) ((a)->ncpaddr_family)
+#define ncprange_family(r) ((r)->ncprange_family)
diff --git a/usr.sbin/ppp/netgraph.c b/usr.sbin/ppp/netgraph.c
new file mode 100644
index 0000000..23a575b
--- /dev/null
+++ b/usr.sbin/ppp/netgraph.c
@@ -0,0 +1,743 @@
+/*-
+ * Copyright (c) 2000 Brian Somers <brian@Awfulhak.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/param.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netgraph.h>
+#include <net/ethernet.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netgraph/ng_ether.h>
+#include <netgraph/ng_message.h>
+#include <netgraph/ng_socket.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <sys/fcntl.h>
+#include <sys/uio.h>
+#include <termios.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "fsm.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "async.h"
+#include "descriptor.h"
+#include "physical.h"
+#include "main.h"
+#include "mp.h"
+#include "chat.h"
+#include "auth.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+#include "slcompress.h"
+#include "iplist.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "filter.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "bundle.h"
+#include "id.h"
+#include "netgraph.h"
+
+
+struct ngdevice {
+ struct device dev; /* What struct physical knows about */
+ int cs; /* Control socket */
+ char hook[NG_HOOKSIZ]; /* Our socket node hook */
+};
+
+#define device2ng(d) ((d)->type == NG_DEVICE ? (struct ngdevice *)d : NULL)
+#define NG_MSGBUFSZ 4096
+#define NETGRAPH_PREFIX "netgraph:"
+
+unsigned
+ng_DeviceSize(void)
+{
+ return sizeof(struct ngdevice);
+}
+
+static int
+ng_MessageOut(struct ngdevice *dev, const char *data)
+{
+ char path[NG_PATHSIZ];
+ char *fmt;
+ size_t len;
+ int pos, dpos;
+
+ /*
+ * We expect a node path, one or more spaces, a command, one or more
+ * spaces and an ascii netgraph structure.
+ */
+ data += strspn(data, " \t");
+ len = strcspn(data, " \t");
+ if (len >= sizeof path) {
+ log_Printf(LogWARN, "%s: %.*s: Node path too long\n",
+ dev->dev.name, len, data);
+ return 0;
+ }
+ memcpy(path, data, len);
+ path[len] = '\0';
+ data += len;
+
+ data += strspn(data, " \t");
+ len = strcspn(data, " \t");
+ for (pos = len; pos >= 0; pos--)
+ if (data[pos] == '%')
+ len++;
+ if ((fmt = alloca(len + 4)) == NULL) {
+ log_Printf(LogWARN, "%s: alloca(%d) failure... %s\n",
+ dev->dev.name, len + 4, strerror(errno));
+ return 0;
+ }
+
+ /*
+ * This is probably a waste of time, but we really don't want to end
+ * up stuffing unexpected % escapes into the kernel....
+ */
+ for (pos = dpos = 0; pos < (int)len;) {
+ if (data[dpos] == '%')
+ fmt[pos++] = '%';
+ fmt[pos++] = data[dpos++];
+ }
+ strcpy(fmt + pos, " %s");
+ data += dpos;
+
+ data += strspn(data, " \t");
+ if (NgSendAsciiMsg(dev->cs, path, fmt, data) < 0) {
+ log_Printf(LogDEBUG, "%s: NgSendAsciiMsg (to %s): \"%s\", \"%s\": %s\n",
+ dev->dev.name, path, fmt, data, strerror(errno));
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * Get a netgraph message
+ */
+static ssize_t
+ng_MessageIn(struct physical *p, char *buf, size_t sz)
+{
+ char msgbuf[sizeof(struct ng_mesg) * 2 + NG_MSGBUFSZ];
+ struct ngdevice *dev = device2ng(p->handler);
+ struct ng_mesg *rep = (struct ng_mesg *)msgbuf;
+ char path[NG_PATHSIZ];
+ size_t len;
+
+#ifdef BROKEN_SELECT
+ struct timeval t;
+ fd_set *r;
+ int ret;
+
+ if (dev->cs < 0)
+ return 0;
+
+ if ((r = mkfdset()) == NULL) {
+ log_Printf(LogERROR, "DoLoop: Cannot create fd_set\n");
+ return -1;
+ }
+ zerofdset(r);
+ FD_SET(dev->cs, r);
+ t.tv_sec = t.tv_usec = 0;
+ ret = select(dev->cs + 1, r, NULL, NULL, &t);
+ free(r);
+
+ if (ret <= 0)
+ return 0;
+#endif
+
+ if (NgRecvAsciiMsg(dev->cs, rep, sizeof msgbuf, path)) {
+ log_Printf(LogWARN, "%s: NgRecvAsciiMsg: %s\n",
+ dev->dev.name, strerror(errno));
+ return -1;
+ }
+
+ /* XXX: Should we check rep->header.version ? */
+
+ if (sz == 0)
+ log_Printf(LogWARN, "%s: Unexpected message: %s\n", dev->dev.name,
+ rep->header.cmdstr);
+ else {
+ log_Printf(LogDEBUG, "%s: Received message: %s\n", dev->dev.name,
+ rep->header.cmdstr);
+ len = strlen(rep->header.cmdstr);
+ if (sz > len)
+ sz = len;
+ memcpy(buf, rep->header.cmdstr, sz);
+ }
+
+ return sz;
+}
+
+static ssize_t
+ng_Write(struct physical *p, const void *v, size_t n)
+{
+ struct ngdevice *dev = device2ng(p->handler);
+
+ switch (p->dl->state) {
+ case DATALINK_DIAL:
+ case DATALINK_LOGIN:
+ return ng_MessageOut(dev, v) ? (ssize_t)n : -1;
+ }
+ return NgSendData(p->fd, dev->hook, v, n) == -1 ? -1 : (ssize_t)n;
+}
+
+static ssize_t
+ng_Read(struct physical *p, void *v, size_t n)
+{
+ char hook[NG_HOOKSIZ];
+
+ switch (p->dl->state) {
+ case DATALINK_DIAL:
+ case DATALINK_LOGIN:
+ return ng_MessageIn(p, v, n);
+ }
+
+ return NgRecvData(p->fd, v, n, hook);
+}
+
+static int
+ng_RemoveFromSet(struct physical *p, fd_set *r, fd_set *w, fd_set *e)
+{
+ struct ngdevice *dev = device2ng(p->handler);
+ int result;
+
+ if (r && dev->cs >= 0 && FD_ISSET(dev->cs, r)) {
+ FD_CLR(dev->cs, r);
+ log_Printf(LogTIMER, "%s: fdunset(ctrl) %d\n", p->link.name, dev->cs);
+ result = 1;
+ } else
+ result = 0;
+
+ /* Careful... physical_RemoveFromSet() called us ! */
+
+ p->handler->removefromset = NULL;
+ result += physical_RemoveFromSet(p, r, w, e);
+ p->handler->removefromset = ng_RemoveFromSet;
+
+ return result;
+}
+
+static void
+ng_Free(struct physical *p)
+{
+ struct ngdevice *dev = device2ng(p->handler);
+
+ physical_SetDescriptor(p);
+ if (dev->cs != -1)
+ close(dev->cs);
+ free(dev);
+}
+
+static void
+ng_device2iov(struct device *d, struct iovec *iov, int *niov,
+ int maxiov __unused, int *auxfd, int *nauxfd)
+{
+ struct ngdevice *dev;
+ int sz = physical_MaxDeviceSize();
+
+ iov[*niov].iov_base = d = realloc(d, sz);
+ if (d == NULL) {
+ log_Printf(LogALERT, "Failed to allocate memory: %d\n", sz);
+ AbortProgram(EX_OSERR);
+ }
+ iov[*niov].iov_len = sz;
+ (*niov)++;
+
+ dev = device2ng(d);
+ *auxfd = dev->cs;
+ (*nauxfd)++;
+}
+
+static const struct device basengdevice = {
+ NG_DEVICE,
+ "netgraph",
+ 0,
+ { CD_REQUIRED, DEF_NGCDDELAY },
+ NULL,
+ ng_RemoveFromSet,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ ng_Free,
+ ng_Read,
+ ng_Write,
+ ng_device2iov,
+ NULL,
+ NULL,
+ NULL
+};
+
+struct device *
+ng_iov2device(int type, struct physical *p, struct iovec *iov, int *niov,
+ int maxiov __unused, int *auxfd, int *nauxfd)
+{
+ if (type == NG_DEVICE) {
+ struct ngdevice *dev = (struct ngdevice *)iov[(*niov)++].iov_base;
+
+ dev = realloc(dev, sizeof *dev); /* Reduce to the correct size */
+ if (dev == NULL) {
+ log_Printf(LogALERT, "Failed to allocate memory: %d\n",
+ (int)(sizeof *dev));
+ AbortProgram(EX_OSERR);
+ }
+
+ if (*nauxfd) {
+ dev->cs = *auxfd;
+ (*nauxfd)--;
+ } else
+ dev->cs = -1;
+
+ /* Refresh function pointers etc */
+ memcpy(&dev->dev, &basengdevice, sizeof dev->dev);
+
+ /* XXX: Are netgraph always synchronous ? */
+ physical_SetupStack(p, dev->dev.name, PHYSICAL_FORCE_SYNCNOACF);
+ return &dev->dev;
+ }
+
+ return NULL;
+}
+
+static int
+ng_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e, int *n)
+{
+ struct physical *p = descriptor2physical(d);
+ struct ngdevice *dev = device2ng(p->handler);
+ int result;
+
+ switch (p->dl->state) {
+ case DATALINK_DIAL:
+ case DATALINK_LOGIN:
+ if (r) {
+ FD_SET(dev->cs, r);
+ log_Printf(LogTIMER, "%s(ctrl): fdset(r) %d\n", p->link.name, dev->cs);
+ result = 1;
+ } else
+ result = 0;
+ break;
+
+ default:
+ result = physical_doUpdateSet(d, r, w, e, n, 0);
+ break;
+ }
+
+ return result;
+}
+
+static int
+ng_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+ struct physical *p = descriptor2physical(d);
+ struct ngdevice *dev = device2ng(p->handler);
+ int result;
+
+ result = dev->cs >= 0 && FD_ISSET(dev->cs, fdset);
+ result += physical_IsSet(d, fdset);
+
+ return result;
+}
+
+static void
+ng_DescriptorRead(struct fdescriptor *d, struct bundle *bundle,
+ const fd_set *fdset)
+{
+ struct physical *p = descriptor2physical(d);
+ struct ngdevice *dev = device2ng(p->handler);
+
+ if (dev->cs >= 0 && FD_ISSET(dev->cs, fdset))
+ ng_MessageIn(p, NULL, 0);
+
+ if (physical_IsSet(d, fdset))
+ physical_DescriptorRead(d, bundle, fdset);
+}
+
+static struct device *
+ng_Abandon(struct ngdevice *dev, struct physical *p)
+{
+ /* Abandon our node construction */
+ close(dev->cs);
+ close(p->fd);
+ p->fd = -2; /* Nobody else need try.. */
+ free(dev);
+
+ return NULL;
+}
+
+
+/*
+ * Populate the ``word'' (of size ``sz'') named ``what'' from ``from''
+ * ending with any character from ``sep''. Point ``endp'' at the next
+ * word.
+ */
+
+#define GETSEGMENT(what, from, sep, endp) \
+ getsegment(#what, (what), sizeof(what), from, sep, endp)
+
+static int
+getsegment(const char *what, char *word, size_t sz, const char *from,
+ const char *sep, const char **endp)
+{
+ size_t len;
+
+ if ((len = strcspn(from, sep)) == 0) {
+ log_Printf(LogWARN, "%s name should not be empty !\n", what);
+ return 0;
+ }
+
+ if (len >= sz) {
+ log_Printf(LogWARN, "%s name too long, max %d !\n", what, sz - 1);
+ return 0;
+ }
+
+ strncpy(word, from, len);
+ word[len] = '\0';
+
+ *endp = from + len;
+ *endp += strspn(*endp, sep);
+
+ return 1;
+}
+
+struct device *
+ng_Create(struct physical *p)
+{
+ struct sockaddr_ng ngsock;
+ u_char rbuf[2048];
+ struct sockaddr *sock = (struct sockaddr *)&ngsock;
+ const struct hooklist *hlist;
+ const struct nodeinfo *ninfo;
+ const struct linkinfo *nlink;
+ struct ngdevice *dev;
+ struct ng_mesg *resp;
+ struct ngm_mkpeer mkp;
+ struct ngm_connect ngc;
+ const char *devp, *endp;
+ char lasthook[NG_HOOKSIZ];
+ char hook[NG_HOOKSIZ];
+ char nodetype[NG_TYPESIZ + NG_NODESIZ];
+ char modname[NG_TYPESIZ + 3];
+ char path[NG_PATHSIZ];
+ char *nodename;
+ int len, sz, done;
+ unsigned f;
+
+ dev = NULL;
+ if (p->fd < 0 && !strncasecmp(p->name.full, NETGRAPH_PREFIX,
+ sizeof NETGRAPH_PREFIX - 1)) {
+ p->fd--; /* We own the device - change fd */
+
+ if ((dev = malloc(sizeof *dev)) == NULL)
+ return NULL;
+
+ loadmodules(LOAD_VERBOSLY, "netgraph", "ng_socket", NULL);
+
+ /* Create a socket node */
+ if (ID0NgMkSockNode(NULL, &dev->cs, &p->fd) == -1) {
+ log_Printf(LogWARN, "Cannot create netgraph socket node: %s\n",
+ strerror(errno));
+ free(dev);
+ p->fd = -2;
+ return NULL;
+ }
+
+ devp = p->name.full + sizeof NETGRAPH_PREFIX - 1;
+ *lasthook = *path = '\0';
+ log_Printf(LogDEBUG, "%s: Opening netgraph device \"%s\"\n",
+ p->link.name, devp);
+ done = 0;
+
+ while (*devp != '\0' && !done) {
+ if (*devp != '[') {
+ if (*lasthook == '\0') {
+ log_Printf(LogWARN, "%s: Netgraph devices must start with"
+ " [nodetype:nodename]\n", p->link.name);
+ return ng_Abandon(dev, p);
+ }
+
+ /* Get the hook name of the new node */
+ if (!GETSEGMENT(hook, devp, ".[", &endp))
+ return ng_Abandon(dev, p);
+ log_Printf(LogDEBUG, "%s: Got hook \"%s\"\n", p->link.name, hook);
+ devp = endp;
+ if (*devp == '\0') {
+ log_Printf(LogWARN, "%s: Netgraph device must not end with a second"
+ " hook\n", p->link.name);
+ return ng_Abandon(dev, p);
+ }
+ if (devp[-1] != '[') {
+ log_Printf(LogWARN, "%s: Expected a [nodetype:nodename] at device"
+ " pos %d\n", p->link.name, devp - p->link.name - 1);
+ return ng_Abandon(dev, p);
+ }
+ } else {
+ /* Use lasthook as the hook name */
+ strcpy(hook, lasthook);
+ devp++;
+ }
+
+ /* We've got ``lasthook'' and ``hook'', get the node type */
+ if (!GETSEGMENT(nodetype, devp, "]", &endp))
+ return ng_Abandon(dev, p);
+ log_Printf(LogDEBUG, "%s: Got node \"%s\"\n", p->link.name, nodetype);
+
+ if ((nodename = strchr(nodetype, ':')) != NULL) {
+ *nodename++ = '\0';
+ if (*nodename == '\0' && *nodetype == '\0') {
+ log_Printf(LogWARN, "%s: Empty [nodetype:nodename] at device"
+ " pos %d\n", p->link.name, devp - p->link.name - 1);
+ return ng_Abandon(dev, p);
+ }
+ }
+
+ /* Ignore optional colons after nodes */
+ devp = *endp == ':' ? endp + 1 : endp;
+ if (*devp == '.')
+ devp++;
+
+ if (*lasthook == '\0') {
+ /* This is the first node in the chain */
+ if (nodename == NULL || *nodename == '\0') {
+ log_Printf(LogWARN, "%s: %s: No initial device nodename\n",
+ p->link.name, devp);
+ return ng_Abandon(dev, p);
+ }
+
+ if (*nodetype != '\0') {
+ /* Attempt to load the module */
+ snprintf(modname, sizeof modname, "ng_%s", nodetype);
+ log_Printf(LogDEBUG, "%s: Attempting to load %s.ko\n",
+ p->link.name, modname);
+ loadmodules(LOAD_QUIETLY, modname, NULL);
+ }
+
+ snprintf(path, sizeof path, "%s:", nodename);
+ /* XXX: If we have a node type, ensure it's correct */
+ } else {
+ /*
+ * Ask for a list of hooks attached to the previous node. If we
+ * find the one we're interested in, and if it's connected to a
+ * node of the right type using the correct hook, use that.
+ * If we find the hook connected to something else, fail.
+ * If we find no match, mkpeer the new node.
+ */
+ if (*nodetype == '\0') {
+ log_Printf(LogWARN, "%s: Nodetype missing at device offset %d\n",
+ p->link.name,
+ devp - p->name.full + sizeof NETGRAPH_PREFIX - 1);
+ return ng_Abandon(dev, p);
+ }
+
+ /* Get a list of node hooks */
+ if (NgSendMsg(dev->cs, path, NGM_GENERIC_COOKIE, NGM_LISTHOOKS,
+ NULL, 0) < 0) {
+ log_Printf(LogWARN, "%s: %s Cannot send a LISTHOOOKS message: %s\n",
+ p->link.name, path, strerror(errno));
+ return ng_Abandon(dev, p);
+ }
+
+ /* Get our list back */
+ resp = (struct ng_mesg *)rbuf;
+ if (NgRecvMsg(dev->cs, resp, sizeof rbuf, NULL) <= 0) {
+ log_Printf(LogWARN, "%s: Cannot get netgraph response: %s\n",
+ p->link.name, strerror(errno));
+ return ng_Abandon(dev, p);
+ }
+
+ hlist = (const struct hooklist *)resp->data;
+ ninfo = &hlist->nodeinfo;
+
+ log_Printf(LogDEBUG, "List of netgraph node ``%s'' (id %x) hooks:\n",
+ path, ninfo->id);
+
+ /* look for a hook already attached. */
+ for (f = 0; f < ninfo->hooks; f++) {
+ nlink = &hlist->link[f];
+
+ log_Printf(LogDEBUG, " Found %s -> %s (type %s)\n", nlink->ourhook,
+ nlink->peerhook, nlink->nodeinfo.type);
+
+ if (!strcmp(nlink->ourhook, lasthook)) {
+ if (strcmp(nlink->peerhook, hook) ||
+ strcmp(nlink->nodeinfo.type, nodetype)) {
+ log_Printf(LogWARN, "%s: hook %s:%s is already in use\n",
+ p->link.name, nlink->ourhook, path);
+ return ng_Abandon(dev, p);
+ }
+ /* The node is already hooked up nicely.... reuse it */
+ break;
+ }
+ }
+
+ if (f == ninfo->hooks) {
+ /* Attempt to load the module */
+ snprintf(modname, sizeof modname, "ng_%s", nodetype);
+ log_Printf(LogDEBUG, "%s: Attempting to load %s.ko\n",
+ p->link.name, modname);
+ loadmodules(LOAD_QUIETLY, modname, NULL);
+
+ /* Create (mkpeer) the new node */
+
+ snprintf(mkp.type, sizeof mkp.type, "%s", nodetype);
+ snprintf(mkp.ourhook, sizeof mkp.ourhook, "%s", lasthook);
+ snprintf(mkp.peerhook, sizeof mkp.peerhook, "%s", hook);
+
+ log_Printf(LogDEBUG, "%s: Doing MKPEER %s%s -> %s (type %s)\n",
+ p->link.name, path, mkp.ourhook, mkp.peerhook, nodetype);
+
+ if (NgSendMsg(dev->cs, path, NGM_GENERIC_COOKIE,
+ NGM_MKPEER, &mkp, sizeof mkp) < 0) {
+ log_Printf(LogWARN, "%s Cannot create %s netgraph node: %s\n",
+ path, nodetype, strerror(errno));
+ return ng_Abandon(dev, p);
+ }
+ }
+ len = strlen(path);
+ snprintf(path + len, sizeof path - len, "%s%s",
+ path[len - 1] == ':' ? "" : ".", lasthook);
+ }
+
+ /* Get a list of node hooks */
+ if (NgSendMsg(dev->cs, path, NGM_GENERIC_COOKIE, NGM_LISTHOOKS,
+ NULL, 0) < 0) {
+ log_Printf(LogWARN, "%s: %s Cannot send a LISTHOOOKS message: %s\n",
+ p->link.name, path, strerror(errno));
+ return ng_Abandon(dev, p);
+ }
+
+ /* Get our list back */
+ resp = (struct ng_mesg *)rbuf;
+ if (NgRecvMsg(dev->cs, resp, sizeof rbuf, NULL) <= 0) {
+ log_Printf(LogWARN, "%s: Cannot get netgraph response: %s\n",
+ p->link.name, strerror(errno));
+ return ng_Abandon(dev, p);
+ }
+
+ hlist = (const struct hooklist *)resp->data;
+ ninfo = &hlist->nodeinfo;
+
+ if (*lasthook != '\0' && nodename != NULL && *nodename != '\0' &&
+ strcmp(ninfo->name, nodename) &&
+ NgNameNode(dev->cs, path, "%s", nodename) < 0) {
+ log_Printf(LogWARN, "%s: %s: Cannot name netgraph node: %s\n",
+ p->link.name, path, strerror(errno));
+ return ng_Abandon(dev, p);
+ }
+
+ if (!GETSEGMENT(lasthook, devp, " \t.[", &endp))
+ return ng_Abandon(dev, p);
+ log_Printf(LogDEBUG, "%s: Got hook \"%s\"\n", p->link.name, lasthook);
+
+ len = strlen(lasthook);
+ done = strchr(" \t", devp[len]) ? 1 : 0;
+ devp = endp;
+
+ if (*devp != '\0') {
+ if (devp[-1] == '[')
+ devp--;
+ } /* else should moan about devp[-1] being '[' ? */
+ }
+
+ snprintf(dev->hook, sizeof dev->hook, "%s", lasthook);
+
+ /* Connect the node to our socket node */
+ snprintf(ngc.path, sizeof ngc.path, "%s", path);
+ snprintf(ngc.ourhook, sizeof ngc.ourhook, "%s", dev->hook);
+ memcpy(ngc.peerhook, ngc.ourhook, sizeof ngc.peerhook);
+
+ log_Printf(LogDEBUG, "Connecting netgraph socket .:%s -> %s.%s\n",
+ ngc.ourhook, ngc.path, ngc.peerhook);
+ if (NgSendMsg(dev->cs, ".:", NGM_GENERIC_COOKIE,
+ NGM_CONNECT, &ngc, sizeof ngc) < 0) {
+ log_Printf(LogWARN, "Cannot connect %s and socket netgraph "
+ "nodes: %s\n", path, strerror(errno));
+ return ng_Abandon(dev, p);
+ }
+
+ /* Hook things up so that we monitor dev->cs */
+ p->desc.UpdateSet = ng_UpdateSet;
+ p->desc.IsSet = ng_IsSet;
+ p->desc.Read = ng_DescriptorRead;
+
+ memcpy(&dev->dev, &basengdevice, sizeof dev->dev);
+
+ } else {
+ /* See if we're a netgraph socket */
+
+ sz = sizeof ngsock;
+ if (getsockname(p->fd, sock, &sz) != -1 && sock->sa_family == AF_NETGRAPH) {
+ /*
+ * It's a netgraph node... We can't determine hook names etc, so we
+ * stay pretty impartial....
+ */
+ log_Printf(LogPHASE, "%s: Link is a netgraph node\n", p->link.name);
+
+ if ((dev = malloc(sizeof *dev)) == NULL) {
+ log_Printf(LogWARN, "%s: Cannot allocate an ether device: %s\n",
+ p->link.name, strerror(errno));
+ return NULL;
+ }
+
+ memcpy(&dev->dev, &basengdevice, sizeof dev->dev);
+ dev->cs = -1;
+ *dev->hook = '\0';
+ }
+ }
+
+ if (dev) {
+ physical_SetupStack(p, dev->dev.name, PHYSICAL_FORCE_SYNCNOACF);
+ return &dev->dev;
+ }
+
+ return NULL;
+}
diff --git a/usr.sbin/ppp/netgraph.h b/usr.sbin/ppp/netgraph.h
new file mode 100644
index 0000000..a5b4082
--- /dev/null
+++ b/usr.sbin/ppp/netgraph.h
@@ -0,0 +1,37 @@
+/*-
+ * Copyright (c) 2000 Brian Somers <brian@Awfulhak.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$
+ */
+
+struct physical;
+struct device;
+
+#define DEF_NGCDDELAY 5 /* Default ``set cd'' value */
+
+extern struct device *ng_Create(struct physical *);
+extern struct device *ng_iov2device(int, struct physical *, struct iovec *,
+ int *, int, int *, int *);
+extern unsigned ng_DeviceSize(void);
diff --git a/usr.sbin/ppp/pap.c b/usr.sbin/ppp/pap.c
new file mode 100644
index 0000000..8eda020
--- /dev/null
+++ b/usr.sbin/ppp/pap.c
@@ -0,0 +1,303 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <stdlib.h>
+#include <string.h> /* strlen/memcpy */
+#include <termios.h>
+
+#include "layer.h"
+#include "mbuf.h"
+#include "log.h"
+#include "defs.h"
+#include "timer.h"
+#include "fsm.h"
+#include "auth.h"
+#include "pap.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "proto.h"
+#include "async.h"
+#include "throughput.h"
+#include "ccp.h"
+#include "link.h"
+#include "descriptor.h"
+#include "physical.h"
+#include "iplist.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "chat.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+
+static const char * const papcodes[] = {
+ "???", "REQUEST", "SUCCESS", "FAILURE"
+};
+#define MAXPAPCODE (sizeof papcodes / sizeof papcodes[0] - 1)
+
+static void
+pap_Req(struct authinfo *authp)
+{
+ struct bundle *bundle = authp->physical->dl->bundle;
+ struct fsmheader lh;
+ struct mbuf *bp;
+ u_char *cp;
+ int namelen, keylen, plen;
+
+ namelen = strlen(bundle->cfg.auth.name);
+ keylen = strlen(bundle->cfg.auth.key);
+ plen = namelen + keylen + 2;
+ log_Printf(LogDEBUG, "pap_Req: namelen = %d, keylen = %d\n", namelen, keylen);
+ log_Printf(LogPHASE, "Pap Output: %s ********\n", bundle->cfg.auth.name);
+ if (*bundle->cfg.auth.name == '\0')
+ log_Printf(LogWARN, "Sending empty PAP authname!\n");
+ lh.code = PAP_REQUEST;
+ lh.id = authp->id;
+ lh.length = htons(plen + sizeof(struct fsmheader));
+ bp = m_get(plen + sizeof(struct fsmheader), MB_PAPOUT);
+ memcpy(MBUF_CTOP(bp), &lh, sizeof(struct fsmheader));
+ cp = MBUF_CTOP(bp) + sizeof(struct fsmheader);
+ *cp++ = namelen;
+ memcpy(cp, bundle->cfg.auth.name, namelen);
+ cp += namelen;
+ *cp++ = keylen;
+ memcpy(cp, bundle->cfg.auth.key, keylen);
+ link_PushPacket(&authp->physical->link, bp, bundle,
+ LINK_QUEUES(&authp->physical->link) - 1, PROTO_PAP);
+}
+
+static void
+SendPapCode(struct authinfo *authp, int code, const char *message)
+{
+ struct fsmheader lh;
+ struct mbuf *bp;
+ u_char *cp;
+ int plen, mlen;
+
+ lh.code = code;
+ lh.id = authp->id;
+ mlen = strlen(message);
+ plen = mlen + 1;
+ lh.length = htons(plen + sizeof(struct fsmheader));
+ bp = m_get(plen + sizeof(struct fsmheader), MB_PAPOUT);
+ memcpy(MBUF_CTOP(bp), &lh, sizeof(struct fsmheader));
+ cp = MBUF_CTOP(bp) + sizeof(struct fsmheader);
+ /*
+ * If our message is longer than 255 bytes, truncate the length to
+ * 255 and send the entire message anyway. Maybe the other end will
+ * display it... (see pap_Input() !)
+ */
+ *cp++ = mlen > 255 ? 255 : mlen;
+ memcpy(cp, message, mlen);
+ log_Printf(LogPHASE, "Pap Output: %s\n", papcodes[code]);
+
+ link_PushPacket(&authp->physical->link, bp, authp->physical->dl->bundle,
+ LINK_QUEUES(&authp->physical->link) - 1, PROTO_PAP);
+}
+
+static void
+pap_Success(struct authinfo *authp)
+{
+ struct bundle *bundle = authp->physical->dl->bundle;
+
+ datalink_GotAuthname(authp->physical->dl, authp->in.name);
+#ifndef NORADIUS
+ if (*bundle->radius.cfg.file && bundle->radius.repstr)
+ SendPapCode(authp, PAP_ACK, bundle->radius.repstr);
+ else
+#endif
+ SendPapCode(authp, PAP_ACK, "Greetings!!");
+ authp->physical->link.lcp.auth_ineed = 0;
+ if (Enabled(bundle, OPT_UTMP))
+ physical_Login(authp->physical, authp->in.name);
+
+ if (authp->physical->link.lcp.auth_iwait == 0)
+ /*
+ * Either I didn't need to authenticate, or I've already been
+ * told that I got the answer right.
+ */
+ datalink_AuthOk(authp->physical->dl);
+}
+
+static void
+pap_Failure(struct authinfo *authp)
+{
+ SendPapCode(authp, PAP_NAK, "Login incorrect");
+ datalink_AuthNotOk(authp->physical->dl);
+}
+
+void
+pap_Init(struct authinfo *pap, struct physical *p)
+{
+ auth_Init(pap, p, pap_Req, pap_Success, pap_Failure);
+}
+
+struct mbuf *
+pap_Input(struct bundle *bundle, struct link *l, struct mbuf *bp)
+{
+ struct physical *p = link2physical(l);
+ struct authinfo *authp = &p->dl->pap;
+ u_char nlen, klen, *key;
+ const char *txt;
+ int txtlen;
+
+ if (p == NULL) {
+ log_Printf(LogERROR, "pap_Input: Not a physical link - dropped\n");
+ m_freem(bp);
+ return NULL;
+ }
+
+ if (bundle_Phase(bundle) != PHASE_NETWORK &&
+ bundle_Phase(bundle) != PHASE_AUTHENTICATE) {
+ log_Printf(LogPHASE, "Unexpected pap input - dropped !\n");
+ m_freem(bp);
+ return NULL;
+ }
+
+ if ((bp = auth_ReadHeader(authp, bp)) == NULL &&
+ ntohs(authp->in.hdr.length) == 0) {
+ log_Printf(LogWARN, "Pap Input: Truncated header !\n");
+ return NULL;
+ }
+
+ if (authp->in.hdr.code == 0 || authp->in.hdr.code > MAXPAPCODE) {
+ log_Printf(LogPHASE, "Pap Input: %d: Bad PAP code !\n", authp->in.hdr.code);
+ m_freem(bp);
+ return NULL;
+ }
+
+ if (authp->in.hdr.code != PAP_REQUEST && authp->id != authp->in.hdr.id &&
+ Enabled(bundle, OPT_IDCHECK)) {
+ /* Wrong conversation dude ! */
+ log_Printf(LogPHASE, "Pap Input: %s dropped (got id %d, not %d)\n",
+ papcodes[authp->in.hdr.code], authp->in.hdr.id, authp->id);
+ m_freem(bp);
+ return NULL;
+ }
+ m_settype(bp, MB_PAPIN);
+ authp->id = authp->in.hdr.id; /* We respond with this id */
+
+ if (bp) {
+ bp = mbuf_Read(bp, &nlen, 1);
+ if (authp->in.hdr.code == PAP_ACK) {
+ /*
+ * Don't restrict the length of our acknowledgement freetext to
+ * nlen (a one-byte length). Show the rest of the ack packet
+ * instead. This isn't really part of the protocol.....
+ */
+ bp = m_pullup(bp);
+ txt = MBUF_CTOP(bp);
+ txtlen = m_length(bp);
+ } else {
+ bp = auth_ReadName(authp, bp, nlen);
+ txt = authp->in.name;
+ txtlen = strlen(authp->in.name);
+ }
+ } else {
+ txt = "";
+ txtlen = 0;
+ }
+
+ log_Printf(LogPHASE, "Pap Input: %s (%.*s)\n",
+ papcodes[authp->in.hdr.code], txtlen, txt);
+
+ switch (authp->in.hdr.code) {
+ case PAP_REQUEST:
+ if (bp == NULL) {
+ log_Printf(LogPHASE, "Pap Input: No key given !\n");
+ break;
+ }
+ bp = mbuf_Read(bp, &klen, 1);
+ if (m_length(bp) < klen) {
+ log_Printf(LogERROR, "Pap Input: Truncated key !\n");
+ break;
+ }
+ if ((key = malloc(klen+1)) == NULL) {
+ log_Printf(LogERROR, "Pap Input: Out of memory !\n");
+ break;
+ }
+ bp = mbuf_Read(bp, key, klen);
+ key[klen] = '\0';
+
+#ifndef NORADIUS
+ if (*bundle->radius.cfg.file) {
+ if (!radius_Authenticate(&bundle->radius, authp, authp->in.name,
+ key, strlen(key), NULL, 0))
+ pap_Failure(authp);
+ } else
+#endif
+ if (auth_Validate(bundle, authp->in.name, key))
+ pap_Success(authp);
+ else
+ pap_Failure(authp);
+
+ free(key);
+ break;
+
+ case PAP_ACK:
+ auth_StopTimer(authp);
+ if (p->link.lcp.auth_iwait == PROTO_PAP) {
+ p->link.lcp.auth_iwait = 0;
+ if (p->link.lcp.auth_ineed == 0)
+ /*
+ * We've succeeded in our ``login''
+ * If we're not expecting the peer to authenticate (or he already
+ * has), proceed to network phase.
+ */
+ datalink_AuthOk(p->dl);
+ }
+ break;
+
+ case PAP_NAK:
+ auth_StopTimer(authp);
+ datalink_AuthNotOk(p->dl);
+ break;
+ }
+
+ m_freem(bp);
+ return NULL;
+}
diff --git a/usr.sbin/ppp/pap.h b/usr.sbin/ppp/pap.h
new file mode 100644
index 0000000..8e8d457
--- /dev/null
+++ b/usr.sbin/ppp/pap.h
@@ -0,0 +1,40 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+#define PAP_REQUEST 1
+#define PAP_ACK 2
+#define PAP_NAK 3
+
+struct mbuf;
+struct physical;
+struct authinfo;
+
+extern void pap_Init(struct authinfo *, struct physical *);
+extern struct mbuf *pap_Input(struct bundle *, struct link *, struct mbuf *);
diff --git a/usr.sbin/ppp/physical.c b/usr.sbin/ppp/physical.c
new file mode 100644
index 0000000..e2892ae
--- /dev/null
+++ b/usr.sbin/ppp/physical.c
@@ -0,0 +1,1140 @@
+/*
+ * Written by Eivind Eklund <eivind@yes.no>
+ * for Yes Interactive
+ *
+ * Copyright (C) 1998, Yes Interactive. All rights reserved.
+ *
+ * Redistribution and use in any form is permitted. Redistribution in
+ * source form should include the above copyright and this set of
+ * conditions, because large sections american law seems to have been
+ * created by a bunch of jerks on drugs that are now illegal, forcing
+ * me to include this copyright-stuff instead of placing this in the
+ * public domain. The name of of 'Yes Interactive' or 'Eivind Eklund'
+ * 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 MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * $FreeBSD$
+ *
+ */
+
+#include <sys/param.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#ifdef NOSUID
+#include <signal.h>
+#endif
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+#include <sysexits.h>
+#include <termios.h>
+#include <time.h>
+#include <unistd.h>
+#include <utmpx.h>
+#if defined(__OpenBSD__) || defined(__NetBSD__)
+#include <sys/ioctl.h>
+#include <util.h>
+#else
+#include <libutil.h>
+#endif
+
+#include "layer.h"
+#ifndef NONAT
+#include "nat_cmd.h"
+#endif
+#include "proto.h"
+#include "acf.h"
+#include "vjcomp.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "id.h"
+#include "timer.h"
+#include "fsm.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "throughput.h"
+#include "sync.h"
+#include "async.h"
+#include "iplist.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "ccp.h"
+#include "link.h"
+#include "physical.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "prompt.h"
+#include "chat.h"
+#include "auth.h"
+#include "main.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+#include "tcp.h"
+#include "udp.h"
+#include "exec.h"
+#include "tty.h"
+#ifndef NONETGRAPH
+#include "ether.h"
+#include "netgraph.h"
+#endif
+#ifndef NOATM
+#include "atm.h"
+#endif
+#include "tcpmss.h"
+
+static int physical_DescriptorWrite(struct fdescriptor *, struct bundle *,
+ const fd_set *);
+
+static unsigned
+physical_DeviceSize(void)
+{
+ return sizeof(struct device);
+}
+
+struct {
+ struct device *(*create)(struct physical *);
+ struct device *(*iov2device)(int, struct physical *, struct iovec *,
+ int *, int, int *, int *);
+ unsigned (*DeviceSize)(void);
+} devices[] = {
+ { tty_Create, tty_iov2device, tty_DeviceSize },
+#ifndef NONETGRAPH
+ /*
+ * This must come before ``udp'' so that the probe routine is
+ * able to identify it as a more specific type of SOCK_DGRAM.
+ */
+ { ether_Create, ether_iov2device, ether_DeviceSize },
+#ifdef EXPERIMENTAL_NETGRAPH
+ { ng_Create, ng_iov2device, ng_DeviceSize },
+#endif
+#endif
+#ifndef NOATM
+ /* Ditto for ATM devices */
+ { atm_Create, atm_iov2device, atm_DeviceSize },
+#endif
+ { tcp_Create, tcp_iov2device, tcp_DeviceSize },
+ { udp_Create, udp_iov2device, udp_DeviceSize },
+ { exec_Create, exec_iov2device, exec_DeviceSize }
+};
+
+#define NDEVICES (sizeof devices / sizeof devices[0])
+
+static int
+physical_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e,
+ int *n)
+{
+ return physical_doUpdateSet(d, r, w, e, n, 0);
+}
+
+void
+physical_SetDescriptor(struct physical *p)
+{
+ p->desc.type = PHYSICAL_DESCRIPTOR;
+ p->desc.UpdateSet = physical_UpdateSet;
+ p->desc.IsSet = physical_IsSet;
+ p->desc.Read = physical_DescriptorRead;
+ p->desc.Write = physical_DescriptorWrite;
+}
+
+struct physical *
+physical_Create(struct datalink *dl, int type)
+{
+ struct physical *p;
+
+ p = (struct physical *)malloc(sizeof(struct physical));
+ if (!p)
+ return NULL;
+
+ p->link.type = PHYSICAL_LINK;
+ p->link.name = dl->name;
+ p->link.len = sizeof *p;
+
+ /* The sample period is fixed - see physical2iov() & iov2physical() */
+ throughput_init(&p->link.stats.total, SAMPLE_PERIOD);
+ p->link.stats.parent = dl->bundle->ncp.mp.active ?
+ &dl->bundle->ncp.mp.link.stats.total : NULL;
+ p->link.stats.gather = 1;
+
+ memset(p->link.Queue, '\0', sizeof p->link.Queue);
+ memset(p->link.proto_in, '\0', sizeof p->link.proto_in);
+ memset(p->link.proto_out, '\0', sizeof p->link.proto_out);
+ link_EmptyStack(&p->link);
+
+ p->handler = NULL;
+ physical_SetDescriptor(p);
+ p->type = type;
+
+ hdlc_Init(&p->hdlc, &p->link.lcp);
+ async_Init(&p->async);
+
+ p->fd = -1;
+ p->out = NULL;
+ p->connect_count = 0;
+ p->dl = dl;
+ p->input.sz = 0;
+ *p->name.full = '\0';
+ p->name.base = p->name.full;
+
+ p->Utmp = 0;
+ p->session_owner = (pid_t)-1;
+
+ p->cfg.rts_cts = MODEM_CTSRTS;
+ p->cfg.speed = MODEM_SPEED;
+ p->cfg.parity = CS8;
+ memcpy(p->cfg.devlist, MODEM_LIST, sizeof MODEM_LIST);
+ p->cfg.ndev = NMODEMS;
+ p->cfg.cd.necessity = CD_DEFAULT;
+ p->cfg.cd.delay = 0; /* reconfigured or device specific default */
+
+ lcp_Init(&p->link.lcp, dl->bundle, &p->link, &dl->fsmp);
+ ccp_Init(&p->link.ccp, dl->bundle, &p->link, &dl->fsmp);
+
+ return p;
+}
+
+static const struct parity {
+ const char *name;
+ const char *name1;
+ int set;
+} validparity[] = {
+ { "even", "P_EVEN", CS7 | PARENB },
+ { "odd", "P_ODD", CS7 | PARENB | PARODD },
+ { "none", "P_ZERO", CS8 },
+ { NULL, NULL, 0 },
+};
+
+static int
+GetParityValue(const char *str)
+{
+ const struct parity *pp;
+
+ for (pp = validparity; pp->name; pp++) {
+ if (strcasecmp(pp->name, str) == 0 ||
+ strcasecmp(pp->name1, str) == 0) {
+ return pp->set;
+ }
+ }
+ return (-1);
+}
+
+int
+physical_SetParity(struct physical *p, const char *str)
+{
+ struct termios rstio;
+ int val;
+
+ val = GetParityValue(str);
+ if (val > 0) {
+ p->cfg.parity = val;
+ if (p->fd >= 0) {
+ tcgetattr(p->fd, &rstio);
+ rstio.c_cflag &= ~(CSIZE | PARODD | PARENB);
+ rstio.c_cflag |= val;
+ tcsetattr(p->fd, TCSADRAIN, &rstio);
+ }
+ return 0;
+ }
+ log_Printf(LogWARN, "%s: %s: Invalid parity\n", p->link.name, str);
+ return -1;
+}
+
+unsigned
+physical_GetSpeed(struct physical *p)
+{
+ if (p->handler && p->handler->speed)
+ return (*p->handler->speed)(p);
+
+ return 0;
+}
+
+int
+physical_SetSpeed(struct physical *p, unsigned speed)
+{
+ if (UnsignedToSpeed(speed) != B0) {
+ p->cfg.speed = speed;
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+physical_Raw(struct physical *p)
+{
+ if (p->handler && p->handler->raw)
+ return (*p->handler->raw)(p);
+
+ return 1;
+}
+
+void
+physical_Offline(struct physical *p)
+{
+ if (p->handler && p->handler->offline)
+ (*p->handler->offline)(p);
+ log_Printf(LogPHASE, "%s: Disconnected!\n", p->link.name);
+}
+
+static int
+physical_Lock(struct physical *p)
+{
+ int res;
+
+ if (*p->name.full == '/' && p->type != PHYS_DIRECT &&
+ (res = ID0uu_lock(p->name.base)) != UU_LOCK_OK) {
+ if (res == UU_LOCK_INUSE)
+ log_Printf(LogPHASE, "%s: %s is in use\n", p->link.name, p->name.full);
+ else
+ log_Printf(LogPHASE, "%s: %s is in use: uu_lock: %s\n",
+ p->link.name, p->name.full, uu_lockerr(res));
+ return 0;
+ }
+
+ return 1;
+}
+
+static void
+physical_Unlock(struct physical *p)
+{
+ if (*p->name.full == '/' && p->type != PHYS_DIRECT &&
+ ID0uu_unlock(p->name.base) == -1)
+ log_Printf(LogALERT, "%s: Can't uu_unlock %s\n", p->link.name,
+ p->name.base);
+}
+
+void
+physical_Close(struct physical *p)
+{
+ int newsid;
+ char fn[PATH_MAX];
+ struct utmpx ut;
+
+ if (p->fd < 0)
+ return;
+
+ log_Printf(LogDEBUG, "%s: Close\n", p->link.name);
+
+ if (p->handler && p->handler->cooked)
+ (*p->handler->cooked)(p);
+
+ physical_StopDeviceTimer(p);
+ if (p->Utmp) {
+ memset(&ut, 0, sizeof ut);
+ ut.ut_type = DEAD_PROCESS;
+ gettimeofday(&ut.ut_tv, NULL);
+ snprintf(ut.ut_id, sizeof ut.ut_id, "%xppp", (int)getpid());
+ ID0logout(&ut);
+ p->Utmp = 0;
+ }
+ newsid = tcgetpgrp(p->fd) == getpgrp();
+ close(p->fd);
+ p->fd = -1;
+ log_SetTtyCommandMode(p->dl);
+
+ throughput_stop(&p->link.stats.total);
+ throughput_log(&p->link.stats.total, LogPHASE, p->link.name);
+
+ if (p->session_owner != (pid_t)-1) {
+ log_Printf(LogPHASE, "%s: HUPing %ld\n", p->link.name,
+ (long)p->session_owner);
+ ID0kill(p->session_owner, SIGHUP);
+ p->session_owner = (pid_t)-1;
+ }
+
+ if (newsid)
+ bundle_setsid(p->dl->bundle, 0);
+
+ if (*p->name.full == '/') {
+ snprintf(fn, sizeof fn, "%s%s.if", _PATH_VARRUN, p->name.base);
+#ifndef RELEASE_CRUNCH
+ if (ID0unlink(fn) == -1)
+ log_Printf(LogALERT, "%s: Can't remove %s: %s\n",
+ p->link.name, fn, strerror(errno));
+#else
+ ID0unlink(fn);
+#endif
+ }
+ physical_Unlock(p);
+ if (p->handler && p->handler->destroy)
+ (*p->handler->destroy)(p);
+ p->handler = NULL;
+ p->name.base = p->name.full;
+ *p->name.full = '\0';
+}
+
+void
+physical_Destroy(struct physical *p)
+{
+ physical_Close(p);
+ throughput_destroy(&p->link.stats.total);
+ free(p);
+}
+
+static int
+physical_DescriptorWrite(struct fdescriptor *d, struct bundle *bundle __unused,
+ const fd_set *fdset __unused)
+{
+ struct physical *p = descriptor2physical(d);
+ int nw, result = 0;
+
+ if (p->out == NULL)
+ p->out = link_Dequeue(&p->link);
+
+ if (p->out) {
+ nw = physical_Write(p, MBUF_CTOP(p->out), p->out->m_len);
+ log_Printf(LogDEBUG, "%s: DescriptorWrite: wrote %d(%lu) to %d\n",
+ p->link.name, nw, (unsigned long)p->out->m_len, p->fd);
+ if (nw > 0) {
+ p->out->m_len -= nw;
+ p->out->m_offset += nw;
+ if (p->out->m_len == 0)
+ p->out = m_free(p->out);
+ result = 1;
+ } else if (nw < 0) {
+ if (errno == EAGAIN)
+ result = 1;
+ else if (errno != ENOBUFS) {
+ log_Printf(LogPHASE, "%s: write (fd %d, len %zd): %s\n", p->link.name,
+ p->fd, p->out->m_len, strerror(errno));
+ datalink_Down(p->dl, CLOSE_NORMAL);
+ }
+ }
+ /* else we shouldn't really have been called ! select() is broken ! */
+ }
+
+ return result;
+}
+
+int
+physical_ShowStatus(struct cmdargs const *arg)
+{
+ struct physical *p = arg->cx->physical;
+ struct cd *cd;
+ const char *dev;
+ int n, slot;
+
+ prompt_Printf(arg->prompt, "Name: %s\n", p->link.name);
+ prompt_Printf(arg->prompt, " State: ");
+ if (p->fd < 0)
+ prompt_Printf(arg->prompt, "closed\n");
+ else {
+ slot = physical_Slot(p);
+ if (p->handler && p->handler->openinfo) {
+ if (slot == -1)
+ prompt_Printf(arg->prompt, "open (%s)\n", (*p->handler->openinfo)(p));
+ else
+ prompt_Printf(arg->prompt, "open (%s, port %d)\n",
+ (*p->handler->openinfo)(p), slot);
+ } else if (slot == -1)
+ prompt_Printf(arg->prompt, "open\n");
+ else
+ prompt_Printf(arg->prompt, "open (port %d)\n", slot);
+ }
+
+ prompt_Printf(arg->prompt, " Device: %s",
+ *p->name.full ? p->name.full :
+ p->type == PHYS_DIRECT ? "unknown" : "N/A");
+ if (p->session_owner != (pid_t)-1)
+ prompt_Printf(arg->prompt, " (session owner: %ld)", (long)p->session_owner);
+
+ prompt_Printf(arg->prompt, "\n Link Type: %s\n", mode2Nam(p->type));
+ prompt_Printf(arg->prompt, " Connect Count: %d\n", p->connect_count);
+#ifdef TIOCOUTQ
+ if (p->fd >= 0 && ioctl(p->fd, TIOCOUTQ, &n) >= 0)
+ prompt_Printf(arg->prompt, " Physical outq: %d\n", n);
+#endif
+
+ prompt_Printf(arg->prompt, " Queued Packets: %lu\n",
+ (u_long)link_QueueLen(&p->link));
+ prompt_Printf(arg->prompt, " Phone Number: %s\n", arg->cx->phone.chosen);
+
+ prompt_Printf(arg->prompt, "\nDefaults:\n");
+
+ prompt_Printf(arg->prompt, " Device List: ");
+ dev = p->cfg.devlist;
+ for (n = 0; n < p->cfg.ndev; n++) {
+ if (n)
+ prompt_Printf(arg->prompt, ", ");
+ prompt_Printf(arg->prompt, "\"%s\"", dev);
+ dev += strlen(dev) + 1;
+ }
+
+ prompt_Printf(arg->prompt, "\n Characteristics: ");
+ if (physical_IsSync(arg->cx->physical))
+ prompt_Printf(arg->prompt, "sync");
+ else
+ prompt_Printf(arg->prompt, "%dbps", p->cfg.speed);
+
+ switch (p->cfg.parity & CSIZE) {
+ case CS7:
+ prompt_Printf(arg->prompt, ", cs7");
+ break;
+ case CS8:
+ prompt_Printf(arg->prompt, ", cs8");
+ break;
+ }
+ if (p->cfg.parity & PARENB) {
+ if (p->cfg.parity & PARODD)
+ prompt_Printf(arg->prompt, ", odd parity");
+ else
+ prompt_Printf(arg->prompt, ", even parity");
+ } else
+ prompt_Printf(arg->prompt, ", no parity");
+
+ prompt_Printf(arg->prompt, ", CTS/RTS %s\n", (p->cfg.rts_cts ? "on" : "off"));
+
+ prompt_Printf(arg->prompt, " CD check delay: ");
+ cd = p->handler ? &p->handler->cd : &p->cfg.cd;
+ if (cd->necessity == CD_NOTREQUIRED)
+ prompt_Printf(arg->prompt, "no cd");
+ else if (p->cfg.cd.necessity == CD_DEFAULT) {
+ prompt_Printf(arg->prompt, "device specific");
+ } else {
+ prompt_Printf(arg->prompt, "%d second%s", p->cfg.cd.delay,
+ p->cfg.cd.delay == 1 ? "" : "s");
+ if (p->cfg.cd.necessity == CD_REQUIRED)
+ prompt_Printf(arg->prompt, " (required!)");
+ }
+ prompt_Printf(arg->prompt, "\n\n");
+
+ throughput_disp(&p->link.stats.total, arg->prompt);
+
+ return 0;
+}
+
+void
+physical_DescriptorRead(struct fdescriptor *d, struct bundle *bundle,
+ const fd_set *fdset __unused)
+{
+ struct physical *p = descriptor2physical(d);
+ u_char *rbuff;
+ int n, found;
+
+ rbuff = p->input.buf + p->input.sz;
+
+ /* something to read */
+ n = physical_Read(p, rbuff, sizeof p->input.buf - p->input.sz);
+ log_Printf(LogDEBUG, "%s: DescriptorRead: read %d/%d from %d\n",
+ p->link.name, n, (int)(sizeof p->input.buf - p->input.sz), p->fd);
+ if (n <= 0) {
+ if (n < 0)
+ log_Printf(LogPHASE, "%s: read (%d): %s\n", p->link.name, p->fd,
+ strerror(errno));
+ else
+ log_Printf(LogPHASE, "%s: read (%d): Got zero bytes\n",
+ p->link.name, p->fd);
+ datalink_Down(p->dl, CLOSE_NORMAL);
+ return;
+ }
+
+ rbuff -= p->input.sz;
+ n += p->input.sz;
+
+ if (p->link.lcp.fsm.state <= ST_CLOSED) {
+ if (p->type != PHYS_DEDICATED) {
+ found = hdlc_Detect((u_char const **)&rbuff, n, physical_IsSync(p));
+ if (rbuff != p->input.buf)
+ log_WritePrompts(p->dl, "%.*s", (int)(rbuff - p->input.buf),
+ p->input.buf);
+ p->input.sz = n - (rbuff - p->input.buf);
+
+ if (found) {
+ /* LCP packet is detected. Turn ourselves into packet mode */
+ log_Printf(LogPHASE, "%s: PPP packet detected, coming up\n",
+ p->link.name);
+ log_SetTtyCommandMode(p->dl);
+ datalink_Up(p->dl, 0, 1);
+ link_PullPacket(&p->link, rbuff, p->input.sz, bundle);
+ p->input.sz = 0;
+ } else
+ bcopy(rbuff, p->input.buf, p->input.sz);
+ } else
+ /* In -dedicated mode, we just discard input until LCP is started */
+ p->input.sz = 0;
+ } else if (n > 0)
+ link_PullPacket(&p->link, rbuff, n, bundle);
+}
+
+struct physical *
+iov2physical(struct datalink *dl, struct iovec *iov, int *niov, int maxiov,
+ int fd, int *auxfd, int *nauxfd)
+{
+ struct physical *p;
+ int type;
+ unsigned h;
+
+ p = (struct physical *)iov[(*niov)++].iov_base;
+ p->link.name = dl->name;
+ memset(p->link.Queue, '\0', sizeof p->link.Queue);
+
+ p->desc.UpdateSet = physical_UpdateSet;
+ p->desc.IsSet = physical_IsSet;
+ p->desc.Read = physical_DescriptorRead;
+ p->desc.Write = physical_DescriptorWrite;
+ p->type = PHYS_DIRECT;
+ p->dl = dl;
+ p->out = NULL;
+ p->connect_count = 1;
+
+ physical_SetDevice(p, p->name.full);
+
+ p->link.lcp.fsm.bundle = dl->bundle;
+ p->link.lcp.fsm.link = &p->link;
+ memset(&p->link.lcp.fsm.FsmTimer, '\0', sizeof p->link.lcp.fsm.FsmTimer);
+ memset(&p->link.lcp.fsm.OpenTimer, '\0', sizeof p->link.lcp.fsm.OpenTimer);
+ memset(&p->link.lcp.fsm.StoppedTimer, '\0',
+ sizeof p->link.lcp.fsm.StoppedTimer);
+ p->link.lcp.fsm.parent = &dl->fsmp;
+ lcp_SetupCallbacks(&p->link.lcp);
+
+ p->link.ccp.fsm.bundle = dl->bundle;
+ p->link.ccp.fsm.link = &p->link;
+ /* Our in.state & out.state are NULL (no link-level ccp yet) */
+ memset(&p->link.ccp.fsm.FsmTimer, '\0', sizeof p->link.ccp.fsm.FsmTimer);
+ memset(&p->link.ccp.fsm.OpenTimer, '\0', sizeof p->link.ccp.fsm.OpenTimer);
+ memset(&p->link.ccp.fsm.StoppedTimer, '\0',
+ sizeof p->link.ccp.fsm.StoppedTimer);
+ p->link.ccp.fsm.parent = &dl->fsmp;
+ ccp_SetupCallbacks(&p->link.ccp);
+
+ p->hdlc.lqm.owner = &p->link.lcp;
+ p->hdlc.ReportTimer.state = TIMER_STOPPED;
+ p->hdlc.lqm.timer.state = TIMER_STOPPED;
+
+ p->fd = fd;
+ p->link.stats.total.in.SampleOctets = (long long *)iov[(*niov)++].iov_base;
+ p->link.stats.total.out.SampleOctets = (long long *)iov[(*niov)++].iov_base;
+ p->link.stats.parent = dl->bundle->ncp.mp.active ?
+ &dl->bundle->ncp.mp.link.stats.total : NULL;
+ p->link.stats.gather = 1;
+
+ type = (long)p->handler;
+ p->handler = NULL;
+ for (h = 0; h < NDEVICES && p->handler == NULL; h++)
+ p->handler = (*devices[h].iov2device)(type, p, iov, niov, maxiov,
+ auxfd, nauxfd);
+ if (p->handler == NULL) {
+ log_Printf(LogPHASE, "%s: Unknown link type\n", p->link.name);
+ free(iov[(*niov)++].iov_base);
+ physical_SetupStack(p, "unknown", PHYSICAL_NOFORCE);
+ } else
+ log_Printf(LogPHASE, "%s: Device %s, link type is %s\n",
+ p->link.name, p->name.full, p->handler->name);
+
+ if (p->hdlc.lqm.method && p->hdlc.lqm.timer.load)
+ lqr_reStart(&p->link.lcp);
+ hdlc_StartTimer(&p->hdlc);
+
+ throughput_restart(&p->link.stats.total, "physical throughput",
+ Enabled(dl->bundle, OPT_THROUGHPUT));
+
+ return p;
+}
+
+unsigned
+physical_MaxDeviceSize()
+{
+ unsigned biggest, sz, n;
+
+ biggest = sizeof(struct device);
+ for (n = 0; n < NDEVICES; n++)
+ if (devices[n].DeviceSize) {
+ sz = (*devices[n].DeviceSize)();
+ if (biggest < sz)
+ biggest = sz;
+ }
+
+ return biggest;
+}
+
+int
+physical2iov(struct physical *p, struct iovec *iov, int *niov, int maxiov,
+ int *auxfd, int *nauxfd)
+{
+ struct device *h;
+ int sz;
+
+ h = NULL;
+ if (p) {
+ hdlc_StopTimer(&p->hdlc);
+ lqr_StopTimer(p);
+ timer_Stop(&p->link.lcp.fsm.FsmTimer);
+ timer_Stop(&p->link.ccp.fsm.FsmTimer);
+ timer_Stop(&p->link.lcp.fsm.OpenTimer);
+ timer_Stop(&p->link.ccp.fsm.OpenTimer);
+ timer_Stop(&p->link.lcp.fsm.StoppedTimer);
+ timer_Stop(&p->link.ccp.fsm.StoppedTimer);
+ if (p->handler) {
+ h = p->handler;
+ p->handler = (struct device *)(long)p->handler->type;
+ }
+
+ if (Enabled(p->dl->bundle, OPT_KEEPSESSION) ||
+ tcgetpgrp(p->fd) == getpgrp())
+ p->session_owner = getpid(); /* So I'll eventually get HUP'd */
+ else
+ p->session_owner = (pid_t)-1;
+ timer_Stop(&p->link.stats.total.Timer);
+ }
+
+ if (*niov + 2 >= maxiov) {
+ log_Printf(LogERROR, "physical2iov: No room for physical + throughput"
+ " + device !\n");
+ if (p)
+ free(p);
+ return -1;
+ }
+
+ iov[*niov].iov_base = (void *)p;
+ iov[*niov].iov_len = sizeof *p;
+ (*niov)++;
+
+ iov[*niov].iov_base = p ? (void *)p->link.stats.total.in.SampleOctets : NULL;
+ iov[*niov].iov_len = SAMPLE_PERIOD * sizeof(long long);
+ (*niov)++;
+ iov[*niov].iov_base = p ? (void *)p->link.stats.total.out.SampleOctets : NULL;
+ iov[*niov].iov_len = SAMPLE_PERIOD * sizeof(long long);
+ (*niov)++;
+
+ sz = physical_MaxDeviceSize();
+ if (p) {
+ if (h && h->device2iov)
+ (*h->device2iov)(h, iov, niov, maxiov, auxfd, nauxfd);
+ else {
+ if ((iov[*niov].iov_base = malloc(sz)) == NULL) {
+ log_Printf(LogALERT, "physical2iov: Out of memory (%d bytes)\n", sz);
+ AbortProgram(EX_OSERR);
+ }
+ if (h)
+ memcpy(iov[*niov].iov_base, h, sizeof *h);
+ iov[*niov].iov_len = sz;
+ (*niov)++;
+ }
+ } else {
+ iov[*niov].iov_base = NULL;
+ iov[*niov].iov_len = sz;
+ (*niov)++;
+ }
+
+ return p ? p->fd : 0;
+}
+
+const char *
+physical_LockedDevice(struct physical *p)
+{
+ if (p->fd >= 0 && *p->name.full == '/' && p->type != PHYS_DIRECT)
+ return p->name.base;
+
+ return NULL;
+}
+
+void
+physical_ChangedPid(struct physical *p, pid_t newpid)
+{
+ if (physical_LockedDevice(p)) {
+ int res;
+
+ if ((res = ID0uu_lock_txfr(p->name.base, newpid)) != UU_LOCK_OK)
+ log_Printf(LogPHASE, "uu_lock_txfr: %s\n", uu_lockerr(res));
+ }
+}
+
+int
+physical_IsSync(struct physical *p)
+{
+ return p->cfg.speed == 0;
+}
+
+u_short
+physical_DeviceMTU(struct physical *p)
+{
+ return p->handler ? p->handler->mtu : 0;
+}
+
+const char *physical_GetDevice(struct physical *p)
+{
+ return p->name.full;
+}
+
+void
+physical_SetDeviceList(struct physical *p, int argc, const char *const *argv)
+{
+ unsigned pos;
+ int f;
+
+ p->cfg.devlist[sizeof p->cfg.devlist - 1] = '\0';
+ for (f = 0, pos = 0; f < argc && pos < sizeof p->cfg.devlist - 1; f++) {
+ if (pos)
+ p->cfg.devlist[pos++] = '\0';
+ strncpy(p->cfg.devlist + pos, argv[f], sizeof p->cfg.devlist - pos - 1);
+ pos += strlen(p->cfg.devlist + pos);
+ }
+ p->cfg.ndev = f;
+}
+
+void
+physical_SetSync(struct physical *p)
+{
+ p->cfg.speed = 0;
+}
+
+int
+physical_SetRtsCts(struct physical *p, int enable)
+{
+ p->cfg.rts_cts = enable ? 1 : 0;
+ return 1;
+}
+
+ssize_t
+physical_Read(struct physical *p, void *buf, size_t nbytes)
+{
+ ssize_t ret;
+
+ if (p->handler && p->handler->read)
+ ret = (*p->handler->read)(p, buf, nbytes);
+ else
+ ret = read(p->fd, buf, nbytes);
+
+ log_DumpBuff(LogPHYSICAL, "read", buf, ret);
+
+ return ret;
+}
+
+ssize_t
+physical_Write(struct physical *p, const void *buf, size_t nbytes)
+{
+ log_DumpBuff(LogPHYSICAL, "write", buf, nbytes);
+
+ if (p->handler && p->handler->write)
+ return (*p->handler->write)(p, buf, nbytes);
+
+ return write(p->fd, buf, nbytes);
+}
+
+int
+physical_doUpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e,
+ int *n, int force)
+{
+ struct physical *p = descriptor2physical(d);
+ int sets;
+
+ sets = 0;
+ if (p->fd >= 0) {
+ if (r) {
+ FD_SET(p->fd, r);
+ log_Printf(LogTIMER, "%s: fdset(r) %d\n", p->link.name, p->fd);
+ sets++;
+ }
+ if (e) {
+ FD_SET(p->fd, e);
+ log_Printf(LogTIMER, "%s: fdset(e) %d\n", p->link.name, p->fd);
+ sets++;
+ }
+ if (w && (force || link_QueueLen(&p->link) || p->out)) {
+ FD_SET(p->fd, w);
+ log_Printf(LogTIMER, "%s: fdset(w) %d\n", p->link.name, p->fd);
+ sets++;
+ }
+ if (sets && *n < p->fd + 1)
+ *n = p->fd + 1;
+ }
+
+ return sets;
+}
+
+int
+physical_RemoveFromSet(struct physical *p, fd_set *r, fd_set *w, fd_set *e)
+{
+ if (p->handler && p->handler->removefromset)
+ return (*p->handler->removefromset)(p, r, w, e);
+ else {
+ int sets;
+
+ sets = 0;
+ if (p->fd >= 0) {
+ if (r && FD_ISSET(p->fd, r)) {
+ FD_CLR(p->fd, r);
+ log_Printf(LogTIMER, "%s: fdunset(r) %d\n", p->link.name, p->fd);
+ sets++;
+ }
+ if (e && FD_ISSET(p->fd, e)) {
+ FD_CLR(p->fd, e);
+ log_Printf(LogTIMER, "%s: fdunset(e) %d\n", p->link.name, p->fd);
+ sets++;
+ }
+ if (w && FD_ISSET(p->fd, w)) {
+ FD_CLR(p->fd, w);
+ log_Printf(LogTIMER, "%s: fdunset(w) %d\n", p->link.name, p->fd);
+ sets++;
+ }
+ }
+
+ return sets;
+ }
+}
+
+int
+physical_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+ struct physical *p = descriptor2physical(d);
+ return p->fd >= 0 && FD_ISSET(p->fd, fdset);
+}
+
+void
+physical_Login(struct physical *p, const char *name)
+{
+ if (p->type == PHYS_DIRECT && *p->name.base && !p->Utmp) {
+ struct utmpx ut;
+ const char *connstr;
+ char *colon;
+
+ memset(&ut, 0, sizeof ut);
+ ut.ut_type = USER_PROCESS;
+ gettimeofday(&ut.ut_tv, NULL);
+ snprintf(ut.ut_id, sizeof ut.ut_id, "%xppp", (int)getpid());
+ strncpy(ut.ut_user, name, sizeof ut.ut_user);
+ if (p->handler && (p->handler->type == TCP_DEVICE ||
+ p->handler->type == UDP_DEVICE)) {
+ strncpy(ut.ut_host, p->name.base, sizeof ut.ut_host);
+ colon = memchr(ut.ut_host, ':', sizeof ut.ut_host);
+ if (colon)
+ *colon = '\0';
+ } else
+ strncpy(ut.ut_line, p->name.base, sizeof ut.ut_line);
+ if ((connstr = getenv("CONNECT")))
+ /* mgetty sets this to the connection speed */
+ strncpy(ut.ut_host, connstr, sizeof ut.ut_host);
+ ID0login(&ut);
+ p->Utmp = 1;
+ }
+}
+
+int
+physical_SetMode(struct physical *p, int mode)
+{
+ if ((p->type & (PHYS_DIRECT|PHYS_DEDICATED) ||
+ mode & (PHYS_DIRECT|PHYS_DEDICATED)) &&
+ (!(p->type & PHYS_DIRECT) || !(mode & PHYS_BACKGROUND))) {
+ /* Note: The -direct -> -background is for callback ! */
+ log_Printf(LogWARN, "%s: Cannot change mode %s to %s\n", p->link.name,
+ mode2Nam(p->type), mode2Nam(mode));
+ return 0;
+ }
+ p->type = mode;
+ return 1;
+}
+
+void
+physical_DeleteQueue(struct physical *p)
+{
+ if (p->out) {
+ m_freem(p->out);
+ p->out = NULL;
+ }
+ link_DeleteQueue(&p->link);
+}
+
+void
+physical_SetDevice(struct physical *p, const char *name)
+{
+ int len = strlen(_PATH_DEV);
+
+ if (name != p->name.full) {
+ strncpy(p->name.full, name, sizeof p->name.full - 1);
+ p->name.full[sizeof p->name.full - 1] = '\0';
+ }
+ p->name.base = *p->name.full == '!' ? p->name.full + 1 :
+ strncmp(p->name.full, _PATH_DEV, len) ?
+ p->name.full : p->name.full + len;
+}
+
+static void
+physical_Found(struct physical *p)
+{
+ FILE *lockfile;
+ char fn[PATH_MAX];
+
+ if (*p->name.full == '/') {
+ snprintf(fn, sizeof fn, "%s%s.if", _PATH_VARRUN, p->name.base);
+ lockfile = ID0fopen(fn, "w");
+ if (lockfile != NULL) {
+ fprintf(lockfile, "%s%d\n", TUN_NAME, p->dl->bundle->unit);
+ fclose(lockfile);
+ }
+#ifndef RELEASE_CRUNCH
+ else
+ log_Printf(LogALERT, "%s: Can't create %s: %s\n",
+ p->link.name, fn, strerror(errno));
+#endif
+ }
+
+ throughput_start(&p->link.stats.total, "physical throughput",
+ Enabled(p->dl->bundle, OPT_THROUGHPUT));
+ p->connect_count++;
+ p->input.sz = 0;
+
+ log_Printf(LogPHASE, "%s: Connected!\n", p->link.name);
+}
+
+int
+physical_Open(struct physical *p)
+{
+ char *dev;
+ int devno, wasfd, err;
+ unsigned h;
+
+ if (p->fd >= 0)
+ log_Printf(LogDEBUG, "%s: Open: Modem is already open!\n", p->link.name);
+ /* We're going back into "term" mode */
+ else if (p->type == PHYS_DIRECT) {
+ physical_SetDevice(p, "");
+ p->fd = STDIN_FILENO;
+ for (h = 0; h < NDEVICES && p->handler == NULL && p->fd >= 0; h++)
+ p->handler = (*devices[h].create)(p);
+ close(STDOUT_FILENO);
+ if (p->fd >= 0) {
+ if (p->handler == NULL) {
+ physical_SetupStack(p, "unknown", PHYSICAL_NOFORCE);
+ log_Printf(LogDEBUG, "%s: stdin is unidentified\n", p->link.name);
+ }
+ physical_Found(p);
+ }
+ } else {
+ dev = p->cfg.devlist;
+ devno = 0;
+ while (devno < p->cfg.ndev && p->fd < 0) {
+ physical_SetDevice(p, dev);
+ if (physical_Lock(p)) {
+ err = 0;
+
+ if (*p->name.full == '/') {
+ p->fd = ID0open(p->name.full, O_RDWR | O_NONBLOCK);
+ if (p->fd < 0)
+ err = errno;
+ }
+
+ wasfd = p->fd;
+ for (h = 0; h < NDEVICES && p->handler == NULL; h++)
+ if ((p->handler = (*devices[h].create)(p)) == NULL && wasfd != p->fd)
+ break;
+
+ if (p->fd < 0) {
+ if (h == NDEVICES) {
+ if (err)
+ log_Printf(LogWARN, "%s: %s: %s\n", p->link.name, p->name.full,
+ strerror(errno));
+ else
+ log_Printf(LogWARN, "%s: Device (%s) must begin with a '/',"
+ " a '!' or contain at least one ':'\n", p->link.name,
+ p->name.full);
+ }
+ physical_Unlock(p);
+ } else
+ physical_Found(p);
+ }
+ dev += strlen(dev) + 1;
+ devno++;
+ }
+ }
+
+ return p->fd;
+}
+
+void
+physical_SetupStack(struct physical *p, const char *who, int how)
+{
+ link_EmptyStack(&p->link);
+ if (how == PHYSICAL_FORCE_SYNC || how == PHYSICAL_FORCE_SYNCNOACF ||
+ (how == PHYSICAL_NOFORCE && physical_IsSync(p)))
+ link_Stack(&p->link, &synclayer);
+ else {
+ link_Stack(&p->link, &asynclayer);
+ link_Stack(&p->link, &hdlclayer);
+ }
+ if (how != PHYSICAL_FORCE_SYNCNOACF)
+ link_Stack(&p->link, &acflayer);
+ link_Stack(&p->link, &protolayer);
+ link_Stack(&p->link, &lqrlayer);
+ link_Stack(&p->link, &ccplayer);
+ link_Stack(&p->link, &vjlayer);
+ link_Stack(&p->link, &tcpmsslayer);
+#ifndef NONAT
+ link_Stack(&p->link, &natlayer);
+#endif
+ if (how == PHYSICAL_FORCE_ASYNC && physical_IsSync(p)) {
+ log_Printf(LogWARN, "Sync device setting ignored for ``%s'' device\n", who);
+ p->cfg.speed = MODEM_SPEED;
+ } else if (how == PHYSICAL_FORCE_SYNC && !physical_IsSync(p)) {
+ log_Printf(LogWARN, "Async device setting ignored for ``%s'' device\n",
+ who);
+ physical_SetSync(p);
+ }
+}
+
+void
+physical_StopDeviceTimer(struct physical *p)
+{
+ if (p->handler && p->handler->stoptimer)
+ (*p->handler->stoptimer)(p);
+}
+
+int
+physical_AwaitCarrier(struct physical *p)
+{
+ if (p->handler && p->handler->awaitcarrier)
+ return (*p->handler->awaitcarrier)(p);
+
+ return CARRIER_OK;
+}
+
+
+void
+physical_SetAsyncParams(struct physical *p, u_int32_t mymap, u_int32_t hismap)
+{
+ if (p->handler && p->handler->setasyncparams)
+ return (*p->handler->setasyncparams)(p, mymap, hismap);
+
+ async_SetLinkParams(&p->async, mymap, hismap);
+}
+
+int
+physical_Slot(struct physical *p)
+{
+ if (p->handler && p->handler->slot)
+ return (*p->handler->slot)(p);
+
+ return -1;
+}
+
+int
+physical_SetPPPoEnonstandard(struct physical *p, int enable)
+{
+ p->cfg.nonstandard_pppoe = enable ? 1 : 0;
+ p->cfg.pppoe_configured = 1;
+ return 1;
+}
diff --git a/usr.sbin/ppp/physical.h b/usr.sbin/ppp/physical.h
new file mode 100644
index 0000000..cf3408e
--- /dev/null
+++ b/usr.sbin/ppp/physical.h
@@ -0,0 +1,176 @@
+/*
+ * Written by Eivind Eklund <eivind@yes.no>
+ * for Yes Interactive
+ *
+ * Copyright (C) 1998, Yes Interactive. All rights reserved.
+ *
+ * Redistribution and use in any form is permitted. Redistribution in
+ * source form should include the above copyright and this set of
+ * conditions, because large sections american law seems to have been
+ * created by a bunch of jerks on drugs that are now illegal, forcing
+ * me to include this copyright-stuff instead of placing this in the
+ * public domain. The name of of 'Yes Interactive' or 'Eivind Eklund'
+ * 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 MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * $FreeBSD$
+ *
+ */
+
+struct datalink;
+struct bundle;
+struct iovec;
+struct physical;
+struct bundle;
+struct ccp;
+struct cmdargs;
+
+/* Device types (don't use zero, it'll be confused with NULL in physical2iov */
+#define I4B_DEVICE 1
+#define TTY_DEVICE 2
+#define TCP_DEVICE 3
+#define UDP_DEVICE 4
+#define ETHER_DEVICE 5
+#define EXEC_DEVICE 6
+#define ATM_DEVICE 7
+#define NG_DEVICE 8
+
+/* Returns from awaitcarrier() */
+#define CARRIER_PENDING 1
+#define CARRIER_OK 2
+#define CARRIER_LOST 3
+
+/* A cd ``necessity'' value */
+#define CD_VARIABLE 0
+#define CD_REQUIRED 1
+#define CD_NOTREQUIRED 2
+#define CD_DEFAULT 3
+
+struct cd {
+ unsigned necessity : 2; /* A CD_ value */
+ int delay; /* Wait this many seconds after login script */
+};
+
+struct device {
+ int type;
+ const char *name;
+ u_short mtu;
+ struct cd cd;
+
+ int (*awaitcarrier)(struct physical *);
+ int (*removefromset)(struct physical *, fd_set *, fd_set *, fd_set *);
+ int (*raw)(struct physical *);
+ void (*offline)(struct physical *);
+ void (*cooked)(struct physical *);
+ void (*setasyncparams)(struct physical *, u_int32_t, u_int32_t);
+ void (*stoptimer)(struct physical *);
+ void (*destroy)(struct physical *);
+ ssize_t (*read)(struct physical *, void *, size_t);
+ ssize_t (*write)(struct physical *, const void *, size_t);
+ void (*device2iov)(struct device *, struct iovec *, int *, int, int *, int *);
+ unsigned (*speed)(struct physical *);
+ const char *(*openinfo)(struct physical *);
+ int (*slot)(struct physical *);
+};
+
+struct physical {
+ struct link link;
+ struct fdescriptor desc;
+ int type; /* What sort of PHYS_* link are we ? */
+ struct async async; /* Our async state */
+ struct hdlc hdlc; /* Our hdlc state */
+ int fd; /* File descriptor for this device */
+ struct mbuf *out; /* mbuf that suffered a short write */
+ int connect_count;
+ struct datalink *dl; /* my owner */
+
+ struct {
+ u_char buf[MAX_MRU]; /* Our input data buffer */
+ size_t sz;
+ } input;
+
+ struct {
+ char full[DEVICE_LEN]; /* Our current device name */
+ char *base;
+ } name;
+
+ int Utmp; /* Are we in utmp ? */
+ pid_t session_owner; /* HUP this when closing the link */
+
+ struct device *handler; /* device specific handler */
+
+ struct {
+ unsigned rts_cts : 1; /* Is rts/cts enabled ? */
+ unsigned nonstandard_pppoe : 1; /* Is PPPoE mode nonstandard */
+ unsigned pppoe_configured : 1; /* temporary hack */
+ unsigned parity; /* What parity is enabled? (tty flags) */
+ unsigned speed; /* tty speed */
+
+ char devlist[LINE_LEN]; /* NUL separated list of devices */
+ int ndev; /* number of devices in list */
+ struct cd cd;
+ } cfg;
+};
+
+#define field2phys(fp, name) \
+ ((struct physical *)((char *)fp - (uintptr_t)(&((struct physical *)0)->name)))
+
+#define link2physical(l) \
+ ((l)->type == PHYSICAL_LINK ? field2phys(l, link) : NULL)
+
+#define descriptor2physical(d) \
+ ((d)->type == PHYSICAL_DESCRIPTOR ? field2phys(d, desc) : NULL)
+
+#define PHYSICAL_NOFORCE 1
+#define PHYSICAL_FORCE_ASYNC 2
+#define PHYSICAL_FORCE_SYNC 3
+#define PHYSICAL_FORCE_SYNCNOACF 4
+
+extern struct physical *physical_Create(struct datalink *, int);
+extern int physical_Open(struct physical *);
+extern int physical_Raw(struct physical *);
+extern unsigned physical_GetSpeed(struct physical *);
+extern int physical_SetSpeed(struct physical *, unsigned);
+extern int physical_SetParity(struct physical *, const char *);
+extern int physical_SetRtsCts(struct physical *, int);
+extern void physical_SetSync(struct physical *);
+extern int physical_ShowStatus(struct cmdargs const *);
+extern void physical_Offline(struct physical *);
+extern void physical_Close(struct physical *);
+extern void physical_Destroy(struct physical *);
+extern struct physical *iov2physical(struct datalink *, struct iovec *, int *,
+ int, int, int *, int *);
+extern int physical2iov(struct physical *, struct iovec *, int *, int, int *,
+ int *);
+extern const char *physical_LockedDevice(struct physical *);
+extern void physical_ChangedPid(struct physical *, pid_t);
+
+extern int physical_IsSync(struct physical *);
+extern u_short physical_DeviceMTU(struct physical *);
+extern const char *physical_GetDevice(struct physical *);
+extern void physical_SetDeviceList(struct physical *, int, const char *const *);
+extern void physical_SetDevice(struct physical *, const char *);
+
+extern ssize_t physical_Read(struct physical *, void *, size_t);
+extern ssize_t physical_Write(struct physical *, const void *, size_t);
+extern int physical_doUpdateSet(struct fdescriptor *, fd_set *, fd_set *,
+ fd_set *, int *, int);
+extern int physical_IsSet(struct fdescriptor *, const fd_set *);
+extern void physical_DescriptorRead(struct fdescriptor *, struct bundle *,
+ const fd_set *);
+extern void physical_Login(struct physical *, const char *);
+extern int physical_RemoveFromSet(struct physical *, fd_set *, fd_set *,
+ fd_set *);
+extern int physical_SetMode(struct physical *, int);
+extern void physical_DeleteQueue(struct physical *);
+extern void physical_SetupStack(struct physical *, const char *, int);
+extern void physical_StopDeviceTimer(struct physical *);
+extern unsigned physical_MaxDeviceSize(void);
+extern int physical_AwaitCarrier(struct physical *);
+extern void physical_SetDescriptor(struct physical *);
+extern void physical_SetAsyncParams(struct physical *, u_int32_t, u_int32_t);
+extern int physical_Slot(struct physical *);
+extern int physical_SetPPPoEnonstandard(struct physical *, int);
diff --git a/usr.sbin/ppp/ppp.8 b/usr.sbin/ppp/ppp.8
new file mode 100644
index 0000000..3fc6b81
--- /dev/null
+++ b/usr.sbin/ppp/ppp.8
@@ -0,0 +1,6079 @@
+.\"
+.\" Copyright (c) 2001 Brian Somers <brian@Awfulhak.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 August 25, 2009
+.Dt PPP 8
+.Os
+.Sh NAME
+.Nm ppp
+.Nd Point to Point Protocol (a.k.a. user-ppp)
+.Sh SYNOPSIS
+.Nm
+.Op Fl Va mode
+.Op Fl nat
+.Op Fl quiet
+.Op Fl unit Ns Ar N
+.Op Ar system ...
+.Sh DESCRIPTION
+This is a user process
+.Em PPP
+software package.
+Sometimes,
+.Em PPP
+is implemented as a part of the kernel (e.g., as managed by
+.Nm pppd )
+and it is thus somewhat hard to debug and/or modify its behaviour.
+However, in this implementation
+.Em PPP
+is done as a user process with the help of the
+tunnel device driver (tun).
+.Pp
+The
+.Fl nat
+flag does the equivalent of a
+.Dq nat enable yes ,
+enabling
+.Nm Ns No 's
+network address translation features.
+This allows
+.Nm
+to act as a NAT or masquerading engine for all machines on an internal
+LAN.
+Refer to
+.Xr libalias 3
+for details on the technical side of the NAT engine.
+Refer to the
+.Sx NETWORK ADDRESS TRANSLATION (PACKET ALIASING)
+section of this manual page for details on how to configure NAT in
+.Nm .
+.Pp
+The
+.Fl quiet
+flag tells
+.Nm
+to be silent at startup rather than displaying the mode and interface
+to standard output.
+.Pp
+The
+.Fl unit
+flag tells
+.Nm
+to only attempt to open
+.Pa /dev/tun Ns Ar N .
+Normally,
+.Nm
+will start with a value of 0 for
+.Ar N ,
+and keep trying to open a tunnel device by incrementing the value of
+.Ar N
+by one each time until it succeeds.
+If it fails three times in a row
+because the device file is missing, it gives up.
+.Pp
+The following
+.Va mode Ns No s
+are understood by
+.Nm :
+.Bl -tag -width XXX -offset XXX
+.It Fl auto
+.Nm
+opens the tun interface, configures it then goes into the background.
+The link is not brought up until outgoing data is detected on the tun
+interface at which point
+.Nm
+attempts to bring up the link.
+Packets received (including the first one) while
+.Nm
+is trying to bring the link up will remain queued for a default of
+2 minutes.
+See the
+.Dq set choked
+command below.
+.Pp
+In
+.Fl auto
+mode, at least one
+.Dq system
+must be given on the command line (see below) and a
+.Dq set ifaddr
+must be done in the system profile that specifies a peer IP address to
+use when configuring the interface.
+Something like
+.Dq 10.0.0.1/0
+is usually appropriate.
+See the
+.Dq pmdemand
+system in
+.Pa /usr/share/examples/ppp/ppp.conf.sample
+for an example.
+.It Fl background
+Here,
+.Nm
+attempts to establish a connection with the peer immediately.
+If it succeeds,
+.Nm
+goes into the background and the parent process returns an exit code
+of 0.
+If it fails,
+.Nm
+exits with a non-zero result.
+.It Fl foreground
+In foreground mode,
+.Nm
+attempts to establish a connection with the peer immediately, but never
+becomes a daemon.
+The link is created in background mode.
+This is useful if you wish to control
+.Nm Ns No 's
+invocation from another process.
+.It Fl direct
+This is used for communicating over an already established connection,
+usually when receiving incoming connections accepted by
+.Xr getty 8 .
+.Nm
+ignores the
+.Dq set device
+line and uses descriptor 0 as the link.
+.Nm
+will also ignore any configured chat scripts unless the
+.Dq force-scripts
+option has been enabled.
+.Pp
+If callback is configured,
+.Nm
+will use the
+.Dq set device
+information when dialing back.
+.Pp
+When run in
+.Fl direct
+mode,
+.Nm
+will behave slightly differently if descriptor 0 was created by
+.Xr pipe 2 .
+As pipes are not bi-directional, ppp will redirect all writes to descriptor
+1 (standard output), leaving only reads acting on descriptor 0.
+No special action is taken if descriptor 0 was created by
+.Xr socketpair 2 .
+.It Fl dedicated
+This option is designed for machines connected with a dedicated
+wire.
+.Nm
+will always keep the device open and will ignore any configured
+chat scripts unless the
+.Dq force-scripts
+option has been enabled.
+.It Fl ddial
+This mode is equivalent to
+.Fl auto
+mode except that
+.Nm
+will bring the link back up any time it is dropped for any reason.
+.It Fl interactive
+This is a no-op, and gives the same behaviour as if none of the above
+modes have been specified.
+.Nm
+loads any sections specified on the command line then provides an
+interactive prompt.
+.El
+.Pp
+One or more configuration entries or systems
+(as specified in
+.Pa /etc/ppp/ppp.conf )
+may also be specified on the command line.
+.Nm
+will read the
+.Dq default
+system from
+.Pa /etc/ppp/ppp.conf
+at startup, followed by each of the systems specified on the command line.
+.Sh Major Features
+.Bl -diag
+.It Provides an interactive user interface.
+Using its command mode, the user can
+easily enter commands to establish the connection with the remote end, check
+the status of connection and close the connection.
+All functions can also be optionally password protected for security.
+.It Supports both manual and automatic dialing.
+Interactive mode has a
+.Dq term
+command which enables you to talk to the device directly.
+When you are connected to the remote peer and it starts to talk
+.Em PPP ,
+.Nm
+detects it and switches to packet mode automatically.
+Once you have
+determined the proper sequence for connecting with the remote host, you
+can write a chat script to {define} the necessary dialing and login
+procedure for later convenience.
+.It Supports on-demand dialup capability.
+By using
+.Fl auto
+mode,
+.Nm
+will act as a daemon and wait for a packet to be sent over the
+.Em PPP
+link.
+When this happens, the daemon automatically dials and establishes the
+connection.
+In almost the same manner
+.Fl ddial
+mode (direct-dial mode) also automatically dials and establishes the
+connection.
+However, it differs in that it will dial the remote site
+any time it detects the link is down, even if there are no packets to be
+sent.
+This mode is useful for full-time connections where we worry less
+about line charges and more about being connected full time.
+A third
+.Fl dedicated
+mode is also available.
+This mode is targeted at a dedicated link between two machines.
+.Nm
+will never voluntarily quit from dedicated mode - you must send it the
+.Dq quit all
+command via its diagnostic socket.
+A
+.Dv SIGHUP
+will force an LCP renegotiation, and a
+.Dv SIGTERM
+will force it to exit.
+.It Supports client callback.
+.Nm
+can use either the standard LCP callback protocol or the Microsoft
+CallBack Control Protocol (ftp://ftp.microsoft.com/developr/rfc/cbcp.txt).
+.It Supports NAT or packet aliasing.
+Packet aliasing (a.k.a.\& IP masquerading) allows computers on a
+private, unregistered network to access the Internet.
+The
+.Em PPP
+host acts as a masquerading gateway.
+IP addresses as well as TCP and
+UDP port numbers are NAT'd for outgoing packets and de-NAT'd for
+returning packets.
+.It Supports background PPP connections.
+In background mode, if
+.Nm
+successfully establishes the connection, it will become a daemon.
+Otherwise, it will exit with an error.
+This allows the setup of
+scripts that wish to execute certain commands only if the connection
+is successfully established.
+.It Supports server-side PPP connections.
+In direct mode,
+.Nm
+acts as server which accepts incoming
+.Em PPP
+connections on stdin/stdout.
+.It Supports PAP and CHAP (rfc 1994, 2433 and 2759) authentication.
+With PAP or CHAP, it is possible to skip the Unix style
+.Xr login 1
+procedure, and use the
+.Em PPP
+protocol for authentication instead.
+If the peer requests Microsoft CHAP authentication and
+.Nm
+is compiled with DES support, an appropriate MD4/DES response will be
+made.
+.It Supports RADIUS (rfc 2138 & 2548) authentication.
+An extension to PAP and CHAP,
+.Em \&R Ns No emote
+.Em \&A Ns No ccess
+.Em \&D Ns No ial
+.Em \&I Ns No n
+.Em \&U Ns No ser
+.Em \&S Ns No ervice
+allows authentication information to be stored in a central or
+distributed database along with various per-user framed connection
+characteristics.
+If
+.Xr libradius 3
+is available at compile time,
+.Nm
+will use it to make
+.Em RADIUS
+requests when configured to do so.
+.It Supports Proxy Arp.
+.Nm
+can be configured to make one or more proxy arp entries on behalf of
+the peer.
+This allows routing from the peer to the LAN without
+configuring each machine on that LAN.
+.It Supports packet filtering.
+User can {define} four kinds of filters: the
+.Em in
+filter for incoming packets, the
+.Em out
+filter for outgoing packets, the
+.Em dial
+filter to {define} a dialing trigger packet and the
+.Em alive
+filter for keeping a connection alive with the trigger packet.
+.It Tunnel driver supports bpf.
+The user can use
+.Xr tcpdump 1
+to check the packet flow over the
+.Em PPP
+link.
+.It Supports PPP over TCP and PPP over UDP.
+If a device name is specified as
+.Em host Ns No : Ns Em port Ns
+.Xo
+.Op / Ns tcp|udp ,
+.Xc
+.Nm
+will open a TCP or UDP connection for transporting data rather than using a
+conventional serial device.
+UDP connections force
+.Nm
+into synchronous mode.
+.It Supports PPP over Ethernet (rfc 2516).
+If
+.Nm
+is given a device specification of the format
+.No PPPoE: Ns Ar iface Ns Xo
+.Op \&: Ns Ar provider Ns
+.Xc
+and if
+.Xr netgraph 4
+is available,
+.Nm
+will attempt talk
+.Em PPP
+over Ethernet to
+.Ar provider
+using the
+.Ar iface
+network interface.
+.Pp
+On systems that do not support
+.Xr netgraph 4 ,
+an external program such as
+.Xr pppoed 8
+may be used.
+.It "Supports IETF draft Predictor-1 (rfc 1978) and DEFLATE (rfc 1979) compression."
+.Nm
+supports not only VJ-compression but also Predictor-1 and DEFLATE compression.
+Normally, a modem has built-in compression (e.g., v42.bis) and the system
+may receive higher data rates from it as a result of such compression.
+While this is generally a good thing in most other situations, this
+higher speed data imposes a penalty on the system by increasing the
+number of serial interrupts the system has to process in talking to the
+modem and also increases latency.
+Unlike VJ-compression, Predictor-1 and DEFLATE compression pre-compresses
+.Em all
+network traffic flowing through the link, thus reducing overheads to a
+minimum.
+.It Supports Microsoft's IPCP extensions (rfc 1877).
+Name Server Addresses and NetBIOS Name Server Addresses can be negotiated
+with clients using the Microsoft
+.Em PPP
+stack (i.e., Win95, WinNT)
+.It Supports Multi-link PPP (rfc 1990)
+It is possible to configure
+.Nm
+to open more than one physical connection to the peer, combining the
+bandwidth of all links for better throughput.
+.It Supports MPPE (draft-ietf-pppext-mppe)
+MPPE is Microsoft Point to Point Encryption scheme.
+It is possible to configure
+.Nm
+to participate in Microsoft's Windows VPN.
+For now,
+.Nm
+can only get encryption keys from CHAP 81 authentication.
+.Nm
+must be compiled with DES for MPPE to operate.
+.It Supports IPV6CP (rfc 2023).
+An IPv6 connection can be made in addition to or instead of the normal
+IPv4 connection.
+.El
+.Sh PERMISSIONS
+.Nm
+is installed as user
+.Dv root
+and group
+.Dv network ,
+with permissions
+.Dv 04554 .
+By default,
+.Nm
+will not run if the invoking user id is not zero.
+This may be overridden by using the
+.Dq allow users
+command in
+.Pa /etc/ppp/ppp.conf .
+When running as a normal user,
+.Nm
+switches to user id 0 in order to alter the system routing table, set up
+system lock files and read the ppp configuration files.
+All external commands (executed via the "shell" or "!bg" commands) are executed
+as the user id that invoked
+.Nm .
+Refer to the
+.Sq ID0
+logging facility if you are interested in what exactly is done as user id
+zero.
+.Sh GETTING STARTED
+When you first run
+.Nm
+you may need to deal with some initial configuration details.
+.Bl -bullet
+.It
+Make sure that your system has a group named
+.Dq network
+in the
+.Pa /etc/group
+file and that the group contains the names of all users expected to use
+.Nm .
+Refer to the
+.Xr group 5
+manual page for details.
+Each of these users must also be given access using the
+.Dq allow users
+command in
+.Pa /etc/ppp/ppp.conf .
+.It
+Create a log file.
+.Nm
+uses
+.Xr syslog 3
+to log information.
+A common log file name is
+.Pa /var/log/ppp.log .
+To make output go to this file, put the following lines in the
+.Pa /etc/syslog.conf
+file:
+.Bd -literal -offset indent
+!ppp
+*.*<TAB>/var/log/ppp.log
+.Ed
+.Pp
+It is possible to have more than one
+.Em PPP
+log file by creating a link to the
+.Nm
+executable:
+.Pp
+.Dl # cd /usr/sbin
+.Dl # ln ppp ppp0
+.Pp
+and using
+.Bd -literal -offset indent
+!ppp0
+*.*<TAB>/var/log/ppp0.log
+.Ed
+.Pp
+in
+.Pa /etc/syslog.conf .
+Do not forget to send a
+.Dv HUP
+signal to
+.Xr syslogd 8
+after altering
+.Pa /etc/syslog.conf .
+.It
+Although not strictly relevant to
+.Nm Ns No 's
+operation, you should configure your resolver so that it works correctly.
+This can be done by configuring a local DNS
+(using
+.Xr named 8 )
+or by adding the correct
+.Sq nameserver
+lines to the file
+.Pa /etc/resolv.conf .
+Refer to the
+.Xr resolv.conf 5
+manual page for details.
+.Pp
+Alternatively, if the peer supports it,
+.Nm
+can be configured to ask the peer for the nameserver address(es) and to
+update
+.Pa /etc/resolv.conf
+automatically.
+Refer to the
+.Dq enable dns
+and
+.Dq resolv
+commands below for details.
+.El
+.Sh MANUAL DIALING
+In the following examples, we assume that your machine name is
+.Dv awfulhak .
+when you invoke
+.Nm
+(see
+.Sx PERMISSIONS
+above) with no arguments, you are presented with a prompt:
+.Bd -literal -offset indent
+ppp ON awfulhak>
+.Ed
+.Pp
+The
+.Sq ON
+part of your prompt should always be in upper case.
+If it is in lower case, it means that you must supply a password using the
+.Dq passwd
+command.
+This only ever happens if you connect to a running version of
+.Nm
+and have not authenticated yourself using the correct password.
+.Pp
+You can start by specifying the device name and speed:
+.Bd -literal -offset indent
+ppp ON awfulhak> set device /dev/cuau0
+ppp ON awfulhak> set speed 38400
+.Ed
+.Pp
+Normally, hardware flow control (CTS/RTS) is used.
+However, under
+certain circumstances (as may happen when you are connected directly
+to certain PPP-capable terminal servers), this may result in
+.Nm
+hanging as soon as it tries to write data to your communications link
+as it is waiting for the CTS (clear to send) signal - which will never
+come.
+Thus, if you have a direct line and cannot seem to make a
+connection, try turning CTS/RTS off with
+.Dq set ctsrts off .
+If you need to do this, check the
+.Dq set accmap
+description below too - you will probably need to
+.Dq set accmap 000a0000 .
+.Pp
+Usually, parity is set to
+.Dq none ,
+and this is
+.Nm Ns No 's
+default.
+Parity is a rather archaic error checking mechanism that is no
+longer used because modern modems do their own error checking, and most
+link-layer protocols (that is what
+.Nm
+is) use much more reliable checking mechanisms.
+Parity has a relatively
+huge overhead (a 12.5% increase in traffic) and as a result, it is always
+disabled
+(set to
+.Dq none )
+when
+.Dv PPP
+is opened.
+However, some ISPs (Internet Service Providers) may use
+specific parity settings at connection time (before
+.Dv PPP
+is opened).
+Notably, Compuserve insist on even parity when logging in:
+.Bd -literal -offset indent
+ppp ON awfulhak> set parity even
+.Ed
+.Pp
+You can now see what your current device settings look like:
+.Bd -literal -offset indent
+ppp ON awfulhak> show physical
+Name: deflink
+ State: closed
+ Device: N/A
+ Link Type: interactive
+ Connect Count: 0
+ Queued Packets: 0
+ Phone Number: N/A
+
+Defaults:
+ Device List: /dev/cuau0
+ Characteristics: 38400bps, cs8, even parity, CTS/RTS on
+
+Connect time: 0 secs
+0 octets in, 0 octets out
+Overall 0 bytes/sec
+ppp ON awfulhak>
+.Ed
+.Pp
+The term command can now be used to talk directly to the device:
+.Bd -literal -offset indent
+ppp ON awfulhak> term
+at
+OK
+atdt123456
+CONNECT
+login: myispusername
+Password: myisppassword
+Protocol: ppp
+.Ed
+.Pp
+When the peer starts to talk in
+.Em PPP ,
+.Nm
+detects this automatically and returns to command mode.
+.Bd -literal -offset indent
+ppp ON awfulhak> # No link has been established
+Ppp ON awfulhak> # We've connected & finished LCP
+PPp ON awfulhak> # We've authenticated
+PPP ON awfulhak> # We've agreed IP numbers
+.Ed
+.Pp
+If it does not, it is probable that the peer is waiting for your end to
+start negotiating.
+To force
+.Nm
+to start sending
+.Em PPP
+configuration packets to the peer, use the
+.Dq ~p
+command to drop out of terminal mode and enter packet mode.
+.Pp
+If you never even receive a login prompt, it is quite likely that the
+peer wants to use PAP or CHAP authentication instead of using Unix-style
+login/password authentication.
+To set things up properly, drop back to
+the prompt and set your authentication name and key, then reconnect:
+.Bd -literal -offset indent
+~.
+ppp ON awfulhak> set authname myispusername
+ppp ON awfulhak> set authkey myisppassword
+ppp ON awfulhak> term
+at
+OK
+atdt123456
+CONNECT
+.Ed
+.Pp
+You may need to tell ppp to initiate negotiations with the peer here too:
+.Bd -literal -offset indent
+~p
+ppp ON awfulhak> # No link has been established
+Ppp ON awfulhak> # We've connected & finished LCP
+PPp ON awfulhak> # We've authenticated
+PPP ON awfulhak> # We've agreed IP numbers
+.Ed
+.Pp
+You are now connected!
+Note that
+.Sq PPP
+in the prompt has changed to capital letters to indicate that you have
+a peer connection.
+If only some of the three Ps go uppercase, wait until
+either everything is uppercase or lowercase.
+If they revert to lowercase, it means that
+.Nm
+could not successfully negotiate with the peer.
+A good first step for troubleshooting at this point would be to
+.Bd -literal -offset indent
+ppp ON awfulhak> set log local phase lcp ipcp
+.Ed
+.Pp
+and try again.
+Refer to the
+.Dq set log
+command description below for further details.
+If things fail at this point,
+it is quite important that you turn logging on and try again.
+It is also
+important that you note any prompt changes and report them to anyone trying
+to help you.
+.Pp
+When the link is established, the show command can be used to see how
+things are going:
+.Bd -literal -offset indent
+PPP ON awfulhak> show physical
+* Modem related information is shown here *
+PPP ON awfulhak> show ccp
+* CCP (compression) related information is shown here *
+PPP ON awfulhak> show lcp
+* LCP (line control) related information is shown here *
+PPP ON awfulhak> show ipcp
+* IPCP (IP) related information is shown here *
+PPP ON awfulhak> show ipv6cp
+* IPV6CP (IPv6) related information is shown here *
+PPP ON awfulhak> show link
+* Link (high level) related information is shown here *
+PPP ON awfulhak> show bundle
+* Logical (high level) connection related information is shown here *
+.Ed
+.Pp
+At this point, your machine has a host route to the peer.
+This means
+that you can only make a connection with the host on the other side
+of the link.
+If you want to add a default route entry (telling your
+machine to send all packets without another routing entry to the other
+side of the
+.Em PPP
+link), enter the following command:
+.Bd -literal -offset indent
+PPP ON awfulhak> add default HISADDR
+.Ed
+.Pp
+The string
+.Sq HISADDR
+represents the IP address of the connected peer.
+If the
+.Dq add
+command fails due to an existing route, you can overwrite the existing
+route using:
+.Bd -literal -offset indent
+PPP ON awfulhak> add! default HISADDR
+.Ed
+.Pp
+This command can also be executed before actually making the connection.
+If a new IP address is negotiated at connection time,
+.Nm
+will update your default route accordingly.
+.Pp
+You can now use your network applications (ping, telnet, ftp, etc.)
+in other windows or terminals on your machine.
+If you wish to reuse the current terminal, you can put
+.Nm
+into the background using your standard shell suspend and background
+commands (usually
+.Dq ^Z
+followed by
+.Dq bg ) .
+.Pp
+Refer to the
+.Sx PPP COMMAND LIST
+section for details on all available commands.
+.Sh AUTOMATIC DIALING
+To use automatic dialing, you must prepare some Dial and Login chat scripts.
+See the example definitions in
+.Pa /usr/share/examples/ppp/ppp.conf.sample
+(the format of
+.Pa /etc/ppp/ppp.conf
+is pretty simple).
+Each line contains one comment, inclusion, label or command:
+.Bl -bullet
+.It
+A line starting with a
+.Pq Dq #
+character is treated as a comment line.
+Leading whitespace are ignored when identifying comment lines.
+.It
+An inclusion is a line beginning with the word
+.Sq {!include} .
+It must have one argument - the file to {include}.
+You may wish to
+.Dq {!include} ~/.ppp.conf
+for compatibility with older versions of
+.Nm .
+.It
+A label name starts in the first column and is followed by
+a colon
+.Pq Dq \&: .
+.It
+A command line must contain a space or tab in the first column.
+.It
+A string starting with the
+.Dq $
+character is substituted with the value of the environment variable by
+the same name.
+Likewise, a string starting with the
+.Dq ~
+character is substituted with the full path to the home directory of
+the user account by the same name, and the
+.Dq ~
+character by itself is substituted with the full path to the home directory
+of the current user.
+If you want to include a literal
+.Dq $
+or
+.Dq ~
+character in a command or argument, enclose them in double quotes, e.g.,
+.Bd -literal -offset indent
+set password "pa$ss~word"
+.Ed
+.El
+.Pp
+The
+.Pa /etc/ppp/ppp.conf
+file should consist of at least a
+.Dq default
+section.
+This section is always executed.
+It should also contain
+one or more sections, named according to their purpose, for example,
+.Dq MyISP
+would represent your ISP, and
+.Dq ppp-in
+would represent an incoming
+.Nm
+configuration.
+You can now specify the destination label name when you invoke
+.Nm .
+Commands associated with the
+.Dq default
+label are executed, followed by those associated with the destination
+label provided.
+When
+.Nm
+is started with no arguments, the
+.Dq default
+section is still executed.
+The load command can be used to manually load a section from the
+.Pa /etc/ppp/ppp.conf
+file:
+.Bd -literal -offset indent
+ppp ON awfulhak> load MyISP
+.Ed
+.Pp
+Note, no action is taken by
+.Nm
+after a section is loaded, whether it is the result of passing a label on
+the command line or using the
+.Dq load
+command.
+Only the commands specified for that label in the configuration
+file are executed.
+However, when invoking
+.Nm
+with the
+.Fl background ,
+.Fl ddial ,
+or
+.Fl dedicated
+switches, the link mode tells
+.Nm
+to establish a connection.
+Refer to the
+.Dq set mode
+command below for further details.
+.Pp
+Once the connection is made, the
+.Sq ppp
+portion of the prompt will change to
+.Sq PPP :
+.Bd -literal -offset indent
+# ppp MyISP
+\&...
+ppp ON awfulhak> dial
+Ppp ON awfulhak>
+PPp ON awfulhak>
+PPP ON awfulhak>
+.Ed
+.Pp
+The Ppp prompt indicates that
+.Nm
+has entered the authentication phase.
+The PPp prompt indicates that
+.Nm
+has entered the network phase.
+The PPP prompt indicates that
+.Nm
+has successfully negotiated a network layer protocol and is in
+a usable state.
+.Pp
+If the
+.Pa /etc/ppp/ppp.linkup
+file is available, its contents are executed
+when the
+.Em PPP
+connection is established.
+See the provided
+.Dq pmdemand
+example in
+.Pa /usr/share/examples/ppp/ppp.conf.sample
+which runs a script in the background after the connection is established
+(refer to the
+.Dq shell
+and
+.Dq bg
+commands below for a description of possible substitution strings).
+Similarly, when a connection is closed, the contents of the
+.Pa /etc/ppp/ppp.linkdown
+file are executed.
+Both of these files have the same format as
+.Pa /etc/ppp/ppp.conf .
+.Pp
+In previous versions of
+.Nm ,
+it was necessary to re-add routes such as the default route in the
+.Pa ppp.linkup
+file.
+.Nm
+supports
+.Sq sticky routes ,
+where all routes that contain the
+.Dv HISADDR ,
+.Dv MYADDR ,
+.Dv HISADDR6
+or
+.Dv MYADDR6
+literals will automatically be updated when the values of these variables
+change.
+.Sh BACKGROUND DIALING
+If you want to establish a connection using
+.Nm
+non-interactively (such as from a
+.Xr crontab 5
+entry or an
+.Xr at 1
+job) you should use the
+.Fl background
+option.
+When
+.Fl background
+is specified,
+.Nm
+attempts to establish the connection immediately.
+If multiple phone
+numbers are specified, each phone number will be tried once.
+If the attempt fails,
+.Nm
+exits immediately with a non-zero exit code.
+If it succeeds, then
+.Nm
+becomes a daemon, and returns an exit status of zero to its caller.
+The daemon exits automatically if the connection is dropped by the
+remote system, or it receives a
+.Dv TERM
+signal.
+.Sh DIAL ON DEMAND
+Demand dialing is enabled with the
+.Fl auto
+or
+.Fl ddial
+options.
+You must also specify the destination label in
+.Pa /etc/ppp/ppp.conf
+to use.
+It must contain the
+.Dq set ifaddr
+command to {define} the remote peers IP address.
+(refer to
+.Pa /usr/share/examples/ppp/ppp.conf.sample )
+.Bd -literal -offset indent
+# ppp -auto pmdemand
+.Ed
+.Pp
+When
+.Fl auto
+or
+.Fl ddial
+is specified,
+.Nm
+runs as a daemon but you can still configure or examine its
+configuration by using the
+.Dq set server
+command in
+.Pa /etc/ppp/ppp.conf ,
+(for example,
+.Dq Li "set server +3000 mypasswd" )
+and connecting to the diagnostic port as follows:
+.Bd -literal -offset indent
+# pppctl 3000 (assuming tun0)
+Password:
+PPP ON awfulhak> show who
+tcp (127.0.0.1:1028) *
+.Ed
+.Pp
+The
+.Dq show who
+command lists users that are currently connected to
+.Nm
+itself.
+If the diagnostic socket is closed or changed to a different
+socket, all connections are immediately dropped.
+.Pp
+In
+.Fl auto
+mode, when an outgoing packet is detected,
+.Nm
+will perform the dialing action (chat script) and try to connect
+with the peer.
+In
+.Fl ddial
+mode, the dialing action is performed any time the line is found
+to be down.
+If the connect fails, the default behaviour is to wait 30 seconds
+and then attempt to connect when another outgoing packet is detected.
+This behaviour can be changed using the
+.Dq set redial
+command:
+.Pp
+.No set redial Ar secs Ns
+.Oo + Ns Ar inc Ns
+.Oo - Ns Ar max Ns Oc Oc Ns
+.Op . Ns Ar next
+.Op Ar attempts
+.Pp
+.Bl -tag -width attempts -compact
+.It Ar secs
+is the number of seconds to wait before attempting
+to connect again.
+If the argument is the literal string
+.Sq Li random ,
+the delay period is a random value between 1 and 30 seconds inclusive.
+.It Ar inc
+is the number of seconds that
+.Ar secs
+should be incremented each time a new dial attempt is made.
+The timeout reverts to
+.Ar secs
+only after a successful connection is established.
+The default value for
+.Ar inc
+is zero.
+.It Ar max
+is the maximum number of times
+.Nm
+should increment
+.Ar secs .
+The default value for
+.Ar max
+is 10.
+.It Ar next
+is the number of seconds to wait before attempting
+to dial the next number in a list of numbers (see the
+.Dq set phone
+command).
+The default is 3 seconds.
+Again, if the argument is the literal string
+.Sq Li random ,
+the delay period is a random value between 1 and 30 seconds.
+.It Ar attempts
+is the maximum number of times to try to connect for each outgoing packet
+that triggers a dial.
+The previous value is unchanged if this parameter is omitted.
+If a value of zero is specified for
+.Ar attempts ,
+.Nm
+will keep trying until a connection is made.
+.El
+.Pp
+So, for example:
+.Bd -literal -offset indent
+set redial 10.3 4
+.Ed
+.Pp
+will attempt to connect 4 times for each outgoing packet that causes
+a dial attempt with a 3 second delay between each number and a 10 second
+delay after all numbers have been tried.
+If multiple phone numbers
+are specified, the total number of attempts is still 4 (it does not
+attempt each number 4 times).
+.Pp
+Alternatively,
+.Bd -literal -offset indent
+set redial 10+10-5.3 20
+.Ed
+.Pp
+tells
+.Nm
+to attempt to connect 20 times.
+After the first attempt,
+.Nm
+pauses for 10 seconds.
+After the next attempt it pauses for 20 seconds
+and so on until after the sixth attempt it pauses for 1 minute.
+The next 14 pauses will also have a duration of one minute.
+If
+.Nm
+connects, disconnects and fails to connect again, the timeout starts again
+at 10 seconds.
+.Pp
+Modifying the dial delay is very useful when running
+.Nm
+in
+.Fl auto
+mode on both ends of the link.
+If each end has the same timeout,
+both ends wind up calling each other at the same time if the link
+drops and both ends have packets queued.
+At some locations, the serial link may not be reliable, and carrier
+may be lost at inappropriate times.
+It is possible to have
+.Nm
+redial should carrier be unexpectedly lost during a session.
+.Bd -literal -offset indent
+set reconnect timeout ntries
+.Ed
+.Pp
+This command tells
+.Nm
+to re-establish the connection
+.Ar ntries
+times on loss of carrier with a pause of
+.Ar timeout
+seconds before each try.
+For example,
+.Bd -literal -offset indent
+set reconnect 3 5
+.Ed
+.Pp
+tells
+.Nm
+that on an unexpected loss of carrier, it should wait
+.Ar 3
+seconds before attempting to reconnect.
+This may happen up to
+.Ar 5
+times before
+.Nm
+gives up.
+The default value of ntries is zero (no reconnect).
+Care should be taken with this option.
+If the local timeout is slightly
+longer than the remote timeout, the reconnect feature will always be
+triggered (up to the given number of times) after the remote side
+times out and hangs up.
+NOTE: In this context, losing too many LQRs constitutes a loss of
+carrier and will trigger a reconnect.
+If the
+.Fl background
+flag is specified, all phone numbers are dialed at most once until
+a connection is made.
+The next number redial period specified with the
+.Dq set redial
+command is honoured, as is the reconnect tries value.
+If your redial
+value is less than the number of phone numbers specified, not all
+the specified numbers will be tried.
+To terminate the program, type
+.Bd -literal -offset indent
+PPP ON awfulhak> close
+ppp ON awfulhak> quit all
+.Ed
+.Pp
+A simple
+.Dq quit
+command will terminate the
+.Xr pppctl 8
+or
+.Xr telnet 1
+connection but not the
+.Nm
+program itself.
+You must use
+.Dq quit all
+to terminate
+.Nm
+as well.
+.Sh RECEIVING INCOMING PPP CONNECTIONS (Method 1)
+To handle an incoming
+.Em PPP
+connection request, follow these steps:
+.Bl -enum
+.It
+Make sure the modem and (optionally)
+.Pa /etc/rc.serial
+is configured correctly.
+.Bl -bullet -compact
+.It
+Use Hardware Handshake (CTS/RTS) for flow control.
+.It
+Modem should be set to NO echo back (ATE0) and NO results string (ATQ1).
+.El
+.It
+Edit
+.Pa /etc/ttys
+to enable a
+.Xr getty 8
+on the port where the modem is attached.
+For example:
+.Pp
+.Dl ttyd1 Qo /usr/libexec/getty std.38400 Qc dialup on secure
+.Pp
+Do not forget to send a
+.Dv HUP
+signal to the
+.Xr init 8
+process to start the
+.Xr getty 8 :
+.Pp
+.Dl # kill -HUP 1
+.Pp
+It is usually also necessary to train your modem to the same DTR speed
+as the getty:
+.Bd -literal -offset indent
+# ppp
+ppp ON awfulhak> set device /dev/cuau1
+ppp ON awfulhak> set speed 38400
+ppp ON awfulhak> term
+deflink: Entering terminal mode on /dev/cuau1
+Type `~?' for help
+at
+OK
+at
+OK
+atz
+OK
+at
+OK
+~.
+ppp ON awfulhak> quit
+.Ed
+.It
+Create a
+.Pa /usr/local/bin/ppplogin
+file with the following contents:
+.Bd -literal -offset indent
+#! /bin/sh
+exec /usr/sbin/ppp -direct incoming
+.Ed
+.Pp
+Direct mode
+.Pq Fl direct
+lets
+.Nm
+work with stdin and stdout.
+You can also use
+.Xr pppctl 8
+to connect to a configured diagnostic port, in the same manner as with
+client-side
+.Nm .
+.Pp
+Here, the
+.Ar incoming
+section must be set up in
+.Pa /etc/ppp/ppp.conf .
+.Pp
+Make sure that the
+.Ar incoming
+section contains the
+.Dq allow users
+command as appropriate.
+.It
+Prepare an account for the incoming user.
+.Bd -literal
+ppp:xxxx:66:66:PPP Login User:/home/ppp:/usr/local/bin/ppplogin
+.Ed
+.Pp
+Refer to the manual entries for
+.Xr adduser 8
+and
+.Xr vipw 8
+for details.
+.It
+Support for IPCP Domain Name Server and NetBIOS Name Server negotiation
+can be enabled using the
+.Dq accept dns
+and
+.Dq set nbns
+commands.
+Refer to their descriptions below.
+.El
+.Sh RECEIVING INCOMING PPP CONNECTIONS (Method 2)
+This method differs in that we use
+.Nm
+to authenticate the connection rather than
+.Xr login 1 :
+.Bl -enum
+.It
+Configure your default section in
+.Pa /etc/gettytab
+with automatic ppp recognition by specifying the
+.Dq pp
+capability:
+.Bd -literal
+default:\\
+ :pp=/usr/local/bin/ppplogin:\\
+ .....
+.Ed
+.It
+Configure your serial device(s), enable a
+.Xr getty 8
+and create
+.Pa /usr/local/bin/ppplogin
+as in the first three steps for method 1 above.
+.It
+Add either
+.Dq enable chap
+or
+.Dq enable pap
+(or both)
+to
+.Pa /etc/ppp/ppp.conf
+under the
+.Sq incoming
+label (or whatever label
+.Pa ppplogin
+uses).
+.It
+Create an entry in
+.Pa /etc/ppp/ppp.secret
+for each incoming user:
+.Bd -literal
+Pfred<TAB>xxxx
+Pgeorge<TAB>yyyy
+.Ed
+.El
+.Pp
+Now, as soon as
+.Xr getty 8
+detects a ppp connection (by recognising the HDLC frame headers), it runs
+.Dq /usr/local/bin/ppplogin .
+.Pp
+It is
+.Em VITAL
+that either PAP or CHAP are enabled as above.
+If they are not, you are
+allowing anybody to establish a ppp session with your machine
+.Em without
+a password, opening yourself up to all sorts of potential attacks.
+.Sh AUTHENTICATING INCOMING CONNECTIONS
+Normally, the receiver of a connection requires that the peer
+authenticates itself.
+This may be done using
+.Xr login 1 ,
+but alternatively, you can use PAP or CHAP.
+CHAP is the more secure of the two, but some clients may not support it.
+Once you decide which you wish to use, add the command
+.Sq enable chap
+or
+.Sq enable pap
+to the relevant section of
+.Pa ppp.conf .
+.Pp
+You must then configure the
+.Pa /etc/ppp/ppp.secret
+file.
+This file contains one line per possible client, each line
+containing up to five fields:
+.Pp
+.Ar name Ar key Oo
+.Ar hisaddr Op Ar label Op Ar callback-number
+.Oc
+.Pp
+The
+.Ar name
+and
+.Ar key
+specify the client username and password.
+If
+.Ar key
+is
+.Dq \&*
+and PAP is being used,
+.Nm
+will look up the password database
+.Pq Xr passwd 5
+when authenticating.
+If the client does not offer a suitable response based on any
+.Ar name Ns No / Ns Ar key
+combination in
+.Pa ppp.secret ,
+authentication fails.
+.Pp
+If authentication is successful,
+.Ar hisaddr
+(if specified)
+is used when negotiating IP numbers.
+See the
+.Dq set ifaddr
+command for details.
+.Pp
+If authentication is successful and
+.Ar label
+is specified, the current system label is changed to match the given
+.Ar label .
+This will change the subsequent parsing of the
+.Pa ppp.linkup
+and
+.Pa ppp.linkdown
+files.
+.Pp
+If authentication is successful and
+.Ar callback-number
+is specified and
+.Dq set callback
+has been used in
+.Pa ppp.conf ,
+the client will be called back on the given number.
+If CBCP is being used,
+.Ar callback-number
+may also contain a list of numbers or a
+.Dq \&* ,
+as if passed to the
+.Dq set cbcp
+command.
+The value will be used in
+.Nm Ns No 's
+subsequent CBCP phase.
+.Sh PPP OVER TCP and UDP (a.k.a Tunnelling)
+Instead of running
+.Nm
+over a serial link, it is possible to
+use a TCP connection instead by specifying the host, port and protocol as the
+device:
+.Pp
+.Dl set device ui-gate:6669/tcp
+.Pp
+Instead of opening a serial device,
+.Nm
+will open a TCP connection to the given machine on the given
+socket.
+It should be noted however that
+.Nm
+does not use the telnet protocol and will be unable to negotiate
+with a telnet server.
+You should set up a port for receiving this
+.Em PPP
+connection on the receiving machine (ui-gate).
+This is done by first updating
+.Pa /etc/services
+to name the service:
+.Pp
+.Dl ppp-in 6669/tcp # Incoming PPP connections over TCP
+.Pp
+and updating
+.Pa /etc/inetd.conf
+to tell
+.Xr inetd 8
+how to deal with incoming connections on that port:
+.Pp
+.Dl ppp-in stream tcp nowait root /usr/sbin/ppp ppp -direct ppp-in
+.Pp
+Do not forget to send a
+.Dv HUP
+signal to
+.Xr inetd 8
+after you have updated
+.Pa /etc/inetd.conf .
+Here, we use a label named
+.Dq ppp-in .
+The entry in
+.Pa /etc/ppp/ppp.conf
+on ui-gate (the receiver) should contain the following:
+.Bd -literal -offset indent
+ppp-in:
+ set timeout 0
+ set ifaddr 10.0.4.1 10.0.4.2
+.Ed
+.Pp
+and the entry in
+.Pa /etc/ppp/ppp.linkup
+should contain:
+.Bd -literal -offset indent
+ppp-in:
+ add 10.0.1.0/24 HISADDR
+.Ed
+.Pp
+It is necessary to put the
+.Dq add
+command in
+.Pa ppp.linkup
+to ensure that the route is only added after
+.Nm
+has negotiated and assigned addresses to its interface.
+.Pp
+You may also want to enable PAP or CHAP for security.
+To enable PAP, add the following line:
+.Bd -literal -offset indent
+ enable PAP
+.Ed
+.Pp
+You will also need to create the following entry in
+.Pa /etc/ppp/ppp.secret :
+.Bd -literal -offset indent
+MyAuthName MyAuthPasswd
+.Ed
+.Pp
+If
+.Ar MyAuthPasswd
+is a
+.Dq * ,
+the password is looked up in the
+.Xr passwd 5
+database.
+.Pp
+The entry in
+.Pa /etc/ppp/ppp.conf
+on awfulhak (the initiator) should contain the following:
+.Bd -literal -offset indent
+ui-gate:
+ set escape 0xff
+ set device ui-gate:ppp-in/tcp
+ set dial
+ set timeout 30
+ set log Phase Chat Connect hdlc LCP IPCP IPV6CP CCP tun
+ set ifaddr 10.0.4.2 10.0.4.1
+.Ed
+.Pp
+with the route setup in
+.Pa /etc/ppp/ppp.linkup :
+.Bd -literal -offset indent
+ui-gate:
+ add 10.0.2.0/24 HISADDR
+.Ed
+.Pp
+Again, if you are enabling PAP, you will also need this in the
+.Pa /etc/ppp/ppp.conf
+profile:
+.Bd -literal -offset indent
+ set authname MyAuthName
+ set authkey MyAuthKey
+.Ed
+.Pp
+We are assigning the address of 10.0.4.1 to ui-gate, and the address
+10.0.4.2 to awfulhak.
+To open the connection, just type
+.Pp
+.Dl awfulhak # ppp -background ui-gate
+.Pp
+The result will be an additional "route" on awfulhak to the
+10.0.2.0/24 network via the TCP connection, and an additional
+"route" on ui-gate to the 10.0.1.0/24 network.
+The networks are effectively bridged - the underlying TCP
+connection may be across a public network (such as the
+Internet), and the
+.Em PPP
+traffic is conceptually encapsulated
+(although not packet by packet) inside the TCP stream between
+the two gateways.
+.Pp
+The major disadvantage of this mechanism is that there are two
+"guaranteed delivery" mechanisms in place - the underlying TCP
+stream and whatever protocol is used over the
+.Em PPP
+link - probably TCP again.
+If packets are lost, both levels will
+get in each others way trying to negotiate sending of the missing
+packet.
+.Pp
+To avoid this overhead, it is also possible to do all this using
+UDP instead of TCP as the transport by simply changing the protocol
+from "tcp" to "udp".
+When using UDP as a transport,
+.Nm
+will operate in synchronous mode.
+This is another gain as the incoming
+data does not have to be rearranged into packets.
+.Pp
+Care should be taken when adding a default route through a tunneled
+setup like this.
+It is quite common for the default route
+(added in
+.Pa /etc/ppp/ppp.linkup )
+to end up routing the link's TCP connection through the tunnel,
+effectively garrotting the connection.
+To avoid this, make sure you add a static route for the benefit of
+the link:
+.Bd -literal -offset indent
+ui-gate:
+ set escape 0xff
+ set device ui-gate:ppp-in/tcp
+ add ui-gate x.x.x.x
+ .....
+.Ed
+.Pp
+where
+.Dq x.x.x.x
+is the IP number that your route to
+.Dq ui-gate
+would normally use.
+.Pp
+When routing your connection across a public network such as the Internet,
+it is preferable to encrypt the data.
+This can be done with the help of the MPPE protocol, although currently this
+means that you will not be able to also compress the traffic as MPPE is
+implemented as a compression layer (thank Microsoft for this).
+To enable MPPE encryption, add the following lines to
+.Pa /etc/ppp/ppp.conf
+on the server:
+.Bd -literal -offset indent
+ enable MSCHAPv2
+ disable deflate pred1
+ deny deflate pred1
+.Ed
+.Pp
+ensuring that you have put the requisite entry in
+.Pa /etc/ppp/ppp.secret
+(MSCHAPv2 is challenge based, so
+.Xr passwd 5
+cannot be used)
+.Pp
+MSCHAPv2 and MPPE are accepted by default, so the client end should work
+without any additional changes (although ensure you have
+.Dq set authname
+and
+.Dq set authkey
+in your profile).
+.Sh NETWORK ADDRESS TRANSLATION (PACKET ALIASING)
+The
+.Fl nat
+command line option enables network address translation (a.k.a.\& packet
+aliasing).
+This allows the
+.Nm
+host to act as a masquerading gateway for other computers over
+a local area network.
+Outgoing IP packets are NAT'd so that they appear to come from the
+.Nm
+host, and incoming packets are de-NAT'd so that they are routed
+to the correct machine on the local area network.
+NAT allows computers on private, unregistered subnets to have Internet
+access, although they are invisible from the outside world.
+In general, correct
+.Nm
+operation should first be verified with network address translation disabled.
+Then, the
+.Fl nat
+option should be switched on, and network applications (web browser,
+.Xr telnet 1 ,
+.Xr ftp 1 ,
+.Xr ping 8 ,
+.Xr traceroute 8 )
+should be checked on the
+.Nm
+host.
+Finally, the same or similar applications should be checked on other
+computers in the LAN.
+If network applications work correctly on the
+.Nm
+host, but not on other machines in the LAN, then the masquerading
+software is working properly, but the host is either not forwarding
+or possibly receiving IP packets.
+Check that IP forwarding is enabled in
+.Pa /etc/rc.conf
+and that other machines have designated the
+.Nm
+host as the gateway for the LAN.
+.Sh PACKET FILTERING
+This implementation supports packet filtering.
+There are four kinds of
+filters: the
+.Em in
+filter, the
+.Em out
+filter, the
+.Em dial
+filter and the
+.Em alive
+filter.
+Here are the basics:
+.Bl -bullet
+.It
+A filter definition has the following syntax:
+.Pp
+set filter
+.Ar name
+.Ar rule-no
+.Ar action
+.Op !\&
+.Oo
+.Op host
+.Ar src_addr Ns Op / Ns Ar width
+.Op Ar dst_addr Ns Op / Ns Ar width
+.Oc
+.Ar [ proto Op src Ar cmp port
+.Op dst Ar cmp port
+.Op estab
+.Op syn
+.Op finrst
+.Op timeout Ar secs ]
+.Bl -enum
+.It
+.Ar Name
+should be one of
+.Sq in ,
+.Sq out ,
+.Sq dial
+or
+.Sq alive .
+.It
+.Ar Rule-no
+is a numeric value between
+.Sq 0
+and
+.Sq 39
+specifying the rule number.
+Rules are specified in numeric order according to
+.Ar rule-no ,
+but only if rule
+.Sq 0
+is defined.
+.It
+.Ar Action
+may be specified as
+.Sq permit
+or
+.Sq deny ,
+in which case, if a given packet matches the rule, the associated action
+is taken immediately.
+.Ar Action
+can also be specified as
+.Sq clear
+to clear the action associated with that particular rule, or as a new
+rule number greater than the current rule.
+In this case, if a given
+packet matches the current rule, the packet will next be matched against
+the new rule number (rather than the next rule number).
+.Pp
+The
+.Ar action
+may optionally be followed with an exclamation mark
+.Pq Dq !\& ,
+telling
+.Nm
+to reverse the sense of the following match.
+.It
+.Op Ar src_addr Ns Op / Ns Ar width
+and
+.Op Ar dst_addr Ns Op / Ns Ar width
+are the source and destination IP number specifications.
+If
+.Op / Ns Ar width
+is specified, it gives the number of relevant netmask bits,
+allowing the specification of an address range.
+.Pp
+Either
+.Ar src_addr
+or
+.Ar dst_addr
+may be given the values
+.Dv MYADDR ,
+.Dv HISADDR ,
+.Dv MYADDR6
+or
+.Dv HISADDR6
+(refer to the description of the
+.Dq bg
+command for a description of these values).
+When these values are used,
+the filters will be updated any time the values change.
+This is similar to the behaviour of the
+.Dq add
+command below.
+.It
+.Ar Proto
+may be any protocol from
+.Xr protocols 5 .
+.It
+.Ar Cmp
+is one of
+.Sq \&lt ,
+.Sq \&eq
+or
+.Sq \&gt ,
+meaning less-than, equal and greater-than respectively.
+.Ar Port
+can be specified as a numeric port or by service name from
+.Pa /etc/services .
+.It
+The
+.Sq estab ,
+.Sq syn ,
+and
+.Sq finrst
+flags are only allowed when
+.Ar proto
+is set to
+.Sq tcp ,
+and represent the TH_ACK, TH_SYN and TH_FIN or TH_RST TCP flags respectively.
+.It
+The timeout value adjusts the current idle timeout to at least
+.Ar secs
+seconds.
+If a timeout is given in the alive filter as well as in the in/out
+filter, the in/out value is used.
+If no timeout is given, the default timeout (set using
+.Ic set timeout
+and defaulting to 180 seconds) is used.
+.El
+.It
+Each filter can hold up to 40 rules, starting from rule 0.
+The entire rule set is not effective until rule 0 is defined,
+i.e., the default is to allow everything through.
+.It
+If no rule in a defined set of rules matches a packet, that packet will
+be discarded (blocked).
+If there are no rules in a given filter, the packet will be permitted.
+.It
+It is possible to filter based on the payload of UDP frames where those
+frames contain a
+.Em PROTO_IP
+.Em PPP
+frame header.
+See the
+.Ar filter-decapsulation
+option below for further details.
+.It
+Use
+.Dq set filter Ar name No -1
+to flush all rules.
+.El
+.Pp
+See
+.Pa /usr/share/examples/ppp/ppp.conf.sample .
+.Sh SETTING THE IDLE TIMER
+To check/set the idle timer, use the
+.Dq show bundle
+and
+.Dq set timeout
+commands:
+.Bd -literal -offset indent
+ppp ON awfulhak> set timeout 600
+.Ed
+.Pp
+The timeout period is measured in seconds, the default value for which
+is 180 seconds
+(or 3 min).
+To disable the idle timer function, use the command
+.Bd -literal -offset indent
+ppp ON awfulhak> set timeout 0
+.Ed
+.Pp
+In
+.Fl ddial
+and
+.Fl dedicated
+modes, the idle timeout is ignored.
+In
+.Fl auto
+mode, when the idle timeout causes the
+.Em PPP
+session to be
+closed, the
+.Nm
+program itself remains running.
+Another trigger packet will cause it to attempt to re-establish the link.
+.Sh PREDICTOR-1 and DEFLATE COMPRESSION
+.Nm
+supports both Predictor type 1 and deflate compression.
+By default,
+.Nm
+will attempt to use (or be willing to accept) both compression protocols
+when the peer agrees
+(or requests them).
+The deflate protocol is preferred by
+.Nm .
+Refer to the
+.Dq disable
+and
+.Dq deny
+commands if you wish to disable this functionality.
+.Pp
+It is possible to use a different compression algorithm in each direction
+by using only one of
+.Dq disable deflate
+and
+.Dq deny deflate
+(assuming that the peer supports both algorithms).
+.Pp
+By default, when negotiating DEFLATE,
+.Nm
+will use a window size of 15.
+Refer to the
+.Dq set deflate
+command if you wish to change this behaviour.
+.Pp
+A special algorithm called DEFLATE24 is also available, and is disabled
+and denied by default.
+This is exactly the same as DEFLATE except that
+it uses CCP ID 24 to negotiate.
+This allows
+.Nm
+to successfully negotiate DEFLATE with
+.Nm pppd
+version 2.3.*.
+.Sh CONTROLLING IP ADDRESS
+For IPv4,
+.Nm
+uses IPCP to negotiate IP addresses.
+Each side of the connection
+specifies the IP address that it is willing to use, and if the requested
+IP address is acceptable then
+.Nm
+returns an ACK to the requester.
+Otherwise,
+.Nm
+returns NAK to suggest that the peer use a different IP address.
+When
+both sides of the connection agree to accept the received request (and
+send an ACK), IPCP is set to the open state and a network level connection
+is established.
+To control this IPCP behaviour, this implementation has the
+.Dq set ifaddr
+command for defining the local and remote IP address:
+.Bd -ragged -offset indent
+.No set ifaddr Oo Ar src_addr Ns
+.Op / Ns Ar \&nn
+.Oo Ar dst_addr Ns Op / Ns Ar \&nn
+.Oo Ar netmask
+.Op Ar trigger_addr
+.Oc
+.Oc
+.Oc
+.Ed
+.Pp
+where,
+.Sq src_addr
+is the IP address that the local side is willing to use,
+.Sq dst_addr
+is the IP address which the remote side should use and
+.Sq netmask
+is the netmask that should be used.
+.Sq Src_addr
+defaults to the current
+.Xr hostname 1 ,
+.Sq dst_addr
+defaults to 0.0.0.0, and
+.Sq netmask
+defaults to whatever mask is appropriate for
+.Sq src_addr .
+It is only possible to make
+.Sq netmask
+smaller than the default.
+The usual value is 255.255.255.255, as
+most kernels ignore the netmask of a POINTOPOINT interface.
+.Pp
+Some incorrect
+.Em PPP
+implementations require that the peer negotiates a specific IP
+address instead of
+.Sq src_addr .
+If this is the case,
+.Sq trigger_addr
+may be used to specify this IP number.
+This will not affect the
+routing table unless the other side agrees with this proposed number.
+.Bd -literal -offset indent
+set ifaddr 192.244.177.38 192.244.177.2 255.255.255.255 0.0.0.0
+.Ed
+.Pp
+The above specification means:
+.Pp
+.Bl -bullet -compact
+.It
+I will first suggest that my IP address should be 0.0.0.0, but I
+will only accept an address of 192.244.177.38.
+.It
+I strongly insist that the peer uses 192.244.177.2 as his own
+address and will not permit the use of any IP address but 192.244.177.2.
+When the peer requests another IP address, I will always suggest that
+it uses 192.244.177.2.
+.It
+The routing table entry will have a netmask of 0xffffffff.
+.El
+.Pp
+This is all fine when each side has a pre-determined IP address, however
+it is often the case that one side is acting as a server which controls
+all IP addresses and the other side should go along with it.
+In order to allow more flexible behaviour, the
+.Dq set ifaddr
+command allows the user to specify IP addresses more loosely:
+.Pp
+.Dl set ifaddr 192.244.177.38/24 192.244.177.2/20
+.Pp
+A number followed by a slash
+.Pq Dq /
+represents the number of bits significant in the IP address.
+The above example means:
+.Pp
+.Bl -bullet -compact
+.It
+I would like to use 192.244.177.38 as my address if it is possible, but I will
+also accept any IP address between 192.244.177.0 and 192.244.177.255.
+.It
+I would like to make him use 192.244.177.2 as his own address, but I will also
+permit him to use any IP address between 192.244.176.0 and
+192.244.191.255.
+.It
+As you may have already noticed, 192.244.177.2 is equivalent to saying
+192.244.177.2/32.
+.It
+As an exception, 0 is equivalent to 0.0.0.0/0, meaning that I have no
+preferred IP address and will obey the remote peers selection.
+When using zero, no routing table entries will be made until a connection
+is established.
+.It
+192.244.177.2/0 means that I will accept/permit any IP address but I will
+suggest that 192.244.177.2 be used first.
+.El
+.Pp
+When negotiating IPv6 addresses, no control is given to the user.
+IPV6CP negotiation is fully automatic.
+.Sh CONNECTING WITH YOUR INTERNET SERVICE PROVIDER
+The following steps should be taken when connecting to your ISP:
+.Bl -enum
+.It
+Describe your providers phone number(s) in the dial script using the
+.Dq set phone
+command.
+This command allows you to set multiple phone numbers for
+dialing and redialing separated by either a pipe
+.Pq Dq \&|
+or a colon
+.Pq Dq \&: :
+.Bd -ragged -offset indent
+.No set phone Ar telno Ns
+.Oo \&| Ns Ar backupnumber Oc Ns ... Ns Oo : Ns Ar nextnumber Oc Ns ...
+.Ed
+.Pp
+Numbers after the first in a pipe-separated list are only used if the
+previous number was used in a failed dial or login script.
+Numbers
+separated by a colon are used sequentially, irrespective of what happened
+as a result of using the previous number.
+For example:
+.Bd -literal -offset indent
+set phone "1234567|2345678:3456789|4567890"
+.Ed
+.Pp
+Here, the 1234567 number is attempted.
+If the dial or login script fails,
+the 2345678 number is used next time, but *only* if the dial or login script
+fails.
+On the dial after this, the 3456789 number is used.
+The 4567890
+number is only used if the dial or login script using the 3456789 fails.
+If the login script of the 2345678 number fails, the next number is still the
+3456789 number.
+As many pipes and colons can be used as are necessary
+(although a given site would usually prefer to use either the pipe or the
+colon, but not both).
+The next number redial timeout is used between all numbers.
+When the end of the list is reached, the normal redial period is
+used before starting at the beginning again.
+The selected phone number is substituted for the \\\\T string in the
+.Dq set dial
+command (see below).
+.It
+Set up your redial requirements using
+.Dq set redial .
+For example, if you have a bad telephone line or your provider is
+usually engaged (not so common these days), you may want to specify
+the following:
+.Bd -literal -offset indent
+set redial 10 4
+.Ed
+.Pp
+This says that up to 4 phone calls should be attempted with a pause of 10
+seconds before dialing the first number again.
+.It
+Describe your login procedure using the
+.Dq set dial
+and
+.Dq set login
+commands.
+The
+.Dq set dial
+command is used to talk to your modem and establish a link with your
+ISP, for example:
+.Bd -literal -offset indent
+set dial "ABORT BUSY ABORT NO\\\\sCARRIER TIMEOUT 4 \\"\\" \e
+ ATZ OK-ATZ-OK ATDT\\\\T TIMEOUT 60 CONNECT"
+.Ed
+.Pp
+This modem "chat" string means:
+.Bl -bullet
+.It
+Abort if the string "BUSY" or "NO CARRIER" are received.
+.It
+Set the timeout to 4 seconds.
+.It
+Expect nothing.
+.It
+Send ATZ.
+.It
+Expect OK.
+If that is not received within the 4 second timeout, send ATZ
+and expect OK.
+.It
+Send ATDTxxxxxxx where xxxxxxx is the next number in the phone list from
+above.
+.It
+Set the timeout to 60.
+.It
+Wait for the CONNECT string.
+.El
+.Pp
+Once the connection is established, the login script is executed.
+This script is written in the same style as the dial script, but care should
+be taken to avoid having your password logged:
+.Bd -literal -offset indent
+set authkey MySecret
+set login "TIMEOUT 15 login:-\\\\r-login: awfulhak \e
+ word: \\\\P ocol: PPP HELLO"
+.Ed
+.Pp
+This login "chat" string means:
+.Bl -bullet
+.It
+Set the timeout to 15 seconds.
+.It
+Expect "login:".
+If it is not received, send a carriage return and expect
+"login:" again.
+.It
+Send "awfulhak"
+.It
+Expect "word:" (the tail end of a "Password:" prompt).
+.It
+Send whatever our current
+.Ar authkey
+value is set to.
+.It
+Expect "ocol:" (the tail end of a "Protocol:" prompt).
+.It
+Send "PPP".
+.It
+Expect "HELLO".
+.El
+.Pp
+The
+.Dq set authkey
+command is logged specially.
+When
+.Ar command
+or
+.Ar chat
+logging is enabled, the actual password is not logged;
+.Sq ********
+is logged instead.
+.Pp
+Login scripts vary greatly between ISPs.
+If you are setting one up for the first time,
+.Em ENABLE CHAT LOGGING
+so that you can see if your script is behaving as you expect.
+.It
+Use
+.Dq set device
+and
+.Dq set speed
+to specify your serial line and speed, for example:
+.Bd -literal -offset indent
+set device /dev/cuau0
+set speed 115200
+.Ed
+.Pp
+Cuad0 is the first serial port on
+.Fx .
+If you are running
+.Nm
+on
+.Ox ,
+cua00 is the first.
+A speed of 115200 should be specified
+if you have a modem capable of bit rates of 28800 or more.
+In general, the serial speed should be about four times the modem speed.
+.It
+Use the
+.Dq set ifaddr
+command to {define} the IP address.
+.Bl -bullet
+.It
+If you know what IP address your provider uses, then use it as the remote
+address (dst_addr), otherwise choose something like 10.0.0.2/0 (see below).
+.It
+If your provider has assigned a particular IP address to you, then use
+it as your address (src_addr).
+.It
+If your provider assigns your address dynamically, choose a suitably
+unobtrusive and unspecific IP number as your address.
+10.0.0.1/0 would be appropriate.
+The bit after the / specifies how many bits of the
+address you consider to be important, so if you wanted to insist on
+something in the class C network 1.2.3.0, you could specify 1.2.3.1/24.
+.It
+If you find that your ISP accepts the first IP number that you suggest,
+specify third and forth arguments of
+.Dq 0.0.0.0 .
+This will force your ISP to assign a number.
+(The third argument will
+be ignored as it is less restrictive than the default mask for your
+.Sq src_addr ) .
+.El
+.Pp
+An example for a connection where you do not know your IP number or your
+ISPs IP number would be:
+.Bd -literal -offset indent
+set ifaddr 10.0.0.1/0 10.0.0.2/0 0.0.0.0 0.0.0.0
+.Ed
+.It
+In most cases, your ISP will also be your default router.
+If this is the case, add the line
+.Bd -literal -offset indent
+add default HISADDR
+.Ed
+.Pp
+to
+.Pa /etc/ppp/ppp.conf
+(or to
+.Pa /etc/ppp/ppp.linkup
+for setups that do not use
+.Fl auto
+mode).
+.Pp
+This tells
+.Nm
+to add a default route to whatever the peer address is
+(10.0.0.2 in this example).
+This route is
+.Sq sticky ,
+meaning that should the value of
+.Dv HISADDR
+change, the route will be updated accordingly.
+.It
+If your provider requests that you use PAP/CHAP authentication methods, add
+the next lines to your
+.Pa /etc/ppp/ppp.conf
+file:
+.Bd -literal -offset indent
+set authname MyName
+set authkey MyPassword
+.Ed
+.Pp
+Both are accepted by default, so
+.Nm
+will provide whatever your ISP requires.
+.Pp
+It should be noted that a login script is rarely (if ever) required
+when PAP or CHAP are in use.
+.It
+Ask your ISP to authenticate your nameserver address(es) with the line
+.Bd -literal -offset indent
+enable dns
+.Ed
+.Pp
+Do
+.Em NOT
+do this if you are running a local DNS unless you also either use
+.Dq resolv readonly
+or have
+.Dq resolv restore
+in
+.Pa /etc/ppp/ppp.linkdown ,
+as
+.Nm
+will simply circumvent its use by entering some nameserver lines in
+.Pa /etc/resolv.conf .
+.El
+.Pp
+Please refer to
+.Pa /usr/share/examples/ppp/ppp.conf.sample
+and
+.Pa /usr/share/examples/ppp/ppp.linkup.sample
+for some real examples.
+The pmdemand label should be appropriate for most ISPs.
+.Sh LOGGING FACILITY
+.Nm
+is able to generate the following log info either via
+.Xr syslog 3
+or directly to the screen:
+.Pp
+.Bl -tag -width XXXXXXXXX -offset XXX -compact
+.It Li All
+Enable all logging facilities.
+This generates a lot of log.
+The most common use of 'all' is as a basis, where you remove some facilities
+after enabling 'all' ('debug' and 'timer' are usually best disabled.)
+.It Li Async
+Dump async level packet in hex.
+.It Li CBCP
+Generate CBCP (CallBack Control Protocol) logs.
+.It Li CCP
+Generate a CCP packet trace.
+.It Li Chat
+Generate
+.Sq dial ,
+.Sq login ,
+.Sq logout
+and
+.Sq hangup
+chat script trace logs.
+.It Li Command
+Log commands executed either from the command line or any of the configuration
+files.
+.It Li Connect
+Log Chat lines containing the string "CONNECT".
+.It Li Debug
+Log debug information.
+.It Li DNS
+Log DNS QUERY packets.
+.It Li Filter
+Log packets permitted by the dial filter and denied by any filter.
+.It Li HDLC
+Dump HDLC packet in hex.
+.It Li ID0
+Log all function calls specifically made as user id 0.
+.It Li IPCP
+Generate an IPCP packet trace.
+.It Li LCP
+Generate an LCP packet trace.
+.It Li LQM
+Generate LQR reports.
+.It Li Phase
+Phase transition log output.
+.It Li Physical
+Dump physical level packet in hex.
+.It Li Radius
+Dump RADIUS information.
+RADIUS information resulting from the link coming up or down is logged at
+.Dq Phase
+level unless
+.Dq Radius
+logging is enabled.
+This log level is most useful for monitoring RADIUS alive information.
+.It Li Sync
+Dump sync level packet in hex.
+.It Li TCP/IP
+Dump all TCP/IP packets.
+.It Li Timer
+Log timer manipulation.
+.It Li TUN
+Include the tun device on each log line.
+.It Li Warning
+Output to the terminal device.
+If there is currently no terminal,
+output is sent to the log file using syslogs
+.Dv LOG_WARNING .
+.It Li Error
+Output to both the terminal device
+and the log file using syslogs
+.Dv LOG_ERROR .
+.It Li Alert
+Output to the log file using
+.Dv LOG_ALERT .
+.El
+.Pp
+The
+.Dq set log
+command allows you to set the logging output level.
+Multiple levels can be specified on a single command line.
+The default is equivalent to
+.Dq set log Phase .
+.Pp
+It is also possible to log directly to the screen.
+The syntax is the same except that the word
+.Dq local
+should immediately follow
+.Dq set log .
+The default is
+.Dq set log local
+(i.e., only the un-maskable warning, error and alert output).
+.Pp
+If The first argument to
+.Dq set log Op local
+begins with a
+.Sq +
+or a
+.Sq -
+character, the current log levels are
+not cleared, for example:
+.Bd -literal -offset indent
+PPP ON awfulhak> set log phase
+PPP ON awfulhak> show log
+Log: Phase Warning Error Alert
+Local: Warning Error Alert
+PPP ON awfulhak> set log +tcp/ip -warning
+PPP ON awfulhak> set log local +command
+PPP ON awfulhak> show log
+Log: Phase TCP/IP Warning Error Alert
+Local: Command Warning Error Alert
+.Ed
+.Pp
+Log messages of level Warning, Error and Alert are not controllable
+using
+.Dq set log Op local .
+.Pp
+The
+.Ar Warning
+level is special in that it will not be logged if it can be displayed
+locally.
+.Sh SIGNAL HANDLING
+.Nm
+deals with the following signals:
+.Bl -tag -width "USR2"
+.It INT
+Receipt of this signal causes the termination of the current connection
+(if any).
+This will cause
+.Nm
+to exit unless it is in
+.Fl auto
+or
+.Fl ddial
+mode.
+.It HUP, TERM & QUIT
+These signals tell
+.Nm
+to exit.
+.It USR1
+This signal, tells
+.Nm
+to re-open any existing server socket, dropping all existing diagnostic
+connections.
+Sockets that could not previously be opened will be retried.
+.It USR2
+This signal, tells
+.Nm
+to close any existing server socket, dropping all existing diagnostic
+connections.
+.Dv SIGUSR1
+can still be used to re-open the socket.
+.El
+.Sh MULTI-LINK PPP
+If you wish to use more than one physical link to connect to a
+.Em PPP
+peer, that peer must also understand the
+.Em MULTI-LINK PPP
+protocol.
+Refer to RFC 1990 for specification details.
+.Pp
+The peer is identified using a combination of his
+.Dq endpoint discriminator
+and his
+.Dq authentication id .
+Either or both of these may be specified.
+It is recommended that
+at least one is specified, otherwise there is no way of ensuring that
+all links are actually connected to the same peer program, and some
+confusing lock-ups may result.
+Locally, these identification variables are specified using the
+.Dq set enddisc
+and
+.Dq set authname
+commands.
+The
+.Sq authname
+(and
+.Sq authkey )
+must be agreed in advance with the peer.
+.Pp
+Multi-link capabilities are enabled using the
+.Dq set mrru
+command (set maximum reconstructed receive unit).
+Once multi-link is enabled,
+.Nm
+will attempt to negotiate a multi-link connection with the peer.
+.Pp
+By default, only one
+.Sq link
+is available
+(called
+.Sq deflink ) .
+To create more links, the
+.Dq clone
+command is used.
+This command will clone existing links, where all
+characteristics are the same except:
+.Bl -enum
+.It
+The new link has its own name as specified on the
+.Dq clone
+command line.
+.It
+The new link is an
+.Sq interactive
+link.
+Its mode may subsequently be changed using the
+.Dq set mode
+command.
+.It
+The new link is in a
+.Sq closed
+state.
+.El
+.Pp
+A summary of all available links can be seen using the
+.Dq show links
+command.
+.Pp
+Once a new link has been created, command usage varies.
+All link specific commands must be prefixed with the
+.Dq link Ar name
+command, specifying on which link the command is to be applied.
+When only a single link is available,
+.Nm
+is smart enough not to require the
+.Dq link Ar name
+prefix.
+.Pp
+Some commands can still be used without specifying a link - resulting
+in an operation at the
+.Sq bundle
+level.
+For example, once two or more links are available, the command
+.Dq show ccp
+will show CCP configuration and statistics at the multi-link level, and
+.Dq link deflink show ccp
+will show the same information at the
+.Dq deflink
+link level.
+.Pp
+Armed with this information, the following configuration might be used:
+.Bd -literal -offset indent
+mp:
+ set timeout 0
+ set log phase chat
+ set device /dev/cuau0 /dev/cuau1 /dev/cuau2
+ set phone "123456789"
+ set dial "ABORT BUSY ABORT NO\\sCARRIER TIMEOUT 5 \\"\\" ATZ \e
+ OK-AT-OK \\\\dATDT\\\\T TIMEOUT 45 CONNECT"
+ set login
+ set ifaddr 10.0.0.1/0 10.0.0.2/0 0.0.0.0 0.0.0.0
+ set authname ppp
+ set authkey ppppassword
+
+ set mrru 1500
+ clone 1,2,3 # Create 3 new links - duplicates of the default
+ link deflink remove # Delete the default link (called ``deflink'')
+.Ed
+.Pp
+Note how all cloning is done at the end of the configuration.
+Usually, the link will be configured first, then cloned.
+If you wish all links
+to be up all the time, you can add the following line to the end of your
+configuration.
+.Bd -literal -offset indent
+ link 1,2,3 set mode ddial
+.Ed
+.Pp
+If you want the links to dial on demand, this command could be used:
+.Bd -literal -offset indent
+ link * set mode auto
+.Ed
+.Pp
+Links may be tied to specific names by removing the
+.Dq set device
+line above, and specifying the following after the
+.Dq clone
+command:
+.Bd -literal -offset indent
+ link 1 set device /dev/cuau0
+ link 2 set device /dev/cuau1
+ link 3 set device /dev/cuau2
+.Ed
+.Pp
+Use the
+.Dq help
+command to see which commands require context (using the
+.Dq link
+command), which have optional
+context and which should not have any context.
+.Pp
+When
+.Nm
+has negotiated
+.Em MULTI-LINK
+mode with the peer, it creates a local domain socket in the
+.Pa /var/run
+directory.
+This socket is used to pass link information (including
+the actual link file descriptor) between different
+.Nm
+invocations.
+This facilitates
+.Nm Ns No 's
+ability to be run from a
+.Xr getty 8
+or directly from
+.Pa /etc/gettydefs
+(using the
+.Sq pp=
+capability), without needing to have initial control of the serial
+line.
+Once
+.Nm
+negotiates multi-link mode, it will pass its open link to any
+already running process.
+If there is no already running process,
+.Nm
+will act as the master, creating the socket and listening for new
+connections.
+.Sh PPP COMMAND LIST
+This section lists the available commands and their effect.
+They are usable either from an interactive
+.Nm
+session, from a configuration file or from a
+.Xr pppctl 8
+or
+.Xr telnet 1
+session.
+.Bl -tag -width 2n
+.It accept|deny|enable|disable Ar option....
+These directives tell
+.Nm
+how to negotiate the initial connection with the peer.
+Each
+.Dq option
+has a default of either accept or deny and enable or disable.
+.Dq Accept
+means that the option will be ACK'd if the peer asks for it.
+.Dq Deny
+means that the option will be NAK'd if the peer asks for it.
+.Dq Enable
+means that the option will be requested by us.
+.Dq Disable
+means that the option will not be requested by us.
+.Pp
+.Dq Option
+may be one of the following:
+.Bl -tag -width 2n
+.It acfcomp
+Default: Enabled and Accepted.
+ACFComp stands for Address and Control Field Compression.
+Non LCP packets will usually have an address
+field of 0xff (the All-Stations address) and a control field of
+0x03 (the Unnumbered Information command).
+If this option is
+negotiated, these two bytes are simply not sent, thus minimising
+traffic.
+.Pp
+See
+.Pa rfc1662
+for details.
+.It chap Ns Op \&05
+Default: Disabled and Accepted.
+CHAP stands for Challenge Handshake Authentication Protocol.
+Only one of CHAP and PAP (below) may be negotiated.
+With CHAP, the authenticator sends a "challenge" message to its peer.
+The peer uses a one-way hash function to encrypt the
+challenge and sends the result back.
+The authenticator does the same, and compares the results.
+The advantage of this mechanism is that no
+passwords are sent across the connection.
+A challenge is made when the connection is first made.
+Subsequent challenges may occur.
+If you want to have your peer authenticate itself, you must
+.Dq enable chap .
+in
+.Pa /etc/ppp/ppp.conf ,
+and have an entry in
+.Pa /etc/ppp/ppp.secret
+for the peer.
+.Pp
+When using CHAP as the client, you need only specify
+.Dq AuthName
+and
+.Dq AuthKey
+in
+.Pa /etc/ppp/ppp.conf .
+CHAP is accepted by default.
+Some
+.Em PPP
+implementations use "MS-CHAP" rather than MD5 when encrypting the
+challenge.
+MS-CHAP is a combination of MD4 and DES.
+If
+.Nm
+was built on a machine with DES libraries available, it will respond
+to MS-CHAP authentication requests, but will never request them.
+.It deflate
+Default: Enabled and Accepted.
+This option decides if deflate
+compression will be used by the Compression Control Protocol (CCP).
+This is the same algorithm as used by the
+.Xr gzip 1
+program.
+Note: There is a problem negotiating
+.Ar deflate
+capabilities with
+.Nm pppd
+- a
+.Em PPP
+implementation available under many operating systems.
+.Nm pppd
+(version 2.3.1) incorrectly attempts to negotiate
+.Ar deflate
+compression using type
+.Em 24
+as the CCP configuration type rather than type
+.Em 26
+as specified in
+.Pa rfc1979 .
+Type
+.Ar 24
+is actually specified as
+.Dq PPP Magna-link Variable Resource Compression
+in
+.Pa rfc1975 !
+.Nm
+is capable of negotiating with
+.Nm pppd ,
+but only if
+.Dq deflate24
+is
+.Ar enable Ns No d
+and
+.Ar accept Ns No ed .
+.It deflate24
+Default: Disabled and Denied.
+This is a variance of the
+.Ar deflate
+option, allowing negotiation with the
+.Nm pppd
+program.
+Refer to the
+.Ar deflate
+section above for details.
+It is disabled by default as it violates
+.Pa rfc1975 .
+.It dns
+Default: Disabled and Denied.
+This option allows DNS negotiation.
+.Pp
+If
+.Dq enable Ns No d,
+.Nm
+will request that the peer confirms the entries in
+.Pa /etc/resolv.conf .
+If the peer NAKs our request (suggesting new IP numbers),
+.Pa /etc/resolv.conf
+is updated and another request is sent to confirm the new entries.
+.Pp
+If
+.Dq accept Ns No ed,
+.Nm
+will answer any DNS queries requested by the peer rather than rejecting
+them.
+The answer is taken from
+.Pa /etc/resolv.conf
+unless the
+.Dq set dns
+command is used as an override.
+.It enddisc
+Default: Enabled and Accepted.
+This option allows control over whether we
+negotiate an endpoint discriminator.
+We only send our discriminator if
+.Dq set enddisc
+is used and
+.Ar enddisc
+is enabled.
+We reject the peers discriminator if
+.Ar enddisc
+is denied.
+.It LANMan|chap80lm
+Default: Disabled and Accepted.
+The use of this authentication protocol
+is discouraged as it partially violates the authentication protocol by
+implementing two different mechanisms (LANMan & NT) under the guise of
+a single CHAP type (0x80).
+.Dq LANMan
+uses a simple DES encryption mechanism and is the least secure of the
+CHAP alternatives (although is still more secure than PAP).
+.Pp
+Refer to the
+.Dq MSChap
+description below for more details.
+.It lqr
+Default: Disabled and Accepted.
+This option decides if Link Quality Requests will be sent or accepted.
+LQR is a protocol that allows
+.Nm
+to determine that the link is down without relying on the modems
+carrier detect.
+When LQR is enabled,
+.Nm
+sends the
+.Em QUALPROTO
+option (see
+.Dq set lqrperiod
+below) as part of the LCP request.
+If the peer agrees, both sides will
+exchange LQR packets at the agreed frequency, allowing detailed link
+quality monitoring by enabling LQM logging.
+If the peer does not agree, and if the
+.Dq echo
+option is enabled,
+.Nm
+will send
+.Em LCP ECHO
+requests instead.
+These packets pass no information of interest, but they
+.Em MUST
+be replied to by the peer.
+.Pp
+Whether using
+.Em LQR
+or
+.Em LCP ECHO ,
+.Nm
+will abruptly drop the connection if 5 unacknowledged packets have been
+sent rather than sending a 6th.
+A message is logged at the
+.Em PHASE
+level, and any appropriate
+.Dq reconnect
+values are honoured as if the peer were responsible for dropping the
+connection.
+.Pp
+Refer to the
+.Dq enable echo
+command description for differences in behaviour prior to
+.Nm
+version 3.4.2.
+.It mppe
+Default: Enabled and Accepted.
+This is Microsoft Point to Point Encryption scheme.
+MPPE key size can be
+40-, 56- and 128-bits.
+Refer to
+.Dq set mppe
+command.
+.It MSChapV2|chap81
+Default: Disabled and Accepted.
+It is very similar to standard CHAP (type 0x05)
+except that it issues challenges of a fixed 16 bytes in length and uses a
+combination of MD4, SHA-1 and DES to encrypt the challenge rather than using the
+standard MD5 mechanism.
+.It MSChap|chap80nt
+Default: Disabled and Accepted.
+The use of this authentication protocol
+is discouraged as it partially violates the authentication protocol by
+implementing two different mechanisms (LANMan & NT) under the guise of
+a single CHAP type (0x80).
+It is very similar to standard CHAP (type 0x05)
+except that it issues challenges of a fixed 8 bytes in length and uses a
+combination of MD4 and DES to encrypt the challenge rather than using the
+standard MD5 mechanism.
+CHAP type 0x80 for LANMan is also supported - see
+.Dq enable LANMan
+for details.
+.Pp
+Because both
+.Dq LANMan
+and
+.Dq NT
+use CHAP type 0x80, when acting as authenticator with both
+.Dq enable Ns No d ,
+.Nm
+will rechallenge the peer up to three times if it responds using the wrong
+one of the two protocols.
+This gives the peer a chance to attempt using both protocols.
+.Pp
+Conversely, when
+.Nm
+acts as the authenticatee with both protocols
+.Dq accept Ns No ed ,
+the protocols are used alternately in response to challenges.
+.Pp
+Note: If only LANMan is enabled,
+.Nm pppd
+(version 2.3.5) misbehaves when acting as authenticatee.
+It provides both
+the NT and the LANMan answers, but also suggests that only the NT answer
+should be used.
+.It pap
+Default: Disabled and Accepted.
+PAP stands for Password Authentication Protocol.
+Only one of PAP and CHAP (above) may be negotiated.
+With PAP, the ID and Password are sent repeatedly to the peer until
+authentication is acknowledged or the connection is terminated.
+This is a rather poor security mechanism.
+It is only performed when the connection is first established.
+If you want to have your peer authenticate itself, you must
+.Dq enable pap .
+in
+.Pa /etc/ppp/ppp.conf ,
+and have an entry in
+.Pa /etc/ppp/ppp.secret
+for the peer (although see the
+.Dq passwdauth
+and
+.Dq set radius
+options below).
+.Pp
+When using PAP as the client, you need only specify
+.Dq AuthName
+and
+.Dq AuthKey
+in
+.Pa /etc/ppp/ppp.conf .
+PAP is accepted by default.
+.It pred1
+Default: Enabled and Accepted.
+This option decides if Predictor 1
+compression will be used by the Compression Control Protocol (CCP).
+.It protocomp
+Default: Enabled and Accepted.
+This option is used to negotiate
+PFC (Protocol Field Compression), a mechanism where the protocol
+field number is reduced to one octet rather than two.
+.It shortseq
+Default: Enabled and Accepted.
+This option determines if
+.Nm
+will request and accept requests for short
+(12 bit)
+sequence numbers when negotiating multi-link mode.
+This is only applicable if our MRRU is set (thus enabling multi-link).
+.It vjcomp
+Default: Enabled and Accepted.
+This option determines if Van Jacobson header compression will be used.
+.El
+.Pp
+The following options are not actually negotiated with the peer.
+Therefore, accepting or denying them makes no sense.
+.Bl -tag -width 2n
+.It echo
+Default: Disabled.
+When this option is enabled,
+.Nm
+will send
+.Em LCP ECHO
+requests to the peer at the frequency defined by
+.Dq echoperiod .
+Note,
+.Em LQR
+requests will supersede
+.Em LCP ECHO
+requests if enabled and negotiated.
+See
+.Dq set lqrperiod
+below for details.
+.Pp
+Prior to
+.Nm
+version 3.4.2,
+.Dq echo
+was considered enabled if lqr was enabled and negotiated, otherwise it was
+considered disabled.
+For the same behaviour, it is now necessary to
+.Dq enable lqr echo
+rather than just
+.Dq enable lqr .
+.It filter-decapsulation
+Default: Disabled.
+When this option is enabled,
+.Nm
+will examine UDP frames to see if they actually contain a
+.Em PPP
+frame as their payload.
+If this is the case, all filters will operate on the payload rather
+than the actual packet.
+.Pp
+This is useful if you want to send PPPoUDP traffic over a
+.Em PPP
+link, but want that link to do smart things with the real data rather than
+the UDP wrapper.
+.Pp
+The UDP frame payload must not be compressed in any way, otherwise
+.Nm
+will not be able to interpret it.
+It is therefore recommended that you
+.Ic disable vj pred1 deflate
+and
+.Ic deny vj pred1 deflate
+in the configuration for the
+.Nm
+invocation with the udp link.
+.It force-scripts
+Default: Disabled.
+Forces execution of the configured chat scripts in
+.Dv direct
+and
+.Dv dedicated
+modes.
+.It idcheck
+Default: Enabled.
+When
+.Nm
+exchanges low-level LCP, CCP and IPCP configuration traffic, the
+.Em Identifier
+field of any replies is expected to be the same as that of the request.
+By default,
+.Nm
+drops any reply packets that do not contain the expected identifier
+field, reporting the fact at the respective log level.
+If
+.Ar idcheck
+is disabled,
+.Nm
+will ignore the identifier field.
+.It iface-alias
+Default: Enabled if
+.Fl nat
+is specified.
+This option simply tells
+.Nm
+to add new interface addresses to the interface rather than replacing them.
+The option can only be enabled if network address translation is enabled
+.Pq Dq nat enable yes .
+.Pp
+With this option enabled,
+.Nm
+will pass traffic for old interface addresses through the NAT
+engine
+(see
+.Xr libalias 3 ) ,
+resulting in the ability (in
+.Fl auto
+mode) to properly connect the process that caused the PPP link to
+come up in the first place.
+.Pp
+Disabling NAT with
+.Dq nat enable no
+will also disable
+.Sq iface-alias .
+.It ipcp
+Default: Enabled.
+This option allows
+.Nm
+to attempt to negotiate IP control protocol capabilities and if
+successful to exchange IP datagrams with the peer.
+.It ipv6cp
+Default: Enabled.
+This option allows
+.Nm
+to attempt to negotiate IPv6 control protocol capabilities and if
+successful to exchange IPv6 datagrams with the peer.
+.It keep-session
+Default: Disabled.
+When
+.Nm
+runs as a Multi-link server, a different
+.Nm
+instance initially receives each connection.
+After determining that
+the link belongs to an already existing bundle (controlled by another
+.Nm
+invocation),
+.Nm
+will transfer the link to that process.
+.Pp
+If the link is a tty device or if this option is enabled,
+.Nm
+will not exit, but will change its process name to
+.Dq session owner
+and wait for the controlling
+.Nm
+to finish with the link and deliver a signal back to the idle process.
+This prevents the confusion that results from
+.Nm Ns No 's
+parent considering the link resource available again.
+.Pp
+For tty devices that have entries in
+.Pa /etc/ttys ,
+this is necessary to prevent another
+.Xr getty 8
+from being started, and for program links such as
+.Xr sshd 8 ,
+it prevents
+.Xr sshd 8
+from exiting due to the death of its child.
+As
+.Nm
+cannot determine its parents requirements (except for the tty case), this
+option must be enabled manually depending on the circumstances.
+.It loopback
+Default: Enabled.
+When
+.Ar loopback
+is enabled,
+.Nm
+will automatically loop back packets being sent
+out with a destination address equal to that of the
+.Em PPP
+interface.
+If disabled,
+.Nm
+will send the packet, probably resulting in an ICMP redirect from
+the other end.
+It is convenient to have this option enabled when
+the interface is also the default route as it avoids the necessity
+of a loopback route.
+.It NAS-IP-Address
+Default: Enabled.
+This option controls whether
+.Nm
+sends the
+.Dq NAS-IP-Address
+attribute to the RADIUS server when RADIUS is in use
+.Pq see Dq set radius .
+.Pp
+Note, at least one of
+.Dq NAS-IP-Address
+and
+.Dq NAS-Identifier
+must be enabled.
+.Pp
+Versions of
+.Nm
+prior to version 3.4.1 did not send the
+.Dq NAS-IP-Address
+attribute as it was reported to break the Radiator RADIUS server.
+As the latest rfc (2865) no longer hints that only one of
+.Dq NAS-IP-Address
+and
+.Dq NAS-Identifier
+should be sent (as rfc 2138 did),
+.Nm
+now sends both and leaves it up to the administrator that chooses to use
+bad RADIUS implementations to
+.Dq disable NAS-IP-Address .
+.It NAS-Identifier
+Default: Enabled.
+This option controls whether
+.Nm
+sends the
+.Dq NAS-Identifier
+attribute to the RADIUS server when RADIUS is in use
+.Pq see Dq set radius .
+.Pp
+Note, at least one of
+.Dq NAS-IP-Address
+and
+.Dq NAS-Identifier
+must be enabled.
+.It passwdauth
+Default: Disabled.
+Enabling this option will tell the PAP authentication
+code to use the password database (see
+.Xr passwd 5 )
+to authenticate the caller if they cannot be found in the
+.Pa /etc/ppp/ppp.secret
+file.
+.Pa /etc/ppp/ppp.secret
+is always checked first.
+If you wish to use passwords from
+.Xr passwd 5 ,
+but also to specify an IP number or label for a given client, use
+.Dq \&*
+as the client password in
+.Pa /etc/ppp/ppp.secret .
+.It proxy
+Default: Disabled.
+Enabling this option will tell
+.Nm
+to proxy ARP for the peer.
+This means that
+.Nm
+will make an entry in the ARP table using
+.Dv HISADDR
+and the
+.Dv MAC
+address of the local network in which
+.Dv HISADDR
+appears.
+This allows other machines connecteed to the LAN to talk to
+the peer as if the peer itself was connected to the LAN.
+The proxy entry cannot be made unless
+.Dv HISADDR
+is an address from a LAN.
+.It proxyall
+Default: Disabled.
+Enabling this will tell
+.Nm
+to add proxy arp entries for every IP address in all class C or
+smaller subnets routed via the tun interface.
+.Pp
+Proxy arp entries are only made for sticky routes that are added
+using the
+.Dq add
+command.
+No proxy arp entries are made for the interface address itself
+(as created by the
+.Dq set ifaddr
+command).
+.It sroutes
+Default: Enabled.
+When the
+.Dq add
+command is used with the
+.Dv HISADDR ,
+.Dv MYADDR ,
+.Dv HISADDR6
+or
+.Dv MYADDR6
+values, entries are stored in the
+.Sq sticky route
+list.
+Each time these variables change, this list is re-applied to the routing table.
+.Pp
+Disabling this option will prevent the re-application of sticky routes,
+although the
+.Sq stick route
+list will still be maintained.
+.It Oo tcp Oc Ns No mssfixup
+Default: Enabled.
+This option tells
+.Nm
+to adjust TCP SYN packets so that the maximum receive segment
+size is not greater than the amount allowed by the interface MTU.
+.It throughput
+Default: Enabled.
+This option tells
+.Nm
+to gather throughput statistics.
+Input and output is sampled over
+a rolling 5 second window, and current, best and total figures are retained.
+This data is output when the relevant
+.Em PPP
+layer shuts down, and is also available using the
+.Dq show
+command.
+Throughput statistics are available at the
+.Dq IPCP
+and
+.Dq physical
+levels.
+.It utmp
+Default: Enabled.
+Normally, when a user is authenticated using PAP or CHAP, and when
+.Nm
+is running in
+.Fl direct
+mode, an entry is made in the utmp and wtmp files for that user.
+Disabling this option will tell
+.Nm
+not to make any utmp or wtmp entries.
+This is usually only necessary if
+you require the user to both login and authenticate themselves.
+.El
+.It add Ns Xo
+.Op !\&
+.Ar dest Ns Op / Ns Ar nn
+.Op Ar mask
+.Op Ar gateway
+.Xc
+.Ar Dest
+is the destination IP address.
+The netmask is specified either as a number of bits with
+.Ar /nn
+or as an IP number using
+.Ar mask .
+.Ar 0 0
+or simply
+.Ar 0
+with no mask refers to the default route.
+It is also possible to use the literal name
+.Sq default
+instead of
+.Ar 0 .
+.Ar Gateway
+is the next hop gateway to get to the given
+.Ar dest
+machine/network.
+Refer to the
+.Xr route 8
+command for further details.
+.Pp
+It is possible to use the symbolic names
+.Sq MYADDR ,
+.Sq HISADDR ,
+.Sq MYADDR6
+or
+.Sq HISADDR6
+as the destination, and
+.Sq HISADDR
+or
+.Sq HISADDR6
+as the
+.Ar gateway .
+.Sq MYADDR
+is replaced with the interface IP address,
+.Sq HISADDR
+is replaced with the interface IP destination (peer) address,
+.Sq MYADDR6
+is replaced with the interface IPv6 address, and
+.Sq HISADDR6
+is replaced with the interface IPv6 destination address,
+.Pp
+If the
+.Ar add!\&
+command is used
+(note the trailing
+.Dq !\& ) ,
+then if the route already exists, it will be updated as with the
+.Sq route change
+command (see
+.Xr route 8
+for further details).
+.Pp
+Routes that contain the
+.Dq HISADDR ,
+.Dq MYADDR ,
+.Dq HISADDR6 ,
+.Dq MYADDR6 ,
+.Dq DNS0 ,
+or
+.Dq DNS1
+constants are considered
+.Sq sticky .
+They are stored in a list (use
+.Dq show ncp
+to see the list), and each time the value of one of these variables
+changes, the appropriate routing table entries are updated.
+This facility may be disabled using
+.Dq disable sroutes .
+.It allow Ar command Op Ar args
+This command controls access to
+.Nm
+and its configuration files.
+It is possible to allow user-level access,
+depending on the configuration file label and on the mode that
+.Nm
+is being run in.
+For example, you may wish to configure
+.Nm
+so that only user
+.Sq fred
+may access label
+.Sq fredlabel
+in
+.Fl background
+mode.
+.Pp
+User id 0 is immune to these commands.
+.Bl -tag -width 2n
+.It allow user Ns Xo
+.Op s
+.Ar logname Ns No ...
+.Xc
+By default, only user id 0 is allowed access to
+.Nm .
+If this command is used, all of the listed users are allowed access to
+the section in which the
+.Dq allow users
+command is found.
+The
+.Sq default
+section is always checked first (even though it is only ever automatically
+loaded at startup).
+.Dq allow users
+commands are cumulative in a given section, but users allowed in any given
+section override users allowed in the default section, so it is possible to
+allow users access to everything except a given label by specifying default
+users in the
+.Sq default
+section, and then specifying a new user list for that label.
+.Pp
+If user
+.Sq *
+is specified, access is allowed to all users.
+.It allow mode Ns Xo
+.Op s
+.Ar mode Ns No ...
+.Xc
+By default, access using any
+.Nm
+mode is possible.
+If this command is used, it restricts the access
+.Ar modes
+allowed to load the label under which this command is specified.
+Again, as with the
+.Dq allow users
+command, each
+.Dq allow modes
+command overrides any previous settings, and the
+.Sq default
+section is always checked first.
+.Pp
+Possible modes are:
+.Sq interactive ,
+.Sq auto ,
+.Sq direct ,
+.Sq dedicated ,
+.Sq ddial ,
+.Sq background
+and
+.Sq * .
+.Pp
+When running in multi-link mode, a section can be loaded if it allows
+.Em any
+of the currently existing line modes.
+.El
+.It nat Ar command Op Ar args
+This command allows the control of the network address translation (also
+known as masquerading or IP aliasing) facilities that are built into
+.Nm .
+NAT is done on the external interface only, and is unlikely to make sense
+if used with the
+.Fl direct
+flag.
+.Pp
+If nat is enabled on your system (it may be omitted at compile time),
+the following commands are possible:
+.Bl -tag -width 2n
+.It nat enable yes|no
+This command either switches network address translation on or turns it off.
+The
+.Fl nat
+command line flag is synonymous with
+.Dq nat enable yes .
+.It nat addr Op Ar addr_local addr_alias
+This command allows data for
+.Ar addr_alias
+to be redirected to
+.Ar addr_local .
+It is useful if you own a small number of real IP numbers that
+you wish to map to specific machines behind your gateway.
+.It nat deny_incoming yes|no
+If set to yes, this command will refuse all incoming packets where an
+aliasing link does not already exist.
+Refer to the
+.Sx CONCEPTUAL BACKGROUND
+section of
+.Xr libalias 3
+for a description of what an
+.Dq aliasing link
+is.
+.Pp
+It should be noted under what circumstances an aliasing link is
+created by
+.Xr libalias 3 .
+It may be necessary to further protect your network from outside
+connections using the
+.Dq set filter
+or
+.Dq nat target
+commands.
+.It nat help|?
+This command gives a summary of available nat commands.
+.It nat log yes|no
+This option causes various NAT statistics and information to
+be logged to the file
+.Pa /var/log/alias.log .
+.It nat port Ar proto Ar targetIP Ns Xo
+.No : Ns Ar targetPort Ns
+.Oo
+.No - Ns Ar targetPort
+.Oc Ar aliasPort Ns
+.Oo
+.No - Ns Ar aliasPort
+.Oc Oo Ar remoteIP : Ns
+.Ar remotePort Ns
+.Oo
+.No - Ns Ar remotePort
+.Oc
+.Oc
+.Xc
+This command causes incoming
+.Ar proto
+connections to
+.Ar aliasPort
+to be redirected to
+.Ar targetPort
+on
+.Ar targetIP .
+.Ar proto
+is either
+.Dq tcp
+or
+.Dq udp .
+.Pp
+A range of port numbers may be specified as shown above.
+The ranges must be of the same size.
+.Pp
+If
+.Ar remoteIP
+is specified, only data coming from that IP number is redirected.
+.Ar remotePort
+must either be
+.Dq 0
+(indicating any source port)
+or a range of ports the same size as the other ranges.
+.Pp
+This option is useful if you wish to run things like Internet phone on
+machines behind your gateway, but is limited in that connections to only
+one interior machine per source machine and target port are possible.
+.It nat proto Ar proto localIP Oo
+.Ar publicIP Op Ar remoteIP
+.Oc
+This command tells
+.Nm
+to redirect packets of protocol type
+.Ar proto
+(see
+.Xr protocols 5 )
+to the internal address
+.Ar localIP .
+.Pp
+If
+.Ar publicIP
+is specified, only packets destined for that address are matched,
+otherwise the default alias address is used.
+.Pp
+If
+.Ar remoteIP
+is specified, only packets matching that source address are matched,
+.Pp
+This command is useful for redirecting tunnel endpoints to an internal machine,
+for example:
+.Pp
+.Dl nat proto ipencap 10.0.0.1
+.It "nat proxy cmd" Ar arg Ns No ...
+This command tells
+.Nm
+to proxy certain connections, redirecting them to a given server.
+Refer to the description of
+.Fn PacketAliasProxyRule
+in
+.Xr libalias 3
+for details of the available commands.
+.It nat punch_fw Op Ar base count
+This command tells
+.Nm
+to punch holes in the firewall for FTP or IRC DCC connections.
+This is done dynamically by installing termporary 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 rule number
+.Ar base
+will be used for punching firewall holes.
+The range will be cleared when the
+.Dq nat punch_fw
+command is run.
+.Pp
+If no arguments are given, firewall punching is disabled.
+.It nat skinny_port Op Ar port
+This command tells
+.Nm
+which TCP port is used by the Skinny Station protocol.
+Skinny is used by
+Cisco IP phones to communicate with Cisco Call Managers to setup voice
+over IP calls.
+The typical port used by Skinny is 2000.
+.Pp
+If no argument is given, skinny aliasing is disabled.
+.It nat same_ports yes|no
+When enabled, this command will tell the network address translation engine to
+attempt to avoid changing the port number on outgoing packets.
+This is useful
+if you want to support protocols such as RPC and LPD which require
+connections to come from a well known port.
+.It nat target Op Ar address
+Set the given target address or clear it if no address is given.
+The target address is used by libalias to specify how to NAT incoming packets
+by default.
+If a target address is not set or if
+.Dq default
+is given, packets are not altered and are allowed to route to the internal
+network.
+.Pp
+The target address may be set to
+.Dq MYADDR ,
+in which case libalias will redirect all packets to the interface address.
+.It nat use_sockets yes|no
+When enabled, this option tells the network address translation engine to
+create a socket so that it can guarantee a correct incoming ftp data or
+IRC connection.
+.It nat unregistered_only yes|no
+Only alter outgoing packets with an 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.
+.El
+.Pp
+These commands are also discussed in the file
+.Pa README.nat
+which comes with the source distribution.
+.It Oo !\& Oc Ns Xo
+.No bg Ar command
+.Xc
+The given
+.Ar command
+is executed in the background with the following words replaced:
+.Bl -tag -width COMPILATIONDATE
+.It Li AUTHNAME
+This is replaced with the local
+.Ar authname
+value.
+See the
+.Dq set authname
+command below.
+.It Li COMPILATIONDATE
+In previous software revisions, this was replaced with the date on which
+.Nm
+was compiled.
+This is no longer supported as it breaks the ability to recompile the same
+code to produce an exact duplicate of a previous compilation.
+.It Li DNS0 & DNS1
+These are replaced with the primary and secondary nameserver IP numbers.
+If nameservers are negotiated by IPCP, the values of these macros will change.
+.It Li ENDDISC
+This is replaced with the local endpoint discriminator value.
+See the
+.Dq set enddisc
+command below.
+.It Li HISADDR
+This is replaced with the peers IP number.
+.It Li HISADDR6
+This is replaced with the peers IPv6 number.
+.It Li INTERFACE
+This is replaced with the name of the interface that is in use.
+.It Li IPOCTETSIN
+This is replaced with the number of IP bytes received since the connection
+was established.
+.It Li IPOCTETSOUT
+This is replaced with the number of IP bytes sent since the connection
+was established.
+.It Li IPPACKETSIN
+This is replaced with the number of IP packets received since the connection
+was established.
+.It Li IPPACKETSOUT
+This is replaced with the number of IP packets sent since the connection
+was established.
+.It Li IPV6OCTETSIN
+This is replaced with the number of IPv6 bytes received since the connection
+was established.
+.It Li IPV6OCTETSOUT
+This is replaced with the number of IPv6 bytes sent since the connection
+was established.
+.It Li IPV6PACKETSIN
+This is replaced with the number of IPv6 packets received since the connection
+was established.
+.It Li IPV6PACKETSOUT
+This is replaced with the number of IPv6 packets sent since the connection
+was established.
+.It Li LABEL
+This is replaced with the last label name used.
+A label may be specified on the
+.Nm
+command line, via the
+.Dq load
+or
+.Dq dial
+commands and in the
+.Pa ppp.secret
+file.
+.It Li MYADDR
+This is replaced with the IP number assigned to the local interface.
+.It Li MYADDR6
+This is replaced with the IPv6 number assigned to the local interface.
+.It Li OCTETSIN
+This is replaced with the number of bytes received since the connection
+was established.
+.It Li OCTETSOUT
+This is replaced with the number of bytes sent since the connection
+was established.
+.It Li PACKETSIN
+This is replaced with the number of packets received since the connection
+was established.
+.It Li PACKETSOUT
+This is replaced with the number of packets sent since the connection
+was established.
+.It Li PEER_ENDDISC
+This is replaced with the value of the peers endpoint discriminator.
+.It Li PROCESSID
+This is replaced with the current process id.
+.It Li SOCKNAME
+This is replaced with the name of the diagnostic socket.
+.It Li UPTIME
+This is replaced with the bundle uptime in HH:MM:SS format.
+.It Li USER
+This is replaced with the username that has been authenticated with PAP or
+CHAP.
+Normally, this variable is assigned only in -direct mode.
+This value is available irrespective of whether utmp logging is enabled.
+.It Li VERSION
+This is replaced with the current version number of
+.Nm .
+.El
+.Pp
+These substitutions are also done by the
+.Dq set proctitle ,
+.Dq ident
+and
+.Dq log
+commands.
+.Pp
+If you wish to pause
+.Nm
+while the command executes, use the
+.Dq shell
+command instead.
+.It clear physical|ipcp|ipv6 Op current|overall|peak...
+Clear the specified throughput values at either the
+.Dq physical ,
+.Dq ipcp
+or
+.Dq ipv6cp
+level.
+If
+.Dq physical
+is specified, context must be given (see the
+.Dq link
+command below).
+If no second argument is given, all values are cleared.
+.It clone Ar name Ns Xo
+.Op \&, Ns Ar name Ns
+.No ...
+.Xc
+Clone the specified link, creating one or more new links according to the
+.Ar name
+argument(s).
+This command must be used from the
+.Dq link
+command below unless you have only got a single link (in which case that
+link becomes the default).
+Links may be removed using the
+.Dq remove
+command below.
+.Pp
+The default link name is
+.Dq deflink .
+.It close Op lcp|ccp Ns Op !\&
+If no arguments are given, the relevant protocol layers will be brought
+down and the link will be closed.
+If
+.Dq lcp
+is specified, the LCP layer is brought down, but
+.Nm
+will not bring the link offline.
+It is subsequently possible to use
+.Dq term
+(see below)
+to talk to the peer machine if, for example, something like
+.Dq slirp
+is being used.
+If
+.Dq ccp
+is specified, only the relevant compression layer is closed.
+If the
+.Dq !\&
+is used, the compression layer will remain in the closed state, otherwise
+it will re-enter the STOPPED state, waiting for the peer to initiate
+further CCP negotiation.
+In any event, this command does not disconnect the user from
+.Nm
+or exit
+.Nm .
+See the
+.Dq quit
+command below.
+.It delete Ns Xo
+.Op !\&
+.Ar dest
+.Xc
+This command deletes the route with the given
+.Ar dest
+IP address.
+If
+.Ar dest
+is specified as
+.Sq ALL ,
+all non-direct entries in the routing table for the current interface,
+and all
+.Sq sticky route
+entries are deleted.
+If
+.Ar dest
+is specified as
+.Sq default ,
+the default route is deleted.
+.Pp
+If the
+.Ar delete!\&
+command is used
+(note the trailing
+.Dq !\& ) ,
+.Nm
+will not complain if the route does not already exist.
+.It dial|call Oo Ar label Oc Ns Xo
+.No ...
+.Xc
+This command is the equivalent of
+.Dq load label
+followed by
+.Dq open ,
+and is provided for backwards compatibility.
+.It down Op Ar lcp|ccp
+Bring the relevant layer down ungracefully, as if the underlying layer
+had become unavailable.
+It is not considered polite to use this command on
+a Finite State Machine that is in the OPEN state.
+If no arguments are
+supplied, the entire link is closed (or if no context is given, all links
+are terminated).
+If
+.Sq lcp
+is specified, the
+.Em LCP
+layer is terminated but the device is not brought offline and the link
+is not closed.
+If
+.Sq ccp
+is specified, only the relevant compression layer(s) are terminated.
+.It help|? Op Ar command
+Show a list of available commands.
+If
+.Ar command
+is specified, show the usage string for that command.
+.It ident Op Ar text Ns No ...
+Identify the link to the peer using
+.Ar text .
+If
+.Ar text
+is empty, link identification is disabled.
+It is possible to use any of the words described for the
+.Ic bg
+command above.
+Refer to the
+.Ic sendident
+command for details of when
+.Nm
+identifies itself to the peer.
+.It iface Ar command Op args
+This command is used to control the interface used by
+.Nm .
+.Ar Command
+may be one of the following:
+.Bl -tag -width 2n
+.It iface add Ns Xo
+.Op !\&
+.Ar addr Ns Op / Ns Ar bits
+.Op Ar peer
+.Xc
+.It iface add Ns Xo
+.Op !\&
+.Ar addr
+.Ar mask
+.Ar peer
+.Xc
+Add the given
+.Ar addr mask peer
+combination to the interface.
+Instead of specifying
+.Ar mask ,
+.Ar /bits
+can be used
+(with no space between it and
+.Ar addr ) .
+If the given address already exists, the command fails unless the
+.Dq !\&
+is used - in which case the previous interface address entry is overwritten
+with the new one, allowing a change of netmask or peer address.
+.Pp
+If only
+.Ar addr
+is specified,
+.Ar bits
+defaults to
+.Dq 32
+and
+.Ar peer
+defaults to
+.Dq 255.255.255.255 .
+This address (the broadcast address) is the only duplicate peer address that
+.Nm
+allows.
+.It iface clear Op INET | INET6
+If this command is used while
+.Nm
+is in the OPENED state or while in
+.Fl auto
+mode, all addresses except for the NCP negotiated address are deleted
+from the interface.
+If
+.Nm
+is not in the OPENED state and is not in
+.Fl auto
+mode, all interface addresses are deleted.
+.Pp
+If the INET or INET6 arguments are used, only addresses for that address
+family are cleared.
+.It iface delete Ns Xo
+.Op !\& Ns
+.No |rm Ns Op !\&
+.Ar addr
+.Xc
+This command deletes the given
+.Ar addr
+from the interface.
+If the
+.Dq !\&
+is used, no error is given if the address is not currently assigned to
+the interface (and no deletion takes place).
+.It iface name Ar name
+Renames the interface to
+.Ar name .
+.It iface description Ar description
+Sets the interface description to
+.Ar description .
+Useful if you have many interfaces on your system.
+.It iface show
+Shows the current state and current addresses for the interface.
+It is much the same as running
+.Dq ifconfig INTERFACE .
+.It iface help Op Ar sub-command
+This command, when invoked without
+.Ar sub-command ,
+will show a list of possible
+.Dq iface
+sub-commands and a brief synopsis for each.
+When invoked with
+.Ar sub-command ,
+only the synopsis for the given sub-command is shown.
+.El
+.It Oo data Oc Ns Xo
+.No link
+.Ar name Ns Oo , Ns Ar name Oc Ns ... Ar command Op Ar args
+.Xc
+This command may prefix any other command if the user wishes to
+specify which link the command should affect.
+This is only applicable after multiple links have been created in Multi-link
+mode using the
+.Dq clone
+command.
+.Pp
+.Ar Name
+specifies the name of an existing link.
+If
+.Ar name
+is a comma separated list,
+.Ar command
+is executed on each link.
+If
+.Ar name
+is
+.Dq * ,
+.Ar command
+is executed on all links.
+.It load Oo Ar label Oc Ns Xo
+.No ...
+.Xc
+Load the given
+.Ar label Ns No (s)
+from the
+.Pa ppp.conf
+file.
+If
+.Ar label
+is not given, the
+.Ar default
+label is used.
+.Pp
+Unless the
+.Ar label
+section uses the
+.Dq set mode ,
+.Dq open
+or
+.Dq dial
+commands,
+.Nm
+will not attempt to make an immediate connection.
+.It log Ar word Ns No ...
+Send the given word(s) to the log file with the prefix
+.Dq LOG: .
+Word substitutions are done as explained under the
+.Dq !bg
+command above.
+.It open Op lcp|ccp|ipcp
+This is the opposite of the
+.Dq close
+command.
+All closed links are immediately brought up apart from second and subsequent
+.Ar demand-dial
+links - these will come up based on the
+.Dq set autoload
+command that has been used.
+.Pp
+If the
+.Dq lcp
+argument is used while the LCP layer is already open, LCP will be
+renegotiated.
+This allows various LCP options to be changed, after which
+.Dq open lcp
+can be used to put them into effect.
+After renegotiating LCP,
+any agreed authentication will also take place.
+.Pp
+If the
+.Dq ccp
+argument is used, the relevant compression layer is opened.
+Again, if it is already open, it will be renegotiated.
+.Pp
+If the
+.Dq ipcp
+argument is used, the link will be brought up as normal, but if
+IPCP is already open, it will be renegotiated and the network
+interface will be reconfigured.
+.Pp
+It is probably not good practice to re-open the PPP state machines
+like this as it is possible that the peer will not behave correctly.
+It
+.Em is
+however useful as a way of forcing the CCP or VJ dictionaries to be reset.
+.It passwd Ar pass
+Specify the password required for access to the full
+.Nm
+command set.
+This password is required when connecting to the diagnostic port (see the
+.Dq set server
+command).
+.Ar Pass
+is specified on the
+.Dq set server
+command line.
+The value of
+.Ar pass
+is not logged when
+.Ar command
+logging is active, instead, the literal string
+.Sq ********
+is logged.
+.It quit|bye Op all
+If
+.Dq quit
+is executed from the controlling connection or from a command file,
+ppp will exit after closing all connections.
+Otherwise, if the user
+is connected to a diagnostic socket, the connection is simply dropped.
+.Pp
+If the
+.Ar all
+argument is given,
+.Nm
+will exit despite the source of the command after closing all existing
+connections.
+.It remove|rm
+This command removes the given link.
+It is only really useful in multi-link mode.
+A link must be in the
+.Dv CLOSED
+state before it is removed.
+.It rename|mv Ar name
+This command renames the given link to
+.Ar name .
+It will fail if
+.Ar name
+is already used by another link.
+.Pp
+The default link name is
+.Sq deflink .
+Renaming it to
+.Sq modem ,
+.Sq cuau0
+or
+.Sq USR
+may make the log file more readable.
+.It resolv Ar command
+This command controls
+.Nm Ns No 's
+manipulation of the
+.Xr resolv.conf 5
+file.
+When
+.Nm
+starts up, it loads the contents of this file into memory and retains this
+image for future use.
+.Ar command
+is one of the following:
+.Bl -tag -width readonly
+.It Em readonly
+Treat
+.Pa /etc/resolv.conf
+as read only.
+If
+.Dq dns
+is enabled,
+.Nm
+will still attempt to negotiate nameservers with the peer, making the results
+available via the
+.Dv DNS0
+and
+.Dv DNS1
+macros.
+This is the opposite of the
+.Dq resolv writable
+command.
+.It Em reload
+Reload
+.Pa /etc/resolv.conf
+into memory.
+This may be necessary if for example a DHCP client overwrote
+.Pa /etc/resolv.conf .
+.It Em restore
+Replace
+.Pa /etc/resolv.conf
+with the version originally read at startup or with the last
+.Dq resolv reload
+command.
+This is sometimes a useful command to put in the
+.Pa /etc/ppp/ppp.linkdown
+file.
+.It Em rewrite
+Rewrite the
+.Pa /etc/resolv.conf
+file.
+This command will work even if the
+.Dq resolv readonly
+command has been used.
+It may be useful as a command in the
+.Pa /etc/ppp/ppp.linkup
+file if you wish to defer updating
+.Pa /etc/resolv.conf
+until after other commands have finished.
+.It Em writable
+Allow
+.Nm
+to update
+.Pa /etc/resolv.conf
+if
+.Dq dns
+is enabled and
+.Nm
+successfully negotiates a DNS.
+This is the opposite of the
+.Dq resolv readonly
+command.
+.El
+.It save
+This option is not (yet) implemented.
+.It sendident
+This command tells
+.Nm
+to identify itself to the peer.
+The link must be in LCP state or higher.
+If no identity has been set (via the
+.Ic ident
+command),
+.Ic sendident
+will fail.
+.Pp
+When an identity has been set,
+.Nm
+will automatically identify itself when it sends or receives a configure
+reject, when negotiation fails or when LCP reaches the opened state.
+.Pp
+Received identification packets are logged to the LCP log (see
+.Ic set log
+for details) and are never responded to.
+.It set Ns Xo
+.Op up
+.Ar var value
+.Xc
+This option allows the setting of any of the following variables:
+.Bl -tag -width 2n
+.It set accmap Ar hex-value
+ACCMap stands for Asynchronous Control Character Map.
+This is always
+negotiated with the peer, and defaults to a value of 00000000 in hex.
+This protocol is required to defeat hardware that depends on passing
+certain characters from end to end (such as XON/XOFF etc).
+.Pp
+For the XON/XOFF scenario, use
+.Dq set accmap 000a0000 .
+.It set Oo auth Oc Ns Xo
+.No key Ar value
+.Xc
+This sets the authentication key (or password) used in client mode
+PAP or CHAP negotiation to the given value.
+It also specifies the
+password to be used in the dial or login scripts in place of the
+.Sq \eP
+sequence, preventing the actual password from being logged.
+If
+.Ar command
+or
+.Ar chat
+logging is in effect,
+.Ar value
+is logged as
+.Sq ********
+for security reasons.
+.Pp
+If the first character of
+.Ar value
+is an exclamation mark
+.Pq Dq !\& ,
+.Nm
+treats the remainder of the string as a program that must be executed
+to determine the
+.Dq authname
+and
+.Dq authkey
+values.
+.Pp
+If the
+.Dq !\&
+is doubled up
+(to
+.Dq !! ) ,
+it is treated as a single literal
+.Dq !\& ,
+otherwise, ignoring the
+.Dq !\& ,
+.Ar value
+is parsed as a program to execute in the same was as the
+.Dq !bg
+command above, substituting special names in the same manner.
+Once executed,
+.Nm
+will feed the program three lines of input, each terminated by a newline
+character:
+.Bl -bullet
+.It
+The host name as sent in the CHAP challenge.
+.It
+The challenge string as sent in the CHAP challenge.
+.It
+The locally defined
+.Dq authname .
+.El
+.Pp
+Two lines of output are expected:
+.Bl -bullet
+.It
+The
+.Dq authname
+to be sent with the CHAP response.
+.It
+The
+.Dq authkey ,
+which is encrypted with the challenge and request id, the answer being sent
+in the CHAP response packet.
+.El
+.Pp
+When configuring
+.Nm
+in this manner, it is expected that the host challenge is a series of ASCII
+digits or characters.
+An encryption device or Secure ID card is usually
+required to calculate the secret appropriate for the given challenge.
+.It set authname Ar id
+This sets the authentication id used in client mode PAP or CHAP negotiation.
+.Pp
+If used in
+.Fl direct
+mode with CHAP enabled,
+.Ar id
+is used in the initial authentication challenge and should normally be set to
+the local machine name.
+.It set autoload Xo
+.Ar min-percent max-percent period
+.Xc
+These settings apply only in multi-link mode and default to zero, zero and
+five respectively.
+When more than one
+.Ar demand-dial
+(also known as
+.Fl auto )
+mode link is available, only the first link is made active when
+.Nm
+first reads data from the tun device.
+The next
+.Ar demand-dial
+link will be opened only when the current bundle throughput is at least
+.Ar max-percent
+percent of the total bundle bandwidth for
+.Ar period
+seconds.
+When the current bundle throughput decreases to
+.Ar min-percent
+percent or less of the total bundle bandwidth for
+.Ar period
+seconds, a
+.Ar demand-dial
+link will be brought down as long as it is not the last active link.
+.Pp
+Bundle throughput is measured as the maximum of inbound and outbound
+traffic.
+.Pp
+The default values cause
+.Ar demand-dial
+links to simply come up one at a time.
+.Pp
+Certain devices cannot determine their physical bandwidth, so it
+is sometimes necessary to use the
+.Dq set bandwidth
+command (described below) to make
+.Dq set autoload
+work correctly.
+.It set bandwidth Ar value
+This command sets the connection bandwidth in bits per second.
+.Ar value
+must be greater than zero.
+It is currently only used by the
+.Dq set autoload
+command above.
+.It set callback Ar option Ns No ...
+If no arguments are given, callback is disabled, otherwise,
+.Nm
+will request (or in
+.Fl direct
+mode, will accept) one of the given
+.Ar option Ns No s .
+In client mode, if an
+.Ar option
+is NAK'd
+.Nm
+will request a different
+.Ar option ,
+until no options remain at which point
+.Nm
+will terminate negotiations (unless
+.Dq none
+is one of the specified
+.Ar option ) .
+In server mode,
+.Nm
+will accept any of the given protocols - but the client
+.Em must
+request one of them.
+If you wish callback to be optional, you must {include}
+.Ar none
+as an option.
+.Pp
+The
+.Ar option Ns No s
+are as follows (in this order of preference):
+.Bl -tag -width Ds
+.It auth
+The callee is expected to decide the callback number based on
+authentication.
+If
+.Nm
+is the callee, the number should be specified as the fifth field of
+the peers entry in
+.Pa /etc/ppp/ppp.secret .
+.It cbcp
+Microsoft's callback control protocol is used.
+See
+.Dq set cbcp
+below.
+.Pp
+If you wish to negotiate
+.Ar cbcp
+in client mode but also wish to allow the server to request no callback at
+CBCP negotiation time, you must specify both
+.Ar cbcp
+and
+.Ar none
+as callback options.
+.It E.164 *| Ns Xo
+.Ar number Ns Op , Ns Ar number Ns
+.No ...
+.Xc
+The caller specifies the
+.Ar number .
+If
+.Nm
+is the callee,
+.Ar number
+should be either a comma separated list of allowable numbers or a
+.Dq \&* ,
+meaning any number is permitted.
+If
+.Nm
+is the caller, only a single number should be specified.
+.Pp
+Note, this option is very unsafe when used with a
+.Dq \&*
+as a malicious caller can tell
+.Nm
+to call any (possibly international) number without first authenticating
+themselves.
+.It none
+If the peer does not wish to do callback at all,
+.Nm
+will accept the fact and continue without callback rather than terminating
+the connection.
+This is required (in addition to one or more other callback
+options) if you wish callback to be optional.
+.El
+.It set cbcp Oo
+.No *| Ns Ar number Ns Oo
+.No , Ns Ar number Ns ...\& Oc
+.Op Ar delay Op Ar retry
+.Oc
+If no arguments are given, CBCP (Microsoft's CallBack Control Protocol)
+is disabled - ie, configuring CBCP in the
+.Dq set callback
+command will result in
+.Nm
+requesting no callback in the CBCP phase.
+Otherwise,
+.Nm
+attempts to use the given phone
+.Ar number Ns No (s).
+.Pp
+In server mode
+.Pq Fl direct ,
+.Nm
+will insist that the client uses one of these numbers, unless
+.Dq \&*
+is used in which case the client is expected to specify the number.
+.Pp
+In client mode,
+.Nm
+will attempt to use one of the given numbers (whichever it finds to
+be agreeable with the peer), or if
+.Dq \&*
+is specified,
+.Nm
+will expect the peer to specify the number.
+.It set cd Oo
+.No off| Ns Ar seconds Ns Op !\&
+.Oc
+Normally,
+.Nm
+checks for the existence of carrier depending on the type of device
+that has been opened:
+.Bl -tag -width XXX -offset XXX
+.It Terminal Devices
+Carrier is checked one second after the login script is complete.
+If it is not set,
+.Nm
+assumes that this is because the device does not support carrier (which
+is true for most
+.Dq laplink
+NULL-modem cables), logs the fact and stops checking
+for carrier.
+.Pp
+As ptys do not support the TIOCMGET ioctl, the tty device will switch all
+carrier detection off when it detects that the device is a pty.
+.It PPPoE (netgraph) Devices
+Carrier is checked once per second for 5 seconds.
+If it is not set after
+the fifth second, the connection attempt is considered to have failed and
+the device is closed.
+Carrier is always required for PPPoE devices.
+.El
+.Pp
+All other device types do not support carrier.
+Setting a carrier value will
+result in a warning when the device is opened.
+.Pp
+Some modems take more than one second after connecting to assert the carrier
+signal.
+If this delay is not increased, this will result in
+.Nm Ns No 's
+inability to detect when the link is dropped, as
+.Nm
+assumes that the device is not asserting carrier.
+.Pp
+The
+.Dq set cd
+command overrides the default carrier behaviour.
+.Ar seconds
+specifies the maximum number of seconds that
+.Nm
+should wait after the dial script has finished before deciding if
+carrier is available or not.
+.Pp
+If
+.Dq off
+is specified,
+.Nm
+will not check for carrier on the device, otherwise
+.Nm
+will not proceed to the login script until either carrier is detected
+or until
+.Ar seconds
+has elapsed, at which point
+.Nm
+assumes that the device will not set carrier.
+.Pp
+If no arguments are given, carrier settings will go back to their default
+values.
+.Pp
+If
+.Ar seconds
+is followed immediately by an exclamation mark
+.Pq Dq !\& ,
+.Nm
+will
+.Em require
+carrier.
+If carrier is not detected after
+.Ar seconds
+seconds, the link will be disconnected.
+.It set choked Op Ar timeout
+This sets the number of seconds that
+.Nm
+will keep a choked output queue before dropping all pending output packets.
+If
+.Ar timeout
+is less than or equal to zero or if
+.Ar timeout
+is not specified, it is set to the default value of
+.Em 120 seconds .
+.Pp
+A choked output queue occurs when
+.Nm
+has read a certain number of packets from the local network for transmission,
+but cannot send the data due to link failure (the peer is busy etc.).
+.Nm
+will not read packets indefinitely.
+Instead, it reads up to
+.Em 30
+packets (or
+.Em 30 No +
+.Em nlinks No *
+.Em 2
+packets in multi-link mode), then stops reading the network interface
+until either
+.Ar timeout
+seconds have passed or at least one packet has been sent.
+.Pp
+If
+.Ar timeout
+seconds pass, all pending output packets are dropped.
+.It set ctsrts|crtscts on|off
+This sets hardware flow control.
+Hardware flow control is
+.Ar on
+by default.
+.It set deflate Ar out-winsize Op Ar in-winsize
+This sets the DEFLATE algorithms default outgoing and incoming window
+sizes.
+Both
+.Ar out-winsize
+and
+.Ar in-winsize
+must be values between
+.Em 8
+and
+.Em 15 .
+If
+.Ar in-winsize
+is specified,
+.Nm
+will insist that this window size is used and will not accept any other
+values from the peer.
+.It set dns Op Ar primary Op Ar secondary
+This command specifies DNS overrides for the
+.Dq accept dns
+command.
+Refer to the
+.Dq accept
+command description above for details.
+This command does not affect the IP numbers requested using
+.Dq enable dns .
+.It set device|line Xo
+.Ar value Ns No ...
+.Xc
+This sets the device(s) to which
+.Nm
+will talk to the given
+.Dq value .
+.Pp
+All serial device names are expected to begin with
+.Pa /dev/ .
+Serial devices are usually called
+.Pa cuaXX .
+.Pp
+If
+.Dq value
+does not begin with
+.Pa /dev/ ,
+it must either begin with an exclamation mark
+.Pq Dq !\& ,
+be of the format
+.No PPPoE: Ns Ar iface Ns Xo
+.Op \&: Ns Ar provider Ns
+.Xc
+(on
+.Xr netgraph 4
+enabled systems), or be of the format
+.Sm off
+.Ar host : port Op /tcp|udp .
+.Sm on
+.Pp
+If it begins with an exclamation mark, the rest of the device name is
+treated as a program name, and that program is executed when the device
+is opened.
+Standard input, output and error are fed back to
+.Nm
+and are read and written as if they were a regular device.
+.Pp
+If a
+.No PPPoE: Ns Ar iface Ns Xo
+.Op \&: Ns Ar provider Ns
+.Xc
+specification is given,
+.Nm
+will attempt to create a
+.Em PPP
+over Ethernet connection using the given
+.Ar iface
+interface by using
+.Xr netgraph 4 .
+If
+.Xr netgraph 4
+is not available,
+.Nm
+will attempt to load it using
+.Xr kldload 2 .
+If this fails, an external program must be used such as the
+.Xr pppoed 8
+program available under
+.Ox .
+The given
+.Ar provider
+is passed as the service name in the PPPoE Discovery Initiation (PADI)
+packet.
+If no provider is given, an empty value will be used.
+.Pp
+When a PPPoE connection is established,
+.Nm
+will place the name of the Access Concentrator in the environment variable
+.Ev ACNAME .
+.Pp
+Refer to
+.Xr netgraph 4
+and
+.Xr ng_pppoe 4
+for further details.
+.Pp
+If a
+.Ar host Ns No : Ns Ar port Ns Oo
+.No /tcp|udp
+.Oc
+specification is given,
+.Nm
+will attempt to connect to the given
+.Ar host
+on the given
+.Ar port .
+If a
+.Dq /tcp
+or
+.Dq /udp
+suffix is not provided, the default is
+.Dq /tcp .
+Refer to the section on
+.Em PPP OVER TCP and UDP
+above for further details.
+.Pp
+If multiple
+.Dq values
+are specified,
+.Nm
+will attempt to open each one in turn until it succeeds or runs out of
+devices.
+.It set dial Ar chat-script
+This specifies the chat script that will be used to dial the other
+side.
+See also the
+.Dq set login
+command below.
+Refer to
+.Xr chat 8
+and to the example configuration files for details of the chat script
+format.
+It is possible to specify some special
+.Sq values
+in your chat script as follows:
+.Bl -tag -width 2n
+.It Li \ec
+When used as the last character in a
+.Sq send
+string, this indicates that a newline should not be appended.
+.It Li \ed
+When the chat script encounters this sequence, it delays two seconds.
+.It Li \ep
+When the chat script encounters this sequence, it delays for one quarter of
+a second.
+.It Li \en
+This is replaced with a newline character.
+.It Li \er
+This is replaced with a carriage return character.
+.It Li \es
+This is replaced with a space character.
+.It Li \et
+This is replaced with a tab character.
+.It Li \eT
+This is replaced by the current phone number (see
+.Dq set phone
+below).
+.It Li \eP
+This is replaced by the current
+.Ar authkey
+value (see
+.Dq set authkey
+above).
+.It Li \eU
+This is replaced by the current
+.Ar authname
+value (see
+.Dq set authname
+above).
+.El
+.Pp
+Note that two parsers will examine these escape sequences, so in order to
+have the
+.Sq chat parser
+see the escape character, it is necessary to escape it from the
+.Sq command parser .
+This means that in practice you should use two escapes, for example:
+.Bd -literal -offset indent
+set dial "... ATDT\\\\T CONNECT"
+.Ed
+.Pp
+It is also possible to execute external commands from the chat script.
+To do this, the first character of the expect or send string is an
+exclamation mark
+.Pq Dq !\& .
+If a literal exclamation mark is required, double it up to
+.Dq !!\&
+and it will be treated as a single literal
+.Dq !\& .
+When the command is executed, standard input and standard output are
+directed to the open device (see the
+.Dq set device
+command), and standard error is read by
+.Nm
+and substituted as the expect or send string.
+If
+.Nm
+is running in interactive mode, file descriptor 3 is attached to
+.Pa /dev/tty .
+.Pp
+For example (wrapped for readability):
+.Bd -literal -offset indent
+set login "TIMEOUT 5 \\"\\" \\"\\" login:--login: ppp \e
+word: ppp \\"!sh \\\\-c \\\\\\"echo \\\\-n label: >&2\\\\\\"\\" \e
+\\"!/bin/echo in\\" HELLO"
+.Ed
+.Pp
+would result in the following chat sequence (output using the
+.Sq set log local chat
+command before dialing):
+.Bd -literal -offset indent
+Dial attempt 1 of 1
+dial OK!
+Chat: Expecting:
+Chat: Sending:
+Chat: Expecting: login:--login:
+Chat: Wait for (5): login:
+Chat: Sending: ppp
+Chat: Expecting: word:
+Chat: Wait for (5): word:
+Chat: Sending: ppp
+Chat: Expecting: !sh \\-c "echo \\-n label: >&2"
+Chat: Exec: sh -c "echo -n label: >&2"
+Chat: Wait for (5): !sh \\-c "echo \\-n label: >&2" --> label:
+Chat: Exec: /bin/echo in
+Chat: Sending:
+Chat: Expecting: HELLO
+Chat: Wait for (5): HELLO
+login OK!
+.Ed
+.Pp
+Note (again) the use of the escape character, allowing many levels of
+nesting.
+Here, there are four parsers at work.
+The first parses the original line, reading it as three arguments.
+The second parses the third argument, reading it as 11 arguments.
+At this point, it is
+important that the
+.Dq \&-
+signs are escaped, otherwise this parser will see them as constituting
+an expect-send-expect sequence.
+When the
+.Dq !\&
+character is seen, the execution parser reads the first command as three
+arguments, and then
+.Xr sh 1
+itself expands the argument after the
+.Fl c .
+As we wish to send the output back to the modem, in the first example
+we redirect our output to file descriptor 2 (stderr) so that
+.Nm
+itself sends and logs it, and in the second example, we just output to stdout,
+which is attached directly to the modem.
+.Pp
+This, of course means that it is possible to execute an entirely external
+.Dq chat
+command rather than using the internal one.
+See
+.Xr chat 8
+for a good alternative.
+.Pp
+The external command that is executed is subjected to the same special
+word expansions as the
+.Dq !bg
+command.
+.It set enddisc Op label|IP|MAC|magic|psn value
+This command sets our local endpoint discriminator.
+If set prior to LCP negotiation, and if no
+.Dq disable enddisc
+command has been used,
+.Nm
+will send the information to the peer using the LCP endpoint discriminator
+option.
+The following discriminators may be set:
+.Bl -tag -width indent
+.It Li label
+The current label is used.
+.It Li IP
+Our local IP number is used.
+As LCP is negotiated prior to IPCP, it is
+possible that the IPCP layer will subsequently change this value.
+If
+it does, the endpoint discriminator stays at the old value unless manually
+reset.
+.It Li MAC
+This is similar to the
+.Ar IP
+option above, except that the MAC address associated with the local IP
+number is used.
+If the local IP number is not resident on any Ethernet
+interface, the command will fail.
+.Pp
+As the local IP number defaults to whatever the machine host name is,
+.Dq set enddisc mac
+is usually done prior to any
+.Dq set ifaddr
+commands.
+.It Li magic
+A 20 digit random number is used.
+Care should be taken when using magic numbers as restarting
+.Nm
+or creating a link using a different
+.Nm
+invocation will also use a different magic number and will therefore not
+be recognised by the peer as belonging to the same bundle.
+This makes it unsuitable for
+.Fl direct
+connections.
+.It Li psn Ar value
+The given
+.Ar value
+is used.
+.Ar Value
+should be set to an absolute public switched network number with the
+country code first.
+.El
+.Pp
+If no arguments are given, the endpoint discriminator is reset.
+.It set escape Ar value...
+This option is similar to the
+.Dq set accmap
+option above.
+It allows the user to specify a set of characters that will be
+.Sq escaped
+as they travel across the link.
+.It set filter dial|alive|in|out Ar rule-no Xo
+.No permit|deny|clear| Ns Ar rule-no
+.Op !\&
+.Oo Op host
+.Ar src_addr Ns Op / Ns Ar width
+.Op Ar dst_addr Ns Op / Ns Ar width
+.Oc [ Ns Ar proto
+.Op src lt|eq|gt Ar port
+.Op dst lt|eq|gt Ar port
+.Op estab
+.Op syn
+.Op finrst
+.Op timeout Ar secs ]
+.Xc
+.Nm
+supports four filter sets.
+The
+.Em alive
+filter specifies packets that keep the connection alive - resetting the
+idle timer.
+The
+.Em dial
+filter specifies packets that cause
+.Nm
+to dial when in
+.Fl auto
+mode.
+The
+.Em in
+filter specifies packets that are allowed to travel
+into the machine and the
+.Em out
+filter specifies packets that are allowed out of the machine.
+.Pp
+Filtering is done prior to any IP alterations that might be done by the
+NAT engine on outgoing packets and after any IP alterations that might
+be done by the NAT engine on incoming packets.
+By default all empty filter sets allow all packets to pass.
+Rules are processed in order according to
+.Ar rule-no
+(unless skipped by specifying a rule number as the
+.Ar action ) .
+Up to 40 rules may be given for each set.
+If a packet does not match
+any of the rules in a given set, it is discarded.
+In the case of
+.Em in
+and
+.Em out
+filters, this means that the packet is dropped.
+In the case of
+.Em alive
+filters it means that the packet will not reset the idle timer (even if
+the
+.Ar in Ns No / Ns Ar out
+filter has a
+.Dq timeout
+value) and in the case of
+.Em dial
+filters it means that the packet will not trigger a dial.
+A packet failing to trigger a dial will be dropped rather than queued.
+Refer to the
+section on
+.Sx PACKET FILTERING
+above for further details.
+.It set hangup Ar chat-script
+This specifies the chat script that will be used to reset the device
+before it is closed.
+It should not normally be necessary, but can
+be used for devices that fail to reset themselves properly on close.
+.It set help|? Op Ar command
+This command gives a summary of available set commands, or if
+.Ar command
+is specified, the command usage is shown.
+.It set ifaddr Oo Ar myaddr Ns
+.Op / Ns Ar \&nn
+.Oo Ar hisaddr Ns Op / Ns Ar \&nn
+.Oo Ar netmask
+.Op Ar triggeraddr
+.Oc Oc
+.Oc
+This command specifies the IP addresses that will be used during
+IPCP negotiation.
+Addresses are specified using the format
+.Pp
+.Dl a.b.c.d/nn
+.Pp
+Where
+.Dq a.b.c.d
+is the preferred IP, but
+.Ar nn
+specifies how many bits of the address we will insist on.
+If
+.No / Ns Ar nn
+is omitted, it defaults to
+.Dq /32
+unless the IP address is 0.0.0.0 in which case it defaults to
+.Dq /0 .
+.Pp
+If you wish to assign a dynamic IP number to the peer,
+.Ar hisaddr
+may also be specified as a range of IP numbers in the format
+.Bd -ragged -offset indent
+.Ar \&IP Ns Oo \&- Ns Ar \&IP Ns Oc Ns Oo , Ns Ar \&IP Ns
+.Oo \&- Ns Ar \&IP Ns Oc Oc Ns ...
+.Ed
+.Pp
+for example:
+.Pp
+.Dl set ifaddr 10.0.0.1 10.0.1.2-10.0.1.10,10.0.1.20
+.Pp
+will only negotiate
+.Dq 10.0.0.1
+as the local IP number, but may assign any of the given 10 IP
+numbers to the peer.
+If the peer requests one of these numbers,
+and that number is not already in use,
+.Nm
+will grant the peers request.
+This is useful if the peer wants
+to re-establish a link using the same IP number as was previously
+allocated (thus maintaining any existing tcp or udp connections).
+.Pp
+If the peer requests an IP number that is either outside
+of this range or is already in use,
+.Nm
+will suggest a random unused IP number from the range.
+.Pp
+If
+.Ar triggeraddr
+is specified, it is used in place of
+.Ar myaddr
+in the initial IPCP negotiation.
+However, only an address in the
+.Ar myaddr
+range will be accepted.
+This is useful when negotiating with some
+.Dv PPP
+implementations that will not assign an IP number unless their peer
+requests
+.Dq 0.0.0.0 .
+.Pp
+It should be noted that in
+.Fl auto
+mode,
+.Nm
+will configure the interface immediately upon reading the
+.Dq set ifaddr
+line in the config file.
+In any other mode, these values are just
+used for IPCP negotiations, and the interface is not configured
+until the IPCP layer is up.
+.Pp
+Note that the
+.Ar HISADDR
+argument may be overridden by the third field in the
+.Pa ppp.secret
+file once the client has authenticated itself
+(if PAP or CHAP are
+.Dq enabled ) .
+Refer to the
+.Sx AUTHENTICATING INCOMING CONNECTIONS
+section for details.
+.Pp
+In all cases, if the interface is already configured,
+.Nm
+will try to maintain the interface IP numbers so that any existing
+bound sockets will remain valid.
+.It set ifqueue Ar packets
+Set the maximum number of packets that
+.Nm
+will read from the tunnel interface while data cannot be sent to any of
+the available links.
+This queue limit is necessary to flow control outgoing data as the tunnel
+interface is likely to be far faster than the combined links available to
+.Nm .
+.Pp
+If
+.Ar packets
+is set to a value less than the number of links,
+.Nm
+will read up to that value regardless.
+This prevents any possible latency problems.
+.Pp
+The default value for
+.Ar packets
+is
+.Dq 30 .
+.It set ccpretry|ccpretries Oo Ar timeout
+.Op Ar reqtries Op Ar trmtries
+.Oc
+.It set chapretry|chapretries Oo Ar timeout
+.Op Ar reqtries
+.Oc
+.It set ipcpretry|ipcpretries Oo Ar timeout
+.Op Ar reqtries Op Ar trmtries
+.Oc
+.It set ipv6cpretry|ipv6cpretries Oo Ar timeout
+.Op Ar reqtries Op Ar trmtries
+.Oc
+.It set lcpretry|lcpretries Oo Ar timeout
+.Op Ar reqtries Op Ar trmtries
+.Oc
+.It set papretry|papretries Oo Ar timeout
+.Op Ar reqtries
+.Oc
+These commands set the number of seconds that
+.Nm
+will wait before resending Finite State Machine (FSM) Request packets.
+The default
+.Ar timeout
+for all FSMs is 3 seconds (which should suffice in most cases).
+.Pp
+If
+.Ar reqtries
+is specified, it tells
+.Nm
+how many configuration request attempts it should make while receiving
+no reply from the peer before giving up.
+The default is 5 attempts for
+CCP, LCP and IPCP and 3 attempts for PAP and CHAP.
+.Pp
+If
+.Ar trmtries
+is specified, it tells
+.Nm
+how many terminate requests should be sent before giving up waiting for the
+peers response.
+The default is 3 attempts.
+Authentication protocols are
+not terminated and it is therefore invalid to specify
+.Ar trmtries
+for PAP or CHAP.
+.Pp
+In order to avoid negotiations with the peer that will never converge,
+.Nm
+will only send at most 3 times the configured number of
+.Ar reqtries
+in any given negotiation session before giving up and closing that layer.
+.It set log Xo
+.Op local
+.Op +|- Ns
+.Ar value Ns No ...
+.Xc
+This command allows the adjustment of the current log level.
+Refer to the Logging Facility section for further details.
+.It set login Ar chat-script
+This
+.Ar chat-script
+compliments the dial-script.
+If both are specified, the login
+script will be executed after the dial script.
+Escape sequences available in the dial script are also available here.
+.It set logout Ar chat-script
+This specifies the chat script that will be used to logout
+before the hangup script is called.
+It should not normally be necessary.
+.It set lqrperiod|echoperiod Ar frequency
+This command sets the
+.Ar frequency
+in seconds at which
+.Em LQR
+or
+.Em LCP ECHO
+packets are sent.
+The default is 30 seconds.
+You must also use the
+.Dq enable lqr
+and/or
+.Dq enable echo
+commands if you wish to send
+.Em LQR
+or
+.Em LCP ECHO
+requests to the peer.
+.It set mode Ar interactive|auto|ddial|background
+This command allows you to change the
+.Sq mode
+of the specified link.
+This is normally only useful in multi-link mode,
+but may also be used in uni-link mode.
+.Pp
+It is not possible to change a link that is
+.Sq direct
+or
+.Sq dedicated .
+.Pp
+Note: If you issue the command
+.Dq set mode auto ,
+and have network address translation enabled, it may be useful to
+.Dq enable iface-alias
+afterwards.
+This will allow
+.Nm
+to do the necessary address translations to enable the process that
+triggers the connection to connect once the link is up despite the
+peer assigning us a new (dynamic) IP address.
+.It set mppe Op 40|56|128|* Op stateless|stateful|*
+This option selects the encryption parameters used when negotiation
+MPPE.
+MPPE can be disabled entirely with the
+.Dq disable mppe
+command.
+If no arguments are given,
+.Nm
+will attempt to negotiate a stateful link with a 128 bit key, but
+will agree to whatever the peer requests (including no encryption
+at all).
+.Pp
+If any arguments are given,
+.Nm
+will
+.Em insist
+on using MPPE and will close the link if it is rejected by the peer (Note;
+this behaviour can be overridden by a configured RADIUS server).
+.Pp
+The first argument specifies the number of bits that
+.Nm
+should insist on during negotiations and the second specifies whether
+.Nm
+should insist on stateful or stateless mode.
+In stateless mode, the
+encryption dictionary is re-initialised with every packet according to
+an encryption key that is changed with every packet.
+In stateful mode,
+the encryption dictionary is re-initialised every 256 packets or after
+the loss of any data and the key is changed every 256 packets.
+Stateless mode is less efficient but is better for unreliable transport
+layers.
+.It set mrru Op Ar value
+Setting this option enables Multi-link PPP negotiations, also known as
+Multi-link Protocol or MP.
+There is no default MRRU (Maximum Reconstructed Receive Unit) value.
+If no argument is given, multi-link mode is disabled.
+.It set mru Xo
+.Op max Ns Op imum
+.Op Ar value
+.Xc
+The default MRU (Maximum Receive Unit) is 1500.
+If it is increased, the other side *may* increase its MTU.
+In theory there is no point in decreasing the MRU to below the default as the
+.Em PPP
+protocol says implementations *must* be able to accept packets of at
+least 1500 octets.
+.Pp
+If the
+.Dq maximum
+keyword is used,
+.Nm
+will refuse to negotiate a higher value.
+The maximum MRU can be set to 2048 at most.
+Setting a maximum of less than 1500 violates the
+.Em PPP
+rfc, but may sometimes be necessary.
+For example,
+.Em PPPoE
+imposes a maximum of 1492 due to hardware limitations.
+.Pp
+If no argument is given, 1500 is assumed.
+A value must be given when
+.Dq maximum
+is specified.
+.It set mtu Xo
+.Op max Ns Op imum
+.Op Ar value
+.Xc
+The default MTU is 1500.
+At negotiation time,
+.Nm
+will accept whatever MRU the peer requests (assuming it is
+not less than 296 bytes or greater than the assigned maximum).
+If the MTU is set,
+.Nm
+will not accept MRU values less than
+.Ar value .
+When negotiations are complete, the MTU is used when writing to the
+interface, even if the peer requested a higher value MRU.
+This can be useful for
+limiting your packet size (giving better bandwidth sharing at the expense
+of more header data).
+.Pp
+If the
+.Dq maximum
+keyword is used,
+.Nm
+will refuse to negotiate a higher value.
+The maximum MTU can be set to 2048 at most.
+Note, it is necessary to use the
+.Dq maximum
+keyword to limit the MTU when using PPPoE.
+.Pp
+If no
+.Ar value
+is given, 1500, or whatever the peer asks for is used.
+A value must be given when
+.Dq maximum
+is specified.
+.It set nbns Op Ar x.x.x.x Op Ar y.y.y.y
+This option allows the setting of the Microsoft NetBIOS name server
+values to be returned at the peers request.
+If no values are given,
+.Nm
+will reject any such requests.
+.It set openmode active|passive Op Ar delay
+By default,
+.Ar openmode
+is always
+.Ar active
+with a one second
+.Ar delay .
+That is,
+.Nm
+will always initiate LCP/IPCP/CCP negotiation one second after the line
+comes up.
+If you want to wait for the peer to initiate negotiations, you
+can use the value
+.Ar passive .
+If you want to initiate negotiations immediately or after more than one
+second, the appropriate
+.Ar delay
+may be specified here in seconds.
+.It set parity odd|even|none|mark
+This allows the line parity to be set.
+The default value is
+.Ar none .
+.It set phone Ar telno Ns Xo
+.Oo \&| Ns Ar backupnumber Oc Ns ... Ns Oo : Ns Ar nextnumber Oc Ns ... Xc
+This allows the specification of the phone number to be used in
+place of the \\\\T string in the dial and login chat scripts.
+Multiple phone numbers may be given separated either by a pipe
+.Pq Dq \&|
+or a colon
+.Pq Dq \&: .
+.Pp
+Numbers after the pipe are only dialed if the dial or login
+script for the previous number failed.
+.Pp
+Numbers after the colon are tried sequentially, irrespective of
+the reason the line was dropped.
+.Pp
+If multiple numbers are given,
+.Nm
+will dial them according to these rules until a connection is made, retrying
+the maximum number of times specified by
+.Dq set redial
+below.
+In
+.Fl background
+mode, each number is attempted at most once.
+.It set pppoe Op standard|3Com
+This option configures the underlying
+.Xr ng_pppoe 4
+node to either standard RFC2516 PPPoE or proprietary 3Com mode.
+If not set the system default will be used.
+.It set Oo proc Oc Ns Xo
+.No title Op Ar value
+.Xc
+The current process title as displayed by
+.Xr ps 1
+is changed according to
+.Ar value .
+If
+.Ar value
+is not specified, the original process title is restored.
+All the
+word replacements done by the shell commands (see the
+.Dq bg
+command above) are done here too.
+.Pp
+Note, if USER is required in the process title, the
+.Dq set proctitle
+command must appear in
+.Pa ppp.linkup ,
+as it is not known when the commands in
+.Pa ppp.conf
+are executed.
+.It set radius Op Ar config-file
+This command enables RADIUS support (if it is compiled in).
+.Ar config-file
+refers to the radius client configuration file as described in
+.Xr radius.conf 5 .
+If PAP, CHAP, MSCHAP or MSCHAPv2 are
+.Dq enable Ns No d ,
+.Nm
+behaves as a
+.Em \&N Ns No etwork
+.Em \&A Ns No ccess
+.Em \&S Ns No erver
+and uses the configured RADIUS server to authenticate rather than
+authenticating from the
+.Pa ppp.secret
+file or from the passwd database.
+.Pp
+If none of PAP, CHAP, MSCHAP or MSCHAPv2 are enabled,
+.Dq set radius
+will do nothing.
+.Pp
+.Nm
+uses the following attributes from the RADIUS reply:
+.Bl -tag -width XXX -offset XXX
+.It RAD_FRAMED_IP_ADDRESS
+The peer IP address is set to the given value.
+.It RAD_FRAMED_IP_NETMASK
+The tun interface netmask is set to the given value.
+.It RAD_FRAMED_MTU
+If the given MTU is less than the peers MRU as agreed during LCP
+negotiation, *and* it is less that any configured MTU (see the
+.Dq set mru
+command), the tun interface MTU is set to the given value.
+.It RAD_FRAMED_COMPRESSION
+If the received compression type is
+.Dq 1 ,
+.Nm
+will request VJ compression during IPCP negotiations despite any
+.Dq disable vj
+configuration command.
+.It RAD_FILTER_ID
+If this attribute is supplied,
+.Nm
+will attempt to use it as an additional label to load from the
+.Pa ppp.linkup
+and
+.Pa ppp.linkdown
+files.
+The load will be attempted before (and in addition to) the normal
+label search.
+If the label does not exist, no action is taken and
+.Nm
+proceeds to the normal load using the current label.
+.It RAD_FRAMED_ROUTE
+The received string is expected to be in the format
+.Ar dest Ns Op / Ns Ar bits
+.Ar gw
+.Op Ar metrics .
+Any specified metrics are ignored.
+.Dv MYADDR
+and
+.Dv HISADDR
+are understood as valid values for
+.Ar dest
+and
+.Ar gw ,
+.Dq default
+can be used for
+.Ar dest
+to specify the default route, and
+.Dq 0.0.0.0
+is understood to be the same as
+.Dq default
+for
+.Ar dest
+and
+.Dv HISADDR
+for
+.Ar gw .
+.Pp
+For example, a returned value of
+.Dq 1.2.3.4/24 0.0.0.0 1 2 -1 3 400
+would result in a routing table entry to the 1.2.3.0/24 network via
+.Dv HISADDR
+and a returned value of
+.Dq 0.0.0.0 0.0.0.0
+or
+.Dq default HISADDR
+would result in a default route to
+.Dv HISADDR .
+.Pp
+All RADIUS routes are applied after any sticky routes are applied, making
+RADIUS routes override configured routes.
+This also applies for RADIUS routes that do not {include} the
+.Dv MYADDR
+or
+.Dv HISADDR
+keywords.
+.It RAD_FRAMED_IPV6_PREFIX
+If this attribute is supplied, the value is substituted for IPV6PREFIX
+in a command.
+You may pass it to an upper layer protocol such as DHCPv6 for delegating an
+IPv6 prefix to a peer.
+.It RAD_FRAMED_IPV6_ROUTE
+The received string is expected to be in the format
+.Ar dest Ns Op / Ns Ar bits
+.Ar gw
+.Op Ar metrics .
+Any specified metrics are ignored.
+.Dv MYADDR6
+and
+.Dv HISADDR6
+are understood as valid values for
+.Ar dest
+and
+.Ar gw ,
+.Dq default
+can be used for
+.Ar dest
+to specify the default route, and
+.Dq ::
+is understood to be the same as
+.Dq default
+for
+.Ar dest
+and
+.Dv HISADDR6
+for
+.Ar gw .
+.Pp
+For example, a returned value of
+.Dq 3ffe:505:abcd::/48 ::
+would result in a routing table entry to the 3ffe:505:abcd::/48 network via
+.Dv HISADDR6
+and a returned value of
+.Dq :: ::
+or
+.Dq default HISADDR6
+would result in a default route to
+.Dv HISADDR6 .
+.Pp
+All RADIUS IPv6 routes are applied after any sticky routes are
+applied, making RADIUS IPv6 routes override configured routes.
+This
+also applies for RADIUS IPv6 routes that do not {include} the
+.Dv MYADDR6
+or
+.Dv HISADDR6
+keywords.
+.It RAD_SESSION_TIMEOUT
+If supplied, the client connection is closed after the given number of
+seconds.
+.It RAD_REPLY_MESSAGE
+If supplied, this message is passed back to the peer as the authentication
+SUCCESS text.
+.It RAD_MICROSOFT_MS_CHAP_ERROR
+If this
+.Dv RAD_VENDOR_MICROSOFT
+vendor specific attribute is supplied, it is passed back to the peer as the
+authentication FAILURE text.
+.It RAD_MICROSOFT_MS_CHAP2_SUCCESS
+If this
+.Dv RAD_VENDOR_MICROSOFT
+vendor specific attribute is supplied and if MS-CHAPv2 authentication is
+being used, it is passed back to the peer as the authentication SUCCESS text.
+.It RAD_MICROSOFT_MS_MPPE_ENCRYPTION_POLICY
+If this
+.Dv RAD_VENDOR_MICROSOFT
+vendor specific attribute is supplied and has a value of 2 (Required),
+.Nm
+will insist that MPPE encryption is used (even if no
+.Dq set mppe
+configuration command has been given with arguments).
+If it is supplied with a value of 1 (Allowed), encryption is made optional
+(despite any
+.Dq set mppe
+configuration commands with arguments).
+.It RAD_MICROSOFT_MS_MPPE_ENCRYPTION_TYPES
+If this
+.Dv RAD_VENDOR_MICROSOFT
+vendor specific attribute is supplied, bits 1 and 2 are examined.
+If either or both are set, 40 bit and/or 128 bit (respectively) encryption
+options are set, overriding any given first argument to the
+.Dq set mppe
+command.
+Note, it is not currently possible for the RADIUS server to specify 56 bit
+encryption.
+.It RAD_MICROSOFT_MS_MPPE_RECV_KEY
+If this
+.Dv RAD_VENDOR_MICROSOFT
+vendor specific attribute is supplied, it is value is used as the master
+key for decryption of incoming data.
+When clients are authenticated using
+MSCHAPv2, the RADIUS server MUST provide this attribute if inbound MPPE is
+to function.
+.It RAD_MICROSOFT_MS_MPPE_SEND_KEY
+If this
+.Dv RAD_VENDOR_MICROSOFT
+vendor specific attribute is supplied, it is value is used as the master
+key for encryption of outgoing data.
+When clients are authenticated using
+MSCHAPv2, the RADIUS server MUST provide this attribute if outbound MPPE is
+to function.
+.El
+.Pp
+Values received from the RADIUS server may be viewed using
+.Dq show bundle .
+.It set rad_alive Ar timeout
+When RADIUS is configured, setting
+.Dq rad_alive
+to a non-zero
+.Ar timeout
+value will tell
+.Nm
+to sent RADIUS accounting information to the RADIUS server every
+.Ar timeout
+seconds.
+.It set rad_port_id Ar option
+When RADIUS is configured, setting the
+.Dq rad_port_id
+value allows to specify what should be sent to the RADIUS server as
+NAS-Port-Id.
+The
+.Ar option Ns No s
+are as follows:
+.Bl -tag -width Ds
+.It pid
+PID of the corresponding tunnel.
+.It tunnum
+.Xr tun 4
+interface number.
+.It ifnum
+index of the interface as returned by
+.Xr if_nametoindex 3 .
+.It default
+keeps the default behavior.
+.El
+.It set reconnect Ar timeout ntries
+Should the line drop unexpectedly (due to loss of CD or LQR
+failure), a connection will be re-established after the given
+.Ar timeout .
+The line will be re-connected at most
+.Ar ntries
+times.
+.Ar Ntries
+defaults to zero.
+A value of
+.Ar random
+for
+.Ar timeout
+will result in a variable pause, somewhere between 1 and 30 seconds.
+.It set recvpipe Op Ar value
+This sets the routing table RECVPIPE value.
+The optimum value is just over twice the MTU value.
+If
+.Ar value
+is unspecified or zero, the default kernel controlled value is used.
+.It set redial Ar secs Ns Xo
+.Oo + Ns Ar inc Ns
+.Oo - Ns Ar max Ns Oc Oc Ns
+.Op . Ns Ar next
+.Op Ar attempts
+.Xc
+.Nm
+can be instructed to attempt to redial
+.Ar attempts
+times.
+If more than one phone number is specified (see
+.Dq set phone
+above), a pause of
+.Ar next
+is taken before dialing each number.
+A pause of
+.Ar secs
+is taken before starting at the first number again.
+A literal value of
+.Dq Li random
+may be used here in place of
+.Ar secs
+and
+.Ar next ,
+causing a random delay of between 1 and 30 seconds.
+.Pp
+If
+.Ar inc
+is specified, its value is added onto
+.Ar secs
+each time
+.Nm
+tries a new number.
+.Ar secs
+will only be incremented at most
+.Ar max
+times.
+.Ar max
+defaults to 10.
+.Pp
+Note, the
+.Ar secs
+delay will be effective, even after
+.Ar attempts
+has been exceeded, so an immediate manual dial may appear to have
+done nothing.
+If an immediate dial is required, a
+.Dq !\&
+should immediately follow the
+.Dq open
+keyword.
+See the
+.Dq open
+description above for further details.
+.It set sendpipe Op Ar value
+This sets the routing table SENDPIPE value.
+The optimum value is just over twice the MTU value.
+If
+.Ar value
+is unspecified or zero, the default kernel controlled value is used.
+.It "set server|socket" Ar TcpPort Ns No \&| Ns Xo
+.Ar LocalName Ns No |none|open|closed
+.Op password Op Ar mask
+.Xc
+This command tells
+.Nm
+to listen on the given socket or
+.Sq diagnostic port
+for incoming command connections.
+.Pp
+The word
+.Dq none
+instructs
+.Nm
+to close any existing socket and clear the socket configuration.
+The word
+.Dq open
+instructs
+.Nm
+to attempt to re-open the port.
+The word
+.Dq closed
+instructs
+.Nm
+to close the open port.
+.Pp
+If you wish to specify a local domain socket,
+.Ar LocalName
+must be specified as an absolute file name, otherwise it is assumed
+to be the name or number of a TCP port.
+You may specify the octal umask to be used with a local domain socket.
+Refer to
+.Xr umask 2
+for umask details.
+Refer to
+.Xr services 5
+for details of how to translate TCP port names.
+.Pp
+You must also specify the password that must be entered by the client
+(using the
+.Dq passwd
+variable above) when connecting to this socket.
+If the password is
+specified as an empty string, no password is required for connecting clients.
+.Pp
+When specifying a local domain socket, the first
+.Dq %d
+sequence found in the socket name will be replaced with the current
+interface unit number.
+This is useful when you wish to use the same
+profile for more than one connection.
+.Pp
+In a similar manner TCP sockets may be prefixed with the
+.Dq +
+character, in which case the current interface unit number is added to
+the port number.
+.Pp
+When using
+.Nm
+with a server socket, the
+.Xr pppctl 8
+command is the preferred mechanism of communications.
+Currently,
+.Xr telnet 1
+can also be used, but link encryption may be implemented in the future, so
+.Xr telnet 1
+should be avoided.
+.Pp
+Note;
+.Dv SIGUSR1
+and
+.Dv SIGUSR2
+interact with the diagnostic socket.
+.It set speed Ar value
+This sets the speed of the serial device.
+If speed is specified as
+.Dq sync ,
+.Nm
+treats the device as a synchronous device.
+.Pp
+Certain device types will know whether they should be specified as
+synchronous or asynchronous.
+These devices will override incorrect
+settings and log a warning to this effect.
+.It set stopped Op Ar LCPseconds Op Ar CCPseconds
+If this option is set,
+.Nm
+will time out after the given FSM (Finite State Machine) has been in
+the stopped state for the given number of
+.Dq seconds .
+This option may be useful if the peer sends a terminate request,
+but never actually closes the connection despite our sending a terminate
+acknowledgement.
+This is also useful if you wish to
+.Dq set openmode passive
+and time out if the peer does not send a Configure Request within the
+given time.
+Use
+.Dq set log +lcp +ccp
+to make
+.Nm
+log the appropriate state transitions.
+.Pp
+The default value is zero, where
+.Nm
+does not time out in the stopped state.
+.Pp
+This value should not be set to less than the openmode delay (see
+.Dq set openmode
+above).
+.It set timeout Ar idleseconds Op Ar mintimeout
+This command allows the setting of the idle timer.
+Refer to the section titled
+.Sx SETTING THE IDLE TIMER
+for further details.
+.Pp
+If
+.Ar mintimeout
+is specified,
+.Nm
+will never idle out before the link has been up for at least that number
+of seconds.
+.It set urgent Xo
+.Op tcp|udp|none
+.Oo Op +|- Ns
+.Ar port
+.Oc No ...
+.Xc
+This command controls the ports that
+.Nm
+prioritizes when transmitting data.
+The default priority TCP ports
+are ports 21 (ftp control), 22 (ssh), 23 (telnet), 513 (login), 514 (shell),
+543 (klogin) and 544 (kshell).
+There are no priority UDP ports by default.
+See
+.Xr services 5
+for details.
+.Pp
+If neither
+.Dq tcp
+or
+.Dq udp
+are specified,
+.Dq tcp
+is assumed.
+.Pp
+If no
+.Ar port Ns No s
+are given, the priority port lists are cleared (although if
+.Dq tcp
+or
+.Dq udp
+is specified, only that list is cleared).
+If the first
+.Ar port
+argument is prefixed with a plus
+.Pq Dq \&+
+or a minus
+.Pq Dq \&- ,
+the current list is adjusted, otherwise the list is reassigned.
+.Ar port Ns No s
+prefixed with a plus or not prefixed at all are added to the list and
+.Ar port Ns No s
+prefixed with a minus are removed from the list.
+.Pp
+If
+.Dq none
+is specified, all priority port lists are disabled and even
+.Dv IPTOS_LOWDELAY
+packets are not prioritised.
+.It set vj slotcomp on|off
+This command tells
+.Nm
+whether it should attempt to negotiate VJ slot compression.
+By default, slot compression is turned
+.Ar on .
+.It set vj slots Ar nslots
+This command sets the initial number of slots that
+.Nm
+will try to negotiate with the peer when VJ compression is enabled (see the
+.Sq enable
+command above).
+It defaults to a value of 16.
+.Ar Nslots
+must be between
+.Ar 4
+and
+.Ar 16
+inclusive.
+.El
+.It shell|! Op Ar command
+If
+.Ar command
+is not specified a shell is invoked according to the
+.Dv SHELL
+environment variable.
+Otherwise, the given
+.Ar command
+is executed.
+Word replacement is done in the same way as for the
+.Dq !bg
+command as described above.
+.Pp
+Use of the !\& character
+requires a following space as with any of the other commands.
+You should note that this command is executed in the foreground;
+.Nm
+will not continue running until this process has exited.
+Use the
+.Dv bg
+command if you wish processing to happen in the background.
+.It show Ar var
+This command allows the user to examine the following:
+.Bl -tag -width 2n
+.It show bundle
+Show the current bundle settings.
+.It show ccp
+Show the current CCP compression statistics.
+.It show compress
+Show the current VJ compression statistics.
+.It show escape
+Show the current escape characters.
+.It show filter Op Ar name
+List the current rules for the given filter.
+If
+.Ar name
+is not specified, all filters are shown.
+.It show hdlc
+Show the current HDLC statistics.
+.It show help|?
+Give a summary of available show commands.
+.It show iface
+Show the current interface information
+(the same as
+.Dq iface show ) .
+.It show ipcp
+Show the current IPCP statistics.
+.It show layers
+Show the protocol layers currently in use.
+.It show lcp
+Show the current LCP statistics.
+.It show Oo data Oc Ns Xo
+.No link
+.Xc
+Show high level link information.
+.It show links
+Show a list of available logical links.
+.It show log
+Show the current log values.
+.It show mem
+Show current memory statistics.
+.It show ncp
+Show the current NCP statistics.
+.It show physical
+Show low level link information.
+.It show mp
+Show Multi-link information.
+.It show proto
+Show current protocol totals.
+.It show route
+Show the current routing tables.
+.It show stopped
+Show the current stopped timeouts.
+.It show timer
+Show the active alarm timers.
+.It show version
+Show the current version number of
+.Nm .
+.El
+.It term
+Go into terminal mode.
+Characters typed at the keyboard are sent to the device.
+Characters read from the device are displayed on the screen.
+When a remote
+.Em PPP
+peer is detected,
+.Nm
+automatically enables Packet Mode and goes back into command mode.
+.El
+.Sh MORE DETAILS
+.Bl -bullet
+.It
+Read the example configuration files.
+They are a good source of information.
+.It
+Use
+.Dq help ,
+.Dq nat \&? ,
+.Dq enable \&? ,
+.Dq set ?\&
+and
+.Dq show ?\&
+to get online information about what is available.
+.It
+The following URLs contain useful information:
+.Bl -bullet -compact
+.It
+http://www.FreeBSD.org/doc/en_US.ISO8859-1/books/faq/ppp.html
+.It
+http://www.FreeBSD.org/doc/handbook/userppp.html
+.El
+.El
+.Sh FILES
+.Nm
+refers to four files:
+.Pa ppp.conf ,
+.Pa ppp.linkup ,
+.Pa ppp.linkdown
+and
+.Pa ppp.secret .
+These files are placed in the
+.Pa /etc/ppp
+directory.
+.Bl -tag -width 2n
+.It Pa /etc/ppp/ppp.conf
+System default configuration file.
+.It Pa /etc/ppp/ppp.secret
+An authorisation file for each system.
+.It Pa /etc/ppp/ppp.linkup
+A file to check when
+.Nm
+establishes a network level connection.
+.It Pa /etc/ppp/ppp.linkdown
+A file to check when
+.Nm
+closes a network level connection.
+.It Pa /var/log/ppp.log
+Logging and debugging information file.
+Note, this name is specified in
+.Pa /etc/syslog.conf .
+See
+.Xr syslog.conf 5
+for further details.
+.It Pa /var/spool/lock/LCK..*
+tty port locking file.
+Refer to
+.Xr uucplock 3
+for further details.
+.It Pa /var/run/tunN.pid
+The process id (pid) of the
+.Nm
+program connected to the tunN device, where
+.Sq N
+is the number of the device.
+.It Pa /var/run/ttyXX.if
+The tun interface used by this port.
+Again, this file is only created in
+.Fl background ,
+.Fl auto
+and
+.Fl ddial
+modes.
+.It Pa /etc/services
+Get port number if port number is using service name.
+.It Pa /var/run/ppp-authname-class-value
+In multi-link mode, local domain sockets are created using the peer
+authentication name
+.Pq Sq authname ,
+the peer endpoint discriminator class
+.Pq Sq class
+and the peer endpoint discriminator value
+.Pq Sq value .
+As the endpoint discriminator value may be a binary value, it is turned
+to HEX to determine the actual file name.
+.Pp
+This socket is used to pass links between different instances of
+.Nm .
+.El
+.Sh SEE ALSO
+.Xr at 1 ,
+.Xr ftp 1 ,
+.Xr gzip 1 ,
+.Xr hostname 1 ,
+.Xr login 1 ,
+.Xr tcpdump 1 ,
+.Xr telnet 1 ,
+.Xr kldload 2 ,
+.Xr pipe 2 ,
+.Xr socketpair 2 ,
+.Xr libalias 3 ,
+.Xr libradius 3 ,
+.Xr syslog 3 ,
+.Xr uucplock 3 ,
+.Xr netgraph 4 ,
+.Xr ng_pppoe 4 ,
+.Xr crontab 5 ,
+.Xr group 5 ,
+.Xr passwd 5 ,
+.Xr protocols 5 ,
+.Xr radius.conf 5 ,
+.Xr resolv.conf 5 ,
+.Xr syslog.conf 5 ,
+.Xr adduser 8 ,
+.Xr chat 8 ,
+.Xr getty 8 ,
+.Xr inetd 8 ,
+.Xr init 8 ,
+.Xr named 8 ,
+.Xr ping 8 ,
+.Xr pppctl 8 ,
+.Xr pppoed 8 ,
+.Xr route 8 ,
+.Xr sshd 8 ,
+.Xr syslogd 8 ,
+.Xr traceroute 8 ,
+.Xr vipw 8
+.Sh HISTORY
+This program was originally written by
+.An Toshiharu OHNO Aq Mt tony-o@iij.ad.jp ,
+and was submitted to
+.Fx 2.0.5
+by
+.An Atsushi Murai Aq Mt amurai@spec.co.jp .
+.Pp
+It was substantially modified during 1997 by
+.An Brian Somers Aq Mt brian@Awfulhak.org ,
+and was ported to
+.Ox
+in November that year
+(just after the 2.2 release).
+.Pp
+Most of the code was rewritten by
+.An Brian Somers
+in early 1998 when multi-link ppp support was added.
diff --git a/usr.sbin/ppp/ppp.conf b/usr.sbin/ppp/ppp.conf
new file mode 100644
index 0000000..2b63834
--- /dev/null
+++ b/usr.sbin/ppp/ppp.conf
@@ -0,0 +1,37 @@
+#################################################################
+# PPP Sample Configuration File
+# Originally written by Toshiharu OHNO
+# Simplified 5/14/1999 by wself@cdrom.com
+#
+# See /usr/share/examples/ppp/ for some examples
+#
+# $FreeBSD$
+#################################################################
+
+default:
+ set log Phase Chat LCP IPCP CCP tun command
+ ident user-ppp VERSION
+
+ # Ensure that "device" references the correct serial port
+ # for your modem. (cuau0 = COM1, cuau1 = COM2)
+ #
+ set device /dev/cuau1
+
+ set speed 115200
+ set dial "ABORT BUSY ABORT NO\\sCARRIER TIMEOUT 5 \
+ \"\" AT OK-AT-OK ATE1Q0 OK \\dATDT\\T TIMEOUT 40 CONNECT"
+ set timeout 180 # 3 minute idle timer (the default)
+ enable dns # request DNS info (for resolv.conf)
+
+papchap:
+ #
+ # edit the next three lines and replace the items in caps with
+ # the values which have been assigned by your ISP.
+ #
+
+ set phone PHONE_NUM
+ set authname USERNAME
+ set authkey PASSWORD
+
+ set ifaddr 10.0.0.1/0 10.0.0.2/0 255.255.255.0 0.0.0.0
+ add default HISADDR # Add a (sticky) default route
diff --git a/usr.sbin/ppp/pred.c b/usr.sbin/ppp/pred.c
new file mode 100644
index 0000000..43d5d94
--- /dev/null
+++ b/usr.sbin/ppp/pred.c
@@ -0,0 +1,345 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
+ * Ian Donaldson <iand@labtam.labtam.oz.au>
+ * Carsten Bormann <cabo@cs.tu-berlin.de>
+ * Dave Rand <dlr@bungi.com>/<dave_rand@novell.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$
+ */
+
+#include <sys/types.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+
+#include "defs.h"
+#include "layer.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "throughput.h"
+#include "link.h"
+#include "pred.h"
+
+/* The following hash code is the heart of the algorithm:
+ * It builds a sliding hash sum of the previous 3-and-a-bit characters
+ * which will be used to index the guess table.
+ * A better hash function would result in additional compression,
+ * at the expense of time.
+ */
+#define HASH(state, x) state->hash = (state->hash << 4) ^ (x)
+#define GUESS_TABLE_SIZE 65536
+
+struct pred1_state {
+ u_short hash;
+ u_char dict[GUESS_TABLE_SIZE];
+};
+
+static int
+compress(struct pred1_state *state, u_char *source, u_char *dest, int len)
+{
+ int i, bitmask;
+ unsigned char *flagdest, flags, *orgdest;
+
+ orgdest = dest;
+ while (len) {
+ flagdest = dest++;
+ flags = 0; /* All guess wrong initially */
+ for (bitmask = 1, i = 0; i < 8 && len; i++, bitmask <<= 1) {
+ if (state->dict[state->hash] == *source) {
+ flags |= bitmask; /* Guess was right - don't output */
+ } else {
+ state->dict[state->hash] = *source;
+ *dest++ = *source; /* Guess wrong, output char */
+ }
+ HASH(state, *source++);
+ len--;
+ }
+ *flagdest = flags;
+ }
+ return (dest - orgdest);
+}
+
+static void
+SyncTable(struct pred1_state *state, u_char *source, u_char *dest, int len)
+{
+ while (len--) {
+ *dest++ = state->dict[state->hash] = *source;
+ HASH(state, *source++);
+ }
+}
+
+static int
+decompress(struct pred1_state *state, u_char *source, u_char *dest, int len)
+{
+ int i, bitmask;
+ unsigned char flags, *orgdest;
+
+ orgdest = dest;
+ while (len) {
+ flags = *source++;
+ len--;
+ for (i = 0, bitmask = 1; i < 8; i++, bitmask <<= 1) {
+ if (flags & bitmask) {
+ *dest = state->dict[state->hash]; /* Guess correct */
+ } else {
+ if (!len)
+ break; /* we seem to be really done -- cabo */
+ state->dict[state->hash] = *source; /* Guess wrong */
+ *dest = *source++; /* Read from source */
+ len--;
+ }
+ HASH(state, *dest++);
+ }
+ }
+ return (dest - orgdest);
+}
+
+static void
+Pred1Term(void *v)
+{
+ struct pred1_state *state = (struct pred1_state *)v;
+ free(state);
+}
+
+static void
+Pred1ResetInput(void *v)
+{
+ struct pred1_state *state = (struct pred1_state *)v;
+ state->hash = 0;
+ memset(state->dict, '\0', sizeof state->dict);
+ log_Printf(LogCCP, "Predictor1: Input channel reset\n");
+}
+
+static int
+Pred1ResetOutput(void *v)
+{
+ struct pred1_state *state = (struct pred1_state *)v;
+ state->hash = 0;
+ memset(state->dict, '\0', sizeof state->dict);
+ log_Printf(LogCCP, "Predictor1: Output channel reset\n");
+
+ return 1; /* Ask FSM to ACK */
+}
+
+static void *
+Pred1InitInput(struct bundle *bundle __unused, struct fsm_opt *o __unused)
+{
+ struct pred1_state *state;
+ state = (struct pred1_state *)malloc(sizeof(struct pred1_state));
+ if (state != NULL)
+ Pred1ResetInput(state);
+ return state;
+}
+
+static void *
+Pred1InitOutput(struct bundle *bundle __unused, struct fsm_opt *o __unused)
+{
+ struct pred1_state *state;
+ state = (struct pred1_state *)malloc(sizeof(struct pred1_state));
+ if (state != NULL)
+ Pred1ResetOutput(state);
+ return state;
+}
+
+static struct mbuf *
+Pred1Output(void *v, struct ccp *ccp, struct link *l __unused,
+ int pri __unused, u_short *proto, struct mbuf *bp)
+{
+ struct pred1_state *state = (struct pred1_state *)v;
+ struct mbuf *mwp;
+ u_char *cp, *wp, *hp;
+ int orglen, len;
+ u_char bufp[MAX_MTU + 2];
+ u_short fcs;
+
+ orglen = m_length(bp) + 2; /* add count of proto */
+ mwp = m_get((orglen + 2) / 8 * 9 + 12, MB_CCPOUT);
+ hp = wp = MBUF_CTOP(mwp);
+ cp = bufp;
+ *wp++ = *cp++ = orglen >> 8;
+ *wp++ = *cp++ = orglen & 0377;
+ *cp++ = *proto >> 8;
+ *cp++ = *proto & 0377;
+ mbuf_Read(bp, cp, orglen - 2);
+ fcs = hdlc_Fcs(bufp, 2 + orglen);
+ fcs = ~fcs;
+
+ len = compress(state, bufp + 2, wp, orglen);
+ log_Printf(LogDEBUG, "Pred1Output: orglen (%d) --> len (%d)\n", orglen, len);
+ ccp->uncompout += orglen;
+ if (len < orglen) {
+ *hp |= 0x80;
+ wp += len;
+ ccp->compout += len;
+ } else {
+ memcpy(wp, bufp + 2, orglen);
+ wp += orglen;
+ ccp->compout += orglen;
+ }
+
+ *wp++ = fcs & 0377;
+ *wp++ = fcs >> 8;
+ mwp->m_len = wp - MBUF_CTOP(mwp);
+ *proto = ccp_Proto(ccp);
+ return mwp;
+}
+
+static struct mbuf *
+Pred1Input(void *v, struct ccp *ccp, u_short *proto, struct mbuf *bp)
+{
+ struct pred1_state *state = (struct pred1_state *)v;
+ u_char *cp, *pp;
+ int len, olen, len1;
+ struct mbuf *wp;
+ u_char *bufp;
+ u_short fcs;
+
+ wp = m_get(MAX_MRU + 2, MB_CCPIN);
+ cp = MBUF_CTOP(bp);
+ olen = m_length(bp);
+ pp = bufp = MBUF_CTOP(wp);
+ *pp++ = *cp & 0177;
+ len = *cp++ << 8;
+ *pp++ = *cp;
+ len += *cp++;
+ ccp->uncompin += len & 0x7fff;
+ if (len & 0x8000) {
+ len1 = decompress(state, cp, pp, olen - 4);
+ ccp->compin += olen;
+ len &= 0x7fff;
+ if (len != len1) { /* Error is detected. Send reset request */
+ log_Printf(LogCCP, "Pred1: Length error (got %d, not %d)\n", len1, len);
+ fsm_Reopen(&ccp->fsm);
+ m_freem(bp);
+ m_freem(wp);
+ return NULL;
+ }
+ cp += olen - 4;
+ pp += len1;
+ } else if (len + 4 != olen) {
+ log_Printf(LogCCP, "Pred1: Length error (got %d, not %d)\n", len + 4, olen);
+ fsm_Reopen(&ccp->fsm);
+ m_freem(wp);
+ m_freem(bp);
+ return NULL;
+ } else {
+ ccp->compin += len;
+ SyncTable(state, cp, pp, len);
+ cp += len;
+ pp += len;
+ }
+ *pp++ = *cp++; /* CRC */
+ *pp++ = *cp++;
+ fcs = hdlc_Fcs(bufp, wp->m_len = pp - bufp);
+ if (fcs == GOODFCS) {
+ wp->m_offset += 2; /* skip length */
+ wp->m_len -= 4; /* skip length & CRC */
+ pp = MBUF_CTOP(wp);
+ *proto = *pp++;
+ if (*proto & 1) {
+ wp->m_offset++;
+ wp->m_len--;
+ } else {
+ wp->m_offset += 2;
+ wp->m_len -= 2;
+ *proto = (*proto << 8) | *pp++;
+ }
+ m_freem(bp);
+ return wp;
+ } else {
+ const char *pre = *MBUF_CTOP(bp) & 0x80 ? "" : "un";
+ log_Printf(LogDEBUG, "Pred1Input: fcs = 0x%04x (%scompressed), len = 0x%x,"
+ " olen = 0x%x\n", fcs, pre, len, olen);
+ log_Printf(LogCCP, "%s: Bad %scompressed CRC-16\n",
+ ccp->fsm.link->name, pre);
+ fsm_Reopen(&ccp->fsm);
+ m_freem(wp);
+ }
+ m_freem(bp);
+ return NULL;
+}
+
+static void
+Pred1DictSetup(void *v __unused, struct ccp *ccp __unused,
+ u_short proto __unused, struct mbuf *bp __unused)
+{
+ /* Nothing to see here */
+}
+
+static const char *
+Pred1DispOpts(struct fsm_opt *o __unused)
+{
+ return NULL;
+}
+
+static void
+Pred1InitOptsOutput(struct bundle *bundle __unused, struct fsm_opt *o,
+ const struct ccp_config *cfg __unused)
+{
+ o->hdr.len = 2;
+}
+
+static int
+Pred1SetOpts(struct bundle *bundle __unused, struct fsm_opt *o,
+ const struct ccp_config *cfg __unused)
+{
+ if (o->hdr.len != 2) {
+ o->hdr.len = 2;
+ return MODE_NAK;
+ }
+ return MODE_ACK;
+}
+
+const struct ccp_algorithm Pred1Algorithm = {
+ TY_PRED1,
+ CCP_NEG_PRED1,
+ Pred1DispOpts,
+ ccp_DefaultUsable,
+ ccp_DefaultRequired,
+ {
+ Pred1SetOpts,
+ Pred1InitInput,
+ Pred1Term,
+ Pred1ResetInput,
+ Pred1Input,
+ Pred1DictSetup
+ },
+ {
+ 0,
+ Pred1InitOptsOutput,
+ Pred1SetOpts,
+ Pred1InitOutput,
+ Pred1Term,
+ Pred1ResetOutput,
+ Pred1Output
+ },
+};
diff --git a/usr.sbin/ppp/pred.h b/usr.sbin/ppp/pred.h
new file mode 100644
index 0000000..1afb77a
--- /dev/null
+++ b/usr.sbin/ppp/pred.h
@@ -0,0 +1,31 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+extern const struct ccp_algorithm Pred1Algorithm;
diff --git a/usr.sbin/ppp/probe.c b/usr.sbin/ppp/probe.c
new file mode 100644
index 0000000..a33734c
--- /dev/null
+++ b/usr.sbin/ppp/probe.c
@@ -0,0 +1,78 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.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/time.h>
+#include <sys/socket.h>
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include "probe.h"
+#include "log.h"
+#include "id.h"
+
+struct probe probe;
+
+/* Does select() alter the passed time value ? */
+static int
+select_changes_time(void)
+{
+ struct timeval t;
+
+ t.tv_sec = 0;
+ t.tv_usec = 100000;
+ select(0, NULL, NULL, NULL, &t);
+ return t.tv_usec != 100000;
+}
+
+#ifndef NOINET6
+static int
+ipv6_available(void)
+{
+ int s;
+
+ if ((s = ID0socket(PF_INET6, SOCK_DGRAM, 0)) == -1)
+ return 0;
+
+ close(s);
+ return 1;
+}
+#endif
+
+void
+probe_Init()
+{
+ probe.select_changes_time = select_changes_time() ? 1 : 0;
+ log_Printf(LogDEBUG, "Select changes time: %s\n",
+ probe.select_changes_time ? "yes" : "no");
+#ifndef NOINET6
+ probe.ipv6_available = ipv6_available() ? 1 : 0;
+ log_Printf(LogDEBUG, "IPv6 available: %s\n",
+ probe.ipv6_available ? "yes" : "no");
+#endif
+}
diff --git a/usr.sbin/ppp/probe.h b/usr.sbin/ppp/probe.h
new file mode 100644
index 0000000..5a7dce9
--- /dev/null
+++ b/usr.sbin/ppp/probe.h
@@ -0,0 +1,38 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.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$
+ */
+
+struct probe {
+ unsigned select_changes_time : 1;
+#ifndef NOINET6
+ unsigned ipv6_available : 1;
+#endif
+};
+
+extern struct probe probe;
+
+extern void probe_Init(void);
diff --git a/usr.sbin/ppp/prompt.c b/usr.sbin/ppp/prompt.c
new file mode 100644
index 0000000..de331ef
--- /dev/null
+++ b/usr.sbin/ppp/prompt.c
@@ -0,0 +1,574 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.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/param.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/fcntl.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "timer.h"
+#include "command.h"
+#include "log.h"
+#include "descriptor.h"
+#include "prompt.h"
+#include "fsm.h"
+#include "auth.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "mbuf.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "async.h"
+#include "ccp.h"
+#include "link.h"
+#include "physical.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "chat.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+#include "server.h"
+#include "main.h"
+
+static void
+prompt_Display(struct prompt *p)
+{
+ /* XXX: See Index2Nam() - should we only figure this out once ? */
+ static char shostname[MAXHOSTNAMELEN];
+ const char *pconnect, *pauth;
+
+ if (p->TermMode || !p->needprompt)
+ return;
+
+ p->needprompt = 0;
+
+ if (p->nonewline)
+ p->nonewline = 0;
+ else
+ fprintf(p->Term, "\n");
+
+ if (p->auth == LOCAL_AUTH)
+ pauth = " ON ";
+ else
+ pauth = " on ";
+
+ if (p->bundle->ncp.ipcp.fsm.state == ST_OPENED)
+ pconnect = "PPP";
+#ifndef NOINET6
+ else if (!Enabled(p->bundle, OPT_IPCP) &&
+ p->bundle->ncp.ipv6cp.fsm.state == ST_OPENED)
+ pconnect = "PPP";
+#endif
+ else if (bundle_Phase(p->bundle) == PHASE_NETWORK)
+ pconnect = "PPp";
+ else if (bundle_Phase(p->bundle) == PHASE_AUTHENTICATE)
+ pconnect = "Ppp";
+ else
+ pconnect = "ppp";
+
+ if (*shostname == '\0') {
+ char *dot;
+
+ if (gethostname(shostname, sizeof shostname) || *shostname == '\0')
+ strcpy(shostname, "localhost");
+ else if ((dot = strchr(shostname, '.')))
+ *dot = '\0';
+ }
+
+ fprintf(p->Term, "%s%s%s> ", pconnect, pauth, shostname);
+ fflush(p->Term);
+}
+
+static int
+prompt_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w __unused,
+ fd_set *e, int *n)
+{
+ struct prompt *p = descriptor2prompt(d);
+ int sets;
+
+ sets = 0;
+
+ if (!p->active)
+ return sets;
+
+ if (p->fd_in >= 0) {
+ if (r) {
+ FD_SET(p->fd_in, r);
+ log_Printf(LogTIMER, "prompt %s: fdset(r) %d\n", p->src.from, p->fd_in);
+ sets++;
+ }
+ if (e) {
+ FD_SET(p->fd_in, e);
+ log_Printf(LogTIMER, "prompt %s: fdset(e) %d\n", p->src.from, p->fd_in);
+ sets++;
+ }
+ if (sets && *n < p->fd_in + 1)
+ *n = p->fd_in + 1;
+ }
+
+ prompt_Display(p);
+
+ return sets;
+}
+
+static int
+prompt_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+ struct prompt *p = descriptor2prompt(d);
+ return p->fd_in >= 0 && FD_ISSET(p->fd_in, fdset);
+}
+
+
+static void
+prompt_ShowHelp(struct prompt *p)
+{
+ prompt_Printf(p, "The following commands are available:\n");
+ prompt_Printf(p, " ~p\tEnter Packet mode\n");
+ prompt_Printf(p, " ~t\tShow timers\n");
+ prompt_Printf(p, " ~m\tShow memory map\n");
+ prompt_Printf(p, " ~.\tTerminate program\n");
+ prompt_Printf(p, " ~?\tThis help\n");
+}
+
+static void
+prompt_Read(struct fdescriptor *d, struct bundle *bundle,
+ const fd_set *fdset __unused)
+{
+ struct prompt *p = descriptor2prompt(d);
+ struct prompt *op;
+ int n;
+ char ch;
+ char linebuff[LINE_LEN];
+
+ if (p->TermMode == NULL) {
+ n = read(p->fd_in, linebuff, sizeof linebuff - 1);
+ if (n > 0) {
+ if (linebuff[n-1] == '\n')
+ linebuff[--n] = '\0';
+ else
+ linebuff[n] = '\0';
+ p->nonewline = 1; /* Maybe command_Decode does a prompt */
+ prompt_Required(p);
+ if (n) {
+ if ((op = log_PromptContext) == NULL)
+ log_PromptContext = p;
+ if (!command_Decode(bundle, linebuff, n, p, p->src.from))
+ prompt_Printf(p, "Syntax error\n");
+ log_PromptContext = op;
+ }
+ } else if (n <= 0) {
+ log_Printf(LogPHASE, "%s: Client connection closed.\n", p->src.from);
+ if (!p->owner)
+ Cleanup();
+ prompt_Destroy(p, 0);
+ }
+ return;
+ }
+
+ switch (p->TermMode->state) {
+ case DATALINK_CLOSED:
+ prompt_Printf(p, "Link lost, terminal mode.\n");
+ prompt_TtyCommandMode(p);
+ p->nonewline = 0;
+ prompt_Required(p);
+ return;
+
+ case DATALINK_READY:
+ break;
+
+ case DATALINK_OPEN:
+ prompt_Printf(p, "\nPacket mode detected.\n");
+ prompt_TtyCommandMode(p);
+ p->nonewline = 0;
+ /* We'll get a prompt because of our status change */
+ /* FALLTHROUGH */
+
+ default:
+ /* Wait 'till we're in a state we care about */
+ return;
+ }
+
+ /*
+ * We are in terminal mode, decode special sequences
+ */
+ n = read(p->fd_in, &ch, 1);
+ log_Printf(LogDEBUG, "Got %d bytes (reading from the terminal)\n", n);
+
+ if (n > 0) {
+ switch (p->readtilde) {
+ case 0:
+ if (ch == '~')
+ p->readtilde = 1;
+ else
+ if (physical_Write(p->TermMode->physical, &ch, n) < 0) {
+ log_Printf(LogWARN, "error writing to modem: %s\n", strerror(errno));
+ prompt_TtyCommandMode(p);
+ }
+ break;
+ case 1:
+ switch (ch) {
+ case '?':
+ prompt_ShowHelp(p);
+ break;
+ case 'p':
+ datalink_Up(p->TermMode, 0, 1);
+ prompt_Printf(p, "\nPacket mode.\n");
+ prompt_TtyCommandMode(p);
+ break;
+ case '.':
+ prompt_TtyCommandMode(p);
+ p->nonewline = 0;
+ prompt_Required(p);
+ break;
+ case 't':
+ timer_Show(0, p);
+ break;
+ case 'm':
+ {
+ struct cmdargs arg;
+
+ arg.cmdtab = NULL;
+ arg.cmd = NULL;
+ arg.argc = 0;
+ arg.argn = 0;
+ arg.argv = NULL;
+ arg.bundle = bundle;
+ arg.cx = p->TermMode;
+ arg.prompt = p;
+
+ mbuf_Show(&arg);
+ }
+ break;
+ default:
+ if (physical_Write(p->TermMode->physical, &ch, n) < 0) {
+ log_Printf(LogWARN, "error writing to modem: %s\n", strerror(errno));
+ prompt_TtyCommandMode(p);
+ }
+ break;
+ }
+ p->readtilde = 0;
+ break;
+ }
+ }
+}
+
+static int
+prompt_Write(struct fdescriptor *d __unused, struct bundle *bundle __unused,
+ const fd_set *fdset __unused)
+{
+ /* We never want to write here ! */
+ log_Printf(LogALERT, "prompt_Write: Internal error: Bad call !\n");
+ return 0;
+}
+
+struct prompt *
+prompt_Create(struct server *s, struct bundle *bundle, int fd)
+{
+ struct prompt *p = (struct prompt *)malloc(sizeof(struct prompt));
+
+ if (p != NULL) {
+ p->desc.type = PROMPT_DESCRIPTOR;
+ p->desc.UpdateSet = prompt_UpdateSet;
+ p->desc.IsSet = prompt_IsSet;
+ p->desc.Read = prompt_Read;
+ p->desc.Write = prompt_Write;
+
+ if (fd == PROMPT_STD) {
+ char *tty = ttyname(STDIN_FILENO);
+
+ if (!tty) {
+ free(p);
+ return NULL;
+ }
+ p->fd_in = STDIN_FILENO;
+ p->fd_out = STDOUT_FILENO;
+ p->Term = stdout;
+ p->owner = NULL;
+ p->auth = LOCAL_AUTH;
+ p->src.type = "Controller";
+ strncpy(p->src.from, tty, sizeof p->src.from - 1);
+ p->src.from[sizeof p->src.from - 1] = '\0';
+ tcgetattr(p->fd_in, &p->oldtio); /* Save original tty mode */
+ } else {
+ p->fd_in = p->fd_out = fd;
+ p->Term = fdopen(fd, "a+");
+ p->owner = s;
+ p->auth = *s->cfg.passwd ? LOCAL_NO_AUTH : LOCAL_AUTH;
+ p->src.type = "unknown";
+ *p->src.from = '\0';
+ }
+ p->TermMode = NULL;
+ p->nonewline = 1;
+ p->needprompt = 1;
+ p->readtilde = 0;
+ p->bundle = bundle;
+ log_RegisterPrompt(p);
+ }
+
+ return p;
+}
+
+void
+prompt_Destroy(struct prompt *p, int verbose)
+{
+ if (p) {
+ if (p->Term != stdout) {
+ fclose(p->Term);
+ close(p->fd_in);
+ if (p->fd_out != p->fd_in)
+ close(p->fd_out);
+ if (verbose)
+ log_Printf(LogPHASE, "%s: Client connection dropped.\n", p->src.from);
+ } else
+ prompt_TtyOldMode(p);
+
+ log_UnRegisterPrompt(p);
+ free(p);
+ }
+}
+
+void
+prompt_Printf(struct prompt *p, const char *fmt,...)
+{
+ if (p && p->active) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ prompt_vPrintf(p, fmt, ap);
+ va_end(ap);
+ }
+}
+
+void
+prompt_vPrintf(struct prompt *p, const char *fmt, va_list ap)
+{
+ if (p && p->active) {
+ char nfmt[LINE_LEN];
+ const char *pfmt;
+
+ if (p->TermMode) {
+ /* Stuff '\r' in front of '\n' 'cos we're in raw mode */
+ size_t len = strlen(fmt);
+
+ if (len && len < sizeof nfmt - 1 && fmt[len-1] == '\n' &&
+ (len == 1 || fmt[len-2] != '\r')) {
+ strcpy(nfmt, fmt);
+ strcpy(nfmt + len - 1, "\r\n");
+ pfmt = nfmt;
+ } else
+ pfmt = fmt;
+ } else
+ pfmt = fmt;
+ vfprintf(p->Term, pfmt, ap);
+ fflush(p->Term);
+ p->nonewline = 1;
+ }
+}
+
+void
+prompt_TtyInit(struct prompt *p)
+{
+ int stat, fd = p ? p->fd_in : STDIN_FILENO;
+ struct termios newtio;
+
+ stat = fcntl(fd, F_GETFL, 0);
+ if (stat > 0) {
+ stat |= O_NONBLOCK;
+ fcntl(fd, F_SETFL, stat);
+ }
+
+ if (p)
+ newtio = p->oldtio;
+ else
+ tcgetattr(fd, &newtio);
+
+ newtio.c_lflag &= ~(ECHO | ISIG | ICANON);
+ newtio.c_iflag = 0;
+ newtio.c_oflag &= ~OPOST;
+ if (!p)
+ newtio.c_cc[VINTR] = _POSIX_VDISABLE;
+ newtio.c_cc[VMIN] = 1;
+ newtio.c_cc[VTIME] = 0;
+ newtio.c_cflag |= CS8;
+ tcsetattr(fd, TCSANOW, &newtio);
+ if (p)
+ p->comtio = newtio;
+}
+
+/*
+ * Set tty into command mode. We allow canonical input and echo processing.
+ */
+void
+prompt_TtyCommandMode(struct prompt *p)
+{
+ struct termios newtio;
+ int stat;
+
+ tcgetattr(p->fd_in, &newtio);
+ newtio.c_lflag |= (ECHO | ISIG | ICANON);
+ newtio.c_iflag = p->oldtio.c_iflag;
+ newtio.c_oflag |= OPOST;
+ tcsetattr(p->fd_in, TCSADRAIN, &newtio);
+
+ stat = fcntl(p->fd_in, F_GETFL, 0);
+ if (stat > 0) {
+ stat |= O_NONBLOCK;
+ fcntl(p->fd_in, F_SETFL, stat);
+ }
+
+ p->TermMode = NULL;
+}
+
+/*
+ * Set tty into terminal mode which is used while we invoke term command.
+ */
+void
+prompt_TtyTermMode(struct prompt *p, struct datalink *dl)
+{
+ int stat;
+
+ if (p->Term == stdout)
+ tcsetattr(p->fd_in, TCSADRAIN, &p->comtio);
+
+ stat = fcntl(p->fd_in, F_GETFL, 0);
+ if (stat > 0) {
+ stat &= ~O_NONBLOCK;
+ fcntl(p->fd_in, F_SETFL, stat);
+ }
+ p->TermMode = dl;
+}
+
+void
+prompt_TtyOldMode(struct prompt *p)
+{
+ int stat;
+
+ stat = fcntl(p->fd_in, F_GETFL, 0);
+ if (stat > 0) {
+ stat &= ~O_NONBLOCK;
+ fcntl(p->fd_in, F_SETFL, stat);
+ }
+
+ if (p->Term == stdout)
+ tcsetattr(p->fd_in, TCSADRAIN, &p->oldtio);
+}
+
+pid_t
+prompt_pgrp(struct prompt *p)
+{
+ return tcgetpgrp(p->fd_in);
+}
+
+int
+PasswdCommand(struct cmdargs const *arg)
+{
+ const char *pass;
+
+ if (!arg->prompt) {
+ log_Printf(LogWARN, "passwd: Cannot specify without a prompt\n");
+ return 0;
+ }
+
+ if (arg->prompt->owner == NULL) {
+ log_Printf(LogWARN, "passwd: Not required\n");
+ return 0;
+ }
+
+ if (arg->argc == arg->argn)
+ pass = "";
+ else if (arg->argc > arg->argn+1)
+ return -1;
+ else
+ pass = arg->argv[arg->argn];
+
+ if (!strcmp(arg->prompt->owner->cfg.passwd, pass))
+ arg->prompt->auth = LOCAL_AUTH;
+ else
+ arg->prompt->auth = LOCAL_NO_AUTH;
+
+ return 0;
+}
+
+static struct pppTimer bgtimer;
+
+static void
+prompt_TimedContinue(void *v)
+{
+ prompt_Continue((struct prompt *)v);
+}
+
+void
+prompt_Continue(struct prompt *p)
+{
+ timer_Stop(&bgtimer);
+ if (getpgrp() == prompt_pgrp(p)) {
+ prompt_TtyCommandMode(p);
+ p->nonewline = 1;
+ prompt_Required(p);
+ log_ActivatePrompt(p);
+ } else if (!p->owner) {
+ bgtimer.func = prompt_TimedContinue;
+ bgtimer.name = "prompt bg";
+ bgtimer.load = SECTICKS;
+ bgtimer.arg = p;
+ timer_Start(&bgtimer);
+ }
+}
+
+void
+prompt_Suspend(struct prompt *p)
+{
+ if (getpgrp() == prompt_pgrp(p)) {
+ prompt_TtyOldMode(p);
+ log_DeactivatePrompt(p);
+ }
+}
diff --git a/usr.sbin/ppp/prompt.h b/usr.sbin/ppp/prompt.h
new file mode 100644
index 0000000..0489338
--- /dev/null
+++ b/usr.sbin/ppp/prompt.h
@@ -0,0 +1,96 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.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$
+ */
+
+#define LOCAL_AUTH 0x01
+#define LOCAL_NO_AUTH 0x02
+#define LOCAL_DENY 0x03
+#define LOCAL_CX 0x04 /* OR'd value - require a context */
+#define LOCAL_CX_OPT 0x08 /* OR'd value - optional context */
+
+struct server;
+struct datalink;
+struct bundle;
+struct cmdargs;
+
+struct prompt {
+ struct fdescriptor desc;
+ int fd_in, fd_out;
+ struct datalink *TermMode; /* The modem we're talking directly to */
+ FILE *Term; /* sits on top of fd_out */
+ u_char auth; /* Local Authorized status */
+ struct server *owner; /* who created me */
+ struct bundle *bundle; /* who I'm controlling */
+ unsigned nonewline : 1; /* need a newline before our prompt ? */
+ unsigned needprompt : 1; /* Show a prompt at the next UpdateSet() */
+ unsigned active : 1; /* Is the prompt active (^Z) */
+ unsigned readtilde : 1; /* We've read a ``~'' from fd_in */
+
+ struct {
+ const char *type; /* Type of connection */
+ char from[40]; /* Source of connection */
+ } src;
+
+ struct prompt *next; /* Maintained in log.c */
+ u_long logmask; /* Maintained in log.c */
+
+ struct termios oldtio; /* Original tty mode */
+ struct termios comtio; /* Command level tty mode */
+};
+
+#define descriptor2prompt(d) \
+ ((d)->type == PROMPT_DESCRIPTOR ? (struct prompt *)(d) : NULL)
+
+#define PROMPT_STD (-1)
+extern struct prompt *prompt_Create(struct server *, struct bundle *, int);
+extern void prompt_Destroy(struct prompt *, int);
+extern void prompt_Required(struct prompt *);
+#ifdef __GNUC__
+extern void prompt_Printf(struct prompt *, const char *, ...)
+ __attribute__ ((format (printf, 2, 3)));
+#else
+extern void prompt_Printf(struct prompt *, const char *, ...);
+#endif
+#ifdef __GNUC__
+extern void prompt_vPrintf(struct prompt *, const char *, va_list)
+ __attribute__ ((format (printf, 2, 0)));
+#else
+extern void prompt_vPrintf(struct prompt *, const char *, va_list);
+#endif
+#define PROMPT_DONT_WANT_INT 1
+#define PROMPT_WANT_INT 0
+extern void prompt_TtyInit(struct prompt *);
+extern void prompt_TtyCommandMode(struct prompt *);
+extern void prompt_TtyTermMode(struct prompt *, struct datalink *);
+extern void prompt_TtyOldMode(struct prompt *);
+extern pid_t prompt_pgrp(struct prompt *);
+extern int PasswdCommand(struct cmdargs const *);
+extern void prompt_Suspend(struct prompt *);
+extern void prompt_Continue(struct prompt *);
+#define prompt_IsTermMode(p, dl) ((p)->TermMode == (dl) ? 1 : 0)
+#define prompt_IsController(p) (!(p) || (p)->owner ? 0 : 1)
+#define prompt_Required(p) ((p)->needprompt = 1)
diff --git a/usr.sbin/ppp/proto.c b/usr.sbin/ppp/proto.c
new file mode 100644
index 0000000..2c2c26d
--- /dev/null
+++ b/usr.sbin/ppp/proto.c
@@ -0,0 +1,115 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.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/types.h>
+
+#include <stdio.h>
+#include <termios.h>
+
+#include "layer.h"
+#include "acf.h"
+#include "defs.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "mbuf.h"
+#include "proto.h"
+#include "throughput.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+
+int
+proto_WrapperOctets(struct lcp *lcp, u_short proto)
+{
+ return (lcp->his_protocomp && !(proto & 0xff00)) ? 1 : 2;
+}
+
+struct mbuf *
+proto_Prepend(struct mbuf *bp, u_short proto, unsigned comp, int extra)
+{
+ u_char cp[2];
+
+ cp[0] = proto >> 8;
+ cp[1] = proto & 0xff;
+
+ if (comp && cp[0] == 0)
+ bp = m_prepend(bp, cp + 1, 1, extra);
+ else
+ bp = m_prepend(bp, cp, 2, extra);
+
+ return bp;
+}
+
+static struct mbuf *
+proto_LayerPush(struct bundle *b __unused, struct link *l, struct mbuf *bp,
+ int pri __unused, u_short *proto)
+{
+ log_Printf(LogDEBUG, "proto_LayerPush: Using 0x%04x\n", *proto);
+ bp = proto_Prepend(bp, *proto, l->lcp.his_protocomp,
+ acf_WrapperOctets(&l->lcp, *proto));
+ m_settype(bp, MB_PROTOOUT);
+ link_ProtocolRecord(l, *proto, PROTO_OUT);
+
+ return bp;
+}
+
+static struct mbuf *
+proto_LayerPull(struct bundle *b __unused, struct link *l, struct mbuf *bp,
+ u_short *proto)
+{
+ u_char cp[2];
+ size_t got;
+
+ if ((got = mbuf_View(bp, cp, 2)) == 0) {
+ m_freem(bp);
+ return NULL;
+ }
+
+ *proto = cp[0];
+ if (!(*proto & 1)) {
+ if (got == 1) {
+ m_freem(bp);
+ return NULL;
+ }
+ bp = mbuf_Read(bp, cp, 2);
+ *proto = (*proto << 8) | cp[1];
+ } else
+ bp = mbuf_Read(bp, cp, 1);
+
+ log_Printf(LogDEBUG, "proto_LayerPull: unknown -> 0x%04x\n", *proto);
+ m_settype(bp, MB_PROTOIN);
+ link_ProtocolRecord(l, *proto, PROTO_IN);
+
+ return bp;
+}
+
+struct layer protolayer =
+ { LAYER_PROTO, "proto", proto_LayerPush, proto_LayerPull };
diff --git a/usr.sbin/ppp/proto.h b/usr.sbin/ppp/proto.h
new file mode 100644
index 0000000..8c93327
--- /dev/null
+++ b/usr.sbin/ppp/proto.h
@@ -0,0 +1,64 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+/*
+ * Definition of protocol numbers
+ */
+#define PROTO_IP 0x0021 /* IP */
+#define PROTO_VJUNCOMP 0x002f /* VJ Uncompressed */
+#define PROTO_VJCOMP 0x002d /* VJ Compressed */
+#define PROTO_MP 0x003d /* Multilink fragment */
+#ifndef NOINET6
+#define PROTO_IPV6 0x0057 /* IPv6 */
+#endif
+#define PROTO_ICOMPD 0x00fb /* Individual link compressed */
+#define PROTO_COMPD 0x00fd /* Compressed datagram */
+
+#define PROTO_COMPRESSIBLE(p) (((p) & 0xff81) == 0x01)
+
+#define PROTO_IPCP 0x8021
+#ifndef NOINET6
+#define PROTO_IPV6CP 0x8057
+#endif
+#define PROTO_ICCP 0x80fb
+#define PROTO_CCP 0x80fd
+
+#define PROTO_LCP 0xc021
+#define PROTO_PAP 0xc023
+#define PROTO_CBCP 0xc029
+#define PROTO_LQR 0xc025
+#define PROTO_CHAP 0xc223
+
+struct lcp;
+
+extern int proto_WrapperOctets(struct lcp *, u_short);
+struct mbuf *proto_Prepend(struct mbuf *, u_short, unsigned, int);
+
+extern struct layer protolayer;
diff --git a/usr.sbin/ppp/radius.c b/usr.sbin/ppp/radius.c
new file mode 100644
index 0000000..f798141
--- /dev/null
+++ b/usr.sbin/ppp/radius.c
@@ -0,0 +1,1361 @@
+/*
+ * Copyright 1999 Internet Business Solutions Ltd., Switzerland
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <stdint.h>
+#include <sys/param.h>
+
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <net/route.h>
+
+#ifdef LOCALRAD
+#include "radlib.h"
+#include "radlib_vs.h"
+#else
+#include <radlib.h>
+#include <radlib_vs.h>
+#endif
+
+#include <errno.h>
+#ifndef NODES
+#include <md5.h>
+#endif
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <termios.h>
+#include <unistd.h>
+#include <netdb.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "log.h"
+#include "descriptor.h"
+#include "prompt.h"
+#include "timer.h"
+#include "fsm.h"
+#include "iplist.h"
+#include "slcompress.h"
+#include "throughput.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "mbuf.h"
+#include "ncpaddr.h"
+#include "ip.h"
+#include "ipcp.h"
+#include "ipv6cp.h"
+#include "route.h"
+#include "command.h"
+#include "filter.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "mp.h"
+#include "radius.h"
+#include "auth.h"
+#include "async.h"
+#include "physical.h"
+#include "chat.h"
+#include "cbcp.h"
+#include "chap.h"
+#include "datalink.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "proto.h"
+#include "iface.h"
+
+#ifndef NODES
+struct mschap_response {
+ u_char ident;
+ u_char flags;
+ u_char lm_response[24];
+ u_char nt_response[24];
+};
+
+struct mschap2_response {
+ u_char ident;
+ u_char flags;
+ u_char pchallenge[16];
+ u_char reserved[8];
+ u_char response[24];
+};
+
+#define AUTH_LEN 16
+#define SALT_LEN 2
+#endif
+
+static const char *
+radius_policyname(int policy)
+{
+ switch(policy) {
+ case MPPE_POLICY_ALLOWED:
+ return "Allowed";
+ case MPPE_POLICY_REQUIRED:
+ return "Required";
+ }
+ return NumStr(policy, NULL, 0);
+}
+
+static const char *
+radius_typesname(int types)
+{
+ switch(types) {
+ case MPPE_TYPE_40BIT:
+ return "40 bit";
+ case MPPE_TYPE_128BIT:
+ return "128 bit";
+ case MPPE_TYPE_40BIT|MPPE_TYPE_128BIT:
+ return "40 or 128 bit";
+ }
+ return NumStr(types, NULL, 0);
+}
+
+#ifndef NODES
+static void
+demangle(struct radius *r, const void *mangled, size_t mlen,
+ char **buf, size_t *len)
+{
+ char R[AUTH_LEN]; /* variable names as per rfc2548 */
+ const char *S;
+ u_char b[16];
+ const u_char *A, *C;
+ MD5_CTX Context;
+ int Slen, i, Clen, Ppos;
+ u_char *P;
+
+ if (mlen % 16 != SALT_LEN) {
+ log_Printf(LogWARN, "Cannot interpret mangled data of length %ld\n",
+ (u_long)mlen);
+ *buf = NULL;
+ *len = 0;
+ return;
+ }
+
+ /* We need the RADIUS Request-Authenticator */
+ if (rad_request_authenticator(r->cx.rad, R, sizeof R) != AUTH_LEN) {
+ log_Printf(LogWARN, "Cannot obtain the RADIUS request authenticator\n");
+ *buf = NULL;
+ *len = 0;
+ return;
+ }
+
+ A = (const u_char *)mangled; /* Salt comes first */
+ C = (const u_char *)mangled + SALT_LEN; /* Then the ciphertext */
+ Clen = mlen - SALT_LEN;
+ S = rad_server_secret(r->cx.rad); /* We need the RADIUS secret */
+ Slen = strlen(S);
+ P = alloca(Clen); /* We derive our plaintext */
+
+ MD5Init(&Context);
+ MD5Update(&Context, S, Slen);
+ MD5Update(&Context, R, AUTH_LEN);
+ MD5Update(&Context, A, SALT_LEN);
+ MD5Final(b, &Context);
+ Ppos = 0;
+
+ while (Clen) {
+ Clen -= 16;
+
+ for (i = 0; i < 16; i++)
+ P[Ppos++] = C[i] ^ b[i];
+
+ if (Clen) {
+ MD5Init(&Context);
+ MD5Update(&Context, S, Slen);
+ MD5Update(&Context, C, 16);
+ MD5Final(b, &Context);
+ }
+
+ C += 16;
+ }
+
+ /*
+ * The resulting plain text consists of a one-byte length, the text and
+ * maybe some padding.
+ */
+ *len = *P;
+ if (*len > mlen - 1) {
+ log_Printf(LogWARN, "Mangled data seems to be garbage\n");
+ *buf = NULL;
+ *len = 0;
+ return;
+ }
+
+ if ((*buf = malloc(*len)) == NULL) {
+ log_Printf(LogWARN, "demangle: Out of memory (%lu bytes)\n", (u_long)*len);
+ *len = 0;
+ } else
+ memcpy(*buf, P + 1, *len);
+}
+#endif
+
+/* XXX: This should go into librarius. */
+#ifndef NOINET6
+static uint8_t *
+rad_cvt_ipv6prefix(const void *data, size_t len)
+{
+ const size_t ipv6len = sizeof(struct in6_addr) + 2;
+ uint8_t *s;
+
+ if (len > ipv6len)
+ return NULL;
+ s = malloc(ipv6len);
+ if (s != NULL) {
+ memset(s, 0, ipv6len);
+ memcpy(s, data, len);
+ }
+ return s;
+}
+#endif
+
+/*
+ * rad_continue_send_request() has given us `got' (non-zero). Deal with it.
+ */
+static void
+radius_Process(struct radius *r, int got)
+{
+ char *argv[MAXARGS], *nuke;
+ struct bundle *bundle;
+ int argc, addrs, res, width;
+ size_t len;
+ struct ncprange dest;
+ struct ncpaddr gw;
+ const void *data;
+ const char *stype;
+ u_int32_t ipaddr, vendor;
+ struct in_addr ip;
+#ifndef NOINET6
+ uint8_t ipv6addr[INET6_ADDRSTRLEN];
+ struct in6_addr ip6;
+#endif
+
+ r->cx.fd = -1; /* Stop select()ing */
+ stype = r->cx.auth ? "auth" : "acct";
+
+ switch (got) {
+ case RAD_ACCESS_ACCEPT:
+ log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+ "Radius(%s): ACCEPT received\n", stype);
+ if (!r->cx.auth) {
+ rad_close(r->cx.rad);
+ return;
+ }
+ break;
+
+ case RAD_ACCESS_REJECT:
+ log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+ "Radius(%s): REJECT received\n", stype);
+ if (!r->cx.auth) {
+ rad_close(r->cx.rad);
+ return;
+ }
+ break;
+
+ case RAD_ACCESS_CHALLENGE:
+ /* we can't deal with this (for now) ! */
+ log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+ "Radius: CHALLENGE received (can't handle yet)\n");
+ if (r->cx.auth)
+ auth_Failure(r->cx.auth);
+ rad_close(r->cx.rad);
+ return;
+
+ case RAD_ACCOUNTING_RESPONSE:
+ /*
+ * It's probably not ideal to log this at PHASE level as we'll see
+ * too much stuff going to the log when ``set rad_alive'' is used.
+ * So we differ from older behaviour (ppp version 3.1 and before)
+ * and just log accounting responses to LogRADIUS.
+ */
+ log_Printf(LogRADIUS, "Radius(%s): Accounting response received\n",
+ stype);
+ if (r->cx.auth)
+ auth_Failure(r->cx.auth); /* unexpected !!! */
+
+ /* No further processing for accounting requests, please */
+ rad_close(r->cx.rad);
+ return;
+
+ case -1:
+ log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+ "radius(%s): %s\n", stype, rad_strerror(r->cx.rad));
+ if (r->cx.auth)
+ auth_Failure(r->cx.auth);
+ rad_close(r->cx.rad);
+ return;
+
+ default:
+ log_Printf(LogERROR, "rad_send_request(%s): Failed %d: %s\n", stype,
+ got, rad_strerror(r->cx.rad));
+ if (r->cx.auth)
+ auth_Failure(r->cx.auth);
+ rad_close(r->cx.rad);
+ return;
+ }
+
+ /* Let's see what we've got in our reply */
+ r->ip.s_addr = r->mask.s_addr = INADDR_NONE;
+ r->mtu = 0;
+ r->vj = 0;
+ while ((res = rad_get_attr(r->cx.rad, &data, &len)) > 0) {
+ switch (res) {
+ case RAD_FRAMED_IP_ADDRESS:
+ r->ip = rad_cvt_addr(data);
+ log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+ " IP %s\n", inet_ntoa(r->ip));
+ break;
+
+ case RAD_FILTER_ID:
+ free(r->filterid);
+ if ((r->filterid = rad_cvt_string(data, len)) == NULL) {
+ log_Printf(LogERROR, "rad_cvt_string: %s\n", rad_strerror(r->cx.rad));
+ auth_Failure(r->cx.auth);
+ rad_close(r->cx.rad);
+ return;
+ }
+ log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+ " Filter \"%s\"\n", r->filterid);
+ break;
+
+ case RAD_SESSION_TIMEOUT:
+ r->sessiontime = rad_cvt_int(data);
+ log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+ " Session-Timeout %lu\n", r->sessiontime);
+ break;
+
+ case RAD_FRAMED_IP_NETMASK:
+ r->mask = rad_cvt_addr(data);
+ log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+ " Netmask %s\n", inet_ntoa(r->mask));
+ break;
+
+ case RAD_FRAMED_MTU:
+ r->mtu = rad_cvt_int(data);
+ log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+ " MTU %lu\n", r->mtu);
+ break;
+
+ case RAD_FRAMED_ROUTING:
+ /* Disabled for now - should we automatically set up some filters ? */
+ /* rad_cvt_int(data); */
+ /* bit 1 = Send routing packets */
+ /* bit 2 = Receive routing packets */
+ break;
+
+ case RAD_FRAMED_COMPRESSION:
+ r->vj = rad_cvt_int(data) == 1 ? 1 : 0;
+ log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+ " VJ %sabled\n", r->vj ? "en" : "dis");
+ break;
+
+ case RAD_FRAMED_ROUTE:
+ /*
+ * We expect a string of the format ``dest[/bits] gw [metrics]''
+ * Any specified metrics are ignored. MYADDR and HISADDR are
+ * understood for ``dest'' and ``gw'' and ``0.0.0.0'' is the same
+ * as ``HISADDR''.
+ */
+
+ if ((nuke = rad_cvt_string(data, len)) == NULL) {
+ log_Printf(LogERROR, "rad_cvt_string: %s\n", rad_strerror(r->cx.rad));
+ auth_Failure(r->cx.auth);
+ rad_close(r->cx.rad);
+ return;
+ }
+
+ log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+ " Route: %s\n", nuke);
+ bundle = r->cx.auth->physical->dl->bundle;
+ ip.s_addr = INADDR_ANY;
+ ncpaddr_setip4(&gw, ip);
+ ncprange_setip4host(&dest, ip);
+ argc = command_Interpret(nuke, strlen(nuke), argv);
+ if (argc < 0)
+ log_Printf(LogWARN, "radius: %s: Syntax error\n",
+ argc == 1 ? argv[0] : "\"\"");
+ else if (argc < 2)
+ log_Printf(LogWARN, "radius: %s: Invalid route\n",
+ argc == 1 ? argv[0] : "\"\"");
+ else if ((strcasecmp(argv[0], "default") != 0 &&
+ !ncprange_aton(&dest, &bundle->ncp, argv[0])) ||
+ !ncpaddr_aton(&gw, &bundle->ncp, argv[1]))
+ log_Printf(LogWARN, "radius: %s %s: Invalid route\n",
+ argv[0], argv[1]);
+ else {
+ ncprange_getwidth(&dest, &width);
+ if (width == 32 && strchr(argv[0], '/') == NULL) {
+ /* No mask specified - use the natural mask */
+ ncprange_getip4addr(&dest, &ip);
+ ncprange_setip4mask(&dest, addr2mask(ip));
+ }
+ addrs = 0;
+
+ if (!strncasecmp(argv[0], "HISADDR", 7))
+ addrs = ROUTE_DSTHISADDR;
+ else if (!strncasecmp(argv[0], "MYADDR", 6))
+ addrs = ROUTE_DSTMYADDR;
+
+ if (ncpaddr_getip4addr(&gw, &ipaddr) && ipaddr == INADDR_ANY) {
+ addrs |= ROUTE_GWHISADDR;
+ ncpaddr_setip4(&gw, bundle->ncp.ipcp.peer_ip);
+ } else if (strcasecmp(argv[1], "HISADDR") == 0)
+ addrs |= ROUTE_GWHISADDR;
+
+ route_Add(&r->routes, addrs, &dest, &gw);
+ }
+ free(nuke);
+ break;
+
+ case RAD_REPLY_MESSAGE:
+ free(r->repstr);
+ if ((r->repstr = rad_cvt_string(data, len)) == NULL) {
+ log_Printf(LogERROR, "rad_cvt_string: %s\n", rad_strerror(r->cx.rad));
+ auth_Failure(r->cx.auth);
+ rad_close(r->cx.rad);
+ return;
+ }
+ log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+ " Reply-Message \"%s\"\n", r->repstr);
+ break;
+
+#ifndef NOINET6
+ case RAD_FRAMED_IPV6_PREFIX:
+ free(r->ipv6prefix);
+ if ((r->ipv6prefix = rad_cvt_ipv6prefix(data, len)) == NULL) {
+ log_Printf(LogERROR, "rad_cvt_ipv6prefix: %s\n",
+ "Malformed attribute in response");
+ auth_Failure(r->cx.auth);
+ rad_close(r->cx.rad);
+ return;
+ }
+ inet_ntop(AF_INET6, &r->ipv6prefix[2], ipv6addr, sizeof(ipv6addr));
+ log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+ " IPv6 %s/%d\n", ipv6addr, r->ipv6prefix[1]);
+ break;
+
+ case RAD_FRAMED_IPV6_ROUTE:
+ /*
+ * We expect a string of the format ``dest[/bits] gw [metrics]''
+ * Any specified metrics are ignored. MYADDR6 and HISADDR6 are
+ * understood for ``dest'' and ``gw'' and ``::'' is the same
+ * as ``HISADDR6''.
+ */
+
+ if ((nuke = rad_cvt_string(data, len)) == NULL) {
+ log_Printf(LogERROR, "rad_cvt_string: %s\n", rad_strerror(r->cx.rad));
+ auth_Failure(r->cx.auth);
+ rad_close(r->cx.rad);
+ return;
+ }
+
+ log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+ " IPv6 Route: %s\n", nuke);
+ bundle = r->cx.auth->physical->dl->bundle;
+ ncpaddr_setip6(&gw, &in6addr_any);
+ ncprange_set(&dest, &gw, 0);
+ argc = command_Interpret(nuke, strlen(nuke), argv);
+ if (argc < 0)
+ log_Printf(LogWARN, "radius: %s: Syntax error\n",
+ argc == 1 ? argv[0] : "\"\"");
+ else if (argc < 2)
+ log_Printf(LogWARN, "radius: %s: Invalid route\n",
+ argc == 1 ? argv[0] : "\"\"");
+ else if ((strcasecmp(argv[0], "default") != 0 &&
+ !ncprange_aton(&dest, &bundle->ncp, argv[0])) ||
+ !ncpaddr_aton(&gw, &bundle->ncp, argv[1]))
+ log_Printf(LogWARN, "radius: %s %s: Invalid route\n",
+ argv[0], argv[1]);
+ else {
+ addrs = 0;
+
+ if (!strncasecmp(argv[0], "HISADDR6", 8))
+ addrs = ROUTE_DSTHISADDR6;
+ else if (!strncasecmp(argv[0], "MYADDR6", 7))
+ addrs = ROUTE_DSTMYADDR6;
+
+ if (ncpaddr_getip6(&gw, &ip6) && IN6_IS_ADDR_UNSPECIFIED(&ip6)) {
+ addrs |= ROUTE_GWHISADDR6;
+ ncpaddr_copy(&gw, &bundle->ncp.ipv6cp.hisaddr);
+ } else if (strcasecmp(argv[1], "HISADDR6") == 0)
+ addrs |= ROUTE_GWHISADDR6;
+
+ route_Add(&r->ipv6routes, addrs, &dest, &gw);
+ }
+ free(nuke);
+ break;
+#endif
+
+ case RAD_VENDOR_SPECIFIC:
+ if ((res = rad_get_vendor_attr(&vendor, &data, &len)) <= 0) {
+ log_Printf(LogERROR, "rad_get_vendor_attr: %s (failing!)\n",
+ rad_strerror(r->cx.rad));
+ auth_Failure(r->cx.auth);
+ rad_close(r->cx.rad);
+ return;
+ }
+
+ switch (vendor) {
+ case RAD_VENDOR_MICROSOFT:
+ switch (res) {
+#ifndef NODES
+ case RAD_MICROSOFT_MS_CHAP_ERROR:
+ free(r->errstr);
+ if (len == 0)
+ r->errstr = NULL;
+ else {
+ if (len < 3 || ((const char *)data)[1] != '=') {
+ /*
+ * Only point at the String field if we don't think the
+ * peer has misformatted the response.
+ */
+ data = (const char *)data + 1;
+ len--;
+ } else
+ log_Printf(LogWARN, "Warning: The MS-CHAP-Error "
+ "attribute is mis-formatted. Compensating\n");
+ if ((r->errstr = rad_cvt_string((const char *)data,
+ len)) == NULL) {
+ log_Printf(LogERROR, "rad_cvt_string: %s\n",
+ rad_strerror(r->cx.rad));
+ auth_Failure(r->cx.auth);
+ rad_close(r->cx.rad);
+ return;
+ }
+ log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+ " MS-CHAP-Error \"%s\"\n", r->errstr);
+ }
+ break;
+
+ case RAD_MICROSOFT_MS_CHAP2_SUCCESS:
+ free(r->msrepstr);
+ if (len == 0)
+ r->msrepstr = NULL;
+ else {
+ if (len < 3 || ((const char *)data)[1] != '=') {
+ /*
+ * Only point at the String field if we don't think the
+ * peer has misformatted the response.
+ */
+ data = (const char *)data + 1;
+ len--;
+ } else
+ log_Printf(LogWARN, "Warning: The MS-CHAP2-Success "
+ "attribute is mis-formatted. Compensating\n");
+ if ((r->msrepstr = rad_cvt_string((const char *)data,
+ len)) == NULL) {
+ log_Printf(LogERROR, "rad_cvt_string: %s\n",
+ rad_strerror(r->cx.rad));
+ auth_Failure(r->cx.auth);
+ rad_close(r->cx.rad);
+ return;
+ }
+ log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+ " MS-CHAP2-Success \"%s\"\n", r->msrepstr);
+ }
+ break;
+
+ case RAD_MICROSOFT_MS_MPPE_ENCRYPTION_POLICY:
+ r->mppe.policy = rad_cvt_int(data);
+ log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+ " MS-MPPE-Encryption-Policy %s\n",
+ radius_policyname(r->mppe.policy));
+ break;
+
+ case RAD_MICROSOFT_MS_MPPE_ENCRYPTION_TYPES:
+ r->mppe.types = rad_cvt_int(data);
+ log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+ " MS-MPPE-Encryption-Types %s\n",
+ radius_typesname(r->mppe.types));
+ break;
+
+ case RAD_MICROSOFT_MS_MPPE_RECV_KEY:
+ free(r->mppe.recvkey);
+ demangle(r, data, len, &r->mppe.recvkey, &r->mppe.recvkeylen);
+ log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+ " MS-MPPE-Recv-Key ********\n");
+ break;
+
+ case RAD_MICROSOFT_MS_MPPE_SEND_KEY:
+ demangle(r, data, len, &r->mppe.sendkey, &r->mppe.sendkeylen);
+ log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+ " MS-MPPE-Send-Key ********\n");
+ break;
+#endif
+
+ default:
+ log_Printf(LogDEBUG, "Dropping MICROSOFT vendor specific "
+ "RADIUS attribute %d\n", res);
+ break;
+ }
+ break;
+
+ default:
+ log_Printf(LogDEBUG, "Dropping vendor %lu RADIUS attribute %d\n",
+ (unsigned long)vendor, res);
+ break;
+ }
+ break;
+
+ default:
+ log_Printf(LogDEBUG, "Dropping RADIUS attribute %d\n", res);
+ break;
+ }
+ }
+
+ if (res == -1) {
+ log_Printf(LogERROR, "rad_get_attr: %s (failing!)\n",
+ rad_strerror(r->cx.rad));
+ auth_Failure(r->cx.auth);
+ } else if (got == RAD_ACCESS_REJECT)
+ auth_Failure(r->cx.auth);
+ else {
+ r->valid = 1;
+ auth_Success(r->cx.auth);
+ }
+ rad_close(r->cx.rad);
+}
+
+/*
+ * We've either timed out or select()ed on the read descriptor
+ */
+static void
+radius_Continue(struct radius *r, int sel)
+{
+ struct timeval tv;
+ int got;
+
+ timer_Stop(&r->cx.timer);
+ if ((got = rad_continue_send_request(r->cx.rad, sel, &r->cx.fd, &tv)) == 0) {
+ log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+ "Radius: Request re-sent\n");
+ r->cx.timer.load = tv.tv_usec / TICKUNIT + tv.tv_sec * SECTICKS;
+ timer_Start(&r->cx.timer);
+ return;
+ }
+
+ radius_Process(r, got);
+}
+
+/*
+ * Time to call rad_continue_send_request() - timed out.
+ */
+static void
+radius_Timeout(void *v)
+{
+ radius_Continue((struct radius *)v, 0);
+}
+
+/*
+ * Time to call rad_continue_send_request() - something to read.
+ */
+static void
+radius_Read(struct fdescriptor *d, struct bundle *bundle __unused,
+ const fd_set *fdset __unused)
+{
+ radius_Continue(descriptor2radius(d), 1);
+}
+
+/*
+ * Flush any pending transactions
+ */
+void
+radius_Flush(struct radius *r)
+{
+ struct timeval tv;
+ fd_set s;
+
+ while (r->cx.fd != -1) {
+ FD_ZERO(&s);
+ FD_SET(r->cx.fd, &s);
+ tv.tv_sec = 0;
+ tv.tv_usec = TICKUNIT;
+ select(r->cx.fd + 1, &s, NULL, NULL, &tv);
+ radius_Continue(r, 1);
+ }
+}
+
+/*
+ * Behave as a struct fdescriptor (descriptor.h)
+ */
+static int
+radius_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w __unused,
+ fd_set *e __unused, int *n)
+{
+ struct radius *rad = descriptor2radius(d);
+
+ if (r && rad->cx.fd != -1) {
+ FD_SET(rad->cx.fd, r);
+ if (*n < rad->cx.fd + 1)
+ *n = rad->cx.fd + 1;
+ log_Printf(LogTIMER, "Radius: fdset(r) %d\n", rad->cx.fd);
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Behave as a struct fdescriptor (descriptor.h)
+ */
+static int
+radius_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+ struct radius *r = descriptor2radius(d);
+
+ return r && r->cx.fd != -1 && FD_ISSET(r->cx.fd, fdset);
+}
+
+/*
+ * Behave as a struct fdescriptor (descriptor.h)
+ */
+static int
+radius_Write(struct fdescriptor *d __unused, struct bundle *bundle __unused,
+ const fd_set *fdset __unused)
+{
+ /* We never want to write here ! */
+ log_Printf(LogALERT, "radius_Write: Internal error: Bad call !\n");
+ return 0;
+}
+
+/*
+ * Initialise ourselves
+ */
+void
+radius_Init(struct radius *r)
+{
+ r->desc.type = RADIUS_DESCRIPTOR;
+ r->desc.UpdateSet = radius_UpdateSet;
+ r->desc.IsSet = radius_IsSet;
+ r->desc.Read = radius_Read;
+ r->desc.Write = radius_Write;
+ r->cx.fd = -1;
+ r->cx.rad = NULL;
+ memset(&r->cx.timer, '\0', sizeof r->cx.timer);
+ r->cx.auth = NULL;
+ r->valid = 0;
+ r->vj = 0;
+ r->ip.s_addr = INADDR_ANY;
+ r->mask.s_addr = INADDR_NONE;
+ r->routes = NULL;
+ r->mtu = DEF_MTU;
+ r->msrepstr = NULL;
+ r->repstr = NULL;
+#ifndef NOINET6
+ r->ipv6prefix = NULL;
+ r->ipv6routes = NULL;
+#endif
+ r->errstr = NULL;
+ r->mppe.policy = 0;
+ r->mppe.types = 0;
+ r->mppe.recvkey = NULL;
+ r->mppe.recvkeylen = 0;
+ r->mppe.sendkey = NULL;
+ r->mppe.sendkeylen = 0;
+ *r->cfg.file = '\0';
+ log_Printf(LogDEBUG, "Radius: radius_Init\n");
+}
+
+/*
+ * Forget everything and go back to initialised state.
+ */
+void
+radius_Destroy(struct radius *r)
+{
+ r->valid = 0;
+ log_Printf(LogDEBUG, "Radius: radius_Destroy\n");
+ timer_Stop(&r->cx.timer);
+ route_DeleteAll(&r->routes);
+#ifndef NOINET6
+ route_DeleteAll(&r->ipv6routes);
+#endif
+ free(r->filterid);
+ r->filterid = NULL;
+ free(r->msrepstr);
+ r->msrepstr = NULL;
+ free(r->repstr);
+ r->repstr = NULL;
+#ifndef NOINET6
+ free(r->ipv6prefix);
+ r->ipv6prefix = NULL;
+#endif
+ free(r->errstr);
+ r->errstr = NULL;
+ free(r->mppe.recvkey);
+ r->mppe.recvkey = NULL;
+ r->mppe.recvkeylen = 0;
+ free(r->mppe.sendkey);
+ r->mppe.sendkey = NULL;
+ r->mppe.sendkeylen = 0;
+ if (r->cx.fd != -1) {
+ r->cx.fd = -1;
+ rad_close(r->cx.rad);
+ }
+}
+
+static int
+radius_put_physical_details(struct radius *rad, struct physical *p)
+{
+ int slot, type;
+
+ type = RAD_VIRTUAL;
+ if (p->handler)
+ switch (p->handler->type) {
+ case I4B_DEVICE:
+ type = RAD_ISDN_SYNC;
+ break;
+
+ case TTY_DEVICE:
+ type = RAD_ASYNC;
+ break;
+
+ case ETHER_DEVICE:
+ type = RAD_ETHERNET;
+ break;
+
+ case TCP_DEVICE:
+ case UDP_DEVICE:
+ case EXEC_DEVICE:
+ case ATM_DEVICE:
+ case NG_DEVICE:
+ type = RAD_VIRTUAL;
+ break;
+ }
+
+ if (rad_put_int(rad->cx.rad, RAD_NAS_PORT_TYPE, type) != 0) {
+ log_Printf(LogERROR, "rad_put: rad_put_int: %s\n", rad_strerror(rad->cx.rad));
+ rad_close(rad->cx.rad);
+ return 0;
+ }
+
+ switch (rad->port_id_type) {
+ case RPI_PID:
+ slot = (int)getpid();
+ break;
+ case RPI_IFNUM:
+ slot = p->dl->bundle->iface->index;
+ break;
+ case RPI_TUNNUM:
+ slot = p->dl->bundle->unit;
+ break;
+ case RPI_DEFAULT:
+ default:
+ slot = physical_Slot(p);
+ break;
+ }
+
+ if (slot >= 0)
+ if (rad_put_int(rad->cx.rad, RAD_NAS_PORT, slot) != 0) {
+ log_Printf(LogERROR, "rad_put: rad_put_int: %s\n", rad_strerror(rad->cx.rad));
+ rad_close(rad->cx.rad);
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * Start an authentication request to the RADIUS server.
+ */
+int
+radius_Authenticate(struct radius *r, struct authinfo *authp, const char *name,
+ const char *key, int klen, const char *nchallenge,
+ int nclen)
+{
+ char hostname[MAXHOSTNAMELEN];
+ struct timeval tv;
+ const char *what = "questionable"; /* silence warnings! */
+ char *mac_addr;
+ int got;
+ struct hostent *hp;
+ struct in_addr hostaddr;
+#ifndef NODES
+ struct mschap_response msresp;
+ struct mschap2_response msresp2;
+ const struct MSCHAPv2_resp *keyv2;
+#endif
+
+ if (!*r->cfg.file)
+ return 0;
+
+ if (r->cx.fd != -1)
+ /*
+ * We assume that our name/key/challenge is the same as last time,
+ * and just continue to wait for the RADIUS server(s).
+ */
+ return 1;
+
+ radius_Destroy(r);
+
+ if ((r->cx.rad = rad_auth_open()) == NULL) {
+ log_Printf(LogERROR, "rad_auth_open: %s\n", strerror(errno));
+ return 0;
+ }
+
+ if (rad_config(r->cx.rad, r->cfg.file) != 0) {
+ log_Printf(LogERROR, "rad_config: %s\n", rad_strerror(r->cx.rad));
+ rad_close(r->cx.rad);
+ return 0;
+ }
+
+ if (rad_create_request(r->cx.rad, RAD_ACCESS_REQUEST) != 0) {
+ log_Printf(LogERROR, "rad_create_request: %s\n", rad_strerror(r->cx.rad));
+ rad_close(r->cx.rad);
+ return 0;
+ }
+
+ if (rad_put_string(r->cx.rad, RAD_USER_NAME, name) != 0 ||
+ rad_put_int(r->cx.rad, RAD_SERVICE_TYPE, RAD_FRAMED) != 0 ||
+ rad_put_int(r->cx.rad, RAD_FRAMED_PROTOCOL, RAD_PPP) != 0) {
+ log_Printf(LogERROR, "rad_put: %s\n", rad_strerror(r->cx.rad));
+ rad_close(r->cx.rad);
+ return 0;
+ }
+
+ switch (authp->physical->link.lcp.want_auth) {
+ case PROTO_PAP:
+ /* We're talking PAP */
+ if (rad_put_attr(r->cx.rad, RAD_USER_PASSWORD, key, klen) != 0) {
+ log_Printf(LogERROR, "PAP: rad_put_string: %s\n",
+ rad_strerror(r->cx.rad));
+ rad_close(r->cx.rad);
+ return 0;
+ }
+ what = "PAP";
+ break;
+
+ case PROTO_CHAP:
+ switch (authp->physical->link.lcp.want_authtype) {
+ case 0x5:
+ if (rad_put_attr(r->cx.rad, RAD_CHAP_PASSWORD, key, klen) != 0 ||
+ rad_put_attr(r->cx.rad, RAD_CHAP_CHALLENGE, nchallenge, nclen) != 0) {
+ log_Printf(LogERROR, "CHAP: rad_put_string: %s\n",
+ rad_strerror(r->cx.rad));
+ rad_close(r->cx.rad);
+ return 0;
+ }
+ what = "CHAP";
+ break;
+
+#ifndef NODES
+ case 0x80:
+ if (klen != 50) {
+ log_Printf(LogERROR, "CHAP80: Unrecognised key length %d\n", klen);
+ rad_close(r->cx.rad);
+ return 0;
+ }
+
+ rad_put_vendor_attr(r->cx.rad, RAD_VENDOR_MICROSOFT,
+ RAD_MICROSOFT_MS_CHAP_CHALLENGE, nchallenge, nclen);
+ msresp.ident = *key;
+ msresp.flags = 0x01;
+ memcpy(msresp.lm_response, key + 1, 24);
+ memcpy(msresp.nt_response, key + 25, 24);
+ rad_put_vendor_attr(r->cx.rad, RAD_VENDOR_MICROSOFT,
+ RAD_MICROSOFT_MS_CHAP_RESPONSE, &msresp,
+ sizeof msresp);
+ what = "MSCHAP";
+ break;
+
+ case 0x81:
+ if (klen != sizeof(*keyv2) + 1) {
+ log_Printf(LogERROR, "CHAP81: Unrecognised key length %d\n", klen);
+ rad_close(r->cx.rad);
+ return 0;
+ }
+
+ keyv2 = (const struct MSCHAPv2_resp *)(key + 1);
+ rad_put_vendor_attr(r->cx.rad, RAD_VENDOR_MICROSOFT,
+ RAD_MICROSOFT_MS_CHAP_CHALLENGE, nchallenge, nclen);
+ msresp2.ident = *key;
+ msresp2.flags = keyv2->Flags;
+ memcpy(msresp2.response, keyv2->NTResponse, sizeof msresp2.response);
+ memset(msresp2.reserved, '\0', sizeof msresp2.reserved);
+ memcpy(msresp2.pchallenge, keyv2->PeerChallenge,
+ sizeof msresp2.pchallenge);
+ rad_put_vendor_attr(r->cx.rad, RAD_VENDOR_MICROSOFT,
+ RAD_MICROSOFT_MS_CHAP2_RESPONSE, &msresp2,
+ sizeof msresp2);
+ what = "MSCHAPv2";
+ break;
+#endif
+ default:
+ log_Printf(LogERROR, "CHAP: Unrecognised type 0x%02x\n",
+ authp->physical->link.lcp.want_authtype);
+ rad_close(r->cx.rad);
+ return 0;
+ }
+ }
+
+ if (gethostname(hostname, sizeof hostname) != 0)
+ log_Printf(LogERROR, "rad_put: gethostname(): %s\n", strerror(errno));
+ else {
+ if (Enabled(authp->physical->dl->bundle, OPT_NAS_IP_ADDRESS) &&
+ (hp = gethostbyname(hostname)) != NULL) {
+ hostaddr.s_addr = *(u_long *)hp->h_addr;
+ if (rad_put_addr(r->cx.rad, RAD_NAS_IP_ADDRESS, hostaddr) != 0) {
+ log_Printf(LogERROR, "rad_put: rad_put_string: %s\n",
+ rad_strerror(r->cx.rad));
+ rad_close(r->cx.rad);
+ return 0;
+ }
+ }
+ if (Enabled(authp->physical->dl->bundle, OPT_NAS_IDENTIFIER) &&
+ rad_put_string(r->cx.rad, RAD_NAS_IDENTIFIER, hostname) != 0) {
+ log_Printf(LogERROR, "rad_put: rad_put_string: %s\n",
+ rad_strerror(r->cx.rad));
+ rad_close(r->cx.rad);
+ return 0;
+ }
+ }
+
+ if ((mac_addr = getenv("HISMACADDR")) != NULL &&
+ rad_put_string(r->cx.rad, RAD_CALLING_STATION_ID, mac_addr) != 0) {
+ log_Printf(LogERROR, "rad_put: %s\n", rad_strerror(r->cx.rad));
+ rad_close(r->cx.rad);
+ return 0;
+ }
+
+ radius_put_physical_details(r, authp->physical);
+
+ log_Printf(LogRADIUS, "Radius(auth): %s data sent for %s\n", what, name);
+
+ r->cx.auth = authp;
+ if ((got = rad_init_send_request(r->cx.rad, &r->cx.fd, &tv)))
+ radius_Process(r, got);
+ else {
+ log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+ "Radius: Request sent\n");
+ log_Printf(LogDEBUG, "Using radius_Timeout [%p]\n", radius_Timeout);
+ r->cx.timer.load = tv.tv_usec / TICKUNIT + tv.tv_sec * SECTICKS;
+ r->cx.timer.func = radius_Timeout;
+ r->cx.timer.name = "radius auth";
+ r->cx.timer.arg = r;
+ timer_Start(&r->cx.timer);
+ }
+
+ return 1;
+}
+
+/* Fetch IP, netmask from IPCP */
+void
+radius_Account_Set_Ip(struct radacct *ac, struct in_addr *peer_ip,
+ struct in_addr *netmask)
+{
+ ac->proto = PROTO_IPCP;
+ memcpy(&ac->peer.ip.addr, peer_ip, sizeof(ac->peer.ip.addr));
+ memcpy(&ac->peer.ip.mask, netmask, sizeof(ac->peer.ip.mask));
+}
+
+#ifndef NOINET6
+/* Fetch interface-id from IPV6CP */
+void
+radius_Account_Set_Ipv6(struct radacct *ac, u_char *ifid)
+{
+ ac->proto = PROTO_IPV6CP;
+ memcpy(&ac->peer.ipv6.ifid, ifid, sizeof(ac->peer.ipv6.ifid));
+}
+#endif
+
+/*
+ * Send an accounting request to the RADIUS server
+ */
+void
+radius_Account(struct radius *r, struct radacct *ac, struct datalink *dl,
+ int acct_type, struct pppThroughput *stats)
+{
+ struct timeval tv;
+ int got;
+ char hostname[MAXHOSTNAMELEN];
+ char *mac_addr;
+ struct hostent *hp;
+ struct in_addr hostaddr;
+
+ if (!*r->cfg.file)
+ return;
+
+ if (r->cx.fd != -1)
+ /*
+ * We assume that our name/key/challenge is the same as last time,
+ * and just continue to wait for the RADIUS server(s).
+ */
+ return;
+
+ timer_Stop(&r->cx.timer);
+
+ if ((r->cx.rad = rad_acct_open()) == NULL) {
+ log_Printf(LogERROR, "rad_auth_open: %s\n", strerror(errno));
+ return;
+ }
+
+ if (rad_config(r->cx.rad, r->cfg.file) != 0) {
+ log_Printf(LogERROR, "rad_config: %s\n", rad_strerror(r->cx.rad));
+ rad_close(r->cx.rad);
+ return;
+ }
+
+ if (rad_create_request(r->cx.rad, RAD_ACCOUNTING_REQUEST) != 0) {
+ log_Printf(LogERROR, "rad_create_request: %s\n", rad_strerror(r->cx.rad));
+ rad_close(r->cx.rad);
+ return;
+ }
+
+ /* Grab some accounting data and initialize structure */
+ if (acct_type == RAD_START) {
+ ac->rad_parent = r;
+ /* Fetch username from datalink */
+ strncpy(ac->user_name, dl->peer.authname, sizeof ac->user_name);
+ ac->user_name[AUTHLEN-1] = '\0';
+
+ ac->authentic = 2; /* Assume RADIUS verified auth data */
+
+ /* Generate a session ID */
+ snprintf(ac->session_id, sizeof ac->session_id, "%s%ld-%s%lu",
+ dl->bundle->cfg.auth.name, (long)getpid(),
+ dl->peer.authname, (unsigned long)stats->uptime);
+
+ /* And grab our MP socket name */
+ snprintf(ac->multi_session_id, sizeof ac->multi_session_id, "%s",
+ dl->bundle->ncp.mp.active ?
+ dl->bundle->ncp.mp.server.socket.sun_path : "");
+ };
+
+ if (rad_put_string(r->cx.rad, RAD_USER_NAME, ac->user_name) != 0 ||
+ rad_put_int(r->cx.rad, RAD_SERVICE_TYPE, RAD_FRAMED) != 0 ||
+ rad_put_int(r->cx.rad, RAD_FRAMED_PROTOCOL, RAD_PPP) != 0) {
+ log_Printf(LogERROR, "rad_put: %s\n", rad_strerror(r->cx.rad));
+ rad_close(r->cx.rad);
+ return;
+ }
+ switch (ac->proto) {
+ case PROTO_IPCP:
+ if (rad_put_addr(r->cx.rad, RAD_FRAMED_IP_ADDRESS,
+ ac->peer.ip.addr) != 0 ||
+ rad_put_addr(r->cx.rad, RAD_FRAMED_IP_NETMASK,
+ ac->peer.ip.mask) != 0) {
+ log_Printf(LogERROR, "rad_put: %s\n", rad_strerror(r->cx.rad));
+ rad_close(r->cx.rad);
+ return;
+ }
+ break;
+#ifndef NOINET6
+ case PROTO_IPV6CP:
+ if (rad_put_attr(r->cx.rad, RAD_FRAMED_INTERFACE_ID, ac->peer.ipv6.ifid,
+ sizeof(ac->peer.ipv6.ifid)) != 0) {
+ log_Printf(LogERROR, "rad_put_attr: %s\n", rad_strerror(r->cx.rad));
+ rad_close(r->cx.rad);
+ return;
+ }
+ if (r->ipv6prefix) {
+ /*
+ * Since PPP doesn't delegate an IPv6 prefix to a peer,
+ * Framed-IPv6-Prefix may be not used, actually.
+ */
+ if (rad_put_attr(r->cx.rad, RAD_FRAMED_IPV6_PREFIX, r->ipv6prefix,
+ sizeof(struct in6_addr) + 2) != 0) {
+ log_Printf(LogERROR, "rad_put_attr: %s\n", rad_strerror(r->cx.rad));
+ rad_close(r->cx.rad);
+ return;
+ }
+ }
+ break;
+#endif
+ default:
+ /* We don't log any protocol specific information */
+ break;
+ }
+
+ if ((mac_addr = getenv("HISMACADDR")) != NULL &&
+ rad_put_string(r->cx.rad, RAD_CALLING_STATION_ID, mac_addr) != 0) {
+ log_Printf(LogERROR, "rad_put: %s\n", rad_strerror(r->cx.rad));
+ rad_close(r->cx.rad);
+ return;
+ }
+
+ if (gethostname(hostname, sizeof hostname) != 0)
+ log_Printf(LogERROR, "rad_put: gethostname(): %s\n", strerror(errno));
+ else {
+ if (Enabled(dl->bundle, OPT_NAS_IP_ADDRESS) &&
+ (hp = gethostbyname(hostname)) != NULL) {
+ hostaddr.s_addr = *(u_long *)hp->h_addr;
+ if (rad_put_addr(r->cx.rad, RAD_NAS_IP_ADDRESS, hostaddr) != 0) {
+ log_Printf(LogERROR, "rad_put: rad_put_string: %s\n",
+ rad_strerror(r->cx.rad));
+ rad_close(r->cx.rad);
+ return;
+ }
+ }
+ if (Enabled(dl->bundle, OPT_NAS_IDENTIFIER) &&
+ rad_put_string(r->cx.rad, RAD_NAS_IDENTIFIER, hostname) != 0) {
+ log_Printf(LogERROR, "rad_put: rad_put_string: %s\n",
+ rad_strerror(r->cx.rad));
+ rad_close(r->cx.rad);
+ return;
+ }
+ }
+
+ radius_put_physical_details(r, dl->physical);
+
+ if (rad_put_int(r->cx.rad, RAD_ACCT_STATUS_TYPE, acct_type) != 0 ||
+ rad_put_string(r->cx.rad, RAD_ACCT_SESSION_ID, ac->session_id) != 0 ||
+ rad_put_string(r->cx.rad, RAD_ACCT_MULTI_SESSION_ID,
+ ac->multi_session_id) != 0 ||
+ rad_put_int(r->cx.rad, RAD_ACCT_DELAY_TIME, 0) != 0) {
+/* XXX ACCT_DELAY_TIME should be increased each time a packet is waiting */
+ log_Printf(LogERROR, "rad_put: %s\n", rad_strerror(r->cx.rad));
+ rad_close(r->cx.rad);
+ return;
+ }
+
+ if (acct_type == RAD_STOP || acct_type == RAD_ALIVE)
+ /* Show some statistics */
+ if (rad_put_int(r->cx.rad, RAD_ACCT_INPUT_OCTETS, stats->OctetsIn % UINT32_MAX) != 0 ||
+ rad_put_int(r->cx.rad, RAD_ACCT_INPUT_GIGAWORDS, stats->OctetsIn / UINT32_MAX) != 0 ||
+ rad_put_int(r->cx.rad, RAD_ACCT_INPUT_PACKETS, stats->PacketsIn) != 0 ||
+ rad_put_int(r->cx.rad, RAD_ACCT_OUTPUT_OCTETS, stats->OctetsOut % UINT32_MAX) != 0 ||
+ rad_put_int(r->cx.rad, RAD_ACCT_OUTPUT_GIGAWORDS, stats->OctetsOut / UINT32_MAX) != 0 ||
+ rad_put_int(r->cx.rad, RAD_ACCT_OUTPUT_PACKETS, stats->PacketsOut)
+ != 0 ||
+ rad_put_int(r->cx.rad, RAD_ACCT_SESSION_TIME, throughput_uptime(stats))
+ != 0) {
+ log_Printf(LogERROR, "rad_put: %s\n", rad_strerror(r->cx.rad));
+ rad_close(r->cx.rad);
+ return;
+ }
+
+ if (log_IsKept(LogPHASE) || log_IsKept(LogRADIUS)) {
+ const char *what;
+ int level;
+
+ switch (acct_type) {
+ case RAD_START:
+ what = "START";
+ level = log_IsKept(LogPHASE) ? LogPHASE : LogRADIUS;
+ break;
+ case RAD_STOP:
+ what = "STOP";
+ level = log_IsKept(LogPHASE) ? LogPHASE : LogRADIUS;
+ break;
+ case RAD_ALIVE:
+ what = "ALIVE";
+ level = LogRADIUS;
+ break;
+ default:
+ what = "<unknown>";
+ level = log_IsKept(LogPHASE) ? LogPHASE : LogRADIUS;
+ break;
+ }
+ log_Printf(level, "Radius(acct): %s data sent\n", what);
+ }
+
+ r->cx.auth = NULL; /* Not valid for accounting requests */
+ if ((got = rad_init_send_request(r->cx.rad, &r->cx.fd, &tv)))
+ radius_Process(r, got);
+ else {
+ log_Printf(LogDEBUG, "Using radius_Timeout [%p]\n", radius_Timeout);
+ r->cx.timer.load = tv.tv_usec / TICKUNIT + tv.tv_sec * SECTICKS;
+ r->cx.timer.func = radius_Timeout;
+ r->cx.timer.name = "radius acct";
+ r->cx.timer.arg = r;
+ timer_Start(&r->cx.timer);
+ }
+}
+
+/*
+ * How do things look at the moment ?
+ */
+void
+radius_Show(struct radius *r, struct prompt *p)
+{
+ prompt_Printf(p, " Radius config: %s",
+ *r->cfg.file ? r->cfg.file : "none");
+ if (r->valid) {
+ prompt_Printf(p, "\n IP: %s\n", inet_ntoa(r->ip));
+ prompt_Printf(p, " Netmask: %s\n", inet_ntoa(r->mask));
+ prompt_Printf(p, " MTU: %lu\n", r->mtu);
+ prompt_Printf(p, " VJ: %sabled\n", r->vj ? "en" : "dis");
+ prompt_Printf(p, " Message: %s\n", r->repstr ? r->repstr : "");
+ prompt_Printf(p, " MPPE Enc Policy: %s\n",
+ radius_policyname(r->mppe.policy));
+ prompt_Printf(p, " MPPE Enc Types: %s\n",
+ radius_typesname(r->mppe.types));
+ prompt_Printf(p, " MPPE Recv Key: %seceived\n",
+ r->mppe.recvkey ? "R" : "Not r");
+ prompt_Printf(p, " MPPE Send Key: %seceived\n",
+ r->mppe.sendkey ? "R" : "Not r");
+ prompt_Printf(p, " MS-CHAP2-Response: %s\n",
+ r->msrepstr ? r->msrepstr : "");
+ prompt_Printf(p, " Error Message: %s\n", r->errstr ? r->errstr : "");
+ if (r->routes)
+ route_ShowSticky(p, r->routes, " Routes", 16);
+#ifndef NOINET6
+ if (r->ipv6routes)
+ route_ShowSticky(p, r->ipv6routes, " IPv6 Routes", 16);
+#endif
+ } else
+ prompt_Printf(p, " (not authenticated)\n");
+}
+
+static void
+radius_alive(void *v)
+{
+ struct bundle *bundle = (struct bundle *)v;
+
+ timer_Stop(&bundle->radius.alive.timer);
+ bundle->radius.alive.timer.load = bundle->radius.alive.interval * SECTICKS;
+ if (bundle->radius.alive.timer.load) {
+ radius_Account(&bundle->radius, &bundle->radacct,
+ bundle->links, RAD_ALIVE, &bundle->ncp.ipcp.throughput);
+ timer_Start(&bundle->radius.alive.timer);
+ }
+}
+
+void
+radius_StartTimer(struct bundle *bundle)
+{
+ if (*bundle->radius.cfg.file && bundle->radius.alive.interval) {
+ bundle->radius.alive.timer.func = radius_alive;
+ bundle->radius.alive.timer.name = "radius alive";
+ bundle->radius.alive.timer.load = bundle->radius.alive.interval * SECTICKS;
+ bundle->radius.alive.timer.arg = bundle;
+ radius_alive(bundle);
+ }
+}
+
+void
+radius_StopTimer(struct radius *r)
+{
+ timer_Stop(&r->alive.timer);
+}
diff --git a/usr.sbin/ppp/radius.h b/usr.sbin/ppp/radius.h
new file mode 100644
index 0000000..ab144a5
--- /dev/null
+++ b/usr.sbin/ppp/radius.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright 1999 Internet Business Solutions Ltd., Switzerland
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+#define MPPE_POLICY_ALLOWED 1
+#define MPPE_POLICY_REQUIRED 2
+
+#define MPPE_TYPE_40BIT 2
+#define MPPE_TYPE_128BIT 4
+
+#define RPI_DEFAULT 1
+#define RPI_PID 2
+#define RPI_IFNUM 3
+#define RPI_TUNNUM 4
+
+struct radius {
+ struct fdescriptor desc; /* We're a sort of (selectable) fdescriptor */
+ struct {
+ int fd; /* We're selecting on this */
+ struct rad_handle *rad; /* Using this to talk to our lib */
+ struct pppTimer timer; /* for this long */
+ struct authinfo *auth; /* Tell this about success/failure */
+ } cx;
+ unsigned valid : 1; /* Is this structure valid ? */
+ unsigned vj : 1; /* FRAMED Compression */
+ struct in_addr ip; /* FRAMED IP */
+ struct in_addr mask; /* FRAMED Netmask */
+ unsigned long mtu; /* FRAMED MTU */
+ unsigned long sessiontime; /* Session-Timeout */
+ char *filterid; /* FRAMED Filter Id */
+ struct sticky_route *routes; /* FRAMED Routes */
+ char *msrepstr; /* MS-CHAP2-Response */
+ char *repstr; /* Reply-Message */
+ char *errstr; /* Error-Message */
+#ifndef NOINET6
+ uint8_t *ipv6prefix; /* FRAMED IPv6 Prefix */
+ struct sticky_route *ipv6routes; /* FRAMED IPv6 Routes */
+#endif
+ struct {
+ int policy; /* MPPE_POLICY_* */
+ int types; /* MPPE_TYPE_*BIT bitmask */
+ char *recvkey;
+ size_t recvkeylen;
+ char *sendkey;
+ size_t sendkeylen;
+ } mppe;
+ struct {
+ char file[PATH_MAX]; /* Radius config file */
+ } cfg;
+ struct {
+ struct pppTimer timer; /* for this long */
+ int interval;
+ } alive;
+ short unsigned int port_id_type;
+};
+
+struct radacct {
+ struct radius *rad_parent; /* "Parent" struct radius stored in bundle */
+ char user_name[AUTHLEN]; /* Session User-Name */
+ char session_id[256]; /* Unique session ID */
+ char multi_session_id[51]; /* Unique MP session ID */
+ int authentic; /* How the session has been authenticated */
+ u_short proto; /* Protocol number */
+ union {
+ struct {
+ struct in_addr addr;
+ struct in_addr mask;
+ } ip;
+#ifndef NOINET6
+ struct {
+ u_char ifid[8];
+ } ipv6;
+#endif
+ } peer;
+};
+
+#define descriptor2radius(d) \
+ ((d)->type == RADIUS_DESCRIPTOR ? (struct radius *)(d) : NULL)
+
+struct bundle;
+
+extern void radius_Flush(struct radius *);
+extern void radius_Init(struct radius *);
+extern void radius_Destroy(struct radius *);
+
+extern void radius_Show(struct radius *, struct prompt *);
+extern void radius_StartTimer(struct bundle *);
+extern void radius_StopTimer(struct radius *);
+extern int radius_Authenticate(struct radius *, struct authinfo *,
+ const char *, const char *, int,
+ const char *, int);
+extern void radius_Account_Set_Ip(struct radacct *, struct in_addr *,
+ struct in_addr *);
+#ifndef NOINET6
+extern void radius_Account_Set_Ipv6(struct radacct *, u_char *);
+#endif
+extern void radius_Account(struct radius *, struct radacct *,
+ struct datalink *, int, struct pppThroughput *);
+
+/* An (int) parameter to radius_Account, from radlib.h */
+#if !defined(RAD_START)
+#define RAD_START 1
+#define RAD_STOP 2
+#endif
+
+#define RAD_ALIVE 3
+
+/* Get address from NAS pool */
+#define RADIUS_INADDR_POOL htonl(0xfffffffe) /* 255.255.255.254 */
diff --git a/usr.sbin/ppp/route.c b/usr.sbin/ppp/route.c
new file mode 100644
index 0000000..758b403
--- /dev/null
+++ b/usr.sbin/ppp/route.c
@@ -0,0 +1,934 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/socket.h>
+#include <net/if_types.h>
+#include <net/route.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <net/if_dl.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/sysctl.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "iplist.h"
+#include "timer.h"
+#include "throughput.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "fsm.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "route.h"
+#include "prompt.h"
+#include "iface.h"
+#include "id.h"
+
+
+static void
+p_sockaddr(struct prompt *prompt, struct sockaddr *phost,
+ struct sockaddr *pmask, int width)
+{
+ struct ncprange range;
+ char buf[29];
+ struct sockaddr_dl *dl = (struct sockaddr_dl *)phost;
+
+ if (log_IsKept(LogDEBUG)) {
+ char tmp[50];
+
+ log_Printf(LogDEBUG, "Found the following sockaddr:\n");
+ log_Printf(LogDEBUG, " Family %d, len %d\n",
+ (int)phost->sa_family, (int)phost->sa_len);
+ inet_ntop(phost->sa_family, phost->sa_data, tmp, sizeof tmp);
+ log_Printf(LogDEBUG, " Addr %s\n", tmp);
+ if (pmask) {
+ inet_ntop(pmask->sa_family, pmask->sa_data, tmp, sizeof tmp);
+ log_Printf(LogDEBUG, " Mask %s\n", tmp);
+ }
+ }
+
+ switch (phost->sa_family) {
+ case AF_INET:
+#ifndef NOINET6
+ case AF_INET6:
+#endif
+ ncprange_setsa(&range, phost, pmask);
+ if (ncprange_isdefault(&range))
+ prompt_Printf(prompt, "%-*s ", width - 1, "default");
+ else
+ prompt_Printf(prompt, "%-*s ", width - 1, ncprange_ntoa(&range));
+ return;
+
+ case AF_LINK:
+ if (dl->sdl_nlen)
+ snprintf(buf, sizeof buf, "%.*s", dl->sdl_nlen, dl->sdl_data);
+ else if (dl->sdl_alen) {
+ if (dl->sdl_type == IFT_ETHER) {
+ if (dl->sdl_alen < sizeof buf / 3) {
+ int f;
+ u_char *MAC;
+
+ MAC = (u_char *)dl->sdl_data + dl->sdl_nlen;
+ for (f = 0; f < dl->sdl_alen; f++)
+ sprintf(buf+f*3, "%02x:", MAC[f]);
+ buf[f*3-1] = '\0';
+ } else
+ strcpy(buf, "??:??:??:??:??:??");
+ } else
+ sprintf(buf, "<IFT type %d>", dl->sdl_type);
+ } else if (dl->sdl_slen)
+ sprintf(buf, "<slen %d?>", dl->sdl_slen);
+ else
+ sprintf(buf, "link#%d", dl->sdl_index);
+ break;
+
+ default:
+ sprintf(buf, "<AF type %d>", phost->sa_family);
+ break;
+ }
+
+ prompt_Printf(prompt, "%-*s ", width-1, buf);
+}
+
+static struct bits {
+ u_int32_t b_mask;
+ char b_val;
+} bits[] = {
+ { RTF_UP, 'U' },
+ { RTF_GATEWAY, 'G' },
+ { RTF_HOST, 'H' },
+ { RTF_REJECT, 'R' },
+ { RTF_DYNAMIC, 'D' },
+ { RTF_MODIFIED, 'M' },
+ { RTF_DONE, 'd' },
+ { RTF_XRESOLVE, 'X' },
+ { RTF_STATIC, 'S' },
+ { RTF_PROTO1, '1' },
+ { RTF_PROTO2, '2' },
+ { RTF_BLACKHOLE, 'B' },
+#ifdef RTF_LLINFO
+ { RTF_LLINFO, 'L' },
+#endif
+#ifdef RTF_CLONING
+ { RTF_CLONING, 'C' },
+#endif
+#ifdef RTF_PROTO3
+ { RTF_PROTO3, '3' },
+#endif
+#ifdef RTF_BROADCAST
+ { RTF_BROADCAST, 'b' },
+#endif
+ { 0, '\0' }
+};
+
+static void
+p_flags(struct prompt *prompt, u_int32_t f, unsigned max)
+{
+ char name[33], *flags;
+ register struct bits *p = bits;
+
+ if (max > sizeof name - 1)
+ max = sizeof name - 1;
+
+ for (flags = name; p->b_mask && flags - name < (int)max; p++)
+ if (p->b_mask & f)
+ *flags++ = p->b_val;
+ *flags = '\0';
+ prompt_Printf(prompt, "%-*.*s", (int)max, (int)max, name);
+}
+
+static int route_nifs = -1;
+
+const char *
+Index2Nam(int idx)
+{
+ /*
+ * XXX: Maybe we should select() on the routing socket so that we can
+ * notice interfaces that come & go (PCCARD support).
+ * Or we could even support a signal that resets these so that
+ * the PCCARD insert/remove events can signal ppp.
+ */
+ static char **ifs; /* Figure these out once */
+ static int debug_done; /* Debug once */
+
+ if (idx > route_nifs || (idx > 0 && ifs[idx-1] == NULL)) {
+ int mib[6], have, had;
+ size_t needed;
+ char *buf, *ptr, *end;
+ struct sockaddr_dl *dl;
+ struct if_msghdr *ifm;
+
+ if (ifs) {
+ free(ifs);
+ ifs = NULL;
+ route_nifs = 0;
+ }
+ debug_done = 0;
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = 0;
+ mib[4] = NET_RT_IFLIST;
+ mib[5] = 0;
+
+ if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) {
+ log_Printf(LogERROR, "Index2Nam: sysctl: estimate: %s\n",
+ strerror(errno));
+ return NumStr(idx, NULL, 0);
+ }
+ if ((buf = malloc(needed)) == NULL)
+ return NumStr(idx, NULL, 0);
+ if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) {
+ free(buf);
+ return NumStr(idx, NULL, 0);
+ }
+ end = buf + needed;
+
+ have = 0;
+ for (ptr = buf; ptr < end; ptr += ifm->ifm_msglen) {
+ ifm = (struct if_msghdr *)ptr;
+ if (ifm->ifm_type != RTM_IFINFO)
+ continue;
+ dl = (struct sockaddr_dl *)(ifm + 1);
+ if (ifm->ifm_index > 0) {
+ if (ifm->ifm_index > have) {
+ char **newifs;
+
+ had = have;
+ have = ifm->ifm_index + 5;
+ if (had)
+ newifs = (char **)realloc(ifs, sizeof(char *) * have);
+ else
+ newifs = (char **)malloc(sizeof(char *) * have);
+ if (!newifs) {
+ log_Printf(LogDEBUG, "Index2Nam: %s\n", strerror(errno));
+ route_nifs = 0;
+ if (ifs) {
+ free(ifs);
+ ifs = NULL;
+ }
+ free(buf);
+ return NumStr(idx, NULL, 0);
+ }
+ ifs = newifs;
+ memset(ifs + had, '\0', sizeof(char *) * (have - had));
+ }
+ if (ifs[ifm->ifm_index-1] == NULL) {
+ ifs[ifm->ifm_index-1] = (char *)malloc(dl->sdl_nlen+1);
+ if (ifs[ifm->ifm_index-1] == NULL)
+ log_Printf(LogDEBUG, "Skipping interface %d: Out of memory\n",
+ ifm->ifm_index);
+ else {
+ memcpy(ifs[ifm->ifm_index-1], dl->sdl_data, dl->sdl_nlen);
+ ifs[ifm->ifm_index-1][dl->sdl_nlen] = '\0';
+ if (route_nifs < ifm->ifm_index)
+ route_nifs = ifm->ifm_index;
+ }
+ }
+ } else if (log_IsKept(LogDEBUG))
+ log_Printf(LogDEBUG, "Skipping out-of-range interface %d!\n",
+ ifm->ifm_index);
+ }
+ free(buf);
+ }
+
+ if (log_IsKept(LogDEBUG) && !debug_done) {
+ int f;
+
+ log_Printf(LogDEBUG, "Found the following interfaces:\n");
+ for (f = 0; f < route_nifs; f++)
+ if (ifs[f] != NULL)
+ log_Printf(LogDEBUG, " Index %d, name \"%s\"\n", f+1, ifs[f]);
+ debug_done = 1;
+ }
+
+ if (idx < 1 || idx > route_nifs || ifs[idx-1] == NULL)
+ return NumStr(idx, NULL, 0);
+
+ return ifs[idx-1];
+}
+
+void
+route_ParseHdr(struct rt_msghdr *rtm, struct sockaddr *sa[RTAX_MAX])
+{
+ char *wp;
+ int rtax;
+
+ wp = (char *)(rtm + 1);
+
+ for (rtax = 0; rtax < RTAX_MAX; rtax++)
+ if (rtm->rtm_addrs & (1 << rtax)) {
+ sa[rtax] = (struct sockaddr *)wp;
+ wp += ROUNDUP(sa[rtax]->sa_len);
+ if (sa[rtax]->sa_family == 0)
+ sa[rtax] = NULL; /* ??? */
+ } else
+ sa[rtax] = NULL;
+}
+
+int
+route_Show(struct cmdargs const *arg)
+{
+ struct rt_msghdr *rtm;
+ struct sockaddr *sa[RTAX_MAX];
+ char *sp, *ep, *cp;
+ size_t needed;
+ int mib[6];
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = 0;
+ mib[4] = NET_RT_DUMP;
+ mib[5] = 0;
+ if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) {
+ log_Printf(LogERROR, "route_Show: sysctl: estimate: %s\n", strerror(errno));
+ return (1);
+ }
+ sp = malloc(needed);
+ if (sp == NULL)
+ return (1);
+ if (sysctl(mib, 6, sp, &needed, NULL, 0) < 0) {
+ log_Printf(LogERROR, "route_Show: sysctl: getroute: %s\n", strerror(errno));
+ free(sp);
+ return (1);
+ }
+ ep = sp + needed;
+
+ prompt_Printf(arg->prompt, "%-20s%-20sFlags Netif\n",
+ "Destination", "Gateway");
+ for (cp = sp; cp < ep; cp += rtm->rtm_msglen) {
+ rtm = (struct rt_msghdr *)cp;
+
+ route_ParseHdr(rtm, sa);
+
+ if (sa[RTAX_DST] && sa[RTAX_GATEWAY]) {
+ p_sockaddr(arg->prompt, sa[RTAX_DST], sa[RTAX_NETMASK], 20);
+ p_sockaddr(arg->prompt, sa[RTAX_GATEWAY], NULL, 20);
+
+ p_flags(arg->prompt, rtm->rtm_flags, 6);
+ prompt_Printf(arg->prompt, " %s\n", Index2Nam(rtm->rtm_index));
+ } else
+ prompt_Printf(arg->prompt, "<can't parse routing entry>\n");
+ }
+ free(sp);
+ return 0;
+}
+
+/*
+ * Delete routes associated with our interface
+ */
+void
+route_IfDelete(struct bundle *bundle, int all)
+{
+ struct rt_msghdr *rtm;
+ struct sockaddr *sa[RTAX_MAX];
+ struct ncprange range;
+ int pass;
+ size_t needed;
+ char *sp, *cp, *ep;
+ int mib[6];
+
+ log_Printf(LogDEBUG, "route_IfDelete (%d)\n", bundle->iface->index);
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = 0;
+ mib[4] = NET_RT_DUMP;
+ mib[5] = 0;
+ if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) {
+ log_Printf(LogERROR, "route_IfDelete: sysctl: estimate: %s\n",
+ strerror(errno));
+ return;
+ }
+
+ sp = malloc(needed);
+ if (sp == NULL)
+ return;
+
+ if (sysctl(mib, 6, sp, &needed, NULL, 0) < 0) {
+ log_Printf(LogERROR, "route_IfDelete: sysctl: getroute: %s\n",
+ strerror(errno));
+ free(sp);
+ return;
+ }
+ ep = sp + needed;
+
+ for (pass = 0; pass < 2; pass++) {
+ /*
+ * We do 2 passes. The first deletes all cloned routes. The second
+ * deletes all non-cloned routes. This is done to avoid
+ * potential errors from trying to delete route X after route Y where
+ * route X was cloned from route Y (and is no longer there 'cos it
+ * may have gone with route Y).
+ */
+ if (pass == 0)
+ /* So we can't tell ! */
+ continue;
+ for (cp = sp; cp < ep; cp += rtm->rtm_msglen) {
+ rtm = (struct rt_msghdr *)cp;
+ route_ParseHdr(rtm, sa);
+ if (rtm->rtm_index == bundle->iface->index &&
+ sa[RTAX_DST] && sa[RTAX_GATEWAY] &&
+ (sa[RTAX_DST]->sa_family == AF_INET
+#ifndef NOINET6
+ || sa[RTAX_DST]->sa_family == AF_INET6
+#endif
+ ) &&
+ (all || (rtm->rtm_flags & RTF_GATEWAY))) {
+ if (log_IsKept(LogDEBUG)) {
+ char gwstr[41];
+ struct ncpaddr gw;
+ ncprange_setsa(&range, sa[RTAX_DST], sa[RTAX_NETMASK]);
+ ncpaddr_setsa(&gw, sa[RTAX_GATEWAY]);
+ snprintf(gwstr, sizeof gwstr, "%s", ncpaddr_ntoa(&gw));
+ log_Printf(LogDEBUG, "Found %s %s\n", ncprange_ntoa(&range), gwstr);
+ }
+ if (sa[RTAX_GATEWAY]->sa_family == AF_INET ||
+#ifndef NOINET6
+ sa[RTAX_GATEWAY]->sa_family == AF_INET6 ||
+#endif
+ sa[RTAX_GATEWAY]->sa_family == AF_LINK) {
+ if (pass == 1) {
+ ncprange_setsa(&range, sa[RTAX_DST], sa[RTAX_NETMASK]);
+ rt_Set(bundle, RTM_DELETE, &range, NULL, 0, 0);
+ } else
+ log_Printf(LogDEBUG, "route_IfDelete: Skip it (pass %d)\n", pass);
+ } else
+ log_Printf(LogDEBUG,
+ "route_IfDelete: Can't remove routes for family %d\n",
+ sa[RTAX_GATEWAY]->sa_family);
+ }
+ }
+ }
+ free(sp);
+}
+
+
+/*
+ * Update the MTU on all routes for the given interface
+ */
+void
+route_UpdateMTU(struct bundle *bundle)
+{
+ struct rt_msghdr *rtm;
+ struct sockaddr *sa[RTAX_MAX];
+ struct ncprange dst;
+ size_t needed;
+ char *sp, *cp, *ep;
+ int mib[6];
+
+ log_Printf(LogDEBUG, "route_UpdateMTU (%d)\n", bundle->iface->index);
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = 0;
+ mib[4] = NET_RT_DUMP;
+ mib[5] = 0;
+ if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) {
+ log_Printf(LogERROR, "route_IfDelete: sysctl: estimate: %s\n",
+ strerror(errno));
+ return;
+ }
+
+ sp = malloc(needed);
+ if (sp == NULL)
+ return;
+
+ if (sysctl(mib, 6, sp, &needed, NULL, 0) < 0) {
+ log_Printf(LogERROR, "route_IfDelete: sysctl: getroute: %s\n",
+ strerror(errno));
+ free(sp);
+ return;
+ }
+ ep = sp + needed;
+
+ for (cp = sp; cp < ep; cp += rtm->rtm_msglen) {
+ rtm = (struct rt_msghdr *)cp;
+ route_ParseHdr(rtm, sa);
+ if (sa[RTAX_DST] && (sa[RTAX_DST]->sa_family == AF_INET
+#ifndef NOINET6
+ || sa[RTAX_DST]->sa_family == AF_INET6
+#endif
+ ) &&
+ sa[RTAX_GATEWAY] && rtm->rtm_index == bundle->iface->index) {
+ if (log_IsKept(LogTCPIP)) {
+ ncprange_setsa(&dst, sa[RTAX_DST], sa[RTAX_NETMASK]);
+ log_Printf(LogTCPIP, "route_UpdateMTU: Netif: %d (%s), dst %s,"
+ " mtu %lu\n", rtm->rtm_index, Index2Nam(rtm->rtm_index),
+ ncprange_ntoa(&dst), bundle->iface->mtu);
+ }
+ rt_Update(bundle, sa[RTAX_DST], sa[RTAX_GATEWAY], sa[RTAX_NETMASK],
+ sa[RTAX_IFP], sa[RTAX_IFA]);
+ }
+ }
+
+ free(sp);
+}
+
+int
+GetIfIndex(char *name)
+{
+ int idx;
+
+ idx = 1;
+ while (route_nifs == -1 || idx < route_nifs)
+ if (strcmp(Index2Nam(idx), name) == 0)
+ return idx;
+ else
+ idx++;
+ return -1;
+}
+
+void
+route_Change(struct bundle *bundle, struct sticky_route *r,
+ const struct ncpaddr *me, const struct ncpaddr *peer)
+{
+ struct ncpaddr dst;
+
+ for (; r; r = r->next) {
+ ncprange_getaddr(&r->dst, &dst);
+ if (ncpaddr_family(me) == AF_INET) {
+ if ((r->type & ROUTE_DSTMYADDR) && !ncpaddr_equal(&dst, me)) {
+ rt_Set(bundle, RTM_DELETE, &r->dst, NULL, 1, 0);
+ ncprange_sethost(&r->dst, me);
+ if (r->type & ROUTE_GWHISADDR)
+ ncpaddr_copy(&r->gw, peer);
+ } else if ((r->type & ROUTE_DSTHISADDR) && !ncpaddr_equal(&dst, peer)) {
+ rt_Set(bundle, RTM_DELETE, &r->dst, NULL, 1, 0);
+ ncprange_sethost(&r->dst, peer);
+ if (r->type & ROUTE_GWHISADDR)
+ ncpaddr_copy(&r->gw, peer);
+ } else if ((r->type & ROUTE_DSTDNS0) && !ncpaddr_equal(&dst, peer)) {
+ if (bundle->ncp.ipcp.ns.dns[0].s_addr == INADDR_NONE)
+ continue;
+ rt_Set(bundle, RTM_DELETE, &r->dst, NULL, 1, 0);
+ if (r->type & ROUTE_GWHISADDR)
+ ncpaddr_copy(&r->gw, peer);
+ } else if ((r->type & ROUTE_DSTDNS1) && !ncpaddr_equal(&dst, peer)) {
+ if (bundle->ncp.ipcp.ns.dns[1].s_addr == INADDR_NONE)
+ continue;
+ rt_Set(bundle, RTM_DELETE, &r->dst, NULL, 1, 0);
+ if (r->type & ROUTE_GWHISADDR)
+ ncpaddr_copy(&r->gw, peer);
+ } else if ((r->type & ROUTE_GWHISADDR) && !ncpaddr_equal(&r->gw, peer))
+ ncpaddr_copy(&r->gw, peer);
+#ifndef NOINET6
+ } else if (ncpaddr_family(me) == AF_INET6) {
+ if ((r->type & ROUTE_DSTMYADDR6) && !ncpaddr_equal(&dst, me)) {
+ rt_Set(bundle, RTM_DELETE, &r->dst, NULL, 1, 0);
+ ncprange_sethost(&r->dst, me);
+ if (r->type & ROUTE_GWHISADDR)
+ ncpaddr_copy(&r->gw, peer);
+ } else if ((r->type & ROUTE_DSTHISADDR6) && !ncpaddr_equal(&dst, peer)) {
+ rt_Set(bundle, RTM_DELETE, &r->dst, NULL, 1, 0);
+ ncprange_sethost(&r->dst, peer);
+ if (r->type & ROUTE_GWHISADDR)
+ ncpaddr_copy(&r->gw, peer);
+ } else if ((r->type & ROUTE_GWHISADDR6) && !ncpaddr_equal(&r->gw, peer))
+ ncpaddr_copy(&r->gw, peer);
+#endif
+ }
+ rt_Set(bundle, RTM_ADD, &r->dst, &r->gw, 1, 0);
+ }
+}
+
+void
+route_Add(struct sticky_route **rp, int type, const struct ncprange *dst,
+ const struct ncpaddr *gw)
+{
+ struct sticky_route *r;
+ int dsttype = type & ROUTE_DSTANY;
+
+ r = NULL;
+ while (*rp) {
+ if ((dsttype && dsttype == ((*rp)->type & ROUTE_DSTANY)) ||
+ (!dsttype && ncprange_equal(&(*rp)->dst, dst))) {
+ /* Oops, we already have this route - unlink it */
+ free(r); /* impossible really */
+ r = *rp;
+ *rp = r->next;
+ } else
+ rp = &(*rp)->next;
+ }
+
+ if (r == NULL) {
+ r = (struct sticky_route *)malloc(sizeof(struct sticky_route));
+ if (r == NULL) {
+ log_Printf(LogERROR, "route_Add: Out of memory!\n");
+ return;
+ }
+ }
+ r->type = type;
+ r->next = NULL;
+ ncprange_copy(&r->dst, dst);
+ ncpaddr_copy(&r->gw, gw);
+ *rp = r;
+}
+
+void
+route_Delete(struct sticky_route **rp, int type, const struct ncprange *dst)
+{
+ struct sticky_route *r;
+ int dsttype = type & ROUTE_DSTANY;
+
+ for (; *rp; rp = &(*rp)->next) {
+ if ((dsttype && dsttype == ((*rp)->type & ROUTE_DSTANY)) ||
+ (!dsttype && ncprange_equal(dst, &(*rp)->dst))) {
+ r = *rp;
+ *rp = r->next;
+ free(r);
+ break;
+ }
+ }
+}
+
+void
+route_DeleteAll(struct sticky_route **rp)
+{
+ struct sticky_route *r, *rn;
+
+ for (r = *rp; r; r = rn) {
+ rn = r->next;
+ free(r);
+ }
+ *rp = NULL;
+}
+
+void
+route_ShowSticky(struct prompt *p, struct sticky_route *r, const char *tag,
+ int indent)
+{
+ int tlen = strlen(tag);
+
+ if (tlen + 2 > indent)
+ prompt_Printf(p, "%s:\n%*s", tag, indent, "");
+ else
+ prompt_Printf(p, "%s:%*s", tag, indent - tlen - 1, "");
+
+ for (; r; r = r->next) {
+ prompt_Printf(p, "%*sadd ", tlen ? 0 : indent, "");
+ tlen = 0;
+ if (r->type & ROUTE_DSTMYADDR)
+ prompt_Printf(p, "MYADDR");
+ else if (r->type & ROUTE_DSTMYADDR6)
+ prompt_Printf(p, "MYADDR6");
+ else if (r->type & ROUTE_DSTHISADDR)
+ prompt_Printf(p, "HISADDR");
+ else if (r->type & ROUTE_DSTHISADDR6)
+ prompt_Printf(p, "HISADDR6");
+ else if (r->type & ROUTE_DSTDNS0)
+ prompt_Printf(p, "DNS0");
+ else if (r->type & ROUTE_DSTDNS1)
+ prompt_Printf(p, "DNS1");
+ else if (ncprange_isdefault(&r->dst))
+ prompt_Printf(p, "default");
+ else
+ prompt_Printf(p, "%s", ncprange_ntoa(&r->dst));
+
+ if (r->type & ROUTE_GWHISADDR)
+ prompt_Printf(p, " HISADDR\n");
+ else if (r->type & ROUTE_GWHISADDR6)
+ prompt_Printf(p, " HISADDR6\n");
+ else
+ prompt_Printf(p, " %s\n", ncpaddr_ntoa(&r->gw));
+ }
+}
+
+struct rtmsg {
+ struct rt_msghdr m_rtm;
+ char m_space[256];
+};
+
+static size_t
+memcpy_roundup(char *cp, const void *data, size_t len)
+{
+ size_t padlen;
+
+ padlen = ROUNDUP(len);
+ memcpy(cp, data, len);
+ if (padlen > len)
+ memset(cp + len, '\0', padlen - len);
+
+ return padlen;
+}
+
+#if defined(__KAME__) && !defined(NOINET6)
+static void
+add_scope(struct sockaddr *sa, int ifindex)
+{
+ struct sockaddr_in6 *sa6;
+
+ if (sa->sa_family != AF_INET6)
+ return;
+ sa6 = (struct sockaddr_in6 *)sa;
+ if (!IN6_IS_ADDR_LINKLOCAL(&sa6->sin6_addr) &&
+ !IN6_IS_ADDR_MC_LINKLOCAL(&sa6->sin6_addr))
+ return;
+ if (*(u_int16_t *)&sa6->sin6_addr.s6_addr[2] != 0)
+ return;
+ *(u_int16_t *)&sa6->sin6_addr.s6_addr[2] = htons(ifindex);
+}
+#endif
+
+int
+rt_Set(struct bundle *bundle, int cmd, const struct ncprange *dst,
+ const struct ncpaddr *gw, int bang, int quiet)
+{
+ struct rtmsg rtmes;
+ int s, nb, wb;
+ char *cp;
+ const char *cmdstr;
+ struct sockaddr_storage sadst, samask, sagw;
+ int result = 1;
+
+ if (bang)
+ cmdstr = (cmd == RTM_ADD ? "Add!" : "Delete!");
+ else
+ cmdstr = (cmd == RTM_ADD ? "Add" : "Delete");
+ s = ID0socket(PF_ROUTE, SOCK_RAW, 0);
+ if (s < 0) {
+ log_Printf(LogERROR, "rt_Set: socket(): %s\n", strerror(errno));
+ return result;
+ }
+ memset(&rtmes, '\0', sizeof rtmes);
+ rtmes.m_rtm.rtm_version = RTM_VERSION;
+ rtmes.m_rtm.rtm_type = cmd;
+ rtmes.m_rtm.rtm_addrs = RTA_DST;
+ rtmes.m_rtm.rtm_seq = ++bundle->routing_seq;
+ rtmes.m_rtm.rtm_pid = getpid();
+ rtmes.m_rtm.rtm_flags = RTF_UP | RTF_GATEWAY | RTF_STATIC;
+
+ if (cmd == RTM_ADD) {
+ if (bundle->ncp.cfg.sendpipe > 0) {
+ rtmes.m_rtm.rtm_rmx.rmx_sendpipe = bundle->ncp.cfg.sendpipe;
+ rtmes.m_rtm.rtm_inits |= RTV_SPIPE;
+ }
+ if (bundle->ncp.cfg.recvpipe > 0) {
+ rtmes.m_rtm.rtm_rmx.rmx_recvpipe = bundle->ncp.cfg.recvpipe;
+ rtmes.m_rtm.rtm_inits |= RTV_RPIPE;
+ }
+ }
+
+ ncprange_getsa(dst, &sadst, &samask);
+#if defined(__KAME__) && !defined(NOINET6)
+ add_scope((struct sockaddr *)&sadst, bundle->iface->index);
+#endif
+
+ cp = rtmes.m_space;
+ cp += memcpy_roundup(cp, &sadst, sadst.ss_len);
+ if (cmd == RTM_ADD) {
+ if (gw == NULL) {
+ log_Printf(LogERROR, "rt_Set: Program error\n");
+ close(s);
+ return result;
+ }
+ ncpaddr_getsa(gw, &sagw);
+#if defined(__KAME__) && !defined(NOINET6)
+ add_scope((struct sockaddr *)&sagw, bundle->iface->index);
+#endif
+ if (ncpaddr_isdefault(gw)) {
+ if (!quiet)
+ log_Printf(LogERROR, "rt_Set: Cannot add a route with"
+ " gateway 0.0.0.0\n");
+ close(s);
+ return result;
+ } else {
+ cp += memcpy_roundup(cp, &sagw, sagw.ss_len);
+ rtmes.m_rtm.rtm_addrs |= RTA_GATEWAY;
+ }
+ }
+
+ if (!ncprange_ishost(dst)) {
+ cp += memcpy_roundup(cp, &samask, samask.ss_len);
+ rtmes.m_rtm.rtm_addrs |= RTA_NETMASK;
+ }
+
+ nb = cp - (char *)&rtmes;
+ rtmes.m_rtm.rtm_msglen = nb;
+ wb = ID0write(s, &rtmes, nb);
+ if (wb < 0) {
+ log_Printf(LogTCPIP, "rt_Set failure:\n");
+ log_Printf(LogTCPIP, "rt_Set: Cmd = %s\n", cmdstr);
+ log_Printf(LogTCPIP, "rt_Set: Dst = %s\n", ncprange_ntoa(dst));
+ if (gw != NULL)
+ log_Printf(LogTCPIP, "rt_Set: Gateway = %s\n", ncpaddr_ntoa(gw));
+failed:
+ if (cmd == RTM_ADD && (rtmes.m_rtm.rtm_errno == EEXIST ||
+ (rtmes.m_rtm.rtm_errno == 0 && errno == EEXIST))) {
+ if (!bang) {
+ log_Printf(LogWARN, "Add route failed: %s already exists\n",
+ ncprange_ntoa(dst));
+ result = 0; /* Don't add to our dynamic list */
+ } else {
+ rtmes.m_rtm.rtm_type = cmd = RTM_CHANGE;
+ if ((wb = ID0write(s, &rtmes, nb)) < 0)
+ goto failed;
+ }
+ } else if (cmd == RTM_DELETE &&
+ (rtmes.m_rtm.rtm_errno == ESRCH ||
+ (rtmes.m_rtm.rtm_errno == 0 && errno == ESRCH))) {
+ if (!bang)
+ log_Printf(LogWARN, "Del route failed: %s: Non-existent\n",
+ ncprange_ntoa(dst));
+ } else if (rtmes.m_rtm.rtm_errno == 0) {
+ if (!quiet || errno != ENETUNREACH)
+ log_Printf(LogWARN, "%s route failed: %s: errno: %s\n", cmdstr,
+ ncprange_ntoa(dst), strerror(errno));
+ } else
+ log_Printf(LogWARN, "%s route failed: %s: %s\n",
+ cmdstr, ncprange_ntoa(dst), strerror(rtmes.m_rtm.rtm_errno));
+ }
+
+ if (log_IsKept(LogDEBUG)) {
+ char gwstr[40];
+
+ if (gw)
+ snprintf(gwstr, sizeof gwstr, "%s", ncpaddr_ntoa(gw));
+ else
+ snprintf(gwstr, sizeof gwstr, "<none>");
+ log_Printf(LogDEBUG, "wrote %d: cmd = %s, dst = %s, gateway = %s\n",
+ wb, cmdstr, ncprange_ntoa(dst), gwstr);
+ }
+ close(s);
+
+ return result;
+}
+
+void
+rt_Update(struct bundle *bundle, const struct sockaddr *dst,
+ const struct sockaddr *gw, const struct sockaddr *mask,
+ const struct sockaddr *ifp, const struct sockaddr *ifa)
+{
+ struct ncprange ncpdst;
+ struct rtmsg rtmes;
+ char *p;
+ int s, wb;
+
+ s = ID0socket(PF_ROUTE, SOCK_RAW, 0);
+ if (s < 0) {
+ log_Printf(LogERROR, "rt_Update: socket(): %s\n", strerror(errno));
+ return;
+ }
+
+ memset(&rtmes, '\0', sizeof rtmes);
+ rtmes.m_rtm.rtm_version = RTM_VERSION;
+ rtmes.m_rtm.rtm_type = RTM_CHANGE;
+ rtmes.m_rtm.rtm_addrs = 0;
+ rtmes.m_rtm.rtm_seq = ++bundle->routing_seq;
+ rtmes.m_rtm.rtm_pid = getpid();
+ rtmes.m_rtm.rtm_flags = RTF_UP | RTF_STATIC;
+
+ if (bundle->ncp.cfg.sendpipe > 0) {
+ rtmes.m_rtm.rtm_rmx.rmx_sendpipe = bundle->ncp.cfg.sendpipe;
+ rtmes.m_rtm.rtm_inits |= RTV_SPIPE;
+ }
+
+ if (bundle->ncp.cfg.recvpipe > 0) {
+ rtmes.m_rtm.rtm_rmx.rmx_recvpipe = bundle->ncp.cfg.recvpipe;
+ rtmes.m_rtm.rtm_inits |= RTV_RPIPE;
+ }
+
+ rtmes.m_rtm.rtm_rmx.rmx_mtu = bundle->iface->mtu;
+ rtmes.m_rtm.rtm_inits |= RTV_MTU;
+ p = rtmes.m_space;
+
+ if (dst) {
+ rtmes.m_rtm.rtm_addrs |= RTA_DST;
+ p += memcpy_roundup(p, dst, dst->sa_len);
+ }
+
+ if (gw) {
+ rtmes.m_rtm.rtm_addrs |= RTA_GATEWAY;
+ p += memcpy_roundup(p, gw, gw->sa_len);
+ }
+
+ if (mask) {
+ rtmes.m_rtm.rtm_addrs |= RTA_NETMASK;
+ p += memcpy_roundup(p, mask, mask->sa_len);
+ }
+
+ if (ifa && ifp && ifp->sa_family == AF_LINK) {
+ rtmes.m_rtm.rtm_addrs |= RTA_IFP;
+ p += memcpy_roundup(p, ifp, ifp->sa_len);
+ rtmes.m_rtm.rtm_addrs |= RTA_IFA;
+ p += memcpy_roundup(p, ifa, ifa->sa_len);
+ }
+
+ rtmes.m_rtm.rtm_msglen = p - (char *)&rtmes;
+
+ wb = ID0write(s, &rtmes, rtmes.m_rtm.rtm_msglen);
+ if (wb < 0) {
+ ncprange_setsa(&ncpdst, dst, mask);
+
+ log_Printf(LogTCPIP, "rt_Update failure:\n");
+ log_Printf(LogTCPIP, "rt_Update: Dst = %s\n", ncprange_ntoa(&ncpdst));
+
+ if (rtmes.m_rtm.rtm_errno == 0)
+ log_Printf(LogWARN, "%s: Change route failed: errno: %s\n",
+ ncprange_ntoa(&ncpdst), strerror(errno));
+ else
+ log_Printf(LogWARN, "%s: Change route failed: %s\n",
+ ncprange_ntoa(&ncpdst), strerror(rtmes.m_rtm.rtm_errno));
+ }
+ close(s);
+}
diff --git a/usr.sbin/ppp/route.h b/usr.sbin/ppp/route.h
new file mode 100644
index 0000000..bb8bd38
--- /dev/null
+++ b/usr.sbin/ppp/route.h
@@ -0,0 +1,74 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+struct bundle;
+struct cmdargs;
+struct rt_msghdr;
+struct sockaddr;
+
+#define ROUTE_STATIC 0x0000
+#define ROUTE_DSTMYADDR 0x0001
+#define ROUTE_DSTMYADDR6 0x0002
+#define ROUTE_DSTHISADDR 0x0004
+#define ROUTE_DSTHISADDR6 0x0008
+#define ROUTE_DSTDNS0 0x0010
+#define ROUTE_DSTDNS1 0x0020
+#define ROUTE_DSTANY 0x0040
+#define ROUTE_GWHISADDR 0x0080 /* May be ORd with DST_* */
+#define ROUTE_GWHISADDR6 0x0100 /* May be ORd with DST_* */
+
+struct sticky_route {
+ int type; /* ROUTE_* value (not _STATIC) */
+ struct sticky_route *next; /* next in list */
+
+ struct ncprange dst;
+ struct ncpaddr gw;
+};
+
+extern int GetIfIndex(char *);
+extern int route_Show(struct cmdargs const *);
+extern void route_IfDelete(struct bundle *, int);
+extern void route_UpdateMTU(struct bundle *);
+extern const char *Index2Nam(int);
+extern void route_Change(struct bundle *, struct sticky_route *,
+ const struct ncpaddr *, const struct ncpaddr *);
+extern void route_Add(struct sticky_route **, int, const struct ncprange *,
+ const struct ncpaddr *);
+extern void route_Delete(struct sticky_route **, int, const struct ncprange *);
+extern void route_DeleteAll(struct sticky_route **);
+extern void route_Clean(struct bundle *, struct sticky_route *);
+extern void route_ShowSticky(struct prompt *, struct sticky_route *,
+ const char *, int);
+extern void route_ParseHdr(struct rt_msghdr *, struct sockaddr *[RTAX_MAX]);
+extern int rt_Set(struct bundle *, int, const struct ncprange *,
+ const struct ncpaddr *, int, int);
+extern void rt_Update(struct bundle *, const struct sockaddr *,
+ const struct sockaddr *, const struct sockaddr *,
+ const struct sockaddr *, const struct sockaddr *);
diff --git a/usr.sbin/ppp/server.c b/usr.sbin/ppp/server.c
new file mode 100644
index 0000000..3997eaf
--- /dev/null
+++ b/usr.sbin/ppp/server.c
@@ -0,0 +1,422 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.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/param.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "descriptor.h"
+#include "server.h"
+#include "prompt.h"
+#include "ncpaddr.h"
+#include "probe.h"
+
+static int
+server_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e, int *n)
+{
+ struct server *s = descriptor2server(d);
+ struct prompt *p;
+ int sets;
+
+ sets = 0;
+ if (r && s->fd >= 0) {
+ if (*n < s->fd + 1)
+ *n = s->fd + 1;
+ FD_SET(s->fd, r);
+ log_Printf(LogTIMER, "server: fdset(r) %d\n", s->fd);
+ sets++;
+ }
+
+ for (p = log_PromptList(); p; p = p->next)
+ sets += descriptor_UpdateSet(&p->desc, r, w, e, n);
+
+ return sets;
+}
+
+static int
+server_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+ struct server *s = descriptor2server(d);
+ struct prompt *p;
+
+ if (s->fd >= 0 && FD_ISSET(s->fd, fdset))
+ return 1;
+
+ for (p = log_PromptList(); p; p = p->next)
+ if (descriptor_IsSet(&p->desc, fdset))
+ return 1;
+
+ return 0;
+}
+
+static void
+server_Read(struct fdescriptor *d, struct bundle *bundle, const fd_set *fdset)
+{
+ struct server *s = descriptor2server(d);
+ struct sockaddr_storage ss;
+ struct sockaddr *sa = (struct sockaddr *)&ss;
+ struct sockaddr_in *sin = (struct sockaddr_in *)&ss;
+#ifndef NOINET6
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&ss;
+#endif
+ int ssize = sizeof ss, wfd;
+ struct prompt *p;
+ struct ncpaddr addr;
+
+ if (s->fd >= 0 && FD_ISSET(s->fd, fdset)) {
+ wfd = accept(s->fd, sa, &ssize);
+ if (wfd < 0)
+ log_Printf(LogERROR, "server_Read: accept(): %s\n", strerror(errno));
+ else if (sa->sa_len == 0) {
+ close(wfd);
+ wfd = -1;
+ }
+ } else
+ wfd = -1;
+
+ if (wfd >= 0)
+ switch (sa->sa_family) {
+ case AF_LOCAL:
+ log_Printf(LogPHASE, "Connected to local client.\n");
+ break;
+
+ case AF_INET:
+ ncpaddr_setsa(&addr, sa);
+ if (ntohs(sin->sin_port) < 1024) {
+ log_Printf(LogALERT, "Rejected client connection from %s:%u"
+ "(invalid port number) !\n",
+ ncpaddr_ntoa(&addr), ntohs(sin->sin_port));
+ close(wfd);
+ wfd = -1;
+ break;
+ }
+ log_Printf(LogPHASE, "Connected to client from %s:%u\n",
+ ncpaddr_ntoa(&addr), ntohs(sin->sin_port));
+ break;
+
+#ifndef NOINET6
+ case AF_INET6:
+ ncpaddr_setsa(&addr, sa);
+ if (ntohs(sin6->sin6_port) < 1024) {
+ log_Printf(LogALERT, "Rejected client connection from %s:%u"
+ "(invalid port number) !\n",
+ ncpaddr_ntoa(&addr), ntohs(sin6->sin6_port));
+ close(wfd);
+ wfd = -1;
+ break;
+ }
+ log_Printf(LogPHASE, "Connected to client from %s:%u\n",
+ ncpaddr_ntoa(&addr), ntohs(sin6->sin6_port));
+ break;
+#endif
+
+ default:
+ write(wfd, "Unrecognised access !\n", 22);
+ close(wfd);
+ wfd = -1;
+ break;
+ }
+
+ if (wfd >= 0) {
+ if ((p = prompt_Create(s, bundle, wfd)) == NULL) {
+ write(wfd, "Connection refused.\n", 20);
+ close(wfd);
+ } else {
+ switch (sa->sa_family) {
+ case AF_LOCAL:
+ p->src.type = "local";
+ strncpy(p->src.from, s->cfg.sockname, sizeof p->src.from - 1);
+ p->src.from[sizeof p->src.from - 1] = '\0';
+ break;
+ case AF_INET:
+ p->src.type = "ip";
+ snprintf(p->src.from, sizeof p->src.from, "%s:%u",
+ ncpaddr_ntoa(&addr), ntohs(sin->sin_port));
+ break;
+#ifndef NOINET6
+ case AF_INET6:
+ p->src.type = "ip6";
+ snprintf(p->src.from, sizeof p->src.from, "%s:%u",
+ ncpaddr_ntoa(&addr), ntohs(sin6->sin6_port));
+ break;
+#endif
+ }
+ prompt_TtyCommandMode(p);
+ prompt_Required(p);
+ }
+ }
+
+ log_PromptListChanged = 0;
+ for (p = log_PromptList(); p; p = p->next)
+ if (descriptor_IsSet(&p->desc, fdset)) {
+ descriptor_Read(&p->desc, bundle, fdset);
+ if (log_PromptListChanged)
+ break;
+ }
+}
+
+static int
+server_Write(struct fdescriptor *d __unused, struct bundle *bundle __unused,
+ const fd_set *fdset __unused)
+{
+ /* We never want to write here ! */
+ log_Printf(LogALERT, "server_Write: Internal error: Bad call !\n");
+ return 0;
+}
+
+struct server server = {
+ {
+ SERVER_DESCRIPTOR,
+ server_UpdateSet,
+ server_IsSet,
+ server_Read,
+ server_Write
+ },
+ -1,
+ { "", "", 0, 0 }
+};
+
+enum server_stat
+server_Reopen(struct bundle *bundle)
+{
+ char name[sizeof server.cfg.sockname];
+ struct stat st;
+ u_short port;
+ mode_t mask;
+ enum server_stat ret;
+
+ if (server.cfg.sockname[0] != '\0') {
+ strcpy(name, server.cfg.sockname);
+ mask = server.cfg.mask;
+ server_Close(bundle);
+ if (server.cfg.sockname[0] != '\0' && stat(server.cfg.sockname, &st) == 0)
+ if (!(st.st_mode & S_IFSOCK) || unlink(server.cfg.sockname) != 0)
+ return SERVER_FAILED;
+ ret = server_LocalOpen(bundle, name, mask);
+ } else if (server.cfg.port != 0) {
+ port = server.cfg.port;
+ server_Close(bundle);
+ ret = server_TcpOpen(bundle, port);
+ } else
+ ret = SERVER_UNSET;
+
+ return ret;
+}
+
+enum server_stat
+server_LocalOpen(struct bundle *bundle, const char *name, mode_t mask)
+{
+ struct sockaddr_un ifsun;
+ mode_t oldmask;
+ int s;
+
+ oldmask = (mode_t)-1; /* Silence compiler */
+
+ if (server.cfg.sockname[0] != '\0' && !strcmp(server.cfg.sockname, name))
+ server_Close(bundle);
+
+ memset(&ifsun, '\0', sizeof ifsun);
+ ifsun.sun_len = strlen(name);
+ if (ifsun.sun_len > sizeof ifsun.sun_path - 1) {
+ log_Printf(LogERROR, "Local: %s: Path too long\n", name);
+ return SERVER_INVALID;
+ }
+ ifsun.sun_family = AF_LOCAL;
+ strcpy(ifsun.sun_path, name);
+
+ s = socket(PF_LOCAL, SOCK_STREAM, 0);
+ if (s < 0) {
+ log_Printf(LogERROR, "Local: socket: %s\n", strerror(errno));
+ goto failed;
+ }
+ setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &s, sizeof s);
+ if (mask != (mode_t)-1)
+ oldmask = umask(mask);
+ if (bind(s, (struct sockaddr *)&ifsun, sizeof ifsun) < 0) {
+ if (mask != (mode_t)-1)
+ umask(oldmask);
+ log_Printf(LogWARN, "Local: bind: %s\n", strerror(errno));
+ close(s);
+ goto failed;
+ }
+ if (mask != (mode_t)-1)
+ umask(oldmask);
+ if (listen(s, 5) != 0) {
+ log_Printf(LogERROR, "Local: Unable to listen to socket -"
+ " BUNDLE overload?\n");
+ close(s);
+ unlink(name);
+ goto failed;
+ }
+ server_Close(bundle);
+ server.fd = s;
+ server.cfg.port = 0;
+ strncpy(server.cfg.sockname, ifsun.sun_path, sizeof server.cfg.sockname - 1);
+ server.cfg.sockname[sizeof server.cfg.sockname - 1] = '\0';
+ server.cfg.mask = mask;
+ log_Printf(LogPHASE, "Listening at local socket %s.\n", name);
+
+ return SERVER_OK;
+
+failed:
+ if (server.fd == -1) {
+ server.fd = -1;
+ server.cfg.port = 0;
+ strncpy(server.cfg.sockname, ifsun.sun_path,
+ sizeof server.cfg.sockname - 1);
+ server.cfg.sockname[sizeof server.cfg.sockname - 1] = '\0';
+ server.cfg.mask = mask;
+ }
+ return SERVER_FAILED;
+}
+
+enum server_stat
+server_TcpOpen(struct bundle *bundle, u_short port)
+{
+ struct sockaddr_storage ss;
+ struct sockaddr_in *sin = (struct sockaddr_in *)&ss;
+#ifndef NOINET6
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&ss;
+#endif
+ int s, sz;
+
+ if (server.cfg.port == port)
+ server_Close(bundle);
+
+ if (port == 0)
+ return SERVER_INVALID;
+
+ memset(&ss, '\0', sizeof ss);
+#ifndef NOINET6
+ if (probe.ipv6_available) {
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_port = htons(port);
+ sin6->sin6_len = (u_int8_t)sizeof ss;
+ sz = sizeof *sin6;
+ s = socket(PF_INET6, SOCK_STREAM, 0);
+ } else
+#endif
+ {
+ sin->sin_family = AF_INET;
+ sin->sin_port = htons(port);
+ sin->sin_len = (u_int8_t)sizeof ss;
+ sin->sin_addr.s_addr = INADDR_ANY;
+ sz = sizeof *sin;
+ s = socket(PF_INET, SOCK_STREAM, 0);
+ }
+
+ if (s < 0) {
+ log_Printf(LogERROR, "Tcp: socket: %s\n", strerror(errno));
+ goto failed;
+ }
+
+#ifndef NOINET6
+ if (probe.ipv6_available) {
+ int off = 0;
+ setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&off, sizeof(off));
+ }
+#endif
+
+ setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &s, sizeof s);
+ if (bind(s, (struct sockaddr *)&ss, sz) < 0) {
+ log_Printf(LogWARN, "Tcp: bind: %s\n", strerror(errno));
+ close(s);
+ goto failed;
+ }
+ if (listen(s, 5) != 0) {
+ log_Printf(LogERROR, "Tcp: Unable to listen to socket: %s\n",
+ strerror(errno));
+ close(s);
+ goto failed;
+ }
+ server_Close(bundle);
+ server.fd = s;
+ server.cfg.port = port;
+ *server.cfg.sockname = '\0';
+ server.cfg.mask = 0;
+ log_Printf(LogPHASE, "Listening at port %d.\n", port);
+ return SERVER_OK;
+
+failed:
+ if (server.fd == -1) {
+ server.fd = -1;
+ server.cfg.port = port;
+ *server.cfg.sockname = '\0';
+ server.cfg.mask = 0;
+ }
+ return SERVER_FAILED;
+}
+
+int
+server_Close(struct bundle *bundle __unused)
+{
+ if (server.fd >= 0) {
+ if (*server.cfg.sockname != '\0') {
+ struct sockaddr_un un;
+ int sz = sizeof un;
+
+ if (getsockname(server.fd, (struct sockaddr *)&un, &sz) == 0 &&
+ un.sun_family == AF_LOCAL && sz == sizeof un)
+ unlink(un.sun_path);
+ }
+ close(server.fd);
+ server.fd = -1;
+ /* Drop associated prompts */
+ log_DestroyPrompts(&server);
+
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+server_Clear(struct bundle *bundle)
+{
+ int ret;
+
+ ret = server_Close(bundle);
+
+ server.fd = -1;
+ server.cfg.port = 0;
+ *server.cfg.sockname = '\0';
+ server.cfg.mask = 0;
+
+ return ret;
+}
diff --git a/usr.sbin/ppp/server.h b/usr.sbin/ppp/server.h
new file mode 100644
index 0000000..9fdc52c
--- /dev/null
+++ b/usr.sbin/ppp/server.h
@@ -0,0 +1,61 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.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$
+ */
+
+struct bundle;
+
+struct server {
+ struct fdescriptor desc;
+ int fd;
+
+ struct {
+ char passwd[50];
+
+ char sockname[PATH_MAX]; /* Points to local socket path */
+ mode_t mask;
+
+ u_short port; /* tcp socket */
+ } cfg;
+};
+
+enum server_stat {
+ SERVER_OK, /* Diagnostic socket available */
+ SERVER_INVALID, /* Bad args, can't be set up */
+ SERVER_FAILED, /* Failed - lack of resources */
+ SERVER_UNSET /* Not already set up */
+};
+
+#define descriptor2server(d) \
+ ((d)->type == SERVER_DESCRIPTOR ? (struct server *)(d) : NULL)
+
+extern struct server server;
+
+extern enum server_stat server_LocalOpen(struct bundle *, const char *, mode_t);
+extern enum server_stat server_TcpOpen(struct bundle *, u_short);
+extern enum server_stat server_Reopen(struct bundle *);
+extern int server_Close(struct bundle *);
+extern int server_Clear(struct bundle *);
diff --git a/usr.sbin/ppp/sig.c b/usr.sbin/ppp/sig.c
new file mode 100644
index 0000000..a42194b
--- /dev/null
+++ b/usr.sbin/ppp/sig.c
@@ -0,0 +1,119 @@
+/*-
+ * Copyright (c) 1997 - 1999, 2001 Brian Somers <brian@Awfulhak.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/types.h>
+
+#include <signal.h>
+
+#include "log.h"
+#include "sig.h"
+
+static int caused[NSIG]; /* An array of pending signals */
+static int necessary; /* Anything set ? */
+static sig_type handler[NSIG]; /* all start at SIG_DFL */
+
+
+/*
+ * Record a signal in the "caused" array
+ *
+ * This function is the only thing actually called in signal context. It
+ * records that a signal has been caused and that sig_Handle() should be
+ * called (in non-signal context) as soon as possible to process that
+ * signal.
+ */
+static void
+signal_recorder(int sig)
+{
+ caused[sig - 1]++;
+ necessary = 1;
+}
+
+
+/*
+ * Set up signal_recorder to handle the given sig and record ``fn'' as
+ * the function to ultimately call in sig_Handle(). ``fn'' will not be
+ * called in signal context (as sig_Handle() is not called in signal
+ * context).
+ */
+sig_type
+sig_signal(int sig, sig_type fn)
+{
+ sig_type Result;
+
+ if (sig <= 0 || sig > NSIG) {
+ /* Oops - we must be a bit out of date (too many sigs ?) */
+ log_Printf(LogALERT, "Eeek! %s:%d: I must be out of date!\n",
+ __FILE__, __LINE__);
+ return signal(sig, fn);
+ }
+ Result = handler[sig - 1];
+ if (fn == SIG_DFL || fn == SIG_IGN) {
+ signal(sig, fn);
+ handler[sig - 1] = (sig_type) 0;
+ } else {
+ handler[sig - 1] = fn;
+ signal(sig, signal_recorder);
+ }
+ caused[sig - 1] = 0;
+ return Result;
+}
+
+
+/*
+ * Call the handlers for any pending signals
+ *
+ * This function is called from a non-signal context - in fact, it's
+ * called every time select() in DoLoop() returns - just in case
+ * select() returned due to a signal being recorded by signal_recorder().
+ */
+int
+sig_Handle()
+{
+ int sig;
+ int got;
+ int result;
+
+ result = 0;
+ if (necessary) {
+ /* We've *probably* got something in `caused' set */
+ necessary = 0;
+ /* `necessary' might go back to 1 while we're in here.... */
+ do {
+ got = 0;
+ for (sig = 0; sig < NSIG; sig++)
+ if (caused[sig]) {
+ caused[sig]--;
+ got++;
+ result++;
+ (*handler[sig])(sig + 1);
+ }
+ } while (got);
+ }
+
+ return result;
+}
diff --git a/usr.sbin/ppp/sig.h b/usr.sbin/ppp/sig.h
new file mode 100644
index 0000000..27d264c
--- /dev/null
+++ b/usr.sbin/ppp/sig.h
@@ -0,0 +1,35 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.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$
+ */
+
+typedef void (*sig_type)(int);
+
+/* Call this instead of signal() */
+extern sig_type sig_signal(int, sig_type);
+
+/* Call this when you want things to *actually* happen */
+extern int sig_Handle(void);
diff --git a/usr.sbin/ppp/slcompress.c b/usr.sbin/ppp/slcompress.c
new file mode 100644
index 0000000..48928c7
--- /dev/null
+++ b/usr.sbin/ppp/slcompress.c
@@ -0,0 +1,605 @@
+/*-
+ * 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.
+ * 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.
+ *
+ * @(#)slcompress.c 8.2 (Berkeley) 4/16/94
+ */
+
+/*
+ * Routines to compress and uncompess tcp packets (for transmission
+ * over low speed serial lines.
+ *
+ * Van Jacobson (van@helios.ee.lbl.gov), Dec 31, 1989:
+ * - Initial distribution.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <termios.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "slcompress.h"
+#include "descriptor.h"
+#include "prompt.h"
+#include "timer.h"
+#include "fsm.h"
+#include "throughput.h"
+#include "iplist.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+
+void
+sl_compress_init(struct slcompress *comp, int max_state)
+{
+ register u_int i;
+ register struct cstate *tstate = comp->tstate;
+
+ memset(comp, '\0', sizeof *comp);
+ for (i = max_state; i > 0; --i) {
+ tstate[i].cs_id = i;
+ tstate[i].cs_next = &tstate[i - 1];
+ }
+ tstate[0].cs_next = &tstate[max_state];
+ tstate[0].cs_id = 0;
+ comp->last_cs = &tstate[0];
+ comp->last_recv = 255;
+ comp->last_xmit = 255;
+ comp->flags = SLF_TOSS;
+}
+
+
+/* ENCODE encodes a number that is known to be non-zero. ENCODEZ
+ * checks for zero (since zero has to be encoded in the 32-bit, 3 byte
+ * form).
+ */
+#define ENCODE(n) { \
+ if ((u_short)(n) >= 256) { \
+ *cp++ = 0; \
+ cp[1] = (n); \
+ cp[0] = (n) >> 8; \
+ cp += 2; \
+ } else { \
+ *cp++ = (n); \
+ } \
+}
+#define ENCODEZ(n) { \
+ if ((u_short)(n) >= 256 || (u_short)(n) == 0) { \
+ *cp++ = 0; \
+ cp[1] = (n); \
+ cp[0] = (n) >> 8; \
+ cp += 2; \
+ } else { \
+ *cp++ = (n); \
+ } \
+}
+
+#define DECODEL(f) { \
+ if (*cp == 0) {\
+ (f) = htonl(ntohl(f) + ((cp[1] << 8) | cp[2])); \
+ cp += 3; \
+ } else { \
+ (f) = htonl(ntohl(f) + (u_int32_t)*cp++); \
+ } \
+}
+
+#define DECODES(f) { \
+ if (*cp == 0) {\
+ (f) = htons(ntohs(f) + ((cp[1] << 8) | cp[2])); \
+ cp += 3; \
+ } else { \
+ (f) = htons(ntohs(f) + (u_int32_t)*cp++); \
+ } \
+}
+
+#define DECODEU(f) { \
+ if (*cp == 0) {\
+ (f) = htons((cp[1] << 8) | cp[2]); \
+ cp += 3; \
+ } else { \
+ (f) = htons((u_int32_t)*cp++); \
+ } \
+}
+
+
+u_char
+sl_compress_tcp(struct mbuf * m,
+ struct ip * ip,
+ struct slcompress *comp,
+ struct slstat *slstat,
+ int compress_cid)
+{
+ register struct cstate *cs = comp->last_cs->cs_next;
+ register u_int hlen = ip->ip_hl;
+ register struct tcphdr *oth;
+ register struct tcphdr *th;
+ register u_int deltaS, deltaA;
+ register u_int changes = 0;
+ u_char new_seq[16];
+ register u_char *cp = new_seq;
+
+ /*
+ * Bail if this is an IP fragment or if the TCP packet isn't `compressible'
+ * (i.e., ACK isn't set or some other control bit is set). (We assume that
+ * the caller has already made sure the packet is IP proto TCP).
+ */
+ if ((ip->ip_off & htons(0x3fff)) || m->m_len < 40) {
+ log_Printf(LogDEBUG, "??? 1 ip_off = %x, m_len = %lu\n",
+ ip->ip_off, (unsigned long)m->m_len);
+ log_DumpBp(LogDEBUG, "", m);
+ return (TYPE_IP);
+ }
+ th = (struct tcphdr *) & ((int *) ip)[hlen];
+ if ((th->th_flags & (TH_SYN | TH_FIN | TH_RST | TH_ACK)) != TH_ACK) {
+ log_Printf(LogDEBUG, "??? 2 th_flags = %x\n", th->th_flags);
+ log_DumpBp(LogDEBUG, "", m);
+ return (TYPE_IP);
+ }
+
+ /*
+ * Packet is compressible -- we're going to send either a COMPRESSED_TCP or
+ * UNCOMPRESSED_TCP packet. Either way we need to locate (or create) the
+ * connection state. Special case the most recently used connection since
+ * it's most likely to be used again & we don't have to do any reordering
+ * if it's used.
+ */
+ slstat->sls_packets++;
+ if (ip->ip_src.s_addr != cs->cs_ip.ip_src.s_addr ||
+ ip->ip_dst.s_addr != cs->cs_ip.ip_dst.s_addr ||
+ *(int *) th != ((int *) &cs->cs_ip)[cs->cs_ip.ip_hl]) {
+
+ /*
+ * Wasn't the first -- search for it.
+ *
+ * States are kept in a circularly linked list with last_cs pointing to the
+ * end of the list. The list is kept in lru order by moving a state to
+ * the head of the list whenever it is referenced. Since the list is
+ * short and, empirically, the connection we want is almost always near
+ * the front, we locate states via linear search. If we don't find a
+ * state for the datagram, the oldest state is (re-)used.
+ */
+ register struct cstate *lcs;
+ register struct cstate *lastcs = comp->last_cs;
+
+ do {
+ lcs = cs;
+ cs = cs->cs_next;
+ slstat->sls_searches++;
+ if (ip->ip_src.s_addr == cs->cs_ip.ip_src.s_addr
+ && ip->ip_dst.s_addr == cs->cs_ip.ip_dst.s_addr
+ && *(int *) th == ((int *) &cs->cs_ip)[cs->cs_ip.ip_hl])
+ goto found;
+ } while (cs != lastcs);
+
+ /*
+ * Didn't find it -- re-use oldest cstate. Send an uncompressed packet
+ * that tells the other side what connection number we're using for this
+ * conversation. Note that since the state list is circular, the oldest
+ * state points to the newest and we only need to set last_cs to update
+ * the lru linkage.
+ */
+ slstat->sls_misses++;
+ comp->last_cs = lcs;
+#define THOFFSET(th) (th->th_off)
+ hlen += th->th_off;
+ hlen <<= 2;
+ if (hlen > m->m_len)
+ return (TYPE_IP);
+ goto uncompressed;
+
+found:
+
+ /*
+ * Found it -- move to the front on the connection list.
+ */
+ if (cs == lastcs)
+ comp->last_cs = lcs;
+ else {
+ lcs->cs_next = cs->cs_next;
+ cs->cs_next = lastcs->cs_next;
+ lastcs->cs_next = cs;
+ }
+ }
+
+ /*
+ * Make sure that only what we expect to change changed. The first line of
+ * the `if' checks the IP protocol version, header length & type of
+ * service. The 2nd line checks the "Don't fragment" bit. The 3rd line
+ * checks the time-to-live and protocol (the protocol check is unnecessary
+ * but costless). The 4th line checks the TCP header length. The 5th line
+ * checks IP options, if any. The 6th line checks TCP options, if any. If
+ * any of these things are different between the previous & current
+ * datagram, we send the current datagram `uncompressed'.
+ */
+ oth = (struct tcphdr *) & ((int *) &cs->cs_ip)[hlen];
+ deltaS = hlen;
+ hlen += th->th_off;
+ hlen <<= 2;
+ if (hlen > m->m_len)
+ return (TYPE_IP);
+
+ if (((u_short *) ip)[0] != ((u_short *) & cs->cs_ip)[0] ||
+ ((u_short *) ip)[3] != ((u_short *) & cs->cs_ip)[3] ||
+ ((u_short *) ip)[4] != ((u_short *) & cs->cs_ip)[4] ||
+ THOFFSET(th) != THOFFSET(oth) ||
+ (deltaS > 5 &&
+ memcmp(ip + 1, &cs->cs_ip + 1, (deltaS - 5) << 2)) ||
+ (THOFFSET(th) > 5 &&
+ memcmp(th + 1, oth + 1, (THOFFSET(th) - 5) << 2))) {
+ goto uncompressed;
+ }
+
+ /*
+ * Figure out which of the changing fields changed. The receiver expects
+ * changes in the order: urgent, window, ack, seq (the order minimizes the
+ * number of temporaries needed in this section of code).
+ */
+ if (th->th_flags & TH_URG) {
+ deltaS = ntohs(th->th_urp);
+ ENCODEZ(deltaS);
+ changes |= NEW_U;
+ } else if (th->th_urp != oth->th_urp) {
+
+ /*
+ * argh! URG not set but urp changed -- a sensible implementation should
+ * never do this but RFC793 doesn't prohibit the change so we have to
+ * deal with it.
+ */
+ goto uncompressed;
+ }
+ deltaS = (u_short) (ntohs(th->th_win) - ntohs(oth->th_win));
+ if (deltaS) {
+ ENCODE(deltaS);
+ changes |= NEW_W;
+ }
+ deltaA = ntohl(th->th_ack) - ntohl(oth->th_ack);
+ if (deltaA) {
+ if (deltaA > 0xffff) {
+ goto uncompressed;
+ }
+ ENCODE(deltaA);
+ changes |= NEW_A;
+ }
+ deltaS = ntohl(th->th_seq) - ntohl(oth->th_seq);
+ if (deltaS) {
+ if (deltaS > 0xffff) {
+ goto uncompressed;
+ }
+ ENCODE(deltaS);
+ changes |= NEW_S;
+ }
+ switch (changes) {
+
+ case 0:
+
+ /*
+ * Nothing changed. If this packet contains data and the last one didn't,
+ * this is probably a data packet following an ack (normal on an
+ * interactive connection) and we send it compressed. Otherwise it's
+ * probably a retransmit, retransmitted ack or window probe. Send it
+ * uncompressed in case the other side missed the compressed version.
+ */
+ if (ip->ip_len != cs->cs_ip.ip_len &&
+ ntohs(cs->cs_ip.ip_len) == hlen)
+ break;
+
+ /* FALLTHROUGH */
+
+ case SPECIAL_I:
+ case SPECIAL_D:
+
+ /*
+ * actual changes match one of our special case encodings -- send packet
+ * uncompressed.
+ */
+ goto uncompressed;
+
+ case NEW_S | NEW_A:
+ if (deltaS == deltaA &&
+ deltaS == ntohs(cs->cs_ip.ip_len) - hlen) {
+ /* special case for echoed terminal traffic */
+ changes = SPECIAL_I;
+ cp = new_seq;
+ }
+ break;
+
+ case NEW_S:
+ if (deltaS == ntohs(cs->cs_ip.ip_len) - hlen) {
+ /* special case for data xfer */
+ changes = SPECIAL_D;
+ cp = new_seq;
+ }
+ break;
+ }
+
+ deltaS = ntohs(ip->ip_id) - ntohs(cs->cs_ip.ip_id);
+ if (deltaS != 1) {
+ ENCODEZ(deltaS);
+ changes |= NEW_I;
+ }
+ if (th->th_flags & TH_PUSH)
+ changes |= TCP_PUSH_BIT;
+
+ /*
+ * Grab the cksum before we overwrite it below. Then update our state with
+ * this packet's header.
+ */
+ deltaA = ntohs(th->th_sum);
+ memcpy(&cs->cs_ip, ip, hlen);
+
+ /*
+ * We want to use the original packet as our compressed packet. (cp -
+ * new_seq) is the number of bytes we need for compressed sequence numbers.
+ * In addition we need one byte for the change mask, one for the connection
+ * id and two for the tcp checksum. So, (cp - new_seq) + 4 bytes of header
+ * are needed. hlen is how many bytes of the original packet to toss so
+ * subtract the two to get the new packet size.
+ */
+ deltaS = cp - new_seq;
+ cp = (u_char *) ip;
+
+ /*
+ * Since fastq traffic can jump ahead of the background traffic, we don't
+ * know what order packets will go on the line. In this case, we always
+ * send a "new" connection id so the receiver state stays synchronized.
+ */
+ if (comp->last_xmit == cs->cs_id && compress_cid) {
+ hlen -= deltaS + 3;
+ cp += hlen;
+ *cp++ = changes;
+ } else {
+ comp->last_xmit = cs->cs_id;
+ hlen -= deltaS + 4;
+ cp += hlen;
+ *cp++ = changes | NEW_C;
+ *cp++ = cs->cs_id;
+ }
+ m->m_len -= hlen;
+ m->m_offset += hlen;
+ *cp++ = deltaA >> 8;
+ *cp++ = deltaA;
+ memcpy(cp, new_seq, deltaS);
+ slstat->sls_compressed++;
+ return (TYPE_COMPRESSED_TCP);
+
+ /*
+ * Update connection state cs & send uncompressed packet ('uncompressed'
+ * means a regular ip/tcp packet but with the 'conversation id' we hope to
+ * use on future compressed packets in the protocol field).
+ */
+uncompressed:
+ memcpy(&cs->cs_ip, ip, hlen);
+ ip->ip_p = cs->cs_id;
+ comp->last_xmit = cs->cs_id;
+ return (TYPE_UNCOMPRESSED_TCP);
+}
+
+
+int
+sl_uncompress_tcp(u_char ** bufp, int len, u_int type, struct slcompress *comp,
+ struct slstat *slstat, int max_state)
+{
+ register u_char *cp;
+ register u_int hlen, changes;
+ register struct tcphdr *th;
+ register struct cstate *cs;
+ register struct ip *ip;
+ u_short *bp;
+
+ switch (type) {
+
+ case TYPE_UNCOMPRESSED_TCP:
+ ip = (struct ip *) * bufp;
+ if (ip->ip_p > max_state)
+ goto bad;
+ cs = &comp->rstate[comp->last_recv = ip->ip_p];
+ comp->flags &= ~SLF_TOSS;
+ ip->ip_p = IPPROTO_TCP;
+
+ /*
+ * Calculate the size of the TCP/IP header and make sure that we don't
+ * overflow the space we have available for it.
+ */
+ hlen = ip->ip_hl << 2;
+ if ((int)(hlen + sizeof(struct tcphdr)) > len)
+ goto bad;
+ th = (struct tcphdr *) & ((char *) ip)[hlen];
+ hlen += THOFFSET(th) << 2;
+ if (hlen > MAX_HDR)
+ goto bad;
+ memcpy(&cs->cs_ip, ip, hlen);
+ cs->cs_hlen = hlen;
+ slstat->sls_uncompressedin++;
+ return (len);
+
+ default:
+ goto bad;
+
+ case TYPE_COMPRESSED_TCP:
+ break;
+ }
+
+ /* We've got a compressed packet. */
+ slstat->sls_compressedin++;
+ cp = *bufp;
+ changes = *cp++;
+ log_Printf(LogDEBUG, "compressed: changes = %02x\n", changes);
+
+ if (changes & NEW_C) {
+ /*
+ * Make sure the state index is in range, then grab the state. If we have
+ * a good state index, clear the 'discard' flag.
+ */
+ if (*cp > max_state || comp->last_recv == 255)
+ goto bad;
+
+ comp->flags &= ~SLF_TOSS;
+ comp->last_recv = *cp++;
+ } else {
+ /*
+ * this packet has an implicit state index. If we've had a line error
+ * since the last time we got an explicit state index, we have to toss
+ * the packet.
+ */
+ if (comp->flags & SLF_TOSS) {
+ slstat->sls_tossed++;
+ return (0);
+ }
+ }
+ cs = &comp->rstate[comp->last_recv];
+ hlen = cs->cs_ip.ip_hl << 2;
+ th = (struct tcphdr *) & ((u_char *) & cs->cs_ip)[hlen];
+ th->th_sum = htons((*cp << 8) | cp[1]);
+ cp += 2;
+ if (changes & TCP_PUSH_BIT)
+ th->th_flags |= TH_PUSH;
+ else
+ th->th_flags &= ~TH_PUSH;
+
+ switch (changes & SPECIALS_MASK) {
+ case SPECIAL_I:
+ {
+ register u_int i = ntohs(cs->cs_ip.ip_len) - cs->cs_hlen;
+
+ th->th_ack = htonl(ntohl(th->th_ack) + i);
+ th->th_seq = htonl(ntohl(th->th_seq) + i);
+ }
+ break;
+
+ case SPECIAL_D:
+ th->th_seq = htonl(ntohl(th->th_seq) + ntohs(cs->cs_ip.ip_len)
+ - cs->cs_hlen);
+ break;
+
+ default:
+ if (changes & NEW_U) {
+ th->th_flags |= TH_URG;
+ DECODEU(th->th_urp)
+ } else
+ th->th_flags &= ~TH_URG;
+ if (changes & NEW_W)
+ DECODES(th->th_win)
+ if (changes & NEW_A)
+ DECODEL(th->th_ack)
+ if (changes & NEW_S) {
+ log_Printf(LogDEBUG, "NEW_S: %02x, %02x, %02x\n",
+ *cp, cp[1], cp[2]);
+ DECODEL(th->th_seq)
+ }
+ break;
+ }
+ if (changes & NEW_I) {
+ DECODES(cs->cs_ip.ip_id)
+ } else
+ cs->cs_ip.ip_id = htons(ntohs(cs->cs_ip.ip_id) + 1);
+
+ log_Printf(LogDEBUG, "Uncompress: id = %04x, seq = %08lx\n",
+ cs->cs_ip.ip_id, (u_long)ntohl(th->th_seq));
+
+ /*
+ * At this point, cp points to the first byte of data in the packet.
+ * Back up cp by the tcp/ip header length to make room for the
+ * reconstructed header (we assume the packet we were handed has enough
+ * space to prepend 128 bytes of header). Adjust the length to account
+ * for the new header & fill in the IP total length.
+ */
+ len -= (cp - *bufp);
+ if (len < 0)
+ /*
+ * we must have dropped some characters (crc should detect this but the
+ * old slip framing won't)
+ */
+ goto bad;
+
+ *bufp = cp - cs->cs_hlen;
+ len += cs->cs_hlen;
+ cs->cs_ip.ip_len = htons(len);
+
+ /* recompute the ip header checksum */
+ cs->cs_ip.ip_sum = 0;
+ bp = (u_short *)&cs->cs_ip;
+ for (changes = 0; hlen > 0; hlen -= 2)
+ changes += *bp++;
+ changes = (changes & 0xffff) + (changes >> 16);
+ changes = (changes & 0xffff) + (changes >> 16);
+ cs->cs_ip.ip_sum = ~changes;
+
+ /* And copy the result into our buffer */
+ memcpy(*bufp, &cs->cs_ip, cs->cs_hlen);
+
+ return (len);
+bad:
+ comp->flags |= SLF_TOSS;
+ slstat->sls_errorin++;
+ return (0);
+}
+
+int
+sl_Show(struct cmdargs const *arg)
+{
+ prompt_Printf(arg->prompt, "VJ compression statistics:\n");
+ prompt_Printf(arg->prompt, " Out: %d (compress) / %d (total)",
+ arg->bundle->ncp.ipcp.vj.slstat.sls_compressed,
+ arg->bundle->ncp.ipcp.vj.slstat.sls_packets);
+ prompt_Printf(arg->prompt, " %d (miss) / %d (search)\n",
+ arg->bundle->ncp.ipcp.vj.slstat.sls_misses,
+ arg->bundle->ncp.ipcp.vj.slstat.sls_searches);
+ prompt_Printf(arg->prompt, " In: %d (compress), %d (uncompress)",
+ arg->bundle->ncp.ipcp.vj.slstat.sls_compressedin,
+ arg->bundle->ncp.ipcp.vj.slstat.sls_uncompressedin);
+ prompt_Printf(arg->prompt, " %d (error), %d (tossed)\n",
+ arg->bundle->ncp.ipcp.vj.slstat.sls_errorin,
+ arg->bundle->ncp.ipcp.vj.slstat.sls_tossed);
+ return 0;
+}
diff --git a/usr.sbin/ppp/slcompress.h b/usr.sbin/ppp/slcompress.h
new file mode 100644
index 0000000..9c415cb
--- /dev/null
+++ b/usr.sbin/ppp/slcompress.h
@@ -0,0 +1,161 @@
+/*
+ * Definitions for tcp compression routines.
+ *
+ * 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.
+ * 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.
+ *
+ * Van Jacobson (van@helios.ee.lbl.gov), Dec 31, 1989:
+ * - Initial distribution.
+ *
+ * $FreeBSD$
+ */
+
+#define MIN_VJ_STATES 3
+#define MAX_VJ_STATES 255
+#define DEF_VJ_STATES 16 /* must be > 2 and < 256 */
+#define MAX_HDR 128
+
+/*
+ * Compressed packet format:
+ *
+ * The first octet contains the packet type (top 3 bits), TCP
+ * 'push' bit, and flags that indicate which of the 4 TCP sequence
+ * numbers have changed (bottom 5 bits). The next octet is a
+ * conversation number that associates a saved IP/TCP header with
+ * the compressed packet. The next two octets are the TCP checksum
+ * from the original datagram. The next 0 to 15 octets are
+ * sequence number changes, one change per bit set in the header
+ * (there may be no changes and there are two special cases where
+ * the receiver implicitly knows what changed -- see below).
+ *
+ * There are 5 numbers which can change (they are always inserted
+ * in the following order): TCP urgent pointer, window,
+ * acknowlegement, sequence number and IP ID. (The urgent pointer
+ * is different from the others in that its value is sent, not the
+ * change in value.) Since typical use of SLIP links is biased
+ * toward small packets (see comments on MTU/MSS below), changes
+ * use a variable length coding with one octet for numbers in the
+ * range 1 - 255 and 3 octets (0, MSB, LSB) for numbers in the
+ * range 256 - 65535 or 0. (If the change in sequence number or
+ * ack is more than 65535, an uncompressed packet is sent.)
+ */
+
+/*
+ * Packet types (must not conflict with IP protocol version)
+ *
+ * The top nibble of the first octet is the packet type. There are
+ * three possible types: IP (not proto TCP or tcp with one of the
+ * control flags set); uncompressed TCP (a normal IP/TCP packet but
+ * with the 8-bit protocol field replaced by an 8-bit connection id --
+ * this type of packet syncs the sender & receiver); and compressed
+ * TCP (described above).
+ *
+ * LSB of 4-bit field is TCP "PUSH" bit (a worthless anachronism) and
+ * is logically part of the 4-bit "changes" field that follows. Top
+ * three bits are actual packet type. For backward compatibility
+ * and in the interest of conserving bits, numbers are chosen so the
+ * IP protocol version number (4) which normally appears in this nibble
+ * means "IP packet".
+ */
+
+/* packet types */
+#define TYPE_IP 0x40
+#define TYPE_UNCOMPRESSED_TCP 0x70
+#define TYPE_COMPRESSED_TCP 0x80
+#define TYPE_ERROR 0x00
+
+/* Bits in first octet of compressed packet */
+#define NEW_C 0x40 /* flag bits for what changed in a packet */
+#define NEW_I 0x20
+#define NEW_S 0x08
+#define NEW_A 0x04
+#define NEW_W 0x02
+#define NEW_U 0x01
+
+/* reserved, special-case values of above */
+#define SPECIAL_I (NEW_S|NEW_W|NEW_U) /* echoed interactive traffic */
+#define SPECIAL_D (NEW_S|NEW_A|NEW_W|NEW_U) /* unidirectional data */
+#define SPECIALS_MASK (NEW_S|NEW_A|NEW_W|NEW_U)
+
+#define TCP_PUSH_BIT 0x10
+
+/*
+ * "state" data for each active tcp conversation on the wire. This is
+ * basically a copy of the entire IP/TCP header from the last packet
+ * we saw from the conversation together with a small identifier
+ * the transmit & receive ends of the line use to locate saved header.
+ */
+struct cstate {
+ struct cstate *cs_next; /* next most recently used cstate (xmit only) */
+ u_short cs_hlen; /* size of hdr (receive only) */
+ u_char cs_id; /* connection # associated with this state */
+ u_char cs_filler;
+ union {
+ char csu_hdr[MAX_HDR];
+ struct ip csu_ip; /* ip/tcp hdr from most recent packet */
+ } slcs_u;
+};
+
+#define cs_ip slcs_u.csu_ip
+#define cs_hdr slcs_u.csu_hdr
+
+/*
+ * all the state data for one serial line (we need one of these
+ * per line).
+ */
+struct slcompress {
+ struct cstate *last_cs; /* most recently used tstate */
+ u_char last_recv; /* last rcvd conn. id */
+ u_char last_xmit; /* last sent conn. id */
+ u_short flags;
+ struct cstate tstate[MAX_VJ_STATES]; /* xmit connection states */
+ struct cstate rstate[MAX_VJ_STATES]; /* receive connection states */
+};
+
+struct slstat {
+ int sls_packets; /* outbound packets */
+ int sls_compressed; /* outbound compressed packets */
+ int sls_searches; /* searches for connection state */
+ int sls_misses; /* times couldn't find conn. state */
+ int sls_uncompressedin; /* inbound uncompressed packets */
+ int sls_compressedin; /* inbound compressed packets */
+ int sls_errorin; /* inbound unknown type packets */
+ int sls_tossed; /* inbound packets tossed because of error */
+};
+
+/* flag values */
+#define SLF_TOSS 1 /* tossing rcvd frames because of input err */
+
+struct mbuf;
+struct cmdargs;
+
+extern void sl_compress_init(struct slcompress *, int);
+extern u_char sl_compress_tcp(struct mbuf *, struct ip *, struct slcompress *,
+ struct slstat *, int);
+extern int sl_uncompress_tcp(u_char **, int, u_int, struct slcompress *,
+ struct slstat *, int);
+extern int sl_Show(struct cmdargs const *);
diff --git a/usr.sbin/ppp/sync.c b/usr.sbin/ppp/sync.c
new file mode 100644
index 0000000..712e036
--- /dev/null
+++ b/usr.sbin/ppp/sync.c
@@ -0,0 +1,84 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.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/types.h>
+
+#include <stdio.h>
+#include <termios.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "mbuf.h"
+#include "log.h"
+#include "sync.h"
+#include "timer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "fsm.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "async.h"
+#include "descriptor.h"
+#include "physical.h"
+
+static struct mbuf *
+sync_LayerPush(struct bundle *bundle __unused, struct link *l __unused,
+ struct mbuf *bp, int pri __unused, u_short *proto __unused)
+{
+ log_DumpBp(LogSYNC, "Write", bp);
+ m_settype(bp, MB_SYNCOUT);
+ bp->priv = 0;
+ return bp;
+}
+
+static struct mbuf *
+sync_LayerPull(struct bundle *b __unused, struct link *l, struct mbuf *bp,
+ u_short *proto __unused)
+{
+ struct physical *p = link2physical(l);
+ int len;
+
+ if (!p)
+ log_Printf(LogERROR, "Can't Pull a sync packet from a logical link\n");
+ else {
+ log_DumpBp(LogSYNC, "Read", bp);
+
+ /* Either done here or by the HDLC layer */
+ len = m_length(bp);
+ p->hdlc.lqm.ifInOctets += len + 1; /* plus 1 flag octet! */
+ p->hdlc.lqm.lqr.InGoodOctets += len + 1; /* plus 1 flag octet! */
+ p->hdlc.lqm.ifInUniPackets++;
+ m_settype(bp, MB_SYNCIN);
+ }
+
+ return bp;
+}
+
+struct layer synclayer = { LAYER_SYNC, "sync", sync_LayerPush, sync_LayerPull };
diff --git a/usr.sbin/ppp/sync.h b/usr.sbin/ppp/sync.h
new file mode 100644
index 0000000..4f5bfee
--- /dev/null
+++ b/usr.sbin/ppp/sync.h
@@ -0,0 +1,29 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.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$
+ */
+
+extern struct layer synclayer;
diff --git a/usr.sbin/ppp/systems.c b/usr.sbin/ppp/systems.c
new file mode 100644
index 0000000..36cb4e9
--- /dev/null
+++ b/usr.sbin/ppp/systems.c
@@ -0,0 +1,483 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <ctype.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+
+#include "defs.h"
+#include "command.h"
+#include "log.h"
+#include "id.h"
+#include "systems.h"
+
+#define issep(ch) ((ch) == ' ' || (ch) == '\t')
+
+FILE *
+OpenSecret(const char *file)
+{
+ FILE *fp;
+ char line[100];
+
+ snprintf(line, sizeof line, "%s/%s", PPP_CONFDIR, file);
+ fp = ID0fopen(line, "r");
+ if (fp == NULL)
+ log_Printf(LogWARN, "OpenSecret: Can't open %s.\n", line);
+ return (fp);
+}
+
+void
+CloseSecret(FILE *fp)
+{
+ fclose(fp);
+}
+
+/* Move string from ``from'' to ``to'', interpreting ``~'' and $.... */
+const char *
+InterpretArg(const char *from, char *to)
+{
+ char *ptr, *startto, *endto;
+ struct passwd *pwd;
+ int instring;
+ size_t len;
+ const char *env;
+
+ instring = 0;
+ startto = to;
+ endto = to + LINE_LEN - 1;
+
+ while(issep(*from))
+ from++;
+
+ while (*from != '\0') {
+ switch (*from) {
+ case '"':
+ instring = !instring;
+ *to++ = *from++;
+ break;
+ case '\\':
+ switch (*++from) {
+ case '$':
+ case '~':
+ break; /* Swallow the escapes */
+
+ default:
+ *to++ = '\\'; /* Pass the escapes on, maybe skipping \# */
+ break;
+ }
+ *to++ = *from++;
+ break;
+ case '$':
+ if (from[1] == '$') {
+ *to = '\0'; /* For an empty var name below */
+ from += 2;
+ } else if (from[1] == '{') {
+ ptr = strchr(from+2, '}');
+ if (ptr) {
+ len = ptr - from - 2;
+ if (endto - to < (int)len )
+ len = endto - to;
+ if (len) {
+ strncpy(to, from+2, len);
+ to[len] = '\0';
+ from = ptr+1;
+ } else {
+ *to++ = *from++;
+ continue;
+ }
+ } else {
+ *to++ = *from++;
+ continue;
+ }
+ } else {
+ ptr = to;
+ for (from++; (isalnum(*from) || *from == '_') && ptr < endto; from++)
+ *ptr++ = *from;
+ *ptr = '\0';
+ }
+ if (*to == '\0')
+ *to++ = '$';
+ else if ((env = getenv(to)) != NULL) {
+ strncpy(to, env, endto - to);
+ *endto = '\0';
+ to += strlen(to);
+ }
+ break;
+
+ case '~':
+ ptr = strchr(++from, '/');
+ len = ptr ? (size_t)(ptr - from) : strlen(from);
+ if (len == 0)
+ pwd = getpwuid(ID0realuid());
+ else {
+ strncpy(to, from, len);
+ to[len] = '\0';
+ pwd = getpwnam(to);
+ }
+ if (pwd == NULL)
+ *to++ = '~';
+ else {
+ strncpy(to, pwd->pw_dir, endto - to);
+ *endto = '\0';
+ to += strlen(to);
+ from += len;
+ }
+ endpwent();
+ break;
+
+ default:
+ *to++ = *from++;
+ break;
+ }
+ }
+
+ while (to > startto) {
+ to--;
+ if (!issep(*to)) {
+ to++;
+ break;
+ }
+ }
+ *to = '\0';
+
+ return from;
+}
+
+#define CTRL_UNKNOWN (0)
+#define CTRL_INCLUDE (1)
+
+static int
+DecodeCtrlCommand(char *line, char *arg)
+{
+ const char *end;
+
+ if (!strncasecmp(line, "include", 7) && issep(line[7])) {
+ end = InterpretArg(line+8, arg);
+ if (*end && *end != '#')
+ log_Printf(LogWARN, "usage: !include filename\n");
+ else
+ return CTRL_INCLUDE;
+ }
+ return CTRL_UNKNOWN;
+}
+
+/*
+ * Initialised in system_IsValid(), set in ReadSystem(),
+ * used by system_IsValid()
+ */
+static int modeok;
+static int userok;
+static int modereq;
+
+int
+AllowUsers(struct cmdargs const *arg)
+{
+ /* arg->bundle may be NULL (see system_IsValid()) ! */
+ int f;
+ struct passwd *pwd;
+
+ if (userok == -1)
+ userok = 0;
+
+ pwd = getpwuid(ID0realuid());
+ if (pwd != NULL)
+ for (f = arg->argn; f < arg->argc; f++)
+ if (!strcmp("*", arg->argv[f]) || !strcmp(pwd->pw_name, arg->argv[f])) {
+ userok = 1;
+ break;
+ }
+ endpwent();
+
+ return 0;
+}
+
+int
+AllowModes(struct cmdargs const *arg)
+{
+ /* arg->bundle may be NULL (see system_IsValid()) ! */
+ int f, mode, allowed;
+
+ allowed = 0;
+ for (f = arg->argn; f < arg->argc; f++) {
+ mode = Nam2mode(arg->argv[f]);
+ if (mode == PHYS_NONE || mode == PHYS_ALL)
+ log_Printf(LogWARN, "allow modes: %s: Invalid mode\n", arg->argv[f]);
+ else
+ allowed |= mode;
+ }
+
+ modeok = modereq & allowed ? 1 : 0;
+ return 0;
+}
+
+static char *
+strip(char *line)
+{
+ int len;
+
+ len = strlen(line);
+ while (len && (line[len-1] == '\n' || line[len-1] == '\r' ||
+ issep(line[len-1])))
+ line[--len] = '\0';
+
+ while (issep(*line))
+ line++;
+
+ if (*line == '#')
+ *line = '\0';
+
+ return line;
+}
+
+static int
+xgets(char *buf, int buflen, FILE *fp)
+{
+ int len, n;
+
+ n = 0;
+ while (fgets(buf, buflen-1, fp)) {
+ n++;
+ buf[buflen-1] = '\0';
+ len = strlen(buf);
+ while (len && (buf[len-1] == '\n' || buf[len-1] == '\r'))
+ buf[--len] = '\0';
+ if (len && buf[len-1] == '\\') {
+ buf += len - 1;
+ buflen -= len - 1;
+ if (!buflen) /* No buffer space */
+ break;
+ } else
+ break;
+ }
+ return n;
+}
+
+/* Values for ``how'' in ReadSystem */
+#define SYSTEM_EXISTS 1
+#define SYSTEM_VALIDATE 2
+#define SYSTEM_EXEC 3
+
+static char *
+GetLabel(char *line, const char *filename, int linenum)
+{
+ char *argv[MAXARGS];
+ int argc, len;
+
+ argc = MakeArgs(line, argv, MAXARGS, PARSE_REDUCE);
+
+ if (argc == 2 && !strcmp(argv[1], ":"))
+ return argv[0];
+
+ if (argc != 1 || (len = strlen(argv[0])) < 2 || argv[0][len-1] != ':') {
+ log_Printf(LogWARN, "Bad label in %s (line %d) - missing colon\n",
+ filename, linenum);
+ return NULL;
+ }
+ argv[0][len-1] = '\0'; /* Lose the ':' */
+
+ return argv[0];
+}
+
+/* Returns -2 for ``file not found'' and -1 for ``label not found'' */
+
+static int
+ReadSystem(struct bundle *bundle, const char *name, const char *file,
+ struct prompt *prompt, struct datalink *cx, int how)
+{
+ FILE *fp;
+ char *cp;
+ int n, len;
+ char line[LINE_LEN];
+ char filename[PATH_MAX];
+ int linenum;
+ int argc;
+ char *argv[MAXARGS];
+ int allowcmd;
+ int indent;
+ char arg[LINE_LEN];
+ struct prompt *op;
+
+ if (*file == '/')
+ snprintf(filename, sizeof filename, "%s", file);
+ else
+ snprintf(filename, sizeof filename, "%s/%s", PPP_CONFDIR, file);
+ fp = ID0fopen(filename, "r");
+ if (fp == NULL) {
+ log_Printf(LogDEBUG, "ReadSystem: Can't open %s.\n", filename);
+ return -2;
+ }
+ log_Printf(LogDEBUG, "ReadSystem: Checking %s (%s).\n", name, filename);
+
+ linenum = 0;
+ while ((n = xgets(line, sizeof line, fp))) {
+ linenum += n;
+ if (issep(*line))
+ continue;
+
+ cp = strip(line);
+
+ switch (*cp) {
+ case '\0': /* empty/comment */
+ break;
+
+ case '!':
+ switch (DecodeCtrlCommand(cp+1, arg)) {
+ case CTRL_INCLUDE:
+ log_Printf(LogCOMMAND, "%s: Including \"%s\"\n", filename, arg);
+ n = ReadSystem(bundle, name, arg, prompt, cx, how);
+ log_Printf(LogCOMMAND, "%s: Done include of \"%s\"\n", filename, arg);
+ if (!n) {
+ fclose(fp);
+ return 0; /* got it */
+ }
+ break;
+ default:
+ log_Printf(LogWARN, "%s: %s: Invalid command\n", filename, cp);
+ break;
+ }
+ break;
+
+ default:
+ if ((cp = GetLabel(cp, filename, linenum)) == NULL)
+ continue;
+
+ if (strcmp(cp, name) == 0) {
+ /* We're in business */
+ if (how == SYSTEM_EXISTS) {
+ fclose(fp);
+ return 0;
+ }
+ while ((n = xgets(line, sizeof line, fp))) {
+ linenum += n;
+ indent = issep(*line);
+ cp = strip(line);
+
+ if (*cp == '\0') /* empty / comment */
+ continue;
+
+ if (!indent) { /* start of next section */
+ if (*cp != '!' && how == SYSTEM_EXEC)
+ cp = GetLabel(cp, filename, linenum);
+ break;
+ }
+
+ len = strlen(cp);
+ if ((argc = command_Expand_Interpret(cp, len, argv, cp - line)) < 0)
+ log_Printf(LogWARN, "%s: %d: Syntax error\n", filename, linenum);
+ else {
+ allowcmd = argc > 0 && !strcasecmp(argv[0], "allow");
+ if ((how != SYSTEM_EXEC && allowcmd) ||
+ (how == SYSTEM_EXEC && !allowcmd)) {
+ /*
+ * Disable any context so that warnings are given to everyone,
+ * including syslog.
+ */
+ op = log_PromptContext;
+ log_PromptContext = NULL;
+ command_Run(bundle, argc, (char const *const *)argv, prompt,
+ name, cx);
+ log_PromptContext = op;
+ }
+ }
+ }
+
+ fclose(fp); /* everything read - get out */
+ return 0;
+ }
+ break;
+ }
+ }
+ fclose(fp);
+ return -1;
+}
+
+const char *
+system_IsValid(const char *name, struct prompt *prompt, int mode)
+{
+ /*
+ * Note: The ReadSystem() calls only result in calls to the Allow*
+ * functions. arg->bundle will be set to NULL for these commands !
+ */
+ int def, how, rs;
+ int defuserok;
+
+ def = !strcmp(name, "default");
+ how = ID0realuid() == 0 ? SYSTEM_EXISTS : SYSTEM_VALIDATE;
+ userok = -1;
+ modeok = 1;
+ modereq = mode;
+
+ rs = ReadSystem(NULL, "default", CONFFILE, prompt, NULL, how);
+
+ defuserok = userok;
+ userok = -1;
+
+ if (!def) {
+ if (rs == -1)
+ rs = 0; /* we don't care that ``default'' doesn't exist */
+
+ if (rs == 0)
+ rs = ReadSystem(NULL, name, CONFFILE, prompt, NULL, how);
+
+ if (rs == -1)
+ return "Configuration label not found";
+
+ if (rs == -2)
+ return PPP_CONFDIR "/" CONFFILE " : File not found";
+ }
+
+ if (userok == -1)
+ userok = defuserok;
+
+ if (how == SYSTEM_EXISTS)
+ userok = modeok = 1;
+
+ if (!userok)
+ return "User access denied";
+
+ if (!modeok)
+ return "Mode denied for this label";
+
+ return NULL;
+}
+
+int
+system_Select(struct bundle *bundle, const char *name, const char *file,
+ struct prompt *prompt, struct datalink *cx)
+{
+ userok = modeok = 1;
+ modereq = PHYS_ALL;
+ return ReadSystem(bundle, name, file, prompt, cx, SYSTEM_EXEC);
+}
diff --git a/usr.sbin/ppp/systems.h b/usr.sbin/ppp/systems.h
new file mode 100644
index 0000000..9091e73
--- /dev/null
+++ b/usr.sbin/ppp/systems.h
@@ -0,0 +1,43 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+struct prompt;
+struct datalink;
+struct bundle;
+struct cmdargs;
+
+extern int system_Select(struct bundle *bundle, const char *, const char *,
+ struct prompt *, struct datalink *);
+extern const char *system_IsValid(const char *, struct prompt *, int);
+extern FILE *OpenSecret(const char *);
+extern void CloseSecret(FILE *);
+extern int AllowUsers(struct cmdargs const *);
+extern int AllowModes(struct cmdargs const *);
+extern const char *InterpretArg(const char *, char *);
diff --git a/usr.sbin/ppp/tcp.c b/usr.sbin/ppp/tcp.c
new file mode 100644
index 0000000..4ace882
--- /dev/null
+++ b/usr.sbin/ppp/tcp.c
@@ -0,0 +1,212 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.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/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "fsm.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "async.h"
+#include "descriptor.h"
+#include "physical.h"
+#include "tcp.h"
+
+static int
+tcp_OpenConnection(const char *name, char *host, char *port)
+{
+ struct sockaddr_in dest;
+ int sock;
+ struct servent *sp;
+
+ dest.sin_family = AF_INET;
+ dest.sin_addr = GetIpAddr(host);
+ if (dest.sin_addr.s_addr == INADDR_NONE) {
+ log_Printf(LogWARN, "%s: %s: unknown host\n", name, host);
+ return -2;
+ }
+ dest.sin_port = htons(atoi(port));
+ if (dest.sin_port == 0) {
+ sp = getservbyname(port, "tcp");
+ if (sp)
+ dest.sin_port = sp->s_port;
+ else {
+ log_Printf(LogWARN, "%s: %s: unknown service\n", name, port);
+ return -2;
+ }
+ }
+ log_Printf(LogPHASE, "%s: Connecting to %s:%s/tcp\n", name, host, port);
+
+ sock = socket(PF_INET, SOCK_STREAM, 0);
+ if (sock < 0)
+ return -2;
+
+ if (connect(sock, (struct sockaddr *)&dest, sizeof dest) < 0) {
+ log_Printf(LogWARN, "%s: connect: %s\n", name, strerror(errno));
+ close(sock);
+ return -2;
+ }
+
+ return sock;
+}
+
+static struct device tcpdevice = {
+ TCP_DEVICE,
+ "tcp",
+ 0,
+ { CD_NOTREQUIRED, 0 },
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+struct device *
+tcp_iov2device(int type, struct physical *p, struct iovec *iov,
+ int *niov, int maxiov __unused, int *auxfd __unused,
+ int *nauxfd __unused)
+{
+ if (type == TCP_DEVICE) {
+ free(iov[(*niov)++].iov_base);
+ physical_SetupStack(p, tcpdevice.name, PHYSICAL_FORCE_ASYNC);
+ return &tcpdevice;
+ }
+
+ return NULL;
+}
+
+struct device *
+tcp_Create(struct physical *p)
+{
+ char *cp, *host, *port, *svc;
+
+ if (p->fd < 0) {
+ if ((cp = strchr(p->name.full, ':')) != NULL && !strchr(cp + 1, ':')) {
+ *cp = '\0';
+ host = p->name.full;
+ port = cp + 1;
+ svc = strchr(port, '/');
+ if (svc && strcasecmp(svc, "/tcp")) {
+ *cp = ':';
+ return 0;
+ }
+ if (svc) {
+ p->fd--; /* We own the device but maybe can't use it - change fd */
+ *svc = '\0';
+ }
+ if (*host && *port) {
+ p->fd = tcp_OpenConnection(p->link.name, host, port);
+ *cp = ':';
+ if (svc)
+ *svc = '/';
+ if (p->fd >= 0)
+ log_Printf(LogDEBUG, "%s: Opened tcp socket %s\n", p->link.name,
+ p->name.full);
+ } else {
+ if (svc)
+ *svc = '/';
+ *cp = ':';
+ }
+ }
+ }
+
+ if (p->fd >= 0) {
+ /* See if we're a tcp socket */
+ struct stat st;
+
+ if (fstat(p->fd, &st) != -1 && (st.st_mode & S_IFSOCK)) {
+ int type, sz;
+
+ sz = sizeof type;
+ if (getsockopt(p->fd, SOL_SOCKET, SO_TYPE, &type, &sz) == -1) {
+ log_Printf(LogPHASE, "%s: Link is a closed socket !\n", p->link.name);
+ close(p->fd);
+ p->fd = -1;
+ return NULL;
+ }
+
+ if (sz == sizeof type && type == SOCK_STREAM) {
+ struct sockaddr_in sock;
+ struct sockaddr *sockp = (struct sockaddr *)&sock;
+
+ if (*p->name.full == '\0') {
+ sz = sizeof sock;
+ if (getpeername(p->fd, sockp, &sz) != 0 ||
+ sz != sizeof(struct sockaddr_in) || sock.sin_family != AF_INET) {
+ log_Printf(LogDEBUG, "%s: Link is SOCK_STREAM, but not inet\n",
+ p->link.name);
+ return NULL;
+ }
+
+ log_Printf(LogPHASE, "%s: Link is a tcp socket\n", p->link.name);
+
+ snprintf(p->name.full, sizeof p->name.full, "%s:%d/tcp",
+ inet_ntoa(sock.sin_addr), ntohs(sock.sin_port));
+ p->name.base = p->name.full;
+ }
+ physical_SetupStack(p, tcpdevice.name, PHYSICAL_FORCE_ASYNC);
+ if (p->cfg.cd.necessity != CD_DEFAULT)
+ log_Printf(LogWARN, "Carrier settings ignored\n");
+ return &tcpdevice;
+ }
+ }
+ }
+
+ return NULL;
+}
diff --git a/usr.sbin/ppp/tcp.h b/usr.sbin/ppp/tcp.h
new file mode 100644
index 0000000..f6a8235
--- /dev/null
+++ b/usr.sbin/ppp/tcp.h
@@ -0,0 +1,34 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.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$
+ */
+
+struct physical;
+
+extern struct device *tcp_Create(struct physical *);
+extern struct device *tcp_iov2device(int, struct physical *,
+ struct iovec *, int *, int, int *, int *);
+#define tcp_DeviceSize physical_DeviceSize
diff --git a/usr.sbin/ppp/tcpmss.c b/usr.sbin/ppp/tcpmss.c
new file mode 100644
index 0000000..38d2920
--- /dev/null
+++ b/usr.sbin/ppp/tcpmss.c
@@ -0,0 +1,185 @@
+/*-
+ * Copyright (c) 2000 Ruslan Ermilov and Brian Somers <brian@Awfulhak.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/param.h>
+
+#include <sys/socket.h>
+#include <net/route.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+#include <sys/un.h>
+
+#include <termios.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "mbuf.h"
+#include "throughput.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "iplist.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "mp.h"
+#include "iface.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+
+
+/*-
+ * We are in a liberal position about MSS
+ * (RFC 879, section 7).
+ */
+#define MAXMSS(mtu) ((mtu) - sizeof(struct ip) - sizeof(struct tcphdr) - 12)
+
+
+/*-
+ * The following macro is used to update an
+ * internet checksum. "acc" is a 32-bit
+ * accumulation of all the changes to the
+ * checksum (adding in old 16-bit words and
+ * subtracting out new words), and "cksum"
+ * is the checksum value to be updated.
+ */
+#define ADJUST_CHECKSUM(acc, cksum) { \
+ acc += cksum; \
+ if (acc < 0) { \
+ acc = -acc; \
+ acc = (acc >> 16) + (acc & 0xffff); \
+ acc += acc >> 16; \
+ cksum = (u_short) ~acc; \
+ } else { \
+ acc = (acc >> 16) + (acc & 0xffff); \
+ acc += acc >> 16; \
+ cksum = (u_short) acc; \
+ } \
+}
+
+static void
+MSSFixup(struct tcphdr *tc, size_t pktlen, u_int16_t maxmss)
+{
+ size_t hlen, olen, optlen;
+ u_char *opt;
+ u_int16_t *mss;
+ int accumulate;
+
+ hlen = tc->th_off << 2;
+
+ /* Invalid header length or header without options. */
+ if (hlen <= sizeof(struct tcphdr) || hlen > pktlen)
+ return;
+
+ /* MSS option only allowed within SYN packets. */
+ if (!(tc->th_flags & TH_SYN))
+ return;
+
+ for (olen = hlen - sizeof(struct tcphdr), opt = (u_char *)(tc + 1);
+ olen > 0; olen -= optlen, opt += optlen) {
+ if (*opt == TCPOPT_EOL)
+ break;
+ else if (*opt == TCPOPT_NOP)
+ optlen = 1;
+ else {
+ optlen = *(opt + 1);
+ if (optlen <= 0 || optlen > olen)
+ break;
+ if (*opt == TCPOPT_MAXSEG) {
+ if (optlen != TCPOLEN_MAXSEG)
+ continue;
+ mss = (u_int16_t *)(opt + 2);
+ if (ntohs(*mss) > maxmss) {
+ log_Printf(LogDEBUG, "MSS: %u -> %u\n",
+ ntohs(*mss), maxmss);
+ accumulate = *mss;
+ *mss = htons(maxmss);
+ accumulate -= *mss;
+ ADJUST_CHECKSUM(accumulate, tc->th_sum);
+ }
+ }
+ }
+ }
+}
+
+static struct mbuf *
+tcpmss_Check(struct bundle *bundle, struct mbuf *bp)
+{
+ struct ip *pip;
+ size_t hlen, plen;
+
+ if (!Enabled(bundle, OPT_TCPMSSFIXUP))
+ return bp;
+
+ bp = m_pullup(bp);
+ plen = m_length(bp);
+ pip = (struct ip *)MBUF_CTOP(bp);
+ hlen = pip->ip_hl << 2;
+
+ /*
+ * Check for MSS option only for TCP packets with zero fragment offsets
+ * and correct total and header lengths.
+ */
+ if (pip->ip_p == IPPROTO_TCP && (ntohs(pip->ip_off) & IP_OFFMASK) == 0 &&
+ ntohs(pip->ip_len) == plen && hlen <= plen &&
+ plen >= sizeof(struct tcphdr) + hlen)
+ MSSFixup((struct tcphdr *)(MBUF_CTOP(bp) + hlen), plen - hlen,
+ MAXMSS(bundle->iface->mtu));
+
+ return bp;
+}
+
+static struct mbuf *
+tcpmss_LayerPush(struct bundle *bundle, struct link *l __unused,
+ struct mbuf *bp, int pri __unused, u_short *proto __unused)
+{
+ return tcpmss_Check(bundle, bp);
+}
+
+static struct mbuf *
+tcpmss_LayerPull(struct bundle *bundle, struct link *l __unused,
+ struct mbuf *bp, u_short *proto __unused)
+{
+ return tcpmss_Check(bundle, bp);
+}
+
+struct layer tcpmsslayer =
+ { LAYER_PROTO, "tcpmss", tcpmss_LayerPush, tcpmss_LayerPull };
diff --git a/usr.sbin/ppp/tcpmss.h b/usr.sbin/ppp/tcpmss.h
new file mode 100644
index 0000000..ffd7b82
--- /dev/null
+++ b/usr.sbin/ppp/tcpmss.h
@@ -0,0 +1,29 @@
+/*-
+ * Copyright (c) 2000 Brian Somers <brian@Awfulhak.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$
+ */
+
+extern struct layer tcpmsslayer;
diff --git a/usr.sbin/ppp/throughput.c b/usr.sbin/ppp/throughput.c
new file mode 100644
index 0000000..a3e948a
--- /dev/null
+++ b/usr.sbin/ppp/throughput.c
@@ -0,0 +1,302 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.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/types.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <time.h>
+
+#include "log.h"
+#include "timer.h"
+#include "throughput.h"
+#include "descriptor.h"
+#include "prompt.h"
+
+
+void
+throughput_init(struct pppThroughput *t, int period)
+{
+ t->OctetsIn = t->OctetsOut = t->PacketsIn = t->PacketsOut = 0;
+ t->SamplePeriod = period;
+ t->in.SampleOctets = (long long *)
+ calloc(period, sizeof *t->in.SampleOctets);
+ t->in.OctetsPerSecond = 0;
+ t->out.SampleOctets = (long long *)
+ calloc(period, sizeof *t->out.SampleOctets);
+ t->out.OctetsPerSecond = 0;
+ t->BestOctetsPerSecond = 0;
+ t->nSample = 0;
+ time(&t->BestOctetsPerSecondTime);
+ memset(&t->Timer, '\0', sizeof t->Timer);
+ t->Timer.name = "throughput";
+ t->uptime = 0;
+ t->downtime = 0;
+ t->rolling = 0;
+ t->callback.data = NULL;
+ t->callback.fn = NULL;
+ throughput_stop(t);
+}
+
+void
+throughput_destroy(struct pppThroughput *t)
+{
+ if (t && t->in.SampleOctets) {
+ throughput_stop(t);
+ free(t->in.SampleOctets);
+ free(t->out.SampleOctets);
+ t->in.SampleOctets = NULL;
+ t->out.SampleOctets = NULL;
+ }
+}
+
+int
+throughput_uptime(struct pppThroughput *t)
+{
+ time_t downat;
+
+ downat = t->downtime ? t->downtime : time(NULL);
+ if (t->uptime && downat < t->uptime) {
+ /* Euch ! The clock's gone back ! */
+ int i;
+
+ for (i = 0; i < t->SamplePeriod; i++)
+ t->in.SampleOctets[i] = t->out.SampleOctets[i] = 0;
+ t->nSample = 0;
+ t->uptime = downat;
+ }
+ return t->uptime ? downat - t->uptime : 0;
+}
+
+void
+throughput_disp(struct pppThroughput *t, struct prompt *prompt)
+{
+ int secs_up, divisor;
+
+ secs_up = throughput_uptime(t);
+ prompt_Printf(prompt, "Connect time: %d:%02d:%02d", secs_up / 3600,
+ (secs_up / 60) % 60, secs_up % 60);
+ if (t->downtime)
+ prompt_Printf(prompt, " - down at %s", ctime(&t->downtime));
+ else
+ prompt_Printf(prompt, "\n");
+
+ divisor = secs_up ? secs_up : 1;
+ prompt_Printf(prompt, "%llu octets in, %llu octets out\n",
+ t->OctetsIn, t->OctetsOut);
+ prompt_Printf(prompt, "%llu packets in, %llu packets out\n",
+ t->PacketsIn, t->PacketsOut);
+ if (t->rolling) {
+ prompt_Printf(prompt, " overall %6llu bytes/sec\n",
+ (t->OctetsIn + t->OctetsOut) / divisor);
+ prompt_Printf(prompt, " %s %6llu bytes/sec in, %6llu bytes/sec out "
+ "(over the last %d secs)\n",
+ t->downtime ? "average " : "currently",
+ t->in.OctetsPerSecond, t->out.OctetsPerSecond,
+ secs_up > t->SamplePeriod ? t->SamplePeriod : secs_up);
+ prompt_Printf(prompt, " peak %6llu bytes/sec on %s",
+ t->BestOctetsPerSecond, ctime(&t->BestOctetsPerSecondTime));
+ } else
+ prompt_Printf(prompt, "Overall %llu bytes/sec\n",
+ (t->OctetsIn + t->OctetsOut) / divisor);
+}
+
+
+void
+throughput_log(struct pppThroughput *t, int level, const char *title)
+{
+ if (t->uptime) {
+ int secs_up;
+
+ secs_up = throughput_uptime(t);
+ if (title == NULL)
+ title = "";
+ log_Printf(level, "%s%sConnect time: %d secs: %llu octets in, %llu octets"
+ " out\n", title, *title ? ": " : "", secs_up, t->OctetsIn,
+ t->OctetsOut);
+ log_Printf(level, "%s%s%llu packets in, %llu packets out\n",
+ title, *title ? ": " : "", t->PacketsIn, t->PacketsOut);
+ if (secs_up == 0)
+ secs_up = 1;
+ if (t->rolling)
+ log_Printf(level, " total %llu bytes/sec, peak %llu bytes/sec on %s",
+ (t->OctetsIn + t->OctetsOut) / secs_up, t->BestOctetsPerSecond,
+ ctime(&t->BestOctetsPerSecondTime));
+ else
+ log_Printf(level, " total %llu bytes/sec\n",
+ (t->OctetsIn + t->OctetsOut) / secs_up);
+ }
+}
+
+static void
+throughput_sampler(void *v)
+{
+ struct pppThroughput *t = (struct pppThroughput *)v;
+ unsigned long long old;
+ int uptime, divisor;
+ unsigned long long octets;
+
+ timer_Stop(&t->Timer);
+
+ uptime = throughput_uptime(t);
+ divisor = uptime < t->SamplePeriod ? uptime + 1 : t->SamplePeriod;
+
+ old = t->in.SampleOctets[t->nSample];
+ t->in.SampleOctets[t->nSample] = t->OctetsIn;
+ t->in.OctetsPerSecond = (t->in.SampleOctets[t->nSample] - old) / divisor;
+
+ old = t->out.SampleOctets[t->nSample];
+ t->out.SampleOctets[t->nSample] = t->OctetsOut;
+ t->out.OctetsPerSecond = (t->out.SampleOctets[t->nSample] - old) / divisor;
+
+ octets = t->in.OctetsPerSecond + t->out.OctetsPerSecond;
+ if (t->BestOctetsPerSecond < octets) {
+ t->BestOctetsPerSecond = octets;
+ time(&t->BestOctetsPerSecondTime);
+ }
+
+ if (++t->nSample == t->SamplePeriod)
+ t->nSample = 0;
+
+ if (t->callback.fn != NULL && uptime >= t->SamplePeriod)
+ (*t->callback.fn)(t->callback.data);
+
+ timer_Start(&t->Timer);
+}
+
+void
+throughput_start(struct pppThroughput *t, const char *name, int rolling)
+{
+ int i;
+ timer_Stop(&t->Timer);
+
+ for (i = 0; i < t->SamplePeriod; i++)
+ t->in.SampleOctets[i] = t->out.SampleOctets[i] = 0;
+ t->nSample = 0;
+ t->OctetsIn = t->OctetsOut = t->PacketsIn = t->PacketsOut = 0;
+ t->in.OctetsPerSecond = t->out.OctetsPerSecond = t->BestOctetsPerSecond = 0;
+ time(&t->BestOctetsPerSecondTime);
+ t->downtime = 0;
+ time(&t->uptime);
+ throughput_restart(t, name, rolling);
+}
+
+void
+throughput_restart(struct pppThroughput *t, const char *name, int rolling)
+{
+ timer_Stop(&t->Timer);
+ t->rolling = rolling ? 1 : 0;
+ if (t->rolling) {
+ t->Timer.load = SECTICKS;
+ t->Timer.func = throughput_sampler;
+ t->Timer.name = name;
+ t->Timer.arg = t;
+ timer_Start(&t->Timer);
+ } else {
+ t->Timer.load = 0;
+ t->Timer.func = NULL;
+ t->Timer.name = NULL;
+ t->Timer.arg = NULL;
+ }
+}
+
+void
+throughput_stop(struct pppThroughput *t)
+{
+ if (t->Timer.state != TIMER_STOPPED)
+ time(&t->downtime);
+ timer_Stop(&t->Timer);
+}
+
+void
+throughput_addin(struct pppThroughput *t, long long n)
+{
+ t->OctetsIn += n;
+ t->PacketsIn++;
+}
+
+void
+throughput_addout(struct pppThroughput *t, long long n)
+{
+ t->OctetsOut += n;
+ t->PacketsOut++;
+}
+
+void
+throughput_clear(struct pppThroughput *t, int clear_type, struct prompt *prompt)
+{
+ if (clear_type & (THROUGHPUT_OVERALL|THROUGHPUT_CURRENT)) {
+ int i;
+
+ for (i = 0; i < t->SamplePeriod; i++)
+ t->in.SampleOctets[i] = t->out.SampleOctets[i] = 0;
+ t->nSample = 0;
+ }
+
+ if (clear_type & THROUGHPUT_OVERALL) {
+ int divisor;
+
+ if ((divisor = throughput_uptime(t)) == 0)
+ divisor = 1;
+ prompt_Printf(prompt, "overall cleared (was %6llu bytes/sec)\n",
+ (t->OctetsIn + t->OctetsOut) / divisor);
+ t->OctetsIn = t->OctetsOut = t->PacketsIn = t->PacketsOut = 0;
+ t->downtime = 0;
+ time(&t->uptime);
+ }
+
+ if (clear_type & THROUGHPUT_CURRENT) {
+ prompt_Printf(prompt, "current cleared (was %6llu bytes/sec in,"
+ " %6llu bytes/sec out)\n",
+ t->in.OctetsPerSecond, t->out.OctetsPerSecond);
+ t->in.OctetsPerSecond = t->out.OctetsPerSecond = 0;
+ }
+
+ if (clear_type & THROUGHPUT_PEAK) {
+ char *time_buf, *last;
+
+ time_buf = ctime(&t->BestOctetsPerSecondTime);
+ last = time_buf + strlen(time_buf);
+ if (last > time_buf && *--last == '\n')
+ *last = '\0';
+ prompt_Printf(prompt, "peak cleared (was %6llu bytes/sec on %s)\n",
+ t->BestOctetsPerSecond, time_buf);
+ t->BestOctetsPerSecond = 0;
+ time(&t->BestOctetsPerSecondTime);
+ }
+}
+
+void
+throughput_callback(struct pppThroughput *t, void (*fn)(void *), void *data)
+{
+ t->callback.fn = fn;
+ t->callback.data = data;
+}
diff --git a/usr.sbin/ppp/throughput.h b/usr.sbin/ppp/throughput.h
new file mode 100644
index 0000000..21e5450
--- /dev/null
+++ b/usr.sbin/ppp/throughput.h
@@ -0,0 +1,70 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.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$
+ */
+
+#define SAMPLE_PERIOD 5 /* Default sample period */
+
+#define THROUGHPUT_OVERALL 0x0001
+#define THROUGHPUT_CURRENT 0x0002
+#define THROUGHPUT_PEAK 0x0004
+#define THROUGHPUT_ALL 0x0007
+
+struct pppThroughput {
+ time_t uptime, downtime;
+ unsigned long long OctetsIn;
+ unsigned long long OctetsOut;
+ unsigned long long PacketsIn;
+ unsigned long long PacketsOut;
+ int SamplePeriod;
+ struct {
+ unsigned long long *SampleOctets;
+ unsigned long long OctetsPerSecond;
+ } in, out;
+ unsigned long long BestOctetsPerSecond;
+ time_t BestOctetsPerSecondTime;
+ int nSample;
+ unsigned rolling : 1;
+ struct pppTimer Timer;
+ struct {
+ void *data;
+ void (*fn)(void *v);
+ } callback;
+};
+
+extern void throughput_init(struct pppThroughput *, int);
+extern void throughput_destroy(struct pppThroughput *);
+extern void throughput_disp(struct pppThroughput *, struct prompt *);
+extern void throughput_log(struct pppThroughput *, int, const char *);
+extern void throughput_start(struct pppThroughput *, const char *, int);
+extern void throughput_restart(struct pppThroughput *, const char *, int);
+extern void throughput_stop(struct pppThroughput *);
+extern void throughput_addin(struct pppThroughput *, long long);
+extern void throughput_addout(struct pppThroughput *, long long);
+extern void throughput_clear(struct pppThroughput *, int, struct prompt *);
+extern void throughput_callback(struct pppThroughput *, void (*)(void *),
+ void *);
+extern int throughput_uptime(struct pppThroughput *);
diff --git a/usr.sbin/ppp/timer.c b/usr.sbin/ppp/timer.c
new file mode 100644
index 0000000..c1b589b7
--- /dev/null
+++ b/usr.sbin/ppp/timer.c
@@ -0,0 +1,302 @@
+/*-
+ * Copyright (c) 1996 - 2001, 2009 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <errno.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/time.h>
+#include <termios.h>
+
+#include "log.h"
+#include "sig.h"
+#include "timer.h"
+#include "descriptor.h"
+#include "prompt.h"
+
+
+#define RESTVAL(t) \
+ ((t).it_value.tv_sec * SECTICKS + (t).it_value.tv_usec / TICKUNIT + \
+ ((((t).it_value.tv_usec % TICKUNIT) >= (TICKUNIT >> 1)) ? 1 : 0))
+
+static struct pppTimer *TimerList = NULL, *ExpiredList = NULL;
+
+static void StopTimerNoBlock(struct pppTimer *);
+
+static const char *
+tState2Nam(u_int state)
+{
+ static const char * const StateNames[] = { "stopped", "running", "expired" };
+
+ if (state >= sizeof StateNames / sizeof StateNames[0])
+ return "unknown";
+ return StateNames[state];
+}
+
+void
+timer_Stop(struct pppTimer *tp)
+{
+ sigset_t mask, omask;
+
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGALRM);
+ sigprocmask(SIG_BLOCK, &mask, &omask);
+ StopTimerNoBlock(tp);
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+}
+
+void
+timer_Start(struct pppTimer *tp)
+{
+ struct itimerval itimer;
+ struct pppTimer *t, *pt;
+ u_long ticks = 0;
+ sigset_t mask, omask;
+
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGALRM);
+ sigprocmask(SIG_BLOCK, &mask, &omask);
+
+ if (tp->state != TIMER_STOPPED)
+ StopTimerNoBlock(tp);
+
+ if (tp->load == 0) {
+ log_Printf(LogTIMER, "%s timer[%p] has 0 load!\n", tp->name, tp);
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+ return;
+ }
+
+ /*
+ * We just need to insert tp in the correct relative place. We don't
+ * need to adjust TimerList->rest (yet).
+ */
+ if (TimerList && getitimer(ITIMER_REAL, &itimer) == 0)
+ ticks = RESTVAL(itimer) - TimerList->rest;
+
+ pt = NULL;
+ for (t = TimerList; t; t = t->next) {
+ if (ticks + t->rest >= tp->load)
+ break;
+ ticks += t->rest;
+ pt = t;
+ }
+
+ tp->state = TIMER_RUNNING;
+ tp->rest = tp->load - ticks;
+
+ if (t)
+ log_Printf(LogTIMER, "timer_Start: Inserting %s timer[%p] before %s "
+ "timer[%p], delta = %ld\n", tp->name, tp, t->name, t, tp->rest);
+ else
+ log_Printf(LogTIMER, "timer_Start: Inserting %s timer[%p]\n", tp->name, tp);
+
+ /* Insert given *tp just before *t */
+ tp->next = t;
+ if (pt) {
+ pt->next = tp;
+ } else {
+ TimerList = tp;
+ timer_InitService(t != NULL); /* [re]Start the Timer Service */
+ }
+ if (t)
+ t->rest -= tp->rest;
+
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+}
+
+static void
+StopTimerNoBlock(struct pppTimer *tp)
+{
+ struct itimerval itimer;
+ struct pppTimer *t, *pt;
+
+ /*
+ * A RUNNING timer must be removed from TimerList (->next list).
+ * A STOPPED timer isn't in any list, but may have a bogus [e]next field.
+ * An EXPIRED timer is in the ->enext list.
+ */
+
+ if (tp->state == TIMER_STOPPED)
+ return;
+
+ pt = NULL;
+ for (t = TimerList; t != tp && t != NULL; t = t->next)
+ pt = t;
+
+ if (t) {
+ if (pt)
+ pt->next = t->next;
+ else {
+ TimerList = t->next;
+ if (TimerList == NULL) /* Last one ? */
+ timer_TermService(); /* Terminate Timer Service */
+ }
+ if (t->next) {
+ if (!pt && getitimer(ITIMER_REAL, &itimer) == 0)
+ t->next->rest += RESTVAL(itimer); /* t (tp) was the first in the list */
+ else
+ t->next->rest += t->rest;
+ if (!pt && t->next->rest > 0) /* t->next is now the first in the list */
+ timer_InitService(1);
+ }
+ } else {
+ /* Search for any pending expired timers */
+ pt = NULL;
+ for (t = ExpiredList; t != tp && t != NULL; t = t->enext)
+ pt = t;
+
+ if (t) {
+ if (pt)
+ pt->enext = t->enext;
+ else
+ ExpiredList = t->enext;
+ } else if (tp->state == TIMER_RUNNING)
+ log_Printf(LogERROR, "Oops, %s timer not found!!\n", tp->name);
+ }
+
+ tp->next = tp->enext = NULL;
+ tp->state = TIMER_STOPPED;
+}
+
+static void
+TimerService(void)
+{
+ struct pppTimer *tp, *exp, *next;
+
+ if (log_IsKept(LogTIMER)) {
+ static time_t t; /* Only show timers globally every second */
+ time_t n = time(NULL);
+
+ if (n > t)
+ timer_Show(LogTIMER, NULL);
+ t = n;
+ }
+
+ tp = TimerList;
+ if (tp) {
+ tp->rest = 0;
+
+ /* Multiple timers might expire at once. Create a list of expired timers */
+ exp = NULL;
+ do {
+ tp->state = TIMER_EXPIRED;
+ next = tp->next;
+ tp->enext = exp;
+ exp = tp;
+ tp = next;
+ } while (tp && tp->rest == 0);
+
+ TimerList = tp;
+ if (TimerList != NULL) /* Any timers remaining ? */
+ timer_InitService(1); /* Restart the Timer Service */
+ else
+ timer_TermService(); /* Stop the Timer Service */
+
+ /* Process all expired timers */
+ while (exp) {
+ ExpiredList = exp->enext;
+ exp->enext = NULL;
+ if (exp->func)
+ (*exp->func)(exp->arg);
+ exp = ExpiredList;
+ }
+ }
+}
+
+void
+timer_Show(int LogLevel, struct prompt *prompt)
+{
+ struct itimerval itimer;
+ struct pppTimer *pt;
+ long rest;
+
+ /*
+ * Adjust the base time so that the deltas reflect what's really
+ * happening. Changing TimerList->rest might cause it to become zero
+ * (if getitimer() returns a value close to zero), and the
+ * timer_InitService() call will call setitimer() with zero it_value,
+ * stopping the itimer... so be careful!
+ */
+ if (TimerList && getitimer(ITIMER_REAL, &itimer) == 0)
+ rest = RESTVAL(itimer) - TimerList->rest;
+ else
+ rest = 0;
+
+#define SECS(val) ((val) / SECTICKS)
+#define HSECS(val) (((val) % SECTICKS) * 100 / SECTICKS)
+#define DISP \
+ "%s timer[%p]: freq = %ld.%02lds, next = %lu.%02lus, state = %s\n", \
+ pt->name, pt, SECS(pt->load), HSECS(pt->load), SECS(rest), \
+ HSECS(rest), tState2Nam(pt->state)
+
+ if (!prompt)
+ log_Printf(LogLevel, "---- Begin of Timer Service List---\n");
+
+ for (pt = TimerList; pt; pt = pt->next) {
+ rest += pt->rest;
+ if (prompt)
+ prompt_Printf(prompt, DISP);
+ else
+ log_Printf(LogLevel, DISP);
+ }
+
+ if (!prompt)
+ log_Printf(LogLevel, "---- End of Timer Service List ---\n");
+}
+
+void
+timer_InitService(int restart)
+{
+ struct itimerval itimer;
+
+ if (TimerList) {
+ if (!restart)
+ sig_signal(SIGALRM, (void (*)(int))TimerService);
+ itimer.it_interval.tv_sec = 0;
+ itimer.it_interval.tv_usec = 0;
+ itimer.it_value.tv_sec = TimerList->rest / SECTICKS;
+ itimer.it_value.tv_usec = (TimerList->rest % SECTICKS) * TICKUNIT;
+ if (setitimer(ITIMER_REAL, &itimer, NULL) == -1)
+ log_Printf(LogERROR, "Unable to set itimer (%s)\n", strerror(errno));
+ }
+}
+
+void
+timer_TermService(void)
+{
+ struct itimerval itimer;
+
+ itimer.it_interval.tv_usec = itimer.it_interval.tv_sec = 0;
+ itimer.it_value.tv_usec = itimer.it_value.tv_sec = 0;
+ if (setitimer(ITIMER_REAL, &itimer, NULL) == -1)
+ log_Printf(LogERROR, "Unable to set itimer (%s)\n", strerror(errno));
+ sig_signal(SIGALRM, SIG_IGN);
+}
diff --git a/usr.sbin/ppp/timer.h b/usr.sbin/ppp/timer.h
new file mode 100644
index 0000000..02fcdd1
--- /dev/null
+++ b/usr.sbin/ppp/timer.h
@@ -0,0 +1,55 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+#define TICKUNIT 100000 /* usec's per Unit */
+#define SECTICKS (1000000/TICKUNIT) /* Units per second */
+
+struct pppTimer {
+ int state;
+ const char *name;
+ u_long rest; /* Ticks to expire */
+ u_long load; /* Initial load value */
+ void (*func)(void *); /* Function called when timer is expired */
+ void *arg; /* Argument passed to timeout function */
+ struct pppTimer *next; /* Link to next timer */
+ struct pppTimer *enext; /* Link to next expired timer */
+};
+
+#define TIMER_STOPPED 0
+#define TIMER_RUNNING 1
+#define TIMER_EXPIRED 2
+
+struct prompt;
+
+extern void timer_Start(struct pppTimer *);
+extern void timer_Stop(struct pppTimer *);
+extern void timer_InitService(int);
+extern void timer_TermService(void);
+extern void timer_Show(int LogLevel, struct prompt *);
diff --git a/usr.sbin/ppp/tty.c b/usr.sbin/ppp/tty.c
new file mode 100644
index 0000000..ab984cc
--- /dev/null
+++ b/usr.sbin/ppp/tty.c
@@ -0,0 +1,770 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.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/param.h>
+#include <sys/un.h>
+#if defined(__OpenBSD__) || defined(__NetBSD__)
+#include <sys/ioctl.h>
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <sys/uio.h>
+#include <termios.h>
+#include <ttyent.h>
+#include <unistd.h>
+#ifndef NONETGRAPH
+#include <netgraph.h>
+#include <netgraph/ng_async.h>
+#include <netgraph/ng_message.h>
+#include <netgraph/ng_ppp.h>
+#include <netgraph/ng_tty.h>
+#endif
+
+#include "layer.h"
+#include "defs.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "fsm.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "async.h"
+#include "descriptor.h"
+#include "physical.h"
+#include "mp.h"
+#include "chat.h"
+#include "auth.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+#include "main.h"
+#include "id.h"
+#include "tty.h"
+
+#if defined(__mac68k__) || defined(__macppc__)
+#undef CRTS_IFLOW
+#undef CCTS_OFLOW
+#define CRTS_IFLOW CDTRCTS
+#define CCTS_OFLOW CDTRCTS
+#endif
+
+#define Online(dev) ((dev)->mbits & TIOCM_CD)
+
+struct ttydevice {
+ struct device dev; /* What struct physical knows about */
+ struct pppTimer Timer; /* CD checks */
+ int mbits; /* Current DCD status */
+ int carrier_seconds; /* seconds before CD is *required* */
+#ifndef NONETGRAPH
+ struct {
+ unsigned speed; /* Pre-line-discipline speed */
+ int fd; /* Pre-line-discipline fd */
+ int disc; /* Old line-discipline */
+ } real;
+ char hook[sizeof NG_ASYNC_HOOK_SYNC]; /* our ng_socket hook */
+ int cs; /* A netgraph control socket (maybe) */
+#endif
+ struct termios ios; /* To be able to reset from raw mode */
+};
+
+#define device2tty(d) ((d)->type == TTY_DEVICE ? (struct ttydevice *)d : NULL)
+
+unsigned
+tty_DeviceSize(void)
+{
+ return sizeof(struct ttydevice);
+}
+
+/*
+ * tty_Timeout() watches the DCD signal and mentions it if it's status
+ * changes.
+ */
+static void
+tty_Timeout(void *data)
+{
+ struct physical *p = data;
+ struct ttydevice *dev = device2tty(p->handler);
+ int ombits, change;
+
+ timer_Stop(&dev->Timer);
+ dev->Timer.load = SECTICKS; /* Once a second please */
+ timer_Start(&dev->Timer);
+ ombits = dev->mbits;
+
+ if (p->fd >= 0) {
+ if (ioctl(p->fd, TIOCMGET, &dev->mbits) < 0) {
+ /* we must be a pty ? */
+ if (p->cfg.cd.necessity != CD_DEFAULT)
+ log_Printf(LogWARN, "%s: Carrier ioctl not supported, "
+ "using ``set cd off''\n", p->link.name);
+ timer_Stop(&dev->Timer);
+ dev->mbits = TIOCM_CD;
+ return;
+ }
+ } else
+ dev->mbits = 0;
+
+ if (ombits == -1) {
+ /* First time looking for carrier */
+ if (Online(dev))
+ log_Printf(LogPHASE, "%s: %s: CD detected\n", p->link.name, p->name.full);
+ else if (++dev->carrier_seconds >= dev->dev.cd.delay) {
+ if (dev->dev.cd.necessity == CD_REQUIRED)
+ log_Printf(LogPHASE, "%s: %s: Required CD not detected\n",
+ p->link.name, p->name.full);
+ else {
+ log_Printf(LogPHASE, "%s: %s doesn't support CD\n",
+ p->link.name, p->name.full);
+ dev->mbits = TIOCM_CD; /* Dodgy null-modem cable ? */
+ }
+ timer_Stop(&dev->Timer);
+ /* tty_AwaitCarrier() will notice */
+ } else {
+ /* Keep waiting */
+ log_Printf(LogDEBUG, "%s: %s: Still no carrier (%d/%d)\n",
+ p->link.name, p->name.full, dev->carrier_seconds,
+ dev->dev.cd.delay);
+ dev->mbits = -1;
+ }
+ } else {
+ change = ombits ^ dev->mbits;
+ if (change & TIOCM_CD) {
+ if (dev->mbits & TIOCM_CD)
+ log_Printf(LogDEBUG, "%s: offline -> online\n", p->link.name);
+ else {
+ log_Printf(LogDEBUG, "%s: online -> offline\n", p->link.name);
+ log_Printf(LogPHASE, "%s: Carrier lost\n", p->link.name);
+ datalink_Down(p->dl, CLOSE_NORMAL);
+ timer_Stop(&dev->Timer);
+ }
+ } else
+ log_Printf(LogDEBUG, "%s: Still %sline\n", p->link.name,
+ Online(dev) ? "on" : "off");
+ }
+}
+
+static void
+tty_StartTimer(struct physical *p)
+{
+ struct ttydevice *dev = device2tty(p->handler);
+
+ timer_Stop(&dev->Timer);
+ dev->Timer.load = SECTICKS;
+ dev->Timer.func = tty_Timeout;
+ dev->Timer.name = "tty CD";
+ dev->Timer.arg = p;
+ log_Printf(LogDEBUG, "%s: Using tty_Timeout [%p]\n",
+ p->link.name, tty_Timeout);
+ timer_Start(&dev->Timer);
+}
+
+static int
+tty_AwaitCarrier(struct physical *p)
+{
+ struct ttydevice *dev = device2tty(p->handler);
+
+ if (dev->dev.cd.necessity == CD_NOTREQUIRED || physical_IsSync(p))
+ return CARRIER_OK;
+
+ if (dev->mbits == -1) {
+ if (dev->Timer.state == TIMER_STOPPED) {
+ dev->carrier_seconds = 0;
+ tty_StartTimer(p);
+ }
+ return CARRIER_PENDING; /* Not yet ! */
+ }
+
+ return Online(dev) ? CARRIER_OK : CARRIER_LOST;
+}
+
+#ifdef NONETGRAPH
+#define tty_SetAsyncParams NULL
+#define tty_Write NULL
+#define tty_Read NULL
+#else
+
+static int
+isngtty(struct ttydevice *dev)
+{
+ return dev->real.fd != -1;
+}
+
+static void
+tty_SetAsyncParams(struct physical *p, u_int32_t mymap, u_int32_t hismap)
+{
+ struct ttydevice *dev = device2tty(p->handler);
+ char asyncpath[NG_PATHSIZ];
+ struct ng_async_cfg cfg;
+
+ if (isngtty(dev)) {
+ /* Configure the async converter node */
+
+ snprintf(asyncpath, sizeof asyncpath, ".:%s", dev->hook);
+ memset(&cfg, 0, sizeof cfg);
+ cfg.enabled = 1;
+ cfg.accm = mymap | hismap;
+ cfg.amru = MAX_MTU;
+ cfg.smru = MAX_MRU;
+ log_Printf(LogDEBUG, "Configure async node at %s\n", asyncpath);
+ if (NgSendMsg(dev->cs, asyncpath, NGM_ASYNC_COOKIE,
+ NGM_ASYNC_CMD_SET_CONFIG, &cfg, sizeof cfg) < 0)
+ log_Printf(LogWARN, "%s: Can't configure async node at %s\n",
+ p->link.name, asyncpath);
+ } else
+ /* No netgraph node, just config the async layer */
+ async_SetLinkParams(&p->async, mymap, hismap);
+}
+
+static int
+LoadLineDiscipline(struct physical *p)
+{
+ struct ttydevice *dev = device2tty(p->handler);
+ u_char rbuf[sizeof(struct ng_mesg) + sizeof(struct nodeinfo)];
+ struct ng_mesg *reply;
+ struct nodeinfo *info;
+ char ttypath[NG_NODESIZ];
+ struct ngm_mkpeer ngm;
+ struct ngm_connect ngc;
+ int ldisc, cs, ds, hot;
+ unsigned speed;
+
+ /*
+ * Don't use the netgraph line discipline for now. Using it works, but
+ * carrier cannot be detected via TIOCMGET and the device doesn't become
+ * selectable with 0 bytes to read when carrier is lost :(
+ */
+ return 0;
+
+ reply = (struct ng_mesg *)rbuf;
+ info = (struct nodeinfo *)reply->data;
+
+ loadmodules(LOAD_VERBOSLY, "netgraph", "ng_tty", "ng_async", "ng_socket",
+ NULL);
+
+ /* Get the speed before loading the line discipline */
+ speed = physical_GetSpeed(p);
+
+ if (ioctl(p->fd, TIOCGETD, &dev->real.disc) < 0) {
+ log_Printf(LogDEBUG, "%s: Couldn't get tty line discipline\n",
+ p->link.name);
+ return 0;
+ }
+ ldisc = NETGRAPHDISC;
+ if (ID0ioctl(p->fd, TIOCSETD, &ldisc) < 0) {
+ log_Printf(LogDEBUG, "%s: Couldn't set NETGRAPHDISC line discipline\n",
+ p->link.name);
+ return 0;
+ }
+
+ /* Get the name of the tty node */
+ if (ioctl(p->fd, NGIOCGINFO, info) < 0) {
+ log_Printf(LogWARN, "%s: ioctl(NGIOCGINFO): %s\n", p->link.name,
+ strerror(errno));
+ ID0ioctl(p->fd, TIOCSETD, &dev->real.disc);
+ return 0;
+ }
+ snprintf(ttypath, sizeof ttypath, "%s:", info->name);
+
+ /* Create a socket node for our endpoint (and to send messages via) */
+ if (ID0NgMkSockNode(NULL, &cs, &ds) == -1) {
+ log_Printf(LogWARN, "%s: NgMkSockNode: %s\n", p->link.name,
+ strerror(errno));
+ ID0ioctl(p->fd, TIOCSETD, &dev->real.disc);
+ return 0;
+ }
+
+ /* Set the ``hot char'' on the TTY node */
+ hot = HDLC_SYN;
+ log_Printf(LogDEBUG, "%s: Set tty hotchar to 0x%02x\n", p->link.name, hot);
+ if (NgSendMsg(cs, ttypath, NGM_TTY_COOKIE,
+ NGM_TTY_SET_HOTCHAR, &hot, sizeof hot) < 0) {
+ log_Printf(LogWARN, "%s: Can't set hot char\n", p->link.name);
+ goto failed;
+ }
+
+ /* Attach an async converter node */
+ snprintf(ngm.type, sizeof ngm.type, "%s", NG_ASYNC_NODE_TYPE);
+ snprintf(ngm.ourhook, sizeof ngm.ourhook, "%s", NG_TTY_HOOK);
+ snprintf(ngm.peerhook, sizeof ngm.peerhook, "%s", NG_ASYNC_HOOK_ASYNC);
+ log_Printf(LogDEBUG, "%s: Send mkpeer async:%s to %s:%s\n", p->link.name,
+ ngm.peerhook, ttypath, ngm.ourhook);
+ if (NgSendMsg(cs, ttypath, NGM_GENERIC_COOKIE,
+ NGM_MKPEER, &ngm, sizeof ngm) < 0) {
+ log_Printf(LogWARN, "%s: Can't create %s node\n", p->link.name,
+ NG_ASYNC_NODE_TYPE);
+ goto failed;
+ }
+
+ /* Connect the async node to our socket */
+ snprintf(ngc.path, sizeof ngc.path, "%s%s", ttypath, NG_TTY_HOOK);
+ snprintf(ngc.peerhook, sizeof ngc.peerhook, "%s", NG_ASYNC_HOOK_SYNC);
+ memcpy(ngc.ourhook, ngc.peerhook, sizeof ngc.ourhook);
+ log_Printf(LogDEBUG, "%s: Send connect %s:%s to .:%s\n", p->link.name,
+ ngc.path, ngc.peerhook, ngc.ourhook);
+ if (NgSendMsg(cs, ".:", NGM_GENERIC_COOKIE, NGM_CONNECT,
+ &ngc, sizeof ngc) < 0) {
+ log_Printf(LogWARN, "%s: Can't connect .:%s -> %s.%s: %s\n",
+ p->link.name, ngc.ourhook, ngc.path, ngc.peerhook,
+ strerror(errno));
+ goto failed;
+ }
+
+ /* Get the async node id */
+ if (NgSendMsg(cs, ngc.path, NGM_GENERIC_COOKIE, NGM_NODEINFO, NULL, 0) < 0) {
+ log_Printf(LogWARN, "%s: Can't request async node info at %s: %s\n",
+ p->link.name, ngc.path, strerror(errno));
+ goto failed;
+ }
+ if (NgRecvMsg(cs, reply, sizeof rbuf, NULL) < 0) {
+ log_Printf(LogWARN, "%s: Can't obtain async node info at %s: %s\n",
+ p->link.name, ngc.path, strerror(errno));
+ goto failed;
+ }
+
+ /* All done, set up our device state */
+ snprintf(dev->hook, sizeof dev->hook, "%s", ngc.ourhook);
+ dev->cs = cs;
+ dev->real.fd = p->fd;
+ p->fd = ds;
+ dev->real.speed = speed;
+ physical_SetSync(p);
+
+ tty_SetAsyncParams(p, 0xffffffff, 0xffffffff);
+ physical_SetupStack(p, dev->dev.name, PHYSICAL_NOFORCE);
+ log_Printf(LogPHASE, "%s: Loaded netgraph tty line discipline\n",
+ p->link.name);
+
+ return 1;
+
+failed:
+ ID0ioctl(p->fd, TIOCSETD, &dev->real.disc);
+ close(ds);
+ close(cs);
+
+ return 0;
+}
+
+static void
+UnloadLineDiscipline(struct physical *p)
+{
+ struct ttydevice *dev = device2tty(p->handler);
+
+ if (isngtty(dev)) {
+ if (!physical_SetSpeed(p, dev->real.speed))
+ log_Printf(LogWARN, "Couldn't reset tty speed to %d\n", dev->real.speed);
+ dev->real.speed = 0;
+ close(p->fd);
+ p->fd = dev->real.fd;
+ dev->real.fd = -1;
+ close(dev->cs);
+ dev->cs = -1;
+ *dev->hook = '\0';
+ if (ID0ioctl(p->fd, TIOCSETD, &dev->real.disc) == 0) {
+ physical_SetupStack(p, dev->dev.name, PHYSICAL_NOFORCE);
+ log_Printf(LogPHASE, "%s: Unloaded netgraph tty line discipline\n",
+ p->link.name);
+ } else
+ log_Printf(LogWARN, "%s: Failed to unload netgraph tty line discipline\n",
+ p->link.name);
+ }
+}
+
+static ssize_t
+tty_Write(struct physical *p, const void *v, size_t n)
+{
+ struct ttydevice *dev = device2tty(p->handler);
+
+ if (isngtty(dev))
+ return NgSendData(p->fd, dev->hook, v, n) == -1 ? -1 : (ssize_t)n;
+ else
+ return write(p->fd, v, n);
+}
+
+static ssize_t
+tty_Read(struct physical *p, void *v, size_t n)
+{
+ struct ttydevice *dev = device2tty(p->handler);
+ char hook[sizeof NG_ASYNC_HOOK_SYNC];
+
+ if (isngtty(dev))
+ return NgRecvData(p->fd, v, n, hook);
+ else
+ return read(p->fd, v, n);
+}
+
+#endif /* NETGRAPH */
+
+static int
+tty_Raw(struct physical *p)
+{
+ struct ttydevice *dev = device2tty(p->handler);
+ struct termios ios;
+ int oldflag;
+
+ log_Printf(LogDEBUG, "%s: Entering tty_Raw\n", p->link.name);
+
+ if (p->type != PHYS_DIRECT && p->fd >= 0 && !Online(dev))
+ log_Printf(LogDEBUG, "%s: Raw: descriptor = %d, mbits = %x\n",
+ p->link.name, p->fd, dev->mbits);
+
+ if (!physical_IsSync(p)) {
+#ifndef NONETGRAPH
+ if (!LoadLineDiscipline(p))
+#endif
+ {
+ tcgetattr(p->fd, &ios);
+ cfmakeraw(&ios);
+ if (p->cfg.rts_cts)
+ ios.c_cflag |= CLOCAL | CCTS_OFLOW | CRTS_IFLOW;
+ else
+ ios.c_cflag |= CLOCAL;
+
+ if (p->type != PHYS_DEDICATED)
+ ios.c_cflag |= HUPCL;
+
+ if (tcsetattr(p->fd, TCSANOW, &ios) == -1)
+ log_Printf(LogWARN, "%s: tcsetattr: Failed configuring device\n",
+ p->link.name);
+ }
+ }
+
+ oldflag = fcntl(p->fd, F_GETFL, 0);
+ if (oldflag < 0)
+ return 0;
+ fcntl(p->fd, F_SETFL, oldflag | O_NONBLOCK);
+
+ return 1;
+}
+
+static void
+tty_Offline(struct physical *p)
+{
+ struct ttydevice *dev = device2tty(p->handler);
+
+ if (p->fd >= 0) {
+ timer_Stop(&dev->Timer);
+ dev->mbits &= ~TIOCM_DTR; /* XXX: Hmm, what's this supposed to do ? */
+ if (Online(dev)) {
+ struct termios tio;
+
+ tcgetattr(p->fd, &tio);
+ if (cfsetspeed(&tio, B0) == -1 || tcsetattr(p->fd, TCSANOW, &tio) == -1)
+ log_Printf(LogWARN, "%s: Unable to set physical to speed 0\n",
+ p->link.name);
+ }
+ }
+}
+
+static void
+tty_Cooked(struct physical *p)
+{
+ struct ttydevice *dev = device2tty(p->handler);
+ int oldflag;
+
+ tty_Offline(p); /* In case of emergency close()s */
+
+ tcflush(p->fd, TCIOFLUSH);
+
+ if (!physical_IsSync(p) && tcsetattr(p->fd, TCSAFLUSH, &dev->ios) == -1)
+ log_Printf(LogWARN, "%s: tcsetattr: Unable to restore device settings\n",
+ p->link.name);
+
+#ifndef NONETGRAPH
+ UnloadLineDiscipline(p);
+#endif
+
+ if ((oldflag = fcntl(p->fd, F_GETFL, 0)) != -1)
+ fcntl(p->fd, F_SETFL, oldflag & ~O_NONBLOCK);
+}
+
+static void
+tty_StopTimer(struct physical *p)
+{
+ struct ttydevice *dev = device2tty(p->handler);
+
+ timer_Stop(&dev->Timer);
+}
+
+static void
+tty_Free(struct physical *p)
+{
+ struct ttydevice *dev = device2tty(p->handler);
+
+ tty_Offline(p); /* In case of emergency close()s */
+ free(dev);
+}
+
+static unsigned
+tty_Speed(struct physical *p)
+{
+ struct termios ios;
+
+ if (tcgetattr(p->fd, &ios) == -1)
+ return 0;
+
+ return SpeedToUnsigned(cfgetispeed(&ios));
+}
+
+static const char *
+tty_OpenInfo(struct physical *p)
+{
+ struct ttydevice *dev = device2tty(p->handler);
+ static char buf[13];
+
+ if (Online(dev))
+ strcpy(buf, "with");
+ else
+ strcpy(buf, "no");
+ strcat(buf, " carrier");
+
+ return buf;
+}
+
+static int
+tty_Slot(struct physical *p)
+{
+ struct ttyent *ttyp;
+ int slot;
+
+ setttyent();
+ for (slot = 1; (ttyp = getttyent()); ++slot)
+ if (!strcmp(ttyp->ty_name, p->name.base)) {
+ endttyent();
+ return slot;
+ }
+
+ endttyent();
+ return -1;
+}
+
+static void
+tty_device2iov(struct device *d, struct iovec *iov, int *niov,
+ int maxiov __unused,
+#ifndef NONETGRAPH
+ int *auxfd, int *nauxfd
+#else
+ int *auxfd __unused, int *nauxfd __unused
+#endif
+ )
+{
+ struct ttydevice *dev;
+ int sz = physical_MaxDeviceSize();
+
+ iov[*niov].iov_base = d = realloc(d, sz);
+ if (d == NULL) {
+ log_Printf(LogALERT, "Failed to allocate memory: %d\n", sz);
+ AbortProgram(EX_OSERR);
+ }
+ iov[*niov].iov_len = sz;
+ (*niov)++;
+
+ dev = device2tty(d);
+
+#ifndef NONETGRAPH
+ if (dev->cs >= 0) {
+ *auxfd = dev->cs;
+ (*nauxfd)++;
+ }
+#endif
+
+ if (dev->Timer.state != TIMER_STOPPED) {
+ timer_Stop(&dev->Timer);
+ dev->Timer.state = TIMER_RUNNING;
+ }
+}
+
+static struct device basettydevice = {
+ TTY_DEVICE,
+ "tty",
+ 0,
+ { CD_VARIABLE, DEF_TTYCDDELAY },
+ tty_AwaitCarrier,
+ NULL,
+ tty_Raw,
+ tty_Offline,
+ tty_Cooked,
+ tty_SetAsyncParams,
+ tty_StopTimer,
+ tty_Free,
+ tty_Read,
+ tty_Write,
+ tty_device2iov,
+ tty_Speed,
+ tty_OpenInfo,
+ tty_Slot
+};
+
+struct device *
+tty_iov2device(int type, struct physical *p, struct iovec *iov, int *niov,
+ int maxiov __unused,
+#ifndef NONETGRAPH
+ int *auxfd, int *nauxfd
+#else
+ int *auxfd __unused, int *nauxfd __unused
+#endif
+ )
+{
+ if (type == TTY_DEVICE) {
+ struct ttydevice *dev = (struct ttydevice *)iov[(*niov)++].iov_base;
+
+ dev = realloc(dev, sizeof *dev); /* Reduce to the correct size */
+ if (dev == NULL) {
+ log_Printf(LogALERT, "Failed to allocate memory: %d\n",
+ (int)(sizeof *dev));
+ AbortProgram(EX_OSERR);
+ }
+
+#ifndef NONETGRAPH
+ if (*nauxfd) {
+ dev->cs = *auxfd;
+ (*nauxfd)--;
+ } else
+ dev->cs = -1;
+#endif
+
+ /* Refresh function pointers etc */
+ memcpy(&dev->dev, &basettydevice, sizeof dev->dev);
+
+ physical_SetupStack(p, dev->dev.name, PHYSICAL_NOFORCE);
+ if (dev->Timer.state != TIMER_STOPPED) {
+ dev->Timer.state = TIMER_STOPPED;
+ p->handler = &dev->dev; /* For the benefit of StartTimer */
+ tty_StartTimer(p);
+ }
+ return &dev->dev;
+ }
+
+ return NULL;
+}
+
+struct device *
+tty_Create(struct physical *p)
+{
+ struct ttydevice *dev;
+ struct termios ios;
+ int oldflag;
+
+ if (p->fd < 0 || !isatty(p->fd))
+ /* Don't want this */
+ return NULL;
+
+ if (*p->name.full == '\0') {
+ physical_SetDevice(p, ttyname(p->fd));
+ log_Printf(LogDEBUG, "%s: Input is a tty (%s)\n",
+ p->link.name, p->name.full);
+ } else
+ log_Printf(LogDEBUG, "%s: Opened %s\n", p->link.name, p->name.full);
+
+ /* We're gonna return a ttydevice (unless something goes horribly wrong) */
+
+ if ((dev = malloc(sizeof *dev)) == NULL) {
+ /* Complete failure - parent doesn't continue trying to ``create'' */
+ close(p->fd);
+ p->fd = -1;
+ return NULL;
+ }
+
+ memcpy(&dev->dev, &basettydevice, sizeof dev->dev);
+ memset(&dev->Timer, '\0', sizeof dev->Timer);
+ dev->mbits = -1;
+#ifndef NONETGRAPH
+ dev->real.speed = 0;
+ dev->real.fd = -1;
+ dev->real.disc = -1;
+ *dev->hook = '\0';
+#endif
+ tcgetattr(p->fd, &ios);
+ dev->ios = ios;
+
+ if (p->cfg.cd.necessity != CD_DEFAULT)
+ /* Any override is ok for the tty device */
+ dev->dev.cd = p->cfg.cd;
+
+ log_Printf(LogDEBUG, "%s: tty_Create: physical (get): fd = %d,"
+ " iflag = %lx, oflag = %lx, cflag = %lx\n", p->link.name, p->fd,
+ (u_long)ios.c_iflag, (u_long)ios.c_oflag, (u_long)ios.c_cflag);
+
+ cfmakeraw(&ios);
+ if (p->cfg.rts_cts)
+ ios.c_cflag |= CLOCAL | CCTS_OFLOW | CRTS_IFLOW;
+ else {
+ ios.c_cflag |= CLOCAL;
+ ios.c_iflag |= IXOFF;
+ }
+ ios.c_iflag |= IXON;
+ if (p->type != PHYS_DEDICATED)
+ ios.c_cflag |= HUPCL;
+
+ if (p->type != PHYS_DIRECT) {
+ /* Change tty speed when we're not in -direct mode */
+ ios.c_cflag &= ~(CSIZE | PARODD | PARENB);
+ ios.c_cflag |= p->cfg.parity;
+ if (cfsetspeed(&ios, UnsignedToSpeed(p->cfg.speed)) == -1)
+ log_Printf(LogWARN, "%s: %s: Unable to set speed to %d\n",
+ p->link.name, p->name.full, p->cfg.speed);
+ }
+
+ if (tcsetattr(p->fd, TCSADRAIN, &ios) == -1) {
+ log_Printf(LogWARN, "%s: tcsetattr: Failed configuring device\n",
+ p->link.name);
+ if (p->type != PHYS_DIRECT && p->cfg.speed > 115200)
+ log_Printf(LogWARN, "%.*s Perhaps the speed is unsupported\n",
+ (int)strlen(p->link.name), "");
+ }
+
+ log_Printf(LogDEBUG, "%s: physical (put): iflag = %lx, oflag = %lx, "
+ "cflag = %lx\n", p->link.name, (u_long)ios.c_iflag,
+ (u_long)ios.c_oflag, (u_long)ios.c_cflag);
+
+ oldflag = fcntl(p->fd, F_GETFL, 0);
+ if (oldflag < 0) {
+ /* Complete failure - parent doesn't continue trying to ``create'' */
+
+ log_Printf(LogWARN, "%s: Open: Cannot get physical flags: %s\n",
+ p->link.name, strerror(errno));
+ tty_Cooked(p);
+ close(p->fd);
+ p->fd = -1;
+ free(dev);
+ return NULL;
+ } else
+ fcntl(p->fd, F_SETFL, oldflag & ~O_NONBLOCK);
+
+ physical_SetupStack(p, dev->dev.name, PHYSICAL_NOFORCE);
+
+ return &dev->dev;
+}
diff --git a/usr.sbin/ppp/tty.h b/usr.sbin/ppp/tty.h
new file mode 100644
index 0000000..0991c81
--- /dev/null
+++ b/usr.sbin/ppp/tty.h
@@ -0,0 +1,37 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.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$
+ */
+
+struct physical;
+struct device;
+
+#define DEF_TTYCDDELAY 1 /* Default ``set cd'' value */
+
+extern struct device *tty_Create(struct physical *);
+extern struct device *tty_iov2device(int, struct physical *,
+ struct iovec *, int *, int, int *, int *);
+extern unsigned tty_DeviceSize(void);
diff --git a/usr.sbin/ppp/tun.c b/usr.sbin/ppp/tun.c
new file mode 100644
index 0000000..883cd5c
--- /dev/null
+++ b/usr.sbin/ppp/tun.c
@@ -0,0 +1,119 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.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/param.h>
+
+#include <sys/socket.h> /* For IFF_ defines */
+#ifndef __FreeBSD__
+#include <net/if.h> /* For IFF_ defines */
+#endif
+#include <net/route.h>
+#include <netinet/in.h>
+#include <net/if_types.h>
+#include <net/if_tun.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <string.h>
+#if defined(__OpenBSD__) || defined(__NetBSD__)
+#include <sys/ioctl.h>
+#endif
+#include <stdio.h>
+#include <termios.h>
+#ifdef __NetBSD__
+#include <unistd.h>
+#endif
+
+#include "layer.h"
+#include "mbuf.h"
+#include "log.h"
+#include "id.h"
+#include "timer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "defs.h"
+#include "fsm.h"
+#include "throughput.h"
+#include "iplist.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "mp.h"
+#include "iface.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "tun.h"
+
+void
+tun_configure(struct bundle *bundle)
+{
+#ifdef __NetBSD__
+ struct ifreq ifr;
+ int s;
+
+ s = socket(PF_INET, SOCK_DGRAM, 0);
+
+ if (s < 0) {
+ log_Printf(LogERROR, "tun_configure: socket(): %s\n", strerror(errno));
+ return;
+ }
+
+ sprintf(ifr.ifr_name, "tun%d", bundle->unit);
+ ifr.ifr_mtu = bundle->iface->mtu;
+ if (ioctl(s, SIOCSIFMTU, &ifr) < 0)
+ log_Printf(LogERROR, "tun_configure: ioctl(SIOCSIFMTU): %s\n",
+ strerror(errno));
+
+ close(s);
+#else
+ struct tuninfo info;
+
+ memset(&info, '\0', sizeof info);
+ info.type = IFT_PPP;
+ info.mtu = bundle->iface->mtu;
+
+ info.baudrate = bundle->bandwidth;
+#ifdef __OpenBSD__
+ info.flags = IFF_UP|IFF_POINTOPOINT|IFF_MULTICAST;
+#endif
+ if (ID0ioctl(bundle->dev.fd, TUNSIFINFO, &info) < 0)
+ log_Printf(LogERROR, "tun_configure: ioctl(TUNSIFINFO): %s\n",
+ strerror(errno));
+#endif
+}
diff --git a/usr.sbin/ppp/tun.h b/usr.sbin/ppp/tun.h
new file mode 100644
index 0000000..1bb0712
--- /dev/null
+++ b/usr.sbin/ppp/tun.h
@@ -0,0 +1,39 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.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$
+ */
+
+struct tun_data {
+ union {
+ u_int32_t family;
+ u_int32_t timeout;
+ } header;
+ u_char data[MAX_MRU];
+};
+
+struct bundle;
+
+extern void tun_configure(struct bundle *);
diff --git a/usr.sbin/ppp/ua.h b/usr.sbin/ppp/ua.h
new file mode 100644
index 0000000..dbd89e5
--- /dev/null
+++ b/usr.sbin/ppp/ua.h
@@ -0,0 +1,74 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.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$
+ */
+
+#ifdef __i386__ /* Do any other archs not care about alignment ? */
+
+# define ua_htonl(src, tgt) (*(u_int32_t *)(tgt) = htonl(*(u_int32_t *)(src)))
+# define ua_ntohl(src, tgt) (*(u_int32_t *)(tgt) = ntohl(*(u_int32_t *)(src)))
+# define ua_htons(src, tgt) (*(u_int16_t *)(tgt) = htons(*(u_int16_t *)(src)))
+# define ua_ntohs(src, tgt) (*(u_int16_t *)(tgt) = ntohs(*(u_int16_t *)(src)))
+
+#else /* We care about alignment (or else drop a core !) */
+
+# define ua_htonl(src, tgt) \
+ do { \
+ u_int32_t __oh; \
+ memcpy(&__oh, (src), sizeof __oh); \
+ *(u_char *)(tgt) = __oh >> 24; \
+ *((u_char *)(tgt) + 1) = (__oh >> 16) & 0xff; \
+ *((u_char *)(tgt) + 2) = (__oh >> 8) & 0xff; \
+ *((u_char *)(tgt) + 3) = __oh & 0xff; \
+ } while (0)
+
+# define ua_ntohl(src, tgt) \
+ do { \
+ u_int32_t __nh; \
+ __nh = ((u_int32_t)*(u_char *)(src) << 24) | \
+ ((u_int32_t)*((u_char *)(src) + 1) << 16) | \
+ ((u_int32_t)*((u_char *)(src) + 2) << 8) | \
+ (u_int32_t)*((u_char *)(src) + 3); \
+ memcpy((tgt), &__nh, sizeof __nh); \
+ } while (0)
+
+# define ua_htons(src, tgt) \
+ do { \
+ u_int16_t __oh; \
+ memcpy(&__oh, (src), sizeof __oh); \
+ *(u_char *)(tgt) = __oh >> 8; \
+ *((u_char *)(tgt) + 1) = __oh & 0xff; \
+ } while (0)
+
+# define ua_ntohs(src, tgt) \
+ do { \
+ u_int16_t __nh; \
+ __nh = ((u_int16_t)*(u_char *)(src) << 8) | \
+ (u_int16_t)*((u_char *)(src) + 1); \
+ memcpy((tgt), &__nh, sizeof __nh); \
+ } while (0)
+
+#endif
diff --git a/usr.sbin/ppp/udp.c b/usr.sbin/ppp/udp.c
new file mode 100644
index 0000000..d9f3252
--- /dev/null
+++ b/usr.sbin/ppp/udp.c
@@ -0,0 +1,335 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.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/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "fsm.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "async.h"
+#include "descriptor.h"
+#include "physical.h"
+#include "main.h"
+#include "udp.h"
+
+
+#define UDP_CONNECTED 1
+#define UDP_UNCONNECTED 2
+#define UDP_MAYBEUNCONNECTED 3
+
+struct udpdevice {
+ struct device dev; /* What struct physical knows about */
+ struct sockaddr_in sock; /* peer address */
+ unsigned connected : 2; /* Have we connect()d ? */
+};
+
+#define device2udp(d) ((d)->type == UDP_DEVICE ? (struct udpdevice *)d : NULL)
+
+unsigned
+udp_DeviceSize(void)
+{
+ return sizeof(struct udpdevice);
+}
+
+static ssize_t
+udp_Sendto(struct physical *p, const void *v, size_t n)
+{
+ struct udpdevice *dev = device2udp(p->handler);
+ int ret;
+
+ switch (dev->connected) {
+ case UDP_CONNECTED:
+ ret = write(p->fd, v, n);
+ break;
+
+ case UDP_UNCONNECTED:
+ default:
+ ret = sendto(p->fd, v, n, 0, (struct sockaddr *)&dev->sock,
+ sizeof dev->sock);
+ break;
+ }
+ if (dev->connected == UDP_MAYBEUNCONNECTED) {
+ if (ret == -1 && errno == EISCONN) {
+ dev->connected = UDP_CONNECTED;
+ ret = write(p->fd, v, n);
+ } else
+ dev->connected = UDP_UNCONNECTED;
+ }
+
+ return ret;
+}
+
+static ssize_t
+udp_Recvfrom(struct physical *p, void *v, size_t n)
+{
+ struct udpdevice *dev = device2udp(p->handler);
+ int sz, ret;
+
+ if (dev->connected == UDP_CONNECTED)
+ return read(p->fd, v, n);
+
+ sz = sizeof dev->sock;
+ ret = recvfrom(p->fd, v, n, 0, (struct sockaddr *)&dev->sock, &sz);
+
+ if (*p->name.full == '\0') {
+ snprintf(p->name.full, sizeof p->name.full, "%s:%d/udp",
+ inet_ntoa(dev->sock.sin_addr), ntohs(dev->sock.sin_port));
+ p->name.base = p->name.full;
+ }
+
+ return ret;
+}
+
+static void
+udp_Free(struct physical *p)
+{
+ struct udpdevice *dev = device2udp(p->handler);
+
+ free(dev);
+}
+
+static void
+udp_device2iov(struct device *d, struct iovec *iov, int *niov,
+ int maxiov __unused, int *auxfd __unused, int *nauxfd __unused)
+{
+ int sz = physical_MaxDeviceSize();
+
+ iov[*niov].iov_base = realloc(d, sz);
+ if (iov[*niov].iov_base == NULL) {
+ log_Printf(LogALERT, "Failed to allocate memory: %d\n", sz);
+ AbortProgram(EX_OSERR);
+ }
+ iov[*niov].iov_len = sz;
+ (*niov)++;
+}
+
+static const struct device baseudpdevice = {
+ UDP_DEVICE,
+ "udp",
+ 0,
+ { CD_NOTREQUIRED, 0 },
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ udp_Free,
+ udp_Recvfrom,
+ udp_Sendto,
+ udp_device2iov,
+ NULL,
+ NULL,
+ NULL
+};
+
+struct device *
+udp_iov2device(int type, struct physical *p, struct iovec *iov, int *niov,
+ int maxiov __unused, int *auxfd __unused, int *nauxfd __unused)
+{
+ if (type == UDP_DEVICE) {
+ struct udpdevice *dev = (struct udpdevice *)iov[(*niov)++].iov_base;
+
+ dev = realloc(dev, sizeof *dev); /* Reduce to the correct size */
+ if (dev == NULL) {
+ log_Printf(LogALERT, "Failed to allocate memory: %d\n",
+ (int)(sizeof *dev));
+ AbortProgram(EX_OSERR);
+ }
+
+ /* Refresh function pointers etc */
+ memcpy(&dev->dev, &baseudpdevice, sizeof dev->dev);
+
+ physical_SetupStack(p, dev->dev.name, PHYSICAL_FORCE_SYNC);
+ return &dev->dev;
+ }
+
+ return NULL;
+}
+
+static struct udpdevice *
+udp_CreateDevice(struct physical *p, char *host, char *port)
+{
+ struct udpdevice *dev;
+ struct servent *sp;
+
+ if ((dev = malloc(sizeof *dev)) == NULL) {
+ log_Printf(LogWARN, "%s: Cannot allocate a udp device: %s\n",
+ p->link.name, strerror(errno));
+ return NULL;
+ }
+
+ dev->sock.sin_family = AF_INET;
+ dev->sock.sin_addr = GetIpAddr(host);
+ if (dev->sock.sin_addr.s_addr == INADDR_NONE) {
+ log_Printf(LogWARN, "%s: %s: unknown host\n", p->link.name, host);
+ free(dev);
+ return NULL;
+ }
+ dev->sock.sin_port = htons(atoi(port));
+ if (dev->sock.sin_port == 0) {
+ sp = getservbyname(port, "udp");
+ if (sp)
+ dev->sock.sin_port = sp->s_port;
+ else {
+ log_Printf(LogWARN, "%s: %s: unknown service\n", p->link.name, port);
+ free(dev);
+ return NULL;
+ }
+ }
+
+ log_Printf(LogPHASE, "%s: Connecting to %s:%s/udp\n", p->link.name,
+ host, port);
+
+ p->fd = socket(PF_INET, SOCK_DGRAM, 0);
+ if (p->fd >= 0) {
+ log_Printf(LogDEBUG, "%s: Opened udp socket %s\n", p->link.name,
+ p->name.full);
+ if (connect(p->fd, (struct sockaddr *)&dev->sock, sizeof dev->sock) == 0) {
+ dev->connected = UDP_CONNECTED;
+ return dev;
+ } else
+ log_Printf(LogWARN, "%s: connect: %s\n", p->name.full, strerror(errno));
+ } else
+ log_Printf(LogWARN, "%s: socket: %s\n", p->name.full, strerror(errno));
+
+ close(p->fd);
+ p->fd = -1;
+ free(dev);
+
+ return NULL;
+}
+
+struct device *
+udp_Create(struct physical *p)
+{
+ char *cp, *host, *port, *svc;
+ struct udpdevice *dev;
+
+ dev = NULL;
+ if (p->fd < 0) {
+ if ((cp = strchr(p->name.full, ':')) != NULL && !strchr(cp + 1, ':')) {
+ *cp = '\0';
+ host = p->name.full;
+ port = cp + 1;
+ svc = strchr(port, '/');
+ if (svc && strcasecmp(svc, "/udp")) {
+ *cp = ':';
+ return NULL;
+ }
+ if (svc) {
+ p->fd--; /* We own the device but maybe can't use it - change fd */
+ *svc = '\0';
+ }
+
+ if (*host && *port)
+ dev = udp_CreateDevice(p, host, port);
+
+ *cp = ':';
+ if (svc)
+ *svc = '/';
+ }
+ } else {
+ /* See if we're a connected udp socket */
+ struct stat st;
+
+ if (fstat(p->fd, &st) != -1 && (st.st_mode & S_IFSOCK)) {
+ int type, sz;
+
+ sz = sizeof type;
+ if (getsockopt(p->fd, SOL_SOCKET, SO_TYPE, &type, &sz) == -1) {
+ log_Printf(LogPHASE, "%s: Link is a closed socket !\n", p->link.name);
+ close(p->fd);
+ p->fd = -1;
+ return NULL;
+ }
+
+ if (sz == sizeof type && type == SOCK_DGRAM) {
+ struct sockaddr_in sock;
+ struct sockaddr *sockp = (struct sockaddr *)&sock;
+
+ if ((dev = malloc(sizeof *dev)) == NULL) {
+ log_Printf(LogWARN, "%s: Cannot allocate a udp device: %s\n",
+ p->link.name, strerror(errno));
+ return NULL;
+ }
+
+ if (getpeername(p->fd, sockp, &sz) == 0) {
+ log_Printf(LogPHASE, "%s: Link is a connected udp socket\n",
+ p->link.name);
+ dev->connected = UDP_CONNECTED;
+ } else {
+ log_Printf(LogPHASE, "%s: Link is a disconnected udp socket\n",
+ p->link.name);
+
+ dev->connected = UDP_MAYBEUNCONNECTED;
+
+ if (p->link.lcp.cfg.openmode != OPEN_PASSIVE) {
+ log_Printf(LogPHASE, "%s: Changing openmode to PASSIVE\n",
+ p->link.name);
+ p->link.lcp.cfg.openmode = OPEN_PASSIVE;
+ }
+ }
+ }
+ }
+ }
+
+ if (dev) {
+ memcpy(&dev->dev, &baseudpdevice, sizeof dev->dev);
+ physical_SetupStack(p, dev->dev.name, PHYSICAL_FORCE_SYNC);
+ if (p->cfg.cd.necessity != CD_DEFAULT)
+ log_Printf(LogWARN, "Carrier settings ignored\n");
+ return &dev->dev;
+ }
+
+ return NULL;
+}
diff --git a/usr.sbin/ppp/udp.h b/usr.sbin/ppp/udp.h
new file mode 100644
index 0000000..c89e276
--- /dev/null
+++ b/usr.sbin/ppp/udp.h
@@ -0,0 +1,35 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.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$
+ */
+
+struct physical;
+struct device;
+
+extern struct device *udp_Create(struct physical *);
+extern struct device *udp_iov2device(int, struct physical *,
+ struct iovec *, int *, int, int *, int *);
+extern unsigned udp_DeviceSize(void);
diff --git a/usr.sbin/ppp/vjcomp.c b/usr.sbin/ppp/vjcomp.c
new file mode 100644
index 0000000..97166af
--- /dev/null
+++ b/usr.sbin/ppp/vjcomp.c
@@ -0,0 +1,200 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ * Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <stdio.h>
+#include <string.h> /* strlen/memcpy */
+#include <termios.h>
+
+#include "layer.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "proto.h"
+#include "slcompress.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "defs.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "vjcomp.h"
+
+#define MAX_VJHEADER 16 /* Maximum size of compressed header */
+
+static struct mbuf *
+vj_LayerPush(struct bundle *bundle, struct link *l __unused, struct mbuf *bp,
+ int pri __unused, u_short *proto)
+{
+ int type;
+ struct ip *pip;
+ u_short cproto = bundle->ncp.ipcp.peer_compproto >> 16;
+
+ bp = m_pullup(bp);
+ pip = (struct ip *)MBUF_CTOP(bp);
+ if (*proto == PROTO_IP && pip->ip_p == IPPROTO_TCP &&
+ cproto == PROTO_VJCOMP) {
+ type = sl_compress_tcp(bp, pip, &bundle->ncp.ipcp.vj.cslc,
+ &bundle->ncp.ipcp.vj.slstat,
+ bundle->ncp.ipcp.peer_compproto & 0xff);
+ log_Printf(LogDEBUG, "vj_LayerWrite: type = %x\n", type);
+ switch (type) {
+ case TYPE_IP:
+ break;
+
+ case TYPE_UNCOMPRESSED_TCP:
+ *proto = PROTO_VJUNCOMP;
+ log_Printf(LogDEBUG, "vj_LayerPush: PROTO_IP -> PROTO_VJUNCOMP\n");
+ m_settype(bp, MB_VJOUT);
+ break;
+
+ case TYPE_COMPRESSED_TCP:
+ *proto = PROTO_VJCOMP;
+ log_Printf(LogDEBUG, "vj_LayerPush: PROTO_IP -> PROTO_VJUNCOMP\n");
+ m_settype(bp, MB_VJOUT);
+ break;
+
+ default:
+ log_Printf(LogERROR, "vj_LayerPush: Unknown frame type %x\n", type);
+ m_freem(bp);
+ return NULL;
+ }
+ }
+
+ return bp;
+}
+
+static struct mbuf *
+VjUncompressTcp(struct ipcp *ipcp, struct mbuf *bp, u_char type)
+{
+ u_char *bufp;
+ int len, olen, rlen;
+ u_char work[MAX_HDR + MAX_VJHEADER]; /* enough to hold TCP/IP header */
+
+ bp = m_pullup(bp);
+ olen = len = m_length(bp);
+ if (type == TYPE_UNCOMPRESSED_TCP) {
+ /*
+ * Uncompressed packet does NOT change its size, so that we can use mbuf
+ * space for uncompression job.
+ */
+ bufp = MBUF_CTOP(bp);
+ len = sl_uncompress_tcp(&bufp, len, type, &ipcp->vj.cslc, &ipcp->vj.slstat,
+ (ipcp->my_compproto >> 8) & 255);
+ if (len <= 0) {
+ m_freem(bp);
+ bp = NULL;
+ } else
+ m_settype(bp, MB_VJIN);
+ return bp;
+ }
+
+ /*
+ * Handle compressed packet. 1) Read up to MAX_VJHEADER bytes into work
+ * space. 2) Try to uncompress it. 3) Compute amount of necessary space. 4)
+ * Copy unread data info there.
+ */
+ if (len > MAX_VJHEADER)
+ len = MAX_VJHEADER;
+ rlen = len;
+ bufp = work + MAX_HDR;
+ bp = mbuf_Read(bp, bufp, rlen);
+ len = sl_uncompress_tcp(&bufp, olen, type, &ipcp->vj.cslc, &ipcp->vj.slstat,
+ (ipcp->my_compproto >> 8) & 255);
+ if (len <= 0) {
+ m_freem(bp);
+ return NULL;
+ }
+ len -= olen;
+ len += rlen;
+
+ bp = m_prepend(bp, bufp, len, 0);
+ m_settype(bp, MB_VJIN);
+
+ return bp;
+}
+
+static struct mbuf *
+vj_LayerPull(struct bundle *bundle, struct link *l __unused, struct mbuf *bp,
+ u_short *proto)
+{
+ u_char type;
+
+ switch (*proto) {
+ case PROTO_VJCOMP:
+ type = TYPE_COMPRESSED_TCP;
+ log_Printf(LogDEBUG, "vj_LayerPull: PROTO_VJCOMP -> PROTO_IP\n");
+ break;
+ case PROTO_VJUNCOMP:
+ type = TYPE_UNCOMPRESSED_TCP;
+ log_Printf(LogDEBUG, "vj_LayerPull: PROTO_VJUNCOMP -> PROTO_IP\n");
+ break;
+ default:
+ return bp;
+ }
+
+ *proto = PROTO_IP;
+ return VjUncompressTcp(&bundle->ncp.ipcp, bp, type);
+}
+
+const char *
+vj2asc(u_int32_t val)
+{
+ static char asc[50]; /* The return value is used immediately */
+
+ if (val)
+ snprintf(asc, sizeof asc, "%d VJ slots with%s slot compression",
+ (int)((val>>8)&15)+1, val & 1 ? "" : "out");
+ else
+ strcpy(asc, "VJ disabled");
+ return asc;
+}
+
+struct layer vjlayer = { LAYER_VJ, "vj", vj_LayerPush, vj_LayerPull };
diff --git a/usr.sbin/ppp/vjcomp.h b/usr.sbin/ppp/vjcomp.h
new file mode 100644
index 0000000..2956122
--- /dev/null
+++ b/usr.sbin/ppp/vjcomp.h
@@ -0,0 +1,36 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.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$
+ */
+
+struct mbuf;
+struct link;
+struct ipcp;
+struct bundle;
+
+extern const char *vj2asc(u_int32_t);
+
+extern struct layer vjlayer;
diff --git a/usr.sbin/pppctl/Makefile b/usr.sbin/pppctl/Makefile
new file mode 100644
index 0000000..dcb7931
--- /dev/null
+++ b/usr.sbin/pppctl/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+PROG= pppctl
+MAN= pppctl.8
+
+WARNS?= 2
+
+LIBADD= edit pthread
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/pppctl/Makefile.depend b/usr.sbin/pppctl/Makefile.depend
new file mode 100644
index 0000000..93d2803
--- /dev/null
+++ b/usr.sbin/pppctl/Makefile.depend
@@ -0,0 +1,22 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libedit \
+ lib/libthr \
+ lib/ncurses/ncursesw \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/pppctl/pppctl.8 b/usr.sbin/pppctl/pppctl.8
new file mode 100644
index 0000000..431b7d7
--- /dev/null
+++ b/usr.sbin/pppctl/pppctl.8
@@ -0,0 +1,232 @@
+.\" $FreeBSD$
+.Dd June 26, 1997
+.Dt PPPCTL 8
+.Os
+.Sh NAME
+.Nm pppctl
+.Nd PPP control program
+.Sh SYNOPSIS
+.Nm
+.Op Fl v
+.Op Fl t Ar n
+.Op Fl p Ar passwd
+.Xo Oo Ar host : Oc Ns
+.Ar Port | LocalSocket
+.Xc
+.Oo
+.Sm off
+.Ar command Oo ; Ar command Oc Ar ...
+.Sm on
+.Oc
+.Sh DESCRIPTION
+This utility provides command line control of the
+.Xr ppp 8
+daemon.
+Its primary use is to facilitate simple scripts that
+control a running daemon.
+.Pp
+The
+.Nm
+utility is passed at least one argument, specifying the socket on which
+.Nm ppp
+is listening.
+Refer to the
+.Sq set server
+command of
+.Nm ppp
+for details.
+If the socket contains a leading '/', it
+is taken as an
+.Dv AF_LOCAL
+socket.
+If it contains a colon, it is treated as a
+.Ar host : Ns Ar port
+pair, otherwise it is treated as a TCP port specification on the
+local machine (127.0.0.1).
+Both the
+.Ar host
+and
+.Ar port
+may be specified numerically if you wish to avoid a DNS lookup
+or do not have an entry for the given port in
+.Pa /etc/services .
+.Pp
+All remaining arguments are concatenated to form the
+.Ar command Ns (s)
+that will be sent to the
+.Nm ppp
+daemon.
+If any semi-colon characters are found, they are treated as
+.Ar command
+delimiters, allowing more than one
+.Ar command
+in a given
+.Sq session .
+For example:
+.Bd -literal -offset indent
+pppctl 3000 set timeout 300\\; show timeout
+.Ed
+.Pp
+Do not forget to escape or quote the ';' as it is a special character
+for most shells.
+.Pp
+If no
+.Ar command
+arguments are given,
+.Nm
+enters interactive mode, where commands are read from standard input.
+When reading commands, the
+.Xr editline 3
+library is used, allowing command-line editing (with
+.Xr editrc 5
+defining editing behaviour).
+The history size
+defaults to
+.Em 20 lines .
+.Pp
+The following command line options are available:
+.Bl -tag -width Ds
+.It Fl v
+Display all data sent to and received from the
+.Nm ppp
+daemon.
+Normally,
+.Nm
+displays only non-prompt lines received.
+This option is ignored in
+interactive mode.
+.It Fl t Ar n
+Use a timeout of
+.Ar n
+instead of the default 2 seconds when connecting.
+This may be required
+if you wish to control a daemon over a slow (or even a dialup) link.
+.It Fl p Ar passwd
+Specify the password required by the
+.Nm ppp
+daemon.
+If this switch is not used,
+.Nm
+will prompt for a password once it has successfully connected to
+.Nm ppp .
+.El
+.Sh ENVIRONMENT
+The following environment variables are understood by
+.Nm
+when in interactive mode:
+.Bl -tag -width XXXXXXXXXX
+.It Dv EL_SIZE
+The number of history lines.
+The default is 20.
+.It Dv EL_EDITOR
+The edit mode.
+Only values of "emacs" and "vi" are accepted.
+Other values
+are silently ignored.
+This environment variable will override the
+.Ar bind -v
+and
+.Ar bind -e
+commands in
+.Pa ~/.editrc .
+.El
+.Sh EXAMPLES
+If you run
+.Nm ppp
+in
+.Fl auto
+mode,
+.Nm
+can be used to automate many frequent tasks (you can actually control
+.Nm ppp
+in any mode except interactive mode).
+Use of the
+.Fl p
+option is discouraged (even in scripts that are not readable by others)
+as a
+.Xr ps 1
+listing may reveal your secret.
+.Pp
+The best way to allow easy, secure
+.Nm
+access is to create a local server socket in
+.Pa /etc/ppp/ppp.conf
+(in the correct section) like this:
+.Bd -literal -offset indent
+set server /var/run/internet "" 0177
+.Ed
+.Pp
+This will instruct
+.Nm ppp
+to create a local domain socket, with srw------- permissions and no
+password, allowing access only to the user that invoked
+.Nm ppp .
+Refer to the
+.Xr ppp 8
+man page for further details.
+.Pp
+You can now create some easy-access scripts.
+To connect to the internet:
+.Bd -literal -offset indent
+#! /bin/sh
+test $# -eq 0 && time=300 || time=$1
+exec pppctl /var/run/internet set timeout $time\\; dial
+.Ed
+.Pp
+To disconnect:
+.Bd -literal -offset indent
+#! /bin/sh
+exec pppctl /var/run/internet set timeout 300\\; close
+.Ed
+.Pp
+To check if the line is up:
+.Bd -literal -offset indent
+#! /bin/sh
+pppctl -p '' -v /var/run/internet quit | grep ^PPP >/dev/null
+if [ $? -eq 0 ]; then
+ echo Link is up
+else
+ echo Link is down
+fi
+.Ed
+.Pp
+You can even make a generic script:
+.Bd -literal -offset indent
+#! /bin/sh
+exec pppctl /var/run/internet "$@"
+.Ed
+.Pp
+You could also use
+.Nm
+to control when dial-on-demand works.
+Suppose you want
+.Nm ppp
+to run all the time, but you want to prevent dial-out between 8pm and 8am
+each day.
+However, any connections active at 8pm should continue to remain
+active until they are closed or naturally time out.
+.Pp
+A
+.Xr cron 8
+entry for 8pm which runs
+.Bd -literal -offset indent
+pppctl /var/run/internet set filter dial 0 deny 0 0
+.Ed
+.Pp
+will block all further dial requests, and the corresponding 8am entry
+.Bd -literal -offset indent
+pppctl /var/run/internet set filter dial -1
+.Ed
+.Pp
+will allow them again.
+.Sh SEE ALSO
+.Xr ps 1 ,
+.Xr editline 3 ,
+.Xr editrc 5 ,
+.Xr services 5 ,
+.Xr ppp 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 2.2.5 .
diff --git a/usr.sbin/pppctl/pppctl.c b/usr.sbin/pppctl/pppctl.c
new file mode 100644
index 0000000..d8e7b42
--- /dev/null
+++ b/usr.sbin/pppctl/pppctl.c
@@ -0,0 +1,680 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.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/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <netdb.h>
+
+#include <sys/time.h>
+#include <err.h>
+#include <errno.h>
+#include <histedit.h>
+#include <semaphore.h>
+#include <pthread.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define LINELEN 2048
+
+/* Data passed to the threads we create */
+struct thread_data {
+ EditLine *edit; /* libedit stuff */
+ History *hist; /* libedit stuff */
+ pthread_t trm; /* Terminal thread (for pthread_kill()) */
+ int ppp; /* ppp descriptor */
+};
+
+/* Flags passed to Receive() */
+#define REC_PASSWD (1) /* Handle a password request from ppp */
+#define REC_SHOW (2) /* Show everything except prompts */
+#define REC_VERBOSE (4) /* Show everything */
+
+static char *passwd;
+static char *prompt; /* Tell libedit what the current prompt is */
+static int data = -1; /* setjmp() has been done when data != -1 */
+static jmp_buf pppdead; /* Jump the Terminal thread out of el_gets() */
+static int timetogo; /* Tell the Monitor thread to exit */
+static sem_t sem_select; /* select() co-ordination between threads */
+static int TimedOut; /* Set if our connect() timed out */
+static int want_sem_post; /* Need to let the Monitor thread in ? */
+
+/*
+ * How to use pppctl...
+ */
+static int
+usage()
+{
+ fprintf(stderr, "usage: pppctl [-v] [-t n] [-p passwd] "
+ "Port|LocalSock [command[;command]...]\n");
+ fprintf(stderr, " -v tells pppctl to output all"
+ " conversation\n");
+ fprintf(stderr, " -t n specifies a timeout of n"
+ " seconds when connecting (default 2)\n");
+ fprintf(stderr, " -p passwd specifies your password\n");
+ exit(1);
+}
+
+/*
+ * Handle the SIGALRM received due to a connect() timeout.
+ */
+static void
+Timeout(int Sig)
+{
+ TimedOut = 1;
+}
+
+/*
+ * A callback routine for libedit to find out what the current prompt is.
+ * All the work is done in Receive() below.
+ */
+static char *
+GetPrompt(EditLine *e)
+{
+ if (prompt == NULL)
+ prompt = "";
+ return prompt;
+}
+
+/*
+ * Receive data from the ppp descriptor.
+ * We also handle password prompts here (if asked via the `display' arg)
+ * and buffer what our prompt looks like (via the `prompt' global).
+ */
+static int
+Receive(int fd, int display)
+{
+ static char Buffer[LINELEN];
+ struct timeval t;
+ int Result;
+ char *last;
+ fd_set f;
+ int len;
+ int err;
+
+ FD_ZERO(&f);
+ FD_SET(fd, &f);
+ t.tv_sec = 0;
+ t.tv_usec = 100000;
+ prompt = Buffer;
+ len = 0;
+
+ while (Result = read(fd, Buffer+len, sizeof(Buffer)-len-1), Result != -1) {
+ if (Result == 0) {
+ Result = -1;
+ break;
+ }
+ len += Result;
+ Buffer[len] = '\0';
+ if (len > 2 && !strcmp(Buffer+len-2, "> ")) {
+ prompt = strrchr(Buffer, '\n');
+ if (display & (REC_SHOW|REC_VERBOSE)) {
+ if (display & REC_VERBOSE)
+ last = Buffer+len-1;
+ else
+ last = prompt;
+ if (last) {
+ last++;
+ write(STDOUT_FILENO, Buffer, last-Buffer);
+ }
+ }
+ prompt = prompt == NULL ? Buffer : prompt+1;
+ for (last = Buffer+len-2; last > Buffer && *last != ' '; last--)
+ ;
+ if (last > Buffer+3 && !strncmp(last-3, " on", 3)) {
+ /* a password is required ! */
+ if (display & REC_PASSWD) {
+ /* password time */
+ if (!passwd)
+ passwd = getpass("Password: ");
+ sprintf(Buffer, "passwd %s\n", passwd);
+ memset(passwd, '\0', strlen(passwd));
+ if (display & REC_VERBOSE)
+ write(STDOUT_FILENO, Buffer, strlen(Buffer));
+ write(fd, Buffer, strlen(Buffer));
+ memset(Buffer, '\0', strlen(Buffer));
+ return Receive(fd, display & ~REC_PASSWD);
+ }
+ Result = 1;
+ } else
+ Result = 0;
+ break;
+ } else
+ prompt = "";
+ if (len == sizeof Buffer - 1) {
+ int flush;
+ if ((last = strrchr(Buffer, '\n')) == NULL)
+ /* Yeuch - this is one mother of a line ! */
+ flush = sizeof Buffer / 2;
+ else
+ flush = last - Buffer + 1;
+ write(STDOUT_FILENO, Buffer, flush);
+ strcpy(Buffer, Buffer + flush);
+ len -= flush;
+ }
+ if ((Result = select(fd + 1, &f, NULL, NULL, &t)) <= 0) {
+ err = Result == -1 ? errno : 0;
+ if (len)
+ write(STDOUT_FILENO, Buffer, len);
+ if (err == EINTR)
+ continue;
+ break;
+ }
+ }
+
+ return Result;
+}
+
+/*
+ * Handle being told by the Monitor thread that there's data to be read
+ * on the ppp descriptor.
+ *
+ * Note, this is a signal handler - be careful of what we do !
+ */
+static void
+InputHandler(int sig)
+{
+ static char buf[LINELEN];
+ struct timeval t;
+ int len;
+ fd_set f;
+
+ if (data != -1) {
+ FD_ZERO(&f);
+ FD_SET(data, &f);
+ t.tv_sec = t.tv_usec = 0;
+
+ if (select(data + 1, &f, NULL, NULL, &t) > 0) {
+ len = read(data, buf, sizeof buf);
+
+ if (len > 0)
+ write(STDOUT_FILENO, buf, len);
+ else if (data != -1)
+ longjmp(pppdead, -1);
+ }
+
+ sem_post(&sem_select);
+ } else
+ /* Don't let the Monitor thread in 'till we've set ``data'' up again */
+ want_sem_post = 1;
+}
+
+/*
+ * This is a simple wrapper for el_gets(), allowing our SIGUSR1 signal
+ * handler (above) to take effect only after we've done a setjmp().
+ *
+ * We don't want it to do anything outside of here as we're going to
+ * service the ppp descriptor anyway.
+ */
+static const char *
+SmartGets(EditLine *e, int *count, int fd)
+{
+ const char *result;
+
+ if (setjmp(pppdead))
+ result = NULL;
+ else {
+ data = fd;
+ if (want_sem_post)
+ /* Let the Monitor thread in again */
+ sem_post(&sem_select);
+ result = el_gets(e, count);
+ }
+
+ data = -1;
+
+ return result;
+}
+
+/*
+ * The Terminal thread entry point.
+ *
+ * The bulk of the interactive work is done here. We read the terminal,
+ * write the results to our ppp descriptor and read the results back.
+ *
+ * While reading the terminal (using el_gets()), it's possible to take
+ * a SIGUSR1 from the Monitor thread, telling us that the ppp descriptor
+ * has some data. The data is read and displayed by the signal handler
+ * itself.
+ */
+static void *
+Terminal(void *v)
+{
+ struct sigaction act, oact;
+ struct thread_data *td;
+ const char *l;
+ int len;
+#ifndef __OpenBSD__
+ HistEvent hev = { 0, "" };
+#endif
+
+ act.sa_handler = InputHandler;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = SA_RESTART;
+ sigaction(SIGUSR1, &act, &oact);
+
+ td = (struct thread_data *)v;
+ want_sem_post = 1;
+
+ while ((l = SmartGets(td->edit, &len, td->ppp))) {
+ if (len > 1)
+#ifdef __OpenBSD__
+ history(td->hist, H_ENTER, l);
+#else
+ history(td->hist, &hev, H_ENTER, l);
+#endif
+ write(td->ppp, l, len);
+ if (Receive(td->ppp, REC_SHOW) != 0)
+ break;
+ }
+
+ return NULL;
+}
+
+/*
+ * The Monitor thread entry point.
+ *
+ * This thread simply monitors our ppp descriptor. When there's something
+ * to read, a SIGUSR1 is sent to the Terminal thread.
+ *
+ * sem_select() is used by the Terminal thread to keep us from sending
+ * flurries of SIGUSR1s, and is used from the main thread to wake us up
+ * when it's time to exit.
+ */
+static void *
+Monitor(void *v)
+{
+ struct thread_data *td;
+ fd_set f;
+ int ret;
+
+ td = (struct thread_data *)v;
+ FD_ZERO(&f);
+ FD_SET(td->ppp, &f);
+
+ sem_wait(&sem_select);
+ while (!timetogo)
+ if ((ret = select(td->ppp + 1, &f, NULL, NULL, NULL)) > 0) {
+ pthread_kill(td->trm, SIGUSR1);
+ sem_wait(&sem_select);
+ }
+
+ return NULL;
+}
+
+static const char *
+sockaddr_ntop(const struct sockaddr *sa)
+{
+ const void *addr;
+ static char addrbuf[INET6_ADDRSTRLEN];
+
+ switch (sa->sa_family) {
+ case AF_INET:
+ addr = &((const struct sockaddr_in *)sa)->sin_addr;
+ break;
+ case AF_UNIX:
+ addr = &((const struct sockaddr_un *)sa)->sun_path;
+ break;
+ case AF_INET6:
+ addr = &((const struct sockaddr_in6 *)sa)->sin6_addr;
+ break;
+ default:
+ return NULL;
+ }
+ inet_ntop(sa->sa_family, addr, addrbuf, sizeof(addrbuf));
+ return addrbuf;
+}
+
+/*
+ * Connect to ppp using either a local domain socket or a tcp socket.
+ *
+ * If we're given arguments, process them and quit, otherwise create two
+ * threads to handle interactive mode.
+ */
+int
+main(int argc, char **argv)
+{
+ struct sockaddr_un ifsun;
+ int n, arg, fd, len, verbose, save_errno, hide1, hide1off, hide2;
+ unsigned TimeoutVal;
+ char *DoneWord = "x", *next, *start;
+ struct sigaction act, oact;
+ void *thread_ret;
+ pthread_t mon;
+ char Command[LINELEN];
+ char Buffer[LINELEN];
+
+ verbose = 0;
+ TimeoutVal = 2;
+ hide1 = hide1off = hide2 = 0;
+
+ for (arg = 1; arg < argc; arg++)
+ if (*argv[arg] == '-') {
+ for (start = argv[arg] + 1; *start; start++)
+ switch (*start) {
+ case 't':
+ TimeoutVal = (unsigned)atoi
+ (start[1] ? start + 1 : argv[++arg]);
+ start = DoneWord;
+ break;
+
+ case 'v':
+ verbose = REC_VERBOSE;
+ break;
+
+ case 'p':
+ if (start[1]) {
+ hide1 = arg;
+ hide1off = start - argv[arg];
+ passwd = start + 1;
+ } else {
+ hide1 = arg;
+ hide1off = start - argv[arg];
+ passwd = argv[++arg];
+ hide2 = arg;
+ }
+ start = DoneWord;
+ break;
+
+ default:
+ usage();
+ }
+ }
+ else
+ break;
+
+
+ if (argc < arg + 1)
+ usage();
+
+ if (hide1) {
+ char title[1024];
+ int pos, harg;
+
+ for (harg = pos = 0; harg < argc; harg++)
+ if (harg == 0 || harg != hide2) {
+ if (harg == 0 || harg != hide1)
+ n = snprintf(title + pos, sizeof title - pos, "%s%s",
+ harg ? " " : "", argv[harg]);
+ else if (hide1off > 1)
+ n = snprintf(title + pos, sizeof title - pos, " %.*s",
+ hide1off, argv[harg]);
+ else
+ n = 0;
+ if (n < 0 || n >= sizeof title - pos)
+ break;
+ pos += n;
+ }
+#ifdef __FreeBSD__
+ setproctitle("-%s", title);
+#else
+ setproctitle("%s", title);
+#endif
+ }
+
+ if (*argv[arg] == '/') {
+ memset(&ifsun, '\0', sizeof ifsun);
+ ifsun.sun_len = strlen(argv[arg]);
+ if (ifsun.sun_len > sizeof ifsun.sun_path - 1) {
+ warnx("%s: path too long", argv[arg]);
+ return 1;
+ }
+ ifsun.sun_family = AF_LOCAL;
+ strcpy(ifsun.sun_path, argv[arg]);
+
+ if (fd = socket(AF_LOCAL, SOCK_STREAM, 0), fd < 0) {
+ warnx("cannot create local domain socket");
+ return 2;
+ }
+ if (connect(fd, (struct sockaddr *)&ifsun, sizeof(ifsun)) < 0) {
+ if (errno)
+ warn("cannot connect to socket %s", argv[arg]);
+ else
+ warnx("cannot connect to socket %s", argv[arg]);
+ close(fd);
+ return 3;
+ }
+ } else {
+ char *addr, *p, *port;
+ const char *caddr;
+ struct addrinfo hints, *res, *pai;
+ int gai;
+ char local[] = "localhost";
+
+ addr = argv[arg];
+ if (addr[strspn(addr, "0123456789")] == '\0') {
+ /* port on local machine */
+ port = addr;
+ addr = local;
+ } else if (*addr == '[') {
+ /* [addr]:port */
+ if ((p = strchr(addr, ']')) == NULL) {
+ warnx("%s: mismatched '['", addr);
+ return 1;
+ }
+ addr++;
+ *p++ = '\0';
+ if (*p != ':') {
+ warnx("%s: missing port", addr);
+ return 1;
+ }
+ port = ++p;
+ } else {
+ /* addr:port */
+ p = addr + strcspn(addr, ":");
+ if (*p != ':') {
+ warnx("%s: missing port", addr);
+ return 1;
+ }
+ *p++ = '\0';
+ port = p;
+ }
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_socktype = SOCK_STREAM;
+ gai = getaddrinfo(addr, port, &hints, &res);
+ if (gai != 0) {
+ warnx("%s: %s", addr, gai_strerror(gai));
+ return 1;
+ }
+ for (pai = res; pai != NULL; pai = pai->ai_next) {
+ if (fd = socket(pai->ai_family, pai->ai_socktype,
+ pai->ai_protocol), fd < 0) {
+ warnx("cannot create socket");
+ continue;
+ }
+ TimedOut = 0;
+ if (TimeoutVal) {
+ act.sa_handler = Timeout;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ sigaction(SIGALRM, &act, &oact);
+ alarm(TimeoutVal);
+ }
+ if (connect(fd, pai->ai_addr, pai->ai_addrlen) == 0)
+ break;
+ if (TimeoutVal) {
+ save_errno = errno;
+ alarm(0);
+ sigaction(SIGALRM, &oact, 0);
+ errno = save_errno;
+ }
+ caddr = sockaddr_ntop(pai->ai_addr);
+ if (caddr == NULL)
+ caddr = argv[arg];
+ if (TimedOut)
+ warnx("timeout: cannot connect to %s", caddr);
+ else {
+ if (errno)
+ warn("cannot connect to %s", caddr);
+ else
+ warnx("cannot connect to %s", caddr);
+ }
+ close(fd);
+ }
+ freeaddrinfo(res);
+ if (pai == NULL)
+ return 1;
+ if (TimeoutVal) {
+ alarm(0);
+ sigaction(SIGALRM, &oact, 0);
+ }
+ }
+
+ len = 0;
+ Command[sizeof(Command)-1] = '\0';
+ for (arg++; arg < argc; arg++) {
+ if (len && len < sizeof(Command)-1)
+ strcpy(Command+len++, " ");
+ strncpy(Command+len, argv[arg], sizeof(Command)-len-1);
+ len += strlen(Command+len);
+ }
+
+ switch (Receive(fd, verbose | REC_PASSWD)) {
+ case 1:
+ fprintf(stderr, "Password incorrect\n");
+ break;
+
+ case 0:
+ passwd = NULL;
+ if (len == 0) {
+ struct thread_data td;
+ const char *env;
+ int size;
+#ifndef __OpenBSD__
+ HistEvent hev = { 0, "" };
+#endif
+
+ td.hist = history_init();
+ if ((env = getenv("EL_SIZE"))) {
+ size = atoi(env);
+ if (size < 0)
+ size = 20;
+ } else
+ size = 20;
+#ifdef __OpenBSD__
+ history(td.hist, H_EVENT, size);
+ td.edit = el_init("pppctl", stdin, stdout);
+#else
+ history(td.hist, &hev, H_SETSIZE, size);
+ td.edit = el_init("pppctl", stdin, stdout, stderr);
+#endif
+ el_source(td.edit, NULL);
+ el_set(td.edit, EL_PROMPT, GetPrompt);
+ if ((env = getenv("EL_EDITOR"))) {
+ if (!strcmp(env, "vi"))
+ el_set(td.edit, EL_EDITOR, "vi");
+ else if (!strcmp(env, "emacs"))
+ el_set(td.edit, EL_EDITOR, "emacs");
+ }
+ el_set(td.edit, EL_SIGNAL, 1);
+ el_set(td.edit, EL_HIST, history, (const char *)td.hist);
+
+ td.ppp = fd;
+ td.trm = NULL;
+
+ /*
+ * We create two threads. The Terminal thread does all the
+ * work while the Monitor thread simply tells the Terminal
+ * thread when ``fd'' becomes readable. The telling is done
+ * by sending a SIGUSR1 to the Terminal thread. The
+ * sem_select semaphore is used to prevent the monitor
+ * thread from firing excessive signals at the Terminal
+ * thread (it's abused for exit handling too - see below).
+ *
+ * The Terminal thread never uses td.trm !
+ */
+ sem_init(&sem_select, 0, 0);
+
+ pthread_create(&td.trm, NULL, Terminal, &td);
+ pthread_create(&mon, NULL, Monitor, &td);
+
+ /* Wait for the terminal thread to finish */
+ pthread_join(td.trm, &thread_ret);
+ fprintf(stderr, "Connection closed\n");
+
+ /* Get rid of the monitor thread by abusing sem_select */
+ timetogo = 1;
+ close(fd);
+ fd = -1;
+ sem_post(&sem_select);
+ pthread_join(mon, &thread_ret);
+
+ /* Restore our terminal and release resources */
+ el_end(td.edit);
+ history_end(td.hist);
+ sem_destroy(&sem_select);
+ } else {
+ start = Command;
+ do {
+ next = strchr(start, ';');
+ while (*start == ' ' || *start == '\t')
+ start++;
+ if (next)
+ *next = '\0';
+ strcpy(Buffer, start);
+ Buffer[sizeof(Buffer)-2] = '\0';
+ strcat(Buffer, "\n");
+ if (verbose)
+ write(STDOUT_FILENO, Buffer, strlen(Buffer));
+ write(fd, Buffer, strlen(Buffer));
+ if (Receive(fd, verbose | REC_SHOW) != 0) {
+ fprintf(stderr, "Connection closed\n");
+ break;
+ }
+ if (next)
+ start = ++next;
+ } while (next && *next);
+ if (verbose)
+ write(STDOUT_FILENO, "quit\n", 5);
+ write(fd, "quit\n", 5);
+ while (Receive(fd, verbose | REC_SHOW) == 0)
+ ;
+ if (verbose)
+ puts("");
+ }
+ break;
+
+ default:
+ warnx("ppp is not responding");
+ break;
+ }
+
+ if (fd != -1)
+ close(fd);
+
+ return 0;
+}
diff --git a/usr.sbin/praliases/Makefile b/usr.sbin/praliases/Makefile
new file mode 100644
index 0000000..b0b3e2d
--- /dev/null
+++ b/usr.sbin/praliases/Makefile
@@ -0,0 +1,30 @@
+# @(#)Makefile 8.2 (Berkeley) 9/21/96
+# $FreeBSD$
+
+SENDMAIL_DIR= ${.CURDIR}/../../contrib/sendmail
+.PATH: ${SENDMAIL_DIR}/praliases
+
+PROG= praliases
+SRCS= praliases.c
+MAN= praliases.8
+
+CFLAGS+= -I${SENDMAIL_DIR}/src -I${SENDMAIL_DIR}/include -I.
+CFLAGS+= -DNEWDB -DNOT_SENDMAIL
+
+WARNS?= 2
+
+LIBADD= sm smdb smutil
+
+SRCS+= sm_os.h
+CLEANFILES+=sm_os.h
+
+# User customizations to the sendmail build environment
+CFLAGS+= ${SENDMAIL_CFLAGS}
+LDFLAGS+=${SENDMAIL_LDFLAGS}
+DPADD+= ${SENDMAIL_DPADD}
+LDADD+= ${SENDMAIL_LDADD}
+
+sm_os.h: ${SENDMAIL_DIR}/include/sm/os/sm_os_freebsd.h .NOMETA
+ ln -sf ${.ALLSRC} ${.TARGET}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/praliases/Makefile.depend b/usr.sbin/praliases/Makefile.depend
new file mode 100644
index 0000000..75daedb
--- /dev/null
+++ b/usr.sbin/praliases/Makefile.depend
@@ -0,0 +1,23 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libsm \
+ lib/libsmdb \
+ lib/libsmutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+praliases.o: sm_os.h
+praliases.po: sm_os.h
+.endif
diff --git a/usr.sbin/praudit/Makefile b/usr.sbin/praudit/Makefile
new file mode 100644
index 0000000..6d85fb3
--- /dev/null
+++ b/usr.sbin/praudit/Makefile
@@ -0,0 +1,15 @@
+#
+# $FreeBSD$
+#
+
+OPENBSMDIR=${.CURDIR}/../../contrib/openbsm
+.PATH: ${OPENBSMDIR}/bin/praudit
+
+PROG= praudit
+MAN= praudit.1
+
+WARNS?= 3
+
+LIBADD= bsm
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/praudit/Makefile.depend b/usr.sbin/praudit/Makefile.depend
new file mode 100644
index 0000000..97143a8
--- /dev/null
+++ b/usr.sbin/praudit/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbsm \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/procctl/Makefile b/usr.sbin/procctl/Makefile
new file mode 100644
index 0000000..880b93a
--- /dev/null
+++ b/usr.sbin/procctl/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= procctl
+MAN= procctl.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/procctl/Makefile.depend b/usr.sbin/procctl/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/procctl/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/procctl/procctl.8 b/usr.sbin/procctl/procctl.8
new file mode 100644
index 0000000..1add286
--- /dev/null
+++ b/usr.sbin/procctl/procctl.8
@@ -0,0 +1,34 @@
+.\" $FreeBSD$
+.Dd November 23, 1997
+.Dt PROCCTL 8
+.Os
+.Sh NAME
+.Nm procctl
+.Nd clear procfs event flags
+.Sh SYNOPSIS
+.Nm
+.Ar pid ...
+.Sh DESCRIPTION
+The
+.Nm
+utility clears the
+.Xr procfs 5
+event mask used by
+.Xr truss 1 .
+This can be used in the event that a process is left stranded, since
+the
+.Xr procfs 5
+events result in a non-killable process.
+The arguments are a list of process IDs;
+.Nm
+goes through the list and clears the event masks for each specified process.
+.Sh SEE ALSO
+.Xr truss 1 ,
+.Xr procfs 5
+.Sh HISTORY
+The
+.Nm
+utility was written by
+.An Sean Eric Fagan
+for
+.Fx .
diff --git a/usr.sbin/procctl/procctl.c b/usr.sbin/procctl/procctl.c
new file mode 100644
index 0000000..5bd1f21
--- /dev/null
+++ b/usr.sbin/procctl/procctl.c
@@ -0,0 +1,79 @@
+/*
+ * Copyright 1997 Sean Eric Fagan
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 Sean Eric Fagan
+ * 4. Neither the name of the author may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * procctl -- clear the event mask, and continue, any specified processes.
+ * This is largely an example of how to use the procfs interface; however,
+ * for now, it is also sometimes necessary, as a stopped process will not
+ * otherwise continue. (This will be fixed in a later version of the
+ * procfs code, almost certainly; however, this program will still be useful
+ * for some annoying circumstances.)
+ */
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/pioctl.h>
+
+int
+main(int ac, char **av) {
+ int fd;
+ int i;
+
+ for (i = 1; i < ac; i++) {
+ char buf[32];
+
+ snprintf(buf, sizeof(buf), "/proc/%s/mem", av[i]);
+ fd = open(buf, O_RDWR);
+ if (fd == -1) {
+ if (errno == ENOENT)
+ continue;
+ warn("cannot open pid %s", av[i]);
+ continue;
+ }
+ if (ioctl(fd, PIOCBIC, ~0) == -1)
+ warn("cannot clear process %s's event mask", av[i]);
+ if (ioctl(fd, PIOCCONT, 0) == -1 && errno != EINVAL)
+ warn("cannot continue process %s", av[i]);
+ close(fd);
+ }
+ return 0;
+}
diff --git a/usr.sbin/pstat/Makefile b/usr.sbin/pstat/Makefile
new file mode 100644
index 0000000..e3b0533
--- /dev/null
+++ b/usr.sbin/pstat/Makefile
@@ -0,0 +1,11 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+PROG= pstat
+LINKS= ${BINDIR}/pstat ${BINDIR}/swapinfo
+MAN= pstat.8
+MLINKS= pstat.8 swapinfo.8
+
+LIBADD= kvm util
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/pstat/Makefile.depend b/usr.sbin/pstat/Makefile.depend
new file mode 100644
index 0000000..7c356c9
--- /dev/null
+++ b/usr.sbin/pstat/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libelf \
+ lib/libkvm \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/pstat/pstat.8 b/usr.sbin/pstat/pstat.8
new file mode 100644
index 0000000..cd13994
--- /dev/null
+++ b/usr.sbin/pstat/pstat.8
@@ -0,0 +1,251 @@
+.\" Copyright (c) 1980, 1991, 1993, 1994
+.\" The Regents of the University of California. All rights reserved.
+.\" Copyright (c) 2002 Networks Associates Technology, Inc.
+.\" All rights reserved.
+.\"
+.\" Portions of this software was developed for the FreeBSD Project by
+.\" ThinkSec AS 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.
+.\" 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.
+.\"
+.\" @(#)pstat.8 8.5 (Berkeley) 5/13/94
+.\" $FreeBSD$
+.\"
+.Dd October 11, 2014
+.Dt PSTAT 8
+.Os
+.Sh NAME
+.Nm pstat ,
+.Nm swapinfo
+.Nd display system data structures
+.Sh SYNOPSIS
+.Nm
+.Op Fl Tfghkmnst
+.Op Fl M Ar core Op Fl N Ar system
+.Nm swapinfo
+.Op Fl ghkm
+.Op Fl M Ar core Op Fl N Ar system
+.Sh DESCRIPTION
+The
+.Nm
+utility displays open file entry, swap space utilization,
+terminal state, and vnode data structures.
+.Pp
+If invoked as
+.Nm swapinfo
+the
+.Fl s
+option is implied, and only the
+.Fl k , m , g ,
+and
+.Fl h
+options are legal.
+.Pp
+If the
+.Fl M
+option is not specified, information is obtained from
+the currently running kernel via the
+.Xr sysctl 3
+interface.
+Otherwise, information is read from the specified core file,
+using the name list from the specified kernel image (or from
+the default image).
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl n
+Print devices out by major/minor instead of name.
+.It Fl h
+.Dq Human-readable
+output.
+Use unit suffixes when printing swap partition sizes:
+Byte, Kilobyte, Megabyte, Gigabyte, Terabyte and Petabyte.
+.It Fl k
+Print sizes in kilobytes, regardless of the setting of the
+.Ev BLOCKSIZE
+environment variable.
+.It Fl m
+Print sizes in megabytes, regardless of the setting of the
+.Ev BLOCKSIZE
+environment variable.
+.It Fl g
+Print sizes in gigabytes, regardless of the setting of the
+.Ev BLOCKSIZE
+environment variable.
+.It Fl T
+Print the number of used and free slots in several system tables.
+This is useful for checking to see how large system tables have become
+if the system is under heavy load.
+.It Fl f
+Print the open file table with these headings:
+.Bl -tag -width indent
+.It LOC
+The core location of this table entry.
+.It TYPE
+The type of object the file table entry points to.
+.It FLG
+Miscellaneous state variables encoded thus:
+.Pp
+.Bl -tag -width indent -compact
+.It R
+open for reading
+.It W
+open for writing
+.It A
+open for appending
+.It I
+signal pgrp when data ready
+.El
+.It CNT
+Number of processes that know this open file.
+.It MSG
+Number of messages outstanding for this file.
+.It DATA
+The location of the vnode table entry or socket structure for this file.
+.It OFFSET
+The file offset (see
+.Xr lseek 2 ) .
+.El
+.It Fl s
+Print information about swap space usage on all the
+swap areas compiled into the kernel.
+The first column is the device name of the partition.
+The next column is
+the total space available in the partition.
+The
+.Ar Used
+column indicates the total blocks used so far; the
+.Ar Available
+column indicates how much space is remaining on each partition.
+The
+.Ar Capacity
+reports the percentage of space used.
+.Pp
+If more than one partition is configured into the system, totals for all
+of the statistics will be reported in the final line of the report.
+.It Fl t
+Print table for terminals
+with these headings:
+.Bl -tag -width indent
+.It LINE
+Device name.
+.It INQ
+Number of characters that can be stored in the input queue.
+.It CAN
+Number of characters in the input queue which can be read.
+.It LIN
+Number of characters in the input queue which cannot be read yet.
+.It LOW
+Low water mark for input.
+.It OUTQ
+Number of characters that can be stored in the output queue.
+.It USE
+Number of bytes in the output queue.
+.It LOW
+Low water mark for output.
+.It COL
+Calculated column position of terminal.
+.It SESS
+Process ID of the session leader.
+.It PGID
+Process group for which this is the controlling terminal.
+.It STATE
+Miscellaneous state variables encoded thus:
+.Pp
+.Bl -tag -width indent -compact
+.It I
+init/lock-state device nodes present
+.It C
+callout device nodes present
+.It O
+opened
+.It c
+console in use
+.It G
+gone
+.It B
+busy in
+.Xr open 2
+.It Y
+send SIGIO for input events
+.It L
+next character is literal
+.It H
+high watermark reached
+.It X
+open for exclusive use
+.It S
+output stopped (ixon flow control)
+.It l
+block mode input routine in use
+.It Z
+connection lost
+.It s
+i/o being snooped
+.It b
+busy in
+.Xr read 2
+or
+.Xr write 2
+.El
+.Pp
+The
+.Ql i
+and
+.Ql o
+characters refer to the previous character, to differentiate between
+input and output.
+.El
+.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 SEE ALSO
+.Xr ps 1 ,
+.Xr systat 1 ,
+.Xr stat 2 ,
+.Xr fs 5 ,
+.Xr iostat 8 ,
+.Xr vmstat 8
+.Rs
+.%T UNIX Implementation
+.%A K. Thompson
+.Re
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.0 .
+.Sh BUGS
+Does not understand
+.Tn NFS
+swap servers.
diff --git a/usr.sbin/pstat/pstat.c b/usr.sbin/pstat/pstat.c
new file mode 100644
index 0000000..2017841
--- /dev/null
+++ b/usr.sbin/pstat/pstat.c
@@ -0,0 +1,597 @@
+/*-
+ * Copyright (c) 1980, 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 2002 Networks Associates Technologies, Inc.
+ * All rights reserved.
+ *
+ * Portions of this software were developed for the FreeBSD Project by
+ * ThinkSec AS 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.
+ * 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, 1991, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)pstat.c 8.16 (Berkeley) 5/9/95";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/stdint.h>
+#include <sys/ioctl.h>
+#include <sys/tty.h>
+#include <sys/blist.h>
+
+#include <sys/sysctl.h>
+#include <vm/vm_param.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <kvm.h>
+#include <libutil.h>
+#include <limits.h>
+#include <nlist.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+enum {
+ NL_CONSTTY,
+ NL_MAXFILES,
+ NL_NFILES,
+ NL_TTY_LIST,
+ NL_MARKER
+};
+
+static struct {
+ int order;
+ const char *name;
+} namelist[] = {
+ { NL_CONSTTY, "_constty" },
+ { NL_MAXFILES, "_maxfiles" },
+ { NL_NFILES, "_openfiles" },
+ { NL_TTY_LIST, "_tty_list" },
+ { NL_MARKER, "" },
+};
+#define NNAMES (sizeof(namelist) / sizeof(*namelist))
+static struct nlist nl[NNAMES];
+
+static int humanflag;
+static int usenumflag;
+static int totalflag;
+static int swapflag;
+static char *nlistf;
+static char *memf;
+static kvm_t *kd;
+
+static const char *usagestr;
+
+static void filemode(void);
+static int getfiles(struct xfile **, size_t *);
+static void swapmode(void);
+static void ttymode(void);
+static void ttyprt(struct xtty *);
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ int ch, quit, ret;
+ int fileflag, ttyflag;
+ unsigned int i;
+ char buf[_POSIX2_LINE_MAX];
+ const char *opts;
+
+ fileflag = swapflag = ttyflag = 0;
+
+ /* We will behave like good old swapinfo if thus invoked */
+ opts = strrchr(argv[0], '/');
+ if (opts)
+ opts++;
+ else
+ opts = argv[0];
+ if (!strcmp(opts, "swapinfo")) {
+ swapflag = 1;
+ opts = "ghkmM:N:";
+ usagestr = "swapinfo [-ghkm] [-M core [-N system]]";
+ } else {
+ opts = "TM:N:fghkmnst";
+ usagestr = "pstat [-Tfghkmnst] [-M core [-N system]]";
+ }
+
+ while ((ch = getopt(argc, argv, opts)) != -1)
+ switch (ch) {
+ case 'f':
+ fileflag = 1;
+ break;
+ case 'g':
+ setenv("BLOCKSIZE", "1G", 1);
+ break;
+ case 'h':
+ humanflag = 1;
+ break;
+ case 'k':
+ setenv("BLOCKSIZE", "1K", 1);
+ break;
+ case 'm':
+ setenv("BLOCKSIZE", "1M", 1);
+ break;
+ case 'M':
+ memf = optarg;
+ break;
+ case 'N':
+ nlistf = optarg;
+ break;
+ case 'n':
+ usenumflag = 1;
+ break;
+ case 's':
+ ++swapflag;
+ break;
+ case 'T':
+ totalflag = 1;
+ break;
+ case 't':
+ ttyflag = 1;
+ break;
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ /*
+ * Initialize symbol names list.
+ */
+ for (i = 0; i < NNAMES; i++)
+ nl[namelist[i].order].n_name = strdup(namelist[i].name);
+
+ if (memf != NULL) {
+ kd = kvm_openfiles(nlistf, memf, NULL, O_RDONLY, buf);
+ if (kd == NULL)
+ errx(1, "kvm_openfiles: %s", buf);
+ if ((ret = kvm_nlist(kd, nl)) != 0) {
+ if (ret == -1)
+ errx(1, "kvm_nlist: %s", kvm_geterr(kd));
+ quit = 0;
+ for (i = 0; nl[i].n_name[0] != '\0'; ++i)
+ if (nl[i].n_value == 0) {
+ quit = 1;
+ warnx("undefined symbol: %s",
+ nl[i].n_name);
+ }
+ if (quit)
+ exit(1);
+ }
+ }
+ if (!(fileflag | ttyflag | swapflag | totalflag))
+ usage();
+ if (fileflag || totalflag)
+ filemode();
+ if (ttyflag)
+ ttymode();
+ if (swapflag || totalflag)
+ swapmode();
+ exit (0);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s\n", usagestr);
+ exit (1);
+}
+
+static const char fhdr32[] =
+ " LOC TYPE FLG CNT MSG DATA OFFSET\n";
+/* c0000000 ------ RWAI 123 123 c0000000 1000000000000000 */
+
+static const char fhdr64[] =
+ " LOC TYPE FLG CNT MSG DATA OFFSET\n";
+/* c000000000000000 ------ RWAI 123 123 c000000000000000 1000000000000000 */
+
+static const char hdr[] =
+" LINE INQ CAN LIN LOW OUTQ USE LOW COL SESS PGID STATE\n";
+
+static void
+ttymode_kvm(void)
+{
+ TAILQ_HEAD(, tty) tl;
+ struct tty *tp, tty;
+ struct xtty xt;
+
+ (void)printf("%s", hdr);
+ bzero(&xt, sizeof xt);
+ xt.xt_size = sizeof xt;
+ if (kvm_read(kd, nl[NL_TTY_LIST].n_value, &tl, sizeof tl) != sizeof tl)
+ errx(1, "kvm_read(): %s", kvm_geterr(kd));
+ tp = TAILQ_FIRST(&tl);
+ while (tp != NULL) {
+ if (kvm_read(kd, (u_long)tp, &tty, sizeof tty) != sizeof tty)
+ errx(1, "kvm_read(): %s", kvm_geterr(kd));
+ xt.xt_insize = tty.t_inq.ti_nblocks * TTYINQ_DATASIZE;
+ xt.xt_incc = tty.t_inq.ti_linestart - tty.t_inq.ti_begin;
+ xt.xt_inlc = tty.t_inq.ti_end - tty.t_inq.ti_linestart;
+ xt.xt_inlow = tty.t_inlow;
+ xt.xt_outsize = tty.t_outq.to_nblocks * TTYOUTQ_DATASIZE;
+ xt.xt_outcc = tty.t_outq.to_end - tty.t_outq.to_begin;
+ xt.xt_outlow = tty.t_outlow;
+ xt.xt_column = tty.t_column;
+ /* xt.xt_pgid = ... */
+ /* xt.xt_sid = ... */
+ xt.xt_flags = tty.t_flags;
+ xt.xt_dev = NODEV;
+ ttyprt(&xt);
+ tp = TAILQ_NEXT(&tty, t_list);
+ }
+}
+
+static void
+ttymode_sysctl(void)
+{
+ struct xtty *xttys;
+ size_t len;
+ unsigned int i, n;
+
+ (void)printf("%s", hdr);
+ if ((xttys = malloc(len = sizeof(*xttys))) == NULL)
+ err(1, "malloc()");
+ while (sysctlbyname("kern.ttys", xttys, &len, 0, 0) == -1) {
+ if (errno != ENOMEM)
+ err(1, "sysctlbyname()");
+ len *= 2;
+ if ((xttys = realloc(xttys, len)) == NULL)
+ err(1, "realloc()");
+ }
+ n = len / sizeof(*xttys);
+ for (i = 0; i < n; i++)
+ ttyprt(&xttys[i]);
+}
+
+static void
+ttymode(void)
+{
+
+ if (kd != NULL)
+ ttymode_kvm();
+ else
+ ttymode_sysctl();
+}
+
+static struct {
+ int flag;
+ char val;
+} ttystates[] = {
+#if 0
+ { TF_NOPREFIX, 'N' },
+#endif
+ { TF_INITLOCK, 'I' },
+ { TF_CALLOUT, 'C' },
+
+ /* Keep these together -> 'Oi' and 'Oo'. */
+ { TF_OPENED, 'O' },
+ { TF_OPENED_IN, 'i' },
+ { TF_OPENED_OUT, 'o' },
+ { TF_OPENED_CONS, 'c' },
+
+ { TF_GONE, 'G' },
+ { TF_OPENCLOSE, 'B' },
+ { TF_ASYNC, 'Y' },
+ { TF_LITERAL, 'L' },
+
+ /* Keep these together -> 'Hi' and 'Ho'. */
+ { TF_HIWAT, 'H' },
+ { TF_HIWAT_IN, 'i' },
+ { TF_HIWAT_OUT, 'o' },
+
+ { TF_STOPPED, 'S' },
+ { TF_EXCLUDE, 'X' },
+ { TF_BYPASS, 'l' },
+ { TF_ZOMBIE, 'Z' },
+ { TF_HOOK, 's' },
+
+ /* Keep these together -> 'bi' and 'bo'. */
+ { TF_BUSY, 'b' },
+ { TF_BUSY_IN, 'i' },
+ { TF_BUSY_OUT, 'o' },
+
+ { 0, '\0'},
+};
+
+static void
+ttyprt(struct xtty *xt)
+{
+ int i, j;
+ char *name;
+
+ if (xt->xt_size != sizeof *xt)
+ errx(1, "struct xtty size mismatch");
+ if (usenumflag || xt->xt_dev == 0 ||
+ (name = devname(xt->xt_dev, S_IFCHR)) == NULL)
+ printf("%#10jx ", (uintmax_t)xt->xt_dev);
+ else
+ printf("%10s ", name);
+ printf("%5zu %4zu %4zu %4zu %5zu %4zu %4zu %5u %5d %5d ",
+ xt->xt_insize, xt->xt_incc, xt->xt_inlc,
+ (xt->xt_insize - xt->xt_inlow), xt->xt_outsize,
+ xt->xt_outcc, (xt->xt_outsize - xt->xt_outlow),
+ MIN(xt->xt_column, 99999), xt->xt_sid, xt->xt_pgid);
+ for (i = j = 0; ttystates[i].flag; i++)
+ if (xt->xt_flags & ttystates[i].flag) {
+ putchar(ttystates[i].val);
+ j++;
+ }
+ if (j == 0)
+ putchar('-');
+ putchar('\n');
+}
+
+static void
+filemode(void)
+{
+ struct xfile *fp, *buf;
+ char flagbuf[16], *fbp;
+ int maxf, openf;
+ size_t len;
+ static char const * const dtypes[] = { "???", "inode", "socket",
+ "pipe", "fifo", "kqueue", "crypto" };
+ int i;
+ int wid;
+
+ if (kd != NULL) {
+ if (kvm_read(kd, nl[NL_MAXFILES].n_value,
+ &maxf, sizeof maxf) != sizeof maxf ||
+ kvm_read(kd, nl[NL_NFILES].n_value,
+ &openf, sizeof openf) != sizeof openf)
+ errx(1, "kvm_read(): %s", kvm_geterr(kd));
+ } else {
+ len = sizeof(int);
+ if (sysctlbyname("kern.maxfiles", &maxf, &len, 0, 0) == -1 ||
+ sysctlbyname("kern.openfiles", &openf, &len, 0, 0) == -1)
+ err(1, "sysctlbyname()");
+ }
+
+ if (totalflag) {
+ (void)printf("%3d/%3d files\n", openf, maxf);
+ return;
+ }
+ if (getfiles(&buf, &len) == -1)
+ return;
+ openf = len / sizeof *fp;
+
+ (void)printf("%d/%d open files\n", openf, maxf);
+ printf(sizeof(uintptr_t) == 4 ? fhdr32 : fhdr64);
+ wid = (int)sizeof(uintptr_t) * 2;
+ for (fp = (struct xfile *)buf, i = 0; i < openf; ++fp, ++i) {
+ if ((size_t)fp->xf_type >= sizeof(dtypes) / sizeof(dtypes[0]))
+ continue;
+ (void)printf("%*jx", wid, (uintmax_t)(uintptr_t)fp->xf_file);
+ (void)printf(" %-6.6s", dtypes[fp->xf_type]);
+ fbp = flagbuf;
+ if (fp->xf_flag & FREAD)
+ *fbp++ = 'R';
+ if (fp->xf_flag & FWRITE)
+ *fbp++ = 'W';
+ if (fp->xf_flag & FAPPEND)
+ *fbp++ = 'A';
+ if (fp->xf_flag & FASYNC)
+ *fbp++ = 'I';
+ *fbp = '\0';
+ (void)printf(" %4s %3d", flagbuf, fp->xf_count);
+ (void)printf(" %3d", fp->xf_msgcount);
+ (void)printf(" %*jx", wid, (uintmax_t)(uintptr_t)fp->xf_data);
+ (void)printf(" %*jx\n", (int)sizeof(fp->xf_offset) * 2,
+ (uintmax_t)fp->xf_offset);
+ }
+ free(buf);
+}
+
+static int
+getfiles(struct xfile **abuf, size_t *alen)
+{
+ struct xfile *buf;
+ size_t len;
+ int mib[2];
+
+ /*
+ * XXX
+ * Add emulation of KINFO_FILE here.
+ */
+ if (kd != NULL)
+ errx(1, "files on dead kernel, not implemented");
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_FILE;
+ if (sysctl(mib, 2, NULL, &len, NULL, 0) == -1) {
+ warn("sysctl: KERN_FILE");
+ return (-1);
+ }
+ if ((buf = malloc(len)) == NULL)
+ errx(1, "malloc");
+ if (sysctl(mib, 2, buf, &len, NULL, 0) == -1) {
+ warn("sysctl: KERN_FILE");
+ return (-1);
+ }
+ *abuf = buf;
+ *alen = len;
+ return (0);
+}
+
+/*
+ * swapmode is based on a program called swapinfo written
+ * by Kevin Lahey <kml@rokkaku.atl.ga.us>.
+ */
+
+#define CONVERT(v) ((int64_t)(v) * pagesize / blocksize)
+#define CONVERT_BLOCKS(v) ((int64_t)(v) * pagesize)
+static struct kvm_swap swtot;
+static int nswdev;
+
+static void
+print_swap_header(void)
+{
+ int hlen;
+ long blocksize;
+ const char *header;
+
+ header = getbsize(&hlen, &blocksize);
+ if (totalflag == 0)
+ (void)printf("%-15s %*s %8s %8s %8s\n",
+ "Device", hlen, header,
+ "Used", "Avail", "Capacity");
+}
+
+static void
+print_swap_line(const char *swdevname, intmax_t nblks, intmax_t bused,
+ intmax_t bavail, float bpercent)
+{
+ char usedbuf[5];
+ char availbuf[5];
+ int hlen, pagesize;
+ long blocksize;
+
+ pagesize = getpagesize();
+ getbsize(&hlen, &blocksize);
+
+ printf("%-15s %*jd ", swdevname, hlen, CONVERT(nblks));
+ if (humanflag) {
+ humanize_number(usedbuf, sizeof(usedbuf),
+ CONVERT_BLOCKS(bused), "",
+ HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
+ humanize_number(availbuf, sizeof(availbuf),
+ CONVERT_BLOCKS(bavail), "",
+ HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
+ printf("%8s %8s %5.0f%%\n", usedbuf, availbuf, bpercent);
+ } else {
+ printf("%8jd %8jd %5.0f%%\n", (intmax_t)CONVERT(bused),
+ (intmax_t)CONVERT(bavail), bpercent);
+ }
+}
+
+static void
+print_swap(struct kvm_swap *ksw)
+{
+
+ swtot.ksw_total += ksw->ksw_total;
+ swtot.ksw_used += ksw->ksw_used;
+ ++nswdev;
+ if (totalflag == 0)
+ print_swap_line(ksw->ksw_devname, ksw->ksw_total,
+ ksw->ksw_used, ksw->ksw_total - ksw->ksw_used,
+ (ksw->ksw_used * 100.0) / ksw->ksw_total);
+}
+
+static void
+print_swap_total(void)
+{
+ int hlen, pagesize;
+ long blocksize;
+
+ pagesize = getpagesize();
+ getbsize(&hlen, &blocksize);
+ if (totalflag) {
+ blocksize = 1024 * 1024;
+ (void)printf("%jdM/%jdM swap space\n",
+ CONVERT(swtot.ksw_used), CONVERT(swtot.ksw_total));
+ } else if (nswdev > 1) {
+ print_swap_line("Total", swtot.ksw_total, swtot.ksw_used,
+ swtot.ksw_total - swtot.ksw_used,
+ (swtot.ksw_used * 100.0) / swtot.ksw_total);
+ }
+}
+
+static void
+swapmode_kvm(void)
+{
+ struct kvm_swap kswap[16];
+ int i, n;
+
+ n = kvm_getswapinfo(kd, kswap, sizeof kswap / sizeof kswap[0],
+ SWIF_DEV_PREFIX);
+
+ print_swap_header();
+ for (i = 0; i < n; ++i)
+ print_swap(&kswap[i]);
+ print_swap_total();
+}
+
+static void
+swapmode_sysctl(void)
+{
+ struct kvm_swap ksw;
+ struct xswdev xsw;
+ size_t mibsize, size;
+ int mib[16], n;
+
+ print_swap_header();
+ mibsize = sizeof mib / sizeof mib[0];
+ if (sysctlnametomib("vm.swap_info", mib, &mibsize) == -1)
+ err(1, "sysctlnametomib()");
+ 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");
+ if (xsw.xsw_dev == NODEV)
+ snprintf(ksw.ksw_devname, sizeof ksw.ksw_devname,
+ "<NFSfile>");
+ else
+ snprintf(ksw.ksw_devname, sizeof ksw.ksw_devname,
+ "/dev/%s", devname(xsw.xsw_dev, S_IFCHR));
+ ksw.ksw_used = xsw.xsw_used;
+ ksw.ksw_total = xsw.xsw_nblks;
+ ksw.ksw_flags = xsw.xsw_flags;
+ print_swap(&ksw);
+ }
+ if (errno != ENOENT)
+ err(1, "sysctl()");
+ print_swap_total();
+}
+
+static void
+swapmode(void)
+{
+ if (kd != NULL)
+ swapmode_kvm();
+ else
+ swapmode_sysctl();
+}
diff --git a/usr.sbin/pw/Makefile b/usr.sbin/pw/Makefile
new file mode 100644
index 0000000..f26c9de
--- /dev/null
+++ b/usr.sbin/pw/Makefile
@@ -0,0 +1,19 @@
+# $FreeBSD$
+
+PROG= pw
+MAN= pw.conf.5 pw.8
+SRCS= pw.c pw_conf.c pw_user.c pw_group.c pw_log.c pw_nis.c pw_vpw.c \
+ grupd.c pwupd.c psdate.c bitmap.c cpdir.c rm_r.c strtounum.c \
+ pw_utils.c
+
+WARNS?= 3
+
+LIBADD= crypt util sbuf
+
+.include <src.opts.mk>
+
+.if ${MK_TESTS} != "no"
+SUBDIR+= tests
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/pw/Makefile.depend b/usr.sbin/pw/Makefile.depend
new file mode 100644
index 0000000..392fb60
--- /dev/null
+++ b/usr.sbin/pw/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libcrypt \
+ lib/libsbuf \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/pw/README b/usr.sbin/pw/README
new file mode 100644
index 0000000..bbb1539
--- /dev/null
+++ b/usr.sbin/pw/README
@@ -0,0 +1,22 @@
+
+pw is a command-line driven passwd/group editor utility that provides
+an easy and safe means of modifying of any/all fields in the system
+password files, and has an add, modify and delete mode for user and
+group records. Command line options have been fashioned to be similar
+to those used by the Sun/shadow commands: useradd, usermod, userdel,
+groupadd, groupmod, groupdel, but combines all operations within the
+single command `pw'.
+
+User add mode also provides a means of easily setting system useradd
+defaults (see pw.conf.5), so that adding a user is as easy as issuing
+the command "pw useradd <loginid>". Creation of a unique primary
+group for each user and automatic membership in secondary groups
+is fully supported.
+
+This program may be FreeBSD specific, but should be trivial to port to
+other bsd4.4 variants.
+
+Author and maintainer: David L. Nugent, <davidn@blaze.net.au>
+
+$FreeBSD$
+
diff --git a/usr.sbin/pw/bitmap.c b/usr.sbin/pw/bitmap.c
new file mode 100644
index 0000000..8e96bff
--- /dev/null
+++ b/usr.sbin/pw/bitmap.c
@@ -0,0 +1,131 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <stdlib.h>
+#include <string.h>
+
+#include "bitmap.h"
+
+struct bitmap
+bm_alloc(int size)
+{
+ struct bitmap bm;
+ int szmap = (size / 8) + !!(size % 8);
+
+ bm.size = size;
+ bm.map = malloc(szmap);
+ if (bm.map)
+ memset(bm.map, 0, szmap);
+ return bm;
+}
+
+void
+bm_dealloc(struct bitmap * bm)
+{
+ free(bm->map);
+}
+
+static void
+bm_getmask(int *pos, unsigned char *bmask)
+{
+ *bmask = (unsigned char) (1 << (*pos % 8));
+ *pos /= 8;
+}
+
+void
+bm_setbit(struct bitmap * bm, int pos)
+{
+ unsigned char bmask;
+
+ bm_getmask(&pos, &bmask);
+ bm->map[pos] |= bmask;
+}
+
+void
+bm_clrbit(struct bitmap * bm, int pos)
+{
+ unsigned char bmask;
+
+ bm_getmask(&pos, &bmask);
+ bm->map[pos] &= ~bmask;
+}
+
+int
+bm_isset(struct bitmap * bm, int pos)
+{
+ unsigned char bmask;
+
+ bm_getmask(&pos, &bmask);
+ return !!(bm->map[pos] & bmask);
+}
+
+int
+bm_firstunset(struct bitmap * bm)
+{
+ int szmap = (bm->size / 8) + !!(bm->size % 8);
+ int at = 0;
+ int pos = 0;
+
+ while (pos < szmap) {
+ unsigned char bmv = bm->map[pos++];
+ unsigned char bmask = 1;
+
+ while (bmask & 0xff) {
+ if ((bmv & bmask) == 0)
+ return at;
+ bmask <<= 1;
+ ++at;
+ }
+ }
+ return at;
+}
+
+int
+bm_lastset(struct bitmap * bm)
+{
+ int szmap = (bm->size / 8) + !!(bm->size % 8);
+ int at = 0;
+ int pos = 0;
+ int ofs = 0;
+
+ while (pos < szmap) {
+ unsigned char bmv = bm->map[pos++];
+ unsigned char bmask = 1;
+
+ while (bmask & 0xff) {
+ if ((bmv & bmask) != 0)
+ ofs = at;
+ bmask <<= 1;
+ ++at;
+ }
+ }
+ return ofs;
+}
diff --git a/usr.sbin/pw/bitmap.h b/usr.sbin/pw/bitmap.h
new file mode 100644
index 0000000..4d6cfe4
--- /dev/null
+++ b/usr.sbin/pw/bitmap.h
@@ -0,0 +1,50 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 _BITMAP_H_
+#define _BITMAP_H_
+
+#include <sys/cdefs.h>
+
+struct bitmap
+{
+ int size;
+ unsigned char *map;
+};
+
+__BEGIN_DECLS
+struct bitmap bm_alloc(int size);
+void bm_dealloc(struct bitmap * bm);
+void bm_setbit(struct bitmap * bm, int pos);
+void bm_clrbit(struct bitmap * bm, int pos);
+int bm_isset(struct bitmap * bm, int pos);
+int bm_firstunset(struct bitmap * bm);
+int bm_lastset(struct bitmap * bm);
+__END_DECLS
+
+#endif /* !_BITMAP_H */
diff --git a/usr.sbin/pw/cpdir.c b/usr.sbin/pw/cpdir.c
new file mode 100644
index 0000000..334f789
--- /dev/null
+++ b/usr.sbin/pw/cpdir.c
@@ -0,0 +1,124 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "pw.h"
+#include "pwupd.h"
+
+void
+copymkdir(int rootfd, char const * dir, int skelfd, mode_t mode, uid_t uid,
+ gid_t gid, int flags)
+{
+ char *p, lnk[MAXPATHLEN], copybuf[4096];
+ int len, homefd, srcfd, destfd;
+ ssize_t sz;
+ struct stat st;
+ struct dirent *e;
+ DIR *d;
+
+ if (*dir == '/')
+ dir++;
+
+ if (mkdirat(rootfd, dir, mode) != 0 && errno != EEXIST) {
+ warn("mkdir(%s)", dir);
+ return;
+ }
+ fchownat(rootfd, dir, uid, gid, AT_SYMLINK_NOFOLLOW);
+ if (flags > 0)
+ chflagsat(rootfd, dir, flags, AT_SYMLINK_NOFOLLOW);
+
+ if (skelfd == -1)
+ return;
+
+ homefd = openat(rootfd, dir, O_DIRECTORY);
+ if ((d = fdopendir(skelfd)) == NULL) {
+ close(skelfd);
+ close(homefd);
+ return;
+ }
+
+ while ((e = readdir(d)) != NULL) {
+ if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0)
+ continue;
+
+ p = e->d_name;
+ if (fstatat(skelfd, p, &st, AT_SYMLINK_NOFOLLOW) == -1)
+ continue;
+
+ if (strncmp(p, "dot.", 4) == 0) /* Conversion */
+ p += 3;
+
+ if (S_ISDIR(st.st_mode)) {
+ copymkdir(homefd, p, openat(skelfd, e->d_name, O_DIRECTORY),
+ st.st_mode & _DEF_DIRMODE, uid, gid, st.st_flags);
+ continue;
+ }
+
+ if (S_ISLNK(st.st_mode) &&
+ (len = readlinkat(skelfd, e->d_name, lnk, sizeof(lnk) -1))
+ != -1) {
+ lnk[len] = '\0';
+ symlinkat(lnk, homefd, p);
+ fchownat(homefd, p, uid, gid, AT_SYMLINK_NOFOLLOW);
+ continue;
+ }
+
+ if (!S_ISREG(st.st_mode))
+ continue;
+
+ if ((srcfd = openat(skelfd, e->d_name, O_RDONLY)) == -1)
+ continue;
+ destfd = openat(homefd, p, O_RDWR | O_CREAT | O_EXCL,
+ st.st_mode);
+ if (destfd == -1) {
+ close(srcfd);
+ continue;
+ }
+
+ while ((sz = read(srcfd, copybuf, sizeof(copybuf))) > 0)
+ write(destfd, copybuf, sz);
+
+ close(srcfd);
+ /*
+ * Propagate special filesystem flags
+ */
+ fchown(destfd, uid, gid);
+ fchflags(destfd, st.st_flags);
+ close(destfd);
+ }
+ closedir(d);
+}
diff --git a/usr.sbin/pw/grupd.c b/usr.sbin/pw/grupd.c
new file mode 100644
index 0000000..9cbe0cb
--- /dev/null
+++ b/usr.sbin/pw/grupd.c
@@ -0,0 +1,105 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <err.h>
+#include <grp.h>
+#include <libutil.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "pwupd.h"
+
+char *
+getgrpath(const char * file)
+{
+ static char pathbuf[MAXPATHLEN];
+
+ snprintf(pathbuf, sizeof pathbuf, "%s/%s", conf.etcpath, file);
+
+ return (pathbuf);
+}
+
+static int
+gr_update(struct group * grp, char const * group)
+{
+ int pfd, tfd;
+ struct group *gr = NULL;
+ struct group *old_gr = NULL;
+
+ if (grp != NULL)
+ gr = gr_dup(grp);
+
+ if (group != NULL)
+ old_gr = GETGRNAM(group);
+
+ if (gr_init(conf.etcpath, NULL))
+ err(1, "gr_init()");
+
+ if ((pfd = gr_lock()) == -1) {
+ gr_fini();
+ err(1, "gr_lock()");
+ }
+ if ((tfd = gr_tmp(-1)) == -1) {
+ gr_fini();
+ err(1, "gr_tmp()");
+ }
+ if (gr_copy(pfd, tfd, gr, old_gr) == -1) {
+ gr_fini();
+ err(1, "gr_copy()");
+ }
+ if (gr_mkdb() == -1) {
+ gr_fini();
+ err(1, "gr_mkdb()");
+ }
+ free(gr);
+ gr_fini();
+ return 0;
+}
+
+
+int
+addgrent(struct group * grp)
+{
+ return gr_update(grp, NULL);
+}
+
+int
+chggrent(char const * login, struct group * grp)
+{
+ return gr_update(grp, login);
+}
+
+int
+delgrent(struct group * grp)
+{
+
+ return (gr_update(NULL, grp->gr_name));
+}
diff --git a/usr.sbin/pw/psdate.c b/usr.sbin/pw/psdate.c
new file mode 100644
index 0000000..bd2aa15
--- /dev/null
+++ b/usr.sbin/pw/psdate.c
@@ -0,0 +1,261 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <ctype.h>
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+#include <xlocale.h>
+
+#include "psdate.h"
+
+
+static int
+numerics(char const * str)
+{
+ int rc = isdigit((unsigned char)*str);
+
+ if (rc)
+ while (isdigit((unsigned char)*str) || *str == 'x')
+ ++str;
+ return rc && !*str;
+}
+
+static int
+aindex(char const * arr[], char const ** str, int len)
+{
+ int l, i;
+ char mystr[32];
+
+ mystr[len] = '\0';
+ l = strlen(strncpy(mystr, *str, len));
+ for (i = 0; i < l; i++)
+ mystr[i] = (char) tolower((unsigned char)mystr[i]);
+ for (i = 0; arr[i] && strcmp(mystr, arr[i]) != 0; i++);
+ if (arr[i] == NULL)
+ i = -1;
+ else { /* Skip past it */
+ while (**str && isalpha((unsigned char)**str))
+ ++(*str);
+ /* And any following whitespace */
+ while (**str && (**str == ',' || isspace((unsigned char)**str)))
+ ++(*str);
+ } /* Return index */
+ return i;
+}
+
+static int
+weekday(char const ** str)
+{
+ static char const *days[] =
+ {"sun", "mon", "tue", "wed", "thu", "fri", "sat", NULL};
+
+ return aindex(days, str, 3);
+}
+
+static void
+parse_datesub(char const * str, struct tm *t)
+{
+ struct tm tm;
+ locale_t l;
+ int i;
+ char *ret;
+ const char *valid_formats[] = {
+ "%d-%b-%y",
+ "%d-%b-%Y",
+ "%d-%m-%y",
+ "%d-%m-%Y",
+ "%H:%M %d-%b-%y",
+ "%H:%M %d-%b-%Y",
+ "%H:%M %d-%m-%y",
+ "%H:%M %d-%m-%Y",
+ "%H:%M:%S %d-%b-%y",
+ "%H:%M:%S %d-%b-%Y",
+ "%H:%M:%S %d-%m-%y",
+ "%H:%M:%S %d-%m-%Y",
+ "%d-%b-%y %H:%M",
+ "%d-%b-%Y %H:%M",
+ "%d-%m-%y %H:%M",
+ "%d-%m-%Y %H:%M",
+ "%d-%b-%y %H:%M:%S",
+ "%d-%b-%Y %H:%M:%S",
+ "%d-%m-%y %H:%M:%S",
+ "%d-%m-%Y %H:%M:%S",
+ "%H:%M\t%d-%b-%y",
+ "%H:%M\t%d-%b-%Y",
+ "%H:%M\t%d-%m-%y",
+ "%H:%M\t%d-%m-%Y",
+ "%H:%M\t%S %d-%b-%y",
+ "%H:%M\t%S %d-%b-%Y",
+ "%H:%M\t%S %d-%m-%y",
+ "%H:%M\t%S %d-%m-%Y",
+ "%d-%b-%y\t%H:%M",
+ "%d-%b-%Y\t%H:%M",
+ "%d-%m-%y\t%H:%M",
+ "%d-%m-%Y\t%H:%M",
+ "%d-%b-%y\t%H:%M:%S",
+ "%d-%b-%Y\t%H:%M:%S",
+ "%d-%m-%y\t%H:%M:%S",
+ "%d-%m-%Y\t%H:%M:%S",
+ NULL,
+ };
+
+ l = newlocale(LC_ALL_MASK, "C", NULL);
+
+ memset(&tm, 0, sizeof(tm));
+ for (i=0; valid_formats[i] != NULL; i++) {
+ ret = strptime_l(str, valid_formats[i], &tm, l);
+ if (ret && *ret == '\0') {
+ t->tm_mday = tm.tm_mday;
+ t->tm_mon = tm.tm_mon;
+ t->tm_year = tm.tm_year;
+ t->tm_hour = tm.tm_hour;
+ t->tm_min = tm.tm_min;
+ t->tm_sec = tm.tm_sec;
+ freelocale(l);
+ return;
+ }
+ }
+
+ freelocale(l);
+
+ errx(EXIT_FAILURE, "Invalid date");
+}
+
+
+/*-
+ * Parse time must be flexible, it handles the following formats:
+ * nnnnnnnnnnn UNIX timestamp (all numeric), 0 = now
+ * 0xnnnnnnnn UNIX timestamp in hexadecimal
+ * 0nnnnnnnnn UNIX timestamp in octal
+ * 0 Given time
+ * +nnnn[smhdwoy] Given time + nnnn hours, mins, days, weeks, months or years
+ * -nnnn[smhdwoy] Given time - nnnn hours, mins, days, weeks, months or years
+ * dd[ ./-]mmm[ ./-]yy Date }
+ * hh:mm:ss Time } May be combined
+ */
+
+time_t
+parse_date(time_t dt, char const * str)
+{
+ char *p;
+ int i;
+ long val;
+ struct tm *T;
+
+ if (dt == 0)
+ dt = time(NULL);
+
+ while (*str && isspace((unsigned char)*str))
+ ++str;
+
+ if (numerics(str)) {
+ dt = strtol(str, &p, 0);
+ } else if (*str == '+' || *str == '-') {
+ val = strtol(str, &p, 0);
+ switch (*p) {
+ case 'h':
+ case 'H': /* hours */
+ dt += (val * 3600L);
+ break;
+ case '\0':
+ case 'm':
+ case 'M': /* minutes */
+ dt += (val * 60L);
+ break;
+ case 's':
+ case 'S': /* seconds */
+ dt += val;
+ break;
+ case 'd':
+ case 'D': /* days */
+ dt += (val * 86400L);
+ break;
+ case 'w':
+ case 'W': /* weeks */
+ dt += (val * 604800L);
+ break;
+ case 'o':
+ case 'O': /* months */
+ T = localtime(&dt);
+ T->tm_mon += (int) val;
+ i = T->tm_mday;
+ goto fixday;
+ case 'y':
+ case 'Y': /* years */
+ T = localtime(&dt);
+ T->tm_year += (int) val;
+ i = T->tm_mday;
+ fixday:
+ dt = mktime(T);
+ T = localtime(&dt);
+ if (T->tm_mday != i) {
+ T->tm_mday = 1;
+ dt = mktime(T);
+ dt -= (time_t) 86400L;
+ }
+ default: /* unknown */
+ break; /* leave untouched */
+ }
+ } else {
+ char *q, tmp[64];
+
+ /*
+ * Skip past any weekday prefix
+ */
+ weekday(&str);
+ strlcpy(tmp, str, sizeof(tmp));
+ str = tmp;
+ T = localtime(&dt);
+
+ /*
+ * See if we can break off any timezone
+ */
+ while ((q = strrchr(tmp, ' ')) != NULL) {
+ if (strchr("(+-", q[1]) != NULL)
+ *q = '\0';
+ else {
+ int j = 1;
+
+ while (q[j] && isupper((unsigned char)q[j]))
+ ++j;
+ if (q[j] == '\0')
+ *q = '\0';
+ else
+ break;
+ }
+ }
+
+ parse_datesub(tmp, T);
+ dt = mktime(T);
+ }
+ return dt;
+}
diff --git a/usr.sbin/pw/psdate.h b/usr.sbin/pw/psdate.h
new file mode 100644
index 0000000..a1e99d4
--- /dev/null
+++ b/usr.sbin/pw/psdate.h
@@ -0,0 +1,40 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 _PSDATE_H_
+#define _PSDATE_H_
+
+#include <time.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+time_t parse_date(time_t dt, char const * str);
+void print_date(char *buf, time_t t, int dotime);
+__END_DECLS
+
+#endif /* !_PSDATE_H_ */
diff --git a/usr.sbin/pw/pw.8 b/usr.sbin/pw/pw.8
new file mode 100644
index 0000000..3a9c0b0
--- /dev/null
+++ b/usr.sbin/pw/pw.8
@@ -0,0 +1,1049 @@
+.\" Copyright (C) 1996
+.\" David L. Nugent. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (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 14, 2015
+.Dt PW 8
+.Os
+.Sh NAME
+.Nm pw
+.Nd create, remove, modify & display system users and groups
+.Sh SYNOPSIS
+.Nm
+.Op Fl R Ar rootdir
+.Op Fl V Ar etcdir
+.Ar useradd
+.Oo Fl n Oc name Oo Fl u Ar uid Oc
+.Op Fl C Ar config
+.Op Fl q
+.Op Fl c Ar comment
+.Op Fl d Ar dir
+.Op Fl e Ar date
+.Op Fl p Ar date
+.Op Fl g Ar group
+.Op Fl G Ar grouplist
+.Op Fl m
+.Op Fl M Ar mode
+.Op Fl k Ar dir
+.Op Fl w Ar method
+.Op Fl s Ar shell
+.Op Fl o
+.Op Fl L Ar class
+.Op Fl h Ar fd | Fl H Ar fd
+.Op Fl N
+.Op Fl P
+.Op Fl Y
+.Nm
+.Op Fl R Ar rootdir
+.Op Fl V Ar etcdir
+.Ar useradd
+.Fl D
+.Op Fl C Ar config
+.Op Fl q
+.Op Fl b Ar dir
+.Op Fl e Ar days
+.Op Fl p Ar days
+.Op Fl g Ar group
+.Op Fl G Ar grouplist
+.Op Fl k Ar dir
+.Op Fl M Ar mode
+.Op Fl u Ar min , Ns Ar max
+.Op Fl i Ar min , Ns Ar max
+.Op Fl w Ar method
+.Op Fl s Ar shell
+.Op Fl y Ar path
+.Nm
+.Op Fl R Ar rootdir
+.Op Fl V Ar etcdir
+.Ar userdel
+.Oo Fl n Oc name|uid | Fl u Ar uid
+.Op Fl r
+.Op Fl Y
+.Nm
+.Op Fl R Ar rootdir
+.Op Fl V Ar etcdir
+.Ar usermod
+.Oo Fl n Oc name|uid Oo Fl u Ar newuid Oc | Fl u Ar uid
+.Op Fl C Ar config
+.Op Fl q
+.Op Fl c Ar comment
+.Op Fl d Ar dir
+.Op Fl e Ar date
+.Op Fl p Ar date
+.Op Fl g Ar group
+.Op Fl G Ar grouplist
+.Op Fl l Ar newname
+.Op Fl m
+.Op Fl M Ar mode
+.Op Fl k Ar dir
+.Op Fl w Ar method
+.Op Fl s Ar shell
+.Op Fl L Ar class
+.Op Fl h Ar fd | Fl H Ar fd
+.Op Fl N
+.Op Fl P
+.Op Fl Y
+.Nm
+.Op Fl R Ar rootdir
+.Op Fl V Ar etcdir
+.Ar usershow
+.Oo Fl n Oc name|uid | Fl u Ar uid
+.Op Fl F
+.Op Fl P
+.Op Fl 7
+.Op Fl a
+.Nm
+.Op Fl R Ar rootdir
+.Op Fl V Ar etcdir
+.Ar usernext
+.Op Fl C Ar config
+.Op Fl q
+.Nm
+.Op Fl R Ar rootdir
+.Op Fl V Ar etcdir
+.Ar groupadd
+.Oo Fl n Oc name Oo Fl g Ar gid Oc
+.Op Fl C Ar config
+.Op Fl q
+.Op Fl M Ar members
+.Op Fl o
+.Op Fl h Ar fd | Fl H Ar fd
+.Op Fl N
+.Op Fl P
+.Op Fl Y
+.Nm
+.Op Fl R Ar rootdir
+.Op Fl V Ar etcdir
+.Ar groupdel
+.Oo Fl n Oc name|gid | Fl g Ar gid
+.Op Fl Y
+.Nm
+.Op Fl R Ar rootdir
+.Op Fl V Ar etcdir
+.Ar groupmod
+.Oo Fl n Oc name|gid Oo Fl g Ar newgid Oc | Fl g Ar gid
+.Op Fl C Ar config
+.Op Fl q
+.Op Fl l Ar newname
+.Op Fl M Ar members
+.Op Fl m Ar newmembers
+.Op Fl d Ar oldmembers
+.Op Fl h Ar fd | Fl H Ar fd
+.Op Fl N
+.Op Fl P
+.Op Fl Y
+.Nm
+.Op Fl R Ar rootdir
+.Op Fl V Ar etcdir
+.Ar groupshow
+.Oo Fl n Oc name|gid | Fl g Ar gid
+.Op Fl F
+.Op Fl P
+.Op Fl a
+.Nm
+.Op Fl R Ar rootdir
+.Op Fl V Ar etcdir
+.Ar groupnext
+.Op Fl C Ar config
+.Op Fl q
+.Nm
+.Op Fl R Ar rootdir
+.Op Fl V Ar etcdir
+.Ar lock
+.Oo Fl n Oc name|uid | Fl u Ar uid
+.Op Fl C Ar config
+.Op Fl q
+.Nm
+.Op Fl R Ar rootdir
+.Op Fl V Ar etcdir
+.Ar unlock
+.Oo Fl n Oc name|uid | Fl u Ar uid
+.Op Fl C Ar config
+.Op Fl q
+.Sh DESCRIPTION
+The
+.Nm
+utility is a command-line based editor for the system
+.Ar user
+and
+.Ar group
+files, allowing the superuser an easy to use and standardized way of adding,
+modifying and removing users and groups.
+Note that
+.Nm
+only operates on the local user and group files.
+.Tn NIS
+users and groups must be
+maintained on the
+.Tn NIS
+server.
+The
+.Nm
+utility handles updating the
+.Pa passwd ,
+.Pa master.passwd ,
+.Pa group
+and the secure and insecure
+password database files, and must be run as root.
+.Pp
+The first one or two keywords provided to
+.Nm
+on the command line provide the context for the remainder of the arguments.
+The keywords
+.Ar user
+and
+.Ar group
+may be combined with
+.Ar add ,
+.Ar del ,
+.Ar mod ,
+.Ar show ,
+or
+.Ar next
+in any order.
+(For example,
+.Ar showuser ,
+.Ar usershow ,
+.Ar show user ,
+and
+.Ar user show
+all mean the same thing.)
+This flexibility is useful for interactive scripts calling
+.Nm
+for user and group database manipulation.
+Following these keywords,
+the user or group name or numeric id may be optionally specified as an
+alternative to using the
+.Fl n Ar name ,
+.Fl u Ar uid ,
+.Fl g Ar gid
+options.
+.Pp
+The following flags are common to most or all modes of operation:
+.Bl -tag -width "-G grouplist"
+.It Fl R Ar rootdir
+Specifies an alternate root directory within which
+.Nm
+will operate.
+Any paths specified will be relative to
+.Va rootdir .
+.It Fl V Ar etcdir
+Set an alternate location for the password, group, and configuration files.
+Can be used to maintain a user/group database in an alternate location.
+If this switch is specified, the system
+.Pa /etc/pw.conf
+will not be sourced for default configuration data,
+but the file pw.conf in the specified directory will be used instead
+.Pq or none, if it does not exist .
+The
+.Fl C
+flag may be used to override this behaviour.
+As an exception to the general rule where options must follow the operation
+type, the
+.Fl V
+flag must be used on the command line before the operation keyword.
+.It Fl C Ar config
+By default,
+.Nm
+reads the file
+.Pa /etc/pw.conf
+to obtain policy information on how new user accounts and groups are to be created.
+The
+.Fl C
+option specifies a different configuration file.
+While most of the contents of the configuration file may be overridden via
+command-line options, it may be more convenient to keep standard information in a
+configuration file.
+.It Fl q
+Use of this option causes
+.Nm
+to suppress error messages,
+which may be useful in interactive environments where it
+is preferable to interpret status codes returned by
+.Nm
+rather than messing up a carefully formatted display.
+.It Fl N
+This option is available in
+.Ar add
+and
+.Ar modify
+operations, and tells
+.Nm
+to output the result of the operation without updating the user or group
+databases.
+You may use the
+.Fl P
+option to switch between standard passwd and readable formats.
+.It Fl Y
+Using this option with any of the update modes causes
+.Nm
+to run
+.Xr make 1
+after changing to the directory
+.Pa /var/yp .
+This is intended to allow automatic updating of
+.Tn NIS
+database files.
+If separate passwd and group files are being used by
+.Tn NIS ,
+then use the
+.Fl y Ar path
+option to specify the location of the
+.Tn NIS
+passwd database so that
+.Nm
+will concurrently update it with the system password
+databases.
+.El
+.Sh USER OPTIONS
+The following options apply to the
+.Ar useradd
+and
+.Ar usermod
+commands:
+.Bl -tag -width "-G grouplist"
+.It Oo Fl n Oc Ar name
+Required unless
+.Fl u Ar uid
+is given.
+Specify the user/account name.
+In the case of
+.Ar usermod
+can be a uid.
+.It Fl u Ar uid
+Required if
+.Ar name
+is not given.
+Specify the user/account numeric id.
+In the case of
+.Ar usermod
+if paired with
+.Ar name ,
+changes the numeric id of the named user/account.
+.Pp
+Usually, only one of these options is required,
+as the account name will imply the uid, or vice versa.
+However, there are times when both are needed.
+For example, when changing the uid of an existing user with
+.Ar usermod ,
+or overriding the default uid when creating a new account with
+.Ar useradd .
+To automatically allocate the uid to a new user with
+.Ar useradd ,
+then do
+.Em not
+use the
+.Fl u
+option.
+Either the account or userid can also be provided immediately after the
+.Ar useradd ,
+.Ar userdel ,
+.Ar usermod
+or
+.Ar usershow
+keywords on the command line without using the
+.Fl n
+or
+.Fl u
+options.
+.El
+.Bl -tag -width "-G grouplist"
+.It Fl c Ar comment
+This field sets the contents of the passwd GECOS field,
+which normally contains up to four comma-separated fields containing the
+user's full name, office or location,
+and work and home phone numbers.
+These sub-fields are used by convention only, however, and are optional.
+If this field is to contain spaces,
+the comment must be enclosed in double quotes
+.Ql \&" .
+Avoid using commas in this field as these are used as sub-field separators,
+and the colon
+.Ql \&:
+character also cannot be used as this is the field separator for the passwd
+file itself.
+.It Fl d Ar dir
+This option sets the account's home directory.
+Normally,
+this is only used if the home directory is to be different from the
+default determined from
+.Pa /etc/pw.conf
+- normally
+.Pa /home
+with the account name as a subdirectory.
+.It Fl e Ar date
+Set the account's expiration date.
+Format of the date is either a UNIX time in decimal, or a date in
+.Ql dd-mmm-yy[yy]
+format, where dd is the day,
+mmm is the month, either in numeric or alphabetic format
+('Jan', 'Feb', etc) and year is either a two or four digit year.
+This option also accepts a relative date in the form
+.Ql \&+n[mhdwoy]
+where
+.Ql \&n
+is a decimal,
+octal (leading 0) or hexadecimal (leading 0x) digit followed by the
+number of Minutes, Hours, Days, Weeks, Months or Years from the current date at
+which the expiration date is to be set.
+.It Fl p Ar date
+Set the account's password expiration date.
+This field is similar to the account expiration date option, except that it
+applies to forced password changes.
+This is set in the same manner as the
+.Fl e
+option.
+.It Fl g Ar group
+Set the account's primary group to the given group.
+.Ar group
+may be defined by either its name or group number.
+.It Fl G Ar grouplist
+Set additional group memberships for an account.
+.Ar grouplist
+is a comma, space or tab-separated list of group names or group numbers.
+The user's name is added to the group lists in
+.Pa /etc/group ,
+and
+removed from any groups not specified in
+.Ar grouplist .
+Note: a user should not be added to their primary group with
+.Ar grouplist .
+Also, group membership changes do not take effect for current user login
+sessions, requiring the user to reconnect to be affected by the changes.
+.It Fl L Ar class
+This option sets the login class for the user being created.
+See
+.Xr login.conf 5
+and
+.Xr passwd 5
+for more information on user login classes.
+.It Fl m
+This option instructs
+.Nm
+to attempt to create the user's home directory.
+While primarily useful when adding a new account with
+.Ar useradd ,
+this may also be of use when moving an existing user's home directory elsewhere
+on the file system.
+The new home directory is populated with the contents of the
+.Ar skeleton
+directory, which typically contains a set of shell configuration files that the
+user may personalize to taste.
+Files in this directory are usually named
+.Pa dot . Ns Aq Ar config
+where the
+.Pa dot
+prefix will be stripped.
+When
+.Fl m
+is used on an account with
+.Ar usermod ,
+existing configuration files in the user's home directory are
+.Em not
+overwritten from the skeleton files.
+.Pp
+When a user's home directory is created,
+it will by default be a subdirectory of the
+.Ar basehome
+directory as specified by the
+.Fl b
+option (see below), bearing the name of the new account.
+This can be overridden by the
+.Fl d
+option on the command line, if desired.
+.It Fl M Ar mode
+Create the user's home directory with the specified
+.Ar mode ,
+modified by the current
+.Xr umask 2 .
+If omitted, it is derived from the parent process'
+.Xr umask 2 .
+This option is only useful in combination with the
+.Fl m
+flag.
+.It Fl k Ar dir
+Set the
+.Ar skeleton
+directory, from which basic startup and configuration files are copied when
+the user's home directory is created.
+This option only has meaning when used with the
+.Fl d
+or
+.Fl m
+flags.
+.It Fl s Ar shell
+Set or changes the user's login shell to
+.Ar shell .
+If the path to the shell program is omitted,
+.Nm
+searches the
+.Ar shellpath
+specified in
+.Pa /etc/pw.conf
+and fills it in as appropriate.
+Note that unless you have a specific reason to do so, you should avoid
+specifying the path - this will allow
+.Nm
+to validate that the program exists and is executable.
+Specifying a full path (or supplying a blank "" shell) avoids this check
+and allows for such entries as
+.Pa /nonexistent
+that should be set for accounts not intended for interactive login.
+.It Fl h Ar fd
+This option provides a special interface by which interactive scripts can
+set an account password using
+.Nm .
+Because the command line and environment are fundamentally insecure mechanisms
+by which programs can accept information,
+.Nm
+will only allow setting of account and group passwords via a file descriptor
+(usually a pipe between an interactive script and the program).
+.Ar sh ,
+.Ar bash ,
+.Ar ksh
+and
+.Ar perl
+all possess mechanisms by which this can be done.
+Alternatively,
+.Nm
+will prompt for the user's password if
+.Fl h Ar 0
+is given, nominating
+.Em stdin
+as the file descriptor on which to read the password.
+Note that this password will be read only once and is intended
+for use by a script rather than for interactive use.
+If you wish to have new password confirmation along the lines of
+.Xr passwd 1 ,
+this must be implemented as part of an interactive script that calls
+.Nm .
+.Pp
+If a value of
+.Ql \&-
+is given as the argument
+.Ar fd ,
+then the password will be set to
+.Ql \&* ,
+rendering the account inaccessible via password-based login.
+.It Fl H Ar fd
+Read an encrypted password string from the specified file descriptor.
+This is like
+.Fl h ,
+but the password should be supplied already encrypted in a form
+suitable for writing directly to the password database.
+.El
+.Pp
+It is possible to use
+.Ar useradd
+to create a new account that duplicates an existing user id.
+While this is normally considered an error and will be rejected, the
+.Fl o
+option overrides the check for duplicates and allows the duplication of
+the user id.
+This may be useful if you allow the same user to login under
+different contexts (different group allocations, different home
+directory, different shell) while providing basically the same
+permissions for access to the user's files in each account.
+.Pp
+The
+.Ar useradd
+command also has the ability to set new user and group defaults by using the
+.Fl D
+option.
+Instead of adding a new user,
+.Nm
+writes a new set of defaults to its configuration file,
+.Pa /etc/pw.conf .
+When using the
+.Fl D
+option, you must not use either
+.Fl n Ar name
+or
+.Fl u Ar uid
+or an error will result.
+Use of
+.Fl D
+changes the meaning of several command line switches in the
+.Ar useradd
+command.
+These are:
+.Bl -tag -width "-G grouplist"
+.It Fl D
+Set default values in
+.Pa /etc/pw.conf
+configuration file, or a different named configuration file if the
+.Fl C Ar config
+option is used.
+.It Fl b Ar dir
+Set the root directory in which user home directories are created.
+The default value for this is
+.Pa /home ,
+but it may be set elsewhere as desired.
+.It Fl e Ar days
+Set the default account expiration period in days.
+When
+.Fl D
+is used, the
+.Ar days
+argument is interpreted differently.
+It must be numeric and represents the number of days after creation
+that the account expires.
+A value of 0 suppresses automatic calculation of the expiry date.
+.It Fl p Ar days
+Set the default password expiration period in days.
+.It Fl g Ar group
+Set the default group for new users.
+If a blank group is specified using
+.Fl g Ar \&"" ,
+then new users will be allocated their own private primary group
+with the same name as their login name.
+If a group is supplied, either its name or uid may be given as an argument.
+.It Fl G Ar grouplist
+Set the default groups in which new users are granted membership.
+This is a separate set of groups from the primary group.
+Avoid nominating the same group as both primary and extra groups.
+In other words, these extra groups determine membership in groups
+.Em other than
+the primary group.
+.Ar grouplist
+is a comma-separated list of group names or ids, and are always
+stored in
+.Pa /etc/pw.conf
+by their symbolic names.
+.It Fl L Ar class
+This option sets the default login class for new users.
+.It Fl k Ar dir
+Set the default
+.Em skeleton
+directory,
+from which prototype shell and other initialization files are copied when
+.Nm
+creates a user's home directory.
+See description of
+.Fl k
+for naming conventions of these files.
+.It Xo
+.Fl u Ar min , Ns Ar max ,
+.Fl i Ar min , Ns Ar max
+.Xc
+Set the minimum and maximum user and group ids allocated for new
+accounts and groups created by
+.Nm .
+The default values for each is 1000 minimum and 32000 maximum.
+.Ar min
+and
+.Ar max
+are both numbers, where max must be greater than min,
+and both must be between 0 and 32767.
+In general,
+user and group ids less than 100 are reserved for use by the system,
+and numbers greater than 32000 may also be reserved for special purposes
+.Pq used by some system daemons .
+.It Fl w Ar method
+The
+.Fl w
+option selects the default method used to set passwords for newly created user
+accounts.
+.Ar method
+is one of:
+.Pp
+.Bl -tag -width random -offset indent -compact
+.It no
+disable login on newly created accounts
+.It yes
+force the password to be the account name
+.It none
+force a blank password
+.It random
+generate a random password
+.El
+.Pp
+The
+.Ql \&random
+or
+.Ql \&no
+methods are the most secure; in the former case,
+.Nm
+generates a password and prints it to stdout,
+which is suitable when users are issued passwords rather than being allowed
+to select their own
+.Pq possibly poorly chosen
+password.
+The
+.Ql \&no
+method requires that the superuser use
+.Xr passwd 1
+to render the account accessible with a password.
+.It Fl y Ar path
+This sets the pathname of the database used by
+.Tn NIS
+if you are not sharing
+the information from
+.Pa /etc/master.passwd
+directly with
+.Tn NIS .
+You should only set this option for
+.Tn NIS
+servers.
+.El
+.Pp
+The
+.Ar userdel
+command has three distinct options.
+The
+.Fl n Ar name
+and
+.Fl u Ar uid
+options have already been covered above.
+The additional option is:
+.Bl -tag -width "-G grouplist"
+.It Fl r
+This tells
+.Nm
+to remove the user's home directory and all of its contents.
+The
+.Nm
+utility errs on the side of caution when removing files from the system.
+Firstly,
+it will not do so if the uid of the account being removed is also used by
+another account on the system, and the 'home' directory in the password file is
+a valid path that commences with the character
+.Ql \&/ .
+Secondly, it will only remove files and directories that are actually owned by
+the user, or symbolic links owned by anyone under the user's home directory.
+Finally, after deleting all contents owned by the user only empty directories
+will be removed.
+If any additional cleanup work is required, this is left to the administrator.
+.El
+.Pp
+Mail spool files and crontabs are always removed when an account is deleted as
+these are unconditionally attached to the user name.
+Jobs queued for processing by
+.Ar at
+are also removed if the user's uid is unique and not also used by another
+account on the system.
+.Pp
+The
+.Ar usermod
+command adds one additional option:
+.Bl -tag -width "-G grouplist"
+.It Fl l Ar newname
+This option allows changing of an existing account name to
+.Ql \&newname .
+The new name must not already exist, and any attempt to duplicate an
+existing account name will be rejected.
+.El
+.Pp
+The
+.Ar usershow
+command allows viewing of an account in one of two formats.
+By default, the format is identical to the format used in
+.Pa /etc/master.passwd
+with the password field replaced with a
+.Ql \&* .
+If the
+.Fl P
+option is used, then
+.Nm
+outputs the account details in a more human readable form.
+If the
+.Fl 7
+option is used, the account details are shown in v7 format.
+The
+.Fl a
+option lists all users currently on file.
+Using
+.Fl F
+forces
+.Nm
+to print the details of an account even if it does not exist.
+.Pp
+The command
+.Ar usernext
+returns the next available user and group ids separated by a colon.
+This is normally of interest only to interactive scripts or front-ends
+that use
+.Nm .
+.Sh GROUP OPTIONS
+The
+.Fl C
+and
+.Fl q
+options (explained at the start of the previous section) are available
+with the group manipulation commands.
+Other common options to all group-related commands are:
+.Bl -tag -width "-m newmembers"
+.It Oo Fl n Oc Ar name
+Required unless
+.Fl g Ar gid
+is given.
+Specify the group name.
+In the case of
+.Ar groupmod
+can be a gid.
+.It Fl g Ar gid
+Required if
+.Ar name
+is not given.
+Specify the group numeric id.
+In the case of
+.Ar groupmod
+if paired with
+.Ar name ,
+changes the numeric id of the named group.
+.Pp
+As with the account name and id fields, you will usually only need
+to supply one of these, as the group name implies the uid and vice
+versa.
+You will only need to use both when setting a specific group id
+against a new group or when changing the uid of an existing group.
+.It Fl M Ar memberlist
+This option provides an alternative way to add existing users to a
+new group (in groupadd) or replace an existing membership list (in
+groupmod).
+.Ar memberlist
+is a comma separated list of valid and existing user names or uids.
+.It Fl m Ar newmembers
+Similar to
+.Fl M ,
+this option allows the
+.Em addition
+of existing users to a group without replacing the existing list of
+members.
+Login names or user ids may be used, and duplicate users are
+silently eliminated.
+.It Fl d Ar oldmembers
+Similar to
+.Fl M ,
+this option allows the
+.Em deletion
+of existing users from a group without replacing the existing list of
+members.
+Login names or user ids may be used, and duplicate users are
+silently eliminated.
+.El
+.Pp
+.Ar groupadd
+also has a
+.Fl o
+option that allows allocation of an existing group id to a new group.
+The default action is to reject an attempt to add a group,
+and this option overrides the check for duplicate group ids.
+There is rarely any need to duplicate a group id.
+.Pp
+The
+.Ar groupmod
+command adds one additional option:
+.Bl -tag -width "-m newmembers"
+.It Fl l Ar newname
+This option allows changing of an existing group name to
+.Ql \&newname .
+The new name must not already exist,
+and any attempt to duplicate an existing group
+name will be rejected.
+.El
+.Pp
+Options for
+.Ar groupshow
+are the same as for
+.Ar usershow ,
+with the
+.Fl g Ar gid
+replacing
+.Fl u Ar uid
+to specify the group id.
+The
+.Fl 7
+option does not apply to the
+.Ar groupshow
+command.
+.Pp
+The command
+.Ar groupnext
+returns the next available group id on standard output.
+.Sh USER LOCKING
+The
+.Nm
+utility
+supports a simple password locking mechanism for users; it works by
+prepending the string
+.Ql *LOCKED*
+to the beginning of the password field in
+.Pa master.passwd
+to prevent successful authentication.
+.Pp
+The
+.Ar lock
+and
+.Ar unlock
+commands take a user name or uid of the account to lock or unlock,
+respectively.
+The
+.Fl V ,
+.Fl C ,
+and
+.Fl q
+options as described above are accepted by these commands.
+.Sh NOTES
+For a summary of options available with each command, you can use
+.Dl pw [command] help
+For example,
+.Dl pw useradd help
+lists all available options for the useradd operation.
+.Pp
+The
+.Nm
+utility allows 8-bit characters in the passwd GECOS field (user's full name,
+office, work and home phone number subfields), but disallows them in
+user login and group names.
+Use 8-bit characters with caution, as connection to the Internet will
+require that your mail transport program supports 8BITMIME, and will
+convert headers containing 8-bit characters to 7-bit quoted-printable
+format.
+.Xr sendmail 8
+does support this.
+Use of 8-bit characters in the GECOS field should be used in
+conjunction with the user's default locale and character set
+and should not be implemented without their use.
+Using 8-bit characters may also affect other
+programs that transmit the contents of the GECOS field over the
+Internet, such as
+.Xr fingerd 8 ,
+and a small number of TCP/IP clients, such as IRC, where full names
+specified in the passwd file may be used by default.
+.Pp
+The
+.Nm
+utility writes a log to the
+.Pa /var/log/userlog
+file when actions such as user or group additions or deletions occur.
+The location of this logfile can be changed in
+.Xr pw.conf 5 .
+.Sh FILES
+.Bl -tag -width /etc/master.passwd.new -compact
+.It Pa /etc/master.passwd
+The user database
+.It Pa /etc/passwd
+A Version 7 format password file
+.It Pa /etc/login.conf
+The user capabilities database
+.It Pa /etc/group
+The group database
+.It Pa /etc/pw.conf
+Pw default options file
+.It Pa /var/log/userlog
+User/group modification logfile
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility returns EXIT_SUCCESS on successful operation, otherwise
+.Nm
+returns one of the
+following exit codes defined by
+.Xr sysexits 3
+as follows:
+.Bl -tag -width xxxx
+.It EX_USAGE
+.Bl -bullet -compact
+.It
+Command line syntax errors (invalid keyword, unknown option).
+.El
+.It EX_NOPERM
+.Bl -bullet -compact
+.It
+Attempting to run one of the update modes as non-root.
+.El
+.It EX_OSERR
+.Bl -bullet -compact
+.It
+Memory allocation error.
+.It
+Read error from password file descriptor.
+.El
+.It EX_DATAERR
+.Bl -bullet -compact
+.It
+Bad or invalid data provided or missing on the command line or
+via the password file descriptor.
+.It
+Attempted to remove, rename root account or change its uid.
+.El
+.It EX_OSFILE
+.Bl -bullet -compact
+.It
+Skeleton directory is invalid or does not exist.
+.It
+Base home directory is invalid or does not exist.
+.It
+Invalid or non-existent shell specified.
+.El
+.It EX_NOUSER
+.Bl -bullet -compact
+.It
+User, user id, group or group id specified does not exist.
+.It
+User or group recorded, added, or modified unexpectedly disappeared.
+.El
+.It EX_SOFTWARE
+.Bl -bullet -compact
+.It
+No more group or user ids available within specified range.
+.El
+.It EX_IOERR
+.Bl -bullet -compact
+.It
+Unable to rewrite configuration file.
+.It
+Error updating group or user database files.
+.It
+Update error for passwd or group database files.
+.El
+.It EX_CONFIG
+.Bl -bullet -compact
+.It
+No base home directory configured.
+.El
+.El
+.Sh SEE ALSO
+.Xr chpass 1 ,
+.Xr passwd 1 ,
+.Xr umask 2 ,
+.Xr group 5 ,
+.Xr login.conf 5 ,
+.Xr passwd 5 ,
+.Xr pw.conf 5 ,
+.Xr pwd_mkdb 8 ,
+.Xr vipw 8
+.Sh HISTORY
+The
+.Nm
+utility was written to mimic many of the options used in the SYSV
+.Em shadow
+support suite, but is modified for passwd and group fields specific to
+the
+.Bx 4.4
+operating system, and combines all of the major elements
+into a single command.
diff --git a/usr.sbin/pw/pw.c b/usr.sbin/pw/pw.c
new file mode 100644
index 0000000..700a7b2
--- /dev/null
+++ b/usr.sbin/pw/pw.c
@@ -0,0 +1,381 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <err.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "pw.h"
+
+const char *Modes[] = {
+ "add", "del", "mod", "show", "next",
+ NULL};
+const char *Which[] = {"user", "group", NULL};
+static const char *Combo1[] = {
+ "useradd", "userdel", "usermod", "usershow", "usernext",
+ "lock", "unlock",
+ "groupadd", "groupdel", "groupmod", "groupshow", "groupnext",
+ NULL};
+static const char *Combo2[] = {
+ "adduser", "deluser", "moduser", "showuser", "nextuser",
+ "lock", "unlock",
+ "addgroup", "delgroup", "modgroup", "showgroup", "nextgroup",
+ NULL};
+
+struct pwf PWF =
+{
+ PWF_REGULAR,
+ setpwent,
+ endpwent,
+ getpwent,
+ getpwuid,
+ getpwnam,
+ setgrent,
+ endgrent,
+ getgrent,
+ getgrgid,
+ getgrnam,
+
+};
+struct pwf VPWF =
+{
+ PWF_ALT,
+ vsetpwent,
+ vendpwent,
+ vgetpwent,
+ vgetpwuid,
+ vgetpwnam,
+ vsetgrent,
+ vendgrent,
+ vgetgrent,
+ vgetgrgid,
+ vgetgrnam,
+};
+
+static int (*cmdfunc[W_NUM][M_NUM])(int argc, char **argv, char *_name) = {
+ { /* user */
+ pw_user_add,
+ pw_user_del,
+ pw_user_mod,
+ pw_user_show,
+ pw_user_next,
+ pw_user_lock,
+ pw_user_unlock,
+ },
+ { /* group */
+ pw_group_add,
+ pw_group_del,
+ pw_group_mod,
+ pw_group_show,
+ pw_group_next,
+ }
+};
+
+struct pwconf conf;
+
+static int getindex(const char *words[], const char *word);
+static void cmdhelp(int mode, int which);
+
+int
+main(int argc, char *argv[])
+{
+ int mode = -1, which = -1, tmp;
+ struct stat st;
+ char arg, *arg1;
+ bool relocated, nis;
+
+ arg1 = NULL;
+ relocated = nis = false;
+ memset(&conf, 0, sizeof(conf));
+ strlcpy(conf.rootdir, "/", sizeof(conf.rootdir));
+ strlcpy(conf.etcpath, _PATH_PWD, sizeof(conf.etcpath));
+ conf.fd = -1;
+ conf.checkduplicate = true;
+
+ setlocale(LC_ALL, "");
+
+ /*
+ * Break off the first couple of words to determine what exactly
+ * we're being asked to do
+ */
+ while (argc > 1) {
+ if (*argv[1] == '-') {
+ /*
+ * Special case, allow pw -V<dir> <operation> [args] for scripts etc.
+ */
+ arg = argv[1][1];
+ if (arg == 'V' || arg == 'R') {
+ if (relocated)
+ errx(EXIT_FAILURE, "Both '-R' and '-V' "
+ "specified, only one accepted");
+ relocated = true;
+ optarg = &argv[1][2];
+ if (*optarg == '\0') {
+ if (stat(argv[2], &st) != 0)
+ errx(EX_OSFILE, \
+ "no such directory `%s'",
+ argv[2]);
+ if (!S_ISDIR(st.st_mode))
+ errx(EX_OSFILE, "`%s' not a "
+ "directory", argv[2]);
+ optarg = argv[2];
+ ++argv;
+ --argc;
+ }
+ memcpy(&PWF, &VPWF, sizeof PWF);
+ if (arg == 'R') {
+ strlcpy(conf.rootdir, optarg,
+ sizeof(conf.rootdir));
+ PWF._altdir = PWF_ROOTDIR;
+ }
+ snprintf(conf.etcpath, sizeof(conf.etcpath),
+ "%s%s", optarg, arg == 'R' ? "/etc" : "");
+ } else
+ break;
+ }
+ else if (mode == -1 && (tmp = getindex(Modes, argv[1])) != -1)
+ mode = tmp;
+ else if (which == -1 && (tmp = getindex(Which, argv[1])) != -1)
+ which = tmp;
+ else if ((mode == -1 && which == -1) &&
+ ((tmp = getindex(Combo1, argv[1])) != -1 ||
+ (tmp = getindex(Combo2, argv[1])) != -1)) {
+ which = tmp / M_NUM;
+ mode = tmp % M_NUM;
+ } else if (strcmp(argv[1], "help") == 0 && argv[2] == NULL)
+ cmdhelp(mode, which);
+ else if (which != -1 && mode != -1)
+ arg1 = argv[1];
+ else
+ errx(EX_USAGE, "unknown keyword `%s'", argv[1]);
+ ++argv;
+ --argc;
+ }
+
+ /*
+ * Bail out unless the user is specific!
+ */
+ if (mode == -1 || which == -1)
+ cmdhelp(mode, which);
+
+ conf.rootfd = open(conf.rootdir, O_DIRECTORY|O_CLOEXEC);
+ if (conf.rootfd == -1)
+ errx(EXIT_FAILURE, "Unable to open '%s'", conf.rootdir);
+
+ return (cmdfunc[which][mode](argc, argv, arg1));
+}
+
+
+static int
+getindex(const char *words[], const char *word)
+{
+ int i = 0;
+
+ while (words[i]) {
+ if (strcmp(words[i], word) == 0)
+ return (i);
+ i++;
+ }
+ return (-1);
+}
+
+
+/*
+ * This is probably an overkill for a cmdline help system, but it reflects
+ * the complexity of the command line.
+ */
+
+static void
+cmdhelp(int mode, int which)
+{
+ if (which == -1)
+ fprintf(stderr, "usage:\n pw [user|group|lock|unlock] [add|del|mod|show|next] [help|switches/values]\n");
+ else if (mode == -1)
+ fprintf(stderr, "usage:\n pw %s [add|del|mod|show|next] [help|switches/values]\n", Which[which]);
+ else {
+
+ /*
+ * We need to give mode specific help
+ */
+ static const char *help[W_NUM][M_NUM] =
+ {
+ {
+ "usage: pw useradd [name] [switches]\n"
+ "\t-V etcdir alternate /etc location\n"
+ "\t-R rootdir alternate root directory\n"
+ "\t-C config configuration file\n"
+ "\t-q quiet operation\n"
+ " Adding users:\n"
+ "\t-n name login name\n"
+ "\t-u uid user id\n"
+ "\t-c comment user name/comment\n"
+ "\t-d directory home directory\n"
+ "\t-e date account expiry date\n"
+ "\t-p date password expiry date\n"
+ "\t-g grp initial group\n"
+ "\t-G grp1,grp2 additional groups\n"
+ "\t-m [ -k dir ] create and set up home\n"
+ "\t-M mode home directory permissions\n"
+ "\t-s shell name of login shell\n"
+ "\t-o duplicate uid ok\n"
+ "\t-L class user class\n"
+ "\t-h fd read password on fd\n"
+ "\t-H fd read encrypted password on fd\n"
+ "\t-Y update NIS maps\n"
+ "\t-N no update\n"
+ " Setting defaults:\n"
+ "\t-V etcdir alternate /etc location\n"
+ "\t-R rootdir alternate root directory\n"
+ "\t-D set user defaults\n"
+ "\t-b dir default home root dir\n"
+ "\t-e period default expiry period\n"
+ "\t-p period default password change period\n"
+ "\t-g group default group\n"
+ "\t-G grp1,grp2 additional groups\n"
+ "\t-L class default user class\n"
+ "\t-k dir default home skeleton\n"
+ "\t-M mode home directory permissions\n"
+ "\t-u min,max set min,max uids\n"
+ "\t-i min,max set min,max gids\n"
+ "\t-w method set default password method\n"
+ "\t-s shell default shell\n"
+ "\t-y path set NIS passwd file path\n",
+ "usage: pw userdel [uid|name] [switches]\n"
+ "\t-V etcdir alternate /etc location\n"
+ "\t-R rootdir alternate root directory\n"
+ "\t-n name login name\n"
+ "\t-u uid user id\n"
+ "\t-Y update NIS maps\n"
+ "\t-y path set NIS passwd file path\n"
+ "\t-r remove home & contents\n",
+ "usage: pw usermod [uid|name] [switches]\n"
+ "\t-V etcdir alternate /etc location\n"
+ "\t-R rootdir alternate root directory\n"
+ "\t-C config configuration file\n"
+ "\t-q quiet operation\n"
+ "\t-F force add if no user\n"
+ "\t-n name login name\n"
+ "\t-u uid user id\n"
+ "\t-c comment user name/comment\n"
+ "\t-d directory home directory\n"
+ "\t-e date account expiry date\n"
+ "\t-p date password expiry date\n"
+ "\t-g grp initial group\n"
+ "\t-G grp1,grp2 additional groups\n"
+ "\t-l name new login name\n"
+ "\t-L class user class\n"
+ "\t-m [ -k dir ] create and set up home\n"
+ "\t-M mode home directory permissions\n"
+ "\t-s shell name of login shell\n"
+ "\t-w method set new password using method\n"
+ "\t-h fd read password on fd\n"
+ "\t-H fd read encrypted password on fd\n"
+ "\t-Y update NIS maps\n"
+ "\t-y path set NIS passwd file path\n"
+ "\t-N no update\n",
+ "usage: pw usershow [uid|name] [switches]\n"
+ "\t-V etcdir alternate /etc location\n"
+ "\t-R rootdir alternate root directory\n"
+ "\t-n name login name\n"
+ "\t-u uid user id\n"
+ "\t-F force print\n"
+ "\t-P prettier format\n"
+ "\t-a print all users\n"
+ "\t-7 print in v7 format\n",
+ "usage: pw usernext [switches]\n"
+ "\t-V etcdir alternate /etc location\n"
+ "\t-R rootdir alternate root directory\n"
+ "\t-C config configuration file\n"
+ "\t-q quiet operation\n",
+ "usage pw: lock [switches]\n"
+ "\t-V etcdir alternate /etc locations\n"
+ "\t-C config configuration file\n"
+ "\t-q quiet operation\n",
+ "usage pw: unlock [switches]\n"
+ "\t-V etcdir alternate /etc locations\n"
+ "\t-C config configuration file\n"
+ "\t-q quiet operation\n"
+ },
+ {
+ "usage: pw groupadd [group|gid] [switches]\n"
+ "\t-V etcdir alternate /etc location\n"
+ "\t-R rootdir alternate root directory\n"
+ "\t-C config configuration file\n"
+ "\t-q quiet operation\n"
+ "\t-n group group name\n"
+ "\t-g gid group id\n"
+ "\t-M usr1,usr2 add users as group members\n"
+ "\t-o duplicate gid ok\n"
+ "\t-Y update NIS maps\n"
+ "\t-N no update\n",
+ "usage: pw groupdel [group|gid] [switches]\n"
+ "\t-V etcdir alternate /etc location\n"
+ "\t-R rootdir alternate root directory\n"
+ "\t-n name group name\n"
+ "\t-g gid group id\n"
+ "\t-Y update NIS maps\n",
+ "usage: pw groupmod [group|gid] [switches]\n"
+ "\t-V etcdir alternate /etc location\n"
+ "\t-R rootdir alternate root directory\n"
+ "\t-C config configuration file\n"
+ "\t-q quiet operation\n"
+ "\t-F force add if not exists\n"
+ "\t-n name group name\n"
+ "\t-g gid group id\n"
+ "\t-M usr1,usr2 replaces users as group members\n"
+ "\t-m usr1,usr2 add users as group members\n"
+ "\t-d usr1,usr2 delete users as group members\n"
+ "\t-l name new group name\n"
+ "\t-Y update NIS maps\n"
+ "\t-N no update\n",
+ "usage: pw groupshow [group|gid] [switches]\n"
+ "\t-V etcdir alternate /etc location\n"
+ "\t-R rootdir alternate root directory\n"
+ "\t-n name group name\n"
+ "\t-g gid group id\n"
+ "\t-F force print\n"
+ "\t-P prettier format\n"
+ "\t-a print all accounting groups\n",
+ "usage: pw groupnext [switches]\n"
+ "\t-V etcdir alternate /etc location\n"
+ "\t-R rootdir alternate root directory\n"
+ "\t-C config configuration file\n"
+ "\t-q quiet operation\n"
+ }
+ };
+
+ fprintf(stderr, "%s", help[which][mode]);
+ }
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.sbin/pw/pw.conf.5 b/usr.sbin/pw/pw.conf.5
new file mode 100644
index 0000000..61c40e8
--- /dev/null
+++ b/usr.sbin/pw/pw.conf.5
@@ -0,0 +1,318 @@
+.\" Copyright (C) 1996
+.\" David L. Nugent. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (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, 2007
+.Dt PW.CONF 5
+.Os
+.Sh NAME
+.Nm pw.conf
+.Nd format of the pw.conf configuration file
+.Sh DESCRIPTION
+The file
+.Pa /etc/pw.conf
+contains configuration data for the
+.Xr pw 8
+utility.
+The
+.Xr pw 8
+utility is used for maintenance of the system password and group
+files, allowing users and groups to be added, deleted and changed.
+This file may be modified via the
+.Xr pw 8
+command using the
+.Ar useradd
+command and the
+.Fl D
+option, or by editing it directly with a text editor.
+.Pp
+Each line in
+.Pa /etc/pw.conf
+is treated either a comment or as configuration data;
+blank lines and lines commencing with a
+.Ql \&#
+character are considered comments, and any remaining lines are
+examined for a leading keyword, followed by corresponding data.
+.Pp
+Keywords recognized by
+.Xr pw 8
+are:
+.Bl -tag -width password_days -offset indent -compact
+.It defaultpasswd
+affect passwords generated for new users
+.It reuseuids
+reuse gaps in uid sequences
+.It reusegids
+reuse gaps in gid sequences
+.It nispasswd
+path to the
+.Tn NIS
+passwd database
+.It skeleton
+where to obtain default home contents
+.It newmail
+mail to send to new users
+.It logfile
+log user/group modifications to this file
+.It home
+root directory for home directories
+.It homemode
+permissions for home directory
+.It shellpath
+paths in which to locate shell programs
+.It shells
+list of valid shells (without path)
+.It defaultshell
+default shell (without path)
+.It defaultgroup
+default group
+.It extragroups
+add new users to this groups
+.It defaultclass
+place new users in this login class
+.It minuid
+.It maxuid
+range of valid default user ids
+.It mingid
+.It maxgid
+range of valid default group ids
+.It expire_days
+days after which account expires
+.It password_days
+days after which password expires
+.El
+.Pp
+Valid values for
+.Ar defaultpasswd
+are:
+.Bl -tag -width password_days -offset indent -compact
+.It no
+disable login on newly created accounts
+.It yes
+force the password to be the account name
+.It none
+force a blank password
+.It random
+generate a random password
+.El
+.Pp
+The second and third options are insecure and should be avoided if
+possible on a publicly accessible system.
+The first option requires that the superuser run
+.Xr passwd 1
+to set a password before the account may be used.
+This may also be useful for creating administrative accounts.
+The final option causes
+.Xr pw 8
+to respond by printing a randomly generated password on stdout.
+This is the preferred and most secure option.
+The
+.Xr pw 8
+utility also provides a method of setting a specific password for the new
+user via a filehandle (command lines are not secure).
+.Pp
+Both
+.Ar reuseuids
+and
+.Ar reusegids
+determine the method by which new user and group id numbers are
+generated.
+A
+.Ql \&yes
+in this field will cause
+.Xr pw 8
+to search for the first unused user or group id within the allowed
+range, whereas a
+.Ql \&no
+will ensure that no other existing user or group id within the range
+is numerically lower than the new one generated, and therefore avoids
+reusing gaps in the user or group id sequence that are caused by
+previous user or group deletions.
+Note that if the default group is not specified using the
+.Ar defaultgroup
+keyword,
+.Xr pw 8
+will create a new group for the user and attempt to keep the new
+user's uid and gid the same.
+If the new user's uid is currently in use as a group id, then the next
+available group id is chosen instead.
+.Pp
+On
+.Tn NIS
+servers which maintain a separate passwd database to
+.Pa /etc/master.passwd ,
+this option allows the additional file to be concurrently updated
+as user records are added, modified or removed.
+If blank or set to 'no', no additional database is updated.
+An absolute pathname must be used.
+.Pp
+The
+.Ar skeleton
+keyword nominates a directory from which the contents of a user's
+new home directory is constructed.
+This is
+.Pa /usr/share/skel
+by default.
+The
+.Xr pw 8 Ns 's
+.Fl m
+option causes the user's home directory to be created and populated
+using the files contained in the
+.Ar skeleton
+directory.
+.Pp
+To send an initial email to new users, the
+.Ar newmail
+keyword may be used to specify a path name to a file containing
+the message body of the message to be sent.
+To avoid sending mail when accounts are created, leave this entry
+blank or specify
+.Ql \&no .
+.Pp
+The
+.Ar logfile
+option allows logging of password file modifications into the
+nominated log file.
+To avoid creating or adding to such a logfile, then leave this
+field blank or specify
+.Ql \&no .
+.Pp
+The
+.Ar home
+keyword is mandatory.
+This specifies the location of the directory in which all new user
+home directories are created.
+.Pp
+The
+.Ar homemode
+keyword is optional.
+It specifies the creation mask of the user's home directory and is modified by
+.Xr umask 2 .
+.Pp
+The
+.Ar shellpath
+keyword specifies a list of directories - separated by colons
+.Ql \&:
+- which contain the programs used by the login shells.
+.Pp
+The
+.Ar shells
+keyword specifies a list of programs available for use as login
+shells.
+This list is a comma-separated list of shell names which should
+not contain a path.
+These shells must exist in one of the directories nominated by
+.Ar shellpath .
+.Pp
+The
+.Ar defaultshell
+keyword nominates which shell program to use for new users when
+none is specified on the
+.Xr pw 8
+command line.
+.Pp
+The
+.Ar defaultgroup
+keyword defines the primary group (the group id number in the
+password file) used for new accounts.
+If left blank, or the word
+.Ql \&no
+is used, then each new user will have a corresponding group of
+their own created automatically.
+This is the recommended procedure for new users as it best secures each
+user's files against interference by other users of the system
+irrespective of the
+.Em umask
+normally used by the user.
+.Pp
+The
+.Ar extragroups
+keyword provides an automatic means of placing new users into groups within
+the
+.Pa /etc/groups
+file.
+This is useful where all users share some resources, and is preferable
+to placing users into the same primary group.
+The effect of this keyword can be overridden using the
+.Fl G
+option on the
+.Xr pw 8
+command line.
+.Pp
+The
+.Ar defaultclass
+field determines the login class (See
+.Xr login.conf 5 )
+that new users will be allocated unless overwritten by
+.Xr pw 8 .
+.Pp
+The
+.Ar minuid ,
+.Ar maxuid ,
+.Ar mingid ,
+.Ar maxgid
+keywords determine the allowed ranges of automatically allocated user
+and group id numbers.
+The default values for both user and group ids are 1000 and 32000 as
+minimum and maximum respectively.
+The user and group id's actually used when creating an account with
+.Xr pw 8
+may be overridden using the
+.Fl u
+and
+.Fl g
+command line options.
+.Pp
+The
+.Ar expire_days
+and
+.Ar password_days
+are used to automatically calculate the number of days from the date
+on which an account is created when the account will expire or the
+user will be forced to change the account's password.
+A value of
+.Ql \&0
+in either field will disable the corresponding (account or password)
+expiration date.
+.Sh LIMITS
+The maximum line length of
+.Pa /etc/pw.conf
+is 1024 characters.
+Longer lines will be skipped and treated
+as comments.
+.Sh FILES
+.Bl -tag -width /etc/master.passwd -compact
+.It Pa /etc/pw.conf
+.It Pa /etc/passwd
+.It Pa /etc/master.passwd
+.It Pa /etc/group
+.El
+.Sh SEE ALSO
+.Xr passwd 1 ,
+.Xr umask 2 ,
+.Xr group 5 ,
+.Xr login.conf 5 ,
+.Xr passwd 5 ,
+.Xr pw 8
diff --git a/usr.sbin/pw/pw.h b/usr.sbin/pw/pw.h
new file mode 100644
index 0000000..b389f12
--- /dev/null
+++ b/usr.sbin/pw/pw.h
@@ -0,0 +1,106 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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/stat.h>
+
+#define _WITH_GETLINE
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "pwupd.h"
+
+enum _mode
+{
+ M_ADD,
+ M_DELETE,
+ M_UPDATE,
+ M_PRINT,
+ M_NEXT,
+ M_LOCK,
+ M_UNLOCK,
+ M_NUM
+};
+
+enum _which
+{
+ W_USER,
+ W_GROUP,
+ W_NUM
+};
+
+#define _DEF_DIRMODE (S_IRWXU | S_IRWXG | S_IRWXO)
+#define _PATH_PW_CONF "/etc/pw.conf"
+#define _UC_MAXLINE 1024
+#define _UC_MAXSHELLS 32
+
+struct userconf *get_userconfig(const char *cfg);
+struct userconf *read_userconfig(char const * file);
+int write_userconfig(struct userconf *cnf, char const * file);
+
+int pw_group_add(int argc, char **argv, char *name);
+int pw_group_del(int argc, char **argv, char *name);
+int pw_group_mod(int argc, char **argv, char *name);
+int pw_group_next(int argc, char **argv, char *name);
+int pw_group_show(int argc, char **argv, char *name);
+int pw_user_add(int argc, char **argv, char *name);
+int pw_user_add(int argc, char **argv, char *name);
+int pw_user_add(int argc, char **argv, char *name);
+int pw_user_add(int argc, char **argv, char *name);
+int pw_user_del(int argc, char **argv, char *name);
+int pw_user_lock(int argc, char **argv, char *name);
+int pw_user_mod(int argc, char **argv, char *name);
+int pw_user_next(int argc, char **argv, char *name);
+int pw_user_show(int argc, char **argv, char *name);
+int pw_user_unlock(int argc, char **argv, char *name);
+int pw_groupnext(struct userconf *cnf, bool quiet);
+char *pw_checkname(char *name, int gecos);
+uintmax_t pw_checkid(char *nptr, uintmax_t maxval);
+int pw_checkfd(char *nptr);
+
+int addnispwent(const char *path, struct passwd *pwd);
+int delnispwent(const char *path, const char *login);
+int chgnispwent(const char *path, const char *login, struct passwd *pwd);
+
+int groupadd(struct userconf *, char *name, gid_t id, char *members, int fd,
+ bool dryrun, bool pretty, bool precrypted);
+
+int nis_update(void);
+
+int boolean_val(char const * str, int dflt);
+char const *boolean_str(int val);
+char *newstr(char const * p);
+
+void pw_log(struct userconf * cnf, int mode, int which, char const * fmt,...) __printflike(4, 5);
+char *pw_pwcrypt(char *password);
+
+extern const char *Modes[];
+extern const char *Which[];
+
+uintmax_t strtounum(const char * __restrict, uintmax_t, uintmax_t,
+ const char ** __restrict);
diff --git a/usr.sbin/pw/pw_conf.c b/usr.sbin/pw/pw_conf.c
new file mode 100644
index 0000000..d30c80e
--- /dev/null
+++ b/usr.sbin/pw/pw_conf.c
@@ -0,0 +1,524 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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/sbuf.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "pw.h"
+
+#define debugging 0
+
+enum {
+ _UC_NONE,
+ _UC_DEFAULTPWD,
+ _UC_REUSEUID,
+ _UC_REUSEGID,
+ _UC_NISPASSWD,
+ _UC_DOTDIR,
+ _UC_NEWMAIL,
+ _UC_LOGFILE,
+ _UC_HOMEROOT,
+ _UC_HOMEMODE,
+ _UC_SHELLPATH,
+ _UC_SHELLS,
+ _UC_DEFAULTSHELL,
+ _UC_DEFAULTGROUP,
+ _UC_EXTRAGROUPS,
+ _UC_DEFAULTCLASS,
+ _UC_MINUID,
+ _UC_MAXUID,
+ _UC_MINGID,
+ _UC_MAXGID,
+ _UC_EXPIRE,
+ _UC_PASSWORD,
+ _UC_FIELDS
+};
+
+static char bourne_shell[] = "sh";
+
+static char *system_shells[_UC_MAXSHELLS] =
+{
+ bourne_shell,
+ "csh",
+ "tcsh"
+};
+
+static char const *booltrue[] =
+{
+ "yes", "true", "1", "on", NULL
+};
+static char const *boolfalse[] =
+{
+ "no", "false", "0", "off", NULL
+};
+
+static struct userconf config =
+{
+ 0, /* Default password for new users? (nologin) */
+ 0, /* Reuse uids? */
+ 0, /* Reuse gids? */
+ NULL, /* NIS version of the passwd file */
+ "/usr/share/skel", /* Where to obtain skeleton files */
+ NULL, /* Mail to send to new accounts */
+ "/var/log/userlog", /* Where to log changes */
+ "/home", /* Where to create home directory */
+ _DEF_DIRMODE, /* Home directory perms, modified by umask */
+ "/bin", /* Where shells are located */
+ system_shells, /* List of shells (first is default) */
+ bourne_shell, /* Default shell */
+ NULL, /* Default group name */
+ NULL, /* Default (additional) groups */
+ NULL, /* Default login class */
+ 1000, 32000, /* Allowed range of uids */
+ 1000, 32000, /* Allowed range of gids */
+ 0, /* Days until account expires */
+ 0 /* Days until password expires */
+};
+
+static char const *comments[_UC_FIELDS] =
+{
+ "#\n# pw.conf - user/group configuration defaults\n#\n",
+ "\n# Password for new users? no=nologin yes=loginid none=blank random=random\n",
+ "\n# Reuse gaps in uid sequence? (yes or no)\n",
+ "\n# Reuse gaps in gid sequence? (yes or no)\n",
+ "\n# Path to the NIS passwd file (blank or 'no' for none)\n",
+ "\n# Obtain default dotfiles from this directory\n",
+ "\n# Mail this file to new user (/etc/newuser.msg or no)\n",
+ "\n# Log add/change/remove information in this file\n",
+ "\n# Root directory in which $HOME directory is created\n",
+ "\n# Mode for the new $HOME directory, will be modified by umask\n",
+ "\n# Colon separated list of directories containing valid shells\n",
+ "\n# Comma separated list of available shells (without paths)\n",
+ "\n# Default shell (without path)\n",
+ "\n# Default group (leave blank for new group per user)\n",
+ "\n# Extra groups for new users\n",
+ "\n# Default login class for new users\n",
+ "\n# Range of valid default user ids\n",
+ NULL,
+ "\n# Range of valid default group ids\n",
+ NULL,
+ "\n# Days after which account expires (0=disabled)\n",
+ "\n# Days after which password expires (0=disabled)\n"
+};
+
+static char const *kwds[] =
+{
+ "",
+ "defaultpasswd",
+ "reuseuids",
+ "reusegids",
+ "nispasswd",
+ "skeleton",
+ "newmail",
+ "logfile",
+ "home",
+ "homemode",
+ "shellpath",
+ "shells",
+ "defaultshell",
+ "defaultgroup",
+ "extragroups",
+ "defaultclass",
+ "minuid",
+ "maxuid",
+ "mingid",
+ "maxgid",
+ "expire_days",
+ "password_days",
+ NULL
+};
+
+static char *
+unquote(char const * str)
+{
+ if (str && (*str == '"' || *str == '\'')) {
+ char *p = strchr(str + 1, *str);
+
+ if (p != NULL)
+ *p = '\0';
+ return (char *) (*++str ? str : NULL);
+ }
+ return (char *) str;
+}
+
+int
+boolean_val(char const * str, int dflt)
+{
+ if ((str = unquote(str)) != NULL) {
+ int i;
+
+ for (i = 0; booltrue[i]; i++)
+ if (strcmp(str, booltrue[i]) == 0)
+ return 1;
+ for (i = 0; boolfalse[i]; i++)
+ if (strcmp(str, boolfalse[i]) == 0)
+ return 0;
+
+ /*
+ * Special cases for defaultpassword
+ */
+ if (strcmp(str, "random") == 0)
+ return -1;
+ if (strcmp(str, "none") == 0)
+ return -2;
+ }
+ return dflt;
+}
+
+char const *
+boolean_str(int val)
+{
+ if (val == -1)
+ return "random";
+ else if (val == -2)
+ return "none";
+ else
+ return val ? booltrue[0] : boolfalse[0];
+}
+
+char *
+newstr(char const * p)
+{
+ char *q;
+
+ if ((p = unquote(p)) == NULL)
+ return (NULL);
+
+ if ((q = strdup(p)) == NULL)
+ err(1, "strdup()");
+
+ return (q);
+}
+
+struct userconf *
+read_userconfig(char const * file)
+{
+ FILE *fp;
+ char *buf, *p;
+ const char *errstr;
+ size_t linecap;
+ ssize_t linelen;
+
+ buf = NULL;
+ linecap = 0;
+
+ if (file == NULL)
+ file = _PATH_PW_CONF;
+
+ if ((fp = fopen(file, "r")) == NULL)
+ return (&config);
+
+ while ((linelen = getline(&buf, &linecap, fp)) > 0) {
+ if (*buf && (p = strtok(buf, " \t\r\n=")) != NULL && *p != '#') {
+ static char const toks[] = " \t\r\n,=";
+ char *q = strtok(NULL, toks);
+ int i = 0;
+ mode_t *modeset;
+
+ while (i < _UC_FIELDS && strcmp(p, kwds[i]) != 0)
+ ++i;
+#if debugging
+ if (i == _UC_FIELDS)
+ printf("Got unknown kwd `%s' val=`%s'\n", p, q ? q : "");
+ else
+ printf("Got kwd[%s]=%s\n", p, q);
+#endif
+ switch (i) {
+ case _UC_DEFAULTPWD:
+ config.default_password = boolean_val(q, 1);
+ break;
+ case _UC_REUSEUID:
+ config.reuse_uids = boolean_val(q, 0);
+ break;
+ case _UC_REUSEGID:
+ config.reuse_gids = boolean_val(q, 0);
+ break;
+ case _UC_NISPASSWD:
+ config.nispasswd = (q == NULL || !boolean_val(q, 1))
+ ? NULL : newstr(q);
+ break;
+ case _UC_DOTDIR:
+ config.dotdir = (q == NULL || !boolean_val(q, 1))
+ ? NULL : newstr(q);
+ break;
+ case _UC_NEWMAIL:
+ config.newmail = (q == NULL || !boolean_val(q, 1))
+ ? NULL : newstr(q);
+ break;
+ case _UC_LOGFILE:
+ config.logfile = (q == NULL || !boolean_val(q, 1))
+ ? NULL : newstr(q);
+ break;
+ case _UC_HOMEROOT:
+ config.home = (q == NULL || !boolean_val(q, 1))
+ ? "/home" : newstr(q);
+ break;
+ case _UC_HOMEMODE:
+ modeset = setmode(q);
+ config.homemode = (q == NULL || !boolean_val(q, 1))
+ ? _DEF_DIRMODE : getmode(modeset, _DEF_DIRMODE);
+ free(modeset);
+ break;
+ case _UC_SHELLPATH:
+ config.shelldir = (q == NULL || !boolean_val(q, 1))
+ ? "/bin" : newstr(q);
+ break;
+ case _UC_SHELLS:
+ for (i = 0; i < _UC_MAXSHELLS && q != NULL; i++, q = strtok(NULL, toks))
+ system_shells[i] = newstr(q);
+ if (i > 0)
+ while (i < _UC_MAXSHELLS)
+ system_shells[i++] = NULL;
+ break;
+ case _UC_DEFAULTSHELL:
+ config.shell_default = (q == NULL || !boolean_val(q, 1))
+ ? (char *) bourne_shell : newstr(q);
+ break;
+ case _UC_DEFAULTGROUP:
+ q = unquote(q);
+ config.default_group = (q == NULL || !boolean_val(q, 1) || GETGRNAM(q) == NULL)
+ ? NULL : newstr(q);
+ break;
+ case _UC_EXTRAGROUPS:
+ while ((q = strtok(NULL, toks)) != NULL) {
+ if (config.groups == NULL)
+ config.groups = sl_init();
+ sl_add(config.groups, newstr(q));
+ }
+ break;
+ case _UC_DEFAULTCLASS:
+ config.default_class = (q == NULL || !boolean_val(q, 1))
+ ? NULL : newstr(q);
+ break;
+ case _UC_MINUID:
+ if ((q = unquote(q)) != NULL) {
+ config.min_uid = strtounum(q, 0,
+ UID_MAX, &errstr);
+ if (errstr)
+ warnx("Invalid min_uid: '%s';"
+ " ignoring", q);
+ }
+ break;
+ case _UC_MAXUID:
+ if ((q = unquote(q)) != NULL) {
+ config.max_uid = strtounum(q, 0,
+ UID_MAX, &errstr);
+ if (errstr)
+ warnx("Invalid max_uid: '%s';"
+ " ignoring", q);
+ }
+ break;
+ case _UC_MINGID:
+ if ((q = unquote(q)) != NULL) {
+ config.min_gid = strtounum(q, 0,
+ GID_MAX, &errstr);
+ if (errstr)
+ warnx("Invalid min_gid: '%s';"
+ " ignoring", q);
+ }
+ break;
+ case _UC_MAXGID:
+ if ((q = unquote(q)) != NULL) {
+ config.max_gid = strtounum(q, 0,
+ GID_MAX, &errstr);
+ if (errstr)
+ warnx("Invalid max_gid: '%s';"
+ " ignoring", q);
+ }
+ break;
+ case _UC_EXPIRE:
+ if ((q = unquote(q)) != NULL) {
+ config.expire_days = strtonum(q, 0,
+ INT_MAX, &errstr);
+ if (errstr)
+ warnx("Invalid expire days:"
+ " '%s'; ignoring", q);
+ }
+ break;
+ case _UC_PASSWORD:
+ if ((q = unquote(q)) != NULL) {
+ config.password_days = strtonum(q, 0,
+ INT_MAX, &errstr);
+ if (errstr)
+ warnx("Invalid password days:"
+ " '%s'; ignoring", q);
+ }
+ break;
+ case _UC_FIELDS:
+ case _UC_NONE:
+ break;
+ }
+ }
+ }
+ free(buf);
+ fclose(fp);
+
+ return (&config);
+}
+
+
+int
+write_userconfig(struct userconf *cnf, const char *file)
+{
+ int fd;
+ int i, j;
+ struct sbuf *buf;
+ FILE *fp;
+
+ if (file == NULL)
+ file = _PATH_PW_CONF;
+
+ if ((fd = open(file, O_CREAT|O_RDWR|O_TRUNC|O_EXLOCK, 0644)) == -1)
+ return (0);
+
+ if ((fp = fdopen(fd, "w")) == NULL) {
+ close(fd);
+ return (0);
+ }
+
+ buf = sbuf_new_auto();
+ for (i = _UC_NONE; i < _UC_FIELDS; i++) {
+ int quote = 1;
+
+ sbuf_clear(buf);
+ switch (i) {
+ case _UC_DEFAULTPWD:
+ sbuf_cat(buf, boolean_str(cnf->default_password));
+ break;
+ case _UC_REUSEUID:
+ sbuf_cat(buf, boolean_str(cnf->reuse_uids));
+ break;
+ case _UC_REUSEGID:
+ sbuf_cat(buf, boolean_str(cnf->reuse_gids));
+ break;
+ case _UC_NISPASSWD:
+ sbuf_cat(buf, cnf->nispasswd ? cnf->nispasswd : "");
+ quote = 0;
+ break;
+ case _UC_DOTDIR:
+ sbuf_cat(buf, cnf->dotdir ? cnf->dotdir :
+ boolean_str(0));
+ break;
+ case _UC_NEWMAIL:
+ sbuf_cat(buf, cnf->newmail ? cnf->newmail :
+ boolean_str(0));
+ break;
+ case _UC_LOGFILE:
+ sbuf_cat(buf, cnf->logfile ? cnf->logfile :
+ boolean_str(0));
+ break;
+ case _UC_HOMEROOT:
+ sbuf_cat(buf, cnf->home);
+ break;
+ case _UC_HOMEMODE:
+ sbuf_printf(buf, "%04o", cnf->homemode);
+ quote = 0;
+ break;
+ case _UC_SHELLPATH:
+ sbuf_cat(buf, cnf->shelldir);
+ break;
+ case _UC_SHELLS:
+ for (j = 0; j < _UC_MAXSHELLS &&
+ system_shells[j] != NULL; j++)
+ sbuf_printf(buf, "%s\"%s\"", j ?
+ "," : "", system_shells[j]);
+ quote = 0;
+ break;
+ case _UC_DEFAULTSHELL:
+ sbuf_cat(buf, cnf->shell_default ?
+ cnf->shell_default : bourne_shell);
+ break;
+ case _UC_DEFAULTGROUP:
+ sbuf_cat(buf, cnf->default_group ?
+ cnf->default_group : "");
+ break;
+ case _UC_EXTRAGROUPS:
+ for (j = 0; cnf->groups != NULL &&
+ j < (int)cnf->groups->sl_cur; j++)
+ sbuf_printf(buf, "%s\"%s\"", j ?
+ "," : "", cnf->groups->sl_str[j]);
+ quote = 0;
+ break;
+ case _UC_DEFAULTCLASS:
+ sbuf_cat(buf, cnf->default_class ?
+ cnf->default_class : "");
+ break;
+ case _UC_MINUID:
+ sbuf_printf(buf, "%ju", (uintmax_t)cnf->min_uid);
+ quote = 0;
+ break;
+ case _UC_MAXUID:
+ sbuf_printf(buf, "%ju", (uintmax_t)cnf->max_uid);
+ quote = 0;
+ break;
+ case _UC_MINGID:
+ sbuf_printf(buf, "%ju", (uintmax_t)cnf->min_gid);
+ quote = 0;
+ break;
+ case _UC_MAXGID:
+ sbuf_printf(buf, "%ju", (uintmax_t)cnf->max_gid);
+ quote = 0;
+ break;
+ case _UC_EXPIRE:
+ sbuf_printf(buf, "%jd", (intmax_t)cnf->expire_days);
+ quote = 0;
+ break;
+ case _UC_PASSWORD:
+ sbuf_printf(buf, "%jd", (intmax_t)cnf->password_days);
+ quote = 0;
+ break;
+ case _UC_NONE:
+ break;
+ }
+ sbuf_finish(buf);
+
+ if (comments[i])
+ fputs(comments[i], fp);
+
+ if (*kwds[i]) {
+ if (quote)
+ fprintf(fp, "%s = \"%s\"\n", kwds[i],
+ sbuf_data(buf));
+ else
+ fprintf(fp, "%s = %s\n", kwds[i], sbuf_data(buf));
+#if debugging
+ printf("WROTE: %s = %s\n", kwds[i], sbuf_data(buf));
+#endif
+ }
+ }
+ sbuf_delete(buf);
+ return (fclose(fp) != EOF);
+}
diff --git a/usr.sbin/pw/pw_group.c b/usr.sbin/pw/pw_group.c
new file mode 100644
index 0000000..289a4c8
--- /dev/null
+++ b/usr.sbin/pw/pw_group.c
@@ -0,0 +1,694 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <ctype.h>
+#include <err.h>
+#include <grp.h>
+#include <libutil.h>
+#include <paths.h>
+#include <string.h>
+#include <sysexits.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "pw.h"
+#include "bitmap.h"
+
+static struct passwd *lookup_pwent(const char *user);
+static void delete_members(struct group *grp, char *list);
+static int print_group(struct group * grp, bool pretty);
+static gid_t gr_gidpolicy(struct userconf * cnf, intmax_t id);
+
+static void
+grp_set_passwd(struct group *grp, bool update, int fd, bool precrypted)
+{
+ int b;
+ int istty;
+ struct termios t, n;
+ char *p, line[256];
+
+ if (fd == -1)
+ return;
+
+ if (fd == '-') {
+ grp->gr_passwd = "*"; /* No access */
+ return;
+ }
+
+ if ((istty = isatty(fd))) {
+ n = t;
+ /* Disable echo */
+ n.c_lflag &= ~(ECHO);
+ tcsetattr(fd, TCSANOW, &n);
+ printf("%sassword for group %s:", update ? "New p" : "P",
+ grp->gr_name);
+ fflush(stdout);
+ }
+ b = read(fd, line, sizeof(line) - 1);
+ if (istty) { /* Restore state */
+ tcsetattr(fd, TCSANOW, &t);
+ fputc('\n', stdout);
+ fflush(stdout);
+ }
+ if (b < 0)
+ err(EX_OSERR, "-h file descriptor");
+ line[b] = '\0';
+ if ((p = strpbrk(line, " \t\r\n")) != NULL)
+ *p = '\0';
+ if (!*line)
+ errx(EX_DATAERR, "empty password read on file descriptor %d",
+ conf.fd);
+ if (precrypted) {
+ if (strchr(line, ':') != 0)
+ errx(EX_DATAERR, "wrong encrypted passwrd");
+ grp->gr_passwd = line;
+ } else
+ grp->gr_passwd = pw_pwcrypt(line);
+}
+
+int
+pw_groupnext(struct userconf *cnf, bool quiet)
+{
+ gid_t next = gr_gidpolicy(cnf, -1);
+
+ if (quiet)
+ return (next);
+ printf("%ju\n", (uintmax_t)next);
+
+ return (EXIT_SUCCESS);
+}
+
+static struct group *
+getgroup(char *name, intmax_t id, bool fatal)
+{
+ struct group *grp;
+
+ if (id < 0 && name == NULL)
+ errx(EX_DATAERR, "groupname or id required");
+ grp = (name != NULL) ? GETGRNAM(name) : GETGRGID(id);
+ if (grp == NULL) {
+ if (!fatal)
+ return (NULL);
+ if (name == NULL)
+ errx(EX_DATAERR, "unknown gid `%ju'", id);
+ errx(EX_DATAERR, "unknown group `%s'", name);
+ }
+ return (grp);
+}
+
+/*
+ * Lookup a passwd entry using a name or UID.
+ */
+static struct passwd *
+lookup_pwent(const char *user)
+{
+ struct passwd *pwd;
+
+ if ((pwd = GETPWNAM(user)) == NULL &&
+ (!isdigit((unsigned char)*user) ||
+ (pwd = getpwuid((uid_t) atoi(user))) == NULL))
+ errx(EX_NOUSER, "user `%s' does not exist", user);
+
+ return (pwd);
+}
+
+
+/*
+ * Delete requested members from a group.
+ */
+static void
+delete_members(struct group *grp, char *list)
+{
+ char *p;
+ int k;
+
+ if (grp->gr_mem == NULL)
+ return;
+
+ for (p = strtok(list, ", \t"); p != NULL; p = strtok(NULL, ", \t")) {
+ for (k = 0; grp->gr_mem[k] != NULL; k++) {
+ if (strcmp(grp->gr_mem[k], p) == 0)
+ break;
+ }
+ if (grp->gr_mem[k] == NULL) /* No match */
+ continue;
+
+ for (; grp->gr_mem[k] != NULL; k++)
+ grp->gr_mem[k] = grp->gr_mem[k+1];
+ }
+}
+
+static gid_t
+gr_gidpolicy(struct userconf * cnf, intmax_t id)
+{
+ struct group *grp;
+ struct bitmap bm;
+ gid_t gid = (gid_t) - 1;
+
+ /*
+ * Check the given gid, if any
+ */
+ if (id > 0) {
+ gid = (gid_t) id;
+
+ if ((grp = GETGRGID(gid)) != NULL && conf.checkduplicate)
+ errx(EX_DATAERR, "gid `%ju' has already been allocated",
+ (uintmax_t)grp->gr_gid);
+ return (gid);
+ }
+
+ /*
+ * We need to allocate the next available gid under one of
+ * two policies a) Grab the first unused gid b) Grab the
+ * highest possible unused gid
+ */
+ if (cnf->min_gid >= cnf->max_gid) { /* Sanity claus^H^H^H^Hheck */
+ cnf->min_gid = 1000;
+ cnf->max_gid = 32000;
+ }
+ bm = bm_alloc(cnf->max_gid - cnf->min_gid + 1);
+
+ /*
+ * Now, let's fill the bitmap from the password file
+ */
+ SETGRENT();
+ while ((grp = GETGRENT()) != NULL)
+ if ((gid_t)grp->gr_gid >= (gid_t)cnf->min_gid &&
+ (gid_t)grp->gr_gid <= (gid_t)cnf->max_gid)
+ bm_setbit(&bm, grp->gr_gid - cnf->min_gid);
+ ENDGRENT();
+
+ /*
+ * Then apply the policy, with fallback to reuse if necessary
+ */
+ if (cnf->reuse_gids)
+ gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
+ else {
+ gid = (gid_t) (bm_lastset(&bm) + 1);
+ if (!bm_isset(&bm, gid))
+ gid += cnf->min_gid;
+ else
+ gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
+ }
+
+ /*
+ * Another sanity check
+ */
+ if (gid < cnf->min_gid || gid > cnf->max_gid)
+ errx(EX_SOFTWARE, "unable to allocate a new gid - range fully "
+ "used");
+ bm_dealloc(&bm);
+ return (gid);
+}
+
+static int
+print_group(struct group * grp, bool pretty)
+{
+ char *buf = NULL;
+ int i;
+
+ if (pretty) {
+ printf("Group Name: %-15s #%lu\n"
+ " Members: ",
+ grp->gr_name, (long) grp->gr_gid);
+ if (grp->gr_mem != NULL) {
+ for (i = 0; grp->gr_mem[i]; i++)
+ printf("%s%s", i ? "," : "", grp->gr_mem[i]);
+ }
+ fputs("\n\n", stdout);
+ return (EXIT_SUCCESS);
+ }
+
+ buf = gr_make(grp);
+ printf("%s\n", buf);
+ free(buf);
+ return (EXIT_SUCCESS);
+}
+
+int
+pw_group_next(int argc, char **argv, char *arg1 __unused)
+{
+ struct userconf *cnf;
+ const char *cfg = NULL;
+ int ch;
+ bool quiet = false;
+
+ while ((ch = getopt(argc, argv, "Cq")) != -1) {
+ switch (ch) {
+ case 'C':
+ cfg = optarg;
+ break;
+ case 'q':
+ quiet = true;
+ break;
+ }
+ }
+
+ if (quiet)
+ freopen(_PATH_DEVNULL, "w", stderr);
+ cnf = get_userconfig(cfg);
+ return (pw_groupnext(cnf, quiet));
+}
+
+int
+pw_group_show(int argc, char **argv, char *arg1)
+{
+ struct group *grp = NULL;
+ char *name;
+ intmax_t id = -1;
+ int ch;
+ bool all, force, quiet, pretty;
+
+ all = force = quiet = pretty = false;
+
+ struct group fakegroup = {
+ "nogroup",
+ "*",
+ -1,
+ NULL
+ };
+
+ if (arg1 != NULL) {
+ if (arg1[strspn(arg1, "0123456789")] == '\0')
+ id = pw_checkid(arg1, GID_MAX);
+ else
+ name = arg1;
+ }
+
+ while ((ch = getopt(argc, argv, "C:qn:g:FPa")) != -1) {
+ switch (ch) {
+ case 'C':
+ /* ignore compatibility */
+ break;
+ case 'q':
+ quiet = true;
+ break;
+ case 'n':
+ name = optarg;
+ break;
+ case 'g':
+ id = pw_checkid(optarg, GID_MAX);
+ break;
+ case 'F':
+ force = true;
+ break;
+ case 'P':
+ pretty = true;
+ break;
+ case 'a':
+ all = true;
+ break;
+ }
+ }
+
+ if (quiet)
+ freopen(_PATH_DEVNULL, "w", stderr);
+
+ if (all) {
+ SETGRENT();
+ while ((grp = GETGRENT()) != NULL)
+ print_group(grp, pretty);
+ ENDGRENT();
+ return (EXIT_SUCCESS);
+ }
+
+ grp = getgroup(name, id, !force);
+ if (grp == NULL)
+ grp = &fakegroup;
+
+ return (print_group(grp, pretty));
+}
+
+int
+pw_group_del(int argc, char **argv, char *arg1)
+{
+ struct userconf *cnf = NULL;
+ struct group *grp = NULL;
+ char *name;
+ const char *cfg = NULL;
+ intmax_t id = -1;
+ int ch, rc;
+ bool quiet = false;
+ bool nis = false;
+
+ if (arg1 != NULL) {
+ if (arg1[strspn(arg1, "0123456789")] == '\0')
+ id = pw_checkid(arg1, GID_MAX);
+ else
+ name = arg1;
+ }
+
+ while ((ch = getopt(argc, argv, "C:qn:g:Y")) != -1) {
+ switch (ch) {
+ case 'C':
+ cfg = optarg;
+ break;
+ case 'q':
+ quiet = true;
+ break;
+ case 'n':
+ name = optarg;
+ break;
+ case 'g':
+ id = pw_checkid(optarg, GID_MAX);
+ break;
+ case 'Y':
+ nis = true;
+ break;
+ }
+ }
+
+ if (quiet)
+ freopen(_PATH_DEVNULL, "w", stderr);
+ grp = getgroup(name, id, true);
+ cnf = get_userconfig(cfg);
+ rc = delgrent(grp);
+ if (rc == -1)
+ err(EX_IOERR, "group '%s' not available (NIS?)", name);
+ else if (rc != 0)
+ err(EX_IOERR, "group update");
+ pw_log(cnf, M_DELETE, W_GROUP, "%s(%ju) removed", name,
+ (uintmax_t)id);
+
+ if (nis && nis_update() == 0)
+ pw_log(cnf, M_DELETE, W_GROUP, "NIS maps updated");
+
+ return (EXIT_SUCCESS);
+}
+
+static bool
+grp_has_member(struct group *grp, const char *name)
+{
+ int j;
+
+ for (j = 0; grp->gr_mem != NULL && grp->gr_mem[j] != NULL; j++)
+ if (strcmp(grp->gr_mem[j], name) == 0)
+ return (true);
+ return (false);
+}
+
+static void
+grp_add_members(struct group **grp, char *members)
+{
+ struct passwd *pwd;
+ char *p;
+ char tok[] = ", \t";
+
+ if (members == NULL)
+ return;
+ for (p = strtok(members, tok); p != NULL; p = strtok(NULL, tok)) {
+ pwd = lookup_pwent(p);
+ if (grp_has_member(*grp, pwd->pw_name))
+ continue;
+ *grp = gr_add(*grp, pwd->pw_name);
+ }
+}
+
+int
+groupadd(struct userconf *cnf, char *name, gid_t id, char *members, int fd,
+ bool dryrun, bool pretty, bool precrypted)
+{
+ struct group *grp;
+ int rc;
+
+ struct group fakegroup = {
+ "nogroup",
+ "*",
+ -1,
+ NULL
+ };
+
+ grp = &fakegroup;
+ grp->gr_name = pw_checkname(name, 0);
+ grp->gr_passwd = "*";
+ grp->gr_gid = gr_gidpolicy(cnf, id);
+ grp->gr_mem = NULL;
+
+ /*
+ * This allows us to set a group password Group passwords is an
+ * antique idea, rarely used and insecure (no secure database) Should
+ * be discouraged, but it is apparently still supported by some
+ * software.
+ */
+ grp_set_passwd(grp, false, fd, precrypted);
+ grp_add_members(&grp, members);
+ if (dryrun)
+ return (print_group(grp, pretty));
+
+ if ((rc = addgrent(grp)) != 0) {
+ if (rc == -1)
+ errx(EX_IOERR, "group '%s' already exists",
+ grp->gr_name);
+ else
+ err(EX_IOERR, "group update");
+ }
+
+ pw_log(cnf, M_ADD, W_GROUP, "%s(%ju)", grp->gr_name,
+ (uintmax_t)grp->gr_gid);
+
+ return (EXIT_SUCCESS);
+}
+
+int
+pw_group_add(int argc, char **argv, char *arg1)
+{
+ struct userconf *cnf = NULL;
+ char *name = NULL;
+ char *members = NULL;
+ const char *cfg = NULL;
+ intmax_t id = -1;
+ int ch, rc, fd = -1;
+ bool quiet, precrypted, dryrun, pretty, nis;
+
+ quiet = precrypted = dryrun = pretty = nis = false;
+
+ if (arg1 != NULL) {
+ if (arg1[strspn(arg1, "0123456789")] == '\0')
+ id = pw_checkid(arg1, GID_MAX);
+ else
+ name = arg1;
+ }
+
+ while ((ch = getopt(argc, argv, "C:qn:g:h:H:M:oNPY")) != -1) {
+ switch (ch) {
+ case 'C':
+ cfg = optarg;
+ break;
+ case 'q':
+ quiet = true;
+ break;
+ case 'n':
+ name = optarg;
+ break;
+ case 'g':
+ id = pw_checkid(optarg, GID_MAX);
+ break;
+ case 'H':
+ if (fd != -1)
+ errx(EX_USAGE, "'-h' and '-H' are mutually "
+ "exclusive options");
+ fd = pw_checkfd(optarg);
+ precrypted = true;
+ if (fd == '-')
+ errx(EX_USAGE, "-H expects a file descriptor");
+ break;
+ case 'h':
+ if (fd != -1)
+ errx(EX_USAGE, "'-h' and '-H' are mutually "
+ "exclusive options");
+ fd = pw_checkfd(optarg);
+ break;
+ case 'M':
+ members = optarg;
+ break;
+ case 'o':
+ conf.checkduplicate = false;
+ break;
+ case 'N':
+ dryrun = true;
+ break;
+ case 'P':
+ pretty = true;
+ break;
+ case 'Y':
+ nis = true;
+ break;
+ }
+ }
+
+ if (quiet)
+ freopen(_PATH_DEVNULL, "w", stderr);
+ if (name == NULL)
+ errx(EX_DATAERR, "group name required");
+ if (GETGRNAM(name) != NULL)
+ errx(EX_DATAERR, "group name `%s' already exists", name);
+ cnf = get_userconfig(cfg);
+ rc = groupadd(cnf, name, gr_gidpolicy(cnf, id), members, fd, dryrun,
+ pretty, precrypted);
+ if (nis && rc == EXIT_SUCCESS && nis_update() == 0)
+ pw_log(cnf, M_ADD, W_GROUP, "NIS maps updated");
+
+ return (rc);
+}
+
+int
+pw_group_mod(int argc, char **argv, char *arg1)
+{
+ struct userconf *cnf;
+ struct group *grp = NULL;
+ const char *cfg = NULL;
+ char *oldmembers = NULL;
+ char *members = NULL;
+ char *newmembers = NULL;
+ char *newname = NULL;
+ char *name = NULL;
+ intmax_t id = -1;
+ int ch, rc, fd = -1;
+ bool quiet, pretty, dryrun, nis, precrypted;
+
+ quiet = pretty = dryrun = nis = precrypted = false;
+
+ if (arg1 != NULL) {
+ if (arg1[strspn(arg1, "0123456789")] == '\0')
+ id = pw_checkid(arg1, GID_MAX);
+ else
+ name = arg1;
+ }
+
+ while ((ch = getopt(argc, argv, "C:qn:d:g:l:h:H:M:m:NPY")) != -1) {
+ switch (ch) {
+ case 'C':
+ cfg = optarg;
+ break;
+ case 'q':
+ quiet = true;
+ break;
+ case 'n':
+ name = optarg;
+ break;
+ case 'g':
+ id = pw_checkid(optarg, GID_MAX);
+ break;
+ case 'd':
+ oldmembers = optarg;
+ break;
+ case 'l':
+ newname = optarg;
+ break;
+ case 'H':
+ if (fd != -1)
+ errx(EX_USAGE, "'-h' and '-H' are mutually "
+ "exclusive options");
+ fd = pw_checkfd(optarg);
+ precrypted = true;
+ if (fd == '-')
+ errx(EX_USAGE, "-H expects a file descriptor");
+ break;
+ case 'h':
+ if (fd != -1)
+ errx(EX_USAGE, "'-h' and '-H' are mutually "
+ "exclusive options");
+ fd = pw_checkfd(optarg);
+ break;
+ case 'M':
+ members = optarg;
+ break;
+ case 'm':
+ newmembers = optarg;
+ break;
+ case 'N':
+ dryrun = true;
+ break;
+ case 'P':
+ pretty = true;
+ break;
+ case 'Y':
+ nis = true;
+ break;
+ }
+ }
+ if (quiet)
+ freopen(_PATH_DEVNULL, "w", stderr);
+ cnf = get_userconfig(cfg);
+ grp = getgroup(name, id, true);
+ if (name == NULL)
+ name = grp->gr_name;
+ if (id > 0)
+ grp->gr_gid = id;
+
+ if (newname != NULL)
+ grp->gr_name = pw_checkname(newname, 0);
+
+ grp_set_passwd(grp, true, fd, precrypted);
+ /*
+ * Keep the same logic as old code for now:
+ * if -M is passed, -d and -m are ignored
+ * then id -d, -m is ignored
+ * last is -m
+ */
+
+ if (members) {
+ grp->gr_mem = NULL;
+ grp_add_members(&grp, members);
+ } else if (oldmembers) {
+ delete_members(grp, oldmembers);
+ } else if (newmembers) {
+ grp_add_members(&grp, newmembers);
+ }
+
+ if (dryrun) {
+ print_group(grp, pretty);
+ return (EXIT_SUCCESS);
+ }
+
+ if ((rc = chggrent(name, grp)) != 0) {
+ if (rc == -1)
+ errx(EX_IOERR, "group '%s' not available (NIS?)",
+ grp->gr_name);
+ else
+ err(EX_IOERR, "group update");
+ }
+
+ if (newname)
+ name = newname;
+
+ /* grp may have been invalidated */
+ if ((grp = GETGRNAM(name)) == NULL)
+ errx(EX_SOFTWARE, "group disappeared during update");
+
+ pw_log(cnf, M_UPDATE, W_GROUP, "%s(%ju)", grp->gr_name,
+ (uintmax_t)grp->gr_gid);
+
+ if (nis && nis_update() == 0)
+ pw_log(cnf, M_UPDATE, W_GROUP, "NIS maps updated");
+
+ return (EXIT_SUCCESS);
+}
diff --git a/usr.sbin/pw/pw_log.c b/usr.sbin/pw/pw_log.c
new file mode 100644
index 0000000..29038d9
--- /dev/null
+++ b/usr.sbin/pw/pw_log.c
@@ -0,0 +1,68 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <fcntl.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "pw.h"
+
+static FILE *logfile = NULL;
+
+void
+pw_log(struct userconf * cnf, int mode, int which, char const * fmt,...)
+{
+ if (cnf->logfile && *cnf->logfile) {
+ if (logfile == NULL) { /* With umask==0 we need to control file access modes on create */
+ int fd = open(cnf->logfile, O_WRONLY | O_CREAT | O_APPEND, 0600);
+
+ if (fd != -1)
+ logfile = fdopen(fd, "a");
+ }
+ if (logfile != NULL) {
+ va_list argp;
+ time_t now = time(NULL);
+ struct tm *t = localtime(&now);
+ char nfmt[256];
+ const char *name;
+
+ if ((name = getenv("LOGNAME")) == NULL && (name = getenv("USER")) == NULL)
+ name = "unknown";
+ /* ISO 8601 International Standard Date format */
+ strftime(nfmt, sizeof nfmt, "%Y-%m-%d %T ", t);
+ sprintf(nfmt + strlen(nfmt), "[%s:%s%s] %s\n", name, Which[which], Modes[mode], fmt);
+ va_start(argp, fmt);
+ vfprintf(logfile, nfmt, argp);
+ va_end(argp);
+ fflush(logfile);
+ }
+ }
+}
diff --git a/usr.sbin/pw/pw_nis.c b/usr.sbin/pw/pw_nis.c
new file mode 100644
index 0000000..6cc361b
--- /dev/null
+++ b/usr.sbin/pw/pw_nis.c
@@ -0,0 +1,95 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <err.h>
+#include <pwd.h>
+#include <libutil.h>
+
+#include "pw.h"
+
+static int
+pw_nisupdate(const char * path, struct passwd * pwd, char const * user)
+{
+ int pfd, tfd;
+ struct passwd *pw = NULL;
+ struct passwd *old_pw = NULL;
+
+ printf("===> %s\n", path);
+ if (pwd != NULL)
+ pw = pw_dup(pwd);
+
+ if (user != NULL)
+ old_pw = GETPWNAM(user);
+
+ if (pw_init(NULL, path))
+ err(1,"pw_init()");
+ if ((pfd = pw_lock()) == -1) {
+ pw_fini();
+ err(1, "pw_lock()");
+ }
+ if ((tfd = pw_tmp(-1)) == -1) {
+ pw_fini();
+ err(1, "pw_tmp()");
+ }
+ if (pw_copy(pfd, tfd, pw, old_pw) == -1) {
+ pw_fini();
+ err(1, "pw_copy()");
+ }
+ if (chmod(pw_tempname(), 0644) == -1)
+ err(1, "chmod()");
+ if (rename(pw_tempname(), path) == -1)
+ err(1, "rename()");
+
+ free(pw);
+ pw_fini();
+
+ return (0);
+}
+
+int
+addnispwent(const char *path, struct passwd * pwd)
+{
+ return pw_nisupdate(path, pwd, NULL);
+}
+
+int
+chgnispwent(const char *path, char const * login, struct passwd * pwd)
+{
+ return pw_nisupdate(path, pwd, login);
+}
+
+int
+delnispwent(const char *path, const char *login)
+{
+ return pw_nisupdate(path, NULL, login);
+}
diff --git a/usr.sbin/pw/pw_user.c b/usr.sbin/pw/pw_user.c
new file mode 100644
index 0000000..30a2749
--- /dev/null
+++ b/usr.sbin/pw/pw_user.c
@@ -0,0 +1,1815 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <sys/time.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <pwd.h>
+#include <libutil.h>
+#include <login_cap.h>
+#include <paths.h>
+#include <string.h>
+#include <sysexits.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "pw.h"
+#include "bitmap.h"
+#include "psdate.h"
+
+#define LOGNAMESIZE (MAXLOGNAME-1)
+
+static char locked_str[] = "*LOCKED*";
+
+static struct passwd fakeuser = {
+ "nouser",
+ "*",
+ -1,
+ -1,
+ 0,
+ "",
+ "User &",
+ "/nonexistent",
+ "/bin/sh",
+ 0,
+ 0
+};
+
+static int print_user(struct passwd *pwd, bool pretty, bool v7);
+static uid_t pw_uidpolicy(struct userconf *cnf, intmax_t id);
+static uid_t pw_gidpolicy(struct userconf *cnf, char *grname, char *nam,
+ gid_t prefer, bool dryrun);
+static char *pw_homepolicy(struct userconf * cnf, char *homedir,
+ const char *user);
+static char *pw_shellpolicy(struct userconf * cnf);
+static char *pw_password(struct userconf * cnf, char const * user,
+ bool dryrun);
+static char *shell_path(char const * path, char *shells[], char *sh);
+static void rmat(uid_t uid);
+static void rmopie(char const * name);
+
+static void
+mkdir_home_parents(int dfd, const char *dir)
+{
+ struct stat st;
+ char *dirs, *tmp;
+
+ if (*dir != '/')
+ errx(EX_DATAERR, "invalid base directory for home '%s'", dir);
+
+ dir++;
+
+ if (fstatat(dfd, dir, &st, 0) != -1) {
+ if (S_ISDIR(st.st_mode))
+ return;
+ errx(EX_OSFILE, "root home `/%s' is not a directory", dir);
+ }
+
+ dirs = strdup(dir);
+ if (dirs == NULL)
+ errx(EX_UNAVAILABLE, "out of memory");
+
+ tmp = strrchr(dirs, '/');
+ if (tmp == NULL) {
+ free(dirs);
+ return;
+ }
+ tmp[0] = '\0';
+
+ /*
+ * This is a kludge especially for Joerg :)
+ * If the home directory would be created in the root partition, then
+ * we really create it under /usr which is likely to have more space.
+ * But we create a symlink from cnf->home -> "/usr" -> cnf->home
+ */
+ if (strchr(dirs, '/') == NULL) {
+ asprintf(&tmp, "usr/%s", dirs);
+ if (tmp == NULL)
+ errx(EX_UNAVAILABLE, "out of memory");
+ if (mkdirat(dfd, tmp, _DEF_DIRMODE) != -1 || errno == EEXIST) {
+ fchownat(dfd, tmp, 0, 0, 0);
+ symlinkat(tmp, dfd, dirs);
+ }
+ free(tmp);
+ }
+ tmp = dirs;
+ if (fstatat(dfd, dirs, &st, 0) == -1) {
+ while ((tmp = strchr(tmp + 1, '/')) != NULL) {
+ *tmp = '\0';
+ if (fstatat(dfd, dirs, &st, 0) == -1) {
+ if (mkdirat(dfd, dirs, _DEF_DIRMODE) == -1)
+ err(EX_OSFILE, "'%s' (root home parent) is not a directory", dirs);
+ }
+ *tmp = '/';
+ }
+ }
+ if (fstatat(dfd, dirs, &st, 0) == -1) {
+ if (mkdirat(dfd, dirs, _DEF_DIRMODE) == -1)
+ err(EX_OSFILE, "'%s' (root home parent) is not a directory", dirs);
+ fchownat(dfd, dirs, 0, 0, 0);
+ }
+
+ free(dirs);
+}
+
+static void
+create_and_populate_homedir(struct userconf *cnf, struct passwd *pwd,
+ const char *skeldir, mode_t homemode, bool update)
+{
+ int skelfd = -1;
+
+ /* Create home parents directories */
+ mkdir_home_parents(conf.rootfd, pwd->pw_dir);
+
+ if (skeldir != NULL && *skeldir != '\0') {
+ if (*skeldir == '/')
+ skeldir++;
+ skelfd = openat(conf.rootfd, skeldir, O_DIRECTORY|O_CLOEXEC);
+ }
+
+ copymkdir(conf.rootfd, pwd->pw_dir, skelfd, homemode, pwd->pw_uid,
+ pwd->pw_gid, 0);
+ pw_log(cnf, update ? M_UPDATE : M_ADD, W_USER, "%s(%ju) home %s made",
+ pwd->pw_name, (uintmax_t)pwd->pw_uid, pwd->pw_dir);
+}
+
+static int
+pw_set_passwd(struct passwd *pwd, int fd, bool precrypted, bool update)
+{
+ int b, istty;
+ struct termios t, n;
+ login_cap_t *lc;
+ char line[_PASSWORD_LEN+1];
+ char *p;
+
+ if (fd == '-') {
+ if (!pwd->pw_passwd || *pwd->pw_passwd != '*') {
+ pwd->pw_passwd = "*"; /* No access */
+ return (1);
+ }
+ return (0);
+ }
+
+ if ((istty = isatty(fd))) {
+ if (tcgetattr(fd, &t) == -1)
+ istty = 0;
+ else {
+ n = t;
+ n.c_lflag &= ~(ECHO);
+ tcsetattr(fd, TCSANOW, &n);
+ printf("%s%spassword for user %s:",
+ update ? "new " : "",
+ precrypted ? "encrypted " : "",
+ pwd->pw_name);
+ fflush(stdout);
+ }
+ }
+ b = read(fd, line, sizeof(line) - 1);
+ if (istty) { /* Restore state */
+ tcsetattr(fd, TCSANOW, &t);
+ fputc('\n', stdout);
+ fflush(stdout);
+ }
+
+ if (b < 0)
+ err(EX_IOERR, "-%c file descriptor",
+ precrypted ? 'H' : 'h');
+ line[b] = '\0';
+ if ((p = strpbrk(line, "\r\n")) != NULL)
+ *p = '\0';
+ if (!*line)
+ errx(EX_DATAERR, "empty password read on file descriptor %d",
+ fd);
+ if (precrypted) {
+ if (strchr(line, ':') != NULL)
+ errx(EX_DATAERR, "bad encrypted password");
+ pwd->pw_passwd = strdup(line);
+ } else {
+ lc = login_getpwclass(pwd);
+ if (lc == NULL ||
+ login_setcryptfmt(lc, "sha512", NULL) == NULL)
+ warn("setting crypt(3) format");
+ login_close(lc);
+ pwd->pw_passwd = pw_pwcrypt(line);
+ }
+ return (1);
+}
+
+static void
+perform_chgpwent(const char *name, struct passwd *pwd, char *nispasswd)
+{
+ int rc;
+ struct passwd *nispwd;
+
+ /* duplicate for nis so that chgpwent is not modifying before NIS */
+ if (nispasswd && *nispasswd == '/')
+ nispwd = pw_dup(pwd);
+
+ rc = chgpwent(name, pwd);
+ if (rc == -1)
+ errx(EX_IOERR, "user '%s' does not exist (NIS?)", pwd->pw_name);
+ else if (rc != 0)
+ err(EX_IOERR, "passwd file update");
+
+ if (nispasswd && *nispasswd == '/') {
+ rc = chgnispwent(nispasswd, name, nispwd);
+ if (rc == -1)
+ warn("User '%s' not found in NIS passwd", pwd->pw_name);
+ else if (rc != 0)
+ warn("NIS passwd update");
+ /* NOTE: NIS-only update errors are not fatal */
+ }
+}
+
+/*
+ * The M_LOCK and M_UNLOCK functions simply add or remove
+ * a "*LOCKED*" prefix from in front of the password to
+ * prevent it decoding correctly, and therefore prevents
+ * access. Of course, this only prevents access via
+ * password authentication (not ssh, kerberos or any
+ * other method that does not use the UNIX password) but
+ * that is a known limitation.
+ */
+static int
+pw_userlock(char *arg1, int mode)
+{
+ struct passwd *pwd = NULL;
+ char *passtmp = NULL;
+ char *name;
+ bool locked = false;
+ uid_t id = (uid_t)-1;
+
+ if (geteuid() != 0)
+ errx(EX_NOPERM, "you must be root");
+
+ if (arg1 == NULL)
+ errx(EX_DATAERR, "username or id required");
+
+ name = arg1;
+ if (arg1[strspn(name, "0123456789")] == '\0')
+ id = pw_checkid(name, UID_MAX);
+
+ pwd = GETPWNAM(pw_checkname(name, 0));
+ if (pwd == NULL && id != (uid_t)-1) {
+ pwd = GETPWUID(id);
+ if (pwd != NULL)
+ name = pwd->pw_name;
+ }
+ if (pwd == NULL) {
+ if (id == (uid_t)-1)
+ errx(EX_NOUSER, "no such name or uid `%ju'", (uintmax_t) id);
+ errx(EX_NOUSER, "no such user `%s'", name);
+ }
+
+ if (name == NULL)
+ name = pwd->pw_name;
+
+ if (strncmp(pwd->pw_passwd, locked_str, sizeof(locked_str) -1) == 0)
+ locked = true;
+ if (mode == M_LOCK && locked)
+ errx(EX_DATAERR, "user '%s' is already locked", pwd->pw_name);
+ if (mode == M_UNLOCK && !locked)
+ errx(EX_DATAERR, "user '%s' is not locked", pwd->pw_name);
+
+ if (mode == M_LOCK) {
+ asprintf(&passtmp, "%s%s", locked_str, pwd->pw_passwd);
+ if (passtmp == NULL) /* disaster */
+ errx(EX_UNAVAILABLE, "out of memory");
+ pwd->pw_passwd = passtmp;
+ } else {
+ pwd->pw_passwd += sizeof(locked_str)-1;
+ }
+
+ perform_chgpwent(name, pwd, NULL);
+ free(passtmp);
+
+ return (EXIT_SUCCESS);
+}
+
+static uid_t
+pw_uidpolicy(struct userconf * cnf, intmax_t id)
+{
+ struct passwd *pwd;
+ struct bitmap bm;
+ uid_t uid = (uid_t) - 1;
+
+ /*
+ * Check the given uid, if any
+ */
+ if (id >= 0) {
+ uid = (uid_t) id;
+
+ if ((pwd = GETPWUID(uid)) != NULL && conf.checkduplicate)
+ errx(EX_DATAERR, "uid `%ju' has already been allocated",
+ (uintmax_t)pwd->pw_uid);
+ return (uid);
+ }
+ /*
+ * We need to allocate the next available uid under one of
+ * two policies a) Grab the first unused uid b) Grab the
+ * highest possible unused uid
+ */
+ if (cnf->min_uid >= cnf->max_uid) { /* Sanity
+ * claus^H^H^H^Hheck */
+ cnf->min_uid = 1000;
+ cnf->max_uid = 32000;
+ }
+ bm = bm_alloc(cnf->max_uid - cnf->min_uid + 1);
+
+ /*
+ * Now, let's fill the bitmap from the password file
+ */
+ SETPWENT();
+ while ((pwd = GETPWENT()) != NULL)
+ if (pwd->pw_uid >= (uid_t) cnf->min_uid && pwd->pw_uid <= (uid_t) cnf->max_uid)
+ bm_setbit(&bm, pwd->pw_uid - cnf->min_uid);
+ ENDPWENT();
+
+ /*
+ * Then apply the policy, with fallback to reuse if necessary
+ */
+ if (cnf->reuse_uids || (uid = (uid_t) (bm_lastset(&bm) + cnf->min_uid + 1)) > cnf->max_uid)
+ uid = (uid_t) (bm_firstunset(&bm) + cnf->min_uid);
+
+ /*
+ * Another sanity check
+ */
+ if (uid < cnf->min_uid || uid > cnf->max_uid)
+ errx(EX_SOFTWARE, "unable to allocate a new uid - range fully used");
+ bm_dealloc(&bm);
+ return (uid);
+}
+
+static uid_t
+pw_gidpolicy(struct userconf *cnf, char *grname, char *nam, gid_t prefer, bool dryrun)
+{
+ struct group *grp;
+ gid_t gid = (uid_t) - 1;
+
+ /*
+ * Check the given gid, if any
+ */
+ SETGRENT();
+ if (grname) {
+ if ((grp = GETGRNAM(grname)) == NULL) {
+ gid = pw_checkid(grname, GID_MAX);
+ grp = GETGRGID(gid);
+ }
+ gid = grp->gr_gid;
+ } else if ((grp = GETGRNAM(nam)) != NULL &&
+ (grp->gr_mem == NULL || grp->gr_mem[0] == NULL)) {
+ gid = grp->gr_gid; /* Already created? Use it anyway... */
+ } else {
+ intmax_t grid = -1;
+
+ /*
+ * We need to auto-create a group with the user's name. We
+ * can send all the appropriate output to our sister routine
+ * bit first see if we can create a group with gid==uid so we
+ * can keep the user and group ids in sync. We purposely do
+ * NOT check the gid range if we can force the sync. If the
+ * user's name dups an existing group, then the group add
+ * function will happily handle that case for us and exit.
+ */
+ if (GETGRGID(prefer) == NULL)
+ grid = prefer;
+ if (dryrun) {
+ gid = pw_groupnext(cnf, true);
+ } else {
+ if (grid == -1)
+ grid = pw_groupnext(cnf, true);
+ groupadd(cnf, nam, grid, NULL, -1, false, false, false);
+ if ((grp = GETGRNAM(nam)) != NULL)
+ gid = grp->gr_gid;
+ }
+ }
+ ENDGRENT();
+ return (gid);
+}
+
+static char *
+pw_homepolicy(struct userconf * cnf, char *homedir, const char *user)
+{
+ static char home[128];
+
+ if (homedir)
+ return (homedir);
+
+ if (cnf->home == NULL || *cnf->home == '\0')
+ errx(EX_CONFIG, "no base home directory set");
+ snprintf(home, sizeof(home), "%s/%s", cnf->home, user);
+
+ return (home);
+}
+
+static char *
+shell_path(char const * path, char *shells[], char *sh)
+{
+ if (sh != NULL && (*sh == '/' || *sh == '\0'))
+ return sh; /* specified full path or forced none */
+ else {
+ char *p;
+ char paths[_UC_MAXLINE];
+
+ /*
+ * We need to search paths
+ */
+ strlcpy(paths, path, sizeof(paths));
+ for (p = strtok(paths, ": \t\r\n"); p != NULL; p = strtok(NULL, ": \t\r\n")) {
+ int i;
+ static char shellpath[256];
+
+ if (sh != NULL) {
+ snprintf(shellpath, sizeof(shellpath), "%s/%s", p, sh);
+ if (access(shellpath, X_OK) == 0)
+ return shellpath;
+ } else
+ for (i = 0; i < _UC_MAXSHELLS && shells[i] != NULL; i++) {
+ snprintf(shellpath, sizeof(shellpath), "%s/%s", p, shells[i]);
+ if (access(shellpath, X_OK) == 0)
+ return shellpath;
+ }
+ }
+ if (sh == NULL)
+ errx(EX_OSFILE, "can't find shell `%s' in shell paths", sh);
+ errx(EX_CONFIG, "no default shell available or defined");
+ return NULL;
+ }
+}
+
+static char *
+pw_shellpolicy(struct userconf * cnf)
+{
+
+ return shell_path(cnf->shelldir, cnf->shells, cnf->shell_default);
+}
+
+#define SALTSIZE 32
+
+static char const chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./";
+
+char *
+pw_pwcrypt(char *password)
+{
+ int i;
+ char salt[SALTSIZE + 1];
+ char *cryptpw;
+ static char buf[256];
+
+ /*
+ * Calculate a salt value
+ */
+ for (i = 0; i < SALTSIZE; i++)
+ salt[i] = chars[arc4random_uniform(sizeof(chars) - 1)];
+ salt[SALTSIZE] = '\0';
+
+ cryptpw = crypt(password, salt);
+ if (cryptpw == NULL)
+ errx(EX_CONFIG, "crypt(3) failure");
+ return strcpy(buf, cryptpw);
+}
+
+static char *
+pw_password(struct userconf * cnf, char const * user, bool dryrun)
+{
+ int i, l;
+ char pwbuf[32];
+
+ switch (cnf->default_password) {
+ case -1: /* Random password */
+ l = (arc4random() % 8 + 8); /* 8 - 16 chars */
+ for (i = 0; i < l; i++)
+ pwbuf[i] = chars[arc4random_uniform(sizeof(chars)-1)];
+ pwbuf[i] = '\0';
+
+ /*
+ * We give this information back to the user
+ */
+ if (conf.fd == -1 && !dryrun) {
+ if (isatty(STDOUT_FILENO))
+ printf("Password for '%s' is: ", user);
+ printf("%s\n", pwbuf);
+ fflush(stdout);
+ }
+ break;
+
+ case -2: /* No password at all! */
+ return "";
+
+ case 0: /* No login - default */
+ default:
+ return "*";
+
+ case 1: /* user's name */
+ strlcpy(pwbuf, user, sizeof(pwbuf));
+ break;
+ }
+ return pw_pwcrypt(pwbuf);
+}
+
+static int
+print_user(struct passwd * pwd, bool pretty, bool v7)
+{
+ int j;
+ char *p;
+ struct group *grp = GETGRGID(pwd->pw_gid);
+ char uname[60] = "User &", office[60] = "[None]",
+ wphone[60] = "[None]", hphone[60] = "[None]";
+ char acexpire[32] = "[None]", pwexpire[32] = "[None]";
+ struct tm * tptr;
+
+ if (!pretty) {
+ p = v7 ? pw_make_v7(pwd) : pw_make(pwd);
+ printf("%s\n", p);
+ free(p);
+ return (EXIT_SUCCESS);
+ }
+
+ if ((p = strtok(pwd->pw_gecos, ",")) != NULL) {
+ strlcpy(uname, p, sizeof(uname));
+ if ((p = strtok(NULL, ",")) != NULL) {
+ strlcpy(office, p, sizeof(office));
+ if ((p = strtok(NULL, ",")) != NULL) {
+ strlcpy(wphone, p, sizeof(wphone));
+ if ((p = strtok(NULL, "")) != NULL) {
+ strlcpy(hphone, p, sizeof(hphone));
+ }
+ }
+ }
+ }
+ /*
+ * Handle '&' in gecos field
+ */
+ if ((p = strchr(uname, '&')) != NULL) {
+ int l = strlen(pwd->pw_name);
+ int m = strlen(p);
+
+ memmove(p + l, p + 1, m);
+ memmove(p, pwd->pw_name, l);
+ *p = (char) toupper((unsigned char)*p);
+ }
+ if (pwd->pw_expire > (time_t)0 && (tptr = localtime(&pwd->pw_expire)) != NULL)
+ strftime(acexpire, sizeof acexpire, "%c", tptr);
+ if (pwd->pw_change > (time_t)0 && (tptr = localtime(&pwd->pw_change)) != NULL)
+ strftime(pwexpire, sizeof pwexpire, "%c", tptr);
+ printf("Login Name: %-15s #%-12ju Group: %-15s #%ju\n"
+ " Full Name: %s\n"
+ " Home: %-26.26s Class: %s\n"
+ " Shell: %-26.26s Office: %s\n"
+ "Work Phone: %-26.26s Home Phone: %s\n"
+ "Acc Expire: %-26.26s Pwd Expire: %s\n",
+ pwd->pw_name, (uintmax_t)pwd->pw_uid,
+ grp ? grp->gr_name : "(invalid)", (uintmax_t)pwd->pw_gid,
+ uname, pwd->pw_dir, pwd->pw_class,
+ pwd->pw_shell, office, wphone, hphone,
+ acexpire, pwexpire);
+ SETGRENT();
+ j = 0;
+ while ((grp=GETGRENT()) != NULL) {
+ int i = 0;
+ if (grp->gr_mem != NULL) {
+ while (grp->gr_mem[i] != NULL) {
+ if (strcmp(grp->gr_mem[i], pwd->pw_name)==0) {
+ printf(j++ == 0 ? " Groups: %s" : ",%s", grp->gr_name);
+ break;
+ }
+ ++i;
+ }
+ }
+ }
+ ENDGRENT();
+ printf("%s", j ? "\n" : "");
+ return (EXIT_SUCCESS);
+}
+
+char *
+pw_checkname(char *name, int gecos)
+{
+ char showch[8];
+ const char *badchars, *ch, *showtype;
+ int reject;
+
+ ch = name;
+ reject = 0;
+ if (gecos) {
+ /* See if the name is valid as a gecos (comment) field. */
+ badchars = ":!@";
+ showtype = "gecos field";
+ } else {
+ /* See if the name is valid as a userid or group. */
+ badchars = " ,\t:+&#%$^()!@~*?<>=|\\/\"";
+ showtype = "userid/group name";
+ /* Userids and groups can not have a leading '-'. */
+ if (*ch == '-')
+ reject = 1;
+ }
+ if (!reject) {
+ while (*ch) {
+ if (strchr(badchars, *ch) != NULL ||
+ (!gecos && *ch < ' ') ||
+ *ch == 127) {
+ reject = 1;
+ break;
+ }
+ /* 8-bit characters are only allowed in GECOS fields */
+ if (!gecos && (*ch & 0x80)) {
+ reject = 1;
+ break;
+ }
+ ch++;
+ }
+ }
+ /*
+ * A `$' is allowed as the final character for userids and groups,
+ * mainly for the benefit of samba.
+ */
+ if (reject && !gecos) {
+ if (*ch == '$' && *(ch + 1) == '\0') {
+ reject = 0;
+ ch++;
+ }
+ }
+ if (reject) {
+ snprintf(showch, sizeof(showch), (*ch >= ' ' && *ch < 127)
+ ? "`%c'" : "0x%02x", *ch);
+ errx(EX_DATAERR, "invalid character %s at position %td in %s",
+ showch, (ch - name), showtype);
+ }
+ if (!gecos && (ch - name) > LOGNAMESIZE)
+ errx(EX_USAGE, "name too long `%s' (max is %d)", name,
+ LOGNAMESIZE);
+
+ return (name);
+}
+
+static void
+rmat(uid_t uid)
+{
+ DIR *d = opendir("/var/at/jobs");
+
+ if (d != NULL) {
+ struct dirent *e;
+
+ while ((e = readdir(d)) != NULL) {
+ struct stat st;
+
+ if (strncmp(e->d_name, ".lock", 5) != 0 &&
+ stat(e->d_name, &st) == 0 &&
+ !S_ISDIR(st.st_mode) &&
+ st.st_uid == uid) {
+ char tmp[MAXPATHLEN];
+
+ snprintf(tmp, sizeof(tmp), "/usr/bin/atrm %s",
+ e->d_name);
+ system(tmp);
+ }
+ }
+ closedir(d);
+ }
+}
+
+static void
+rmopie(char const * name)
+{
+ char tmp[1014];
+ FILE *fp;
+ int fd;
+ size_t len;
+ off_t atofs = 0;
+
+ if ((fd = openat(conf.rootfd, "etc/opiekeys", O_RDWR)) == -1)
+ return;
+
+ fp = fdopen(fd, "r+");
+ len = strlen(name);
+
+ while (fgets(tmp, sizeof(tmp), fp) != NULL) {
+ if (strncmp(name, tmp, len) == 0 && tmp[len]==' ') {
+ /* Comment username out */
+ if (fseek(fp, atofs, SEEK_SET) == 0)
+ fwrite("#", 1, 1, fp);
+ break;
+ }
+ atofs = ftell(fp);
+ }
+ /*
+ * If we got an error of any sort, don't update!
+ */
+ fclose(fp);
+}
+
+int
+pw_user_next(int argc, char **argv, char *name __unused)
+{
+ struct userconf *cnf = NULL;
+ const char *cfg = NULL;
+ int ch;
+ bool quiet = false;
+ uid_t next;
+
+ while ((ch = getopt(argc, argv, "Cq")) != -1) {
+ switch (ch) {
+ case 'C':
+ cfg = optarg;
+ break;
+ case 'q':
+ quiet = true;
+ break;
+ }
+ }
+
+ if (quiet)
+ freopen(_PATH_DEVNULL, "w", stderr);
+
+ cnf = get_userconfig(cfg);
+
+ next = pw_uidpolicy(cnf, -1);
+
+ printf("%ju:", (uintmax_t)next);
+ pw_groupnext(cnf, quiet);
+
+ return (EXIT_SUCCESS);
+}
+
+int
+pw_user_show(int argc, char **argv, char *arg1)
+{
+ struct passwd *pwd = NULL;
+ char *name = NULL;
+ intmax_t id = -1;
+ int ch;
+ bool all = false;
+ bool pretty = false;
+ bool force = false;
+ bool v7 = false;
+ bool quiet = false;
+
+ if (arg1 != NULL) {
+ if (arg1[strspn(arg1, "0123456789")] == '\0')
+ id = pw_checkid(arg1, UID_MAX);
+ else
+ name = arg1;
+ }
+
+ while ((ch = getopt(argc, argv, "C:qn:u:FPa7")) != -1) {
+ switch (ch) {
+ case 'C':
+ /* ignore compatibility */
+ break;
+ case 'q':
+ quiet = true;
+ break;
+ case 'n':
+ name = optarg;
+ break;
+ case 'u':
+ id = pw_checkid(optarg, UID_MAX);
+ break;
+ case 'F':
+ force = true;
+ break;
+ case 'P':
+ pretty = true;
+ break;
+ case 'a':
+ all = true;
+ break;
+ case '7':
+ v7 = true;
+ break;
+ }
+ }
+
+ if (quiet)
+ freopen(_PATH_DEVNULL, "w", stderr);
+
+ if (all) {
+ SETPWENT();
+ while ((pwd = GETPWENT()) != NULL)
+ print_user(pwd, pretty, v7);
+ ENDPWENT();
+ return (EXIT_SUCCESS);
+ }
+
+ if (id < 0 && name == NULL)
+ errx(EX_DATAERR, "username or id required");
+
+ pwd = (name != NULL) ? GETPWNAM(pw_checkname(name, 0)) : GETPWUID(id);
+ if (pwd == NULL) {
+ if (force) {
+ pwd = &fakeuser;
+ } else {
+ if (name == NULL)
+ errx(EX_NOUSER, "no such uid `%ju'",
+ (uintmax_t) id);
+ errx(EX_NOUSER, "no such user `%s'", name);
+ }
+ }
+
+ return (print_user(pwd, pretty, v7));
+}
+
+int
+pw_user_del(int argc, char **argv, char *arg1)
+{
+ struct userconf *cnf = NULL;
+ struct passwd *pwd = NULL;
+ struct group *gr, *grp;
+ char *name = NULL;
+ char grname[MAXLOGNAME];
+ char *nispasswd = NULL;
+ char file[MAXPATHLEN];
+ char home[MAXPATHLEN];
+ const char *cfg = NULL;
+ struct stat st;
+ intmax_t id = -1;
+ int ch, rc;
+ bool nis = false;
+ bool deletehome = false;
+ bool quiet = false;
+
+ if (arg1 != NULL) {
+ if (arg1[strspn(arg1, "0123456789")] == '\0')
+ id = pw_checkid(arg1, UID_MAX);
+ else
+ name = arg1;
+ }
+
+ while ((ch = getopt(argc, argv, "C:qn:u:rYy:")) != -1) {
+ switch (ch) {
+ case 'C':
+ cfg = optarg;
+ break;
+ case 'q':
+ quiet = true;
+ break;
+ case 'n':
+ name = optarg;
+ break;
+ case 'u':
+ id = pw_checkid(optarg, UID_MAX);
+ break;
+ case 'r':
+ deletehome = true;
+ break;
+ case 'y':
+ nispasswd = optarg;
+ break;
+ case 'Y':
+ nis = true;
+ break;
+ }
+ }
+
+ if (quiet)
+ freopen(_PATH_DEVNULL, "w", stderr);
+
+ if (id < 0 && name == NULL)
+ errx(EX_DATAERR, "username or id required");
+
+ cnf = get_userconfig(cfg);
+
+ if (nispasswd == NULL)
+ nispasswd = cnf->nispasswd;
+
+ pwd = (name != NULL) ? GETPWNAM(pw_checkname(name, 0)) : GETPWUID(id);
+ if (pwd == NULL) {
+ if (name == NULL)
+ errx(EX_NOUSER, "no such uid `%ju'", (uintmax_t) id);
+ errx(EX_NOUSER, "no such user `%s'", name);
+ }
+
+ if (PWF._altdir == PWF_REGULAR &&
+ ((pwd->pw_fields & _PWF_SOURCE) != _PWF_FILES)) {
+ if ((pwd->pw_fields & _PWF_SOURCE) == _PWF_NIS) {
+ if (!nis && nispasswd && *nispasswd != '/')
+ errx(EX_NOUSER, "Cannot remove NIS user `%s'",
+ name);
+ } else {
+ errx(EX_NOUSER, "Cannot remove non local user `%s'",
+ name);
+ }
+ }
+
+ id = pwd->pw_uid;
+ if (name == NULL)
+ name = pwd->pw_name;
+
+ if (strcmp(pwd->pw_name, "root") == 0)
+ errx(EX_DATAERR, "cannot remove user 'root'");
+
+ /* Remove opie record from /etc/opiekeys */
+ if (PWALTDIR() != PWF_ALT)
+ rmopie(pwd->pw_name);
+
+ if (!PWALTDIR()) {
+ /* Remove crontabs */
+ snprintf(file, sizeof(file), "/var/cron/tabs/%s", pwd->pw_name);
+ if (access(file, F_OK) == 0) {
+ snprintf(file, sizeof(file), "crontab -u %s -r",
+ pwd->pw_name);
+ system(file);
+ }
+ }
+
+ /*
+ * Save these for later, since contents of pwd may be
+ * invalidated by deletion
+ */
+ snprintf(file, sizeof(file), "%s/%s", _PATH_MAILDIR, pwd->pw_name);
+ strlcpy(home, pwd->pw_dir, sizeof(home));
+ gr = GETGRGID(pwd->pw_gid);
+ if (gr != NULL)
+ strlcpy(grname, gr->gr_name, LOGNAMESIZE);
+ else
+ grname[0] = '\0';
+
+ rc = delpwent(pwd);
+ if (rc == -1)
+ err(EX_IOERR, "user '%s' does not exist", pwd->pw_name);
+ else if (rc != 0)
+ err(EX_IOERR, "passwd update");
+
+ if (nis && nispasswd && *nispasswd=='/') {
+ rc = delnispwent(nispasswd, name);
+ if (rc == -1)
+ warnx("WARNING: user '%s' does not exist in NIS passwd",
+ pwd->pw_name);
+ else if (rc != 0)
+ warn("WARNING: NIS passwd update");
+ }
+
+ grp = GETGRNAM(name);
+ if (grp != NULL &&
+ (grp->gr_mem == NULL || *grp->gr_mem == NULL) &&
+ strcmp(name, grname) == 0)
+ delgrent(GETGRNAM(name));
+ SETGRENT();
+ while ((grp = GETGRENT()) != NULL) {
+ int i, j;
+ char group[MAXLOGNAME];
+ if (grp->gr_mem == NULL)
+ continue;
+
+ for (i = 0; grp->gr_mem[i] != NULL; i++) {
+ if (strcmp(grp->gr_mem[i], name) != 0)
+ continue;
+
+ for (j = i; grp->gr_mem[j] != NULL; j++)
+ grp->gr_mem[j] = grp->gr_mem[j+1];
+ strlcpy(group, grp->gr_name, MAXLOGNAME);
+ chggrent(group, grp);
+ }
+ }
+ ENDGRENT();
+
+ pw_log(cnf, M_DELETE, W_USER, "%s(%ju) account removed", name,
+ (uintmax_t)id);
+
+ /* Remove mail file */
+ if (PWALTDIR() != PWF_ALT)
+ unlinkat(conf.rootfd, file + 1, 0);
+
+ /* Remove at jobs */
+ if (!PWALTDIR() && getpwuid(id) == NULL)
+ rmat(id);
+
+ /* Remove home directory and contents */
+ if (PWALTDIR() != PWF_ALT && deletehome && *home == '/' &&
+ GETPWUID(id) == NULL &&
+ fstatat(conf.rootfd, home + 1, &st, 0) != -1) {
+ rm_r(conf.rootfd, home, id);
+ pw_log(cnf, M_DELETE, W_USER, "%s(%ju) home '%s' %s"
+ "removed", name, (uintmax_t)id, home,
+ fstatat(conf.rootfd, home + 1, &st, 0) == -1 ? "" : "not "
+ "completely ");
+ }
+
+ return (EXIT_SUCCESS);
+}
+
+int
+pw_user_lock(int argc, char **argv, char *arg1)
+{
+ int ch;
+
+ while ((ch = getopt(argc, argv, "Cq")) != -1) {
+ switch (ch) {
+ case 'C':
+ case 'q':
+ /* compatibility */
+ break;
+ }
+ }
+
+ return (pw_userlock(arg1, M_LOCK));
+}
+
+int
+pw_user_unlock(int argc, char **argv, char *arg1)
+{
+ int ch;
+
+ while ((ch = getopt(argc, argv, "Cq")) != -1) {
+ switch (ch) {
+ case 'C':
+ case 'q':
+ /* compatibility */
+ break;
+ }
+ }
+
+ return (pw_userlock(arg1, M_UNLOCK));
+}
+
+static struct group *
+group_from_name_or_id(char *name)
+{
+ const char *errstr = NULL;
+ struct group *grp;
+ uintmax_t id;
+
+ if ((grp = GETGRNAM(name)) == NULL) {
+ id = strtounum(name, 0, GID_MAX, &errstr);
+ if (errstr)
+ errx(EX_NOUSER, "group `%s' does not exist", name);
+ grp = GETGRGID(id);
+ if (grp == NULL)
+ errx(EX_NOUSER, "group `%s' does not exist", name);
+ }
+
+ return (grp);
+}
+
+static void
+split_groups(StringList **groups, char *groupsstr)
+{
+ struct group *grp;
+ char *p;
+ char tok[] = ", \t";
+
+ for (p = strtok(groupsstr, tok); p != NULL; p = strtok(NULL, tok)) {
+ grp = group_from_name_or_id(p);
+ if (*groups == NULL)
+ *groups = sl_init();
+ sl_add(*groups, newstr(grp->gr_name));
+ }
+}
+
+static void
+validate_grname(struct userconf *cnf, char *group)
+{
+ struct group *grp;
+
+ if (group == NULL || *group == '\0') {
+ cnf->default_group = "";
+ return;
+ }
+ grp = group_from_name_or_id(group);
+ cnf->default_group = newstr(grp->gr_name);
+}
+
+static mode_t
+validate_mode(char *mode)
+{
+ mode_t m;
+ void *set;
+
+ if ((set = setmode(mode)) == NULL)
+ errx(EX_DATAERR, "invalid directory creation mode '%s'", mode);
+
+ m = getmode(set, _DEF_DIRMODE);
+ free(set);
+ return (m);
+}
+
+static void
+mix_config(struct userconf *cmdcnf, struct userconf *cfg)
+{
+
+ if (cmdcnf->default_password == 0)
+ cmdcnf->default_password = cfg->default_password;
+ if (cmdcnf->reuse_uids == 0)
+ cmdcnf->reuse_uids = cfg->reuse_uids;
+ if (cmdcnf->reuse_gids == 0)
+ cmdcnf->reuse_gids = cfg->reuse_gids;
+ if (cmdcnf->nispasswd == NULL)
+ cmdcnf->nispasswd = cfg->nispasswd;
+ if (cmdcnf->dotdir == NULL)
+ cmdcnf->dotdir = cfg->dotdir;
+ if (cmdcnf->newmail == NULL)
+ cmdcnf->newmail = cfg->newmail;
+ if (cmdcnf->logfile == NULL)
+ cmdcnf->logfile = cfg->logfile;
+ if (cmdcnf->home == NULL)
+ cmdcnf->home = cfg->home;
+ if (cmdcnf->homemode == 0)
+ cmdcnf->homemode = cfg->homemode;
+ if (cmdcnf->shelldir == NULL)
+ cmdcnf->shelldir = cfg->shelldir;
+ if (cmdcnf->shells == NULL)
+ cmdcnf->shells = cfg->shells;
+ if (cmdcnf->shell_default == NULL)
+ cmdcnf->shell_default = cfg->shell_default;
+ if (cmdcnf->default_group == NULL)
+ cmdcnf->default_group = cfg->default_group;
+ if (cmdcnf->groups == NULL)
+ cmdcnf->groups = cfg->groups;
+ if (cmdcnf->default_class == NULL)
+ cmdcnf->default_class = cfg->default_class;
+ if (cmdcnf->min_uid == 0)
+ cmdcnf->min_uid = cfg->min_uid;
+ if (cmdcnf->max_uid == 0)
+ cmdcnf->max_uid = cfg->max_uid;
+ if (cmdcnf->min_gid == 0)
+ cmdcnf->min_gid = cfg->min_gid;
+ if (cmdcnf->max_gid == 0)
+ cmdcnf->max_gid = cfg->max_gid;
+ if (cmdcnf->expire_days == 0)
+ cmdcnf->expire_days = cfg->expire_days;
+ if (cmdcnf->password_days == 0)
+ cmdcnf->password_days = cfg->password_days;
+}
+
+int
+pw_user_add(int argc, char **argv, char *arg1)
+{
+ struct userconf *cnf, *cmdcnf;
+ struct passwd *pwd;
+ struct group *grp;
+ struct stat st;
+ char args[] = "C:qn:u:c:d:e:p:g:G:mM:k:s:oL:i:w:h:H:Db:NPy:Y";
+ char line[_PASSWORD_LEN+1], path[MAXPATHLEN];
+ char *gecos, *homedir, *skel, *walk, *userid, *groupid, *grname;
+ char *default_passwd, *name, *p;
+ const char *cfg;
+ login_cap_t *lc;
+ FILE *pfp, *fp;
+ intmax_t id = -1;
+ time_t now;
+ int rc, ch, fd = -1;
+ size_t i;
+ bool dryrun, nis, pretty, quiet, createhome, precrypted, genconf;
+
+ dryrun = nis = pretty = quiet = createhome = precrypted = false;
+ genconf = false;
+ gecos = homedir = skel = userid = groupid = default_passwd = NULL;
+ grname = name = NULL;
+
+ if ((cmdcnf = calloc(1, sizeof(struct userconf))) == NULL)
+ err(EXIT_FAILURE, "calloc()");
+
+ if (arg1 != NULL) {
+ if (arg1[strspn(arg1, "0123456789")] == '\0')
+ id = pw_checkid(arg1, UID_MAX);
+ else
+ name = arg1;
+ }
+
+ while ((ch = getopt(argc, argv, args)) != -1) {
+ switch (ch) {
+ case 'C':
+ cfg = optarg;
+ break;
+ case 'q':
+ quiet = true;
+ break;
+ case 'n':
+ name = optarg;
+ break;
+ case 'u':
+ userid = optarg;
+ break;
+ case 'c':
+ gecos = pw_checkname(optarg, 1);
+ break;
+ case 'd':
+ homedir = optarg;
+ break;
+ case 'e':
+ now = time(NULL);
+ cmdcnf->expire_days = parse_date(now, optarg);
+ break;
+ case 'p':
+ now = time(NULL);
+ cmdcnf->password_days = parse_date(now, optarg);
+ break;
+ case 'g':
+ validate_grname(cmdcnf, optarg);
+ grname = optarg;
+ break;
+ case 'G':
+ split_groups(&cmdcnf->groups, optarg);
+ break;
+ case 'm':
+ createhome = true;
+ break;
+ case 'M':
+ cmdcnf->homemode = validate_mode(optarg);
+ break;
+ case 'k':
+ walk = skel = optarg;
+ if (*walk == '/')
+ walk++;
+ if (fstatat(conf.rootfd, walk, &st, 0) == -1)
+ errx(EX_OSFILE, "skeleton `%s' does not "
+ "exists", skel);
+ if (!S_ISDIR(st.st_mode))
+ errx(EX_OSFILE, "skeleton `%s' is not a "
+ "directory", skel);
+ cmdcnf->dotdir = skel;
+ break;
+ case 's':
+ cmdcnf->shell_default = optarg;
+ break;
+ case 'o':
+ conf.checkduplicate = false;
+ break;
+ case 'L':
+ cmdcnf->default_class = pw_checkname(optarg, 0);
+ break;
+ case 'i':
+ groupid = optarg;
+ break;
+ case 'w':
+ default_passwd = optarg;
+ break;
+ case 'H':
+ if (fd != -1)
+ errx(EX_USAGE, "'-h' and '-H' are mutually "
+ "exclusive options");
+ fd = pw_checkfd(optarg);
+ precrypted = true;
+ if (fd == '-')
+ errx(EX_USAGE, "-H expects a file descriptor");
+ break;
+ case 'h':
+ if (fd != -1)
+ errx(EX_USAGE, "'-h' and '-H' are mutually "
+ "exclusive options");
+ fd = pw_checkfd(optarg);
+ break;
+ case 'D':
+ genconf = true;
+ break;
+ case 'b':
+ cmdcnf->home = optarg;
+ break;
+ case 'N':
+ dryrun = true;
+ break;
+ case 'P':
+ pretty = true;
+ break;
+ case 'y':
+ cmdcnf->nispasswd = optarg;
+ break;
+ case 'Y':
+ nis = true;
+ break;
+ }
+ }
+
+ if (geteuid() != 0 && ! dryrun)
+ errx(EX_NOPERM, "you must be root");
+
+ if (quiet)
+ freopen(_PATH_DEVNULL, "w", stderr);
+
+ cnf = get_userconfig(cfg);
+
+ mix_config(cmdcnf, cnf);
+ if (default_passwd)
+ cmdcnf->default_password = boolean_val(default_passwd,
+ cnf->default_password);
+ if (genconf) {
+ if (name != NULL)
+ errx(EX_DATAERR, "can't combine `-D' with `-n name'");
+ if (userid != NULL) {
+ if ((p = strtok(userid, ", \t")) != NULL)
+ cmdcnf->min_uid = pw_checkid(p, UID_MAX);
+ if (cmdcnf->min_uid == 0)
+ cmdcnf->min_uid = 1000;
+ if ((p = strtok(NULL, " ,\t")) != NULL)
+ cmdcnf->max_uid = pw_checkid(p, UID_MAX);
+ if (cmdcnf->max_uid == 0)
+ cmdcnf->max_uid = 32000;
+ }
+ if (groupid != NULL) {
+ if ((p = strtok(groupid, ", \t")) != NULL)
+ cmdcnf->min_gid = pw_checkid(p, GID_MAX);
+ if (cmdcnf->min_gid == 0)
+ cmdcnf->min_gid = 1000;
+ if ((p = strtok(NULL, " ,\t")) != NULL)
+ cmdcnf->max_gid = pw_checkid(p, GID_MAX);
+ if (cmdcnf->max_gid == 0)
+ cmdcnf->max_gid = 32000;
+ }
+ if (write_userconfig(cmdcnf, cfg))
+ return (EXIT_SUCCESS);
+ err(EX_IOERR, "config update");
+ }
+
+ if (userid)
+ id = pw_checkid(userid, UID_MAX);
+ if (id < 0 && name == NULL)
+ errx(EX_DATAERR, "user name or id required");
+
+ if (name == NULL)
+ errx(EX_DATAERR, "login name required");
+
+ if (GETPWNAM(name) != NULL)
+ errx(EX_DATAERR, "login name `%s' already exists", name);
+
+ pwd = &fakeuser;
+ pwd->pw_name = name;
+ pwd->pw_class = cmdcnf->default_class ? cmdcnf->default_class : "";
+ pwd->pw_uid = pw_uidpolicy(cmdcnf, id);
+ pwd->pw_gid = pw_gidpolicy(cnf, grname, pwd->pw_name,
+ (gid_t) pwd->pw_uid, dryrun);
+ pwd->pw_change = cmdcnf->password_days;
+ pwd->pw_expire = cmdcnf->expire_days;
+ pwd->pw_dir = pw_homepolicy(cmdcnf, homedir, pwd->pw_name);
+ pwd->pw_shell = pw_shellpolicy(cmdcnf);
+ lc = login_getpwclass(pwd);
+ if (lc == NULL || login_setcryptfmt(lc, "sha512", NULL) == NULL)
+ warn("setting crypt(3) format");
+ login_close(lc);
+ pwd->pw_passwd = pw_password(cmdcnf, pwd->pw_name, dryrun);
+ if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0)
+ warnx("WARNING: new account `%s' has a uid of 0 "
+ "(superuser access!)", pwd->pw_name);
+ if (gecos)
+ pwd->pw_gecos = gecos;
+
+ if (fd != -1)
+ pw_set_passwd(pwd, fd, precrypted, false);
+
+ if (dryrun)
+ return (print_user(pwd, pretty, false));
+
+ if ((rc = addpwent(pwd)) != 0) {
+ if (rc == -1)
+ errx(EX_IOERR, "user '%s' already exists",
+ pwd->pw_name);
+ else if (rc != 0)
+ err(EX_IOERR, "passwd file update");
+ }
+ if (nis && cmdcnf->nispasswd && *cmdcnf->nispasswd == '/') {
+ printf("%s\n", cmdcnf->nispasswd);
+ rc = addnispwent(cmdcnf->nispasswd, pwd);
+ if (rc == -1)
+ warnx("User '%s' already exists in NIS passwd",
+ pwd->pw_name);
+ else if (rc != 0)
+ warn("NIS passwd update");
+ /* NOTE: we treat NIS-only update errors as non-fatal */
+ }
+
+ if (cmdcnf->groups != NULL) {
+ for (i = 0; i < cmdcnf->groups->sl_cur; i++) {
+ grp = GETGRNAM(cmdcnf->groups->sl_str[i]);
+ grp = gr_add(grp, pwd->pw_name);
+ /*
+ * grp can only be NULL in 2 cases:
+ * - the new member is already a member
+ * - a problem with memory occurs
+ * in both cases we want to skip now.
+ */
+ if (grp == NULL)
+ continue;
+ chggrent(grp->gr_name, grp);
+ free(grp);
+ }
+ }
+
+ pwd = GETPWNAM(name);
+ if (pwd == NULL)
+ errx(EX_NOUSER, "user '%s' disappeared during update", name);
+
+ grp = GETGRGID(pwd->pw_gid);
+ pw_log(cnf, M_ADD, W_USER, "%s(%ju):%s(%ju):%s:%s:%s",
+ pwd->pw_name, (uintmax_t)pwd->pw_uid,
+ grp ? grp->gr_name : "unknown",
+ (uintmax_t)(grp ? grp->gr_gid : (uid_t)-1),
+ pwd->pw_gecos, pwd->pw_dir, pwd->pw_shell);
+
+ /*
+ * let's touch and chown the user's mail file. This is not
+ * strictly necessary under BSD with a 0755 maildir but it also
+ * doesn't hurt anything to create the empty mailfile
+ */
+ if (PWALTDIR() != PWF_ALT) {
+ snprintf(path, sizeof(path), "%s/%s", _PATH_MAILDIR,
+ pwd->pw_name);
+ /* Preserve contents & mtime */
+ close(openat(conf.rootfd, path +1, O_RDWR | O_CREAT, 0600));
+ fchownat(conf.rootfd, path + 1, pwd->pw_uid, pwd->pw_gid,
+ AT_SYMLINK_NOFOLLOW);
+ }
+
+ /*
+ * Let's create and populate the user's home directory. Note
+ * that this also `works' for editing users if -m is used, but
+ * existing files will *not* be overwritten.
+ */
+ if (PWALTDIR() != PWF_ALT && createhome && pwd->pw_dir &&
+ *pwd->pw_dir == '/' && pwd->pw_dir[1])
+ create_and_populate_homedir(cmdcnf, pwd, cmdcnf->dotdir,
+ cmdcnf->homemode, false);
+
+ if (!PWALTDIR() && cmdcnf->newmail && *cmdcnf->newmail &&
+ (fp = fopen(cnf->newmail, "r")) != NULL) {
+ if ((pfp = popen(_PATH_SENDMAIL " -t", "w")) == NULL)
+ warn("sendmail");
+ else {
+ fprintf(pfp, "From: root\n" "To: %s\n"
+ "Subject: Welcome!\n\n", pwd->pw_name);
+ while (fgets(line, sizeof(line), fp) != NULL) {
+ /* Do substitutions? */
+ fputs(line, pfp);
+ }
+ pclose(pfp);
+ pw_log(cnf, M_ADD, W_USER, "%s(%ju) new user mail sent",
+ pwd->pw_name, (uintmax_t)pwd->pw_uid);
+ }
+ fclose(fp);
+ }
+
+ if (nis && nis_update() == 0)
+ pw_log(cnf, M_ADD, W_USER, "NIS maps updated");
+
+ return (EXIT_SUCCESS);
+}
+
+int
+pw_user_mod(int argc, char **argv, char *arg1)
+{
+ struct userconf *cnf;
+ struct passwd *pwd;
+ struct group *grp;
+ StringList *groups = NULL;
+ char args[] = "C:qn:u:c:d:e:p:g:G:mM:l:k:s:w:L:h:H:NPYy:";
+ const char *cfg;
+ char *gecos, *homedir, *grname, *name, *newname, *walk, *skel, *shell;
+ char *passwd, *class, *nispasswd;
+ login_cap_t *lc;
+ struct stat st;
+ intmax_t id = -1;
+ int ch, fd = -1;
+ size_t i, j;
+ bool quiet, createhome, pretty, dryrun, nis, edited, docreatehome;
+ bool precrypted;
+ mode_t homemode = 0;
+ time_t expire_days, password_days, now;
+
+ expire_days = password_days = -1;
+ gecos = homedir = grname = name = newname = skel = shell =NULL;
+ passwd = NULL;
+ class = nispasswd = NULL;
+ quiet = createhome = pretty = dryrun = nis = precrypted = false;
+ edited = docreatehome = false;
+
+ if (arg1 != NULL) {
+ if (arg1[strspn(arg1, "0123456789")] == '\0')
+ id = pw_checkid(arg1, UID_MAX);
+ else
+ name = arg1;
+ }
+
+ while ((ch = getopt(argc, argv, args)) != -1) {
+ switch (ch) {
+ case 'C':
+ cfg = optarg;
+ break;
+ case 'q':
+ quiet = true;
+ break;
+ case 'n':
+ name = optarg;
+ break;
+ case 'u':
+ id = pw_checkid(optarg, UID_MAX);
+ break;
+ case 'c':
+ gecos = pw_checkname(optarg, 1);
+ break;
+ case 'd':
+ homedir = optarg;
+ break;
+ case 'e':
+ now = time(NULL);
+ expire_days = parse_date(now, optarg);
+ break;
+ case 'p':
+ now = time(NULL);
+ password_days = parse_date(now, optarg);
+ break;
+ case 'g':
+ group_from_name_or_id(optarg);
+ grname = optarg;
+ break;
+ case 'G':
+ split_groups(&groups, optarg);
+ break;
+ case 'm':
+ createhome = true;
+ break;
+ case 'M':
+ homemode = validate_mode(optarg);
+ break;
+ case 'l':
+ newname = optarg;
+ break;
+ case 'k':
+ walk = skel = optarg;
+ if (*walk == '/')
+ walk++;
+ if (fstatat(conf.rootfd, walk, &st, 0) == -1)
+ errx(EX_OSFILE, "skeleton `%s' does not "
+ "exists", skel);
+ if (!S_ISDIR(st.st_mode))
+ errx(EX_OSFILE, "skeleton `%s' is not a "
+ "directory", skel);
+ break;
+ case 's':
+ shell = optarg;
+ break;
+ case 'w':
+ passwd = optarg;
+ break;
+ case 'L':
+ class = pw_checkname(optarg, 0);
+ break;
+ case 'H':
+ if (fd != -1)
+ errx(EX_USAGE, "'-h' and '-H' are mutually "
+ "exclusive options");
+ fd = pw_checkfd(optarg);
+ precrypted = true;
+ if (fd == '-')
+ errx(EX_USAGE, "-H expects a file descriptor");
+ break;
+ case 'h':
+ if (fd != -1)
+ errx(EX_USAGE, "'-h' and '-H' are mutually "
+ "exclusive options");
+ fd = pw_checkfd(optarg);
+ break;
+ case 'N':
+ dryrun = true;
+ break;
+ case 'P':
+ pretty = true;
+ break;
+ case 'y':
+ nispasswd = optarg;
+ break;
+ case 'Y':
+ nis = true;
+ break;
+ }
+ }
+
+ if (geteuid() != 0 && ! dryrun)
+ errx(EX_NOPERM, "you must be root");
+
+ if (quiet)
+ freopen(_PATH_DEVNULL, "w", stderr);
+
+ cnf = get_userconfig(cfg);
+
+ if (id < 0 && name == NULL)
+ errx(EX_DATAERR, "username or id required");
+
+ pwd = (name != NULL) ? GETPWNAM(pw_checkname(name, 0)) : GETPWUID(id);
+ if (pwd == NULL) {
+ if (name == NULL)
+ errx(EX_NOUSER, "no such uid `%ju'",
+ (uintmax_t) id);
+ errx(EX_NOUSER, "no such user `%s'", name);
+ }
+
+ if (name == NULL)
+ name = pwd->pw_name;
+
+ if (nis && nispasswd == NULL)
+ nispasswd = cnf->nispasswd;
+
+ if (PWF._altdir == PWF_REGULAR &&
+ ((pwd->pw_fields & _PWF_SOURCE) != _PWF_FILES)) {
+ if ((pwd->pw_fields & _PWF_SOURCE) == _PWF_NIS) {
+ if (!nis && nispasswd && *nispasswd != '/')
+ errx(EX_NOUSER, "Cannot modify NIS user `%s'",
+ name);
+ } else {
+ errx(EX_NOUSER, "Cannot modify non local user `%s'",
+ name);
+ }
+ }
+
+ if (newname) {
+ if (strcmp(pwd->pw_name, "root") == 0)
+ errx(EX_DATAERR, "can't rename `root' account");
+ if (strcmp(pwd->pw_name, newname) != 0) {
+ pwd->pw_name = pw_checkname(newname, 0);
+ edited = true;
+ }
+ }
+
+ if (id > 0 && pwd->pw_uid != id) {
+ pwd->pw_uid = id;
+ edited = true;
+ if (pwd->pw_uid != 0 && strcmp(pwd->pw_name, "root") == 0)
+ errx(EX_DATAERR, "can't change uid of `root' account");
+ if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0)
+ warnx("WARNING: account `%s' will have a uid of 0 "
+ "(superuser access!)", pwd->pw_name);
+ }
+
+ if (grname && pwd->pw_uid != 0) {
+ grp = GETGRNAM(grname);
+ if (grp == NULL)
+ grp = GETGRGID(pw_checkid(grname, GID_MAX));
+ if (grp->gr_gid != pwd->pw_gid) {
+ pwd->pw_gid = grp->gr_gid;
+ edited = true;
+ }
+ }
+
+ if (password_days >= 0 && pwd->pw_change != password_days) {
+ pwd->pw_change = password_days;
+ edited = true;
+ }
+
+ if (expire_days >= 0 && pwd->pw_expire != expire_days) {
+ pwd->pw_expire = expire_days;
+ edited = true;
+ }
+
+ if (shell) {
+ shell = shell_path(cnf->shelldir, cnf->shells, shell);
+ if (shell == NULL)
+ shell = "";
+ if (strcmp(shell, pwd->pw_shell) != 0) {
+ pwd->pw_shell = shell;
+ edited = true;
+ }
+ }
+
+ if (class && strcmp(pwd->pw_class, class) != 0) {
+ pwd->pw_class = class;
+ edited = true;
+ }
+
+ if (homedir && strcmp(pwd->pw_dir, homedir) != 0) {
+ pwd->pw_dir = homedir;
+ edited = true;
+ if (fstatat(conf.rootfd, pwd->pw_dir, &st, 0) == -1) {
+ if (!createhome)
+ warnx("WARNING: home `%s' does not exist",
+ pwd->pw_dir);
+ else
+ docreatehome = true;
+ } else if (!S_ISDIR(st.st_mode)) {
+ warnx("WARNING: home `%s' is not a directory",
+ pwd->pw_dir);
+ }
+ }
+
+ if (passwd && conf.fd == -1) {
+ lc = login_getpwclass(pwd);
+ if (lc == NULL || login_setcryptfmt(lc, "sha512", NULL) == NULL)
+ warn("setting crypt(3) format");
+ login_close(lc);
+ cnf->default_password = boolean_val(passwd,
+ cnf->default_password);
+ pwd->pw_passwd = pw_password(cnf, pwd->pw_name, dryrun);
+ edited = true;
+ }
+
+ if (gecos && strcmp(pwd->pw_gecos, gecos) != 0) {
+ pwd->pw_gecos = gecos;
+ edited = true;
+ }
+
+ if (fd != -1)
+ edited = pw_set_passwd(pwd, fd, precrypted, true);
+
+ if (dryrun)
+ return (print_user(pwd, pretty, false));
+
+ if (edited) /* Only updated this if required */
+ perform_chgpwent(name, pwd, nis ? nispasswd : NULL);
+ /* Now perform the needed changes concern groups */
+ if (groups != NULL) {
+ /* Delete User from groups using old name */
+ SETGRENT();
+ while ((grp = GETGRENT()) != NULL) {
+ if (grp->gr_mem == NULL)
+ continue;
+ for (i = 0; grp->gr_mem[i] != NULL; i++) {
+ if (strcmp(grp->gr_mem[i] , name) != 0)
+ continue;
+ for (j = i; grp->gr_mem[j] != NULL ; j++)
+ grp->gr_mem[j] = grp->gr_mem[j+1];
+ chggrent(grp->gr_name, grp);
+ break;
+ }
+ }
+ ENDGRENT();
+ /* Add the user to the needed groups */
+ for (i = 0; i < groups->sl_cur; i++) {
+ grp = GETGRNAM(groups->sl_str[i]);
+ grp = gr_add(grp, pwd->pw_name);
+ if (grp == NULL)
+ continue;
+ chggrent(grp->gr_name, grp);
+ free(grp);
+ }
+ }
+ /* In case of rename we need to walk over the different groups */
+ if (newname) {
+ SETGRENT();
+ while ((grp = GETGRENT()) != NULL) {
+ if (grp->gr_mem == NULL)
+ continue;
+ for (i = 0; grp->gr_mem[i] != NULL; i++) {
+ if (strcmp(grp->gr_mem[i], name) != 0)
+ continue;
+ grp->gr_mem[i] = newname;
+ chggrent(grp->gr_name, grp);
+ break;
+ }
+ }
+ }
+
+ /* go get a current version of pwd */
+ if (newname)
+ name = newname;
+ pwd = GETPWNAM(name);
+ if (pwd == NULL)
+ errx(EX_NOUSER, "user '%s' disappeared during update", name);
+ grp = GETGRGID(pwd->pw_gid);
+ pw_log(cnf, M_UPDATE, W_USER, "%s(%ju):%s(%ju):%s:%s:%s",
+ pwd->pw_name, (uintmax_t)pwd->pw_uid,
+ grp ? grp->gr_name : "unknown",
+ (uintmax_t)(grp ? grp->gr_gid : (uid_t)-1),
+ pwd->pw_gecos, pwd->pw_dir, pwd->pw_shell);
+
+ /*
+ * Let's create and populate the user's home directory. Note
+ * that this also `works' for editing users if -m is used, but
+ * existing files will *not* be overwritten.
+ */
+ if (PWALTDIR() != PWF_ALT && docreatehome && pwd->pw_dir &&
+ *pwd->pw_dir == '/' && pwd->pw_dir[1]) {
+ if (!skel)
+ skel = cnf->dotdir;
+ if (homemode == 0)
+ homemode = cnf->homemode;
+ create_and_populate_homedir(cnf, pwd, skel, homemode, true);
+ }
+
+ if (nis && nis_update() == 0)
+ pw_log(cnf, M_UPDATE, W_USER, "NIS maps updated");
+
+ return (EXIT_SUCCESS);
+}
diff --git a/usr.sbin/pw/pw_utils.c b/usr.sbin/pw/pw_utils.c
new file mode 100644
index 0000000..1a4f812
--- /dev/null
+++ b/usr.sbin/pw/pw_utils.c
@@ -0,0 +1,99 @@
+/*-
+ * Copyright (C) 2015 Baptiste Daroussin <bapt@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
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <err.h>
+#include <inttypes.h>
+#include <sysexits.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "pw.h"
+
+int
+pw_checkfd(char *nptr)
+{
+ const char *errstr;
+ int fd = -1;
+
+ if (strcmp(nptr, "-") == 0)
+ return '-';
+ fd = strtonum(nptr, 0, INT_MAX, &errstr);
+ if (errstr != NULL)
+ errx(EX_USAGE, "Bad file descriptor '%s': %s",
+ nptr, errstr);
+ return (fd);
+}
+
+uintmax_t
+pw_checkid(char *nptr, uintmax_t maxval)
+{
+ const char *errstr = NULL;
+ uintmax_t id;
+
+ id = strtounum(nptr, 0, maxval, &errstr);
+ if (errstr)
+ errx(EX_USAGE, "Bad id '%s': %s", nptr, errstr);
+ return (id);
+}
+
+struct userconf *
+get_userconfig(const char *config)
+{
+ char defaultcfg[MAXPATHLEN];
+
+ if (config != NULL)
+ return (read_userconfig(config));
+ snprintf(defaultcfg, sizeof(defaultcfg), "%s/pw.conf", conf.etcpath);
+ return (read_userconfig(defaultcfg));
+}
+
+int
+nis_update(void) {
+ pid_t pid;
+ int i;
+
+ fflush(NULL);
+ if ((pid = fork()) == -1) {
+ warn("fork()");
+ return (1);
+ }
+ if (pid == 0) {
+ execlp("/usr/bin/make", "make", "-C", "/var/yp/", (char*) NULL);
+ _exit(1);
+ }
+ waitpid(pid, &i, 0);
+ if ((i = WEXITSTATUS(i)) != 0)
+ errx(i, "make exited with status %d", i);
+ return (i);
+}
diff --git a/usr.sbin/pw/pw_vpw.c b/usr.sbin/pw/pw_vpw.c
new file mode 100644
index 0000000..2d1c75b
--- /dev/null
+++ b/usr.sbin/pw/pw_vpw.c
@@ -0,0 +1,205 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <pwd.h>
+#include <grp.h>
+#include <libutil.h>
+#define _WITH_GETLINE
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/param.h>
+#include <err.h>
+
+#include "pwupd.h"
+
+static FILE * pwd_fp = NULL;
+
+void
+vendpwent(void)
+{
+ if (pwd_fp != NULL) {
+ fclose(pwd_fp);
+ pwd_fp = NULL;
+ }
+}
+
+void
+vsetpwent(void)
+{
+ vendpwent();
+}
+
+static struct passwd *
+vnextpwent(char const *nam, uid_t uid, int doclose)
+{
+ struct passwd *pw;
+ char *line;
+ size_t linecap;
+ ssize_t linelen;
+
+ pw = NULL;
+ line = NULL;
+ linecap = 0;
+
+ if (pwd_fp != NULL || (pwd_fp = fopen(getpwpath(_MASTERPASSWD), "r")) != NULL) {
+ while ((linelen = getline(&line, &linecap, pwd_fp)) > 0) {
+ /* Skip comments and empty lines */
+ if (*line == '\n' || *line == '#')
+ continue;
+ /* trim latest \n */
+ if (line[linelen - 1 ] == '\n')
+ line[linelen - 1] = '\0';
+ pw = pw_scan(line, PWSCAN_MASTER);
+ if (pw == NULL)
+ errx(EXIT_FAILURE, "Invalid user entry in '%s':"
+ " '%s'", getpwpath(_MASTERPASSWD), line);
+ if (uid != (uid_t)-1) {
+ if (uid == pw->pw_uid)
+ break;
+ } else if (nam != NULL) {
+ if (strcmp(nam, pw->pw_name) == 0)
+ break;
+ } else
+ break;
+ free(pw);
+ pw = NULL;
+ }
+ if (doclose)
+ vendpwent();
+ }
+ free(line);
+
+ return (pw);
+}
+
+struct passwd *
+vgetpwent(void)
+{
+ return vnextpwent(NULL, -1, 0);
+}
+
+struct passwd *
+vgetpwuid(uid_t uid)
+{
+ return vnextpwent(NULL, uid, 1);
+}
+
+struct passwd *
+vgetpwnam(const char * nam)
+{
+ return vnextpwent(nam, -1, 1);
+}
+
+
+static FILE * grp_fp = NULL;
+
+void
+vendgrent(void)
+{
+ if (grp_fp != NULL) {
+ fclose(grp_fp);
+ grp_fp = NULL;
+ }
+}
+
+RET_SETGRENT
+vsetgrent(void)
+{
+ vendgrent();
+#if defined(__FreeBSD__)
+ return 0;
+#endif
+}
+
+static struct group *
+vnextgrent(char const *nam, gid_t gid, int doclose)
+{
+ struct group *gr;
+ char *line;
+ size_t linecap;
+ ssize_t linelen;
+
+ gr = NULL;
+ line = NULL;
+ linecap = 0;
+
+ if (grp_fp != NULL || (grp_fp = fopen(getgrpath(_GROUP), "r")) != NULL) {
+ while ((linelen = getline(&line, &linecap, grp_fp)) > 0) {
+ /* Skip comments and empty lines */
+ if (*line == '\n' || *line == '#')
+ continue;
+ /* trim latest \n */
+ if (line[linelen - 1 ] == '\n')
+ line[linelen - 1] = '\0';
+ gr = gr_scan(line);
+ if (gr == NULL)
+ errx(EXIT_FAILURE, "Invalid group entry in '%s':"
+ " '%s'", getgrpath(_GROUP), line);
+ if (gid != (gid_t)-1) {
+ if (gid == gr->gr_gid)
+ break;
+ } else if (nam != NULL) {
+ if (strcmp(nam, gr->gr_name) == 0)
+ break;
+ } else
+ break;
+ free(gr);
+ gr = NULL;
+ }
+ if (doclose)
+ vendgrent();
+ }
+ free(line);
+
+ return (gr);
+}
+
+struct group *
+vgetgrent(void)
+{
+ return vnextgrent(NULL, -1, 0);
+}
+
+
+struct group *
+vgetgrgid(gid_t gid)
+{
+ return vnextgrent(NULL, gid, 1);
+}
+
+struct group *
+vgetgrnam(const char * nam)
+{
+ return vnextgrent(nam, -1, 1);
+}
+
diff --git a/usr.sbin/pw/pwupd.c b/usr.sbin/pw/pwupd.c
new file mode 100644
index 0000000..ee23952
--- /dev/null
+++ b/usr.sbin/pw/pwupd.c
@@ -0,0 +1,149 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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/wait.h>
+
+#include <err.h>
+#include <errno.h>
+#include <pwd.h>
+#include <libutil.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "pwupd.h"
+
+char *
+getpwpath(char const * file)
+{
+ static char pathbuf[MAXPATHLEN];
+
+ snprintf(pathbuf, sizeof pathbuf, "%s/%s", conf.etcpath, file);
+
+ return (pathbuf);
+}
+
+static int
+pwdb_check(void)
+{
+ int i = 0;
+ pid_t pid;
+ char *args[10];
+
+ args[i++] = _PATH_PWD_MKDB;
+ args[i++] = "-C";
+
+ if (strcmp(conf.etcpath, _PATH_PWD) != 0) {
+ args[i++] = "-d";
+ args[i++] = conf.etcpath;
+ }
+ args[i++] = getpwpath(_MASTERPASSWD);
+ args[i] = NULL;
+
+ if ((pid = fork()) == -1) /* Error (errno set) */
+ i = errno;
+ else if (pid == 0) { /* Child */
+ execv(args[0], args);
+ _exit(1);
+ } else { /* Parent */
+ waitpid(pid, &i, 0);
+ if (WEXITSTATUS(i))
+ i = EIO;
+ }
+
+ return (i);
+}
+
+static int
+pw_update(struct passwd * pwd, char const * user)
+{
+ struct passwd *pw = NULL;
+ struct passwd *old_pw = NULL;
+ int rc, pfd, tfd;
+
+ if ((rc = pwdb_check()) != 0)
+ return (rc);
+
+ if (pwd != NULL)
+ pw = pw_dup(pwd);
+
+ if (user != NULL)
+ old_pw = GETPWNAM(user);
+
+ if (pw_init(conf.etcpath, NULL))
+ err(1, "pw_init()");
+ if ((pfd = pw_lock()) == -1) {
+ pw_fini();
+ err(1, "pw_lock()");
+ }
+ if ((tfd = pw_tmp(-1)) == -1) {
+ pw_fini();
+ err(1, "pw_tmp()");
+ }
+ if (pw_copy(pfd, tfd, pw, old_pw) == -1) {
+ pw_fini();
+ err(1, "pw_copy()");
+ }
+ /*
+ * in case of deletion of a user, the whole database
+ * needs to be regenerated
+ */
+ if (pw_mkdb(pw != NULL ? pw->pw_name : NULL) == -1) {
+ pw_fini();
+ err(1, "pw_mkdb()");
+ }
+ free(pw);
+ pw_fini();
+
+ return (0);
+}
+
+int
+addpwent(struct passwd * pwd)
+{
+
+ return (pw_update(pwd, NULL));
+}
+
+int
+chgpwent(char const * login, struct passwd * pwd)
+{
+
+ return (pw_update(pwd, login));
+}
+
+int
+delpwent(struct passwd * pwd)
+{
+
+ return (pw_update(NULL, pwd->pw_name));
+}
diff --git a/usr.sbin/pw/pwupd.h b/usr.sbin/pw/pwupd.h
new file mode 100644
index 0000000..7fecffb
--- /dev/null
+++ b/usr.sbin/pw/pwupd.h
@@ -0,0 +1,152 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 _PWUPD_H_
+#define _PWUPD_H_
+
+#include <sys/cdefs.h>
+#include <sys/param.h>
+#include <sys/types.h>
+
+#include <pwd.h>
+#include <grp.h>
+#include <stdbool.h>
+#include <stringlist.h>
+
+#if defined(__FreeBSD__)
+#define RET_SETGRENT int
+#else
+#define RET_SETGRENT void
+#endif
+
+struct pwf {
+ int _altdir;
+ void (*_setpwent)(void);
+ void (*_endpwent)(void);
+ struct passwd * (*_getpwent)(void);
+ struct passwd * (*_getpwuid)(uid_t uid);
+ struct passwd * (*_getpwnam)(const char * nam);
+ RET_SETGRENT (*_setgrent)(void);
+ void (*_endgrent)(void);
+ struct group * (*_getgrent)(void);
+ struct group * (*_getgrgid)(gid_t gid);
+ struct group * (*_getgrnam)(const char * nam);
+};
+
+struct userconf {
+ int default_password; /* Default password for new users? */
+ int reuse_uids; /* Reuse uids? */
+ int reuse_gids; /* Reuse gids? */
+ char *nispasswd; /* Path to NIS version of the passwd file */
+ char *dotdir; /* Where to obtain skeleton files */
+ char *newmail; /* Mail to send to new accounts */
+ char *logfile; /* Where to log changes */
+ char *home; /* Where to create home directory */
+ mode_t homemode; /* Home directory permissions */
+ char *shelldir; /* Where shells are located */
+ char **shells; /* List of shells */
+ char *shell_default; /* Default shell */
+ char *default_group; /* Default group number */
+ StringList *groups; /* Default (additional) groups */
+ char *default_class; /* Default user class */
+ uid_t min_uid, max_uid; /* Allowed range of uids */
+ gid_t min_gid, max_gid; /* Allowed range of gids */
+ time_t expire_days; /* Days to expiry */
+ time_t password_days; /* Days to password expiry */
+};
+
+struct pwconf {
+ char rootdir[MAXPATHLEN];
+ char etcpath[MAXPATHLEN];
+ int fd;
+ int rootfd;
+ bool checkduplicate;
+};
+
+extern struct pwf PWF;
+extern struct pwf VPWF;
+extern struct pwconf conf;
+
+#define SETPWENT() PWF._setpwent()
+#define ENDPWENT() PWF._endpwent()
+#define GETPWENT() PWF._getpwent()
+#define GETPWUID(uid) PWF._getpwuid(uid)
+#define GETPWNAM(nam) PWF._getpwnam(nam)
+
+#define SETGRENT() PWF._setgrent()
+#define ENDGRENT() PWF._endgrent()
+#define GETGRENT() PWF._getgrent()
+#define GETGRGID(gid) PWF._getgrgid(gid)
+#define GETGRNAM(nam) PWF._getgrnam(nam)
+
+#define PWF_REGULAR 0
+#define PWF_ALT 1
+#define PWF_ROOTDIR 2
+
+#define PWALTDIR() PWF._altdir
+#ifndef _PATH_PWD
+#define _PATH_PWD "/etc"
+#endif
+#ifndef _GROUP
+#define _GROUP "group"
+#endif
+#ifndef _MASTERPASSWD
+#define _MASTERPASSWD "master.passwd"
+#endif
+
+__BEGIN_DECLS
+int addpwent(struct passwd * pwd);
+int delpwent(struct passwd * pwd);
+int chgpwent(char const * login, struct passwd * pwd);
+
+char * getpwpath(char const * file);
+
+int addgrent(struct group * grp);
+int delgrent(struct group * grp);
+int chggrent(char const * name, struct group * grp);
+
+char * getgrpath(const char *file);
+
+void vsetpwent(void);
+void vendpwent(void);
+struct passwd * vgetpwent(void);
+struct passwd * vgetpwuid(uid_t uid);
+struct passwd * vgetpwnam(const char * nam);
+
+struct group * vgetgrent(void);
+struct group * vgetgrgid(gid_t gid);
+struct group * vgetgrnam(const char * nam);
+RET_SETGRENT vsetgrent(void);
+void vendgrent(void);
+
+void copymkdir(int rootfd, char const * dir, int skelfd, mode_t mode, uid_t uid,
+ gid_t gid, int flags);
+void rm_r(int rootfd, char const * dir, uid_t uid);
+__END_DECLS
+
+#endif /* !_PWUPD_H */
diff --git a/usr.sbin/pw/rm_r.c b/usr.sbin/pw/rm_r.c
new file mode 100644
index 0000000..172c7b0
--- /dev/null
+++ b/usr.sbin/pw/rm_r.c
@@ -0,0 +1,70 @@
+/*-
+ * Copyright (C) 1996
+ * David L. Nugent. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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/stat.h>
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "pwupd.h"
+
+void
+rm_r(int rootfd, const char *path, uid_t uid)
+{
+ int dirfd;
+ DIR *d;
+ struct dirent *e;
+ struct stat st;
+
+ if (*path == '/')
+ path++;
+
+ dirfd = openat(rootfd, path, O_DIRECTORY);
+
+ d = fdopendir(dirfd);
+ while ((e = readdir(d)) != NULL) {
+ if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0)
+ continue;
+
+ if (fstatat(dirfd, e->d_name, &st, AT_SYMLINK_NOFOLLOW) != 0)
+ continue;
+ if (S_ISDIR(st.st_mode))
+ rm_r(dirfd, e->d_name, uid);
+ else if (S_ISLNK(st.st_mode) || st.st_uid == uid)
+ unlinkat(dirfd, e->d_name, 0);
+ }
+ closedir(d);
+ if (fstatat(rootfd, path, &st, AT_SYMLINK_NOFOLLOW) != 0)
+ return;
+ unlinkat(rootfd, path, S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0);
+}
diff --git a/usr.sbin/pw/strtounum.c b/usr.sbin/pw/strtounum.c
new file mode 100644
index 0000000..b2fefeb
--- /dev/null
+++ b/usr.sbin/pw/strtounum.c
@@ -0,0 +1,72 @@
+/*-
+ * Copyright (C) 2015 Baptiste Daroussin <bapt@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
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#include "pw.h"
+
+uintmax_t
+strtounum(const char * __restrict np, uintmax_t minval, uintmax_t maxval,
+ const char ** __restrict errpp)
+{
+ char *endp;
+ uintmax_t ret;
+
+ *errpp = NULL;
+ if (minval > maxval) {
+ errno = EINVAL;
+ if (errpp != NULL)
+ *errpp = "invalid";
+ return (0);
+ }
+ errno = 0;
+ ret = strtoumax(np, &endp, 10);
+ if (endp == np || *endp != '\0') {
+ errno = EINVAL;
+ if (errpp != NULL)
+ *errpp = "invalid";
+ return (0);
+ }
+ if (ret < minval) {
+ errno = ERANGE;
+ if (errpp != NULL)
+ *errpp = "too small";
+ return (0);
+ }
+ if (errno == ERANGE || ret > maxval) {
+ errno = ERANGE;
+ if (errpp != NULL)
+ *errpp = "too large";
+ return (0);
+ }
+ return (ret);
+}
diff --git a/usr.sbin/pw/tests/Makefile b/usr.sbin/pw/tests/Makefile
new file mode 100644
index 0000000..27f0ac4
--- /dev/null
+++ b/usr.sbin/pw/tests/Makefile
@@ -0,0 +1,22 @@
+# $FreeBSD$
+
+ATF_TESTS_SH= pw_etcdir \
+ pw_lock \
+ pw_config \
+ pw_groupadd \
+ pw_groupdel \
+ pw_groupmod \
+ pw_useradd \
+ pw_userdel \
+ pw_usermod \
+ pw_usernext
+
+.for tp in ${ATF_TESTS_SH}
+TEST_METADATA.${tp}+= required_user="root"
+.endfor
+
+FILES= group helper_functions.shin master.passwd pw.conf \
+ pw-modified.conf
+FILESDIR= ${TESTSDIR}
+
+.include <bsd.test.mk>
diff --git a/usr.sbin/pw/tests/group b/usr.sbin/pw/tests/group
new file mode 100644
index 0000000..620c588
--- /dev/null
+++ b/usr.sbin/pw/tests/group
@@ -0,0 +1,3 @@
+# $FreeBSD$
+#
+wheel:*:0:root
diff --git a/usr.sbin/pw/tests/helper_functions.shin b/usr.sbin/pw/tests/helper_functions.shin
new file mode 100755
index 0000000..1ee731a
--- /dev/null
+++ b/usr.sbin/pw/tests/helper_functions.shin
@@ -0,0 +1,32 @@
+# $FreeBSD$
+
+# The pw command
+PW="pw -V ${HOME}"
+RPW="pw -R ${HOME}"
+
+# Workdir to run tests in
+TESTDIR=$(atf_get_srcdir)
+
+# Populate the files pw needs to use into $HOME
+populate_etc_skel() {
+ cp ${TESTDIR}/master.passwd ${HOME} || \
+ atf_fail "Populating master.passwd in ${HOME}"
+ cp ${TESTDIR}/group ${HOME} || atf_fail "Populating group in ${HOME}"
+
+ # Generate the passwd file
+ pwd_mkdb -p -d ${HOME} ${HOME}/master.passwd || \
+ atf_fail "generate passwd from master.passwd"
+}
+
+# Populate the files pw needs to use into $HOME/etc
+populate_root_etc_skel() {
+ mkdir ${HOME}/etc
+ cp ${TESTDIR}/master.passwd ${HOME}/etc || \
+ atf_fail "Populating master.passwd in ${HOME}/etc"
+ cp ${TESTDIR}/group ${HOME}/etc || \
+ atf_fail "Populating group in ${HOME}/etc"
+
+ # Generate the passwd file
+ pwd_mkdb -p -d ${HOME}/etc ${HOME}//etc/master.passwd || \
+ atf_fail "generate passwd from master.passwd"
+}
diff --git a/usr.sbin/pw/tests/master.passwd b/usr.sbin/pw/tests/master.passwd
new file mode 100644
index 0000000..f7dc837
--- /dev/null
+++ b/usr.sbin/pw/tests/master.passwd
@@ -0,0 +1,4 @@
+# $FreeBSD$
+#
+root:*:0:0::0:0:Charlie &:/root:/bin/csh
+toor:*:0:0::0:0:Bourne-again Superuser:/root:
diff --git a/usr.sbin/pw/tests/pw-modified.conf b/usr.sbin/pw/tests/pw-modified.conf
new file mode 100644
index 0000000..84f44e7
--- /dev/null
+++ b/usr.sbin/pw/tests/pw-modified.conf
@@ -0,0 +1,62 @@
+#
+# pw.conf - user/group configuration defaults
+#
+
+# Password for new users? no=nologin yes=loginid none=blank random=random
+defaultpasswd = "no"
+
+# Reuse gaps in uid sequence? (yes or no)
+reuseuids = "no"
+
+# Reuse gaps in gid sequence? (yes or no)
+reusegids = "no"
+
+# Path to the NIS passwd file (blank or 'no' for none)
+nispasswd =
+
+# Obtain default dotfiles from this directory
+skeleton = "/usr/share/skel"
+
+# Mail this file to new user (/etc/newuser.msg or no)
+newmail = "no"
+
+# Log add/change/remove information in this file
+logfile = "/var/log/userlog"
+
+# Root directory in which $HOME directory is created
+home = "/home"
+
+# Mode for the new $HOME directory, will be modified by umask
+homemode = 0777
+
+# Colon separated list of directories containing valid shells
+shellpath = "/bin"
+
+# Comma separated list of available shells (without paths)
+shells = "sh","csh","tcsh"
+
+# Default shell (without path)
+defaultshell = "sh"
+
+# Default group (leave blank for new group per user)
+defaultgroup = ""
+
+# Extra groups for new users
+extragroups =
+
+# Default login class for new users
+defaultclass = ""
+
+# Range of valid default user ids
+minuid = 2000
+maxuid = 5000
+
+# Range of valid default group ids
+mingid = 2100
+maxgid = 6000
+
+# Days after which account expires (0=disabled)
+expire_days = 0
+
+# Days after which password expires (0=disabled)
+password_days = 0
diff --git a/usr.sbin/pw/tests/pw.conf b/usr.sbin/pw/tests/pw.conf
new file mode 100644
index 0000000..4e493f6
--- /dev/null
+++ b/usr.sbin/pw/tests/pw.conf
@@ -0,0 +1,62 @@
+#
+# pw.conf - user/group configuration defaults
+#
+
+# Password for new users? no=nologin yes=loginid none=blank random=random
+defaultpasswd = "no"
+
+# Reuse gaps in uid sequence? (yes or no)
+reuseuids = "no"
+
+# Reuse gaps in gid sequence? (yes or no)
+reusegids = "no"
+
+# Path to the NIS passwd file (blank or 'no' for none)
+nispasswd =
+
+# Obtain default dotfiles from this directory
+skeleton = "/usr/share/skel"
+
+# Mail this file to new user (/etc/newuser.msg or no)
+newmail = "no"
+
+# Log add/change/remove information in this file
+logfile = "/var/log/userlog"
+
+# Root directory in which $HOME directory is created
+home = "/home"
+
+# Mode for the new $HOME directory, will be modified by umask
+homemode = 0777
+
+# Colon separated list of directories containing valid shells
+shellpath = "/bin"
+
+# Comma separated list of available shells (without paths)
+shells = "sh","csh","tcsh"
+
+# Default shell (without path)
+defaultshell = "sh"
+
+# Default group (leave blank for new group per user)
+defaultgroup = ""
+
+# Extra groups for new users
+extragroups =
+
+# Default login class for new users
+defaultclass = ""
+
+# Range of valid default user ids
+minuid = 1000
+maxuid = 32000
+
+# Range of valid default group ids
+mingid = 1000
+maxgid = 32000
+
+# Days after which account expires (0=disabled)
+expire_days = 0
+
+# Days after which password expires (0=disabled)
+password_days = 0
diff --git a/usr.sbin/pw/tests/pw_config.sh b/usr.sbin/pw/tests/pw_config.sh
new file mode 100755
index 0000000..fb6489a
--- /dev/null
+++ b/usr.sbin/pw/tests/pw_config.sh
@@ -0,0 +1,26 @@
+# $FreeBSD$
+
+# Import helper functions
+. $(atf_get_srcdir)/helper_functions.shin
+
+atf_test_case generate_config
+generate_config_body() {
+ atf_check -s exit:0 \
+ ${PW} useradd -D -C ${HOME}/foo.conf
+ atf_check -o file:$(atf_get_srcdir)/pw.conf \
+ cat ${HOME}/foo.conf
+}
+
+atf_test_case modify_config_uid_gid_boundaries
+modify_config_uid_gid_boundaries_body() {
+ atf_check -s exit:0 \
+ ${PW} useradd -D -C ${HOME}/foo.conf \
+ -u 2000,5000 -i 2100,6000
+ atf_check -o file:$(atf_get_srcdir)/pw-modified.conf \
+ cat ${HOME}/foo.conf
+}
+
+atf_init_test_cases() {
+ atf_add_test_case generate_config
+ atf_add_test_case modify_config_uid_gid_boundaries
+}
diff --git a/usr.sbin/pw/tests/pw_etcdir.sh b/usr.sbin/pw/tests/pw_etcdir.sh
new file mode 100755
index 0000000..b237789
--- /dev/null
+++ b/usr.sbin/pw/tests/pw_etcdir.sh
@@ -0,0 +1,18 @@
+# $FreeBSD$
+
+# When the '-V directory' option is provided, the directory must exist
+atf_test_case etcdir_must_exist
+etcdir_must_exist_head() {
+ atf_set "descr" "When the '-V directory' option is provided, the directory must exist"
+}
+
+etcdir_must_exist_body() {
+ local fakedir="/this_directory_does_not_exist"
+ atf_check -e inline:"pw: no such directory \`$fakedir'\n" \
+ -s exit:72 -x pw -V ${fakedir} usershow root
+}
+
+atf_init_test_cases() {
+ atf_add_test_case etcdir_must_exist
+}
+
diff --git a/usr.sbin/pw/tests/pw_groupadd.sh b/usr.sbin/pw/tests/pw_groupadd.sh
new file mode 100755
index 0000000..5fa7bef
--- /dev/null
+++ b/usr.sbin/pw/tests/pw_groupadd.sh
@@ -0,0 +1,26 @@
+# $FreeBSD$
+
+# Import helper functions
+. $(atf_get_srcdir)/helper_functions.shin
+
+atf_test_case group_add_gid_too_large
+group_add_gid_too_large_body() {
+ populate_etc_skel
+ atf_check -s exit:64 -e inline:"pw: Bad id '9999999999999': too large\n" \
+ ${PW} groupadd -n test1 -g 9999999999999
+}
+
+atf_test_case group_add_already_exists
+group_add_already_exists_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 ${PW} groupadd foo
+ atf_check -s exit:65 \
+ -e inline:"pw: group name \`foo' already exists\n" \
+ ${PW} groupadd foo
+}
+
+atf_init_test_cases() {
+ atf_add_test_case group_add_gid_too_large
+ atf_add_test_case group_add_already_exists
+}
diff --git a/usr.sbin/pw/tests/pw_groupdel.sh b/usr.sbin/pw/tests/pw_groupdel.sh
new file mode 100755
index 0000000..88cc0e0
--- /dev/null
+++ b/usr.sbin/pw/tests/pw_groupdel.sh
@@ -0,0 +1,24 @@
+# $FreeBSD$
+
+# Import helper functions
+. $(atf_get_srcdir)/helper_functions.shin
+
+
+# Test to make sure we do not accidentially delete wheel when trying to delete
+# an unknown group
+atf_test_case group_do_not_delete_wheel_if_group_unknown
+group_do_not_delete_wheel_if_group_unknown_head() {
+ atf_set "descr" "Make sure we do not consider gid 0 an unknown group"
+}
+group_do_not_delete_wheel_if_group_unknown_body() {
+ populate_etc_skel
+ atf_check -s exit:0 -o inline:"wheel:*:0:root\n" -x ${PW} groupshow wheel
+ atf_check -e inline:"pw: Bad id 'I_do_not_exist': invalid\n" -s exit:64 -x \
+ ${PW} groupdel -g I_do_not_exist
+ atf_check -s exit:0 -o inline:"wheel:*:0:root\n" -x ${PW} groupshow wheel
+}
+
+
+atf_init_test_cases() {
+ atf_add_test_case group_do_not_delete_wheel_if_group_unknown
+}
diff --git a/usr.sbin/pw/tests/pw_groupmod.sh b/usr.sbin/pw/tests/pw_groupmod.sh
new file mode 100755
index 0000000..5806925
--- /dev/null
+++ b/usr.sbin/pw/tests/pw_groupmod.sh
@@ -0,0 +1,118 @@
+# $FreeBSD$
+
+# Import helper functions
+. $(atf_get_srcdir)/helper_functions.shin
+
+
+# Test adding & removing a user from a group
+atf_test_case groupmod_user
+groupmod_user_body() {
+ populate_etc_skel
+ atf_check -s exit:0 ${PW} addgroup test
+ atf_check -s exit:0 ${PW} groupmod test -m root
+ atf_check -s exit:0 -o match:"^test:\*:1001:root$" \
+ grep "^test:\*:.*:root$" $HOME/group
+ atf_check -s exit:0 ${PW} groupmod test -d root
+ atf_check -s exit:0 -o match:"^test:\*:1001:$" \
+ grep "^test:\*:.*:$" $HOME/group
+}
+
+
+# Test adding and removing a user that does not exist
+atf_test_case groupmod_invalid_user
+groupmod_invalid_user_body() {
+ populate_etc_skel
+ atf_check -s exit:0 ${PW} addgroup test
+ atf_check -s exit:67 -e match:"does not exist" ${PW} groupmod test -m foo
+ atf_check -s exit:0 ${PW} groupmod test -d foo
+}
+
+atf_test_case groupmod_bug_193704
+groupmod_bug_193704_head() {
+ atf_set "descr" "Regression test for the #193704 bug"
+}
+groupmod_bug_193704_body() {
+ populate_etc_skel
+ atf_check -s exit:0 -x ${PW} groupadd test
+ atf_check -s exit:0 -x ${PW} groupmod test -l newgroupname
+ atf_check -s exit:65 -e match:"^pw: unknown group" -x ${PW} groupshow test
+}
+
+atf_test_case usermod_bug_185666
+usermod_bug_185666_head() {
+ atf_set "descr" "Regression test for the #185666 bug"
+}
+
+usermod_bug_185666_body() {
+ populate_etc_skel
+ atf_check -s exit:0 -x ${PW} useradd testuser
+ atf_check -s exit:0 -x ${PW} groupadd testgroup
+ atf_check -s exit:0 -x ${PW} groupadd testgroup2
+ atf_check -s exit:0 -x ${PW} usermod testuser -G testgroup
+ atf_check -o inline:"testuser:*:1001:\n" -x ${PW} groupshow testuser
+ atf_check -o inline:"testgroup:*:1002:testuser\n" -x ${PW} groupshow testgroup
+ atf_check -o inline:"testgroup2:*:1003:\n" -x ${PW} groupshow testgroup2
+ atf_check -s exit:0 -x ${PW} usermod testuser -G testgroup2
+ atf_check -o inline:"testuser:*:1001:\n" -x ${PW} groupshow testuser
+ atf_check -o inline:"testgroup:*:1002:\n" -x ${PW} groupshow testgroup
+ atf_check -o inline:"testgroup2:*:1003:testuser\n" -x ${PW} groupshow testgroup2
+}
+
+atf_test_case do_not_duplicate_group_on_gid_change
+do_not_duplicate_group_on_gid_change_head() {
+ atf_set "descr" "Do not duplicate group on gid change"
+}
+
+do_not_duplicate_group_on_gid_change_body() {
+ populate_etc_skel
+ atf_check -s exit:0 -x ${PW} groupadd testgroup
+ atf_check -s exit:0 -x ${PW} groupmod testgroup -g 12345
+ # use grep to see if the entry has not be duplicated
+ atf_check -o inline:"testgroup:*:12345:\n" -s exit:0 -x grep "^testgroup" ${HOME}/group
+}
+
+atf_test_case groupmod_rename
+groupmod_rename_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 ${PW} groupadd foo
+ atf_check -s exit:0 ${PW} groupmod foo -l bar
+ atf_check -s exit:0 -o match:"^bar:.*" \
+ grep "^bar:.*" ${HOME}/group
+}
+
+atf_test_case groupmod_members
+groupmod_members_body() {
+ populate_etc_skel
+
+ for i in user1 user2 user3 user4; do
+ atf_check -s exit:0 ${PW} useradd $i
+ done
+
+ atf_check -s exit:0 ${PW} groupadd foo -M "user1, user2"
+ atf_check -o inline:"foo:*:1005:user1,user2\n" -s exit:0 \
+ ${PW} groupshow foo
+ atf_check -s exit:0 ${PW} groupmod foo -m "user3, user4"
+ atf_check -o inline:"foo:*:1005:user1,user2,user3,user4\n" -s exit:0 \
+ ${PW} groupshow foo
+ atf_check -s exit:0 ${PW} groupmod foo -M "user1, user4"
+ atf_check -o inline:"foo:*:1005:user1,user4\n" -s exit:0 \
+ ${PW} groupshow foo
+ # what about duplicates
+ atf_check -s exit:0 ${PW} groupmod foo -m "user1, user2, user3, user4"
+ atf_check -o inline:"foo:*:1005:user1,user4,user2,user3\n" -s exit:0 \
+ ${PW} groupshow foo
+ atf_check -s exit:0 ${PW} groupmod foo -d "user1, user3"
+ atf_check -o inline:"foo:*:1005:user4,user2\n" -s exit:0 \
+ ${PW} groupshow foo
+}
+
+atf_init_test_cases() {
+ atf_add_test_case groupmod_user
+ atf_add_test_case groupmod_invalid_user
+ atf_add_test_case groupmod_bug_193704
+ atf_add_test_case usermod_bug_185666
+ atf_add_test_case do_not_duplicate_group_on_gid_change
+ atf_add_test_case groupmod_rename
+ atf_add_test_case groupmod_members
+}
diff --git a/usr.sbin/pw/tests/pw_lock.sh b/usr.sbin/pw/tests/pw_lock.sh
new file mode 100755
index 0000000..5ec1b09
--- /dev/null
+++ b/usr.sbin/pw/tests/pw_lock.sh
@@ -0,0 +1,42 @@
+# $FreeBSD$
+
+# Import helper functions
+. $(atf_get_srcdir)/helper_functions.shin
+
+# Test locking and unlocking a user account
+atf_test_case user_locking cleanup
+user_locking_body() {
+ populate_etc_skel
+ ${PW} useradd test || atf_fail "Creating test user"
+ ${PW} lock test || atf_fail "Locking the user"
+ atf_check -s exit:0 -o match:"^test:\*LOCKED\*\*:1001:" \
+ grep "^test:\*LOCKED\*\*:1001:" $HOME/master.passwd
+ ${PW} unlock test || atf_fail "Locking the user"
+ atf_check -s exit:0 -o match:"^test:\*:1001:" \
+ grep "^test:\*:1001:" $HOME/master.passwd
+}
+
+atf_test_case numeric_locking cleanup
+numeric_locking_body() {
+ populate_etc_skel
+ ${PW} useradd test || atf_fail "Creating test user"
+ ${PW} lock 1001 || atf_fail "Locking the user"
+ atf_check -s exit:0 -o match:"^test:\*LOCKED\*\*:1001:" \
+ grep "^test:\*LOCKED\*\*:1001:" $HOME/master.passwd
+ ${PW} unlock 1001 || atf_fail "Unlocking the user"
+ atf_check -s exit:0 -o match:"^test:\*:1001:" \
+ grep "^test:\*:1001:" $HOME/master.passwd
+ # Now numeric names
+ ${PW} useradd -n 1001 || atf_fail "Creating test user"
+ ${PW} lock 1001 || atf_fail "Locking the user"
+ atf_check -s exit:0 -o match:"^1001:\*LOCKED\*\*:1002:" \
+ grep "^1001:\*LOCKED\*\*:1002:" $HOME/master.passwd
+ ${PW} unlock 1001 || atf_fail "Unlocking the user"
+ atf_check -s exit:0 -o match:"^1001:\*:1002:" \
+ grep "^1001:\*:1002:" $HOME/master.passwd
+}
+
+atf_init_test_cases() {
+ atf_add_test_case user_locking
+ atf_add_test_case numeric_locking
+}
diff --git a/usr.sbin/pw/tests/pw_useradd.sh b/usr.sbin/pw/tests/pw_useradd.sh
new file mode 100755
index 0000000..cb62944
--- /dev/null
+++ b/usr.sbin/pw/tests/pw_useradd.sh
@@ -0,0 +1,385 @@
+# $FreeBSD$
+
+# Import helper functions
+. $(atf_get_srcdir)/helper_functions.shin
+
+# Test add user
+atf_test_case user_add
+user_add_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 ${PW} useradd test
+ atf_check -s exit:0 -o match:"^test:.*" \
+ grep "^test:.*" $HOME/master.passwd
+}
+
+# Test add user with option -N
+atf_test_case user_add_noupdate
+user_add_noupdate_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 -o match:"^test:.*" ${PW} useradd test -N
+ atf_check -s exit:1 -o empty grep "^test:.*" $HOME/master.passwd
+}
+
+# Test add user with comments
+atf_test_case user_add_comments
+user_add_comments_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 ${PW} useradd test -c "Test User,work,123,456"
+ atf_check -s exit:0 -o match:"^test:.*:Test User,work,123,456:" \
+ grep "^test:.*:Test User,work,123,456:" $HOME/master.passwd
+}
+
+# Test add user with comments and option -N
+atf_test_case user_add_comments_noupdate
+user_add_comments_noupdate_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 -o match:"^test:.*:Test User,work,123,456:" \
+ ${PW} useradd test -c "Test User,work,123,456" -N
+ atf_check -s exit:1 -o empty grep "^test:.*" $HOME/master.passwd
+}
+
+# Test add user with invalid comments
+atf_test_case user_add_comments_invalid
+user_add_comments_invalid_body() {
+ populate_etc_skel
+
+ atf_check -s exit:65 -e match:"invalid character" \
+ ${PW} useradd test -c "Test User,work,123:456,456"
+ atf_check -s exit:1 -o empty \
+ grep "^test:.*:Test User,work,123:456,456:" $HOME/master.passwd
+}
+
+# Test add user with invalid comments and option -N
+atf_test_case user_add_comments_invalid_noupdate
+user_add_comments_invalid_noupdate_body() {
+ populate_etc_skel
+
+ atf_check -s exit:65 -e match:"invalid character" \
+ ${PW} useradd test -c "Test User,work,123:456,456" -N
+ atf_check -s exit:1 -o empty grep "^test:.*" $HOME/master.passwd
+}
+
+# Test add user with alternate homedir
+atf_test_case user_add_homedir
+user_add_homedir_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 ${PW} useradd test -d /foo/bar
+ atf_check -s exit:0 -o match:"^test:\*:.*::0:0:User &:/foo/bar:.*" \
+ ${PW} usershow test
+}
+
+# Test add user with account expiration as an epoch date
+atf_test_case user_add_account_expiration_epoch
+user_add_account_expiration_epoch_body() {
+ populate_etc_skel
+
+ DATE=`date -j -v+1d "+%s"`
+ atf_check -s exit:0 ${PW} useradd test -e ${DATE}
+ atf_check -s exit:0 -o match:"^test:\*:.*::0:${DATE}:.*" \
+ ${PW} usershow test
+}
+
+# Test add user with account expiration as a DD-MM-YYYY date
+atf_test_case user_add_account_expiration_date_numeric
+user_add_account_expiration_date_numeric_body() {
+ populate_etc_skel
+
+ DATE=`date -j -v+1d "+%d-%m-%Y"`
+ EPOCH=`date -j -f "%d-%m-%Y %H:%M:%S" "${DATE} 00:00:00" "+%s"`
+ atf_check -s exit:0 ${PW} useradd test -e ${DATE}
+ atf_check -s exit:0 -o match:"^test:\*:.*::0:${EPOCH}:User &:.*" \
+ ${PW} usershow test
+}
+
+# Test add user with account expiration as a DD-MM-YYYY date
+atf_test_case user_add_account_expiration_date_month
+user_add_account_expiration_date_month_body() {
+ populate_etc_skel
+
+ DATE=`date -j -v+1d "+%d-%b-%Y"`
+ EPOCH=`date -j -f "%d-%b-%Y %H:%M:%S" "${DATE} 00:00:00" "+%s"`
+ atf_check -s exit:0 ${PW} useradd test -e ${DATE}
+ atf_check -s exit:0 -o match:"^test:\*:.*::0:${EPOCH}:User &:.*" \
+ ${PW} usershow test
+}
+
+# Test add user with account expiration as a relative date
+atf_test_case user_add_account_expiration_date_relative
+user_add_account_expiration_date_relative_body() {
+ populate_etc_skel
+
+ EPOCH=`date -j -v+13m "+%s"`
+ BUF=`expr $EPOCH + 5`
+ atf_check -s exit:0 ${PW} useradd test -e +13o
+ TIME=`${PW} usershow test | awk -F ':' '{print $7}'`
+ [ ! -z $TIME -a $TIME -ge $EPOCH -a $TIME -lt $BUF ] || \
+ atf_fail "Expiration time($TIME) was not within $EPOCH - $BUF seconds."
+}
+
+# Test add user with password expiration as an epoch date
+atf_test_case user_add_password_expiration_epoch
+user_add_password_expiration_epoch_body() {
+ populate_etc_skel
+
+ DATE=`date -j -v+1d "+%s"`
+ atf_check -s exit:0 ${PW} useradd test -p ${DATE}
+ atf_check -s exit:0 -o match:"^test:\*:.*::${DATE}:0:.*" \
+ ${PW} usershow test
+}
+
+# Test add user with password expiration as a DD-MM-YYYY date
+atf_test_case user_add_password_expiration_date_numeric
+user_add_password_expiration_date_numeric_body() {
+ populate_etc_skel
+
+ DATE=`date -j -v+1d "+%d-%m-%Y"`
+ EPOCH=`date -j -f "%d-%m-%Y %H:%M:%S" "${DATE} 00:00:00" "+%s"`
+ atf_check -s exit:0 ${PW} useradd test -p ${DATE}
+ atf_check -s exit:0 -o match:"^test:\*:.*::${EPOCH}:0:User &:.*" \
+ ${PW} usershow test
+}
+
+# Test add user with password expiration as a DD-MMM-YYYY date
+atf_test_case user_add_password_expiration_date_month
+user_add_password_expiration_date_month_body() {
+ populate_etc_skel
+
+ DATE=`date -j -v+1d "+%d-%b-%Y"`
+ EPOCH=`date -j -f "%d-%b-%Y %H:%M:%S" "${DATE} 00:00:00" "+%s"`
+ atf_check -s exit:0 ${PW} useradd test -p ${DATE}
+ atf_check -s exit:0 -o match:"^test:\*:.*::${EPOCH}:0:User &:.*" \
+ ${PW} usershow test
+}
+
+# Test add user with password expiration as a relative date
+atf_test_case user_add_password_expiration_date_relative
+user_add_password_expiration_date_relative_body() {
+ populate_etc_skel
+
+ EPOCH=`date -j -v+13m "+%s"`
+ BUF=`expr $EPOCH + 5`
+ atf_check -s exit:0 ${PW} useradd test -p +13o
+ TIME=`${PW} usershow test | awk -F ':' '{print $6}'`
+ [ ! -z $TIME -a $TIME -ge $EPOCH -a $TIME -lt $BUF ] || \
+ atf_fail "Expiration time($TIME) was not within $EPOCH - $BUF seconds."
+}
+
+atf_test_case user_add_name_too_long
+user_add_name_too_long_body() {
+ populate_etc_skel
+ atf_check -e match:"too long" -s exit:64 \
+ ${PW} useradd name_very_vert_very_very_very_long
+}
+
+atf_test_case user_add_expiration
+user_add_expiration_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 \
+ ${PW} useradd foo -e 20-03-2037
+ atf_check -o inline:"foo:*:1001:1001::0:2121120000:User &:/home/foo:/bin/sh\n" \
+ -s exit:0 grep "^foo" ${HOME}/master.passwd
+ atf_check -s exit:0 ${PW} userdel foo
+ atf_check -s exit:0 \
+ ${PW} useradd foo -e 20-03-37
+ atf_check -o inline:"foo:*:1001:1001::0:2121120000:User &:/home/foo:/bin/sh\n" \
+ -s exit:0 grep "^foo" ${HOME}/master.passwd
+ atf_check -s exit:0 ${PW} userdel foo
+ atf_check -s exit:0 \
+ ${PW} useradd foo -e 20-Mar-2037
+ atf_check -o inline:"foo:*:1001:1001::0:2121120000:User &:/home/foo:/bin/sh\n" \
+ -s exit:0 grep "^foo" ${HOME}/master.passwd
+ atf_check -s exit:0 ${PW} userdel foo
+ atf_check -e inline:"pw: Invalid date\n" -s exit:1 \
+ ${PW} useradd foo -e 20-Foo-2037
+ atf_check -e inline:"pw: Invalid date\n" -s exit:1 \
+ ${PW} useradd foo -e 20-13-2037
+ atf_check -s exit:0 ${PW} useradd foo -e "12:00 20-03-2037"
+ atf_check -s exit:0 ${PW} userdel foo
+ atf_check -e inline:"pw: Invalid date\n" -s exit:1 \
+ ${PW} useradd foo -e "12 20-03-2037"
+ atf_check -s exit:0 ${PW} useradd foo -e "20-03-2037 12:00"
+ atf_check -s exit:0 ${PW} userdel foo
+}
+
+atf_test_case user_add_invalid_user_entry
+user_add_invalid_user_entry_body() {
+ touch ${HOME}/master.passwd
+ touch ${HOME}/group
+
+ pwd_mkdb -p -d ${HOME} ${HOME}/master.passwd || \
+ atf_fail "generate passwd from master.passwd"
+ atf_check -s exit:0 ${PW} useradd foo
+ echo "foo1:*:1002" >> ${HOME}/master.passwd
+ atf_check -s exit:1 -e match:"Invalid user entry" ${PW} useradd foo2
+}
+
+atf_test_case user_add_invalid_group_entry
+user_add_invalid_group_entry_body() {
+ touch ${HOME}/master.passwd
+ touch ${HOME}/group
+
+ pwd_mkdb -p -d ${HOME} ${HOME}/master.passwd || \
+ atf_fail "generate passwd from master.passwd"
+ atf_check -s exit:0 ${PW} useradd foo
+ echo 'foo1:*:1002' >> group
+ atf_check -s exit:1 -e match:"Invalid group entry" ${PW} useradd foo2
+}
+
+atf_test_case user_add_password_from_h
+user_add_password_from_h_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 ${PW} useradd test -h 0 <<-EOF
+ $(echo test)
+ EOF
+}
+
+atf_test_case user_add_R
+user_add_R_body() {
+ populate_root_etc_skel
+
+ atf_check -s exit:0 ${RPW} useradd foo
+ atf_check -s exit:0 ${RPW} useradd bar -m
+ test -d ${HOME}/home || atf_fail "Home parent directory not created"
+ test -d ${HOME}/home/bar || atf_fail "Directory not created"
+ atf_check -s exit:0 ${RPW} userdel bar
+ test -d ${HOME}/home/bar || atf_fail "Directory removed"
+ atf_check -s exit:0 ${RPW} useradd bar
+ atf_check -s exit:0 ${RPW} userdel bar -r
+ [ ! -d ${HOME}/home/bar ] || atf_fail "Directory not removed"
+}
+
+atf_test_case user_add_R_symlink
+user_add_R_symlink_body() {
+ populate_root_etc_skel
+
+ mkdir ${HOME}/usr
+ atf_check -s exit:0 ${RPW} useradd foo -m
+ test -d ${HOME}/usr/home || atf_fail "Home parent directory not created"
+ test -h ${HOME}/home || atf_fail "/home directory is not a symlink"
+ atf_check -s exit:0 -o inline:"usr/home\n" readlink ${HOME}/home
+}
+
+atf_test_case user_add_skel
+user_add_skel_body() {
+ populate_root_etc_skel
+
+ mkdir ${HOME}/skel
+ echo "a" > ${HOME}/skel/.a
+ echo "b" > ${HOME}/skel/b
+ mkdir ${HOME}/skel/c
+ mkdir ${HOME}/skel/c/d
+ mkdir ${HOME}/skel/dot.plop
+ echo "c" > ${HOME}/skel/c/d/dot.c
+ mkdir ${HOME}/home
+ ln -sf /nonexistent ${HOME}/skel/c/foo
+ atf_check -s exit:0 ${RPW} useradd foo -k /skel -m
+ test -d ${HOME}/home/foo || atf_fail "Directory not created"
+ test -f ${HOME}/home/foo/.a || atf_fail "File not created"
+ atf_check -o file:${HOME}/skel/.a -s exit:0 cat ${HOME}/home/foo/.a
+ atf_check -o file:${HOME}/skel/b -s exit:0 cat ${HOME}/home/foo/b
+ test -d ${HOME}/home/foo/c || atf_fail "Dotted directory in skel not copied"
+ test -d ${HOME}/home/foo/.plop || atf_fail "Directory in skell not created"
+ atf_check -o inline:"/nonexistent\n" -s ignore readlink -f ${HOME}/home/foo/c/foo
+ atf_check -o file:${HOME}/skel/c/d/dot.c -s exit:0 cat ${HOME}/home/foo/c/d/.c
+}
+
+atf_test_case user_add_uid0
+user_add_uid0_body() {
+ populate_etc_skel
+ atf_check -e inline:"pw: WARNING: new account \`foo' has a uid of 0 (superuser access!)\n" \
+ -s exit:0 ${PW} useradd foo -u 0 -g 0 -d /root -s /bin/sh -c "Bourne-again Superuser" -o
+ atf_check \
+ -o inline:"foo:*:0:0::0:0:Bourne-again Superuser:/root:/bin/sh\n" \
+ -s exit:0 ${PW} usershow foo
+}
+
+atf_test_case user_add_uid_too_large
+user_add_uid_too_large_body() {
+ populate_etc_skel
+ atf_check -s exit:64 -e inline:"pw: Bad id '9999999999999': too large\n" \
+ ${PW} useradd -n test1 -u 9999999999999
+}
+
+atf_test_case user_add_bad_shell
+user_add_bad_shell_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 ${PW} useradd foo -s sh
+ atf_check -s exit:78 -e ignore ${PW} useradd bar -s badshell
+}
+
+atf_test_case user_add_already_exists
+user_add_already_exists_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 ${PW} useradd foo
+ atf_check -s exit:65 \
+ -e inline:"pw: login name \`foo' already exists\n" \
+ ${PW} useradd foo
+}
+
+atf_test_case user_add_w_yes
+user_add_w_yes_body() {
+ populate_etc_skel
+ atf_check -s exit:0 ${PW} useradd foo -w yes
+ atf_check -s exit:0 \
+ -o match:'^foo:\$.*' \
+ grep "^foo" ${HOME}/master.passwd
+ atf_check -s exit:0 ${PW} usermod foo -w yes
+ atf_check -s exit:0 \
+ -o match:'^foo:\$.*' \
+ grep "^foo" ${HOME}/master.passwd
+}
+
+atf_test_case user_add_with_pw_conf
+user_add_with_pw_conf_body()
+{
+ populate_etc_skel
+ atf_check -s exit:0 \
+ ${PW} useradd -D -C ${HOME}/pw.conf \
+ -u 2000,32767 -i 2000,32767
+ atf_check -s exit:0 \
+ -o inline:"minuid = 2000\nmaxuid = 32767\nmingid = 2000\nmaxgid = 32767\n" \
+ grep "^m.*id =" ${HOME}/pw.conf
+ atf_check -s exit:0 \
+ ${PW} useradd foo -C ${HOME}/pw.conf
+}
+
+atf_init_test_cases() {
+ atf_add_test_case user_add
+ atf_add_test_case user_add_noupdate
+ atf_add_test_case user_add_comments
+ atf_add_test_case user_add_comments_noupdate
+ atf_add_test_case user_add_comments_invalid
+ atf_add_test_case user_add_comments_invalid_noupdate
+ atf_add_test_case user_add_homedir
+ atf_add_test_case user_add_account_expiration_epoch
+ atf_add_test_case user_add_account_expiration_date_numeric
+ atf_add_test_case user_add_account_expiration_date_month
+ atf_add_test_case user_add_account_expiration_date_relative
+ atf_add_test_case user_add_password_expiration_epoch
+ atf_add_test_case user_add_password_expiration_date_numeric
+ atf_add_test_case user_add_password_expiration_date_month
+ atf_add_test_case user_add_password_expiration_date_relative
+ atf_add_test_case user_add_name_too_long
+ atf_add_test_case user_add_expiration
+ atf_add_test_case user_add_invalid_user_entry
+ atf_add_test_case user_add_invalid_group_entry
+ atf_add_test_case user_add_password_from_h
+ atf_add_test_case user_add_R
+ atf_add_test_case user_add_R_symlink
+ atf_add_test_case user_add_skel
+ atf_add_test_case user_add_uid0
+ atf_add_test_case user_add_uid_too_large
+ atf_add_test_case user_add_bad_shell
+ atf_add_test_case user_add_already_exists
+ atf_add_test_case user_add_w_yes
+ atf_add_test_case user_add_with_pw_conf
+}
diff --git a/usr.sbin/pw/tests/pw_userdel.sh b/usr.sbin/pw/tests/pw_userdel.sh
new file mode 100755
index 0000000..f608029
--- /dev/null
+++ b/usr.sbin/pw/tests/pw_userdel.sh
@@ -0,0 +1,67 @@
+# $FreeBSD$
+
+# Import helper functions
+. $(atf_get_srcdir)/helper_functions.shin
+
+
+# Test that a user can be deleted when another user is part of this
+# user's default group and does not go into an infinate loop.
+# PR: 191427
+atf_test_case rmuser_seperate_group cleanup
+rmuser_seperate_group_head() {
+ atf_set "timeout" "30"
+}
+rmuser_seperate_group_body() {
+ populate_etc_skel
+ ${PW} useradd test || atf_fail "Creating test user"
+ ${PW} groupmod test -M 'test,root' || \
+ atf_fail "Modifying the group"
+ ${PW} userdel test || atf_fail "Delete the test user"
+}
+
+
+atf_test_case user_do_not_try_to_delete_root_if_user_unknown
+user_do_not_try_to_delete_root_if_user_unknown_head() {
+ atf_set "descr" \
+ "Make sure not to try to remove root if deleting an unknown user"
+}
+user_do_not_try_to_delete_root_if_user_unknown_body() {
+ populate_etc_skel
+ atf_check -e inline:"pw: Bad id 'plop': invalid\n" -s exit:64 -x \
+ ${PW} userdel -u plop
+}
+
+atf_test_case delete_files
+delete_files_body() {
+ populate_root_etc_skel
+
+ mkdir -p ${HOME}/skel
+ touch ${HOME}/skel/a
+ mkdir -p ${HOME}/home
+ mkdir -p ${HOME}/var/mail
+ echo "foo wedontcare" > ${HOME}/etc/opiekeys
+ atf_check -s exit:0 ${RPW} useradd foo -k /skel -m
+ test -d ${HOME}/home || atf_fail "Fail to create home directory"
+ test -f ${HOME}/var/mail/foo || atf_fail "Mail file not created"
+ atf_check -s exit:0 ${RPW} userdel foo -r
+ atf_check -s exit:0 -o inline:"#oo wedontcare\n" cat ${HOME}/etc/opiekeys
+ if test -f ${HOME}/var/mail/foo; then
+ atf_fail "Mail file not removed"
+ fi
+}
+
+atf_test_case delete_numeric_name
+delete_numeric_name_body() {
+ populate_etc_skel
+
+ atf_check ${PW} useradd -n foo -u 4001
+ atf_check -e inline:"pw: no such user \`4001'\n" -s exit:67 \
+ ${PW} userdel -n 4001
+}
+
+atf_init_test_cases() {
+ atf_add_test_case rmuser_seperate_group
+ atf_add_test_case user_do_not_try_to_delete_root_if_user_unknown
+ atf_add_test_case delete_files
+ atf_add_test_case delete_numeric_name
+}
diff --git a/usr.sbin/pw/tests/pw_usermod.sh b/usr.sbin/pw/tests/pw_usermod.sh
new file mode 100755
index 0000000..236fd27
--- /dev/null
+++ b/usr.sbin/pw/tests/pw_usermod.sh
@@ -0,0 +1,222 @@
+# $FreeBSD$
+
+# Import helper functions
+. $(atf_get_srcdir)/helper_functions.shin
+
+# Test modifying a user
+atf_test_case user_mod
+user_mod_body() {
+ populate_etc_skel
+
+ atf_check -s exit:67 -e match:"no such user" ${PW} usermod test
+ atf_check -s exit:0 ${PW} useradd test
+ atf_check -s exit:0 ${PW} usermod test
+ atf_check -s exit:0 -o match:"^test:.*" \
+ grep "^test:.*" $HOME/master.passwd
+}
+
+# Test modifying a user with option -N
+atf_test_case user_mod_noupdate
+user_mod_noupdate_body() {
+ populate_etc_skel
+
+ atf_check -s exit:67 -e match:"no such user" ${PW} usermod test -N
+ atf_check -s exit:0 ${PW} useradd test
+ atf_check -s exit:0 -o match:"^test:.*" ${PW} usermod test -N
+ atf_check -s exit:0 -o match:"^test:.*" \
+ grep "^test:.*" $HOME/master.passwd
+}
+
+# Test modifying a user with comments
+atf_test_case user_mod_comments
+user_mod_comments_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 ${PW} useradd test -c "Test User,home,123,456"
+ atf_check -s exit:0 ${PW} usermod test -c "Test User,work,123,456"
+ atf_check -s exit:0 -o match:"^test:.*:Test User,work,123,456:" \
+ grep "^test:.*:Test User,work,123,456:" $HOME/master.passwd
+}
+
+# Test modifying a user with comments with option -N
+atf_test_case user_mod_comments_noupdate
+user_mod_comments_noupdate_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 ${PW} useradd test -c "Test User,home,123,456"
+ atf_check -s exit:0 -o match:"^test:.*:Test User,work,123,456:" \
+ ${PW} usermod test -c "Test User,work,123,456" -N
+ atf_check -s exit:0 -o match:"^test:.*:Test User,home,123,456:" \
+ grep "^test:.*:Test User,home,123,456:" $HOME/master.passwd
+}
+
+# Test modifying a user with invalid comments
+atf_test_case user_mod_comments_invalid
+user_mod_comments_invalid_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 ${PW} useradd test
+ atf_check -s exit:65 -e match:"invalid character" \
+ ${PW} usermod test -c "Test User,work,123:456,456"
+ atf_check -s exit:1 -o empty \
+ grep "^test:.*:Test User,work,123:456,456:" $HOME/master.passwd
+ atf_check -s exit:0 -o match:"^test:\*" \
+ grep "^test:\*" $HOME/master.passwd
+}
+
+# Test modifying a user with invalid comments with option -N
+atf_test_case user_mod_comments_invalid_noupdate
+user_mod_comments_invalid_noupdate_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 ${PW} useradd test
+ atf_check -s exit:65 -e match:"invalid character" \
+ ${PW} usermod test -c "Test User,work,123:456,456" -N
+ atf_check -s exit:1 -o empty \
+ grep "^test:.*:Test User,work,123:456,456:" $HOME/master.passwd
+ atf_check -s exit:0 -o match:"^test:\*" \
+ grep "^test:\*" $HOME/master.passwd
+}
+
+# Test modifying a user name with -l
+atf_test_case user_mod_name
+user_mod_name_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 ${PW} useradd foo
+ atf_check -s exit:0 ${PW} usermod foo -l "bar"
+ atf_check -s exit:0 -o match:"^bar:.*" \
+ grep "^bar:.*" $HOME/master.passwd
+}
+
+# Test modifying a user name with -l with option -N
+atf_test_case user_mod_name_noupdate
+user_mod_name_noupdate_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 ${PW} useradd foo
+ atf_check -s exit:0 -o match:"^bar:.*" ${PW} usermod foo -l "bar" -N
+ atf_check -s exit:0 -o match:"^foo:.*" \
+ grep "^foo:.*" $HOME/master.passwd
+}
+
+atf_test_case user_mod_rename_multigroups
+user_mod_rename_multigroups_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 ${PW} groupadd test1
+ atf_check -s exit:0 ${PW} groupadd test2
+ atf_check -s exit:0 ${PW} useradd foo -G test1,test2
+ atf_check -o match:"foo" -s exit:0 ${PW} groupshow test1
+ atf_check -o match:"foo" -s exit:0 ${PW} groupshow test2
+ atf_check -s exit:0 ${PW} usermod foo -l bar
+ atf_check -o match:"bar" -s exit:0 ${PW} groupshow test1
+ atf_check -o match:"bar" -s exit:0 ${PW} groupshow test2
+}
+
+atf_test_case user_mod_nogroups
+user_mod_nogroups_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 ${PW} groupadd test1
+ atf_check -s exit:0 ${PW} groupadd test2
+ atf_check -s exit:0 ${PW} groupadd test3
+ atf_check -s exit:0 ${PW} groupadd test4
+ atf_check -s exit:0 ${PW} useradd foo -G test1,test2
+ atf_check -o match:"foo" -s exit:0 ${PW} groupshow test1
+ atf_check -o match:"foo" -s exit:0 ${PW} groupshow test2
+ atf_check -s exit:0 ${PW} usermod foo -G test3,test4
+ atf_check -s exit:0 -o inline:"test3\ntest4\n" \
+ awk -F\: '$4 == "foo" { print $1 }' ${HOME}/group
+}
+
+atf_test_case user_mod_rename
+user_mod_rename_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 ${PW} useradd foo
+ atf_check -s exit:0 ${PW} usermod foo -l bar
+ atf_check -s exit:0 -o match:"^bar:.*" \
+ grep "^bar:.*" ${HOME}/master.passwd
+}
+
+atf_test_case user_mod_rename_too_long
+user_mod_rename_too_long_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 ${PW} useradd foo
+ atf_check -s exit:64 -e match:"too long" ${PW} usermod foo \
+ -l name_very_very_very_very_very_long
+}
+
+atf_test_case user_mod_h
+user_mod_h_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 ${PW} useradd foo
+ atf_check -s exit:0 ${PW} usermod foo -h 0 <<- EOF
+ $(echo a)
+ EOF
+ atf_check -s exit:0 -o not-match:"^foo:\*:.*" \
+ grep "^foo" ${HOME}/master.passwd
+ atf_check -s exit:0 ${PW} usermod foo -h - <<- EOF
+ $(echo b)
+ EOF
+ atf_check -s exit:0 -o match:"^foo:\*:.*" \
+ grep "^foo" ${HOME}/master.passwd
+ atf_check -e inline:"pw: Bad file descriptor 'a': invalid\n" \
+ -s exit:64 ${PW} usermod foo -h a <<- EOF
+ $(echo a)
+ EOF
+}
+
+atf_test_case user_mod_H
+user_mod_H_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 ${PW} useradd foo
+ atf_check -s exit:0 ${PW} usermod foo -H 0 <<- EOF
+ $(echo a)
+ EOF
+ atf_check -s exit:0 -o match:"^foo:a:.*" \
+ grep "^foo" ${HOME}/master.passwd
+ atf_check -s exit:64 -e inline:"pw: -H expects a file descriptor\n" \
+ ${PW} usermod foo -H -
+}
+
+atf_test_case user_mod_renamehome
+user_mod_renamehome_body() {
+ populate_root_etc_skel
+
+ mkdir -p ${HOME}/home
+ atf_check -s exit:0 ${RPW} useradd foo -m
+ test -d ${HOME}/home/foo || atf_fail "Directory not created"
+ atf_check -s exit:0 ${RPW} usermod foo -l bar -d /home/bar -m
+ test -d ${HOME}/home/bar || atf_fail "Directory not created"
+}
+
+atf_test_case user_mod_uid
+user_mod_uid_body() {
+ populate_etc_skel
+
+ atf_check -s exit:0 ${PW} useradd foo
+ atf_check -s exit:0 ${PW} usermod foo -u 5000
+}
+
+atf_init_test_cases() {
+ atf_add_test_case user_mod
+ atf_add_test_case user_mod_noupdate
+ atf_add_test_case user_mod_comments
+ atf_add_test_case user_mod_comments_noupdate
+ atf_add_test_case user_mod_comments_invalid
+ atf_add_test_case user_mod_comments_invalid_noupdate
+ atf_add_test_case user_mod_nogroups
+ atf_add_test_case user_mod_rename
+ atf_add_test_case user_mod_name_noupdate
+ atf_add_test_case user_mod_rename_too_long
+ atf_add_test_case user_mod_rename_multigroups
+ atf_add_test_case user_mod_h
+ atf_add_test_case user_mod_H
+ atf_add_test_case user_mod_renamehome
+ atf_add_test_case user_mod_uid
+}
diff --git a/usr.sbin/pw/tests/pw_usernext.sh b/usr.sbin/pw/tests/pw_usernext.sh
new file mode 100755
index 0000000..89f938e
--- /dev/null
+++ b/usr.sbin/pw/tests/pw_usernext.sh
@@ -0,0 +1,45 @@
+# $FreeBSD$
+
+# Import helper functions
+. $(atf_get_srcdir)/helper_functions.shin
+
+# Test usernext after adding a random number of new users.
+atf_test_case usernext
+usernext_body() {
+ populate_etc_skel
+
+ CURRENT=`${PW} usernext | sed -e 's/:.*//'`
+ RANDOM=`jot -r 1 1 150`
+ MAX=`expr ${CURRENT} + ${RANDOM}`
+ while [ "${CURRENT}" -lt "${MAX}" ]
+ do
+ atf_check -s exit:0 ${PW} useradd test${CURRENT}
+ CURRENT=`expr ${CURRENT} + 1`
+ done
+ atf_check -s exit:0 -o match:"${CURRENT}:${CURRENT}" \
+ ${PW} usernext
+}
+
+# Test usernext when multiple users are added to the same group so
+# that group id doesn't increment at the same pace as new users.
+atf_test_case usernext_assigned_group
+usernext_assigned_group_body() {
+ populate_etc_skel
+
+ CURRENT=`${PW} usernext | sed -e 's/:.*//'`
+ CURRENTGID=`${PW} groupnext`
+ RANDOM=`jot -r 1 1 150`
+ MAX=`expr ${CURRENT} + ${RANDOM}`
+ while [ "${CURRENT}" -lt "${MAX}" ]
+ do
+ atf_check -s exit:0 ${PW} useradd -n test${CURRENT} -g 0
+ CURRENT=`expr ${CURRENT} + 1`
+ done
+ atf_check -s exit:0 -o match:"${CURRENT}:${CURRENTGID}" \
+ ${PW} usernext
+}
+
+atf_init_test_cases() {
+ atf_add_test_case usernext
+ atf_add_test_case usernext_assigned_group
+}
diff --git a/usr.sbin/pwd_mkdb/Makefile b/usr.sbin/pwd_mkdb/Makefile
new file mode 100644
index 0000000..7616629
--- /dev/null
+++ b/usr.sbin/pwd_mkdb/Makefile
@@ -0,0 +1,12 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../lib/libc/gen # for pw_scan.c
+
+PROG= pwd_mkdb
+MAN= pwd_mkdb.8
+SRCS= pw_scan.c pwd_mkdb.c
+
+CFLAGS+= -I${.CURDIR}/../../lib/libc/gen # for pw_scan.h
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/pwd_mkdb/Makefile.depend b/usr.sbin/pwd_mkdb/Makefile.depend
new file mode 100644
index 0000000..54c1f6f
--- /dev/null
+++ b/usr.sbin/pwd_mkdb/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/pwd_mkdb/pwd_mkdb.8 b/usr.sbin/pwd_mkdb/pwd_mkdb.8
new file mode 100644
index 0000000..b8378b8
--- /dev/null
+++ b/usr.sbin/pwd_mkdb/pwd_mkdb.8
@@ -0,0 +1,212 @@
+.\" 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.
+.\"
+.\" @(#)pwd_mkdb.8 8.1 (Berkeley) 6/6/93
+.\" $FreeBSD$
+.\"
+.Dd February 5, 2014
+.Dt PWD_MKDB 8
+.Os
+.Sh NAME
+.Nm pwd_mkdb
+.Nd "generate the password databases"
+.Sh SYNOPSIS
+.Nm
+.Op Fl BCilLNp
+.Op Fl d Ar directory
+.Op Fl s Ar cachesize
+.Op Fl u Ar username
+.Ar file
+.Sh DESCRIPTION
+The
+.Nm
+utility creates
+.Xr db 3
+style secure and insecure databases for the specified file.
+These databases are then installed into
+.Pa /etc/spwd.db
+and
+.Pa /etc/pwd.db
+respectively.
+The file is installed into
+.Pa /etc/master.passwd .
+The file must be in the correct format (see
+.Xr passwd 5 ) .
+It is important to note that the format used in this system is
+different from the historic Version 7 style format.
+.Pp
+The options are as follows:
+.Bl -tag -width flag
+.It Fl C
+Check if the password file is in the correct format.
+Do not
+change, add, or remove any files.
+.It Fl N
+Tell
+.Nm
+to exit with an error if it cannot obtain a lock on the file.
+By default,
+we block waiting for a lock on the source file.
+The lock is held through
+the rebuilding of the database.
+.It Fl p
+Create a Version 7 style password file and install it into
+.Pa /etc/passwd .
+.It Fl i
+Ignore locking failure of the
+.Pa master.passwd
+file.
+This option is intended to be used to build password files in
+the release process over NFS where no contention can happen.
+A non-default directory must also be specified with the
+.Fl d
+option for locking to be ignored.
+Other use of this option is strongly discouraged.
+.It Fl d Ar directory
+Store databases into specified destination directory instead of
+.Pa /etc .
+.It Fl u Ar username
+Only update the record for the specified user.
+Utilities that
+operate on a single user can use this option to avoid the
+overhead of rebuilding the entire database.
+.It Fl s Ar cachesize
+Specify in megabytes the size of the memory cache used by the
+hashing library.
+On systems with a large user base, a small cache
+size can lead to prohibitively long database file rebuild times.
+As a rough guide, the memory usage of
+.Nm
+in megabytes will be a little bit more than twice the figure
+specified here.
+The default is 2 megabytes.
+.El
+.Pp
+The two databases differ in that the secure version contains the user's
+encrypted password and the insecure version has an asterisk (``*'')
+.Pp
+The databases are used by the C library password routines (see
+.Xr getpwent 3 ) .
+.Pp
+By default,
+the
+.Nm
+utility generates new,
+machine independent format
+.Pq v4
+entries only.
+For compatibility with
+.Fx 5.0
+and earlier releases,
+the
+.Fl l
+option may be specified,
+which enables generation of legacy format
+.Pq v3
+entries.
+The legacy format entries are endianness dependent.
+.Pp
+The following options may be specified and will affect the
+generation of legacy entries.
+.Bl -tag -width flag
+.It Fl B
+Store data in big-endian format.
+.It Fl L
+Store data in little-endian format.
+.El
+.Pp
+The
+.Nm
+utility exits zero on success, non-zero on failure.
+.Sh ENVIRONMENT
+If the
+.Ev PW_SCAN_BIG_IDS
+environment variable is set,
+.Nm
+will suppress the warning messages that are
+normally generated for large user and group IDs.
+Such IDs can cause serious problems with software
+that makes assumptions about the values of IDs.
+.Sh FILES
+.Bl -tag -width Pa -compact
+.It Pa /etc/pwd.db
+The insecure password database file.
+.It Pa /etc/pwd.db.tmp
+A temporary file.
+.It Pa /etc/spwd.db
+The secure password database file.
+.It Pa /etc/spwd.db.tmp
+A temporary file.
+.It Pa /etc/master.passwd
+The current password file.
+.It Pa /etc/passwd
+A Version 7 format password file.
+.El
+.Sh EXAMPLES
+Regenerate the password database after manually editing or replacing
+the password file:
+.Bd -literal -offset -indent
+/usr/sbin/pwd_mkdb -p /etc/master.passwd
+.Ed
+.Sh COMPATIBILITY
+Previous versions of the system had a program similar to
+.Nm ,
+.Xr mkpasswd 8 ,
+which built
+.Xr dbm 3
+style databases for the password file but depended on the calling programs
+to install them.
+The program was renamed in order that previous users of the program
+not be surprised by the changes in functionality.
+.Sh SEE ALSO
+.Xr chpass 1 ,
+.Xr passwd 1 ,
+.Xr db 3 ,
+.Xr getpwent 3 ,
+.Xr passwd 5 ,
+.Xr vipw 8
+.Sh BUGS
+Because of the necessity for atomic update of the password files,
+.Nm
+uses
+.Xr rename 2
+to install them.
+This, however, requires that the file specified on the command line live
+on the same file system as the
+.Pa /etc
+directory.
+.Pp
+There are the obvious races with multiple people running
+.Nm
+on different password files at the same time.
+The front-ends to
+.Nm ,
+.Xr chpass 1 ,
+.Xr passwd 1
+and
+.Xr vipw 8 ,
+handle the locking necessary to avoid this problem.
diff --git a/usr.sbin/pwd_mkdb/pwd_mkdb.c b/usr.sbin/pwd_mkdb/pwd_mkdb.c
new file mode 100644
index 0000000..fe44520
--- /dev/null
+++ b/usr.sbin/pwd_mkdb/pwd_mkdb.c
@@ -0,0 +1,784 @@
+/*-
+ * Copyright (c) 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.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1991, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)pwd_mkdb.c 8.5 (Berkeley) 4/20/94";
+#endif /* not lint */
+#endif
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/endian.h>
+#include <sys/stat.h>
+#include <arpa/inet.h>
+
+#include <db.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "pw_scan.h"
+
+#define INSECURE 1
+#define SECURE 2
+#define PERM_INSECURE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)
+#define PERM_SECURE (S_IRUSR|S_IWUSR)
+#define LEGACY_VERSION(x) _PW_VERSIONED(x, 3)
+#define CURRENT_VERSION(x) _PW_VERSIONED(x, 4)
+
+static HASHINFO openinfo = {
+ 4096, /* bsize */
+ 32, /* ffactor */
+ 256, /* nelem */
+ 2048 * 1024, /* cachesize */
+ NULL, /* hash() */
+ BYTE_ORDER /* lorder */
+};
+
+static enum state { FILE_INSECURE, FILE_SECURE, FILE_ORIG } clean;
+static struct passwd pwd; /* password structure */
+static char *pname; /* password file name */
+static char prefix[MAXPATHLEN];
+
+static int is_comment; /* flag for comments */
+static char line[LINE_MAX];
+
+void cleanup(void);
+void error(const char *);
+void cp(char *, char *, mode_t mode);
+void mv(char *, char *);
+int scan(FILE *, struct passwd *);
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ static char verskey[] = _PWD_VERSION_KEY;
+ char version = _PWD_CURRENT_VERSION;
+ DB *dp, *sdp, *pw_db;
+ DBT data, sdata, key;
+ FILE *fp, *oldfp;
+ sigset_t set;
+ int ch, cnt, ypcnt, makeold, tfd, yp_enabled = 0;
+ unsigned int len;
+ uint32_t store;
+ const char *t;
+ char *p;
+ char buf[MAX(MAXPATHLEN, LINE_MAX * 2)], tbuf[1024];
+ char sbuf[MAX(MAXPATHLEN, LINE_MAX * 2)];
+ char buf2[MAXPATHLEN];
+ char sbuf2[MAXPATHLEN];
+ char *username;
+ u_int method, methoduid;
+ int Cflag, dflag, iflag, lflag;
+ int nblock = 0;
+
+ iflag = dflag = Cflag = lflag = 0;
+ strcpy(prefix, _PATH_PWD);
+ makeold = 0;
+ username = NULL;
+ oldfp = NULL;
+ while ((ch = getopt(argc, argv, "BCLlNd:ips:u:v")) != -1)
+ switch(ch) {
+ case 'B': /* big-endian output */
+ openinfo.lorder = BIG_ENDIAN;
+ break;
+ case 'C': /* verify only */
+ Cflag = 1;
+ break;
+ case 'l': /* generate legacy entries */
+ lflag = 1;
+ break;
+ case 'L': /* little-endian output */
+ openinfo.lorder = LITTLE_ENDIAN;
+ break;
+ case 'N': /* do not wait for lock */
+ nblock = LOCK_NB; /* will fail if locked */
+ break;
+ case 'd':
+ dflag++;
+ strlcpy(prefix, optarg, sizeof(prefix));
+ break;
+ case 'i':
+ iflag++;
+ break;
+ case 'p': /* create V7 "file.orig" */
+ makeold = 1;
+ break;
+ case 's': /* change default cachesize */
+ openinfo.cachesize = atoi(optarg) * 1024 * 1024;
+ break;
+ case 'u': /* only update this record */
+ username = optarg;
+ break;
+ case 'v': /* backward compatible */
+ break;
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1 || (username && (*username == '+' || *username == '-')))
+ usage();
+
+ /*
+ * This could be changed to allow the user to interrupt.
+ * Probably not worth the effort.
+ */
+ sigemptyset(&set);
+ sigaddset(&set, SIGTSTP);
+ sigaddset(&set, SIGHUP);
+ sigaddset(&set, SIGINT);
+ sigaddset(&set, SIGQUIT);
+ sigaddset(&set, SIGTERM);
+ (void)sigprocmask(SIG_BLOCK, &set, (sigset_t *)NULL);
+
+ /* We don't care what the user wants. */
+ (void)umask(0);
+
+ pname = *argv;
+
+ /*
+ * Open and lock the original password file. We have to check
+ * the hardlink count after we get the lock to handle any potential
+ * unlink/rename race.
+ *
+ * This lock is necessary when someone runs pwd_mkdb manually, directly
+ * on master.passwd, to handle the case where a user might try to
+ * change his password while pwd_mkdb is running.
+ */
+ for (;;) {
+ struct stat st;
+
+ if (!(fp = fopen(pname, "r")))
+ error(pname);
+ if (flock(fileno(fp), LOCK_EX|nblock) < 0 && !(dflag && iflag))
+ error("flock");
+ if (fstat(fileno(fp), &st) < 0)
+ error(pname);
+ if (st.st_nlink != 0)
+ break;
+ fclose(fp);
+ fp = NULL;
+ }
+
+ /* check only if password database is valid */
+ if (Cflag) {
+ while (scan(fp, &pwd))
+ if (!is_comment && strlen(pwd.pw_name) >= MAXLOGNAME) {
+ warnx("%s: username too long", pwd.pw_name);
+ exit(1);
+ }
+ exit(0);
+ }
+
+ /* Open the temporary insecure password database. */
+ (void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _MP_DB);
+ (void)snprintf(sbuf, sizeof(sbuf), "%s/%s.tmp", prefix, _SMP_DB);
+ if (username) {
+ int use_version;
+
+ (void)snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _MP_DB);
+ (void)snprintf(sbuf2, sizeof(sbuf2), "%s/%s", prefix, _SMP_DB);
+
+ clean = FILE_INSECURE;
+ cp(buf2, buf, PERM_INSECURE);
+ dp = dbopen(buf,
+ O_RDWR|O_EXCL|O_SYNC, PERM_INSECURE, DB_HASH, &openinfo);
+ if (dp == NULL)
+ error(buf);
+
+ clean = FILE_SECURE;
+ cp(sbuf2, sbuf, PERM_SECURE);
+ sdp = dbopen(sbuf,
+ O_RDWR|O_EXCL|O_SYNC, PERM_SECURE, DB_HASH, &openinfo);
+ if (sdp == NULL)
+ error(sbuf);
+
+ /*
+ * Do some trouble to check if we should store this users
+ * uid. Don't use getpwnam/getpwuid as that interferes
+ * with NIS.
+ */
+ pw_db = dbopen(_PATH_MP_DB, O_RDONLY, 0, DB_HASH, NULL);
+ if (!pw_db)
+ error(_MP_DB);
+
+ key.data = verskey;
+ key.size = sizeof(verskey)-1;
+ if ((pw_db->get)(pw_db, &key, &data, 0) == 0)
+ use_version = *(unsigned char *)data.data;
+ else
+ use_version = 3;
+ buf[0] = _PW_VERSIONED(_PW_KEYBYNAME, use_version);
+ len = strlen(username);
+
+ /* Only check that username fits in buffer */
+ memmove(buf + 1, username, MIN(len, sizeof(buf) - 1));
+ key.data = (u_char *)buf;
+ key.size = len + 1;
+ if ((pw_db->get)(pw_db, &key, &data, 0) == 0) {
+ p = (char *)data.data;
+
+ /* jump over pw_name and pw_passwd, to get to pw_uid */
+ while (*p++)
+ ;
+ while (*p++)
+ ;
+
+ buf[0] = _PW_VERSIONED(_PW_KEYBYUID, use_version);
+ memmove(buf + 1, p, sizeof(store));
+ key.data = (u_char *)buf;
+ key.size = sizeof(store) + 1;
+
+ if ((pw_db->get)(pw_db, &key, &data, 0) == 0) {
+ /* First field of data.data holds pw_pwname */
+ if (!strcmp(data.data, username))
+ methoduid = 0;
+ else
+ methoduid = R_NOOVERWRITE;
+ } else {
+ methoduid = R_NOOVERWRITE;
+ }
+ } else {
+ methoduid = R_NOOVERWRITE;
+ }
+ if ((pw_db->close)(pw_db))
+ error("close pw_db");
+ method = 0;
+ } else {
+ dp = dbopen(buf,
+ O_RDWR|O_CREAT|O_EXCL|O_SYNC, PERM_INSECURE, DB_HASH, &openinfo);
+ if (dp == NULL)
+ error(buf);
+ clean = FILE_INSECURE;
+
+ sdp = dbopen(sbuf,
+ O_RDWR|O_CREAT|O_EXCL|O_SYNC, PERM_SECURE, DB_HASH, &openinfo);
+ if (sdp == NULL)
+ error(sbuf);
+ clean = FILE_SECURE;
+
+ method = R_NOOVERWRITE;
+ methoduid = R_NOOVERWRITE;
+ }
+
+ /*
+ * Open file for old password file. Minor trickiness -- don't want to
+ * chance the file already existing, since someone (stupidly) might
+ * still be using this for permission checking. So, open it first and
+ * fdopen the resulting fd. The resulting file should be readable by
+ * everyone.
+ */
+ if (makeold) {
+ (void)snprintf(buf, sizeof(buf), "%s.orig", pname);
+ if ((tfd = open(buf,
+ O_WRONLY|O_CREAT|O_EXCL, PERM_INSECURE)) < 0)
+ error(buf);
+ if ((oldfp = fdopen(tfd, "w")) == NULL)
+ error(buf);
+ clean = FILE_ORIG;
+ }
+
+ /*
+ * The databases actually contain three copies of the original data.
+ * Each password file entry is converted into a rough approximation
+ * of a ``struct passwd'', with the strings placed inline. This
+ * object is then stored as the data for three separate keys. The
+ * first key * is the pw_name field prepended by the _PW_KEYBYNAME
+ * character. The second key is the pw_uid field prepended by the
+ * _PW_KEYBYUID character. The third key is the line number in the
+ * original file prepended by the _PW_KEYBYNUM character. (The special
+ * characters are prepended to ensure that the keys do not collide.)
+ */
+ /* In order to transition this file into a machine-independent
+ * form, we have to change the format of entries. However, since
+ * older binaries will still expect the old MD format entries, we
+ * create those as usual and use versioned tags for the new entries.
+ */
+ if (username == NULL) {
+ /* Do not add the VERSION tag when updating a single
+ * user. When operating on `old format' databases, this
+ * would result in applications `seeing' only the updated
+ * entries.
+ */
+ key.data = verskey;
+ key.size = sizeof(verskey)-1;
+ data.data = &version;
+ data.size = 1;
+ if ((dp->put)(dp, &key, &data, 0) == -1)
+ error("put");
+ if ((sdp->put)(sdp, &key, &data, 0) == -1)
+ error("put");
+ }
+ ypcnt = 0;
+ data.data = (u_char *)buf;
+ sdata.data = (u_char *)sbuf;
+ key.data = (u_char *)tbuf;
+ for (cnt = 1; scan(fp, &pwd); ++cnt) {
+ if (!is_comment &&
+ (pwd.pw_name[0] == '+' || pwd.pw_name[0] == '-')) {
+ yp_enabled = 1;
+ ypcnt++;
+ }
+ if (is_comment)
+ --cnt;
+#define COMPACT(e) t = e; while ((*p++ = *t++));
+#define SCALAR(e) store = htonl((uint32_t)(e)); \
+ memmove(p, &store, sizeof(store)); \
+ p += sizeof(store);
+#define LSCALAR(e) store = HTOL((uint32_t)(e)); \
+ memmove(p, &store, sizeof(store)); \
+ p += sizeof(store);
+#define HTOL(e) (openinfo.lorder == BYTE_ORDER ? \
+ (uint32_t)(e) : \
+ bswap32((uint32_t)(e)))
+ if (!is_comment &&
+ (!username || (strcmp(username, pwd.pw_name) == 0))) {
+ /* Create insecure data. */
+ p = buf;
+ COMPACT(pwd.pw_name);
+ COMPACT("*");
+ SCALAR(pwd.pw_uid);
+ SCALAR(pwd.pw_gid);
+ SCALAR(pwd.pw_change);
+ COMPACT(pwd.pw_class);
+ COMPACT(pwd.pw_gecos);
+ COMPACT(pwd.pw_dir);
+ COMPACT(pwd.pw_shell);
+ SCALAR(pwd.pw_expire);
+ SCALAR(pwd.pw_fields);
+ data.size = p - buf;
+
+ /* Create secure data. */
+ p = sbuf;
+ COMPACT(pwd.pw_name);
+ COMPACT(pwd.pw_passwd);
+ SCALAR(pwd.pw_uid);
+ SCALAR(pwd.pw_gid);
+ SCALAR(pwd.pw_change);
+ COMPACT(pwd.pw_class);
+ COMPACT(pwd.pw_gecos);
+ COMPACT(pwd.pw_dir);
+ COMPACT(pwd.pw_shell);
+ SCALAR(pwd.pw_expire);
+ SCALAR(pwd.pw_fields);
+ sdata.size = p - sbuf;
+
+ /* Store insecure by name. */
+ tbuf[0] = CURRENT_VERSION(_PW_KEYBYNAME);
+ len = strlen(pwd.pw_name);
+ memmove(tbuf + 1, pwd.pw_name, len);
+ key.size = len + 1;
+ if ((dp->put)(dp, &key, &data, method) == -1)
+ error("put");
+
+ /* Store insecure by number. */
+ tbuf[0] = CURRENT_VERSION(_PW_KEYBYNUM);
+ store = htonl(cnt);
+ memmove(tbuf + 1, &store, sizeof(store));
+ key.size = sizeof(store) + 1;
+ if ((dp->put)(dp, &key, &data, method) == -1)
+ error("put");
+
+ /* Store insecure by uid. */
+ tbuf[0] = CURRENT_VERSION(_PW_KEYBYUID);
+ store = htonl(pwd.pw_uid);
+ memmove(tbuf + 1, &store, sizeof(store));
+ key.size = sizeof(store) + 1;
+ if ((dp->put)(dp, &key, &data, methoduid) == -1)
+ error("put");
+
+ /* Store secure by name. */
+ tbuf[0] = CURRENT_VERSION(_PW_KEYBYNAME);
+ len = strlen(pwd.pw_name);
+ memmove(tbuf + 1, pwd.pw_name, len);
+ key.size = len + 1;
+ if ((sdp->put)(sdp, &key, &sdata, method) == -1)
+ error("put");
+
+ /* Store secure by number. */
+ tbuf[0] = CURRENT_VERSION(_PW_KEYBYNUM);
+ store = htonl(cnt);
+ memmove(tbuf + 1, &store, sizeof(store));
+ key.size = sizeof(store) + 1;
+ if ((sdp->put)(sdp, &key, &sdata, method) == -1)
+ error("put");
+
+ /* Store secure by uid. */
+ tbuf[0] = CURRENT_VERSION(_PW_KEYBYUID);
+ store = htonl(pwd.pw_uid);
+ memmove(tbuf + 1, &store, sizeof(store));
+ key.size = sizeof(store) + 1;
+ if ((sdp->put)(sdp, &key, &sdata, methoduid) == -1)
+ error("put");
+
+ /* Store insecure and secure special plus and special minus */
+ if (pwd.pw_name[0] == '+' || pwd.pw_name[0] == '-') {
+ tbuf[0] = CURRENT_VERSION(_PW_KEYYPBYNUM);
+ store = htonl(ypcnt);
+ memmove(tbuf + 1, &store, sizeof(store));
+ key.size = sizeof(store) + 1;
+ if ((dp->put)(dp, &key, &data, method) == -1)
+ error("put");
+ if ((sdp->put)(sdp, &key, &sdata, method) == -1)
+ error("put");
+ }
+
+ if (lflag) {
+ /* Create insecure data. (legacy version) */
+ p = buf;
+ COMPACT(pwd.pw_name);
+ COMPACT("*");
+ LSCALAR(pwd.pw_uid);
+ LSCALAR(pwd.pw_gid);
+ LSCALAR(pwd.pw_change);
+ COMPACT(pwd.pw_class);
+ COMPACT(pwd.pw_gecos);
+ COMPACT(pwd.pw_dir);
+ COMPACT(pwd.pw_shell);
+ LSCALAR(pwd.pw_expire);
+ LSCALAR(pwd.pw_fields);
+ data.size = p - buf;
+
+ /* Create secure data. (legacy version) */
+ p = sbuf;
+ COMPACT(pwd.pw_name);
+ COMPACT(pwd.pw_passwd);
+ LSCALAR(pwd.pw_uid);
+ LSCALAR(pwd.pw_gid);
+ LSCALAR(pwd.pw_change);
+ COMPACT(pwd.pw_class);
+ COMPACT(pwd.pw_gecos);
+ COMPACT(pwd.pw_dir);
+ COMPACT(pwd.pw_shell);
+ LSCALAR(pwd.pw_expire);
+ LSCALAR(pwd.pw_fields);
+ sdata.size = p - sbuf;
+
+ /* Store insecure by name. */
+ tbuf[0] = LEGACY_VERSION(_PW_KEYBYNAME);
+ len = strlen(pwd.pw_name);
+ memmove(tbuf + 1, pwd.pw_name, len);
+ key.size = len + 1;
+ if ((dp->put)(dp, &key, &data, method) == -1)
+ error("put");
+
+ /* Store insecure by number. */
+ tbuf[0] = LEGACY_VERSION(_PW_KEYBYNUM);
+ store = HTOL(cnt);
+ memmove(tbuf + 1, &store, sizeof(store));
+ key.size = sizeof(store) + 1;
+ if ((dp->put)(dp, &key, &data, method) == -1)
+ error("put");
+
+ /* Store insecure by uid. */
+ tbuf[0] = LEGACY_VERSION(_PW_KEYBYUID);
+ store = HTOL(pwd.pw_uid);
+ memmove(tbuf + 1, &store, sizeof(store));
+ key.size = sizeof(store) + 1;
+ if ((dp->put)(dp, &key, &data, methoduid) == -1)
+ error("put");
+
+ /* Store secure by name. */
+ tbuf[0] = LEGACY_VERSION(_PW_KEYBYNAME);
+ len = strlen(pwd.pw_name);
+ memmove(tbuf + 1, pwd.pw_name, len);
+ key.size = len + 1;
+ if ((sdp->put)(sdp, &key, &sdata, method) == -1)
+ error("put");
+
+ /* Store secure by number. */
+ tbuf[0] = LEGACY_VERSION(_PW_KEYBYNUM);
+ store = HTOL(cnt);
+ memmove(tbuf + 1, &store, sizeof(store));
+ key.size = sizeof(store) + 1;
+ if ((sdp->put)(sdp, &key, &sdata, method) == -1)
+ error("put");
+
+ /* Store secure by uid. */
+ tbuf[0] = LEGACY_VERSION(_PW_KEYBYUID);
+ store = HTOL(pwd.pw_uid);
+ memmove(tbuf + 1, &store, sizeof(store));
+ key.size = sizeof(store) + 1;
+ if ((sdp->put)(sdp, &key, &sdata, methoduid) == -1)
+ error("put");
+
+ /* Store insecure and secure special plus and special minus */
+ if (pwd.pw_name[0] == '+' || pwd.pw_name[0] == '-') {
+ tbuf[0] = LEGACY_VERSION(_PW_KEYYPBYNUM);
+ store = HTOL(ypcnt);
+ memmove(tbuf + 1, &store, sizeof(store));
+ key.size = sizeof(store) + 1;
+ if ((dp->put)(dp, &key, &data, method) == -1)
+ error("put");
+ if ((sdp->put)(sdp, &key, &sdata, method) == -1)
+ error("put");
+ }
+ }
+ }
+ /* Create original format password file entry */
+ if (is_comment && makeold){ /* copy comments */
+ if (fprintf(oldfp, "%s\n", line) < 0)
+ error("write old");
+ } else if (makeold) {
+ char uidstr[20];
+ char gidstr[20];
+
+ snprintf(uidstr, sizeof(uidstr), "%u", pwd.pw_uid);
+ snprintf(gidstr, sizeof(gidstr), "%u", pwd.pw_gid);
+
+ if (fprintf(oldfp, "%s:*:%s:%s:%s:%s:%s\n",
+ pwd.pw_name, pwd.pw_fields & _PWF_UID ? uidstr : "",
+ pwd.pw_fields & _PWF_GID ? gidstr : "",
+ pwd.pw_gecos, pwd.pw_dir, pwd.pw_shell) < 0)
+ error("write old");
+ }
+ }
+ /* If YP enabled, set flag. */
+ if (yp_enabled) {
+ buf[0] = yp_enabled + 2;
+ data.size = 1;
+ key.size = 1;
+ tbuf[0] = CURRENT_VERSION(_PW_KEYYPENABLED);
+ if ((dp->put)(dp, &key, &data, method) == -1)
+ error("put");
+ if ((sdp->put)(sdp, &key, &data, method) == -1)
+ error("put");
+ if (lflag) {
+ tbuf[0] = LEGACY_VERSION(_PW_KEYYPENABLED);
+ key.size = 1;
+ if ((dp->put)(dp, &key, &data, method) == -1)
+ error("put");
+ if ((sdp->put)(sdp, &key, &data, method) == -1)
+ error("put");
+ }
+ }
+
+ if ((dp->close)(dp) == -1)
+ error("close");
+ if ((sdp->close)(sdp) == -1)
+ error("close");
+ if (makeold) {
+ (void)fflush(oldfp);
+ if (fclose(oldfp) == EOF)
+ error("close old");
+ }
+
+ /* Set master.passwd permissions, in case caller forgot. */
+ (void)fchmod(fileno(fp), S_IRUSR|S_IWUSR);
+
+ /* Install as the real password files. */
+ (void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _MP_DB);
+ (void)snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _MP_DB);
+ mv(buf, buf2);
+ (void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _SMP_DB);
+ (void)snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _SMP_DB);
+ mv(buf, buf2);
+ if (makeold) {
+ (void)snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _PASSWD);
+ (void)snprintf(buf, sizeof(buf), "%s.orig", pname);
+ mv(buf, buf2);
+ }
+ /*
+ * Move the master password LAST -- chpass(1), passwd(1) and vipw(8)
+ * all use flock(2) on it to block other incarnations of themselves.
+ * The rename means that everything is unlocked, as the original file
+ * can no longer be accessed.
+ */
+ (void)snprintf(buf, sizeof(buf), "%s/%s", prefix, _MASTERPASSWD);
+ mv(pname, buf);
+
+ /*
+ * Close locked password file after rename()
+ */
+ if (fclose(fp) == EOF)
+ error("close fp");
+
+ exit(0);
+}
+
+int
+scan(FILE *fp, struct passwd *pw)
+{
+ static int lcnt;
+ size_t len;
+ char *p;
+
+ p = fgetln(fp, &len);
+ if (p == NULL)
+ return (0);
+ ++lcnt;
+ /*
+ * ``... if I swallow anything evil, put your fingers down my
+ * throat...''
+ * -- The Who
+ */
+ if (len > 0 && p[len - 1] == '\n')
+ len--;
+ if (len >= sizeof(line) - 1) {
+ warnx("line #%d too long", lcnt);
+ goto fmt;
+ }
+ memcpy(line, p, len);
+ line[len] = '\0';
+
+ /*
+ * Ignore comments: ^[ \t]*#
+ */
+ for (p = line; *p != '\0'; p++)
+ if (*p != ' ' && *p != '\t')
+ break;
+ if (*p == '#' || *p == '\0') {
+ is_comment = 1;
+ return(1);
+ } else
+ is_comment = 0;
+
+ if (!__pw_scan(line, pw, _PWSCAN_WARN|_PWSCAN_MASTER)) {
+ warnx("at line #%d", lcnt);
+fmt: errno = EFTYPE; /* XXX */
+ error(pname);
+ }
+
+ return (1);
+}
+
+void
+cp(char *from, char *to, mode_t mode)
+{
+ static char buf[MAXBSIZE];
+ int from_fd, rcount, to_fd, wcount;
+
+ if ((from_fd = open(from, O_RDONLY, 0)) < 0)
+ error(from);
+ if ((to_fd = open(to, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0)
+ error(to);
+ while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) {
+ wcount = write(to_fd, buf, rcount);
+ if (rcount != wcount || wcount == -1) {
+ int sverrno = errno;
+
+ (void)snprintf(buf, sizeof(buf), "%s to %s", from, to);
+ errno = sverrno;
+ error(buf);
+ }
+ }
+ if (rcount < 0) {
+ int sverrno = errno;
+
+ (void)snprintf(buf, sizeof(buf), "%s to %s", from, to);
+ errno = sverrno;
+ error(buf);
+ }
+}
+
+
+void
+mv(char *from, char *to)
+{
+ char buf[MAXPATHLEN];
+ char *to_dir;
+ int to_dir_fd = -1;
+
+ /*
+ * Make sure file is safe on disk. To improve performance we will call
+ * fsync() to the directory where file lies
+ */
+ if (rename(from, to) != 0 ||
+ (to_dir = dirname(to)) == NULL ||
+ (to_dir_fd = open(to_dir, O_RDONLY|O_DIRECTORY)) == -1 ||
+ fsync(to_dir_fd) != 0) {
+ int sverrno = errno;
+ (void)snprintf(buf, sizeof(buf), "%s to %s", from, to);
+ errno = sverrno;
+ if (to_dir_fd != -1)
+ close(to_dir_fd);
+ error(buf);
+ }
+
+ if (to_dir_fd != -1)
+ close(to_dir_fd);
+}
+
+void
+error(const char *name)
+{
+
+ warn("%s", name);
+ cleanup();
+ exit(1);
+}
+
+void
+cleanup(void)
+{
+ char buf[MAXPATHLEN];
+
+ switch(clean) {
+ case FILE_ORIG:
+ (void)snprintf(buf, sizeof(buf), "%s.orig", pname);
+ (void)unlink(buf);
+ /* FALLTHROUGH */
+ case FILE_SECURE:
+ (void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _SMP_DB);
+ (void)unlink(buf);
+ /* FALLTHROUGH */
+ case FILE_INSECURE:
+ (void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _MP_DB);
+ (void)unlink(buf);
+ }
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr,
+"usage: pwd_mkdb [-BCiLNp] [-d directory] [-s cachesize] [-u username] file\n");
+ exit(1);
+}
diff --git a/usr.sbin/quot/Makefile b/usr.sbin/quot/Makefile
new file mode 100644
index 0000000..07bcc7e
--- /dev/null
+++ b/usr.sbin/quot/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= quot
+MAN= quot.8
+
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/quot/Makefile.depend b/usr.sbin/quot/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/quot/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/quot/quot.8 b/usr.sbin/quot/quot.8
new file mode 100644
index 0000000..5451f63
--- /dev/null
+++ b/usr.sbin/quot/quot.8
@@ -0,0 +1,110 @@
+.\" Copyright (C) 1994 Wolfgang Solfrank.
+.\" Copyright (C) 1994 TooLs GmbH.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 TooLs GmbH.
+.\" 4. The name of TooLs GmbH may not be used to endorse or promote products
+.\" derived from this software without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL TOOLS GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+.\" PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+.\" OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+.\" WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 8, 1994
+.Dt QUOT 8
+.Os
+.Sh NAME
+.Nm quot
+.Nd display disk space occupied by each user
+.Sh SYNOPSIS
+.Nm
+.Op Fl acfhknv
+.Op Ar filesystem ...
+.Sh DESCRIPTION
+The
+.Nm
+utility
+is used to gather statistics about the disk usage for each local user.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl a
+Include statistics for all mounted file systems.
+.It Fl c
+Display three columns containing number of blocks per file,
+number of files in this category, and aggregate total of
+blocks in files with this or lower size.
+.It Fl f
+For each user, display count of files and space occupied.
+.It Fl h
+Estimate the number of blocks in each file based on its size.
+Despite that this does not give the correct results (it does not
+account for the holes in files), this option is not any faster
+and thus is discouraged.
+.It Fl k
+Force the numbers to be reported in kilobyte counts.
+By default, all sizes are reported in 512-byte block counts.
+.It Fl n
+Given a list of inodes (plus some optional data on each line)
+in the standard input, for each file print out the owner (plus
+the remainder of the input line).
+This is traditionally used
+in the pipe:
+.Bd -literal -offset indent
+.\" ncheck filesystem | sort +0n | quot -n filesystem
+ls -i | sed -e 's,^ *,,' | sort -k 1n | quot -n filesystem
+.Ed
+.Pp
+to get a report of files and their owners.
+.It Fl v
+In addition to the default output, display the number of files
+not accessed within 30, 60 and 90 days.
+.El
+.Sh ENVIRONMENT
+.Bl -tag -width BLOCKSIZE
+.It Ev BLOCKSIZE
+If the environment variable
+.Ev BLOCKSIZE
+is set, and the
+.Fl k
+option is not specified, the block counts will be displayed in units of that
+size block.
+.El
+.Sh SEE ALSO
+.Xr df 1 ,
+.Xr quota 1 ,
+.Xr getmntinfo 3 ,
+.Xr fstab 5 ,
+.Xr mount 8
+.Sh HISTORY
+This implementation of
+.Nm
+is by
+.An Wolfgang Solfrank
+/ TooLs GmbH.
+.Sh BUGS
+.Xr ncheck
+(which would be a lot more useful than
+.Nm ls Fl i
+in the example above)
+does not exist in
+.Fx .
diff --git a/usr.sbin/quot/quot.c b/usr.sbin/quot/quot.c
new file mode 100644
index 0000000..ac90da6
--- /dev/null
+++ b/usr.sbin/quot/quot.c
@@ -0,0 +1,644 @@
+/*
+ * Copyright (C) 1991, 1994 Wolfgang Solfrank.
+ * Copyright (C) 1991, 1994 TooLs GmbH.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 TooLs GmbH.
+ * 4. The name of TooLs GmbH may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL TOOLS GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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/stdint.h>
+#include <sys/mount.h>
+#include <sys/disklabel.h>
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <fstab.h>
+#include <errno.h>
+#include <paths.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+/* some flags of what to do: */
+static char estimate;
+static char count;
+static char unused;
+static void (*func)(int, struct fs *, char *);
+static long blocksize;
+static char *header;
+static int headerlen;
+
+static union dinode *get_inode(int, struct fs *, ino_t);
+static int virtualblocks(struct fs *, union dinode *);
+static int isfree(struct fs *, union dinode *);
+static void inituser(void);
+static void usrrehash(void);
+static struct user *user(uid_t);
+static int cmpusers(const void *, const void *);
+static void uses(uid_t, daddr_t, time_t);
+static void initfsizes(void);
+static void dofsizes(int, struct fs *, char *);
+static void douser(int, struct fs *, char *);
+static void donames(int, struct fs *, char *);
+static void usage(void);
+static void quot(char *, char *);
+
+/*
+ * Original BSD quot doesn't round to number of frags/blocks,
+ * doesn't account for indirection blocks and gets it totally
+ * wrong if the size is a multiple of the blocksize.
+ * The new code always counts the number of 512 byte blocks
+ * instead of the number of kilobytes and converts them to
+ * kByte when done (on request).
+ *
+ * Due to the size of modern disks, we must cast intermediate
+ * values to 64 bits to prevent potential overflows.
+ */
+#ifdef COMPAT
+#define SIZE(n) (n)
+#else
+#define SIZE(n) ((int)(((quad_t)(n) * 512 + blocksize - 1)/blocksize))
+#endif
+
+#define INOCNT(fs) ((fs)->fs_ipg)
+#define INOSZ(fs) \
+ (((fs)->fs_magic == FS_UFS1_MAGIC ? sizeof(struct ufs1_dinode) : \
+ sizeof(struct ufs2_dinode)) * INOCNT(fs))
+
+union dinode {
+ struct ufs1_dinode dp1;
+ struct ufs2_dinode dp2;
+};
+#define DIP(fs, dp, field) \
+ (((fs)->fs_magic == FS_UFS1_MAGIC) ? \
+ (dp)->dp1.field : (dp)->dp2.field)
+
+static union dinode *
+get_inode(int fd, struct fs *super, ino_t ino)
+{
+ static caddr_t ipbuf;
+ static struct cg *cgp;
+ static ino_t last;
+ static int cg;
+ struct ufs2_dinode *di2;
+
+ if (fd < 0) { /* flush cache */
+ if (ipbuf) {
+ free(ipbuf);
+ ipbuf = 0;
+ if (super != NULL && super->fs_magic == FS_UFS2_MAGIC) {
+ free(cgp);
+ cgp = 0;
+ }
+ }
+ return 0;
+ }
+
+ if (!ipbuf || ino < last || ino >= last + INOCNT(super)) {
+ if (super->fs_magic == FS_UFS2_MAGIC &&
+ (!cgp || cg != ino_to_cg(super, ino))) {
+ cg = ino_to_cg(super, ino);
+ if (!cgp && !(cgp = malloc(super->fs_cgsize)))
+ errx(1, "allocate cg");
+ if (lseek(fd, (off_t)cgtod(super, cg) << super->fs_fshift, 0) < 0)
+ err(1, "lseek cg");
+ if (read(fd, cgp, super->fs_cgsize) != super->fs_cgsize)
+ err(1, "read cg");
+ if (!cg_chkmagic(cgp))
+ errx(1, "cg has bad magic");
+ }
+ if (!ipbuf
+ && !(ipbuf = malloc(INOSZ(super))))
+ errx(1, "allocate inodes");
+ last = (ino / INOCNT(super)) * INOCNT(super);
+ if (lseek(fd, (off_t)ino_to_fsba(super, last) << super->fs_fshift, 0) < (off_t)0
+ || read(fd, ipbuf, INOSZ(super)) != (ssize_t)INOSZ(super))
+ err(1, "read inodes");
+ }
+
+ if (super->fs_magic == FS_UFS1_MAGIC)
+ return ((union dinode *)
+ &((struct ufs1_dinode *)ipbuf)[ino % INOCNT(super)]);
+ di2 = &((struct ufs2_dinode *)ipbuf)[ino % INOCNT(super)];
+ /* If the inode is unused, it might be unallocated too, so zero it. */
+ if (isclr(cg_inosused(cgp), ino % super->fs_ipg))
+ bzero(di2, sizeof (*di2));
+ return ((union dinode *)di2);
+}
+
+#ifdef COMPAT
+#define actualblocks(fs, dp) (DIP(fs, dp, di_blocks) / 2)
+#else
+#define actualblocks(fs, dp) DIP(fs, dp, di_blocks)
+#endif
+
+static int virtualblocks(struct fs *super, union dinode *dp)
+{
+ off_t nblk, sz;
+
+ sz = DIP(super, dp, di_size);
+#ifdef COMPAT
+ if (lblkno(super,sz) >= NDADDR) {
+ nblk = blkroundup(super,sz);
+ if (sz == nblk)
+ nblk += super->fs_bsize;
+ }
+
+ return sz / 1024;
+
+#else /* COMPAT */
+
+ if (lblkno(super,sz) >= NDADDR) {
+ nblk = blkroundup(super,sz);
+ sz = lblkno(super,nblk);
+ sz = (sz - NDADDR + NINDIR(super) - 1) / NINDIR(super);
+ while (sz > 0) {
+ nblk += sz * super->fs_bsize;
+ /* sz - 1 rounded up */
+ sz = (sz - 1 + NINDIR(super) - 1) / NINDIR(super);
+ }
+ } else
+ nblk = fragroundup(super,sz);
+
+ return nblk / 512;
+#endif /* COMPAT */
+}
+
+static int
+isfree(struct fs *super, union dinode *dp)
+{
+#ifdef COMPAT
+ return (DIP(super, dp, di_mode) & IFMT) == 0;
+#else /* COMPAT */
+
+ switch (DIP(super, dp, di_mode) & IFMT) {
+ case IFIFO:
+ case IFLNK: /* should check FASTSYMLINK? */
+ case IFDIR:
+ case IFREG:
+ return 0;
+ case IFCHR:
+ case IFBLK:
+ case IFSOCK:
+ case IFWHT:
+ case 0:
+ return 1;
+ default:
+ errx(1, "unknown IFMT 0%o", DIP(super, dp, di_mode) & IFMT);
+ }
+#endif
+}
+
+static struct user {
+ uid_t uid;
+ char *name;
+ daddr_t space;
+ long count;
+ daddr_t spc30;
+ daddr_t spc60;
+ daddr_t spc90;
+} *users;
+static int nusers;
+
+static void
+inituser(void)
+{
+ int i;
+ struct user *usr;
+
+ if (!nusers) {
+ nusers = 8;
+ if (!(users =
+ (struct user *)calloc(nusers,sizeof(struct user))))
+ errx(1, "allocate users");
+ } else {
+ for (usr = users, i = nusers; --i >= 0; usr++) {
+ usr->space = usr->spc30 = usr->spc60 = usr->spc90 = 0;
+ usr->count = 0;
+ }
+ }
+}
+
+static void
+usrrehash(void)
+{
+ int i;
+ struct user *usr, *usrn;
+ struct user *svusr;
+
+ svusr = users;
+ nusers <<= 1;
+ if (!(users = (struct user *)calloc(nusers,sizeof(struct user))))
+ errx(1, "allocate users");
+ for (usr = svusr, i = nusers >> 1; --i >= 0; usr++) {
+ for (usrn = users + (usr->uid&(nusers - 1)); usrn->name;
+ usrn--) {
+ if (usrn <= users)
+ usrn = users + nusers;
+ }
+ *usrn = *usr;
+ }
+}
+
+static struct user *
+user(uid_t uid)
+{
+ struct user *usr;
+ int i;
+ struct passwd *pwd;
+
+ while (1) {
+ for (usr = users + (uid&(nusers - 1)), i = nusers; --i >= 0;
+ usr--) {
+ if (!usr->name) {
+ usr->uid = uid;
+
+ if (!(pwd = getpwuid(uid))) {
+ if ((usr->name = (char *)malloc(7)))
+ sprintf(usr->name,"#%d",uid);
+ } else {
+ if ((usr->name = (char *)
+ malloc(strlen(pwd->pw_name) + 1)))
+ strcpy(usr->name,pwd->pw_name);
+ }
+ if (!usr->name)
+ errx(1, "allocate users");
+
+ return usr;
+
+ } else if (usr->uid == uid)
+ return usr;
+
+ if (usr <= users)
+ usr = users + nusers;
+ }
+ usrrehash();
+ }
+}
+
+static int
+cmpusers(const void *v1, const void *v2)
+{
+ const struct user *u1, *u2;
+ u1 = (const struct user *)v1;
+ u2 = (const struct user *)v2;
+
+ return u2->space - u1->space;
+}
+
+#define sortusers(users) (qsort((users),nusers,sizeof(struct user), \
+ cmpusers))
+
+static void
+uses(uid_t uid, daddr_t blks, time_t act)
+{
+ static time_t today;
+ struct user *usr;
+
+ if (!today)
+ time(&today);
+
+ usr = user(uid);
+ usr->count++;
+ usr->space += blks;
+
+ if (today - act > 90L * 24L * 60L * 60L)
+ usr->spc90 += blks;
+ if (today - act > 60L * 24L * 60L * 60L)
+ usr->spc60 += blks;
+ if (today - act > 30L * 24L * 60L * 60L)
+ usr->spc30 += blks;
+}
+
+#ifdef COMPAT
+#define FSZCNT 500
+#else
+#define FSZCNT 512
+#endif
+struct fsizes {
+ struct fsizes *fsz_next;
+ daddr_t fsz_first, fsz_last;
+ ino_t fsz_count[FSZCNT];
+ daddr_t fsz_sz[FSZCNT];
+} *fsizes;
+
+static void
+initfsizes(void)
+{
+ struct fsizes *fp;
+ int i;
+
+ for (fp = fsizes; fp; fp = fp->fsz_next) {
+ for (i = FSZCNT; --i >= 0;) {
+ fp->fsz_count[i] = 0;
+ fp->fsz_sz[i] = 0;
+ }
+ }
+}
+
+static void
+dofsizes(int fd, struct fs *super, char *name)
+{
+ ino_t inode, maxino;
+ union dinode *dp;
+ daddr_t sz, ksz;
+ struct fsizes *fp, **fsp;
+ int i;
+
+ maxino = super->fs_ncg * super->fs_ipg - 1;
+#ifdef COMPAT
+ if (!(fsizes = (struct fsizes *)malloc(sizeof(struct fsizes))))
+ errx(1, "allocate fsize structure");
+#endif /* COMPAT */
+ for (inode = 0; inode < maxino; inode++) {
+ errno = 0;
+ if ((dp = get_inode(fd,super,inode))
+#ifdef COMPAT
+ && ((DIP(super, dp, di_mode) & IFMT) == IFREG
+ || (DIP(super, dp, di_mode) & IFMT) == IFDIR)
+#else /* COMPAT */
+ && !isfree(super, dp)
+#endif /* COMPAT */
+ ) {
+ sz = estimate ? virtualblocks(super, dp) :
+ actualblocks(super, dp);
+#ifdef COMPAT
+ if (sz >= FSZCNT) {
+ fsizes->fsz_count[FSZCNT-1]++;
+ fsizes->fsz_sz[FSZCNT-1] += sz;
+ } else {
+ fsizes->fsz_count[sz]++;
+ fsizes->fsz_sz[sz] += sz;
+ }
+#else /* COMPAT */
+ ksz = SIZE(sz);
+ for (fsp = &fsizes; (fp = *fsp); fsp = &fp->fsz_next) {
+ if (ksz < fp->fsz_last)
+ break;
+ }
+ if (!fp || ksz < fp->fsz_first) {
+ if (!(fp = (struct fsizes *)
+ malloc(sizeof(struct fsizes))))
+ errx(1, "allocate fsize structure");
+ fp->fsz_next = *fsp;
+ *fsp = fp;
+ fp->fsz_first = (ksz / FSZCNT) * FSZCNT;
+ fp->fsz_last = fp->fsz_first + FSZCNT;
+ for (i = FSZCNT; --i >= 0;) {
+ fp->fsz_count[i] = 0;
+ fp->fsz_sz[i] = 0;
+ }
+ }
+ fp->fsz_count[ksz % FSZCNT]++;
+ fp->fsz_sz[ksz % FSZCNT] += sz;
+#endif /* COMPAT */
+ } else if (errno) {
+ err(1, "%s", name);
+ }
+ }
+ sz = 0;
+ for (fp = fsizes; fp; fp = fp->fsz_next) {
+ for (i = 0; i < FSZCNT; i++) {
+ if (fp->fsz_count[i])
+ printf("%jd\t%jd\t%d\n",
+ (intmax_t)(fp->fsz_first + i),
+ (intmax_t)fp->fsz_count[i],
+ SIZE(sz += fp->fsz_sz[i]));
+ }
+ }
+}
+
+static void
+douser(int fd, struct fs *super, char *name)
+{
+ ino_t inode, maxino;
+ struct user *usr, *usrs;
+ union dinode *dp;
+ int n;
+
+ maxino = super->fs_ncg * super->fs_ipg - 1;
+ for (inode = 0; inode < maxino; inode++) {
+ errno = 0;
+ if ((dp = get_inode(fd,super,inode))
+ && !isfree(super, dp))
+ uses(DIP(super, dp, di_uid),
+ estimate ? virtualblocks(super, dp) :
+ actualblocks(super, dp),
+ DIP(super, dp, di_atime));
+ else if (errno) {
+ err(1, "%s", name);
+ }
+ }
+ if (!(usrs = (struct user *)malloc(nusers * sizeof(struct user))))
+ errx(1, "allocate users");
+ bcopy(users,usrs,nusers * sizeof(struct user));
+ sortusers(usrs);
+ for (usr = usrs, n = nusers; --n >= 0 && usr->count; usr++) {
+ printf("%5d",SIZE(usr->space));
+ if (count)
+ printf("\t%5ld",usr->count);
+ printf("\t%-8s",usr->name);
+ if (unused)
+ printf("\t%5d\t%5d\t%5d",
+ SIZE(usr->spc30),
+ SIZE(usr->spc60),
+ SIZE(usr->spc90));
+ printf("\n");
+ }
+ free(usrs);
+}
+
+static void
+donames(int fd, struct fs *super, char *name)
+{
+ int c;
+ ino_t maxino;
+ uintmax_t inode;
+ union dinode *dp;
+
+ maxino = super->fs_ncg * super->fs_ipg - 1;
+ /* first skip the name of the filesystem */
+ while ((c = getchar()) != EOF && (c < '0' || c > '9'))
+ while ((c = getchar()) != EOF && c != '\n');
+ ungetc(c,stdin);
+ while (scanf("%ju", &inode) == 1) {
+ if (inode > maxino) {
+ warnx("illegal inode %ju", inode);
+ return;
+ }
+ errno = 0;
+ if ((dp = get_inode(fd,super,inode))
+ && !isfree(super, dp)) {
+ printf("%s\t",user(DIP(super, dp, di_uid))->name);
+ /* now skip whitespace */
+ while ((c = getchar()) == ' ' || c == '\t');
+ /* and print out the remainder of the input line */
+ while (c != EOF && c != '\n') {
+ putchar(c);
+ c = getchar();
+ }
+ putchar('\n');
+ } else {
+ if (errno) {
+ err(1, "%s", name);
+ }
+ /* skip this line */
+ while ((c = getchar()) != EOF && c != '\n');
+ }
+ if (c == EOF)
+ break;
+ }
+}
+
+static void
+usage(void)
+{
+#ifdef COMPAT
+ fprintf(stderr,"usage: quot [-nfcvha] [filesystem ...]\n");
+#else /* COMPAT */
+ fprintf(stderr,"usage: quot [-acfhknv] [filesystem ...]\n");
+#endif /* COMPAT */
+ exit(1);
+}
+
+/*
+ * Possible superblock locations ordered from most to least likely.
+ */
+static int sblock_try[] = SBLOCKSEARCH;
+static char superblock[SBLOCKSIZE];
+
+void
+quot(char *name, char *mp)
+{
+ int i, fd;
+ struct fs *fs;
+
+ get_inode(-1, NULL, 0); /* flush cache */
+ inituser();
+ initfsizes();
+ if ((fd = open(name,0)) < 0) {
+ warn("%s", name);
+ close(fd);
+ return;
+ }
+ for (i = 0; sblock_try[i] != -1; i++) {
+ if (lseek(fd, sblock_try[i], 0) != sblock_try[i]) {
+ close(fd);
+ return;
+ }
+ if (read(fd, superblock, SBLOCKSIZE) != SBLOCKSIZE) {
+ close(fd);
+ return;
+ }
+ fs = (struct fs *)superblock;
+ if ((fs->fs_magic == FS_UFS1_MAGIC ||
+ (fs->fs_magic == FS_UFS2_MAGIC &&
+ fs->fs_sblockloc == sblock_try[i])) &&
+ fs->fs_bsize <= MAXBSIZE &&
+ fs->fs_bsize >= sizeof(struct fs))
+ break;
+ }
+ if (sblock_try[i] == -1) {
+ warnx("%s: not a BSD filesystem",name);
+ close(fd);
+ return;
+ }
+ printf("%s:",name);
+ if (mp)
+ printf(" (%s)",mp);
+ putchar('\n');
+ (*func)(fd, fs, name);
+ close(fd);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char all = 0;
+ struct statfs *mp;
+ struct fstab *fs;
+ int cnt;
+
+ func = douser;
+#ifndef COMPAT
+ header = getbsize(&headerlen,&blocksize);
+#endif
+ while (--argc > 0 && **++argv == '-') {
+ while (*++*argv) {
+ switch (**argv) {
+ case 'n':
+ func = donames;
+ break;
+ case 'c':
+ func = dofsizes;
+ break;
+ case 'a':
+ all = 1;
+ break;
+ case 'f':
+ count = 1;
+ break;
+ case 'h':
+ estimate = 1;
+ break;
+#ifndef COMPAT
+ case 'k':
+ blocksize = 1024;
+ break;
+#endif /* COMPAT */
+ case 'v':
+ unused = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+ }
+ if (all) {
+ cnt = getmntinfo(&mp,MNT_NOWAIT);
+ for (; --cnt >= 0; mp++) {
+ if (!strncmp(mp->f_fstypename, "ufs", MFSNAMELEN))
+ quot(mp->f_mntfromname, mp->f_mntonname);
+ }
+ }
+ while (--argc >= 0) {
+ if ((fs = getfsfile(*argv)) != NULL)
+ quot(fs->fs_spec, 0);
+ else
+ quot(*argv,0);
+ argv++;
+ }
+ return 0;
+}
diff --git a/usr.sbin/quotaon/Makefile b/usr.sbin/quotaon/Makefile
new file mode 100644
index 0000000..ec3dc1a
--- /dev/null
+++ b/usr.sbin/quotaon/Makefile
@@ -0,0 +1,11 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+PROG= quotaon
+LINKS= ${BINDIR}/quotaon ${BINDIR}/quotaoff
+MAN= quotaon.8
+MLINKS= quotaon.8 quotaoff.8
+
+LIBADD= util
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/quotaon/Makefile.depend b/usr.sbin/quotaon/Makefile.depend
new file mode 100644
index 0000000..58f9a33
--- /dev/null
+++ b/usr.sbin/quotaon/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/quotaon/quotaon.8 b/usr.sbin/quotaon/quotaon.8
new file mode 100644
index 0000000..08e8d11
--- /dev/null
+++ b/usr.sbin/quotaon/quotaon.8
@@ -0,0 +1,137 @@
+.\" 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.
+.\"
+.\" @(#)quotaon.8 8.2 (Berkeley) 12/11/93
+.\" $FreeBSD$
+.\"
+.Dd December 11, 1993
+.Dt QUOTAON 8
+.Os
+.Sh NAME
+.Nm quotaon ,
+.Nm quotaoff
+.Nd turn file system quotas on and off
+.Sh SYNOPSIS
+.Nm
+.Op Fl g
+.Op Fl u
+.Op Fl v
+.Ar filesystem Ar ...
+.Nm
+.Op Fl g
+.Op Fl u
+.Op Fl v
+.Fl a
+.Nm quotaoff
+.Op Fl g
+.Op Fl u
+.Op Fl v
+.Ar filesystem Ar ...
+.Nm quotaoff
+.Op Fl g
+.Op Fl u
+.Op Fl v
+.Fl a
+.Sh DESCRIPTION
+The
+.Nm
+utility
+announces to the system that disk quotas should be enabled on one or more
+file systems.
+The
+.Nm quotaoff
+utility announces to the system that the specified
+file systems should have any disk quotas turned off.
+The file systems specified must have entries in
+.Pa /etc/fstab
+and be mounted.
+The
+.Nm
+utility expects each file system to have 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 .
+By default both user and group quotas are enabled.
+.Pp
+Available options:
+.Bl -tag -width indent
+.It Fl a
+If supplied in place of any file system names,
+.Nm Ns / Ns Nm quotaoff
+will enable/disable 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 enabled.
+.It Fl g
+Only group quotas listed in
+.Pa /etc/fstab
+should be enabled/disabled.
+.It Fl u
+Only user quotas listed in
+.Pa /etc/fstab
+should be enabled/disabled.
+.It Fl v
+Cause
+.Nm
+and
+.Nm quotaoff
+to print a message for each file system where quotas are turned on or off.
+.El
+.Pp
+Specifying both
+.Fl g
+and
+.Fl u
+is equivalent to the default.
+.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
+file system table
+.El
+.Sh SEE ALSO
+.Xr quota 1 ,
+.Xr quotactl 2 ,
+.Xr fstab 5 ,
+.Xr edquota 8 ,
+.Xr quotacheck 8 ,
+.Xr repquota 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.2 .
diff --git a/usr.sbin/quotaon/quotaon.c b/usr.sbin/quotaon/quotaon.c
new file mode 100644
index 0000000..740df39
--- /dev/null
+++ b/usr.sbin/quotaon/quotaon.c
@@ -0,0 +1,192 @@
+/*
+ * 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[] = "@(#)quotaon.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Turn quota on/off for a filesystem.
+ */
+#include <sys/param.h>
+#include <sys/file.h>
+#include <sys/mount.h>
+#include <ufs/ufs/quota.h>
+#include <err.h>
+#include <fstab.h>
+#include <libutil.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static const char *qfextension[] = INITQFNAMES;
+
+static int aflag; /* all filesystems */
+static int gflag; /* operate on group quotas */
+static int uflag; /* operate on user quotas */
+static int vflag; /* verbose */
+
+static int oneof(char *, char *[], int);
+static int quotaonoff(struct fstab *fs, int, int);
+static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ struct fstab *fs;
+ const char *whoami;
+ long argnum, done = 0;
+ int ch, i, offmode = 0, errs = 0;
+
+ whoami = getprogname();
+ if (strcmp(whoami, "quotaoff") == 0)
+ offmode++;
+ else if (strcmp(whoami, "quotaon") != 0)
+ errx(1, "name must be quotaon or quotaoff");
+ while ((ch = getopt(argc, argv, "avug")) != -1) {
+ switch(ch) {
+ case 'a':
+ aflag++;
+ break;
+ case 'g':
+ gflag++;
+ break;
+ case 'u':
+ uflag++;
+ break;
+ case 'v':
+ vflag++;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc <= 0 && !aflag)
+ usage();
+ if (!gflag && !uflag) {
+ gflag++;
+ uflag++;
+ }
+ setfsent();
+ while ((fs = getfsent()) != NULL) {
+ if (strcmp(fs->fs_vfstype, "ufs") ||
+ strcmp(fs->fs_type, FSTAB_RW))
+ continue;
+ if (aflag) {
+ if (gflag)
+ errs += quotaonoff(fs, offmode, GRPQUOTA);
+ if (uflag)
+ errs += quotaonoff(fs, offmode, USRQUOTA);
+ continue;
+ }
+ if ((argnum = oneof(fs->fs_file, argv, argc)) >= 0 ||
+ (argnum = oneof(fs->fs_spec, argv, argc)) >= 0) {
+ done |= 1 << argnum;
+ if (gflag)
+ errs += quotaonoff(fs, offmode, GRPQUOTA);
+ if (uflag)
+ errs += quotaonoff(fs, offmode, USRQUOTA);
+ }
+ }
+ endfsent();
+ for (i = 0; i < argc; i++)
+ if ((done & (1 << i)) == 0)
+ warnx("%s not found in fstab", argv[i]);
+ exit(errs);
+}
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "%s\n%s\n%s\n%s\n",
+ "usage: quotaon [-g] [-u] [-v] -a",
+ " quotaon [-g] [-u] [-v] filesystem ...",
+ " quotaoff [-g] [-u] [-v] -a",
+ " quotaoff [-g] [-u] [-v] filesystem ...");
+ exit(1);
+}
+
+static int
+quotaonoff(struct fstab *fs, int offmode, int type)
+{
+ struct quotafile *qf;
+
+ if ((qf = quota_open(fs, type, O_RDONLY)) == NULL)
+ return (0);
+ if (offmode) {
+ if (quota_off(qf) != 0) {
+ warn("%s", quota_fsname(qf));
+ return (1);
+ }
+ if (vflag)
+ printf("%s: quotas turned off\n", quota_fsname(qf));
+ quota_close(qf);
+ return(0);
+ }
+ if (quota_on(qf) != 0) {
+ warn("using %s on %s", quota_qfname(qf), quota_fsname(qf));
+ return (1);
+ }
+ if (vflag)
+ printf("%s: %s quotas turned on with data file %s\n",
+ quota_fsname(qf), qfextension[type], quota_qfname(qf));
+ quota_close(qf);
+ return(0);
+}
+
+/*
+ * Check to see if target appears in list of size cnt.
+ */
+static 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);
+}
diff --git a/usr.sbin/rarpd/Makefile b/usr.sbin/rarpd/Makefile
new file mode 100644
index 0000000..b7816cd
--- /dev/null
+++ b/usr.sbin/rarpd/Makefile
@@ -0,0 +1,13 @@
+# from: @(#)Makefile 5.8 (Berkeley) 7/28/90
+# $FreeBSD$
+
+PROG= rarpd
+MAN= rarpd.8
+
+LIBADD= util
+
+WARNS?= 3
+# This breaks with format strings returned by expand_syslog_m().. argh!
+#FORMAT_AUDIT?= 1
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/rarpd/Makefile.depend b/usr.sbin/rarpd/Makefile.depend
new file mode 100644
index 0000000..7de116d
--- /dev/null
+++ b/usr.sbin/rarpd/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/rarpd/rarpd.8 b/usr.sbin/rarpd/rarpd.8
new file mode 100644
index 0000000..4b00c9e
--- /dev/null
+++ b/usr.sbin/rarpd/rarpd.8
@@ -0,0 +1,160 @@
+.\" 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: (1) source code distributions
+.\" retain the above copyright notice and this paragraph in its entirety, (2)
+.\" distributions including binary code include the above copyright notice and
+.\" this paragraph in its entirety in the documentation or other materials
+.\" provided with the distribution. Neither the name of
+.\" the University nor the names of its contributors may be used to endorse
+.\" or promote products derived from this software without specific prior
+.\" written permission.
+.\" THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
+.\" WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd July 9, 2012
+.Dt RARPD 8
+.Os
+.Sh NAME
+.Nm rarpd
+.Nd reverse ARP daemon
+.Sh SYNOPSIS
+.Nm
+.Fl a
+.Op Fl dfsv
+.Op Fl t Ar directory
+.Op Fl P Ar pidfile
+.Nm
+.Op Fl dfsv
+.Op Fl t Ar directory
+.Op Fl P Ar pidfile
+.Ar interface
+.Sh DESCRIPTION
+The
+.Nm
+utility services Reverse ARP requests on the Ethernet connected to
+.Ar interface .
+Upon receiving a request,
+.Nm
+maps the target hardware address to an IP address via its name, which
+must be present in both the
+.Xr ethers 5
+and
+.Xr hosts 5
+databases.
+If a host does not exist in both databases, the translation cannot
+proceed and a reply will not be sent.
+.Pp
+By default, a request is honored only if the server
+(i.e., the host that
+.Nm
+is running on)
+can "boot" the target; that is, a file or directory matching the glob
+.Pa /tftpboot/\fIipaddr\fP*
+exists, where
+.Em ipaddr
+is the target IP address in hex.
+For example, the IP address 204.216.27.18 will be replied to if any of
+.Pa /tftpboot/CCD81B12 ,
+.Pa /tftpboot/CCD81B12.SUN3 ,
+or
+.Pa /tftpboot/CCD81B12-boot
+exist.
+This requirement can be overridden with the
+.Fl s
+flag (see below).
+.Pp
+In normal operation,
+.Nm
+forks a copy of itself and runs in the background.
+Anomalies and errors are reported via
+.Xr syslog 3 .
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl a
+Listen on all the Ethernets attached to the system.
+If
+.Fl a
+is omitted, an interface must be specified.
+.It Fl d
+If
+.Fl f
+is also specified,
+.Nm
+logs messages to
+.Em stdout
+and
+.Em stderr
+instead of via
+.Xr syslog 3 .
+.It Fl f
+Run in the foreground.
+.It Fl P
+Specify the pathname of the PID file.
+If not specified,
+.Pa /var/run/rarpd.pid
+or
+.Pa /var/run/rarpd.ifname.pid
+will be used depending on the
+.Fl a
+flag or the specified interface name.
+.It Fl s
+Supply a response to any RARP request for which an ethernet to IP address
+mapping exists; do not depend on the existence of
+.Pa /tftpboot/\fIipaddr\fP* .
+.It Fl t
+Supply an alternate tftp root directory to
+.Pa /tftpboot ,
+similar to the
+.Fl s
+option of
+.Xr tftpd 8 .
+This permits
+.Nm
+to selectively respond to RARP requests, but use an alternate directory
+for IP checking.
+.It Fl v
+Enable verbose syslogging.
+.El
+.Sh FILES
+.Bl -tag -width /etc/ethers -compact
+.It Pa /etc/ethers
+.It Pa /etc/hosts
+.It Pa /tftpboot
+.It Pa /var/run/rarpd.pid
+.El
+.Sh SEE ALSO
+.Xr bpf 4
+.Rs
+.%A "Finlayson, R."
+.%A "Mann, T."
+.%A "Mogul, J.C."
+.%A "Theimer, M."
+.%T "RFC 903: Reverse Address Resolution Protocol"
+.%D "June 1984"
+.%O "4 p"
+.Re
+.Sh AUTHORS
+.An -nosplit
+.An Craig Leres Aq Mt leres@ee.lbl.gov
+and
+.An Steven McCanne Aq Mt mccanne@ee.lbl.gov .
+Lawrence Berkeley Laboratory, University of California, Berkeley, CA.
+.Sh BUGS
+The
+.Nm
+utility can depend on the DNS to resolve the name discovered from
+.Pa /etc/ethers .
+If this name is not in the DNS but is in
+.Pa /etc/hosts ,
+the DNS lookup can cause a delayed RARP response, so in this situation
+it is recommended to configure
+.Xr nsswitch.conf 5
+to read
+.Pa /etc/hosts
+first.
diff --git a/usr.sbin/rarpd/rarpd.c b/usr.sbin/rarpd/rarpd.c
new file mode 100644
index 0000000..74f7940
--- /dev/null
+++ b/usr.sbin/rarpd/rarpd.c
@@ -0,0 +1,1003 @@
+/*
+ * Copyright (c) 1990, 1991, 1992, 1993, 1996
+ * 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: (1) source code distributions
+ * retain the above copyright notice and this paragraph in its entirety, (2)
+ * distributions including binary code include the above copyright notice and
+ * this paragraph in its entirety in the documentation or other materials
+ * provided with the distribution
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1990, 1991, 1992, 1993, 1996\n\
+The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * rarpd - Reverse ARP Daemon
+ *
+ * Usage: rarpd -a [-dfsv] [-t directory] [-P pidfile] [hostname]
+ * rarpd [-dfsv] [-t directory] [-P pidfile] interface [hostname]
+ *
+ * 'hostname' is optional solely for backwards compatibility with Sun's rarpd.
+ * Currently, the argument is ignored.
+ */
+#include <sys/param.h>
+#include <sys/file.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <net/bpf.h>
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_types.h>
+#include <net/if_dl.h>
+#include <net/route.h>
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <arpa/inet.h>
+
+#include <dirent.h>
+#include <errno.h>
+#include <ifaddrs.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <libutil.h>
+
+/* Cast a struct sockaddr to a struct sockaddr_in */
+#define SATOSIN(sa) ((struct sockaddr_in *)(sa))
+
+#ifndef TFTP_DIR
+#define TFTP_DIR "/tftpboot"
+#endif
+
+#define ARPSECS (20 * 60) /* as per code in netinet/if_ether.c */
+#define REVARP_REQUEST ARPOP_REVREQUEST
+#define REVARP_REPLY ARPOP_REVREPLY
+
+/*
+ * The structure for each interface.
+ */
+struct if_info {
+ struct if_info *ii_next;
+ int ii_fd; /* BPF file descriptor */
+ in_addr_t ii_ipaddr; /* IP address */
+ in_addr_t ii_netmask; /* subnet or net mask */
+ u_char ii_eaddr[ETHER_ADDR_LEN]; /* ethernet address */
+ char ii_ifname[IF_NAMESIZE];
+};
+
+/*
+ * The list of all interfaces that are being listened to. rarp_loop()
+ * "selects" on the descriptors in this list.
+ */
+static struct if_info *iflist;
+
+static int verbose; /* verbose messages */
+static const char *tftp_dir = TFTP_DIR; /* tftp directory */
+
+static int dflag; /* messages to stdout/stderr, not syslog(3) */
+static int sflag; /* ignore /tftpboot */
+
+static u_char zero[6];
+
+static char pidfile_buf[PATH_MAX];
+static char *pidfile;
+#define RARPD_PIDFILE "/var/run/rarpd.%s.pid"
+static struct pidfh *pidfile_fh;
+
+static int bpf_open(void);
+static in_addr_t choose_ipaddr(in_addr_t **, in_addr_t, in_addr_t);
+static char *eatoa(u_char *);
+static int expand_syslog_m(const char *fmt, char **newfmt);
+static void init(char *);
+static void init_one(struct ifaddrs *, char *, int);
+static char *intoa(in_addr_t);
+static in_addr_t ipaddrtonetmask(in_addr_t);
+static void logmsg(int, const char *, ...) __printflike(2, 3);
+static int rarp_bootable(in_addr_t);
+static int rarp_check(u_char *, u_int);
+static void rarp_loop(void);
+static int rarp_open(char *);
+static void rarp_process(struct if_info *, u_char *, u_int);
+static void rarp_reply(struct if_info *, struct ether_header *,
+ in_addr_t, u_int);
+static void update_arptab(u_char *, in_addr_t);
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ int op;
+ char *ifname, *name;
+
+ int aflag = 0; /* listen on "all" interfaces */
+ int fflag = 0; /* don't fork */
+
+ if ((name = strrchr(argv[0], '/')) != NULL)
+ ++name;
+ else
+ name = argv[0];
+ if (*name == '-')
+ ++name;
+
+ /*
+ * All error reporting is done through syslog, unless -d is specified
+ */
+ openlog(name, LOG_PID | LOG_CONS, LOG_DAEMON);
+
+ opterr = 0;
+ while ((op = getopt(argc, argv, "adfsP:t:v")) != -1)
+ switch (op) {
+ case 'a':
+ ++aflag;
+ break;
+
+ case 'd':
+ ++dflag;
+ break;
+
+ case 'f':
+ ++fflag;
+ break;
+
+ case 's':
+ ++sflag;
+ break;
+
+ case 'P':
+ strncpy(pidfile_buf, optarg, sizeof(pidfile_buf) - 1);
+ pidfile_buf[sizeof(pidfile_buf) - 1] = '\0';
+ pidfile = pidfile_buf;
+ break;
+
+ case 't':
+ tftp_dir = optarg;
+ break;
+
+ case 'v':
+ ++verbose;
+ break;
+
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ argc -= optind;
+ argv += optind;
+
+ ifname = (aflag == 0) ? argv[0] : NULL;
+
+ if ((aflag && ifname) || (!aflag && ifname == NULL))
+ usage();
+
+ init(ifname);
+
+ if (!fflag) {
+ if (pidfile == NULL && ifname != NULL && aflag == 0) {
+ snprintf(pidfile_buf, sizeof(pidfile_buf) - 1,
+ RARPD_PIDFILE, ifname);
+ pidfile_buf[sizeof(pidfile_buf) - 1] = '\0';
+ pidfile = pidfile_buf;
+ }
+ /* If pidfile == NULL, /var/run/<progname>.pid will be used. */
+ pidfile_fh = pidfile_open(pidfile, 0600, NULL);
+ if (pidfile_fh == NULL)
+ logmsg(LOG_ERR, "Cannot open or create pidfile: %s",
+ (pidfile == NULL) ? "/var/run/rarpd.pid" : pidfile);
+ if (daemon(0,0)) {
+ logmsg(LOG_ERR, "cannot fork");
+ pidfile_remove(pidfile_fh);
+ exit(1);
+ }
+ pidfile_write(pidfile_fh);
+ }
+ rarp_loop();
+ return(0);
+}
+
+/*
+ * Add to the interface list.
+ */
+static void
+init_one(struct ifaddrs *ifa, char *target, int pass1)
+{
+ struct if_info *ii, *ii2;
+ struct sockaddr_dl *ll;
+ int family;
+
+ family = ifa->ifa_addr->sa_family;
+ switch (family) {
+ case AF_INET:
+ if (pass1)
+ /* Consider only AF_LINK during pass1. */
+ return;
+ /* FALLTHROUGH */
+ case AF_LINK:
+ if (!(ifa->ifa_flags & IFF_UP) ||
+ (ifa->ifa_flags & (IFF_LOOPBACK | IFF_POINTOPOINT)))
+ return;
+ break;
+ default:
+ return;
+ }
+
+ /* Don't bother going any further if not the target interface */
+ if (target != NULL && strcmp(ifa->ifa_name, target) != 0)
+ return;
+
+ /* Look for interface in list */
+ for (ii = iflist; ii != NULL; ii = ii->ii_next)
+ if (strcmp(ifa->ifa_name, ii->ii_ifname) == 0)
+ break;
+
+ if (pass1 && ii != NULL)
+ /* We've already seen that interface once. */
+ return;
+
+ /* Allocate a new one if not found */
+ if (ii == NULL) {
+ ii = (struct if_info *)malloc(sizeof(*ii));
+ if (ii == NULL) {
+ logmsg(LOG_ERR, "malloc: %m");
+ pidfile_remove(pidfile_fh);
+ exit(1);
+ }
+ bzero(ii, sizeof(*ii));
+ ii->ii_fd = -1;
+ strlcpy(ii->ii_ifname, ifa->ifa_name, sizeof(ii->ii_ifname));
+ ii->ii_next = iflist;
+ iflist = ii;
+ } else if (!pass1 && ii->ii_ipaddr != 0) {
+ /*
+ * Second AF_INET definition for that interface: clone
+ * the existing one, and work on that cloned one.
+ * This must be another IP address for this interface,
+ * so avoid killing the previous configuration.
+ */
+ ii2 = (struct if_info *)malloc(sizeof(*ii2));
+ if (ii2 == NULL) {
+ logmsg(LOG_ERR, "malloc: %m");
+ pidfile_remove(pidfile_fh);
+ exit(1);
+ }
+ memcpy(ii2, ii, sizeof(*ii2));
+ ii2->ii_fd = -1;
+ ii2->ii_next = iflist;
+ iflist = ii2;
+
+ ii = ii2;
+ }
+
+ switch (family) {
+ case AF_INET:
+ ii->ii_ipaddr = SATOSIN(ifa->ifa_addr)->sin_addr.s_addr;
+ ii->ii_netmask = SATOSIN(ifa->ifa_netmask)->sin_addr.s_addr;
+ if (ii->ii_netmask == 0)
+ ii->ii_netmask = ipaddrtonetmask(ii->ii_ipaddr);
+ if (ii->ii_fd < 0)
+ ii->ii_fd = rarp_open(ii->ii_ifname);
+ break;
+
+ case AF_LINK:
+ ll = (struct sockaddr_dl *)ifa->ifa_addr;
+ switch (ll->sdl_type) {
+ case IFT_ETHER:
+ case IFT_L2VLAN:
+ bcopy(LLADDR(ll), ii->ii_eaddr, 6);
+ }
+ break;
+ }
+}
+
+/*
+ * Initialize all "candidate" interfaces that are in the system
+ * configuration list. A "candidate" is up, not loopback and not
+ * point to point.
+ */
+static void
+init(char *target)
+{
+ struct if_info *ii, *nii, *lii;
+ struct ifaddrs *ifhead, *ifa;
+ int error;
+
+ error = getifaddrs(&ifhead);
+ if (error) {
+ logmsg(LOG_ERR, "getifaddrs: %m");
+ pidfile_remove(pidfile_fh);
+ exit(1);
+ }
+ /*
+ * We make two passes over the list we have got. In the first
+ * one, we only collect AF_LINK interfaces, and initialize our
+ * list of interfaces from them. In the second pass, we
+ * collect the actual IP addresses from the AF_INET
+ * interfaces, and allow for the same interface name to appear
+ * multiple times (in case of more than one IP address).
+ */
+ for (ifa = ifhead; ifa != NULL; ifa = ifa->ifa_next)
+ init_one(ifa, target, 1);
+ for (ifa = ifhead; ifa != NULL; ifa = ifa->ifa_next)
+ init_one(ifa, target, 0);
+ freeifaddrs(ifhead);
+
+ /* Throw away incomplete interfaces */
+ lii = NULL;
+ for (ii = iflist; ii != NULL; ii = nii) {
+ nii = ii->ii_next;
+ if (ii->ii_ipaddr == 0 ||
+ bcmp(ii->ii_eaddr, zero, 6) == 0) {
+ if (lii == NULL)
+ iflist = nii;
+ else
+ lii->ii_next = nii;
+ if (ii->ii_fd >= 0)
+ close(ii->ii_fd);
+ free(ii);
+ continue;
+ }
+ lii = ii;
+ }
+
+ /* Verbose stuff */
+ if (verbose)
+ for (ii = iflist; ii != NULL; ii = ii->ii_next)
+ logmsg(LOG_DEBUG, "%s %s 0x%08x %s",
+ ii->ii_ifname, intoa(ntohl(ii->ii_ipaddr)),
+ (in_addr_t)ntohl(ii->ii_netmask), eatoa(ii->ii_eaddr));
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "%s\n%s\n",
+ "usage: rarpd -a [-dfsv] [-t directory] [-P pidfile]",
+ " rarpd [-dfsv] [-t directory] [-P pidfile] interface");
+ exit(1);
+}
+
+static int
+bpf_open(void)
+{
+ int fd;
+ int n = 0;
+ char device[sizeof "/dev/bpf000"];
+
+ /*
+ * Go through all the minors and find one that isn't in use.
+ */
+ do {
+ (void)sprintf(device, "/dev/bpf%d", n++);
+ fd = open(device, O_RDWR);
+ } while ((fd == -1) && (errno == EBUSY));
+
+ if (fd == -1) {
+ logmsg(LOG_ERR, "%s: %m", device);
+ pidfile_remove(pidfile_fh);
+ exit(1);
+ }
+ return fd;
+}
+
+/*
+ * Open a BPF file and attach it to the interface named 'device'.
+ * Set immediate mode, and set a filter that accepts only RARP requests.
+ */
+static int
+rarp_open(char *device)
+{
+ int fd;
+ struct ifreq ifr;
+ u_int dlt;
+ int immediate;
+
+ static struct bpf_insn insns[] = {
+ BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 12),
+ BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, ETHERTYPE_REVARP, 0, 3),
+ BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 20),
+ BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, REVARP_REQUEST, 0, 1),
+ BPF_STMT(BPF_RET|BPF_K, sizeof(struct ether_arp) +
+ sizeof(struct ether_header)),
+ BPF_STMT(BPF_RET|BPF_K, 0),
+ };
+ static struct bpf_program filter = {
+ sizeof insns / sizeof(insns[0]),
+ insns
+ };
+
+ fd = bpf_open();
+ /*
+ * Set immediate mode so packets are processed as they arrive.
+ */
+ immediate = 1;
+ if (ioctl(fd, BIOCIMMEDIATE, &immediate) == -1) {
+ logmsg(LOG_ERR, "BIOCIMMEDIATE: %m");
+ goto rarp_open_err;
+ }
+ strlcpy(ifr.ifr_name, device, sizeof(ifr.ifr_name));
+ if (ioctl(fd, BIOCSETIF, (caddr_t)&ifr) == -1) {
+ logmsg(LOG_ERR, "BIOCSETIF: %m");
+ goto rarp_open_err;
+ }
+ /*
+ * Check that the data link layer is an Ethernet; this code won't
+ * work with anything else.
+ */
+ if (ioctl(fd, BIOCGDLT, (caddr_t)&dlt) == -1) {
+ logmsg(LOG_ERR, "BIOCGDLT: %m");
+ goto rarp_open_err;
+ }
+ if (dlt != DLT_EN10MB) {
+ logmsg(LOG_ERR, "%s is not an ethernet", device);
+ goto rarp_open_err;
+ }
+ /*
+ * Set filter program.
+ */
+ if (ioctl(fd, BIOCSETF, (caddr_t)&filter) == -1) {
+ logmsg(LOG_ERR, "BIOCSETF: %m");
+ goto rarp_open_err;
+ }
+ return fd;
+
+rarp_open_err:
+ pidfile_remove(pidfile_fh);
+ exit(1);
+}
+
+/*
+ * Perform various sanity checks on the RARP request packet. Return
+ * false on failure and log the reason.
+ */
+static int
+rarp_check(u_char *p, u_int len)
+{
+ struct ether_header *ep = (struct ether_header *)p;
+ struct ether_arp *ap = (struct ether_arp *)(p + sizeof(*ep));
+
+ if (len < sizeof(*ep) + sizeof(*ap)) {
+ logmsg(LOG_ERR, "truncated request, got %u, expected %lu",
+ len, (u_long)(sizeof(*ep) + sizeof(*ap)));
+ return 0;
+ }
+ /*
+ * XXX This test might be better off broken out...
+ */
+ if (ntohs(ep->ether_type) != ETHERTYPE_REVARP ||
+ ntohs(ap->arp_hrd) != ARPHRD_ETHER ||
+ ntohs(ap->arp_op) != REVARP_REQUEST ||
+ ntohs(ap->arp_pro) != ETHERTYPE_IP ||
+ ap->arp_hln != 6 || ap->arp_pln != 4) {
+ logmsg(LOG_DEBUG, "request fails sanity check");
+ return 0;
+ }
+ if (bcmp((char *)&ep->ether_shost, (char *)&ap->arp_sha, 6) != 0) {
+ logmsg(LOG_DEBUG, "ether/arp sender address mismatch");
+ return 0;
+ }
+ if (bcmp((char *)&ap->arp_sha, (char *)&ap->arp_tha, 6) != 0) {
+ logmsg(LOG_DEBUG, "ether/arp target address mismatch");
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * Loop indefinitely listening for RARP requests on the
+ * interfaces in 'iflist'.
+ */
+static void
+rarp_loop(void)
+{
+ u_char *buf, *bp, *ep;
+ int cc, fd;
+ fd_set fds, listeners;
+ int bufsize, maxfd = 0;
+ struct if_info *ii;
+
+ if (iflist == NULL) {
+ logmsg(LOG_ERR, "no interfaces");
+ goto rarpd_loop_err;
+ }
+ if (ioctl(iflist->ii_fd, BIOCGBLEN, (caddr_t)&bufsize) == -1) {
+ logmsg(LOG_ERR, "BIOCGBLEN: %m");
+ goto rarpd_loop_err;
+ }
+ buf = malloc(bufsize);
+ if (buf == NULL) {
+ logmsg(LOG_ERR, "malloc: %m");
+ goto rarpd_loop_err;
+ }
+
+ while (1) {
+ /*
+ * Find the highest numbered file descriptor for select().
+ * Initialize the set of descriptors to listen to.
+ */
+ FD_ZERO(&fds);
+ for (ii = iflist; ii != NULL; ii = ii->ii_next) {
+ FD_SET(ii->ii_fd, &fds);
+ if (ii->ii_fd > maxfd)
+ maxfd = ii->ii_fd;
+ }
+ listeners = fds;
+ if (select(maxfd + 1, &listeners, NULL, NULL, NULL) == -1) {
+ /* Don't choke when we get ptraced */
+ if (errno == EINTR)
+ continue;
+ logmsg(LOG_ERR, "select: %m");
+ goto rarpd_loop_err;
+ }
+ for (ii = iflist; ii != NULL; ii = ii->ii_next) {
+ fd = ii->ii_fd;
+ if (!FD_ISSET(fd, &listeners))
+ continue;
+ again:
+ cc = read(fd, (char *)buf, bufsize);
+ /* Don't choke when we get ptraced */
+ if ((cc == -1) && (errno == EINTR))
+ goto again;
+
+ /* Loop through the packet(s) */
+#define bhp ((struct bpf_hdr *)bp)
+ bp = buf;
+ ep = bp + cc;
+ while (bp < ep) {
+ u_int caplen, hdrlen;
+
+ caplen = bhp->bh_caplen;
+ hdrlen = bhp->bh_hdrlen;
+ if (rarp_check(bp + hdrlen, caplen))
+ rarp_process(ii, bp + hdrlen, caplen);
+ bp += BPF_WORDALIGN(hdrlen + caplen);
+ }
+ }
+ }
+#undef bhp
+ return;
+
+rarpd_loop_err:
+ pidfile_remove(pidfile_fh);
+ exit(1);
+}
+
+/*
+ * True if this server can boot the host whose IP address is 'addr'.
+ * This check is made by looking in the tftp directory for the
+ * configuration file.
+ */
+static int
+rarp_bootable(in_addr_t addr)
+{
+ struct dirent *dent;
+ DIR *d;
+ char ipname[9];
+ static DIR *dd = NULL;
+
+ (void)sprintf(ipname, "%08X", (in_addr_t)ntohl(addr));
+
+ /*
+ * If directory is already open, rewind it. Otherwise, open it.
+ */
+ if ((d = dd) != NULL)
+ rewinddir(d);
+ else {
+ if (chdir(tftp_dir) == -1) {
+ logmsg(LOG_ERR, "chdir: %s: %m", tftp_dir);
+ goto rarp_bootable_err;
+ }
+ d = opendir(".");
+ if (d == NULL) {
+ logmsg(LOG_ERR, "opendir: %m");
+ goto rarp_bootable_err;
+ }
+ dd = d;
+ }
+ while ((dent = readdir(d)) != NULL)
+ if (strncmp(dent->d_name, ipname, 8) == 0)
+ return 1;
+ return 0;
+
+rarp_bootable_err:
+ pidfile_remove(pidfile_fh);
+ exit(1);
+}
+
+/*
+ * Given a list of IP addresses, 'alist', return the first address that
+ * is on network 'net'; 'netmask' is a mask indicating the network portion
+ * of the address.
+ */
+static in_addr_t
+choose_ipaddr(in_addr_t **alist, in_addr_t net, in_addr_t netmask)
+{
+
+ for (; *alist; ++alist)
+ if ((**alist & netmask) == net)
+ return **alist;
+ return 0;
+}
+
+/*
+ * Answer the RARP request in 'pkt', on the interface 'ii'. 'pkt' has
+ * already been checked for validity. The reply is overlaid on the request.
+ */
+static void
+rarp_process(struct if_info *ii, u_char *pkt, u_int len)
+{
+ struct ether_header *ep;
+ struct hostent *hp;
+ in_addr_t target_ipaddr;
+ char ename[256];
+
+ ep = (struct ether_header *)pkt;
+ /* should this be arp_tha? */
+ if (ether_ntohost(ename, (struct ether_addr *)&ep->ether_shost) != 0) {
+ logmsg(LOG_ERR, "cannot map %s to name",
+ eatoa(ep->ether_shost));
+ return;
+ }
+
+ if ((hp = gethostbyname(ename)) == NULL) {
+ logmsg(LOG_ERR, "cannot map %s to IP address", ename);
+ return;
+ }
+
+ /*
+ * Choose correct address from list.
+ */
+ if (hp->h_addrtype != AF_INET) {
+ logmsg(LOG_ERR, "cannot handle non IP addresses for %s",
+ ename);
+ return;
+ }
+ target_ipaddr = choose_ipaddr((in_addr_t **)hp->h_addr_list,
+ ii->ii_ipaddr & ii->ii_netmask,
+ ii->ii_netmask);
+ if (target_ipaddr == 0) {
+ logmsg(LOG_ERR, "cannot find %s on net %s",
+ ename, intoa(ntohl(ii->ii_ipaddr & ii->ii_netmask)));
+ return;
+ }
+ if (sflag || rarp_bootable(target_ipaddr))
+ rarp_reply(ii, ep, target_ipaddr, len);
+ else if (verbose > 1)
+ logmsg(LOG_INFO, "%s %s at %s DENIED (not bootable)",
+ ii->ii_ifname,
+ eatoa(ep->ether_shost),
+ intoa(ntohl(target_ipaddr)));
+}
+
+/*
+ * Poke the kernel arp tables with the ethernet/ip address combinataion
+ * given. When processing a reply, we must do this so that the booting
+ * host (i.e. the guy running rarpd), won't try to ARP for the hardware
+ * address of the guy being booted (he cannot answer the ARP).
+ */
+static struct sockaddr_in sin_inarp = {
+ sizeof(struct sockaddr_in), AF_INET, 0,
+ {0},
+ {0},
+};
+
+static struct sockaddr_dl sin_dl = {
+ sizeof(struct sockaddr_dl), AF_LINK, 0, IFT_ETHER, 0, 6,
+ 0, ""
+};
+
+static struct {
+ struct rt_msghdr rthdr;
+ char rtspace[512];
+} rtmsg;
+
+static void
+update_arptab(u_char *ep, in_addr_t ipaddr)
+{
+ struct timespec tp;
+ int cc;
+ struct sockaddr_in *ar, *ar2;
+ struct sockaddr_dl *ll, *ll2;
+ struct rt_msghdr *rt;
+ int xtype, xindex;
+ static pid_t pid;
+ int r;
+ static int seq;
+
+ r = socket(PF_ROUTE, SOCK_RAW, 0);
+ if (r == -1) {
+ logmsg(LOG_ERR, "raw route socket: %m");
+ pidfile_remove(pidfile_fh);
+ exit(1);
+ }
+ pid = getpid();
+
+ ar = &sin_inarp;
+ ar->sin_addr.s_addr = ipaddr;
+ ll = &sin_dl;
+ bcopy(ep, LLADDR(ll), 6);
+
+ /* Get the type and interface index */
+ rt = &rtmsg.rthdr;
+ bzero(rt, sizeof(rtmsg));
+ rt->rtm_version = RTM_VERSION;
+ rt->rtm_addrs = RTA_DST;
+ rt->rtm_type = RTM_GET;
+ rt->rtm_seq = ++seq;
+ ar2 = (struct sockaddr_in *)rtmsg.rtspace;
+ bcopy(ar, ar2, sizeof(*ar));
+ rt->rtm_msglen = sizeof(*rt) + sizeof(*ar);
+ errno = 0;
+ if ((write(r, rt, rt->rtm_msglen) == -1) && (errno != ESRCH)) {
+ logmsg(LOG_ERR, "rtmsg get write: %m");
+ close(r);
+ return;
+ }
+ do {
+ cc = read(r, rt, sizeof(rtmsg));
+ } while (cc > 0 && (rt->rtm_seq != seq || rt->rtm_pid != pid));
+ if (cc == -1) {
+ logmsg(LOG_ERR, "rtmsg get read: %m");
+ close(r);
+ return;
+ }
+ ll2 = (struct sockaddr_dl *)((u_char *)ar2 + ar2->sin_len);
+ if (ll2->sdl_family != AF_LINK) {
+ /*
+ * XXX I think this means the ip address is not on a
+ * directly connected network (the family is AF_INET in
+ * this case).
+ */
+ logmsg(LOG_ERR, "bogus link family (%d) wrong net for %08X?\n",
+ ll2->sdl_family, ipaddr);
+ close(r);
+ return;
+ }
+ xtype = ll2->sdl_type;
+ xindex = ll2->sdl_index;
+
+ /* Set the new arp entry */
+ bzero(rt, sizeof(rtmsg));
+ rt->rtm_version = RTM_VERSION;
+ rt->rtm_addrs = RTA_DST | RTA_GATEWAY;
+ rt->rtm_inits = RTV_EXPIRE;
+ clock_gettime(CLOCK_MONOTONIC, &tp);
+ rt->rtm_rmx.rmx_expire = tp.tv_sec + ARPSECS;
+ rt->rtm_flags = RTF_HOST | RTF_STATIC;
+ rt->rtm_type = RTM_ADD;
+ rt->rtm_seq = ++seq;
+
+ bcopy(ar, ar2, sizeof(*ar));
+
+ ll2 = (struct sockaddr_dl *)((u_char *)ar2 + sizeof(*ar2));
+ bcopy(ll, ll2, sizeof(*ll));
+ ll2->sdl_type = xtype;
+ ll2->sdl_index = xindex;
+
+ rt->rtm_msglen = sizeof(*rt) + sizeof(*ar2) + sizeof(*ll2);
+ errno = 0;
+ if ((write(r, rt, rt->rtm_msglen) == -1) && (errno != EEXIST)) {
+ logmsg(LOG_ERR, "rtmsg add write: %m");
+ close(r);
+ return;
+ }
+ do {
+ cc = read(r, rt, sizeof(rtmsg));
+ } while (cc > 0 && (rt->rtm_seq != seq || rt->rtm_pid != pid));
+ close(r);
+ if (cc == -1) {
+ logmsg(LOG_ERR, "rtmsg add read: %m");
+ return;
+ }
+}
+
+/*
+ * Build a reverse ARP packet and sent it out on the interface.
+ * 'ep' points to a valid REVARP_REQUEST. The REVARP_REPLY is built
+ * on top of the request, then written to the network.
+ *
+ * RFC 903 defines the ether_arp fields as follows. The following comments
+ * are taken (more or less) straight from this document.
+ *
+ * REVARP_REQUEST
+ *
+ * arp_sha is the hardware address of the sender of the packet.
+ * arp_spa is undefined.
+ * arp_tha is the 'target' hardware address.
+ * In the case where the sender wishes to determine his own
+ * protocol address, this, like arp_sha, will be the hardware
+ * address of the sender.
+ * arp_tpa is undefined.
+ *
+ * REVARP_REPLY
+ *
+ * arp_sha is the hardware address of the responder (the sender of the
+ * reply packet).
+ * arp_spa is the protocol address of the responder (see the note below).
+ * arp_tha is the hardware address of the target, and should be the same as
+ * that which was given in the request.
+ * arp_tpa is the protocol address of the target, that is, the desired address.
+ *
+ * Note that the requirement that arp_spa be filled in with the responder's
+ * protocol is purely for convenience. For instance, if a system were to use
+ * both ARP and RARP, then the inclusion of the valid protocol-hardware
+ * address pair (arp_spa, arp_sha) may eliminate the need for a subsequent
+ * ARP request.
+ */
+static void
+rarp_reply(struct if_info *ii, struct ether_header *ep, in_addr_t ipaddr,
+ u_int len)
+{
+ u_int n;
+ struct ether_arp *ap = (struct ether_arp *)(ep + 1);
+
+ update_arptab((u_char *)&ap->arp_sha, ipaddr);
+
+ /*
+ * Build the rarp reply by modifying the rarp request in place.
+ */
+ ap->arp_op = htons(REVARP_REPLY);
+
+#ifdef BROKEN_BPF
+ ep->ether_type = ETHERTYPE_REVARP;
+#endif
+ bcopy((char *)&ap->arp_sha, (char *)&ep->ether_dhost, 6);
+ bcopy((char *)ii->ii_eaddr, (char *)&ep->ether_shost, 6);
+ bcopy((char *)ii->ii_eaddr, (char *)&ap->arp_sha, 6);
+
+ bcopy((char *)&ipaddr, (char *)ap->arp_tpa, 4);
+ /* Target hardware is unchanged. */
+ bcopy((char *)&ii->ii_ipaddr, (char *)ap->arp_spa, 4);
+
+ /* Zero possible garbage after packet. */
+ bzero((char *)ep + (sizeof(*ep) + sizeof(*ap)),
+ len - (sizeof(*ep) + sizeof(*ap)));
+ n = write(ii->ii_fd, (char *)ep, len);
+ if (n != len)
+ logmsg(LOG_ERR, "write: only %d of %d bytes written", n, len);
+ if (verbose)
+ logmsg(LOG_INFO, "%s %s at %s REPLIED", ii->ii_ifname,
+ eatoa(ap->arp_tha),
+ intoa(ntohl(ipaddr)));
+}
+
+/*
+ * Get the netmask of an IP address. This routine is used if
+ * SIOCGIFNETMASK doesn't work.
+ */
+static in_addr_t
+ipaddrtonetmask(in_addr_t addr)
+{
+
+ addr = ntohl(addr);
+ if (IN_CLASSA(addr))
+ return htonl(IN_CLASSA_NET);
+ if (IN_CLASSB(addr))
+ return htonl(IN_CLASSB_NET);
+ if (IN_CLASSC(addr))
+ return htonl(IN_CLASSC_NET);
+ logmsg(LOG_DEBUG, "unknown IP address class: %08X", addr);
+ return htonl(0xffffffff);
+}
+
+/*
+ * A faster replacement for inet_ntoa().
+ */
+static char *
+intoa(in_addr_t addr)
+{
+ char *cp;
+ u_int byte;
+ int n;
+ static char buf[sizeof(".xxx.xxx.xxx.xxx")];
+
+ cp = &buf[sizeof buf];
+ *--cp = '\0';
+
+ n = 4;
+ do {
+ byte = addr & 0xff;
+ *--cp = byte % 10 + '0';
+ byte /= 10;
+ if (byte > 0) {
+ *--cp = byte % 10 + '0';
+ byte /= 10;
+ if (byte > 0)
+ *--cp = byte + '0';
+ }
+ *--cp = '.';
+ addr >>= 8;
+ } while (--n > 0);
+
+ return cp + 1;
+}
+
+static char *
+eatoa(u_char *ea)
+{
+ static char buf[sizeof("xx:xx:xx:xx:xx:xx")];
+
+ (void)sprintf(buf, "%x:%x:%x:%x:%x:%x",
+ ea[0], ea[1], ea[2], ea[3], ea[4], ea[5]);
+ return (buf);
+}
+
+static void
+logmsg(int pri, const char *fmt, ...)
+{
+ va_list v;
+ FILE *fp;
+ char *newfmt;
+
+ va_start(v, fmt);
+ if (dflag) {
+ if (pri == LOG_ERR)
+ fp = stderr;
+ else
+ fp = stdout;
+ if (expand_syslog_m(fmt, &newfmt) == -1) {
+ vfprintf(fp, fmt, v);
+ } else {
+ vfprintf(fp, newfmt, v);
+ free(newfmt);
+ }
+ fputs("\n", fp);
+ fflush(fp);
+ } else {
+ vsyslog(pri, fmt, v);
+ }
+ va_end(v);
+}
+
+static int
+expand_syslog_m(const char *fmt, char **newfmt) {
+ const char *str, *m;
+ char *p, *np;
+
+ p = strdup("");
+ str = fmt;
+ while ((m = strstr(str, "%m")) != NULL) {
+ asprintf(&np, "%s%.*s%s", p, (int)(m - str),
+ str, strerror(errno));
+ free(p);
+ if (np == NULL) {
+ errno = ENOMEM;
+ return (-1);
+ }
+ p = np;
+ str = m + 2;
+ }
+
+ if (*str != '\0') {
+ asprintf(&np, "%s%s", p, str);
+ free(p);
+ if (np == NULL) {
+ errno = ENOMEM;
+ return (-1);
+ }
+ p = np;
+ }
+
+ *newfmt = p;
+ return (0);
+}
diff --git a/usr.sbin/repquota/Makefile b/usr.sbin/repquota/Makefile
new file mode 100644
index 0000000..78fd398
--- /dev/null
+++ b/usr.sbin/repquota/Makefile
@@ -0,0 +1,8 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+PROG= repquota
+MAN= repquota.8
+LIBADD= util
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/repquota/Makefile.depend b/usr.sbin/repquota/Makefile.depend
new file mode 100644
index 0000000..58f9a33
--- /dev/null
+++ b/usr.sbin/repquota/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/repquota/repquota.8 b/usr.sbin/repquota/repquota.8
new file mode 100644
index 0000000..8b5ab68
--- /dev/null
+++ b/usr.sbin/repquota/repquota.8
@@ -0,0 +1,111 @@
+.\" 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.
+.\"
+.\" @(#)repquota.8 8.1 (Berkeley) 6/6/93
+.\" $FreeBSD$
+.\"
+.Dd June 6, 1993
+.Dt REPQUOTA 8
+.Os
+.Sh NAME
+.Nm repquota
+.Nd summarize quotas for a file system
+.Sh SYNOPSIS
+.Nm
+.Op Fl h
+.Op Fl g
+.Op Fl n
+.Op Fl u
+.Op Fl v
+.Ar filesystem Ar ...
+.Nm
+.Op Fl h
+.Op Fl g
+.Op Fl n
+.Op Fl u
+.Op Fl v
+.Fl a
+.Sh DESCRIPTION
+The
+.Nm
+utility prints a summary of the disk usage and quotas for the
+specified file systems.
+.Pp
+Available options:
+.Bl -tag -width indent
+.It Fl a
+Print the quotas of all the file systems listed in
+.Pa /etc/fstab .
+.It Fl g
+Print only group quotas (the default is to print both
+group and user quotas if they exist).
+.It Fl h
+Display information in a more human readable format
+rather than in historic kilobyte format.
+.It Fl n
+Display user and group IDs numerically rather than converting to
+a user or group name.
+.It Fl u
+Print only user quotas (the default is to print both
+group and user quotas if they exist).
+.It Fl v
+Print a header line before printing each file system quotas.
+.El
+.Pp
+For each user or group, the current
+number files and amount of space is
+printed, along with any quotas created with
+.Xr edquota 8 .
+.Pp
+Only members of the operator group or the super-user may
+use this command.
+.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
+for file system names and locations
+.El
+.Sh DIAGNOSTICS
+Various messages about inaccessible files; self-explanatory.
+.Sh SEE ALSO
+.Xr quota 1 ,
+.Xr quotactl 2 ,
+.Xr fstab 5 ,
+.Xr edquota 8 ,
+.Xr quotacheck 8 ,
+.Xr quotaon 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.2 .
diff --git a/usr.sbin/repquota/repquota.c b/usr.sbin/repquota/repquota.c
new file mode 100644
index 0000000..4ffc2e6
--- /dev/null
+++ b/usr.sbin/repquota/repquota.c
@@ -0,0 +1,371 @@
+/*
+ * 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[] = "@(#)repquota.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Quota report
+ */
+#include <sys/param.h>
+#include <sys/mount.h>
+
+#include <ufs/ufs/quota.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 <time.h>
+#include <unistd.h>
+
+/* Let's be paranoid about block size */
+#if 10 > DEV_BSHIFT
+#define dbtokb(db) \
+ ((off_t)(db) >> (10-DEV_BSHIFT))
+#elif 10 < DEV_BSHIFT
+#define dbtokb(db) \
+ ((off_t)(db) << (DEV_BSHIFT-10))
+#else
+#define dbtokb(db) (db)
+#endif
+
+#define max(a,b) ((a) >= (b) ? (a) : (b))
+
+static const char *qfextension[] = INITQFNAMES;
+
+struct fileusage {
+ struct fileusage *fu_next;
+ u_long fu_id;
+ char fu_name[1];
+ /* actually bigger */
+};
+#define FUHASH 1024 /* must be power of two */
+static struct fileusage *fuhead[MAXQUOTAS][FUHASH];
+static struct fileusage *lookup(u_long, int);
+static struct fileusage *addid(u_long, int, char *);
+static u_long highid[MAXQUOTAS]; /* highest addid()'ed identifier per type */
+
+static int vflag; /* verbose */
+static int aflag; /* all filesystems */
+static int nflag; /* display user/group by id */
+static int hflag; /* display in human readable format */
+
+int oneof(char *, char *[], int);
+int repquota(struct fstab *, int);
+char *timeprt(time_t);
+static void prthumanval(int64_t bytes);
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ struct fstab *fs;
+ struct passwd *pw;
+ struct group *gr;
+ int ch, gflag = 0, uflag = 0, errs = 0;
+ long i, argnum, done = 0;
+
+ while ((ch = getopt(argc, argv, "aghnuv")) != -1) {
+ switch(ch) {
+ case 'a':
+ aflag++;
+ break;
+ case 'g':
+ gflag++;
+ break;
+ case 'h':
+ hflag++;
+ break;
+ case 'n':
+ nflag++;
+ break;
+ case 'u':
+ uflag++;
+ break;
+ case 'v':
+ vflag++;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc == 0 && !aflag)
+ usage();
+ if (!gflag && !uflag) {
+ if (aflag)
+ gflag++;
+ uflag++;
+ }
+ if (gflag && !nflag) {
+ setgrent();
+ while ((gr = getgrent()) != 0)
+ (void) addid((u_long)gr->gr_gid, GRPQUOTA, gr->gr_name);
+ endgrent();
+ }
+ if (uflag && !nflag) {
+ setpwent();
+ while ((pw = getpwent()) != 0)
+ (void) addid((u_long)pw->pw_uid, USRQUOTA, pw->pw_name);
+ endpwent();
+ }
+ setfsent();
+ while ((fs = getfsent()) != NULL) {
+ if (strcmp(fs->fs_vfstype, "ufs"))
+ continue;
+ if (aflag) {
+ if (gflag)
+ errs += repquota(fs, GRPQUOTA);
+ if (uflag)
+ errs += repquota(fs, USRQUOTA);
+ continue;
+ }
+ if ((argnum = oneof(fs->fs_file, argv, argc)) >= 0 ||
+ (argnum = oneof(fs->fs_spec, argv, argc)) >= 0) {
+ done |= 1 << argnum;
+ if (gflag)
+ errs += repquota(fs, GRPQUOTA);
+ if (uflag)
+ errs += repquota(fs, USRQUOTA);
+ }
+ }
+ endfsent();
+ for (i = 0; i < argc; i++)
+ if ((done & (1 << i)) == 0)
+ warnx("%s not found in fstab", argv[i]);
+ exit(errs);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "%s\n%s\n",
+ "usage: repquota [-h] [-v] [-g] [-n] [-u] -a",
+ " repquota [-h] [-v] [-g] [-n] [-u] filesystem ...");
+ exit(1);
+}
+
+int
+repquota(struct fstab *fs, int type)
+{
+ struct fileusage *fup;
+ struct quotafile *qf;
+ u_long id, maxid;
+ struct dqblk dqbuf;
+ static int multiple = 0;
+
+ if ((qf = quota_open(fs, type, O_RDONLY)) == NULL) {
+ if (vflag && !aflag) {
+ if (multiple++)
+ printf("\n");
+ fprintf(stdout, "*** No %s quotas on %s (%s)\n",
+ qfextension[type], fs->fs_file, fs->fs_spec);
+ return(1);
+ }
+ return(0);
+ }
+ if (multiple++)
+ printf("\n");
+ if (vflag)
+ fprintf(stdout, "*** Report for %s quotas on %s (%s)\n",
+ qfextension[type], fs->fs_file, fs->fs_spec);
+ printf("%*s Block limits File limits\n",
+ max(MAXLOGNAME - 1, 10), " ");
+ printf("User%*s used soft hard grace used soft hard grace\n",
+ max(MAXLOGNAME - 1, 10), " ");
+ maxid = quota_maxid(qf);
+ for (id = 0; id <= maxid; id++) {
+ if (quota_read(qf, &dqbuf, id) != 0)
+ break;
+ if (dqbuf.dqb_curinodes == 0 && dqbuf.dqb_curblocks == 0)
+ continue;
+ if ((fup = lookup(id, type)) == 0)
+ fup = addid(id, type, (char *)0);
+ printf("%-*s ", max(MAXLOGNAME - 1, 10), fup->fu_name);
+ printf("%c%c",
+ dqbuf.dqb_bsoftlimit &&
+ dqbuf.dqb_curblocks >=
+ dqbuf.dqb_bsoftlimit ? '+' : '-',
+ dqbuf.dqb_isoftlimit &&
+ dqbuf.dqb_curinodes >=
+ dqbuf.dqb_isoftlimit ? '+' : '-');
+ prthumanval(dqbuf.dqb_curblocks);
+ prthumanval(dqbuf.dqb_bsoftlimit);
+ prthumanval(dqbuf.dqb_bhardlimit);
+ printf(" %6s",
+ dqbuf.dqb_bsoftlimit &&
+ dqbuf.dqb_curblocks >=
+ dqbuf.dqb_bsoftlimit ?
+ timeprt(dqbuf.dqb_btime) : "-");
+ printf(" %7ju %7ju %7ju %6s\n",
+ (uintmax_t)dqbuf.dqb_curinodes,
+ (uintmax_t)dqbuf.dqb_isoftlimit,
+ (uintmax_t)dqbuf.dqb_ihardlimit,
+ dqbuf.dqb_isoftlimit &&
+ dqbuf.dqb_curinodes >=
+ dqbuf.dqb_isoftlimit ?
+ timeprt(dqbuf.dqb_itime) : "-");
+ }
+ quota_close(qf);
+ return (0);
+}
+
+static void
+prthumanval(int64_t blocks)
+{
+ char buf[7];
+ int flags;
+
+ if (!hflag) {
+ printf(" %6ju", (uintmax_t)dbtokb(blocks));
+ return;
+ }
+ flags = HN_NOSPACE | HN_DECIMAL;
+ if (blocks != 0)
+ flags |= HN_B;
+ humanize_number(buf, sizeof(buf) - (blocks < 0 ? 0 : 1),
+ dbtob(blocks), "", HN_AUTOSCALE, flags);
+ (void)printf("%7s", buf);
+}
+
+/*
+ * 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);
+}
+
+/*
+ * 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 ((struct fileusage *)0);
+}
+
+/*
+ * Add a new file usage id if it does not already exist.
+ */
+struct fileusage *
+addid(u_long id, int type, char *name)
+{
+ struct fileusage *fup, **fhp;
+ int len;
+
+ if ((fup = lookup(id, type)))
+ return (fup);
+ if (name)
+ len = strlen(name);
+ else
+ len = 10;
+ if ((fup = (struct fileusage *)calloc(1, sizeof(*fup) + len)) == NULL)
+ errx(1, "out of memory for fileusage structures");
+ fhp = &fuhead[type][id & (FUHASH - 1)];
+ fup->fu_next = *fhp;
+ *fhp = fup;
+ fup->fu_id = id;
+ if (id > highid[type])
+ highid[type] = id;
+ if (name) {
+ bcopy(name, fup->fu_name, len + 1);
+ } else {
+ sprintf(fup->fu_name, "%lu", id);
+ }
+ return (fup);
+}
+
+/*
+ * Calculate the grace period and return a printable string for it.
+ */
+char *
+timeprt(time_t seconds)
+{
+ time_t hours, minutes;
+ static char buf[20];
+ static time_t now;
+
+ if (now == 0)
+ time(&now);
+ if (now > seconds) {
+ strlcpy(buf, "none", sizeof (buf));
+ return (buf);
+ }
+ seconds -= now;
+ minutes = (seconds + 30) / 60;
+ hours = (minutes + 30) / 60;
+ if (hours >= 36) {
+ sprintf(buf, "%lddays", (long)(hours + 12) / 24);
+ return (buf);
+ }
+ if (minutes >= 60) {
+ sprintf(buf, "%2ld:%ld", (long)minutes / 60,
+ (long)minutes % 60);
+ return (buf);
+ }
+ sprintf(buf, "%2ld", (long)minutes);
+ return (buf);
+}
diff --git a/usr.sbin/rip6query/Makefile b/usr.sbin/rip6query/Makefile
new file mode 100644
index 0000000..fac9028
--- /dev/null
+++ b/usr.sbin/rip6query/Makefile
@@ -0,0 +1,9 @@
+# @(#)Makefile 8.1 (Berkeley) 6/5/93
+# $FreeBSD$
+
+PROG= rip6query
+MAN= rip6query.8
+
+CFLAGS+= -I${.CURDIR}/../route6d
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/rip6query/Makefile.depend b/usr.sbin/rip6query/Makefile.depend
new file mode 100644
index 0000000..54c1f6f
--- /dev/null
+++ b/usr.sbin/rip6query/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/rip6query/rip6query.8 b/usr.sbin/rip6query/rip6query.8
new file mode 100644
index 0000000..9bcd4a5
--- /dev/null
+++ b/usr.sbin/rip6query/rip6query.8
@@ -0,0 +1,64 @@
+.\" $KAME: rip6query.8,v 1.4 2001/03/15 12:35:24 itojun Exp $
+.\"
+.\" Copyright (C) 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.
+.\"
+.\" $Id: rip6query.8,v 1.2 2000/01/19 06:24:55 itojun Exp $
+.\" $FreeBSD$
+.\"
+.Dd October 7, 1999
+.Dt RIP6QUERY 8
+.Os
+.Sh NAME
+.Nm rip6query
+.Nd RIPng debugging tool
+.\"
+.Sh SYNOPSIS
+.Nm
+.Op Fl I Ar interface
+.Ar destination
+.\"
+.Sh DESCRIPTION
+The
+.Nm
+utility requests remote RIPng daemon on
+.Ar destination
+to dump RIPng routing information.
+.Fl I
+lets you specify outgoing
+.Ar interface
+for the query packet,
+and is useful when link-local address is specified for
+.Ar destination .
+.\"
+.Sh SEE ALSO
+.Xr route6d 8
+.\"
+.Sh HISTORY
+The
+.Nm
+utility first appeared in WIDE Hydrangea IPv6 protocol stack kit.
diff --git a/usr.sbin/rip6query/rip6query.c b/usr.sbin/rip6query/rip6query.c
new file mode 100644
index 0000000..a35c750
--- /dev/null
+++ b/usr.sbin/rip6query/rip6query.c
@@ -0,0 +1,197 @@
+/* $KAME: rip6query.c,v 1.11 2001/05/08 04:36:37 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$
+ */
+
+#include <stdio.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <signal.h>
+#include <errno.h>
+#include <err.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include "route6d.h"
+
+static int s;
+static struct sockaddr_in6 sin6;
+static struct rip6 *ripbuf;
+
+#define RIPSIZE(n) (sizeof(struct rip6) + (n-1) * sizeof(struct netinfo6))
+
+int main(int, char **);
+static void usage(void);
+static const char *sa_n2a(struct sockaddr *);
+static const char *inet6_n2a(struct in6_addr *);
+
+int
+main(int argc, char *argv[])
+{
+ struct netinfo6 *np;
+ struct sockaddr_in6 fsock;
+ int i, n, len;
+ int c;
+ int ifidx = -1;
+ int error;
+ socklen_t flen;
+ char pbuf[10];
+ struct addrinfo hints, *res;
+
+ while ((c = getopt(argc, argv, "I:")) != -1) {
+ switch (c) {
+ case 'I':
+ ifidx = if_nametoindex(optarg);
+ if (ifidx == 0) {
+ errx(1, "invalid interface %s", optarg);
+ /*NOTREACHED*/
+ }
+ break;
+ default:
+ usage();
+ exit(1);
+ /*NOTREACHED*/
+ }
+ }
+ argv += optind;
+ argc -= optind;
+
+ if (argc != 1) {
+ usage();
+ exit(1);
+ }
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
+ err(1, "socket");
+ /*NOTREACHED*/
+ }
+
+ /* getaddrinfo is preferred for addr@ifname syntax */
+ snprintf(pbuf, sizeof(pbuf), "%d", RIP6_PORT);
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_DGRAM;
+ error = getaddrinfo(argv[0], pbuf, &hints, &res);
+ if (error) {
+ errx(1, "%s: %s", argv[0], gai_strerror(error));
+ /*NOTREACHED*/
+ }
+ if (res->ai_next) {
+ errx(1, "%s: %s", argv[0], "resolved to multiple addrs");
+ /*NOTREACHED*/
+ }
+ if (sizeof(sin6) != res->ai_addrlen) {
+ errx(1, "%s: %s", argv[0], "invalid addrlen");
+ /*NOTREACHED*/
+ }
+ memcpy(&sin6, res->ai_addr, res->ai_addrlen);
+ if (ifidx >= 0)
+ sin6.sin6_scope_id = ifidx;
+
+ if ((ripbuf = (struct rip6 *)malloc(BUFSIZ)) == NULL) {
+ err(1, "malloc");
+ /*NOTREACHED*/
+ }
+ ripbuf->rip6_cmd = RIP6_REQUEST;
+ ripbuf->rip6_vers = RIP6_VERSION;
+ ripbuf->rip6_res1[0] = 0;
+ ripbuf->rip6_res1[1] = 0;
+ np = ripbuf->rip6_nets;
+ bzero(&np->rip6_dest, sizeof(struct in6_addr));
+ np->rip6_tag = 0;
+ np->rip6_plen = 0;
+ np->rip6_metric = HOPCNT_INFINITY6;
+ if (sendto(s, ripbuf, RIPSIZE(1), 0, (struct sockaddr *)&sin6,
+ sizeof(struct sockaddr_in6)) < 0) {
+ err(1, "send");
+ /*NOTREACHED*/
+ }
+ do {
+ flen = sizeof(fsock);
+ if ((len = recvfrom(s, ripbuf, BUFSIZ, 0,
+ (struct sockaddr *)&fsock, &flen)) < 0) {
+ err(1, "recvfrom");
+ /*NOTREACHED*/
+ }
+ printf("Response from %s len %d\n",
+ sa_n2a((struct sockaddr *)&fsock), len);
+ n = (len - sizeof(struct rip6) + sizeof(struct netinfo6)) /
+ sizeof(struct netinfo6);
+ np = ripbuf->rip6_nets;
+ for (i = 0; i < n; i++, np++) {
+ printf("\t%s/%d [%d]", inet6_n2a(&np->rip6_dest),
+ np->rip6_plen, np->rip6_metric);
+ if (np->rip6_tag)
+ printf(" tag=0x%x", ntohs(np->rip6_tag));
+ printf("\n");
+ }
+ } while (len == RIPSIZE(24));
+
+ exit(0);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: rip6query [-I iface] address\n");
+}
+
+/* getnameinfo() is preferred as we may be able to show ifindex as ifname */
+static const char *
+sa_n2a(struct sockaddr *sa)
+{
+ static char buf[NI_MAXHOST];
+
+ if (getnameinfo(sa, sa->sa_len, buf, sizeof(buf),
+ NULL, 0, NI_NUMERICHOST) != 0) {
+ snprintf(buf, sizeof(buf), "%s", "(invalid)");
+ }
+ return buf;
+}
+
+static const char *
+inet6_n2a(struct in6_addr *addr)
+{
+ static char buf[NI_MAXHOST];
+
+ return inet_ntop(AF_INET6, addr, buf, sizeof(buf));
+}
diff --git a/usr.sbin/rmt/Makefile b/usr.sbin/rmt/Makefile
new file mode 100644
index 0000000..8a678b6
--- /dev/null
+++ b/usr.sbin/rmt/Makefile
@@ -0,0 +1,12 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+PROG= rmt
+MAN= rmt.8
+
+# called from /usr/src/etc/Makefile
+etc-rmt:
+ rm -f ${DESTDIR}/etc/rmt
+ ln -s ${BINDIR}/rmt ${DESTDIR}/etc/rmt
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/rmt/Makefile.depend b/usr.sbin/rmt/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/rmt/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/rmt/rmt.8 b/usr.sbin/rmt/rmt.8
new file mode 100644
index 0000000..8dfe245
--- /dev/null
+++ b/usr.sbin/rmt/rmt.8
@@ -0,0 +1,221 @@
+.\" 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.
+.\"
+.\" @(#)rmt.8 8.3 (Berkeley) 6/1/94
+.\" $FreeBSD$
+.\"
+.Dd June 1, 1994
+.Dt RMT 8
+.Os
+.Sh NAME
+.Nm rmt
+.Nd remote magtape protocol module
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+The
+.Nm
+utility is used by the remote dump and restore programs
+in manipulating a magnetic tape drive through an interprocess
+communication connection.
+It is normally started up with an
+.Xr rcmd 3
+call.
+.Pp
+The
+.Nm
+utility accepts requests specific to the manipulation of
+magnetic tapes, performs the commands, then responds with
+a status indication.
+All responses are in
+.Tn ASCII
+and in
+one of two forms.
+Successful commands have responses of:
+.Bd -ragged -offset indent
+.Sm off
+.Sy A Ar number No \en
+.Sm on
+.Ed
+.Pp
+.Ar Number
+is an
+.Tn ASCII
+representation of a decimal number.
+Unsuccessful commands are responded to with:
+.Bd -ragged -offset indent
+.Sm off
+.Xo Sy E Ar error-number
+.No \en Ar error-message
+.No \en
+.Xc
+.Sm on
+.Ed
+.Pp
+.Ar Error-number
+is one of the possible error
+numbers described in
+.Xr intro 2
+and
+.Ar error-message
+is the corresponding error string as printed
+from a call to
+.Xr perror 3 .
+The protocol is comprised of the
+following commands, which are sent as indicated - no spaces are supplied
+between the command and its arguments, or between its arguments, and
+.Ql \en
+indicates that a newline should be supplied:
+.Bl -tag -width Ds
+.Sm off
+.It Xo Sy \&O Ar device
+.No \en Ar mode No \en
+.Xc
+.Sm on
+Open the specified
+.Ar device
+using the indicated
+.Ar mode .
+.Ar Device
+is a full pathname and
+.Ar mode
+is an
+.Tn ASCII
+representation of a decimal
+number suitable for passing to
+.Xr open 2 .
+If a device had already been opened, it is
+closed before a new open is performed.
+.Sm off
+.It Xo Sy C Ar device No \en
+.Xc
+.Sm on
+Close the currently open device.
+The
+.Ar device
+specified is ignored.
+.Sm off
+.It Xo Sy L
+.Ar whence No \en
+.Ar offset No \en
+.Xc
+.Sm on
+Perform an
+.Xr lseek 2
+operation using the specified parameters.
+The response value is that returned from the
+.Xr lseek 2
+call.
+.Sm off
+.It Sy W Ar count No \en
+.Sm on
+Write data onto the open device.
+The
+.Nm
+utility reads
+.Ar count
+bytes from the connection, aborting if
+a premature end-of-file is encountered.
+The response value is that returned from
+the
+.Xr write 2
+call.
+.Sm off
+.It Sy R Ar count No \en
+.Sm on
+Read
+.Ar count
+bytes of data from the open device.
+If
+.Ar count
+exceeds the size of the data buffer (10 kilobytes), it is
+truncated to the data buffer size.
+The
+.Nm
+utility then performs the requested
+.Xr read 2
+and responds with
+.Sm off
+.Sy A Ar count-read No \en
+.Sm on
+if the read was
+successful; otherwise an error in the
+standard format is returned.
+If the read
+was successful, the data read is then sent.
+.Sm off
+.It Xo Sy I Ar operation
+.No \en Ar count No \en
+.Xc
+.Sm on
+Perform a
+.Dv MTIOCOP
+.Xr ioctl 2
+command using the specified parameters.
+The parameters are interpreted as the
+.Tn ASCII
+representations of the decimal values
+to place in the
+.Ar mt_op
+and
+.Ar mt_count
+fields of the structure used in the
+.Xr ioctl 2
+call.
+The return value is the
+.Ar count
+parameter when the operation is successful.
+.It Sy S
+Return the status of the open device, as
+obtained with a
+.Dv MTIOCGET
+.Xr ioctl 2
+call.
+If the operation was successful,
+an ``ack'' is sent with the size of the
+status buffer, then the status buffer is
+sent (in binary).
+.El
+.Pp
+Any other command causes
+.Nm
+to exit.
+.Sh DIAGNOSTICS
+All responses are of the form described above.
+.Sh SEE ALSO
+.Xr rcmd 3 ,
+.Xr mtio 4 ,
+.Xr rdump 8 ,
+.Xr rrestore 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.2 .
+.Sh BUGS
+People should be discouraged from using this for a remote
+file access protocol.
diff --git a/usr.sbin/rmt/rmt.c b/usr.sbin/rmt/rmt.c
new file mode 100644
index 0000000..a5c048f
--- /dev/null
+++ b/usr.sbin/rmt/rmt.c
@@ -0,0 +1,250 @@
+/*
+ * 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[] = "@(#)rmt.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * rmt
+ */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/mtio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static int tape = -1;
+
+static char *record;
+static int maxrecsize = -1;
+
+#define SSIZE 64
+static char device[SSIZE];
+static char count[SSIZE], mode[SSIZE], pos[SSIZE], op[SSIZE];
+
+static char resp[BUFSIZ];
+
+static FILE *debug;
+#define DEBUG(f) if (debug) fprintf(debug, f)
+#define DEBUG1(f,a) if (debug) fprintf(debug, f, a)
+#define DEBUG2(f,a1,a2) if (debug) fprintf(debug, f, a1, a2)
+
+static char *checkbuf(char *, int);
+static void error(int);
+static void getstring(char *);
+
+int
+main(int argc, char **argv)
+{
+ int rval;
+ char c;
+ int n, i, cc;
+
+ argc--, argv++;
+ if (argc > 0) {
+ debug = fopen(*argv, "w");
+ if (debug == 0)
+ exit(1);
+ (void)setbuf(debug, (char *)0);
+ }
+top:
+ errno = 0;
+ rval = 0;
+ if (read(STDIN_FILENO, &c, 1) != 1)
+ exit(0);
+ switch (c) {
+
+ case 'O':
+ if (tape >= 0)
+ (void) close(tape);
+ getstring(device);
+ getstring(mode);
+ DEBUG2("rmtd: O %s %s\n", device, mode);
+ /*
+ * XXX the rmt protocol does not provide a means to
+ * specify the permission bits; allow rw for everyone,
+ * as modified by the users umask
+ */
+ tape = open(device, atoi(mode), 0666);
+ if (tape < 0)
+ goto ioerror;
+ goto respond;
+
+ case 'C':
+ DEBUG("rmtd: C\n");
+ getstring(device); /* discard */
+ if (close(tape) < 0)
+ goto ioerror;
+ tape = -1;
+ goto respond;
+
+ case 'L':
+ getstring(count);
+ getstring(pos);
+ DEBUG2("rmtd: L %s %s\n", count, pos);
+ rval = lseek(tape, (off_t)strtoll(count, NULL, 10), atoi(pos));
+ if (rval < 0)
+ goto ioerror;
+ goto respond;
+
+ case 'W':
+ getstring(count);
+ n = atoi(count);
+ DEBUG1("rmtd: W %s\n", count);
+ record = checkbuf(record, n);
+ for (i = 0; i < n; i += cc) {
+ cc = read(STDIN_FILENO, &record[i], n - i);
+ if (cc <= 0) {
+ DEBUG("rmtd: premature eof\n");
+ exit(2);
+ }
+ }
+ rval = write(tape, record, n);
+ if (rval < 0)
+ goto ioerror;
+ goto respond;
+
+ case 'R':
+ getstring(count);
+ DEBUG1("rmtd: R %s\n", count);
+ n = atoi(count);
+ record = checkbuf(record, n);
+ rval = read(tape, record, n);
+ if (rval < 0)
+ goto ioerror;
+ (void)sprintf(resp, "A%d\n", rval);
+ (void)write(STDOUT_FILENO, resp, strlen(resp));
+ (void)write(STDOUT_FILENO, record, rval);
+ goto top;
+
+ case 'I':
+ getstring(op);
+ getstring(count);
+ DEBUG2("rmtd: I %s %s\n", op, count);
+ { struct mtop mtop;
+ mtop.mt_op = atoi(op);
+ mtop.mt_count = atoi(count);
+ if (ioctl(tape, MTIOCTOP, (char *)&mtop) < 0)
+ goto ioerror;
+ rval = mtop.mt_count;
+ }
+ goto respond;
+
+ case 'S': /* status */
+ DEBUG("rmtd: S\n");
+ { struct mtget mtget;
+ if (ioctl(tape, MTIOCGET, (char *)&mtget) < 0)
+ goto ioerror;
+ rval = sizeof (mtget);
+ if (rval > 24) /* original mtget structure size */
+ rval = 24;
+ (void)sprintf(resp, "A%d\n", rval);
+ (void)write(STDOUT_FILENO, resp, strlen(resp));
+ (void)write(STDOUT_FILENO, (char *)&mtget, rval);
+ goto top;
+ }
+
+ case 'V': /* version */
+ getstring(op);
+ DEBUG1("rmtd: V %s\n", op);
+ rval = 2;
+ goto respond;
+
+ default:
+ DEBUG1("rmtd: garbage command %c\n", c);
+ exit(3);
+ }
+respond:
+ DEBUG1("rmtd: A %d\n", rval);
+ (void)sprintf(resp, "A%d\n", rval);
+ (void)write(STDOUT_FILENO, resp, strlen(resp));
+ goto top;
+ioerror:
+ error(errno);
+ goto top;
+}
+
+void
+getstring(char *bp)
+{
+ int i;
+ char *cp = bp;
+
+ for (i = 0; i < SSIZE; i++) {
+ if (read(STDIN_FILENO, cp+i, 1) != 1)
+ exit(0);
+ if (cp[i] == '\n')
+ break;
+ }
+ cp[i] = '\0';
+}
+
+static char *
+checkbuf(char *rec, int size)
+{
+
+ if (size <= maxrecsize)
+ return (rec);
+ if (rec != 0)
+ free(rec);
+ rec = malloc(size);
+ if (rec == 0) {
+ DEBUG("rmtd: cannot allocate buffer space\n");
+ exit(4);
+ }
+ maxrecsize = size;
+ while (size > 1024 &&
+ setsockopt(0, SOL_SOCKET, SO_RCVBUF, &size, sizeof (size)) < 0)
+ size -= 1024;
+ return (rec);
+}
+
+static void
+error(int num)
+{
+
+ DEBUG2("rmtd: E %d (%s)\n", num, strerror(num));
+ (void)snprintf(resp, sizeof(resp), "E%d\n%s\n", num, strerror(num));
+ (void)write(STDOUT_FILENO, resp, strlen(resp));
+}
diff --git a/usr.sbin/route6d/Makefile b/usr.sbin/route6d/Makefile
new file mode 100644
index 0000000..df23d33
--- /dev/null
+++ b/usr.sbin/route6d/Makefile
@@ -0,0 +1,11 @@
+# @(#)Makefile 8.1 (Berkeley) 6/5/93
+# $FreeBSD$
+
+PROG= route6d
+MAN= route6d.8
+
+CFLAGS+= -DHAVE_POLL_H
+
+WARNS?= 1
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/route6d/Makefile.depend b/usr.sbin/route6d/Makefile.depend
new file mode 100644
index 0000000..54c1f6f
--- /dev/null
+++ b/usr.sbin/route6d/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/route6d/misc/chkrt b/usr.sbin/route6d/misc/chkrt
new file mode 100755
index 0000000..6ae01f3
--- /dev/null
+++ b/usr.sbin/route6d/misc/chkrt
@@ -0,0 +1,64 @@
+#!/usr/bin/perl
+#
+# $FreeBSD$
+#
+$dump="/var/tmp/route6d_dump";
+$pidfile="/var/run/route6d.pid";
+
+system("rm -f $dump");
+
+open(FD, "< $pidfile") || die "Can not open $pidfile";
+$_ = <FD>;
+chop;
+close(FD);
+system("kill -INT $_");
+
+open(NS, "/usr/bin/netstat -r -n|") || die "Can not open netstat";
+while (<NS>) {
+ chop;
+ next unless (/^3f/ || /^5f/);
+ @f = split(/\s+/);
+ $gw{$f[0]} = $f[1];
+ $int{$f[0]} = $f[3];
+}
+close(NS);
+
+$err=0;
+sleep(2);
+open(FD, "< $dump") || die "Can not open $dump";
+while (<FD>) {
+ chop;
+ next unless (/^ 3f/ || /^ 5f/);
+ @f = split(/\s+/);
+ $dst = $f[1];
+ $f[2] =~ /if\(\d:([a-z0-9]+)\)/;
+ $intf = $1;
+ $f[3] =~ /gw\(([a-z0-9:]+)\)/;
+ $gateway = $1;
+ $f[4] =~ /\[(\d+)\]/;
+ $metric = $1;
+ $f[5] =~ /age\((\d+)\)/;
+ $age = $1;
+ unless (defined($gw{$dst})) {
+ print "NOT FOUND: $dst $intf $gateway $metric $age\n";
+ $err++;
+ next;
+ }
+ if ($gw{$dst} ne $gateway && $gw{$dst} !~ /link#\d+/) {
+ print "WRONG GW: $dst $intf $gateway $metric $age\n";
+ print "kernel gw: $gw{$dst}\n";
+ $err++;
+ next;
+ }
+ if ($int{$dst} ne $intf) {
+ print "WRONG IF: $dst $intf $gateway $metric $age\n";
+ print "kernel if: $int{$dst}\n";
+ $err++;
+ next;
+ }
+}
+close(FD);
+
+if ($err == 0) {
+ print "No error found\n";
+}
diff --git a/usr.sbin/route6d/misc/cksum.c b/usr.sbin/route6d/misc/cksum.c
new file mode 100644
index 0000000..bff7e2d
--- /dev/null
+++ b/usr.sbin/route6d/misc/cksum.c
@@ -0,0 +1,52 @@
+/*
+ * 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$
+ */
+
+#include <stdio.h>
+
+unsigned short buf[BUFSIZ];
+
+main()
+{
+ int i;
+ unsigned short *p = buf, *q = &buf[4];
+ unsigned long sum, sum2;
+
+ while (scanf("%x", &i) != EOF) {
+ *p++ = i; printf("%d ", i);
+ }
+ printf("\n");
+
+ sum = buf[2] + (buf[3] >> 8) & 0xff;
+ while (q != p)
+ sum += (*q++ & 0xffff);
+ sum2 = (sum & 0xffff) + (sum >> 16) & 0xffff;
+ printf("%x, %x\n", sum, sum2);
+}
diff --git a/usr.sbin/route6d/route6d.8 b/usr.sbin/route6d/route6d.8
new file mode 100644
index 0000000..2dfd91c
--- /dev/null
+++ b/usr.sbin/route6d/route6d.8
@@ -0,0 +1,297 @@
+.\" $KAME: route6d.8,v 1.10 2000/11/24 11:57:18 itojun Exp $
+.\"
+.\" 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$
+.\"
+.Dd November 18, 2012
+.Dt ROUTE6D 8
+.Os
+.Sh NAME
+.Nm route6d
+.Nd RIP6 Routing Daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl adDhlnqsS
+.Bk -words
+.Op Fl R Ar routelog
+.Ek
+.Bk -words
+.Op Fl A Ar prefix/preflen,if1[,if2...\&]
+.Ek
+.Bk -words
+.Op Fl L Ar prefix/preflen,if1[,if2...\&]
+.Ek
+.Bk -words
+.Op Fl N Ar if1[,if2...\&]
+.Ek
+.Bk -words
+.Op Fl O Ar prefix/preflen,if1[,if2...\&]
+.Ek
+.Bk -words
+.Op Fl P Ar number
+.Ek
+.Bk -words
+.Op Fl p Ar pidfile
+.Ek
+.Bk -words
+.Op Fl Q Ar number
+.Ek
+.Bk -words
+.Op Fl T Ar if1[,if2...\&]
+.Ek
+.Bk -words
+.Op Fl t Ar tag
+.Ek
+.\"
+.Sh DESCRIPTION
+The
+.Nm
+utility is a routing daemon which supports RIP over IPv6.
+.Pp
+Options are:
+.Bl -tag -width indent
+.\"
+.It Fl a
+Enables aging of the statically defined routes.
+With this option, any
+statically defined routes will be removed unless corresponding updates
+arrive as if the routes are received at the startup of
+.Nm .
+.\"
+.It Fl R Ar routelog
+This option makes the
+.Nm
+to log the route change (add/delete) to the file
+.Ar routelog .
+.\"
+.It Fl A Ar prefix/preflen,if1[,if2...]
+This option is used for aggregating routes.
+.Ar prefix/preflen
+specifies the prefix and the prefix length of the
+aggregated route.
+When advertising routes,
+.Nm
+filters specific routes covered by the aggregate,
+and advertises the aggregated route
+.Ar prefix/preflen ,
+to the interfaces specified in the comma-separated interface list,
+.Ar if1[,if2...] .
+The characters
+.Qq Li * ,
+.Qq Li \&? ,
+and
+.Qq Li \&[
+in the interface list will be interpreted as shell-style pattern.
+The
+.Nm
+utility creates a static route to
+.Ar prefix/preflen
+with
+.Dv RTF_REJECT
+flag, into the kernel routing table.
+.\"
+.It Fl d
+Enables output of debugging message.
+This option also instructs
+.Nm
+to run in foreground mode
+(does not become daemon).
+.\"
+.It Fl D
+Enables extensive output of debugging message.
+This option also instructs
+.Nm
+to run in foreground mode
+(does not become daemon).
+.\"
+.It Fl h
+Disables the split horizon processing.
+.\"
+.It Fl l
+By default,
+.Nm
+will not exchange site local routes for safety reasons.
+This is because semantics of site local address space is rather vague
+(specification is still in being worked),
+and there is no good way to define site local boundary.
+With
+.Fl l
+option,
+.Nm
+will exchange site local routes as well.
+It must not be used on site boundary routers,
+since
+.Fl l
+option assumes that all interfaces are in the same site.
+.\"
+.It Fl L Ar prefix/preflen,if1[,if2...]
+Filter incoming routes from interfaces
+.Ar if1,[if2...] .
+The
+.Nm
+utility will accept incoming routes that are in
+.Ar prefix/preflen .
+If multiple
+.Fl L
+options are specified, any routes that match one of the options is accepted.
+.Li ::/0
+is treated specially as default route, not
+.Do
+any route that has longer prefix length than, or equal to 0
+.Dc .
+If you would like to accept any route, specify no
+.Fl L
+option.
+For example, with
+.Do
+.Fl L
+.Li 2001:db8::/16,if1
+.Fl L
+.Li ::/0,if1
+.Dc
+.Nm
+will accept default route and routes in 6bone test address, but no others.
+.\"
+.It Fl n
+Do not update the kernel routing table.
+.\"
+.It Fl N Ar if1[,if2...]
+Do not listen to, or advertise, route from/to interfaces specified by
+.Ar if1,[if2...] .
+.\"
+.It Fl O Ar prefix/preflen,if1[,if2...]
+Restrict route advertisement toward interfaces specified by
+.Ar if1,[if2...] .
+With this option
+.Nm
+will only advertise routes that matches
+.Ar prefix/preflen .
+.It Fl P Ar number
+Specifies routes to be ignored in calculation of expiration timer.
+The
+.Ar number
+must be
+.Li 1 ,
+.Li 2 ,
+or
+.Li 3
+and it means route flags of
+.Li RTF_PROTO1 ,
+.Li RTF_PROTO2 ,
+or
+.Li RTF_PROTO3 .
+When
+.Li 1
+is specified, routes with
+.Li RTF_PROTO1
+will never expire.
+.It Fl p Ar pidfile
+Specifies an alternative file in which to store the process ID.
+The default is
+.Pa /var/run/route6d.pid .
+.It Fl Q Ar number
+Specifies flag which will be used for routes added by RIP protocol.
+The default is
+.Li 2 Pq Li RTF_PROTO2 .
+.\"
+.It Fl q
+Makes
+.Nm
+in listen-only mode.
+No advertisement is sent.
+.\"
+.It Fl s
+Makes
+.Nm
+to advertise the statically defined routes which exist in the kernel routing
+table when
+.Nm
+invoked.
+Announcements obey the regular split horizon rule.
+.\"
+.It Fl S
+This option is the same as
+.Fl s
+option except that no split horizon rule does apply.
+.\"
+.It Fl T Ar if1[,if2...]
+Advertise only default route, toward
+.Ar if1,[if2...] .
+.\"
+.It Fl t Ar tag
+Attach route tag
+.Ar tag
+to originated route entries.
+.Ar tag
+can be decimal, octal prefixed by
+.Li 0 ,
+or hexadecimal prefixed by
+.Li 0x .
+.\"
+.El
+.Pp
+Upon receipt of signal
+.Dv SIGINT
+or
+.Dv SIGUSR1 ,
+.Nm
+will dump the current internal state into
+.Pa /var/run/route6d_dump .
+.\"
+.Sh FILES
+.Bl -tag -width /var/run/route6d_dump -compact
+.It Pa /var/run/route6d_dump
+dumps internal state on
+.Dv SIGINT
+or
+.Dv SIGUSR1
+.El
+.\"
+.Sh SEE ALSO
+.Rs
+.%A G. Malkin
+.%A R. Minnear
+.%T RIPng for IPv6
+.%R RFC2080
+.%D January 1997
+.Re
+.\"
+.Sh NOTE
+The
+.Nm
+utility uses IPv6 advanced API,
+defined in RFC2292,
+for communicating with peers using link-local addresses.
+.Pp
+Internally
+.Nm
+embeds interface identifier into bit 32 to 63 of link-local addresses
+.Li ( fe80::xx
+and
+.Li ff02::xx )
+so they will be visible on internal state dump file
+.Pq Pa /var/run/route6d_dump .
+.Pp
+Routing table manipulation differs from IPv6 implementation to implementation.
+Currently
+.Nm
+obeys WIDE Hydrangea/KAME IPv6 kernel,
+and will not be able to run on other platforms.
+.Pp
+Current
+.Nm
+does not reduce the rate of the triggered updates when consecutive updates
+arrive.
diff --git a/usr.sbin/route6d/route6d.c b/usr.sbin/route6d/route6d.c
new file mode 100644
index 0000000..3c3d9d3
--- /dev/null
+++ b/usr.sbin/route6d/route6d.c
@@ -0,0 +1,3623 @@
+/* $FreeBSD$ */
+/* $KAME: route6d.c,v 1.104 2003/10/31 00:30:20 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.
+ */
+
+#ifndef lint
+static const char _rcsid[] = "$KAME: route6d.c,v 1.104 2003/10/31 00:30:20 itojun Exp $";
+#endif
+
+#include <stdio.h>
+
+#include <time.h>
+#include <unistd.h>
+#include <fnmatch.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#ifdef __STDC__
+#include <stdarg.h>
+#else
+#include <varargs.h>
+#endif
+#include <syslog.h>
+#include <stddef.h>
+#include <errno.h>
+#include <err.h>
+#ifdef HAVE_POLL_H
+#include <poll.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/file.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/sysctl.h>
+#include <sys/uio.h>
+#include <net/if.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <netinet/ip6.h>
+#include <netinet/udp.h>
+#include <netdb.h>
+#include <ifaddrs.h>
+
+#include <arpa/inet.h>
+
+#include "route6d.h"
+
+#define MAXFILTER 40
+#define RT_DUMP_MAXRETRY 15
+
+#ifdef DEBUG
+#define INIT_INTERVAL6 6
+#else
+#define INIT_INTERVAL6 10 /* Wait to submit an initial riprequest */
+#endif
+
+/* alignment constraint for routing socket */
+#define ROUNDUP(a) \
+ ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
+#define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len))
+
+struct ifc { /* Configuration of an interface */
+ TAILQ_ENTRY(ifc) ifc_next;
+
+ char ifc_name[IFNAMSIZ]; /* if name */
+ int ifc_index; /* if index */
+ int ifc_mtu; /* if mtu */
+ int ifc_metric; /* if metric */
+ u_int ifc_flags; /* flags */
+ short ifc_cflags; /* IFC_XXX */
+ struct in6_addr ifc_mylladdr; /* my link-local address */
+ struct sockaddr_in6 ifc_ripsin; /* rip multicast address */
+ TAILQ_HEAD(, ifac) ifc_ifac_head; /* list of AF_INET6 addrs */
+ TAILQ_HEAD(, iff) ifc_iff_head; /* list of filters */
+ int ifc_joined; /* joined to ff02::9 */
+};
+TAILQ_HEAD(, ifc) ifc_head = TAILQ_HEAD_INITIALIZER(ifc_head);
+
+struct ifac { /* Adddress associated to an interface */
+ TAILQ_ENTRY(ifac) ifac_next;
+
+ struct ifc *ifac_ifc; /* back pointer */
+ struct in6_addr ifac_addr; /* address */
+ struct in6_addr ifac_raddr; /* remote address, valid in p2p */
+ int ifac_scope_id; /* scope id */
+ int ifac_plen; /* prefix length */
+};
+
+struct iff { /* Filters for an interface */
+ TAILQ_ENTRY(iff) iff_next;
+
+ int iff_type;
+ struct in6_addr iff_addr;
+ int iff_plen;
+};
+
+struct ifc **index2ifc;
+unsigned int nindex2ifc;
+struct ifc *loopifcp = NULL; /* pointing to loopback */
+#ifdef HAVE_POLL_H
+struct pollfd set[2];
+#else
+fd_set *sockvecp; /* vector to select() for receiving */
+fd_set *recvecp;
+int fdmasks;
+int maxfd; /* maximum fd for select() */
+#endif
+int rtsock; /* the routing socket */
+int ripsock; /* socket to send/receive RIP datagram */
+
+struct rip6 *ripbuf; /* packet buffer for sending */
+
+/*
+ * Maintain the routes in a linked list. When the number of the routes
+ * grows, somebody would like to introduce a hash based or a radix tree
+ * based structure. I believe the number of routes handled by RIP is
+ * limited and I don't have to manage a complex data structure, however.
+ *
+ * One of the major drawbacks of the linear linked list is the difficulty
+ * of representing the relationship between a couple of routes. This may
+ * be a significant problem when we have to support route aggregation with
+ * suppressing the specifics covered by the aggregate.
+ */
+
+struct riprt {
+ TAILQ_ENTRY(riprt) rrt_next; /* next destination */
+
+ struct riprt *rrt_same; /* same destination - future use */
+ struct netinfo6 rrt_info; /* network info */
+ struct in6_addr rrt_gw; /* gateway */
+ u_long rrt_flags; /* kernel routing table flags */
+ u_long rrt_rflags; /* route6d routing table flags */
+ time_t rrt_t; /* when the route validated */
+ int rrt_index; /* ifindex from which this route got */
+};
+TAILQ_HEAD(, riprt) riprt_head = TAILQ_HEAD_INITIALIZER(riprt_head);
+
+int dflag = 0; /* debug flag */
+int qflag = 0; /* quiet flag */
+int nflag = 0; /* don't update kernel routing table */
+int aflag = 0; /* age out even the statically defined routes */
+int hflag = 0; /* don't split horizon */
+int lflag = 0; /* exchange site local routes */
+int Pflag = 0; /* don't age out routes with RTF_PROTO[123] */
+int Qflag = RTF_PROTO2; /* set RTF_PROTO[123] flag to routes by RIPng */
+int sflag = 0; /* announce static routes w/ split horizon */
+int Sflag = 0; /* announce static routes to every interface */
+unsigned long routetag = 0; /* route tag attached on originating case */
+
+char *filter[MAXFILTER];
+int filtertype[MAXFILTER];
+int nfilter = 0;
+
+pid_t pid;
+
+struct sockaddr_storage ripsin;
+
+int interval = 1;
+time_t nextalarm = 0;
+time_t sup_trig_update = 0;
+
+FILE *rtlog = NULL;
+
+int logopened = 0;
+
+static int seq = 0;
+
+volatile sig_atomic_t seenalrm;
+volatile sig_atomic_t seenquit;
+volatile sig_atomic_t seenusr1;
+
+#define RRTF_AGGREGATE 0x08000000
+#define RRTF_NOADVERTISE 0x10000000
+#define RRTF_NH_NOT_LLADDR 0x20000000
+#define RRTF_SENDANYWAY 0x40000000
+#define RRTF_CHANGED 0x80000000
+
+int main(int, char **);
+void sighandler(int);
+void ripalarm(void);
+void riprecv(void);
+void ripsend(struct ifc *, struct sockaddr_in6 *, int);
+int out_filter(struct riprt *, struct ifc *);
+void init(void);
+void sockopt(struct ifc *);
+void ifconfig(void);
+int ifconfig1(const char *, const struct sockaddr *, struct ifc *, int);
+void rtrecv(void);
+int rt_del(const struct sockaddr_in6 *, const struct sockaddr_in6 *,
+ const struct sockaddr_in6 *);
+int rt_deladdr(struct ifc *, const struct sockaddr_in6 *,
+ const struct sockaddr_in6 *);
+void filterconfig(void);
+int getifmtu(int);
+const char *rttypes(struct rt_msghdr *);
+const char *rtflags(struct rt_msghdr *);
+const char *ifflags(int);
+int ifrt(struct ifc *, int);
+void ifrt_p2p(struct ifc *, int);
+void applymask(struct in6_addr *, struct in6_addr *);
+void applyplen(struct in6_addr *, int);
+void ifrtdump(int);
+void ifdump(int);
+void ifdump0(FILE *, const struct ifc *);
+void ifremove(int);
+void rtdump(int);
+void rt_entry(struct rt_msghdr *, int);
+void rtdexit(void);
+void riprequest(struct ifc *, struct netinfo6 *, int,
+ struct sockaddr_in6 *);
+void ripflush(struct ifc *, struct sockaddr_in6 *, int, struct netinfo6 *np);
+void sendrequest(struct ifc *);
+int sin6mask2len(const struct sockaddr_in6 *);
+int mask2len(const struct in6_addr *, int);
+int sendpacket(struct sockaddr_in6 *, int);
+int addroute(struct riprt *, const struct in6_addr *, struct ifc *);
+int delroute(struct netinfo6 *, struct in6_addr *);
+struct in6_addr *getroute(struct netinfo6 *, struct in6_addr *);
+void krtread(int);
+int tobeadv(struct riprt *, struct ifc *);
+char *allocopy(char *);
+char *hms(void);
+const char *inet6_n2p(const struct in6_addr *);
+struct ifac *ifa_match(const struct ifc *, const struct in6_addr *, int);
+struct in6_addr *plen2mask(int);
+struct riprt *rtsearch(struct netinfo6 *);
+int ripinterval(int);
+time_t ripsuptrig(void);
+void fatal(const char *, ...)
+ __attribute__((__format__(__printf__, 1, 2)));
+void trace(int, const char *, ...)
+ __attribute__((__format__(__printf__, 2, 3)));
+void tracet(int, const char *, ...)
+ __attribute__((__format__(__printf__, 2, 3)));
+unsigned int if_maxindex(void);
+struct ifc *ifc_find(char *);
+struct iff *iff_find(struct ifc *, int);
+void setindex2ifc(int, struct ifc *);
+
+#define MALLOC(type) ((type *)malloc(sizeof(type)))
+
+#define IFIL_TYPE_ANY 0x0
+#define IFIL_TYPE_A 'A'
+#define IFIL_TYPE_N 'N'
+#define IFIL_TYPE_T 'T'
+#define IFIL_TYPE_O 'O'
+#define IFIL_TYPE_L 'L'
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ int error = 0;
+ unsigned long proto;
+ struct ifc *ifcp;
+ sigset_t mask, omask;
+ const char *pidfile = ROUTE6D_PID;
+ FILE *pidfh;
+ char *progname;
+ char *ep;
+
+ progname = strrchr(*argv, '/');
+ if (progname)
+ progname++;
+ else
+ progname = *argv;
+
+ pid = getpid();
+ while ((ch = getopt(argc, argv, "A:N:O:R:T:L:t:adDhlnp:P:Q:qsS")) != -1) {
+ switch (ch) {
+ case 'A':
+ case 'N':
+ case 'O':
+ case 'T':
+ case 'L':
+ if (nfilter >= MAXFILTER) {
+ fatal("Exceeds MAXFILTER");
+ /*NOTREACHED*/
+ }
+ filtertype[nfilter] = ch;
+ filter[nfilter++] = allocopy(optarg);
+ break;
+ case 't':
+ ep = NULL;
+ routetag = strtoul(optarg, &ep, 0);
+ if (!ep || *ep != '\0' || (routetag & ~0xffff) != 0) {
+ fatal("invalid route tag");
+ /*NOTREACHED*/
+ }
+ break;
+ case 'p':
+ pidfile = optarg;
+ break;
+ case 'P':
+ ep = NULL;
+ proto = strtoul(optarg, &ep, 0);
+ if (!ep || *ep != '\0' || 3 < proto) {
+ fatal("invalid P flag");
+ /*NOTREACHED*/
+ }
+ if (proto == 0)
+ Pflag = 0;
+ if (proto == 1)
+ Pflag |= RTF_PROTO1;
+ if (proto == 2)
+ Pflag |= RTF_PROTO2;
+ if (proto == 3)
+ Pflag |= RTF_PROTO3;
+ break;
+ case 'Q':
+ ep = NULL;
+ proto = strtoul(optarg, &ep, 0);
+ if (!ep || *ep != '\0' || 3 < proto) {
+ fatal("invalid Q flag");
+ /*NOTREACHED*/
+ }
+ if (proto == 0)
+ Qflag = 0;
+ if (proto == 1)
+ Qflag |= RTF_PROTO1;
+ if (proto == 2)
+ Qflag |= RTF_PROTO2;
+ if (proto == 3)
+ Qflag |= RTF_PROTO3;
+ break;
+ case 'R':
+ if ((rtlog = fopen(optarg, "w")) == NULL) {
+ fatal("Can not write to routelog");
+ /*NOTREACHED*/
+ }
+ break;
+#define FLAG(c, flag, n) case c: do { flag = n; break; } while(0)
+ FLAG('a', aflag, 1); break;
+ FLAG('d', dflag, 1); break;
+ FLAG('D', dflag, 2); break;
+ FLAG('h', hflag, 1); break;
+ FLAG('l', lflag, 1); break;
+ FLAG('n', nflag, 1); break;
+ FLAG('q', qflag, 1); break;
+ FLAG('s', sflag, 1); break;
+ FLAG('S', Sflag, 1); break;
+#undef FLAG
+ default:
+ fatal("Invalid option specified, terminating");
+ /*NOTREACHED*/
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc > 0) {
+ fatal("bogus extra arguments");
+ /*NOTREACHED*/
+ }
+
+ if (geteuid()) {
+ nflag = 1;
+ fprintf(stderr, "No kernel update is allowed\n");
+ }
+
+ if (dflag == 0) {
+ if (daemon(0, 0) < 0) {
+ fatal("daemon");
+ /*NOTREACHED*/
+ }
+ }
+
+ openlog(progname, LOG_NDELAY|LOG_PID, LOG_DAEMON);
+ logopened++;
+
+ if ((ripbuf = (struct rip6 *)malloc(RIP6_MAXMTU)) == NULL)
+ fatal("malloc");
+ memset(ripbuf, 0, RIP6_MAXMTU);
+ ripbuf->rip6_cmd = RIP6_RESPONSE;
+ ripbuf->rip6_vers = RIP6_VERSION;
+ ripbuf->rip6_res1[0] = 0;
+ ripbuf->rip6_res1[1] = 0;
+
+ init();
+ ifconfig();
+ TAILQ_FOREACH(ifcp, &ifc_head, ifc_next) {
+ if (ifcp->ifc_index < 0) {
+ fprintf(stderr, "No ifindex found at %s "
+ "(no link-local address?)\n", ifcp->ifc_name);
+ error++;
+ }
+ }
+ if (error)
+ exit(1);
+ if (loopifcp == NULL) {
+ fatal("No loopback found");
+ /*NOTREACHED*/
+ }
+ TAILQ_FOREACH(ifcp, &ifc_head, ifc_next) {
+ ifrt(ifcp, 0);
+ }
+ filterconfig();
+ krtread(0);
+ if (dflag)
+ ifrtdump(0);
+
+ pid = getpid();
+ if ((pidfh = fopen(pidfile, "w")) != NULL) {
+ fprintf(pidfh, "%d\n", pid);
+ fclose(pidfh);
+ }
+
+ if ((ripbuf = (struct rip6 *)malloc(RIP6_MAXMTU)) == NULL) {
+ fatal("malloc");
+ /*NOTREACHED*/
+ }
+ memset(ripbuf, 0, RIP6_MAXMTU);
+ ripbuf->rip6_cmd = RIP6_RESPONSE;
+ ripbuf->rip6_vers = RIP6_VERSION;
+ ripbuf->rip6_res1[0] = 0;
+ ripbuf->rip6_res1[1] = 0;
+
+ if (signal(SIGALRM, sighandler) == SIG_ERR ||
+ signal(SIGQUIT, sighandler) == SIG_ERR ||
+ signal(SIGTERM, sighandler) == SIG_ERR ||
+ signal(SIGUSR1, sighandler) == SIG_ERR ||
+ signal(SIGHUP, sighandler) == SIG_ERR ||
+ signal(SIGINT, sighandler) == SIG_ERR) {
+ fatal("signal");
+ /*NOTREACHED*/
+ }
+ /*
+ * To avoid rip packet congestion (not on a cable but in this
+ * process), wait for a moment to send the first RIP6_RESPONSE
+ * packets.
+ */
+ alarm(ripinterval(INIT_INTERVAL6));
+
+ TAILQ_FOREACH(ifcp, &ifc_head, ifc_next) {
+ if (iff_find(ifcp, IFIL_TYPE_N) != NULL)
+ continue;
+ if (ifcp->ifc_index > 0 && (ifcp->ifc_flags & IFF_UP))
+ sendrequest(ifcp);
+ }
+
+ syslog(LOG_INFO, "**** Started ****");
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGALRM);
+ while (1) {
+ if (seenalrm) {
+ ripalarm();
+ seenalrm = 0;
+ continue;
+ }
+ if (seenquit) {
+ rtdexit();
+ seenquit = 0;
+ continue;
+ }
+ if (seenusr1) {
+ ifrtdump(SIGUSR1);
+ seenusr1 = 0;
+ continue;
+ }
+
+#ifdef HAVE_POLL_H
+ switch (poll(set, 2, INFTIM))
+#else
+ memcpy(recvecp, sockvecp, fdmasks);
+ switch (select(maxfd + 1, recvecp, 0, 0, 0))
+#endif
+ {
+ case -1:
+ if (errno != EINTR) {
+ fatal("select");
+ /*NOTREACHED*/
+ }
+ continue;
+ case 0:
+ continue;
+ default:
+#ifdef HAVE_POLL_H
+ if (set[0].revents & POLLIN)
+#else
+ if (FD_ISSET(ripsock, recvecp))
+#endif
+ {
+ sigprocmask(SIG_BLOCK, &mask, &omask);
+ riprecv();
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+ }
+#ifdef HAVE_POLL_H
+ if (set[1].revents & POLLIN)
+#else
+ if (FD_ISSET(rtsock, recvecp))
+#endif
+ {
+ sigprocmask(SIG_BLOCK, &mask, &omask);
+ rtrecv();
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+ }
+ }
+ }
+}
+
+void
+sighandler(int signo)
+{
+
+ switch (signo) {
+ case SIGALRM:
+ seenalrm++;
+ break;
+ case SIGQUIT:
+ case SIGTERM:
+ seenquit++;
+ break;
+ case SIGUSR1:
+ case SIGHUP:
+ case SIGINT:
+ seenusr1++;
+ break;
+ }
+}
+
+/*
+ * gracefully exits after resetting sockopts.
+ */
+/* ARGSUSED */
+void
+rtdexit(void)
+{
+ struct riprt *rrt;
+
+ alarm(0);
+ TAILQ_FOREACH(rrt, &riprt_head, rrt_next) {
+ if (rrt->rrt_rflags & RRTF_AGGREGATE) {
+ delroute(&rrt->rrt_info, &rrt->rrt_gw);
+ }
+ }
+ close(ripsock);
+ close(rtsock);
+ syslog(LOG_INFO, "**** Terminated ****");
+ closelog();
+ exit(1);
+}
+
+/*
+ * Called periodically:
+ * 1. age out the learned route. remove it if necessary.
+ * 2. submit RIP6_RESPONSE packets.
+ * Invoked in every SUPPLY_INTERVAL6 (30) seconds. I believe we don't have
+ * to invoke this function in every 1 or 5 or 10 seconds only to age the
+ * routes more precisely.
+ */
+/* ARGSUSED */
+void
+ripalarm(void)
+{
+ struct ifc *ifcp;
+ struct riprt *rrt, *rrt_tmp;
+ time_t t_lifetime, t_holddown;
+
+ /* age the RIP routes */
+ t_lifetime = time(NULL) - RIP_LIFETIME;
+ t_holddown = t_lifetime - RIP_HOLDDOWN;
+ TAILQ_FOREACH_SAFE(rrt, &riprt_head, rrt_next, rrt_tmp) {
+ if (rrt->rrt_t == 0)
+ continue;
+ else if (rrt->rrt_t < t_holddown) {
+ TAILQ_REMOVE(&riprt_head, rrt, rrt_next);
+ delroute(&rrt->rrt_info, &rrt->rrt_gw);
+ free(rrt);
+ } else if (rrt->rrt_t < t_lifetime)
+ rrt->rrt_info.rip6_metric = HOPCNT_INFINITY6;
+ }
+ /* Supply updates */
+ TAILQ_FOREACH(ifcp, &ifc_head, ifc_next) {
+ if (ifcp->ifc_index > 0 && (ifcp->ifc_flags & IFF_UP))
+ ripsend(ifcp, &ifcp->ifc_ripsin, 0);
+ }
+ alarm(ripinterval(SUPPLY_INTERVAL6));
+}
+
+void
+init(void)
+{
+ int error;
+ const int int0 = 0, int1 = 1, int255 = 255;
+ struct addrinfo hints, *res;
+ char port[NI_MAXSERV];
+
+ TAILQ_INIT(&ifc_head);
+ nindex2ifc = 0; /*initial guess*/
+ index2ifc = NULL;
+ snprintf(port, sizeof(port), "%u", RIP6_PORT);
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_INET6;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ hints.ai_flags = AI_PASSIVE;
+ error = getaddrinfo(NULL, port, &hints, &res);
+ if (error) {
+ fatal("%s", gai_strerror(error));
+ /*NOTREACHED*/
+ }
+ if (res->ai_next) {
+ fatal(":: resolved to multiple address");
+ /*NOTREACHED*/
+ }
+
+ ripsock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (ripsock < 0) {
+ fatal("rip socket");
+ /*NOTREACHED*/
+ }
+#ifdef IPV6_V6ONLY
+ if (setsockopt(ripsock, IPPROTO_IPV6, IPV6_V6ONLY,
+ &int1, sizeof(int1)) < 0) {
+ fatal("rip IPV6_V6ONLY");
+ /*NOTREACHED*/
+ }
+#endif
+ if (bind(ripsock, res->ai_addr, res->ai_addrlen) < 0) {
+ fatal("rip bind");
+ /*NOTREACHED*/
+ }
+ if (setsockopt(ripsock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
+ &int255, sizeof(int255)) < 0) {
+ fatal("rip IPV6_MULTICAST_HOPS");
+ /*NOTREACHED*/
+ }
+ if (setsockopt(ripsock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP,
+ &int0, sizeof(int0)) < 0) {
+ fatal("rip IPV6_MULTICAST_LOOP");
+ /*NOTREACHED*/
+ }
+
+#ifdef IPV6_RECVPKTINFO
+ if (setsockopt(ripsock, IPPROTO_IPV6, IPV6_RECVPKTINFO,
+ &int1, sizeof(int1)) < 0) {
+ fatal("rip IPV6_RECVPKTINFO");
+ /*NOTREACHED*/
+ }
+#else /* old adv. API */
+ if (setsockopt(ripsock, IPPROTO_IPV6, IPV6_PKTINFO,
+ &int1, sizeof(int1)) < 0) {
+ fatal("rip IPV6_PKTINFO");
+ /*NOTREACHED*/
+ }
+#endif
+
+#ifdef IPV6_RECVPKTINFO
+ if (setsockopt(ripsock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT,
+ &int1, sizeof(int1)) < 0) {
+ fatal("rip IPV6_RECVHOPLIMIT");
+ /*NOTREACHED*/
+ }
+#else /* old adv. API */
+ if (setsockopt(ripsock, IPPROTO_IPV6, IPV6_HOPLIMIT,
+ &int1, sizeof(int1)) < 0) {
+ fatal("rip IPV6_HOPLIMIT");
+ /*NOTREACHED*/
+ }
+#endif
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_INET6;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ error = getaddrinfo(RIP6_DEST, port, &hints, &res);
+ if (error) {
+ fatal("%s", gai_strerror(error));
+ /*NOTREACHED*/
+ }
+ if (res->ai_next) {
+ fatal("%s resolved to multiple address", RIP6_DEST);
+ /*NOTREACHED*/
+ }
+ memcpy(&ripsin, res->ai_addr, res->ai_addrlen);
+
+#ifdef HAVE_POLL_H
+ set[0].fd = ripsock;
+ set[0].events = POLLIN;
+#else
+ maxfd = ripsock;
+#endif
+
+ if (nflag == 0) {
+ if ((rtsock = socket(PF_ROUTE, SOCK_RAW, 0)) < 0) {
+ fatal("route socket");
+ /*NOTREACHED*/
+ }
+#ifdef HAVE_POLL_H
+ set[1].fd = rtsock;
+ set[1].events = POLLIN;
+#else
+ if (rtsock > maxfd)
+ maxfd = rtsock;
+#endif
+ } else {
+#ifdef HAVE_POLL_H
+ set[1].fd = -1;
+#else
+ rtsock = -1; /*just for safety */
+#endif
+ }
+
+#ifndef HAVE_POLL_H
+ fdmasks = howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask);
+ if ((sockvecp = malloc(fdmasks)) == NULL) {
+ fatal("malloc");
+ /*NOTREACHED*/
+ }
+ if ((recvecp = malloc(fdmasks)) == NULL) {
+ fatal("malloc");
+ /*NOTREACHED*/
+ }
+ memset(sockvecp, 0, fdmasks);
+ FD_SET(ripsock, sockvecp);
+ if (rtsock >= 0)
+ FD_SET(rtsock, sockvecp);
+#endif
+}
+
+#define RIPSIZE(n) \
+ (sizeof(struct rip6) + ((n)-1) * sizeof(struct netinfo6))
+
+/*
+ * ripflush flushes the rip datagram stored in the rip buffer
+ */
+void
+ripflush(struct ifc *ifcp, struct sockaddr_in6 *sin6, int nrt, struct netinfo6 *np)
+{
+ int i;
+ int error;
+
+ if (ifcp)
+ tracet(1, "Send(%s): info(%d) to %s.%d\n",
+ ifcp->ifc_name, nrt,
+ inet6_n2p(&sin6->sin6_addr), ntohs(sin6->sin6_port));
+ else
+ tracet(1, "Send: info(%d) to %s.%d\n",
+ nrt, inet6_n2p(&sin6->sin6_addr), ntohs(sin6->sin6_port));
+ if (dflag >= 2) {
+ np = ripbuf->rip6_nets;
+ for (i = 0; i < nrt; i++, np++) {
+ if (np->rip6_metric == NEXTHOP_METRIC) {
+ if (IN6_IS_ADDR_UNSPECIFIED(&np->rip6_dest))
+ trace(2, " NextHop reset");
+ else {
+ trace(2, " NextHop %s",
+ inet6_n2p(&np->rip6_dest));
+ }
+ } else {
+ trace(2, " %s/%d[%d]",
+ inet6_n2p(&np->rip6_dest),
+ np->rip6_plen, np->rip6_metric);
+ }
+ if (np->rip6_tag) {
+ trace(2, " tag=0x%04x",
+ ntohs(np->rip6_tag) & 0xffff);
+ }
+ trace(2, "\n");
+ }
+ }
+ error = sendpacket(sin6, RIPSIZE(nrt));
+ if (error == EAFNOSUPPORT) {
+ /* Protocol not supported */
+ tracet(1, "Could not send info to %s (%s): "
+ "set IFF_UP to 0\n",
+ ifcp->ifc_name, inet6_n2p(&ifcp->ifc_ripsin.sin6_addr));
+ ifcp->ifc_flags &= ~IFF_UP; /* As if down for AF_INET6 */
+ }
+}
+
+/*
+ * Generate RIP6_RESPONSE packets and send them.
+ */
+void
+ripsend(struct ifc *ifcp, struct sockaddr_in6 *sin6, int flag)
+{
+ struct riprt *rrt;
+ struct in6_addr *nh; /* next hop */
+ struct netinfo6 *np;
+ int maxrte;
+ int nrt;
+
+ if (qflag)
+ return;
+
+ if (ifcp == NULL) {
+ /*
+ * Request from non-link local address is not
+ * a regular route6d update.
+ */
+ maxrte = (IFMINMTU - sizeof(struct ip6_hdr) -
+ sizeof(struct udphdr) -
+ sizeof(struct rip6) + sizeof(struct netinfo6)) /
+ sizeof(struct netinfo6);
+ nh = NULL;
+ nrt = 0;
+ np = ripbuf->rip6_nets;
+ TAILQ_FOREACH(rrt, &riprt_head, rrt_next) {
+ if (rrt->rrt_rflags & RRTF_NOADVERTISE)
+ continue;
+ /* Put the route to the buffer */
+ *np = rrt->rrt_info;
+ np++; nrt++;
+ if (nrt == maxrte) {
+ ripflush(NULL, sin6, nrt, np);
+ nh = NULL;
+ nrt = 0;
+ np = ripbuf->rip6_nets;
+ }
+ }
+ if (nrt) /* Send last packet */
+ ripflush(NULL, sin6, nrt, np);
+ return;
+ }
+
+ if ((flag & RRTF_SENDANYWAY) == 0 &&
+ (qflag || (ifcp->ifc_flags & IFF_LOOPBACK)))
+ return;
+
+ /* -N: no use */
+ if (iff_find(ifcp, IFIL_TYPE_N) != NULL)
+ return;
+
+ /* -T: generate default route only */
+ if (iff_find(ifcp, IFIL_TYPE_T) != NULL) {
+ struct netinfo6 rrt_info;
+ memset(&rrt_info, 0, sizeof(struct netinfo6));
+ rrt_info.rip6_dest = in6addr_any;
+ rrt_info.rip6_plen = 0;
+ rrt_info.rip6_metric = 1;
+ rrt_info.rip6_metric += ifcp->ifc_metric;
+ rrt_info.rip6_tag = htons(routetag & 0xffff);
+ np = ripbuf->rip6_nets;
+ *np = rrt_info;
+ nrt = 1;
+ ripflush(ifcp, sin6, nrt, np);
+ return;
+ }
+
+ maxrte = (ifcp->ifc_mtu - sizeof(struct ip6_hdr) -
+ sizeof(struct udphdr) -
+ sizeof(struct rip6) + sizeof(struct netinfo6)) /
+ sizeof(struct netinfo6);
+
+ nrt = 0; np = ripbuf->rip6_nets; nh = NULL;
+ TAILQ_FOREACH(rrt, &riprt_head, rrt_next) {
+ if (rrt->rrt_rflags & RRTF_NOADVERTISE)
+ continue;
+
+ /* Need to check filter here */
+ if (out_filter(rrt, ifcp) == 0)
+ continue;
+
+ /* Check split horizon and other conditions */
+ if (tobeadv(rrt, ifcp) == 0)
+ continue;
+
+ /* Only considers the routes with flag if specified */
+ if ((flag & RRTF_CHANGED) &&
+ (rrt->rrt_rflags & RRTF_CHANGED) == 0)
+ continue;
+
+ /* Check nexthop */
+ if (rrt->rrt_index == ifcp->ifc_index &&
+ !IN6_IS_ADDR_UNSPECIFIED(&rrt->rrt_gw) &&
+ (rrt->rrt_rflags & RRTF_NH_NOT_LLADDR) == 0) {
+ if (nh == NULL || !IN6_ARE_ADDR_EQUAL(nh, &rrt->rrt_gw)) {
+ if (nrt == maxrte - 2) {
+ ripflush(ifcp, sin6, nrt, np);
+ nh = NULL;
+ nrt = 0;
+ np = ripbuf->rip6_nets;
+ }
+
+ np->rip6_dest = rrt->rrt_gw;
+ np->rip6_plen = 0;
+ np->rip6_tag = 0;
+ np->rip6_metric = NEXTHOP_METRIC;
+ nh = &rrt->rrt_gw;
+ np++; nrt++;
+ }
+ } else if (nh && (rrt->rrt_index != ifcp->ifc_index ||
+ !IN6_ARE_ADDR_EQUAL(nh, &rrt->rrt_gw) ||
+ rrt->rrt_rflags & RRTF_NH_NOT_LLADDR)) {
+ /* Reset nexthop */
+ if (nrt == maxrte - 2) {
+ ripflush(ifcp, sin6, nrt, np);
+ nh = NULL;
+ nrt = 0;
+ np = ripbuf->rip6_nets;
+ }
+ memset(np, 0, sizeof(struct netinfo6));
+ np->rip6_metric = NEXTHOP_METRIC;
+ nh = NULL;
+ np++; nrt++;
+ }
+
+ /* Put the route to the buffer */
+ *np = rrt->rrt_info;
+ np++; nrt++;
+ if (nrt == maxrte) {
+ ripflush(ifcp, sin6, nrt, np);
+ nh = NULL;
+ nrt = 0;
+ np = ripbuf->rip6_nets;
+ }
+ }
+ if (nrt) /* Send last packet */
+ ripflush(ifcp, sin6, nrt, np);
+}
+
+/*
+ * outbound filter logic, per-route/interface.
+ */
+int
+out_filter(struct riprt *rrt, struct ifc *ifcp)
+{
+ struct iff *iffp;
+ struct in6_addr ia;
+ int ok;
+
+ /*
+ * -A: filter out less specific routes, if we have aggregated
+ * route configured.
+ */
+ TAILQ_FOREACH(iffp, &ifcp->ifc_iff_head, iff_next) {
+ if (iffp->iff_type != 'A')
+ continue;
+ if (rrt->rrt_info.rip6_plen <= iffp->iff_plen)
+ continue;
+ ia = rrt->rrt_info.rip6_dest;
+ applyplen(&ia, iffp->iff_plen);
+ if (IN6_ARE_ADDR_EQUAL(&ia, &iffp->iff_addr))
+ return 0;
+ }
+
+ /*
+ * if it is an aggregated route, advertise it only to the
+ * interfaces specified on -A.
+ */
+ if ((rrt->rrt_rflags & RRTF_AGGREGATE) != 0) {
+ ok = 0;
+ TAILQ_FOREACH(iffp, &ifcp->ifc_iff_head, iff_next) {
+ if (iffp->iff_type != 'A')
+ continue;
+ if (rrt->rrt_info.rip6_plen == iffp->iff_plen &&
+ IN6_ARE_ADDR_EQUAL(&rrt->rrt_info.rip6_dest,
+ &iffp->iff_addr)) {
+ ok = 1;
+ break;
+ }
+ }
+ if (!ok)
+ return 0;
+ }
+
+ /*
+ * -O: advertise only if prefix matches the configured prefix.
+ */
+ if (iff_find(ifcp, IFIL_TYPE_O) != NULL) {
+ ok = 0;
+ TAILQ_FOREACH(iffp, &ifcp->ifc_iff_head, iff_next) {
+ if (iffp->iff_type != 'O')
+ continue;
+ if (rrt->rrt_info.rip6_plen < iffp->iff_plen)
+ continue;
+ ia = rrt->rrt_info.rip6_dest;
+ applyplen(&ia, iffp->iff_plen);
+ if (IN6_ARE_ADDR_EQUAL(&ia, &iffp->iff_addr)) {
+ ok = 1;
+ break;
+ }
+ }
+ if (!ok)
+ return 0;
+ }
+
+ /* the prefix should be advertised */
+ return 1;
+}
+
+/*
+ * Determine if the route is to be advertised on the specified interface.
+ * It checks options specified in the arguments and the split horizon rule.
+ */
+int
+tobeadv(struct riprt *rrt, struct ifc *ifcp)
+{
+
+ /* Special care for static routes */
+ if (rrt->rrt_flags & RTF_STATIC) {
+ /* XXX don't advertise reject/blackhole routes */
+ if (rrt->rrt_flags & (RTF_REJECT | RTF_BLACKHOLE))
+ return 0;
+
+ if (Sflag) /* Yes, advertise it anyway */
+ return 1;
+ if (sflag && rrt->rrt_index != ifcp->ifc_index)
+ return 1;
+ return 0;
+ }
+ /* Regular split horizon */
+ if (hflag == 0 && rrt->rrt_index == ifcp->ifc_index)
+ return 0;
+ return 1;
+}
+
+/*
+ * Send a rip packet actually.
+ */
+int
+sendpacket(struct sockaddr_in6 *sin6, int len)
+{
+ struct msghdr m;
+ struct cmsghdr *cm;
+ struct iovec iov[2];
+ u_char cmsgbuf[256];
+ struct in6_pktinfo *pi;
+ int idx;
+ struct sockaddr_in6 sincopy;
+
+ /* do not overwrite the given sin */
+ sincopy = *sin6;
+ sin6 = &sincopy;
+
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) ||
+ IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr))
+ idx = sin6->sin6_scope_id;
+ else
+ idx = 0;
+
+ m.msg_name = (caddr_t)sin6;
+ m.msg_namelen = sizeof(*sin6);
+ iov[0].iov_base = (caddr_t)ripbuf;
+ iov[0].iov_len = len;
+ m.msg_iov = iov;
+ m.msg_iovlen = 1;
+ if (!idx) {
+ m.msg_control = NULL;
+ m.msg_controllen = 0;
+ } else {
+ memset(cmsgbuf, 0, sizeof(cmsgbuf));
+ cm = (struct cmsghdr *)cmsgbuf;
+ m.msg_control = (caddr_t)cm;
+ m.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo));
+
+ cm->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_PKTINFO;
+ pi = (struct in6_pktinfo *)CMSG_DATA(cm);
+ memset(&pi->ipi6_addr, 0, sizeof(pi->ipi6_addr)); /*::*/
+ pi->ipi6_ifindex = idx;
+ }
+
+ if (sendmsg(ripsock, &m, 0 /*MSG_DONTROUTE*/) < 0) {
+ trace(1, "sendmsg: %s\n", strerror(errno));
+ return errno;
+ }
+
+ return 0;
+}
+
+/*
+ * Receive and process RIP packets. Update the routes/kernel forwarding
+ * table if necessary.
+ */
+void
+riprecv(void)
+{
+ struct ifc *ifcp, *ic;
+ struct sockaddr_in6 fsock;
+ struct in6_addr nh; /* next hop */
+ struct rip6 *rp;
+ struct netinfo6 *np, *nq;
+ struct riprt *rrt;
+ ssize_t len, nn;
+ unsigned int need_trigger, idx;
+ char buf[4 * RIP6_MAXMTU];
+ time_t t;
+ struct msghdr m;
+ struct cmsghdr *cm;
+ struct iovec iov[2];
+ u_char cmsgbuf[256];
+ struct in6_pktinfo *pi = NULL;
+ int *hlimp = NULL;
+ struct iff *iffp;
+ struct in6_addr ia;
+ int ok;
+ time_t t_half_lifetime;
+
+ need_trigger = 0;
+
+ m.msg_name = (caddr_t)&fsock;
+ m.msg_namelen = sizeof(fsock);
+ iov[0].iov_base = (caddr_t)buf;
+ iov[0].iov_len = sizeof(buf);
+ m.msg_iov = iov;
+ m.msg_iovlen = 1;
+ cm = (struct cmsghdr *)cmsgbuf;
+ m.msg_control = (caddr_t)cm;
+ m.msg_controllen = sizeof(cmsgbuf);
+ if ((len = recvmsg(ripsock, &m, 0)) < 0) {
+ fatal("recvmsg");
+ /*NOTREACHED*/
+ }
+ idx = 0;
+ for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&m);
+ cm;
+ cm = (struct cmsghdr *)CMSG_NXTHDR(&m, cm)) {
+ if (cm->cmsg_level != IPPROTO_IPV6)
+ continue;
+ switch (cm->cmsg_type) {
+ case IPV6_PKTINFO:
+ if (cm->cmsg_len != CMSG_LEN(sizeof(*pi))) {
+ trace(1,
+ "invalid cmsg length for IPV6_PKTINFO\n");
+ return;
+ }
+ pi = (struct in6_pktinfo *)(CMSG_DATA(cm));
+ idx = pi->ipi6_ifindex;
+ break;
+ case IPV6_HOPLIMIT:
+ if (cm->cmsg_len != CMSG_LEN(sizeof(int))) {
+ trace(1,
+ "invalid cmsg length for IPV6_HOPLIMIT\n");
+ return;
+ }
+ hlimp = (int *)CMSG_DATA(cm);
+ break;
+ }
+ }
+
+ if ((size_t)len < sizeof(struct rip6)) {
+ trace(1, "Packet too short\n");
+ return;
+ }
+
+ if (pi == NULL || hlimp == NULL) {
+ /*
+ * This can happen when the kernel failed to allocate memory
+ * for the ancillary data. Although we might be able to handle
+ * some cases without this info, those are minor and not so
+ * important, so it's better to discard the packet for safer
+ * operation.
+ */
+ trace(1, "IPv6 packet information cannot be retrieved\n");
+ return;
+ }
+
+ nh = fsock.sin6_addr;
+ nn = (len - sizeof(struct rip6) + sizeof(struct netinfo6)) /
+ sizeof(struct netinfo6);
+ rp = (struct rip6 *)buf;
+ np = rp->rip6_nets;
+
+ if (rp->rip6_vers != RIP6_VERSION) {
+ trace(1, "Incorrect RIP version %d\n", rp->rip6_vers);
+ return;
+ }
+ if (rp->rip6_cmd == RIP6_REQUEST) {
+ if (idx && idx < nindex2ifc) {
+ ifcp = index2ifc[idx];
+ riprequest(ifcp, np, nn, &fsock);
+ } else {
+ riprequest(NULL, np, nn, &fsock);
+ }
+ return;
+ }
+
+ if (!IN6_IS_ADDR_LINKLOCAL(&fsock.sin6_addr)) {
+ trace(1, "Response from non-ll addr: %s\n",
+ inet6_n2p(&fsock.sin6_addr));
+ return; /* Ignore packets from non-link-local addr */
+ }
+ if (ntohs(fsock.sin6_port) != RIP6_PORT) {
+ trace(1, "Response from non-rip port from %s\n",
+ inet6_n2p(&fsock.sin6_addr));
+ return;
+ }
+ if (IN6_IS_ADDR_MULTICAST(&pi->ipi6_addr) && *hlimp != 255) {
+ trace(1,
+ "Response packet with a smaller hop limit (%d) from %s\n",
+ *hlimp, inet6_n2p(&fsock.sin6_addr));
+ return;
+ }
+ /*
+ * Further validation: since this program does not send off-link
+ * requests, an incoming response must always come from an on-link
+ * node. Although this is normally ensured by the source address
+ * check above, it may not 100% be safe because there are router
+ * implementations that (invalidly) allow a packet with a link-local
+ * source address to be forwarded to a different link.
+ * So we also check whether the destination address is a link-local
+ * address or the hop limit is 255. Note that RFC2080 does not require
+ * the specific hop limit for a unicast response, so we cannot assume
+ * the limitation.
+ */
+ if (!IN6_IS_ADDR_LINKLOCAL(&pi->ipi6_addr) && *hlimp != 255) {
+ trace(1,
+ "Response packet possibly from an off-link node: "
+ "from %s to %s hlim=%d\n",
+ inet6_n2p(&fsock.sin6_addr),
+ inet6_n2p(&pi->ipi6_addr), *hlimp);
+ return;
+ }
+
+ idx = fsock.sin6_scope_id;
+ ifcp = (idx < nindex2ifc) ? index2ifc[idx] : NULL;
+ if (!ifcp) {
+ trace(1, "Packets to unknown interface index %d\n", idx);
+ return; /* Ignore it */
+ }
+ if (IN6_ARE_ADDR_EQUAL(&ifcp->ifc_mylladdr, &fsock.sin6_addr))
+ return; /* The packet is from me; ignore */
+ if (rp->rip6_cmd != RIP6_RESPONSE) {
+ trace(1, "Invalid command %d\n", rp->rip6_cmd);
+ return;
+ }
+
+ /* -N: no use */
+ if (iff_find(ifcp, IFIL_TYPE_N) != NULL)
+ return;
+
+ tracet(1, "Recv(%s): from %s.%d info(%zd)\n",
+ ifcp->ifc_name, inet6_n2p(&nh), ntohs(fsock.sin6_port), nn);
+
+ t = time(NULL);
+ t_half_lifetime = t - (RIP_LIFETIME/2);
+ for (; nn; nn--, np++) {
+ if (np->rip6_metric == NEXTHOP_METRIC) {
+ /* modify neighbor address */
+ if (IN6_IS_ADDR_LINKLOCAL(&np->rip6_dest)) {
+ nh = np->rip6_dest;
+ trace(1, "\tNexthop: %s\n", inet6_n2p(&nh));
+ } else if (IN6_IS_ADDR_UNSPECIFIED(&np->rip6_dest)) {
+ nh = fsock.sin6_addr;
+ trace(1, "\tNexthop: %s\n", inet6_n2p(&nh));
+ } else {
+ nh = fsock.sin6_addr;
+ trace(1, "\tInvalid Nexthop: %s\n",
+ inet6_n2p(&np->rip6_dest));
+ }
+ continue;
+ }
+ if (IN6_IS_ADDR_MULTICAST(&np->rip6_dest)) {
+ trace(1, "\tMulticast netinfo6: %s/%d [%d]\n",
+ inet6_n2p(&np->rip6_dest),
+ np->rip6_plen, np->rip6_metric);
+ continue;
+ }
+ if (IN6_IS_ADDR_LOOPBACK(&np->rip6_dest)) {
+ trace(1, "\tLoopback netinfo6: %s/%d [%d]\n",
+ inet6_n2p(&np->rip6_dest),
+ np->rip6_plen, np->rip6_metric);
+ continue;
+ }
+ if (IN6_IS_ADDR_LINKLOCAL(&np->rip6_dest)) {
+ trace(1, "\tLink Local netinfo6: %s/%d [%d]\n",
+ inet6_n2p(&np->rip6_dest),
+ np->rip6_plen, np->rip6_metric);
+ continue;
+ }
+ /* may need to pass sitelocal prefix in some case, however*/
+ if (IN6_IS_ADDR_SITELOCAL(&np->rip6_dest) && !lflag) {
+ trace(1, "\tSite Local netinfo6: %s/%d [%d]\n",
+ inet6_n2p(&np->rip6_dest),
+ np->rip6_plen, np->rip6_metric);
+ continue;
+ }
+ trace(2, "\tnetinfo6: %s/%d [%d]",
+ inet6_n2p(&np->rip6_dest),
+ np->rip6_plen, np->rip6_metric);
+ if (np->rip6_tag)
+ trace(2, " tag=0x%04x", ntohs(np->rip6_tag) & 0xffff);
+ if (dflag >= 2) {
+ ia = np->rip6_dest;
+ applyplen(&ia, np->rip6_plen);
+ if (!IN6_ARE_ADDR_EQUAL(&ia, &np->rip6_dest))
+ trace(2, " [junk outside prefix]");
+ }
+
+ /*
+ * -L: listen only if the prefix matches the configuration
+ */
+ ok = 1; /* if there's no L filter, it is ok */
+ TAILQ_FOREACH(iffp, &ifcp->ifc_iff_head, iff_next) {
+ if (iffp->iff_type != IFIL_TYPE_L)
+ continue;
+ ok = 0;
+ if (np->rip6_plen < iffp->iff_plen)
+ continue;
+ /* special rule: ::/0 means default, not "in /0" */
+ if (iffp->iff_plen == 0 && np->rip6_plen > 0)
+ continue;
+ ia = np->rip6_dest;
+ applyplen(&ia, iffp->iff_plen);
+ if (IN6_ARE_ADDR_EQUAL(&ia, &iffp->iff_addr)) {
+ ok = 1;
+ break;
+ }
+ }
+ if (!ok) {
+ trace(2, " (filtered)\n");
+ continue;
+ }
+
+ trace(2, "\n");
+ np->rip6_metric++;
+ np->rip6_metric += ifcp->ifc_metric;
+ if (np->rip6_metric > HOPCNT_INFINITY6)
+ np->rip6_metric = HOPCNT_INFINITY6;
+
+ applyplen(&np->rip6_dest, np->rip6_plen);
+ if ((rrt = rtsearch(np)) != NULL) {
+ if (rrt->rrt_t == 0)
+ continue; /* Intf route has priority */
+ nq = &rrt->rrt_info;
+ if (nq->rip6_metric > np->rip6_metric) {
+ if (rrt->rrt_index == ifcp->ifc_index &&
+ IN6_ARE_ADDR_EQUAL(&nh, &rrt->rrt_gw)) {
+ /* Small metric from the same gateway */
+ nq->rip6_metric = np->rip6_metric;
+ } else {
+ /* Better route found */
+ rrt->rrt_index = ifcp->ifc_index;
+ /* Update routing table */
+ delroute(nq, &rrt->rrt_gw);
+ rrt->rrt_gw = nh;
+ *nq = *np;
+ addroute(rrt, &nh, ifcp);
+ }
+ rrt->rrt_rflags |= RRTF_CHANGED;
+ rrt->rrt_t = t;
+ need_trigger = 1;
+ } else if (nq->rip6_metric < np->rip6_metric &&
+ rrt->rrt_index == ifcp->ifc_index &&
+ IN6_ARE_ADDR_EQUAL(&nh, &rrt->rrt_gw)) {
+ /* Got worse route from same gw */
+ nq->rip6_metric = np->rip6_metric;
+ rrt->rrt_t = t;
+ rrt->rrt_rflags |= RRTF_CHANGED;
+ need_trigger = 1;
+ } else if (nq->rip6_metric == np->rip6_metric &&
+ np->rip6_metric < HOPCNT_INFINITY6) {
+ if (rrt->rrt_index == ifcp->ifc_index &&
+ IN6_ARE_ADDR_EQUAL(&nh, &rrt->rrt_gw)) {
+ /* same metric, same route from same gw */
+ rrt->rrt_t = t;
+ } else if (rrt->rrt_t < t_half_lifetime) {
+ /* Better route found */
+ rrt->rrt_index = ifcp->ifc_index;
+ /* Update routing table */
+ delroute(nq, &rrt->rrt_gw);
+ rrt->rrt_gw = nh;
+ *nq = *np;
+ addroute(rrt, &nh, ifcp);
+ rrt->rrt_rflags |= RRTF_CHANGED;
+ rrt->rrt_t = t;
+ }
+ }
+ /*
+ * if nq->rip6_metric == HOPCNT_INFINITY6 then
+ * do not update age value. Do nothing.
+ */
+ } else if (np->rip6_metric < HOPCNT_INFINITY6) {
+ /* Got a new valid route */
+ if ((rrt = MALLOC(struct riprt)) == NULL) {
+ fatal("malloc: struct riprt");
+ /*NOTREACHED*/
+ }
+ memset(rrt, 0, sizeof(*rrt));
+ nq = &rrt->rrt_info;
+
+ rrt->rrt_same = NULL;
+ rrt->rrt_index = ifcp->ifc_index;
+ rrt->rrt_flags = RTF_UP|RTF_GATEWAY;
+ rrt->rrt_gw = nh;
+ *nq = *np;
+ applyplen(&nq->rip6_dest, nq->rip6_plen);
+ if (nq->rip6_plen == sizeof(struct in6_addr) * 8)
+ rrt->rrt_flags |= RTF_HOST;
+
+ /* Update routing table */
+ addroute(rrt, &nh, ifcp);
+ rrt->rrt_rflags |= RRTF_CHANGED;
+ need_trigger = 1;
+ rrt->rrt_t = t;
+
+ /* Put the route to the list */
+ TAILQ_INSERT_HEAD(&riprt_head, rrt, rrt_next);
+ }
+ }
+ /* XXX need to care the interval between triggered updates */
+ if (need_trigger) {
+ if (nextalarm > time(NULL) + RIP_TRIG_INT6_MAX) {
+ TAILQ_FOREACH(ic, &ifc_head, ifc_next) {
+ if (ifcp->ifc_index == ic->ifc_index)
+ continue;
+ if (ic->ifc_flags & IFF_UP)
+ ripsend(ic, &ic->ifc_ripsin,
+ RRTF_CHANGED);
+ }
+ }
+ /* Reset the flag */
+ TAILQ_FOREACH(rrt, &riprt_head, rrt_next) {
+ rrt->rrt_rflags &= ~RRTF_CHANGED;
+ }
+ }
+}
+
+/*
+ * Send all routes request packet to the specified interface.
+ */
+void
+sendrequest(struct ifc *ifcp)
+{
+ struct netinfo6 *np;
+ int error;
+
+ if (ifcp->ifc_flags & IFF_LOOPBACK)
+ return;
+ ripbuf->rip6_cmd = RIP6_REQUEST;
+ np = ripbuf->rip6_nets;
+ memset(np, 0, sizeof(struct netinfo6));
+ np->rip6_metric = HOPCNT_INFINITY6;
+ tracet(1, "Send rtdump Request to %s (%s)\n",
+ ifcp->ifc_name, inet6_n2p(&ifcp->ifc_ripsin.sin6_addr));
+ error = sendpacket(&ifcp->ifc_ripsin, RIPSIZE(1));
+ if (error == EAFNOSUPPORT) {
+ /* Protocol not supported */
+ tracet(1, "Could not send rtdump Request to %s (%s): "
+ "set IFF_UP to 0\n",
+ ifcp->ifc_name, inet6_n2p(&ifcp->ifc_ripsin.sin6_addr));
+ ifcp->ifc_flags &= ~IFF_UP; /* As if down for AF_INET6 */
+ }
+ ripbuf->rip6_cmd = RIP6_RESPONSE;
+}
+
+/*
+ * Process a RIP6_REQUEST packet.
+ */
+void
+riprequest(struct ifc *ifcp,
+ struct netinfo6 *np,
+ int nn,
+ struct sockaddr_in6 *sin6)
+{
+ int i;
+ struct riprt *rrt;
+
+ if (!(nn == 1 && IN6_IS_ADDR_UNSPECIFIED(&np->rip6_dest) &&
+ np->rip6_plen == 0 && np->rip6_metric == HOPCNT_INFINITY6)) {
+ /* Specific response, don't split-horizon */
+ trace(1, "\tRIP Request\n");
+ for (i = 0; i < nn; i++, np++) {
+ rrt = rtsearch(np);
+ if (rrt)
+ np->rip6_metric = rrt->rrt_info.rip6_metric;
+ else
+ np->rip6_metric = HOPCNT_INFINITY6;
+ }
+ (void)sendpacket(sin6, RIPSIZE(nn));
+ return;
+ }
+ /* Whole routing table dump */
+ trace(1, "\tRIP Request -- whole routing table\n");
+ ripsend(ifcp, sin6, RRTF_SENDANYWAY);
+}
+
+/*
+ * Get information of each interface.
+ */
+void
+ifconfig(void)
+{
+ struct ifaddrs *ifap, *ifa;
+ struct ifc *ifcp;
+ struct ipv6_mreq mreq;
+ int s;
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
+ fatal("socket");
+ /*NOTREACHED*/
+ }
+
+ if (getifaddrs(&ifap) != 0) {
+ fatal("getifaddrs");
+ /*NOTREACHED*/
+ }
+
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr->sa_family != AF_INET6)
+ continue;
+ ifcp = ifc_find(ifa->ifa_name);
+ /* we are interested in multicast-capable interfaces */
+ if ((ifa->ifa_flags & IFF_MULTICAST) == 0)
+ continue;
+ if (!ifcp) {
+ /* new interface */
+ if ((ifcp = MALLOC(struct ifc)) == NULL) {
+ fatal("malloc: struct ifc");
+ /*NOTREACHED*/
+ }
+ memset(ifcp, 0, sizeof(*ifcp));
+
+ ifcp->ifc_index = -1;
+ strlcpy(ifcp->ifc_name, ifa->ifa_name,
+ sizeof(ifcp->ifc_name));
+ TAILQ_INIT(&ifcp->ifc_ifac_head);
+ TAILQ_INIT(&ifcp->ifc_iff_head);
+ ifcp->ifc_flags = ifa->ifa_flags;
+ TAILQ_INSERT_HEAD(&ifc_head, ifcp, ifc_next);
+ trace(1, "newif %s <%s>\n", ifcp->ifc_name,
+ ifflags(ifcp->ifc_flags));
+ if (!strcmp(ifcp->ifc_name, LOOPBACK_IF))
+ loopifcp = ifcp;
+ } else {
+ /* update flag, this may be up again */
+ if (ifcp->ifc_flags != ifa->ifa_flags) {
+ trace(1, "%s: <%s> -> ", ifcp->ifc_name,
+ ifflags(ifcp->ifc_flags));
+ trace(1, "<%s>\n", ifflags(ifa->ifa_flags));
+ ifcp->ifc_cflags |= IFC_CHANGED;
+ }
+ ifcp->ifc_flags = ifa->ifa_flags;
+ }
+ if (ifconfig1(ifa->ifa_name, ifa->ifa_addr, ifcp, s) < 0) {
+ /* maybe temporary failure */
+ continue;
+ }
+ if ((ifcp->ifc_flags & (IFF_LOOPBACK | IFF_UP)) == IFF_UP
+ && 0 < ifcp->ifc_index && !ifcp->ifc_joined) {
+ mreq.ipv6mr_multiaddr = ifcp->ifc_ripsin.sin6_addr;
+ mreq.ipv6mr_interface = ifcp->ifc_index;
+ if (setsockopt(ripsock, IPPROTO_IPV6, IPV6_JOIN_GROUP,
+ &mreq, sizeof(mreq)) < 0) {
+ fatal("IPV6_JOIN_GROUP");
+ /*NOTREACHED*/
+ }
+ trace(1, "join %s %s\n", ifcp->ifc_name, RIP6_DEST);
+ ifcp->ifc_joined++;
+ }
+ }
+ close(s);
+ freeifaddrs(ifap);
+}
+
+int
+ifconfig1(const char *name,
+ const struct sockaddr *sa,
+ struct ifc *ifcp,
+ int s)
+{
+ struct in6_ifreq ifr;
+ const struct sockaddr_in6 *sin6;
+ struct ifac *ifac;
+ int plen;
+ char buf[BUFSIZ];
+
+ sin6 = (const struct sockaddr_in6 *)sa;
+ if (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr) && !lflag)
+ return (-1);
+ ifr.ifr_addr = *sin6;
+ strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+ if (ioctl(s, SIOCGIFNETMASK_IN6, (char *)&ifr) < 0) {
+ syslog(LOG_INFO, "ioctl: SIOCGIFNETMASK_IN6");
+ return (-1);
+ }
+ plen = sin6mask2len(&ifr.ifr_addr);
+ if ((ifac = ifa_match(ifcp, &sin6->sin6_addr, plen)) != NULL) {
+ /* same interface found */
+ /* need check if something changed */
+ /* XXX not yet implemented */
+ return (-1);
+ }
+ /*
+ * New address is found
+ */
+ if ((ifac = MALLOC(struct ifac)) == NULL) {
+ fatal("malloc: struct ifac");
+ /*NOTREACHED*/
+ }
+ memset(ifac, 0, sizeof(*ifac));
+
+ ifac->ifac_ifc = ifcp;
+ ifac->ifac_addr = sin6->sin6_addr;
+ ifac->ifac_plen = plen;
+ ifac->ifac_scope_id = sin6->sin6_scope_id;
+ if (ifcp->ifc_flags & IFF_POINTOPOINT) {
+ ifr.ifr_addr = *sin6;
+ if (ioctl(s, SIOCGIFDSTADDR_IN6, (char *)&ifr) < 0) {
+ fatal("ioctl: SIOCGIFDSTADDR_IN6");
+ /*NOTREACHED*/
+ }
+ ifac->ifac_raddr = ifr.ifr_dstaddr.sin6_addr;
+ inet_ntop(AF_INET6, (void *)&ifac->ifac_raddr, buf,
+ sizeof(buf));
+ trace(1, "found address %s/%d -- %s\n",
+ inet6_n2p(&ifac->ifac_addr), ifac->ifac_plen, buf);
+ } else {
+ trace(1, "found address %s/%d\n",
+ inet6_n2p(&ifac->ifac_addr), ifac->ifac_plen);
+ }
+ if (ifcp->ifc_index < 0 && IN6_IS_ADDR_LINKLOCAL(&ifac->ifac_addr)) {
+ ifcp->ifc_mylladdr = ifac->ifac_addr;
+ ifcp->ifc_index = ifac->ifac_scope_id;
+ memcpy(&ifcp->ifc_ripsin, &ripsin, ripsin.ss_len);
+ ifcp->ifc_ripsin.sin6_scope_id = ifcp->ifc_index;
+ setindex2ifc(ifcp->ifc_index, ifcp);
+ ifcp->ifc_mtu = getifmtu(ifcp->ifc_index);
+ if (ifcp->ifc_mtu > RIP6_MAXMTU)
+ ifcp->ifc_mtu = RIP6_MAXMTU;
+ if (ioctl(s, SIOCGIFMETRIC, (char *)&ifr) < 0) {
+ fatal("ioctl: SIOCGIFMETRIC");
+ /*NOTREACHED*/
+ }
+ ifcp->ifc_metric = ifr.ifr_metric;
+ trace(1, "\tindex: %d, mtu: %d, metric: %d\n",
+ ifcp->ifc_index, ifcp->ifc_mtu, ifcp->ifc_metric);
+ } else
+ ifcp->ifc_cflags |= IFC_CHANGED;
+
+ TAILQ_INSERT_HEAD(&ifcp->ifc_ifac_head, ifac, ifac_next);
+
+ return 0;
+}
+
+void
+ifremove(int ifindex)
+{
+ struct ifc *ifcp;
+ struct riprt *rrt;
+
+ TAILQ_FOREACH(ifcp, &ifc_head, ifc_next) {
+ if (ifcp->ifc_index == ifindex)
+ break;
+ }
+ if (ifcp == NULL)
+ return;
+
+ tracet(1, "ifremove: %s is departed.\n", ifcp->ifc_name);
+ TAILQ_REMOVE(&ifc_head, ifcp, ifc_next);
+
+ TAILQ_FOREACH(rrt, &riprt_head, rrt_next) {
+ if (rrt->rrt_index == ifcp->ifc_index &&
+ rrt->rrt_rflags & RRTF_AGGREGATE)
+ delroute(&rrt->rrt_info, &rrt->rrt_gw);
+ }
+ free(ifcp);
+}
+
+/*
+ * Receive and process routing messages.
+ * Update interface information as necesssary.
+ */
+void
+rtrecv(void)
+{
+ char buf[BUFSIZ];
+ char *p, *q = NULL;
+ struct rt_msghdr *rtm;
+ struct ifa_msghdr *ifam;
+ struct if_msghdr *ifm;
+ struct if_announcemsghdr *ifan;
+ int len;
+ struct ifc *ifcp, *ic;
+ int iface = 0, rtable = 0;
+ struct sockaddr_in6 *rta[RTAX_MAX];
+ struct sockaddr_in6 mask;
+ int i, addrs = 0;
+ struct riprt *rrt;
+
+ if ((len = read(rtsock, buf, sizeof(buf))) < 0) {
+ perror("read from rtsock");
+ exit(1);
+ }
+ if (len == 0)
+ return;
+#if 0
+ if (len < sizeof(*rtm)) {
+ trace(1, "short read from rtsock: %d (should be > %lu)\n",
+ len, (u_long)sizeof(*rtm));
+ return;
+ }
+#endif
+ if (dflag >= 2) {
+ fprintf(stderr, "rtmsg:\n");
+ for (i = 0; i < len; i++) {
+ fprintf(stderr, "%02x ", buf[i] & 0xff);
+ if (i % 16 == 15) fprintf(stderr, "\n");
+ }
+ fprintf(stderr, "\n");
+ }
+
+ for (p = buf; p - buf < len; p += ((struct rt_msghdr *)p)->rtm_msglen) {
+ if (((struct rt_msghdr *)p)->rtm_version != RTM_VERSION)
+ continue;
+
+ /* safety against bogus message */
+ if (((struct rt_msghdr *)p)->rtm_msglen <= 0) {
+ trace(1, "bogus rtmsg: length=%d\n",
+ ((struct rt_msghdr *)p)->rtm_msglen);
+ break;
+ }
+ rtm = NULL;
+ ifam = NULL;
+ ifm = NULL;
+ switch (((struct rt_msghdr *)p)->rtm_type) {
+ case RTM_NEWADDR:
+ case RTM_DELADDR:
+ ifam = (struct ifa_msghdr *)p;
+ addrs = ifam->ifam_addrs;
+ q = (char *)(ifam + 1);
+ break;
+ case RTM_IFINFO:
+ ifm = (struct if_msghdr *)p;
+ addrs = ifm->ifm_addrs;
+ q = (char *)(ifm + 1);
+ break;
+ case RTM_IFANNOUNCE:
+ ifan = (struct if_announcemsghdr *)p;
+ switch (ifan->ifan_what) {
+ case IFAN_ARRIVAL:
+ iface++;
+ break;
+ case IFAN_DEPARTURE:
+ ifremove(ifan->ifan_index);
+ iface++;
+ break;
+ }
+ break;
+ default:
+ rtm = (struct rt_msghdr *)p;
+ addrs = rtm->rtm_addrs;
+ q = (char *)(rtm + 1);
+ if (rtm->rtm_version != RTM_VERSION) {
+ trace(1, "unexpected rtmsg version %d "
+ "(should be %d)\n",
+ rtm->rtm_version, RTM_VERSION);
+ continue;
+ }
+ if (rtm->rtm_pid == pid) {
+#if 0
+ trace(1, "rtmsg looped back to me, ignored\n");
+#endif
+ continue;
+ }
+ break;
+ }
+ memset(&rta, 0, sizeof(rta));
+ for (i = 0; i < RTAX_MAX; i++) {
+ if (addrs & (1 << i)) {
+ rta[i] = (struct sockaddr_in6 *)q;
+ q += ROUNDUP(rta[i]->sin6_len);
+ }
+ }
+
+ trace(1, "rtsock: %s (addrs=%x)\n",
+ rttypes((struct rt_msghdr *)p), addrs);
+ if (dflag >= 2) {
+ for (i = 0;
+ i < ((struct rt_msghdr *)p)->rtm_msglen;
+ i++) {
+ fprintf(stderr, "%02x ", p[i] & 0xff);
+ if (i % 16 == 15) fprintf(stderr, "\n");
+ }
+ fprintf(stderr, "\n");
+ }
+
+ /*
+ * Easy ones first.
+ *
+ * We may be able to optimize by using ifm->ifm_index or
+ * ifam->ifam_index. For simplicity we don't do that here.
+ */
+ switch (((struct rt_msghdr *)p)->rtm_type) {
+ case RTM_NEWADDR:
+ case RTM_IFINFO:
+ iface++;
+ continue;
+ case RTM_ADD:
+ rtable++;
+ continue;
+ case RTM_LOSING:
+ case RTM_MISS:
+ case RTM_GET:
+ case RTM_LOCK:
+ /* nothing to be done here */
+ trace(1, "\tnothing to be done, ignored\n");
+ continue;
+ }
+
+#if 0
+ if (rta[RTAX_DST] == NULL) {
+ trace(1, "\tno destination, ignored\n");
+ continue;
+ }
+ if (rta[RTAX_DST]->sin6_family != AF_INET6) {
+ trace(1, "\taf mismatch, ignored\n");
+ continue;
+ }
+ if (IN6_IS_ADDR_LINKLOCAL(&rta[RTAX_DST]->sin6_addr)) {
+ trace(1, "\tlinklocal destination, ignored\n");
+ continue;
+ }
+ if (IN6_ARE_ADDR_EQUAL(&rta[RTAX_DST]->sin6_addr, &in6addr_loopback)) {
+ trace(1, "\tloopback destination, ignored\n");
+ continue; /* Loopback */
+ }
+ if (IN6_IS_ADDR_MULTICAST(&rta[RTAX_DST]->sin6_addr)) {
+ trace(1, "\tmulticast destination, ignored\n");
+ continue;
+ }
+#endif
+
+ /* hard ones */
+ switch (((struct rt_msghdr *)p)->rtm_type) {
+ case RTM_NEWADDR:
+ case RTM_IFINFO:
+ case RTM_ADD:
+ case RTM_LOSING:
+ case RTM_MISS:
+ case RTM_GET:
+ case RTM_LOCK:
+ /* should already be handled */
+ fatal("rtrecv: never reach here");
+ /*NOTREACHED*/
+ case RTM_DELETE:
+ if (!rta[RTAX_DST] || !rta[RTAX_GATEWAY]) {
+ trace(1, "\tsome of dst/gw/netamsk are "
+ "unavailable, ignored\n");
+ break;
+ }
+ if ((rtm->rtm_flags & RTF_HOST) != 0) {
+ mask.sin6_len = sizeof(mask);
+ memset(&mask.sin6_addr, 0xff,
+ sizeof(mask.sin6_addr));
+ rta[RTAX_NETMASK] = &mask;
+ } else if (!rta[RTAX_NETMASK]) {
+ trace(1, "\tsome of dst/gw/netamsk are "
+ "unavailable, ignored\n");
+ break;
+ }
+ if (rt_del(rta[RTAX_DST], rta[RTAX_GATEWAY],
+ rta[RTAX_NETMASK]) == 0) {
+ rtable++; /*just to be sure*/
+ }
+ break;
+ case RTM_CHANGE:
+ case RTM_REDIRECT:
+ trace(1, "\tnot supported yet, ignored\n");
+ break;
+ case RTM_DELADDR:
+ if (!rta[RTAX_NETMASK] || !rta[RTAX_IFA]) {
+ trace(1, "\tno netmask or ifa given, ignored\n");
+ break;
+ }
+ if (ifam->ifam_index < nindex2ifc)
+ ifcp = index2ifc[ifam->ifam_index];
+ else
+ ifcp = NULL;
+ if (!ifcp) {
+ trace(1, "\tinvalid ifam_index %d, ignored\n",
+ ifam->ifam_index);
+ break;
+ }
+ if (!rt_deladdr(ifcp, rta[RTAX_IFA], rta[RTAX_NETMASK]))
+ iface++;
+ break;
+ }
+
+ }
+
+ if (iface) {
+ trace(1, "rtsock: reconfigure interfaces, refresh interface routes\n");
+ ifconfig();
+ TAILQ_FOREACH(ifcp, &ifc_head, ifc_next) {
+ if (ifcp->ifc_cflags & IFC_CHANGED) {
+ if (ifrt(ifcp, 1)) {
+ TAILQ_FOREACH(ic, &ifc_head, ifc_next) {
+ if (ifcp->ifc_index == ic->ifc_index)
+ continue;
+ if (ic->ifc_flags & IFF_UP)
+ ripsend(ic, &ic->ifc_ripsin,
+ RRTF_CHANGED);
+ }
+ /* Reset the flag */
+ TAILQ_FOREACH(rrt, &riprt_head, rrt_next) {
+ rrt->rrt_rflags &= ~RRTF_CHANGED;
+ }
+ }
+ ifcp->ifc_cflags &= ~IFC_CHANGED;
+ }
+ }
+ }
+ if (rtable) {
+ trace(1, "rtsock: read routing table again\n");
+ krtread(1);
+ }
+}
+
+/*
+ * remove specified route from the internal routing table.
+ */
+int
+rt_del(const struct sockaddr_in6 *sdst,
+ const struct sockaddr_in6 *sgw,
+ const struct sockaddr_in6 *smask)
+{
+ const struct in6_addr *dst = NULL;
+ const struct in6_addr *gw = NULL;
+ int prefix;
+ struct netinfo6 ni6;
+ struct riprt *rrt = NULL;
+ time_t t_lifetime;
+
+ if (sdst->sin6_family != AF_INET6) {
+ trace(1, "\tother AF, ignored\n");
+ return -1;
+ }
+ if (IN6_IS_ADDR_LINKLOCAL(&sdst->sin6_addr)
+ || IN6_ARE_ADDR_EQUAL(&sdst->sin6_addr, &in6addr_loopback)
+ || IN6_IS_ADDR_MULTICAST(&sdst->sin6_addr)) {
+ trace(1, "\taddress %s not interesting, ignored\n",
+ inet6_n2p(&sdst->sin6_addr));
+ return -1;
+ }
+ dst = &sdst->sin6_addr;
+ if (sgw->sin6_family == AF_INET6) {
+ /* easy case */
+ gw = &sgw->sin6_addr;
+ prefix = sin6mask2len(smask);
+ } else if (sgw->sin6_family == AF_LINK) {
+ /*
+ * Interface route... a hard case. We need to get the prefix
+ * length from the kernel, but we now are parsing rtmsg.
+ * We'll purge matching routes from my list, then get the
+ * fresh list.
+ */
+ struct riprt *longest;
+ trace(1, "\t%s is an interface route, guessing prefixlen\n",
+ inet6_n2p(dst));
+ longest = NULL;
+ TAILQ_FOREACH(rrt, &riprt_head, rrt_next) {
+ if (IN6_ARE_ADDR_EQUAL(&rrt->rrt_info.rip6_dest,
+ &sdst->sin6_addr)
+ && IN6_IS_ADDR_LOOPBACK(&rrt->rrt_gw)) {
+ if (!longest
+ || longest->rrt_info.rip6_plen <
+ rrt->rrt_info.rip6_plen) {
+ longest = rrt;
+ }
+ }
+ }
+ rrt = longest;
+ if (!rrt) {
+ trace(1, "\tno matching interface route found\n");
+ return -1;
+ }
+ gw = &in6addr_loopback;
+ prefix = rrt->rrt_info.rip6_plen;
+ } else {
+ trace(1, "\tunsupported af: (gw=%d)\n", sgw->sin6_family);
+ return -1;
+ }
+
+ trace(1, "\tdeleting %s/%d ", inet6_n2p(dst), prefix);
+ trace(1, "gw %s\n", inet6_n2p(gw));
+ t_lifetime = time(NULL) - RIP_LIFETIME;
+ /* age route for interface address */
+ memset(&ni6, 0, sizeof(ni6));
+ ni6.rip6_dest = *dst;
+ ni6.rip6_plen = prefix;
+ applyplen(&ni6.rip6_dest, ni6.rip6_plen); /*to be sure*/
+ trace(1, "\tfind route %s/%d\n", inet6_n2p(&ni6.rip6_dest),
+ ni6.rip6_plen);
+ if (!rrt && (rrt = rtsearch(&ni6)) == NULL) {
+ trace(1, "\tno route found\n");
+ return -1;
+ }
+#if 0
+ if ((rrt->rrt_flags & RTF_STATIC) == 0) {
+ trace(1, "\tyou can delete static routes only\n");
+ } else
+#endif
+ if (!IN6_ARE_ADDR_EQUAL(&rrt->rrt_gw, gw)) {
+ trace(1, "\tgw mismatch: %s <-> ",
+ inet6_n2p(&rrt->rrt_gw));
+ trace(1, "%s\n", inet6_n2p(gw));
+ } else {
+ trace(1, "\troute found, age it\n");
+ if (rrt->rrt_t == 0 || rrt->rrt_t > t_lifetime) {
+ rrt->rrt_t = t_lifetime;
+ rrt->rrt_info.rip6_metric = HOPCNT_INFINITY6;
+ }
+ }
+ return 0;
+}
+
+/*
+ * remove specified address from internal interface/routing table.
+ */
+int
+rt_deladdr(struct ifc *ifcp,
+ const struct sockaddr_in6 *sifa,
+ const struct sockaddr_in6 *smask)
+{
+ const struct in6_addr *addr = NULL;
+ int prefix;
+ struct ifac *ifac = NULL;
+ struct netinfo6 ni6;
+ struct riprt *rrt = NULL;
+ time_t t_lifetime;
+ int updated = 0;
+
+ if (sifa->sin6_family != AF_INET6) {
+ trace(1, "\tother AF, ignored\n");
+ return -1;
+ }
+ addr = &sifa->sin6_addr;
+ prefix = sin6mask2len(smask);
+
+ trace(1, "\tdeleting %s/%d from %s\n",
+ inet6_n2p(addr), prefix, ifcp->ifc_name);
+ ifac = ifa_match(ifcp, addr, prefix);
+ if (!ifac) {
+ trace(1, "\tno matching ifa found for %s/%d on %s\n",
+ inet6_n2p(addr), prefix, ifcp->ifc_name);
+ return -1;
+ }
+ if (ifac->ifac_ifc != ifcp) {
+ trace(1, "\taddress table corrupt: back pointer does not match "
+ "(%s != %s)\n",
+ ifcp->ifc_name, ifac->ifac_ifc->ifc_name);
+ return -1;
+ }
+ TAILQ_REMOVE(&ifcp->ifc_ifac_head, ifac, ifac_next);
+ t_lifetime = time(NULL) - RIP_LIFETIME;
+ /* age route for interface address */
+ memset(&ni6, 0, sizeof(ni6));
+ ni6.rip6_dest = ifac->ifac_addr;
+ ni6.rip6_plen = ifac->ifac_plen;
+ applyplen(&ni6.rip6_dest, ni6.rip6_plen);
+ trace(1, "\tfind interface route %s/%d on %d\n",
+ inet6_n2p(&ni6.rip6_dest), ni6.rip6_plen, ifcp->ifc_index);
+ if ((rrt = rtsearch(&ni6)) != NULL) {
+ struct in6_addr none;
+ memset(&none, 0, sizeof(none));
+ if (rrt->rrt_index == ifcp->ifc_index &&
+ (IN6_ARE_ADDR_EQUAL(&rrt->rrt_gw, &none) ||
+ IN6_IS_ADDR_LOOPBACK(&rrt->rrt_gw))) {
+ trace(1, "\troute found, age it\n");
+ if (rrt->rrt_t == 0 || rrt->rrt_t > t_lifetime) {
+ rrt->rrt_t = t_lifetime;
+ rrt->rrt_info.rip6_metric = HOPCNT_INFINITY6;
+ }
+ updated++;
+ } else {
+ trace(1, "\tnon-interface route found: %s/%d on %d\n",
+ inet6_n2p(&rrt->rrt_info.rip6_dest),
+ rrt->rrt_info.rip6_plen,
+ rrt->rrt_index);
+ }
+ } else
+ trace(1, "\tno interface route found\n");
+ /* age route for p2p destination */
+ if (ifcp->ifc_flags & IFF_POINTOPOINT) {
+ memset(&ni6, 0, sizeof(ni6));
+ ni6.rip6_dest = ifac->ifac_raddr;
+ ni6.rip6_plen = 128;
+ applyplen(&ni6.rip6_dest, ni6.rip6_plen); /*to be sure*/
+ trace(1, "\tfind p2p route %s/%d on %d\n",
+ inet6_n2p(&ni6.rip6_dest), ni6.rip6_plen,
+ ifcp->ifc_index);
+ if ((rrt = rtsearch(&ni6)) != NULL) {
+ if (rrt->rrt_index == ifcp->ifc_index &&
+ IN6_ARE_ADDR_EQUAL(&rrt->rrt_gw,
+ &ifac->ifac_addr)) {
+ trace(1, "\troute found, age it\n");
+ if (rrt->rrt_t == 0 || rrt->rrt_t > t_lifetime) {
+ rrt->rrt_t = t_lifetime;
+ rrt->rrt_info.rip6_metric =
+ HOPCNT_INFINITY6;
+ updated++;
+ }
+ } else {
+ trace(1, "\tnon-p2p route found: %s/%d on %d\n",
+ inet6_n2p(&rrt->rrt_info.rip6_dest),
+ rrt->rrt_info.rip6_plen,
+ rrt->rrt_index);
+ }
+ } else
+ trace(1, "\tno p2p route found\n");
+ }
+ free(ifac);
+
+ return ((updated) ? 0 : -1);
+}
+
+/*
+ * Get each interface address and put those interface routes to the route
+ * list.
+ */
+int
+ifrt(struct ifc *ifcp, int again)
+{
+ struct ifac *ifac;
+ struct riprt *rrt = NULL, *search_rrt, *loop_rrt;
+ struct netinfo6 *np;
+ time_t t_lifetime;
+ int need_trigger = 0;
+
+#if 0
+ if (ifcp->ifc_flags & IFF_LOOPBACK)
+ return 0; /* ignore loopback */
+#endif
+
+ if (ifcp->ifc_flags & IFF_POINTOPOINT) {
+ ifrt_p2p(ifcp, again);
+ return 0;
+ }
+
+ TAILQ_FOREACH(ifac, &ifcp->ifc_ifac_head, ifac_next) {
+ if (IN6_IS_ADDR_LINKLOCAL(&ifac->ifac_addr)) {
+#if 0
+ trace(1, "route: %s on %s: "
+ "skip linklocal interface address\n",
+ inet6_n2p(&ifac->ifac_addr), ifcp->ifc_name);
+#endif
+ continue;
+ }
+ if (IN6_IS_ADDR_UNSPECIFIED(&ifac->ifac_addr)) {
+#if 0
+ trace(1, "route: %s: skip unspec interface address\n",
+ ifcp->ifc_name);
+#endif
+ continue;
+ }
+ if (IN6_IS_ADDR_LOOPBACK(&ifac->ifac_addr)) {
+#if 0
+ trace(1, "route: %s: skip loopback address\n",
+ ifcp->ifc_name);
+#endif
+ continue;
+ }
+ if (ifcp->ifc_flags & IFF_UP) {
+ if ((rrt = MALLOC(struct riprt)) == NULL)
+ fatal("malloc: struct riprt");
+ memset(rrt, 0, sizeof(*rrt));
+ rrt->rrt_same = NULL;
+ rrt->rrt_index = ifcp->ifc_index;
+ rrt->rrt_t = 0; /* don't age */
+ rrt->rrt_info.rip6_dest = ifac->ifac_addr;
+ rrt->rrt_info.rip6_tag = htons(routetag & 0xffff);
+ rrt->rrt_info.rip6_metric = 1 + ifcp->ifc_metric;
+ rrt->rrt_info.rip6_plen = ifac->ifac_plen;
+ rrt->rrt_flags = RTF_HOST;
+ rrt->rrt_rflags |= RRTF_CHANGED;
+ applyplen(&rrt->rrt_info.rip6_dest, ifac->ifac_plen);
+ memset(&rrt->rrt_gw, 0, sizeof(struct in6_addr));
+ rrt->rrt_gw = ifac->ifac_addr;
+ np = &rrt->rrt_info;
+ search_rrt = rtsearch(np);
+ if (search_rrt != NULL) {
+ if (search_rrt->rrt_info.rip6_metric <=
+ rrt->rrt_info.rip6_metric) {
+ /* Already have better route */
+ if (!again) {
+ trace(1, "route: %s/%d: "
+ "already registered (%s)\n",
+ inet6_n2p(&np->rip6_dest), np->rip6_plen,
+ ifcp->ifc_name);
+ }
+ goto next;
+ }
+
+ TAILQ_REMOVE(&riprt_head, rrt, rrt_next);
+ delroute(&rrt->rrt_info, &rrt->rrt_gw);
+ }
+ /* Attach the route to the list */
+ trace(1, "route: %s/%d: register route (%s)\n",
+ inet6_n2p(&np->rip6_dest), np->rip6_plen,
+ ifcp->ifc_name);
+ TAILQ_INSERT_HEAD(&riprt_head, rrt, rrt_next);
+ addroute(rrt, &rrt->rrt_gw, ifcp);
+ rrt = NULL;
+ sendrequest(ifcp);
+ ripsend(ifcp, &ifcp->ifc_ripsin, 0);
+ need_trigger = 1;
+ } else {
+ TAILQ_FOREACH(loop_rrt, &riprt_head, rrt_next) {
+ if (loop_rrt->rrt_index == ifcp->ifc_index) {
+ t_lifetime = time(NULL) - RIP_LIFETIME;
+ if (loop_rrt->rrt_t == 0 || loop_rrt->rrt_t > t_lifetime) {
+ loop_rrt->rrt_t = t_lifetime;
+ loop_rrt->rrt_info.rip6_metric = HOPCNT_INFINITY6;
+ loop_rrt->rrt_rflags |= RRTF_CHANGED;
+ need_trigger = 1;
+ }
+ }
+ }
+ }
+ next:
+ if (rrt)
+ free(rrt);
+ }
+ return need_trigger;
+}
+
+/*
+ * there are couple of p2p interface routing models. "behavior" lets
+ * you pick one. it looks that gated behavior fits best with BSDs,
+ * since BSD kernels do not look at prefix length on p2p interfaces.
+ */
+void
+ifrt_p2p(struct ifc *ifcp, int again)
+{
+ struct ifac *ifac;
+ struct riprt *rrt, *orrt;
+ struct netinfo6 *np;
+ struct in6_addr addr, dest;
+ int advert, ignore, i;
+#define P2PADVERT_NETWORK 1
+#define P2PADVERT_ADDR 2
+#define P2PADVERT_DEST 4
+#define P2PADVERT_MAX 4
+ const enum { CISCO, GATED, ROUTE6D } behavior = GATED;
+ const char *category = "";
+ const char *noadv;
+
+ TAILQ_FOREACH(ifac, &ifcp->ifc_ifac_head, ifac_next) {
+ addr = ifac->ifac_addr;
+ dest = ifac->ifac_raddr;
+ applyplen(&addr, ifac->ifac_plen);
+ applyplen(&dest, ifac->ifac_plen);
+ advert = ignore = 0;
+ switch (behavior) {
+ case CISCO:
+ /*
+ * honor addr/plen, just like normal shared medium
+ * interface. this may cause trouble if you reuse
+ * addr/plen on other interfaces.
+ *
+ * advertise addr/plen.
+ */
+ advert |= P2PADVERT_NETWORK;
+ break;
+ case GATED:
+ /*
+ * prefixlen on p2p interface is meaningless.
+ * advertise addr/128 and dest/128.
+ *
+ * do not install network route to route6d routing
+ * table (if we do, it would prevent route installation
+ * for other p2p interface that shares addr/plen).
+ *
+ * XXX what should we do if dest is ::? it will not
+ * get announced anyways (see following filter),
+ * but we need to think.
+ */
+ advert |= P2PADVERT_ADDR;
+ advert |= P2PADVERT_DEST;
+ ignore |= P2PADVERT_NETWORK;
+ break;
+ case ROUTE6D:
+ /*
+ * just for testing. actually the code is redundant
+ * given the current p2p interface address assignment
+ * rule for kame kernel.
+ *
+ * intent:
+ * A/n -> announce A/n
+ * A B/n, A and B share prefix -> A/n (= B/n)
+ * A B/n, do not share prefix -> A/128 and B/128
+ * actually, A/64 and A B/128 are the only cases
+ * permitted by the kernel:
+ * A/64 -> A/64
+ * A B/128 -> A/128 and B/128
+ */
+ if (!IN6_IS_ADDR_UNSPECIFIED(&ifac->ifac_raddr)) {
+ if (IN6_ARE_ADDR_EQUAL(&addr, &dest))
+ advert |= P2PADVERT_NETWORK;
+ else {
+ advert |= P2PADVERT_ADDR;
+ advert |= P2PADVERT_DEST;
+ ignore |= P2PADVERT_NETWORK;
+ }
+ } else
+ advert |= P2PADVERT_NETWORK;
+ break;
+ }
+
+ for (i = 1; i <= P2PADVERT_MAX; i *= 2) {
+ if ((ignore & i) != 0)
+ continue;
+ if ((rrt = MALLOC(struct riprt)) == NULL) {
+ fatal("malloc: struct riprt");
+ /*NOTREACHED*/
+ }
+ memset(rrt, 0, sizeof(*rrt));
+ rrt->rrt_same = NULL;
+ rrt->rrt_index = ifcp->ifc_index;
+ rrt->rrt_t = 0; /* don't age */
+ switch (i) {
+ case P2PADVERT_NETWORK:
+ rrt->rrt_info.rip6_dest = ifac->ifac_addr;
+ rrt->rrt_info.rip6_plen = ifac->ifac_plen;
+ applyplen(&rrt->rrt_info.rip6_dest,
+ ifac->ifac_plen);
+ category = "network";
+ break;
+ case P2PADVERT_ADDR:
+ rrt->rrt_info.rip6_dest = ifac->ifac_addr;
+ rrt->rrt_info.rip6_plen = 128;
+ rrt->rrt_gw = in6addr_loopback;
+ category = "addr";
+ break;
+ case P2PADVERT_DEST:
+ rrt->rrt_info.rip6_dest = ifac->ifac_raddr;
+ rrt->rrt_info.rip6_plen = 128;
+ rrt->rrt_gw = ifac->ifac_addr;
+ category = "dest";
+ break;
+ }
+ if (IN6_IS_ADDR_UNSPECIFIED(&rrt->rrt_info.rip6_dest) ||
+ IN6_IS_ADDR_LINKLOCAL(&rrt->rrt_info.rip6_dest)) {
+#if 0
+ trace(1, "route: %s: skip unspec/linklocal "
+ "(%s on %s)\n", category, ifcp->ifc_name);
+#endif
+ free(rrt);
+ continue;
+ }
+ if ((advert & i) == 0) {
+ rrt->rrt_rflags |= RRTF_NOADVERTISE;
+ noadv = ", NO-ADV";
+ } else
+ noadv = "";
+ rrt->rrt_info.rip6_tag = htons(routetag & 0xffff);
+ rrt->rrt_info.rip6_metric = 1 + ifcp->ifc_metric;
+ np = &rrt->rrt_info;
+ orrt = rtsearch(np);
+ if (!orrt) {
+ /* Attach the route to the list */
+ trace(1, "route: %s/%d: register route "
+ "(%s on %s%s)\n",
+ inet6_n2p(&np->rip6_dest), np->rip6_plen,
+ category, ifcp->ifc_name, noadv);
+ TAILQ_INSERT_HEAD(&riprt_head, rrt, rrt_next);
+ } else if (rrt->rrt_index != orrt->rrt_index ||
+ rrt->rrt_info.rip6_metric != orrt->rrt_info.rip6_metric) {
+ /* replace route */
+ TAILQ_INSERT_BEFORE(orrt, rrt, rrt_next);
+ TAILQ_REMOVE(&riprt_head, orrt, rrt_next);
+ free(orrt);
+
+ trace(1, "route: %s/%d: update (%s on %s%s)\n",
+ inet6_n2p(&np->rip6_dest), np->rip6_plen,
+ category, ifcp->ifc_name, noadv);
+ } else {
+ /* Already found */
+ if (!again) {
+ trace(1, "route: %s/%d: "
+ "already registered (%s on %s%s)\n",
+ inet6_n2p(&np->rip6_dest),
+ np->rip6_plen, category,
+ ifcp->ifc_name, noadv);
+ }
+ free(rrt);
+ }
+ }
+ }
+#undef P2PADVERT_NETWORK
+#undef P2PADVERT_ADDR
+#undef P2PADVERT_DEST
+#undef P2PADVERT_MAX
+}
+
+int
+getifmtu(int ifindex)
+{
+ int mib[6];
+ char *buf;
+ size_t msize;
+ struct if_msghdr *ifm;
+ int mtu;
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = AF_INET6;
+ mib[4] = NET_RT_IFLIST;
+ mib[5] = ifindex;
+ if (sysctl(mib, 6, NULL, &msize, NULL, 0) < 0) {
+ fatal("sysctl estimate NET_RT_IFLIST");
+ /*NOTREACHED*/
+ }
+ if ((buf = malloc(msize)) == NULL) {
+ fatal("malloc");
+ /*NOTREACHED*/
+ }
+ if (sysctl(mib, 6, buf, &msize, NULL, 0) < 0) {
+ fatal("sysctl NET_RT_IFLIST");
+ /*NOTREACHED*/
+ }
+ ifm = (struct if_msghdr *)buf;
+ mtu = ifm->ifm_data.ifi_mtu;
+ if (ifindex != ifm->ifm_index) {
+ fatal("ifindex does not match with ifm_index");
+ /*NOTREACHED*/
+ }
+ free(buf);
+ return mtu;
+}
+
+const char *
+rttypes(struct rt_msghdr *rtm)
+{
+#define RTTYPE(s, f) \
+do { \
+ if (rtm->rtm_type == (f)) \
+ return (s); \
+} while (0)
+ RTTYPE("ADD", RTM_ADD);
+ RTTYPE("DELETE", RTM_DELETE);
+ RTTYPE("CHANGE", RTM_CHANGE);
+ RTTYPE("GET", RTM_GET);
+ RTTYPE("LOSING", RTM_LOSING);
+ RTTYPE("REDIRECT", RTM_REDIRECT);
+ RTTYPE("MISS", RTM_MISS);
+ RTTYPE("LOCK", RTM_LOCK);
+ RTTYPE("NEWADDR", RTM_NEWADDR);
+ RTTYPE("DELADDR", RTM_DELADDR);
+ RTTYPE("IFINFO", RTM_IFINFO);
+#ifdef RTM_OIFINFO
+ RTTYPE("OIFINFO", RTM_OIFINFO);
+#endif
+#ifdef RTM_IFANNOUNCE
+ RTTYPE("IFANNOUNCE", RTM_IFANNOUNCE);
+#endif
+#ifdef RTM_NEWMADDR
+ RTTYPE("NEWMADDR", RTM_NEWMADDR);
+#endif
+#ifdef RTM_DELMADDR
+ RTTYPE("DELMADDR", RTM_DELMADDR);
+#endif
+#undef RTTYPE
+ return NULL;
+}
+
+const char *
+rtflags(struct rt_msghdr *rtm)
+{
+ static char buf[BUFSIZ];
+
+ /*
+ * letter conflict should be okay. painful when *BSD diverges...
+ */
+ strlcpy(buf, "", sizeof(buf));
+#define RTFLAG(s, f) \
+do { \
+ if (rtm->rtm_flags & (f)) \
+ strlcat(buf, (s), sizeof(buf)); \
+} while (0)
+ RTFLAG("U", RTF_UP);
+ RTFLAG("G", RTF_GATEWAY);
+ RTFLAG("H", RTF_HOST);
+ RTFLAG("R", RTF_REJECT);
+ RTFLAG("D", RTF_DYNAMIC);
+ RTFLAG("M", RTF_MODIFIED);
+ RTFLAG("d", RTF_DONE);
+#ifdef RTF_MASK
+ RTFLAG("m", RTF_MASK);
+#endif
+#ifdef RTF_CLONED
+ RTFLAG("c", RTF_CLONED);
+#endif
+ RTFLAG("X", RTF_XRESOLVE);
+#ifdef RTF_LLINFO
+ RTFLAG("L", RTF_LLINFO);
+#endif
+ RTFLAG("S", RTF_STATIC);
+ RTFLAG("B", RTF_BLACKHOLE);
+#ifdef RTF_PROTO3
+ RTFLAG("3", RTF_PROTO3);
+#endif
+ RTFLAG("2", RTF_PROTO2);
+ RTFLAG("1", RTF_PROTO1);
+#ifdef RTF_BROADCAST
+ RTFLAG("b", RTF_BROADCAST);
+#endif
+#ifdef RTF_DEFAULT
+ RTFLAG("d", RTF_DEFAULT);
+#endif
+#ifdef RTF_ISAROUTER
+ RTFLAG("r", RTF_ISAROUTER);
+#endif
+#ifdef RTF_TUNNEL
+ RTFLAG("T", RTF_TUNNEL);
+#endif
+#ifdef RTF_AUTH
+ RTFLAG("A", RTF_AUTH);
+#endif
+#ifdef RTF_CRYPT
+ RTFLAG("E", RTF_CRYPT);
+#endif
+#undef RTFLAG
+ return buf;
+}
+
+const char *
+ifflags(int flags)
+{
+ static char buf[BUFSIZ];
+
+ strlcpy(buf, "", sizeof(buf));
+#define IFFLAG(s, f) \
+do { \
+ if (flags & (f)) { \
+ if (buf[0]) \
+ strlcat(buf, ",", sizeof(buf)); \
+ strlcat(buf, (s), sizeof(buf)); \
+ } \
+} while (0)
+ IFFLAG("UP", IFF_UP);
+ IFFLAG("BROADCAST", IFF_BROADCAST);
+ IFFLAG("DEBUG", IFF_DEBUG);
+ IFFLAG("LOOPBACK", IFF_LOOPBACK);
+ IFFLAG("POINTOPOINT", IFF_POINTOPOINT);
+#ifdef IFF_NOTRAILERS
+ IFFLAG("NOTRAILERS", IFF_NOTRAILERS);
+#endif
+ IFFLAG("RUNNING", IFF_RUNNING);
+ IFFLAG("NOARP", IFF_NOARP);
+ IFFLAG("PROMISC", IFF_PROMISC);
+ IFFLAG("ALLMULTI", IFF_ALLMULTI);
+ IFFLAG("OACTIVE", IFF_OACTIVE);
+ IFFLAG("SIMPLEX", IFF_SIMPLEX);
+ IFFLAG("LINK0", IFF_LINK0);
+ IFFLAG("LINK1", IFF_LINK1);
+ IFFLAG("LINK2", IFF_LINK2);
+ IFFLAG("MULTICAST", IFF_MULTICAST);
+#undef IFFLAG
+ return buf;
+}
+
+void
+krtread(int again)
+{
+ int mib[6];
+ size_t msize;
+ char *buf, *p, *lim;
+ struct rt_msghdr *rtm;
+ int retry;
+ const char *errmsg;
+
+ retry = 0;
+ buf = NULL;
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = AF_INET6; /* Address family */
+ mib[4] = NET_RT_DUMP; /* Dump the kernel routing table */
+ mib[5] = 0; /* No flags */
+ do {
+ if (retry)
+ sleep(1);
+ retry++;
+ errmsg = NULL;
+ if (buf)
+ free(buf);
+ if (sysctl(mib, 6, NULL, &msize, NULL, 0) < 0) {
+ errmsg = "sysctl estimate";
+ continue;
+ }
+ if ((buf = malloc(msize)) == NULL) {
+ errmsg = "malloc";
+ continue;
+ }
+ if (sysctl(mib, 6, buf, &msize, NULL, 0) < 0) {
+ errmsg = "sysctl NET_RT_DUMP";
+ continue;
+ }
+ } while (retry < RT_DUMP_MAXRETRY && errmsg != NULL);
+ if (errmsg) {
+ fatal("%s (with %d retries, msize=%lu)", errmsg, retry,
+ (u_long)msize);
+ /*NOTREACHED*/
+ } else if (1 < retry)
+ syslog(LOG_INFO, "NET_RT_DUMP %d retires", retry);
+
+ lim = buf + msize;
+ for (p = buf; p < lim; p += rtm->rtm_msglen) {
+ rtm = (struct rt_msghdr *)p;
+ rt_entry(rtm, again);
+ }
+ free(buf);
+}
+
+void
+rt_entry(struct rt_msghdr *rtm, int again)
+{
+ struct sockaddr_in6 *sin6_dst, *sin6_gw, *sin6_mask;
+ struct sockaddr_in6 *sin6_genmask, *sin6_ifp;
+ char *rtmp, *ifname = NULL;
+ struct riprt *rrt, *orrt;
+ struct netinfo6 *np;
+ int ifindex;
+
+ sin6_dst = sin6_gw = sin6_mask = sin6_genmask = sin6_ifp = 0;
+ if ((rtm->rtm_flags & RTF_UP) == 0 || rtm->rtm_flags &
+ (RTF_XRESOLVE|RTF_BLACKHOLE)) {
+ return; /* not interested in the link route */
+ }
+ /* do not look at cloned routes */
+#ifdef RTF_WASCLONED
+ if (rtm->rtm_flags & RTF_WASCLONED)
+ return;
+#endif
+#ifdef RTF_CLONED
+ if (rtm->rtm_flags & RTF_CLONED)
+ return;
+#endif
+ /* XXX: Ignore connected routes. */
+ if (!(rtm->rtm_flags & (RTF_GATEWAY|RTF_HOST|RTF_STATIC)))
+ return;
+ /*
+ * do not look at dynamic routes.
+ * netbsd/openbsd cloned routes have UGHD.
+ */
+ if (rtm->rtm_flags & RTF_DYNAMIC)
+ return;
+ rtmp = (char *)(rtm + 1);
+ /* Destination */
+ if ((rtm->rtm_addrs & RTA_DST) == 0)
+ return; /* ignore routes without destination address */
+ sin6_dst = (struct sockaddr_in6 *)rtmp;
+ rtmp += ROUNDUP(sin6_dst->sin6_len);
+ if (rtm->rtm_addrs & RTA_GATEWAY) {
+ sin6_gw = (struct sockaddr_in6 *)rtmp;
+ rtmp += ROUNDUP(sin6_gw->sin6_len);
+ }
+ if (rtm->rtm_addrs & RTA_NETMASK) {
+ sin6_mask = (struct sockaddr_in6 *)rtmp;
+ rtmp += ROUNDUP(sin6_mask->sin6_len);
+ }
+ if (rtm->rtm_addrs & RTA_GENMASK) {
+ sin6_genmask = (struct sockaddr_in6 *)rtmp;
+ rtmp += ROUNDUP(sin6_genmask->sin6_len);
+ }
+ if (rtm->rtm_addrs & RTA_IFP) {
+ sin6_ifp = (struct sockaddr_in6 *)rtmp;
+ rtmp += ROUNDUP(sin6_ifp->sin6_len);
+ }
+
+ /* Destination */
+ if (sin6_dst->sin6_family != AF_INET6)
+ return;
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6_dst->sin6_addr))
+ return; /* Link-local */
+ if (IN6_ARE_ADDR_EQUAL(&sin6_dst->sin6_addr, &in6addr_loopback))
+ return; /* Loopback */
+ if (IN6_IS_ADDR_MULTICAST(&sin6_dst->sin6_addr))
+ return;
+
+ if ((rrt = MALLOC(struct riprt)) == NULL) {
+ fatal("malloc: struct riprt");
+ /*NOTREACHED*/
+ }
+ memset(rrt, 0, sizeof(*rrt));
+ np = &rrt->rrt_info;
+ rrt->rrt_same = NULL;
+ rrt->rrt_t = time(NULL);
+ if (aflag == 0 && (rtm->rtm_flags & RTF_STATIC))
+ rrt->rrt_t = 0; /* Don't age static routes */
+ if (rtm->rtm_flags & Pflag)
+ rrt->rrt_t = 0; /* Don't age PROTO[123] routes */
+ if ((rtm->rtm_flags & (RTF_HOST|RTF_GATEWAY)) == RTF_HOST)
+ rrt->rrt_t = 0; /* Don't age non-gateway host routes */
+ np->rip6_tag = 0;
+ np->rip6_metric = rtm->rtm_rmx.rmx_hopcount;
+ if (np->rip6_metric < 1)
+ np->rip6_metric = 1;
+ rrt->rrt_flags = rtm->rtm_flags;
+ np->rip6_dest = sin6_dst->sin6_addr;
+
+ /* Mask or plen */
+ if (rtm->rtm_flags & RTF_HOST)
+ np->rip6_plen = 128; /* Host route */
+ else if (sin6_mask)
+ np->rip6_plen = sin6mask2len(sin6_mask);
+ else
+ np->rip6_plen = 0;
+
+ orrt = rtsearch(np);
+ if (orrt && orrt->rrt_info.rip6_metric != HOPCNT_INFINITY6) {
+ /* Already found */
+ if (!again) {
+ trace(1, "route: %s/%d flags %s: already registered\n",
+ inet6_n2p(&np->rip6_dest), np->rip6_plen,
+ rtflags(rtm));
+ }
+ free(rrt);
+ return;
+ }
+ /* Gateway */
+ if (!sin6_gw)
+ memset(&rrt->rrt_gw, 0, sizeof(struct in6_addr));
+ else {
+ if (sin6_gw->sin6_family == AF_INET6)
+ rrt->rrt_gw = sin6_gw->sin6_addr;
+ else if (sin6_gw->sin6_family == AF_LINK) {
+ /* XXX in case ppp link? */
+ rrt->rrt_gw = in6addr_loopback;
+ } else
+ memset(&rrt->rrt_gw, 0, sizeof(struct in6_addr));
+ }
+ trace(1, "route: %s/%d flags %s",
+ inet6_n2p(&np->rip6_dest), np->rip6_plen, rtflags(rtm));
+ trace(1, " gw %s", inet6_n2p(&rrt->rrt_gw));
+
+ /* Interface */
+ ifindex = rtm->rtm_index;
+ if ((unsigned int)ifindex < nindex2ifc && index2ifc[ifindex])
+ ifname = index2ifc[ifindex]->ifc_name;
+ else {
+ trace(1, " not configured\n");
+ free(rrt);
+ return;
+ }
+ trace(1, " if %s sock %d", ifname, ifindex);
+ rrt->rrt_index = ifindex;
+
+ trace(1, "\n");
+
+ /* Check gateway */
+ if (!IN6_IS_ADDR_LINKLOCAL(&rrt->rrt_gw) &&
+ !IN6_IS_ADDR_LOOPBACK(&rrt->rrt_gw) &&
+ (rrt->rrt_flags & RTF_LOCAL) == 0) {
+ trace(0, "***** Gateway %s is not a link-local address.\n",
+ inet6_n2p(&rrt->rrt_gw));
+ trace(0, "***** dest(%s) if(%s) -- Not optimized.\n",
+ inet6_n2p(&rrt->rrt_info.rip6_dest), ifname);
+ rrt->rrt_rflags |= RRTF_NH_NOT_LLADDR;
+ }
+
+ /* Put it to the route list */
+ if (orrt && orrt->rrt_info.rip6_metric == HOPCNT_INFINITY6) {
+ /* replace route list */
+ TAILQ_INSERT_BEFORE(orrt, rrt, rrt_next);
+ TAILQ_REMOVE(&riprt_head, orrt, rrt_next);
+
+ trace(1, "route: %s/%d flags %s: replace new route\n",
+ inet6_n2p(&np->rip6_dest), np->rip6_plen,
+ rtflags(rtm));
+ free(orrt);
+ } else
+ TAILQ_INSERT_HEAD(&riprt_head, rrt, rrt_next);
+}
+
+int
+addroute(struct riprt *rrt,
+ const struct in6_addr *gw,
+ struct ifc *ifcp)
+{
+ struct netinfo6 *np;
+ u_char buf[BUFSIZ], buf1[BUFSIZ], buf2[BUFSIZ];
+ struct rt_msghdr *rtm;
+ struct sockaddr_in6 *sin6;
+ int len;
+
+ np = &rrt->rrt_info;
+ inet_ntop(AF_INET6, (const void *)gw, (char *)buf1, sizeof(buf1));
+ inet_ntop(AF_INET6, (void *)&ifcp->ifc_mylladdr, (char *)buf2, sizeof(buf2));
+ tracet(1, "ADD: %s/%d gw %s [%d] ifa %s\n",
+ inet6_n2p(&np->rip6_dest), np->rip6_plen, buf1,
+ np->rip6_metric - 1, buf2);
+ if (rtlog)
+ fprintf(rtlog, "%s: ADD: %s/%d gw %s [%d] ifa %s\n", hms(),
+ inet6_n2p(&np->rip6_dest), np->rip6_plen, buf1,
+ np->rip6_metric - 1, buf2);
+ if (nflag)
+ return 0;
+
+ memset(buf, 0, sizeof(buf));
+ rtm = (struct rt_msghdr *)buf;
+ rtm->rtm_type = RTM_ADD;
+ rtm->rtm_version = RTM_VERSION;
+ rtm->rtm_seq = ++seq;
+ rtm->rtm_pid = pid;
+ rtm->rtm_flags = rrt->rrt_flags;
+ rtm->rtm_flags |= Qflag;
+ rtm->rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK;
+ rtm->rtm_rmx.rmx_hopcount = np->rip6_metric - 1;
+ rtm->rtm_inits = RTV_HOPCOUNT;
+ sin6 = (struct sockaddr_in6 *)&buf[sizeof(struct rt_msghdr)];
+ /* Destination */
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_addr = np->rip6_dest;
+ sin6 = (struct sockaddr_in6 *)((char *)sin6 + ROUNDUP(sin6->sin6_len));
+ /* Gateway */
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_addr = *gw;
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
+ sin6->sin6_scope_id = ifcp->ifc_index;
+ sin6 = (struct sockaddr_in6 *)((char *)sin6 + ROUNDUP(sin6->sin6_len));
+ /* Netmask */
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_addr = *(plen2mask(np->rip6_plen));
+ sin6 = (struct sockaddr_in6 *)((char *)sin6 + ROUNDUP(sin6->sin6_len));
+
+ len = (char *)sin6 - (char *)buf;
+ rtm->rtm_msglen = len;
+ if (write(rtsock, buf, len) > 0)
+ return 0;
+
+ if (errno == EEXIST) {
+ trace(0, "ADD: Route already exists %s/%d gw %s\n",
+ inet6_n2p(&np->rip6_dest), np->rip6_plen, buf1);
+ if (rtlog)
+ fprintf(rtlog, "ADD: Route already exists %s/%d gw %s\n",
+ inet6_n2p(&np->rip6_dest), np->rip6_plen, buf1);
+ } else {
+ trace(0, "Can not write to rtsock (addroute): %s\n",
+ strerror(errno));
+ if (rtlog)
+ fprintf(rtlog, "\tCan not write to rtsock: %s\n",
+ strerror(errno));
+ }
+ return -1;
+}
+
+int
+delroute(struct netinfo6 *np, struct in6_addr *gw)
+{
+ u_char buf[BUFSIZ], buf2[BUFSIZ];
+ struct rt_msghdr *rtm;
+ struct sockaddr_in6 *sin6;
+ int len;
+
+ inet_ntop(AF_INET6, (void *)gw, (char *)buf2, sizeof(buf2));
+ tracet(1, "DEL: %s/%d gw %s\n", inet6_n2p(&np->rip6_dest),
+ np->rip6_plen, buf2);
+ if (rtlog)
+ fprintf(rtlog, "%s: DEL: %s/%d gw %s\n",
+ hms(), inet6_n2p(&np->rip6_dest), np->rip6_plen, buf2);
+ if (nflag)
+ return 0;
+
+ memset(buf, 0, sizeof(buf));
+ rtm = (struct rt_msghdr *)buf;
+ rtm->rtm_type = RTM_DELETE;
+ rtm->rtm_version = RTM_VERSION;
+ rtm->rtm_seq = ++seq;
+ rtm->rtm_pid = pid;
+ rtm->rtm_flags = RTF_UP | RTF_GATEWAY;
+ rtm->rtm_flags |= Qflag;
+ if (np->rip6_plen == sizeof(struct in6_addr) * 8)
+ rtm->rtm_flags |= RTF_HOST;
+ rtm->rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK;
+ sin6 = (struct sockaddr_in6 *)&buf[sizeof(struct rt_msghdr)];
+ /* Destination */
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_addr = np->rip6_dest;
+ sin6 = (struct sockaddr_in6 *)((char *)sin6 + ROUNDUP(sin6->sin6_len));
+ /* Gateway */
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_addr = *gw;
+ sin6 = (struct sockaddr_in6 *)((char *)sin6 + ROUNDUP(sin6->sin6_len));
+ /* Netmask */
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_addr = *(plen2mask(np->rip6_plen));
+ sin6 = (struct sockaddr_in6 *)((char *)sin6 + ROUNDUP(sin6->sin6_len));
+
+ len = (char *)sin6 - (char *)buf;
+ rtm->rtm_msglen = len;
+ if (write(rtsock, buf, len) >= 0)
+ return 0;
+
+ if (errno == ESRCH) {
+ trace(0, "RTDEL: Route does not exist: %s/%d gw %s\n",
+ inet6_n2p(&np->rip6_dest), np->rip6_plen, buf2);
+ if (rtlog)
+ fprintf(rtlog, "RTDEL: Route does not exist: %s/%d gw %s\n",
+ inet6_n2p(&np->rip6_dest), np->rip6_plen, buf2);
+ } else {
+ trace(0, "Can not write to rtsock (delroute): %s\n",
+ strerror(errno));
+ if (rtlog)
+ fprintf(rtlog, "\tCan not write to rtsock: %s\n",
+ strerror(errno));
+ }
+ return -1;
+}
+
+struct in6_addr *
+getroute(struct netinfo6 *np, struct in6_addr *gw)
+{
+ u_char buf[BUFSIZ];
+ int myseq;
+ int len;
+ struct rt_msghdr *rtm;
+ struct sockaddr_in6 *sin6;
+
+ rtm = (struct rt_msghdr *)buf;
+ len = sizeof(struct rt_msghdr) + sizeof(struct sockaddr_in6);
+ memset(rtm, 0, len);
+ rtm->rtm_type = RTM_GET;
+ rtm->rtm_version = RTM_VERSION;
+ myseq = ++seq;
+ rtm->rtm_seq = myseq;
+ rtm->rtm_addrs = RTA_DST;
+ rtm->rtm_msglen = len;
+ sin6 = (struct sockaddr_in6 *)&buf[sizeof(struct rt_msghdr)];
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_addr = np->rip6_dest;
+ if (write(rtsock, buf, len) < 0) {
+ if (errno == ESRCH) /* No such route found */
+ return NULL;
+ perror("write to rtsock");
+ exit(1);
+ }
+ do {
+ if ((len = read(rtsock, buf, sizeof(buf))) < 0) {
+ perror("read from rtsock");
+ exit(1);
+ }
+ rtm = (struct rt_msghdr *)buf;
+ } while (rtm->rtm_seq != myseq || rtm->rtm_pid != pid);
+ sin6 = (struct sockaddr_in6 *)&buf[sizeof(struct rt_msghdr)];
+ if (rtm->rtm_addrs & RTA_DST) {
+ sin6 = (struct sockaddr_in6 *)
+ ((char *)sin6 + ROUNDUP(sin6->sin6_len));
+ }
+ if (rtm->rtm_addrs & RTA_GATEWAY) {
+ *gw = sin6->sin6_addr;
+ return gw;
+ }
+ return NULL;
+}
+
+const char *
+inet6_n2p(const struct in6_addr *p)
+{
+ static char buf[BUFSIZ];
+
+ return inet_ntop(AF_INET6, (const void *)p, buf, sizeof(buf));
+}
+
+void
+ifrtdump(int sig)
+{
+
+ ifdump(sig);
+ rtdump(sig);
+}
+
+void
+ifdump(int sig)
+{
+ struct ifc *ifcp;
+ FILE *dump;
+ int nifc = 0;
+
+ if (sig == 0)
+ dump = stderr;
+ else
+ if ((dump = fopen(ROUTE6D_DUMP, "a")) == NULL)
+ dump = stderr;
+
+ fprintf(dump, "%s: Interface Table Dump\n", hms());
+ TAILQ_FOREACH(ifcp, &ifc_head, ifc_next)
+ nifc++;
+ fprintf(dump, " Number of interfaces: %d\n", nifc);
+
+ fprintf(dump, " advertising interfaces:\n");
+ TAILQ_FOREACH(ifcp, &ifc_head, ifc_next) {
+ if ((ifcp->ifc_flags & IFF_UP) == 0)
+ continue;
+ if (iff_find(ifcp, IFIL_TYPE_N) != NULL)
+ continue;
+ ifdump0(dump, ifcp);
+ }
+ fprintf(dump, "\n");
+ fprintf(dump, " non-advertising interfaces:\n");
+ TAILQ_FOREACH(ifcp, &ifc_head, ifc_next) {
+ if ((ifcp->ifc_flags & IFF_UP) &&
+ (iff_find(ifcp, IFIL_TYPE_N) == NULL))
+ continue;
+ ifdump0(dump, ifcp);
+ }
+ fprintf(dump, "\n");
+ if (dump != stderr)
+ fclose(dump);
+}
+
+void
+ifdump0(FILE *dump, const struct ifc *ifcp)
+{
+ struct ifac *ifac;
+ struct iff *iffp;
+ char buf[BUFSIZ];
+ const char *ft;
+ int addr;
+
+ fprintf(dump, " %s: index(%d) flags(%s) addr(%s) mtu(%d) metric(%d)\n",
+ ifcp->ifc_name, ifcp->ifc_index, ifflags(ifcp->ifc_flags),
+ inet6_n2p(&ifcp->ifc_mylladdr),
+ ifcp->ifc_mtu, ifcp->ifc_metric);
+ TAILQ_FOREACH(ifac, &ifcp->ifc_ifac_head, ifac_next) {
+ if (ifcp->ifc_flags & IFF_POINTOPOINT) {
+ inet_ntop(AF_INET6, (void *)&ifac->ifac_raddr,
+ buf, sizeof(buf));
+ fprintf(dump, "\t%s/%d -- %s\n",
+ inet6_n2p(&ifac->ifac_addr),
+ ifac->ifac_plen, buf);
+ } else {
+ fprintf(dump, "\t%s/%d\n",
+ inet6_n2p(&ifac->ifac_addr),
+ ifac->ifac_plen);
+ }
+ }
+
+ fprintf(dump, "\tFilter:\n");
+ TAILQ_FOREACH(iffp, &ifcp->ifc_iff_head, iff_next) {
+ addr = 0;
+ switch (iffp->iff_type) {
+ case IFIL_TYPE_A:
+ ft = "Aggregate"; addr++; break;
+ case IFIL_TYPE_N:
+ ft = "No-use"; break;
+ case IFIL_TYPE_O:
+ ft = "Advertise-only"; addr++; break;
+ case IFIL_TYPE_T:
+ ft = "Default-only"; break;
+ case IFIL_TYPE_L:
+ ft = "Listen-only"; addr++; break;
+ default:
+ snprintf(buf, sizeof(buf), "Unknown-%c", iffp->iff_type);
+ ft = buf;
+ addr++;
+ break;
+ }
+ fprintf(dump, "\t\t%s", ft);
+ if (addr)
+ fprintf(dump, "(%s/%d)", inet6_n2p(&iffp->iff_addr),
+ iffp->iff_plen);
+ fprintf(dump, "\n");
+ }
+ fprintf(dump, "\n");
+}
+
+void
+rtdump(int sig)
+{
+ struct riprt *rrt;
+ char buf[BUFSIZ];
+ FILE *dump;
+ time_t t, age;
+
+ if (sig == 0)
+ dump = stderr;
+ else
+ if ((dump = fopen(ROUTE6D_DUMP, "a")) == NULL)
+ dump = stderr;
+
+ t = time(NULL);
+ fprintf(dump, "\n%s: Routing Table Dump\n", hms());
+ TAILQ_FOREACH(rrt, &riprt_head, rrt_next) {
+ if (rrt->rrt_t == 0)
+ age = 0;
+ else
+ age = t - rrt->rrt_t;
+ inet_ntop(AF_INET6, (void *)&rrt->rrt_info.rip6_dest,
+ buf, sizeof(buf));
+ fprintf(dump, " %s/%d if(%d:%s) gw(%s) [%d] age(%ld)",
+ buf, rrt->rrt_info.rip6_plen, rrt->rrt_index,
+ index2ifc[rrt->rrt_index]->ifc_name,
+ inet6_n2p(&rrt->rrt_gw),
+ rrt->rrt_info.rip6_metric, (long)age);
+ if (rrt->rrt_info.rip6_tag) {
+ fprintf(dump, " tag(0x%04x)",
+ ntohs(rrt->rrt_info.rip6_tag) & 0xffff);
+ }
+ if (rrt->rrt_rflags & RRTF_NH_NOT_LLADDR)
+ fprintf(dump, " NOT-LL");
+ if (rrt->rrt_rflags & RRTF_NOADVERTISE)
+ fprintf(dump, " NO-ADV");
+ fprintf(dump, "\n");
+ }
+ fprintf(dump, "\n");
+ if (dump != stderr)
+ fclose(dump);
+}
+
+/*
+ * Parse the -A (and -O) options and put corresponding filter object to the
+ * specified interface structures. Each of the -A/O option has the following
+ * syntax: -A 5f09:c400::/32,ef0,ef1 (aggregate)
+ * -O 5f09:c400::/32,ef0,ef1 (only when match)
+ */
+void
+filterconfig(void)
+{
+ int i;
+ char *p, *ap, *iflp, *ifname, *ep;
+ struct iff iff, *iffp;
+ struct ifc *ifcp;
+ struct riprt *rrt;
+#if 0
+ struct in6_addr gw;
+#endif
+ u_long plen;
+
+ for (i = 0; i < nfilter; i++) {
+ ap = filter[i];
+ iflp = NULL;
+ iffp = &iff;
+ memset(iffp, 0, sizeof(*iffp));
+ if (filtertype[i] == 'N' || filtertype[i] == 'T') {
+ iflp = ap;
+ goto ifonly;
+ }
+ if ((p = strchr(ap, ',')) != NULL) {
+ *p++ = '\0';
+ iflp = p;
+ }
+ if ((p = strchr(ap, '/')) == NULL) {
+ fatal("no prefixlen specified for '%s'", ap);
+ /*NOTREACHED*/
+ }
+ *p++ = '\0';
+ if (inet_pton(AF_INET6, ap, &iffp->iff_addr) != 1) {
+ fatal("invalid prefix specified for '%s'", ap);
+ /*NOTREACHED*/
+ }
+ errno = 0;
+ ep = NULL;
+ plen = strtoul(p, &ep, 10);
+ if (errno || !*p || *ep || plen > sizeof(iffp->iff_addr) * 8) {
+ fatal("invalid prefix length specified for '%s'", ap);
+ /*NOTREACHED*/
+ }
+ iffp->iff_plen = plen;
+ applyplen(&iffp->iff_addr, iffp->iff_plen);
+ifonly:
+ iffp->iff_type = filtertype[i];
+ if (iflp == NULL || *iflp == '\0') {
+ fatal("no interface specified for '%s'", ap);
+ /*NOTREACHED*/
+ }
+ /* parse the interface listing portion */
+ while (iflp) {
+ ifname = iflp;
+ if ((iflp = strchr(iflp, ',')) != NULL)
+ *iflp++ = '\0';
+
+ TAILQ_FOREACH(ifcp, &ifc_head, ifc_next) {
+ if (fnmatch(ifname, ifcp->ifc_name, 0) != 0)
+ continue;
+
+ iffp = malloc(sizeof(*iffp));
+ if (iffp == NULL) {
+ fatal("malloc of iff");
+ /*NOTREACHED*/
+ }
+ memcpy(iffp, &iff, sizeof(*iffp));
+#if 0
+ syslog(LOG_INFO, "Add filter: type %d, ifname %s.", iffp->iff_type, ifname);
+#endif
+ TAILQ_INSERT_HEAD(&ifcp->ifc_iff_head, iffp, iff_next);
+ }
+ }
+
+ /*
+ * -A: aggregate configuration.
+ */
+ if (filtertype[i] != IFIL_TYPE_A)
+ continue;
+ /* put the aggregate to the kernel routing table */
+ rrt = (struct riprt *)malloc(sizeof(struct riprt));
+ if (rrt == NULL) {
+ fatal("malloc: rrt");
+ /*NOTREACHED*/
+ }
+ memset(rrt, 0, sizeof(struct riprt));
+ rrt->rrt_info.rip6_dest = iff.iff_addr;
+ rrt->rrt_info.rip6_plen = iff.iff_plen;
+ rrt->rrt_info.rip6_metric = 1;
+ rrt->rrt_info.rip6_tag = htons(routetag & 0xffff);
+ rrt->rrt_gw = in6addr_loopback;
+ rrt->rrt_flags = RTF_UP | RTF_REJECT;
+ rrt->rrt_rflags = RRTF_AGGREGATE;
+ rrt->rrt_t = 0;
+ rrt->rrt_index = loopifcp->ifc_index;
+#if 0
+ if (getroute(&rrt->rrt_info, &gw)) {
+#if 0
+ /*
+ * When the address has already been registered in the
+ * kernel routing table, it should be removed
+ */
+ delroute(&rrt->rrt_info, &gw);
+#else
+ /* it is safer behavior */
+ errno = EINVAL;
+ fatal("%s/%u already in routing table, "
+ "cannot aggregate",
+ inet6_n2p(&rrt->rrt_info.rip6_dest),
+ rrt->rrt_info.rip6_plen);
+ /*NOTREACHED*/
+#endif
+ }
+#endif
+ /* Put the route to the list */
+ TAILQ_INSERT_HEAD(&riprt_head, rrt, rrt_next);
+ trace(1, "Aggregate: %s/%d for %s\n",
+ inet6_n2p(&iff.iff_addr), iff.iff_plen,
+ loopifcp->ifc_name);
+ /* Add this route to the kernel */
+ if (nflag) /* do not modify kernel routing table */
+ continue;
+ addroute(rrt, &in6addr_loopback, loopifcp);
+ }
+}
+
+/***************** utility functions *****************/
+
+/*
+ * Returns a pointer to ifac whose address and prefix length matches
+ * with the address and prefix length specified in the arguments.
+ */
+struct ifac *
+ifa_match(const struct ifc *ifcp,
+ const struct in6_addr *ia,
+ int plen)
+{
+ struct ifac *ifac;
+
+ TAILQ_FOREACH(ifac, &ifcp->ifc_ifac_head, ifac_next) {
+ if (IN6_ARE_ADDR_EQUAL(&ifac->ifac_addr, ia) &&
+ ifac->ifac_plen == plen)
+ break;
+ }
+
+ return (ifac);
+}
+
+/*
+ * Return a pointer to riprt structure whose address and prefix length
+ * matches with the address and prefix length found in the argument.
+ * Note: This is not a rtalloc(). Therefore exact match is necessary.
+ */
+struct riprt *
+rtsearch(struct netinfo6 *np)
+{
+ struct riprt *rrt;
+
+ TAILQ_FOREACH(rrt, &riprt_head, rrt_next) {
+ if (rrt->rrt_info.rip6_plen == np->rip6_plen &&
+ IN6_ARE_ADDR_EQUAL(&rrt->rrt_info.rip6_dest,
+ &np->rip6_dest))
+ break;
+ }
+
+ return (rrt);
+}
+
+int
+sin6mask2len(const struct sockaddr_in6 *sin6)
+{
+
+ return mask2len(&sin6->sin6_addr,
+ sin6->sin6_len - offsetof(struct sockaddr_in6, sin6_addr));
+}
+
+int
+mask2len(const struct in6_addr *addr, int lenlim)
+{
+ int i = 0, j;
+ const u_char *p = (const u_char *)addr;
+
+ for (j = 0; j < lenlim; j++, p++) {
+ if (*p != 0xff)
+ break;
+ i += 8;
+ }
+ if (j < lenlim) {
+ switch (*p) {
+#define MASKLEN(m, l) case m: do { i += l; break; } while (0)
+ MASKLEN(0xfe, 7); break;
+ MASKLEN(0xfc, 6); break;
+ MASKLEN(0xf8, 5); break;
+ MASKLEN(0xf0, 4); break;
+ MASKLEN(0xe0, 3); break;
+ MASKLEN(0xc0, 2); break;
+ MASKLEN(0x80, 1); break;
+#undef MASKLEN
+ }
+ }
+ return i;
+}
+
+void
+applymask(struct in6_addr *addr, struct in6_addr *mask)
+{
+ int i;
+ u_long *p, *q;
+
+ p = (u_long *)addr; q = (u_long *)mask;
+ for (i = 0; i < 4; i++)
+ *p++ &= *q++;
+}
+
+static const u_char plent[8] = {
+ 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe
+};
+
+void
+applyplen(struct in6_addr *ia, int plen)
+{
+ u_char *p;
+ int i;
+
+ p = ia->s6_addr;
+ for (i = 0; i < 16; i++) {
+ if (plen <= 0)
+ *p = 0;
+ else if (plen < 8)
+ *p &= plent[plen];
+ p++, plen -= 8;
+ }
+}
+
+static const int pl2m[9] = {
+ 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff
+};
+
+struct in6_addr *
+plen2mask(int n)
+{
+ static struct in6_addr ia;
+ u_char *p;
+ int i;
+
+ memset(&ia, 0, sizeof(struct in6_addr));
+ p = (u_char *)&ia;
+ for (i = 0; i < 16; i++, p++, n -= 8) {
+ if (n >= 8) {
+ *p = 0xff;
+ continue;
+ }
+ *p = pl2m[n];
+ break;
+ }
+ return &ia;
+}
+
+char *
+allocopy(char *p)
+{
+ int len = strlen(p) + 1;
+ char *q = (char *)malloc(len);
+
+ if (!q) {
+ fatal("malloc");
+ /*NOTREACHED*/
+ }
+
+ strlcpy(q, p, len);
+ return q;
+}
+
+char *
+hms(void)
+{
+ static char buf[BUFSIZ];
+ time_t t;
+ struct tm *tm;
+
+ t = time(NULL);
+ if ((tm = localtime(&t)) == 0) {
+ fatal("localtime");
+ /*NOTREACHED*/
+ }
+ snprintf(buf, sizeof(buf), "%02d:%02d:%02d", tm->tm_hour, tm->tm_min,
+ tm->tm_sec);
+ return buf;
+}
+
+#define RIPRANDDEV 1.0 /* 30 +- 15, max - min = 30 */
+
+int
+ripinterval(int timer)
+{
+ double r = rand();
+
+ interval = (int)(timer + timer * RIPRANDDEV * (r / RAND_MAX - 0.5));
+ nextalarm = time(NULL) + interval;
+ return interval;
+}
+
+time_t
+ripsuptrig(void)
+{
+ time_t t;
+
+ double r = rand();
+ t = (int)(RIP_TRIG_INT6_MIN +
+ (RIP_TRIG_INT6_MAX - RIP_TRIG_INT6_MIN) * (r / RAND_MAX));
+ sup_trig_update = time(NULL) + t;
+ return t;
+}
+
+void
+#ifdef __STDC__
+fatal(const char *fmt, ...)
+#else
+fatal(fmt, va_alist)
+ char *fmt;
+ va_dcl
+#endif
+{
+ va_list ap;
+ char buf[1024];
+
+#ifdef __STDC__
+ va_start(ap, fmt);
+#else
+ va_start(ap);
+#endif
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+ perror(buf);
+ if (errno)
+ syslog(LOG_ERR, "%s: %s", buf, strerror(errno));
+ else
+ syslog(LOG_ERR, "%s", buf);
+ rtdexit();
+}
+
+void
+#ifdef __STDC__
+tracet(int level, const char *fmt, ...)
+#else
+tracet(level, fmt, va_alist)
+ int level;
+ char *fmt;
+ va_dcl
+#endif
+{
+ va_list ap;
+
+ if (level <= dflag) {
+#ifdef __STDC__
+ va_start(ap, fmt);
+#else
+ va_start(ap);
+#endif
+ fprintf(stderr, "%s: ", hms());
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ }
+ if (dflag) {
+#ifdef __STDC__
+ va_start(ap, fmt);
+#else
+ va_start(ap);
+#endif
+ if (level > 0)
+ vsyslog(LOG_DEBUG, fmt, ap);
+ else
+ vsyslog(LOG_WARNING, fmt, ap);
+ va_end(ap);
+ }
+}
+
+void
+#ifdef __STDC__
+trace(int level, const char *fmt, ...)
+#else
+trace(level, fmt, va_alist)
+ int level;
+ char *fmt;
+ va_dcl
+#endif
+{
+ va_list ap;
+
+ if (level <= dflag) {
+#ifdef __STDC__
+ va_start(ap, fmt);
+#else
+ va_start(ap);
+#endif
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ }
+ if (dflag) {
+#ifdef __STDC__
+ va_start(ap, fmt);
+#else
+ va_start(ap);
+#endif
+ if (level > 0)
+ vsyslog(LOG_DEBUG, fmt, ap);
+ else
+ vsyslog(LOG_WARNING, fmt, ap);
+ va_end(ap);
+ }
+}
+
+unsigned int
+if_maxindex(void)
+{
+ struct if_nameindex *p, *p0;
+ unsigned int max = 0;
+
+ p0 = if_nameindex();
+ for (p = p0; p && p->if_index && p->if_name; p++) {
+ if (max < p->if_index)
+ max = p->if_index;
+ }
+ if_freenameindex(p0);
+ return max;
+}
+
+struct ifc *
+ifc_find(char *name)
+{
+ struct ifc *ifcp;
+
+ TAILQ_FOREACH(ifcp, &ifc_head, ifc_next) {
+ if (strcmp(name, ifcp->ifc_name) == 0)
+ break;
+ }
+ return (ifcp);
+}
+
+struct iff *
+iff_find(struct ifc *ifcp, int type)
+{
+ struct iff *iffp;
+
+ TAILQ_FOREACH(iffp, &ifcp->ifc_iff_head, iff_next) {
+ if (type == IFIL_TYPE_ANY ||
+ type == iffp->iff_type)
+ break;
+ }
+
+ return (iffp);
+}
+
+void
+setindex2ifc(int idx, struct ifc *ifcp)
+{
+ int n, nsize;
+ struct ifc **p;
+
+ if (!index2ifc) {
+ nindex2ifc = 5; /*initial guess*/
+ index2ifc = (struct ifc **)
+ malloc(sizeof(*index2ifc) * nindex2ifc);
+ if (index2ifc == NULL) {
+ fatal("malloc");
+ /*NOTREACHED*/
+ }
+ memset(index2ifc, 0, sizeof(*index2ifc) * nindex2ifc);
+ }
+ n = nindex2ifc;
+ for (nsize = nindex2ifc; nsize <= idx; nsize *= 2)
+ ;
+ if (n != nsize) {
+ p = (struct ifc **)realloc(index2ifc,
+ sizeof(*index2ifc) * nsize);
+ if (p == NULL) {
+ fatal("realloc");
+ /*NOTREACHED*/
+ }
+ memset(p + n, 0, sizeof(*index2ifc) * (nindex2ifc - n));
+ index2ifc = p;
+ nindex2ifc = nsize;
+ }
+ index2ifc[idx] = ifcp;
+}
diff --git a/usr.sbin/route6d/route6d.h b/usr.sbin/route6d/route6d.h
new file mode 100644
index 0000000..49a7d5a
--- /dev/null
+++ b/usr.sbin/route6d/route6d.h
@@ -0,0 +1,81 @@
+/* $FreeBSD$ */
+/* $KAME: route6d.h,v 1.8 2003/05/28 09:11:13 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.
+ */
+
+#define ROUTE6D_DUMP "/var/run/route6d_dump"
+#define ROUTE6D_PID "/var/run/route6d.pid"
+
+#define RIP6_VERSION 1
+
+#define RIP6_REQUEST 1
+#define RIP6_RESPONSE 2
+
+#define IFC_CHANGED 1
+
+struct netinfo6 {
+ struct in6_addr rip6_dest;
+ u_short rip6_tag;
+ u_char rip6_plen;
+ u_char rip6_metric;
+};
+
+struct rip6 {
+ u_char rip6_cmd;
+ u_char rip6_vers;
+ u_char rip6_res1[2];
+ struct netinfo6 rip6_nets[1];
+};
+
+#define HOPCNT_INFINITY6 16
+#define NEXTHOP_METRIC 0xff
+#define RIP6_MAXMTU 1500
+
+#define IFMINMTU 1280
+
+#ifndef DEBUG
+#define SUPPLY_INTERVAL6 30
+#define RIP_LIFETIME 180
+#define RIP_HOLDDOWN 120
+#define RIP_TRIG_INT6_MAX 5
+#define RIP_TRIG_INT6_MIN 1
+#else
+/* only for debugging; can not wait for 30sec to appear a bug */
+#define SUPPLY_INTERVAL6 10
+#define RIP_LIFETIME 60
+#define RIP_HOLDDOWN 40
+#define RIP_TRIG_INT6_MAX 5
+#define RIP_TRIG_INT6_MIN 1
+#endif
+
+#define RIP6_PORT 521
+#define RIP6_DEST "ff02::9"
+
+#define LOOPBACK_IF "lo0"
diff --git a/usr.sbin/rpc.lockd/Makefile b/usr.sbin/rpc.lockd/Makefile
new file mode 100644
index 0000000..73c69ef
--- /dev/null
+++ b/usr.sbin/rpc.lockd/Makefile
@@ -0,0 +1,28 @@
+# $NetBSD: Makefile,v 1.12 2000/08/07 16:23:31 thorpej Exp $
+# $FreeBSD$
+
+PROG= rpc.lockd
+MAN= rpc.lockd.8
+MLINKS= rpc.lockd.8 lockd.8
+SRCS= kern.c nlm_prot_svc.c lockd.c lock_proc.c lockd_lock.c
+
+CFLAGS+= -I. -I${DESTDIR}/usr/include/rpcsvc
+WARNS?= 3
+
+LIBADD= rpcsvc
+
+CLEANFILES= nlm_prot_svc.c nlm_prot.h test
+
+RPCSRC= ${DESTDIR}/usr/include/rpcsvc/nlm_prot.x
+RPCGEN= RPCGEN_CPP=${CPP:Q} rpcgen -L -C
+
+nlm_prot_svc.c: ${RPCSRC}
+ ${RPCGEN} -m -o ${.TARGET} ${RPCSRC}
+
+nlm_prot.h: ${RPCSRC}
+ ${RPCGEN} -h -o ${.TARGET} ${RPCSRC}
+
+test: ${.CURDIR}/test.c
+ cc -o test ${.CURDIR}/test.c -lrpcsvc
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/rpc.lockd/Makefile.depend b/usr.sbin/rpc.lockd/Makefile.depend
new file mode 100644
index 0000000..2e2391d
--- /dev/null
+++ b/usr.sbin/rpc.lockd/Makefile.depend
@@ -0,0 +1,25 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/librpcsvc \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+nlm_prot_svc.o: nlm_prot_svc.c
+nlm_prot_svc.po: nlm_prot_svc.c
+.endif
diff --git a/usr.sbin/rpc.lockd/kern.c b/usr.sbin/rpc.lockd/kern.c
new file mode 100644
index 0000000..759e147
--- /dev/null
+++ b/usr.sbin/rpc.lockd/kern.c
@@ -0,0 +1,602 @@
+/*-
+ * Copyright (c) 1997 Berkeley Software Design, 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.
+ * 3. Berkeley Software Design Inc's name may not be used to endorse or
+ * promote products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY BERKELEY SOFTWARE DESIGN 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 BERKELEY SOFTWARE DESIGN 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.
+ *
+ * from BSDI kern.c,v 1.2 1998/11/25 22:38:27 don Exp
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <netdb.h>
+
+#include "nlm_prot.h"
+#include <nfs/nfsproto.h>
+#include <nfs/nfs_lock.h>
+
+#include "lockd.h"
+#include "lockd_lock.h"
+#include <nfsclient/nfs.h>
+
+#define DAEMON_USERNAME "daemon"
+
+/* Lock request owner. */
+typedef struct __owner {
+ pid_t pid; /* Process ID. */
+ time_t tod; /* Time-of-day. */
+} OWNER;
+static OWNER owner;
+
+static char hostname[MAXHOSTNAMELEN + 1]; /* Hostname. */
+static int devfd;
+
+static void client_cleanup(void);
+static const char *from_addr(struct sockaddr *);
+int lock_request(LOCKD_MSG *);
+static void set_auth(CLIENT *cl, struct xucred *ucred);
+void show(LOCKD_MSG *);
+int test_request(LOCKD_MSG *);
+int unlock_request(LOCKD_MSG *);
+
+static int
+nfslockdans(int vers, struct lockd_ans *ansp)
+{
+
+ ansp->la_vers = vers;
+ return (write(devfd, ansp, sizeof *ansp) <= 0);
+}
+
+/*
+ * will break because fifo needs to be repopened when EOF'd
+ */
+#define lockd_seteuid(uid) seteuid(uid)
+
+#define d_calls (debug_level > 1)
+#define d_args (debug_level > 2)
+
+static const char *
+from_addr(struct sockaddr *saddr)
+{
+ static char inet_buf[INET6_ADDRSTRLEN];
+
+ if (getnameinfo(saddr, saddr->sa_len, inet_buf, sizeof(inet_buf),
+ NULL, 0, NI_NUMERICHOST) == 0)
+ return inet_buf;
+ return "???";
+}
+
+void
+client_cleanup(void)
+{
+ (void)lockd_seteuid(0);
+ exit(-1);
+}
+
+/*
+ * client_request --
+ * Loop around messages from the kernel, forwarding them off to
+ * NLM servers.
+ */
+pid_t
+client_request(void)
+{
+ LOCKD_MSG msg;
+ int nr, ret;
+ pid_t child;
+ uid_t daemon_uid;
+ struct passwd *pw;
+
+ /* Open the dev . */
+ devfd = open(_PATH_DEV _PATH_NFSLCKDEV, O_RDWR | O_NONBLOCK);
+ if (devfd < 0) {
+ syslog(LOG_ERR, "open: %s: %m", _PATH_NFSLCKDEV);
+ goto err;
+ }
+
+ signal(SIGPIPE, SIG_IGN);
+
+ /*
+ * Create a separate process, the client code is really a separate
+ * daemon that shares a lot of code.
+ */
+ switch (child = fork()) {
+ case -1:
+ err(1, "fork");
+ case 0:
+ setproctitle("client");
+ break;
+ default:
+ setproctitle("server");
+ return (child);
+ }
+
+ signal(SIGHUP, (sig_t)client_cleanup);
+ signal(SIGTERM, (sig_t)client_cleanup);
+
+ /* Setup. */
+ (void)time(&owner.tod);
+ owner.pid = getpid();
+ (void)gethostname(hostname, sizeof(hostname) - 1);
+
+ pw = getpwnam(DAEMON_USERNAME);
+ if (pw == NULL) {
+ syslog(LOG_ERR, "getpwnam: %s: %m", DAEMON_USERNAME);
+ goto err;
+ }
+ daemon_uid = pw->pw_uid;
+ /* drop our root privileges */
+ (void)lockd_seteuid(daemon_uid);
+
+ for (;;) {
+ /* Read the fixed length message. */
+ if ((nr = read(devfd, &msg, sizeof(msg))) == sizeof(msg)) {
+ if (d_args)
+ show(&msg);
+
+ if (msg.lm_version != LOCKD_MSG_VERSION) {
+ syslog(LOG_ERR,
+ "unknown msg type: %d", msg.lm_version);
+ }
+ /*
+ * Send it to the NLM server and don't grant the lock
+ * if we fail for any reason.
+ */
+ switch (msg.lm_fl.l_type) {
+ case F_RDLCK:
+ case F_WRLCK:
+ if (msg.lm_getlk)
+ ret = test_request(&msg);
+ else
+ ret = lock_request(&msg);
+ break;
+ case F_UNLCK:
+ ret = unlock_request(&msg);
+ break;
+ default:
+ ret = 1;
+ syslog(LOG_ERR,
+ "unknown lock type: %d", msg.lm_fl.l_type);
+ break;
+ }
+ if (ret) {
+ struct lockd_ans ans;
+
+ ans.la_msg_ident = msg.lm_msg_ident;
+ ans.la_errno = EHOSTUNREACH;
+
+ if (nfslockdans(LOCKD_ANS_VERSION, &ans)) {
+ syslog((errno == EPIPE ? LOG_INFO :
+ LOG_ERR), "process %lu: %m",
+ (u_long)msg.lm_msg_ident.pid);
+ }
+ }
+ } else if (nr == -1) {
+ if (errno != EAGAIN) {
+ syslog(LOG_ERR, "read: %s: %m", _PATH_NFSLCKDEV);
+ goto err;
+ }
+ } else if (nr != 0) {
+ syslog(LOG_ERR,
+ "%s: discard %d bytes", _PATH_NFSLCKDEV, nr);
+ }
+ }
+
+ /* Reached only on error. */
+err:
+ (void)lockd_seteuid(0);
+ _exit (1);
+}
+
+void
+set_auth(CLIENT *cl, struct xucred *xucred)
+{
+ int ngroups;
+
+ ngroups = xucred->cr_ngroups - 1;
+ if (ngroups > NGRPS)
+ ngroups = NGRPS;
+ if (cl->cl_auth != NULL)
+ cl->cl_auth->ah_ops->ah_destroy(cl->cl_auth);
+ cl->cl_auth = authunix_create(hostname,
+ xucred->cr_uid,
+ xucred->cr_groups[0],
+ ngroups,
+ &xucred->cr_groups[1]);
+}
+
+
+/*
+ * test_request --
+ * Convert a lock LOCKD_MSG into an NLM request, and send it off.
+ */
+int
+test_request(LOCKD_MSG *msg)
+{
+ CLIENT *cli;
+ struct timeval timeout = {0, 0}; /* No timeout, no response. */
+ char dummy;
+
+ if (d_calls)
+ syslog(LOG_DEBUG, "test request: %s: %s to %s",
+ msg->lm_nfsv3 ? "V4" : "V1/3",
+ msg->lm_fl.l_type == F_WRLCK ? "write" : "read",
+ from_addr((struct sockaddr *)&msg->lm_addr));
+
+ if (msg->lm_nfsv3) {
+ struct nlm4_testargs arg4;
+
+ arg4.cookie.n_bytes = (char *)&msg->lm_msg_ident;
+ arg4.cookie.n_len = sizeof(msg->lm_msg_ident);
+ arg4.exclusive = msg->lm_fl.l_type == F_WRLCK ? 1 : 0;
+ arg4.alock.caller_name = hostname;
+ arg4.alock.fh.n_bytes = (char *)&msg->lm_fh;
+ arg4.alock.fh.n_len = msg->lm_fh_len;
+ arg4.alock.oh.n_bytes = (char *)&owner;
+ arg4.alock.oh.n_len = sizeof(owner);
+ arg4.alock.svid = msg->lm_msg_ident.pid;
+ arg4.alock.l_offset = msg->lm_fl.l_start;
+ arg4.alock.l_len = msg->lm_fl.l_len;
+
+ if ((cli = get_client(
+ (struct sockaddr *)&msg->lm_addr,
+ NLM_VERS4)) == NULL)
+ return (1);
+
+ set_auth(cli, &msg->lm_cred);
+ (void)clnt_call(cli, NLM_TEST_MSG,
+ (xdrproc_t)xdr_nlm4_testargs, &arg4,
+ (xdrproc_t)xdr_void, &dummy, timeout);
+ } else {
+ struct nlm_testargs arg;
+
+ arg.cookie.n_bytes = (char *)&msg->lm_msg_ident;
+ arg.cookie.n_len = sizeof(msg->lm_msg_ident);
+ arg.exclusive = msg->lm_fl.l_type == F_WRLCK ? 1 : 0;
+ arg.alock.caller_name = hostname;
+ arg.alock.fh.n_bytes = (char *)&msg->lm_fh;
+ arg.alock.fh.n_len = msg->lm_fh_len;
+ arg.alock.oh.n_bytes = (char *)&owner;
+ arg.alock.oh.n_len = sizeof(owner);
+ arg.alock.svid = msg->lm_msg_ident.pid;
+ arg.alock.l_offset = msg->lm_fl.l_start;
+ arg.alock.l_len = msg->lm_fl.l_len;
+
+ if ((cli = get_client(
+ (struct sockaddr *)&msg->lm_addr,
+ NLM_VERS)) == NULL)
+ return (1);
+
+ set_auth(cli, &msg->lm_cred);
+ (void)clnt_call(cli, NLM_TEST_MSG,
+ (xdrproc_t)xdr_nlm_testargs, &arg,
+ (xdrproc_t)xdr_void, &dummy, timeout);
+ }
+ return (0);
+}
+
+/*
+ * lock_request --
+ * Convert a lock LOCKD_MSG into an NLM request, and send it off.
+ */
+int
+lock_request(LOCKD_MSG *msg)
+{
+ CLIENT *cli;
+ struct nlm4_lockargs arg4;
+ struct nlm_lockargs arg;
+ struct timeval timeout = {0, 0}; /* No timeout, no response. */
+ char dummy;
+
+ if (d_calls)
+ syslog(LOG_DEBUG, "lock request: %s: %s to %s",
+ msg->lm_nfsv3 ? "V4" : "V1/3",
+ msg->lm_fl.l_type == F_WRLCK ? "write" : "read",
+ from_addr((struct sockaddr *)&msg->lm_addr));
+
+ if (msg->lm_nfsv3) {
+ arg4.cookie.n_bytes = (char *)&msg->lm_msg_ident;
+ arg4.cookie.n_len = sizeof(msg->lm_msg_ident);
+ arg4.block = msg->lm_wait ? 1 : 0;
+ arg4.exclusive = msg->lm_fl.l_type == F_WRLCK ? 1 : 0;
+ arg4.alock.caller_name = hostname;
+ arg4.alock.fh.n_bytes = (char *)&msg->lm_fh;
+ arg4.alock.fh.n_len = msg->lm_fh_len;
+ arg4.alock.oh.n_bytes = (char *)&owner;
+ arg4.alock.oh.n_len = sizeof(owner);
+ arg4.alock.svid = msg->lm_msg_ident.pid;
+ arg4.alock.l_offset = msg->lm_fl.l_start;
+ arg4.alock.l_len = msg->lm_fl.l_len;
+ arg4.reclaim = 0;
+ arg4.state = nsm_state;
+
+ if ((cli = get_client(
+ (struct sockaddr *)&msg->lm_addr,
+ NLM_VERS4)) == NULL)
+ return (1);
+
+ set_auth(cli, &msg->lm_cred);
+ (void)clnt_call(cli, NLM_LOCK_MSG,
+ (xdrproc_t)xdr_nlm4_lockargs, &arg4,
+ (xdrproc_t)xdr_void, &dummy, timeout);
+ } else {
+ arg.cookie.n_bytes = (char *)&msg->lm_msg_ident;
+ arg.cookie.n_len = sizeof(msg->lm_msg_ident);
+ arg.block = msg->lm_wait ? 1 : 0;
+ arg.exclusive = msg->lm_fl.l_type == F_WRLCK ? 1 : 0;
+ arg.alock.caller_name = hostname;
+ arg.alock.fh.n_bytes = (char *)&msg->lm_fh;
+ arg.alock.fh.n_len = msg->lm_fh_len;
+ arg.alock.oh.n_bytes = (char *)&owner;
+ arg.alock.oh.n_len = sizeof(owner);
+ arg.alock.svid = msg->lm_msg_ident.pid;
+ arg.alock.l_offset = msg->lm_fl.l_start;
+ arg.alock.l_len = msg->lm_fl.l_len;
+ arg.reclaim = 0;
+ arg.state = nsm_state;
+
+ if ((cli = get_client(
+ (struct sockaddr *)&msg->lm_addr,
+ NLM_VERS)) == NULL)
+ return (1);
+
+ set_auth(cli, &msg->lm_cred);
+ (void)clnt_call(cli, NLM_LOCK_MSG,
+ (xdrproc_t)xdr_nlm_lockargs, &arg,
+ (xdrproc_t)xdr_void, &dummy, timeout);
+ }
+ return (0);
+}
+
+/*
+ * unlock_request --
+ * Convert an unlock LOCKD_MSG into an NLM request, and send it off.
+ */
+int
+unlock_request(LOCKD_MSG *msg)
+{
+ CLIENT *cli;
+ struct nlm4_unlockargs arg4;
+ struct nlm_unlockargs arg;
+ struct timeval timeout = {0, 0}; /* No timeout, no response. */
+ char dummy;
+
+ if (d_calls)
+ syslog(LOG_DEBUG, "unlock request: %s: to %s",
+ msg->lm_nfsv3 ? "V4" : "V1/3",
+ from_addr((struct sockaddr *)&msg->lm_addr));
+
+ if (msg->lm_nfsv3) {
+ arg4.cookie.n_bytes = (char *)&msg->lm_msg_ident;
+ arg4.cookie.n_len = sizeof(msg->lm_msg_ident);
+ arg4.alock.caller_name = hostname;
+ arg4.alock.fh.n_bytes = (char *)&msg->lm_fh;
+ arg4.alock.fh.n_len = msg->lm_fh_len;
+ arg4.alock.oh.n_bytes = (char *)&owner;
+ arg4.alock.oh.n_len = sizeof(owner);
+ arg4.alock.svid = msg->lm_msg_ident.pid;
+ arg4.alock.l_offset = msg->lm_fl.l_start;
+ arg4.alock.l_len = msg->lm_fl.l_len;
+
+ if ((cli = get_client(
+ (struct sockaddr *)&msg->lm_addr,
+ NLM_VERS4)) == NULL)
+ return (1);
+
+ set_auth(cli, &msg->lm_cred);
+ (void)clnt_call(cli, NLM_UNLOCK_MSG,
+ (xdrproc_t)xdr_nlm4_unlockargs, &arg4,
+ (xdrproc_t)xdr_void, &dummy, timeout);
+ } else {
+ arg.cookie.n_bytes = (char *)&msg->lm_msg_ident;
+ arg.cookie.n_len = sizeof(msg->lm_msg_ident);
+ arg.alock.caller_name = hostname;
+ arg.alock.fh.n_bytes = (char *)&msg->lm_fh;
+ arg.alock.fh.n_len = msg->lm_fh_len;
+ arg.alock.oh.n_bytes = (char *)&owner;
+ arg.alock.oh.n_len = sizeof(owner);
+ arg.alock.svid = msg->lm_msg_ident.pid;
+ arg.alock.l_offset = msg->lm_fl.l_start;
+ arg.alock.l_len = msg->lm_fl.l_len;
+
+ if ((cli = get_client(
+ (struct sockaddr *)&msg->lm_addr,
+ NLM_VERS)) == NULL)
+ return (1);
+
+ set_auth(cli, &msg->lm_cred);
+ (void)clnt_call(cli, NLM_UNLOCK_MSG,
+ (xdrproc_t)xdr_nlm_unlockargs, &arg,
+ (xdrproc_t)xdr_void, &dummy, timeout);
+ }
+
+ return (0);
+}
+
+int
+lock_answer(int pid, netobj *netcookie, int result, int *pid_p, int version)
+{
+ struct lockd_ans ans;
+
+ if (netcookie->n_len != sizeof(ans.la_msg_ident)) {
+ if (pid == -1) { /* we're screwed */
+ syslog(LOG_ERR, "inedible nlm cookie");
+ return -1;
+ }
+ ans.la_msg_ident.pid = pid;
+ ans.la_msg_ident.msg_seq = -1;
+ } else {
+ memcpy(&ans.la_msg_ident, netcookie->n_bytes,
+ sizeof(ans.la_msg_ident));
+ }
+
+ if (d_calls)
+ syslog(LOG_DEBUG, "lock answer: pid %lu: %s %d",
+ (unsigned long)ans.la_msg_ident.pid,
+ version == NLM_VERS4 ? "nlmv4" : "nlmv3",
+ result);
+
+ ans.la_set_getlk_pid = 0;
+ if (version == NLM_VERS4)
+ switch (result) {
+ case nlm4_granted:
+ ans.la_errno = 0;
+ break;
+ default:
+ ans.la_errno = EACCES;
+ break;
+ case nlm4_denied:
+ if (pid_p == NULL)
+ ans.la_errno = EAGAIN;
+ else {
+ /* this is an answer to a nlm_test msg */
+ ans.la_set_getlk_pid = 1;
+ ans.la_getlk_pid = *pid_p;
+ ans.la_errno = 0;
+ }
+ break;
+ case nlm4_denied_nolocks:
+ ans.la_errno = EAGAIN;
+ break;
+ case nlm4_blocked:
+ return -1;
+ /* NOTREACHED */
+ case nlm4_denied_grace_period:
+ ans.la_errno = EAGAIN;
+ break;
+ case nlm4_deadlck:
+ ans.la_errno = EDEADLK;
+ break;
+ case nlm4_rofs:
+ ans.la_errno = EROFS;
+ break;
+ case nlm4_stale_fh:
+ ans.la_errno = ESTALE;
+ break;
+ case nlm4_fbig:
+ ans.la_errno = EFBIG;
+ break;
+ case nlm4_failed:
+ ans.la_errno = EACCES;
+ break;
+ }
+ else
+ switch (result) {
+ case nlm_granted:
+ ans.la_errno = 0;
+ break;
+ default:
+ ans.la_errno = EACCES;
+ break;
+ case nlm_denied:
+ if (pid_p == NULL)
+ ans.la_errno = EAGAIN;
+ else {
+ /* this is an answer to a nlm_test msg */
+ ans.la_set_getlk_pid = 1;
+ ans.la_getlk_pid = *pid_p;
+ ans.la_errno = 0;
+ }
+ break;
+ case nlm_denied_nolocks:
+ ans.la_errno = EAGAIN;
+ break;
+ case nlm_blocked:
+ return -1;
+ /* NOTREACHED */
+ case nlm_denied_grace_period:
+ ans.la_errno = EAGAIN;
+ break;
+ case nlm_deadlck:
+ ans.la_errno = EDEADLK;
+ break;
+ }
+
+ if (nfslockdans(LOCKD_ANS_VERSION, &ans)) {
+ syslog(((errno == EPIPE || errno == ESRCH) ?
+ LOG_INFO : LOG_ERR),
+ "process %lu: %m", (u_long)ans.la_msg_ident.pid);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * show --
+ * Display the contents of a kernel LOCKD_MSG structure.
+ */
+void
+show(LOCKD_MSG *mp)
+{
+ static char hex[] = "0123456789abcdef";
+ struct fid *fidp;
+ fsid_t *fsidp;
+ size_t len;
+ u_int8_t *p, *t, buf[NFS_SMALLFH*3+1];
+
+ syslog(LOG_DEBUG, "process ID: %lu\n", (long)mp->lm_msg_ident.pid);
+
+ fsidp = (fsid_t *)&mp->lm_fh;
+ fidp = (struct fid *)((u_int8_t *)&mp->lm_fh + sizeof(fsid_t));
+
+ for (t = buf, p = (u_int8_t *)mp->lm_fh,
+ len = mp->lm_fh_len;
+ len > 0; ++p, --len) {
+ *t++ = '\\';
+ *t++ = hex[(*p & 0xf0) >> 4];
+ *t++ = hex[*p & 0x0f];
+ }
+ *t = '\0';
+
+ syslog(LOG_DEBUG, "fh_len %d, fh %s\n", (int)mp->lm_fh_len, buf);
+
+ /* Show flock structure. */
+ syslog(LOG_DEBUG, "start %llu; len %llu; pid %lu; type %d; whence %d\n",
+ (unsigned long long)mp->lm_fl.l_start,
+ (unsigned long long)mp->lm_fl.l_len, (u_long)mp->lm_fl.l_pid,
+ mp->lm_fl.l_type, mp->lm_fl.l_whence);
+
+ /* Show wait flag. */
+ syslog(LOG_DEBUG, "wait was %s\n", mp->lm_wait ? "set" : "not set");
+}
diff --git a/usr.sbin/rpc.lockd/lock_proc.c b/usr.sbin/rpc.lockd/lock_proc.c
new file mode 100644
index 0000000..8884ad9
--- /dev/null
+++ b/usr.sbin/rpc.lockd/lock_proc.c
@@ -0,0 +1,1321 @@
+/* $NetBSD: lock_proc.c,v 1.7 2000/10/11 20:23:56 is Exp $ */
+/* $FreeBSD$ */
+/*
+ * Copyright (c) 1995
+ * A.R. Gordon (andrew.gordon@net-tel.co.uk). All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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
+ * 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 ANDREW GORDON AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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>
+#ifndef lint
+__RCSID("$NetBSD: lock_proc.c,v 1.7 2000/10/11 20:23:56 is Exp $");
+#endif
+
+#include <sys/param.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <netdb.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <netconfig.h>
+
+#include <rpc/rpc.h>
+#include <rpcsvc/sm_inter.h>
+
+#include "lockd.h"
+#include <rpcsvc/nlm_prot.h>
+#include "lockd_lock.h"
+
+
+#define CLIENT_CACHE_SIZE 64 /* No. of client sockets cached */
+#define CLIENT_CACHE_LIFETIME 120 /* In seconds */
+
+#define getrpcaddr(rqstp) (struct sockaddr *)(svc_getrpccaller((rqstp)->rq_xprt)->buf)
+
+static void log_from_addr(const char *, struct svc_req *);
+static void log_netobj(netobj *obj);
+static int addrcmp(struct sockaddr *, struct sockaddr *);
+
+/* log_from_addr ----------------------------------------------------------- */
+/*
+ * Purpose: Log name of function called and source address
+ * Returns: Nothing
+ * Notes: Extracts the source address from the transport handle
+ * passed in as part of the called procedure specification
+ */
+static void
+log_from_addr(const char *fun_name, struct svc_req *req)
+{
+ struct sockaddr *addr;
+ char hostname_buf[NI_MAXHOST];
+
+ addr = svc_getrpccaller(req->rq_xprt)->buf;
+ if (getnameinfo(addr , addr->sa_len, hostname_buf, sizeof hostname_buf,
+ NULL, 0, 0) != 0)
+ return;
+
+ syslog(LOG_DEBUG, "%s from %s", fun_name, hostname_buf);
+}
+
+/* log_netobj ----------------------------------------------------------- */
+/*
+ * Purpose: Log a netobj
+ * Returns: Nothing
+ * Notes: This function should only really be called as part of
+ * a debug subsystem.
+*/
+static void
+log_netobj(netobj *obj)
+{
+ char objvalbuffer[(sizeof(char)*2)*MAX_NETOBJ_SZ+2];
+ char objascbuffer[sizeof(char)*MAX_NETOBJ_SZ+1];
+ unsigned int i, maxlen;
+ char *tmp1, *tmp2;
+
+ /* Notify of potential security attacks */
+ if (obj->n_len > MAX_NETOBJ_SZ) {
+ syslog(LOG_DEBUG, "SOMEONE IS TRYING TO DO SOMETHING NASTY!\n");
+ syslog(LOG_DEBUG, "netobj too large! Should be %d was %d\n",
+ MAX_NETOBJ_SZ, obj->n_len);
+ }
+ /* Prevent the security hazard from the buffer overflow */
+ maxlen = (obj->n_len < MAX_NETOBJ_SZ ? obj->n_len : MAX_NETOBJ_SZ);
+ for (i=0, tmp1 = objvalbuffer, tmp2 = objascbuffer; i < obj->n_len;
+ i++, tmp1 +=2, tmp2 +=1) {
+ sprintf(tmp1,"%02X",*(obj->n_bytes+i));
+ sprintf(tmp2,"%c",*(obj->n_bytes+i));
+ }
+ *tmp1 = '\0';
+ *tmp2 = '\0';
+ syslog(LOG_DEBUG,"netobjvals: %s\n",objvalbuffer);
+ syslog(LOG_DEBUG,"netobjascs: %s\n",objascbuffer);
+}
+/* get_client -------------------------------------------------------------- */
+/*
+ * Purpose: Get a CLIENT* for making RPC calls to lockd on given host
+ * Returns: CLIENT* pointer, from clnt_udp_create, or NULL if error
+ * Notes: Creating a CLIENT* is quite expensive, involving a
+ * conversation with the remote portmapper to get the
+ * port number. Since a given client is quite likely
+ * to make several locking requests in succession, it is
+ * desirable to cache the created CLIENT*.
+ *
+ * Since we are using UDP rather than TCP, there is no cost
+ * to the remote system in keeping these cached indefinitely.
+ * Unfortunately there is a snag: if the remote system
+ * reboots, the cached portmapper results will be invalid,
+ * and we will never detect this since all of the xxx_msg()
+ * calls return no result - we just fire off a udp packet
+ * and hope for the best.
+ *
+ * We solve this by discarding cached values after two
+ * minutes, regardless of whether they have been used
+ * in the meanwhile (since a bad one might have been used
+ * plenty of times, as the host keeps retrying the request
+ * and we keep sending the reply back to the wrong port).
+ *
+ * Given that the entries will always expire in the order
+ * that they were created, there is no point in a LRU
+ * algorithm for when the cache gets full - entries are
+ * always re-used in sequence.
+ */
+static CLIENT *clnt_cache_ptr[CLIENT_CACHE_SIZE];
+static long clnt_cache_time[CLIENT_CACHE_SIZE]; /* time entry created */
+static struct sockaddr_storage clnt_cache_addr[CLIENT_CACHE_SIZE];
+static rpcvers_t clnt_cache_vers[CLIENT_CACHE_SIZE];
+static int clnt_cache_next_to_use = 0;
+
+static int
+addrcmp(struct sockaddr *sa1, struct sockaddr *sa2)
+{
+ int len;
+ void *p1, *p2;
+
+ if (sa1->sa_family != sa2->sa_family)
+ return -1;
+
+ switch (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;
+ break;
+ default:
+ return -1;
+ }
+
+ return memcmp(p1, p2, len);
+}
+
+CLIENT *
+get_client(struct sockaddr *host_addr, rpcvers_t vers)
+{
+ CLIENT *client;
+ struct timeval retry_time, time_now;
+ int error, i;
+ const char *netid;
+ struct netconfig *nconf;
+ char host[NI_MAXHOST];
+ uid_t old_euid;
+ int clnt_fd;
+
+ gettimeofday(&time_now, NULL);
+
+ /*
+ * Search for the given client in the cache, zapping any expired
+ * entries that we happen to notice in passing.
+ */
+ for (i = 0; i < CLIENT_CACHE_SIZE; i++) {
+ client = clnt_cache_ptr[i];
+ if (client && ((clnt_cache_time[i] + CLIENT_CACHE_LIFETIME)
+ < time_now.tv_sec)) {
+ /* Cache entry has expired. */
+ if (debug_level > 3)
+ syslog(LOG_DEBUG, "Expired CLIENT* in cache");
+ clnt_cache_time[i] = 0L;
+ clnt_destroy(client);
+ clnt_cache_ptr[i] = NULL;
+ client = NULL;
+ }
+ if (client && !addrcmp((struct sockaddr *)&clnt_cache_addr[i],
+ host_addr) && clnt_cache_vers[i] == vers) {
+ /* Found it! */
+ if (debug_level > 3)
+ syslog(LOG_DEBUG, "Found CLIENT* in cache");
+ return (client);
+ }
+ }
+
+ if (debug_level > 3)
+ syslog(LOG_DEBUG, "CLIENT* not found in cache, creating");
+
+ /* Not found in cache. Free the next entry if it is in use. */
+ if (clnt_cache_ptr[clnt_cache_next_to_use]) {
+ clnt_destroy(clnt_cache_ptr[clnt_cache_next_to_use]);
+ clnt_cache_ptr[clnt_cache_next_to_use] = NULL;
+ }
+
+ /*
+ * Need a host string for clnt_tp_create. Use NI_NUMERICHOST
+ * to avoid DNS lookups.
+ */
+ error = getnameinfo(host_addr, host_addr->sa_len, host, sizeof host,
+ NULL, 0, NI_NUMERICHOST);
+ if (error != 0) {
+ syslog(LOG_ERR, "unable to get name string for caller: %s",
+ gai_strerror(error));
+ return NULL;
+ }
+
+#if 1
+ if (host_addr->sa_family == AF_INET6)
+ netid = "udp6";
+ else
+ netid = "udp";
+#else
+ if (host_addr->sa_family == AF_INET6)
+ netid = "tcp6";
+ else
+ netid = "tcp";
+#endif
+ nconf = getnetconfigent(netid);
+ if (nconf == NULL) {
+ syslog(LOG_ERR, "could not get netconfig info for '%s': "
+ "no /etc/netconfig file?", netid);
+ return NULL;
+ }
+
+ client = clnt_tp_create(host, NLM_PROG, vers, nconf);
+ freenetconfigent(nconf);
+
+ if (!client) {
+ syslog(LOG_ERR, "%s", clnt_spcreateerror("clntudp_create"));
+ syslog(LOG_ERR, "Unable to return result to %s", host);
+ return NULL;
+ }
+
+ /* Get the FD of the client, for bindresvport. */
+ clnt_control(client, CLGET_FD, &clnt_fd);
+
+ /* Regain root privileges, for bindresvport. */
+ old_euid = geteuid();
+ seteuid(0);
+
+ /*
+ * Bind the client FD to a reserved port.
+ * Some NFS servers reject any NLM request from a non-reserved port.
+ */
+ bindresvport(clnt_fd, NULL);
+
+ /* Drop root privileges again. */
+ seteuid(old_euid);
+
+ /* Success - update the cache entry */
+ clnt_cache_ptr[clnt_cache_next_to_use] = client;
+ memcpy(&clnt_cache_addr[clnt_cache_next_to_use], host_addr,
+ host_addr->sa_len);
+ clnt_cache_vers[clnt_cache_next_to_use] = vers;
+ clnt_cache_time[clnt_cache_next_to_use] = time_now.tv_sec;
+ if (++clnt_cache_next_to_use >= CLIENT_CACHE_SIZE)
+ clnt_cache_next_to_use = 0;
+
+ /*
+ * Disable the default timeout, so we can specify our own in calls
+ * to clnt_call(). (Note that the timeout is a different concept
+ * from the retry period set in clnt_udp_create() above.)
+ */
+ retry_time.tv_sec = -1;
+ retry_time.tv_usec = -1;
+ clnt_control(client, CLSET_TIMEOUT, (char *)&retry_time);
+
+ if (debug_level > 3)
+ syslog(LOG_DEBUG, "Created CLIENT* for %s", host);
+ return client;
+}
+
+
+/* transmit_result --------------------------------------------------------- */
+/*
+ * Purpose: Transmit result for nlm_xxx_msg pseudo-RPCs
+ * Returns: Nothing - we have no idea if the datagram got there
+ * Notes: clnt_call() will always fail (with timeout) as we are
+ * calling it with timeout 0 as a hack to just issue a datagram
+ * without expecting a result
+ */
+void
+transmit_result(int opcode, nlm_res *result, struct sockaddr *addr)
+{
+ static char dummy;
+ CLIENT *cli;
+ struct timeval timeo;
+ int success;
+
+ if ((cli = get_client(addr, NLM_VERS)) != NULL) {
+ timeo.tv_sec = 0; /* No timeout - not expecting response */
+ timeo.tv_usec = 0;
+
+ success = clnt_call(cli, opcode, (xdrproc_t)xdr_nlm_res, result,
+ (xdrproc_t)xdr_void, &dummy, timeo);
+
+ if (debug_level > 2)
+ syslog(LOG_DEBUG, "clnt_call returns %d(%s)",
+ success, clnt_sperrno(success));
+ }
+}
+/* transmit4_result --------------------------------------------------------- */
+/*
+ * Purpose: Transmit result for nlm4_xxx_msg pseudo-RPCs
+ * Returns: Nothing - we have no idea if the datagram got there
+ * Notes: clnt_call() will always fail (with timeout) as we are
+ * calling it with timeout 0 as a hack to just issue a datagram
+ * without expecting a result
+ */
+void
+transmit4_result(int opcode, nlm4_res *result, struct sockaddr *addr)
+{
+ static char dummy;
+ CLIENT *cli;
+ struct timeval timeo;
+ int success;
+
+ if ((cli = get_client(addr, NLM_VERS4)) != NULL) {
+ timeo.tv_sec = 0; /* No timeout - not expecting response */
+ timeo.tv_usec = 0;
+
+ success = clnt_call(cli, opcode,
+ (xdrproc_t)xdr_nlm4_res, result,
+ (xdrproc_t)xdr_void, &dummy, timeo);
+
+ if (debug_level > 2)
+ syslog(LOG_DEBUG, "clnt_call returns %d(%s)",
+ success, clnt_sperrno(success));
+ }
+}
+
+/*
+ * converts a struct nlm_lock to struct nlm4_lock
+ */
+static void
+nlmtonlm4(struct nlm_lock *arg, struct nlm4_lock *arg4)
+{
+ arg4->caller_name = arg->caller_name;
+ arg4->fh = arg->fh;
+ arg4->oh = arg->oh;
+ arg4->svid = arg->svid;
+ arg4->l_offset = arg->l_offset;
+ arg4->l_len = arg->l_len;
+}
+/* ------------------------------------------------------------------------- */
+/*
+ * Functions for Unix<->Unix locking (ie. monitored locking, with rpc.statd
+ * involved to ensure reclaim of locks after a crash of the "stateless"
+ * server.
+ *
+ * These all come in two flavours - nlm_xxx() and nlm_xxx_msg().
+ * The first are standard RPCs with argument and result.
+ * The nlm_xxx_msg() calls implement exactly the same functions, but
+ * use two pseudo-RPCs (one in each direction). These calls are NOT
+ * standard use of the RPC protocol in that they do not return a result
+ * at all (NB. this is quite different from returning a void result).
+ * The effect of this is to make the nlm_xxx_msg() calls simple unacknowledged
+ * datagrams, requiring higher-level code to perform retries.
+ *
+ * Despite the disadvantages of the nlm_xxx_msg() approach (some of which
+ * are documented in the comments to get_client() above), this is the
+ * interface used by all current commercial NFS implementations
+ * [Solaris, SCO, AIX etc.]. This is presumed to be because these allow
+ * implementations to continue using the standard RPC libraries, while
+ * avoiding the block-until-result nature of the library interface.
+ *
+ * No client implementations have been identified so far that make use
+ * of the true RPC version (early SunOS releases would be a likely candidate
+ * for testing).
+ */
+
+/* nlm_test ---------------------------------------------------------------- */
+/*
+ * Purpose: Test whether a specified lock would be granted if requested
+ * Returns: nlm_granted (or error code)
+ * Notes:
+ */
+nlm_testres *
+nlm_test_1_svc(nlm_testargs *arg, struct svc_req *rqstp)
+{
+ static nlm_testres res;
+ struct nlm4_lock arg4;
+ struct nlm4_holder *holder;
+ nlmtonlm4(&arg->alock, &arg4);
+
+ if (debug_level)
+ log_from_addr("nlm_test", rqstp);
+
+ holder = testlock(&arg4, arg->exclusive, 0);
+ /*
+ * Copy the cookie from the argument into the result. Note that this
+ * is slightly hazardous, as the structure contains a pointer to a
+ * malloc()ed buffer that will get freed by the caller. However, the
+ * main function transmits the result before freeing the argument
+ * so it is in fact safe.
+ */
+ res.cookie = arg->cookie;
+ if (holder == NULL) {
+ res.stat.stat = nlm_granted;
+ } else {
+ res.stat.stat = nlm_denied;
+ memcpy(&res.stat.nlm_testrply_u.holder, holder,
+ sizeof(struct nlm_holder));
+ res.stat.nlm_testrply_u.holder.l_offset = holder->l_offset;
+ res.stat.nlm_testrply_u.holder.l_len = holder->l_len;
+ }
+ return (&res);
+}
+
+void *
+nlm_test_msg_1_svc(nlm_testargs *arg, struct svc_req *rqstp)
+{
+ nlm_testres res;
+ static char dummy;
+ struct sockaddr *addr;
+ CLIENT *cli;
+ int success;
+ struct timeval timeo;
+ struct nlm4_lock arg4;
+ struct nlm4_holder *holder;
+
+ nlmtonlm4(&arg->alock, &arg4);
+
+ if (debug_level)
+ log_from_addr("nlm_test_msg", rqstp);
+
+ holder = testlock(&arg4, arg->exclusive, 0);
+
+ res.cookie = arg->cookie;
+ if (holder == NULL) {
+ res.stat.stat = nlm_granted;
+ } else {
+ res.stat.stat = nlm_denied;
+ memcpy(&res.stat.nlm_testrply_u.holder, holder,
+ sizeof(struct nlm_holder));
+ res.stat.nlm_testrply_u.holder.l_offset = holder->l_offset;
+ res.stat.nlm_testrply_u.holder.l_len = holder->l_len;
+ }
+
+ /*
+ * nlm_test has different result type to the other operations, so
+ * can't use transmit_result() in this case
+ */
+ addr = svc_getrpccaller(rqstp->rq_xprt)->buf;
+ if ((cli = get_client(addr, NLM_VERS)) != NULL) {
+ timeo.tv_sec = 0; /* No timeout - not expecting response */
+ timeo.tv_usec = 0;
+
+ success = clnt_call(cli, NLM_TEST_RES,
+ (xdrproc_t)xdr_nlm_testres, &res,
+ (xdrproc_t)xdr_void, &dummy, timeo);
+
+ if (debug_level > 2)
+ syslog(LOG_DEBUG, "clnt_call returns %d", success);
+ }
+ return (NULL);
+}
+
+/* nlm_lock ---------------------------------------------------------------- */
+/*
+ * Purposes: Establish a lock
+ * Returns: granted, denied or blocked
+ * Notes: *** grace period support missing
+ */
+nlm_res *
+nlm_lock_1_svc(nlm_lockargs *arg, struct svc_req *rqstp)
+{
+ static nlm_res res;
+ struct nlm4_lockargs arg4;
+ nlmtonlm4(&arg->alock, &arg4.alock);
+ arg4.cookie = arg->cookie;
+ arg4.block = arg->block;
+ arg4.exclusive = arg->exclusive;
+ arg4.reclaim = arg->reclaim;
+ arg4.state = arg->state;
+
+ if (debug_level)
+ log_from_addr("nlm_lock", rqstp);
+
+ /* copy cookie from arg to result. See comment in nlm_test_1() */
+ res.cookie = arg->cookie;
+
+ res.stat.stat = getlock(&arg4, rqstp, LOCK_MON);
+ return (&res);
+}
+
+void *
+nlm_lock_msg_1_svc(nlm_lockargs *arg, struct svc_req *rqstp)
+{
+ static nlm_res res;
+ struct nlm4_lockargs arg4;
+
+ nlmtonlm4(&arg->alock, &arg4.alock);
+ arg4.cookie = arg->cookie;
+ arg4.block = arg->block;
+ arg4.exclusive = arg->exclusive;
+ arg4.reclaim = arg->reclaim;
+ arg4.state = arg->state;
+
+ if (debug_level)
+ log_from_addr("nlm_lock_msg", rqstp);
+
+ res.cookie = arg->cookie;
+ res.stat.stat = getlock(&arg4, rqstp, LOCK_ASYNC | LOCK_MON);
+ transmit_result(NLM_LOCK_RES, &res, getrpcaddr(rqstp));
+
+ return (NULL);
+}
+
+/* nlm_cancel -------------------------------------------------------------- */
+/*
+ * Purpose: Cancel a blocked lock request
+ * Returns: granted or denied
+ * Notes:
+ */
+nlm_res *
+nlm_cancel_1_svc(nlm_cancargs *arg, struct svc_req *rqstp)
+{
+ static nlm_res res;
+ struct nlm4_lock arg4;
+
+ nlmtonlm4(&arg->alock, &arg4);
+
+ if (debug_level)
+ log_from_addr("nlm_cancel", rqstp);
+
+ /* copy cookie from arg to result. See comment in nlm_test_1() */
+ res.cookie = arg->cookie;
+
+ /*
+ * Since at present we never return 'nlm_blocked', there can never be
+ * a lock to cancel, so this call always fails.
+ */
+ res.stat.stat = unlock(&arg4, LOCK_CANCEL);
+ return (&res);
+}
+
+void *
+nlm_cancel_msg_1_svc(nlm_cancargs *arg, struct svc_req *rqstp)
+{
+ static nlm_res res;
+ struct nlm4_lock arg4;
+
+ nlmtonlm4(&arg->alock, &arg4);
+
+ if (debug_level)
+ log_from_addr("nlm_cancel_msg", rqstp);
+
+ res.cookie = arg->cookie;
+ /*
+ * Since at present we never return 'nlm_blocked', there can never be
+ * a lock to cancel, so this call always fails.
+ */
+ res.stat.stat = unlock(&arg4, LOCK_CANCEL);
+ transmit_result(NLM_CANCEL_RES, &res, getrpcaddr(rqstp));
+ return (NULL);
+}
+
+/* nlm_unlock -------------------------------------------------------------- */
+/*
+ * Purpose: Release an existing lock
+ * Returns: Always granted, unless during grace period
+ * Notes: "no such lock" error condition is ignored, as the
+ * protocol uses unreliable UDP datagrams, and may well
+ * re-try an unlock that has already succeeded.
+ */
+nlm_res *
+nlm_unlock_1_svc(nlm_unlockargs *arg, struct svc_req *rqstp)
+{
+ static nlm_res res;
+ struct nlm4_lock arg4;
+
+ nlmtonlm4(&arg->alock, &arg4);
+
+ if (debug_level)
+ log_from_addr("nlm_unlock", rqstp);
+
+ res.stat.stat = unlock(&arg4, 0);
+ res.cookie = arg->cookie;
+
+ return (&res);
+}
+
+void *
+nlm_unlock_msg_1_svc(nlm_unlockargs *arg, struct svc_req *rqstp)
+{
+ static nlm_res res;
+ struct nlm4_lock arg4;
+
+ nlmtonlm4(&arg->alock, &arg4);
+
+ if (debug_level)
+ log_from_addr("nlm_unlock_msg", rqstp);
+
+ res.stat.stat = unlock(&arg4, 0);
+ res.cookie = arg->cookie;
+
+ transmit_result(NLM_UNLOCK_RES, &res, getrpcaddr(rqstp));
+ return (NULL);
+}
+
+/* ------------------------------------------------------------------------- */
+/*
+ * Client-side pseudo-RPCs for results. Note that for the client there
+ * are only nlm_xxx_msg() versions of each call, since the 'real RPC'
+ * version returns the results in the RPC result, and so the client
+ * does not normally receive incoming RPCs.
+ *
+ * The exception to this is nlm_granted(), which is genuinely an RPC
+ * call from the server to the client - a 'call-back' in normal procedure
+ * call terms.
+ */
+
+/* nlm_granted ------------------------------------------------------------- */
+/*
+ * Purpose: Receive notification that formerly blocked lock now granted
+ * Returns: always success ('granted')
+ * Notes:
+ */
+nlm_res *
+nlm_granted_1_svc(nlm_testargs *arg, struct svc_req *rqstp)
+{
+ static nlm_res res;
+
+ if (debug_level)
+ log_from_addr("nlm_granted", rqstp);
+
+ res.stat.stat = lock_answer(arg->alock.svid, &arg->cookie,
+ nlm_granted, NULL, NLM_VERS) == 0 ?
+ nlm_granted : nlm_denied;
+
+ /* copy cookie from arg to result. See comment in nlm_test_1() */
+ res.cookie = arg->cookie;
+
+ return (&res);
+}
+
+void *
+nlm_granted_msg_1_svc(nlm_testargs *arg, struct svc_req *rqstp)
+{
+ static nlm_res res;
+
+ if (debug_level)
+ log_from_addr("nlm_granted_msg", rqstp);
+
+ res.cookie = arg->cookie;
+ res.stat.stat = lock_answer(arg->alock.svid, &arg->cookie,
+ nlm_granted, NULL, NLM_VERS) == 0 ?
+ nlm_granted : nlm_denied;
+
+ transmit_result(NLM_GRANTED_RES, &res, getrpcaddr(rqstp));
+ return (NULL);
+}
+
+/* nlm_test_res ------------------------------------------------------------ */
+/*
+ * Purpose: Accept result from earlier nlm_test_msg() call
+ * Returns: Nothing
+ */
+void *
+nlm_test_res_1_svc(nlm_testres *arg, struct svc_req *rqstp)
+{
+ if (debug_level)
+ log_from_addr("nlm_test_res", rqstp);
+ (void)lock_answer(-1, &arg->cookie, arg->stat.stat,
+ &arg->stat.nlm_testrply_u.holder.svid, NLM_VERS);
+ return (NULL);
+}
+
+/* nlm_lock_res ------------------------------------------------------------ */
+/*
+ * Purpose: Accept result from earlier nlm_lock_msg() call
+ * Returns: Nothing
+ */
+void *
+nlm_lock_res_1_svc(nlm_res *arg, struct svc_req *rqstp)
+{
+ if (debug_level)
+ log_from_addr("nlm_lock_res", rqstp);
+
+ (void)lock_answer(-1, &arg->cookie, arg->stat.stat, NULL, NLM_VERS);
+
+ return (NULL);
+}
+
+/* nlm_cancel_res ---------------------------------------------------------- */
+/*
+ * Purpose: Accept result from earlier nlm_cancel_msg() call
+ * Returns: Nothing
+ */
+void *
+nlm_cancel_res_1_svc(nlm_res *arg __unused, struct svc_req *rqstp)
+{
+ if (debug_level)
+ log_from_addr("nlm_cancel_res", rqstp);
+ return (NULL);
+}
+
+/* nlm_unlock_res ---------------------------------------------------------- */
+/*
+ * Purpose: Accept result from earlier nlm_unlock_msg() call
+ * Returns: Nothing
+ */
+void *
+nlm_unlock_res_1_svc(nlm_res *arg, struct svc_req *rqstp)
+{
+ if (debug_level)
+ log_from_addr("nlm_unlock_res", rqstp);
+
+ lock_answer(-1, &arg->cookie, arg->stat.stat, NULL, NLM_VERS);
+
+ return (NULL);
+}
+
+/* nlm_granted_res --------------------------------------------------------- */
+/*
+ * Purpose: Accept result from earlier nlm_granted_msg() call
+ * Returns: Nothing
+ */
+void *
+nlm_granted_res_1_svc(nlm_res *arg __unused, struct svc_req *rqstp)
+{
+ if (debug_level)
+ log_from_addr("nlm_granted_res", rqstp);
+ return (NULL);
+}
+
+/* ------------------------------------------------------------------------- */
+/*
+ * Calls for PCNFS locking (aka non-monitored locking, no involvement
+ * of rpc.statd).
+ *
+ * These are all genuine RPCs - no nlm_xxx_msg() nonsense here.
+ */
+
+/* nlm_share --------------------------------------------------------------- */
+/*
+ * Purpose: Establish a DOS-style lock
+ * Returns: success or failure
+ * Notes: Blocking locks are not supported - client is expected
+ * to retry if required.
+ */
+nlm_shareres *
+nlm_share_3_svc(nlm_shareargs *arg, struct svc_req *rqstp)
+{
+ static nlm_shareres res;
+
+ if (debug_level)
+ log_from_addr("nlm_share", rqstp);
+
+ res.cookie = arg->cookie;
+ res.stat = nlm_granted;
+ res.sequence = 1234356; /* X/Open says this field is ignored? */
+ return (&res);
+}
+
+/* nlm_unshare ------------------------------------------------------------ */
+/*
+ * Purpose: Release a DOS-style lock
+ * Returns: nlm_granted, unless in grace period
+ * Notes:
+ */
+nlm_shareres *
+nlm_unshare_3_svc(nlm_shareargs *arg, struct svc_req *rqstp)
+{
+ static nlm_shareres res;
+
+ if (debug_level)
+ log_from_addr("nlm_unshare", rqstp);
+
+ res.cookie = arg->cookie;
+ res.stat = nlm_granted;
+ res.sequence = 1234356; /* X/Open says this field is ignored? */
+ return (&res);
+}
+
+/* nlm_nm_lock ------------------------------------------------------------ */
+/*
+ * Purpose: non-monitored version of nlm_lock()
+ * Returns: as for nlm_lock()
+ * Notes: These locks are in the same style as the standard nlm_lock,
+ * but the rpc.statd should not be called to establish a
+ * monitor for the client machine, since that machine is
+ * declared not to be running a rpc.statd, and so would not
+ * respond to the statd protocol.
+ */
+nlm_res *
+nlm_nm_lock_3_svc(nlm_lockargs *arg, struct svc_req *rqstp)
+{
+ static nlm_res res;
+
+ if (debug_level)
+ log_from_addr("nlm_nm_lock", rqstp);
+
+ /* copy cookie from arg to result. See comment in nlm_test_1() */
+ res.cookie = arg->cookie;
+ res.stat.stat = nlm_granted;
+ return (&res);
+}
+
+/* nlm_free_all ------------------------------------------------------------ */
+/*
+ * Purpose: Release all locks held by a named client
+ * Returns: Nothing
+ * Notes: Potential denial of service security problem here - the
+ * locks to be released are specified by a host name, independent
+ * of the address from which the request has arrived.
+ * Should probably be rejected if the named host has been
+ * using monitored locks.
+ */
+void *
+nlm_free_all_3_svc(nlm_notify *arg __unused, struct svc_req *rqstp)
+{
+ static char dummy;
+
+ if (debug_level)
+ log_from_addr("nlm_free_all", rqstp);
+ return (&dummy);
+}
+
+/* calls for nlm version 4 (NFSv3) */
+/* nlm_test ---------------------------------------------------------------- */
+/*
+ * Purpose: Test whether a specified lock would be granted if requested
+ * Returns: nlm_granted (or error code)
+ * Notes:
+ */
+nlm4_testres *
+nlm4_test_4_svc(nlm4_testargs *arg, struct svc_req *rqstp)
+{
+ static nlm4_testres res;
+ struct nlm4_holder *holder;
+
+ if (debug_level)
+ log_from_addr("nlm4_test", rqstp);
+ if (debug_level > 5) {
+ syslog(LOG_DEBUG, "Locking arguments:\n");
+ log_netobj(&(arg->cookie));
+ syslog(LOG_DEBUG, "Alock arguments:\n");
+ syslog(LOG_DEBUG, "Caller Name: %s\n",arg->alock.caller_name);
+ syslog(LOG_DEBUG, "File Handle:\n");
+ log_netobj(&(arg->alock.fh));
+ syslog(LOG_DEBUG, "Owner Handle:\n");
+ log_netobj(&(arg->alock.oh));
+ syslog(LOG_DEBUG, "SVID: %d\n", arg->alock.svid);
+ syslog(LOG_DEBUG, "Lock Offset: %llu\n",
+ (unsigned long long)arg->alock.l_offset);
+ syslog(LOG_DEBUG, "Lock Length: %llu\n",
+ (unsigned long long)arg->alock.l_len);
+ syslog(LOG_DEBUG, "Exclusive: %s\n",
+ (arg->exclusive ? "true" : "false"));
+ }
+
+ holder = testlock(&arg->alock, arg->exclusive, LOCK_V4);
+
+ /*
+ * Copy the cookie from the argument into the result. Note that this
+ * is slightly hazardous, as the structure contains a pointer to a
+ * malloc()ed buffer that will get freed by the caller. However, the
+ * main function transmits the result before freeing the argument
+ * so it is in fact safe.
+ */
+ res.cookie = arg->cookie;
+ if (holder == NULL) {
+ res.stat.stat = nlm4_granted;
+ } else {
+ res.stat.stat = nlm4_denied;
+ memcpy(&res.stat.nlm4_testrply_u.holder, holder,
+ sizeof(struct nlm4_holder));
+ }
+ return (&res);
+}
+
+void *
+nlm4_test_msg_4_svc(nlm4_testargs *arg, struct svc_req *rqstp)
+{
+ nlm4_testres res;
+ static char dummy;
+ struct sockaddr *addr;
+ CLIENT *cli;
+ int success;
+ struct timeval timeo;
+ struct nlm4_holder *holder;
+
+ if (debug_level)
+ log_from_addr("nlm4_test_msg", rqstp);
+
+ holder = testlock(&arg->alock, arg->exclusive, LOCK_V4);
+
+ res.cookie = arg->cookie;
+ if (holder == NULL) {
+ res.stat.stat = nlm4_granted;
+ } else {
+ res.stat.stat = nlm4_denied;
+ memcpy(&res.stat.nlm4_testrply_u.holder, holder,
+ sizeof(struct nlm4_holder));
+ }
+
+ /*
+ * nlm_test has different result type to the other operations, so
+ * can't use transmit4_result() in this case
+ */
+ addr = svc_getrpccaller(rqstp->rq_xprt)->buf;
+ if ((cli = get_client(addr, NLM_VERS4)) != NULL) {
+ timeo.tv_sec = 0; /* No timeout - not expecting response */
+ timeo.tv_usec = 0;
+
+ success = clnt_call(cli, NLM4_TEST_RES,
+ (xdrproc_t)xdr_nlm4_testres, &res,
+ (xdrproc_t)xdr_void, &dummy, timeo);
+
+ if (debug_level > 2)
+ syslog(LOG_DEBUG, "clnt_call returns %d", success);
+ }
+ return (NULL);
+}
+
+/* nlm_lock ---------------------------------------------------------------- */
+/*
+ * Purposes: Establish a lock
+ * Returns: granted, denied or blocked
+ * Notes: *** grace period support missing
+ */
+nlm4_res *
+nlm4_lock_4_svc(nlm4_lockargs *arg, struct svc_req *rqstp)
+{
+ static nlm4_res res;
+
+ if (debug_level)
+ log_from_addr("nlm4_lock", rqstp);
+ if (debug_level > 5) {
+ syslog(LOG_DEBUG, "Locking arguments:\n");
+ log_netobj(&(arg->cookie));
+ syslog(LOG_DEBUG, "Alock arguments:\n");
+ syslog(LOG_DEBUG, "Caller Name: %s\n",arg->alock.caller_name);
+ syslog(LOG_DEBUG, "File Handle:\n");
+ log_netobj(&(arg->alock.fh));
+ syslog(LOG_DEBUG, "Owner Handle:\n");
+ log_netobj(&(arg->alock.oh));
+ syslog(LOG_DEBUG, "SVID: %d\n", arg->alock.svid);
+ syslog(LOG_DEBUG, "Lock Offset: %llu\n",
+ (unsigned long long)arg->alock.l_offset);
+ syslog(LOG_DEBUG, "Lock Length: %llu\n",
+ (unsigned long long)arg->alock.l_len);
+ syslog(LOG_DEBUG, "Block: %s\n", (arg->block ? "true" : "false"));
+ syslog(LOG_DEBUG, "Exclusive: %s\n", (arg->exclusive ? "true" : "false"));
+ syslog(LOG_DEBUG, "Reclaim: %s\n", (arg->reclaim ? "true" : "false"));
+ syslog(LOG_DEBUG, "State num: %d\n", arg->state);
+ }
+
+ /* copy cookie from arg to result. See comment in nlm_test_4() */
+ res.cookie = arg->cookie;
+
+ res.stat.stat = getlock(arg, rqstp, LOCK_MON | LOCK_V4);
+ return (&res);
+}
+
+void *
+nlm4_lock_msg_4_svc(nlm4_lockargs *arg, struct svc_req *rqstp)
+{
+ static nlm4_res res;
+
+ if (debug_level)
+ log_from_addr("nlm4_lock_msg", rqstp);
+
+ res.cookie = arg->cookie;
+ res.stat.stat = getlock(arg, rqstp, LOCK_MON | LOCK_ASYNC | LOCK_V4);
+ transmit4_result(NLM4_LOCK_RES, &res, getrpcaddr(rqstp));
+
+ return (NULL);
+}
+
+/* nlm_cancel -------------------------------------------------------------- */
+/*
+ * Purpose: Cancel a blocked lock request
+ * Returns: granted or denied
+ * Notes:
+ */
+nlm4_res *
+nlm4_cancel_4_svc(nlm4_cancargs *arg, struct svc_req *rqstp)
+{
+ static nlm4_res res;
+
+ if (debug_level)
+ log_from_addr("nlm4_cancel", rqstp);
+
+ /* copy cookie from arg to result. See comment in nlm_test_1() */
+ res.cookie = arg->cookie;
+
+ /*
+ * Since at present we never return 'nlm_blocked', there can never be
+ * a lock to cancel, so this call always fails.
+ */
+ res.stat.stat = unlock(&arg->alock, LOCK_CANCEL);
+ return (&res);
+}
+
+void *
+nlm4_cancel_msg_4_svc(nlm4_cancargs *arg, struct svc_req *rqstp)
+{
+ static nlm4_res res;
+
+ if (debug_level)
+ log_from_addr("nlm4_cancel_msg", rqstp);
+
+ res.cookie = arg->cookie;
+ /*
+ * Since at present we never return 'nlm_blocked', there can never be
+ * a lock to cancel, so this call always fails.
+ */
+ res.stat.stat = unlock(&arg->alock, LOCK_CANCEL | LOCK_V4);
+ transmit4_result(NLM4_CANCEL_RES, &res, getrpcaddr(rqstp));
+ return (NULL);
+}
+
+/* nlm_unlock -------------------------------------------------------------- */
+/*
+ * Purpose: Release an existing lock
+ * Returns: Always granted, unless during grace period
+ * Notes: "no such lock" error condition is ignored, as the
+ * protocol uses unreliable UDP datagrams, and may well
+ * re-try an unlock that has already succeeded.
+ */
+nlm4_res *
+nlm4_unlock_4_svc(nlm4_unlockargs *arg, struct svc_req *rqstp)
+{
+ static nlm4_res res;
+
+ if (debug_level)
+ log_from_addr("nlm4_unlock", rqstp);
+
+ res.stat.stat = unlock(&arg->alock, LOCK_V4);
+ res.cookie = arg->cookie;
+
+ return (&res);
+}
+
+void *
+nlm4_unlock_msg_4_svc(nlm4_unlockargs *arg, struct svc_req *rqstp)
+{
+ static nlm4_res res;
+
+ if (debug_level)
+ log_from_addr("nlm4_unlock_msg", rqstp);
+
+ res.stat.stat = unlock(&arg->alock, LOCK_V4);
+ res.cookie = arg->cookie;
+
+ transmit4_result(NLM4_UNLOCK_RES, &res, getrpcaddr(rqstp));
+ return (NULL);
+}
+
+/* ------------------------------------------------------------------------- */
+/*
+ * Client-side pseudo-RPCs for results. Note that for the client there
+ * are only nlm_xxx_msg() versions of each call, since the 'real RPC'
+ * version returns the results in the RPC result, and so the client
+ * does not normally receive incoming RPCs.
+ *
+ * The exception to this is nlm_granted(), which is genuinely an RPC
+ * call from the server to the client - a 'call-back' in normal procedure
+ * call terms.
+ */
+
+/* nlm_granted ------------------------------------------------------------- */
+/*
+ * Purpose: Receive notification that formerly blocked lock now granted
+ * Returns: always success ('granted')
+ * Notes:
+ */
+nlm4_res *
+nlm4_granted_4_svc(nlm4_testargs *arg, struct svc_req *rqstp)
+{
+ static nlm4_res res;
+
+ if (debug_level)
+ log_from_addr("nlm4_granted", rqstp);
+
+ res.stat.stat = lock_answer(arg->alock.svid, &arg->cookie,
+ nlm4_granted, NULL, NLM_VERS4) == 0 ?
+ nlm4_granted : nlm4_denied;
+
+ /* copy cookie from arg to result. See comment in nlm_test_1() */
+ res.cookie = arg->cookie;
+
+ return (&res);
+}
+
+void *
+nlm4_granted_msg_4_svc(nlm4_testargs *arg, struct svc_req *rqstp)
+{
+ static nlm4_res res;
+
+ if (debug_level)
+ log_from_addr("nlm4_granted_msg", rqstp);
+
+ res.cookie = arg->cookie;
+ res.stat.stat = lock_answer(arg->alock.svid, &arg->cookie,
+ nlm4_granted, NULL, NLM_VERS4) == 0 ?
+ nlm4_granted : nlm4_denied;
+ transmit4_result(NLM4_GRANTED_RES, &res, getrpcaddr(rqstp));
+ return (NULL);
+}
+
+/* nlm_test_res ------------------------------------------------------------ */
+/*
+ * Purpose: Accept result from earlier nlm_test_msg() call
+ * Returns: Nothing
+ */
+void *
+nlm4_test_res_4_svc(nlm4_testres *arg, struct svc_req *rqstp)
+{
+ if (debug_level)
+ log_from_addr("nlm4_test_res", rqstp);
+
+ (void)lock_answer(-1, &arg->cookie, arg->stat.stat,
+ (int *)&arg->stat.nlm4_testrply_u.holder.svid,
+ NLM_VERS4);
+ return (NULL);
+}
+
+/* nlm_lock_res ------------------------------------------------------------ */
+/*
+ * Purpose: Accept result from earlier nlm_lock_msg() call
+ * Returns: Nothing
+ */
+void *
+nlm4_lock_res_4_svc(nlm4_res *arg, struct svc_req *rqstp)
+{
+ if (debug_level)
+ log_from_addr("nlm4_lock_res", rqstp);
+
+ (void)lock_answer(-1, &arg->cookie, arg->stat.stat, NULL, NLM_VERS4);
+
+ return (NULL);
+}
+
+/* nlm_cancel_res ---------------------------------------------------------- */
+/*
+ * Purpose: Accept result from earlier nlm_cancel_msg() call
+ * Returns: Nothing
+ */
+void *
+nlm4_cancel_res_4_svc(nlm4_res *arg __unused, struct svc_req *rqstp)
+{
+ if (debug_level)
+ log_from_addr("nlm4_cancel_res", rqstp);
+ return (NULL);
+}
+
+/* nlm_unlock_res ---------------------------------------------------------- */
+/*
+ * Purpose: Accept result from earlier nlm_unlock_msg() call
+ * Returns: Nothing
+ */
+void *
+nlm4_unlock_res_4_svc(nlm4_res *arg __unused, struct svc_req *rqstp)
+{
+ if (debug_level)
+ log_from_addr("nlm4_unlock_res", rqstp);
+ return (NULL);
+}
+
+/* nlm_granted_res --------------------------------------------------------- */
+/*
+ * Purpose: Accept result from earlier nlm_granted_msg() call
+ * Returns: Nothing
+ */
+void *
+nlm4_granted_res_4_svc(nlm4_res *arg __unused, struct svc_req *rqstp)
+{
+ if (debug_level)
+ log_from_addr("nlm4_granted_res", rqstp);
+ return (NULL);
+}
+
+/* ------------------------------------------------------------------------- */
+/*
+ * Calls for PCNFS locking (aka non-monitored locking, no involvement
+ * of rpc.statd).
+ *
+ * These are all genuine RPCs - no nlm_xxx_msg() nonsense here.
+ */
+
+/* nlm_share --------------------------------------------------------------- */
+/*
+ * Purpose: Establish a DOS-style lock
+ * Returns: success or failure
+ * Notes: Blocking locks are not supported - client is expected
+ * to retry if required.
+ */
+nlm4_shareres *
+nlm4_share_4_svc(nlm4_shareargs *arg, struct svc_req *rqstp)
+{
+ static nlm4_shareres res;
+
+ if (debug_level)
+ log_from_addr("nlm4_share", rqstp);
+
+ res.cookie = arg->cookie;
+ res.stat = nlm4_granted;
+ res.sequence = 1234356; /* X/Open says this field is ignored? */
+ return (&res);
+}
+
+/* nlm4_unshare ------------------------------------------------------------ */
+/*
+ * Purpose: Release a DOS-style lock
+ * Returns: nlm_granted, unless in grace period
+ * Notes:
+ */
+nlm4_shareres *
+nlm4_unshare_4_svc(nlm4_shareargs *arg, struct svc_req *rqstp)
+{
+ static nlm4_shareres res;
+
+ if (debug_level)
+ log_from_addr("nlm_unshare", rqstp);
+
+ res.cookie = arg->cookie;
+ res.stat = nlm4_granted;
+ res.sequence = 1234356; /* X/Open says this field is ignored? */
+ return (&res);
+}
+
+/* nlm4_nm_lock ------------------------------------------------------------ */
+/*
+ * Purpose: non-monitored version of nlm4_lock()
+ * Returns: as for nlm4_lock()
+ * Notes: These locks are in the same style as the standard nlm4_lock,
+ * but the rpc.statd should not be called to establish a
+ * monitor for the client machine, since that machine is
+ * declared not to be running a rpc.statd, and so would not
+ * respond to the statd protocol.
+ */
+nlm4_res *
+nlm4_nm_lock_4_svc(nlm4_lockargs *arg, struct svc_req *rqstp)
+{
+ static nlm4_res res;
+
+ if (debug_level)
+ log_from_addr("nlm4_nm_lock", rqstp);
+
+ /* copy cookie from arg to result. See comment in nlm4_test_1() */
+ res.cookie = arg->cookie;
+ res.stat.stat = nlm4_granted;
+ return (&res);
+}
+
+/* nlm4_free_all ------------------------------------------------------------ */
+/*
+ * Purpose: Release all locks held by a named client
+ * Returns: Nothing
+ * Notes: Potential denial of service security problem here - the
+ * locks to be released are specified by a host name, independent
+ * of the address from which the request has arrived.
+ * Should probably be rejected if the named host has been
+ * using monitored locks.
+ */
+void *
+nlm4_free_all_4_svc(struct nlm4_notify *arg __unused, struct svc_req *rqstp)
+{
+ static char dummy;
+
+ if (debug_level)
+ log_from_addr("nlm4_free_all", rqstp);
+ return (&dummy);
+}
+
+/* nlm_sm_notify --------------------------------------------------------- */
+/*
+ * Purpose: called by rpc.statd when a monitored host state changes.
+ * Returns: Nothing
+ */
+void *
+nlm_sm_notify_0_svc(struct nlm_sm_status *arg, struct svc_req *rqstp __unused)
+{
+ static char dummy;
+ notify(arg->mon_name, arg->state);
+ return (&dummy);
+}
diff --git a/usr.sbin/rpc.lockd/lockd.c b/usr.sbin/rpc.lockd/lockd.c
new file mode 100644
index 0000000..88b19b7
--- /dev/null
+++ b/usr.sbin/rpc.lockd/lockd.c
@@ -0,0 +1,1025 @@
+/* $NetBSD: lockd.c,v 1.7 2000/08/12 18:08:44 thorpej Exp $ */
+/* $FreeBSD$ */
+
+/*
+ * Copyright (c) 1995
+ * A.R. Gordon (andrew.gordon@net-tel.co.uk). All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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
+ * 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 ANDREW GORDON AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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>
+#ifndef lint
+__RCSID("$NetBSD: lockd.c,v 1.7 2000/08/12 18:08:44 thorpej Exp $");
+#endif
+
+/*
+ * main() function for NFS lock daemon. Most of the code in this
+ * file was generated by running rpcgen /usr/include/rpcsvc/nlm_prot.x.
+ *
+ * The actual program logic is in the file lock_proc.c
+ */
+
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <syslog.h>
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+#include <libutil.h>
+#include <netconfig.h>
+#include <netdb.h>
+
+#include <rpc/rpc.h>
+#include <rpc/rpc_com.h>
+#include <rpcsvc/sm_inter.h>
+
+#include "lockd.h"
+#include <rpcsvc/nlm_prot.h>
+
+#define GETPORT_MAXTRY 20 /* Max tries to get a port # */
+
+int debug_level = 0; /* 0 = no debugging syslog() calls */
+int _rpcsvcdirty = 0;
+
+int grace_expired;
+int nsm_state;
+int kernel_lockd;
+int kernel_lockd_client;
+pid_t client_pid;
+struct mon mon_host;
+char **hosts, *svcport_str = NULL;
+static int mallocd_svcport = 0;
+static int *sock_fd;
+static int sock_fdcnt;
+static int sock_fdpos;
+int nhosts = 0;
+int xcreated = 0;
+char **addrs; /* actually (netid, uaddr) pairs */
+int naddrs; /* count of how many (netid, uaddr) pairs */
+char localhost[] = "localhost";
+
+static int create_service(struct netconfig *nconf);
+static void complete_service(struct netconfig *nconf, char *port_str);
+static void clearout_service(void);
+void lookup_addresses(struct netconfig *nconf);
+void init_nsm(void);
+void out_of_mem(void);
+void usage(void);
+
+void sigalarm_handler(void);
+
+/*
+ * XXX move to some header file.
+ */
+#define _PATH_RPCLOCKDSOCK "/var/run/rpclockd.sock"
+
+int
+main(int argc, char **argv)
+{
+ int ch, i, s;
+ void *nc_handle;
+ char *endptr, **hosts_bak;
+ struct sigaction sigalarm;
+ int grace_period = 30;
+ struct netconfig *nconf;
+ int have_v6 = 1;
+ int maxrec = RPC_MAXDATASIZE;
+ in_port_t svcport = 0;
+ int attempt_cnt, port_len, port_pos, ret;
+ char **port_list;
+
+ while ((ch = getopt(argc, argv, "d:g:h:p:")) != (-1)) {
+ switch (ch) {
+ case 'd':
+ debug_level = atoi(optarg);
+ if (!debug_level) {
+ usage();
+ /* NOTREACHED */
+ }
+ break;
+ case 'g':
+ grace_period = atoi(optarg);
+ if (!grace_period) {
+ usage();
+ /* NOTREACHED */
+ }
+ break;
+ case 'h':
+ ++nhosts;
+ hosts_bak = hosts;
+ hosts_bak = realloc(hosts, nhosts * sizeof(char *));
+ if (hosts_bak == NULL) {
+ if (hosts != NULL) {
+ for (i = 0; i < nhosts; i++)
+ free(hosts[i]);
+ free(hosts);
+ out_of_mem();
+ }
+ }
+ hosts = hosts_bak;
+ hosts[nhosts - 1] = strdup(optarg);
+ if (hosts[nhosts - 1] == NULL) {
+ for (i = 0; i < (nhosts - 1); i++)
+ free(hosts[i]);
+ free(hosts);
+ out_of_mem();
+ }
+ break;
+ case 'p':
+ endptr = NULL;
+ svcport = (in_port_t)strtoul(optarg, &endptr, 10);
+ if (endptr == NULL || *endptr != '\0' ||
+ svcport == 0 || svcport >= IPPORT_MAX)
+ usage();
+ svcport_str = strdup(optarg);
+ break;
+ default:
+ case '?':
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ if (geteuid()) { /* This command allowed only to root */
+ fprintf(stderr, "Sorry. You are not superuser\n");
+ exit(1);
+ }
+
+ kernel_lockd = FALSE;
+ kernel_lockd_client = FALSE;
+ if (modfind("nfslockd") < 0) {
+ if (kldload("nfslockd") < 0) {
+ fprintf(stderr, "Can't find or load kernel support for rpc.lockd - using non-kernel implementation\n");
+ } else {
+ kernel_lockd = TRUE;
+ }
+ } else {
+ kernel_lockd = TRUE;
+ }
+ if (kernel_lockd) {
+ if (getosreldate() >= 800040)
+ kernel_lockd_client = TRUE;
+ }
+
+ (void)rpcb_unset(NLM_PROG, NLM_SM, NULL);
+ (void)rpcb_unset(NLM_PROG, NLM_VERS, NULL);
+ (void)rpcb_unset(NLM_PROG, NLM_VERSX, NULL);
+ (void)rpcb_unset(NLM_PROG, NLM_VERS4, NULL);
+
+ /*
+ * Check if IPv6 support is present.
+ */
+ s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ if (s < 0)
+ have_v6 = 0;
+ else
+ close(s);
+
+ rpc_control(RPC_SVC_CONNMAXREC_SET, &maxrec);
+
+ /*
+ * If no hosts were specified, add a wildcard entry to bind to
+ * INADDR_ANY. Otherwise make sure 127.0.0.1 and ::1 are added to the
+ * list.
+ */
+ if (nhosts == 0) {
+ hosts = malloc(sizeof(char *));
+ if (hosts == NULL)
+ out_of_mem();
+
+ hosts[0] = strdup("*");
+ nhosts = 1;
+ } else {
+ hosts_bak = hosts;
+ if (have_v6) {
+ hosts_bak = realloc(hosts, (nhosts + 2) *
+ sizeof(char *));
+ if (hosts_bak == NULL) {
+ for (i = 0; i < nhosts; i++)
+ free(hosts[i]);
+ free(hosts);
+ out_of_mem();
+ } else
+ hosts = hosts_bak;
+
+ nhosts += 2;
+ hosts[nhosts - 2] = strdup("::1");
+ } else {
+ hosts_bak = realloc(hosts, (nhosts + 1) * sizeof(char *));
+ if (hosts_bak == NULL) {
+ for (i = 0; i < nhosts; i++)
+ free(hosts[i]);
+
+ free(hosts);
+ out_of_mem();
+ } else {
+ nhosts += 1;
+ hosts = hosts_bak;
+ }
+ }
+ hosts[nhosts - 1] = strdup("127.0.0.1");
+ }
+
+ if (kernel_lockd) {
+ if (!kernel_lockd_client) {
+ /*
+ * For the case where we have a kernel lockd but it
+ * doesn't provide client locking, we run a cut-down
+ * RPC service on a local-domain socket. The kernel's
+ * RPC server will pass what it can't handle (mainly
+ * client replies) down to us.
+ */
+ struct sockaddr_un sun;
+ int fd, oldmask;
+ SVCXPRT *xprt;
+
+ memset(&sun, 0, sizeof sun);
+ sun.sun_family = AF_LOCAL;
+ unlink(_PATH_RPCLOCKDSOCK);
+ strcpy(sun.sun_path, _PATH_RPCLOCKDSOCK);
+ sun.sun_len = SUN_LEN(&sun);
+ fd = socket(AF_LOCAL, SOCK_STREAM, 0);
+ if (!fd) {
+ err(1, "Can't create local lockd socket");
+ }
+ oldmask = umask(S_IXUSR|S_IRWXG|S_IRWXO);
+ if (bind(fd, (struct sockaddr *) &sun, sun.sun_len) < 0) {
+ err(1, "Can't bind local lockd socket");
+ }
+ umask(oldmask);
+ if (listen(fd, SOMAXCONN) < 0) {
+ err(1, "Can't listen on local lockd socket");
+ }
+ xprt = svc_vc_create(fd, RPC_MAXDATASIZE, RPC_MAXDATASIZE);
+ if (!xprt) {
+ err(1, "Can't create transport for local lockd socket");
+ }
+ if (!svc_reg(xprt, NLM_PROG, NLM_VERS4, nlm_prog_4, NULL)) {
+ err(1, "Can't register service for local lockd socket");
+ }
+ }
+
+ /*
+ * We need to look up the addresses so that we can
+ * hand uaddrs (ascii encoded address+port strings) to
+ * the kernel.
+ */
+ nc_handle = setnetconfig();
+ while ((nconf = getnetconfig(nc_handle))) {
+ /* We want to listen only on udp6, tcp6, udp, tcp transports */
+ if (nconf->nc_flag & NC_VISIBLE) {
+ /* Skip if there's no IPv6 support */
+ if (have_v6 == 0 && strcmp(nconf->nc_protofmly, "inet6") == 0) {
+ /* DO NOTHING */
+ } else {
+ lookup_addresses(nconf);
+ }
+ }
+ }
+ endnetconfig(nc_handle);
+ } else {
+ attempt_cnt = 1;
+ sock_fdcnt = 0;
+ sock_fd = NULL;
+ port_list = NULL;
+ port_len = 0;
+ nc_handle = setnetconfig();
+ while ((nconf = getnetconfig(nc_handle))) {
+ /* We want to listen only on udp6, tcp6, udp, tcp transports */
+ if (nconf->nc_flag & NC_VISIBLE) {
+ /* Skip if there's no IPv6 support */
+ if (have_v6 == 0 && strcmp(nconf->nc_protofmly, "inet6") == 0) {
+ /* DO NOTHING */
+ } else {
+ ret = create_service(nconf);
+ if (ret == 1)
+ /* Ignore this call */
+ continue;
+ if (ret < 0) {
+ /*
+ * Failed to bind port, so close
+ * off all sockets created and
+ * try again if the port# was
+ * dynamically assigned via
+ * bind(2).
+ */
+ clearout_service();
+ if (mallocd_svcport != 0 &&
+ attempt_cnt <
+ GETPORT_MAXTRY) {
+ free(svcport_str);
+ svcport_str = NULL;
+ mallocd_svcport = 0;
+ } else {
+ errno = EADDRINUSE;
+ syslog(LOG_ERR,
+ "bindresvport_sa: %m");
+ exit(1);
+ }
+
+ /*
+ * Start over at the first
+ * service.
+ */
+ free(sock_fd);
+ sock_fdcnt = 0;
+ sock_fd = NULL;
+ nc_handle = setnetconfig();
+ attempt_cnt++;
+ } else if (mallocd_svcport != 0 &&
+ attempt_cnt == GETPORT_MAXTRY) {
+ /*
+ * For the last attempt, allow
+ * different port #s for each
+ * nconf by saving the
+ * svcport_str and setting it
+ * back to NULL.
+ */
+ port_list = realloc(port_list,
+ (port_len + 1) *
+ sizeof(char *));
+ if (port_list == NULL)
+ out_of_mem();
+ port_list[port_len++] =
+ svcport_str;
+ svcport_str = NULL;
+ mallocd_svcport = 0;
+ }
+ }
+ }
+ }
+
+ /*
+ * Successfully bound the ports, so call complete_service() to
+ * do the rest of the setup on the service(s).
+ */
+ sock_fdpos = 0;
+ port_pos = 0;
+ nc_handle = setnetconfig();
+ while ((nconf = getnetconfig(nc_handle))) {
+ /* We want to listen only on udp6, tcp6, udp, tcp transports */
+ if (nconf->nc_flag & NC_VISIBLE) {
+ /* Skip if there's no IPv6 support */
+ if (have_v6 == 0 && strcmp(nconf->nc_protofmly, "inet6") == 0) {
+ /* DO NOTHING */
+ } else if (port_list != NULL) {
+ if (port_pos >= port_len) {
+ syslog(LOG_ERR,
+ "too many port#s");
+ exit(1);
+ }
+ complete_service(nconf,
+ port_list[port_pos++]);
+ } else
+ complete_service(nconf, svcport_str);
+ }
+ }
+ endnetconfig(nc_handle);
+ free(sock_fd);
+ if (port_list != NULL) {
+ for (port_pos = 0; port_pos < port_len; port_pos++)
+ free(port_list[port_pos]);
+ free(port_list);
+ }
+ }
+
+ /*
+ * Note that it is NOT sensible to run this program from inetd - the
+ * protocol assumes that it will run immediately at boot time.
+ */
+ if (daemon(0, debug_level > 0)) {
+ err(1, "cannot fork");
+ /* NOTREACHED */
+ }
+
+ openlog("rpc.lockd", 0, LOG_DAEMON);
+ if (debug_level)
+ syslog(LOG_INFO, "Starting, debug level %d", debug_level);
+ else
+ syslog(LOG_INFO, "Starting");
+
+ sigalarm.sa_handler = (sig_t) sigalarm_handler;
+ sigemptyset(&sigalarm.sa_mask);
+ sigalarm.sa_flags = SA_RESETHAND; /* should only happen once */
+ sigalarm.sa_flags |= SA_RESTART;
+ if (sigaction(SIGALRM, &sigalarm, NULL) != 0) {
+ syslog(LOG_WARNING, "sigaction(SIGALRM) failed: %s",
+ strerror(errno));
+ exit(1);
+ }
+
+ if (kernel_lockd) {
+ if (!kernel_lockd_client) {
+ init_nsm();
+ client_pid = client_request();
+
+ /*
+ * Create a child process to enter the kernel and then
+ * wait for RPCs on our local domain socket.
+ */
+ if (!fork())
+ nlm_syscall(debug_level, grace_period,
+ naddrs, addrs);
+ else
+ svc_run();
+ } else {
+ /*
+ * The kernel lockd implementation provides
+ * both client and server so we don't need to
+ * do anything else.
+ */
+ nlm_syscall(debug_level, grace_period, naddrs, addrs);
+ }
+ } else {
+ grace_expired = 0;
+ alarm(grace_period);
+
+ init_nsm();
+
+ client_pid = client_request();
+
+ svc_run(); /* Should never return */
+ }
+ exit(1);
+}
+
+/*
+ * This routine creates and binds sockets on the appropriate
+ * addresses. It gets called one time for each transport.
+ * It returns 0 upon success, 1 for ingore the call and -1 to indicate
+ * bind failed with EADDRINUSE.
+ * Any file descriptors that have been created are stored in sock_fd and
+ * the total count of them is maintained in sock_fdcnt.
+ */
+static int
+create_service(struct netconfig *nconf)
+{
+ struct addrinfo hints, *res = NULL;
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ struct __rpc_sockinfo si;
+ int aicode;
+ int fd;
+ int nhostsbak;
+ int r;
+ u_int32_t host_addr[4]; /* IPv4 or IPv6 */
+ int mallocd_res;
+
+ if ((nconf->nc_semantics != NC_TPI_CLTS) &&
+ (nconf->nc_semantics != NC_TPI_COTS) &&
+ (nconf->nc_semantics != NC_TPI_COTS_ORD))
+ return (1); /* not my type */
+
+ /*
+ * XXX - using RPC library internal functions.
+ */
+ if (!__rpc_nconf2sockinfo(nconf, &si)) {
+ syslog(LOG_ERR, "cannot get information for %s",
+ nconf->nc_netid);
+ return (1);
+ }
+
+ /* Get rpc.statd's address on this transport */
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = si.si_af;
+ hints.ai_socktype = si.si_socktype;
+ hints.ai_protocol = si.si_proto;
+
+ /*
+ * Bind to specific IPs if asked to
+ */
+ nhostsbak = nhosts;
+ while (nhostsbak > 0) {
+ --nhostsbak;
+ sock_fd = realloc(sock_fd, (sock_fdcnt + 1) * sizeof(int));
+ if (sock_fd == NULL)
+ out_of_mem();
+ sock_fd[sock_fdcnt++] = -1; /* Set invalid for now. */
+ mallocd_res = 0;
+ hints.ai_flags = AI_PASSIVE;
+
+ /*
+ * XXX - using RPC library internal functions.
+ */
+ if ((fd = __rpc_nconf2fd(nconf)) < 0) {
+ syslog(LOG_ERR, "cannot create socket for %s",
+ nconf->nc_netid);
+ continue;
+ }
+
+ switch (hints.ai_family) {
+ case AF_INET:
+ if (inet_pton(AF_INET, hosts[nhostsbak],
+ host_addr) == 1) {
+ hints.ai_flags |= AI_NUMERICHOST;
+ } else {
+ /*
+ * Skip if we have an AF_INET6 address.
+ */
+ if (inet_pton(AF_INET6, hosts[nhostsbak],
+ host_addr) == 1) {
+ close(fd);
+ continue;
+ }
+ }
+ break;
+ case AF_INET6:
+ if (inet_pton(AF_INET6, hosts[nhostsbak],
+ host_addr) == 1) {
+ hints.ai_flags |= AI_NUMERICHOST;
+ } else {
+ /*
+ * Skip if we have an AF_INET address.
+ */
+ if (inet_pton(AF_INET, hosts[nhostsbak],
+ host_addr) == 1) {
+ close(fd);
+ continue;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * If no hosts were specified, just bind to INADDR_ANY
+ */
+ if (strcmp("*", hosts[nhostsbak]) == 0) {
+ if (svcport_str == NULL) {
+ res = malloc(sizeof(struct addrinfo));
+ if (res == NULL)
+ out_of_mem();
+ mallocd_res = 1;
+ res->ai_flags = hints.ai_flags;
+ res->ai_family = hints.ai_family;
+ res->ai_protocol = hints.ai_protocol;
+ switch (res->ai_family) {
+ case AF_INET:
+ sin = malloc(sizeof(struct sockaddr_in));
+ if (sin == NULL)
+ out_of_mem();
+ sin->sin_family = AF_INET;
+ sin->sin_port = htons(0);
+ sin->sin_addr.s_addr = htonl(INADDR_ANY);
+ res->ai_addr = (struct sockaddr*) sin;
+ res->ai_addrlen = (socklen_t)
+ sizeof(struct sockaddr_in);
+ break;
+ case AF_INET6:
+ sin6 = malloc(sizeof(struct sockaddr_in6));
+ if (sin6 == NULL)
+ out_of_mem();
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_port = htons(0);
+ sin6->sin6_addr = in6addr_any;
+ res->ai_addr = (struct sockaddr*) sin6;
+ res->ai_addrlen = (socklen_t)
+ sizeof(struct sockaddr_in6);
+ break;
+ default:
+ syslog(LOG_ERR,
+ "bad addr fam %d",
+ res->ai_family);
+ exit(1);
+ }
+ } else {
+ if ((aicode = getaddrinfo(NULL, svcport_str,
+ &hints, &res)) != 0) {
+ syslog(LOG_ERR,
+ "cannot get local address for %s: %s",
+ nconf->nc_netid,
+ gai_strerror(aicode));
+ close(fd);
+ continue;
+ }
+ }
+ } else {
+ if ((aicode = getaddrinfo(hosts[nhostsbak], svcport_str,
+ &hints, &res)) != 0) {
+ syslog(LOG_ERR,
+ "cannot get local address for %s: %s",
+ nconf->nc_netid, gai_strerror(aicode));
+ close(fd);
+ continue;
+ }
+ }
+
+
+ /* Store the fd. */
+ sock_fd[sock_fdcnt - 1] = fd;
+
+ /* Now, attempt the bind. */
+ r = bindresvport_sa(fd, res->ai_addr);
+ if (r != 0) {
+ if (errno == EADDRINUSE && mallocd_svcport != 0) {
+ if (mallocd_res != 0) {
+ free(res->ai_addr);
+ free(res);
+ } else
+ freeaddrinfo(res);
+ return (-1);
+ }
+ syslog(LOG_ERR, "bindresvport_sa: %m");
+ exit(1);
+ }
+
+ if (svcport_str == NULL) {
+ svcport_str = malloc(NI_MAXSERV * sizeof(char));
+ if (svcport_str == NULL)
+ out_of_mem();
+ mallocd_svcport = 1;
+
+ if (getnameinfo(res->ai_addr,
+ res->ai_addr->sa_len, NULL, NI_MAXHOST,
+ svcport_str, NI_MAXSERV * sizeof(char),
+ NI_NUMERICHOST | NI_NUMERICSERV))
+ errx(1, "Cannot get port number");
+ }
+ if (mallocd_res != 0) {
+ free(res->ai_addr);
+ free(res);
+ } else
+ freeaddrinfo(res);
+ res = NULL;
+ }
+ return (0);
+}
+
+/*
+ * Called after all the create_service() calls have succeeded, to complete
+ * the setup and registration.
+ */
+static void
+complete_service(struct netconfig *nconf, char *port_str)
+{
+ struct addrinfo hints, *res = NULL;
+ struct __rpc_sockinfo si;
+ struct netbuf servaddr;
+ SVCXPRT *transp = NULL;
+ int aicode, fd, nhostsbak;
+ int registered = 0;
+
+ if ((nconf->nc_semantics != NC_TPI_CLTS) &&
+ (nconf->nc_semantics != NC_TPI_COTS) &&
+ (nconf->nc_semantics != NC_TPI_COTS_ORD))
+ return; /* not my type */
+
+ /*
+ * XXX - using RPC library internal functions.
+ */
+ if (!__rpc_nconf2sockinfo(nconf, &si)) {
+ syslog(LOG_ERR, "cannot get information for %s",
+ nconf->nc_netid);
+ return;
+ }
+
+ nhostsbak = nhosts;
+ while (nhostsbak > 0) {
+ --nhostsbak;
+ if (sock_fdpos >= sock_fdcnt) {
+ /* Should never happen. */
+ syslog(LOG_ERR, "Ran out of socket fd's");
+ return;
+ }
+ fd = sock_fd[sock_fdpos++];
+ if (fd < 0)
+ continue;
+
+ if (nconf->nc_semantics != NC_TPI_CLTS)
+ listen(fd, SOMAXCONN);
+
+ transp = svc_tli_create(fd, nconf, NULL,
+ RPC_MAXDATASIZE, RPC_MAXDATASIZE);
+
+ if (transp != (SVCXPRT *) NULL) {
+ if (!svc_reg(transp, NLM_PROG, NLM_SM, nlm_prog_0,
+ NULL))
+ syslog(LOG_ERR,
+ "can't register %s NLM_PROG, NLM_SM service",
+ nconf->nc_netid);
+
+ if (!svc_reg(transp, NLM_PROG, NLM_VERS, nlm_prog_1,
+ NULL))
+ syslog(LOG_ERR,
+ "can't register %s NLM_PROG, NLM_VERS service",
+ nconf->nc_netid);
+
+ if (!svc_reg(transp, NLM_PROG, NLM_VERSX, nlm_prog_3,
+ NULL))
+ syslog(LOG_ERR,
+ "can't register %s NLM_PROG, NLM_VERSX service",
+ nconf->nc_netid);
+
+ if (!svc_reg(transp, NLM_PROG, NLM_VERS4, nlm_prog_4,
+ NULL))
+ syslog(LOG_ERR,
+ "can't register %s NLM_PROG, NLM_VERS4 service",
+ nconf->nc_netid);
+
+ } else
+ syslog(LOG_WARNING, "can't create %s services",
+ nconf->nc_netid);
+
+ if (registered == 0) {
+ registered = 1;
+ memset(&hints, 0, sizeof hints);
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = si.si_af;
+ hints.ai_socktype = si.si_socktype;
+ hints.ai_protocol = si.si_proto;
+
+ if ((aicode = getaddrinfo(NULL, port_str, &hints,
+ &res)) != 0) {
+ syslog(LOG_ERR, "cannot get local address: %s",
+ gai_strerror(aicode));
+ exit(1);
+ }
+
+ servaddr.buf = malloc(res->ai_addrlen);
+ memcpy(servaddr.buf, res->ai_addr, res->ai_addrlen);
+ servaddr.len = res->ai_addrlen;
+
+ rpcb_set(NLM_PROG, NLM_SM, nconf, &servaddr);
+ rpcb_set(NLM_PROG, NLM_VERS, nconf, &servaddr);
+ rpcb_set(NLM_PROG, NLM_VERSX, nconf, &servaddr);
+ rpcb_set(NLM_PROG, NLM_VERS4, nconf, &servaddr);
+
+ xcreated++;
+ freeaddrinfo(res);
+ }
+ } /* end while */
+}
+
+/*
+ * Clear out sockets after a failure to bind one of them, so that the
+ * cycle of socket creation/binding can start anew.
+ */
+static void
+clearout_service(void)
+{
+ int i;
+
+ for (i = 0; i < sock_fdcnt; i++) {
+ if (sock_fd[i] >= 0) {
+ shutdown(sock_fd[i], SHUT_RDWR);
+ close(sock_fd[i]);
+ }
+ }
+}
+
+/*
+ * Look up addresses for the kernel to create transports for.
+ */
+void
+lookup_addresses(struct netconfig *nconf)
+{
+ struct addrinfo hints, *res = NULL;
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ struct __rpc_sockinfo si;
+ struct netbuf servaddr;
+ int aicode;
+ int nhostsbak;
+ u_int32_t host_addr[4]; /* IPv4 or IPv6 */
+ char *uaddr;
+
+ if ((nconf->nc_semantics != NC_TPI_CLTS) &&
+ (nconf->nc_semantics != NC_TPI_COTS) &&
+ (nconf->nc_semantics != NC_TPI_COTS_ORD))
+ return; /* not my type */
+
+ /*
+ * XXX - using RPC library internal functions.
+ */
+ if (!__rpc_nconf2sockinfo(nconf, &si)) {
+ syslog(LOG_ERR, "cannot get information for %s",
+ nconf->nc_netid);
+ return;
+ }
+
+ /* Get rpc.statd's address on this transport */
+ memset(&hints, 0, sizeof hints);
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = si.si_af;
+ hints.ai_socktype = si.si_socktype;
+ hints.ai_protocol = si.si_proto;
+
+ /*
+ * Bind to specific IPs if asked to
+ */
+ nhostsbak = nhosts;
+ while (nhostsbak > 0) {
+ --nhostsbak;
+
+ switch (hints.ai_family) {
+ case AF_INET:
+ if (inet_pton(AF_INET, hosts[nhostsbak],
+ host_addr) == 1) {
+ hints.ai_flags &= AI_NUMERICHOST;
+ } else {
+ /*
+ * Skip if we have an AF_INET6 address.
+ */
+ if (inet_pton(AF_INET6, hosts[nhostsbak],
+ host_addr) == 1) {
+ continue;
+ }
+ }
+ break;
+ case AF_INET6:
+ if (inet_pton(AF_INET6, hosts[nhostsbak],
+ host_addr) == 1) {
+ hints.ai_flags &= AI_NUMERICHOST;
+ } else {
+ /*
+ * Skip if we have an AF_INET address.
+ */
+ if (inet_pton(AF_INET, hosts[nhostsbak],
+ host_addr) == 1) {
+ continue;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * If no hosts were specified, just bind to INADDR_ANY
+ */
+ if (strcmp("*", hosts[nhostsbak]) == 0) {
+ if (svcport_str == NULL) {
+ res = malloc(sizeof(struct addrinfo));
+ if (res == NULL)
+ out_of_mem();
+ res->ai_flags = hints.ai_flags;
+ res->ai_family = hints.ai_family;
+ res->ai_protocol = hints.ai_protocol;
+ switch (res->ai_family) {
+ case AF_INET:
+ sin = malloc(sizeof(struct sockaddr_in));
+ if (sin == NULL)
+ out_of_mem();
+ sin->sin_family = AF_INET;
+ sin->sin_port = htons(0);
+ sin->sin_addr.s_addr = htonl(INADDR_ANY);
+ res->ai_addr = (struct sockaddr*) sin;
+ res->ai_addrlen = (socklen_t)
+ sizeof(res->ai_addr);
+ break;
+ case AF_INET6:
+ sin6 = malloc(sizeof(struct sockaddr_in6));
+ if (sin6 == NULL)
+ out_of_mem();
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_port = htons(0);
+ sin6->sin6_addr = in6addr_any;
+ res->ai_addr = (struct sockaddr*) sin6;
+ res->ai_addrlen = (socklen_t) sizeof(res->ai_addr);
+ break;
+ default:
+ break;
+ }
+ } else {
+ if ((aicode = getaddrinfo(NULL, svcport_str,
+ &hints, &res)) != 0) {
+ syslog(LOG_ERR,
+ "cannot get local address for %s: %s",
+ nconf->nc_netid,
+ gai_strerror(aicode));
+ continue;
+ }
+ }
+ } else {
+ if ((aicode = getaddrinfo(hosts[nhostsbak], svcport_str,
+ &hints, &res)) != 0) {
+ syslog(LOG_ERR,
+ "cannot get local address for %s: %s",
+ nconf->nc_netid, gai_strerror(aicode));
+ continue;
+ }
+ }
+
+ servaddr.len = servaddr.maxlen = res->ai_addr->sa_len;
+ servaddr.buf = res->ai_addr;
+ uaddr = taddr2uaddr(nconf, &servaddr);
+
+ addrs = realloc(addrs, 2 * (naddrs + 1) * sizeof(char *));
+ if (!addrs)
+ out_of_mem();
+ addrs[2 * naddrs] = strdup(nconf->nc_netid);
+ addrs[2 * naddrs + 1] = uaddr;
+ naddrs++;
+ } /* end while */
+}
+
+void
+sigalarm_handler(void)
+{
+
+ grace_expired = 1;
+}
+
+void
+usage()
+{
+ errx(1, "usage: rpc.lockd [-d <debuglevel>]"
+ " [-g <grace period>] [-h <bindip>] [-p <port>]");
+}
+
+/*
+ * init_nsm --
+ * Reset the NSM state-of-the-world and acquire its state.
+ */
+void
+init_nsm(void)
+{
+ enum clnt_stat ret;
+ my_id id;
+ sm_stat stat;
+ char name[] = "NFS NLM";
+
+ /*
+ * !!!
+ * The my_id structure isn't used by the SM_UNMON_ALL call, as far
+ * as I know. Leave it empty for now.
+ */
+ memset(&id, 0, sizeof(id));
+ id.my_name = name;
+
+ /*
+ * !!!
+ * The statd program must already be registered when lockd runs.
+ */
+ do {
+ ret = callrpc("localhost", SM_PROG, SM_VERS, SM_UNMON_ALL,
+ (xdrproc_t)xdr_my_id, &id, (xdrproc_t)xdr_sm_stat, &stat);
+ if (ret == RPC_PROGUNAVAIL) {
+ syslog(LOG_WARNING, "%lu %s", SM_PROG,
+ clnt_sperrno(ret));
+ sleep(2);
+ continue;
+ }
+ break;
+ } while (0);
+
+ if (ret != 0) {
+ syslog(LOG_ERR, "%lu %s", SM_PROG, clnt_sperrno(ret));
+ exit(1);
+ }
+
+ nsm_state = stat.state;
+
+ /* setup constant data for SM_MON calls */
+ mon_host.mon_id.my_id.my_name = localhost;
+ mon_host.mon_id.my_id.my_prog = NLM_PROG;
+ mon_host.mon_id.my_id.my_vers = NLM_SM;
+ mon_host.mon_id.my_id.my_proc = NLM_SM_NOTIFY; /* bsdi addition */
+}
+
+/*
+ * Out of memory, fatal
+ */
+void out_of_mem()
+{
+ syslog(LOG_ERR, "out of memory");
+ exit(2);
+}
diff --git a/usr.sbin/rpc.lockd/lockd.h b/usr.sbin/rpc.lockd/lockd.h
new file mode 100644
index 0000000..0a8e2e6
--- /dev/null
+++ b/usr.sbin/rpc.lockd/lockd.h
@@ -0,0 +1,41 @@
+/* $NetBSD: lockd.h,v 1.2 2000/06/07 14:34:40 bouyer Exp $ */
+/* $FreeBSD$ */
+
+/*
+ * Copyright (c) 1995
+ * A.R. Gordon (andrew.gordon@net-tel.co.uk). All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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
+ * 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 ANDREW GORDON AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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.
+ *
+ */
+
+extern int debug_level;
+extern int grace_expired;
+pid_t client_request(void);
+extern int nsm_state;
+extern pid_t client_pid;
diff --git a/usr.sbin/rpc.lockd/lockd_lock.c b/usr.sbin/rpc.lockd/lockd_lock.c
new file mode 100644
index 0000000..dfb0d2a
--- /dev/null
+++ b/usr.sbin/rpc.lockd/lockd_lock.c
@@ -0,0 +1,2302 @@
+/* $NetBSD: lockd_lock.c,v 1.5 2000/11/21 03:47:41 enami Exp $ */
+
+/*
+ * Copyright (c) 2001 Andrew P. Lentvorski, Jr.
+ * Copyright (c) 2000 Manuel Bouyer.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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>
+__FBSDID("$FreeBSD$");
+
+#define LOCKD_DEBUG
+
+#include <stdio.h>
+#ifdef LOCKD_DEBUG
+#include <stdarg.h>
+#endif
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <rpc/rpc.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/wait.h>
+#include <rpcsvc/sm_inter.h>
+#include <rpcsvc/nlm_prot.h>
+#include "lockd_lock.h"
+#include "lockd.h"
+
+#define MAXOBJECTSIZE 64
+#define MAXBUFFERSIZE 1024
+
+/*
+ * A set of utilities for managing file locking
+ *
+ * XXX: All locks are in a linked list, a better structure should be used
+ * to improve search/access efficiency.
+ */
+
+/* struct describing a lock */
+struct file_lock {
+ LIST_ENTRY(file_lock) nfslocklist;
+ fhandle_t filehandle; /* NFS filehandle */
+ struct sockaddr *addr;
+ struct nlm4_holder client; /* lock holder */
+ /* XXX: client_cookie used *only* in send_granted */
+ netobj client_cookie; /* cookie sent by the client */
+ int nsm_status; /* status from the remote lock manager */
+ int status; /* lock status, see below */
+ int flags; /* lock flags, see lockd_lock.h */
+ int blocking; /* blocking lock or not */
+ char client_name[SM_MAXSTRLEN]; /* client_name is really variable
+ length and must be last! */
+};
+
+LIST_HEAD(nfslocklist_head, file_lock);
+struct nfslocklist_head nfslocklist_head = LIST_HEAD_INITIALIZER(nfslocklist_head);
+
+LIST_HEAD(blockedlocklist_head, file_lock);
+struct blockedlocklist_head blockedlocklist_head = LIST_HEAD_INITIALIZER(blockedlocklist_head);
+
+/* lock status */
+#define LKST_LOCKED 1 /* lock is locked */
+/* XXX: Is this flag file specific or lock specific? */
+#define LKST_WAITING 2 /* file is already locked by another host */
+#define LKST_PROCESSING 3 /* child is trying to acquire the lock */
+#define LKST_DYING 4 /* must dies when we get news from the child */
+
+/* struct describing a monitored host */
+struct host {
+ LIST_ENTRY(host) hostlst;
+ int refcnt;
+ char name[SM_MAXSTRLEN]; /* name is really variable length and
+ must be last! */
+};
+/* list of hosts we monitor */
+LIST_HEAD(hostlst_head, host);
+struct hostlst_head hostlst_head = LIST_HEAD_INITIALIZER(hostlst_head);
+
+/*
+ * File monitoring handlers
+ * XXX: These might be able to be removed when kevent support
+ * is placed into the hardware lock/unlock routines. (ie.
+ * let the kernel do all the file monitoring)
+ */
+
+/* Struct describing a monitored file */
+struct monfile {
+ LIST_ENTRY(monfile) monfilelist;
+ fhandle_t filehandle; /* Local access filehandle */
+ int fd; /* file descriptor: remains open until unlock! */
+ int refcount;
+ int exclusive;
+};
+
+/* List of files we monitor */
+LIST_HEAD(monfilelist_head, monfile);
+struct monfilelist_head monfilelist_head = LIST_HEAD_INITIALIZER(monfilelist_head);
+
+static int debugdelay = 0;
+
+enum nfslock_status { NFS_GRANTED = 0, NFS_GRANTED_DUPLICATE,
+ NFS_DENIED, NFS_DENIED_NOLOCK,
+ NFS_RESERR };
+
+enum hwlock_status { HW_GRANTED = 0, HW_GRANTED_DUPLICATE,
+ HW_DENIED, HW_DENIED_NOLOCK,
+ HW_STALEFH, HW_READONLY, HW_RESERR };
+
+enum partialfilelock_status { PFL_GRANTED=0, PFL_GRANTED_DUPLICATE, PFL_DENIED,
+ PFL_NFSDENIED, PFL_NFSBLOCKED, PFL_NFSDENIED_NOLOCK, PFL_NFSRESERR,
+ PFL_HWDENIED, PFL_HWBLOCKED, PFL_HWDENIED_NOLOCK, PFL_HWRESERR};
+
+enum LFLAGS {LEDGE_LEFT, LEDGE_LBOUNDARY, LEDGE_INSIDE, LEDGE_RBOUNDARY, LEDGE_RIGHT};
+enum RFLAGS {REDGE_LEFT, REDGE_LBOUNDARY, REDGE_INSIDE, REDGE_RBOUNDARY, REDGE_RIGHT};
+/* XXX: WARNING! I HAVE OVERLOADED THIS STATUS ENUM! SPLIT IT APART INTO TWO */
+enum split_status {SPL_DISJOINT=0, SPL_LOCK1=1, SPL_LOCK2=2, SPL_CONTAINED=4, SPL_RESERR=8};
+
+enum partialfilelock_status lock_partialfilelock(struct file_lock *fl);
+
+void send_granted(struct file_lock *fl, int opcode);
+void siglock(void);
+void sigunlock(void);
+void monitor_lock_host(const char *hostname);
+void unmonitor_lock_host(char *hostname);
+
+void copy_nlm4_lock_to_nlm4_holder(const struct nlm4_lock *src,
+ const bool_t exclusive, struct nlm4_holder *dest);
+struct file_lock * allocate_file_lock(const netobj *lockowner,
+ const netobj *matchcookie,
+ const struct sockaddr *addr,
+ const char *caller_name);
+void deallocate_file_lock(struct file_lock *fl);
+void fill_file_lock(struct file_lock *fl, const fhandle_t *fh,
+ const bool_t exclusive, const int32_t svid,
+ const u_int64_t offset, const u_int64_t len,
+ const int state, const int status, const int flags, const int blocking);
+int regions_overlap(const u_int64_t start1, const u_int64_t len1,
+ const u_int64_t start2, const u_int64_t len2);
+enum split_status region_compare(const u_int64_t starte, const u_int64_t lene,
+ const u_int64_t startu, const u_int64_t lenu,
+ u_int64_t *start1, u_int64_t *len1, u_int64_t *start2, u_int64_t *len2);
+int same_netobj(const netobj *n0, const netobj *n1);
+int same_filelock_identity(const struct file_lock *fl0,
+ const struct file_lock *fl2);
+
+static void debuglog(char const *fmt, ...);
+void dump_static_object(const unsigned char* object, const int sizeof_object,
+ unsigned char* hbuff, const int sizeof_hbuff,
+ unsigned char* cbuff, const int sizeof_cbuff);
+void dump_netobj(const struct netobj *nobj);
+void dump_filelock(const struct file_lock *fl);
+struct file_lock * get_lock_matching_unlock(const struct file_lock *fl);
+enum nfslock_status test_nfslock(const struct file_lock *fl,
+ struct file_lock **conflicting_fl);
+enum nfslock_status lock_nfslock(struct file_lock *fl);
+enum nfslock_status delete_nfslock(struct file_lock *fl);
+enum nfslock_status unlock_nfslock(const struct file_lock *fl,
+ struct file_lock **released_lock, struct file_lock **left_lock,
+ struct file_lock **right_lock);
+enum hwlock_status lock_hwlock(struct file_lock *fl);
+enum split_status split_nfslock(const struct file_lock *exist_lock,
+ const struct file_lock *unlock_lock, struct file_lock **left_lock,
+ struct file_lock **right_lock);
+int duplicate_block(struct file_lock *fl);
+void add_blockingfilelock(struct file_lock *fl);
+enum hwlock_status unlock_hwlock(const struct file_lock *fl);
+enum hwlock_status test_hwlock(const struct file_lock *fl,
+ struct file_lock **conflicting_fl);
+void remove_blockingfilelock(struct file_lock *fl);
+void clear_blockingfilelock(const char *hostname);
+void retry_blockingfilelocklist(void);
+enum partialfilelock_status unlock_partialfilelock(
+ const struct file_lock *fl);
+void clear_partialfilelock(const char *hostname);
+enum partialfilelock_status test_partialfilelock(
+ const struct file_lock *fl, struct file_lock **conflicting_fl);
+enum nlm_stats do_test(struct file_lock *fl,
+ struct file_lock **conflicting_fl);
+enum nlm_stats do_unlock(struct file_lock *fl);
+enum nlm_stats do_lock(struct file_lock *fl);
+void do_clear(const char *hostname);
+size_t strnlen(const char *, size_t);
+
+void
+debuglog(char const *fmt, ...)
+{
+ va_list ap;
+
+ if (debug_level < 1) {
+ return;
+ }
+
+ sleep(debugdelay);
+
+ va_start(ap, fmt);
+ vsyslog(LOG_DEBUG, fmt, ap);
+ va_end(ap);
+}
+
+void
+dump_static_object(object, size_object, hbuff, size_hbuff, cbuff, size_cbuff)
+ const unsigned char *object;
+ const int size_object;
+ unsigned char *hbuff;
+ const int size_hbuff;
+ unsigned char *cbuff;
+ const int size_cbuff;
+{
+ int i, objectsize;
+
+ if (debug_level < 2) {
+ return;
+ }
+
+ objectsize = size_object;
+
+ if (objectsize == 0) {
+ debuglog("object is size 0\n");
+ } else {
+ if (objectsize > MAXOBJECTSIZE) {
+ debuglog("Object of size %d being clamped"
+ "to size %d\n", objectsize, MAXOBJECTSIZE);
+ objectsize = MAXOBJECTSIZE;
+ }
+
+ if (hbuff != NULL) {
+ if (size_hbuff < objectsize*2+1) {
+ debuglog("Hbuff not large enough."
+ " Increase size\n");
+ } else {
+ for(i=0;i<objectsize;i++) {
+ sprintf(hbuff+i*2,"%02x",*(object+i));
+ }
+ *(hbuff+i*2) = '\0';
+ }
+ }
+
+ if (cbuff != NULL) {
+ if (size_cbuff < objectsize+1) {
+ debuglog("Cbuff not large enough."
+ " Increase Size\n");
+ }
+
+ for(i=0;i<objectsize;i++) {
+ if (*(object+i) >= 32 && *(object+i) <= 127) {
+ *(cbuff+i) = *(object+i);
+ } else {
+ *(cbuff+i) = '.';
+ }
+ }
+ *(cbuff+i) = '\0';
+ }
+ }
+}
+
+void
+dump_netobj(const struct netobj *nobj)
+{
+ char hbuff[MAXBUFFERSIZE*2];
+ char cbuff[MAXBUFFERSIZE];
+
+ if (debug_level < 2) {
+ return;
+ }
+
+ if (nobj == NULL) {
+ debuglog("Null netobj pointer\n");
+ }
+ else if (nobj->n_len == 0) {
+ debuglog("Size zero netobj\n");
+ } else {
+ dump_static_object(nobj->n_bytes, nobj->n_len,
+ hbuff, sizeof(hbuff), cbuff, sizeof(cbuff));
+ debuglog("netobj: len: %d data: %s ::: %s\n",
+ nobj->n_len, hbuff, cbuff);
+ }
+}
+
+/* #define DUMP_FILELOCK_VERBOSE */
+void
+dump_filelock(const struct file_lock *fl)
+{
+#ifdef DUMP_FILELOCK_VERBOSE
+ char hbuff[MAXBUFFERSIZE*2];
+ char cbuff[MAXBUFFERSIZE];
+#endif
+
+ if (debug_level < 2) {
+ return;
+ }
+
+ if (fl != NULL) {
+ debuglog("Dumping file lock structure @ %p\n", fl);
+
+#ifdef DUMP_FILELOCK_VERBOSE
+ dump_static_object((unsigned char *)&fl->filehandle,
+ sizeof(fl->filehandle), hbuff, sizeof(hbuff),
+ cbuff, sizeof(cbuff));
+ debuglog("Filehandle: %8s ::: %8s\n", hbuff, cbuff);
+#endif
+
+ debuglog("Dumping nlm4_holder:\n"
+ "exc: %x svid: %x offset:len %llx:%llx\n",
+ fl->client.exclusive, fl->client.svid,
+ fl->client.l_offset, fl->client.l_len);
+
+#ifdef DUMP_FILELOCK_VERBOSE
+ debuglog("Dumping client identity:\n");
+ dump_netobj(&fl->client.oh);
+
+ debuglog("Dumping client cookie:\n");
+ dump_netobj(&fl->client_cookie);
+
+ debuglog("nsm: %d status: %d flags: %d svid: %x"
+ " client_name: %s\n", fl->nsm_status, fl->status,
+ fl->flags, fl->client.svid, fl->client_name);
+#endif
+ } else {
+ debuglog("NULL file lock structure\n");
+ }
+}
+
+void
+copy_nlm4_lock_to_nlm4_holder(src, exclusive, dest)
+ const struct nlm4_lock *src;
+ const bool_t exclusive;
+ struct nlm4_holder *dest;
+{
+
+ dest->exclusive = exclusive;
+ dest->oh.n_len = src->oh.n_len;
+ dest->oh.n_bytes = src->oh.n_bytes;
+ dest->svid = src->svid;
+ dest->l_offset = src->l_offset;
+ dest->l_len = src->l_len;
+}
+
+
+size_t
+strnlen(const char *s, size_t len)
+{
+ size_t n;
+
+ for (n = 0; s[n] != 0 && n < len; n++)
+ ;
+ return n;
+}
+
+/*
+ * allocate_file_lock: Create a lock with the given parameters
+ */
+
+struct file_lock *
+allocate_file_lock(const netobj *lockowner, const netobj *matchcookie,
+ const struct sockaddr *addr, const char *caller_name)
+{
+ struct file_lock *newfl;
+ size_t n;
+
+ /* Beware of rubbish input! */
+ n = strnlen(caller_name, SM_MAXSTRLEN);
+ if (n == SM_MAXSTRLEN) {
+ return NULL;
+ }
+
+ newfl = malloc(sizeof(*newfl) - sizeof(newfl->client_name) + n + 1);
+ if (newfl == NULL) {
+ return NULL;
+ }
+ bzero(newfl, sizeof(*newfl) - sizeof(newfl->client_name));
+ memcpy(newfl->client_name, caller_name, n);
+ newfl->client_name[n] = 0;
+
+ newfl->client.oh.n_bytes = malloc(lockowner->n_len);
+ if (newfl->client.oh.n_bytes == NULL) {
+ free(newfl);
+ return NULL;
+ }
+ newfl->client.oh.n_len = lockowner->n_len;
+ bcopy(lockowner->n_bytes, newfl->client.oh.n_bytes, lockowner->n_len);
+
+ newfl->client_cookie.n_bytes = malloc(matchcookie->n_len);
+ if (newfl->client_cookie.n_bytes == NULL) {
+ free(newfl->client.oh.n_bytes);
+ free(newfl);
+ return NULL;
+ }
+ newfl->client_cookie.n_len = matchcookie->n_len;
+ bcopy(matchcookie->n_bytes, newfl->client_cookie.n_bytes, matchcookie->n_len);
+
+ newfl->addr = malloc(addr->sa_len);
+ if (newfl->addr == NULL) {
+ free(newfl->client_cookie.n_bytes);
+ free(newfl->client.oh.n_bytes);
+ free(newfl);
+ return NULL;
+ }
+ memcpy(newfl->addr, addr, addr->sa_len);
+
+ return newfl;
+}
+
+/*
+ * file_file_lock: Force creation of a valid file lock
+ */
+void
+fill_file_lock(struct file_lock *fl, const fhandle_t *fh,
+ const bool_t exclusive, const int32_t svid,
+ const u_int64_t offset, const u_int64_t len,
+ const int state, const int status, const int flags, const int blocking)
+{
+ bcopy(fh, &fl->filehandle, sizeof(fhandle_t));
+
+ fl->client.exclusive = exclusive;
+ fl->client.svid = svid;
+ fl->client.l_offset = offset;
+ fl->client.l_len = len;
+
+ fl->nsm_status = state;
+ fl->status = status;
+ fl->flags = flags;
+ fl->blocking = blocking;
+}
+
+/*
+ * deallocate_file_lock: Free all storage associated with a file lock
+ */
+void
+deallocate_file_lock(struct file_lock *fl)
+{
+ free(fl->addr);
+ free(fl->client.oh.n_bytes);
+ free(fl->client_cookie.n_bytes);
+ free(fl);
+}
+
+/*
+ * regions_overlap(): This function examines the two provided regions for
+ * overlap.
+ */
+int
+regions_overlap(start1, len1, start2, len2)
+ const u_int64_t start1, len1, start2, len2;
+{
+ u_int64_t d1,d2,d3,d4;
+ enum split_status result;
+
+ debuglog("Entering region overlap with vals: %llu:%llu--%llu:%llu\n",
+ start1, len1, start2, len2);
+
+ result = region_compare(start1, len1, start2, len2,
+ &d1, &d2, &d3, &d4);
+
+ debuglog("Exiting region overlap with val: %d\n",result);
+
+ if (result == SPL_DISJOINT) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+/*
+ * region_compare(): Examine lock regions and split appropriately
+ *
+ * XXX: Fix 64 bit overflow problems
+ * XXX: Check to make sure I got *ALL* the cases.
+ * XXX: This DESPERATELY needs a regression test.
+ */
+enum split_status
+region_compare(starte, lene, startu, lenu,
+ start1, len1, start2, len2)
+ const u_int64_t starte, lene, startu, lenu;
+ u_int64_t *start1, *len1, *start2, *len2;
+{
+ /*
+ * Please pay attention to the sequential exclusions
+ * of the if statements!!!
+ */
+ enum LFLAGS lflags;
+ enum RFLAGS rflags;
+ enum split_status retval;
+
+ retval = SPL_DISJOINT;
+
+ if (lene == 0 && lenu == 0) {
+ /* Examine left edge of locker */
+ lflags = LEDGE_INSIDE;
+ if (startu < starte) {
+ lflags = LEDGE_LEFT;
+ } else if (startu == starte) {
+ lflags = LEDGE_LBOUNDARY;
+ }
+
+ rflags = REDGE_RBOUNDARY; /* Both are infiinite */
+
+ if (lflags == LEDGE_INSIDE) {
+ *start1 = starte;
+ *len1 = startu - starte;
+ }
+
+ if (lflags == LEDGE_LEFT || lflags == LEDGE_LBOUNDARY) {
+ retval = SPL_CONTAINED;
+ } else {
+ retval = SPL_LOCK1;
+ }
+ } else if (lene == 0 && lenu != 0) {
+ /* Established lock is infinite */
+ /* Examine left edge of unlocker */
+ lflags = LEDGE_INSIDE;
+ if (startu < starte) {
+ lflags = LEDGE_LEFT;
+ } else if (startu == starte) {
+ lflags = LEDGE_LBOUNDARY;
+ }
+
+ /* Examine right edge of unlocker */
+ if (startu + lenu < starte) {
+ /* Right edge of unlocker left of established lock */
+ rflags = REDGE_LEFT;
+ return SPL_DISJOINT;
+ } else if (startu + lenu == starte) {
+ /* Right edge of unlocker on start of established lock */
+ rflags = REDGE_LBOUNDARY;
+ return SPL_DISJOINT;
+ } else { /* Infinifty is right of finity */
+ /* Right edge of unlocker inside established lock */
+ rflags = REDGE_INSIDE;
+ }
+
+ if (lflags == LEDGE_INSIDE) {
+ *start1 = starte;
+ *len1 = startu - starte;
+ retval |= SPL_LOCK1;
+ }
+
+ if (rflags == REDGE_INSIDE) {
+ /* Create right lock */
+ *start2 = startu+lenu;
+ *len2 = 0;
+ retval |= SPL_LOCK2;
+ }
+ } else if (lene != 0 && lenu == 0) {
+ /* Unlocker is infinite */
+ /* Examine left edge of unlocker */
+ lflags = LEDGE_RIGHT;
+ if (startu < starte) {
+ lflags = LEDGE_LEFT;
+ retval = SPL_CONTAINED;
+ return retval;
+ } else if (startu == starte) {
+ lflags = LEDGE_LBOUNDARY;
+ retval = SPL_CONTAINED;
+ return retval;
+ } else if ((startu > starte) && (startu < starte + lene - 1)) {
+ lflags = LEDGE_INSIDE;
+ } else if (startu == starte + lene - 1) {
+ lflags = LEDGE_RBOUNDARY;
+ } else { /* startu > starte + lene -1 */
+ lflags = LEDGE_RIGHT;
+ return SPL_DISJOINT;
+ }
+
+ rflags = REDGE_RIGHT; /* Infinity is right of finity */
+
+ if (lflags == LEDGE_INSIDE || lflags == LEDGE_RBOUNDARY) {
+ *start1 = starte;
+ *len1 = startu - starte;
+ retval |= SPL_LOCK1;
+ return retval;
+ }
+ } else {
+ /* Both locks are finite */
+
+ /* Examine left edge of unlocker */
+ lflags = LEDGE_RIGHT;
+ if (startu < starte) {
+ lflags = LEDGE_LEFT;
+ } else if (startu == starte) {
+ lflags = LEDGE_LBOUNDARY;
+ } else if ((startu > starte) && (startu < starte + lene - 1)) {
+ lflags = LEDGE_INSIDE;
+ } else if (startu == starte + lene - 1) {
+ lflags = LEDGE_RBOUNDARY;
+ } else { /* startu > starte + lene -1 */
+ lflags = LEDGE_RIGHT;
+ return SPL_DISJOINT;
+ }
+
+ /* Examine right edge of unlocker */
+ if (startu + lenu < starte) {
+ /* Right edge of unlocker left of established lock */
+ rflags = REDGE_LEFT;
+ return SPL_DISJOINT;
+ } else if (startu + lenu == starte) {
+ /* Right edge of unlocker on start of established lock */
+ rflags = REDGE_LBOUNDARY;
+ return SPL_DISJOINT;
+ } else if (startu + lenu < starte + lene) {
+ /* Right edge of unlocker inside established lock */
+ rflags = REDGE_INSIDE;
+ } else if (startu + lenu == starte + lene) {
+ /* Right edge of unlocker on right edge of established lock */
+ rflags = REDGE_RBOUNDARY;
+ } else { /* startu + lenu > starte + lene */
+ /* Right edge of unlocker is right of established lock */
+ rflags = REDGE_RIGHT;
+ }
+
+ if (lflags == LEDGE_INSIDE || lflags == LEDGE_RBOUNDARY) {
+ /* Create left lock */
+ *start1 = starte;
+ *len1 = (startu - starte);
+ retval |= SPL_LOCK1;
+ }
+
+ if (rflags == REDGE_INSIDE) {
+ /* Create right lock */
+ *start2 = startu+lenu;
+ *len2 = starte+lene-(startu+lenu);
+ retval |= SPL_LOCK2;
+ }
+
+ if ((lflags == LEDGE_LEFT || lflags == LEDGE_LBOUNDARY) &&
+ (rflags == REDGE_RBOUNDARY || rflags == REDGE_RIGHT)) {
+ retval = SPL_CONTAINED;
+ }
+ }
+ return retval;
+}
+
+/*
+ * same_netobj: Compares the apprpriate bits of a netobj for identity
+ */
+int
+same_netobj(const netobj *n0, const netobj *n1)
+{
+ int retval;
+
+ retval = 0;
+
+ debuglog("Entering netobj identity check\n");
+
+ if (n0->n_len == n1->n_len) {
+ debuglog("Preliminary length check passed\n");
+ retval = !bcmp(n0->n_bytes, n1->n_bytes, n0->n_len);
+ debuglog("netobj %smatch\n", retval ? "" : "mis");
+ }
+
+ return (retval);
+}
+
+/*
+ * same_filelock_identity: Compares the appropriate bits of a file_lock
+ */
+int
+same_filelock_identity(fl0, fl1)
+ const struct file_lock *fl0, *fl1;
+{
+ int retval;
+
+ retval = 0;
+
+ debuglog("Checking filelock identity\n");
+
+ /*
+ * Check process ids and host information.
+ */
+ retval = (fl0->client.svid == fl1->client.svid &&
+ same_netobj(&(fl0->client.oh), &(fl1->client.oh)));
+
+ debuglog("Exiting checking filelock identity: retval: %d\n",retval);
+
+ return (retval);
+}
+
+/*
+ * Below here are routines associated with manipulating the NFS
+ * lock list.
+ */
+
+/*
+ * get_lock_matching_unlock: Return a lock which matches the given unlock lock
+ * or NULL otehrwise
+ * XXX: It is a shame that this duplicates so much code from test_nfslock.
+ */
+struct file_lock *
+get_lock_matching_unlock(const struct file_lock *fl)
+{
+ struct file_lock *ifl; /* Iterator */
+
+ debuglog("Entering get_lock_matching_unlock\n");
+ debuglog("********Dump of fl*****************\n");
+ dump_filelock(fl);
+
+ LIST_FOREACH(ifl, &nfslocklist_head, nfslocklist) {
+ debuglog("Pointer to file lock: %p\n",ifl);
+
+ debuglog("****Dump of ifl****\n");
+ dump_filelock(ifl);
+ debuglog("*******************\n");
+
+ /*
+ * XXX: It is conceivable that someone could use the NLM RPC
+ * system to directly access filehandles. This may be a
+ * security hazard as the filehandle code may bypass normal
+ * file access controls
+ */
+ if (bcmp(&fl->filehandle, &ifl->filehandle, sizeof(fhandle_t)))
+ continue;
+
+ debuglog("get_lock_matching_unlock: Filehandles match, "
+ "checking regions\n");
+
+ /* Filehandles match, check for region overlap */
+ if (!regions_overlap(fl->client.l_offset, fl->client.l_len,
+ ifl->client.l_offset, ifl->client.l_len))
+ continue;
+
+ debuglog("get_lock_matching_unlock: Region overlap"
+ " found %llu : %llu -- %llu : %llu\n",
+ fl->client.l_offset,fl->client.l_len,
+ ifl->client.l_offset,ifl->client.l_len);
+
+ /* Regions overlap, check the identity */
+ if (!same_filelock_identity(fl,ifl))
+ continue;
+
+ debuglog("get_lock_matching_unlock: Duplicate lock id. Granting\n");
+ return (ifl);
+ }
+
+ debuglog("Exiting bet_lock_matching_unlock\n");
+
+ return (NULL);
+}
+
+/*
+ * test_nfslock: check for NFS lock in lock list
+ *
+ * This routine makes the following assumptions:
+ * 1) Nothing will adjust the lock list during a lookup
+ *
+ * This routine has an intersting quirk which bit me hard.
+ * The conflicting_fl is the pointer to the conflicting lock.
+ * However, to modify the "*pointer* to the conflicting lock" rather
+ * that the "conflicting lock itself" one must pass in a "pointer to
+ * the pointer of the conflicting lock". Gross.
+ */
+
+enum nfslock_status
+test_nfslock(const struct file_lock *fl, struct file_lock **conflicting_fl)
+{
+ struct file_lock *ifl; /* Iterator */
+ enum nfslock_status retval;
+
+ debuglog("Entering test_nfslock\n");
+
+ retval = NFS_GRANTED;
+ (*conflicting_fl) = NULL;
+
+ debuglog("Entering lock search loop\n");
+
+ debuglog("***********************************\n");
+ debuglog("Dumping match filelock\n");
+ debuglog("***********************************\n");
+ dump_filelock(fl);
+ debuglog("***********************************\n");
+
+ LIST_FOREACH(ifl, &nfslocklist_head, nfslocklist) {
+ if (retval == NFS_DENIED)
+ break;
+
+ debuglog("Top of lock loop\n");
+ debuglog("Pointer to file lock: %p\n",ifl);
+
+ debuglog("***********************************\n");
+ debuglog("Dumping test filelock\n");
+ debuglog("***********************************\n");
+ dump_filelock(ifl);
+ debuglog("***********************************\n");
+
+ /*
+ * XXX: It is conceivable that someone could use the NLM RPC
+ * system to directly access filehandles. This may be a
+ * security hazard as the filehandle code may bypass normal
+ * file access controls
+ */
+ if (bcmp(&fl->filehandle, &ifl->filehandle, sizeof(fhandle_t)))
+ continue;
+
+ debuglog("test_nfslock: filehandle match found\n");
+
+ /* Filehandles match, check for region overlap */
+ if (!regions_overlap(fl->client.l_offset, fl->client.l_len,
+ ifl->client.l_offset, ifl->client.l_len))
+ continue;
+
+ debuglog("test_nfslock: Region overlap found"
+ " %llu : %llu -- %llu : %llu\n",
+ fl->client.l_offset,fl->client.l_len,
+ ifl->client.l_offset,ifl->client.l_len);
+
+ /* Regions overlap, check the exclusivity */
+ if (!(fl->client.exclusive || ifl->client.exclusive))
+ continue;
+
+ debuglog("test_nfslock: Exclusivity failure: %d %d\n",
+ fl->client.exclusive,
+ ifl->client.exclusive);
+
+ if (same_filelock_identity(fl,ifl)) {
+ debuglog("test_nfslock: Duplicate id. Granting\n");
+ (*conflicting_fl) = ifl;
+ retval = NFS_GRANTED_DUPLICATE;
+ } else {
+ /* locking attempt fails */
+ debuglog("test_nfslock: Lock attempt failed\n");
+ debuglog("Desired lock\n");
+ dump_filelock(fl);
+ debuglog("Conflicting lock\n");
+ dump_filelock(ifl);
+ (*conflicting_fl) = ifl;
+ retval = NFS_DENIED;
+ }
+ }
+
+ debuglog("Dumping file locks\n");
+ debuglog("Exiting test_nfslock\n");
+
+ return (retval);
+}
+
+/*
+ * lock_nfslock: attempt to create a lock in the NFS lock list
+ *
+ * This routine tests whether the lock will be granted and then adds
+ * the entry to the lock list if so.
+ *
+ * Argument fl gets modified as its list housekeeping entries get modified
+ * upon insertion into the NFS lock list
+ *
+ * This routine makes several assumptions:
+ * 1) It is perfectly happy to grant a duplicate lock from the same pid.
+ * While this seems to be intuitively wrong, it is required for proper
+ * Posix semantics during unlock. It is absolutely imperative to not
+ * unlock the main lock before the two child locks are established. Thus,
+ * one has be be able to create duplicate locks over an existing lock
+ * 2) It currently accepts duplicate locks from the same id,pid
+ */
+
+enum nfslock_status
+lock_nfslock(struct file_lock *fl)
+{
+ enum nfslock_status retval;
+ struct file_lock *dummy_fl;
+
+ dummy_fl = NULL;
+
+ debuglog("Entering lock_nfslock...\n");
+
+ retval = test_nfslock(fl,&dummy_fl);
+
+ if (retval == NFS_GRANTED || retval == NFS_GRANTED_DUPLICATE) {
+ debuglog("Inserting lock...\n");
+ dump_filelock(fl);
+ LIST_INSERT_HEAD(&nfslocklist_head, fl, nfslocklist);
+ }
+
+ debuglog("Exiting lock_nfslock...\n");
+
+ return (retval);
+}
+
+/*
+ * delete_nfslock: delete an NFS lock list entry
+ *
+ * This routine is used to delete a lock out of the NFS lock list
+ * without regard to status, underlying locks, regions or anything else
+ *
+ * Note that this routine *does not deallocate memory* of the lock.
+ * It just disconnects it from the list. The lock can then be used
+ * by other routines without fear of trashing the list.
+ */
+
+enum nfslock_status
+delete_nfslock(struct file_lock *fl)
+{
+
+ LIST_REMOVE(fl, nfslocklist);
+
+ return (NFS_GRANTED);
+}
+
+enum split_status
+split_nfslock(exist_lock, unlock_lock, left_lock, right_lock)
+ const struct file_lock *exist_lock, *unlock_lock;
+ struct file_lock **left_lock, **right_lock;
+{
+ u_int64_t start1, len1, start2, len2;
+ enum split_status spstatus;
+
+ spstatus = region_compare(exist_lock->client.l_offset, exist_lock->client.l_len,
+ unlock_lock->client.l_offset, unlock_lock->client.l_len,
+ &start1, &len1, &start2, &len2);
+
+ if ((spstatus & SPL_LOCK1) != 0) {
+ *left_lock = allocate_file_lock(&exist_lock->client.oh, &exist_lock->client_cookie, exist_lock->addr, exist_lock->client_name);
+ if (*left_lock == NULL) {
+ debuglog("Unable to allocate resource for split 1\n");
+ return SPL_RESERR;
+ }
+
+ fill_file_lock(*left_lock, &exist_lock->filehandle,
+ exist_lock->client.exclusive, exist_lock->client.svid,
+ start1, len1,
+ exist_lock->nsm_status,
+ exist_lock->status, exist_lock->flags, exist_lock->blocking);
+ }
+
+ if ((spstatus & SPL_LOCK2) != 0) {
+ *right_lock = allocate_file_lock(&exist_lock->client.oh, &exist_lock->client_cookie, exist_lock->addr, exist_lock->client_name);
+ if (*right_lock == NULL) {
+ debuglog("Unable to allocate resource for split 1\n");
+ if (*left_lock != NULL) {
+ deallocate_file_lock(*left_lock);
+ }
+ return SPL_RESERR;
+ }
+
+ fill_file_lock(*right_lock, &exist_lock->filehandle,
+ exist_lock->client.exclusive, exist_lock->client.svid,
+ start2, len2,
+ exist_lock->nsm_status,
+ exist_lock->status, exist_lock->flags, exist_lock->blocking);
+ }
+
+ return spstatus;
+}
+
+enum nfslock_status
+unlock_nfslock(fl, released_lock, left_lock, right_lock)
+ const struct file_lock *fl;
+ struct file_lock **released_lock;
+ struct file_lock **left_lock;
+ struct file_lock **right_lock;
+{
+ struct file_lock *mfl; /* Matching file lock */
+ enum nfslock_status retval;
+ enum split_status spstatus;
+
+ debuglog("Entering unlock_nfslock\n");
+
+ *released_lock = NULL;
+ *left_lock = NULL;
+ *right_lock = NULL;
+
+ retval = NFS_DENIED_NOLOCK;
+
+ debuglog("Attempting to match lock...\n");
+ mfl = get_lock_matching_unlock(fl);
+
+ if (mfl != NULL) {
+ debuglog("Unlock matched. Querying for split\n");
+
+ spstatus = split_nfslock(mfl, fl, left_lock, right_lock);
+
+ debuglog("Split returned %d %p %p %p %p\n",spstatus,mfl,fl,*left_lock,*right_lock);
+ debuglog("********Split dumps********");
+ dump_filelock(mfl);
+ dump_filelock(fl);
+ dump_filelock(*left_lock);
+ dump_filelock(*right_lock);
+ debuglog("********End Split dumps********");
+
+ if (spstatus == SPL_RESERR) {
+ if (*left_lock != NULL) {
+ deallocate_file_lock(*left_lock);
+ *left_lock = NULL;
+ }
+
+ if (*right_lock != NULL) {
+ deallocate_file_lock(*right_lock);
+ *right_lock = NULL;
+ }
+
+ return NFS_RESERR;
+ }
+
+ /* Insert new locks from split if required */
+ if (*left_lock != NULL) {
+ debuglog("Split left activated\n");
+ LIST_INSERT_HEAD(&nfslocklist_head, *left_lock, nfslocklist);
+ }
+
+ if (*right_lock != NULL) {
+ debuglog("Split right activated\n");
+ LIST_INSERT_HEAD(&nfslocklist_head, *right_lock, nfslocklist);
+ }
+
+ /* Unlock the lock since it matches identity */
+ LIST_REMOVE(mfl, nfslocklist);
+ *released_lock = mfl;
+ retval = NFS_GRANTED;
+ }
+
+ debuglog("Exiting unlock_nfslock\n");
+
+ return retval;
+}
+
+/*
+ * Below here are the routines for manipulating the file lock directly
+ * on the disk hardware itself
+ */
+enum hwlock_status
+lock_hwlock(struct file_lock *fl)
+{
+ struct monfile *imf,*nmf;
+ int lflags, flerror;
+
+ /* Scan to see if filehandle already present */
+ LIST_FOREACH(imf, &monfilelist_head, monfilelist) {
+ if (bcmp(&fl->filehandle, &imf->filehandle,
+ sizeof(fl->filehandle)) == 0) {
+ /* imf is the correct filehandle */
+ break;
+ }
+ }
+
+ /*
+ * Filehandle already exists (we control the file)
+ * *AND* NFS has already cleared the lock for availability
+ * Grant it and bump the refcount.
+ */
+ if (imf != NULL) {
+ ++(imf->refcount);
+ return (HW_GRANTED);
+ }
+
+ /* No filehandle found, create and go */
+ nmf = malloc(sizeof(struct monfile));
+ if (nmf == NULL) {
+ debuglog("hwlock resource allocation failure\n");
+ return (HW_RESERR);
+ }
+
+ /* XXX: Is O_RDWR always the correct mode? */
+ nmf->fd = fhopen(&fl->filehandle, O_RDWR);
+ if (nmf->fd < 0) {
+ debuglog("fhopen failed (from %16s): %32s\n",
+ fl->client_name, strerror(errno));
+ free(nmf);
+ switch (errno) {
+ case ESTALE:
+ return (HW_STALEFH);
+ case EROFS:
+ return (HW_READONLY);
+ default:
+ return (HW_RESERR);
+ }
+ }
+
+ /* File opened correctly, fill the monitor struct */
+ bcopy(&fl->filehandle, &nmf->filehandle, sizeof(fl->filehandle));
+ nmf->refcount = 1;
+ nmf->exclusive = fl->client.exclusive;
+
+ lflags = (nmf->exclusive == 1) ?
+ (LOCK_EX | LOCK_NB) : (LOCK_SH | LOCK_NB);
+
+ flerror = flock(nmf->fd, lflags);
+
+ if (flerror != 0) {
+ debuglog("flock failed (from %16s): %32s\n",
+ fl->client_name, strerror(errno));
+ close(nmf->fd);
+ free(nmf);
+ switch (errno) {
+ case EAGAIN:
+ return (HW_DENIED);
+ case ESTALE:
+ return (HW_STALEFH);
+ case EROFS:
+ return (HW_READONLY);
+ default:
+ return (HW_RESERR);
+ break;
+ }
+ }
+
+ /* File opened and locked */
+ LIST_INSERT_HEAD(&monfilelist_head, nmf, monfilelist);
+
+ debuglog("flock succeeded (from %16s)\n", fl->client_name);
+ return (HW_GRANTED);
+}
+
+enum hwlock_status
+unlock_hwlock(const struct file_lock *fl)
+{
+ struct monfile *imf;
+
+ debuglog("Entering unlock_hwlock\n");
+ debuglog("Entering loop interation\n");
+
+ /* Scan to see if filehandle already present */
+ LIST_FOREACH(imf, &monfilelist_head, monfilelist) {
+ if (bcmp(&fl->filehandle, &imf->filehandle,
+ sizeof(fl->filehandle)) == 0) {
+ /* imf is the correct filehandle */
+ break;
+ }
+ }
+
+ debuglog("Completed iteration. Proceeding\n");
+
+ if (imf == NULL) {
+ /* No lock found */
+ debuglog("Exiting unlock_hwlock (HW_DENIED_NOLOCK)\n");
+ return (HW_DENIED_NOLOCK);
+ }
+
+ /* Lock found */
+ --imf->refcount;
+
+ if (imf->refcount < 0) {
+ debuglog("Negative hardware reference count\n");
+ }
+
+ if (imf->refcount <= 0) {
+ close(imf->fd);
+ LIST_REMOVE(imf, monfilelist);
+ free(imf);
+ }
+ debuglog("Exiting unlock_hwlock (HW_GRANTED)\n");
+ return (HW_GRANTED);
+}
+
+enum hwlock_status
+test_hwlock(fl, conflicting_fl)
+ const struct file_lock *fl __unused;
+ struct file_lock **conflicting_fl __unused;
+{
+
+ /*
+ * XXX: lock tests on hardware are not required until
+ * true partial file testing is done on the underlying file
+ */
+ return (HW_RESERR);
+}
+
+
+
+/*
+ * Below here are routines for manipulating blocked lock requests
+ * They should only be called from the XXX_partialfilelock routines
+ * if at all possible
+ */
+
+int
+duplicate_block(struct file_lock *fl)
+{
+ struct file_lock *ifl;
+ int retval = 0;
+
+ debuglog("Entering duplicate_block");
+
+ /*
+ * Is this lock request already on the blocking list?
+ * Consider it a dupe if the file handles, offset, length,
+ * exclusivity and client match.
+ */
+ LIST_FOREACH(ifl, &blockedlocklist_head, nfslocklist) {
+ if (!bcmp(&fl->filehandle, &ifl->filehandle,
+ sizeof(fhandle_t)) &&
+ fl->client.exclusive == ifl->client.exclusive &&
+ fl->client.l_offset == ifl->client.l_offset &&
+ fl->client.l_len == ifl->client.l_len &&
+ same_filelock_identity(fl, ifl)) {
+ retval = 1;
+ break;
+ }
+ }
+
+ debuglog("Exiting duplicate_block: %s\n", retval ? "already blocked"
+ : "not already blocked");
+ return retval;
+}
+
+void
+add_blockingfilelock(struct file_lock *fl)
+{
+ debuglog("Entering add_blockingfilelock\n");
+
+ /*
+ * A blocking lock request _should_ never be duplicated as a client
+ * that is already blocked shouldn't be able to request another
+ * lock. Alas, there are some buggy clients that do request the same
+ * lock repeatedly. Make sure only unique locks are on the blocked
+ * lock list.
+ */
+ if (duplicate_block(fl)) {
+ debuglog("Exiting add_blockingfilelock: already blocked\n");
+ return;
+ }
+
+ /*
+ * Clear the blocking flag so that it can be reused without
+ * adding it to the blocking queue a second time
+ */
+
+ fl->blocking = 0;
+ LIST_INSERT_HEAD(&blockedlocklist_head, fl, nfslocklist);
+
+ debuglog("Exiting add_blockingfilelock: added blocked lock\n");
+}
+
+void
+remove_blockingfilelock(struct file_lock *fl)
+{
+
+ debuglog("Entering remove_blockingfilelock\n");
+
+ LIST_REMOVE(fl, nfslocklist);
+
+ debuglog("Exiting remove_blockingfilelock\n");
+}
+
+void
+clear_blockingfilelock(const char *hostname)
+{
+ struct file_lock *ifl,*nfl;
+
+ /*
+ * Normally, LIST_FOREACH is called for, but since
+ * the current element *is* the iterator, deleting it
+ * would mess up the iteration. Thus, a next element
+ * must be used explicitly
+ */
+
+ ifl = LIST_FIRST(&blockedlocklist_head);
+
+ while (ifl != NULL) {
+ nfl = LIST_NEXT(ifl, nfslocklist);
+
+ if (strncmp(hostname, ifl->client_name, SM_MAXSTRLEN) == 0) {
+ remove_blockingfilelock(ifl);
+ deallocate_file_lock(ifl);
+ }
+
+ ifl = nfl;
+ }
+}
+
+void
+retry_blockingfilelocklist(void)
+{
+ /* Retry all locks in the blocked list */
+ struct file_lock *ifl, *nfl; /* Iterator */
+ enum partialfilelock_status pflstatus;
+
+ debuglog("Entering retry_blockingfilelocklist\n");
+
+ LIST_FOREACH_SAFE(ifl, &blockedlocklist_head, nfslocklist, nfl) {
+ debuglog("Iterator choice %p\n",ifl);
+ debuglog("Next iterator choice %p\n",nfl);
+
+ /*
+ * SUBTLE BUG: The file_lock must be removed from the
+ * old list so that it's list pointers get disconnected
+ * before being allowed to participate in the new list
+ * which will automatically add it in if necessary.
+ */
+
+ LIST_REMOVE(ifl, nfslocklist);
+ pflstatus = lock_partialfilelock(ifl);
+
+ if (pflstatus == PFL_GRANTED || pflstatus == PFL_GRANTED_DUPLICATE) {
+ debuglog("Granted blocked lock\n");
+ /* lock granted and is now being used */
+ send_granted(ifl,0);
+ } else {
+ /* Reinsert lock back into blocked list */
+ debuglog("Replacing blocked lock\n");
+ LIST_INSERT_HEAD(&blockedlocklist_head, ifl, nfslocklist);
+ }
+ }
+
+ debuglog("Exiting retry_blockingfilelocklist\n");
+}
+
+/*
+ * Below here are routines associated with manipulating all
+ * aspects of the partial file locking system (list, hardware, etc.)
+ */
+
+/*
+ * Please note that lock monitoring must be done at this level which
+ * keeps track of *individual* lock requests on lock and unlock
+ *
+ * XXX: Split unlocking is going to make the unlock code miserable
+ */
+
+/*
+ * lock_partialfilelock:
+ *
+ * Argument fl gets modified as its list housekeeping entries get modified
+ * upon insertion into the NFS lock list
+ *
+ * This routine makes several assumptions:
+ * 1) It (will) pass locks through to flock to lock the entire underlying file
+ * and then parcel out NFS locks if it gets control of the file.
+ * This matches the old rpc.lockd file semantics (except where it
+ * is now more correct). It is the safe solution, but will cause
+ * overly restrictive blocking if someone is trying to use the
+ * underlying files without using NFS. This appears to be an
+ * acceptable tradeoff since most people use standalone NFS servers.
+ * XXX: The right solution is probably kevent combined with fcntl
+ *
+ * 2) Nothing modifies the lock lists between testing and granting
+ * I have no idea whether this is a useful assumption or not
+ */
+
+enum partialfilelock_status
+lock_partialfilelock(struct file_lock *fl)
+{
+ enum partialfilelock_status retval;
+ enum nfslock_status lnlstatus;
+ enum hwlock_status hwstatus;
+
+ debuglog("Entering lock_partialfilelock\n");
+
+ retval = PFL_DENIED;
+
+ /*
+ * Execute the NFS lock first, if possible, as it is significantly
+ * easier and less expensive to undo than the filesystem lock
+ */
+
+ lnlstatus = lock_nfslock(fl);
+
+ switch (lnlstatus) {
+ case NFS_GRANTED:
+ case NFS_GRANTED_DUPLICATE:
+ /*
+ * At this point, the NFS lock is allocated and active.
+ * Remember to clean it up if the hardware lock fails
+ */
+ hwstatus = lock_hwlock(fl);
+
+ switch (hwstatus) {
+ case HW_GRANTED:
+ case HW_GRANTED_DUPLICATE:
+ debuglog("HW GRANTED\n");
+ /*
+ * XXX: Fixme: Check hwstatus for duplicate when
+ * true partial file locking and accounting is
+ * done on the hardware.
+ */
+ if (lnlstatus == NFS_GRANTED_DUPLICATE) {
+ retval = PFL_GRANTED_DUPLICATE;
+ } else {
+ retval = PFL_GRANTED;
+ }
+ monitor_lock_host(fl->client_name);
+ break;
+ case HW_RESERR:
+ debuglog("HW RESERR\n");
+ retval = PFL_HWRESERR;
+ break;
+ case HW_DENIED:
+ debuglog("HW DENIED\n");
+ retval = PFL_HWDENIED;
+ break;
+ default:
+ debuglog("Unmatched hwstatus %d\n",hwstatus);
+ break;
+ }
+
+ if (retval != PFL_GRANTED &&
+ retval != PFL_GRANTED_DUPLICATE) {
+ /* Clean up the NFS lock */
+ debuglog("Deleting trial NFS lock\n");
+ delete_nfslock(fl);
+ }
+ break;
+ case NFS_DENIED:
+ retval = PFL_NFSDENIED;
+ break;
+ case NFS_RESERR:
+ retval = PFL_NFSRESERR;
+ default:
+ debuglog("Unmatched lnlstatus %d\n");
+ retval = PFL_NFSDENIED_NOLOCK;
+ break;
+ }
+
+ /*
+ * By the time fl reaches here, it is completely free again on
+ * failure. The NFS lock done before attempting the
+ * hardware lock has been backed out
+ */
+
+ if (retval == PFL_NFSDENIED || retval == PFL_HWDENIED) {
+ /* Once last chance to check the lock */
+ if (fl->blocking == 1) {
+ if (retval == PFL_NFSDENIED) {
+ /* Queue the lock */
+ debuglog("BLOCKING LOCK RECEIVED\n");
+ retval = PFL_NFSBLOCKED;
+ add_blockingfilelock(fl);
+ dump_filelock(fl);
+ } else {
+ /* retval is okay as PFL_HWDENIED */
+ debuglog("BLOCKING LOCK DENIED IN HARDWARE\n");
+ dump_filelock(fl);
+ }
+ } else {
+ /* Leave retval alone, it's already correct */
+ debuglog("Lock denied. Non-blocking failure\n");
+ dump_filelock(fl);
+ }
+ }
+
+ debuglog("Exiting lock_partialfilelock\n");
+
+ return retval;
+}
+
+/*
+ * unlock_partialfilelock:
+ *
+ * Given a file_lock, unlock all locks which match.
+ *
+ * Note that a given lock might have to unlock ITSELF! See
+ * clear_partialfilelock for example.
+ */
+
+enum partialfilelock_status
+unlock_partialfilelock(const struct file_lock *fl)
+{
+ struct file_lock *lfl,*rfl,*releasedfl,*selffl;
+ enum partialfilelock_status retval;
+ enum nfslock_status unlstatus;
+ enum hwlock_status unlhwstatus, lhwstatus;
+
+ debuglog("Entering unlock_partialfilelock\n");
+
+ selffl = NULL;
+ lfl = NULL;
+ rfl = NULL;
+ releasedfl = NULL;
+ retval = PFL_DENIED;
+
+ /*
+ * There are significant overlap and atomicity issues
+ * with partially releasing a lock. For example, releasing
+ * part of an NFS shared lock does *not* always release the
+ * corresponding part of the file since there is only one
+ * rpc.lockd UID but multiple users could be requesting it
+ * from NFS. Also, an unlock request should never allow
+ * another process to gain a lock on the remaining parts.
+ * ie. Always apply the new locks before releasing the
+ * old one
+ */
+
+ /*
+ * Loop is required since multiple little locks
+ * can be allocated and then deallocated with one
+ * big unlock.
+ *
+ * The loop is required to be here so that the nfs &
+ * hw subsystems do not need to communicate with one
+ * one another
+ */
+
+ do {
+ debuglog("Value of releasedfl: %p\n",releasedfl);
+ /* lfl&rfl are created *AND* placed into the NFS lock list if required */
+ unlstatus = unlock_nfslock(fl, &releasedfl, &lfl, &rfl);
+ debuglog("Value of releasedfl: %p\n",releasedfl);
+
+
+ /* XXX: This is grungy. It should be refactored to be cleaner */
+ if (lfl != NULL) {
+ lhwstatus = lock_hwlock(lfl);
+ if (lhwstatus != HW_GRANTED &&
+ lhwstatus != HW_GRANTED_DUPLICATE) {
+ debuglog("HW duplicate lock failure for left split\n");
+ }
+ monitor_lock_host(lfl->client_name);
+ }
+
+ if (rfl != NULL) {
+ lhwstatus = lock_hwlock(rfl);
+ if (lhwstatus != HW_GRANTED &&
+ lhwstatus != HW_GRANTED_DUPLICATE) {
+ debuglog("HW duplicate lock failure for right split\n");
+ }
+ monitor_lock_host(rfl->client_name);
+ }
+
+ switch (unlstatus) {
+ case NFS_GRANTED:
+ /* Attempt to unlock on the hardware */
+ debuglog("NFS unlock granted. Attempting hardware unlock\n");
+
+ /* This call *MUST NOT* unlock the two newly allocated locks */
+ unlhwstatus = unlock_hwlock(fl);
+ debuglog("HW unlock returned with code %d\n",unlhwstatus);
+
+ switch (unlhwstatus) {
+ case HW_GRANTED:
+ debuglog("HW unlock granted\n");
+ unmonitor_lock_host(releasedfl->client_name);
+ retval = PFL_GRANTED;
+ break;
+ case HW_DENIED_NOLOCK:
+ /* Huh?!?! This shouldn't happen */
+ debuglog("HW unlock denied no lock\n");
+ retval = PFL_HWRESERR;
+ /* Break out of do-while */
+ unlstatus = NFS_RESERR;
+ break;
+ default:
+ debuglog("HW unlock failed\n");
+ retval = PFL_HWRESERR;
+ /* Break out of do-while */
+ unlstatus = NFS_RESERR;
+ break;
+ }
+
+ debuglog("Exiting with status retval: %d\n",retval);
+
+ retry_blockingfilelocklist();
+ break;
+ case NFS_DENIED_NOLOCK:
+ retval = PFL_GRANTED;
+ debuglog("All locks cleaned out\n");
+ break;
+ default:
+ retval = PFL_NFSRESERR;
+ debuglog("NFS unlock failure\n");
+ dump_filelock(fl);
+ break;
+ }
+
+ if (releasedfl != NULL) {
+ if (fl == releasedfl) {
+ /*
+ * XXX: YECHHH!!! Attempt to unlock self succeeded
+ * but we can't deallocate the space yet. This is what
+ * happens when you don't write malloc and free together
+ */
+ debuglog("Attempt to unlock self\n");
+ selffl = releasedfl;
+ } else {
+ /*
+ * XXX: this deallocation *still* needs to migrate closer
+ * to the allocation code way up in get_lock or the allocation
+ * code needs to migrate down (violation of "When you write
+ * malloc you must write free")
+ */
+
+ deallocate_file_lock(releasedfl);
+ }
+ }
+
+ } while (unlstatus == NFS_GRANTED);
+
+ if (selffl != NULL) {
+ /*
+ * This statement wipes out the incoming file lock (fl)
+ * in spite of the fact that it is declared const
+ */
+ debuglog("WARNING! Destroying incoming lock pointer\n");
+ deallocate_file_lock(selffl);
+ }
+
+ debuglog("Exiting unlock_partialfilelock\n");
+
+ return retval;
+}
+
+/*
+ * clear_partialfilelock
+ *
+ * Normally called in response to statd state number change.
+ * Wipe out all locks held by a host. As a bonus, the act of
+ * doing so should automatically clear their statd entries and
+ * unmonitor the host.
+ */
+
+void
+clear_partialfilelock(const char *hostname)
+{
+ struct file_lock *ifl, *nfl;
+
+ /* Clear blocking file lock list */
+ clear_blockingfilelock(hostname);
+
+ /* do all required unlocks */
+ /* Note that unlock can smash the current pointer to a lock */
+
+ /*
+ * Normally, LIST_FOREACH is called for, but since
+ * the current element *is* the iterator, deleting it
+ * would mess up the iteration. Thus, a next element
+ * must be used explicitly
+ */
+
+ ifl = LIST_FIRST(&nfslocklist_head);
+
+ while (ifl != NULL) {
+ nfl = LIST_NEXT(ifl, nfslocklist);
+
+ if (strncmp(hostname, ifl->client_name, SM_MAXSTRLEN) == 0) {
+ /* Unlock destroys ifl out from underneath */
+ unlock_partialfilelock(ifl);
+ /* ifl is NO LONGER VALID AT THIS POINT */
+ }
+ ifl = nfl;
+ }
+}
+
+/*
+ * test_partialfilelock:
+ */
+enum partialfilelock_status
+test_partialfilelock(const struct file_lock *fl,
+ struct file_lock **conflicting_fl)
+{
+ enum partialfilelock_status retval;
+ enum nfslock_status teststatus;
+
+ debuglog("Entering testpartialfilelock...\n");
+
+ retval = PFL_DENIED;
+
+ teststatus = test_nfslock(fl, conflicting_fl);
+ debuglog("test_partialfilelock: teststatus %d\n",teststatus);
+
+ if (teststatus == NFS_GRANTED || teststatus == NFS_GRANTED_DUPLICATE) {
+ /* XXX: Add the underlying filesystem locking code */
+ retval = (teststatus == NFS_GRANTED) ?
+ PFL_GRANTED : PFL_GRANTED_DUPLICATE;
+ debuglog("Dumping locks...\n");
+ dump_filelock(fl);
+ dump_filelock(*conflicting_fl);
+ debuglog("Done dumping locks...\n");
+ } else {
+ retval = PFL_NFSDENIED;
+ debuglog("NFS test denied.\n");
+ dump_filelock(fl);
+ debuglog("Conflicting.\n");
+ dump_filelock(*conflicting_fl);
+ }
+
+ debuglog("Exiting testpartialfilelock...\n");
+
+ return retval;
+}
+
+/*
+ * Below here are routines associated with translating the partial file locking
+ * codes into useful codes to send back to the NFS RPC messaging system
+ */
+
+/*
+ * These routines translate the (relatively) useful return codes back onto
+ * the few return codes which the nlm subsystems wishes to trasmit
+ */
+
+enum nlm_stats
+do_test(struct file_lock *fl, struct file_lock **conflicting_fl)
+{
+ enum partialfilelock_status pfsret;
+ enum nlm_stats retval;
+
+ debuglog("Entering do_test...\n");
+
+ pfsret = test_partialfilelock(fl,conflicting_fl);
+
+ switch (pfsret) {
+ case PFL_GRANTED:
+ debuglog("PFL test lock granted\n");
+ dump_filelock(fl);
+ dump_filelock(*conflicting_fl);
+ retval = (fl->flags & LOCK_V4) ? nlm4_granted : nlm_granted;
+ break;
+ case PFL_GRANTED_DUPLICATE:
+ debuglog("PFL test lock granted--duplicate id detected\n");
+ dump_filelock(fl);
+ dump_filelock(*conflicting_fl);
+ debuglog("Clearing conflicting_fl for call semantics\n");
+ *conflicting_fl = NULL;
+ retval = (fl->flags & LOCK_V4) ? nlm4_granted : nlm_granted;
+ break;
+ case PFL_NFSDENIED:
+ case PFL_HWDENIED:
+ debuglog("PFL test lock denied\n");
+ dump_filelock(fl);
+ dump_filelock(*conflicting_fl);
+ retval = (fl->flags & LOCK_V4) ? nlm4_denied : nlm_denied;
+ break;
+ case PFL_NFSRESERR:
+ case PFL_HWRESERR:
+ debuglog("PFL test lock resource fail\n");
+ dump_filelock(fl);
+ dump_filelock(*conflicting_fl);
+ retval = (fl->flags & LOCK_V4) ? nlm4_denied_nolocks : nlm_denied_nolocks;
+ break;
+ default:
+ debuglog("PFL test lock *FAILED*\n");
+ dump_filelock(fl);
+ dump_filelock(*conflicting_fl);
+ retval = (fl->flags & LOCK_V4) ? nlm4_failed : nlm_denied;
+ break;
+ }
+
+ debuglog("Exiting do_test...\n");
+
+ return retval;
+}
+
+/*
+ * do_lock: Try to acquire a lock
+ *
+ * This routine makes a distinction between NLM versions. I am pretty
+ * convinced that this should be abstracted out and bounced up a level
+ */
+
+enum nlm_stats
+do_lock(struct file_lock *fl)
+{
+ enum partialfilelock_status pfsret;
+ enum nlm_stats retval;
+
+ debuglog("Entering do_lock...\n");
+
+ pfsret = lock_partialfilelock(fl);
+
+ switch (pfsret) {
+ case PFL_GRANTED:
+ debuglog("PFL lock granted");
+ dump_filelock(fl);
+ retval = (fl->flags & LOCK_V4) ? nlm4_granted : nlm_granted;
+ break;
+ case PFL_GRANTED_DUPLICATE:
+ debuglog("PFL lock granted--duplicate id detected");
+ dump_filelock(fl);
+ retval = (fl->flags & LOCK_V4) ? nlm4_granted : nlm_granted;
+ break;
+ case PFL_NFSDENIED:
+ case PFL_HWDENIED:
+ debuglog("PFL_NFS lock denied");
+ dump_filelock(fl);
+ retval = (fl->flags & LOCK_V4) ? nlm4_denied : nlm_denied;
+ break;
+ case PFL_NFSBLOCKED:
+ case PFL_HWBLOCKED:
+ debuglog("PFL_NFS blocking lock denied. Queued.\n");
+ dump_filelock(fl);
+ retval = (fl->flags & LOCK_V4) ? nlm4_blocked : nlm_blocked;
+ break;
+ case PFL_NFSRESERR:
+ case PFL_HWRESERR:
+ debuglog("PFL lock resource alocation fail\n");
+ dump_filelock(fl);
+ retval = (fl->flags & LOCK_V4) ? nlm4_denied_nolocks : nlm_denied_nolocks;
+ break;
+ default:
+ debuglog("PFL lock *FAILED*");
+ dump_filelock(fl);
+ retval = (fl->flags & LOCK_V4) ? nlm4_failed : nlm_denied;
+ break;
+ }
+
+ debuglog("Exiting do_lock...\n");
+
+ return retval;
+}
+
+enum nlm_stats
+do_unlock(struct file_lock *fl)
+{
+ enum partialfilelock_status pfsret;
+ enum nlm_stats retval;
+
+ debuglog("Entering do_unlock...\n");
+ pfsret = unlock_partialfilelock(fl);
+
+ switch (pfsret) {
+ case PFL_GRANTED:
+ debuglog("PFL unlock granted");
+ dump_filelock(fl);
+ retval = (fl->flags & LOCK_V4) ? nlm4_granted : nlm_granted;
+ break;
+ case PFL_NFSDENIED:
+ case PFL_HWDENIED:
+ debuglog("PFL_NFS unlock denied");
+ dump_filelock(fl);
+ retval = (fl->flags & LOCK_V4) ? nlm4_denied : nlm_denied;
+ break;
+ case PFL_NFSDENIED_NOLOCK:
+ case PFL_HWDENIED_NOLOCK:
+ debuglog("PFL_NFS no lock found\n");
+ retval = (fl->flags & LOCK_V4) ? nlm4_granted : nlm_granted;
+ break;
+ case PFL_NFSRESERR:
+ case PFL_HWRESERR:
+ debuglog("PFL unlock resource failure");
+ dump_filelock(fl);
+ retval = (fl->flags & LOCK_V4) ? nlm4_denied_nolocks : nlm_denied_nolocks;
+ break;
+ default:
+ debuglog("PFL unlock *FAILED*");
+ dump_filelock(fl);
+ retval = (fl->flags & LOCK_V4) ? nlm4_failed : nlm_denied;
+ break;
+ }
+
+ debuglog("Exiting do_unlock...\n");
+
+ return retval;
+}
+
+/*
+ * do_clear
+ *
+ * This routine is non-existent because it doesn't have a return code.
+ * It is here for completeness in case someone *does* need to do return
+ * codes later. A decent compiler should optimize this away.
+ */
+
+void
+do_clear(const char *hostname)
+{
+
+ clear_partialfilelock(hostname);
+}
+
+/*
+ * The following routines are all called from the code which the
+ * RPC layer invokes
+ */
+
+/*
+ * testlock(): inform the caller if the requested lock would be granted
+ *
+ * returns NULL if lock would granted
+ * returns pointer to a conflicting nlm4_holder if not
+ */
+
+struct nlm4_holder *
+testlock(struct nlm4_lock *lock, bool_t exclusive, int flags __unused)
+{
+ struct file_lock test_fl, *conflicting_fl;
+
+ bzero(&test_fl, sizeof(test_fl));
+
+ bcopy(lock->fh.n_bytes, &(test_fl.filehandle), sizeof(fhandle_t));
+ copy_nlm4_lock_to_nlm4_holder(lock, exclusive, &test_fl.client);
+
+ siglock();
+ do_test(&test_fl, &conflicting_fl);
+
+ if (conflicting_fl == NULL) {
+ debuglog("No conflicting lock found\n");
+ sigunlock();
+ return NULL;
+ } else {
+ debuglog("Found conflicting lock\n");
+ dump_filelock(conflicting_fl);
+ sigunlock();
+ return (&conflicting_fl->client);
+ }
+}
+
+/*
+ * getlock: try to acquire the lock.
+ * If file is already locked and we can sleep, put the lock in the list with
+ * status LKST_WAITING; it'll be processed later.
+ * Otherwise try to lock. If we're allowed to block, fork a child which
+ * will do the blocking lock.
+ */
+
+enum nlm_stats
+getlock(nlm4_lockargs *lckarg, struct svc_req *rqstp, const int flags)
+{
+ struct file_lock *newfl;
+ enum nlm_stats retval;
+
+ debuglog("Entering getlock...\n");
+
+ if (grace_expired == 0 && lckarg->reclaim == 0)
+ return (flags & LOCK_V4) ?
+ nlm4_denied_grace_period : nlm_denied_grace_period;
+
+ /* allocate new file_lock for this request */
+ newfl = allocate_file_lock(&lckarg->alock.oh, &lckarg->cookie,
+ (struct sockaddr *)svc_getrpccaller(rqstp->rq_xprt)->buf, lckarg->alock.caller_name);
+ if (newfl == NULL) {
+ syslog(LOG_NOTICE, "lock allocate failed: %s", strerror(errno));
+ /* failed */
+ return (flags & LOCK_V4) ?
+ nlm4_denied_nolocks : nlm_denied_nolocks;
+ }
+
+ if (lckarg->alock.fh.n_len != sizeof(fhandle_t)) {
+ debuglog("received fhandle size %d, local size %d",
+ lckarg->alock.fh.n_len, (int)sizeof(fhandle_t));
+ }
+
+ fill_file_lock(newfl, (fhandle_t *)lckarg->alock.fh.n_bytes,
+ lckarg->exclusive, lckarg->alock.svid, lckarg->alock.l_offset,
+ lckarg->alock.l_len,
+ lckarg->state, 0, flags, lckarg->block);
+
+ /*
+ * newfl is now fully constructed and deallocate_file_lock
+ * can now be used to delete it
+ */
+
+ siglock();
+ debuglog("Pointer to new lock is %p\n",newfl);
+
+ retval = do_lock(newfl);
+
+ debuglog("Pointer to new lock is %p\n",newfl);
+ sigunlock();
+
+ switch (retval)
+ {
+ case nlm4_granted:
+ /* case nlm_granted: is the same as nlm4_granted */
+ /* do_mon(lckarg->alock.caller_name); */
+ break;
+ case nlm4_blocked:
+ /* case nlm_blocked: is the same as nlm4_blocked */
+ /* do_mon(lckarg->alock.caller_name); */
+ break;
+ default:
+ deallocate_file_lock(newfl);
+ break;
+ }
+
+ debuglog("Exiting getlock...\n");
+
+ return retval;
+}
+
+
+/* unlock a filehandle */
+enum nlm_stats
+unlock(nlm4_lock *lock, const int flags __unused)
+{
+ struct file_lock fl;
+ enum nlm_stats err;
+
+ siglock();
+
+ debuglog("Entering unlock...\n");
+
+ bzero(&fl,sizeof(struct file_lock));
+ bcopy(lock->fh.n_bytes, &fl.filehandle, sizeof(fhandle_t));
+
+ copy_nlm4_lock_to_nlm4_holder(lock, 0, &fl.client);
+
+ err = do_unlock(&fl);
+
+ sigunlock();
+
+ debuglog("Exiting unlock...\n");
+
+ return err;
+}
+
+/*
+ * XXX: The following monitor/unmonitor routines
+ * have not been extensively tested (ie. no regression
+ * script exists like for the locking sections
+ */
+
+/*
+ * monitor_lock_host: monitor lock hosts locally with a ref count and
+ * inform statd
+ */
+void
+monitor_lock_host(const char *hostname)
+{
+ struct host *ihp, *nhp;
+ struct mon smon;
+ struct sm_stat_res sres;
+ int rpcret, statflag;
+ size_t n;
+
+ rpcret = 0;
+ statflag = 0;
+
+ LIST_FOREACH(ihp, &hostlst_head, hostlst) {
+ if (strncmp(hostname, ihp->name, SM_MAXSTRLEN) == 0) {
+ /* Host is already monitored, bump refcount */
+ ++ihp->refcnt;
+ /* Host should only be in the monitor list once */
+ return;
+ }
+ }
+
+ /* Host is not yet monitored, add it */
+ n = strnlen(hostname, SM_MAXSTRLEN);
+ if (n == SM_MAXSTRLEN) {
+ return;
+ }
+ nhp = malloc(sizeof(*nhp) - sizeof(nhp->name) + n + 1);
+ if (nhp == NULL) {
+ debuglog("Unable to allocate entry for statd mon\n");
+ return;
+ }
+
+ /* Allocated new host entry, now fill the fields */
+ memcpy(nhp->name, hostname, n);
+ nhp->name[n] = 0;
+ nhp->refcnt = 1;
+ debuglog("Locally Monitoring host %16s\n",hostname);
+
+ debuglog("Attempting to tell statd\n");
+
+ bzero(&smon,sizeof(smon));
+
+ smon.mon_id.mon_name = nhp->name;
+ smon.mon_id.my_id.my_name = "localhost";
+ smon.mon_id.my_id.my_prog = NLM_PROG;
+ smon.mon_id.my_id.my_vers = NLM_SM;
+ smon.mon_id.my_id.my_proc = NLM_SM_NOTIFY;
+
+ rpcret = callrpc("localhost", SM_PROG, SM_VERS, SM_MON,
+ (xdrproc_t)xdr_mon, &smon,
+ (xdrproc_t)xdr_sm_stat_res, &sres);
+
+ if (rpcret == 0) {
+ if (sres.res_stat == stat_fail) {
+ debuglog("Statd call failed\n");
+ statflag = 0;
+ } else {
+ statflag = 1;
+ }
+ } else {
+ debuglog("Rpc call to statd failed with return value: %d\n",
+ rpcret);
+ statflag = 0;
+ }
+
+ if (statflag == 1) {
+ LIST_INSERT_HEAD(&hostlst_head, nhp, hostlst);
+ } else {
+ free(nhp);
+ }
+
+}
+
+/*
+ * unmonitor_lock_host: clear monitor ref counts and inform statd when gone
+ */
+void
+unmonitor_lock_host(char *hostname)
+{
+ struct host *ihp;
+ struct mon_id smon_id;
+ struct sm_stat smstat;
+ int rpcret;
+
+ rpcret = 0;
+
+ for( ihp=LIST_FIRST(&hostlst_head); ihp != NULL;
+ ihp=LIST_NEXT(ihp, hostlst)) {
+ if (strncmp(hostname, ihp->name, SM_MAXSTRLEN) == 0) {
+ /* Host is monitored, bump refcount */
+ --ihp->refcnt;
+ /* Host should only be in the monitor list once */
+ break;
+ }
+ }
+
+ if (ihp == NULL) {
+ debuglog("Could not find host %16s in mon list\n", hostname);
+ return;
+ }
+
+ if (ihp->refcnt > 0)
+ return;
+
+ if (ihp->refcnt < 0) {
+ debuglog("Negative refcount!: %d\n",
+ ihp->refcnt);
+ }
+
+ debuglog("Attempting to unmonitor host %16s\n", hostname);
+
+ bzero(&smon_id,sizeof(smon_id));
+
+ smon_id.mon_name = hostname;
+ smon_id.my_id.my_name = "localhost";
+ smon_id.my_id.my_prog = NLM_PROG;
+ smon_id.my_id.my_vers = NLM_SM;
+ smon_id.my_id.my_proc = NLM_SM_NOTIFY;
+
+ rpcret = callrpc("localhost", SM_PROG, SM_VERS, SM_UNMON,
+ (xdrproc_t)xdr_mon_id, &smon_id,
+ (xdrproc_t)xdr_sm_stat, &smstat);
+
+ if (rpcret != 0) {
+ debuglog("Rpc call to unmonitor statd failed with "
+ " return value: %d\n", rpcret);
+ }
+
+ LIST_REMOVE(ihp, hostlst);
+ free(ihp);
+}
+
+/*
+ * notify: Clear all locks from a host if statd complains
+ *
+ * XXX: This routine has not been thoroughly tested. However, neither
+ * had the old one been. It used to compare the statd crash state counter
+ * to the current lock state. The upshot of this was that it basically
+ * cleared all locks from the specified host 99% of the time (with the
+ * other 1% being a bug). Consequently, the assumption is that clearing
+ * all locks from a host when notified by statd is acceptable.
+ *
+ * Please note that this routine skips the usual level of redirection
+ * through a do_* type routine. This introduces a possible level of
+ * error and might better be written as do_notify and take this one out.
+
+ */
+
+void
+notify(const char *hostname, const int state)
+{
+ debuglog("notify from %s, new state %d", hostname, state);
+
+ siglock();
+ do_clear(hostname);
+ sigunlock();
+
+ debuglog("Leaving notify\n");
+}
+
+void
+send_granted(fl, opcode)
+ struct file_lock *fl;
+ int opcode __unused;
+{
+ CLIENT *cli;
+ static char dummy;
+ struct timeval timeo;
+ int success;
+ static struct nlm_res retval;
+ static struct nlm4_res retval4;
+
+ debuglog("About to send granted on blocked lock\n");
+
+ cli = get_client(fl->addr,
+ (fl->flags & LOCK_V4) ? NLM_VERS4 : NLM_VERS);
+ if (cli == NULL) {
+ syslog(LOG_NOTICE, "failed to get CLIENT for %s",
+ fl->client_name);
+ /*
+ * We fail to notify remote that the lock has been granted.
+ * The client will timeout and retry, the lock will be
+ * granted at this time.
+ */
+ return;
+ }
+ timeo.tv_sec = 0;
+ timeo.tv_usec = (fl->flags & LOCK_ASYNC) ? 0 : 500000; /* 0.5s */
+
+ if (fl->flags & LOCK_V4) {
+ static nlm4_testargs res;
+ res.cookie = fl->client_cookie;
+ res.exclusive = fl->client.exclusive;
+ res.alock.caller_name = fl->client_name;
+ res.alock.fh.n_len = sizeof(fhandle_t);
+ res.alock.fh.n_bytes = (char*)&fl->filehandle;
+ res.alock.oh = fl->client.oh;
+ res.alock.svid = fl->client.svid;
+ res.alock.l_offset = fl->client.l_offset;
+ res.alock.l_len = fl->client.l_len;
+ debuglog("sending v4 reply%s",
+ (fl->flags & LOCK_ASYNC) ? " (async)":"");
+ if (fl->flags & LOCK_ASYNC) {
+ success = clnt_call(cli, NLM4_GRANTED_MSG,
+ (xdrproc_t)xdr_nlm4_testargs, &res,
+ (xdrproc_t)xdr_void, &dummy, timeo);
+ } else {
+ success = clnt_call(cli, NLM4_GRANTED,
+ (xdrproc_t)xdr_nlm4_testargs, &res,
+ (xdrproc_t)xdr_nlm4_res, &retval4, timeo);
+ }
+ } else {
+ static nlm_testargs res;
+
+ res.cookie = fl->client_cookie;
+ res.exclusive = fl->client.exclusive;
+ res.alock.caller_name = fl->client_name;
+ res.alock.fh.n_len = sizeof(fhandle_t);
+ res.alock.fh.n_bytes = (char*)&fl->filehandle;
+ res.alock.oh = fl->client.oh;
+ res.alock.svid = fl->client.svid;
+ res.alock.l_offset = fl->client.l_offset;
+ res.alock.l_len = fl->client.l_len;
+ debuglog("sending v1 reply%s",
+ (fl->flags & LOCK_ASYNC) ? " (async)":"");
+ if (fl->flags & LOCK_ASYNC) {
+ success = clnt_call(cli, NLM_GRANTED_MSG,
+ (xdrproc_t)xdr_nlm_testargs, &res,
+ (xdrproc_t)xdr_void, &dummy, timeo);
+ } else {
+ success = clnt_call(cli, NLM_GRANTED,
+ (xdrproc_t)xdr_nlm_testargs, &res,
+ (xdrproc_t)xdr_nlm_res, &retval, timeo);
+ }
+ }
+ if (debug_level > 2)
+ debuglog("clnt_call returns %d(%s) for granted",
+ success, clnt_sperrno(success));
+
+}
+
+/*
+ * Routines below here have not been modified in the overhaul
+ */
+
+/*
+ * Are these two routines still required since lockd is not spawning off
+ * children to service locks anymore? Presumably they were originally
+ * put in place to prevent a one child from changing the lock list out
+ * from under another one.
+ */
+
+void
+siglock(void)
+{
+ sigset_t block;
+
+ sigemptyset(&block);
+ sigaddset(&block, SIGCHLD);
+
+ if (sigprocmask(SIG_BLOCK, &block, NULL) < 0) {
+ syslog(LOG_WARNING, "siglock failed: %s", strerror(errno));
+ }
+}
+
+void
+sigunlock(void)
+{
+ sigset_t block;
+
+ sigemptyset(&block);
+ sigaddset(&block, SIGCHLD);
+
+ if (sigprocmask(SIG_UNBLOCK, &block, NULL) < 0) {
+ syslog(LOG_WARNING, "sigunlock failed: %s", strerror(errno));
+ }
+}
diff --git a/usr.sbin/rpc.lockd/lockd_lock.h b/usr.sbin/rpc.lockd/lockd_lock.h
new file mode 100644
index 0000000..62f6981
--- /dev/null
+++ b/usr.sbin/rpc.lockd/lockd_lock.h
@@ -0,0 +1,25 @@
+/* $NetBSD: lockd_lock.h,v 1.2 2000/06/09 14:00:54 fvdl Exp $ */
+/* $FreeBSD$ */
+
+/* Headers and function declarations for file-locking utilities */
+
+struct nlm4_holder * testlock(struct nlm4_lock *lock, bool_t exclusive,
+ int flags);
+enum nlm_stats getlock(nlm4_lockargs *lckarg, struct svc_req *rqstp,
+ const int flags);
+enum nlm_stats unlock(nlm4_lock *lock, const int flags);
+int lock_answer(int pid, netobj *netcookie, int result, int *pid_p,
+ int version);
+
+void notify(const char *hostname, const int state);
+
+/* flags for testlock, getlock & unlock */
+#define LOCK_ASYNC 0x01 /* async version (getlock only) */
+#define LOCK_V4 0x02 /* v4 version */
+#define LOCK_MON 0x04 /* monitored lock (getlock only) */
+#define LOCK_CANCEL 0x08 /* cancel, not unlock request (unlock only) */
+
+/* callbacks from lock_proc.c */
+void transmit_result(int, nlm_res *, struct sockaddr *);
+void transmit4_result(int, nlm4_res *, struct sockaddr *);
+CLIENT *get_client(struct sockaddr *, rpcvers_t);
diff --git a/usr.sbin/rpc.lockd/rpc.lockd.8 b/usr.sbin/rpc.lockd/rpc.lockd.8
new file mode 100644
index 0000000..763a163
--- /dev/null
+++ b/usr.sbin/rpc.lockd/rpc.lockd.8
@@ -0,0 +1,146 @@
+.\" $NetBSD: rpc.lockd.8,v 1.5 2000/06/09 18:51:47 cgd Exp $
+.\"
+.\" Copyright (c) 1995 A.R.Gordon, andrew.gordon@net-tel.co.uk
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 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 November 2, 2007
+.Dt RPC.LOCKD 8
+.Os
+.Sh NAME
+.Nm rpc.lockd
+.Nd NFS file locking daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl d Ar debug_level
+.Op Fl g Ar grace period
+.Op Fl h Ar bindip
+.Op Fl p Ar port
+.Sh DESCRIPTION
+The
+.Nm
+utility provides monitored and unmonitored file and record locking services
+in an NFS environment.
+To monitor the status of hosts requesting locks,
+the locking daemon typically operates in conjunction
+with
+.Xr rpc.statd 8 .
+.Pp
+Options and operands available for
+.Nm :
+.Bl -tag -width indent
+.It Fl d
+The
+.Fl d
+option causes debugging information to be written to syslog, recording
+all RPC transactions to the daemon.
+These messages are logged with level
+.Dv LOG_DEBUG
+and facility
+.Dv LOG_DAEMON .
+Specifying a
+.Ar debug_level
+of 1 results
+in the generation of one log line per protocol operation.
+Higher
+debug levels can be specified, causing display of operation arguments
+and internal operations of the daemon.
+.It Fl g
+The
+.Fl g
+option allow to specify the
+.Ar grace period ,
+in seconds.
+During the grace period
+.Nm
+only accepts requests from hosts which are reinitialising locks which
+existed before the server restart.
+Default is 30 seconds.
+.It Fl h Ar bindip
+Specify specific IP addresses to bind to.
+This option may be specified multiple times.
+If no
+.Fl h
+option is specified,
+.Nm
+will bind to
+.Dv INADDR_ANY .
+Note that when specifying IP addresses with
+.Fl h ,
+.Nm
+will automatically add
+.Li 127.0.0.1
+and if IPv6 is enabled,
+.Li ::1
+to the list.
+.It Fl p
+The
+.Fl p
+option allow to force the daemon to bind to the specified
+.Ar port ,
+for both AF_INET and AF_INET6 address families.
+.El
+.Pp
+Error conditions are logged to syslog, irrespective of the debug level,
+using log level
+.Dv LOG_ERR
+and facility
+.Dv LOG_DAEMON .
+.Pp
+The
+.Nm
+utility must NOT be invoked by
+.Xr inetd 8
+because the protocol assumes that the daemon will run from system start time.
+Instead, it should be configured in
+.Xr rc.conf 5
+to run at system startup.
+.Sh FILES
+.Bl -tag -width /usr/include/rpcsvc/nlm_prot.x -compact
+.It Pa /usr/include/rpcsvc/nlm_prot.x
+RPC protocol specification for the network lock manager protocol.
+.El
+.Sh SEE ALSO
+.Xr syslog 3 ,
+.Xr rc.conf 5 ,
+.Xr rpc.statd 8
+.Sh STANDARDS
+The implementation is based on the specification in
+.Rs
+.%B "X/Open CAE Specification C218"
+.%T "Protocols for X/Open PC Interworking: XNFS, Issue 4"
+.%O ISBN 1 872630 66 9
+.Re
+.Sh HISTORY
+A version of
+.Nm
+appeared in
+.Tn SunOS
+4.
+.Sh BUGS
+The current implementation serialises locks requests that could be shared.
diff --git a/usr.sbin/rpc.lockd/test.c b/usr.sbin/rpc.lockd/test.c
new file mode 100644
index 0000000..a751e5c
--- /dev/null
+++ b/usr.sbin/rpc.lockd/test.c
@@ -0,0 +1,365 @@
+/* $NetBSD: test.c,v 1.2 1997/10/18 04:01:21 lukem Exp $ */
+
+#include <sys/cdefs.h>
+#include <rpc/rpc.h>
+#include <rpcsvc/nlm_prot.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "from: @(#)nlm_prot.x 1.8 87/09/21 Copyr 1987 Sun Micro";
+static char sccsid[] = "from: * @(#)nlm_prot.x 2.1 88/08/01 4.0 RPCSRC";
+#else
+__RCSID("$NetBSD: test.c,v 1.2 1997/10/18 04:01:21 lukem Exp $");
+static const char rcsid[] = "$FreeBSD$";
+#endif
+#endif /* not lint */
+
+/* Default timeout can be changed using clnt_control() */
+static struct timeval TIMEOUT = { 0, 0 };
+
+nlm_testres *
+nlm_test_1(argp, clnt)
+ struct nlm_testargs *argp;
+ CLIENT *clnt;
+{
+ static nlm_testres res;
+
+ bzero((char *)&res, sizeof(res));
+ if (clnt_call(clnt, NLM_TEST, xdr_nlm_testargs, argp, xdr_nlm_testres, &res, TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return (&res);
+}
+
+
+nlm_res *
+nlm_lock_1(argp, clnt)
+ struct nlm_lockargs *argp;
+ CLIENT *clnt;
+{
+ enum clnt_stat st;
+ static nlm_res res;
+
+ bzero((char *)&res, sizeof(res));
+ if (st = clnt_call(clnt, NLM_LOCK, xdr_nlm_lockargs, argp, xdr_nlm_res, &res, TIMEOUT) != RPC_SUCCESS) {
+ printf("clnt_call returns %d\n", st);
+ clnt_perror(clnt, "humbug");
+ return (NULL);
+ }
+ return (&res);
+}
+
+
+nlm_res *
+nlm_cancel_1(argp, clnt)
+ struct nlm_cancargs *argp;
+ CLIENT *clnt;
+{
+ static nlm_res res;
+
+ bzero((char *)&res, sizeof(res));
+ if (clnt_call(clnt, NLM_CANCEL, xdr_nlm_cancargs, argp, xdr_nlm_res, &res, TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return (&res);
+}
+
+
+nlm_res *
+nlm_unlock_1(argp, clnt)
+ struct nlm_unlockargs *argp;
+ CLIENT *clnt;
+{
+ static nlm_res res;
+
+ bzero((char *)&res, sizeof(res));
+ if (clnt_call(clnt, NLM_UNLOCK, xdr_nlm_unlockargs, argp, xdr_nlm_res, &res, TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return (&res);
+}
+
+
+nlm_res *
+nlm_granted_1(argp, clnt)
+ struct nlm_testargs *argp;
+ CLIENT *clnt;
+{
+ static nlm_res res;
+
+ bzero((char *)&res, sizeof(res));
+ if (clnt_call(clnt, NLM_GRANTED, xdr_nlm_testargs, argp, xdr_nlm_res, &res, TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return (&res);
+}
+
+
+void *
+nlm_test_msg_1(argp, clnt)
+ struct nlm_testargs *argp;
+ CLIENT *clnt;
+{
+ static char res;
+
+ bzero((char *)&res, sizeof(res));
+ if (clnt_call(clnt, NLM_TEST_MSG, xdr_nlm_testargs, argp, xdr_void, &res, TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return ((void *)&res);
+}
+
+
+void *
+nlm_lock_msg_1(argp, clnt)
+ struct nlm_lockargs *argp;
+ CLIENT *clnt;
+{
+ static char res;
+
+ bzero((char *)&res, sizeof(res));
+ if (clnt_call(clnt, NLM_LOCK_MSG, xdr_nlm_lockargs, argp, xdr_void, NULL, TIMEOUT) != RPC_SUCCESS) {
+ clnt_perror(clnt, "nlm_lock_msg_1");
+ return (NULL);
+ }
+ return ((void *)&res);
+}
+
+
+void *
+nlm_cancel_msg_1(argp, clnt)
+ struct nlm_cancargs *argp;
+ CLIENT *clnt;
+{
+ static char res;
+
+ bzero((char *)&res, sizeof(res));
+ if (clnt_call(clnt, NLM_CANCEL_MSG, xdr_nlm_cancargs, argp, xdr_void, &res, TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return ((void *)&res);
+}
+
+
+void *
+nlm_unlock_msg_1(argp, clnt)
+ struct nlm_unlockargs *argp;
+ CLIENT *clnt;
+{
+ static char res;
+
+ bzero((char *)&res, sizeof(res));
+ if (clnt_call(clnt, NLM_UNLOCK_MSG, xdr_nlm_unlockargs, argp, xdr_void, &res, TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return ((void *)&res);
+}
+
+
+void *
+nlm_granted_msg_1(argp, clnt)
+ struct nlm_testargs *argp;
+ CLIENT *clnt;
+{
+ static char res;
+
+ bzero((char *)&res, sizeof(res));
+ if (clnt_call(clnt, NLM_GRANTED_MSG, xdr_nlm_testargs, argp, xdr_void, &res, TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return ((void *)&res);
+}
+
+
+void *
+nlm_test_res_1(argp, clnt)
+ nlm_testres *argp;
+ CLIENT *clnt;
+{
+ static char res;
+
+ bzero((char *)&res, sizeof(res));
+ if (clnt_call(clnt, NLM_TEST_RES, xdr_nlm_testres, argp, xdr_void, &res, TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return ((void *)&res);
+}
+
+
+void *
+nlm_lock_res_1(argp, clnt)
+ nlm_res *argp;
+ CLIENT *clnt;
+{
+ static char res;
+
+ bzero((char *)&res, sizeof(res));
+ if (clnt_call(clnt, NLM_LOCK_RES, xdr_nlm_res, argp, xdr_void, &res, TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return ((void *)&res);
+}
+
+
+void *
+nlm_cancel_res_1(argp, clnt)
+ nlm_res *argp;
+ CLIENT *clnt;
+{
+ static char res;
+
+ bzero((char *)&res, sizeof(res));
+ if (clnt_call(clnt, NLM_CANCEL_RES, xdr_nlm_res, argp, xdr_void, &res, TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return ((void *)&res);
+}
+
+
+void *
+nlm_unlock_res_1(argp, clnt)
+ nlm_res *argp;
+ CLIENT *clnt;
+{
+ static char res;
+
+ bzero((char *)&res, sizeof(res));
+ if (clnt_call(clnt, NLM_UNLOCK_RES, xdr_nlm_res, argp, xdr_void, &res, TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return ((void *)&res);
+}
+
+
+void *
+nlm_granted_res_1(argp, clnt)
+ nlm_res *argp;
+ CLIENT *clnt;
+{
+ static char res;
+
+ bzero((char *)&res, sizeof(res));
+ if (clnt_call(clnt, NLM_GRANTED_RES, xdr_nlm_res, argp, xdr_void, &res, TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return ((void *)&res);
+}
+
+
+nlm_shareres *
+nlm_share_3(argp, clnt)
+ nlm_shareargs *argp;
+ CLIENT *clnt;
+{
+ static nlm_shareres res;
+
+ bzero((char *)&res, sizeof(res));
+ if (clnt_call(clnt, NLM_SHARE, xdr_nlm_shareargs, argp, xdr_nlm_shareres, &res, TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return (&res);
+}
+
+
+nlm_shareres *
+nlm_unshare_3(argp, clnt)
+ nlm_shareargs *argp;
+ CLIENT *clnt;
+{
+ static nlm_shareres res;
+
+ bzero((char *)&res, sizeof(res));
+ if (clnt_call(clnt, NLM_UNSHARE, xdr_nlm_shareargs, argp, xdr_nlm_shareres, &res, TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return (&res);
+}
+
+
+nlm_res *
+nlm_nm_lock_3(argp, clnt)
+ nlm_lockargs *argp;
+ CLIENT *clnt;
+{
+ static nlm_res res;
+
+ bzero((char *)&res, sizeof(res));
+ if (clnt_call(clnt, NLM_NM_LOCK, xdr_nlm_lockargs, argp, xdr_nlm_res, &res, TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return (&res);
+}
+
+
+void *
+nlm_free_all_3(argp, clnt)
+ nlm_notify *argp;
+ CLIENT *clnt;
+{
+ static char res;
+
+ bzero((char *)&res, sizeof(res));
+ if (clnt_call(clnt, NLM_FREE_ALL, xdr_nlm_notify, argp, xdr_void, &res, TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return ((void *)&res);
+}
+
+
+int main(int argc, char **argv)
+{
+ CLIENT *cli;
+ nlm_res res_block;
+ nlm_res *out;
+ nlm_lockargs arg;
+ struct timeval tim;
+
+ printf("Creating client for host %s\n", argv[1]);
+ cli = clnt_create(argv[1], NLM_PROG, NLM_VERS, "udp");
+ if (!cli) {
+ errx(1, "Failed to create client\n");
+ /* NOTREACHED */
+ }
+ clnt_control(cli, CLGET_TIMEOUT, &tim);
+ printf("Default timeout was %d.%d\n", tim.tv_sec, tim.tv_usec);
+ tim.tv_usec = -1;
+ tim.tv_sec = -1;
+ clnt_control(cli, CLSET_TIMEOUT, &tim);
+ clnt_control(cli, CLGET_TIMEOUT, &tim);
+ printf("timeout now %d.%d\n", tim.tv_sec, tim.tv_usec);
+
+
+ arg.cookie.n_len = 4;
+ arg.cookie.n_bytes = "hello";
+ arg.block = 0;
+ arg.exclusive = 0;
+ arg.reclaim = 0;
+ arg.state = 0x1234;
+ arg.alock.caller_name = "localhost";
+ arg.alock.fh.n_len = 32;
+ arg.alock.fh.n_bytes = "\x04\x04\x02\x00\x01\x00\x00\x00\x0c\x00\x00\x00\xff\xff\xff\xd0\x16\x00\x00\x5b\x7c\xff\xff\xff\xec\x2f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x19\x54\xef\xbf\xd7\x94";
+ arg.alock.oh.n_len = 8;
+ arg.alock.oh.n_bytes = "\x00\x00\x02\xff\xff\xff\xd3";
+ arg.alock.svid = 0x5678;
+ arg.alock.l_offset = 0;
+ arg.alock.l_len = 100;
+
+ res_block.stat.stat = nlm_granted;
+ res_block.cookie.n_bytes = "hello";
+ res_block.cookie.n_len = 5;
+
+#if 0
+ if (nlm_lock_res_1(&res_block, cli))
+ printf("Success!\n");
+ else
+ printf("Fail\n");
+#else
+ if (out = nlm_lock_msg_1(&arg, cli)) {
+ printf("Success!\n");
+ printf("out->stat = %d", out->stat);
+ } else {
+ printf("Fail\n");
+ }
+#endif
+
+ return 0;
+}
diff --git a/usr.sbin/rpc.statd/Makefile b/usr.sbin/rpc.statd/Makefile
new file mode 100644
index 0000000..43504e4
--- /dev/null
+++ b/usr.sbin/rpc.statd/Makefile
@@ -0,0 +1,26 @@
+# $FreeBSD$
+
+PROG= rpc.statd
+MAN= rpc.statd.8
+SRCS= file.c sm_inter_svc.c sm_inter.h statd.c procs.c
+
+CFLAGS+= -I.
+WARNS?= 2
+
+LIBADD= rpcsvc
+
+CLEANFILES= sm_inter_svc.c sm_inter.h
+
+RPCSRC= ${DESTDIR}/usr/include/rpcsvc/sm_inter.x
+RPCGEN= RPCGEN_CPP=${CPP:Q} rpcgen -L -C
+
+sm_inter_svc.c: ${RPCSRC}
+ ${RPCGEN} -m -o ${.TARGET} ${RPCSRC}
+
+sm_inter.h: ${RPCSRC}
+ ${RPCGEN} -h -o ${.TARGET} ${RPCSRC}
+
+test: test.c
+ cc -o test test.c -lrpcsvc
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/rpc.statd/Makefile.depend b/usr.sbin/rpc.statd/Makefile.depend
new file mode 100644
index 0000000..a356b97
--- /dev/null
+++ b/usr.sbin/rpc.statd/Makefile.depend
@@ -0,0 +1,32 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/librpcsvc \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+file.o: sm_inter.h
+file.po: sm_inter.h
+procs.o: sm_inter.h
+procs.po: sm_inter.h
+sm_inter_svc.o: sm_inter.h
+sm_inter_svc.o: sm_inter_svc.c
+sm_inter_svc.po: sm_inter.h
+sm_inter_svc.po: sm_inter_svc.c
+statd.o: sm_inter.h
+statd.po: sm_inter.h
+.endif
diff --git a/usr.sbin/rpc.statd/file.c b/usr.sbin/rpc.statd/file.c
new file mode 100644
index 0000000..0625e30
--- /dev/null
+++ b/usr.sbin/rpc.statd/file.c
@@ -0,0 +1,361 @@
+/*
+ * Copyright (c) 1995
+ * A.R. Gordon (andrew.gordon@net-tel.co.uk). All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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
+ * 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 ANDREW GORDON AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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 <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/mman.h> /* For mmap() */
+#include <rpc/rpc.h>
+#include <syslog.h>
+#include <stdlib.h>
+
+#include "statd.h"
+
+FileLayout *status_info; /* Pointer to the mmap()ed status file */
+static int status_fd; /* File descriptor for the open file */
+static off_t status_file_len; /* Current on-disc length of file */
+
+/* sync_file --------------------------------------------------------------- */
+/*
+ Purpose: Packaged call of msync() to flush changes to mmap()ed file
+ Returns: Nothing. Errors to syslog.
+*/
+
+void sync_file(void)
+{
+ if (msync((void *)status_info, 0, 0) < 0)
+ {
+ syslog(LOG_ERR, "msync() failed: %s", strerror(errno));
+ }
+}
+
+/* find_host -------------------------------------------------------------- */
+/*
+ Purpose: Find the entry in the status file for a given host
+ Returns: Pointer to that entry in the mmap() region, or NULL.
+ Notes: Also creates entries if requested.
+ Failure to create also returns NULL.
+*/
+
+HostInfo *find_host(char *hostname, int create)
+{
+ HostInfo *hp;
+ HostInfo *spare_slot = NULL;
+ HostInfo *result = NULL;
+ struct addrinfo *ai1, *ai2;
+ int i;
+
+ if (getaddrinfo(hostname, NULL, NULL, &ai1) != 0)
+ ai1 = NULL;
+ for (i = 0, hp = status_info->hosts; i < status_info->noOfHosts; i++, hp++)
+ {
+ if (!strncasecmp(hostname, hp->hostname, SM_MAXSTRLEN))
+ {
+ result = hp;
+ break;
+ }
+ if (hp->hostname[0] &&
+ getaddrinfo(hp->hostname, NULL, NULL, &ai2) != 0)
+ ai2 = NULL;
+ if (ai1 && ai2)
+ {
+ struct addrinfo *p1, *p2;
+ for (p1 = ai1; !result && p1; p1 = p1->ai_next)
+ {
+ for (p2 = ai2; !result && p2; p2 = p2->ai_next)
+ {
+ if (p1->ai_family == p2->ai_family
+ && p1->ai_addrlen == p2->ai_addrlen
+ && !memcmp(p1->ai_addr, p2->ai_addr, p1->ai_addrlen))
+ {
+ result = hp;
+ break;
+ }
+ }
+ }
+ if (result)
+ break;
+ }
+ if (ai2)
+ freeaddrinfo(ai2);
+ if (!spare_slot && !hp->monList && !hp->notifyReqd)
+ spare_slot = hp;
+ }
+ if (ai1)
+ freeaddrinfo(ai1);
+
+ /* Return if entry found, or if not asked to create one. */
+ if (result || !create) return (result);
+
+ /* Now create an entry, using the spare slot if one was found or */
+ /* adding to the end of the list otherwise, extending file if reqd */
+ if (!spare_slot)
+ {
+ off_t desired_size;
+ spare_slot = &status_info->hosts[status_info->noOfHosts];
+ desired_size = ((char*)spare_slot - (char*)status_info) + sizeof(HostInfo);
+ if (desired_size > status_file_len)
+ {
+ /* Extend file by writing 1 byte of junk at the desired end pos */
+ lseek(status_fd, desired_size - 1, SEEK_SET);
+ i = write(status_fd, &i, 1);
+ if (i < 1)
+ {
+ syslog(LOG_ERR, "Unable to extend status file");
+ return (NULL);
+ }
+ status_file_len = desired_size;
+ }
+ status_info->noOfHosts++;
+ }
+
+ /* Initialise the spare slot that has been found/created */
+ /* Note that we do not msync(), since the caller is presumed to be */
+ /* about to modify the entry further */
+ memset(spare_slot, 0, sizeof(HostInfo));
+ strncpy(spare_slot->hostname, hostname, SM_MAXSTRLEN);
+ return (spare_slot);
+}
+
+/* init_file -------------------------------------------------------------- */
+/*
+ Purpose: Open file, create if necessary, initialise it.
+ Returns: Nothing - exits on error
+ Notes: Called before process becomes daemon, hence logs to
+ stderr rather than syslog.
+ Opens the file, then mmap()s it for ease of access.
+ Also performs initial clean-up of the file, zeroing
+ monitor list pointers, setting the notifyReqd flag in
+ all hosts that had a monitor list, and incrementing
+ the state number to the next even value.
+*/
+
+void init_file(const char *filename)
+{
+ int new_file = FALSE;
+ char buf[HEADER_LEN];
+ int i;
+
+ /* try to open existing file - if not present, create one */
+ status_fd = open(filename, O_RDWR);
+ if ((status_fd < 0) && (errno == ENOENT))
+ {
+ status_fd = open(filename, O_RDWR | O_CREAT, 0644);
+ new_file = TRUE;
+ }
+ if (status_fd < 0)
+ errx(1, "unable to open status file %s", filename);
+
+ /* File now open. mmap() it, with a generous size to allow for */
+ /* later growth, where we will extend the file but not re-map it. */
+ status_info = (FileLayout *)
+ mmap(NULL, 0x10000000, PROT_READ | PROT_WRITE, MAP_SHARED, status_fd, 0);
+
+ if (status_info == (FileLayout *) MAP_FAILED)
+ err(1, "unable to mmap() status file");
+
+ status_file_len = lseek(status_fd, 0L, SEEK_END);
+
+ /* If the file was not newly created, validate the contents, and if */
+ /* defective, re-create from scratch. */
+ if (!new_file)
+ {
+ if ((status_file_len < HEADER_LEN) || (status_file_len
+ < (HEADER_LEN + sizeof(HostInfo) * status_info->noOfHosts)) )
+ {
+ warnx("status file is corrupt");
+ new_file = TRUE;
+ }
+ }
+
+ /* Initialisation of a new, empty file. */
+ if (new_file)
+ {
+ memset(buf, 0, sizeof(buf));
+ lseek(status_fd, 0L, SEEK_SET);
+ write(status_fd, buf, HEADER_LEN);
+ status_file_len = HEADER_LEN;
+ }
+ else
+ {
+ /* Clean-up of existing file - monitored hosts will have a pointer */
+ /* to a list of clients, which refers to memory in the previous */
+ /* incarnation of the program and so are meaningless now. These */
+ /* pointers are zeroed and the fact that the host was previously */
+ /* monitored is recorded by setting the notifyReqd flag, which will */
+ /* in due course cause a SM_NOTIFY to be sent. */
+ /* Note that if we crash twice in quick succession, some hosts may */
+ /* already have notifyReqd set, where we didn't manage to notify */
+ /* them before the second crash occurred. */
+ for (i = 0; i < status_info->noOfHosts; i++)
+ {
+ HostInfo *this_host = &status_info->hosts[i];
+
+ if (this_host->monList)
+ {
+ this_host->notifyReqd = TRUE;
+ this_host->monList = NULL;
+ }
+ }
+ /* Select the next higher even number for the state counter */
+ status_info->ourState = (status_info->ourState + 2) & 0xfffffffe;
+/*???????******/ status_info->ourState++;
+ }
+}
+
+/* notify_one_host --------------------------------------------------------- */
+/*
+ Purpose: Perform SM_NOTIFY procedure at specified host
+ Returns: TRUE if success, FALSE if failed.
+*/
+
+static int notify_one_host(char *hostname)
+{
+ struct timeval timeout = { 20, 0 }; /* 20 secs timeout */
+ CLIENT *cli;
+ char dummy;
+ stat_chge arg;
+ char our_hostname[SM_MAXSTRLEN+1];
+
+ gethostname(our_hostname, sizeof(our_hostname));
+ our_hostname[SM_MAXSTRLEN] = '\0';
+ arg.mon_name = our_hostname;
+ arg.state = status_info->ourState;
+
+ if (debug) syslog (LOG_DEBUG, "Sending SM_NOTIFY to host %s from %s", hostname, our_hostname);
+
+ cli = clnt_create(hostname, SM_PROG, SM_VERS, "udp");
+ if (!cli)
+ {
+ syslog(LOG_ERR, "Failed to contact host %s%s", hostname,
+ clnt_spcreateerror(""));
+ return (FALSE);
+ }
+
+ if (clnt_call(cli, SM_NOTIFY, (xdrproc_t)xdr_stat_chge, &arg,
+ (xdrproc_t)xdr_void, &dummy, timeout)
+ != RPC_SUCCESS)
+ {
+ syslog(LOG_ERR, "Failed to contact rpc.statd at host %s", hostname);
+ clnt_destroy(cli);
+ return (FALSE);
+ }
+
+ clnt_destroy(cli);
+ return (TRUE);
+}
+
+/* notify_hosts ------------------------------------------------------------ */
+/*
+ Purpose: Send SM_NOTIFY to all hosts marked as requiring it
+ Returns: Nothing, immediately - forks a process to do the work.
+ Notes: Does nothing if there are no monitored hosts.
+ Called after all the initialisation has been done -
+ logs to syslog.
+*/
+
+void notify_hosts(void)
+{
+ int i;
+ int attempts;
+ int work_to_do = FALSE;
+ HostInfo *hp;
+ pid_t pid;
+
+ /* First check if there is in fact any work to do. */
+ for (i = status_info->noOfHosts, hp = status_info->hosts; i ; i--, hp++)
+ {
+ if (hp->notifyReqd)
+ {
+ work_to_do = TRUE;
+ break;
+ }
+ }
+
+ if (!work_to_do) return; /* No work found */
+
+ pid = fork();
+ if (pid == -1)
+ {
+ syslog(LOG_ERR, "Unable to fork notify process - %s", strerror(errno));
+ return;
+ }
+ if (pid) return;
+
+ /* Here in the child process. We continue until all the hosts marked */
+ /* as requiring notification have been duly notified. */
+ /* If one of the initial attempts fails, we sleep for a while and */
+ /* have another go. This is necessary because when we have crashed, */
+ /* (eg. a power outage) it is quite possible that we won't be able to */
+ /* contact all monitored hosts immediately on restart, either because */
+ /* they crashed too and take longer to come up (in which case the */
+ /* notification isn't really required), or more importantly if some */
+ /* router etc. needed to reach the monitored host has not come back */
+ /* up yet. In this case, we will be a bit late in re-establishing */
+ /* locks (after the grace period) but that is the best we can do. */
+ /* We try 10 times at 5 sec intervals, 10 more times at 1 minute */
+ /* intervals, then 24 more times at hourly intervals, finally */
+ /* giving up altogether if the host hasn't come back to life after */
+ /* 24 hours. */
+
+ for (attempts = 0; attempts < 44; attempts++)
+ {
+ work_to_do = FALSE; /* Unless anything fails */
+ for (i = status_info->noOfHosts, hp = status_info->hosts; i ; i--, hp++)
+ {
+ if (hp->notifyReqd)
+ {
+ if (notify_one_host(hp->hostname))
+ {
+ hp->notifyReqd = FALSE;
+ sync_file();
+ }
+ else work_to_do = TRUE;
+ }
+ }
+ if (!work_to_do) break;
+ if (attempts < 10) sleep(5);
+ else if (attempts < 20) sleep(60);
+ else sleep(60*60);
+ }
+ exit(0);
+}
+
+
diff --git a/usr.sbin/rpc.statd/procs.c b/usr.sbin/rpc.statd/procs.c
new file mode 100644
index 0000000..f6067e7
--- /dev/null
+++ b/usr.sbin/rpc.statd/procs.c
@@ -0,0 +1,436 @@
+/*
+ * Copyright (c) 1995
+ * A.R. Gordon (andrew.gordon@net-tel.co.uk). All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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
+ * 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 ANDREW GORDON AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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 <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <rpc/rpc.h>
+#include <syslog.h>
+#include <vis.h>
+#include <netdb.h> /* for getaddrinfo() */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "statd.h"
+
+static const char *
+from_addr(saddr)
+ struct sockaddr *saddr;
+{
+ static char inet_buf[INET6_ADDRSTRLEN];
+
+ if (getnameinfo(saddr, saddr->sa_len, inet_buf, sizeof(inet_buf),
+ NULL, 0, NI_NUMERICHOST) == 0)
+ return inet_buf;
+ return "???";
+}
+
+/* sm_check_hostname -------------------------------------------------------- */
+/*
+ * Purpose: Check `mon_name' member of sm_name struct to ensure that the array
+ * consists only of printable characters.
+ *
+ * Returns: TRUE if hostname is good. FALSE if hostname contains binary or
+ * otherwise non-printable characters.
+ *
+ * Notes: Will syslog(3) to warn of corrupt hostname.
+ */
+
+int sm_check_hostname(struct svc_req *req, char *arg)
+{
+ int len, dstlen, ret;
+ struct sockaddr *claddr;
+ char *dst;
+
+ len = strlen(arg);
+ dstlen = (4 * len) + 1;
+ dst = malloc(dstlen);
+ claddr = (struct sockaddr *) (svc_getrpccaller(req->rq_xprt)->buf) ;
+ ret = 1;
+
+ if (claddr == NULL || dst == NULL)
+ {
+ ret = 0;
+ }
+ else if (strvis(dst, arg, VIS_WHITE) != len)
+ {
+ syslog(LOG_ERR,
+ "sm_stat: client %s hostname %s contained invalid characters.",
+ from_addr(claddr),
+ dst);
+ ret = 0;
+ }
+ free(dst);
+ return (ret);
+}
+
+/* sm_stat_1 --------------------------------------------------------------- */
+/*
+ Purpose: RPC call to enquire if a host can be monitored
+ Returns: TRUE for any hostname that can be looked up to give
+ an address.
+*/
+
+struct sm_stat_res *sm_stat_1_svc(sm_name *arg, struct svc_req *req)
+{
+ static sm_stat_res res;
+ struct addrinfo *ai;
+ struct sockaddr *claddr;
+ static int err;
+
+ err = 1;
+ if ((err = sm_check_hostname(req, arg->mon_name)) == 0)
+ {
+ res.res_stat = stat_fail;
+ }
+ if (err != 0)
+ {
+ if (debug)
+ syslog(LOG_DEBUG, "stat called for host %s", arg->mon_name);
+ if (getaddrinfo(arg->mon_name, NULL, NULL, &ai) == 0) {
+ res.res_stat = stat_succ;
+ freeaddrinfo(ai);
+ }
+ else
+ {
+ claddr = (struct sockaddr *) (svc_getrpccaller(req->rq_xprt)->buf) ;
+ syslog(LOG_ERR, "invalid hostname to sm_stat from %s: %s",
+ from_addr(claddr), arg->mon_name);
+ res.res_stat = stat_fail;
+ }
+ }
+ res.state = status_info->ourState;
+ return (&res);
+}
+
+/* sm_mon_1 ---------------------------------------------------------------- */
+/*
+ Purpose: RPC procedure to establish a monitor request
+ Returns: Success, unless lack of resources prevents
+ the necessary structures from being set up
+ to record the request, or if the hostname is not
+ valid (as judged by getaddrinfo())
+*/
+
+struct sm_stat_res *sm_mon_1_svc(mon *arg, struct svc_req *req)
+{
+ static sm_stat_res res;
+ HostInfo *hp;
+ static int err;
+ MonList *lp;
+ struct addrinfo *ai;
+
+ if ((err = sm_check_hostname(req, arg->mon_id.mon_name)) == 0)
+ {
+ res.res_stat = stat_fail;
+ }
+
+ if (err != 0)
+ {
+ if (debug)
+ {
+ syslog(LOG_DEBUG, "monitor request for host %s", arg->mon_id.mon_name);
+ syslog(LOG_DEBUG, "recall host: %s prog: %d ver: %d proc: %d",
+ arg->mon_id.my_id.my_name,
+ arg->mon_id.my_id.my_prog,
+ arg->mon_id.my_id.my_vers,
+ arg->mon_id.my_id.my_proc);
+ }
+ res.res_stat = stat_fail; /* Assume fail until set otherwise */
+ res.state = status_info->ourState;
+
+ /* Find existing host entry, or create one if not found */
+ /* If find_host() fails, it will have logged the error already. */
+ if (getaddrinfo(arg->mon_id.mon_name, NULL, NULL, &ai) != 0)
+ {
+ syslog(LOG_ERR, "Invalid hostname to sm_mon: %s", arg->mon_id.mon_name);
+ return (&res);
+ }
+ freeaddrinfo(ai);
+ if ((hp = find_host(arg->mon_id.mon_name, TRUE)))
+ {
+ lp = (MonList *)malloc(sizeof(MonList));
+ if (!lp)
+ {
+ syslog(LOG_ERR, "Out of memory");
+ }
+ else
+ {
+ strncpy(lp->notifyHost, arg->mon_id.my_id.my_name, SM_MAXSTRLEN);
+ lp->notifyProg = arg->mon_id.my_id.my_prog;
+ lp->notifyVers = arg->mon_id.my_id.my_vers;
+ lp->notifyProc = arg->mon_id.my_id.my_proc;
+ memcpy(lp->notifyData, arg->priv, sizeof(lp->notifyData));
+
+ lp->next = hp->monList;
+ hp->monList = lp;
+ sync_file();
+
+ res.res_stat = stat_succ; /* Report success */
+ }
+ }
+ }
+ return (&res);
+}
+
+/* do_unmon ---------------------------------------------------------------- */
+/*
+ Purpose: Remove a monitor request from a host
+ Returns: TRUE if found, FALSE if not found.
+ Notes: Common code from sm_unmon_1_svc and sm_unmon_all_1_svc
+ In the unlikely event of more than one identical monitor
+ request, all are removed.
+*/
+
+static int do_unmon(HostInfo *hp, my_id *idp)
+{
+ MonList *lp, *next;
+ MonList *last = NULL;
+ int result = FALSE;
+
+ lp = hp->monList;
+ while (lp)
+ {
+ if (!strncasecmp(idp->my_name, lp->notifyHost, SM_MAXSTRLEN)
+ && (idp->my_prog == lp->notifyProg) && (idp->my_proc == lp->notifyProc)
+ && (idp->my_vers == lp->notifyVers))
+ {
+ /* found one. Unhook from chain and free. */
+ next = lp->next;
+ if (last) last->next = next;
+ else hp->monList = next;
+ free(lp);
+ lp = next;
+ result = TRUE;
+ }
+ else
+ {
+ last = lp;
+ lp = lp->next;
+ }
+ }
+ return (result);
+}
+
+/* sm_unmon_1 -------------------------------------------------------------- */
+/*
+ Purpose: RPC procedure to release a monitor request.
+ Returns: Local machine's status number
+ Notes: The supplied mon_id should match the value passed in an
+ earlier call to sm_mon_1
+*/
+
+struct sm_stat *sm_unmon_1_svc(mon_id *arg, struct svc_req *req __unused)
+{
+ static sm_stat res;
+ HostInfo *hp;
+
+ if (debug)
+ {
+ syslog(LOG_DEBUG, "un-monitor request for host %s", arg->mon_name);
+ syslog(LOG_DEBUG, "recall host: %s prog: %d ver: %d proc: %d",
+ arg->mon_name,
+ arg->my_id.my_prog, arg->my_id.my_vers, arg->my_id.my_proc);
+ }
+
+ if ((hp = find_host(arg->mon_name, FALSE)))
+ {
+ if (do_unmon(hp, &arg->my_id)) sync_file();
+ else
+ {
+ syslog(LOG_ERR, "unmon request from %s, no matching monitor",
+ arg->my_id.my_name);
+ }
+ }
+ else syslog(LOG_ERR, "unmon request from %s for unknown host %s",
+ arg->my_id.my_name, arg->mon_name);
+
+ res.state = status_info->ourState;
+
+ return (&res);
+}
+
+/* sm_unmon_all_1 ---------------------------------------------------------- */
+/*
+ Purpose: RPC procedure to release monitor requests.
+ Returns: Local machine's status number
+ Notes: Releases all monitor requests (if any) from the specified
+ host and program number.
+*/
+
+struct sm_stat *sm_unmon_all_1_svc(my_id *arg, struct svc_req *req __unused)
+{
+ static sm_stat res;
+ HostInfo *hp;
+ int i;
+
+ if (debug)
+ {
+ syslog(LOG_DEBUG, "unmon_all for host: %s prog: %d ver: %d proc: %d",
+ arg->my_name, arg->my_prog, arg->my_vers, arg->my_proc);
+ }
+
+ for (i = status_info->noOfHosts, hp = status_info->hosts; i; i--, hp++)
+ {
+ do_unmon(hp, arg);
+ }
+ sync_file();
+
+ res.state = status_info->ourState;
+
+ return (&res);
+}
+
+/* sm_simu_crash_1 --------------------------------------------------------- */
+/*
+ Purpose: RPC procedure to simulate a crash
+ Returns: Nothing
+ Notes: Standardised mechanism for debug purposes
+ The specification says that we should drop all of our
+ status information (apart from the list of monitored hosts
+ on disc). However, this would confuse the rpc.lockd
+ which would be unaware that all of its monitor requests
+ had been silently junked. Hence we in fact retain all
+ current requests and simply increment the status counter
+ and inform all hosts on the monitor list.
+*/
+
+void *sm_simu_crash_1_svc(void *v __unused, struct svc_req *req __unused)
+{
+ static char dummy;
+ int work_to_do;
+ HostInfo *hp;
+ int i;
+
+ work_to_do = FALSE;
+ if (debug) syslog(LOG_DEBUG, "simu_crash called!!");
+
+ /* Simulate crash by setting notify-required flag on all monitored */
+ /* hosts, and incrementing our status number. notify_hosts() is */
+ /* then called to fork a process to do the notifications. */
+
+ for (i = status_info->noOfHosts, hp = status_info->hosts; i ; i--, hp++)
+ {
+ if (hp->monList)
+ {
+ work_to_do = TRUE;
+ hp->notifyReqd = TRUE;
+ }
+ }
+ status_info->ourState += 2; /* always even numbers if not crashed */
+
+ if (work_to_do) notify_hosts();
+
+ return (&dummy);
+}
+
+/* sm_notify_1 ------------------------------------------------------------- */
+/*
+ Purpose: RPC procedure notifying local statd of the crash of another
+ Returns: Nothing
+ Notes: There is danger of deadlock, since it is quite likely that
+ the client procedure that we call will in turn call us
+ to remove or adjust the monitor request.
+ We therefore fork() a process to do the notifications.
+ Note that the main HostInfo structure is in a mmap()
+ region and so will be shared with the child, but the
+ monList pointed to by the HostInfo is in normal memory.
+ Hence if we read the monList before forking, we are
+ protected from the parent servicing other requests
+ that modify the list.
+*/
+
+void *sm_notify_1_svc(stat_chge *arg, struct svc_req *req __unused)
+{
+ struct timeval timeout = { 20, 0 }; /* 20 secs timeout */
+ CLIENT *cli;
+ static char dummy;
+ sm_status tx_arg; /* arg sent to callback procedure */
+ MonList *lp;
+ HostInfo *hp;
+ pid_t pid;
+
+ if (debug) syslog(LOG_DEBUG, "notify from host %s, new state %d",
+ arg->mon_name, arg->state);
+
+ hp = find_host(arg->mon_name, FALSE);
+ if (!hp)
+ {
+ /* Never heard of this host - why is it notifying us? */
+ syslog(LOG_ERR, "Unsolicited notification from host %s", arg->mon_name);
+ return (&dummy);
+ }
+ lp = hp->monList;
+ if (!lp) return (&dummy); /* We know this host, but have no */
+ /* outstanding requests. */
+ pid = fork();
+ if (pid == -1)
+ {
+ syslog(LOG_ERR, "Unable to fork notify process - %s", strerror(errno));
+ return (NULL); /* no answer, the client will retry */
+ }
+ if (pid) return (&dummy); /* Parent returns */
+
+ while (lp)
+ {
+ tx_arg.mon_name = arg->mon_name;
+ tx_arg.state = arg->state;
+ memcpy(tx_arg.priv, lp->notifyData, sizeof(tx_arg.priv));
+ cli = clnt_create(lp->notifyHost, lp->notifyProg, lp->notifyVers, "udp");
+ if (!cli)
+ {
+ syslog(LOG_ERR, "Failed to contact host %s%s", lp->notifyHost,
+ clnt_spcreateerror(""));
+ }
+ else
+ {
+ if (clnt_call(cli, lp->notifyProc, (xdrproc_t)xdr_sm_status, &tx_arg,
+ (xdrproc_t)xdr_void, &dummy, timeout) != RPC_SUCCESS)
+ {
+ syslog(LOG_ERR, "Failed to call rpc.statd client at host %s",
+ lp->notifyHost);
+ }
+ clnt_destroy(cli);
+ }
+ lp = lp->next;
+ }
+
+ exit (0); /* Child quits */
+}
diff --git a/usr.sbin/rpc.statd/rpc.statd.8 b/usr.sbin/rpc.statd/rpc.statd.8
new file mode 100644
index 0000000..32559ad
--- /dev/null
+++ b/usr.sbin/rpc.statd/rpc.statd.8
@@ -0,0 +1,137 @@
+.\" -*- nroff -*-
+.\"
+.\" Copyright (c) 1995 A.R.Gordon, andrew.gordon@net-tel.co.uk
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 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 November 1, 2007
+.Dt RPC.STATD 8
+.Os
+.Sh NAME
+.Nm rpc.statd
+.Nd host status monitoring daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl d
+.Op Fl h Ar bindip
+.Op Fl p Ar port
+.Sh DESCRIPTION
+The
+.Nm
+utility
+is a daemon which co-operates with
+.Nm
+daemons on other hosts to provide
+a status monitoring service.
+The daemon accepts requests from
+programs running on the local host (typically,
+.Xr rpc.lockd 8 ,
+the NFS file locking daemon) to monitor the status of specified
+hosts.
+If a monitored host crashes and restarts, the remote daemon will
+notify the local daemon, which in turn will notify the local program(s)
+which requested the monitoring service.
+Conversely, if this host crashes
+and re-starts, when the
+.Nm
+re-starts, it will notify all of the hosts which were being monitored
+at the time of the crash.
+.Pp
+The following option is available:
+.Bl -tag -width indent
+.It Fl d
+Cause debugging information to be written to syslog, recording
+all RPC transactions to the daemon.
+These messages are logged with level
+LOG_DEBUG and facility LOG_DAEMON.
+Error conditions are logged irrespective
+of this option, using level LOG_ERR.
+.It Fl h Ar bindip
+Specify specific IP addresses to bind to.
+This option may be specified multiple times.
+If no
+.Fl h
+option is specified,
+.Nm
+will bind to
+.Dv INADDR_ANY .
+Note that when specifying IP addresses with
+.Fl h ,
+.Nm
+will automatically add
+.Li 127.0.0.1
+and if IPv6 is enabled,
+.Li ::1
+to the list.
+.It Fl p
+The
+.Fl p
+option allow to force the daemon to bind to the specified
+.Ar port ,
+for both AF_INET and AF_INET6 address families.
+.El
+.Pp
+The
+.Nm
+utility must NOT be invoked by
+.Xr inetd 8
+because the protocol assumes that the daemon will run from system start time.
+Instead, it should be run from
+.Xr rc 8
+after the network has been started.
+.Sh FILES
+.Bl -tag -width /usr/include/rpcsvc/sm_inter.x -compact
+.It Pa /var/db/statd.status
+non-volatile record of currently monitored hosts.
+.It Pa /usr/include/rpcsvc/sm_inter.x
+RPC protocol specification used by local applications to register monitoring requests.
+.El
+.Sh SEE ALSO
+.Xr syslog 3 ,
+.Xr rc 8 ,
+.Xr rpc.lockd 8
+.Sh STANDARDS
+The implementation is based on the specification in X/Open CAE Specification
+C218, "Protocols for X/Open PC Interworking: XNFS, Issue 4", ISBN 1 872630 66 9
+.Sh BUGS
+There is no means for the daemon to tell when a monitored host has
+disappeared permanently (e.g.\& catastrophic hardware failure), as opposed
+to transient failure of the host or an intermediate router.
+At present,
+it will re-try notification attempts at frequent intervals for 10 minutes,
+then hourly, and finally gives up after 24 hours.
+.Pp
+The protocol requires that symmetric monitor requests are made to both
+the local and remote daemon in order to establish a monitored relationship.
+This is convenient for the NFS locking protocol, but probably reduces the
+usefulness of the monitoring system for other applications.
+.Pp
+The current implementation uses more than 1Kbyte per monitored host in
+the status file (and also in VM).
+This may be inefficient for NFS servers
+with large numbers of clients.
diff --git a/usr.sbin/rpc.statd/statd.c b/usr.sbin/rpc.statd/statd.c
new file mode 100644
index 0000000..256f58d
--- /dev/null
+++ b/usr.sbin/rpc.statd/statd.c
@@ -0,0 +1,655 @@
+/*
+ * Copyright (c) 1995
+ * A.R. Gordon (andrew.gordon@net-tel.co.uk). All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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
+ * 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 ANDREW GORDON AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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.
+ *
+ */
+
+/* main() function for status monitor daemon. Some of the code in this */
+/* file was generated by running rpcgen /usr/include/rpcsvc/sm_inter.x */
+/* The actual program logic is in the file procs.c */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <rpc/rpc.h>
+#include <rpc/rpc_com.h>
+#include <string.h>
+#include <syslog.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <signal.h>
+#include <unistd.h>
+#include "statd.h"
+
+#define GETPORT_MAXTRY 20 /* Max tries to get a port # */
+
+int debug = 0; /* Controls syslog() calls for debug messages */
+
+char **hosts, *svcport_str = NULL;
+int nhosts = 0;
+int xcreated = 0;
+static int mallocd_svcport = 0;
+static int *sock_fd;
+static int sock_fdcnt;
+static int sock_fdpos;
+
+static int create_service(struct netconfig *nconf);
+static void complete_service(struct netconfig *nconf, char *port_str);
+static void clearout_service(void);
+static void handle_sigchld(int sig);
+void out_of_mem(void);
+
+static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ struct sigaction sa;
+ struct netconfig *nconf;
+ void *nc_handle;
+ in_port_t svcport;
+ int ch, i, s;
+ char *endptr, **hosts_bak;
+ int have_v6 = 1;
+ int maxrec = RPC_MAXDATASIZE;
+ int attempt_cnt, port_len, port_pos, ret;
+ char **port_list;
+
+ while ((ch = getopt(argc, argv, "dh:p:")) != -1)
+ switch (ch) {
+ case 'd':
+ debug = 1;
+ break;
+ case 'h':
+ ++nhosts;
+ hosts_bak = hosts;
+ hosts_bak = realloc(hosts, nhosts * sizeof(char *));
+ if (hosts_bak == NULL) {
+ if (hosts != NULL) {
+ for (i = 0; i < nhosts; i++)
+ free(hosts[i]);
+ free(hosts);
+ out_of_mem();
+ }
+ }
+ hosts = hosts_bak;
+ hosts[nhosts - 1] = strdup(optarg);
+ if (hosts[nhosts - 1] == NULL) {
+ for (i = 0; i < (nhosts - 1); i++)
+ free(hosts[i]);
+ free(hosts);
+ out_of_mem();
+ }
+ break;
+ case 'p':
+ endptr = NULL;
+ svcport = (in_port_t)strtoul(optarg, &endptr, 10);
+ if (endptr == NULL || *endptr != '\0' || svcport == 0 ||
+ svcport >= IPPORT_MAX)
+ usage();
+
+ svcport_str = strdup(optarg);
+ break;
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ (void)rpcb_unset(SM_PROG, SM_VERS, NULL);
+
+ /*
+ * Check if IPv6 support is present.
+ */
+ s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ if (s < 0)
+ have_v6 = 0;
+ else
+ close(s);
+
+ rpc_control(RPC_SVC_CONNMAXREC_SET, &maxrec);
+
+ /*
+ * If no hosts were specified, add a wildcard entry to bind to
+ * INADDR_ANY. Otherwise make sure 127.0.0.1 and ::1 are added to the
+ * list.
+ */
+ if (nhosts == 0) {
+ hosts = malloc(sizeof(char *));
+ if (hosts == NULL)
+ out_of_mem();
+
+ hosts[0] = "*";
+ nhosts = 1;
+ } else {
+ hosts_bak = hosts;
+ if (have_v6) {
+ hosts_bak = realloc(hosts, (nhosts + 2) *
+ sizeof(char *));
+ if (hosts_bak == NULL) {
+ for (i = 0; i < nhosts; i++)
+ free(hosts[i]);
+ free(hosts);
+ out_of_mem();
+ } else
+ hosts = hosts_bak;
+
+ nhosts += 2;
+ hosts[nhosts - 2] = "::1";
+ } else {
+ hosts_bak = realloc(hosts, (nhosts + 1) * sizeof(char *));
+ if (hosts_bak == NULL) {
+ for (i = 0; i < nhosts; i++)
+ free(hosts[i]);
+
+ free(hosts);
+ out_of_mem();
+ } else {
+ nhosts += 1;
+ hosts = hosts_bak;
+ }
+ }
+ hosts[nhosts - 1] = "127.0.0.1";
+ }
+
+ attempt_cnt = 1;
+ sock_fdcnt = 0;
+ sock_fd = NULL;
+ port_list = NULL;
+ port_len = 0;
+ nc_handle = setnetconfig();
+ while ((nconf = getnetconfig(nc_handle))) {
+ /* We want to listen only on udp6, tcp6, udp, tcp transports */
+ if (nconf->nc_flag & NC_VISIBLE) {
+ /* Skip if there's no IPv6 support */
+ if (have_v6 == 0 && strcmp(nconf->nc_protofmly, "inet6") == 0) {
+ /* DO NOTHING */
+ } else {
+ ret = create_service(nconf);
+ if (ret == 1)
+ /* Ignore this call */
+ continue;
+ if (ret < 0) {
+ /*
+ * Failed to bind port, so close off
+ * all sockets created and try again
+ * if the port# was dynamically
+ * assigned via bind(2).
+ */
+ clearout_service();
+ if (mallocd_svcport != 0 &&
+ attempt_cnt < GETPORT_MAXTRY) {
+ free(svcport_str);
+ svcport_str = NULL;
+ mallocd_svcport = 0;
+ } else {
+ errno = EADDRINUSE;
+ syslog(LOG_ERR,
+ "bindresvport_sa: %m");
+ exit(1);
+ }
+
+ /* Start over at the first service. */
+ free(sock_fd);
+ sock_fdcnt = 0;
+ sock_fd = NULL;
+ nc_handle = setnetconfig();
+ attempt_cnt++;
+ } else if (mallocd_svcport != 0 &&
+ attempt_cnt == GETPORT_MAXTRY) {
+ /*
+ * For the last attempt, allow
+ * different port #s for each nconf
+ * by saving the svcport_str and
+ * setting it back to NULL.
+ */
+ port_list = realloc(port_list,
+ (port_len + 1) * sizeof(char *));
+ if (port_list == NULL)
+ out_of_mem();
+ port_list[port_len++] = svcport_str;
+ svcport_str = NULL;
+ mallocd_svcport = 0;
+ }
+ }
+ }
+ }
+
+ /*
+ * Successfully bound the ports, so call complete_service() to
+ * do the rest of the setup on the service(s).
+ */
+ sock_fdpos = 0;
+ port_pos = 0;
+ nc_handle = setnetconfig();
+ while ((nconf = getnetconfig(nc_handle))) {
+ /* We want to listen only on udp6, tcp6, udp, tcp transports */
+ if (nconf->nc_flag & NC_VISIBLE) {
+ /* Skip if there's no IPv6 support */
+ if (have_v6 == 0 && strcmp(nconf->nc_protofmly, "inet6") == 0) {
+ /* DO NOTHING */
+ } else if (port_list != NULL) {
+ if (port_pos >= port_len) {
+ syslog(LOG_ERR, "too many port#s");
+ exit(1);
+ }
+ complete_service(nconf, port_list[port_pos++]);
+ } else
+ complete_service(nconf, svcport_str);
+ }
+ }
+ endnetconfig(nc_handle);
+ free(sock_fd);
+ if (port_list != NULL) {
+ for (port_pos = 0; port_pos < port_len; port_pos++)
+ free(port_list[port_pos]);
+ free(port_list);
+ }
+
+ init_file("/var/db/statd.status");
+
+ /* Note that it is NOT sensible to run this program from inetd - the */
+ /* protocol assumes that it will run immediately at boot time. */
+ daemon(0, 0);
+ openlog("rpc.statd", 0, LOG_DAEMON);
+ if (debug) syslog(LOG_INFO, "Starting - debug enabled");
+ else syslog(LOG_INFO, "Starting");
+
+ /* Install signal handler to collect exit status of child processes */
+ sa.sa_handler = handle_sigchld;
+ sigemptyset(&sa.sa_mask);
+ sigaddset(&sa.sa_mask, SIGCHLD);
+ sa.sa_flags = SA_RESTART;
+ sigaction(SIGCHLD, &sa, NULL);
+
+ /* Initialisation now complete - start operating */
+ notify_hosts(); /* Forks a process (if necessary) to do the */
+ /* SM_NOTIFY calls, which may be slow. */
+
+ svc_run(); /* Should never return */
+ exit(1);
+}
+
+/*
+ * This routine creates and binds sockets on the appropriate
+ * addresses. It gets called one time for each transport.
+ * It returns 0 upon success, 1 for ingore the call and -1 to indicate
+ * bind failed with EADDRINUSE.
+ * Any file descriptors that have been created are stored in sock_fd and
+ * the total count of them is maintained in sock_fdcnt.
+ */
+static int
+create_service(struct netconfig *nconf)
+{
+ struct addrinfo hints, *res = NULL;
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ struct __rpc_sockinfo si;
+ int aicode;
+ int fd;
+ int nhostsbak;
+ int r;
+ u_int32_t host_addr[4]; /* IPv4 or IPv6 */
+ int mallocd_res;
+
+ if ((nconf->nc_semantics != NC_TPI_CLTS) &&
+ (nconf->nc_semantics != NC_TPI_COTS) &&
+ (nconf->nc_semantics != NC_TPI_COTS_ORD))
+ return (1); /* not my type */
+
+ /*
+ * XXX - using RPC library internal functions.
+ */
+ if (!__rpc_nconf2sockinfo(nconf, &si)) {
+ syslog(LOG_ERR, "cannot get information for %s",
+ nconf->nc_netid);
+ return (1);
+ }
+
+ /* Get rpc.statd's address on this transport */
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = si.si_af;
+ hints.ai_socktype = si.si_socktype;
+ hints.ai_protocol = si.si_proto;
+
+ /*
+ * Bind to specific IPs if asked to
+ */
+ nhostsbak = nhosts;
+ while (nhostsbak > 0) {
+ --nhostsbak;
+ sock_fd = realloc(sock_fd, (sock_fdcnt + 1) * sizeof(int));
+ if (sock_fd == NULL)
+ out_of_mem();
+ sock_fd[sock_fdcnt++] = -1; /* Set invalid for now. */
+ mallocd_res = 0;
+ hints.ai_flags = AI_PASSIVE;
+
+ /*
+ * XXX - using RPC library internal functions.
+ */
+ if ((fd = __rpc_nconf2fd(nconf)) < 0) {
+ syslog(LOG_ERR, "cannot create socket for %s",
+ nconf->nc_netid);
+ continue;
+ }
+ switch (hints.ai_family) {
+ case AF_INET:
+ if (inet_pton(AF_INET, hosts[nhostsbak],
+ host_addr) == 1) {
+ hints.ai_flags |= AI_NUMERICHOST;
+ } else {
+ /*
+ * Skip if we have an AF_INET6 address.
+ */
+ if (inet_pton(AF_INET6, hosts[nhostsbak],
+ host_addr) == 1) {
+ close(fd);
+ continue;
+ }
+ }
+ break;
+ case AF_INET6:
+ if (inet_pton(AF_INET6, hosts[nhostsbak],
+ host_addr) == 1) {
+ hints.ai_flags |= AI_NUMERICHOST;
+ } else {
+ /*
+ * Skip if we have an AF_INET address.
+ */
+ if (inet_pton(AF_INET, hosts[nhostsbak],
+ host_addr) == 1) {
+ close(fd);
+ continue;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * If no hosts were specified, just bind to INADDR_ANY
+ */
+ if (strcmp("*", hosts[nhostsbak]) == 0) {
+ if (svcport_str == NULL) {
+ res = malloc(sizeof(struct addrinfo));
+ if (res == NULL)
+ out_of_mem();
+ mallocd_res = 1;
+ res->ai_flags = hints.ai_flags;
+ res->ai_family = hints.ai_family;
+ res->ai_protocol = hints.ai_protocol;
+ switch (res->ai_family) {
+ case AF_INET:
+ sin = malloc(sizeof(struct sockaddr_in));
+ if (sin == NULL)
+ out_of_mem();
+ sin->sin_family = AF_INET;
+ sin->sin_port = htons(0);
+ sin->sin_addr.s_addr = htonl(INADDR_ANY);
+ res->ai_addr = (struct sockaddr*) sin;
+ res->ai_addrlen = (socklen_t)
+ sizeof(struct sockaddr_in);
+ break;
+ case AF_INET6:
+ sin6 = malloc(sizeof(struct sockaddr_in6));
+ if (sin6 == NULL)
+ out_of_mem();
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_port = htons(0);
+ sin6->sin6_addr = in6addr_any;
+ res->ai_addr = (struct sockaddr*) sin6;
+ res->ai_addrlen = (socklen_t)
+ sizeof(struct sockaddr_in6);
+ break;
+ default:
+ syslog(LOG_ERR, "bad addr fam %d",
+ res->ai_family);
+ exit(1);
+ }
+ } else {
+ if ((aicode = getaddrinfo(NULL, svcport_str,
+ &hints, &res)) != 0) {
+ syslog(LOG_ERR,
+ "cannot get local address for %s: %s",
+ nconf->nc_netid,
+ gai_strerror(aicode));
+ close(fd);
+ continue;
+ }
+ }
+ } else {
+ if ((aicode = getaddrinfo(hosts[nhostsbak], svcport_str,
+ &hints, &res)) != 0) {
+ syslog(LOG_ERR,
+ "cannot get local address for %s: %s",
+ nconf->nc_netid, gai_strerror(aicode));
+ close(fd);
+ continue;
+ }
+ }
+
+ /* Store the fd. */
+ sock_fd[sock_fdcnt - 1] = fd;
+
+ /* Now, attempt the bind. */
+ r = bindresvport_sa(fd, res->ai_addr);
+ if (r != 0) {
+ if (errno == EADDRINUSE && mallocd_svcport != 0) {
+ if (mallocd_res != 0) {
+ free(res->ai_addr);
+ free(res);
+ } else
+ freeaddrinfo(res);
+ return (-1);
+ }
+ syslog(LOG_ERR, "bindresvport_sa: %m");
+ exit(1);
+ }
+
+ if (svcport_str == NULL) {
+ svcport_str = malloc(NI_MAXSERV * sizeof(char));
+ if (svcport_str == NULL)
+ out_of_mem();
+ mallocd_svcport = 1;
+
+ if (getnameinfo(res->ai_addr,
+ res->ai_addr->sa_len, NULL, NI_MAXHOST,
+ svcport_str, NI_MAXSERV * sizeof(char),
+ NI_NUMERICHOST | NI_NUMERICSERV))
+ errx(1, "Cannot get port number");
+ }
+ if (mallocd_res != 0) {
+ free(res->ai_addr);
+ free(res);
+ } else
+ freeaddrinfo(res);
+ res = NULL;
+ }
+ return (0);
+}
+
+/*
+ * Called after all the create_service() calls have succeeded, to complete
+ * the setup and registration.
+ */
+static void
+complete_service(struct netconfig *nconf, char *port_str)
+{
+ struct addrinfo hints, *res = NULL;
+ struct __rpc_sockinfo si;
+ struct netbuf servaddr;
+ SVCXPRT *transp = NULL;
+ int aicode, fd, nhostsbak;
+ int registered = 0;
+
+ if ((nconf->nc_semantics != NC_TPI_CLTS) &&
+ (nconf->nc_semantics != NC_TPI_COTS) &&
+ (nconf->nc_semantics != NC_TPI_COTS_ORD))
+ return; /* not my type */
+
+ /*
+ * XXX - using RPC library internal functions.
+ */
+ if (!__rpc_nconf2sockinfo(nconf, &si)) {
+ syslog(LOG_ERR, "cannot get information for %s",
+ nconf->nc_netid);
+ return;
+ }
+
+ nhostsbak = nhosts;
+ while (nhostsbak > 0) {
+ --nhostsbak;
+ if (sock_fdpos >= sock_fdcnt) {
+ /* Should never happen. */
+ syslog(LOG_ERR, "Ran out of socket fd's");
+ return;
+ }
+ fd = sock_fd[sock_fdpos++];
+ if (fd < 0)
+ continue;
+
+ if (nconf->nc_semantics != NC_TPI_CLTS)
+ listen(fd, SOMAXCONN);
+
+ transp = svc_tli_create(fd, nconf, NULL,
+ RPC_MAXDATASIZE, RPC_MAXDATASIZE);
+
+ if (transp != (SVCXPRT *) NULL) {
+ if (!svc_register(transp, SM_PROG, SM_VERS,
+ sm_prog_1, 0)) {
+ syslog(LOG_ERR, "can't register on %s",
+ nconf->nc_netid);
+ } else {
+ if (!svc_reg(transp, SM_PROG, SM_VERS,
+ sm_prog_1, NULL))
+ syslog(LOG_ERR,
+ "can't register %s SM_PROG service",
+ nconf->nc_netid);
+ }
+ } else
+ syslog(LOG_WARNING, "can't create %s services",
+ nconf->nc_netid);
+
+ if (registered == 0) {
+ registered = 1;
+ memset(&hints, 0, sizeof hints);
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = si.si_af;
+ hints.ai_socktype = si.si_socktype;
+ hints.ai_protocol = si.si_proto;
+
+
+ if ((aicode = getaddrinfo(NULL, port_str, &hints,
+ &res)) != 0) {
+ syslog(LOG_ERR, "cannot get local address: %s",
+ gai_strerror(aicode));
+ exit(1);
+ }
+
+ servaddr.buf = malloc(res->ai_addrlen);
+ memcpy(servaddr.buf, res->ai_addr, res->ai_addrlen);
+ servaddr.len = res->ai_addrlen;
+
+ rpcb_set(SM_PROG, SM_VERS, nconf, &servaddr);
+
+ xcreated++;
+ freeaddrinfo(res);
+ }
+ } /* end while */
+}
+
+/*
+ * Clear out sockets after a failure to bind one of them, so that the
+ * cycle of socket creation/binding can start anew.
+ */
+static void
+clearout_service(void)
+{
+ int i;
+
+ for (i = 0; i < sock_fdcnt; i++) {
+ if (sock_fd[i] >= 0) {
+ shutdown(sock_fd[i], SHUT_RDWR);
+ close(sock_fd[i]);
+ }
+ }
+}
+
+static void
+usage()
+{
+ fprintf(stderr, "usage: rpc.statd [-d] [-h <bindip>] [-p <port>]\n");
+ exit(1);
+}
+
+/* handle_sigchld ---------------------------------------------------------- */
+/*
+ Purpose: Catch SIGCHLD and collect process status
+ Retruns: Nothing.
+ Notes: No special action required, other than to collect the
+ process status and hence allow the child to die:
+ we only use child processes for asynchronous transmission
+ of SM_NOTIFY to other systems, so it is normal for the
+ children to exit when they have done their work.
+*/
+
+static void handle_sigchld(int sig __unused)
+{
+ int pid, status;
+ pid = wait4(-1, &status, WNOHANG, (struct rusage*)0);
+ if (!pid) syslog(LOG_ERR, "Phantom SIGCHLD??");
+ else if (status == 0)
+ {
+ if (debug) syslog(LOG_DEBUG, "Child %d exited OK", pid);
+ }
+ else syslog(LOG_ERR, "Child %d failed with status %d", pid,
+ WEXITSTATUS(status));
+}
+
+/*
+ * Out of memory, fatal
+ */
+void
+out_of_mem()
+{
+
+ syslog(LOG_ERR, "out of memory");
+ exit(2);
+}
diff --git a/usr.sbin/rpc.statd/statd.h b/usr.sbin/rpc.statd/statd.h
new file mode 100644
index 0000000..a82d760
--- /dev/null
+++ b/usr.sbin/rpc.statd/statd.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 1995
+ * A.R. Gordon (andrew.gordon@net-tel.co.uk). All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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
+ * 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 ANDREW GORDON AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED 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 "sm_inter.h"
+
+/* ------------------------------------------------------------------------- */
+/*
+ Data structures for recording monitored hosts
+
+ The information held by the status monitor comprises a list of hosts
+ that we have been asked to monitor, and, associated with each monitored
+ host, one or more clients to be called back if the monitored host crashes.
+
+ The list of monitored hosts must be retained over a crash, so that upon
+ re-boot we can call the SM_NOTIFY procedure in all those hosts so as to
+ cause them to start recovery processing. On the other hand, the client
+ call-backs are not required to be preserved: they are assumed (in the
+ protocol design) to be local processes which will have crashed when
+ we did, and so are discarded on restart.
+
+ We handle this by keeping the list of monitored hosts in a file
+ (/var/statd.state) which is mmap()ed and whose format is described
+ by the typedef FileLayout. The lists of client callbacks are chained
+ off this structure, but are held in normal memory and so will be
+ lost after a re-boot. Hence the actual values of MonList * pointers
+ in the copy on disc have no significance, but their NULL/non-NULL
+ status indicates whether this host is actually being monitored or if it
+ is an empty slot in the file.
+*/
+
+typedef struct MonList_s
+{
+ struct MonList_s *next; /* Next in list or NULL */
+ char notifyHost[SM_MAXSTRLEN + 1]; /* Host to notify */
+ int notifyProg; /* RPC program number to call */
+ int notifyVers; /* version number */
+ int notifyProc; /* procedure number */
+ unsigned char notifyData[16]; /* Opaque data from caller */
+} MonList;
+
+typedef struct
+{
+ char hostname[SM_MAXSTRLEN + 1]; /* Name of monitored host */
+ int notifyReqd; /* TRUE if we've crashed and not yet */
+ /* informed the monitored host */
+ MonList *monList; /* List of clients to inform if we */
+ /* hear that the monitored host has */
+ /* crashed, NULL if no longer monitored */
+} HostInfo;
+
+
+/* Overall file layout. */
+
+typedef struct
+{
+ int ourState; /* State number as defined in statd protocol */
+ int noOfHosts; /* Number of elements in hosts[] */
+ char reserved[248]; /* Reserved for future use */
+ HostInfo hosts[1]; /* vector of monitored hosts */
+} FileLayout;
+
+#define HEADER_LEN (sizeof(FileLayout) - sizeof(HostInfo))
+
+/* ------------------------------------------------------------------------- */
+
+/* Global variables */
+
+extern FileLayout *status_info; /* The mmap()ed status file */
+
+extern int debug; /* =1 to enable diagnostics to syslog */
+
+/* Function prototypes */
+
+extern HostInfo *find_host(char * /*hostname*/, int /*create*/);
+extern void init_file(const char * /*filename*/);
+extern void notify_hosts(void);
+extern void sync_file(void);
+extern int sm_check_hostname(struct svc_req *req, char *arg);
diff --git a/usr.sbin/rpc.statd/test.c b/usr.sbin/rpc.statd/test.c
new file mode 100644
index 0000000..6df1501
--- /dev/null
+++ b/usr.sbin/rpc.statd/test.c
@@ -0,0 +1,144 @@
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <stdio.h>
+#include <rpc/rpc.h>
+#include <rpcsvc/sm_inter.h>
+
+
+/* Default timeout can be changed using clnt_control() */
+static struct timeval TIMEOUT = { 25, 0 };
+
+struct sm_stat_res *
+sm_stat_1(argp, clnt)
+ struct sm_name *argp;
+ CLIENT *clnt;
+{
+ static struct sm_stat_res res;
+
+ bzero((char *)&res, sizeof(res));
+ if (clnt_call(clnt, SM_STAT, xdr_sm_name, argp, xdr_sm_stat_res, &res, TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return (&res);
+}
+
+
+struct sm_stat_res *
+sm_mon_1(argp, clnt)
+ struct mon *argp;
+ CLIENT *clnt;
+{
+ static struct sm_stat_res res;
+
+ bzero((char *)&res, sizeof(res));
+ if (clnt_call(clnt, SM_MON, xdr_mon, argp, xdr_sm_stat_res, &res, TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return (&res);
+}
+
+
+struct sm_stat *
+sm_unmon_1(argp, clnt)
+ struct mon_id *argp;
+ CLIENT *clnt;
+{
+ static struct sm_stat res;
+
+ bzero((char *)&res, sizeof(res));
+ if (clnt_call(clnt, SM_UNMON, xdr_mon_id, argp, xdr_sm_stat, &res, TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return (&res);
+}
+
+
+struct sm_stat *
+sm_unmon_all_1(argp, clnt)
+ struct my_id *argp;
+ CLIENT *clnt;
+{
+ static struct sm_stat res;
+
+ bzero((char *)&res, sizeof(res));
+ if (clnt_call(clnt, SM_UNMON_ALL, xdr_my_id, argp, xdr_sm_stat, &res, TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return (&res);
+}
+
+
+void *
+sm_simu_crash_1(argp, clnt)
+ void *argp;
+ CLIENT *clnt;
+{
+ static char res;
+
+ bzero((char *)&res, sizeof(res));
+ if (clnt_call(clnt, SM_SIMU_CRASH, xdr_void, argp, xdr_void, &res, TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return ((void *)&res);
+}
+
+
+int main(int argc, char **argv)
+{
+ CLIENT *cli;
+ char dummy;
+ void *out;
+ struct mon mon;
+
+ if (argc < 2)
+ {
+ fprintf(stderr, "usage: test <hostname> | crash\n");
+ fprintf(stderr, "always talks to statd at localhost\n");
+ exit(1);
+ }
+
+ printf("Creating client for localhost\n" );
+ cli = clnt_create("localhost", SM_PROG, SM_VERS, "udp");
+ if (!cli)
+ {
+ printf("Failed to create client\n");
+ exit(1);
+ }
+
+ mon.mon_id.mon_name = argv[1];
+ mon.mon_id.my_id.my_name = argv[1];
+ mon.mon_id.my_id.my_prog = SM_PROG;
+ mon.mon_id.my_id.my_vers = SM_VERS;
+ mon.mon_id.my_id.my_proc = 1; /* have it call sm_stat() !!! */
+
+ if (strcmp(argv[1], "crash"))
+ {
+ /* Hostname given */
+ struct sm_stat_res *res;
+ if (res = sm_mon_1(&mon, cli))
+ {
+ printf("Success!\n");
+ }
+ else
+ {
+ printf("Fail\n");
+ }
+ }
+ else
+ {
+ if (out = sm_simu_crash_1(&dummy, cli))
+ {
+ printf("Success!\n");
+ }
+ else
+ {
+ printf("Fail\n");
+ }
+ }
+
+ return 0;
+}
diff --git a/usr.sbin/rpc.umntall/Makefile b/usr.sbin/rpc.umntall/Makefile
new file mode 100644
index 0000000..fc4a399
--- /dev/null
+++ b/usr.sbin/rpc.umntall/Makefile
@@ -0,0 +1,10 @@
+# @(#)Makefile 8.4 (Berkeley) 6/22/95
+# $FreeBSD$
+
+PROG= rpc.umntall
+MAN= rpc.umntall.8
+SRCS= rpc.umntall.c mounttab.c
+
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/rpc.umntall/Makefile.depend b/usr.sbin/rpc.umntall/Makefile.depend
new file mode 100644
index 0000000..ddd5dbd
--- /dev/null
+++ b/usr.sbin/rpc.umntall/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/rpc.umntall/mounttab.c b/usr.sbin/rpc.umntall/mounttab.c
new file mode 100644
index 0000000..56333ca
--- /dev/null
+++ b/usr.sbin/rpc.umntall/mounttab.c
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) 1999 Martin Blapp
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/syslog.h>
+
+#include <rpc/rpc.h>
+#include <rpcsvc/mount.h>
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mounttab.h"
+
+struct mtablist *mtabhead;
+
+static void badline(const char *field, const char *bad);
+
+/*
+ * Add an entry to PATH_MOUNTTAB for each mounted NFS filesystem,
+ * so the client can notify the NFS server even after reboot.
+ */
+int
+add_mtab(char *hostp, char *dirp)
+{
+ FILE *mtabfile;
+
+ if ((mtabfile = fopen(PATH_MOUNTTAB, "a")) == NULL)
+ return (0);
+ else {
+ fprintf(mtabfile, "%ld\t%s\t%s\n",
+ (long)time(NULL), hostp, dirp);
+ fclose(mtabfile);
+ return (1);
+ }
+}
+
+/*
+ * Read mounttab line for line and return struct mtablist.
+ */
+int
+read_mtab(void)
+{
+ struct mtablist **mtabpp, *mtabp;
+ char *hostp, *dirp, *cp;
+ char str[STRSIZ];
+ char *timep, *endp;
+ time_t actiontime;
+ u_long ultmp;
+ FILE *mtabfile;
+
+ if ((mtabfile = fopen(PATH_MOUNTTAB, "r")) == NULL) {
+ if (errno == ENOENT)
+ return (0);
+ else {
+ syslog(LOG_ERR, "can't open %s", PATH_MOUNTTAB);
+ return (0);
+ }
+ }
+ actiontime = 0;
+ mtabpp = &mtabhead;
+ while (fgets(str, STRSIZ, mtabfile) != NULL) {
+ cp = str;
+ errno = 0;
+ if (*cp == '#' || *cp == ' ' || *cp == '\n')
+ continue;
+ timep = strsep(&cp, " \t\n");
+ if (timep == NULL || *timep == '\0') {
+ badline("time", timep);
+ continue;
+ }
+ hostp = strsep(&cp, " \t\n");
+ if (hostp == NULL || *hostp == '\0') {
+ badline("host", hostp);
+ continue;
+ }
+ dirp = strsep(&cp, " \t\n");
+ if (dirp == NULL || *dirp == '\0') {
+ badline("dir", dirp);
+ continue;
+ }
+ ultmp = strtoul(timep, &endp, 10);
+ if (ultmp == ULONG_MAX || *endp != '\0') {
+ badline("time", timep);
+ continue;
+ }
+ actiontime = ultmp;
+ if ((mtabp = malloc(sizeof (struct mtablist))) == NULL) {
+ syslog(LOG_ERR, "malloc");
+ fclose(mtabfile);
+ return (0);
+ }
+ mtabp->mtab_time = actiontime;
+ memmove(mtabp->mtab_host, hostp, MNTNAMLEN);
+ mtabp->mtab_host[MNTNAMLEN - 1] = '\0';
+ memmove(mtabp->mtab_dirp, dirp, MNTPATHLEN);
+ mtabp->mtab_dirp[MNTPATHLEN - 1] = '\0';
+ mtabp->mtab_next = (struct mtablist *)NULL;
+ *mtabpp = mtabp;
+ mtabpp = &mtabp->mtab_next;
+ }
+ fclose(mtabfile);
+ return (1);
+}
+
+/*
+ * Rewrite PATH_MOUNTTAB from scratch and skip bad entries.
+ * Unlink PATH_MOUNTAB if no entry is left.
+ */
+int
+write_mtab(int verbose)
+{
+ struct mtablist *mtabp, *mp;
+ FILE *mtabfile;
+ int line;
+
+ if ((mtabfile = fopen(PATH_MOUNTTAB, "w")) == NULL) {
+ syslog(LOG_ERR, "can't write to %s", PATH_MOUNTTAB);
+ return (0);
+ }
+ line = 0;
+ for (mtabp = mtabhead; mtabp != NULL; mtabp = mtabp->mtab_next) {
+ if (mtabp->mtab_host[0] == '\0')
+ continue;
+ /* Skip if a later (hence more recent) entry is identical. */
+ for (mp = mtabp->mtab_next; mp != NULL; mp = mp->mtab_next)
+ if (strcmp(mtabp->mtab_host, mp->mtab_host) == 0 &&
+ strcmp(mtabp->mtab_dirp, mp->mtab_dirp) == 0)
+ break;
+ if (mp != NULL)
+ continue;
+
+ fprintf(mtabfile, "%ld\t%s\t%s\n",
+ (long)mtabp->mtab_time, mtabp->mtab_host,
+ mtabp->mtab_dirp);
+ if (verbose)
+ warnx("write mounttab entry %s:%s",
+ mtabp->mtab_host, mtabp->mtab_dirp);
+ line++;
+ }
+ fclose(mtabfile);
+ if (line == 0) {
+ if (unlink(PATH_MOUNTTAB) == -1) {
+ syslog(LOG_ERR, "can't remove %s", PATH_MOUNTTAB);
+ return (0);
+ }
+ }
+ return (1);
+}
+
+/*
+ * Mark the entries as clean where RPC calls have been done successfully.
+ */
+void
+clean_mtab(char *hostp, char *dirp, int verbose)
+{
+ struct mtablist *mtabp;
+ char *host;
+
+ /* Copy hostp in case it points to an entry that we are zeroing out. */
+ host = strdup(hostp);
+ for (mtabp = mtabhead; mtabp != NULL; mtabp = mtabp->mtab_next) {
+ if (strcmp(mtabp->mtab_host, host) != 0)
+ continue;
+ if (dirp != NULL && strcmp(mtabp->mtab_dirp, dirp) != 0)
+ continue;
+
+ if (verbose)
+ warnx("delete mounttab entry%s %s:%s",
+ (dirp == NULL) ? " by host" : "",
+ mtabp->mtab_host, mtabp->mtab_dirp);
+ bzero(mtabp->mtab_host, MNTNAMLEN);
+ }
+ free(host);
+}
+
+/*
+ * Free struct mtablist mtab.
+ */
+void
+free_mtab(void)
+{
+ struct mtablist *mtabp;
+
+ while ((mtabp = mtabhead) != NULL) {
+ mtabhead = mtabhead->mtab_next;
+ free(mtabp);
+ }
+}
+
+/*
+ * Print bad lines to syslog.
+ */
+static void
+badline(const char *field, const char *bad)
+{
+ syslog(LOG_ERR, "bad mounttab %s field '%s'", field,
+ (bad == NULL) ? "<null>" : bad);
+}
diff --git a/usr.sbin/rpc.umntall/mounttab.h b/usr.sbin/rpc.umntall/mounttab.h
new file mode 100644
index 0000000..9999b03
--- /dev/null
+++ b/usr.sbin/rpc.umntall/mounttab.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 1999 Martin Blapp
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+#define STRSIZ (MNTNAMLEN+MNTPATHLEN+100)
+#define PATH_MOUNTTAB "/var/db/mounttab"
+
+/* Structure for /var/db/mounttab */
+struct mtablist {
+ time_t mtab_time;
+ char mtab_host[MNTNAMLEN];
+ char mtab_dirp[MNTPATHLEN];
+ struct mtablist *mtab_next;
+};
+
+extern struct mtablist *mtabhead;
+
+int add_mtab(char *, char *);
+void clean_mtab(char *, char *, int);
+int read_mtab(void);
+int write_mtab(int);
+void free_mtab(void);
diff --git a/usr.sbin/rpc.umntall/rpc.umntall.8 b/usr.sbin/rpc.umntall/rpc.umntall.8
new file mode 100644
index 0000000..1f7607f
--- /dev/null
+++ b/usr.sbin/rpc.umntall/rpc.umntall.8
@@ -0,0 +1,126 @@
+.\"
+.\" Copyright (c) 1999 Martin Blapp
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 November 17, 1999
+.Dt RPC.UMNTALL 8
+.Os
+.Sh NAME
+.Nm rpc.umntall
+.Nd notify NFS servers about unmounted NFS file systems
+.Sh SYNOPSIS
+.Nm
+.Op Fl e Ar expire
+.Op Fl h Ar host
+.Op Fl k
+.Op Fl p Ar remotepath
+.Op Fl v
+.Sh DESCRIPTION
+The
+.Nm
+utility is proposed in the
+.Tn NFS
+RPC specification; see
+.Rs
+.%T "NFS Version 3 Protocol Specification"
+.%O "RFC 1813, Appendix I"
+.Re
+It uses remote procedure calls to remove mount entries from
+.Pa /var/db/mountdtab
+on the remote NFS server.
+It is called automatically
+without any parameters during startup and shutdown of
+the system.
+This ensures that
+.Xr showmount 8
+does not display old and expired entries.
+The
+.Nm
+utility
+is only needed on client side, where
+.Xr mount_nfs 8
+adds a mount entry with the current date to
+.Pa /var/db/mounttab ,
+and
+.Xr umount 8
+removes the entry again.
+The
+.Nm
+utility
+cares about all remaining entries in this table which result from crashes
+or unproper shutdowns.
+.Pp
+The options are as follows:
+.Bl -tag -width indentxxx
+.It Fl e Ar expire
+All entries which are not actually mounted or older than
+.Ar expire
+(seconds) are removed from
+.Pa /var/db/mounttab .
+This may be the case
+for DNS changes or long out of service periods.
+Default expire time
+is 86400 seconds (one day).
+.It Fl h Ar host
+Only remove the specific hostname.
+Send a UMNTALL RPC to the NFS server.
+.It Fl k
+Keep entries for existing NFS file systems.
+Compare the NFS file systems from
+the mounttab against the kernel mountlist and do not send the RPC to
+existing mount entries.
+Useful during startup of the system.
+It may be
+possible that there are already mounted NFS file systems, so calling
+RPC UMOUNT is not a good idea.
+This is the case if the user has rebooted
+to 'single user mode' and starts up the system again.
+.It Fl p Ar path
+Only remove the specific mount-path.
+Send a UMOUNT RPC to the NFS server.
+This option implies the
+.Fl host
+option.
+.It Fl v
+Verbose, additional information is printed for each processed mounttab
+entry.
+.El
+.Sh FILES
+.Bl -tag -width /var/db/mounttab -compact
+.It Pa /var/db/mounttab
+mounted nfs-file systems
+.El
+.Sh SEE ALSO
+.Xr mount_nfs 8 ,
+.Xr mountd 8 ,
+.Xr umount 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 4.0 .
+.Sh AUTHORS
+.An Martin Blapp Aq Mt mb@imp.ch
diff --git a/usr.sbin/rpc.umntall/rpc.umntall.c b/usr.sbin/rpc.umntall/rpc.umntall.c
new file mode 100644
index 0000000..a64fae6
--- /dev/null
+++ b/usr.sbin/rpc.umntall/rpc.umntall.c
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 1999 Martin Blapp
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/param.h>
+#include <sys/ucred.h>
+#include <sys/mount.h>
+
+#include <rpc/rpc.h>
+#include <rpcsvc/mount.h>
+
+#include <err.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mounttab.h"
+
+int verbose;
+
+static int do_umount (char *, char *);
+static int do_umntall (char *);
+static int is_mounted (char *, char *);
+static void usage (void);
+int xdr_dir (XDR *, char *);
+
+int
+main(int argc, char **argv) {
+ int ch, keep, success, pathlen;
+ time_t expire, now;
+ char *host, *path;
+ struct mtablist *mtab;
+
+ expire = 0;
+ host = path = NULL;
+ success = keep = verbose = 0;
+ while ((ch = getopt(argc, argv, "h:kp:ve:")) != -1)
+ switch (ch) {
+ case 'h':
+ host = optarg;
+ break;
+ case 'e':
+ expire = atoi(optarg);
+ break;
+ case 'k':
+ keep = 1;
+ break;
+ case 'p':
+ path = optarg;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ case '?':
+ usage();
+ default:
+ break;
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Default expiretime is one day */
+ if (expire == 0)
+ expire = 86400;
+ time(&now);
+
+ /* Read PATH_MOUNTTAB. */
+ if (!read_mtab()) {
+ if (verbose)
+ warnx("no mounttab entries (%s does not exist)",
+ PATH_MOUNTTAB);
+ mtabhead = NULL;
+ }
+
+ if (host == NULL && path == NULL) {
+ /* Check each entry and do any necessary unmount RPCs. */
+ for (mtab = mtabhead; mtab != NULL; mtab = mtab->mtab_next) {
+ if (*mtab->mtab_host == '\0')
+ continue;
+ if (mtab->mtab_time + expire < now) {
+ /* Clear expired entry. */
+ if (verbose)
+ warnx("remove expired entry %s:%s",
+ mtab->mtab_host, mtab->mtab_dirp);
+ bzero(mtab->mtab_host,
+ sizeof(mtab->mtab_host));
+ continue;
+ }
+ if (keep && is_mounted(mtab->mtab_host,
+ mtab->mtab_dirp)) {
+ if (verbose)
+ warnx("skip entry %s:%s",
+ mtab->mtab_host, mtab->mtab_dirp);
+ continue;
+ }
+ if (do_umount(mtab->mtab_host, mtab->mtab_dirp)) {
+ if (verbose)
+ warnx("umount RPC for %s:%s succeeded",
+ mtab->mtab_host, mtab->mtab_dirp);
+ /* Remove all entries for this host + path. */
+ clean_mtab(mtab->mtab_host, mtab->mtab_dirp,
+ verbose);
+ }
+ }
+ success = 1;
+ } else {
+ if (host == NULL && path != NULL)
+ /* Missing hostname. */
+ usage();
+ if (path == NULL) {
+ /* Do a RPC UMNTALL for this specific host */
+ success = do_umntall(host);
+ if (verbose && success)
+ warnx("umntall RPC for %s succeeded", host);
+ } else {
+ /* Do a RPC UMNTALL for this specific mount */
+ for (pathlen = strlen(path);
+ pathlen > 1 && path[pathlen - 1] == '/'; pathlen--)
+ path[pathlen - 1] = '\0';
+ success = do_umount(host, path);
+ if (verbose && success)
+ warnx("umount RPC for %s:%s succeeded", host,
+ path);
+ }
+ /* If successful, remove any corresponding mounttab entries. */
+ if (success)
+ clean_mtab(host, path, verbose);
+ }
+ /* Write and unlink PATH_MOUNTTAB if necessary */
+ if (success)
+ success = write_mtab(verbose);
+ free_mtab();
+ exit (success ? 0 : 1);
+}
+
+/*
+ * Send a RPC_MNT UMNTALL request to hostname.
+ * XXX This works for all mountd implementations,
+ * but produces a RPC IOERR on non FreeBSD systems.
+ */
+int
+do_umntall(char *hostname) {
+ enum clnt_stat clnt_stat;
+ struct timeval try;
+ CLIENT *clp;
+
+ try.tv_sec = 3;
+ try.tv_usec = 0;
+ clp = clnt_create_timed(hostname, MOUNTPROG, MOUNTVERS, "udp",
+ &try);
+ if (clp == NULL) {
+ warnx("%s: %s", hostname, clnt_spcreateerror("MOUNTPROG"));
+ return (0);
+ }
+ clp->cl_auth = authunix_create_default();
+ clnt_stat = clnt_call(clp, MOUNTPROC_UMNTALL,
+ (xdrproc_t)xdr_void, (caddr_t)0,
+ (xdrproc_t)xdr_void, (caddr_t)0, try);
+ if (clnt_stat != RPC_SUCCESS)
+ warnx("%s: %s", hostname, clnt_sperror(clp, "MOUNTPROC_UMNTALL"));
+ auth_destroy(clp->cl_auth);
+ clnt_destroy(clp);
+ return (clnt_stat == RPC_SUCCESS);
+}
+
+/*
+ * Send a RPC_MNT UMOUNT request for dirp to hostname.
+ */
+int
+do_umount(char *hostname, char *dirp) {
+ enum clnt_stat clnt_stat;
+ struct timeval try;
+ CLIENT *clp;
+
+ try.tv_sec = 3;
+ try.tv_usec = 0;
+ clp = clnt_create_timed(hostname, MOUNTPROG, MOUNTVERS, "udp",
+ &try);
+ if (clp == NULL) {
+ warnx("%s: %s", hostname, clnt_spcreateerror("MOUNTPROG"));
+ return (0);
+ }
+ clp->cl_auth = authsys_create_default();
+ clnt_stat = clnt_call(clp, MOUNTPROC_UMNT, (xdrproc_t)xdr_dir, dirp,
+ (xdrproc_t)xdr_void, (caddr_t)0, try);
+ if (clnt_stat != RPC_SUCCESS)
+ warnx("%s: %s", hostname, clnt_sperror(clp, "MOUNTPROC_UMNT"));
+ auth_destroy(clp->cl_auth);
+ clnt_destroy(clp);
+ return (clnt_stat == RPC_SUCCESS);
+}
+
+/*
+ * Check if the entry is still/already mounted.
+ */
+int
+is_mounted(char *hostname, char *dirp) {
+ struct statfs *mntbuf;
+ char name[MNAMELEN + 1];
+ size_t bufsize;
+ int mntsize, i;
+
+ if (strlen(hostname) + strlen(dirp) >= MNAMELEN)
+ return (0);
+ snprintf(name, sizeof(name), "%s:%s", hostname, dirp);
+ mntsize = getfsstat(NULL, 0, MNT_NOWAIT);
+ if (mntsize <= 0)
+ return (0);
+ bufsize = (mntsize + 1) * sizeof(struct statfs);
+ if ((mntbuf = malloc(bufsize)) == NULL)
+ err(1, "malloc");
+ mntsize = getfsstat(mntbuf, (long)bufsize, MNT_NOWAIT);
+ for (i = mntsize - 1; i >= 0; i--) {
+ if (strcmp(mntbuf[i].f_mntfromname, name) == 0) {
+ free(mntbuf);
+ return (1);
+ }
+ }
+ free(mntbuf);
+ return (0);
+}
+
+/*
+ * xdr routines for mount rpc's
+ */
+int
+xdr_dir(XDR *xdrsp, char *dirp) {
+ return (xdr_string(xdrsp, &dirp, MNTPATHLEN));
+}
+
+static void
+usage() {
+ (void)fprintf(stderr, "%s\n",
+ "usage: rpc.umntall [-kv] [-e expire] [-h host] [-p path]");
+ exit(1);
+}
diff --git a/usr.sbin/rpc.yppasswdd/Makefile b/usr.sbin/rpc.yppasswdd/Makefile
new file mode 100644
index 0000000..9b2fb39
--- /dev/null
+++ b/usr.sbin/rpc.yppasswdd/Makefile
@@ -0,0 +1,61 @@
+# $FreeBSD$
+
+RPCDIR= ${DESTDIR}/usr/include/rpcsvc
+
+.PATH: ${.CURDIR}/../../usr.sbin/ypserv ${.CURDIR}/../../usr.bin/chpass \
+ ${.CURDIR}/../../libexec/ypxfr ${RPCDIR}
+
+PROG= rpc.yppasswdd
+SCRIPTS=yppwupdate
+SCRIPTSDIR= ${LIBEXECDIR}
+MAN= rpc.yppasswdd.8
+SRCS= util.c yp_access.c yp_dblookup.c yp_dbwrite.c \
+ yp_error.c yppasswdd_main.c yppasswdd_server.c ypxfr_misc.c ${GENSRCS}
+GENSRCS=yp.h yp_clnt.c yppasswd.h yppasswd_private.h yppasswd_private_svc.c \
+ yppasswd_private_xdr.c yppasswd_svc.c
+
+WARNS?= 5
+CFLAGS+= -fno-strict-aliasing
+CFLAGS+= -I${.CURDIR}/../../usr.sbin/vipw \
+ -I${.CURDIR}/../../usr.sbin/ypserv \
+ -I${.CURDIR}/../../libexec/ypxfr \
+ -I${.CURDIR} -I.
+LIBADD= rpcsvc crypt util
+
+CLEANFILES= ${GENSRCS}
+
+RPCGEN= RPCGEN_CPP=${CPP:Q} rpcgen -I -C
+
+# We need to remove the 'static' keyword from _rpcsvcstate so that
+# yppasswdd_main.c can see it.
+yppasswd_svc.c: yppasswd.x
+ rm -f ${.TARGET}
+ ${RPCGEN} -m ${RPCDIR}/yppasswd.x | \
+ sed s/"static int _rpcsvcstate"/"int _rpcsvcstate"/g > ${.TARGET}
+
+yppasswd.h: yppasswd.x
+ rm -f ${.TARGET}
+ ${RPCGEN} -h -o ${.TARGET} ${RPCDIR}/yppasswd.x
+
+yp.h: yp.x
+ rm -f ${.TARGET}
+ ${RPCGEN} -h -o ${.TARGET} ${RPCDIR}/yp.x
+
+yp_clnt.c: yp.x
+ rm -f ${.TARGET}
+ ${RPCGEN} -DYPSERV_ONLY -l -o ${.TARGET} ${RPCDIR}/yp.x
+
+yppasswd_private.h: yppasswd_private.x
+ rm -f ${.TARGET}
+ ${RPCGEN} -h -o ${.TARGET} ${.CURDIR}/yppasswd_private.x
+
+yppasswd_private_xdr.c: yppasswd_private.x
+ rm -f ${.TARGET}
+ ${RPCGEN} -c -o ${.TARGET} ${.CURDIR}/yppasswd_private.x
+
+yppasswd_private_svc.c: yppasswd_private.x
+ rm -f ${.TARGET}
+ ${RPCGEN} -m ${.CURDIR}/yppasswd_private.x | \
+ sed s/"static int _rpcsvcstate = _IDLE"/"extern int _rpcsvcstate"/g > ${.TARGET}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/rpc.yppasswdd/Makefile.depend b/usr.sbin/rpc.yppasswdd/Makefile.depend
new file mode 100644
index 0000000..ffc93ca
--- /dev/null
+++ b/usr.sbin/rpc.yppasswdd/Makefile.depend
@@ -0,0 +1,48 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libcrypt \
+ lib/librpcsvc \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+yp_clnt.o: yp.h
+yp_clnt.o: yp_clnt.c
+yp_clnt.po: yp.h
+yp_clnt.po: yp_clnt.c
+yppasswd_private_svc.o: yppasswd_private.h
+yppasswd_private_svc.o: yppasswd_private_svc.c
+yppasswd_private_svc.po: yppasswd_private.h
+yppasswd_private_svc.po: yppasswd_private_svc.c
+yppasswd_private_xdr.o: yppasswd_private.h
+yppasswd_private_xdr.o: yppasswd_private_xdr.c
+yppasswd_private_xdr.po: yppasswd_private.h
+yppasswd_private_xdr.po: yppasswd_private_xdr.c
+yppasswd_svc.o: yppasswd.h
+yppasswd_svc.o: yppasswd_svc.c
+yppasswd_svc.po: yppasswd.h
+yppasswd_svc.po: yppasswd_svc.c
+yppasswdd_main.o: yppasswd.h
+yppasswdd_main.o: yppasswd_private.h
+yppasswdd_main.po: yppasswd.h
+yppasswdd_main.po: yppasswd_private.h
+yppasswdd_server.o: yppasswd.h
+yppasswdd_server.o: yppasswd_private.h
+yppasswdd_server.po: yppasswd.h
+yppasswdd_server.po: yppasswd_private.h
+.endif
diff --git a/usr.sbin/rpc.yppasswdd/rpc.yppasswdd.8 b/usr.sbin/rpc.yppasswdd/rpc.yppasswdd.8
new file mode 100644
index 0000000..3d0b486
--- /dev/null
+++ b/usr.sbin/rpc.yppasswdd/rpc.yppasswdd.8
@@ -0,0 +1,359 @@
+.\" Copyright (c) 1995, 1996
+.\" 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 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 CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (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 8, 1996
+.Dt RPC.YPPASSWDD 8
+.Os
+.Sh NAME
+.Nm rpc.yppasswdd
+.Nd "server for updating NIS passwords"
+.Sh SYNOPSIS
+.Nm
+.Op Fl t Ar master.passwd template file
+.Op Fl d Ar default domain
+.Op Fl p Ar path
+.Op Fl s
+.Op Fl f
+.Op Fl a
+.Op Fl m
+.Op Fl i
+.Op Fl v
+.Op Fl u
+.Op Fl h
+.Sh DESCRIPTION
+The
+.Nm
+utility allows users to change their NIS passwords and certain
+other information using the
+.Xr yppasswd 1
+and
+.Xr ypchpass 1
+commands.
+The
+.Nm
+utility
+is an RPC-based server that accepts incoming password change requests,
+authenticates them, places the updated information in the
+.Pa /var/yp/master.passwd
+template file and then updates the NIS
+.Pa master.passwd
+and
+.Pa passwd
+maps.
+.Pp
+The
+.Nm
+utility allows a normal NIS user to change
+his or her NIS password, full name (also
+known as 'GECOS' field) or shell.
+These updates are typically done using
+the
+.Xr yppasswd 1 ,
+.Xr ypchfn 1 ,
+.Xr ypchsh 1 ,
+or
+.Xr ypchpass 1
+commands.
+(Some administrators do not want users to be able to change their
+full name information or shells; the server can be invoked with option flags
+that disallow such changes.)
+When the server receives an update request,
+it compares the address of the client making the request against the
+.Pa securenets
+rules outlined in
+.Pa /var/yp/securenets .
+(See the
+.Xr ypserv 8
+manual page for more information on securenets; the
+.Nm
+utility uses the same access control mechanism as
+.Xr ypserv 8 . )
+.Pp
+The server then
+checks the 'old' password supplied by the user to make sure it is
+valid, then performs some sanity checks on the updated information (these
+include checking for embedded control characters, colons or invalid shells).
+Once it is satisfied that the update request is valid, the server modifies
+the template password file (the default is
+.Pa /var/yp/master.passwd )
+and then runs the
+.Pa /usr/libexec/yppwupdate
+script to rebuild the NIS maps.
+(This script has two arguments passed
+to it: the absolute pathname of the password template that was modified
+and the name of the domain that is to be updated.
+These in turn are
+passed to
+.Pa /var/yp/Makefile ) .
+.Pp
+The
+.Fx
+version of
+.Nm
+also allows the super-user on the NIS master server to perform more
+sophisticated updates on the NIS passwd maps.
+The super-user can modify
+any field in any user's master.passwd entry in any domain, and can
+do so without knowing the user's existing NIS password (when the server
+receives a request from the super-user, the password authentication
+check is bypassed).
+Furthermore, if the server is invoked with the
+.Fl a
+flag, the super-user can even add new entries to the maps using
+.Xr ypchpass 1 .
+Again, this only applies to the super-user on the NIS
+master server: none of these special functions can be performed over
+the network.
+.Pp
+The
+.Nm
+utility can only be run on a machine that is an NIS master server.
+.Sh OPTIONS
+The following options are available:
+.Bl -tag -width indent
+.It Fl t Ar master.passwd template file
+By default,
+.Nm
+assumes that the template file used to generates the
+.Pa master.passwd
+and
+.Pa passwd
+maps for the default domain is called
+.Pa /var/yp/master.passwd .
+This default can be overridden by specifying an alternate file name
+with the
+.Fl t
+flag.
+.Pp
+Note: if the template file specified with this flag is
+.Pa /etc/master.passwd ,
+.Nm
+will also automatically invoke
+.Xr pwd_mkdb 8
+to rebuild the local password databases in addition to the NIS
+maps.
+.It Fl d Ar domain
+The
+.Nm
+utility can support multiple domains, however it must
+choose one domain as a default.
+It will try to use the system default domain name as set by the
+.Xr domainname 1
+command for this default.
+However,
+if the system domain name is not
+set, a default domain must be specified on
+the command line.
+If the system default domain is set,
+then this option can be used to override it.
+.It Fl p Ar path
+This option can be used to override the default path to
+the location of the NIS
+map databases.
+The compiled-in default path is
+.Pa /var/yp .
+.It Fl s
+Disallow changing of shell information.
+.It Fl f
+Disallow changing of full name ('GECOS') information.
+.It Fl a
+Allow additions to be made to the NIS passwd databases.
+The super-user on the
+NIS master server is permitted to use the
+.Xr ypchpass 1
+command to perform unrestricted modifications to any field in a user's
+.Pa master.passwd
+map entry.
+When
+.Nm
+is started with this flag, it will also allow the super-user to add new
+records to the NIS passwd maps, just as is possible when using
+.Xr chpass 1
+to modify the local password database.
+.It Fl m
+Turn on multi-domain mode.
+Even though
+.Xr ypserv 8
+can handle several simultaneous domains, most implementations of
+.Nm
+can only operate on a single NIS domain, which is generally the same as
+the system default domain of the NIS master server.
+The
+.Fx
+.Nm
+attempts to overcome this problem in spite of the inherent limitations
+of the
+.Pa yppasswd
+protocol, which does not allow for a
+.Pa domain
+argument in client requests.
+In multi-domain mode,
+.Nm
+will search through all the passwd maps of all the domains it
+can find under
+.Pa /var/yp
+until it finds an entry that matches the user information specified in
+a given update request.
+(Matches are determined by checking the username,
+UID and GID fields.)
+The matched entry and corresponding domain are then
+used for the update.
+.Pp
+Note that in order for multi-domain mode to work, there have to be
+separate template files for each domain.
+For example, if a server
+supports three domains,
+.Pa foo ,
+.Pa bar ,
+and
+.Pa baz ,
+there should be three separate master.passwd template files called
+.Pa /var/yp/foo/master.passwd ,
+.Pa /var/yp/bar/master.passwd ,
+and
+.Pa /var/yp/baz/master.passwd .
+If
+.Pa foo
+happens to be the system default domain, then its template file can
+be either
+.Pa /var/yp/foo/master.passwd
+or
+.Pa /var/yp/master.passwd .
+The server will check for the latter file first and then use the former
+if it cannot find it.
+.Pp
+Multi-domain mode is off by default since it can fail if there are
+duplicate or near-duplicate user entries in different domains.
+The server
+will abort an update request if it finds more than one user entry that
+matches its search criteria.
+Even so, paranoid administrators
+may wish to leave multi-domain mode disabled.
+.It Fl i
+If
+.Nm
+is invoked with this flag, it will perform map updates in place.
+This
+means that instead of just modifying the password template file and
+starting a map update, the server will modify the map databases
+directly.
+This is useful when the password maps are large: if, for
+example, the password database has tens of thousands of entries, it
+can take several minutes for a map update to complete.
+Updating the
+maps in place reduces this time to a few seconds.
+.It Fl v
+Turn on verbose logging mode.
+The server normally only logs messages
+using the
+.Xr syslog 3
+facility when it encounters an error condition, or when processing
+updates for the super-user on the NIS master server.
+Running the server
+with the
+.Fl v
+flag will cause it to log informational messages for all updates.
+.It Fl u
+Many commercial
+.Xr yppasswd 1
+clients do not use a reserved port when sending requests to
+.Nm .
+This is either because the
+.Xr yppasswd 1
+program is not installed set-uid root, or because the RPC
+implementation does not place any emphasis on binding to reserved
+ports when establishing client connections for the super-user.
+By default,
+.Nm
+expects to receive requests from clients using reserved ports; requests
+received from non-privileged ports are rejected.
+Unfortunately, this
+behavior prevents any client systems that to not use privileged
+ports from successfully submitting password updates.
+Specifying
+the
+.Fl u
+flag to
+.Nm
+disables the privileged port check so that it will work with
+.Xr yppasswd 1
+clients that do not use privileged ports.
+This reduces security to
+a certain small degree, but it might be necessary in cases where it
+is not possible to change the client behavior.
+.It Fl h
+Display the list of flags and options understood by
+.Nm .
+.El
+.Sh FILES
+.Bl -tag -width Pa -compact
+.It Pa /usr/libexec/yppwupdate
+The script invoked by
+.Nm
+to update and push the NIS maps after
+an update.
+.It Pa /var/yp/master.passwd
+The template password file for the default domain.
+.It Pa /var/yp/[domainname]/[maps]
+The NIS maps for a particular NIS domain.
+.It Pa /var/yp/[domainname]/master.passwd
+The template password file(s) for non-default domains
+(used only in multi-domain mode).
+.El
+.Sh SEE ALSO
+.Xr yp 8 ,
+.Xr yppush 8 ,
+.Xr ypserv 8 ,
+.Xr ypxfr 8
+.Sh AUTHORS
+.An Bill Paul Aq Mt wpaul@ctr.columbia.edu
+.Sh BUGS
+As listed in the yppasswd.x protocol definition, the YPPASSWDPROC_UPDATE
+procedure takes two arguments: a V7-style passwd structure containing
+updated user information and the user's existing unencrypted (cleartext)
+password.
+Since
+.Nm
+is supposed to handle update requests from remote NIS client machines,
+this means that
+.Xr yppasswd 1
+and similar client programs will in fact be transmitting users' cleartext
+passwords over the network.
+.Pp
+This is not a problem for password updates since the plaintext password
+sent with the update will no longer be valid once the new encrypted password
+is put into place, but if the user is only updating his or her 'GECOS'
+information or shell, then the cleartext password sent with the update
+will still be valid once the update is completed.
+If the network is
+insecure, this cleartext password could be intercepted and used to
+gain unauthorized access to the user's account.
diff --git a/usr.sbin/rpc.yppasswdd/yppasswd_private.x b/usr.sbin/rpc.yppasswdd/yppasswd_private.x
new file mode 100644
index 0000000..a1d6be5
--- /dev/null
+++ b/usr.sbin/rpc.yppasswdd/yppasswd_private.x
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 1995, 1996
+ * 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 RPC_HDR
+%#include <sys/cdefs.h>
+%__FBSDID("$FreeBSD$");
+#endif
+
+#ifdef RPC_HDR
+%#define YP_SOCKNAME "/var/run/yppasswdsock"
+#endif
+
+struct x_master_passwd {
+ string pw_name<>; /* username */
+ string pw_passwd<>; /* encrypted password */
+ int pw_uid; /* user id */
+ int pw_gid; /* group id */
+ unsigned long pw_change;/* password change time */
+ string pw_class<>; /* user access class */
+ string pw_gecos<>; /* in real life name */
+ string pw_dir<>; /* home directory */
+ string pw_shell<>; /* default shell */
+ unsigned long pw_expire;/* account expiration */
+ unsigned long pw_fields;/* internal: fields filled in */
+};
+
+const _YPMAXDOMAIN = 64;
+
+struct master_yppasswd {
+ string oldpass<>; /* unencrypted old password */
+ string domain<_YPMAXDOMAIN>; /* domain we want to operate on */
+ x_master_passwd newpw; /* new passwd entry */
+};
+
+
+program MASTER_YPPASSWDPROG {
+ version MASTER_YPPASSWDVERS {
+ int
+ YPPASSWDPROC_UPDATE_MASTER(master_yppasswd) = 1;
+ } = 1;
+} = 600100009;
diff --git a/usr.sbin/rpc.yppasswdd/yppasswdd_extern.h b/usr.sbin/rpc.yppasswdd/yppasswdd_extern.h
new file mode 100644
index 0000000..db15be2
--- /dev/null
+++ b/usr.sbin/rpc.yppasswdd/yppasswdd_extern.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 1995, 1996
+ * 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 _YPPASSWDD_EXTERN_H
+#define _YPPASSWDD_EXTERN_H
+
+#ifndef YPLIBDIR
+#define YPLIBDIR "/usr/libexec/"
+#endif
+
+#ifndef _PATH_YP
+#define _PATH_YP "/var/yp/"
+#endif
+
+#define MAP_UPDATE "yppwupdate"
+#define MAP_UPDATE_PATH YPLIBDIR "yppwupdate"
+
+extern char *yp_dir;
+extern char *progname;
+extern void do_master(void);
+extern void yppasswdprog_1(struct svc_req *, register SVCXPRT *);
+extern void master_yppasswdprog_1(struct svc_req *, register SVCXPRT *);
+extern void reaper(int);
+extern void install_reaper(int);
+extern char *ok_shell(char *);
+extern char *passfile;
+extern char *passfile_default;
+extern char *yppasswd_domain;
+extern int no_chsh;
+extern int no_chfn;
+extern int allow_additions;
+extern int multidomain;
+extern int resvport;
+extern int inplace;
+extern int verbose;
+extern int _rpc_dtablesize(void);
+
+#endif
diff --git a/usr.sbin/rpc.yppasswdd/yppasswdd_main.c b/usr.sbin/rpc.yppasswdd/yppasswdd_main.c
new file mode 100644
index 0000000..38719c4
--- /dev/null
+++ b/usr.sbin/rpc.yppasswdd/yppasswdd_main.c
@@ -0,0 +1,352 @@
+/*
+ * Copyright (c) 1995, 1996
+ * 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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/stat.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <netinet/in.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <memory.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h> /* getenv, exit */
+#include <string.h> /* strcmp */
+#include <syslog.h>
+#include <unistd.h>
+
+#include <rpc/rpc.h>
+#include <rpc/rpc_com.h>
+#include <rpc/pmap_clnt.h> /* for pmap_unset */
+#include <rpcsvc/yp.h>
+#include <rpcsvc/ypclnt.h>
+
+#include "yppasswd.h"
+#include "yppasswdd_extern.h"
+#include "yppasswd_private.h"
+#include "ypxfr_extern.h"
+#include "yp_extern.h"
+
+#ifndef SIG_PF
+#define SIG_PF void(*)(int)
+#endif
+
+#ifdef DEBUG
+#define RPC_SVC_FG
+#endif
+
+#define _RPCSVC_CLOSEDOWN 120
+int _rpcpmstart = 0; /* Started by a port monitor ? */
+static int _rpcfdtype;
+ /* Whether Stream or Datagram ? */
+ /* States a server can be in wrt request */
+
+#define _IDLE 0
+#define _SERVED 1
+#define _SERVING 2
+
+static char _localhost[] = "localhost";
+static char _passwd_byname[] = "passwd.byname";
+extern int _rpcsvcstate; /* Set when a request is serviced */
+static char _progname[] = "rpc.yppasswdd";
+char *progname = _progname;
+static char _yp_dir[] = _PATH_YP;
+char *yp_dir = _yp_dir;
+static char _passfile_default[] = _PATH_YP "master.passwd";
+char *passfile_default = _passfile_default;
+char *passfile;
+char *yppasswd_domain = NULL;
+int no_chsh = 0;
+int no_chfn = 0;
+int allow_additions = 0;
+int multidomain = 0;
+int verbose = 0;
+int resvport = 1;
+int inplace = 0;
+char sockname[] = YP_SOCKNAME;
+
+static void
+terminate(int sig __unused)
+{
+ rpcb_unset(YPPASSWDPROG, YPPASSWDVERS, NULL);
+ rpcb_unset(MASTER_YPPASSWDPROG, MASTER_YPPASSWDVERS, NULL);
+ unlink(sockname);
+ exit(0);
+}
+
+static void
+reload(int sig __unused)
+{
+ load_securenets();
+}
+
+static void
+closedown(int sig __unused)
+{
+ if (_rpcsvcstate == _IDLE) {
+ extern fd_set svc_fdset;
+ static int size;
+ int i, openfd;
+
+ if (_rpcfdtype == SOCK_DGRAM) {
+ unlink(sockname);
+ exit(0);
+ }
+ if (size == 0) {
+ size = getdtablesize();
+ }
+ for (i = 0, openfd = 0; i < size && openfd < 2; i++)
+ if (FD_ISSET(i, &svc_fdset))
+ openfd++;
+ if (openfd <= 1) {
+ unlink(sockname);
+ exit(0);
+ }
+ }
+ if (_rpcsvcstate == _SERVED)
+ _rpcsvcstate = _IDLE;
+
+ (void) signal(SIGALRM, (SIG_PF) closedown);
+ (void) alarm(_RPCSVC_CLOSEDOWN/2);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "%s\n%s\n",
+"usage: rpc.yppasswdd [-t master.passwd file] [-d domain] [-p path] [-s]",
+" [-f] [-m] [-i] [-a] [-v] [-u] [-h]");
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct rlimit rlim;
+ SVCXPRT *transp = NULL;
+ struct sockaddr_in saddr;
+ socklen_t asize = sizeof (saddr);
+ struct netconfig *nconf;
+ struct sigaction sa;
+ void *localhandle;
+ int ch;
+ char *mastername;
+ char myname[MAXHOSTNAMELEN + 2];
+ int maxrec = RPC_MAXDATASIZE;
+
+ extern int debug;
+
+ debug = 1;
+
+ while ((ch = getopt(argc, argv, "t:d:p:sfamuivh")) != -1) {
+ switch (ch) {
+ case 't':
+ passfile_default = optarg;
+ break;
+ case 'd':
+ yppasswd_domain = optarg;
+ break;
+ case 's':
+ no_chsh++;
+ break;
+ case 'f':
+ no_chfn++;
+ break;
+ case 'p':
+ yp_dir = optarg;
+ break;
+ case 'a':
+ allow_additions++;
+ break;
+ case 'm':
+ multidomain++;
+ break;
+ case 'i':
+ inplace++;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'u':
+ resvport = 0;
+ break;
+ default:
+ case 'h':
+ usage();
+ break;
+ }
+ }
+
+ if (yppasswd_domain == NULL) {
+ if (yp_get_default_domain(&yppasswd_domain)) {
+ yp_error("no domain specified and system domain \
+name isn't set -- aborting");
+ usage();
+ }
+ }
+
+ load_securenets();
+
+ if (getrpcport(_localhost, YPPROG, YPVERS, IPPROTO_UDP) <= 0) {
+ yp_error("no ypserv processes registered with local portmap");
+ yp_error("this host is not an NIS server -- aborting");
+ exit(1);
+ }
+
+ if ((mastername = ypxfr_get_master(yppasswd_domain,
+ _passwd_byname, _localhost, 0)) == NULL) {
+ yp_error("can't get name of NIS master server for domain %s",
+ yppasswd_domain);
+ exit(1);
+ }
+
+ if (gethostname((char *)&myname, sizeof(myname)) == -1) {
+ yp_error("can't get local hostname: %s", strerror(errno));
+ exit(1);
+ }
+
+ if (strncasecmp(mastername, (char *)&myname, sizeof(myname))) {
+ yp_error("master of %s is %s, but we are %s",
+ "passwd.byname", mastername, myname);
+ yp_error("this host is not the NIS master server for \
+the %s domain -- aborting", yppasswd_domain);
+ exit(1);
+ }
+
+ debug = 0;
+
+ if (getsockname(0, (struct sockaddr *)&saddr, &asize) == 0) {
+ socklen_t ssize = sizeof (int);
+ if (saddr.sin_family != AF_INET)
+ exit(1);
+ if (getsockopt(0, SOL_SOCKET, SO_TYPE,
+ (char *)&_rpcfdtype, &ssize) == -1)
+ exit(1);
+ _rpcpmstart = 1;
+ }
+
+ if (!debug && _rpcpmstart == 0) {
+ if (daemon(0,0)) {
+ err(1,"cannot fork");
+ }
+ }
+ openlog("rpc.yppasswdd", LOG_PID, LOG_DAEMON);
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_flags = SA_NOCLDWAIT;
+ sigaction(SIGCHLD, &sa, NULL);
+
+ rpcb_unset(YPPASSWDPROG, YPPASSWDVERS, NULL);
+ rpcb_unset(MASTER_YPPASSWDPROG, MASTER_YPPASSWDVERS, NULL);
+
+ rpc_control(RPC_SVC_CONNMAXREC_SET, &maxrec);
+
+ if (svc_create(yppasswdprog_1, YPPASSWDPROG, YPPASSWDVERS, "netpath") == 0) {
+ yp_error("cannot create yppasswd service.");
+ exit(1);
+ }
+ if (svc_create(master_yppasswdprog_1, MASTER_YPPASSWDPROG,
+ MASTER_YPPASSWDVERS, "netpath") == 0) {
+ yp_error("cannot create master_yppasswd service.");
+ exit(1);
+ }
+
+ nconf = NULL;
+ localhandle = setnetconfig();
+ while ((nconf = getnetconfig(localhandle)) != NULL) {
+ if (nconf->nc_protofmly != NULL &&
+ strcmp(nconf->nc_protofmly, NC_LOOPBACK) == 0)
+ break;
+ }
+ if (nconf == NULL) {
+ yp_error("getnetconfigent unix: %s", nc_sperror());
+ exit(1);
+ }
+ unlink(sockname);
+ transp = svcunix_create(RPC_ANYSOCK, 0, 0, sockname);
+ if (transp == NULL) {
+ yp_error("cannot create AF_LOCAL service.");
+ exit(1);
+ }
+ if (!svc_reg(transp, MASTER_YPPASSWDPROG, MASTER_YPPASSWDVERS,
+ master_yppasswdprog_1, nconf)) {
+ yp_error("unable to register (MASTER_YPPASSWDPROG, \
+ MASTER_YPPASSWDVERS, unix).");
+ exit(1);
+ }
+ endnetconfig(localhandle);
+
+ /* Only root may connect() to the AF_UNIX link. */
+ if (chmod(sockname, 0))
+ err(1, "chmod of %s failed", sockname);
+
+ if (transp == (SVCXPRT *)NULL) {
+ yp_error("could not create a handle");
+ exit(1);
+ }
+ if (_rpcpmstart) {
+ (void) signal(SIGALRM, (SIG_PF) closedown);
+ (void) alarm(_RPCSVC_CLOSEDOWN/2);
+ }
+
+ /* Unlimited resource limits. */
+ rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
+ (void)setrlimit(RLIMIT_CPU, &rlim);
+ (void)setrlimit(RLIMIT_FSIZE, &rlim);
+ (void)setrlimit(RLIMIT_STACK, &rlim);
+ (void)setrlimit(RLIMIT_DATA, &rlim);
+ (void)setrlimit(RLIMIT_RSS, &rlim);
+
+ /* Don't drop core (not really necessary, but GP's). */
+ rlim.rlim_cur = rlim.rlim_max = 0;
+ (void)setrlimit(RLIMIT_CORE, &rlim);
+
+ /* Turn off signals. */
+ (void)signal(SIGALRM, SIG_IGN);
+ (void)signal(SIGHUP, (SIG_PF) reload);
+ (void)signal(SIGINT, SIG_IGN);
+ (void)signal(SIGPIPE, SIG_IGN);
+ (void)signal(SIGQUIT, SIG_IGN);
+ (void)signal(SIGTERM, (SIG_PF) terminate);
+
+ svc_run();
+ yp_error("svc_run returned");
+ exit(1);
+ /* NOTREACHED */
+}
diff --git a/usr.sbin/rpc.yppasswdd/yppasswdd_server.c b/usr.sbin/rpc.yppasswdd/yppasswdd_server.c
new file mode 100644
index 0000000..9eb874b
--- /dev/null
+++ b/usr.sbin/rpc.yppasswdd/yppasswdd_server.c
@@ -0,0 +1,919 @@
+/*
+ * Copyright (c) 1995, 1996
+ * 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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/fcntl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <ctype.h>
+#include <db.h>
+#include <dirent.h>
+#include <errno.h>
+#include <limits.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libgen.h>
+#include <libutil.h>
+
+#include <rpc/rpc.h>
+#include <rpcsvc/yp.h>
+struct dom_binding;
+#include <rpcsvc/ypclnt.h>
+#include "yppasswdd_extern.h"
+#include "yppasswd.h"
+#include "yppasswd_private.h"
+#include "ypxfr_extern.h"
+#include "yp_extern.h"
+
+static struct passwd yp_password;
+
+static void
+xlate_passwd(struct x_master_passwd *xpwd, struct passwd *pwd)
+{
+ pwd->pw_name = xpwd->pw_name;
+ pwd->pw_passwd = xpwd->pw_passwd;
+ pwd->pw_uid = xpwd->pw_uid;
+ pwd->pw_gid = xpwd->pw_gid;
+ pwd->pw_change = xpwd->pw_change;
+ pwd->pw_class = xpwd->pw_class;
+ pwd->pw_gecos = xpwd->pw_gecos;
+ pwd->pw_dir = xpwd->pw_dir;
+ pwd->pw_shell = xpwd->pw_shell;
+ pwd->pw_expire = xpwd->pw_expire;
+ pwd->pw_fields = xpwd->pw_fields;
+}
+
+static void
+copy_yp_pass(char *p, int x, int m)
+{
+ char *t, *s = p;
+ static char *buf;
+
+ yp_password.pw_fields = 0;
+
+ buf = realloc(buf, m + 10);
+ bzero(buf, m + 10);
+
+ /* Turn all colons into NULLs */
+ while (strchr(s, ':')) {
+ s = (strchr(s, ':') + 1);
+ *(s - 1)= '\0';
+ }
+
+ t = buf;
+#define EXPAND(e) e = t; while ((*t++ = *p++));
+ EXPAND(yp_password.pw_name);
+ yp_password.pw_fields |= _PWF_NAME;
+ EXPAND(yp_password.pw_passwd);
+ yp_password.pw_fields |= _PWF_PASSWD;
+ yp_password.pw_uid = atoi(p);
+ p += (strlen(p) + 1);
+ yp_password.pw_fields |= _PWF_UID;
+ yp_password.pw_gid = atoi(p);
+ p += (strlen(p) + 1);
+ yp_password.pw_fields |= _PWF_GID;
+ if (x) {
+ EXPAND(yp_password.pw_class);
+ yp_password.pw_fields |= _PWF_CLASS;
+ yp_password.pw_change = atol(p);
+ p += (strlen(p) + 1);
+ yp_password.pw_fields |= _PWF_CHANGE;
+ yp_password.pw_expire = atol(p);
+ p += (strlen(p) + 1);
+ yp_password.pw_fields |= _PWF_EXPIRE;
+ }
+ EXPAND(yp_password.pw_gecos);
+ yp_password.pw_fields |= _PWF_GECOS;
+ EXPAND(yp_password.pw_dir);
+ yp_password.pw_fields |= _PWF_DIR;
+ EXPAND(yp_password.pw_shell);
+ yp_password.pw_fields |= _PWF_SHELL;
+
+ return;
+}
+
+static int
+validchars(char *arg)
+{
+ size_t i;
+
+ for (i = 0; i < strlen(arg); i++) {
+ if (iscntrl(arg[i])) {
+ yp_error("string contains a control character");
+ return(1);
+ }
+ if (arg[i] == ':') {
+ yp_error("string contains a colon");
+ return(1);
+ }
+ /* Be evil: truncate strings with \n in them silently. */
+ if (arg[i] == '\n') {
+ arg[i] = '\0';
+ return(0);
+ }
+ }
+ return(0);
+}
+
+static int
+validate_master(struct passwd *opw __unused, struct x_master_passwd *npw)
+{
+
+ if (npw->pw_name[0] == '+' || npw->pw_name[0] == '-') {
+ yp_error("client tried to modify an NIS entry");
+ return(1);
+ }
+
+ if (validchars(npw->pw_shell)) {
+ yp_error("specified shell contains invalid characters");
+ return(1);
+ }
+
+ if (validchars(npw->pw_gecos)) {
+ yp_error("specified gecos field contains invalid characters");
+ return(1);
+ }
+
+ if (validchars(npw->pw_passwd)) {
+ yp_error("specified password contains invalid characters");
+ return(1);
+ }
+ return(0);
+}
+
+static int
+validate(struct passwd *opw, struct x_passwd *npw)
+{
+
+ if (npw->pw_name[0] == '+' || npw->pw_name[0] == '-') {
+ yp_error("client tried to modify an NIS entry");
+ return(1);
+ }
+
+ if ((uid_t)npw->pw_uid != opw->pw_uid) {
+ yp_error("UID mismatch: client says user %s has UID %d",
+ npw->pw_name, npw->pw_uid);
+ yp_error("database says user %s has UID %d", opw->pw_name,
+ opw->pw_uid);
+ return(1);
+ }
+
+ if ((gid_t)npw->pw_gid != opw->pw_gid) {
+ yp_error("GID mismatch: client says user %s has GID %d",
+ npw->pw_name, npw->pw_gid);
+ yp_error("database says user %s has GID %d", opw->pw_name,
+ opw->pw_gid);
+ return(1);
+ }
+
+ /*
+ * Don't allow the user to shoot himself in the foot,
+ * even on purpose.
+ */
+ if (!no_chsh && !ok_shell(npw->pw_shell)) {
+ yp_error("%s is not a valid shell", npw->pw_shell);
+ return(1);
+ }
+
+ if (!no_chsh && validchars(npw->pw_shell)) {
+ yp_error("specified shell contains invalid characters");
+ return(1);
+ }
+
+ if (validchars(npw->pw_gecos)) {
+ yp_error("specified gecos field contains invalid characters");
+ return(1);
+ }
+
+ if (validchars(npw->pw_passwd)) {
+ yp_error("specified password contains invalid characters");
+ return(1);
+ }
+ return(0);
+}
+
+/*
+ * Kludge alert:
+ * In order to have one rpc.yppasswdd support multiple domains,
+ * we have to cheat: we search each directory under /var/yp
+ * and try to match the user in each master.passwd.byname
+ * map that we find. If the user matches (username, uid and gid
+ * all agree), then we use that domain. If we match the user in
+ * more than one database, we must abort.
+ */
+static char *
+find_domain(struct x_passwd *pw)
+{
+ struct stat statbuf;
+ struct dirent *dirp;
+ DIR *dird;
+ char yp_mapdir[MAXPATHLEN + 2];
+ static char domain[YPMAXDOMAIN];
+ char *tmp = NULL;
+ DBT key, data;
+ int hit = 0;
+
+ yp_error("performing multidomain lookup");
+
+ if ((dird = opendir(yp_dir)) == NULL) {
+ yp_error("opendir(%s) failed: %s", yp_dir, strerror(errno));
+ return(NULL);
+ }
+
+ while ((dirp = readdir(dird)) != NULL) {
+ snprintf(yp_mapdir, sizeof yp_mapdir, "%s/%s",
+ yp_dir, dirp->d_name);
+ if (stat(yp_mapdir, &statbuf) < 0) {
+ yp_error("stat(%s) failed: %s", yp_mapdir,
+ strerror(errno));
+ closedir(dird);
+ return(NULL);
+ }
+ if (S_ISDIR(statbuf.st_mode)) {
+ tmp = (char *)dirp->d_name;
+ key.data = pw->pw_name;
+ key.size = strlen(pw->pw_name);
+
+ if (yp_get_record(tmp,"master.passwd.byname",
+ &key, &data, 0) != YP_TRUE) {
+ continue;
+ }
+ *((char *)data.data + data.size) = '\0';
+ copy_yp_pass(data.data, 1, data.size);
+ if (yp_password.pw_uid == (uid_t)pw->pw_uid &&
+ yp_password.pw_gid == (gid_t)pw->pw_gid) {
+ hit++;
+ snprintf(domain, YPMAXDOMAIN, "%s", tmp);
+ }
+ }
+ }
+
+ closedir(dird);
+ if (hit > 1) {
+ yp_error("found same user in two different domains");
+ return(NULL);
+ } else
+ return((char *)&domain);
+}
+
+static const char *maps[] = {
+ "master.passwd.byname",
+ "master.passwd.byuid",
+ "passwd.byname",
+ "passwd.byuid"
+};
+
+static const char *formats[] = {
+ "%s:%s:%d:%d:%s:%ld:%ld:%s:%s:%s",
+ "%s:%s:%d:%d:%s:%ld:%ld:%s:%s:%s",
+ "%s:%s:%d:%d:%s:%s:%s",
+ "%s:%s:%d:%d:%s:%s:%s"
+};
+
+static int
+update_inplace(struct passwd *pw, char *domain)
+{
+ DB *dbp = NULL;
+ DBT key = { NULL, 0 };
+ DBT data = { NULL, 0 };
+ char pwbuf[YPMAXRECORD];
+ char keybuf[20];
+ int i;
+ char *ptr = NULL;
+ static char yp_last[] = "YP_LAST_MODIFIED";
+ char yplastbuf[YPMAXRECORD];
+
+ snprintf(yplastbuf, sizeof yplastbuf, "%llu",
+ (unsigned long long)time(NULL));
+
+ for (i = 0; i < 4; i++) {
+
+ if (i % 2) {
+ snprintf(keybuf, sizeof keybuf,
+ "%llu", (unsigned long long)pw->pw_uid);
+ key.data = &keybuf;
+ key.size = strlen(keybuf);
+ } else {
+ key.data = pw->pw_name;
+ key.size = strlen(pw->pw_name);
+ }
+
+ /*
+ * XXX The passwd.byname and passwd.byuid maps come in
+ * two flavors: secure and insecure. The secure version
+ * has a '*' in the password field whereas the insecure one
+ * has a real crypted password. The maps will be insecure
+ * if they were built with 'unsecure = TRUE' enabled in
+ * /var/yp/Makefile, but we'd have no way of knowing if
+ * this has been done unless we were to try parsing the
+ * Makefile, which is a disgusting thought. Instead, we
+ * read the records from the maps, skip to the first ':'
+ * in them, and then look at the character immediately
+ * following it. If it's an '*' then the map is 'secure'
+ * and we must not insert a real password into the pw_passwd
+ * field. If it's not an '*', then we put the real crypted
+ * password in.
+ */
+ if (yp_get_record(domain,maps[i],&key,&data,1) != YP_TRUE) {
+ yp_error("couldn't read %s/%s: %s", domain,
+ maps[i], strerror(errno));
+ return(1);
+ }
+
+ if ((ptr = strchr(data.data, ':')) == NULL) {
+ yp_error("no colon in passwd record?!");
+ return(1);
+ }
+
+ /*
+ * XXX Supposing we have more than one user with the same
+ * UID? (Or more than one user with the same name?) We could
+ * end up modifying the wrong record if were not careful.
+ */
+ if (i % 2) {
+ if (strncmp(data.data, pw->pw_name,
+ strlen(pw->pw_name))) {
+ yp_error("warning: found entry for UID %d \
+in map %s@%s with wrong name (%.*s)", pw->pw_uid, maps[i], domain,
+ (int)(ptr - (char *)data.data),
+ (char *)data.data);
+ yp_error("there may be more than one user \
+with the same UID - continuing");
+ continue;
+ }
+ } else {
+ /*
+ * We're really being ultra-paranoid here.
+ * This is generally a 'can't happen' condition.
+ */
+ snprintf(pwbuf, sizeof pwbuf, ":%d:%d:", pw->pw_uid,
+ pw->pw_gid);
+ if (!strstr(data.data, pwbuf)) {
+ yp_error("warning: found entry for user %s \
+in map %s@%s with wrong UID", pw->pw_name, maps[i], domain);
+ yp_error("there may be more than one user \
+with the same name - continuing");
+ continue;
+ }
+ }
+
+ if (i < 2) {
+ snprintf(pwbuf, sizeof pwbuf, formats[i],
+ pw->pw_name, pw->pw_passwd, pw->pw_uid,
+ pw->pw_gid, pw->pw_class, pw->pw_change,
+ pw->pw_expire, pw->pw_gecos, pw->pw_dir,
+ pw->pw_shell);
+ } else {
+ snprintf(pwbuf, sizeof pwbuf, formats[i],
+ pw->pw_name, *(ptr+1) == '*' ? "*" : pw->pw_passwd,
+ pw->pw_uid, pw->pw_gid, pw->pw_gecos, pw->pw_dir,
+ pw->pw_shell);
+ }
+
+#define FLAGS O_RDWR|O_CREAT
+
+ if ((dbp = yp_open_db_rw(domain, maps[i], FLAGS)) == NULL) {
+ yp_error("couldn't open %s/%s r/w: %s",domain,
+ maps[i],strerror(errno));
+ return(1);
+ }
+
+ data.data = pwbuf;
+ data.size = strlen(pwbuf);
+
+ if (yp_put_record(dbp, &key, &data, 1) != YP_TRUE) {
+ yp_error("failed to update record in %s/%s", domain,
+ maps[i]);
+ (void)(dbp->close)(dbp);
+ return(1);
+ }
+
+ key.data = yp_last;
+ key.size = strlen(yp_last);
+ data.data = (char *)&yplastbuf;
+ data.size = strlen(yplastbuf);
+
+ if (yp_put_record(dbp, &key, &data, 1) != YP_TRUE) {
+ yp_error("failed to update timestamp in %s/%s", domain,
+ maps[i]);
+ (void)(dbp->close)(dbp);
+ return(1);
+ }
+
+ (void)(dbp->close)(dbp);
+ }
+
+ return(0);
+}
+
+int *
+yppasswdproc_update_1_svc(yppasswd *argp, struct svc_req *rqstp)
+{
+ static int result;
+ struct sockaddr_in *rqhost;
+ DBT key, data;
+ int rval = 0;
+ int pfd, tfd;
+ int pid;
+ int passwd_changed = 0;
+ int shell_changed = 0;
+ int gecos_changed = 0;
+ char *cryptpw;
+ char *oldshell = NULL;
+ char *oldgecos = NULL;
+ char *passfile_hold;
+ char passfile_buf[MAXPATHLEN + 2];
+ char passfile_hold_buf[MAXPATHLEN + 2];
+ char *domain = yppasswd_domain;
+ static struct sockaddr_in clntaddr;
+ static struct timeval t_saved, t_test;
+
+ /*
+ * Normal user updates always use the 'default' master.passwd file.
+ */
+
+ passfile = passfile_default;
+ result = 1;
+
+ rqhost = svc_getcaller(rqstp->rq_xprt);
+
+ gettimeofday(&t_test, NULL);
+ if (!bcmp(rqhost, &clntaddr, sizeof *rqhost) &&
+ t_test.tv_sec > t_saved.tv_sec &&
+ t_test.tv_sec - t_saved.tv_sec < 300) {
+
+ bzero(&clntaddr, sizeof clntaddr);
+ bzero(&t_saved, sizeof t_saved);
+ return(NULL);
+ }
+
+ bcopy(rqhost, &clntaddr, sizeof clntaddr);
+ gettimeofday(&t_saved, NULL);
+
+ if (yp_access(resvport ? "master.passwd.byname" : NULL, rqstp)) {
+ yp_error("rejected update request from unauthorized host");
+ svcerr_auth(rqstp->rq_xprt, AUTH_BADCRED);
+ return(&result);
+ }
+
+ /*
+ * Step one: find the user. (It's kinda pointless to
+ * proceed if the user doesn't exist.) We look for the
+ * user in the master.passwd.byname database, _NOT_ by
+ * using getpwent() and friends! We can't use getpwent()
+ * since the NIS master server is not guaranteed to be
+ * configured as an NIS client.
+ */
+
+ if (multidomain) {
+ if ((domain = find_domain(&argp->newpw)) == NULL) {
+ yp_error("multidomain lookup failed - aborting update");
+ return(&result);
+ } else
+ yp_error("updating user %s in domain %s",
+ argp->newpw.pw_name, domain);
+ }
+
+ key.data = argp->newpw.pw_name;
+ key.size = strlen(argp->newpw.pw_name);
+
+ if ((rval = yp_get_record(domain,"master.passwd.byname",
+ &key, &data, 0)) != YP_TRUE) {
+ if (rval == YP_NOKEY) {
+ yp_error("user %s not found in passwd database",
+ argp->newpw.pw_name);
+ } else {
+ yp_error("database access error: %s",
+ yperr_string(rval));
+ }
+ return(&result);
+ }
+
+ /* Nul terminate, please. */
+ *((char *)data.data + data.size) = '\0';
+
+ copy_yp_pass(data.data, 1, data.size);
+
+ /* Step 2: check that the supplied oldpass is valid. */
+
+ cryptpw = crypt(argp->oldpass, yp_password.pw_passwd);
+ if (cryptpw == NULL || strcmp(cryptpw, yp_password.pw_passwd)) {
+ yp_error("rejected change attempt -- bad password");
+ yp_error("client address: %s username: %s",
+ inet_ntoa(rqhost->sin_addr),
+ argp->newpw.pw_name);
+ return(&result);
+ }
+
+ /* Step 3: validate the arguments passed to us by the client. */
+
+ if (validate(&yp_password, &argp->newpw)) {
+ yp_error("rejecting change attempt: bad arguments");
+ yp_error("client address: %s username: %s",
+ inet_ntoa(rqhost->sin_addr),
+ argp->newpw.pw_name);
+ svcerr_decode(rqstp->rq_xprt);
+ return(&result);
+ }
+
+ /* Step 4: update the user's passwd structure. */
+
+ if (!no_chsh && strcmp(argp->newpw.pw_shell, yp_password.pw_shell)) {
+ oldshell = yp_password.pw_shell;
+ yp_password.pw_shell = argp->newpw.pw_shell;
+ shell_changed++;
+ }
+
+
+ if (!no_chfn && strcmp(argp->newpw.pw_gecos, yp_password.pw_gecos)) {
+ oldgecos = yp_password.pw_gecos;
+ yp_password.pw_gecos = argp->newpw.pw_gecos;
+ gecos_changed++;
+ }
+
+ if (strcmp(argp->newpw.pw_passwd, yp_password.pw_passwd)) {
+ yp_password.pw_passwd = argp->newpw.pw_passwd;
+ yp_password.pw_change = 0;
+ passwd_changed++;
+ }
+
+ /*
+ * If the caller specified a domain other than our 'default'
+ * domain, change the path to master.passwd accordingly.
+ */
+
+ if (strcmp(domain, yppasswd_domain)) {
+ snprintf(passfile_buf, sizeof(passfile_buf),
+ "%s/%s/master.passwd", yp_dir, domain);
+ passfile = (char *)&passfile_buf;
+ }
+
+ /*
+ * Create a filename to hold the original master.passwd
+ * so if our call to yppwupdate fails we can roll back
+ */
+ snprintf(passfile_hold_buf, sizeof(passfile_hold_buf),
+ "%s.hold", passfile);
+ passfile_hold = (char *)&passfile_hold_buf;
+
+
+ /* Step 5: make a new password file with the updated info. */
+
+ if (pw_init(dirname(passfile), passfile)) {
+ yp_error("pw_init() failed");
+ return &result;
+ }
+ if ((pfd = pw_lock()) == -1) {
+ pw_fini();
+ yp_error("pw_lock() failed");
+ return &result;
+ }
+ if ((tfd = pw_tmp(-1)) == -1) {
+ pw_fini();
+ yp_error("pw_tmp() failed");
+ return &result;
+ }
+ if (pw_copy(pfd, tfd, &yp_password, NULL) == -1) {
+ pw_fini();
+ yp_error("pw_copy() failed");
+ return &result;
+ }
+ if (rename(passfile, passfile_hold) == -1) {
+ pw_fini();
+ yp_error("rename of %s to %s failed", passfile,
+ passfile_hold);
+ return &result;
+ }
+
+ if (strcmp(passfile, _PATH_MASTERPASSWD) == 0) {
+ /*
+ * NIS server is exporting the system's master.passwd.
+ * Call pw_mkdb to rebuild passwd and the .db files
+ */
+ if (pw_mkdb(yp_password.pw_name) == -1) {
+ pw_fini();
+ yp_error("pw_mkdb() failed");
+ rename(passfile_hold, passfile);
+ return &result;
+ }
+ } else {
+ /*
+ * NIS server is exporting a private master.passwd.
+ * Rename tempfile into final location
+ */
+ if (rename(pw_tempname(), passfile) == -1) {
+ pw_fini();
+ yp_error("rename of %s to %s failed",
+ pw_tempname(), passfile);
+ rename(passfile_hold, passfile);
+ return &result;
+ }
+ }
+
+ pw_fini();
+
+ if (inplace) {
+ if ((rval = update_inplace(&yp_password, domain))) {
+ yp_error("inplace update failed -- rebuilding maps");
+ }
+ }
+
+ switch ((pid = fork())) {
+ case 0:
+ if (inplace && !rval) {
+ execlp(MAP_UPDATE_PATH, MAP_UPDATE, passfile,
+ yppasswd_domain, "pushpw", (char *)NULL);
+ } else {
+ execlp(MAP_UPDATE_PATH, MAP_UPDATE, passfile,
+ yppasswd_domain, (char *)NULL);
+ }
+ yp_error("couldn't exec map update process: %s",
+ strerror(errno));
+ unlink(passfile);
+ rename(passfile_hold, passfile);
+ exit(1);
+ break;
+ case -1:
+ yp_error("fork() failed: %s", strerror(errno));
+ unlink(passfile);
+ rename(passfile_hold, passfile);
+ return(&result);
+ break;
+ default:
+ unlink(passfile_hold);
+ break;
+ }
+
+ if (verbose) {
+ yp_error("update completed for user %s (uid %d) in %s:",
+ argp->newpw.pw_name, argp->newpw.pw_uid, passfile);
+
+ if (passwd_changed)
+ yp_error("password changed");
+
+ if (gecos_changed)
+ yp_error("gecos changed ('%s' -> '%s')",
+ oldgecos, argp->newpw.pw_gecos);
+
+ if (shell_changed)
+ yp_error("shell changed ('%s' -> '%s')",
+ oldshell, argp->newpw.pw_shell);
+ }
+
+ result = 0;
+ return (&result);
+}
+
+/*
+ * Note that this function performs a little less sanity checking
+ * than the last one. Since only the superuser is allowed to use it,
+ * it is assumed that the caller knows what he's doing.
+ */
+int *
+yppasswdproc_update_master_1_svc(master_yppasswd *argp,
+ struct svc_req *rqstp)
+{
+ static int result;
+ int pfd, tfd;
+ int pid;
+ uid_t uid;
+ int rval = 0;
+ DBT key, data;
+ char *passfile_hold;
+ char passfile_buf[MAXPATHLEN + 2];
+ char passfile_hold_buf[MAXPATHLEN + 2];
+ struct sockaddr_in *rqhost;
+ SVCXPRT *transp;
+ struct passwd newpasswd;
+
+ result = 1;
+ transp = rqstp->rq_xprt;
+
+ /*
+ * NO AF_INET CONNETCIONS ALLOWED!
+ */
+ rqhost = svc_getcaller(transp);
+ if (rqhost->sin_family != AF_UNIX) {
+ yp_error("Alert! %s/%d attempted to use superuser-only \
+procedure!\n", inet_ntoa(rqhost->sin_addr), rqhost->sin_port);
+ svcerr_auth(transp, AUTH_BADCRED);
+ return(&result);
+ }
+
+ if (rqstp->rq_cred.oa_flavor != AUTH_SYS) {
+ yp_error("caller didn't send proper credentials");
+ svcerr_auth(transp, AUTH_BADCRED);
+ return(&result);
+ }
+
+ if (__rpc_get_local_uid(transp, &uid) < 0) {
+ yp_error("caller didn't send proper credentials");
+ svcerr_auth(transp, AUTH_BADCRED);
+ return(&result);
+ }
+
+ if (uid) {
+ yp_error("caller euid is %d, expecting 0 -- rejecting request",
+ uid);
+ svcerr_auth(rqstp->rq_xprt, AUTH_BADCRED);
+ return(&result);
+ }
+
+ passfile = passfile_default;
+
+ key.data = argp->newpw.pw_name;
+ key.size = strlen(argp->newpw.pw_name);
+
+ /*
+ * The superuser may add entries to the passwd maps if
+ * rpc.yppasswdd is started with the -a flag. Paranoia
+ * prevents me from allowing additions by default.
+ */
+ if ((rval = yp_get_record(argp->domain, "master.passwd.byname",
+ &key, &data, 0)) != YP_TRUE) {
+ if (rval == YP_NOKEY) {
+ yp_error("user %s not found in passwd database",
+ argp->newpw.pw_name);
+ if (allow_additions)
+ yp_error("notice: adding user %s to \
+master.passwd database for domain %s", argp->newpw.pw_name, argp->domain);
+ else
+ yp_error("restart rpc.yppasswdd with the -a flag to \
+allow additions to be made to the password database");
+ } else {
+ yp_error("database access error: %s",
+ yperr_string(rval));
+ }
+ if (!allow_additions)
+ return(&result);
+ } else {
+
+ /* Nul terminate, please. */
+ *((char *)data.data + data.size) = '\0';
+
+ copy_yp_pass(data.data, 1, data.size);
+ }
+
+ /*
+ * Perform a small bit of sanity checking.
+ */
+ if (validate_master(rval == YP_TRUE ? &yp_password:NULL,&argp->newpw)){
+ yp_error("rejecting update attempt for %s: bad arguments",
+ argp->newpw.pw_name);
+ return(&result);
+ }
+
+ /*
+ * If the caller specified a domain other than our 'default'
+ * domain, change the path to master.passwd accordingly.
+ */
+
+ if (strcmp(argp->domain, yppasswd_domain)) {
+ snprintf(passfile_buf, sizeof(passfile_buf),
+ "%s/%s/master.passwd", yp_dir, argp->domain);
+ passfile = (char *)&passfile_buf;
+ }
+
+ /*
+ * Create a filename to hold the original master.passwd
+ * so if our call to yppwupdate fails we can roll back
+ */
+ snprintf(passfile_hold_buf, sizeof(passfile_hold_buf),
+ "%s.hold", passfile);
+ passfile_hold = (char *)&passfile_hold_buf;
+
+ if (pw_init(dirname(passfile), passfile)) {
+ yp_error("pw_init() failed");
+ return &result;
+ }
+ if ((pfd = pw_lock()) == -1) {
+ pw_fini();
+ yp_error("pw_lock() failed");
+ return &result;
+ }
+ if ((tfd = pw_tmp(-1)) == -1) {
+ pw_fini();
+ yp_error("pw_tmp() failed");
+ return &result;
+ }
+ xlate_passwd(&argp->newpw, &newpasswd);
+ if (pw_copy(pfd, tfd, &newpasswd, NULL) == -1) {
+ pw_fini();
+ yp_error("pw_copy() failed");
+ return &result;
+ }
+ if (rename(passfile, passfile_hold) == -1) {
+ pw_fini();
+ yp_error("rename of %s to %s failed", passfile,
+ passfile_hold);
+ return &result;
+ }
+ if (strcmp(passfile, _PATH_MASTERPASSWD) == 0) {
+ /*
+ * NIS server is exporting the system's master.passwd.
+ * Call pw_mkdb to rebuild passwd and the .db files
+ */
+ if (pw_mkdb(argp->newpw.pw_name) == -1) {
+ pw_fini();
+ yp_error("pw_mkdb() failed");
+ rename(passfile_hold, passfile);
+ return &result;
+ }
+ } else {
+ /*
+ * NIS server is exporting a private master.passwd.
+ * Rename tempfile into final location
+ */
+ if (rename(pw_tempname(), passfile) == -1) {
+ pw_fini();
+ yp_error("rename of %s to %s failed",
+ pw_tempname(), passfile);
+ rename(passfile_hold, passfile);
+ return &result;
+ }
+ }
+ pw_fini();
+
+ if (inplace) {
+ xlate_passwd(&argp->newpw, &newpasswd);
+ if ((rval = update_inplace(&newpasswd, argp->domain))) {
+ yp_error("inplace update failed -- rebuilding maps");
+ }
+ }
+
+ switch ((pid = fork())) {
+ case 0:
+ if (inplace && !rval) {
+ execlp(MAP_UPDATE_PATH, MAP_UPDATE, passfile,
+ argp->domain, "pushpw", (char *)NULL);
+ } else {
+ execlp(MAP_UPDATE_PATH, MAP_UPDATE, passfile,
+ argp->domain, (char *)NULL);
+ }
+ yp_error("couldn't exec map update process: %s",
+ strerror(errno));
+ unlink(passfile);
+ rename(passfile_hold, passfile);
+ exit(1);
+ break;
+ case -1:
+ yp_error("fork() failed: %s", strerror(errno));
+ unlink(passfile);
+ rename(passfile_hold, passfile);
+ return(&result);
+ break;
+ default:
+ unlink(passfile_hold);
+ break;
+ }
+
+ yp_error("performed update of user %s (uid %d) domain %s",
+ argp->newpw.pw_name,
+ argp->newpw.pw_uid,
+ argp->domain);
+
+ result = 0;
+ return(&result);
+}
diff --git a/usr.sbin/rpc.yppasswdd/yppwupdate b/usr.sbin/rpc.yppasswdd/yppwupdate
new file mode 100644
index 0000000..a9db8f7
--- /dev/null
+++ b/usr.sbin/rpc.yppasswdd/yppwupdate
@@ -0,0 +1,34 @@
+#!/bin/sh
+#
+# This script is invoked by rpc.yppasswdd to update the password
+# maps after the master password file has been modified. It expects
+# to be passed two arguments: the name of the master.passwd template
+# file that was modified by the server, and the name of the domain to
+# update. These are passed to /var/yp/Makefile.
+#
+# Comment out the LOG=yes line to disable logging.
+#
+# $FreeBSD$
+#
+
+PATH=/bin:/usr/bin; export PATH
+LOG=yes
+LOGFILE=/var/yp/ypupdate.log
+
+umask 077
+
+if [ ! -f $LOGFILE ];
+then
+ touch $LOGFILE
+ echo "# Edit /usr/libexec/yppwupdate to disable" >> $LOGFILE
+ echo "# logging to this file from yppasswdd." >> $LOGFILE
+ echo -n "# Log started on: " >> $LOGFILE
+ date >> $LOGFILE
+fi
+
+if [ ! $LOG ];
+then
+ cd /var/yp && make MASTER_PASSWD=$1 UPDATE_DOMAIN=$2 $3 2>&1
+else
+ cd /var/yp && make MASTER_PASSWD=$1 UPDATE_DOMAIN=$2 $3 >> $LOGFILE 2>&1
+fi
diff --git a/usr.sbin/rpc.ypupdated/Makefile b/usr.sbin/rpc.ypupdated/Makefile
new file mode 100644
index 0000000..d5c346e
--- /dev/null
+++ b/usr.sbin/rpc.ypupdated/Makefile
@@ -0,0 +1,33 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../ypserv ${.CURDIR}/../../libexec/ypxfr
+
+PROG= rpc.ypupdated
+MAN=
+SRCS= ypupdate_prot_svc.c ypupdate_prot.h ypupdated_main.c \
+ yp_error.c update.c ypupdated_server.c \
+ yp_dblookup.c yp_dbwrite.c yp_dbdelete.c yp_dbupdate.c
+
+#CFLAGS+= -DYP
+CFLAGS+= -I${.CURDIR}/../ypserv -I. -I${.CURDIR}/../../libexec/ypxfr
+
+WARNS?= 1
+
+LIBADD= rpcsvc
+
+CLEANFILES= ypupdate_prot_svc.c ypupdate_prot.h
+
+RPCDIR= ${DESTDIR}/usr/include/rpcsvc
+RPCGEN= RPCGEN_CPP=${CPP:Q} rpcgen -I -C
+
+# We need to remove the 'static' keyword from _rpcsvcstate so that
+# ypupdated_main.c can see it.
+ypupdate_prot_svc.c: ${RPCDIR}/ypupdate_prot.x
+ rm -f ${.TARGET}
+ ${RPCGEN} -m ${RPCDIR}/ypupdate_prot.x | \
+ sed s/"static int _rpcsvcstate"/"int _rpcsvcstate"/g > ${.TARGET}
+
+ypupdate_prot.h: ${RPCDIR}/ypupdate_prot.x
+ ${RPCGEN} -h -o ${.TARGET} ${RPCDIR}/ypupdate_prot.x
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/rpc.ypupdated/Makefile.depend b/usr.sbin/rpc.ypupdated/Makefile.depend
new file mode 100644
index 0000000..11b2940
--- /dev/null
+++ b/usr.sbin/rpc.ypupdated/Makefile.depend
@@ -0,0 +1,29 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/librpcsvc \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+ypupdate_prot_svc.o: ypupdate_prot.h
+ypupdate_prot_svc.o: ypupdate_prot_svc.c
+ypupdate_prot_svc.po: ypupdate_prot.h
+ypupdate_prot_svc.po: ypupdate_prot_svc.c
+ypupdated_main.o: ypupdate_prot.h
+ypupdated_main.po: ypupdate_prot.h
+ypupdated_server.o: ypupdate_prot.h
+ypupdated_server.po: ypupdate_prot.h
+.endif
diff --git a/usr.sbin/rpc.ypupdated/update.c b/usr.sbin/rpc.ypupdated/update.c
new file mode 100644
index 0000000..956b057
--- /dev/null
+++ b/usr.sbin/rpc.ypupdated/update.c
@@ -0,0 +1,329 @@
+/*
+ * Sun RPC is a product of Sun Microsystems, Inc. and is provided for
+ * unrestricted use provided that this legend is included on all tape
+ * media and as a part of the software program in whole or part. Users
+ * may copy or modify Sun RPC without charge, but are not authorized
+ * to license or distribute it to anyone else except as part of a product or
+ * program developed by the user or with the express written consent of
+ * Sun Microsystems, Inc.
+ *
+ * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
+ * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
+ *
+ * Sun RPC is provided with no support and without any obligation on the
+ * part of Sun Microsystems, Inc. to assist in its use, correction,
+ * modification or enhancement.
+ *
+ * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
+ * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC
+ * OR ANY PART THEREOF.
+ *
+ * In no event will Sun Microsystems, Inc. be liable for any lost revenue
+ * or profits or other special, indirect and consequential damages, even if
+ * Sun has been advised of the possibility of such damages.
+ *
+ * Sun Microsystems, Inc.
+ * 2550 Garcia Avenue
+ * Mountain View, California 94043
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)update.c 1.2 91/03/11 Copyr 1986 Sun Micro";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+/*
+ * Copyright (C) 1986, 1989, Sun Microsystems, Inc.
+ */
+
+/*
+ * Administrative tool to add a new user to the publickey database
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <rpc/rpc.h>
+#include <rpc/key_prot.h>
+#ifdef YP
+#include <rpcsvc/yp_prot.h>
+#include <rpcsvc/ypclnt.h>
+#include <sys/wait.h>
+#include <netdb.h>
+#endif /* YP */
+#include <pwd.h>
+#include <string.h>
+#include <sys/resource.h>
+#include "ypupdated_extern.h"
+
+#ifdef YP
+#define MAXMAPNAMELEN 256
+#else
+#define YPOP_CHANGE 1 /* change, do not add */
+#define YPOP_INSERT 2 /* add, do not change */
+#define YPOP_DELETE 3 /* delete this entry */
+#define YPOP_STORE 4 /* add, or change */
+#endif
+
+#ifdef YP
+static char SHELL[] = "/bin/sh";
+static char YPDBPATH[]="/var/yp"; /* This is defined but not used! */
+static char PKMAP[] = "publickey.byname";
+static char UPDATEFILE[] = "updaters";
+static char PKFILE[] = "/etc/publickey";
+#endif /* YP */
+
+#ifdef YP
+static int _openchild(char *, FILE **, FILE **);
+
+/*
+ * Determine if requester is allowed to update the given map,
+ * and update it if so. Returns the yp status, which is zero
+ * if there is no access violation.
+ */
+int
+mapupdate(char *requester, char *mapname, u_int op, u_int keylen, char *key,
+ u_int datalen, char *data)
+{
+ char updater[MAXMAPNAMELEN + 40];
+ FILE *childargs;
+ FILE *childrslt;
+#ifdef WEXITSTATUS
+ int status;
+#else
+ union wait status;
+#endif
+ pid_t pid;
+ u_int yperrno;
+
+
+#ifdef DEBUG
+ printf("%s %s\n", key, data);
+#endif
+ (void)sprintf(updater, "make -s -f %s/%s %s", YPDBPATH, /* !!! */
+ UPDATEFILE, mapname);
+ pid = _openchild(updater, &childargs, &childrslt);
+ if (pid < 0) {
+ return (YPERR_YPERR);
+ }
+
+ /*
+ * Write to child
+ */
+ (void)fprintf(childargs, "%s\n", requester);
+ (void)fprintf(childargs, "%u\n", op);
+ (void)fprintf(childargs, "%u\n", keylen);
+ (void)fwrite(key, (int)keylen, 1, childargs);
+ (void)fprintf(childargs, "\n");
+ (void)fprintf(childargs, "%u\n", datalen);
+ (void)fwrite(data, (int)datalen, 1, childargs);
+ (void)fprintf(childargs, "\n");
+ (void)fclose(childargs);
+
+ /*
+ * Read from child
+ */
+ (void)fscanf(childrslt, "%d", &yperrno);
+ (void)fclose(childrslt);
+
+ (void)wait(&status);
+#ifdef WEXITSTATUS
+ if (WEXITSTATUS(status) != 0)
+#else
+ if (status.w_retcode != 0)
+#endif
+ return (YPERR_YPERR);
+ return (yperrno);
+}
+
+/*
+ * returns pid, or -1 for failure
+ */
+static int
+_openchild(char *command, FILE **fto, FILE **ffrom)
+{
+ int i;
+ pid_t pid;
+ int pdto[2];
+ int pdfrom[2];
+ char *com;
+ struct rlimit rl;
+
+ if (pipe(pdto) < 0) {
+ goto error1;
+ }
+ if (pipe(pdfrom) < 0) {
+ goto error2;
+ }
+ switch (pid = fork()) {
+ case -1:
+ goto error3;
+
+ case 0:
+ /*
+ * child: read from pdto[0], write into pdfrom[1]
+ */
+ (void)close(0);
+ (void)dup(pdto[0]);
+ (void)close(1);
+ (void)dup(pdfrom[1]);
+ getrlimit(RLIMIT_NOFILE, &rl);
+ for (i = rl.rlim_max - 1; i >= 3; i--) {
+ (void) close(i);
+ }
+ com = malloc((unsigned) strlen(command) + 6);
+ if (com == NULL) {
+ _exit(~0);
+ }
+ (void)sprintf(com, "exec %s", command);
+ execl(SHELL, basename(SHELL), "-c", com, (char *)NULL);
+ _exit(~0);
+
+ default:
+ /*
+ * parent: write into pdto[1], read from pdfrom[0]
+ */
+ *fto = fdopen(pdto[1], "w");
+ (void)close(pdto[0]);
+ *ffrom = fdopen(pdfrom[0], "r");
+ (void)close(pdfrom[1]);
+ break;
+ }
+ return (pid);
+
+ /*
+ * error cleanup and return
+ */
+error3:
+ (void)close(pdfrom[0]);
+ (void)close(pdfrom[1]);
+error2:
+ (void)close(pdto[0]);
+ (void)close(pdto[1]);
+error1:
+ return (-1);
+}
+
+static char *
+basename(char *path)
+{
+ char *p;
+
+ p = strrchr(path, '/');
+ if (p == NULL) {
+ return (path);
+ } else {
+ return (p + 1);
+ }
+}
+
+#else /* YP */
+
+static int match(char *, char *);
+
+/*
+ * Determine if requester is allowed to update the given map,
+ * and update it if so. Returns the status, which is zero
+ * if there is no access violation. This function updates
+ * the local file and then shuts up.
+ */
+int
+localupdate(char *name, char *filename, u_int op, u_int keylen __unused,
+ char *key, u_int datalen __unused, char *data)
+{
+ char line[256];
+ FILE *rf;
+ FILE *wf;
+ char *tmpname;
+ int err;
+
+ /*
+ * Check permission
+ */
+ if (strcmp(name, key) != 0) {
+ return (ERR_ACCESS);
+ }
+ if (strcmp(name, "nobody") == 0) {
+ /*
+ * Can't change "nobody"s key.
+ */
+ return (ERR_ACCESS);
+ }
+
+ /*
+ * Open files
+ */
+ tmpname = malloc(strlen(filename) + 4);
+ if (tmpname == NULL) {
+ return (ERR_MALLOC);
+ }
+ sprintf(tmpname, "%s.tmp", filename);
+ rf = fopen(filename, "r");
+ if (rf == NULL) {
+ return (ERR_READ);
+ }
+ wf = fopen(tmpname, "w");
+ if (wf == NULL) {
+ return (ERR_WRITE);
+ }
+ err = -1;
+ while (fgets(line, sizeof (line), rf)) {
+ if (err < 0 && match(line, name)) {
+ switch (op) {
+ case YPOP_INSERT:
+ err = ERR_KEY;
+ break;
+ case YPOP_STORE:
+ case YPOP_CHANGE:
+ fprintf(wf, "%s %s\n", key, data);
+ err = 0;
+ break;
+ case YPOP_DELETE:
+ /* do nothing */
+ err = 0;
+ break;
+ }
+ } else {
+ fputs(line, wf);
+ }
+ }
+ if (err < 0) {
+ switch (op) {
+ case YPOP_CHANGE:
+ case YPOP_DELETE:
+ err = ERR_KEY;
+ break;
+ case YPOP_INSERT:
+ case YPOP_STORE:
+ err = 0;
+ fprintf(wf, "%s %s\n", key, data);
+ break;
+ }
+ }
+ fclose(wf);
+ fclose(rf);
+ if (err == 0) {
+ if (rename(tmpname, filename) < 0) {
+ return (ERR_DBASE);
+ }
+ } else {
+ if (unlink(tmpname) < 0) {
+ return (ERR_DBASE);
+ }
+ }
+ return (err);
+}
+
+static int
+match(char *line, char *name)
+{
+ int len;
+
+ len = strlen(name);
+ return (strncmp(line, name, len) == 0 &&
+ (line[len] == ' ' || line[len] == '\t'));
+}
+#endif /* !YP */
diff --git a/usr.sbin/rpc.ypupdated/yp_dbdelete.c b/usr.sbin/rpc.ypupdated/yp_dbdelete.c
new file mode 100644
index 0000000..0552aab
--- /dev/null
+++ b/usr.sbin/rpc.ypupdated/yp_dbdelete.c
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 1995, 1996
+ * 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <stdlib.h>
+#include <fcntl.h>
+#include <string.h>
+#include <limits.h>
+#include <unistd.h>
+#include <db.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <paths.h>
+#include <rpcsvc/yp.h>
+#include "ypxfr_extern.h"
+
+int
+yp_del_record(DB *dbp, DBT *key)
+{
+ int rval;
+
+ if ((rval = (dbp->del)(dbp,key,0))) {
+ switch (rval) {
+ case 1:
+ return(YP_FALSE);
+ break;
+ case -1:
+ default:
+ (void)(dbp->close)(dbp);
+ return(YP_BADDB);
+ break;
+ }
+ }
+
+ return(YP_TRUE);
+}
diff --git a/usr.sbin/rpc.ypupdated/yp_dbupdate.c b/usr.sbin/rpc.ypupdated/yp_dbupdate.c
new file mode 100644
index 0000000..3481a6b
--- /dev/null
+++ b/usr.sbin/rpc.ypupdated/yp_dbupdate.c
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 1996
+ * 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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/fcntl.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <db.h>
+#include <unistd.h>
+#include <rpcsvc/ypclnt.h>
+#include <rpcsvc/ypupdate_prot.h>
+#include "ypxfr_extern.h"
+#include "ypupdated_extern.h"
+
+static int
+yp_domake(char *map, char *domain)
+{
+ int pid;
+
+ switch ((pid = fork())) {
+ case 0:
+ execlp(MAP_UPDATE_PATH, MAP_UPDATE, map, domain, (char *)NULL);
+ yp_error("couldn't exec map update process: %s",
+ strerror(errno));
+ exit(1);
+ break;
+ case -1:
+ yp_error("fork() failed: %s", strerror(errno));
+ return(YPERR_YPERR);
+ break;
+ default:
+ children++;
+ break;
+ }
+
+ return(0);
+}
+
+int
+ypmap_update(char *netname, char *map, unsigned int op, unsigned int keylen,
+ char *keyval, unsigned int datlen, char *datval)
+{
+ DB *dbp;
+ DBT key = { NULL, 0 }, data = { NULL, 0 };
+ char *yp_last = "YP_LAST_MODIFIED";
+ char yplastbuf[YPMAXRECORD];
+ char *domptr;
+ int rval = 0;
+
+ if ((domptr = strchr(netname, '@')) == NULL)
+ return(ERR_ACCESS);
+ domptr++;
+
+
+ dbp = yp_open_db_rw(domptr, map, O_RDWR);
+ if (dbp == NULL)
+ return(ERR_DBASE);
+
+ key.data = keyval;
+ key.size = keylen;
+ data.data = datval;
+ data.size = datlen;
+
+ switch (op) {
+ case YPOP_DELETE: /* delete this entry */
+ rval = yp_del_record(dbp, &key);
+ if (rval == YP_TRUE)
+ rval = 0;
+ break;
+ case YPOP_INSERT: /* add, do not change */
+ rval = yp_put_record(dbp, &key, &data, 0);
+ if (rval == YP_TRUE)
+ rval = 0;
+ break;
+ case YPOP_STORE: /* add, or change */
+ rval = yp_put_record(dbp, &key, &data, 1);
+ if (rval == YP_TRUE)
+ rval = 0;
+ break;
+ case YPOP_CHANGE: /* change, do not add */
+ if (yp_get_record(domptr, map, &key, &data, 0) != YP_TRUE) {
+ rval = ERR_KEY;
+ break;
+ }
+ rval = yp_put_record(dbp, &key, &data, 1);
+ if (rval == YP_TRUE)
+ rval = 0;
+ break;
+ default:
+ yp_error("unknown update command: (%d)", op);
+ }
+
+ if (rval) {
+ (void)(dbp->close)(dbp);
+ return(rval);
+ }
+
+ snprintf(yplastbuf, sizeof(yplastbuf), "%jd", (intmax_t)time(NULL));
+ key.data = yp_last;
+ key.size = strlen(yp_last);
+ data.data = (char *)&yplastbuf;
+ data.size = strlen(yplastbuf);
+ if (yp_put_record(dbp, &key, &data, 1) != YP_TRUE) {
+ yp_error("failed to update timestamp in %s/%s", domptr, map);
+ (void)(dbp->close)(dbp);
+ return(ERR_DBASE);
+ }
+
+ (void)(dbp->close)(dbp);
+ return(yp_domake(map, domptr));
+}
diff --git a/usr.sbin/rpc.ypupdated/ypupdate b/usr.sbin/rpc.ypupdated/ypupdate
new file mode 100755
index 0000000..4a26f4b
--- /dev/null
+++ b/usr.sbin/rpc.ypupdated/ypupdate
@@ -0,0 +1,33 @@
+#!/bin/sh
+#
+# This script is invoked by rpc.ypupdatedd to propagate NIS maps
+# after the master map databases have been modified. It expects
+# to be passed two arguments: the name of the map that was updated
+# and the name of the domain where the map resides.
+# These are passed to /var/yp/Makefile.
+#
+# Comment out the LOG=yes line to disable logging.
+#
+# $FreeBSD$
+#
+
+LOG=yes
+LOGFILE=/var/yp/ypupdate.log
+
+umask 077
+
+if [ ! -f $LOGFILE ];
+then
+ /usr/bin/touch $LOGFILE
+ echo "# Edit /usr/libexec/yppwupdate to disable" >> $LOGFILE
+ echo "# logging to this file from yppasswdd." >> $LOGFILE
+ echo -n "# Log started on: " >> $LOGFILE
+ /bin/date >> $LOGFILE
+fi
+
+if [ ! $LOG ];
+then
+ cd /var/yp/$2; /usr/bin/make -f ../Makefile $1 2>&1
+else
+ cd /var/yp/$2; /usr/bin/make -f ../Makefile $1 >> $LOGFILE
+fi
diff --git a/usr.sbin/rpc.ypupdated/ypupdated_extern.h b/usr.sbin/rpc.ypupdated/ypupdated_extern.h
new file mode 100644
index 0000000..2cacb95
--- /dev/null
+++ b/usr.sbin/rpc.ypupdated/ypupdated_extern.h
@@ -0,0 +1,33 @@
+/*
+ * $FreeBSD$
+ */
+
+#include <db.h>
+
+#define YPOP_CHANGE 1 /* change, do not add */
+#define YPOP_INSERT 2 /* add, do not change */
+#define YPOP_DELETE 3 /* delete this entry */
+#define YPOP_STORE 4 /* add, or change */
+
+#define ERR_ACCESS 1
+#define ERR_MALLOC 2
+#define ERR_READ 3
+#define ERR_WRITE 4
+#define ERR_DBASE 5
+#define ERR_KEY 6
+
+#ifndef YPLIBDIR
+#define YPLIBDIR "/usr/libexec/"
+#endif
+
+#ifndef MAP_UPPATE
+#define MAP_UPDATE "ypupdate"
+#endif
+
+#define MAP_UPDATE_PATH YPLIBDIR MAP_UPDATE
+
+extern int children;
+extern void ypu_prog_1(struct svc_req *, register SVCXPRT *);
+extern int localupdate(char *, char *, u_int, u_int, char *, u_int, char *);
+extern int ypmap_update(char *, char *, u_int, u_int, char *, u_int, char *);
+extern int yp_del_record(DB *, DBT *);
diff --git a/usr.sbin/rpc.ypupdated/ypupdated_main.c b/usr.sbin/rpc.ypupdated/ypupdated_main.c
new file mode 100644
index 0000000..92d8374
--- /dev/null
+++ b/usr.sbin/rpc.ypupdated/ypupdated_main.c
@@ -0,0 +1,285 @@
+/*
+ * Copyright (c) 1995, 1996
+ * 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 "ypupdate_prot.h"
+#include <stdio.h>
+#include <stdlib.h> /* getenv, exit */
+#include <rpc/pmap_clnt.h> /* for pmap_unset */
+#include <rpc/rpc_com.h>
+#include <string.h> /* strcmp */
+#include <signal.h>
+#ifdef __cplusplus
+#include <sysent.h> /* getdtablesize, open */
+#endif /* __cplusplus */
+#include <memory.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <syslog.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <err.h>
+#include <unistd.h>
+#include "ypupdated_extern.h"
+#include "yp_extern.h"
+
+#ifndef SIG_PF
+#define SIG_PF void(*)(int)
+#endif
+
+#ifdef DEBUG
+#define RPC_SVC_FG
+#endif
+
+#define _RPCSVC_CLOSEDOWN 120
+int _rpcpmstart; /* Started by a port monitor ? */
+static int _rpcfdtype;
+ /* Whether Stream or Datagram ? */
+ /* States a server can be in wrt request */
+
+#define _IDLE 0
+#define _SERVED 1
+#define _SERVING 2
+
+extern int _rpcsvcstate; /* Set when a request is serviced */
+
+char *progname = "rpc.ypupdated";
+char *yp_dir = "/var/yp/";
+
+static void
+_msgout(char* msg)
+{
+#ifdef RPC_SVC_FG
+ if (_rpcpmstart)
+ syslog(LOG_ERR, "%s", msg);
+ else
+ warnx("%s", msg);
+#else
+ syslog(LOG_ERR, "%s", msg);
+#endif
+}
+
+static void
+closedown(int sig)
+{
+ if (_rpcsvcstate == _IDLE) {
+ extern fd_set svc_fdset;
+ static int size;
+ int i, openfd;
+
+ if (_rpcfdtype == SOCK_DGRAM)
+ exit(0);
+ if (size == 0) {
+ size = getdtablesize();
+ }
+ for (i = 0, openfd = 0; i < size && openfd < 2; i++)
+ if (FD_ISSET(i, &svc_fdset))
+ openfd++;
+ if (openfd <= 1)
+ exit(0);
+ }
+ if (_rpcsvcstate == _SERVED)
+ _rpcsvcstate = _IDLE;
+
+ (void) signal(SIGALRM, (SIG_PF) closedown);
+ (void) alarm(_RPCSVC_CLOSEDOWN/2);
+}
+
+static void
+ypupdated_svc_run(void)
+{
+#ifdef FD_SETSIZE
+ fd_set readfds;
+#else
+ int readfds;
+#endif /* def FD_SETSIZE */
+ extern int forked;
+ int pid;
+ int fd_setsize = _rpc_dtablesize();
+
+ /* Establish the identity of the parent ypupdated process. */
+ pid = getpid();
+
+ for (;;) {
+#ifdef FD_SETSIZE
+ readfds = svc_fdset;
+#else
+ readfds = svc_fds;
+#endif /* def FD_SETSIZE */
+ switch (select(fd_setsize, &readfds, NULL, NULL,
+ (struct timeval *)0)) {
+ case -1:
+ if (errno == EINTR) {
+ continue;
+ }
+ warn("svc_run: - select failed");
+ return;
+ case 0:
+ continue;
+ default:
+ svc_getreqset(&readfds);
+ if (forked && pid != getpid())
+ exit(0);
+ }
+ }
+}
+
+static void
+reaper(int sig)
+{
+ int status;
+
+ if (sig == SIGHUP) {
+#ifdef foo
+ load_securenets();
+#endif
+ return;
+ }
+
+ if (sig == SIGCHLD) {
+ while (wait3(&status, WNOHANG, NULL) > 0)
+ children--;
+ } else {
+ (void) pmap_unset(YPU_PROG, YPU_VERS);
+ exit(0);
+ }
+}
+
+void
+usage(void)
+{
+ fprintf(stderr, "rpc.ypupdatedd [-p path]\n");
+ exit(0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ register SVCXPRT *transp = NULL;
+ int sock;
+ int proto = 0;
+ struct sockaddr_in saddr;
+ int asize = sizeof (saddr);
+ int ch;
+
+ while ((ch = getopt(argc, argv, "p:h")) != -1) {
+ switch (ch) {
+ case 'p':
+ yp_dir = optarg;
+ break;
+ default:
+ usage();
+ break;
+ }
+ }
+#ifdef foo
+ load_securenets();
+#endif
+
+ if (svc_auth_reg(AUTH_DES, _svcauth_des) == -1) {
+ yp_error("failed to register AUTH_DES flavor");
+ exit(1);
+ }
+
+ if (getsockname(0, (struct sockaddr *)&saddr, &asize) == 0) {
+ int ssize = sizeof (int);
+
+ if (saddr.sin_family != AF_INET)
+ exit(1);
+ if (getsockopt(0, SOL_SOCKET, SO_TYPE,
+ (char *)&_rpcfdtype, &ssize) == -1)
+ exit(1);
+ sock = 0;
+ _rpcpmstart = 1;
+ proto = 0;
+ openlog("rpc.ypupdatedd", LOG_PID, LOG_DAEMON);
+ } else {
+#ifndef RPC_SVC_FG
+ if (daemon(0,0)) {
+ err(1, "cannot fork");
+ }
+ openlog("rpc.ypupdated", LOG_PID, LOG_DAEMON);
+#endif
+ sock = RPC_ANYSOCK;
+ (void) pmap_unset(YPU_PROG, YPU_VERS);
+ }
+
+ if ((_rpcfdtype == 0) || (_rpcfdtype == SOCK_DGRAM)) {
+ transp = svcudp_create(sock);
+ if (transp == NULL) {
+ _msgout("cannot create udp service.");
+ exit(1);
+ }
+ if (!_rpcpmstart)
+ proto = IPPROTO_UDP;
+ if (!svc_register(transp, YPU_PROG, YPU_VERS, ypu_prog_1, proto)) {
+ _msgout("unable to register (YPU_PROG, YPU_VERS, udp).");
+ exit(1);
+ }
+ }
+
+ if ((_rpcfdtype == 0) || (_rpcfdtype == SOCK_STREAM)) {
+ transp = svctcp_create(sock, 0, 0);
+ if (transp == NULL) {
+ _msgout("cannot create tcp service.");
+ exit(1);
+ }
+ if (!_rpcpmstart)
+ proto = IPPROTO_TCP;
+ if (!svc_register(transp, YPU_PROG, YPU_VERS, ypu_prog_1, proto)) {
+ _msgout("unable to register (YPU_PROG, YPU_VERS, tcp).");
+ exit(1);
+ }
+ }
+
+ if (transp == (SVCXPRT *)NULL) {
+ _msgout("could not create a handle");
+ exit(1);
+ }
+ if (_rpcpmstart) {
+ (void) signal(SIGALRM, (SIG_PF) closedown);
+ (void) alarm(_RPCSVC_CLOSEDOWN/2);
+ }
+
+ (void) signal(SIGPIPE, SIG_IGN);
+ (void) signal(SIGCHLD, (SIG_PF) reaper);
+ (void) signal(SIGTERM, (SIG_PF) reaper);
+ (void) signal(SIGINT, (SIG_PF) reaper);
+ (void) signal(SIGHUP, (SIG_PF) reaper);
+
+ ypupdated_svc_run();
+ _msgout("svc_run returned");
+ exit(1);
+ /* NOTREACHED */
+}
diff --git a/usr.sbin/rpc.ypupdated/ypupdated_server.c b/usr.sbin/rpc.ypupdated/ypupdated_server.c
new file mode 100644
index 0000000..c4e163a
--- /dev/null
+++ b/usr.sbin/rpc.ypupdated/ypupdated_server.c
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 1995, 1996
+ * 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * ypupdate server implementation
+ *
+ * Written by Bill Paul <wpaul@ctr.columbia.edu>
+ * Center for Telecommunications Research
+ * Columbia University, New York City
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <stdio.h>
+#include <rpc/rpc.h>
+#include <rpc/key_prot.h>
+#include <sys/param.h>
+#include <rpcsvc/yp.h>
+#include "ypupdate_prot.h"
+#include "ypupdated_extern.h"
+#include "yp_extern.h"
+#include "ypxfr_extern.h"
+
+int children = 0;
+int forked = 0;
+
+/*
+ * Try to avoid spoofing: if a client chooses to use a very large
+ * window and then tries a bunch of randomly chosen encrypted timestamps,
+ * there's a chance he might stumble onto a valid combination.
+ * We therefore reject any RPCs with a window size larger than a preset
+ * value.
+ */
+#ifndef WINDOW
+#define WINDOW (60*60)
+#endif
+
+static enum auth_stat
+yp_checkauth(struct svc_req *svcreq)
+{
+ struct authdes_cred *des_cred;
+
+ switch (svcreq->rq_cred.oa_flavor) {
+ case AUTH_DES:
+ des_cred = (struct authdes_cred *) svcreq->rq_clntcred;
+ if (des_cred->adc_fullname.window > WINDOW) {
+ yp_error("warning: client-specified window size \
+was too large -- possible spoof attempt");
+ return(AUTH_BADCRED);
+ }
+ return(AUTH_OK);
+ break;
+ case AUTH_UNIX:
+ case AUTH_NONE:
+ yp_error("warning: client didn't use DES authentication");
+ return(AUTH_TOOWEAK);
+ break;
+ default:
+ yp_error("client used unknown auth flavor");
+ return(AUTH_REJECTEDCRED);
+ break;
+ }
+}
+
+unsigned int *
+ypu_change_1_svc(struct ypupdate_args *args, struct svc_req *svcreq)
+{
+ struct authdes_cred *des_cred;
+ static int res;
+ char *netname;
+ enum auth_stat astat;
+
+ res = 0;
+
+ astat = yp_checkauth(svcreq);
+
+ if (astat != AUTH_OK) {
+ svcerr_auth(svcreq->rq_xprt, astat);
+ return(&res);
+ }
+
+ des_cred = (struct authdes_cred *) svcreq->rq_clntcred;
+ netname = des_cred->adc_fullname.name;
+
+ res = localupdate(netname, "/etc/publickey", YPOP_CHANGE,
+ args->key.yp_buf_len, args->key.yp_buf_val,
+ args->datum.yp_buf_len, args->datum.yp_buf_val);
+
+ if (res)
+ return (&res);
+
+ res = ypmap_update(netname, args->mapname, YPOP_CHANGE,
+ args->key.yp_buf_len, args->key.yp_buf_val,
+ args->datum.yp_buf_len, args->datum.yp_buf_val);
+
+ return (&res);
+}
+
+unsigned int *
+ypu_insert_1_svc(struct ypupdate_args *args, struct svc_req *svcreq)
+{
+ struct authdes_cred *des_cred;
+ static int res;
+ char *netname;
+ enum auth_stat astat;
+
+ res = 0;
+
+ astat = yp_checkauth(svcreq);
+
+ if (astat != AUTH_OK) {
+ svcerr_auth(svcreq->rq_xprt, astat);
+ return(&res);
+ }
+
+ des_cred = (struct authdes_cred *) svcreq->rq_clntcred;
+ netname = des_cred->adc_fullname.name;
+
+ res = localupdate(netname, "/etc/publickey", YPOP_INSERT,
+ args->key.yp_buf_len, args->key.yp_buf_val,
+ args->datum.yp_buf_len, args->datum.yp_buf_val);
+
+ if (res)
+ return (&res);
+
+ res = ypmap_update(netname, args->mapname, YPOP_INSERT,
+ args->key.yp_buf_len, args->key.yp_buf_val,
+ args->datum.yp_buf_len, args->datum.yp_buf_val);
+
+ return (&res);
+}
+
+unsigned int *
+ypu_delete_1_svc(struct ypdelete_args *args, struct svc_req *svcreq)
+{
+ struct authdes_cred *des_cred;
+ static int res;
+ char *netname;
+ enum auth_stat astat;
+
+ res = 0;
+
+ astat = yp_checkauth(svcreq);
+
+ if (astat != AUTH_OK) {
+ svcerr_auth(svcreq->rq_xprt, astat);
+ return(&res);
+ }
+
+ des_cred = (struct authdes_cred *) svcreq->rq_clntcred;
+ netname = des_cred->adc_fullname.name;
+
+ res = localupdate(netname, "/etc/publickey", YPOP_DELETE,
+ args->key.yp_buf_len, args->key.yp_buf_val,
+ 0, NULL);
+
+ if (res)
+ return (&res);
+
+ res = ypmap_update(netname, args->mapname, YPOP_DELETE,
+ args->key.yp_buf_len, args->key.yp_buf_val,
+ 0, NULL);
+
+ return (&res);
+}
+
+unsigned int *
+ypu_store_1_svc(struct ypupdate_args *args, struct svc_req *svcreq)
+{
+ struct authdes_cred *des_cred;
+ static int res;
+ char *netname;
+ enum auth_stat astat;
+
+ res = 0;
+
+ astat = yp_checkauth(svcreq);
+
+ if (astat != AUTH_OK) {
+ svcerr_auth(svcreq->rq_xprt, astat);
+ return(&res);
+ }
+
+ des_cred = (struct authdes_cred *) svcreq->rq_clntcred;
+ netname = des_cred->adc_fullname.name;
+
+ res = localupdate(netname, "/etc/publickey", YPOP_STORE,
+ args->key.yp_buf_len, args->key.yp_buf_val,
+ args->datum.yp_buf_len, args->datum.yp_buf_val);
+
+ if (res)
+ return (&res);
+
+ res = ypmap_update(netname, args->mapname, YPOP_STORE,
+ args->key.yp_buf_len, args->key.yp_buf_val,
+ args->datum.yp_buf_len, args->datum.yp_buf_val);
+
+ return (&res);
+}
diff --git a/usr.sbin/rpc.ypxfrd/Makefile b/usr.sbin/rpc.ypxfrd/Makefile
new file mode 100644
index 0000000..903ac16
--- /dev/null
+++ b/usr.sbin/rpc.ypxfrd/Makefile
@@ -0,0 +1,36 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../usr.sbin/ypserv
+
+PROG= rpc.ypxfrd
+MAN= rpc.ypxfrd.8
+SRCS= ypxfrd_svc.c ypxfrd.h ypxfrd_server.c yp_error.c \
+ yp_access.c ypxfrd_main.c
+
+CFLAGS+= -I. -DXFRBLOCKSIZE=65535
+
+WARNS?= 2
+
+LIBADD= rpcsvc
+
+CLEANFILES= ypxfrd_svc.c ypxfrd.h
+
+RPCDIR= ${.CURDIR}/../../include/rpcsvc
+RPCGEN= RPCGEN_CPP=${CPP:Q} rpcgen -I -C
+
+# We need to remove the 'static' keyword from _rpcsvcstate so that
+# ypxfrd_main.c can see it.
+ypxfrd_svc.c: ${RPCDIR}/ypxfrd.x
+ rm -f ${.TARGET}
+ ${RPCGEN} -m ${RPCDIR}/ypxfrd.x | \
+ sed s/"static int _rpcsvcstate"/"int _rpcsvcstate"/g > ${.TARGET}
+
+# ypxfrd_xdr.c: ${RPCDIR}/ypxfrd.x
+# rm -f ${.TARGET}
+# ${RPCGEN} -c -o ${.TARGET} ${RPCDIR}/ypxfrd.x
+
+ypxfrd.h: ${RPCDIR}/ypxfrd.x
+ rm -f ${.TARGET}
+ ${RPCGEN} -h -o ${.TARGET} ${RPCDIR}/ypxfrd.x
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/rpc.ypxfrd/Makefile.depend b/usr.sbin/rpc.ypxfrd/Makefile.depend
new file mode 100644
index 0000000..08d54b2
--- /dev/null
+++ b/usr.sbin/rpc.ypxfrd/Makefile.depend
@@ -0,0 +1,30 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/librpcsvc \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+ypxfrd_main.o: ypxfrd.h
+ypxfrd_main.po: ypxfrd.h
+ypxfrd_server.o: ypxfrd.h
+ypxfrd_server.po: ypxfrd.h
+ypxfrd_svc.o: ypxfrd.h
+ypxfrd_svc.o: ypxfrd_svc.c
+ypxfrd_svc.po: ypxfrd.h
+ypxfrd_svc.po: ypxfrd_svc.c
+.endif
diff --git a/usr.sbin/rpc.ypxfrd/rpc.ypxfrd.8 b/usr.sbin/rpc.ypxfrd/rpc.ypxfrd.8
new file mode 100644
index 0000000..5a7f8da
--- /dev/null
+++ b/usr.sbin/rpc.ypxfrd/rpc.ypxfrd.8
@@ -0,0 +1,153 @@
+.\" Copyright (c) 1995, 1996
+.\" 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 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 CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (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 2, 1996
+.Dt RPC.YPXFRD 8
+.Os
+.Sh NAME
+.Nm rpc.ypxfrd
+.Nd "NIS map transfer server"
+.Sh SYNOPSIS
+.Nm
+.Op Fl p Ar path
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to speed up the distribution of very large NIS maps
+from NIS master to NIS slave servers.
+The normal method for transferring
+maps involves several steps:
+.Bl -bullet -offset indent
+.It
+The master server calls
+.Xr yppush 8
+to inform the slave servers to start a transfer.
+.It
+The slave servers invoke
+.Xr ypxfr 8 ,
+which reads the entire contents of a map from the master server
+using the yp_all() function.
+.It
+The
+.Xr ypxfr 8
+program then creates a new map database file by using the
+.Xr db 3
+library hash method to store the data that it receives from the server.
+.It
+When all the data has been retrieved,
+.Xr ypxfr 8
+moves the new file into place and sends
+.Xr ypserv 8
+on the local machine a YPPROC_CLEAR to tell it to refresh its
+database handles.
+.El
+.Pp
+This process can take several minutes when there are very large
+maps involved.
+For example: a passwd database with several tens of
+thousands of entries can consume several megabytes of disk space,
+and it can take the
+.Xr db 3
+library package a long time to sort and store all the records
+in a hash database.
+Consider also that there are two sets of map
+files:
+.Pa master.passwd.by{name,uid}
+and
+.Pa passwd.by{name,uid} .
+.Pp
+The
+.Nm
+utility speeds up the transfer process by allowing NIS slave servers to
+simply copy the master server's map files rather than building their
+own from scratch.
+Simply put,
+.Nm
+implements an RPC-based file transfer protocol.
+Transferring even
+a multi-megabyte file in this fashion takes only a few seconds compared
+to the several minutes it would take even a reasonably fast slave server
+to build a new map from scratch.
+.Pp
+The
+.Nm
+utility uses the same access restriction mechanism as
+.Xr ypserv 8 .
+This means that slave servers will only be permitted to transfer
+files if the rules in the
+.Pa securenets
+database permit it (see
+.Xr ypserv 8
+for more information on
+.Pa securenets ) .
+Furthermore, only slave servers using reserved
+ports will be allowed to transfer the
+.Pa master.passwd
+maps.
+.Sh OPTIONS
+The following option is available:
+.Bl -tag -width indent
+.It Fl p Ar path
+This option can be used to override the default path to
+the location of the NIS
+map databases.
+The compiled-in default path is
+.Pa /var/yp .
+.El
+.Sh FILES
+.Bl -tag -width Pa -compact
+.It Pa /var/yp/[domainname]/[maps]
+The NIS maps for a particular NIS domain.
+.El
+.Sh SEE ALSO
+.Xr yp 8 ,
+.Xr yppush 8 ,
+.Xr ypserv 8 ,
+.Xr ypxfr 8
+.Sh AUTHORS
+.An Bill Paul Aq Mt wpaul@ctr.columbia.edu
+.Sh BUGS
+The
+.Fx
+.Nm ypxfrd
+protocol is not compatible with that used by SunOS.
+This is unfortunate
+but unavoidable: Sun's protocol is not freely available, and even if it
+were it would probably not be useful since the SunOS NIS v2 implementation
+uses the original ndbm package for its map databases whereas the
+.Fx
+implementation uses Berkeley DB.
+These two packages use vastly different
+file formats.
+Furthermore, ndbm is byte-order sensitive and not very
+smart about it, meaning that am ndbm database created on a big endian
+system cannot be read on a little endian system.
diff --git a/usr.sbin/rpc.ypxfrd/ypxfrd_extern.h b/usr.sbin/rpc.ypxfrd/ypxfrd_extern.h
new file mode 100644
index 0000000..5aba934
--- /dev/null
+++ b/usr.sbin/rpc.ypxfrd/ypxfrd_extern.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 1995, 1996
+ * 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 MAX_CHILDREN
+#define MAX_CHILDREN 20
+#endif
+
+#ifndef XFRBLOCKSIZE
+#define XFRBLOCKSIZE YPXFRBLOCK
+#endif
+
+extern int forked;
+extern int children;
+extern void load_securenets(void);
+extern void yp_error(const char *, ...);
+extern int yp_access(const char *, const struct svc_req *);
+extern int yp_validdomain(const char *);
+extern char *yp_dir;
+extern void ypxfrd_freebsd_prog_1(struct svc_req *, register SVCXPRT *);
diff --git a/usr.sbin/rpc.ypxfrd/ypxfrd_main.c b/usr.sbin/rpc.ypxfrd/ypxfrd_main.c
new file mode 100644
index 0000000..8fa3e22
--- /dev/null
+++ b/usr.sbin/rpc.ypxfrd/ypxfrd_main.c
@@ -0,0 +1,302 @@
+/*
+ * Copyright (c) 1995, 1996
+ * 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 "ypxfrd.h"
+#include <err.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h> /* getenv, exit */
+#include <unistd.h>
+#include <rpc/pmap_clnt.h> /* for pmap_unset */
+#include <rpc/rpc_com.h>
+#include <string.h> /* strcmp */
+#include <signal.h>
+#include <sys/ttycom.h> /* TIOCNOTTY */
+#ifdef __cplusplus
+#include <sysent.h> /* getdtablesize, open */
+#endif /* __cplusplus */
+#include <memory.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <syslog.h>
+#include "ypxfrd_extern.h"
+#include <sys/wait.h>
+#include <errno.h>
+
+#ifndef SIG_PF
+#define SIG_PF void(*)(int)
+#endif
+
+#ifdef DEBUG
+#define RPC_SVC_FG
+#endif
+
+#define _RPCSVC_CLOSEDOWN 120
+int _rpcpmstart; /* Started by a port monitor ? */
+static int _rpcfdtype;
+ /* Whether Stream or Datagram ? */
+ /* States a server can be in wrt request */
+
+#define _IDLE 0
+#define _SERVED 1
+#define _SERVING 2
+
+extern int _rpcsvcstate; /* Set when a request is serviced */
+
+char *progname = "rpc.ypxfrd";
+char *yp_dir = "/var/yp/";
+
+static void
+_msgout(char *msg)
+{
+#ifdef RPC_SVC_FG
+ if (_rpcpmstart)
+ syslog(LOG_ERR, "%s", msg);
+ else
+ warnx("%s", msg);
+#else
+ syslog(LOG_ERR, "%s", msg);
+#endif
+}
+
+static void
+closedown(int sig)
+{
+ if (_rpcsvcstate == _IDLE) {
+ extern fd_set svc_fdset;
+ static int size;
+ int i, openfd;
+
+ if (_rpcfdtype == SOCK_DGRAM)
+ exit(0);
+ if (size == 0) {
+ size = getdtablesize();
+ }
+ for (i = 0, openfd = 0; i < size && openfd < 2; i++)
+ if (FD_ISSET(i, &svc_fdset))
+ openfd++;
+ if (openfd <= 1)
+ exit(0);
+ }
+ if (_rpcsvcstate == _SERVED)
+ _rpcsvcstate = _IDLE;
+
+ (void) signal(SIGALRM, (SIG_PF) closedown);
+ (void) alarm(_RPCSVC_CLOSEDOWN/2);
+}
+
+
+static void
+ypxfrd_svc_run(void)
+{
+#ifdef FD_SETSIZE
+ fd_set readfds;
+#else
+ int readfds;
+#endif /* def FD_SETSIZE */
+ extern int forked;
+ int pid;
+ int fd_setsize = _rpc_dtablesize();
+
+ /* Establish the identity of the parent ypserv process. */
+ pid = getpid();
+
+ for (;;) {
+#ifdef FD_SETSIZE
+ readfds = svc_fdset;
+#else
+ readfds = svc_fds;
+#endif /* def FD_SETSIZE */
+ switch (select(fd_setsize, &readfds, NULL, NULL,
+ (struct timeval *)0)) {
+ case -1:
+ if (errno == EINTR) {
+ continue;
+ }
+ warn("svc_run: - select failed");
+ return;
+ case 0:
+ continue;
+ default:
+ svc_getreqset(&readfds);
+ if (forked && pid != getpid())
+ exit(0);
+ }
+ }
+}
+
+static void reaper(int sig)
+{
+ int status;
+ int saved_errno;
+
+ saved_errno = errno;
+
+ if (sig == SIGHUP) {
+ load_securenets();
+ errno = saved_errno;
+ return;
+ }
+
+ if (sig == SIGCHLD) {
+ while (wait3(&status, WNOHANG, NULL) > 0)
+ children--;
+ } else {
+ (void) pmap_unset(YPXFRD_FREEBSD_PROG, YPXFRD_FREEBSD_VERS);
+ exit(0);
+ }
+
+ errno = saved_errno;
+ return;
+}
+
+void
+usage(void)
+{
+ fprintf(stderr, "usage: rpc.ypxfrd [-p path]\n");
+ exit(0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ register SVCXPRT *transp = NULL;
+ int sock;
+ int proto = 0;
+ struct sockaddr_in saddr;
+ int asize = sizeof (saddr);
+ int ch;
+
+ while ((ch = getopt(argc, argv, "p:h")) != -1) {
+ switch (ch) {
+ case 'p':
+ yp_dir = optarg;
+ break;
+ default:
+ usage();
+ break;
+ }
+ }
+
+ load_securenets();
+
+ if (getsockname(0, (struct sockaddr *)&saddr, &asize) == 0) {
+ int ssize = sizeof (int);
+
+ if (saddr.sin_family != AF_INET)
+ exit(1);
+ if (getsockopt(0, SOL_SOCKET, SO_TYPE,
+ (char *)&_rpcfdtype, &ssize) == -1)
+ exit(1);
+ sock = 0;
+ _rpcpmstart = 1;
+ proto = 0;
+ openlog("rpc.ypxfrd", LOG_PID, LOG_DAEMON);
+ } else {
+#ifndef RPC_SVC_FG
+ int size;
+ int pid, i;
+
+ pid = fork();
+ if (pid < 0)
+ err(1, "fork");
+ if (pid)
+ exit(0);
+ size = getdtablesize();
+ for (i = 0; i < size; i++)
+ (void) close(i);
+ i = open(_PATH_CONSOLE, 2);
+ (void) dup2(i, 1);
+ (void) dup2(i, 2);
+ i = open(_PATH_TTY, 2);
+ if (i >= 0) {
+ (void) ioctl(i, TIOCNOTTY, (char *)NULL);
+ (void) close(i);
+ }
+ openlog("rpc.ypxfrd", LOG_PID, LOG_DAEMON);
+#endif
+ sock = RPC_ANYSOCK;
+ (void) pmap_unset(YPXFRD_FREEBSD_PROG, YPXFRD_FREEBSD_VERS);
+ }
+
+ if ((_rpcfdtype == 0) || (_rpcfdtype == SOCK_DGRAM)) {
+ transp = svcudp_create(sock);
+ if (transp == NULL) {
+ _msgout("cannot create udp service.");
+ exit(1);
+ }
+ if (!_rpcpmstart)
+ proto = IPPROTO_UDP;
+ if (!svc_register(transp, YPXFRD_FREEBSD_PROG, YPXFRD_FREEBSD_VERS, ypxfrd_freebsd_prog_1, proto)) {
+ _msgout("unable to register (YPXFRD_FREEBSD_PROG, YPXFRD_FREEBSD_VERS, udp).");
+ exit(1);
+ }
+ }
+
+ if ((_rpcfdtype == 0) || (_rpcfdtype == SOCK_STREAM)) {
+ transp = svctcp_create(sock, 0, 0);
+ if (transp == NULL) {
+ _msgout("cannot create tcp service.");
+ exit(1);
+ }
+ if (!_rpcpmstart)
+ proto = IPPROTO_TCP;
+ if (!svc_register(transp, YPXFRD_FREEBSD_PROG, YPXFRD_FREEBSD_VERS, ypxfrd_freebsd_prog_1, proto)) {
+ _msgout("unable to register (YPXFRD_FREEBSD_PROG, YPXFRD_FREEBSD_VERS, tcp).");
+ exit(1);
+ }
+ }
+
+ if (transp == (SVCXPRT *)NULL) {
+ _msgout("could not create a handle");
+ exit(1);
+ }
+ if (_rpcpmstart) {
+ (void) signal(SIGALRM, (SIG_PF) closedown);
+ (void) alarm(_RPCSVC_CLOSEDOWN/2);
+ }
+
+ (void) signal(SIGPIPE, SIG_IGN);
+ (void) signal(SIGCHLD, (SIG_PF) reaper);
+ (void) signal(SIGTERM, (SIG_PF) reaper);
+ (void) signal(SIGINT, (SIG_PF) reaper);
+ (void) signal(SIGHUP, (SIG_PF) reaper);
+
+ ypxfrd_svc_run();
+ _msgout("svc_run returned");
+ exit(1);
+ /* NOTREACHED */
+}
diff --git a/usr.sbin/rpc.ypxfrd/ypxfrd_server.c b/usr.sbin/rpc.ypxfrd/ypxfrd_server.c
new file mode 100644
index 0000000..5f60712
--- /dev/null
+++ b/usr.sbin/rpc.ypxfrd/ypxfrd_server.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 1995, 1996
+ * 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 "ypxfrd.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/uio.h>
+#include <sys/fcntl.h>
+#include <machine/endian.h>
+#include "ypxfrd_extern.h"
+
+int forked = 0;
+int children = 0;
+int fp = 0;
+
+static bool_t
+xdr_my_xfr(register XDR *xdrs, xfr *objp)
+{
+ unsigned char buf[XFRBLOCKSIZE];
+
+ while (1) {
+ if ((objp->xfr_u.xfrblock_buf.xfrblock_buf_len =
+ read(fp, &buf, XFRBLOCKSIZE)) != -1) {
+ objp->ok = TRUE;
+ objp->xfr_u.xfrblock_buf.xfrblock_buf_val = (char *)&buf;
+ } else {
+ objp->ok = FALSE;
+ objp->xfr_u.xfrstat = XFR_READ_ERR;
+ yp_error("read error: %s", strerror(errno));
+ }
+
+ /* Serialize */
+ if (!xdr_xfr(xdrs, objp))
+ return(FALSE);
+ if (objp->ok == FALSE)
+ return(TRUE);
+ if (objp->xfr_u.xfrblock_buf.xfrblock_buf_len < XFRBLOCKSIZE) {
+ objp->ok = FALSE;
+ objp->xfr_u.xfrstat = XFR_DONE;
+ if (!xdr_xfr(xdrs, objp))
+ return(FALSE);
+ return(TRUE);
+ }
+ }
+}
+
+struct xfr *
+ypxfrd_getmap_1_svc(ypxfr_mapname *argp, struct svc_req *rqstp)
+{
+ static struct xfr result;
+ char buf[MAXPATHLEN];
+
+ result.ok = FALSE;
+ result.xfr_u.xfrstat = XFR_DENIED;
+
+ if (yp_validdomain(argp->xfrdomain)) {
+ return(&result);
+ }
+
+ if (yp_access(argp->xfrmap, (struct svc_req *)rqstp)) {
+ return(&result);
+ }
+
+ snprintf (buf, sizeof(buf), "%s/%s/%s", yp_dir, argp->xfrdomain,
+ argp->xfrmap);
+ if (access(buf, R_OK) == -1) {
+ result.xfr_u.xfrstat = XFR_ACCESS;
+ return(&result);
+ }
+
+ if (argp->xfr_db_type != XFR_DB_BSD_HASH &&
+ argp->xfr_db_type != XFR_DB_ANY) {
+ result.xfr_u.xfrstat = XFR_DB_TYPE_MISMATCH;
+ return(&result);
+ }
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+ if (argp->xfr_byte_order == XFR_ENDIAN_BIG) {
+#else
+ if (argp->xfr_byte_order == XFR_ENDIAN_LITTLE) {
+#endif
+ result.xfr_u.xfrstat = XFR_DB_ENDIAN_MISMATCH;
+ return(&result);
+ }
+
+#ifndef DEBUG
+ if (children < MAX_CHILDREN && fork()) {
+ children++;
+ forked = 0;
+ return (NULL);
+ } else {
+ forked++;
+ }
+#endif
+ if ((fp = open(buf, O_RDONLY)) == -1) {
+ result.xfr_u.xfrstat = XFR_READ_ERR;
+ return(&result);
+ }
+
+ /* Start sending the file. */
+
+ svc_sendreply(rqstp->rq_xprt, (xdrproc_t)xdr_my_xfr, &result);
+
+ close(fp);
+
+ return (NULL);
+}
diff --git a/usr.sbin/rpcbind/Makefile b/usr.sbin/rpcbind/Makefile
new file mode 100644
index 0000000..b328260
--- /dev/null
+++ b/usr.sbin/rpcbind/Makefile
@@ -0,0 +1,25 @@
+# $NetBSD: Makefile,v 1.3 2000/06/20 13:56:43 fvdl Exp $
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= rpcbind
+MAN= rpcbind.8
+SRCS= check_bound.c rpcb_stat.c rpcb_svc_4.c rpcbind.c pmap_svc.c \
+ rpcb_svc.c rpcb_svc_com.c security.c warmstart.c util.c
+
+CFLAGS+= -DPORTMAP -DLIBWRAP
+
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+= -DINET6
+.endif
+
+.if ${MK_TESTS} != "no"
+SUBDIR+= tests
+.endif
+
+WARNS?= 1
+
+LIBADD= wrap
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/rpcbind/Makefile.depend b/usr.sbin/rpcbind/Makefile.depend
new file mode 100644
index 0000000..0944291
--- /dev/null
+++ b/usr.sbin/rpcbind/Makefile.depend
@@ -0,0 +1,23 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+ lib/libwrap \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/rpcbind/check_bound.c b/usr.sbin/rpcbind/check_bound.c
new file mode 100644
index 0000000..64b73c7
--- /dev/null
+++ b/usr.sbin/rpcbind/check_bound.c
@@ -0,0 +1,241 @@
+/* $NetBSD: check_bound.c,v 1.2 2000/06/22 08:09:26 fvdl Exp $ */
+/* $FreeBSD$ */
+
+/*-
+ * Copyright (c) 2009, Sun Microsystems, 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:
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of Sun Microsystems, Inc. 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 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 HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (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 - 1991 by Sun Microsystems, Inc.
+ */
+
+/* #ident "@(#)check_bound.c 1.15 93/07/05 SMI" */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)check_bound.c 1.11 89/04/21 Copyr 1989 Sun Micro";
+#endif
+#endif
+
+/*
+ * check_bound.c
+ * Checks to see whether the program is still bound to the
+ * claimed address and returns the universal merged address
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <rpc/rpc.h>
+#include <rpc/svc_dg.h>
+#include <stdio.h>
+#include <netconfig.h>
+#include <syslog.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "rpcbind.h"
+
+struct fdlist {
+ int fd;
+ struct netconfig *nconf;
+ struct fdlist *next;
+ int check_binding;
+};
+
+static struct fdlist *fdhead; /* Link list of the check fd's */
+static struct fdlist *fdtail;
+static char *nullstring = "";
+
+static bool_t check_bound(struct fdlist *, char *uaddr);
+
+/*
+ * Returns 1 if the given address is bound for the given addr & transport
+ * For all error cases, we assume that the address is bound
+ * Returns 0 for success.
+ */
+static bool_t
+check_bound(struct fdlist *fdl, char *uaddr)
+{
+ int fd;
+ struct netbuf *na;
+ int ans;
+
+ if (fdl->check_binding == FALSE)
+ return (TRUE);
+
+ na = uaddr2taddr(fdl->nconf, uaddr);
+ if (!na)
+ return (TRUE); /* punt, should never happen */
+
+ fd = __rpc_nconf2fd(fdl->nconf);
+ if (fd < 0) {
+ free(na->buf);
+ free(na);
+ return (TRUE);
+ }
+
+ ans = bind(fd, (struct sockaddr *)na->buf, na->len);
+
+ close(fd);
+ free(na->buf);
+ free(na);
+
+ return (ans == 0 ? FALSE : TRUE);
+}
+
+int
+add_bndlist(struct netconfig *nconf, struct netbuf *baddr __unused)
+{
+ struct fdlist *fdl;
+ struct netconfig *newnconf;
+
+ newnconf = getnetconfigent(nconf->nc_netid);
+ if (newnconf == NULL)
+ return (-1);
+ fdl = malloc(sizeof (struct fdlist));
+ if (fdl == NULL) {
+ freenetconfigent(newnconf);
+ syslog(LOG_ERR, "no memory!");
+ return (-1);
+ }
+ fdl->nconf = newnconf;
+ fdl->next = NULL;
+ if (fdhead == NULL) {
+ fdhead = fdl;
+ fdtail = fdl;
+ } else {
+ fdtail->next = fdl;
+ fdtail = fdl;
+ }
+ /* XXX no bound checking for now */
+ fdl->check_binding = FALSE;
+
+ return 0;
+}
+
+bool_t
+is_bound(char *netid, char *uaddr)
+{
+ struct fdlist *fdl;
+
+ for (fdl = fdhead; fdl; fdl = fdl->next)
+ if (strcmp(fdl->nconf->nc_netid, netid) == 0)
+ break;
+ if (fdl == NULL)
+ return (TRUE);
+ return (check_bound(fdl, uaddr));
+}
+
+/*
+ * Returns NULL if there was some system error.
+ * Returns "" if the address was not bound, i.e the server crashed.
+ * Returns the merged address otherwise.
+ */
+char *
+mergeaddr(SVCXPRT *xprt, char *netid, char *uaddr, char *saddr)
+{
+ struct fdlist *fdl;
+ struct svc_dg_data *dg_data;
+ char *c_uaddr, *s_uaddr, *m_uaddr, *allocated_uaddr = NULL;
+
+ for (fdl = fdhead; fdl; fdl = fdl->next)
+ if (strcmp(fdl->nconf->nc_netid, netid) == 0)
+ break;
+ if (fdl == NULL)
+ return (NULL);
+ if (check_bound(fdl, uaddr) == FALSE)
+ /* that server died */
+ return (nullstring);
+ /*
+ * Try to determine the local address on which the client contacted us,
+ * so we can send a reply from the same address. If it's unknown, then
+ * try to determine which address the client used, and pick a nearby
+ * local address.
+ *
+ * If saddr is not NULL, the remote client may have included the
+ * address by which it contacted us. Use that for the "client" uaddr,
+ * otherwise use the info from the SVCXPRT.
+ */
+ dg_data = (struct svc_dg_data*)xprt->xp_p2;
+ if (dg_data != NULL && dg_data->su_srcaddr.buf != NULL) {
+ c_uaddr = taddr2uaddr(fdl->nconf, &dg_data->su_srcaddr);
+ }
+ else if (saddr != NULL) {
+ c_uaddr = saddr;
+ } else {
+ c_uaddr = taddr2uaddr(fdl->nconf, svc_getrpccaller(xprt));
+ if (c_uaddr == NULL) {
+ syslog(LOG_ERR, "taddr2uaddr failed for %s",
+ fdl->nconf->nc_netid);
+ return (NULL);
+ }
+ allocated_uaddr = c_uaddr;
+ }
+
+#ifdef ND_DEBUG
+ if (debugging) {
+ if (saddr == NULL) {
+ fprintf(stderr, "mergeaddr: client uaddr = %s\n",
+ c_uaddr);
+ } else {
+ fprintf(stderr, "mergeaddr: contact uaddr = %s\n",
+ c_uaddr);
+ }
+ }
+#endif
+ s_uaddr = uaddr;
+ /*
+ * This is all we should need for IP 4 and 6
+ */
+ m_uaddr = addrmerge(svc_getrpccaller(xprt), s_uaddr, c_uaddr, netid);
+#ifdef ND_DEBUG
+ if (debugging)
+ fprintf(stderr, "mergeaddr: uaddr = %s, merged uaddr = %s\n",
+ uaddr, m_uaddr);
+#endif
+ if (allocated_uaddr != NULL)
+ free(allocated_uaddr);
+ return (m_uaddr);
+}
+
+/*
+ * Returns a netconf structure from its internal list. This
+ * structure should not be freed.
+ */
+struct netconfig *
+rpcbind_get_conf(const char *netid)
+{
+ struct fdlist *fdl;
+
+ for (fdl = fdhead; fdl; fdl = fdl->next)
+ if (strcmp(fdl->nconf->nc_netid, netid) == 0)
+ break;
+ if (fdl == NULL)
+ return (NULL);
+ return (fdl->nconf);
+}
diff --git a/usr.sbin/rpcbind/pmap_svc.c b/usr.sbin/rpcbind/pmap_svc.c
new file mode 100644
index 0000000..d20f18f
--- /dev/null
+++ b/usr.sbin/rpcbind/pmap_svc.c
@@ -0,0 +1,367 @@
+/* $NetBSD: pmap_svc.c,v 1.2 2000/10/20 11:49:40 fvdl Exp $ */
+/* $FreeBSD$ */
+
+/*-
+ * Copyright (c) 2009, Sun Microsystems, 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:
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of Sun Microsystems, Inc. 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 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 HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (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) 1984 - 1991 by Sun Microsystems, Inc.
+ */
+
+/* #ident "@(#)pmap_svc.c 1.14 93/07/05 SMI" */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)pmap_svc.c 1.23 89/04/05 Copyr 1984 Sun Micro";
+#endif
+#endif
+
+/*
+ * pmap_svc.c
+ * The server procedure for the version 2 portmaper.
+ * All the portmapper related interface from the portmap side.
+ */
+
+#ifdef PORTMAP
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <stdio.h>
+#include <rpc/rpc.h>
+#include <rpc/pmap_prot.h>
+#include <rpc/rpcb_prot.h>
+#ifdef RPCBIND_DEBUG
+#include <stdlib.h>
+#endif
+#include "rpcbind.h"
+
+static struct pmaplist *find_service_pmap(rpcprog_t, rpcvers_t,
+ rpcprot_t);
+static bool_t pmapproc_change(struct svc_req *, SVCXPRT *, u_long);
+static bool_t pmapproc_getport(struct svc_req *, SVCXPRT *);
+static bool_t pmapproc_dump(struct svc_req *, SVCXPRT *);
+
+/*
+ * Called for all the version 2 inquiries.
+ */
+void
+pmap_service(struct svc_req *rqstp, SVCXPRT *xprt)
+{
+ rpcbs_procinfo(RPCBVERS_2_STAT, rqstp->rq_proc);
+ switch (rqstp->rq_proc) {
+ case PMAPPROC_NULL:
+ /*
+ * Null proc call
+ */
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "PMAPPROC_NULL\n");
+#endif
+ check_access(xprt, rqstp->rq_proc, NULL, PMAPVERS);
+ if ((!svc_sendreply(xprt, (xdrproc_t) xdr_void, NULL)) &&
+ debugging) {
+ if (doabort) {
+ rpcbind_abort();
+ }
+ }
+ break;
+
+ case PMAPPROC_SET:
+ /*
+ * Set a program, version to port mapping
+ */
+ pmapproc_change(rqstp, xprt, rqstp->rq_proc);
+ break;
+
+ case PMAPPROC_UNSET:
+ /*
+ * Remove a program, version to port mapping.
+ */
+ pmapproc_change(rqstp, xprt, rqstp->rq_proc);
+ break;
+
+ case PMAPPROC_GETPORT:
+ /*
+ * Lookup the mapping for a program, version and return its
+ * port number.
+ */
+ pmapproc_getport(rqstp, xprt);
+ break;
+
+ case PMAPPROC_DUMP:
+ /*
+ * Return the current set of mapped program, version
+ */
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "PMAPPROC_DUMP\n");
+#endif
+ pmapproc_dump(rqstp, xprt);
+ break;
+
+ case PMAPPROC_CALLIT:
+ /*
+ * Calls a procedure on the local machine. If the requested
+ * procedure is not registered this procedure does not return
+ * error information!!
+ * This procedure is only supported on rpc/udp and calls via
+ * rpc/udp. It passes null authentication parameters.
+ */
+ rpcbproc_callit_com(rqstp, xprt, PMAPPROC_CALLIT, PMAPVERS);
+ break;
+
+ default:
+ svcerr_noproc(xprt);
+ break;
+ }
+}
+
+/*
+ * returns the item with the given program, version number. If that version
+ * number is not found, it returns the item with that program number, so that
+ * the port number is now returned to the caller. The caller when makes a
+ * call to this program, version number, the call will fail and it will
+ * return with PROGVERS_MISMATCH. The user can then determine the highest
+ * and the lowest version number for this program using clnt_geterr() and
+ * use those program version numbers.
+ */
+static struct pmaplist *
+find_service_pmap(rpcprog_t prog, rpcvers_t vers, rpcprot_t prot)
+{
+ register struct pmaplist *hit = NULL;
+ register struct pmaplist *pml;
+
+ for (pml = list_pml; pml != NULL; pml = pml->pml_next) {
+ if ((pml->pml_map.pm_prog != prog) ||
+ (pml->pml_map.pm_prot != prot))
+ continue;
+ hit = pml;
+ if (pml->pml_map.pm_vers == vers)
+ break;
+ }
+ return (hit);
+}
+
+static bool_t
+pmapproc_change(struct svc_req *rqstp __unused, SVCXPRT *xprt, unsigned long op)
+{
+ struct pmap reg;
+ RPCB rpcbreg;
+ long ans;
+ struct sockaddr_in *who;
+ uid_t uid;
+ char uidbuf[32];
+
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "%s request for (%lu, %lu) : ",
+ op == PMAPPROC_SET ? "PMAP_SET" : "PMAP_UNSET",
+ reg.pm_prog, reg.pm_vers);
+#endif
+
+ if (!svc_getargs(xprt, (xdrproc_t) xdr_pmap, (char *)&reg)) {
+ svcerr_decode(xprt);
+ return (FALSE);
+ }
+
+ if (!check_access(xprt, op, &reg, PMAPVERS)) {
+ svcerr_weakauth(xprt);
+ return FALSE;
+ }
+
+ who = svc_getcaller(xprt);
+
+ /*
+ * Can't use getpwnam here. We might end up calling ourselves
+ * and looping.
+ */
+ if (__rpc_get_local_uid(xprt, &uid) < 0)
+ rpcbreg.r_owner = "unknown";
+ else if (uid == 0)
+ rpcbreg.r_owner = "superuser";
+ else {
+ /* r_owner will be strdup-ed later */
+ snprintf(uidbuf, sizeof uidbuf, "%d", uid);
+ rpcbreg.r_owner = uidbuf;
+ }
+
+ rpcbreg.r_prog = reg.pm_prog;
+ rpcbreg.r_vers = reg.pm_vers;
+
+ if (op == PMAPPROC_SET) {
+ char buf[32];
+
+ snprintf(buf, sizeof buf, "0.0.0.0.%d.%d",
+ (int)((reg.pm_port >> 8) & 0xff),
+ (int)(reg.pm_port & 0xff));
+ rpcbreg.r_addr = buf;
+ if (reg.pm_prot == IPPROTO_UDP) {
+ rpcbreg.r_netid = udptrans;
+ } else if (reg.pm_prot == IPPROTO_TCP) {
+ rpcbreg.r_netid = tcptrans;
+ } else {
+ ans = FALSE;
+ goto done_change;
+ }
+ ans = map_set(&rpcbreg, rpcbreg.r_owner);
+ } else if (op == PMAPPROC_UNSET) {
+ bool_t ans1, ans2;
+
+ rpcbreg.r_addr = NULL;
+ rpcbreg.r_netid = tcptrans;
+ ans1 = map_unset(&rpcbreg, rpcbreg.r_owner);
+ rpcbreg.r_netid = udptrans;
+ ans2 = map_unset(&rpcbreg, rpcbreg.r_owner);
+ ans = ans1 || ans2;
+ } else {
+ ans = FALSE;
+ }
+done_change:
+ if ((!svc_sendreply(xprt, (xdrproc_t) xdr_long, (caddr_t) &ans)) &&
+ debugging) {
+ fprintf(stderr, "portmap: svc_sendreply\n");
+ if (doabort) {
+ rpcbind_abort();
+ }
+ }
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "%s\n", ans == TRUE ? "succeeded" : "failed");
+#endif
+ if (op == PMAPPROC_SET)
+ rpcbs_set(RPCBVERS_2_STAT, ans);
+ else
+ rpcbs_unset(RPCBVERS_2_STAT, ans);
+ return (TRUE);
+}
+
+/* ARGSUSED */
+static bool_t
+pmapproc_getport(struct svc_req *rqstp __unused, SVCXPRT *xprt)
+{
+ struct pmap reg;
+ long lport;
+ int port = 0;
+ struct pmaplist *fnd;
+#ifdef RPCBIND_DEBUG
+ char *uaddr;
+#endif
+
+ if (!svc_getargs(xprt, (xdrproc_t) xdr_pmap, (char *)&reg)) {
+ svcerr_decode(xprt);
+ return (FALSE);
+ }
+
+ if (!check_access(xprt, PMAPPROC_GETPORT, &reg, PMAPVERS)) {
+ svcerr_weakauth(xprt);
+ return FALSE;
+ }
+
+#ifdef RPCBIND_DEBUG
+ if (debugging) {
+ uaddr = taddr2uaddr(rpcbind_get_conf(xprt->xp_netid),
+ svc_getrpccaller(xprt));
+ fprintf(stderr, "PMAP_GETPORT req for (%lu, %lu, %s) from %s :",
+ reg.pm_prog, reg.pm_vers,
+ reg.pm_prot == IPPROTO_UDP ? "udp" : "tcp", uaddr);
+ free(uaddr);
+ }
+#endif
+ fnd = find_service_pmap(reg.pm_prog, reg.pm_vers, reg.pm_prot);
+ if (fnd) {
+ char serveuaddr[32], *ua;
+ int h1, h2, h3, h4, p1, p2;
+ char *netid;
+
+ if (reg.pm_prot == IPPROTO_UDP) {
+ ua = udp_uaddr;
+ netid = udptrans;
+ } else {
+ ua = tcp_uaddr; /* To get the len */
+ netid = tcptrans;
+ }
+ if (ua == NULL) {
+ goto sendreply;
+ }
+ if (sscanf(ua, "%d.%d.%d.%d.%d.%d", &h1, &h2, &h3,
+ &h4, &p1, &p2) == 6) {
+ p1 = (fnd->pml_map.pm_port >> 8) & 0xff;
+ p2 = (fnd->pml_map.pm_port) & 0xff;
+ snprintf(serveuaddr, sizeof serveuaddr,
+ "%d.%d.%d.%d.%d.%d", h1, h2, h3, h4, p1, p2);
+ if (is_bound(netid, serveuaddr)) {
+ port = fnd->pml_map.pm_port;
+ } else { /* this service is dead; delete it */
+ delete_prog(reg.pm_prog);
+ }
+ }
+ }
+sendreply:
+ lport = port;
+ if ((!svc_sendreply(xprt, (xdrproc_t) xdr_long, (caddr_t)&lport)) &&
+ debugging) {
+ (void) fprintf(stderr, "portmap: svc_sendreply\n");
+ if (doabort) {
+ rpcbind_abort();
+ }
+ }
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "port = %d\n", port);
+#endif
+ rpcbs_getaddr(RPCBVERS_2_STAT, reg.pm_prog, reg.pm_vers,
+ reg.pm_prot == IPPROTO_UDP ? udptrans : tcptrans,
+ port ? udptrans : "");
+
+ return (TRUE);
+}
+
+/* ARGSUSED */
+static bool_t
+pmapproc_dump(struct svc_req *rqstp __unused, SVCXPRT *xprt)
+{
+ if (!svc_getargs(xprt, (xdrproc_t)xdr_void, NULL)) {
+ svcerr_decode(xprt);
+ return (FALSE);
+ }
+
+ if (!check_access(xprt, PMAPPROC_DUMP, NULL, PMAPVERS)) {
+ svcerr_weakauth(xprt);
+ return FALSE;
+ }
+
+ if ((!svc_sendreply(xprt, (xdrproc_t) xdr_pmaplist_ptr,
+ (caddr_t)&list_pml)) && debugging) {
+ if (debugging)
+ (void) fprintf(stderr, "portmap: svc_sendreply\n");
+ if (doabort) {
+ rpcbind_abort();
+ }
+ }
+ return (TRUE);
+}
+
+#endif /* PORTMAP */
diff --git a/usr.sbin/rpcbind/rpcb_stat.c b/usr.sbin/rpcbind/rpcb_stat.c
new file mode 100644
index 0000000..fd92d78
--- /dev/null
+++ b/usr.sbin/rpcbind/rpcb_stat.c
@@ -0,0 +1,205 @@
+/*
+ * $NetBSD: rpcb_stat.c,v 1.2 2000/07/04 20:27:40 matt Exp $
+ * $FreeBSD$
+ */
+/*-
+ * Copyright (c) 2009, Sun Microsystems, 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:
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of Sun Microsystems, Inc. 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 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 HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/* #pragma ident "@(#)rpcb_stat.c 1.7 94/04/25 SMI" */
+
+/*
+ * rpcb_stat.c
+ * Allows for gathering of statistics
+ *
+ * Copyright (c) 1990 by Sun Microsystems, Inc.
+ */
+
+#include <stdio.h>
+#include <netconfig.h>
+#include <rpc/rpc.h>
+#include <rpc/rpcb_prot.h>
+#include <sys/stat.h>
+#ifdef PORTMAP
+#include <rpc/pmap_prot.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include "rpcbind.h"
+
+static rpcb_stat_byvers inf;
+
+void
+rpcbs_init(void)
+{
+
+}
+
+void
+rpcbs_procinfo(rpcvers_t rtype, rpcproc_t proc)
+{
+ switch (rtype + 2) {
+#ifdef PORTMAP
+ case PMAPVERS: /* version 2 */
+ if (proc > rpcb_highproc_2)
+ return;
+ break;
+#endif
+ case RPCBVERS: /* version 3 */
+ if (proc > rpcb_highproc_3)
+ return;
+ break;
+ case RPCBVERS4: /* version 4 */
+ if (proc > rpcb_highproc_4)
+ return;
+ break;
+ default: return;
+ }
+ inf[rtype].info[proc]++;
+ return;
+}
+
+void
+rpcbs_set(rpcvers_t rtype, bool_t success)
+{
+ if ((rtype >= RPCBVERS_STAT) || (success == FALSE))
+ return;
+ inf[rtype].setinfo++;
+ return;
+}
+
+void
+rpcbs_unset(rpcvers_t rtype, bool_t success)
+{
+ if ((rtype >= RPCBVERS_STAT) || (success == FALSE))
+ return;
+ inf[rtype].unsetinfo++;
+ return;
+}
+
+void
+rpcbs_getaddr(rpcvers_t rtype, rpcprog_t prog, rpcvers_t vers, char *netid,
+ char *uaddr)
+{
+ rpcbs_addrlist *al;
+ struct netconfig *nconf;
+
+ if (rtype >= RPCBVERS_STAT)
+ return;
+ for (al = inf[rtype].addrinfo; al; al = al->next) {
+
+ if(al->netid == NULL)
+ return;
+ if ((al->prog == prog) && (al->vers == vers) &&
+ (strcmp(al->netid, netid) == 0)) {
+ if ((uaddr == NULL) || (uaddr[0] == 0))
+ al->failure++;
+ else
+ al->success++;
+ return;
+ }
+ }
+ nconf = rpcbind_get_conf(netid);
+ if (nconf == NULL) {
+ return;
+ }
+ al = (rpcbs_addrlist *) malloc(sizeof (rpcbs_addrlist));
+ if (al == NULL) {
+ return;
+ }
+ al->prog = prog;
+ al->vers = vers;
+ al->netid = nconf->nc_netid;
+ if ((uaddr == NULL) || (uaddr[0] == 0)) {
+ al->failure = 1;
+ al->success = 0;
+ } else {
+ al->failure = 0;
+ al->success = 1;
+ }
+ al->next = inf[rtype].addrinfo;
+ inf[rtype].addrinfo = al;
+}
+
+void
+rpcbs_rmtcall(rpcvers_t rtype, rpcproc_t rpcbproc, rpcprog_t prog,
+ rpcvers_t vers, rpcproc_t proc, char *netid, rpcblist_ptr rbl)
+{
+ rpcbs_rmtcalllist *rl;
+ struct netconfig *nconf;
+
+ if (rtype > RPCBVERS_STAT)
+ return;
+ for (rl = inf[rtype].rmtinfo; rl; rl = rl->next) {
+
+ if(rl->netid == NULL)
+ return;
+
+ if ((rl->prog == prog) && (rl->vers == vers) &&
+ (rl->proc == proc) &&
+ (strcmp(rl->netid, netid) == 0)) {
+ if ((rbl == NULL) ||
+ (rbl->rpcb_map.r_vers != vers))
+ rl->failure++;
+ else
+ rl->success++;
+ if (rpcbproc == RPCBPROC_INDIRECT)
+ rl->indirect++;
+ return;
+ }
+ }
+ nconf = rpcbind_get_conf(netid);
+ if (nconf == NULL) {
+ return;
+ }
+ rl = (rpcbs_rmtcalllist *) malloc(sizeof (rpcbs_rmtcalllist));
+ if (rl == NULL) {
+ return;
+ }
+ rl->prog = prog;
+ rl->vers = vers;
+ rl->proc = proc;
+ rl->netid = nconf->nc_netid;
+ if ((rbl == NULL) ||
+ (rbl->rpcb_map.r_vers != vers)) {
+ rl->failure = 1;
+ rl->success = 0;
+ } else {
+ rl->failure = 0;
+ rl->success = 1;
+ }
+ rl->indirect = 1;
+ rl->next = inf[rtype].rmtinfo;
+ inf[rtype].rmtinfo = rl;
+ return;
+}
+
+void *
+rpcbproc_getstat(void *arg __unused, struct svc_req *req __unused,
+ SVCXPRT *xprt __unused, rpcvers_t versnum __unused)
+{
+ return (void *)&inf;
+}
diff --git a/usr.sbin/rpcbind/rpcb_svc.c b/usr.sbin/rpcbind/rpcb_svc.c
new file mode 100644
index 0000000..7aa5393
--- /dev/null
+++ b/usr.sbin/rpcbind/rpcb_svc.c
@@ -0,0 +1,232 @@
+/* $NetBSD: rpcb_svc.c,v 1.1 2000/06/02 23:15:41 fvdl Exp $ */
+/* $FreeBSD$ */
+
+/*-
+ * Copyright (c) 2009, Sun Microsystems, 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:
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of Sun Microsystems, Inc. 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 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 HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (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 - 1991 by Sun Microsystems, Inc.
+ */
+
+/* #ident "@(#)rpcb_svc.c 1.16 93/07/05 SMI" */
+
+/*
+ * rpcb_svc.c
+ * The server procedure for the version 3 rpcbind (TLI).
+ *
+ * It maintains a separate list of all the registered services with the
+ * version 3 of rpcbind.
+ */
+#include <sys/types.h>
+#include <rpc/rpc.h>
+#include <rpc/rpcb_prot.h>
+#include <netconfig.h>
+#include <syslog.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "rpcbind.h"
+
+static void *rpcbproc_getaddr_3_local(void *, struct svc_req *, SVCXPRT *,
+ rpcvers_t);
+static void *rpcbproc_dump_3_local(void *, struct svc_req *, SVCXPRT *,
+ rpcvers_t);
+
+/*
+ * Called by svc_getreqset. There is a separate server handle for
+ * every transport that it waits on.
+ */
+void
+rpcb_service_3(struct svc_req *rqstp, SVCXPRT *transp)
+{
+ union {
+ RPCB rpcbproc_set_3_arg;
+ RPCB rpcbproc_unset_3_arg;
+ RPCB rpcbproc_getaddr_3_local_arg;
+ struct rpcb_rmtcallargs rpcbproc_callit_3_arg;
+ char *rpcbproc_uaddr2taddr_3_arg;
+ struct netbuf rpcbproc_taddr2uaddr_3_arg;
+ } argument;
+ char *result;
+ xdrproc_t xdr_argument, xdr_result;
+ void *(*local)(void *, struct svc_req *, SVCXPRT *, rpcvers_t);
+
+ rpcbs_procinfo(RPCBVERS_3_STAT, rqstp->rq_proc);
+
+ switch (rqstp->rq_proc) {
+ case NULLPROC:
+ /*
+ * Null proc call
+ */
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "RPCBPROC_NULL\n");
+#endif
+ /* This call just logs, no actual checks */
+ check_access(transp, rqstp->rq_proc, NULL, RPCBVERS);
+ (void) svc_sendreply(transp, (xdrproc_t)xdr_void, (char *)NULL);
+ return;
+
+ case RPCBPROC_SET:
+ xdr_argument = (xdrproc_t )xdr_rpcb;
+ xdr_result = (xdrproc_t )xdr_bool;
+ local = rpcbproc_set_com;
+ break;
+
+ case RPCBPROC_UNSET:
+ xdr_argument = (xdrproc_t)xdr_rpcb;
+ xdr_result = (xdrproc_t)xdr_bool;
+ local = rpcbproc_unset_com;
+ break;
+
+ case RPCBPROC_GETADDR:
+ xdr_argument = (xdrproc_t)xdr_rpcb;
+ xdr_result = (xdrproc_t)xdr_wrapstring;
+ local = rpcbproc_getaddr_3_local;
+ break;
+
+ case RPCBPROC_DUMP:
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "RPCBPROC_DUMP\n");
+#endif
+ xdr_argument = (xdrproc_t)xdr_void;
+ xdr_result = (xdrproc_t)xdr_rpcblist_ptr;
+ local = rpcbproc_dump_3_local;
+ break;
+
+ case RPCBPROC_CALLIT:
+ rpcbproc_callit_com(rqstp, transp, rqstp->rq_proc, RPCBVERS);
+ return;
+
+ case RPCBPROC_GETTIME:
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "RPCBPROC_GETTIME\n");
+#endif
+ xdr_argument = (xdrproc_t)xdr_void;
+ xdr_result = (xdrproc_t)xdr_u_long;
+ local = rpcbproc_gettime_com;
+ break;
+
+ case RPCBPROC_UADDR2TADDR:
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "RPCBPROC_UADDR2TADDR\n");
+#endif
+ xdr_argument = (xdrproc_t)xdr_wrapstring;
+ xdr_result = (xdrproc_t)xdr_netbuf;
+ local = rpcbproc_uaddr2taddr_com;
+ break;
+
+ case RPCBPROC_TADDR2UADDR:
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "RPCBPROC_TADDR2UADDR\n");
+#endif
+ xdr_argument = (xdrproc_t)xdr_netbuf;
+ xdr_result = (xdrproc_t)xdr_wrapstring;
+ local = rpcbproc_taddr2uaddr_com;
+ break;
+
+ default:
+ svcerr_noproc(transp);
+ return;
+ }
+ (void) memset((char *)&argument, 0, sizeof (argument));
+ if (!svc_getargs(transp, (xdrproc_t) xdr_argument,
+ (char *) &argument)) {
+ svcerr_decode(transp);
+ if (debugging)
+ (void) fprintf(stderr, "rpcbind: could not decode\n");
+ return;
+ }
+ if (!check_access(transp, rqstp->rq_proc, &argument, RPCBVERS)) {
+ svcerr_weakauth(transp);
+ goto done;
+ }
+ result = (*local)(&argument, rqstp, transp, RPCBVERS);
+ if (result != NULL && !svc_sendreply(transp, (xdrproc_t)xdr_result,
+ result)) {
+ svcerr_systemerr(transp);
+ if (debugging) {
+ (void) fprintf(stderr, "rpcbind: svc_sendreply\n");
+ if (doabort) {
+ rpcbind_abort();
+ }
+ }
+ }
+done:
+ if (!svc_freeargs(transp, (xdrproc_t)xdr_argument, (char *)
+ &argument)) {
+ if (debugging) {
+ (void) fprintf(stderr, "unable to free arguments\n");
+ if (doabort) {
+ rpcbind_abort();
+ }
+ }
+ }
+}
+
+/*
+ * Lookup the mapping for a program, version and return its
+ * address. Assuming that the caller wants the address of the
+ * server running on the transport on which the request came.
+ *
+ * We also try to resolve the universal address in terms of
+ * address of the caller.
+ */
+/* ARGSUSED */
+static void *
+rpcbproc_getaddr_3_local(void *arg, struct svc_req *rqstp __unused,
+ SVCXPRT *transp __unused, rpcvers_t versnum __unused)
+{
+ RPCB *regp = (RPCB *)arg;
+#ifdef RPCBIND_DEBUG
+ if (debugging) {
+ char *uaddr;
+
+ uaddr = taddr2uaddr(rpcbind_get_conf(transp->xp_netid),
+ svc_getrpccaller(transp));
+ fprintf(stderr, "RPCB_GETADDR req for (%lu, %lu, %s) from %s: ",
+ (unsigned long)regp->r_prog, (unsigned long)regp->r_vers,
+ regp->r_netid, uaddr);
+ free(uaddr);
+ }
+#endif
+ return (rpcbproc_getaddr_com(regp, rqstp, transp, RPCBVERS,
+ RPCB_ALLVERS));
+}
+
+/* ARGSUSED */
+static void *
+rpcbproc_dump_3_local(void *arg __unused, struct svc_req *rqstp __unused,
+ SVCXPRT *transp __unused, rpcvers_t versnum __unused)
+{
+ return ((void *)&list_rbl);
+}
diff --git a/usr.sbin/rpcbind/rpcb_svc_4.c b/usr.sbin/rpcbind/rpcb_svc_4.c
new file mode 100644
index 0000000..852abaf
--- /dev/null
+++ b/usr.sbin/rpcbind/rpcb_svc_4.c
@@ -0,0 +1,454 @@
+/*
+ * $NetBSD: rpcb_svc_4.c,v 1.1 2000/06/02 23:15:41 fvdl Exp $
+ * $FreeBSD$
+ */
+
+/*-
+ * Copyright (c) 2009, Sun Microsystems, 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:
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of Sun Microsystems, Inc. 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 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 HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (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 - 1991 by Sun Microsystems, Inc.
+ */
+
+/* #ident "@(#)rpcb_svc_4.c 1.8 93/07/05 SMI" */
+
+/*
+ * rpcb_svc_4.c
+ * The server procedure for the version 4 rpcbind.
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <rpc/rpc.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <netconfig.h>
+#include <syslog.h>
+#include <string.h>
+#include <stdlib.h>
+#include "rpcbind.h"
+
+static void *rpcbproc_getaddr_4_local(void *, struct svc_req *, SVCXPRT *,
+ rpcvers_t);
+static void *rpcbproc_getversaddr_4_local(void *, struct svc_req *, SVCXPRT *, rpcvers_t);
+static void *rpcbproc_getaddrlist_4_local
+ (void *, struct svc_req *, SVCXPRT *, rpcvers_t);
+static void free_rpcb_entry_list(rpcb_entry_list_ptr *);
+static void *rpcbproc_dump_4_local(void *, struct svc_req *, SVCXPRT *, rpcvers_t);
+
+/*
+ * Called by svc_getreqset. There is a separate server handle for
+ * every transport that it waits on.
+ */
+void
+rpcb_service_4(struct svc_req *rqstp, SVCXPRT *transp)
+{
+ union {
+ rpcb rpcbproc_set_4_arg;
+ rpcb rpcbproc_unset_4_arg;
+ rpcb rpcbproc_getaddr_4_local_arg;
+ char *rpcbproc_uaddr2taddr_4_arg;
+ struct netbuf rpcbproc_taddr2uaddr_4_arg;
+ } argument;
+ char *result;
+ xdrproc_t xdr_argument, xdr_result;
+ void *(*local)(void *, struct svc_req *, SVCXPRT *, rpcvers_t);
+
+ rpcbs_procinfo(RPCBVERS_4_STAT, rqstp->rq_proc);
+
+ switch (rqstp->rq_proc) {
+ case NULLPROC:
+ /*
+ * Null proc call
+ */
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "RPCBPROC_NULL\n");
+#endif
+ check_access(transp, rqstp->rq_proc, NULL, RPCBVERS4);
+ (void) svc_sendreply(transp, (xdrproc_t) xdr_void,
+ (char *)NULL);
+ return;
+
+ case RPCBPROC_SET:
+ /*
+ * Check to see whether the message came from
+ * loopback transports (for security reasons)
+ */
+ xdr_argument = (xdrproc_t)xdr_rpcb;
+ xdr_result = (xdrproc_t)xdr_bool;
+ local = rpcbproc_set_com;
+ break;
+
+ case RPCBPROC_UNSET:
+ /*
+ * Check to see whether the message came from
+ * loopback transports (for security reasons)
+ */
+ xdr_argument = (xdrproc_t)xdr_rpcb;
+ xdr_result = (xdrproc_t)xdr_bool;
+ local = rpcbproc_unset_com;
+ break;
+
+ case RPCBPROC_GETADDR:
+ xdr_argument = (xdrproc_t)xdr_rpcb;
+ xdr_result = (xdrproc_t)xdr_wrapstring;
+ local = rpcbproc_getaddr_4_local;
+ break;
+
+ case RPCBPROC_GETVERSADDR:
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "RPCBPROC_GETVERSADDR\n");
+#endif
+ xdr_argument = (xdrproc_t)xdr_rpcb;
+ xdr_result = (xdrproc_t)xdr_wrapstring;
+ local = rpcbproc_getversaddr_4_local;
+ break;
+
+ case RPCBPROC_DUMP:
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "RPCBPROC_DUMP\n");
+#endif
+ xdr_argument = (xdrproc_t)xdr_void;
+ xdr_result = (xdrproc_t)xdr_rpcblist_ptr;
+ local = rpcbproc_dump_4_local;
+ break;
+
+ case RPCBPROC_INDIRECT:
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "RPCBPROC_INDIRECT\n");
+#endif
+ rpcbproc_callit_com(rqstp, transp, rqstp->rq_proc, RPCBVERS4);
+ return;
+
+/* case RPCBPROC_CALLIT: */
+ case RPCBPROC_BCAST:
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "RPCBPROC_BCAST\n");
+#endif
+ rpcbproc_callit_com(rqstp, transp, rqstp->rq_proc, RPCBVERS4);
+ return;
+
+ case RPCBPROC_GETTIME:
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "RPCBPROC_GETTIME\n");
+#endif
+ xdr_argument = (xdrproc_t)xdr_void;
+ xdr_result = (xdrproc_t)xdr_u_long;
+ local = rpcbproc_gettime_com;
+ break;
+
+ case RPCBPROC_UADDR2TADDR:
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "RPCBPROC_UADDR2TADDR\n");
+#endif
+ xdr_argument = (xdrproc_t)xdr_wrapstring;
+ xdr_result = (xdrproc_t)xdr_netbuf;
+ local = rpcbproc_uaddr2taddr_com;
+ break;
+
+ case RPCBPROC_TADDR2UADDR:
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "RPCBPROC_TADDR2UADDR\n");
+#endif
+ xdr_argument = (xdrproc_t)xdr_netbuf;
+ xdr_result = (xdrproc_t)xdr_wrapstring;
+ local = rpcbproc_taddr2uaddr_com;
+ break;
+
+ case RPCBPROC_GETADDRLIST:
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "RPCBPROC_GETADDRLIST\n");
+#endif
+ xdr_argument = (xdrproc_t)xdr_rpcb;
+ xdr_result = (xdrproc_t)xdr_rpcb_entry_list_ptr;
+ local = rpcbproc_getaddrlist_4_local;
+ break;
+
+ case RPCBPROC_GETSTAT:
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "RPCBPROC_GETSTAT\n");
+#endif
+ xdr_argument = (xdrproc_t)xdr_void;
+ xdr_result = (xdrproc_t)xdr_rpcb_stat_byvers;
+ local = rpcbproc_getstat;
+ break;
+
+ default:
+ svcerr_noproc(transp);
+ return;
+ }
+ memset((char *)&argument, 0, sizeof (argument));
+ if (!svc_getargs(transp, (xdrproc_t) xdr_argument,
+ (char *)&argument)) {
+ svcerr_decode(transp);
+ if (debugging)
+ (void) fprintf(stderr, "rpcbind: could not decode\n");
+ return;
+ }
+ if (!check_access(transp, rqstp->rq_proc, &argument, RPCBVERS4)) {
+ svcerr_weakauth(transp);
+ goto done;
+ }
+ result = (*local)(&argument, rqstp, transp, RPCBVERS4);
+ if (result != NULL && !svc_sendreply(transp, (xdrproc_t) xdr_result,
+ result)) {
+ svcerr_systemerr(transp);
+ if (debugging) {
+ (void) fprintf(stderr, "rpcbind: svc_sendreply\n");
+ if (doabort) {
+ rpcbind_abort();
+ }
+ }
+ }
+done:
+ if (!svc_freeargs(transp, (xdrproc_t) xdr_argument,
+ (char *)&argument)) {
+ if (debugging) {
+ (void) fprintf(stderr, "unable to free arguments\n");
+ if (doabort) {
+ rpcbind_abort();
+ }
+ }
+ }
+ return;
+}
+
+/*
+ * Lookup the mapping for a program, version and return its
+ * address. Assuming that the caller wants the address of the
+ * server running on the transport on which the request came.
+ * Even if a service with a different version number is available,
+ * it will return that address. The client should check with an
+ * clnt_call to verify whether the service is the one that is desired.
+ * We also try to resolve the universal address in terms of
+ * address of the caller.
+ */
+/* ARGSUSED */
+static void *
+rpcbproc_getaddr_4_local(void *arg, struct svc_req *rqstp, SVCXPRT *transp,
+ rpcvers_t rpcbversnum __unused)
+{
+ RPCB *regp = (RPCB *)arg;
+#ifdef RPCBIND_DEBUG
+ if (debugging) {
+ char *uaddr;
+
+ uaddr = taddr2uaddr(rpcbind_get_conf(transp->xp_netid),
+ svc_getrpccaller(transp));
+ fprintf(stderr, "RPCB_GETADDR req for (%lu, %lu, %s) from %s: ",
+ (unsigned long)regp->r_prog, (unsigned long)regp->r_vers,
+ regp->r_netid, uaddr);
+ free(uaddr);
+ }
+#endif
+ return (rpcbproc_getaddr_com(regp, rqstp, transp, RPCBVERS4,
+ RPCB_ALLVERS));
+}
+
+/*
+ * Lookup the mapping for a program, version and return its
+ * address. Assuming that the caller wants the address of the
+ * server running on the transport on which the request came.
+ *
+ * We also try to resolve the universal address in terms of
+ * address of the caller.
+ */
+/* ARGSUSED */
+static void *
+rpcbproc_getversaddr_4_local(void *arg, struct svc_req *rqstp, SVCXPRT *transp,
+ rpcvers_t versnum __unused)
+{
+ RPCB *regp = (RPCB *)arg;
+#ifdef RPCBIND_DEBUG
+ if (debugging) {
+ char *uaddr;
+
+ uaddr = taddr2uaddr(rpcbind_get_conf(transp->xp_netid),
+ svc_getrpccaller(transp));
+ fprintf(stderr, "RPCB_GETVERSADDR rqst for (%lu, %lu, %s)"
+ " from %s : ",
+ (unsigned long)regp->r_prog, (unsigned long)regp->r_vers,
+ regp->r_netid, uaddr);
+ free(uaddr);
+ }
+#endif
+ return (rpcbproc_getaddr_com(regp, rqstp, transp, RPCBVERS4,
+ RPCB_ONEVERS));
+}
+
+/*
+ * Lookup the mapping for a program, version and return the
+ * addresses for all transports in the current transport family.
+ * We return a merged address.
+ */
+/* ARGSUSED */
+static void *
+rpcbproc_getaddrlist_4_local(void *arg, struct svc_req *rqstp __unused,
+ SVCXPRT *transp, rpcvers_t versnum __unused)
+{
+ RPCB *regp = (RPCB *)arg;
+ static rpcb_entry_list_ptr rlist;
+ register rpcblist_ptr rbl;
+ rpcb_entry_list_ptr rp, tail;
+ rpcprog_t prog;
+ rpcvers_t vers;
+ rpcb_entry *a;
+ struct netconfig *nconf;
+ struct netconfig *reg_nconf;
+ char *saddr, *maddr = NULL;
+
+ free_rpcb_entry_list(&rlist);
+ tail = NULL;
+ prog = regp->r_prog;
+ vers = regp->r_vers;
+ reg_nconf = rpcbind_get_conf(transp->xp_netid);
+ if (reg_nconf == NULL)
+ return (NULL);
+ if (*(regp->r_addr) != '\0') {
+ saddr = regp->r_addr;
+ } else {
+ saddr = NULL;
+ }
+#ifdef RPCBIND_DEBUG
+ if (debugging) {
+ fprintf(stderr, "r_addr: %s r_netid: %s nc_protofmly: %s\n",
+ regp->r_addr, regp->r_netid, reg_nconf->nc_protofmly);
+ }
+#endif
+ for (rbl = list_rbl; rbl != NULL; rbl = rbl->rpcb_next) {
+ if ((rbl->rpcb_map.r_prog == prog) &&
+ (rbl->rpcb_map.r_vers == vers)) {
+ nconf = rpcbind_get_conf(rbl->rpcb_map.r_netid);
+ if (nconf == NULL)
+ goto fail;
+ if (strcmp(nconf->nc_protofmly, reg_nconf->nc_protofmly)
+ != 0) {
+ continue; /* not same proto family */
+ }
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "\tmerge with: %s\n",
+ rbl->rpcb_map.r_addr);
+#endif
+ if ((maddr = mergeaddr(transp, rbl->rpcb_map.r_netid,
+ rbl->rpcb_map.r_addr, saddr)) == NULL) {
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, " FAILED\n");
+#endif
+ continue;
+ } else if (!maddr[0]) {
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, " SUCCEEDED, but port died - maddr: nullstring\n");
+#endif
+ /* The server died. Unset this combination */
+ delete_prog(regp->r_prog);
+ continue;
+ }
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, " SUCCEEDED maddr: %s\n", maddr);
+#endif
+ /*
+ * Add it to rlist.
+ */
+ rp = malloc(sizeof (rpcb_entry_list));
+ if (rp == NULL)
+ goto fail;
+ a = &rp->rpcb_entry_map;
+ a->r_maddr = maddr;
+ a->r_nc_netid = nconf->nc_netid;
+ a->r_nc_semantics = nconf->nc_semantics;
+ a->r_nc_protofmly = nconf->nc_protofmly;
+ a->r_nc_proto = nconf->nc_proto;
+ rp->rpcb_entry_next = NULL;
+ if (rlist == NULL) {
+ rlist = rp;
+ tail = rp;
+ } else {
+ tail->rpcb_entry_next = rp;
+ tail = rp;
+ }
+ rp = NULL;
+ }
+ }
+#ifdef RPCBIND_DEBUG
+ if (debugging) {
+ for (rp = rlist; rp; rp = rp->rpcb_entry_next) {
+ fprintf(stderr, "\t%s %s\n", rp->rpcb_entry_map.r_maddr,
+ rp->rpcb_entry_map.r_nc_proto);
+ }
+ }
+#endif
+ /*
+ * XXX: getaddrlist info is also being stuffed into getaddr.
+ * Perhaps wrong, but better than it not getting counted at all.
+ */
+ rpcbs_getaddr(RPCBVERS4 - 2, prog, vers, transp->xp_netid, maddr);
+ return (void *)&rlist;
+
+fail: free_rpcb_entry_list(&rlist);
+ return (NULL);
+}
+
+/*
+ * Free only the allocated structure, rest is all a pointer to some
+ * other data somewhere else.
+ */
+static void
+free_rpcb_entry_list(rpcb_entry_list_ptr *rlistp)
+{
+ register rpcb_entry_list_ptr rbl, tmp;
+
+ for (rbl = *rlistp; rbl != NULL; ) {
+ tmp = rbl;
+ rbl = rbl->rpcb_entry_next;
+ free((char *)tmp->rpcb_entry_map.r_maddr);
+ free((char *)tmp);
+ }
+ *rlistp = NULL;
+}
+
+/* ARGSUSED */
+static void *
+rpcbproc_dump_4_local(void *arg __unused, struct svc_req *req __unused,
+ SVCXPRT *xprt __unused, rpcvers_t versnum __unused)
+{
+ return ((void *)&list_rbl);
+}
diff --git a/usr.sbin/rpcbind/rpcb_svc_com.c b/usr.sbin/rpcbind/rpcb_svc_com.c
new file mode 100644
index 0000000..2ee2a4c
--- /dev/null
+++ b/usr.sbin/rpcbind/rpcb_svc_com.c
@@ -0,0 +1,1486 @@
+/* $NetBSD: rpcb_svc_com.c,v 1.9 2002/11/08 00:16:39 fvdl Exp $ */
+/* $FreeBSD$ */
+
+/*-
+ * Copyright (c) 2009, Sun Microsystems, 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:
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of Sun Microsystems, Inc. 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 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 HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (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 - 1991 by Sun Microsystems, Inc.
+ */
+
+/* #ident "@(#)rpcb_svc_com.c 1.18 94/05/02 SMI" */
+
+/*
+ * rpcb_svc_com.c
+ * The commom server procedure for the rpcbind.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include <rpc/rpc.h>
+#include <rpc/rpcb_prot.h>
+#include <rpc/svc_dg.h>
+#include <assert.h>
+#include <netconfig.h>
+#include <errno.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <stdio.h>
+#ifdef PORTMAP
+#include <netinet/in.h>
+#include <rpc/pmap_prot.h>
+#endif /* PORTMAP */
+#include <string.h>
+#include <stdlib.h>
+
+#include "rpcbind.h"
+
+#define RPC_BUF_MAX 65536 /* can be raised if required */
+
+static char *nullstring = "";
+static int rpcb_rmtcalls;
+
+struct rmtcallfd_list {
+ int fd;
+ SVCXPRT *xprt;
+ char *netid;
+ struct rmtcallfd_list *next;
+};
+
+#define NFORWARD 64
+#define MAXTIME_OFF 300 /* 5 minutes */
+
+struct finfo {
+ int flag;
+#define FINFO_ACTIVE 0x1
+ u_int32_t caller_xid;
+ struct netbuf *caller_addr;
+ u_int32_t forward_xid;
+ int forward_fd;
+ char *uaddr;
+ rpcproc_t reply_type;
+ rpcvers_t versnum;
+ time_t time;
+};
+static struct finfo FINFO[NFORWARD];
+
+
+static bool_t xdr_encap_parms(XDR *, struct encap_parms *);
+static bool_t xdr_rmtcall_args(XDR *, struct r_rmtcall_args *);
+static bool_t xdr_rmtcall_result(XDR *, struct r_rmtcall_args *);
+static bool_t xdr_opaque_parms(XDR *, struct r_rmtcall_args *);
+static int find_rmtcallfd_by_netid(char *);
+static SVCXPRT *find_rmtcallxprt_by_fd(int);
+static int forward_register(u_int32_t, struct netbuf *, int, char *,
+ rpcproc_t, rpcvers_t, u_int32_t *);
+static struct finfo *forward_find(u_int32_t);
+static int free_slot_by_xid(u_int32_t);
+static int free_slot_by_index(int);
+static int netbufcmp(struct netbuf *, struct netbuf *);
+static struct netbuf *netbufdup(struct netbuf *);
+static void netbuffree(struct netbuf *);
+static int check_rmtcalls(struct pollfd *, int);
+static void xprt_set_caller(SVCXPRT *, struct finfo *);
+static void send_svcsyserr(SVCXPRT *, struct finfo *);
+static void handle_reply(int, SVCXPRT *);
+static void find_versions(rpcprog_t, char *, rpcvers_t *, rpcvers_t *);
+static rpcblist_ptr find_service(rpcprog_t, rpcvers_t, char *);
+static char *getowner(SVCXPRT *, char *, size_t);
+static int add_pmaplist(RPCB *);
+static int del_pmaplist(RPCB *);
+
+/*
+ * Set a mapping of program, version, netid
+ */
+/* ARGSUSED */
+void *
+rpcbproc_set_com(void *arg, struct svc_req *rqstp __unused, SVCXPRT *transp,
+ rpcvers_t rpcbversnum)
+{
+ RPCB *regp = (RPCB *)arg;
+ static bool_t ans;
+ char owner[64];
+
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "RPCB_SET request for (%lu, %lu, %s, %s) : ",
+ (unsigned long)regp->r_prog, (unsigned long)regp->r_vers,
+ regp->r_netid, regp->r_addr);
+#endif
+ ans = map_set(regp, getowner(transp, owner, sizeof owner));
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "%s\n", ans == TRUE ? "succeeded" : "failed");
+#endif
+ /* XXX: should have used some defined constant here */
+ rpcbs_set(rpcbversnum - 2, ans);
+ return (void *)&ans;
+}
+
+bool_t
+map_set(RPCB *regp, char *owner)
+{
+ RPCB reg, *a;
+ rpcblist_ptr rbl, fnd;
+
+ reg = *regp;
+ /*
+ * check to see if already used
+ * find_service returns a hit even if
+ * the versions don't match, so check for it
+ */
+ fnd = find_service(reg.r_prog, reg.r_vers, reg.r_netid);
+ if (fnd && (fnd->rpcb_map.r_vers == reg.r_vers)) {
+ if (!strcmp(fnd->rpcb_map.r_addr, reg.r_addr))
+ /*
+ * if these match then it is already
+ * registered so just say "OK".
+ */
+ return (TRUE);
+ else
+ return (FALSE);
+ }
+ /*
+ * add to the end of the list
+ */
+ rbl = malloc(sizeof (RPCBLIST));
+ if (rbl == NULL)
+ return (FALSE);
+ a = &(rbl->rpcb_map);
+ a->r_prog = reg.r_prog;
+ a->r_vers = reg.r_vers;
+ a->r_netid = strdup(reg.r_netid);
+ a->r_addr = strdup(reg.r_addr);
+ a->r_owner = strdup(owner);
+ if (!a->r_addr || !a->r_netid || !a->r_owner) {
+ if (a->r_netid)
+ free(a->r_netid);
+ if (a->r_addr)
+ free(a->r_addr);
+ if (a->r_owner)
+ free(a->r_owner);
+ free(rbl);
+ return (FALSE);
+ }
+ rbl->rpcb_next = (rpcblist_ptr)NULL;
+ if (list_rbl == NULL) {
+ list_rbl = rbl;
+ } else {
+ for (fnd = list_rbl; fnd->rpcb_next;
+ fnd = fnd->rpcb_next)
+ ;
+ fnd->rpcb_next = rbl;
+ }
+#ifdef PORTMAP
+ (void) add_pmaplist(regp);
+#endif
+ return (TRUE);
+}
+
+/*
+ * Unset a mapping of program, version, netid
+ */
+/* ARGSUSED */
+void *
+rpcbproc_unset_com(void *arg, struct svc_req *rqstp __unused, SVCXPRT *transp,
+ rpcvers_t rpcbversnum)
+{
+ RPCB *regp = (RPCB *)arg;
+ static bool_t ans;
+ char owner[64];
+
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "RPCB_UNSET request for (%lu, %lu, %s) : ",
+ (unsigned long)regp->r_prog, (unsigned long)regp->r_vers,
+ regp->r_netid);
+#endif
+ ans = map_unset(regp, getowner(transp, owner, sizeof owner));
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "%s\n", ans == TRUE ? "succeeded" : "failed");
+#endif
+ /* XXX: should have used some defined constant here */
+ rpcbs_unset(rpcbversnum - 2, ans);
+ return (void *)&ans;
+}
+
+bool_t
+map_unset(RPCB *regp, char *owner)
+{
+ int ans = 0;
+ rpcblist_ptr rbl, prev, tmp;
+
+ if (owner == NULL)
+ return (0);
+
+ for (prev = NULL, rbl = list_rbl; rbl; /* cstyle */) {
+ if ((rbl->rpcb_map.r_prog != regp->r_prog) ||
+ (rbl->rpcb_map.r_vers != regp->r_vers) ||
+ (regp->r_netid[0] && strcasecmp(regp->r_netid,
+ rbl->rpcb_map.r_netid))) {
+ /* both rbl & prev move forwards */
+ prev = rbl;
+ rbl = rbl->rpcb_next;
+ continue;
+ }
+ /*
+ * Check whether appropriate uid. Unset only
+ * if superuser or the owner itself.
+ */
+ if (strcmp(owner, "superuser") &&
+ strcmp(rbl->rpcb_map.r_owner, owner))
+ return (0);
+ /* found it; rbl moves forward, prev stays */
+ ans = 1;
+ tmp = rbl;
+ rbl = rbl->rpcb_next;
+ if (prev == NULL)
+ list_rbl = rbl;
+ else
+ prev->rpcb_next = rbl;
+ free(tmp->rpcb_map.r_addr);
+ free(tmp->rpcb_map.r_netid);
+ free(tmp->rpcb_map.r_owner);
+ free(tmp);
+ }
+#ifdef PORTMAP
+ if (ans)
+ (void) del_pmaplist(regp);
+#endif
+ /*
+ * We return 1 either when the entry was not there or it
+ * was able to unset it. It can come to this point only if
+ * atleast one of the conditions is true.
+ */
+ return (1);
+}
+
+void
+delete_prog(unsigned int prog)
+{
+ RPCB reg;
+ register rpcblist_ptr rbl;
+
+ for (rbl = list_rbl; rbl != NULL; rbl = rbl->rpcb_next) {
+ if ((rbl->rpcb_map.r_prog != prog))
+ continue;
+ if (is_bound(rbl->rpcb_map.r_netid, rbl->rpcb_map.r_addr))
+ continue;
+ reg.r_prog = rbl->rpcb_map.r_prog;
+ reg.r_vers = rbl->rpcb_map.r_vers;
+ reg.r_netid = strdup(rbl->rpcb_map.r_netid);
+ (void) map_unset(&reg, "superuser");
+ free(reg.r_netid);
+ }
+}
+
+void *
+rpcbproc_getaddr_com(RPCB *regp, struct svc_req *rqstp __unused,
+ SVCXPRT *transp, rpcvers_t rpcbversnum, rpcvers_t verstype)
+{
+ static char *uaddr;
+ char *saddr = NULL;
+ rpcblist_ptr fnd;
+
+ if (uaddr != NULL && uaddr != nullstring) {
+ free(uaddr);
+ uaddr = NULL;
+ }
+ fnd = find_service(regp->r_prog, regp->r_vers, transp->xp_netid);
+ if (fnd && ((verstype == RPCB_ALLVERS) ||
+ (regp->r_vers == fnd->rpcb_map.r_vers))) {
+ if (*(regp->r_addr) != '\0') { /* may contain a hint about */
+ saddr = regp->r_addr; /* the interface that we */
+ } /* should use */
+ if (!(uaddr = mergeaddr(transp, transp->xp_netid,
+ fnd->rpcb_map.r_addr, saddr))) {
+ /* Try whatever we have */
+ uaddr = strdup(fnd->rpcb_map.r_addr);
+ } else if (!uaddr[0]) {
+ /*
+ * The server died. Unset all versions of this prog.
+ */
+ delete_prog(regp->r_prog);
+ uaddr = nullstring;
+ }
+ } else {
+ uaddr = nullstring;
+ }
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "getaddr: %s\n", uaddr);
+#endif
+ /* XXX: should have used some defined constant here */
+ rpcbs_getaddr(rpcbversnum - 2, regp->r_prog, regp->r_vers,
+ transp->xp_netid, uaddr);
+ return (void *)&uaddr;
+}
+
+/* ARGSUSED */
+void *
+rpcbproc_gettime_com(void *arg __unused, struct svc_req *rqstp __unused,
+ SVCXPRT *transp __unused, rpcvers_t rpcbversnum __unused)
+{
+ static time_t curtime;
+
+ (void) time(&curtime);
+ return (void *)&curtime;
+}
+
+/*
+ * Convert uaddr to taddr. Should be used only by
+ * local servers/clients. (kernel level stuff only)
+ */
+/* ARGSUSED */
+void *
+rpcbproc_uaddr2taddr_com(void *arg, struct svc_req *rqstp __unused,
+ SVCXPRT *transp, rpcvers_t rpcbversnum __unused)
+{
+ char **uaddrp = (char **)arg;
+ struct netconfig *nconf;
+ static struct netbuf nbuf;
+ static struct netbuf *taddr;
+
+ if (taddr) {
+ free(taddr->buf);
+ free(taddr);
+ taddr = NULL;
+ }
+ if (((nconf = rpcbind_get_conf(transp->xp_netid)) == NULL) ||
+ ((taddr = uaddr2taddr(nconf, *uaddrp)) == NULL)) {
+ (void) memset((char *)&nbuf, 0, sizeof (struct netbuf));
+ return (void *)&nbuf;
+ }
+ return (void *)taddr;
+}
+
+/*
+ * Convert taddr to uaddr. Should be used only by
+ * local servers/clients. (kernel level stuff only)
+ */
+/* ARGSUSED */
+void *
+rpcbproc_taddr2uaddr_com(void *arg, struct svc_req *rqstp __unused,
+ SVCXPRT *transp, rpcvers_t rpcbversnum __unused)
+{
+ struct netbuf *taddr = (struct netbuf *)arg;
+ static char *uaddr;
+ struct netconfig *nconf;
+
+#ifdef CHEW_FDS
+ int fd;
+
+ if ((fd = open("/dev/null", O_RDONLY)) == -1) {
+ uaddr = (char *)strerror(errno);
+ return (&uaddr);
+ }
+#endif /* CHEW_FDS */
+ if (uaddr != NULL && uaddr != nullstring) {
+ free(uaddr);
+ uaddr = NULL;
+ }
+ if (((nconf = rpcbind_get_conf(transp->xp_netid)) == NULL) ||
+ ((uaddr = taddr2uaddr(nconf, taddr)) == NULL)) {
+ uaddr = nullstring;
+ }
+ return (void *)&uaddr;
+}
+
+
+static bool_t
+xdr_encap_parms(XDR *xdrs, struct encap_parms *epp)
+{
+ return (xdr_bytes(xdrs, &(epp->args), (u_int *) &(epp->arglen), ~0));
+}
+
+/*
+ * XDR remote call arguments. It ignores the address part.
+ * written for XDR_DECODE direction only
+ */
+static bool_t
+xdr_rmtcall_args(XDR *xdrs, struct r_rmtcall_args *cap)
+{
+ /* does not get the address or the arguments */
+ if (xdr_u_int32_t(xdrs, &(cap->rmt_prog)) &&
+ xdr_u_int32_t(xdrs, &(cap->rmt_vers)) &&
+ xdr_u_int32_t(xdrs, &(cap->rmt_proc))) {
+ return (xdr_encap_parms(xdrs, &(cap->rmt_args)));
+ }
+ return (FALSE);
+}
+
+/*
+ * XDR remote call results along with the address. Ignore
+ * program number, version number and proc number.
+ * Written for XDR_ENCODE direction only.
+ */
+static bool_t
+xdr_rmtcall_result(XDR *xdrs, struct r_rmtcall_args *cap)
+{
+ bool_t result;
+
+#ifdef PORTMAP
+ if (cap->rmt_localvers == PMAPVERS) {
+ int h1, h2, h3, h4, p1, p2;
+ u_long port;
+
+ /* interpret the universal address for TCP/IP */
+ if (sscanf(cap->rmt_uaddr, "%d.%d.%d.%d.%d.%d",
+ &h1, &h2, &h3, &h4, &p1, &p2) != 6)
+ return (FALSE);
+ port = ((p1 & 0xff) << 8) + (p2 & 0xff);
+ result = xdr_u_long(xdrs, &port);
+ } else
+#endif
+ if ((cap->rmt_localvers == RPCBVERS) ||
+ (cap->rmt_localvers == RPCBVERS4)) {
+ result = xdr_wrapstring(xdrs, &(cap->rmt_uaddr));
+ } else {
+ return (FALSE);
+ }
+ if (result == TRUE)
+ return (xdr_encap_parms(xdrs, &(cap->rmt_args)));
+ return (FALSE);
+}
+
+/*
+ * only worries about the struct encap_parms part of struct r_rmtcall_args.
+ * The arglen must already be set!!
+ */
+static bool_t
+xdr_opaque_parms(XDR *xdrs, struct r_rmtcall_args *cap)
+{
+ return (xdr_opaque(xdrs, cap->rmt_args.args, cap->rmt_args.arglen));
+}
+
+static struct rmtcallfd_list *rmthead;
+static struct rmtcallfd_list *rmttail;
+
+int
+create_rmtcall_fd(struct netconfig *nconf)
+{
+ int fd;
+ struct rmtcallfd_list *rmt;
+ SVCXPRT *xprt;
+
+ if ((fd = __rpc_nconf2fd(nconf)) == -1) {
+ if (debugging)
+ fprintf(stderr,
+ "create_rmtcall_fd: couldn't open \"%s\" (errno %d)\n",
+ nconf->nc_device, errno);
+ return (-1);
+ }
+ xprt = svc_tli_create(fd, 0, (struct t_bind *) 0, 0, 0);
+ if (xprt == NULL) {
+ if (debugging)
+ fprintf(stderr,
+ "create_rmtcall_fd: svc_tli_create failed\n");
+ return (-1);
+ }
+ rmt = malloc(sizeof (struct rmtcallfd_list));
+ if (rmt == NULL) {
+ syslog(LOG_ERR, "create_rmtcall_fd: no memory!");
+ return (-1);
+ }
+ rmt->xprt = xprt;
+ rmt->netid = strdup(nconf->nc_netid);
+ xprt->xp_netid = rmt->netid;
+ rmt->fd = fd;
+ rmt->next = NULL;
+ if (rmthead == NULL) {
+ rmthead = rmt;
+ rmttail = rmt;
+ } else {
+ rmttail->next = rmt;
+ rmttail = rmt;
+ }
+ /* XXX not threadsafe */
+ if (fd > svc_maxfd)
+ svc_maxfd = fd;
+ FD_SET(fd, &svc_fdset);
+ return (fd);
+}
+
+static int
+find_rmtcallfd_by_netid(char *netid)
+{
+ struct rmtcallfd_list *rmt;
+
+ for (rmt = rmthead; rmt != NULL; rmt = rmt->next) {
+ if (strcmp(netid, rmt->netid) == 0) {
+ return (rmt->fd);
+ }
+ }
+ return (-1);
+}
+
+static SVCXPRT *
+find_rmtcallxprt_by_fd(int fd)
+{
+ struct rmtcallfd_list *rmt;
+
+ for (rmt = rmthead; rmt != NULL; rmt = rmt->next) {
+ if (fd == rmt->fd) {
+ return (rmt->xprt);
+ }
+ }
+ return (NULL);
+}
+
+
+/*
+ * Call a remote procedure service. This procedure is very quiet when things
+ * go wrong. The proc is written to support broadcast rpc. In the broadcast
+ * case, a machine should shut-up instead of complain, lest the requestor be
+ * overrun with complaints at the expense of not hearing a valid reply.
+ * When receiving a request and verifying that the service exists, we
+ *
+ * receive the request
+ *
+ * open a new TLI endpoint on the same transport on which we received
+ * the original request
+ *
+ * remember the original request's XID (which requires knowing the format
+ * of the svc_dg_data structure)
+ *
+ * forward the request, with a new XID, to the requested service,
+ * remembering the XID used to send this request (for later use in
+ * reassociating the answer with the original request), the requestor's
+ * address, the file descriptor on which the forwarded request is
+ * made and the service's address.
+ *
+ * mark the file descriptor on which we anticipate receiving a reply from
+ * the service and one to select for in our private svc_run procedure
+ *
+ * At some time in the future, a reply will be received from the service to
+ * which we forwarded the request. At that time, we detect that the socket
+ * used was for forwarding (by looking through the finfo structures to see
+ * whether the fd corresponds to one of those) and call handle_reply() to
+ *
+ * receive the reply
+ *
+ * bundle the reply, along with the service's universal address
+ *
+ * create a SVCXPRT structure and use a version of svc_sendreply
+ * that allows us to specify the reply XID and destination, send the reply
+ * to the original requestor.
+ */
+
+void
+rpcbproc_callit_com(struct svc_req *rqstp, SVCXPRT *transp,
+ rpcproc_t reply_type, rpcvers_t versnum)
+{
+ register rpcblist_ptr rbl;
+ struct netconfig *nconf;
+ struct netbuf *caller;
+ struct r_rmtcall_args a;
+ char *buf_alloc = NULL, *outbufp;
+ char *outbuf_alloc = NULL;
+ char buf[RPC_BUF_MAX], outbuf[RPC_BUF_MAX];
+ struct netbuf *na = (struct netbuf *) NULL;
+ struct rpc_msg call_msg;
+ int outlen;
+ u_int sendsz;
+ XDR outxdr;
+ AUTH *auth;
+ int fd = -1;
+ char *uaddr, *m_uaddr = NULL, *local_uaddr = NULL;
+ u_int32_t *xidp;
+ struct __rpc_sockinfo si;
+ struct sockaddr *localsa;
+ struct netbuf tbuf;
+
+ if (!__rpc_fd2sockinfo(transp->xp_fd, &si)) {
+ if (reply_type == RPCBPROC_INDIRECT)
+ svcerr_systemerr(transp);
+ return;
+ }
+ if (si.si_socktype != SOCK_DGRAM)
+ return; /* Only datagram type accepted */
+ sendsz = __rpc_get_t_size(si.si_af, si.si_proto, UDPMSGSIZE);
+ if (sendsz == 0) { /* data transfer not supported */
+ if (reply_type == RPCBPROC_INDIRECT)
+ svcerr_systemerr(transp);
+ return;
+ }
+ /*
+ * Should be multiple of 4 for XDR.
+ */
+ sendsz = ((sendsz + 3) / 4) * 4;
+ if (sendsz > RPC_BUF_MAX) {
+#ifdef notyet
+ buf_alloc = alloca(sendsz); /* not in IDR2? */
+#else
+ buf_alloc = malloc(sendsz);
+#endif /* notyet */
+ if (buf_alloc == NULL) {
+ if (debugging)
+ fprintf(stderr,
+ "rpcbproc_callit_com: No Memory!\n");
+ if (reply_type == RPCBPROC_INDIRECT)
+ svcerr_systemerr(transp);
+ return;
+ }
+ a.rmt_args.args = buf_alloc;
+ } else {
+ a.rmt_args.args = buf;
+ }
+
+ call_msg.rm_xid = 0; /* For error checking purposes */
+ if (!svc_getargs(transp, (xdrproc_t) xdr_rmtcall_args, (char *) &a)) {
+ if (reply_type == RPCBPROC_INDIRECT)
+ svcerr_decode(transp);
+ if (debugging)
+ fprintf(stderr,
+ "rpcbproc_callit_com: svc_getargs failed\n");
+ goto error;
+ }
+
+ if (!check_callit(transp, &a, versnum)) {
+ svcerr_weakauth(transp);
+ goto error;
+ }
+
+ caller = svc_getrpccaller(transp);
+#ifdef RPCBIND_DEBUG
+ if (debugging) {
+ uaddr = taddr2uaddr(rpcbind_get_conf(transp->xp_netid), caller);
+ fprintf(stderr, "%s %s req for (%lu, %lu, %lu, %s) from %s : ",
+ versnum == PMAPVERS ? "pmap_rmtcall" :
+ versnum == RPCBVERS ? "rpcb_rmtcall" :
+ versnum == RPCBVERS4 ? "rpcb_indirect" : "unknown",
+ reply_type == RPCBPROC_INDIRECT ? "indirect" : "callit",
+ (unsigned long)a.rmt_prog, (unsigned long)a.rmt_vers,
+ (unsigned long)a.rmt_proc, transp->xp_netid,
+ uaddr ? uaddr : "unknown");
+ if (uaddr)
+ free(uaddr);
+ }
+#endif
+
+ rbl = find_service(a.rmt_prog, a.rmt_vers, transp->xp_netid);
+
+ rpcbs_rmtcall(versnum - 2, reply_type, a.rmt_prog, a.rmt_vers,
+ a.rmt_proc, transp->xp_netid, rbl);
+
+ if (rbl == (rpcblist_ptr)NULL) {
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "not found\n");
+#endif
+ if (reply_type == RPCBPROC_INDIRECT)
+ svcerr_noprog(transp);
+ goto error;
+ }
+ if (rbl->rpcb_map.r_vers != a.rmt_vers) {
+ if (reply_type == RPCBPROC_INDIRECT) {
+ rpcvers_t vers_low, vers_high;
+
+ find_versions(a.rmt_prog, transp->xp_netid,
+ &vers_low, &vers_high);
+ svcerr_progvers(transp, vers_low, vers_high);
+ }
+ goto error;
+ }
+
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "found at uaddr %s\n", rbl->rpcb_map.r_addr);
+#endif
+ /*
+ * Check whether this entry is valid and a server is present
+ * Mergeaddr() returns NULL if no such entry is present, and
+ * returns "" if the entry was present but the server is not
+ * present (i.e., it crashed).
+ */
+ if (reply_type == RPCBPROC_INDIRECT) {
+ uaddr = mergeaddr(transp, transp->xp_netid,
+ rbl->rpcb_map.r_addr, NULL);
+ if (uaddr == NULL || uaddr[0] == '\0') {
+ svcerr_noprog(transp);
+ if (uaddr != NULL)
+ free(uaddr);
+ goto error;
+ }
+ free(uaddr);
+ }
+ nconf = rpcbind_get_conf(transp->xp_netid);
+ if (nconf == (struct netconfig *)NULL) {
+ if (reply_type == RPCBPROC_INDIRECT)
+ svcerr_systemerr(transp);
+ if (debugging)
+ fprintf(stderr,
+ "rpcbproc_callit_com: rpcbind_get_conf failed\n");
+ goto error;
+ }
+ localsa = local_sa(((struct sockaddr *)caller->buf)->sa_family);
+ if (localsa == NULL) {
+ if (debugging)
+ fprintf(stderr,
+ "rpcbproc_callit_com: no local address\n");
+ goto error;
+ }
+ tbuf.len = tbuf.maxlen = localsa->sa_len;
+ tbuf.buf = localsa;
+ local_uaddr =
+ addrmerge(&tbuf, rbl->rpcb_map.r_addr, NULL, nconf->nc_netid);
+ m_uaddr = addrmerge(caller, rbl->rpcb_map.r_addr, NULL,
+ nconf->nc_netid);
+#ifdef RPCBIND_DEBUG
+ if (debugging)
+ fprintf(stderr, "merged uaddr %s\n", m_uaddr);
+#endif
+ if ((fd = find_rmtcallfd_by_netid(nconf->nc_netid)) == -1) {
+ if (reply_type == RPCBPROC_INDIRECT)
+ svcerr_systemerr(transp);
+ goto error;
+ }
+ xidp = __rpcb_get_dg_xidp(transp);
+ switch (forward_register(*xidp, caller, fd, m_uaddr, reply_type,
+ versnum, &call_msg.rm_xid)) {
+ case 1:
+ /* Success; forward_register() will free m_uaddr for us. */
+ m_uaddr = NULL;
+ break;
+ case 0:
+ /*
+ * A duplicate request for the slow server. Let's not
+ * beat on it any more.
+ */
+ if (debugging)
+ fprintf(stderr,
+ "rpcbproc_callit_com: duplicate request\n");
+ goto error;
+ case -1:
+ /* forward_register failed. Perhaps no memory. */
+ if (debugging)
+ fprintf(stderr,
+ "rpcbproc_callit_com: forward_register failed\n");
+ goto error;
+ }
+
+#ifdef DEBUG_RMTCALL
+ if (debugging)
+ fprintf(stderr,
+ "rpcbproc_callit_com: original XID %x, new XID %x\n",
+ *xidp, call_msg.rm_xid);
+#endif
+ call_msg.rm_direction = CALL;
+ call_msg.rm_call.cb_rpcvers = RPC_MSG_VERSION;
+ call_msg.rm_call.cb_prog = a.rmt_prog;
+ call_msg.rm_call.cb_vers = a.rmt_vers;
+ if (sendsz > RPC_BUF_MAX) {
+#ifdef notyet
+ outbuf_alloc = alloca(sendsz); /* not in IDR2? */
+#else
+ outbuf_alloc = malloc(sendsz);
+#endif /* notyet */
+ if (outbuf_alloc == NULL) {
+ if (reply_type == RPCBPROC_INDIRECT)
+ svcerr_systemerr(transp);
+ if (debugging)
+ fprintf(stderr,
+ "rpcbproc_callit_com: No memory!\n");
+ goto error;
+ }
+ xdrmem_create(&outxdr, outbuf_alloc, sendsz, XDR_ENCODE);
+ } else {
+ xdrmem_create(&outxdr, outbuf, sendsz, XDR_ENCODE);
+ }
+ if (!xdr_callhdr(&outxdr, &call_msg)) {
+ if (reply_type == RPCBPROC_INDIRECT)
+ svcerr_systemerr(transp);
+ if (debugging)
+ fprintf(stderr,
+ "rpcbproc_callit_com: xdr_callhdr failed\n");
+ goto error;
+ }
+ if (!xdr_u_int32_t(&outxdr, &(a.rmt_proc))) {
+ if (reply_type == RPCBPROC_INDIRECT)
+ svcerr_systemerr(transp);
+ if (debugging)
+ fprintf(stderr,
+ "rpcbproc_callit_com: xdr_u_long failed\n");
+ goto error;
+ }
+
+ if (rqstp->rq_cred.oa_flavor == AUTH_NULL) {
+ auth = authnone_create();
+ } else if (rqstp->rq_cred.oa_flavor == AUTH_SYS) {
+ struct authunix_parms *au;
+
+ au = (struct authunix_parms *)rqstp->rq_clntcred;
+ auth = authunix_create(au->aup_machname,
+ au->aup_uid, au->aup_gid,
+ au->aup_len, au->aup_gids);
+ if (auth == NULL) /* fall back */
+ auth = authnone_create();
+ } else {
+ /* we do not support any other authentication scheme */
+ if (debugging)
+ fprintf(stderr,
+"rpcbproc_callit_com: oa_flavor != AUTH_NONE and oa_flavor != AUTH_SYS\n");
+ if (reply_type == RPCBPROC_INDIRECT)
+ svcerr_weakauth(transp); /* XXX too strong.. */
+ goto error;
+ }
+ if (auth == NULL) {
+ if (reply_type == RPCBPROC_INDIRECT)
+ svcerr_systemerr(transp);
+ if (debugging)
+ fprintf(stderr,
+ "rpcbproc_callit_com: authwhatever_create returned NULL\n");
+ goto error;
+ }
+ if (!AUTH_MARSHALL(auth, &outxdr)) {
+ if (reply_type == RPCBPROC_INDIRECT)
+ svcerr_systemerr(transp);
+ AUTH_DESTROY(auth);
+ if (debugging)
+ fprintf(stderr,
+ "rpcbproc_callit_com: AUTH_MARSHALL failed\n");
+ goto error;
+ }
+ AUTH_DESTROY(auth);
+ if (!xdr_opaque_parms(&outxdr, &a)) {
+ if (reply_type == RPCBPROC_INDIRECT)
+ svcerr_systemerr(transp);
+ if (debugging)
+ fprintf(stderr,
+ "rpcbproc_callit_com: xdr_opaque_parms failed\n");
+ goto error;
+ }
+ outlen = (int) XDR_GETPOS(&outxdr);
+ if (outbuf_alloc)
+ outbufp = outbuf_alloc;
+ else
+ outbufp = outbuf;
+
+ na = uaddr2taddr(nconf, local_uaddr);
+ if (!na) {
+ if (reply_type == RPCBPROC_INDIRECT)
+ svcerr_systemerr(transp);
+ goto error;
+ }
+
+ if (sendto(fd, outbufp, outlen, 0, (struct sockaddr *)na->buf, na->len)
+ != outlen) {
+ if (debugging)
+ fprintf(stderr,
+ "rpcbproc_callit_com: sendto failed: errno %d\n", errno);
+ if (reply_type == RPCBPROC_INDIRECT)
+ svcerr_systemerr(transp);
+ goto error;
+ }
+ goto out;
+
+error:
+ if (call_msg.rm_xid != 0)
+ (void) free_slot_by_xid(call_msg.rm_xid);
+out:
+ if (local_uaddr)
+ free(local_uaddr);
+ if (buf_alloc)
+ free(buf_alloc);
+ if (outbuf_alloc)
+ free(outbuf_alloc);
+ if (na) {
+ free(na->buf);
+ free(na);
+ }
+ if (m_uaddr != NULL)
+ free(m_uaddr);
+}
+
+/*
+ * Makes an entry into the FIFO for the given request.
+ * Returns 1 on success, 0 if this is a duplicate request, or -1 on error.
+ * *callxidp is set to the xid of the call.
+ */
+static int
+forward_register(u_int32_t caller_xid, struct netbuf *caller_addr,
+ int forward_fd, char *uaddr, rpcproc_t reply_type,
+ rpcvers_t versnum, u_int32_t *callxidp)
+{
+ int i;
+ int j = 0;
+ time_t min_time, time_now;
+ static u_int32_t lastxid;
+ int entry = -1;
+
+ min_time = FINFO[0].time;
+ time_now = time((time_t *)0);
+ /* initialization */
+ if (lastxid == 0)
+ lastxid = time_now * NFORWARD;
+
+ /*
+ * Check if it is a duplicate entry. Then,
+ * try to find an empty slot. If not available, then
+ * use the slot with the earliest time.
+ */
+ for (i = 0; i < NFORWARD; i++) {
+ if (FINFO[i].flag & FINFO_ACTIVE) {
+ if ((FINFO[i].caller_xid == caller_xid) &&
+ (FINFO[i].reply_type == reply_type) &&
+ (FINFO[i].versnum == versnum) &&
+ (!netbufcmp(FINFO[i].caller_addr,
+ caller_addr))) {
+ FINFO[i].time = time((time_t *)0);
+ return (0); /* Duplicate entry */
+ } else {
+ /* Should we wait any longer */
+ if ((time_now - FINFO[i].time) > MAXTIME_OFF)
+ (void) free_slot_by_index(i);
+ }
+ }
+ if (entry == -1) {
+ if ((FINFO[i].flag & FINFO_ACTIVE) == 0) {
+ entry = i;
+ } else if (FINFO[i].time < min_time) {
+ j = i;
+ min_time = FINFO[i].time;
+ }
+ }
+ }
+ if (entry != -1) {
+ /* use this empty slot */
+ j = entry;
+ } else {
+ (void) free_slot_by_index(j);
+ }
+ if ((FINFO[j].caller_addr = netbufdup(caller_addr)) == NULL) {
+ return (-1);
+ }
+ rpcb_rmtcalls++; /* no of pending calls */
+ FINFO[j].flag = FINFO_ACTIVE;
+ FINFO[j].reply_type = reply_type;
+ FINFO[j].versnum = versnum;
+ FINFO[j].time = time_now;
+ FINFO[j].caller_xid = caller_xid;
+ FINFO[j].forward_fd = forward_fd;
+ /*
+ * Though uaddr is not allocated here, it will still be freed
+ * from free_slot_*().
+ */
+ FINFO[j].uaddr = uaddr;
+ lastxid = lastxid + NFORWARD;
+ /* Don't allow a zero xid below. */
+ if ((u_int32_t)(lastxid + NFORWARD) <= NFORWARD)
+ lastxid = NFORWARD;
+ FINFO[j].forward_xid = lastxid + j; /* encode slot */
+ *callxidp = FINFO[j].forward_xid; /* forward on this xid */
+ return (1);
+}
+
+static struct finfo *
+forward_find(u_int32_t reply_xid)
+{
+ int i;
+
+ i = reply_xid % (u_int32_t)NFORWARD;
+ if ((FINFO[i].flag & FINFO_ACTIVE) &&
+ (FINFO[i].forward_xid == reply_xid)) {
+ return (&FINFO[i]);
+ }
+ return (NULL);
+}
+
+static int
+free_slot_by_xid(u_int32_t xid)
+{
+ int entry;
+
+ entry = xid % (u_int32_t)NFORWARD;
+ return (free_slot_by_index(entry));
+}
+
+static int
+free_slot_by_index(int index)
+{
+ struct finfo *fi;
+
+ fi = &FINFO[index];
+ if (fi->flag & FINFO_ACTIVE) {
+ netbuffree(fi->caller_addr);
+ /* XXX may be too big, but can't access xprt array here */
+ if (fi->forward_fd >= svc_maxfd)
+ svc_maxfd--;
+ free(fi->uaddr);
+ fi->flag &= ~FINFO_ACTIVE;
+ rpcb_rmtcalls--;
+ return (1);
+ }
+ return (0);
+}
+
+static int
+netbufcmp(struct netbuf *n1, struct netbuf *n2)
+{
+ return ((n1->len != n2->len) || memcmp(n1->buf, n2->buf, n1->len));
+}
+
+static bool_t
+netbuf_copybuf(struct netbuf *dst, const struct netbuf *src)
+{
+ assert(src->len <= src->maxlen);
+
+ if (dst->maxlen < src->len || dst->buf == NULL) {
+ if (dst->buf != NULL)
+ free(dst->buf);
+ if ((dst->buf = calloc(1, src->maxlen)) == NULL)
+ return (FALSE);
+ dst->maxlen = src->maxlen;
+ }
+
+ dst->len = src->len;
+ memcpy(dst->buf, src->buf, src->len);
+
+ return (TRUE);
+}
+
+static struct netbuf *
+netbufdup(struct netbuf *ap)
+{
+ struct netbuf *np;
+
+ if ((np = calloc(1, sizeof(struct netbuf))) == NULL)
+ return (NULL);
+ if (netbuf_copybuf(np, ap) == FALSE) {
+ free(np);
+ return (NULL);
+ }
+ return (np);
+}
+
+static void
+netbuffree(struct netbuf *ap)
+{
+ free(ap->buf);
+ ap->buf = NULL;
+ free(ap);
+}
+
+
+#define MASKVAL (POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND)
+extern bool_t __svc_clean_idle(fd_set *, int, bool_t);
+
+void
+my_svc_run(void)
+{
+ size_t nfds;
+ struct pollfd pollfds[FD_SETSIZE];
+ int poll_ret, check_ret;
+ int n;
+#ifdef SVC_RUN_DEBUG
+ int i;
+#endif
+ register struct pollfd *p;
+ fd_set cleanfds;
+
+ for (;;) {
+ p = pollfds;
+ for (n = 0; n <= svc_maxfd; n++) {
+ if (FD_ISSET(n, &svc_fdset)) {
+ p->fd = n;
+ p->events = MASKVAL;
+ p++;
+ }
+ }
+ nfds = p - pollfds;
+ poll_ret = 0;
+#ifdef SVC_RUN_DEBUG
+ if (debugging) {
+ fprintf(stderr, "polling for read on fd < ");
+ for (i = 0, p = pollfds; i < nfds; i++, p++)
+ if (p->events)
+ fprintf(stderr, "%d ", p->fd);
+ fprintf(stderr, ">\n");
+ }
+#endif
+ switch (poll_ret = poll(pollfds, nfds, 30 * 1000)) {
+ case -1:
+ /*
+ * We ignore all errors, continuing with the assumption
+ * that it was set by the signal handlers (or any
+ * other outside event) and not caused by poll().
+ */
+ case 0:
+ cleanfds = svc_fdset;
+ __svc_clean_idle(&cleanfds, 30, FALSE);
+ continue;
+ default:
+#ifdef SVC_RUN_DEBUG
+ if (debugging) {
+ fprintf(stderr, "poll returned read fds < ");
+ for (i = 0, p = pollfds; i < nfds; i++, p++)
+ if (p->revents)
+ fprintf(stderr, "%d ", p->fd);
+ fprintf(stderr, ">\n");
+ }
+#endif
+ /*
+ * If we found as many replies on callback fds
+ * as the number of descriptors selectable which
+ * poll() returned, there can be no more so we
+ * don't call svc_getreq_poll. Otherwise, there
+ * must be another so we must call svc_getreq_poll.
+ */
+ if ((check_ret = check_rmtcalls(pollfds, nfds)) ==
+ poll_ret)
+ continue;
+ svc_getreq_poll(pollfds, poll_ret-check_ret);
+ }
+#ifdef SVC_RUN_DEBUG
+ if (debugging) {
+ fprintf(stderr, "svc_maxfd now %u\n", svc_maxfd);
+ }
+#endif
+ }
+}
+
+static int
+check_rmtcalls(struct pollfd *pfds, int nfds)
+{
+ int j, ncallbacks_found = 0, rmtcalls_pending;
+ SVCXPRT *xprt;
+
+ if (rpcb_rmtcalls == 0)
+ return (0);
+
+ rmtcalls_pending = rpcb_rmtcalls;
+ for (j = 0; j < nfds; j++) {
+ if ((xprt = find_rmtcallxprt_by_fd(pfds[j].fd)) != NULL) {
+ if (pfds[j].revents) {
+ ncallbacks_found++;
+#ifdef DEBUG_RMTCALL
+ if (debugging)
+ fprintf(stderr,
+"my_svc_run: polled on forwarding fd %d, netid %s - calling handle_reply\n",
+ pfds[j].fd, xprt->xp_netid);
+#endif
+ handle_reply(pfds[j].fd, xprt);
+ pfds[j].revents = 0;
+ if (ncallbacks_found >= rmtcalls_pending) {
+ break;
+ }
+ }
+ }
+ }
+ return (ncallbacks_found);
+}
+
+static void
+xprt_set_caller(SVCXPRT *xprt, struct finfo *fi)
+{
+ u_int32_t *xidp;
+
+ netbuf_copybuf(svc_getrpccaller(xprt), fi->caller_addr);
+ xidp = __rpcb_get_dg_xidp(xprt);
+ *xidp = fi->caller_xid;
+}
+
+/*
+ * Call svcerr_systemerr() only if RPCBVERS4
+ */
+static void
+send_svcsyserr(SVCXPRT *xprt, struct finfo *fi)
+{
+ if (fi->reply_type == RPCBPROC_INDIRECT) {
+ xprt_set_caller(xprt, fi);
+ svcerr_systemerr(xprt);
+ }
+ return;
+}
+
+static void
+handle_reply(int fd, SVCXPRT *xprt)
+{
+ XDR reply_xdrs;
+ struct rpc_msg reply_msg;
+ struct rpc_err reply_error;
+ char *buffer;
+ struct finfo *fi;
+ int inlen, pos, len;
+ struct r_rmtcall_args a;
+ struct sockaddr_storage ss;
+ socklen_t fromlen;
+#ifdef SVC_RUN_DEBUG
+ char *uaddr;
+#endif
+
+ buffer = malloc(RPC_BUF_MAX);
+ if (buffer == NULL)
+ goto done;
+
+ do {
+ fromlen = sizeof(ss);
+ inlen = recvfrom(fd, buffer, RPC_BUF_MAX, 0,
+ (struct sockaddr *)&ss, &fromlen);
+ } while (inlen < 0 && errno == EINTR);
+ if (inlen < 0) {
+ if (debugging)
+ fprintf(stderr,
+ "handle_reply: recvfrom returned %d, errno %d\n", inlen, errno);
+ goto done;
+ }
+
+ reply_msg.acpted_rply.ar_verf = _null_auth;
+ reply_msg.acpted_rply.ar_results.where = 0;
+ reply_msg.acpted_rply.ar_results.proc = (xdrproc_t) xdr_void;
+
+ xdrmem_create(&reply_xdrs, buffer, (u_int)inlen, XDR_DECODE);
+ if (!xdr_replymsg(&reply_xdrs, &reply_msg)) {
+ if (debugging)
+ (void) fprintf(stderr,
+ "handle_reply: xdr_replymsg failed\n");
+ goto done;
+ }
+ fi = forward_find(reply_msg.rm_xid);
+#ifdef SVC_RUN_DEBUG
+ if (debugging) {
+ fprintf(stderr, "handle_reply: reply xid: %d fi addr: %p\n",
+ reply_msg.rm_xid, fi);
+ }
+#endif
+ if (fi == NULL) {
+ goto done;
+ }
+ _seterr_reply(&reply_msg, &reply_error);
+ if (reply_error.re_status != RPC_SUCCESS) {
+ if (debugging)
+ (void) fprintf(stderr, "handle_reply: %s\n",
+ clnt_sperrno(reply_error.re_status));
+ send_svcsyserr(xprt, fi);
+ goto done;
+ }
+ pos = XDR_GETPOS(&reply_xdrs);
+ len = inlen - pos;
+ a.rmt_args.args = &buffer[pos];
+ a.rmt_args.arglen = len;
+ a.rmt_uaddr = fi->uaddr;
+ a.rmt_localvers = fi->versnum;
+
+ xprt_set_caller(xprt, fi);
+#ifdef SVC_RUN_DEBUG
+ uaddr = taddr2uaddr(rpcbind_get_conf("udp"),
+ svc_getrpccaller(xprt));
+ if (debugging) {
+ fprintf(stderr, "handle_reply: forwarding address %s to %s\n",
+ a.rmt_uaddr, uaddr ? uaddr : "unknown");
+ }
+ if (uaddr)
+ free(uaddr);
+#endif
+ svc_sendreply(xprt, (xdrproc_t) xdr_rmtcall_result, (char *) &a);
+done:
+ if (buffer)
+ free(buffer);
+
+ if (reply_msg.rm_xid == 0) {
+#ifdef SVC_RUN_DEBUG
+ if (debugging) {
+ fprintf(stderr, "handle_reply: NULL xid on exit!\n");
+ }
+#endif
+ } else
+ (void) free_slot_by_xid(reply_msg.rm_xid);
+ return;
+}
+
+static void
+find_versions(rpcprog_t prog, char *netid, rpcvers_t *lowvp, rpcvers_t *highvp)
+{
+ register rpcblist_ptr rbl;
+ unsigned int lowv = 0;
+ unsigned int highv = 0;
+
+ for (rbl = list_rbl; rbl != NULL; rbl = rbl->rpcb_next) {
+ if ((rbl->rpcb_map.r_prog != prog) ||
+ ((rbl->rpcb_map.r_netid != NULL) &&
+ (strcasecmp(rbl->rpcb_map.r_netid, netid) != 0)))
+ continue;
+ if (lowv == 0) {
+ highv = rbl->rpcb_map.r_vers;
+ lowv = highv;
+ } else if (rbl->rpcb_map.r_vers < lowv) {
+ lowv = rbl->rpcb_map.r_vers;
+ } else if (rbl->rpcb_map.r_vers > highv) {
+ highv = rbl->rpcb_map.r_vers;
+ }
+ }
+ *lowvp = lowv;
+ *highvp = highv;
+ return;
+}
+
+/*
+ * returns the item with the given program, version number and netid.
+ * If that version number is not found, it returns the item with that
+ * program number, so that address is now returned to the caller. The
+ * caller when makes a call to this program, version number, the call
+ * will fail and it will return with PROGVERS_MISMATCH. The user can
+ * then determine the highest and the lowest version number for this
+ * program using clnt_geterr() and use those program version numbers.
+ *
+ * Returns the RPCBLIST for the given prog, vers and netid
+ */
+static rpcblist_ptr
+find_service(rpcprog_t prog, rpcvers_t vers, char *netid)
+{
+ register rpcblist_ptr hit = NULL;
+ register rpcblist_ptr rbl;
+
+ for (rbl = list_rbl; rbl != NULL; rbl = rbl->rpcb_next) {
+ if ((rbl->rpcb_map.r_prog != prog) ||
+ ((rbl->rpcb_map.r_netid != NULL) &&
+ (strcasecmp(rbl->rpcb_map.r_netid, netid) != 0)))
+ continue;
+ hit = rbl;
+ if (rbl->rpcb_map.r_vers == vers)
+ break;
+ }
+ return (hit);
+}
+
+/*
+ * Copies the name associated with the uid of the caller and returns
+ * a pointer to it. Similar to getwd().
+ */
+static char *
+getowner(SVCXPRT *transp, char *owner, size_t ownersize)
+{
+ uid_t uid;
+
+ if (__rpc_get_local_uid(transp, &uid) < 0)
+ strlcpy(owner, "unknown", ownersize);
+ else if (uid == 0)
+ strlcpy(owner, "superuser", ownersize);
+ else
+ snprintf(owner, ownersize, "%d", uid);
+
+ return owner;
+}
+
+#ifdef PORTMAP
+/*
+ * Add this to the pmap list only if it is UDP or TCP.
+ */
+static int
+add_pmaplist(RPCB *arg)
+{
+ struct pmap pmap;
+ struct pmaplist *pml;
+ int h1, h2, h3, h4, p1, p2;
+
+ if (strcmp(arg->r_netid, udptrans) == 0) {
+ /* It is UDP! */
+ pmap.pm_prot = IPPROTO_UDP;
+ } else if (strcmp(arg->r_netid, tcptrans) == 0) {
+ /* It is TCP */
+ pmap.pm_prot = IPPROTO_TCP;
+ } else
+ /* Not an IP protocol */
+ return (0);
+
+ /* interpret the universal address for TCP/IP */
+ if (sscanf(arg->r_addr, "%d.%d.%d.%d.%d.%d",
+ &h1, &h2, &h3, &h4, &p1, &p2) != 6)
+ return (0);
+ pmap.pm_port = ((p1 & 0xff) << 8) + (p2 & 0xff);
+ pmap.pm_prog = arg->r_prog;
+ pmap.pm_vers = arg->r_vers;
+ /*
+ * add to END of list
+ */
+ pml = malloc(sizeof (struct pmaplist));
+ if (pml == NULL) {
+ (void) syslog(LOG_ERR, "rpcbind: no memory!\n");
+ return (1);
+ }
+ pml->pml_map = pmap;
+ pml->pml_next = NULL;
+ if (list_pml == NULL) {
+ list_pml = pml;
+ } else {
+ struct pmaplist *fnd;
+
+ /* Attach to the end of the list */
+ for (fnd = list_pml; fnd->pml_next; fnd = fnd->pml_next)
+ ;
+ fnd->pml_next = pml;
+ }
+ return (0);
+}
+
+/*
+ * Delete this from the pmap list only if it is UDP or TCP.
+ */
+static int
+del_pmaplist(RPCB *arg)
+{
+ struct pmaplist *pml;
+ struct pmaplist *prevpml, *fnd;
+ unsigned long prot;
+
+ if (strcmp(arg->r_netid, udptrans) == 0) {
+ /* It is UDP! */
+ prot = IPPROTO_UDP;
+ } else if (strcmp(arg->r_netid, tcptrans) == 0) {
+ /* It is TCP */
+ prot = IPPROTO_TCP;
+ } else if (arg->r_netid[0] == 0) {
+ prot = 0; /* Remove all occurrences */
+ } else {
+ /* Not an IP protocol */
+ return (0);
+ }
+ for (prevpml = NULL, pml = list_pml; pml; /* cstyle */) {
+ if ((pml->pml_map.pm_prog != arg->r_prog) ||
+ (pml->pml_map.pm_vers != arg->r_vers) ||
+ (prot && (pml->pml_map.pm_prot != prot))) {
+ /* both pml & prevpml move forwards */
+ prevpml = pml;
+ pml = pml->pml_next;
+ continue;
+ }
+ /* found it; pml moves forward, prevpml stays */
+ fnd = pml;
+ pml = pml->pml_next;
+ if (prevpml == NULL)
+ list_pml = pml;
+ else
+ prevpml->pml_next = pml;
+ free(fnd);
+ }
+ return (0);
+}
+#endif /* PORTMAP */
diff --git a/usr.sbin/rpcbind/rpcbind.8 b/usr.sbin/rpcbind/rpcbind.8
new file mode 100644
index 0000000..0df1fd0
--- /dev/null
+++ b/usr.sbin/rpcbind/rpcbind.8
@@ -0,0 +1,150 @@
+.\" @(#)rpcbind.1m 1.19 92/09/14 SMI; from SVr4
+.\" Copyright 1989 AT&T
+.\" Copyright 1991 Sun Microsystems, Inc.
+.\" $FreeBSD$
+.Dd March 6, 2014
+.Dt RPCBIND 8
+.Os
+.Sh NAME
+.Nm rpcbind
+.Nd universal addresses to RPC program number mapper
+.Sh SYNOPSIS
+.Nm
+.Op Fl 6adiLls
+.Op Fl h Ar bindip
+.Sh DESCRIPTION
+The
+.Nm
+utility is a server that converts
+.Tn RPC
+program numbers into
+universal addresses.
+It must be running on the host to be able to make
+.Tn RPC
+calls
+on a server on that machine.
+.Pp
+When an
+.Tn RPC
+service is started,
+it tells
+.Nm
+the address at which it is listening,
+and the
+.Tn RPC
+program numbers it is prepared to serve.
+When a client wishes to make an
+.Tn RPC
+call to a given program number,
+it first contacts
+.Nm
+on the server machine to determine
+the address where
+.Tn RPC
+requests should be sent.
+.Pp
+The
+.Nm
+utility should be started before any other RPC service.
+Normally, standard
+.Tn RPC
+servers are started by port monitors, so
+.Nm
+must be started before port monitors are invoked.
+.Pp
+When
+.Nm
+is started, it checks that certain name-to-address
+translation-calls function correctly.
+If they fail, the network configuration databases may be corrupt.
+Since
+.Tn RPC
+services cannot function correctly in this situation,
+.Nm
+reports the condition and terminates.
+.Pp
+The
+.Nm
+utility can only be started by the super-user.
+.Sh OPTIONS
+.Bl -tag -width indent
+.It Fl 6
+Bind to AF_INET6 (IPv6) addresses only.
+.It Fl a
+When debugging
+.Pq Fl d ,
+do an abort on errors.
+.It Fl d
+Run in debug mode.
+In this mode,
+.Nm
+will not fork when it starts, will print additional information
+during operation, and will abort on certain errors if
+.Fl a
+is also specified.
+With this option, the name-to-address translation consistency
+checks are shown in detail.
+.It Fl h Ar bindip
+Specify specific IP addresses to bind to for TCP and UDP requests.
+This option
+may be specified multiple times and is typically necessary when running
+on a multi-homed host.
+If no
+.Fl h
+option is specified,
+.Nm
+will bind to
+.Dv INADDR_ANY ,
+which could lead to problems on a multi-homed host due to
+.Nm
+returning a UDP packet from a different IP address than it was
+sent to.
+Note that when specifying IP addresses with
+.Fl h ,
+.Nm
+will automatically add
+.Li 127.0.0.1
+and if IPv6 is enabled,
+.Li ::1
+to the list.
+.It Fl i
+.Dq Insecure
+mode.
+Allow calls to SET and UNSET from any host.
+Normally
+.Nm
+accepts these requests only from the loopback interface for security reasons.
+This change is necessary for programs that were compiled with earlier
+versions of the rpc library and do not make those requests using the
+loopback interface.
+.It Fl L
+Allow old-style local connections over the loopback interface.
+Without this flag, local connections are only allowed over a local socket,
+.Pa /var/run/rpcbind.sock .
+.It Fl l
+Turn on libwrap connection logging.
+.It Fl s
+Cause
+.Nm
+to change to the user daemon as soon as possible.
+This causes
+.Nm
+to use non-privileged ports for outgoing connections, preventing non-privileged
+clients from using
+.Nm
+to connect to services from a privileged port.
+.It Fl W
+Enable libwrap (TCP wrappers) support.
+.El
+.Sh NOTES
+All RPC servers must be restarted if
+.Nm
+is restarted.
+.Sh FILES
+.Bl -tag -width /var/run/rpcbind.sock -compact
+.It Pa /var/run/rpcbind.sock
+.El
+.Sh SEE ALSO
+.Xr rpcbind 3 ,
+.Xr netconfig 5 ,
+.Xr rpcinfo 8
diff --git a/usr.sbin/rpcbind/rpcbind.c b/usr.sbin/rpcbind/rpcbind.c
new file mode 100644
index 0000000..67c7f7b
--- /dev/null
+++ b/usr.sbin/rpcbind/rpcbind.c
@@ -0,0 +1,868 @@
+/* $NetBSD: rpcbind.c,v 1.3 2002/11/08 00:16:40 fvdl Exp $ */
+/* $FreeBSD$ */
+
+/*-
+ * Copyright (c) 2009, Sun Microsystems, 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:
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of Sun Microsystems, Inc. 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 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 HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (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) 1984 - 1991 by Sun Microsystems, Inc.
+ */
+
+/* #ident "@(#)rpcbind.c 1.19 94/04/25 SMI" */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)rpcbind.c 1.35 89/04/21 Copyr 1984 Sun Micro";
+#endif
+#endif
+
+/*
+ * rpcbind.c
+ * Implements the program, version to address mapping for rpc.
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/errno.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/wait.h>
+#include <sys/signal.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <rpc/rpc.h>
+#include <rpc/rpc_com.h>
+#ifdef PORTMAP
+#include <netinet/in.h>
+#endif
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <netconfig.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <err.h>
+#include <libutil.h>
+#include <pwd.h>
+#include <string.h>
+#include <errno.h>
+#include "rpcbind.h"
+
+/* Global variables */
+int debugging = 0; /* Tell me what's going on */
+int doabort = 0; /* When debugging, do an abort on errors */
+rpcblist_ptr list_rbl; /* A list of version 3/4 rpcbind services */
+
+/* who to suid to if -s is given */
+#define RUN_AS "daemon"
+
+#define RPCBINDDLOCK "/var/run/rpcbind.lock"
+
+int runasdaemon = 0;
+int insecure = 0;
+int oldstyle_local = 0;
+#ifdef LIBWRAP
+int libwrap = 0;
+#endif
+int verboselog = 0;
+
+char **hosts = NULL;
+struct sockaddr **bound_sa;
+int ipv6_only = 0;
+int nhosts = 0;
+int on = 1;
+int rpcbindlockfd;
+
+#ifdef WARMSTART
+/* Local Variable */
+static int warmstart = 0; /* Grab an old copy of registrations. */
+#endif
+
+#ifdef PORTMAP
+struct pmaplist *list_pml; /* A list of version 2 rpcbind services */
+char *udptrans; /* Name of UDP transport */
+char *tcptrans; /* Name of TCP transport */
+char *udp_uaddr; /* Universal UDP address */
+char *tcp_uaddr; /* Universal TCP address */
+#endif
+static char servname[] = "rpcbind";
+static char superuser[] = "superuser";
+
+int main(int, char *[]);
+
+static int init_transport(struct netconfig *);
+static void rbllist_add(rpcprog_t, rpcvers_t, struct netconfig *,
+ struct netbuf *);
+static void terminate(int);
+static void parseargs(int, char *[]);
+static void update_bound_sa(void);
+
+int
+main(int argc, char *argv[])
+{
+ struct netconfig *nconf;
+ void *nc_handle; /* Net config handle */
+ struct rlimit rl;
+ int maxrec = RPC_MAXDATASIZE;
+
+ parseargs(argc, argv);
+
+ update_bound_sa();
+
+ /* Check that another rpcbind isn't already running. */
+ if ((rpcbindlockfd = (open(RPCBINDDLOCK,
+ O_RDONLY|O_CREAT, 0444))) == -1)
+ err(1, "%s", RPCBINDDLOCK);
+
+ if(flock(rpcbindlockfd, LOCK_EX|LOCK_NB) == -1 && errno == EWOULDBLOCK)
+ errx(1, "another rpcbind is already running. Aborting");
+
+ getrlimit(RLIMIT_NOFILE, &rl);
+ if (rl.rlim_cur < 128) {
+ if (rl.rlim_max <= 128)
+ rl.rlim_cur = rl.rlim_max;
+ else
+ rl.rlim_cur = 128;
+ setrlimit(RLIMIT_NOFILE, &rl);
+ }
+ openlog("rpcbind", LOG_CONS, LOG_DAEMON);
+ if (geteuid()) { /* This command allowed only to root */
+ fprintf(stderr, "Sorry. You are not superuser\n");
+ exit(1);
+ }
+ nc_handle = setnetconfig(); /* open netconfig file */
+ if (nc_handle == NULL) {
+ syslog(LOG_ERR, "could not read /etc/netconfig");
+ exit(1);
+ }
+#ifdef PORTMAP
+ udptrans = "";
+ tcptrans = "";
+#endif
+
+ nconf = getnetconfigent("local");
+ if (nconf == NULL)
+ nconf = getnetconfigent("unix");
+ if (nconf == NULL) {
+ syslog(LOG_ERR, "%s: can't find local transport\n", argv[0]);
+ exit(1);
+ }
+
+ rpc_control(RPC_SVC_CONNMAXREC_SET, &maxrec);
+
+ init_transport(nconf);
+
+ while ((nconf = getnetconfig(nc_handle))) {
+ if (nconf->nc_flag & NC_VISIBLE) {
+ if (ipv6_only == 1 && strcmp(nconf->nc_protofmly,
+ "inet") == 0) {
+ /* DO NOTHING */
+ } else
+ init_transport(nconf);
+ }
+ }
+ endnetconfig(nc_handle);
+
+ /* catch the usual termination signals for graceful exit */
+ (void) signal(SIGCHLD, reap);
+ (void) signal(SIGINT, terminate);
+ (void) signal(SIGTERM, terminate);
+ (void) signal(SIGQUIT, terminate);
+ /* ignore others that could get sent */
+ (void) signal(SIGPIPE, SIG_IGN);
+ (void) signal(SIGHUP, SIG_IGN);
+ (void) signal(SIGUSR1, SIG_IGN);
+ (void) signal(SIGUSR2, SIG_IGN);
+#ifdef WARMSTART
+ if (warmstart) {
+ read_warmstart();
+ }
+#endif
+ if (debugging) {
+ printf("rpcbind debugging enabled.");
+ if (doabort) {
+ printf(" Will abort on errors!\n");
+ } else {
+ printf("\n");
+ }
+ } else {
+ if (daemon(0, 0))
+ err(1, "fork failed");
+ }
+
+ if (runasdaemon) {
+ struct passwd *p;
+
+ if((p = getpwnam(RUN_AS)) == NULL) {
+ syslog(LOG_ERR, "cannot get uid of daemon: %m");
+ exit(1);
+ }
+ if (setuid(p->pw_uid) == -1) {
+ syslog(LOG_ERR, "setuid to daemon failed: %m");
+ exit(1);
+ }
+ }
+
+ network_init();
+
+ my_svc_run();
+ syslog(LOG_ERR, "svc_run returned unexpectedly");
+ rpcbind_abort();
+ /* NOTREACHED */
+
+ return 0;
+}
+
+/*
+ * Adds the entry into the rpcbind database.
+ * If PORTMAP, then for UDP and TCP, it adds the entries for version 2 also
+ * Returns 0 if succeeds, else fails
+ */
+static int
+init_transport(struct netconfig *nconf)
+{
+ int fd;
+ struct t_bind taddr;
+ struct addrinfo hints, *res = NULL;
+ struct __rpc_sockinfo si;
+ SVCXPRT *my_xprt;
+ int status; /* bound checking ? */
+ int aicode;
+ int addrlen;
+ int nhostsbak;
+ int bound;
+ struct sockaddr *sa;
+ u_int32_t host_addr[4]; /* IPv4 or IPv6 */
+ struct sockaddr_un sun;
+ mode_t oldmask;
+
+ if ((nconf->nc_semantics != NC_TPI_CLTS) &&
+ (nconf->nc_semantics != NC_TPI_COTS) &&
+ (nconf->nc_semantics != NC_TPI_COTS_ORD))
+ return (1); /* not my type */
+#ifdef ND_DEBUG
+ if (debugging) {
+ int i;
+ char **s;
+
+ (void)fprintf(stderr, "%s: %ld lookup routines :\n",
+ nconf->nc_netid, nconf->nc_nlookups);
+ for (i = 0, s = nconf->nc_lookups; i < nconf->nc_nlookups;
+ i++, s++)
+ fprintf(stderr, "[%d] - %s\n", i, *s);
+ }
+#endif
+
+ /*
+ * XXX - using RPC library internal functions.
+ */
+ if ((strcmp(nconf->nc_netid, "local") == 0) ||
+ (strcmp(nconf->nc_netid, "unix") == 0)) {
+ /*
+ * For other transports we call this later, for each socket we
+ * like to bind.
+ */
+ if ((fd = __rpc_nconf2fd(nconf)) < 0) {
+ int non_fatal = 0;
+ if (errno == EAFNOSUPPORT)
+ non_fatal = 1;
+ syslog(non_fatal?LOG_DEBUG:LOG_ERR, "cannot create socket for %s",
+ nconf->nc_netid);
+ return (1);
+ }
+ }
+
+ if (!__rpc_nconf2sockinfo(nconf, &si)) {
+ syslog(LOG_ERR, "cannot get information for %s",
+ nconf->nc_netid);
+ return (1);
+ }
+
+ if ((strcmp(nconf->nc_netid, "local") == 0) ||
+ (strcmp(nconf->nc_netid, "unix") == 0)) {
+ memset(&sun, 0, sizeof sun);
+ sun.sun_family = AF_LOCAL;
+ unlink(_PATH_RPCBINDSOCK);
+ strcpy(sun.sun_path, _PATH_RPCBINDSOCK);
+ sun.sun_len = SUN_LEN(&sun);
+ addrlen = sizeof (struct sockaddr_un);
+ sa = (struct sockaddr *)&sun;
+ } else {
+ /* Get rpcbind's address on this transport */
+
+ memset(&hints, 0, sizeof hints);
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = si.si_af;
+ hints.ai_socktype = si.si_socktype;
+ hints.ai_protocol = si.si_proto;
+ }
+
+ if ((strcmp(nconf->nc_netid, "local") != 0) &&
+ (strcmp(nconf->nc_netid, "unix") != 0)) {
+ /*
+ * If no hosts were specified, just bind to INADDR_ANY.
+ * Otherwise make sure 127.0.0.1 is added to the list.
+ */
+ nhostsbak = nhosts + 1;
+ hosts = realloc(hosts, nhostsbak * sizeof(char *));
+ if (nhostsbak == 1)
+ hosts[0] = "*";
+ else {
+ if (hints.ai_family == AF_INET) {
+ hosts[nhostsbak - 1] = "127.0.0.1";
+ } else if (hints.ai_family == AF_INET6) {
+ hosts[nhostsbak - 1] = "::1";
+ } else
+ return 1;
+ }
+
+ /*
+ * Bind to specific IPs if asked to
+ */
+ bound = 0;
+ while (nhostsbak > 0) {
+ --nhostsbak;
+ /*
+ * XXX - using RPC library internal functions.
+ */
+ if ((fd = __rpc_nconf2fd(nconf)) < 0) {
+ int non_fatal = 0;
+ if (errno == EAFNOSUPPORT &&
+ nconf->nc_semantics != NC_TPI_CLTS)
+ non_fatal = 1;
+ syslog(non_fatal ? LOG_DEBUG : LOG_ERR,
+ "cannot create socket for %s", nconf->nc_netid);
+ return (1);
+ }
+ switch (hints.ai_family) {
+ case AF_INET:
+ if (inet_pton(AF_INET, hosts[nhostsbak],
+ host_addr) == 1) {
+ hints.ai_flags &= AI_NUMERICHOST;
+ } else {
+ /*
+ * Skip if we have an AF_INET6 address.
+ */
+ if (inet_pton(AF_INET6,
+ hosts[nhostsbak], host_addr) == 1) {
+ close(fd);
+ continue;
+ }
+ }
+ break;
+ case AF_INET6:
+ if (inet_pton(AF_INET6, hosts[nhostsbak],
+ host_addr) == 1) {
+ hints.ai_flags &= AI_NUMERICHOST;
+ } else {
+ /*
+ * Skip if we have an AF_INET address.
+ */
+ if (inet_pton(AF_INET, hosts[nhostsbak],
+ host_addr) == 1) {
+ close(fd);
+ continue;
+ }
+ }
+ if (setsockopt(fd, IPPROTO_IPV6,
+ IPV6_V6ONLY, &on, sizeof on) < 0) {
+ syslog(LOG_ERR,
+ "can't set v6-only binding for "
+ "ipv6 socket: %m");
+ continue;
+ }
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * If no hosts were specified, just bind to INADDR_ANY
+ */
+ if (strcmp("*", hosts[nhostsbak]) == 0)
+ hosts[nhostsbak] = NULL;
+ if ((strcmp(nconf->nc_netid, "local") != 0) &&
+ (strcmp(nconf->nc_netid, "unix") != 0)) {
+ if ((aicode = getaddrinfo(hosts[nhostsbak],
+ servname, &hints, &res)) != 0) {
+ syslog(LOG_ERR,
+ "cannot get local address for %s: %s",
+ nconf->nc_netid, gai_strerror(aicode));
+ continue;
+ }
+ addrlen = res->ai_addrlen;
+ sa = (struct sockaddr *)res->ai_addr;
+ }
+ oldmask = umask(S_IXUSR|S_IXGRP|S_IXOTH);
+ if (bind(fd, sa, addrlen) != 0) {
+ syslog(LOG_ERR, "cannot bind %s on %s: %m",
+ (hosts[nhostsbak] == NULL) ? "*" :
+ hosts[nhostsbak], nconf->nc_netid);
+ if (res != NULL)
+ freeaddrinfo(res);
+ continue;
+ } else
+ bound = 1;
+ (void)umask(oldmask);
+
+ /* Copy the address */
+ taddr.addr.len = taddr.addr.maxlen = addrlen;
+ taddr.addr.buf = malloc(addrlen);
+ if (taddr.addr.buf == NULL) {
+ syslog(LOG_ERR,
+ "cannot allocate memory for %s address",
+ nconf->nc_netid);
+ if (res != NULL)
+ freeaddrinfo(res);
+ return 1;
+ }
+ memcpy(taddr.addr.buf, sa, addrlen);
+#ifdef ND_DEBUG
+ if (debugging) {
+ /*
+ * for debugging print out our universal
+ * address
+ */
+ char *uaddr;
+ struct netbuf nb;
+
+ nb.buf = sa;
+ nb.len = nb.maxlen = sa->sa_len;
+ uaddr = taddr2uaddr(nconf, &nb);
+ (void)fprintf(stderr,
+ "rpcbind : my address is %s\n", uaddr);
+ (void)free(uaddr);
+ }
+#endif
+
+ if (nconf->nc_semantics != NC_TPI_CLTS)
+ listen(fd, SOMAXCONN);
+
+ my_xprt = (SVCXPRT *)svc_tli_create(fd, nconf, &taddr,
+ RPC_MAXDATASIZE, RPC_MAXDATASIZE);
+ if (my_xprt == (SVCXPRT *)NULL) {
+ syslog(LOG_ERR, "%s: could not create service",
+ nconf->nc_netid);
+ goto error;
+ }
+ }
+ if (!bound)
+ return 1;
+ } else {
+ oldmask = umask(S_IXUSR|S_IXGRP|S_IXOTH);
+ if (bind(fd, sa, addrlen) < 0) {
+ syslog(LOG_ERR, "cannot bind %s: %m", nconf->nc_netid);
+ if (res != NULL)
+ freeaddrinfo(res);
+ return 1;
+ }
+ (void) umask(oldmask);
+
+ /* Copy the address */
+ taddr.addr.len = taddr.addr.maxlen = addrlen;
+ taddr.addr.buf = malloc(addrlen);
+ if (taddr.addr.buf == NULL) {
+ syslog(LOG_ERR, "cannot allocate memory for %s address",
+ nconf->nc_netid);
+ if (res != NULL)
+ freeaddrinfo(res);
+ return 1;
+ }
+ memcpy(taddr.addr.buf, sa, addrlen);
+#ifdef ND_DEBUG
+ if (debugging) {
+ /* for debugging print out our universal address */
+ char *uaddr;
+ struct netbuf nb;
+
+ nb.buf = sa;
+ nb.len = nb.maxlen = sa->sa_len;
+ uaddr = taddr2uaddr(nconf, &nb);
+ (void) fprintf(stderr, "rpcbind : my address is %s\n",
+ uaddr);
+ (void) free(uaddr);
+ }
+#endif
+
+ if (nconf->nc_semantics != NC_TPI_CLTS)
+ listen(fd, SOMAXCONN);
+
+ my_xprt = (SVCXPRT *)svc_tli_create(fd, nconf, &taddr,
+ RPC_MAXDATASIZE, RPC_MAXDATASIZE);
+ if (my_xprt == (SVCXPRT *)NULL) {
+ syslog(LOG_ERR, "%s: could not create service",
+ nconf->nc_netid);
+ goto error;
+ }
+ }
+
+#ifdef PORTMAP
+ /*
+ * Register both the versions for tcp/ip, udp/ip and local.
+ */
+ if ((strcmp(nconf->nc_protofmly, NC_INET) == 0 &&
+ (strcmp(nconf->nc_proto, NC_TCP) == 0 ||
+ strcmp(nconf->nc_proto, NC_UDP) == 0)) ||
+ (strcmp(nconf->nc_netid, "unix") == 0) ||
+ (strcmp(nconf->nc_netid, "local") == 0)) {
+ struct pmaplist *pml;
+
+ if (!svc_register(my_xprt, PMAPPROG, PMAPVERS,
+ pmap_service, 0)) {
+ syslog(LOG_ERR, "could not register on %s",
+ nconf->nc_netid);
+ goto error;
+ }
+ pml = malloc(sizeof (struct pmaplist));
+ if (pml == NULL) {
+ syslog(LOG_ERR, "no memory!");
+ exit(1);
+ }
+ pml->pml_map.pm_prog = PMAPPROG;
+ pml->pml_map.pm_vers = PMAPVERS;
+ pml->pml_map.pm_port = PMAPPORT;
+ if (strcmp(nconf->nc_proto, NC_TCP) == 0) {
+ if (tcptrans[0]) {
+ syslog(LOG_ERR,
+ "cannot have more than one TCP transport");
+ goto error;
+ }
+ tcptrans = strdup(nconf->nc_netid);
+ pml->pml_map.pm_prot = IPPROTO_TCP;
+
+ /* Let's snarf the universal address */
+ /* "h1.h2.h3.h4.p1.p2" */
+ tcp_uaddr = taddr2uaddr(nconf, &taddr.addr);
+ } else if (strcmp(nconf->nc_proto, NC_UDP) == 0) {
+ if (udptrans[0]) {
+ syslog(LOG_ERR,
+ "cannot have more than one UDP transport");
+ goto error;
+ }
+ udptrans = strdup(nconf->nc_netid);
+ pml->pml_map.pm_prot = IPPROTO_UDP;
+
+ /* Let's snarf the universal address */
+ /* "h1.h2.h3.h4.p1.p2" */
+ udp_uaddr = taddr2uaddr(nconf, &taddr.addr);
+ } else if (strcmp(nconf->nc_netid, "local") == 0)
+ pml->pml_map.pm_prot = IPPROTO_ST;
+ else if (strcmp(nconf->nc_netid, "unix") == 0)
+ pml->pml_map.pm_prot = IPPROTO_ST;
+ pml->pml_next = list_pml;
+ list_pml = pml;
+
+ /* Add version 3 information */
+ pml = malloc(sizeof (struct pmaplist));
+ if (pml == NULL) {
+ syslog(LOG_ERR, "no memory!");
+ exit(1);
+ }
+ pml->pml_map = list_pml->pml_map;
+ pml->pml_map.pm_vers = RPCBVERS;
+ pml->pml_next = list_pml;
+ list_pml = pml;
+
+ /* Add version 4 information */
+ pml = malloc (sizeof (struct pmaplist));
+ if (pml == NULL) {
+ syslog(LOG_ERR, "no memory!");
+ exit(1);
+ }
+ pml->pml_map = list_pml->pml_map;
+ pml->pml_map.pm_vers = RPCBVERS4;
+ pml->pml_next = list_pml;
+ list_pml = pml;
+
+ /* Also add version 2 stuff to rpcbind list */
+ rbllist_add(PMAPPROG, PMAPVERS, nconf, &taddr.addr);
+ }
+#endif
+
+ /* version 3 registration */
+ if (!svc_reg(my_xprt, RPCBPROG, RPCBVERS, rpcb_service_3, NULL)) {
+ syslog(LOG_ERR, "could not register %s version 3",
+ nconf->nc_netid);
+ goto error;
+ }
+ rbllist_add(RPCBPROG, RPCBVERS, nconf, &taddr.addr);
+
+ /* version 4 registration */
+ if (!svc_reg(my_xprt, RPCBPROG, RPCBVERS4, rpcb_service_4, NULL)) {
+ syslog(LOG_ERR, "could not register %s version 4",
+ nconf->nc_netid);
+ goto error;
+ }
+ rbllist_add(RPCBPROG, RPCBVERS4, nconf, &taddr.addr);
+
+ /* decide if bound checking works for this transport */
+ status = add_bndlist(nconf, &taddr.addr);
+#ifdef BIND_DEBUG
+ if (debugging) {
+ if (status < 0) {
+ fprintf(stderr, "Error in finding bind status for %s\n",
+ nconf->nc_netid);
+ } else if (status == 0) {
+ fprintf(stderr, "check binding for %s\n",
+ nconf->nc_netid);
+ } else if (status > 0) {
+ fprintf(stderr, "No check binding for %s\n",
+ nconf->nc_netid);
+ }
+ }
+#endif
+ /*
+ * rmtcall only supported on CLTS transports for now.
+ */
+ if (nconf->nc_semantics == NC_TPI_CLTS) {
+ status = create_rmtcall_fd(nconf);
+
+#ifdef BIND_DEBUG
+ if (debugging) {
+ if (status < 0) {
+ fprintf(stderr,
+ "Could not create rmtcall fd for %s\n",
+ nconf->nc_netid);
+ } else {
+ fprintf(stderr, "rmtcall fd for %s is %d\n",
+ nconf->nc_netid, status);
+ }
+ }
+#endif
+ }
+ return (0);
+error:
+ close(fd);
+ return (1);
+}
+
+/*
+ * Create the list of addresses that we're bound to. Normally, this
+ * list is empty because we're listening on the wildcard address
+ * (nhost == 0). If -h is specified on the command line, then
+ * bound_sa will have a list of the addresses that the program binds
+ * to specifically. This function takes that list and converts them to
+ * struct sockaddr * and stores them in bound_sa.
+ */
+static void
+update_bound_sa(void)
+{
+ struct addrinfo hints, *res = NULL;
+ int i;
+
+ if (nhosts == 0)
+ return;
+ bound_sa = malloc(sizeof(*bound_sa) * nhosts);
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ for (i = 0; i < nhosts; i++) {
+ if (getaddrinfo(hosts[i], NULL, &hints, &res) != 0)
+ continue;
+ bound_sa[i] = malloc(res->ai_addrlen);
+ memcpy(bound_sa[i], res->ai_addr, res->ai_addrlen);
+ }
+}
+
+/*
+ * Match the sa against the list of addresses we've bound to. If
+ * we've not specifically bound to anything, we match everything.
+ * Otherwise, if the IPv4 or IPv6 address matches one of the addresses
+ * in bound_sa, we return true. If not, we return false.
+ */
+int
+listen_addr(const struct sockaddr *sa)
+{
+ int i;
+
+ /*
+ * If nhosts == 0, then there were no -h options on the
+ * command line, so all addresses are addresses we're
+ * listening to.
+ */
+ if (nhosts == 0)
+ return 1;
+ for (i = 0; i < nhosts; i++) {
+ if (bound_sa[i] == NULL ||
+ sa->sa_family != bound_sa[i]->sa_family)
+ continue;
+ switch (sa->sa_family) {
+ case AF_INET:
+ if (memcmp(&SA2SINADDR(sa), &SA2SINADDR(bound_sa[i]),
+ sizeof(struct in_addr)) == 0)
+ return (1);
+ break;
+#ifdef INET6
+ case AF_INET6:
+ if (memcmp(&SA2SIN6ADDR(sa), &SA2SIN6ADDR(bound_sa[i]),
+ sizeof(struct in6_addr)) == 0)
+ return (1);
+ break;
+#endif
+ default:
+ break;
+ }
+ }
+ return (0);
+}
+
+static void
+rbllist_add(rpcprog_t prog, rpcvers_t vers, struct netconfig *nconf,
+ struct netbuf *addr)
+{
+ rpcblist_ptr rbl;
+
+ rbl = malloc(sizeof (rpcblist));
+ if (rbl == NULL) {
+ syslog(LOG_ERR, "no memory!");
+ exit(1);
+ }
+
+ rbl->rpcb_map.r_prog = prog;
+ rbl->rpcb_map.r_vers = vers;
+ rbl->rpcb_map.r_netid = strdup(nconf->nc_netid);
+ rbl->rpcb_map.r_addr = taddr2uaddr(nconf, addr);
+ rbl->rpcb_map.r_owner = strdup(superuser);
+ rbl->rpcb_next = list_rbl; /* Attach to global list */
+ list_rbl = rbl;
+}
+
+/*
+ * Catch the signal and die
+ */
+static void
+terminate(int dummy __unused)
+{
+ close(rpcbindlockfd);
+#ifdef WARMSTART
+ syslog(LOG_ERR,
+ "rpcbind terminating on signal. Restart with \"rpcbind -w\"");
+ write_warmstart(); /* Dump yourself */
+#endif
+ exit(2);
+}
+
+void
+rpcbind_abort(void)
+{
+#ifdef WARMSTART
+ write_warmstart(); /* Dump yourself */
+#endif
+ abort();
+}
+
+/* get command line options */
+static void
+parseargs(int argc, char *argv[])
+{
+ int c;
+
+#ifdef WARMSTART
+#define WSOP "w"
+#else
+#define WSOP ""
+#endif
+#ifdef LIBWRAP
+#define WRAPOP "W"
+#else
+#define WRAPOP ""
+#endif
+ while ((c = getopt(argc, argv, "6adh:iLls" WRAPOP WSOP)) != -1) {
+ switch (c) {
+ case '6':
+ ipv6_only = 1;
+ break;
+ case 'a':
+ doabort = 1; /* when debugging, do an abort on */
+ break; /* errors; for rpcbind developers */
+ /* only! */
+ case 'd':
+ debugging = 1;
+ break;
+ case 'h':
+ ++nhosts;
+ hosts = realloc(hosts, nhosts * sizeof(char *));
+ if (hosts == NULL)
+ errx(1, "Out of memory");
+ hosts[nhosts - 1] = strdup(optarg);
+ if (hosts[nhosts - 1] == NULL)
+ errx(1, "Out of memory");
+ break;
+ case 'i':
+ insecure = 1;
+ break;
+ case 'L':
+ oldstyle_local = 1;
+ break;
+ case 'l':
+ verboselog = 1;
+ break;
+ case 's':
+ runasdaemon = 1;
+ break;
+#ifdef LIBWRAP
+ case 'W':
+ libwrap = 1;
+ break;
+#endif
+#ifdef WARMSTART
+ case 'w':
+ warmstart = 1;
+ break;
+#endif
+ default: /* error */
+ fprintf(stderr,
+ "usage: rpcbind [-6adiLls%s%s] [-h bindip]\n",
+ WRAPOP, WSOP);
+ exit (1);
+ }
+ }
+ if (doabort && !debugging) {
+ fprintf(stderr,
+ "-a (abort) specified without -d (debugging) -- ignored.\n");
+ doabort = 0;
+ }
+#undef WSOP
+}
+
+void
+reap(int dummy __unused)
+{
+ int save_errno = errno;
+
+ while (wait3(NULL, WNOHANG, NULL) > 0)
+ ;
+ errno = save_errno;
+}
+
+void
+toggle_verboselog(int dummy __unused)
+{
+ verboselog = !verboselog;
+}
diff --git a/usr.sbin/rpcbind/rpcbind.h b/usr.sbin/rpcbind/rpcbind.h
new file mode 100644
index 0000000..309bc0b
--- /dev/null
+++ b/usr.sbin/rpcbind/rpcbind.h
@@ -0,0 +1,155 @@
+/* $NetBSD: rpcbind.h,v 1.1 2000/06/03 00:47:21 fvdl Exp $ */
+/* $FreeBSD$ */
+
+/*-
+ * Copyright (c) 2009, Sun Microsystems, 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:
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of Sun Microsystems, Inc. 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 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 HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (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 - 1991 by Sun Microsystems, Inc.
+ */
+
+/* #ident "@(#)rpcbind.h 1.4 90/04/12 SMI" */
+
+/*
+ * rpcbind.h
+ * The common header declarations
+ */
+
+#ifndef rpcbind_h
+#define rpcbind_h
+
+#ifdef PORTMAP
+#include <rpc/pmap_prot.h>
+#endif
+#include <rpc/rpcb_prot.h>
+
+/*
+ * Stuff for the rmtcall service
+ */
+struct encap_parms {
+ u_int32_t arglen;
+ char *args;
+};
+
+struct r_rmtcall_args {
+ u_int32_t rmt_prog;
+ u_int32_t rmt_vers;
+ u_int32_t rmt_proc;
+ int rmt_localvers; /* whether to send port # or uaddr */
+ char *rmt_uaddr;
+ struct encap_parms rmt_args;
+};
+
+extern int debugging;
+extern int doabort;
+#ifdef LIBWRAP
+extern int libwrap;
+#endif
+extern int verboselog;
+extern int insecure;
+extern int oldstyle_local;
+extern rpcblist_ptr list_rbl; /* A list of version 3 & 4 rpcbind services */
+
+#ifdef PORTMAP
+extern struct pmaplist *list_pml; /* A list of version 2 rpcbind services */
+extern char *udptrans; /* Name of UDP transport */
+extern char *tcptrans; /* Name of TCP transport */
+extern char *udp_uaddr; /* Universal UDP address */
+extern char *tcp_uaddr; /* Universal TCP address */
+#endif
+
+int add_bndlist(struct netconfig *, struct netbuf *);
+bool_t is_bound(char *, char *);
+char *mergeaddr(SVCXPRT *, char *, char *, char *);
+struct netconfig *rpcbind_get_conf(const char *);
+
+void rpcbs_init(void);
+void rpcbs_procinfo(rpcvers_t, rpcproc_t);
+void rpcbs_set(rpcvers_t, bool_t);
+void rpcbs_unset(rpcvers_t, bool_t);
+void rpcbs_getaddr(rpcvers_t, rpcprog_t, rpcvers_t, char *, char *);
+void rpcbs_rmtcall(rpcvers_t, rpcproc_t, rpcprog_t, rpcvers_t, rpcproc_t,
+ char *, rpcblist_ptr);
+void *rpcbproc_getstat(void *, struct svc_req *, SVCXPRT *, rpcvers_t);
+
+void rpcb_service_3(struct svc_req *, SVCXPRT *);
+void rpcb_service_4(struct svc_req *, SVCXPRT *);
+
+/* Common functions shared between versions */
+void *rpcbproc_set_com(void *, struct svc_req *, SVCXPRT *, rpcvers_t);
+void *rpcbproc_unset_com(void *, struct svc_req *, SVCXPRT *, rpcvers_t);
+bool_t map_set(RPCB *, char *);
+bool_t map_unset(RPCB *, char *);
+void delete_prog(unsigned int);
+void *rpcbproc_getaddr_com(RPCB *, struct svc_req *, SVCXPRT *, rpcvers_t,
+ rpcvers_t);
+void *rpcbproc_gettime_com(void *, struct svc_req *, SVCXPRT *,
+ rpcvers_t);
+void *rpcbproc_uaddr2taddr_com(void *, struct svc_req *,
+ SVCXPRT *, rpcvers_t);
+void *rpcbproc_taddr2uaddr_com(void *, struct svc_req *, SVCXPRT *,
+ rpcvers_t);
+int create_rmtcall_fd(struct netconfig *);
+void rpcbproc_callit_com(struct svc_req *, SVCXPRT *, rpcvers_t,
+ rpcvers_t);
+void my_svc_run(void);
+
+void rpcbind_abort(void);
+void reap(int);
+void toggle_verboselog(int);
+
+int check_access(SVCXPRT *, rpcproc_t, void *, unsigned int);
+int check_callit(SVCXPRT *, struct r_rmtcall_args *, int);
+void logit(int, struct sockaddr *, rpcproc_t, rpcprog_t, const char *);
+int is_loopback(struct netbuf *);
+
+#ifdef PORTMAP
+extern void pmap_service(struct svc_req *, SVCXPRT *);
+#endif
+
+void write_warmstart(void);
+void read_warmstart(void);
+
+char *addrmerge(struct netbuf *caller, const char *serv_uaddr,
+ const char *clnt_uaddr, char const *netid);
+int listen_addr(const struct sockaddr *sa);
+void network_init(void);
+struct sockaddr *local_sa(int);
+
+/* For different getaddr semantics */
+#define RPCB_ALLVERS 0
+#define RPCB_ONEVERS 1
+
+/* To convert a struct sockaddr to IPv4 or IPv6 address */
+#define SA2SIN(sa) ((struct sockaddr_in *)(sa))
+#define SA2SINADDR(sa) (SA2SIN(sa)->sin_addr)
+#ifdef INET6
+#define SA2SIN6(sa) ((struct sockaddr_in6 *)(sa))
+#define SA2SIN6ADDR(sa) (SA2SIN6(sa)->sin6_addr)
+#endif
+
+#endif /* rpcbind_h */
diff --git a/usr.sbin/rpcbind/security.c b/usr.sbin/rpcbind/security.c
new file mode 100644
index 0000000..2cff10a
--- /dev/null
+++ b/usr.sbin/rpcbind/security.c
@@ -0,0 +1,290 @@
+/* $NetBSD: security.c,v 1.5 2000/06/08 09:01:05 fvdl Exp $ */
+/* $FreeBSD$ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <rpc/rpc.h>
+#include <rpc/rpcb_prot.h>
+#include <rpc/pmap_prot.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <libutil.h>
+#include <syslog.h>
+#include <netdb.h>
+
+/*
+ * XXX for special case checks in check_callit.
+ */
+#include <rpcsvc/mount.h>
+#include <rpcsvc/rquota.h>
+#include <rpcsvc/nfs_prot.h>
+#include <rpcsvc/yp.h>
+#include <rpcsvc/ypclnt.h>
+#include <rpcsvc/yppasswd.h>
+
+#include "rpcbind.h"
+
+#ifdef LIBWRAP
+# include <tcpd.h>
+#ifndef LIBWRAP_ALLOW_FACILITY
+# define LIBWRAP_ALLOW_FACILITY LOG_AUTH
+#endif
+#ifndef LIBWRAP_ALLOW_SEVERITY
+# define LIBWRAP_ALLOW_SEVERITY LOG_INFO
+#endif
+#ifndef LIBWRAP_DENY_FACILITY
+# define LIBWRAP_DENY_FACILITY LOG_AUTH
+#endif
+#ifndef LIBWRAP_DENY_SEVERITY
+# define LIBWRAP_DENY_SEVERITY LOG_WARNING
+#endif
+int allow_severity = LIBWRAP_ALLOW_FACILITY|LIBWRAP_ALLOW_SEVERITY;
+int deny_severity = LIBWRAP_DENY_FACILITY|LIBWRAP_DENY_SEVERITY;
+#endif
+
+#ifndef PORTMAP_LOG_FACILITY
+# define PORTMAP_LOG_FACILITY LOG_AUTH
+#endif
+#ifndef PORTMAP_LOG_SEVERITY
+# define PORTMAP_LOG_SEVERITY LOG_INFO
+#endif
+int log_severity = PORTMAP_LOG_FACILITY|PORTMAP_LOG_SEVERITY;
+
+extern int verboselog;
+
+int
+check_access(SVCXPRT *xprt, rpcproc_t proc, void *args, unsigned int rpcbvers)
+{
+ struct netbuf *caller = svc_getrpccaller(xprt);
+ struct sockaddr *addr = (struct sockaddr *)caller->buf;
+#ifdef LIBWRAP
+ struct request_info req;
+#endif
+ rpcprog_t prog = 0;
+ rpcb *rpcbp;
+ struct pmap *pmap;
+
+ /*
+ * The older PMAP_* equivalents have the same numbers, so
+ * they are accounted for here as well.
+ */
+ switch (proc) {
+ case RPCBPROC_GETADDR:
+ case RPCBPROC_SET:
+ case RPCBPROC_UNSET:
+ if (rpcbvers > PMAPVERS) {
+ rpcbp = (rpcb *)args;
+ prog = rpcbp->r_prog;
+ } else {
+ pmap = (struct pmap *)args;
+ prog = pmap->pm_prog;
+ }
+ if (proc == RPCBPROC_GETADDR)
+ break;
+ if (!insecure && !is_loopback(caller)) {
+ if (verboselog)
+ logit(log_severity, addr, proc, prog,
+ " declined (non-loopback sender)");
+ return 0;
+ }
+ break;
+ case RPCBPROC_CALLIT:
+ case RPCBPROC_INDIRECT:
+ case RPCBPROC_DUMP:
+ case RPCBPROC_GETTIME:
+ case RPCBPROC_UADDR2TADDR:
+ case RPCBPROC_TADDR2UADDR:
+ case RPCBPROC_GETVERSADDR:
+ case RPCBPROC_GETADDRLIST:
+ case RPCBPROC_GETSTAT:
+ default:
+ break;
+ }
+
+#ifdef LIBWRAP
+ if (libwrap && addr->sa_family != AF_LOCAL) {
+ request_init(&req, RQ_DAEMON, "rpcbind", RQ_CLIENT_SIN, addr,
+ 0);
+ sock_methods(&req);
+ if(!hosts_access(&req)) {
+ logit(deny_severity, addr, proc, prog,
+ ": request from unauthorized host");
+ return 0;
+ }
+ }
+#endif
+ if (verboselog)
+ logit(log_severity, addr, proc, prog, "");
+ return 1;
+}
+
+int
+is_loopback(struct netbuf *nbuf)
+{
+ struct sockaddr *addr = (struct sockaddr *)nbuf->buf;
+ struct sockaddr_in *sin;
+#ifdef INET6
+ struct sockaddr_in6 *sin6;
+#endif
+
+ switch (addr->sa_family) {
+ case AF_INET:
+ if (!oldstyle_local)
+ return 0;
+ sin = (struct sockaddr_in *)addr;
+ return ((sin->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) &&
+ (ntohs(sin->sin_port) < IPPORT_RESERVED));
+#ifdef INET6
+ case AF_INET6:
+ if (!oldstyle_local)
+ return 0;
+ sin6 = (struct sockaddr_in6 *)addr;
+ return (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr) &&
+ (ntohs(sin6->sin6_port) < IPV6PORT_RESERVED));
+#endif
+ case AF_LOCAL:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+
+/* logit - report events of interest via the syslog daemon */
+void
+logit(int severity, struct sockaddr *addr, rpcproc_t procnum, rpcprog_t prognum,
+ const char *text)
+{
+ const char *procname;
+ char procbuf[32];
+ char *progname;
+ char progbuf[32];
+ char fromname[NI_MAXHOST];
+ struct rpcent *rpc;
+ static const char *procmap[] = {
+ /* RPCBPROC_NULL */ "null",
+ /* RPCBPROC_SET */ "set",
+ /* RPCBPROC_UNSET */ "unset",
+ /* RPCBPROC_GETADDR */ "getport/addr",
+ /* RPCBPROC_DUMP */ "dump",
+ /* RPCBPROC_CALLIT */ "callit",
+ /* RPCBPROC_GETTIME */ "gettime",
+ /* RPCBPROC_UADDR2TADDR */ "uaddr2taddr",
+ /* RPCBPROC_TADDR2UADDR */ "taddr2uaddr",
+ /* RPCBPROC_GETVERSADDR */ "getversaddr",
+ /* RPCBPROC_INDIRECT */ "indirect",
+ /* RPCBPROC_GETADDRLIST */ "getaddrlist",
+ /* RPCBPROC_GETSTAT */ "getstat"
+ };
+
+ /*
+ * Fork off a process or the portmap daemon might hang while
+ * getrpcbynumber() or syslog() does its thing.
+ */
+
+ if (fork() == 0) {
+ setproctitle("logit");
+
+ /* Try to map program number to name. */
+
+ if (prognum == 0) {
+ progname = "";
+ } else if ((rpc = getrpcbynumber((int) prognum))) {
+ progname = rpc->r_name;
+ } else {
+ snprintf(progname = progbuf, sizeof(progbuf), "%u",
+ (unsigned)prognum);
+ }
+
+ /* Try to map procedure number to name. */
+
+ if (procnum >= (sizeof procmap / sizeof (char *))) {
+ snprintf(procbuf, sizeof procbuf, "%u",
+ (unsigned)procnum);
+ procname = procbuf;
+ } else
+ procname = procmap[procnum];
+
+ /* Write syslog record. */
+
+ if (addr->sa_family == AF_LOCAL)
+ strcpy(fromname, "local");
+ else
+ getnameinfo(addr, addr->sa_len, fromname,
+ sizeof fromname, NULL, 0, NI_NUMERICHOST);
+
+ syslog(severity, "connect from %s to %s(%s)%s",
+ fromname, procname, progname, text);
+ _exit(0);
+ }
+}
+
+int
+check_callit(SVCXPRT *xprt, struct r_rmtcall_args *args, int versnum __unused)
+{
+ struct sockaddr *sa = (struct sockaddr *)svc_getrpccaller(xprt)->buf;
+
+ /*
+ * Always allow calling NULLPROC
+ */
+ if (args->rmt_proc == 0)
+ return 1;
+
+ /*
+ * XXX - this special casing sucks.
+ */
+ switch (args->rmt_prog) {
+ case RPCBPROG:
+ /*
+ * Allow indirect calls to ourselves in insecure mode.
+ * The is_loopback checks aren't useful then anyway.
+ */
+ if (!insecure)
+ goto deny;
+ break;
+ case MOUNTPROG:
+ if (args->rmt_proc != MOUNTPROC_MNT &&
+ args->rmt_proc != MOUNTPROC_UMNT)
+ break;
+ goto deny;
+ case YPBINDPROG:
+ if (args->rmt_proc != YPBINDPROC_SETDOM)
+ break;
+ /* FALLTHROUGH */
+ case YPPASSWDPROG:
+ case NFS_PROGRAM:
+ case RQUOTAPROG:
+ goto deny;
+ case YPPROG:
+ switch (args->rmt_proc) {
+ case YPPROC_ALL:
+ case YPPROC_MATCH:
+ case YPPROC_FIRST:
+ case YPPROC_NEXT:
+ goto deny;
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+
+ return 1;
+deny:
+#ifdef LIBWRAP
+ logit(deny_severity, sa, args->rmt_proc, args->rmt_prog,
+ ": indirect call not allowed");
+#else
+ logit(0, sa, args->rmt_proc, args->rmt_prog,
+ ": indirect call not allowed");
+#endif
+ return 0;
+}
diff --git a/usr.sbin/rpcbind/tests/Makefile b/usr.sbin/rpcbind/tests/Makefile
new file mode 100644
index 0000000..4b0cf15
--- /dev/null
+++ b/usr.sbin/rpcbind/tests/Makefile
@@ -0,0 +1,17 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+.PATH: ${.CURDIR}/..
+
+ATF_TESTS_C= addrmerge_test
+CFLAGS+= -I${.CURDIR}/.. -Wno-cast-qual
+SRCS.addrmerge_test= addrmerge_test.c util.c
+
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+= -DINET6
+.endif
+
+WARNS?= 3
+
+.include <bsd.test.mk>
diff --git a/usr.sbin/rpcbind/tests/addrmerge_test.c b/usr.sbin/rpcbind/tests/addrmerge_test.c
new file mode 100644
index 0000000..357354a
--- /dev/null
+++ b/usr.sbin/rpcbind/tests/addrmerge_test.c
@@ -0,0 +1,849 @@
+/*-
+ * 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,
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <rpc/rpc.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ifaddrs.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <atf-c.h>
+
+#include "rpcbind.h"
+
+#define MAX_IFADDRS 16
+
+int debugging = false;
+
+/* Data for mocking getifaddrs */
+struct ifaddr_storage {
+ struct ifaddrs ifaddr;
+ struct sockaddr_storage addr;
+ struct sockaddr_storage mask;
+ struct sockaddr_storage bcast;
+} mock_ifaddr_storage[MAX_IFADDRS];
+struct ifaddrs *mock_ifaddrs = NULL;
+int ifaddr_count = 0;
+
+/* Data for mocking listen_addr */
+int bind_address_count = 0;
+struct sockaddr* bind_addresses[MAX_IFADDRS];
+
+/* Stub library functions */
+void
+freeifaddrs(struct ifaddrs *ifp __unused)
+{
+ return ;
+}
+
+int
+getifaddrs(struct ifaddrs **ifap)
+{
+ *ifap = mock_ifaddrs;
+ return (0);
+}
+
+static void
+mock_ifaddr4(const char* name, const char* addr, const char* mask,
+ const char* bcast, unsigned int flags, bool bind)
+{
+ struct ifaddrs *ifaddr = &mock_ifaddr_storage[ifaddr_count].ifaddr;
+ struct sockaddr_in *in = (struct sockaddr_in*)
+ &mock_ifaddr_storage[ifaddr_count].addr;
+ struct sockaddr_in *mask_in = (struct sockaddr_in*)
+ &mock_ifaddr_storage[ifaddr_count].mask;
+ struct sockaddr_in *bcast_in = (struct sockaddr_in*)
+ &mock_ifaddr_storage[ifaddr_count].bcast;
+
+ in->sin_family = AF_INET;
+ in->sin_port = 0;
+ in->sin_len = sizeof(in);
+ in->sin_addr.s_addr = inet_addr(addr);
+ mask_in->sin_family = AF_INET;
+ mask_in->sin_port = 0;
+ mask_in->sin_len = sizeof(mask_in);
+ mask_in->sin_addr.s_addr = inet_addr(mask);
+ bcast_in->sin_family = AF_INET;
+ bcast_in->sin_port = 0;
+ bcast_in->sin_len = sizeof(bcast_in);
+ bcast_in->sin_addr.s_addr = inet_addr(bcast);
+ *ifaddr = (struct ifaddrs) {
+ .ifa_next = NULL,
+ .ifa_name = (char*) name,
+ .ifa_flags = flags,
+ .ifa_addr = (struct sockaddr*) in,
+ .ifa_netmask = (struct sockaddr*) mask_in,
+ .ifa_broadaddr = (struct sockaddr*) bcast_in,
+ .ifa_data = NULL, /* addrmerge doesn't care*/
+ };
+
+ if (ifaddr_count > 0)
+ mock_ifaddr_storage[ifaddr_count - 1].ifaddr.ifa_next = ifaddr;
+ ifaddr_count++;
+ mock_ifaddrs = &mock_ifaddr_storage[0].ifaddr;
+
+ /* Optionally simulate binding an ip ala "rpcbind -h foo" */
+ if (bind) {
+ bind_addresses[bind_address_count] = (struct sockaddr*)in;
+ bind_address_count++;
+ }
+}
+
+#ifdef INET6
+static void
+mock_ifaddr6(const char* name, const char* addr, const char* mask,
+ const char* bcast, unsigned int flags, uint32_t scope_id, bool bind)
+{
+ struct ifaddrs *ifaddr = &mock_ifaddr_storage[ifaddr_count].ifaddr;
+ struct sockaddr_in6 *in6 = (struct sockaddr_in6*)
+ &mock_ifaddr_storage[ifaddr_count].addr;
+ struct sockaddr_in6 *mask_in6 = (struct sockaddr_in6*)
+ &mock_ifaddr_storage[ifaddr_count].mask;
+ struct sockaddr_in6 *bcast_in6 = (struct sockaddr_in6*)
+ &mock_ifaddr_storage[ifaddr_count].bcast;
+
+ in6->sin6_family = AF_INET6;
+ in6->sin6_port = 0;
+ in6->sin6_len = sizeof(*in6);
+ in6->sin6_scope_id = scope_id;
+ ATF_REQUIRE_EQ(1, inet_pton(AF_INET6, addr, (void*)&in6->sin6_addr));
+ mask_in6->sin6_family = AF_INET6;
+ mask_in6->sin6_port = 0;
+ mask_in6->sin6_len = sizeof(*mask_in6);
+ mask_in6->sin6_scope_id = scope_id;
+ ATF_REQUIRE_EQ(1, inet_pton(AF_INET6, mask,
+ (void*)&mask_in6->sin6_addr));
+ bcast_in6->sin6_family = AF_INET6;
+ bcast_in6->sin6_port = 0;
+ bcast_in6->sin6_len = sizeof(*bcast_in6);
+ bcast_in6->sin6_scope_id = scope_id;
+ ATF_REQUIRE_EQ(1, inet_pton(AF_INET6, bcast,
+ (void*)&bcast_in6->sin6_addr));
+ *ifaddr = (struct ifaddrs) {
+ .ifa_next = NULL,
+ .ifa_name = (char*) name,
+ .ifa_flags = flags,
+ .ifa_addr = (struct sockaddr*) in6,
+ .ifa_netmask = (struct sockaddr*) mask_in6,
+ .ifa_broadaddr = (struct sockaddr*) bcast_in6,
+ .ifa_data = NULL, /* addrmerge doesn't care*/
+ };
+
+ if (ifaddr_count > 0)
+ mock_ifaddr_storage[ifaddr_count - 1].ifaddr.ifa_next = ifaddr;
+ ifaddr_count++;
+ mock_ifaddrs = &mock_ifaddr_storage[0].ifaddr;
+
+ /* Optionally simulate binding an ip ala "rpcbind -h foo" */
+ if (bind) {
+ bind_addresses[bind_address_count] = (struct sockaddr*)in6;
+ bind_address_count++;
+ }
+}
+#else
+static void
+mock_ifaddr6(const char* name __unused, const char* addr __unused,
+ const char* mask __unused, const char* bcast __unused,
+ unsigned int flags __unused, uint32_t scope_id __unused, bool bind __unused)
+{
+}
+#endif /*INET6 */
+
+static void
+mock_lo0(void)
+{
+ /*
+ * This broadcast address looks wrong, but it's what getifaddrs(2)
+ * actually returns. It's invalid because IFF_BROADCAST is not set
+ */
+ mock_ifaddr4("lo0", "127.0.0.1", "255.0.0.0", "127.0.0.1",
+ IFF_LOOPBACK | IFF_UP | IFF_RUNNING | IFF_MULTICAST, false);
+ mock_ifaddr6("lo0", "::1", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+ "::1",
+ IFF_LOOPBACK | IFF_UP | IFF_RUNNING | IFF_MULTICAST, 0, false);
+}
+
+static void
+mock_igb0(void)
+{
+ mock_ifaddr4("igb0", "192.0.2.2", "255.255.255.128", "192.0.2.127",
+ IFF_UP | IFF_BROADCAST | IFF_RUNNING | IFF_SIMPLEX | IFF_MULTICAST,
+ false);
+ mock_ifaddr6("igb0", "2001:db8::2", "ffff:ffff:ffff:ffff::",
+ "2001:db8::ffff:ffff:ffff:ffff",
+ IFF_UP | IFF_BROADCAST | IFF_RUNNING | IFF_SIMPLEX | IFF_MULTICAST,
+ 0, false);
+ /* Link local address */
+ mock_ifaddr6("igb0", "fe80::2", "ffff:ffff:ffff:ffff::",
+ "fe80::ffff:ffff:ffff:ffff",
+ IFF_UP | IFF_BROADCAST | IFF_RUNNING | IFF_SIMPLEX | IFF_MULTICAST,
+ 2, false);
+}
+
+/* On the same subnet as igb0 */
+static void
+mock_igb1(bool bind)
+{
+ mock_ifaddr4("igb1", "192.0.2.3", "255.255.255.128", "192.0.2.127",
+ IFF_UP | IFF_BROADCAST | IFF_RUNNING | IFF_SIMPLEX | IFF_MULTICAST,
+ bind);
+ mock_ifaddr6("igb1", "2001:db8::3", "ffff:ffff:ffff:ffff::",
+ "2001:db8::ffff:ffff:ffff:ffff",
+ IFF_UP | IFF_BROADCAST | IFF_RUNNING | IFF_SIMPLEX | IFF_MULTICAST,
+ 0, bind);
+ /* Link local address */
+ mock_ifaddr6("igb1", "fe80::3", "ffff:ffff:ffff:ffff::",
+ "fe80::ffff:ffff:ffff:ffff",
+ IFF_UP | IFF_BROADCAST | IFF_RUNNING | IFF_SIMPLEX | IFF_MULTICAST,
+ 3, bind);
+}
+
+/* igb2 is on a different subnet than igb0 */
+static void
+mock_igb2(void)
+{
+ mock_ifaddr4("igb2", "192.0.2.130", "255.255.255.128", "192.0.2.255",
+ IFF_UP | IFF_BROADCAST | IFF_RUNNING | IFF_SIMPLEX | IFF_MULTICAST,
+ false);
+ mock_ifaddr6("igb2", "2001:db8:1::2", "ffff:ffff:ffff:ffff::",
+ "2001:db8:1:0:ffff:ffff:ffff:ffff",
+ IFF_UP | IFF_BROADCAST | IFF_RUNNING | IFF_SIMPLEX | IFF_MULTICAST,
+ 0, false);
+}
+
+/* tun0 is a P2P interface */
+static void
+mock_tun0(void)
+{
+ mock_ifaddr4("tun0", "192.0.2.5", "255.255.255.255", "192.0.2.6",
+ IFF_UP | IFF_RUNNING | IFF_POINTOPOINT | IFF_MULTICAST, false);
+ mock_ifaddr6("tun0", "2001:db8::5",
+ "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+ "2001:db8::6",
+ IFF_UP | IFF_RUNNING | IFF_POINTOPOINT | IFF_MULTICAST, 0, false);
+}
+
+
+/* Stub rpcbind functions */
+int
+listen_addr(const struct sockaddr *sa)
+{
+ int i;
+
+ if (bind_address_count == 0)
+ return (1);
+
+ for (i = 0; i < bind_address_count; i++) {
+ if (bind_addresses[i]->sa_family != sa->sa_family)
+ continue;
+
+ if (0 == memcmp(bind_addresses[i]->sa_data, sa->sa_data,
+ sa->sa_len))
+ return (1);
+ }
+ return (0);
+}
+
+struct netconfig*
+rpcbind_get_conf(const char* netid __unused)
+{
+ /* Use static variables so we can return pointers to them */
+ static char* lookups = NULL;
+ static struct netconfig nconf_udp;
+#ifdef INET6
+ static struct netconfig nconf_udp6;
+#endif /* INET6 */
+
+ nconf_udp.nc_netid = "udp"; //netid_storage;
+ nconf_udp.nc_semantics = NC_TPI_CLTS;
+ nconf_udp.nc_flag = NC_VISIBLE;
+ nconf_udp.nc_protofmly = (char*)"inet";
+ nconf_udp.nc_proto = (char*)"udp";
+ nconf_udp.nc_device = (char*)"-";
+ nconf_udp.nc_nlookups = 0;
+ nconf_udp.nc_lookups = &lookups;
+
+#ifdef INET6
+ nconf_udp6.nc_netid = "udp6"; //netid_storage;
+ nconf_udp6.nc_semantics = NC_TPI_CLTS;
+ nconf_udp6.nc_flag = NC_VISIBLE;
+ nconf_udp6.nc_protofmly = (char*)"inet6";
+ nconf_udp6.nc_proto = (char*)"udp6";
+ nconf_udp6.nc_device = (char*)"-";
+ nconf_udp6.nc_nlookups = 0;
+ nconf_udp6.nc_lookups = &lookups;
+#endif /* INET6 */
+
+ if (0 == strncmp("udp", netid, sizeof("udp")))
+ return (&nconf_udp);
+#ifdef INET6
+ else if (0 == strncmp("udp6", netid, sizeof("udp6")))
+ return (&nconf_udp6);
+#endif /* INET6 */
+ else
+ return (NULL);
+}
+
+/*
+ * Helper function used by most test cases
+ * param recvdstaddr If non-null, the uaddr on which the request was received
+ */
+static char*
+do_addrmerge4(const char* recvdstaddr)
+{
+ struct netbuf caller;
+ struct sockaddr_in caller_in;
+ const char *serv_uaddr, *clnt_uaddr, *netid;
+
+ /* caller contains the client's IP address */
+ caller.maxlen = sizeof(struct sockaddr_storage);
+ caller.len = sizeof(caller_in);
+ caller_in.sin_family = AF_INET;
+ caller_in.sin_len = sizeof(caller_in);
+ caller_in.sin_port = 1234;
+ caller_in.sin_addr.s_addr = inet_addr("192.0.2.1");
+ caller.buf = (void*)&caller_in;
+ if (recvdstaddr != NULL)
+ clnt_uaddr = recvdstaddr;
+ else
+ clnt_uaddr = "192.0.2.1.3.46";
+
+ /* assume server is bound in INADDR_ANY port 814 */
+ serv_uaddr = "0.0.0.0.3.46";
+
+ netid = "udp";
+ return (addrmerge(&caller, serv_uaddr, clnt_uaddr, netid));
+}
+
+#ifdef INET6
+/*
+ * Variant of do_addrmerge4 where the caller has an IPv6 address
+ * param recvdstaddr If non-null, the uaddr on which the request was received
+ */
+static char*
+do_addrmerge6(const char* recvdstaddr)
+{
+ struct netbuf caller;
+ struct sockaddr_in6 caller_in6;
+ const char *serv_uaddr, *clnt_uaddr, *netid;
+
+ /* caller contains the client's IP address */
+ caller.maxlen = sizeof(struct sockaddr_storage);
+ caller.len = sizeof(caller_in6);
+ caller_in6.sin6_family = AF_INET6;
+ caller_in6.sin6_len = sizeof(caller_in6);
+ caller_in6.sin6_port = 1234;
+ ATF_REQUIRE_EQ(1, inet_pton(AF_INET6, "2001:db8::1",
+ (void*)&caller_in6.sin6_addr));
+ caller.buf = (void*)&caller_in6;
+ if (recvdstaddr != NULL)
+ clnt_uaddr = recvdstaddr;
+ else
+ clnt_uaddr = "2001:db8::1.3.46";
+
+ /* assume server is bound in INADDR_ANY port 814 */
+ serv_uaddr = "::1.3.46";
+
+ netid = "udp6";
+ return (addrmerge(&caller, serv_uaddr, clnt_uaddr, netid));
+}
+
+/* Variant of do_addrmerge6 where the caller uses a link local address */
+static char*
+do_addrmerge6_ll(void)
+{
+ struct netbuf caller;
+ struct sockaddr_in6 caller_in6;
+ const char *serv_uaddr, *clnt_uaddr, *netid;
+
+ /* caller contains the client's IP address */
+ caller.maxlen = sizeof(struct sockaddr_storage);
+ caller.len = sizeof(caller_in6);
+ caller_in6.sin6_family = AF_INET6;
+ caller_in6.sin6_len = sizeof(caller_in6);
+ caller_in6.sin6_port = 1234;
+ caller_in6.sin6_scope_id = 2; /* same as igb0 */
+ ATF_REQUIRE_EQ(1, inet_pton(AF_INET6, "fe80::beef",
+ (void*)&caller_in6.sin6_addr));
+ caller.buf = (void*)&caller_in6;
+ clnt_uaddr = "fe80::beef.3.46";
+
+ /* assume server is bound in INADDR_ANY port 814 */
+ serv_uaddr = "::1.3.46";
+
+ netid = "udp6";
+ return (addrmerge(&caller, serv_uaddr, clnt_uaddr, netid));
+}
+#endif /* INET6 */
+
+ATF_TC_WITHOUT_HEAD(addrmerge_noifaddrs);
+ATF_TC_BODY(addrmerge_noifaddrs, tc)
+{
+ char* maddr;
+
+ maddr = do_addrmerge4(NULL);
+
+ /* Since getifaddrs returns null, addrmerge must too */
+ ATF_CHECK_EQ(NULL, maddr);
+}
+
+ATF_TC_WITHOUT_HEAD(addrmerge_localhost_only);
+ATF_TC_BODY(addrmerge_localhost_only, tc)
+{
+ char *maddr;
+
+ /* getifaddrs will return localhost only */
+ mock_lo0();
+
+ maddr = do_addrmerge4(NULL);
+
+ /* We must return localhost if there is nothing better */
+ ATF_REQUIRE(maddr != NULL);
+ ATF_CHECK_STREQ("127.0.0.1.3.46", maddr);
+}
+
+ATF_TC_WITHOUT_HEAD(addrmerge_singlehomed);
+ATF_TC_BODY(addrmerge_singlehomed, tc)
+{
+ char *maddr;
+
+ /* getifaddrs will return one public address */
+ mock_lo0();
+ mock_igb0();
+
+ maddr = do_addrmerge4(NULL);
+
+ ATF_REQUIRE(maddr != NULL);
+ ATF_CHECK_STREQ("192.0.2.2.3.46", maddr);
+}
+
+ATF_TC_WITHOUT_HEAD(addrmerge_one_addr_on_each_subnet);
+ATF_TC_BODY(addrmerge_one_addr_on_each_subnet, tc)
+{
+ char *maddr;
+
+ mock_lo0();
+ mock_igb0();
+ mock_igb2();
+
+ maddr = do_addrmerge4(NULL);
+
+ /* We must return the address on the caller's subnet */
+ ATF_REQUIRE(maddr != NULL);
+ ATF_CHECK_STREQ("192.0.2.2.3.46", maddr);
+}
+
+
+/*
+ * Like addrmerge_one_addr_on_each_subnet, but getifaddrs returns a different
+ * order
+ */
+ATF_TC_WITHOUT_HEAD(addrmerge_one_addr_on_each_subnet_rev);
+ATF_TC_BODY(addrmerge_one_addr_on_each_subnet_rev, tc)
+{
+ char *maddr;
+
+ /* getifaddrs will return one public address on each of two subnets */
+ mock_igb2();
+ mock_igb0();
+ mock_lo0();
+
+ maddr = do_addrmerge4(NULL);
+
+ /* We must return the address on the caller's subnet */
+ ATF_REQUIRE(maddr != NULL);
+ ATF_CHECK_STREQ("192.0.2.2.3.46", maddr);
+}
+
+ATF_TC_WITHOUT_HEAD(addrmerge_point2point);
+ATF_TC_BODY(addrmerge_point2point, tc)
+{
+ char *maddr;
+
+ /* getifaddrs will return one normal and one p2p address */
+ mock_lo0();
+ mock_igb2();
+ mock_tun0();
+
+ maddr = do_addrmerge4(NULL);
+
+ /* addrmerge should disprefer P2P interfaces */
+ ATF_REQUIRE(maddr != NULL);
+ ATF_CHECK_STREQ("192.0.2.130.3.46", maddr);
+}
+
+/* Like addrerge_point2point, but getifaddrs returns a different order */
+ATF_TC_WITHOUT_HEAD(addrmerge_point2point_rev);
+ATF_TC_BODY(addrmerge_point2point_rev, tc)
+{
+ char *maddr;
+
+ /* getifaddrs will return one normal and one p2p address */
+ mock_tun0();
+ mock_igb2();
+ mock_lo0();
+
+ maddr = do_addrmerge4(NULL);
+
+ /* addrmerge should disprefer P2P interfaces */
+ ATF_REQUIRE(maddr != NULL);
+ ATF_CHECK_STREQ("192.0.2.130.3.46", maddr);
+}
+
+/*
+ * Simulate using rpcbind -h to select just one ip when the subnet has
+ * multiple
+ */
+ATF_TC_WITHOUT_HEAD(addrmerge_bindip);
+ATF_TC_BODY(addrmerge_bindip, tc)
+{
+ char *maddr;
+
+ /* getifaddrs will return one public address on each of two subnets */
+ mock_lo0();
+ mock_igb0();
+ mock_igb1(true);
+
+ maddr = do_addrmerge4(NULL);
+
+ /* We must return the address to which we are bound */
+ ATF_REQUIRE(maddr != NULL);
+ ATF_CHECK_STREQ("192.0.2.3.3.46", maddr);
+}
+
+/* Like addrmerge_bindip, but getifaddrs returns a different order */
+ATF_TC_WITHOUT_HEAD(addrmerge_bindip_rev);
+ATF_TC_BODY(addrmerge_bindip_rev, tc)
+{
+ char *maddr;
+
+ /* getifaddrs will return one public address on each of two subnets */
+ mock_igb1(true);
+ mock_igb0();
+ mock_lo0();
+
+ maddr = do_addrmerge4(NULL);
+
+ /* We must return the address to which we are bound */
+ ATF_REQUIRE(maddr != NULL);
+ ATF_CHECK_STREQ("192.0.2.3.3.46", maddr);
+}
+
+/*
+ * The address on which the request was received is known, and is provided as
+ * the hint.
+ */
+ATF_TC_WITHOUT_HEAD(addrmerge_recvdstaddr);
+ATF_TC_BODY(addrmerge_recvdstaddr, tc)
+{
+ char *maddr;
+
+ mock_lo0();
+ mock_igb0();
+ mock_igb1(false);
+
+ maddr = do_addrmerge4("192.0.2.2.3.46");
+
+ /* We must return the address on which the request was received */
+ ATF_REQUIRE(maddr != NULL);
+ ATF_CHECK_STREQ("192.0.2.2.3.46", maddr);
+}
+
+ATF_TC_WITHOUT_HEAD(addrmerge_recvdstaddr_rev);
+ATF_TC_BODY(addrmerge_recvdstaddr_rev, tc)
+{
+ char *maddr;
+
+ mock_igb1(false);
+ mock_igb0();
+ mock_lo0();
+
+ maddr = do_addrmerge4("192.0.2.2.3.46");
+
+ /* We must return the address on which the request was received */
+ ATF_REQUIRE(maddr != NULL);
+ ATF_CHECK_STREQ("192.0.2.2.3.46", maddr);
+}
+
+#ifdef INET6
+ATF_TC_WITHOUT_HEAD(addrmerge_localhost_only6);
+ATF_TC_BODY(addrmerge_localhost_only6, tc)
+{
+ char *maddr;
+
+ /* getifaddrs will return localhost only */
+ mock_lo0();
+
+ maddr = do_addrmerge6(NULL);
+
+ /* We must return localhost if there is nothing better */
+ ATF_REQUIRE(maddr != NULL);
+ ATF_CHECK_STREQ("::1.3.46", maddr);
+}
+
+ATF_TC_WITHOUT_HEAD(addrmerge_singlehomed6);
+ATF_TC_BODY(addrmerge_singlehomed6, tc)
+{
+ char *maddr;
+
+ /* getifaddrs will return one public address */
+ mock_lo0();
+ mock_igb0();
+
+ maddr = do_addrmerge6(NULL);
+
+ ATF_REQUIRE(maddr != NULL);
+ ATF_CHECK_STREQ("2001:db8::2.3.46", maddr);
+}
+
+ATF_TC_WITHOUT_HEAD(addrmerge_one_addr_on_each_subnet6);
+ATF_TC_BODY(addrmerge_one_addr_on_each_subnet6, tc)
+{
+ char *maddr;
+
+ mock_lo0();
+ mock_igb0();
+ mock_igb2();
+
+ maddr = do_addrmerge6(NULL);
+
+ /* We must return the address on the caller's subnet */
+ ATF_REQUIRE(maddr != NULL);
+ ATF_CHECK_STREQ("2001:db8::2.3.46", maddr);
+}
+
+
+/*
+ * Like addrmerge_one_addr_on_each_subnet6, but getifaddrs returns a different
+ * order
+ */
+ATF_TC_WITHOUT_HEAD(addrmerge_one_addr_on_each_subnet6_rev);
+ATF_TC_BODY(addrmerge_one_addr_on_each_subnet6_rev, tc)
+{
+ char *maddr;
+
+ /* getifaddrs will return one public address on each of two subnets */
+ mock_igb2();
+ mock_igb0();
+ mock_lo0();
+
+ maddr = do_addrmerge6(NULL);
+
+ /* We must return the address on the caller's subnet */
+ ATF_REQUIRE(maddr != NULL);
+ ATF_CHECK_STREQ("2001:db8::2.3.46", maddr);
+}
+
+ATF_TC_WITHOUT_HEAD(addrmerge_point2point6);
+ATF_TC_BODY(addrmerge_point2point6, tc)
+{
+ char *maddr;
+
+ /* getifaddrs will return one normal and one p2p address */
+ mock_lo0();
+ mock_igb2();
+ mock_tun0();
+
+ maddr = do_addrmerge6(NULL);
+
+ /* addrmerge should disprefer P2P interfaces */
+ ATF_REQUIRE(maddr != NULL);
+ ATF_CHECK_STREQ("2001:db8:1::2.3.46", maddr);
+}
+
+/* Like addrerge_point2point, but getifaddrs returns a different order */
+ATF_TC_WITHOUT_HEAD(addrmerge_point2point6_rev);
+ATF_TC_BODY(addrmerge_point2point6_rev, tc)
+{
+ char *maddr;
+
+ /* getifaddrs will return one normal and one p2p address */
+ mock_tun0();
+ mock_igb2();
+ mock_lo0();
+
+ maddr = do_addrmerge6(NULL);
+
+ /* addrmerge should disprefer P2P interfaces */
+ ATF_REQUIRE(maddr != NULL);
+ ATF_CHECK_STREQ("2001:db8:1::2.3.46", maddr);
+}
+
+ATF_TC_WITHOUT_HEAD(addrmerge_bindip6);
+ATF_TC_BODY(addrmerge_bindip6, tc)
+{
+ char *maddr;
+
+ /* getifaddrs will return one public address on each of two subnets */
+ mock_lo0();
+ mock_igb0();
+ mock_igb1(true);
+
+ maddr = do_addrmerge6(NULL);
+
+ /* We must return the address to which we are bound */
+ ATF_REQUIRE(maddr != NULL);
+ ATF_CHECK_STREQ("2001:db8::3.3.46", maddr);
+}
+
+/* Like addrerge_bindip, but getifaddrs returns a different order */
+ATF_TC_WITHOUT_HEAD(addrmerge_bindip6_rev);
+ATF_TC_BODY(addrmerge_bindip6_rev, tc)
+{
+ char *maddr;
+
+ /* getifaddrs will return one public address on each of two subnets */
+ mock_igb1(true);
+ mock_igb0();
+ mock_lo0();
+
+ maddr = do_addrmerge6(NULL);
+
+ /* We must return the address to which we are bound */
+ ATF_REQUIRE(maddr != NULL);
+ ATF_CHECK_STREQ("2001:db8::3.3.46", maddr);
+}
+
+/*
+ * IPv6 Link Local addresses with the same scope id as the caller, if the caller
+ * is also a link local address, should be preferred
+ */
+ATF_TC_WITHOUT_HEAD(addrmerge_ipv6_linklocal);
+ATF_TC_BODY(addrmerge_ipv6_linklocal, tc)
+{
+ char *maddr;
+
+ /*
+ * getifaddrs will return two link local addresses with the same netmask
+ * and prefix but different scope IDs
+ */
+ mock_igb1(false);
+ mock_igb0();
+ mock_lo0();
+
+ maddr = do_addrmerge6_ll();
+
+ /* We must return the address to which we are bound */
+ ATF_REQUIRE(maddr != NULL);
+ ATF_CHECK_STREQ("fe80::2.3.46", maddr);
+}
+
+ATF_TC_WITHOUT_HEAD(addrmerge_ipv6_linklocal_rev);
+ATF_TC_BODY(addrmerge_ipv6_linklocal_rev, tc)
+{
+ char *maddr;
+
+ /*
+ * getifaddrs will return two link local addresses with the same netmask
+ * and prefix but different scope IDs
+ */
+ mock_lo0();
+ mock_igb0();
+ mock_igb1(false);
+
+ maddr = do_addrmerge6_ll();
+
+ /* We must return the address to which we are bound */
+ ATF_REQUIRE(maddr != NULL);
+ ATF_CHECK_STREQ("fe80::2.3.46", maddr);
+}
+
+ATF_TC_WITHOUT_HEAD(addrmerge_recvdstaddr6);
+ATF_TC_BODY(addrmerge_recvdstaddr6, tc)
+{
+ char *maddr;
+
+ mock_lo0();
+ mock_igb0();
+ mock_igb1(false);
+
+ maddr = do_addrmerge6("2001:db8::2.3.46");
+
+ /* We must return the address on which the request was received */
+ ATF_REQUIRE(maddr != NULL);
+ ATF_CHECK_STREQ("2001:db8::2.3.46", maddr);
+}
+
+ATF_TC_WITHOUT_HEAD(addrmerge_recvdstaddr6_rev);
+ATF_TC_BODY(addrmerge_recvdstaddr6_rev, tc)
+{
+ char *maddr;
+
+ mock_igb1(false);
+ mock_igb0();
+ mock_lo0();
+
+ maddr = do_addrmerge6("2001:db8::2.3.46");
+
+ /* We must return the address on which the request was received */
+ ATF_REQUIRE(maddr != NULL);
+ ATF_CHECK_STREQ("2001:db8::2.3.46", maddr);
+}
+#endif /* INET6 */
+
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, addrmerge_noifaddrs);
+ ATF_TP_ADD_TC(tp, addrmerge_localhost_only);
+ ATF_TP_ADD_TC(tp, addrmerge_singlehomed);
+ ATF_TP_ADD_TC(tp, addrmerge_one_addr_on_each_subnet);
+ ATF_TP_ADD_TC(tp, addrmerge_one_addr_on_each_subnet_rev);
+ ATF_TP_ADD_TC(tp, addrmerge_point2point);
+ ATF_TP_ADD_TC(tp, addrmerge_point2point_rev);
+ ATF_TP_ADD_TC(tp, addrmerge_bindip);
+ ATF_TP_ADD_TC(tp, addrmerge_bindip_rev);
+ ATF_TP_ADD_TC(tp, addrmerge_recvdstaddr);
+ ATF_TP_ADD_TC(tp, addrmerge_recvdstaddr_rev);
+#ifdef INET6
+ ATF_TP_ADD_TC(tp, addrmerge_localhost_only6);
+ ATF_TP_ADD_TC(tp, addrmerge_singlehomed6);
+ ATF_TP_ADD_TC(tp, addrmerge_one_addr_on_each_subnet6);
+ ATF_TP_ADD_TC(tp, addrmerge_one_addr_on_each_subnet6_rev);
+ ATF_TP_ADD_TC(tp, addrmerge_point2point6);
+ ATF_TP_ADD_TC(tp, addrmerge_point2point6_rev);
+ ATF_TP_ADD_TC(tp, addrmerge_bindip6);
+ ATF_TP_ADD_TC(tp, addrmerge_bindip6_rev);
+ ATF_TP_ADD_TC(tp, addrmerge_ipv6_linklocal);
+ ATF_TP_ADD_TC(tp, addrmerge_ipv6_linklocal_rev);
+ ATF_TP_ADD_TC(tp, addrmerge_recvdstaddr6);
+ ATF_TP_ADD_TC(tp, addrmerge_recvdstaddr6_rev);
+#endif
+
+ return (atf_no_error());
+}
diff --git a/usr.sbin/rpcbind/util.c b/usr.sbin/rpcbind/util.c
new file mode 100644
index 0000000..da6a5ab
--- /dev/null
+++ b/usr.sbin/rpcbind/util.c
@@ -0,0 +1,401 @@
+/*
+ * $NetBSD: util.c,v 1.4 2000/08/03 00:04:30 fvdl Exp $
+ * $FreeBSD$
+ */
+
+/*-
+ * Copyright (c) 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Frank van der Linden.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/socket.h>
+#include <sys/queue.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <ifaddrs.h>
+#include <sys/poll.h>
+#include <rpc/rpc.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netconfig.h>
+#include <stdio.h>
+#include <arpa/inet.h>
+
+#include "rpcbind.h"
+
+static struct sockaddr_in *local_in4;
+#ifdef INET6
+static struct sockaddr_in6 *local_in6;
+#endif
+
+static int bitmaskcmp(struct sockaddr *, struct sockaddr *, struct sockaddr *);
+
+/*
+ * For all bits set in "mask", compare the corresponding bits in
+ * "dst" and "src", and see if they match. Returns 0 if the addresses
+ * match.
+ */
+static int
+bitmaskcmp(struct sockaddr *dst, struct sockaddr *src, struct sockaddr *mask)
+{
+ int i;
+ u_int8_t *p1, *p2, *netmask;
+ int bytelen;
+
+ if (dst->sa_family != src->sa_family ||
+ dst->sa_family != mask->sa_family)
+ return (1);
+
+ switch (dst->sa_family) {
+ case AF_INET:
+ p1 = (uint8_t*) &SA2SINADDR(dst);
+ p2 = (uint8_t*) &SA2SINADDR(src);
+ netmask = (uint8_t*) &SA2SINADDR(mask);
+ bytelen = sizeof(struct in_addr);
+ break;
+#ifdef INET6
+ case AF_INET6:
+ p1 = (uint8_t*) &SA2SIN6ADDR(dst);
+ p2 = (uint8_t*) &SA2SIN6ADDR(src);
+ netmask = (uint8_t*) &SA2SIN6ADDR(mask);
+ bytelen = sizeof(struct in6_addr);
+ break;
+#endif
+ default:
+ return (1);
+ }
+
+ for (i = 0; i < bytelen; i++)
+ if ((p1[i] & netmask[i]) != (p2[i] & netmask[i]))
+ return (1);
+ return (0);
+}
+
+/*
+ * Find a server address that can be used by `caller' to contact
+ * the local service specified by `serv_uaddr'. If `clnt_uaddr' is
+ * non-NULL, it is used instead of `caller' as a hint suggesting
+ * the best address (e.g. the `r_addr' field of an rpc, which
+ * contains the rpcbind server address that the caller used).
+ *
+ * Returns the best server address as a malloc'd "universal address"
+ * string which should be freed by the caller. On error, returns NULL.
+ */
+char *
+addrmerge(struct netbuf *caller, const char *serv_uaddr, const char *clnt_uaddr,
+ const char *netid)
+{
+ struct ifaddrs *ifap, *ifp = NULL, *bestif;
+ struct netbuf *serv_nbp = NULL, *hint_nbp = NULL, tbuf;
+ struct sockaddr *caller_sa, *hint_sa, *ifsa, *ifmasksa, *serv_sa;
+ struct sockaddr_storage ss;
+ struct netconfig *nconf;
+ char *caller_uaddr = NULL;
+ const char *hint_uaddr = NULL;
+ char *ret = NULL;
+ int bestif_goodness;
+
+#ifdef ND_DEBUG
+ if (debugging)
+ fprintf(stderr, "addrmerge(caller, %s, %s, %s\n", serv_uaddr,
+ clnt_uaddr == NULL ? "NULL" : clnt_uaddr, netid);
+#endif
+ caller_sa = caller->buf;
+ if ((nconf = rpcbind_get_conf(netid)) == NULL)
+ goto freeit;
+ if ((caller_uaddr = taddr2uaddr(nconf, caller)) == NULL)
+ goto freeit;
+
+ /*
+ * Use `clnt_uaddr' as the hint if non-NULL, but ignore it if its
+ * address family is different from that of the caller.
+ */
+ hint_sa = NULL;
+ if (clnt_uaddr != NULL) {
+ hint_uaddr = clnt_uaddr;
+ if ((hint_nbp = uaddr2taddr(nconf, clnt_uaddr)) == NULL)
+ goto freeit;
+ hint_sa = hint_nbp->buf;
+ }
+ if (hint_sa == NULL || hint_sa->sa_family != caller_sa->sa_family) {
+ hint_uaddr = caller_uaddr;
+ hint_sa = caller->buf;
+ }
+
+#ifdef ND_DEBUG
+ if (debugging)
+ fprintf(stderr, "addrmerge: hint %s\n", hint_uaddr);
+#endif
+ /* Local caller, just return the server address. */
+ if (strncmp(caller_uaddr, "0.0.0.0.", 8) == 0 ||
+ strncmp(caller_uaddr, "::.", 3) == 0 || caller_uaddr[0] == '/') {
+ ret = strdup(serv_uaddr);
+ goto freeit;
+ }
+
+ if (getifaddrs(&ifp) < 0)
+ goto freeit;
+
+ /*
+ * Loop through all interface addresses. We are listening to an address
+ * if any of the following are true:
+ * a) It's a loopback address
+ * b) It was specified with the -h command line option
+ * c) There were no -h command line options.
+ *
+ * Among addresses on which we are listening, choose in order of
+ * preference an address that is:
+ *
+ * a) Equal to the hint
+ * b) A link local address with the same scope ID as the client's
+ * address, if the client's address is also link local
+ * c) An address on the same subnet as the client's address
+ * d) A non-localhost, non-p2p address
+ * e) Any usable address
+ */
+ bestif = NULL;
+ bestif_goodness = 0;
+ for (ifap = ifp; ifap != NULL; ifap = ifap->ifa_next) {
+ ifsa = ifap->ifa_addr;
+ ifmasksa = ifap->ifa_netmask;
+
+ /* Skip addresses where we don't listen */
+ if (ifsa == NULL || ifsa->sa_family != hint_sa->sa_family ||
+ !(ifap->ifa_flags & IFF_UP))
+ continue;
+
+ if (!(ifap->ifa_flags & IFF_LOOPBACK) && !listen_addr(ifsa))
+ continue;
+
+ if ((hint_sa->sa_family == AF_INET) &&
+ ((((struct sockaddr_in*)hint_sa)->sin_addr.s_addr ==
+ ((struct sockaddr_in*)ifsa)->sin_addr.s_addr))) {
+ const int goodness = 4;
+
+ bestif_goodness = goodness;
+ bestif = ifap;
+ goto found;
+ }
+#ifdef INET6
+ if ((hint_sa->sa_family == AF_INET6) &&
+ (0 == memcmp(&((struct sockaddr_in6*)hint_sa)->sin6_addr,
+ &((struct sockaddr_in6*)ifsa)->sin6_addr,
+ sizeof(struct in6_addr))) &&
+ (((struct sockaddr_in6*)hint_sa)->sin6_scope_id ==
+ (((struct sockaddr_in6*)ifsa)->sin6_scope_id))) {
+ const int goodness = 4;
+
+ bestif_goodness = goodness;
+ bestif = ifap;
+ goto found;
+ }
+ if (hint_sa->sa_family == AF_INET6) {
+ /*
+ * For v6 link local addresses, if the caller is on
+ * a link-local address then use the scope id to see
+ * which one.
+ */
+ if (IN6_IS_ADDR_LINKLOCAL(&SA2SIN6ADDR(ifsa)) &&
+ IN6_IS_ADDR_LINKLOCAL(&SA2SIN6ADDR(caller_sa)) &&
+ IN6_IS_ADDR_LINKLOCAL(&SA2SIN6ADDR(hint_sa))) {
+ if (SA2SIN6(ifsa)->sin6_scope_id ==
+ SA2SIN6(caller_sa)->sin6_scope_id) {
+ const int goodness = 3;
+
+ if (bestif_goodness < goodness) {
+ bestif = ifap;
+ bestif_goodness = goodness;
+ }
+ }
+ }
+ }
+#endif /* INET6 */
+ if (0 == bitmaskcmp(hint_sa, ifsa, ifmasksa)) {
+ const int goodness = 2;
+
+ if (bestif_goodness < goodness) {
+ bestif = ifap;
+ bestif_goodness = goodness;
+ }
+ }
+ if (!(ifap->ifa_flags & (IFF_LOOPBACK | IFF_POINTOPOINT))) {
+ const int goodness = 1;
+
+ if (bestif_goodness < goodness) {
+ bestif = ifap;
+ bestif_goodness = goodness;
+ }
+ }
+ if (bestif == NULL)
+ bestif = ifap;
+ }
+ if (bestif == NULL)
+ goto freeit;
+
+found:
+ /*
+ * Construct the new address using the address from
+ * `bestif', and the port number from `serv_uaddr'.
+ */
+ serv_nbp = uaddr2taddr(nconf, serv_uaddr);
+ if (serv_nbp == NULL)
+ goto freeit;
+ serv_sa = serv_nbp->buf;
+
+ memcpy(&ss, bestif->ifa_addr, bestif->ifa_addr->sa_len);
+ switch (ss.ss_family) {
+ case AF_INET:
+ SA2SIN(&ss)->sin_port = SA2SIN(serv_sa)->sin_port;
+ break;
+#ifdef INET6
+ case AF_INET6:
+ SA2SIN6(&ss)->sin6_port = SA2SIN6(serv_sa)->sin6_port;
+ break;
+#endif
+ }
+ tbuf.len = ss.ss_len;
+ tbuf.maxlen = sizeof(ss);
+ tbuf.buf = &ss;
+ ret = taddr2uaddr(nconf, &tbuf);
+
+freeit:
+ if (caller_uaddr != NULL)
+ free(caller_uaddr);
+ if (hint_nbp != NULL) {
+ free(hint_nbp->buf);
+ free(hint_nbp);
+ }
+ if (serv_nbp != NULL) {
+ free(serv_nbp->buf);
+ free(serv_nbp);
+ }
+ if (ifp != NULL)
+ freeifaddrs(ifp);
+
+#ifdef ND_DEBUG
+ if (debugging)
+ fprintf(stderr, "addrmerge: returning %s\n", ret);
+#endif
+ return ret;
+}
+
+void
+network_init(void)
+{
+#ifdef INET6
+ struct ifaddrs *ifap, *ifp;
+ struct ipv6_mreq mreq6;
+ unsigned int ifindex;
+ int s;
+#endif
+ int ecode;
+ struct addrinfo hints, *res;
+
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = AF_INET;
+ if ((ecode = getaddrinfo(NULL, "sunrpc", &hints, &res))) {
+ if (debugging)
+ fprintf(stderr, "can't get local ip4 address: %s\n",
+ gai_strerror(ecode));
+ } else {
+ local_in4 = (struct sockaddr_in *)malloc(sizeof *local_in4);
+ if (local_in4 == NULL) {
+ if (debugging)
+ fprintf(stderr, "can't alloc local ip4 addr\n");
+ }
+ memcpy(local_in4, res->ai_addr, sizeof *local_in4);
+ }
+
+#ifdef INET6
+ hints.ai_family = AF_INET6;
+ if ((ecode = getaddrinfo(NULL, "sunrpc", &hints, &res))) {
+ if (debugging)
+ fprintf(stderr, "can't get local ip6 address: %s\n",
+ gai_strerror(ecode));
+ } else {
+ local_in6 = (struct sockaddr_in6 *)malloc(sizeof *local_in6);
+ if (local_in6 == NULL) {
+ if (debugging)
+ fprintf(stderr, "can't alloc local ip6 addr\n");
+ }
+ memcpy(local_in6, res->ai_addr, sizeof *local_in6);
+ }
+
+ /*
+ * Now join the RPC ipv6 multicast group on all interfaces.
+ */
+ if (getifaddrs(&ifp) < 0)
+ return;
+
+ mreq6.ipv6mr_interface = 0;
+ inet_pton(AF_INET6, RPCB_MULTICAST_ADDR, &mreq6.ipv6mr_multiaddr);
+
+ s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+
+ /*
+ * Loop through all interfaces. For each IPv6 multicast-capable
+ * interface, join the RPC multicast group on that interface.
+ */
+ for (ifap = ifp; ifap != NULL; ifap = ifap->ifa_next) {
+ if (ifap->ifa_addr->sa_family != AF_INET6 ||
+ !(ifap->ifa_flags & IFF_MULTICAST))
+ continue;
+ ifindex = if_nametoindex(ifap->ifa_name);
+ if (ifindex == mreq6.ipv6mr_interface)
+ /*
+ * Already did this one.
+ */
+ continue;
+ mreq6.ipv6mr_interface = ifindex;
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq6,
+ sizeof mreq6) < 0)
+ if (debugging)
+ perror("setsockopt v6 multicast");
+ }
+#endif
+
+ /* close(s); */
+}
+
+struct sockaddr *
+local_sa(int af)
+{
+ switch (af) {
+ case AF_INET:
+ return (struct sockaddr *)local_in4;
+#ifdef INET6
+ case AF_INET6:
+ return (struct sockaddr *)local_in6;
+#endif
+ default:
+ return NULL;
+ }
+}
diff --git a/usr.sbin/rpcbind/warmstart.c b/usr.sbin/rpcbind/warmstart.c
new file mode 100644
index 0000000..473b6f7
--- /dev/null
+++ b/usr.sbin/rpcbind/warmstart.c
@@ -0,0 +1,178 @@
+/*-
+ * Copyright (c) 2009, Sun Microsystems, 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:
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of Sun Microsystems, Inc. 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 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 HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * warmstart.c
+ * Allows for gathering of registrations from an earlier dumped file.
+ *
+ * Copyright (c) 1990 by Sun Microsystems, Inc.
+ */
+
+/*
+ * #ident "@(#)warmstart.c 1.7 93/07/05 SMI"
+ * $FreeBSD$/
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <rpc/rpc.h>
+#include <rpc/rpcb_prot.h>
+#include <rpc/xdr.h>
+#ifdef PORTMAP
+#include <netinet/in.h>
+#include <rpc/pmap_prot.h>
+#endif
+#include <syslog.h>
+#include <unistd.h>
+
+#include "rpcbind.h"
+
+/*
+ * XXX this code is unsafe and is not used. It should be made safe.
+ */
+
+
+/* These files keep the pmap_list and rpcb_list in XDR format */
+#define RPCBFILE "/tmp/rpcbind.file"
+#ifdef PORTMAP
+#define PMAPFILE "/tmp/portmap.file"
+#endif
+
+static bool_t write_struct(char *, xdrproc_t, void *);
+static bool_t read_struct(char *, xdrproc_t, void *);
+
+static bool_t
+write_struct(char *filename, xdrproc_t structproc, void *list)
+{
+ FILE *fp;
+ XDR xdrs;
+ mode_t omask;
+
+ omask = umask(077);
+ fp = fopen(filename, "w");
+ if (fp == NULL) {
+ int i;
+
+ for (i = 0; i < 10; i++)
+ close(i);
+ fp = fopen(filename, "w");
+ if (fp == NULL) {
+ syslog(LOG_ERR,
+ "cannot open file = %s for writing", filename);
+ syslog(LOG_ERR, "cannot save any registration");
+ return (FALSE);
+ }
+ }
+ (void) umask(omask);
+ xdrstdio_create(&xdrs, fp, XDR_ENCODE);
+
+ if (structproc(&xdrs, list) == FALSE) {
+ syslog(LOG_ERR, "rpcbind: xdr_%s: failed", filename);
+ fclose(fp);
+ return (FALSE);
+ }
+ XDR_DESTROY(&xdrs);
+ fclose(fp);
+ return (TRUE);
+}
+
+static bool_t
+read_struct(char *filename, xdrproc_t structproc, void *list)
+{
+ FILE *fp;
+ XDR xdrs;
+ struct stat sbuf;
+
+ if (stat(filename, &sbuf) != 0) {
+ fprintf(stderr,
+ "rpcbind: cannot stat file = %s for reading\n", filename);
+ goto error;
+ }
+ if ((sbuf.st_uid != 0) || (sbuf.st_mode & S_IRWXG) ||
+ (sbuf.st_mode & S_IRWXO)) {
+ fprintf(stderr,
+ "rpcbind: invalid permissions on file = %s for reading\n",
+ filename);
+ goto error;
+ }
+ fp = fopen(filename, "r");
+ if (fp == NULL) {
+ fprintf(stderr,
+ "rpcbind: cannot open file = %s for reading\n", filename);
+ goto error;
+ }
+ xdrstdio_create(&xdrs, fp, XDR_DECODE);
+
+ if (structproc(&xdrs, list) == FALSE) {
+ fprintf(stderr, "rpcbind: xdr_%s: failed\n", filename);
+ fclose(fp);
+ goto error;
+ }
+ XDR_DESTROY(&xdrs);
+ fclose(fp);
+ return (TRUE);
+
+error: fprintf(stderr, "rpcbind: will start from scratch\n");
+ return (FALSE);
+}
+
+void
+write_warmstart(void)
+{
+ (void) write_struct(RPCBFILE, (xdrproc_t)xdr_rpcblist_ptr, &list_rbl);
+#ifdef PORTMAP
+ (void) write_struct(PMAPFILE, (xdrproc_t)xdr_pmaplist_ptr, &list_pml);
+#endif
+
+}
+
+void
+read_warmstart(void)
+{
+ rpcblist_ptr tmp_rpcbl = NULL;
+#ifdef PORTMAP
+ struct pmaplist *tmp_pmapl = NULL;
+#endif
+ int ok1, ok2 = TRUE;
+
+ ok1 = read_struct(RPCBFILE, (xdrproc_t)xdr_rpcblist_ptr, &tmp_rpcbl);
+ if (ok1 == FALSE)
+ return;
+#ifdef PORTMAP
+ ok2 = read_struct(PMAPFILE, (xdrproc_t)xdr_pmaplist_ptr, &tmp_pmapl);
+#endif
+ if (ok2 == FALSE) {
+ xdr_free((xdrproc_t) xdr_rpcblist_ptr, (char *)&tmp_rpcbl);
+ return;
+ }
+ xdr_free((xdrproc_t) xdr_rpcblist_ptr, (char *)&list_rbl);
+ list_rbl = tmp_rpcbl;
+#ifdef PORTMAP
+ xdr_free((xdrproc_t) xdr_pmaplist_ptr, (char *)&list_pml);
+ list_pml = tmp_pmapl;
+#endif
+}
diff --git a/usr.sbin/rrenumd/Makefile b/usr.sbin/rrenumd/Makefile
new file mode 100644
index 0000000..8c82fc3
--- /dev/null
+++ b/usr.sbin/rrenumd/Makefile
@@ -0,0 +1,37 @@
+# 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$
+
+PROG= rrenumd
+MAN= rrenumd.conf.5 rrenumd.8
+SRCS= rrenumd.c parser.y lexer.l
+
+CFLAGS+= -DIPSEC -I. -I${.CURDIR}
+YFLAGS= -d
+
+WARNS?= 2
+
+LIBADD= ipsec l y
+
+CLEANFILES= y.tab.h
+SRCS+= y.tab.h
+y.tab.h: parser.y
+
+.if defined(YACCDEBUG)
+CFLAGS+= -DYYDEBUG
+YFLAGS+= -t -v
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/rrenumd/Makefile.depend b/usr.sbin/rrenumd/Makefile.depend
new file mode 100644
index 0000000..13367d0
--- /dev/null
+++ b/usr.sbin/rrenumd/Makefile.depend
@@ -0,0 +1,29 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libipsec \
+ lib/liby \
+ usr.bin/lex/lib \
+ usr.bin/yacc.host \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+lexer.o: lexer.c
+lexer.o: y.tab.h
+lexer.po: lexer.c
+lexer.po: y.tab.h
+parser.o: parser.c
+parser.po: parser.c
+.endif
diff --git a/usr.sbin/rrenumd/lexer.l b/usr.sbin/rrenumd/lexer.l
new file mode 100644
index 0000000..47656e8
--- /dev/null
+++ b/usr.sbin/rrenumd/lexer.l
@@ -0,0 +1,269 @@
+/* $KAME: lexer.l,v 1.7 2000/11/08 02:40:53 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$
+ */
+
+%{
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+
+#include <string.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <netinet/icmp6.h>
+
+#include <arpa/inet.h>
+
+#include "y.tab.h"
+
+int lineno = 1;
+
+#define LINEBUF_SIZE 1000
+char linebuf[LINEBUF_SIZE];
+
+int parse(FILE **);
+void yyerror(const char *);
+int yylex(void);
+%}
+
+%option nounput
+
+/* common section */
+nl \n
+ws [ \t]+
+digit [0-9]
+letter [0-9A-Za-z]
+hexdigit [0-9A-Fa-f]
+special [()+\|\?\*,]
+dot \.
+hyphen \-
+colon \:
+slash \/
+bcl \{
+ecl \}
+semi \;
+usec {dot}{digit}{1,6}
+comment \#.*
+qstring \"[^"]*\"
+decstring {digit}+
+hexpair {hexdigit}{hexdigit}
+hexstring 0[xX]{hexdigit}+
+octetstring {octet}({dot}{octet})+
+ipv4addr {digit}{1,3}({dot}{digit}{1,3}){0,3}
+ipv6addr {hexdigit}{0,4}({colon}{hexdigit}{0,4}){2,7}
+ipaddrmask {slash}{digit}{1,3}
+keyword {letter}{letter}+
+name {letter}(({letter}|{digit}|{hyphen})*({letter}|{digit}))*
+hostname {name}(({dot}{name})+{dot}?)?
+
+timeval {digit}{0,2}
+days d{timeval}
+hours h{timeval}
+minutes m{timeval}
+seconds s{timeval}
+
+mprefix match_prefix|match-prefix
+uprefix use_prefix|use-prefix
+
+%%
+ /* rrenumd keywords */
+debug {
+ return(DEBUG_CMD);
+ }
+dest {
+ return(DEST_CMD);
+ }
+retry {
+ return(RETRY_CMD);
+ }
+seqnum {
+ return(SEQNUM_CMD);
+ }
+add {
+ yylval.num = RPM_PCO_ADD;
+ return(ADD);
+ }
+change {
+ yylval.num = RPM_PCO_CHANGE;
+ return(CHANGE);
+ }
+setglobal {
+ yylval.num = RPM_PCO_SETGLOBAL;
+ return(SETGLOBAL);
+ }
+{mprefix} {
+ return(MATCH_PREFIX_CMD);
+ }
+maxlen {
+ return(MAXLEN_CMD);
+ }
+minlen {
+ return(MINLEN_CMD);
+ }
+{uprefix} {
+ return(USE_PREFIX_CMD);
+ }
+keeplen {
+ return(KEEPLEN_CMD);
+ }
+
+vltime {
+ return(VLTIME_CMD);
+ }
+pltime {
+ return(PLTIME_CMD);
+ }
+raf_onlink {
+ return(RAF_ONLINK_CMD);
+ }
+raf_auto {
+ return(RAF_AUTO_CMD);
+ }
+rrf_decrvalid {
+ return(RAF_DECRVALID_CMD);
+ }
+rrf_decrprefd {
+ return(RAF_DECRPREFD_CMD);
+ }
+{days} {
+ yytext++;
+ yylval.num = atoi(yytext);
+ return(DAYS);
+ }
+{hours} {
+ yytext++;
+ yylval.num = atoi(yytext);
+ return(HOURS);
+ }
+{minutes} {
+ yytext++;
+ yylval.num = atoi(yytext);
+ return(MINUTES);
+ }
+{seconds} {
+ yytext++;
+ yylval.num = atoi(yytext);
+ return(SECONDS);
+ }
+infinity {
+ return(INFINITY);
+ }
+
+on {
+ yylval.num = 1;
+ return(ON);
+ }
+off {
+ yylval.num = 0;
+ return(OFF);
+ }
+
+ /* basic rules */
+{ws} ;
+{nl} {
+ lineno++;
+ }
+{semi} {
+ return EOS;
+ }
+{bcl} {
+ return BCL;
+ }
+{ecl} {
+ return ECL;
+ }
+{qstring} {
+ yylval.cs.cp = yytext;
+ yylval.cs.len = yyleng;
+ return QSTRING;
+ }
+{decstring} {
+ yylval.cs.cp = yytext;
+ yylval.cs.len = yyleng;
+ return DECSTRING;
+ }
+{name} {
+ yylval.cs.cp = yytext;
+ yylval.cs.len = yyleng;
+ return NAME;
+ }
+{ipv4addr} {
+ memset(&yylval.addr4, 0, sizeof(struct in_addr));
+ if (inet_pton(AF_INET, yytext,
+ &yylval.addr4) == 1) {
+ return IPV4ADDR;
+ } else {
+ return ERROR;
+ }
+ }
+{ipv6addr} {
+ memset(&yylval.addr6, 0, sizeof(struct in6_addr));
+ if (inet_pton(AF_INET6, yytext,
+ &yylval.addr6) == 1) {
+ return IPV6ADDR;
+ } else {
+ return ERROR;
+ }
+ }
+{ipaddrmask} {
+ yytext++;
+ yylval.num = atoi(yytext);
+ return(PREFIXLEN);
+ }
+{hostname} {
+ yylval.cs.cp = yytext;
+ yylval.cs.len = yyleng;
+ return HOSTNAME;
+ }
+%%
+
+int parse(FILE **fp)
+{
+ extern int yyparse(void);
+
+ yyin = *fp;
+
+ if (yyparse())
+ return(-1);
+
+ return(0);
+
+}
+
+void
+yyerror(const char *s)
+{
+ printf("%s: at %s in line %d\n", s, yytext, lineno);
+}
diff --git a/usr.sbin/rrenumd/parser.y b/usr.sbin/rrenumd/parser.y
new file mode 100644
index 0000000..6436df7
--- /dev/null
+++ b/usr.sbin/rrenumd/parser.y
@@ -0,0 +1,673 @@
+/* $KAME: parser.y,v 1.8 2000/11/08 03:03:34 jinmei 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$
+ */
+
+%{
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/queue.h>
+
+#include <net/if.h>
+
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <netinet/icmp6.h>
+
+#include <limits.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "rrenumd.h"
+
+struct config_is_set {
+ u_short cis_dest : 1;
+} cis;
+
+struct dst_list *dl_head;
+struct payload_list *pl_head, ple_cur;
+u_int retry;
+char errbuf[LINE_MAX];
+
+extern int lineno;
+extern void yyerror(const char *s);
+extern int yylex(void);
+static struct payload_list * pllist_lookup(int seqnum);
+static void pllist_enqueue(struct payload_list *pl_entry);
+
+#define MAX_RETRYNUM 10 /* upper limit of retry in this rrenumd program */
+#define MAX_SEQNUM 256 /* upper limit of seqnum in this rrenumd program */
+#define NOSPEC -1
+
+%}
+
+%union {
+ u_long num;
+ struct {
+ char *cp;
+ int len;
+ } cs;
+ struct in_addr addr4;
+ struct in6_addr addr6;
+ struct {
+ struct in6_addr addr;
+ u_char plen;
+ } prefix;
+ struct dst_list *dl;
+ struct payload_list *pl;
+ struct sockaddr *sa;
+}
+
+%token <num> ADD CHANGE SETGLOBAL
+%token DEBUG_CMD DEST_CMD RETRY_CMD SEQNUM_CMD
+%token MATCH_PREFIX_CMD MAXLEN_CMD MINLEN_CMD
+%token USE_PREFIX_CMD KEEPLEN_CMD
+%token VLTIME_CMD PLTIME_CMD
+%token RAF_ONLINK_CMD RAF_AUTO_CMD RAF_DECRVALID_CMD RAF_DECRPREFD_CMD
+%token <num> DAYS HOURS MINUTES SECONDS INFINITY
+%token <num> ON OFF
+%token BCL ECL EOS ERROR
+%token <cs> NAME HOSTNAME QSTRING DECSTRING
+%token <addr4> IPV4ADDR
+%token <addr6> IPV6ADDR
+%token <num> PREFIXLEN
+
+%type <num> retrynum seqnum rrenum_cmd
+%type <num> prefixlen maxlen minlen keeplen vltime pltime
+%type <num> lifetime days hours minutes seconds
+%type <num> decstring
+%type <num> raf_onlink raf_auto raf_decrvalid raf_decrprefd flag
+%type <dl> dest_addrs dest_addr sin sin6
+%type <pl> rrenum_statement
+%type <cs> ifname
+%type <prefix> prefixval
+
+%%
+config:
+ /* empty */
+ | statements
+ ;
+
+statements:
+ statement
+ | statements statement
+ ;
+
+statement:
+ debug_statement
+ | destination_statement
+ | rrenum_statement_without_seqnum
+ | rrenum_statement_with_seqnum
+ | error EOS
+ {
+ yyerrok;
+ }
+ | EOS
+ ;
+
+debug_statement:
+ DEBUG_CMD flag EOS
+ {
+#ifdef YYDEBUG
+ yydebug = $2;
+#endif /* YYDEBUG */
+ }
+ ;
+
+destination_statement:
+ DEST_CMD dest_addrs retrynum EOS
+ {
+ dl_head = $2;
+ retry = $3;
+ }
+ ;
+
+dest_addrs:
+ dest_addr
+ | dest_addrs dest_addr
+ {
+ $2->dl_next = $1;
+ $$ = $2;
+ }
+ ;
+
+dest_addr :
+ sin
+ {
+ with_v4dest = 1;
+ }
+ | sin6
+ {
+ with_v6dest = 1;
+ }
+ | sin6 ifname
+ {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)$1->dl_dst;
+ sin6->sin6_scope_id = if_nametoindex($2.cp);
+ with_v6dest = 1;
+ $$ = $1;
+ }
+ | HOSTNAME
+ {
+ struct sockaddr_storage *ss;
+ struct addrinfo hints, *res;
+ int error;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_CANONNAME;
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_RAW;
+ hints.ai_protocol = 0;
+ error = getaddrinfo($1.cp, 0, &hints, &res);
+ if (error) {
+ snprintf(errbuf, sizeof(errbuf),
+ "name resolution failed for %s:%s",
+ $1.cp, gai_strerror(error));
+ yyerror(errbuf);
+ }
+ ss = (struct sockaddr_storage *)malloc(sizeof(*ss));
+ memset(ss, 0, sizeof(*ss));
+ memcpy(ss, res->ai_addr, res->ai_addr->sa_len);
+ freeaddrinfo(res);
+
+ $$ = (struct dst_list *)
+ malloc(sizeof(struct dst_list));
+ memset($$, 0, sizeof(struct dst_list));
+ $$->dl_dst = (struct sockaddr *)ss;
+ }
+ ;
+
+sin:
+ IPV4ADDR
+ {
+ struct sockaddr_in *sin;
+
+ sin = (struct sockaddr_in *)malloc(sizeof(*sin));
+ memset(sin, 0, sizeof(*sin));
+ sin->sin_len = sizeof(*sin);
+ sin->sin_family = AF_INET;
+ sin->sin_addr = $1;
+
+ $$ = (struct dst_list *)
+ malloc(sizeof(struct dst_list));
+ memset($$, 0, sizeof(struct dst_list));
+ $$->dl_dst = (struct sockaddr *)sin;
+ }
+ ;
+
+sin6:
+ IPV6ADDR
+ {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)malloc(sizeof(*sin6));
+ memset(sin6, 0, sizeof(*sin6));
+ sin6->sin6_len = sizeof(*sin6);
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_addr = $1;
+
+ $$ = (struct dst_list *)
+ malloc(sizeof(struct dst_list));
+ memset($$, 0, sizeof(struct dst_list));
+ $$->dl_dst = (struct sockaddr *)sin6;
+ }
+
+ifname:
+ NAME
+ {
+ $$.cp = strdup($1.cp);
+ $$.len = $1.len;
+ }
+ | QSTRING
+ {
+ $1.cp[$1.len - 1] = 0;
+ $$.cp = strdup(&$1.cp[1]);
+ $$.len = $1.len - 2;
+ }
+ ;
+
+retrynum:
+ /* empty */
+ {
+ $$ = 2;
+ }
+ | RETRY_CMD decstring
+ {
+ if ($2 > MAX_RETRYNUM)
+ $2 = MAX_RETRYNUM;
+ $$ = $2;
+ }
+ ;
+
+rrenum_statement_with_seqnum:
+ SEQNUM_CMD seqnum
+ {
+ if (pllist_lookup($2)) {
+ snprintf(errbuf, sizeof(errbuf),
+ "duplicate seqnum %ld specified at %d",
+ $2, lineno);
+ yyerror(errbuf);
+ }
+ }
+ BCL rrenum_statement EOS ECL EOS
+ {
+ $5->pl_irr.rr_seqnum = $2;
+ pllist_enqueue($5);
+ }
+ ;
+
+seqnum:
+ /* empty */
+ {
+ $$ = 0;
+ }
+ | decstring
+ {
+ if ($1 > MAX_SEQNUM) {
+ snprintf(errbuf, sizeof(errbuf),
+ "seqnum %ld is illegal for this program. "
+ "should be between 0 and %d",
+ $1, MAX_SEQNUM);
+ yyerror(errbuf);
+ }
+ $$ = $1;
+ }
+ ;
+
+rrenum_statement_without_seqnum:
+ rrenum_statement EOS
+ {
+ if (pllist_lookup(0)) {
+ snprintf(errbuf, sizeof(errbuf),
+ "duplicate seqnum %d specified at %d",
+ 0, lineno);
+ yyerror(errbuf);
+ }
+ $1->pl_irr.rr_seqnum = 0;
+ pllist_enqueue($1);
+ }
+ ;
+
+rrenum_statement:
+ match_prefix_definition use_prefix_definition
+ {
+ $$ = (struct payload_list *)
+ malloc(sizeof(struct payload_list));
+ memcpy($$, &ple_cur, sizeof(ple_cur));
+ }
+ ;
+
+match_prefix_definition:
+ rrenum_cmd MATCH_PREFIX_CMD prefixval maxlen minlen
+ {
+ struct icmp6_router_renum *irr;
+ struct rr_pco_match *rpm;
+
+ irr = (struct icmp6_router_renum *)&ple_cur.pl_irr;
+ rpm = (struct rr_pco_match *)(irr + 1);
+ memset(rpm, 0, sizeof(*rpm));
+
+ rpm->rpm_code = $1;
+ rpm->rpm_prefix = $3.addr;
+ rpm->rpm_matchlen = $3.plen;
+ rpm->rpm_maxlen = $4;
+ rpm->rpm_minlen = $5;
+ }
+ ;
+
+rrenum_cmd:
+ /* empty */
+ {
+ $$ = RPM_PCO_ADD;
+ }
+ | ADD
+ | CHANGE
+ | SETGLOBAL
+ ;
+
+prefixval:
+ IPV6ADDR prefixlen
+ {
+ $$.addr = $1;
+ $$.plen = $2;
+ }
+ ;
+
+prefixlen:
+ /* empty */
+ {
+ $$ = 64;
+ }
+ | PREFIXLEN
+ ;
+
+maxlen:
+ /* empty */
+ {
+ $$ = 128;
+ }
+ | MAXLEN_CMD decstring
+ {
+ if ($2 > 128)
+ $2 = 128;
+ $$ = $2;
+ }
+ ;
+
+minlen:
+ /* empty */
+ {
+ $$ = 0;
+ }
+ | MINLEN_CMD decstring
+ {
+ if ($2 > 128)
+ $2 = 128;
+ $$ = $2;
+ }
+ ;
+
+use_prefix_definition:
+ /* empty */
+ {
+ struct icmp6_router_renum *irr;
+ struct rr_pco_match *rpm;
+ struct rr_pco_use *rpu;
+
+ irr = (struct icmp6_router_renum *)&ple_cur.pl_irr;
+ rpm = (struct rr_pco_match *)(irr + 1);
+ rpu = (struct rr_pco_use *)(rpm + 1);
+ memset(rpu, 0, sizeof(*rpu));
+ }
+ | USE_PREFIX_CMD prefixval keeplen use_prefix_values
+ {
+ struct icmp6_router_renum *irr;
+ struct rr_pco_match *rpm;
+ struct rr_pco_use *rpu;
+
+ irr = (struct icmp6_router_renum *)&ple_cur.pl_irr;
+ rpm = (struct rr_pco_match *)(irr + 1);
+ rpu = (struct rr_pco_use *)(rpm + 1);
+
+ rpu->rpu_prefix = $2.addr;
+ rpu->rpu_uselen = $2.plen;
+ rpu->rpu_keeplen = $3;
+ }
+ ;
+
+use_prefix_values:
+ /* empty */
+ {
+ struct icmp6_router_renum *irr;
+ struct rr_pco_match *rpm;
+ struct rr_pco_use *rpu;
+
+ irr = (struct icmp6_router_renum *)&ple_cur.pl_irr;
+ rpm = (struct rr_pco_match *)(irr + 1);
+ rpu = (struct rr_pco_use *)(rpm + 1);
+ memset(rpu, 0, sizeof(*rpu));
+
+ rpu->rpu_vltime = htonl(DEF_VLTIME);
+ rpu->rpu_pltime = htonl(DEF_PLTIME);
+ rpu->rpu_ramask = 0;
+ rpu->rpu_flags = 0;
+ }
+ | BCL vltime pltime raf_onlink raf_auto raf_decrvalid raf_decrprefd ECL
+ {
+ struct icmp6_router_renum *irr;
+ struct rr_pco_match *rpm;
+ struct rr_pco_use *rpu;
+
+ irr = (struct icmp6_router_renum *)&ple_cur.pl_irr;
+ rpm = (struct rr_pco_match *)(irr + 1);
+ rpu = (struct rr_pco_use *)(rpm + 1);
+ memset(rpu, 0, sizeof(*rpu));
+
+ rpu->rpu_vltime = $2;
+ rpu->rpu_pltime = $3;
+ if ($4 == NOSPEC) {
+ rpu->rpu_ramask &=
+ ~ICMP6_RR_PCOUSE_RAFLAGS_ONLINK;
+ } else {
+ rpu->rpu_ramask |=
+ ICMP6_RR_PCOUSE_RAFLAGS_ONLINK;
+ if ($4 == ON) {
+ rpu->rpu_raflags |=
+ ICMP6_RR_PCOUSE_RAFLAGS_ONLINK;
+ } else {
+ rpu->rpu_raflags &=
+ ~ICMP6_RR_PCOUSE_RAFLAGS_ONLINK;
+ }
+ }
+ if ($5 == NOSPEC) {
+ rpu->rpu_ramask &=
+ ICMP6_RR_PCOUSE_RAFLAGS_AUTO;
+ } else {
+ rpu->rpu_ramask |=
+ ICMP6_RR_PCOUSE_RAFLAGS_AUTO;
+ if ($5 == ON) {
+ rpu->rpu_raflags |=
+ ICMP6_RR_PCOUSE_RAFLAGS_AUTO;
+ } else {
+ rpu->rpu_raflags &=
+ ~ICMP6_RR_PCOUSE_RAFLAGS_AUTO;
+ }
+ }
+ rpu->rpu_flags = 0;
+ if ($6 == ON) {
+ rpu->rpu_flags |=
+ ICMP6_RR_PCOUSE_FLAGS_DECRVLTIME;
+ }
+ if ($7 == ON) {
+ rpu->rpu_flags |=
+ ICMP6_RR_PCOUSE_FLAGS_DECRPLTIME;
+ }
+ }
+ ;
+
+keeplen:
+ /* empty */
+ {
+ $$ = 0;
+ }
+ | KEEPLEN_CMD decstring
+ {
+ if ($2 > 128)
+ $2 = 128;
+ $$ = $2;
+ }
+ ;
+
+
+vltime:
+ /* empty */
+ {
+ $$ = htonl(DEF_VLTIME);
+ }
+ | VLTIME_CMD lifetime
+ {
+ $$ = htonl($2);
+ }
+ ;
+
+pltime:
+ /* empty */
+ {
+ $$ = htonl(DEF_PLTIME);
+ }
+ | PLTIME_CMD lifetime
+ {
+ $$ = htonl($2);
+ }
+
+raf_onlink:
+ /* empty */
+ {
+ $$ = NOSPEC;
+ }
+ | RAF_ONLINK_CMD flag
+ {
+ $$ = $2;
+ }
+ ;
+
+raf_auto:
+ /* empty */
+ {
+ $$ = NOSPEC;
+ }
+ | RAF_AUTO_CMD flag
+ {
+ $$ = $2;
+ }
+ ;
+
+raf_decrvalid:
+ /* empty */
+ {
+ $$ = NOSPEC;
+ }
+ | RAF_DECRVALID_CMD flag
+ {
+ $$ = $2;
+ }
+ ;
+
+raf_decrprefd:
+ /* empty */
+ {
+ $$ = NOSPEC;
+ }
+ | RAF_DECRPREFD_CMD flag
+ {
+ $$ = $2;
+ }
+ ;
+
+flag:
+ ON { $$ = ON; }
+ | OFF { $$ = OFF; }
+ ;
+
+lifetime:
+ decstring
+ | INFINITY
+ {
+ $$ = 0xffffffff;
+ }
+ | days hours minutes seconds
+ {
+ int d, h, m, s;
+
+ d = $1 * 24 * 60 * 60;
+ h = $2 * 60 * 60;
+ m = $3 * 60;
+ s = $4;
+ $$ = d + h + m + s;
+ }
+ ;
+
+days:
+ /* empty */
+ {
+ $$ = 0;
+ }
+ | DAYS
+ ;
+
+hours:
+ /* empty */
+ {
+ $$ = 0;
+ }
+ | HOURS
+ ;
+
+minutes:
+ /* empty */
+ {
+ $$ = 0;
+ }
+ | MINUTES
+ ;
+
+seconds:
+ /* empty */
+ {
+ $$ = 0;
+ }
+ | SECONDS
+ ;
+
+decstring:
+ DECSTRING
+ {
+ int dval;
+
+ dval = atoi($1.cp);
+ $$ = dval;
+ }
+ ;
+
+%%
+
+static struct payload_list *
+pllist_lookup(int seqnum)
+{
+ struct payload_list *pl;
+ for (pl = pl_head; pl && pl->pl_irr.rr_seqnum != seqnum;
+ pl = pl->pl_next)
+ continue;
+ return (pl);
+}
+
+static void
+pllist_enqueue(struct payload_list *pl_entry)
+{
+ struct payload_list *pl, *pl_last;
+
+ pl_last = NULL;
+ for (pl = pl_head;
+ pl && pl->pl_irr.rr_seqnum < pl_entry->pl_irr.rr_seqnum;
+ pl_last = pl, pl = pl->pl_next)
+ continue;
+ if (pl_last)
+ pl_last->pl_next = pl_entry;
+ else
+ pl_head = pl_entry;
+
+ return;
+}
diff --git a/usr.sbin/rrenumd/rrenumd.8 b/usr.sbin/rrenumd/rrenumd.8
new file mode 100644
index 0000000..cc13a01
--- /dev/null
+++ b/usr.sbin/rrenumd/rrenumd.8
@@ -0,0 +1,103 @@
+.\" $KAME: rrenumd.8,v 1.6 2001/01/22 02:06:24 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 7, 1998
+.Dt RRENUMD 8
+.Os
+.Sh NAME
+.Nm rrenumd
+.Nd router renumbering daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl df
+.Oo
+.Fl c Ar conf_file | Fl s
+.Oc
+.Sh DESCRIPTION
+The
+.Nm
+utility transmits router renumbering request packets,
+to renumber the routers in the site network.
+.Pp
+On KAME-based systems,
+router renumbering requests are received and processed by
+.Xr rtadvd 8 .
+For other systems, refer to relevant documents.
+.Pp
+The program will daemonize itself on invocation.
+It reads configuration information from standard input if
+.Fl s
+is specified, or from
+.Ar conf_file
+if
+.Fl c Ar conf_file
+is specified.
+.Pp
+The contents of configuration information are described in
+.Xr rrenumd.conf 5 .
+.Pp
+After successful configuration,
+.Nm
+sends router renumbering
+messages periodically to configured destinations.
+Messages contain prefixes configured to be renumbered.
+.Bl -tag -width indent
+.\"
+.It Fl d
+Debug mode.
+.It Fl f
+Foreground mode.
+Do not become daemon.
+.It Fl s
+Script mode.
+Configuration information is obtained from standard input.
+.It Fl c Ar conf_file
+Specify a configuration file where configuration information is kept.
+.El
+.Sh EXIT STATUS
+The program exits with 0 on success, and non-zero on failures.
+.Sh SEE ALSO
+.Xr rrenumd.conf 5 ,
+.Xr rtadvd 8
+.Sh STANDARDS
+.Rs
+.%A Matt Crawford
+.%R RFC
+.%N 2894
+.%D August 2000
+.%T "Router Renumbering for IPv6"
+.Re
+.Sh HISTORY
+The
+.Nm
+utility first appeared in KAME IPv6 protocol stack kit.
+.\" .Sh BUGS
+.\" (to be written)
diff --git a/usr.sbin/rrenumd/rrenumd.c b/usr.sbin/rrenumd/rrenumd.c
new file mode 100644
index 0000000..68f6847
--- /dev/null
+++ b/usr.sbin/rrenumd/rrenumd.c
@@ -0,0 +1,663 @@
+/* $KAME: rrenumd.c,v 1.20 2000/11/08 02:40:53 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$
+ */
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/time.h>
+
+#include <string.h>
+
+#include <net/route.h>
+
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+
+#include <arpa/inet.h>
+
+#ifdef IPSEC
+#include <netipsec/ipsec.h>
+#endif
+
+#include <stdio.h>
+#include <err.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+
+#include "rrenumd.h"
+
+#define LL_ALLROUTERS "ff02::2"
+#define SL_ALLROUTERS "ff05::2"
+
+#define RR_MCHLIM_DEFAULT 64
+
+#ifndef IN6_IS_SCOPE_LINKLOCAL
+#define IN6_IS_SCOPE_LINKLOCAL(a) \
+ ((IN6_IS_ADDR_LINKLOCAL(a)) || \
+ (IN6_IS_ADDR_MC_LINKLOCAL(a)))
+#endif /* IN6_IS_SCOPE_LINKLOCAL */
+
+struct flags {
+ u_long debug : 1;
+ u_long fg : 1;
+#ifdef IPSEC
+#ifdef IPSEC_POLICY_IPSEC
+ u_long policy : 1;
+#else /* IPSEC_POLICY_IPSEC */
+ u_long auth : 1;
+ u_long encrypt : 1;
+#endif /* IPSEC_POLICY_IPSEC */
+#endif /*IPSEC*/
+};
+
+struct msghdr sndmhdr;
+struct msghdr rcvmhdr;
+struct sockaddr_in6 from;
+struct sockaddr_in6 sin6_ll_allrouters;
+
+int s4, s6;
+int with_v4dest, with_v6dest;
+struct in6_addr prefix; /* ADHOC */
+int prefixlen = 64; /* ADHOC */
+
+extern int parse(FILE **);
+
+static void show_usage(void);
+static void init_sin6(struct sockaddr_in6 *, const char *);
+#if 0
+static void join_multi(const char *);
+#endif
+static void init_globals(void);
+static void config(FILE **);
+#ifdef IPSEC_POLICY_IPSEC
+static void sock6_open(struct flags *, char *);
+static void sock4_open(struct flags *, char *);
+#else
+static void sock6_open(struct flags *);
+static void sock4_open(struct flags *);
+#endif
+static void rrenum_output(struct payload_list *, struct dst_list *);
+static void rrenum_snd_eachdst(struct payload_list *);
+#if 0
+static void rrenum_snd_fullsequence(void);
+#endif
+static void rrenum_input(int);
+int main(int, char *[]);
+
+
+/* Print usage. Don't call this after daemonized. */
+static void
+show_usage()
+{
+ fprintf(stderr, "usage: rrenumd [-c conf_file|-s] [-df"
+#ifdef IPSEC
+#ifdef IPSEC_POLICY_IPSEC
+ "] [-P policy"
+#else /* IPSEC_POLICY_IPSEC */
+ "AE"
+#endif /* IPSEC_POLICY_IPSEC */
+#endif /* IPSEC */
+ "]\n");
+ exit(1);
+}
+
+static void
+init_sin6(struct sockaddr_in6 *sin6, const char *addr_ascii)
+{
+ memset(sin6, 0, sizeof(*sin6));
+ sin6->sin6_len = sizeof(*sin6);
+ sin6->sin6_family = AF_INET6;
+ if (inet_pton(AF_INET6, addr_ascii, &sin6->sin6_addr) != 1)
+ ; /* XXX do something */
+}
+
+#if 0 /* XXX: not necessary ?? */
+static void
+join_multi(const char *addrname)
+{
+ struct ipv6_mreq mreq;
+
+ if (inet_pton(AF_INET6, addrname, &mreq.ipv6mr_multiaddr.s6_addr)
+ != 1) {
+ syslog(LOG_ERR, "<%s> inet_pton failed(library bug?)",
+ __func__);
+ exit(1);
+ }
+ /* ADHOC: currently join only one */
+ {
+ if ((mreq.ipv6mr_interface = if_nametoindex(ifname)) == 0) {
+ syslog(LOG_ERR, "<%s> ifname %s should be invalid: %s",
+ __func__, ifname, strerror(errno));
+ exit(1);
+ }
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_JOIN_GROUP,
+ &mreq,
+ sizeof(mreq)) < 0) {
+ syslog(LOG_ERR, "<%s> IPV6_JOIN_GROUP on %s: %s",
+ __func__, ifname, strerror(errno));
+ exit(1);
+ }
+ }
+}
+#endif
+
+static void
+init_globals()
+{
+ static struct iovec rcviov;
+ static u_char rprdata[4500]; /* maximal MTU of connected links */
+ static u_char *rcvcmsgbuf = NULL;
+ static u_char *sndcmsgbuf = NULL;
+ int sndcmsglen, rcvcmsglen;
+
+ /* init ll_allrouters */
+ init_sin6(&sin6_ll_allrouters, LL_ALLROUTERS);
+
+ /* initialize msghdr for receiving packets */
+ rcviov.iov_base = (caddr_t)rprdata;
+ rcviov.iov_len = sizeof(rprdata);
+ rcvmhdr.msg_namelen = sizeof(struct sockaddr_in6);
+ rcvmhdr.msg_iov = &rcviov;
+ rcvmhdr.msg_iovlen = 1;
+ rcvcmsglen = CMSG_SPACE(sizeof(struct in6_pktinfo)) +
+ CMSG_SPACE(sizeof(int));
+ if (rcvcmsgbuf == NULL &&
+ (rcvcmsgbuf = (u_char *)malloc(rcvcmsglen)) == NULL) {
+ syslog(LOG_ERR, "<%s>: malloc failed", __func__);
+ exit(1);
+ }
+ rcvmhdr.msg_control = (caddr_t)rcvcmsgbuf;
+ rcvmhdr.msg_controllen = rcvcmsglen;
+
+ /* initialize msghdr for sending packets */
+ sndmhdr.msg_namelen = sizeof(struct sockaddr_in6);
+ sndmhdr.msg_iovlen = 1;
+ sndcmsglen = CMSG_SPACE(sizeof(struct in6_pktinfo)) +
+ CMSG_SPACE(sizeof(int));
+ if (sndcmsgbuf == NULL &&
+ (sndcmsgbuf = (u_char *)malloc(sndcmsglen)) == NULL) {
+ syslog(LOG_ERR, "<%s>: malloc failed", __func__);
+ exit(1);
+ }
+ sndmhdr.msg_control = (caddr_t)sndcmsgbuf;
+ sndmhdr.msg_controllen = sndcmsglen;
+}
+
+static void
+config(FILE **fpp)
+{
+ struct payload_list *pl;
+ struct iovec *iov;
+ struct icmp6_router_renum *irr;
+ struct rr_pco_match *rpm;
+
+ if (parse(fpp) < 0) {
+ syslog(LOG_ERR, "<%s> parse failed", __func__);
+ exit(1);
+ }
+
+ /* initialize fields not configured by parser */
+ for (pl = pl_head; pl; pl = pl->pl_next) {
+ iov = (struct iovec *)&pl->pl_sndiov;
+ irr = (struct icmp6_router_renum *)&pl->pl_irr;
+ rpm = (struct rr_pco_match *)&pl->pl_rpm;
+
+ irr->rr_type = ICMP6_ROUTER_RENUMBERING;
+ irr->rr_code = 0;
+ /*
+ * now we don't support multiple PCOs in a rr message.
+ * so segment number is not supported.
+ */
+ /* TODO: rr flags config in parser */
+ irr->rr_flags |= ICMP6_RR_FLAGS_SPECSITE;
+ /* TODO: max delay config in parser */
+
+ /*
+ * means only 1 use_prefix is contained as router-renum-05.txt.
+ * now we don't support multiple PCOs in a rr message,
+ * nor multiple use_prefix in one PCO.
+ */
+ rpm->rpm_len = 4*1 +3;
+ rpm->rpm_ordinal = 0;
+ iov->iov_base = (caddr_t)irr;
+ iov->iov_len = sizeof(struct icmp6_router_renum)
+ + sizeof(struct rr_pco_match)
+ + sizeof(struct rr_pco_use);
+ }
+}
+
+static void
+sock6_open(struct flags *flags
+#ifdef IPSEC_POLICY_IPSEC
+ , char *policy
+#endif /* IPSEC_POLICY_IPSEC */
+ )
+{
+ struct icmp6_filter filt;
+ int on;
+#ifdef IPSEC
+#ifndef IPSEC_POLICY_IPSEC
+ int optval;
+#endif
+#endif
+
+ if (with_v6dest == 0)
+ return;
+ if (with_v6dest &&
+ (s6 = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0) {
+ syslog(LOG_ERR, "<%s> socket(v6): %s", __func__,
+ strerror(errno));
+ exit(1);
+ }
+
+ /*
+ * join all routers multicast addresses.
+ */
+#if 0 /* XXX: not necessary ?? */
+ join_multi(LL_ALLROUTERS);
+ join_multi(SL_ALLROUTERS);
+#endif
+
+ /* set icmpv6 filter */
+ ICMP6_FILTER_SETBLOCKALL(&filt);
+ ICMP6_FILTER_SETPASS(ICMP6_ROUTER_RENUMBERING, &filt);
+ if (setsockopt(s6, IPPROTO_ICMPV6, ICMP6_FILTER, &filt,
+ sizeof(filt)) < 0) {
+ syslog(LOG_ERR, "<%s> IICMP6_FILTER: %s",
+ __func__, strerror(errno));
+ exit(1);
+ }
+
+ /* specify to tell receiving interface */
+ on = 1;
+ if (setsockopt(s6, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on,
+ sizeof(on)) < 0) {
+ syslog(LOG_ERR, "<%s> IPV6_RECVPKTINFO: %s",
+ __func__, strerror(errno));
+ exit(1);
+ }
+
+#ifdef IPSEC
+#ifdef IPSEC_POLICY_IPSEC
+ if (flags->policy) {
+ char *buf;
+ buf = ipsec_set_policy(policy, strlen(policy));
+ if (buf == NULL)
+ errx(1, "%s", ipsec_strerror());
+ /* XXX should handle in/out bound policy. */
+ if (setsockopt(s6, IPPROTO_IPV6, IPV6_IPSEC_POLICY,
+ buf, ipsec_get_policylen(buf)) < 0)
+ err(1, "setsockopt(IPV6_IPSEC_POLICY)");
+ free(buf);
+ }
+#else /* IPSEC_POLICY_IPSEC */
+ if (flags->auth) {
+ optval = IPSEC_LEVEL_REQUIRE;
+ if (setsockopt(s6, IPPROTO_IPV6, IPV6_AUTH_TRANS_LEVEL,
+ &optval, sizeof(optval)) == -1) {
+ syslog(LOG_ERR, "<%s> IPV6_AUTH_TRANS_LEVEL: %s",
+ __func__, strerror(errno));
+ exit(1);
+ }
+ }
+ if (flags->encrypt) {
+ optval = IPSEC_LEVEL_REQUIRE;
+ if (setsockopt(s6, IPPROTO_IPV6, IPV6_ESP_TRANS_LEVEL,
+ &optval, sizeof(optval)) == -1) {
+ syslog(LOG_ERR, "<%s> IPV6_ESP_TRANS_LEVEL: %s",
+ __func__, strerror(errno));
+ exit(1);
+ }
+ }
+#endif /* IPSEC_POLICY_IPSEC */
+#endif /* IPSEC */
+
+ return;
+}
+
+static void
+sock4_open(struct flags *flags
+#ifdef IPSEC_POLICY_IPSEC
+ , char *policy
+#endif /* IPSEC_POLICY_IPSEC */
+ )
+{
+#ifdef IPSEC
+#ifndef IPSEC_POLICY_IPSEC
+ int optval;
+#endif
+#endif
+
+ if (with_v4dest == 0)
+ return;
+ if ((s4 = socket(AF_INET, SOCK_RAW, IPPROTO_ICMPV6)) < 0) {
+ syslog(LOG_ERR, "<%s> socket(v4): %s", __func__,
+ strerror(errno));
+ exit(1);
+ }
+
+#if 0 /* XXX: not necessary ?? */
+ /*
+ * join all routers multicast addresses.
+ */
+ some_join_function();
+#endif
+
+#ifdef IPSEC
+#ifdef IPSEC_POLICY_IPSEC
+ if (flags->policy) {
+ char *buf;
+ buf = ipsec_set_policy(policy, strlen(policy));
+ if (buf == NULL)
+ errx(1, "%s", ipsec_strerror());
+ /* XXX should handle in/out bound policy. */
+ if (setsockopt(s4, IPPROTO_IP, IP_IPSEC_POLICY,
+ buf, ipsec_get_policylen(buf)) < 0)
+ err(1, "setsockopt(IP_IPSEC_POLICY)");
+ free(buf);
+ }
+#else /* IPSEC_POLICY_IPSEC */
+ if (flags->auth) {
+ optval = IPSEC_LEVEL_REQUIRE;
+ if (setsockopt(s4, IPPROTO_IP, IP_AUTH_TRANS_LEVEL,
+ &optval, sizeof(optval)) == -1) {
+ syslog(LOG_ERR, "<%s> IP_AUTH_TRANS_LEVEL: %s",
+ __func__, strerror(errno));
+ exit(1);
+ }
+ }
+ if (flags->encrypt) {
+ optval = IPSEC_LEVEL_REQUIRE;
+ if (setsockopt(s4, IPPROTO_IP, IP_ESP_TRANS_LEVEL,
+ &optval, sizeof(optval)) == -1) {
+ syslog(LOG_ERR, "<%s> IP_ESP_TRANS_LEVEL: %s",
+ __func__, strerror(errno));
+ exit(1);
+ }
+ }
+#endif /* IPSEC_POLICY_IPSEC */
+#endif /* IPSEC */
+
+ return;
+}
+
+static void
+rrenum_output(struct payload_list *pl, struct dst_list *dl)
+{
+ int i, msglen = 0;
+ struct cmsghdr *cm;
+ struct in6_pktinfo *pi;
+ struct sockaddr_in6 *sin6 = NULL;
+
+ sndmhdr.msg_name = (caddr_t)dl->dl_dst;
+ if (dl->dl_dst->sa_family == AF_INET6)
+ sin6 = (struct sockaddr_in6 *)dl->dl_dst;
+
+ if (sin6 != NULL &&
+ IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr)) {
+ int hoplimit = RR_MCHLIM_DEFAULT;
+
+ cm = CMSG_FIRSTHDR(&sndmhdr);
+ /* specify the outgoing interface */
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
+ pi = (struct in6_pktinfo *)CMSG_DATA(cm);
+ memset(&pi->ipi6_addr, 0, sizeof(pi->ipi6_addr)); /*XXX*/
+ pi->ipi6_ifindex = sin6->sin6_scope_id;
+ msglen += CMSG_LEN(sizeof(struct in6_pktinfo));
+
+ /* specify the hop limit of the packet if dest is link local */
+ /* not defined by router-renum-05.txt, but maybe its OK */
+ cm = CMSG_NXTHDR(&sndmhdr, cm);
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_HOPLIMIT;
+ cm->cmsg_len = CMSG_LEN(sizeof(int));
+ memcpy(CMSG_DATA(cm), &hoplimit, sizeof(int));
+ msglen += CMSG_LEN(sizeof(int));
+ }
+ sndmhdr.msg_controllen = msglen;
+ if (sndmhdr.msg_controllen == 0)
+ sndmhdr.msg_control = 0;
+
+ sndmhdr.msg_iov = &pl->pl_sndiov;
+ i = sendmsg(dl->dl_dst->sa_family == AF_INET ? s4 : s6, &sndmhdr, 0);
+
+ if (i < 0 || i != sndmhdr.msg_iov->iov_len)
+ syslog(LOG_ERR, "<%s> sendmsg: %s", __func__,
+ strerror(errno));
+}
+
+static void
+rrenum_snd_eachdst(struct payload_list *pl)
+{
+ struct dst_list *dl;
+
+ for (dl = dl_head; dl; dl = dl->dl_next) {
+ rrenum_output(pl, dl);
+ }
+}
+
+#if 0
+static void
+rrenum_snd_fullsequence()
+{
+ struct payload_list *pl;
+
+ for (pl = pl_head; pl; pl = pl->pl_next) {
+ rrenum_snd_eachdst(pl);
+ }
+}
+#endif
+
+static void
+rrenum_input(int s)
+{
+ int i;
+ struct icmp6_router_renum *rr;
+
+ /* get message */
+ if ((i = recvmsg(s, &rcvmhdr, 0)) < 0) {
+ syslog(LOG_ERR, "<%s> recvmsg: %s", __func__,
+ strerror(errno));
+ return;
+ }
+ if (s == s4)
+ i -= sizeof(struct ip);
+ if (i < sizeof(struct icmp6_router_renum)) {
+ syslog(LOG_ERR, "<%s> packet size(%d) is too short",
+ __func__, i);
+ return;
+ }
+ if (s == s4) {
+ struct ip *ip = (struct ip *)rcvmhdr.msg_iov->iov_base;
+
+ rr = (struct icmp6_router_renum *)(ip + 1);
+ } else /* s == s6 */
+ rr = (struct icmp6_router_renum *)rcvmhdr.msg_iov->iov_base;
+
+ switch(rr->rr_code) {
+ case ICMP6_ROUTER_RENUMBERING_COMMAND:
+ /* COMMAND will be processed by rtadvd */
+ break;
+ case ICMP6_ROUTER_RENUMBERING_RESULT:
+ /* TODO: receiving result message */
+ break;
+ default:
+ syslog(LOG_ERR, "<%s> received unknown code %d",
+ __func__, rr->rr_code);
+ break;
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp = stdin;
+ fd_set fdset;
+ struct timeval timeout;
+ int ch, i, maxfd = 0, send_counter = 0;
+ struct flags flags;
+ struct payload_list *pl;
+#ifdef IPSEC_POLICY_IPSEC
+ char *policy = NULL;
+#endif
+
+ memset(&flags, 0, sizeof(flags));
+ openlog("rrenumd", LOG_PID, LOG_DAEMON);
+
+ /* get options */
+ while ((ch = getopt(argc, argv, "c:sdf"
+#ifdef IPSEC
+#ifdef IPSEC_POLICY_IPSEC
+ "P:"
+#else /* IPSEC_POLICY_IPSEC */
+ "AE"
+#endif /* IPSEC_POLICY_IPSEC */
+#endif /* IPSEC */
+ )) != -1){
+ switch (ch) {
+ case 'c':
+ if((fp = fopen(optarg, "r")) == NULL) {
+ syslog(LOG_ERR,
+ "<%s> config file %s open failed",
+ __func__, optarg);
+ exit(1);
+ }
+ break;
+ case 's':
+ fp = stdin;
+ break;
+ case 'd':
+ flags.debug = 1;
+ break;
+ case 'f':
+ flags.fg = 1;
+ break;
+#ifdef IPSEC
+#ifdef IPSEC_POLICY_IPSEC
+ case 'P':
+ flags.policy = 1;
+ policy = strdup(optarg);
+ break;
+#else /* IPSEC_POLICY_IPSEC */
+ case 'A':
+ flags.auth = 1;
+ break;
+ case 'E':
+ flags.encrypt = 1;
+ break;
+#endif /* IPSEC_POLICY_IPSEC */
+#endif /*IPSEC*/
+ default:
+ show_usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* set log level */
+ if (flags.debug == 0)
+ (void)setlogmask(LOG_UPTO(LOG_ERR));
+ if (flags.debug == 1)
+ (void)setlogmask(LOG_UPTO(LOG_INFO));
+
+ /* init global variables */
+ init_globals();
+
+ config(&fp);
+
+ sock6_open(&flags
+#ifdef IPSEC_POLICY_IPSEC
+ , policy
+#endif /* IPSEC_POLICY_IPSEC */
+ );
+ sock4_open(&flags
+#ifdef IPSEC_POLICY_IPSEC
+ , policy
+#endif /* IPSEC_POLICY_IPSEC */
+ );
+
+ if (!flags.fg)
+ daemon(0, 0);
+
+ FD_ZERO(&fdset);
+ if (with_v6dest) {
+ FD_SET(s6, &fdset);
+ if (s6 > maxfd)
+ maxfd = s6;
+ }
+ if (with_v4dest) {
+ FD_SET(s4, &fdset);
+ if (s4 > maxfd)
+ maxfd = s4;
+ }
+
+ /* ADHOC: timeout each 30seconds */
+ memset(&timeout, 0, sizeof(timeout));
+
+ /* init temporary payload_list and send_counter*/
+ pl = pl_head;
+ send_counter = retry + 1;
+ while (1) {
+ struct fd_set select_fd = fdset; /* reinitialize */
+
+ if ((i = select(maxfd + 1, &select_fd, NULL, NULL,
+ &timeout)) < 0){
+ syslog(LOG_ERR, "<%s> select: %s",
+ __func__, strerror(errno));
+ continue;
+ }
+ if (i == 0) { /* timeout */
+ if (pl == NULL)
+ exit(0);
+ rrenum_snd_eachdst(pl);
+ send_counter--;
+ timeout.tv_sec = 30;
+ if (send_counter == 0) {
+ timeout.tv_sec = 0;
+ pl = pl->pl_next;
+ send_counter = retry + 1;
+ }
+ }
+ if (FD_ISSET(s4, &select_fd))
+ rrenum_input(s4);
+ if (FD_ISSET(s6, &select_fd))
+ rrenum_input(s6);
+ }
+}
diff --git a/usr.sbin/rrenumd/rrenumd.conf.5 b/usr.sbin/rrenumd/rrenumd.conf.5
new file mode 100644
index 0000000..9ce2306
--- /dev/null
+++ b/usr.sbin/rrenumd/rrenumd.conf.5
@@ -0,0 +1,369 @@
+.\" $KAME: rrenumd.conf.5,v 1.8 2001/02/06 02:17:23 jinmei 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 November 5, 1998
+.Dt RRENUMD.CONF 5
+.Os
+.Sh NAME
+.\"
+.Nm rrenumd.conf
+.Nd configuration file for router renumbering daemon
+.\"
+.Sh DESCRIPTION
+The rrenumd config file describes how the router renumbering packet
+must be constructed and to which destinations it should be sent.
+This file consists of a sequence of statements terminated by a semi-colon (`;').
+Statements are composed of tokens
+separated by white space, which can be any combination of blanks, tabs
+and newlines.
+This structure simplifies identification of
+the parts of the configuration associated with each other.
+Lines beginning with
+.Ql #
+are comments.
+.\"
+.Sh Meta Syntax
+Keywords and special characters that the parser expects exactly are
+displayed using the
+.Ic bold
+font.
+Parameters are specifying with
+.Ar underline .
+Parameters shown in
+square brackets (`[' and `]') are used to show optional
+keywords and parameters.
+The vertical bar (`|') is used to indicate
+between a choice of optional parameters.
+Curly braces (`{' and
+`}') are used to group keywords and parameters when necessary.
+.\"
+.Sh Interface specification
+There are some statements that may or have to specify interface.
+Interfaces are specified in the form of "name unit", such as
+.Ar lo0
+and
+.Ar ep1 .
+.\"
+.Sh Configuration Statements
+.Bl -tag -width Ds
+.\"
+.It Ic debug on|off ;
+Enables configuration file parser debugging.
+If
+.Ic on
+is specified,
+then debugging is enabled,
+If
+.Ic off
+is specified,
+then debugging is disabled.
+It is disabled by default.
+.\"
+.It Ic dest Ar dest-list Op Ar retrycmd ;
+Specifies destinations to which router renumbering messages should be
+sent.
+.Ar dest-list
+can be any combination of single or multiple numerical IPv6 addrs,
+or Full Qualified Domain Names.
+.Ar retrycmd
+has following syntax.
+.\"
+.Bl -tag -width Ds
+.It Ic retry Ar retry-num
+.Ar retry-num
+specifies how many router renumbering messages are sent repeatedly.
+.El
+.It Op Ic add|change|setglobal
+.Cm match-prefix Ar match-prefix-val
+.Bk -words
+.Op /match-prefix-len
+.Ek
+.Bk -words
+.Op Cm maxlen Ar maxlen-val
+.Ek
+.Bk -words
+.Op Cm minlen Ar minlen-val
+.Ek
+.Bk -words
+.Op Cm use-prefix Ar use-prefix-val
+.Ek
+.Bk -words
+.Op /use-prefix-len
+.Ek
+.Bk -words
+.Op Cm keeplen Ar keeplen-val
+.Ek
+.Bk -words
+.Op Ar use-prefix-values ;
+.Ek
+.Pp
+Specifies contents of sending router renumbering message with seqnum 0.
+If
+.Cm add|change|setglobal
+is not specified, then
+.Cm add
+is assumed.
+.Ar use-prefix-values
+has following syntax.
+.Pp
+{
+.Op Cm vltime Ar vltime-val
+.Bk -words
+.Op Cm pltime Ar pltime-val
+.Ek
+.Bk -words
+.Op Cm raf_onlink Cm on|off
+.Ek
+.Bk -words
+.Op Cm raf_auto Cm on|off
+.Ek
+.Bk -words
+.Op Cm rrf_decrprefd Cm on|off
+.Ek
+.Bk -words
+.Op Cm rrf_decrvalid Cm on|off
+.Ek
+}
+.Pp
+Each value has following meaning.
+.Pp
+.Bl -tag -width Ds -compact
+.It Cm match-prefix Ar match-prefix-val Op /match-prefix-len
+Specify
+.Ar match-prefix-val
+that is used for matching with preassigned prefixes to which
+.Cm add|change|setglobal
+command should be applied.
+.Ar /match-prefix-len
+Specify the starting part of
+.Ar match-prefix-val
+to be used for matching with preassigned prefixes, as decimal bit number.
+.It Cm maxlen Ar maxlen-val
+Specify the maximum length of prefixes which is allowed to be
+matched to
+.Ar match-prefix-val ,
+as decimal bit number.
+.It Cm minlen Ar minlen-val
+Specify the minimum length of prefixes which is allowed to be matched to
+.Ar match-prefix-val ,
+as decimal bit number.
+.It Cm use-prefix Ar use-prefix-val Op /usr-prefix-len
+Specify
+.Ar use-prefix-val
+that is used for prefixes to be added on
+.Cm add|change|setglobal
+command.
+.Ar /use-prefix-len
+Specify the starting part of
+.Ar use-prefix-val
+copied to the starting part of prefixes to be added on
+.Cm add|change|setglobal
+command, as decimal bit number.
+.It Cm keeplen Ar keeplen-val
+Specify the medium part of
+.Ar use-prefix-val
+just next to the starting part specified by
+.Ar use-prefix-len ,
+as decimal bit number.
+Contiguous bits part in the same bit position of an existent prefix
+matched with
+.Ar match-prefix-val
+is copied to the same bit position of prefixes to be added.
+.It Cm vltime Ar vmtime-val
+Assign an
+.Ar time
+as prefix valid life time for a prefix to be added.
+Valid value for
+.Ar time
+is decimal seconds number or special format as "d00h00m00s00",
+where 00 can take any decimal number, and "d" means days, "h" means hours,
+"m" means minutes, "s" means seconds.
+And alternatively, special keyword
+"infinity" can be also be specified.
+.It Cm pltime Ar pltime-val
+Assign an
+.Ar time
+as prefix preferred life time for a prefix to be added.
+Valid value for
+.Ar time
+is same as for
+.Ar vltime-val .
+.It Cm raf_onlink Cm on|off
+Let the prefix to be added to have on-link or off-link nature
+for the assigned interface.
+If
+.Cm on
+is specified, the prefix have on-link nature
+(e.g.\& the prefix
+belong to the link).
+If
+.Cm off
+is specified, the prefix have off-link nature
+(e.g.\& the
+prefix does not belong to the link).
+.It Cm raf_auto Cm on|off
+Enable or disable the autonomous address auto configuration
+for the prefix to be added.
+If
+.Cm on
+is specified, autonomous address auto configuration is
+enabled.
+If
+.Cm off
+is specified, it is disabled.
+.It Cm rrf_decrprefd Cm on|off
+Enable or disable the decrementation of the pltime.
+If
+.Cm on
+is specified, decrementation of the pltime is enabled.
+If
+.Cm off
+is specified, decrementation of the pltime is disabled.
+.It Cm rrf_decrvalid Cm on|off
+Enable or disable the decrementation of the vltime.
+If
+.Cm on
+is specified, decrementation of the vltime is enabled.
+If
+.Cm off
+is specified, decrementation of the vltime is disabled.
+.El
+.\"
+.It seqnum Ar seqnum-val { Ar rrenum-cmd } ;
+Specifies contents of sending router renumbering message with some
+specific seqnum.
+Multiple of this statement can be specified if they
+have different
+.Ar seqnum-val
+each other.
+.Ar rrenum-cmd
+has just same syntax with above add|change|setglobal statement.
+.El
+.\"
+.Sh EXAMPLES
+For each configuration file example shown below, we suppose
+every IPv6 subnet has its own prefix beginning with
+fec0:0:0::/48 and with its own subnet number
+(in this case,
+subnet number is 7th and 8th octet value of the prefix).
+.Pp
+If you want to assign prefixes beginning with 3ffe:501:ffff::/48
+to each subnet, then following configuration will be enough,
+if each of your routers supports IPv6 multicast forwarding.
+The subnet number of the existing fec0:0:0::/48 prefix and the
+newly assigned 3ffe:501:ffff::/48 prefix will be same.
+.\"
+.Bd -literal -offset indent
+dest ff05::2;
+
+add match-prefix fec0:0:0:: /48 use-prefix 3ffe:501:ffff:: /48 keeplen 16;
+.Ed
+.Pp
+.\"
+If your routers do not support IPv6 multicast forwarding,
+you will need to specify each destination at
+.Cm dest
+command.
+.\"
+.Bd -literal -offset indent
+dest fec0:0:0:1:260:8ff:fe24:fb3a fec0:0:0:2:200:eff:fe2e:dfe1 fec0:0:0:3:5254:ff:fedc:5217;
+
+add match-prefix fec0:0:0:: /48 use-prefix 3ffe:501:ffff:: /48 keeplen 16;
+.Ed
+.Pp
+.\"
+If you are going to do renumbering, then following procedure will be natural.
+.Bl -enum -offset indent
+.It
+Assign a new prefix.
+.It
+Set old prefix lifetimes to some appropriate transition
+period.
+In the following example we use 1 week for valid
+lifetime, and 0 for preferred lifetime.
+Also, enable old prefix lifetime expiration
+(By default, it is static and does not expire).
+.It
+After the transition period, old prefixes should become
+invalid, and may have been deleted.
+To make sure that they are deleted, send new router
+renumbering message, which specifies old prefixes as match
+prefix, and no use prefix.
+.El
+.Pp
+.\"
+The following configuration file will do 1 and 2.
+.\"
+.Bd -literal -offset indent
+dest ff05::2;
+
+seqnum 0 {
+ add match-prefix fec0:0:0:: /48 use-prefix 3ffe:501:fffe:: /48 keeplen 16;
+ };
+
+seqnum 1 {
+ change match-prefix 3ffe:501:ffff:: /48 use-prefix 3ffe:501:ffff:: /48 keeplen 16 vltime d7 pltime 0 rrf_decrvalid on rrf_decrprefd on;
+ };
+.Ed
+.Pp
+.\"
+And the following configuration file will do 3
+(should be
+used for the router renumbering message to be sent 1 week
+afterward).
+.\"
+.Bd -literal -offset indent
+dest ff05::2;
+
+change match-prefix 3ffe:501:ffff:: /48;
+.Ed
+.Pp
+.\"
+In the above example, only
+.Cm add
+and
+.Cm change
+commands are used, and there is no example for
+.Cm setglobal
+command.
+.Cm setglobal
+command is almost same with
+.Cm change
+command except that it deletes all pre-defined IPv6 global address.
+.Sh SEE ALSO
+.Xr prefix 8 ,
+.Xr rrenumd 8
+.Sh HISTORY
+The
+.Nm
+configuration file was first appeared in KAME IPv6 protocol stack kit.
+.\" .Sh BUGS
+.\" (to be written)
diff --git a/usr.sbin/rrenumd/rrenumd.h b/usr.sbin/rrenumd/rrenumd.h
new file mode 100644
index 0000000..df0280b
--- /dev/null
+++ b/usr.sbin/rrenumd/rrenumd.h
@@ -0,0 +1,60 @@
+/* $KAME: rrenumd.h,v 1.2 2000/07/03 02:54:09 itojun Exp $ */
+
+/*
+ * Copyright (C) 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by WIDE Project and
+ * its contributors.
+ * 4. 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$
+ */
+
+struct dst_list {
+ struct dst_list * dl_next;
+ struct sockaddr * dl_dst;
+};
+
+extern struct dst_list *dl_head;
+
+struct payload_list {
+ struct payload_list * pl_next;
+ struct iovec pl_sndiov;
+ struct icmp6_router_renum
+ pl_irr;
+ struct rr_pco_match pl_rpm;
+ /* currently, support only 1 rr_pco_use field per packet */
+ struct rr_pco_use pl_rpu;
+};
+
+extern struct payload_list *pl_head;
+extern u_int retry;
+extern int with_v4dest, with_v6dest;
+
+#define DEF_VLTIME 2592000
+#define DEF_PLTIME 604800
diff --git a/usr.sbin/rtadvctl/Makefile b/usr.sbin/rtadvctl/Makefile
new file mode 100644
index 0000000..a66db84c4
--- /dev/null
+++ b/usr.sbin/rtadvctl/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+#
+.PATH: ${.CURDIR}/../rtadvd
+
+PROG= rtadvctl
+MAN= rtadvctl.8
+
+SRCS= rtadvctl.c control.c control_client.c if.c timer_subr.c
+
+CFLAGS+= -I${.CURDIR} -I${.CURDIR}/../rtadvd
+WARNS?= 1
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/rtadvctl/Makefile.depend b/usr.sbin/rtadvctl/Makefile.depend
new file mode 100644
index 0000000..54c1f6f
--- /dev/null
+++ b/usr.sbin/rtadvctl/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/rtadvctl/rtadvctl.8 b/usr.sbin/rtadvctl/rtadvctl.8
new file mode 100644
index 0000000..4b7c888
--- /dev/null
+++ b/usr.sbin/rtadvctl/rtadvctl.8
@@ -0,0 +1,105 @@
+.\" Copyright (C) 2011 Hiroki Sato <hrs@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 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 16, 2011
+.Dt RTADVCTL 8
+.Os
+.Sh NAME
+.Nm rtadvctl
+.Nd control program for
+.Xr rtadvd 8
+daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl v
+.Ar subcommand
+.Op Ar interface ...
+.Sh DESCRIPTION
+.Nm
+is a utility that communicates with
+.Xr rtadvd 8
+daemon and displays information about Router Advertisement messages being
+sent on each interface.
+.Pp
+This utility provides several options and subcommands.
+The options are as follows:
+.Bl -tag -width indent
+.\"
+.It Fl v
+Increase verbosity level.
+When specified once, the
+.Nm
+utility shows additional information about prefixes, RDNSS, and DNSSL
+options.
+When given twice, it additionally shows information about
+inactive interfaces and some statistics.
+.El
+.Pp
+The subcommands are as follows:
+.Bl -tag -width indent
+.\"
+.It reload Op interfaces...
+Specifies to reload the configuration file. If one or more
+.Ar interface
+is specified, configuration entries for the interfaces will be reloaded
+selectively.
+.It enable interfaces...
+Specifies to mark the interface as enable and to try to reload the
+configuration entry.
+This subcommand is useful for dynamically-added interfaces.
+.Pp
+The
+.Xr rtadvd 8
+daemon marks an interface as enable if the interface exists and the
+configuration file has a valid entry for that when it is invoked.
+.It disable interfaces...
+Specifies to mark the interface as disable.
+.It shutdown
+Makes the
+.Xr rtadvd 8
+daemon shut down.
+Note that the
+.Xr rtadvd 8
+daemon will send several RAs with zero lifetime to invalidate the old
+information on each interface.
+It will take at most nine seconds.
+.It show Op interfaces...
+Displays information on Router Advertisement messages being sent
+on each interface.
+.El
+.Sh SEE ALSO
+.Xr rtadvd.conf 5 ,
+.Xr rtadvd 8
+.Sh HISTORY
+The
+.Nm
+command first appeared in
+.Fx 9.0 .
+.Sh AUTHORS
+.Nm
+was written by
+.An Hiroki Sato Aq Mt hrs@FreeBSD.org .
diff --git a/usr.sbin/rtadvctl/rtadvctl.c b/usr.sbin/rtadvctl/rtadvctl.c
new file mode 100644
index 0000000..f1657d7
--- /dev/null
+++ b/usr.sbin/rtadvctl/rtadvctl.c
@@ -0,0 +1,938 @@
+/*-
+ * Copyright (C) 2011 Hiroki Sato <hrs@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 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/queue.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <sys/uio.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/ethernet.h>
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+#include <netinet6/in6_var.h>
+#include <netinet6/nd6.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <syslog.h>
+#include <time.h>
+#include <err.h>
+
+#include "pathnames.h"
+#include "rtadvd.h"
+#include "if.h"
+#include "timer_subr.h"
+#include "timer.h"
+#include "control.h"
+#include "control_client.h"
+
+#define RA_IFSTATUS_INACTIVE 0
+#define RA_IFSTATUS_RA_RECV 1
+#define RA_IFSTATUS_RA_SEND 2
+
+static int vflag = LOG_ERR;
+
+static void usage(void);
+
+static int action_propset(char *);
+static int action_propget(char *, struct ctrl_msg_pl *);
+static int action_plgeneric(int, char *, char *);
+
+static int action_enable(int, char **);
+static int action_disable(int, char **);
+static int action_reload(int, char **);
+static int action_echo(int, char **);
+static int action_version(int, char **);
+static int action_shutdown(int, char **);
+
+static int action_show(int, char **);
+static int action_show_prefix(struct prefix *);
+static int action_show_rtinfo(struct rtinfo *);
+static int action_show_rdnss(void *);
+static int action_show_dnssl(void *);
+
+static int csock_client_open(struct sockinfo *);
+static size_t dname_labeldec(char *, size_t, const char *);
+static void mysyslog(int, const char *, ...);
+
+static const char *rtpref_str[] = {
+ "medium", /* 00 */
+ "high", /* 01 */
+ "rsv", /* 10 */
+ "low" /* 11 */
+};
+
+static struct dispatch_table {
+ const char *dt_comm;
+ int (*dt_act)(int, char **);
+} dtable[] = {
+ { "show", action_show },
+ { "reload", action_reload },
+ { "shutdown", action_shutdown },
+ { "enable", action_enable },
+ { "disable", action_disable },
+ { NULL, NULL },
+ { "echo", action_echo },
+ { "version", action_version },
+ { NULL, NULL },
+};
+
+static char errmsgbuf[1024];
+static char *errmsg = NULL;
+
+static void
+mysyslog(int priority, const char * restrict fmt, ...)
+{
+ va_list ap;
+
+ if (vflag >= priority) {
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ va_end(ap);
+ }
+}
+
+static void
+usage(void)
+{
+ int i;
+
+ for (i = 0; (size_t)i < sizeof(dtable)/sizeof(dtable[0]); i++) {
+ if (dtable[i].dt_comm == NULL)
+ break;
+ printf("%s\n", dtable[i].dt_comm);
+ }
+
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int i;
+ int ch;
+ int (*action)(int, char **) = NULL;
+ int error;
+
+ while ((ch = getopt(argc, argv, "Dv")) != -1) {
+ switch (ch) {
+ case 'D':
+ vflag = LOG_DEBUG;
+ break;
+ case 'v':
+ vflag++;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0)
+ usage();
+
+ for (i = 0; (size_t)i < sizeof(dtable)/sizeof(dtable[0]); i++) {
+ if (dtable[i].dt_comm == NULL ||
+ strcmp(dtable[i].dt_comm, argv[0]) == 0) {
+ action = dtable[i].dt_act;
+ break;
+ }
+ }
+
+ if (action == NULL)
+ usage();
+
+ error = (dtable[i].dt_act)(--argc, ++argv);
+ if (error) {
+ fprintf(stderr, "%s failed", dtable[i].dt_comm);
+ if (errmsg != NULL)
+ fprintf(stderr, ": %s", errmsg);
+ fprintf(stderr, ".\n");
+ }
+
+ return (error);
+}
+
+static int
+csock_client_open(struct sockinfo *s)
+{
+ struct sockaddr_un sun;
+
+ if ((s->si_fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
+ err(1, "cannot open control socket.");
+
+ memset(&sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ sun.sun_len = sizeof(sun);
+ strlcpy(sun.sun_path, s->si_name, sizeof(sun.sun_path));
+
+ if (connect(s->si_fd, (struct sockaddr *)&sun, sizeof(sun)) == -1)
+ err(1, "connect: %s", s->si_name);
+
+ mysyslog(LOG_DEBUG,
+ "<%s> connected to %s", __func__, sun.sun_path);
+
+ return (0);
+}
+
+static int
+action_plgeneric(int action, char *plstr, char *buf)
+{
+ struct ctrl_msg_hdr *cm;
+ struct ctrl_msg_pl cp;
+ struct sockinfo *s;
+ char *msg;
+ char *p;
+ char *q;
+
+ s = &ctrlsock;
+ csock_client_open(s);
+
+ cm = (struct ctrl_msg_hdr *)buf;
+ msg = (char *)buf + sizeof(*cm);
+
+ cm->cm_version = CM_VERSION;
+ cm->cm_type = action;
+ cm->cm_len = sizeof(*cm);
+
+ if (plstr != NULL) {
+ memset(&cp, 0, sizeof(cp));
+ p = strchr(plstr, ':');
+ q = strchr(plstr, '=');
+ if (p != NULL && q != NULL && p > q)
+ return (1);
+
+ if (p == NULL) { /* No : */
+ cp.cp_ifname = NULL;
+ cp.cp_key = plstr;
+ } else if (p == plstr) { /* empty */
+ cp.cp_ifname = NULL;
+ cp.cp_key = plstr + 1;
+ } else {
+ *p++ = '\0';
+ cp.cp_ifname = plstr;
+ cp.cp_key = p;
+ }
+ if (q == NULL)
+ cp.cp_val = NULL;
+ else {
+ *q++ = '\0';
+ cp.cp_val = q;
+ }
+ cm->cm_len += cm_pl2bin(msg, &cp);
+
+ mysyslog(LOG_DEBUG, "<%s> key=%s, val_len=%d, ifname=%s",
+ __func__,cp.cp_key, cp.cp_val_len, cp.cp_ifname);
+ }
+
+ return (cm_handler_client(s->si_fd, CM_STATE_MSG_DISPATCH, buf));
+}
+
+static int
+action_propget(char *argv, struct ctrl_msg_pl *cp)
+{
+ int error;
+ struct ctrl_msg_hdr *cm;
+ char buf[CM_MSG_MAXLEN];
+ char *msg;
+
+ memset(cp, 0, sizeof(*cp));
+ cm = (struct ctrl_msg_hdr *)buf;
+ msg = (char *)buf + sizeof(*cm);
+
+ error = action_plgeneric(CM_TYPE_REQ_GET_PROP, argv, buf);
+ if (error || cm->cm_len <= sizeof(*cm))
+ return (1);
+
+ cm_bin2pl(msg, cp);
+ mysyslog(LOG_DEBUG, "<%s> type=%d, len=%d",
+ __func__, cm->cm_type, cm->cm_len);
+ mysyslog(LOG_DEBUG, "<%s> key=%s, val_len=%d, ifname=%s",
+ __func__,cp->cp_key, cp->cp_val_len, cp->cp_ifname);
+
+ return (0);
+}
+
+static int
+action_propset(char *argv)
+{
+ char buf[CM_MSG_MAXLEN];
+
+ return (action_plgeneric(CM_TYPE_REQ_SET_PROP, argv, buf));
+}
+
+static int
+action_disable(int argc, char **argv)
+{
+ char *action_argv;
+ char argv_disable[IFNAMSIZ + sizeof(":disable=")];
+ int i;
+ int error;
+
+ if (argc < 1)
+ return (1);
+
+ error = 0;
+ for (i = 0; i < argc; i++) {
+ sprintf(argv_disable, "%s:disable=", argv[i]);
+ action_argv = argv_disable;
+ error += action_propset(action_argv);
+ }
+
+ return (error);
+}
+
+static int
+action_enable(int argc, char **argv)
+{
+ char *action_argv;
+ char argv_enable[IFNAMSIZ + sizeof(":enable=")];
+ int i;
+ int error;
+
+ if (argc < 1)
+ return (1);
+
+ error = 0;
+ for (i = 0; i < argc; i++) {
+ sprintf(argv_enable, "%s:enable=", argv[i]);
+ action_argv = argv_enable;
+ error += action_propset(action_argv);
+ }
+
+ return (error);
+}
+
+static int
+action_reload(int argc, char **argv)
+{
+ char *action_argv;
+ char argv_reload[IFNAMSIZ + sizeof(":reload=")];
+ int i;
+ int error;
+
+ if (argc == 0) {
+ action_argv = strdup(":reload=");
+ return (action_propset(action_argv));
+ }
+
+ error = 0;
+ for (i = 0; i < argc; i++) {
+ sprintf(argv_reload, "%s:reload=", argv[i]);
+ action_argv = argv_reload;
+ error += action_propset(action_argv);
+ }
+
+ return (error);
+}
+
+static int
+action_echo(int argc __unused, char **argv __unused)
+{
+ char *action_argv;
+
+ action_argv = strdup("echo");
+ return (action_propset(action_argv));
+}
+
+static int
+action_shutdown(int argc __unused, char **argv __unused)
+{
+ char *action_argv;
+
+ action_argv = strdup("shutdown");
+ return (action_propset(action_argv));
+}
+
+/* XXX */
+static int
+action_version(int argc __unused, char **argv __unused)
+{
+ char *action_argv;
+ struct ctrl_msg_pl cp;
+ int error;
+
+ action_argv = strdup(":version=");
+ error = action_propget(action_argv, &cp);
+ if (error)
+ return (error);
+
+ printf("version=%s\n", cp.cp_val);
+ return (0);
+}
+
+static int
+action_show(int argc, char **argv)
+{
+ char *action_argv;
+ char argv_ifilist[sizeof(":ifilist=")] = ":ifilist=";
+ char argv_ifi[IFNAMSIZ + sizeof(":ifi=")];
+ char argv_rai[IFNAMSIZ + sizeof(":rai=")];
+ char argv_rti[IFNAMSIZ + sizeof(":rti=")];
+ char argv_pfx[IFNAMSIZ + sizeof(":pfx=")];
+ char argv_ifi_ra_timer[IFNAMSIZ + sizeof(":ifi_ra_timer=")];
+ char argv_rdnss[IFNAMSIZ + sizeof(":rdnss=")];
+ char argv_dnssl[IFNAMSIZ + sizeof(":dnssl=")];
+ char ssbuf[SSBUFLEN];
+
+ struct timespec now, ts0, ts;
+ struct ctrl_msg_pl cp;
+ struct ifinfo *ifi;
+ TAILQ_HEAD(, ifinfo) ifl = TAILQ_HEAD_INITIALIZER(ifl);
+ char *endp;
+ char *p;
+ int error;
+ int i;
+ int len;
+
+ if (argc == 0) {
+ action_argv = argv_ifilist;
+ error = action_propget(action_argv, &cp);
+ if (error)
+ return (error);
+
+ p = cp.cp_val;
+ endp = p + cp.cp_val_len;
+ while (p < endp) {
+ ifi = malloc(sizeof(*ifi));
+ if (ifi == NULL)
+ return (1);
+ memset(ifi, 0, sizeof(*ifi));
+
+ strcpy(ifi->ifi_ifname, p);
+ ifi->ifi_ifindex = if_nametoindex(ifi->ifi_ifname);
+ TAILQ_INSERT_TAIL(&ifl, ifi, ifi_next);
+ p += strlen(ifi->ifi_ifname) + 1;
+ }
+ } else {
+ for (i = 0; i < argc; i++) {
+ ifi = malloc(sizeof(*ifi));
+ if (ifi == NULL)
+ return (1);
+ memset(ifi, 0, sizeof(*ifi));
+
+ strcpy(ifi->ifi_ifname, argv[i]);
+ ifi->ifi_ifindex = if_nametoindex(ifi->ifi_ifname);
+ if (ifi->ifi_ifindex == 0) {
+ sprintf(errmsgbuf, "invalid interface %s",
+ ifi->ifi_ifname);
+ errmsg = errmsgbuf;
+ return (1);
+ }
+
+ TAILQ_INSERT_TAIL(&ifl, ifi, ifi_next);
+ }
+ }
+
+ clock_gettime(CLOCK_REALTIME_FAST, &now);
+ clock_gettime(CLOCK_MONOTONIC_FAST, &ts);
+ TS_SUB(&now, &ts, &ts0);
+
+ TAILQ_FOREACH(ifi, &ifl, ifi_next) {
+ struct ifinfo *ifi_s;
+ struct rtadvd_timer *rat;
+ struct rainfo *rai;
+ struct rtinfo *rti;
+ struct prefix *pfx;
+ int c;
+ int ra_ifstatus;
+
+ sprintf(argv_ifi, "%s:ifi=", ifi->ifi_ifname);
+ action_argv = argv_ifi;
+ error = action_propget(action_argv, &cp);
+ if (error)
+ return (error);
+ ifi_s = (struct ifinfo *)cp.cp_val;
+
+ if (!(ifi_s->ifi_persist) && vflag < LOG_NOTICE)
+ continue;
+
+ printf("%s: flags=<", ifi->ifi_ifname);
+
+ c = 0;
+ if (ifi_s->ifi_ifindex == 0)
+ c += printf("NONEXISTENT");
+ else
+ c += printf("%s", (ifi_s->ifi_flags & IFF_UP) ?
+ "UP" : "DOWN");
+ switch (ifi_s->ifi_state) {
+ case IFI_STATE_CONFIGURED:
+ c += printf("%s%s", (c) ? "," : "", "CONFIGURED");
+ break;
+ case IFI_STATE_TRANSITIVE:
+ c += printf("%s%s", (c) ? "," : "", "TRANSITIVE");
+ break;
+ }
+ if (ifi_s->ifi_persist)
+ c += printf("%s%s", (c) ? "," : "", "PERSIST");
+ printf(">");
+
+ ra_ifstatus = RA_IFSTATUS_INACTIVE;
+ if ((ifi_s->ifi_flags & IFF_UP) &&
+ ((ifi_s->ifi_state == IFI_STATE_CONFIGURED) ||
+ (ifi_s->ifi_state == IFI_STATE_TRANSITIVE))) {
+#if (__FreeBSD_version < 900000)
+ /*
+ * RA_RECV: !ip6.forwarding && ip6.accept_rtadv
+ * RA_SEND: ip6.forwarding
+ */
+ if (getinet6sysctl(IPV6CTL_FORWARDING) == 0) {
+ if (getinet6sysctl(IPV6CTL_ACCEPT_RTADV))
+ ra_ifstatus = RA_IFSTATUS_RA_RECV;
+ else
+ ra_ifstatus = RA_IFSTATUS_INACTIVE;
+ } else
+ ra_ifstatus = RA_IFSTATUS_RA_SEND;
+#else
+ /*
+ * RA_RECV: ND6_IFF_ACCEPT_RTADV
+ * RA_SEND: ip6.forwarding
+ */
+ if (ifi_s->ifi_nd_flags & ND6_IFF_ACCEPT_RTADV)
+ ra_ifstatus = RA_IFSTATUS_RA_RECV;
+ else if (getinet6sysctl(IPV6CTL_FORWARDING))
+ ra_ifstatus = RA_IFSTATUS_RA_SEND;
+ else
+ ra_ifstatus = RA_IFSTATUS_INACTIVE;
+#endif
+ }
+
+ c = 0;
+ printf(" status=<");
+ if (ra_ifstatus == RA_IFSTATUS_INACTIVE)
+ printf("%s%s", (c) ? "," : "", "INACTIVE");
+ else if (ra_ifstatus == RA_IFSTATUS_RA_RECV)
+ printf("%s%s", (c) ? "," : "", "RA_RECV");
+ else if (ra_ifstatus == RA_IFSTATUS_RA_SEND)
+ printf("%s%s", (c) ? "," : "", "RA_SEND");
+ printf("> ");
+
+ switch (ifi_s->ifi_state) {
+ case IFI_STATE_CONFIGURED:
+ case IFI_STATE_TRANSITIVE:
+ break;
+ default:
+ printf("\n");
+ continue;
+ }
+
+ printf("mtu %d\n", ifi_s->ifi_phymtu);
+
+ sprintf(argv_rai, "%s:rai=", ifi->ifi_ifname);
+ action_argv = argv_rai;
+
+ error = action_propget(action_argv, &cp);
+ if (error)
+ continue;
+
+ rai = (struct rainfo *)cp.cp_val;
+
+ printf("\tDefaultLifetime: %s",
+ sec2str(rai->rai_lifetime, ssbuf));
+ if (ra_ifstatus != RA_IFSTATUS_RA_SEND &&
+ rai->rai_lifetime == 0)
+ printf(" (RAs will be sent with zero lifetime)");
+
+ printf("\n");
+
+ printf("\tMinAdvInterval/MaxAdvInterval: ");
+ printf("%s/", sec2str(rai->rai_mininterval, ssbuf));
+ printf("%s\n", sec2str(rai->rai_maxinterval, ssbuf));
+ if (rai->rai_linkmtu)
+ printf("\tAdvLinkMTU: %d", rai->rai_linkmtu);
+ else
+ printf("\tAdvLinkMTU: <none>");
+
+ printf(", ");
+
+ printf("Flags: ");
+ if (rai->rai_managedflg || rai->rai_otherflg) {
+ printf("%s", rai->rai_managedflg ? "M" : "");
+ printf("%s", rai->rai_otherflg ? "O" : "");
+ } else
+ printf("<none>");
+
+ printf(", ");
+
+ printf("Preference: %s\n",
+ rtpref_str[(rai->rai_rtpref >> 3) & 0xff]);
+
+ printf("\tReachableTime: %s, ",
+ sec2str(rai->rai_reachabletime, ssbuf));
+ printf("RetransTimer: %s, "
+ "CurHopLimit: %d\n",
+ sec2str(rai->rai_retranstimer, ssbuf),
+ rai->rai_hoplimit);
+ printf("\tAdvIfPrefixes: %s\n",
+ rai->rai_advifprefix ? "yes" : "no");
+
+ /* RA timer */
+ rat = NULL;
+ if (ifi_s->ifi_ra_timer != NULL) {
+ sprintf(argv_ifi_ra_timer, "%s:ifi_ra_timer=",
+ ifi->ifi_ifname);
+ action_argv = argv_ifi_ra_timer;
+
+ error = action_propget(action_argv, &cp);
+ if (error)
+ return (error);
+
+ rat = (struct rtadvd_timer *)cp.cp_val;
+ }
+ printf("\tNext RA send: ");
+ if (rat == NULL)
+ printf("never\n");
+ else {
+ ts.tv_sec = rat->rat_tm.tv_sec + ts0.tv_sec;
+ printf("%s", ctime(&ts.tv_sec));
+ }
+ printf("\tLast RA send: ");
+ if (ifi_s->ifi_ra_lastsent.tv_sec == 0)
+ printf("never\n");
+ else {
+ ts.tv_sec = ifi_s->ifi_ra_lastsent.tv_sec + ts0.tv_sec;
+ printf("%s", ctime(&ts.tv_sec));
+ }
+ if (rai->rai_clockskew)
+ printf("\tClock skew: %" PRIu16 "sec\n",
+ rai->rai_clockskew);
+
+ if (vflag < LOG_WARNING)
+ continue;
+
+ /* route information */
+ sprintf(argv_rti, "%s:rti=", ifi->ifi_ifname);
+ action_argv = argv_rti;
+ error = action_propget(action_argv, &cp);
+ if (error)
+ return (error);
+
+ rti = (struct rtinfo *)cp.cp_val;
+ len = cp.cp_val_len / sizeof(*rti);
+ if (len > 0) {
+ printf("\tRoute Info:\n");
+
+ for (i = 0; i < len; i++)
+ action_show_rtinfo(&rti[i]);
+ }
+
+ /* prefix information */
+ sprintf(argv_pfx, "%s:pfx=", ifi->ifi_ifname);
+ action_argv = argv_pfx;
+
+ error = action_propget(action_argv, &cp);
+ if (error)
+ continue;
+
+ pfx = (struct prefix *)cp.cp_val;
+ len = cp.cp_val_len / sizeof(*pfx);
+
+ if (len > 0) {
+ printf("\tPrefixes (%d):\n", len);
+
+ for (i = 0; i < len; i++)
+ action_show_prefix(&pfx[i]);
+ }
+
+ /* RDNSS information */
+ sprintf(argv_rdnss, "%s:rdnss=", ifi->ifi_ifname);
+ action_argv = argv_rdnss;
+
+ error = action_propget(action_argv, &cp);
+ if (error)
+ continue;
+
+ len = *((uint16_t *)cp.cp_val);
+
+ if (len > 0) {
+ printf("\tRDNSS entries:\n");
+ action_show_rdnss(cp.cp_val);
+ }
+
+ /* DNSSL information */
+ sprintf(argv_dnssl, "%s:dnssl=", ifi->ifi_ifname);
+ action_argv = argv_dnssl;
+
+ error = action_propget(action_argv, &cp);
+ if (error)
+ continue;
+
+ len = *((uint16_t *)cp.cp_val);
+
+ if (len > 0) {
+ printf("\tDNSSL entries:\n");
+ action_show_dnssl(cp.cp_val);
+ }
+
+ if (vflag < LOG_NOTICE)
+ continue;
+
+ printf("\n");
+
+ printf("\tCounters\n"
+ "\t RA burst counts: %" PRIu16 " (interval: %s)\n"
+ "\t RS wait counts: %" PRIu16 "\n",
+ ifi_s->ifi_burstcount,
+ sec2str(ifi_s->ifi_burstinterval, ssbuf),
+ ifi_s->ifi_rs_waitcount);
+
+ printf("\tOutputs\n"
+ "\t RA: %" PRIu64 "\n", ifi_s->ifi_raoutput);
+
+ printf("\tInputs\n"
+ "\t RA: %" PRIu64 " (normal)\n"
+ "\t RA: %" PRIu64 " (inconsistent)\n"
+ "\t RS: %" PRIu64 "\n",
+ ifi_s->ifi_rainput,
+ ifi_s->ifi_rainconsistent,
+ ifi_s->ifi_rsinput);
+
+ printf("\n");
+
+#if 0 /* Not implemented yet */
+ printf("\tReceived RAs:\n");
+#endif
+ }
+
+ return (0);
+}
+
+static int
+action_show_rtinfo(struct rtinfo *rti)
+{
+ char ntopbuf[INET6_ADDRSTRLEN];
+ char ssbuf[SSBUFLEN];
+
+ printf("\t %s/%d (pref: %s, ltime: %s)\n",
+ inet_ntop(AF_INET6, &rti->rti_prefix,
+ ntopbuf, sizeof(ntopbuf)),
+ rti->rti_prefixlen,
+ rtpref_str[0xff & (rti->rti_rtpref >> 3)],
+ (rti->rti_ltime == ND6_INFINITE_LIFETIME) ?
+ "infinity" : sec2str(rti->rti_ltime, ssbuf));
+
+ return (0);
+}
+
+static int
+action_show_prefix(struct prefix *pfx)
+{
+ char ntopbuf[INET6_ADDRSTRLEN];
+ char ssbuf[SSBUFLEN];
+ struct timespec now;
+
+ clock_gettime(CLOCK_MONOTONIC_FAST, &now);
+ printf("\t %s/%d", inet_ntop(AF_INET6, &pfx->pfx_prefix,
+ ntopbuf, sizeof(ntopbuf)), pfx->pfx_prefixlen);
+
+ printf(" (");
+ switch (pfx->pfx_origin) {
+ case PREFIX_FROM_KERNEL:
+ printf("KERNEL");
+ break;
+ case PREFIX_FROM_CONFIG:
+ printf("CONFIG");
+ break;
+ case PREFIX_FROM_DYNAMIC:
+ printf("DYNAMIC");
+ break;
+ }
+
+ printf(",");
+
+ printf(" vltime=%s",
+ (pfx->pfx_validlifetime == ND6_INFINITE_LIFETIME) ?
+ "infinity" : sec2str(pfx->pfx_validlifetime, ssbuf));
+
+ if (pfx->pfx_vltimeexpire > 0)
+ printf("(expire: %s)",
+ ((long)pfx->pfx_vltimeexpire > now.tv_sec) ?
+ sec2str(pfx->pfx_vltimeexpire - now.tv_sec, ssbuf) :
+ "0");
+
+ printf(",");
+
+ printf(" pltime=%s",
+ (pfx->pfx_preflifetime == ND6_INFINITE_LIFETIME) ?
+ "infinity" : sec2str(pfx->pfx_preflifetime, ssbuf));
+
+ if (pfx->pfx_pltimeexpire > 0)
+ printf("(expire %s)",
+ ((long)pfx->pfx_pltimeexpire > now.tv_sec) ?
+ sec2str(pfx->pfx_pltimeexpire - now.tv_sec, ssbuf) :
+ "0");
+
+ printf(",");
+
+ printf(" flags=");
+ if (pfx->pfx_onlinkflg || pfx->pfx_autoconfflg) {
+ printf("%s", pfx->pfx_onlinkflg ? "L" : "");
+ printf("%s", pfx->pfx_autoconfflg ? "A" : "");
+ } else
+ printf("<none>");
+
+ if (pfx->pfx_timer) {
+ struct timespec *rest;
+
+ rest = rtadvd_timer_rest(pfx->pfx_timer);
+ if (rest) { /* XXX: what if not? */
+ printf(" expire=%s", sec2str(rest->tv_sec, ssbuf));
+ }
+ }
+
+ printf(")\n");
+
+ return (0);
+}
+
+static int
+action_show_rdnss(void *msg)
+{
+ struct rdnss *rdn;
+ struct rdnss_addr *rda;
+ uint16_t *rdn_cnt;
+ uint16_t *rda_cnt;
+ int i;
+ int j;
+ char *p;
+ uint32_t ltime;
+ char ntopbuf[INET6_ADDRSTRLEN];
+ char ssbuf[SSBUFLEN];
+
+ p = msg;
+ rdn_cnt = (uint16_t *)p;
+ p += sizeof(*rdn_cnt);
+
+ if (*rdn_cnt > 0) {
+ for (i = 0; i < *rdn_cnt; i++) {
+ rdn = (struct rdnss *)p;
+ ltime = rdn->rd_ltime;
+ p += sizeof(*rdn);
+
+ rda_cnt = (uint16_t *)p;
+ p += sizeof(*rda_cnt);
+ if (*rda_cnt > 0)
+ for (j = 0; j < *rda_cnt; j++) {
+ rda = (struct rdnss_addr *)p;
+ printf("\t %s (ltime=%s)\n",
+ inet_ntop(AF_INET6,
+ &rda->ra_dns,
+ ntopbuf,
+ sizeof(ntopbuf)),
+ sec2str(ltime, ssbuf));
+ p += sizeof(*rda);
+ }
+ }
+ }
+
+ return (0);
+}
+
+static int
+action_show_dnssl(void *msg)
+{
+ struct dnssl *dns;
+ struct dnssl_addr *dna;
+ uint16_t *dns_cnt;
+ uint16_t *dna_cnt;
+ int i;
+ int j;
+ char *p;
+ uint32_t ltime;
+ char hbuf[NI_MAXHOST];
+ char ssbuf[SSBUFLEN];
+
+ p = msg;
+ dns_cnt = (uint16_t *)p;
+ p += sizeof(*dns_cnt);
+
+ if (*dns_cnt > 0) {
+ for (i = 0; i < *dns_cnt; i++) {
+ dns = (struct dnssl *)p;
+ ltime = dns->dn_ltime;
+ p += sizeof(*dns);
+
+ dna_cnt = (uint16_t *)p;
+ p += sizeof(*dna_cnt);
+ if (*dna_cnt > 0)
+ for (j = 0; j < *dna_cnt; j++) {
+ dna = (struct dnssl_addr *)p;
+ dname_labeldec(hbuf, sizeof(hbuf),
+ dna->da_dom);
+ printf("\t %s (ltime=%s)\n",
+ hbuf, sec2str(ltime, ssbuf));
+ p += sizeof(*dna);
+ }
+ }
+ }
+
+ return (0);
+}
+
+/* Decode domain name label encoding in RFC 1035 Section 3.1 */
+static size_t
+dname_labeldec(char *dst, size_t dlen, const char *src)
+{
+ size_t len;
+ const char *src_origin;
+ const char *src_last;
+ const char *dst_origin;
+
+ src_origin = src;
+ src_last = strchr(src, '\0');
+ dst_origin = dst;
+ memset(dst, '\0', dlen);
+ while (src && (len = (uint8_t)(*src++) & 0x3f) &&
+ (src + len) <= src_last) {
+ if (dst != dst_origin)
+ *dst++ = '.';
+ mysyslog(LOG_DEBUG, "<%s> labellen = %zd", __func__, len);
+ memcpy(dst, src, len);
+ src += len;
+ dst += len;
+ }
+ *dst = '\0';
+
+ return (src - src_origin);
+}
diff --git a/usr.sbin/rtadvd/Makefile b/usr.sbin/rtadvd/Makefile
new file mode 100644
index 0000000..5d627e5
--- /dev/null
+++ b/usr.sbin/rtadvd/Makefile
@@ -0,0 +1,26 @@
+# 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$
+
+PROG= rtadvd
+MAN= rtadvd.conf.5 rtadvd.8
+SRCS= rtadvd.c rrenum.c advcap.c if.c config.c timer.c timer_subr.c \
+ control.c control_server.c
+
+LIBADD= util
+
+WARNS?= 1
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/rtadvd/Makefile.depend b/usr.sbin/rtadvd/Makefile.depend
new file mode 100644
index 0000000..7de116d
--- /dev/null
+++ b/usr.sbin/rtadvd/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/rtadvd/advcap.c b/usr.sbin/rtadvd/advcap.c
new file mode 100644
index 0000000..542e066
--- /dev/null
+++ b/usr.sbin/rtadvd/advcap.c
@@ -0,0 +1,436 @@
+/* $FreeBSD$ */
+/* $KAME: advcap.c,v 1.11 2003/05/19 09:46:50 keiichi Exp $ */
+
+/*
+ * Copyright (c) 1983 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.
+ */
+
+/*
+ * remcap - routines for dealing with the remote host data base
+ *
+ * derived from termcap
+ */
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <errno.h>
+#include <string.h>
+#include "pathnames.h"
+
+#ifndef BUFSIZ
+#define BUFSIZ 1024
+#endif
+#define MAXHOP 32 /* max number of tc= indirections */
+
+#define tgetent agetent
+#define tnchktc anchktc
+#define tnamatch anamatch
+#define tgetnum agetnum
+#define tgetflag agetflag
+#define tgetstr agetstr
+
+#if 0
+#define V_TERMCAP "REMOTE"
+#define V_TERM "HOST"
+#endif
+
+/*
+ * termcap - routines for dealing with the terminal capability data base
+ *
+ * BUG: Should use a "last" pointer in tbuf, so that searching
+ * for capabilities alphabetically would not be a n**2/2
+ * process when large numbers of capabilities are given.
+ * Note: If we add a last pointer now we will screw up the
+ * tc capability. We really should compile termcap.
+ *
+ * Essentially all the work here is scanning and decoding escapes
+ * in string capabilities. We don't use stdio because the editor
+ * doesn't, and because living w/o it is not hard.
+ */
+
+static char *tbuf;
+static int hopcount; /* detect infinite loops in termcap, init 0 */
+
+extern const char *conffile;
+
+int tgetent(char *, char *);
+int getent(char *, char *, const char *);
+int tnchktc(void);
+int tnamatch(char *);
+static char *tskip(char *);
+int64_t tgetnum(char *);
+int tgetflag(char *);
+char *tgetstr(char *, char **);
+static char *tdecode(char *, char **);
+
+/*
+ * Get an entry for terminal name in buffer bp,
+ * from the termcap file. Parse is very rudimentary;
+ * we just notice escaped newlines.
+ */
+int
+tgetent(char *bp, char *name)
+{
+ return (getent(bp, name, conffile));
+}
+
+int
+getent(char *bp, char *name, const char *cfile)
+{
+ int c;
+ int i = 0, cnt = 0;
+ char ibuf[BUFSIZ];
+ char *cp;
+ int tf;
+
+ tbuf = bp;
+ tf = 0;
+ /*
+ * TERMCAP can have one of two things in it. It can be the
+ * name of a file to use instead of /etc/termcap. In this
+ * case it better start with a "/". Or it can be an entry to
+ * use so we don't have to read the file. In this case it
+ * has to already have the newlines crunched out.
+ */
+ if (cfile && *cfile)
+ tf = open(cfile, O_RDONLY);
+
+ if (tf < 0) {
+ syslog(LOG_INFO,
+ "<%s> open: %s", __func__, strerror(errno));
+ return (-2);
+ }
+ for (;;) {
+ cp = bp;
+ for (;;) {
+ if (i == cnt) {
+ cnt = read(tf, ibuf, BUFSIZ);
+ if (cnt <= 0) {
+ close(tf);
+ return (0);
+ }
+ i = 0;
+ }
+ c = ibuf[i++];
+ if (c == '\n') {
+ if (cp > bp && cp[-1] == '\\') {
+ cp--;
+ continue;
+ }
+ break;
+ }
+ if (cp >= bp + BUFSIZ - 1) {
+ write(STDERR_FILENO, "Remcap entry too long\n",
+ 22);
+ break;
+ } else
+ *cp++ = c;
+ }
+ *cp = 0;
+
+ /*
+ * The real work for the match.
+ */
+ if (tnamatch(name)) {
+ close(tf);
+ return (tnchktc());
+ }
+ }
+}
+
+/*
+ * tnchktc: check the last entry, see if it's tc=xxx. If so,
+ * recursively find xxx and append that entry (minus the names)
+ * to take the place of the tc=xxx entry. This allows termcap
+ * entries to say "like an HP2621 but doesn't turn on the labels".
+ * Note that this works because of the left to right scan.
+ */
+int
+tnchktc(void)
+{
+ char *p, *q;
+ char tcname[16]; /* name of similar terminal */
+ char tcbuf[BUFSIZ];
+ char *holdtbuf = tbuf;
+ int l;
+
+ p = tbuf + strlen(tbuf) - 2; /* before the last colon */
+ while (*--p != ':')
+ if (p < tbuf) {
+ write(STDERR_FILENO, "Bad remcap entry\n", 18);
+ return (0);
+ }
+ p++;
+ /* p now points to beginning of last field */
+ if (p[0] != 't' || p[1] != 'c')
+ return (1);
+ strlcpy(tcname, p + 3, sizeof tcname);
+ q = tcname;
+ while (*q && *q != ':')
+ q++;
+ *q = 0;
+ if (++hopcount > MAXHOP) {
+ write(STDERR_FILENO, "Infinite tc= loop\n", 18);
+ return (0);
+ }
+ if (getent(tcbuf, tcname, conffile) != 1) {
+ return (0);
+ }
+ for (q = tcbuf; *q++ != ':'; )
+ ;
+ l = p - holdtbuf + strlen(q);
+ if (l > BUFSIZ) {
+ write(STDERR_FILENO, "Remcap entry too long\n", 23);
+ q[BUFSIZ - (p-holdtbuf)] = 0;
+ }
+ strcpy(p, q);
+ tbuf = holdtbuf;
+ return (1);
+}
+
+/*
+ * Tnamatch deals with name matching. The first field of the termcap
+ * entry is a sequence of names separated by |'s, so we compare
+ * against each such name. The normal : terminator after the last
+ * name (before the first field) stops us.
+ */
+int
+tnamatch(char *np)
+{
+ char *Np, *Bp;
+
+ Bp = tbuf;
+ if (*Bp == '#')
+ return (0);
+ for (;;) {
+ for (Np = np; *Np && *Bp == *Np; Bp++, Np++)
+ continue;
+ if (*Np == 0 && (*Bp == '|' || *Bp == ':' || *Bp == 0))
+ return (1);
+ while (*Bp && *Bp != ':' && *Bp != '|')
+ Bp++;
+ if (*Bp == 0 || *Bp == ':')
+ return (0);
+ Bp++;
+ }
+}
+
+/*
+ * Skip to the next field. Notice that this is very dumb, not
+ * knowing about \: escapes or any such. If necessary, :'s can be put
+ * into the termcap file in octal.
+ */
+static char *
+tskip(char *bp)
+{
+ int dquote;
+
+ dquote = 0;
+ while (*bp) {
+ switch (*bp) {
+ case ':':
+ if (!dquote)
+ goto breakbreak;
+ else
+ bp++;
+ break;
+ case '\\':
+ bp++;
+ if (isdigit(*bp)) {
+ while (isdigit(*bp++))
+ ;
+ } else
+ bp++;
+ case '"':
+ dquote = (dquote ? 1 : 0);
+ bp++;
+ break;
+ default:
+ bp++;
+ break;
+ }
+ }
+breakbreak:
+ if (*bp == ':')
+ bp++;
+ return (bp);
+}
+
+/*
+ * Return the (numeric) option id.
+ * Numeric options look like
+ * li#80
+ * i.e. the option string is separated from the numeric value by
+ * a # character. If the option is not found we return -1.
+ * Note that we handle octal numbers beginning with 0.
+ */
+int64_t
+tgetnum(char *id)
+{
+ int64_t i;
+ int base;
+ char *bp = tbuf;
+
+ for (;;) {
+ bp = tskip(bp);
+ if (*bp == 0)
+ return (-1);
+ if (strncmp(bp, id, strlen(id)) != 0)
+ continue;
+ bp += strlen(id);
+ if (*bp == '@')
+ return (-1);
+ if (*bp != '#')
+ continue;
+ bp++;
+ base = 10;
+ if (*bp == '0')
+ base = 8;
+ i = 0;
+ while (isdigit(*bp))
+ i *= base, i += *bp++ - '0';
+ return (i);
+ }
+}
+
+/*
+ * Handle a flag option.
+ * Flag options are given "naked", i.e. followed by a : or the end
+ * of the buffer. Return 1 if we find the option, or 0 if it is
+ * not given.
+ */
+int
+tgetflag(char *id)
+{
+ char *bp = tbuf;
+
+ for (;;) {
+ bp = tskip(bp);
+ if (!*bp)
+ return (0);
+ if (strncmp(bp, id, strlen(id)) == 0) {
+ bp += strlen(id);
+ if (!*bp || *bp == ':')
+ return (1);
+ else if (*bp == '@')
+ return (0);
+ }
+ }
+}
+
+/*
+ * Get a string valued option.
+ * These are given as
+ * cl=^Z
+ * Much decoding is done on the strings, and the strings are
+ * placed in area, which is a ref parameter which is updated.
+ * No checking on area overflow.
+ */
+char *
+tgetstr(char *id, char **area)
+{
+ char *bp = tbuf;
+
+ for (;;) {
+ bp = tskip(bp);
+ if (!*bp)
+ return (0);
+ if (strncmp(bp, id, strlen(id)) != 0)
+ continue;
+ bp += strlen(id);
+ if (*bp == '@')
+ return (0);
+ if (*bp != '=')
+ continue;
+ bp++;
+ return (tdecode(bp, area));
+ }
+}
+
+/*
+ * Tdecode does the grung work to decode the
+ * string capability escapes.
+ */
+static char *
+tdecode(char *str, char **area)
+{
+ char *cp;
+ int c;
+ const char *dp;
+ int i;
+ char term;
+
+ term = ':';
+ cp = *area;
+again:
+ if (*str == '"') {
+ term = '"';
+ str++;
+ }
+ while ((c = *str++) && c != term) {
+ switch (c) {
+
+ case '^':
+ c = *str++ & 037;
+ break;
+
+ case '\\':
+ dp = "E\033^^\\\\::n\nr\rt\tb\bf\f\"\"";
+ c = *str++;
+nextc:
+ if (*dp++ == c) {
+ c = *dp++;
+ break;
+ }
+ dp++;
+ if (*dp)
+ goto nextc;
+ if (isdigit(c)) {
+ c -= '0', i = 2;
+ do
+ c <<= 3, c |= *str++ - '0';
+ while (--i && isdigit(*str));
+ }
+ break;
+ }
+ *cp++ = c;
+ }
+ if (c == term && term != ':') {
+ term = ':';
+ goto again;
+ }
+ *cp++ = 0;
+ str = *area;
+ *area = cp;
+ return (str);
+}
diff --git a/usr.sbin/rtadvd/advcap.h b/usr.sbin/rtadvd/advcap.h
new file mode 100644
index 0000000..3cc124a
--- /dev/null
+++ b/usr.sbin/rtadvd/advcap.h
@@ -0,0 +1,46 @@
+/* $FreeBSD$ */
+/* $KAME: advcap.h,v 1.5 2003/06/09 05:40:54 t-momose Exp $ */
+
+/*
+ * Copyright (C) 1994,1995 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 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.
+ */
+
+/* Based on Id: termcap.h,v 1.8 1996/09/10 12:42:10 peter Exp */
+
+#ifndef _ADVCAP_H_
+#define _ADVCAP_H_
+
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+extern int agetent(char *, const char *);
+extern int agetflag(const char *);
+extern int64_t agetnum(const char *);
+extern char *agetstr(const char *, char **);
+
+__END_DECLS
+
+#endif /* _ADVCAP_H_ */
diff --git a/usr.sbin/rtadvd/config.c b/usr.sbin/rtadvd/config.c
new file mode 100644
index 0000000..d63af5a
--- /dev/null
+++ b/usr.sbin/rtadvd/config.c
@@ -0,0 +1,1540 @@
+/* $FreeBSD$ */
+/* $KAME: config.c,v 1.84 2003/08/05 12:34:23 itojun Exp $ */
+
+/*
+ * Copyright (C) 1998 WIDE Project.
+ * Copyright (C) 2011 Hiroki Sato <hrs@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.
+ * 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/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <net/route.h>
+#include <net/if_dl.h>
+
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <netinet/ip6.h>
+#include <netinet6/ip6_var.h>
+#include <netinet/icmp6.h>
+#include <netinet6/nd6.h>
+
+#include <arpa/inet.h>
+
+#include <stdio.h>
+#include <syslog.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <netdb.h>
+#include <string.h>
+#include <search.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <ifaddrs.h>
+
+#include "rtadvd.h"
+#include "advcap.h"
+#include "timer.h"
+#include "if.h"
+#include "config.h"
+
+/* label of tcapcode + number + domain name + zero octet */
+static char entbuf[10 + 3 + NI_MAXHOST + 1];
+static char oentbuf[10 + 3 + NI_MAXHOST + 1];
+static char abuf[DNAME_LABELENC_MAXLEN];
+
+static time_t prefix_timo = (60 * 120); /* 2 hours.
+ * XXX: should be configurable. */
+
+static struct rtadvd_timer *prefix_timeout(void *);
+static void makeentry(char *, size_t, int, const char *);
+static ssize_t dname_labelenc(char *, const char *);
+
+/* Encode domain name label encoding in RFC 1035 Section 3.1 */
+static ssize_t
+dname_labelenc(char *dst, const char *src)
+{
+ char *dst_origin;
+ char *p;
+ size_t len;
+
+ dst_origin = dst;
+ len = strlen(src);
+
+ if (len + len / 64 + 1 + 1 > DNAME_LABELENC_MAXLEN)
+ return (-1);
+ /* Length fields per 63 octets + '\0' (<= DNAME_LABELENC_MAXLEN) */
+ memset(dst, 0, len + len / 64 + 1 + 1);
+
+ syslog(LOG_DEBUG, "<%s> labelenc = %s", __func__, src);
+ while (src && (len = strlen(src)) != 0) {
+ /* Put a length field with 63 octet limitation first. */
+ p = strchr(src, '.');
+ if (p == NULL)
+ *dst = len = MIN(63, len);
+ else
+ *dst = len = MIN(63, p - src);
+ if (dst + 1 + len < dst_origin + DNAME_LABELENC_MAXLEN)
+ dst++;
+ else
+ return (-1);
+ /* Copy 63 octets at most. */
+ memcpy(dst, src, len);
+ dst += len;
+ if (p == NULL) /* the last label */
+ break;
+ src = p + 1;
+ }
+ /* Always need a 0-length label at the tail. */
+ *dst++ = '\0';
+
+ syslog(LOG_DEBUG, "<%s> labellen = %td", __func__, dst - dst_origin);
+ return (dst - dst_origin);
+}
+
+#define MUSTHAVE(var, cap) \
+ do { \
+ int64_t t; \
+ if ((t = agetnum(cap)) < 0) { \
+ fprintf(stderr, "rtadvd: need %s for interface %s\n", \
+ cap, intface); \
+ exit(1); \
+ } \
+ var = t; \
+ } while (0)
+
+#define MAYHAVE(var, cap, def) \
+ do { \
+ if ((var = agetnum(cap)) < 0) \
+ var = def; \
+ } while (0)
+
+int
+loadconfig_index(int idx)
+{
+ char ifname[IFNAMSIZ];
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ if (if_indextoname(idx, ifname) != NULL)
+ return (loadconfig_ifname(ifname));
+ else
+ return (1);
+}
+
+int
+loadconfig_ifname(char *ifname)
+{
+ struct ifinfo *ifi;
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ update_ifinfo(&ifilist, UPDATE_IFINFO_ALL);
+ TAILQ_FOREACH(ifi, &ifilist, ifi_next) {
+ /* NULL means all IFs will be processed. */
+ if (ifname != NULL &&
+ strcmp(ifi->ifi_ifname, ifname) != 0)
+ continue;
+
+ if (!ifi->ifi_persist) {
+ syslog(LOG_INFO,
+ "<%s> %s is not a target interface. "
+ "Ignored at this moment.", __func__,
+ ifi->ifi_ifname);
+ continue;
+
+ }
+ if (ifi->ifi_ifindex == 0) {
+ syslog(LOG_ERR,
+ "<%s> %s not found. "
+ "Ignored at this moment.", __func__,
+ ifi->ifi_ifname);
+ continue;
+ }
+ if (getconfig(ifi) == NULL) {
+ syslog(LOG_ERR,
+ "<%s> invalid configuration for %s. "
+ "Ignored at this moment.", __func__,
+ ifi->ifi_ifname);
+ continue;
+ }
+ }
+ return (0);
+}
+
+int
+rm_ifinfo_index(int idx)
+{
+ struct ifinfo *ifi;
+
+ ifi = if_indextoifinfo(idx);
+ if (ifi == NULL) {
+ syslog(LOG_ERR, "<%s>: ifinfo not found (idx=%d)",
+ __func__, idx);
+ return (-1);
+ }
+
+ return (rm_ifinfo(ifi));
+}
+
+int
+rm_ifinfo(struct ifinfo *ifi)
+{
+ int error;
+
+ syslog(LOG_DEBUG, "<%s> enter (%s).", __func__, ifi->ifi_ifname);
+ switch (ifi->ifi_state) {
+ case IFI_STATE_UNCONFIGURED:
+ return (0);
+ break;
+ default:
+ ifi->ifi_state = IFI_STATE_UNCONFIGURED;
+ syslog(LOG_DEBUG,
+ "<%s> ifname=%s marked as UNCONFIGURED.",
+ __func__, ifi->ifi_ifname);
+
+ /* XXX: No MC leaving here because index is disappeared */
+
+ /* Inactivate timer */
+ rtadvd_remove_timer(ifi->ifi_ra_timer);
+ ifi->ifi_ra_timer = NULL;
+ break;
+ }
+
+ /* clean up ifi */
+ if (!ifi->ifi_persist) {
+ TAILQ_REMOVE(&ifilist, ifi, ifi_next);
+ syslog(LOG_DEBUG, "<%s>: ifinfo (idx=%d) removed.",
+ __func__, ifi->ifi_ifindex);
+ free(ifi);
+ } else {
+ /* recreate an empty entry */
+ update_persist_ifinfo(&ifilist, ifi->ifi_ifname);
+ syslog(LOG_DEBUG, "<%s>: ifname=%s is persistent.",
+ __func__, ifi->ifi_ifname);
+ }
+
+ /* clean up rai if any */
+ switch (ifi->ifi_state) {
+ case IFI_STATE_CONFIGURED:
+ if (ifi->ifi_rainfo != NULL) {
+ error = rm_rainfo(ifi->ifi_rainfo);
+ if (error)
+ return (error);
+ ifi->ifi_rainfo = NULL;
+ }
+ break;
+ case IFI_STATE_TRANSITIVE:
+ if (ifi->ifi_rainfo == ifi->ifi_rainfo_trans) {
+ if (ifi->ifi_rainfo != NULL) {
+ error = rm_rainfo(ifi->ifi_rainfo);
+ if (error)
+ return (error);
+ ifi->ifi_rainfo = NULL;
+ ifi->ifi_rainfo_trans = NULL;
+ }
+ } else {
+ if (ifi->ifi_rainfo != NULL) {
+ error = rm_rainfo(ifi->ifi_rainfo);
+ if (error)
+ return (error);
+ ifi->ifi_rainfo = NULL;
+ }
+ if (ifi->ifi_rainfo_trans != NULL) {
+ error = rm_rainfo(ifi->ifi_rainfo_trans);
+ if (error)
+ return (error);
+ ifi->ifi_rainfo_trans = NULL;
+ }
+ }
+ }
+
+ syslog(LOG_DEBUG, "<%s> leave (%s).", __func__, ifi->ifi_ifname);
+ return (0);
+}
+
+int
+rm_rainfo(struct rainfo *rai)
+{
+ struct prefix *pfx;
+ struct soliciter *sol;
+ struct rdnss *rdn;
+ struct rdnss_addr *rdna;
+ struct dnssl *dns;
+ struct rtinfo *rti;
+
+ syslog(LOG_DEBUG, "<%s>: enter", __func__);
+
+ TAILQ_REMOVE(&railist, rai, rai_next);
+ if (rai->rai_ifinfo != NULL)
+ syslog(LOG_DEBUG, "<%s>: rainfo (idx=%d) removed.",
+ __func__, rai->rai_ifinfo->ifi_ifindex);
+
+ if (rai->rai_ra_data != NULL)
+ free(rai->rai_ra_data);
+
+ while ((pfx = TAILQ_FIRST(&rai->rai_prefix)) != NULL)
+ delete_prefix(pfx);
+ while ((sol = TAILQ_FIRST(&rai->rai_soliciter)) != NULL) {
+ TAILQ_REMOVE(&rai->rai_soliciter, sol, sol_next);
+ free(sol);
+ }
+ while ((rdn = TAILQ_FIRST(&rai->rai_rdnss)) != NULL) {
+ TAILQ_REMOVE(&rai->rai_rdnss, rdn, rd_next);
+ while ((rdna = TAILQ_FIRST(&rdn->rd_list)) != NULL) {
+ TAILQ_REMOVE(&rdn->rd_list, rdna, ra_next);
+ free(rdna);
+ }
+ free(rdn);
+ }
+ while ((dns = TAILQ_FIRST(&rai->rai_dnssl)) != NULL) {
+ TAILQ_REMOVE(&rai->rai_dnssl, dns, dn_next);
+ free(dns);
+ }
+ while ((rti = TAILQ_FIRST(&rai->rai_route)) != NULL) {
+ TAILQ_REMOVE(&rai->rai_route, rti, rti_next);
+ free(rti);
+ }
+ free(rai);
+ syslog(LOG_DEBUG, "<%s>: leave", __func__);
+
+ return (0);
+}
+
+struct ifinfo *
+getconfig(struct ifinfo *ifi)
+{
+ int stat, i;
+ int error;
+ char tbuf[BUFSIZ];
+ struct rainfo *rai;
+ struct rainfo *rai_old;
+ int32_t val;
+ int64_t val64;
+ char buf[BUFSIZ];
+ char *bp = buf;
+ char *addr, *flagstr;
+
+ if (ifi == NULL) /* if does not exist */
+ return (NULL);
+
+ if (ifi->ifi_state == IFI_STATE_TRANSITIVE &&
+ ifi->ifi_rainfo == NULL) {
+ syslog(LOG_INFO, "<%s> %s is shutting down. Skipped.",
+ __func__, ifi->ifi_ifname);
+ return (NULL);
+ }
+
+ if ((stat = agetent(tbuf, ifi->ifi_ifname)) <= 0) {
+ memset(tbuf, 0, sizeof(tbuf));
+ syslog(LOG_INFO,
+ "<%s> %s isn't defined in the configuration file"
+ " or the configuration file doesn't exist."
+ " Treat it as default",
+ __func__, ifi->ifi_ifname);
+ }
+
+ ELM_MALLOC(rai, exit(1));
+ TAILQ_INIT(&rai->rai_prefix);
+ TAILQ_INIT(&rai->rai_route);
+ TAILQ_INIT(&rai->rai_rdnss);
+ TAILQ_INIT(&rai->rai_dnssl);
+ TAILQ_INIT(&rai->rai_soliciter);
+ rai->rai_ifinfo = ifi;
+
+ /* gather on-link prefixes from the network interfaces. */
+ if (agetflag("noifprefix"))
+ rai->rai_advifprefix = 0;
+ else
+ rai->rai_advifprefix = 1;
+
+ /* get interface information */
+ if (agetflag("nolladdr"))
+ rai->rai_advlinkopt = 0;
+ else
+ rai->rai_advlinkopt = 1;
+ if (rai->rai_advlinkopt) {
+ if (ifi->ifi_sdl.sdl_type == 0) {
+ syslog(LOG_ERR,
+ "<%s> can't get information of %s",
+ __func__, ifi->ifi_ifname);
+ goto getconfig_free_rai;
+ }
+ }
+
+ /*
+ * set router configuration variables.
+ */
+ MAYHAVE(val, "maxinterval", DEF_MAXRTRADVINTERVAL);
+ if (val < MIN_MAXINTERVAL || val > MAX_MAXINTERVAL) {
+ syslog(LOG_ERR,
+ "<%s> maxinterval (%" PRIu32 ") on %s is invalid "
+ "(must be between %u and %u)", __func__, val,
+ ifi->ifi_ifname, MIN_MAXINTERVAL, MAX_MAXINTERVAL);
+ goto getconfig_free_rai;
+ }
+ rai->rai_maxinterval = (uint16_t)val;
+
+ MAYHAVE(val, "mininterval", rai->rai_maxinterval/3);
+ if ((uint16_t)val < MIN_MININTERVAL ||
+ (uint16_t)val > (rai->rai_maxinterval * 3) / 4) {
+ syslog(LOG_ERR,
+ "<%s> mininterval (%" PRIu32 ") on %s is invalid "
+ "(must be between %d and %d)",
+ __func__, val, ifi->ifi_ifname, MIN_MININTERVAL,
+ (rai->rai_maxinterval * 3) / 4);
+ goto getconfig_free_rai;
+ }
+ rai->rai_mininterval = (uint16_t)val;
+
+ MAYHAVE(val, "chlim", DEF_ADVCURHOPLIMIT);
+ rai->rai_hoplimit = val & 0xff;
+
+ if ((flagstr = (char *)agetstr("raflags", &bp))) {
+ val = 0;
+ if (strchr(flagstr, 'm'))
+ val |= ND_RA_FLAG_MANAGED;
+ if (strchr(flagstr, 'o'))
+ val |= ND_RA_FLAG_OTHER;
+ if (strchr(flagstr, 'h'))
+ val |= ND_RA_FLAG_RTPREF_HIGH;
+ if (strchr(flagstr, 'l')) {
+ if ((val & ND_RA_FLAG_RTPREF_HIGH)) {
+ syslog(LOG_ERR, "<%s> the \'h\' and \'l\'"
+ " router flags are exclusive", __func__);
+ goto getconfig_free_rai;
+ }
+ val |= ND_RA_FLAG_RTPREF_LOW;
+ }
+ } else
+ MAYHAVE(val, "raflags", 0);
+
+ rai->rai_managedflg = val & ND_RA_FLAG_MANAGED;
+ rai->rai_otherflg = val & ND_RA_FLAG_OTHER;
+#ifndef ND_RA_FLAG_RTPREF_MASK
+#define ND_RA_FLAG_RTPREF_MASK 0x18 /* 00011000 */
+#define ND_RA_FLAG_RTPREF_RSV 0x10 /* 00010000 */
+#endif
+ rai->rai_rtpref = val & ND_RA_FLAG_RTPREF_MASK;
+ if (rai->rai_rtpref == ND_RA_FLAG_RTPREF_RSV) {
+ syslog(LOG_ERR, "<%s> invalid router preference (%02x) on %s",
+ __func__, rai->rai_rtpref, ifi->ifi_ifname);
+ goto getconfig_free_rai;
+ }
+
+ MAYHAVE(val, "rltime", rai->rai_maxinterval * 3);
+ if ((uint16_t)val && ((uint16_t)val < rai->rai_maxinterval ||
+ (uint16_t)val > MAXROUTERLIFETIME)) {
+ syslog(LOG_ERR,
+ "<%s> router lifetime (%" PRIu32 ") on %s is invalid "
+ "(must be 0 or between %d and %d)",
+ __func__, val, ifi->ifi_ifname, rai->rai_maxinterval,
+ MAXROUTERLIFETIME);
+ goto getconfig_free_rai;
+ }
+ rai->rai_lifetime = val & 0xffff;
+
+ MAYHAVE(val, "rtime", DEF_ADVREACHABLETIME);
+ if (val < 0 || val > MAXREACHABLETIME) {
+ syslog(LOG_ERR,
+ "<%s> reachable time (%" PRIu32 ") on %s is invalid "
+ "(must be no greater than %d)",
+ __func__, val, ifi->ifi_ifname, MAXREACHABLETIME);
+ goto getconfig_free_rai;
+ }
+ rai->rai_reachabletime = (uint32_t)val;
+
+ MAYHAVE(val64, "retrans", DEF_ADVRETRANSTIMER);
+ if (val64 < 0 || val64 > 0xffffffff) {
+ syslog(LOG_ERR, "<%s> retrans time (%" PRIu64 ") on %s out of range",
+ __func__, val64, ifi->ifi_ifname);
+ goto getconfig_free_rai;
+ }
+ rai->rai_retranstimer = (uint32_t)val64;
+
+ if (agetnum("hapref") != -1 || agetnum("hatime") != -1) {
+ syslog(LOG_ERR,
+ "<%s> mobile-ip6 configuration not supported",
+ __func__);
+ goto getconfig_free_rai;
+ }
+ /* prefix information */
+
+ /*
+ * This is an implementation specific parameter to consider
+ * link propagation delays and poorly synchronized clocks when
+ * checking consistency of advertised lifetimes.
+ */
+ MAYHAVE(val, "clockskew", 0);
+ rai->rai_clockskew = val;
+
+ rai->rai_pfxs = 0;
+ for (i = -1; i < MAXPREFIX; i++) {
+ struct prefix *pfx;
+
+ makeentry(entbuf, sizeof(entbuf), i, "addr");
+ addr = (char *)agetstr(entbuf, &bp);
+ if (addr == NULL)
+ continue;
+
+ /* allocate memory to store prefix information */
+ ELM_MALLOC(pfx, exit(1));
+ pfx->pfx_rainfo = rai;
+ pfx->pfx_origin = PREFIX_FROM_CONFIG;
+
+ if (inet_pton(AF_INET6, addr, &pfx->pfx_prefix) != 1) {
+ syslog(LOG_ERR,
+ "<%s> inet_pton failed for %s",
+ __func__, addr);
+ goto getconfig_free_pfx;
+ }
+ if (IN6_IS_ADDR_MULTICAST(&pfx->pfx_prefix)) {
+ syslog(LOG_ERR,
+ "<%s> multicast prefix (%s) must "
+ "not be advertised on %s",
+ __func__, addr, ifi->ifi_ifname);
+ goto getconfig_free_pfx;
+ }
+ if (IN6_IS_ADDR_LINKLOCAL(&pfx->pfx_prefix))
+ syslog(LOG_NOTICE,
+ "<%s> link-local prefix (%s) will be"
+ " advertised on %s",
+ __func__, addr, ifi->ifi_ifname);
+
+ makeentry(entbuf, sizeof(entbuf), i, "prefixlen");
+ MAYHAVE(val, entbuf, 64);
+ if (val < 0 || val > 128) {
+ syslog(LOG_ERR, "<%s> prefixlen (%" PRIu32 ") for %s "
+ "on %s out of range",
+ __func__, val, addr, ifi->ifi_ifname);
+ goto getconfig_free_pfx;
+ }
+ pfx->pfx_prefixlen = (int)val;
+
+ makeentry(entbuf, sizeof(entbuf), i, "pinfoflags");
+ if ((flagstr = (char *)agetstr(entbuf, &bp))) {
+ val = 0;
+ if (strchr(flagstr, 'l'))
+ val |= ND_OPT_PI_FLAG_ONLINK;
+ if (strchr(flagstr, 'a'))
+ val |= ND_OPT_PI_FLAG_AUTO;
+ } else {
+ MAYHAVE(val, entbuf,
+ (ND_OPT_PI_FLAG_ONLINK|ND_OPT_PI_FLAG_AUTO));
+ }
+ pfx->pfx_onlinkflg = val & ND_OPT_PI_FLAG_ONLINK;
+ pfx->pfx_autoconfflg = val & ND_OPT_PI_FLAG_AUTO;
+
+ makeentry(entbuf, sizeof(entbuf), i, "vltime");
+ MAYHAVE(val64, entbuf, DEF_ADVVALIDLIFETIME);
+ if (val64 < 0 || val64 > 0xffffffff) {
+ syslog(LOG_ERR, "<%s> vltime (%" PRIu64 ") for "
+ "%s/%d on %s is out of range",
+ __func__, val64,
+ addr, pfx->pfx_prefixlen, ifi->ifi_ifname);
+ goto getconfig_free_pfx;
+ }
+ pfx->pfx_validlifetime = (uint32_t)val64;
+
+ makeentry(entbuf, sizeof(entbuf), i, "vltimedecr");
+ if (agetflag(entbuf)) {
+ struct timespec now;
+
+ clock_gettime(CLOCK_MONOTONIC_FAST, &now);
+ pfx->pfx_vltimeexpire =
+ now.tv_sec + pfx->pfx_validlifetime;
+ }
+
+ makeentry(entbuf, sizeof(entbuf), i, "pltime");
+ MAYHAVE(val64, entbuf, DEF_ADVPREFERREDLIFETIME);
+ if (val64 < 0 || val64 > 0xffffffff) {
+ syslog(LOG_ERR,
+ "<%s> pltime (%" PRIu64 ") for %s/%d on %s "
+ "is out of range",
+ __func__, val64,
+ addr, pfx->pfx_prefixlen, ifi->ifi_ifname);
+ goto getconfig_free_pfx;
+ }
+ pfx->pfx_preflifetime = (uint32_t)val64;
+
+ makeentry(entbuf, sizeof(entbuf), i, "pltimedecr");
+ if (agetflag(entbuf)) {
+ struct timespec now;
+
+ clock_gettime(CLOCK_MONOTONIC_FAST, &now);
+ pfx->pfx_pltimeexpire =
+ now.tv_sec + pfx->pfx_preflifetime;
+ }
+ /* link into chain */
+ TAILQ_INSERT_TAIL(&rai->rai_prefix, pfx, pfx_next);
+ rai->rai_pfxs++;
+ continue;
+getconfig_free_pfx:
+ free(pfx);
+ }
+ if (rai->rai_advifprefix && rai->rai_pfxs == 0)
+ get_prefix(rai);
+
+ MAYHAVE(val64, "mtu", 0);
+ if (val < 0 || val64 > 0xffffffff) {
+ syslog(LOG_ERR,
+ "<%s> mtu (%" PRIu64 ") on %s out of range",
+ __func__, val64, ifi->ifi_ifname);
+ goto getconfig_free_rai;
+ }
+ rai->rai_linkmtu = (uint32_t)val64;
+ if (rai->rai_linkmtu == 0) {
+ char *mtustr;
+
+ if ((mtustr = (char *)agetstr("mtu", &bp)) &&
+ strcmp(mtustr, "auto") == 0)
+ rai->rai_linkmtu = ifi->ifi_phymtu;
+ }
+ else if (rai->rai_linkmtu < IPV6_MMTU ||
+ rai->rai_linkmtu > ifi->ifi_phymtu) {
+ syslog(LOG_ERR,
+ "<%s> advertised link mtu (%" PRIu32 ") on %s is invalid (must "
+ "be between least MTU (%d) and physical link MTU (%d)",
+ __func__, rai->rai_linkmtu, ifi->ifi_ifname,
+ IPV6_MMTU, ifi->ifi_phymtu);
+ goto getconfig_free_rai;
+ }
+
+#ifdef SIOCSIFINFO_IN6
+ {
+ struct in6_ndireq ndi;
+ int s;
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
+ syslog(LOG_ERR, "<%s> socket: %s", __func__,
+ strerror(errno));
+ exit(1);
+ }
+ memset(&ndi, 0, sizeof(ndi));
+ strncpy(ndi.ifname, ifi->ifi_ifname, sizeof(ndi.ifname));
+ if (ioctl(s, SIOCGIFINFO_IN6, (caddr_t)&ndi) < 0)
+ syslog(LOG_INFO, "<%s> ioctl:SIOCGIFINFO_IN6 at %s: %s",
+ __func__, ifi->ifi_ifname, strerror(errno));
+
+ /* reflect the RA info to the host variables in kernel */
+ ndi.ndi.chlim = rai->rai_hoplimit;
+ ndi.ndi.retrans = rai->rai_retranstimer;
+ ndi.ndi.basereachable = rai->rai_reachabletime;
+ if (ioctl(s, SIOCSIFINFO_IN6, (caddr_t)&ndi) < 0)
+ syslog(LOG_INFO, "<%s> ioctl:SIOCSIFINFO_IN6 at %s: %s",
+ __func__, ifi->ifi_ifname, strerror(errno));
+
+ close(s);
+ }
+#endif
+
+ /* route information */
+ rai->rai_routes = 0;
+ for (i = -1; i < MAXROUTE; i++) {
+ struct rtinfo *rti;
+
+ makeentry(entbuf, sizeof(entbuf), i, "rtprefix");
+ addr = (char *)agetstr(entbuf, &bp);
+ if (addr == NULL) {
+ makeentry(oentbuf, sizeof(oentbuf), i, "rtrprefix");
+ addr = (char *)agetstr(oentbuf, &bp);
+ if (addr)
+ fprintf(stderr, "%s was obsoleted. Use %s.\n",
+ oentbuf, entbuf);
+ }
+ if (addr == NULL)
+ continue;
+
+ /* allocate memory to store prefix information */
+ ELM_MALLOC(rti, exit(1));
+
+ if (inet_pton(AF_INET6, addr, &rti->rti_prefix) != 1) {
+ syslog(LOG_ERR, "<%s> inet_pton failed for %s",
+ __func__, addr);
+ goto getconfig_free_rti;
+ }
+#if 0
+ /*
+ * XXX: currently there's no restriction in route information
+ * prefix according to
+ * draft-ietf-ipngwg-router-selection-00.txt.
+ * However, I think the similar restriction be necessary.
+ */
+ MAYHAVE(val64, entbuf, DEF_ADVVALIDLIFETIME);
+ if (IN6_IS_ADDR_MULTICAST(&rti->prefix)) {
+ syslog(LOG_ERR,
+ "<%s> multicast route (%s) must "
+ "not be advertised on %s",
+ __func__, addr, ifi->ifi_ifname);
+ goto getconfig_free_rti;
+ }
+ if (IN6_IS_ADDR_LINKLOCAL(&rti->prefix)) {
+ syslog(LOG_NOTICE,
+ "<%s> link-local route (%s) will "
+ "be advertised on %s",
+ __func__, addr, ifi->ifi_ifname);
+ goto getconfig_free_rti;
+ }
+#endif
+
+ makeentry(entbuf, sizeof(entbuf), i, "rtplen");
+ /* XXX: 256 is a magic number for compatibility check. */
+ MAYHAVE(val, entbuf, 256);
+ if (val == 256) {
+ makeentry(oentbuf, sizeof(oentbuf), i, "rtrplen");
+ MAYHAVE(val, oentbuf, 256);
+ if (val != 256)
+ fprintf(stderr, "%s was obsoleted. Use %s.\n",
+ oentbuf, entbuf);
+ else
+ val = 64;
+ }
+ if (val < 0 || val > 128) {
+ syslog(LOG_ERR, "<%s> prefixlen (%" PRIu32 ") for %s on %s "
+ "out of range",
+ __func__, val, addr, ifi->ifi_ifname);
+ goto getconfig_free_rti;
+ }
+ rti->rti_prefixlen = (int)val;
+
+ makeentry(entbuf, sizeof(entbuf), i, "rtflags");
+ if ((flagstr = (char *)agetstr(entbuf, &bp))) {
+ val = 0;
+ if (strchr(flagstr, 'h'))
+ val |= ND_RA_FLAG_RTPREF_HIGH;
+ if (strchr(flagstr, 'l')) {
+ if ((val & ND_RA_FLAG_RTPREF_HIGH)) {
+ syslog(LOG_ERR,
+ "<%s> the \'h\' and \'l\' route"
+ " preferences are exclusive",
+ __func__);
+ goto getconfig_free_rti;
+ }
+ val |= ND_RA_FLAG_RTPREF_LOW;
+ }
+ } else
+ MAYHAVE(val, entbuf, 256); /* XXX */
+ if (val == 256) {
+ makeentry(oentbuf, sizeof(oentbuf), i, "rtrflags");
+ MAYHAVE(val, oentbuf, 256);
+ if (val != 256) {
+ fprintf(stderr, "%s was obsoleted. Use %s.\n",
+ oentbuf, entbuf);
+ } else
+ val = 0;
+ }
+ rti->rti_rtpref = val & ND_RA_FLAG_RTPREF_MASK;
+ if (rti->rti_rtpref == ND_RA_FLAG_RTPREF_RSV) {
+ syslog(LOG_ERR, "<%s> invalid route preference (%02x) "
+ "for %s/%d on %s",
+ __func__, rti->rti_rtpref, addr,
+ rti->rti_prefixlen, ifi->ifi_ifname);
+ goto getconfig_free_rti;
+ }
+
+ /*
+ * Since the spec does not a default value, we should make
+ * this entry mandatory. However, FreeBSD 4.4 has shipped
+ * with this field being optional, we use the router lifetime
+ * as an ad-hoc default value with a warning message.
+ */
+ makeentry(entbuf, sizeof(entbuf), i, "rtltime");
+ MAYHAVE(val64, entbuf, -1);
+ if (val64 == -1) {
+ makeentry(oentbuf, sizeof(oentbuf), i, "rtrltime");
+ MAYHAVE(val64, oentbuf, -1);
+ if (val64 != -1)
+ fprintf(stderr, "%s was obsoleted. Use %s.\n",
+ oentbuf, entbuf);
+ else {
+ fprintf(stderr, "%s should be specified "
+ "for interface %s.\n", entbuf,
+ ifi->ifi_ifname);
+ val64 = rai->rai_lifetime;
+ }
+ }
+ if (val64 < 0 || val64 > 0xffffffff) {
+ syslog(LOG_ERR, "<%s> route lifetime (%" PRIu64 ") for "
+ "%s/%d on %s out of range", __func__,
+ val64, addr, rti->rti_prefixlen,
+ ifi->ifi_ifname);
+ goto getconfig_free_rti;
+ }
+ rti->rti_ltime = (uint32_t)val64;
+
+ /* link into chain */
+ TAILQ_INSERT_TAIL(&rai->rai_route, rti, rti_next);
+ rai->rai_routes++;
+ continue;
+getconfig_free_rti:
+ free(rti);
+ }
+
+ /* DNS server and DNS search list information */
+ for (i = -1; i < MAXRDNSSENT ; i++) {
+ struct rdnss *rdn;
+ struct rdnss_addr *rdna;
+ char *ap;
+ int c;
+
+ makeentry(entbuf, sizeof(entbuf), i, "rdnss");
+ addr = (char *)agetstr(entbuf, &bp);
+ if (addr == NULL)
+ continue;
+ ELM_MALLOC(rdn, exit(1));
+
+ TAILQ_INIT(&rdn->rd_list);
+
+ for (ap = addr; ap - addr < (ssize_t)strlen(addr); ap += c+1) {
+ c = strcspn(ap, ",");
+ strncpy(abuf, ap, c);
+ abuf[c] = '\0';
+ ELM_MALLOC(rdna, goto getconfig_free_rdn);
+ if (inet_pton(AF_INET6, abuf, &rdna->ra_dns) != 1) {
+ syslog(LOG_ERR, "<%s> inet_pton failed for %s",
+ __func__, abuf);
+ free(rdna);
+ goto getconfig_free_rdn;
+ }
+ TAILQ_INSERT_TAIL(&rdn->rd_list, rdna, ra_next);
+ }
+
+ makeentry(entbuf, sizeof(entbuf), i, "rdnssltime");
+ MAYHAVE(val, entbuf, (rai->rai_maxinterval * 3 / 2));
+ if ((uint16_t)val < rai->rai_maxinterval ||
+ (uint16_t)val > rai->rai_maxinterval * 2) {
+ syslog(LOG_ERR, "%s (%" PRIu16 ") on %s is invalid "
+ "(must be between %d and %d)",
+ entbuf, val, ifi->ifi_ifname, rai->rai_maxinterval,
+ rai->rai_maxinterval * 2);
+ goto getconfig_free_rdn;
+ }
+ rdn->rd_ltime = val;
+
+ /* link into chain */
+ TAILQ_INSERT_TAIL(&rai->rai_rdnss, rdn, rd_next);
+ continue;
+getconfig_free_rdn:
+ while ((rdna = TAILQ_FIRST(&rdn->rd_list)) != NULL) {
+ TAILQ_REMOVE(&rdn->rd_list, rdna, ra_next);
+ free(rdna);
+ }
+ free(rdn);
+ }
+
+ for (i = -1; i < MAXDNSSLENT ; i++) {
+ struct dnssl *dns;
+ struct dnssl_addr *dnsa;
+ char *ap;
+ int c;
+
+ makeentry(entbuf, sizeof(entbuf), i, "dnssl");
+ addr = (char *)agetstr(entbuf, &bp);
+ if (addr == NULL)
+ continue;
+
+ ELM_MALLOC(dns, exit(1));
+
+ TAILQ_INIT(&dns->dn_list);
+
+ for (ap = addr; ap - addr < (ssize_t)strlen(addr); ap += c+1) {
+ c = strcspn(ap, ",");
+ strncpy(abuf, ap, c);
+ abuf[c] = '\0';
+ ELM_MALLOC(dnsa, goto getconfig_free_dns);
+ dnsa->da_len = dname_labelenc(dnsa->da_dom, abuf);
+ if (dnsa->da_len < 0) {
+ syslog(LOG_ERR, "Invalid dnssl entry: %s",
+ abuf);
+ goto getconfig_free_dns;
+ }
+ syslog(LOG_DEBUG, "<%s>: dnsa->da_len = %d", __func__,
+ dnsa->da_len);
+ TAILQ_INSERT_TAIL(&dns->dn_list, dnsa, da_next);
+ }
+
+ makeentry(entbuf, sizeof(entbuf), i, "dnsslltime");
+ MAYHAVE(val, entbuf, (rai->rai_maxinterval * 3 / 2));
+ if ((uint16_t)val < rai->rai_maxinterval ||
+ (uint16_t)val > rai->rai_maxinterval * 2) {
+ syslog(LOG_ERR, "%s (%" PRIu16 ") on %s is invalid "
+ "(must be between %d and %d)",
+ entbuf, val, ifi->ifi_ifname, rai->rai_maxinterval,
+ rai->rai_maxinterval * 2);
+ goto getconfig_free_dns;
+ }
+ dns->dn_ltime = val;
+
+ /* link into chain */
+ TAILQ_INSERT_TAIL(&rai->rai_dnssl, dns, dn_next);
+ continue;
+getconfig_free_dns:
+ while ((dnsa = TAILQ_FIRST(&dns->dn_list)) != NULL) {
+ TAILQ_REMOVE(&dns->dn_list, dnsa, da_next);
+ free(dnsa);
+ }
+ free(dns);
+ }
+ /* construct the sending packet */
+ make_packet(rai);
+
+ /*
+ * If an entry with the same ifindex exists, remove it first.
+ * Before the removal, RDNSS and DNSSL options with
+ * zero-lifetime will be sent.
+ */
+ switch (ifi->ifi_state) {
+ case IFI_STATE_UNCONFIGURED:
+ /* UNCONFIGURED -> TRANSITIVE */
+
+ error = sock_mc_join(&sock, ifi->ifi_ifindex);
+ if (error)
+ exit(1);
+
+ ifi->ifi_state = IFI_STATE_TRANSITIVE;
+ ifi->ifi_burstcount = MAX_INITIAL_RTR_ADVERTISEMENTS;
+ ifi->ifi_burstinterval = MAX_INITIAL_RTR_ADVERT_INTERVAL;
+
+ /* The same two rai mean initial burst */
+ ifi->ifi_rainfo = rai;
+ ifi->ifi_rainfo_trans = rai;
+ TAILQ_INSERT_TAIL(&railist, rai, rai_next);
+
+ if (ifi->ifi_ra_timer == NULL)
+ ifi->ifi_ra_timer = rtadvd_add_timer(ra_timeout,
+ ra_timer_update, ifi, ifi);
+ ra_timer_update(ifi, &ifi->ifi_ra_timer->rat_tm);
+ rtadvd_set_timer(&ifi->ifi_ra_timer->rat_tm,
+ ifi->ifi_ra_timer);
+
+ syslog(LOG_DEBUG,
+ "<%s> ifname=%s marked as TRANSITIVE (initial burst).",
+ __func__, ifi->ifi_ifname);
+ break;
+ case IFI_STATE_CONFIGURED:
+ /* CONFIGURED -> TRANSITIVE */
+ rai_old = ifi->ifi_rainfo;
+ if (rai_old == NULL) {
+ syslog(LOG_ERR,
+ "<%s> ifi_rainfo is NULL"
+ " in IFI_STATE_CONFIGURED.", __func__);
+ ifi = NULL;
+ break;
+ } else {
+ struct rdnss *rdn;
+ struct dnssl *dns;
+
+ rai_old->rai_lifetime = 0;
+ TAILQ_FOREACH(rdn, &rai_old->rai_rdnss, rd_next)
+ rdn->rd_ltime = 0;
+ TAILQ_FOREACH(dns, &rai_old->rai_dnssl, dn_next)
+ dns->dn_ltime = 0;
+
+ ifi->ifi_rainfo_trans = rai_old;
+ ifi->ifi_state = IFI_STATE_TRANSITIVE;
+ ifi->ifi_burstcount = MAX_FINAL_RTR_ADVERTISEMENTS;
+ ifi->ifi_burstinterval = MIN_DELAY_BETWEEN_RAS;
+
+ ra_timer_update(ifi, &ifi->ifi_ra_timer->rat_tm);
+ rtadvd_set_timer(&ifi->ifi_ra_timer->rat_tm,
+ ifi->ifi_ra_timer);
+
+ syslog(LOG_DEBUG,
+ "<%s> ifname=%s marked as TRANSITIVE"
+ " (transitional burst)",
+ __func__, ifi->ifi_ifname);
+ }
+ ifi->ifi_rainfo = rai;
+ TAILQ_INSERT_TAIL(&railist, rai, rai_next);
+ break;
+ case IFI_STATE_TRANSITIVE:
+ if (ifi->ifi_rainfo != NULL) {
+ if (ifi->ifi_rainfo == ifi->ifi_rainfo_trans) {
+ /* Reinitialize initial burst */
+ rm_rainfo(ifi->ifi_rainfo);
+ ifi->ifi_rainfo = rai;
+ ifi->ifi_rainfo_trans = rai;
+ ifi->ifi_burstcount =
+ MAX_INITIAL_RTR_ADVERTISEMENTS;
+ ifi->ifi_burstinterval =
+ MAX_INITIAL_RTR_ADVERT_INTERVAL;
+ } else {
+ /* Replace ifi_rainfo with the new one */
+ rm_rainfo(ifi->ifi_rainfo);
+ ifi->ifi_rainfo = rai;
+ }
+ TAILQ_INSERT_TAIL(&railist, rai, rai_next);
+
+ ra_timer_update(ifi, &ifi->ifi_ra_timer->rat_tm);
+ rtadvd_set_timer(&ifi->ifi_ra_timer->rat_tm,
+ ifi->ifi_ra_timer);
+ } else {
+ /* XXX: NOTREACHED. Being shut down. */
+ syslog(LOG_ERR,
+ "<%s> %s is shutting down. Skipped.",
+ __func__, ifi->ifi_ifname);
+ rm_rainfo(rai);
+
+ return (NULL);
+ }
+ break;
+ }
+
+ return (ifi);
+
+getconfig_free_rai:
+ free(rai);
+ return (NULL);
+}
+
+void
+get_prefix(struct rainfo *rai)
+{
+ struct ifaddrs *ifap, *ifa;
+ struct prefix *pfx;
+ struct in6_addr *a;
+ struct ifinfo *ifi;
+ char *p, *ep, *m, *lim;
+ char ntopbuf[INET6_ADDRSTRLEN];
+
+ if (getifaddrs(&ifap) < 0) {
+ syslog(LOG_ERR,
+ "<%s> can't get interface addresses",
+ __func__);
+ exit(1);
+ }
+ ifi = rai->rai_ifinfo;
+
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ int plen;
+
+ if (strcmp(ifa->ifa_name, ifi->ifi_ifname) != 0)
+ continue;
+ if (ifa->ifa_addr->sa_family != AF_INET6)
+ continue;
+ a = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
+ if (IN6_IS_ADDR_LINKLOCAL(a))
+ continue;
+
+ /* get prefix length */
+ m = (char *)&((struct sockaddr_in6 *)ifa->ifa_netmask)->sin6_addr;
+ lim = (char *)(ifa->ifa_netmask) + ifa->ifa_netmask->sa_len;
+ plen = prefixlen(m, lim);
+ if (plen <= 0 || plen > 128) {
+ syslog(LOG_ERR, "<%s> failed to get prefixlen "
+ "or prefix is invalid",
+ __func__);
+ exit(1);
+ }
+ if (plen == 128) /* XXX */
+ continue;
+ if (find_prefix(rai, a, plen)) {
+ /* ignore a duplicated prefix. */
+ continue;
+ }
+
+ /* allocate memory to store prefix info. */
+ ELM_MALLOC(pfx, exit(1));
+
+ /* set prefix, sweep bits outside of prefixlen */
+ pfx->pfx_prefixlen = plen;
+ memcpy(&pfx->pfx_prefix, a, sizeof(*a));
+ p = (char *)&pfx->pfx_prefix;
+ ep = (char *)(&pfx->pfx_prefix + 1);
+ while (m < lim && p < ep)
+ *p++ &= *m++;
+ while (p < ep)
+ *p++ = 0x00;
+ if (!inet_ntop(AF_INET6, &pfx->pfx_prefix, ntopbuf,
+ sizeof(ntopbuf))) {
+ syslog(LOG_ERR, "<%s> inet_ntop failed", __func__);
+ exit(1);
+ }
+ syslog(LOG_DEBUG,
+ "<%s> add %s/%d to prefix list on %s",
+ __func__, ntopbuf, pfx->pfx_prefixlen, ifi->ifi_ifname);
+
+ /* set other fields with protocol defaults */
+ pfx->pfx_validlifetime = DEF_ADVVALIDLIFETIME;
+ pfx->pfx_preflifetime = DEF_ADVPREFERREDLIFETIME;
+ pfx->pfx_onlinkflg = 1;
+ pfx->pfx_autoconfflg = 1;
+ pfx->pfx_origin = PREFIX_FROM_KERNEL;
+ pfx->pfx_rainfo = rai;
+
+ /* link into chain */
+ TAILQ_INSERT_TAIL(&rai->rai_prefix, pfx, pfx_next);
+
+ /* counter increment */
+ rai->rai_pfxs++;
+ }
+
+ freeifaddrs(ifap);
+}
+
+static void
+makeentry(char *buf, size_t len, int id, const char *string)
+{
+
+ if (id < 0)
+ strlcpy(buf, string, len);
+ else
+ snprintf(buf, len, "%s%d", string, id);
+}
+
+/*
+ * Add a prefix to the list of specified interface and reconstruct
+ * the outgoing packet.
+ * The prefix must not be in the list.
+ * XXX: other parameters of the prefix (e.g. lifetime) should be
+ * able to be specified.
+ */
+static void
+add_prefix(struct rainfo *rai, struct in6_prefixreq *ipr)
+{
+ struct prefix *pfx;
+ struct ifinfo *ifi;
+ char ntopbuf[INET6_ADDRSTRLEN];
+
+ ifi = rai->rai_ifinfo;
+ ELM_MALLOC(pfx, return);
+ pfx->pfx_prefix = ipr->ipr_prefix.sin6_addr;
+ pfx->pfx_prefixlen = ipr->ipr_plen;
+ pfx->pfx_validlifetime = ipr->ipr_vltime;
+ pfx->pfx_preflifetime = ipr->ipr_pltime;
+ pfx->pfx_onlinkflg = ipr->ipr_raf_onlink;
+ pfx->pfx_autoconfflg = ipr->ipr_raf_auto;
+ pfx->pfx_origin = PREFIX_FROM_DYNAMIC;
+ pfx->pfx_rainfo = rai;
+
+ TAILQ_INSERT_TAIL(&rai->rai_prefix, pfx, pfx_next);
+
+ syslog(LOG_DEBUG, "<%s> new prefix %s/%d was added on %s",
+ __func__,
+ inet_ntop(AF_INET6, &ipr->ipr_prefix.sin6_addr, ntopbuf,
+ sizeof(ntopbuf)), ipr->ipr_plen, ifi->ifi_ifname);
+
+ rai->rai_pfxs++;
+}
+
+/*
+ * Delete a prefix to the list of specified interface and reconstruct
+ * the outgoing packet.
+ * The prefix must be in the list.
+ */
+void
+delete_prefix(struct prefix *pfx)
+{
+ struct rainfo *rai;
+ struct ifinfo *ifi;
+ char ntopbuf[INET6_ADDRSTRLEN];
+
+ rai = pfx->pfx_rainfo;
+ ifi = rai->rai_ifinfo;
+ TAILQ_REMOVE(&rai->rai_prefix, pfx, pfx_next);
+ syslog(LOG_DEBUG, "<%s> prefix %s/%d was deleted on %s",
+ __func__,
+ inet_ntop(AF_INET6, &pfx->pfx_prefix, ntopbuf,
+ sizeof(ntopbuf)), pfx->pfx_prefixlen, ifi->ifi_ifname);
+ if (pfx->pfx_timer)
+ rtadvd_remove_timer(pfx->pfx_timer);
+ free(pfx);
+
+ rai->rai_pfxs--;
+}
+
+void
+invalidate_prefix(struct prefix *pfx)
+{
+ struct timespec timo;
+ struct rainfo *rai;
+ struct ifinfo *ifi;
+ char ntopbuf[INET6_ADDRSTRLEN];
+
+ rai = pfx->pfx_rainfo;
+ ifi = rai->rai_ifinfo;
+ if (pfx->pfx_timer) { /* sanity check */
+ syslog(LOG_ERR,
+ "<%s> assumption failure: timer already exists",
+ __func__);
+ exit(1);
+ }
+
+ syslog(LOG_DEBUG, "<%s> prefix %s/%d was invalidated on %s, "
+ "will expire in %ld seconds", __func__,
+ inet_ntop(AF_INET6, &pfx->pfx_prefix, ntopbuf, sizeof(ntopbuf)),
+ pfx->pfx_prefixlen, ifi->ifi_ifname, (long)prefix_timo);
+
+ /* set the expiration timer */
+ pfx->pfx_timer = rtadvd_add_timer(prefix_timeout, NULL, pfx, NULL);
+ if (pfx->pfx_timer == NULL) {
+ syslog(LOG_ERR, "<%s> failed to add a timer for a prefix. "
+ "remove the prefix", __func__);
+ delete_prefix(pfx);
+ }
+ timo.tv_sec = prefix_timo;
+ timo.tv_nsec = 0;
+ rtadvd_set_timer(&timo, pfx->pfx_timer);
+}
+
+static struct rtadvd_timer *
+prefix_timeout(void *arg)
+{
+
+ delete_prefix((struct prefix *)arg);
+
+ return (NULL);
+}
+
+void
+update_prefix(struct prefix *pfx)
+{
+ struct rainfo *rai;
+ struct ifinfo *ifi;
+ char ntopbuf[INET6_ADDRSTRLEN];
+
+ rai = pfx->pfx_rainfo;
+ ifi = rai->rai_ifinfo;
+ if (pfx->pfx_timer == NULL) { /* sanity check */
+ syslog(LOG_ERR,
+ "<%s> assumption failure: timer does not exist",
+ __func__);
+ exit(1);
+ }
+
+ syslog(LOG_DEBUG, "<%s> prefix %s/%d was re-enabled on %s",
+ __func__, inet_ntop(AF_INET6, &pfx->pfx_prefix, ntopbuf,
+ sizeof(ntopbuf)), pfx->pfx_prefixlen, ifi->ifi_ifname);
+
+ /* stop the expiration timer */
+ rtadvd_remove_timer(pfx->pfx_timer);
+ pfx->pfx_timer = NULL;
+}
+
+/*
+ * Try to get an in6_prefixreq contents for a prefix which matches
+ * ipr->ipr_prefix and ipr->ipr_plen and belongs to
+ * the interface whose name is ipr->ipr_name[].
+ */
+static int
+init_prefix(struct in6_prefixreq *ipr)
+{
+#if 0
+ int s;
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
+ syslog(LOG_ERR, "<%s> socket: %s", __func__,
+ strerror(errno));
+ exit(1);
+ }
+
+ if (ioctl(s, SIOCGIFPREFIX_IN6, (caddr_t)ipr) < 0) {
+ syslog(LOG_INFO, "<%s> ioctl:SIOCGIFPREFIX %s", __func__,
+ strerror(errno));
+
+ ipr->ipr_vltime = DEF_ADVVALIDLIFETIME;
+ ipr->ipr_pltime = DEF_ADVPREFERREDLIFETIME;
+ ipr->ipr_raf_onlink = 1;
+ ipr->ipr_raf_auto = 1;
+ /* omit other field initialization */
+ }
+ else if (ipr->ipr_origin < PR_ORIG_RR) {
+ char ntopbuf[INET6_ADDRSTRLEN];
+
+ syslog(LOG_WARNING, "<%s> Added prefix(%s)'s origin %d is"
+ "lower than PR_ORIG_RR(router renumbering)."
+ "This should not happen if I am router", __func__,
+ inet_ntop(AF_INET6, &ipr->ipr_prefix.sin6_addr, ntopbuf,
+ sizeof(ntopbuf)), ipr->ipr_origin);
+ close(s);
+ return (1);
+ }
+
+ close(s);
+ return (0);
+#else
+ ipr->ipr_vltime = DEF_ADVVALIDLIFETIME;
+ ipr->ipr_pltime = DEF_ADVPREFERREDLIFETIME;
+ ipr->ipr_raf_onlink = 1;
+ ipr->ipr_raf_auto = 1;
+ return (0);
+#endif
+}
+
+void
+make_prefix(struct rainfo *rai, int ifindex, struct in6_addr *addr, int plen)
+{
+ struct in6_prefixreq ipr;
+
+ memset(&ipr, 0, sizeof(ipr));
+ if (if_indextoname(ifindex, ipr.ipr_name) == NULL) {
+ syslog(LOG_ERR, "<%s> Prefix added interface No.%d doesn't "
+ "exist. This should not happen! %s", __func__,
+ ifindex, strerror(errno));
+ exit(1);
+ }
+ ipr.ipr_prefix.sin6_len = sizeof(ipr.ipr_prefix);
+ ipr.ipr_prefix.sin6_family = AF_INET6;
+ ipr.ipr_prefix.sin6_addr = *addr;
+ ipr.ipr_plen = plen;
+
+ if (init_prefix(&ipr))
+ return; /* init failed by some error */
+ add_prefix(rai, &ipr);
+}
+
+void
+make_packet(struct rainfo *rai)
+{
+ size_t packlen, lladdroptlen = 0;
+ char *buf;
+ struct nd_router_advert *ra;
+ struct nd_opt_prefix_info *ndopt_pi;
+ struct nd_opt_mtu *ndopt_mtu;
+ struct nd_opt_route_info *ndopt_rti;
+ struct rtinfo *rti;
+ struct nd_opt_rdnss *ndopt_rdnss;
+ struct rdnss *rdn;
+ struct nd_opt_dnssl *ndopt_dnssl;
+ struct dnssl *dns;
+ size_t len;
+ struct prefix *pfx;
+ struct ifinfo *ifi;
+
+ ifi = rai->rai_ifinfo;
+ /* calculate total length */
+ packlen = sizeof(struct nd_router_advert);
+ if (rai->rai_advlinkopt) {
+ if ((lladdroptlen = lladdropt_length(&ifi->ifi_sdl)) == 0) {
+ syslog(LOG_INFO,
+ "<%s> link-layer address option has"
+ " null length on %s. Treat as not included.",
+ __func__, ifi->ifi_ifname);
+ rai->rai_advlinkopt = 0;
+ }
+ packlen += lladdroptlen;
+ }
+ if (rai->rai_pfxs)
+ packlen += sizeof(struct nd_opt_prefix_info) * rai->rai_pfxs;
+ if (rai->rai_linkmtu)
+ packlen += sizeof(struct nd_opt_mtu);
+
+ TAILQ_FOREACH(rti, &rai->rai_route, rti_next)
+ packlen += sizeof(struct nd_opt_route_info) +
+ ((rti->rti_prefixlen + 0x3f) >> 6) * 8;
+
+ TAILQ_FOREACH(rdn, &rai->rai_rdnss, rd_next) {
+ struct rdnss_addr *rdna;
+
+ packlen += sizeof(struct nd_opt_rdnss);
+ TAILQ_FOREACH(rdna, &rdn->rd_list, ra_next)
+ packlen += sizeof(rdna->ra_dns);
+ }
+ TAILQ_FOREACH(dns, &rai->rai_dnssl, dn_next) {
+ struct dnssl_addr *dnsa;
+
+ packlen += sizeof(struct nd_opt_dnssl);
+ len = 0;
+ TAILQ_FOREACH(dnsa, &dns->dn_list, da_next)
+ len += dnsa->da_len;
+
+ /* A zero octet and 8 octet boundary */
+ len++;
+ len += (len % 8) ? 8 - len % 8 : 0;
+
+ packlen += len;
+ }
+ /* allocate memory for the packet */
+ if ((buf = malloc(packlen)) == NULL) {
+ syslog(LOG_ERR,
+ "<%s> can't get enough memory for an RA packet",
+ __func__);
+ exit(1);
+ }
+ memset(buf, 0, packlen);
+ if (rai->rai_ra_data) /* Free old data if any. */
+ free(rai->rai_ra_data);
+ rai->rai_ra_data = buf;
+ /* XXX: what if packlen > 576? */
+ rai->rai_ra_datalen = packlen;
+
+ /*
+ * construct the packet
+ */
+ ra = (struct nd_router_advert *)buf;
+ ra->nd_ra_type = ND_ROUTER_ADVERT;
+ ra->nd_ra_code = 0;
+ ra->nd_ra_cksum = 0;
+ ra->nd_ra_curhoplimit = (uint8_t)(0xff & rai->rai_hoplimit);
+ ra->nd_ra_flags_reserved = 0; /* just in case */
+ /*
+ * XXX: the router preference field, which is a 2-bit field, should be
+ * initialized before other fields.
+ */
+ ra->nd_ra_flags_reserved = 0xff & rai->rai_rtpref;
+ ra->nd_ra_flags_reserved |=
+ rai->rai_managedflg ? ND_RA_FLAG_MANAGED : 0;
+ ra->nd_ra_flags_reserved |=
+ rai->rai_otherflg ? ND_RA_FLAG_OTHER : 0;
+ ra->nd_ra_router_lifetime = htons(rai->rai_lifetime);
+ ra->nd_ra_reachable = htonl(rai->rai_reachabletime);
+ ra->nd_ra_retransmit = htonl(rai->rai_retranstimer);
+ buf += sizeof(*ra);
+
+ if (rai->rai_advlinkopt) {
+ lladdropt_fill(&ifi->ifi_sdl, (struct nd_opt_hdr *)buf);
+ buf += lladdroptlen;
+ }
+
+ if (rai->rai_linkmtu) {
+ ndopt_mtu = (struct nd_opt_mtu *)buf;
+ ndopt_mtu->nd_opt_mtu_type = ND_OPT_MTU;
+ ndopt_mtu->nd_opt_mtu_len = 1;
+ ndopt_mtu->nd_opt_mtu_reserved = 0;
+ ndopt_mtu->nd_opt_mtu_mtu = htonl(rai->rai_linkmtu);
+ buf += sizeof(struct nd_opt_mtu);
+ }
+
+ TAILQ_FOREACH(pfx, &rai->rai_prefix, pfx_next) {
+ uint32_t vltime, pltime;
+ struct timespec now;
+
+ ndopt_pi = (struct nd_opt_prefix_info *)buf;
+ ndopt_pi->nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION;
+ ndopt_pi->nd_opt_pi_len = 4;
+ ndopt_pi->nd_opt_pi_prefix_len = pfx->pfx_prefixlen;
+ ndopt_pi->nd_opt_pi_flags_reserved = 0;
+ if (pfx->pfx_onlinkflg)
+ ndopt_pi->nd_opt_pi_flags_reserved |=
+ ND_OPT_PI_FLAG_ONLINK;
+ if (pfx->pfx_autoconfflg)
+ ndopt_pi->nd_opt_pi_flags_reserved |=
+ ND_OPT_PI_FLAG_AUTO;
+ if (pfx->pfx_timer)
+ vltime = 0;
+ else {
+ if (pfx->pfx_vltimeexpire || pfx->pfx_pltimeexpire)
+ clock_gettime(CLOCK_MONOTONIC_FAST, &now);
+ if (pfx->pfx_vltimeexpire == 0)
+ vltime = pfx->pfx_validlifetime;
+ else
+ vltime = ((time_t)pfx->pfx_vltimeexpire > now.tv_sec) ?
+ pfx->pfx_vltimeexpire - now.tv_sec : 0;
+ }
+ if (pfx->pfx_timer)
+ pltime = 0;
+ else {
+ if (pfx->pfx_pltimeexpire == 0)
+ pltime = pfx->pfx_preflifetime;
+ else
+ pltime = ((time_t)pfx->pfx_pltimeexpire > now.tv_sec) ?
+ pfx->pfx_pltimeexpire - now.tv_sec : 0;
+ }
+ if (vltime < pltime) {
+ /*
+ * this can happen if vltime is decrement but pltime
+ * is not.
+ */
+ pltime = vltime;
+ }
+ ndopt_pi->nd_opt_pi_valid_time = htonl(vltime);
+ ndopt_pi->nd_opt_pi_preferred_time = htonl(pltime);
+ ndopt_pi->nd_opt_pi_reserved2 = 0;
+ ndopt_pi->nd_opt_pi_prefix = pfx->pfx_prefix;
+
+ buf += sizeof(struct nd_opt_prefix_info);
+ }
+
+ TAILQ_FOREACH(rti, &rai->rai_route, rti_next) {
+ uint8_t psize = (rti->rti_prefixlen + 0x3f) >> 6;
+
+ ndopt_rti = (struct nd_opt_route_info *)buf;
+ ndopt_rti->nd_opt_rti_type = ND_OPT_ROUTE_INFO;
+ ndopt_rti->nd_opt_rti_len = 1 + psize;
+ ndopt_rti->nd_opt_rti_prefixlen = rti->rti_prefixlen;
+ ndopt_rti->nd_opt_rti_flags = 0xff & rti->rti_rtpref;
+ ndopt_rti->nd_opt_rti_lifetime = htonl(rti->rti_ltime);
+ memcpy(ndopt_rti + 1, &rti->rti_prefix, psize * 8);
+ buf += sizeof(struct nd_opt_route_info) + psize * 8;
+ }
+
+ TAILQ_FOREACH(rdn, &rai->rai_rdnss, rd_next) {
+ struct rdnss_addr *rdna;
+
+ ndopt_rdnss = (struct nd_opt_rdnss *)buf;
+ ndopt_rdnss->nd_opt_rdnss_type = ND_OPT_RDNSS;
+ ndopt_rdnss->nd_opt_rdnss_len = 0;
+ ndopt_rdnss->nd_opt_rdnss_reserved = 0;
+ ndopt_rdnss->nd_opt_rdnss_lifetime = htonl(rdn->rd_ltime);
+ buf += sizeof(struct nd_opt_rdnss);
+
+ TAILQ_FOREACH(rdna, &rdn->rd_list, ra_next) {
+ memcpy(buf, &rdna->ra_dns, sizeof(rdna->ra_dns));
+ buf += sizeof(rdna->ra_dns);
+ }
+ /* Length field should be in 8 octets */
+ ndopt_rdnss->nd_opt_rdnss_len = (buf - (char *)ndopt_rdnss) / 8;
+
+ syslog(LOG_DEBUG, "<%s>: nd_opt_dnss_len = %d", __func__,
+ ndopt_rdnss->nd_opt_rdnss_len);
+ }
+
+ TAILQ_FOREACH(dns, &rai->rai_dnssl, dn_next) {
+ struct dnssl_addr *dnsa;
+
+ ndopt_dnssl = (struct nd_opt_dnssl *)buf;
+ ndopt_dnssl->nd_opt_dnssl_type = ND_OPT_DNSSL;
+ ndopt_dnssl->nd_opt_dnssl_len = 0;
+ ndopt_dnssl->nd_opt_dnssl_reserved = 0;
+ ndopt_dnssl->nd_opt_dnssl_lifetime = htonl(dns->dn_ltime);
+ buf += sizeof(*ndopt_dnssl);
+
+ TAILQ_FOREACH(dnsa, &dns->dn_list, da_next) {
+ memcpy(buf, dnsa->da_dom, dnsa->da_len);
+ buf += dnsa->da_len;
+ }
+
+ /* A zero octet after encoded DNS server list. */
+ *buf++ = '\0';
+
+ /* Padding to next 8 octets boundary */
+ len = buf - (char *)ndopt_dnssl;
+ len += (len % 8) ? 8 - len % 8 : 0;
+ buf = (char *)ndopt_dnssl + len;
+
+ /* Length field must be in 8 octets */
+ ndopt_dnssl->nd_opt_dnssl_len = len / 8;
+
+ syslog(LOG_DEBUG, "<%s>: nd_opt_dnssl_len = %d", __func__,
+ ndopt_dnssl->nd_opt_dnssl_len);
+ }
+ return;
+}
diff --git a/usr.sbin/rtadvd/config.h b/usr.sbin/rtadvd/config.h
new file mode 100644
index 0000000..219390b
--- /dev/null
+++ b/usr.sbin/rtadvd/config.h
@@ -0,0 +1,53 @@
+/* $FreeBSD$ */
+/* $KAME: config.h,v 1.8 2003/06/17 08:26:22 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.
+ */
+
+extern struct ifinfo *getconfig(struct ifinfo *);
+extern int rm_ifinfo(struct ifinfo *);
+extern int rm_ifinfo_index(int);
+extern int rm_rainfo(struct rainfo *);
+extern int loadconfig_ifname(char *);
+extern int loadconfig_index(int);
+extern void delete_prefix(struct prefix *);
+extern void invalidate_prefix(struct prefix *);
+extern void update_prefix(struct prefix *);
+extern void make_prefix(struct rainfo *, int, struct in6_addr *, int);
+extern void make_packet(struct rainfo *);
+extern void get_prefix(struct rainfo *);
+
+/*
+ * it is highly unlikely to have 100 prefix information options,
+ * so it should be okay to limit it
+ */
+#define MAXPREFIX 100
+#define MAXROUTE 100
+#define MAXRDNSSENT 100
+#define MAXDNSSLENT 100
diff --git a/usr.sbin/rtadvd/control.c b/usr.sbin/rtadvd/control.c
new file mode 100644
index 0000000..5e4a68b
--- /dev/null
+++ b/usr.sbin/rtadvd/control.c
@@ -0,0 +1,492 @@
+/*-
+ * Copyright (C) 2011 Hiroki Sato <hrs@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 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/queue.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <sys/uio.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <netinet/in.h>
+#include <netinet/icmp6.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <poll.h>
+#include <signal.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syslog.h>
+
+#include "rtadvd.h"
+#include "if.h"
+#include "pathnames.h"
+#include "control.h"
+
+#define CM_RECV_TIMEOUT 30
+
+int
+cm_recv(int fd, char *buf)
+{
+ int n;
+ struct ctrl_msg_hdr *cm;
+ char *msg;
+ struct pollfd pfds[1];
+ int i;
+
+ syslog(LOG_DEBUG, "<%s> enter, fd=%d", __func__, fd);
+
+ memset(buf, 0, CM_MSG_MAXLEN);
+ cm = (struct ctrl_msg_hdr *)buf;
+ msg = (char *)buf + sizeof(*cm);
+
+ pfds[0].fd = fd;
+ pfds[0].events = POLLIN;
+
+ for (;;) {
+ i = poll(pfds, sizeof(pfds)/sizeof(pfds[0]),
+ CM_RECV_TIMEOUT);
+
+ if (i == 0)
+ continue;
+
+ if (i < 0) {
+ syslog(LOG_ERR, "<%s> poll error: %s",
+ __func__, strerror(errno));
+ continue;
+ }
+
+ if (pfds[0].revents & POLLIN) {
+ n = read(fd, cm, sizeof(*cm));
+ if (n < 0 && errno == EAGAIN) {
+ syslog(LOG_DEBUG,
+ "<%s> waiting...", __func__);
+ continue;
+ }
+ break;
+ }
+ }
+
+ if (n != sizeof(*cm)) {
+ syslog(LOG_WARNING,
+ "<%s> received a too small message.", __func__);
+ goto cm_recv_err;
+ }
+ if (cm->cm_len > CM_MSG_MAXLEN) {
+ syslog(LOG_WARNING,
+ "<%s> received a too large message.", __func__);
+ goto cm_recv_err;
+ }
+ if (cm->cm_version != CM_VERSION) {
+ syslog(LOG_WARNING,
+ "<%s> version mismatch", __func__);
+ goto cm_recv_err;
+ }
+ if (cm->cm_type >= CM_TYPE_MAX) {
+ syslog(LOG_WARNING,
+ "<%s> invalid msg type.", __func__);
+ goto cm_recv_err;
+ }
+
+ syslog(LOG_DEBUG,
+ "<%s> ctrl msg received: type=%d", __func__,
+ cm->cm_type);
+
+ if (cm->cm_len > sizeof(cm)) {
+ int msglen = cm->cm_len - sizeof(*cm);
+
+ syslog(LOG_DEBUG,
+ "<%s> ctrl msg has payload (len=%d)", __func__,
+ msglen);
+
+ for (;;) {
+ i = poll(pfds, sizeof(pfds)/sizeof(pfds[0]),
+ CM_RECV_TIMEOUT);
+
+ if (i == 0)
+ continue;
+
+ if (i < 0) {
+ syslog(LOG_ERR, "<%s> poll error: %s",
+ __func__, strerror(errno));
+ continue;
+ }
+
+ if (pfds[0].revents & POLLIN) {
+ n = read(fd, msg, msglen);
+ if (n < 0 && errno == EAGAIN) {
+ syslog(LOG_DEBUG,
+ "<%s> waiting...", __func__);
+ continue;
+ }
+ }
+ break;
+ }
+ if (n != msglen) {
+ syslog(LOG_WARNING,
+ "<%s> payload size mismatch.", __func__);
+ goto cm_recv_err;
+ }
+ buf[CM_MSG_MAXLEN - 1] = '\0';
+ }
+
+ return (0);
+
+cm_recv_err:
+ close(fd);
+ return (-1);
+}
+
+int
+cm_send(int fd, char *buf)
+{
+ struct iovec iov[2];
+ int iovcnt;
+ ssize_t len;
+ ssize_t iov_len_total;
+ struct ctrl_msg_hdr *cm;
+ char *msg;
+
+ cm = (struct ctrl_msg_hdr *)buf;
+ msg = (char *)buf + sizeof(*cm);
+
+ iovcnt = 1;
+ iov[0].iov_base = cm;
+ iov[0].iov_len = sizeof(*cm);
+ iov_len_total = iov[0].iov_len;
+ if (cm->cm_len > sizeof(*cm)) {
+ iovcnt++;
+ iov[1].iov_base = msg;
+ iov[1].iov_len = cm->cm_len - iov[0].iov_len;
+ iov_len_total += iov[1].iov_len;
+ }
+
+ syslog(LOG_DEBUG,
+ "<%s> ctrl msg send: type=%d, count=%d, total_len=%zd", __func__,
+ cm->cm_type, iovcnt, iov_len_total);
+
+ len = writev(fd, iov, iovcnt);
+ syslog(LOG_DEBUG,
+ "<%s> ctrl msg send: length=%zd", __func__, len);
+
+ if (len == -1) {
+ syslog(LOG_DEBUG,
+ "<%s> write failed: (%d)%s", __func__, errno,
+ strerror(errno));
+ close(fd);
+ return (-1);
+ }
+
+ syslog(LOG_DEBUG,
+ "<%s> write length = %zd (actual)", __func__, len);
+ syslog(LOG_DEBUG,
+ "<%s> write length = %zd (expected)", __func__, iov_len_total);
+
+ if (len != iov_len_total) {
+ close(fd);
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+csock_accept(struct sockinfo *s)
+{
+ struct sockaddr_un sun;
+ int flags;
+ int fd;
+
+ sun.sun_len = sizeof(sun);
+ if ((fd = accept(s->si_fd, (struct sockaddr *)&sun,
+ (socklen_t *)&sun.sun_len)) == -1) {
+ if (errno != EWOULDBLOCK && errno != EINTR)
+ syslog(LOG_WARNING, "<%s> accept ", __func__);
+ syslog(LOG_WARNING, "<%s> Xaccept: %s", __func__, strerror(errno));
+ return (-1);
+ }
+ if ((flags = fcntl(fd, F_GETFL, 0)) == -1) {
+ syslog(LOG_WARNING, "<%s> fcntl F_GETFL", __func__);
+ close(s->si_fd);
+ return (-1);
+ }
+ if ((flags = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1) {
+ syslog(LOG_WARNING, "<%s> fcntl F_SETFL", __func__);
+ return (-1);
+ }
+ syslog(LOG_DEBUG, "<%s> accept connfd=%d, listenfd=%d", __func__,
+ fd, s->si_fd);
+
+ return (fd);
+}
+
+int
+csock_close(struct sockinfo *s)
+{
+ close(s->si_fd);
+ unlink(s->si_name);
+ syslog(LOG_DEBUG, "<%s> remove %s", __func__, s->si_name);
+ return (0);
+}
+
+int
+csock_listen(struct sockinfo *s)
+{
+ if (s->si_fd == -1) {
+ syslog(LOG_ERR, "<%s> listen failed", __func__);
+ return (-1);
+ }
+ if (listen(s->si_fd, SOCK_BACKLOG) == -1) {
+ syslog(LOG_ERR, "<%s> listen failed", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+csock_open(struct sockinfo *s, mode_t mode)
+{
+ int flags;
+ struct sockaddr_un sun;
+ mode_t old_umask;
+
+ if (s == NULL) {
+ syslog(LOG_ERR, "<%s> internal error.", __func__);
+ exit(1);
+ }
+ if (s->si_name == NULL)
+ s->si_name = _PATH_CTRL_SOCK;
+
+ if ((s->si_fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) {
+ syslog(LOG_ERR,
+ "<%s> cannot open control socket", __func__);
+ return (-1);
+ }
+ memset(&sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ sun.sun_len = sizeof(sun);
+ strlcpy(sun.sun_path, s->si_name, sizeof(sun.sun_path));
+
+ if (unlink(s->si_name) == -1)
+ if (errno != ENOENT) {
+ syslog(LOG_ERR,
+ "<%s> unlink %s", __func__, s->si_name);
+ close(s->si_fd);
+ return (-1);
+ }
+ old_umask = umask(S_IXUSR|S_IXGRP|S_IXOTH);
+ if (bind(s->si_fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
+ syslog(LOG_ERR,
+ "<%s> bind failed: %s", __func__, s->si_name);
+ close(s->si_fd);
+ umask(old_umask);
+ return (-1);
+ }
+ umask(old_umask);
+ if (chmod(s->si_name, mode) == -1) {
+ syslog(LOG_ERR,
+ "<%s> chmod failed: %s", __func__, s->si_name);
+ goto csock_open_err;
+ }
+ if ((flags = fcntl(s->si_fd, F_GETFL, 0)) == -1) {
+ syslog(LOG_ERR,
+ "<%s> fcntl F_GETFL failed: %s", __func__, s->si_name);
+ goto csock_open_err;
+ }
+ if ((flags = fcntl(s->si_fd, F_SETFL, flags | O_NONBLOCK)) == -1) {
+ syslog(LOG_ERR,
+ "<%s> fcntl F_SETFL failed: %s", __func__, s->si_name);
+ goto csock_open_err;
+ }
+
+ return (s->si_fd);
+
+csock_open_err:
+ close(s->si_fd);
+ unlink(s->si_name);
+ return (-1);
+}
+
+struct ctrl_msg_pl *
+cm_bin2pl(char *str, struct ctrl_msg_pl *cp)
+{
+ size_t len;
+ size_t *lenp;
+ char *p;
+
+ memset(cp, 0, sizeof(*cp));
+
+ p = str;
+
+ lenp = (size_t *)p;
+ len = *lenp++;
+ p = (char *)lenp;
+ syslog(LOG_DEBUG, "<%s> len(ifname) = %zu", __func__, len);
+ if (len > 0) {
+ cp->cp_ifname = malloc(len + 1);
+ if (cp->cp_ifname == NULL) {
+ syslog(LOG_ERR, "<%s> malloc", __func__);
+ exit(1);
+ }
+ memcpy(cp->cp_ifname, p, len);
+ cp->cp_ifname[len] = '\0';
+ p += len;
+ }
+
+ lenp = (size_t *)p;
+ len = *lenp++;
+ p = (char *)lenp;
+ syslog(LOG_DEBUG, "<%s> len(key) = %zu", __func__, len);
+ if (len > 0) {
+ cp->cp_key = malloc(len + 1);
+ if (cp->cp_key == NULL) {
+ syslog(LOG_ERR, "<%s> malloc", __func__);
+ exit(1);
+ }
+ memcpy(cp->cp_key, p, len);
+ cp->cp_key[len] = '\0';
+ p += len;
+ }
+
+ lenp = (size_t *)p;
+ len = *lenp++;
+ p = (char *)lenp;
+ syslog(LOG_DEBUG, "<%s> len(val) = %zu", __func__, len);
+ if (len > 0) {
+ cp->cp_val = malloc(len + 1);
+ if (cp->cp_val == NULL) {
+ syslog(LOG_ERR, "<%s> malloc", __func__);
+ exit(1);
+ }
+ memcpy(cp->cp_val, p, len);
+ cp->cp_val[len] = '\0';
+ cp->cp_val_len = len;
+ } else
+ cp->cp_val_len = 0;
+
+ return (cp);
+}
+
+size_t
+cm_pl2bin(char *str, struct ctrl_msg_pl *cp)
+{
+ size_t len;
+ size_t *lenp;
+ char *p;
+ struct ctrl_msg_hdr *cm;
+
+ len = sizeof(size_t);
+ if (cp->cp_ifname != NULL)
+ len += strlen(cp->cp_ifname);
+ len += sizeof(size_t);
+ if (cp->cp_key != NULL)
+ len += strlen(cp->cp_key);
+ len += sizeof(size_t);
+ if (cp->cp_val != NULL && cp->cp_val_len > 0)
+ len += cp->cp_val_len;
+
+ if (len > CM_MSG_MAXLEN - sizeof(*cm)) {
+ syslog(LOG_DEBUG, "<%s> msg too long (len=%zu)",
+ __func__, len);
+ return (0);
+ }
+ syslog(LOG_DEBUG, "<%s> msglen=%zu", __func__, len);
+ memset(str, 0, len);
+ p = str;
+ lenp = (size_t *)p;
+
+ if (cp->cp_ifname != NULL) {
+ *lenp++ = strlen(cp->cp_ifname);
+ p = (char *)lenp;
+ memcpy(p, cp->cp_ifname, strlen(cp->cp_ifname));
+ p += strlen(cp->cp_ifname);
+ } else {
+ *lenp++ = '\0';
+ p = (char *)lenp;
+ }
+
+ lenp = (size_t *)p;
+ if (cp->cp_key != NULL) {
+ *lenp++ = strlen(cp->cp_key);
+ p = (char *)lenp;
+ memcpy(p, cp->cp_key, strlen(cp->cp_key));
+ p += strlen(cp->cp_key);
+ } else {
+ *lenp++ = '\0';
+ p = (char *)lenp;
+ }
+
+ lenp = (size_t *)p;
+ if (cp->cp_val != NULL && cp->cp_val_len > 0) {
+ *lenp++ = cp->cp_val_len;
+ p = (char *)lenp;
+ memcpy(p, cp->cp_val, cp->cp_val_len);
+ p += cp->cp_val_len;
+ } else {
+ *lenp++ = '\0';
+ p = (char *)lenp;
+ }
+
+ return (len);
+}
+
+size_t
+cm_str2bin(char *bin, void *str, size_t len)
+{
+ struct ctrl_msg_hdr *cm;
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ if (len > CM_MSG_MAXLEN - sizeof(*cm)) {
+ syslog(LOG_DEBUG, "<%s> msg too long (len=%zu)",
+ __func__, len);
+ return (0);
+ }
+ syslog(LOG_DEBUG, "<%s> msglen=%zu", __func__, len);
+ memcpy(bin, (char *)str, len);
+
+ return (len);
+}
+
+void *
+cm_bin2str(char *bin, void *str, size_t len)
+{
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ memcpy((char *)str, bin, len);
+
+ return (str);
+}
diff --git a/usr.sbin/rtadvd/control.h b/usr.sbin/rtadvd/control.h
new file mode 100644
index 0000000..2168302
--- /dev/null
+++ b/usr.sbin/rtadvd/control.h
@@ -0,0 +1,74 @@
+/*-
+ * Copyright (C) 2011 Hiroki Sato <hrs@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 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$
+ *
+ */
+
+#define SOCK_BACKLOG 5
+
+#define CM_MSG_MAXLEN 8192
+#define CM_VERSION 1
+#define CM_VERSION_STR "1.0"
+
+#define CM_TYPE_EOM 0
+#define CM_TYPE_ACK 1
+#define CM_TYPE_ERR 2
+#define CM_TYPE_NUL 3
+#define CM_TYPE_REQ_SET_PROP 4
+#define CM_TYPE_REQ_GET_PROP 5
+#define CM_TYPE_MAX 6
+
+#define CM_STATE_EOM 0
+#define CM_STATE_INIT 1
+#define CM_STATE_MSG_DISPATCH 2
+#define CM_STATE_MSG_RECV 3
+#define CM_STATE_ACK_WAIT 4
+
+struct ctrl_msg_hdr {
+ int cm_version;
+ size_t cm_len;
+ int cm_type;
+};
+
+struct ctrl_msg_pl {
+ char *cp_ifname;
+ char *cp_key;
+
+ size_t cp_val_len;
+ char *cp_val;
+};
+
+int csock_open(struct sockinfo *, mode_t);
+int csock_close(struct sockinfo *);
+int csock_listen(struct sockinfo *);
+int csock_accept(struct sockinfo *);
+int cm_send(int, char *);
+int cm_recv(int, char *);
+
+size_t cm_pl2bin(char *, struct ctrl_msg_pl *);
+struct ctrl_msg_pl *cm_bin2pl(char *, struct ctrl_msg_pl *);
+size_t cm_str2bin(char *, void *, size_t);
+void *cm_bin2str(char *, void *, size_t);
diff --git a/usr.sbin/rtadvd/control_client.c b/usr.sbin/rtadvd/control_client.c
new file mode 100644
index 0000000..33efe37
--- /dev/null
+++ b/usr.sbin/rtadvd/control_client.c
@@ -0,0 +1,131 @@
+/*-
+ * Copyright (C) 2011 Hiroki Sato <hrs@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 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/queue.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <sys/uio.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <netinet/in.h>
+#include <netinet/icmp6.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syslog.h>
+
+#include "pathnames.h"
+#include "rtadvd.h"
+#include "if.h"
+#include "control.h"
+#include "control_client.h"
+
+int
+cm_handler_client(int fd, int state, char *buf_orig)
+{
+ char buf[CM_MSG_MAXLEN];
+ struct ctrl_msg_hdr *cm;
+ struct ctrl_msg_hdr *cm_orig;
+ int error;
+ char *msg;
+ char *msg_orig;
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ memset(buf, 0, sizeof(buf));
+ cm = (struct ctrl_msg_hdr *)buf;
+ cm_orig = (struct ctrl_msg_hdr *)buf_orig;
+ msg = (char *)buf + sizeof(*cm);
+ msg_orig = (char *)buf_orig + sizeof(*cm_orig);
+
+ if (cm_orig->cm_len > CM_MSG_MAXLEN) {
+ syslog(LOG_DEBUG, "<%s> msg too long", __func__);
+ close(fd);
+ return (-1);
+ }
+ cm->cm_type = cm_orig->cm_type;
+ if (cm_orig->cm_len > sizeof(*cm_orig)) {
+ memcpy(msg, msg_orig, cm_orig->cm_len - sizeof(*cm));
+ cm->cm_len = cm_orig->cm_len;
+ }
+ while (state != CM_STATE_EOM) {
+ syslog(LOG_DEBUG, "<%s> state = %d", __func__, state);
+
+ switch (state) {
+ case CM_STATE_INIT:
+ state = CM_STATE_EOM;
+ break;
+ case CM_STATE_MSG_DISPATCH:
+ cm->cm_version = CM_VERSION;
+ error = cm_send(fd, buf);
+ if (error)
+ syslog(LOG_WARNING,
+ "<%s> cm_send()", __func__);
+ state = CM_STATE_ACK_WAIT;
+ break;
+ case CM_STATE_ACK_WAIT:
+ error = cm_recv(fd, buf);
+ if (error) {
+ syslog(LOG_ERR,
+ "<%s> cm_recv()", __func__);
+ close(fd);
+ return (-1);
+ }
+ switch (cm->cm_type) {
+ case CM_TYPE_ACK:
+ syslog(LOG_DEBUG,
+ "<%s> CM_TYPE_ACK", __func__);
+ break;
+ case CM_TYPE_ERR:
+ syslog(LOG_DEBUG,
+ "<%s> CM_TYPE_ERR", __func__);
+ close(fd);
+ return (-1);
+ default:
+ syslog(LOG_DEBUG,
+ "<%s> unknown status", __func__);
+ close(fd);
+ return (-1);
+ }
+ memcpy(buf_orig, buf, cm->cm_len);
+ state = CM_STATE_EOM;
+ break;
+ }
+ }
+ close(fd);
+ return (0);
+}
diff --git a/usr.sbin/rtadvd/control_client.h b/usr.sbin/rtadvd/control_client.h
new file mode 100644
index 0000000..2f50f17
--- /dev/null
+++ b/usr.sbin/rtadvd/control_client.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2011 Hiroki Sato <hrs@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 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$
+ *
+ */
+
+int cm_handler_client(int, int, char *);
diff --git a/usr.sbin/rtadvd/control_server.c b/usr.sbin/rtadvd/control_server.c
new file mode 100644
index 0000000..76ca541
--- /dev/null
+++ b/usr.sbin/rtadvd/control_server.c
@@ -0,0 +1,752 @@
+/*-
+ * Copyright (C) 2011 Hiroki Sato <hrs@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 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/queue.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <sys/uio.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <netinet/in.h>
+#include <netinet/icmp6.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syslog.h>
+
+#include "pathnames.h"
+#include "rtadvd.h"
+#include "if.h"
+#include "config.h"
+#include "control.h"
+#include "control_server.h"
+#include "timer.h"
+
+static char *do_reload_ifname;
+static int do_reload;
+static int do_shutdown;
+
+void set_do_reload(int sig __unused) { do_reload = 1; }
+void set_do_reload_ifname(char *ifname){ do_reload_ifname = ifname; }
+void set_do_shutdown(int sig __unused) { do_shutdown = 1; }
+void reset_do_reload(void) { do_reload = 0; do_reload_ifname = NULL; }
+void reset_do_shutdown(void) { do_shutdown = 0; }
+int is_do_reload(void) { return (do_reload); }
+int is_do_shutdown(void) { return (do_shutdown); }
+char *reload_ifname(void) { return (do_reload_ifname); }
+
+#define DEF_PL_HANDLER(key) { #key, cm_getprop_##key }
+
+static int cm_getprop_echo(struct ctrl_msg_pl *);
+static int cm_getprop_version(struct ctrl_msg_pl *);
+static int cm_getprop_ifilist(struct ctrl_msg_pl *);
+static int cm_getprop_ifi(struct ctrl_msg_pl *);
+static int cm_getprop_ifi_ra_timer(struct ctrl_msg_pl *);
+static int cm_getprop_rai(struct ctrl_msg_pl *);
+static int cm_getprop_pfx(struct ctrl_msg_pl *);
+static int cm_getprop_rdnss(struct ctrl_msg_pl *);
+static int cm_getprop_dnssl(struct ctrl_msg_pl *);
+static int cm_getprop_rti(struct ctrl_msg_pl *);
+
+static int cm_setprop_reload(struct ctrl_msg_pl *);
+static int cm_setprop_enable(struct ctrl_msg_pl *);
+static int cm_setprop_disable(struct ctrl_msg_pl *);
+
+static struct dispatch_table {
+ const char *dt_comm;
+ int (*dt_act)(struct ctrl_msg_pl *cp);
+} getprop_dtable[] = {
+ { "", cm_getprop_echo },
+ DEF_PL_HANDLER(echo),
+ DEF_PL_HANDLER(version),
+ DEF_PL_HANDLER(ifilist),
+ DEF_PL_HANDLER(ifi),
+ DEF_PL_HANDLER(ifi_ra_timer),
+ DEF_PL_HANDLER(rai),
+ DEF_PL_HANDLER(rti),
+ DEF_PL_HANDLER(pfx),
+ DEF_PL_HANDLER(rdnss),
+ DEF_PL_HANDLER(dnssl),
+};
+
+static int
+cm_getprop_echo(struct ctrl_msg_pl *cp)
+{
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+ cp->cp_val = strdup("");
+ cp->cp_val_len = strlen(cp->cp_val) + 1;
+
+ return (0);
+}
+
+static int
+cm_getprop_version(struct ctrl_msg_pl *cp)
+{
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+ cp->cp_val = strdup(CM_VERSION_STR);
+ cp->cp_val_len = strlen(cp->cp_val) + 1;
+
+ return (0);
+}
+
+static int
+cm_getprop_ifilist(struct ctrl_msg_pl *cp)
+{
+ struct ifinfo *ifi;
+ char *p;
+ size_t len;
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ len = 0;
+ TAILQ_FOREACH(ifi, &ifilist, ifi_next) {
+ len += strlen(ifi->ifi_ifname) + 1;
+ }
+
+ syslog(LOG_DEBUG, "<%s> len = %zu", __func__, len);
+
+ p = malloc(len);
+ if (p == NULL)
+ exit(1);
+ memset(p, 0, len);
+ cp->cp_val = p;
+
+ if (len > 0)
+ TAILQ_FOREACH(ifi, &ifilist, ifi_next) {
+ syslog(LOG_DEBUG, "<%s> add ifname=%s(%d)",
+ __func__, ifi->ifi_ifname, ifi->ifi_ifindex);
+ strcpy(p, ifi->ifi_ifname);
+ p += strlen(ifi->ifi_ifname) + 1;
+ }
+ cp->cp_val_len = p - cp->cp_val;
+
+ return (0);
+}
+
+static int
+cm_getprop_ifi(struct ctrl_msg_pl *cp)
+{
+ struct ifinfo *ifi;
+ char *p;
+ size_t len;
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ TAILQ_FOREACH(ifi, &ifilist, ifi_next) {
+ if (strcmp(cp->cp_ifname, ifi->ifi_ifname) == 0)
+ break;
+ }
+ if (ifi == NULL) {
+ syslog(LOG_ERR, "<%s> %s not found", __func__,
+ cp->cp_ifname);
+ return (1);
+ }
+
+ p = malloc(sizeof(*ifi));
+ if (p == NULL)
+ exit(1);
+ len = cm_str2bin(p, ifi, sizeof(*ifi));
+
+ syslog(LOG_DEBUG, "<%s> len = %zu", __func__, len);
+
+ if (len == 0)
+ return (1);
+
+ cp->cp_val = p;
+ cp->cp_val_len = len;
+
+ return (0);
+}
+
+static int
+cm_getprop_rai(struct ctrl_msg_pl *cp)
+{
+ struct ifinfo *ifi;
+ struct rainfo *rai;
+ char *p;
+ size_t len;
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ TAILQ_FOREACH(ifi, &ifilist, ifi_next) {
+ if (strcmp(cp->cp_ifname, ifi->ifi_ifname) == 0)
+ break;
+ }
+ if (ifi == NULL) {
+ syslog(LOG_ERR, "<%s> %s not found", __func__,
+ cp->cp_ifname);
+ return (1);
+ }
+ if ((rai = ifi->ifi_rainfo) == NULL) {
+ syslog(LOG_ERR, "<%s> %s has no rainfo", __func__,
+ cp->cp_ifname);
+ return (1);
+ }
+
+ p = malloc(sizeof(*rai));
+ if (p == NULL)
+ exit(1);
+ len = cm_str2bin(p, rai, sizeof(*rai));
+
+ syslog(LOG_DEBUG, "<%s> len = %zu", __func__, len);
+
+ if (len == 0)
+ return (1);
+
+ cp->cp_val = p;
+ cp->cp_val_len = len;
+
+ return (0);
+}
+
+static int
+cm_getprop_ifi_ra_timer(struct ctrl_msg_pl *cp)
+{
+ struct ifinfo *ifi;
+ struct rainfo *rai;
+ struct rtadvd_timer *rtimer;
+ char *p;
+ size_t len;
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ TAILQ_FOREACH(ifi, &ifilist, ifi_next) {
+ if (strcmp(cp->cp_ifname, ifi->ifi_ifname) == 0)
+ break;
+ }
+ if (ifi == NULL) {
+ syslog(LOG_ERR, "<%s> %s not found", __func__,
+ cp->cp_ifname);
+ return (1);
+ }
+ if ((rai = ifi->ifi_rainfo) == NULL) {
+ syslog(LOG_ERR, "<%s> %s has no rainfo", __func__,
+ cp->cp_ifname);
+ return (1);
+ }
+ if ((rtimer = ifi->ifi_ra_timer) == NULL) {
+ syslog(LOG_ERR, "<%s> %s has no ifi_ra_timer", __func__,
+ cp->cp_ifname);
+ return (1);
+ }
+ p = malloc(sizeof(*rtimer));
+ if (p == NULL)
+ exit(1);
+ len = cm_str2bin(p, rtimer, sizeof(*rtimer));
+
+ syslog(LOG_DEBUG, "<%s> len = %zu", __func__, len);
+
+ if (len == 0)
+ return (1);
+
+ cp->cp_val = p;
+ cp->cp_val_len = len;
+
+ return (0);
+}
+
+static int
+cm_getprop_rti(struct ctrl_msg_pl *cp)
+{
+ struct ifinfo *ifi;
+ struct rainfo *rai;
+ struct rtinfo *rti;
+ char *p;
+ size_t len;
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ len = 0;
+ TAILQ_FOREACH(ifi, &ifilist, ifi_next) {
+ if (strcmp(cp->cp_ifname, ifi->ifi_ifname) == 0)
+ break;
+ }
+ if (ifi == NULL) {
+ syslog(LOG_ERR, "<%s> %s not found", __func__,
+ cp->cp_ifname);
+ return (1);
+ }
+ if (ifi->ifi_rainfo == NULL) {
+ syslog(LOG_ERR, "<%s> %s has no rainfo", __func__,
+ cp->cp_ifname);
+ return (1);
+ }
+ rai = ifi->ifi_rainfo;
+ TAILQ_FOREACH(rti, &rai->rai_route, rti_next) {
+ len += sizeof(*rti);
+ }
+
+ syslog(LOG_DEBUG, "<%s> len = %zu", __func__, len);
+
+ p = malloc(len);
+ if (p == NULL)
+ exit(1);
+ memset(p, 0, len);
+ cp->cp_val = p;
+
+ if (len > 0)
+ TAILQ_FOREACH(rti, &rai->rai_route, rti_next) {
+ memcpy(p, rti, sizeof(*rti));
+ p += sizeof(*rti);
+ }
+ cp->cp_val_len = p - cp->cp_val;
+
+ return (0);
+}
+
+static int
+cm_getprop_pfx(struct ctrl_msg_pl *cp)
+{
+ struct ifinfo *ifi;
+ struct rainfo *rai;
+ struct prefix *pfx;
+ char *p;
+ size_t len;
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ len = 0;
+ TAILQ_FOREACH(ifi, &ifilist, ifi_next) {
+ if (strcmp(cp->cp_ifname, ifi->ifi_ifname) == 0)
+ break;
+ }
+ if (ifi == NULL) {
+ syslog(LOG_ERR, "<%s> %s not found", __func__,
+ cp->cp_ifname);
+ return (1);
+ }
+ if (ifi->ifi_rainfo == NULL) {
+ syslog(LOG_ERR, "<%s> %s has no rainfo", __func__,
+ cp->cp_ifname);
+ return (1);
+ }
+ rai = ifi->ifi_rainfo;
+ TAILQ_FOREACH(pfx, &rai->rai_prefix, pfx_next) {
+ len += sizeof(*pfx);
+ }
+
+ syslog(LOG_DEBUG, "<%s> len = %zu", __func__, len);
+
+ p = malloc(len);
+ if (p == NULL)
+ exit(1);
+ memset(p, 0, len);
+ cp->cp_val = p;
+
+ if (len > 0)
+ TAILQ_FOREACH(pfx, &rai->rai_prefix, pfx_next) {
+ memcpy(p, pfx, sizeof(*pfx));
+ p += sizeof(*pfx);
+ }
+ cp->cp_val_len = p - cp->cp_val;
+
+ return (0);
+}
+
+static int
+cm_getprop_rdnss(struct ctrl_msg_pl *cp)
+{
+ struct ifinfo *ifi;
+ struct rainfo *rai;
+ struct rdnss *rdn;
+ struct rdnss_addr *rda;
+ char *p;
+ size_t len;
+ uint16_t *rdn_cnt;
+ uint16_t *rda_cnt;
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ len = 0;
+ TAILQ_FOREACH(ifi, &ifilist, ifi_next) {
+ if (strcmp(cp->cp_ifname, ifi->ifi_ifname) == 0)
+ break;
+ }
+ if (ifi == NULL) {
+ syslog(LOG_ERR, "<%s> %s not found", __func__,
+ cp->cp_ifname);
+ return (1);
+ }
+ if (ifi->ifi_rainfo == NULL) {
+ syslog(LOG_ERR, "<%s> %s has no rainfo", __func__,
+ cp->cp_ifname);
+ return (1);
+ }
+ rai = ifi->ifi_rainfo;
+
+ len = sizeof(*rdn_cnt);
+ TAILQ_FOREACH(rdn, &rai->rai_rdnss, rd_next) {
+ len += sizeof(*rdn);
+ len += sizeof(*rda_cnt);
+ TAILQ_FOREACH(rda, &rdn->rd_list, ra_next) {
+ len += sizeof(*rda);
+ }
+ }
+
+ syslog(LOG_DEBUG, "<%s> len = %zu", __func__, len);
+
+ p = malloc(len);
+ if (p == NULL)
+ exit(1);
+ memset(p, 0, len);
+ cp->cp_val = p;
+
+ rdn_cnt = (uint16_t *)p;
+ p += sizeof(*rdn_cnt);
+ TAILQ_FOREACH(rdn, &rai->rai_rdnss, rd_next) {
+ *rdn_cnt += 1;
+ memcpy(p, rdn, sizeof(*rdn));
+ p += sizeof(*rdn);
+
+ rda_cnt = (uint16_t *)p;
+ p += sizeof(*rda_cnt);
+ TAILQ_FOREACH(rda, &rdn->rd_list, ra_next) {
+ *rda_cnt += 1;
+ memcpy(p, rda, sizeof(*rda));
+ p += sizeof(*rda);
+ }
+ }
+ syslog(LOG_DEBUG, "<%s> rdn_cnt = %d", __func__, *rdn_cnt);
+ cp->cp_val_len = p - cp->cp_val;
+
+ return (0);
+}
+
+static int
+cm_getprop_dnssl(struct ctrl_msg_pl *cp)
+{
+ struct ifinfo *ifi;
+ struct rainfo *rai;
+ struct dnssl *dns;
+ struct dnssl_addr *dna;
+ char *p;
+ size_t len;
+ uint16_t *dns_cnt;
+ uint16_t *dna_cnt;
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ len = 0;
+ TAILQ_FOREACH(ifi, &ifilist, ifi_next) {
+ if (strcmp(cp->cp_ifname, ifi->ifi_ifname) == 0)
+ break;
+ }
+ if (ifi == NULL) {
+ syslog(LOG_ERR, "<%s> %s not found", __func__,
+ cp->cp_ifname);
+ return (1);
+ }
+ if (ifi->ifi_rainfo == NULL) {
+ syslog(LOG_ERR, "<%s> %s has no rainfo", __func__,
+ cp->cp_ifname);
+ return (1);
+ }
+ rai = ifi->ifi_rainfo;
+
+ len = sizeof(*dns_cnt);
+ TAILQ_FOREACH(dns, &rai->rai_dnssl, dn_next) {
+ len += sizeof(*dns);
+ len += sizeof(*dna_cnt);
+ TAILQ_FOREACH(dna, &dns->dn_list, da_next) {
+ len += sizeof(*dna);
+ }
+ }
+
+ syslog(LOG_DEBUG, "<%s> len = %zu", __func__, len);
+
+ p = malloc(len);
+ if (p == NULL)
+ exit(1);
+ memset(p, 0, len);
+ cp->cp_val = p;
+
+ dns_cnt = (uint16_t *)cp->cp_val;
+ p += sizeof(*dns_cnt);
+ TAILQ_FOREACH(dns, &rai->rai_dnssl, dn_next) {
+ (*dns_cnt)++;
+ memcpy(p, dns, sizeof(*dns));
+ p += sizeof(*dns);
+
+ dna_cnt = (uint16_t *)p;
+ p += sizeof(*dna_cnt);
+ TAILQ_FOREACH(dna, &dns->dn_list, da_next) {
+ (*dna_cnt)++;
+ memcpy(p, dna, sizeof(*dna));
+ p += sizeof(*dna);
+ }
+ }
+ cp->cp_val_len = p - cp->cp_val;
+
+ return (0);
+}
+
+int
+cm_getprop(struct ctrl_msg_pl *cp)
+{
+ size_t i;
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ if (cp == NULL)
+ return (1);
+
+ for (i = 0;
+ i < sizeof(getprop_dtable) / sizeof(getprop_dtable[0]);
+ i++) {
+ if (strcmp(cp->cp_key, getprop_dtable[i].dt_comm) == 0)
+ return (getprop_dtable[i].dt_act(cp));
+ }
+ return (1);
+}
+
+int
+cm_setprop(struct ctrl_msg_pl *cp)
+{
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ if (cp == NULL || cp->cp_key == NULL)
+ return (1);
+
+ if (strncmp(cp->cp_key, "reload", sizeof("reload")) == 0)
+ cm_setprop_reload(cp);
+ else if (strncmp(cp->cp_key, "shutdown", sizeof("shutdown")) == 0)
+ set_do_shutdown(0);
+ else if (strncmp(cp->cp_key, "enable", sizeof("enable")) == 0)
+ cm_setprop_enable(cp);
+ else if (strncmp(cp->cp_key, "disable", sizeof("disable")) == 0)
+ cm_setprop_disable(cp);
+ else if (strncmp(cp->cp_key, "echo", 8) == 0)
+ ; /* do nothing */
+ else
+ return (1);
+
+ return (0);
+}
+
+static int
+cm_setprop_reload(struct ctrl_msg_pl *cp)
+{
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ set_do_reload_ifname(cp->cp_ifname);
+ set_do_reload(1);
+
+ return (0);
+}
+
+static int
+cm_setprop_enable(struct ctrl_msg_pl *cp)
+{
+ struct ifinfo *ifi;
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ TAILQ_FOREACH(ifi, &ifilist, ifi_next) {
+ if (strcmp(cp->cp_ifname, ifi->ifi_ifname) == 0)
+ break;
+ }
+ if (ifi == NULL) {
+ syslog(LOG_ERR, "<%s> %s not found", __func__,
+ cp->cp_ifname);
+ return (1);
+ }
+
+ ifi->ifi_persist = 1;
+ set_do_reload_ifname(ifi->ifi_ifname);
+ set_do_reload(0);
+
+ return (0);
+}
+
+static int
+cm_setprop_disable(struct ctrl_msg_pl *cp)
+{
+ struct ifinfo *ifi;
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ TAILQ_FOREACH(ifi, &ifilist, ifi_next) {
+ if (strcmp(cp->cp_ifname, ifi->ifi_ifname) == 0)
+ break;
+ }
+ if (ifi == NULL) {
+ syslog(LOG_ERR, "<%s> %s not found", __func__,
+ cp->cp_ifname);
+ return (1);
+ }
+
+ if (ifi->ifi_persist == 1) {
+ ifi->ifi_persist = 0;
+ rm_ifinfo(ifi);
+
+ /* MC leaving needed here */
+ sock_mc_leave(&sock, ifi->ifi_ifindex);
+
+ set_do_reload_ifname(ifi->ifi_ifname);
+ set_do_reload(0);
+ }
+
+ return (0);
+}
+
+int
+cm_handler_server(int fd)
+{
+ int state;
+ char *msg;
+ struct ctrl_msg_hdr *cm;
+ struct ctrl_msg_pl cp;
+ char buf[CM_MSG_MAXLEN];
+ char pbuf[CM_MSG_MAXLEN];
+ int error;
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ memset(buf, 0, sizeof(buf));
+ memset(pbuf, 0, sizeof(pbuf));
+ cm = (struct ctrl_msg_hdr *)buf;
+ msg = (char *)buf + sizeof(*cm);
+
+ state = CM_STATE_INIT;
+ while (state != CM_STATE_EOM) {
+ syslog(LOG_DEBUG, "<%s> state = %d", __func__, state);
+
+ switch (state) {
+ case CM_STATE_INIT:
+ state = CM_STATE_MSG_RECV;
+ break;
+ case CM_STATE_MSG_DISPATCH:
+ cm->cm_version = CM_VERSION;
+ error = cm_send(fd, buf);
+ if (error)
+ syslog(LOG_WARNING,
+ "<%s> cm_send()", __func__);
+ state = CM_STATE_EOM;
+ break;
+ case CM_STATE_ACK_WAIT:
+ error = cm_recv(fd, buf);
+ if (error) {
+ syslog(LOG_ERR,
+ "<%s> cm_recv()", __func__);
+ close(fd);
+ return (-1);
+ }
+
+ switch (cm->cm_type) {
+ case CM_TYPE_ACK:
+ break;
+ case CM_TYPE_ERR:
+ syslog(LOG_DEBUG,
+ "<%s> CM_TYPE_ERR", __func__);
+ close(fd);
+ return (-1);
+ default:
+ syslog(LOG_DEBUG,
+ "<%s> unknown status", __func__);
+ close(fd);
+ return (-1);
+ }
+ state = CM_STATE_EOM;
+ break;
+ case CM_STATE_MSG_RECV:
+ error = cm_recv(fd, buf);
+
+ if (error) {
+ syslog(LOG_ERR,
+ "<%s> cm_recv()", __func__);
+ close(fd);
+ return (-1);
+ }
+ memset(&cp, 0, sizeof(cp));
+
+ syslog(LOG_DEBUG,
+ "<%s> cm->cm_type = %d", __func__, cm->cm_type);
+ syslog(LOG_DEBUG,
+ "<%s> cm->cm_len = %zu", __func__, cm->cm_len);
+
+ switch (cm->cm_type) {
+ case CM_TYPE_EOM:
+ state = CM_STATE_EOM;
+ case CM_TYPE_NUL:
+ cm->cm_type = CM_TYPE_ACK;
+ cm->cm_len = sizeof(*cm);
+ break;
+ case CM_TYPE_REQ_GET_PROP:
+ cm_bin2pl(msg, &cp);
+ error = cm_getprop(&cp);
+ if (error) {
+ cm->cm_type = CM_TYPE_ERR;
+ cm->cm_len = sizeof(*cm);
+ } else {
+ cm->cm_type = CM_TYPE_ACK;
+ cm->cm_len = sizeof(*cm);
+ cm->cm_len += cm_pl2bin(msg, &cp);
+ }
+ if (cp.cp_val != NULL)
+ free(cp.cp_val);
+ break;
+ case CM_TYPE_REQ_SET_PROP:
+ cm_bin2pl(msg, &cp);
+ error = cm_setprop(&cp);
+ if (error) {
+ cm->cm_type = CM_TYPE_ERR;
+ cm->cm_len = sizeof(*cm);
+ } else {
+ cm->cm_type = CM_TYPE_ACK;
+ cm->cm_len = sizeof(*cm);
+ }
+ break;
+ default:
+ cm->cm_type = CM_TYPE_ERR;
+ cm->cm_len = sizeof(*cm);
+ }
+
+ switch (cm->cm_type) {
+ case CM_TYPE_ERR:
+ case CM_TYPE_ACK:
+ state = CM_STATE_MSG_DISPATCH;
+ break;
+ }
+ }
+ }
+ syslog(LOG_DEBUG, "<%s> leave", __func__);
+
+ return (0);
+}
diff --git a/usr.sbin/rtadvd/control_server.h b/usr.sbin/rtadvd/control_server.h
new file mode 100644
index 0000000..76fe9cd
--- /dev/null
+++ b/usr.sbin/rtadvd/control_server.h
@@ -0,0 +1,42 @@
+/*-
+ * Copyright (C) 2011 Hiroki Sato <hrs@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 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$
+ *
+ */
+
+int cm_getprop(struct ctrl_msg_pl *);
+int cm_setprop(struct ctrl_msg_pl *);
+
+int cm_handler_server(int);
+
+void set_do_reload(int);
+void set_do_reload_ifname(char *);
+void set_do_shutdown(int);
+void reset_do_reload(void);
+void reset_do_shutdown(void);
+int is_do_reload(void);
+char *reload_ifname(void);
+int is_do_shutdown(void);
diff --git a/usr.sbin/rtadvd/if.c b/usr.sbin/rtadvd/if.c
new file mode 100644
index 0000000..b9af28d
--- /dev/null
+++ b/usr.sbin/rtadvd/if.c
@@ -0,0 +1,748 @@
+/* $FreeBSD$ */
+/* $KAME: if.c,v 1.17 2001/01/21 15:27:30 itojun Exp $ */
+
+/*
+ * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
+ * Copyright (C) 2011 Hiroki Sato <hrs@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.
+ * 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/param.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/ethernet.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+#include <netinet6/nd6.h>
+#include <unistd.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "pathnames.h"
+#include "rtadvd.h"
+#include "if.h"
+
+#define ROUNDUP(a, size) \
+ (((a) & ((size)-1)) ? (1 + ((a) | ((size)-1))) : (a))
+
+#define NEXT_SA(ap) \
+ (ap) = (struct sockaddr *)((caddr_t)(ap) + \
+ ((ap)->sa_len ? ROUNDUP((ap)->sa_len, sizeof(u_long)) : \
+ sizeof(u_long)))
+
+struct sockaddr_in6 sin6_linklocal_allnodes = {
+ .sin6_len = sizeof(sin6_linklocal_allnodes),
+ .sin6_family = AF_INET6,
+ .sin6_addr = IN6ADDR_LINKLOCAL_ALLNODES_INIT,
+};
+
+struct sockaddr_in6 sin6_linklocal_allrouters = {
+ .sin6_len = sizeof(sin6_linklocal_allrouters),
+ .sin6_family = AF_INET6,
+ .sin6_addr = IN6ADDR_LINKLOCAL_ALLROUTERS_INIT,
+};
+
+struct sockaddr_in6 sin6_sitelocal_allrouters = {
+ .sin6_len = sizeof(sin6_sitelocal_allrouters),
+ .sin6_family = AF_INET6,
+ .sin6_addr = IN6ADDR_SITELOCAL_ALLROUTERS_INIT,
+};
+
+struct sockinfo sock = { .si_fd = -1, .si_name = NULL };
+struct sockinfo rtsock = { .si_fd = -1, .si_name = NULL };
+struct sockinfo ctrlsock = { .si_fd = -1, .si_name = _PATH_CTRL_SOCK };
+
+char *mcastif;
+
+static void get_rtaddrs(int, struct sockaddr *,
+ struct sockaddr **);
+static struct if_msghdr *get_next_msghdr(struct if_msghdr *,
+ struct if_msghdr *);
+
+static void
+get_rtaddrs(int addrs, struct sockaddr *sa, struct sockaddr **rti_info)
+{
+ int i;
+
+ for (i = 0; i < RTAX_MAX; i++) {
+ if (addrs & (1 << i)) {
+ rti_info[i] = sa;
+ NEXT_SA(sa);
+ }
+ else
+ rti_info[i] = NULL;
+ }
+}
+
+#define ROUNDUP8(a) (1 + (((a) - 1) | 7))
+int
+lladdropt_length(struct sockaddr_dl *sdl)
+{
+ switch (sdl->sdl_type) {
+ case IFT_ETHER:
+ return (ROUNDUP8(ETHER_ADDR_LEN + 2));
+ default:
+ return (0);
+ }
+}
+
+void
+lladdropt_fill(struct sockaddr_dl *sdl, struct nd_opt_hdr *ndopt)
+{
+ char *addr;
+
+ ndopt->nd_opt_type = ND_OPT_SOURCE_LINKADDR; /* fixed */
+
+ switch (sdl->sdl_type) {
+ case IFT_ETHER:
+ ndopt->nd_opt_len = (ROUNDUP8(ETHER_ADDR_LEN + 2)) >> 3;
+ addr = (char *)(ndopt + 1);
+ memcpy(addr, LLADDR(sdl), ETHER_ADDR_LEN);
+ break;
+ default:
+ syslog(LOG_ERR, "<%s> unsupported link type(%d)",
+ __func__, sdl->sdl_type);
+ exit(1);
+ }
+
+ return;
+}
+
+int
+rtbuf_len(void)
+{
+ size_t len;
+ int mib[6] = {CTL_NET, AF_ROUTE, 0, AF_INET6, NET_RT_DUMP, 0};
+
+ if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0)
+ return (-1);
+
+ return (len);
+}
+
+#define FILTER_MATCH(type, filter) ((0x1 << type) & filter)
+#define SIN6(s) ((struct sockaddr_in6 *)(s))
+#define SDL(s) ((struct sockaddr_dl *)(s))
+char *
+get_next_msg(char *buf, char *lim, int ifindex, size_t *lenp, int filter)
+{
+ struct rt_msghdr *rtm;
+ struct ifa_msghdr *ifam;
+ struct sockaddr *sa, *dst, *gw, *ifa, *rti_info[RTAX_MAX];
+
+ *lenp = 0;
+ for (rtm = (struct rt_msghdr *)buf;
+ rtm < (struct rt_msghdr *)lim;
+ rtm = (struct rt_msghdr *)(((char *)rtm) + rtm->rtm_msglen)) {
+ /* just for safety */
+ if (!rtm->rtm_msglen) {
+ syslog(LOG_WARNING, "<%s> rtm_msglen is 0 "
+ "(buf=%p lim=%p rtm=%p)", __func__,
+ buf, lim, rtm);
+ break;
+ }
+ if (((struct rt_msghdr *)buf)->rtm_version != RTM_VERSION) {
+ syslog(LOG_WARNING,
+ "<%s> routing message version mismatch "
+ "(buf=%p lim=%p rtm=%p)", __func__,
+ buf, lim, rtm);
+ continue;
+ }
+
+ if (FILTER_MATCH(rtm->rtm_type, filter) == 0)
+ continue;
+
+ switch (rtm->rtm_type) {
+ case RTM_GET:
+ case RTM_ADD:
+ case RTM_DELETE:
+ /* address related checks */
+ sa = (struct sockaddr *)(rtm + 1);
+ get_rtaddrs(rtm->rtm_addrs, sa, rti_info);
+ if ((dst = rti_info[RTAX_DST]) == NULL ||
+ dst->sa_family != AF_INET6)
+ continue;
+
+ if (IN6_IS_ADDR_LINKLOCAL(&SIN6(dst)->sin6_addr) ||
+ IN6_IS_ADDR_MULTICAST(&SIN6(dst)->sin6_addr))
+ continue;
+
+ if ((gw = rti_info[RTAX_GATEWAY]) == NULL ||
+ gw->sa_family != AF_LINK)
+ continue;
+ if (ifindex && SDL(gw)->sdl_index != ifindex)
+ continue;
+
+ if (rti_info[RTAX_NETMASK] == NULL)
+ continue;
+
+ /* found */
+ *lenp = rtm->rtm_msglen;
+ return (char *)rtm;
+ /* NOTREACHED */
+ case RTM_NEWADDR:
+ case RTM_DELADDR:
+ ifam = (struct ifa_msghdr *)rtm;
+
+ /* address related checks */
+ sa = (struct sockaddr *)(ifam + 1);
+ get_rtaddrs(ifam->ifam_addrs, sa, rti_info);
+ if ((ifa = rti_info[RTAX_IFA]) == NULL ||
+ (ifa->sa_family != AF_INET &&
+ ifa->sa_family != AF_INET6))
+ continue;
+
+ if (ifa->sa_family == AF_INET6 &&
+ (IN6_IS_ADDR_LINKLOCAL(&SIN6(ifa)->sin6_addr) ||
+ IN6_IS_ADDR_MULTICAST(&SIN6(ifa)->sin6_addr)))
+ continue;
+
+ if (ifindex && ifam->ifam_index != ifindex)
+ continue;
+
+ /* found */
+ *lenp = ifam->ifam_msglen;
+ return (char *)rtm;
+ /* NOTREACHED */
+ case RTM_IFINFO:
+ case RTM_IFANNOUNCE:
+ /* found */
+ *lenp = rtm->rtm_msglen;
+ return (char *)rtm;
+ /* NOTREACHED */
+ }
+ }
+
+ return ((char *)rtm);
+}
+#undef FILTER_MATCH
+
+struct in6_addr *
+get_addr(char *buf)
+{
+ struct rt_msghdr *rtm = (struct rt_msghdr *)buf;
+ struct sockaddr *sa, *rti_info[RTAX_MAX];
+
+ sa = (struct sockaddr *)(rtm + 1);
+ get_rtaddrs(rtm->rtm_addrs, sa, rti_info);
+
+ return (&SIN6(rti_info[RTAX_DST])->sin6_addr);
+}
+
+int
+get_rtm_ifindex(char *buf)
+{
+ struct rt_msghdr *rtm = (struct rt_msghdr *)buf;
+ struct sockaddr *sa, *rti_info[RTAX_MAX];
+
+ sa = (struct sockaddr *)(rtm + 1);
+ get_rtaddrs(rtm->rtm_addrs, sa, rti_info);
+
+ return (((struct sockaddr_dl *)rti_info[RTAX_GATEWAY])->sdl_index);
+}
+
+int
+get_prefixlen(char *buf)
+{
+ struct rt_msghdr *rtm = (struct rt_msghdr *)buf;
+ struct sockaddr *sa, *rti_info[RTAX_MAX];
+ char *p, *lim;
+
+ sa = (struct sockaddr *)(rtm + 1);
+ get_rtaddrs(rtm->rtm_addrs, sa, rti_info);
+ sa = rti_info[RTAX_NETMASK];
+
+ p = (char *)(&SIN6(sa)->sin6_addr);
+ lim = (char *)sa + sa->sa_len;
+ return prefixlen(p, lim);
+}
+
+int
+prefixlen(unsigned char *p, unsigned char *lim)
+{
+ int masklen;
+
+ for (masklen = 0; p < lim; p++) {
+ switch (*p) {
+ case 0xff:
+ masklen += 8;
+ break;
+ case 0xfe:
+ masklen += 7;
+ break;
+ case 0xfc:
+ masklen += 6;
+ break;
+ case 0xf8:
+ masklen += 5;
+ break;
+ case 0xf0:
+ masklen += 4;
+ break;
+ case 0xe0:
+ masklen += 3;
+ break;
+ case 0xc0:
+ masklen += 2;
+ break;
+ case 0x80:
+ masklen += 1;
+ break;
+ case 0x00:
+ break;
+ default:
+ return (-1);
+ }
+ }
+
+ return (masklen);
+}
+
+struct ifinfo *
+update_persist_ifinfo(struct ifilist_head_t *ifi_head, const char *ifname)
+{
+ struct ifinfo *ifi;
+ int ifindex;
+
+ ifi = NULL;
+ ifindex = if_nametoindex(ifname);
+ TAILQ_FOREACH(ifi, ifi_head, ifi_next) {
+ if (ifindex != 0) {
+ if (ifindex == ifi->ifi_ifindex)
+ break;
+ } else {
+ if (strncmp(ifname, ifi->ifi_ifname,
+ sizeof(ifi->ifi_ifname)) == 0)
+ break;
+ }
+ }
+
+ if (ifi == NULL) {
+ /* A new ifinfo element is needed. */
+ syslog(LOG_DEBUG, "<%s> new entry: %s", __func__,
+ ifname);
+
+ ELM_MALLOC(ifi, exit(1));
+ ifi->ifi_ifindex = 0;
+ strlcpy(ifi->ifi_ifname, ifname, sizeof(ifi->ifi_ifname));
+ ifi->ifi_rainfo = NULL;
+ ifi->ifi_state = IFI_STATE_UNCONFIGURED;
+ TAILQ_INSERT_TAIL(ifi_head, ifi, ifi_next);
+ }
+
+ ifi->ifi_persist = 1;
+
+ syslog(LOG_DEBUG, "<%s> %s is marked PERSIST", __func__,
+ ifi->ifi_ifname);
+ syslog(LOG_DEBUG, "<%s> %s is state = %d", __func__,
+ ifi->ifi_ifname, ifi->ifi_state);
+ return (ifi);
+}
+
+int
+update_ifinfo_nd_flags(struct ifinfo *ifi)
+{
+ struct in6_ndireq nd;
+ int s;
+ int error;
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
+ syslog(LOG_ERR,
+ "<%s> socket() failed.", __func__);
+ return (1);
+ }
+ /* ND flags */
+ memset(&nd, 0, sizeof(nd));
+ strncpy(nd.ifname, ifi->ifi_ifname,
+ sizeof(nd.ifname));
+ error = ioctl(s, SIOCGIFINFO_IN6, (caddr_t)&nd);
+ if (error) {
+ close(s);
+ if (errno != EPFNOSUPPORT)
+ syslog(LOG_ERR, "<%s> ioctl() failed.", __func__);
+ return (1);
+ }
+ ifi->ifi_nd_flags = nd.ndi.flags;
+ close(s);
+
+ return (0);
+}
+
+struct ifinfo *
+update_ifinfo(struct ifilist_head_t *ifi_head, int ifindex)
+{
+ struct if_msghdr *ifm;
+ struct ifinfo *ifi = NULL;
+ struct sockaddr *sa;
+ struct sockaddr *rti_info[RTAX_MAX];
+ char *msg;
+ size_t len;
+ char *lim;
+ int mib[] = { CTL_NET, PF_ROUTE, 0, AF_INET6, NET_RT_IFLIST, 0 };
+ int error;
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), NULL, &len, NULL, 0) <
+ 0) {
+ syslog(LOG_ERR,
+ "<%s> sysctl: NET_RT_IFLIST size get failed", __func__);
+ exit(1);
+ }
+ if ((msg = malloc(len)) == NULL) {
+ syslog(LOG_ERR, "<%s> malloc failed", __func__);
+ exit(1);
+ }
+ if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), msg, &len, NULL, 0) <
+ 0) {
+ syslog(LOG_ERR,
+ "<%s> sysctl: NET_RT_IFLIST get failed", __func__);
+ exit(1);
+ }
+
+ lim = msg + len;
+ for (ifm = (struct if_msghdr *)msg;
+ ifm != NULL && ifm < (struct if_msghdr *)lim;
+ ifm = get_next_msghdr(ifm,(struct if_msghdr *)lim)) {
+ int ifi_new;
+
+ syslog(LOG_DEBUG, "<%s> ifm = %p, lim = %p, diff = %zu",
+ __func__, ifm, lim, (char *)lim - (char *)ifm);
+
+ if (ifm->ifm_version != RTM_VERSION) {
+ syslog(LOG_ERR,
+ "<%s> ifm_vesrion mismatch", __func__);
+ exit(1);
+ }
+ if (ifm->ifm_msglen == 0) {
+ syslog(LOG_WARNING,
+ "<%s> ifm_msglen is 0", __func__);
+ free(msg);
+ return (NULL);
+ }
+
+ ifi_new = 0;
+ if (ifm->ifm_type == RTM_IFINFO) {
+ struct ifreq ifr;
+ int s;
+ char ifname[IFNAMSIZ];
+
+ syslog(LOG_DEBUG, "<%s> RTM_IFINFO found. "
+ "ifm_index = %d, ifindex = %d",
+ __func__, ifm->ifm_index, ifindex);
+
+ /* when ifindex is specified */
+ if (ifindex != UPDATE_IFINFO_ALL &&
+ ifindex != ifm->ifm_index)
+ continue;
+
+ /* lookup an entry with the same ifindex */
+ TAILQ_FOREACH(ifi, ifi_head, ifi_next) {
+ if (ifm->ifm_index == ifi->ifi_ifindex)
+ break;
+ if_indextoname(ifm->ifm_index, ifname);
+ if (strncmp(ifname, ifi->ifi_ifname,
+ sizeof(ifname)) == 0)
+ break;
+ }
+ if (ifi == NULL) {
+ syslog(LOG_DEBUG,
+ "<%s> new entry for idx=%d",
+ __func__, ifm->ifm_index);
+ ELM_MALLOC(ifi, exit(1));
+ ifi->ifi_rainfo = NULL;
+ ifi->ifi_state = IFI_STATE_UNCONFIGURED;
+ ifi->ifi_persist = 0;
+ ifi_new = 1;
+ }
+ /* ifindex */
+ ifi->ifi_ifindex = ifm->ifm_index;
+
+ /* ifname */
+ if_indextoname(ifm->ifm_index, ifi->ifi_ifname);
+ if (ifi->ifi_ifname == NULL) {
+ syslog(LOG_WARNING,
+ "<%s> ifname not found (idx=%d)",
+ __func__, ifm->ifm_index);
+ if (ifi_new)
+ free(ifi);
+ continue;
+ }
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
+ syslog(LOG_ERR,
+ "<%s> socket() failed.", __func__);
+ if (ifi_new)
+ free(ifi);
+ continue;
+ }
+
+ /* MTU */
+ ifi->ifi_phymtu = ifm->ifm_data.ifi_mtu;
+ if (ifi->ifi_phymtu == 0) {
+ memset(&ifr, 0, sizeof(ifr));
+ ifr.ifr_addr.sa_family = AF_INET6;
+ strncpy(ifr.ifr_name, ifi->ifi_ifname,
+ sizeof(ifr.ifr_name));
+ error = ioctl(s, SIOCGIFMTU, (caddr_t)&ifr);
+ if (error) {
+ close(s);
+ syslog(LOG_ERR,
+ "<%s> ioctl() failed.",
+ __func__);
+ if (ifi_new)
+ free(ifi);
+ continue;
+ }
+ ifi->ifi_phymtu = ifr.ifr_mtu;
+ if (ifi->ifi_phymtu == 0) {
+ syslog(LOG_WARNING,
+ "<%s> no interface mtu info"
+ " on %s. %d will be used.",
+ __func__, ifi->ifi_ifname,
+ IPV6_MMTU);
+ ifi->ifi_phymtu = IPV6_MMTU;
+ }
+ }
+ close(s);
+
+ /* ND flags */
+ error = update_ifinfo_nd_flags(ifi);
+ if (error) {
+ if (ifi_new)
+ free(ifi);
+ continue;
+ }
+
+ /* SDL */
+ sa = (struct sockaddr *)(ifm + 1);
+ get_rtaddrs(ifm->ifm_addrs, sa, rti_info);
+ if ((sa = rti_info[RTAX_IFP]) != NULL) {
+ if (sa->sa_family == AF_LINK) {
+ memcpy(&ifi->ifi_sdl,
+ (struct sockaddr_dl *)sa,
+ sizeof(ifi->ifi_sdl));
+ }
+ } else
+ memset(&ifi->ifi_sdl, 0,
+ sizeof(ifi->ifi_sdl));
+
+ /* flags */
+ ifi->ifi_flags = ifm->ifm_flags;
+
+ /* type */
+ ifi->ifi_type = ifm->ifm_type;
+ } else {
+ syslog(LOG_ERR,
+ "out of sync parsing NET_RT_IFLIST\n"
+ "expected %d, got %d\n msglen = %d\n",
+ RTM_IFINFO, ifm->ifm_type, ifm->ifm_msglen);
+ exit(1);
+ }
+
+ if (ifi_new) {
+ syslog(LOG_DEBUG,
+ "<%s> adding %s(idx=%d) to ifilist",
+ __func__, ifi->ifi_ifname, ifi->ifi_ifindex);
+ TAILQ_INSERT_TAIL(ifi_head, ifi, ifi_next);
+ }
+ }
+ free(msg);
+
+ if (mcastif != NULL) {
+ error = sock_mc_rr_update(&sock, mcastif);
+ if (error)
+ exit(1);
+ }
+
+ return (ifi);
+}
+
+static struct if_msghdr *
+get_next_msghdr(struct if_msghdr *ifm, struct if_msghdr *lim)
+{
+ struct ifa_msghdr *ifam;
+
+ for (ifam = (struct ifa_msghdr *)((char *)ifm + ifm->ifm_msglen);
+ ifam < (struct ifa_msghdr *)lim;
+ ifam = (struct ifa_msghdr *)((char *)ifam + ifam->ifam_msglen)) {
+ if (!ifam->ifam_msglen) {
+ syslog(LOG_WARNING,
+ "<%s> ifa_msglen is 0", __func__);
+ return (NULL);
+ }
+ if (ifam->ifam_type != RTM_NEWADDR)
+ break;
+ }
+
+ return ((struct if_msghdr *)ifam);
+}
+
+int
+getinet6sysctl(int code)
+{
+ int mib[] = { CTL_NET, PF_INET6, IPPROTO_IPV6, 0 };
+ int value;
+ size_t size;
+
+ mib[3] = code;
+ size = sizeof(value);
+ if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), &value, &size, NULL, 0)
+ < 0) {
+ syslog(LOG_ERR, "<%s>: failed to get ip6 sysctl(%d): %s",
+ __func__, code,
+ strerror(errno));
+ return (-1);
+ }
+ else
+ return (value);
+}
+
+
+int
+sock_mc_join(struct sockinfo *s, int ifindex)
+{
+ struct ipv6_mreq mreq;
+ char ifname[IFNAMSIZ];
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ if (ifindex == 0)
+ return (1);
+
+ /*
+ * join all routers multicast address on each advertising
+ * interface.
+ */
+ memset(&mreq, 0, sizeof(mreq));
+ /* XXX */
+ memcpy(&mreq.ipv6mr_multiaddr.s6_addr,
+ &sin6_linklocal_allrouters.sin6_addr,
+ sizeof(mreq.ipv6mr_multiaddr.s6_addr));
+
+ mreq.ipv6mr_interface = ifindex;
+ if (setsockopt(s->si_fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq,
+ sizeof(mreq)) < 0) {
+ syslog(LOG_ERR,
+ "<%s> IPV6_JOIN_GROUP(link) on %s: %s",
+ __func__, if_indextoname(ifindex, ifname),
+ strerror(errno));
+ return (1);
+ }
+ syslog(LOG_DEBUG,
+ "<%s> %s: join link-local all-routers MC group",
+ __func__, if_indextoname(ifindex, ifname));
+
+ return (0);
+}
+
+int
+sock_mc_leave(struct sockinfo *s, int ifindex)
+{
+ struct ipv6_mreq mreq;
+ char ifname[IFNAMSIZ];
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ if (ifindex == 0)
+ return (1);
+
+ /*
+ * join all routers multicast address on each advertising
+ * interface.
+ */
+
+ memset(&mreq, 0, sizeof(mreq));
+ /* XXX */
+ memcpy(&mreq.ipv6mr_multiaddr.s6_addr,
+ &sin6_linklocal_allrouters.sin6_addr,
+ sizeof(mreq.ipv6mr_multiaddr.s6_addr));
+
+ mreq.ipv6mr_interface = ifindex;
+ if (setsockopt(s->si_fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &mreq,
+ sizeof(mreq)) < 0) {
+ syslog(LOG_ERR,
+ "<%s> IPV6_JOIN_LEAVE(link) on %s: %s",
+ __func__, if_indextoname(ifindex, ifname),
+ strerror(errno));
+ return (1);
+ }
+ syslog(LOG_DEBUG,
+ "<%s> %s: leave link-local all-routers MC group",
+ __func__, if_indextoname(ifindex, ifname));
+
+ return (0);
+}
+
+int
+sock_mc_rr_update(struct sockinfo *s, char *mif)
+{
+ struct ipv6_mreq mreq;
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ if (mif == NULL)
+ return (1);
+ /*
+ * When attending router renumbering, join all-routers site-local
+ * multicast group.
+ */
+ /* XXX */
+ memcpy(&mreq.ipv6mr_multiaddr.s6_addr,
+ &sin6_sitelocal_allrouters.sin6_addr,
+ sizeof(mreq.ipv6mr_multiaddr.s6_addr));
+ if ((mreq.ipv6mr_interface = if_nametoindex(mif)) == 0) {
+ syslog(LOG_ERR,
+ "<%s> invalid interface: %s",
+ __func__, mif);
+ return (1);
+ }
+
+ if (setsockopt(s->si_fd, IPPROTO_IPV6, IPV6_JOIN_GROUP,
+ &mreq, sizeof(mreq)) < 0) {
+ syslog(LOG_ERR,
+ "<%s> IPV6_JOIN_GROUP(site) on %s: %s",
+ __func__, mif, strerror(errno));
+ return (1);
+ }
+
+ syslog(LOG_DEBUG,
+ "<%s> %s: join site-local all-routers MC group",
+ __func__, mif);
+
+ return (0);
+}
diff --git a/usr.sbin/rtadvd/if.h b/usr.sbin/rtadvd/if.h
new file mode 100644
index 0000000..6efdd56
--- /dev/null
+++ b/usr.sbin/rtadvd/if.h
@@ -0,0 +1,61 @@
+/* $FreeBSD$ */
+/* $KAME: if.h,v 1.10 2003/02/24 11:29:10 ono 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.
+ */
+
+#define UPDATE_IFINFO_ALL 0
+
+struct sockinfo {
+ int si_fd;
+ const char *si_name;
+};
+
+extern struct sockinfo sock;
+extern struct sockinfo rtsock;
+extern struct sockinfo ctrlsock;
+
+int lladdropt_length(struct sockaddr_dl *);
+void lladdropt_fill(struct sockaddr_dl *, struct nd_opt_hdr *);
+int rtbuf_len(void);
+char *get_next_msg(char *, char *, int, size_t *, int);
+struct in6_addr *get_addr(char *);
+int get_rtm_ifindex(char *);
+int get_prefixlen(char *);
+int prefixlen(unsigned char *, unsigned char *);
+
+struct ifinfo *update_ifinfo(struct ifilist_head_t *, int);
+int update_ifinfo_nd_flags(struct ifinfo *);
+struct ifinfo *update_persist_ifinfo(struct ifilist_head_t *,
+ const char *);
+
+int sock_mc_join(struct sockinfo *, int);
+int sock_mc_leave(struct sockinfo *, int);
+int sock_mc_rr_update(struct sockinfo *, char *);
+int getinet6sysctl(int);
diff --git a/usr.sbin/rtadvd/pathnames.h b/usr.sbin/rtadvd/pathnames.h
new file mode 100644
index 0000000..248ee19
--- /dev/null
+++ b/usr.sbin/rtadvd/pathnames.h
@@ -0,0 +1,6 @@
+/* $KAME: pathnames.h,v 1.2 2000/05/16 13:34:13 itojun Exp $ */
+/* $FreeBSD$ */
+
+#define _PATH_RTADVDCONF "/etc/rtadvd.conf"
+#define _PATH_RTADVDPID "/var/run/rtadvd.pid"
+#define _PATH_CTRL_SOCK "/var/run/rtadvd.sock"
diff --git a/usr.sbin/rtadvd/rrenum.c b/usr.sbin/rtadvd/rrenum.c
new file mode 100644
index 0000000..eede4b6
--- /dev/null
+++ b/usr.sbin/rtadvd/rrenum.c
@@ -0,0 +1,502 @@
+/* $FreeBSD$ */
+/* $KAME: rrenum.c,v 1.12 2002/06/10 19:59: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.
+ */
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <netinet/icmp6.h>
+
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include <syslog.h>
+#include "rtadvd.h"
+#include "rrenum.h"
+#include "if.h"
+
+#define RR_ISSET_SEGNUM(segnum_bits, segnum) \
+ ((((segnum_bits)[(segnum) >> 5]) & (1 << ((segnum) & 31))) != 0)
+#define RR_SET_SEGNUM(segnum_bits, segnum) \
+ (((segnum_bits)[(segnum) >> 5]) |= (1 << ((segnum) & 31)))
+
+struct rr_operation {
+ u_long rro_seqnum;
+ u_long rro_segnum_bits[8];
+};
+
+static struct rr_operation rro;
+static int rr_rcvifindex;
+static int rrcmd2pco[RPM_PCO_MAX] = {
+ 0,
+ SIOCAIFPREFIX_IN6,
+ SIOCCIFPREFIX_IN6,
+ SIOCSGIFPREFIX_IN6
+};
+static int s = -1;
+
+/*
+ * Check validity of a Prefix Control Operation(PCO).
+ * return 0 on success, 1 on failure.
+ */
+static int
+rr_pco_check(int len, struct rr_pco_match *rpm)
+{
+ struct rr_pco_use *rpu, *rpulim;
+ int checklen;
+
+ /* rpm->rpm_len must be (4N * 3) as router-renum-05.txt */
+ if ((rpm->rpm_len - 3) < 0 || /* must be at least 3 */
+ (rpm->rpm_len - 3) & 0x3) { /* must be multiple of 4 */
+ syslog(LOG_WARNING, "<%s> rpm_len %d is not 4N * 3",
+ __func__, rpm->rpm_len);
+ return (1);
+ }
+ /* rpm->rpm_code must be valid value */
+ switch (rpm->rpm_code) {
+ case RPM_PCO_ADD:
+ case RPM_PCO_CHANGE:
+ case RPM_PCO_SETGLOBAL:
+ break;
+ default:
+ syslog(LOG_WARNING, "<%s> unknown rpm_code %d", __func__,
+ rpm->rpm_code);
+ return (1);
+ }
+ /* rpm->rpm_matchlen must be 0 to 128 inclusive */
+ if (rpm->rpm_matchlen > 128) {
+ syslog(LOG_WARNING, "<%s> rpm_matchlen %d is over 128",
+ __func__, rpm->rpm_matchlen);
+ return (1);
+ }
+
+ /*
+ * rpu->rpu_uselen, rpu->rpu_keeplen, and sum of them must be
+ * between 0 and 128 inclusive
+ */
+ for (rpu = (struct rr_pco_use *)(rpm + 1),
+ rpulim = (struct rr_pco_use *)((char *)rpm + len);
+ rpu < rpulim;
+ rpu += 1) {
+ checklen = rpu->rpu_uselen;
+ checklen += rpu->rpu_keeplen;
+ /*
+ * omit these check, because either of rpu_uselen
+ * and rpu_keeplen is unsigned char
+ * (128 > rpu_uselen > 0)
+ * (128 > rpu_keeplen > 0)
+ * (rpu_uselen + rpu_keeplen > 0)
+ */
+ if (checklen > 128) {
+ syslog(LOG_WARNING, "<%s> sum of rpu_uselen %d and"
+ " rpu_keeplen %d is %d(over 128)",
+ __func__, rpu->rpu_uselen, rpu->rpu_keeplen,
+ rpu->rpu_uselen + rpu->rpu_keeplen);
+ return (1);
+ }
+ }
+ return (0);
+}
+
+static void
+do_use_prefix(int len, struct rr_pco_match *rpm,
+ struct in6_rrenumreq *irr, int ifindex)
+{
+ struct rr_pco_use *rpu, *rpulim;
+ struct rainfo *rai;
+ struct ifinfo *ifi;
+ struct prefix *pfx;
+
+ rpu = (struct rr_pco_use *)(rpm + 1);
+ rpulim = (struct rr_pco_use *)((char *)rpm + len);
+
+ if (rpu == rpulim) { /* no use prefix */
+ if (rpm->rpm_code == RPM_PCO_ADD)
+ return;
+
+ irr->irr_u_uselen = 0;
+ irr->irr_u_keeplen = 0;
+ irr->irr_raf_mask_onlink = 0;
+ irr->irr_raf_mask_auto = 0;
+ irr->irr_vltime = 0;
+ irr->irr_pltime = 0;
+ memset(&irr->irr_flags, 0, sizeof(irr->irr_flags));
+ irr->irr_useprefix.sin6_len = 0; /* let it mean, no addition */
+ irr->irr_useprefix.sin6_family = 0;
+ irr->irr_useprefix.sin6_addr = in6addr_any;
+ if (ioctl(s, rrcmd2pco[rpm->rpm_code], (caddr_t)irr) < 0 &&
+ errno != EADDRNOTAVAIL)
+ syslog(LOG_ERR, "<%s> ioctl: %s", __func__,
+ strerror(errno));
+ return;
+ }
+
+ for (rpu = (struct rr_pco_use *)(rpm + 1),
+ rpulim = (struct rr_pco_use *)((char *)rpm + len);
+ rpu < rpulim;
+ rpu += 1) {
+ /* init in6_rrenumreq fields */
+ irr->irr_u_uselen = rpu->rpu_uselen;
+ irr->irr_u_keeplen = rpu->rpu_keeplen;
+ irr->irr_raf_mask_onlink =
+ !!(rpu->rpu_ramask & ICMP6_RR_PCOUSE_RAFLAGS_ONLINK);
+ irr->irr_raf_mask_auto =
+ !!(rpu->rpu_ramask & ICMP6_RR_PCOUSE_RAFLAGS_AUTO);
+ irr->irr_vltime = ntohl(rpu->rpu_vltime);
+ irr->irr_pltime = ntohl(rpu->rpu_pltime);
+ irr->irr_raf_onlink =
+ (rpu->rpu_raflags & ICMP6_RR_PCOUSE_RAFLAGS_ONLINK) == 0 ?
+ 0 : 1;
+ irr->irr_raf_auto =
+ (rpu->rpu_raflags & ICMP6_RR_PCOUSE_RAFLAGS_AUTO) == 0 ?
+ 0 : 1;
+ irr->irr_rrf_decrvalid =
+ (rpu->rpu_flags & ICMP6_RR_PCOUSE_FLAGS_DECRVLTIME) == 0 ?
+ 0 : 1;
+ irr->irr_rrf_decrprefd =
+ (rpu->rpu_flags & ICMP6_RR_PCOUSE_FLAGS_DECRPLTIME) == 0 ?
+ 0 : 1;
+ irr->irr_useprefix.sin6_len = sizeof(irr->irr_useprefix);
+ irr->irr_useprefix.sin6_family = AF_INET6;
+ irr->irr_useprefix.sin6_addr = rpu->rpu_prefix;
+
+ if (ioctl(s, rrcmd2pco[rpm->rpm_code], (caddr_t)irr) < 0 &&
+ errno != EADDRNOTAVAIL)
+ syslog(LOG_ERR, "<%s> ioctl: %s", __func__,
+ strerror(errno));
+
+ /* very adhoc: should be rewritten */
+ if (rpm->rpm_code == RPM_PCO_CHANGE &&
+ IN6_ARE_ADDR_EQUAL(&rpm->rpm_prefix, &rpu->rpu_prefix) &&
+ rpm->rpm_matchlen == rpu->rpu_uselen &&
+ rpu->rpu_uselen == rpu->rpu_keeplen) {
+ ifi = if_indextoifinfo(ifindex);
+ if (ifi == NULL || ifi->ifi_rainfo == NULL)
+ continue; /* non-advertising IF */
+ rai = ifi->ifi_rainfo;
+
+ TAILQ_FOREACH(pfx, &rai->rai_prefix, pfx_next) {
+ struct timespec now;
+
+ if (prefix_match(&pfx->pfx_prefix,
+ pfx->pfx_prefixlen, &rpm->rpm_prefix,
+ rpm->rpm_matchlen)) {
+ /* change parameters */
+ pfx->pfx_validlifetime =
+ ntohl(rpu->rpu_vltime);
+ pfx->pfx_preflifetime =
+ ntohl(rpu->rpu_pltime);
+ if (irr->irr_rrf_decrvalid) {
+ clock_gettime(CLOCK_MONOTONIC_FAST,
+ &now);
+ pfx->pfx_vltimeexpire =
+ now.tv_sec +
+ pfx->pfx_validlifetime;
+ } else
+ pfx->pfx_vltimeexpire = 0;
+ if (irr->irr_rrf_decrprefd) {
+ clock_gettime(CLOCK_MONOTONIC_FAST,
+ &now);
+ pfx->pfx_pltimeexpire =
+ now.tv_sec +
+ pfx->pfx_preflifetime;
+ } else
+ pfx->pfx_pltimeexpire = 0;
+ }
+ }
+ }
+ }
+}
+
+/*
+ * process a Prefix Control Operation(PCO).
+ * return 0 on success, 1 on failure
+ */
+static int
+do_pco(struct icmp6_router_renum *rr, int len, struct rr_pco_match *rpm)
+{
+ int ifindex = 0;
+ struct in6_rrenumreq irr;
+ struct ifinfo *ifi;
+
+ if ((rr_pco_check(len, rpm) != 0))
+ return (1);
+
+ if (s == -1 && (s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
+ syslog(LOG_ERR, "<%s> socket: %s", __func__,
+ strerror(errno));
+ exit(1);
+ }
+
+ memset(&irr, 0, sizeof(irr));
+ irr.irr_origin = PR_ORIG_RR;
+ irr.irr_m_len = rpm->rpm_matchlen;
+ irr.irr_m_minlen = rpm->rpm_minlen;
+ irr.irr_m_maxlen = rpm->rpm_maxlen;
+ irr.irr_matchprefix.sin6_len = sizeof(irr.irr_matchprefix);
+ irr.irr_matchprefix.sin6_family = AF_INET6;
+ irr.irr_matchprefix.sin6_addr = rpm->rpm_prefix;
+
+ while (if_indextoname(++ifindex, irr.irr_name)) {
+ ifi = if_indextoifinfo(ifindex);
+ if (ifi == NULL) {
+ syslog(LOG_ERR, "<%s> ifindex not found.",
+ __func__);
+ return (1);
+ }
+ /*
+ * if ICMP6_RR_FLAGS_FORCEAPPLY(A flag) is 0 and
+ * IFF_UP is off, the interface is not applied
+ */
+ if ((rr->rr_flags & ICMP6_RR_FLAGS_FORCEAPPLY) == 0 &&
+ (ifi->ifi_flags & IFF_UP) == 0)
+ continue;
+ /* TODO: interface scope check */
+ do_use_prefix(len, rpm, &irr, ifindex);
+ }
+ if (errno == ENXIO)
+ return (0);
+ else if (errno) {
+ syslog(LOG_ERR, "<%s> if_indextoname: %s", __func__,
+ strerror(errno));
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * call do_pco() for each Prefix Control Operations(PCOs) in a received
+ * Router Renumbering Command packet.
+ * return 0 on success, 1 on failure
+ */
+static int
+do_rr(int len, struct icmp6_router_renum *rr)
+{
+ struct rr_pco_match *rpm;
+ char *cp, *lim;
+
+ lim = (char *)rr + len;
+ cp = (char *)(rr + 1);
+ len -= sizeof(struct icmp6_router_renum);
+
+ update_ifinfo(&ifilist, UPDATE_IFINFO_ALL);
+
+ while (cp < lim) {
+ int rpmlen;
+
+ rpm = (struct rr_pco_match *)cp;
+ if ((size_t)len < sizeof(struct rr_pco_match)) {
+ tooshort:
+ syslog(LOG_ERR, "<%s> pkt too short. left len = %d. "
+ "garbage at end of pkt?", __func__, len);
+ return (1);
+ }
+ rpmlen = rpm->rpm_len << 3;
+ if (len < rpmlen)
+ goto tooshort;
+
+ if (do_pco(rr, rpmlen, rpm)) {
+ syslog(LOG_WARNING, "<%s> invalid PCO", __func__);
+ goto next;
+ }
+
+ next:
+ cp += rpmlen;
+ len -= rpmlen;
+ }
+
+ return (0);
+}
+
+/*
+ * check validity of a router renumbering command packet
+ * return 0 on success, 1 on failure
+ */
+static int
+rr_command_check(int len, struct icmp6_router_renum *rr, struct in6_addr *from,
+ struct in6_addr *dst)
+{
+ u_char ntopbuf[INET6_ADDRSTRLEN];
+
+ /* omit rr minimal length check. hope kernel have done it. */
+ /* rr_command length check */
+ if ((size_t)len < (sizeof(struct icmp6_router_renum) +
+ sizeof(struct rr_pco_match))) {
+ syslog(LOG_ERR, "<%s> rr_command len %d is too short",
+ __func__, len);
+ return (1);
+ }
+
+ /* destination check. only for multicast. omit unicast check. */
+ if (IN6_IS_ADDR_MULTICAST(dst) && !IN6_IS_ADDR_MC_LINKLOCAL(dst) &&
+ !IN6_IS_ADDR_MC_SITELOCAL(dst)) {
+ syslog(LOG_ERR, "<%s> dst mcast addr %s is illegal",
+ __func__,
+ inet_ntop(AF_INET6, dst, ntopbuf, sizeof(ntopbuf)));
+ return (1);
+ }
+
+ /* seqnum and segnum check */
+ if (rro.rro_seqnum > rr->rr_seqnum) {
+ syslog(LOG_WARNING,
+ "<%s> rcvd old seqnum %d from %s",
+ __func__, (u_int32_t)ntohl(rr->rr_seqnum),
+ inet_ntop(AF_INET6, from, ntopbuf, sizeof(ntopbuf)));
+ return (1);
+ }
+ if (rro.rro_seqnum == rr->rr_seqnum &&
+ (rr->rr_flags & ICMP6_RR_FLAGS_TEST) == 0 &&
+ RR_ISSET_SEGNUM(rro.rro_segnum_bits, rr->rr_segnum)) {
+ if ((rr->rr_flags & ICMP6_RR_FLAGS_REQRESULT) != 0)
+ syslog(LOG_WARNING,
+ "<%s> rcvd duped segnum %d from %s",
+ __func__, rr->rr_segnum, inet_ntop(AF_INET6, from,
+ ntopbuf, sizeof(ntopbuf)));
+ return (0);
+ }
+
+ /* update seqnum */
+ if (rro.rro_seqnum != rr->rr_seqnum) {
+ /* then must be "<" */
+
+ /* init rro_segnum_bits */
+ memset(rro.rro_segnum_bits, 0,
+ sizeof(rro.rro_segnum_bits));
+ }
+ rro.rro_seqnum = rr->rr_seqnum;
+
+ return (0);
+}
+
+static void
+rr_command_input(int len, struct icmp6_router_renum *rr,
+ struct in6_addr *from, struct in6_addr *dst)
+{
+ /* rr_command validity check */
+ if (rr_command_check(len, rr, from, dst))
+ goto failed;
+ if ((rr->rr_flags & (ICMP6_RR_FLAGS_TEST|ICMP6_RR_FLAGS_REQRESULT)) ==
+ ICMP6_RR_FLAGS_TEST)
+ return;
+
+ /* do router renumbering */
+ if (do_rr(len, rr))
+ goto failed;
+
+ /* update segnum */
+ RR_SET_SEGNUM(rro.rro_segnum_bits, rr->rr_segnum);
+
+ return;
+
+ failed:
+ syslog(LOG_ERR, "<%s> received RR was invalid", __func__);
+ return;
+}
+
+void
+rr_input(int len, struct icmp6_router_renum *rr, struct in6_pktinfo *pi,
+ struct sockaddr_in6 *from, struct in6_addr *dst)
+{
+ u_char ntopbuf[2][INET6_ADDRSTRLEN], ifnamebuf[IFNAMSIZ];
+
+ syslog(LOG_DEBUG,
+ "<%s> RR received from %s to %s on %s",
+ __func__,
+ inet_ntop(AF_INET6, &from->sin6_addr, ntopbuf[0] ,sizeof(ntopbuf[0])),
+ inet_ntop(AF_INET6, &dst, ntopbuf[1], sizeof(ntopbuf[1])),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+
+ /* packet validation based on Section 4.1 of RFC2894 */
+ if ((size_t)len < sizeof(struct icmp6_router_renum)) {
+ syslog(LOG_NOTICE,
+ "<%s>: RR short message (size %d) from %s to %s on %s",
+ __func__, len,
+ inet_ntop(AF_INET6, &from->sin6_addr, ntopbuf[0],
+ sizeof(ntopbuf[0])),
+ inet_ntop(AF_INET6, &dst, ntopbuf[1], sizeof(ntopbuf[1])),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+ return;
+ }
+
+ /*
+ * If the IPv6 destination address is neither an All Routers multicast
+ * address [AARCH] nor one of the receiving router's unicast addresses,
+ * the message MUST be discarded and SHOULD be logged to network
+ * management.
+ * We rely on the kernel input routine for unicast addresses, and thus
+ * check multicast destinations only.
+ */
+ if (IN6_IS_ADDR_MULTICAST(&pi->ipi6_addr) && !IN6_ARE_ADDR_EQUAL(
+ &sin6_sitelocal_allrouters.sin6_addr, &pi->ipi6_addr)) {
+ syslog(LOG_NOTICE,
+ "<%s>: RR message with invalid destination (%s) "
+ "from %s on %s",
+ __func__,
+ inet_ntop(AF_INET6, &dst, ntopbuf[0], sizeof(ntopbuf[0])),
+ inet_ntop(AF_INET6, &from->sin6_addr, ntopbuf[1],
+ sizeof(ntopbuf[1])),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+ return;
+ }
+
+ rr_rcvifindex = pi->ipi6_ifindex;
+
+ switch (rr->rr_code) {
+ case ICMP6_ROUTER_RENUMBERING_COMMAND:
+ rr_command_input(len, rr, &from->sin6_addr, dst);
+ /* TODO: send reply msg */
+ break;
+ case ICMP6_ROUTER_RENUMBERING_RESULT:
+ /* RESULT will be processed by rrenumd */
+ break;
+ case ICMP6_ROUTER_RENUMBERING_SEQNUM_RESET:
+ /* TODO: sequence number reset */
+ break;
+ default:
+ syslog(LOG_ERR, "<%s> received unknown code %d",
+ __func__, rr->rr_code);
+ break;
+
+ }
+
+ return;
+}
diff --git a/usr.sbin/rtadvd/rrenum.h b/usr.sbin/rtadvd/rrenum.h
new file mode 100644
index 0000000..2b20d59
--- /dev/null
+++ b/usr.sbin/rtadvd/rrenum.h
@@ -0,0 +1,34 @@
+/* $FreeBSD$ */
+/* $KAME: rrenum.h,v 1.3 2001/01/21 15:37:14 itojun Exp $ */
+
+/*
+ * Copyright (C) 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.
+ */
+
+void rr_input(int, struct icmp6_router_renum *, struct in6_pktinfo *,
+ struct sockaddr_in6 *, struct in6_addr *);
diff --git a/usr.sbin/rtadvd/rtadvd.8 b/usr.sbin/rtadvd/rtadvd.8
new file mode 100644
index 0000000..fcb46fe
--- /dev/null
+++ b/usr.sbin/rtadvd/rtadvd.8
@@ -0,0 +1,242 @@
+.\" $KAME: rtadvd.8,v 1.24 2002/05/31 16:16:08 jinmei 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 February 25, 2013
+.Dt RTADVD 8
+.Os
+.Sh NAME
+.Nm rtadvd
+.Nd router advertisement daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl dDfRs
+.Op Fl c Ar configfile
+.Op Fl C Ar ctlsock
+.Op Fl M Ar ifname
+.Op Fl p Ar pidfile
+.Op Ar interface ...
+.Sh DESCRIPTION
+.Nm
+sends router advertisement packets to the specified
+.Ar interfaces .
+If no interfaces are specified,
+.Nm
+will still run, but will not advertise any routes until interfaces are
+added using
+.Xr rtadvctl 8 .
+.Pp
+The program will daemonize itself on invocation.
+It will then send router advertisement packets periodically, as well
+as in response to router solicitation messages sent by end hosts.
+.Pp
+Router advertisements can be configured on a per-interface basis, as
+described in
+.Xr rtadvd.conf 5 .
+.Pp
+If there is no configuration file entry for an interface,
+or if the configuration file does not exist altogether,
+.Nm
+sets all the parameters to their default values.
+In particular,
+.Nm
+reads all the interface routes from the routing table and advertises
+them as on-link prefixes.
+.Pp
+.Nm
+also watches the routing table.
+If an interface direct route is
+added on an advertising interface and no static prefixes are
+specified by the configuration file,
+.Nm
+adds the corresponding prefix to its advertising list.
+.Pp
+Similarly, when an interface direct route is deleted,
+.Nm
+will start advertising the prefixes with zero valid and preferred
+lifetimes to help the receiving hosts switch to a new prefix when
+renumbering.
+Note, however, that the zero valid lifetime cannot invalidate the
+autoconfigured addresses at a receiving host immediately.
+According to the specification, the host will retain the address
+for a certain period, which will typically be two hours.
+The zero lifetimes rather intend to make the address deprecated,
+indicating that a new non-deprecated address should be used as the
+source address of a new connection.
+This behavior will last for two hours.
+Then
+.Nm
+will completely remove the prefix from the advertising list,
+and succeeding advertisements will not contain the prefix information.
+.Pp
+Moreover, if the status of an advertising interface changes,
+.Nm
+will start or stop sending router advertisements according
+to the latest status.
+.Pp
+The
+.Fl s
+option may be used to disable this behavior;
+.Nm
+will not watch the routing table and the whole functionality described
+above will be suppressed.
+.Pp
+Basically, hosts MUST NOT send Router Advertisement messages at any
+time (RFC 4861, Section 6.2.3).
+However, it would sometimes be useful to allow hosts to advertise some
+parameters such as prefix information and link MTU.
+Thus,
+.Nm
+can be invoked if router lifetime is explicitly set zero on every
+advertising interface.
+.Pp
+The command line options are:
+.Bl -tag -width indent
+.\"
+.It Fl c
+Specify an alternate location,
+.Ar configfile ,
+for the configuration file.
+By default,
+.Pa /etc/rtadvd.conf
+is used.
+.It Fl C
+Specify an alternate location for the control socket used by
+.Xr rtadvctl 8 .
+The default is
+.Pa /var/run/rtadvd.sock .
+.It Fl d
+Print debugging information.
+.It Fl D
+Even more debugging information is printed.
+.It Fl f
+Foreground mode (useful when debugging).
+Log messages will be dumped to stderr when this option is specified.
+.It Fl M
+Specify an interface to join the all-routers site-local multicast group.
+By default,
+.Nm
+tries to join the first advertising interface appearing on the command
+line.
+This option has meaning only with the
+.Fl R
+option, which enables routing renumbering protocol support.
+.It Fl p
+Specify an alternative file in which to store the process ID.
+The default is
+.Pa /var/run/rtadvd.pid .
+.It Fl R
+Accept router renumbering requests.
+If you enable it, certain IPsec setup is suggested for security reasons.
+This option is currently disabled, and is ignored by
+.Nm
+with a warning message.
+.It Fl s
+Do not add or delete prefixes dynamically.
+Only statically configured prefixes, if any, will be advertised.
+.El
+.Pp
+Use
+.Dv SIGHUP
+to reload the configuration file
+.Pa /etc/rtadvd.conf .
+If an invalid parameter is found in the configuration file upon the reload,
+the entry will be ignored and the old configuration will be used.
+When parameters in an existing entry are updated,
+.Nm
+will send Router Advertisement messages with the old configuration but
+zero router lifetime to the interface first, and then start to send a new
+message.
+.Pp
+Use
+.Dv SIGTERM
+to kill
+.Nm
+gracefully.
+In this case,
+.Nm
+will transmit router advertisement with router lifetime 0
+to all the interfaces
+.Pq in accordance with RFC 4861 6.2.5 .
+.Sh FILES
+.Bl -tag -width Pa -compact
+.It Pa /etc/rtadvd.conf
+The default configuration file.
+.It Pa /var/run/rtadvd.pid
+The default process ID file.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr rtadvd.conf 5 ,
+.Xr rtadvctl 8 ,
+.Xr rtsol 8
+.Rs
+.%A Thomas Narten
+.%A Erik Nordmark
+.%A W. A. Simpson
+.%A Hesham Soliman
+.%T Neighbor Discovery for IP version 6 (IPv6)
+.%R RFC 4861
+.Re
+.Rs
+.%A Thomas Narten
+.%A Erik Nordmark
+.%A W. A. Simpson
+.%T Neighbor Discovery for IP version 6 (IPv6)
+.%R RFC 2461 (obsoleted by RFC 4861)
+.Re
+.Rs
+.%A Richard Draves
+.%T Default Router Preferences and More-Specific Routes
+.%R draft-ietf-ipngwg-router-selection-xx.txt
+.Re
+.Rs
+.%A J. Jeong
+.%A S. Park
+.%A L. Beloeil
+.%A S. Madanapalli
+.%T IPv6 Router Advertisement Options for DNS Configuration
+.%R RFC 6106
+.Re
+.Sh HISTORY
+The
+.Nm
+command first appeared in the WIDE Hydrangea IPv6 protocol stack kit.
+.Sh BUGS
+There used to be some text that recommended users not to let
+.Nm
+advertise Router Advertisement messages on an upstream link to avoid
+undesirable
+.Xr icmp6 4
+redirect messages.
+However, based on the later discussion in the IETF ipng working group,
+all routers should rather advertise the messages regardless of
+the network topology, in order to ensure reachability.
diff --git a/usr.sbin/rtadvd/rtadvd.c b/usr.sbin/rtadvd/rtadvd.c
new file mode 100644
index 0000000..7694811
--- /dev/null
+++ b/usr.sbin/rtadvd/rtadvd.c
@@ -0,0 +1,1914 @@
+/* $FreeBSD$ */
+/* $KAME: rtadvd.c,v 1.82 2003/08/05 12:34:23 itojun Exp $ */
+
+/*
+ * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
+ * Copyright (C) 2011 Hiroki Sato <hrs@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.
+ * 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/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+
+#include <net/if.h>
+#include <net/if_types.h>
+#include <net/if_media.h>
+#include <net/if_dl.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <netinet6/ip6_var.h>
+#include <netinet/icmp6.h>
+
+#include <arpa/inet.h>
+
+#include <netinet/in_var.h>
+#include <netinet6/nd6.h>
+
+#include <time.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <err.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <libutil.h>
+#include <netdb.h>
+#include <signal.h>
+#include <string.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <poll.h>
+
+#include "pathnames.h"
+#include "rtadvd.h"
+#include "if.h"
+#include "rrenum.h"
+#include "advcap.h"
+#include "timer_subr.h"
+#include "timer.h"
+#include "config.h"
+#include "control.h"
+#include "control_server.h"
+
+#define RTADV_TYPE2BITMASK(type) (0x1 << type)
+
+struct msghdr rcvmhdr;
+static char *rcvcmsgbuf;
+static size_t rcvcmsgbuflen;
+static char *sndcmsgbuf = NULL;
+static size_t sndcmsgbuflen;
+struct msghdr sndmhdr;
+struct iovec rcviov[2];
+struct iovec sndiov[2];
+struct sockaddr_in6 rcvfrom;
+static const char *pidfilename = _PATH_RTADVDPID;
+const char *conffile = _PATH_RTADVDCONF;
+static struct pidfh *pfh;
+static int dflag, sflag;
+static int wait_shutdown;
+
+#define PFD_RAWSOCK 0
+#define PFD_RTSOCK 1
+#define PFD_CSOCK 2
+#define PFD_MAX 3
+
+struct railist_head_t railist =
+ TAILQ_HEAD_INITIALIZER(railist);
+struct ifilist_head_t ifilist =
+ TAILQ_HEAD_INITIALIZER(ifilist);
+
+struct nd_optlist {
+ TAILQ_ENTRY(nd_optlist) nol_next;
+ struct nd_opt_hdr *nol_opt;
+};
+union nd_opt {
+ struct nd_opt_hdr *opt_array[9];
+ struct {
+ struct nd_opt_hdr *zero;
+ struct nd_opt_hdr *src_lladdr;
+ struct nd_opt_hdr *tgt_lladdr;
+ struct nd_opt_prefix_info *pi;
+ struct nd_opt_rd_hdr *rh;
+ struct nd_opt_mtu *mtu;
+ TAILQ_HEAD(, nd_optlist) opt_list;
+ } nd_opt_each;
+};
+#define opt_src_lladdr nd_opt_each.src_lladdr
+#define opt_tgt_lladdr nd_opt_each.tgt_lladdr
+#define opt_pi nd_opt_each.pi
+#define opt_rh nd_opt_each.rh
+#define opt_mtu nd_opt_each.mtu
+#define opt_list nd_opt_each.opt_list
+
+#define NDOPT_FLAG_SRCLINKADDR (1 << 0)
+#define NDOPT_FLAG_TGTLINKADDR (1 << 1)
+#define NDOPT_FLAG_PREFIXINFO (1 << 2)
+#define NDOPT_FLAG_RDHDR (1 << 3)
+#define NDOPT_FLAG_MTU (1 << 4)
+#define NDOPT_FLAG_RDNSS (1 << 5)
+#define NDOPT_FLAG_DNSSL (1 << 6)
+
+static uint32_t ndopt_flags[] = {
+ [ND_OPT_SOURCE_LINKADDR] = NDOPT_FLAG_SRCLINKADDR,
+ [ND_OPT_TARGET_LINKADDR] = NDOPT_FLAG_TGTLINKADDR,
+ [ND_OPT_PREFIX_INFORMATION] = NDOPT_FLAG_PREFIXINFO,
+ [ND_OPT_REDIRECTED_HEADER] = NDOPT_FLAG_RDHDR,
+ [ND_OPT_MTU] = NDOPT_FLAG_MTU,
+ [ND_OPT_RDNSS] = NDOPT_FLAG_RDNSS,
+ [ND_OPT_DNSSL] = NDOPT_FLAG_DNSSL,
+};
+
+static void rtadvd_shutdown(void);
+static void sock_open(struct sockinfo *);
+static void rtsock_open(struct sockinfo *);
+static void rtadvd_input(struct sockinfo *);
+static void rs_input(int, struct nd_router_solicit *,
+ struct in6_pktinfo *, struct sockaddr_in6 *);
+static void ra_input(int, struct nd_router_advert *,
+ struct in6_pktinfo *, struct sockaddr_in6 *);
+static int prefix_check(struct nd_opt_prefix_info *, struct rainfo *,
+ struct sockaddr_in6 *);
+static int nd6_options(struct nd_opt_hdr *, int,
+ union nd_opt *, uint32_t);
+static void free_ndopts(union nd_opt *);
+static void rtmsg_input(struct sockinfo *);
+static void set_short_delay(struct ifinfo *);
+static int check_accept_rtadv(int);
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: rtadvd [-dDfRs] "
+ "[-c configfile] [-C ctlsock] [-M ifname] [-p pidfile]\n");
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct pollfd set[PFD_MAX];
+ struct timespec *timeout;
+ int i, ch;
+ int fflag = 0, logopt;
+ int error;
+ pid_t pid, otherpid;
+
+ /* get command line options and arguments */
+ while ((ch = getopt(argc, argv, "c:C:dDfhM:p:Rs")) != -1) {
+ switch (ch) {
+ case 'c':
+ conffile = optarg;
+ break;
+ case 'C':
+ ctrlsock.si_name = optarg;
+ break;
+ case 'd':
+ dflag++;
+ break;
+ case 'D':
+ dflag += 3;
+ break;
+ case 'f':
+ fflag = 1;
+ break;
+ case 'M':
+ mcastif = optarg;
+ break;
+ case 'R':
+ fprintf(stderr, "rtadvd: "
+ "the -R option is currently ignored.\n");
+ /* accept_rr = 1; */
+ /* run anyway... */
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ case 'p':
+ pidfilename = optarg;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ logopt = LOG_NDELAY | LOG_PID;
+ if (fflag)
+ logopt |= LOG_PERROR;
+ openlog("rtadvd", logopt, LOG_DAEMON);
+
+ /* set log level */
+ if (dflag > 2)
+ (void)setlogmask(LOG_UPTO(LOG_DEBUG));
+ else if (dflag > 1)
+ (void)setlogmask(LOG_UPTO(LOG_INFO));
+ else if (dflag > 0)
+ (void)setlogmask(LOG_UPTO(LOG_NOTICE));
+ else
+ (void)setlogmask(LOG_UPTO(LOG_ERR));
+
+ /* timer initialization */
+ rtadvd_timer_init();
+
+ pfh = pidfile_open(pidfilename, 0600, &otherpid);
+ if (pfh == NULL) {
+ if (errno == EEXIST)
+ errx(1, "%s already running, pid: %d",
+ getprogname(), otherpid);
+ syslog(LOG_ERR,
+ "failed to open the pid file %s, run anyway.",
+ pidfilename);
+ }
+ if (!fflag)
+ daemon(1, 0);
+
+ sock_open(&sock);
+
+ update_ifinfo(&ifilist, UPDATE_IFINFO_ALL);
+ for (i = 0; i < argc; i++)
+ update_persist_ifinfo(&ifilist, argv[i]);
+
+ csock_open(&ctrlsock, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
+ if (ctrlsock.si_fd == -1) {
+ syslog(LOG_ERR, "cannot open control socket: %s",
+ strerror(errno));
+ exit(1);
+ }
+
+ /* record the current PID */
+ pid = getpid();
+ pidfile_write(pfh);
+
+ set[PFD_RAWSOCK].fd = sock.si_fd;
+ set[PFD_RAWSOCK].events = POLLIN;
+ if (sflag == 0) {
+ rtsock_open(&rtsock);
+ set[PFD_RTSOCK].fd = rtsock.si_fd;
+ set[PFD_RTSOCK].events = POLLIN;
+ } else
+ set[PFD_RTSOCK].fd = -1;
+ set[PFD_CSOCK].fd = ctrlsock.si_fd;
+ set[PFD_CSOCK].events = POLLIN;
+ signal(SIGTERM, set_do_shutdown);
+ signal(SIGINT, set_do_shutdown);
+ signal(SIGHUP, set_do_reload);
+
+ error = csock_listen(&ctrlsock);
+ if (error) {
+ syslog(LOG_ERR, "cannot listen control socket: %s",
+ strerror(errno));
+ exit(1);
+ }
+
+ /* load configuration file */
+ set_do_reload(0);
+
+ while (1) {
+ if (is_do_shutdown())
+ rtadvd_shutdown();
+
+ if (is_do_reload()) {
+ loadconfig_ifname(reload_ifname());
+ if (reload_ifname() == NULL)
+ syslog(LOG_INFO,
+ "configuration file reloaded.");
+ else
+ syslog(LOG_INFO,
+ "configuration file for %s reloaded.",
+ reload_ifname());
+ reset_do_reload();
+ }
+
+ /* timeout handler update for active interfaces */
+ rtadvd_update_timeout_handler();
+
+ /* timer expiration check and reset the timer */
+ timeout = rtadvd_check_timer();
+
+ if (timeout != NULL) {
+ syslog(LOG_DEBUG,
+ "<%s> set timer to %ld:%ld. waiting for "
+ "inputs or timeout", __func__,
+ (long int)timeout->tv_sec,
+ (long int)timeout->tv_nsec / 1000);
+ } else {
+ syslog(LOG_DEBUG,
+ "<%s> there's no timer. waiting for inputs",
+ __func__);
+ }
+ if ((i = poll(set, sizeof(set)/sizeof(set[0]),
+ timeout ? (timeout->tv_sec * 1000 +
+ timeout->tv_nsec / 1000 / 1000) : INFTIM)) < 0) {
+
+ /* EINTR would occur if a signal was delivered */
+ if (errno != EINTR)
+ syslog(LOG_ERR, "poll() failed: %s",
+ strerror(errno));
+ continue;
+ }
+ if (i == 0) /* timeout */
+ continue;
+ if (rtsock.si_fd != -1 && set[PFD_RTSOCK].revents & POLLIN)
+ rtmsg_input(&rtsock);
+
+ if (set[PFD_RAWSOCK].revents & POLLIN)
+ rtadvd_input(&sock);
+
+ if (set[PFD_CSOCK].revents & POLLIN) {
+ int fd;
+
+ fd = csock_accept(&ctrlsock);
+ if (fd == -1)
+ syslog(LOG_ERR,
+ "cannot accept() control socket: %s",
+ strerror(errno));
+ else {
+ cm_handler_server(fd);
+ close(fd);
+ }
+ }
+ }
+ exit(0); /* NOTREACHED */
+}
+
+static void
+rtadvd_shutdown(void)
+{
+ struct ifinfo *ifi;
+ struct rainfo *rai;
+ struct rdnss *rdn;
+ struct dnssl *dns;
+
+ if (wait_shutdown) {
+ syslog(LOG_INFO,
+ "waiting expiration of the all RA timers.");
+
+ TAILQ_FOREACH(ifi, &ifilist, ifi_next) {
+ /*
+ * Ignore !IFF_UP interfaces in waiting for shutdown.
+ */
+ if (!(ifi->ifi_flags & IFF_UP) &&
+ ifi->ifi_ra_timer != NULL) {
+ ifi->ifi_state = IFI_STATE_UNCONFIGURED;
+ rtadvd_remove_timer(ifi->ifi_ra_timer);
+ ifi->ifi_ra_timer = NULL;
+ syslog(LOG_DEBUG, "<%s> %s(idx=%d) is down. "
+ "Timer removed and marked as UNCONFIGURED.",
+ __func__, ifi->ifi_ifname,
+ ifi->ifi_ifindex);
+ }
+ }
+ TAILQ_FOREACH(ifi, &ifilist, ifi_next) {
+ if (ifi->ifi_ra_timer != NULL)
+ break;
+ }
+ if (ifi == NULL) {
+ syslog(LOG_NOTICE, "gracefully terminated.");
+ exit(0);
+ }
+
+ sleep(1);
+ return;
+ }
+
+ syslog(LOG_DEBUG, "<%s> cease to be an advertising router",
+ __func__);
+
+ wait_shutdown = 1;
+
+ TAILQ_FOREACH(rai, &railist, rai_next) {
+ rai->rai_lifetime = 0;
+ TAILQ_FOREACH(rdn, &rai->rai_rdnss, rd_next)
+ rdn->rd_ltime = 0;
+ TAILQ_FOREACH(dns, &rai->rai_dnssl, dn_next)
+ dns->dn_ltime = 0;
+ }
+ TAILQ_FOREACH(ifi, &ifilist, ifi_next) {
+ if (!ifi->ifi_persist)
+ continue;
+ if (ifi->ifi_state == IFI_STATE_UNCONFIGURED)
+ continue;
+ if (ifi->ifi_ra_timer == NULL)
+ continue;
+ if (ifi->ifi_ra_lastsent.tv_sec == 0 &&
+ ifi->ifi_ra_lastsent.tv_nsec == 0 &&
+ ifi->ifi_ra_timer != NULL) {
+ /*
+ * When RA configured but never sent,
+ * ignore the IF immediately.
+ */
+ rtadvd_remove_timer(ifi->ifi_ra_timer);
+ ifi->ifi_ra_timer = NULL;
+ ifi->ifi_state = IFI_STATE_UNCONFIGURED;
+ continue;
+ }
+
+ ifi->ifi_state = IFI_STATE_TRANSITIVE;
+
+ /* Mark as the shut-down state. */
+ ifi->ifi_rainfo_trans = ifi->ifi_rainfo;
+ ifi->ifi_rainfo = NULL;
+
+ ifi->ifi_burstcount = MAX_FINAL_RTR_ADVERTISEMENTS;
+ ifi->ifi_burstinterval = MIN_DELAY_BETWEEN_RAS;
+
+ ra_timer_update(ifi, &ifi->ifi_ra_timer->rat_tm);
+ rtadvd_set_timer(&ifi->ifi_ra_timer->rat_tm,
+ ifi->ifi_ra_timer);
+ }
+ syslog(LOG_NOTICE, "final RA transmission started.");
+
+ pidfile_remove(pfh);
+ csock_close(&ctrlsock);
+}
+
+static void
+rtmsg_input(struct sockinfo *s)
+{
+ int n, type, ifindex = 0, plen;
+ size_t len;
+ char msg[2048], *next, *lim;
+ char ifname[IFNAMSIZ];
+ struct if_announcemsghdr *ifan;
+ struct rt_msghdr *rtm;
+ struct prefix *pfx;
+ struct rainfo *rai;
+ struct in6_addr *addr;
+ struct ifinfo *ifi;
+ char addrbuf[INET6_ADDRSTRLEN];
+ int prefixchange = 0;
+
+ if (s == NULL) {
+ syslog(LOG_ERR, "<%s> internal error", __func__);
+ exit(1);
+ }
+ n = read(s->si_fd, msg, sizeof(msg));
+ rtm = (struct rt_msghdr *)msg;
+ syslog(LOG_DEBUG, "<%s> received a routing message "
+ "(type = %d, len = %d)", __func__, rtm->rtm_type, n);
+
+ if (n > rtm->rtm_msglen) {
+ /*
+ * This usually won't happen for messages received on
+ * a routing socket.
+ */
+ syslog(LOG_DEBUG,
+ "<%s> received data length is larger than "
+ "1st routing message len. multiple messages? "
+ "read %d bytes, but 1st msg len = %d",
+ __func__, n, rtm->rtm_msglen);
+#if 0
+ /* adjust length */
+ n = rtm->rtm_msglen;
+#endif
+ }
+
+ lim = msg + n;
+ for (next = msg; next < lim; next += len) {
+ int oldifflags;
+
+ next = get_next_msg(next, lim, 0, &len,
+ RTADV_TYPE2BITMASK(RTM_ADD) |
+ RTADV_TYPE2BITMASK(RTM_DELETE) |
+ RTADV_TYPE2BITMASK(RTM_NEWADDR) |
+ RTADV_TYPE2BITMASK(RTM_DELADDR) |
+ RTADV_TYPE2BITMASK(RTM_IFINFO) |
+ RTADV_TYPE2BITMASK(RTM_IFANNOUNCE));
+ if (len == 0)
+ break;
+ type = ((struct rt_msghdr *)next)->rtm_type;
+ switch (type) {
+ case RTM_ADD:
+ case RTM_DELETE:
+ ifindex = get_rtm_ifindex(next);
+ break;
+ case RTM_NEWADDR:
+ case RTM_DELADDR:
+ ifindex = (int)((struct ifa_msghdr *)next)->ifam_index;
+ break;
+ case RTM_IFINFO:
+ ifindex = (int)((struct if_msghdr *)next)->ifm_index;
+ break;
+ case RTM_IFANNOUNCE:
+ ifan = (struct if_announcemsghdr *)next;
+ switch (ifan->ifan_what) {
+ case IFAN_ARRIVAL:
+ case IFAN_DEPARTURE:
+ break;
+ default:
+ syslog(LOG_DEBUG,
+ "<%s:%d> unknown ifan msg (ifan_what=%d)",
+ __func__, __LINE__, ifan->ifan_what);
+ continue;
+ }
+
+ syslog(LOG_DEBUG, "<%s>: if_announcemsg (idx=%d:%d)",
+ __func__, ifan->ifan_index, ifan->ifan_what);
+ switch (ifan->ifan_what) {
+ case IFAN_ARRIVAL:
+ syslog(LOG_NOTICE,
+ "interface added (idx=%d)",
+ ifan->ifan_index);
+ update_ifinfo(&ifilist, ifan->ifan_index);
+ loadconfig_index(ifan->ifan_index);
+ break;
+ case IFAN_DEPARTURE:
+ syslog(LOG_NOTICE,
+ "interface removed (idx=%d)",
+ ifan->ifan_index);
+ rm_ifinfo_index(ifan->ifan_index);
+
+ /* Clear ifi_ifindex */
+ TAILQ_FOREACH(ifi, &ifilist, ifi_next) {
+ if (ifi->ifi_ifindex
+ == ifan->ifan_index) {
+ ifi->ifi_ifindex = 0;
+ break;
+ }
+ }
+ update_ifinfo(&ifilist, ifan->ifan_index);
+ break;
+ }
+ continue;
+ default:
+ /* should not reach here */
+ syslog(LOG_DEBUG,
+ "<%s:%d> unknown rtmsg %d on %s",
+ __func__, __LINE__, type,
+ if_indextoname(ifindex, ifname));
+ continue;
+ }
+ ifi = if_indextoifinfo(ifindex);
+ if (ifi == NULL) {
+ syslog(LOG_DEBUG,
+ "<%s> ifinfo not found for idx=%d. Why?",
+ __func__, ifindex);
+ continue;
+ }
+ rai = ifi->ifi_rainfo;
+ if (rai == NULL) {
+ syslog(LOG_DEBUG,
+ "<%s> route changed on "
+ "non advertising interface(%s)",
+ __func__, ifi->ifi_ifname);
+ continue;
+ }
+
+ oldifflags = ifi->ifi_flags;
+ /* init ifflags because it may have changed */
+ update_ifinfo(&ifilist, ifindex);
+
+ switch (type) {
+ case RTM_ADD:
+ if (sflag)
+ break; /* we aren't interested in prefixes */
+
+ addr = get_addr(msg);
+ plen = get_prefixlen(msg);
+ /* sanity check for plen */
+ /* as RFC2373, prefixlen is at least 4 */
+ if (plen < 4 || plen > 127) {
+ syslog(LOG_INFO, "<%s> new interface route's"
+ "plen %d is invalid for a prefix",
+ __func__, plen);
+ break;
+ }
+ pfx = find_prefix(rai, addr, plen);
+ if (pfx) {
+ if (pfx->pfx_timer) {
+ /*
+ * If the prefix has been invalidated,
+ * make it available again.
+ */
+ update_prefix(pfx);
+ prefixchange = 1;
+ } else
+ syslog(LOG_DEBUG,
+ "<%s> new prefix(%s/%d) "
+ "added on %s, "
+ "but it was already in list",
+ __func__,
+ inet_ntop(AF_INET6, addr,
+ (char *)addrbuf,
+ sizeof(addrbuf)),
+ plen, ifi->ifi_ifname);
+ break;
+ }
+ make_prefix(rai, ifindex, addr, plen);
+ prefixchange = 1;
+ break;
+ case RTM_DELETE:
+ if (sflag)
+ break;
+
+ addr = get_addr(msg);
+ plen = get_prefixlen(msg);
+ /* sanity check for plen */
+ /* as RFC2373, prefixlen is at least 4 */
+ if (plen < 4 || plen > 127) {
+ syslog(LOG_INFO,
+ "<%s> deleted interface route's "
+ "plen %d is invalid for a prefix",
+ __func__, plen);
+ break;
+ }
+ pfx = find_prefix(rai, addr, plen);
+ if (pfx == NULL) {
+ syslog(LOG_DEBUG,
+ "<%s> prefix(%s/%d) was deleted on %s, "
+ "but it was not in list",
+ __func__, inet_ntop(AF_INET6, addr,
+ (char *)addrbuf, sizeof(addrbuf)),
+ plen, ifi->ifi_ifname);
+ break;
+ }
+ invalidate_prefix(pfx);
+ prefixchange = 1;
+ break;
+ case RTM_NEWADDR:
+ case RTM_DELADDR:
+ case RTM_IFINFO:
+ break;
+ default:
+ /* should not reach here */
+ syslog(LOG_DEBUG,
+ "<%s:%d> unknown rtmsg %d on %s",
+ __func__, __LINE__, type,
+ if_indextoname(ifindex, ifname));
+ return;
+ }
+
+ /* check if an interface flag is changed */
+ if ((oldifflags & IFF_UP) && /* UP to DOWN */
+ !(ifi->ifi_flags & IFF_UP)) {
+ syslog(LOG_NOTICE,
+ "<interface %s becomes down. stop timer.",
+ ifi->ifi_ifname);
+ rtadvd_remove_timer(ifi->ifi_ra_timer);
+ ifi->ifi_ra_timer = NULL;
+ } else if (!(oldifflags & IFF_UP) && /* DOWN to UP */
+ (ifi->ifi_flags & IFF_UP)) {
+ syslog(LOG_NOTICE,
+ "interface %s becomes up. restart timer.",
+ ifi->ifi_ifname);
+
+ ifi->ifi_state = IFI_STATE_TRANSITIVE;
+ ifi->ifi_burstcount =
+ MAX_INITIAL_RTR_ADVERTISEMENTS;
+ ifi->ifi_burstinterval =
+ MAX_INITIAL_RTR_ADVERT_INTERVAL;
+
+ ifi->ifi_ra_timer = rtadvd_add_timer(ra_timeout,
+ ra_timer_update, ifi, ifi);
+ ra_timer_update(ifi, &ifi->ifi_ra_timer->rat_tm);
+ rtadvd_set_timer(&ifi->ifi_ra_timer->rat_tm,
+ ifi->ifi_ra_timer);
+ } else if (prefixchange &&
+ (ifi->ifi_flags & IFF_UP)) {
+ /*
+ * An advertised prefix has been added or invalidated.
+ * Will notice the change in a short delay.
+ */
+ set_short_delay(ifi);
+ }
+ }
+
+ return;
+}
+
+void
+rtadvd_input(struct sockinfo *s)
+{
+ ssize_t i;
+ int *hlimp = NULL;
+#ifdef OLDRAWSOCKET
+ struct ip6_hdr *ip;
+#endif
+ struct icmp6_hdr *icp;
+ int ifindex = 0;
+ struct cmsghdr *cm;
+ struct in6_pktinfo *pi = NULL;
+ char ntopbuf[INET6_ADDRSTRLEN], ifnamebuf[IFNAMSIZ];
+ struct in6_addr dst = in6addr_any;
+ struct ifinfo *ifi;
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ if (s == NULL) {
+ syslog(LOG_ERR, "<%s> internal error", __func__);
+ exit(1);
+ }
+ /*
+ * Get message. We reset msg_controllen since the field could
+ * be modified if we had received a message before setting
+ * receive options.
+ */
+ rcvmhdr.msg_controllen = rcvcmsgbuflen;
+ if ((i = recvmsg(s->si_fd, &rcvmhdr, 0)) < 0)
+ return;
+
+ /* extract optional information via Advanced API */
+ for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&rcvmhdr);
+ cm;
+ cm = (struct cmsghdr *)CMSG_NXTHDR(&rcvmhdr, cm)) {
+ if (cm->cmsg_level == IPPROTO_IPV6 &&
+ cm->cmsg_type == IPV6_PKTINFO &&
+ cm->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo))) {
+ pi = (struct in6_pktinfo *)(CMSG_DATA(cm));
+ ifindex = pi->ipi6_ifindex;
+ dst = pi->ipi6_addr;
+ }
+ if (cm->cmsg_level == IPPROTO_IPV6 &&
+ cm->cmsg_type == IPV6_HOPLIMIT &&
+ cm->cmsg_len == CMSG_LEN(sizeof(int)))
+ hlimp = (int *)CMSG_DATA(cm);
+ }
+ if (ifindex == 0) {
+ syslog(LOG_ERR, "failed to get receiving interface");
+ return;
+ }
+ if (hlimp == NULL) {
+ syslog(LOG_ERR, "failed to get receiving hop limit");
+ return;
+ }
+
+ /*
+ * If we happen to receive data on an interface which is now gone
+ * or down, just discard the data.
+ */
+ ifi = if_indextoifinfo(pi->ipi6_ifindex);
+ if (ifi == NULL || !(ifi->ifi_flags & IFF_UP)) {
+ syslog(LOG_INFO,
+ "<%s> received data on a disabled interface (%s)",
+ __func__,
+ (ifi == NULL) ? "[gone]" : ifi->ifi_ifname);
+ return;
+ }
+
+#ifdef OLDRAWSOCKET
+ if ((size_t)i < sizeof(struct ip6_hdr) + sizeof(struct icmp6_hdr)) {
+ syslog(LOG_ERR,
+ "packet size(%d) is too short", i);
+ return;
+ }
+
+ ip = (struct ip6_hdr *)rcvmhdr.msg_iov[0].iov_base;
+ icp = (struct icmp6_hdr *)(ip + 1); /* XXX: ext. hdr? */
+#else
+ if ((size_t)i < sizeof(struct icmp6_hdr)) {
+ syslog(LOG_ERR, "packet size(%zd) is too short", i);
+ return;
+ }
+
+ icp = (struct icmp6_hdr *)rcvmhdr.msg_iov[0].iov_base;
+#endif
+
+ switch (icp->icmp6_type) {
+ case ND_ROUTER_SOLICIT:
+ /*
+ * Message verification - RFC 4861 6.1.1
+ * XXX: these checks must be done in the kernel as well,
+ * but we can't completely rely on them.
+ */
+ if (*hlimp != 255) {
+ syslog(LOG_NOTICE,
+ "RS with invalid hop limit(%d) "
+ "received from %s on %s",
+ *hlimp,
+ inet_ntop(AF_INET6, &rcvfrom.sin6_addr, ntopbuf,
+ sizeof(ntopbuf)),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+ return;
+ }
+ if (icp->icmp6_code) {
+ syslog(LOG_NOTICE,
+ "RS with invalid ICMP6 code(%d) "
+ "received from %s on %s",
+ icp->icmp6_code,
+ inet_ntop(AF_INET6, &rcvfrom.sin6_addr, ntopbuf,
+ sizeof(ntopbuf)),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+ return;
+ }
+ if ((size_t)i < sizeof(struct nd_router_solicit)) {
+ syslog(LOG_NOTICE,
+ "RS from %s on %s does not have enough "
+ "length (len = %zd)",
+ inet_ntop(AF_INET6, &rcvfrom.sin6_addr, ntopbuf,
+ sizeof(ntopbuf)),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf), i);
+ return;
+ }
+ rs_input(i, (struct nd_router_solicit *)icp, pi, &rcvfrom);
+ break;
+ case ND_ROUTER_ADVERT:
+ /*
+ * Message verification - RFC 4861 6.1.2
+ * XXX: there's the same dilemma as above...
+ */
+ if (!IN6_IS_ADDR_LINKLOCAL(&rcvfrom.sin6_addr)) {
+ syslog(LOG_NOTICE,
+ "RA with non-linklocal source address "
+ "received from %s on %s",
+ inet_ntop(AF_INET6, &rcvfrom.sin6_addr,
+ ntopbuf, sizeof(ntopbuf)),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+ return;
+ }
+ if (*hlimp != 255) {
+ syslog(LOG_NOTICE,
+ "RA with invalid hop limit(%d) "
+ "received from %s on %s",
+ *hlimp,
+ inet_ntop(AF_INET6, &rcvfrom.sin6_addr, ntopbuf,
+ sizeof(ntopbuf)),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+ return;
+ }
+ if (icp->icmp6_code) {
+ syslog(LOG_NOTICE,
+ "RA with invalid ICMP6 code(%d) "
+ "received from %s on %s",
+ icp->icmp6_code,
+ inet_ntop(AF_INET6, &rcvfrom.sin6_addr, ntopbuf,
+ sizeof(ntopbuf)),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+ return;
+ }
+ if ((size_t)i < sizeof(struct nd_router_advert)) {
+ syslog(LOG_NOTICE,
+ "RA from %s on %s does not have enough "
+ "length (len = %zd)",
+ inet_ntop(AF_INET6, &rcvfrom.sin6_addr, ntopbuf,
+ sizeof(ntopbuf)),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf), i);
+ return;
+ }
+ ra_input(i, (struct nd_router_advert *)icp, pi, &rcvfrom);
+ break;
+ case ICMP6_ROUTER_RENUMBERING:
+ if (mcastif == NULL) {
+ syslog(LOG_ERR, "received a router renumbering "
+ "message, but not allowed to be accepted");
+ break;
+ }
+ rr_input(i, (struct icmp6_router_renum *)icp, pi, &rcvfrom,
+ &dst);
+ break;
+ default:
+ /*
+ * Note that this case is POSSIBLE, especially just
+ * after invocation of the daemon. This is because we
+ * could receive message after opening the socket and
+ * before setting ICMP6 type filter(see sock_open()).
+ */
+ syslog(LOG_ERR, "invalid icmp type(%d)", icp->icmp6_type);
+ return;
+ }
+
+ return;
+}
+
+static void
+rs_input(int len, struct nd_router_solicit *rs,
+ struct in6_pktinfo *pi, struct sockaddr_in6 *from)
+{
+ char ntopbuf[INET6_ADDRSTRLEN];
+ char ifnamebuf[IFNAMSIZ];
+ union nd_opt ndopts;
+ struct rainfo *rai;
+ struct ifinfo *ifi;
+ struct soliciter *sol;
+
+ syslog(LOG_DEBUG,
+ "<%s> RS received from %s on %s",
+ __func__,
+ inet_ntop(AF_INET6, &from->sin6_addr, ntopbuf, sizeof(ntopbuf)),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+
+ /* ND option check */
+ memset(&ndopts, 0, sizeof(ndopts));
+ TAILQ_INIT(&ndopts.opt_list);
+ if (nd6_options((struct nd_opt_hdr *)(rs + 1),
+ len - sizeof(struct nd_router_solicit),
+ &ndopts, NDOPT_FLAG_SRCLINKADDR)) {
+ syslog(LOG_INFO,
+ "<%s> ND option check failed for an RS from %s on %s",
+ __func__,
+ inet_ntop(AF_INET6, &from->sin6_addr, ntopbuf,
+ sizeof(ntopbuf)),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+ return;
+ }
+
+ /*
+ * If the IP source address is the unspecified address, there
+ * must be no source link-layer address option in the message.
+ * (RFC 4861 6.1.1)
+ */
+ if (IN6_IS_ADDR_UNSPECIFIED(&from->sin6_addr) &&
+ ndopts.opt_src_lladdr) {
+ syslog(LOG_INFO,
+ "<%s> RS from unspecified src on %s has a link-layer"
+ " address option",
+ __func__, if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+ goto done;
+ }
+
+ ifi = if_indextoifinfo(pi->ipi6_ifindex);
+ if (ifi == NULL) {
+ syslog(LOG_INFO,
+ "<%s> if (idx=%d) not found. Why?",
+ __func__, pi->ipi6_ifindex);
+ goto done;
+ }
+ rai = ifi->ifi_rainfo;
+ if (rai == NULL) {
+ syslog(LOG_INFO,
+ "<%s> RS received on non advertising interface(%s)",
+ __func__,
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+ goto done;
+ }
+
+ rai->rai_ifinfo->ifi_rsinput++;
+
+ /*
+ * Decide whether to send RA according to the rate-limit
+ * consideration.
+ */
+
+ /* record sockaddr waiting for RA, if possible */
+ sol = (struct soliciter *)malloc(sizeof(*sol));
+ if (sol) {
+ sol->sol_addr = *from;
+ /* XXX RFC 2553 need clarification on flowinfo */
+ sol->sol_addr.sin6_flowinfo = 0;
+ TAILQ_INSERT_TAIL(&rai->rai_soliciter, sol, sol_next);
+ }
+
+ /*
+ * If there is already a waiting RS packet, don't
+ * update the timer.
+ */
+ if (ifi->ifi_rs_waitcount++)
+ goto done;
+
+ set_short_delay(ifi);
+
+ done:
+ free_ndopts(&ndopts);
+ return;
+}
+
+static void
+set_short_delay(struct ifinfo *ifi)
+{
+ long delay; /* must not be greater than 1000000 */
+ struct timespec interval, now, min_delay, tm_tmp, *rest;
+
+ if (ifi->ifi_ra_timer == NULL)
+ return;
+ /*
+ * Compute a random delay. If the computed value
+ * corresponds to a time later than the time the next
+ * multicast RA is scheduled to be sent, ignore the random
+ * delay and send the advertisement at the
+ * already-scheduled time. RFC 4861 6.2.6
+ */
+ delay = arc4random_uniform(MAX_RA_DELAY_TIME);
+ interval.tv_sec = 0;
+ interval.tv_nsec = delay * 1000;
+ rest = rtadvd_timer_rest(ifi->ifi_ra_timer);
+ if (TS_CMP(rest, &interval, <)) {
+ syslog(LOG_DEBUG, "<%s> random delay is larger than "
+ "the rest of the current timer", __func__);
+ interval = *rest;
+ }
+
+ /*
+ * If we sent a multicast Router Advertisement within
+ * the last MIN_DELAY_BETWEEN_RAS seconds, schedule
+ * the advertisement to be sent at a time corresponding to
+ * MIN_DELAY_BETWEEN_RAS plus the random value after the
+ * previous advertisement was sent.
+ */
+ clock_gettime(CLOCK_MONOTONIC_FAST, &now);
+ TS_SUB(&now, &ifi->ifi_ra_lastsent, &tm_tmp);
+ min_delay.tv_sec = MIN_DELAY_BETWEEN_RAS;
+ min_delay.tv_nsec = 0;
+ if (TS_CMP(&tm_tmp, &min_delay, <)) {
+ TS_SUB(&min_delay, &tm_tmp, &min_delay);
+ TS_ADD(&min_delay, &interval, &interval);
+ }
+ rtadvd_set_timer(&interval, ifi->ifi_ra_timer);
+}
+
+static int
+check_accept_rtadv(int idx)
+{
+ struct ifinfo *ifi;
+
+ TAILQ_FOREACH(ifi, &ifilist, ifi_next) {
+ if (ifi->ifi_ifindex == idx)
+ break;
+ }
+ if (ifi == NULL) {
+ syslog(LOG_DEBUG,
+ "<%s> if (idx=%d) not found. Why?",
+ __func__, idx);
+ return (0);
+ }
+#if (__FreeBSD_version < 900000)
+ /*
+ * RA_RECV: !ip6.forwarding && ip6.accept_rtadv
+ * RA_SEND: ip6.forwarding
+ */
+ return ((getinet6sysctl(IPV6CTL_FORWARDING) == 0) &&
+ (getinet6sysctl(IPV6CTL_ACCEPT_RTADV) == 1));
+#else
+ /*
+ * RA_RECV: ND6_IFF_ACCEPT_RTADV
+ * RA_SEND: ip6.forwarding
+ */
+ if (update_ifinfo_nd_flags(ifi) != 0) {
+ syslog(LOG_ERR, "cannot get nd6 flags (idx=%d)", idx);
+ return (0);
+ }
+
+ return (ifi->ifi_nd_flags & ND6_IFF_ACCEPT_RTADV);
+#endif
+}
+
+static void
+ra_input(int len, struct nd_router_advert *nra,
+ struct in6_pktinfo *pi, struct sockaddr_in6 *from)
+{
+ struct rainfo *rai;
+ struct ifinfo *ifi;
+ char ntopbuf[INET6_ADDRSTRLEN];
+ char ifnamebuf[IFNAMSIZ];
+ union nd_opt ndopts;
+ const char *on_off[] = {"OFF", "ON"};
+ uint32_t reachabletime, retranstimer, mtu;
+ int inconsistent = 0;
+ int error;
+
+ syslog(LOG_DEBUG, "<%s> RA received from %s on %s", __func__,
+ inet_ntop(AF_INET6, &from->sin6_addr, ntopbuf, sizeof(ntopbuf)),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+
+ /* ND option check */
+ memset(&ndopts, 0, sizeof(ndopts));
+ TAILQ_INIT(&ndopts.opt_list);
+ error = nd6_options((struct nd_opt_hdr *)(nra + 1),
+ len - sizeof(struct nd_router_advert), &ndopts,
+ NDOPT_FLAG_SRCLINKADDR | NDOPT_FLAG_PREFIXINFO | NDOPT_FLAG_MTU |
+ NDOPT_FLAG_RDNSS | NDOPT_FLAG_DNSSL);
+ if (error) {
+ syslog(LOG_INFO,
+ "<%s> ND option check failed for an RA from %s on %s",
+ __func__,
+ inet_ntop(AF_INET6, &from->sin6_addr, ntopbuf,
+ sizeof(ntopbuf)), if_indextoname(pi->ipi6_ifindex,
+ ifnamebuf));
+ return;
+ }
+
+ /*
+ * RA consistency check according to RFC 4861 6.2.7
+ */
+ ifi = if_indextoifinfo(pi->ipi6_ifindex);
+ if (ifi->ifi_rainfo == NULL) {
+ syslog(LOG_INFO,
+ "<%s> received RA from %s on non-advertising"
+ " interface(%s)",
+ __func__,
+ inet_ntop(AF_INET6, &from->sin6_addr, ntopbuf,
+ sizeof(ntopbuf)), if_indextoname(pi->ipi6_ifindex,
+ ifnamebuf));
+ goto done;
+ }
+ rai = ifi->ifi_rainfo;
+ ifi->ifi_rainput++;
+ syslog(LOG_DEBUG, "<%s> ifi->ifi_rainput = %" PRIu64, __func__,
+ ifi->ifi_rainput);
+
+ /* Cur Hop Limit value */
+ if (nra->nd_ra_curhoplimit && rai->rai_hoplimit &&
+ nra->nd_ra_curhoplimit != rai->rai_hoplimit) {
+ syslog(LOG_NOTICE,
+ "CurHopLimit inconsistent on %s:"
+ " %d from %s, %d from us",
+ ifi->ifi_ifname, nra->nd_ra_curhoplimit,
+ inet_ntop(AF_INET6, &from->sin6_addr, ntopbuf,
+ sizeof(ntopbuf)), rai->rai_hoplimit);
+ inconsistent++;
+ }
+ /* M flag */
+ if ((nra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) !=
+ rai->rai_managedflg) {
+ syslog(LOG_NOTICE,
+ "M flag inconsistent on %s:"
+ " %s from %s, %s from us",
+ ifi->ifi_ifname, on_off[!rai->rai_managedflg],
+ inet_ntop(AF_INET6, &from->sin6_addr, ntopbuf,
+ sizeof(ntopbuf)), on_off[rai->rai_managedflg]);
+ inconsistent++;
+ }
+ /* O flag */
+ if ((nra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER) !=
+ rai->rai_otherflg) {
+ syslog(LOG_NOTICE,
+ "O flag inconsistent on %s:"
+ " %s from %s, %s from us",
+ ifi->ifi_ifname, on_off[!rai->rai_otherflg],
+ inet_ntop(AF_INET6, &from->sin6_addr, ntopbuf,
+ sizeof(ntopbuf)), on_off[rai->rai_otherflg]);
+ inconsistent++;
+ }
+ /* Reachable Time */
+ reachabletime = ntohl(nra->nd_ra_reachable);
+ if (reachabletime && rai->rai_reachabletime &&
+ reachabletime != rai->rai_reachabletime) {
+ syslog(LOG_NOTICE,
+ "ReachableTime inconsistent on %s:"
+ " %d from %s, %d from us",
+ ifi->ifi_ifname, reachabletime,
+ inet_ntop(AF_INET6, &from->sin6_addr, ntopbuf,
+ sizeof(ntopbuf)), rai->rai_reachabletime);
+ inconsistent++;
+ }
+ /* Retrans Timer */
+ retranstimer = ntohl(nra->nd_ra_retransmit);
+ if (retranstimer && rai->rai_retranstimer &&
+ retranstimer != rai->rai_retranstimer) {
+ syslog(LOG_NOTICE,
+ "RetranceTimer inconsistent on %s:"
+ " %d from %s, %d from us",
+ ifi->ifi_ifname, retranstimer,
+ inet_ntop(AF_INET6, &from->sin6_addr, ntopbuf,
+ sizeof(ntopbuf)), rai->rai_retranstimer);
+ inconsistent++;
+ }
+ /* Values in the MTU options */
+ if (ndopts.opt_mtu) {
+ mtu = ntohl(ndopts.opt_mtu->nd_opt_mtu_mtu);
+ if (mtu && rai->rai_linkmtu && mtu != rai->rai_linkmtu) {
+ syslog(LOG_NOTICE,
+ "MTU option value inconsistent on %s:"
+ " %d from %s, %d from us",
+ ifi->ifi_ifname, mtu,
+ inet_ntop(AF_INET6, &from->sin6_addr, ntopbuf,
+ sizeof(ntopbuf)), rai->rai_linkmtu);
+ inconsistent++;
+ }
+ }
+ /* Preferred and Valid Lifetimes for prefixes */
+ {
+ struct nd_optlist *nol;
+
+ if (ndopts.opt_pi)
+ if (prefix_check(ndopts.opt_pi, rai, from))
+ inconsistent++;
+
+ TAILQ_FOREACH(nol, &ndopts.opt_list, nol_next)
+ if (prefix_check((struct nd_opt_prefix_info *)nol->nol_opt,
+ rai, from))
+ inconsistent++;
+ }
+
+ if (inconsistent)
+ ifi->ifi_rainconsistent++;
+
+ done:
+ free_ndopts(&ndopts);
+ return;
+}
+
+static uint32_t
+udiff(uint32_t u, uint32_t v)
+{
+ return (u >= v ? u - v : v - u);
+}
+
+/* return a non-zero value if the received prefix is inconsitent with ours */
+static int
+prefix_check(struct nd_opt_prefix_info *pinfo,
+ struct rainfo *rai, struct sockaddr_in6 *from)
+{
+ struct ifinfo *ifi;
+ uint32_t preferred_time, valid_time;
+ struct prefix *pfx;
+ int inconsistent = 0;
+ char ntopbuf[INET6_ADDRSTRLEN];
+ char prefixbuf[INET6_ADDRSTRLEN];
+ struct timespec now;
+
+#if 0 /* impossible */
+ if (pinfo->nd_opt_pi_type != ND_OPT_PREFIX_INFORMATION)
+ return (0);
+#endif
+ ifi = rai->rai_ifinfo;
+ /*
+ * log if the adveritsed prefix has link-local scope(sanity check?)
+ */
+ if (IN6_IS_ADDR_LINKLOCAL(&pinfo->nd_opt_pi_prefix))
+ syslog(LOG_INFO,
+ "<%s> link-local prefix %s/%d is advertised "
+ "from %s on %s",
+ __func__,
+ inet_ntop(AF_INET6, &pinfo->nd_opt_pi_prefix, prefixbuf,
+ sizeof(prefixbuf)),
+ pinfo->nd_opt_pi_prefix_len,
+ inet_ntop(AF_INET6, &from->sin6_addr, ntopbuf,
+ sizeof(ntopbuf)), ifi->ifi_ifname);
+
+ if ((pfx = find_prefix(rai, &pinfo->nd_opt_pi_prefix,
+ pinfo->nd_opt_pi_prefix_len)) == NULL) {
+ syslog(LOG_INFO,
+ "<%s> prefix %s/%d from %s on %s is not in our list",
+ __func__,
+ inet_ntop(AF_INET6, &pinfo->nd_opt_pi_prefix, prefixbuf,
+ sizeof(prefixbuf)),
+ pinfo->nd_opt_pi_prefix_len,
+ inet_ntop(AF_INET6, &from->sin6_addr, ntopbuf,
+ sizeof(ntopbuf)), ifi->ifi_ifname);
+ return (0);
+ }
+
+ preferred_time = ntohl(pinfo->nd_opt_pi_preferred_time);
+ if (pfx->pfx_pltimeexpire) {
+ /*
+ * The lifetime is decremented in real time, so we should
+ * compare the expiration time.
+ * (RFC 2461 Section 6.2.7.)
+ * XXX: can we really expect that all routers on the link
+ * have synchronized clocks?
+ */
+ clock_gettime(CLOCK_MONOTONIC_FAST, &now);
+ preferred_time += now.tv_sec;
+
+ if (!pfx->pfx_timer && rai->rai_clockskew &&
+ udiff(preferred_time, pfx->pfx_pltimeexpire) > rai->rai_clockskew) {
+ syslog(LOG_INFO,
+ "<%s> preferred lifetime for %s/%d"
+ " (decr. in real time) inconsistent on %s:"
+ " %" PRIu32 " from %s, %" PRIu32 " from us",
+ __func__,
+ inet_ntop(AF_INET6, &pinfo->nd_opt_pi_prefix, prefixbuf,
+ sizeof(prefixbuf)),
+ pinfo->nd_opt_pi_prefix_len,
+ ifi->ifi_ifname, preferred_time,
+ inet_ntop(AF_INET6, &from->sin6_addr, ntopbuf,
+ sizeof(ntopbuf)), pfx->pfx_pltimeexpire);
+ inconsistent++;
+ }
+ } else if (!pfx->pfx_timer && preferred_time != pfx->pfx_preflifetime)
+ syslog(LOG_INFO,
+ "<%s> preferred lifetime for %s/%d"
+ " inconsistent on %s:"
+ " %d from %s, %d from us",
+ __func__,
+ inet_ntop(AF_INET6, &pinfo->nd_opt_pi_prefix, prefixbuf,
+ sizeof(prefixbuf)),
+ pinfo->nd_opt_pi_prefix_len,
+ ifi->ifi_ifname, preferred_time,
+ inet_ntop(AF_INET6, &from->sin6_addr, ntopbuf,
+ sizeof(ntopbuf)), pfx->pfx_preflifetime);
+
+ valid_time = ntohl(pinfo->nd_opt_pi_valid_time);
+ if (pfx->pfx_vltimeexpire) {
+ clock_gettime(CLOCK_MONOTONIC_FAST, &now);
+ valid_time += now.tv_sec;
+
+ if (!pfx->pfx_timer && rai->rai_clockskew &&
+ udiff(valid_time, pfx->pfx_vltimeexpire) > rai->rai_clockskew) {
+ syslog(LOG_INFO,
+ "<%s> valid lifetime for %s/%d"
+ " (decr. in real time) inconsistent on %s:"
+ " %d from %s, %" PRIu32 " from us",
+ __func__,
+ inet_ntop(AF_INET6, &pinfo->nd_opt_pi_prefix, prefixbuf,
+ sizeof(prefixbuf)),
+ pinfo->nd_opt_pi_prefix_len,
+ ifi->ifi_ifname, preferred_time,
+ inet_ntop(AF_INET6, &from->sin6_addr, ntopbuf,
+ sizeof(ntopbuf)), pfx->pfx_vltimeexpire);
+ inconsistent++;
+ }
+ } else if (!pfx->pfx_timer && valid_time != pfx->pfx_validlifetime) {
+ syslog(LOG_INFO,
+ "<%s> valid lifetime for %s/%d"
+ " inconsistent on %s:"
+ " %d from %s, %d from us",
+ __func__,
+ inet_ntop(AF_INET6, &pinfo->nd_opt_pi_prefix, prefixbuf,
+ sizeof(prefixbuf)),
+ pinfo->nd_opt_pi_prefix_len,
+ ifi->ifi_ifname, valid_time,
+ inet_ntop(AF_INET6, &from->sin6_addr, ntopbuf,
+ sizeof(ntopbuf)), pfx->pfx_validlifetime);
+ inconsistent++;
+ }
+
+ return (inconsistent);
+}
+
+struct prefix *
+find_prefix(struct rainfo *rai, struct in6_addr *prefix, int plen)
+{
+ struct prefix *pfx;
+ int bytelen, bitlen;
+ char bitmask;
+
+ TAILQ_FOREACH(pfx, &rai->rai_prefix, pfx_next) {
+ if (plen != pfx->pfx_prefixlen)
+ continue;
+
+ bytelen = plen / 8;
+ bitlen = plen % 8;
+ bitmask = 0xff << (8 - bitlen);
+
+ if (memcmp((void *)prefix, (void *)&pfx->pfx_prefix, bytelen))
+ continue;
+
+ if (bitlen == 0 ||
+ ((prefix->s6_addr[bytelen] & bitmask) ==
+ (pfx->pfx_prefix.s6_addr[bytelen] & bitmask))) {
+ return (pfx);
+ }
+ }
+
+ return (NULL);
+}
+
+/* check if p0/plen0 matches p1/plen1; return 1 if matches, otherwise 0. */
+int
+prefix_match(struct in6_addr *p0, int plen0,
+ struct in6_addr *p1, int plen1)
+{
+ int bytelen, bitlen;
+ char bitmask;
+
+ if (plen0 < plen1)
+ return (0);
+
+ bytelen = plen1 / 8;
+ bitlen = plen1 % 8;
+ bitmask = 0xff << (8 - bitlen);
+
+ if (memcmp((void *)p0, (void *)p1, bytelen))
+ return (0);
+
+ if (bitlen == 0 ||
+ ((p0->s6_addr[bytelen] & bitmask) ==
+ (p1->s6_addr[bytelen] & bitmask))) {
+ return (1);
+ }
+
+ return (0);
+}
+
+static int
+nd6_options(struct nd_opt_hdr *hdr, int limit,
+ union nd_opt *ndopts, uint32_t optflags)
+{
+ int optlen = 0;
+
+ for (; limit > 0; limit -= optlen) {
+ if ((size_t)limit < sizeof(struct nd_opt_hdr)) {
+ syslog(LOG_INFO, "<%s> short option header", __func__);
+ goto bad;
+ }
+
+ hdr = (struct nd_opt_hdr *)((caddr_t)hdr + optlen);
+ if (hdr->nd_opt_len == 0) {
+ syslog(LOG_INFO,
+ "<%s> bad ND option length(0) (type = %d)",
+ __func__, hdr->nd_opt_type);
+ goto bad;
+ }
+ optlen = hdr->nd_opt_len << 3;
+ if (optlen > limit) {
+ syslog(LOG_INFO, "<%s> short option", __func__);
+ goto bad;
+ }
+
+ if (hdr->nd_opt_type > ND_OPT_MTU &&
+ hdr->nd_opt_type != ND_OPT_RDNSS &&
+ hdr->nd_opt_type != ND_OPT_DNSSL) {
+ syslog(LOG_INFO, "<%s> unknown ND option(type %d)",
+ __func__, hdr->nd_opt_type);
+ continue;
+ }
+
+ if ((ndopt_flags[hdr->nd_opt_type] & optflags) == 0) {
+ syslog(LOG_INFO, "<%s> unexpected ND option(type %d)",
+ __func__, hdr->nd_opt_type);
+ continue;
+ }
+
+ /*
+ * Option length check. Do it here for all fixed-length
+ * options.
+ */
+ switch (hdr->nd_opt_type) {
+ case ND_OPT_MTU:
+ if (optlen == sizeof(struct nd_opt_mtu))
+ break;
+ goto skip;
+ case ND_OPT_RDNSS:
+ if (optlen >= 24 &&
+ (optlen - sizeof(struct nd_opt_rdnss)) % 16 == 0)
+ break;
+ goto skip;
+ case ND_OPT_DNSSL:
+ if (optlen >= 16 &&
+ (optlen - sizeof(struct nd_opt_dnssl)) % 8 == 0)
+ break;
+ goto skip;
+ case ND_OPT_PREFIX_INFORMATION:
+ if (optlen == sizeof(struct nd_opt_prefix_info))
+ break;
+skip:
+ syslog(LOG_INFO, "<%s> invalid option length",
+ __func__);
+ continue;
+ }
+
+ switch (hdr->nd_opt_type) {
+ case ND_OPT_TARGET_LINKADDR:
+ case ND_OPT_REDIRECTED_HEADER:
+ case ND_OPT_RDNSS:
+ case ND_OPT_DNSSL:
+ break; /* we don't care about these options */
+ case ND_OPT_SOURCE_LINKADDR:
+ case ND_OPT_MTU:
+ if (ndopts->opt_array[hdr->nd_opt_type]) {
+ syslog(LOG_INFO,
+ "<%s> duplicated ND option (type = %d)",
+ __func__, hdr->nd_opt_type);
+ }
+ ndopts->opt_array[hdr->nd_opt_type] = hdr;
+ break;
+ case ND_OPT_PREFIX_INFORMATION:
+ {
+ struct nd_optlist *nol;
+
+ if (ndopts->opt_pi == 0) {
+ ndopts->opt_pi =
+ (struct nd_opt_prefix_info *)hdr;
+ continue;
+ }
+ nol = malloc(sizeof(*nol));
+ if (nol == NULL) {
+ syslog(LOG_ERR, "<%s> can't allocate memory",
+ __func__);
+ goto bad;
+ }
+ nol->nol_opt = hdr;
+ TAILQ_INSERT_TAIL(&(ndopts->opt_list), nol, nol_next);
+
+ break;
+ }
+ default: /* impossible */
+ break;
+ }
+ }
+
+ return (0);
+
+ bad:
+ free_ndopts(ndopts);
+
+ return (-1);
+}
+
+static void
+free_ndopts(union nd_opt *ndopts)
+{
+ struct nd_optlist *nol;
+
+ while ((nol = TAILQ_FIRST(&ndopts->opt_list)) != NULL) {
+ TAILQ_REMOVE(&ndopts->opt_list, nol, nol_next);
+ free(nol);
+ }
+}
+
+void
+sock_open(struct sockinfo *s)
+{
+ struct icmp6_filter filt;
+ int on;
+ /* XXX: should be max MTU attached to the node */
+ static char answer[1500];
+
+ syslog(LOG_DEBUG, "<%s> enter", __func__);
+
+ if (s == NULL) {
+ syslog(LOG_ERR, "<%s> internal error", __func__);
+ exit(1);
+ }
+ rcvcmsgbuflen = CMSG_SPACE(sizeof(struct in6_pktinfo)) +
+ CMSG_SPACE(sizeof(int));
+ rcvcmsgbuf = (char *)malloc(rcvcmsgbuflen);
+ if (rcvcmsgbuf == NULL) {
+ syslog(LOG_ERR, "<%s> not enough core", __func__);
+ exit(1);
+ }
+
+ sndcmsgbuflen = CMSG_SPACE(sizeof(struct in6_pktinfo)) +
+ CMSG_SPACE(sizeof(int));
+ sndcmsgbuf = (char *)malloc(sndcmsgbuflen);
+ if (sndcmsgbuf == NULL) {
+ syslog(LOG_ERR, "<%s> not enough core", __func__);
+ exit(1);
+ }
+
+ if ((s->si_fd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0) {
+ syslog(LOG_ERR, "<%s> socket: %s", __func__, strerror(errno));
+ exit(1);
+ }
+ /* specify to tell receiving interface */
+ on = 1;
+ if (setsockopt(s->si_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on,
+ sizeof(on)) < 0) {
+ syslog(LOG_ERR, "<%s> IPV6_RECVPKTINFO: %s", __func__,
+ strerror(errno));
+ exit(1);
+ }
+ on = 1;
+ /* specify to tell value of hoplimit field of received IP6 hdr */
+ if (setsockopt(s->si_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on,
+ sizeof(on)) < 0) {
+ syslog(LOG_ERR, "<%s> IPV6_RECVHOPLIMIT: %s", __func__,
+ strerror(errno));
+ exit(1);
+ }
+ ICMP6_FILTER_SETBLOCKALL(&filt);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filt);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt);
+ if (mcastif != NULL)
+ ICMP6_FILTER_SETPASS(ICMP6_ROUTER_RENUMBERING, &filt);
+
+ if (setsockopt(s->si_fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filt,
+ sizeof(filt)) < 0) {
+ syslog(LOG_ERR, "<%s> IICMP6_FILTER: %s",
+ __func__, strerror(errno));
+ exit(1);
+ }
+
+ /* initialize msghdr for receiving packets */
+ rcviov[0].iov_base = (caddr_t)answer;
+ rcviov[0].iov_len = sizeof(answer);
+ rcvmhdr.msg_name = (caddr_t)&rcvfrom;
+ rcvmhdr.msg_namelen = sizeof(rcvfrom);
+ rcvmhdr.msg_iov = rcviov;
+ rcvmhdr.msg_iovlen = 1;
+ rcvmhdr.msg_control = (caddr_t) rcvcmsgbuf;
+ rcvmhdr.msg_controllen = rcvcmsgbuflen;
+
+ /* initialize msghdr for sending packets */
+ sndmhdr.msg_namelen = sizeof(struct sockaddr_in6);
+ sndmhdr.msg_iov = sndiov;
+ sndmhdr.msg_iovlen = 1;
+ sndmhdr.msg_control = (caddr_t)sndcmsgbuf;
+ sndmhdr.msg_controllen = sndcmsgbuflen;
+
+ return;
+}
+
+/* open a routing socket to watch the routing table */
+static void
+rtsock_open(struct sockinfo *s)
+{
+ if (s == NULL) {
+ syslog(LOG_ERR, "<%s> internal error", __func__);
+ exit(1);
+ }
+ if ((s->si_fd = socket(PF_ROUTE, SOCK_RAW, 0)) < 0) {
+ syslog(LOG_ERR,
+ "<%s> socket: %s", __func__, strerror(errno));
+ exit(1);
+ }
+}
+
+struct ifinfo *
+if_indextoifinfo(int idx)
+{
+ struct ifinfo *ifi;
+ char *name, name0[IFNAMSIZ];
+
+ /* Check if the interface has a valid name or not. */
+ if (if_indextoname(idx, name0) == NULL)
+ return (NULL);
+
+ TAILQ_FOREACH(ifi, &ifilist, ifi_next) {
+ if (ifi->ifi_ifindex == idx)
+ return (ifi);
+ }
+
+ if (ifi != NULL)
+ syslog(LOG_DEBUG, "<%s> ifi found (idx=%d)",
+ __func__, idx);
+ else
+ syslog(LOG_DEBUG, "<%s> ifi not found (idx=%d)",
+ __func__, idx);
+
+ return (NULL); /* search failed */
+}
+
+void
+ra_output(struct ifinfo *ifi)
+{
+ int i;
+ struct cmsghdr *cm;
+ struct in6_pktinfo *pi;
+ struct soliciter *sol;
+ struct rainfo *rai;
+
+ switch (ifi->ifi_state) {
+ case IFI_STATE_CONFIGURED:
+ rai = ifi->ifi_rainfo;
+ break;
+ case IFI_STATE_TRANSITIVE:
+ rai = ifi->ifi_rainfo_trans;
+ break;
+ case IFI_STATE_UNCONFIGURED:
+ syslog(LOG_DEBUG, "<%s> %s is unconfigured. "
+ "Skip sending RAs.",
+ __func__, ifi->ifi_ifname);
+ return;
+ default:
+ rai = NULL;
+ }
+ if (rai == NULL) {
+ syslog(LOG_DEBUG, "<%s> rainfo is NULL on %s."
+ "Skip sending RAs.",
+ __func__, ifi->ifi_ifname);
+ return;
+ }
+ if (!(ifi->ifi_flags & IFF_UP)) {
+ syslog(LOG_DEBUG, "<%s> %s is not up. "
+ "Skip sending RAs.",
+ __func__, ifi->ifi_ifname);
+ return;
+ }
+ /*
+ * Check lifetime, ACCEPT_RTADV flag, and ip6.forwarding.
+ *
+ * (lifetime == 0) = output
+ * (lifetime != 0 && (check_accept_rtadv()) = no output
+ *
+ * Basically, hosts MUST NOT send Router Advertisement
+ * messages at any time (RFC 4861, Section 6.2.3). However, it
+ * would sometimes be useful to allow hosts to advertise some
+ * parameters such as prefix information and link MTU. Thus,
+ * we allow hosts to invoke rtadvd only when router lifetime
+ * (on every advertising interface) is explicitly set
+ * zero. (see also the above section)
+ */
+ syslog(LOG_DEBUG,
+ "<%s> check lifetime=%d, ACCEPT_RTADV=%d, ip6.forwarding=%d "
+ "on %s", __func__,
+ rai->rai_lifetime,
+ check_accept_rtadv(ifi->ifi_ifindex),
+ getinet6sysctl(IPV6CTL_FORWARDING),
+ ifi->ifi_ifname);
+
+ if (rai->rai_lifetime != 0) {
+ if (getinet6sysctl(IPV6CTL_FORWARDING) == 0) {
+ syslog(LOG_ERR,
+ "non-zero lifetime RA "
+ "but net.inet6.ip6.forwarding=0. "
+ "Ignored.");
+ return;
+ }
+ if (check_accept_rtadv(ifi->ifi_ifindex)) {
+ syslog(LOG_ERR,
+ "non-zero lifetime RA "
+ "on RA receiving interface %s."
+ " Ignored.", ifi->ifi_ifname);
+ return;
+ }
+ }
+
+ make_packet(rai); /* XXX: inefficient */
+
+ sndmhdr.msg_name = (caddr_t)&sin6_linklocal_allnodes;
+ sndmhdr.msg_iov[0].iov_base = (caddr_t)rai->rai_ra_data;
+ sndmhdr.msg_iov[0].iov_len = rai->rai_ra_datalen;
+
+ cm = CMSG_FIRSTHDR(&sndmhdr);
+ /* specify the outgoing interface */
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
+ pi = (struct in6_pktinfo *)CMSG_DATA(cm);
+ memset(&pi->ipi6_addr, 0, sizeof(pi->ipi6_addr)); /*XXX*/
+ pi->ipi6_ifindex = ifi->ifi_ifindex;
+
+ /* specify the hop limit of the packet */
+ {
+ int hoplimit = 255;
+
+ cm = CMSG_NXTHDR(&sndmhdr, cm);
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_HOPLIMIT;
+ cm->cmsg_len = CMSG_LEN(sizeof(int));
+ memcpy(CMSG_DATA(cm), &hoplimit, sizeof(int));
+ }
+
+ syslog(LOG_DEBUG,
+ "<%s> send RA on %s, # of RS waitings = %d",
+ __func__, ifi->ifi_ifname, ifi->ifi_rs_waitcount);
+
+ i = sendmsg(sock.si_fd, &sndmhdr, 0);
+
+ if (i < 0 || (size_t)i != rai->rai_ra_datalen) {
+ if (i < 0) {
+ syslog(LOG_ERR, "<%s> sendmsg on %s: %s",
+ __func__, ifi->ifi_ifname,
+ strerror(errno));
+ }
+ }
+
+ /*
+ * unicast advertisements
+ * XXX commented out. reason: though spec does not forbit it, unicast
+ * advert does not really help
+ */
+ while ((sol = TAILQ_FIRST(&rai->rai_soliciter)) != NULL) {
+ TAILQ_REMOVE(&rai->rai_soliciter, sol, sol_next);
+ free(sol);
+ }
+
+ /* update timestamp */
+ clock_gettime(CLOCK_MONOTONIC_FAST, &ifi->ifi_ra_lastsent);
+
+ /* update counter */
+ ifi->ifi_rs_waitcount = 0;
+ ifi->ifi_raoutput++;
+
+ switch (ifi->ifi_state) {
+ case IFI_STATE_CONFIGURED:
+ if (ifi->ifi_burstcount > 0)
+ ifi->ifi_burstcount--;
+ break;
+ case IFI_STATE_TRANSITIVE:
+ ifi->ifi_burstcount--;
+ if (ifi->ifi_burstcount == 0) {
+ if (ifi->ifi_rainfo == ifi->ifi_rainfo_trans) {
+ /* Initial burst finished. */
+ if (ifi->ifi_rainfo_trans != NULL)
+ ifi->ifi_rainfo_trans = NULL;
+ }
+
+ /* Remove burst RA information */
+ if (ifi->ifi_rainfo_trans != NULL) {
+ rm_rainfo(ifi->ifi_rainfo_trans);
+ ifi->ifi_rainfo_trans = NULL;
+ }
+
+ if (ifi->ifi_rainfo != NULL) {
+ /*
+ * TRANSITIVE -> CONFIGURED
+ *
+ * After initial burst or transition from
+ * one configuration to another,
+ * ifi_rainfo always points to the next RA
+ * information.
+ */
+ ifi->ifi_state = IFI_STATE_CONFIGURED;
+ syslog(LOG_DEBUG,
+ "<%s> ifname=%s marked as "
+ "CONFIGURED.", __func__,
+ ifi->ifi_ifname);
+ } else {
+ /*
+ * TRANSITIVE -> UNCONFIGURED
+ *
+ * If ifi_rainfo points to NULL, this
+ * interface is shutting down.
+ *
+ */
+ int error;
+
+ ifi->ifi_state = IFI_STATE_UNCONFIGURED;
+ syslog(LOG_DEBUG,
+ "<%s> ifname=%s marked as "
+ "UNCONFIGURED.", __func__,
+ ifi->ifi_ifname);
+ error = sock_mc_leave(&sock,
+ ifi->ifi_ifindex);
+ if (error)
+ exit(1);
+ }
+ }
+ break;
+ }
+}
+
+/* process RA timer */
+struct rtadvd_timer *
+ra_timeout(void *arg)
+{
+ struct ifinfo *ifi;
+
+ ifi = (struct ifinfo *)arg;
+ syslog(LOG_DEBUG, "<%s> RA timer on %s is expired",
+ __func__, ifi->ifi_ifname);
+
+ ra_output(ifi);
+
+ return (ifi->ifi_ra_timer);
+}
+
+/* update RA timer */
+void
+ra_timer_update(void *arg, struct timespec *tm)
+{
+ uint16_t interval;
+ struct rainfo *rai;
+ struct ifinfo *ifi;
+
+ ifi = (struct ifinfo *)arg;
+ rai = ifi->ifi_rainfo;
+ interval = 0;
+
+ switch (ifi->ifi_state) {
+ case IFI_STATE_UNCONFIGURED:
+ return;
+ break;
+ case IFI_STATE_CONFIGURED:
+ /*
+ * Whenever a multicast advertisement is sent from
+ * an interface, the timer is reset to a
+ * uniformly-distributed random value between the
+ * interface's configured MinRtrAdvInterval and
+ * MaxRtrAdvInterval (RFC4861 6.2.4).
+ */
+ interval = rai->rai_mininterval;
+ interval += arc4random_uniform(rai->rai_maxinterval -
+ rai->rai_mininterval);
+ break;
+ case IFI_STATE_TRANSITIVE:
+ /*
+ * For the first few advertisements (up to
+ * MAX_INITIAL_RTR_ADVERTISEMENTS), if the randomly chosen
+ * interval is greater than
+ * MAX_INITIAL_RTR_ADVERT_INTERVAL, the timer SHOULD be
+ * set to MAX_INITIAL_RTR_ADVERT_INTERVAL instead. (RFC
+ * 4861 6.2.4)
+ *
+ * In such cases, the router SHOULD transmit one or more
+ * (but not more than MAX_FINAL_RTR_ADVERTISEMENTS) final
+ * multicast Router Advertisements on the interface with a
+ * Router Lifetime field of zero. (RFC 4861 6.2.5)
+ */
+ interval = ifi->ifi_burstinterval;
+ break;
+ }
+
+ tm->tv_sec = interval;
+ tm->tv_nsec = 0;
+
+ syslog(LOG_DEBUG,
+ "<%s> RA timer on %s is set to %ld:%ld",
+ __func__, ifi->ifi_ifname,
+ (long int)tm->tv_sec, (long int)tm->tv_nsec / 1000);
+
+ return;
+}
diff --git a/usr.sbin/rtadvd/rtadvd.conf b/usr.sbin/rtadvd/rtadvd.conf
new file mode 100644
index 0000000..1e42c75
--- /dev/null
+++ b/usr.sbin/rtadvd/rtadvd.conf
@@ -0,0 +1,22 @@
+# $FreeBSD$
+# $KAME: rtadvd.conf,v 1.13 2003/06/25 03:45:21 itojun Exp $
+#
+# Note: All of the following parameters have default values defined
+# in specifications, and hence you usually do not have to set them
+# by hand unless you need special non-default values.
+#
+# You even do not need to create the configuration file. rtadvd
+# would usually work well without a configuration file.
+# See also: rtadvd(8)
+
+# per-interface definitions.
+# Mainly IPv6 prefixes are configured in this part. However, rtadvd
+# automatically learns appropriate prefixes from the kernel's routing
+# table, and advertises the prefixes, so you don't have to configure
+# this part, either.
+# If you don't want the automatic advertisement, (uncomment and) configure
+# this part by hand, and then invoke rtadvd with the -s option.
+
+#ef0:\
+# :addr="2001:db8:ffff:1000::":prefixlen#64:\
+# :rdnss="2001:db8:ffff:1000::1":dnssl="example.com":
diff --git a/usr.sbin/rtadvd/rtadvd.conf.5 b/usr.sbin/rtadvd/rtadvd.conf.5
new file mode 100644
index 0000000..d4a0c02
--- /dev/null
+++ b/usr.sbin/rtadvd/rtadvd.conf.5
@@ -0,0 +1,530 @@
+.\" $KAME: rtadvd.conf.5,v 1.50 2005/01/14 05:30:59 jinmei 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 June 4, 2011
+.Dt RTADVD.CONF 5
+.Os
+.Sh NAME
+.Nm rtadvd.conf
+.Nd config file for router advertisement daemon
+.Sh DESCRIPTION
+This file describes how the router advertisement packets must be constructed
+for each of the interfaces.
+.Pp
+As described in
+.Xr rtadvd 8 ,
+you do not have to set this configuration file up at all,
+unless you need some special configurations.
+You may even omit the file as a whole.
+In such cases, the
+.Nm rtadvd
+daemon will automatically configure itself using default values
+specified in the specification.
+.Pp
+It obeys the famous
+.Xr termcap 5
+file format.
+Each line in the file describes a network interface.
+Fields are separated by a colon
+.Pq Sq \&: ,
+and each field contains one capability description.
+Lines may be concatenated by the
+.Sq \e
+character.
+The comment marker is the
+.Sq \&#
+character.
+.Sh CAPABILITIES
+Capabilities describe the value to be filled into ICMPv6 router
+advertisement messages and to control
+.Xr rtadvd 8
+behavior.
+Therefore, you are encouraged to read IETF neighbor discovery documents
+if you would like to modify the sample configuration file.
+.Pp
+Note that almost all items have default values.
+If you omit an item, the default value of the item will be used.
+.Pp
+There are two items which control the interval of sending router advertisements.
+These items can be omitted, then
+.Nm rtadvd
+will use the default values.
+.Bl -tag -width indent
+.It Cm \&maxinterval
+(num) The maximum time allowed between sending unsolicited
+multicast router advertisements
+.Pq unit: seconds .
+The default value is 600.
+Its value must be no less than 4 seconds
+and no greater than 1800 seconds.
+.It Cm \&mininterval
+(num) The minimum time allowed between sending unsolicited multicast
+router advertisements
+.Pq unit: seconds .
+The default value is the one third of value of
+.Cm maxinterval .
+Its value must be no less than 3 seconds and no greater than .75 *
+the value of
+.Cm maxinterval .
+.El
+.Pp
+The following items are for ICMPv6 router advertisement message
+header.
+These items can be omitted, then
+.Nm rtadvd
+will use the default values.
+.Bl -tag -width indent
+.It Cm \&chlim
+(num) The value for Cur Hop Limit field.
+The default value is 64.
+.It Cm \&raflags
+(str or num) A 8-bit flags field in router advertisement message header.
+This field can be specified either as a case-sensitive string or as an
+integer.
+A string consists of characters each of which corresponds to a
+particular flag bit(s).
+An integer should be the logical OR of all enabled bits.
+Bit 7
+.Po
+.Li 'm' or 0x80
+.Pc
+means Managed address configuration flag bit,
+and Bit 6
+.Po
+.Li 'o' or 0x40
+.Pc
+means Other stateful configuration flag bit.
+Bit 4
+.Po
+.Li 0x10
+.Pc
+and Bit 3
+.Po
+.Li 0x08
+.Pc
+are used to encode router preference.
+Bits 01
+.Po
+or 'h'
+.Pc
+means high, 00 means medium, and 11
+.Po
+or 'l'
+.Pc
+means low.
+Bits 10 is reserved, and must not be specified.
+There is no character to specify the medium preference explicitly.
+The default value of the entire flag is 0
+.Po
+or a null string,
+.Pc
+which means no additional
+configuration methods, and the medium router preference.
+.It Cm \&rltime
+(num) Router lifetime field
+.Pq unit: seconds .
+The value must be either zero or between
+the value of
+.Cm maxinterval
+and 9000.
+When
+.Nm rtadvd
+runs on a host, this value must explicitly set 0 on all the
+advertising interfaces as described in
+.Xr rtadvd 8 .
+The default value is 1800.
+.It Cm \&rtime
+(num) Reachable time field
+.Pq unit: milliseconds .
+The default value is 0, which means unspecified by this router.
+.It Cm \&retrans
+(num) Retrans Timer field
+.Pq unit: milliseconds .
+The default value is 0, which means unspecified by this router.
+.El
+.Pp
+The following items are for ICMPv6 prefix information option,
+which will be attached to router advertisement header.
+These items can be omitted, then
+.Nm rtadvd
+will automatically get appropriate prefixes from the kernel's routing table,
+and advertise the prefixes with the default parameters.
+Keywords other than
+.Cm clockskew
+and
+.Cm noifprefix
+can be augmented with a number, like
+.Dq Li prefix2 ,
+to specify multiple prefixes.
+.Bl -tag -width indent
+.It Cm \&noifprefix
+(bool) Specifies no prefix on the network interfaces will be advertised.
+By default
+.Nm rtadvd
+automatically gathers on-link prefixes from all of the network interfaces
+and advertise them.
+The
+.Cm noifprefix
+disables that behavior.
+If this is specified and no
+.Cm addr
+keyword is specified, no prefix information option will be included in the
+message.
+.It Cm \&clockskew
+(num) Time skew to adjust link propagation delays and clock skews
+between routers on the link
+.Pq unit: seconds .
+This value is used in consistency check for locally-configured and
+advertised prefix lifetimes, and has its meaning when the local router
+configures a prefix on the link with a lifetime that decrements in
+real time.
+If the value is 0, it means the consistency check will be skipped
+for such prefixes.
+The default value is 0.
+.It Cm \&prefixlen
+(num) Prefix length field.
+The default value is 64.
+.It Cm \&pinfoflags
+(str or num) A 8-bit flags field in prefix information option.
+This field can be specified either as a case-sensitive string or as an
+integer.
+A string consists of characters each of which corresponds to a
+particular flag bit(s).
+An integer should be the logical OR of all enabled bits.
+Bit 7
+.Po
+.Li 'l' or 0x80
+.Pc
+means On-link flag bit,
+and Bit 6
+.Po
+.Li 'a' or 0x40
+.Pc
+means Autonomous address-configuration flag bit.
+The default value is "la" or 0xc0, i.e., both bits are set.
+.It Cm \&addr
+(str) The address filled into Prefix field.
+Since
+.Dq \&:
+is used for
+.Xr termcap 5
+file format as well as IPv6 numeric address, the field MUST be quoted by
+doublequote character.
+.It Cm \&vltime
+(num) Valid lifetime field
+.Pq unit: seconds .
+The default value is 2592000 (30 days).
+.It Cm \&vltimedecr
+(bool) This item means the advertised valid lifetime will decrement
+in real time, which is disabled by default.
+.It Cm \&pltime
+(num) Preferred lifetime field
+.Pq unit: seconds .
+The default value is 604800 (7 days).
+.It Cm \&pltimedecr
+(bool) This item means the advertised preferred lifetime will decrement
+in real time, which is disabled by default.
+.El
+.Pp
+The following item is for ICMPv6 MTU option,
+which will be attached to router advertisement header.
+This item can be omitted, then
+.Nm rtadvd
+will use the default value.
+.Bl -tag -width indent
+.It Cm \&mtu
+(num or str) MTU (maximum transmission unit) field.
+If 0 is specified, it means that the option will not be included.
+The default value is 0.
+If the special string
+.Dq auto
+is specified for this item, MTU option will be included and its value
+will be set to the interface MTU automatically.
+.El
+.Pp
+The following item controls ICMPv6 source link-layer address option,
+which will be attached to router advertisement header.
+As noted above, you can just omit the item, then
+.Nm rtadvd
+will use the default value.
+.Bl -tag -width indent
+.It Cm \&nolladdr
+(bool) By default
+.Po
+if
+.Cm \&nolladdr
+is not specified
+.Pc ,
+.Xr rtadvd 8
+will try to get link-layer address for the interface from the kernel,
+and attach that in source link-layer address option.
+If this capability exists,
+.Xr rtadvd 8
+will not attach source link-layer address option to
+router advertisement packets.
+.El
+.Pp
+The following item controls ICMPv6 home agent information option,
+which was defined with mobile IPv6 support.
+It will be attached to router advertisement header just like other options do.
+.Bl -tag -width indent
+.It Cm \&hapref
+(num) Specifies home agent preference.
+If set to non-zero,
+.Cm \&hatime
+must be present as well.
+.It Cm \&hatime
+(num) Specifies home agent lifetime.
+.El
+.Pp
+When mobile IPv6 support is turned on for
+.Xr rtadvd 8 ,
+advertisement interval option will be attached to router advertisement
+packet, by configuring
+.Cm \&maxinterval
+explicitly.
+.Pp
+The following items are for ICMPv6 route information option,
+which will be attached to router advertisement header.
+These items are optional.
+Each items can be augmented with number, like
+.Dq Li rtplen2 ,
+to specify multiple routes.
+.Bl -tag -width indent
+.It Cm \&rtprefix
+(str) The prefix filled into the Prefix field of route information option.
+Since
+.Dq \&:
+is used for
+.Xr termcap 5
+file format as well as IPv6 numeric address, the field MUST be quoted by
+doublequote character.
+.It Cm \&rtplen
+(num) Prefix length field in route information option.
+The default value is 64.
+.It Cm \&rtflags
+(str or num) A 8-bit flags field in route information option.
+Currently only the preference values are defined.
+The notation is same as that of the raflags field.
+Bit 4
+.Po
+.Li 0x10
+.Pc
+and
+Bit 3
+.Po
+.Li 0x08
+.Pc
+are used to encode the route preference for the route.
+The default value is 0x00, i.e., medium preference.
+.It Cm \&rtltime
+(num) route lifetime field in route information option.
+.Pq unit: seconds .
+Since the specification does not define the default value of this
+item, the value for this item should be specified by hand.
+However,
+.Nm rtadvd
+allows this item to be unspecified, and uses the router lifetime
+as the default value in such a case, just for compatibility with an
+old version of the program.
+.El
+.Pp
+In the above list, each keyword beginning with
+.Dq Li rt
+could be replaced with the one beginning with
+.Dq Li rtr
+for backward compatibility reason.
+For example,
+.Cm rtrplen
+is accepted instead of
+.Cm rtplen .
+However, keywords that start with
+.Dq Li rtr
+have basically been obsoleted, and should not be used any more.
+.Pp
+The following items are for ICMPv6 Recursive DNS Server Option and
+DNS Search List Option
+.Pq RFC 6106 ,
+which will be attached to router advertisement header.
+These items are optional.
+.Bl -tag -width indent
+.It Cm \&rdnss
+(str) The IPv6 address of one or more recursive DNS servers.
+The argument must be inside double quotes.
+Multiple DNS servers can be specified in a comma-separated string.
+If different lifetimes are needed for different servers,
+separate entries can be given by using
+.Cm rdnss ,
+.Cm rdnss0 ,
+.Cm rdnss1 ,
+.Cm rdnss2 ...
+options with corresponding
+.Cm rdnssltime ,
+.Cm rdnssltime0 ,
+.Cm rdnssltime1 ,
+.Cm rdnssltime2 ...
+entries.
+Note that the maximum number of servers depends on the receiver side.
+See also
+.Xr resolver 5
+manual page for resolver implementation in
+.Fx .
+.It Cm \&rdnssltime
+The lifetime of the
+.Cm rdnss
+DNS server entries.
+The default value is 3/2 of the interval time.
+.It Cm \&dnssl
+(str) One or more domain names in a comma-separated string.
+These domain names will be used when making DNS queries on a
+non-fully-qualified domain name.
+If different lifetimes are needed for different domains, separate entries
+can be given by using
+.Cm dnssl ,
+.Cm dnssl0 ,
+.Cm dnssl1 ,
+.Cm dnssl2 ...
+options with corresponding
+.Cm dnsslltime ,
+.Cm dnsslltime0 ,
+.Cm dnsslltime1 ,
+.Cm dnsslltime2 ...
+entries.
+Note that the maximum number of names depends on the receiver side.
+See also
+.Xr resolver 5
+manual page for resolver implementation in
+.Fx .
+.It Cm \&dnsslltime
+The lifetime of the
+.Cm dnssl
+DNS search list entries.
+The default value is 3/2 of the interval time.
+.El
+.Pp
+You can also refer one line from another by using
+.Cm tc
+capability.
+See
+.Xr termcap 5
+for details on the capability.
+.Sh EXAMPLES
+As presented above, all of the advertised parameters have default values
+defined in specifications, and hence you usually do not have to set them
+by hand, unless you need special non-default values.
+It can cause interoperability problem if you use an ill-configured
+parameter.
+.Pp
+To override a configuration parameter, you can specify the parameter alone.
+With the following configuration,
+.Xr rtadvd 8
+overrides the router lifetime parameter for the
+.Li ne0
+interface.
+.Bd -literal -offset indent
+ne0:\\
+ :rltime#0:
+.Ed
+.Pp
+The following example manually configures prefixes advertised from the
+.Li ef0
+interface.
+The configuration must be used with the
+.Fl s
+option to
+.Xr rtadvd 8 .
+.Bd -literal -offset indent
+ef0:\\
+ :addr="2001:db8:ffff:1000::":prefixlen#64:
+.Ed
+.Pp
+The following example configures the
+.Li wlan0
+interface and adds two DNS servers and a DNS domain search options
+using the default option lifetime values.
+.Bd -literal -offset indent
+wlan0:\\
+ :addr="2001:db8:ffff:1000::":prefixlen#64:\\
+ :rdnss="2001:db8:ffff::10,2001:db8:ffff::2:43":\\
+ :dnssl="example.com":
+.Ed
+.Pp
+The following example presents the default values in an explicit manner.
+The configuration is provided just for reference purposes;
+YOU DO NOT NEED TO HAVE IT AT ALL.
+.Bd -literal -offset indent
+default:\\
+ :chlim#64:raflags#0:rltime#1800:rtime#0:retrans#0:\\
+ :pinfoflags="la":vltime#2592000:pltime#604800:mtu#0:
+ef0:\\
+ :addr="2001:db8:ffff:1000::":prefixlen#64:tc=default:
+.Ed
+.Sh SEE ALSO
+.Xr resolver 5 ,
+.Xr termcap 5 ,
+.Xr rtadvd 8 ,
+.Xr rtsol 8
+.Rs
+.%A Thomas Narten
+.%A Erik Nordmark
+.%A W. A. Simpson
+.%A Hesham Soliman
+.%T Neighbor Discovery for IP version 6 (IPv6)
+.%R RFC 4861
+.Re
+.Rs
+.%A Thomas Narten
+.%A Erik Nordmark
+.%A W. A. Simpson
+.%T Neighbor Discovery for IP version 6 (IPv6)
+.%R RFC 2461 (obsoleted by RFC 4861)
+.Re
+.Rs
+.%A Richard Draves
+.%T Default Router Preferences and More-Specific Routes
+.%R draft-ietf-ipngwg-router-selection-xx.txt
+.Re
+.Rs
+.%A J. Jeong
+.%A S. Park
+.%A L. Beloeil
+.%A S. Madanapalli
+.%T IPv6 Router Advertisement Options for DNS Configuration
+.%R RFC 6106
+.Re
+.Sh HISTORY
+The
+.Xr rtadvd 8
+and the configuration file
+.Nm
+first appeared in WIDE Hydrangea IPv6 protocol stack kit.
+.\" .Sh BUGS
+.\" (to be written)
diff --git a/usr.sbin/rtadvd/rtadvd.h b/usr.sbin/rtadvd/rtadvd.h
new file mode 100644
index 0000000..e7ed87f
--- /dev/null
+++ b/usr.sbin/rtadvd/rtadvd.h
@@ -0,0 +1,298 @@
+/* $FreeBSD$ */
+/* $KAME: rtadvd.h,v 1.26 2003/08/05 12:34:23 itojun Exp $ */
+
+/*
+ * Copyright (C) 1998 WIDE Project.
+ * Copyright (C) 2011 Hiroki Sato <hrs@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.
+ * 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.
+ */
+
+#define ELM_MALLOC(p,error_action) \
+ do { \
+ p = malloc(sizeof(*p)); \
+ if (p == NULL) { \
+ syslog(LOG_ERR, "<%s> malloc failed: %s", \
+ __func__, strerror(errno)); \
+ error_action; \
+ } \
+ memset(p, 0, sizeof(*p)); \
+ } while(0)
+
+#define IN6ADDR_LINKLOCAL_ALLNODES_INIT \
+ {{{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }}}
+
+#define IN6ADDR_LINKLOCAL_ALLROUTERS_INIT \
+ {{{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }}}
+
+#define IN6ADDR_SITELOCAL_ALLROUTERS_INIT \
+ {{{ 0xff, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }}}
+
+extern struct sockaddr_in6 sin6_linklocal_allnodes;
+extern struct sockaddr_in6 sin6_linklocal_allrouters;
+extern struct sockaddr_in6 sin6_sitelocal_allrouters;
+
+/*
+ * RFC 3542 API deprecates IPV6_PKTINFO in favor of
+ * IPV6_RECVPKTINFO
+ */
+#ifndef IPV6_RECVPKTINFO
+#ifdef IPV6_PKTINFO
+#define IPV6_RECVPKTINFO IPV6_PKTINFO
+#endif
+#endif
+
+/*
+ * RFC 3542 API deprecates IPV6_HOPLIMIT in favor of
+ * IPV6_RECVHOPLIMIT
+ */
+#ifndef IPV6_RECVHOPLIMIT
+#ifdef IPV6_HOPLIMIT
+#define IPV6_RECVHOPLIMIT IPV6_HOPLIMIT
+#endif
+#endif
+
+/* protocol constants and default values */
+#define DEF_MAXRTRADVINTERVAL 600
+#define DEF_ADVLINKMTU 0
+#define DEF_ADVREACHABLETIME 0
+#define DEF_ADVRETRANSTIMER 0
+#define DEF_ADVCURHOPLIMIT 64
+#define DEF_ADVVALIDLIFETIME 2592000
+#define DEF_ADVPREFERREDLIFETIME 604800
+
+#define MAXROUTERLIFETIME 9000
+#define MIN_MAXINTERVAL 4
+#define MAX_MAXINTERVAL 1800
+#define MIN_MININTERVAL 3
+#define MAXREACHABLETIME 3600000
+
+#define MAX_INITIAL_RTR_ADVERT_INTERVAL 16
+#define MAX_INITIAL_RTR_ADVERTISEMENTS 3
+#define MAX_FINAL_RTR_ADVERTISEMENTS 3
+#define MIN_DELAY_BETWEEN_RAS 3
+#define MAX_RA_DELAY_TIME 500000 /* usec */
+
+#define PREFIX_FROM_KERNEL 1
+#define PREFIX_FROM_CONFIG 2
+#define PREFIX_FROM_DYNAMIC 3
+
+struct prefix {
+ TAILQ_ENTRY(prefix) pfx_next;
+
+ struct rainfo *pfx_rainfo; /* back pointer to the interface */
+ /*
+ * Expiration timer. This is used when a prefix derived from
+ * the kernel is deleted.
+ */
+ struct rtadvd_timer *pfx_timer;
+
+ uint32_t pfx_validlifetime; /* AdvValidLifetime */
+ uint32_t pfx_vltimeexpire; /* Expiration of vltime */
+ uint32_t pfx_preflifetime; /* AdvPreferredLifetime */
+ uint32_t pfx_pltimeexpire; /* Expiration of pltime */
+ int pfx_onlinkflg; /* bool: AdvOnLinkFlag */
+ int pfx_autoconfflg; /* bool: AdvAutonomousFlag */
+ int pfx_prefixlen;
+ int pfx_origin; /* From kernel or config */
+
+ struct in6_addr pfx_prefix;
+};
+
+struct rtinfo {
+ TAILQ_ENTRY(rtinfo) rti_next;
+
+ uint32_t rti_ltime; /* route lifetime */
+ int rti_rtpref; /* route preference */
+ int rti_prefixlen;
+ struct in6_addr rti_prefix;
+};
+
+struct rdnss_addr {
+ TAILQ_ENTRY(rdnss_addr) ra_next;
+
+ struct in6_addr ra_dns; /* DNS server entry */
+};
+
+struct rdnss {
+ TAILQ_ENTRY(rdnss) rd_next;
+
+ TAILQ_HEAD(, rdnss_addr) rd_list; /* list of DNS servers */
+ uint32_t rd_ltime; /* number of seconds valid */
+};
+
+/*
+ * The maximum length of a domain name in a DNS search list is calculated
+ * by a domain name + length fields per 63 octets + a zero octet at
+ * the tail and adding 8 octet boundary padding.
+ */
+#define _DNAME_LABELENC_MAXLEN \
+ (NI_MAXHOST + (NI_MAXHOST / 64 + 1) + 1)
+
+#define DNAME_LABELENC_MAXLEN \
+ (_DNAME_LABELENC_MAXLEN + 8 - _DNAME_LABELENC_MAXLEN % 8)
+
+struct dnssl_addr {
+ TAILQ_ENTRY(dnssl_addr) da_next;
+
+ int da_len; /* length of entry */
+ char da_dom[DNAME_LABELENC_MAXLEN]; /* search domain name entry */
+};
+
+struct dnssl {
+ TAILQ_ENTRY(dnssl) dn_next;
+
+ TAILQ_HEAD(, dnssl_addr) dn_list; /* list of search domains */
+ uint32_t dn_ltime; /* number of seconds valid */
+};
+
+struct soliciter {
+ TAILQ_ENTRY(soliciter) sol_next;
+
+ struct sockaddr_in6 sol_addr;
+};
+
+struct rainfo {
+ /* pointer for list */
+ TAILQ_ENTRY(rainfo) rai_next;
+
+ /* interface information */
+ struct ifinfo *rai_ifinfo;
+
+ int rai_advlinkopt; /* bool: whether include link-layer addr opt */
+ int rai_advifprefix; /* bool: gather IF prefixes? */
+
+ /* Router configuration variables */
+ uint16_t rai_lifetime; /* AdvDefaultLifetime */
+ uint16_t rai_maxinterval; /* MaxRtrAdvInterval */
+ uint16_t rai_mininterval; /* MinRtrAdvInterval */
+ int rai_managedflg; /* AdvManagedFlag */
+ int rai_otherflg; /* AdvOtherConfigFlag */
+
+ int rai_rtpref; /* router preference */
+ uint32_t rai_linkmtu; /* AdvLinkMTU */
+ uint32_t rai_reachabletime; /* AdvReachableTime */
+ uint32_t rai_retranstimer; /* AdvRetransTimer */
+ uint8_t rai_hoplimit; /* AdvCurHopLimit */
+
+ TAILQ_HEAD(, prefix) rai_prefix;/* AdvPrefixList(link head) */
+ int rai_pfxs; /* number of prefixes */
+
+ uint16_t rai_clockskew; /* used for consisitency check of lifetimes */
+
+ TAILQ_HEAD(, rdnss) rai_rdnss; /* DNS server list */
+ TAILQ_HEAD(, dnssl) rai_dnssl; /* search domain list */
+ TAILQ_HEAD(, rtinfo) rai_route; /* route information option (link head) */
+ int rai_routes; /* number of route information options */
+ /* actual RA packet data and its length */
+ size_t rai_ra_datalen;
+ char *rai_ra_data;
+
+ /* info about soliciter */
+ TAILQ_HEAD(, soliciter) rai_soliciter; /* recent solication source */
+};
+
+/* RA information list */
+extern TAILQ_HEAD(railist_head_t, rainfo) railist;
+
+/*
+ * ifi_state:
+ *
+ * (INIT)
+ * |
+ * | update_ifinfo()
+ * | update_persist_ifinfo()
+ * v
+ * UNCONFIGURED
+ * | ^
+ * loadconfig()| |rm_ifinfo(), ra_output()
+ * (MC join)| |(MC leave)
+ * | |
+ * | |
+ * v |
+ * TRANSITIVE
+ * | ^
+ * ra_output()| |getconfig()
+ * | |
+ * | |
+ * | |
+ * v |
+ * CONFIGURED
+ *
+ *
+ */
+#define IFI_STATE_UNCONFIGURED 0
+#define IFI_STATE_CONFIGURED 1
+#define IFI_STATE_TRANSITIVE 2
+
+struct ifinfo {
+ TAILQ_ENTRY(ifinfo) ifi_next;
+
+ uint16_t ifi_state;
+ uint16_t ifi_persist;
+ uint16_t ifi_ifindex;
+ char ifi_ifname[IFNAMSIZ];
+ uint8_t ifi_type;
+ uint16_t ifi_flags;
+ uint32_t ifi_nd_flags;
+ uint32_t ifi_phymtu;
+ struct sockaddr_dl ifi_sdl;
+
+ struct rainfo *ifi_rainfo;
+ struct rainfo *ifi_rainfo_trans;
+ uint16_t ifi_burstcount;
+ uint32_t ifi_burstinterval;
+ struct rtadvd_timer *ifi_ra_timer;
+ /* timestamp when the latest RA was sent */
+ struct timespec ifi_ra_lastsent;
+ uint16_t ifi_rs_waitcount;
+
+ /* statistics */
+ uint64_t ifi_raoutput; /* # of RAs sent */
+ uint64_t ifi_rainput; /* # of RAs received */
+ uint64_t ifi_rainconsistent; /* # of inconsistent recv'd RAs */
+ uint64_t ifi_rsinput; /* # of RSs received */
+};
+
+/* Interface list */
+extern TAILQ_HEAD(ifilist_head_t, ifinfo) ifilist;
+
+extern char *mcastif;
+
+struct rtadvd_timer *ra_timeout(void *);
+void ra_timer_update(void *, struct timespec *);
+void ra_output(struct ifinfo *);
+
+int prefix_match(struct in6_addr *, int,
+ struct in6_addr *, int);
+struct ifinfo *if_indextoifinfo(int);
+struct prefix *find_prefix(struct rainfo *,
+ struct in6_addr *, int);
+void rtadvd_set_reload(int);
+void rtadvd_set_shutdown(int);
diff --git a/usr.sbin/rtadvd/timer.c b/usr.sbin/rtadvd/timer.c
new file mode 100644
index 0000000..452add4
--- /dev/null
+++ b/usr.sbin/rtadvd/timer.c
@@ -0,0 +1,199 @@
+/* $FreeBSD$ */
+/* $KAME: timer.c,v 1.9 2002/06/10 19:59:47 itojun Exp $ */
+
+/*
+ * Copyright (C) 1998 WIDE Project.
+ * Copyright (C) 2011 Hiroki Sato <hrs@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.
+ * 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/queue.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <netinet/in.h>
+
+#include <unistd.h>
+#include <syslog.h>
+#include <stdlib.h>
+#include <string.h>
+#include <search.h>
+#include <time.h>
+#include <netdb.h>
+
+#include "rtadvd.h"
+#include "timer_subr.h"
+#include "timer.h"
+
+struct rtadvd_timer_head_t ra_timer =
+ TAILQ_HEAD_INITIALIZER(ra_timer);
+static struct timespec tm_limit;
+static struct timespec tm_max;
+
+void
+rtadvd_timer_init(void)
+{
+ /* Generate maximum time in timespec. */
+ tm_limit.tv_sec = (-1) & ~((time_t)1 << ((sizeof(tm_max.tv_sec) * 8) - 1));
+ tm_limit.tv_nsec = (-1) & ~((long)1 << ((sizeof(tm_max.tv_nsec) * 8) - 1));
+ tm_max = tm_limit;
+ TAILQ_INIT(&ra_timer);
+}
+
+void
+rtadvd_update_timeout_handler(void)
+{
+ struct ifinfo *ifi;
+
+ TAILQ_FOREACH(ifi, &ifilist, ifi_next) {
+ switch (ifi->ifi_state) {
+ case IFI_STATE_CONFIGURED:
+ case IFI_STATE_TRANSITIVE:
+ if (ifi->ifi_ra_timer != NULL)
+ continue;
+
+ syslog(LOG_DEBUG, "<%s> add timer for %s (idx=%d)",
+ __func__, ifi->ifi_ifname, ifi->ifi_ifindex);
+ ifi->ifi_ra_timer = rtadvd_add_timer(ra_timeout,
+ ra_timer_update, ifi, ifi);
+ ra_timer_update((void *)ifi,
+ &ifi->ifi_ra_timer->rat_tm);
+ rtadvd_set_timer(&ifi->ifi_ra_timer->rat_tm,
+ ifi->ifi_ra_timer);
+ break;
+ case IFI_STATE_UNCONFIGURED:
+ if (ifi->ifi_ra_timer == NULL)
+ continue;
+
+ syslog(LOG_DEBUG,
+ "<%s> remove timer for %s (idx=%d)", __func__,
+ ifi->ifi_ifname, ifi->ifi_ifindex);
+ rtadvd_remove_timer(ifi->ifi_ra_timer);
+ ifi->ifi_ra_timer = NULL;
+ break;
+ }
+ }
+
+ return;
+}
+
+struct rtadvd_timer *
+rtadvd_add_timer(struct rtadvd_timer *(*timeout)(void *),
+ void (*update)(void *, struct timespec *),
+ void *timeodata, void *updatedata)
+{
+ struct rtadvd_timer *rat;
+
+ if (timeout == NULL) {
+ syslog(LOG_ERR,
+ "<%s> timeout function unspecified", __func__);
+ exit(1);
+ }
+
+ rat = malloc(sizeof(*rat));
+ if (rat == NULL) {
+ syslog(LOG_ERR,
+ "<%s> can't allocate memory", __func__);
+ exit(1);
+ }
+ memset(rat, 0, sizeof(*rat));
+
+ rat->rat_expire = timeout;
+ rat->rat_update = update;
+ rat->rat_expire_data = timeodata;
+ rat->rat_update_data = updatedata;
+ rat->rat_tm = tm_max;
+
+ /* link into chain */
+ TAILQ_INSERT_TAIL(&ra_timer, rat, rat_next);
+
+ return (rat);
+}
+
+void
+rtadvd_remove_timer(struct rtadvd_timer *rat)
+{
+
+ if (rat == NULL)
+ return;
+
+ TAILQ_REMOVE(&ra_timer, rat, rat_next);
+ free(rat);
+}
+
+/*
+ * Check expiration for each timer. If a timer expires,
+ * call the expire function for the timer and update the timer.
+ * Return the next interval for select() call.
+ */
+struct timespec *
+rtadvd_check_timer(void)
+{
+ static struct timespec returnval;
+ struct timespec now;
+ struct rtadvd_timer *rat;
+
+ clock_gettime(CLOCK_MONOTONIC_FAST, &now);
+ tm_max = tm_limit;
+ TAILQ_FOREACH(rat, &ra_timer, rat_next) {
+ if (TS_CMP(&rat->rat_tm, &now, <=)) {
+ if (((*rat->rat_expire)(rat->rat_expire_data) == NULL))
+ continue; /* the timer was removed */
+ if (rat->rat_update)
+ (*rat->rat_update)(rat->rat_update_data, &rat->rat_tm);
+ TS_ADD(&rat->rat_tm, &now, &rat->rat_tm);
+ }
+ if (TS_CMP(&rat->rat_tm, &tm_max, <))
+ tm_max = rat->rat_tm;
+ }
+ if (TS_CMP(&tm_max, &tm_limit, ==)) {
+ /* no need to timeout */
+ return (NULL);
+ } else if (TS_CMP(&tm_max, &now, <)) {
+ /* this may occur when the interval is too small */
+ returnval.tv_sec = returnval.tv_nsec = 0;
+ } else
+ TS_SUB(&tm_max, &now, &returnval);
+ return (&returnval);
+}
+
+void
+rtadvd_set_timer(struct timespec *tm, struct rtadvd_timer *rat)
+{
+ struct timespec now;
+
+ /* reset the timer */
+ clock_gettime(CLOCK_MONOTONIC_FAST, &now);
+ TS_ADD(&now, tm, &rat->rat_tm);
+
+ /* update the next expiration time */
+ if (TS_CMP(&rat->rat_tm, &tm_max, <))
+ tm_max = rat->rat_tm;
+
+ return;
+}
diff --git a/usr.sbin/rtadvd/timer.h b/usr.sbin/rtadvd/timer.h
new file mode 100644
index 0000000..4498aaf
--- /dev/null
+++ b/usr.sbin/rtadvd/timer.h
@@ -0,0 +1,52 @@
+/* $FreeBSD$ */
+/* $KAME: timer.h,v 1.5 2002/05/31 13:30:38 jinmei Exp $ */
+
+/*
+ * Copyright (C) 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.
+ */
+
+extern TAILQ_HEAD(rtadvd_timer_head_t, rtadvd_timer) ra_timer;
+struct rtadvd_timer {
+ TAILQ_ENTRY(rtadvd_timer) rat_next;
+
+ struct rainfo *rat_rai;
+ struct timespec rat_tm;
+ struct rtadvd_timer *(*rat_expire)(void *);
+ void *rat_expire_data;
+ void (*rat_update)(void *, struct timespec *);
+ void *rat_update_data;
+};
+
+void rtadvd_timer_init(void);
+void rtadvd_update_timeout_handler(void);
+struct rtadvd_timer *rtadvd_add_timer(struct rtadvd_timer *(*)(void *),
+ void (*)(void *, struct timespec *), void *, void *);
+void rtadvd_set_timer(struct timespec *,
+ struct rtadvd_timer *);
+void rtadvd_remove_timer(struct rtadvd_timer *);
+struct timespec *rtadvd_check_timer(void);
diff --git a/usr.sbin/rtadvd/timer_subr.c b/usr.sbin/rtadvd/timer_subr.c
new file mode 100644
index 0000000..0ddf0a4
--- /dev/null
+++ b/usr.sbin/rtadvd/timer_subr.c
@@ -0,0 +1,91 @@
+/* $FreeBSD$ */
+/* $KAME: timer.c,v 1.9 2002/06/10 19:59:47 itojun Exp $ */
+
+/*
+ * Copyright (C) 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.
+ */
+
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <syslog.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <time.h>
+
+#include "timer.h"
+#include "timer_subr.h"
+
+struct timespec *
+rtadvd_timer_rest(struct rtadvd_timer *rat)
+{
+ static struct timespec returnval, now;
+
+ clock_gettime(CLOCK_MONOTONIC_FAST, &now);
+ if (TS_CMP(&rat->rat_tm, &now, <=)) {
+ syslog(LOG_DEBUG,
+ "<%s> a timer must be expired, but not yet",
+ __func__);
+ returnval.tv_sec = returnval.tv_nsec = 0;
+ }
+ else
+ TS_SUB(&rat->rat_tm, &now, &returnval);
+
+ return (&returnval);
+}
+
+char *
+sec2str(uint32_t s, char *buf)
+{
+ uint32_t day;
+ uint32_t hour;
+ uint32_t min;
+ uint32_t sec;
+ char *p;
+
+ min = s / 60;
+ sec = s % 60;
+
+ hour = min / 60;
+ min = min % 60;
+
+ day = hour / 24;
+ hour = hour % 24;
+
+ p = buf;
+ if (day > 0)
+ p += sprintf(p, "%" PRIu32 "d", day);
+ if (hour > 0)
+ p += sprintf(p, "%" PRIu32 "h", hour);
+ if (min > 0)
+ p += sprintf(p, "%" PRIu32 "m", min);
+
+ if ((p == buf) || (sec > 0 && p > buf))
+ sprintf(p, "%" PRIu32 "s", sec);
+
+ return (buf);
+}
diff --git a/usr.sbin/rtadvd/timer_subr.h b/usr.sbin/rtadvd/timer_subr.h
new file mode 100644
index 0000000..32e1bb1
--- /dev/null
+++ b/usr.sbin/rtadvd/timer_subr.h
@@ -0,0 +1,59 @@
+/* $FreeBSD$ */
+/* $KAME: timer.h,v 1.5 2002/05/31 13:30:38 jinmei Exp $ */
+
+/*
+ * Copyright (C) 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.
+ */
+
+#define SSBUFLEN 1024
+
+#define TS_CMP(tsp, usp, cmp) \
+ (((tsp)->tv_sec == (usp)->tv_sec) ? \
+ ((tsp)->tv_nsec cmp (usp)->tv_nsec) : \
+ ((tsp)->tv_sec cmp (usp)->tv_sec))
+#define TS_ADD(tsp, usp, vsp) \
+ do { \
+ (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec; \
+ (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec; \
+ if ((vsp)->tv_nsec >= 1000000000L) { \
+ (vsp)->tv_sec++; \
+ (vsp)->tv_nsec -= 1000000000L; \
+ } \
+ } while (0)
+#define TS_SUB(tsp, usp, vsp) \
+ do { \
+ (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \
+ (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \
+ if ((vsp)->tv_nsec < 0) { \
+ (vsp)->tv_sec--; \
+ (vsp)->tv_nsec += 1000000000L; \
+ } \
+ } while (0)
+
+struct timespec *rtadvd_timer_rest(struct rtadvd_timer *);
+char *sec2str(uint32_t, char *buf);
diff --git a/usr.sbin/rtprio/Makefile b/usr.sbin/rtprio/Makefile
new file mode 100644
index 0000000..b612f2b
--- /dev/null
+++ b/usr.sbin/rtprio/Makefile
@@ -0,0 +1,10 @@
+# from: @(#)Makefile 5.5 (Berkeley) 5/11/90
+# $FreeBSD$
+
+PROG= rtprio
+LINKS= ${BINDIR}/rtprio ${BINDIR}/idprio
+MLINKS= rtprio.1 idprio.1
+
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/rtprio/Makefile.depend b/usr.sbin/rtprio/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/rtprio/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/rtprio/rtprio.1 b/usr.sbin/rtprio/rtprio.1
new file mode 100644
index 0000000..85130c8
--- /dev/null
+++ b/usr.sbin/rtprio/rtprio.1
@@ -0,0 +1,200 @@
+.\"
+.\" Copyright (c) 1994, Henrik Vestergaard Draboel
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 Henrik Vestergaard Draboel.
+.\" 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 AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED 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 29, 2012
+.Dt RTPRIO 1
+.Os
+.Sh NAME
+.Nm rtprio ,
+.Nm idprio
+.Nd execute, examine or modify a utility's or process's realtime
+or idletime scheduling priority
+.Sh SYNOPSIS
+.Nm [id|rt]prio
+.Nm [id|rt]prio
+.Oo Fl Oc Ns Ar pid
+.Nm [id|rt]prio
+.Ar priority
+.Ar command
+.Op args
+.Nm [id|rt]prio
+.Ar priority
+.Fl Ar pid
+.Nm [id|rt]prio
+.Fl t
+.Ar command
+.Op args
+.Nm [id|rt]prio
+.Fl t
+.Fl Ar pid
+.Sh DESCRIPTION
+The
+.Nm
+utility is used for controlling realtime process scheduling.
+.Pp
+The
+.Nm idprio
+utility is used for controlling idletime process scheduling, and can be called
+with the same options as
+.Nm .
+.Pp
+A process with a realtime priority is not subject to priority
+degradation, and will only be preempted by another process of equal or
+higher realtime priority.
+.Pp
+A process with an idle priority will run only when no other
+process is runnable and then only if its idle priority is equal or
+greater than all other runnable idle priority processes.
+.Pp
+Both
+.Nm
+or
+.Nm idprio
+when called without arguments will return the realtime priority
+of the current process.
+.Pp
+If
+.Nm
+is called with 1 argument, it will return the realtime priority
+of the process with the specified
+.Ar pid .
+.Pp
+If
+.Ar priority
+is specified, the process or program is run at that realtime priority.
+If
+.Fl t
+is specified, the process or program is run as a normal (non-realtime)
+process.
+.Pp
+If
+.Ar -pid
+is specified, the process with the process identifier
+.Ar pid
+will be modified, else if
+.Ar command
+is specified, that program is run with its arguments.
+.Pp
+.Ar Priority
+is an integer between 0 and RTP_PRIO_MAX (usually 31).
+0 is the
+highest priority
+.Pp
+.Ar Pid
+of 0 means "the current process".
+.Pp
+Only root is allowed to set realtime or idle priority for a process.
+A user may modify the idle priority of their own processes if the
+.Xr sysctl 8
+variable
+.Va security.bsd.unprivileged_idprio
+is set to non-zero.
+Note that this increases the chance that a deadlock can occur
+if a process locks a required resource and then does
+not get to run.
+.Sh EXIT STATUS
+If
+.Nm
+execute a command, the exit value is that of the command executed.
+In all other cases,
+.Nm
+exits 0 on success, and 1 for all other errors.
+.Sh EXAMPLES
+To see which realtime priority the current process is at:
+.Dl rtprio
+.Pp
+To see which realtime priority of process 1423:
+.Dl "rtprio 1423"
+.Pp
+To run
+.Xr cron 8
+at the lowest realtime priority:
+.Dl "rtprio 31 cron"
+.Pp
+To change the realtime priority of process 1423 to 16:
+.Dl "rtprio 16 -1423"
+.Pp
+To run
+.Xr tcpdump 1
+without realtime priority:
+.Dl "rtprio -t tcpdump"
+.Pp
+To change the realtime priority of process 1423
+to
+.Dv RTP_PRIO_NORMAL
+(non-realtime/normal priority):
+.Dl "rtprio -t -1423"
+.Pp
+To make depend while not disturbing other machine usage:
+.Dl "idprio 31 make depend"
+.Sh SEE ALSO
+.Xr nice 1 ,
+.Xr ps 1 ,
+.Xr rtprio 2 ,
+.Xr setpriority 2 ,
+.Xr nice 3 ,
+.Xr renice 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 2.0 ,
+but is similar to the HP-UX version.
+.Sh AUTHORS
+.An -nosplit
+.An Henrik Vestergaard Draboel Aq Mt hvd@terry.ping.dk
+is the original author.
+This
+implementation in
+.Fx
+was substantially rewritten by
+.An David Greenman .
+.Sh CAVEATS
+You can lock yourself out of the system by placing a cpu-heavy
+process in a realtime priority.
+.Sh BUGS
+There is no way to set/view the realtime priority of process 0
+(swapper) (see
+.Xr ps 1 ) .
+.Pp
+There is in
+.Fx
+no way to ensure that a process page is present in memory therefore
+the process may be stopped for pagein (see
+.Xr mprotect 2 ,
+.Xr madvise 2 ) .
+.Pp
+Under
+.Fx
+system calls are currently never preempted, therefore non-realtime
+processes can starve realtime processes, or idletime processes can
+starve normal priority processes.
diff --git a/usr.sbin/rtprio/rtprio.c b/usr.sbin/rtprio/rtprio.c
new file mode 100644
index 0000000..a0b67ea
--- /dev/null
+++ b/usr.sbin/rtprio/rtprio.c
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 1994 David Greenman
+ * Copyright (c) 1994 Henrik Vestergaard Draboel (hvd@terry.ping.dk)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 Henrik Vestergaard Draboel.
+ * This product includes software developed by David Greenman.
+ * 4. Neither the names of the authors nor the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * 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/rtprio.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static int parseint(const char *, const char *);
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ struct rtprio rtp;
+ const char *progname;
+ pid_t proc = 0;
+
+ progname = getprogname();
+
+ if (strcmp(progname, "rtprio") == 0)
+ rtp.type = RTP_PRIO_REALTIME;
+ else if (strcmp(progname, "idprio") == 0)
+ rtp.type = RTP_PRIO_IDLE;
+ else
+ errx(1, "invalid progname");
+
+ switch (argc) {
+ case 2:
+ proc = parseint(argv[1], "pid");
+ proc = abs(proc);
+ /* FALLTHROUGH */
+ case 1:
+ if (rtprio(RTP_LOOKUP, proc, &rtp) != 0)
+ err(1, "RTP_LOOKUP");
+ switch (rtp.type) {
+ case RTP_PRIO_REALTIME:
+ case RTP_PRIO_FIFO:
+ warnx("realtime priority %d", rtp.prio);
+ break;
+ case RTP_PRIO_NORMAL:
+ warnx("normal priority");
+ break;
+ case RTP_PRIO_IDLE:
+ warnx("idle priority %d", rtp.prio);
+ break;
+ default:
+ errx(1, "invalid priority type %d", rtp.type);
+ break;
+ }
+ exit(0);
+ default:
+ if (argv[1][0] == '-' || isdigit(argv[1][0])) {
+ if (argv[1][0] == '-') {
+ if (strcmp(argv[1], "-t") == 0) {
+ rtp.type = RTP_PRIO_NORMAL;
+ rtp.prio = 0;
+ } else {
+ usage();
+ break;
+ }
+ } else
+ rtp.prio = parseint(argv[1], "priority");
+ } else {
+ usage();
+ break;
+ }
+
+ if (argv[2][0] == '-') {
+ proc = parseint(argv[2], "pid");
+ proc = abs(proc);
+ }
+
+ if (rtprio(RTP_SET, proc, &rtp) != 0)
+ err(1, "RTP_SET");
+
+ if (proc == 0) {
+ execvp(argv[2], &argv[2]);
+ err(1, "execvp: %s", argv[2]);
+ }
+ exit(0);
+ }
+ /* NOTREACHED */
+}
+
+static int
+parseint(const char *str, const char *errname)
+{
+ char *endp;
+ long res;
+
+ errno = 0;
+ res = strtol(str, &endp, 10);
+ if (errno != 0 || endp == str || *endp != '\0')
+ errx(1, "%s must be a number", errname);
+ if (res >= INT_MAX)
+ errx(1, "Integer overflow parsing %s", errname);
+ return (res);
+}
+
+static void
+usage(void)
+{
+
+ (void) fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n%s\n",
+ "usage: [id|rt]prio",
+ " [id|rt]prio [-]pid",
+ " [id|rt]prio priority command [args]",
+ " [id|rt]prio priority -pid",
+ " [id|rt]prio -t command [args]",
+ " [id|rt]prio -t -pid");
+ exit(1);
+}
diff --git a/usr.sbin/rtsold/Makefile b/usr.sbin/rtsold/Makefile
new file mode 100644
index 0000000..84cf0b2
--- /dev/null
+++ b/usr.sbin/rtsold/Makefile
@@ -0,0 +1,24 @@
+# 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$
+
+PROG= rtsold
+MAN= rtsold.8
+MLINKS= rtsold.8 rtsol.8
+SRCS= rtsold.c rtsol.c if.c probe.c dump.c rtsock.c
+
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/rtsold/Makefile.depend b/usr.sbin/rtsold/Makefile.depend
new file mode 100644
index 0000000..54c1f6f
--- /dev/null
+++ b/usr.sbin/rtsold/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/rtsold/dump.c b/usr.sbin/rtsold/dump.c
new file mode 100644
index 0000000..a0bf2c2
--- /dev/null
+++ b/usr.sbin/rtsold/dump.c
@@ -0,0 +1,183 @@
+/* $KAME: dump.c,v 1.13 2003/10/05 00:09:36 itojun Exp $ */
+
+/*
+ * Copyright (C) 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/socket.h>
+#include <sys/queue.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/icmp6.h>
+#include <arpa/inet.h>
+
+#include <syslog.h>
+#include <time.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "rtsold.h"
+
+static FILE *fp;
+
+static void dump_interface_status(void);
+static const char * const ifstatstr[] = {"IDLE", "DELAY", "PROBE", "DOWN", "TENTATIVE"};
+
+static void
+dump_interface_status(void)
+{
+ struct ifinfo *ifi;
+ struct rainfo *rai;
+ struct ra_opt *rao;
+ struct timespec now;
+ char ntopbuf[INET6_ADDRSTRLEN];
+
+ clock_gettime(CLOCK_MONOTONIC_FAST, &now);
+
+ TAILQ_FOREACH(ifi, &ifinfo_head, ifi_next) {
+ fprintf(fp, "Interface %s\n", ifi->ifname);
+ fprintf(fp, " probe interval: ");
+ if (ifi->probeinterval) {
+ fprintf(fp, "%d\n", ifi->probeinterval);
+ fprintf(fp, " probe timer: %d\n", ifi->probetimer);
+ } else {
+ fprintf(fp, "infinity\n");
+ fprintf(fp, " no probe timer\n");
+ }
+ fprintf(fp, " interface status: %s\n",
+ ifi->active > 0 ? "active" : "inactive");
+ fprintf(fp, " other config: %s\n",
+ ifi->otherconfig ? "on" : "off");
+ fprintf(fp, " rtsold status: %s\n", ifstatstr[ifi->state]);
+ fprintf(fp, " carrier detection: %s\n",
+ ifi->mediareqok ? "available" : "unavailable");
+ fprintf(fp, " probes: %d, dadcount = %d\n",
+ ifi->probes, ifi->dadcount);
+ if (ifi->timer.tv_sec == tm_max.tv_sec &&
+ ifi->timer.tv_nsec == tm_max.tv_nsec)
+ fprintf(fp, " no timer\n");
+ else {
+ fprintf(fp, " timer: interval=%d:%d, expire=%s\n",
+ (int)ifi->timer.tv_sec,
+ (int)ifi->timer.tv_nsec / 1000,
+ (ifi->expire.tv_sec < now.tv_sec) ? "expired"
+ : sec2str(&ifi->expire));
+ }
+ fprintf(fp, " number of valid RAs: %d\n", ifi->racnt);
+
+ TAILQ_FOREACH(rai, &ifi->ifi_rainfo, rai_next) {
+ fprintf(fp, " RA from %s\n",
+ inet_ntop(AF_INET6, &rai->rai_saddr.sin6_addr,
+ ntopbuf, sizeof(ntopbuf)));
+ TAILQ_FOREACH(rao, &rai->rai_ra_opt, rao_next) {
+ fprintf(fp, " option: ");
+ switch (rao->rao_type) {
+ case ND_OPT_RDNSS:
+ fprintf(fp, "RDNSS: %s (expire: %s)\n",
+ (char *)rao->rao_msg,
+ sec2str(&rao->rao_expire));
+ break;
+ case ND_OPT_DNSSL:
+ fprintf(fp, "DNSSL: %s (expire: %s)\n",
+ (char *)rao->rao_msg,
+ sec2str(&rao->rao_expire));
+ break;
+ default:
+ break;
+ }
+ }
+ fprintf(fp, "\n");
+ }
+ }
+}
+
+void
+rtsold_dump_file(const char *dumpfile)
+{
+ if ((fp = fopen(dumpfile, "w")) == NULL) {
+ warnmsg(LOG_WARNING, __func__, "open a dump file(%s): %s",
+ dumpfile, strerror(errno));
+ return;
+ }
+ dump_interface_status();
+ fclose(fp);
+}
+
+const char *
+sec2str(const struct timespec *total)
+{
+ static char result[256];
+ int days, hours, mins, secs;
+ int first = 1;
+ char *p = result;
+ char *ep = &result[sizeof(result)];
+ int n;
+ struct timespec now;
+ time_t tsec;
+
+ clock_gettime(CLOCK_MONOTONIC_FAST, &now);
+ tsec = total->tv_sec;
+ tsec += total->tv_nsec / 1000 / 1000000;
+ tsec -= now.tv_sec;
+ tsec -= now.tv_nsec / 1000 / 1000000;
+
+ days = tsec / 3600 / 24;
+ hours = (tsec / 3600) % 24;
+ mins = (tsec / 60) % 60;
+ secs = tsec % 60;
+
+ if (days) {
+ first = 0;
+ n = snprintf(p, ep - p, "%dd", days);
+ if (n < 0 || n >= ep - p)
+ return "?";
+ p += n;
+ }
+ if (!first || hours) {
+ first = 0;
+ n = snprintf(p, ep - p, "%dh", hours);
+ if (n < 0 || n >= ep - p)
+ return "?";
+ p += n;
+ }
+ if (!first || mins) {
+ first = 0;
+ n = snprintf(p, ep - p, "%dm", mins);
+ if (n < 0 || n >= ep - p)
+ return "?";
+ p += n;
+ }
+ snprintf(p, ep - p, "%ds", secs);
+
+ return (result);
+}
diff --git a/usr.sbin/rtsold/if.c b/usr.sbin/rtsold/if.c
new file mode 100644
index 0000000..cb4b001
--- /dev/null
+++ b/usr.sbin/rtsold/if.c
@@ -0,0 +1,430 @@
+/* $KAME: if.c,v 1.27 2003/10/05 00:09:36 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$
+ */
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/ioctl.h>
+#include <sys/queue.h>
+
+#include <net/if.h>
+#include <net/if_types.h>
+#include <net/route.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/ethernet.h>
+#include <netinet/in.h>
+#include <netinet/icmp6.h>
+
+#include <netinet6/in6_var.h>
+#include <netinet6/nd6.h>
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <limits.h>
+#include <ifaddrs.h>
+#include "rtsold.h"
+
+static int ifsock;
+
+static int get_llflag(const char *);
+static void get_rtaddrs(int, struct sockaddr *, struct sockaddr **);
+
+int
+ifinit(void)
+{
+ ifsock = rssock;
+
+ return(0);
+}
+
+int
+interface_up(char *name)
+{
+ struct ifreq ifr;
+ struct in6_ndireq nd;
+ int llflag;
+ int s;
+
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+ memset(&nd, 0, sizeof(nd));
+ strlcpy(nd.ifname, name, sizeof(nd.ifname));
+
+ if (ioctl(ifsock, SIOCGIFFLAGS, (caddr_t)&ifr) < 0) {
+ warnmsg(LOG_WARNING, __func__, "ioctl(SIOCGIFFLAGS): %s",
+ strerror(errno));
+ return (-1);
+ }
+ if (!(ifr.ifr_flags & IFF_UP)) {
+ ifr.ifr_flags |= IFF_UP;
+ if (ioctl(ifsock, SIOCSIFFLAGS, (caddr_t)&ifr) < 0)
+ warnmsg(LOG_ERR, __func__,
+ "ioctl(SIOCSIFFLAGS): %s", strerror(errno));
+ return (-1);
+ }
+ if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
+ warnmsg(LOG_WARNING, __func__, "socket(AF_INET6, SOCK_DGRAM): %s",
+ strerror(errno));
+ return (-1);
+ }
+ if (ioctl(s, SIOCGIFINFO_IN6, (caddr_t)&nd) < 0) {
+ warnmsg(LOG_WARNING, __func__, "ioctl(SIOCGIFINFO_IN6): %s",
+ strerror(errno));
+ close(s);
+ return (-1);
+ }
+
+ warnmsg(LOG_DEBUG, __func__, "checking if %s is ready...", name);
+
+ if (nd.ndi.flags & ND6_IFF_IFDISABLED) {
+ if (Fflag) {
+ nd.ndi.flags &= ~ND6_IFF_IFDISABLED;
+ if (ioctl(s, SIOCSIFINFO_IN6, (caddr_t)&nd)) {
+ warnmsg(LOG_WARNING, __func__,
+ "ioctl(SIOCSIFINFO_IN6): %s",
+ strerror(errno));
+ close(s);
+ return (-1);
+ }
+ } else {
+ warnmsg(LOG_WARNING, __func__,
+ "%s is disabled.", name);
+ close(s);
+ return (-1);
+ }
+ }
+ if (!(nd.ndi.flags & ND6_IFF_ACCEPT_RTADV)) {
+ if (Fflag) {
+ nd.ndi.flags |= ND6_IFF_ACCEPT_RTADV;
+ if (ioctl(s, SIOCSIFINFO_IN6, (caddr_t)&nd)) {
+ warnmsg(LOG_WARNING, __func__,
+ "ioctl(SIOCSIFINFO_IN6): %s",
+ strerror(errno));
+ close(s);
+ return (-1);
+ }
+ } else {
+ warnmsg(LOG_WARNING, __func__,
+ "%s does not accept Router Advertisement.", name);
+ close(s);
+ return (-1);
+ }
+ }
+ close(s);
+
+ llflag = get_llflag(name);
+ if (llflag < 0) {
+ warnmsg(LOG_WARNING, __func__,
+ "get_llflag() failed, anyway I'll try");
+ return (0);
+ }
+
+ if (!(llflag & IN6_IFF_NOTREADY)) {
+ warnmsg(LOG_DEBUG, __func__, "%s is ready", name);
+ return (0);
+ } else {
+ if (llflag & IN6_IFF_TENTATIVE) {
+ warnmsg(LOG_DEBUG, __func__, "%s is tentative",
+ name);
+ return (IFS_TENTATIVE);
+ }
+ if (llflag & IN6_IFF_DUPLICATED)
+ warnmsg(LOG_DEBUG, __func__, "%s is duplicated",
+ name);
+ return (-1);
+ }
+}
+
+int
+interface_status(struct ifinfo *ifinfo)
+{
+ char *ifname = ifinfo->ifname;
+ struct ifreq ifr;
+ struct ifmediareq ifmr;
+
+ /* get interface flags */
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ if (ioctl(ifsock, SIOCGIFFLAGS, &ifr) < 0) {
+ warnmsg(LOG_ERR, __func__, "ioctl(SIOCGIFFLAGS) on %s: %s",
+ ifname, strerror(errno));
+ return (-1);
+ }
+ /*
+ * 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->mediareqok)
+ goto active;
+ memset(&ifmr, 0, sizeof(ifmr));
+ strncpy(ifmr.ifm_name, ifname, sizeof(ifmr.ifm_name));
+
+ if (ioctl(ifsock, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0) {
+ if (errno != EINVAL) {
+ warnmsg(LOG_DEBUG, __func__,
+ "ioctl(SIOCGIFMEDIA) on %s: %s",
+ ifname, strerror(errno));
+ return(-1);
+ }
+ /*
+ * EINVAL simply means that the interface does not support
+ * the SIOCGIFMEDIA ioctl. We regard it alive.
+ */
+ ifinfo->mediareqok = 0;
+ 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);
+}
+
+#define ROUNDUP(a, size) \
+ (((a) & ((size)-1)) ? (1 + ((a) | ((size)-1))) : (a))
+
+#define NEXT_SA(ap) (ap) = (struct sockaddr *) \
+ ((caddr_t)(ap) + ((ap)->sa_len ? ROUNDUP((ap)->sa_len,\
+ sizeof(u_long)) : sizeof(u_long)))
+#define ROUNDUP8(a) (1 + (((a) - 1) | 7))
+
+int
+lladdropt_length(struct sockaddr_dl *sdl)
+{
+ switch (sdl->sdl_type) {
+ case IFT_ETHER:
+ case IFT_IEEE80211:
+ return (ROUNDUP8(ETHER_ADDR_LEN + 2));
+ default:
+ return (0);
+ }
+}
+
+void
+lladdropt_fill(struct sockaddr_dl *sdl, struct nd_opt_hdr *ndopt)
+{
+ char *addr;
+
+ ndopt->nd_opt_type = ND_OPT_SOURCE_LINKADDR; /* fixed */
+
+ switch (sdl->sdl_type) {
+ case IFT_ETHER:
+ case IFT_IEEE80211:
+ ndopt->nd_opt_len = (ROUNDUP8(ETHER_ADDR_LEN + 2)) >> 3;
+ addr = (char *)(ndopt + 1);
+ memcpy(addr, LLADDR(sdl), ETHER_ADDR_LEN);
+ break;
+ default:
+ warnmsg(LOG_ERR, __func__,
+ "unsupported link type(%d)", sdl->sdl_type);
+ exit(1);
+ }
+
+ return;
+}
+
+struct sockaddr_dl *
+if_nametosdl(char *name)
+{
+ int mib[] = {CTL_NET, AF_ROUTE, 0, 0, NET_RT_IFLIST, 0};
+ char *buf, *next, *lim;
+ size_t len;
+ struct if_msghdr *ifm;
+ struct sockaddr *sa, *rti_info[RTAX_MAX];
+ struct sockaddr_dl *sdl = NULL, *ret_sdl;
+
+ if (sysctl(mib, nitems(mib), NULL, &len, NULL, 0) < 0)
+ return(NULL);
+ if ((buf = malloc(len)) == NULL)
+ return(NULL);
+ if (sysctl(mib, nitems(mib), buf, &len, NULL, 0) < 0) {
+ free(buf);
+ return (NULL);
+ }
+
+ lim = buf + len;
+ for (next = buf; next < lim; next += ifm->ifm_msglen) {
+ ifm = (struct if_msghdr *)(void *)next;
+ if (ifm->ifm_type == RTM_IFINFO) {
+ sa = (struct sockaddr *)(ifm + 1);
+ get_rtaddrs(ifm->ifm_addrs, sa, rti_info);
+ if ((sa = rti_info[RTAX_IFP]) != NULL) {
+ if (sa->sa_family == AF_LINK) {
+ sdl = (struct sockaddr_dl *)(void *)sa;
+ if (strlen(name) != sdl->sdl_nlen)
+ continue; /* not same len */
+ if (strncmp(&sdl->sdl_data[0],
+ name,
+ sdl->sdl_nlen) == 0) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (next == lim) {
+ /* search failed */
+ free(buf);
+ return (NULL);
+ }
+
+ if ((ret_sdl = malloc(sdl->sdl_len)) == NULL) {
+ free(buf);
+ return (NULL);
+ }
+ memcpy((caddr_t)ret_sdl, (caddr_t)sdl, sdl->sdl_len);
+
+ free(buf);
+ return (ret_sdl);
+}
+
+int
+getinet6sysctl(int code)
+{
+ int mib[] = { CTL_NET, PF_INET6, IPPROTO_IPV6, 0 };
+ int value;
+ size_t size;
+
+ mib[3] = code;
+ size = sizeof(value);
+ if (sysctl(mib, nitems(mib), &value, &size, NULL, 0) < 0)
+ return (-1);
+ else
+ return (value);
+}
+
+int
+setinet6sysctl(int code, int newval)
+{
+ int mib[] = { CTL_NET, PF_INET6, IPPROTO_IPV6, 0 };
+ int value;
+ size_t size;
+
+ mib[3] = code;
+ size = sizeof(value);
+ if (sysctl(mib, nitems(mib), &value, &size,
+ &newval, sizeof(newval)) < 0)
+ return (-1);
+ else
+ return (value);
+}
+
+/*------------------------------------------------------------*/
+
+/* get ia6_flags for link-local addr on if. returns -1 on error. */
+static int
+get_llflag(const char *name)
+{
+ struct ifaddrs *ifap, *ifa;
+ struct in6_ifreq ifr6;
+ struct sockaddr_in6 *sin6;
+ int s;
+
+ if ((s = socket(PF_INET6, SOCK_DGRAM, 0)) < 0) {
+ warnmsg(LOG_ERR, __func__, "socket(SOCK_DGRAM): %s",
+ strerror(errno));
+ exit(1);
+ }
+ if (getifaddrs(&ifap) != 0) {
+ warnmsg(LOG_ERR, __func__, "getifaddrs: %s",
+ strerror(errno));
+ exit(1);
+ }
+
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ if (strlen(ifa->ifa_name) != strlen(name) ||
+ strncmp(ifa->ifa_name, name, strlen(name)) != 0)
+ continue;
+ if (ifa->ifa_addr->sa_family != AF_INET6)
+ continue;
+ sin6 = (struct sockaddr_in6 *)(void *)ifa->ifa_addr;
+ if (!IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
+ continue;
+
+ memset(&ifr6, 0, sizeof(ifr6));
+ strncpy(ifr6.ifr_name, name, sizeof(ifr6.ifr_name));
+ memcpy(&ifr6.ifr_ifru.ifru_addr, sin6, sin6->sin6_len);
+ if (ioctl(s, SIOCGIFAFLAG_IN6, &ifr6) < 0) {
+ warnmsg(LOG_ERR, __func__,
+ "ioctl(SIOCGIFAFLAG_IN6): %s", strerror(errno));
+ exit(1);
+ }
+
+ freeifaddrs(ifap);
+ close(s);
+ return (ifr6.ifr_ifru.ifru_flags6);
+ }
+
+ freeifaddrs(ifap);
+ close(s);
+ return (-1);
+}
+
+
+static void
+get_rtaddrs(int addrs, struct sockaddr *sa, struct sockaddr **rti_info)
+{
+ int i;
+
+ for (i = 0; i < RTAX_MAX; i++) {
+ if (addrs & (1 << i)) {
+ rti_info[i] = sa;
+ NEXT_SA(sa);
+ } else
+ rti_info[i] = NULL;
+ }
+}
diff --git a/usr.sbin/rtsold/probe.c b/usr.sbin/rtsold/probe.c
new file mode 100644
index 0000000..3aa5303
--- /dev/null
+++ b/usr.sbin/rtsold/probe.c
@@ -0,0 +1,189 @@
+/* $KAME: probe.c,v 1.17 2003/10/05 00:09:36 itojun Exp $ */
+
+/*
+ * Copyright (C) 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$
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/uio.h>
+#include <sys/queue.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+
+#include <netinet/in.h>
+#include <netinet6/in6_var.h>
+#include <netinet/icmp6.h>
+#include <netinet6/nd6.h>
+
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <syslog.h>
+#include <stdlib.h>
+
+#include "rtsold.h"
+
+static struct msghdr sndmhdr;
+static struct iovec sndiov[2];
+static int probesock;
+static void sendprobe(struct in6_addr *, struct ifinfo *);
+
+int
+probe_init(void)
+{
+ int scmsglen = CMSG_SPACE(sizeof(struct in6_pktinfo)) +
+ CMSG_SPACE(sizeof(int));
+ static u_char *sndcmsgbuf = NULL;
+
+ if (sndcmsgbuf == NULL &&
+ (sndcmsgbuf = (u_char *)malloc(scmsglen)) == NULL) {
+ warnmsg(LOG_ERR, __func__, "malloc failed");
+ return (-1);
+ }
+
+ if ((probesock = socket(AF_INET6, SOCK_RAW, IPPROTO_NONE)) < 0) {
+ warnmsg(LOG_ERR, __func__, "socket: %s", strerror(errno));
+ return (-1);
+ }
+
+ /* initialize msghdr for sending packets */
+ sndmhdr.msg_namelen = sizeof(struct sockaddr_in6);
+ sndmhdr.msg_iov = sndiov;
+ sndmhdr.msg_iovlen = 1;
+ sndmhdr.msg_control = (caddr_t)sndcmsgbuf;
+ sndmhdr.msg_controllen = scmsglen;
+
+ return (0);
+}
+
+/*
+ * Probe if each router in the default router list is still alive.
+ */
+void
+defrouter_probe(struct ifinfo *ifinfo)
+{
+ struct in6_defrouter *p, *ep;
+ int ifindex, mib[4];
+ char *buf, ntopbuf[INET6_ADDRSTRLEN];
+ size_t l;
+
+ ifindex = ifinfo->sdl->sdl_index;
+ if (ifindex == 0)
+ return;
+ mib[0] = CTL_NET;
+ mib[1] = PF_INET6;
+ mib[2] = IPPROTO_ICMPV6;
+ mib[3] = ICMPV6CTL_ND6_DRLIST;
+ if (sysctl(mib, nitems(mib), NULL, &l, NULL, 0) < 0) {
+ warnmsg(LOG_ERR, __func__, "sysctl(ICMPV6CTL_ND6_DRLIST): %s",
+ strerror(errno));
+ return;
+ }
+ if (l == 0)
+ return;
+ buf = malloc(l);
+ if (buf == NULL) {
+ warnmsg(LOG_ERR, __func__, "malloc(): %s", strerror(errno));
+ return;
+ }
+ if (sysctl(mib, nitems(mib), buf, &l, NULL, 0) < 0) {
+ warnmsg(LOG_ERR, __func__, "sysctl(ICMPV6CTL_ND6_DRLIST): %s",
+ strerror(errno));
+ free(buf);
+ return;
+ }
+ ep = (struct in6_defrouter *)(void *)(buf + l);
+ for (p = (struct in6_defrouter *)(void *)buf; p < ep; p++) {
+ if (ifindex != p->if_index)
+ continue;
+ if (!IN6_IS_ADDR_LINKLOCAL(&p->rtaddr.sin6_addr)) {
+ warnmsg(LOG_ERR, __func__,
+ "default router list contains a "
+ "non-link-local address(%s)",
+ inet_ntop(AF_INET6, &p->rtaddr.sin6_addr, ntopbuf,
+ INET6_ADDRSTRLEN));
+ continue; /* ignore the address */
+ }
+ sendprobe(&p->rtaddr.sin6_addr, ifinfo);
+ }
+ free(buf);
+}
+
+static void
+sendprobe(struct in6_addr *addr, struct ifinfo *ifinfo)
+{
+ u_char ntopbuf[INET6_ADDRSTRLEN], ifnamebuf[IFNAMSIZ];
+ struct sockaddr_in6 sa6_probe;
+ struct in6_pktinfo *pi;
+ struct cmsghdr *cm;
+ u_int32_t ifindex = ifinfo->sdl->sdl_index;
+ int hoplimit = 1;
+
+ memset(&sa6_probe, 0, sizeof(sa6_probe));
+ sa6_probe.sin6_family = AF_INET6;
+ sa6_probe.sin6_len = sizeof(sa6_probe);
+ sa6_probe.sin6_addr = *addr;
+ sa6_probe.sin6_scope_id = ifinfo->linkid;
+
+ sndmhdr.msg_name = (caddr_t)&sa6_probe;
+ sndmhdr.msg_iov[0].iov_base = NULL;
+ sndmhdr.msg_iov[0].iov_len = 0;
+
+ cm = CMSG_FIRSTHDR(&sndmhdr);
+ /* specify the outgoing interface */
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
+ pi = (struct in6_pktinfo *)(void *)CMSG_DATA(cm);
+ memset(&pi->ipi6_addr, 0, sizeof(pi->ipi6_addr)); /*XXX*/
+ pi->ipi6_ifindex = ifindex;
+
+ /* specify the hop limit of the packet for safety */
+ cm = CMSG_NXTHDR(&sndmhdr, cm);
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_HOPLIMIT;
+ cm->cmsg_len = CMSG_LEN(sizeof(int));
+ memcpy(CMSG_DATA(cm), &hoplimit, sizeof(int));
+
+ warnmsg(LOG_DEBUG, __func__, "probe a router %s on %s",
+ inet_ntop(AF_INET6, addr, ntopbuf, INET6_ADDRSTRLEN),
+ if_indextoname(ifindex, ifnamebuf));
+
+ if (sendmsg(probesock, &sndmhdr, 0))
+ warnmsg(LOG_ERR, __func__, "sendmsg on %s: %s",
+ if_indextoname(ifindex, ifnamebuf), strerror(errno));
+}
diff --git a/usr.sbin/rtsold/rtsock.c b/usr.sbin/rtsold/rtsock.c
new file mode 100644
index 0000000..702ffe1
--- /dev/null
+++ b/usr.sbin/rtsold/rtsock.c
@@ -0,0 +1,175 @@
+/* $KAME: rtsock.c,v 1.3 2000/10/10 08:46:45 itojun Exp $ */
+/* $FreeBSD$ */
+
+/*
+ * Copyright (C) 2000 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/param.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/time.h>
+#include <sys/queue.h>
+
+#include <net/if.h>
+#include <net/route.h>
+#include <net/if_dl.h>
+
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+
+#include <time.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <err.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include "rtsold.h"
+
+#define ROUNDUP(a, size) \
+ (((a) & ((size)-1)) ? (1 + ((a) | ((size)-1))) : (a))
+
+#define NEXT_SA(ap) (ap) = (struct sockaddr *) \
+ ((caddr_t)(ap) + \
+ ((ap)->sa_len ? ROUNDUP((ap)->sa_len, sizeof(u_long)) \
+ : sizeof(u_long)))
+
+#ifdef RTM_IFANNOUNCE /*NetBSD 1.5 or later*/
+static int rtsock_input_ifannounce(int, struct rt_msghdr *, char *);
+#endif
+
+static struct {
+ u_char type;
+ size_t minlen;
+ int (*func)(int, struct rt_msghdr *, char *);
+} rtsock_dispatch[] = {
+#ifdef RTM_IFANNOUNCE /*NetBSD 1.5 or later*/
+ { RTM_IFANNOUNCE, sizeof(struct if_announcemsghdr),
+ rtsock_input_ifannounce },
+#endif
+ { 0, 0, NULL },
+};
+
+int
+rtsock_open(void)
+{
+
+ return (socket(PF_ROUTE, SOCK_RAW, 0));
+}
+
+int
+rtsock_input(int s)
+{
+ ssize_t n;
+ char msg[2048];
+ char *lim, *next;
+ struct rt_msghdr *rtm;
+ int idx;
+ ssize_t len;
+ int ret = 0;
+ const ssize_t lenlim =
+ offsetof(struct rt_msghdr, rtm_msglen) + sizeof(rtm->rtm_msglen);
+
+ n = read(s, msg, sizeof(msg));
+
+ lim = msg + n;
+ for (next = msg; next < lim; next += len) {
+ rtm = (struct rt_msghdr *)(void *)next;
+ if (lim - next < lenlim)
+ break;
+ len = rtm->rtm_msglen;
+ if (len < lenlim)
+ break;
+
+ if (dflag > 1) {
+ warnmsg(LOG_INFO, __func__,
+ "rtmsg type %d, len=%lu", rtm->rtm_type,
+ (u_long)len);
+ }
+
+ for (idx = 0; rtsock_dispatch[idx].func; idx++) {
+ if (rtm->rtm_type != rtsock_dispatch[idx].type)
+ continue;
+ if (rtm->rtm_msglen < rtsock_dispatch[idx].minlen) {
+ warnmsg(LOG_INFO, __func__,
+ "rtmsg type %d too short!", rtm->rtm_type);
+ continue;
+ }
+
+ ret = (*rtsock_dispatch[idx].func)(s, rtm, lim);
+ break;
+ }
+ }
+
+ return (ret);
+}
+
+#ifdef RTM_IFANNOUNCE /*NetBSD 1.5 or later*/
+static int
+rtsock_input_ifannounce(int s __unused, struct rt_msghdr *rtm, char *lim)
+{
+ struct if_announcemsghdr *ifan;
+ struct ifinfo *ifi;
+
+ ifan = (struct if_announcemsghdr *)rtm;
+ if ((char *)(ifan + 1) > lim)
+ return (-1);
+
+ switch (ifan->ifan_what) {
+ case IFAN_ARRIVAL:
+ /*
+ * XXX for NetBSD 1.5, interface index will monotonically be
+ * increased as new pcmcia card gets inserted.
+ * we may be able to do a name-based interface match,
+ * and call ifreconfig() to enable the interface again.
+ */
+ warnmsg(LOG_INFO, __func__,
+ "interface %s inserted", ifan->ifan_name);
+ break;
+ case IFAN_DEPARTURE:
+ warnmsg(LOG_WARNING, __func__,
+ "interface %s removed", ifan->ifan_name);
+ ifi = find_ifinfo(ifan->ifan_index);
+ if (ifi) {
+ if (dflag > 1) {
+ warnmsg(LOG_INFO, __func__,
+ "bring interface %s to DOWN state",
+ ifan->ifan_name);
+ }
+ ifi->state = IFS_DOWN;
+ }
+ break;
+ }
+
+ return (0);
+}
+#endif
diff --git a/usr.sbin/rtsold/rtsol.c b/usr.sbin/rtsold/rtsol.c
new file mode 100644
index 0000000..118206a
--- /dev/null
+++ b/usr.sbin/rtsold/rtsol.c
@@ -0,0 +1,955 @@
+/* $KAME: rtsol.c,v 1.27 2003/10/05 00:09:36 itojun Exp $ */
+
+/*
+ * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
+ * Copyright (C) 2011 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.
+ * 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/param.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/queue.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+
+#include <net/if.h>
+#include <net/route.h>
+#include <net/if_dl.h>
+
+#define __BSD_VISIBLE 1 /* IN6ADDR_LINKLOCAL_ALLROUTERS_INIT */
+#include <netinet/in.h>
+#undef __BSD_VISIBLE
+#include <netinet/ip6.h>
+#include <netinet6/ip6_var.h>
+#include <netinet/icmp6.h>
+
+#include <arpa/inet.h>
+
+#include <netdb.h>
+#include <time.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <time.h>
+#include <err.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include "rtsold.h"
+
+static struct msghdr rcvmhdr;
+static struct msghdr sndmhdr;
+static struct iovec rcviov[2];
+static struct iovec sndiov[2];
+static struct sockaddr_in6 from;
+static int rcvcmsglen;
+
+int rssock;
+static char rsid[IFNAMSIZ + 1 + sizeof(DNSINFO_ORIGIN_LABEL) + 1 + NI_MAXHOST];
+struct ifinfo_head_t ifinfo_head =
+ TAILQ_HEAD_INITIALIZER(ifinfo_head);
+
+static const struct sockaddr_in6 sin6_allrouters = {
+ .sin6_len = sizeof(sin6_allrouters),
+ .sin6_family = AF_INET6,
+ .sin6_addr = IN6ADDR_LINKLOCAL_ALLROUTERS_INIT,
+};
+
+static void call_script(const int, const char *const *,
+ struct script_msg_head_t *);
+static size_t dname_labeldec(char *, size_t, const char *);
+static int safefile(const char *);
+static struct ra_opt *find_raopt(struct rainfo *, int, void *, size_t);
+static int ra_opt_rdnss_dispatch(struct ifinfo *, struct rainfo *,
+ struct script_msg_head_t *, struct script_msg_head_t *);
+static char *make_rsid(const char *, const char *, struct rainfo *);
+
+#define _ARGS_OTHER otherconf_script, ifi->ifname
+#define _ARGS_RESADD resolvconf_script, "-a", rsid
+#define _ARGS_RESDEL resolvconf_script, "-d", rsid
+
+#define CALL_SCRIPT(name, sm_head) \
+ do { \
+ const char *const sarg[] = { _ARGS_##name, NULL }; \
+ call_script(sizeof(sarg), sarg, sm_head); \
+ } while(0)
+
+#define ELM_MALLOC(p,error_action) \
+ do { \
+ p = malloc(sizeof(*p)); \
+ if (p == NULL) { \
+ warnmsg(LOG_ERR, __func__, "malloc failed: %s", \
+ strerror(errno)); \
+ error_action; \
+ } \
+ memset(p, 0, sizeof(*p)); \
+ } while(0)
+
+int
+sockopen(void)
+{
+ static u_char *rcvcmsgbuf = NULL, *sndcmsgbuf = NULL;
+ int sndcmsglen, on;
+ static u_char answer[1500];
+ struct icmp6_filter filt;
+
+ sndcmsglen = rcvcmsglen = CMSG_SPACE(sizeof(struct in6_pktinfo)) +
+ CMSG_SPACE(sizeof(int));
+ if (rcvcmsgbuf == NULL && (rcvcmsgbuf = malloc(rcvcmsglen)) == NULL) {
+ warnmsg(LOG_ERR, __func__,
+ "malloc for receive msghdr failed");
+ return (-1);
+ }
+ if (sndcmsgbuf == NULL && (sndcmsgbuf = malloc(sndcmsglen)) == NULL) {
+ warnmsg(LOG_ERR, __func__,
+ "malloc for send msghdr failed");
+ return (-1);
+ }
+ if ((rssock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0) {
+ warnmsg(LOG_ERR, __func__, "socket: %s", strerror(errno));
+ return (-1);
+ }
+
+ /* specify to tell receiving interface */
+ on = 1;
+ if (setsockopt(rssock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on,
+ sizeof(on)) < 0) {
+ warnmsg(LOG_ERR, __func__, "IPV6_RECVPKTINFO: %s",
+ strerror(errno));
+ exit(1);
+ }
+
+ /* specify to tell value of hoplimit field of received IP6 hdr */
+ on = 1;
+ if (setsockopt(rssock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on,
+ sizeof(on)) < 0) {
+ warnmsg(LOG_ERR, __func__, "IPV6_RECVHOPLIMIT: %s",
+ strerror(errno));
+ exit(1);
+ }
+
+ /* specfiy to accept only router advertisements on the socket */
+ ICMP6_FILTER_SETBLOCKALL(&filt);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt);
+ if (setsockopt(rssock, IPPROTO_ICMPV6, ICMP6_FILTER, &filt,
+ sizeof(filt)) == -1) {
+ warnmsg(LOG_ERR, __func__, "setsockopt(ICMP6_FILTER): %s",
+ strerror(errno));
+ return(-1);
+ }
+
+ /* initialize msghdr for receiving packets */
+ rcviov[0].iov_base = (caddr_t)answer;
+ rcviov[0].iov_len = sizeof(answer);
+ rcvmhdr.msg_name = (caddr_t)&from;
+ rcvmhdr.msg_iov = rcviov;
+ rcvmhdr.msg_iovlen = 1;
+ rcvmhdr.msg_control = (caddr_t) rcvcmsgbuf;
+
+ /* initialize msghdr for sending packets */
+ sndmhdr.msg_namelen = sizeof(struct sockaddr_in6);
+ sndmhdr.msg_iov = sndiov;
+ sndmhdr.msg_iovlen = 1;
+ sndmhdr.msg_control = (caddr_t)sndcmsgbuf;
+ sndmhdr.msg_controllen = sndcmsglen;
+
+ return (rssock);
+}
+
+void
+sendpacket(struct ifinfo *ifi)
+{
+ struct in6_pktinfo *pi;
+ struct cmsghdr *cm;
+ int hoplimit = 255;
+ ssize_t i;
+ struct sockaddr_in6 dst;
+
+ dst = sin6_allrouters;
+ dst.sin6_scope_id = ifi->linkid;
+
+ sndmhdr.msg_name = (caddr_t)&dst;
+ sndmhdr.msg_iov[0].iov_base = (caddr_t)ifi->rs_data;
+ sndmhdr.msg_iov[0].iov_len = ifi->rs_datalen;
+
+ cm = CMSG_FIRSTHDR(&sndmhdr);
+ /* specify the outgoing interface */
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
+ pi = (struct in6_pktinfo *)(void *)CMSG_DATA(cm);
+ memset(&pi->ipi6_addr, 0, sizeof(pi->ipi6_addr)); /*XXX*/
+ pi->ipi6_ifindex = ifi->sdl->sdl_index;
+
+ /* specify the hop limit of the packet */
+ cm = CMSG_NXTHDR(&sndmhdr, cm);
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_HOPLIMIT;
+ cm->cmsg_len = CMSG_LEN(sizeof(int));
+ memcpy(CMSG_DATA(cm), &hoplimit, sizeof(int));
+
+ warnmsg(LOG_DEBUG, __func__,
+ "send RS on %s, whose state is %d",
+ ifi->ifname, ifi->state);
+ i = sendmsg(rssock, &sndmhdr, 0);
+ if (i < 0 || (size_t)i != ifi->rs_datalen) {
+ /*
+ * ENETDOWN is not so serious, especially when using several
+ * network cards on a mobile node. We ignore it.
+ */
+ if (errno != ENETDOWN || dflag > 0)
+ warnmsg(LOG_ERR, __func__, "sendmsg on %s: %s",
+ ifi->ifname, strerror(errno));
+ }
+
+ /* update counter */
+ ifi->probes++;
+}
+
+void
+rtsol_input(int s)
+{
+ char ntopbuf[INET6_ADDRSTRLEN], ifnamebuf[IFNAMSIZ];
+ int l, ifindex = 0, *hlimp = NULL;
+ ssize_t msglen;
+ struct in6_pktinfo *pi = NULL;
+ struct ifinfo *ifi = NULL;
+ struct ra_opt *rao = NULL;
+ struct icmp6_hdr *icp;
+ struct nd_router_advert *nd_ra;
+ struct cmsghdr *cm;
+ struct rainfo *rai;
+ char *raoptp;
+ char *p;
+ struct in6_addr *addr;
+ struct nd_opt_hdr *ndo;
+ struct nd_opt_rdnss *rdnss;
+ struct nd_opt_dnssl *dnssl;
+ size_t len;
+ char nsbuf[INET6_ADDRSTRLEN + 1 + IFNAMSIZ + 1];
+ char dname[NI_MAXHOST];
+ struct timespec now;
+ struct timespec lifetime;
+ int newent_rai;
+ int newent_rao;
+
+ /* get message. namelen and controllen must always be initialized. */
+ rcvmhdr.msg_namelen = sizeof(from);
+ rcvmhdr.msg_controllen = rcvcmsglen;
+ if ((msglen = recvmsg(s, &rcvmhdr, 0)) < 0) {
+ warnmsg(LOG_ERR, __func__, "recvmsg: %s", strerror(errno));
+ return;
+ }
+
+ /* extract optional information via Advanced API */
+ for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&rcvmhdr); cm;
+ cm = (struct cmsghdr *)CMSG_NXTHDR(&rcvmhdr, cm)) {
+ if (cm->cmsg_level == IPPROTO_IPV6 &&
+ cm->cmsg_type == IPV6_PKTINFO &&
+ cm->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo))) {
+ pi = (struct in6_pktinfo *)(void *)(CMSG_DATA(cm));
+ ifindex = pi->ipi6_ifindex;
+ }
+ if (cm->cmsg_level == IPPROTO_IPV6 &&
+ cm->cmsg_type == IPV6_HOPLIMIT &&
+ cm->cmsg_len == CMSG_LEN(sizeof(int)))
+ hlimp = (int *)(void *)CMSG_DATA(cm);
+ }
+
+ if (ifindex == 0) {
+ warnmsg(LOG_ERR, __func__,
+ "failed to get receiving interface");
+ return;
+ }
+ if (hlimp == NULL) {
+ warnmsg(LOG_ERR, __func__,
+ "failed to get receiving hop limit");
+ return;
+ }
+
+ if ((size_t)msglen < sizeof(struct nd_router_advert)) {
+ warnmsg(LOG_INFO, __func__,
+ "packet size(%zd) is too short", msglen);
+ return;
+ }
+
+ icp = (struct icmp6_hdr *)rcvmhdr.msg_iov[0].iov_base;
+
+ if (icp->icmp6_type != ND_ROUTER_ADVERT) {
+ /*
+ * this should not happen because we configured a filter
+ * that only passes RAs on the receiving socket.
+ */
+ warnmsg(LOG_ERR, __func__,
+ "invalid icmp type(%d) from %s on %s", icp->icmp6_type,
+ inet_ntop(AF_INET6, &from.sin6_addr, ntopbuf,
+ sizeof(ntopbuf)),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+ return;
+ }
+
+ if (icp->icmp6_code != 0) {
+ warnmsg(LOG_INFO, __func__,
+ "invalid icmp code(%d) from %s on %s", icp->icmp6_code,
+ inet_ntop(AF_INET6, &from.sin6_addr, ntopbuf,
+ sizeof(ntopbuf)),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+ return;
+ }
+
+ if (*hlimp != 255) {
+ warnmsg(LOG_INFO, __func__,
+ "invalid RA with hop limit(%d) from %s on %s",
+ *hlimp,
+ inet_ntop(AF_INET6, &from.sin6_addr, ntopbuf,
+ sizeof(ntopbuf)),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+ return;
+ }
+
+ if (pi && !IN6_IS_ADDR_LINKLOCAL(&from.sin6_addr)) {
+ warnmsg(LOG_INFO, __func__,
+ "invalid RA with non link-local source from %s on %s",
+ inet_ntop(AF_INET6, &from.sin6_addr, ntopbuf,
+ sizeof(ntopbuf)),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+ return;
+ }
+
+ /* xxx: more validation? */
+
+ if ((ifi = find_ifinfo(pi->ipi6_ifindex)) == NULL) {
+ warnmsg(LOG_INFO, __func__,
+ "received RA from %s on an unexpected IF(%s)",
+ inet_ntop(AF_INET6, &from.sin6_addr, ntopbuf,
+ sizeof(ntopbuf)),
+ if_indextoname(pi->ipi6_ifindex, ifnamebuf));
+ return;
+ }
+
+ warnmsg(LOG_DEBUG, __func__,
+ "received RA from %s on %s, state is %d",
+ inet_ntop(AF_INET6, &from.sin6_addr, ntopbuf, sizeof(ntopbuf)),
+ ifi->ifname, ifi->state);
+
+ nd_ra = (struct nd_router_advert *)icp;
+
+ /*
+ * Process the "O bit."
+ * If the value of OtherConfigFlag changes from FALSE to TRUE, the
+ * host should invoke the stateful autoconfiguration protocol,
+ * requesting information.
+ * [RFC 2462 Section 5.5.3]
+ */
+ if (((nd_ra->nd_ra_flags_reserved) & ND_RA_FLAG_OTHER) &&
+ !ifi->otherconfig) {
+ warnmsg(LOG_DEBUG, __func__,
+ "OtherConfigFlag on %s is turned on", ifi->ifname);
+ ifi->otherconfig = 1;
+ CALL_SCRIPT(OTHER, NULL);
+ }
+ clock_gettime(CLOCK_MONOTONIC_FAST, &now);
+ newent_rai = 0;
+ rai = find_rainfo(ifi, &from);
+ if (rai == NULL) {
+ ELM_MALLOC(rai, exit(1));
+ rai->rai_ifinfo = ifi;
+ TAILQ_INIT(&rai->rai_ra_opt);
+ rai->rai_saddr.sin6_family = AF_INET6;
+ rai->rai_saddr.sin6_len = sizeof(rai->rai_saddr);
+ memcpy(&rai->rai_saddr.sin6_addr, &from.sin6_addr,
+ sizeof(rai->rai_saddr.sin6_addr));
+ newent_rai = 1;
+ }
+
+#define RA_OPT_NEXT_HDR(x) (struct nd_opt_hdr *)((char *)x + \
+ (((struct nd_opt_hdr *)x)->nd_opt_len * 8))
+ /* Process RA options. */
+ warnmsg(LOG_DEBUG, __func__, "Processing RA");
+ raoptp = (char *)icp + sizeof(struct nd_router_advert);
+ while (raoptp < (char *)icp + msglen) {
+ ndo = (struct nd_opt_hdr *)raoptp;
+ warnmsg(LOG_DEBUG, __func__, "ndo = %p", raoptp);
+ warnmsg(LOG_DEBUG, __func__, "ndo->nd_opt_type = %d",
+ ndo->nd_opt_type);
+ warnmsg(LOG_DEBUG, __func__, "ndo->nd_opt_len = %d",
+ ndo->nd_opt_len);
+
+ switch (ndo->nd_opt_type) {
+ case ND_OPT_RDNSS:
+ rdnss = (struct nd_opt_rdnss *)raoptp;
+
+ /* Optlen sanity check (Section 5.3.1 in RFC 6106) */
+ if (rdnss->nd_opt_rdnss_len < 3) {
+ warnmsg(LOG_INFO, __func__,
+ "too short RDNSS option"
+ "in RA from %s was ignored.",
+ inet_ntop(AF_INET6, &from.sin6_addr,
+ ntopbuf, sizeof(ntopbuf)));
+ break;
+ }
+
+ addr = (struct in6_addr *)(void *)(raoptp + sizeof(*rdnss));
+ while ((char *)addr < (char *)RA_OPT_NEXT_HDR(raoptp)) {
+ if (inet_ntop(AF_INET6, addr, ntopbuf,
+ sizeof(ntopbuf)) == NULL) {
+ warnmsg(LOG_INFO, __func__,
+ "an invalid address in RDNSS option"
+ " in RA from %s was ignored.",
+ inet_ntop(AF_INET6, &from.sin6_addr,
+ ntopbuf, sizeof(ntopbuf)));
+ addr++;
+ continue;
+ }
+ if (IN6_IS_ADDR_LINKLOCAL(addr))
+ /* XXX: % has to be escaped here */
+ l = snprintf(nsbuf, sizeof(nsbuf),
+ "%s%c%s", ntopbuf,
+ SCOPE_DELIMITER,
+ ifi->ifname);
+ else
+ l = snprintf(nsbuf, sizeof(nsbuf),
+ "%s", ntopbuf);
+ if (l < 0 || (size_t)l >= sizeof(nsbuf)) {
+ warnmsg(LOG_ERR, __func__,
+ "address copying error in "
+ "RDNSS option: %d.", l);
+ addr++;
+ continue;
+ }
+ warnmsg(LOG_DEBUG, __func__, "nsbuf = %s",
+ nsbuf);
+
+ newent_rao = 0;
+ rao = find_raopt(rai, ndo->nd_opt_type, nsbuf,
+ strlen(nsbuf));
+ if (rao == NULL) {
+ ELM_MALLOC(rao, break);
+ rao->rao_type = ndo->nd_opt_type;
+ rao->rao_len = strlen(nsbuf);
+ rao->rao_msg = strdup(nsbuf);
+ if (rao->rao_msg == NULL) {
+ warnmsg(LOG_ERR, __func__,
+ "strdup failed: %s",
+ strerror(errno));
+ free(rao);
+ addr++;
+ continue;
+ }
+ newent_rao = 1;
+ }
+ /* Set expiration timer */
+ memset(&rao->rao_expire, 0,
+ sizeof(rao->rao_expire));
+ memset(&lifetime, 0, sizeof(lifetime));
+ lifetime.tv_sec =
+ ntohl(rdnss->nd_opt_rdnss_lifetime);
+ TS_ADD(&now, &lifetime, &rao->rao_expire);
+
+ if (newent_rao)
+ TAILQ_INSERT_TAIL(&rai->rai_ra_opt,
+ rao, rao_next);
+ addr++;
+ }
+ break;
+ case ND_OPT_DNSSL:
+ dnssl = (struct nd_opt_dnssl *)raoptp;
+
+ /* Optlen sanity check (Section 5.3.1 in RFC 6106) */
+ if (dnssl->nd_opt_dnssl_len < 2) {
+ warnmsg(LOG_INFO, __func__,
+ "too short DNSSL option"
+ "in RA from %s was ignored.",
+ inet_ntop(AF_INET6, &from.sin6_addr,
+ ntopbuf, sizeof(ntopbuf)));
+ break;
+ }
+
+ /*
+ * Ensure NUL-termination in DNSSL in case of
+ * malformed field.
+ */
+ p = (char *)RA_OPT_NEXT_HDR(raoptp);
+ *(p - 1) = '\0';
+
+ p = raoptp + sizeof(*dnssl);
+ while (1 < (len = dname_labeldec(dname, sizeof(dname),
+ p))) {
+ /* length == 1 means empty string */
+ warnmsg(LOG_DEBUG, __func__, "dname = %s",
+ dname);
+
+ newent_rao = 0;
+ rao = find_raopt(rai, ndo->nd_opt_type, dname,
+ strlen(dname));
+ if (rao == NULL) {
+ ELM_MALLOC(rao, break);
+ rao->rao_type = ndo->nd_opt_type;
+ rao->rao_len = strlen(dname);
+ rao->rao_msg = strdup(dname);
+ if (rao->rao_msg == NULL) {
+ warnmsg(LOG_ERR, __func__,
+ "strdup failed: %s",
+ strerror(errno));
+ free(rao);
+ addr++;
+ continue;
+ }
+ newent_rao = 1;
+ }
+ /* Set expiration timer */
+ memset(&rao->rao_expire, 0,
+ sizeof(rao->rao_expire));
+ memset(&lifetime, 0, sizeof(lifetime));
+ lifetime.tv_sec =
+ ntohl(dnssl->nd_opt_dnssl_lifetime);
+ TS_ADD(&now, &lifetime, &rao->rao_expire);
+
+ if (newent_rao)
+ TAILQ_INSERT_TAIL(&rai->rai_ra_opt,
+ rao, rao_next);
+ p += len;
+ }
+ break;
+ default:
+ /* nothing to do for other options */
+ break;
+ }
+ raoptp = (char *)RA_OPT_NEXT_HDR(raoptp);
+ }
+ if (newent_rai)
+ TAILQ_INSERT_TAIL(&ifi->ifi_rainfo, rai, rai_next);
+
+ ra_opt_handler(ifi);
+ ifi->racnt++;
+
+ switch (ifi->state) {
+ case IFS_IDLE: /* should be ignored */
+ case IFS_DELAY: /* right? */
+ break;
+ case IFS_PROBE:
+ ifi->state = IFS_IDLE;
+ ifi->probes = 0;
+ rtsol_timer_update(ifi);
+ break;
+ }
+}
+
+static char resstr_ns_prefix[] = "nameserver ";
+static char resstr_sh_prefix[] = "search ";
+static char resstr_nl[] = "\n";
+static char resstr_sp[] = " ";
+
+int
+ra_opt_handler(struct ifinfo *ifi)
+{
+ struct ra_opt *rao;
+ struct rainfo *rai;
+ struct script_msg *smp1, *smp2, *smp3;
+ struct timespec now;
+ struct script_msg_head_t sm_rdnss_head =
+ TAILQ_HEAD_INITIALIZER(sm_rdnss_head);
+ struct script_msg_head_t sm_dnssl_head =
+ TAILQ_HEAD_INITIALIZER(sm_dnssl_head);
+
+ int dcount, dlen;
+
+ dcount = 0;
+ dlen = strlen(resstr_sh_prefix) + strlen(resstr_nl);
+ clock_gettime(CLOCK_MONOTONIC_FAST, &now);
+
+ /*
+ * All options from multiple RAs with the same or different
+ * source addresses on a single interface will be gathered and
+ * handled, not overridden. [RFC 4861 6.3.4]
+ */
+ TAILQ_FOREACH(rai, &ifi->ifi_rainfo, rai_next) {
+ TAILQ_FOREACH(rao, &rai->rai_ra_opt, rao_next) {
+ switch (rao->rao_type) {
+ case ND_OPT_RDNSS:
+ if (TS_CMP(&now, &rao->rao_expire, >)) {
+ warnmsg(LOG_INFO, __func__,
+ "expired rdnss entry: %s",
+ (char *)rao->rao_msg);
+ break;
+ }
+ ELM_MALLOC(smp1, continue);
+ ELM_MALLOC(smp2, goto free1);
+ ELM_MALLOC(smp3, goto free2);
+ smp1->sm_msg = resstr_ns_prefix;
+ TAILQ_INSERT_TAIL(&sm_rdnss_head, smp1,
+ sm_next);
+ smp2->sm_msg = rao->rao_msg;
+ TAILQ_INSERT_TAIL(&sm_rdnss_head, smp2,
+ sm_next);
+ smp3->sm_msg = resstr_nl;
+ TAILQ_INSERT_TAIL(&sm_rdnss_head, smp3,
+ sm_next);
+ ifi->ifi_rdnss = IFI_DNSOPT_STATE_RECEIVED;
+
+ break;
+ case ND_OPT_DNSSL:
+ if (TS_CMP(&now, &rao->rao_expire, >)) {
+ warnmsg(LOG_INFO, __func__,
+ "expired dnssl entry: %s",
+ (char *)rao->rao_msg);
+ break;
+ }
+ dcount++;
+ /* Check resolv.conf(5) restrictions. */
+ if (dcount > 6) {
+ warnmsg(LOG_INFO, __func__,
+ "dnssl entry exceeding maximum count (%d>6)"
+ ": %s", dcount, (char *)rao->rao_msg);
+ break;
+ }
+ if (256 < dlen + strlen(rao->rao_msg) +
+ strlen(resstr_sp)) {
+ warnmsg(LOG_INFO, __func__,
+ "dnssl entry exceeding maximum length "
+ "(>256): %s", (char *)rao->rao_msg);
+ break;
+ }
+ ELM_MALLOC(smp1, continue);
+ ELM_MALLOC(smp2, goto free1);
+ if (TAILQ_EMPTY(&sm_dnssl_head)) {
+ ELM_MALLOC(smp3, goto free2);
+ smp3->sm_msg = resstr_sh_prefix;
+ TAILQ_INSERT_TAIL(&sm_dnssl_head, smp3,
+ sm_next);
+ }
+ smp1->sm_msg = rao->rao_msg;
+ TAILQ_INSERT_TAIL(&sm_dnssl_head, smp1,
+ sm_next);
+ smp2->sm_msg = resstr_sp;
+ TAILQ_INSERT_TAIL(&sm_dnssl_head, smp2,
+ sm_next);
+ dlen += strlen(rao->rao_msg) +
+ strlen(resstr_sp);
+ break;
+
+ ifi->ifi_dnssl = IFI_DNSOPT_STATE_RECEIVED;
+ default:
+ break;
+ }
+ continue;
+free2:
+ free(smp2);
+free1:
+ free(smp1);
+ }
+ /* Call the script for each information source. */
+ if (uflag)
+ ra_opt_rdnss_dispatch(ifi, rai, &sm_rdnss_head,
+ &sm_dnssl_head);
+ }
+ /* Call the script for each interface. */
+ if (!uflag)
+ ra_opt_rdnss_dispatch(ifi, NULL, &sm_rdnss_head,
+ &sm_dnssl_head);
+ return (0);
+}
+
+char *
+make_rsid(const char *ifname, const char *origin, struct rainfo *rai)
+{
+ char hbuf[NI_MAXHOST];
+
+ if (rai == NULL)
+ sprintf(rsid, "%s:%s", ifname, origin);
+ else {
+ if (!IN6_IS_ADDR_LINKLOCAL(&rai->rai_saddr.sin6_addr))
+ return (NULL);
+ if (getnameinfo((struct sockaddr *)&rai->rai_saddr,
+ rai->rai_saddr.sin6_len, hbuf, sizeof(hbuf), NULL, 0,
+ NI_NUMERICHOST) != 0)
+ return (NULL);
+ sprintf(rsid, "%s:%s:[%s]", ifname, origin, hbuf);
+ }
+ warnmsg(LOG_DEBUG, __func__, "rsid = [%s]", rsid);
+ return (rsid);
+}
+
+int
+ra_opt_rdnss_dispatch(struct ifinfo *ifi,
+ struct rainfo *rai,
+ struct script_msg_head_t *sm_rdnss_head,
+ struct script_msg_head_t *sm_dnssl_head)
+{
+ const char *r;
+ struct script_msg *smp1;
+ int error;
+
+ error = 0;
+ /* Add \n for DNSSL list. */
+ if (!TAILQ_EMPTY(sm_dnssl_head)) {
+ ELM_MALLOC(smp1, goto ra_opt_rdnss_freeit);
+ smp1->sm_msg = resstr_nl;
+ TAILQ_INSERT_TAIL(sm_dnssl_head, smp1, sm_next);
+ }
+ TAILQ_CONCAT(sm_rdnss_head, sm_dnssl_head, sm_next);
+
+ if (rai != NULL && uflag)
+ r = make_rsid(ifi->ifname, DNSINFO_ORIGIN_LABEL, rai);
+ else
+ r = make_rsid(ifi->ifname, DNSINFO_ORIGIN_LABEL, NULL);
+ if (r == NULL) {
+ warnmsg(LOG_ERR, __func__, "make_rsid() failed. "
+ "Script was not invoked.");
+ error = 1;
+ goto ra_opt_rdnss_freeit;
+ }
+ if (!TAILQ_EMPTY(sm_rdnss_head))
+ CALL_SCRIPT(RESADD, sm_rdnss_head);
+ else if (ifi->ifi_rdnss == IFI_DNSOPT_STATE_RECEIVED ||
+ ifi->ifi_dnssl == IFI_DNSOPT_STATE_RECEIVED) {
+ CALL_SCRIPT(RESDEL, NULL);
+ ifi->ifi_rdnss = IFI_DNSOPT_STATE_NOINFO;
+ ifi->ifi_dnssl = IFI_DNSOPT_STATE_NOINFO;
+ }
+
+ra_opt_rdnss_freeit:
+ /* Clear script message queue. */
+ if (!TAILQ_EMPTY(sm_rdnss_head)) {
+ while ((smp1 = TAILQ_FIRST(sm_rdnss_head)) != NULL) {
+ TAILQ_REMOVE(sm_rdnss_head, smp1, sm_next);
+ free(smp1);
+ }
+ }
+ if (!TAILQ_EMPTY(sm_dnssl_head)) {
+ while ((smp1 = TAILQ_FIRST(sm_dnssl_head)) != NULL) {
+ TAILQ_REMOVE(sm_dnssl_head, smp1, sm_next);
+ free(smp1);
+ }
+ }
+ return (error);
+}
+
+static struct ra_opt *
+find_raopt(struct rainfo *rai, int type, void *msg, size_t len)
+{
+ struct ra_opt *rao;
+
+ TAILQ_FOREACH(rao, &rai->rai_ra_opt, rao_next) {
+ if (rao->rao_type == type &&
+ rao->rao_len == strlen(msg) &&
+ memcmp(rao->rao_msg, msg, len) == 0)
+ break;
+ }
+
+ return (rao);
+}
+
+static void
+call_script(const int argc, const char *const argv[],
+ struct script_msg_head_t *sm_head)
+{
+ const char *scriptpath;
+ int fd[2];
+ int error;
+ pid_t pid, wpid;
+
+ if ((scriptpath = argv[0]) == NULL)
+ return;
+
+ fd[0] = fd[1] = -1;
+ if (sm_head != NULL && !TAILQ_EMPTY(sm_head)) {
+ error = pipe(fd);
+ if (error) {
+ warnmsg(LOG_ERR, __func__,
+ "failed to create a pipe: %s", strerror(errno));
+ return;
+ }
+ }
+
+ /* launch the script */
+ pid = fork();
+ if (pid < 0) {
+ warnmsg(LOG_ERR, __func__,
+ "failed to fork: %s", strerror(errno));
+ return;
+ } else if (pid) { /* parent */
+ int wstatus;
+
+ if (fd[0] != -1) { /* Send message to the child if any. */
+ ssize_t len;
+ struct script_msg *smp;
+
+ close(fd[0]);
+ TAILQ_FOREACH(smp, sm_head, sm_next) {
+ len = strlen(smp->sm_msg);
+ warnmsg(LOG_DEBUG, __func__,
+ "write to child = %s(%zd)",
+ smp->sm_msg, len);
+ if (write(fd[1], smp->sm_msg, len) != len) {
+ warnmsg(LOG_ERR, __func__,
+ "write to child failed: %s",
+ strerror(errno));
+ break;
+ }
+ }
+ close(fd[1]);
+ }
+ do {
+ wpid = wait(&wstatus);
+ } while (wpid != pid && wpid > 0);
+
+ if (wpid < 0)
+ warnmsg(LOG_ERR, __func__,
+ "wait: %s", strerror(errno));
+ else
+ warnmsg(LOG_DEBUG, __func__,
+ "script \"%s\" terminated", scriptpath);
+ } else { /* child */
+ int nullfd;
+ char **_argv;
+
+ if (safefile(scriptpath)) {
+ warnmsg(LOG_ERR, __func__,
+ "script \"%s\" cannot be executed safely",
+ scriptpath);
+ exit(1);
+ }
+ nullfd = open("/dev/null", O_RDWR);
+ if (nullfd < 0) {
+ warnmsg(LOG_ERR, __func__,
+ "open /dev/null: %s", strerror(errno));
+ exit(1);
+ }
+ if (fd[0] != -1) { /* Receive message from STDIN if any. */
+ close(fd[1]);
+ if (fd[0] != STDIN_FILENO) {
+ /* Connect a pipe read-end to child's STDIN. */
+ if (dup2(fd[0], STDIN_FILENO) != STDIN_FILENO) {
+ warnmsg(LOG_ERR, __func__,
+ "dup2 STDIN: %s", strerror(errno));
+ exit(1);
+ }
+ close(fd[0]);
+ }
+ } else
+ dup2(nullfd, STDIN_FILENO);
+
+ dup2(nullfd, STDOUT_FILENO);
+ dup2(nullfd, STDERR_FILENO);
+ if (nullfd > STDERR_FILENO)
+ close(nullfd);
+
+ _argv = malloc(sizeof(*_argv) * argc);
+ if (_argv == NULL) {
+ warnmsg(LOG_ERR, __func__,
+ "malloc: %s", strerror(errno));
+ exit(1);
+ }
+ memcpy(_argv, argv, (size_t)argc);
+ execv(scriptpath, (char *const *)_argv);
+ warnmsg(LOG_ERR, __func__, "child: exec failed: %s",
+ strerror(errno));
+ exit(1);
+ }
+
+ return;
+}
+
+static int
+safefile(const char *path)
+{
+ struct stat s;
+ uid_t myuid;
+
+ /* no setuid */
+ if (getuid() != geteuid()) {
+ warnmsg(LOG_NOTICE, __func__,
+ "setuid'ed execution not allowed\n");
+ return (-1);
+ }
+
+ if (lstat(path, &s) != 0) {
+ warnmsg(LOG_NOTICE, __func__, "lstat failed: %s",
+ strerror(errno));
+ return (-1);
+ }
+
+ /* the file must be owned by the running uid */
+ myuid = getuid();
+ if (s.st_uid != myuid) {
+ warnmsg(LOG_NOTICE, __func__,
+ "%s has invalid owner uid\n", path);
+ return (-1);
+ }
+
+ switch (s.st_mode & S_IFMT) {
+ case S_IFREG:
+ break;
+ default:
+ warnmsg(LOG_NOTICE, __func__,
+ "%s is an invalid file type 0x%o\n",
+ path, (s.st_mode & S_IFMT));
+ return (-1);
+ }
+
+ return (0);
+}
+
+/* Decode domain name label encoding in RFC 1035 Section 3.1 */
+static size_t
+dname_labeldec(char *dst, size_t dlen, const char *src)
+{
+ size_t len;
+ const char *src_origin;
+ const char *src_last;
+ const char *dst_origin;
+
+ src_origin = src;
+ src_last = strchr(src, '\0');
+ dst_origin = dst;
+ memset(dst, '\0', dlen);
+ while (src && (len = (uint8_t)(*src++) & 0x3f) &&
+ (src + len) <= src_last &&
+ (dst - dst_origin < (ssize_t)dlen)) {
+ if (dst != dst_origin)
+ *dst++ = '.';
+ warnmsg(LOG_DEBUG, __func__, "labellen = %zd", len);
+ memcpy(dst, src, len);
+ src += len;
+ dst += len;
+ }
+ *dst = '\0';
+
+ /*
+ * XXX validate that domain name only contains valid characters
+ * for two reasons: 1) correctness, 2) we do not want to pass
+ * possible malicious, unescaped characters like `` to a script
+ * or program that could be exploited that way.
+ */
+
+ return (src - src_origin);
+}
diff --git a/usr.sbin/rtsold/rtsold.8 b/usr.sbin/rtsold/rtsold.8
new file mode 100644
index 0000000..a723e9c
--- /dev/null
+++ b/usr.sbin/rtsold/rtsold.8
@@ -0,0 +1,304 @@
+.\" $KAME: rtsold.8,v 1.20 2003/04/11 12:46:12 jinmei 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 June 14, 2011
+.Dt RTSOLD 8
+.Os
+.\"
+.Sh NAME
+.Nm rtsold , rtsol
+.Nd router solicitation daemon
+.\"
+.Sh SYNOPSIS
+.Nm
+.Op Fl dDfFmu1
+.Op Fl O Ar script-name
+.Op Fl p Ar pidfile
+.Op Fl R Ar script-name
+.Ar interface ...
+.Nm
+.Op Fl dDfFmu1
+.Op Fl O Ar script-name
+.Op Fl p Ar pidfile
+.Op Fl R Ar script-name
+.Fl a
+.Nm rtsol
+.Op Fl dDu
+.Op Fl O Ar script-name
+.Op Fl R Ar script-name
+.Ar interface ...
+.Nm rtsol
+.Op Fl dDu
+.Op Fl O Ar script-name
+.Op Fl R Ar script-name
+.Fl a
+.\"
+.Sh DESCRIPTION
+.Nm
+is the daemon program to send ICMPv6 Router Solicitation messages
+on the specified interfaces.
+If a node (re)attaches to a link,
+.Nm
+sends some Router Solicitations on the link destined to the link-local scope
+all-routers multicast address to discover new routers
+and to get non link-local addresses.
+.Pp
+.Nm
+should be used on IPv6 hosts
+.Pq non-router nodes
+only.
+.Pp
+If you invoke the program as
+.Nm rtsol ,
+it will transmit probes from the specified
+.Ar interface ,
+without becoming a daemon.
+In other words,
+.Nm rtsol
+behaves as
+.Do
+.Nm
+.Fl f1
+.Ar interfaces
+.Dc .
+.Pp
+Specifically,
+.Nm
+sends at most 3 Router Solicitations on an interface
+after one of the following events:
+.Pp
+.Bl -bullet -compact
+.It
+Just after invocation of
+.Nm
+daemon.
+.It
+The interface is up after a temporary interface failure.
+.Nm
+detects such failures by periodically probing to see if the status
+of the interface is active or not.
+Note that some network cards and drivers do not allow the extraction
+of link state.
+In such cases,
+.Nm
+cannot detect the change of the interface status.
+.It
+Every 60 seconds if the
+.Fl m
+option is specified and the
+.Nm
+daemon cannot get the interface status.
+This feature does not conform to the IPv6 neighbor discovery
+specification, but is provided for mobile stations.
+The default interval for router advertisements, which is on the order of 10
+minutes, is slightly long for mobile stations.
+This feature is provided
+for such stations so that they can find new routers as soon as possible
+when they attach to another link.
+.El
+.Lp
+Once
+.Nm
+has sent a Router Solicitation, and has received a valid Router Advertisement,
+it refrains from sending additional solicitations on that interface, until
+the next time one of the above events occurs.
+.Lp
+When sending a Router Solicitation on an interface,
+.Nm
+includes a Source Link-layer address option if the interface
+has a link-layer address.
+.Lp
+.Nm
+manages a per-interface parameter to detect if a separate protocol is
+needed for configuration parameters other than host's addresses.
+At the invocation time, the flag is FALSE, and becomes TRUE when
+the daemon receives a router advertisement with the OtherConfig flag
+being set.
+A script file can be specified to deal with the case
+.Pq see below .
+When
+.Nm
+start resending router solicitation messages by one of the conditions
+events,
+the daemon resets the parameter because the event may indicate a
+change on the attached link.
+.Pp
+Upon receipt of signal
+.Dv SIGUSR1 ,
+.Nm
+will dump the current internal state into
+.Pa /var/run/rtsold.dump .
+.\"
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl a
+Autoprobe outgoing interfaces.
+.Nm
+will try to find any non-loopback, non-point-to-point, IPv6-capable interfaces
+and send router solicitation messages on all of them.
+.It Fl d
+Enable debugging.
+.It Fl D
+Enable more debugging including the printing of internal timer information.
+.It Fl f
+Prevent
+.Nm
+from becoming a daemon (foreground mode).
+Warning messages are generated to standard error
+instead of
+.Xr syslog 3 .
+.It Fl F
+Explicitly configure the kernel to accept Router Advertisements and
+disable IPv6 forwarding.
+These settings are required for proper
+.Nm
+operation.
+Without this option, the current settings will be obeyed;
+if they are incompatible with proper operation,
+warning messages will be generated,
+but Router Solicitations will still be sent.
+The settings may be changed manually with
+.Xr sysctl 8
+and
+.Xr ifconfig 8 .
+.It Fl m
+Enable mobility support.
+If this option is specified,
+.Nm
+sends probing packets to default routers that have advertised Router
+Advertisements
+when the node (re)attaches to an interface.
+Moreover, if the option is specified,
+.Nm
+periodically sends Router Solicitation on an interface that does not support
+.Dv SIOCGIFMEDIA
+ioctl.
+.It Fl 1
+Perform only one probe.
+Transmit Router Solicitation packets until at least one valid Router
+Advertisement packet has arrived on each
+.Ar interface ,
+then exit.
+.It Fl O Ar script-name
+Specifies a supplement script file to handle the Other Configuration
+flag of the router advertisement.
+When the flag changes from FALSE to TRUE,
+.Nm
+will invoke
+.Ar script-name
+with a single argument of the receiving interface name,
+expecting the script will then start a protocol for the other
+configuration.
+.Ar script-name
+must be the absolute path from root to the script file, be a regular
+file, and be created by the same owner who runs
+.Nm .
+.It Fl p Ar pidfile
+Writes the process ID of
+.Nm
+to
+.Pa pidfile
+instead of the default PID file
+.Pa /var/run/rtsold.pid .
+.It Fl R Ar script-name
+Specifies a script to run when router advertisement options
+.Dv RDNSS Pq Recursive DNS Server
+or
+.Dv DNSSL Pq DNS Search List
+are encountered.
+The information of DNS servers and DNS search domains will be sent to
+standard input of this script.
+The
+.Xr resolvconf 8
+script is used by default.
+.It Fl u
+Specifies whether adding the source address of Router Advertisement
+messages to the interface name in an argument of the RDNSS and DNSSL
+script.
+.Pp
+If
+.Fl u
+is specified, the interface name in the script argument will be
+.Ql ifname:slaac:[RA-source-address] .
+.Pp
+If not, it will be
+.Ql ifname:slaac .
+.El
+.Sh FILES
+.Bl -tag -width /var/run/rtsold.dump -compact
+.It Pa /var/run/rtsold.pid
+The PID of the currently running
+.Nm .
+.It Pa /var/run/rtsold.dump
+Internal state dump file.
+.El
+.\"
+.Sh EXIT STATUS
+.Ex -std
+.\"
+.Sh SEE ALSO
+.Xr resolvconf 8 ,
+.Xr rtadvd 8 ,
+.Xr sysctl 8
+.\"
+.Sh HISTORY
+The
+.Nm
+command is based on the
+.Nm rtsol
+command, which first appeared in WIDE/KAME IPv6 protocol stack kit.
+.Nm rtsol
+is now integrated into
+.Xr rtsold 8 .
+.\"
+.Sh BUGS
+In some operating systems, when a PCMCIA network card is removed
+and reinserted, the corresponding interface index is changed.
+However,
+.Nm
+assumes such changes will not occur, and always uses the index that
+it got at invocation.
+As a result,
+.Nm
+may not work if you reinsert a network card.
+In such a case,
+.Nm
+should be killed and restarted.
+.Pp
+The IPv6 autoconfiguration specification assumes a single-interface host.
+You may see kernel error messages if you try to autoconfigure a host with
+multiple interfaces.
+Also, it seems contradictory for
+.Nm
+to accept multiple
+.Ar interface
+arguments.
diff --git a/usr.sbin/rtsold/rtsold.c b/usr.sbin/rtsold/rtsold.c
new file mode 100644
index 0000000..1482798
--- /dev/null
+++ b/usr.sbin/rtsold/rtsold.c
@@ -0,0 +1,924 @@
+/* $KAME: rtsold.c,v 1.67 2003/05/17 18:16:15 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$
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/param.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+
+#include <netinet/in.h>
+#include <netinet/icmp6.h>
+#include <netinet/in_var.h>
+#include <arpa/inet.h>
+
+#include <netinet6/nd6.h>
+
+#include <signal.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <errno.h>
+#include <err.h>
+#include <stdarg.h>
+#include <ifaddrs.h>
+#include <poll.h>
+
+#include "rtsold.h"
+
+#define RTSOL_DUMPFILE "/var/run/rtsold.dump";
+#define RTSOL_PIDFILE "/var/run/rtsold.pid";
+
+struct timespec tm_max;
+static int log_upto = 999;
+static int fflag = 0;
+
+int Fflag = 0; /* force setting sysctl parameters */
+int aflag = 0;
+int dflag = 0;
+int uflag = 0;
+
+const char *otherconf_script;
+const char *resolvconf_script = "/sbin/resolvconf";
+
+/* protocol constants */
+#define MAX_RTR_SOLICITATION_DELAY 1 /* second */
+#define RTR_SOLICITATION_INTERVAL 4 /* seconds */
+#define MAX_RTR_SOLICITATIONS 3 /* times */
+
+/*
+ * implementation dependent constants in seconds
+ * XXX: should be configurable
+ */
+#define PROBE_INTERVAL 60
+
+/* static variables and functions */
+static int mobile_node = 0;
+static const char *pidfilename = RTSOL_PIDFILE;
+
+#ifndef SMALL
+static int do_dump;
+static const char *dumpfilename = RTSOL_DUMPFILE;
+#endif
+
+#if 0
+static int ifreconfig(char *);
+#endif
+
+static int make_packet(struct ifinfo *);
+static struct timespec *rtsol_check_timer(void);
+
+#ifndef SMALL
+static void rtsold_set_dump_file(int);
+#endif
+static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ int s, ch, once = 0;
+ struct timespec *timeout;
+ const char *opts;
+ struct pollfd set[2];
+ int rtsock;
+ char *argv0;
+
+#ifndef SMALL
+ /* rtsold */
+ opts = "adDfFm1O:p:R:u";
+#else
+ /* rtsol */
+ opts = "adDFO:R:u";
+ fflag = 1;
+ once = 1;
+#endif
+ argv0 = argv[0];
+
+ while ((ch = getopt(argc, argv, opts)) != -1) {
+ switch (ch) {
+ case 'a':
+ aflag = 1;
+ break;
+ case 'd':
+ dflag += 1;
+ break;
+ case 'D':
+ dflag += 2;
+ break;
+ case 'f':
+ fflag = 1;
+ break;
+ case 'F':
+ Fflag = 1;
+ break;
+ case 'm':
+ mobile_node = 1;
+ break;
+ case '1':
+ once = 1;
+ break;
+ case 'O':
+ otherconf_script = optarg;
+ break;
+ case 'p':
+ pidfilename = optarg;
+ break;
+ case 'R':
+ resolvconf_script = optarg;
+ break;
+ case 'u':
+ uflag = 1;
+ break;
+ default:
+ usage();
+ exit(1);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if ((!aflag && argc == 0) || (aflag && argc != 0)) {
+ usage();
+ exit(1);
+ }
+
+ /* Generate maximum time in timespec. */
+ tm_max.tv_sec = (-1) & ~((time_t)1 << ((sizeof(tm_max.tv_sec) * 8) - 1));
+ tm_max.tv_nsec = (-1) & ~((long)1 << ((sizeof(tm_max.tv_nsec) * 8) - 1));
+
+ /* set log level */
+ if (dflag > 1)
+ log_upto = LOG_DEBUG;
+ else if (dflag > 0)
+ log_upto = LOG_INFO;
+ else
+ log_upto = LOG_NOTICE;
+
+ if (!fflag) {
+ char *ident;
+
+ ident = strrchr(argv0, '/');
+ if (!ident)
+ ident = argv0;
+ else
+ ident++;
+ openlog(ident, LOG_NDELAY|LOG_PID, LOG_DAEMON);
+ if (log_upto >= 0)
+ setlogmask(LOG_UPTO(log_upto));
+ }
+
+ if (otherconf_script && *otherconf_script != '/') {
+ errx(1, "configuration script (%s) must be an absolute path",
+ otherconf_script);
+ }
+ if (resolvconf_script && *resolvconf_script != '/') {
+ errx(1, "configuration script (%s) must be an absolute path",
+ resolvconf_script);
+ }
+ if (pidfilename && *pidfilename != '/') {
+ errx(1, "pid filename (%s) must be an absolute path",
+ pidfilename);
+ }
+
+#if (__FreeBSD_version < 900000)
+ if (Fflag) {
+ setinet6sysctl(IPV6CTL_FORWARDING, 0);
+ } else {
+ /* warn if forwarding is up */
+ if (getinet6sysctl(IPV6CTL_FORWARDING))
+ warnx("kernel is configured as a router, not a host");
+ }
+#endif
+
+#ifndef SMALL
+ /* initialization to dump internal status to a file */
+ signal(SIGUSR1, rtsold_set_dump_file);
+#endif
+
+ if (!fflag)
+ daemon(0, 0); /* act as a daemon */
+
+ /*
+ * Open a socket for sending RS and receiving RA.
+ * This should be done before calling ifinit(), since the function
+ * uses the socket.
+ */
+ if ((s = sockopen()) < 0) {
+ warnmsg(LOG_ERR, __func__, "failed to open a socket");
+ exit(1);
+ }
+ set[0].fd = s;
+ set[0].events = POLLIN;
+ set[1].fd = -1;
+
+ if ((rtsock = rtsock_open()) < 0) {
+ warnmsg(LOG_ERR, __func__, "failed to open a socket");
+ exit(1);
+ }
+ set[1].fd = rtsock;
+ set[1].events = POLLIN;
+
+ /* configuration per interface */
+ if (ifinit()) {
+ warnmsg(LOG_ERR, __func__,
+ "failed to initialize interfaces");
+ exit(1);
+ }
+ if (aflag)
+ argv = autoifprobe();
+ while (argv && *argv) {
+ if (ifconfig(*argv)) {
+ warnmsg(LOG_ERR, __func__,
+ "failed to initialize %s", *argv);
+ exit(1);
+ }
+ argv++;
+ }
+
+ /* setup for probing default routers */
+ if (probe_init()) {
+ warnmsg(LOG_ERR, __func__,
+ "failed to setup for probing routers");
+ exit(1);
+ /*NOTREACHED*/
+ }
+
+ /* dump the current pid */
+ if (!once) {
+ pid_t pid = getpid();
+ FILE *fp;
+
+ if ((fp = fopen(pidfilename, "w")) == NULL)
+ warnmsg(LOG_ERR, __func__,
+ "failed to open a pid log file(%s): %s",
+ pidfilename, strerror(errno));
+ else {
+ fprintf(fp, "%d\n", pid);
+ fclose(fp);
+ }
+ }
+ while (1) { /* main loop */
+ int e;
+#ifndef SMALL
+ if (do_dump) { /* SIGUSR1 */
+ do_dump = 0;
+ rtsold_dump_file(dumpfilename);
+ }
+#endif
+
+ timeout = rtsol_check_timer();
+
+ if (once) {
+ struct ifinfo *ifi;
+
+ /* if we have no timeout, we are done (or failed) */
+ if (timeout == NULL)
+ break;
+
+ /* if all interfaces have got RA packet, we are done */
+ TAILQ_FOREACH(ifi, &ifinfo_head, ifi_next) {
+ if (ifi->state != IFS_DOWN && ifi->racnt == 0)
+ break;
+ }
+ if (ifi == NULL)
+ break;
+ }
+ e = poll(set, 2, timeout ? (timeout->tv_sec * 1000 + timeout->tv_nsec / 1000 / 1000) : INFTIM);
+ if (e < 1) {
+ if (e < 0 && errno != EINTR) {
+ warnmsg(LOG_ERR, __func__, "select: %s",
+ strerror(errno));
+ }
+ continue;
+ }
+
+ /* packet reception */
+ if (set[1].revents & POLLIN)
+ rtsock_input(rtsock);
+ if (set[0].revents & POLLIN)
+ rtsol_input(s);
+ }
+ /* NOTREACHED */
+
+ return (0);
+}
+
+int
+ifconfig(char *ifname)
+{
+ struct ifinfo *ifi;
+ struct sockaddr_dl *sdl;
+ int flags;
+
+ if ((sdl = if_nametosdl(ifname)) == NULL) {
+ warnmsg(LOG_ERR, __func__,
+ "failed to get link layer information for %s", ifname);
+ return (-1);
+ }
+ if (find_ifinfo(sdl->sdl_index)) {
+ warnmsg(LOG_ERR, __func__,
+ "interface %s was already configured", ifname);
+ free(sdl);
+ return (-1);
+ }
+
+ if (Fflag) {
+ struct in6_ndireq nd;
+ int s;
+
+ if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
+ warnmsg(LOG_ERR, __func__, "socket() failed.");
+ return (-1);
+ }
+ memset(&nd, 0, sizeof(nd));
+ strlcpy(nd.ifname, ifname, sizeof(nd.ifname));
+ if (ioctl(s, SIOCGIFINFO_IN6, (caddr_t)&nd) < 0) {
+ warnmsg(LOG_ERR, __func__,
+ "cannot get accept_rtadv flag");
+ close(s);
+ return (-1);
+ }
+ nd.ndi.flags |= ND6_IFF_ACCEPT_RTADV;
+ if (ioctl(s, SIOCSIFINFO_IN6, (caddr_t)&nd) < 0) {
+ warnmsg(LOG_ERR, __func__,
+ "cannot set accept_rtadv flag");
+ close(s);
+ return (-1);
+ }
+ close(s);
+ }
+
+ if ((ifi = malloc(sizeof(*ifi))) == NULL) {
+ warnmsg(LOG_ERR, __func__, "memory allocation failed");
+ free(sdl);
+ return (-1);
+ }
+ memset(ifi, 0, sizeof(*ifi));
+ ifi->sdl = sdl;
+ ifi->ifi_rdnss = IFI_DNSOPT_STATE_NOINFO;
+ ifi->ifi_dnssl = IFI_DNSOPT_STATE_NOINFO;
+ TAILQ_INIT(&ifi->ifi_rainfo);
+ strlcpy(ifi->ifname, ifname, sizeof(ifi->ifname));
+
+ /* construct a router solicitation message */
+ if (make_packet(ifi))
+ goto bad;
+
+ /* set link ID of this interface. */
+#ifdef HAVE_SCOPELIB
+ if (inet_zoneid(AF_INET6, 2, ifname, &ifi->linkid))
+ goto bad;
+#else
+ /* XXX: assume interface IDs as link IDs */
+ ifi->linkid = ifi->sdl->sdl_index;
+#endif
+
+ /*
+ * check if the interface is available.
+ * also check if SIOCGIFMEDIA ioctl is OK on the interface.
+ */
+ ifi->mediareqok = 1;
+ ifi->active = interface_status(ifi);
+ if (!ifi->mediareqok) {
+ /*
+ * probe routers periodically even if the link status
+ * does not change.
+ */
+ ifi->probeinterval = PROBE_INTERVAL;
+ }
+
+ /* activate interface: interface_up returns 0 on success */
+ flags = interface_up(ifi->ifname);
+ if (flags == 0)
+ ifi->state = IFS_DELAY;
+ else if (flags == IFS_TENTATIVE)
+ ifi->state = IFS_TENTATIVE;
+ else
+ ifi->state = IFS_DOWN;
+
+ rtsol_timer_update(ifi);
+
+ TAILQ_INSERT_TAIL(&ifinfo_head, ifi, ifi_next);
+ return (0);
+
+bad:
+ free(ifi->sdl);
+ free(ifi);
+ return (-1);
+}
+
+void
+iflist_init(void)
+{
+ struct ifinfo *ifi;
+
+ while ((ifi = TAILQ_FIRST(&ifinfo_head)) != NULL) {
+ TAILQ_REMOVE(&ifinfo_head, ifi, ifi_next);
+ if (ifi->sdl != NULL)
+ free(ifi->sdl);
+ if (ifi->rs_data != NULL)
+ free(ifi->rs_data);
+ free(ifi);
+ }
+}
+
+#if 0
+static int
+ifreconfig(char *ifname)
+{
+ struct ifinfo *ifi, *prev;
+ int rv;
+
+ prev = NULL;
+ TAILQ_FOREACH(ifi, &ifinfo_head, ifi_next) {
+ if (strncmp(ifi->ifname, ifname, sizeof(ifi->ifname)) == 0)
+ break;
+ prev = ifi;
+ }
+ prev->next = ifi->next;
+
+ rv = ifconfig(ifname);
+
+ /* reclaim it after ifconfig() in case ifname is pointer inside ifi */
+ if (ifi->rs_data)
+ free(ifi->rs_data);
+ free(ifi->sdl);
+ free(ifi);
+
+ return (rv);
+}
+#endif
+
+struct rainfo *
+find_rainfo(struct ifinfo *ifi, struct sockaddr_in6 *sin6)
+{
+ struct rainfo *rai;
+
+ TAILQ_FOREACH(rai, &ifi->ifi_rainfo, rai_next)
+ if (memcmp(&rai->rai_saddr.sin6_addr, &sin6->sin6_addr,
+ sizeof(rai->rai_saddr.sin6_addr)) == 0)
+ return (rai);
+
+ return (NULL);
+}
+
+struct ifinfo *
+find_ifinfo(int ifindex)
+{
+ struct ifinfo *ifi;
+
+ TAILQ_FOREACH(ifi, &ifinfo_head, ifi_next) {
+ if (ifi->sdl->sdl_index == ifindex)
+ return (ifi);
+ }
+ return (NULL);
+}
+
+static int
+make_packet(struct ifinfo *ifi)
+{
+ size_t packlen = sizeof(struct nd_router_solicit), lladdroptlen = 0;
+ struct nd_router_solicit *rs;
+ char *buf;
+
+ if ((lladdroptlen = lladdropt_length(ifi->sdl)) == 0) {
+ warnmsg(LOG_INFO, __func__,
+ "link-layer address option has null length"
+ " on %s. Treat as not included.", ifi->ifname);
+ }
+ packlen += lladdroptlen;
+ ifi->rs_datalen = packlen;
+
+ /* allocate buffer */
+ if ((buf = malloc(packlen)) == NULL) {
+ warnmsg(LOG_ERR, __func__,
+ "memory allocation failed for %s", ifi->ifname);
+ return (-1);
+ }
+ ifi->rs_data = buf;
+
+ /* fill in the message */
+ rs = (struct nd_router_solicit *)buf;
+ rs->nd_rs_type = ND_ROUTER_SOLICIT;
+ rs->nd_rs_code = 0;
+ rs->nd_rs_cksum = 0;
+ rs->nd_rs_reserved = 0;
+ buf += sizeof(*rs);
+
+ /* fill in source link-layer address option */
+ if (lladdroptlen)
+ lladdropt_fill(ifi->sdl, (struct nd_opt_hdr *)buf);
+
+ return (0);
+}
+
+static struct timespec *
+rtsol_check_timer(void)
+{
+ static struct timespec returnval;
+ struct timespec now, rtsol_timer;
+ struct ifinfo *ifi;
+ struct rainfo *rai;
+ struct ra_opt *rao;
+ int flags;
+
+ clock_gettime(CLOCK_MONOTONIC_FAST, &now);
+
+ rtsol_timer = tm_max;
+
+ TAILQ_FOREACH(ifi, &ifinfo_head, ifi_next) {
+ if (TS_CMP(&ifi->expire, &now, <=)) {
+ warnmsg(LOG_DEBUG, __func__, "timer expiration on %s, "
+ "state = %d", ifi->ifname, ifi->state);
+
+ while((rai = TAILQ_FIRST(&ifi->ifi_rainfo)) != NULL) {
+ /* Remove all RA options. */
+ TAILQ_REMOVE(&ifi->ifi_rainfo, rai, rai_next);
+ while ((rao = TAILQ_FIRST(&rai->rai_ra_opt)) !=
+ NULL) {
+ TAILQ_REMOVE(&rai->rai_ra_opt, rao,
+ rao_next);
+ if (rao->rao_msg != NULL)
+ free(rao->rao_msg);
+ free(rao);
+ }
+ free(rai);
+ }
+ switch (ifi->state) {
+ case IFS_DOWN:
+ case IFS_TENTATIVE:
+ /* interface_up returns 0 on success */
+ flags = interface_up(ifi->ifname);
+ if (flags == 0)
+ ifi->state = IFS_DELAY;
+ else if (flags == IFS_TENTATIVE)
+ ifi->state = IFS_TENTATIVE;
+ else
+ ifi->state = IFS_DOWN;
+ break;
+ case IFS_IDLE:
+ {
+ int oldstatus = ifi->active;
+ int probe = 0;
+
+ ifi->active = interface_status(ifi);
+
+ if (oldstatus != ifi->active) {
+ warnmsg(LOG_DEBUG, __func__,
+ "%s status is changed"
+ " from %d to %d",
+ ifi->ifname,
+ oldstatus, ifi->active);
+ probe = 1;
+ ifi->state = IFS_DELAY;
+ } else if (ifi->probeinterval &&
+ (ifi->probetimer -=
+ ifi->timer.tv_sec) <= 0) {
+ /* probe timer expired */
+ ifi->probetimer =
+ ifi->probeinterval;
+ probe = 1;
+ ifi->state = IFS_PROBE;
+ }
+
+ /*
+ * If we need a probe, clear the previous
+ * status wrt the "other" configuration.
+ */
+ if (probe)
+ ifi->otherconfig = 0;
+
+ if (probe && mobile_node)
+ defrouter_probe(ifi);
+ break;
+ }
+ case IFS_DELAY:
+ ifi->state = IFS_PROBE;
+ sendpacket(ifi);
+ break;
+ case IFS_PROBE:
+ if (ifi->probes < MAX_RTR_SOLICITATIONS)
+ sendpacket(ifi);
+ else {
+ warnmsg(LOG_INFO, __func__,
+ "No answer after sending %d RSs",
+ ifi->probes);
+ ifi->probes = 0;
+ ifi->state = IFS_IDLE;
+ }
+ break;
+ }
+ rtsol_timer_update(ifi);
+ } else {
+ /* Expiration check for RA options. */
+ int expire = 0;
+
+ TAILQ_FOREACH(rai, &ifi->ifi_rainfo, rai_next) {
+ TAILQ_FOREACH(rao, &rai->rai_ra_opt, rao_next) {
+ warnmsg(LOG_DEBUG, __func__,
+ "RA expiration timer: "
+ "type=%d, msg=%s, expire=%s",
+ rao->rao_type, (char *)rao->rao_msg,
+ sec2str(&rao->rao_expire));
+ if (TS_CMP(&now, &rao->rao_expire,
+ >=)) {
+ warnmsg(LOG_DEBUG, __func__,
+ "RA expiration timer: "
+ "expired.");
+ TAILQ_REMOVE(&rai->rai_ra_opt,
+ rao, rao_next);
+ if (rao->rao_msg != NULL)
+ free(rao->rao_msg);
+ free(rao);
+ expire = 1;
+ }
+ }
+ }
+ if (expire)
+ ra_opt_handler(ifi);
+ }
+ if (TS_CMP(&ifi->expire, &rtsol_timer, <))
+ rtsol_timer = ifi->expire;
+ }
+
+ if (TS_CMP(&rtsol_timer, &tm_max, ==)) {
+ warnmsg(LOG_DEBUG, __func__, "there is no timer");
+ return (NULL);
+ } else if (TS_CMP(&rtsol_timer, &now, <))
+ /* this may occur when the interval is too small */
+ returnval.tv_sec = returnval.tv_nsec = 0;
+ else
+ TS_SUB(&rtsol_timer, &now, &returnval);
+
+ now.tv_sec += returnval.tv_sec;
+ now.tv_nsec += returnval.tv_nsec;
+ warnmsg(LOG_DEBUG, __func__, "New timer is %s",
+ sec2str(&now));
+
+ return (&returnval);
+}
+
+void
+rtsol_timer_update(struct ifinfo *ifi)
+{
+#define MILLION 1000000
+#define DADRETRY 10 /* XXX: adhoc */
+ long interval;
+ struct timespec now;
+
+ bzero(&ifi->timer, sizeof(ifi->timer));
+
+ switch (ifi->state) {
+ case IFS_DOWN:
+ case IFS_TENTATIVE:
+ if (++ifi->dadcount > DADRETRY) {
+ ifi->dadcount = 0;
+ ifi->timer.tv_sec = PROBE_INTERVAL;
+ } else
+ ifi->timer.tv_sec = 1;
+ break;
+ case IFS_IDLE:
+ if (mobile_node) {
+ /* XXX should be configurable */
+ ifi->timer.tv_sec = 3;
+ }
+ else
+ ifi->timer = tm_max; /* stop timer(valid?) */
+ break;
+ case IFS_DELAY:
+ interval = arc4random_uniform(MAX_RTR_SOLICITATION_DELAY * MILLION);
+ ifi->timer.tv_sec = interval / MILLION;
+ ifi->timer.tv_nsec = (interval % MILLION) * 1000;
+ break;
+ case IFS_PROBE:
+ if (ifi->probes < MAX_RTR_SOLICITATIONS)
+ ifi->timer.tv_sec = RTR_SOLICITATION_INTERVAL;
+ else {
+ /*
+ * After sending MAX_RTR_SOLICITATIONS solicitations,
+ * we're just waiting for possible replies; there
+ * will be no more solicitation. Thus, we change
+ * the timer value to MAX_RTR_SOLICITATION_DELAY based
+ * on RFC 2461, Section 6.3.7.
+ */
+ ifi->timer.tv_sec = MAX_RTR_SOLICITATION_DELAY;
+ }
+ break;
+ default:
+ warnmsg(LOG_ERR, __func__,
+ "illegal interface state(%d) on %s",
+ ifi->state, ifi->ifname);
+ return;
+ }
+
+ /* reset the timer */
+ if (TS_CMP(&ifi->timer, &tm_max, ==)) {
+ ifi->expire = tm_max;
+ warnmsg(LOG_DEBUG, __func__,
+ "stop timer for %s", ifi->ifname);
+ } else {
+ clock_gettime(CLOCK_MONOTONIC_FAST, &now);
+ TS_ADD(&now, &ifi->timer, &ifi->expire);
+
+ now.tv_sec += ifi->timer.tv_sec;
+ now.tv_nsec += ifi->timer.tv_nsec;
+ warnmsg(LOG_DEBUG, __func__, "set timer for %s to %s",
+ ifi->ifname, sec2str(&now));
+ }
+
+#undef MILLION
+}
+
+/* timer related utility functions */
+#define MILLION 1000000
+
+#ifndef SMALL
+static void
+rtsold_set_dump_file(int sig __unused)
+{
+ do_dump = 1;
+}
+#endif
+
+static void
+usage(void)
+{
+#ifndef SMALL
+ fprintf(stderr, "usage: rtsold [-dDfFm1] [-O script-name] "
+ "[-p pidfile] [-R script-name] interface ...\n");
+ fprintf(stderr, "usage: rtsold [-dDfFm1] [-O script-name] "
+ "[-p pidfile] [-R script-name] -a\n");
+#else
+ fprintf(stderr, "usage: rtsol [-dDF] [-O script-name] "
+ "[-p pidfile] [-R script-name] interface ...\n");
+ fprintf(stderr, "usage: rtsol [-dDF] [-O script-name] "
+ "[-p pidfile] [-R script-name] -a\n");
+#endif
+}
+
+void
+warnmsg(int priority, const char *func, const char *msg, ...)
+{
+ va_list ap;
+ char buf[BUFSIZ];
+
+ va_start(ap, msg);
+ if (fflag) {
+ if (priority <= log_upto) {
+ (void)vfprintf(stderr, msg, ap);
+ (void)fprintf(stderr, "\n");
+ }
+ } else {
+ snprintf(buf, sizeof(buf), "<%s> %s", func, msg);
+ msg = buf;
+ vsyslog(priority, msg, ap);
+ }
+ va_end(ap);
+}
+
+/*
+ * return a list of interfaces which is suitable to sending an RS.
+ */
+char **
+autoifprobe(void)
+{
+ static char **argv = NULL;
+ static int n = 0;
+ char **a;
+ int s = 0, i, found;
+ struct ifaddrs *ifap, *ifa;
+ struct in6_ndireq nd;
+
+ /* initialize */
+ while (n--)
+ free(argv[n]);
+ if (argv) {
+ free(argv);
+ argv = NULL;
+ }
+ n = 0;
+
+ if (getifaddrs(&ifap) != 0)
+ return (NULL);
+
+ if (!Fflag && (s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
+ warnmsg(LOG_ERR, __func__, "socket");
+ exit(1);
+ }
+
+ /* find an ethernet */
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ if ((ifa->ifa_flags & IFF_UP) == 0)
+ continue;
+ if ((ifa->ifa_flags & IFF_POINTOPOINT) != 0)
+ continue;
+ if ((ifa->ifa_flags & IFF_LOOPBACK) != 0)
+ continue;
+ if ((ifa->ifa_flags & IFF_MULTICAST) == 0)
+ continue;
+
+ if (ifa->ifa_addr->sa_family != AF_INET6)
+ continue;
+
+ found = 0;
+ for (i = 0; i < n; i++) {
+ if (strcmp(argv[i], ifa->ifa_name) == 0) {
+ found++;
+ break;
+ }
+ }
+ if (found)
+ continue;
+
+ /*
+ * Skip the interfaces which IPv6 and/or accepting RA
+ * is disabled.
+ */
+ if (!Fflag) {
+ memset(&nd, 0, sizeof(nd));
+ strlcpy(nd.ifname, ifa->ifa_name, sizeof(nd.ifname));
+ if (ioctl(s, SIOCGIFINFO_IN6, (caddr_t)&nd) < 0) {
+ warnmsg(LOG_ERR, __func__,
+ "ioctl(SIOCGIFINFO_IN6)");
+ exit(1);
+ }
+ if ((nd.ndi.flags & ND6_IFF_IFDISABLED))
+ continue;
+ if (!(nd.ndi.flags & ND6_IFF_ACCEPT_RTADV))
+ continue;
+ }
+
+ /* if we find multiple candidates, just warn. */
+ if (n != 0 && dflag > 1)
+ warnmsg(LOG_WARNING, __func__,
+ "multiple interfaces found");
+
+ a = realloc(argv, (n + 1) * sizeof(char *));
+ if (a == NULL) {
+ warnmsg(LOG_ERR, __func__, "realloc");
+ exit(1);
+ }
+ argv = a;
+ argv[n] = strdup(ifa->ifa_name);
+ if (!argv[n]) {
+ warnmsg(LOG_ERR, __func__, "malloc");
+ exit(1);
+ }
+ n++;
+ }
+
+ if (n) {
+ a = realloc(argv, (n + 1) * sizeof(char *));
+ if (a == NULL) {
+ warnmsg(LOG_ERR, __func__, "realloc");
+ exit(1);
+ }
+ argv = a;
+ argv[n] = NULL;
+
+ if (dflag > 0) {
+ for (i = 0; i < n; i++)
+ warnmsg(LOG_WARNING, __func__, "probing %s",
+ argv[i]);
+ }
+ }
+ if (!Fflag)
+ close(s);
+ freeifaddrs(ifap);
+ return (argv);
+}
diff --git a/usr.sbin/rtsold/rtsold.h b/usr.sbin/rtsold/rtsold.h
new file mode 100644
index 0000000..f19fb3c
--- /dev/null
+++ b/usr.sbin/rtsold/rtsold.h
@@ -0,0 +1,194 @@
+/* $KAME: rtsold.h,v 1.19 2003/04/16 09:48:15 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$
+ */
+
+struct script_msg {
+ TAILQ_ENTRY(script_msg) sm_next;
+
+ char *sm_msg;
+};
+
+TAILQ_HEAD(script_msg_head_t, script_msg);
+
+struct ra_opt {
+ TAILQ_ENTRY(ra_opt) rao_next;
+
+ u_int8_t rao_type;
+ struct timespec rao_expire;
+ size_t rao_len;
+ void *rao_msg;
+};
+
+TAILQ_HEAD(rainfo_head, ra_opt);
+
+struct rainfo {
+ TAILQ_ENTRY(rainfo) rai_next;
+
+ struct ifinfo *rai_ifinfo;
+ struct sockaddr_in6 rai_saddr;
+ TAILQ_HEAD(, ra_opt) rai_ra_opt;
+};
+
+struct ifinfo {
+ TAILQ_ENTRY(ifinfo) ifi_next; /* pointer to the next interface */
+
+ struct sockaddr_dl *sdl; /* link-layer address */
+ char ifname[IFNAMSIZ]; /* interface name */
+ u_int32_t linkid; /* link ID of this interface */
+ int active; /* interface status */
+ int probeinterval; /* interval of probe timer (if necessary) */
+ int probetimer; /* rest of probe timer */
+ int mediareqok; /* whether the IF supports SIOCGIFMEDIA */
+ int otherconfig; /* need a separate protocol for the "other"
+ * configuration */
+ int state;
+ int probes;
+ int dadcount;
+ struct timespec timer;
+ struct timespec expire;
+ int errors; /* # of errors we've got - detect wedge */
+#define IFI_DNSOPT_STATE_NOINFO 0
+#define IFI_DNSOPT_STATE_RECEIVED 1
+ int ifi_rdnss; /* RDNSS option state */
+ int ifi_dnssl; /* DNSSL option state */
+
+ int racnt; /* total # of valid RAs it have got */
+ TAILQ_HEAD(, rainfo) ifi_rainfo;
+
+ size_t rs_datalen;
+ u_char *rs_data;
+};
+
+/* per interface status */
+#define IFS_IDLE 0
+#define IFS_DELAY 1
+#define IFS_PROBE 2
+#define IFS_DOWN 3
+#define IFS_TENTATIVE 4
+
+/* Interface list */
+extern TAILQ_HEAD(ifinfo_head_t, ifinfo) ifinfo_head;
+
+#define DNSINFO_ORIGIN_LABEL "slaac"
+/*
+ * RFC 3542 API deprecates IPV6_PKTINFO in favor of
+ * IPV6_RECVPKTINFO
+ */
+#ifndef IPV6_RECVPKTINFO
+#ifdef IPV6_PKTINFO
+#define IPV6_RECVPKTINFO IPV6_PKTINFO
+#endif
+#endif
+/*
+ * RFC 3542 API deprecates IPV6_HOPLIMIT in favor of
+ * IPV6_RECVHOPLIMIT
+ */
+#ifndef IPV6_RECVHOPLIMIT
+#ifdef IPV6_HOPLIMIT
+#define IPV6_RECVHOPLIMIT IPV6_HOPLIMIT
+#endif
+#endif
+
+#ifndef IN6ADDR_LINKLOCAL_ALLROUTERS_INIT
+#define IN6ADDR_LINKLOCAL_ALLROUTERS_INIT \
+ {{{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }}}
+#endif
+
+#define TS_CMP(tsp, usp, cmp) \
+ (((tsp)->tv_sec == (usp)->tv_sec) ? \
+ ((tsp)->tv_nsec cmp (usp)->tv_nsec) : \
+ ((tsp)->tv_sec cmp (usp)->tv_sec))
+#define TS_ADD(tsp, usp, vsp) \
+ do { \
+ (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec; \
+ (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec; \
+ if ((vsp)->tv_nsec >= 1000000000L) { \
+ (vsp)->tv_sec++; \
+ (vsp)->tv_nsec -= 1000000000L; \
+ } \
+ } while (0)
+#define TS_SUB(tsp, usp, vsp) \
+ do { \
+ (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \
+ (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \
+ if ((vsp)->tv_nsec < 0) { \
+ (vsp)->tv_sec--; \
+ (vsp)->tv_nsec += 1000000000L; \
+ } \
+ } while (0)
+
+/* rtsold.c */
+extern struct timespec tm_max;
+extern int dflag;
+extern int aflag;
+extern int Fflag;
+extern int uflag;
+extern const char *otherconf_script;
+extern const char *resolvconf_script;
+extern int ifconfig(char *);
+extern void iflist_init(void);
+struct ifinfo *find_ifinfo(int);
+struct rainfo *find_rainfo(struct ifinfo *, struct sockaddr_in6 *);
+void rtsol_timer_update(struct ifinfo *);
+extern void warnmsg(int, const char *, const char *, ...)
+ __attribute__((__format__(__printf__, 3, 4)));
+extern char **autoifprobe(void);
+extern int ra_opt_handler(struct ifinfo *);
+
+/* if.c */
+extern int ifinit(void);
+extern int interface_up(char *);
+extern int interface_status(struct ifinfo *);
+extern int lladdropt_length(struct sockaddr_dl *);
+extern void lladdropt_fill(struct sockaddr_dl *, struct nd_opt_hdr *);
+extern struct sockaddr_dl *if_nametosdl(char *);
+extern int getinet6sysctl(int);
+extern int setinet6sysctl(int, int);
+
+/* rtsol.c */
+extern int rssock;
+extern int sockopen(void);
+extern void sendpacket(struct ifinfo *);
+extern void rtsol_input(int);
+
+/* probe.c */
+extern int probe_init(void);
+extern void defrouter_probe(struct ifinfo *);
+
+/* dump.c */
+extern void rtsold_dump_file(const char *);
+extern const char *sec2str(const struct timespec *);
+
+/* rtsock.c */
+extern int rtsock_open(void);
+extern int rtsock_input(int);
diff --git a/usr.sbin/rwhod/Makefile b/usr.sbin/rwhod/Makefile
new file mode 100644
index 0000000..f1b5d99
--- /dev/null
+++ b/usr.sbin/rwhod/Makefile
@@ -0,0 +1,9 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+PROG= rwhod
+MAN= rwhod.8
+
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/rwhod/Makefile.depend b/usr.sbin/rwhod/Makefile.depend
new file mode 100644
index 0000000..e77ca8d
--- /dev/null
+++ b/usr.sbin/rwhod/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/protocols \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/rwhod/rwhod.8 b/usr.sbin/rwhod/rwhod.8
new file mode 100644
index 0000000..d4ecf4e
--- /dev/null
+++ b/usr.sbin/rwhod/rwhod.8
@@ -0,0 +1,239 @@
+.\" 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.
+.\"
+.\" @(#)rwhod.8 8.2 (Berkeley) 12/11/93
+.\" $FreeBSD$
+.\"
+.Dd January 21, 2010
+.Dt RWHOD 8
+.Os
+.Sh NAME
+.Nm rwhod
+.Nd system status server
+.Sh SYNOPSIS
+.Nm
+.Op Fl i
+.Op Fl p
+.Op Fl l
+.Op Fl m Op Ar ttl
+.Sh DESCRIPTION
+The
+.Nm
+utility is the server which maintains the database used by the
+.Xr rwho 1
+and
+.Xr ruptime 1
+programs.
+Its operation is predicated on the ability to
+.Em broadcast
+or
+.Em multicast
+messages on a network.
+.Pp
+The
+.Nm
+utility operates as both a producer and consumer of status information,
+unless the
+.Fl l
+(listen mode) option is specified, in which case
+it acts as a consumer only.
+As a producer of information it periodically
+queries the state of the system and constructs
+status messages which are broadcasted or multicasted on a network.
+As a consumer of information, it listens for other
+.Nm
+servers' status messages, validating them, then recording
+them in a collection of files located in the directory
+.Pa /var/rwho .
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl i
+Enable insecure mode, which causes
+.Nm
+to ignore the source port on incoming packets.
+.It Fl p
+Ignore all
+.Dv POINTOPOINT
+interfaces.
+This is useful if you do not wish to keep dial on demand
+interfaces permanently active.
+.It Fl l
+Enable listen mode, which causes
+.Nm
+to not broadcast any information.
+This allows you to monitor other machines'
+.Nm
+information, without broadcasting your own.
+.It Fl m Op Ar ttl
+Cause
+.Nm
+to use IP multicast (instead of
+broadcast) on all interfaces that have
+the IFF_MULTICAST flag set in their "ifnet" structs
+(excluding the loopback interface).
+The multicast
+reports are sent with a time-to-live of 1, to prevent
+forwarding beyond the directly-connected subnet(s).
+.Pp
+If the optional
+.Ar ttl
+argument is supplied with the
+.Fl m
+flag,
+.Nm
+will send IP multicast datagrams with a
+time-to-live of
+.Ar ttl ,
+via a SINGLE interface rather
+than all interfaces.
+.Ar ttl
+must be between 0 and
+32 (or MAX_MULTICAST_SCOPE).
+Note that
+.Fl m Ar 1
+is different from
+.Fl m ,
+in that
+.Fl m Ar 1
+specifies transmission on one interface only.
+.Pp
+When
+.Fl m
+is used without a
+.Ar ttl
+argument, the program accepts multicast
+.Nm
+reports from all multicast-capable interfaces.
+If a
+.Ar ttl
+argument is given, it accepts multicast reports from only one interface, the
+one on which reports are sent (which may be controlled via the host's routing
+table).
+Regardless of the
+.Fl m
+option, the program accepts broadcast or
+unicast reports from all interfaces.
+Thus, this program will hear the
+reports of old, non-multicasting
+.Nm Ns s ,
+but, if multicasting is used,
+those old
+.Nm Ns s
+will not hear the reports generated by this program.
+.El
+.Pp
+The server transmits and receives messages at the port indicated
+in the ``who'' service specification; see
+.Xr services 5 .
+The messages sent and received, are of the form:
+.Bd -literal -offset indent
+struct outmp {
+ char out_line[8]; /* tty name */
+ char out_name[8]; /* user id */
+ long out_time; /* time on */
+};
+
+struct whod {
+ char wd_vers;
+ char wd_type;
+ char wd_fill[2];
+ int wd_sendtime;
+ int wd_recvtime;
+ char wd_hostname[32];
+ int wd_loadav[3];
+ int wd_boottime;
+ struct whoent {
+ struct outmp we_utmp;
+ int we_idle;
+ } wd_we[1024 / sizeof (struct whoent)];
+};
+.Ed
+.Pp
+All fields are converted to network byte order prior to
+transmission.
+The load averages are as calculated by the
+.Xr w 1
+program, and represent load averages over the 5, 10, and 15 minute
+intervals prior to a server's transmission; they are multiplied by 100
+for representation in an integer.
+The host name
+included is that returned by the
+.Xr gethostname 3
+system call, with any trailing domain name omitted.
+The array at the end of the message contains information about
+the users logged in to the sending machine.
+This information
+includes the contents of the entry from the user accounting database
+for each non-idle terminal line and a value indicating the
+time in seconds since a character was last received on the terminal line.
+.Pp
+Messages received by the
+.Nm rwho
+server are discarded unless they originated at an
+.Nm rwho
+server's port or the
+.Fl i
+option was specified.
+In addition, if the host's name, as specified
+in the message, contains any unprintable
+.Tn ASCII
+characters, the
+message is discarded.
+Valid messages received by
+.Nm
+are placed in files named
+.Pa whod.hostname
+in the directory
+.Pa /var/rwho .
+These files contain only the most recent message, in the
+format described above.
+.Pp
+Status messages are generated approximately once every
+3 minutes.
+The
+.Nm
+utility performs an
+.Xr nlist 3
+on
+.Pa /boot/kernel/kernel
+every 30 minutes to guard against
+the possibility that this file is not the system
+image currently operating.
+.Sh SEE ALSO
+.Xr ruptime 1 ,
+.Xr rwho 1
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.2 .
+.Sh BUGS
+Status information should be sent only upon request rather than continuously.
+People often interpret the server dying
+or network communication failures
+as a machine going down.
diff --git a/usr.sbin/rwhod/rwhod.c b/usr.sbin/rwhod/rwhod.c
new file mode 100644
index 0000000..ed45fe9
--- /dev/null
+++ b/usr.sbin/rwhod/rwhod.c
@@ -0,0 +1,781 @@
+/*-
+ * Copyright (c) 1983, 1993 The Regents of the University of California.
+ * Copyright (c) 2013 Mariusz Zaborski <oshogbo@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.
+ * 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 */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)rwhod.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/capsicum.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/signal.h>
+#include <sys/ioctl.h>
+#include <sys/sysctl.h>
+#include <sys/procdesc.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 <protocols/rwhod.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <netdb.h>
+#include <paths.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <timeconv.h>
+#include <utmpx.h>
+#include <unistd.h>
+
+#define UNPRIV_USER "daemon"
+#define UNPRIV_GROUP "daemon"
+
+#define NO_MULTICAST 0 /* multicast modes */
+#define PER_INTERFACE_MULTICAST 1
+#define SCOPED_MULTICAST 2
+
+#define MAX_MULTICAST_SCOPE 32 /* "site-wide", by convention */
+
+#define INADDR_WHOD_GROUP (u_long)0xe0000103 /* 224.0.1.3 */
+ /* (belongs in protocols/rwhod.h) */
+
+int insecure_mode;
+int quiet_mode;
+int iff_flag = IFF_POINTOPOINT;
+int multicast_mode = NO_MULTICAST;
+int multicast_scope;
+struct sockaddr_in multicast_addr =
+ { sizeof(multicast_addr), AF_INET, 0, { 0 }, { 0 } };
+
+/*
+ * Sleep interval. Don't forget to change the down time check in ruptime
+ * if this is changed.
+ */
+#define SL_INTERVAL (3 * 60)
+
+char myname[MAXHOSTNAMELEN];
+
+/*
+ * We communicate with each neighbor in a list constructed at the time we're
+ * started up. Neighbors are currently directly connected via a hardware
+ * interface.
+ */
+struct neighbor {
+ struct neighbor *n_next;
+ char *n_name; /* interface name */
+ struct sockaddr *n_addr; /* who to send to */
+ int n_addrlen; /* size of address */
+ int n_flags; /* should forward?, interface flags */
+};
+
+struct neighbor *neighbors;
+struct whod mywd;
+struct servent *sp;
+int s;
+int fdp;
+pid_t pid_child_receiver;
+
+#define WHDRSIZE (int)(sizeof(mywd) - sizeof(mywd.wd_we))
+
+int configure(int so);
+void getboottime(int signo __unused);
+void receiver_process(void);
+void rt_xaddrs(caddr_t cp, caddr_t cplim, struct rt_addrinfo *rtinfo);
+void run_as(uid_t *uid, gid_t *gid);
+void quit(const char *msg);
+void sender_process(void);
+int verify(char *name, int maxlen);
+static void usage(void);
+
+#ifdef DEBUG
+char *interval(int time, char *updown);
+void Sendto(int s, const void *buf, size_t cc, int flags,
+ const struct sockaddr *to, int tolen);
+#define sendto Sendto
+#endif
+
+/*
+ * This version of Berkeley's rwhod has been modified to use IP multicast
+ * datagrams, under control of a new command-line option:
+ *
+ * rwhod -m causes rwhod to use IP multicast (instead of
+ * broadcast or unicast) on all interfaces that have
+ * the IFF_MULTICAST flag set in their "ifnet" structs
+ * (excluding the loopback interface). The multicast
+ * reports are sent with a time-to-live of 1, to prevent
+ * forwarding beyond the directly-connected subnet(s).
+ *
+ * rwhod -m <ttl> causes rwhod to send IP multicast datagrams with a
+ * time-to-live of <ttl>, via a SINGLE interface rather
+ * than all interfaces. <ttl> must be between 0 and
+ * MAX_MULTICAST_SCOPE, defined below. Note that "-m 1"
+ * is different than "-m", in that "-m 1" specifies
+ * transmission on one interface only.
+ *
+ * When "-m" is used without a <ttl> argument, the program accepts multicast
+ * rwhod reports from all multicast-capable interfaces. If a <ttl> argument
+ * is given, it accepts multicast reports from only one interface, the one
+ * on which reports are sent (which may be controlled via the host's routing
+ * table). Regardless of the "-m" option, the program accepts broadcast or
+ * unicast reports from all interfaces. Thus, this program will hear the
+ * reports of old, non-multicasting rwhods, but, if multicasting is used,
+ * those old rwhods won't hear the reports generated by this program.
+ *
+ * -- Steve Deering, Stanford University, February 1989
+ */
+int
+main(int argc, char *argv[])
+{
+ int on;
+ char *cp;
+ struct sockaddr_in soin;
+ uid_t unpriv_uid;
+ gid_t unpriv_gid;
+
+ on = 1;
+ if (getuid())
+ errx(1, "not super user");
+
+ run_as(&unpriv_uid, &unpriv_gid);
+
+ argv++;
+ argc--;
+ while (argc > 0 && *argv[0] == '-') {
+ if (strcmp(*argv, "-m") == 0) {
+ if (argc > 1 && isdigit(*(argv + 1)[0])) {
+ argv++;
+ argc--;
+ multicast_mode = SCOPED_MULTICAST;
+ multicast_scope = atoi(*argv);
+ if (multicast_scope > MAX_MULTICAST_SCOPE) {
+ errx(1, "ttl must not exceed %u",
+ MAX_MULTICAST_SCOPE);
+ }
+ } else {
+ multicast_mode = PER_INTERFACE_MULTICAST;
+ }
+ } else if (strcmp(*argv, "-i") == 0) {
+ insecure_mode = 1;
+ } else if (strcmp(*argv, "-l") == 0) {
+ quiet_mode = 1;
+ } else if (strcmp(*argv, "-p") == 0) {
+ iff_flag = 0;
+ } else {
+ usage();
+ }
+ argv++;
+ argc--;
+ }
+ if (argc > 0)
+ usage();
+#ifndef DEBUG
+ daemon(1, 0);
+#endif
+ (void) signal(SIGHUP, getboottime);
+ openlog("rwhod", LOG_PID | LOG_NDELAY, LOG_DAEMON);
+ sp = getservbyname("who", "udp");
+ if (sp == NULL) {
+ syslog(LOG_ERR, "who/udp: unknown service");
+ exit(1);
+ }
+ if (chdir(_PATH_RWHODIR) < 0) {
+ syslog(LOG_ERR, "%s: %m", _PATH_RWHODIR);
+ exit(1);
+ }
+ /*
+ * Establish host name as returned by system.
+ */
+ if (gethostname(myname, sizeof(myname) - 1) < 0) {
+ syslog(LOG_ERR, "gethostname: %m");
+ exit(1);
+ }
+ if ((cp = strchr(myname, '.')) != NULL)
+ *cp = '\0';
+ strlcpy(mywd.wd_hostname, myname, sizeof(mywd.wd_hostname));
+ getboottime(0);
+ if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+ syslog(LOG_ERR, "socket: %m");
+ exit(1);
+ }
+ if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) < 0) {
+ syslog(LOG_ERR, "setsockopt SO_BROADCAST: %m");
+ exit(1);
+ }
+ memset(&soin, 0, sizeof(soin));
+ soin.sin_len = sizeof(soin);
+ soin.sin_family = AF_INET;
+ soin.sin_port = sp->s_port;
+ if (bind(s, (struct sockaddr *)&soin, sizeof(soin)) < 0) {
+ syslog(LOG_ERR, "bind: %m");
+ exit(1);
+ }
+ if (setgid(unpriv_gid) != 0) {
+ syslog(LOG_ERR, "setgid: %m");
+ exit(1);
+ }
+ if (setgroups(1, &unpriv_gid) != 0) { /* XXX BOGUS groups[0] = egid */
+ syslog(LOG_ERR, "setgroups: %m");
+ exit(1);
+ }
+ if (setuid(unpriv_uid) != 0) {
+ syslog(LOG_ERR, "setuid: %m");
+ exit(1);
+ }
+ if (!configure(s))
+ exit(1);
+ if (!quiet_mode) {
+ pid_child_receiver = pdfork(&fdp, 0);
+ if (pid_child_receiver == 0) {
+ receiver_process();
+ } else if (pid_child_receiver > 0) {
+ sender_process();
+ } else if (pid_child_receiver == -1) {
+ if (errno == ENOSYS) {
+ syslog(LOG_ERR,
+ "The pdfork(2) system call is not available - kernel too old.");
+ } else {
+ syslog(LOG_ERR, "pdfork: %m");
+ }
+ exit(1);
+ }
+ } else {
+ receiver_process();
+ }
+}
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: rwhod [-i] [-p] [-l] [-m [ttl]]\n");
+ exit(1);
+}
+
+void
+run_as(uid_t *uid, gid_t *gid)
+{
+ struct passwd *pw;
+ struct group *gr;
+
+ pw = getpwnam(UNPRIV_USER);
+ if (pw == NULL) {
+ syslog(LOG_ERR, "getpwnam(%s): %m", UNPRIV_USER);
+ exit(1);
+ }
+ *uid = pw->pw_uid;
+
+ gr = getgrnam(UNPRIV_GROUP);
+ if (gr == NULL) {
+ syslog(LOG_ERR, "getgrnam(%s): %m", UNPRIV_GROUP);
+ exit(1);
+ }
+ *gid = gr->gr_gid;
+}
+
+/*
+ * Check out host name for unprintables
+ * and other funnies before allowing a file
+ * to be created. Sorry, but blanks aren't allowed.
+ */
+int
+verify(char *name, int maxlen)
+{
+ int size;
+
+ size = 0;
+ while (*name != '\0' && size < maxlen - 1) {
+ if (!isascii((unsigned char)*name) ||
+ !(isalnum((unsigned char)*name) ||
+ ispunct((unsigned char)*name))) {
+ return (0);
+ }
+ name++;
+ size++;
+ }
+ *name = '\0';
+ return (size > 0);
+}
+
+void
+receiver_process(void)
+{
+ struct sockaddr_in from;
+ struct stat st;
+ cap_rights_t rights;
+ char path[64];
+ int dirfd;
+ struct whod wd;
+ socklen_t len;
+ int cc, whod;
+ time_t t;
+
+ len = sizeof(from);
+ dirfd = open(".", O_RDONLY | O_DIRECTORY);
+ if (dirfd < 0) {
+ syslog(LOG_WARNING, "%s: %m", _PATH_RWHODIR);
+ exit(1);
+ }
+ cap_rights_init(&rights, CAP_CREATE, CAP_FSTAT, CAP_FTRUNCATE,
+ CAP_LOOKUP, CAP_SEEK, CAP_WRITE);
+ if (cap_rights_limit(dirfd, &rights) < 0 && errno != ENOSYS) {
+ syslog(LOG_WARNING, "cap_rights_limit: %m");
+ exit(1);
+ }
+ if (cap_enter() < 0 && errno != ENOSYS) {
+ syslog(LOG_ERR, "cap_enter: %m");
+ exit(1);
+ }
+ for (;;) {
+ cc = recvfrom(s, &wd, sizeof(wd), 0, (struct sockaddr *)&from,
+ &len);
+ if (cc <= 0) {
+ if (cc < 0 && errno != EINTR)
+ syslog(LOG_WARNING, "recv: %m");
+ continue;
+ }
+ if (from.sin_port != sp->s_port && !insecure_mode) {
+ syslog(LOG_WARNING, "%d: bad source port from %s",
+ ntohs(from.sin_port), inet_ntoa(from.sin_addr));
+ continue;
+ }
+ if (cc < WHDRSIZE) {
+ syslog(LOG_WARNING, "short packet from %s",
+ inet_ntoa(from.sin_addr));
+ continue;
+ }
+ if (wd.wd_vers != WHODVERSION)
+ continue;
+ if (wd.wd_type != WHODTYPE_STATUS)
+ continue;
+ if (!verify(wd.wd_hostname, sizeof(wd.wd_hostname))) {
+ syslog(LOG_WARNING, "malformed host name from %s",
+ inet_ntoa(from.sin_addr));
+ continue;
+ }
+ (void) snprintf(path, sizeof(path), "whod.%s", wd.wd_hostname);
+ /*
+ * Rather than truncating and growing the file each time,
+ * use ftruncate if size is less than previous size.
+ */
+ whod = openat(dirfd, path, O_WRONLY | O_CREAT, 0644);
+ if (whod < 0) {
+ syslog(LOG_WARNING, "%s: %m", path);
+ continue;
+ }
+ cap_rights_init(&rights, CAP_FSTAT, CAP_FTRUNCATE, CAP_WRITE);
+ if (cap_rights_limit(whod, &rights) < 0 && errno != ENOSYS) {
+ syslog(LOG_WARNING, "cap_rights_limit: %m");
+ exit(1);
+ }
+#if ENDIAN != BIG_ENDIAN
+ {
+ struct whoent *we;
+ int i, n;
+
+ n = (cc - WHDRSIZE) / sizeof(struct whoent);
+ /* undo header byte swapping before writing to file */
+ wd.wd_sendtime = ntohl(wd.wd_sendtime);
+ for (i = 0; i < 3; i++)
+ wd.wd_loadav[i] = ntohl(wd.wd_loadav[i]);
+ wd.wd_boottime = ntohl(wd.wd_boottime);
+ we = wd.wd_we;
+ for (i = 0; i < n; i++) {
+ we->we_idle = ntohl(we->we_idle);
+ we->we_utmp.out_time =
+ ntohl(we->we_utmp.out_time);
+ we++;
+ }
+ }
+#endif
+ (void) time(&t);
+ wd.wd_recvtime = _time_to_int(t);
+ (void) write(whod, (char *)&wd, cc);
+ if (fstat(whod, &st) < 0 || st.st_size > cc)
+ ftruncate(whod, cc);
+ (void) close(whod);
+ }
+ (void) close(dirfd);
+}
+
+void
+sender_process(void)
+{
+ int sendcount;
+ double avenrun[3];
+ time_t now;
+ int i, cc, status;
+ struct utmpx *ut;
+ struct stat stb;
+ struct neighbor *np;
+ struct whoent *we, *wend;
+
+ sendcount = 0;
+ for (;;) {
+ we = mywd.wd_we;
+ now = time(NULL);
+ if (sendcount % 10 == 0)
+ getboottime(0);
+ sendcount++;
+ wend = &mywd.wd_we[1024 / sizeof(struct whoent)];
+ setutxent();
+ while ((ut = getutxent()) != NULL && we < wend) {
+ if (ut->ut_type != USER_PROCESS)
+ continue;
+ strncpy(we->we_utmp.out_line, ut->ut_line,
+ sizeof(we->we_utmp.out_line));
+ strncpy(we->we_utmp.out_name, ut->ut_user,
+ sizeof(we->we_utmp.out_name));
+ we->we_utmp.out_time =
+ htonl(_time_to_time32(ut->ut_tv.tv_sec));
+ we++;
+ }
+ endutxent();
+
+ if (chdir(_PATH_DEV) < 0) {
+ syslog(LOG_ERR, "chdir(%s): %m", _PATH_DEV);
+ exit(1);
+ }
+ wend = we;
+ for (we = mywd.wd_we; we < wend; we++) {
+ if (stat(we->we_utmp.out_line, &stb) >= 0)
+ we->we_idle = htonl(now - stb.st_atime);
+ }
+ (void) getloadavg(avenrun,
+ sizeof(avenrun) / sizeof(avenrun[0]));
+ for (i = 0; i < 3; i++)
+ mywd.wd_loadav[i] = htonl((u_long)(avenrun[i] * 100));
+ cc = (char *)wend - (char *)&mywd;
+ mywd.wd_sendtime = htonl(_time_to_time32(time(NULL)));
+ mywd.wd_vers = WHODVERSION;
+ mywd.wd_type = WHODTYPE_STATUS;
+ if (multicast_mode == SCOPED_MULTICAST) {
+ (void) sendto(s, (char *)&mywd, cc, 0,
+ (struct sockaddr *)&multicast_addr,
+ sizeof(multicast_addr));
+ } else {
+ for (np = neighbors; np != NULL; np = np->n_next) {
+ if (multicast_mode == PER_INTERFACE_MULTICAST &&
+ (np->n_flags & IFF_MULTICAST) != 0) {
+ /*
+ * Select the outgoing interface for the
+ * multicast.
+ */
+ if (setsockopt(s, IPPROTO_IP,
+ IP_MULTICAST_IF,
+ &(((struct sockaddr_in *)np->n_addr)->sin_addr),
+ sizeof(struct in_addr)) < 0) {
+ syslog(LOG_ERR,
+ "setsockopt IP_MULTICAST_IF: %m");
+ exit(1);
+ }
+ (void) sendto(s, (char *)&mywd, cc, 0,
+ (struct sockaddr *)&multicast_addr,
+ sizeof(multicast_addr));
+ } else {
+ (void) sendto(s, (char *)&mywd, cc, 0,
+ np->n_addr, np->n_addrlen);
+ }
+ }
+ }
+ if (chdir(_PATH_RWHODIR) < 0) {
+ syslog(LOG_ERR, "chdir(%s): %m", _PATH_RWHODIR);
+ exit(1);
+ }
+ if (waitpid(pid_child_receiver, &status, WNOHANG) ==
+ pid_child_receiver) {
+ break;
+ }
+ sleep(SL_INTERVAL);
+ }
+}
+
+void
+getboottime(int signo __unused)
+{
+ int mib[2];
+ size_t size;
+ struct timeval tm;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_BOOTTIME;
+ size = sizeof(tm);
+ if (sysctl(mib, 2, &tm, &size, NULL, 0) == -1) {
+ syslog(LOG_ERR, "cannot get boottime: %m");
+ exit(1);
+ }
+ mywd.wd_boottime = htonl(_time_to_time32(tm.tv_sec));
+}
+
+void
+quit(const char *msg)
+{
+
+ syslog(LOG_ERR, "%s", msg);
+ exit(1);
+}
+
+void
+rt_xaddrs(caddr_t cp, caddr_t cplim, struct rt_addrinfo *rtinfo)
+{
+ struct sockaddr *sa;
+ int i;
+
+ memset(rtinfo->rti_info, 0, sizeof(rtinfo->rti_info));
+ for (i = 0; i < RTAX_MAX && cp < cplim; i++) {
+ if ((rtinfo->rti_addrs & (1 << i)) == 0)
+ continue;
+ sa = (struct sockaddr *)cp;
+ rtinfo->rti_info[i] = sa;
+ cp += SA_SIZE(sa);
+ }
+}
+
+/*
+ * Figure out device configuration and select
+ * networks which deserve status information.
+ */
+int
+configure(int so)
+{
+ struct neighbor *np;
+ struct if_msghdr *ifm;
+ struct ifa_msghdr *ifam;
+ struct sockaddr_dl *sdl;
+ size_t needed;
+ int mib[6], flags, lflags, len;
+ char *buf, *lim, *next;
+ struct rt_addrinfo info;
+
+ flags = 0;
+ if (multicast_mode != NO_MULTICAST) {
+ multicast_addr.sin_addr.s_addr = htonl(INADDR_WHOD_GROUP);
+ multicast_addr.sin_port = sp->s_port;
+ }
+
+ if (multicast_mode == SCOPED_MULTICAST) {
+ struct ip_mreq mreq;
+ unsigned char ttl;
+
+ mreq.imr_multiaddr.s_addr = htonl(INADDR_WHOD_GROUP);
+ mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+ if (setsockopt(so, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ &mreq, sizeof(mreq)) < 0) {
+ syslog(LOG_ERR,
+ "setsockopt IP_ADD_MEMBERSHIP: %m");
+ return (0);
+ }
+ ttl = multicast_scope;
+ if (setsockopt(so, IPPROTO_IP, IP_MULTICAST_TTL, &ttl,
+ sizeof(ttl)) < 0) {
+ syslog(LOG_ERR,
+ "setsockopt IP_MULTICAST_TTL: %m");
+ return (0);
+ }
+ return (1);
+ }
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = AF_INET;
+ mib[4] = NET_RT_IFLIST;
+ mib[5] = 0;
+ if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0)
+ quit("route-sysctl-estimate");
+ if ((buf = malloc(needed)) == NULL)
+ quit("malloc");
+ if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0)
+ quit("actual retrieval of interface table");
+ lim = buf + needed;
+
+ sdl = NULL; /* XXX just to keep gcc -Wall happy */
+ for (next = buf; next < lim; next += ifm->ifm_msglen) {
+ ifm = (struct if_msghdr *)next;
+ if (ifm->ifm_type == RTM_IFINFO) {
+ sdl = (struct sockaddr_dl *)(ifm + 1);
+ flags = ifm->ifm_flags;
+ continue;
+ }
+ if ((flags & IFF_UP) == 0)
+ continue;
+ lflags = IFF_BROADCAST | iff_flag;
+ if (multicast_mode == PER_INTERFACE_MULTICAST)
+ lflags |= IFF_MULTICAST;
+ if ((flags & lflags) == 0)
+ continue;
+ if (ifm->ifm_type != RTM_NEWADDR)
+ quit("out of sync parsing NET_RT_IFLIST");
+ ifam = (struct ifa_msghdr *)ifm;
+ info.rti_addrs = ifam->ifam_addrs;
+ rt_xaddrs((char *)(ifam + 1), ifam->ifam_msglen + (char *)ifam,
+ &info);
+ /* gag, wish we could get rid of Internet dependencies */
+#define dstaddr info.rti_info[RTAX_BRD]
+#define ifaddr info.rti_info[RTAX_IFA]
+#define IPADDR_SA(x) ((struct sockaddr_in *)(x))->sin_addr.s_addr
+#define PORT_SA(x) ((struct sockaddr_in *)(x))->sin_port
+ if (dstaddr == 0 || dstaddr->sa_family != AF_INET)
+ continue;
+ PORT_SA(dstaddr) = sp->s_port;
+ for (np = neighbors; np != NULL; np = np->n_next) {
+ if (memcmp(sdl->sdl_data, np->n_name,
+ sdl->sdl_nlen) == 0 &&
+ IPADDR_SA(np->n_addr) == IPADDR_SA(dstaddr)) {
+ break;
+ }
+ }
+ if (np != NULL)
+ continue;
+ len = sizeof(*np) + dstaddr->sa_len + sdl->sdl_nlen + 1;
+ np = malloc(len);
+ if (np == NULL)
+ quit("malloc of neighbor structure");
+ memset(np, 0, len);
+ np->n_flags = flags;
+ np->n_addr = (struct sockaddr *)(np + 1);
+ np->n_addrlen = dstaddr->sa_len;
+ np->n_name = np->n_addrlen + (char *)np->n_addr;
+ memcpy((char *)np->n_addr, (char *)dstaddr, np->n_addrlen);
+ memcpy(np->n_name, sdl->sdl_data, sdl->sdl_nlen);
+ if (multicast_mode == PER_INTERFACE_MULTICAST &&
+ (flags & IFF_MULTICAST) != 0 &&
+ (flags & IFF_LOOPBACK) == 0) {
+ struct ip_mreq mreq;
+
+ memcpy((char *)np->n_addr, (char *)ifaddr,
+ np->n_addrlen);
+ mreq.imr_multiaddr.s_addr = htonl(INADDR_WHOD_GROUP);
+ mreq.imr_interface.s_addr =
+ ((struct sockaddr_in *)np->n_addr)->sin_addr.s_addr;
+ if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ &mreq, sizeof(mreq)) < 0) {
+ syslog(LOG_ERR,
+ "setsockopt IP_ADD_MEMBERSHIP: %m");
+#if 0
+ /* Fall back to broadcast on this if. */
+ np->n_flags &= ~IFF_MULTICAST;
+#else
+ free(np);
+ continue;
+#endif
+ }
+ }
+ np->n_next = neighbors;
+ neighbors = np;
+ }
+ free(buf);
+ return (1);
+}
+
+#ifdef DEBUG
+void
+Sendto(int s, const void *buf, size_t cc, int flags, const struct sockaddr *to,
+ int tolen)
+{
+ struct whod *w;
+ struct whoent *we;
+ struct sockaddr_in *sin;
+
+ w = (struct whod *)buf;
+ sin = (struct sockaddr_in *)to;
+ printf("sendto %x.%d\n", ntohl(sin->sin_addr.s_addr),
+ ntohs(sin->sin_port));
+ printf("hostname %s %s\n", w->wd_hostname,
+ interval(ntohl(w->wd_sendtime) - ntohl(w->wd_boottime), " up"));
+ printf("load %4.2f, %4.2f, %4.2f\n",
+ ntohl(w->wd_loadav[0]) / 100.0, ntohl(w->wd_loadav[1]) / 100.0,
+ ntohl(w->wd_loadav[2]) / 100.0);
+ cc -= WHDRSIZE;
+ for (we = w->wd_we, cc /= sizeof(struct whoent); cc > 0; cc--, we++) {
+ time_t t = _time32_to_time(ntohl(we->we_utmp.out_time));
+
+ printf("%-8.8s %s:%s %.12s", we->we_utmp.out_name,
+ w->wd_hostname, we->we_utmp.out_line, ctime(&t) + 4);
+ we->we_idle = ntohl(we->we_idle) / 60;
+ if (we->we_idle != 0) {
+ if (we->we_idle >= 100 * 60)
+ we->we_idle = 100 * 60 - 1;
+ if (we->we_idle >= 60)
+ printf(" %2d", we->we_idle / 60);
+ else
+ printf(" ");
+ printf(":%02d", we->we_idle % 60);
+ }
+ printf("\n");
+ }
+}
+
+char *
+interval(int time, char *updown)
+{
+ static char resbuf[32];
+ int days, hours, minutes;
+
+ if (time < 0 || time > 3 * 30 * 24 * 60 * 60) {
+ (void) sprintf(resbuf, " %s ??:??", updown);
+ return (resbuf);
+ }
+ minutes = (time + 59) / 60; /* round to minutes */
+ hours = minutes / 60;
+ minutes %= 60;
+ days = hours / 24;
+ hours %= 24;
+ if (days > 0) {
+ (void) sprintf(resbuf, "%s %2d+%02d:%02d",
+ updown, days, hours, minutes);
+ } else {
+ (void) sprintf(resbuf, "%s %2d:%02d",
+ updown, hours, minutes);
+ }
+ return (resbuf);
+}
+#endif
diff --git a/usr.sbin/sa/Makefile b/usr.sbin/sa/Makefile
new file mode 100644
index 0000000..c3c79b0
--- /dev/null
+++ b/usr.sbin/sa/Makefile
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+.PATH: ${.CURDIR}/../../usr.bin/lastcomm
+
+PROG= sa
+MAN= sa.8
+SRCS= main.c db.c pdb.c usrdb.c readrec.c
+
+.if ${MK_TESTS} != "no"
+SUBDIR+= tests
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/sa/Makefile.depend b/usr.sbin/sa/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/sa/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/sa/db.c b/usr.sbin/sa/db.c
new file mode 100644
index 0000000..6634e54
--- /dev/null
+++ b/usr.sbin/sa/db.c
@@ -0,0 +1,207 @@
+/*-
+ * Copyright (c) 2007 Diomidis Spinellis
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/acct.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <db.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include "extern.h"
+
+/* Key used to store the version of the database data elements. */
+static char VERSION_KEY[] = "\0VERSION";
+
+/*
+ * Create the in-memory database, *mdb.
+ * If iflag is not set, fill-in mdb with the records of the disk-based
+ * database dbname.
+ * Upgrade old-version records by calling v1_to_v2.
+ * Return 0 if OK, -1 on error.
+ */
+int
+db_copy_in(DB **mdb, const char *dbname, const char *uname, BTREEINFO *bti,
+ int (*v1_to_v2)(DBT *key, DBT *data))
+{
+ DBT key, data;
+ DB *ddb;
+ int error, rv, version;
+
+ if ((*mdb = dbopen(NULL, O_RDWR, 0, DB_BTREE, bti)) == NULL)
+ return (-1);
+
+ if (iflag)
+ return (0);
+
+ if ((ddb = dbopen(dbname, O_RDONLY, 0, DB_BTREE, bti)) == NULL) {
+ if (errno == ENOENT)
+ return (0);
+ warn("retrieving %s summary", uname);
+ db_destroy(*mdb, uname);
+ return (-1);
+ }
+
+ error = 0;
+
+ /* Obtain/set version. */
+ version = 1;
+ key.data = (void*)&VERSION_KEY;
+ key.size = sizeof(VERSION_KEY);
+
+ rv = DB_GET(ddb, &key, &data, 0);
+ if (rv < 0) {
+ warn("get version key from %s stats", uname);
+ error = -1;
+ goto closeout;
+ } else if (rv == 0) { /* It's there; verify version. */
+ if (data.size != sizeof(version)) {
+ warnx("invalid version size %zd in %s",
+ data.size, uname);
+ error = -1;
+ goto closeout;
+ }
+ memcpy(&version, data.data, data.size);
+ if (version != 2) {
+ warnx("unsupported version %d in %s",
+ version, uname);
+ error = -1;
+ goto closeout;
+ }
+ }
+
+ for (rv = DB_SEQ(ddb, &key, &data, R_FIRST); rv == 0;
+ rv = DB_SEQ(ddb, &key, &data, R_NEXT)) {
+
+ /* See if this is a version record. */
+ if (key.size == sizeof(VERSION_KEY) &&
+ memcmp(key.data, VERSION_KEY, sizeof(VERSION_KEY)) == 0)
+ continue;
+
+ /* Convert record from v1, if needed. */
+ if (version == 1 && v1_to_v2(&key, &data) < 0) {
+ warn("converting %s stats", uname);
+ error = -1;
+ goto closeout;
+ }
+
+ /* Copy record to the in-memory database. */
+ if ((rv = DB_PUT(*mdb, &key, &data, 0)) < 0) {
+ warn("initializing %s stats", uname);
+ error = -1;
+ goto closeout;
+ }
+ }
+ if (rv < 0) {
+ warn("retrieving %s summary", uname);
+ error = -1;
+ }
+
+closeout:
+ if (DB_CLOSE(ddb) < 0) {
+ warn("closing %s summary", uname);
+ error = -1;
+ }
+
+ if (error)
+ db_destroy(*mdb, uname);
+ return (error);
+}
+
+/*
+ * Save the in-memory database mdb to the disk database dbname.
+ * Return 0 if OK, -1 on error.
+ */
+int
+db_copy_out(DB *mdb, const char *dbname, const char *uname, BTREEINFO *bti)
+{
+ DB *ddb;
+ DBT key, data;
+ int error, rv, version;
+
+ if ((ddb = dbopen(dbname, O_RDWR|O_CREAT|O_TRUNC, 0644,
+ DB_BTREE, bti)) == NULL) {
+ warn("creating %s summary", uname);
+ return (-1);
+ }
+
+ error = 0;
+
+ for (rv = DB_SEQ(mdb, &key, &data, R_FIRST);
+ rv == 0; rv = DB_SEQ(mdb, &key, &data, R_NEXT)) {
+ if ((rv = DB_PUT(ddb, &key, &data, 0)) < 0) {
+ warn("saving %s summary", uname);
+ error = -1;
+ goto out;
+ }
+ }
+ if (rv < 0) {
+ warn("retrieving %s stats", uname);
+ error = -1;
+ }
+
+out:
+ /* Add a version record. */
+ key.data = (void*)&VERSION_KEY;
+ key.size = sizeof(VERSION_KEY);
+ version = 2;
+ data.data = &version;
+ data.size = sizeof(version);
+ if ((rv = DB_PUT(ddb, &key, &data, 0)) < 0) {
+ warn("add version record to %s stats", uname);
+ error = -1;
+ } else if (rv == 1) {
+ warnx("duplicate version record in %s stats", uname);
+ error = -1;
+ }
+
+ if (DB_SYNC(ddb, 0) < 0) {
+ warn("syncing %s summary", uname);
+ error = -1;
+ }
+ if (DB_CLOSE(ddb) < 0) {
+ warn("closing %s summary", uname);
+ error = -1;
+ }
+ return error;
+}
+
+void
+db_destroy(DB *db, const char *uname)
+{
+ if (DB_CLOSE(db) < 0)
+ warn("destroying %s stats", uname);
+}
diff --git a/usr.sbin/sa/extern.h b/usr.sbin/sa/extern.h
new file mode 100644
index 0000000..70f97a7
--- /dev/null
+++ b/usr.sbin/sa/extern.h
@@ -0,0 +1,110 @@
+/*
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <db.h>
+
+/* structures */
+
+/* All times are stored in 1e-6s units. */
+
+struct cmdinfo {
+ char ci_comm[MAXCOMLEN+2]; /* command name (+ '*') */
+ uid_t ci_uid; /* user id */
+ u_quad_t ci_calls; /* number of calls */
+ double ci_etime; /* elapsed time */
+ double ci_utime; /* user time */
+ double ci_stime; /* system time */
+ double ci_mem; /* memory use */
+ double ci_io; /* number of disk i/o ops */
+ u_int ci_flags; /* flags; see below */
+};
+#define CI_UNPRINTABLE 0x0001 /* unprintable chars in name */
+
+struct userinfo {
+ uid_t ui_uid; /* user id; for consistency */
+ u_quad_t ui_calls; /* number of invocations */
+ double ui_utime; /* user time */
+ double ui_stime; /* system time */
+ double ui_mem; /* memory use */
+ double ui_io; /* number of disk i/o ops */
+};
+
+/* typedefs */
+
+typedef int (*cmpf_t)(const DBT *, const DBT *);
+
+/* external functions in db.c */
+int db_copy_in(DB **mdb, const char *dbname, const char *name,
+ BTREEINFO *bti, int (*v1_to_v2)(DBT *key, DBT *data));
+int db_copy_out(DB *mdb, const char *dbname, const char *name,
+ BTREEINFO *bti);
+void db_destroy(DB *db, const char *uname);
+
+/* external functions in pdb.c */
+int pacct_init(void);
+void pacct_destroy(void);
+int pacct_add(const struct cmdinfo *);
+int pacct_update(void);
+void pacct_print(void);
+
+/* external functions in readrec.c */
+int readrec_forward(FILE *f, struct acctv2 *av2);
+
+/* external functions in usrdb.c */
+int usracct_init(void);
+void usracct_destroy(void);
+int usracct_add(const struct cmdinfo *);
+int usracct_update(void);
+void usracct_print(void);
+
+/* variables */
+
+extern int aflag, bflag, cflag, dflag, Dflag, fflag, iflag, jflag, kflag;
+extern int Kflag, lflag, mflag, qflag, rflag, sflag, tflag, uflag, vflag;
+extern u_quad_t cutoff;
+extern cmpf_t sa_cmp;
+extern const char *pdb_file, *usrdb_file;
+
+/* some #defines to help with db's stupidity */
+
+#define DB_CLOSE(db) \
+ ((*(db)->close)(db))
+#define DB_GET(db, key, data, flags) \
+ ((*(db)->get)((db), (key), (data), (flags)))
+#define DB_PUT(db, key, data, flags) \
+ ((*(db)->put)((db), (key), (data), (flags)))
+#define DB_SYNC(db, flags) \
+ ((*(db)->sync)((db), (flags)))
+#define DB_SEQ(db, key, data, flags) \
+ ((*(db)->seq)((db), (key), (data), (flags)))
diff --git a/usr.sbin/sa/main.c b/usr.sbin/sa/main.c
new file mode 100644
index 0000000..64736ac
--- /dev/null
+++ b/usr.sbin/sa/main.c
@@ -0,0 +1,533 @@
+/*
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1994 Christopher G. Demetriou\n\
+ All rights reserved.\n";
+#endif
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * sa: system accounting
+ */
+
+#include <sys/types.h>
+#include <sys/acct.h>
+#include <ctype.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 <unistd.h>
+#include "extern.h"
+#include "pathnames.h"
+
+static FILE *acct_load(const char *, int);
+static int cmp_comm(const char *, const char *);
+static int cmp_usrsys(const DBT *, const DBT *);
+static int cmp_avgusrsys(const DBT *, const DBT *);
+static int cmp_dkio(const DBT *, const DBT *);
+static int cmp_avgdkio(const DBT *, const DBT *);
+static int cmp_cpumem(const DBT *, const DBT *);
+static int cmp_avgcpumem(const DBT *, const DBT *);
+static int cmp_calls(const DBT *, const DBT *);
+static void usage(void);
+
+int aflag, bflag, cflag, dflag, Dflag, fflag, iflag, jflag, kflag;
+int Kflag, lflag, mflag, qflag, rflag, sflag, tflag, uflag, vflag;
+u_quad_t cutoff = 1;
+const char *pdb_file = _PATH_SAVACCT;
+const char *usrdb_file = _PATH_USRACCT;
+
+static char *dfltargv[] = { NULL };
+static int dfltargc = (sizeof dfltargv/sizeof(char *));
+
+/* default to comparing by sum of user + system time */
+cmpf_t sa_cmp = cmp_usrsys;
+
+int
+main(int argc, char **argv)
+{
+ FILE *f;
+ char pathacct[] = _PATH_ACCT;
+ int ch, error = 0;
+
+ dfltargv[0] = pathacct;
+
+ while ((ch = getopt(argc, argv, "abcdDfijkKlmnP:qrstuU:v:")) != -1)
+ switch (ch) {
+ case 'a':
+ /* print all commands */
+ aflag = 1;
+ break;
+ case 'b':
+ /* sort by per-call user/system time average */
+ bflag = 1;
+ sa_cmp = cmp_avgusrsys;
+ break;
+ case 'c':
+ /* print percentage total time */
+ cflag = 1;
+ break;
+ case 'd':
+ /* sort by averge number of disk I/O ops */
+ dflag = 1;
+ sa_cmp = cmp_avgdkio;
+ break;
+ case 'D':
+ /* print and sort by total disk I/O ops */
+ Dflag = 1;
+ sa_cmp = cmp_dkio;
+ break;
+ case 'f':
+ /* force no interactive threshold comprison */
+ fflag = 1;
+ break;
+ case 'i':
+ /* do not read in summary file */
+ iflag = 1;
+ break;
+ case 'j':
+ /* instead of total minutes, give sec/call */
+ jflag = 1;
+ break;
+ case 'k':
+ /* sort by cpu-time average memory usage */
+ kflag = 1;
+ sa_cmp = cmp_avgcpumem;
+ break;
+ case 'K':
+ /* print and sort by cpu-storage integral */
+ sa_cmp = cmp_cpumem;
+ Kflag = 1;
+ break;
+ case 'l':
+ /* separate system and user time */
+ lflag = 1;
+ break;
+ case 'm':
+ /* print procs and time per-user */
+ mflag = 1;
+ break;
+ case 'n':
+ /* sort by number of calls */
+ sa_cmp = cmp_calls;
+ break;
+ case 'P':
+ /* specify program database summary file */
+ pdb_file = optarg;
+ break;
+ case 'q':
+ /* quiet; error messages only */
+ qflag = 1;
+ break;
+ case 'r':
+ /* reverse order of sort */
+ rflag = 1;
+ break;
+ case 's':
+ /* merge accounting file into summaries */
+ sflag = 1;
+ break;
+ case 't':
+ /* report ratio of user and system times */
+ tflag = 1;
+ break;
+ case 'u':
+ /* first, print uid and command name */
+ uflag = 1;
+ break;
+ case 'U':
+ /* specify user database summary file */
+ usrdb_file = optarg;
+ break;
+ case 'v':
+ /* cull junk */
+ vflag = 1;
+ cutoff = atoi(optarg);
+ break;
+ case '?':
+ default:
+ usage();
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ /* various argument checking */
+ if (fflag && !vflag)
+ errx(1, "only one of -f requires -v");
+ if (fflag && aflag)
+ errx(1, "only one of -a and -v may be specified");
+ /* XXX need more argument checking */
+
+ if (!uflag) {
+ /* initialize tables */
+ if ((sflag || (!mflag && !qflag)) && pacct_init() != 0)
+ errx(1, "process accounting initialization failed");
+ if ((sflag || (mflag && !qflag)) && usracct_init() != 0)
+ errx(1, "user accounting initialization failed");
+ }
+
+ if (argc == 0) {
+ argc = dfltargc;
+ argv = dfltargv;
+ }
+
+ /* for each file specified */
+ for (; argc > 0; argc--, argv++) {
+ /*
+ * load the accounting data from the file.
+ * if it fails, go on to the next file.
+ */
+ f = acct_load(argv[0], sflag);
+ if (f == NULL)
+ continue;
+
+ if (!uflag && sflag) {
+#ifndef DEBUG
+ sigset_t nmask, omask;
+ int unmask = 1;
+
+ /*
+ * block most signals so we aren't interrupted during
+ * the update.
+ */
+ if (sigfillset(&nmask) == -1) {
+ warn("sigfillset");
+ unmask = 0;
+ error = 1;
+ }
+ if (unmask &&
+ (sigprocmask(SIG_BLOCK, &nmask, &omask) == -1)) {
+ warn("couldn't set signal mask");
+ unmask = 0;
+ error = 1;
+ }
+#endif /* DEBUG */
+
+ /*
+ * truncate the accounting data file ASAP, to avoid
+ * losing data. don't worry about errors in updating
+ * the saved stats; better to underbill than overbill,
+ * but we want every accounting record intact.
+ */
+ if (ftruncate(fileno(f), 0) == -1) {
+ warn("couldn't truncate %s", *argv);
+ error = 1;
+ }
+
+ /*
+ * update saved user and process accounting data.
+ * note errors for later.
+ */
+ if (pacct_update() != 0 || usracct_update() != 0)
+ error = 1;
+
+#ifndef DEBUG
+ /*
+ * restore signals
+ */
+ if (unmask &&
+ (sigprocmask(SIG_SETMASK, &omask, NULL) == -1)) {
+ warn("couldn't restore signal mask");
+ error = 1;
+ }
+#endif /* DEBUG */
+ }
+
+ /*
+ * close the opened accounting file
+ */
+ if (fclose(f) == EOF) {
+ warn("fclose %s", *argv);
+ error = 1;
+ }
+ }
+
+ if (!uflag && !qflag) {
+ /* print any results we may have obtained. */
+ if (!mflag)
+ pacct_print();
+ else
+ usracct_print();
+ }
+
+ if (!uflag) {
+ /* finally, deallocate databases */
+ if (sflag || (!mflag && !qflag))
+ pacct_destroy();
+ if (sflag || (mflag && !qflag))
+ usracct_destroy();
+ }
+
+ exit(error);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "usage: sa [-abcdDfijkKlmnqrstu] [-P file] [-U file] [-v cutoff] [file ...]\n");
+ exit(1);
+}
+
+static FILE *
+acct_load(const char *pn, int wr)
+{
+ struct acctv2 ac;
+ struct cmdinfo ci;
+ ssize_t rv;
+ FILE *f;
+ int i;
+
+ /*
+ * open the file
+ */
+ f = fopen(pn, wr ? "r+" : "r");
+ if (f == NULL) {
+ warn("open %s %s", pn, wr ? "for read/write" : "read-only");
+ return (NULL);
+ }
+
+ /*
+ * read all we can; don't stat and open because more processes
+ * could exit, and we'd miss them
+ */
+ while (1) {
+ /* get one accounting entry and punt if there's an error */
+ rv = readrec_forward(f, &ac);
+ if (rv != 1) {
+ if (rv == EOF)
+ warn("error reading %s", pn);
+ break;
+ }
+
+ /* decode it */
+ ci.ci_calls = 1;
+ for (i = 0; i < (int)sizeof ac.ac_comm && ac.ac_comm[i] != '\0';
+ i++) {
+ char c = ac.ac_comm[i];
+
+ if (!isascii(c) || iscntrl(c)) {
+ ci.ci_comm[i] = '?';
+ ci.ci_flags |= CI_UNPRINTABLE;
+ } else
+ ci.ci_comm[i] = c;
+ }
+ if (ac.ac_flagx & AFORK)
+ ci.ci_comm[i++] = '*';
+ ci.ci_comm[i++] = '\0';
+ ci.ci_etime = ac.ac_etime;
+ ci.ci_utime = ac.ac_utime;
+ ci.ci_stime = ac.ac_stime;
+ ci.ci_uid = ac.ac_uid;
+ ci.ci_mem = ac.ac_mem;
+ ci.ci_io = ac.ac_io;
+
+ if (!uflag) {
+ /* and enter it into the usracct and pacct databases */
+ if (sflag || (!mflag && !qflag))
+ pacct_add(&ci);
+ if (sflag || (mflag && !qflag))
+ usracct_add(&ci);
+ } else if (!qflag)
+ printf("%6u %12.3lf cpu %12.0lfk mem %12.0lf io %s\n",
+ ci.ci_uid,
+ (ci.ci_utime + ci.ci_stime) / 1000000,
+ ci.ci_mem, ci.ci_io,
+ ci.ci_comm);
+ }
+
+ /* Finally, return the file stream for possible truncation. */
+ return (f);
+}
+
+/* sort commands, doing the right thing in terms of reversals */
+static int
+cmp_comm(const char *s1, const char *s2)
+{
+ int rv;
+
+ rv = strcmp(s1, s2);
+ if (rv == 0)
+ rv = -1;
+ return (rflag ? rv : -rv);
+}
+
+/* sort by total user and system time */
+static int
+cmp_usrsys(const DBT *d1, const DBT *d2)
+{
+ struct cmdinfo c1, c2;
+ double t1, t2;
+
+ memcpy(&c1, d1->data, sizeof(c1));
+ memcpy(&c2, d2->data, sizeof(c2));
+
+ t1 = c1.ci_utime + c1.ci_stime;
+ t2 = c2.ci_utime + c2.ci_stime;
+
+ if (t1 < t2)
+ return -1;
+ else if (t1 == t2)
+ return (cmp_comm(c1.ci_comm, c2.ci_comm));
+ else
+ return 1;
+}
+
+/* sort by average user and system time */
+static int
+cmp_avgusrsys(const DBT *d1, const DBT *d2)
+{
+ struct cmdinfo c1, c2;
+ double t1, t2;
+
+ memcpy(&c1, d1->data, sizeof(c1));
+ memcpy(&c2, d2->data, sizeof(c2));
+
+ t1 = c1.ci_utime + c1.ci_stime;
+ t1 /= (double) (c1.ci_calls ? c1.ci_calls : 1);
+
+ t2 = c2.ci_utime + c2.ci_stime;
+ t2 /= (double) (c2.ci_calls ? c2.ci_calls : 1);
+
+ if (t1 < t2)
+ return -1;
+ else if (t1 == t2)
+ return (cmp_comm(c1.ci_comm, c2.ci_comm));
+ else
+ return 1;
+}
+
+/* sort by total number of disk I/O operations */
+static int
+cmp_dkio(const DBT *d1, const DBT *d2)
+{
+ struct cmdinfo c1, c2;
+
+ memcpy(&c1, d1->data, sizeof(c1));
+ memcpy(&c2, d2->data, sizeof(c2));
+
+ if (c1.ci_io < c2.ci_io)
+ return -1;
+ else if (c1.ci_io == c2.ci_io)
+ return (cmp_comm(c1.ci_comm, c2.ci_comm));
+ else
+ return 1;
+}
+
+/* sort by average number of disk I/O operations */
+static int
+cmp_avgdkio(const DBT *d1, const DBT *d2)
+{
+ struct cmdinfo c1, c2;
+ double n1, n2;
+
+ memcpy(&c1, d1->data, sizeof(c1));
+ memcpy(&c2, d2->data, sizeof(c2));
+
+ n1 = c1.ci_io / (double) (c1.ci_calls ? c1.ci_calls : 1);
+ n2 = c2.ci_io / (double) (c2.ci_calls ? c2.ci_calls : 1);
+
+ if (n1 < n2)
+ return -1;
+ else if (n1 == n2)
+ return (cmp_comm(c1.ci_comm, c2.ci_comm));
+ else
+ return 1;
+}
+
+/* sort by the cpu-storage integral */
+static int
+cmp_cpumem(const DBT *d1, const DBT *d2)
+{
+ struct cmdinfo c1, c2;
+
+ memcpy(&c1, d1->data, sizeof(c1));
+ memcpy(&c2, d2->data, sizeof(c2));
+
+ if (c1.ci_mem < c2.ci_mem)
+ return -1;
+ else if (c1.ci_mem == c2.ci_mem)
+ return (cmp_comm(c1.ci_comm, c2.ci_comm));
+ else
+ return 1;
+}
+
+/* sort by the cpu-time average memory usage */
+static int
+cmp_avgcpumem(const DBT *d1, const DBT *d2)
+{
+ struct cmdinfo c1, c2;
+ double t1, t2;
+ double n1, n2;
+
+ memcpy(&c1, d1->data, sizeof(c1));
+ memcpy(&c2, d2->data, sizeof(c2));
+
+ t1 = c1.ci_utime + c1.ci_stime;
+ t2 = c2.ci_utime + c2.ci_stime;
+
+ n1 = c1.ci_mem / (t1 ? t1 : 1);
+ n2 = c2.ci_mem / (t2 ? t2 : 1);
+
+ if (n1 < n2)
+ return -1;
+ else if (n1 == n2)
+ return (cmp_comm(c1.ci_comm, c2.ci_comm));
+ else
+ return 1;
+}
+
+/* sort by the number of invocations */
+static int
+cmp_calls(const DBT *d1, const DBT *d2)
+{
+ struct cmdinfo c1, c2;
+
+ memcpy(&c1, d1->data, sizeof(c1));
+ memcpy(&c2, d2->data, sizeof(c2));
+
+ if (c1.ci_calls < c2.ci_calls)
+ return -1;
+ else if (c1.ci_calls == c2.ci_calls)
+ return (cmp_comm(c1.ci_comm, c2.ci_comm));
+ else
+ return 1;
+}
diff --git a/usr.sbin/sa/pathnames.h b/usr.sbin/sa/pathnames.h
new file mode 100644
index 0000000..7a7edf6
--- /dev/null
+++ b/usr.sbin/sa/pathnames.h
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#define _PATH_ACCT "/var/account/acct"
+#define _PATH_SAVACCT "/var/account/savacct"
+#define _PATH_USRACCT "/var/account/usracct"
diff --git a/usr.sbin/sa/pdb.c b/usr.sbin/sa/pdb.c
new file mode 100644
index 0000000..e5e35f5
--- /dev/null
+++ b/usr.sbin/sa/pdb.c
@@ -0,0 +1,374 @@
+/*
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/acct.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include "extern.h"
+#include "pathnames.h"
+
+static int check_junk(const struct cmdinfo *);
+static void add_ci(const struct cmdinfo *, struct cmdinfo *);
+static void print_ci(const struct cmdinfo *, const struct cmdinfo *);
+
+static DB *pacct_db;
+
+/* Legacy format in AHZV1 units. */
+struct cmdinfov1 {
+ char ci_comm[MAXCOMLEN+2]; /* command name (+ '*') */
+ uid_t ci_uid; /* user id */
+ u_quad_t ci_calls; /* number of calls */
+ u_quad_t ci_etime; /* elapsed time */
+ u_quad_t ci_utime; /* user time */
+ u_quad_t ci_stime; /* system time */
+ u_quad_t ci_mem; /* memory use */
+ u_quad_t ci_io; /* number of disk i/o ops */
+ u_int ci_flags; /* flags; see below */
+};
+
+/*
+ * Convert a v1 data record into the current version.
+ * Return 0 if OK, -1 on error, setting errno.
+ */
+static int
+v1_to_v2(DBT *key __unused, DBT *data)
+{
+ struct cmdinfov1 civ1;
+ static struct cmdinfo civ2;
+
+ if (data->size != sizeof(civ1)) {
+ errno = EFTYPE;
+ return (-1);
+ }
+ memcpy(&civ1, data->data, data->size);
+ memset(&civ2, 0, sizeof(civ2));
+ memcpy(civ2.ci_comm, civ1.ci_comm, sizeof(civ2.ci_comm));
+ civ2.ci_uid = civ1.ci_uid;
+ civ2.ci_calls = civ1.ci_calls;
+ civ2.ci_etime = ((double)civ1.ci_etime / AHZV1) * 1000000;
+ civ2.ci_utime = ((double)civ1.ci_utime / AHZV1) * 1000000;
+ civ2.ci_stime = ((double)civ1.ci_stime / AHZV1) * 1000000;
+ civ2.ci_mem = civ1.ci_mem;
+ civ2.ci_io = civ1.ci_io;
+ civ2.ci_flags = civ1.ci_flags;
+ data->size = sizeof(civ2);
+ data->data = &civ2;
+ return (0);
+}
+
+/* Copy pdb_file to in-memory pacct_db. */
+int
+pacct_init(void)
+{
+ return (db_copy_in(&pacct_db, pdb_file, "process accounting",
+ NULL, v1_to_v2));
+}
+
+void
+pacct_destroy(void)
+{
+ db_destroy(pacct_db, "process accounting");
+}
+
+int
+pacct_add(const struct cmdinfo *ci)
+{
+ DBT key, data;
+ struct cmdinfo newci;
+ char keydata[sizeof ci->ci_comm];
+ int rv;
+
+ bcopy(ci->ci_comm, &keydata, sizeof keydata);
+ key.data = &keydata;
+ key.size = strlen(keydata);
+
+ rv = DB_GET(pacct_db, &key, &data, 0);
+ if (rv < 0) {
+ warn("get key %s from process accounting stats", ci->ci_comm);
+ return (-1);
+ } else if (rv == 0) { /* it's there; copy whole thing */
+ /* XXX compare size if paranoid */
+ /* add the old data to the new data */
+ bcopy(data.data, &newci, data.size);
+ } else { /* it's not there; zero it and copy the key */
+ bzero(&newci, sizeof newci);
+ bcopy(key.data, newci.ci_comm, key.size);
+ }
+
+ add_ci(ci, &newci);
+
+ data.data = &newci;
+ data.size = sizeof newci;
+ rv = DB_PUT(pacct_db, &key, &data, 0);
+ if (rv < 0) {
+ warn("add key %s to process accounting stats", ci->ci_comm);
+ return (-1);
+ } else if (rv == 1) {
+ warnx("duplicate key %s in process accounting stats",
+ ci->ci_comm);
+ return (-1);
+ }
+
+ return (0);
+}
+
+/* Copy in-memory pacct_db to pdb_file. */
+int
+pacct_update(void)
+{
+ return (db_copy_out(pacct_db, pdb_file, "process accounting",
+ NULL));
+}
+
+void
+pacct_print(void)
+{
+ BTREEINFO bti;
+ DBT key, data, ndata;
+ DB *output_pacct_db;
+ struct cmdinfo *cip, ci, ci_total, ci_other, ci_junk;
+ int rv;
+
+ bzero(&ci_total, sizeof ci_total);
+ strcpy(ci_total.ci_comm, "");
+ bzero(&ci_other, sizeof ci_other);
+ strcpy(ci_other.ci_comm, "***other");
+ bzero(&ci_junk, sizeof ci_junk);
+ strcpy(ci_junk.ci_comm, "**junk**");
+
+ /*
+ * Retrieve them into new DB, sorted by appropriate key.
+ * At the same time, cull 'other' and 'junk'
+ */
+ bzero(&bti, sizeof bti);
+ bti.compare = sa_cmp;
+ output_pacct_db = dbopen(NULL, O_RDWR, 0, DB_BTREE, &bti);
+ if (output_pacct_db == NULL) {
+ warn("couldn't sort process accounting stats");
+ return;
+ }
+
+ ndata.data = NULL;
+ ndata.size = 0;
+ rv = DB_SEQ(pacct_db, &key, &data, R_FIRST);
+ if (rv < 0)
+ warn("retrieving process accounting stats");
+ while (rv == 0) {
+ cip = (struct cmdinfo *) data.data;
+ bcopy(cip, &ci, sizeof ci);
+
+ /* add to total */
+ add_ci(&ci, &ci_total);
+
+ if (vflag && ci.ci_calls <= cutoff &&
+ (fflag || check_junk(&ci))) {
+ /* put it into **junk** */
+ add_ci(&ci, &ci_junk);
+ goto next;
+ }
+ if (!aflag &&
+ ((ci.ci_flags & CI_UNPRINTABLE) != 0 || ci.ci_calls <= 1)) {
+ /* put into ***other */
+ add_ci(&ci, &ci_other);
+ goto next;
+ }
+ rv = DB_PUT(output_pacct_db, &data, &ndata, 0);
+ if (rv < 0)
+ warn("sorting process accounting stats");
+
+next: rv = DB_SEQ(pacct_db, &key, &data, R_NEXT);
+ if (rv < 0)
+ warn("retrieving process accounting stats");
+ }
+
+ /* insert **junk** and ***other */
+ if (ci_junk.ci_calls != 0) {
+ data.data = &ci_junk;
+ data.size = sizeof ci_junk;
+ rv = DB_PUT(output_pacct_db, &data, &ndata, 0);
+ if (rv < 0)
+ warn("sorting process accounting stats");
+ }
+ if (ci_other.ci_calls != 0) {
+ data.data = &ci_other;
+ data.size = sizeof ci_other;
+ rv = DB_PUT(output_pacct_db, &data, &ndata, 0);
+ if (rv < 0)
+ warn("sorting process accounting stats");
+ }
+
+ /* print out the total */
+ print_ci(&ci_total, &ci_total);
+
+ /* print out; if reversed, print first (smallest) first */
+ rv = DB_SEQ(output_pacct_db, &data, &ndata, rflag ? R_FIRST : R_LAST);
+ if (rv < 0)
+ warn("retrieving process accounting report");
+ while (rv == 0) {
+ cip = (struct cmdinfo *) data.data;
+ bcopy(cip, &ci, sizeof ci);
+
+ print_ci(&ci, &ci_total);
+
+ rv = DB_SEQ(output_pacct_db, &data, &ndata,
+ rflag ? R_NEXT : R_PREV);
+ if (rv < 0)
+ warn("retrieving process accounting report");
+ }
+ DB_CLOSE(output_pacct_db);
+}
+
+static int
+check_junk(const struct cmdinfo *cip)
+{
+ char *cp;
+ size_t len;
+
+ fprintf(stderr, "%s (%ju) -- ", cip->ci_comm, (uintmax_t)cip->ci_calls);
+ cp = fgetln(stdin, &len);
+
+ return (cp && (cp[0] == 'y' || cp[0] == 'Y')) ? 1 : 0;
+}
+
+static void
+add_ci(const struct cmdinfo *fromcip, struct cmdinfo *tocip)
+{
+ tocip->ci_calls += fromcip->ci_calls;
+ tocip->ci_etime += fromcip->ci_etime;
+ tocip->ci_utime += fromcip->ci_utime;
+ tocip->ci_stime += fromcip->ci_stime;
+ tocip->ci_mem += fromcip->ci_mem;
+ tocip->ci_io += fromcip->ci_io;
+}
+
+static void
+print_ci(const struct cmdinfo *cip, const struct cmdinfo *totalcip)
+{
+ double t, c;
+ int uflow;
+
+ c = cip->ci_calls ? cip->ci_calls : 1;
+ t = (cip->ci_utime + cip->ci_stime) / 1000000;
+ if (t < 0.01) {
+ t = 0.01;
+ uflow = 1;
+ } else
+ uflow = 0;
+
+ printf("%8ju ", (uintmax_t)cip->ci_calls);
+ if (cflag) {
+ if (cip != totalcip)
+ printf(" %4.1f%% ", cip->ci_calls /
+ (double)totalcip->ci_calls * 100);
+ else
+ printf(" %4s ", "");
+ }
+
+ if (jflag)
+ printf("%11.3fre ", cip->ci_etime / (1000000 * c));
+ else
+ printf("%11.3fre ", cip->ci_etime / (60.0 * 1000000));
+ if (cflag) {
+ if (cip != totalcip)
+ printf(" %4.1f%% ", cip->ci_etime /
+ totalcip->ci_etime * 100);
+ else
+ printf(" %4s ", "");
+ }
+
+ if (!lflag) {
+ if (jflag)
+ printf("%11.3fcp ", t / (double) cip->ci_calls);
+ else
+ printf("%11.2fcp ", t / 60.0);
+ if (cflag) {
+ if (cip != totalcip)
+ printf(" %4.1f%% ",
+ (cip->ci_utime + cip->ci_stime) /
+ (totalcip->ci_utime + totalcip->ci_stime) *
+ 100);
+ else
+ printf(" %4s ", "");
+ }
+ } else {
+ if (jflag)
+ printf("%11.3fu ", cip->ci_utime / (1000000 * c));
+ else
+ printf("%11.2fu ", cip->ci_utime / (60.0 * 1000000));
+ if (cflag) {
+ if (cip != totalcip)
+ printf(" %4.1f%% ", cip->ci_utime /
+ (double)totalcip->ci_utime * 100);
+ else
+ printf(" %4s ", "");
+ }
+ if (jflag)
+ printf("%11.3fs ", cip->ci_stime / (1000000 * c));
+ else
+ printf("%11.2fs ", cip->ci_stime / (60.0 * 1000000));
+ if (cflag) {
+ if (cip != totalcip)
+ printf(" %4.1f%% ", cip->ci_stime /
+ (double)totalcip->ci_stime * 100);
+ else
+ printf(" %4s ", "");
+ }
+ }
+
+ if (tflag) {
+ if (!uflow)
+ printf("%8.2fre/cp ",
+ cip->ci_etime /
+ (cip->ci_utime + cip->ci_stime));
+ else
+ printf("*ignore* ");
+ }
+
+ if (Dflag)
+ printf("%10.0fio ", cip->ci_io);
+ else
+ printf("%8.0favio ", cip->ci_io / c);
+
+ if (Kflag)
+ printf("%10.0fk*sec ", cip->ci_mem);
+ else
+ printf("%8.0fk ", cip->ci_mem / t);
+
+ printf(" %s\n", cip->ci_comm);
+}
diff --git a/usr.sbin/sa/sa.8 b/usr.sbin/sa/sa.8
new file mode 100644
index 0000000..1b66ac2
--- /dev/null
+++ b/usr.sbin/sa/sa.8
@@ -0,0 +1,262 @@
+.\"
+.\" 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.
+.\" 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 May 18, 2007
+.Dt SA 8
+.Os
+.Sh NAME
+.Nm sa
+.Nd print system accounting statistics
+.Sh SYNOPSIS
+.Nm
+.Op Fl abcdDfijkKlmnqrstu
+.Op Fl P Ar file
+.Op Fl U Ar file
+.Op Fl v Ar cutoff
+.Op Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility reports on, cleans up,
+and generally maintains system
+accounting files.
+.Pp
+The
+.Nm
+utility is able to condense the information in
+.Pa /var/account/acct
+into the summary files
+.Pa /var/account/savacct
+and
+.Pa /var/account/usracct ,
+which contain system statistics according
+to command name and login id, respectively.
+This condensation is desirable because on a
+large system,
+.Pa /var/account/acct
+can grow by hundreds of blocks per day.
+The summary files are normally read before
+the accounting file, so that reports include
+all available information.
+.Pp
+If file names are supplied, they are read instead of
+.Pa /var/account/acct .
+After each file is read, if the summary
+files are being updated, an updated summary will
+be saved to disk.
+Only one report is printed,
+after the last file is processed.
+.Pp
+The labels used in the output indicate the following, except
+where otherwise specified by individual options:
+.Bl -tag -width k*sec
+.It Dv avio
+Average number of I/O operations per execution
+.It Dv cp
+Sum of user and system time, in minutes
+.It Dv cpu
+Same as
+.Dv cp
+.It Dv k
+CPU-time averaged core usage, in 1k units
+.It Dv k*sec
+CPU storage integral, in 1k-core seconds
+.It Dv re
+Real time, in minutes
+.It Dv s
+System time, in minutes
+.It Dv tio
+Total number of I/O operations
+.It Dv u
+User time, in minutes
+.El
+.Pp
+The options to
+.Nm
+are:
+.Bl -tag -width Ds
+.It Fl a
+List all command names, including those containing unprintable
+characters and those used only once.
+By default,
+.Nm
+places all names containing unprintable characters and
+those used only once under the name ``***other''.
+.It Fl b
+If printing command statistics, sort output by the sum of user and system
+time divided by number of calls.
+.It Fl c
+In addition to the number of calls and the user, system and real times
+for each command, print their percentage of the total over all commands.
+.It Fl d
+If printing command statistics, sort by the average number of disk
+I/O operations.
+If printing user statistics, print the average number of
+disk I/O operations per user.
+.It Fl D
+If printing command statistics, sort and print by the total number
+of disk I/O operations.
+.It Fl f
+Force no interactive threshold comparison with the
+.Fl v
+option.
+.It Fl i
+Do not read in the summary files.
+.It Fl j
+Instead of the total minutes per category, give seconds per call.
+.It Fl k
+If printing command statistics, sort by the cpu-time average memory
+usage.
+If printing user statistics, print the cpu-time average
+memory usage.
+.It Fl K
+If printing command statistics, print and sort by the cpu-storage integral.
+.It Fl l
+Separate system and user time; normally they are combined.
+.It Fl m
+Print per-user statistics rather than per-command statistics.
+.It Fl n
+Sort by number of calls.
+.It Fl P Ar file
+Use the specified
+.Ar file
+for accessing the per-command accounting summary database,
+instead of the default
+.Pa /var/account/savacct .
+.It Fl q
+Create no output other than error messages.
+.It Fl r
+Reverse order of sort.
+.It Fl s
+Truncate the accounting files when done and merge their data
+into the summary files.
+.It Fl t
+For each command, report the ratio of real time to the sum
+of user and system cpu times.
+If the cpu time is too small to report, ``*ignore*'' appears in
+this field.
+.It Fl U Ar file
+Use the specified
+.Ar file
+for accessing the per-user accounting summary database,
+instead of the default
+.Pa /var/account/usracct .
+.It Fl u
+Superseding all other flags, for each entry
+in the accounting file, print the user ID, total seconds of cpu usage,
+total memory usage, number of I/O operations performed, and
+command name.
+.It Fl v Ar cutoff
+For each command used
+.Ar cutoff
+times or fewer, print the command name and await a reply
+from the terminal.
+If the reply begins with ``y'', add
+the command to the category ``**junk**''.
+This flag is
+used to strip garbage from the report.
+.El
+.Pp
+By default, per-command statistics will be printed.
+The number of
+calls, the total elapsed time in minutes, total cpu and user time
+in minutes, average number of I/O operations, and CPU-time
+averaged core usage will be printed.
+If the
+.Fl m
+option is specified, per-user statistics will be printed, including
+the user name, the number of commands invoked, total cpu time used
+(in minutes), total number of I/O operations, and CPU storage integral
+for each user.
+If the
+.Fl u
+option is specified, the uid, user and system time (in seconds),
+CPU storage integral, I/O usage, and command name will be printed
+for each entry in the accounting data file.
+.Pp
+If the
+.Fl u
+flag is specified, all flags other than
+.Fl q
+are ignored.
+If the
+.Fl m
+flag is specified, only the
+.Fl b ,
+.Fl d ,
+.Fl i ,
+.Fl k ,
+.Fl q ,
+and
+.Fl s
+flags are honored.
+.Sh FILES
+.Bl -tag -width /var/account/usracct -compact
+.It Pa /var/account/acct
+raw accounting data file
+.It Pa /var/account/savacct
+per-command accounting summary database
+.It Pa /var/account/usracct
+per-user accounting summary database
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr lastcomm 1 ,
+.Xr acct 5 ,
+.Xr ac 8 ,
+.Xr accton 8
+.Sh AUTHORS
+.An Chris G. Demetriou Aq Mt cgd@postgres.berkeley.edu
+.Sh CAVEATS
+While the behavior of the options in this version of
+.Nm
+was modeled after the original version, there are some intentional
+differences and undoubtedly some unintentional ones as well.
+In
+particular, the
+.Fl q
+option has been added, and the
+.Fl m
+option now understands more options than it used to.
+.Pp
+The formats of the summary files created by this version of
+.Nm
+are very different from the those used by the original version.
+This is not considered a problem, however, because the accounting record
+format has changed as well (since user ids are now 32 bits).
+.Sh BUGS
+The number of options to this program is absurd, especially considering
+that there is not much logic behind their lettering.
+.Pp
+The field labels should be more consistent.
+.Pp
+The VM system does not record the CPU storage integral.
diff --git a/usr.sbin/sa/tests/Makefile b/usr.sbin/sa/tests/Makefile
new file mode 100644
index 0000000..3c74ba9
--- /dev/null
+++ b/usr.sbin/sa/tests/Makefile
@@ -0,0 +1,31 @@
+# $FreeBSD$
+
+TAP_TESTS_SH= legacy_test
+
+FILESDIR= ${TESTSDIR}
+FILES= v1-amd64-sav.in
+FILES+= v1-amd64-sav.out
+FILES+= v1-amd64-u.out
+FILES+= v1-amd64-usr.in
+FILES+= v1-amd64-usr.out
+FILES+= v1-i386-sav.in
+FILES+= v1-i386-sav.out
+FILES+= v1-i386-u.out
+FILES+= v1-i386-usr.in
+FILES+= v1-i386-usr.out
+FILES+= v1-sparc64-sav.in
+FILES+= v1-sparc64-sav.out
+FILES+= v1-sparc64-u.out
+FILES+= v1-sparc64-usr.in
+FILES+= v1-sparc64-usr.out
+FILES+= v2-amd64-sav.in
+FILES+= v2-amd64-u.out
+FILES+= v2-amd64-usr.in
+FILES+= v2-i386-sav.in
+FILES+= v2-i386-u.out
+FILES+= v2-i386-usr.in
+FILES+= v2-sparc64-sav.in
+FILES+= v2-sparc64-u.out
+FILES+= v2-sparc64-usr.in
+
+.include <bsd.test.mk>
diff --git a/usr.sbin/sa/tests/legacy_test.sh b/usr.sbin/sa/tests/legacy_test.sh
new file mode 100644
index 0000000..d0d8f42
--- /dev/null
+++ b/usr.sbin/sa/tests/legacy_test.sh
@@ -0,0 +1,78 @@
+#!/bin/sh
+#
+# $FreeBSD$
+#
+
+DIR=`dirname $0`
+LCDIR=`dirname $0`/../../usr.bin/lastcomm
+ARCH=`uname -m`
+
+collapse_whitespace()
+{
+ sed -E 's,[ ]+, ,g'
+}
+
+check()
+{
+ NUM=$1
+ shift
+ collapse_whitespace | \
+ if diff -q - $1
+ then
+ echo "ok $NUM"
+ else
+ echo "not ok $NUM"
+ fi
+}
+
+install -c -m 644 $LCDIR/v1-$ARCH-acct.in v1-$ARCH-acct.in
+install -c -m 644 $LCDIR/v2-$ARCH-acct.in v2-$ARCH-acct.in
+
+echo 1..13
+
+# Command listings of the two acct versions
+sa -u v1-$ARCH-acct.in | check 1 $DIR/v1-$ARCH-u.out
+sa -u v2-$ARCH-acct.in | check 2 $DIR/v2-$ARCH-u.out
+
+# Plain summaries of user/process
+sa -i v1-$ARCH-acct.in | check 3 $DIR/v1-$ARCH-sav.out
+sa -im v1-$ARCH-acct.in | check 4 $DIR/v1-$ARCH-usr.out
+
+# Backward compatibility of v1 summary files
+sa -P $DIR/v1-$ARCH-sav.in -U $DIR/v1-$ARCH-usr.in /dev/null |
+ check 5 $DIR/v1-$ARCH-sav.out
+sa -m -P $DIR/v1-$ARCH-sav.in -U $DIR/v1-$ARCH-usr.in /dev/null |
+ check 6 $DIR/v1-$ARCH-usr.out
+
+# Convert old summary format to new
+install -c -m 644 $DIR/v1-$ARCH-sav.in v2c-$ARCH-sav.in
+install -c -m 644 $DIR/v1-$ARCH-usr.in v2c-$ARCH-usr.in
+sa -s -P v2c-$ARCH-sav.in -U v2c-$ARCH-usr.in /dev/null >/dev/null
+sa -P v2c-$ARCH-sav.in -U v2c-$ARCH-usr.in /dev/null |
+ check 7 $DIR/v1-$ARCH-sav.out
+sa -m -P v2c-$ARCH-sav.in -U v2c-$ARCH-usr.in /dev/null |
+ check 8 $DIR/v1-$ARCH-usr.out
+
+# Reading v2 summary files
+sa -P $DIR/v2-$ARCH-sav.in -U $DIR/v2-$ARCH-usr.in /dev/null |
+ check 9 $DIR/v1-$ARCH-sav.out
+sa -m -P $DIR/v2-$ARCH-sav.in -U $DIR/v2-$ARCH-usr.in /dev/null |
+ check 10 $DIR/v1-$ARCH-usr.out
+
+# Summarize
+sa -is -P v2c-$ARCH-sav.in -U v2c-$ARCH-usr.in v1-$ARCH-acct.in >/dev/null
+sa -P v2c-$ARCH-sav.in -U v2c-$ARCH-usr.in /dev/null |
+ check 11 $DIR/v1-$ARCH-sav.out
+sa -m -P v2c-$ARCH-sav.in -U v2c-$ARCH-usr.in /dev/null |
+ check 12 $DIR/v1-$ARCH-usr.out
+
+# Accumulate
+install -c -m 644 $LCDIR/v1-$ARCH-acct.in v1-$ARCH-acct.in
+sa -is -P v2c-$ARCH-sav.in -U v2c-$ARCH-usr.in v1-$ARCH-acct.in >/dev/null
+install -c -m 644 $LCDIR/v1-$ARCH-acct.in v1-$ARCH-acct.in
+sa -s -P v2c-$ARCH-sav.in -U v2c-$ARCH-usr.in v1-$ARCH-acct.in \
+ | collapse_whitespace >double
+cp $LCDIR/v1-$ARCH-acct.in v1-$ARCH-acct.in
+sa -i v1-$ARCH-acct.in v1-$ARCH-acct.in | check 13 double
+
+exit 0
diff --git a/usr.sbin/sa/tests/prime.sh b/usr.sbin/sa/tests/prime.sh
new file mode 100755
index 0000000..6992f45
--- /dev/null
+++ b/usr.sbin/sa/tests/prime.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+#
+# Configure and run this script to create the files for regression testing
+# for a new architecture/configuration.
+#
+# $FreeBSD$
+#
+
+TZ=UTC; export TZ
+
+# Set this to the path of the current sa command
+SANEW=/usr/sbin/sa
+
+# Set this to the path of the sa as of 2007-05-19.
+# You can obtain it with a command like:
+# cvs co -D '2007-05-19' sa
+# To compile it you will also need sys/acct.h from that date
+# and sa configured to use that file, instead of the current version.
+SAOLD=/$HOME/src/sa/sa
+
+# Machine architecture
+ARCH=`uname -m`
+
+# Location of lastcomm regression files
+LCDIR=../../usr.bin/lastcomm
+
+$SANEW -u $LCDIR/v1-$ARCH-acct.in >v1-$ARCH-u.out
+$SANEW -u $LCDIR/v2-$ARCH-acct.in >v2-$ARCH-u.out
+$SANEW -i $LCDIR/v1-$ARCH-acct.in >v1-$ARCH-sav.out
+$SANEW -im $LCDIR/v1-$ARCH-acct.in >v1-$ARCH-usr.out
+cp $LCDIR/v1-$ARCH-acct.in acct.in
+rm -f v1-$ARCH-sav.in v1-$ARCH-usr.in
+$SAOLD -s -P v1-$ARCH-sav.in -U v1-$ARCH-usr.in acct.in >/dev/null
+cp $LCDIR/v1-$ARCH-acct.in acct.in
+rm -f v2-$ARCH-sav.in v2-$ARCH-usr.in
+$SANEW -s -P v2-$ARCH-sav.in -U v2-$ARCH-usr.in acct.in >/dev/null
+rm acct.in
diff --git a/usr.sbin/sa/tests/v1-amd64-sav.in b/usr.sbin/sa/tests/v1-amd64-sav.in
new file mode 100644
index 0000000..adc38dd
--- /dev/null
+++ b/usr.sbin/sa/tests/v1-amd64-sav.in
Binary files differ
diff --git a/usr.sbin/sa/tests/v1-amd64-sav.out b/usr.sbin/sa/tests/v1-amd64-sav.out
new file mode 100644
index 0000000..a2289ee
--- /dev/null
+++ b/usr.sbin/sa/tests/v1-amd64-sav.out
@@ -0,0 +1,5 @@
+ 28 0.282re 0.06cp 40avio 957k
+ 13 0.141re 0.06cp 87avio 903k ***other
+ 3 0.000re 0.00cp 0avio 0k ln
+ 9 0.141re 0.00cp 0avio 19700k time
+ 3 0.000re 0.00cp 0avio 0k time*
diff --git a/usr.sbin/sa/tests/v1-amd64-u.out b/usr.sbin/sa/tests/v1-amd64-u.out
new file mode 100644
index 0000000..4c13b76
--- /dev/null
+++ b/usr.sbin/sa/tests/v1-amd64-u.out
@@ -0,0 +1,28 @@
+ 0 0.000 cpu 0k mem 0 io accton
+ 0 0.172 cpu 41k mem 0 io awk
+ 0 0.000 cpu 140k mem 0 io time
+ 0 3.031 cpu 45k mem 1 io egrep
+ 0 0.000 cpu 0k mem 0 io time
+ 0 0.250 cpu 42k mem 1087 io find
+ 0 0.000 cpu 0k mem 0 io time
+ 0 0.000 cpu 0k mem 0 io sleep
+ 0 0.000 cpu 57k mem 0 io time
+ 0 0.016 cpu 31k mem 16 io dd
+ 0 0.000 cpu 0k mem 0 io time
+ 0 0.203 cpu 1976k mem 0 io diff
+ 0 0.000 cpu 0k mem 0 io time
+ 0 0.000 cpu 0k mem 0 io ln
+ 0 0.000 cpu 0k mem 0 io time*
+ 0 0.000 cpu 0k mem 0 io time
+ 0 0.000 cpu 0k mem 0 io ln
+ 0 0.000 cpu 0k mem 0 io time*
+ 0 0.000 cpu 0k mem 0 io time
+ 0 0.000 cpu 0k mem 0 io ln
+ 0 0.000 cpu 0k mem 0 io time*
+ 0 0.000 cpu 0k mem 0 io time
+ 0 0.000 cpu 713k mem 0 io cc1
+ 0 0.000 cpu 0k mem 0 io as
+ 0 0.000 cpu 228k mem 1 io ld
+ 0 0.000 cpu 196k mem 9 io cc
+ 0 0.000 cpu 45k mem 7 io core
+ 0 0.000 cpu 0k mem 9 io core*
diff --git a/usr.sbin/sa/tests/v1-amd64-usr.in b/usr.sbin/sa/tests/v1-amd64-usr.in
new file mode 100644
index 0000000..6896d32
--- /dev/null
+++ b/usr.sbin/sa/tests/v1-amd64-usr.in
Binary files differ
diff --git a/usr.sbin/sa/tests/v1-amd64-usr.out b/usr.sbin/sa/tests/v1-amd64-usr.out
new file mode 100644
index 0000000..2282808
--- /dev/null
+++ b/usr.sbin/sa/tests/v1-amd64-usr.out
@@ -0,0 +1 @@
+root 28 0.06cpu 1130tio 3514k*sec
diff --git a/usr.sbin/sa/tests/v1-i386-sav.in b/usr.sbin/sa/tests/v1-i386-sav.in
new file mode 100644
index 0000000..4ed7aef
--- /dev/null
+++ b/usr.sbin/sa/tests/v1-i386-sav.in
Binary files differ
diff --git a/usr.sbin/sa/tests/v1-i386-sav.out b/usr.sbin/sa/tests/v1-i386-sav.out
new file mode 100644
index 0000000..a8d68b1
--- /dev/null
+++ b/usr.sbin/sa/tests/v1-i386-sav.out
@@ -0,0 +1,5 @@
+ 28 0.425re 0.10cp 143avio 477k
+ 14 0.220re 0.10cp 286avio 411k ***other
+ 2 0.000re 0.00cp 0avio 0k 1234567890123456
+ 3 0.000re 0.00cp 0avio 0k ln
+ 9 0.205re 0.00cp 0avio 40400k time
diff --git a/usr.sbin/sa/tests/v1-i386-u.out b/usr.sbin/sa/tests/v1-i386-u.out
new file mode 100644
index 0000000..17c28be
--- /dev/null
+++ b/usr.sbin/sa/tests/v1-i386-u.out
@@ -0,0 +1,28 @@
+ 0 0.000 cpu 264k mem 0 io accton
+ 0 0.453 cpu 41k mem 0 io awk
+ 0 0.000 cpu 0k mem 0 io time
+ 0 4.984 cpu 41k mem 28 io egrep
+ 0 0.000 cpu 140k mem 0 io time
+ 0 0.266 cpu 36k mem 3921 io find
+ 0 0.000 cpu 0k mem 0 io time
+ 0 0.000 cpu 0k mem 0 io sleep
+ 0 0.000 cpu 0k mem 0 io time
+ 0 0.016 cpu 25k mem 16 io dd
+ 0 0.000 cpu 0k mem 0 io time
+ 0 0.312 cpu 1361k mem 0 io diff
+ 0 0.000 cpu 0k mem 0 io time
+ 0 0.000 cpu 0k mem 0 io ln
+ 0 0.000 cpu 0k mem 0 io 123456789012345
+ 0 0.000 cpu 0k mem 0 io time
+ 0 0.000 cpu 0k mem 0 io ln
+ 0 0.000 cpu 0k mem 0 io 1234567890123456
+ 0 0.000 cpu 264k mem 0 io time
+ 0 0.000 cpu 0k mem 0 io ln
+ 0 0.000 cpu 0k mem 0 io 1234567890123456
+ 0 0.000 cpu 0k mem 0 io time
+ 0 0.016 cpu 233k mem 1 io cc1
+ 0 0.000 cpu 482k mem 1 io as
+ 0 0.000 cpu 0k mem 11 io ld
+ 0 0.000 cpu 0k mem 6 io cc
+ 0 0.000 cpu 0k mem 12 io core
+ 0 0.000 cpu 0k mem 7 io core*
diff --git a/usr.sbin/sa/tests/v1-i386-usr.in b/usr.sbin/sa/tests/v1-i386-usr.in
new file mode 100644
index 0000000..791b6ed
--- /dev/null
+++ b/usr.sbin/sa/tests/v1-i386-usr.in
Binary files differ
diff --git a/usr.sbin/sa/tests/v1-i386-usr.out b/usr.sbin/sa/tests/v1-i386-usr.out
new file mode 100644
index 0000000..cf9e7bd
--- /dev/null
+++ b/usr.sbin/sa/tests/v1-i386-usr.out
@@ -0,0 +1 @@
+root 28 0.10cpu 4003tio 2887k*sec
diff --git a/usr.sbin/sa/tests/v1-sparc64-sav.in b/usr.sbin/sa/tests/v1-sparc64-sav.in
new file mode 100644
index 0000000..d6911cf
--- /dev/null
+++ b/usr.sbin/sa/tests/v1-sparc64-sav.in
Binary files differ
diff --git a/usr.sbin/sa/tests/v1-sparc64-sav.out b/usr.sbin/sa/tests/v1-sparc64-sav.out
new file mode 100644
index 0000000..1905420
--- /dev/null
+++ b/usr.sbin/sa/tests/v1-sparc64-sav.out
@@ -0,0 +1,5 @@
+ 28 1.839re 0.66cp 161avio 252k
+ 14 0.930re 0.66cp 322avio 197k ***other
+ 9 0.908re 0.00cp 0avio 10190k time
+ 3 0.001re 0.00cp 0avio 16256k ln
+ 2 0.001re 0.00cp 0avio 27900k 1234567890123456
diff --git a/usr.sbin/sa/tests/v1-sparc64-u.out b/usr.sbin/sa/tests/v1-sparc64-u.out
new file mode 100644
index 0000000..d0ecb51
--- /dev/null
+++ b/usr.sbin/sa/tests/v1-sparc64-u.out
@@ -0,0 +1,28 @@
+ 0 0.016 cpu 162k mem 0 io accton
+ 0 1.609 cpu 273k mem 0 io awk
+ 0 0.016 cpu 174k mem 0 io time
+ 0 29.750 cpu 233k mem 26 io egrep
+ 0 0.016 cpu 161k mem 0 io time
+ 0 5.516 cpu 184k mem 4437 io find
+ 0 0.016 cpu 156k mem 0 io time
+ 0 0.000 cpu 136k mem 0 io sleep
+ 0 0.016 cpu 152k mem 0 io time
+ 0 0.562 cpu 161k mem 16 io dd
+ 0 0.016 cpu 156k mem 0 io time
+ 0 1.641 cpu 193k mem 0 io diff
+ 0 0.016 cpu 156k mem 0 io time
+ 0 0.000 cpu 169k mem 0 io ln
+ 0 0.000 cpu 144k mem 0 io 123456789012345
+ 0 0.016 cpu 166k mem 0 io time
+ 0 0.016 cpu 170k mem 0 io ln
+ 0 0.000 cpu 144k mem 0 io 1234567890123456
+ 0 0.016 cpu 156k mem 0 io time
+ 0 0.016 cpu 169k mem 0 io ln
+ 0 0.000 cpu 135k mem 0 io 1234567890123456
+ 0 0.016 cpu 156k mem 0 io time
+ 0 0.094 cpu 3462k mem 2 io cc1
+ 0 0.016 cpu 1047k mem 1 io as
+ 0 0.094 cpu 1118k mem 10 io ld
+ 0 0.016 cpu 361k mem 5 io cc
+ 0 0.031 cpu 165k mem 7 io core
+ 0 0.000 cpu 112k mem 4 io core*
diff --git a/usr.sbin/sa/tests/v1-sparc64-usr.in b/usr.sbin/sa/tests/v1-sparc64-usr.in
new file mode 100644
index 0000000..08d795b
--- /dev/null
+++ b/usr.sbin/sa/tests/v1-sparc64-usr.in
Binary files differ
diff --git a/usr.sbin/sa/tests/v1-sparc64-usr.out b/usr.sbin/sa/tests/v1-sparc64-usr.out
new file mode 100644
index 0000000..cab001f
--- /dev/null
+++ b/usr.sbin/sa/tests/v1-sparc64-usr.out
@@ -0,0 +1 @@
+root 28 0.66cpu 4508tio 9971k*sec
diff --git a/usr.sbin/sa/tests/v2-amd64-sav.in b/usr.sbin/sa/tests/v2-amd64-sav.in
new file mode 100644
index 0000000..24f863b
--- /dev/null
+++ b/usr.sbin/sa/tests/v2-amd64-sav.in
Binary files differ
diff --git a/usr.sbin/sa/tests/v2-amd64-u.out b/usr.sbin/sa/tests/v2-amd64-u.out
new file mode 100644
index 0000000..4196f4a
--- /dev/null
+++ b/usr.sbin/sa/tests/v2-amd64-u.out
@@ -0,0 +1,28 @@
+ 0 0.002 cpu 68k mem 0 io accton
+ 0 0.163 cpu 35k mem 0 io awk
+ 0 0.003 cpu 0k mem 0 io time
+ 0 3.247 cpu 29k mem 26 io egrep
+ 0 0.003 cpu 93k mem 0 io time
+ 0 0.822 cpu 19k mem 4472 io find
+ 0 0.003 cpu 0k mem 0 io time
+ 0 0.002 cpu 0k mem 0 io sleep
+ 0 0.004 cpu 88k mem 0 io time
+ 0 0.076 cpu 18k mem 16 io dd
+ 0 0.003 cpu 0k mem 0 io time
+ 0 0.223 cpu 24k mem 0 io diff
+ 0 0.003 cpu 0k mem 0 io time
+ 0 0.003 cpu 0k mem 0 io ln
+ 0 0.002 cpu 0k mem 0 io 123456789012345
+ 0 0.003 cpu 0k mem 0 io time
+ 0 0.002 cpu 0k mem 0 io ln
+ 0 0.002 cpu 0k mem 0 io 1234567890123456
+ 0 0.003 cpu 0k mem 0 io time
+ 0 0.002 cpu 0k mem 0 io ln
+ 0 0.002 cpu 70k mem 0 io 1234567890123456
+ 0 0.003 cpu 0k mem 0 io time
+ 0 0.024 cpu 661k mem 2 io cc1
+ 0 0.005 cpu 0k mem 1 io as
+ 0 0.014 cpu 192k mem 9 io ld
+ 0 0.005 cpu 162k mem 5 io cc
+ 0 0.005 cpu 0k mem 13 io core
+ 0 0.002 cpu 0k mem 7 io core*
diff --git a/usr.sbin/sa/tests/v2-amd64-usr.in b/usr.sbin/sa/tests/v2-amd64-usr.in
new file mode 100644
index 0000000..4c3b136
--- /dev/null
+++ b/usr.sbin/sa/tests/v2-amd64-usr.in
Binary files differ
diff --git a/usr.sbin/sa/tests/v2-i386-sav.in b/usr.sbin/sa/tests/v2-i386-sav.in
new file mode 100644
index 0000000..d3172db
--- /dev/null
+++ b/usr.sbin/sa/tests/v2-i386-sav.in
Binary files differ
diff --git a/usr.sbin/sa/tests/v2-i386-u.out b/usr.sbin/sa/tests/v2-i386-u.out
new file mode 100644
index 0000000..b553eb8
--- /dev/null
+++ b/usr.sbin/sa/tests/v2-i386-u.out
@@ -0,0 +1,28 @@
+ 0 0.001 cpu 0k mem 0 io accton
+ 0 0.448 cpu 112k mem 0 io awk
+ 0 0.001 cpu 0k mem 0 io time
+ 0 6.680 cpu 110k mem 0 io egrep
+ 0 0.001 cpu 0k mem 0 io time
+ 0 0.248 cpu 108k mem 0 io find
+ 0 0.001 cpu 0k mem 0 io time
+ 0 0.001 cpu 0k mem 0 io sleep
+ 0 0.001 cpu 0k mem 0 io time
+ 0 0.025 cpu 103k mem 16 io dd
+ 0 0.001 cpu 0k mem 0 io time
+ 0 0.423 cpu 110k mem 0 io diff
+ 0 0.001 cpu 0k mem 0 io time
+ 0 0.001 cpu 0k mem 0 io ln
+ 0 0.001 cpu 0k mem 0 io 123456789012345
+ 0 0.001 cpu 0k mem 0 io time
+ 0 0.001 cpu 0k mem 0 io ln
+ 0 0.001 cpu 0k mem 0 io 1234567890123456
+ 0 0.001 cpu 140k mem 0 io time
+ 0 0.001 cpu 0k mem 0 io ln
+ 0 0.001 cpu 0k mem 0 io 1234567890123456
+ 0 0.001 cpu 0k mem 0 io time
+ 0 0.016 cpu 620k mem 0 io cc1
+ 0 0.002 cpu 432k mem 0 io as
+ 0 0.028 cpu 96k mem 0 io ld
+ 0 0.002 cpu 0k mem 0 io cc
+ 0 0.002 cpu 68k mem 12 io core
+ 0 0.000 cpu 0k mem 7 io core*
diff --git a/usr.sbin/sa/tests/v2-i386-usr.in b/usr.sbin/sa/tests/v2-i386-usr.in
new file mode 100644
index 0000000..465fdb4
--- /dev/null
+++ b/usr.sbin/sa/tests/v2-i386-usr.in
Binary files differ
diff --git a/usr.sbin/sa/tests/v2-sparc64-sav.in b/usr.sbin/sa/tests/v2-sparc64-sav.in
new file mode 100644
index 0000000..b6d836a
--- /dev/null
+++ b/usr.sbin/sa/tests/v2-sparc64-sav.in
Binary files differ
diff --git a/usr.sbin/sa/tests/v2-sparc64-u.out b/usr.sbin/sa/tests/v2-sparc64-u.out
new file mode 100644
index 0000000..9339238
--- /dev/null
+++ b/usr.sbin/sa/tests/v2-sparc64-u.out
@@ -0,0 +1,36 @@
+ 0 0.019 cpu 163k mem 0 io accton
+ 0 1.644 cpu 272k mem 0 io awk
+ 0 0.029 cpu 175k mem 0 io time
+ 0 0.019 cpu 163k mem 0 io sleep
+ 0 0.032 cpu 214k mem 35 io fsck_ufs
+ 0 0.028 cpu 213k mem 10 io fsck_ufs
+ 0 0.028 cpu 189k mem 8 io fsck_ufs
+ 0 0.028 cpu 196k mem 7 io fsck_ufs
+ 0 0.055 cpu 168k mem 0 io fsck
+ 0 0.008 cpu 204k mem 0 io sh*
+ 0 0.023 cpu 179k mem 0 io logger
+ 0 26.715 cpu 233k mem 25 io egrep
+ 0 0.029 cpu 162k mem 0 io time
+ 0 5.703 cpu 184k mem 4444 io find
+ 0 0.029 cpu 166k mem 0 io time
+ 0 0.018 cpu 136k mem 0 io sleep
+ 0 0.028 cpu 157k mem 0 io time
+ 0 0.588 cpu 160k mem 16 io dd
+ 0 0.027 cpu 162k mem 0 io time
+ 0 1.636 cpu 193k mem 0 io diff
+ 0 0.029 cpu 172k mem 0 io time
+ 0 0.020 cpu 170k mem 0 io ln
+ 0 0.018 cpu 135k mem 0 io 123456789012345
+ 0 0.029 cpu 167k mem 0 io time
+ 0 0.019 cpu 163k mem 0 io ln
+ 0 0.017 cpu 135k mem 0 io 1234567890123456
+ 0 0.029 cpu 157k mem 0 io time
+ 0 0.019 cpu 163k mem 0 io ln
+ 0 0.017 cpu 135k mem 0 io 1234567890123456
+ 0 0.029 cpu 162k mem 0 io time
+ 0 0.105 cpu 3358k mem 2 io cc1
+ 0 0.030 cpu 996k mem 1 io as
+ 0 0.186 cpu 1114k mem 11 io ld
+ 0 0.033 cpu 386k mem 6 io cc
+ 0 0.037 cpu 158k mem 12 io core
+ 0 0.010 cpu 129k mem 7 io core*
diff --git a/usr.sbin/sa/tests/v2-sparc64-usr.in b/usr.sbin/sa/tests/v2-sparc64-usr.in
new file mode 100644
index 0000000..01a1bdc
--- /dev/null
+++ b/usr.sbin/sa/tests/v2-sparc64-usr.in
Binary files differ
diff --git a/usr.sbin/sa/usrdb.c b/usr.sbin/sa/usrdb.c
new file mode 100644
index 0000000..50778ef
--- /dev/null
+++ b/usr.sbin/sa/usrdb.c
@@ -0,0 +1,239 @@
+/*
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/acct.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "extern.h"
+#include "pathnames.h"
+
+static int uid_compare(const DBT *, const DBT *);
+
+static DB *usracct_db;
+
+/* Legacy format in AHZV1 units. */
+struct userinfov1 {
+ uid_t ui_uid; /* user id; for consistency */
+ u_quad_t ui_calls; /* number of invocations */
+ u_quad_t ui_utime; /* user time */
+ u_quad_t ui_stime; /* system time */
+ u_quad_t ui_mem; /* memory use */
+ u_quad_t ui_io; /* number of disk i/o ops */
+};
+
+/*
+ * Convert a v1 data record into the current version.
+ * Return 0 if OK, -1 on error, setting errno.
+ */
+static int
+v1_to_v2(DBT *key, DBT *data)
+{
+ struct userinfov1 uiv1;
+ static struct userinfo uiv2;
+ static uid_t uid;
+
+ if (key->size != sizeof(u_long) || data->size != sizeof(uiv1)) {
+ errno = EFTYPE;
+ return (-1);
+ }
+
+ /* Convert key. */
+ key->size = sizeof(uid_t);
+ uid = (uid_t)*(u_long *)(key->data);
+ key->data = &uid;
+
+ /* Convert data. */
+ memcpy(&uiv1, data->data, data->size);
+ memset(&uiv2, 0, sizeof(uiv2));
+ uiv2.ui_uid = uiv1.ui_uid;
+ uiv2.ui_calls = uiv1.ui_calls;
+ uiv2.ui_utime = ((double)uiv1.ui_utime / AHZV1) * 1000000;
+ uiv2.ui_stime = ((double)uiv1.ui_stime / AHZV1) * 1000000;
+ uiv2.ui_mem = uiv1.ui_mem;
+ uiv2.ui_io = uiv1.ui_io;
+ data->size = sizeof(uiv2);
+ data->data = &uiv2;
+
+ return (0);
+}
+
+/* Copy usrdb_file to in-memory usracct_db. */
+int
+usracct_init(void)
+{
+ BTREEINFO bti;
+
+ bzero(&bti, sizeof bti);
+ bti.compare = uid_compare;
+
+ return (db_copy_in(&usracct_db, usrdb_file, "user accounting",
+ &bti, v1_to_v2));
+}
+
+void
+usracct_destroy(void)
+{
+ db_destroy(usracct_db, "user accounting");
+}
+
+int
+usracct_add(const struct cmdinfo *ci)
+{
+ DBT key, data;
+ struct userinfo newui;
+ uid_t uid;
+ int rv;
+
+ uid = ci->ci_uid;
+ key.data = &uid;
+ key.size = sizeof uid;
+
+ rv = DB_GET(usracct_db, &key, &data, 0);
+ if (rv < 0) {
+ warn("get key %u from user accounting stats", uid);
+ return (-1);
+ } else if (rv == 0) { /* it's there; copy whole thing */
+ /* add the old data to the new data */
+ bcopy(data.data, &newui, data.size);
+ if (newui.ui_uid != uid) {
+ warnx("key %u != expected record number %u",
+ newui.ui_uid, uid);
+ warnx("inconsistent user accounting stats");
+ return (-1);
+ }
+ } else { /* it's not there; zero it and copy the key */
+ bzero(&newui, sizeof newui);
+ newui.ui_uid = ci->ci_uid;
+ }
+
+ newui.ui_calls += ci->ci_calls;
+ newui.ui_utime += ci->ci_utime;
+ newui.ui_stime += ci->ci_stime;
+ newui.ui_mem += ci->ci_mem;
+ newui.ui_io += ci->ci_io;
+
+ data.data = &newui;
+ data.size = sizeof newui;
+ rv = DB_PUT(usracct_db, &key, &data, 0);
+ if (rv < 0) {
+ warn("add key %u to user accounting stats", uid);
+ return (-1);
+ } else if (rv != 0) {
+ warnx("DB_PUT returned 1");
+ return (-1);
+ }
+
+ return (0);
+}
+
+/* Copy in-memory usracct_db to usrdb_file. */
+int
+usracct_update(void)
+{
+ BTREEINFO bti;
+
+ bzero(&bti, sizeof bti);
+ bti.compare = uid_compare;
+
+ return (db_copy_out(usracct_db, usrdb_file, "user accounting",
+ &bti));
+}
+
+void
+usracct_print(void)
+{
+ DBT key, data;
+ struct userinfo uistore, *ui = &uistore;
+ double t;
+ int rv;
+
+ rv = DB_SEQ(usracct_db, &key, &data, R_FIRST);
+ if (rv < 0)
+ warn("retrieving user accounting stats");
+
+ while (rv == 0) {
+ memcpy(ui, data.data, sizeof(struct userinfo));
+
+ printf("%-*s %9ju ", MAXLOGNAME - 1,
+ user_from_uid(ui->ui_uid, 0), (uintmax_t)ui->ui_calls);
+
+ t = (ui->ui_utime + ui->ui_stime) / 1000000;
+ if (t < 0.000001) /* kill divide by zero */
+ t = 0.000001;
+
+ printf("%12.2f%s ", t / 60.0, "cpu");
+
+ /* ui->ui_calls is always != 0 */
+ if (dflag)
+ printf("%12.0f%s",
+ ui->ui_io / ui->ui_calls, "avio");
+ else
+ printf("%12.0f%s", ui->ui_io, "tio");
+
+ /* t is always >= 0.000001; see above. */
+ if (kflag)
+ printf("%12.0f%s", ui->ui_mem / t, "k");
+ else
+ printf("%12.0f%s", ui->ui_mem, "k*sec");
+
+ printf("\n");
+
+ rv = DB_SEQ(usracct_db, &key, &data, R_NEXT);
+ if (rv < 0)
+ warn("retrieving user accounting stats");
+ }
+}
+
+static int
+uid_compare(const DBT *k1, const DBT *k2)
+{
+ uid_t d1, d2;
+
+ bcopy(k1->data, &d1, sizeof d1);
+ bcopy(k2->data, &d2, sizeof d2);
+
+ if (d1 < d2)
+ return -1;
+ else if (d1 == d2)
+ return 0;
+ else
+ return 1;
+}
diff --git a/usr.sbin/sendmail/Makefile b/usr.sbin/sendmail/Makefile
new file mode 100644
index 0000000..f9641c1
--- /dev/null
+++ b/usr.sbin/sendmail/Makefile
@@ -0,0 +1,68 @@
+# @(#)Makefile 8.8 (Berkeley) 3/28/97
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+SENDMAIL_DIR=${.CURDIR}/../../contrib/sendmail
+SMDIR= ${SENDMAIL_DIR}/src
+.PATH: ${SMDIR}
+
+BINDIR= ${LIBEXECDIR}/sendmail
+
+PROG= sendmail
+MAN= mailq.1 newaliases.1 aliases.5 sendmail.8
+MLINKS+=sendmail.8 hoststat.8
+MLINKS+=sendmail.8 purgestat.8
+SRCS= alias.c arpadate.c bf.c collect.c conf.c control.c \
+ convtime.c daemon.c deliver.c domain.c envelope.c err.c headers.c \
+ macro.c main.c map.c mci.c milter.c mime.c parseaddr.c queue.c \
+ ratectrl.c readcf.c recipient.c savemail.c sasl.c sfsasl.c \
+ shmticklib.c sm_resolve.c srvrsmtp.c stab.c stats.c sysexits.c \
+ timers.c tls.c trace.c udb.c usersmtp.c util.c version.c
+BINOWN= root
+BINGRP= smmsp
+.ifdef SENDMAIL_SET_USER_ID
+BINMODE=4555
+.else
+BINMODE=2555
+.endif
+
+# Define the database format to use for aliases et al.
+DBMDEF= -DNEWDB
+# If you don't want NIS alias/map support, comment out this line
+.if ${MK_NIS} != "no"
+NIS= -DNIS
+.endif
+# Map extensions
+MAPS= -DMAP_REGEX -DDNSMAP
+
+CFLAGS+= -I${SMDIR} -I${SENDMAIL_DIR}/include -I.
+CFLAGS+= ${DBMDEF} ${NIS} -DTCPWRAPPERS ${MAPS}
+
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+= -DNETINET6
+.endif
+
+WARNS?= 0
+
+LIBADD= util wrap sm smutil
+
+SRCS+= sm_os.h
+CLEANFILES+=sm_os.h
+
+.if ${MK_OPENSSL} != "no"
+# STARTTLS support
+CFLAGS+= -DSTARTTLS -D_FFR_TLS_1
+LIBADD+= ssl crypto
+.endif
+
+# User customizations to the sendmail build environment
+CFLAGS+=${SENDMAIL_CFLAGS}
+DPADD+=${SENDMAIL_DPADD}
+LDADD+=${SENDMAIL_LDADD}
+LDFLAGS+=${SENDMAIL_LDFLAGS}
+
+sm_os.h: ${SENDMAIL_DIR}/include/sm/os/sm_os_freebsd.h .NOMETA
+ ln -sf ${.ALLSRC} ${.TARGET}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/sendmail/Makefile.depend b/usr.sbin/sendmail/Makefile.depend
new file mode 100644
index 0000000..a272438
--- /dev/null
+++ b/usr.sbin/sendmail/Makefile.depend
@@ -0,0 +1,106 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libsm \
+ lib/libsmutil \
+ lib/libutil \
+ lib/libwrap \
+ secure/lib/libcrypto \
+ secure/lib/libssl \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+alias.o: sm_os.h
+alias.po: sm_os.h
+arpadate.o: sm_os.h
+arpadate.po: sm_os.h
+bf.o: sm_os.h
+bf.po: sm_os.h
+collect.o: sm_os.h
+collect.po: sm_os.h
+conf.o: sm_os.h
+conf.po: sm_os.h
+control.o: sm_os.h
+control.po: sm_os.h
+convtime.o: sm_os.h
+convtime.po: sm_os.h
+daemon.o: sm_os.h
+daemon.po: sm_os.h
+deliver.o: sm_os.h
+deliver.po: sm_os.h
+domain.o: sm_os.h
+domain.po: sm_os.h
+envelope.o: sm_os.h
+envelope.po: sm_os.h
+err.o: sm_os.h
+err.po: sm_os.h
+headers.o: sm_os.h
+headers.po: sm_os.h
+macro.o: sm_os.h
+macro.po: sm_os.h
+main.o: sm_os.h
+main.po: sm_os.h
+map.o: sm_os.h
+map.po: sm_os.h
+mci.o: sm_os.h
+mci.po: sm_os.h
+milter.o: sm_os.h
+milter.po: sm_os.h
+mime.o: sm_os.h
+mime.po: sm_os.h
+parseaddr.o: sm_os.h
+parseaddr.po: sm_os.h
+queue.o: sm_os.h
+queue.po: sm_os.h
+ratectrl.o: sm_os.h
+ratectrl.po: sm_os.h
+readcf.o: sm_os.h
+readcf.po: sm_os.h
+recipient.o: sm_os.h
+recipient.po: sm_os.h
+sasl.o: sm_os.h
+sasl.po: sm_os.h
+savemail.o: sm_os.h
+savemail.po: sm_os.h
+sfsasl.o: sm_os.h
+sfsasl.po: sm_os.h
+shmticklib.o: sm_os.h
+shmticklib.po: sm_os.h
+sm_resolve.o: sm_os.h
+sm_resolve.po: sm_os.h
+srvrsmtp.o: sm_os.h
+srvrsmtp.po: sm_os.h
+stab.o: sm_os.h
+stab.po: sm_os.h
+stats.o: sm_os.h
+stats.po: sm_os.h
+sysexits.o: sm_os.h
+sysexits.po: sm_os.h
+timers.o: sm_os.h
+timers.po: sm_os.h
+tls.o: sm_os.h
+tls.po: sm_os.h
+trace.o: sm_os.h
+trace.po: sm_os.h
+udb.o: sm_os.h
+udb.po: sm_os.h
+usersmtp.o: sm_os.h
+usersmtp.po: sm_os.h
+util.o: sm_os.h
+util.po: sm_os.h
+version.o: sm_os.h
+version.po: sm_os.h
+.endif
diff --git a/usr.sbin/service/Makefile b/usr.sbin/service/Makefile
new file mode 100644
index 0000000..a03714d
--- /dev/null
+++ b/usr.sbin/service/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+SCRIPTS=service.sh
+MAN= service.8
+
+.include <bsd.prog.mk>
+
diff --git a/usr.sbin/service/Makefile.depend b/usr.sbin/service/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/service/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/service/service.8 b/usr.sbin/service/service.8
new file mode 100644
index 0000000..81d24a0
--- /dev/null
+++ b/usr.sbin/service/service.8
@@ -0,0 +1,135 @@
+.\" Copyright (c) 2009 Douglas Barton
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 11, 2012
+.Dt SERVICE 8
+.Os
+.Sh NAME
+.Nm service
+.Nd "control (start/stop/etc.) or list system services"
+.Sh SYNOPSIS
+.Nm
+.Fl e
+.Nm
+.Fl R
+.Nm
+.Op Fl v
+.Fl l | r
+.Nm
+.Op Fl v
+.Ar <rc.d script> start|stop|etc.
+.Sh DESCRIPTION
+The
+.Nm
+command is an easy interface to the rc.d system.
+Its primary purpose is to start and stop services provided
+by the rc.d scripts.
+When used for this purpose it will set the same restricted
+environment that is in use at boot time (see below).
+It can also be used to list
+the scripts using various criteria.
+.Pp
+The options are as follows:
+.Bl -tag -width F1
+.It Fl e
+List services that are enabled.
+The list of scripts to check is compiled using
+.Xr rcorder 8
+the same way that it is done in
+.Xr rc 8 ,
+then that list of scripts is checked for an
+.Qq rcvar
+assignment.
+If present the script is checked to see if it is enabled.
+.It Fl R
+Restart all enabled local services.
+.It Fl l
+List all files in
+.Pa /etc/rc.d
+and the local startup directories.
+As described in
+.Xr rc.conf 5
+this is usually
+.Pa /usr/local/etc/rc.d .
+All files will be listed whether they are an actual
+rc.d script or not.
+.It Fl r
+Generate the
+.Xr rcorder 8
+as in
+.Fl e
+above, but list all of the files, not just what is enabled.
+.It Fl v
+Be slightly more verbose
+.El
+.Sh ENVIRONMENT
+When used to run rc.d scripts the
+.Nm
+command sets
+.Ev HOME
+to
+.Pa /
+and
+.Ev PATH
+to
+.Pa /sbin:/bin:/usr/sbin:/usr/bin
+which is how they are set in
+.Pa /etc/rc
+at boot time.
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+The following are examples of typical usage of the
+.Nm
+command:
+.Pp
+.Dl "service named status"
+.Dl "service -rv"
+.Pp
+The following programmable completion entry can be use in
+.Xr bash 1
+for the names of the rc.d scripts:
+.Dl "_service () {"
+.Dl " local cur"
+.Dl " cur=${COMP_WORDS[COMP_CWORD]}"
+.Dl " COMPREPLY=( $( compgen -W '$( service -l )' -- $cur ) )"
+.Dl " return 0"
+.Dl "}"
+.Dl "complete -F _service service"
+.Sh SEE ALSO
+.Xr bash 1 Pq Pa ports/shells/bash ,
+.Xr rc.conf 5 ,
+.Xr rc 8 ,
+.Xr rcorder 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 7.3 .
+.Sh AUTHORS
+This
+manual page was written by
+.An Douglas Barton Aq Mt dougb@FreeBSD.org .
diff --git a/usr.sbin/service/service.sh b/usr.sbin/service/service.sh
new file mode 100755
index 0000000..7aaecff
--- /dev/null
+++ b/usr.sbin/service/service.sh
@@ -0,0 +1,155 @@
+#!/bin/sh
+
+# $FreeBSD$
+
+# Copyright (c) 2009 Douglas Barton
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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.
+
+. /etc/rc.subr
+load_rc_config 'XXX'
+
+usage () {
+ echo ''
+ echo 'Usage:'
+ echo "${0##*/} -e"
+ echo "${0##*/} -R"
+ echo "${0##*/} [-v] -l | -r"
+ echo "${0##*/} [-v] <rc.d script> start|stop|etc."
+ echo "${0##*/} -h"
+ echo ''
+ echo '-e Show services that are enabled'
+ echo "-R Stop and start enabled $local_startup services"
+ echo "-l List all scripts in /etc/rc.d and $local_startup"
+ echo '-r Show the results of boot time rcorder'
+ echo '-v Verbose'
+ echo ''
+}
+
+while getopts 'ehlrRv' COMMAND_LINE_ARGUMENT ; do
+ case "${COMMAND_LINE_ARGUMENT}" in
+ e) ENABLED=eopt ;;
+ h) usage ; exit 0 ;;
+ l) LIST=lopt ;;
+ r) RCORDER=ropt ;;
+ R) RESTART=Ropt ;;
+ v) VERBOSE=vopt ;;
+ *) usage ; exit 1 ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+if [ -n "$RESTART" ]; then
+ skip="-s nostart"
+ if [ `/sbin/sysctl -n security.jail.jailed` -eq 1 ]; then
+ skip="$skip -s nojail"
+ fi
+ [ -n "$local_startup" ] && find_local_scripts_new
+ files=`rcorder ${skip} ${local_rc} 2>/dev/null`
+
+ for file in `reverse_list ${files}`; do
+ if grep -q ^rcvar $file; then
+ eval `grep ^name= $file`
+ eval `grep ^rcvar $file`
+ if [ -n "$rcvar" ]; then
+ load_rc_config_var ${name} ${rcvar}
+ fi
+ checkyesno $rcvar 2>/dev/null && run_rc_script ${file} stop
+ fi
+ done
+ for file in $files; do
+ if grep -q ^rcvar $file; then
+ eval `grep ^name= $file`
+ eval `grep ^rcvar $file`
+ checkyesno $rcvar 2>/dev/null && run_rc_script ${file} start
+ fi
+ done
+
+ exit 0
+fi
+
+if [ -n "$ENABLED" -o -n "$RCORDER" ]; then
+ # Copied from /etc/rc
+ skip="-s nostart"
+ if [ `/sbin/sysctl -n security.jail.jailed` -eq 1 ]; then
+ skip="$skip -s nojail"
+ fi
+ [ -n "$local_startup" ] && find_local_scripts_new
+ files=`rcorder ${skip} /etc/rc.d/* ${local_rc} 2>/dev/null`
+fi
+
+if [ -n "$ENABLED" ]; then
+ for file in $files; do
+ if grep -q ^rcvar $file; then
+ eval `grep ^name= $file`
+ eval `grep ^rcvar $file`
+ if [ -n "$rcvar" ]; then
+ load_rc_config_var ${name} ${rcvar}
+ fi
+ checkyesno $rcvar 2>/dev/null && echo $file
+ fi
+ done
+ exit 0
+fi
+
+if [ -n "$LIST" ]; then
+ for dir in /etc/rc.d $local_startup; do
+ [ -n "$VERBOSE" ] && echo "From ${dir}:"
+ [ -d ${dir} ] && /bin/ls -1 ${dir}
+ done
+ exit 0
+fi
+
+if [ -n "$RCORDER" ]; then
+ for file in $files; do
+ echo $file
+ if [ -n "$VERBOSE" ]; then
+ case "$file" in
+ */${early_late_divider})
+ echo '========= Early/Late Divider =========' ;;
+ esac
+ fi
+ done
+ exit 0
+fi
+
+if [ $# -gt 1 ]; then
+ script=$1
+ shift
+else
+ usage
+ exit 1
+fi
+
+cd /
+for dir in /etc/rc.d $local_startup; do
+ if [ -x "$dir/$script" ]; then
+ [ -n "$VERBOSE" ] && echo "$script is located in $dir"
+ exec env -i HOME=/ PATH=/sbin:/bin:/usr/sbin:/usr/bin $dir/$script $*
+ fi
+done
+
+# If the script was not found
+echo "$script does not exist in /etc/rc.d or the local startup"
+echo "directories (${local_startup}), or is not executable"
+exit 1
diff --git a/usr.sbin/services_mkdb/Makefile b/usr.sbin/services_mkdb/Makefile
new file mode 100644
index 0000000..05a4b76
--- /dev/null
+++ b/usr.sbin/services_mkdb/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PROG= services_mkdb
+MAN= services_mkdb.8
+SRCS= services_mkdb.c uniq.c extern.h
+
+LIBADD= util
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/services_mkdb/Makefile.depend b/usr.sbin/services_mkdb/Makefile.depend
new file mode 100644
index 0000000..58f9a33
--- /dev/null
+++ b/usr.sbin/services_mkdb/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/services_mkdb/extern.h b/usr.sbin/services_mkdb/extern.h
new file mode 100644
index 0000000..b973972
--- /dev/null
+++ b/usr.sbin/services_mkdb/extern.h
@@ -0,0 +1,34 @@
+/*-
+ * Copyright (c) 2007 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+extern HASHINFO hinfo;
+
+void uniq(const char *);
diff --git a/usr.sbin/services_mkdb/services_mkdb.8 b/usr.sbin/services_mkdb/services_mkdb.8
new file mode 100644
index 0000000..e3b70e2
--- /dev/null
+++ b/usr.sbin/services_mkdb/services_mkdb.8
@@ -0,0 +1,111 @@
+.\" $NetBSD: services_mkdb.8,v 1.9 2009/05/13 22:36:39 wiz Exp $
+.\"
+.\" Copyright (c) 1999 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$
+.\"
+.Dd April 4, 2010
+.Dt SERVICES_MKDB 8
+.Os
+.Sh NAME
+.Nm services_mkdb
+.Nd generate the services database
+.Sh SYNOPSIS
+.Nm
+.Op Fl b | l
+.Op Fl q
+.Op Fl o Ar database
+.Op Ar file
+.Nm
+.Fl u
+.Op Ar file
+.Sh DESCRIPTION
+The
+.Nm
+utility
+creates a
+.Xr db 3
+database for the specified
+.Ar file .
+If no file is specified, then
+.Pa /etc/services
+is used.
+The database is installed into
+.Pa /var/db/services.db .
+The file must be in the correct format (see
+.Xr services 5 ) .
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl b
+Use big-endian byte order for database metadata.
+.It Fl l
+Use little-endian byte order for database metadata.
+.It Fl o Ar database
+Put the output databases in the named file.
+.It Fl q
+Do not warn about duplicate services.
+.It Fl u
+Print the services file to stdout, omitting duplicate entries and comments.
+.El
+.Pp
+The databases are used by the C library services routines (see
+.Pp
+The
+.Fl b
+and
+.Fl l
+flags are mutually exclusive.
+The default byte ordering is the current host order.
+.Xr getservent 3 ) .
+.Sh FILES
+.Bl -tag -width ".Pa /var/db/services.db.tmp" -compact
+.It Pa /var/db/services.db
+The current services database.
+.It Pa /var/db/services.db.tmp
+A temporary file.
+.It Pa /etc/services
+The current services file.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr db 3 ,
+.Xr getservent 3 ,
+.Xr services 5
+.Sh BUGS
+Because
+.Nm
+guarantees not to install a partial destination file it must
+build a temporary file in the same file system and if successful use
+.Xr rename 2
+to install over the destination file.
+.Pp
+If
+.Nm
+fails it will leave the previous version of the destination file intact.
diff --git a/usr.sbin/services_mkdb/services_mkdb.c b/usr.sbin/services_mkdb/services_mkdb.c
new file mode 100644
index 0000000..9ea66de
--- /dev/null
+++ b/usr.sbin/services_mkdb/services_mkdb.c
@@ -0,0 +1,456 @@
+/* $NetBSD: services_mkdb.c,v 1.14 2008/04/28 20:24:17 martin Exp $ */
+
+/*-
+ * Copyright (c) 1999 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn and Christos Zoulas.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <db.h>
+#include <err.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <libgen.h>
+#include <libutil.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stringlist.h>
+
+#include "extern.h"
+
+static char tname[MAXPATHLEN];
+
+#define PMASK 0xffff
+#define PROTOMAX 5
+
+static void add(DB *, StringList *, size_t, const char *, size_t *, int);
+static StringList ***parseservices(const char *, StringList *);
+static void cleanup(void);
+static void store(DB *, DBT *, DBT *, int);
+static void killproto(DBT *);
+static char *getstring(const char *, size_t, char **, const char *);
+static size_t getprotoindex(StringList *, const char *);
+static const char *getprotostr(StringList *, size_t);
+static const char *mkaliases(StringList *, char *, size_t);
+static void usage(void);
+
+HASHINFO hinfo = {
+ .bsize = 256,
+ .ffactor = 4,
+ .nelem = 32768,
+ .cachesize = 1024,
+ .hash = NULL,
+ .lorder = 0
+};
+
+
+int
+main(int argc, char *argv[])
+{
+ DB *db;
+ int ch;
+ const char *fname = _PATH_SERVICES;
+ const char *dbname = _PATH_SERVICES_DB;
+ int warndup = 1;
+ int unique = 0;
+ int otherflag = 0;
+ int byteorder = 0;
+ size_t cnt = 0;
+ StringList *sl, ***svc;
+ size_t port, proto;
+ char *dbname_dir;
+ int dbname_dir_fd = -1;
+
+ setprogname(argv[0]);
+
+ while ((ch = getopt(argc, argv, "blo:qu")) != -1)
+ switch (ch) {
+ case 'b':
+ case 'l':
+ if (byteorder != 0)
+ usage();
+ byteorder = ch == 'b' ? 4321 : 1234;
+ break;
+ case 'q':
+ otherflag = 1;
+ warndup = 0;
+ break;
+ case 'o':
+ otherflag = 1;
+ dbname = optarg;
+ break;
+ case 'u':
+ unique++;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 1 || (unique && otherflag))
+ usage();
+ if (argc == 1)
+ fname = argv[0];
+
+ /* Set byte order. */
+ hinfo.lorder = byteorder;
+
+ if (unique)
+ uniq(fname);
+
+ svc = parseservices(fname, sl = sl_init());
+
+ if (atexit(cleanup))
+ err(1, "Cannot install exit handler");
+
+ (void)snprintf(tname, sizeof(tname), "%s.tmp", dbname);
+ db = dbopen(tname, O_RDWR | O_CREAT | O_EXCL | O_SYNC,
+ (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH), DB_HASH, &hinfo);
+ if (!db)
+ err(1, "Error opening temporary database `%s'", tname);
+
+
+ for (port = 0; port < PMASK + 1; port++) {
+ if (svc[port] == NULL)
+ continue;
+
+ for (proto = 0; proto < PROTOMAX; proto++) {
+ StringList *s;
+ if ((s = svc[port][proto]) == NULL)
+ continue;
+ add(db, s, port, getprotostr(sl, proto), &cnt, warndup);
+ }
+
+ free(svc[port]);
+ }
+
+ free(svc);
+ sl_free(sl, 1);
+
+ if ((db->close)(db))
+ err(1, "Error closing temporary database `%s'", tname);
+
+ /*
+ * Make sure file is safe on disk. To improve performance we will call
+ * fsync() to the directory where file lies
+ */
+ if (rename(tname, dbname) == -1 ||
+ (dbname_dir = dirname(dbname)) == NULL ||
+ (dbname_dir_fd = open(dbname_dir, O_RDONLY|O_DIRECTORY)) == -1 ||
+ fsync(dbname_dir_fd) != 0) {
+ if (dbname_dir_fd != -1)
+ close(dbname_dir_fd);
+ err(1, "Cannot rename `%s' to `%s'", tname, dbname);
+ }
+
+ if (dbname_dir_fd != -1)
+ close(dbname_dir_fd);
+
+ return 0;
+}
+
+static void
+add(DB *db, StringList *sl, size_t port, const char *proto, size_t *cnt,
+ int warndup)
+{
+ size_t i;
+ char keyb[BUFSIZ], datab[BUFSIZ], abuf[BUFSIZ];
+ DBT data, key;
+ key.data = keyb;
+ data.data = datab;
+
+#ifdef DEBUG
+ (void)printf("add %s %zu %s [ ", sl->sl_str[0], port, proto);
+ for (i = 1; i < sl->sl_cur; i++)
+ (void)printf("%s ", sl->sl_str[i]);
+ (void)printf("]\n");
+#endif
+
+ /* key `indirect key', data `full line' */
+ data.size = snprintf(datab, sizeof(datab), "%zu", (*cnt)++) + 1;
+ key.size = snprintf(keyb, sizeof(keyb), "%s %zu/%s %s",
+ sl->sl_str[0], port, proto, mkaliases(sl, abuf, sizeof(abuf))) + 1;
+ store(db, &data, &key, warndup);
+
+ /* key `\377port/proto', data = `indirect key' */
+ key.size = snprintf(keyb, sizeof(keyb), "\377%zu/%s",
+ port, proto) + 1;
+ store(db, &key, &data, warndup);
+
+ /* key `\377port', data = `indirect key' */
+ killproto(&key);
+ store(db, &key, &data, warndup);
+
+ /* add references for service and all aliases */
+ for (i = 0; i < sl->sl_cur; i++) {
+ /* key `\376service/proto', data = `indirect key' */
+ key.size = snprintf(keyb, sizeof(keyb), "\376%s/%s",
+ sl->sl_str[i], proto) + 1;
+ store(db, &key, &data, warndup);
+
+ /* key `\376service', data = `indirect key' */
+ killproto(&key);
+ store(db, &key, &data, warndup);
+ }
+ sl_free(sl, 1);
+}
+
+static StringList ***
+parseservices(const char *fname, StringList *sl)
+{
+ size_t len, line, pindex;
+ FILE *fp;
+ StringList ***svc, *s;
+ char *p, *ep;
+
+ if ((fp = fopen(fname, "r")) == NULL)
+ err(1, "Cannot open `%s'", fname);
+
+ line = 0;
+ if ((svc = calloc(PMASK + 1, sizeof(StringList **))) == NULL)
+ err(1, "Cannot allocate %zu bytes", (size_t)(PMASK + 1));
+
+ /* XXX: change NULL to "\0\0#" when fparseln fixed */
+ for (; (p = fparseln(fp, &len, &line, NULL, 0)) != NULL; free(p)) {
+ char *name, *port, *proto, *aliases, *cp, *alias;
+ unsigned long pnum;
+
+ if (len == 0)
+ continue;
+
+ for (cp = p; *cp && isspace((unsigned char)*cp); cp++)
+ continue;
+
+ if (*cp == '\0' || *cp == '#')
+ continue;
+
+ if ((name = getstring(fname, line, &cp, "name")) == NULL)
+ continue;
+
+ if ((port = getstring(fname, line, &cp, "port")) == NULL)
+ continue;
+
+ if (cp) {
+ for (aliases = cp; *cp && *cp != '#'; cp++)
+ continue;
+
+ if (*cp)
+ *cp = '\0';
+ } else
+ aliases = NULL;
+
+ proto = strchr(port, '/');
+ if (proto == NULL || proto[1] == '\0') {
+ warnx("%s, %zu: no protocol found", fname, line);
+ continue;
+ }
+ *proto++ = '\0';
+
+ errno = 0;
+ pnum = strtoul(port, &ep, 0);
+ if (*port == '\0' || *ep != '\0') {
+ warnx("%s, %zu: invalid port `%s'", fname, line, port);
+ continue;
+ }
+ if ((errno == ERANGE && pnum == ULONG_MAX) || pnum > PMASK) {
+ warnx("%s, %zu: port too big `%s'", fname, line, port);
+ continue;
+ }
+
+ if (svc[pnum] == NULL) {
+ svc[pnum] = calloc(PROTOMAX, sizeof(StringList *));
+ if (svc[pnum] == NULL)
+ err(1, "Cannot allocate %zu bytes",
+ (size_t)PROTOMAX);
+ }
+
+ pindex = getprotoindex(sl, proto);
+ if (svc[pnum][pindex] == NULL)
+ s = svc[pnum][pindex] = sl_init();
+ else
+ s = svc[pnum][pindex];
+
+ /* build list of aliases */
+ if (sl_find(s, name) == NULL) {
+ char *p2;
+
+ if ((p2 = strdup(name)) == NULL)
+ err(1, "Cannot copy string");
+ (void)sl_add(s, p2);
+ }
+
+ if (aliases) {
+ while ((alias = strsep(&aliases, " \t")) != NULL) {
+ if (alias[0] == '\0')
+ continue;
+ if (sl_find(s, alias) == NULL) {
+ char *p2;
+
+ if ((p2 = strdup(alias)) == NULL)
+ err(1, "Cannot copy string");
+ (void)sl_add(s, p2);
+ }
+ }
+ }
+ }
+ (void)fclose(fp);
+ return svc;
+}
+
+/*
+ * cleanup(): Remove temporary files upon exit
+ */
+static void
+cleanup(void)
+{
+ if (tname[0])
+ (void)unlink(tname);
+}
+
+static char *
+getstring(const char *fname, size_t line, char **cp, const char *tag)
+{
+ char *str;
+
+ while ((str = strsep(cp, " \t")) != NULL && *str == '\0')
+ continue;
+
+ if (str == NULL)
+ warnx("%s, %zu: no %s found", fname, line, tag);
+
+ return str;
+}
+
+static void
+killproto(DBT *key)
+{
+ char *p, *d = key->data;
+
+ if ((p = strchr(d, '/')) == NULL)
+ abort();
+ *p++ = '\0';
+ key->size = p - d;
+}
+
+static void
+store(DB *db, DBT *key, DBT *data, int warndup)
+{
+#ifdef DEBUG
+ int k = key->size - 1;
+ int d = data->size - 1;
+ (void)printf("store [%*.*s] [%*.*s]\n",
+ k, k, (char *)key->data + 1,
+ d, d, (char *)data->data + 1);
+#endif
+ switch ((db->put)(db, key, data, R_NOOVERWRITE)) {
+ case 0:
+ break;
+ case 1:
+ if (warndup)
+ warnx("duplicate service `%s'",
+ &((char *)key->data)[1]);
+ break;
+ case -1:
+ err(1, "put");
+ break;
+ default:
+ abort();
+ break;
+ }
+}
+
+static size_t
+getprotoindex(StringList *sl, const char *str)
+{
+ size_t i;
+ char *p;
+
+ for (i= 0; i < sl->sl_cur; i++)
+ if (strcmp(sl->sl_str[i], str) == 0)
+ return i;
+
+ if (i == PROTOMAX)
+ errx(1, "Ran out of protocols adding `%s';"
+ " recompile with larger PROTOMAX", str);
+ if ((p = strdup(str)) == NULL)
+ err(1, "Cannot copy string");
+ (void)sl_add(sl, p);
+ return i;
+}
+
+static const char *
+getprotostr(StringList *sl, size_t i)
+{
+ assert(i < sl->sl_cur);
+ return sl->sl_str[i];
+}
+
+static const char *
+mkaliases(StringList *sl, char *buf, size_t len)
+{
+ size_t nc, i, pos;
+
+ buf[0] = 0;
+ for (i = 1, pos = 0; i < sl->sl_cur; i++) {
+ nc = strlcpy(buf + pos, sl->sl_str[i], len);
+ if (nc >= len)
+ goto out;
+ pos += nc;
+ len -= nc;
+ nc = strlcpy(buf + pos, " ", len);
+ if (nc >= len)
+ goto out;
+ pos += nc;
+ len -= nc;
+ }
+ return buf;
+out:
+ warn("aliases for `%s' truncated", sl->sl_str[0]);
+ return buf;
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "Usage:\t%s [-b | -l] [-q] [-o <db>] [<servicefile>]\n"
+ "\t%s -u [<servicefile>]\n", getprogname(), getprogname());
+ exit(1);
+}
diff --git a/usr.sbin/services_mkdb/uniq.c b/usr.sbin/services_mkdb/uniq.c
new file mode 100644
index 0000000..e1bac38
--- /dev/null
+++ b/usr.sbin/services_mkdb/uniq.c
@@ -0,0 +1,158 @@
+/* $NetBSD: uniq.c,v 1.4 2008/04/28 20:24:17 martin Exp $ */
+
+/*-
+ * Copyright (c) 2007 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <db.h>
+#include <err.h>
+#include <libutil.h>
+#include <ctype.h>
+#include <fcntl.h>
+
+#include "extern.h"
+
+static int comp(const char *, char **, size_t *);
+
+/*
+ * Preserve only unique content lines in a file. Input lines that have
+ * content [alphanumeric characters before a comment] are white-space
+ * normalized and have their comments removed. Then they are placed
+ * in a hash table, and only the first instance of them is printed.
+ * Comment lines without any alphanumeric content are always printed
+ * since they are there to make the file "pretty". Comment lines with
+ * alphanumeric content are also placed into the hash table and only
+ * printed once.
+ */
+void
+uniq(const char *fname)
+{
+ DB *db;
+ DBT key;
+ static const DBT data = { NULL, 0 };
+ FILE *fp;
+ char *line;
+ size_t len;
+
+ if ((db = dbopen(NULL, O_RDWR, 0, DB_HASH, &hinfo)) == NULL)
+ err(1, "Cannot create in memory database");
+
+ if ((fp = fopen(fname, "r")) == NULL)
+ err(1, "Cannot open `%s'", fname);
+ while ((line = fgetln(fp, &len)) != NULL) {
+ size_t complen = len;
+ char *compline;
+ if (!comp(line, &compline, &complen)) {
+ (void)fprintf(stdout, "%*.*s", (int)len, (int)len,
+ line);
+ continue;
+ }
+ key.data = compline;
+ key.size = complen;
+ switch ((db->put)(db, &key, &data, R_NOOVERWRITE)) {
+ case 0:
+ (void)fprintf(stdout, "%*.*s", (int)len, (int)len,
+ line);
+ break;
+ case 1:
+ break;
+ case -1:
+ err(1, "put");
+ default:
+ abort();
+ break;
+ }
+ }
+ (void)fflush(stdout);
+ exit(0);
+}
+
+/*
+ * normalize whitespace in the original line and place a new string
+ * with whitespace converted to a single space in compline. If the line
+ * contains just comments, we preserve them. If it contains data and
+ * comments, we kill the comments. Return 1 if the line had actual
+ * contents, or 0 if it was just a comment without alphanumeric characters.
+ */
+static int
+comp(const char *origline, char **compline, size_t *len)
+{
+ const unsigned char *p;
+ unsigned char *q;
+ char *cline;
+ size_t l = *len, complen;
+ int hasalnum, iscomment;
+
+ /* Eat leading space */
+ for (p = (const unsigned char *)origline; l && *p && isspace(*p);
+ p++, l--)
+ continue;
+ if ((cline = malloc(l + 1)) == NULL)
+ err(1, "Cannot allocate %zu bytes", l + 1);
+ (void)memcpy(cline, p, l);
+ cline[l] = '\0';
+ if (*cline == '\0')
+ return 0;
+
+ complen = 0;
+ hasalnum = 0;
+ iscomment = 0;
+
+ for (q = (unsigned char *)cline; l && *p; p++, l--) {
+ if (isspace(*p)) {
+ if (complen && isspace(q[-1]))
+ continue;
+ *q++ = ' ';
+ complen++;
+ } else {
+ if (!iscomment && *p == '#') {
+ if (hasalnum)
+ break;
+ iscomment = 1;
+ } else
+ hasalnum |= isalnum(*p);
+ *q++ = *p;
+ complen++;
+ }
+ }
+
+ /* Eat trailing space */
+ while (complen && isspace(q[-1])) {
+ --q;
+ --complen;
+ }
+ *q = '\0';
+ *compline = cline;
+ *len = complen;
+ return hasalnum;
+}
diff --git a/usr.sbin/sesutil/Makefile b/usr.sbin/sesutil/Makefile
new file mode 100644
index 0000000..bf37192
--- /dev/null
+++ b/usr.sbin/sesutil/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PROG= sesutil
+SRCS= sesutil.c eltsub.c
+MAN= sesutil.8
+
+LIBADD= sbuf
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/sesutil/Makefile.depend b/usr.sbin/sesutil/Makefile.depend
new file mode 100644
index 0000000..6002005
--- /dev/null
+++ b/usr.sbin/sesutil/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libsbuf \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/sesutil/eltsub.c b/usr.sbin/sesutil/eltsub.c
new file mode 100644
index 0000000..287530d
--- /dev/null
+++ b/usr.sbin/sesutil/eltsub.c
@@ -0,0 +1,235 @@
+/* $FreeBSD$ */
+/*
+ * Copyright (c) 2000 by 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,
+ * without modification, immediately at the beginning of the file.
+ * 2. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * the GNU Public License ("GPL").
+ *
+ * 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.
+ *
+ * Matthew Jacob
+ * Feral Software
+ * mjacob@feral.com
+ */
+
+#include <sys/endian.h>
+#include <sys/types.h>
+#include <sys/sbuf.h>
+
+#include <err.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <cam/scsi/scsi_all.h>
+#include <cam/scsi/scsi_enc.h>
+
+#include "eltsub.h"
+
+/*
+ * offset by +20 degrees.
+ * The range of the value expresses a temperature between -19 and +235 degrees
+ * Celsius. A value of 00h is reserved.
+ */
+#define TEMPERATURE_OFFSET 20
+
+char *
+geteltnm(int type)
+{
+ static char rbuf[132];
+
+ switch (type) {
+ case ELMTYP_UNSPECIFIED:
+ sprintf(rbuf, "Unspecified");
+ break;
+ case ELMTYP_DEVICE:
+ sprintf(rbuf, "Device Slot");
+ break;
+ case ELMTYP_POWER:
+ sprintf(rbuf, "Power Supply");
+ break;
+ case ELMTYP_FAN:
+ sprintf(rbuf, "Cooling");
+ break;
+ case ELMTYP_THERM:
+ sprintf(rbuf, "Temperature Sensors");
+ break;
+ case ELMTYP_DOORLOCK:
+ sprintf(rbuf, "Door Lock");
+ break;
+ case ELMTYP_ALARM:
+ sprintf(rbuf, "Audible alarm");
+ break;
+ case ELMTYP_ESCC:
+ sprintf(rbuf, "Enclosure Services Controller Electronics");
+ break;
+ case ELMTYP_SCC:
+ sprintf(rbuf, "SCC Controller Electronics");
+ break;
+ case ELMTYP_NVRAM:
+ sprintf(rbuf, "Nonvolatile Cache");
+ break;
+ case ELMTYP_INV_OP_REASON:
+ sprintf(rbuf, "Invalid Operation Reason");
+ break;
+ case ELMTYP_UPS:
+ sprintf(rbuf, "Uninterruptible Power Supply");
+ break;
+ case ELMTYP_DISPLAY:
+ sprintf(rbuf, "Display");
+ break;
+ case ELMTYP_KEYPAD:
+ sprintf(rbuf, "Key Pad Entry");
+ break;
+ case ELMTYP_ENCLOSURE:
+ sprintf(rbuf, "Enclosure");
+ break;
+ case ELMTYP_SCSIXVR:
+ sprintf(rbuf, "SCSI Port/Transceiver");
+ break;
+ case ELMTYP_LANGUAGE:
+ sprintf(rbuf, "Language");
+ break;
+ case ELMTYP_COMPORT:
+ sprintf(rbuf, "Communication Port");
+ break;
+ case ELMTYP_VOM:
+ sprintf(rbuf, "Voltage Sensor");
+ break;
+ case ELMTYP_AMMETER:
+ sprintf(rbuf, "Current Sensor");
+ break;
+ case ELMTYP_SCSI_TGT:
+ sprintf(rbuf, "SCSI Target Port");
+ break;
+ case ELMTYP_SCSI_INI:
+ sprintf(rbuf, "SCSI Initiator Port");
+ break;
+ case ELMTYP_SUBENC:
+ sprintf(rbuf, "Simple Subenclosure");
+ break;
+ case ELMTYP_ARRAY_DEV:
+ sprintf(rbuf, "Array Device Slot");
+ break;
+ case ELMTYP_SAS_EXP:
+ sprintf(rbuf, "SAS Expander");
+ break;
+ case ELMTYP_SAS_CONN:
+ sprintf(rbuf, "SAS Connector");
+ break;
+ default:
+ (void) sprintf(rbuf, "<Type 0x%x>", type);
+ break;
+ }
+ return (rbuf);
+}
+
+char *
+scode2ascii(u_char code)
+{
+ static char rbuf[32];
+ switch (code & 0xf) {
+ case SES_OBJSTAT_UNSUPPORTED:
+ sprintf(rbuf, "Unsupported");
+ break;
+ case SES_OBJSTAT_OK:
+ sprintf(rbuf, "OK");
+ break;
+ case SES_OBJSTAT_CRIT:
+ sprintf(rbuf, "Critical");
+ break;
+ case SES_OBJSTAT_NONCRIT:
+ sprintf(rbuf, "Noncritical");
+ break;
+ case SES_OBJSTAT_UNRECOV:
+ sprintf(rbuf, "Unrecoverable");
+ break;
+ case SES_OBJSTAT_NOTINSTALLED:
+ sprintf(rbuf, "Not Installed");
+ break;
+ case SES_OBJSTAT_UNKNOWN:
+ sprintf(rbuf, "Unknown");
+ break;
+ case SES_OBJSTAT_NOTAVAIL:
+ sprintf(rbuf, "Not Available");
+ break;
+ case SES_OBJSTAT_NOACCESS:
+ sprintf(rbuf, "No Access Allowed");
+ break;
+ default:
+ sprintf(rbuf, "<Status 0x%x>", code & 0xf);
+ break;
+ }
+ return (rbuf);
+}
+
+struct sbuf *
+stat2sbuf(int eletype, u_char *cstat)
+{
+ struct sbuf *buf;
+
+ buf = sbuf_new_auto();
+ if (buf == NULL)
+ err(EXIT_FAILURE, "sbuf_new_auto()");
+
+ if (cstat[0] & 0x40)
+ sbuf_printf(buf, "\t\t- Predicted Failure\n");
+ if (cstat[0] & 0x20)
+ sbuf_printf(buf, "\t\t- Disabled\n");
+ if (cstat[0] & 0x10)
+ sbuf_printf(buf, "\t\t- Swapped\n");
+ switch (eletype) {
+ case ELMTYP_DEVICE:
+ if (cstat[2] & 0x02)
+ sbuf_printf(buf, "\t\t- LED=locate\n");
+ if (cstat[2] & 0x20)
+ sbuf_printf(buf, "\t\t- LED=fault\n");
+ break;
+ case ELMTYP_ARRAY_DEV:
+ if (cstat[2] & 0x02)
+ sbuf_printf(buf, "\t\t- LED=locate\n");
+ if (cstat[2] & 0x20)
+ sbuf_printf(buf, "\t\t- LED=fault\n");
+ break;
+ case ELMTYP_FAN:
+ sbuf_printf(buf, "\t\t- Speed: %d rpm\n",
+ (((0x7 & cstat[1]) << 8) + cstat[2]) * 10);
+ break;
+ case ELMTYP_THERM:
+ if (cstat[2]) {
+ sbuf_printf(buf, "\t\t- Temperature: %d C\n",
+ cstat[2] - TEMPERATURE_OFFSET);
+ } else {
+ sbuf_printf(buf, "\t\t- Temperature: -reserved-\n");
+ }
+ break;
+ case ELMTYP_VOM:
+ sbuf_printf(buf, "\t\t- Voltage: %.2f V\n",
+ be16dec(cstat + 2) / 100.0);
+ break;
+ }
+ sbuf_finish(buf);
+ return (buf);
+}
diff --git a/usr.sbin/sesutil/eltsub.h b/usr.sbin/sesutil/eltsub.h
new file mode 100644
index 0000000..299ada3
--- /dev/null
+++ b/usr.sbin/sesutil/eltsub.h
@@ -0,0 +1,37 @@
+/* $FreeBSD$ */
+/*
+ * Copyright (c) 2000 by 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,
+ * without modification, immediately at the beginning of the file.
+ * 2. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * the GNU Public License ("GPL").
+ *
+ * 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.
+ *
+ * Matthew Jacob
+ * Feral Software
+ * mjacob@feral.com
+ */
+
+char *geteltnm(int);
+char *scode2ascii(u_char);
+struct sbuf *stat2sbuf(int, u_char *);
diff --git a/usr.sbin/sesutil/sesutil.8 b/usr.sbin/sesutil/sesutil.8
new file mode 100644
index 0000000..79d0f66
--- /dev/null
+++ b/usr.sbin/sesutil/sesutil.8
@@ -0,0 +1,130 @@
+.\" Copyright (c) 2015 Baptiste Daroussin <bapt@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 September 6, 2015
+.Dt SESUTIL 8
+.Os
+.Sh NAME
+.Nm sesutil
+.Nd Utility for managing SCSI Enclosure Services (SES) device
+.Sh SYNOPSIS
+.Nm
+.Cm fault
+.Op Fl u Ar /dev/sesN
+.Aq Ar disk | Ar sesid | Li all
+.Op on | off
+.Nm
+.Cm locate
+.Op Fl u Ar /dev/sesN
+.Aq Ar disk | Ar sesid | Li all
+.Op on | off
+.Nm
+.Cm map
+.Op Fl u Ar /dev/sesN
+.Nm
+.Cm status
+.Op Fl u Ar /dev/sesN
+.Sh DESCRIPTION
+The
+.Nm
+utility can be used to query and modify various parameter of SCSI Enclosure
+Services (SES) devices.
+.Pp
+List of supported commands:
+.Bl -tag -width indent
+.It Cm fault Oo Fl u Ar /dev/sesN Oc Ao Ar disk | Li all Ac Op on | off
+Change the state of the external fault LED associated with
+.Ar disk .
+.Ar disk
+can be the device name of the disk, like
+.Cm da12 ,
+or
+.Ql all .
+to indicate all disks attached to SES controllers.
+.It Cm fault Fl u Ar /dev/sesN Ar sesid Op on | off
+Change the state of the external fault LED associated with an element
+connected to the SES controller.
+.Ar sesid
+must be the element ID of a valid item attached to the controller.
+Use the
+.Cm map
+command to list the elements attached to a controller.
+.It Cm locate Oo Fl u Ar /dev/sesN Oc Ao Ar disk | Li all Ac Op on | off
+Change the state of the external locate LED associated with
+.Ar disk .
+.Ar disk
+can be the device name of the disk, like
+.Cm da12 ,
+or
+.Ql all .
+to indicate all disks attached to SES controllers.
+.It Cm locate Fl u Ar /dev/sesN Ar sesid Op on | off
+Change the state of the external locate LED associated with an element
+connected to the SES controller.
+.Ar sesid
+must be the element ID of a valid item attached to the controller.
+Use the
+.Cm map
+command to list the elements attached to a controller.
+.It Cm map Op Fl u Ar /dev/sesN
+Display a map of all elements connected to the specified
+.Xr ses 4
+controller.
+If no controller is specified, all controllers are mapped.
+.It Cm status Op Fl u Ar /dev/sesN
+Display the status of the specified
+.Xr ses 4
+controller.
+If no controller is specified, the status of each controller is returned.
+.El
+.Sh EXAMPLES
+Turn off all locate LEDs:
+.Pp
+.Dl Nm Cm locate all off
+.Pp
+Turn on the locate LED for the drive bay corresponding to
+.Pa da15 :
+.Pp
+.Dl Nm Cm locate da15 on
+.Pp
+Turn on the fault LED for a drive bay not associated with a device:
+.Pp
+.Dl Nm Cm fault -u /dev/ses2 7 on
+.Sh SEE ALSO
+.Xr ses 4
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 11.0 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility was written by
+.An Baptiste Daroussin Aq Mt bapt@FreeBSD.org
+and
+.An Allan Jude Aq Mt allanjude@FreeBSD.org .
diff --git a/usr.sbin/sesutil/sesutil.c b/usr.sbin/sesutil/sesutil.c
new file mode 100644
index 0000000..0f04c07
--- /dev/null
+++ b/usr.sbin/sesutil/sesutil.c
@@ -0,0 +1,560 @@
+/*-
+ * Copyright (c) 2015 Baptiste Daroussin <bapt@FreeBSD.org>
+ * Copyright (c) 2015 Allan Jude <allanjude@FreeBSD.org>
+ * Copyright (c) 2000 by 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
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/sbuf.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <glob.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <cam/scsi/scsi_all.h>
+#include <cam/scsi/scsi_enc.h>
+
+#include "eltsub.h"
+
+static int encstatus(int argc, char **argv);
+static int fault(int argc, char **argv);
+static int locate(int argc, char **argv);
+static int objmap(int argc, char **argv);
+static int sesled(int argc, char **argv, bool fault);
+
+static struct command {
+ const char *name;
+ const char *param;
+ const char *desc;
+ int (*exec)(int argc, char **argv);
+} cmds[] = {
+ { "fault",
+ "(<disk>|<sesid>|all) (on|off)",
+ "Change the state of the fault LED associated with a disk",
+ fault },
+ { "locate",
+ "(<disk>|<sesid>|all) (on|off)",
+ "Change the state of the locate LED associated with a disk",
+ locate },
+ { "map", "",
+ "Print a map of the devices managed by the enclosure", objmap } ,
+ { "status", "", "Print the status of the enclosure",
+ encstatus },
+};
+
+static const int nbcmds = nitems(cmds);
+static const char *uflag;
+
+static void
+usage(FILE *out, const char *subcmd)
+{
+ int i;
+
+ if (subcmd == NULL) {
+ fprintf(out, "Usage: %s [-u /dev/ses<N>] <command> [options]\n",
+ getprogname());
+ fprintf(out, "Commands supported:\n");
+ }
+ for (i = 0; i < nbcmds; i++) {
+ if (subcmd != NULL) {
+ if (strcmp(subcmd, cmds[i].name) == 0) {
+ fprintf(out, "Usage: %s %s [-u /dev/ses<N>] "
+ "%s\n\t%s\n", getprogname(), subcmd,
+ cmds[i].param, cmds[i].desc);
+ break;
+ }
+ continue;
+ }
+ fprintf(out, " %-12s%s\n\t\t%s\n\n", cmds[i].name,
+ cmds[i].param, cmds[i].desc);
+ }
+
+ exit(EXIT_FAILURE);
+}
+
+static void
+do_led(int fd, unsigned int idx, bool onoff, bool setfault)
+{
+ encioc_elm_status_t o;
+
+ o.elm_idx = idx;
+ if (ioctl(fd, ENCIOC_GETELMSTAT, (caddr_t) &o) < 0) {
+ close(fd);
+ err(EXIT_FAILURE, "ENCIOC_GETELMSTAT");
+ }
+ o.cstat[0] |= 0x80;
+ if (onoff) {
+ o.cstat[2] |= (setfault ? 0x20 : 0x02);
+ } else {
+ o.cstat[2] &= (setfault ? 0xdf : 0xfd);
+ }
+
+ if (ioctl(fd, ENCIOC_SETELMSTAT, (caddr_t) &o) < 0) {
+ close(fd);
+ err(EXIT_FAILURE, "ENCIOC_SETELMSTAT");
+ }
+}
+
+static bool
+disk_match(const char *devnames, const char *disk, size_t len)
+{
+ const char *dname;
+
+ dname = devnames;
+ while ((dname = strstr(dname, disk)) != NULL) {
+ if (dname[len] == '\0' || dname[len] == ',') {
+ return (true);
+ }
+ dname++;
+ }
+
+ return (false);
+}
+
+static int
+sesled(int argc, char **argv, bool setfault)
+{
+ encioc_elm_devnames_t objdn;
+ encioc_element_t *objp;
+ glob_t g;
+ char *disk, *endptr;
+ size_t len, i, ndisks;
+ int fd;
+ unsigned int nobj, j, sesid;
+ bool all, isses, onoff;
+
+ isses = false;
+ all = false;
+ onoff = false;
+
+ if (argc != 3) {
+ usage(stderr, (setfault ? "fault" : "locate"));
+ }
+
+ disk = argv[1];
+
+ sesid = strtoul(disk, &endptr, 10);
+ if (*endptr == '\0') {
+ endptr = strrchr(uflag, '*');
+ if (endptr != NULL && *endptr == '*') {
+ warnx("Must specifying a SES device (-u) to use a SES "
+ "id# to identify a disk");
+ usage(stderr, (setfault ? "fault" : "locate"));
+ }
+ isses = true;
+ }
+
+ if (strcmp(argv[2], "on") == 0) {
+ onoff = true;
+ } else if (strcmp(argv[2], "off") == 0) {
+ onoff = false;
+ } else {
+ usage(stderr, (setfault ? "fault" : "locate"));
+ }
+
+ if (strcmp(disk, "all") == 0) {
+ all = true;
+ }
+ len = strlen(disk);
+
+ /* Get the list of ses devices */
+ if (glob((uflag != NULL ? uflag : "/dev/ses[0-9]*"), 0, NULL, &g) ==
+ GLOB_NOMATCH) {
+ globfree(&g);
+ errx(EXIT_FAILURE, "No SES devices found");
+ }
+
+ ndisks = 0;
+ for (i = 0; i < g.gl_pathc; i++) {
+ /* ensure we only got numbers after ses */
+ if (strspn(g.gl_pathv[i] + 8, "0123456789") !=
+ strlen(g.gl_pathv[i] + 8)) {
+ continue;
+ }
+ if ((fd = open(g.gl_pathv[i], O_RDWR)) < 0) {
+ /*
+ * Don't treat non-access errors as critical if we are
+ * accessing all devices
+ */
+ if (errno == EACCES && g.gl_pathc > 1) {
+ err(EXIT_FAILURE, "unable to access SES device");
+ }
+ warn("unable to access SES device: %s", g.gl_pathv[i]);
+ continue;
+ }
+
+ if (ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj) < 0) {
+ close(fd);
+ err(EXIT_FAILURE, "ENCIOC_GETNELM");
+ }
+
+ objp = calloc(nobj, sizeof(encioc_element_t));
+ if (objp == NULL) {
+ close(fd);
+ err(EXIT_FAILURE, "calloc()");
+ }
+
+ if (ioctl(fd, ENCIOC_GETELMMAP, (caddr_t) objp) < 0) {
+ close(fd);
+ err(EXIT_FAILURE, "ENCIOC_GETELMMAP");
+ }
+
+ if (isses) {
+ if (sesid > nobj) {
+ close(fd);
+ errx(EXIT_FAILURE,
+ "Requested SES ID does not exist");
+ }
+ do_led(fd, sesid, onoff, setfault);
+ ndisks++;
+ close(fd);
+ break;
+ }
+ for (j = 0; j < nobj; j++) {
+ memset(&objdn, 0, sizeof(objdn));
+ objdn.elm_idx = objp[j].elm_idx;
+ objdn.elm_names_size = 128;
+ objdn.elm_devnames = calloc(128, sizeof(char));
+ if (objdn.elm_devnames == NULL) {
+ close(fd);
+ err(EXIT_FAILURE, "calloc()");
+ }
+ if (ioctl(fd, ENCIOC_GETELMDEVNAMES,
+ (caddr_t) &objdn) <0) {
+ continue;
+ }
+ if (objdn.elm_names_len > 0) {
+ if (all) {
+ do_led(fd, objdn.elm_idx,
+ onoff, setfault);
+ continue;
+ }
+ if (disk_match(objdn.elm_devnames, disk, len)) {
+ do_led(fd, objdn.elm_idx,
+ onoff, setfault);
+ ndisks++;
+ break;
+ }
+ }
+ }
+ close(fd);
+ }
+ globfree(&g);
+ if (ndisks == 0 && all == false) {
+ errx(EXIT_FAILURE, "Count not find the SES id of device '%s'",
+ disk);
+ }
+
+ return (EXIT_SUCCESS);
+}
+
+static int
+locate(int argc, char **argv)
+{
+
+ return (sesled(argc, argv, false));
+}
+
+static int
+fault(int argc, char **argv)
+{
+
+ return (sesled(argc, argv, true));
+}
+
+static int
+objmap(int argc, char **argv __unused)
+{
+ struct sbuf *extra;
+ encioc_string_t stri;
+ encioc_elm_devnames_t e_devname;
+ encioc_elm_status_t e_status;
+ encioc_elm_desc_t e_desc;
+ encioc_element_t *e_ptr;
+ glob_t g;
+ int fd;
+ unsigned int j, nobj;
+ size_t i;
+ char str[32];
+
+ if (argc != 1) {
+ usage(stderr, "map");
+ }
+
+ /* Get the list of ses devices */
+ if (glob(uflag, 0, NULL, &g) == GLOB_NOMATCH) {
+ globfree(&g);
+ errx(EXIT_FAILURE, "No SES devices found");
+ }
+ for (i = 0; i < g.gl_pathc; i++) {
+ /* ensure we only got numbers after ses */
+ if (strspn(g.gl_pathv[i] + 8, "0123456789") !=
+ strlen(g.gl_pathv[i] + 8)) {
+ continue;
+ }
+ if ((fd = open(g.gl_pathv[i], O_RDWR)) < 0) {
+ /*
+ * Don't treat non-access errors as critical if we are
+ * accessing all devices
+ */
+ if (errno == EACCES && g.gl_pathc > 1) {
+ err(EXIT_FAILURE, "unable to access SES device");
+ }
+ warn("unable to access SES device: %s", g.gl_pathv[i]);
+ continue;
+ }
+
+ if (ioctl(fd, ENCIOC_GETNELM, (caddr_t) &nobj) < 0) {
+ close(fd);
+ err(EXIT_FAILURE, "ENCIOC_GETNELM");
+ }
+
+ e_ptr = calloc(nobj, sizeof(encioc_element_t));
+ if (e_ptr == NULL) {
+ close(fd);
+ err(EXIT_FAILURE, "calloc()");
+ }
+
+ if (ioctl(fd, ENCIOC_GETELMMAP, (caddr_t) e_ptr) < 0) {
+ close(fd);
+ err(EXIT_FAILURE, "ENCIOC_GETELMMAP");
+ }
+
+ printf("%s:\n", g.gl_pathv[i] + 5);
+ stri.bufsiz = sizeof(str);
+ stri.buf = &str[0];
+ if (ioctl(fd, ENCIOC_GETENCNAME, (caddr_t) &stri) == 0)
+ printf("\tEnclosure Name: %s\n", stri.buf);
+ stri.bufsiz = sizeof(str);
+ stri.buf = &str[0];
+ if (ioctl(fd, ENCIOC_GETENCID, (caddr_t) &stri) == 0)
+ printf("\tEnclosure ID: %s\n", stri.buf);
+
+ for (j = 0; j < nobj; j++) {
+ /* Get the status of the element */
+ memset(&e_status, 0, sizeof(e_status));
+ e_status.elm_idx = e_ptr[j].elm_idx;
+ if (ioctl(fd, ENCIOC_GETELMSTAT,
+ (caddr_t) &e_status) < 0) {
+ close(fd);
+ err(EXIT_FAILURE, "ENCIOC_GETELMSTAT");
+ }
+ /* Get the description of the element */
+ memset(&e_desc, 0, sizeof(e_desc));
+ e_desc.elm_idx = e_ptr[j].elm_idx;
+ e_desc.elm_desc_len = UINT16_MAX;
+ e_desc.elm_desc_str = calloc(UINT16_MAX, sizeof(char));
+ if (e_desc.elm_desc_str == NULL) {
+ close(fd);
+ err(EXIT_FAILURE, "calloc()");
+ }
+ if (ioctl(fd, ENCIOC_GETELMDESC,
+ (caddr_t) &e_desc) < 0) {
+ close(fd);
+ err(EXIT_FAILURE, "ENCIOC_GETELMDESC");
+ }
+ /* Get the device name(s) of the element */
+ memset(&e_devname, 0, sizeof(e_devname));
+ e_devname.elm_idx = e_ptr[j].elm_idx;
+ e_devname.elm_names_size = 128;
+ e_devname.elm_devnames = calloc(128, sizeof(char));
+ if (e_devname.elm_devnames == NULL) {
+ close(fd);
+ err(EXIT_FAILURE, "calloc()");
+ }
+ if (ioctl(fd, ENCIOC_GETELMDEVNAMES,
+ (caddr_t) &e_devname) <0) {
+ /* We don't care if this fails */
+ e_devname.elm_devnames[0] = '\0';
+ }
+ printf("\tElement %u, Type: %s\n", e_ptr[j].elm_idx,
+ geteltnm(e_ptr[j].elm_type));
+ printf("\t\tStatus: %s (0x%02x 0x%02x 0x%02x 0x%02x)\n",
+ scode2ascii(e_status.cstat[0]), e_status.cstat[0],
+ e_status.cstat[1], e_status.cstat[2],
+ e_status.cstat[3]);
+ if (e_desc.elm_desc_len > 0) {
+ printf("\t\tDescription: %s\n",
+ e_desc.elm_desc_str);
+ }
+ if (e_devname.elm_names_len > 0) {
+ printf("\t\tDevice Names: %s\n",
+ e_devname.elm_devnames);
+ }
+ extra = stat2sbuf(e_ptr[j].elm_type, e_status.cstat);
+ if (sbuf_len(extra) > 0) {
+ printf("\t\tExtra status:\n%s",
+ sbuf_data(extra));
+ }
+ sbuf_delete(extra);
+ free(e_devname.elm_devnames);
+ }
+ close(fd);
+ }
+ globfree(&g);
+
+ return (EXIT_SUCCESS);
+}
+
+static int
+encstatus(int argc, char **argv __unused)
+{
+ glob_t g;
+ int fd, status;
+ size_t i, e;
+ u_char estat;
+
+ status = 0;
+ if (argc != 1) {
+ usage(stderr, "status");
+ }
+
+ /* Get the list of ses devices */
+ if (glob(uflag, 0, NULL, &g) == GLOB_NOMATCH) {
+ globfree(&g);
+ errx(EXIT_FAILURE, "No SES devices found");
+ }
+ for (i = 0; i < g.gl_pathc; i++) {
+ /* ensure we only got numbers after ses */
+ if (strspn(g.gl_pathv[i] + 8, "0123456789") !=
+ strlen(g.gl_pathv[i] + 8)) {
+ continue;
+ }
+ if ((fd = open(g.gl_pathv[i], O_RDWR)) < 0) {
+ /*
+ * Don't treat non-access errors as critical if we are
+ * accessing all devices
+ */
+ if (errno == EACCES && g.gl_pathc > 1) {
+ err(EXIT_FAILURE, "unable to access SES device");
+ }
+ warn("unable to access SES device: %s", g.gl_pathv[i]);
+ continue;
+ }
+
+ if (ioctl(fd, ENCIOC_GETENCSTAT, (caddr_t) &estat) < 0) {
+ close(fd);
+ err(EXIT_FAILURE, "ENCIOC_GETENCSTAT");
+ }
+
+ printf("%s: ", g.gl_pathv[i] + 5);
+ e = 0;
+ if (estat == 0) {
+ if (status == 0) {
+ status = 1;
+ }
+ printf("OK");
+ } else {
+ if (estat & SES_ENCSTAT_INFO) {
+ printf("INFO");
+ e++;
+ }
+ if (estat & SES_ENCSTAT_NONCRITICAL) {
+ if (e)
+ printf(",");
+ printf("NONCRITICAL");
+ e++;
+ }
+ if (estat & SES_ENCSTAT_CRITICAL) {
+ if (e)
+ printf(",");
+ printf("CRITICAL");
+ e++;
+ status = -1;
+ }
+ if (estat & SES_ENCSTAT_UNRECOV) {
+ if (e)
+ printf(",");
+ printf("UNRECOV");
+ e++;
+ status = -1;
+ }
+ }
+ printf("\n");
+
+ close(fd);
+ }
+ globfree(&g);
+
+ if (status == 1) {
+ return (EXIT_SUCCESS);
+ } else {
+ return (EXIT_FAILURE);
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ int i, ch;
+ struct command *cmd = NULL;
+
+ uflag = "/dev/ses[0-9]*";
+ while ((ch = getopt_long(argc, argv, "u:", NULL, NULL)) != -1) {
+ switch (ch) {
+ case 'u':
+ uflag = optarg;
+ break;
+ case '?':
+ default:
+ usage(stderr, NULL);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1) {
+ warnx("Missing command");
+ usage(stderr, NULL);
+ }
+
+ for (i = 0; i < nbcmds; i++) {
+ if (strcmp(argv[0], cmds[i].name) == 0) {
+ cmd = &cmds[i];
+ break;
+ }
+ }
+
+ if (cmd == NULL) {
+ warnx("unknown command %s", argv[0]);
+ usage(stderr, NULL);
+ }
+
+ return (cmd->exec(argc, argv));
+}
diff --git a/usr.sbin/setfib/Makefile b/usr.sbin/setfib/Makefile
new file mode 100644
index 0000000..8508ed1
--- /dev/null
+++ b/usr.sbin/setfib/Makefile
@@ -0,0 +1,6 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+PROG= setfib
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/setfib/Makefile.depend b/usr.sbin/setfib/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/setfib/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/setfib/setfib.1 b/usr.sbin/setfib/setfib.1
new file mode 100644
index 0000000..5c36748
--- /dev/null
+++ b/usr.sbin/setfib/setfib.1
@@ -0,0 +1,98 @@
+.\" Copyright (c) 2008 Cisco systems
+.\" Author Julian Elischer. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. 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 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 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 20, 2008
+.Dt SETFIB 1
+.Os
+.Sh NAME
+.Nm setfib
+.Nd execute a utility with an altered default network view
+.Sh SYNOPSIS
+.Nm
+.Op Fl F
+.Ar fib
+.Ar utility
+.Op Ar argument ...
+.Sh DESCRIPTION
+The
+.Nm
+utility runs another
+.Ar utility
+with a different routing table.
+The table number
+.Ar fib
+will be used by default for all sockets started by this
+process or descendants.
+.Sh ENVIRONMENT
+The
+.Ev PATH
+environment variable is used to locate the requested
+.Ar utility
+if the name contains no
+.Ql /
+characters.
+.Sh EXIT STATUS
+If
+.Ar utility
+is invoked, the exit status of
+.Nm
+is the exit status of
+.Ar utility .
+.Pp
+An exit status of 126 indicates
+.Ar utility
+was found, but could not be executed.
+An exit status of 127 indicates
+.Ar utility
+could not be found.
+.Sh EXAMPLES
+Run
+.Xr netstat 1
+to view the second routing table.
+.Pp
+.Dl "setfib -F 1 netstat -rn"
+or
+.Dl "setfib 1 netstat -rn"
+or
+.Dl "setfib -1 netstat -rn"
+.Sh SEE ALSO
+.Xr setfib 2 ,
+.Xr setsockopt 2
+.Sh STANDARDS
+The
+.Nm
+utility is a
+.Fx
+specific extension.
+However, many
+.Ux Ns - Ns
+like systems
+have an equivalent function.
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 7.1 .
diff --git a/usr.sbin/setfib/setfib.c b/usr.sbin/setfib/setfib.c
new file mode 100644
index 0000000..f4b6c3d
--- /dev/null
+++ b/usr.sbin/setfib/setfib.c
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 2008 Cisco Systems, All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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.
+ *
+ * setfib file skelaton taken from nice.c
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ long fib = 0;
+ int ch;
+ char *ep;
+ int numfibs;
+ size_t intsize = sizeof(int);
+
+ if (sysctlbyname("net.fibs", &numfibs, &intsize, NULL, 0) == -1)
+ errx(1, "Multiple FIBS not supported");
+ if (argc < 2)
+ usage();
+ ep = argv[1];
+ /*
+ * convert -N or N to -FN. (N is a number)
+ */
+ if (ep[0]== '-' && isdigit((unsigned char)ep[1]))
+ ep++;
+ if (isdigit((unsigned char)*ep))
+ if (asprintf(&argv[1], "-F%s", ep) < 0)
+ err(1, "asprintf");
+
+ while ((ch = getopt(argc, argv, "F:")) != -1) {
+ switch (ch) {
+ case 'F':
+ errno = 0;
+ fib = strtol(optarg, &ep, 10);
+ if (ep == optarg || *ep != '\0' || errno ||
+ fib < 0 || fib >= numfibs)
+ errx(1, "%s: invalid FIB (max %d)",
+ optarg, numfibs - 1);
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0)
+ usage();
+
+ errno = 0;
+ if (setfib((int)fib))
+ warn("setfib");
+ execvp(*argv, argv);
+ err(errno == ENOENT ? 127 : 126, "%s", *argv);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr,
+ "usage: setfib [-[F]]value command\n");
+ exit(1);
+}
diff --git a/usr.sbin/setfmac/Makefile b/usr.sbin/setfmac/Makefile
new file mode 100644
index 0000000..bcbd85f
--- /dev/null
+++ b/usr.sbin/setfmac/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+PROG= setfmac
+LINKS= ${BINDIR}/setfmac ${BINDIR}/setfsmac
+MAN= setfmac.8 setfsmac.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/setfmac/Makefile.depend b/usr.sbin/setfmac/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/setfmac/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/setfmac/setfmac.8 b/usr.sbin/setfmac/setfmac.8
new file mode 100644
index 0000000..b541a4a
--- /dev/null
+++ b/usr.sbin/setfmac/setfmac.8
@@ -0,0 +1,66 @@
+.\" Copyright (c) 2002, 2004 Networks Associates Technology, Inc.
+.\" All rights reserved.
+.\"
+.\" This software was developed for the FreeBSD Project by Chris
+.\" Costello at Safeport Network Services 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 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, 2004
+.Dt SETFMAC 8
+.Os
+.Sh NAME
+.Nm setfmac
+.Nd set MAC label for a file system object
+.Sh SYNOPSIS
+.Nm setfmac
+.Op Fl hqR
+.Ar label
+.Ar
+.Sh DESCRIPTION
+The
+.Nm setfmac
+utility assigns the specified MAC label to the specified files.
+The following options are available:
+.Bl -tag -width indent
+.It Fl R
+Set the labels on the file hierarchies rooted in the files instead of
+just the files themselves.
+.It Fl h
+If the file is a symbolic link, change the label of the link rather
+than the file that the link points to.
+.It Fl q
+Do not print non-fatal warnings during execution.
+.El
+.Sh SEE ALSO
+.Xr mac 3 ,
+.Xr mac_set_file 3 ,
+.Xr mac_set_link 3 ,
+.Xr mac 4 ,
+.Xr re_format 7 ,
+.Xr getfmac 8 ,
+.Xr setfsmac 8 ,
+.Xr mac 9
diff --git a/usr.sbin/setfmac/setfmac.c b/usr.sbin/setfmac/setfmac.c
new file mode 100644
index 0000000..e3da25d
--- /dev/null
+++ b/usr.sbin/setfmac/setfmac.c
@@ -0,0 +1,498 @@
+/*-
+ * Copyright (c) 2002, 2004 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.
+ *
+ * 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/mac.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fts.h>
+#include <libgen.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+struct label_spec {
+ struct label_spec_entry {
+ regex_t regex; /* compiled regular expression to match */
+ char *regexstr; /* uncompiled regular expression */
+ mode_t mode; /* mode to possibly match */
+ const char *modestr; /* print-worthy ",-?" mode string */
+ char *mactext; /* MAC label to apply */
+ int flags; /* miscellaneous flags */
+#define F_DONTLABEL 0x01
+#define F_ALWAYSMATCH 0x02
+ } *entries, /* entries[0..nentries] */
+ *match; /* cached decision for MAC label to apply */
+ size_t nentries; /* size of entries list */
+ STAILQ_ENTRY(label_spec) link;
+};
+
+struct label_specs {
+ STAILQ_HEAD(label_specs_head, label_spec) head;
+};
+
+void usage(int) __dead2;
+struct label_specs *new_specs(void);
+void add_specs(struct label_specs *, const char *, int);
+void add_setfmac_specs(struct label_specs *, char *);
+void add_spec_line(const char *, int, struct label_spec_entry *, char *);
+int apply_specs(struct label_specs *, FTSENT *, int, int);
+int specs_empty(struct label_specs *);
+
+static int qflag;
+
+int
+main(int argc, char **argv)
+{
+ FTSENT *ftsent;
+ FTS *fts;
+ struct label_specs *specs;
+ int eflag = 0, xflag = 0, vflag = 0, Rflag = 0, hflag;
+ int ch, is_setfmac;
+ char *bn;
+
+ bn = basename(argv[0]);
+ if (bn == NULL)
+ err(1, "basename");
+ is_setfmac = strcmp(bn, "setfmac") == 0;
+ hflag = is_setfmac ? FTS_LOGICAL : FTS_PHYSICAL;
+ specs = new_specs();
+ while ((ch = getopt(argc, argv, is_setfmac ? "Rhq" : "ef:qs:vx")) !=
+ -1) {
+ switch (ch) {
+ case 'R':
+ Rflag = 1;
+ break;
+ case 'e':
+ eflag = 1;
+ break;
+ case 'f':
+ add_specs(specs, optarg, 0);
+ break;
+ case 'h':
+ hflag = FTS_PHYSICAL;
+ break;
+ case 'q':
+ qflag = 1;
+ break;
+ case 's':
+ add_specs(specs, optarg, 1);
+ break;
+ case 'v':
+ vflag++;
+ break;
+ case 'x':
+ xflag = FTS_XDEV;
+ break;
+ default:
+ usage(is_setfmac);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (is_setfmac) {
+ if (argc <= 1)
+ usage(is_setfmac);
+ add_setfmac_specs(specs, *argv);
+ argc--;
+ argv++;
+ } else {
+ if (argc == 0 || specs_empty(specs))
+ usage(is_setfmac);
+ }
+ fts = fts_open(argv, hflag | xflag, NULL);
+ if (fts == NULL)
+ err(1, "cannot traverse filesystem%s", argc ? "s" : "");
+ while ((ftsent = fts_read(fts)) != NULL) {
+ switch (ftsent->fts_info) {
+ case FTS_DP: /* skip post-order */
+ break;
+ case FTS_D: /* do pre-order */
+ case FTS_DC: /* do cyclic? */
+ /* don't ever recurse directories as setfmac(8) */
+ if (is_setfmac && !Rflag)
+ fts_set(fts, ftsent, FTS_SKIP);
+ case FTS_DEFAULT: /* do default */
+ case FTS_F: /* do regular */
+ case FTS_SL: /* do symlink */
+ case FTS_SLNONE: /* do symlink */
+ case FTS_W: /* do whiteout */
+ if (apply_specs(specs, ftsent, hflag, vflag)) {
+ if (eflag) {
+ errx(1, "labeling not supported in %s",
+ ftsent->fts_path);
+ }
+ if (!qflag)
+ warnx("labeling not supported in %s",
+ ftsent->fts_path);
+ fts_set(fts, ftsent, FTS_SKIP);
+ }
+ break;
+ case FTS_DNR: /* die on all errors */
+ case FTS_ERR:
+ case FTS_NS:
+ err(1, "traversing %s", ftsent->fts_path);
+ default:
+ errx(1, "CANNOT HAPPEN (%d) traversing %s",
+ ftsent->fts_info, ftsent->fts_path);
+ }
+ }
+ fts_close(fts);
+ exit(0);
+}
+
+void
+usage(int is_setfmac)
+{
+
+ if (is_setfmac)
+ fprintf(stderr, "usage: setfmac [-Rhq] label file ...\n");
+ else
+ fprintf(stderr, "usage: setfsmac [-ehqvx] [-f specfile [...]] [-s specfile [...]] file ...\n");
+ exit(1);
+}
+
+static int
+chomp_line(char **line, size_t *linesize)
+{
+ char *s;
+ int freeme = 0;
+
+ for (s = *line; (unsigned)(s - *line) < *linesize; s++) {
+ if (!isspace(*s))
+ break;
+ }
+ if (*s == '#') {
+ **line = '\0';
+ *linesize = 0;
+ return (freeme);
+ }
+ memmove(*line, s, *linesize - (s - *line));
+ *linesize -= s - *line;
+ for (s = &(*line)[*linesize - 1]; s >= *line; s--) {
+ if (!isspace(*s))
+ break;
+ }
+ if (s != &(*line)[*linesize - 1]) {
+ *linesize = s - *line + 1;
+ } else {
+ s = malloc(*linesize + 1);
+ if (s == NULL)
+ err(1, "malloc");
+ strncpy(s, *line, *linesize);
+ *line = s;
+ freeme = 1;
+ }
+ (*line)[*linesize] = '\0';
+ return (freeme);
+}
+
+void
+add_specs(struct label_specs *specs, const char *file, int is_sebsd)
+{
+ struct label_spec *spec;
+ FILE *fp;
+ char *line;
+ size_t nlines = 0, linesize;
+ int freeline;
+
+ spec = malloc(sizeof(*spec));
+ if (spec == NULL)
+ err(1, "malloc");
+ fp = fopen(file, "r");
+ if (fp == NULL)
+ err(1, "opening %s", file);
+ while ((line = fgetln(fp, &linesize)) != NULL) {
+ freeline = chomp_line(&line, &linesize);
+ if (linesize > 0) /* only allocate space for non-comments */
+ nlines++;
+ if (freeline)
+ free(line);
+ }
+ if (ferror(fp))
+ err(1, "fgetln on %s", file);
+ rewind(fp);
+ spec->entries = calloc(nlines, sizeof(*spec->entries));
+ if (spec->entries == NULL)
+ err(1, "malloc");
+ spec->nentries = nlines;
+ while (nlines > 0) {
+ line = fgetln(fp, &linesize);
+ if (line == NULL) {
+ if (feof(fp))
+ errx(1, "%s ended prematurely", file);
+ else
+ err(1, "failure reading %s", file);
+ }
+ freeline = chomp_line(&line, &linesize);
+ if (linesize == 0) {
+ if (freeline)
+ free(line);
+ continue;
+ }
+ add_spec_line(file, is_sebsd, &spec->entries[--nlines], line);
+ if (freeline)
+ free(line);
+ }
+ fclose(fp);
+ if (!qflag)
+ warnx("%s: read %lu specifications", file,
+ (long)spec->nentries);
+ STAILQ_INSERT_TAIL(&specs->head, spec, link);
+}
+
+void
+add_setfmac_specs(struct label_specs *specs, char *label)
+{
+ struct label_spec *spec;
+
+ spec = malloc(sizeof(*spec));
+ if (spec == NULL)
+ err(1, "malloc");
+ spec->nentries = 1;
+ spec->entries = calloc(spec->nentries, sizeof(*spec->entries));
+ if (spec->entries == NULL)
+ err(1, "malloc");
+ /* The _only_ thing specified here is the mactext! */
+ spec->entries->mactext = label;
+ spec->entries->flags |= F_ALWAYSMATCH;
+ STAILQ_INSERT_TAIL(&specs->head, spec, link);
+}
+
+void
+add_spec_line(const char *file, int is_sebsd, struct label_spec_entry *entry,
+ char *line)
+{
+ char *regexstr, *modestr, *macstr, *regerrorstr;
+ size_t size;
+ int error;
+
+ regexstr = strtok(line, " \t");
+ if (regexstr == NULL)
+ errx(1, "%s: need regular expression", file);
+ modestr = strtok(NULL, " \t");
+ if (modestr == NULL)
+ errx(1, "%s: need a label", file);
+ macstr = strtok(NULL, " \t");
+ if (macstr == NULL) { /* the mode is just optional */
+ macstr = modestr;
+ modestr = NULL;
+ }
+ if (strtok(NULL, " \t") != NULL)
+ errx(1, "%s: extraneous fields at end of line", file);
+ /* assume we need to anchor this regex */
+ if (asprintf(&regexstr, "^%s$", regexstr) == -1)
+ err(1, "%s: processing regular expression", file);
+ entry->regexstr = regexstr;
+ error = regcomp(&entry->regex, regexstr, REG_EXTENDED | REG_NOSUB);
+ if (error) {
+ size = regerror(error, &entry->regex, NULL, 0);
+ regerrorstr = malloc(size);
+ if (regerrorstr == NULL)
+ err(1, "malloc");
+ (void)regerror(error, &entry->regex, regerrorstr, size);
+ errx(1, "%s: %s: %s", file, entry->regexstr, regerrorstr);
+ }
+ if (!is_sebsd) {
+ entry->mactext = strdup(macstr);
+ if (entry->mactext == NULL)
+ err(1, "strdup");
+ } else {
+ if (asprintf(&entry->mactext, "sebsd/%s", macstr) == -1)
+ err(1, "asprintf");
+ if (strcmp(macstr, "<<none>>") == 0)
+ entry->flags |= F_DONTLABEL;
+ }
+ if (modestr != NULL) {
+ if (strlen(modestr) != 2 || modestr[0] != '-')
+ errx(1, "%s: invalid mode string: %s", file, modestr);
+ switch (modestr[1]) {
+ case 'b':
+ entry->mode = S_IFBLK;
+ entry->modestr = ",-b";
+ break;
+ case 'c':
+ entry->mode = S_IFCHR;
+ entry->modestr = ",-c";
+ break;
+ case 'd':
+ entry->mode = S_IFDIR;
+ entry->modestr = ",-d";
+ break;
+ case 'p':
+ entry->mode = S_IFIFO;
+ entry->modestr = ",-p";
+ break;
+ case 'l':
+ entry->mode = S_IFLNK;
+ entry->modestr = ",-l";
+ break;
+ case 's':
+ entry->mode = S_IFSOCK;
+ entry->modestr = ",-s";
+ break;
+ case '-':
+ entry->mode = S_IFREG;
+ entry->modestr = ",--";
+ break;
+ default:
+ errx(1, "%s: invalid mode string: %s", file, modestr);
+ }
+ } else {
+ entry->modestr = "";
+ }
+}
+
+int
+specs_empty(struct label_specs *specs)
+{
+
+ return (STAILQ_EMPTY(&specs->head));
+}
+
+int
+apply_specs(struct label_specs *specs, FTSENT *ftsent, int hflag, int vflag)
+{
+ regmatch_t pmatch;
+ struct label_spec *ls;
+ struct label_spec_entry *ent;
+ char *regerrorstr, *macstr;
+ size_t size;
+ mac_t mac;
+ int error, matchedby;
+
+ /*
+ * Work through file context sources in order of specification
+ * on the command line, and through their entries in reverse
+ * order to find the "last" (hopefully "best") match.
+ */
+ matchedby = 0;
+ STAILQ_FOREACH(ls, &specs->head, link) {
+ for (ls->match = NULL, ent = ls->entries;
+ ent < &ls->entries[ls->nentries]; ent++) {
+ if (ent->flags & F_ALWAYSMATCH)
+ goto matched;
+ if (ent->mode != 0 &&
+ (ftsent->fts_statp->st_mode & S_IFMT) != ent->mode)
+ continue;
+ pmatch.rm_so = 0;
+ pmatch.rm_eo = ftsent->fts_pathlen;
+ error = regexec(&ent->regex, ftsent->fts_path, 1,
+ &pmatch, REG_STARTEND);
+ switch (error) {
+ case REG_NOMATCH:
+ continue;
+ case 0:
+ break;
+ default:
+ size = regerror(error, &ent->regex, NULL, 0);
+ regerrorstr = malloc(size);
+ if (regerrorstr == NULL)
+ err(1, "malloc");
+ (void)regerror(error, &ent->regex, regerrorstr,
+ size);
+ errx(1, "%s: %s", ent->regexstr, regerrorstr);
+ }
+ matched:
+ ls->match = ent;
+ if (vflag) {
+ if (matchedby == 0) {
+ printf("%s matched by ",
+ ftsent->fts_path);
+ matchedby = 1;
+ }
+ printf("%s(%s%s,%s)", matchedby == 2 ? "," : "",
+ ent->regexstr, ent->modestr, ent->mactext);
+ if (matchedby == 1)
+ matchedby = 2;
+ }
+ break;
+ }
+ }
+ if (vflag && matchedby)
+ printf("\n");
+ size = 0;
+ STAILQ_FOREACH(ls, &specs->head, link) {
+ /* cached match decision */
+ if (ls->match && (ls->match->flags & F_DONTLABEL) == 0)
+ /* add length of "x\0"/"y," */
+ size += strlen(ls->match->mactext) + 1;
+ }
+ if (size == 0)
+ return (0);
+ macstr = malloc(size);
+ if (macstr == NULL)
+ err(1, "malloc");
+ *macstr = '\0';
+ STAILQ_FOREACH(ls, &specs->head, link) {
+ /* cached match decision */
+ if (ls->match && (ls->match->flags & F_DONTLABEL) == 0) {
+ if (*macstr != '\0')
+ strcat(macstr, ",");
+ strcat(macstr, ls->match->mactext);
+ }
+ }
+ if (mac_from_text(&mac, macstr))
+ err(1, "mac_from_text(%s)", macstr);
+ if ((hflag == FTS_PHYSICAL ? mac_set_link(ftsent->fts_accpath, mac) :
+ mac_set_file(ftsent->fts_accpath, mac)) != 0) {
+ if (errno == EOPNOTSUPP) {
+ mac_free(mac);
+ free(macstr);
+ return (1);
+ }
+ err(1, "mac_set_link(%s, %s)", ftsent->fts_path, macstr);
+ }
+ mac_free(mac);
+ free(macstr);
+ return (0);
+}
+
+struct label_specs *
+new_specs(void)
+{
+ struct label_specs *specs;
+
+ specs = malloc(sizeof(*specs));
+ if (specs == NULL)
+ err(1, "malloc");
+ STAILQ_INIT(&specs->head);
+ return (specs);
+}
diff --git a/usr.sbin/setfmac/setfsmac.8 b/usr.sbin/setfmac/setfsmac.8
new file mode 100644
index 0000000..9bdb0e8
--- /dev/null
+++ b/usr.sbin/setfmac/setfsmac.8
@@ -0,0 +1,129 @@
+.\" Copyright (c) 2003, 2004 Networks Associates Technology, Inc.
+.\" All rights reserved.
+.\"
+.\" This software was developed for the FreeBSD Project by Chris Costello
+.\" at Safeport Network Services and Network Associates 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 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, 2004
+.Dt SETFSMAC 8
+.Os
+.Sh NAME
+.Nm setfsmac
+.Nd set MAC label for a file hierarchy
+.Sh SYNOPSIS
+.Nm
+.Op Fl ehqvx
+.Oo Fl f Ar specfile Oc ...
+.Oo Fl s Ar specfile Oc ...
+.Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility accepts a list of specification files as input and sets the MAC
+labels on the specified file system hierarchies.
+Path names specified will be visited in order as given on the command
+line, and each tree will be traversed in pre-order.
+(Generally, it will not be very useful to use relative paths instead of
+absolute paths.)
+Multiple entries matching a single file will be combined and applied in
+a single transaction.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl e
+Treat any file systems encountered which do not support MAC labelling as
+errors, instead of warning and skipping them.
+.It Fl f Ar specfile
+Apply the specifications in
+.Ar specfile
+to the specified paths.
+.\" XXX
+.Bf -emphasis
+NOTE: Only the first entry for each file is applied;
+all others are disregarded and silently dropped.
+.Ef
+Multiple
+.Fl f
+arguments may be specified to include multiple
+specification files.
+.It Fl h
+When a symbolic link is encountered, change the label of the link rather
+than the file the link points to.
+.It Fl q
+Do not print non-fatal warnings during execution.
+.It Fl s Ar specfile
+Apply the specifications in
+.Ar specfile ,
+but assume the specification format is compatible with the SELinux
+.Ar specfile
+format.
+.\" XXX
+.Bf -emphasis
+NOTE: Only the first entry for each file is applied;
+all others are disregarded and silently dropped.
+.Ef
+The prefix
+.Dq Li sebsd/
+will be automatically prepended to the labels in
+.Ar specfile .
+Labels matching
+.Dq Li <<none>>
+will be explicitly not relabeled.
+This permits SEBSD to reuse existing SELinux policy specification files.
+.It Fl v
+Increase the degree of verbosity.
+.It Fl x
+Do not recurse into new file systems when traversing them.
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /usr/share/security/lomac-policy.contexts" -compact
+.It Pa /usr/share/security/lomac-policy.contexts
+Sample specfile containing LOMAC policy entries.
+.El
+.Sh EXAMPLES
+See
+.Sx FILES .
+.Sh SEE ALSO
+.Xr mac 3 ,
+.Xr mac_set_file 3 ,
+.Xr mac_set_link 3 ,
+.Xr mac 4 ,
+.Xr re_format 7 ,
+.Xr getfmac 8 ,
+.Xr setfmac 8 ,
+.Xr mac 9
+.Sh AUTHORS
+This software was contributed to the
+.Fx
+Project by Network Associates 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.
diff --git a/usr.sbin/setpmac/Makefile b/usr.sbin/setpmac/Makefile
new file mode 100644
index 0000000..a8bc1ac
--- /dev/null
+++ b/usr.sbin/setpmac/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= setpmac
+MAN= setpmac.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/setpmac/Makefile.depend b/usr.sbin/setpmac/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/setpmac/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/setpmac/setpmac.8 b/usr.sbin/setpmac/setpmac.8
new file mode 100644
index 0000000..c5c2963
--- /dev/null
+++ b/usr.sbin/setpmac/setpmac.8
@@ -0,0 +1,65 @@
+.\" Copyright (c) 2003 Networks Associates Technology, Inc.
+.\" All rights reserved.
+.\"
+.\" This software was developed for the FreeBSD Project by Chris Costello
+.\" at Safeport Network Services and Network Associates 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 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 14, 2003
+.Dt SETPMAC 8
+.Os
+.Sh NAME
+.Nm setpmac
+.Nd "run a command with a different MAC process label"
+.Sh SYNOPSIS
+.Nm
+.Ar label
+.Ar command
+.Op Ar arg ...
+.Sh DESCRIPTION
+The
+.Nm
+utility forks a new process, attempts to set the label to
+.Ar label
+and if successful, runs
+.Ar command .
+.Sh SEE ALSO
+.Xr mac 4 ,
+.Xr maclabel 7 ,
+.Xr getfmac 8 ,
+.Xr getpmac 8 ,
+.Xr setfmac 8
+.Sh AUTHORS
+This software was contributed to the
+.Fx
+Project by Network Associates 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.
diff --git a/usr.sbin/setpmac/setpmac.c b/usr.sbin/setpmac/setpmac.c
new file mode 100644
index 0000000..bc096bb
--- /dev/null
+++ b/usr.sbin/setpmac/setpmac.c
@@ -0,0 +1,92 @@
+/*-
+ * Copyright (c) 2002 Networks Associates Technology, Inc.
+ * All rights reserved.
+ *
+ * This software was developed for the FreeBSD Project by 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/types.h>
+#include <sys/mac.h>
+
+#include <err.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#define MAXELEMENTS 32
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "setpmac [label] [command] [args ...]\n");
+ exit (EX_USAGE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ const char *shell;
+ mac_t label;
+ int error;
+
+
+ if (argc < 3)
+ usage();
+
+ error = mac_from_text(&label, argv[1]);
+ if (error != 0) {
+ perror("mac_from_text");
+ return (-1);
+ }
+
+ error = mac_set_proc(label);
+ if (error != 0) {
+ perror(argv[1]);
+ return (-1);
+ }
+
+ mac_free(label);
+
+ if (argc >= 3) {
+ execvp(argv[2], argv + 2);
+ err(1, "%s", argv[2]);
+ } else {
+ if (!(shell = getenv("SHELL")))
+ shell = _PATH_BSHELL;
+ execlp(shell, shell, "-i", (char *)NULL);
+ err(1, "%s", shell);
+ }
+ /* NOTREACHED */
+}
diff --git a/usr.sbin/sicontrol/Makefile b/usr.sbin/sicontrol/Makefile
new file mode 100644
index 0000000..eb31205
--- /dev/null
+++ b/usr.sbin/sicontrol/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PROG= sicontrol
+MAN= sicontrol.8
+
+CFLAGS+= -I${.CURDIR}/../../sys
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/sicontrol/Makefile.depend b/usr.sbin/sicontrol/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/sicontrol/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/sicontrol/sicontrol.8 b/usr.sbin/sicontrol/sicontrol.8
new file mode 100644
index 0000000..1503591
--- /dev/null
+++ b/usr.sbin/sicontrol/sicontrol.8
@@ -0,0 +1,109 @@
+.\" $FreeBSD$
+.\"
+.Dd September 26, 1995
+.Dt SICONTROL 8
+.Os
+.Sh NAME
+.Nm sicontrol
+.Nd Specialix SI/XIO driver configuration and debugging
+.Sh SYNOPSIS
+.Nm
+device
+.Ar command Op Ar param ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to configure and monitor the SI/XIO device driver.
+.Pp
+The
+.Nm
+utility operates on the specified
+.Ar device
+to indicate which port is to be used.
+.Pp
+The special
+.Ar device
+string `-' is used to indicate the global driver settings instead.
+.Pp
+A
+.Pa /dev/
+is included if necessary.
+.Pp
+The following commands are used for the global settings and should be
+specified with the '-' device name:
+.Bl -tag -width 4n
+.It Cm int_throttle Op Cm value
+Configure the `aggregate interrupt throttle value'.
+The maximum number of host adapter interrupts per second is determined by:
+.Pp
+.Ar "controller CPU clock / (8 * int_throttle)"
+.Pp
+The default value at boot time is 25000.
+The host adapter cpu clock is
+25MHz.
+This gives a maximum interrupt rate of about 125 interrupts per
+second.
+.Pp
+Lowering this value will increase the rate in which the host adapter can
+interrupt the operating system for attention.
+.\"
+.It Cm rxint_throttle Op Cm value
+Configure the receiver interrupt throttle value.
+The default value of 4 at boot time allows an interrupt rate of
+approximately 25.
+.Pp
+Lowering this value will increase the rate in which the host adapter can
+interrupt the operating system to empty the receiver fifos.
+.\"
+.It Cm nport
+Return the number of ports under the control of the device driver.
+.El
+.Pp
+The following commands are used for the individual ports and should be
+specified with a device name from
+.Pa /dev :
+.Bl -tag -width 4n
+.It Cm mstate
+Show the current incoming modem control signals.
+.It Cm ccbstat
+Show the current "ccb" structure for the specified port.
+This is not of
+much use outside of debugging the driver and determining why a port is
+wedged.
+.El
+.Sh FILES
+.Bl -tag -width /dev/si_control -compact
+.It Pa /dev/si_control
+global driver control file for use by
+.Nm
+.It Pa /dev/ttyA*
+terminal control ports
+.It Pa /dev/ttyiA*
+initial termios state devices, for use by
+.Xr stty 1
+.It Pa /dev/ttylA*
+locked termios state devices, for use by
+.Xr stty 1
+.El
+.Sh DIAGNOSTICS
+Generally self explanatory.....
+.Sh SEE ALSO
+.Xr stty 1 ,
+.Xr si 4 ,
+.Xr termios 4 ,
+.Xr tty 4 ,
+.Xr comcontrol 8
+.Sh HISTORY
+The
+.Nm
+utility is loosely based on a utility called
+.Nm siconfig
+which was written by
+.An Andy Rutter Aq Mt andy@acronym.co.uk .
+.Pp
+Specialix International do not support this device driver in any way.
+.Sh AUTHORS
+.An Peter Wemm Aq Mt peter@FreeBSD.org
+.Sh BUGS
+Bound to be many...
+:-)
diff --git a/usr.sbin/sicontrol/sicontrol.c b/usr.sbin/sicontrol/sicontrol.c
new file mode 100644
index 0000000..061807d
--- /dev/null
+++ b/usr.sbin/sicontrol/sicontrol.c
@@ -0,0 +1,726 @@
+/*
+ * Device driver for Specialix range (SLXOS) of serial line multiplexors.
+ * SLXOS configuration and debug interface
+ *
+ * Copyright (C) 1990, 1992 Specialix International,
+ * Copyright (C) 1993, Andy Rutter <andy@acronym.co.uk>
+ * Copyright (C) 1995, Peter Wemm
+ *
+ * Derived from: SunOS 4.x version
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notices, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notices, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of Advanced Methods and Tools, nor Specialix
+ * International may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * 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.
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <ctype.h>
+#include <err.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/tty.h>
+
+#define SI_DEBUG
+#include <dev/si/si.h>
+#include <dev/si/sivar.h>
+
+struct lv {
+ char *lv_name;
+ int lv_bit;
+} lv[] = {
+ {"entry", DBG_ENTRY},
+ {"open", DBG_OPEN},
+ {"close", DBG_CLOSE},
+ {"read", DBG_READ},
+ {"write", DBG_WRITE},
+ {"param", DBG_PARAM},
+ {"modem", DBG_MODEM},
+ {"select", DBG_SELECT},
+ {"optim", DBG_OPTIM},
+ {"intr", DBG_INTR},
+ {"start", DBG_START},
+ {"lstart", DBG_LSTART},
+ {"ioctl", DBG_IOCTL},
+ {"fail", DBG_FAIL},
+ {"autoboot", DBG_AUTOBOOT},
+ {"download", DBG_DOWNLOAD},
+ {"drain", DBG_DRAIN},
+ {"poll", DBG_POLL},
+ {0, 0}
+};
+
+static int alldev = 0;
+
+void ccb_stat(int, char **);
+void port_stat(int, char **);
+void debug(int, char **);
+void dostat(void);
+int getnum(char *);
+int islevel(char *);
+int lvls2bits(char *);
+void mstate(int, char **);
+void nport(int, char **);
+void onoff(int, char **, int, char *, char *, int);
+int opencontrol(void);
+void prlevels(int);
+void prusage(int, int);
+void rxint(int, char **);
+void txint(int, char **);
+
+struct opt {
+ char *o_name;
+ void (*o_func)(int, char **);
+} opt[] = {
+ {"debug", debug},
+ {"rxint_throttle", rxint},
+ {"int_throttle", txint},
+ {"nport", nport},
+ {"mstate", mstate},
+ {"ccbstat", ccb_stat},
+ {"portstat", port_stat},
+ {0, 0}
+};
+
+struct stat_list {
+ void (*st_func)(int, char **);
+} stat_list[] = {
+ {mstate},
+ {0}
+};
+
+#define U_DEBUG 0
+#define U_TXINT 1
+#define U_RXINT 2
+#define U_NPORT 3
+#define U_MSTATE 4
+#define U_STAT_CCB 5
+#define U_STAT_PORT 6
+
+#define U_MAX 7
+#define U_ALL -1
+char *usage[] = {
+ "debug [[add|del|set debug_levels] | [off]]\n",
+ "int_throttle [newvalue]\n",
+ "rxint_throttle [newvalue]\n",
+ "nport\n",
+ "mstate\n",
+ "ccbstat\n",
+ "portstat\n",
+ 0
+};
+
+int ctlfd;
+char *Devname;
+struct si_tcsi tc;
+
+int
+main(int argc, char **argv)
+{
+ struct opt *op;
+ void (*func)(int, char **) = NULL;
+
+ if (argc < 2)
+ prusage(U_ALL, 1);
+ Devname = argv[1];
+ if (strcmp(Devname, "-") == 0) {
+ alldev = 1;
+ } else {
+ sidev_t dev;
+ int n;
+ int card, port;
+
+ n = sscanf(Devname, "%d:%d", &card, &port);
+ if (n != 2)
+ errx(1, "Devname must be in form card:port. eg: 0:7");
+ dev.sid_card = card;
+ dev.sid_port = port;
+ tc.tc_dev = dev;
+ }
+ ctlfd = opencontrol();
+ if (argc == 2) {
+ dostat();
+ exit(0);
+ }
+
+ argc--; argv++;
+ for (op = opt; op->o_name; op++) {
+ if (strcmp(argv[1], op->o_name) == 0) {
+ func = op->o_func;
+ break;
+ }
+ }
+ if (func == NULL)
+ prusage(U_ALL, 1);
+
+ argc -= 2;
+ argv += 2;
+ (*func)(argc, argv);
+ exit(0);
+}
+
+int
+opencontrol(void)
+{
+ int fd;
+
+ fd = open(CONTROLDEV, O_RDWR|O_NDELAY);
+ if (fd < 0)
+ err(1, "open on %s", CONTROLDEV);
+ return(fd);
+}
+
+/*
+ * Print a usage message - this relies on U_DEBUG==0 and U_BOOT==1.
+ * Don't print the DEBUG usage string unless explicity requested.
+ */
+void
+prusage(int strn, int eflag)
+{
+ char **cp;
+
+ if (strn == U_ALL) {
+ fprintf(stderr, "usage: sicontrol %s", usage[1]);
+ fprintf(stderr, " sicontrol %s", usage[2]);
+ fprintf(stderr, " sicontrol %s", usage[3]);
+ fprintf(stderr, " sicontrol devname %s", usage[4]);
+ for (cp = &usage[5]; *cp; cp++)
+ fprintf(stderr, " sicontrol devname %s", *cp);
+ }
+ else if (strn >= 0 && strn <= U_MAX)
+ fprintf(stderr, "usage: sicontrol devname %s", usage[strn]);
+ else
+ fprintf(stderr, "sicontrol: usage ???\n");
+ exit(eflag);
+}
+
+/* print port status */
+void
+dostat(void)
+{
+ char *av[1], *acp;
+ struct stat_list *stp;
+ struct si_tcsi stc;
+ int donefirst = 0;
+
+ printf("%s: ", alldev ? "ALL" : Devname);
+ acp = malloc(strlen(Devname) + 3);
+ memset(acp, ' ', strlen(Devname));
+ strcat(acp, " ");
+ stc = tc;
+ for (stp = stat_list; stp->st_func != NULL; stp++) {
+ if (donefirst)
+ fputs(acp, stdout);
+ else
+ donefirst++;
+ av[0] = NULL;
+ tc = stc;
+ (*stp->st_func)(-1, av);
+ }
+}
+
+/*
+ * debug
+ * debug [[set|add|del debug_lvls] | [off]]
+ */
+void
+debug(int ac, char **av)
+{
+ int level;
+
+ if (ac > 2)
+ prusage(U_DEBUG, 1);
+ if (alldev) {
+ if (ioctl(ctlfd, TCSIGDBG_ALL, &tc.tc_dbglvl) < 0)
+ err(1, "TCSIGDBG_ALL on %s", Devname);
+ } else {
+ if (ioctl(ctlfd, TCSIGDBG_LEVEL, &tc) < 0)
+ err(1, "TCSIGDBG_LEVEL on %s", Devname);
+ }
+
+ switch (ac) {
+ case 0:
+ printf("%s: debug levels - ", Devname);
+ prlevels(tc.tc_dbglvl);
+ return;
+ case 1:
+ if (strcmp(av[0], "off") == 0) {
+ tc.tc_dbglvl = 0;
+ break;
+ }
+ prusage(U_DEBUG, 1);
+ /* no return */
+ case 2:
+ level = lvls2bits(av[1]);
+ if (strcmp(av[0], "add") == 0)
+ tc.tc_dbglvl |= level;
+ else if (strcmp(av[0], "del") == 0)
+ tc.tc_dbglvl &= ~level;
+ else if (strcmp(av[0], "set") == 0)
+ tc.tc_dbglvl = level;
+ else
+ prusage(U_DEBUG, 1);
+ }
+ if (alldev) {
+ if (ioctl(ctlfd, TCSISDBG_ALL, &tc.tc_dbglvl) < 0)
+ err(1, "TCSISDBG_ALL on %s", Devname);
+ } else {
+ if (ioctl(ctlfd, TCSISDBG_LEVEL, &tc) < 0)
+ err(1, "TCSISDBG_LEVEL on %s", Devname);
+ }
+}
+
+void
+rxint(int ac, char **av)
+{
+ tc.tc_port = 0;
+ switch (ac) {
+ case 0:
+ printf("%s: ", Devname);
+ case -1:
+ if (ioctl(ctlfd, TCSIGRXIT, &tc) < 0)
+ err(1, "TCSIGRXIT");
+ printf("RX interrupt throttle: %d msec\n", tc.tc_int*10);
+ break;
+ case 1:
+ tc.tc_int = getnum(av[0]) / 10;
+ if (tc.tc_int == 0)
+ tc.tc_int = 1;
+ if (ioctl(ctlfd, TCSIRXIT, &tc) < 0)
+ err(1, "TCSIRXIT on %s at %d msec",
+ Devname, tc.tc_int*10);
+ break;
+ default:
+ prusage(U_RXINT, 1);
+ }
+}
+
+void
+txint(int ac, char **av)
+{
+
+ tc.tc_port = 0;
+ switch (ac) {
+ case 0:
+ printf("%s: ", Devname);
+ case -1:
+ if (ioctl(ctlfd, TCSIGIT, &tc) < 0)
+ err(1, "TCSIGIT");
+ printf("aggregate interrupt throttle: %d\n", tc.tc_int);
+ break;
+ case 1:
+ tc.tc_int = getnum(av[0]);
+ if (ioctl(ctlfd, TCSIIT, &tc) < 0)
+ err(1, "TCSIIT on %s at %d", Devname, tc.tc_int);
+ break;
+ default:
+ prusage(U_TXINT, 1);
+ }
+}
+
+void
+onoff(int ac, char **av, int cmd, char *cmdstr, char *prstr, int usage)
+{
+ if (ac > 1)
+ prusage(usage, 1);
+ if (ac == 1) {
+ if (strcmp(av[0], "on") == 0)
+ tc.tc_int = 1;
+ else if (strcmp(av[0], "off") == 0)
+ tc.tc_int = 0;
+ else
+ prusage(usage, 1);
+ } else
+ tc.tc_int = -1;
+ if (ioctl(ctlfd, cmd, &tc) < 0)
+ err(1, "%s on %s", cmdstr, Devname);
+ switch (ac) {
+ case 0:
+ printf("%s: ", Devname);
+ case -1:
+ printf("%s ", prstr);
+ if (tc.tc_int)
+ printf("on\n");
+ else
+ printf("off\n");
+ }
+}
+
+void
+mstate(int ac, char **av)
+{
+ switch (ac) {
+ case 0:
+ printf("%s: ", Devname);
+ case -1:
+ break;
+ default:
+ prusage(U_MSTATE, 1);
+ }
+ if (ioctl(ctlfd, TCSISTATE, &tc) < 0)
+ err(1, "TCSISTATE on %s", Devname);
+ printf("modem bits state - (0x%x)", tc.tc_int);
+ if (tc.tc_int & IP_DCD) printf(" DCD");
+ if (tc.tc_int & IP_DTR) printf(" DTR");
+ if (tc.tc_int & IP_RTS) printf(" RTS");
+ printf("\n");
+}
+
+void
+nport(int ac, char **av)
+{
+ int ports;
+
+ if (ac != 0)
+ prusage(U_NPORT, 1);
+ if (ioctl(ctlfd, TCSIPORTS, &ports) < 0)
+ err(1, "TCSIPORTS on %s", Devname);
+ printf("SLXOS: total of %d ports\n", ports);
+}
+
+const char *s_stat(int stat)
+{
+ switch (stat) {
+ case IDLE_OPEN: return "IDLE_OPEN";
+ case LOPEN: return "LOPEN";
+ case MOPEN: return "MOPEN";
+ case MPEND: return "MPEND";
+ case CONFIG: return "CONFIG";
+ case CLOSE: return "CLOSE";
+ case SBREAK: return "SBREAK";
+ case EBREAK: return "EBREAK";
+ case IDLE_CLOSE:return "IDLE_CLOSE";
+ case IDLE_BREAK:return "IDLE_BREAK";
+ case FCLOSE: return "FCLOSE";
+ case RESUME: return "RESUME";
+ case WFLUSH: return "WFLUSH";
+ case RFLUSH: return "RFLUSH";
+ default: return "??";
+ }
+}
+const char *s_mr1(int mr1)
+{
+ static char msg[200];
+
+ sprintf(msg, "%dbit, %s, parity:[", 5 + (mr1 & MR1_8_BITS), mr1 & MR1_ODD ? "odd" : "even");
+ if (mr1 & MR1_WITH)
+ strcat(msg, "with;");
+ if (mr1 & MR1_FORCE)
+ strcat(msg, "force;");
+ if (mr1 & MR1_NONE)
+ strcat(msg, "none;");
+ if (mr1 & MR1_SPECIAL)
+ strcat(msg, "special;");
+ strcpy(msg + strlen(msg) - 1, "]");
+ sprintf(msg + strlen(msg), ", err: %s", mr1 & MR1_BLOCK ? "block" : "none");
+ sprintf(msg + strlen(msg), ", cts: %s", mr1 & MR1_CTSCONT ? "auto" : "none");
+ return (msg);
+}
+const char *s_mr2(int mr2)
+{
+ static char msg[200];
+
+ switch (mr2 & 0xf) {
+ case MR2_1_STOP: strcpy(msg, "1stop"); break;
+ case MR2_2_STOP: strcpy(msg, "2stop"); break;
+ default: sprintf(msg, "??stop (0x%x)", mr2 & 0xf); break;
+ }
+ if (mr2 & MR2_RTSCONT) strcat(msg, ", rtscont");
+ if (mr2 & MR2_CTSCONT) strcat(msg, ", ctscont");
+ switch (mr2 & 0xc0) {
+ case MR2_NORMAL: strcat(msg, ", mode:normal"); break;
+ case MR2_AUTO: strcat(msg, ", mode:auto"); break;
+ case MR2_LOCAL: strcat(msg, ", mode:local"); break;
+ case MR2_REMOTE: strcat(msg, ", mode:remote"); break;
+ }
+ return (msg);
+}
+const char *s_clk(int clk)
+{
+ switch (clk & 0xf) {
+ case 0x0: return "75";
+ case 0x1: return "110/115200";
+ case 0x2: return "38400";
+ case 0x3: return "150";
+ case 0x4: return "300";
+ case 0x5: return "600";
+ case 0x6: return "1200";
+ case 0x7: return "2000";
+ case 0x8: return "2400";
+ case 0x9: return "4800";
+ case 0xa: return "7200";
+ case 0xb: return "9600";
+ case 0xc: return "19200";
+ case 0xd: return "57600";
+ case 0xe: return "?0xe";
+ case 0xf: return "?0xf";
+ }
+ return ("gcc sucks");
+}
+const char *s_op(int op)
+{
+ static char msg[200];
+
+ sprintf(msg, "cts:%s", (op & OP_CTS) ? "on" : "off");
+ sprintf(msg + strlen(msg), ", dsr:%s", (op & OP_DSR) ? "on" : "off");
+ return (msg);
+}
+
+const char *s_ip(int ip)
+{
+ static char msg[200];
+
+ sprintf(msg, "rts:%s", (ip & IP_RTS) ? "on" : "off");
+ sprintf(msg + strlen(msg), ", dcd:%s", (ip & IP_DCD) ? "on" : "off");
+ sprintf(msg + strlen(msg), ", dtr:%s", (ip & IP_DTR) ? "on" : "off");
+ sprintf(msg + strlen(msg), ", ri:%s", (ip & IP_RI) ? "on" : "off");
+ return (msg);
+}
+
+const char *s_state(int state)
+{
+ return (state & ST_BREAK ? "break:on" : "break:off");
+}
+
+const char *s_prtcl(int pr)
+{
+ static char msg[200];
+
+ sprintf(msg, "tx xon any:%s", (pr & SP_TANY) ? "on" : "off");
+ sprintf(msg + strlen(msg), ", tx xon/xoff:%s", (pr & SP_TXEN) ? "on" : "off");
+ sprintf(msg + strlen(msg), ", cooking:%s", (pr & SP_CEN) ? "on" : "off");
+ sprintf(msg + strlen(msg), ", rx xon/xoff:%s", (pr & SP_RXEN) ? "on" : "off");
+ sprintf(msg + strlen(msg), ", dcd/dsr check:%s", (pr & SP_DCEN) ? "on" : "off");
+ sprintf(msg + strlen(msg), ", parity check:%s", (pr & SP_PAEN) ? "on" : "off");
+ return (msg);
+}
+const char *s_break(int br)
+{
+ static char msg[200];
+
+ sprintf(msg, "ignore rx brk:%s", (br & BR_IGN) ? "on" : "off");
+ sprintf(msg + strlen(msg), ", brk interrupt:%s", (br & BR_INT) ? "on" : "off");
+ sprintf(msg + strlen(msg), ", parmrking:%s", (br & BR_PARMRK) ? "on" : "off");
+ sprintf(msg + strlen(msg), ", parign:%s", (br & BR_PARIGN) ? "on" : "off");
+ return (msg);
+}
+
+const char *
+s_xstat(int xs)
+{
+ static char msg[200];
+
+ msg[0] = 0;
+ /* MTA definitions, not TA */
+ if (xs & 0x01) strcat(msg, "TION "); /* Tx interrupts on (MTA only) */
+ if (xs & 0x02) strcat(msg, "RTSEN "); /* RTS FLOW enabled (MTA only) */
+ if (xs & 0x04) strcat(msg, "RTSLOW "); /* XOFF received (TA only) */
+ if (xs & 0x08) strcat(msg, "RXEN "); /* Rx XON/XOFF enabled */
+ if (xs & 0x10) strcat(msg, "ANYXO "); /* XOFF pending/sent or RTS dropped */
+ if (xs & 0x20) strcat(msg, "RXSE "); /* Rx XOFF sent */
+ if (xs & 0x40) strcat(msg, "NPEND "); /* Rx XON pending or XOFF pending */
+ if (xs & 0x40) strcat(msg, "FPEND "); /* Rx XOFF pending */
+ return (msg);
+}
+
+const char *
+s_cstat(int cs)
+{
+ static char msg[200];
+
+ msg[0] = 0;
+ /* MTA definitions, not TA */
+ if (cs & 0x01) strcat(msg, "TEMR "); /* Tx empty requested (MTA only) */
+ if (cs & 0x02) strcat(msg, "TEMA "); /* Tx empty acked (MTA only) */
+ if (cs & 0x04) strcat(msg, "EN "); /* Cooking enabled (on MTA means port is also || */
+ if (cs & 0x08) strcat(msg, "HIGH "); /* Buffer previously hit high water */
+ if (cs & 0x10) strcat(msg, "CTSEN "); /* CTS automatic flow-control enabled */
+ if (cs & 0x20) strcat(msg, "DCDEN "); /* DCD/DTR checking enabled */
+ if (cs & 0x40) strcat(msg, "BREAK "); /* Break detected */
+ if (cs & 0x80) strcat(msg, "RTSEN "); /* RTS automatic flow control enabled (MTA only) */
+ return (msg);
+}
+
+void
+ccb_stat(int ac, char **av)
+{
+ struct si_pstat sip;
+#define CCB sip.tc_ccb
+
+ if (ac != 0)
+ prusage(U_STAT_CCB, 1);
+ sip.tc_dev = tc.tc_dev;
+ if (ioctl(ctlfd, TCSI_CCB, &sip) < 0)
+ err(1, "TCSI_CCB on %s", Devname);
+ printf("%s: ", Devname);
+
+ /* WORD next - Next Channel */
+ /* WORD addr_uart - Uart address */
+ /* WORD module - address of module struct */
+ printf("\tuart_type 0x%x\n", CCB.type); /* BYTE type - Uart type */
+ /* BYTE fill - */
+ printf("\tx_status 0x%x %s\n", CCB.x_status, s_xstat(CCB.x_status)); /* BYTE x_status - XON / XOFF status */
+ printf("\tc_status 0x%x %s\n", CCB.c_status, s_cstat(CCB.c_status)); /* BYTE c_status - cooking status */
+ printf("\thi_rxipos 0x%x\n", CCB.hi_rxipos); /* BYTE hi_rxipos - stuff into rx buff */
+ printf("\thi_rxopos 0x%x\n", CCB.hi_rxopos); /* BYTE hi_rxopos - stuff out of rx buffer */
+ printf("\thi_txopos 0x%x\n", CCB.hi_txopos); /* BYTE hi_txopos - Stuff into tx ptr */
+ printf("\thi_txipos 0x%x\n", CCB.hi_txipos); /* BYTE hi_txipos - ditto out */
+ printf("\thi_stat 0x%x %s\n", CCB.hi_stat, s_stat(CCB.hi_stat));/* BYTE hi_stat - Command register */
+ printf("\tdsr_bit 0x%x\n", CCB.dsr_bit); /* BYTE dsr_bit - Magic bit for DSR */
+ printf("\ttxon 0x%x\n", CCB.txon); /* BYTE txon - TX XON char */
+ printf("\ttxoff 0x%x\n", CCB.txoff); /* BYTE txoff - ditto XOFF */
+ printf("\trxon 0x%x\n", CCB.rxon); /* BYTE rxon - RX XON char */
+ printf("\trxoff 0x%x\n", CCB.rxoff); /* BYTE rxoff - ditto XOFF */
+ printf("\thi_mr1 0x%x %s\n", CCB.hi_mr1, s_mr1(CCB.hi_mr1)); /* BYTE hi_mr1 - mode 1 image */
+ printf("\thi_mr2 0x%x %s\n", CCB.hi_mr2, s_mr2(CCB.hi_mr2)); /* BYTE hi_mr2 - mode 2 image */
+ printf("\thi_csr 0x%x in:%s out:%s\n", CCB.hi_csr, s_clk(CCB.hi_csr >> 4), s_clk(CCB.hi_csr)); /* BYTE hi_csr - clock register */
+ printf("\thi_op 0x%x %s\n", CCB.hi_op, s_op(CCB.hi_op)); /* BYTE hi_op - Op control */
+ printf("\thi_ip 0x%x %s\n", CCB.hi_ip, s_ip(CCB.hi_ip)); /* BYTE hi_ip - Input pins */
+ printf("\thi_state 0x%x %s\n", CCB.hi_state, s_state(CCB.hi_state)); /* BYTE hi_state - status */
+ printf("\thi_prtcl 0x%x %s\n", CCB.hi_prtcl, s_prtcl(CCB.hi_prtcl)); /* BYTE hi_prtcl - Protocol */
+ printf("\thi_txon 0x%x\n", CCB.hi_txon); /* BYTE hi_txon - host copy tx xon stuff */
+ printf("\thi_txoff 0x%x\n", CCB.hi_txoff); /* BYTE hi_txoff - */
+ printf("\thi_rxon 0x%x\n", CCB.hi_rxon); /* BYTE hi_rxon - */
+ printf("\thi_rxoff 0x%x\n", CCB.hi_rxoff); /* BYTE hi_rxoff - */
+ printf("\tclose_prev 0x%x\n", CCB.close_prev); /* BYTE close_prev - Was channel previously closed */
+ printf("\thi_break 0x%x %s\n", CCB.hi_break, s_break(CCB.hi_break)); /* BYTE hi_break - host copy break process */
+ printf("\tbreak_state 0x%x\n", CCB.break_state); /* BYTE break_state - local copy ditto */
+ printf("\thi_mask 0x%x\n", CCB.hi_mask); /* BYTE hi_mask - Mask for CS7 etc. */
+ printf("\tmask_z280 0x%x\n", CCB.mask_z280); /* BYTE mask_z280 - Z280's copy */
+ /* BYTE res[0x60 - 36] - */
+ /* BYTE hi_txbuf[SLXOS_BUFFERSIZE] - */
+ /* BYTE hi_rxbuf[SLXOS_BUFFERSIZE] - */
+ /* BYTE res1[0xA0] - */
+}
+
+const char *sp_state(int st)
+{
+
+ if (st & SS_LSTART)
+ return("lstart ");
+ else
+ return("");
+}
+
+void
+port_stat(int ac, char **av)
+{
+ struct si_pstat sip;
+#define PRT sip.tc_siport
+
+ if (ac != 0)
+ prusage(U_STAT_PORT, 1);
+ sip.tc_dev = tc.tc_dev;
+ if (ioctl(ctlfd, TCSI_PORT, &sip) < 0)
+ err(1, "TCSI_PORT on %s", Devname);
+ printf("%s: ", Devname);
+
+ printf("\tsp_pend 0x%x %s\n", PRT.sp_pend, s_stat(PRT.sp_pend));
+ printf("\tsp_last_hi_ip 0x%x %s\n", PRT.sp_last_hi_ip, s_ip(PRT.sp_last_hi_ip));
+ printf("\tsp_state 0x%x %s\n", PRT.sp_state, sp_state(PRT.sp_state));
+ printf("\tsp_delta_overflows 0x%d\n", PRT.sp_delta_overflows);
+}
+
+int
+islevel(char *tk)
+{
+ struct lv *lvp;
+ char *acp;
+
+ for (acp = tk; *acp; acp++)
+ if (isupper(*acp))
+ *acp = tolower(*acp);
+ for (lvp = lv; lvp->lv_name; lvp++)
+ if (strcmp(lvp->lv_name, tk) == 0)
+ return(lvp->lv_bit);
+ return(0);
+}
+
+/*
+ * Convert a string consisting of tokens separated by white space, commas
+ * or `|' into a bitfield - flag any unrecognised tokens.
+ */
+int
+lvls2bits(char *str)
+{
+ int i, bits = 0;
+ int errflag = 0;
+ char token[20];
+
+ while (sscanf(str, "%[^,| \t]", token) == 1) {
+ str += strlen(token);
+ while (isspace(*str) || *str==',' || *str=='|')
+ str++;
+ if (strcmp(token, "all") == 0)
+ return(0xffffffff);
+ if ((i = islevel(token)) == 0) {
+ warnx("unknown token '%s'", token);
+ errflag++;
+ } else
+ bits |= i;
+ }
+ if (errflag)
+ exit(1);
+
+ return(bits);
+}
+
+int
+getnum(char *str)
+{
+ int x;
+ char *acp = str;
+
+ x = 0;
+ while (*acp) {
+ if (!isdigit(*acp))
+ errx(1, "%s is not a number", str);
+ x *= 10;
+ x += (*acp - '0');
+ acp++;
+ }
+ return(x);
+}
+
+void
+prlevels(int x)
+{
+ struct lv *lvp;
+
+ switch (x) {
+ case 0:
+ printf("(none)\n");
+ break;
+ case 0xffffffff:
+ printf("all\n");
+ break;
+ default:
+ for (lvp = lv; lvp->lv_name; lvp++)
+ if (x & lvp->lv_bit)
+ printf(" %s", lvp->lv_name);
+ printf("\n");
+ }
+}
diff --git a/usr.sbin/smbmsg/Makefile b/usr.sbin/smbmsg/Makefile
new file mode 100644
index 0000000..2ae6404
--- /dev/null
+++ b/usr.sbin/smbmsg/Makefile
@@ -0,0 +1,7 @@
+#
+# $FreeBSD$
+
+PROG= smbmsg
+MAN= smbmsg.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/smbmsg/Makefile.depend b/usr.sbin/smbmsg/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/smbmsg/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/smbmsg/pathnames.h b/usr.sbin/smbmsg/pathnames.h
new file mode 100644
index 0000000..aff7335
--- /dev/null
+++ b/usr.sbin/smbmsg/pathnames.h
@@ -0,0 +1,29 @@
+/*-
+ * Copyright (C) 2004 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 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$
+ */
+
+#define PATH_DEFAULTSMBDEV "/dev/smb0"
diff --git a/usr.sbin/smbmsg/smbmsg.8 b/usr.sbin/smbmsg/smbmsg.8
new file mode 100644
index 0000000..faa5bb0
--- /dev/null
+++ b/usr.sbin/smbmsg/smbmsg.8
@@ -0,0 +1,286 @@
+.\" Copyright (c) 2004 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 May 16, 2004
+.Dt SMBMSG 8
+.Os
+.Sh NAME
+.Nm smbmsg
+.Nd "send or receive messages over an SMBus"
+.Sh SYNOPSIS
+.Nm
+.Op Fl f Ar dev
+.Fl p
+.Pp
+.Nm
+.Op Fl f Ar dev
+.Fl s Ar slave
+.Op Fl F Ar fmt
+.Op Fl c Ar cmd
+.Op Fl w
+.Op Fl i Ar incnt
+.Op Fl o Ar outcnt
+.Op Ar outdata ...
+.Sh DESCRIPTION
+The
+.Nm
+utility can be used to send or receive messages over an
+SMBus, see
+.Xr smbus 4 .
+.Pp
+The
+.Nm
+utility has two different modi of operation.
+The first form shown in the synopsis can be used to
+.Dq probe
+the devices on the SMBus.
+This is done by sending each valid device address one
+receive byte, and one quick read message, respectively.
+Devices that respond to these requests will be displayed
+by their device address, followed by the strings
+.Ql r ,
+.Ql w ,
+or
+.Ql rw ,
+for devices that are readable, writeable, or both, readable
+and writeable, respectively.
+The only valid additional option for this modus of operation (besides
+the
+.Fl p
+option that chooses the modus) is
+.Fl f Ar dev .
+See below for a description.
+.Pp
+Note that probing the bus is risky, since individual devices could
+perform unwanted actions upon receiving one of the mentioned messages.
+For example, if a particular SMBus device considers
+.Em any
+write operation issued to it as a request to power off the system,
+the probing would trigger this action.
+.Pp
+The second form shown in the synopsis can be used to send or receive
+arbitrary messages to or from individual devices.
+This might be useful to explore individual devices on the SMBus, or
+maybe even to write short shell scripts performing maintenance
+operations on the bus.
+.Pp
+Any data values on the command-line are integer values in the
+range 0 through 255 for byte values, or 0 through 65535 for
+word values.
+They can be specified using standard
+.Ql C
+notation (prefix 0 for octal interpretation, or 0x for
+hexadecimal interpretation).
+.Pp
+Since the low-order bit of the device address of SMBus devices
+selects between read and write operations, only even-numbered
+slave addresses can exist on the bus.
+.Pp
+The options are as follows:
+.Bl -tag -width ".Fl o Ar outcnt"
+.It Fl F Ar fmt
+Specify the
+.Xr printf 3
+format to be used for displaying input data.
+This option is ignored in messages that do not read any input
+from the SMBus device.
+The format defaults to
+.Ql 0x%02x
+for byte input operations, and to
+.Ql 0x%04x
+for word input operations.
+For multi-byte input (block read), the same format is used for
+each individual byte read from the SMBus.
+.It Fl c Ar cmd
+This is the value of the
+.Em command
+byte to be issued as part of the SMBus message.
+.It Fl f Ar dev
+This specifies that
+.Ar dev
+should be used as the connection to the SMBus, rather than the
+default of
+.Pa /dev/smb0 .
+.It Fl i Ar incnt
+An SMBus message should be generated to read
+.Ar incnt
+bytes from the device.
+.It Fl o Ar outcnt
+An SMBus message should be generated to write
+.Ar outcnt
+bytes to the device.
+The data values to write are expected to follow all of the options
+(and their arguments) on the command-line, where the number of data
+bytes must match the
+.Ar outcnt
+value.
+.It Fl p
+This selects the
+.Em probe bus
+modus of operation.
+.It Fl s Ar slave
+The
+.Ar slave
+parameter specifies which SMBus device to connect to.
+This option also selects the
+.Em transfer messages from/to device
+modus of operation, where a slave address is mandatory.
+.It Fl w
+This option specifies that IO operations are word operations,
+rather than byte operations.
+Either
+.Ar incnt ,
+or
+.Ar outcnt
+(or both) must be equal 2 in this case.
+Note that the SMBus byte order is defined to be little-endian
+(low byte first, high byte follows).
+.El
+.Pp
+Not all argument combinations make sense in order to form valid SMBus
+messages.
+If no
+.Fl c Ar cmd
+option has been provided, the following messages can be
+issued:
+.Bd -unfilled -offset indent
+.TS
+l r r.
+\fBmessage incnt outcnt\fR
+quick read 0 \&-
+quick write \&- 0
+receive byte 1 \&-
+send byte \&- 1
+.TE
+.Ed
+.Pp
+Note in particular that specifying 0 as a count value
+has a different meaning than omitting the respective
+option entirely.
+.Pp
+If a command value has been given using the
+.Fl c Ar cmd
+option, the following messages can be generated:
+.Bd -unfilled -offset indent
+.TS
+l l r r.
+\fBmessage \&-w incnt outcnt\fR
+read byte no 1 \&-
+write byte no \&- 1
+read word yes 2 \&-
+write word yes \&- 2
+process call yes 2 2
+block read no \*(Ge 2 \&-
+block write no \&- \*(Ge 2
+.TE
+.Ed
+.Sh FILES
+.Bl -tag -width ".Pa /dev/smb0" -compact
+.It Pa /dev/smb0
+The default device to connect to, unless
+.Fl f Ar dev
+has been provided.
+.El
+.Sh EXIT STATUS
+Exit status is 0 on success, or according to
+.Xr sysexits 3
+in case of failure.
+.Sh EXAMPLES
+Typical usage examples of the
+.Nm
+command include:
+.Pp
+.Dl "smbmsg -f /dev/smb1 -p"
+.Pp
+Probe all devices on the SMBus attached to
+.Pa /dev/smb1 .
+.Pp
+.Dl "smbmsg -s 0x70 -i 1"
+.Pp
+Issue a
+.Em receive byte
+message to the device at address 0x70, and display
+the received byte using the default format.
+.Pp
+.Dl "smbmsg -s 0x70 -c 0xff -i 1 -F %d"
+.Pp
+Issue a
+.Em read byte
+message to the device at slave address 0x70, using
+255 (0xff) as the command-byte to send to the device,
+and display the result using the custom format
+.Ql %d .
+.Pp
+.Dl "smbmsg -s 0xa0 -c 0 -o 1 0x80"
+.Pp
+Send a
+.Em write byte
+message to the slave device at address 0xa0, using
+0 as the command-byte value, and 0x80 as the byte to
+send (after the command).
+Assuming this might be a Philips PCF8583 real-time clock,
+this would stop the clock.
+.Pp
+.Dl "smbmsg -s 0xa0 -c 1 -i 6 -F %02x"
+.Pp
+Send a
+.Em block read
+command to device at address 0xa0, and read 6 bytes from
+it, using hexadecimal display.
+Again, assuming a PCF8583 RTC, this would display the
+fractions of second, seconds, minutes, hours, year/date,
+and weekday/month values.
+Since this RTC uses BCD notation, the actual values displayed
+were decimal then.
+.Pp
+.Dl "smbmsg -s 0xa0 -c 2 -o 5 0x00 0x07 0x22 0x16 0x05"
+.Pp
+Send a
+.Em block write
+command to device at address 0xa0.
+For the PCF8583 RTC, this would set the clock to Sunday (2004%4)-05-16
+22:07:00.
+.Sh DIAGNOSTICS
+Diagnostic messages issued are supposed to be self-explanatory.
+.Sh SEE ALSO
+.Xr printf 3 ,
+.Xr sysexits 3 ,
+.Xr smb 4 ,
+.Xr smbus 4
+.Rs
+.%T "The SMBus specification"
+.%U http://www.smbus.org/specs/
+.Re
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 5.3 .
+.Sh AUTHORS
+The
+.Nm
+utility and this manual page were written by
+.An J\(:org Wunsch .
diff --git a/usr.sbin/smbmsg/smbmsg.c b/usr.sbin/smbmsg/smbmsg.c
new file mode 100644
index 0000000..4e7e609
--- /dev/null
+++ b/usr.sbin/smbmsg/smbmsg.c
@@ -0,0 +1,347 @@
+/*-
+ * Copyright (C) 2004 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 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$
+ */
+
+/*
+ * Send or receive messages over an SMBus.
+ */
+
+#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 <dev/smbus/smb.h>
+
+#include "pathnames.h"
+
+static const char *dev = PATH_DEFAULTSMBDEV;
+static const char *bytefmt = "0x%02x";
+static const char *wordfmt = "0x%04x";
+static const char *fmt;
+
+static int fd; /* file descriptor for /dev/smbX */
+static int cflag = -1; /* SMBus cmd */
+static int iflag = -1; /* input data */
+static int oflag = -1; /* output data */
+static int pflag; /* probe bus */
+static int slave = -1; /* slave address */
+static int wflag; /* word IO */
+
+static unsigned char ibuf[SMB_MAXBLOCKSIZE];
+static unsigned char obuf[SMB_MAXBLOCKSIZE];
+static unsigned short oword, iword;
+
+/*
+ * The I2C specs say that all addresses below 16 and above or equal
+ * 240 are reserved. Address 0 is the global address, but we do not
+ * care for this detail.
+ */
+#define MIN_I2C_ADDR 16
+#define MAX_I2C_ADDR 240
+
+static int do_io(void);
+static int getnum(const char *s);
+static void probe_i2c(void);
+static void usage(void);
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: smbmsg [-f dev] -p\n"
+ " smbmsg [-f dev] -s slave [-F fmt] [-c cmd] [-w] "
+ "[-i incnt] [-o outcnt] [outdata ...]\n");
+ exit(EX_USAGE);
+}
+
+static int
+getnum(const char *s)
+{
+ char *endp;
+ unsigned long l;
+
+ l = strtoul(s, &endp, 0);
+ if (*s != '\0' && *endp == '\0')
+ return (int)l;
+ return (-1);
+}
+
+static void
+probe_i2c(void)
+{
+ unsigned char addr;
+ int flags;
+#define IS_READABLE 1
+#define IS_WRITEABLE 2
+ struct smbcmd c;
+
+ printf("Probing for devices on %s:\n", dev);
+
+ for (addr = MIN_I2C_ADDR; addr < MAX_I2C_ADDR; addr += 2) {
+ c.slave = addr;
+ flags = 0;
+ if (ioctl(fd, SMB_RECVB, &c) != -1)
+ flags = IS_READABLE;
+ if (ioctl(fd, SMB_QUICK_WRITE, &c) != -1)
+ flags |= IS_WRITEABLE;
+ if (flags != 0) {
+ printf("Device @0x%02x: ", addr);
+ if (flags & IS_READABLE)
+ putchar('r');
+ if (flags & IS_WRITEABLE)
+ putchar('w');
+ putchar('\n');
+ }
+ }
+}
+
+static int
+do_io(void)
+{
+ struct smbcmd c;
+ int i;
+
+ c.slave = slave;
+ c.cmd = cflag;
+
+ if (fmt == NULL && iflag > 0)
+ fmt = wflag? wordfmt: bytefmt;
+
+ if (cflag == -1) {
+ /* operations that do not require a command byte */
+ if (iflag == -1 && oflag == 0)
+ /* 0 bytes output: quick write operation */
+ return (ioctl(fd, SMB_QUICK_WRITE, &c));
+ else if (iflag == 0 && oflag == -1)
+ /* 0 bytes input: quick read operation */
+ return (ioctl(fd, SMB_QUICK_READ, &c));
+ else if (iflag == 1 && oflag == -1) {
+ /* no command, 1 byte input: receive byte op. */
+ if (ioctl(fd, SMB_RECVB, &c) == -1)
+ return (-1);
+ printf(fmt, (unsigned char)c.cmd);
+ putchar('\n');
+ return (0);
+ } else if (iflag == -1 && oflag == 1) {
+ /* no command, 1 byte output: send byte op. */
+ c.cmd = obuf[0];
+ return (ioctl(fd, SMB_SENDB, &c));
+ } else
+ return (-2);
+ }
+ if (iflag == 1 && oflag == -1) {
+ /* command + 1 byte input: read byte op. */
+ c.rbuf = ibuf;
+ c.rcount = iflag;
+ if (ioctl(fd, SMB_READB, &c) == -1)
+ return (-1);
+ printf(fmt, (int)(unsigned char)ibuf[0]);
+ putchar('\n');
+ return (0);
+ } else if (iflag == -1 && oflag == 1) {
+ /* command + 1 byte output: write byte op. */
+ c.wdata.byte = obuf[0];
+ return (ioctl(fd, SMB_WRITEB, &c));
+ } else if (wflag && iflag == 2 && oflag == -1) {
+ /* command + 2 bytes input: read word op. */
+ c.rbuf = (char*) &iword;
+ c.rcount = iflag;
+ if (ioctl(fd, SMB_READW, &c) == -1)
+ return (-1);
+ printf(fmt, (int)(unsigned short)iword);
+ putchar('\n');
+ return (0);
+ } else if (wflag && iflag == -1 && oflag == 2) {
+ /* command + 2 bytes output: write word op. */
+ c.wdata.word = oword;
+ return (ioctl(fd, SMB_WRITEW, &c));
+ } else if (wflag && iflag == 2 && oflag == 2) {
+ /*
+ * command + 2 bytes output + 2 bytes input:
+ * "process call" op.
+ */
+ c.wdata.word = oword;
+ c.rbuf = (char*) &iword;
+ c.rcount = iflag;
+ if (ioctl(fd, SMB_PCALL, &c) == -1)
+ return (-1);
+ printf(fmt, (int)(unsigned short)iword);
+ putchar('\n');
+ return (0);
+ } else if (iflag > 1 && oflag == -1) {
+ /* command + > 1 bytes of input: block read */
+ c.rbuf = ibuf;
+ c.rcount = iflag;
+ if (ioctl(fd, SMB_BREAD, &c) == -1)
+ return (-1);
+ for (i = 0; i < iflag; i++) {
+ if (i != 0)
+ putchar(' ');
+ printf(fmt, ibuf[i]);
+ }
+ putchar('\n');
+ return (0);
+ } else if (iflag == -1 && oflag > 1) {
+ /* command + > 1 bytes of output: block write */
+ c.wbuf = obuf;
+ c.wcount = oflag;
+ return (ioctl(fd, SMB_BWRITE, &c));
+ }
+
+ return (-2);
+}
+
+
+int
+main(int argc, char **argv)
+{
+ int i, n, errs = 0;
+ int savederrno;
+
+ while ((i = getopt(argc, argv, "F:c:f:i:o:ps:w")) != -1)
+ switch (i) {
+ case 'F':
+ fmt = optarg;
+ break;
+
+ case 'c':
+ if ((cflag = getnum(optarg)) == -1)
+ errx(EX_USAGE, "Invalid number: %s", optarg);
+ if (cflag < 0 || cflag >= 256)
+ errx(EX_USAGE,
+ "CMD out of range: %d",
+ cflag);
+ break;
+
+ case 'f':
+ dev = optarg;
+ break;
+
+ case 'i':
+ if ((iflag = getnum(optarg)) == -1)
+ errx(EX_USAGE, "Invalid number: %s", optarg);
+ if (iflag < 0 || iflag > SMB_MAXBLOCKSIZE)
+ errx(EX_USAGE,
+ "# input bytes out of range: %d",
+ iflag);
+ break;
+
+ case 'o':
+ if ((oflag = getnum(optarg)) == -1)
+ errx(EX_USAGE, "Invalid number: %s", optarg);
+ if (oflag < 0 || oflag > SMB_MAXBLOCKSIZE)
+ errx(EX_USAGE,
+ "# output bytes out of range: %d",
+ oflag);
+ break;
+
+ case 'p':
+ pflag = 1;
+ break;
+
+ case 's':
+ if ((slave = getnum(optarg)) == -1)
+ errx(EX_USAGE, "Invalid number: %s", optarg);
+
+ if (slave < MIN_I2C_ADDR || slave >= MAX_I2C_ADDR)
+ errx(EX_USAGE,
+ "Slave address out of range: %d",
+ slave);
+ break;
+
+ case 'w':
+ wflag = 1;
+ break;
+
+ default:
+ errs++;
+ }
+ argc -= optind;
+ argv += optind;
+ if (errs || (slave != -1 && pflag) || (slave == -1 && !pflag))
+ usage();
+ if (wflag &&
+ !((iflag == 2 && oflag == -1) ||
+ (iflag == -1 && oflag == 2) ||
+ (iflag == 2 && oflag == 2)))
+ errx(EX_USAGE, "Illegal # IO bytes for word IO");
+ if (!pflag && iflag == -1 && oflag == -1)
+ errx(EX_USAGE, "Nothing to do");
+ if (pflag && (cflag != -1 || iflag != -1 || oflag != -1 || wflag != 0))
+ usage();
+ if (oflag > 0) {
+ if (oflag == 2 && wflag) {
+ if (argc == 0)
+ errx(EX_USAGE, "Too few arguments for -o count");
+ if ((n = getnum(*argv)) == -1)
+ errx(EX_USAGE, "Invalid number: %s", *argv);
+ if (n < 0 || n >= 65535)
+ errx(EX_USAGE, "Value out of range: %d", n);
+ oword = n;
+ argc--;
+ argv++;
+ } else for (i = 0; i < oflag; i++, argv++, argc--) {
+ if (argc == 0)
+ errx(EX_USAGE, "Too few arguments for -o count");
+ if ((n = getnum(*argv)) == -1)
+ errx(EX_USAGE, "Invalid number: %s", *argv);
+ if (n < 0 || n >= 256)
+ errx(EX_USAGE, "Value out of range: %d", n);
+ obuf[i] = n;
+ }
+ }
+ if (argc != 0)
+ usage();
+
+ if ((fd = open(dev, O_RDWR)) == -1)
+ err(EX_UNAVAILABLE, "Cannot open %s", dev);
+
+ i = 0;
+ if (pflag)
+ probe_i2c();
+ else
+ i = do_io();
+
+ savederrno = errno;
+ close(fd);
+ errno = savederrno;
+
+ if (i == -1)
+ err(EX_UNAVAILABLE, "Error performing SMBus IO");
+ else if (i == -2)
+ errx(EX_USAGE, "Invalid option combination");
+
+ return (0);
+}
diff --git a/usr.sbin/snapinfo/Makefile b/usr.sbin/snapinfo/Makefile
new file mode 100644
index 0000000..7601374
--- /dev/null
+++ b/usr.sbin/snapinfo/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+#
+
+PROG= snapinfo
+MAN= snapinfo.8
+
+LIBADD= ufs
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/snapinfo/Makefile.depend b/usr.sbin/snapinfo/Makefile.depend
new file mode 100644
index 0000000..99cf113
--- /dev/null
+++ b/usr.sbin/snapinfo/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libufs \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/snapinfo/snapinfo.8 b/usr.sbin/snapinfo/snapinfo.8
new file mode 100644
index 0000000..010291e
--- /dev/null
+++ b/usr.sbin/snapinfo/snapinfo.8
@@ -0,0 +1,66 @@
+.\"
+.\" Copyright (c) 2005 Mark Santcroos <marks@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 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd July 20, 2005
+.Dt SNAPINFO 8
+.Os
+.Sh NAME
+.Nm snapinfo
+.Nd "show snapshot location on UFS file systems"
+.Sh SYNOPSIS
+.Nm
+.Op Fl v
+.Fl a
+.Nm
+.Op Fl v
+.Ar mountpoint
+.Sh DESCRIPTION
+The
+.Nm
+utility searches and displays the location of snapshots on UFS file systems.
+.Pp
+Currently it works only on mounted file systems.
+It can either search one file system or all of them.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl a
+Search for snapshots on all mounted UFS file systems.
+.It Fl v
+Verbose mode.
+.It Ar mountpoint
+Search the file system mounted on this mountpoint.
+.El
+.Sh SEE ALSO
+.Xr ffs 7 ,
+.Xr mksnap_ffs 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 6.1 .
+.Sh AUTHORS
+.An Mark Santcroos Aq Mt marks@FreeBSD.org
diff --git a/usr.sbin/snapinfo/snapinfo.c b/usr.sbin/snapinfo/snapinfo.c
new file mode 100644
index 0000000..23c3b4b
--- /dev/null
+++ b/usr.sbin/snapinfo/snapinfo.c
@@ -0,0 +1,181 @@
+/*-
+ * Copyright (c) 2005 Mark Santcroos <marks@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 ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED 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$
+ *
+ */
+
+#include <sys/param.h>
+#include <sys/mount.h>
+
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include <errno.h>
+#include <ftw.h>
+#include <libufs.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static void find_inum(char *path);
+static void usage(void);
+static int compare_function(const char *, const struct stat *,
+ int, struct FTW *);
+static int find_snapshot(struct statfs *sfs);
+
+static int verbose;
+static int cont_search;
+static uint32_t inode;
+
+int
+main(int argc, char **argv)
+{
+ char *path;
+ struct stat st;
+ struct statfs *mntbuf;
+ int all = 0, ch, done = 0, fscount, n;
+
+ while ((ch = getopt(argc, argv, "adv")) != -1) {
+ switch (ch) {
+ case 'a':
+ all++;
+ break;
+ case 'd':
+ /* continue to search when matching inode is found
+ * this feature is not documented */
+ cont_search++;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if ((all == 0 && argc != 1) || (all == 1 && argc > 0))
+ usage();
+
+ if (!all) {
+ char resolved[PATH_MAX];
+
+ path = *argv;
+ /*
+ * mount(8) use realpath(3) before mounting file system,
+ * so let's do the same with the given path.
+ */
+ if (realpath(path, resolved) == NULL || /* can create full path */
+ stat(resolved, &st) == -1 || /* is it stat'able */
+ !S_ISDIR(st.st_mode)) { /* is it a directory */
+ usage();
+ }
+ path = resolved;
+ }
+
+ fscount = getmntinfo(&mntbuf, MNT_WAIT);
+ for (n = 0; n < fscount; n++) {
+ if (!strncmp(mntbuf[n].f_fstypename, "ufs", 3)) {
+ if (all || strcmp(path, mntbuf[n].f_mntonname) == 0) {
+ find_snapshot(&mntbuf[n]);
+ done++;
+ }
+ }
+ }
+
+ if (done == 0)
+ usage();
+
+ return (0);
+}
+
+static int
+find_snapshot(struct statfs *sfs)
+{
+ struct uufsd disk;
+ int j, snapcount = 0;
+
+ if (ufs_disk_fillout(&disk, sfs->f_mntfromname) == -1)
+ perror("ufs_disk_fillout");
+
+ if (verbose)
+ printf("%s mounted on %s\n", disk.d_name, disk.d_fs.fs_fsmnt);
+
+ for (j = 0; j < FSMAXSNAP; j++) {
+ if (disk.d_fs.fs_snapinum[j]) {
+ inode = disk.d_fs.fs_snapinum[j];
+ find_inum(sfs->f_mntonname);
+ snapcount++;
+ }
+ }
+
+ if (!snapcount && verbose)
+ printf("\tno snapshots found\n");
+
+ return 0;
+}
+
+static int
+compare_function(const char *path, const struct stat *st, int flags,
+ struct FTW * ftwv __unused)
+{
+
+ if (flags == FTW_F && st->st_ino == inode) {
+ if (verbose)
+ printf("\tsnapshot ");
+ printf("%s", path);
+ if (verbose)
+ printf(" (inode %ju)", (uintmax_t)st->st_ino);
+ printf("\n");
+ if (!cont_search)
+ return (EEXIST);
+ }
+
+ return (0);
+}
+
+static void
+find_inum(char *path)
+{
+ int ret;
+
+ ret = nftw(path, compare_function, 1, FTW_PHYS|FTW_MOUNT);
+ if (ret != EEXIST && ret != 0) {
+ perror("ftw");
+ exit(ret);
+ }
+}
+
+static void
+usage(void)
+{
+
+ printf("usage: snapinfo [-v] -a\n");
+ printf(" snapinfo [-v] mountpoint\n");
+ exit(1);
+}
diff --git a/usr.sbin/spkrtest/Makefile b/usr.sbin/spkrtest/Makefile
new file mode 100644
index 0000000..8e9245a
--- /dev/null
+++ b/usr.sbin/spkrtest/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+SCRIPTS=spkrtest.sh
+MAN= spkrtest.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/spkrtest/Makefile.depend b/usr.sbin/spkrtest/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/spkrtest/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/spkrtest/spkrtest.8 b/usr.sbin/spkrtest/spkrtest.8
new file mode 100644
index 0000000..58eced5
--- /dev/null
+++ b/usr.sbin/spkrtest/spkrtest.8
@@ -0,0 +1,48 @@
+.\" Copyright (c) May 1995 Wolfram Schneider <wosch@FreeBSD.org>. Berlin.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 23, 1995
+.Dt SPKRTEST 8
+.Os
+.Sh NAME
+.Nm spkrtest
+.Nd test script for the speaker driver
+.Sh DESCRIPTION
+The
+.Nm
+utility is an easy to use test script for the speaker driver.
+.Sh FILES
+.Bl -tag -width /dev/speakerxx
+.It Pa /dev/speaker
+speaker device file
+.El
+.Sh SEE ALSO
+.Xr dialog 1 ,
+.Xr spkr 4
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 1.0
diff --git a/usr.sbin/spkrtest/spkrtest.sh b/usr.sbin/spkrtest/spkrtest.sh
new file mode 100644
index 0000000..f9f0f06
--- /dev/null
+++ b/usr.sbin/spkrtest/spkrtest.sh
@@ -0,0 +1,113 @@
+#!/bin/sh
+
+#
+# Copyright (c) 2002 The FreeBSD 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.
+#
+# 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.
+#
+
+#
+# Inspired on spkrtest.pl, rewritten from scratch to remove perl dependency
+# $VER: spkrtest 0.3 (9.5.2002) Riccardo "VIC" Torrini <riccardo@torrini.org>
+# $FreeBSD$
+#
+
+cleanExit() {
+ rm -f ${choices}
+ exit ${1:-0}
+}
+
+trap 'cleanExit 1' 1 2 3 5 15 # HUP, INT, QUIT, TRAP, TERM
+
+choices=${TMP:-/tmp}/_spkrtest_choices.$$
+speaker=/dev/speaker
+
+test -w ${speaker}
+if [ $? -ne 0 ]
+then
+ echo "You have no write access to $speaker or the speaker device is"
+ echo "not enabled in kernel. Cannot play any melody! See spkr(4)."
+ sleep 2
+ cleanExit 1
+fi
+
+/usr/bin/dialog --title "Speaker test" --checklist \
+ "Please select the melodies you wish to play (space for select)" \
+ 0 0 0 \
+ reveille "Reveille" OFF \
+ contact "Contact theme from Close Encounters" OFF \
+ dance "Lord of the Dance (aka Simple Gifts)" OFF \
+ loony "Loony Toons theme" OFF \
+ sinister "Standard villain's entrance music" OFF \
+ rightstuff "A trope from 'The Right Stuff' score by Bill Conti" OFF \
+ toccata "Opening bars of Bach's Toccata and Fugue in D Minor" OFF \
+ startrek "Opening bars of the theme from Star Trek Classic" OFF \
+ 2> ${choices} || cleanExit 0
+
+echo ""
+tunes="`cat ${choices} | tr -d '\"'`"
+for tune in ${tunes:-DEFAULT}
+do
+ case ${tune:-NULL} in
+ DEFAULT)
+ title="(default melody)"
+ music="ec"
+ ;;
+ reveille)
+ title="Reveille"
+ music="t255l8c.f.afc~c.f.afc~c.f.afc.f.a..f.~c.f.afc~c.f.afc~c.f.afc~c.f.."
+ ;;
+ contact)
+ title="Contact theme from Close Encounters"
+ music="<cd<a#~<a#>f"
+ ;;
+ dance)
+ title="Lord of the Dance (aka Simple Gifts)"
+ music="t240<cfcfgagaa#b#>dc<a#a.~fg.gaa#.agagegc.~cfcfgagaa#b#>dc<a#a.~fg.gga.agfgfgf."
+ ;;
+ loony)
+ title="Loony Toons theme"
+ music="t255cf8f8edc<a>~cf8f8edd#e~ce8cdce8cd.<a>c8c8c#def8af8"
+ ;;
+ sinister)
+ title="standard villain's entrance music"
+ music="mst200o2ola.l8bc.~a.~>l2d#"
+ ;;
+ rightstuff)
+ title="a trope from 'The Right Stuff' score by Bill Conti"
+ music="olcega.a8f>cd2bgc.c8dee2"
+ ;;
+ toccata)
+ title="opening bars of Bach's Toccata and Fugue in D Minor"
+ music="msl16oldcd4mll8pcb-agf+4.g4p4<msl16dcd4mll8pa.a+f+4p16g4"
+ ;;
+ startrek)
+ title="opening bars of the theme from Star Trek Classic"
+ music="l2b.f+.p16a.c+.p l4mn<b.>e8a2mspg+e8c+f+8b2"
+ ;;
+ esac
+ echo "Title: ${title}"
+ echo ${music} > ${speaker}
+ sleep 1
+done
+cleanExit 0
diff --git a/usr.sbin/spray/Makefile b/usr.sbin/spray/Makefile
new file mode 100644
index 0000000..f007072
--- /dev/null
+++ b/usr.sbin/spray/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= spray
+MAN= spray.8
+
+LIBADD= rpcsvc
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/spray/Makefile.depend b/usr.sbin/spray/Makefile.depend
new file mode 100644
index 0000000..c8383bd
--- /dev/null
+++ b/usr.sbin/spray/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/librpcsvc \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/spray/spray.8 b/usr.sbin/spray/spray.8
new file mode 100644
index 0000000..e107048
--- /dev/null
+++ b/usr.sbin/spray/spray.8
@@ -0,0 +1,76 @@
+.\"
+.\" Copyright (c) 1994 James A. Jegers
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the 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 ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED 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 July 10, 1995
+.Dt SPRAY 8
+.Os
+.Sh NAME
+.Nm spray
+.Nd send many packets to host
+.Sh SYNOPSIS
+.Nm
+.Op Fl c Ar count
+.Op Fl d Ar delay
+.Op Fl l Ar length
+.Ar host
+.Sh DESCRIPTION
+The
+.Nm
+utility sends multiple RPC packets to
+.Ar host
+and records how many of them were correctly received and how long it took.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl c Ar count
+Send
+.Ar count
+packets.
+.It Fl d Ar delay
+Pause
+.Ar delay
+microseconds between sending each packet.
+.It Fl l Ar length
+Set the length of the packet that holds the RPC call message to
+.Ar length
+bytes.
+Not all values of
+.Ar length
+are possible because RPC data is encoded using XDR.
+The
+.Nm
+utility rounds up to the nearest possible value.
+.El
+.Pp
+The
+.Nm
+utility is intended for use in network testing, measurement, and management.
+This command
+.Em "can be very hard on a network and should be used with caution" .
+.Sh SEE ALSO
+.Xr netstat 1 ,
+.Xr ifconfig 8 ,
+.Xr ping 8 ,
+.Xr rpc.sprayd 8
diff --git a/usr.sbin/spray/spray.c b/usr.sbin/spray/spray.c
new file mode 100644
index 0000000..69691d1
--- /dev/null
+++ b/usr.sbin/spray/spray.c
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 1993 Winning Strategies, 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Winning Strategies, Inc.
+ * 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 <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <rpc/rpc.h>
+#include <rpcsvc/spray.h>
+
+#ifndef SPRAYOVERHEAD
+#define SPRAYOVERHEAD 86
+#endif
+
+static void usage(void);
+static void print_xferstats(unsigned int, int, double);
+
+/* spray buffer */
+static char spray_buffer[SPRAYMAX];
+
+/* RPC timeouts */
+static struct timeval NO_DEFAULT = { -1, -1 };
+static struct timeval ONE_WAY = { 0, 0 };
+static struct timeval TIMEOUT = { 25, 0 };
+
+int
+main(int argc, char *argv[])
+{
+ spraycumul host_stats;
+ sprayarr host_array;
+ CLIENT *cl;
+ int c;
+ u_int i;
+ u_int count = 0;
+ int delay = 0;
+ int length = 0;
+ double xmit_time; /* time to receive data */
+
+ while ((c = getopt(argc, argv, "c:d:l:")) != -1) {
+ switch (c) {
+ case 'c':
+ count = atoi(optarg);
+ break;
+ case 'd':
+ delay = atoi(optarg);
+ break;
+ case 'l':
+ length = atoi(optarg);
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1) {
+ usage();
+ /* NOTREACHED */
+ }
+
+
+ /* Correct packet length. */
+ if (length > SPRAYMAX) {
+ length = SPRAYMAX;
+ } else if (length < SPRAYOVERHEAD) {
+ length = SPRAYOVERHEAD;
+ } else {
+ /* The RPC portion of the packet is a multiple of 32 bits. */
+ length -= SPRAYOVERHEAD - 3;
+ length &= ~3;
+ length += SPRAYOVERHEAD;
+ }
+
+
+ /*
+ * The default value of count is the number of packets required
+ * to make the total stream size 100000 bytes.
+ */
+ if (!count) {
+ count = 100000 / length;
+ }
+
+ /* Initialize spray argument */
+ host_array.sprayarr_len = length - SPRAYOVERHEAD;
+ host_array.sprayarr_val = spray_buffer;
+
+
+ /* create connection with server */
+ cl = clnt_create(*argv, SPRAYPROG, SPRAYVERS, "udp");
+ if (cl == NULL)
+ errx(1, "%s", clnt_spcreateerror(""));
+
+
+ /*
+ * For some strange reason, RPC 4.0 sets the default timeout,
+ * thus timeouts specified in clnt_call() are always ignored.
+ *
+ * The following (undocumented) hack resets the internal state
+ * of the client handle.
+ */
+ clnt_control(cl, CLSET_TIMEOUT, &NO_DEFAULT);
+
+
+ /* Clear server statistics */
+ if (clnt_call(cl, SPRAYPROC_CLEAR, (xdrproc_t)xdr_void, NULL,
+ (xdrproc_t)xdr_void, NULL, TIMEOUT) != RPC_SUCCESS)
+ errx(1, "%s", clnt_sperror(cl, ""));
+
+
+ /* Spray server with packets */
+ printf ("sending %u packets of lnth %d to %s ...", count, length,
+ *argv);
+ fflush (stdout);
+
+ for (i = 0; i < count; i++) {
+ clnt_call(cl, SPRAYPROC_SPRAY, (xdrproc_t)xdr_sprayarr,
+ &host_array, (xdrproc_t)xdr_void, NULL, ONE_WAY);
+
+ if (delay) {
+ usleep(delay);
+ }
+ }
+
+
+ /* Collect statistics from server */
+ if (clnt_call(cl, SPRAYPROC_GET, (xdrproc_t)xdr_void, NULL,
+ (xdrproc_t)xdr_spraycumul, &host_stats, TIMEOUT) != RPC_SUCCESS)
+ errx(1, "%s", clnt_sperror(cl, ""));
+
+ xmit_time = host_stats.clock.sec +
+ (host_stats.clock.usec / 1000000.0);
+
+ printf ("\n\tin %.2f seconds elapsed time\n", xmit_time);
+
+
+ /* report dropped packets */
+ if (host_stats.counter != count) {
+ int packets_dropped = count - host_stats.counter;
+
+ printf("\t%d packets (%.2f%%) dropped\n",
+ packets_dropped,
+ 100.0 * packets_dropped / count );
+ } else {
+ printf("\tno packets dropped\n");
+ }
+
+ printf("Sent:");
+ print_xferstats(count, length, xmit_time);
+
+ printf("Rcvd:");
+ print_xferstats(host_stats.counter, length, xmit_time);
+
+ exit (0);
+}
+
+
+static void
+print_xferstats(u_int packets, int packetlen, double xfertime)
+{
+ int datalen;
+ double pps; /* packets per second */
+ double bps; /* bytes per second */
+
+ datalen = packets * packetlen;
+ pps = packets / xfertime;
+ bps = datalen / xfertime;
+
+ printf("\t%.0f packets/sec, ", pps);
+
+ if (bps >= 1024)
+ printf ("%.1fK ", bps / 1024);
+ else
+ printf ("%.0f ", bps);
+
+ printf("bytes/sec\n");
+}
+
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: spray [-c count] [-l length] [-d delay] host\n");
+ exit(1);
+}
diff --git a/usr.sbin/syslogd/Makefile b/usr.sbin/syslogd/Makefile
new file mode 100644
index 0000000..716efbe
--- /dev/null
+++ b/usr.sbin/syslogd/Makefile
@@ -0,0 +1,22 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+.PATH: ${.CURDIR}/../../usr.bin/wall
+
+PROG= syslogd
+MAN= syslog.conf.5 syslogd.8
+SRCS= syslogd.c ttymsg.c
+
+LIBADD= util
+
+WARNS?= 3
+
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+= -DINET6
+.endif
+
+CFLAGS+= -I${.CURDIR}/../../usr.bin/wall
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/syslogd/Makefile.depend b/usr.sbin/syslogd/Makefile.depend
new file mode 100644
index 0000000..7de116d
--- /dev/null
+++ b/usr.sbin/syslogd/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/syslogd/pathnames.h b/usr.sbin/syslogd/pathnames.h
new file mode 100644
index 0000000..00631e0
--- /dev/null
+++ b/usr.sbin/syslogd/pathnames.h
@@ -0,0 +1,35 @@
+/*
+ * 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/6/93
+ * $FreeBSD$
+ */
+
+#define _PATH_KLOG "/dev/klog"
+#define _PATH_LOGCONF "/etc/syslog.conf"
+#define _PATH_LOGPID "/var/run/syslog.pid"
diff --git a/usr.sbin/syslogd/syslog.conf.5 b/usr.sbin/syslogd/syslog.conf.5
new file mode 100644
index 0000000..3378aa0
--- /dev/null
+++ b/usr.sbin/syslogd/syslog.conf.5
@@ -0,0 +1,525 @@
+.\" 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.
+.\"
+.\" @(#)syslog.conf.5 8.1 (Berkeley) 6/9/93
+.\" $FreeBSD$
+.\"
+.Dd September 12, 2012
+.Dt SYSLOG.CONF 5
+.Os
+.Sh NAME
+.Nm syslog.conf
+.Nd
+.Xr syslogd 8
+configuration file
+.Sh DESCRIPTION
+The
+.Nm
+file is the configuration file for the
+.Xr syslogd 8
+program.
+It consists of
+blocks of lines separated by
+.Em program
+and
+.Em hostname
+specifications (separations appear alone on their lines),
+with each line containing two fields: the
+.Em selector
+field which specifies the types of messages and priorities to which the
+line applies, and an
+.Em action
+field which specifies the action to be taken if a message
+.Xr syslogd 8
+receives matches the selection criteria.
+The
+.Em selector
+field is separated from the
+.Em action
+field by one or more tab characters or spaces.
+.Pp
+Note that if you use spaces as separators, your
+.Nm
+might be incompatible with other Unices or Unix-like systems.
+This functionality was added for ease of configuration
+(e.g.\& it is possible to cut-and-paste into
+.Nm ) ,
+and to avoid possible mistakes.
+This change however preserves
+backwards compatibility with the old style of
+.Nm
+(i.e., tab characters only).
+.Pp
+The
+.Em selectors
+are encoded as a
+.Em facility ,
+a period
+.Pq Dq \&. ,
+an optional set of comparison flags
+.Pq Oo \&! Oc Op <=> ,
+and a
+.Em level ,
+with no intervening white-space.
+Both the
+.Em facility
+and the
+.Em level
+are case insensitive.
+.Pp
+The
+.Em facility
+describes the part of the system generating the message, and is one of
+the following keywords:
+.Cm auth , authpriv , console , cron , daemon , ftp , kern , lpr ,
+.Cm mail , mark , news , ntp , security , syslog , user , uucp ,
+and
+.Cm local0
+through
+.Cm local7 .
+These keywords (with the exception of mark) correspond to
+similar
+.Dq Dv LOG_
+values specified to the
+.Xr openlog 3
+and
+.Xr syslog 3
+library routines.
+.Pp
+The
+.Em comparison flags
+may be used to specify exactly what is logged.
+The default comparison is
+.Dq =>
+(or, if you prefer,
+.Dq >= ) ,
+which means that messages from the specified
+.Em facility
+list, and of a priority
+level equal to or greater than
+.Em level
+will be logged.
+Comparison flags beginning with
+.Dq Li \&!
+will have their logical sense inverted.
+Thus
+.Dq !=info
+means all levels except info and
+.Dq !notice
+has the same meaning as
+.Dq <notice .
+.Pp
+The
+.Em level
+describes the severity of the message, and is a keyword from the
+following ordered list (higher to lower):
+.Cm emerg , crit , alert , err , warning , notice , info
+and
+.Cm debug .
+These keywords correspond to
+similar
+.Dq Dv LOG_
+values specified to the
+.Xr syslog 3
+library routine.
+.Pp
+Each block of lines is separated from the previous block by a
+.Em program
+or
+.Em hostname
+specification.
+A block will only log messages corresponding to the most recent
+.Em program
+and
+.Em hostname
+specifications given.
+Thus, with a block which selects
+.Ql ppp
+as the
+.Em program ,
+directly followed by a block that selects messages from the
+.Em hostname
+.Ql dialhost ,
+the second block will only log messages
+from the
+.Xr ppp 8
+program on dialhost.
+.Pp
+A
+.Em program
+specification is a line beginning with
+.Ql #!prog
+or
+.Ql !prog
+(the former is for compatibility with the previous syslogd, if one is sharing
+.Nm
+files, for example)
+and the following blocks will be associated with calls to
+.Xr syslog 3
+from that specific program.
+A
+.Em program
+specification for
+.Ql foo
+will also match any message logged by the kernel with the prefix
+.Ql "foo: " .
+The
+.Ql #!+prog
+or
+.Ql !+prog
+specification works just like the previous one,
+and the
+.Ql #!-prog
+or
+.Ql !-prog
+specification will match any message but the ones from that
+program.
+Multiple programs may be listed, separated by commas:
+.Ql !prog1,prog2
+matches messages from either program, while
+.Ql !-prog1,prog2
+matches all messages but those from
+.Ql prog1
+or
+.Ql prog2 .
+.Pp
+A
+.Em hostname
+specification of the form
+.Ql #+hostname
+or
+.Ql +hostname
+means the following blocks will be applied to messages
+received from the specified hostname.
+Alternatively, the
+.Em hostname
+specification
+.Ql #-hostname
+or
+.Ql -hostname
+causes the following blocks to be applied to messages
+from any host but the one specified.
+If the hostname is given as
+.Ql @ ,
+the local hostname will be used.
+As for program specifications, multiple comma-separated
+values may be specified for hostname specifications.
+.Pp
+A
+.Em program
+or
+.Em hostname
+specification may be reset by giving the program or hostname as
+.Ql * .
+.Pp
+See
+.Xr syslog 3
+for further descriptions of both the
+.Em facility
+and
+.Em level
+keywords and their significance.
+It is preferred that selections be made on
+.Em facility
+rather than
+.Em program ,
+since the latter can easily vary in a networked environment.
+In some cases,
+though, an appropriate
+.Em facility
+simply does not exist.
+.Pp
+If a received message matches the specified
+.Em facility
+and is of the specified
+.Em level
+.Em (or a higher level) ,
+and the first word in the message after the date matches the
+.Em program ,
+the action specified in the
+.Em action
+field will be taken.
+.Pp
+Multiple
+.Em selectors
+may be specified for a single
+.Em action
+by separating them with semicolon
+.Pq Dq \&;
+characters.
+It is important to note, however, that each
+.Em selector
+can modify the ones preceding it.
+.Pp
+Multiple
+.Em facilities
+may be specified for a single
+.Em level
+by separating them with comma
+.Pq Dq \&,
+characters.
+.Pp
+An asterisk
+.Pq Dq *
+can be used to specify all
+.Em facilities ,
+all
+.Em levels ,
+or all
+.Em programs .
+.Pp
+The special
+.Em facility
+.Dq mark
+receives a message at priority
+.Dq info
+every 20 minutes
+(see
+.Xr syslogd 8 ) .
+This is not enabled by a
+.Em facility
+field containing an asterisk.
+.Pp
+The special
+.Em level
+.Dq none
+disables a particular
+.Em facility .
+.Pp
+The
+.Em action
+field of each line specifies the action to be taken when the
+.Em selector
+field selects a message.
+There are five forms:
+.Bl -bullet
+.It
+A pathname (beginning with a leading slash).
+Selected messages are appended to the file.
+.Pp
+To ensure that kernel messages are written to disk promptly,
+.Nm
+calls
+.Xr fsync 2
+after writing messages from the kernel.
+Other messages are not synced explicitly.
+You may prefix a pathname with the minus sign,
+.Dq - ,
+to forego syncing the specified file after every kernel message.
+Note that you might lose information if the system crashes
+immediately following a write attempt.
+Nevertheless, using the
+.Dq -
+option may improve performance,
+especially if the kernel is logging many messages.
+.It
+A hostname (preceded by an at
+.Pq Dq @
+sign).
+Selected messages are forwarded to the
+.Xr syslogd 8
+program on the named host.
+If a port number is added after a colon
+.Pq Ql :\&
+then that port will be used as the destination port
+rather than the usual syslog port.
+IPv6 addresses can be used
+by surrounding the address portion with
+square brackets
+.Po
+.Ql [\&
+and
+.Ql ]\&
+.Pc .
+.It
+A comma separated list of users.
+Selected messages are written to those users
+if they are logged in.
+.It
+An asterisk.
+Selected messages are written to all logged-in users.
+.It
+A vertical bar
+.Pq Dq \&| ,
+followed by a command to pipe the selected
+messages to.
+The command is passed to
+.Xr sh 1
+for evaluation, so usual shell metacharacters or input/output
+redirection can occur.
+(Note however that redirecting
+.Xr stdio 3
+buffered output from the invoked command can cause additional delays,
+or even lost output data in case a logging subprocess exited with a
+signal.)
+The command itself runs with
+.Em stdout
+and
+.Em stderr
+redirected to
+.Pa /dev/null .
+Upon receipt of a
+.Dv SIGHUP ,
+.Xr syslogd 8
+will close the pipe to the process.
+If the process did not exit
+voluntarily, it will be sent a
+.Dv SIGTERM
+signal after a grace period of up to 60 seconds.
+.Pp
+The command will only be started once data arrives that should be piped
+to it.
+If it exited later, it will be restarted as necessary.
+So if it
+is desired that the subprocess should get exactly one line of input only
+(which can be very resource-consuming if there are a lot of messages
+flowing quickly), this can be achieved by exiting after just one line of
+input.
+If necessary, a script wrapper can be written to this effect.
+.Pp
+Unless the command is a full pipeline, it is probably useful to
+start the command with
+.Em exec
+so that the invoking shell process does not wait for the command to
+complete.
+Warning: the process is started under the UID invoking
+.Xr syslogd 8 ,
+normally the superuser.
+.El
+.Pp
+Blank lines and lines whose first non-blank character is a hash
+.Pq Dq #
+character are ignored.
+If
+.Ql #
+is placed in the middle of the line, the
+.Ql #
+character and the rest of the line after it is ignored.
+To prevent special meaning, the
+.Ql #
+character may be escaped with
+.Ql \e ;
+in this case preceding
+.Ql \e
+is removed and
+.Ql #
+is treated as an ordinary character.
+.Sh IMPLEMENTATION NOTES
+The
+.Dq kern
+facility is usually reserved for messages
+generated by the local kernel.
+Other messages logged with facility
+.Dq kern
+are usually translated to facility
+.Dq user .
+This translation can be disabled;
+see
+.Xr syslogd 8
+for details.
+.Sh FILES
+.Bl -tag -width /etc/syslog.conf -compact
+.It Pa /etc/syslog.conf
+.Xr syslogd 8
+configuration file
+.El
+.Sh EXAMPLES
+A configuration file might appear as follows:
+.Bd -literal
+# Log all kernel messages, authentication messages of
+# level notice or higher, and anything of level err or
+# higher to the console.
+# Don't log private authentication messages!
+*.err;kern.*;auth.notice;authpriv.none;mail.crit /dev/console
+
+# Log anything (except mail) of level info or higher.
+# Don't log private authentication messages!
+*.info;mail.none;authpriv.none /var/log/messages
+
+# Log daemon messages at debug level only
+daemon.=debug /var/log/daemon.debug
+
+# The authpriv file has restricted access.
+authpriv.* /var/log/secure
+
+# Log all the mail messages in one place.
+mail.* /var/log/maillog
+
+# Everybody gets emergency messages, plus log them on another
+# machine.
+*.emerg *
+*.emerg @arpa.berkeley.edu
+
+# Root and Eric get alert and higher messages.
+*.alert root,eric
+
+# Save mail and news errors of level err and higher in a
+# special file.
+uucp,news.crit /var/log/spoolerr
+
+# Pipe all authentication messages to a filter.
+auth.* |exec /usr/local/sbin/authfilter
+
+# Log all security messages to a separate file.
+security.* /var/log/security
+
+# Log all writes to /dev/console to a separate file.
+console.* /var/log/console.log
+
+# Save ftpd transactions along with mail and news
+!ftpd
+*.* /var/log/spoolerr
+
+# Log ipfw messages without syncing after every message.
+!ipfw
+*.* -/var/log/ipfw
+.Ed
+.Sh SEE ALSO
+.Xr syslog 3 ,
+.Xr syslogd 8
+.Sh BUGS
+The effects of multiple
+.Em selectors
+are sometimes not intuitive.
+For example
+.Dq mail.crit,*.err
+will select
+.Dq mail
+facility messages at the level of
+.Dq err
+or higher, not at the level of
+.Dq crit
+or higher.
+.Pp
+In networked environments, note that not all operating systems
+implement the same set of facilities.
+The facilities
+authpriv, cron, ftp, and ntp that are known to this implementation
+might be absent on the target system.
+Even worse, DEC UNIX uses
+facility number 10 (which is authpriv in this implementation) to
+log events for their AdvFS file system.
diff --git a/usr.sbin/syslogd/syslogd.8 b/usr.sbin/syslogd/syslogd.8
new file mode 100644
index 0000000..3074d08
--- /dev/null
+++ b/usr.sbin/syslogd/syslogd.8
@@ -0,0 +1,427 @@
+.\" Copyright (c) 1983, 1986, 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.
+.\"
+.\" @(#)syslogd.8 8.1 (Berkeley) 6/6/93
+.\" $FreeBSD$
+.\"
+.Dd June 16, 2015
+.Dt SYSLOGD 8
+.Os
+.Sh NAME
+.Nm syslogd
+.Nd log systems messages
+.Sh SYNOPSIS
+.Nm
+.Op Fl 468ACcdFkNnosTuv
+.Op Fl a Ar allowed_peer
+.Op Fl b Ar bind_address
+.Op Fl f Ar config_file
+.Op Fl l Oo Ar mode : Oc Ns Ar path
+.Op Fl m Ar mark_interval
+.Op Fl P Ar pid_file
+.Op Fl p Ar log_socket
+.Sh DESCRIPTION
+The
+.Nm
+utility reads and logs messages to the system console, log files, other
+machines and/or users as specified by its configuration file.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl 4
+Force
+.Nm
+to use IPv4 addresses only.
+.It Fl 6
+Force
+.Nm
+to use IPv6 addresses only.
+.It Fl 8
+Tells
+.Nm
+not to interfere with 8-bit data. Normally
+.Nm
+will replace C1 control characters
+.Pq ISO 8859 and Unicode characters
+with their
+.Dq M- Ns Em x
+equivalent.
+Note, this option does not change the way
+.Nm
+alters control characters
+.Pq see Xr iscntrl 3 .
+They will always be replaced with their
+.Dq ^ Ns Em x
+equivalent.
+.It Fl A
+Ordinarily,
+.Nm
+tries to send the message to only one address
+even if the host has more than one A or AAAA record.
+If this option is specified,
+.Nm
+tries to send the message to all addresses.
+.It Fl a Ar allowed_peer
+Allow
+.Ar allowed_peer
+to log to this
+.Nm
+using UDP datagrams.
+Multiple
+.Fl a
+options may be specified.
+.Pp
+The
+.Ar allowed_peer
+option may be any of the following:
+.Bl -tag -width "ipaddr/masklen[:service]XX"
+.It Xo
+.Sm off
+.Ar ipaddr
+.No / Ar masklen
+.Op : Ar service
+.Sm on
+.Xc
+Accept datagrams from
+.Ar ipaddr
+(in the usual dotted quad notation) with
+.Ar masklen
+bits being taken into account when doing the address comparison.
+.Ar ipaddr
+can be also IPv6 address by enclosing the address with
+.Ql \&[
+and
+.Ql \&] .
+If specified,
+.Ar service
+is the name or number of an UDP service (see
+.Xr services 5 )
+the source packet must belong to.
+A
+.Ar service
+of
+.Ql \&*
+allows packets being sent from any UDP port.
+The default
+.Ar service
+is
+.Ql syslog .
+If
+.Ar ipaddr
+is IPv4 address, a missing
+.Ar masklen
+will be substituted by the historic class A or class B netmasks if
+.Ar ipaddr
+belongs into the address range of class A or B, respectively, or
+by 24 otherwise.
+If
+.Ar ipaddr
+is IPv6 address, a missing
+.Ar masklen
+will be substituted by 128.
+.It Xo
+.Sm off
+.Ar domainname Op : Ar service
+.Sm on
+.Xc
+Accept datagrams where the reverse address lookup yields
+.Ar domainname
+for the sender address.
+The meaning of
+.Ar service
+is as explained above.
+.It Xo
+.Sm off
+.No * Ar domainname Op : Ar service
+.Sm on
+.Xc
+Same as before, except that any source host whose name
+.Em ends
+in
+.Ar domainname
+will get permission.
+.El
+.Pp
+The
+.Fl a
+options are ignored if the
+.Fl s
+option is also specified.
+.It Xo
+.Fl b
+.Sm off
+.Ar bind_address Op : Ar service
+.Sm on
+.Xc
+.It Xo
+.Fl b
+.Sm off
+.Li : Ar service
+.Sm on
+.Xc
+Bind to a specific address and/or port.
+The address can be specified as a hostname,
+and the port as a service name.
+If an IPv6 address is specified, it should be enclosed with
+.Ql \&[
+and
+.Ql \&] .
+The default
+.Ar service
+is
+.Ql syslog .
+This option can be specified multiple times to bind to
+multiple addresses and/or ports.
+.It Fl C
+Create log files that do not exist (permission is set to
+.Li 0600 ) .
+.It Fl c
+Disable the compression of repeated instances of the same line
+into a single line of the form
+.Dq Li "last message repeated N times"
+when the output is a pipe to another program.
+If specified twice, disable this compression in all cases.
+.It Fl d
+Put
+.Nm
+into debugging mode.
+This is probably only of use to developers working on
+.Nm .
+.It Fl f
+Specify the pathname of an alternate configuration file;
+the default is
+.Pa /etc/syslog.conf .
+.It Fl F
+Run
+.Nm
+in the foreground, rather than going into daemon mode. This is useful if
+some other process uses
+.Xr fork 2
+and
+.Xr exec 3
+to run
+.Nm ,
+and wants to monitor when and how it exits.
+.It Fl k
+Disable the translation of
+messages received with facility
+.Dq kern
+to facility
+.Dq user .
+Usually the
+.Dq kern
+facility is reserved for messages read directly from
+.Pa /dev/klog .
+.It Fl m
+Select the number of minutes between
+.Dq mark
+messages; the default is 20 minutes.
+.It Fl N
+Disable binding on UDP sockets. RFC 3164 recommends that outgoing
+syslogd messages should originate from the privileged port, this
+option
+.Em disables
+the recommended behavior. This option inherits
+.Fl s .
+.It Fl n
+Disable dns query for every request.
+.It Fl o
+Prefix kernel messages with the full kernel boot file as determined by
+.Xr getbootfile 3 .
+Without this, the kernel message prefix is always
+.Dq Li kernel: .
+.It Fl p
+Specify the pathname of an alternate log socket to be used instead;
+the default is
+.Pa /var/run/log .
+.It Fl P
+Specify an alternative file in which to store the process ID.
+The default is
+.Pa /var/run/syslog.pid .
+.It Fl S
+Specify the pathname of an alternate log socket for privileged
+applications to be used instead; the default is
+.Pa /var/run/logpriv .
+.It Fl l
+Specify a location where
+.Nm
+should place an additional log socket.
+The primary use for this is to place additional log sockets in
+.Pa /var/run/log
+of various chroot filespaces.
+File permissions for socket can be specified in octal representation
+before socket name, delimited with a colon.
+Path to socket location must be absolute.
+.It Fl s
+Operate in secure mode.
+Do not log messages from remote machines.
+If
+specified twice, no network socket will be opened at all, which also
+disables logging to remote machines.
+.It Fl T
+Always use the local time and date for messages received from the network,
+instead of the timestamp field supplied in the message by the remote host.
+This is useful if some of the originating hosts can't keep time properly
+or are unable to generate a correct timestamp.
+.It Fl u
+Unique priority logging.
+Only log messages at the specified priority.
+Without this option, messages at the stated priority or higher are logged.
+This option changes the default comparison from
+.Dq =>
+to
+.Dq = .
+.It Fl v
+Verbose logging.
+If specified once, the numeric facility and priority are
+logged with each locally-written message.
+If specified more than once,
+the names of the facility and priority are logged with each locally-written
+message.
+.El
+.Pp
+The
+.Nm
+utility reads its configuration file when it starts up and whenever it
+receives a hangup signal.
+For information on the format of the configuration file,
+see
+.Xr syslog.conf 5 .
+.Pp
+The
+.Nm
+utility reads messages from the
+.Ux
+domain sockets
+.Pa /var/run/log
+and
+.Pa /var/run/logpriv ,
+from an Internet domain socket specified in
+.Pa /etc/services ,
+and from the special device
+.Pa /dev/klog
+(to read kernel messages).
+.Pp
+The
+.Nm
+utility creates its process ID file,
+by default
+.Pa /var/run/syslog.pid ,
+and stores its process
+ID there.
+This can be used to kill or reconfigure
+.Nm .
+.Pp
+The message sent to
+.Nm
+should consist of a single line.
+The message can contain a priority code, which should be a preceding
+decimal number in angle braces, for example,
+.Sq Aq 5 .
+This priority code should map into the priorities defined in the
+include file
+.In sys/syslog.h .
+.Pp
+For security reasons,
+.Nm
+will not append to log files that do not exist (unless
+.Fl C
+option is specified);
+therefore, they must be created manually before running
+.Nm .
+.Pp
+The date and time are taken from the received message.
+If the format of the timestamp field is incorrect,
+time obtained from the local host is used instead.
+This can be overridden by the
+.Fl T
+flag.
+.Sh FILES
+.Bl -tag -width /var/run/syslog.pid -compact
+.It Pa /etc/syslog.conf
+configuration file
+.It Pa /var/run/syslog.pid
+default process ID file
+.It Pa /var/run/log
+name of the
+.Ux
+domain datagram log socket
+.It Pa /var/run/logpriv
+.Ux
+socket for privileged applications
+.It Pa /dev/klog
+kernel log device
+.El
+.Sh SEE ALSO
+.Xr logger 1 ,
+.Xr syslog 3 ,
+.Xr services 5 ,
+.Xr syslog.conf 5 ,
+.Xr newsyslog 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.3 .
+.Pp
+The
+.Fl a ,
+.Fl s ,
+.Fl u ,
+and
+.Fl v
+options are
+.Fx 2.2
+extensions.
+.Sh BUGS
+The ability to log messages received in UDP packets is equivalent to
+an unauthenticated remote disk-filling service, and should probably be
+disabled by default.
+Some sort of
+.No inter- Ns Nm syslogd
+authentication mechanism ought to be worked out.
+To prevent the worst
+abuse, use of the
+.Fl a
+option is therefore highly recommended.
+.Pp
+The
+.Fl a
+matching algorithm does not pretend to be very efficient; use of numeric
+IP addresses is faster than domain name comparison.
+Since the allowed
+peer list is being walked linearly, peer groups where frequent messages
+are being anticipated from should be put early into the
+.Fl a
+list.
+.Pp
+The log socket was moved from
+.Pa /dev
+to ease the use of a read-only root file system.
+This may confuse
+some old binaries so that a symbolic link might be used for a
+transitional period.
diff --git a/usr.sbin/syslogd/syslogd.c b/usr.sbin/syslogd/syslogd.c
new file mode 100644
index 0000000..cd0f1b6
--- /dev/null
+++ b/usr.sbin/syslogd/syslogd.c
@@ -0,0 +1,2851 @@
+/*
+ * Copyright (c) 1983, 1988, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 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, 1988, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)syslogd.c 8.3 (Berkeley) 4/4/94";
+#endif
+#endif /* not lint */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * syslogd -- log system messages
+ *
+ * This program implements a system log. It takes a series of lines.
+ * Each line may have a priority, signified as "<n>" as
+ * the first characters of the line. If this is
+ * not present, a default priority is used.
+ *
+ * To kill syslogd, send a signal 15 (terminate). A signal 1 (hup) will
+ * cause it to reread its configuration file.
+ *
+ * Defined Constants:
+ *
+ * MAXLINE -- the maximum line length that can be handled.
+ * DEFUPRI -- the default priority for user messages
+ * DEFSPRI -- the default priority for kernel messages
+ *
+ * Author: Eric Allman
+ * extensive changes by Ralph Campbell
+ * more extensive changes by Eric Allman (again)
+ * Extension to log by program name as well as facility and priority
+ * by Peter da Silva.
+ * -u and -v by Harlan Stenn.
+ * Priority comparison code by Harlan Stenn.
+ */
+
+#define MAXLINE 1024 /* maximum line length */
+#define MAXSVLINE 120 /* maximum saved line length */
+#define DEFUPRI (LOG_USER|LOG_NOTICE)
+#define DEFSPRI (LOG_KERN|LOG_CRIT)
+#define TIMERINTVL 30 /* interval for checking flush, mark */
+#define TTYMSGTIME 1 /* timeout passed to ttymsg */
+#define RCVBUF_MINSIZE (80 * 1024) /* minimum size of dgram rcv buffer */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/syslimits.h>
+#include <sys/types.h>
+
+#include <netinet/in.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libutil.h>
+#include <limits.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <utmpx.h>
+
+#include "pathnames.h"
+#include "ttymsg.h"
+
+#define SYSLOG_NAMES
+#include <sys/syslog.h>
+
+const char *ConfFile = _PATH_LOGCONF;
+const char *PidFile = _PATH_LOGPID;
+const char ctty[] = _PATH_CONSOLE;
+
+#define dprintf if (Debug) printf
+
+#define MAXUNAMES 20 /* maximum number of user names */
+
+/*
+ * List of hosts for binding.
+ */
+static STAILQ_HEAD(, host) hqueue;
+struct host {
+ char *name;
+ STAILQ_ENTRY(host) next;
+};
+
+/*
+ * Unix sockets.
+ * We have two default sockets, one with 666 permissions,
+ * and one for privileged programs.
+ */
+struct funix {
+ int s;
+ const char *name;
+ mode_t mode;
+ STAILQ_ENTRY(funix) next;
+};
+struct funix funix_secure = { -1, _PATH_LOG_PRIV, S_IRUSR | S_IWUSR,
+ { NULL } };
+struct funix funix_default = { -1, _PATH_LOG, DEFFILEMODE,
+ { &funix_secure } };
+
+STAILQ_HEAD(, funix) funixes = { &funix_default,
+ &(funix_secure.next.stqe_next) };
+
+/*
+ * Flags to logmsg().
+ */
+
+#define IGN_CONS 0x001 /* don't print on console */
+#define SYNC_FILE 0x002 /* do fsync on file after printing */
+#define ADDDATE 0x004 /* add a date to the message */
+#define MARK 0x008 /* this message is a mark */
+#define ISKERNEL 0x010 /* kernel generated message */
+
+/*
+ * This structure represents the files that will have log
+ * copies printed.
+ * We require f_file to be valid if f_type is F_FILE, F_CONSOLE, F_TTY
+ * or if f_type if F_PIPE and f_pid > 0.
+ */
+
+struct filed {
+ struct filed *f_next; /* next in linked list */
+ short f_type; /* entry type, see below */
+ short f_file; /* file descriptor */
+ time_t f_time; /* time this was last written */
+ char *f_host; /* host from which to recd. */
+ u_char f_pmask[LOG_NFACILITIES+1]; /* priority mask */
+ u_char f_pcmp[LOG_NFACILITIES+1]; /* compare priority */
+#define PRI_LT 0x1
+#define PRI_EQ 0x2
+#define PRI_GT 0x4
+ char *f_program; /* program this applies to */
+ union {
+ char f_uname[MAXUNAMES][MAXLOGNAME];
+ struct {
+ char f_hname[MAXHOSTNAMELEN];
+ struct addrinfo *f_addr;
+
+ } f_forw; /* forwarding address */
+ char f_fname[MAXPATHLEN];
+ struct {
+ char f_pname[MAXPATHLEN];
+ pid_t f_pid;
+ } f_pipe;
+ } f_un;
+ char f_prevline[MAXSVLINE]; /* last message logged */
+ char f_lasttime[16]; /* time of last occurrence */
+ char f_prevhost[MAXHOSTNAMELEN]; /* host from which recd. */
+ int f_prevpri; /* pri of f_prevline */
+ int f_prevlen; /* length of f_prevline */
+ int f_prevcount; /* repetition cnt of prevline */
+ u_int f_repeatcount; /* number of "repeated" msgs */
+ int f_flags; /* file-specific flags */
+#define FFLAG_SYNC 0x01
+#define FFLAG_NEEDSYNC 0x02
+};
+
+/*
+ * Queue of about-to-be dead processes we should watch out for.
+ */
+
+TAILQ_HEAD(stailhead, deadq_entry) deadq_head;
+struct stailhead *deadq_headp;
+
+struct deadq_entry {
+ pid_t dq_pid;
+ int dq_timeout;
+ TAILQ_ENTRY(deadq_entry) dq_entries;
+};
+
+/*
+ * The timeout to apply to processes waiting on the dead queue. Unit
+ * of measure is `mark intervals', i.e. 20 minutes by default.
+ * Processes on the dead queue will be terminated after that time.
+ */
+
+#define DQ_TIMO_INIT 2
+
+typedef struct deadq_entry *dq_t;
+
+
+/*
+ * Struct to hold records of network addresses that are allowed to log
+ * to us.
+ */
+struct allowedpeer {
+ int isnumeric;
+ u_short port;
+ union {
+ struct {
+ struct sockaddr_storage addr;
+ struct sockaddr_storage mask;
+ } numeric;
+ char *name;
+ } u;
+#define a_addr u.numeric.addr
+#define a_mask u.numeric.mask
+#define a_name u.name
+};
+
+
+/*
+ * Intervals at which we flush out "message repeated" messages,
+ * in seconds after previous message is logged. After each flush,
+ * we move to the next interval until we reach the largest.
+ */
+int repeatinterval[] = { 30, 120, 600 }; /* # of secs before flush */
+#define MAXREPEAT ((sizeof(repeatinterval) / sizeof(repeatinterval[0])) - 1)
+#define REPEATTIME(f) ((f)->f_time + repeatinterval[(f)->f_repeatcount])
+#define BACKOFF(f) { if (++(f)->f_repeatcount > MAXREPEAT) \
+ (f)->f_repeatcount = MAXREPEAT; \
+ }
+
+/* values for f_type */
+#define F_UNUSED 0 /* unused entry */
+#define F_FILE 1 /* regular file */
+#define F_TTY 2 /* terminal */
+#define F_CONSOLE 3 /* console terminal */
+#define F_FORW 4 /* remote machine */
+#define F_USERS 5 /* list of users */
+#define F_WALL 6 /* everyone logged on */
+#define F_PIPE 7 /* pipe to program */
+
+const char *TypeNames[8] = {
+ "UNUSED", "FILE", "TTY", "CONSOLE",
+ "FORW", "USERS", "WALL", "PIPE"
+};
+
+static struct filed *Files; /* Log files that we write to */
+static struct filed consfile; /* Console */
+
+static int Debug; /* debug flag */
+static int Foreground = 0; /* Run in foreground, instead of daemonizing */
+static int resolve = 1; /* resolve hostname */
+static char LocalHostName[MAXHOSTNAMELEN]; /* our hostname */
+static const char *LocalDomain; /* our local domain name */
+static int *finet; /* Internet datagram sockets */
+static int fklog = -1; /* /dev/klog */
+static int Initialized; /* set when we have initialized ourselves */
+static int MarkInterval = 20 * 60; /* interval between marks in seconds */
+static int MarkSeq; /* mark sequence number */
+static int NoBind; /* don't bind() as suggested by RFC 3164 */
+static int SecureMode; /* when true, receive only unix domain socks */
+#ifdef INET6
+static int family = PF_UNSPEC; /* protocol family (IPv4, IPv6 or both) */
+#else
+static int family = PF_INET; /* protocol family (IPv4 only) */
+#endif
+static int mask_C1 = 1; /* mask characters from 0x80 - 0x9F */
+static int send_to_all; /* send message to all IPv4/IPv6 addresses */
+static int use_bootfile; /* log entire bootfile for every kern msg */
+static int no_compress; /* don't compress messages (1=pipes, 2=all) */
+static int logflags = O_WRONLY|O_APPEND; /* flags used to open log files */
+
+static char bootfile[MAXLINE+1]; /* booted kernel file */
+
+struct allowedpeer *AllowedPeers; /* List of allowed peers */
+static int NumAllowed; /* Number of entries in AllowedPeers */
+static int RemoteAddDate; /* Always set the date on remote messages */
+
+static int UniquePriority; /* Only log specified priority? */
+static int LogFacPri; /* Put facility and priority in log message: */
+ /* 0=no, 1=numeric, 2=names */
+static int KeepKernFac; /* Keep remotely logged kernel facility */
+static int needdofsync = 0; /* Are any file(s) waiting to be fsynced? */
+static struct pidfh *pfh;
+
+volatile sig_atomic_t MarkSet, WantDie;
+
+static int allowaddr(char *);
+static void cfline(const char *, struct filed *,
+ const char *, const char *);
+static const char *cvthname(struct sockaddr *);
+static void deadq_enter(pid_t, const char *);
+static int deadq_remove(pid_t);
+static int decode(const char *, const CODE *);
+static void die(int) __dead2;
+static void dodie(int);
+static void dofsync(void);
+static void domark(int);
+static void fprintlog(struct filed *, int, const char *);
+static int *socksetup(int, char *);
+static void init(int);
+static void logerror(const char *);
+static void logmsg(int, const char *, const char *, int);
+static void log_deadchild(pid_t, int, const char *);
+static void markit(void);
+static int skip_message(const char *, const char *, int);
+static void printline(const char *, char *, int);
+static void printsys(char *);
+static int p_open(const char *, pid_t *);
+static void readklog(void);
+static void reapchild(int);
+static const char *ttymsg_check(struct iovec *, int, char *, int);
+static void usage(void);
+static int validate(struct sockaddr *, const char *);
+static void unmapped(struct sockaddr *);
+static void wallmsg(struct filed *, struct iovec *, const int iovlen);
+static int waitdaemon(int, int, int);
+static void timedout(int);
+static void increase_rcvbuf(int);
+
+static void
+close_filed(struct filed *f)
+{
+
+ if (f == NULL || f->f_file == -1)
+ return;
+
+ (void)close(f->f_file);
+ f->f_file = -1;
+ f->f_type = F_UNUSED;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ch, i, fdsrmax = 0, l;
+ struct sockaddr_un sunx, fromunix;
+ struct sockaddr_storage frominet;
+ fd_set *fdsr = NULL;
+ char line[MAXLINE + 1];
+ const char *hname;
+ struct timeval tv, *tvp;
+ struct sigaction sact;
+ struct host *host;
+ struct funix *fx, *fx1;
+ sigset_t mask;
+ pid_t ppid = 1, spid;
+ socklen_t len;
+
+ if (madvise(NULL, 0, MADV_PROTECT) != 0)
+ dprintf("madvise() failed: %s\n", strerror(errno));
+
+ STAILQ_INIT(&hqueue);
+
+ while ((ch = getopt(argc, argv, "468Aa:b:cCdf:Fkl:m:nNop:P:sS:Tuv"))
+ != -1)
+ switch (ch) {
+ case '4':
+ family = PF_INET;
+ break;
+#ifdef INET6
+ case '6':
+ family = PF_INET6;
+ break;
+#endif
+ case '8':
+ mask_C1 = 0;
+ break;
+ case 'A':
+ send_to_all++;
+ break;
+ case 'a': /* allow specific network addresses only */
+ if (allowaddr(optarg) == -1)
+ usage();
+ break;
+ case 'b':
+ {
+ if ((host = malloc(sizeof(struct host))) == NULL)
+ err(1, "malloc failed");
+ host->name = optarg;
+ STAILQ_INSERT_TAIL(&hqueue, host, next);
+ break;
+ }
+ case 'c':
+ no_compress++;
+ break;
+ case 'C':
+ logflags |= O_CREAT;
+ break;
+ case 'd': /* debug */
+ Debug++;
+ break;
+ case 'f': /* configuration file */
+ ConfFile = optarg;
+ break;
+ case 'F': /* run in foreground instead of daemon */
+ Foreground++;
+ break;
+ case 'k': /* keep remote kern fac */
+ KeepKernFac = 1;
+ break;
+ case 'l':
+ {
+ long perml;
+ mode_t mode;
+ char *name, *ep;
+
+ if (optarg[0] == '/') {
+ mode = DEFFILEMODE;
+ name = optarg;
+ } else if ((name = strchr(optarg, ':')) != NULL) {
+ *name++ = '\0';
+ if (name[0] != '/')
+ errx(1, "socket name must be absolute "
+ "path");
+ if (isdigit(*optarg)) {
+ perml = strtol(optarg, &ep, 8);
+ if (*ep || perml < 0 ||
+ perml & ~(S_IRWXU|S_IRWXG|S_IRWXO))
+ errx(1, "invalid mode %s, exiting",
+ optarg);
+ mode = (mode_t )perml;
+ } else
+ errx(1, "invalid mode %s, exiting",
+ optarg);
+ } else /* doesn't begin with '/', and no ':' */
+ errx(1, "can't parse path %s", optarg);
+
+ if (strlen(name) >= sizeof(sunx.sun_path))
+ errx(1, "%s path too long, exiting", name);
+ if ((fx = malloc(sizeof(struct funix))) == NULL)
+ err(1, "malloc failed");
+ fx->s = -1;
+ fx->name = name;
+ fx->mode = mode;
+ STAILQ_INSERT_TAIL(&funixes, fx, next);
+ break;
+ }
+ case 'm': /* mark interval */
+ MarkInterval = atoi(optarg) * 60;
+ break;
+ case 'N':
+ NoBind = 1;
+ SecureMode = 1;
+ break;
+ case 'n':
+ resolve = 0;
+ break;
+ case 'o':
+ use_bootfile = 1;
+ break;
+ case 'p': /* path */
+ if (strlen(optarg) >= sizeof(sunx.sun_path))
+ errx(1, "%s path too long, exiting", optarg);
+ funix_default.name = optarg;
+ break;
+ case 'P': /* path for alt. PID */
+ PidFile = optarg;
+ break;
+ case 's': /* no network mode */
+ SecureMode++;
+ break;
+ case 'S': /* path for privileged originator */
+ if (strlen(optarg) >= sizeof(sunx.sun_path))
+ errx(1, "%s path too long, exiting", optarg);
+ funix_secure.name = optarg;
+ break;
+ case 'T':
+ RemoteAddDate = 1;
+ break;
+ case 'u': /* only log specified priority */
+ UniquePriority++;
+ break;
+ case 'v': /* log facility and priority */
+ LogFacPri++;
+ break;
+ default:
+ usage();
+ }
+ if ((argc -= optind) != 0)
+ usage();
+
+ pfh = pidfile_open(PidFile, 0600, &spid);
+ if (pfh == NULL) {
+ if (errno == EEXIST)
+ errx(1, "syslogd already running, pid: %d", spid);
+ warn("cannot open pid file");
+ }
+
+ if ((!Foreground) && (!Debug)) {
+ ppid = waitdaemon(0, 0, 30);
+ if (ppid < 0) {
+ warn("could not become daemon");
+ pidfile_remove(pfh);
+ exit(1);
+ }
+ } else if (Debug) {
+ setlinebuf(stdout);
+ }
+
+ if (NumAllowed)
+ endservent();
+
+ consfile.f_type = F_CONSOLE;
+ (void)strlcpy(consfile.f_un.f_fname, ctty + sizeof _PATH_DEV - 1,
+ sizeof(consfile.f_un.f_fname));
+ (void)strlcpy(bootfile, getbootfile(), sizeof(bootfile));
+ (void)signal(SIGTERM, dodie);
+ (void)signal(SIGINT, Debug ? dodie : SIG_IGN);
+ (void)signal(SIGQUIT, Debug ? dodie : SIG_IGN);
+ /*
+ * We don't want the SIGCHLD and SIGHUP handlers to interfere
+ * with each other; they are likely candidates for being called
+ * simultaneously (SIGHUP closes pipe descriptor, process dies,
+ * SIGCHLD happens).
+ */
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGHUP);
+ sact.sa_handler = reapchild;
+ sact.sa_mask = mask;
+ sact.sa_flags = SA_RESTART;
+ (void)sigaction(SIGCHLD, &sact, NULL);
+ (void)signal(SIGALRM, domark);
+ (void)signal(SIGPIPE, SIG_IGN); /* We'll catch EPIPE instead. */
+ (void)alarm(TIMERINTVL);
+
+ TAILQ_INIT(&deadq_head);
+
+#ifndef SUN_LEN
+#define SUN_LEN(unp) (strlen((unp)->sun_path) + 2)
+#endif
+ STAILQ_FOREACH_SAFE(fx, &funixes, next, fx1) {
+ (void)unlink(fx->name);
+ memset(&sunx, 0, sizeof(sunx));
+ sunx.sun_family = AF_LOCAL;
+ (void)strlcpy(sunx.sun_path, fx->name, sizeof(sunx.sun_path));
+ fx->s = socket(PF_LOCAL, SOCK_DGRAM, 0);
+ if (fx->s < 0 ||
+ bind(fx->s, (struct sockaddr *)&sunx, SUN_LEN(&sunx)) < 0 ||
+ chmod(fx->name, fx->mode) < 0) {
+ (void)snprintf(line, sizeof line,
+ "cannot create %s", fx->name);
+ logerror(line);
+ dprintf("cannot create %s (%d)\n", fx->name, errno);
+ if (fx == &funix_default || fx == &funix_secure)
+ die(0);
+ else {
+ STAILQ_REMOVE(&funixes, fx, funix, next);
+ continue;
+ }
+ }
+ increase_rcvbuf(fx->s);
+ }
+ if (SecureMode <= 1) {
+ if (STAILQ_EMPTY(&hqueue))
+ finet = socksetup(family, NULL);
+ STAILQ_FOREACH(host, &hqueue, next) {
+ int *finet0, total;
+ finet0 = socksetup(family, host->name);
+ if (finet0 && !finet) {
+ finet = finet0;
+ } else if (finet0 && finet) {
+ total = *finet0 + *finet + 1;
+ finet = realloc(finet, total * sizeof(int));
+ if (finet == NULL)
+ err(1, "realloc failed");
+ for (i = 1; i <= *finet0; i++) {
+ finet[(*finet)+i] = finet0[i];
+ }
+ *finet = total - 1;
+ free(finet0);
+ }
+ }
+ }
+
+ if (finet) {
+ if (SecureMode) {
+ for (i = 0; i < *finet; i++) {
+ if (shutdown(finet[i+1], SHUT_RD) < 0 &&
+ errno != ENOTCONN) {
+ logerror("shutdown");
+ if (!Debug)
+ die(0);
+ }
+ }
+ } else {
+ dprintf("listening on inet and/or inet6 socket\n");
+ }
+ dprintf("sending on inet and/or inet6 socket\n");
+ }
+
+ if ((fklog = open(_PATH_KLOG, O_RDONLY, 0)) >= 0)
+ if (fcntl(fklog, F_SETFL, O_NONBLOCK) < 0)
+ fklog = -1;
+ if (fklog < 0)
+ dprintf("can't open %s (%d)\n", _PATH_KLOG, errno);
+
+ /* tuck my process id away */
+ pidfile_write(pfh);
+
+ dprintf("off & running....\n");
+
+ init(0);
+ /* prevent SIGHUP and SIGCHLD handlers from running in parallel */
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGCHLD);
+ sact.sa_handler = init;
+ sact.sa_mask = mask;
+ sact.sa_flags = SA_RESTART;
+ (void)sigaction(SIGHUP, &sact, NULL);
+
+ tvp = &tv;
+ tv.tv_sec = tv.tv_usec = 0;
+
+ if (fklog != -1 && fklog > fdsrmax)
+ fdsrmax = fklog;
+ if (finet && !SecureMode) {
+ for (i = 0; i < *finet; i++) {
+ if (finet[i+1] != -1 && finet[i+1] > fdsrmax)
+ fdsrmax = finet[i+1];
+ }
+ }
+ STAILQ_FOREACH(fx, &funixes, next)
+ if (fx->s > fdsrmax)
+ fdsrmax = fx->s;
+
+ fdsr = (fd_set *)calloc(howmany(fdsrmax+1, NFDBITS),
+ sizeof(fd_mask));
+ if (fdsr == NULL)
+ errx(1, "calloc fd_set");
+
+ for (;;) {
+ if (MarkSet)
+ markit();
+ if (WantDie)
+ die(WantDie);
+
+ bzero(fdsr, howmany(fdsrmax+1, NFDBITS) *
+ sizeof(fd_mask));
+
+ if (fklog != -1)
+ FD_SET(fklog, fdsr);
+ if (finet && !SecureMode) {
+ for (i = 0; i < *finet; i++) {
+ if (finet[i+1] != -1)
+ FD_SET(finet[i+1], fdsr);
+ }
+ }
+ STAILQ_FOREACH(fx, &funixes, next)
+ FD_SET(fx->s, fdsr);
+
+ i = select(fdsrmax+1, fdsr, NULL, NULL,
+ needdofsync ? &tv : tvp);
+ switch (i) {
+ case 0:
+ dofsync();
+ needdofsync = 0;
+ if (tvp) {
+ tvp = NULL;
+ if (ppid != 1)
+ kill(ppid, SIGALRM);
+ }
+ continue;
+ case -1:
+ if (errno != EINTR)
+ logerror("select");
+ continue;
+ }
+ if (fklog != -1 && FD_ISSET(fklog, fdsr))
+ readklog();
+ if (finet && !SecureMode) {
+ for (i = 0; i < *finet; i++) {
+ if (FD_ISSET(finet[i+1], fdsr)) {
+ len = sizeof(frominet);
+ l = recvfrom(finet[i+1], line, MAXLINE,
+ 0, (struct sockaddr *)&frominet,
+ &len);
+ if (l > 0) {
+ line[l] = '\0';
+ hname = cvthname((struct sockaddr *)&frominet);
+ unmapped((struct sockaddr *)&frominet);
+ if (validate((struct sockaddr *)&frominet, hname))
+ printline(hname, line, RemoteAddDate ? ADDDATE : 0);
+ } else if (l < 0 && errno != EINTR)
+ logerror("recvfrom inet");
+ }
+ }
+ }
+ STAILQ_FOREACH(fx, &funixes, next) {
+ if (FD_ISSET(fx->s, fdsr)) {
+ len = sizeof(fromunix);
+ l = recvfrom(fx->s, line, MAXLINE, 0,
+ (struct sockaddr *)&fromunix, &len);
+ if (l > 0) {
+ line[l] = '\0';
+ printline(LocalHostName, line, 0);
+ } else if (l < 0 && errno != EINTR)
+ logerror("recvfrom unix");
+ }
+ }
+ }
+ if (fdsr)
+ free(fdsr);
+}
+
+static void
+unmapped(struct sockaddr *sa)
+{
+ struct sockaddr_in6 *sin6;
+ struct sockaddr_in sin4;
+
+ if (sa->sa_family != AF_INET6)
+ return;
+ if (sa->sa_len != sizeof(struct sockaddr_in6) ||
+ sizeof(sin4) > sa->sa_len)
+ return;
+ sin6 = (struct sockaddr_in6 *)sa;
+ if (!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
+ return;
+
+ memset(&sin4, 0, sizeof(sin4));
+ sin4.sin_family = AF_INET;
+ sin4.sin_len = sizeof(struct sockaddr_in);
+ memcpy(&sin4.sin_addr, &sin6->sin6_addr.s6_addr[12],
+ sizeof(sin4.sin_addr));
+ sin4.sin_port = sin6->sin6_port;
+
+ memcpy(sa, &sin4, sin4.sin_len);
+}
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "%s\n%s\n%s\n%s\n",
+ "usage: syslogd [-468ACcdFknosTuv] [-a allowed_peer]",
+ " [-b bind_address] [-f config_file]",
+ " [-l [mode:]path] [-m mark_interval]",
+ " [-P pid_file] [-p log_socket]");
+ exit(1);
+}
+
+/*
+ * Take a raw input line, decode the message, and print the message
+ * on the appropriate log files.
+ */
+static void
+printline(const char *hname, char *msg, int flags)
+{
+ char *p, *q;
+ long n;
+ int c, pri;
+ char line[MAXLINE + 1];
+
+ /* test for special codes */
+ p = msg;
+ pri = DEFUPRI;
+ if (*p == '<') {
+ errno = 0;
+ n = strtol(p + 1, &q, 10);
+ if (*q == '>' && n >= 0 && n < INT_MAX && errno == 0) {
+ p = q + 1;
+ pri = n;
+ }
+ }
+ if (pri &~ (LOG_FACMASK|LOG_PRIMASK))
+ pri = DEFUPRI;
+
+ /*
+ * Don't allow users to log kernel messages.
+ * NOTE: since LOG_KERN == 0 this will also match
+ * messages with no facility specified.
+ */
+ if ((pri & LOG_FACMASK) == LOG_KERN && !KeepKernFac)
+ pri = LOG_MAKEPRI(LOG_USER, LOG_PRI(pri));
+
+ q = line;
+
+ while ((c = (unsigned char)*p++) != '\0' &&
+ q < &line[sizeof(line) - 4]) {
+ if (mask_C1 && (c & 0x80) && c < 0xA0) {
+ c &= 0x7F;
+ *q++ = 'M';
+ *q++ = '-';
+ }
+ if (isascii(c) && iscntrl(c)) {
+ if (c == '\n') {
+ *q++ = ' ';
+ } else if (c == '\t') {
+ *q++ = '\t';
+ } else {
+ *q++ = '^';
+ *q++ = c ^ 0100;
+ }
+ } else {
+ *q++ = c;
+ }
+ }
+ *q = '\0';
+
+ logmsg(pri, line, hname, flags);
+}
+
+/*
+ * Read /dev/klog while data are available, split into lines.
+ */
+static void
+readklog(void)
+{
+ char *p, *q, line[MAXLINE + 1];
+ int len, i;
+
+ len = 0;
+ for (;;) {
+ i = read(fklog, line + len, MAXLINE - 1 - len);
+ if (i > 0) {
+ line[i + len] = '\0';
+ } else {
+ if (i < 0 && errno != EINTR && errno != EAGAIN) {
+ logerror("klog");
+ fklog = -1;
+ }
+ break;
+ }
+
+ for (p = line; (q = strchr(p, '\n')) != NULL; p = q + 1) {
+ *q = '\0';
+ printsys(p);
+ }
+ len = strlen(p);
+ if (len >= MAXLINE - 1) {
+ printsys(p);
+ len = 0;
+ }
+ if (len > 0)
+ memmove(line, p, len + 1);
+ }
+ if (len > 0)
+ printsys(line);
+}
+
+/*
+ * Take a raw input line from /dev/klog, format similar to syslog().
+ */
+static void
+printsys(char *msg)
+{
+ char *p, *q;
+ long n;
+ int flags, isprintf, pri;
+
+ flags = ISKERNEL | SYNC_FILE | ADDDATE; /* fsync after write */
+ p = msg;
+ pri = DEFSPRI;
+ isprintf = 1;
+ if (*p == '<') {
+ errno = 0;
+ n = strtol(p + 1, &q, 10);
+ if (*q == '>' && n >= 0 && n < INT_MAX && errno == 0) {
+ p = q + 1;
+ pri = n;
+ isprintf = 0;
+ }
+ }
+ /*
+ * Kernel printf's and LOG_CONSOLE messages have been displayed
+ * on the console already.
+ */
+ if (isprintf || (pri & LOG_FACMASK) == LOG_CONSOLE)
+ flags |= IGN_CONS;
+ if (pri &~ (LOG_FACMASK|LOG_PRIMASK))
+ pri = DEFSPRI;
+ logmsg(pri, p, LocalHostName, flags);
+}
+
+static time_t now;
+
+/*
+ * Match a program or host name against a specification.
+ * Return a non-0 value if the message must be ignored
+ * based on the specification.
+ */
+static int
+skip_message(const char *name, const char *spec, int checkcase)
+{
+ const char *s;
+ char prev, next;
+ int exclude = 0;
+ /* Behaviour on explicit match */
+
+ if (spec == NULL)
+ return 0;
+ switch (*spec) {
+ case '-':
+ exclude = 1;
+ /*FALLTHROUGH*/
+ case '+':
+ spec++;
+ break;
+ default:
+ break;
+ }
+ if (checkcase)
+ s = strstr (spec, name);
+ else
+ s = strcasestr (spec, name);
+
+ if (s != NULL) {
+ prev = (s == spec ? ',' : *(s - 1));
+ next = *(s + strlen (name));
+
+ if (prev == ',' && (next == '\0' || next == ','))
+ /* Explicit match: skip iff the spec is an
+ exclusive one. */
+ return exclude;
+ }
+
+ /* No explicit match for this name: skip the message iff
+ the spec is an inclusive one. */
+ return !exclude;
+}
+
+/*
+ * Log a message to the appropriate log files, users, etc. based on
+ * the priority.
+ */
+static void
+logmsg(int pri, const char *msg, const char *from, int flags)
+{
+ struct filed *f;
+ int i, fac, msglen, omask, prilev;
+ const char *timestamp;
+ char prog[NAME_MAX+1];
+ char buf[MAXLINE+1];
+
+ dprintf("logmsg: pri %o, flags %x, from %s, msg %s\n",
+ pri, flags, from, msg);
+
+ omask = sigblock(sigmask(SIGHUP)|sigmask(SIGALRM));
+
+ /*
+ * Check to see if msg looks non-standard.
+ */
+ msglen = strlen(msg);
+ if (msglen < 16 || msg[3] != ' ' || msg[6] != ' ' ||
+ msg[9] != ':' || msg[12] != ':' || msg[15] != ' ')
+ flags |= ADDDATE;
+
+ (void)time(&now);
+ if (flags & ADDDATE) {
+ timestamp = ctime(&now) + 4;
+ } else {
+ timestamp = msg;
+ msg += 16;
+ msglen -= 16;
+ }
+
+ /* skip leading blanks */
+ while (isspace(*msg)) {
+ msg++;
+ msglen--;
+ }
+
+ /* extract facility and priority level */
+ if (flags & MARK)
+ fac = LOG_NFACILITIES;
+ else
+ fac = LOG_FAC(pri);
+
+ /* Check maximum facility number. */
+ if (fac > LOG_NFACILITIES) {
+ (void)sigsetmask(omask);
+ return;
+ }
+
+ prilev = LOG_PRI(pri);
+
+ /* extract program name */
+ for (i = 0; i < NAME_MAX; i++) {
+ if (!isprint(msg[i]) || msg[i] == ':' || msg[i] == '[' ||
+ msg[i] == '/' || isspace(msg[i]))
+ break;
+ prog[i] = msg[i];
+ }
+ prog[i] = 0;
+
+ /* add kernel prefix for kernel messages */
+ if (flags & ISKERNEL) {
+ snprintf(buf, sizeof(buf), "%s: %s",
+ use_bootfile ? bootfile : "kernel", msg);
+ msg = buf;
+ msglen = strlen(buf);
+ }
+
+ /* log the message to the particular outputs */
+ if (!Initialized) {
+ f = &consfile;
+ /*
+ * Open in non-blocking mode to avoid hangs during open
+ * and close(waiting for the port to drain).
+ */
+ f->f_file = open(ctty, O_WRONLY | O_NONBLOCK, 0);
+
+ if (f->f_file >= 0) {
+ (void)strlcpy(f->f_lasttime, timestamp,
+ sizeof(f->f_lasttime));
+ fprintlog(f, flags, msg);
+ close(f->f_file);
+ f->f_file = -1;
+ }
+ (void)sigsetmask(omask);
+ return;
+ }
+ for (f = Files; f; f = f->f_next) {
+ /* skip messages that are incorrect priority */
+ if (!(((f->f_pcmp[fac] & PRI_EQ) && (f->f_pmask[fac] == prilev))
+ ||((f->f_pcmp[fac] & PRI_LT) && (f->f_pmask[fac] < prilev))
+ ||((f->f_pcmp[fac] & PRI_GT) && (f->f_pmask[fac] > prilev))
+ )
+ || f->f_pmask[fac] == INTERNAL_NOPRI)
+ continue;
+
+ /* skip messages with the incorrect hostname */
+ if (skip_message(from, f->f_host, 0))
+ continue;
+
+ /* skip messages with the incorrect program name */
+ if (skip_message(prog, f->f_program, 1))
+ continue;
+
+ /* skip message to console if it has already been printed */
+ if (f->f_type == F_CONSOLE && (flags & IGN_CONS))
+ continue;
+
+ /* don't output marks to recently written files */
+ if ((flags & MARK) && (now - f->f_time) < MarkInterval / 2)
+ continue;
+
+ /*
+ * suppress duplicate lines to this file
+ */
+ if (no_compress - (f->f_type != F_PIPE) < 1 &&
+ (flags & MARK) == 0 && msglen == f->f_prevlen &&
+ !strcmp(msg, f->f_prevline) &&
+ !strcasecmp(from, f->f_prevhost)) {
+ (void)strlcpy(f->f_lasttime, timestamp,
+ sizeof(f->f_lasttime));
+ f->f_prevcount++;
+ dprintf("msg repeated %d times, %ld sec of %d\n",
+ f->f_prevcount, (long)(now - f->f_time),
+ repeatinterval[f->f_repeatcount]);
+ /*
+ * If domark would have logged this by now,
+ * flush it now (so we don't hold isolated messages),
+ * but back off so we'll flush less often
+ * in the future.
+ */
+ if (now > REPEATTIME(f)) {
+ fprintlog(f, flags, (char *)NULL);
+ BACKOFF(f);
+ }
+ } else {
+ /* new line, save it */
+ if (f->f_prevcount)
+ fprintlog(f, 0, (char *)NULL);
+ f->f_repeatcount = 0;
+ f->f_prevpri = pri;
+ (void)strlcpy(f->f_lasttime, timestamp,
+ sizeof(f->f_lasttime));
+ (void)strlcpy(f->f_prevhost, from,
+ sizeof(f->f_prevhost));
+ if (msglen < MAXSVLINE) {
+ f->f_prevlen = msglen;
+ (void)strlcpy(f->f_prevline, msg, sizeof(f->f_prevline));
+ fprintlog(f, flags, (char *)NULL);
+ } else {
+ f->f_prevline[0] = 0;
+ f->f_prevlen = 0;
+ fprintlog(f, flags, msg);
+ }
+ }
+ }
+ (void)sigsetmask(omask);
+}
+
+static void
+dofsync(void)
+{
+ struct filed *f;
+
+ for (f = Files; f; f = f->f_next) {
+ if ((f->f_type == F_FILE) &&
+ (f->f_flags & FFLAG_NEEDSYNC)) {
+ f->f_flags &= ~FFLAG_NEEDSYNC;
+ (void)fsync(f->f_file);
+ }
+ }
+}
+
+#define IOV_SIZE 7
+static void
+fprintlog(struct filed *f, int flags, const char *msg)
+{
+ struct iovec iov[IOV_SIZE];
+ struct iovec *v;
+ struct addrinfo *r;
+ int i, l, lsent = 0;
+ char line[MAXLINE + 1], repbuf[80], greetings[200], *wmsg = NULL;
+ char nul[] = "", space[] = " ", lf[] = "\n", crlf[] = "\r\n";
+ const char *msgret;
+
+ v = iov;
+ if (f->f_type == F_WALL) {
+ v->iov_base = greetings;
+ /* The time displayed is not synchornized with the other log
+ * destinations (like messages). Following fragment was using
+ * ctime(&now), which was updating the time every 30 sec.
+ * With f_lasttime, time is synchronized correctly.
+ */
+ v->iov_len = snprintf(greetings, sizeof greetings,
+ "\r\n\7Message from syslogd@%s at %.24s ...\r\n",
+ f->f_prevhost, f->f_lasttime);
+ if (v->iov_len >= sizeof greetings)
+ v->iov_len = sizeof greetings - 1;
+ v++;
+ v->iov_base = nul;
+ v->iov_len = 0;
+ v++;
+ } else {
+ v->iov_base = f->f_lasttime;
+ v->iov_len = strlen(f->f_lasttime);
+ v++;
+ v->iov_base = space;
+ v->iov_len = 1;
+ v++;
+ }
+
+ if (LogFacPri) {
+ static char fp_buf[30]; /* Hollow laugh */
+ int fac = f->f_prevpri & LOG_FACMASK;
+ int pri = LOG_PRI(f->f_prevpri);
+ const char *f_s = NULL;
+ char f_n[5]; /* Hollow laugh */
+ const char *p_s = NULL;
+ char p_n[5]; /* Hollow laugh */
+
+ if (LogFacPri > 1) {
+ const CODE *c;
+
+ for (c = facilitynames; c->c_name; c++) {
+ if (c->c_val == fac) {
+ f_s = c->c_name;
+ break;
+ }
+ }
+ for (c = prioritynames; c->c_name; c++) {
+ if (c->c_val == pri) {
+ p_s = c->c_name;
+ break;
+ }
+ }
+ }
+ if (!f_s) {
+ snprintf(f_n, sizeof f_n, "%d", LOG_FAC(fac));
+ f_s = f_n;
+ }
+ if (!p_s) {
+ snprintf(p_n, sizeof p_n, "%d", pri);
+ p_s = p_n;
+ }
+ snprintf(fp_buf, sizeof fp_buf, "<%s.%s> ", f_s, p_s);
+ v->iov_base = fp_buf;
+ v->iov_len = strlen(fp_buf);
+ } else {
+ v->iov_base = nul;
+ v->iov_len = 0;
+ }
+ v++;
+
+ v->iov_base = f->f_prevhost;
+ v->iov_len = strlen(v->iov_base);
+ v++;
+ v->iov_base = space;
+ v->iov_len = 1;
+ v++;
+
+ if (msg) {
+ wmsg = strdup(msg); /* XXX iov_base needs a `const' sibling. */
+ if (wmsg == NULL) {
+ logerror("strdup");
+ exit(1);
+ }
+ v->iov_base = wmsg;
+ v->iov_len = strlen(msg);
+ } else if (f->f_prevcount > 1) {
+ v->iov_base = repbuf;
+ v->iov_len = snprintf(repbuf, sizeof repbuf,
+ "last message repeated %d times", f->f_prevcount);
+ } else {
+ v->iov_base = f->f_prevline;
+ v->iov_len = f->f_prevlen;
+ }
+ v++;
+
+ dprintf("Logging to %s", TypeNames[f->f_type]);
+ f->f_time = now;
+
+ switch (f->f_type) {
+ int port;
+ case F_UNUSED:
+ dprintf("\n");
+ break;
+
+ case F_FORW:
+ port = (int)ntohs(((struct sockaddr_in *)
+ (f->f_un.f_forw.f_addr->ai_addr))->sin_port);
+ if (port != 514) {
+ dprintf(" %s:%d\n", f->f_un.f_forw.f_hname, port);
+ } else {
+ dprintf(" %s\n", f->f_un.f_forw.f_hname);
+ }
+ /* check for local vs remote messages */
+ if (strcasecmp(f->f_prevhost, LocalHostName))
+ l = snprintf(line, sizeof line - 1,
+ "<%d>%.15s Forwarded from %s: %s",
+ f->f_prevpri, (char *)iov[0].iov_base,
+ f->f_prevhost, (char *)iov[5].iov_base);
+ else
+ l = snprintf(line, sizeof line - 1, "<%d>%.15s %s",
+ f->f_prevpri, (char *)iov[0].iov_base,
+ (char *)iov[5].iov_base);
+ if (l < 0)
+ l = 0;
+ else if (l > MAXLINE)
+ l = MAXLINE;
+
+ if (finet) {
+ for (r = f->f_un.f_forw.f_addr; r; r = r->ai_next) {
+ for (i = 0; i < *finet; i++) {
+#if 0
+ /*
+ * should we check AF first, or just
+ * trial and error? FWD
+ */
+ if (r->ai_family ==
+ address_family_of(finet[i+1]))
+#endif
+ lsent = sendto(finet[i+1], line, l, 0,
+ r->ai_addr, r->ai_addrlen);
+ if (lsent == l)
+ break;
+ }
+ if (lsent == l && !send_to_all)
+ break;
+ }
+ dprintf("lsent/l: %d/%d\n", lsent, l);
+ if (lsent != l) {
+ int e = errno;
+ logerror("sendto");
+ errno = e;
+ switch (errno) {
+ case ENOBUFS:
+ case ENETDOWN:
+ case ENETUNREACH:
+ case EHOSTUNREACH:
+ case EHOSTDOWN:
+ case EADDRNOTAVAIL:
+ break;
+ /* case EBADF: */
+ /* case EACCES: */
+ /* case ENOTSOCK: */
+ /* case EFAULT: */
+ /* case EMSGSIZE: */
+ /* case EAGAIN: */
+ /* case ENOBUFS: */
+ /* case ECONNREFUSED: */
+ default:
+ dprintf("removing entry: errno=%d\n", e);
+ f->f_type = F_UNUSED;
+ break;
+ }
+ }
+ }
+ break;
+
+ case F_FILE:
+ dprintf(" %s\n", f->f_un.f_fname);
+ v->iov_base = lf;
+ v->iov_len = 1;
+ if (writev(f->f_file, iov, IOV_SIZE) < 0) {
+ /*
+ * If writev(2) fails for potentially transient errors
+ * like the filesystem being full, ignore it.
+ * Otherwise remove this logfile from the list.
+ */
+ if (errno != ENOSPC) {
+ int e = errno;
+ close_filed(f);
+ errno = e;
+ logerror(f->f_un.f_fname);
+ }
+ } else if ((flags & SYNC_FILE) && (f->f_flags & FFLAG_SYNC)) {
+ f->f_flags |= FFLAG_NEEDSYNC;
+ needdofsync = 1;
+ }
+ break;
+
+ case F_PIPE:
+ dprintf(" %s\n", f->f_un.f_pipe.f_pname);
+ v->iov_base = lf;
+ v->iov_len = 1;
+ if (f->f_un.f_pipe.f_pid == 0) {
+ if ((f->f_file = p_open(f->f_un.f_pipe.f_pname,
+ &f->f_un.f_pipe.f_pid)) < 0) {
+ f->f_type = F_UNUSED;
+ logerror(f->f_un.f_pipe.f_pname);
+ break;
+ }
+ }
+ if (writev(f->f_file, iov, IOV_SIZE) < 0) {
+ int e = errno;
+ close_filed(f);
+ if (f->f_un.f_pipe.f_pid > 0)
+ deadq_enter(f->f_un.f_pipe.f_pid,
+ f->f_un.f_pipe.f_pname);
+ f->f_un.f_pipe.f_pid = 0;
+ errno = e;
+ logerror(f->f_un.f_pipe.f_pname);
+ }
+ break;
+
+ case F_CONSOLE:
+ if (flags & IGN_CONS) {
+ dprintf(" (ignored)\n");
+ break;
+ }
+ /* FALLTHROUGH */
+
+ case F_TTY:
+ dprintf(" %s%s\n", _PATH_DEV, f->f_un.f_fname);
+ v->iov_base = crlf;
+ v->iov_len = 2;
+
+ errno = 0; /* ttymsg() only sometimes returns an errno */
+ if ((msgret = ttymsg(iov, IOV_SIZE, f->f_un.f_fname, 10))) {
+ f->f_type = F_UNUSED;
+ logerror(msgret);
+ }
+ break;
+
+ case F_USERS:
+ case F_WALL:
+ dprintf("\n");
+ v->iov_base = crlf;
+ v->iov_len = 2;
+ wallmsg(f, iov, IOV_SIZE);
+ break;
+ }
+ f->f_prevcount = 0;
+ free(wmsg);
+}
+
+/*
+ * WALLMSG -- Write a message to the world at large
+ *
+ * Write the specified message to either the entire
+ * world, or a list of approved users.
+ */
+static void
+wallmsg(struct filed *f, struct iovec *iov, const int iovlen)
+{
+ static int reenter; /* avoid calling ourselves */
+ struct utmpx *ut;
+ int i;
+ const char *p;
+
+ if (reenter++)
+ return;
+ setutxent();
+ /* NOSTRICT */
+ while ((ut = getutxent()) != NULL) {
+ if (ut->ut_type != USER_PROCESS)
+ continue;
+ if (f->f_type == F_WALL) {
+ if ((p = ttymsg(iov, iovlen, ut->ut_line,
+ TTYMSGTIME)) != NULL) {
+ errno = 0; /* already in msg */
+ logerror(p);
+ }
+ continue;
+ }
+ /* should we send the message to this user? */
+ for (i = 0; i < MAXUNAMES; i++) {
+ if (!f->f_un.f_uname[i][0])
+ break;
+ if (!strcmp(f->f_un.f_uname[i], ut->ut_user)) {
+ if ((p = ttymsg_check(iov, iovlen, ut->ut_line,
+ TTYMSGTIME)) != NULL) {
+ errno = 0; /* already in msg */
+ logerror(p);
+ }
+ break;
+ }
+ }
+ }
+ endutxent();
+ reenter = 0;
+}
+
+/*
+ * Wrapper routine for ttymsg() that checks the terminal for messages enabled.
+ */
+static const char *
+ttymsg_check(struct iovec *iov, int iovcnt, char *line, int tmout)
+{
+ static char device[1024];
+ static char errbuf[1024];
+ struct stat sb;
+
+ (void) snprintf(device, sizeof(device), "%s%s", _PATH_DEV, line);
+
+ if (stat(device, &sb) < 0) {
+ (void) snprintf(errbuf, sizeof(errbuf),
+ "%s: %s", device, strerror(errno));
+ return (errbuf);
+ }
+ if ((sb.st_mode & S_IWGRP) == 0)
+ /* Messages disabled. */
+ return (NULL);
+ return ttymsg(iov, iovcnt, line, tmout);
+}
+
+static void
+reapchild(int signo __unused)
+{
+ int status;
+ pid_t pid;
+ struct filed *f;
+
+ while ((pid = wait3(&status, WNOHANG, (struct rusage *)NULL)) > 0) {
+ if (!Initialized)
+ /* Don't tell while we are initting. */
+ continue;
+
+ /* First, look if it's a process from the dead queue. */
+ if (deadq_remove(pid))
+ goto oncemore;
+
+ /* Now, look in list of active processes. */
+ for (f = Files; f; f = f->f_next)
+ if (f->f_type == F_PIPE &&
+ f->f_un.f_pipe.f_pid == pid) {
+ close_filed(f);
+ f->f_un.f_pipe.f_pid = 0;
+ log_deadchild(pid, status,
+ f->f_un.f_pipe.f_pname);
+ break;
+ }
+ oncemore:
+ continue;
+ }
+}
+
+/*
+ * Return a printable representation of a host address.
+ */
+static const char *
+cvthname(struct sockaddr *f)
+{
+ int error, hl;
+ sigset_t omask, nmask;
+ static char hname[NI_MAXHOST], ip[NI_MAXHOST];
+
+ error = getnameinfo((struct sockaddr *)f,
+ ((struct sockaddr *)f)->sa_len,
+ ip, sizeof ip, NULL, 0, NI_NUMERICHOST);
+ dprintf("cvthname(%s)\n", ip);
+
+ if (error) {
+ dprintf("Malformed from address %s\n", gai_strerror(error));
+ return ("???");
+ }
+ if (!resolve)
+ return (ip);
+
+ sigemptyset(&nmask);
+ sigaddset(&nmask, SIGHUP);
+ sigprocmask(SIG_BLOCK, &nmask, &omask);
+ error = getnameinfo((struct sockaddr *)f,
+ ((struct sockaddr *)f)->sa_len,
+ hname, sizeof hname, NULL, 0, NI_NAMEREQD);
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+ if (error) {
+ dprintf("Host name for your address (%s) unknown\n", ip);
+ return (ip);
+ }
+ hl = strlen(hname);
+ if (hl > 0 && hname[hl-1] == '.')
+ hname[--hl] = '\0';
+ trimdomain(hname, hl);
+ return (hname);
+}
+
+static void
+dodie(int signo)
+{
+
+ WantDie = signo;
+}
+
+static void
+domark(int signo __unused)
+{
+
+ MarkSet = 1;
+}
+
+/*
+ * Print syslogd errors some place.
+ */
+static void
+logerror(const char *type)
+{
+ char buf[512];
+ static int recursed = 0;
+
+ /* If there's an error while trying to log an error, give up. */
+ if (recursed)
+ return;
+ recursed++;
+ if (errno)
+ (void)snprintf(buf,
+ sizeof buf, "syslogd: %s: %s", type, strerror(errno));
+ else
+ (void)snprintf(buf, sizeof buf, "syslogd: %s", type);
+ errno = 0;
+ dprintf("%s\n", buf);
+ logmsg(LOG_SYSLOG|LOG_ERR, buf, LocalHostName, ADDDATE);
+ recursed--;
+}
+
+static void
+die(int signo)
+{
+ struct filed *f;
+ struct funix *fx;
+ int was_initialized;
+ char buf[100];
+
+ was_initialized = Initialized;
+ Initialized = 0; /* Don't log SIGCHLDs. */
+ for (f = Files; f != NULL; f = f->f_next) {
+ /* flush any pending output */
+ if (f->f_prevcount)
+ fprintlog(f, 0, (char *)NULL);
+ if (f->f_type == F_PIPE && f->f_un.f_pipe.f_pid > 0) {
+ close_filed(f);
+ f->f_un.f_pipe.f_pid = 0;
+ }
+ }
+ Initialized = was_initialized;
+ if (signo) {
+ dprintf("syslogd: exiting on signal %d\n", signo);
+ (void)snprintf(buf, sizeof(buf), "exiting on signal %d", signo);
+ errno = 0;
+ logerror(buf);
+ }
+ STAILQ_FOREACH(fx, &funixes, next)
+ (void)unlink(fx->name);
+ pidfile_remove(pfh);
+
+ exit(1);
+}
+
+/*
+ * INIT -- Initialize syslogd from configuration table
+ */
+static void
+init(int signo)
+{
+ int i;
+ FILE *cf;
+ struct filed *f, *next, **nextp;
+ char *p;
+ char cline[LINE_MAX];
+ char prog[LINE_MAX];
+ char host[MAXHOSTNAMELEN];
+ char oldLocalHostName[MAXHOSTNAMELEN];
+ char hostMsg[2*MAXHOSTNAMELEN+40];
+ char bootfileMsg[LINE_MAX];
+
+ dprintf("init\n");
+
+ /*
+ * Load hostname (may have changed).
+ */
+ if (signo != 0)
+ (void)strlcpy(oldLocalHostName, LocalHostName,
+ sizeof(oldLocalHostName));
+ if (gethostname(LocalHostName, sizeof(LocalHostName)))
+ err(EX_OSERR, "gethostname() failed");
+ if ((p = strchr(LocalHostName, '.')) != NULL) {
+ *p++ = '\0';
+ LocalDomain = p;
+ } else {
+ LocalDomain = "";
+ }
+
+ /*
+ * Load / reload timezone data (in case it changed).
+ *
+ * Just calling tzset() again does not work, the timezone code
+ * caches the result. However, by setting the TZ variable, one
+ * can defeat the caching and have the timezone code really
+ * reload the timezone data. Respect any initial setting of
+ * TZ, in case the system is configured specially.
+ */
+ dprintf("loading timezone data via tzset()\n");
+ if (getenv("TZ")) {
+ tzset();
+ } else {
+ setenv("TZ", ":/etc/localtime", 1);
+ tzset();
+ unsetenv("TZ");
+ }
+
+ /*
+ * Close all open log files.
+ */
+ Initialized = 0;
+ for (f = Files; f != NULL; f = next) {
+ /* flush any pending output */
+ if (f->f_prevcount)
+ fprintlog(f, 0, (char *)NULL);
+
+ switch (f->f_type) {
+ case F_FILE:
+ case F_FORW:
+ case F_CONSOLE:
+ case F_TTY:
+ close_filed(f);
+ break;
+ case F_PIPE:
+ if (f->f_un.f_pipe.f_pid > 0) {
+ close_filed(f);
+ deadq_enter(f->f_un.f_pipe.f_pid,
+ f->f_un.f_pipe.f_pname);
+ }
+ f->f_un.f_pipe.f_pid = 0;
+ break;
+ }
+ next = f->f_next;
+ if (f->f_program) free(f->f_program);
+ if (f->f_host) free(f->f_host);
+ free((char *)f);
+ }
+ Files = NULL;
+ nextp = &Files;
+
+ /* open the configuration file */
+ if ((cf = fopen(ConfFile, "r")) == NULL) {
+ dprintf("cannot open %s\n", ConfFile);
+ *nextp = (struct filed *)calloc(1, sizeof(*f));
+ if (*nextp == NULL) {
+ logerror("calloc");
+ exit(1);
+ }
+ cfline("*.ERR\t/dev/console", *nextp, "*", "*");
+ (*nextp)->f_next = (struct filed *)calloc(1, sizeof(*f));
+ if ((*nextp)->f_next == NULL) {
+ logerror("calloc");
+ exit(1);
+ }
+ cfline("*.PANIC\t*", (*nextp)->f_next, "*", "*");
+ Initialized = 1;
+ return;
+ }
+
+ /*
+ * Foreach line in the conf table, open that file.
+ */
+ f = NULL;
+ (void)strlcpy(host, "*", sizeof(host));
+ (void)strlcpy(prog, "*", sizeof(prog));
+ while (fgets(cline, sizeof(cline), cf) != NULL) {
+ /*
+ * check for end-of-section, comments, strip off trailing
+ * spaces and newline character. #!prog is treated specially:
+ * following lines apply only to that program.
+ */
+ for (p = cline; isspace(*p); ++p)
+ continue;
+ if (*p == 0)
+ continue;
+ if (*p == '#') {
+ p++;
+ if (*p != '!' && *p != '+' && *p != '-')
+ continue;
+ }
+ if (*p == '+' || *p == '-') {
+ host[0] = *p++;
+ while (isspace(*p))
+ p++;
+ if ((!*p) || (*p == '*')) {
+ (void)strlcpy(host, "*", sizeof(host));
+ continue;
+ }
+ if (*p == '@')
+ p = LocalHostName;
+ for (i = 1; i < MAXHOSTNAMELEN - 1; i++) {
+ if (!isalnum(*p) && *p != '.' && *p != '-'
+ && *p != ',' && *p != ':' && *p != '%')
+ break;
+ host[i] = *p++;
+ }
+ host[i] = '\0';
+ continue;
+ }
+ if (*p == '!') {
+ p++;
+ while (isspace(*p)) p++;
+ if ((!*p) || (*p == '*')) {
+ (void)strlcpy(prog, "*", sizeof(prog));
+ continue;
+ }
+ for (i = 0; i < LINE_MAX - 1; i++) {
+ if (!isprint(p[i]) || isspace(p[i]))
+ break;
+ prog[i] = p[i];
+ }
+ prog[i] = 0;
+ continue;
+ }
+ for (p = cline + 1; *p != '\0'; p++) {
+ if (*p != '#')
+ continue;
+ if (*(p - 1) == '\\') {
+ strcpy(p - 1, p);
+ p--;
+ continue;
+ }
+ *p = '\0';
+ break;
+ }
+ for (i = strlen(cline) - 1; i >= 0 && isspace(cline[i]); i--)
+ cline[i] = '\0';
+ f = (struct filed *)calloc(1, sizeof(*f));
+ if (f == NULL) {
+ logerror("calloc");
+ exit(1);
+ }
+ *nextp = f;
+ nextp = &f->f_next;
+ cfline(cline, f, prog, host);
+ }
+
+ /* close the configuration file */
+ (void)fclose(cf);
+
+ Initialized = 1;
+
+ if (Debug) {
+ int port;
+ for (f = Files; f; f = f->f_next) {
+ for (i = 0; i <= LOG_NFACILITIES; i++)
+ if (f->f_pmask[i] == INTERNAL_NOPRI)
+ printf("X ");
+ else
+ printf("%d ", f->f_pmask[i]);
+ printf("%s: ", TypeNames[f->f_type]);
+ switch (f->f_type) {
+ case F_FILE:
+ printf("%s", f->f_un.f_fname);
+ break;
+
+ case F_CONSOLE:
+ case F_TTY:
+ printf("%s%s", _PATH_DEV, f->f_un.f_fname);
+ break;
+
+ case F_FORW:
+ port = (int)ntohs(((struct sockaddr_in *)
+ (f->f_un.f_forw.f_addr->ai_addr))->sin_port);
+ if (port != 514) {
+ printf("%s:%d",
+ f->f_un.f_forw.f_hname, port);
+ } else {
+ printf("%s", f->f_un.f_forw.f_hname);
+ }
+ break;
+
+ case F_PIPE:
+ printf("%s", f->f_un.f_pipe.f_pname);
+ break;
+
+ case F_USERS:
+ for (i = 0; i < MAXUNAMES && *f->f_un.f_uname[i]; i++)
+ printf("%s, ", f->f_un.f_uname[i]);
+ break;
+ }
+ if (f->f_program)
+ printf(" (%s)", f->f_program);
+ printf("\n");
+ }
+ }
+
+ logmsg(LOG_SYSLOG|LOG_INFO, "syslogd: restart", LocalHostName, ADDDATE);
+ dprintf("syslogd: restarted\n");
+ /*
+ * Log a change in hostname, but only on a restart.
+ */
+ if (signo != 0 && strcmp(oldLocalHostName, LocalHostName) != 0) {
+ (void)snprintf(hostMsg, sizeof(hostMsg),
+ "syslogd: hostname changed, \"%s\" to \"%s\"",
+ oldLocalHostName, LocalHostName);
+ logmsg(LOG_SYSLOG|LOG_INFO, hostMsg, LocalHostName, ADDDATE);
+ dprintf("%s\n", hostMsg);
+ }
+ /*
+ * Log the kernel boot file if we aren't going to use it as
+ * the prefix, and if this is *not* a restart.
+ */
+ if (signo == 0 && !use_bootfile) {
+ (void)snprintf(bootfileMsg, sizeof(bootfileMsg),
+ "syslogd: kernel boot file is %s", bootfile);
+ logmsg(LOG_KERN|LOG_INFO, bootfileMsg, LocalHostName, ADDDATE);
+ dprintf("%s\n", bootfileMsg);
+ }
+}
+
+/*
+ * Crack a configuration file line
+ */
+static void
+cfline(const char *line, struct filed *f, const char *prog, const char *host)
+{
+ struct addrinfo hints, *res;
+ int error, i, pri, syncfile;
+ const char *p, *q;
+ char *bp;
+ char buf[MAXLINE], ebuf[100];
+
+ dprintf("cfline(\"%s\", f, \"%s\", \"%s\")\n", line, prog, host);
+
+ errno = 0; /* keep strerror() stuff out of logerror messages */
+
+ /* clear out file entry */
+ memset(f, 0, sizeof(*f));
+ for (i = 0; i <= LOG_NFACILITIES; i++)
+ f->f_pmask[i] = INTERNAL_NOPRI;
+
+ /* save hostname if any */
+ if (host && *host == '*')
+ host = NULL;
+ if (host) {
+ int hl;
+
+ f->f_host = strdup(host);
+ if (f->f_host == NULL) {
+ logerror("strdup");
+ exit(1);
+ }
+ hl = strlen(f->f_host);
+ if (hl > 0 && f->f_host[hl-1] == '.')
+ f->f_host[--hl] = '\0';
+ trimdomain(f->f_host, hl);
+ }
+
+ /* save program name if any */
+ if (prog && *prog == '*')
+ prog = NULL;
+ if (prog) {
+ f->f_program = strdup(prog);
+ if (f->f_program == NULL) {
+ logerror("strdup");
+ exit(1);
+ }
+ }
+
+ /* scan through the list of selectors */
+ for (p = line; *p && *p != '\t' && *p != ' ';) {
+ int pri_done;
+ int pri_cmp;
+ int pri_invert;
+
+ /* find the end of this facility name list */
+ for (q = p; *q && *q != '\t' && *q != ' ' && *q++ != '.'; )
+ continue;
+
+ /* get the priority comparison */
+ pri_cmp = 0;
+ pri_done = 0;
+ pri_invert = 0;
+ if (*q == '!') {
+ pri_invert = 1;
+ q++;
+ }
+ while (!pri_done) {
+ switch (*q) {
+ case '<':
+ pri_cmp |= PRI_LT;
+ q++;
+ break;
+ case '=':
+ pri_cmp |= PRI_EQ;
+ q++;
+ break;
+ case '>':
+ pri_cmp |= PRI_GT;
+ q++;
+ break;
+ default:
+ pri_done++;
+ break;
+ }
+ }
+
+ /* collect priority name */
+ for (bp = buf; *q && !strchr("\t,; ", *q); )
+ *bp++ = *q++;
+ *bp = '\0';
+
+ /* skip cruft */
+ while (strchr(",;", *q))
+ q++;
+
+ /* decode priority name */
+ if (*buf == '*') {
+ pri = LOG_PRIMASK;
+ pri_cmp = PRI_LT | PRI_EQ | PRI_GT;
+ } else {
+ /* Ignore trailing spaces. */
+ for (i = strlen(buf) - 1; i >= 0 && buf[i] == ' '; i--)
+ buf[i] = '\0';
+
+ pri = decode(buf, prioritynames);
+ if (pri < 0) {
+ errno = 0;
+ (void)snprintf(ebuf, sizeof ebuf,
+ "unknown priority name \"%s\"", buf);
+ logerror(ebuf);
+ return;
+ }
+ }
+ if (!pri_cmp)
+ pri_cmp = (UniquePriority)
+ ? (PRI_EQ)
+ : (PRI_EQ | PRI_GT)
+ ;
+ if (pri_invert)
+ pri_cmp ^= PRI_LT | PRI_EQ | PRI_GT;
+
+ /* scan facilities */
+ while (*p && !strchr("\t.; ", *p)) {
+ for (bp = buf; *p && !strchr("\t,;. ", *p); )
+ *bp++ = *p++;
+ *bp = '\0';
+
+ if (*buf == '*') {
+ for (i = 0; i < LOG_NFACILITIES; i++) {
+ f->f_pmask[i] = pri;
+ f->f_pcmp[i] = pri_cmp;
+ }
+ } else {
+ i = decode(buf, facilitynames);
+ if (i < 0) {
+ errno = 0;
+ (void)snprintf(ebuf, sizeof ebuf,
+ "unknown facility name \"%s\"",
+ buf);
+ logerror(ebuf);
+ return;
+ }
+ f->f_pmask[i >> 3] = pri;
+ f->f_pcmp[i >> 3] = pri_cmp;
+ }
+ while (*p == ',' || *p == ' ')
+ p++;
+ }
+
+ p = q;
+ }
+
+ /* skip to action part */
+ while (*p == '\t' || *p == ' ')
+ p++;
+
+ if (*p == '-') {
+ syncfile = 0;
+ p++;
+ } else
+ syncfile = 1;
+
+ switch (*p) {
+ case '@':
+ {
+ char *tp;
+ char endkey = ':';
+ /*
+ * scan forward to see if there is a port defined.
+ * so we can't use strlcpy..
+ */
+ i = sizeof(f->f_un.f_forw.f_hname);
+ tp = f->f_un.f_forw.f_hname;
+ p++;
+
+ /*
+ * an ipv6 address should start with a '[' in that case
+ * we should scan for a ']'
+ */
+ if (*p == '[') {
+ p++;
+ endkey = ']';
+ }
+ while (*p && (*p != endkey) && (i-- > 0)) {
+ *tp++ = *p++;
+ }
+ if (endkey == ']' && *p == endkey)
+ p++;
+ *tp = '\0';
+ }
+ /* See if we copied a domain and have a port */
+ if (*p == ':')
+ p++;
+ else
+ p = NULL;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_DGRAM;
+ error = getaddrinfo(f->f_un.f_forw.f_hname,
+ p ? p : "syslog", &hints, &res);
+ if (error) {
+ logerror(gai_strerror(error));
+ break;
+ }
+ f->f_un.f_forw.f_addr = res;
+ f->f_type = F_FORW;
+ break;
+
+ case '/':
+ if ((f->f_file = open(p, logflags, 0600)) < 0) {
+ f->f_type = F_UNUSED;
+ logerror(p);
+ break;
+ }
+ if (syncfile)
+ f->f_flags |= FFLAG_SYNC;
+ if (isatty(f->f_file)) {
+ if (strcmp(p, ctty) == 0)
+ f->f_type = F_CONSOLE;
+ else
+ f->f_type = F_TTY;
+ (void)strlcpy(f->f_un.f_fname, p + sizeof(_PATH_DEV) - 1,
+ sizeof(f->f_un.f_fname));
+ } else {
+ (void)strlcpy(f->f_un.f_fname, p, sizeof(f->f_un.f_fname));
+ f->f_type = F_FILE;
+ }
+ break;
+
+ case '|':
+ f->f_un.f_pipe.f_pid = 0;
+ (void)strlcpy(f->f_un.f_pipe.f_pname, p + 1,
+ sizeof(f->f_un.f_pipe.f_pname));
+ f->f_type = F_PIPE;
+ break;
+
+ case '*':
+ f->f_type = F_WALL;
+ break;
+
+ default:
+ for (i = 0; i < MAXUNAMES && *p; i++) {
+ for (q = p; *q && *q != ','; )
+ q++;
+ (void)strncpy(f->f_un.f_uname[i], p, MAXLOGNAME - 1);
+ if ((q - p) >= MAXLOGNAME)
+ f->f_un.f_uname[i][MAXLOGNAME - 1] = '\0';
+ else
+ f->f_un.f_uname[i][q - p] = '\0';
+ while (*q == ',' || *q == ' ')
+ q++;
+ p = q;
+ }
+ f->f_type = F_USERS;
+ break;
+ }
+}
+
+
+/*
+ * Decode a symbolic name to a numeric value
+ */
+static int
+decode(const char *name, const CODE *codetab)
+{
+ const CODE *c;
+ char *p, buf[40];
+
+ if (isdigit(*name))
+ return (atoi(name));
+
+ for (p = buf; *name && p < &buf[sizeof(buf) - 1]; p++, name++) {
+ if (isupper(*name))
+ *p = tolower(*name);
+ else
+ *p = *name;
+ }
+ *p = '\0';
+ for (c = codetab; c->c_name; c++)
+ if (!strcmp(buf, c->c_name))
+ return (c->c_val);
+
+ return (-1);
+}
+
+static void
+markit(void)
+{
+ struct filed *f;
+ dq_t q, next;
+
+ now = time((time_t *)NULL);
+ MarkSeq += TIMERINTVL;
+ if (MarkSeq >= MarkInterval) {
+ logmsg(LOG_INFO, "-- MARK --",
+ LocalHostName, ADDDATE|MARK);
+ MarkSeq = 0;
+ }
+
+ for (f = Files; f; f = f->f_next) {
+ if (f->f_prevcount && now >= REPEATTIME(f)) {
+ dprintf("flush %s: repeated %d times, %d sec.\n",
+ TypeNames[f->f_type], f->f_prevcount,
+ repeatinterval[f->f_repeatcount]);
+ fprintlog(f, 0, (char *)NULL);
+ BACKOFF(f);
+ }
+ }
+
+ /* Walk the dead queue, and see if we should signal somebody. */
+ for (q = TAILQ_FIRST(&deadq_head); q != NULL; q = next) {
+ next = TAILQ_NEXT(q, dq_entries);
+
+ switch (q->dq_timeout) {
+ case 0:
+ /* Already signalled once, try harder now. */
+ if (kill(q->dq_pid, SIGKILL) != 0)
+ (void)deadq_remove(q->dq_pid);
+ break;
+
+ case 1:
+ /*
+ * Timed out on dead queue, send terminate
+ * signal. Note that we leave the removal
+ * from the dead queue to reapchild(), which
+ * will also log the event (unless the process
+ * didn't even really exist, in case we simply
+ * drop it from the dead queue).
+ */
+ if (kill(q->dq_pid, SIGTERM) != 0)
+ (void)deadq_remove(q->dq_pid);
+ /* FALLTHROUGH */
+
+ default:
+ q->dq_timeout--;
+ }
+ }
+ MarkSet = 0;
+ (void)alarm(TIMERINTVL);
+}
+
+/*
+ * fork off and become a daemon, but wait for the child to come online
+ * before returing to the parent, or we get disk thrashing at boot etc.
+ * Set a timer so we don't hang forever if it wedges.
+ */
+static int
+waitdaemon(int nochdir, int noclose, int maxwait)
+{
+ int fd;
+ int status;
+ pid_t pid, childpid;
+
+ switch (childpid = fork()) {
+ case -1:
+ return (-1);
+ case 0:
+ break;
+ default:
+ signal(SIGALRM, timedout);
+ alarm(maxwait);
+ while ((pid = wait3(&status, 0, NULL)) != -1) {
+ if (WIFEXITED(status))
+ errx(1, "child pid %d exited with return code %d",
+ pid, WEXITSTATUS(status));
+ if (WIFSIGNALED(status))
+ errx(1, "child pid %d exited on signal %d%s",
+ pid, WTERMSIG(status),
+ WCOREDUMP(status) ? " (core dumped)" :
+ "");
+ if (pid == childpid) /* it's gone... */
+ break;
+ }
+ exit(0);
+ }
+
+ if (setsid() == -1)
+ return (-1);
+
+ if (!nochdir)
+ (void)chdir("/");
+
+ if (!noclose && (fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) {
+ (void)dup2(fd, STDIN_FILENO);
+ (void)dup2(fd, STDOUT_FILENO);
+ (void)dup2(fd, STDERR_FILENO);
+ if (fd > 2)
+ (void)close (fd);
+ }
+ return (getppid());
+}
+
+/*
+ * We get a SIGALRM from the child when it's running and finished doing it's
+ * fsync()'s or O_SYNC writes for all the boot messages.
+ *
+ * We also get a signal from the kernel if the timer expires, so check to
+ * see what happened.
+ */
+static void
+timedout(int sig __unused)
+{
+ int left;
+ left = alarm(0);
+ signal(SIGALRM, SIG_DFL);
+ if (left == 0)
+ errx(1, "timed out waiting for child");
+ else
+ _exit(0);
+}
+
+/*
+ * Add `s' to the list of allowable peer addresses to accept messages
+ * from.
+ *
+ * `s' is a string in the form:
+ *
+ * [*]domainname[:{servicename|portnumber|*}]
+ *
+ * or
+ *
+ * netaddr/maskbits[:{servicename|portnumber|*}]
+ *
+ * Returns -1 on error, 0 if the argument was valid.
+ */
+static int
+allowaddr(char *s)
+{
+ char *cp1, *cp2;
+ struct allowedpeer ap;
+ struct servent *se;
+ int masklen = -1;
+ struct addrinfo hints, *res;
+ struct in_addr *addrp, *maskp;
+#ifdef INET6
+ int i;
+ u_int32_t *addr6p, *mask6p;
+#endif
+ char ip[NI_MAXHOST];
+
+#ifdef INET6
+ if (*s != '[' || (cp1 = strchr(s + 1, ']')) == NULL)
+#endif
+ cp1 = s;
+ if ((cp1 = strrchr(cp1, ':'))) {
+ /* service/port provided */
+ *cp1++ = '\0';
+ if (strlen(cp1) == 1 && *cp1 == '*')
+ /* any port allowed */
+ ap.port = 0;
+ else if ((se = getservbyname(cp1, "udp"))) {
+ ap.port = ntohs(se->s_port);
+ } else {
+ ap.port = strtol(cp1, &cp2, 0);
+ if (*cp2 != '\0')
+ return (-1); /* port not numeric */
+ }
+ } else {
+ if ((se = getservbyname("syslog", "udp")))
+ ap.port = ntohs(se->s_port);
+ else
+ /* sanity, should not happen */
+ ap.port = 514;
+ }
+
+ if ((cp1 = strchr(s, '/')) != NULL &&
+ strspn(cp1 + 1, "0123456789") == strlen(cp1 + 1)) {
+ *cp1 = '\0';
+ if ((masklen = atoi(cp1 + 1)) < 0)
+ return (-1);
+ }
+#ifdef INET6
+ if (*s == '[') {
+ cp2 = s + strlen(s) - 1;
+ if (*cp2 == ']') {
+ ++s;
+ *cp2 = '\0';
+ } else {
+ cp2 = NULL;
+ }
+ } else {
+ cp2 = NULL;
+ }
+#endif
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
+ if (getaddrinfo(s, NULL, &hints, &res) == 0) {
+ ap.isnumeric = 1;
+ memcpy(&ap.a_addr, res->ai_addr, res->ai_addrlen);
+ memset(&ap.a_mask, 0, sizeof(ap.a_mask));
+ ap.a_mask.ss_family = res->ai_family;
+ if (res->ai_family == AF_INET) {
+ ap.a_mask.ss_len = sizeof(struct sockaddr_in);
+ maskp = &((struct sockaddr_in *)&ap.a_mask)->sin_addr;
+ addrp = &((struct sockaddr_in *)&ap.a_addr)->sin_addr;
+ if (masklen < 0) {
+ /* use default netmask */
+ if (IN_CLASSA(ntohl(addrp->s_addr)))
+ maskp->s_addr = htonl(IN_CLASSA_NET);
+ else if (IN_CLASSB(ntohl(addrp->s_addr)))
+ maskp->s_addr = htonl(IN_CLASSB_NET);
+ else
+ maskp->s_addr = htonl(IN_CLASSC_NET);
+ } else if (masklen <= 32) {
+ /* convert masklen to netmask */
+ if (masklen == 0)
+ maskp->s_addr = 0;
+ else
+ maskp->s_addr = htonl(~((1 << (32 - masklen)) - 1));
+ } else {
+ freeaddrinfo(res);
+ return (-1);
+ }
+ /* Lose any host bits in the network number. */
+ addrp->s_addr &= maskp->s_addr;
+ }
+#ifdef INET6
+ else if (res->ai_family == AF_INET6 && masklen <= 128) {
+ ap.a_mask.ss_len = sizeof(struct sockaddr_in6);
+ if (masklen < 0)
+ masklen = 128;
+ mask6p = (u_int32_t *)&((struct sockaddr_in6 *)&ap.a_mask)->sin6_addr;
+ /* convert masklen to netmask */
+ while (masklen > 0) {
+ if (masklen < 32) {
+ *mask6p = htonl(~(0xffffffff >> masklen));
+ break;
+ }
+ *mask6p++ = 0xffffffff;
+ masklen -= 32;
+ }
+ /* Lose any host bits in the network number. */
+ mask6p = (u_int32_t *)&((struct sockaddr_in6 *)&ap.a_mask)->sin6_addr;
+ addr6p = (u_int32_t *)&((struct sockaddr_in6 *)&ap.a_addr)->sin6_addr;
+ for (i = 0; i < 4; i++)
+ addr6p[i] &= mask6p[i];
+ }
+#endif
+ else {
+ freeaddrinfo(res);
+ return (-1);
+ }
+ freeaddrinfo(res);
+ } else {
+ /* arg `s' is domain name */
+ ap.isnumeric = 0;
+ ap.a_name = s;
+ if (cp1)
+ *cp1 = '/';
+#ifdef INET6
+ if (cp2) {
+ *cp2 = ']';
+ --s;
+ }
+#endif
+ }
+
+ if (Debug) {
+ printf("allowaddr: rule %d: ", NumAllowed);
+ if (ap.isnumeric) {
+ printf("numeric, ");
+ getnameinfo((struct sockaddr *)&ap.a_addr,
+ ((struct sockaddr *)&ap.a_addr)->sa_len,
+ ip, sizeof ip, NULL, 0, NI_NUMERICHOST);
+ printf("addr = %s, ", ip);
+ getnameinfo((struct sockaddr *)&ap.a_mask,
+ ((struct sockaddr *)&ap.a_mask)->sa_len,
+ ip, sizeof ip, NULL, 0, NI_NUMERICHOST);
+ printf("mask = %s; ", ip);
+ } else {
+ printf("domainname = %s; ", ap.a_name);
+ }
+ printf("port = %d\n", ap.port);
+ }
+
+ if ((AllowedPeers = realloc(AllowedPeers,
+ ++NumAllowed * sizeof(struct allowedpeer)))
+ == NULL) {
+ logerror("realloc");
+ exit(1);
+ }
+ memcpy(&AllowedPeers[NumAllowed - 1], &ap, sizeof(struct allowedpeer));
+ return (0);
+}
+
+/*
+ * Validate that the remote peer has permission to log to us.
+ */
+static int
+validate(struct sockaddr *sa, const char *hname)
+{
+ int i;
+ size_t l1, l2;
+ char *cp, name[NI_MAXHOST], ip[NI_MAXHOST], port[NI_MAXSERV];
+ struct allowedpeer *ap;
+ struct sockaddr_in *sin4, *a4p = NULL, *m4p = NULL;
+#ifdef INET6
+ int j, reject;
+ struct sockaddr_in6 *sin6, *a6p = NULL, *m6p = NULL;
+#endif
+ struct addrinfo hints, *res;
+ u_short sport;
+
+ if (NumAllowed == 0)
+ /* traditional behaviour, allow everything */
+ return (1);
+
+ (void)strlcpy(name, hname, sizeof(name));
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
+ if (getaddrinfo(name, NULL, &hints, &res) == 0)
+ freeaddrinfo(res);
+ else if (strchr(name, '.') == NULL) {
+ strlcat(name, ".", sizeof name);
+ strlcat(name, LocalDomain, sizeof name);
+ }
+ if (getnameinfo(sa, sa->sa_len, ip, sizeof ip, port, sizeof port,
+ NI_NUMERICHOST | NI_NUMERICSERV) != 0)
+ return (0); /* for safety, should not occur */
+ dprintf("validate: dgram from IP %s, port %s, name %s;\n",
+ ip, port, name);
+ sport = atoi(port);
+
+ /* now, walk down the list */
+ for (i = 0, ap = AllowedPeers; i < NumAllowed; i++, ap++) {
+ if (ap->port != 0 && ap->port != sport) {
+ dprintf("rejected in rule %d due to port mismatch.\n", i);
+ continue;
+ }
+
+ if (ap->isnumeric) {
+ if (ap->a_addr.ss_family != sa->sa_family) {
+ dprintf("rejected in rule %d due to address family mismatch.\n", i);
+ continue;
+ }
+ if (ap->a_addr.ss_family == AF_INET) {
+ sin4 = (struct sockaddr_in *)sa;
+ a4p = (struct sockaddr_in *)&ap->a_addr;
+ m4p = (struct sockaddr_in *)&ap->a_mask;
+ if ((sin4->sin_addr.s_addr & m4p->sin_addr.s_addr)
+ != a4p->sin_addr.s_addr) {
+ dprintf("rejected in rule %d due to IP mismatch.\n", i);
+ continue;
+ }
+ }
+#ifdef INET6
+ else if (ap->a_addr.ss_family == AF_INET6) {
+ sin6 = (struct sockaddr_in6 *)sa;
+ a6p = (struct sockaddr_in6 *)&ap->a_addr;
+ m6p = (struct sockaddr_in6 *)&ap->a_mask;
+ if (a6p->sin6_scope_id != 0 &&
+ sin6->sin6_scope_id != a6p->sin6_scope_id) {
+ dprintf("rejected in rule %d due to scope mismatch.\n", i);
+ continue;
+ }
+ reject = 0;
+ for (j = 0; j < 16; j += 4) {
+ if ((*(u_int32_t *)&sin6->sin6_addr.s6_addr[j] & *(u_int32_t *)&m6p->sin6_addr.s6_addr[j])
+ != *(u_int32_t *)&a6p->sin6_addr.s6_addr[j]) {
+ ++reject;
+ break;
+ }
+ }
+ if (reject) {
+ dprintf("rejected in rule %d due to IP mismatch.\n", i);
+ continue;
+ }
+ }
+#endif
+ else
+ continue;
+ } else {
+ cp = ap->a_name;
+ l1 = strlen(name);
+ if (*cp == '*') {
+ /* allow wildmatch */
+ cp++;
+ l2 = strlen(cp);
+ if (l2 > l1 || memcmp(cp, &name[l1 - l2], l2) != 0) {
+ dprintf("rejected in rule %d due to name mismatch.\n", i);
+ continue;
+ }
+ } else {
+ /* exact match */
+ l2 = strlen(cp);
+ if (l2 != l1 || memcmp(cp, name, l1) != 0) {
+ dprintf("rejected in rule %d due to name mismatch.\n", i);
+ continue;
+ }
+ }
+ }
+ dprintf("accepted in rule %d.\n", i);
+ return (1); /* hooray! */
+ }
+ return (0);
+}
+
+/*
+ * Fairly similar to popen(3), but returns an open descriptor, as
+ * opposed to a FILE *.
+ */
+static int
+p_open(const char *prog, pid_t *rpid)
+{
+ int pfd[2], nulldesc;
+ pid_t pid;
+ sigset_t omask, mask;
+ char *argv[4]; /* sh -c cmd NULL */
+ char errmsg[200];
+
+ if (pipe(pfd) == -1)
+ return (-1);
+ if ((nulldesc = open(_PATH_DEVNULL, O_RDWR)) == -1)
+ /* we are royally screwed anyway */
+ return (-1);
+
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGALRM);
+ sigaddset(&mask, SIGHUP);
+ sigprocmask(SIG_BLOCK, &mask, &omask);
+ switch ((pid = fork())) {
+ case -1:
+ sigprocmask(SIG_SETMASK, &omask, 0);
+ close(nulldesc);
+ return (-1);
+
+ case 0:
+ argv[0] = strdup("sh");
+ argv[1] = strdup("-c");
+ argv[2] = strdup(prog);
+ argv[3] = NULL;
+ if (argv[0] == NULL || argv[1] == NULL || argv[2] == NULL) {
+ logerror("strdup");
+ exit(1);
+ }
+
+ alarm(0);
+ (void)setsid(); /* Avoid catching SIGHUPs. */
+
+ /*
+ * Throw away pending signals, and reset signal
+ * behaviour to standard values.
+ */
+ signal(SIGALRM, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+ sigprocmask(SIG_SETMASK, &omask, 0);
+ signal(SIGPIPE, SIG_DFL);
+ signal(SIGQUIT, SIG_DFL);
+ signal(SIGALRM, SIG_DFL);
+ signal(SIGHUP, SIG_DFL);
+
+ dup2(pfd[0], STDIN_FILENO);
+ dup2(nulldesc, STDOUT_FILENO);
+ dup2(nulldesc, STDERR_FILENO);
+ closefrom(3);
+
+ (void)execvp(_PATH_BSHELL, argv);
+ _exit(255);
+ }
+
+ sigprocmask(SIG_SETMASK, &omask, 0);
+ close(nulldesc);
+ close(pfd[0]);
+ /*
+ * Avoid blocking on a hung pipe. With O_NONBLOCK, we are
+ * supposed to get an EWOULDBLOCK on writev(2), which is
+ * caught by the logic above anyway, which will in turn close
+ * the pipe, and fork a new logging subprocess if necessary.
+ * The stale subprocess will be killed some time later unless
+ * it terminated itself due to closing its input pipe (so we
+ * get rid of really dead puppies).
+ */
+ if (fcntl(pfd[1], F_SETFL, O_NONBLOCK) == -1) {
+ /* This is bad. */
+ (void)snprintf(errmsg, sizeof errmsg,
+ "Warning: cannot change pipe to PID %d to "
+ "non-blocking behaviour.",
+ (int)pid);
+ logerror(errmsg);
+ }
+ *rpid = pid;
+ return (pfd[1]);
+}
+
+static void
+deadq_enter(pid_t pid, const char *name)
+{
+ dq_t p;
+ int status;
+
+ /*
+ * Be paranoid, if we can't signal the process, don't enter it
+ * into the dead queue (perhaps it's already dead). If possible,
+ * we try to fetch and log the child's status.
+ */
+ if (kill(pid, 0) != 0) {
+ if (waitpid(pid, &status, WNOHANG) > 0)
+ log_deadchild(pid, status, name);
+ return;
+ }
+
+ p = malloc(sizeof(struct deadq_entry));
+ if (p == NULL) {
+ logerror("malloc");
+ exit(1);
+ }
+
+ p->dq_pid = pid;
+ p->dq_timeout = DQ_TIMO_INIT;
+ TAILQ_INSERT_TAIL(&deadq_head, p, dq_entries);
+}
+
+static int
+deadq_remove(pid_t pid)
+{
+ dq_t q;
+
+ TAILQ_FOREACH(q, &deadq_head, dq_entries) {
+ if (q->dq_pid == pid) {
+ TAILQ_REMOVE(&deadq_head, q, dq_entries);
+ free(q);
+ return (1);
+ }
+ }
+
+ return (0);
+}
+
+static void
+log_deadchild(pid_t pid, int status, const char *name)
+{
+ int code;
+ char buf[256];
+ const char *reason;
+
+ errno = 0; /* Keep strerror() stuff out of logerror messages. */
+ if (WIFSIGNALED(status)) {
+ reason = "due to signal";
+ code = WTERMSIG(status);
+ } else {
+ reason = "with status";
+ code = WEXITSTATUS(status);
+ if (code == 0)
+ return;
+ }
+ (void)snprintf(buf, sizeof buf,
+ "Logging subprocess %d (%s) exited %s %d.",
+ pid, name, reason, code);
+ logerror(buf);
+}
+
+static int *
+socksetup(int af, char *bindhostname)
+{
+ struct addrinfo hints, *res, *r;
+ const char *bindservice;
+ char *cp;
+ int error, maxs, *s, *socks;
+
+ /*
+ * We have to handle this case for backwards compatibility:
+ * If there are two (or more) colons but no '[' and ']',
+ * assume this is an inet6 address without a service.
+ */
+ bindservice = "syslog";
+ if (bindhostname != NULL) {
+#ifdef INET6
+ if (*bindhostname == '[' &&
+ (cp = strchr(bindhostname + 1, ']')) != NULL) {
+ ++bindhostname;
+ *cp = '\0';
+ if (cp[1] == ':' && cp[2] != '\0')
+ bindservice = cp + 2;
+ } else {
+#endif
+ cp = strchr(bindhostname, ':');
+ if (cp != NULL && strchr(cp + 1, ':') == NULL) {
+ *cp = '\0';
+ if (cp[1] != '\0')
+ bindservice = cp + 1;
+ if (cp == bindhostname)
+ bindhostname = NULL;
+ }
+#ifdef INET6
+ }
+#endif
+ }
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = af;
+ hints.ai_socktype = SOCK_DGRAM;
+ error = getaddrinfo(bindhostname, bindservice, &hints, &res);
+ if (error) {
+ logerror(gai_strerror(error));
+ errno = 0;
+ die(0);
+ }
+
+ /* Count max number of sockets we may open */
+ for (maxs = 0, r = res; r; r = r->ai_next, maxs++);
+ socks = malloc((maxs+1) * sizeof(int));
+ if (socks == NULL) {
+ logerror("couldn't allocate memory for sockets");
+ die(0);
+ }
+
+ *socks = 0; /* num of sockets counter at start of array */
+ s = socks + 1;
+ for (r = res; r; r = r->ai_next) {
+ int on = 1;
+ *s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
+ if (*s < 0) {
+ logerror("socket");
+ continue;
+ }
+#ifdef INET6
+ if (r->ai_family == AF_INET6) {
+ if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY,
+ (char *)&on, sizeof (on)) < 0) {
+ logerror("setsockopt");
+ close(*s);
+ continue;
+ }
+ }
+#endif
+ if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR,
+ (char *)&on, sizeof (on)) < 0) {
+ logerror("setsockopt");
+ close(*s);
+ continue;
+ }
+ /*
+ * RFC 3164 recommends that client side message
+ * should come from the privileged syslogd port.
+ *
+ * If the system administrator choose not to obey
+ * this, we can skip the bind() step so that the
+ * system will choose a port for us.
+ */
+ if (!NoBind) {
+ if (bind(*s, r->ai_addr, r->ai_addrlen) < 0) {
+ logerror("bind");
+ close(*s);
+ continue;
+ }
+
+ if (!SecureMode)
+ increase_rcvbuf(*s);
+ }
+
+ (*socks)++;
+ dprintf("socksetup: new socket fd is %d\n", *s);
+ s++;
+ }
+
+ if (*socks == 0) {
+ free(socks);
+ if (Debug)
+ return (NULL);
+ else
+ die(0);
+ }
+ if (res)
+ freeaddrinfo(res);
+
+ return (socks);
+}
+
+static void
+increase_rcvbuf(int fd)
+{
+ socklen_t len, slen;
+
+ slen = sizeof(len);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &len, &slen) == 0) {
+ if (len < RCVBUF_MINSIZE) {
+ len = RCVBUF_MINSIZE;
+ setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len));
+ }
+ }
+}
diff --git a/usr.sbin/sysrc/Makefile b/usr.sbin/sysrc/Makefile
new file mode 100644
index 0000000..1ace38a
--- /dev/null
+++ b/usr.sbin/sysrc/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+SCRIPTS= sysrc
+
+MAN= sysrc.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/sysrc/Makefile.depend b/usr.sbin/sysrc/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/sysrc/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/sysrc/sysrc b/usr.sbin/sysrc/sysrc
new file mode 100644
index 0000000..e384dff
--- /dev/null
+++ b/usr.sbin/sysrc/sysrc
@@ -0,0 +1,929 @@
+#!/bin/sh
+#-
+# Copyright (c) 2010-2015 Devin Teske
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+############################################################ INCLUDES
+
+# Prevent `-d' from being interpreted as a debug flag by common.subr
+DEBUG_SELF_INITIALIZE=
+
+BSDCFG_SHARE="/usr/share/bsdconfig"
+[ "$_COMMON_SUBR" ] || . $BSDCFG_SHARE/common.subr || exit 1
+[ "$_SYSRC_SUBR" ] || f_include $BSDCFG_SHARE/sysrc.subr
+
+############################################################ GLOBALS
+
+#
+# Version information
+#
+SYSRC_VERSION="7.0 Sep-13,2015"
+
+#
+# Options
+#
+CHECK_ONLY=
+DEFAULT=
+DELETE=
+DESCRIBE=
+EXISTING_ONLY=
+IGNORE_UNKNOWNS=
+JAIL=
+LIST_SERVICE_CONFS=
+LIST_CONFS=
+QUIET=
+ROOTDIR=
+SERVICE=
+SHOW_ALL=
+SHOW_EQUALS=
+SHOW_FILE=
+SHOW_NAME=1
+SHOW_VALUE=1
+VERBOSE=
+
+############################################################ FUNCTIONS
+
+# die [$fmt [$opts ...]]
+#
+# Optionally print a message to stderr before exiting with failure status.
+#
+die()
+{
+ local fmt="$1"
+ [ $# -gt 0 ] && shift 1
+ [ "$fmt" ] && f_err "$fmt\n" "$@"
+
+ exit $FAILURE
+}
+
+# usage
+#
+# Prints a short syntax statement and exits.
+#
+usage()
+{
+ f_err "Usage: %s [OPTIONS] %s\n" "$pgm" \
+ "{name[[+|-]=value] ... | -a | -A | -l | -L [name ...]}"
+ f_err "Try \`%s --help' for more information.\n" "$pgm"
+ die
+}
+
+# help
+#
+# Prints a full syntax statement and exits.
+#
+help()
+{
+ local optfmt="\t%-11s%s\n"
+ local envfmt="\t%-17s%s\n"
+
+ f_err "Usage: %s [OPTIONS] name[[+|-]=value] ...\n" "$pgm"
+ f_err "Usage: %s [OPTIONS] -a | -A\n" "$pgm"
+ f_err "Usage: %s [OPTIONS] -l | -L [name ...]\n" "$pgm"
+
+ f_err "OPTIONS:\n"
+ f_err "$optfmt" "-a" \
+ "Dump a list of all non-default configuration variables."
+ f_err "$optfmt" "-A" \
+ "Dump a list of all configuration variables (incl. defaults)."
+ f_err "$optfmt" "-c" \
+ "Check. Return success if set or no changes, else error."
+ f_err "$optfmt" "-d" \
+ "Print a description of the given variable."
+ f_err "$optfmt" "-D" \
+ "Show default value(s) only (this is the same as setting"
+ f_err "$optfmt" "" \
+ "RC_CONFS to NULL or passing \`-f' with a NULL file-argument)."
+ f_err "$optfmt" "-e" \
+ "Print query results as \`var=value' (useful for producing"
+ f_err "$optfmt" "" \
+ "output to be fed back in). Ignored if \`-n' is specified."
+ f_err "$optfmt" "-E" \
+ "Existing files only with \`-[lL]' or when changing a setting."
+ f_err "$optfmt" "-f file" \
+ "Operate on the specified file(s) instead of rc_conf_files."
+ f_err "$optfmt" "" \
+ "Can be specified multiple times for additional files."
+ f_err "$optfmt" "-F" \
+ "Show only the last rc.conf(5) file each directive is in."
+ f_err "$optfmt" "-h" \
+ "Print a short usage statement to stderr and exit."
+ f_err "$optfmt" "--help" \
+ "Print this message to stderr and exit."
+ f_err "$optfmt" "-i" \
+ "Ignore unknown variables."
+ f_err "$optfmt" "-j jail" \
+ "The jid or name of the jail to operate within (overrides"
+ f_err "$optfmt" "" \
+ "\`-R dir'; requires jexec(8))."
+ f_err "$optfmt" "-l" \
+ "List configuration files used at startup on stdout and exit."
+ f_err "$optfmt" "-L" \
+ "List all configuration files including rc.conf.d entries."
+ f_err "$optfmt" "-n" \
+ "Show only variable values, not their names."
+ f_err "$optfmt" "-N" \
+ "Show only variable names, not their values."
+ f_err "$optfmt" "-q" \
+ "Quiet. Disable verbose and hide certain errors."
+ f_err "$optfmt" "-s name" \
+ "Process additional \`rc.conf.d' entries for service name."
+ f_err "$optfmt" "" \
+ "Ignored if \`-f file' is given."
+ f_err "$optfmt" "-R dir" \
+ "Operate within the root directory \`dir' rather than \`/'."
+ f_err "$optfmt" "-v" \
+ "Verbose. Print the pathname of the specific rc.conf(5)"
+ f_err "$optfmt" "" \
+ "file where the directive was found."
+ f_err "$optfmt" "--version" \
+ "Print version information to stdout and exit."
+ f_err "$optfmt" "-x" \
+ "Remove variable(s) from specified file(s)."
+ f_err "\n"
+
+ f_err "ENVIRONMENT:\n"
+ f_err "$envfmt" "RC_CONFS" \
+ "Override default rc_conf_files (even if set to NULL)."
+ f_err "$envfmt" "RC_DEFAULTS" \
+ "Location of \`/etc/defaults/rc.conf' file."
+
+ die
+}
+
+# jail_depend
+#
+# Dump dependencies such as language-file variables and include files to stdout
+# to be piped-into sh(1) running via jexec(8)/chroot(8). As a security measure,
+# this prevents existing language files and library files from being loaded in
+# the jail. This also relaxes the requirement to have these files in every jail
+# before sysrc can be used on said jail.
+#
+jail_depend()
+{
+ #
+ # Indicate that we are jailed
+ #
+ echo export _SYSRC_JAILED=1
+
+ #
+ # Print i18n language variables (their current values are sanitized
+ # and re-printed for interpretation so that the i18n language files
+ # do not need to exist within the jail).
+ #
+ local var val
+ for var in \
+ msg_cannot_create_permission_denied \
+ msg_permission_denied \
+ msg_previous_syntax_errors \
+ ; do
+ val=$( eval echo \"\$$var\" |
+ awk '{ gsub(/'\''/, "'\''\\'\'\''"); print }' )
+ echo $var="'$val'"
+ done
+
+ #
+ # Print include dependencies
+ #
+ echo DEBUG_SELF_INITIALIZE=
+ cat $BSDCFG_SHARE/common.subr
+ cat $BSDCFG_SHARE/sysrc.subr
+}
+
+# escape $string [$var_to_set]
+#
+# Escape $string contents so that the contents can be properly encapsulated in
+# single-quotes (making for safe evaluation).
+#
+# NB: See `bsdconfig includes -dF escape' for relevant information/discussion.
+# NB: Abridged version of `f_shell_escape()' from bsdconfig(8) `strings.subr'.
+#
+escape()
+{
+ local __start="$1" __var_to_set="$2" __string=
+ while [ "$__start" ]; do
+ case "$__start" in *\'*)
+ __string="$__string${__start%%\'*}'\\''"
+ __start="${__start#*\'}" continue
+ esac
+ break
+ done
+ __string="$__string$__start"
+ if [ "$__var_to_set" ]; then
+ setvar "$__var_to_set" "$__string"
+ else
+ echo "$__string"
+ fi
+}
+
+############################################################ MAIN SOURCE
+
+#
+# Perform sanity checks
+#
+[ $# -gt 0 ] || usage # NOTREACHED
+
+#
+# Check for `--help' and `--version' command-line option
+#
+for arg in "$@"; do
+ case "$arg" in
+ --) break ;;
+ --help) help ;; # NOTREACHED
+ --version) # see GLOBALS
+ echo "$SYSRC_VERSION"
+ exit $FAILURE ;;
+ esac
+done
+unset arg
+
+#
+# Process command-line flags
+#
+while getopts aAcdDeEf:Fhij:lLnNqR:s:vxX flag; do
+ case "$flag" in
+ a) SHOW_ALL=${SHOW_ALL:-1} ;;
+ A) SHOW_ALL=2 ;;
+ c) CHECK_ONLY=1 ;;
+ d) DESCRIBE=1 ;;
+ D) DEFAULT=1 RC_CONFS= ;;
+ e) SHOW_EQUALS=1 ;;
+ E) EXISTING_ONLY=1 ;;
+ f) DEFAULT= RC_CONFS="$RC_CONFS${RC_CONFS:+ }$OPTARG" ;;
+ F) SHOW_FILE=1 ;;
+ h) usage ;; # NOTREACHED
+ i) IGNORE_UNKNOWNS=1 ;;
+ j) [ "$OPTARG" ] ||
+ die "%s: Missing or null argument to \`-j' flag" "$pgm"
+ JAIL="$OPTARG" ;;
+ l) LIST_CONFS=1 ;;
+ L) LIST_SERVICE_CONFS=1 ;;
+ n) SHOW_NAME= ;;
+ N) SHOW_VALUE= ;;
+ q) QUIET=1 VERBOSE= ;;
+ R) [ "$OPTARG" ] ||
+ die "%s: Missing or null argument to \`-R' flag" "$pgm"
+ ROOTDIR="$OPTARG" ;;
+ s) [ "$OPTARG" ] ||
+ die "%s: Missing or null argument to \`-s' flag" "$pgm"
+ SERVICE="$OPTARG" ;;
+ v) VERBOSE=1 QUIET= ;;
+ x) DELETE=${DELETE:-1} ;;
+ X) DELETE=2 ;;
+ \?) usage ;; # NOTREACHED
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+#
+# Process `-L' flag
+#
+if [ "$LIST_SERVICE_CONFS" ]; then
+ list=
+
+ #
+ # List rc_conf_files if no service names given
+ #
+ files=
+ [ $# -eq 0 ] && files=$( f_sysrc_get rc_conf_files )
+ for file in $files; do
+ if [ "$EXISTING_ONLY" ]; then
+ [ -e "$file" -a ! -d "$file" ] || continue
+ fi
+ case "$list" in
+ "$file"|*" $file"|"$file "*|*" $file "*) continue ;;
+ esac
+ list="$list $file"
+ done
+ list="${list# }"
+ if [ $# -eq 0 ]; then
+ if [ "$VERBOSE" ]; then
+ echo rc_conf_files: $list
+ elif [ "$SHOW_EQUALS" ]; then
+ echo "rc_conf_files=\"$list\""
+ fi
+ fi
+
+ #
+ # List rc.conf.d entries
+ #
+ retval=$SUCCESS
+ for service in ${*:-$( service -l )}; do
+ slist=
+ f_sysrc_service_configs $service files || retval=$? continue
+ for file in $files; do
+ if [ "$EXISTING_ONLY" ]; then
+ [ -e "$file" -a ! -d "$file" ] || continue
+ fi
+ if [ ! "$VERBOSE" -a ! "$SHOW_EQUALS" ]; then
+ case "$list" in
+ "$file"|*" $file"|"$file "*|*" $file "*)
+ continue ;;
+ esac
+ fi
+ slist="$slist $file"
+ done
+ slist="${slist# }"
+ if [ $# -gt 0 ]; then
+ [ "$slist" ] || retval=$?
+ fi
+ if [ "$VERBOSE" ]; then
+ [ "$slist" ] && echo "$service: $slist"
+ continue
+ elif [ "$SHOW_EQUALS" ]; then
+ [ "$slist" ] && echo "$service=\"$slist\""
+ continue
+ fi
+ list="$list${slist:+ }$slist"
+ done
+ if [ ! "$VERBOSE" -a ! "$SHOW_EQUALS" ]; then
+ if [ $# -eq 0 -o ! "$QUIET" ]; then
+ list="${list# }"
+ [ "$list" ] && echo $list
+ fi
+ fi
+
+ exit $retval
+fi
+
+#
+# Process `-s name' argument
+#
+if [ "$SERVICE" -a ! "${RC_CONFS+set}" ]; then
+ if f_sysrc_service_configs "$SERVICE" RC_CONFS; then
+ rc_conf_files=$( f_sysrc_get rc_conf_files )
+ RC_CONFS="$rc_conf_files${RC_CONFS:+ }$RC_CONFS"
+ unset rc_conf_files
+ else
+ unset RC_CONFS
+ fi
+fi
+
+#
+# Process `-E' option flag
+#
+if [ "$EXISTING_ONLY" ]; then
+ #
+ # To get f_sysrc_*() to ignore missing rc_conf_files, we have to use
+ # RC_CONFS to override the unpreened value. If RC_CONFS already has a
+ # value (`-D', `-f file', `-s name', or inherited from parent), use it.
+ # Otherwise, include filtered contents of rc_conf_files.
+ #
+ RC_CONFS=$(
+ if [ "${RC_CONFS+set}" ]; then
+ set -- $RC_CONFS
+ else
+ set -- $( f_sysrc_get rc_conf_files )
+ fi
+ while [ $# -gt 0 ]; do
+ [ -f "$1" ] && echo -n " $1"
+ shift
+ done
+ )
+ RC_CONFS="${RC_CONFS# }"
+fi
+
+#
+# Process `-l' option flag
+#
+if [ "$LIST_CONFS" ]; then
+ [ $# -eq 0 ] || usage
+ if [ "$DEFAULT" ]; then
+ echo "$RC_DEFAULTS"
+ elif [ "${RC_CONFS+set}" ]; then
+ echo "$RC_CONFS"
+ else
+ f_sysrc_get rc_conf_files
+ fi
+ exit $SUCCESS
+fi
+
+#
+# [More] Sanity checks (e.g., "sysrc --")
+#
+[ $# -eq 0 -a ! "$SHOW_ALL" ] && usage # NOTREACHED
+
+#
+# Taint-check all rc.conf(5) files
+#
+errmsg="$pgm: Exiting due to previous syntax errors"
+if [ "${RC_CONFS+set}" ]; then
+ ( for i in $RC_CONFS; do
+ [ -e "$i" ] || continue
+ /bin/sh -n "$i" || exit $FAILURE
+ done
+ exit $SUCCESS
+ ) || die "$errmsg"
+else
+ /bin/sh -n "$RC_DEFAULTS" || die "$errmsg"
+ ( . "$RC_DEFAULTS"
+ for i in $rc_conf_files; do
+ [ -e "$i" ] || continue
+ /bin/sh -n "$i" || exit $FAILURE
+ done
+ exit $SUCCESS
+ ) || die "$errmsg"
+fi
+
+#
+# Process `-x' (and secret `-X') command-line options
+#
+errmsg="$pgm: \`-x' option incompatible with \`-a'/\`-A' options"
+errmsg="$errmsg (use \`-X' to override)"
+if [ "$DELETE" -a "$SHOW_ALL" ]; then
+ [ "$DELETE" = "2" ] || die "$errmsg"
+fi
+
+#
+# Pre-flight for `-c' command-line option
+#
+[ "$CHECK_ONLY" -a "$SHOW_ALL" ] &&
+ die "$pgm: \`-c' option incompatible with \`-a'/\`-A' options"
+
+#
+# Process `-e', `-n', and `-N' command-line options
+#
+SEP=': '
+[ "$SHOW_FILE" ] && SHOW_EQUALS=
+[ "$SHOW_NAME" ] || SHOW_EQUALS=
+[ "$VERBOSE" = "0" ] && VERBOSE=
+if [ ! "$SHOW_VALUE" ]; then
+ SHOW_NAME=1
+ SHOW_EQUALS=
+fi
+[ "$SHOW_EQUALS" ] && SEP='="'
+
+#
+# Process `-j jail' and `-R dir' command-line options
+#
+if [ "$JAIL" -o "$ROOTDIR" ]; then
+ #
+ # Reconstruct the arguments that we want to carry-over
+ #
+ args="
+ ${VERBOSE:+-v}
+ ${QUIET:+-q}
+ $( [ "$DELETE" = "1" ] && echo \ -x )
+ $( [ "$DELETE" = "2" ] && echo \ -X )
+ $( [ "$SHOW_ALL" = "1" ] && echo \ -a )
+ $( [ "$SHOW_ALL" = "2" ] && echo \ -A )
+ ${CHECK_ONLY:+-c}
+ ${DEFAULT:+-D}
+ ${EXISTING_ONLY:+-E}
+ ${LIST_CONFS:+-l}
+ ${LIST_SERVICE_CONFS:+-L}
+ ${DESCRIBE:+-d}
+ ${SHOW_EQUALS:+-e}
+ ${IGNORE_UNKNOWNS:+-i}
+ $( [ "$SHOW_NAME" ] || echo \ -n )
+ $( [ "$SHOW_VALUE" ] || echo \ -N )
+ $( [ "$SHOW_FILE" ] && echo \ -F )
+ "
+ if [ "$SERVICE" ]; then
+ escape "$SERVICE" _SERVICE
+ args="$args -s '$_SERVICE'"
+ unset _SERVICE
+ fi
+ if [ "${RC_CONFS+set}" ]; then
+ escape "$RC_CONFS" _RC_CONFS
+ args="$args -f '$_RC_CONFS'"
+ unset _RC_CONFS
+ fi
+ for arg in "$@"; do
+ escape "$arg" arg
+ args="$args '$arg'"
+ done
+
+ #
+ # If both are supplied, `-j jail' supercedes `-R dir'
+ #
+ if [ "$JAIL" ]; then
+ #
+ # Re-execute ourselves with sh(1) via jexec(8)
+ #
+ ( echo set -- $args
+ jail_depend
+ cat $0
+ ) | env - RC_DEFAULTS="$RC_DEFAULTS" \
+ /usr/sbin/jexec "$JAIL" /bin/sh
+ exit $?
+ elif [ "$ROOTDIR" ]; then
+ #
+ # Make sure that the root directory specified is not to any
+ # running jails.
+ #
+ # NOTE: To maintain backward compatibility with older jails on
+ # older systems, we will not perform this check if either the
+ # jls(1) or jexec(8) utilities are missing.
+ #
+ if f_have jexec && f_have jls; then
+ jid=$( jls jid path |
+ while read JID JROOT; do
+ [ "$JROOT" = "$ROOTDIR" ] || continue
+ echo $JID
+ done
+ )
+
+ #
+ # If multiple running jails match the specified root
+ # directory, exit with error.
+ #
+ if [ "$jid" -a "${jid%[$IFS]*}" != "$jid" ]; then
+ die "%s: %s: %s" "$pgm" "$ROOTDIR" \
+ "$( echo "Multiple jails claim this" \
+ "directory as their root." \
+ "(use \`-j jail' instead)" )"
+ fi
+
+ #
+ # If only a single running jail matches the specified
+ # root directory, implicitly use `-j jail'.
+ #
+ if [ "$jid" ]; then
+ #
+ # Re-execute outselves with sh(1) via jexec(8)
+ #
+ ( echo set -- $args
+ jail_depend
+ cat $0
+ ) | env - RC_DEFAULTS="$RC_DEFAULTS" \
+ /usr/sbin/jexec "$jid" /bin/sh
+ exit $?
+ fi
+
+ # Otherwise, fall through and allow chroot(8)
+ fi
+
+ #
+ # Re-execute ourselves with sh(1) via chroot(8)
+ #
+ ( echo set -- $args
+ jail_depend
+ cat $0
+ ) | env - RC_DEFAULTS="$RC_DEFAULTS" \
+ /usr/sbin/chroot "$ROOTDIR" /bin/sh
+ exit $?
+ fi
+fi
+
+#
+# Process `-a' or `-A' command-line options
+#
+if [ "$SHOW_ALL" ]; then
+ #
+ # Get a list of variables that are currently set in the rc.conf(5)
+ # files (included `/etc/defaults/rc.conf') by performing a call to
+ # source_rc_confs() in a clean environment.
+ #
+ ( # Operate in a sub-shell to protect the parent environment
+ #
+ # Set which variables we want to preserve in the environment.
+ # Append the pipe-character (|) to the list of internal field
+ # separation (IFS) characters, allowing us to use the below
+ # list both as an extended grep (-E) pattern and argument list
+ # (required to first get f_clean_env() to preserve these in the
+ # environment and then later to prune them from the list of
+ # variables produced by set(1)).
+ #
+ IFS="$IFS|"
+ EXCEPT="IFS|EXCEPT|PATH|RC_DEFAULTS|OPTIND|DESCRIBE|SEP"
+ EXCEPT="$EXCEPT|DELETE|SHOW_ALL|SHOW_EQUALS|SHOW_NAME|DEFAULT"
+ EXCEPT="$EXCEPT|SHOW_VALUE|SHOW_FILE|VERBOSE|RC_CONFS|SERVICE"
+ EXCEPT="$EXCEPT|pgm|SUCCESS|FAILURE|CHECK_ONLY|EXISTING_ONLY"
+ EXCEPT="$EXCEPT|LIST_CONFS|LIST_SERVICE_CONFS"
+ EXCEPT="$EXCEPT|f_sysrc_desc_awk|f_sysrc_delete_awk"
+
+ #
+ # Clean the environment (except for our required variables)
+ # and then source the required files.
+ #
+ f_clean_env --except $EXCEPT
+ if [ -f "$RC_DEFAULTS" -a -r "$RC_DEFAULTS" ]; then
+ . "$RC_DEFAULTS"
+
+ #
+ # If passed `-a' (rather than `-A'), re-purge the
+ # environment, removing the rc.conf(5) defaults.
+ #
+ [ "$SHOW_ALL" = "1" ] &&
+ f_clean_env --except rc_conf_files $EXCEPT
+
+ #
+ # If `-f file' was passed, set $rc_conf_files to an
+ # explicit value, modifying the default behavior of
+ # source_rc_confs().
+ #
+ if [ "${RC_CONFS+set}" ]; then
+ [ "$SHOW_ALL" = "1" -a "$SERVICE" -a \
+ ! "$DEFAULT" ] || rc_conf_files=
+ rc_conf_files="$rc_conf_files $RC_CONFS"
+ rc_conf_files="${rc_conf_files# }"
+ rc_conf_files="${rc_conf_files% }"
+ fi
+
+ source_rc_confs
+
+ #
+ # If passed `-a' (rather than `-A'), remove
+ # `rc_conf_files' unless it was defined somewhere
+ # other than rc.conf(5) defaults.
+ #
+ [ "$SHOW_ALL" = "1" -a \
+ "$( f_sysrc_find rc_conf_files )" = "$RC_DEFAULTS" \
+ ] && unset rc_conf_files
+ fi
+
+ for NAME in $( set |
+ awk -F= '/^[[:alpha:]_][[:alnum:]_]*=/ {print $1}' |
+ grep -Ev "^($EXCEPT)$"
+ ); do
+ #
+ # If enabled, describe rather than expand value
+ #
+ if [ "$DESCRIBE" ]; then
+ echo "$NAME: $( f_sysrc_desc "$NAME" )"
+ continue
+ fi
+
+ #
+ # If `-F' is passed, find it and move on
+ #
+ if [ "$SHOW_FILE" ]; then
+ [ "$SHOW_NAME" ] && echo -n "$NAME: "
+ f_sysrc_find "$NAME"
+ continue
+ fi
+
+ #
+ # If `-X' is passed, delete the variables
+ #
+ if [ "$DELETE" = "2" ]; then
+ f_sysrc_delete "$NAME"
+ continue
+ fi
+
+ [ "$VERBOSE" ] &&
+ echo -n "$( f_sysrc_find "$NAME" ): "
+
+ #
+ # If `-N' is passed, simplify the output
+ #
+ if [ ! "$SHOW_VALUE" ]; then
+ echo "$NAME"
+ continue
+ fi
+
+ echo "${SHOW_NAME:+$NAME$SEP}$(
+ f_sysrc_get "$NAME" )${SHOW_EQUALS:+\"}"
+
+ done
+ )
+
+ #
+ # Ignore the remainder of positional arguments.
+ #
+ exit $SUCCESS
+fi
+
+#
+# Process command-line arguments
+#
+status=$SUCCESS
+while [ $# -gt 0 ]; do
+ NAME="${1%%=*}"
+
+ case "$NAME" in
+ *+) mode=APPEND NAME="${NAME%+}" ;;
+ *-) mode=REMOVE NAME="${NAME%-}" ;;
+ *) mode=ASSIGN
+ esac
+
+ [ "$DESCRIBE" ] &&
+ echo "$NAME: $( f_sysrc_desc "$NAME" )"
+
+ case "$1" in
+ *=*)
+ #
+ # Like sysctl(8), if both `-d' AND "name=value" is passed,
+ # first describe (done above), then attempt to set
+ #
+
+ # If verbose, prefix line with where the directive lives
+ if [ "$VERBOSE" -a ! "$CHECK_ONLY" ]; then
+ file=$( f_sysrc_find "$NAME" )
+ [ "$file" = "$RC_DEFAULTS" -o ! "$file" ] &&
+ file=$( f_sysrc_get 'rc_conf_files%%[$IFS]*' )
+ if [ "$SHOW_EQUALS" ]; then
+ echo -n ": $file; "
+ else
+ echo -n "$file: "
+ fi
+ fi
+
+ #
+ # If `-x' or `-X' is passed, delete the variable and ignore the
+ # desire to set some value
+ #
+ if [ "$DELETE" ]; then
+ f_sysrc_delete "$NAME" || status=$FAILURE
+ shift 1
+ continue
+ fi
+
+ #
+ # If `-c' is passed, simply compare and move on
+ #
+ if [ "$CHECK_ONLY" ]; then
+ if ! IGNORED=$( f_sysrc_get "$NAME?" ); then
+ status=$FAILURE
+ [ "$VERBOSE" ] &&
+ echo "$NAME: not currently set"
+ shift 1
+ continue
+ fi
+ value=$( f_sysrc_get "$NAME" )
+ if [ "$value" != "${1#*=}" ]; then
+ status=$FAILURE
+ if [ "$VERBOSE" ]; then
+ echo -n "$( f_sysrc_find "$NAME" ): "
+ echo -n "$NAME: would change from "
+ echo "\`$value' to \`${1#*=}'"
+ fi
+ elif [ "$VERBOSE" ]; then
+ echo -n "$( f_sysrc_find "$NAME" ): "
+ echo "$NAME: already set to \`$value'"
+ fi
+ shift 1
+ continue
+ fi
+
+ #
+ # Determine both `before' value and appropriate `new' value
+ #
+ case "$mode" in
+ APPEND)
+ before=$( f_sysrc_get "$NAME" )
+ add="${1#*=}"
+ delim="${add%"${add#?}"}" # first character
+ oldIFS="$IFS"
+ case "$delim" in
+ ""|[$IFS]|[a-zA-Z0-9]) delim=" " ;;
+ *) IFS="$delim"
+ esac
+ new="$before"
+ for a in $add; do
+ [ "$a" ] || continue
+ skip=
+ for b in $before; do
+ [ "$b" = "$a" ] && skip=1 break
+ done
+ [ "$skip" ] || new="$new$delim$a"
+ done
+ new="${new#"$delim"}" IFS="$oldIFS"
+ unset add delim oldIFS a skip b
+ [ "$SHOW_FILE" ] && before=$( f_sysrc_find "$NAME" )
+ ;;
+ REMOVE)
+ before=$( f_sysrc_get "$NAME" )
+ remove="${1#*=}"
+ delim="${remove%"${remove#?}"}" # first character
+ oldIFS="$IFS"
+ case "$delim" in
+ ""|[$IFS]|[a-zA-Z0-9]) delim=" " ;;
+ *) IFS="$delim"
+ esac
+ new=
+ for b in $before; do
+ [ "$b" ] || continue
+ add=1
+ for r in $remove; do
+ [ "$r" = "$b" ] && add= break
+ done
+ [ "$add" ] && new="$new$delim$b"
+ done
+ new="${new#"$delim"}" IFS="$oldIFS"
+ unset remove delim oldIFS b add r
+ [ "$SHOW_FILE" ] && before=$( f_sysrc_find "$NAME" )
+ ;;
+ *) # ASSIGN
+ if [ "$SHOW_FILE" ]; then
+ before=$( f_sysrc_find "$NAME" )
+ else
+ before=$( f_sysrc_get "$NAME" )
+ fi
+ new="${1#*=}"
+ esac
+
+ #
+ # If `-N' is passed, simplify the output
+ #
+ if [ ! "$SHOW_VALUE" ]; then
+ echo "$NAME"
+ f_sysrc_set "$NAME" "$new"
+ else
+ if f_sysrc_set "$NAME" "$new"; then
+ if [ "$SHOW_FILE" ]; then
+ after=$( f_sysrc_find "$NAME" )
+ else
+ after=$( f_sysrc_get "$NAME" )
+ fi
+ echo -n "${SHOW_NAME:+$NAME$SEP}"
+ echo -n "$before${SHOW_EQUALS:+\" #}"
+ echo -n " -> ${SHOW_EQUALS:+\"}$after"
+ echo "${SHOW_EQUALS:+\"}"
+ fi
+ fi
+ ;;
+ *)
+ if ! IGNORED=$( f_sysrc_get "$NAME?" ); then
+ [ "$IGNORE_UNKNOWNS" -o "$QUIET" ] ||
+ echo "$pgm: unknown variable '$NAME'"
+ shift 1
+ status=$FAILURE
+ continue
+ fi
+
+ # The above check told us what we needed for `-c'
+ if [ "$CHECK_ONLY" ]; then
+ shift 1
+ continue
+ fi
+
+ #
+ # Like sysctl(8), when `-d' is passed, desribe it
+ # (already done above) rather than expanding it
+ #
+
+ if [ "$DESCRIBE" ]; then
+ shift 1
+ continue
+ fi
+
+ #
+ # If `-x' or `-X' is passed, delete the variable
+ #
+ if [ "$DELETE" ]; then
+ f_sysrc_delete "$NAME" || status=$FAILURE
+ shift 1
+ continue
+ fi
+
+ #
+ # If `-F' is passed, find it and move on
+ #
+ if [ "$SHOW_FILE" ]; then
+ [ "$SHOW_NAME" ] && echo -n "$NAME: "
+ f_sysrc_find "$NAME"
+ shift 1
+ continue
+ fi
+
+ if [ "$VERBOSE" ]; then
+ if [ "$SHOW_EQUALS" ]; then
+ echo -n ": $( f_sysrc_find "$NAME" ); "
+ else
+ echo -n "$( f_sysrc_find "$NAME" ): "
+ fi
+ fi
+
+ #
+ # If `-N' is passed, simplify the output
+ #
+ if [ ! "$SHOW_VALUE" ]; then
+ echo "$NAME"
+ else
+ echo "${SHOW_NAME:+$NAME$SEP}$(
+ f_sysrc_get "$NAME" )${SHOW_EQUALS:+\"}"
+ fi
+ esac
+ shift 1
+done
+
+exit $status # $SUCCESS unless error occurred with either `-c' or `-x'
+
+################################################################################
+# END
+################################################################################
diff --git a/usr.sbin/sysrc/sysrc.8 b/usr.sbin/sysrc/sysrc.8
new file mode 100644
index 0000000..dbd0e4b
--- /dev/null
+++ b/usr.sbin/sysrc/sysrc.8
@@ -0,0 +1,477 @@
+.\" Copyright (c) 2011-2015 Devin Teske
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 12, 2015
+.Dt SYSRC 8
+.Os
+.Sh NAME
+.Nm sysrc
+.Nd safely edit system rc files
+.Sh SYNOPSIS
+.Nm
+.Op Fl cdDeEFhinNqvx
+.Op Fl s Ar name
+.Op Fl f Ar file
+.Op Fl j Ar jail | Fl R Ar dir
+.Ar name Ns Op Ns Oo +|- Oc Ns = Ns Ar value
+.Ar ...
+.Nm
+.Op Fl cdDeEFhinNqvx
+.Op Fl s Ar name
+.Op Fl f Ar file
+.Op Fl j Ar jail | Fl R Ar dir
+.Fl a | A
+.Nm
+.Op Fl E
+.Op Fl s Ar name
+.Op Fl f Ar file
+.Fl l
+.Nm
+.Op Fl eEqv
+.Fl L
+.Op Ar name ...
+.Sh DESCRIPTION
+The
+.Nm
+utility retrieves
+.Xr rc.conf 5
+variables from the collection of system rc files and allows processes with
+appropriate privilege to change values in a safe and effective manner.
+.Pp
+The following options are available:
+.Bl -tag -width indent+
+.It Fl a
+Dump a list of all non-default configuration variables.
+.It Fl A
+Dump a list of all configuration variables
+.Pq incl. defaults .
+.It Fl c
+Check only.
+For querying, return success if all requested variables are set
+.Pq even if NULL ,
+otherwise return error status.
+For assignments, return success if no changes are required, otherwise failure.
+If verbose
+.Pq see Dq Fl v
+prints a message stating whether variables are set and/or changes are required.
+.It Fl d
+Print a description of the given variable.
+.It Fl D
+Show default value(s) only (this is the same as setting RC_CONFS to NULL or
+passing `-f' with a NULL file-argument).
+.It Fl e
+Print query results as
+.Xr sh 1
+compatible syntax
+.Pq for example, Ql var=value .
+Ignored if either
+.Ql Fl n
+or
+.Ql Fl F
+is specified.
+.It Fl E
+When given
+.Sq Fl l
+or
+.Sq Fl L
+to list configuration files, only list those that exist.
+When changing a setting, prefer to modify existing files.
+.It Fl f Ar file
+Operate on the specified file(s) instead of the files obtained by reading the
+.Sq rc_conf_files
+entry in the
+.Ev RC_DEFAULTS
+file.
+This option can be specified multiple times for additional files.
+.It Fl F
+Show only the last
+.Xr rc.conf 5
+file each directive is in.
+.It Fl h
+Print a short usage message to stderr and exit.
+.It Fl -help
+Print a full usage statement to stderr and exit.
+.It Fl i
+Ignore unknown variables.
+.It Fl j Ar jail
+The
+.Ar jid
+or name of the
+.Ar jail
+to operate within
+.Pq overrides So Fl R Ar dir Sc ; requires Xr jexec 8 .
+.It Fl l
+List configuration files used at startup on stdout and exit.
+.It Fl L
+List all configuration files including rc.conf.d entries on stdout and exit.
+Can be combined with
+.Sq Fl v
+or
+.Sq Fl e
+to show service names.
+.Nm
+exits with success if all named services are installed, failure otherwise.
+.It Fl n
+Show only variable values, not their names.
+.It Fl N
+Show only variable names, not their values.
+.It Fl q
+Quiet.
+Disable verbose and hide certain errors.
+When combined with
+.Sq Fl L
+and one or more
+.Li Ar name
+arguments, provide only exit status and no output.
+.It Fl R Ar dir
+Operate within the root directory
+.Sq Ar dir
+rather than
+.Sq / .
+.It Fl s Ar name
+If an
+.Li rc.d
+script of
+.Ar name
+exists
+.Po
+in
+.Dq /etc/rc.d
+or
+.Li local_startup
+directories
+.Pc ,
+process its
+.Dq rc.conf.d
+entries as potential overrides to
+.Sq rc_conf_files .
+See
+.Xr rc.subr 8
+for additional information on
+.Dq rc.conf.d .
+Can be combined with
+.Sq Fl l
+to list configuration files used by service at startup.
+.It Fl v
+Verbose.
+Print the pathname of the specific
+.Xr rc.conf 5
+file where the directive was found.
+.It Fl -version
+Print version information to stdout and exit.
+.It Fl x
+Remove variable(s) from specified file(s).
+.El
+.Pp
+This utility has a similar syntax to
+.Xr sysctl 8 .
+It shares the `-e' and `-n' options
+.Pq detailed above
+and also has the same
+.Ql name[=value]
+syntax for making queries/assignments.
+In addition
+.Pq but unlike Xr sysctl 8 ,
+.Ql name+=value
+is supported for adding items to values
+.Pq see APPENDING VALUES
+and
+.Ql name-=value
+is supported for removing items from values
+.Pq see SUBTRACTING VALUES .
+.Pp
+However, while
+.Xr sysctl 8
+serves to query/modify MIBs in the entrant kernel,
+.Nm
+instead works on values in the system
+.Xr rc.conf 5
+configuration files.
+.Pp
+The list of system configuration files is configured in the file
+.Ql /etc/defaults/rc.conf
+within the variable
+.Ql rc_conf_files ,
+which by-default contains a space-separated list of pathnames.
+On all FreeBSD
+systems, this defaults to the value "/etc/rc.conf /etc/rc.conf.local".
+Each
+pathname is sourced in-order upon startup.
+It is in the same fashion that
+.Nm
+sources the configuration files before returning the value of the given
+variable.
+.Pp
+When supplied a variable name,
+.Nm
+will return the value of the variable.
+If the variable does not appear in any
+of the configured
+.Ql rc_conf_files ,
+an error is printed and error status is returned.
+.Pp
+When changing values of a given variable, it does not matter if the variable
+appears in any of the
+.Ql rc_conf_files
+or not.
+If the variable does not appear in any of the files, it is appended to
+the end of the first pathname in the
+.Ql rc_conf_files
+variable.
+Otherwise,
+.Nm
+will replace only the last-occurrence in the last-file found to contain the
+variable.
+This gets the value to take effect next boot without heavily
+modifying these integral files (yet taking care not to allow the file to
+grow unwieldy should
+.Nm
+be called repeatedly).
+.Sh APPENDING VALUES
+When using the
+.Ql key+=value
+syntax to add items to existing values,
+the first character of the value is taken as the delimiter separating items
+.Pq usually Qo \ Qc or Qo , Qc .
+For example, in the following statement:
+.Bl -item -offset indent
+.It
+.Nm
+cloned_interfaces+=" gif0"
+.El
+.Pp
+the first character is a space, informing
+.Nm
+that existing values are to be considered separated by whitespace.
+If
+.Ql gif0
+is not found in the existing value for
+.Va cloned_interfaces ,
+it is added
+.Pq with delimiter only if existing value is non-NULL .
+.Pp
+For convenience, if the first character is alpha-numeric
+.Pq letters A-Z, a-z, or numbers 0-9 ,
+.Nm
+uses the default setting of whitespace as separator.
+For example, the above and below statements are equivalent since
+.Dq gif0
+starts with an alpha-numeric character
+.Pq the letter Li g :
+.Bl -item -offset indent
+.It
+.Nm
+cloned_interfaces+=gif0
+.El
+.Pp
+Take the following sequence for example:
+.Bl -item -offset indent
+.It
+.Nm
+cloned_interfaces= # start with NULL
+.It
+.Nm
+cloned_interfaces+=gif0
+.Dl # NULL -> `gif0' Pq NB: no preceding delimiter
+.It
+.Nm
+cloned_interfaces+=gif0 # no change
+.It
+.Nm
+cloned_interfaces+="tun0 gif0"
+.Dl # `gif0' -> `gif0 tun0' Pq NB: no duplication
+.El
+.Pp
+.Nm
+prevents the same value from being added if already there.
+.Sh SUBTRACTING VALUES
+When using the
+.Ql key-=value
+syntax to remove items from existing values,
+the first character of the value is taken as the delimiter separating items
+.Pq usually Qo \ Qc or Qo , Qc .
+For example, in the following statement:
+.Pp
+.Dl Nm cloned_interfaces-=" gif0"
+.Pp
+the first character is a space, informing
+.Nm
+that existing values are to be considered separated by whitespace.
+If
+.Ql gif0
+is found in the existing value for
+.Va cloned_interfaces ,
+it is removed
+.Pq extra delimiters removed .
+.Pp
+For convenience, if the first character is alpha-numeric
+.Pq letters A-Z, a-z, or numbers 0-9 ,
+.Nm
+uses the default setting of whitespace as separator.
+For example, the above and below statements are equivalent since
+.Dq gif0
+starts with an alpha-numeric character
+.Pq the letter Li g :
+.Bl -item -offset indent
+.It
+.Nm
+cloned_interfaces-=gif0
+.El
+.Pp
+Take the following sequence for example:
+.Bl -item -offset indent
+.It
+.Nm
+foo="bar baz" # start
+.It
+.Nm
+foo-=bar # `bar baz' -> `baz'
+.It
+.Nm
+foo-=baz # `baz' -> NULL
+.El
+.Pp
+.Nm
+removes all occurrences of all items provided
+and collapses extra delimiters between items.
+.Sh ENVIRONMENT
+The following environment variables are referenced by
+.Nm :
+.Bl -tag -width ".Ev RC_DEFAULTS"
+.It Ev RC_CONFS
+Override default
+.Ql rc_conf_files
+.Pq even if set to NULL .
+.It Ev RC_DEFAULTS
+Location of
+.Ql /etc/defaults/rc.conf
+file.
+.El
+.Sh DEPENDENCIES
+The following standard commands are required by
+.Nm :
+.Pp
+.Xr awk 1 ,
+.Xr cat 1 ,
+.Xr chmod 1 ,
+.Xr env 1 ,
+.Xr grep 1 ,
+.Xr jls 1 ,
+.Xr mktemp 1 ,
+.Xr mv 1 ,
+.Xr rm 1 ,
+.Xr sh 1 ,
+.Xr stat 1 ,
+.Xr tail 1 ,
+.Xr chown 8
+and
+.Xr jexec 8 .
+.Sh FILES
+.Bl -tag -width ".Pa /etc/defaults/rc.conf" -compact
+.It Pa /etc/defaults/rc.conf
+.It Pa /etc/rc.conf
+.It Pa /etc/rc.conf.local
+.It Pa /etc/rc.conf.d/name
+.It Pa /etc/rc.conf.d/name/*
+.It Pa /usr/local/etc/rc.conf.d/name
+.It Pa /usr/local/etc/rc.conf.d/name/*
+.El
+.Sh EXAMPLES
+Below are some simple examples of how
+.Nm
+can be used to query certain values from the
+.Xr rc.conf 5
+collection of system configuration files:
+.Pp
+.Nm
+sshd_enable
+.Dl returns the value of $sshd_enable, usually YES or NO .
+.Pp
+.Nm
+defaultrouter
+.Dl returns IP address of default router Pq if configured .
+.Pp
+Working on other files, such as
+.Xr crontab 5 :
+.Pp
+.Nm
+-f /etc/crontab MAILTO
+.Dl returns the value of the MAILTO setting Pq if configured .
+.Pp
+Appending to existing values:
+.Pp
+.Nm
+\&cloned_interfaces+=gif0
+.Dl appends Qo gif0 Qc to $cloned_interfaces Pq see APPENDING VALUES .
+.Pp
+.Nm
+\&cloned_interfaces-=gif0
+.Dl removes Qo gif0 Qc from $cloned_interfaces Pq see SUBTRACTING VALUES .
+.Pp
+In addition to the above syntax,
+.Nm
+also supports inline
+.Xr sh 1
+PARAMETER expansion for changing the way values are reported, shown below:
+.Pp
+.Nm
+\&'hostname%%.*'
+.Dl returns $hostname up to (but not including) first `.' .
+.Pp
+.Nm
+\&'network_interfaces%%[$IFS]*'
+.Dl returns first word of $network_interfaces .
+.Pp
+.Nm
+\&'ntpdate_flags##*[$IFS]'
+.Dl returns last word of $ntpdate_flags (time server address) .
+.Pp
+.Nm
+usbd_flags-"default"
+.Dl returns $usbd_flags or "default" if unset or NULL .
+.Pp
+.Nm
+cloned_interfaces+"alternate"
+.Dl returns "alternate" if $cloned_interfaces is set .
+.Sh SEE ALSO
+.Xr jls 1 ,
+.Xr rc.conf 5 ,
+.Xr rc.subr 8 ,
+.Xr jail 8 ,
+.Xr jexec 8 ,
+.Xr rc 8 ,
+.Xr sysctl 8
+.Sh HISTORY
+A
+.Nm
+utility first appeared in
+.Fx 9.2 .
+.Sh AUTHORS
+.An Devin Teske Aq Mt dteske@FreeBSD.org
+.Sh THANKS TO
+Brandon Gooch, Garrett Cooper, Julian Elischer, Pawel Jakub Dawidek,
+Cyrille Lefevre, Ross West, Stefan Esser, Marco Steinbach, Jilles Tjoelker,
+Allan Jude, and Lars Engels for suggestions, help, and testing.
diff --git a/usr.sbin/tcpdchk/Makefile b/usr.sbin/tcpdchk/Makefile
new file mode 100644
index 0000000..c7d4155
--- /dev/null
+++ b/usr.sbin/tcpdchk/Makefile
@@ -0,0 +1,22 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+.PATH: ${.CURDIR}/../../contrib/tcp_wrappers
+
+PROG= tcpdchk
+MAN= tcpdchk.8
+SRCS= tcpdchk.c fakelog.c inetcf.c scaffold.c
+
+CFLAGS+=-DREAL_DAEMON_DIR=\"${LIBEXECDIR}\" \
+ -DSEVERITY=LOG_INFO -DRFC931_TIMEOUT=10 -DPROCESS_OPTIONS \
+ -DHOSTS_DENY=\"/etc/hosts.deny\" -DHOSTS_ALLOW=\"/etc/hosts.allow\"
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+=-DINET6
+.endif
+
+WARNS?= 0
+
+LIBADD= wrap
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/tcpdchk/Makefile.depend b/usr.sbin/tcpdchk/Makefile.depend
new file mode 100644
index 0000000..e314de5
--- /dev/null
+++ b/usr.sbin/tcpdchk/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libwrap \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/tcpdmatch/Makefile b/usr.sbin/tcpdmatch/Makefile
new file mode 100644
index 0000000..2f92427
--- /dev/null
+++ b/usr.sbin/tcpdmatch/Makefile
@@ -0,0 +1,21 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+.PATH: ${.CURDIR}/../../contrib/tcp_wrappers
+
+PROG= tcpdmatch
+MAN= tcpdmatch.8
+SRCS= tcpdmatch.c fakelog.c inetcf.c scaffold.c
+
+CFLAGS+=-DREAL_DAEMON_DIR=\"${LIBEXECDIR}\" \
+ -DSEVERITY=LOG_INFO -DRFC931_TIMEOUT=10
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+=-DINET6
+.endif
+
+WARNS?= 0
+
+LIBADD= wrap
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/tcpdmatch/Makefile.depend b/usr.sbin/tcpdmatch/Makefile.depend
new file mode 100644
index 0000000..e314de5
--- /dev/null
+++ b/usr.sbin/tcpdmatch/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libwrap \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/tcpdrop/Makefile b/usr.sbin/tcpdrop/Makefile
new file mode 100644
index 0000000..2fc606a
--- /dev/null
+++ b/usr.sbin/tcpdrop/Makefile
@@ -0,0 +1,7 @@
+# $OpenBSD: Makefile,v 1.1 2004/04/26 19:51:20 markus Exp $
+# $FreeBSD$
+
+PROG= tcpdrop
+MAN= tcpdrop.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/tcpdrop/Makefile.depend b/usr.sbin/tcpdrop/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/tcpdrop/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/tcpdrop/tcpdrop.8 b/usr.sbin/tcpdrop/tcpdrop.8
new file mode 100644
index 0000000..61c3f37
--- /dev/null
+++ b/usr.sbin/tcpdrop/tcpdrop.8
@@ -0,0 +1,97 @@
+.\" $OpenBSD: tcpdrop.8,v 1.5 2004/05/24 13:57:31 jmc Exp $
+.\"
+.\" Copyright (c) 2009 Juli Mallett <jmallett@FreeBSD.org>
+.\" Copyright (c) 2004 Markus Friedl <markus@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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd January 30, 2013
+.Dt TCPDROP 8
+.Os
+.Sh NAME
+.Nm tcpdrop
+.Nd drop TCP connections
+.Sh SYNOPSIS
+.Nm tcpdrop
+.Ar local-address
+.Ar local-port
+.Ar foreign-address
+.Ar foreign-port
+.Nm tcpdrop
+.Op Fl l
+.Fl a
+.Sh DESCRIPTION
+The
+.Nm
+command may be used to drop TCP connections from the command line.
+.Pp
+If
+.Fl a
+is specified then
+.Nm
+will attempt to drop all active connections.
+The
+.Fl l
+flag may be given to list the tcpdrop invocation to drop all active
+connections one at a time.
+.Pp
+If
+.Fl a
+is not specified then only the connection between the given local
+address
+.Ar local-address ,
+port
+.Ar local-port ,
+and the foreign address
+.Ar foreign-address ,
+port
+.Ar foreign-port ,
+will be dropped.
+.Pp
+Addresses and ports may be specified by name or numeric value.
+Both IPv4 and IPv6 address formats are supported.
+.Pp
+The addresses and ports may be separated by periods or colons
+instead of spaces.
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+If a connection to
+.Xr httpd 8
+is causing congestion on a network link, one can drop the TCP session
+in charge:
+.Bd -literal -offset indent
+# sockstat -c | grep httpd
+www httpd 16525 3 tcp4 \e
+ 192.168.5.41:80 192.168.5.1:26747
+.Ed
+.Pp
+The following command will drop the connection:
+.Bd -literal -offset indent
+# tcpdrop 192.168.5.41 80 192.168.5.1 26747
+.Ed
+.Pp
+The following command will drop all connections but those to or from
+port 22, the port used by
+.Xr sshd 8 :
+.Bd -literal -offset indent
+# tcpdrop -l -a | grep -vw 22 | sh
+.Ed
+.Sh SEE ALSO
+.Xr netstat 1 ,
+.Xr sockstat 1
+.Sh AUTHORS
+.An Markus Friedl Aq Mt markus@openbsd.org
+.An Juli Mallett Aq Mt jmallett@FreeBSD.org
diff --git a/usr.sbin/tcpdrop/tcpdrop.c b/usr.sbin/tcpdrop/tcpdrop.c
new file mode 100644
index 0000000..ef3f6bd
--- /dev/null
+++ b/usr.sbin/tcpdrop/tcpdrop.c
@@ -0,0 +1,355 @@
+/* $OpenBSD: tcpdrop.c,v 1.4 2004/05/22 23:55:22 deraadt Exp $ */
+
+/*-
+ * Copyright (c) 2009 Juli Mallett <jmallett@FreeBSD.org>
+ * Copyright (c) 2004 Markus Friedl <markus@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/param.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/sysctl.h>
+
+#include <netinet/in.h>
+#include <netinet/in_pcb.h>
+#define TCPSTATES
+#include <netinet/tcp_fsm.h>
+#include <netinet/tcp_var.h>
+
+#include <err.h>
+#include <netdb.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define TCPDROP_FOREIGN 0
+#define TCPDROP_LOCAL 1
+
+struct host_service {
+ char hs_host[NI_MAXHOST];
+ char hs_service[NI_MAXSERV];
+};
+
+static bool tcpdrop_list_commands = false;
+
+static char *findport(const char *);
+static struct xinpgen *getxpcblist(const char *);
+static void sockinfo(const struct sockaddr *, struct host_service *);
+static bool tcpdrop(const struct sockaddr *, const struct sockaddr *);
+static bool tcpdropall(void);
+static bool tcpdropbyname(const char *, const char *, const char *,
+ const char *);
+static bool tcpdropconn(const struct in_conninfo *);
+static void usage(void);
+
+/*
+ * Drop a tcp connection.
+ */
+int
+main(int argc, char *argv[])
+{
+ char *lport, *fport;
+ bool dropall;
+ int ch;
+
+ dropall = false;
+
+ while ((ch = getopt(argc, argv, "al")) != -1) {
+ switch (ch) {
+ case 'a':
+ dropall = true;
+ break;
+ case 'l':
+ tcpdrop_list_commands = true;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (dropall) {
+ if (argc != 0)
+ usage();
+ if (!tcpdropall())
+ exit(1);
+ exit(0);
+ }
+
+ if ((argc != 2 && argc != 4) || tcpdrop_list_commands)
+ usage();
+
+ if (argc == 2) {
+ lport = findport(argv[0]);
+ fport = findport(argv[1]);
+ if (lport == NULL || lport[1] == '\0' || fport == NULL ||
+ fport[1] == '\0')
+ usage();
+ *lport++ = '\0';
+ *fport++ = '\0';
+ if (!tcpdropbyname(argv[0], lport, argv[1], fport))
+ exit(1);
+ } else if (!tcpdropbyname(argv[0], argv[1], argv[2], argv[3]))
+ exit(1);
+
+ exit(0);
+}
+
+static char *
+findport(const char *arg)
+{
+ char *dot, *colon;
+
+ /* A strrspn() or strrpbrk() would be nice. */
+ dot = strrchr(arg, '.');
+ colon = strrchr(arg, ':');
+ if (dot == NULL)
+ return (colon);
+ if (colon == NULL)
+ return (dot);
+ if (dot < colon)
+ return (colon);
+ else
+ return (dot);
+}
+
+static struct xinpgen *
+getxpcblist(const char *name)
+{
+ struct xinpgen *xinp;
+ size_t len;
+ int rv;
+
+ len = 0;
+ rv = sysctlbyname(name, NULL, &len, NULL, 0);
+ if (rv == -1)
+ err(1, "sysctlbyname %s", name);
+
+ if (len == 0)
+ errx(1, "%s is empty", name);
+
+ xinp = malloc(len);
+ if (xinp == NULL)
+ errx(1, "malloc failed");
+
+ rv = sysctlbyname(name, xinp, &len, NULL, 0);
+ if (rv == -1)
+ err(1, "sysctlbyname %s", name);
+
+ return (xinp);
+}
+
+static void
+sockinfo(const struct sockaddr *sa, struct host_service *hs)
+{
+ static const int flags = NI_NUMERICHOST | NI_NUMERICSERV;
+ int rv;
+
+ rv = getnameinfo(sa, sa->sa_len, hs->hs_host, sizeof hs->hs_host,
+ hs->hs_service, sizeof hs->hs_service, flags);
+ if (rv == -1)
+ err(1, "getnameinfo");
+}
+
+static bool
+tcpdrop(const struct sockaddr *lsa, const struct sockaddr *fsa)
+{
+ struct host_service local, foreign;
+ struct sockaddr_storage addrs[2];
+ int rv;
+
+ memcpy(&addrs[TCPDROP_FOREIGN], fsa, fsa->sa_len);
+ memcpy(&addrs[TCPDROP_LOCAL], lsa, lsa->sa_len);
+
+ sockinfo(lsa, &local);
+ sockinfo(fsa, &foreign);
+
+ if (tcpdrop_list_commands) {
+ printf("tcpdrop %s %s %s %s\n", local.hs_host, local.hs_service,
+ foreign.hs_host, foreign.hs_service);
+ return (true);
+ }
+
+ rv = sysctlbyname("net.inet.tcp.drop", NULL, NULL, &addrs,
+ sizeof addrs);
+ if (rv == -1) {
+ warn("%s %s %s %s", local.hs_host, local.hs_service,
+ foreign.hs_host, foreign.hs_service);
+ return (false);
+ }
+ printf("%s %s %s %s: dropped\n", local.hs_host, local.hs_service,
+ foreign.hs_host, foreign.hs_service);
+ return (true);
+}
+
+static bool
+tcpdropall(void)
+{
+ struct xinpgen *head, *xinp;
+ struct xtcpcb *xpcb;
+ struct tcpcb *tp;
+ struct inpcb *inp;
+ bool ok;
+
+ ok = true;
+
+ head = getxpcblist("net.inet.tcp.pcblist");
+
+#define XINP_NEXT(xinp) \
+ ((struct xinpgen *)((uintptr_t)(xinp) + (xinp)->xig_len))
+
+ for (xinp = XINP_NEXT(head); xinp->xig_len > sizeof *xinp;
+ xinp = XINP_NEXT(xinp)) {
+ xpcb = (struct xtcpcb *)xinp;
+ tp = &xpcb->xt_tp;
+ inp = &xpcb->xt_inp;
+
+ /*
+ * XXX
+ * Check protocol, support just v4 or v6, etc.
+ */
+
+ /* Ignore PCBs which were freed during copyout. */
+ if (inp->inp_gencnt > head->xig_gen)
+ continue;
+
+ /* Skip listening sockets. */
+ if (tp->t_state == TCPS_LISTEN)
+ continue;
+
+ if (!tcpdropconn(&inp->inp_inc))
+ ok = false;
+ }
+ free(head);
+
+ return (ok);
+}
+
+static bool
+tcpdropbyname(const char *lhost, const char *lport, const char *fhost,
+ const char *fport)
+{
+ static const struct addrinfo hints = {
+ /*
+ * Look for streams in all domains.
+ */
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_STREAM,
+ };
+ struct addrinfo *ail, *local, *aif, *foreign;
+ int error;
+ bool ok, infamily;
+
+ error = getaddrinfo(lhost, lport, &hints, &local);
+ if (error != 0)
+ errx(1, "getaddrinfo: %s port %s: %s", lhost, lport,
+ gai_strerror(error));
+
+ error = getaddrinfo(fhost, fport, &hints, &foreign);
+ if (error != 0) {
+ freeaddrinfo(local); /* XXX gratuitous */
+ errx(1, "getaddrinfo: %s port %s: %s", fhost, fport,
+ gai_strerror(error));
+ }
+
+ ok = true;
+ infamily = false;
+
+ /*
+ * Try every combination of local and foreign address pairs.
+ */
+ for (ail = local; ail != NULL; ail = ail->ai_next) {
+ for (aif = foreign; aif != NULL; aif = aif->ai_next) {
+ if (ail->ai_family != aif->ai_family)
+ continue;
+ infamily = true;
+ if (!tcpdrop(ail->ai_addr, aif->ai_addr))
+ ok = false;
+ }
+ }
+
+ if (!infamily) {
+ warnx("%s %s %s %s: different address families", lhost, lport,
+ fhost, fport);
+ ok = false;
+ }
+
+ freeaddrinfo(local);
+ freeaddrinfo(foreign);
+
+ return (ok);
+}
+
+static bool
+tcpdropconn(const struct in_conninfo *inc)
+{
+ struct sockaddr *local, *foreign;
+ struct sockaddr_in6 sin6[2];
+ struct sockaddr_in sin4[2];
+
+ if ((inc->inc_flags & INC_ISIPV6) != 0) {
+ memset(sin6, 0, sizeof sin6);
+
+ sin6[TCPDROP_LOCAL].sin6_len = sizeof sin6[TCPDROP_LOCAL];
+ sin6[TCPDROP_LOCAL].sin6_family = AF_INET6;
+ sin6[TCPDROP_LOCAL].sin6_port = inc->inc_lport;
+ memcpy(&sin6[TCPDROP_LOCAL].sin6_addr, &inc->inc6_laddr,
+ sizeof inc->inc6_laddr);
+ local = (struct sockaddr *)&sin6[TCPDROP_LOCAL];
+
+ sin6[TCPDROP_FOREIGN].sin6_len = sizeof sin6[TCPDROP_FOREIGN];
+ sin6[TCPDROP_FOREIGN].sin6_family = AF_INET6;
+ sin6[TCPDROP_FOREIGN].sin6_port = inc->inc_fport;
+ memcpy(&sin6[TCPDROP_FOREIGN].sin6_addr, &inc->inc6_faddr,
+ sizeof inc->inc6_faddr);
+ foreign = (struct sockaddr *)&sin6[TCPDROP_FOREIGN];
+ } else {
+ memset(&sin4[TCPDROP_LOCAL], 0, sizeof sin4[TCPDROP_LOCAL]);
+
+ sin4[TCPDROP_LOCAL].sin_len = sizeof sin4[TCPDROP_LOCAL];
+ sin4[TCPDROP_LOCAL].sin_family = AF_INET;
+ sin4[TCPDROP_LOCAL].sin_port = inc->inc_lport;
+ memcpy(&sin4[TCPDROP_LOCAL].sin_addr, &inc->inc_laddr,
+ sizeof inc->inc_laddr);
+ local = (struct sockaddr *)&sin4[TCPDROP_LOCAL];
+
+ sin4[TCPDROP_FOREIGN].sin_len = sizeof sin4[TCPDROP_FOREIGN];
+ sin4[TCPDROP_FOREIGN].sin_family = AF_INET;
+ sin4[TCPDROP_FOREIGN].sin_port = inc->inc_fport;
+ memcpy(&sin4[TCPDROP_FOREIGN].sin_addr, &inc->inc_faddr,
+ sizeof inc->inc_faddr);
+ foreign = (struct sockaddr *)&sin4[TCPDROP_FOREIGN];
+ }
+
+ return (tcpdrop(local, foreign));
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+"usage: tcpdrop local-address local-port foreign-address foreign-port\n"
+" tcpdrop local-address:local-port foreign-address:foreign-port\n"
+" tcpdrop local-address.local-port foreign-address.foreign-port\n"
+" tcpdrop [-l] -a\n");
+ exit(1);
+}
diff --git a/usr.sbin/tcpdump/Makefile b/usr.sbin/tcpdump/Makefile
new file mode 100644
index 0000000..d183feb
--- /dev/null
+++ b/usr.sbin/tcpdump/Makefile
@@ -0,0 +1,6 @@
+# @(#)Makefile 0.1 (RGrimes) 4/4/93
+# $FreeBSD$
+
+SUBDIR= tcpdump
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/tcpdump/Makefile.inc b/usr.sbin/tcpdump/Makefile.inc
new file mode 100644
index 0000000..6c4b6e2
--- /dev/null
+++ b/usr.sbin/tcpdump/Makefile.inc
@@ -0,0 +1,6 @@
+# @(#)Makefile.inc 5.1 (Berkeley) 5/11/90
+# $FreeBSD$
+
+BINDIR?= /usr/sbin
+
+WARNS?= 3
diff --git a/usr.sbin/tcpdump/tcpdump/Makefile b/usr.sbin/tcpdump/tcpdump/Makefile
new file mode 100644
index 0000000..d54b9bf
--- /dev/null
+++ b/usr.sbin/tcpdump/tcpdump/Makefile
@@ -0,0 +1,200 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+TCPDUMP_DISTDIR?= ${.CURDIR}/../../../contrib/tcpdump
+.PATH: ${TCPDUMP_DISTDIR}
+
+PROG= tcpdump
+
+SRCS= addrtoname.c \
+ af.c \
+ checksum.c \
+ cpack.c \
+ gmpls.c \
+ gmt2local.c \
+ in_cksum.c \
+ ipproto.c \
+ l2vpn.c \
+ machdep.c \
+ nlpid.c \
+ oui.c \
+ parsenfsfh.c \
+ print-802_11.c \
+ print-802_15_4.c \
+ print-ah.c \
+ print-ahcp.c \
+ print-aodv.c \
+ print-aoe.c \
+ print-ap1394.c \
+ print-arcnet.c \
+ print-arp.c \
+ print-ascii.c \
+ print-atalk.c \
+ print-atm.c \
+ print-beep.c \
+ print-bfd.c \
+ print-bgp.c \
+ print-bootp.c \
+ print-bt.c \
+ print-calm-fast.c \
+ print-carp.c \
+ print-cdp.c \
+ print-cfm.c \
+ print-chdlc.c \
+ print-cip.c \
+ print-cnfp.c \
+ print-dccp.c \
+ print-decnet.c \
+ print-domain.c \
+ print-dtp.c \
+ print-dvmrp.c \
+ print-eap.c \
+ print-egp.c \
+ print-eigrp.c \
+ print-enc.c \
+ print-esp.c \
+ print-ether.c \
+ print-fddi.c \
+ print-forces.c \
+ print-fr.c \
+ print-ftp.c \
+ print-geneve.c \
+ print-geonet.c \
+ print-gre.c \
+ print-hsrp.c \
+ print-http.c \
+ print-icmp.c \
+ print-igmp.c \
+ print-igrp.c \
+ print-ip.c \
+ print-ip6.c \
+ print-ipcomp.c \
+ print-ipfc.c \
+ print-ipnet.c \
+ print-ipx.c \
+ print-isakmp.c \
+ print-isoclns.c \
+ print-juniper.c \
+ print-krb.c \
+ print-l2tp.c \
+ print-lane.c \
+ print-ldp.c \
+ print-llc.c \
+ print-lldp.c \
+ print-lmp.c \
+ print-loopback.c \
+ print-lspping.c \
+ print-lwapp.c \
+ print-lwres.c \
+ print-m3ua.c \
+ print-mobile.c \
+ print-mpcp.c \
+ print-mpls.c \
+ print-mptcp.c \
+ print-msdp.c \
+ print-msnlb.c \
+ print-nfs.c \
+ print-ntp.c \
+ print-null.c \
+ print-olsr.c \
+ print-openflow.c \
+ print-openflow-1.0.c \
+ print-ospf.c \
+ print-otv.c \
+ print-pgm.c \
+ print-pim.c \
+ print-pktap.c \
+ print-ppi.c \
+ print-ppp.c \
+ print-pppoe.c \
+ print-pptp.c \
+ print-radius.c \
+ print-raw.c \
+ print-rip.c \
+ print-rpki-rtr.c \
+ print-rrcp.c \
+ print-rsvp.c \
+ print-rtsp.c \
+ print-rx.c \
+ print-sctp.c \
+ print-sflow.c \
+ print-sip.c \
+ print-sl.c \
+ print-sll.c \
+ print-slow.c \
+ print-smb.c \
+ print-smtp.c \
+ print-snmp.c \
+ print-stp.c \
+ print-sunatm.c \
+ print-sunrpc.c \
+ print-symantec.c \
+ print-syslog.c \
+ print-tcp.c \
+ print-telnet.c \
+ print-tftp.c \
+ print-timed.c \
+ print-tipc.c \
+ print-token.c \
+ print-udld.c \
+ print-udp.c \
+ print-vjc.c \
+ print-vqp.c \
+ print-vrrp.c \
+ print-vtp.c \
+ print-vxlan.c \
+ print-wb.c \
+ print-zephyr.c \
+ print-zeromq.c \
+ setsignal.c \
+ signature.c \
+ smbutil.c \
+ tcpdump.c \
+ util.c \
+ version.c
+CLEANFILES+= version.c
+
+CFLAGS+= -I${.CURDIR} -I${TCPDUMP_DISTDIR}
+CFLAGS+= -DHAVE_CONFIG_H
+CFLAGS+= -D_U_="__attribute__((unused))"
+
+.if ${MK_INET6_SUPPORT} != "no"
+SRCS+= print-babel.c \
+ print-dhcp6.c \
+ print-frag6.c \
+ print-icmp6.c \
+ print-ip6opts.c \
+ print-mobility.c \
+ print-ospf6.c \
+ print-ripng.c \
+ print-rt6.c
+CFLAGS+= -DINET6
+.endif
+.if ${MACHINE_CPUARCH} != "i386"
+CFLAGS+= -DLBL_ALIGN
+.endif
+
+LIBADD= l pcap
+.if ${MK_CASPER} != "no"
+LIBADD+= capsicum
+CFLAGS+=-DHAVE_CAPSICUM
+.endif
+.if ${MK_OPENSSL} != "no"
+LIBADD+= crypto
+CFLAGS+= -I${DESTDIR}/usr/include/openssl
+CFLAGS+= -DHAVE_LIBCRYPTO -DHAVE_OPENSSL_EVP_H
+.endif
+
+.if ${MK_PF} != "no"
+SRCS+= print-pflog.c \
+ print-pfsync.c
+CFLAGS+= -DHAVE_NET_PFVAR_H
+.endif
+
+version.c: ${TCPDUMP_DISTDIR}/VERSION
+ rm -f version.c ; \
+ sed 's/.*/char version[] = "&";/' ${TCPDUMP_DISTDIR}/VERSION \
+ > version.c
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/tcpdump/tcpdump/Makefile.depend b/usr.sbin/tcpdump/tcpdump/Makefile.depend
new file mode 100644
index 0000000..481ce6b
--- /dev/null
+++ b/usr.sbin/tcpdump/tcpdump/Makefile.depend
@@ -0,0 +1,27 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcapsicum \
+ lib/libcompiler_rt \
+ lib/libnv \
+ lib/libpcap \
+ secure/lib/libcrypto \
+ usr.bin/lex/lib \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+version.o: version.c
+version.po: version.c
+.endif
diff --git a/usr.sbin/tcpdump/tcpdump/config.h b/usr.sbin/tcpdump/tcpdump/config.h
new file mode 100644
index 0000000..a3e6f3d
--- /dev/null
+++ b/usr.sbin/tcpdump/tcpdump/config.h
@@ -0,0 +1,406 @@
+/* $FreeBSD$ */
+/* This is an edited copy of the config.h generated by configure. */
+
+/* config.h. Generated from config.h.in by configure. */
+/* config.h.in. Generated from configure.in by autoheader. */
+
+/* define if you have the addrinfo function */
+#define HAVE_ADDRINFO 1
+
+/* Define to 1 if you have the `alarm' function. */
+#define HAVE_ALARM 1
+
+/* Define to 1 if you have the `bpf_dump' function. */
+#define HAVE_BPF_DUMP 1
+
+/* capsicum support available */
+/* See Makefile */
+/* #undef HAVE_CAPSICUM */
+
+/* Define to 1 if you have the `cap_enter' function. */
+#define HAVE_CAP_ENTER 1
+
+/* Define to 1 if you have the `cap_ioctls_limit' function. */
+#define HAVE_CAP_IOCTLS_LIMIT 1
+
+/* Define to 1 if you have the <cap-ng.h> header file. */
+/* #undef HAVE_CAP_NG_H */
+
+/* Define to 1 if you have the `cap_rights_limit' function. */
+#define HAVE_CAP_RIGHTS_LIMIT 1
+
+/* Define to 1 if you have the declaration of `ether_ntohost', and to 0 if you
+ don't. */
+#define HAVE_DECL_ETHER_NTOHOST 1
+
+/* define if you have the dnet_htoa function */
+/* #undef HAVE_DNET_HTOA */
+
+/* Define to 1 if you have the `ether_ntohost' function. */
+#define HAVE_ETHER_NTOHOST 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the `fork' function. */
+#define HAVE_FORK 1
+
+/* Define to 1 if you have the `getnameinfo' function. */
+#define HAVE_GETNAMEINFO 1
+
+/* Define to 1 if you have the `getopt_long' function. */
+#define HAVE_GETOPT_LONG 1
+
+/* define if you have getrpcbynumber() */
+#define HAVE_GETRPCBYNUMBER 1
+
+/* define if you have the h_errno variable */
+#define HAVE_H_ERRNO 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `cap-ng' library (-lcap-ng). */
+/* #undef HAVE_LIBCAP_NG */
+
+/* Define to 1 if you have the `crypto' library (-lcrypto). */
+/* See Makefile */
+/* #undef HAVE_LIBCRYPTO */
+
+/* Define to 1 if you have the `rpc' library (-lrpc). */
+/* #undef HAVE_LIBRPC */
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the <netdnet/dnetdb.h> header file. */
+/* #undef HAVE_NETDNET_DNETDB_H */
+
+/* define if you have a dnet_htoa declaration in <netdnet/dnetdb.h> */
+/* #undef HAVE_NETDNET_DNETDB_H_DNET_HTOA */
+
+/* Define to 1 if you have the <netinet/ether.h> header file. */
+/* #undef HAVE_NETINET_ETHER_H */
+
+/* Define to 1 if you have the <netinet/if_ether.h> header file. */
+#define HAVE_NETINET_IF_ETHER_H 1
+
+/* Define to 1 if you have the <net/pfvar.h> header file. */
+/* See Makefile */
+/* #undef HAVE_NET_PFVAR_H */
+
+/* Define to 1 if you have the `openat' function. */
+#define HAVE_OPENAT 1
+
+/* Define to 1 if you have the <openssl/evp.h> header file. */
+/* See Makefile */
+/* #undef HAVE_OPENSSL_EVP_H 1 */
+
+/* if there's an os_proto.h for this platform, to use additional prototypes */
+/* #undef HAVE_OS_PROTO_H */
+
+/* Define to 1 if you have the <pcap/bluetooth.h> header file. */
+/* #undef HAVE_PCAP_BLUETOOTH_H */
+
+/* Define to 1 if you have the `pcap_breakloop' function. */
+#define HAVE_PCAP_BREAKLOOP 1
+
+/* Define to 1 if you have the `pcap_create' function. */
+#define HAVE_PCAP_CREATE 1
+
+/* define if libpcap has pcap_datalink_name_to_val() */
+#define HAVE_PCAP_DATALINK_NAME_TO_VAL 1
+
+/* define if libpcap has pcap_datalink_val_to_description() */
+#define HAVE_PCAP_DATALINK_VAL_TO_DESCRIPTION 1
+
+/* define if libpcap has pcap_debug */
+/* #undef HAVE_PCAP_DEBUG */
+
+/* Define to 1 if you have the `pcap_dump_flush' function. */
+#define HAVE_PCAP_DUMP_FLUSH 1
+
+/* define if libpcap has pcap_dump_ftell() */
+#define HAVE_PCAP_DUMP_FTELL 1
+
+/* Define to 1 if you have the `pcap_findalldevs' function. */
+#define HAVE_PCAP_FINDALLDEVS 1
+
+/* Define to 1 if you have the `pcap_free_datalinks' function. */
+#define HAVE_PCAP_FREE_DATALINKS 1
+
+/* Define to 1 if the system has the type `pcap_if_t'. */
+#define HAVE_PCAP_IF_T 1
+
+/* Define to 1 if you have the `pcap_lib_version' function. */
+#define HAVE_PCAP_LIB_VERSION 1
+
+/* define if libpcap has pcap_list_datalinks() */
+#define HAVE_PCAP_LIST_DATALINKS 1
+
+/* Define to 1 if you have the <pcap/nflog.h> header file. */
+/* #undef HAVE_PCAP_NFLOG_H */
+
+/* Define to 1 if you have the `pcap_setdirection' function. */
+#define HAVE_PCAP_SETDIRECTION 1
+
+/* Define to 1 if you have the `pcap_set_datalink' function. */
+#define HAVE_PCAP_SET_DATALINK 1
+
+/* Define to 1 if you have the `pcap_set_immediate_mode' function. */
+#define HAVE_PCAP_SET_IMMEDIATE_MODE 1
+
+/* Define to 1 if you have the `pcap_set_tstamp_precision' function. */
+#define HAVE_PCAP_SET_TSTAMP_PRECISION 1
+
+/* Define to 1 if you have the `pcap_set_tstamp_type' function. */
+#define HAVE_PCAP_SET_TSTAMP_TYPE 1
+
+/* Define to 1 if you have the <pcap/usb.h> header file. */
+/* #undef HAVE_PCAP_USB_H */
+
+/* define if libpcap has pcap_version */
+/* #undef HAVE_PCAP_VERSION */
+
+/* Define to 1 if you have the `pfopen' function. */
+/* #undef HAVE_PFOPEN */
+
+/* Define to 1 if you have the <rpc/rpcent.h> header file. */
+#define HAVE_RPC_RPCENT_H 1
+
+/* Define to 1 if you have the <rpc/rpc.h> header file. */
+#define HAVE_RPC_RPC_H 1
+
+/* Define to 1 if you have the `setlinebuf' function. */
+#define HAVE_SETLINEBUF 1
+
+/* Define to 1 if you have the `sigaction' function. */
+#define HAVE_SIGACTION 1
+
+/* Define to 1 if you have the `sigset' function. */
+/* #undef HAVE_SIGSET */
+
+/* Define to 1 if you have the `snprintf' function. */
+#define HAVE_SNPRINTF 1
+
+/* if struct sockaddr has the sa_len member */
+#define HAVE_SOCKADDR_SA_LEN 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the `strcasecmp' function. */
+#define HAVE_STRCASECMP 1
+
+/* Define to 1 if you have the `strdup' function. */
+#define HAVE_STRDUP 1
+
+/* Define to 1 if you have the `strftime' function. */
+#define HAVE_STRFTIME 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strlcat' function. */
+#define HAVE_STRLCAT 1
+
+/* Define to 1 if you have the `strlcpy' function. */
+#define HAVE_STRLCPY 1
+
+/* Define to 1 if you have the `strsep' function. */
+#define HAVE_STRSEP 1
+
+/* Define to 1 if the system has the type `struct ether_addr'. */
+/* #undef HAVE_STRUCT_ETHER_ADDR */
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if the system has the type `uintptr_t'. */
+#define HAVE_UINTPTR_T 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the `vfork' function. */
+#define HAVE_VFORK 1
+
+/* Define to 1 if you have the `vfprintf' function. */
+#define HAVE_VFPRINTF 1
+
+/* Define to 1 if you have the `vsnprintf' function. */
+#define HAVE_VSNPRINTF 1
+
+/* define if libpcap has yydebug */
+/* #undef HAVE_YYDEBUG */
+
+/* define if your compiler has __attribute__ */
+#define HAVE___ATTRIBUTE__ 1
+
+/* Define if you enable IPv6 support */
+/* See Makefile */
+/* #undef INET6 */
+
+/* if unaligned access fails */
+/* #undef LBL_ALIGN */
+
+/* define if you need to include missing/addrinfo.h */
+/* #undef NEED_ADDRINFO_H */
+
+/* Define to 1 if netinet/ether.h declares `ether_ntohost' */
+/* #undef NETINET_ETHER_H_DECLARES_ETHER_NTOHOST */
+
+/* Define to 1 if netinet/if_ether.h declares `ether_ntohost' */
+#define NETINET_IF_ETHER_H_DECLARES_ETHER_NTOHOST /**/
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME ""
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING ""
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME ""
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION ""
+
+/* define if the platform doesn't define PRId64 */
+/* #undef PRId64 */
+
+/* define if the platform doesn't define PRIo64 */
+/* #undef PRIo64 */
+
+/* define if the platform doesn't define PRIx64 */
+/* #undef PRIu64 */
+
+/* define if the platform doesn't define PRIu64 */
+/* #undef PRIx64 */
+
+/* Define as the return type of signal handlers (`int' or `void'). */
+#define RETSIGTYPE void
+
+/* return value of signal handlers */
+#define RETSIGVAL /**/
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* define if you want to build the possibly-buggy SMB printer */
+#define TCPDUMP_DO_SMB 1
+
+/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
+#define TIME_WITH_SYS_TIME 1
+
+/* define if you have ether_ntohost() and it works */
+#define USE_ETHER_NTOHOST 1
+
+/* Define if you enable support for libsmi */
+/* #undef USE_LIBSMI */
+
+/* define if should chroot when dropping privileges */
+/* #undef WITH_CHROOT */
+
+/* define if should drop privileges by default */
+/* #undef WITH_USER */
+
+/* get BSD semantics on Irix */
+/* #undef _BSD_SIGNALS */
+
+/* define on AIX to get certain functions */
+/* #undef _SUN */
+
+/* Define for Solaris 2.5.1 so the uint32_t typedef from <sys/synch.h>,
+ <pthread.h>, or <semaphore.h> is not used. If the typedef were allowed, the
+ #define below would cause a syntax error. */
+/* #undef _UINT32_T */
+
+/* Define for Solaris 2.5.1 so the uint64_t typedef from <sys/synch.h>,
+ <pthread.h>, or <semaphore.h> is not used. If the typedef were allowed, the
+ #define below would cause a syntax error. */
+/* #undef _UINT64_T */
+
+/* Define for Solaris 2.5.1 so the uint8_t typedef from <sys/synch.h>,
+ <pthread.h>, or <semaphore.h> is not used. If the typedef were allowed, the
+ #define below would cause a syntax error. */
+/* #undef _UINT8_T */
+
+/* define if your compiler allows __attribute__((format)) without a warning */
+#define __ATTRIBUTE___FORMAT_OK 1
+
+/* define if your compiler allows __attribute__((format)) to be applied to
+ function pointers */
+#define __ATTRIBUTE___FORMAT_OK_FOR_FUNCTION_POINTERS 1
+
+/* define if your compiler allows __attribute__((noreturn)) to be applied to
+ function pointers */
+#define __ATTRIBUTE___NORETURN_OK_FOR_FUNCTION_POINTERS 1
+
+/* to handle Ultrix compilers that don't support const in prototypes */
+/* #undef const */
+
+/* Define as token for inline if inlining supported */
+#define inline inline
+
+/* Define to the type of a signed integer type of width exactly 16 bits if
+ such a type exists and the standard includes do not define it. */
+/* #undef int16_t */
+
+/* Define to the type of a signed integer type of width exactly 32 bits if
+ such a type exists and the standard includes do not define it. */
+/* #undef int32_t */
+
+/* Define to the type of a signed integer type of width exactly 64 bits if
+ such a type exists and the standard includes do not define it. */
+/* #undef int64_t */
+
+/* Define to the type of a signed integer type of width exactly 8 bits if such
+ a type exists and the standard includes do not define it. */
+/* #undef int8_t */
+
+/* Define to `uint16_t' if u_int16_t not defined. */
+/* #undef u_int16_t */
+
+/* Define to `uint32_t' if u_int32_t not defined. */
+/* #undef u_int32_t */
+
+/* Define to `uint64_t' if u_int64_t not defined. */
+/* #undef u_int64_t */
+
+/* Define to `uint8_t' if u_int8_t not defined. */
+/* #undef u_int8_t */
+
+/* Define to the type of an unsigned integer type of width exactly 16 bits if
+ such a type exists and the standard includes do not define it. */
+/* #undef uint16_t */
+
+/* Define to the type of an unsigned integer type of width exactly 32 bits if
+ such a type exists and the standard includes do not define it. */
+/* #undef uint32_t */
+
+/* Define to the type of an unsigned integer type of width exactly 64 bits if
+ such a type exists and the standard includes do not define it. */
+/* #undef uint64_t */
+
+/* Define to the type of an unsigned integer type of width exactly 8 bits if
+ such a type exists and the standard includes do not define it. */
+/* #undef uint8_t */
+
+/* Define to the type of an unsigned integer type wide enough to hold a
+ pointer, if such a type exists, and if the system does not define it. */
+/* #undef uintptr_t */
diff --git a/usr.sbin/tcpdump/tcpdump/tcpdump.1 b/usr.sbin/tcpdump/tcpdump/tcpdump.1
new file mode 100644
index 0000000..2a2388c
--- /dev/null
+++ b/usr.sbin/tcpdump/tcpdump/tcpdump.1
@@ -0,0 +1,1977 @@
+.\" $FreeBSD$
+.\" $NetBSD: tcpdump.8,v 1.9 2003/03/31 00:18:17 perry Exp $
+.\"
+.\" Copyright (c) 1987, 1988, 1989, 1990, 1991, 1992, 1994, 1995, 1996, 1997
+.\" The Regents of the University of California. All rights reserved.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that: (1) source code distributions
+.\" retain the above copyright notice and this paragraph in its entirety, (2)
+.\" distributions including binary code include the above copyright notice and
+.\" this paragraph in its entirety in the documentation or other materials
+.\" provided with the distribution, and (3) all advertising materials mentioning
+.\" features or use of this software display the following acknowledgement:
+.\" ``This product includes software developed by the University of California,
+.\" Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
+.\" the University nor the names of its contributors may be used to endorse
+.\" or promote products derived from this software without specific prior
+.\" written permission.
+.\" THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
+.\" WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+.\"
+.TH TCPDUMP 1 "11 July 2014"
+.SH NAME
+tcpdump \- dump traffic on a network
+.SH SYNOPSIS
+.na
+.B tcpdump
+[
+.B \-AbdDefhHIJKlLnNOpqRStuUvxX#
+] [
+.B \-B
+.I buffer_size
+]
+.br
+.ti +8
+[
+.B \-c
+.I count
+]
+.br
+.ti +8
+[
+.B \-C
+.I file_size
+] [
+.B \-G
+.I rotate_seconds
+] [
+.B \-F
+.I file
+]
+.br
+.ti +8
+[
+.B \-i
+.I interface
+]
+[
+.B \-j
+.I tstamp_type
+]
+[
+.B \-m
+.I module
+]
+[
+.B \-M
+.I secret
+]
+.br
+.ti +8
+[
+.B \-\-number
+]
+[
+.B \-Q
+.I in|out|inout
+]
+.ti +8
+[
+.B \-r
+.I file
+]
+[
+.B \-V
+.I file
+]
+[
+.B \-s
+.I snaplen
+]
+[
+.B \-T
+.I type
+]
+[
+.B \-w
+.I file
+]
+.br
+.ti +8
+[
+.B \-W
+.I filecount
+]
+.br
+.ti +8
+[
+.B \-E
+.I spi@ipaddr algo:secret,...
+]
+.br
+.ti +8
+[
+.B \-y
+.I datalinktype
+]
+[
+.B \-z
+.I postrotate-command
+]
+[
+.B \-Z
+.I user
+]
+.ti +8
+[
+.BI \-\-time\-stamp\-precision= tstamp_precision
+]
+.ti +8
+[
+.B \-\-immediate\-mode
+]
+[
+.B \-\-version
+]
+.ti +8
+[
+.I expression
+]
+.br
+.ad
+.SH DESCRIPTION
+.LP
+\fITcpdump\fP prints out a description of the contents of packets on a
+network interface that match the boolean \fIexpression\fP; the
+description is preceded by a time stamp, printed, by default, as hours,
+minutes, seconds, and fractions of a second since midnight. It can also
+be run with the
+.B \-w
+flag, which causes it to save the packet data to a file for later
+analysis, and/or with the
+.B \-r
+flag, which causes it to read from a saved packet file rather than to
+read packets from a network interface. It can also be run with the
+.B \-V
+flag, which causes it to read a list of saved packet files. In all cases,
+only packets that match
+.I expression
+will be processed by
+.IR tcpdump .
+.LP
+.I Tcpdump
+will, if not run with the
+.B \-c
+flag, continue capturing packets until it is interrupted by a SIGINT
+signal (generated, for example, by typing your interrupt character,
+typically control-C) or a SIGTERM signal (typically generated with the
+.BR kill (1)
+command); if run with the
+.B \-c
+flag, it will capture packets until it is interrupted by a SIGINT or
+SIGTERM signal or the specified number of packets have been processed.
+.LP
+When
+.I tcpdump
+finishes capturing packets, it will report counts of:
+.IP
+packets ``captured'' (this is the number of packets that
+.I tcpdump
+has received and processed);
+.IP
+packets ``received by filter'' (the meaning of this depends on the OS on
+which you're running
+.IR tcpdump ,
+and possibly on the way the OS was configured - if a filter was
+specified on the command line, on some OSes it counts packets regardless
+of whether they were matched by the filter expression and, even if they
+were matched by the filter expression, regardless of whether
+.I tcpdump
+has read and processed them yet, on other OSes it counts only packets that were
+matched by the filter expression regardless of whether
+.I tcpdump
+has read and processed them yet, and on other OSes it counts only
+packets that were matched by the filter expression and were processed by
+.IR tcpdump );
+.IP
+packets ``dropped by kernel'' (this is the number of packets that were
+dropped, due to a lack of buffer space, by the packet capture mechanism
+in the OS on which
+.I tcpdump
+is running, if the OS reports that information to applications; if not,
+it will be reported as 0).
+.LP
+On platforms that support the SIGINFO signal, such as most BSDs
+(including Mac OS X) and Digital/Tru64 UNIX, it will report those counts
+when it receives a SIGINFO signal (generated, for example, by typing
+your ``status'' character, typically control-T, although on some
+platforms, such as Mac OS X, the ``status'' character is not set by
+default, so you must set it with
+.BR stty (1)
+in order to use it) and will continue capturing packets. On platforms that
+do not support the SIGINFO signal, the same can be achieved by using the
+SIGUSR1 signal.
+.LP
+Reading packets from a network interface may require that you have
+special privileges; see the
+.B pcap (3PCAP)
+man page for details. Reading a saved packet file doesn't require
+special privileges.
+.SH OPTIONS
+.TP
+.B \-A
+Print each packet (minus its link level header) in ASCII. Handy for
+capturing web pages.
+.TP
+.B \-b
+Print the AS number in BGP packets in ASDOT notation rather than ASPLAIN
+notation.
+.TP
+.BI \-B " buffer_size"
+.PD 0
+.TP
+.BI \-\-buffer\-size= buffer_size
+.PD
+Set the operating system capture buffer size to \fIbuffer_size\fP, in
+units of KiB (1024 bytes).
+.TP
+.BI \-c " count"
+Exit after receiving \fIcount\fP packets.
+.TP
+.BI \-C " file_size"
+Before writing a raw packet to a savefile, check whether the file is
+currently larger than \fIfile_size\fP and, if so, close the current
+savefile and open a new one. Savefiles after the first savefile will
+have the name specified with the
+.B \-w
+flag, with a number after it, starting at 1 and continuing upward.
+The units of \fIfile_size\fP are millions of bytes (1,000,000 bytes,
+not 1,048,576 bytes).
+.TP
+.B \-d
+Dump the compiled packet-matching code in a human readable form to
+standard output and stop.
+.TP
+.B \-dd
+Dump packet-matching code as a
+.B C
+program fragment.
+.TP
+.B \-ddd
+Dump packet-matching code as decimal numbers (preceded with a count).
+.TP
+.B \-D
+.PD 0
+.TP
+.B \-\-list\-interfaces
+.PD
+Print the list of the network interfaces available on the system and on
+which
+.I tcpdump
+can capture packets. For each network interface, a number and an
+interface name, possibly followed by a text description of the
+interface, is printed. The interface name or the number can be supplied
+to the
+.B \-i
+flag to specify an interface on which to capture.
+.IP
+This can be useful on systems that don't have a command to list them
+(e.g., Windows systems, or UNIX systems lacking
+.BR "ifconfig \-a" );
+the number can be useful on Windows 2000 and later systems, where the
+interface name is a somewhat complex string.
+.IP
+The
+.B \-D
+flag will not be supported if
+.I tcpdump
+was built with an older version of
+.I libpcap
+that lacks the
+.B pcap_findalldevs()
+function.
+.TP
+.B \-e
+Print the link-level header on each dump line. This can be used, for
+example, to print MAC layer addresses for protocols such as Ethernet and
+IEEE 802.11.
+.TP
+.B \-E
+Use \fIspi@ipaddr algo:secret\fP for decrypting IPsec ESP packets that
+are addressed to \fIaddr\fP and contain Security Parameter Index value
+\fIspi\fP. This combination may be repeated with comma or newline separation.
+.IP
+Note that setting the secret for IPv4 ESP packets is supported at this time.
+.IP
+Algorithms may be
+\fBdes-cbc\fP,
+\fB3des-cbc\fP,
+\fBblowfish-cbc\fP,
+\fBrc3-cbc\fP,
+\fBcast128-cbc\fP, or
+\fBnone\fP.
+The default is \fBdes-cbc\fP.
+The ability to decrypt packets is only present if \fItcpdump\fP was compiled
+with cryptography enabled.
+.IP
+\fIsecret\fP is the ASCII text for ESP secret key.
+If preceded by 0x, then a hex value will be read.
+.IP
+The option assumes RFC2406 ESP, not RFC1827 ESP.
+The option is only for debugging purposes, and
+the use of this option with a true `secret' key is discouraged.
+By presenting IPsec secret key onto command line
+you make it visible to others, via
+.IR ps (1)
+and other occasions.
+.IP
+In addition to the above syntax, the syntax \fIfile name\fP may be used
+to have tcpdump read the provided file in. The file is opened upon
+receiving the first ESP packet, so any special permissions that tcpdump
+may have been given should already have been given up.
+.TP
+.B \-f
+Print `foreign' IPv4 addresses numerically rather than symbolically
+(this option is intended to get around serious brain damage in
+Sun's NIS server \(em usually it hangs forever translating non-local
+internet numbers).
+.IP
+The test for `foreign' IPv4 addresses is done using the IPv4 address and
+netmask of the interface on which capture is being done. If that
+address or netmask are not available, available, either because the
+interface on which capture is being done has no address or netmask or
+because the capture is being done on the Linux "any" interface, which
+can capture on more than one interface, this option will not work
+correctly.
+.TP
+.BI \-F " file"
+Use \fIfile\fP as input for the filter expression.
+An additional expression given on the command line is ignored.
+.TP
+.BI \-G " rotate_seconds"
+If specified, rotates the dump file specified with the
+.B \-w
+option every \fIrotate_seconds\fP seconds.
+Savefiles will have the name specified by
+.B \-w
+which should include a time format as defined by
+.BR strftime (3).
+If no time format is specified, each new file will overwrite the previous.
+.IP
+If used in conjunction with the
+.B \-C
+option, filenames will take the form of `\fIfile\fP<count>'.
+.TP
+.B \-h
+.PD 0
+.TP
+.B \-\-help
+.PD
+Print the tcpdump and libpcap version strings, print a usage message,
+and exit.
+.TP
+.B \-\-version
+.PD
+Print the tcpdump and libpcap version strings and exit.
+.TP
+.B \-H
+Attempt to detect 802.11s draft mesh headers.
+.TP
+.BI \-i " interface"
+.PD 0
+.TP
+.BI \-\-interface= interface
+.PD
+Listen on \fIinterface\fP.
+If unspecified, \fItcpdump\fP searches the system interface list for the
+lowest numbered, configured up interface (excluding loopback), which may turn
+out to be, for example, ``eth0''.
+.IP
+On Linux systems with 2.2 or later kernels, an
+.I interface
+argument of ``any'' can be used to capture packets from all interfaces.
+Note that captures on the ``any'' device will not be done in promiscuous
+mode.
+.IP
+If the
+.B \-D
+flag is supported, an interface number as printed by that flag can be
+used as the
+.I interface
+argument.
+.TP
+.B \-I
+.PD 0
+.TP
+.B \-\-monitor\-mode
+.PD
+Put the interface in "monitor mode"; this is supported only on IEEE
+802.11 Wi-Fi interfaces, and supported only on some operating systems.
+.IP
+Note that in monitor mode the adapter might disassociate from the
+network with which it's associated, so that you will not be able to use
+any wireless networks with that adapter. This could prevent accessing
+files on a network server, or resolving host names or network addresses,
+if you are capturing in monitor mode and are not connected to another
+network with another adapter.
+.IP
+This flag will affect the output of the
+.B \-L
+flag. If
+.B \-I
+isn't specified, only those link-layer types available when not in
+monitor mode will be shown; if
+.B \-I
+is specified, only those link-layer types available when in monitor mode
+will be shown.
+.TP
+.BI \-\-immediate\-mode
+Capture in "immediate mode". In this mode, packets are delivered to
+tcpdump as soon as they arrive, rather than being buffered for
+efficiency. This is the default when printing packets rather than
+saving packets to a ``savefile'' if the packets are being printed to a
+terminal rather than to a file or pipe.
+.TP
+.BI \-j " tstamp_type"
+.PD 0
+.TP
+.BI \-\-time\-stamp\-type= tstamp_type
+.PD
+Set the time stamp type for the capture to \fItstamp_type\fP. The names
+to use for the time stamp types are given in
+.BR pcap-tstamp (7);
+not all the types listed there will necessarily be valid for any given
+interface.
+.TP
+.B \-J
+.PD 0
+.TP
+.B \-\-list\-time\-stamp\-types
+.PD
+List the supported time stamp types for the interface and exit. If the
+time stamp type cannot be set for the interface, no time stamp types are
+listed.
+.TP
+.BI \-\-time\-stamp\-precision= tstamp_precision
+When capturing, set the time stamp precision for the capture to
+\fItstamp_precision\fP. Note that availability of high precision time
+stamps (nanoseconds) and their actual accuracy is platform and hardware
+dependent. Also note that when writing captures made with nanosecond
+accuracy to a savefile, the time stamps are written with nanosecond
+resolution, and the file is written with a different magic number, to
+indicate that the time stamps are in seconds and nanoseconds; not all
+programs that read pcap savefiles will be able to read those captures.
+.LP
+When reading a savefile, convert time stamps to the precision specified
+by \fItimestamp_precision\fP, and display them with that resolution. If
+the precision specified is less than the precision of time stamps in the
+file, the conversion will lose precision.
+.LP
+The supported values for \fItimestamp_precision\fP are \fBmicro\fP for
+microsecond resolution and \fBnano\fP for nanosecond resolution. The
+default is microsecond resolution.
+.TP
+.B \-K
+.PD 0
+.TP
+.B \-\-dont\-verify\-checksums
+.PD
+Don't attempt to verify IP, TCP, or UDP checksums. This is useful for
+interfaces that perform some or all of those checksum calculation in
+hardware; otherwise, all outgoing TCP checksums will be flagged as bad.
+.TP
+.B \-l
+Make stdout line buffered.
+Useful if you want to see the data
+while capturing it.
+E.g.,
+.IP
+.RS
+.RS
+.nf
+\fBtcpdump \-l | tee dat\fP
+.fi
+.RE
+.RE
+.IP
+or
+.IP
+.RS
+.RS
+.nf
+\fBtcpdump \-l > dat & tail \-f dat\fP
+.fi
+.RE
+.RE
+.IP
+Note that on Windows,``line buffered'' means ``unbuffered'', so that
+WinDump will write each character individually if
+.B \-l
+is specified.
+.IP
+.B \-U
+is similar to
+.B \-l
+in its behavior, but it will cause output to be ``packet-buffered'', so
+that the output is written to stdout at the end of each packet rather
+than at the end of each line; this is buffered on all platforms,
+including Windows.
+.TP
+.B \-L
+.PD 0
+.TP
+.B \-\-list\-data\-link\-types
+.PD
+List the known data link types for the interface, in the specified mode,
+and exit. The list of known data link types may be dependent on the
+specified mode; for example, on some platforms, a Wi-Fi interface might
+support one set of data link types when not in monitor mode (for
+example, it might support only fake Ethernet headers, or might support
+802.11 headers but not support 802.11 headers with radio information)
+and another set of data link types when in monitor mode (for example, it
+might support 802.11 headers, or 802.11 headers with radio information,
+only in monitor mode).
+.TP
+.BI \-m " module"
+Load SMI MIB module definitions from file \fImodule\fR.
+This option
+can be used several times to load several MIB modules into \fItcpdump\fP.
+.TP
+.BI \-M " secret"
+Use \fIsecret\fP as a shared secret for validating the digests found in
+TCP segments with the TCP-MD5 option (RFC 2385), if present.
+.TP
+.B \-n
+Don't convert addresses (i.e., host addresses, port numbers, etc.) to names.
+.TP
+.B \-N
+Don't print domain name qualification of host names.
+E.g.,
+if you give this flag then \fItcpdump\fP will print ``nic''
+instead of ``nic.ddn.mil''.
+.TP
+.B \-#
+.PD 0
+.TP
+.B \-\-number
+.PD
+Print an optional packet number at the beginning of the line.
+.TP
+.B \-O
+.PD 0
+.TP
+.B \-\-no\-optimize
+.PD
+Do not run the packet-matching code optimizer.
+This is useful only
+if you suspect a bug in the optimizer.
+.TP
+.B \-p
+.PD 0
+.TP
+.B \-\-no\-promiscuous\-mode
+.PD
+\fIDon't\fP put the interface
+into promiscuous mode.
+Note that the interface might be in promiscuous
+mode for some other reason; hence, `-p' cannot be used as an abbreviation for
+`ether host {local-hw-addr} or ether broadcast'.
+.TP
+.BI \-Q " direction"
+.PD 0
+.TP
+.BI \-\-direction= direction
+.PD
+Choose send/receive direction \fIdirection\fR for which packets should be
+captured. Possible values are `in', `out' and `inout'. Not available
+on all platforms.
+.TP
+.B \-q
+Quick (quiet?) output.
+Print less protocol information so output
+lines are shorter.
+.TP
+.B \-R
+Assume ESP/AH packets to be based on old specification (RFC1825 to RFC1829).
+If specified, \fItcpdump\fP will not print replay prevention field.
+Since there is no protocol version field in ESP/AH specification,
+\fItcpdump\fP cannot deduce the version of ESP/AH protocol.
+.TP
+.BI \-r " file"
+Read packets from \fIfile\fR (which was created with the
+.B \-w
+option or by other tools that write pcap or pcap-ng files).
+Standard input is used if \fIfile\fR is ``-''.
+.TP
+.B \-S
+.PD 0
+.TP
+.B \-\-absolute\-tcp\-sequence\-numbers
+.PD
+Print absolute, rather than relative, TCP sequence numbers.
+.TP
+.BI \-s " snaplen"
+.PD 0
+.TP
+.BI \-\-snapshot\-length= snaplen
+.PD
+Snarf \fIsnaplen\fP bytes of data from each packet rather than the
+default of 65535 bytes.
+Packets truncated because of a limited snapshot
+are indicated in the output with ``[|\fIproto\fP]'', where \fIproto\fP
+is the name of the protocol level at which the truncation has occurred.
+Note that taking larger snapshots both increases
+the amount of time it takes to process packets and, effectively,
+decreases the amount of packet buffering.
+This may cause packets to be
+lost.
+You should limit \fIsnaplen\fP to the smallest number that will
+capture the protocol information you're interested in.
+Setting
+\fIsnaplen\fP to 0 sets it to the default of 65535,
+for backwards compatibility with recent older versions of
+.IR tcpdump .
+.TP
+.BI \-T " type"
+Force packets selected by "\fIexpression\fP" to be interpreted the
+specified \fItype\fR.
+Currently known types are
+\fBaodv\fR (Ad-hoc On-demand Distance Vector protocol),
+\fBcarp\fR (Common Address Redundancy Protocol),
+\fBcnfp\fR (Cisco NetFlow protocol),
+\fBlmp\fR (Link Management Protocol),
+\fBpgm\fR (Pragmatic General Multicast),
+\fBpgm_zmtp1\fR (ZMTP/1.0 inside PGM/EPGM),
+\fBradius\fR (RADIUS),
+\fBrpc\fR (Remote Procedure Call),
+\fBrtp\fR (Real-Time Applications protocol),
+\fBrtcp\fR (Real-Time Applications control protocol),
+\fBsnmp\fR (Simple Network Management Protocol),
+\fBtftp\fR (Trivial File Transfer Protocol),
+\fBvat\fR (Visual Audio Tool),
+\fBwb\fR (distributed White Board),
+\fBzmtp1\fR (ZeroMQ Message Transport Protocol 1.0)
+and
+\fBvxlan\fR (Virtual eXtensible Local Area Network).
+.IP
+Note that the \fBpgm\fR type above affects UDP interpretation only, the native
+PGM is always recognised as IP protocol 113 regardless. UDP-encapsulated PGM is
+often called "EPGM" or "PGM/UDP".
+.IP
+Note that the \fBpgm_zmtp1\fR type above affects interpretation of both native
+PGM and UDP at once. During the native PGM decoding the application data of an
+ODATA/RDATA packet would be decoded as a ZeroMQ datagram with ZMTP/1.0 frames.
+During the UDP decoding in addition to that any UDP packet would be treated as
+an encapsulated PGM packet.
+.TP
+.B \-t
+\fIDon't\fP print a timestamp on each dump line.
+.TP
+.B \-tt
+Print the timestamp, as seconds since January 1, 1970, 00:00:00, UTC, and
+fractions of a second since that time, on each dump line.
+.TP
+.B \-ttt
+Print a delta (micro-second resolution) between current and previous line
+on each dump line.
+.TP
+.B \-tttt
+Print a timestamp, as hours, minutes, seconds, and fractions of a second
+since midnight, preceded by the date, on each dump line.
+.TP
+.B \-ttttt
+Print a delta (micro-second resolution) between current and first line
+on each dump line.
+.TP
+.B \-u
+Print undecoded NFS handles.
+.TP
+.B \-U
+.PD 0
+.TP
+.B \-\-packet\-buffered
+.PD
+If the
+.B \-w
+option is not specified, make the printed packet output
+``packet-buffered''; i.e., as the description of the contents of each
+packet is printed, it will be written to the standard output, rather
+than, when not writing to a terminal, being written only when the output
+buffer fills.
+.IP
+If the
+.B \-w
+option is specified, make the saved raw packet output
+``packet-buffered''; i.e., as each packet is saved, it will be written
+to the output file, rather than being written only when the output
+buffer fills.
+.IP
+The
+.B \-U
+flag will not be supported if
+.I tcpdump
+was built with an older version of
+.I libpcap
+that lacks the
+.B pcap_dump_flush()
+function.
+.TP
+.B \-v
+When parsing and printing, produce (slightly more) verbose output.
+For example, the time to live,
+identification, total length and options in an IP packet are printed.
+Also enables additional packet integrity checks such as verifying the
+IP and ICMP header checksum.
+.IP
+When writing to a file with the
+.B \-w
+option, report, every 10 seconds, the number of packets captured.
+.TP
+.B \-vv
+Even more verbose output.
+For example, additional fields are
+printed from NFS reply packets, and SMB packets are fully decoded.
+.TP
+.B \-vvv
+Even more verbose output.
+For example,
+telnet \fBSB\fP ... \fBSE\fP options
+are printed in full.
+With
+.B \-X
+Telnet options are printed in hex as well.
+.TP
+.BI \-V " file"
+Read a list of filenames from \fIfile\fR. Standard input is used
+if \fIfile\fR is ``-''.
+.TP
+.BI \-w " file"
+Write the raw packets to \fIfile\fR rather than parsing and printing
+them out.
+They can later be printed with the \-r option.
+Standard output is used if \fIfile\fR is ``-''.
+.IP
+This output will be buffered if written to a file or pipe, so a program
+reading from the file or pipe may not see packets for an arbitrary
+amount of time after they are received. Use the
+.B \-U
+flag to cause packets to be written as soon as they are received.
+.IP
+The MIME type \fIapplication/vnd.tcpdump.pcap\fP has been registered
+with IANA for \fIpcap\fP files. The filename extension \fI.pcap\fP
+appears to be the most commonly used along with \fI.cap\fP and
+\fI.dmp\fP. \fITcpdump\fP itself doesn't check the extension when
+reading capture files and doesn't add an extension when writing them
+(it uses magic numbers in the file header instead). However, many
+operating systems and applications will use the extension if it is
+present and adding one (e.g. .pcap) is recommended.
+.IP
+See
+.BR pcap-savefile (5)
+for a description of the file format.
+.TP
+.B \-W
+Used in conjunction with the
+.B \-C
+option, this will limit the number
+of files created to the specified number, and begin overwriting files
+from the beginning, thus creating a 'rotating' buffer.
+In addition, it will name
+the files with enough leading 0s to support the maximum number of
+files, allowing them to sort correctly.
+.IP
+Used in conjunction with the
+.B \-G
+option, this will limit the number of rotated dump files that get
+created, exiting with status 0 when reaching the limit. If used with
+.B \-C
+as well, the behavior will result in cyclical files per timeslice.
+.TP
+.B \-x
+When parsing and printing,
+in addition to printing the headers of each packet, print the data of
+each packet (minus its link level header) in hex.
+The smaller of the entire packet or
+.I snaplen
+bytes will be printed. Note that this is the entire link-layer
+packet, so for link layers that pad (e.g. Ethernet), the padding bytes
+will also be printed when the higher layer packet is shorter than the
+required padding.
+.TP
+.B \-xx
+When parsing and printing,
+in addition to printing the headers of each packet, print the data of
+each packet,
+.I including
+its link level header, in hex.
+.TP
+.B \-X
+When parsing and printing,
+in addition to printing the headers of each packet, print the data of
+each packet (minus its link level header) in hex and ASCII.
+This is very handy for analysing new protocols.
+.TP
+.B \-XX
+When parsing and printing,
+in addition to printing the headers of each packet, print the data of
+each packet,
+.I including
+its link level header, in hex and ASCII.
+.TP
+.BI \-y " datalinktype"
+.PD 0
+.TP
+.BI \-\-linktype= datalinktype
+.PD
+Set the data link type to use while capturing packets to \fIdatalinktype\fP.
+.TP
+.BI \-z " postrotate-command"
+Used in conjunction with the
+.B -C
+or
+.B -G
+options, this will make
+.I tcpdump
+run "
+.I postrotate-command file
+" where
+.I file
+is the savefile being closed after each rotation. For example, specifying
+.B \-z gzip
+or
+.B \-z bzip2
+will compress each savefile using gzip or bzip2.
+.IP
+Note that tcpdump will run the command in parallel to the capture, using
+the lowest priority so that this doesn't disturb the capture process.
+.IP
+And in case you would like to use a command that itself takes flags or
+different arguments, you can always write a shell script that will take the
+savefile name as the only argument, make the flags & arguments arrangements
+and execute the command that you want.
+.TP
+.BI \-Z " user"
+.PD 0
+.TP
+.BI \-\-relinquish\-privileges= user
+.PD
+If
+.I tcpdump
+is running as root, after opening the capture device or input savefile,
+but before opening any savefiles for output, change the user ID to
+.I user
+and the group ID to the primary group of
+.IR user .
+.IP
+This behavior can also be enabled by default at compile time.
+.IP "\fI expression\fP"
+.RS
+selects which packets will be dumped.
+If no \fIexpression\fP
+is given, all packets on the net will be dumped.
+Otherwise,
+only packets for which \fIexpression\fP is `true' will be dumped.
+.LP
+For the \fIexpression\fP syntax, see
+.BR pcap-filter (7).
+.LP
+The \fIexpression\fP argument can be passed to \fItcpdump\fP as either a single
+Shell argument, or as multiple Shell arguments, whichever is more convenient.
+Generally, if the expression contains Shell metacharacters, such as
+backslashes used to escape protocol names, it is easier to pass it as
+a single, quoted argument rather than to escape the Shell
+metacharacters.
+Multiple arguments are concatenated with spaces before being parsed.
+.SH EXAMPLES
+.LP
+To print all packets arriving at or departing from \fIsundown\fP:
+.RS
+.nf
+\fBtcpdump host sundown\fP
+.fi
+.RE
+.LP
+To print traffic between \fIhelios\fR and either \fIhot\fR or \fIace\fR:
+.RS
+.nf
+\fBtcpdump host helios and \\( hot or ace \\)\fP
+.fi
+.RE
+.LP
+To print all IP packets between \fIace\fR and any host except \fIhelios\fR:
+.RS
+.nf
+\fBtcpdump ip host ace and not helios\fP
+.fi
+.RE
+.LP
+To print all traffic between local hosts and hosts at Berkeley:
+.RS
+.nf
+.B
+tcpdump net ucb-ether
+.fi
+.RE
+.LP
+To print all ftp traffic through internet gateway \fIsnup\fP:
+(note that the expression is quoted to prevent the shell from
+(mis-)interpreting the parentheses):
+.RS
+.nf
+.B
+tcpdump 'gateway snup and (port ftp or ftp-data)'
+.fi
+.RE
+.LP
+To print traffic neither sourced from nor destined for local hosts
+(if you gateway to one other net, this stuff should never make it
+onto your local net).
+.RS
+.nf
+.B
+tcpdump ip and not net \fIlocalnet\fP
+.fi
+.RE
+.LP
+To print the start and end packets (the SYN and FIN packets) of each
+TCP conversation that involves a non-local host.
+.RS
+.nf
+.B
+tcpdump 'tcp[tcpflags] & (tcp-syn|tcp-fin) != 0 and not src and dst net \fIlocalnet\fP'
+.fi
+.RE
+.LP
+To print all IPv4 HTTP packets to and from port 80, i.e. print only
+packets that contain data, not, for example, SYN and FIN packets and
+ACK-only packets. (IPv6 is left as an exercise for the reader.)
+.RS
+.nf
+.B
+tcpdump 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'
+.fi
+.RE
+.LP
+To print IP packets longer than 576 bytes sent through gateway \fIsnup\fP:
+.RS
+.nf
+.B
+tcpdump 'gateway snup and ip[2:2] > 576'
+.fi
+.RE
+.LP
+To print IP broadcast or multicast packets that were
+.I not
+sent via Ethernet broadcast or multicast:
+.RS
+.nf
+.B
+tcpdump 'ether[0] & 1 = 0 and ip[16] >= 224'
+.fi
+.RE
+.LP
+To print all ICMP packets that are not echo requests/replies (i.e., not
+ping packets):
+.RS
+.nf
+.B
+tcpdump 'icmp[icmptype] != icmp-echo and icmp[icmptype] != icmp-echoreply'
+.fi
+.RE
+.SH OUTPUT FORMAT
+.LP
+The output of \fItcpdump\fP is protocol dependent.
+The following
+gives a brief description and examples of most of the formats.
+.de HD
+.sp 1.5
+.B
+..
+.HD
+Link Level Headers
+.LP
+If the '-e' option is given, the link level header is printed out.
+On Ethernets, the source and destination addresses, protocol,
+and packet length are printed.
+.LP
+On FDDI networks, the '-e' option causes \fItcpdump\fP to print
+the `frame control' field, the source and destination addresses,
+and the packet length.
+(The `frame control' field governs the
+interpretation of the rest of the packet.
+Normal packets (such
+as those containing IP datagrams) are `async' packets, with a priority
+value between 0 and 7; for example, `\fBasync4\fR'.
+Such packets
+are assumed to contain an 802.2 Logical Link Control (LLC) packet;
+the LLC header is printed if it is \fInot\fR an ISO datagram or a
+so-called SNAP packet.
+.LP
+On Token Ring networks, the '-e' option causes \fItcpdump\fP to print
+the `access control' and `frame control' fields, the source and
+destination addresses, and the packet length.
+As on FDDI networks,
+packets are assumed to contain an LLC packet.
+Regardless of whether
+the '-e' option is specified or not, the source routing information is
+printed for source-routed packets.
+.LP
+On 802.11 networks, the '-e' option causes \fItcpdump\fP to print
+the `frame control' fields, all of the addresses in the 802.11 header,
+and the packet length.
+As on FDDI networks,
+packets are assumed to contain an LLC packet.
+.LP
+\fI(N.B.: The following description assumes familiarity with
+the SLIP compression algorithm described in RFC-1144.)\fP
+.LP
+On SLIP links, a direction indicator (``I'' for inbound, ``O'' for outbound),
+packet type, and compression information are printed out.
+The packet type is printed first.
+The three types are \fIip\fP, \fIutcp\fP, and \fIctcp\fP.
+No further link information is printed for \fIip\fR packets.
+For TCP packets, the connection identifier is printed following the type.
+If the packet is compressed, its encoded header is printed out.
+The special cases are printed out as
+\fB*S+\fIn\fR and \fB*SA+\fIn\fR, where \fIn\fR is the amount by which
+the sequence number (or sequence number and ack) has changed.
+If it is not a special case,
+zero or more changes are printed.
+A change is indicated by U (urgent pointer), W (window), A (ack),
+S (sequence number), and I (packet ID), followed by a delta (+n or -n),
+or a new value (=n).
+Finally, the amount of data in the packet and compressed header length
+are printed.
+.LP
+For example, the following line shows an outbound compressed TCP packet,
+with an implicit connection identifier; the ack has changed by 6,
+the sequence number by 49, and the packet ID by 6; there are 3 bytes of
+data and 6 bytes of compressed header:
+.RS
+.nf
+\fBO ctcp * A+6 S+49 I+6 3 (6)\fP
+.fi
+.RE
+.HD
+ARP/RARP Packets
+.LP
+Arp/rarp output shows the type of request and its arguments.
+The
+format is intended to be self explanatory.
+Here is a short sample taken from the start of an `rlogin' from
+host \fIrtsg\fP to host \fIcsam\fP:
+.RS
+.nf
+.sp .5
+\f(CWarp who-has csam tell rtsg
+arp reply csam is-at CSAM\fR
+.sp .5
+.fi
+.RE
+The first line says that rtsg sent an arp packet asking
+for the Ethernet address of internet host csam.
+Csam
+replies with its Ethernet address (in this example, Ethernet addresses
+are in caps and internet addresses in lower case).
+.LP
+This would look less redundant if we had done \fItcpdump \-n\fP:
+.RS
+.nf
+.sp .5
+\f(CWarp who-has 128.3.254.6 tell 128.3.254.68
+arp reply 128.3.254.6 is-at 02:07:01:00:01:c4\fP
+.fi
+.RE
+.LP
+If we had done \fItcpdump \-e\fP, the fact that the first packet is
+broadcast and the second is point-to-point would be visible:
+.RS
+.nf
+.sp .5
+\f(CWRTSG Broadcast 0806 64: arp who-has csam tell rtsg
+CSAM RTSG 0806 64: arp reply csam is-at CSAM\fR
+.sp .5
+.fi
+.RE
+For the first packet this says the Ethernet source address is RTSG, the
+destination is the Ethernet broadcast address, the type field
+contained hex 0806 (type ETHER_ARP) and the total length was 64 bytes.
+.HD
+TCP Packets
+.LP
+\fI(N.B.:The following description assumes familiarity with
+the TCP protocol described in RFC-793.
+If you are not familiar
+with the protocol, neither this description nor \fItcpdump\fP will
+be of much use to you.)\fP
+.LP
+The general format of a tcp protocol line is:
+.RS
+.nf
+.sp .5
+\fIsrc > dst: flags data-seqno ack window urgent options\fP
+.sp .5
+.fi
+.RE
+\fISrc\fP and \fIdst\fP are the source and destination IP
+addresses and ports.
+\fIFlags\fP are some combination of S (SYN),
+F (FIN), P (PUSH), R (RST), U (URG), W (ECN CWR), E (ECN-Echo) or
+`.' (ACK), or `none' if no flags are set.
+\fIData-seqno\fP describes the portion of sequence space covered
+by the data in this packet (see example below).
+\fIAck\fP is sequence number of the next data expected the other
+direction on this connection.
+\fIWindow\fP is the number of bytes of receive buffer space available
+the other direction on this connection.
+\fIUrg\fP indicates there is `urgent' data in the packet.
+\fIOptions\fP are tcp options enclosed in angle brackets (e.g., <mss 1024>).
+.LP
+\fISrc, dst\fP and \fIflags\fP are always present.
+The other fields
+depend on the contents of the packet's tcp protocol header and
+are output only if appropriate.
+.LP
+Here is the opening portion of an rlogin from host \fIrtsg\fP to
+host \fIcsam\fP.
+.RS
+.nf
+.sp .5
+\s-2\f(CWrtsg.1023 > csam.login: S 768512:768512(0) win 4096 <mss 1024>
+csam.login > rtsg.1023: S 947648:947648(0) ack 768513 win 4096 <mss 1024>
+rtsg.1023 > csam.login: . ack 1 win 4096
+rtsg.1023 > csam.login: P 1:2(1) ack 1 win 4096
+csam.login > rtsg.1023: . ack 2 win 4096
+rtsg.1023 > csam.login: P 2:21(19) ack 1 win 4096
+csam.login > rtsg.1023: P 1:2(1) ack 21 win 4077
+csam.login > rtsg.1023: P 2:3(1) ack 21 win 4077 urg 1
+csam.login > rtsg.1023: P 3:4(1) ack 21 win 4077 urg 1\fR\s+2
+.sp .5
+.fi
+.RE
+The first line says that tcp port 1023 on rtsg sent a packet
+to port \fIlogin\fP
+on csam.
+The \fBS\fP indicates that the \fISYN\fP flag was set.
+The packet sequence number was 768512 and it contained no data.
+(The notation is `first:last(nbytes)' which means `sequence
+numbers \fIfirst\fP
+up to but not including \fIlast\fP which is \fInbytes\fP bytes of user data'.)
+There was no piggy-backed ack, the available receive window was 4096
+bytes and there was a max-segment-size option requesting an mss of
+1024 bytes.
+.LP
+Csam replies with a similar packet except it includes a piggy-backed
+ack for rtsg's SYN.
+Rtsg then acks csam's SYN.
+The `.' means the ACK flag was set.
+The packet contained no data so there is no data sequence number.
+Note that the ack sequence
+number is a small integer (1).
+The first time \fItcpdump\fP sees a
+tcp `conversation', it prints the sequence number from the packet.
+On subsequent packets of the conversation, the difference between
+the current packet's sequence number and this initial sequence number
+is printed.
+This means that sequence numbers after the
+first can be interpreted
+as relative byte positions in the conversation's data stream (with the
+first data byte each direction being `1').
+`-S' will override this
+feature, causing the original sequence numbers to be output.
+.LP
+On the 6th line, rtsg sends csam 19 bytes of data (bytes 2 through 20
+in the rtsg \(-> csam side of the conversation).
+The PUSH flag is set in the packet.
+On the 7th line, csam says it's received data sent by rtsg up to
+but not including byte 21.
+Most of this data is apparently sitting in the
+socket buffer since csam's receive window has gotten 19 bytes smaller.
+Csam also sends one byte of data to rtsg in this packet.
+On the 8th and 9th lines,
+csam sends two bytes of urgent, pushed data to rtsg.
+.LP
+If the snapshot was small enough that \fItcpdump\fP didn't capture
+the full TCP header, it interprets as much of the header as it can
+and then reports ``[|\fItcp\fP]'' to indicate the remainder could not
+be interpreted.
+If the header contains a bogus option (one with a length
+that's either too small or beyond the end of the header), \fItcpdump\fP
+reports it as ``[\fIbad opt\fP]'' and does not interpret any further
+options (since it's impossible to tell where they start).
+If the header
+length indicates options are present but the IP datagram length is not
+long enough for the options to actually be there, \fItcpdump\fP reports
+it as ``[\fIbad hdr length\fP]''.
+.HD
+.B Capturing TCP packets with particular flag combinations (SYN-ACK, URG-ACK, etc.)
+.PP
+There are 8 bits in the control bits section of the TCP header:
+.IP
+.I CWR | ECE | URG | ACK | PSH | RST | SYN | FIN
+.PP
+Let's assume that we want to watch packets used in establishing
+a TCP connection.
+Recall that TCP uses a 3-way handshake protocol
+when it initializes a new connection; the connection sequence with
+regard to the TCP control bits is
+.PP
+.RS
+1) Caller sends SYN
+.RE
+.RS
+2) Recipient responds with SYN, ACK
+.RE
+.RS
+3) Caller sends ACK
+.RE
+.PP
+Now we're interested in capturing packets that have only the
+SYN bit set (Step 1).
+Note that we don't want packets from step 2
+(SYN-ACK), just a plain initial SYN.
+What we need is a correct filter
+expression for \fItcpdump\fP.
+.PP
+Recall the structure of a TCP header without options:
+.PP
+.nf
+ 0 15 31
+-----------------------------------------------------------------
+| source port | destination port |
+-----------------------------------------------------------------
+| sequence number |
+-----------------------------------------------------------------
+| acknowledgment number |
+-----------------------------------------------------------------
+| HL | rsvd |C|E|U|A|P|R|S|F| window size |
+-----------------------------------------------------------------
+| TCP checksum | urgent pointer |
+-----------------------------------------------------------------
+.fi
+.PP
+A TCP header usually holds 20 octets of data, unless options are
+present.
+The first line of the graph contains octets 0 - 3, the
+second line shows octets 4 - 7 etc.
+.PP
+Starting to count with 0, the relevant TCP control bits are contained
+in octet 13:
+.PP
+.nf
+ 0 7| 15| 23| 31
+----------------|---------------|---------------|----------------
+| HL | rsvd |C|E|U|A|P|R|S|F| window size |
+----------------|---------------|---------------|----------------
+| | 13th octet | | |
+.fi
+.PP
+Let's have a closer look at octet no. 13:
+.PP
+.nf
+ | |
+ |---------------|
+ |C|E|U|A|P|R|S|F|
+ |---------------|
+ |7 5 3 0|
+.fi
+.PP
+These are the TCP control bits we are interested
+in.
+We have numbered the bits in this octet from 0 to 7, right to
+left, so the PSH bit is bit number 3, while the URG bit is number 5.
+.PP
+Recall that we want to capture packets with only SYN set.
+Let's see what happens to octet 13 if a TCP datagram arrives
+with the SYN bit set in its header:
+.PP
+.nf
+ |C|E|U|A|P|R|S|F|
+ |---------------|
+ |0 0 0 0 0 0 1 0|
+ |---------------|
+ |7 6 5 4 3 2 1 0|
+.fi
+.PP
+Looking at the
+control bits section we see that only bit number 1 (SYN) is set.
+.PP
+Assuming that octet number 13 is an 8-bit unsigned integer in
+network byte order, the binary value of this octet is
+.IP
+00000010
+.PP
+and its decimal representation is
+.PP
+.nf
+ 7 6 5 4 3 2 1 0
+0*2 + 0*2 + 0*2 + 0*2 + 0*2 + 0*2 + 1*2 + 0*2 = 2
+.fi
+.PP
+We're almost done, because now we know that if only SYN is set,
+the value of the 13th octet in the TCP header, when interpreted
+as a 8-bit unsigned integer in network byte order, must be exactly 2.
+.PP
+This relationship can be expressed as
+.RS
+.B
+tcp[13] == 2
+.RE
+.PP
+We can use this expression as the filter for \fItcpdump\fP in order
+to watch packets which have only SYN set:
+.RS
+.B
+tcpdump -i xl0 tcp[13] == 2
+.RE
+.PP
+The expression says "let the 13th octet of a TCP datagram have
+the decimal value 2", which is exactly what we want.
+.PP
+Now, let's assume that we need to capture SYN packets, but we
+don't care if ACK or any other TCP control bit is set at the
+same time.
+Let's see what happens to octet 13 when a TCP datagram
+with SYN-ACK set arrives:
+.PP
+.nf
+ |C|E|U|A|P|R|S|F|
+ |---------------|
+ |0 0 0 1 0 0 1 0|
+ |---------------|
+ |7 6 5 4 3 2 1 0|
+.fi
+.PP
+Now bits 1 and 4 are set in the 13th octet.
+The binary value of
+octet 13 is
+.IP
+ 00010010
+.PP
+which translates to decimal
+.PP
+.nf
+ 7 6 5 4 3 2 1 0
+0*2 + 0*2 + 0*2 + 1*2 + 0*2 + 0*2 + 1*2 + 0*2 = 18
+.fi
+.PP
+Now we can't just use 'tcp[13] == 18' in the \fItcpdump\fP filter
+expression, because that would select only those packets that have
+SYN-ACK set, but not those with only SYN set.
+Remember that we don't care
+if ACK or any other control bit is set as long as SYN is set.
+.PP
+In order to achieve our goal, we need to logically AND the
+binary value of octet 13 with some other value to preserve
+the SYN bit.
+We know that we want SYN to be set in any case,
+so we'll logically AND the value in the 13th octet with
+the binary value of a SYN:
+.PP
+.nf
+
+ 00010010 SYN-ACK 00000010 SYN
+ AND 00000010 (we want SYN) AND 00000010 (we want SYN)
+ -------- --------
+ = 00000010 = 00000010
+.fi
+.PP
+We see that this AND operation delivers the same result
+regardless whether ACK or another TCP control bit is set.
+The decimal representation of the AND value as well as
+the result of this operation is 2 (binary 00000010),
+so we know that for packets with SYN set the following
+relation must hold true:
+.IP
+( ( value of octet 13 ) AND ( 2 ) ) == ( 2 )
+.PP
+This points us to the \fItcpdump\fP filter expression
+.RS
+.B
+ tcpdump -i xl0 'tcp[13] & 2 == 2'
+.RE
+.PP
+Some offsets and field values may be expressed as names
+rather than as numeric values. For example tcp[13] may
+be replaced with tcp[tcpflags]. The following TCP flag
+field values are also available: tcp-fin, tcp-syn, tcp-rst,
+tcp-push, tcp-act, tcp-urg.
+.PP
+This can be demonstrated as:
+.RS
+.B
+ tcpdump -i xl0 'tcp[tcpflags] & tcp-push != 0'
+.RE
+.PP
+Note that you should use single quotes or a backslash
+in the expression to hide the AND ('&') special character
+from the shell.
+.HD
+.B
+UDP Packets
+.LP
+UDP format is illustrated by this rwho packet:
+.RS
+.nf
+.sp .5
+\f(CWactinide.who > broadcast.who: udp 84\fP
+.sp .5
+.fi
+.RE
+This says that port \fIwho\fP on host \fIactinide\fP sent a udp
+datagram to port \fIwho\fP on host \fIbroadcast\fP, the Internet
+broadcast address.
+The packet contained 84 bytes of user data.
+.LP
+Some UDP services are recognized (from the source or destination
+port number) and the higher level protocol information printed.
+In particular, Domain Name service requests (RFC-1034/1035) and Sun
+RPC calls (RFC-1050) to NFS.
+.HD
+UDP Name Server Requests
+.LP
+\fI(N.B.:The following description assumes familiarity with
+the Domain Service protocol described in RFC-1035.
+If you are not familiar
+with the protocol, the following description will appear to be written
+in greek.)\fP
+.LP
+Name server requests are formatted as
+.RS
+.nf
+.sp .5
+\fIsrc > dst: id op? flags qtype qclass name (len)\fP
+.sp .5
+\f(CWh2opolo.1538 > helios.domain: 3+ A? ucbvax.berkeley.edu. (37)\fR
+.sp .5
+.fi
+.RE
+Host \fIh2opolo\fP asked the domain server on \fIhelios\fP for an
+address record (qtype=A) associated with the name \fIucbvax.berkeley.edu.\fP
+The query id was `3'.
+The `+' indicates the \fIrecursion desired\fP flag
+was set.
+The query length was 37 bytes, not including the UDP and
+IP protocol headers.
+The query operation was the normal one, \fIQuery\fP,
+so the op field was omitted.
+If the op had been anything else, it would
+have been printed between the `3' and the `+'.
+Similarly, the qclass was the normal one,
+\fIC_IN\fP, and omitted.
+Any other qclass would have been printed
+immediately after the `A'.
+.LP
+A few anomalies are checked and may result in extra fields enclosed in
+square brackets: If a query contains an answer, authority records or
+additional records section,
+.IR ancount ,
+.IR nscount ,
+or
+.I arcount
+are printed as `[\fIn\fPa]', `[\fIn\fPn]' or `[\fIn\fPau]' where \fIn\fP
+is the appropriate count.
+If any of the response bits are set (AA, RA or rcode) or any of the
+`must be zero' bits are set in bytes two and three, `[b2&3=\fIx\fP]'
+is printed, where \fIx\fP is the hex value of header bytes two and three.
+.HD
+UDP Name Server Responses
+.LP
+Name server responses are formatted as
+.RS
+.nf
+.sp .5
+\fIsrc > dst: id op rcode flags a/n/au type class data (len)\fP
+.sp .5
+\f(CWhelios.domain > h2opolo.1538: 3 3/3/7 A 128.32.137.3 (273)
+helios.domain > h2opolo.1537: 2 NXDomain* 0/1/0 (97)\fR
+.sp .5
+.fi
+.RE
+In the first example, \fIhelios\fP responds to query id 3 from \fIh2opolo\fP
+with 3 answer records, 3 name server records and 7 additional records.
+The first answer record is type A (address) and its data is internet
+address 128.32.137.3.
+The total size of the response was 273 bytes,
+excluding UDP and IP headers.
+The op (Query) and response code
+(NoError) were omitted, as was the class (C_IN) of the A record.
+.LP
+In the second example, \fIhelios\fP responds to query 2 with a
+response code of non-existent domain (NXDomain) with no answers,
+one name server and no authority records.
+The `*' indicates that
+the \fIauthoritative answer\fP bit was set.
+Since there were no
+answers, no type, class or data were printed.
+.LP
+Other flag characters that might appear are `\-' (recursion available,
+RA, \fInot\fP set) and `|' (truncated message, TC, set).
+If the
+`question' section doesn't contain exactly one entry, `[\fIn\fPq]'
+is printed.
+.HD
+SMB/CIFS decoding
+.LP
+\fItcpdump\fP now includes fairly extensive SMB/CIFS/NBT decoding for data
+on UDP/137, UDP/138 and TCP/139.
+Some primitive decoding of IPX and
+NetBEUI SMB data is also done.
+.LP
+By default a fairly minimal decode is done, with a much more detailed
+decode done if -v is used.
+Be warned that with -v a single SMB packet
+may take up a page or more, so only use -v if you really want all the
+gory details.
+.LP
+For information on SMB packet formats and what all the fields mean see
+www.cifs.org or the pub/samba/specs/ directory on your favorite
+samba.org mirror site.
+The SMB patches were written by Andrew Tridgell
+(tridge@samba.org).
+.HD
+NFS Requests and Replies
+.LP
+Sun NFS (Network File System) requests and replies are printed as:
+.RS
+.nf
+.sp .5
+\fIsrc.sport > dst.nfs: NFS request xid xid len op args\fP
+\fIsrc.nfs > dst.dport: NFS reply xid xid reply stat len op results\fP
+.sp .5
+\f(CW
+sushi.1023 > wrl.nfs: NFS request xid 26377
+ 112 readlink fh 21,24/10.73165
+wrl.nfs > sushi.1023: NFS reply xid 26377
+ reply ok 40 readlink "../var"
+sushi.1022 > wrl.nfs: NFS request xid 8219
+ 144 lookup fh 9,74/4096.6878 "xcolors"
+wrl.nfs > sushi.1022: NFS reply xid 8219
+ reply ok 128 lookup fh 9,74/4134.3150
+\fR
+.sp .5
+.fi
+.RE
+In the first line, host \fIsushi\fP sends a transaction with id \fI26377\fP
+to \fIwrl\fP.
+The request was 112 bytes,
+excluding the UDP and IP headers.
+The operation was a \fIreadlink\fP
+(read symbolic link) on file handle (\fIfh\fP) 21,24/10.731657119.
+(If one is lucky, as in this case, the file handle can be interpreted
+as a major,minor device number pair, followed by the inode number and
+generation number.) In the second line, \fIwrl\fP replies `ok' with
+the same transaction id and the contents of the link.
+.LP
+In the third line, \fIsushi\fP asks (using a new transaction id) \fIwrl\fP
+to lookup the name `\fIxcolors\fP' in directory file 9,74/4096.6878. In
+the fourth line, \fIwrl\fP sends a reply with the respective transaction id.
+.LP
+Note that the data printed
+depends on the operation type.
+The format is intended to be self
+explanatory if read in conjunction with
+an NFS protocol spec.
+Also note that older versions of tcpdump printed NFS packets in a
+slightly different format: the transaction id (xid) would be printed
+instead of the non-NFS port number of the packet.
+.LP
+If the \-v (verbose) flag is given, additional information is printed.
+For example:
+.RS
+.nf
+.sp .5
+\f(CW
+sushi.1023 > wrl.nfs: NFS request xid 79658
+ 148 read fh 21,11/12.195 8192 bytes @ 24576
+wrl.nfs > sushi.1023: NFS reply xid 79658
+ reply ok 1472 read REG 100664 ids 417/0 sz 29388
+\fP
+.sp .5
+.fi
+.RE
+(\-v also prints the IP header TTL, ID, length, and fragmentation fields,
+which have been omitted from this example.) In the first line,
+\fIsushi\fP asks \fIwrl\fP to read 8192 bytes from file 21,11/12.195,
+at byte offset 24576.
+\fIWrl\fP replies `ok'; the packet shown on the
+second line is the first fragment of the reply, and hence is only 1472
+bytes long (the other bytes will follow in subsequent fragments, but
+these fragments do not have NFS or even UDP headers and so might not be
+printed, depending on the filter expression used).
+Because the \-v flag
+is given, some of the file attributes (which are returned in addition
+to the file data) are printed: the file type (``REG'', for regular file),
+the file mode (in octal), the uid and gid, and the file size.
+.LP
+If the \-v flag is given more than once, even more details are printed.
+.LP
+Note that NFS requests are very large and much of the detail won't be printed
+unless \fIsnaplen\fP is increased.
+Try using `\fB\-s 192\fP' to watch
+NFS traffic.
+.LP
+NFS reply packets do not explicitly identify the RPC operation.
+Instead,
+\fItcpdump\fP keeps track of ``recent'' requests, and matches them to the
+replies using the transaction ID.
+If a reply does not closely follow the
+corresponding request, it might not be parsable.
+.HD
+AFS Requests and Replies
+.LP
+Transarc AFS (Andrew File System) requests and replies are printed
+as:
+.HD
+.RS
+.nf
+.sp .5
+\fIsrc.sport > dst.dport: rx packet-type\fP
+\fIsrc.sport > dst.dport: rx packet-type service call call-name args\fP
+\fIsrc.sport > dst.dport: rx packet-type service reply call-name args\fP
+.sp .5
+\f(CW
+elvis.7001 > pike.afsfs:
+ rx data fs call rename old fid 536876964/1/1 ".newsrc.new"
+ new fid 536876964/1/1 ".newsrc"
+pike.afsfs > elvis.7001: rx data fs reply rename
+\fR
+.sp .5
+.fi
+.RE
+In the first line, host elvis sends a RX packet to pike.
+This was
+a RX data packet to the fs (fileserver) service, and is the start of
+an RPC call.
+The RPC call was a rename, with the old directory file id
+of 536876964/1/1 and an old filename of `.newsrc.new', and a new directory
+file id of 536876964/1/1 and a new filename of `.newsrc'.
+The host pike
+responds with a RPC reply to the rename call (which was successful, because
+it was a data packet and not an abort packet).
+.LP
+In general, all AFS RPCs are decoded at least by RPC call name.
+Most
+AFS RPCs have at least some of the arguments decoded (generally only
+the `interesting' arguments, for some definition of interesting).
+.LP
+The format is intended to be self-describing, but it will probably
+not be useful to people who are not familiar with the workings of
+AFS and RX.
+.LP
+If the -v (verbose) flag is given twice, acknowledgement packets and
+additional header information is printed, such as the RX call ID,
+call number, sequence number, serial number, and the RX packet flags.
+.LP
+If the -v flag is given twice, additional information is printed,
+such as the RX call ID, serial number, and the RX packet flags.
+The MTU negotiation information is also printed from RX ack packets.
+.LP
+If the -v flag is given three times, the security index and service id
+are printed.
+.LP
+Error codes are printed for abort packets, with the exception of Ubik
+beacon packets (because abort packets are used to signify a yes vote
+for the Ubik protocol).
+.LP
+Note that AFS requests are very large and many of the arguments won't
+be printed unless \fIsnaplen\fP is increased.
+Try using `\fB-s 256\fP'
+to watch AFS traffic.
+.LP
+AFS reply packets do not explicitly identify the RPC operation.
+Instead,
+\fItcpdump\fP keeps track of ``recent'' requests, and matches them to the
+replies using the call number and service ID.
+If a reply does not closely
+follow the
+corresponding request, it might not be parsable.
+
+.HD
+KIP AppleTalk (DDP in UDP)
+.LP
+AppleTalk DDP packets encapsulated in UDP datagrams are de-encapsulated
+and dumped as DDP packets (i.e., all the UDP header information is
+discarded).
+The file
+.I /etc/atalk.names
+is used to translate AppleTalk net and node numbers to names.
+Lines in this file have the form
+.RS
+.nf
+.sp .5
+\fInumber name\fP
+
+\f(CW1.254 ether
+16.1 icsd-net
+1.254.110 ace\fR
+.sp .5
+.fi
+.RE
+The first two lines give the names of AppleTalk networks.
+The third
+line gives the name of a particular host (a host is distinguished
+from a net by the 3rd octet in the number \-
+a net number \fImust\fP have two octets and a host number \fImust\fP
+have three octets.) The number and name should be separated by
+whitespace (blanks or tabs).
+The
+.I /etc/atalk.names
+file may contain blank lines or comment lines (lines starting with
+a `#').
+.LP
+AppleTalk addresses are printed in the form
+.RS
+.nf
+.sp .5
+\fInet.host.port\fP
+
+\f(CW144.1.209.2 > icsd-net.112.220
+office.2 > icsd-net.112.220
+jssmag.149.235 > icsd-net.2\fR
+.sp .5
+.fi
+.RE
+(If the
+.I /etc/atalk.names
+doesn't exist or doesn't contain an entry for some AppleTalk
+host/net number, addresses are printed in numeric form.)
+In the first example, NBP (DDP port 2) on net 144.1 node 209
+is sending to whatever is listening on port 220 of net icsd node 112.
+The second line is the same except the full name of the source node
+is known (`office').
+The third line is a send from port 235 on
+net jssmag node 149 to broadcast on the icsd-net NBP port (note that
+the broadcast address (255) is indicated by a net name with no host
+number \- for this reason it's a good idea to keep node names and
+net names distinct in /etc/atalk.names).
+.LP
+NBP (name binding protocol) and ATP (AppleTalk transaction protocol)
+packets have their contents interpreted.
+Other protocols just dump
+the protocol name (or number if no name is registered for the
+protocol) and packet size.
+
+\fBNBP packets\fP are formatted like the following examples:
+.RS
+.nf
+.sp .5
+\s-2\f(CWicsd-net.112.220 > jssmag.2: nbp-lkup 190: "=:LaserWriter@*"
+jssmag.209.2 > icsd-net.112.220: nbp-reply 190: "RM1140:LaserWriter@*" 250
+techpit.2 > icsd-net.112.220: nbp-reply 190: "techpit:LaserWriter@*" 186\fR\s+2
+.sp .5
+.fi
+.RE
+The first line is a name lookup request for laserwriters sent by net icsd host
+112 and broadcast on net jssmag.
+The nbp id for the lookup is 190.
+The second line shows a reply for this request (note that it has the
+same id) from host jssmag.209 saying that it has a laserwriter
+resource named "RM1140" registered on port 250.
+The third line is
+another reply to the same request saying host techpit has laserwriter
+"techpit" registered on port 186.
+
+\fBATP packet\fP formatting is demonstrated by the following example:
+.RS
+.nf
+.sp .5
+\s-2\f(CWjssmag.209.165 > helios.132: atp-req 12266<0-7> 0xae030001
+helios.132 > jssmag.209.165: atp-resp 12266:0 (512) 0xae040000
+helios.132 > jssmag.209.165: atp-resp 12266:1 (512) 0xae040000
+helios.132 > jssmag.209.165: atp-resp 12266:2 (512) 0xae040000
+helios.132 > jssmag.209.165: atp-resp 12266:3 (512) 0xae040000
+helios.132 > jssmag.209.165: atp-resp 12266:4 (512) 0xae040000
+helios.132 > jssmag.209.165: atp-resp 12266:5 (512) 0xae040000
+helios.132 > jssmag.209.165: atp-resp 12266:6 (512) 0xae040000
+helios.132 > jssmag.209.165: atp-resp*12266:7 (512) 0xae040000
+jssmag.209.165 > helios.132: atp-req 12266<3,5> 0xae030001
+helios.132 > jssmag.209.165: atp-resp 12266:3 (512) 0xae040000
+helios.132 > jssmag.209.165: atp-resp 12266:5 (512) 0xae040000
+jssmag.209.165 > helios.132: atp-rel 12266<0-7> 0xae030001
+jssmag.209.133 > helios.132: atp-req* 12267<0-7> 0xae030002\fR\s+2
+.sp .5
+.fi
+.RE
+Jssmag.209 initiates transaction id 12266 with host helios by requesting
+up to 8 packets (the `<0-7>').
+The hex number at the end of the line
+is the value of the `userdata' field in the request.
+.LP
+Helios responds with 8 512-byte packets.
+The `:digit' following the
+transaction id gives the packet sequence number in the transaction
+and the number in parens is the amount of data in the packet,
+excluding the atp header.
+The `*' on packet 7 indicates that the
+EOM bit was set.
+.LP
+Jssmag.209 then requests that packets 3 & 5 be retransmitted.
+Helios
+resends them then jssmag.209 releases the transaction.
+Finally,
+jssmag.209 initiates the next request.
+The `*' on the request
+indicates that XO (`exactly once') was \fInot\fP set.
+
+.HD
+IP Fragmentation
+.LP
+Fragmented Internet datagrams are printed as
+.RS
+.nf
+.sp .5
+\fB(frag \fIid\fB:\fIsize\fB@\fIoffset\fB+)\fR
+\fB(frag \fIid\fB:\fIsize\fB@\fIoffset\fB)\fR
+.sp .5
+.fi
+.RE
+(The first form indicates there are more fragments.
+The second
+indicates this is the last fragment.)
+.LP
+\fIId\fP is the fragment id.
+\fISize\fP is the fragment
+size (in bytes) excluding the IP header.
+\fIOffset\fP is this
+fragment's offset (in bytes) in the original datagram.
+.LP
+The fragment information is output for each fragment.
+The first
+fragment contains the higher level protocol header and the frag
+info is printed after the protocol info.
+Fragments
+after the first contain no higher level protocol header and the
+frag info is printed after the source and destination addresses.
+For example, here is part of an ftp from arizona.edu to lbl-rtsg.arpa
+over a CSNET connection that doesn't appear to handle 576 byte datagrams:
+.RS
+.nf
+.sp .5
+\s-2\f(CWarizona.ftp-data > rtsg.1170: . 1024:1332(308) ack 1 win 4096 (frag 595a:328@0+)
+arizona > rtsg: (frag 595a:204@328)
+rtsg.1170 > arizona.ftp-data: . ack 1536 win 2560\fP\s+2
+.sp .5
+.fi
+.RE
+There are a couple of things to note here: First, addresses in the
+2nd line don't include port numbers.
+This is because the TCP
+protocol information is all in the first fragment and we have no idea
+what the port or sequence numbers are when we print the later fragments.
+Second, the tcp sequence information in the first line is printed as if there
+were 308 bytes of user data when, in fact, there are 512 bytes (308 in
+the first frag and 204 in the second).
+If you are looking for holes
+in the sequence space or trying to match up acks
+with packets, this can fool you.
+.LP
+A packet with the IP \fIdon't fragment\fP flag is marked with a
+trailing \fB(DF)\fP.
+.HD
+Timestamps
+.LP
+By default, all output lines are preceded by a timestamp.
+The timestamp
+is the current clock time in the form
+.RS
+.nf
+\fIhh:mm:ss.frac\fP
+.fi
+.RE
+and is as accurate as the kernel's clock.
+The timestamp reflects the time the kernel first saw the packet.
+No attempt
+is made to account for the time lag between when the
+Ethernet interface removed the packet from the wire and when the kernel
+serviced the `new packet' interrupt.
+.SH "SEE ALSO"
+stty(1), pcap(3PCAP), bpf(4), nit(4P), pcap-savefile(5),
+pcap-filter(7), pcap-tstamp(7)
+.LP
+.RS
+.I http://www.iana.org/assignments/media-types/application/vnd.tcpdump.pcap
+.RE
+.LP
+.SH AUTHORS
+The original authors are:
+.LP
+Van Jacobson,
+Craig Leres and
+Steven McCanne, all of the
+Lawrence Berkeley National Laboratory, University of California, Berkeley, CA.
+.LP
+It is currently being maintained by tcpdump.org.
+.LP
+The current version is available via http:
+.LP
+.RS
+.I http://www.tcpdump.org/
+.RE
+.LP
+The original distribution is available via anonymous ftp:
+.LP
+.RS
+.I ftp://ftp.ee.lbl.gov/old/tcpdump.tar.Z
+.RE
+.LP
+IPv6/IPsec support is added by WIDE/KAME project.
+This program uses Eric Young's SSLeay library, under specific configurations.
+.SH BUGS
+Please send problems, bugs, questions, desirable enhancements, patches
+etc. to:
+.LP
+.RS
+tcpdump-workers@lists.tcpdump.org
+.RE
+.LP
+NIT doesn't let you watch your own outbound traffic, BPF will.
+We recommend that you use the latter.
+.LP
+On Linux systems with 2.0[.x] kernels:
+.IP
+packets on the loopback device will be seen twice;
+.IP
+packet filtering cannot be done in the kernel, so that all packets must
+be copied from the kernel in order to be filtered in user mode;
+.IP
+all of a packet, not just the part that's within the snapshot length,
+will be copied from the kernel (the 2.0[.x] packet capture mechanism, if
+asked to copy only part of a packet to userland, will not report the
+true length of the packet; this would cause most IP packets to get an
+error from
+.BR tcpdump );
+.IP
+capturing on some PPP devices won't work correctly.
+.LP
+We recommend that you upgrade to a 2.2 or later kernel.
+.LP
+Some attempt should be made to reassemble IP fragments or, at least
+to compute the right length for the higher level protocol.
+.LP
+Name server inverse queries are not dumped correctly: the (empty)
+question section is printed rather than real query in the answer
+section.
+Some believe that inverse queries are themselves a bug and
+prefer to fix the program generating them rather than \fItcpdump\fP.
+.LP
+A packet trace that crosses a daylight savings time change will give
+skewed time stamps (the time change is ignored).
+.LP
+Filter expressions on fields other than those in Token Ring headers will
+not correctly handle source-routed Token Ring packets.
+.LP
+Filter expressions on fields other than those in 802.11 headers will not
+correctly handle 802.11 data packets with both To DS and From DS set.
+.LP
+.BR "ip6 proto"
+should chase header chain, but at this moment it does not.
+.BR "ip6 protochain"
+is supplied for this behavior.
+.LP
+Arithmetic expression against transport layer headers, like \fBtcp[0]\fP,
+does not work against IPv6 packets.
+It only looks at IPv4 packets.
diff --git a/usr.sbin/tests/Makefile b/usr.sbin/tests/Makefile
new file mode 100644
index 0000000..a0e63e5
--- /dev/null
+++ b/usr.sbin/tests/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+.include <bsd.own.mk>
+
+.PATH: ${SRCTOP}/tests
+KYUAFILE= yes
+
+.include <bsd.test.mk>
diff --git a/usr.sbin/timed/Makefile b/usr.sbin/timed/Makefile
new file mode 100644
index 0000000..2f56510
--- /dev/null
+++ b/usr.sbin/timed/Makefile
@@ -0,0 +1,6 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+SUBDIR= timed timedc
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/timed/timed/CHANGES b/usr.sbin/timed/timed/CHANGES
new file mode 100644
index 0000000..e51acd1
--- /dev/null
+++ b/usr.sbin/timed/timed/CHANGES
@@ -0,0 +1,145 @@
+# @(#)CHANGES 5.1 (Berkeley) 5/11/93
+# $FreeBSD$
+
+This new version is almost identical to the timed and timedc code
+that has been shipped for years by a workstation vendor.
+
+Among the many changes:
+
+improve `timedc msite` to accept a list of hostnames.
+
+change slave-masters to answer the packets generated by `timedc msite`
+ with the name of the real master, not their own. This makes it
+ possible to "chase the chain" of slave servers to the ultimate
+ master.
+
+much improve the log caused by `timedc trace on`:
+ -made `timed -t` work.
+ -suppression of repeated entries, which both slowed down the daemon
+ (sometimes catastrophically) and tended to make disks fill up
+ even more quickly.
+ -better time stamps on log entries
+ -more messages
+ -dump information about slaves, master, and so on each time
+ a message asking the log be turned on is received, and
+ when the log is turned off.
+ -fewer CPU cycles
+
+use a hash table to keep track of slaves, instead of the stupid linear
+ list. This becomes handy with hundreds of slaves, instead of
+ the original design limit of "a room with a few VAX's."
+
+separate the main protocol timer from that used to look for other networks
+ to master.
+
+time stamp packets received by the daemon, so that time corrections
+ are not made (even more) inaccurate by waiting in the internal,
+ timed queue while the daemon is processing other messages.
+
+made -n and -i work with subnets not named in /etc/networks
+
+compute the median of the measured clocks, instead of the average
+ of "good" times.
+
+vastly improve the accuracy of the clock difference measure by
+ `timedc clockdiff`.
+
+use adjtime() when possible, and directly set the clock only when
+ necessary.
+
+when the requested adjustment is small, perform only part of it, to
+ damp oscillations and improve the long term accuracy of the
+ adjustments.
+
+fix uncounted core-dumps on machines that do not allow dereferencing 0
+ in both the daemon and timedc.
+
+fix "master loop detection".
+
+fix several cases in which multi-homed masters could get into shouting
+ matches, consuming all available network bandwidth and CPU cycles
+ (which ever runs out first), and convincing all bystanders to stop
+ advancing their own clocks.
+
+refuse to behave badly when other machines do. Instead of arguing forever,
+ go off and sulk when other machines refuse to play by the rules.
+
+increase the maximum number of clients.
+
+add "-F host,host2,..." to "freerun" or "trust" only some hosts. This
+ is handy both when only some machines should be trusted to let
+ root use the `date` command to change time in the network.
+
+ It is also handy when one machine has some other way of adjusting
+ its clock, whether NTP or a direct radio or atomic connection.
+ "-F localhost" causes `timed` to "trust" only itself.
+
+ It is also handy to build a hierarchy of timed masters crossing
+ networks. The TSP protocol has no provision of "goodness of clock",
+ no natural way to completely heal network paritions. Judicious
+ use of -F or -G can cause each gateway to trust only itself and
+ machines closer to a central machine with a radio or atomic clock.
+
+add #ifdef code that supports NIS "netgroups" of trusted hosts, which
+ can be easier to administer than -F.
+
+add #ifdef code to compute an aged total adjustment. This can be used
+ in systems that can make long term changes in their system clock
+ frequency, e.g. "timetrim" in the Silicon Graphics kernel.
+
+
+Problems observed by others that are unresolved include:
+
+Practically any users can send to the master TSP messages and this
+ way corrupt the reliability of the system. Authentication
+ of messages should be provided. Unfortunately, that would
+ require changing the protocol with all of the implied
+ compatibility problems. Fortunately, the new -F and -G args
+ can be used to cause the daemon to ignore time changes from
+ untrusted machines.
+
+MAN. The limit of 1013 on the number of slaves hosts should be doc'ed.
+
+ It should be dynamically allocated with no limit. On a
+ large network, one host could possibly master over many
+ more than 30 hosts. Given the timers in the code and
+ effectively in the protocol, and the time required by each
+ master to talk to each slave, it is not practical to have
+ more than 200-300 slaves. The master cannot keep up because
+ the slave-chatting is single-threaded. when the master
+ gets behind, slaves start demanding elections. To
+ significantly increase the number of slaves would require
+ multi-treading things, and given that a network with more
+ than 300 directly addressable machines has worse problems
+ than keep the time of day right, not worth worrying about.
+
+UGLY,CODE. timedc/cmds.c has a lots of repeated code in it.
+
+**** The first thing is that each command is set up as if it
+ were an individual program taking argc and argv. A more
+ conventional calling style should be used. I don't think
+ any of the routines take more than a couple arguments.
+
+UGLY. fxn definition syntax does't follow convention:
+ has type on same line.
+
+**** It needs to be fixed at least enough that tags
+ will work on it. An entire cleanup might be nice later, but
+ is noncritical.
+
+LOBBY(mildly),CODE: Would be very convenient if date(1) took a
+ +-<number> argument to set the time relatively. With
+ the advent of timed it is now reasonable to synchronize
+ with WWV, which is nearly impossible to do "by hand"
+ with just an absolute date, and scripts are too slow.
+ format could be +-nn...nn.ss, where the '.' is required
+ to remove ambiguity.
+
+**** If you want to do it go ahead. It sounds useful. As far as
+ syntax goes, the normal format for the date should work just
+ fine for this. If the date is preceded by a plus or minus,
+ the change is relative, otherwise it is absolute.
+
+
+Vernon Schryver.
+vjs@sgi.com
diff --git a/usr.sbin/timed/timed/Makefile b/usr.sbin/timed/timed/Makefile
new file mode 100644
index 0000000..d4ce0fb
--- /dev/null
+++ b/usr.sbin/timed/timed/Makefile
@@ -0,0 +1,16 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+#
+# optional flags are: MEASURE TESTING DEBUG
+
+PROG= timed
+MAN= timed.8
+SRCS= acksend.c candidate.c correct.c master.c networkdelta.c readmsg.c \
+ slave.c timed.c byteorder.c measure.c cksum.c
+
+LIBADD= util
+
+WARNS?= 1
+
+.include "../../Makefile.inc"
+.include <bsd.prog.mk>
diff --git a/usr.sbin/timed/timed/Makefile.depend b/usr.sbin/timed/timed/Makefile.depend
new file mode 100644
index 0000000..c0e7169
--- /dev/null
+++ b/usr.sbin/timed/timed/Makefile.depend
@@ -0,0 +1,22 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/protocols \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+ lib/msun \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/timed/timed/acksend.c b/usr.sbin/timed/timed/acksend.c
new file mode 100644
index 0000000..8492f9e
--- /dev/null
+++ b/usr.sbin/timed/timed/acksend.c
@@ -0,0 +1,125 @@
+/*-
+ * 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[] = "@(#)acksend.c 8.1 (Berkeley) 6/6/93";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include "globals.h"
+
+struct tsp *answer;
+
+extern u_short sequence;
+
+void
+xmit(int type, u_int seq, struct sockaddr_in *addr)
+{
+ static struct tsp msg;
+
+ msg.tsp_type = type;
+ msg.tsp_seq = seq;
+ msg.tsp_vers = TSPVERSION;
+ (void)strcpy(msg.tsp_name, hostname);
+ bytenetorder(&msg);
+ if (sendto(sock, (char *)&msg, sizeof(struct tsp), 0,
+ (struct sockaddr*)addr, sizeof(struct sockaddr)) < 0) {
+ trace_sendto_err(addr->sin_addr);
+ }
+}
+
+
+/*
+ * Acksend implements reliable datagram transmission by using sequence
+ * numbers and retransmission when necessary.
+ * If `name' is ANYADDR, this routine implements reliable broadcast.
+ *
+ * Because this function calls readmsg(), none of its args may be in
+ * a message provided by readmsg().
+ * message this message
+ * addr to here
+ * ack look for this ack
+ * net receive from this network
+ * bad 1=losing patience
+ */
+struct tsp *
+acksend(struct tsp *message, struct sockaddr_in *addr, char *name,
+ int ack, struct netinfo *net, int bad)
+{
+ struct timeval twait;
+ int count;
+ long msec;
+
+ message->tsp_vers = TSPVERSION;
+ message->tsp_seq = sequence;
+ if (trace) {
+ fprintf(fd, "acksend: to %s: ",
+ (name == ANYADDR ? "broadcast" : name));
+ print(message, addr);
+ }
+ bytenetorder(message);
+
+ msec = 200;
+ count = bad ? 1 : 5; /* 5 packets in 6.4 seconds */
+ answer = 0;
+ do {
+ if (!answer) {
+ /* do not go crazy transmitting just because the
+ * other guy cannot keep our sequence numbers
+ * straight.
+ */
+ if (sendto(sock, (char *)message, sizeof(struct tsp),
+ 0, (struct sockaddr*)addr,
+ sizeof(struct sockaddr)) < 0) {
+ trace_sendto_err(addr->sin_addr);
+ break;
+ }
+ }
+
+ mstotvround(&twait, msec);
+ answer = readmsg(ack, name, &twait, net);
+ if (answer != 0) {
+ if (answer->tsp_seq != sequence) {
+ if (trace)
+ fprintf(fd,"acksend: seq # %u!=%u\n",
+ answer->tsp_seq, sequence);
+ continue;
+ }
+ break;
+ }
+
+ msec *= 2;
+ } while (--count > 0);
+ sequence++;
+
+ return(answer);
+}
diff --git a/usr.sbin/timed/timed/byteorder.c b/usr.sbin/timed/timed/byteorder.c
new file mode 100644
index 0000000..abacbd6
--- /dev/null
+++ b/usr.sbin/timed/timed/byteorder.c
@@ -0,0 +1,80 @@
+/*-
+ * 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[] = "@(#)byteorder.c 8.1 (Berkeley) 6/6/93";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include "globals.h"
+
+/*
+ * Two routines to do the necessary byte swapping for timed protocol
+ * messages. Protocol is defined in /usr/include/protocols/timed.h
+ */
+void
+bytenetorder(struct tsp *ptr)
+{
+ ptr->tsp_seq = htons((u_short)ptr->tsp_seq);
+ switch (ptr->tsp_type) {
+
+ case TSP_SETTIME:
+ case TSP_ADJTIME:
+ case TSP_SETDATE:
+ case TSP_SETDATEREQ:
+ ptr->tsp_time.tv_sec = htonl((u_long)ptr->tsp_time.tv_sec);
+ ptr->tsp_time.tv_usec = htonl((u_long)ptr->tsp_time.tv_usec);
+ break;
+
+ default:
+ break; /* nothing more needed */
+ }
+}
+
+void
+bytehostorder(struct tsp *ptr)
+{
+ ptr->tsp_seq = ntohs((u_short)ptr->tsp_seq);
+ switch (ptr->tsp_type) {
+
+ case TSP_SETTIME:
+ case TSP_ADJTIME:
+ case TSP_SETDATE:
+ case TSP_SETDATEREQ:
+ ptr->tsp_time.tv_sec = ntohl((u_long)ptr->tsp_time.tv_sec);
+ ptr->tsp_time.tv_usec = ntohl((u_long)ptr->tsp_time.tv_usec);
+ break;
+
+ default:
+ break; /* nothing more needed */
+ }
+}
diff --git a/usr.sbin/timed/timed/candidate.c b/usr.sbin/timed/timed/candidate.c
new file mode 100644
index 0000000..c92ae70
--- /dev/null
+++ b/usr.sbin/timed/timed/candidate.c
@@ -0,0 +1,162 @@
+/*-
+ * 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[] = "@(#)candidate.c 8.1 (Berkeley) 6/6/93";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include "globals.h"
+
+/*
+ * `election' candidates a host as master: it is called by a slave
+ * which runs with the -M option set when its election timeout expires.
+ * Note the conservative approach: if a new timed comes up, or another
+ * candidate sends an election request, the candidature is withdrawn.
+ */
+int
+election(struct netinfo *net)
+{
+ struct tsp *resp, msg;
+ struct timeval then, wait;
+ struct tsp *answer;
+ struct hosttbl *htp;
+ char loop_lim = 0;
+
+/* This code can get totally confused if it gets slightly behind. For
+ * example, if readmsg() has some QUIT messages waiting from the last
+ * round, we would send an ELECTION message, get the stale QUIT,
+ * and give up. This results in network storms when several machines
+ * do it at once.
+ */
+ wait.tv_sec = 0;
+ wait.tv_usec = 0;
+ while (0 != readmsg(TSP_REFUSE, ANYADDR, &wait, net)) {
+ if (trace)
+ fprintf(fd, "election: discarded stale REFUSE\n");
+ }
+ while (0 != readmsg(TSP_QUIT, ANYADDR, &wait, net)) {
+ if (trace)
+ fprintf(fd, "election: discarded stale QUIT\n");
+ }
+
+again:
+ syslog(LOG_INFO, "This machine is a candidate time master");
+ if (trace)
+ fprintf(fd, "This machine is a candidate time master\n");
+ msg.tsp_type = TSP_ELECTION;
+ msg.tsp_vers = TSPVERSION;
+ (void)strcpy(msg.tsp_name, hostname);
+ bytenetorder(&msg);
+ if (sendto(sock, (char *)&msg, sizeof(struct tsp), 0,
+ (struct sockaddr*)&net->dest_addr,
+ sizeof(struct sockaddr)) < 0) {
+ trace_sendto_err(net->dest_addr.sin_addr);
+ return(SLAVE);
+ }
+
+ (void)gettimeofday(&then, 0);
+ then.tv_sec += 3;
+ for (;;) {
+ (void)gettimeofday(&wait, 0);
+ timevalsub(&wait,&then,&wait);
+ resp = readmsg(TSP_ANY, ANYADDR, &wait, net);
+ if (!resp)
+ return(MASTER);
+
+ switch (resp->tsp_type) {
+
+ case TSP_ACCEPT:
+ (void)addmach(resp->tsp_name, &from,fromnet);
+ break;
+
+ case TSP_MASTERUP:
+ case TSP_MASTERREQ:
+ /*
+ * If another timedaemon is coming up at the same
+ * time, give up, and let it be the master.
+ */
+ if (++loop_lim < 5
+ && !good_host_name(resp->tsp_name)) {
+ (void)addmach(resp->tsp_name, &from,fromnet);
+ suppress(&from, resp->tsp_name, net);
+ goto again;
+ }
+ rmnetmachs(net);
+ return(SLAVE);
+
+ case TSP_QUIT:
+ case TSP_REFUSE:
+ /*
+ * Collision: change value of election timer
+ * using exponential backoff.
+ *
+ * Fooey.
+ * An exponential backoff on a delay starting at
+ * 6 to 15 minutes for a process that takes
+ * milliseconds is silly. It is particularly
+ * strange that the original code would increase
+ * the backoff without bound.
+ */
+ rmnetmachs(net);
+ return(SLAVE);
+
+ case TSP_ELECTION:
+ /* no master for another round */
+ htp = addmach(resp->tsp_name,&from,fromnet);
+ msg.tsp_type = TSP_REFUSE;
+ (void)strcpy(msg.tsp_name, hostname);
+ answer = acksend(&msg, &htp->addr, htp->name,
+ TSP_ACK, 0, htp->noanswer);
+ if (!answer) {
+ syslog(LOG_ERR, "error in election from %s",
+ htp->name);
+ }
+ break;
+
+ case TSP_SLAVEUP:
+ (void)addmach(resp->tsp_name, &from,fromnet);
+ break;
+
+ case TSP_SETDATE:
+ case TSP_SETDATEREQ:
+ break;
+
+ default:
+ if (trace) {
+ fprintf(fd, "candidate: ");
+ print(resp, &from);
+ }
+ break;
+ }
+ }
+}
diff --git a/usr.sbin/timed/timed/cksum.c b/usr.sbin/timed/timed/cksum.c
new file mode 100644
index 0000000..7fa0a5e
--- /dev/null
+++ b/usr.sbin/timed/timed/cksum.c
@@ -0,0 +1,81 @@
+/*-
+ * 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[] = "@(#)cksum.c 8.1 (Berkeley) 6/6/93";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <sys/types.h>
+
+/*
+ * I N _ C K S U M
+ *
+ * Checksum routine for Internet Protocol family headers (C Version)
+ *
+ * There is no profit in a specialized version of the checksum
+ * function for any machine where int's are 32 bits and shorts are 16.
+ *
+ * All timed packets are smaller than 32K shorts, so there is no need to
+ * worry about carries except at the end.
+ */
+int
+in_cksum(u_short *addr, int len)
+{
+ register int nleft = len;
+ register u_short *w = addr;
+ register u_short answer;
+ register int sum = 0;
+
+ /*
+ * Our algorithm is simple, using a 32 bit accumulator (sum),
+ * we add sequential 16 bit words to it, and at the end, fold
+ * back all the carry bits from the top 16 bits into the lower
+ * 16 bits.
+ */
+ while( nleft > 1 ) {
+ sum += *w++;
+ nleft -= 2;
+ }
+
+ /* mop up an odd byte, if necessary */
+ if( nleft == 1 )
+ sum += (*(u_char *)w) << 8;
+
+ /*
+ * 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);
+}
diff --git a/usr.sbin/timed/timed/correct.c b/usr.sbin/timed/timed/correct.c
new file mode 100644
index 0000000..8a5b464
--- /dev/null
+++ b/usr.sbin/timed/timed/correct.c
@@ -0,0 +1,190 @@
+/*-
+ * 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[] = "@(#)correct.c 8.1 (Berkeley) 6/6/93";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include "globals.h"
+#include <math.h>
+#include <sys/types.h>
+#include <sys/times.h>
+
+static void adjclock(struct timeval *);
+
+/*
+ * sends to the slaves the corrections for their clocks after fixing our
+ * own
+ */
+void
+correct(long avdelta)
+{
+ struct hosttbl *htp;
+ int corr;
+ struct timeval adjlocal, tmptv;
+ struct tsp to;
+ struct tsp *answer;
+
+ mstotvround(&adjlocal, avdelta);
+
+ for (htp = self.l_fwd; htp != &self; htp = htp->l_fwd) {
+ if (htp->delta != HOSTDOWN) {
+ corr = avdelta - htp->delta;
+/* If the other machine is off in the weeds, set its time directly.
+ * If a slave gets the wrong day, the original code would simply
+ * fix the minutes. If you fix a network partition, you can get
+ * into such situations.
+ */
+ if (htp->need_set
+ || corr >= MAXADJ*1000
+ || corr <= -MAXADJ*1000) {
+ htp->need_set = 0;
+ (void)gettimeofday(&tmptv,0);
+ timevaladd(&tmptv, &adjlocal);
+ to.tsp_time.tv_sec = tmptv.tv_sec;
+ to.tsp_time.tv_usec = tmptv.tv_usec;
+ to.tsp_type = TSP_SETTIME;
+ } else {
+ tmptv.tv_sec = to.tsp_time.tv_sec;
+ tmptv.tv_usec = to.tsp_time.tv_usec;
+ mstotvround(&tmptv, corr);
+ to.tsp_time.tv_sec = tmptv.tv_sec;
+ to.tsp_time.tv_usec = tmptv.tv_usec;
+ to.tsp_type = TSP_ADJTIME;
+ }
+ (void)strcpy(to.tsp_name, hostname);
+ answer = acksend(&to, &htp->addr, htp->name,
+ TSP_ACK, 0, 0);
+ if (!answer) {
+ htp->delta = HOSTDOWN;
+ syslog(LOG_WARNING,
+ "no reply to time correction from %s",
+ htp->name);
+ if (++htp->noanswer >= LOSTHOST) {
+ if (trace) {
+ fprintf(fd,
+ "purging %s for not answering\n",
+ htp->name);
+ (void)fflush(fd);
+ }
+ htp = remmach(htp);
+ }
+ }
+ }
+ }
+
+ /*
+ * adjust our own clock now that we are not sending it out
+ */
+ adjclock(&adjlocal);
+}
+
+
+static void
+adjclock(struct timeval *corr)
+{
+ static int passes = 0;
+ static int smoother = 0;
+ long delta; /* adjustment in usec */
+ long ndelta;
+ struct timeval now;
+ struct timeval adj;
+
+ if (!timerisset(corr))
+ return;
+
+ adj = *corr;
+ if (adj.tv_sec < MAXADJ && adj.tv_sec > - MAXADJ) {
+ delta = adj.tv_sec*1000000 + adj.tv_usec;
+ /* If the correction is less than the minimum round
+ * trip time for an ICMP packet, and thus
+ * less than the likely error in the measurement,
+ * do not do the entire correction. Do half
+ * or a quarter of it.
+ */
+
+ if (delta > -MIN_ROUND*1000
+ && delta < MIN_ROUND*1000) {
+ if (smoother <= 4)
+ smoother++;
+ ndelta = delta >> smoother;
+ if (trace)
+ fprintf(fd,
+ "trimming delta %ld usec to %ld\n",
+ delta, ndelta);
+ adj.tv_usec = ndelta;
+ adj.tv_sec = 0;
+ } else if (smoother > 0) {
+ smoother--;
+ }
+ if (0 > adjtime(corr, 0)) {
+ syslog(LOG_ERR, "adjtime: %m");
+ }
+ if (passes > 1
+ && (delta < -BIG_ADJ || delta > BIG_ADJ)) {
+ smoother = 0;
+ passes = 0;
+ syslog(LOG_WARNING,
+ "large time adjustment of %+.3f sec",
+ delta/1000000.0);
+ }
+ } else {
+ syslog(LOG_WARNING,
+ "clock correction %jd sec too large to adjust",
+ (intmax_t)adj.tv_sec);
+ (void) gettimeofday(&now, 0);
+ timevaladd(&now, corr);
+ if (settimeofday(&now, 0) < 0)
+ syslog(LOG_ERR, "settimeofday: %m");
+ }
+}
+
+
+/* adjust the time in a message by the time it
+ * spent in the queue
+ */
+void
+adj_msg_time(struct tsp *msg, struct timeval *now)
+{
+ msg->tsp_time.tv_sec += (now->tv_sec - from_when.tv_sec);
+ msg->tsp_time.tv_usec += (now->tv_usec - from_when.tv_usec);
+
+ while (msg->tsp_time.tv_usec < 0) {
+ msg->tsp_time.tv_sec--;
+ msg->tsp_time.tv_usec += 1000000;
+ }
+ while (msg->tsp_time.tv_usec >= 1000000) {
+ msg->tsp_time.tv_sec++;
+ msg->tsp_time.tv_usec -= 1000000;
+ }
+}
diff --git a/usr.sbin/timed/timed/extern.h b/usr.sbin/timed/timed/extern.h
new file mode 100644
index 0000000..b17afe0
--- /dev/null
+++ b/usr.sbin/timed/timed/extern.h
@@ -0,0 +1,87 @@
+/* $FreeBSD$ */
+
+/*-
+ * 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.
+ *
+ * @(#)extern.h 8.1 (Berkeley) 6/6/93
+ */
+
+struct hosttbl;
+struct netinfo;
+struct sockaddr_in;
+struct timeval;
+struct tsp;
+
+struct hosttbl *addmach(char *, struct sockaddr_in *, struct netinfo *);
+struct hosttbl *findhost(char *);
+struct hosttbl *remmach(struct hosttbl *);
+
+struct tsp *readmsg(int,
+ char *, struct timeval *, struct netinfo *);
+struct tsp *acksend(struct tsp *,
+ struct sockaddr_in *, char *, int, struct netinfo *, int);
+
+void addnetname(char *);
+void adj_msg_time(struct tsp *, struct timeval *);
+void bytehostorder(struct tsp *);
+void bytenetorder(struct tsp *);
+void byteorder(struct tsp *);
+long casual(long, long);
+int cksum(u_short *, int);
+void correct(long);
+char *date(void);
+void doquit(struct tsp *);
+int election(struct netinfo *);
+void get_goodgroup(int);
+int good_host_name(char *);
+void ignoreack(void);
+int in_cksum(u_short *, int);
+void lookformaster(struct netinfo *);
+void makeslave(struct netinfo *);
+int master(void);
+void masterack(void);
+void masterup(struct netinfo *);
+int measure(u_long, u_long, char *, struct sockaddr_in *, int);
+void msterup(struct netinfo *);
+void mstotvround(struct timeval *, long);
+long networkdelta(void);
+void newslave(struct tsp *);
+void print(struct tsp *, struct sockaddr_in *);
+void prthp(clock_t);
+void rmnetmachs(struct netinfo *);
+void setstatus(void);
+int slave(void);
+void slaveack(void);
+void spreadtime(void);
+void suppress(struct sockaddr_in *, char *, struct netinfo *);
+void synch(long);
+void timevaladd(struct timeval *, struct timeval *);
+void timevalsub(struct timeval *, struct timeval *, struct timeval *);
+void traceoff(char *);
+void traceon(void);
+void xmit(int, u_int, struct sockaddr_in *);
diff --git a/usr.sbin/timed/timed/globals.h b/usr.sbin/timed/timed/globals.h
new file mode 100644
index 0000000..0492d79
--- /dev/null
+++ b/usr.sbin/timed/timed/globals.h
@@ -0,0 +1,169 @@
+/*-
+ * 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.
+ *
+ * @(#)globals.h 8.1 (Berkeley) 6/6/93
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <netdb.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <protocols/timed.h>
+#define SECHR (60*60)
+#define SECDAY (24*SECHR)
+
+/* Best expected round trip for a measurement.
+ * This is essentially the number of milliseconds per CPU tick (CLK_TCK?).
+ * All delays shorter than this are usually reported as 0.
+ */
+#define MIN_ROUND ((1000-1)/CLK_TCK)
+
+
+#define SAMPLEINTVL 240 /* synch() freq for master in sec */
+#define MAXADJ 20 /* max adjtime() correction in sec */
+
+#define MAX_TRIM 3000000 /* max drift in nsec/sec, 0.3% */
+#define BIG_ADJ (MAX_TRIM/1000*SAMPLEINTVL*2) /* max good adj */
+
+#define MINTOUT 360 /* election delays, 6-15 minutes */
+#define MAXTOUT 900
+
+#define BAD_STATUS (-1)
+#define GOOD 1
+#define UNREACHABLE 2
+#define NONSTDTIME 3
+#define HOSTDOWN 0x7fffffff
+
+#define OFF 0
+#define ON 1
+
+#define MAX_HOPCNT 10 /* max value for tsp_hpcnt */
+
+#define LOSTHOST 3 /* forget after this many failures */
+
+#define VALID_RANGE (MAXADJ*1000) /* good times in milliseconds */
+#define GOOD_RANGE (MIN_ROUND*2)
+#define VGOOD_RANGE (MIN_ROUND-1)
+
+
+/*
+ * Global and per-network states.
+ */
+#define NOMASTER 0 /* no good master */
+#define SLAVE 1
+#define MASTER 2
+#define IGNORE 4
+#define ALL (SLAVE|MASTER|IGNORE)
+#define SUBMASTER (SLAVE|MASTER)
+
+#define NHOSTS 1013 /* max of hosts controlled by timed
+ * This must be a prime number.
+ */
+struct hosttbl {
+ struct hosttbl *h_bak; /* hash chain */
+ struct hosttbl *h_fwd;
+ struct hosttbl *l_bak; /* "sequential" list */
+ struct hosttbl *l_fwd;
+ struct netinfo *ntp;
+ struct sockaddr_in addr;
+ char name[MAXHOSTNAMELEN];
+ u_char head; /* 1=head of hash chain */
+ u_char good; /* 0=trusted host, for averaging */
+ u_char noanswer; /* count of failures to answer */
+ u_char need_set; /* need a SETTIME */
+ u_short seq;
+ long delta;
+};
+
+/* closed hash table with internal chaining */
+extern struct hosttbl hosttbl[NHOSTS+1];
+#define self hosttbl[0]
+#define hostname (self.name)
+
+
+struct netinfo {
+ struct netinfo *next;
+ struct in_addr net;
+ u_int32_t mask;
+ struct in_addr my_addr;
+ struct sockaddr_in dest_addr; /* broadcast addr or point-point */
+ long status;
+ struct timeval slvwait; /* delay before sending our time */
+ int quit_count; /* recent QUITs */
+};
+
+#include "extern.h"
+
+#define tvtomsround(tv) ((tv).tv_sec*1000 + ((tv).tv_usec + 500)/1000)
+
+extern struct netinfo *nettab;
+extern int status;
+extern int trace;
+extern int sock;
+extern struct sockaddr_in from;
+extern struct timeval from_when; /* when the last msg arrived */
+extern u_short sequence; /* TSP message sequence number */
+extern struct netinfo *fromnet, *slavenet;
+extern FILE *fd;
+extern long delay1, delay2;
+extern int nslavenets; /* nets were I could be a slave */
+extern int nmasternets; /* nets were I could be a master */
+extern int nignorednets; /* ignored nets */
+extern int nnets; /* nets I am connected to */
+
+
+#define trace_msg(msg) {if (trace) fprintf(fd, msg);}
+
+#define trace_sendto_err(addr) { \
+ int st_errno = errno; \
+ syslog(LOG_ERR, "%s %d: sendto %s: %m", \
+ __FILE__, __LINE__, inet_ntoa(addr)); \
+ if (trace) \
+ fprintf(fd, "%s %d: sendto %s: %d", __FILE__, __LINE__, \
+ inet_ntoa(addr), st_errno); \
+}
+
+
+# define max(a,b) (a<b ? b : a)
+# define min(a,b) (a>b ? b : a)
+# define abs(x) (x>=0 ? x : -(x))
diff --git a/usr.sbin/timed/timed/master.c b/usr.sbin/timed/timed/master.c
new file mode 100644
index 0000000..9a130bf
--- /dev/null
+++ b/usr.sbin/timed/timed/master.c
@@ -0,0 +1,839 @@
+/*-
+ * 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[] = "@(#)master.c 8.1 (Berkeley) 6/6/93";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include "globals.h"
+#include <sys/file.h>
+#include <sys/types.h>
+#include <sys/times.h>
+#include <setjmp.h>
+#include <utmpx.h>
+#include "pathnames.h"
+
+extern int measure_delta;
+extern jmp_buf jmpenv;
+extern int Mflag;
+extern int justquit;
+
+static int dictate;
+static int slvcount; /* slaves listening to our clock */
+
+static void mchgdate(struct tsp *);
+
+/*
+ * The main function of `master' is to periodically compute the differences
+ * (deltas) between its clock and the clocks of the slaves, to compute the
+ * network average delta, and to send to the slaves the differences between
+ * their individual deltas and the network delta.
+ * While waiting, it receives messages from the slaves (i.e. requests for
+ * master's name, remote requests to set the network time, ...), and
+ * takes the appropriate action.
+ */
+int
+master(void)
+{
+ struct hosttbl *htp;
+ long pollingtime;
+#define POLLRATE 4
+ int polls;
+ struct timeval wait, ntime;
+ time_t tsp_time_sec;
+ struct tsp *msg, *answer, to;
+ char newdate[32];
+ struct sockaddr_in taddr;
+ char tname[MAXHOSTNAMELEN];
+ struct netinfo *ntp;
+ int i;
+
+ syslog(LOG_NOTICE, "This machine is master");
+ if (trace)
+ fprintf(fd, "This machine is master\n");
+ for (ntp = nettab; ntp != NULL; ntp = ntp->next) {
+ if (ntp->status == MASTER)
+ masterup(ntp);
+ }
+ (void)gettimeofday(&ntime, NULL);
+ pollingtime = ntime.tv_sec+3;
+ if (justquit)
+ polls = 0;
+ else
+ polls = POLLRATE-1;
+
+/* Process all outstanding messages before spending the long time necessary
+ * to update all timers.
+ */
+loop:
+ (void)gettimeofday(&ntime, NULL);
+ wait.tv_sec = pollingtime - ntime.tv_sec;
+ if (wait.tv_sec < 0)
+ wait.tv_sec = 0;
+ wait.tv_usec = 0;
+ msg = readmsg(TSP_ANY, ANYADDR, &wait, 0);
+ if (!msg) {
+ (void)gettimeofday(&ntime, NULL);
+ if (ntime.tv_sec >= pollingtime) {
+ pollingtime = ntime.tv_sec + SAMPLEINTVL;
+ get_goodgroup(0);
+
+/* If a bogus master told us to quit, we can have decided to ignore a
+ * network. Therefore, periodically try to take over everything.
+ */
+ polls = (polls + 1) % POLLRATE;
+ if (0 == polls && nignorednets > 0) {
+ trace_msg("Looking for nets to re-master\n");
+ for (ntp = nettab; ntp; ntp = ntp->next) {
+ if (ntp->status == IGNORE
+ || ntp->status == NOMASTER) {
+ lookformaster(ntp);
+ if (ntp->status == MASTER) {
+ masterup(ntp);
+ polls = POLLRATE-1;
+ }
+ }
+ if (ntp->status == MASTER
+ && --ntp->quit_count < 0)
+ ntp->quit_count = 0;
+ }
+ if (polls != 0)
+ setstatus();
+ }
+
+ synch(0L);
+
+ for (ntp = nettab; ntp != NULL; ntp = ntp->next) {
+ to.tsp_type = TSP_LOOP;
+ to.tsp_vers = TSPVERSION;
+ to.tsp_seq = sequence++;
+ to.tsp_hopcnt = MAX_HOPCNT;
+ (void)strcpy(to.tsp_name, hostname);
+ bytenetorder(&to);
+ if (sendto(sock, (char *)&to,
+ sizeof(struct tsp), 0,
+ (struct sockaddr*)&ntp->dest_addr,
+ sizeof(ntp->dest_addr)) < 0) {
+ trace_sendto_err(ntp->dest_addr.sin_addr);
+ }
+ }
+ }
+
+
+ } else {
+ switch (msg->tsp_type) {
+
+ case TSP_MASTERREQ:
+ break;
+
+ case TSP_SLAVEUP:
+ newslave(msg);
+ break;
+
+ case TSP_SETDATE:
+ /*
+ * XXX check to see it is from ourself
+ */
+ tsp_time_sec = msg->tsp_time.tv_sec;
+ (void)strcpy(newdate, ctime(&tsp_time_sec));
+ if (!good_host_name(msg->tsp_name)) {
+ syslog(LOG_NOTICE,
+ "attempted date change by %s to %s",
+ msg->tsp_name, newdate);
+ spreadtime();
+ break;
+ }
+
+ mchgdate(msg);
+ (void)gettimeofday(&ntime, NULL);
+ pollingtime = ntime.tv_sec + SAMPLEINTVL;
+ break;
+
+ case TSP_SETDATEREQ:
+ if (!fromnet || fromnet->status != MASTER)
+ break;
+ tsp_time_sec = msg->tsp_time.tv_sec;
+ (void)strcpy(newdate, ctime(&tsp_time_sec));
+ htp = findhost(msg->tsp_name);
+ if (htp == 0) {
+ syslog(LOG_ERR,
+ "attempted SET DATEREQ by uncontrolled %s to %s",
+ msg->tsp_name, newdate);
+ break;
+ }
+ if (htp->seq == msg->tsp_seq)
+ break;
+ htp->seq = msg->tsp_seq;
+ if (!htp->good) {
+ syslog(LOG_NOTICE,
+ "attempted SET DATEREQ by untrusted %s to %s",
+ msg->tsp_name, newdate);
+ spreadtime();
+ break;
+ }
+
+ mchgdate(msg);
+ (void)gettimeofday(&ntime, NULL);
+ pollingtime = ntime.tv_sec + SAMPLEINTVL;
+ break;
+
+ case TSP_MSITE:
+ xmit(TSP_ACK, msg->tsp_seq, &from);
+ break;
+
+ case TSP_MSITEREQ:
+ break;
+
+ case TSP_TRACEON:
+ traceon();
+ break;
+
+ case TSP_TRACEOFF:
+ traceoff("Tracing ended at %s\n");
+ break;
+
+ case TSP_ELECTION:
+ if (!fromnet)
+ break;
+ if (fromnet->status == MASTER) {
+ pollingtime = 0;
+ (void)addmach(msg->tsp_name, &from,fromnet);
+ }
+ taddr = from;
+ (void)strcpy(tname, msg->tsp_name);
+ to.tsp_type = TSP_QUIT;
+ (void)strcpy(to.tsp_name, hostname);
+ answer = acksend(&to, &taddr, tname,
+ TSP_ACK, 0, 1);
+ if (answer == NULL) {
+ syslog(LOG_ERR, "election error by %s",
+ tname);
+ }
+ break;
+
+ case TSP_CONFLICT:
+ /*
+ * After a network partition, there can be
+ * more than one master: the first slave to
+ * come up will notify here the situation.
+ */
+ if (!fromnet || fromnet->status != MASTER)
+ break;
+ (void)strcpy(to.tsp_name, hostname);
+
+ /* The other master often gets into the same state,
+ * with boring results if we stay at it forever.
+ */
+ ntp = fromnet; /* (acksend() can leave fromnet=0 */
+ for (i = 0; i < 3; i++) {
+ to.tsp_type = TSP_RESOLVE;
+ (void)strcpy(to.tsp_name, hostname);
+ answer = acksend(&to, &ntp->dest_addr,
+ ANYADDR, TSP_MASTERACK,
+ ntp, 0);
+ if (!answer)
+ break;
+ htp = addmach(answer->tsp_name,&from,ntp);
+ to.tsp_type = TSP_QUIT;
+ msg = acksend(&to, &htp->addr, htp->name,
+ TSP_ACK, 0, htp->noanswer);
+ if (msg == NULL) {
+ syslog(LOG_ERR,
+ "no response from %s to CONFLICT-QUIT",
+ htp->name);
+ }
+ }
+ masterup(ntp);
+ pollingtime = 0;
+ break;
+
+ case TSP_RESOLVE:
+ if (!fromnet || fromnet->status != MASTER)
+ break;
+ /*
+ * do not want to call synch() while waiting
+ * to be killed!
+ */
+ (void)gettimeofday(&ntime, NULL);
+ pollingtime = ntime.tv_sec + SAMPLEINTVL;
+ break;
+
+ case TSP_QUIT:
+ doquit(msg); /* become a slave */
+ break;
+
+ case TSP_LOOP:
+ if (!fromnet || fromnet->status != MASTER
+ || !strcmp(msg->tsp_name, hostname))
+ break;
+ /*
+ * We should not have received this from a net
+ * we are master on. There must be two masters.
+ */
+ htp = addmach(msg->tsp_name, &from,fromnet);
+ to.tsp_type = TSP_QUIT;
+ (void)strcpy(to.tsp_name, hostname);
+ answer = acksend(&to, &htp->addr, htp->name,
+ TSP_ACK, 0, 1);
+ if (!answer) {
+ syslog(LOG_WARNING,
+ "loop breakage: no reply from %s=%s to QUIT",
+ htp->name, inet_ntoa(htp->addr.sin_addr));
+ (void)remmach(htp);
+ }
+
+ case TSP_TEST:
+ if (trace) {
+ fprintf(fd,
+ "\tnets = %d, masters = %d, slaves = %d, ignored = %d\n",
+ nnets, nmasternets, nslavenets, nignorednets);
+ setstatus();
+ }
+ pollingtime = 0;
+ polls = POLLRATE-1;
+ break;
+
+ default:
+ if (trace) {
+ fprintf(fd, "garbage message: ");
+ print(msg, &from);
+ }
+ break;
+ }
+ }
+ goto loop;
+}
+
+
+/*
+ * change the system date on the master
+ */
+static void
+mchgdate(struct tsp *msg)
+{
+ char tname[MAXHOSTNAMELEN];
+ char olddate[32];
+ struct timeval otime, ntime, tmptv;
+ struct utmpx utx;
+
+ (void)strcpy(tname, msg->tsp_name);
+
+ xmit(TSP_DATEACK, msg->tsp_seq, &from);
+
+ (void)strcpy(olddate, date());
+
+ /* adjust time for residence on the queue */
+ (void)gettimeofday(&otime, NULL);
+ adj_msg_time(msg,&otime);
+
+ tmptv.tv_sec = msg->tsp_time.tv_sec;
+ tmptv.tv_usec = msg->tsp_time.tv_usec;
+ timevalsub(&ntime, &tmptv, &otime);
+ if (ntime.tv_sec < MAXADJ && ntime.tv_sec > -MAXADJ) {
+ /*
+ * do not change the clock if we can adjust it
+ */
+ dictate = 3;
+ synch(tvtomsround(ntime));
+ } else {
+ utx.ut_type = OLD_TIME;
+ (void)gettimeofday(&utx.ut_tv, NULL);
+ pututxline(&utx);
+ (void)settimeofday(&tmptv, 0);
+ utx.ut_type = NEW_TIME;
+ (void)gettimeofday(&utx.ut_tv, NULL);
+ pututxline(&utx);
+ spreadtime();
+ }
+
+ syslog(LOG_NOTICE, "date changed by %s from %s",
+ tname, olddate);
+}
+
+
+/*
+ * synchronize all of the slaves
+ */
+void
+synch(long mydelta)
+{
+ struct hosttbl *htp;
+ int measure_status;
+ struct timeval check, stop, wait;
+
+ if (slvcount > 0) {
+ if (trace)
+ fprintf(fd, "measurements starting at %s\n", date());
+ (void)gettimeofday(&check, NULL);
+ for (htp = self.l_fwd; htp != &self; htp = htp->l_fwd) {
+ if (htp->noanswer != 0) {
+ measure_status = measure(500, 100,
+ htp->name,
+ &htp->addr,0);
+ } else {
+ measure_status = measure(3000, 100,
+ htp->name,
+ &htp->addr,0);
+ }
+ if (measure_status != GOOD) {
+ /* The slave did not respond. We have
+ * just wasted lots of time on it.
+ */
+ htp->delta = HOSTDOWN;
+ if (++htp->noanswer >= LOSTHOST) {
+ if (trace) {
+ fprintf(fd,
+ "purging %s for not answering ICMP\n",
+ htp->name);
+ (void)fflush(fd);
+ }
+ htp = remmach(htp);
+ }
+ } else {
+ htp->delta = measure_delta;
+ }
+ (void)gettimeofday(&stop, NULL);
+ timevalsub(&stop, &stop, &check);
+ if (stop.tv_sec >= 1) {
+ if (trace)
+ (void)fflush(fd);
+ /*
+ * ack messages periodically
+ */
+ wait.tv_sec = 0;
+ wait.tv_usec = 0;
+ if (0 != readmsg(TSP_TRACEON,ANYADDR,
+ &wait,0))
+ traceon();
+ (void)gettimeofday(&check, NULL);
+ }
+ }
+ if (trace)
+ fprintf(fd, "measurements finished at %s\n", date());
+ }
+ if (!(status & SLAVE)) {
+ if (!dictate) {
+ mydelta = networkdelta();
+ } else {
+ dictate--;
+ }
+ }
+ if (trace && (mydelta != 0 || (status & SLAVE)))
+ fprintf(fd,"local correction of %ld ms.\n", mydelta);
+ correct(mydelta);
+}
+
+/*
+ * sends the time to each slave after the master
+ * has received the command to set the network time
+ */
+void
+spreadtime(void)
+{
+ struct hosttbl *htp;
+ struct tsp to;
+ struct tsp *answer;
+ struct timeval tmptv;
+
+/* Do not listen to the consensus after forcing the time. This is because
+ * the consensus takes a while to reach the time we are dictating.
+ */
+ dictate = 2;
+ for (htp = self.l_fwd; htp != &self; htp = htp->l_fwd) {
+ to.tsp_type = TSP_SETTIME;
+ (void)strcpy(to.tsp_name, hostname);
+ (void)gettimeofday(&tmptv, NULL);
+ to.tsp_time.tv_sec = tmptv.tv_sec;
+ to.tsp_time.tv_usec = tmptv.tv_usec;
+ answer = acksend(&to, &htp->addr, htp->name,
+ TSP_ACK, 0, htp->noanswer);
+ if (answer == 0) {
+ /* We client does not respond, then we have
+ * just wasted lots of time on it.
+ */
+ syslog(LOG_WARNING,
+ "no reply to SETTIME from %s", htp->name);
+ if (++htp->noanswer >= LOSTHOST) {
+ if (trace) {
+ fprintf(fd,
+ "purging %s for not answering",
+ htp->name);
+ (void)fflush(fd);
+ }
+ htp = remmach(htp);
+ }
+ }
+ }
+}
+
+void
+prthp(clock_t delta)
+{
+ static time_t next_time;
+ time_t this_time;
+ struct tms tm;
+ struct hosttbl *htp;
+ int length, l;
+ int i;
+
+ if (!fd) /* quit if tracing already off */
+ return;
+
+ this_time = times(&tm);
+ if ((time_t)(this_time + delta) < next_time)
+ return;
+ next_time = this_time + CLK_TCK;
+
+ fprintf(fd, "host table: %d entries at %s\n", slvcount, date());
+ htp = self.l_fwd;
+ length = 1;
+ for (i = 1; i <= slvcount; i++, htp = htp->l_fwd) {
+ l = strlen(htp->name) + 1;
+ if (length+l >= 80) {
+ fprintf(fd, "\n");
+ length = 0;
+ }
+ length += l;
+ fprintf(fd, " %s", htp->name);
+ }
+ fprintf(fd, "\n");
+}
+
+
+static struct hosttbl *newhost_hash;
+static struct hosttbl *lasthfree = &hosttbl[0];
+
+
+struct hosttbl * /* answer or 0 */
+findhost(char *name)
+{
+ int i, j;
+ struct hosttbl *htp;
+ char *p;
+
+ j= 0;
+ for (p = name, i = 0; i < 8 && *p != '\0'; i++, p++)
+ j = (j << 2) ^ *p;
+ newhost_hash = &hosttbl[j % NHOSTS];
+
+ htp = newhost_hash;
+ if (htp->name[0] == '\0')
+ return(0);
+ do {
+ if (!strcmp(name, htp->name))
+ return(htp);
+ htp = htp->h_fwd;
+ } while (htp != newhost_hash);
+ return(0);
+}
+
+/*
+ * add a host to the list of controlled machines if not already there
+ */
+struct hosttbl *
+addmach(char *name, struct sockaddr_in *addr, struct netinfo *ntp)
+{
+ struct hosttbl *ret, *p, *b, *f;
+
+ ret = findhost(name);
+ if (ret == 0) {
+ if (slvcount >= NHOSTS) {
+ if (trace) {
+ fprintf(fd, "no more slots in host table\n");
+ prthp(CLK_TCK);
+ }
+ syslog(LOG_ERR, "no more slots in host table");
+ Mflag = 0;
+ longjmp(jmpenv, 2); /* give up and be a slave */
+ }
+
+ /* if our home hash slot is occupied, find a free entry
+ * in the hash table
+ */
+ if (newhost_hash->name[0] != '\0') {
+ do {
+ ret = lasthfree;
+ if (++lasthfree > &hosttbl[NHOSTS])
+ lasthfree = &hosttbl[1];
+ } while (ret->name[0] != '\0');
+
+ if (!newhost_hash->head) {
+ /* Move an interloper using our home. Use
+ * scratch pointers in case the new head is
+ * pointing to itself.
+ */
+ f = newhost_hash->h_fwd;
+ b = newhost_hash->h_bak;
+ f->h_bak = ret;
+ b->h_fwd = ret;
+ f = newhost_hash->l_fwd;
+ b = newhost_hash->l_bak;
+ f->l_bak = ret;
+ b->l_fwd = ret;
+ bcopy(newhost_hash,ret,sizeof(*ret));
+ ret = newhost_hash;
+ ret->head = 1;
+ ret->h_fwd = ret;
+ ret->h_bak = ret;
+ } else {
+ /* link to an existing chain in our home
+ */
+ ret->head = 0;
+ p = newhost_hash->h_bak;
+ ret->h_fwd = newhost_hash;
+ ret->h_bak = p;
+ p->h_fwd = ret;
+ newhost_hash->h_bak = ret;
+ }
+ } else {
+ ret = newhost_hash;
+ ret->head = 1;
+ ret->h_fwd = ret;
+ ret->h_bak = ret;
+ }
+ ret->addr = *addr;
+ ret->ntp = ntp;
+ (void)strncpy(ret->name, name, sizeof(ret->name));
+ ret->good = good_host_name(name);
+ ret->l_fwd = &self;
+ ret->l_bak = self.l_bak;
+ self.l_bak->l_fwd = ret;
+ self.l_bak = ret;
+ slvcount++;
+
+ ret->noanswer = 0;
+ ret->need_set = 1;
+
+ } else {
+ ret->noanswer = (ret->noanswer != 0);
+ }
+
+ /* need to clear sequence number anyhow */
+ ret->seq = 0;
+ return(ret);
+}
+
+/*
+ * remove the machine with the given index in the host table.
+ */
+struct hosttbl *
+remmach(struct hosttbl *htp)
+{
+ struct hosttbl *lprv, *hnxt, *f, *b;
+
+ if (trace)
+ fprintf(fd, "remove %s\n", htp->name);
+
+ /* get out of the lists */
+ htp->l_fwd->l_bak = lprv = htp->l_bak;
+ htp->l_bak->l_fwd = htp->l_fwd;
+ htp->h_fwd->h_bak = htp->h_bak;
+ htp->h_bak->h_fwd = hnxt = htp->h_fwd;
+
+ /* If we are in the home slot, pull up the chain */
+ if (htp->head && hnxt != htp) {
+ if (lprv == hnxt)
+ lprv = htp;
+
+ /* Use scratch pointers in case the new head is pointing to
+ * itself.
+ */
+ f = hnxt->h_fwd;
+ b = hnxt->h_bak;
+ f->h_bak = htp;
+ b->h_fwd = htp;
+ f = hnxt->l_fwd;
+ b = hnxt->l_bak;
+ f->l_bak = htp;
+ b->l_fwd = htp;
+ hnxt->head = 1;
+ bcopy(hnxt, htp, sizeof(*htp));
+ lasthfree = hnxt;
+ } else {
+ lasthfree = htp;
+ }
+
+ lasthfree->name[0] = '\0';
+ lasthfree->h_fwd = 0;
+ lasthfree->l_fwd = 0;
+ slvcount--;
+
+ return lprv;
+}
+
+
+/*
+ * Remove all the machines from the host table that exist on the given
+ * network. This is called when a master transitions to a slave on a
+ * given network.
+ */
+void
+rmnetmachs(struct netinfo *ntp)
+{
+ struct hosttbl *htp;
+
+ if (trace)
+ prthp(CLK_TCK);
+ for (htp = self.l_fwd; htp != &self; htp = htp->l_fwd) {
+ if (ntp == htp->ntp)
+ htp = remmach(htp);
+ }
+ if (trace)
+ prthp(CLK_TCK);
+}
+
+void
+masterup(struct netinfo *net)
+{
+ xmit(TSP_MASTERUP, 0, &net->dest_addr);
+
+ /*
+ * Do not tell new slaves our time for a while. This ensures
+ * we do not tell them to start using our time, before we have
+ * found a good master.
+ */
+ (void)gettimeofday(&net->slvwait, NULL);
+}
+
+void
+newslave(struct tsp *msg)
+{
+ struct hosttbl *htp;
+ struct tsp *answer, to;
+ struct timeval now, tmptv;
+
+ if (!fromnet || fromnet->status != MASTER)
+ return;
+
+ htp = addmach(msg->tsp_name, &from,fromnet);
+ htp->seq = msg->tsp_seq;
+ if (trace)
+ prthp(0);
+
+ /*
+ * If we are stable, send our time to the slave.
+ * Do not go crazy if the date has been changed.
+ */
+ (void)gettimeofday(&now, NULL);
+ if (now.tv_sec >= fromnet->slvwait.tv_sec+3
+ || now.tv_sec < fromnet->slvwait.tv_sec) {
+ to.tsp_type = TSP_SETTIME;
+ (void)strcpy(to.tsp_name, hostname);
+ (void)gettimeofday(&tmptv, NULL);
+ to.tsp_time.tv_sec = tmptv.tv_sec;
+ to.tsp_time.tv_usec = tmptv.tv_usec;
+ answer = acksend(&to, &htp->addr,
+ htp->name, TSP_ACK,
+ 0, htp->noanswer);
+ if (answer) {
+ htp->need_set = 0;
+ } else {
+ syslog(LOG_WARNING,
+ "no reply to initial SETTIME from %s",
+ htp->name);
+ htp->noanswer = LOSTHOST;
+ }
+ }
+}
+
+
+/*
+ * react to a TSP_QUIT:
+ */
+void
+doquit(struct tsp *msg)
+{
+ if (fromnet->status == MASTER) {
+ if (!good_host_name(msg->tsp_name)) {
+ if (fromnet->quit_count <= 0) {
+ syslog(LOG_NOTICE,"untrusted %s told us QUIT",
+ msg->tsp_name);
+ suppress(&from, msg->tsp_name, fromnet);
+ fromnet->quit_count = 1;
+ return;
+ }
+ syslog(LOG_NOTICE, "untrusted %s told us QUIT twice",
+ msg->tsp_name);
+ fromnet->quit_count = 2;
+ fromnet->status = NOMASTER;
+ } else {
+ fromnet->status = SLAVE;
+ }
+ rmnetmachs(fromnet);
+ longjmp(jmpenv, 2); /* give up and be a slave */
+
+ } else {
+ if (!good_host_name(msg->tsp_name)) {
+ syslog(LOG_NOTICE, "untrusted %s told us QUIT",
+ msg->tsp_name);
+ fromnet->quit_count = 2;
+ }
+ }
+}
+
+void
+traceon(void)
+{
+ if (!fd) {
+ fd = fopen(_PATH_TIMEDLOG, "w");
+ if (!fd) {
+ trace = 0;
+ return;
+ }
+ fprintf(fd,"Tracing started at %s\n", date());
+ }
+ trace = 1;
+ get_goodgroup(1);
+ setstatus();
+ prthp(CLK_TCK);
+}
+
+
+void
+traceoff(char *msg)
+{
+ get_goodgroup(1);
+ setstatus();
+ prthp(CLK_TCK);
+ if (trace) {
+ fprintf(fd, msg, date());
+ (void)fclose(fd);
+ fd = 0;
+ }
+#ifdef GPROF
+ moncontrol(0);
+ _mcleanup();
+ moncontrol(1);
+#endif
+ trace = OFF;
+}
diff --git a/usr.sbin/timed/timed/measure.c b/usr.sbin/timed/timed/measure.c
new file mode 100644
index 0000000..8fe43b5
--- /dev/null
+++ b/usr.sbin/timed/timed/measure.c
@@ -0,0 +1,336 @@
+/*-
+ * 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[] = "@(#)measure.c 8.1 (Berkeley) 6/6/93";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include "globals.h"
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+
+#define MSEC_DAY (SECDAY*1000)
+
+#define PACKET_IN 1024
+
+#define MSGS 5 /* timestamps to average */
+#define TRIALS 10 /* max # of timestamps sent */
+
+extern int sock_raw;
+
+int measure_delta;
+
+static n_short seqno = 0;
+
+/*
+ * Measures the differences between machines' clocks using
+ * ICMP timestamp messages.
+ * maxmsec wait this many msec at most
+ * wmsec msec to wait for an answer
+ * print print complaints on stderr
+ */
+int /* status val defined in globals.h */
+measure(u_long maxmsec, u_long wmsec, char *hname, struct sockaddr_in *addr, int print)
+{
+ int length;
+ int measure_status;
+ int rcvcount, trials;
+ int cc, count;
+ fd_set ready;
+ long sendtime, recvtime, histime1, histime2;
+ long idelta, odelta, total;
+ long min_idelta, min_odelta;
+ struct timeval tdone, tcur, ttrans, twait, tout;
+ u_char packet[PACKET_IN], opacket[64];
+ register struct icmp *icp = (struct icmp *) packet;
+ register struct icmp *oicp = (struct icmp *) opacket;
+ struct ip *ip = (struct ip *) packet;
+
+ min_idelta = min_odelta = 0x7fffffff;
+ measure_status = HOSTDOWN;
+ measure_delta = HOSTDOWN;
+ trials = 0;
+ errno = 0;
+
+ /* open raw socket used to measure time differences */
+ if (sock_raw < 0) {
+ sock_raw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
+ if (sock_raw < 0) {
+ syslog(LOG_ERR, "opening raw socket: %m");
+ goto quit;
+ }
+ }
+
+
+ /*
+ * empty the icmp input queue
+ */
+ FD_ZERO(&ready);
+ for (;;) {
+ tout.tv_sec = tout.tv_usec = 0;
+ FD_SET(sock_raw, &ready);
+ if (select(sock_raw+1, &ready, 0,0, &tout)) {
+ length = sizeof(struct sockaddr_in);
+ cc = recvfrom(sock_raw, (char *)packet, PACKET_IN, 0,
+ 0,&length);
+ if (cc < 0)
+ goto quit;
+ continue;
+ }
+ break;
+ }
+
+ /*
+ * Choose the smallest transmission time in each of the two
+ * directions. Use these two latter quantities to compute the delta
+ * between the two clocks.
+ */
+
+ oicp->icmp_type = ICMP_TSTAMP;
+ oicp->icmp_code = 0;
+ oicp->icmp_id = getpid();
+ oicp->icmp_rtime = 0;
+ oicp->icmp_ttime = 0;
+ oicp->icmp_seq = seqno;
+
+ FD_ZERO(&ready);
+
+ (void)gettimeofday(&tdone, NULL);
+ mstotvround(&tout, maxmsec);
+ timevaladd(&tdone, &tout); /* when we give up */
+
+ mstotvround(&twait, wmsec);
+
+ rcvcount = 0;
+ while (rcvcount < MSGS) {
+ (void)gettimeofday(&tcur, NULL);
+
+ /*
+ * keep sending until we have sent the max
+ */
+ if (trials < TRIALS) {
+ trials++;
+ oicp->icmp_otime = htonl((tcur.tv_sec % SECDAY) * 1000
+ + tcur.tv_usec / 1000);
+ oicp->icmp_cksum = 0;
+ oicp->icmp_cksum = in_cksum((u_short*)oicp,
+ sizeof(*oicp));
+
+ count = sendto(sock_raw, opacket, sizeof(*oicp), 0,
+ (struct sockaddr*)addr,
+ sizeof(struct sockaddr));
+ if (count < 0) {
+ if (measure_status == HOSTDOWN)
+ measure_status = UNREACHABLE;
+ goto quit;
+ }
+ ++oicp->icmp_seq;
+
+ ttrans = tcur;
+ timevaladd(&ttrans, &twait);
+ } else {
+ ttrans = tdone;
+ }
+
+ while (rcvcount < trials) {
+ timevalsub(&tout, &ttrans, &tcur);
+ if (tout.tv_sec < 0)
+ tout.tv_sec = 0;
+
+ FD_SET(sock_raw, &ready);
+ count = select(sock_raw+1, &ready, (fd_set *)0,
+ (fd_set *)0, &tout);
+ (void)gettimeofday(&tcur, NULL);
+ if (count <= 0)
+ break;
+
+ length = sizeof(struct sockaddr_in);
+ cc = recvfrom(sock_raw, (char *)packet, PACKET_IN, 0,
+ 0,&length);
+ if (cc < 0)
+ goto quit;
+
+ /*
+ * got something. See if it is ours
+ */
+ icp = (struct icmp *)(packet + (ip->ip_hl << 2));
+ if (cc < sizeof(*ip)
+ || icp->icmp_type != ICMP_TSTAMPREPLY
+ || icp->icmp_id != oicp->icmp_id
+ || icp->icmp_seq < seqno
+ || icp->icmp_seq >= oicp->icmp_seq)
+ continue;
+
+
+ sendtime = ntohl(icp->icmp_otime);
+ recvtime = ((tcur.tv_sec % SECDAY) * 1000 +
+ tcur.tv_usec / 1000);
+
+ total = recvtime-sendtime;
+ if (total < 0) /* do not hassle midnight */
+ continue;
+
+ rcvcount++;
+ histime1 = ntohl(icp->icmp_rtime);
+ histime2 = ntohl(icp->icmp_ttime);
+ /*
+ * a host using a time format different from
+ * msec. since midnight UT (as per RFC792) should
+ * set the high order bit of the 32-bit time
+ * value it transmits.
+ */
+ if ((histime1 & 0x80000000) != 0) {
+ measure_status = NONSTDTIME;
+ goto quit;
+ }
+ measure_status = GOOD;
+
+ idelta = recvtime-histime2;
+ odelta = histime1-sendtime;
+
+ /* do not be confused by midnight */
+ if (idelta < -MSEC_DAY/2) idelta += MSEC_DAY;
+ else if (idelta > MSEC_DAY/2) idelta -= MSEC_DAY;
+
+ if (odelta < -MSEC_DAY/2) odelta += MSEC_DAY;
+ else if (odelta > MSEC_DAY/2) odelta -= MSEC_DAY;
+
+ /* save the quantization error so that we can get a
+ * measurement finer than our system clock.
+ */
+ if (total < MIN_ROUND) {
+ measure_delta = (odelta - idelta)/2;
+ goto quit;
+ }
+
+ if (idelta < min_idelta)
+ min_idelta = idelta;
+ if (odelta < min_odelta)
+ min_odelta = odelta;
+
+ measure_delta = (min_odelta - min_idelta)/2;
+ }
+
+ if (tcur.tv_sec > tdone.tv_sec
+ || (tcur.tv_sec == tdone.tv_sec
+ && tcur.tv_usec >= tdone.tv_usec))
+ break;
+ }
+
+quit:
+ seqno += TRIALS; /* allocate our sequence numbers */
+
+ /*
+ * If no answer is received for TRIALS consecutive times,
+ * the machine is assumed to be down
+ */
+ if (measure_status == GOOD) {
+ if (trace) {
+ fprintf(fd,
+ "measured delta %4d, %d trials to %-15s %s\n",
+ measure_delta, trials,
+ inet_ntoa(addr->sin_addr), hname);
+ }
+ } else if (print) {
+ if (errno != 0)
+ warn("measure %s", hname);
+ } else {
+ if (errno != 0) {
+ syslog(LOG_ERR, "measure %s: %m", hname);
+ } else {
+ syslog(LOG_ERR, "measure: %s did not respond", hname);
+ }
+ if (trace) {
+ fprintf(fd,
+ "measure: %s failed after %d trials\n",
+ hname, trials);
+ (void)fflush(fd);
+ }
+ }
+
+ return(measure_status);
+}
+
+
+
+
+
+/*
+ * round a number of milliseconds into a struct timeval
+ */
+void
+mstotvround(struct timeval *res, long x)
+{
+ if (x < 0)
+ x = -((-x + 3)/5);
+ else
+ x = (x+3)/5;
+ x *= 5;
+ res->tv_sec = x/1000;
+ res->tv_usec = (x-res->tv_sec*1000)*1000;
+ if (res->tv_usec < 0) {
+ res->tv_usec += 1000000;
+ res->tv_sec--;
+ }
+}
+
+void
+timevaladd(struct timeval *tv1, struct timeval *tv2)
+{
+ tv1->tv_sec += tv2->tv_sec;
+ tv1->tv_usec += tv2->tv_usec;
+ if (tv1->tv_usec >= 1000000) {
+ tv1->tv_sec++;
+ tv1->tv_usec -= 1000000;
+ }
+ if (tv1->tv_usec < 0) {
+ tv1->tv_sec--;
+ tv1->tv_usec += 1000000;
+ }
+}
+
+void
+timevalsub(struct timeval *res, struct timeval *tv1, struct timeval *tv2)
+{
+ res->tv_sec = tv1->tv_sec - tv2->tv_sec;
+ res->tv_usec = tv1->tv_usec - tv2->tv_usec;
+ if (res->tv_usec >= 1000000) {
+ res->tv_sec++;
+ res->tv_usec -= 1000000;
+ }
+ if (res->tv_usec < 0) {
+ res->tv_sec--;
+ res->tv_usec += 1000000;
+ }
+}
diff --git a/usr.sbin/timed/timed/networkdelta.c b/usr.sbin/timed/timed/networkdelta.c
new file mode 100644
index 0000000..bf2a353
--- /dev/null
+++ b/usr.sbin/timed/timed/networkdelta.c
@@ -0,0 +1,260 @@
+/*-
+ * 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[] = "@(#)networkdelta.c 8.1 (Berkeley) 6/6/93";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include "globals.h"
+
+static long median(float, float *, long *, long *, unsigned int);
+
+/*
+ * Compute a corrected date.
+ * Compute the median of the reasonable differences. First compute
+ * the median of all authorized differences, and then compute the
+ * median of all differences that are reasonably close to the first
+ * median.
+ *
+ * This differs from the original BSD implementation, which looked for
+ * the largest group of machines with essentially the same date.
+ * That assumed that machines with bad clocks would be uniformly
+ * distributed. Unfortunately, in real life networks, the distribution
+ * of machines is not uniform among models of machines, and the
+ * distribution of errors in clocks tends to be quite consistent
+ * for a given model. In other words, all model VI Supre Servres
+ * from GoFast Inc. tend to have about the same error.
+ * The original BSD implementation would chose the clock of the
+ * most common model, and discard all others.
+ *
+ * Therefore, get best we can do is to try to average over all
+ * of the machines in the network, while discarding "obviously"
+ * bad values.
+ */
+long
+networkdelta(void)
+{
+ struct hosttbl *htp;
+ long med;
+ long lodelta, hidelta;
+ long logood, higood;
+ long x[NHOSTS];
+ long *xp;
+ int numdelta;
+ float eps;
+
+ /*
+ * compute the median of the good values
+ */
+ med = 0;
+ numdelta = 1;
+ xp = &x[0];
+ *xp = 0; /* account for ourself */
+ for (htp = self.l_fwd; htp != &self; htp = htp->l_fwd) {
+ if (htp->good
+ && htp->noanswer == 0
+ && htp->delta != HOSTDOWN) {
+ med += htp->delta;
+ numdelta++;
+ *++xp = htp->delta;
+ }
+ }
+
+ /*
+ * If we are the only trusted time keeper, then do not change our
+ * clock. There may be another time keeping service active.
+ */
+ if (numdelta == 1)
+ return 0;
+
+ med /= numdelta;
+ eps = med - x[0];
+ if (trace)
+ fprintf(fd, "median of %d values starting at %ld is about ",
+ numdelta, med);
+ med = median(med, &eps, &x[0], xp+1, VALID_RANGE);
+
+ /*
+ * compute the median of all values near the good median
+ */
+ hidelta = med + GOOD_RANGE;
+ lodelta = med - GOOD_RANGE;
+ higood = med + VGOOD_RANGE;
+ logood = med - VGOOD_RANGE;
+ xp = &x[0];
+ htp = &self;
+ do {
+ if (htp->noanswer == 0
+ && htp->delta >= lodelta
+ && htp->delta <= hidelta
+ && (htp->good
+ || (htp->delta >= logood
+ && htp->delta <= higood))) {
+ *xp++ = htp->delta;
+ }
+ } while (&self != (htp = htp->l_fwd));
+
+ if (xp == &x[0]) {
+ if (trace)
+ fprintf(fd, "nothing close to median %ld\n", med);
+ return med;
+ }
+
+ if (xp == &x[1]) {
+ if (trace)
+ fprintf(fd, "only value near median is %ld\n", x[0]);
+ return x[0];
+ }
+
+ if (trace)
+ fprintf(fd, "median of %td values starting at %ld is ",
+ xp-&x[0], med);
+ return median(med, &eps, &x[0], xp, 1);
+}
+
+
+/*
+ * compute the median of an array of signed integers, using the idea
+ * in <<Numerical Recipes>>.
+ */
+static long
+median(float a, float *eps_ptr, long *x, long *xlim, unsigned int gnuf)
+ /* float a; */ /* initial guess for the median */
+ /* float *eps_ptr; */ /* spacing near the median */
+ /* long *x, *xlim; */ /* the data */
+ /* unsigned int gnuf; */ /* good enough estimate */
+{
+ long *xptr;
+ float ap = LONG_MAX; /* bounds on the median */
+ float am = -LONG_MAX;
+ float aa;
+ int npts; /* # of points above & below guess */
+ float xp; /* closet point above the guess */
+ float xm; /* closet point below the guess */
+ float eps;
+ float dum, sum, sumx;
+ int pass;
+#define AMP 1.5 /* smoothing constants */
+#define AFAC 1.5
+
+ eps = *eps_ptr;
+ if (eps < 1.0) {
+ eps = -eps;
+ if (eps < 1.0)
+ eps = 1.0;
+ }
+
+ for (pass = 1; ; pass++) { /* loop over the data */
+ sum = 0.0;
+ sumx = 0.0;
+ npts = 0;
+ xp = LONG_MAX;
+ xm = -LONG_MAX;
+
+ for (xptr = x; xptr != xlim; xptr++) {
+ float xx = *xptr;
+
+ dum = xx - a;
+ if (dum != 0.0) { /* avoid dividing by 0 */
+ if (dum > 0.0) {
+ npts++;
+ if (xx < xp)
+ xp = xx;
+ } else {
+ npts--;
+ if (xx > xm)
+ xm = xx;
+ dum = -dum;
+ }
+ dum = 1.0/(eps + dum);
+ sum += dum;
+ sumx += xx * dum;
+ }
+ }
+
+ if (ap-am < gnuf || sum == 0) {
+ if (trace)
+ fprintf(fd,
+ "%ld in %d passes; early out balance=%d\n",
+ (long)a, pass, npts);
+ return a; /* guess was good enough */
+ }
+
+ aa = (sumx/sum-a)*AMP;
+ if (npts >= 2) { /* guess was too low */
+ am = a;
+ aa = xp + max(0.0, aa);
+ if (aa > ap)
+ aa = (a + ap)/2;
+
+ } else if (npts <= -2) { /* guess was two high */
+ ap = a;
+ aa = xm + min(0.0, aa);
+ if (aa < am)
+ aa = (a + am)/2;
+
+ } else {
+ break; /* got it */
+ }
+
+ if (a == aa) {
+ if (trace)
+ fprintf(fd,
+ "%ld in %d passes; force out balance=%d\n",
+ (long)a, pass, npts);
+ return a;
+ }
+ eps = AFAC*abs(aa - a);
+ *eps_ptr = eps;
+ a = aa;
+ }
+
+ if (((x - xlim) % 2) != 0) { /* even number of points? */
+ if (npts == 0) /* yes, return an average */
+ a = (xp+xm)/2;
+ else if (npts > 0)
+ a = (a+xp)/2;
+ else
+ a = (xm+a)/2;
+
+ } else if (npts != 0) { /* odd number of points */
+ if (npts > 0)
+ a = xp;
+ else
+ a = xm;
+ }
+
+ if (trace)
+ fprintf(fd, "%ld in %d passes\n", (long)a, pass);
+ return a;
+}
diff --git a/usr.sbin/timed/timed/pathnames.h b/usr.sbin/timed/timed/pathnames.h
new file mode 100644
index 0000000..a2396e1
--- /dev/null
+++ b/usr.sbin/timed/timed/pathnames.h
@@ -0,0 +1,37 @@
+/*-
+ * 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.
+ *
+ * @(#)pathnames.h 8.1 (Berkeley) 6/6/93
+ *
+ * $FreeBSD$
+ */
+
+#include <paths.h>
+
+#define _PATH_MASTERLOG "/var/log/timed.masterlog"
+#define _PATH_TIMEDLOG "/var/log/timed.log"
diff --git a/usr.sbin/timed/timed/readmsg.c b/usr.sbin/timed/timed/readmsg.c
new file mode 100644
index 0000000..8168612
--- /dev/null
+++ b/usr.sbin/timed/timed/readmsg.c
@@ -0,0 +1,502 @@
+/*-
+ * 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[] = "@(#)readmsg.c 8.1 (Berkeley) 6/6/93";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#define TSPTYPES
+#include "globals.h"
+
+/*
+ * LOOKAT checks if the message is of the requested type and comes from
+ * the right machine, returning 1 in case of affirmative answer
+ */
+#define LOOKAT(msg, mtype, mfrom, netp, froms) \
+ (((mtype) == TSP_ANY || (mtype) == (msg).tsp_type) && \
+ ((mfrom) == 0 || !strcmp((mfrom), (msg).tsp_name)) && \
+ ((netp) == 0 || \
+ ((netp)->mask & (froms).sin_addr.s_addr) == (netp)->net.s_addr))
+
+struct timeval rtime, rwait, rtout;
+struct tsp msgin;
+static struct tsplist {
+ struct tsp info;
+ struct timeval when;
+ struct sockaddr_in addr;
+ struct tsplist *p;
+} msgslist;
+struct sockaddr_in from;
+struct netinfo *fromnet;
+struct timeval from_when;
+
+/*
+ * `readmsg' returns message `type' sent by `machfrom' if it finds it
+ * either in the receive queue, or in a linked list of previously received
+ * messages that it maintains.
+ * Otherwise it waits to see if the appropriate message arrives within
+ * `intvl' seconds. If not, it returns NULL.
+ */
+
+struct tsp *
+readmsg(int type, char *machfrom, struct timeval *intvl, struct netinfo *netfrom)
+{
+ int length;
+ fd_set ready;
+ static struct tsplist *head = &msgslist;
+ static struct tsplist *tail = &msgslist;
+ static int msgcnt = 0;
+ struct tsplist *prev;
+ register struct netinfo *ntp;
+ register struct tsplist *ptr;
+ ssize_t n;
+
+ if (trace) {
+ fprintf(fd, "readmsg: looking for %s from %s, %s\n",
+ tsptype[type], machfrom == NULL ? "ANY" : machfrom,
+ netfrom == NULL ? "ANYNET" : inet_ntoa(netfrom->net));
+ if (head->p != 0) {
+ length = 1;
+ for (ptr = head->p; ptr != 0; ptr = ptr->p) {
+ /* do not repeat the hundreds of messages */
+ if (++length > 3) {
+ if (ptr == tail) {
+ fprintf(fd,"\t ...%d skipped\n",
+ length);
+ } else {
+ continue;
+ }
+ }
+ fprintf(fd, length > 1 ? "\t" : "queue:\t");
+ print(&ptr->info, &ptr->addr);
+ }
+ }
+ }
+
+ ptr = head->p;
+ prev = head;
+
+ /*
+ * Look for the requested message scanning through the
+ * linked list. If found, return it and free the space
+ */
+
+ while (ptr != NULL) {
+ if (LOOKAT(ptr->info, type, machfrom, netfrom, ptr->addr)) {
+again:
+ msgin = ptr->info;
+ from = ptr->addr;
+ from_when = ptr->when;
+ prev->p = ptr->p;
+ if (ptr == tail)
+ tail = prev;
+ free((char *)ptr);
+ fromnet = NULL;
+ if (netfrom == NULL)
+ for (ntp = nettab; ntp != NULL; ntp = ntp->next) {
+ if ((ntp->mask & from.sin_addr.s_addr) ==
+ ntp->net.s_addr) {
+ fromnet = ntp;
+ break;
+ }
+ }
+ else
+ fromnet = netfrom;
+ if (trace) {
+ fprintf(fd, "readmsg: found ");
+ print(&msgin, &from);
+ }
+
+/* The protocol can get far behind. When it does, it gets
+ * hopelessly confused. So delete duplicate messages.
+ */
+ for (ptr = prev; (ptr = ptr->p) != NULL; prev = ptr) {
+ if (ptr->addr.sin_addr.s_addr
+ == from.sin_addr.s_addr
+ && ptr->info.tsp_type == msgin.tsp_type) {
+ if (trace)
+ fprintf(fd, "\tdup ");
+ goto again;
+ }
+ }
+ msgcnt--;
+ return(&msgin);
+ } else {
+ prev = ptr;
+ ptr = ptr->p;
+ }
+ }
+
+ /*
+ * If the message was not in the linked list, it may still be
+ * coming from the network. Set the timer and wait
+ * on a select to read the next incoming message: if it is the
+ * right one, return it, otherwise insert it in the linked list.
+ */
+
+ (void)gettimeofday(&rtout, NULL);
+ timevaladd(&rtout, intvl);
+ FD_ZERO(&ready);
+ for (;;) {
+ (void)gettimeofday(&rtime, NULL);
+ timevalsub(&rwait, &rtout, &rtime);
+ if (rwait.tv_sec < 0)
+ rwait.tv_sec = rwait.tv_usec = 0;
+ else if (rwait.tv_sec == 0
+ && rwait.tv_usec < 1000000/CLK_TCK)
+ rwait.tv_usec = 1000000/CLK_TCK;
+
+ if (trace) {
+ fprintf(fd, "readmsg: wait %jd.%6ld at %s\n",
+ (intmax_t)rwait.tv_sec, rwait.tv_usec, date());
+ /* Notice a full disk, as we flush trace info.
+ * It is better to flush periodically than at
+ * every line because the tracing consists of bursts
+ * of many lines. Without care, tracing slows
+ * down the code enough to break the protocol.
+ */
+ if (rwait.tv_sec != 0
+ && EOF == fflush(fd))
+ traceoff("Tracing ended for cause at %s\n");
+ }
+
+ FD_SET(sock, &ready);
+ if (!select(sock+1, &ready, (fd_set *)0, (fd_set *)0,
+ &rwait)) {
+ if (rwait.tv_sec == 0 && rwait.tv_usec == 0)
+ return(0);
+ continue;
+ }
+ length = sizeof(from);
+ if ((n = recvfrom(sock, (char *)&msgin, sizeof(struct tsp), 0,
+ (struct sockaddr*)&from, &length)) < 0) {
+ syslog(LOG_ERR, "recvfrom: %m");
+ exit(1);
+ }
+ /*
+ * The 4.3BSD protocol spec had a 32-byte tsp_name field, and
+ * this is still OS-dependent. Demand that the packet is at
+ * least long enough to hold a 4.3BSD packet.
+ */
+ if (n < (ssize_t)(sizeof(struct tsp) - MAXHOSTNAMELEN + 32)) {
+ syslog(LOG_NOTICE,
+ "short packet (%zd/%zu bytes) from %s",
+ n, sizeof(struct tsp) - MAXHOSTNAMELEN + 32,
+ inet_ntoa(from.sin_addr));
+ continue;
+ }
+ (void)gettimeofday(&from_when, NULL);
+ bytehostorder(&msgin);
+
+ if (msgin.tsp_vers > TSPVERSION) {
+ if (trace) {
+ fprintf(fd,"readmsg: version mismatch\n");
+ /* should do a dump of the packet */
+ }
+ continue;
+ }
+
+ if (memchr(msgin.tsp_name,
+ '\0', sizeof msgin.tsp_name) == NULL) {
+ syslog(LOG_NOTICE, "hostname field not NUL terminated "
+ "in packet from %s", inet_ntoa(from.sin_addr));
+ continue;
+ }
+
+ fromnet = NULL;
+ for (ntp = nettab; ntp != NULL; ntp = ntp->next)
+ if ((ntp->mask & from.sin_addr.s_addr) ==
+ ntp->net.s_addr) {
+ fromnet = ntp;
+ break;
+ }
+
+ /*
+ * drop packets from nets we are ignoring permanently
+ */
+ if (fromnet == NULL) {
+ /*
+ * The following messages may originate on
+ * this host with an ignored network address
+ */
+ if (msgin.tsp_type != TSP_TRACEON &&
+ msgin.tsp_type != TSP_SETDATE &&
+ msgin.tsp_type != TSP_MSITE &&
+ msgin.tsp_type != TSP_TEST &&
+ msgin.tsp_type != TSP_TRACEOFF) {
+ if (trace) {
+ fprintf(fd,"readmsg: discard null net ");
+ print(&msgin, &from);
+ }
+ continue;
+ }
+ }
+
+ /*
+ * Throw away messages coming from this machine,
+ * unless they are of some particular type.
+ * This gets rid of broadcast messages and reduces
+ * master processing time.
+ */
+ if (!strcmp(msgin.tsp_name, hostname)
+ && msgin.tsp_type != TSP_SETDATE
+ && msgin.tsp_type != TSP_TEST
+ && msgin.tsp_type != TSP_MSITE
+ && msgin.tsp_type != TSP_TRACEON
+ && msgin.tsp_type != TSP_TRACEOFF
+ && msgin.tsp_type != TSP_LOOP) {
+ if (trace) {
+ fprintf(fd, "readmsg: discard own ");
+ print(&msgin, &from);
+ }
+ continue;
+ }
+
+ /*
+ * Send acknowledgements here; this is faster and
+ * avoids deadlocks that would occur if acks were
+ * sent from a higher level routine. Different
+ * acknowledgements are necessary, depending on
+ * status.
+ */
+ if (fromnet == NULL) /* do not de-reference 0 */
+ ignoreack();
+ else if (fromnet->status == MASTER)
+ masterack();
+ else if (fromnet->status == SLAVE)
+ slaveack();
+ else
+ ignoreack();
+
+ if (LOOKAT(msgin, type, machfrom, netfrom, from)) {
+ if (trace) {
+ fprintf(fd, "readmsg: ");
+ print(&msgin, &from);
+ }
+ return(&msgin);
+ } else if (++msgcnt > NHOSTS*3) {
+
+/* The protocol gets hopelessly confused if it gets too far
+* behind. However, it seems able to recover from all cases of lost
+* packets. Therefore, if we are swamped, throw everything away.
+*/
+ if (trace)
+ fprintf(fd,
+ "readmsg: discarding %d msgs\n",
+ msgcnt);
+ msgcnt = 0;
+ while ((ptr=head->p) != NULL) {
+ head->p = ptr->p;
+ free((char *)ptr);
+ }
+ tail = head;
+ } else {
+ tail->p = (struct tsplist *)
+ malloc(sizeof(struct tsplist));
+ tail = tail->p;
+ tail->p = NULL;
+ tail->info = msgin;
+ tail->addr = from;
+ /* timestamp msgs so SETTIMEs are correct */
+ tail->when = from_when;
+ }
+ }
+}
+
+/*
+ * Send the necessary acknowledgements:
+ * only the type ACK is to be sent by a slave
+ */
+void
+slaveack(void)
+{
+ switch(msgin.tsp_type) {
+
+ case TSP_ADJTIME:
+ case TSP_SETTIME:
+ case TSP_ACCEPT:
+ case TSP_REFUSE:
+ case TSP_TRACEON:
+ case TSP_TRACEOFF:
+ case TSP_QUIT:
+ if (trace) {
+ fprintf(fd, "Slaveack: ");
+ print(&msgin, &from);
+ }
+ xmit(TSP_ACK,msgin.tsp_seq, &from);
+ break;
+
+ default:
+ if (trace) {
+ fprintf(fd, "Slaveack: no ack: ");
+ print(&msgin, &from);
+ }
+ break;
+ }
+}
+
+/*
+ * Certain packets may arrive from this machine on ignored networks.
+ * These packets should be acknowledged.
+ */
+void
+ignoreack(void)
+{
+ switch(msgin.tsp_type) {
+
+ case TSP_TRACEON:
+ case TSP_TRACEOFF:
+ case TSP_QUIT:
+ if (trace) {
+ fprintf(fd, "Ignoreack: ");
+ print(&msgin, &from);
+ }
+ xmit(TSP_ACK,msgin.tsp_seq, &from);
+ break;
+
+ default:
+ if (trace) {
+ fprintf(fd, "Ignoreack: no ack: ");
+ print(&msgin, &from);
+ }
+ break;
+ }
+}
+
+/*
+ * `masterack' sends the necessary acknowledgments
+ * to the messages received by a master
+ */
+void
+masterack(void)
+{
+ struct tsp resp;
+
+ resp = msgin;
+ resp.tsp_vers = TSPVERSION;
+ (void)strcpy(resp.tsp_name, hostname);
+
+ switch(msgin.tsp_type) {
+
+ case TSP_QUIT:
+ case TSP_TRACEON:
+ case TSP_TRACEOFF:
+ case TSP_MSITEREQ:
+ if (trace) {
+ fprintf(fd, "Masterack: ");
+ print(&msgin, &from);
+ }
+ xmit(TSP_ACK,msgin.tsp_seq, &from);
+ break;
+
+ case TSP_RESOLVE:
+ case TSP_MASTERREQ:
+ if (trace) {
+ fprintf(fd, "Masterack: ");
+ print(&msgin, &from);
+ }
+ xmit(TSP_MASTERACK,msgin.tsp_seq, &from);
+ break;
+
+ default:
+ if (trace) {
+ fprintf(fd,"Masterack: no ack: ");
+ print(&msgin, &from);
+ }
+ break;
+ }
+}
+
+/*
+ * Print a TSP message
+ */
+void
+print(struct tsp *msg, struct sockaddr_in *addr)
+{
+ char tm[26];
+ time_t tsp_time_sec;
+
+ if (msg->tsp_type >= TSPTYPENUMBER) {
+ fprintf(fd, "bad type (%u) on packet from %s\n",
+ msg->tsp_type, inet_ntoa(addr->sin_addr));
+ return;
+ }
+
+ switch (msg->tsp_type) {
+
+ case TSP_LOOP:
+ fprintf(fd, "%s %d %-6u #%d %-15s %s\n",
+ tsptype[msg->tsp_type],
+ msg->tsp_vers,
+ msg->tsp_seq,
+ msg->tsp_hopcnt,
+ inet_ntoa(addr->sin_addr),
+ msg->tsp_name);
+ break;
+
+ case TSP_SETTIME:
+ case TSP_SETDATE:
+ case TSP_SETDATEREQ:
+ tsp_time_sec = msg->tsp_time.tv_sec;
+ strncpy(tm, ctime(&tsp_time_sec)+3+1, sizeof(tm));
+ tm[15] = '\0'; /* ugh */
+ fprintf(fd, "%s %d %-6u %s %-15s %s\n",
+ tsptype[msg->tsp_type],
+ msg->tsp_vers,
+ msg->tsp_seq,
+ tm,
+ inet_ntoa(addr->sin_addr),
+ msg->tsp_name);
+ break;
+
+ case TSP_ADJTIME:
+ fprintf(fd, "%s %d %-6u (%d,%d) %-15s %s\n",
+ tsptype[msg->tsp_type],
+ msg->tsp_vers,
+ msg->tsp_seq,
+ msg->tsp_time.tv_sec,
+ msg->tsp_time.tv_usec,
+ inet_ntoa(addr->sin_addr),
+ msg->tsp_name);
+ break;
+
+ default:
+ fprintf(fd, "%s %d %-6u %-15s %s\n",
+ tsptype[msg->tsp_type],
+ msg->tsp_vers,
+ msg->tsp_seq,
+ inet_ntoa(addr->sin_addr),
+ msg->tsp_name);
+ break;
+ }
+}
diff --git a/usr.sbin/timed/timed/slave.c b/usr.sbin/timed/timed/slave.c
new file mode 100644
index 0000000..15c0c43
--- /dev/null
+++ b/usr.sbin/timed/timed/slave.c
@@ -0,0 +1,690 @@
+/*-
+ * 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[] = "@(#)slave.c 8.1 (Berkeley) 6/6/93";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include "globals.h"
+#include <setjmp.h>
+#include <utmpx.h>
+#include "pathnames.h"
+
+extern jmp_buf jmpenv;
+extern int Mflag;
+extern int justquit;
+
+extern u_short sequence;
+
+static char master_name[MAXHOSTNAMELEN];
+static struct netinfo *old_slavenet;
+static int old_status;
+
+static void schgdate(struct tsp *, char *);
+static void setmaster(struct tsp *);
+static void answerdelay(void);
+
+int
+slave(void)
+{
+ int tries;
+ long electiontime, refusetime, looktime, looptime, adjtime;
+ u_short seq;
+ long fastelection;
+#define FASTTOUT 3
+ struct in_addr cadr;
+ struct timeval otime;
+ struct sockaddr_in taddr;
+ char tname[MAXHOSTNAMELEN];
+ struct tsp *msg, to;
+ struct timeval ntime, wait, tmptv;
+ time_t tsp_time_sec;
+ struct tsp *answer;
+ int timeout();
+ char olddate[32];
+ char newdate[32];
+ struct netinfo *ntp;
+ struct hosttbl *htp;
+ struct utmpx utx;
+
+
+ old_slavenet = 0;
+ seq = 0;
+ refusetime = 0;
+ adjtime = 0;
+
+ (void)gettimeofday(&ntime, NULL);
+ electiontime = ntime.tv_sec + delay2;
+ fastelection = ntime.tv_sec + FASTTOUT;
+ if (justquit)
+ looktime = electiontime;
+ else
+ looktime = fastelection;
+ looptime = fastelection;
+
+ if (slavenet)
+ xmit(TSP_SLAVEUP, 0, &slavenet->dest_addr);
+ if (status & MASTER) {
+ for (ntp = nettab; ntp != NULL; ntp = ntp->next) {
+ if (ntp->status == MASTER)
+ masterup(ntp);
+ }
+ }
+
+loop:
+ get_goodgroup(0);
+ (void)gettimeofday(&ntime, NULL);
+ if (ntime.tv_sec > electiontime) {
+ if (trace)
+ fprintf(fd, "election timer expired\n");
+ longjmp(jmpenv, 1);
+ }
+
+ if (ntime.tv_sec >= looktime) {
+ if (trace)
+ fprintf(fd, "Looking for nets to master\n");
+
+ if (Mflag && nignorednets > 0) {
+ for (ntp = nettab; ntp != NULL; ntp = ntp->next) {
+ if (ntp->status == IGNORE
+ || ntp->status == NOMASTER) {
+ lookformaster(ntp);
+ if (ntp->status == MASTER) {
+ masterup(ntp);
+ } else if (ntp->status == MASTER) {
+ ntp->status = NOMASTER;
+ }
+ }
+ if (ntp->status == MASTER
+ && --ntp->quit_count < 0)
+ ntp->quit_count = 0;
+ }
+ makeslave(slavenet); /* prune extras */
+ setstatus();
+ }
+ (void)gettimeofday(&ntime, NULL);
+ looktime = ntime.tv_sec + delay2;
+ }
+ if (ntime.tv_sec >= looptime) {
+ if (trace)
+ fprintf(fd, "Looking for loops\n");
+ for (ntp = nettab; ntp != NULL; ntp = ntp->next) {
+ if (ntp->status == MASTER) {
+ to.tsp_type = TSP_LOOP;
+ to.tsp_vers = TSPVERSION;
+ to.tsp_seq = sequence++;
+ to.tsp_hopcnt = MAX_HOPCNT;
+ (void)strcpy(to.tsp_name, hostname);
+ bytenetorder(&to);
+ if (sendto(sock, (char *)&to, sizeof(struct tsp), 0,
+ (struct sockaddr*)&ntp->dest_addr,
+ sizeof(ntp->dest_addr)) < 0) {
+ trace_sendto_err(ntp->dest_addr.sin_addr);
+ }
+ }
+ }
+ (void)gettimeofday(&ntime, NULL);
+ looptime = ntime.tv_sec + delay2;
+ }
+
+ wait.tv_sec = min(electiontime,min(looktime,looptime)) - ntime.tv_sec;
+ if (wait.tv_sec < 0)
+ wait.tv_sec = 0;
+ wait.tv_sec += FASTTOUT;
+ wait.tv_usec = 0;
+ msg = readmsg(TSP_ANY, ANYADDR, &wait, 0);
+
+ if (msg != NULL) {
+ /*
+ * filter stuff not for us
+ */
+ switch (msg->tsp_type) {
+ case TSP_SETDATE:
+ case TSP_TRACEOFF:
+ case TSP_TRACEON:
+ /*
+ * XXX check to see they are from ourself
+ */
+ break;
+
+ case TSP_TEST:
+ case TSP_MSITE:
+ break;
+
+ case TSP_MASTERUP:
+ if (!fromnet) {
+ if (trace) {
+ fprintf(fd, "slave ignored: ");
+ print(msg, &from);
+ }
+ goto loop;
+ }
+ break;
+
+ default:
+ if (!fromnet
+ || fromnet->status == IGNORE
+ || fromnet->status == NOMASTER) {
+ if (trace) {
+ fprintf(fd, "slave ignored: ");
+ print(msg, &from);
+ }
+ goto loop;
+ }
+ break;
+ }
+
+
+ /*
+ * now process the message
+ */
+ switch (msg->tsp_type) {
+
+ case TSP_ADJTIME:
+ if (fromnet != slavenet)
+ break;
+ if (!good_host_name(msg->tsp_name)) {
+ syslog(LOG_NOTICE,
+ "attempted time adjustment by %s",
+ msg->tsp_name);
+ suppress(&from, msg->tsp_name, fromnet);
+ break;
+ }
+ /*
+ * Speed up loop detection in case we have a loop.
+ * Otherwise the clocks can race until the loop
+ * is found.
+ */
+ (void)gettimeofday(&otime, NULL);
+ if (adjtime < otime.tv_sec)
+ looptime -= (looptime-otime.tv_sec)/2 + 1;
+
+ setmaster(msg);
+ if (seq != msg->tsp_seq) {
+ seq = msg->tsp_seq;
+ synch(tvtomsround(msg->tsp_time));
+ }
+ (void)gettimeofday(&ntime, NULL);
+ electiontime = ntime.tv_sec + delay2;
+ fastelection = ntime.tv_sec + FASTTOUT;
+ adjtime = ntime.tv_sec + SAMPLEINTVL*2;
+ break;
+
+ case TSP_SETTIME:
+ if (fromnet != slavenet)
+ break;
+ if (seq == msg->tsp_seq)
+ break;
+ seq = msg->tsp_seq;
+
+ /* adjust time for residence on the queue */
+ (void)gettimeofday(&otime, NULL);
+ adj_msg_time(msg,&otime);
+ /*
+ * the following line is necessary due to syslog
+ * calling ctime() which clobbers the static buffer
+ */
+ (void)strcpy(olddate, date());
+ tsp_time_sec = msg->tsp_time.tv_sec;
+ (void)strcpy(newdate, ctime(&tsp_time_sec));
+
+ if (!good_host_name(msg->tsp_name)) {
+ syslog(LOG_NOTICE,
+ "attempted time setting by untrusted %s to %s",
+ msg->tsp_name, newdate);
+ suppress(&from, msg->tsp_name, fromnet);
+ break;
+ }
+
+ setmaster(msg);
+ tmptv.tv_sec = msg->tsp_time.tv_sec;
+ tmptv.tv_usec = msg->tsp_time.tv_usec;
+ timevalsub(&ntime, &tmptv, &otime);
+ if (ntime.tv_sec < MAXADJ && ntime.tv_sec > -MAXADJ) {
+ /*
+ * do not change the clock if we can adjust it
+ */
+ synch(tvtomsround(ntime));
+ } else {
+ utx.ut_type = OLD_TIME;
+ gettimeofday(&utx.ut_tv, NULL);
+ pututxline(&utx);
+ (void)settimeofday(&tmptv, 0);
+ utx.ut_type = NEW_TIME;
+ gettimeofday(&utx.ut_tv, NULL);
+ pututxline(&utx);
+ syslog(LOG_NOTICE,
+ "date changed by %s from %s",
+ msg->tsp_name, olddate);
+ if (status & MASTER)
+ spreadtime();
+ }
+ (void)gettimeofday(&ntime, NULL);
+ electiontime = ntime.tv_sec + delay2;
+ fastelection = ntime.tv_sec + FASTTOUT;
+
+/* This patches a bad protocol bug. Imagine a system with several networks,
+ * where there are a pair of redundant gateways between a pair of networks,
+ * each running timed. Assume that we start with a third machine mastering
+ * one of the networks, and one of the gateways mastering the other.
+ * Imagine that the third machine goes away and the non-master gateway
+ * decides to replace it. If things are timed just 'right,' we will have
+ * each gateway mastering one network for a little while. If a SETTIME
+ * message gets into the network at that time, perhaps from the newly
+ * masterful gateway as it was taking control, the SETTIME will loop
+ * forever. Each time a gateway receives it on its slave side, it will
+ * call spreadtime to forward it on its mastered network. We are now in
+ * a permanent loop, since the SETTIME msgs will keep any clock
+ * in the network from advancing. Normally, the 'LOOP' stuff will detect
+ * and correct the situation. However, with the clocks stopped, the
+ * 'looptime' timer cannot expire. While they are in this state, the
+ * masters will try to saturate the network with SETTIME packets.
+ */
+ looptime = ntime.tv_sec + (looptime-otime.tv_sec)/2-1;
+ break;
+
+ case TSP_MASTERUP:
+ if (slavenet && fromnet != slavenet)
+ break;
+ if (!good_host_name(msg->tsp_name)) {
+ suppress(&from, msg->tsp_name, fromnet);
+ if (electiontime > fastelection)
+ electiontime = fastelection;
+ break;
+ }
+ makeslave(fromnet);
+ setmaster(msg);
+ setstatus();
+ answerdelay();
+ xmit(TSP_SLAVEUP, 0, &from);
+ (void)gettimeofday(&ntime, NULL);
+ electiontime = ntime.tv_sec + delay2;
+ fastelection = ntime.tv_sec + FASTTOUT;
+ refusetime = 0;
+ break;
+
+ case TSP_MASTERREQ:
+ if (fromnet->status != SLAVE)
+ break;
+ (void)gettimeofday(&ntime, NULL);
+ electiontime = ntime.tv_sec + delay2;
+ break;
+
+ case TSP_SETDATE:
+ tsp_time_sec = msg->tsp_time.tv_sec;
+ (void)strcpy(newdate, ctime(&tsp_time_sec));
+ schgdate(msg, newdate);
+ break;
+
+ case TSP_SETDATEREQ:
+ if (fromnet->status != MASTER)
+ break;
+ tsp_time_sec = msg->tsp_time.tv_sec;
+ (void)strcpy(newdate, ctime(&tsp_time_sec));
+ htp = findhost(msg->tsp_name);
+ if (0 == htp) {
+ syslog(LOG_WARNING,
+ "DATEREQ from uncontrolled machine");
+ break;
+ }
+ if (!htp->good) {
+ syslog(LOG_WARNING,
+ "attempted date change by untrusted %s to %s",
+ htp->name, newdate);
+ spreadtime();
+ break;
+ }
+ schgdate(msg, newdate);
+ break;
+
+ case TSP_TRACEON:
+ traceon();
+ break;
+
+ case TSP_TRACEOFF:
+ traceoff("Tracing ended at %s\n");
+ break;
+
+ case TSP_SLAVEUP:
+ newslave(msg);
+ break;
+
+ case TSP_ELECTION:
+ if (fromnet->status == SLAVE) {
+ (void)gettimeofday(&ntime, NULL);
+ electiontime = ntime.tv_sec + delay2;
+ fastelection = ntime.tv_sec + FASTTOUT;
+ seq = 0;
+ if (!good_host_name(msg->tsp_name)) {
+ syslog(LOG_NOTICE,
+ "suppress election of %s",
+ msg->tsp_name);
+ to.tsp_type = TSP_QUIT;
+ electiontime = fastelection;
+ } else if (cadr.s_addr != from.sin_addr.s_addr
+ && ntime.tv_sec < refusetime) {
+/* if the candidate has to repeat itself, the old code would refuse it
+ * the second time. That would prevent elections.
+ */
+ to.tsp_type = TSP_REFUSE;
+ } else {
+ cadr.s_addr = from.sin_addr.s_addr;
+ to.tsp_type = TSP_ACCEPT;
+ refusetime = ntime.tv_sec + 30;
+ }
+ taddr = from;
+ (void)strcpy(tname, msg->tsp_name);
+ (void)strcpy(to.tsp_name, hostname);
+ answerdelay();
+ if (!acksend(&to, &taddr, tname,
+ TSP_ACK, 0, 0))
+ syslog(LOG_WARNING,
+ "no answer from candidate %s\n",
+ tname);
+
+ } else { /* fromnet->status == MASTER */
+ htp = addmach(msg->tsp_name, &from,fromnet);
+ to.tsp_type = TSP_QUIT;
+ (void)strcpy(to.tsp_name, hostname);
+ if (!acksend(&to, &htp->addr, htp->name,
+ TSP_ACK, 0, htp->noanswer)) {
+ syslog(LOG_ERR,
+ "no reply from %s to ELECTION-QUIT",
+ htp->name);
+ (void)remmach(htp);
+ }
+ }
+ break;
+
+ case TSP_CONFLICT:
+ if (fromnet->status != MASTER)
+ break;
+ /*
+ * After a network partition, there can be
+ * more than one master: the first slave to
+ * come up will notify here the situation.
+ */
+ (void)strcpy(to.tsp_name, hostname);
+
+ /* The other master often gets into the same state,
+ * with boring results.
+ */
+ ntp = fromnet; /* (acksend() can leave fromnet=0 */
+ for (tries = 0; tries < 3; tries++) {
+ to.tsp_type = TSP_RESOLVE;
+ answer = acksend(&to, &ntp->dest_addr,
+ ANYADDR, TSP_MASTERACK,
+ ntp, 0);
+ if (answer == NULL)
+ break;
+ htp = addmach(answer->tsp_name,&from,ntp);
+ to.tsp_type = TSP_QUIT;
+ answer = acksend(&to, &htp->addr, htp->name,
+ TSP_ACK, 0, htp->noanswer);
+ if (!answer) {
+ syslog(LOG_WARNING,
+ "conflict error: no reply from %s to QUIT",
+ htp->name);
+ (void)remmach(htp);
+ }
+ }
+ masterup(ntp);
+ break;
+
+ case TSP_MSITE:
+ if (!slavenet)
+ break;
+ taddr = from;
+ to.tsp_type = TSP_MSITEREQ;
+ to.tsp_vers = TSPVERSION;
+ to.tsp_seq = 0;
+ (void)strcpy(to.tsp_name, hostname);
+ answer = acksend(&to, &slavenet->dest_addr,
+ ANYADDR, TSP_ACK,
+ slavenet, 0);
+ if (answer != NULL
+ && good_host_name(answer->tsp_name)) {
+ setmaster(answer);
+ to.tsp_type = TSP_ACK;
+ (void)strcpy(to.tsp_name, answer->tsp_name);
+ bytenetorder(&to);
+ if (sendto(sock, (char *)&to,
+ sizeof(struct tsp), 0,
+ (struct sockaddr*)&taddr,
+ sizeof(taddr)) < 0) {
+ trace_sendto_err(taddr.sin_addr);
+ }
+ }
+ break;
+
+ case TSP_MSITEREQ:
+ break;
+
+ case TSP_ACCEPT:
+ case TSP_REFUSE:
+ case TSP_RESOLVE:
+ break;
+
+ case TSP_QUIT:
+ doquit(msg); /* become a slave */
+ break;
+
+ case TSP_TEST:
+ electiontime = 0;
+ break;
+
+ case TSP_LOOP:
+ /* looking for loops of masters */
+ if (!(status & MASTER))
+ break;
+ if (fromnet->status == SLAVE) {
+ if (!strcmp(msg->tsp_name, hostname)) {
+ /*
+ * Someone forwarded our message back to
+ * us. There must be a loop. Tell the
+ * master of this network to quit.
+ *
+ * The other master often gets into
+ * the same state, with boring results.
+ */
+ ntp = fromnet;
+ for (tries = 0; tries < 3; tries++) {
+ to.tsp_type = TSP_RESOLVE;
+ answer = acksend(&to, &ntp->dest_addr,
+ ANYADDR, TSP_MASTERACK,
+ ntp,0);
+ if (answer == NULL)
+ break;
+ taddr = from;
+ (void)strcpy(tname, answer->tsp_name);
+ to.tsp_type = TSP_QUIT;
+ (void)strcpy(to.tsp_name, hostname);
+ if (!acksend(&to, &taddr, tname,
+ TSP_ACK, 0, 1)) {
+ syslog(LOG_ERR,
+ "no reply from %s to slave LOOP-QUIT",
+ tname);
+ } else {
+ electiontime = 0;
+ }
+ }
+ (void)gettimeofday(&ntime, NULL);
+ looptime = ntime.tv_sec + FASTTOUT;
+ } else {
+ if (msg->tsp_hopcnt-- < 1)
+ break;
+ bytenetorder(msg);
+ for (ntp = nettab; ntp != 0; ntp = ntp->next) {
+ if (ntp->status == MASTER
+ && 0 > sendto(sock, (char *)msg,
+ sizeof(struct tsp), 0,
+ (struct sockaddr*)&ntp->dest_addr,
+ sizeof(ntp->dest_addr)))
+ trace_sendto_err(ntp->dest_addr.sin_addr);
+ }
+ }
+ } else { /* fromnet->status == MASTER */
+ /*
+ * We should not have received this from a net
+ * we are master on. There must be two masters,
+ * unless the packet was really from us.
+ */
+ if (from.sin_addr.s_addr
+ == fromnet->my_addr.s_addr) {
+ if (trace)
+ fprintf(fd,"discarding forwarded LOOP\n");
+ break;
+ }
+
+ /*
+ * The other master often gets into the same
+ * state, with boring results.
+ */
+ ntp = fromnet;
+ for (tries = 0; tries < 3; tries++) {
+ to.tsp_type = TSP_RESOLVE;
+ answer = acksend(&to, &ntp->dest_addr,
+ ANYADDR, TSP_MASTERACK,
+ ntp,0);
+ if (!answer)
+ break;
+ htp = addmach(answer->tsp_name,
+ &from,ntp);
+ to.tsp_type = TSP_QUIT;
+ (void)strcpy(to.tsp_name, hostname);
+ if (!acksend(&to,&htp->addr,htp->name,
+ TSP_ACK, 0, htp->noanswer)) {
+ syslog(LOG_ERR,
+ "no reply from %s to master LOOP-QUIT",
+ htp->name);
+ (void)remmach(htp);
+ }
+ }
+ (void)gettimeofday(&ntime, NULL);
+ looptime = ntime.tv_sec + FASTTOUT;
+ }
+ break;
+ default:
+ if (trace) {
+ fprintf(fd, "garbage message: ");
+ print(msg, &from);
+ }
+ break;
+ }
+ }
+ goto loop;
+}
+
+
+/*
+ * tell the world who our master is
+ */
+static void
+setmaster(struct tsp *msg)
+{
+ if (slavenet
+ && (slavenet != old_slavenet
+ || strcmp(msg->tsp_name, master_name)
+ || old_status != status)) {
+ (void)strcpy(master_name, msg->tsp_name);
+ old_slavenet = slavenet;
+ old_status = status;
+
+ if (status & MASTER) {
+ syslog(LOG_NOTICE, "submaster to %s", master_name);
+ if (trace)
+ fprintf(fd, "submaster to %s\n", master_name);
+
+ } else {
+ syslog(LOG_NOTICE, "slave to %s", master_name);
+ if (trace)
+ fprintf(fd, "slave to %s\n", master_name);
+ }
+ }
+}
+
+
+
+/*
+ * handle date change request on a slave
+ */
+static void
+schgdate(struct tsp *msg, char *newdate)
+{
+ struct tsp to;
+ u_short seq;
+ struct sockaddr_in taddr;
+ struct timeval otime;
+
+ if (!slavenet)
+ return; /* no where to forward */
+
+ taddr = from;
+ seq = msg->tsp_seq;
+
+ syslog(LOG_INFO,
+ "forwarding date change by %s to %s",
+ msg->tsp_name, newdate);
+
+ /* adjust time for residence on the queue */
+ (void)gettimeofday(&otime, NULL);
+ adj_msg_time(msg, &otime);
+
+ to.tsp_type = TSP_SETDATEREQ;
+ to.tsp_time = msg->tsp_time;
+ (void)strcpy(to.tsp_name, hostname);
+ if (!acksend(&to, &slavenet->dest_addr,
+ ANYADDR, TSP_DATEACK,
+ slavenet, 0))
+ return; /* no answer */
+
+ xmit(TSP_DATEACK, seq, &taddr);
+}
+
+
+/*
+ * Used before answering a broadcast message to avoid network
+ * contention and likely collisions.
+ */
+static void
+answerdelay(void)
+{
+ struct timeval timeout;
+
+ timeout.tv_sec = 0;
+ timeout.tv_usec = delay1;
+
+ (void)select(0, (fd_set *)NULL, (fd_set *)NULL, (fd_set *)NULL,
+ &timeout);
+ return;
+}
diff --git a/usr.sbin/timed/timed/timed.8 b/usr.sbin/timed/timed/timed.8
new file mode 100644
index 0000000..9997563
--- /dev/null
+++ b/usr.sbin/timed/timed/timed.8
@@ -0,0 +1,288 @@
+.\" 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.
+.\"
+.\" @(#)timed.8 8.1 (Berkeley) 6/6/93
+.\" $FreeBSD$
+.\"
+.Dd February 11, 2008
+.Dt TIMED 8
+.Os
+.Sh NAME
+.Nm timed
+.Nd time server daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl dtM
+.Op Fl i Ar network | Fl n Ar network
+.Op Fl F Ar host ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is a time server daemon
+which is normally invoked at boot time from the
+.Xr rc.conf 5
+file.
+It synchronizes the host's time with the time of other
+machines, which are also running
+.Nm ,
+in a local area network.
+These time servers will slow down the clocks of some machines
+and speed up the clocks of others to bring them to the average network time.
+The average network time is computed from measurements of clock differences
+using the
+.Tn ICMP
+timestamp request message.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl d
+Enable debugging mode;
+do not detach from the terminal.
+.It Fl i Ar network
+Add
+.Ar network
+to the list of networks to ignore.
+All other networks
+to which the machine is directly connected
+are used by
+.Nm .
+This option may be specified multiple times
+to add more than one network to the list.
+.It Fl F Ar host ...
+.Bl -dash -compact
+.It
+Create a list of trusted hosts.
+.It
+Can take one or more parameters.
+.It
+.Nm
+will only accept trusted hosts as masters.
+If it finds an untrusted host claiming to be master,
+.Nm
+will suppress incoming messages from that host
+and call for a new election.
+.It
+Use real host names (resolvable by RDNS) not aliases (eg in
+.Xr named 8
+parlance: use A names, not C names).
+.It
+Use full names eg time1.domain.com not time1.
+.It
+.Fl F
+automatically includes the functionality of
+.Fl M
+(so
+.Fl M
+does not need to asserted).
+.It
+If
+.Fl F
+is not specified,
+all hosts on connected networks are treated as trustworthy.
+.El
+.It Fl M
+Allow this host to become a
+.Nm
+master if necessary.
+.It Fl n Ar network
+Add
+.Ar network
+to the list of allowed networks.
+All other networks
+to which the machine is directly connected
+are ignored by
+.Nm .
+This option may be specified multiple times
+to add more than one network to the list.
+.It Fl t
+Enable tracing of received messages
+and log to the file
+.Pa /var/log/timed.log .
+Tracing can be turned on or off while
+.Nm
+is running with the
+.Xr timedc 8
+utility.
+.El
+.Pp
+The
+.Fl n
+and
+.Fl i
+flags are mutually exclusive
+and require as arguments real networks to which
+the host is connected
+(see
+.Xr networks 5 ) .
+If neither flag is specified,
+.Nm
+will listen on all connected networks.
+.Pp
+A
+.Nm
+running without the
+.Fl M
+nor
+.Fl F
+flags will always remain a slave.
+If the
+.Fl F
+flag is not used,
+.Nm
+will treat all machines as trustworthy.
+.Pp
+The
+.Nm
+utility is based on a master-slave
+scheme.
+When
+.Nm
+is started on a machine, it asks the master for the network time
+and sets the host's clock to that time.
+After that, it accepts synchronization messages periodically sent by
+the master and calls
+.Xr adjtime 2
+to perform the needed corrections on the host's clock.
+.Pp
+It also communicates with
+.Xr date 1
+in order to set the date globally,
+and with
+.Xr timedc 8 ,
+a
+.Nm
+control utility.
+If the machine running the master becomes unreachable,
+the slaves will elect a new master
+from among those slaves
+which are running with at least one of the
+.Fl M
+and
+.Fl F
+flags.
+.Pp
+At startup
+.Nm
+normally checks for a master time server on each network to which
+it is connected, except as modified by the
+.Fl n
+and
+.Fl i
+options described above.
+It will request synchronization service from the first master server
+located.
+If permitted by the
+.Fl M
+or
+.Fl F
+flags, it will provide synchronization service on any attached networks
+on which no trusted master server was detected.
+Such a server propagates the time computed by the top-level master.
+The
+.Nm
+utility will periodically check for the presence of a master
+on those networks for which it is operating as a slave.
+If it finds that there are no trusted masters on a network,
+it will begin the election process on that network.
+.Pp
+One way to synchronize a group of machines is to use
+.Xr ntpd 8
+to
+synchronize the clock of one machine to a distant standard or a radio
+receiver and
+.Fl F Ar hostname
+to tell its
+.Nm
+to trust only itself.
+.Pp
+Messages printed by the kernel on the system console occur with
+interrupts disabled.
+This means that the clock stops while they are printing.
+A machine with many disk or network hardware problems and consequent
+messages cannot keep good time by itself.
+Each message typically causes
+the clock to lose a dozen milliseconds.
+A time daemon can correct the result.
+.Pp
+Messages in the system log about machines that failed to respond
+usually indicate machines that crashed or were turned off.
+Complaints about machines that failed to respond to initial time
+settings are often associated with
+.Dq multi-homed
+machines that looked for time masters on more than one network and eventually
+chose to become a slave on the other network.
+.Sh WARNINGS
+Temporal chaos will result if two or more time daemons attempt
+to adjust the same clock.
+If both
+.Nm
+and another time daemon are run on the same machine,
+ensure that the
+.Fl F
+flag is used, so that
+.Nm
+never attempts to adjust the local clock.
+.Pp
+The protocol is based on
+.Tn UDP/IP
+broadcasts.
+All machines within the range of a broadcast that are using the
+.Tn TSP
+protocol must cooperate.
+There cannot be more than a single administrative domain using the
+.Fl F
+flag among all machines reached by a broadcast packet.
+Failure to follow this rule is usually indicated by complaints concerning
+.Dq untrusted
+machines in the system log.
+.Sh FILES
+.Bl -tag -width /var/log/timed.masterlog -compact
+.It Pa /var/log/timed.log
+tracing file for
+.Nm
+.It Pa /var/log/timed.masterlog
+log file for master
+.Nm
+.El
+.Sh SEE ALSO
+.Xr date 1 ,
+.Xr adjtime 2 ,
+.Xr gettimeofday 2 ,
+.Xr icmp 4 ,
+.Xr networks 5 ,
+.Xr ntpd 8 ,
+.Xr timedc 8
+.Rs
+.%T "TSP: The Time Synchronization Protocol for UNIX 4.3BSD"
+.%A R. Gusella
+.%A S. Zatti
+.Re
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.3 .
diff --git a/usr.sbin/timed/timed/timed.c b/usr.sbin/timed/timed/timed.c
new file mode 100644
index 0000000..e178912
--- /dev/null
+++ b/usr.sbin/timed/timed/timed.c
@@ -0,0 +1,833 @@
+/*-
+ * 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
+static const char copyright[] =
+"@(#) Copyright (c) 1985, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)timed.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "globals.h"
+#include <net/if.h>
+#include <sys/file.h>
+#include <sys/ioctl.h>
+#include <setjmp.h>
+#include "pathnames.h"
+#include <math.h>
+#include <sys/types.h>
+#include <sys/times.h>
+
+int trace = 0;
+int sock, sock_raw = -1;
+int status = 0;
+u_short sequence; /* sequence number */
+long delay1;
+long delay2;
+
+int nslavenets; /* nets were I could be a slave */
+int nmasternets; /* nets were I could be a master */
+int nignorednets; /* ignored nets */
+int nnets; /* nets I am connected to */
+
+FILE *fd; /* trace file FD */
+
+jmp_buf jmpenv;
+
+struct netinfo *nettab = 0;
+struct netinfo *slavenet;
+int Mflag;
+int justquit = 0;
+int debug;
+
+static struct nets {
+ char *name;
+ long net;
+ struct nets *next;
+} *nets = 0;
+
+struct hosttbl hosttbl[NHOSTS+1]; /* known hosts */
+
+static struct goodhost { /* hosts that we trust */
+ char name[MAXHOSTNAMELEN];
+ struct goodhost *next;
+ char perm;
+} *goodhosts;
+
+static char *goodgroup; /* net group of trusted hosts */
+static void checkignorednets(void);
+static void pickslavenet(struct netinfo *);
+static void add_good_host(char *, int);
+static void usage(void);
+
+/*
+ * The timedaemons synchronize the clocks of hosts in a local area network.
+ * One daemon runs as master, all the others as slaves. The master
+ * performs the task of computing clock differences and sends correction
+ * values to the slaves.
+ * Slaves start an election to choose a new master when the latter disappears
+ * because of a machine crash, network partition, or when killed.
+ * A resolution protocol is used to kill all but one of the masters
+ * that happen to exist in segments of a partitioned network when the
+ * network partition is fixed.
+ *
+ * Authors: Riccardo Gusella & Stefano Zatti
+ *
+ * overhauled at Silicon Graphics
+ */
+int
+main(int argc, char *argv[])
+{
+ int on;
+ int ret;
+ int nflag, iflag;
+ struct timeval ntime;
+ struct servent *srvp;
+ char buf[BUFSIZ], *cp, *cplim;
+ struct ifconf ifc;
+ struct ifreq ifreq, ifreqf, *ifr;
+ register struct netinfo *ntp;
+ struct netinfo *ntip;
+ struct netinfo *savefromnet;
+ struct netent *nentp;
+ struct nets *nt;
+ struct sockaddr_in server;
+ u_short port;
+ int c;
+
+#ifdef lint
+ ntip = NULL;
+#endif
+
+ on = 1;
+ nflag = OFF;
+ iflag = OFF;
+
+
+ opterr = 0;
+ while ((c = getopt(argc, argv, "Mtdn:i:F:G:P:")) != -1) {
+ switch (c) {
+ case 'M':
+ Mflag = 1;
+ break;
+
+ case 't':
+ trace = 1;
+ break;
+
+ case 'n':
+ if (iflag) {
+ errx(1, "-i and -n make no sense together");
+ } else {
+ nflag = ON;
+ addnetname(optarg);
+ }
+ break;
+
+ case 'i':
+ if (nflag) {
+ errx(1, "-i and -n make no sense together");
+ } else {
+ iflag = ON;
+ addnetname(optarg);
+ }
+ break;
+
+ case 'F':
+ add_good_host(optarg,1);
+ while (optind < argc && argv[optind][0] != '-')
+ add_good_host(argv[optind++], 1);
+ break;
+
+ case 'd':
+ debug = 1;
+ break;
+ case 'G':
+ if (goodgroup != 0)
+ errx(1, "only one net group");
+ goodgroup = optarg;
+ break;
+
+ default:
+ usage();
+ break;
+ }
+ }
+ if (optind < argc)
+ usage();
+
+ /* If we care about which machine is the master, then we must
+ * be willing to be a master
+ */
+ if (0 != goodgroup || 0 != goodhosts)
+ Mflag = 1;
+
+ if (gethostname(hostname, sizeof(hostname) - 1) < 0)
+ err(1, "gethostname");
+ self.l_bak = &self;
+ self.l_fwd = &self;
+ self.h_bak = &self;
+ self.h_fwd = &self;
+ self.head = 1;
+ self.good = 1;
+
+ if (goodhosts != 0) /* trust ourself */
+ add_good_host(hostname,1);
+
+ srvp = getservbyname("timed", "udp");
+ if (srvp == 0)
+ errx(1, "timed/udp: unknown service");
+ port = srvp->s_port;
+ bzero(&server, sizeof(struct sockaddr_in));
+ server.sin_port = srvp->s_port;
+ server.sin_family = AF_INET;
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0)
+ err(1, "socket");
+ if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&on,
+ sizeof(on)) < 0)
+ err(1, "setsockopt");
+ if (bind(sock, (struct sockaddr*)&server, sizeof(server))) {
+ if (errno == EADDRINUSE)
+ warnx("time daemon already running");
+ else
+ warn("bind");
+ exit(1);
+ }
+
+ /* choose a unique seed for random number generation */
+ (void)gettimeofday(&ntime, NULL);
+ srandom(ntime.tv_sec + ntime.tv_usec);
+
+ sequence = random(); /* initial seq number */
+
+ /* rounds kernel variable time to multiple of 5 ms. */
+ ntime.tv_sec = 0;
+ ntime.tv_usec = -((ntime.tv_usec/1000) % 5) * 1000;
+ (void)adjtime(&ntime, (struct timeval *)0);
+
+ for (nt = nets; nt; nt = nt->next) {
+ nentp = getnetbyname(nt->name);
+ if (nentp == 0) {
+ nt->net = inet_network(nt->name);
+ if (nt->net != INADDR_NONE)
+ nentp = getnetbyaddr(nt->net, AF_INET);
+ }
+ if (nentp != 0) {
+ nt->net = nentp->n_net;
+ } else if (nt->net == INADDR_NONE) {
+ errx(1, "unknown net %s", nt->name);
+ } else if (nt->net == INADDR_ANY) {
+ errx(1, "bad net %s", nt->name);
+ } else {
+ warnx("warning: %s unknown in /etc/networks",
+ nt->name);
+ }
+
+ if (0 == (nt->net & 0xff000000))
+ nt->net <<= 8;
+ if (0 == (nt->net & 0xff000000))
+ nt->net <<= 8;
+ if (0 == (nt->net & 0xff000000))
+ nt->net <<= 8;
+ }
+ ifc.ifc_len = sizeof(buf);
+ ifc.ifc_buf = buf;
+ if (ioctl(sock, SIOCGIFCONF, (char *)&ifc) < 0)
+ err(1, "get interface configuration");
+ ntp = NULL;
+#define size(p) max((p).sa_len, sizeof(p))
+ cplim = buf + ifc.ifc_len; /*skip over if's with big ifr_addr's */
+ for (cp = buf; cp < cplim;
+ cp += sizeof (ifr->ifr_name) + size(ifr->ifr_addr)) {
+ ifr = (struct ifreq *)cp;
+ if (ifr->ifr_addr.sa_family != AF_INET)
+ continue;
+ if (!ntp)
+ ntp = (struct netinfo*)malloc(sizeof(struct netinfo));
+ bzero(ntp,sizeof(*ntp));
+ ntp->my_addr=((struct sockaddr_in *)&ifr->ifr_addr)->sin_addr;
+ ntp->status = NOMASTER;
+ ifreq = *ifr;
+ ifreqf = *ifr;
+
+ if (ioctl(sock, SIOCGIFFLAGS, (char *)&ifreqf) < 0) {
+ warn("get interface flags");
+ continue;
+ }
+ if ((ifreqf.ifr_flags & IFF_UP) == 0)
+ continue;
+ if ((ifreqf.ifr_flags & IFF_BROADCAST) == 0 &&
+ (ifreqf.ifr_flags & IFF_POINTOPOINT) == 0) {
+ continue;
+ }
+
+
+ if (ioctl(sock, SIOCGIFNETMASK, (char *)&ifreq) < 0) {
+ warn("get netmask");
+ continue;
+ }
+ ntp->mask = ((struct sockaddr_in *)
+ &ifreq.ifr_addr)->sin_addr.s_addr;
+
+ if (ifreqf.ifr_flags & IFF_BROADCAST) {
+ if (ioctl(sock, SIOCGIFBRDADDR, (char *)&ifreq) < 0) {
+ warn("get broadaddr");
+ continue;
+ }
+ ntp->dest_addr = *(struct sockaddr_in *)&ifreq.ifr_broadaddr;
+ /* What if the broadcast address is all ones?
+ * So we cannot just mask ntp->dest_addr. */
+ ntp->net = ntp->my_addr;
+ ntp->net.s_addr &= ntp->mask;
+ } else {
+ if (ioctl(sock, SIOCGIFDSTADDR,
+ (char *)&ifreq) < 0) {
+ warn("get destaddr");
+ continue;
+ }
+ ntp->dest_addr = *(struct sockaddr_in *)&ifreq.ifr_dstaddr;
+ ntp->net = ntp->dest_addr.sin_addr;
+ }
+
+ ntp->dest_addr.sin_port = port;
+
+ for (nt = nets; nt; nt = nt->next) {
+ if (ntp->net.s_addr == htonl(nt->net))
+ break;
+ }
+ if ((nflag && !nt) || (iflag && nt))
+ continue;
+
+ ntp->next = NULL;
+ if (nettab == NULL) {
+ nettab = ntp;
+ } else {
+ ntip->next = ntp;
+ }
+ ntip = ntp;
+ ntp = NULL;
+ }
+ if (ntp)
+ (void) free((char *)ntp);
+ if (nettab == NULL)
+ errx(1, "no network usable");
+
+ /* microseconds to delay before responding to a broadcast */
+ delay1 = casual(1, 100*1000);
+
+ /* election timer delay in secs. */
+ delay2 = casual(MINTOUT, MAXTOUT);
+
+ if (!debug)
+ daemon(debug, 0);
+
+ if (trace)
+ traceon();
+ openlog("timed", LOG_CONS|LOG_PID, LOG_DAEMON);
+
+ /*
+ * keep returning here
+ */
+ ret = setjmp(jmpenv);
+ savefromnet = fromnet;
+ setstatus();
+
+ if (Mflag) {
+ switch (ret) {
+
+ case 0:
+ checkignorednets();
+ pickslavenet(0);
+ break;
+ case 1:
+ /* Just lost our master */
+ if (slavenet != 0)
+ slavenet->status = election(slavenet);
+ if (!slavenet || slavenet->status == MASTER) {
+ checkignorednets();
+ pickslavenet(0);
+ } else {
+ makeslave(slavenet); /* prune extras */
+ }
+ break;
+
+ case 2:
+ /* Just been told to quit */
+ justquit = 1;
+ pickslavenet(savefromnet);
+ break;
+ }
+
+ setstatus();
+ if (!(status & MASTER) && sock_raw != -1) {
+ /* sock_raw is not being used now */
+ (void)close(sock_raw);
+ sock_raw = -1;
+ }
+
+ if (status == MASTER)
+ master();
+ else
+ slave();
+
+ } else {
+ if (sock_raw != -1) {
+ (void)close(sock_raw);
+ sock_raw = -1;
+ }
+
+ if (ret) {
+ /* we just lost our master or were told to quit */
+ justquit = 1;
+ }
+ for (ntp = nettab; ntp != NULL; ntp = ntp->next) {
+ if (ntp->status == MASTER) {
+ rmnetmachs(ntp);
+ ntp->status = NOMASTER;
+ }
+ }
+ checkignorednets();
+ pickslavenet(0);
+ setstatus();
+
+ slave();
+ }
+ /* NOTREACHED */
+ return(0);
+}
+
+static void
+usage(void)
+{
+#ifdef HAVENIS
+ fprintf(stderr,
+"usage: timed [-dtM] [-i net|-n net] [-F host1 host2 ...] [-G netgp]\n");
+#else
+ fprintf(stderr,
+"usage: timed [-dtM] [-i net|-n net] [-F host1 host2 ...]\n");
+#endif /* HAVENIS */
+ exit(1);
+}
+
+/*
+ * suppress an upstart, untrustworthy, self-appointed master
+ */
+void
+suppress(struct sockaddr_in *addr, char *name, struct netinfo *net)
+{
+ struct sockaddr_in tgt;
+ char tname[MAXHOSTNAMELEN];
+ struct tsp msg;
+ static struct timeval wait;
+
+ if (trace)
+ fprintf(fd, "suppress: %s\n", name);
+ tgt = *addr;
+ (void)strcpy(tname, name);
+
+ while (0 != readmsg(TSP_ANY, ANYADDR, &wait, net)) {
+ if (trace)
+ fprintf(fd, "suppress:\tdiscarded packet from %s\n",
+ name);
+ }
+
+ syslog(LOG_NOTICE, "suppressing false master %s", tname);
+ msg.tsp_type = TSP_QUIT;
+ (void)strcpy(msg.tsp_name, hostname);
+ (void)acksend(&msg, &tgt, tname, TSP_ACK, 0, 1);
+}
+
+void
+lookformaster(struct netinfo *ntp)
+{
+ struct tsp resp, conflict, *answer;
+ struct timeval ntime;
+ char mastername[MAXHOSTNAMELEN];
+ struct sockaddr_in masteraddr;
+
+ get_goodgroup(0);
+ ntp->status = SLAVE;
+
+ /* look for master */
+ resp.tsp_type = TSP_MASTERREQ;
+ (void)strcpy(resp.tsp_name, hostname);
+ answer = acksend(&resp, &ntp->dest_addr, ANYADDR,
+ TSP_MASTERACK, ntp, 0);
+ if (answer != 0 && !good_host_name(answer->tsp_name)) {
+ suppress(&from, answer->tsp_name, ntp);
+ ntp->status = NOMASTER;
+ answer = 0;
+ }
+ if (answer == 0) {
+ /*
+ * Various conditions can cause conflict: races between
+ * two just started timedaemons when no master is
+ * present, or timedaemons started during an election.
+ * A conservative approach is taken. Give up and became a
+ * slave, postponing election of a master until first
+ * timer expires.
+ */
+ ntime.tv_sec = ntime.tv_usec = 0;
+ answer = readmsg(TSP_MASTERREQ, ANYADDR, &ntime, ntp);
+ if (answer != 0) {
+ if (!good_host_name(answer->tsp_name)) {
+ suppress(&from, answer->tsp_name, ntp);
+ ntp->status = NOMASTER;
+ }
+ return;
+ }
+
+ ntime.tv_sec = ntime.tv_usec = 0;
+ answer = readmsg(TSP_MASTERUP, ANYADDR, &ntime, ntp);
+ if (answer != 0) {
+ if (!good_host_name(answer->tsp_name)) {
+ suppress(&from, answer->tsp_name, ntp);
+ ntp->status = NOMASTER;
+ }
+ return;
+ }
+
+ ntime.tv_sec = ntime.tv_usec = 0;
+ answer = readmsg(TSP_ELECTION, ANYADDR, &ntime, ntp);
+ if (answer != 0) {
+ if (!good_host_name(answer->tsp_name)) {
+ suppress(&from, answer->tsp_name, ntp);
+ ntp->status = NOMASTER;
+ }
+ return;
+ }
+
+ if (Mflag)
+ ntp->status = MASTER;
+ else
+ ntp->status = NOMASTER;
+ return;
+ }
+
+ ntp->status = SLAVE;
+ (void)strcpy(mastername, answer->tsp_name);
+ masteraddr = from;
+
+ /*
+ * If network has been partitioned, there might be other
+ * masters; tell the one we have just acknowledged that
+ * it has to gain control over the others.
+ */
+ ntime.tv_sec = 0;
+ ntime.tv_usec = 300000;
+ answer = readmsg(TSP_MASTERACK, ANYADDR, &ntime, ntp);
+ /*
+ * checking also not to send CONFLICT to ack'ed master
+ * due to duplicated MASTERACKs
+ */
+ if (answer != NULL &&
+ strcmp(answer->tsp_name, mastername) != 0) {
+ conflict.tsp_type = TSP_CONFLICT;
+ (void)strcpy(conflict.tsp_name, hostname);
+ if (!acksend(&conflict, &masteraddr, mastername,
+ TSP_ACK, 0, 0)) {
+ syslog(LOG_ERR,
+ "error on sending TSP_CONFLICT");
+ }
+ }
+}
+
+/*
+ * based on the current network configuration, set the status, and count
+ * networks;
+ */
+void
+setstatus(void)
+{
+ struct netinfo *ntp;
+
+ status = 0;
+ nmasternets = nslavenets = nnets = nignorednets = 0;
+ if (trace)
+ fprintf(fd, "Net status:\n");
+ for (ntp = nettab; ntp != NULL; ntp = ntp->next) {
+ switch ((int)ntp->status) {
+ case MASTER:
+ nmasternets++;
+ break;
+ case SLAVE:
+ nslavenets++;
+ break;
+ case NOMASTER:
+ case IGNORE:
+ nignorednets++;
+ break;
+ }
+ if (trace) {
+ fprintf(fd, "\t%-16s", inet_ntoa(ntp->net));
+ switch ((int)ntp->status) {
+ case NOMASTER:
+ fprintf(fd, "NOMASTER\n");
+ break;
+ case MASTER:
+ fprintf(fd, "MASTER\n");
+ break;
+ case SLAVE:
+ fprintf(fd, "SLAVE\n");
+ break;
+ case IGNORE:
+ fprintf(fd, "IGNORE\n");
+ break;
+ default:
+ fprintf(fd, "invalid state %d\n",
+ (int)ntp->status);
+ break;
+ }
+ }
+ nnets++;
+ status |= ntp->status;
+ }
+ status &= ~IGNORE;
+ if (trace)
+ fprintf(fd,
+ "\tnets=%d masters=%d slaves=%d ignored=%d delay2=%ld\n",
+ nnets, nmasternets, nslavenets, nignorednets, delay2);
+}
+
+void
+makeslave(struct netinfo *net)
+{
+ register struct netinfo *ntp;
+
+ for (ntp = nettab; ntp != NULL; ntp = ntp->next) {
+ if (ntp->status == SLAVE && ntp != net)
+ ntp->status = IGNORE;
+ }
+ slavenet = net;
+}
+
+/*
+ * Try to become master over ignored nets..
+ */
+static void
+checkignorednets(void)
+{
+ register struct netinfo *ntp;
+
+ for (ntp = nettab; ntp != NULL; ntp = ntp->next) {
+ if (!Mflag && ntp->status == SLAVE)
+ break;
+
+ if (ntp->status == IGNORE || ntp->status == NOMASTER) {
+ lookformaster(ntp);
+ if (!Mflag && ntp->status == SLAVE)
+ break;
+ }
+ }
+}
+
+/*
+ * choose a good network on which to be a slave
+ * The ignored networks must have already been checked.
+ * Take a hint about for a good network.
+ */
+static void
+pickslavenet(struct netinfo *ntp)
+{
+ if (slavenet != 0 && slavenet->status == SLAVE) {
+ makeslave(slavenet); /* prune extras */
+ return;
+ }
+
+ if (ntp == 0 || ntp->status != SLAVE) {
+ for (ntp = nettab; ntp != 0; ntp = ntp->next) {
+ if (ntp->status == SLAVE)
+ break;
+ }
+ }
+ makeslave(ntp);
+}
+
+/*
+ * returns a random number in the range [inf, sup]
+ */
+long
+casual(long inf, long sup)
+{
+ double value;
+
+ value = ((double)(random() & 0x7fffffff)) / (0x7fffffff*1.0);
+ return(inf + (sup - inf)*value);
+}
+
+char *
+date(void)
+{
+ time_t tv_sec;
+
+ tv_sec = time(NULL);
+ return (ctime(&tv_sec));
+}
+
+void
+addnetname(char *name)
+{
+ register struct nets **netlist = &nets;
+
+ while (*netlist)
+ netlist = &((*netlist)->next);
+ *netlist = (struct nets *)malloc(sizeof **netlist);
+ if (*netlist == 0)
+ errx(1, "malloc failed");
+ bzero((char *)*netlist, sizeof(**netlist));
+ (*netlist)->name = name;
+}
+
+/* note a host as trustworthy
+ * perm 1=not part of the netgroup
+ */
+static void
+add_good_host(char *name, int perm)
+{
+ register struct goodhost *ghp;
+ register struct hostent *hentp;
+
+ ghp = (struct goodhost*)malloc(sizeof(*ghp));
+ if (!ghp) {
+ syslog(LOG_ERR, "malloc failed");
+ exit(1);
+ }
+
+ bzero((char*)ghp, sizeof(*ghp));
+ (void)strncpy(&ghp->name[0], name, sizeof(ghp->name));
+ ghp->next = goodhosts;
+ ghp->perm = perm;
+ goodhosts = ghp;
+
+ hentp = gethostbyname(name);
+ if (0 == hentp && perm)
+ warnx("unknown host %s", name);
+}
+
+
+/* update our image of the net-group of trustworthy hosts
+ */
+void
+get_goodgroup(int force)
+{
+# define NG_DELAY (30*60*CLK_TCK) /* 30 minutes */
+ static unsigned long last_update = -NG_DELAY;
+ unsigned long new_update;
+ struct goodhost *ghp, **ghpp;
+#ifdef HAVENIS
+ struct hosttbl *htp;
+ char *mach, *usr, *dom;
+#endif /* HAVENIS */
+ struct tms tm;
+
+
+ /* if no netgroup, then we are finished */
+ if (goodgroup == 0 || !Mflag)
+ return;
+
+ /* Do not chatter with the netgroup master too often.
+ */
+ new_update = times(&tm);
+ if (new_update < last_update + NG_DELAY
+ && !force)
+ return;
+ last_update = new_update;
+
+ /* forget the old temporary entries */
+ ghpp = &goodhosts;
+ while (0 != (ghp = *ghpp)) {
+ if (!ghp->perm) {
+ *ghpp = ghp->next;
+ free((char*)ghp);
+ } else {
+ ghpp = &ghp->next;
+ }
+ }
+
+#ifdef HAVENIS
+ /* quit now if we are not one of the trusted masters
+ */
+ if (!innetgr(goodgroup, &hostname[0], 0,0)) {
+ if (trace)
+ (void)fprintf(fd, "get_goodgroup: %s not in %s\n",
+ &hostname[0], goodgroup);
+ return;
+ }
+ if (trace)
+ (void)fprintf(fd, "get_goodgroup: %s in %s\n",
+ &hostname[0], goodgroup);
+
+ /* mark the entire netgroup as trusted */
+ (void)setnetgrent(goodgroup);
+ while (getnetgrent(&mach,&usr,&dom)) {
+ if (0 != mach)
+ add_good_host(mach,0);
+ }
+ (void)endnetgrent();
+
+ /* update list of slaves */
+ for (htp = self.l_fwd; htp != &self; htp = htp->l_fwd) {
+ htp->good = good_host_name(&htp->name[0]);
+ }
+#endif /* HAVENIS */
+}
+
+
+/* see if a machine is trustworthy
+ */
+int /* 1=trust hp to change our date */
+good_host_name(char *name)
+{
+ register struct goodhost *ghp = goodhosts;
+ register char c;
+
+ if (!ghp || !Mflag) /* trust everyone if no one named */
+ return 1;
+
+ c = *name;
+ do {
+ if (c == ghp->name[0]
+ && !strcasecmp(name, ghp->name))
+ return 1; /* found him, so say so */
+ } while (0 != (ghp = ghp->next));
+
+ if (!strcasecmp(name,hostname)) /* trust ourself */
+ return 1;
+
+ return 0; /* did not find him */
+}
diff --git a/usr.sbin/timed/timedc/Makefile b/usr.sbin/timed/timedc/Makefile
new file mode 100644
index 0000000..89e303b
--- /dev/null
+++ b/usr.sbin/timed/timedc/Makefile
@@ -0,0 +1,15 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../timed
+
+PROG= timedc
+MAN= timedc.8
+SRCS= cmds.c cmdtab.c timedc.c byteorder.c measure.c cksum.c
+BINOWN= root
+BINMODE= 4555
+
+WARNS?= 1
+
+.include "../../Makefile.inc"
+.include <bsd.prog.mk>
diff --git a/usr.sbin/timed/timedc/Makefile.depend b/usr.sbin/timed/timedc/Makefile.depend
new file mode 100644
index 0000000..e77ca8d
--- /dev/null
+++ b/usr.sbin/timed/timedc/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/protocols \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/timed/timedc/cmds.c b/usr.sbin/timed/timedc/cmds.c
new file mode 100644
index 0000000..e39bf03
--- /dev/null
+++ b/usr.sbin/timed/timedc/cmds.c
@@ -0,0 +1,542 @@
+/*-
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)cmds.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "timedc.h"
+#include <sys/file.h>
+
+#include <arpa/inet.h>
+
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define TSPTYPES
+#include <protocols/timed.h>
+
+#define SECHR (60*60)
+#define SECDAY (24*SECHR)
+
+# define DATE_PROTO "udp"
+# define DATE_PORT "time"
+
+
+int sock;
+int sock_raw;
+char myname[MAXHOSTNAMELEN];
+struct hostent *hp;
+struct sockaddr_in server;
+struct sockaddr_in dayaddr;
+extern int measure_delta;
+
+void bytenetorder(struct tsp *);
+void bytehostorder(struct tsp *);
+
+
+#define BU (2208988800UL) /* seconds before UNIX epoch */
+
+
+/* compute the difference between our date and another machine
+ */
+static int /* difference in days from our time */
+daydiff(char *hostname)
+{
+ int i;
+ int trials;
+ struct timeval tout, now;
+ fd_set ready;
+ struct sockaddr from;
+ int fromlen;
+ unsigned long sec;
+
+
+ /* wait 2 seconds between 10 tries */
+ tout.tv_sec = 2;
+ tout.tv_usec = 0;
+ for (trials = 0; trials < 10; trials++) {
+ /* ask for the time */
+ sec = 0;
+ if (sendto(sock, &sec, sizeof(sec), 0,
+ (struct sockaddr*)&dayaddr, sizeof(dayaddr)) < 0) {
+ warn("sendto(sock)");
+ return 0;
+ }
+
+ for (;;) {
+ FD_ZERO(&ready);
+ FD_SET(sock, &ready);
+ i = select(sock+1, &ready, (fd_set *)0,
+ (fd_set *)0, &tout);
+ if (i < 0) {
+ if (errno == EINTR)
+ continue;
+ warn("select(date read)");
+ return 0;
+ }
+ if (0 == i)
+ break;
+
+ fromlen = sizeof(from);
+ if (recvfrom(sock,&sec,sizeof(sec),0,
+ &from,&fromlen) < 0) {
+ warn("recvfrom(date read)");
+ return 0;
+ }
+
+ sec = ntohl(sec);
+ if (sec < BU) {
+ warnx("%s says it is before 1970: %lu",
+ hostname, sec);
+ return 0;
+ }
+ sec -= BU;
+
+ (void)gettimeofday(&now, NULL);
+ return (sec - now.tv_sec);
+ }
+ }
+
+ /* if we get here, we tried too many times */
+ warnx("%s will not tell us the date", hostname);
+ return 0;
+}
+
+
+/*
+ * Clockdiff computes the difference between the time of the machine on
+ * which it is called and the time of the machines given as argument.
+ * The time differences measured by clockdiff are obtained using a sequence
+ * of ICMP TSTAMP messages which are returned to the sender by the IP module
+ * in the remote machine.
+ * In order to compare clocks of machines in different time zones, the time
+ * is transmitted (as a 32-bit value) in milliseconds since midnight UT.
+ * If a hosts uses a different time format, it should set the high order
+ * bit of the 32-bit quantity it transmits.
+ * However, VMS apparently transmits the time in milliseconds since midnight
+ * local time (rather than GMT) without setting the high order bit.
+ * Furthermore, it does not understand daylight-saving time. This makes
+ * clockdiff behaving inconsistently with hosts running VMS.
+ *
+ * In order to reduce the sensitivity to the variance of message transmission
+ * time, clockdiff sends a sequence of messages. Yet, measures between
+ * two `distant' hosts can be affected by a small error. The error can,
+ * however, be reduced by increasing the number of messages sent in each
+ * measurement.
+ */
+void
+clockdiff(int argc, char *argv[])
+{
+ int measure_status;
+ extern int measure(u_long, u_long, char *, struct sockaddr_in*, int);
+ register int avg_cnt;
+ register long avg;
+ struct servent *sp;
+
+ if (argc < 2) {
+ printf("usage: timedc clockdiff host ...\n");
+ return;
+ }
+
+ if (gethostname(myname, sizeof(myname) - 1) < 0)
+ err(1, "gethostname");
+
+ /* get the address for the date ready */
+ sp = getservbyname(DATE_PORT, DATE_PROTO);
+ if (!sp) {
+ warnx("%s/%s: unknown service", DATE_PORT, DATE_PROTO);
+ dayaddr.sin_port = 0;
+ } else {
+ dayaddr.sin_port = sp->s_port;
+ }
+
+ while (argc > 1) {
+ argc--; argv++;
+ hp = gethostbyname(*argv);
+ if (hp == NULL) {
+ warnx("%s: %s", *argv, hstrerror(h_errno));
+ continue;
+ }
+
+ server.sin_family = hp->h_addrtype;
+ bcopy(hp->h_addr, &server.sin_addr.s_addr, hp->h_length);
+ for (avg_cnt = 0, avg = 0; avg_cnt < 16; avg_cnt++) {
+ measure_status = measure(10000,100, *argv, &server, 1);
+ if (measure_status != GOOD)
+ break;
+ avg += measure_delta;
+ }
+ if (measure_status == GOOD)
+ measure_delta = avg/avg_cnt;
+
+ switch (measure_status) {
+ case HOSTDOWN:
+ printf("%s is down\n", hp->h_name);
+ continue;
+ case NONSTDTIME:
+ printf("%s transmits a non-standard time format\n",
+ hp->h_name);
+ continue;
+ case UNREACHABLE:
+ printf("%s is unreachable\n", hp->h_name);
+ continue;
+ }
+
+ /*
+ * Try to get the date only after using ICMP timestamps to
+ * get the time. This is because the date protocol
+ * is optional.
+ */
+ if (dayaddr.sin_port != 0) {
+ dayaddr.sin_family = hp->h_addrtype;
+ bcopy(hp->h_addr, &dayaddr.sin_addr.s_addr,
+ hp->h_length);
+ avg = daydiff(*argv);
+ if (avg > SECDAY) {
+ printf("time on %s is %ld days ahead %s\n",
+ hp->h_name, avg/SECDAY, myname);
+ continue;
+ } else if (avg < -SECDAY) {
+ printf("time on %s is %ld days behind %s\n",
+ hp->h_name, -avg/SECDAY, myname);
+ continue;
+ }
+ }
+
+ if (measure_delta > 0) {
+ printf("time on %s is %d ms. ahead of time on %s\n",
+ hp->h_name, measure_delta, myname);
+ } else if (measure_delta == 0) {
+ printf("%s and %s have the same time\n",
+ hp->h_name, myname);
+ } else {
+ printf("time on %s is %d ms. behind time on %s\n",
+ hp->h_name, -measure_delta, myname);
+ }
+ }
+ return;
+}
+
+
+/*
+ * finds location of master timedaemon
+ */
+void
+msite(int argc, char *argv[])
+{
+ ssize_t cc;
+ fd_set ready;
+ struct sockaddr_in dest;
+ int i, length;
+ struct sockaddr_in from;
+ struct timeval tout;
+ struct tsp msg;
+ struct servent *srvp;
+ char *tgtname;
+
+ if (argc < 1) {
+ printf("usage: timedc msite [host ...]\n");
+ return;
+ }
+
+ srvp = getservbyname("timed", "udp");
+ if (srvp == 0) {
+ warnx("timed/udp: unknown service");
+ return;
+ }
+ dest.sin_port = srvp->s_port;
+ dest.sin_family = AF_INET;
+
+ if (gethostname(myname, sizeof(myname) - 1) < 0)
+ err(1, "gethostname");
+ i = 1;
+ do {
+ tgtname = (i >= argc) ? myname : argv[i];
+ hp = gethostbyname(tgtname);
+ if (hp == 0) {
+ warnx("%s: %s", tgtname, hstrerror(h_errno));
+ continue;
+ }
+ bcopy(hp->h_addr, &dest.sin_addr.s_addr, hp->h_length);
+
+ (void)strlcpy(msg.tsp_name, myname, sizeof(msg.tsp_name));
+ msg.tsp_type = TSP_MSITE;
+ msg.tsp_vers = TSPVERSION;
+ bytenetorder(&msg);
+ if (sendto(sock, &msg, sizeof(struct tsp), 0,
+ (struct sockaddr*)&dest,
+ sizeof(struct sockaddr)) < 0) {
+ warn("sendto");
+ continue;
+ }
+
+ tout.tv_sec = 15;
+ tout.tv_usec = 0;
+ FD_ZERO(&ready);
+ FD_SET(sock, &ready);
+ if (select(FD_SETSIZE, &ready, (fd_set *)0, (fd_set *)0,
+ &tout)) {
+ length = sizeof(from);
+ cc = recvfrom(sock, &msg, sizeof(struct tsp), 0,
+ (struct sockaddr *)&from, &length);
+ if (cc < 0) {
+ warn("recvfrom");
+ continue;
+ }
+ /*
+ * The 4.3BSD protocol spec had a 32-byte tsp_name field, and
+ * this is still OS-dependent. Demand that the packet is at
+ * least long enough to hold a 4.3BSD packet.
+ */
+ if (cc < (sizeof(struct tsp) - MAXHOSTNAMELEN + 32)) {
+ fprintf(stderr,
+ "short packet (%zd/%zu bytes) from %s\n",
+ cc, sizeof(struct tsp) - MAXHOSTNAMELEN + 32,
+ inet_ntoa(from.sin_addr));
+ continue;
+ }
+ bytehostorder(&msg);
+ if (msg.tsp_type == TSP_ACK) {
+ printf("master timedaemon at %s is %s\n",
+ tgtname, msg.tsp_name);
+ } else {
+ if (msg.tsp_type >= TSPTYPENUMBER)
+ printf("unknown ack received: %u\n",
+ msg.tsp_type);
+ else
+ printf("wrong ack received: %s\n",
+ tsptype[msg.tsp_type]);
+ }
+ } else {
+ printf("communication error with %s\n", tgtname);
+ }
+ } while (++i < argc);
+}
+
+/*
+ * quits timedc
+ */
+void
+quit(void)
+{
+ exit(0);
+}
+
+
+/*
+ * Causes the election timer to expire on the selected hosts
+ * It sends just one udp message per machine, relying on
+ * reliability of communication channel.
+ */
+void
+testing(int argc, char *argv[])
+{
+ struct servent *srvp;
+ struct sockaddr_in sin;
+ struct tsp msg;
+
+ if (argc < 2) {
+ printf("usage: timedc election host1 [host2 ...]\n");
+ return;
+ }
+
+ srvp = getservbyname("timed", "udp");
+ if (srvp == 0) {
+ warnx("timed/udp: unknown service");
+ return;
+ }
+
+ while (argc > 1) {
+ argc--; argv++;
+ hp = gethostbyname(*argv);
+ if (hp == NULL) {
+ warnx("%s: %s", *argv, hstrerror(h_errno));
+ argc--; argv++;
+ continue;
+ }
+ sin.sin_port = srvp->s_port;
+ sin.sin_family = hp->h_addrtype;
+ bcopy(hp->h_addr, &sin.sin_addr.s_addr, hp->h_length);
+
+ msg.tsp_type = TSP_TEST;
+ msg.tsp_vers = TSPVERSION;
+ if (gethostname(myname, sizeof(myname) - 1) < 0)
+ err(1, "gethostname");
+ (void)strlcpy(msg.tsp_name, myname, sizeof(msg.tsp_name));
+ bytenetorder(&msg);
+ if (sendto(sock, &msg, sizeof(struct tsp), 0,
+ (struct sockaddr*)&sin,
+ sizeof(struct sockaddr)) < 0) {
+ warn("sendto");
+ }
+ }
+}
+
+
+/*
+ * Enables or disables tracing on local timedaemon
+ */
+void
+tracing(int argc, char *argv[])
+{
+ int onflag;
+ int length;
+ ssize_t cc;
+ fd_set ready;
+ struct sockaddr_in dest;
+ struct sockaddr_in from;
+ struct timeval tout;
+ struct tsp msg;
+ struct servent *srvp;
+
+ if (argc != 2) {
+ printf("usage: timedc trace { on | off }\n");
+ return;
+ }
+
+ srvp = getservbyname("timed", "udp");
+ if (srvp == 0) {
+ warnx("timed/udp: unknown service");
+ return;
+ }
+ dest.sin_port = srvp->s_port;
+ dest.sin_family = AF_INET;
+
+ if (gethostname(myname, sizeof(myname) - 1) < 0)
+ err(1, "gethostname");
+ hp = gethostbyname(myname);
+ bcopy(hp->h_addr, &dest.sin_addr.s_addr, hp->h_length);
+
+ if (strcmp(argv[1], "on") == 0) {
+ msg.tsp_type = TSP_TRACEON;
+ onflag = ON;
+ } else {
+ msg.tsp_type = TSP_TRACEOFF;
+ onflag = OFF;
+ }
+
+ (void)strcpy(msg.tsp_name, myname);
+ msg.tsp_vers = TSPVERSION;
+ bytenetorder(&msg);
+ if (sendto(sock, &msg, sizeof(struct tsp), 0,
+ (struct sockaddr*)&dest, sizeof(struct sockaddr)) < 0) {
+ warn("sendto");
+ return;
+ }
+
+ tout.tv_sec = 5;
+ tout.tv_usec = 0;
+ FD_ZERO(&ready);
+ FD_SET(sock, &ready);
+ if (select(FD_SETSIZE, &ready, (fd_set *)0, (fd_set *)0, &tout)) {
+ length = sizeof(from);
+ cc = recvfrom(sock, &msg, sizeof(struct tsp), 0,
+ (struct sockaddr *)&from, &length);
+ if (cc < 0) {
+ warn("recvfrom");
+ return;
+ }
+ /*
+ * The 4.3BSD protocol spec had a 32-byte tsp_name field, and
+ * this is still OS-dependent. Demand that the packet is at
+ * least long enough to hold a 4.3BSD packet.
+ */
+ if (cc < (sizeof(struct tsp) - MAXHOSTNAMELEN + 32)) {
+ fprintf(stderr, "short packet (%zd/%zu bytes) from %s\n",
+ cc, sizeof(struct tsp) - MAXHOSTNAMELEN + 32,
+ inet_ntoa(from.sin_addr));
+ return;
+ }
+ bytehostorder(&msg);
+ if (msg.tsp_type == TSP_ACK)
+ if (onflag)
+ printf("timed tracing enabled\n");
+ else
+ printf("timed tracing disabled\n");
+ else {
+ if (msg.tsp_type >= TSPTYPENUMBER)
+ printf("unknown ack received: %u\n",
+ msg.tsp_type);
+ else
+ printf("wrong ack received: %s\n",
+ tsptype[msg.tsp_type]);
+ }
+ } else
+ printf("communication error\n");
+}
+
+int
+priv_resources(void)
+{
+ int port;
+ struct sockaddr_in sin;
+
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0) {
+ warn("opening socket");
+ return(-1);
+ }
+
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = 0;
+ for (port = IPPORT_RESERVED - 1; port > IPPORT_RESERVED / 2; port--) {
+ sin.sin_port = htons((u_short)port);
+ if (bind(sock, (struct sockaddr*)&sin, sizeof (sin)) >= 0)
+ break;
+ if (errno != EADDRINUSE && errno != EADDRNOTAVAIL) {
+ warn("bind");
+ (void) close(sock);
+ return(-1);
+ }
+ }
+ if (port == IPPORT_RESERVED / 2) {
+ warnx("all reserved ports in use");
+ (void) close(sock);
+ return(-1);
+ }
+
+ sock_raw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
+ if (sock_raw < 0) {
+ warn("opening raw socket");
+ (void) close(sock);
+ return(-1);
+ }
+ return(1);
+}
diff --git a/usr.sbin/timed/timedc/cmdtab.c b/usr.sbin/timed/timedc/cmdtab.c
new file mode 100644
index 0000000..62c01da
--- /dev/null
+++ b/usr.sbin/timed/timedc/cmdtab.c
@@ -0,0 +1,57 @@
+/*
+ * 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[] = "@(#)cmdtab.c 8.1 (Berkeley) 6/6/93";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include "timedc.h"
+
+char clockdiffhelp[] = "measures clock differences between machines";
+char helphelp[] = "gets help on commands";
+char msitehelp[] = "finds location of master";
+char quithelp[] = "exits timedc";
+char testinghelp[] = "causes election timers to expire";
+char tracinghelp[] = "turns tracing on or off";
+
+struct cmd cmdtab[] = {
+ { "clockdiff", clockdiffhelp, clockdiff, 0 },
+ { "election", testinghelp, testing, 1 },
+ { "help", helphelp, help, 0 },
+ { "msite", msitehelp, msite, 0 },
+ { "quit", quithelp, quit, 0 },
+ { "trace", tracinghelp, tracing, 1 },
+ { "?", helphelp, help, 0 },
+};
+
+int NCMDS = sizeof (cmdtab) / sizeof (cmdtab[0]);
diff --git a/usr.sbin/timed/timedc/extern.h b/usr.sbin/timed/timedc/extern.h
new file mode 100644
index 0000000..bbbd83e
--- /dev/null
+++ b/usr.sbin/timed/timedc/extern.h
@@ -0,0 +1,50 @@
+/* $FreeBSD$ */
+
+/*-
+ * 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.
+ *
+ * @(#)extern.h 8.1 (Berkeley) 6/6/93
+ */
+
+#if __STDC__
+struct tsp;
+#endif
+
+extern struct cmd cmdtab[];
+
+void bytehostorder(struct tsp *);
+void bytenetorder(struct tsp *);
+void clockdiff(int, char *[]);
+void help(int, char *[]);
+void intr(int);
+void makeargv(void);
+void msite(int, char *[]);
+int priv_resources(void);
+void quit(void);
+void testing(int, char *[]);
+void tracing(int, char *[]);
diff --git a/usr.sbin/timed/timedc/timedc.8 b/usr.sbin/timed/timedc/timedc.8
new file mode 100644
index 0000000..3b0aea1
--- /dev/null
+++ b/usr.sbin/timed/timedc/timedc.8
@@ -0,0 +1,141 @@
+.\" 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.
+.\"
+.\" @(#)timedc.8 8.1 (Berkeley) 6/6/93
+.\" $FreeBSD$
+.\"
+.Dd June 6, 1993
+.Dt TIMEDC 8
+.Os
+.Sh NAME
+.Nm timedc
+.Nd timed control program
+.Sh SYNOPSIS
+.Nm
+.Op Ar command Op Ar argument ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to control the operation of the
+.Xr timed 8
+program.
+It may be used to:
+.Bl -bullet
+.It
+Measure the differences between machines' clocks,
+.It
+Find the location where the master time server is running,
+.It
+Enable or disable tracing of messages received by
+.Xr timed 8 ,
+and
+.It
+Perform various debugging actions.
+.El
+.Pp
+Without any arguments,
+.Nm
+will prompt for commands from the standard input.
+If arguments are supplied,
+.Nm
+interprets the first argument as a command and the remaining
+arguments as parameters to the command.
+The standard input
+may be redirected causing
+.Nm
+to read commands from a file.
+Commands may be abbreviated;
+recognized commands are:
+.Pp
+.Bl -tag -width Ds -compact
+.It Ic \&? Op Ar command ...
+.Pp
+.It Ic help Op Ar command ...
+Print a short description of each command specified in the argument list,
+or, if no arguments are given, a list of the recognized commands.
+.Pp
+.It Ic clockdiff Ar host ...
+Compute the differences between the clock of the host machine
+and the clocks of the machines given as arguments.
+.Pp
+.It Ic msite Op Ar host ...
+Show the master time server for specified host(s).
+.Pp
+.It Xo
+.Ic trace
+.Li \&{ Ar on Li \&|
+.Ar off \&}
+.Xc
+Enable or disable the tracing of incoming messages to
+.Xr timed 8
+in the file
+.Pa /var/log/timed.log .
+.Pp
+.It Ic election Ar host1 Op Ar host2 ...
+Asks the daemon
+on the target host to reset its "election" timers and to ensure that
+a time master has been elected.
+.Pp
+.It Ic quit
+Exit from timedc.
+.El
+.Pp
+Other commands may be included for use in testing and debugging
+.Xr timed 8 ;
+the help command and
+the program source may be consulted for details.
+.Sh FILES
+.Bl -tag -width /var/log/timed.masterlog -compact
+.It Pa /var/log/timed.log
+tracing file for timed
+.It Pa /var/log/timed.masterlog
+log file for master timed
+.El
+.Sh DIAGNOSTICS
+.Bl -diag
+.It ?Ambiguous command
+abbreviation matches more than one command
+.It ?Invalid command
+no match found
+.It ?Privileged command
+command can be executed by root only
+.El
+.Sh SEE ALSO
+.Xr date 1 ,
+.Xr adjtime 2 ,
+.Xr icmp 4 ,
+.Xr timed 8
+.Rs
+.%T "TSP: The Time Synchronization Protocol for UNIX 4.3BSD"
+.%A R. Gusella
+.%A S. Zatti
+.Re
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.3 .
diff --git a/usr.sbin/timed/timedc/timedc.c b/usr.sbin/timed/timedc/timedc.c
new file mode 100644
index 0000000..2f3e508
--- /dev/null
+++ b/usr.sbin/timed/timedc/timedc.c
@@ -0,0 +1,252 @@
+/*-
+ * 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
+static const char copyright[] =
+"@(#) Copyright (c) 1985, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)timedc.c 8.1 (Berkeley) 6/6/93";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include "timedc.h"
+#include <ctype.h>
+#include <err.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+int trace = 0;
+FILE *fd = 0;
+int margc;
+int fromatty;
+#define MAX_MARGV 20
+char *margv[MAX_MARGV];
+char cmdline[200];
+jmp_buf toplevel;
+static struct cmd *getcmd(char *);
+
+int
+main(int argc, char *argv[])
+{
+ register struct cmd *c;
+
+ openlog("timedc", 0, LOG_AUTH);
+
+ /*
+ * security dictates!
+ */
+ if (priv_resources() < 0)
+ errx(1, "could not get privileged resources");
+ if (setuid(getuid()) != 0)
+ err(1, "setuid()");
+
+ if (--argc > 0) {
+ c = getcmd(*++argv);
+ if (c == (struct cmd *)-1) {
+ printf("?Ambiguous command\n");
+ exit(1);
+ }
+ if (c == 0) {
+ printf("?Invalid command\n");
+ exit(1);
+ }
+ if (c->c_priv && getuid()) {
+ printf("?Privileged command\n");
+ exit(1);
+ }
+ (*c->c_handler)(argc, argv);
+ exit(0);
+ }
+
+ fromatty = isatty(fileno(stdin));
+ if (setjmp(toplevel))
+ putchar('\n');
+ (void) signal(SIGINT, intr);
+ for (;;) {
+ if (fromatty) {
+ printf("timedc> ");
+ (void) fflush(stdout);
+ }
+ if (fgets(cmdline, sizeof(cmdline), stdin) == NULL)
+ quit();
+ if (cmdline[0] == 0)
+ break;
+ makeargv();
+ if (margv[0] == 0)
+ continue;
+ c = getcmd(margv[0]);
+ if (c == (struct cmd *)-1) {
+ printf("?Ambiguous command\n");
+ continue;
+ }
+ if (c == 0) {
+ printf("?Invalid command\n");
+ continue;
+ }
+ if (c->c_priv && getuid()) {
+ printf("?Privileged command\n");
+ continue;
+ }
+ (*c->c_handler)(margc, margv);
+ }
+ return 0;
+}
+
+void
+intr(int signo __unused)
+{
+ if (!fromatty)
+ exit(0);
+ longjmp(toplevel, 1);
+}
+
+
+static struct cmd *
+getcmd(char *name)
+{
+ register char *p, *q;
+ register struct cmd *c, *found;
+ register int nmatches, longest;
+ extern int NCMDS;
+
+ longest = 0;
+ nmatches = 0;
+ found = 0;
+ for (c = cmdtab; c < &cmdtab[NCMDS]; c++) {
+ p = c->c_name;
+ for (q = name; *q == *p++; q++)
+ if (*q == 0) /* exact match? */
+ return(c);
+ if (!*q) { /* the name was a prefix */
+ if (q - name > longest) {
+ longest = q - name;
+ nmatches = 1;
+ found = c;
+ } else if (q - name == longest)
+ nmatches++;
+ }
+ }
+ if (nmatches > 1)
+ return((struct cmd *)-1);
+ return(found);
+}
+
+/*
+ * Slice a string up into argc/argv.
+ */
+void
+makeargv(void)
+{
+ register char *cp;
+ register char **argp = margv;
+
+ margc = 0;
+ for (cp = cmdline; margc < MAX_MARGV - 1 && *cp; ) {
+ while (isspace(*cp))
+ cp++;
+ if (*cp == '\0')
+ break;
+ *argp++ = cp;
+ margc += 1;
+ while (*cp != '\0' && !isspace(*cp))
+ cp++;
+ if (*cp == '\0')
+ break;
+ *cp++ = '\0';
+ }
+ *argp++ = 0;
+}
+
+#define HELPINDENT (sizeof ("directory"))
+
+/*
+ * Help command.
+ */
+void
+help(int argc, char *argv[])
+{
+ register struct cmd *c;
+
+ if (argc == 1) {
+ register int i, j, w;
+ int columns, width = 0, lines;
+ extern int NCMDS;
+
+ printf("Commands may be abbreviated. Commands are:\n\n");
+ for (c = cmdtab; c < &cmdtab[NCMDS]; c++) {
+ int len = strlen(c->c_name);
+
+ if (len > width)
+ width = len;
+ }
+ width = (width + 8) &~ 7;
+ columns = 80 / width;
+ if (columns == 0)
+ columns = 1;
+ lines = (NCMDS + columns - 1) / columns;
+ for (i = 0; i < lines; i++) {
+ for (j = 0; j < columns; j++) {
+ c = cmdtab + j * lines + i;
+ printf("%s", c->c_name);
+ if (c + lines >= &cmdtab[NCMDS]) {
+ printf("\n");
+ break;
+ }
+ w = strlen(c->c_name);
+ while (w < width) {
+ w = (w + 8) &~ 7;
+ putchar('\t');
+ }
+ }
+ }
+ return;
+ }
+ while (--argc > 0) {
+ register char *arg;
+ arg = *++argv;
+ c = getcmd(arg);
+ if (c == (struct cmd *)-1)
+ printf("?Ambiguous help command %s\n", arg);
+ else if (c == (struct cmd *)0)
+ printf("?Invalid help command %s\n", arg);
+ else
+ printf("%-*s\t%s\n", (int)HELPINDENT,
+ c->c_name, c->c_help);
+ }
+}
diff --git a/usr.sbin/timed/timedc/timedc.h b/usr.sbin/timed/timedc/timedc.h
new file mode 100644
index 0000000..bfeef28
--- /dev/null
+++ b/usr.sbin/timed/timedc/timedc.h
@@ -0,0 +1,59 @@
+/*-
+ * 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.
+ *
+ * @(#)timedc.h 8.1 (Berkeley) 6/6/93
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+
+#define ON 1
+#define OFF 0
+
+#define GOOD 1
+#define UNREACHABLE 2
+#define NONSTDTIME 3
+#define HOSTDOWN 0x7fffffff
+
+struct cmd {
+ char *c_name; /* command name */
+ char *c_help; /* help message */
+ void (*c_handler)(); /* routine to do the work */
+ int c_priv; /* privileged command */
+};
+
+#include "extern.h"
diff --git a/usr.sbin/traceroute/Makefile b/usr.sbin/traceroute/Makefile
new file mode 100644
index 0000000..a3b6a14
--- /dev/null
+++ b/usr.sbin/traceroute/Makefile
@@ -0,0 +1,41 @@
+# $FreeBSD$
+
+TRACEROUTE_DISTDIR?= ${.CURDIR}/../../contrib/traceroute
+.PATH: ${TRACEROUTE_DISTDIR}
+
+PROG= traceroute
+MAN= traceroute.8
+SRCS= as.c version.c traceroute.c ifaddrlist.c findsaddr-udp.c
+BINOWN= root
+BINMODE=4555
+CLEANFILES= version.c
+
+CFLAGS+= -DHAVE_SYS_SELECT_H=1 -DHAVE_SYS_SOCKIO_H=1 \
+ -DHAVE_NET_ROUTE_H=1 -DHAVE_NET_IF_DL_H=1 \
+ -DHAVE_STRERROR=1 -DHAVE_USLEEP=1 \
+ -DHAVE_SYS_SYSCTL_H=1 -DBYTESWAP_IP_HDR=1 \
+ -DHAVE_SETLINEBUF=1 -DHAVE_RAW_OPTIONS=1 \
+ -DHAVE_SOCKADDR_SA_LEN=1 -DHAVE_ICMP_NEXTMTU=1
+.if !defined(TRACEROUTE_NO_IPSEC)
+CFLAGS+= -DIPSEC
+.endif
+# RTT Jitter on the internet these days means printing 3 decimal places on
+# > 1000ms times is plain useless. Uncomment this to enable variable precision
+# reporting, ie: print a variable precision from 0.001ms through 1000ms
+# CFLAGS+= -DSANE_PRECISION
+
+.if !defined(TRACEROUTE_NO_IPSEC)
+LIBADD+= ipsec
+.endif
+
+CFLAGS+= -I${TRACEROUTE_DISTDIR}
+
+WARNS?= 3
+
+version.c: ${TRACEROUTE_DISTDIR}/VERSION
+ @rm -f ${.TARGET}
+ head -1 ${TRACEROUTE_DISTDIR}/VERSION | \
+ sed -e 's/.*/char version[] = "&";/' \
+ > ${.TARGET}
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/traceroute/Makefile.depend b/usr.sbin/traceroute/Makefile.depend
new file mode 100644
index 0000000..60e14c2
--- /dev/null
+++ b/usr.sbin/traceroute/Makefile.depend
@@ -0,0 +1,22 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libipsec \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+version.o: version.c
+version.po: version.c
+.endif
diff --git a/usr.sbin/traceroute/findsaddr-udp.c b/usr.sbin/traceroute/findsaddr-udp.c
new file mode 100644
index 0000000..3da72c7
--- /dev/null
+++ b/usr.sbin/traceroute/findsaddr-udp.c
@@ -0,0 +1,94 @@
+/*-
+ * Copyright (c) 2010 Bjoern A. Zeeb <bz@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 <string.h>
+#include <unistd.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+
+#include "findsaddr.h"
+#include "traceroute.h"
+
+/*
+ * Return the source address for the given destination address.
+ *
+ * This makes use of proper source address selection in the FreeBSD kernel
+ * even taking jails into account (sys/netinet/in_pcb.c:in_pcbladdr()).
+ * We open a UDP socket, and connect to the destination, letting the kernel
+ * do the bind and then read the source IPv4 address using getsockname(2).
+ * This has multiple advantages: no need to do PF_ROUTE operations possibly
+ * needing special privileges, jails properly taken into account and most
+ * important - getting the result the kernel would give us rather than
+ * best-guessing ourselves.
+ */
+const char *
+findsaddr(register const struct sockaddr_in *to,
+ register struct sockaddr_in *from)
+{
+ const char *errstr;
+ struct sockaddr_in cto, cfrom;
+ int s;
+ socklen_t len;
+
+ s = socket(AF_INET, SOCK_DGRAM, 0);
+ if (s == -1)
+ return ("failed to open DGRAM socket for src addr selection.");
+
+ errstr = NULL;
+ len = sizeof(struct sockaddr_in);
+ memcpy(&cto, to, len);
+ cto.sin_port = htons(65535); /* Dummy port for connect(2). */
+ if (connect(s, (struct sockaddr *)&cto, len) == -1) {
+ errstr = "failed to connect to peer for src addr selection.";
+ goto err;
+ }
+
+ if (getsockname(s, (struct sockaddr *)&cfrom, &len) == -1) {
+ errstr = "failed to get socket name for src addr selection.";
+ goto err;
+ }
+
+ if (len != sizeof(struct sockaddr_in) || cfrom.sin_family != AF_INET) {
+ errstr = "unexpected address family in src addr selection.";
+ goto err;
+ }
+
+ /* Update source address for traceroute. */
+ setsin(from, cfrom.sin_addr.s_addr);
+
+err:
+ (void) close(s);
+
+ /* No error (string) to return. */
+ return (errstr);
+}
+
+/* end */
diff --git a/usr.sbin/traceroute6/Makefile b/usr.sbin/traceroute6/Makefile
new file mode 100644
index 0000000..fc0b5fe
--- /dev/null
+++ b/usr.sbin/traceroute6/Makefile
@@ -0,0 +1,32 @@
+# 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$
+
+TRACEROUTE_DISTDIR?= ${.CURDIR}/../../contrib/traceroute
+.PATH: ${TRACEROUTE_DISTDIR}
+
+PROG= traceroute6
+MAN= traceroute6.8
+SRCS= as.c traceroute6.c
+BINOWN= root
+BINMODE= 4555
+
+CFLAGS+= -DIPSEC -DUSE_RFC2292BIS -DHAVE_POLL
+CFLAGS+= -I${.CURDIR} -I${TRACEROUTE_DISTDIR} -I.
+
+WARNS?= 3
+
+LIBADD= ipsec
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/traceroute6/Makefile.depend b/usr.sbin/traceroute6/Makefile.depend
new file mode 100644
index 0000000..27cbf26
--- /dev/null
+++ b/usr.sbin/traceroute6/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libipsec \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/traceroute6/traceroute6.8 b/usr.sbin/traceroute6/traceroute6.8
new file mode 100644
index 0000000..884ad6d
--- /dev/null
+++ b/usr.sbin/traceroute6/traceroute6.8
@@ -0,0 +1,185 @@
+.\" $KAME: traceroute6.8,v 1.10 2004/06/06 12:35:15 suz 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 August 24, 2009
+.Dt TRACEROUTE6 8
+.Os
+.\"
+.Sh NAME
+.Nm traceroute6
+.Nd "print the route IPv6 packets will take to a network node"
+.\"
+.Sh SYNOPSIS
+.Nm
+.Bk -words
+.Op Fl adIlnNrvU
+.Ek
+.Bk -words
+.Op Fl f Ar firsthop
+.Ek
+.Bk -words
+.Op Fl g Ar gateway
+.Ek
+.Bk -words
+.Op Fl m Ar hoplimit
+.Ek
+.Bk -words
+.Op Fl p Ar port
+.Ek
+.Bk -words
+.Op Fl q Ar probes
+.Ek
+.Bk -words
+.Op Fl s Ar src
+.Ek
+.Bk -words
+.Op Fl w Ar waittime
+.Ek
+.Bk -words
+.Op Fl A Ar as_server
+.Ek
+.Bk -words
+.Ar target
+.Op Ar datalen
+.Ek
+.\"
+.Sh DESCRIPTION
+The
+.Nm
+utility
+uses the IPv6 protocol hop limit field to elicit an ICMPv6 TIME_EXCEEDED
+response from each gateway along the path to some host.
+.Pp
+The only mandatory parameter is the destination host name or IPv6 address.
+The default probe datagram carries 12 bytes of payload,
+in addition to the IPv6 header.
+The size of the payload can be specified by giving a length
+(in bytes)
+after the destination host name.
+.Pp
+Other options are:
+.Bl -tag -width Ds
+.It Fl a
+Turn on AS# lookups for each hop encountered.
+.It Fl A Ar as_server
+Turn on AS# lookups and use the given server instead of the default.
+.It Fl d
+Debug mode.
+.It Fl f Ar firsthop
+Specify how many hops to skip in trace.
+.It Fl g Ar gateway
+Specify intermediate gateway
+.Nm (
+uses routing header).
+.It Fl I
+Use ICMP6 ECHO instead of UDP datagrams.
+.It Fl l
+Print both host hostnames and numeric addresses.
+Normally
+.Nm
+prints only hostnames if
+.Fl n
+is not specified, and only numeric addresses if
+.Fl n
+is specified.
+.It Fl m Ar hoplimit
+Specify maximum hoplimit, up to 255.
+The default is 30 hops.
+.It Fl n
+Do not resolve numeric address to hostname.
+.It Fl N
+Use a packet with no upper layer header for the probes,
+instead of UDP datagrams.
+.It Fl p Ar port
+Set UDP port number to
+.Ar port .
+.It Fl q Ar probes
+Set the number of probe per hop count to
+.Ar probes .
+.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-connected network,
+an error is returned.
+This option corresponds to the
+.Dv SO_DONTROUTE
+socket option;
+it 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 a routing daemon).
+.It Fl s Ar src
+.Ar Src
+specifies the source IPv6 address to be used.
+.It Fl U
+Use UDP datagrams for the probes.
+This is the default.
+.It Fl v
+Be verbose.
+.It Fl w Ar waittime
+Specify the delay time between probes.
+.El
+.Pp
+This program prints the route to the given destination and the round-trip
+time to each gateway, in the same manner as traceroute.
+.Pp
+Here is a list of possible annotations after the round-trip time for each gateway:
+.Bl -hang -offset indent
+.It !N
+Destination Unreachable - No Route to Host.
+.It !P
+Destination Unreachable - Administratively Prohibited.
+.It !S
+Destination Unreachable - Not a Neighbour.
+.It !A
+Destination Unreachable - Address Unreachable.
+.It !\&
+This is printed if the hop limit is <= 1 on a port unreachable message.
+This means that the packet got to the destination,
+but that the reply had a hop limit that was just large enough to
+allow it to get back to the source of the traceroute6.
+This was more interesting in the IPv4 case,
+where some IP stack bugs could be identified by this behaviour.
+.El
+.\"
+.Sh EXIT STATUS
+The
+.Nm
+utility will exit with 0 on success, and non-zero on errors.
+.\"
+.Sh SEE ALSO
+.Xr ping 8 ,
+.Xr ping6 8 ,
+.Xr traceroute 8
+.\"
+.Sh HISTORY
+The
+.Nm
+utility first appeared in WIDE hydrangea IPv6 protocol stack kit.
diff --git a/usr.sbin/traceroute6/traceroute6.c b/usr.sbin/traceroute6/traceroute6.c
new file mode 100644
index 0000000..3f116dc
--- /dev/null
+++ b/usr.sbin/traceroute6/traceroute6.c
@@ -0,0 +1,1455 @@
+/* $KAME: traceroute6.c,v 1.68 2004/01/25 11:16:12 suz 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.
+ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Van Jacobson.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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) 1990, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)traceroute.c 8.1 (Berkeley) 6/6/93";
+#endif
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+/*
+ * traceroute host - trace the route ip packets follow going to "host".
+ *
+ * Attempt to trace the route an ip packet would follow to some
+ * internet host. We find out intermediate hops by launching probe
+ * packets with a small ttl (time to live) then listening for an
+ * icmp "time exceeded" reply from a gateway. We start our probes
+ * with a ttl of one and increase by one until we get an icmp "port
+ * unreachable" (which means we got to "host") or hit a max (which
+ * defaults to 30 hops & can be changed with the -m flag). Three
+ * probes (change with -q flag) are sent at each ttl setting and a
+ * line is printed showing the ttl, address of the gateway and
+ * round trip time of each probe. If the probe answers come from
+ * different gateways, the address of each responding system will
+ * be printed. If there is no response within a 5 sec. timeout
+ * interval (changed with the -w flag), a "*" is printed for that
+ * probe.
+ *
+ * Probe packets are UDP format. We don't want the destination
+ * host to process them so the destination port is set to an
+ * unlikely value (if some clod on the destination is using that
+ * value, it can be changed with the -p flag).
+ *
+ * A sample use might be:
+ *
+ * [yak 71]% traceroute nis.nsf.net.
+ * traceroute to nis.nsf.net (35.1.1.48), 30 hops max, 56 byte packet
+ * 1 helios.ee.lbl.gov (128.3.112.1) 19 ms 19 ms 0 ms
+ * 2 lilac-dmc.Berkeley.EDU (128.32.216.1) 39 ms 39 ms 19 ms
+ * 3 lilac-dmc.Berkeley.EDU (128.32.216.1) 39 ms 39 ms 19 ms
+ * 4 ccngw-ner-cc.Berkeley.EDU (128.32.136.23) 39 ms 40 ms 39 ms
+ * 5 ccn-nerif22.Berkeley.EDU (128.32.168.22) 39 ms 39 ms 39 ms
+ * 6 128.32.197.4 (128.32.197.4) 40 ms 59 ms 59 ms
+ * 7 131.119.2.5 (131.119.2.5) 59 ms 59 ms 59 ms
+ * 8 129.140.70.13 (129.140.70.13) 99 ms 99 ms 80 ms
+ * 9 129.140.71.6 (129.140.71.6) 139 ms 239 ms 319 ms
+ * 10 129.140.81.7 (129.140.81.7) 220 ms 199 ms 199 ms
+ * 11 nic.merit.edu (35.1.1.48) 239 ms 239 ms 239 ms
+ *
+ * Note that lines 2 & 3 are the same. This is due to a buggy
+ * kernel on the 2nd hop system -- lbl-csam.arpa -- that forwards
+ * packets with a zero ttl.
+ *
+ * A more interesting example is:
+ *
+ * [yak 72]% traceroute allspice.lcs.mit.edu.
+ * traceroute to allspice.lcs.mit.edu (18.26.0.115), 30 hops max
+ * 1 helios.ee.lbl.gov (128.3.112.1) 0 ms 0 ms 0 ms
+ * 2 lilac-dmc.Berkeley.EDU (128.32.216.1) 19 ms 19 ms 19 ms
+ * 3 lilac-dmc.Berkeley.EDU (128.32.216.1) 39 ms 19 ms 19 ms
+ * 4 ccngw-ner-cc.Berkeley.EDU (128.32.136.23) 19 ms 39 ms 39 ms
+ * 5 ccn-nerif22.Berkeley.EDU (128.32.168.22) 20 ms 39 ms 39 ms
+ * 6 128.32.197.4 (128.32.197.4) 59 ms 119 ms 39 ms
+ * 7 131.119.2.5 (131.119.2.5) 59 ms 59 ms 39 ms
+ * 8 129.140.70.13 (129.140.70.13) 80 ms 79 ms 99 ms
+ * 9 129.140.71.6 (129.140.71.6) 139 ms 139 ms 159 ms
+ * 10 129.140.81.7 (129.140.81.7) 199 ms 180 ms 300 ms
+ * 11 129.140.72.17 (129.140.72.17) 300 ms 239 ms 239 ms
+ * 12 * * *
+ * 13 128.121.54.72 (128.121.54.72) 259 ms 499 ms 279 ms
+ * 14 * * *
+ * 15 * * *
+ * 16 * * *
+ * 17 * * *
+ * 18 ALLSPICE.LCS.MIT.EDU (18.26.0.115) 339 ms 279 ms 279 ms
+ *
+ * (I start to see why I'm having so much trouble with mail to
+ * MIT.) Note that the gateways 12, 14, 15, 16 & 17 hops away
+ * either don't send ICMP "time exceeded" messages or send them
+ * with a ttl too small to reach us. 14 - 17 are running the
+ * MIT C Gateway code that doesn't send "time exceeded"s. God
+ * only knows what's going on with 12.
+ *
+ * The silent gateway 12 in the above may be the result of a bug in
+ * the 4.[23]BSD network code (and its derivatives): 4.x (x <= 3)
+ * sends an unreachable message using whatever ttl remains in the
+ * original datagram. Since, for gateways, the remaining ttl is
+ * zero, the icmp "time exceeded" is guaranteed to not make it back
+ * to us. The behavior of this bug is slightly more interesting
+ * when it appears on the destination system:
+ *
+ * 1 helios.ee.lbl.gov (128.3.112.1) 0 ms 0 ms 0 ms
+ * 2 lilac-dmc.Berkeley.EDU (128.32.216.1) 39 ms 19 ms 39 ms
+ * 3 lilac-dmc.Berkeley.EDU (128.32.216.1) 19 ms 39 ms 19 ms
+ * 4 ccngw-ner-cc.Berkeley.EDU (128.32.136.23) 39 ms 40 ms 19 ms
+ * 5 ccn-nerif35.Berkeley.EDU (128.32.168.35) 39 ms 39 ms 39 ms
+ * 6 csgw.Berkeley.EDU (128.32.133.254) 39 ms 59 ms 39 ms
+ * 7 * * *
+ * 8 * * *
+ * 9 * * *
+ * 10 * * *
+ * 11 * * *
+ * 12 * * *
+ * 13 rip.Berkeley.EDU (128.32.131.22) 59 ms ! 39 ms ! 39 ms !
+ *
+ * Notice that there are 12 "gateways" (13 is the final
+ * destination) and exactly the last half of them are "missing".
+ * What's really happening is that rip (a Sun-3 running Sun OS3.5)
+ * is using the ttl from our arriving datagram as the ttl in its
+ * icmp reply. So, the reply will time out on the return path
+ * (with no notice sent to anyone since icmp's aren't sent for
+ * icmp's) until we probe with a ttl that's at least twice the path
+ * length. I.e., rip is really only 7 hops away. A reply that
+ * returns with a ttl of 1 is a clue this problem exists.
+ * Traceroute prints a "!" after the time if the ttl is <= 1.
+ * Since vendors ship a lot of obsolete (DEC's Ultrix, Sun 3.x) or
+ * non-standard (HPUX) software, expect to see this problem
+ * frequently and/or take care picking the target host of your
+ * probes.
+ *
+ * Other possible annotations after the time are !H, !N, !P (got a host,
+ * network or protocol unreachable, respectively), !S or !F (source
+ * route failed or fragmentation needed -- neither of these should
+ * ever occur and the associated gateway is busted if you see one). If
+ * almost all the probes result in some kind of unreachable, traceroute
+ * will give up and exit.
+ *
+ * Notes
+ * -----
+ * This program must be run by root or be setuid. (I suggest that
+ * you *don't* make it setuid -- casual use could result in a lot
+ * of unnecessary traffic on our poor, congested nets.)
+ *
+ * This program requires a kernel mod that does not appear in any
+ * system available from Berkeley: A raw ip socket using proto
+ * IPPROTO_RAW must interpret the data sent as an ip datagram (as
+ * opposed to data to be wrapped in an ip datagram). See the README
+ * file that came with the source to this program for a description
+ * of the mods I made to /sys/netinet/raw_ip.c. Your mileage may
+ * vary. But, again, ANY 4.x (x < 4) BSD KERNEL WILL HAVE TO BE
+ * MODIFIED TO RUN THIS PROGRAM.
+ *
+ * The udp port usage may appear bizarre (well, ok, it is bizarre).
+ * The problem is that an icmp message only contains 8 bytes of
+ * data from the original datagram. 8 bytes is the size of a udp
+ * header so, if we want to associate replies with the original
+ * datagram, the necessary information must be encoded into the
+ * udp header (the ip id could be used but there's no way to
+ * interlock with the kernel's assignment of ip id's and, anyway,
+ * it would have taken a lot more kernel hacking to allow this
+ * code to set the ip id). So, to allow two or more users to
+ * use traceroute simultaneously, we use this task's pid as the
+ * source port (the high bit is set to move the port number out
+ * of the "likely" range). To keep track of which probe is being
+ * replied to (so times and/or hop counts don't get confused by a
+ * reply that was delayed in transit), we increment the destination
+ * port number before each probe.
+ *
+ * Don't use this as a coding example. I was trying to find a
+ * routing problem and this code sort-of popped out after 48 hours
+ * without sleep. I was amazed it ever compiled, much less ran.
+ *
+ * I stole the idea for this program from Steve Deering. Since
+ * the first release, I've learned that had I attended the right
+ * IETF working group meetings, I also could have stolen it from Guy
+ * Almes or Matt Mathis. I don't know (or care) who came up with
+ * the idea first. I envy the originators' perspicacity and I'm
+ * glad they didn't keep the idea a secret.
+ *
+ * Tim Seaver, Ken Adelman and C. Philip Wood provided bug fixes and/or
+ * enhancements to the original distribution.
+ *
+ * I've hacked up a round-trip-route version of this that works by
+ * sending a loose-source-routed udp datagram through the destination
+ * back to yourself. Unfortunately, SO many gateways botch source
+ * routing, the thing is almost worthless. Maybe one day...
+ *
+ * -- Van Jacobson (van@helios.ee.lbl.gov)
+ * Tue Dec 20 03:50:13 PST 1988
+ */
+
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/file.h>
+#include <sys/ioctl.h>
+#include <sys/sysctl.h>
+
+#include <netinet/in.h>
+
+#include <arpa/inet.h>
+
+#include <netdb.h>
+#include <stdio.h>
+#include <err.h>
+#ifdef HAVE_POLL
+#include <poll.h>
+#endif
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+#include <netinet/udp.h>
+
+#ifdef IPSEC
+#include <net/route.h>
+#include <netipsec/ipsec.h>
+#endif
+
+#include "as.h"
+
+#define DUMMY_PORT 10010
+
+#define MAXPACKET 65535 /* max ip packet size */
+
+#ifndef HAVE_GETIPNODEBYNAME
+#define getipnodebyname(x, y, z, u) gethostbyname2((x), (y))
+#define freehostent(x)
+#endif
+
+/*
+ * format of a (udp) probe packet.
+ */
+struct tv32 {
+ u_int32_t tv32_sec;
+ u_int32_t tv32_usec;
+};
+
+struct opacket {
+ u_char seq; /* sequence number of this packet */
+ u_char hops; /* hop limit of the packet */
+ u_char pad[2];
+ struct tv32 tv; /* time packet left */
+} __attribute__((__packed__));
+
+u_char packet[512]; /* last inbound (icmp) packet */
+struct opacket *outpacket; /* last output (udp) packet */
+
+int main(int, char *[]);
+int wait_for_reply(int, struct msghdr *);
+#ifdef IPSEC
+#ifdef IPSEC_POLICY_IPSEC
+int setpolicy(int so, char *policy);
+#endif
+#endif
+void send_probe(int, u_long);
+void *get_uphdr(struct ip6_hdr *, u_char *);
+int get_hoplim(struct msghdr *);
+double deltaT(struct timeval *, struct timeval *);
+const char *pr_type(int);
+int packet_ok(struct msghdr *, int, int);
+void print(struct msghdr *, int);
+const char *inetname(struct sockaddr *);
+void usage(void);
+
+int rcvsock; /* receive (icmp) socket file descriptor */
+int sndsock; /* send (udp) socket file descriptor */
+
+struct msghdr rcvmhdr;
+struct iovec rcviov[2];
+int rcvhlim;
+struct in6_pktinfo *rcvpktinfo;
+
+struct sockaddr_in6 Src, Dst, Rcv;
+u_long datalen; /* How much data */
+#define ICMP6ECHOLEN 8
+/* XXX: 2064 = 127(max hops in type 0 rthdr) * sizeof(ip6_hdr) + 16(margin) */
+char rtbuf[2064];
+#ifdef USE_RFC2292BIS
+struct ip6_rthdr *rth;
+#endif
+struct cmsghdr *cmsg;
+
+char *source = 0;
+char *hostname;
+
+u_long nprobes = 3;
+u_long first_hop = 1;
+u_long max_hops = 30;
+u_int16_t srcport;
+u_int16_t port = 32768+666; /* start udp dest port # for probe packets */
+u_int16_t ident;
+int options; /* socket options */
+int verbose;
+int waittime = 5; /* time to wait for response (in seconds) */
+int nflag; /* print addresses numerically */
+int useproto = IPPROTO_UDP; /* protocol to use to send packet */
+int lflag; /* print both numerical address & hostname */
+int as_path; /* print as numbers for each hop */
+char *as_server = NULL;
+void *asn;
+
+int
+main(argc, argv)
+ int argc;
+ char *argv[];
+{
+ int mib[4] = { CTL_NET, PF_INET6, IPPROTO_IPV6, IPV6CTL_DEFHLIM };
+ char hbuf[NI_MAXHOST], src0[NI_MAXHOST], *ep;
+ int ch, i, on = 1, seq, rcvcmsglen, error;
+ struct addrinfo hints, *res;
+ static u_char *rcvcmsgbuf;
+ u_long probe, hops, lport;
+ struct hostent *hp;
+ size_t size, minlen;
+ uid_t uid;
+
+ /*
+ * Receive ICMP
+ */
+ if ((rcvsock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0) {
+ perror("socket(ICMPv6)");
+ exit(5);
+ }
+
+ size = sizeof(i);
+ (void) sysctl(mib, sizeof(mib)/sizeof(mib[0]), &i, &size, NULL, 0);
+ max_hops = i;
+
+ /* specify to tell receiving interface */
+#ifdef IPV6_RECVPKTINFO
+ if (setsockopt(rcvsock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on,
+ sizeof(on)) < 0)
+ err(1, "setsockopt(IPV6_RECVPKTINFO)");
+#else /* old adv. API */
+ if (setsockopt(rcvsock, IPPROTO_IPV6, IPV6_PKTINFO, &on,
+ sizeof(on)) < 0)
+ err(1, "setsockopt(IPV6_PKTINFO)");
+#endif
+
+ /* specify to tell value of hoplimit field of received IP6 hdr */
+#ifdef IPV6_RECVHOPLIMIT
+ if (setsockopt(rcvsock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on,
+ sizeof(on)) < 0)
+ err(1, "setsockopt(IPV6_RECVHOPLIMIT)");
+#else /* old adv. API */
+ if (setsockopt(rcvsock, IPPROTO_IPV6, IPV6_HOPLIMIT, &on,
+ sizeof(on)) < 0)
+ err(1, "setsockopt(IPV6_HOPLIMIT)");
+#endif
+
+ seq = 0;
+
+ while ((ch = getopt(argc, argv, "aA:df:g:Ilm:nNp:q:rs:Uvw:")) != -1)
+ switch (ch) {
+ case 'a':
+ as_path = 1;
+ break;
+ case 'A':
+ as_path = 1;
+ as_server = optarg;
+ break;
+ case 'd':
+ options |= SO_DEBUG;
+ break;
+ case 'f':
+ ep = NULL;
+ errno = 0;
+ first_hop = strtoul(optarg, &ep, 0);
+ if (errno || !*optarg || *ep || first_hop > 255) {
+ fprintf(stderr,
+ "traceroute6: invalid min hoplimit.\n");
+ exit(1);
+ }
+ break;
+ case 'g':
+ hp = getipnodebyname(optarg, AF_INET6, 0, &h_errno);
+ if (hp == NULL) {
+ fprintf(stderr,
+ "traceroute6: unknown host %s\n", optarg);
+ exit(1);
+ }
+#ifdef USE_RFC2292BIS
+ if (rth == NULL) {
+ /*
+ * XXX: We can't detect the number of
+ * intermediate nodes yet.
+ */
+ if ((rth = inet6_rth_init((void *)rtbuf,
+ sizeof(rtbuf), IPV6_RTHDR_TYPE_0,
+ 0)) == NULL) {
+ fprintf(stderr,
+ "inet6_rth_init failed.\n");
+ exit(1);
+ }
+ }
+ if (inet6_rth_add((void *)rth,
+ (struct in6_addr *)hp->h_addr)) {
+ fprintf(stderr,
+ "inet6_rth_add failed for %s\n",
+ optarg);
+ exit(1);
+ }
+#else /* old advanced API */
+ if (cmsg == NULL)
+ cmsg = inet6_rthdr_init(rtbuf, IPV6_RTHDR_TYPE_0);
+ inet6_rthdr_add(cmsg, (struct in6_addr *)hp->h_addr,
+ IPV6_RTHDR_LOOSE);
+#endif
+ freehostent(hp);
+ break;
+ case 'I':
+ useproto = IPPROTO_ICMPV6;
+ ident = htons(getpid() & 0xffff); /* same as ping6 */
+ break;
+ case 'l':
+ lflag++;
+ break;
+ case 'm':
+ ep = NULL;
+ errno = 0;
+ max_hops = strtoul(optarg, &ep, 0);
+ if (errno || !*optarg || *ep || max_hops > 255) {
+ fprintf(stderr,
+ "traceroute6: invalid max hoplimit.\n");
+ exit(1);
+ }
+ break;
+ case 'n':
+ nflag++;
+ break;
+ case 'N':
+ useproto = IPPROTO_NONE;
+ break;
+ case 'p':
+ ep = NULL;
+ errno = 0;
+ lport = strtoul(optarg, &ep, 0);
+ if (errno || !*optarg || *ep) {
+ fprintf(stderr, "traceroute6: invalid port.\n");
+ exit(1);
+ }
+ if (lport == 0 || lport != (lport & 0xffff)) {
+ fprintf(stderr,
+ "traceroute6: port out of range.\n");
+ exit(1);
+ }
+ port = lport & 0xffff;
+ break;
+ case 'q':
+ ep = NULL;
+ errno = 0;
+ nprobes = strtoul(optarg, &ep, 0);
+ if (errno || !*optarg || *ep) {
+ fprintf(stderr,
+ "traceroute6: invalid nprobes.\n");
+ exit(1);
+ }
+ if (nprobes < 1) {
+ fprintf(stderr,
+ "traceroute6: nprobes must be >0.\n");
+ exit(1);
+ }
+ break;
+ case 'r':
+ options |= SO_DONTROUTE;
+ break;
+ case 's':
+ /*
+ * set the ip source address of the outbound
+ * probe (e.g., on a multi-homed host).
+ */
+ source = optarg;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'U':
+ useproto = IPPROTO_UDP;
+ break;
+ case 'w':
+ ep = NULL;
+ errno = 0;
+ waittime = strtoul(optarg, &ep, 0);
+ if (errno || !*optarg || *ep) {
+ fprintf(stderr,
+ "traceroute6: invalid wait time.\n");
+ exit(1);
+ }
+ if (waittime < 1) {
+ fprintf(stderr,
+ "traceroute6: wait must be >= 1 sec.\n");
+ exit(1);
+ }
+ break;
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ /*
+ * Open socket to send probe packets.
+ */
+ switch (useproto) {
+ case IPPROTO_ICMPV6:
+ sndsock = rcvsock;
+ break;
+ case IPPROTO_UDP:
+ if ((sndsock = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
+ perror("socket(SOCK_DGRAM)");
+ exit(5);
+ }
+ break;
+ case IPPROTO_NONE:
+ if ((sndsock = socket(AF_INET6, SOCK_RAW, IPPROTO_NONE)) < 0) {
+ perror("socket(SOCK_RAW)");
+ exit(5);
+ }
+ break;
+ default:
+ fprintf(stderr, "traceroute6: unknown probe protocol %d",
+ useproto);
+ exit(5);
+ }
+ if (max_hops < first_hop) {
+ fprintf(stderr,
+ "traceroute6: max hoplimit must be larger than first hoplimit.\n");
+ exit(1);
+ }
+
+ /* revoke privs */
+ uid = getuid();
+ if (setresuid(uid, uid, uid) == -1) {
+ perror("setresuid");
+ exit(1);
+ }
+
+
+ if (argc < 1 || argc > 2)
+ usage();
+
+#if 1
+ setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
+#else
+ setlinebuf(stdout);
+#endif
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_INET6;
+ hints.ai_socktype = SOCK_RAW;
+ hints.ai_protocol = IPPROTO_ICMPV6;
+ hints.ai_flags = AI_CANONNAME;
+ error = getaddrinfo(*argv, NULL, &hints, &res);
+ if (error) {
+ fprintf(stderr,
+ "traceroute6: %s\n", gai_strerror(error));
+ exit(1);
+ }
+ if (res->ai_addrlen != sizeof(Dst)) {
+ fprintf(stderr,
+ "traceroute6: size of sockaddr mismatch\n");
+ exit(1);
+ }
+ memcpy(&Dst, res->ai_addr, res->ai_addrlen);
+ hostname = res->ai_canonname ? strdup(res->ai_canonname) : *argv;
+ if (!hostname) {
+ fprintf(stderr, "traceroute6: not enough core\n");
+ exit(1);
+ }
+ if (res->ai_next) {
+ if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf,
+ sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0)
+ strlcpy(hbuf, "?", sizeof(hbuf));
+ fprintf(stderr, "traceroute6: Warning: %s has multiple "
+ "addresses; using %s\n", hostname, hbuf);
+ }
+
+ if (*++argv) {
+ ep = NULL;
+ errno = 0;
+ datalen = strtoul(*argv, &ep, 0);
+ if (errno || !*argv || *ep) {
+ fprintf(stderr,
+ "traceroute6: invalid packet length.\n");
+ exit(1);
+ }
+ }
+ switch (useproto) {
+ case IPPROTO_ICMPV6:
+ minlen = ICMP6ECHOLEN + sizeof(struct tv32);
+ break;
+ case IPPROTO_UDP:
+ minlen = sizeof(struct opacket);
+ break;
+ case IPPROTO_NONE:
+ minlen = 0;
+ datalen = 0;
+ break;
+ default:
+ fprintf(stderr, "traceroute6: unknown probe protocol %d.\n",
+ useproto);
+ exit(1);
+ }
+ if (datalen < minlen)
+ datalen = minlen;
+ else if (datalen >= MAXPACKET) {
+ fprintf(stderr,
+ "traceroute6: packet size must be %zu <= s < %d.\n",
+ minlen, MAXPACKET);
+ exit(1);
+ }
+ outpacket = malloc(datalen);
+ if (!outpacket) {
+ perror("malloc");
+ exit(1);
+ }
+ (void) bzero((char *)outpacket, datalen);
+
+ /* initialize msghdr for receiving packets */
+ rcviov[0].iov_base = (caddr_t)packet;
+ rcviov[0].iov_len = sizeof(packet);
+ rcvmhdr.msg_name = (caddr_t)&Rcv;
+ rcvmhdr.msg_namelen = sizeof(Rcv);
+ rcvmhdr.msg_iov = rcviov;
+ rcvmhdr.msg_iovlen = 1;
+ rcvcmsglen = CMSG_SPACE(sizeof(struct in6_pktinfo)) +
+ CMSG_SPACE(sizeof(int));
+ if ((rcvcmsgbuf = malloc(rcvcmsglen)) == NULL) {
+ fprintf(stderr, "traceroute6: malloc failed\n");
+ exit(1);
+ }
+ rcvmhdr.msg_control = (caddr_t) rcvcmsgbuf;
+ rcvmhdr.msg_controllen = rcvcmsglen;
+
+ if (options & SO_DEBUG)
+ (void) setsockopt(rcvsock, SOL_SOCKET, SO_DEBUG,
+ (char *)&on, sizeof(on));
+ if (options & SO_DONTROUTE)
+ (void) setsockopt(rcvsock, SOL_SOCKET, SO_DONTROUTE,
+ (char *)&on, sizeof(on));
+#ifdef IPSEC
+#ifdef IPSEC_POLICY_IPSEC
+ /*
+ * do not raise error even if setsockopt fails, kernel may have ipsec
+ * turned off.
+ */
+ if (setpolicy(rcvsock, "in bypass") < 0)
+ errx(1, "%s", ipsec_strerror());
+ if (setpolicy(rcvsock, "out bypass") < 0)
+ errx(1, "%s", ipsec_strerror());
+#else
+ {
+ int level = IPSEC_LEVEL_NONE;
+
+ (void)setsockopt(rcvsock, IPPROTO_IPV6, IPV6_ESP_TRANS_LEVEL, &level,
+ sizeof(level));
+ (void)setsockopt(rcvsock, IPPROTO_IPV6, IPV6_ESP_NETWORK_LEVEL, &level,
+ sizeof(level));
+#ifdef IP_AUTH_TRANS_LEVEL
+ (void)setsockopt(rcvsock, IPPROTO_IPV6, IPV6_AUTH_TRANS_LEVEL, &level,
+ sizeof(level));
+#else
+ (void)setsockopt(rcvsock, IPPROTO_IPV6, IPV6_AUTH_LEVEL, &level,
+ sizeof(level));
+#endif
+#ifdef IP_AUTH_NETWORK_LEVEL
+ (void)setsockopt(rcvsock, IPPROTO_IPV6, IPV6_AUTH_NETWORK_LEVEL, &level,
+ sizeof(level));
+#endif
+ }
+#endif /*IPSEC_POLICY_IPSEC*/
+#endif /*IPSEC*/
+
+#ifdef SO_SNDBUF
+ i = datalen;
+ if (setsockopt(sndsock, SOL_SOCKET, SO_SNDBUF, (char *)&i,
+ sizeof(i)) < 0 && useproto != IPPROTO_NONE) {
+ perror("setsockopt(SO_SNDBUF)");
+ exit(6);
+ }
+#endif /* SO_SNDBUF */
+ if (options & SO_DEBUG)
+ (void) setsockopt(sndsock, SOL_SOCKET, SO_DEBUG,
+ (char *)&on, sizeof(on));
+ if (options & SO_DONTROUTE)
+ (void) setsockopt(sndsock, SOL_SOCKET, SO_DONTROUTE,
+ (char *)&on, sizeof(on));
+#ifdef USE_RFC2292BIS
+ if (rth) {/* XXX: there is no library to finalize the header... */
+ rth->ip6r_len = rth->ip6r_segleft * 2;
+ if (setsockopt(sndsock, IPPROTO_IPV6, IPV6_RTHDR,
+ (void *)rth, (rth->ip6r_len + 1) << 3)) {
+ fprintf(stderr, "setsockopt(IPV6_RTHDR): %s\n",
+ strerror(errno));
+ exit(1);
+ }
+ }
+#else /* old advanced API */
+ if (cmsg != NULL) {
+ inet6_rthdr_lasthop(cmsg, IPV6_RTHDR_LOOSE);
+ if (setsockopt(sndsock, IPPROTO_IPV6, IPV6_PKTOPTIONS,
+ rtbuf, cmsg->cmsg_len) < 0) {
+ fprintf(stderr, "setsockopt(IPV6_PKTOPTIONS): %s\n",
+ strerror(errno));
+ exit(1);
+ }
+ }
+#endif /* USE_RFC2292BIS */
+#ifdef IPSEC
+#ifdef IPSEC_POLICY_IPSEC
+ /*
+ * do not raise error even if setsockopt fails, kernel may have ipsec
+ * turned off.
+ */
+ if (setpolicy(sndsock, "in bypass") < 0)
+ errx(1, "%s", ipsec_strerror());
+ if (setpolicy(sndsock, "out bypass") < 0)
+ errx(1, "%s", ipsec_strerror());
+#else
+ {
+ int level = IPSEC_LEVEL_BYPASS;
+
+ (void)setsockopt(sndsock, IPPROTO_IPV6, IPV6_ESP_TRANS_LEVEL, &level,
+ sizeof(level));
+ (void)setsockopt(sndsock, IPPROTO_IPV6, IPV6_ESP_NETWORK_LEVEL, &level,
+ sizeof(level));
+#ifdef IP_AUTH_TRANS_LEVEL
+ (void)setsockopt(sndsock, IPPROTO_IPV6, IPV6_AUTH_TRANS_LEVEL, &level,
+ sizeof(level));
+#else
+ (void)setsockopt(sndsock, IPPROTO_IPV6, IPV6_AUTH_LEVEL, &level,
+ sizeof(level));
+#endif
+#ifdef IP_AUTH_NETWORK_LEVEL
+ (void)setsockopt(sndsock, IPPROTO_IPV6, IPV6_AUTH_NETWORK_LEVEL, &level,
+ sizeof(level));
+#endif
+ }
+#endif /*IPSEC_POLICY_IPSEC*/
+#endif /*IPSEC*/
+
+ /*
+ * Source selection
+ */
+ bzero(&Src, sizeof(Src));
+ if (source) {
+ struct addrinfo hints, *res;
+ int error;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_DGRAM; /*dummy*/
+ hints.ai_flags = AI_NUMERICHOST;
+ error = getaddrinfo(source, "0", &hints, &res);
+ if (error) {
+ printf("traceroute6: %s: %s\n", source,
+ gai_strerror(error));
+ exit(1);
+ }
+ if (res->ai_addrlen > sizeof(Src)) {
+ printf("traceroute6: %s: %s\n", source,
+ gai_strerror(error));
+ exit(1);
+ }
+ memcpy(&Src, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+ } else {
+ struct sockaddr_in6 Nxt;
+ int dummy;
+ socklen_t len;
+
+ Nxt = Dst;
+ Nxt.sin6_port = htons(DUMMY_PORT);
+ if (cmsg != NULL)
+ bcopy(inet6_rthdr_getaddr(cmsg, 1), &Nxt.sin6_addr,
+ sizeof(Nxt.sin6_addr));
+ if ((dummy = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
+ perror("socket");
+ exit(1);
+ }
+ if (connect(dummy, (struct sockaddr *)&Nxt, Nxt.sin6_len) < 0) {
+ perror("connect");
+ exit(1);
+ }
+ len = sizeof(Src);
+ if (getsockname(dummy, (struct sockaddr *)&Src, &len) < 0) {
+ perror("getsockname");
+ exit(1);
+ }
+ if (getnameinfo((struct sockaddr *)&Src, Src.sin6_len,
+ src0, sizeof(src0), NULL, 0, NI_NUMERICHOST)) {
+ fprintf(stderr, "getnameinfo failed for source\n");
+ exit(1);
+ }
+ source = src0;
+ close(dummy);
+ }
+
+ Src.sin6_port = htons(0);
+ if (bind(sndsock, (struct sockaddr *)&Src, Src.sin6_len) < 0) {
+ perror("bind");
+ exit(1);
+ }
+
+ {
+ socklen_t len;
+
+ len = sizeof(Src);
+ if (getsockname(sndsock, (struct sockaddr *)&Src, &len) < 0) {
+ perror("getsockname");
+ exit(1);
+ }
+ srcport = ntohs(Src.sin6_port);
+ }
+
+ if (as_path) {
+ asn = as_setup(as_server);
+ if (asn == NULL) {
+ fprintf(stderr,
+ "traceroute6: as_setup failed, AS# lookups"
+ " disabled\n");
+ (void)fflush(stderr);
+ as_path = 0;
+ }
+ }
+
+ /*
+ * Message to users
+ */
+ if (getnameinfo((struct sockaddr *)&Dst, Dst.sin6_len, hbuf,
+ sizeof(hbuf), NULL, 0, NI_NUMERICHOST))
+ strlcpy(hbuf, "(invalid)", sizeof(hbuf));
+ fprintf(stderr, "traceroute6");
+ fprintf(stderr, " to %s (%s)", hostname, hbuf);
+ if (source)
+ fprintf(stderr, " from %s", source);
+ fprintf(stderr, ", %lu hops max, %lu byte packets\n",
+ max_hops, datalen);
+ (void) fflush(stderr);
+
+ if (first_hop > 1)
+ printf("Skipping %lu intermediate hops\n", first_hop - 1);
+
+ /*
+ * Main loop
+ */
+ for (hops = first_hop; hops <= max_hops; ++hops) {
+ struct in6_addr lastaddr;
+ int got_there = 0;
+ unsigned unreachable = 0;
+
+ printf("%2lu ", hops);
+ bzero(&lastaddr, sizeof(lastaddr));
+ for (probe = 0; probe < nprobes; ++probe) {
+ int cc;
+ struct timeval t1, t2;
+
+ (void) gettimeofday(&t1, NULL);
+ send_probe(++seq, hops);
+ while ((cc = wait_for_reply(rcvsock, &rcvmhdr))) {
+ (void) gettimeofday(&t2, NULL);
+ if ((i = packet_ok(&rcvmhdr, cc, seq))) {
+ if (!IN6_ARE_ADDR_EQUAL(&Rcv.sin6_addr,
+ &lastaddr)) {
+ if (probe > 0)
+ fputs("\n ", stdout);
+ print(&rcvmhdr, cc);
+ lastaddr = Rcv.sin6_addr;
+ }
+ printf(" %.3f ms", deltaT(&t1, &t2));
+ switch (i - 1) {
+ case ICMP6_DST_UNREACH_NOROUTE:
+ ++unreachable;
+ printf(" !N");
+ break;
+ case ICMP6_DST_UNREACH_ADMIN:
+ ++unreachable;
+ printf(" !P");
+ break;
+ case ICMP6_DST_UNREACH_NOTNEIGHBOR:
+ ++unreachable;
+ printf(" !S");
+ break;
+ case ICMP6_DST_UNREACH_ADDR:
+ ++unreachable;
+ printf(" !A");
+ break;
+ case ICMP6_DST_UNREACH_NOPORT:
+ if (rcvhlim >= 0 &&
+ rcvhlim <= 1)
+ printf(" !");
+ ++got_there;
+ break;
+ }
+ break;
+ }
+ }
+ if (cc == 0)
+ printf(" *");
+ (void) fflush(stdout);
+ }
+ putchar('\n');
+ if (got_there ||
+ (unreachable > 0 && unreachable >= ((nprobes + 1) / 2))) {
+ exit(0);
+ }
+ }
+ if (as_path)
+ as_shutdown(asn);
+
+ exit(0);
+}
+
+int
+wait_for_reply(sock, mhdr)
+ int sock;
+ struct msghdr *mhdr;
+{
+#ifdef HAVE_POLL
+ struct pollfd pfd[1];
+ int cc = 0;
+
+ pfd[0].fd = sock;
+ pfd[0].events = POLLIN;
+ pfd[0].revents = 0;
+
+ if (poll(pfd, 1, waittime * 1000) > 0)
+ cc = recvmsg(rcvsock, mhdr, 0);
+
+ return(cc);
+#else
+ fd_set *fdsp;
+ struct timeval wait;
+ int cc = 0, fdsn;
+
+ fdsn = howmany(sock + 1, NFDBITS) * sizeof(fd_mask);
+ if ((fdsp = (fd_set *)malloc(fdsn)) == NULL)
+ err(1, "malloc");
+ memset(fdsp, 0, fdsn);
+ FD_SET(sock, fdsp);
+ wait.tv_sec = waittime; wait.tv_usec = 0;
+
+ if (select(sock+1, fdsp, (fd_set *)0, (fd_set *)0, &wait) > 0)
+ cc = recvmsg(rcvsock, mhdr, 0);
+
+ free(fdsp);
+ return(cc);
+#endif
+}
+
+#ifdef IPSEC
+#ifdef IPSEC_POLICY_IPSEC
+int
+setpolicy(so, policy)
+ int so;
+ char *policy;
+{
+ char *buf;
+
+ buf = ipsec_set_policy(policy, strlen(policy));
+ if (buf == NULL) {
+ warnx("%s", ipsec_strerror());
+ return -1;
+ }
+ (void)setsockopt(so, IPPROTO_IPV6, IPV6_IPSEC_POLICY,
+ buf, ipsec_get_policylen(buf));
+
+ free(buf);
+
+ return 0;
+}
+#endif
+#endif
+
+void
+send_probe(seq, hops)
+ int seq;
+ u_long hops;
+{
+ struct icmp6_hdr *icp;
+ struct opacket *op;
+ struct timeval tv;
+ struct tv32 tv32;
+ int i;
+
+ i = hops;
+ if (setsockopt(sndsock, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
+ (char *)&i, sizeof(i)) < 0) {
+ perror("setsockopt IPV6_UNICAST_HOPS");
+ }
+
+ Dst.sin6_port = htons(port + seq);
+ (void) gettimeofday(&tv, NULL);
+ tv32.tv32_sec = htonl(tv.tv_sec);
+ tv32.tv32_usec = htonl(tv.tv_usec);
+
+ switch (useproto) {
+ case IPPROTO_ICMPV6:
+ icp = (struct icmp6_hdr *)outpacket;
+
+ icp->icmp6_type = ICMP6_ECHO_REQUEST;
+ icp->icmp6_code = 0;
+ icp->icmp6_cksum = 0;
+ icp->icmp6_id = ident;
+ icp->icmp6_seq = htons(seq);
+ bcopy(&tv32, ((u_int8_t *)outpacket + ICMP6ECHOLEN),
+ sizeof(tv32));
+ break;
+ case IPPROTO_UDP:
+ op = outpacket;
+
+ op->seq = seq;
+ op->hops = hops;
+ bcopy(&tv32, &op->tv, sizeof tv32);
+ break;
+ case IPPROTO_NONE:
+ /* No space for anything. No harm as seq/tv32 are decorative. */
+ break;
+ default:
+ fprintf(stderr, "Unknown probe protocol %d.\n", useproto);
+ exit(1);
+ }
+
+ i = sendto(sndsock, (char *)outpacket, datalen, 0,
+ (struct sockaddr *)&Dst, Dst.sin6_len);
+ if (i < 0 || (u_long)i != datalen) {
+ if (i < 0)
+ perror("sendto");
+ printf("traceroute6: wrote %s %lu chars, ret=%d\n",
+ hostname, datalen, i);
+ (void) fflush(stdout);
+ }
+}
+
+int
+get_hoplim(mhdr)
+ struct msghdr *mhdr;
+{
+ struct cmsghdr *cm;
+
+ for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(mhdr); cm;
+ cm = (struct cmsghdr *)CMSG_NXTHDR(mhdr, cm)) {
+ 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);
+}
+
+double
+deltaT(t1p, t2p)
+ struct timeval *t1p, *t2p;
+{
+ double dt;
+
+ dt = (double)(t2p->tv_sec - t1p->tv_sec) * 1000.0 +
+ (double)(t2p->tv_usec - t1p->tv_usec) / 1000.0;
+ return (dt);
+}
+
+/*
+ * Convert an ICMP "type" field to a printable string.
+ */
+const char *
+pr_type(int t0)
+{
+ u_char t = t0 & 0xff;
+ const char *cp;
+
+ switch (t) {
+ case ICMP6_DST_UNREACH:
+ cp = "Destination Unreachable";
+ break;
+ case ICMP6_PACKET_TOO_BIG:
+ cp = "Packet Too Big";
+ break;
+ case ICMP6_TIME_EXCEEDED:
+ cp = "Time Exceeded";
+ break;
+ case ICMP6_PARAM_PROB:
+ cp = "Parameter Problem";
+ break;
+ case ICMP6_ECHO_REQUEST:
+ cp = "Echo Request";
+ break;
+ case ICMP6_ECHO_REPLY:
+ cp = "Echo Reply";
+ break;
+ case ICMP6_MEMBERSHIP_QUERY:
+ cp = "Group Membership Query";
+ break;
+ case ICMP6_MEMBERSHIP_REPORT:
+ cp = "Group Membership Report";
+ break;
+ case ICMP6_MEMBERSHIP_REDUCTION:
+ cp = "Group Membership Reduction";
+ break;
+ case ND_ROUTER_SOLICIT:
+ cp = "Router Solicitation";
+ break;
+ case ND_ROUTER_ADVERT:
+ cp = "Router Advertisement";
+ break;
+ case ND_NEIGHBOR_SOLICIT:
+ cp = "Neighbor Solicitation";
+ break;
+ case ND_NEIGHBOR_ADVERT:
+ cp = "Neighbor Advertisement";
+ break;
+ case ND_REDIRECT:
+ cp = "Redirect";
+ break;
+ default:
+ cp = "Unknown";
+ break;
+ }
+ return cp;
+}
+
+int
+packet_ok(mhdr, cc, seq)
+ struct msghdr *mhdr;
+ int cc;
+ int seq;
+{
+ struct icmp6_hdr *icp;
+ struct sockaddr_in6 *from = (struct sockaddr_in6 *)mhdr->msg_name;
+ u_char type, code;
+ char *buf = (char *)mhdr->msg_iov[0].iov_base;
+ struct cmsghdr *cm;
+ int *hlimp;
+ char hbuf[NI_MAXHOST];
+
+#ifdef OLDRAWSOCKET
+ int hlen;
+ struct ip6_hdr *ip;
+#endif
+
+#ifdef OLDRAWSOCKET
+ ip = (struct ip6_hdr *) buf;
+ hlen = sizeof(struct ip6_hdr);
+ if (cc < hlen + sizeof(struct icmp6_hdr)) {
+ if (verbose) {
+ if (getnameinfo((struct sockaddr *)from, from->sin6_len,
+ hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0)
+ strlcpy(hbuf, "invalid", sizeof(hbuf));
+ printf("packet too short (%d bytes) from %s\n", cc,
+ hbuf);
+ }
+ return (0);
+ }
+ cc -= hlen;
+ icp = (struct icmp6_hdr *)(buf + hlen);
+#else
+ if (cc < (int)sizeof(struct icmp6_hdr)) {
+ if (verbose) {
+ if (getnameinfo((struct sockaddr *)from, from->sin6_len,
+ hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0)
+ strlcpy(hbuf, "invalid", sizeof(hbuf));
+ printf("data too short (%d bytes) from %s\n", cc, hbuf);
+ }
+ return(0);
+ }
+ icp = (struct icmp6_hdr *)buf;
+#endif
+ /* get optional information via advanced API */
+ rcvpktinfo = NULL;
+ hlimp = NULL;
+ for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(mhdr); cm;
+ cm = (struct cmsghdr *)CMSG_NXTHDR(mhdr, cm)) {
+ if (cm->cmsg_level == IPPROTO_IPV6 &&
+ cm->cmsg_type == IPV6_PKTINFO &&
+ cm->cmsg_len ==
+ CMSG_LEN(sizeof(struct in6_pktinfo)))
+ rcvpktinfo = (struct in6_pktinfo *)(CMSG_DATA(cm));
+
+ if (cm->cmsg_level == IPPROTO_IPV6 &&
+ cm->cmsg_type == IPV6_HOPLIMIT &&
+ cm->cmsg_len == CMSG_LEN(sizeof(int)))
+ hlimp = (int *)CMSG_DATA(cm);
+ }
+ if (rcvpktinfo == NULL || hlimp == NULL) {
+ warnx("failed to get received hop limit or packet info");
+#if 0
+ return(0);
+#else
+ rcvhlim = 0; /*XXX*/
+#endif
+ }
+ else
+ rcvhlim = *hlimp;
+
+ type = icp->icmp6_type;
+ code = icp->icmp6_code;
+ if ((type == ICMP6_TIME_EXCEEDED && code == ICMP6_TIME_EXCEED_TRANSIT)
+ || type == ICMP6_DST_UNREACH) {
+ struct ip6_hdr *hip;
+ void *up;
+
+ hip = (struct ip6_hdr *)(icp + 1);
+ if ((up = get_uphdr(hip, (u_char *)(buf + cc))) == NULL) {
+ if (verbose)
+ warnx("failed to get upper layer header");
+ return(0);
+ }
+ switch (useproto) {
+ case IPPROTO_ICMPV6:
+ if (((struct icmp6_hdr *)up)->icmp6_id == ident &&
+ ((struct icmp6_hdr *)up)->icmp6_seq == htons(seq))
+ return (type == ICMP6_TIME_EXCEEDED ?
+ -1 : code + 1);
+ break;
+ case IPPROTO_UDP:
+ if (((struct udphdr *)up)->uh_sport == htons(srcport) &&
+ ((struct udphdr *)up)->uh_dport == htons(port + seq))
+ return (type == ICMP6_TIME_EXCEEDED ?
+ -1 : code + 1);
+ break;
+ case IPPROTO_NONE:
+ return (type == ICMP6_TIME_EXCEEDED ? -1 : code + 1);
+ default:
+ fprintf(stderr, "Unknown probe proto %d.\n", useproto);
+ break;
+ }
+ } else if (useproto == IPPROTO_ICMPV6 && type == ICMP6_ECHO_REPLY) {
+ if (icp->icmp6_id == ident &&
+ icp->icmp6_seq == htons(seq))
+ return (ICMP6_DST_UNREACH_NOPORT + 1);
+ }
+ if (verbose) {
+ char sbuf[NI_MAXHOST+1], dbuf[INET6_ADDRSTRLEN];
+ u_int8_t *p;
+ int i;
+
+ if (getnameinfo((struct sockaddr *)from, from->sin6_len,
+ sbuf, sizeof(sbuf), NULL, 0, NI_NUMERICHOST) != 0)
+ strlcpy(sbuf, "invalid", sizeof(sbuf));
+ printf("\n%d bytes from %s to %s", cc, sbuf,
+ rcvpktinfo ? inet_ntop(AF_INET6, &rcvpktinfo->ipi6_addr,
+ dbuf, sizeof(dbuf)) : "?");
+ printf(": icmp type %d (%s) code %d\n", type, pr_type(type),
+ icp->icmp6_code);
+ p = (u_int8_t *)(icp + 1);
+#define WIDTH 16
+ for (i = 0; i < cc; i++) {
+ if (i % WIDTH == 0)
+ printf("%04x:", i);
+ if (i % 4 == 0)
+ printf(" ");
+ printf("%02x", p[i]);
+ if (i % WIDTH == WIDTH - 1)
+ printf("\n");
+ }
+ if (cc % WIDTH != 0)
+ printf("\n");
+ }
+ return(0);
+}
+
+/*
+ * Increment pointer until find the UDP or ICMP header.
+ */
+void *
+get_uphdr(ip6, lim)
+ struct ip6_hdr *ip6;
+ u_char *lim;
+{
+ u_char *cp = (u_char *)ip6, nh;
+ int hlen;
+ static u_char none_hdr[1]; /* Fake pointer for IPPROTO_NONE. */
+
+ if (cp + sizeof(*ip6) > lim)
+ return(NULL);
+
+ nh = ip6->ip6_nxt;
+ cp += sizeof(struct ip6_hdr);
+
+ while (lim - cp >= (nh == IPPROTO_NONE ? 0 : 8)) {
+ switch (nh) {
+ case IPPROTO_ESP:
+ case IPPROTO_TCP:
+ return(NULL);
+ case IPPROTO_ICMPV6:
+ return(useproto == nh ? cp : NULL);
+ case IPPROTO_UDP:
+ return(useproto == nh ? cp : NULL);
+ case IPPROTO_NONE:
+ return(useproto == nh ? none_hdr : NULL);
+ case IPPROTO_FRAGMENT:
+ hlen = sizeof(struct ip6_frag);
+ nh = ((struct ip6_frag *)cp)->ip6f_nxt;
+ break;
+ case IPPROTO_AH:
+ hlen = (((struct ip6_ext *)cp)->ip6e_len + 2) << 2;
+ nh = ((struct ip6_ext *)cp)->ip6e_nxt;
+ break;
+ default:
+ hlen = (((struct ip6_ext *)cp)->ip6e_len + 1) << 3;
+ nh = ((struct ip6_ext *)cp)->ip6e_nxt;
+ break;
+ }
+
+ cp += hlen;
+ }
+
+ return(NULL);
+}
+
+void
+print(mhdr, cc)
+ struct msghdr *mhdr;
+ int cc;
+{
+ struct sockaddr_in6 *from = (struct sockaddr_in6 *)mhdr->msg_name;
+ char hbuf[NI_MAXHOST];
+
+ if (getnameinfo((struct sockaddr *)from, from->sin6_len,
+ hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0)
+ strlcpy(hbuf, "invalid", sizeof(hbuf));
+ if (as_path)
+ printf(" [AS%u]", as_lookup(asn, hbuf, AF_INET6));
+ if (nflag)
+ printf(" %s", hbuf);
+ else if (lflag)
+ printf(" %s (%s)", inetname((struct sockaddr *)from), hbuf);
+ else
+ printf(" %s", inetname((struct sockaddr *)from));
+
+ if (verbose) {
+#ifdef OLDRAWSOCKET
+ printf(" %d bytes to %s", cc,
+ rcvpktinfo ? inet_ntop(AF_INET6, &rcvpktinfo->ipi6_addr,
+ hbuf, sizeof(hbuf)) : "?");
+#else
+ printf(" %d bytes of data to %s", cc,
+ rcvpktinfo ? inet_ntop(AF_INET6, &rcvpktinfo->ipi6_addr,
+ hbuf, sizeof(hbuf)) : "?");
+#endif
+ }
+}
+
+/*
+ * Construct an Internet address representation.
+ * If the nflag has been supplied, give
+ * numeric value, otherwise try for symbolic name.
+ */
+const char *
+inetname(sa)
+ struct sockaddr *sa;
+{
+ static char line[NI_MAXHOST], domain[MAXHOSTNAMELEN + 1];
+ static int first = 1;
+ char *cp;
+
+ if (first && !nflag) {
+ first = 0;
+ if (gethostname(domain, sizeof(domain)) == 0 &&
+ (cp = strchr(domain, '.')))
+ (void) strlcpy(domain, cp + 1, sizeof(domain));
+ else
+ domain[0] = 0;
+ }
+ cp = NULL;
+ if (!nflag) {
+ if (getnameinfo(sa, sa->sa_len, line, sizeof(line), NULL, 0,
+ NI_NAMEREQD) == 0) {
+ if ((cp = strchr(line, '.')) &&
+ !strcmp(cp + 1, domain))
+ *cp = 0;
+ cp = line;
+ }
+ }
+ if (cp)
+ return cp;
+
+ if (getnameinfo(sa, sa->sa_len, line, sizeof(line), NULL, 0,
+ NI_NUMERICHOST) != 0)
+ strlcpy(line, "invalid", sizeof(line));
+ return line;
+}
+
+void
+usage()
+{
+
+ fprintf(stderr,
+"usage: traceroute6 [-adIlnNrUv] [-A as_server] [-f firsthop] [-g gateway]\n"
+" [-m hoplimit] [-p port] [-q probes] [-s src] [-w waittime] target\n"
+" [datalen]\n");
+ exit(1);
+}
diff --git a/usr.sbin/trpt/Makefile b/usr.sbin/trpt/Makefile
new file mode 100644
index 0000000..e49fff1
--- /dev/null
+++ b/usr.sbin/trpt/Makefile
@@ -0,0 +1,17 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG= trpt
+MAN= trpt.8
+BINGRP= kmem
+BINMODE= 2555
+
+WARNS?= 4
+
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+= -DINET6
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/trpt/Makefile.depend b/usr.sbin/trpt/Makefile.depend
new file mode 100644
index 0000000..54c1f6f
--- /dev/null
+++ b/usr.sbin/trpt/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/trpt/trpt.8 b/usr.sbin/trpt/trpt.8
new file mode 100644
index 0000000..19cae477
--- /dev/null
+++ b/usr.sbin/trpt/trpt.8
@@ -0,0 +1,149 @@
+.\" 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.
+.\"
+.\" @(#)trpt.8 8.2 (Berkeley) 12/11/93
+.\" $FreeBSD$
+.\"
+.Dd December 11, 1993
+.Dt TRPT 8
+.Os
+.Sh NAME
+.Nm trpt
+.Nd transliterate protocol trace
+.Sh SYNOPSIS
+.Nm
+.Op Fl a
+.Op Fl f
+.Op Fl j
+.Op Fl p Ar hex-address
+.Op Fl s
+.Op Fl t
+.Oo
+.Ar system Op Ar core
+.Oc
+.Sh DESCRIPTION
+The
+.Nm
+utility interrogates the buffer of
+.Tn TCP
+trace records created
+when a socket is marked for
+.Dq debugging
+(see
+.Xr setsockopt 2 ) ,
+and prints a readable description of these records.
+When no options are supplied,
+.Nm
+prints all the trace records found in the system
+grouped according to
+.Tn TCP
+connection protocol control
+block
+.Pq Tn PCB .
+.Pp
+The following options may be used to
+alter this behavior:
+.Bl -tag -width indent
+.It Fl a
+In addition to the normal output,
+print the values of the source and destination
+addresses for each packet recorded.
+.It Fl f
+Follow the trace as it occurs, waiting a short time for additional records
+each time the end of the log is reached.
+.It Fl j
+Just give a list of the protocol control block
+addresses for which there are trace records.
+.It Fl p
+Show only trace records associated with the protocol
+control block at the given address
+.Ar hex-address .
+.It Fl s
+In addition to the normal output,
+print a detailed description of the packet
+sequencing information.
+.It Fl t
+In addition to the normal output,
+print the values for all timers at each
+point in the trace.
+.El
+.Pp
+The recommended use of
+.Nm
+is as follows.
+Isolate the problem and enable debugging on the
+socket(s) involved in the connection.
+Find the address of the protocol control blocks
+associated with the sockets using the
+.Fl A
+option to
+.Xr netstat 1 .
+Then run
+.Nm
+with the
+.Fl p
+option, supplying the associated
+protocol control block addresses.
+The
+.Fl f
+option can be used to follow the trace log once the trace is located.
+If there are
+many sockets using the debugging option, the
+.Fl j
+option may be useful in checking to see if
+any trace records are present for the socket in
+question.
+.Pp
+If debugging is being performed on a system or
+core file other than the default, the last two
+arguments may be used to supplant the defaults.
+.Sh FILES
+.Bl -tag -width /boot/kernel/kernel -compact
+.It Pa /boot/kernel/kernel
+.It Pa /dev/kmem
+.El
+.Sh DIAGNOSTICS
+.Bl -diag
+.It no namelist
+When the system image does not
+contain the proper symbols to find the trace buffer;
+others which should be self explanatory.
+.El
+.Sh SEE ALSO
+.Xr netstat 1 ,
+.Xr setsockopt 2
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.2 .
+.Sh BUGS
+Should also print the data for each input or output,
+but this is not saved in the trace record.
+.Pp
+The output format is inscrutable and should be described
+here.
diff --git a/usr.sbin/trpt/trpt.c b/usr.sbin/trpt/trpt.c
new file mode 100644
index 0000000..d85d41d
--- /dev/null
+++ b/usr.sbin/trpt/trpt.c
@@ -0,0 +1,465 @@
+/*
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1983, 1988, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)trpt.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#define PRUREQUESTS
+#include <sys/protosw.h>
+#include <sys/file.h>
+#include <sys/time.h>
+
+#include <net/route.h>
+#include <net/if.h>
+
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#ifdef INET6
+#include <netinet/ip6.h>
+#endif
+#include <netinet/ip_var.h>
+#include <netinet/tcp.h>
+#define TCPSTATES
+#include <netinet/tcp_fsm.h>
+#include <netinet/tcp_seq.h>
+#define TCPTIMERS
+#include <netinet/tcp_timer.h>
+#include <netinet/tcp_var.h>
+#include <netinet/tcpip.h>
+#define TANAMES
+#include <netinet/tcp_debug.h>
+
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <nlist.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+struct nlist nl[3];
+#define N_TCP_DEBUG 0
+#define N_TCP_DEBX 1
+
+static caddr_t tcp_pcbs[TCP_NDEBUG];
+static n_time ntime;
+static int aflag, kflag, memf, follow, sflag, tflag;
+
+void dotrace(caddr_t);
+void klseek(int, off_t, int);
+int numeric(const void *, const void *);
+void tcp_trace(short, short, struct tcpcb *, int, void *, struct tcphdr *, int);
+static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ int ch, i, jflag, npcbs;
+ const char *core, *syst;
+
+ nl[0].n_name = strdup("_tcp_debug");
+ nl[1].n_name = strdup("_tcp_debx");
+
+ jflag = npcbs = 0;
+ while ((ch = getopt(argc, argv, "afjp:st")) != -1)
+ switch (ch) {
+ case 'a':
+ ++aflag;
+ break;
+ case 'f':
+ ++follow;
+ setlinebuf(stdout);
+ break;
+ case 'j':
+ ++jflag;
+ break;
+ case 'p':
+ if (npcbs >= TCP_NDEBUG)
+ errx(1, "too many pcb's specified");
+ (void)sscanf(optarg, "%x", (int *)&tcp_pcbs[npcbs++]);
+ break;
+ case 's':
+ ++sflag;
+ break;
+ case 't':
+ ++tflag;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ core = _PATH_KMEM;
+ if (argc > 0) {
+ syst = *argv;
+ argc--, argv++;
+ if (argc > 0) {
+ core = *argv;
+ argc--, argv++;
+ ++kflag;
+ }
+ /*
+ * Discard setgid privileges if not the running kernel so that
+ * bad guys can't print interesting stuff from kernel memory.
+ */
+ if (setgid(getgid()) != 0)
+ err(1, "setgid");
+ }
+ else
+ syst = getbootfile();
+
+ if (nlist(syst, nl) < 0 || !nl[0].n_value)
+ errx(1, "%s: no namelist", syst);
+ if ((memf = open(core, O_RDONLY)) < 0)
+ err(2, "%s", core);
+ if (setgid(getgid()) != 0)
+ err(1, "setgid");
+ if (kflag)
+ errx(1, "can't do core files yet");
+ (void)klseek(memf, (off_t)nl[N_TCP_DEBX].n_value, L_SET);
+ if (read(memf, (char *)&tcp_debx, sizeof(tcp_debx)) !=
+ sizeof(tcp_debx))
+ err(3, "tcp_debx");
+ (void)klseek(memf, (off_t)nl[N_TCP_DEBUG].n_value, L_SET);
+ if (read(memf, (char *)tcp_debug, sizeof(tcp_debug)) !=
+ sizeof(tcp_debug))
+ err(3, "tcp_debug");
+ /*
+ * If no control blocks have been specified, figure
+ * out how many distinct one we have and summarize
+ * them in tcp_pcbs for sorting the trace records
+ * below.
+ */
+ if (!npcbs) {
+ for (i = 0; i < TCP_NDEBUG; i++) {
+ register struct tcp_debug *td = &tcp_debug[i];
+ register int j;
+
+ if (td->td_tcb == 0)
+ continue;
+ for (j = 0; j < npcbs; j++)
+ if (tcp_pcbs[j] == td->td_tcb)
+ break;
+ if (j >= npcbs)
+ tcp_pcbs[npcbs++] = td->td_tcb;
+ }
+ if (!npcbs)
+ exit(0);
+ }
+ qsort(tcp_pcbs, npcbs, sizeof(caddr_t), numeric);
+ if (jflag) {
+ for (i = 0;;) {
+ printf("%p", (void *)tcp_pcbs[i]);
+ if (++i == npcbs)
+ break;
+ fputs(", ", stdout);
+ }
+ putchar('\n');
+ }
+ else for (i = 0; i < npcbs; i++) {
+ printf("\n%p:\n", tcp_pcbs[i]);
+ dotrace(tcp_pcbs[i]);
+ }
+ exit(0);
+}
+
+static void
+usage()
+{
+ (void)fprintf(stderr,
+ "usage: trpt [-afjst] [-p hex-address] [system [core]]\n");
+ exit(1);
+}
+
+void
+dotrace(tcpcb)
+ register caddr_t tcpcb;
+{
+ register struct tcp_debug *td;
+ register int i;
+ int prev_debx = tcp_debx, family;
+
+again: if (--tcp_debx < 0)
+ tcp_debx = TCP_NDEBUG - 1;
+ for (i = prev_debx % TCP_NDEBUG; i < TCP_NDEBUG; i++) {
+ td = &tcp_debug[i];
+ if (tcpcb && td->td_tcb != tcpcb)
+ continue;
+ ntime = ntohl(td->td_time);
+#ifdef INET6
+ family = td->td_family;
+#else
+ family = AF_INET;
+#endif
+ switch(family) {
+ case AF_INET:
+ tcp_trace(td->td_act, td->td_ostate,
+ &td->td_cb, td->td_family, &td->td_ti.ti_i,
+ &td->td_ti.ti_t, td->td_req);
+ break;
+#ifdef INET6
+ case AF_INET6:
+ tcp_trace(td->td_act, td->td_ostate,
+ &td->td_cb, td->td_family, &td->td_ti6.ip6,
+ &td->td_ti6.th, td->td_req);
+ break;
+#endif
+ }
+ if (i == tcp_debx)
+ goto done;
+ }
+ for (i = 0; i <= tcp_debx % TCP_NDEBUG; i++) {
+ td = &tcp_debug[i];
+ if (tcpcb && td->td_tcb != tcpcb)
+ continue;
+ ntime = ntohl(td->td_time);
+#ifdef INET6
+ family = td->td_family;
+#else
+ family = AF_INET;
+#endif
+ switch(family) {
+ case AF_INET:
+ tcp_trace(td->td_act, td->td_ostate,
+ &td->td_cb, td->td_family, &td->td_ti.ti_i,
+ &td->td_ti.ti_t, td->td_req);
+ break;
+#ifdef INET6
+ case AF_INET6:
+ tcp_trace(td->td_act, td->td_ostate,
+ &td->td_cb, td->td_family, &td->td_ti6.ip6,
+ &td->td_ti6.th, td->td_req);
+ break;
+#endif
+ }
+ }
+done: if (follow) {
+ prev_debx = tcp_debx + 1;
+ if (prev_debx >= TCP_NDEBUG)
+ prev_debx = 0;
+ do {
+ sleep(1);
+ (void)klseek(memf, (off_t)nl[N_TCP_DEBX].n_value, L_SET);
+ if (read(memf, (char *)&tcp_debx, sizeof(tcp_debx)) !=
+ sizeof(tcp_debx))
+ err(3, "tcp_debx");
+ } while (tcp_debx == prev_debx);
+ (void)klseek(memf, (off_t)nl[N_TCP_DEBUG].n_value, L_SET);
+ if (read(memf, (char *)tcp_debug, sizeof(tcp_debug)) !=
+ sizeof(tcp_debug))
+ err(3, "tcp_debug");
+ goto again;
+ }
+}
+
+/*
+ * Tcp debug routines
+ */
+/*ARGSUSED*/
+void
+tcp_trace(short act, short ostate, struct tcpcb *tp, int family __unused,
+ void *ip, struct tcphdr *th, int req)
+{
+ tcp_seq seq, ack;
+ int flags, len, win, timer;
+ struct ip *ip4;
+#ifdef INET6
+ int isipv6, nopkt = 1;
+ struct ip6_hdr *ip6;
+ char ntop_buf[INET6_ADDRSTRLEN];
+#endif
+
+#ifdef INET6
+ switch (family) {
+ case AF_INET:
+ nopkt = 0;
+ isipv6 = 0;
+ ip4 = (struct ip *)ip;
+ break;
+ case AF_INET6:
+ nopkt = 0;
+ isipv6 = 1;
+ ip6 = (struct ip6_hdr *)ip;
+ case 0:
+ default:
+ break;
+ }
+#else
+ ip4 = (struct ip *)ip;
+#endif
+ printf("%03ld %s:%s ", (long)((ntime/10) % 1000), tcpstates[ostate],
+ tanames[act]);
+ switch (act) {
+ case TA_INPUT:
+ case TA_OUTPUT:
+ case TA_DROP:
+#ifdef INET6
+ if (nopkt != 0)
+ break;
+#endif
+ if (aflag) {
+ printf("(src=%s,%u, ",
+
+#ifdef INET6
+ isipv6
+ ? inet_ntop(AF_INET6, &ip6->ip6_src, ntop_buf,
+ sizeof(ntop_buf)) :
+#endif
+ inet_ntoa(ip4->ip_src),
+ ntohs(th->th_sport));
+ printf("dst=%s,%u)",
+#ifdef INET6
+ isipv6
+ ? inet_ntop(AF_INET6, &ip6->ip6_dst, ntop_buf,
+ sizeof(ntop_buf)) :
+#endif
+ inet_ntoa(ip4->ip_dst),
+ ntohs(th->th_dport));
+ }
+ seq = th->th_seq;
+ ack = th->th_ack;
+
+ len =
+#ifdef INET6
+ isipv6 ? ip6->ip6_plen :
+#endif
+ ip4->ip_len;
+ win = th->th_win;
+ if (act == TA_OUTPUT) {
+ seq = ntohl(seq);
+ ack = ntohl(ack);
+ len = ntohs(len);
+ win = ntohs(win);
+ }
+ if (act == TA_OUTPUT)
+ len -= sizeof(struct tcphdr);
+ if (len)
+ printf("[%lx..%lx)", (u_long)seq, (u_long)(seq + len));
+ else
+ printf("%lx", (u_long)seq);
+ printf("@%lx", (u_long)ack);
+ if (win)
+ printf("(win=%x)", win);
+ flags = th->th_flags;
+ if (flags) {
+ const char *cp = "<";
+#define pf(flag, string) { \
+ if (th->th_flags&flag) { \
+ (void)printf("%s%s", cp, string); \
+ cp = ","; \
+ } \
+}
+ pf(TH_SYN, "SYN");
+ pf(TH_ACK, "ACK");
+ pf(TH_FIN, "FIN");
+ pf(TH_RST, "RST");
+ pf(TH_PUSH, "PUSH");
+ pf(TH_URG, "URG");
+ printf(">");
+ }
+ break;
+ case TA_USER:
+ timer = req >> 8;
+ req &= 0xff;
+ printf("%s", prurequests[req]);
+ if (req == PRU_SLOWTIMO || req == PRU_FASTTIMO)
+ printf("<%s>", tcptimers[timer]);
+ break;
+ }
+ printf(" -> %s", tcpstates[tp->t_state]);
+ /* print out internal state of tp !?! */
+ printf("\n");
+ if (sflag) {
+ printf("\trcv_nxt %lx rcv_wnd %lx snd_una %lx snd_nxt %lx snd_max %lx\n",
+ (u_long)tp->rcv_nxt, tp->rcv_wnd,
+ (u_long)tp->snd_una, (u_long)tp->snd_nxt,
+ (u_long)tp->snd_max);
+ printf("\tsnd_wl1 %lx snd_wl2 %lx snd_wnd %lx\n",
+ (u_long)tp->snd_wl1,
+ (u_long)tp->snd_wl2, tp->snd_wnd);
+ }
+ /* print out timers? */
+#if 0
+ /*
+ * XXX
+ * kernel now uses callouts, not integer time values.
+ */
+ if (tflag) {
+ register char *cp = "\t";
+ register int i;
+
+ for (i = 0; i < TCPT_NTIMERS; i++) {
+ if (tp->t_timer[i] == 0)
+ continue;
+ printf("%s%s=%d", cp, tcptimers[i], tp->t_timer[i]);
+ if (i == TCPT_REXMT)
+ printf(" (t_rxtshft=%d)", tp->t_rxtshift);
+ cp = ", ";
+ }
+ if (*cp != '\t')
+ putchar('\n');
+ }
+#endif
+}
+
+int
+numeric(v1, v2)
+ const void *v1, *v2;
+{
+ const caddr_t *c1 = v1, *c2 = v2;
+ return(*c1 - *c2);
+}
+
+void
+klseek(fd, base, off)
+ int fd, off;
+ off_t base;
+{
+ (void)lseek(fd, base, off);
+}
diff --git a/usr.sbin/tzsetup/Makefile b/usr.sbin/tzsetup/Makefile
new file mode 100644
index 0000000..de7375f
--- /dev/null
+++ b/usr.sbin/tzsetup/Makefile
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+PROG= tzsetup
+MAN= tzsetup.8
+
+CFLAGS+= -I${.CURDIR}/../../contrib/dialog -I.
+
+WARNS?= 3
+
+LIBADD= dialog ncursesw
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/tzsetup/Makefile.depend b/usr.sbin/tzsetup/Makefile.depend
new file mode 100644
index 0000000..5e97e25
--- /dev/null
+++ b/usr.sbin/tzsetup/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libdialog \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/msun \
+ lib/ncurses/ncursesw \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/tzsetup/tzsetup.8 b/usr.sbin/tzsetup/tzsetup.8
new file mode 100644
index 0000000..6ac5749
--- /dev/null
+++ b/usr.sbin/tzsetup/tzsetup.8
@@ -0,0 +1,167 @@
+.\" Copyright (c) 1996 Wolfram Schneider <wosch@FreeBSD.org>. Berlin.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 21, 2009
+.Dt TZSETUP 8
+.Os
+.Sh NAME
+.Nm tzsetup
+.Nd set local timezone
+.Sh SYNOPSIS
+.Nm
+.Op Fl nrs
+.Op Fl C Ar chroot_directory
+.Op Ar zoneinfo_file | zoneinfo_name
+.Sh DESCRIPTION
+The
+.Nm
+utility reads a database of timezone information and presents a menu
+allowing the user to select a specific zone without knowing the details
+of the database layout.
+The selected zone is installed as the system
+default zone.
+The
+.Nm
+utility also determines whether any adjustment is necessary for systems where
+the hardware clock does not keep
+.Tn UTC .
+.Pp
+The following options are available:
+.Bl -tag -offset indent -width Fl
+.It Fl C Ar chroot_directory
+Open all files and directories relative to
+.Ar chroot_directory .
+.It Fl n
+Do not create or copy files.
+.It Fl r
+Reinstall the zoneinfo file installed last time.
+The name is obtained from
+.Pa /var/db/zoneinfo .
+.It Fl s
+Skip the initial question about adjusting the clock if not set to
+.Tn UTC .
+.El
+.Pp
+It is possible to short-circuit the menu system by specifying the
+location of a
+.Ar zoneinfo_file
+or the name of the
+.Ar zoneinfo_name
+on the command line; this is intended mainly for pre-configured installation
+scripts or people who know which zoneinfo they want to install.
+.Sh TIMEZONE DATABASE
+The contents of the timezone database are indexed by
+.Pa /usr/share/zoneinfo/zone.tab .
+This file lists, for each timezone data file, the
+.Tn ISO
+3166 territory code, approximate geographical coordinates
+(in
+.Tn ISO
+6709 format),
+and location within the territory.
+.Pp
+The maintainers of the database maintain the following policies:
+.Bl -enum -offset indent
+.It
+At least one zone for every country or inhabited geographical territory.
+.It
+One zone for every distinct, documented timezone history since the
+beginning of the
+.Ux
+epoch (January 1, 1970,
+.Tn GMT ) .
+.It
+Each zone is named for the most populous city therein.
+(Where possible,
+the database includes pre-1970 history for its city.)
+.El
+The source code to the database
+.Pq Pa /usr/src/share/zoneinfo/[a-z]*
+contains many additional comments and documentation references for the
+historically minded.
+.Sh FILES
+.Bl -tag -width ".Pa /usr/share/zoneinfo/zone.tab" -compact
+.It Pa /etc/localtime
+current time zone file
+.It Pa /etc/wall_cmos_clock
+see
+.Xr adjkerntz 8
+.It Pa /usr/share/misc/iso3166
+mapping of
+.Tn ISO
+3166 territory codes to names
+.It Pa /usr/share/zoneinfo
+directory for zoneinfo files
+.It Pa /usr/share/zoneinfo/zone.tab
+mapping of timezone file to country and location
+.It Pa /var/db/zoneinfo
+saved name of the timezone file installed last
+.El
+.Sh EXAMPLES
+Normal usage, to select the right zoneinfo file via the dialog-based
+user interface:
+.Dl tzsetup
+.Pp
+Install the file
+.Pa /usr/share/zoneinfo/Australia/Sydney :
+.Dl "tzsetup /usr/share/zoneinfo/Australia/Sydney"
+.Pp
+Install the zoneinfo file for Australia/Sydney, assumed to be located
+in
+.Pa /usr/share/zoneinfo :
+.Dl "tzsetup Australia/Sydney"
+.Pp
+After a reinstall of the zoneinfo files, you can reinstall the
+latest installed zoneinfo file (as specified in
+.Pa /var/db/zoneinfo ) :
+.Dl "tzsetup -r"
+.Sh SEE ALSO
+.Xr date 1 ,
+.Xr adjtime 2 ,
+.Xr ctime 3 ,
+.Xr timezone 3 ,
+.Xr tzfile 5 ,
+.Xr adjkerntz 8 ,
+.Xr zdump 8 ,
+.Xr zic 8
+.Sh DISCLAIMER
+The representation of certain localities as being associated with certain
+countries and/or territories is for the purposes of identification only,
+and does not imply any endorsement or rejection on the part of the
+.Fx
+Project of the territorial claims of any entity.
+.Sh BUGS
+Programs which are already running when
+.Nm
+creates or updates
+.Pa /etc/localtime
+will not reflect the updated timezone.
+When the system is first configured for a
+.Pf non- Tn UTC
+hardware clock, it is necessary to run
+.Xr adjkerntz 8
+(which normally happens as a part of system startup) in order to update
+the kernel's idea of the correct timezone offset.
diff --git a/usr.sbin/tzsetup/tzsetup.c b/usr.sbin/tzsetup/tzsetup.c
new file mode 100644
index 0000000..fc80364
--- /dev/null
+++ b/usr.sbin/tzsetup/tzsetup.c
@@ -0,0 +1,1065 @@
+/*
+ * Copyright 1996 Massachusetts Institute of Technology
+ *
+ * Permission to use, copy, modify, and distribute this software and
+ * its documentation for any purpose and without fee is hereby
+ * granted, provided that both the above copyright notice and this
+ * permission notice appear in all copies, that both the above
+ * copyright notice and this permission notice appear in all
+ * supporting documentation, and that the name of M.I.T. not be used
+ * in advertising or publicity pertaining to distribution of the
+ * software without specific, written prior permission. M.I.T. makes
+ * no representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS
+ * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
+ * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Second attempt at a `tzmenu' program, using the separate description
+ * files provided in newer tzdata releases.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sys/fcntl.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+
+#include <dialog.h>
+
+#define _PATH_ZONETAB "/usr/share/zoneinfo/zone.tab"
+#define _PATH_ISO3166 "/usr/share/misc/iso3166"
+#define _PATH_ZONEINFO "/usr/share/zoneinfo"
+#define _PATH_LOCALTIME "/etc/localtime"
+#define _PATH_DB "/var/db/zoneinfo"
+#define _PATH_WALL_CMOS_CLOCK "/etc/wall_cmos_clock"
+
+#ifdef PATH_MAX
+#define SILLY_BUFFER_SIZE 2*PATH_MAX
+#else
+#warning "Somebody needs to fix this to dynamically size this buffer."
+#define SILLY_BUFFER_SIZE 2048
+#endif
+
+/* special return codes for `fire' actions */
+#define DITEM_FAILURE 1
+
+/* flags - returned in upper 16 bits of return status */
+#define DITEM_LEAVE_MENU (1 << 16)
+#define DITEM_RECREATE (1 << 18)
+
+/* for use in describing more exotic behaviors */
+typedef struct dialogMenuItem {
+ char *prompt;
+ char *title;
+ int (*fire)(struct dialogMenuItem *self);
+ void *data;
+} dialogMenuItem;
+
+static int
+xdialog_count_rows(const char *p)
+{
+ int rows = 0;
+
+ while ((p = strchr(p, '\n')) != NULL) {
+ p++;
+ if (*p == '\0')
+ break;
+ rows++;
+ }
+
+ return rows ? rows : 1;
+}
+
+static int
+xdialog_count_columns(const char *p)
+{
+ int len;
+ int max_len = 0;
+ const char *q;
+
+ for (; (q = strchr(p, '\n')) != NULL; p = q + 1) {
+ len = q - p;
+ max_len = MAX(max_len, len);
+ }
+
+ len = strlen(p);
+ max_len = MAX(max_len, len);
+ return max_len;
+}
+
+static int
+xdialog_menu(const char *title, const char *cprompt, int height, int width,
+ int menu_height, int item_no, dialogMenuItem *ditems)
+{
+ int i, result, choice = 0;
+ DIALOG_LISTITEM *listitems;
+ DIALOG_VARS save_vars;
+
+ dlg_save_vars(&save_vars);
+
+ /* initialize list items */
+ listitems = dlg_calloc(DIALOG_LISTITEM, item_no + 1);
+ assert_ptr(listitems, "xdialog_menu");
+ for (i = 0; i < item_no; i++) {
+ listitems[i].name = ditems[i].prompt;
+ listitems[i].text = ditems[i].title;
+ }
+
+ /* calculate height */
+ if (height < 0)
+ height = xdialog_count_rows(cprompt) + menu_height + 4 + 2;
+ if (height > LINES)
+ height = LINES;
+
+ /* calculate width */
+ if (width < 0) {
+ int tag_x = 0;
+
+ for (i = 0; i < item_no; i++) {
+ int j, l;
+
+ l = strlen(listitems[i].name);
+ for (j = 0; j < item_no; j++) {
+ int k = strlen(listitems[j].text);
+ tag_x = MAX(tag_x, l + k + 2);
+ }
+ }
+ width = MAX(xdialog_count_columns(cprompt), title != NULL ? xdialog_count_columns(title) : 0);
+ width = MAX(width, tag_x + 4) + 4;
+ }
+ width = MAX(width, 24);
+ if (width > COLS)
+ width = COLS;
+
+again:
+ dialog_vars.default_item = listitems[choice].name;
+ result = dlg_menu(title, cprompt, height, width,
+ menu_height, item_no, listitems, &choice, NULL);
+ switch (result) {
+ case DLG_EXIT_ESC:
+ result = -1;
+ break;
+ case DLG_EXIT_OK:
+ if (ditems[choice].fire != NULL) {
+ int status;
+
+ status = ditems[choice].fire(ditems + choice);
+ if (status & DITEM_RECREATE) {
+ dlg_clear();
+ goto again;
+ }
+ }
+ result = 0;
+ break;
+ case DLG_EXIT_CANCEL:
+ default:
+ result = 1;
+ break;
+ }
+
+ free(listitems);
+ dlg_restore_vars(&save_vars);
+ return result;
+}
+
+static char path_zonetab[MAXPATHLEN], path_iso3166[MAXPATHLEN],
+ path_zoneinfo[MAXPATHLEN], path_localtime[MAXPATHLEN],
+ path_db[MAXPATHLEN], path_wall_cmos_clock[MAXPATHLEN];
+
+static int reallydoit = 1;
+static int reinstall = 0;
+static int usedialog = 1;
+static char *chrootenv = NULL;
+
+static void usage(void);
+static int confirm_zone(const char *filename);
+static int continent_country_menu(dialogMenuItem *);
+static int install_zoneinfo_file(const char *zoneinfo_file);
+static int set_zone_multi(dialogMenuItem *);
+static int set_zone_whole_country(dialogMenuItem *);
+static int set_zone_menu(dialogMenuItem *);
+static int set_zone_utc(void);
+
+struct continent {
+ dialogMenuItem *menu;
+ int nitems;
+};
+
+static struct continent africa, america, antarctica, arctic, asia, atlantic;
+static struct continent australia, europe, indian, pacific, utc;
+
+static struct continent_names {
+ const char *name;
+ struct continent *continent;
+} continent_names[] = {
+ { "Africa", &africa },
+ { "America", &america },
+ { "Antarctica", &antarctica },
+ { "Arctic", &arctic },
+ { "Asia", &asia },
+ { "Atlantic", &atlantic },
+ { "Australia", &australia },
+ { "Europe", &europe },
+ { "Indian", &indian },
+ { "Pacific", &pacific },
+ { "UTC", &utc }
+};
+
+static struct continent_items {
+ char prompt[2];
+ char title[30];
+} continent_items[] = {
+ { "1", "Africa" },
+ { "2", "America -- North and South" },
+ { "3", "Antarctica" },
+ { "4", "Arctic Ocean" },
+ { "5", "Asia" },
+ { "6", "Atlantic Ocean" },
+ { "7", "Australia" },
+ { "8", "Europe" },
+ { "9", "Indian Ocean" },
+ { "0", "Pacific Ocean" },
+ { "a", "UTC" }
+};
+
+#define NCONTINENTS \
+ (int)((sizeof(continent_items)) / (sizeof(continent_items[0])))
+static dialogMenuItem continents[NCONTINENTS];
+
+#define OCEANP(x) ((x) == 3 || (x) == 5 || (x) == 8 || (x) == 9)
+
+static int
+continent_country_menu(dialogMenuItem *continent)
+{
+ char title[64], prompt[64];
+ struct continent *contp = continent->data;
+ int isocean = OCEANP(continent - continents);
+ int menulen;
+ int rv;
+
+ if (strcmp(continent->title, "UTC") == 0)
+ return set_zone_utc();
+
+ /* Short cut -- if there's only one country, don't post a menu. */
+ if (contp->nitems == 1)
+ return (contp->menu[0].fire(&contp->menu[0]));
+
+ /* It's amazing how much good grammar really matters... */
+ if (!isocean) {
+ snprintf(title, sizeof(title), "Countries in %s",
+ continent->title);
+ snprintf(prompt, sizeof(prompt), "Select a country or region");
+ } else {
+ snprintf(title, sizeof(title), "Islands and groups in the %s",
+ continent->title);
+ snprintf(prompt, sizeof(prompt), "Select an island or group");
+ }
+
+ menulen = contp->nitems < 16 ? contp->nitems : 16;
+ rv = xdialog_menu(title, prompt, -1, -1, menulen, contp->nitems,
+ contp->menu);
+ if (rv == 0)
+ return (DITEM_LEAVE_MENU);
+ return (DITEM_RECREATE);
+}
+
+static struct continent *
+find_continent(const char *name)
+{
+ int i;
+
+ for (i = 0; i < NCONTINENTS; i++)
+ if (strcmp(name, continent_names[i].name) == 0)
+ return (continent_names[i].continent);
+ return (0);
+}
+
+struct country {
+ char *name;
+ char *tlc;
+ int nzones;
+ char *filename; /* use iff nzones < 0 */
+ struct continent *continent; /* use iff nzones < 0 */
+ TAILQ_HEAD(, zone) zones; /* use iff nzones > 0 */
+ dialogMenuItem *submenu; /* use iff nzones > 0 */
+};
+
+struct zone {
+ TAILQ_ENTRY(zone) link;
+ char *descr;
+ char *filename;
+ struct continent *continent;
+};
+
+/*
+ * This is the easiest organization... we use ISO 3166 country codes,
+ * of the two-letter variety, so we just size this array to suit.
+ * Beats worrying about dynamic allocation.
+ */
+#define NCOUNTRIES (26 * 26)
+static struct country countries[NCOUNTRIES];
+
+#define CODE2INT(s) ((s[0] - 'A') * 26 + (s[1] - 'A'))
+
+/*
+ * Read the ISO 3166 country code database in _PATH_ISO3166
+ * (/usr/share/misc/iso3166). On error, exit via err(3).
+ */
+static void
+read_iso3166_table(void)
+{
+ FILE *fp;
+ struct country *cp;
+ size_t len;
+ char *s, *t, *name;
+ int lineno;
+
+ fp = fopen(path_iso3166, "r");
+ if (!fp)
+ err(1, "%s", path_iso3166);
+ lineno = 0;
+
+ while ((s = fgetln(fp, &len)) != 0) {
+ lineno++;
+ if (s[len - 1] != '\n')
+ errx(1, "%s:%d: invalid format", path_iso3166, lineno);
+ s[len - 1] = '\0';
+ if (s[0] == '#' || strspn(s, " \t") == len - 1)
+ continue;
+
+ /* Isolate the two-letter code. */
+ t = strsep(&s, "\t");
+ if (t == 0 || strlen(t) != 2)
+ errx(1, "%s:%d: invalid format", path_iso3166, lineno);
+ if (t[0] < 'A' || t[0] > 'Z' || t[1] < 'A' || t[1] > 'Z')
+ errx(1, "%s:%d: invalid code `%s'", path_iso3166,
+ lineno, t);
+
+ /* Now skip past the three-letter and numeric codes. */
+ name = strsep(&s, "\t"); /* 3-let */
+ if (name == 0 || strlen(name) != 3)
+ errx(1, "%s:%d: invalid format", path_iso3166, lineno);
+ name = strsep(&s, "\t"); /* numeric */
+ if (name == 0 || strlen(name) != 3)
+ errx(1, "%s:%d: invalid format", path_iso3166, lineno);
+
+ name = s;
+
+ cp = &countries[CODE2INT(t)];
+ if (cp->name)
+ errx(1, "%s:%d: country code `%s' multiply defined: %s",
+ path_iso3166, lineno, t, cp->name);
+ cp->name = strdup(name);
+ if (cp->name == NULL)
+ errx(1, "malloc failed");
+ cp->tlc = strdup(t);
+ if (cp->tlc == NULL)
+ errx(1, "malloc failed");
+ }
+
+ fclose(fp);
+}
+
+static void
+add_zone_to_country(int lineno, const char *tlc, const char *descr,
+ const char *file, struct continent *cont)
+{
+ struct zone *zp;
+ struct country *cp;
+
+ if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z')
+ errx(1, "%s:%d: country code `%s' invalid", path_zonetab,
+ lineno, tlc);
+
+ cp = &countries[CODE2INT(tlc)];
+ if (cp->name == 0)
+ errx(1, "%s:%d: country code `%s' unknown", path_zonetab,
+ lineno, tlc);
+
+ if (descr) {
+ if (cp->nzones < 0)
+ errx(1, "%s:%d: conflicting zone definition",
+ path_zonetab, lineno);
+
+ zp = malloc(sizeof(*zp));
+ if (zp == 0)
+ errx(1, "malloc(%zu)", sizeof(*zp));
+
+ if (cp->nzones == 0)
+ TAILQ_INIT(&cp->zones);
+
+ zp->descr = strdup(descr);
+ if (zp->descr == NULL)
+ errx(1, "malloc failed");
+ zp->filename = strdup(file);
+ if (zp->filename == NULL)
+ errx(1, "malloc failed");
+ zp->continent = cont;
+ TAILQ_INSERT_TAIL(&cp->zones, zp, link);
+ cp->nzones++;
+ } else {
+ if (cp->nzones > 0)
+ errx(1, "%s:%d: zone must have description",
+ path_zonetab, lineno);
+ if (cp->nzones < 0)
+ errx(1, "%s:%d: zone multiply defined",
+ path_zonetab, lineno);
+ cp->nzones = -1;
+ cp->filename = strdup(file);
+ if (cp->filename == NULL)
+ errx(1, "malloc failed");
+ cp->continent = cont;
+ }
+}
+
+/*
+ * This comparison function intentionally sorts all of the null-named
+ * ``countries''---i.e., the codes that don't correspond to a real
+ * country---to the end. Everything else is lexical by country name.
+ */
+static int
+compare_countries(const void *xa, const void *xb)
+{
+ const struct country *a = xa, *b = xb;
+
+ if (a->name == 0 && b->name == 0)
+ return (0);
+ if (a->name == 0 && b->name != 0)
+ return (1);
+ if (b->name == 0)
+ return (-1);
+
+ return (strcmp(a->name, b->name));
+}
+
+/*
+ * This must be done AFTER all zone descriptions are read, since it breaks
+ * CODE2INT().
+ */
+static void
+sort_countries(void)
+{
+
+ qsort(countries, NCOUNTRIES, sizeof(countries[0]), compare_countries);
+}
+
+static void
+read_zones(void)
+{
+ char contbuf[16];
+ FILE *fp;
+ struct continent *cont;
+ size_t len;
+ char *line, *tlc, *file, *descr, *p;
+ int lineno;
+
+ fp = fopen(path_zonetab, "r");
+ if (!fp)
+ err(1, "%s", path_zonetab);
+ lineno = 0;
+
+ while ((line = fgetln(fp, &len)) != 0) {
+ lineno++;
+ if (line[len - 1] != '\n')
+ errx(1, "%s:%d: invalid format", path_zonetab, lineno);
+ line[len - 1] = '\0';
+ if (line[0] == '#')
+ continue;
+
+ tlc = strsep(&line, "\t");
+ if (strlen(tlc) != 2)
+ errx(1, "%s:%d: invalid country code `%s'",
+ path_zonetab, lineno, tlc);
+ /* coord = */ strsep(&line, "\t"); /* Unused */
+ file = strsep(&line, "\t");
+ p = strchr(file, '/');
+ if (p == 0)
+ errx(1, "%s:%d: invalid zone name `%s'", path_zonetab,
+ lineno, file);
+ contbuf[0] = '\0';
+ strncat(contbuf, file, p - file);
+ cont = find_continent(contbuf);
+ if (!cont)
+ errx(1, "%s:%d: invalid region `%s'", path_zonetab,
+ lineno, contbuf);
+
+ descr = (line != NULL && *line != '\0') ? line : NULL;
+
+ add_zone_to_country(lineno, tlc, descr, file, cont);
+ }
+ fclose(fp);
+}
+
+static void
+make_menus(void)
+{
+ struct country *cp;
+ struct zone *zp, *zp2;
+ struct continent *cont;
+ dialogMenuItem *dmi;
+ int i;
+
+ /*
+ * First, count up all the countries in each continent/ocean.
+ * Be careful to count those countries which have multiple zones
+ * only once for each. NB: some countries are in multiple
+ * continents/oceans.
+ */
+ for (cp = countries; cp->name; cp++) {
+ if (cp->nzones == 0)
+ continue;
+ if (cp->nzones < 0) {
+ cp->continent->nitems++;
+ } else {
+ TAILQ_FOREACH(zp, &cp->zones, link) {
+ cont = zp->continent;
+ for (zp2 = TAILQ_FIRST(&cp->zones);
+ zp2->continent != cont;
+ zp2 = TAILQ_NEXT(zp2, link))
+ ;
+ if (zp2 == zp)
+ zp->continent->nitems++;
+ }
+ }
+ }
+
+ /*
+ * Now allocate memory for the country menus and initialize
+ * continent menus. We set nitems back to zero so that we can
+ * use it for counting again when we actually build the menus.
+ */
+ memset(continents, 0, sizeof(continents));
+ for (i = 0; i < NCONTINENTS; i++) {
+ continent_names[i].continent->menu =
+ malloc(sizeof(dialogMenuItem) *
+ continent_names[i].continent->nitems);
+ if (continent_names[i].continent->menu == 0)
+ errx(1, "malloc for continent menu");
+ continent_names[i].continent->nitems = 0;
+ continents[i].prompt = continent_items[i].prompt;
+ continents[i].title = continent_items[i].title;
+ continents[i].fire = continent_country_menu;
+ continents[i].data = continent_names[i].continent;
+ }
+
+ /*
+ * Now that memory is allocated, create the menu items for
+ * each continent. For multiple-zone countries, also create
+ * the country's zone submenu.
+ */
+ for (cp = countries; cp->name; cp++) {
+ if (cp->nzones == 0)
+ continue;
+ if (cp->nzones < 0) {
+ dmi = &cp->continent->menu[cp->continent->nitems];
+ memset(dmi, 0, sizeof(*dmi));
+ asprintf(&dmi->prompt, "%d", ++cp->continent->nitems);
+ dmi->title = cp->name;
+ dmi->fire = set_zone_whole_country;
+ dmi->data = cp;
+ } else {
+ cp->submenu = malloc(cp->nzones * sizeof(*dmi));
+ if (cp->submenu == 0)
+ errx(1, "malloc for submenu");
+ cp->nzones = 0;
+ TAILQ_FOREACH(zp, &cp->zones, link) {
+ cont = zp->continent;
+ dmi = &cp->submenu[cp->nzones];
+ memset(dmi, 0, sizeof(*dmi));
+ asprintf(&dmi->prompt, "%d", ++cp->nzones);
+ dmi->title = zp->descr;
+ dmi->fire = set_zone_multi;
+ dmi->data = zp;
+
+ for (zp2 = TAILQ_FIRST(&cp->zones);
+ zp2->continent != cont;
+ zp2 = TAILQ_NEXT(zp2, link))
+ ;
+ if (zp2 != zp)
+ continue;
+
+ dmi = &cont->menu[cont->nitems];
+ memset(dmi, 0, sizeof(*dmi));
+ asprintf(&dmi->prompt, "%d", ++cont->nitems);
+ dmi->title = cp->name;
+ dmi->fire = set_zone_menu;
+ dmi->data = cp;
+ }
+ }
+ }
+}
+
+static int
+set_zone_menu(dialogMenuItem *dmi)
+{
+ char title[64], prompt[64];
+ struct country *cp = dmi->data;
+ int menulen;
+ int rv;
+
+ snprintf(title, sizeof(title), "%s Time Zones", cp->name);
+ snprintf(prompt, sizeof(prompt),
+ "Select a zone which observes the same time as your locality.");
+ menulen = cp->nzones < 16 ? cp->nzones : 16;
+ rv = xdialog_menu(title, prompt, -1, -1, menulen, cp->nzones,
+ cp->submenu);
+ if (rv != 0)
+ return (DITEM_RECREATE);
+ return (DITEM_LEAVE_MENU);
+}
+
+int
+set_zone_utc(void)
+{
+ if (!confirm_zone(NULL))
+ return (DITEM_FAILURE | DITEM_RECREATE);
+
+ return (install_zoneinfo_file(NULL));
+}
+
+static int
+install_zoneinfo_file(const char *zoneinfo_file)
+{
+ char buf[1024];
+ char title[64], prompt[SILLY_BUFFER_SIZE];
+ struct stat sb;
+ ssize_t len;
+ int fd1, fd2, copymode;
+
+ if (lstat(path_localtime, &sb) < 0) {
+ /* Nothing there yet... */
+ copymode = 1;
+ } else if (S_ISLNK(sb.st_mode))
+ copymode = 0;
+ else
+ copymode = 1;
+
+#ifdef VERBOSE
+ snprintf(title, sizeof(title), "Info");
+ if (zoneinfo_file == NULL)
+ snprintf(prompt, sizeof(prompt),
+ "Removing %s", path_localtime);
+ else if (copymode)
+ snprintf(prompt, sizeof(prompt),
+ "Copying %s to %s", zoneinfo_file, path_localtime);
+ else
+ snprintf(prompt, sizeof(prompt),
+ "Creating symbolic link %s to %s",
+ path_localtime, zoneinfo_file);
+ if (usedialog)
+ dialog_msgbox(title, prompt, 8, 72, 1);
+ else
+ fprintf(stderr, "%s\n", prompt);
+#endif
+
+ if (reallydoit) {
+ if (zoneinfo_file == NULL) {
+ if (unlink(path_localtime) < 0 && errno != ENOENT) {
+ snprintf(title, sizeof(title), "Error");
+ snprintf(prompt, sizeof(prompt),
+ "Could not delete %s: %s", path_localtime,
+ strerror(errno));
+ if (usedialog)
+ dialog_msgbox(title, prompt, 8, 72, 1);
+ else
+ fprintf(stderr, "%s\n", prompt);
+
+ return (DITEM_FAILURE | DITEM_RECREATE);
+ }
+ if (unlink(path_db) < 0 && errno != ENOENT) {
+ snprintf(title, sizeof(title), "Error");
+ snprintf(prompt, sizeof(prompt),
+ "Could not delete %s: %s", path_db,
+ strerror(errno));
+ if (usedialog)
+ dialog_msgbox(title, prompt, 8, 72, 1);
+ else
+ fprintf(stderr, "%s\n", prompt);
+
+ return (DITEM_FAILURE | DITEM_RECREATE);
+ }
+#ifdef VERBOSE
+ snprintf(title, sizeof(title), "Done");
+ snprintf(prompt, sizeof(prompt),
+ "Removed %s", path_localtime);
+ if (usedialog)
+ dialog_msgbox(title, prompt, 8, 72, 1);
+ else
+ fprintf(stderr, "%s\n", prompt);
+#endif
+ return (DITEM_LEAVE_MENU);
+ }
+
+ if (copymode) {
+ fd1 = open(zoneinfo_file, O_RDONLY, 0);
+ if (fd1 < 0) {
+ snprintf(title, sizeof(title), "Error");
+ snprintf(prompt, sizeof(prompt),
+ "Could not open %s: %s", zoneinfo_file,
+ strerror(errno));
+ if (usedialog)
+ dialog_msgbox(title, prompt, 8, 72, 1);
+ else
+ fprintf(stderr, "%s\n", prompt);
+ return (DITEM_FAILURE | DITEM_RECREATE);
+ }
+
+ if (unlink(path_localtime) < 0 && errno != ENOENT) {
+ snprintf(prompt, sizeof(prompt),
+ "Could not unlink %s: %s",
+ path_localtime, strerror(errno));
+ if (usedialog) {
+ snprintf(title, sizeof(title), "Error");
+ dialog_msgbox(title, prompt, 8, 72, 1);
+ } else
+ fprintf(stderr, "%s\n", prompt);
+ return (DITEM_FAILURE | DITEM_RECREATE);
+ }
+
+ fd2 = open(path_localtime, O_CREAT | O_EXCL | O_WRONLY,
+ S_IRUSR | S_IRGRP | S_IROTH);
+ if (fd2 < 0) {
+ snprintf(title, sizeof(title), "Error");
+ snprintf(prompt, sizeof(prompt),
+ "Could not open %s: %s",
+ path_localtime, strerror(errno));
+ if (usedialog)
+ dialog_msgbox(title, prompt, 8, 72, 1);
+ else
+ fprintf(stderr, "%s\n", prompt);
+ return (DITEM_FAILURE | DITEM_RECREATE);
+ }
+
+ while ((len = read(fd1, buf, sizeof(buf))) > 0)
+ if ((len = write(fd2, buf, len)) < 0)
+ break;
+
+ if (len == -1) {
+ snprintf(title, sizeof(title), "Error");
+ snprintf(prompt, sizeof(prompt),
+ "Error copying %s to %s %s", zoneinfo_file,
+ path_localtime, strerror(errno));
+ if (usedialog)
+ dialog_msgbox(title, prompt, 8, 72, 1);
+ else
+ fprintf(stderr, "%s\n", prompt);
+ /* Better to leave none than a corrupt one. */
+ unlink(path_localtime);
+ return (DITEM_FAILURE | DITEM_RECREATE);
+ }
+ close(fd1);
+ close(fd2);
+ } else {
+ if (access(zoneinfo_file, R_OK) != 0) {
+ snprintf(title, sizeof(title), "Error");
+ snprintf(prompt, sizeof(prompt),
+ "Cannot access %s: %s", zoneinfo_file,
+ strerror(errno));
+ if (usedialog)
+ dialog_msgbox(title, prompt, 8, 72, 1);
+ else
+ fprintf(stderr, "%s\n", prompt);
+ return (DITEM_FAILURE | DITEM_RECREATE);
+ }
+ if (unlink(path_localtime) < 0 && errno != ENOENT) {
+ snprintf(prompt, sizeof(prompt),
+ "Could not unlink %s: %s",
+ path_localtime, strerror(errno));
+ if (usedialog) {
+ snprintf(title, sizeof(title), "Error");
+ dialog_msgbox(title, prompt, 8, 72, 1);
+ } else
+ fprintf(stderr, "%s\n", prompt);
+ return (DITEM_FAILURE | DITEM_RECREATE);
+ }
+ if (symlink(zoneinfo_file, path_localtime) < 0) {
+ snprintf(title, sizeof(title), "Error");
+ snprintf(prompt, sizeof(prompt),
+ "Cannot create symbolic link %s to %s: %s",
+ path_localtime, zoneinfo_file,
+ strerror(errno));
+ if (usedialog)
+ dialog_msgbox(title, prompt, 8, 72, 1);
+ else
+ fprintf(stderr, "%s\n", prompt);
+ return (DITEM_FAILURE | DITEM_RECREATE);
+ }
+ }
+
+#ifdef VERBOSE
+ snprintf(title, sizeof(title), "Done");
+ if (copymode)
+ snprintf(prompt, sizeof(prompt),
+ "Copied timezone file from %s to %s",
+ zoneinfo_file, path_localtime);
+ else
+ snprintf(prompt, sizeof(prompt),
+ "Created symbolic link from %s to %s",
+ zoneinfo_file, path_localtime);
+ if (usedialog)
+ dialog_msgbox(title, prompt, 8, 72, 1);
+ else
+ fprintf(stderr, "%s\n", prompt);
+#endif
+ } /* reallydoit */
+
+ return (DITEM_LEAVE_MENU);
+}
+
+static int
+install_zoneinfo(const char *zoneinfo)
+{
+ int rv;
+ FILE *f;
+ char path_zoneinfo_file[MAXPATHLEN];
+
+ sprintf(path_zoneinfo_file, "%s/%s", path_zoneinfo, zoneinfo);
+ rv = install_zoneinfo_file(path_zoneinfo_file);
+
+ /* Save knowledge for later */
+ if (reallydoit && (rv & DITEM_FAILURE) == 0) {
+ if ((f = fopen(path_db, "w")) != NULL) {
+ fprintf(f, "%s\n", zoneinfo);
+ fclose(f);
+ }
+ }
+
+ return (rv);
+}
+
+static int
+confirm_zone(const char *filename)
+{
+ char title[64], prompt[64];
+ time_t t = time(0);
+ struct tm *tm;
+ int rv;
+
+ setenv("TZ", filename == NULL ? "" : filename, 1);
+ tzset();
+ tm = localtime(&t);
+
+ snprintf(title, sizeof(title), "Confirmation");
+ snprintf(prompt, sizeof(prompt),
+ "Does the abbreviation `%s' look reasonable?", tm->tm_zone);
+ rv = !dialog_yesno(title, prompt, 5, 72);
+ return (rv);
+}
+
+static int
+set_zone_multi(dialogMenuItem *dmi)
+{
+ struct zone *zp = dmi->data;
+ int rv;
+
+ if (!confirm_zone(zp->filename))
+ return (DITEM_FAILURE | DITEM_RECREATE);
+
+ rv = install_zoneinfo(zp->filename);
+ return (rv);
+}
+
+static int
+set_zone_whole_country(dialogMenuItem *dmi)
+{
+ struct country *cp = dmi->data;
+ int rv;
+
+ if (!confirm_zone(cp->filename))
+ return (DITEM_FAILURE | DITEM_RECREATE);
+
+ rv = install_zoneinfo(cp->filename);
+ return (rv);
+}
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: tzsetup [-nrs] [-C chroot_directory]"
+ " [zoneinfo_file | zoneinfo_name]\n");
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ char title[64], prompt[128];
+ int c, fd, rv, skiputc;
+ char vm_guest[16] = "";
+ size_t len = sizeof(vm_guest);
+
+ skiputc = 0;
+
+ /* Default skiputc to 1 for VM guests */
+ if (sysctlbyname("kern.vm_guest", vm_guest, &len, NULL, 0) == 0 &&
+ strcmp(vm_guest, "none") != 0)
+ skiputc = 1;
+
+ while ((c = getopt(argc, argv, "C:nrs")) != -1) {
+ switch(c) {
+ case 'C':
+ chrootenv = optarg;
+ break;
+ case 'n':
+ reallydoit = 0;
+ break;
+ case 'r':
+ reinstall = 1;
+ usedialog = 0;
+ break;
+ case 's':
+ skiputc = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ if (argc - optind > 1)
+ usage();
+
+ if (chrootenv == NULL) {
+ strcpy(path_zonetab, _PATH_ZONETAB);
+ strcpy(path_iso3166, _PATH_ISO3166);
+ strcpy(path_zoneinfo, _PATH_ZONEINFO);
+ strcpy(path_localtime, _PATH_LOCALTIME);
+ strcpy(path_db, _PATH_DB);
+ strcpy(path_wall_cmos_clock, _PATH_WALL_CMOS_CLOCK);
+ } else {
+ sprintf(path_zonetab, "%s/%s", chrootenv, _PATH_ZONETAB);
+ sprintf(path_iso3166, "%s/%s", chrootenv, _PATH_ISO3166);
+ sprintf(path_zoneinfo, "%s/%s", chrootenv, _PATH_ZONEINFO);
+ sprintf(path_localtime, "%s/%s", chrootenv, _PATH_LOCALTIME);
+ sprintf(path_db, "%s/%s", chrootenv, _PATH_DB);
+ sprintf(path_wall_cmos_clock, "%s/%s", chrootenv,
+ _PATH_WALL_CMOS_CLOCK);
+ }
+
+
+ /* Override the user-supplied umask. */
+ (void)umask(S_IWGRP | S_IWOTH);
+
+ if (reinstall == 1) {
+ FILE *f;
+ char zoneinfo[MAXPATHLEN];
+
+ if ((f = fopen(path_db, "r")) != NULL) {
+ if (fgets(zoneinfo, sizeof(zoneinfo), f) != NULL) {
+ zoneinfo[sizeof(zoneinfo) - 1] = 0;
+ if (strlen(zoneinfo) > 0) {
+ zoneinfo[strlen(zoneinfo) - 1] = 0;
+ rv = install_zoneinfo(zoneinfo);
+ exit(rv & ~DITEM_LEAVE_MENU);
+ }
+ errx(1, "Error reading %s.\n", path_db);
+ }
+ fclose(f);
+ errx(1,
+ "Unable to determine earlier installed zoneinfo "
+ "name. Check %s", path_db);
+ }
+ errx(1, "Cannot open %s for reading. Does it exist?", path_db);
+ }
+
+ /*
+ * If the arguments on the command-line do not specify a file,
+ * then interpret it as a zoneinfo name
+ */
+ if (optind == argc - 1) {
+ struct stat sb;
+
+ if (stat(argv[optind], &sb) != 0) {
+ usedialog = 0;
+ rv = install_zoneinfo(argv[optind]);
+ exit(rv & ~DITEM_LEAVE_MENU);
+ }
+ /* FALLTHROUGH */
+ }
+
+ read_iso3166_table();
+ read_zones();
+ sort_countries();
+ make_menus();
+
+ init_dialog(stdin, stdout);
+ if (skiputc == 0) {
+ DIALOG_VARS save_vars;
+ int yesno;
+
+ snprintf(title, sizeof(title),
+ "Select local or UTC (Greenwich Mean Time) clock");
+ snprintf(prompt, sizeof(prompt),
+ "Is this machine's CMOS clock set to UTC? "
+ "If it is set to local time,\n"
+ "or you don't know, please choose NO here!");
+ dlg_save_vars(&save_vars);
+#if !defined(__sparc64__)
+ dialog_vars.defaultno = TRUE;
+#endif
+ yesno = dialog_yesno(title, prompt, 7, 73);
+ dlg_restore_vars(&save_vars);
+ if (!yesno) {
+ if (reallydoit)
+ unlink(path_wall_cmos_clock);
+ } else {
+ if (reallydoit) {
+ fd = open(path_wall_cmos_clock,
+ O_WRONLY | O_CREAT | O_TRUNC,
+ S_IRUSR | S_IRGRP | S_IROTH);
+ if (fd < 0) {
+ end_dialog();
+ err(1, "create %s",
+ path_wall_cmos_clock);
+ }
+ close(fd);
+ }
+ }
+ dlg_clear();
+ }
+ if (optind == argc - 1) {
+ snprintf(title, sizeof(title), "Default timezone provided");
+ snprintf(prompt, sizeof(prompt),
+ "\nUse the default `%s' zone?", argv[optind]);
+ if (!dialog_yesno(title, prompt, 7, 72)) {
+ rv = install_zoneinfo_file(argv[optind]);
+ dlg_clear();
+ end_dialog();
+ exit(rv & ~DITEM_LEAVE_MENU);
+ }
+ dlg_clear();
+ }
+ snprintf(title, sizeof(title), "Time Zone Selector");
+ snprintf(prompt, sizeof(prompt), "Select a region");
+ xdialog_menu(title, prompt, -1, -1, NCONTINENTS, NCONTINENTS,
+ continents);
+
+ dlg_clear();
+ end_dialog();
+ return (0);
+}
diff --git a/usr.sbin/uathload/Makefile b/usr.sbin/uathload/Makefile
new file mode 100644
index 0000000..949f800
--- /dev/null
+++ b/usr.sbin/uathload/Makefile
@@ -0,0 +1,30 @@
+# $FreeBSD$
+
+PROG= uathload
+MAN= uathload.8
+
+SRCS= uathload.c ar5523.bin
+
+CLEANFILES= ar5523.bin
+
+# It's hard to tag ar5523.o with the proper gnu note saying that it has a
+# non-executable stack, so ld doesn't properly mark his executable as
+# not having an executable stack. Mark it explicitly, but only for those
+# platforms that support his feature (otherwise signals don't work).
+# Note: Newer versions of ld than is in the tree ignore -z.
+.if ${MACHINE_ARCH} == "i386" || ${MACHINE_ARCH} == "amd64" || ${MACHINE_ARCH} == "powerpc" || ${MACHINE_ARCH} == "powerpc64"
+LDFLAGS+= -Wl,-z,noexecstack
+.endif
+
+# The conversion from .bin to .o doesn't always produce a pedantically correct
+# .o's. And it doesn't matter, so turn off the mismatch warnings since it is
+# pure data. On mips64 here's no easy way to produce a proper .o.
+LDFLAGS+= -Wl,--no-warn-mismatch
+
+ar5523.bin: ${.CURDIR}/../../sys/contrib/dev/uath/ar5523.bin.uu
+ uudecode -p ${.CURDIR}/../../sys/contrib/dev/uath/ar5523.bin.uu > ${.TARGET}
+
+ar5523.o: ar5523.bin
+ ${LD} -b binary -d -warn-common -r -d -o ${.TARGET} ar5523.bin
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/uathload/Makefile.depend b/usr.sbin/uathload/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/uathload/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/uathload/uathload.8 b/usr.sbin/uathload/uathload.8
new file mode 100644
index 0000000..49c2612
--- /dev/null
+++ b/usr.sbin/uathload/uathload.8
@@ -0,0 +1,69 @@
+.\"
+.\" Copyright (c) 2009 Weongyo Jeong.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 May 11, 2010
+.Dt UATHLOAD 8
+.Os
+.Sh NAME
+.Nm uathload
+.Nd "firmware loader for Atheros USB wireless driver"
+.Sh SYNOPSIS
+.Nm
+.Op Fl v
+.Fl d Ar ugen-device
+.Op Ar firmware-file
+.Sh DESCRIPTION
+The
+.Nm
+utility provides a way to load the firmware for Atheros USB wireless
+devices with AR5005UG and AR5005UX chipsets.
+.Pp
+The following options are accepted.
+.Bl -tag -width ".Fl f Ar file"
+.It Fl d Ar ugen-device
+Use a specific
+.Xr ugen 4
+device.
+.It Fl v
+Enable debugging messages.
+.El
+.Pp
+If
+.Ar firmware-file
+is specified
+.Nm
+tries to load it instead of the built-in firmware image.
+.Sh EXAMPLES
+Load the built-in firmware image into a specific
+.Xr ugen 4
+device:
+.Pp
+.Dl "uathload -d /dev/ugen0.2"
+.Sh SEE ALSO
+.Xr uath 4
+.Sh AUTHORS
+.An Weongyo Jeong
diff --git a/usr.sbin/uathload/uathload.c b/usr.sbin/uathload/uathload.c
new file mode 100644
index 0000000..64ae661
--- /dev/null
+++ b/usr.sbin/uathload/uathload.c
@@ -0,0 +1,233 @@
+/*-
+ * Copyright (c) 2006 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,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * 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 NONINFRINGEMENT, 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.
+ *
+ * $FreeBSD$
+ */
+
+/*
+ * Atheros AR5523 USB Station Firmware downloader.
+ *
+ * uathload -d ugen-device [firmware-file]
+ *
+ * Intended to be called from devd on device discovery.
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/endian.h>
+#include <sys/mman.h>
+
+#include <sys/ioctl.h>
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_ioctl.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <paths.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+/* all fields are big endian */
+struct uath_fwmsg {
+ uint32_t flags;
+#define UATH_WRITE_BLOCK (1 << 4)
+
+ uint32_t len;
+#define UATH_MAX_FWBLOCK_SIZE 2048
+
+ uint32_t total;
+ uint32_t remain;
+ uint32_t rxtotal;
+ uint32_t pad[123];
+} __packed;
+
+#define UATH_DATA_TIMEOUT 10000
+#define UATH_CMD_TIMEOUT 1000
+
+#define VERBOSE(_fmt, ...) do { \
+ if (verbose) { \
+ printf(_fmt, __VA_ARGS__); \
+ fflush(stdout); \
+ } \
+} while (0)
+
+extern uint8_t _binary_ar5523_bin_start;
+extern uint8_t _binary_ar5523_bin_end;
+
+static int
+getdevname(const char *devname, char *msgdev, char *datadev)
+{
+ char *bn, *dn;
+
+ dn = dirname(devname);
+ if (dn == NULL)
+ return (-1);
+ bn = basename(devname);
+ if (bn == NULL || strncmp(bn, "ugen", 4))
+ return (-1);
+ bn += 4;
+
+ /* NB: pipes are hardcoded */
+ snprintf(msgdev, 256, "%s/usb/%s.1", dn, bn);
+ snprintf(datadev, 256, "%s/usb/%s.2", dn, bn);
+ return (0);
+}
+
+static void
+usage(void)
+{
+ errx(-1, "usage: uathload [-v] -d devname [firmware]");
+}
+
+int
+main(int argc, char *argv[])
+{
+ const char *fwname, *devname;
+ char msgdev[256], datadev[256];
+ struct uath_fwmsg txmsg, rxmsg;
+ char *txdata;
+ struct stat sb;
+ int msg, data, fw, timeout, b, c;
+ int bufsize = 512, verbose = 0;
+ ssize_t len;
+
+ devname = NULL;
+ while ((c = getopt(argc, argv, "d:v")) != -1) {
+ switch (c) {
+ case 'd':
+ devname = optarg;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ default:
+ usage();
+ /*NOTREACHED*/
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (devname == NULL)
+ errx(-1, "No device name; use -d to specify the ugen device");
+ if (argc > 1)
+ usage();
+
+ if (argc == 1) {
+ fwname = argv[0];
+ fw = open(fwname, O_RDONLY, 0);
+ if (fw < 0)
+ err(-1, "open(%s)", fwname);
+ if (fstat(fw, &sb) < 0)
+ err(-1, "fstat(%s)", fwname);
+ txdata = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fw, 0);
+ if (txdata == MAP_FAILED)
+ err(-1, "mmap(%s)", fwname);
+ len = sb.st_size;
+ } else {
+ fwname = "ar5523.bin (builtin)";
+ fw = -1;
+ txdata = &_binary_ar5523_bin_start;
+ len = &_binary_ar5523_bin_end - &_binary_ar5523_bin_start;
+ }
+ /* XXX verify device is an AR5005 part */
+ if (getdevname(devname, msgdev, datadev))
+ err(-1, "getdevname error");
+
+ msg = open(msgdev, O_RDWR, 0);
+ if (msg < 0)
+ err(-1, "open(%s)", msgdev);
+ timeout = UATH_DATA_TIMEOUT;
+ if (ioctl(msg, USB_SET_RX_TIMEOUT, &timeout) < 0)
+ err(-1, "%s: USB_SET_RX_TIMEOUT(%u)", msgdev, UATH_DATA_TIMEOUT);
+ if (ioctl(msg, USB_SET_RX_BUFFER_SIZE, &bufsize) < 0)
+ err(-1, "%s: USB_SET_RX_BUFFER_SIZE(%u)", msgdev, bufsize);
+
+ data = open(datadev, O_WRONLY, 0);
+ if (data < 0)
+ err(-1, "open(%s)", datadev);
+ timeout = UATH_DATA_TIMEOUT;
+ if (ioctl(data, USB_SET_TX_TIMEOUT, &timeout) < 0)
+ err(-1, "%s: USB_SET_TX_TIMEOUT(%u)", datadev,
+ UATH_DATA_TIMEOUT);
+
+ VERBOSE("Load firmware %s to %s\n", fwname, devname);
+
+ bzero(&txmsg, sizeof (struct uath_fwmsg));
+ txmsg.flags = htobe32(UATH_WRITE_BLOCK);
+ txmsg.total = htobe32(len);
+
+ b = 0;
+ while (len > 0) {
+ int mlen;
+
+ mlen = len;
+ if (mlen > UATH_MAX_FWBLOCK_SIZE)
+ mlen = UATH_MAX_FWBLOCK_SIZE;
+ txmsg.remain = htobe32(len - mlen);
+ txmsg.len = htobe32(mlen);
+
+ /* send firmware block meta-data */
+ VERBOSE("send block %2u: %zd bytes remaining", b, len - mlen);
+ if (write(msg, &txmsg, sizeof(txmsg)) != sizeof(txmsg)) {
+ VERBOSE("%s", "\n");
+ err(-1, "error sending msg (%s)", msgdev);
+ break;
+ }
+
+ /* send firmware block data */
+ VERBOSE("%s", "\n : data...");
+ if (write(data, txdata, mlen) != mlen) {
+ VERBOSE("%s", "\n");
+ err(-1, "error sending data (%s)", datadev);
+ break;
+ }
+
+ /* wait for ack from firmware */
+ VERBOSE("%s", "\n : wait for ack...");
+ bzero(&rxmsg, sizeof(rxmsg));
+ if (read(msg, &rxmsg, sizeof(rxmsg)) != sizeof(rxmsg)) {
+ VERBOSE("%s", "\n");
+ err(-1, "error reading msg (%s)", msgdev);
+ break;
+ }
+
+ VERBOSE("flags=0x%x total=%d\n",
+ be32toh(rxmsg.flags), be32toh(rxmsg.rxtotal));
+ len -= mlen;
+ txdata += mlen;
+ b++;
+ }
+ sleep(1);
+ close(fw);
+ close(msg);
+ close(data);
+ return 0;
+}
diff --git a/usr.sbin/uefisign/Makefile b/usr.sbin/uefisign/Makefile
new file mode 100644
index 0000000..3fdebc2
--- /dev/null
+++ b/usr.sbin/uefisign/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+PROG= uefisign
+SRCS= uefisign.c child.c pe.c
+MAN= uefisign.8
+
+LIBADD= crypto
+
+WARNS= 6
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/uefisign/Makefile.depend b/usr.sbin/uefisign/Makefile.depend
new file mode 100644
index 0000000..fc0b633
--- /dev/null
+++ b/usr.sbin/uefisign/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ secure/lib/libcrypto \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/uefisign/child.c b/usr.sbin/uefisign/child.c
new file mode 100644
index 0000000..7dfc211
--- /dev/null
+++ b/usr.sbin/uefisign/child.c
@@ -0,0 +1,277 @@
+/*-
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#if __FreeBSD_version >= 1100000
+#include <sys/capsicum.h>
+#else
+#include <sys/capability.h>
+#endif
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <openssl/evp.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+
+#include "uefisign.h"
+
+static void
+load(struct executable *x)
+{
+ int error, fd;
+ struct stat sb;
+ char *buf;
+ size_t nread, len;
+
+ fd = fileno(x->x_fp);
+
+ error = fstat(fd, &sb);
+ if (error != 0)
+ err(1, "%s: fstat", x->x_path);
+
+ len = sb.st_size;
+ if (len <= 0)
+ errx(1, "%s: file is empty", x->x_path);
+
+ buf = malloc(len);
+ if (buf == NULL)
+ err(1, "%s: cannot malloc %zd bytes", x->x_path, len);
+
+ nread = fread(buf, len, 1, x->x_fp);
+ if (nread != 1)
+ err(1, "%s: fread", x->x_path);
+
+ x->x_buf = buf;
+ x->x_len = len;
+}
+
+static void
+digest_range(struct executable *x, EVP_MD_CTX *mdctx, off_t off, size_t len)
+{
+ int ok;
+
+ range_check(x, off, len, "chunk");
+
+ ok = EVP_DigestUpdate(mdctx, x->x_buf + off, len);
+ if (ok == 0) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "EVP_DigestUpdate(3) failed");
+ }
+}
+
+static void
+digest(struct executable *x)
+{
+ EVP_MD_CTX *mdctx;
+ const EVP_MD *md;
+ size_t sum_of_bytes_hashed;
+ int i, ok;
+
+ /*
+ * Windows Authenticode Portable Executable Signature Format
+ * spec version 1.0 specifies MD5 and SHA1. However, pesign
+ * and sbsign both use SHA256, so do the same.
+ */
+ md = EVP_get_digestbyname(DIGEST);
+ if (md == NULL) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "EVP_get_digestbyname(\"%s\") failed", DIGEST);
+ }
+
+ mdctx = EVP_MD_CTX_create();
+ if (mdctx == NULL) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "EVP_MD_CTX_create(3) failed");
+ }
+
+ ok = EVP_DigestInit_ex(mdctx, md, NULL);
+ if (ok == 0) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "EVP_DigestInit_ex(3) failed");
+ }
+
+ /*
+ * According to the Authenticode spec, we need to compute
+ * the digest in a rather... specific manner; see "Calculating
+ * the PE Image Hash" part of the spec for details.
+ *
+ * First, everything from 0 to before the PE checksum.
+ */
+ digest_range(x, mdctx, 0, x->x_checksum_off);
+
+ /*
+ * Second, from after the PE checksum to before the Certificate
+ * entry in Data Directory.
+ */
+ digest_range(x, mdctx, x->x_checksum_off + x->x_checksum_len,
+ x->x_certificate_entry_off -
+ (x->x_checksum_off + x->x_checksum_len));
+
+ /*
+ * Then, from after the Certificate entry to the end of headers.
+ */
+ digest_range(x, mdctx,
+ x->x_certificate_entry_off + x->x_certificate_entry_len,
+ x->x_headers_len -
+ (x->x_certificate_entry_off + x->x_certificate_entry_len));
+
+ /*
+ * Then, each section in turn, as specified in the PE Section Table.
+ *
+ * XXX: Sorting.
+ */
+ sum_of_bytes_hashed = x->x_headers_len;
+ for (i = 0; i < x->x_nsections; i++) {
+ digest_range(x, mdctx,
+ x->x_section_off[i], x->x_section_len[i]);
+ sum_of_bytes_hashed += x->x_section_len[i];
+ }
+
+ /*
+ * I believe this can happen with overlapping sections.
+ */
+ if (sum_of_bytes_hashed > x->x_len)
+ errx(1, "number of bytes hashed is larger than file size");
+
+ /*
+ * I can't really explain this one; just do what the spec says.
+ */
+ if (sum_of_bytes_hashed < x->x_len) {
+ digest_range(x, mdctx, sum_of_bytes_hashed,
+ x->x_len - (signature_size(x) + sum_of_bytes_hashed));
+ }
+
+ ok = EVP_DigestFinal_ex(mdctx, x->x_digest, &x->x_digest_len);
+ if (ok == 0) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "EVP_DigestFinal_ex(3) failed");
+ }
+
+ EVP_MD_CTX_destroy(mdctx);
+}
+
+static void
+show_digest(const struct executable *x)
+{
+ int i;
+
+ printf("computed %s digest ", DIGEST);
+ for (i = 0; i < (int)x->x_digest_len; i++)
+ printf("%02x", (unsigned char)x->x_digest[i]);
+ printf("; digest len %u\n", x->x_digest_len);
+}
+
+static void
+send_digest(const struct executable *x, int pipefd)
+{
+
+ send_chunk(x->x_digest, x->x_digest_len, pipefd);
+}
+
+static void
+receive_signature(struct executable *x, int pipefd)
+{
+
+ receive_chunk(&x->x_signature, &x->x_signature_len, pipefd);
+}
+
+static void
+save(struct executable *x, FILE *fp, const char *path)
+{
+ size_t nwritten;
+
+ assert(fp != NULL);
+ assert(path != NULL);
+
+ nwritten = fwrite(x->x_buf, x->x_len, 1, fp);
+ if (nwritten != 1)
+ err(1, "%s: fwrite", path);
+}
+
+int
+child(const char *inpath, const char *outpath, int pipefd,
+ bool Vflag, bool vflag)
+{
+ int error;
+ FILE *outfp = NULL, *infp = NULL;
+ struct executable *x;
+
+ infp = checked_fopen(inpath, "r");
+ if (outpath != NULL)
+ outfp = checked_fopen(outpath, "w");
+
+ error = cap_enter();
+ if (error != 0 && errno != ENOSYS)
+ err(1, "cap_enter");
+
+ x = calloc(1, sizeof(*x));
+ if (x == NULL)
+ err(1, "calloc");
+ x->x_path = inpath;
+ x->x_fp = infp;
+
+ load(x);
+ parse(x);
+ if (Vflag) {
+ if (signature_size(x) == 0)
+ errx(1, "file not signed");
+
+ printf("file contains signature\n");
+ if (vflag) {
+ digest(x);
+ show_digest(x);
+ show_certificate(x);
+ }
+ } else {
+ if (signature_size(x) != 0)
+ errx(1, "file already signed");
+
+ digest(x);
+ if (vflag)
+ show_digest(x);
+ send_digest(x, pipefd);
+ receive_signature(x, pipefd);
+ update(x);
+ save(x, outfp, outpath);
+ }
+
+ return (0);
+}
diff --git a/usr.sbin/uefisign/magic.h b/usr.sbin/uefisign/magic.h
new file mode 100644
index 0000000..85f2c55
--- /dev/null
+++ b/usr.sbin/uefisign/magic.h
@@ -0,0 +1,66 @@
+/*-
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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$
+ *
+ */
+
+/*
+ * This file contains Authenticode-specific ASN.1 "configuration", used,
+ * after being processed by asprintf(3), as an input to ASN1_generate_nconf(3).
+ */
+static const char *magic_fmt =
+"asn1 = SEQUENCE:SpcIndirectDataContent\n"
+"\n"
+"[SpcIndirectDataContent]\n"
+"a = SEQUENCE:SpcAttributeTypeAndOptionalValue\n"
+"b = SEQUENCE:DigestInfo\n"
+"\n"
+"[SpcAttributeTypeAndOptionalValue]\n"
+"# SPC_PE_IMAGE_DATAOBJ\n"
+"a = OID:1.3.6.1.4.1.311.2.1.15\n"
+"b = SEQUENCE:SpcPeImageData\n"
+"\n"
+"[SpcPeImageData]\n"
+"a = FORMAT:HEX,BITSTRING:00\n"
+/*
+ * Well, there should be some other struct here, "SPCLink", but it doesn't
+ * appear to be necessary for UEFI, and I have no idea how to synthesize it,
+ * as it uses the CHOICE type.
+ */
+"\n"
+"[DigestInfo]\n"
+"a = SEQUENCE:AlgorithmIdentifier\n"
+/*
+ * Here goes the digest computed from PE headers and sections.
+ */
+"b = FORMAT:HEX,OCTETSTRING:%s\n"
+"\n"
+"[AlgorithmIdentifier]\n"
+"a = OBJECT:sha256\n"
+"b = NULL\n";
diff --git a/usr.sbin/uefisign/pe.c b/usr.sbin/uefisign/pe.c
new file mode 100644
index 0000000..768ba5a
--- /dev/null
+++ b/usr.sbin/uefisign/pe.c
@@ -0,0 +1,564 @@
+/*-
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+/*
+ * PE format reference:
+ * http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "uefisign.h"
+
+#ifndef CTASSERT
+#define CTASSERT(x) _CTASSERT(x, __LINE__)
+#define _CTASSERT(x, y) __CTASSERT(x, y)
+#define __CTASSERT(x, y) typedef char __assert_ ## y [(x) ? 1 : -1]
+#endif
+
+struct mz_header {
+ uint8_t mz_signature[2];
+ uint8_t mz_dont_care[58];
+ uint16_t mz_lfanew;
+} __attribute__((packed));
+
+struct coff_header {
+ uint8_t coff_dont_care[2];
+ uint16_t coff_number_of_sections;
+ uint8_t coff_dont_care_either[16];
+} __attribute__((packed));
+
+#define PE_SIGNATURE 0x00004550
+
+struct pe_header {
+ uint32_t pe_signature;
+ struct coff_header pe_coff;
+} __attribute__((packed));
+
+#define PE_OPTIONAL_MAGIC_32 0x010B
+#define PE_OPTIONAL_MAGIC_32_PLUS 0x020B
+
+#define PE_OPTIONAL_SUBSYSTEM_EFI_APPLICATION 10
+#define PE_OPTIONAL_SUBSYSTEM_EFI_BOOT 11
+#define PE_OPTIONAL_SUBSYSTEM_EFI_RUNTIME 12
+
+struct pe_optional_header_32 {
+ uint16_t po_magic;
+ uint8_t po_dont_care[58];
+ uint32_t po_size_of_headers;
+ uint32_t po_checksum;
+ uint16_t po_subsystem;
+ uint8_t po_dont_care_either[22];
+ uint32_t po_number_of_rva_and_sizes;
+} __attribute__((packed));
+
+CTASSERT(offsetof(struct pe_optional_header_32, po_size_of_headers) == 60);
+CTASSERT(offsetof(struct pe_optional_header_32, po_checksum) == 64);
+CTASSERT(offsetof(struct pe_optional_header_32, po_subsystem) == 68);
+CTASSERT(offsetof(struct pe_optional_header_32, po_number_of_rva_and_sizes) == 92);
+
+struct pe_optional_header_32_plus {
+ uint16_t po_magic;
+ uint8_t po_dont_care[58];
+ uint32_t po_size_of_headers;
+ uint32_t po_checksum;
+ uint16_t po_subsystem;
+ uint8_t po_dont_care_either[38];
+ uint32_t po_number_of_rva_and_sizes;
+} __attribute__((packed));
+
+CTASSERT(offsetof(struct pe_optional_header_32_plus, po_size_of_headers) == 60);
+CTASSERT(offsetof(struct pe_optional_header_32_plus, po_checksum) == 64);
+CTASSERT(offsetof(struct pe_optional_header_32_plus, po_subsystem) == 68);
+CTASSERT(offsetof(struct pe_optional_header_32_plus, po_number_of_rva_and_sizes) == 108);
+
+#define PE_DIRECTORY_ENTRY_CERTIFICATE 4
+
+struct pe_directory_entry {
+ uint32_t pde_rva;
+ uint32_t pde_size;
+} __attribute__((packed));
+
+struct pe_section_header {
+ uint8_t psh_dont_care[16];
+ uint32_t psh_size_of_raw_data;
+ uint32_t psh_pointer_to_raw_data;
+ uint8_t psh_dont_care_either[16];
+} __attribute__((packed));
+
+CTASSERT(offsetof(struct pe_section_header, psh_size_of_raw_data) == 16);
+CTASSERT(offsetof(struct pe_section_header, psh_pointer_to_raw_data) == 20);
+
+#define PE_CERTIFICATE_REVISION 0x0200
+#define PE_CERTIFICATE_TYPE 0x0002
+
+struct pe_certificate {
+ uint32_t pc_len;
+ uint16_t pc_revision;
+ uint16_t pc_type;
+ char pc_signature[0];
+} __attribute__((packed));
+
+void
+range_check(const struct executable *x, off_t off, size_t len,
+ const char *name)
+{
+
+ if (off < 0) {
+ errx(1, "%s starts at negative offset %jd",
+ name, (intmax_t)off);
+ }
+ if (off >= (off_t)x->x_len) {
+ errx(1, "%s starts at %jd, past the end of executable at %zd",
+ name, (intmax_t)off, x->x_len);
+ }
+ if (len >= x->x_len) {
+ errx(1, "%s size %zd is larger than the executable size %zd",
+ name, len, x->x_len);
+ }
+ if (off + len > x->x_len) {
+ errx(1, "%s extends to %jd, past the end of executable at %zd",
+ name, (intmax_t)(off + len), x->x_len);
+ }
+}
+
+size_t
+signature_size(const struct executable *x)
+{
+ const struct pe_directory_entry *pde;
+
+ range_check(x, x->x_certificate_entry_off,
+ x->x_certificate_entry_len, "Certificate Directory");
+
+ pde = (struct pe_directory_entry *)
+ (x->x_buf + x->x_certificate_entry_off);
+
+ if (pde->pde_rva != 0 && pde->pde_size == 0)
+ warnx("signature size is 0, but its RVA is %d", pde->pde_rva);
+ if (pde->pde_rva == 0 && pde->pde_size != 0)
+ warnx("signature RVA is 0, but its size is %d", pde->pde_size);
+
+ return (pde->pde_size);
+}
+
+void
+show_certificate(const struct executable *x)
+{
+ struct pe_certificate *pc;
+ const struct pe_directory_entry *pde;
+
+ range_check(x, x->x_certificate_entry_off,
+ x->x_certificate_entry_len, "Certificate Directory");
+
+ pde = (struct pe_directory_entry *)
+ (x->x_buf + x->x_certificate_entry_off);
+
+ if (signature_size(x) == 0) {
+ printf("file not signed\n");
+ return;
+ }
+
+#if 0
+ printf("certificate chunk at offset %zd, size %zd\n",
+ pde->pde_rva, pde->pde_size);
+#endif
+
+ range_check(x, pde->pde_rva, pde->pde_size, "Certificate chunk");
+
+ pc = (struct pe_certificate *)(x->x_buf + pde->pde_rva);
+ if (pc->pc_revision != PE_CERTIFICATE_REVISION) {
+ errx(1, "wrong certificate chunk revision, is %d, should be %d",
+ pc->pc_revision, PE_CERTIFICATE_REVISION);
+ }
+ if (pc->pc_type != PE_CERTIFICATE_TYPE) {
+ errx(1, "wrong certificate chunk type, is %d, should be %d",
+ pc->pc_type, PE_CERTIFICATE_TYPE);
+ }
+ printf("to dump PKCS7:\n "
+ "dd if='%s' bs=1 skip=%zd | openssl pkcs7 -inform DER -print\n",
+ x->x_path, pde->pde_rva + offsetof(struct pe_certificate, pc_signature));
+ printf("to dump raw ASN.1:\n "
+ "openssl asn1parse -i -inform DER -offset %zd -in '%s'\n",
+ pde->pde_rva + offsetof(struct pe_certificate, pc_signature), x->x_path);
+}
+
+static void
+parse_section_table(struct executable *x, off_t off, int number_of_sections)
+{
+ const struct pe_section_header *psh;
+ int i;
+
+ range_check(x, off, sizeof(*psh) * number_of_sections,
+ "section table");
+
+ if (x->x_headers_len <= off + sizeof(*psh) * number_of_sections)
+ errx(1, "section table outside of headers");
+
+ psh = (const struct pe_section_header *)(x->x_buf + off);
+
+ if (number_of_sections >= MAX_SECTIONS) {
+ errx(1, "too many sections: got %d, should be %d",
+ number_of_sections, MAX_SECTIONS);
+ }
+ x->x_nsections = number_of_sections;
+
+ for (i = 0; i < number_of_sections; i++) {
+ if (psh->psh_pointer_to_raw_data < x->x_headers_len)
+ errx(1, "section points inside the headers");
+
+ range_check(x, psh->psh_pointer_to_raw_data,
+ psh->psh_size_of_raw_data, "section");
+#if 0
+ printf("section %d: start %d, size %d\n",
+ i, psh->psh_pointer_to_raw_data, psh->psh_size_of_raw_data);
+#endif
+ x->x_section_off[i] = psh->psh_pointer_to_raw_data;
+ x->x_section_len[i] = psh->psh_size_of_raw_data;
+ psh++;
+ }
+}
+
+static void
+parse_directory(struct executable *x, off_t off,
+ int number_of_rva_and_sizes, int number_of_sections)
+{
+ //int i;
+ const struct pe_directory_entry *pde;
+
+ //printf("Data Directory at offset %zd\n", off);
+
+ if (number_of_rva_and_sizes <= PE_DIRECTORY_ENTRY_CERTIFICATE) {
+ errx(1, "wrong NumberOfRvaAndSizes %d; should be at least %d",
+ number_of_rva_and_sizes, PE_DIRECTORY_ENTRY_CERTIFICATE);
+ }
+
+ range_check(x, off, sizeof(*pde) * number_of_rva_and_sizes,
+ "PE Data Directory");
+ if (x->x_headers_len <= off + sizeof(*pde) * number_of_rva_and_sizes)
+ errx(1, "PE Data Directory outside of headers");
+
+ x->x_certificate_entry_off =
+ off + sizeof(*pde) * PE_DIRECTORY_ENTRY_CERTIFICATE;
+ x->x_certificate_entry_len = sizeof(*pde);
+#if 0
+ printf("certificate directory entry at offset %zd, len %zd\n",
+ x->x_certificate_entry_off, x->x_certificate_entry_len);
+
+ pde = (struct pe_directory_entry *)(x->x_buf + off);
+ for (i = 0; i < number_of_rva_and_sizes; i++) {
+ printf("rva %zd, size %zd\n", pde->pde_rva, pde->pde_size);
+ pde++;
+ }
+#endif
+
+ return (parse_section_table(x,
+ off + sizeof(*pde) * number_of_rva_and_sizes, number_of_sections));
+}
+
+/*
+ * The PE checksum algorithm is undocumented; this code is mostly based on
+ * http://forum.sysinternals.com/optional-header-checksum-calculation_topic24214.html
+ *
+ * "Sum the entire image file, excluding the CheckSum field in the optional
+ * header, as an array of USHORTs, allowing any carry above 16 bits to be added
+ * back onto the low 16 bits. Then add the file size to get a 32-bit value."
+ *
+ * Note that most software does not care about the checksum at all; perhaps
+ * we could just set it to 0 instead.
+ *
+ * XXX: Endianness?
+ */
+static uint32_t
+compute_checksum(const struct executable *x)
+{
+ uint32_t cksum = 0;
+ uint16_t tmp;
+ int i;
+
+ range_check(x, x->x_checksum_off, x->x_checksum_len, "PE checksum");
+
+ assert(x->x_checksum_off % 2 == 0);
+
+ for (i = 0; i + sizeof(tmp) < x->x_len; i += 2) {
+ /*
+ * Don't checksum the checksum. The +2 is because the checksum
+ * is 4 bytes, and here we're iterating over 2 byte chunks.
+ */
+ if (i == x->x_checksum_off || i == x->x_checksum_off + 2) {
+ tmp = 0;
+ } else {
+ assert(i + sizeof(tmp) <= x->x_len);
+ memcpy(&tmp, x->x_buf + i, sizeof(tmp));
+ }
+
+ cksum += tmp;
+ cksum += cksum >> 16;
+ cksum &= 0xffff;
+ }
+
+ cksum += cksum >> 16;
+ cksum &= 0xffff;
+
+ cksum += x->x_len;
+
+ return (cksum);
+}
+
+static void
+parse_optional_32_plus(struct executable *x, off_t off,
+ int number_of_sections)
+{
+#if 0
+ uint32_t computed_checksum;
+#endif
+ const struct pe_optional_header_32_plus *po;
+
+ range_check(x, off, sizeof(*po), "PE Optional Header");
+
+ po = (struct pe_optional_header_32_plus *)(x->x_buf + off);
+ switch (po->po_subsystem) {
+ case PE_OPTIONAL_SUBSYSTEM_EFI_APPLICATION:
+ case PE_OPTIONAL_SUBSYSTEM_EFI_BOOT:
+ case PE_OPTIONAL_SUBSYSTEM_EFI_RUNTIME:
+ break;
+ default:
+ errx(1, "wrong PE Optional Header subsystem 0x%x",
+ po->po_subsystem);
+ }
+
+#if 0
+ printf("subsystem %d, checksum 0x%x, %d data directories\n",
+ po->po_subsystem, po->po_checksum, po->po_number_of_rva_and_sizes);
+#endif
+
+ x->x_checksum_off = off +
+ offsetof(struct pe_optional_header_32_plus, po_checksum);
+ x->x_checksum_len = sizeof(po->po_checksum);
+#if 0
+ printf("checksum 0x%x at offset %zd, len %zd\n",
+ po->po_checksum, x->x_checksum_off, x->x_checksum_len);
+
+ computed_checksum = compute_checksum(x);
+ if (computed_checksum != po->po_checksum) {
+ warnx("invalid PE+ checksum; is 0x%x, should be 0x%x",
+ po->po_checksum, computed_checksum);
+ }
+#endif
+
+ if (x->x_len < x->x_headers_len)
+ errx(1, "invalid SizeOfHeaders %d", po->po_size_of_headers);
+ x->x_headers_len = po->po_size_of_headers;
+ //printf("Size of Headers: %d\n", po->po_size_of_headers);
+
+ return (parse_directory(x, off + sizeof(*po),
+ po->po_number_of_rva_and_sizes, number_of_sections));
+}
+
+static void
+parse_optional_32(struct executable *x, off_t off, int number_of_sections)
+{
+#if 0
+ uint32_t computed_checksum;
+#endif
+ const struct pe_optional_header_32 *po;
+
+ range_check(x, off, sizeof(*po), "PE Optional Header");
+
+ po = (struct pe_optional_header_32 *)(x->x_buf + off);
+ switch (po->po_subsystem) {
+ case PE_OPTIONAL_SUBSYSTEM_EFI_APPLICATION:
+ case PE_OPTIONAL_SUBSYSTEM_EFI_BOOT:
+ case PE_OPTIONAL_SUBSYSTEM_EFI_RUNTIME:
+ break;
+ default:
+ errx(1, "wrong PE Optional Header subsystem 0x%x",
+ po->po_subsystem);
+ }
+
+#if 0
+ printf("subsystem %d, checksum 0x%x, %d data directories\n",
+ po->po_subsystem, po->po_checksum, po->po_number_of_rva_and_sizes);
+#endif
+
+ x->x_checksum_off = off +
+ offsetof(struct pe_optional_header_32, po_checksum);
+ x->x_checksum_len = sizeof(po->po_checksum);
+#if 0
+ printf("checksum at offset %zd, len %zd\n",
+ x->x_checksum_off, x->x_checksum_len);
+
+ computed_checksum = compute_checksum(x);
+ if (computed_checksum != po->po_checksum) {
+ warnx("invalid PE checksum; is 0x%x, should be 0x%x",
+ po->po_checksum, computed_checksum);
+ }
+#endif
+
+ if (x->x_len < x->x_headers_len)
+ errx(1, "invalid SizeOfHeaders %d", po->po_size_of_headers);
+ x->x_headers_len = po->po_size_of_headers;
+ //printf("Size of Headers: %d\n", po->po_size_of_headers);
+
+ return (parse_directory(x, off + sizeof(*po),
+ po->po_number_of_rva_and_sizes, number_of_sections));
+}
+
+static void
+parse_optional(struct executable *x, off_t off, int number_of_sections)
+{
+ const struct pe_optional_header_32 *po;
+
+ //printf("Optional header offset %zd\n", off);
+
+ range_check(x, off, sizeof(*po), "PE Optional Header");
+
+ po = (struct pe_optional_header_32 *)(x->x_buf + off);
+
+ switch (po->po_magic) {
+ case PE_OPTIONAL_MAGIC_32:
+ return (parse_optional_32(x, off, number_of_sections));
+ case PE_OPTIONAL_MAGIC_32_PLUS:
+ return (parse_optional_32_plus(x, off, number_of_sections));
+ default:
+ errx(1, "wrong PE Optional Header magic 0x%x", po->po_magic);
+ }
+}
+
+static void
+parse_pe(struct executable *x, off_t off)
+{
+ const struct pe_header *pe;
+
+ //printf("PE offset %zd, PE size %zd\n", off, sizeof(*pe));
+
+ range_check(x, off, sizeof(*pe), "PE header");
+
+ pe = (struct pe_header *)(x->x_buf + off);
+ if (pe->pe_signature != PE_SIGNATURE)
+ errx(1, "wrong PE signature 0x%x", pe->pe_signature);
+
+ //printf("Number of sections: %d\n", pe->pe_coff.coff_number_of_sections);
+
+ parse_optional(x, off + sizeof(*pe),
+ pe->pe_coff.coff_number_of_sections);
+}
+
+void
+parse(struct executable *x)
+{
+ const struct mz_header *mz;
+
+ range_check(x, 0, sizeof(*mz), "MZ header");
+
+ mz = (struct mz_header *)x->x_buf;
+ if (mz->mz_signature[0] != 'M' || mz->mz_signature[1] != 'Z')
+ errx(1, "MZ header not found");
+
+ return (parse_pe(x, mz->mz_lfanew));
+}
+
+static off_t
+append(struct executable *x, void *ptr, size_t len)
+{
+ off_t off;
+
+ /*
+ * XXX: Alignment.
+ */
+ off = x->x_len;
+ x->x_buf = realloc(x->x_buf, x->x_len + len);
+ if (x->x_buf == NULL)
+ err(1, "realloc");
+ memcpy(x->x_buf + x->x_len, ptr, len);
+ x->x_len += len;
+
+ return (off);
+}
+
+void
+update(struct executable *x)
+{
+ uint32_t checksum;
+ struct pe_certificate *pc;
+ struct pe_directory_entry pde;
+ size_t pc_len;
+ off_t pc_off;
+
+ pc_len = sizeof(*pc) + x->x_signature_len;
+ pc = calloc(1, pc_len);
+ if (pc == NULL)
+ err(1, "calloc");
+
+#if 0
+ /*
+ * Note that pc_len is the length of pc_certificate,
+ * not the whole structure.
+ *
+ * XXX: That's what the spec says - but it breaks at least
+ * sbverify and "pesign -S", so the spec is probably wrong.
+ */
+ pc->pc_len = x->x_signature_len;
+#else
+ pc->pc_len = pc_len;
+#endif
+ pc->pc_revision = PE_CERTIFICATE_REVISION;
+ pc->pc_type = PE_CERTIFICATE_TYPE;
+ memcpy(&pc->pc_signature, x->x_signature, x->x_signature_len);
+
+ pc_off = append(x, pc, pc_len);
+#if 0
+ printf("added signature chunk at offset %zd, len %zd\n",
+ pc_off, pc_len);
+#endif
+
+ free(pc);
+
+ pde.pde_rva = pc_off;
+ pde.pde_size = pc_len;
+ memcpy(x->x_buf + x->x_certificate_entry_off, &pde, sizeof(pde));
+
+ checksum = compute_checksum(x);
+ assert(sizeof(checksum) == x->x_checksum_len);
+ memcpy(x->x_buf + x->x_checksum_off, &checksum, sizeof(checksum));
+#if 0
+ printf("new checksum 0x%x\n", checksum);
+#endif
+}
diff --git a/usr.sbin/uefisign/uefisign.8 b/usr.sbin/uefisign/uefisign.8
new file mode 100644
index 0000000..5ef79d9
--- /dev/null
+++ b/usr.sbin/uefisign/uefisign.8
@@ -0,0 +1,93 @@
+.\" Copyright (c) 2014 The FreeBSD Foundation
+.\" All rights reserved.
+.\"
+.\" This software was 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 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 11, 2015
+.Dt UEFISIGN 8
+.Os
+.Sh NAME
+.Nm uefisign
+.Nd UEFI Secure Boot signing utility
+.Sh SYNOPSIS
+.Nm
+.Fl k Ar key
+.Fl c Ar certificate
+.Fl o Ar output
+.Op Fl v
+.Ar file
+.Nm
+.Fl V
+.Op Fl v
+.Ar file
+.Sh DESCRIPTION
+The
+.Nm
+utility signs PE binary files using Authenticode scheme, as required by
+UEFI Secure Boot specification.
+Alternatively, it can be used to view and verify existing signatures.
+These options are available:
+.Bl -tag -width ".Fl l"
+.It Fl V
+Determine whether the file is signed.
+Note that this does not verify the correctness of the signature;
+only that the file contains a signature.
+.It Fl k
+Name of file containing the private key used to sign the binary.
+.It Fl c
+Name of file containing the certificate used to sign the binary.
+.It Fl o
+Name of file to write the signed binary to.
+.It Fl v
+Be verbose.
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility exits 0 on success, and >0 if an error occurs.
+.Sh EXAMPLES
+Generate self-signed certificate and use it to sign a binary:
+.Dl /usr/share/examples/uefisign/uefikeys testcert
+.Dl uefisign -c testcert.pem -k testcert.key -o signed-binary binary
+.Pp
+View signature:
+.Dl uefisign -Vv binary
+.Sh SEE ALSO
+.Xr openssl 1 ,
+.Xr loader 8 ,
+.Xr uefi 8
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Fx 10.2 .
+.Sh AUTHORS
+The
+.Nm
+utility was developed by
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+under sponsorship from the FreeBSD Foundation.
diff --git a/usr.sbin/uefisign/uefisign.c b/usr.sbin/uefisign/uefisign.c
new file mode 100644
index 0000000..927f02b
--- /dev/null
+++ b/usr.sbin/uefisign/uefisign.c
@@ -0,0 +1,425 @@
+/*-
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/wait.h>
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <openssl/conf.h>
+#include <openssl/evp.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/pkcs7.h>
+
+#include "uefisign.h"
+#include "magic.h"
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: uefisign -c cert -k key -o outfile [-v] file\n"
+ " uefisign -V [-c cert] [-v] file\n");
+ exit(1);
+}
+
+static char *
+checked_strdup(const char *s)
+{
+ char *c;
+
+ c = strdup(s);
+ if (c == NULL)
+ err(1, "strdup");
+ return (c);
+}
+
+FILE *
+checked_fopen(const char *path, const char *mode)
+{
+ FILE *fp;
+
+ assert(path != NULL);
+
+ fp = fopen(path, mode);
+ if (fp == NULL)
+ err(1, "%s", path);
+ return (fp);
+}
+
+void
+send_chunk(const void *buf, size_t len, int pipefd)
+{
+ ssize_t ret;
+
+ ret = write(pipefd, &len, sizeof(len));
+ if (ret != sizeof(len))
+ err(1, "write");
+ ret = write(pipefd, buf, len);
+ if (ret != (ssize_t)len)
+ err(1, "write");
+}
+
+void
+receive_chunk(void **bufp, size_t *lenp, int pipefd)
+{
+ ssize_t ret;
+ size_t len;
+ void *buf;
+
+ ret = read(pipefd, &len, sizeof(len));
+ if (ret != sizeof(len))
+ err(1, "read");
+
+ buf = calloc(1, len);
+ if (buf == NULL)
+ err(1, "calloc");
+
+ ret = read(pipefd, buf, len);
+ if (ret != (ssize_t)len)
+ err(1, "read");
+
+ *bufp = buf;
+ *lenp = len;
+}
+
+static char *
+bin2hex(const char *bin, size_t bin_len)
+{
+ unsigned char *hex, *tmp, ch;
+ size_t hex_len;
+ size_t i;
+
+ hex_len = bin_len * 2 + 1; /* +1 for '\0'. */
+ hex = malloc(hex_len);
+ if (hex == NULL)
+ err(1, "malloc");
+
+ tmp = hex;
+ for (i = 0; i < bin_len; i++) {
+ ch = bin[i];
+ tmp += sprintf(tmp, "%02x", ch);
+ }
+
+ return (hex);
+}
+
+/*
+ * We need to replace a standard chunk of PKCS7 signature with one mandated
+ * by Authenticode. Problem is, replacing it just like that and then calling
+ * PKCS7_final() would make OpenSSL segfault somewhere in PKCS7_dataFinal().
+ * So, instead, we call PKCS7_dataInit(), then put our Authenticode-specific
+ * data into BIO it returned, then call PKCS7_dataFinal() - which now somehow
+ * does not panic - and _then_ we replace it in the signature. This technique
+ * was used in sbsigntool by Jeremy Kerr, and might have originated in
+ * osslsigncode.
+ */
+static void
+magic(PKCS7 *pkcs7, const char *digest, size_t digest_len)
+{
+ BIO *bio, *t_bio;
+ ASN1_TYPE *t;
+ ASN1_STRING *s;
+ CONF *cnf;
+ unsigned char *buf, *tmp;
+ char *digest_hex, *magic_conf, *str;
+ int len, nid, ok;
+
+ digest_hex = bin2hex(digest, digest_len);
+
+ /*
+ * Construct the SpcIndirectDataContent chunk.
+ */
+ nid = OBJ_create("1.3.6.1.4.1.311.2.1.4", NULL, NULL);
+
+ asprintf(&magic_conf, magic_fmt, digest_hex);
+ if (magic_conf == NULL)
+ err(1, "asprintf");
+
+ bio = BIO_new_mem_buf((void *)magic_conf, -1);
+ if (bio == NULL) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "BIO_new_mem_buf(3) failed");
+ }
+
+ cnf = NCONF_new(NULL);
+ if (cnf == NULL) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "NCONF_new(3) failed");
+ }
+
+ ok = NCONF_load_bio(cnf, bio, NULL);
+ if (ok == 0) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "NCONF_load_bio(3) failed");
+ }
+
+ str = NCONF_get_string(cnf, "default", "asn1");
+ if (str == NULL) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "NCONF_get_string(3) failed");
+ }
+
+ t = ASN1_generate_nconf(str, cnf);
+ if (t == NULL) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "ASN1_generate_nconf(3) failed");
+ }
+
+ /*
+ * We now have our proprietary piece of ASN.1. Let's do
+ * the actual signing.
+ */
+ len = i2d_ASN1_TYPE(t, NULL);
+ tmp = buf = calloc(1, len);
+ if (tmp == NULL)
+ err(1, "calloc");
+ i2d_ASN1_TYPE(t, &tmp);
+
+ /*
+ * We now have contents of 't' stuffed into memory buffer 'buf'.
+ */
+ tmp = NULL;
+ t = NULL;
+
+ t_bio = PKCS7_dataInit(pkcs7, NULL);
+ if (t_bio == NULL) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "PKCS7_dataInit(3) failed");
+ }
+
+ BIO_write(t_bio, buf + 2, len - 2);
+
+ ok = PKCS7_dataFinal(pkcs7, t_bio);
+ if (ok == 0) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "PKCS7_dataFinal(3) failed");
+ }
+
+ t = ASN1_TYPE_new();
+ s = ASN1_STRING_new();
+ ASN1_STRING_set(s, buf, len);
+ ASN1_TYPE_set(t, V_ASN1_SEQUENCE, s);
+
+ PKCS7_set0_type_other(pkcs7->d.sign->contents, nid, t);
+}
+
+static void
+sign(X509 *cert, EVP_PKEY *key, int pipefd)
+{
+ PKCS7 *pkcs7;
+ BIO *bio, *out;
+ const EVP_MD *md;
+ PKCS7_SIGNER_INFO *info;
+ void *digest, *signature;
+ size_t digest_len, signature_len;
+ int ok;
+
+ assert(cert != NULL);
+ assert(key != NULL);
+
+ receive_chunk(&digest, &digest_len, pipefd);
+
+ bio = BIO_new_mem_buf(digest, digest_len);
+ if (bio == NULL) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "BIO_new_mem_buf(3) failed");
+ }
+
+ pkcs7 = PKCS7_sign(NULL, NULL, NULL, bio, PKCS7_BINARY | PKCS7_PARTIAL);
+ if (pkcs7 == NULL) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "PKCS7_sign(3) failed");
+ }
+
+ md = EVP_get_digestbyname(DIGEST);
+ if (md == NULL) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "EVP_get_digestbyname(\"%s\") failed", DIGEST);
+ }
+
+ info = PKCS7_sign_add_signer(pkcs7, cert, key, md, 0);
+ if (info == NULL) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "PKCS7_sign_add_signer(3) failed");
+ }
+
+ /*
+ * XXX: All the signed binaries seem to have this, but where is it
+ * described in the spec?
+ */
+ PKCS7_add_signed_attribute(info, NID_pkcs9_contentType,
+ V_ASN1_OBJECT, OBJ_txt2obj("1.3.6.1.4.1.311.2.1.4", 1));
+
+ magic(pkcs7, digest, digest_len);
+
+#if 0
+ out = BIO_new(BIO_s_file());
+ BIO_set_fp(out, stdout, BIO_NOCLOSE);
+ PKCS7_print_ctx(out, pkcs7, 0, NULL);
+
+ i2d_PKCS7_bio(out, pkcs7);
+#endif
+
+ out = BIO_new(BIO_s_mem());
+ if (out == NULL) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "BIO_new(3) failed");
+ }
+
+ ok = i2d_PKCS7_bio(out, pkcs7);
+ if (ok == 0) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "i2d_PKCS7_bio(3) failed");
+ }
+
+ signature_len = BIO_get_mem_data(out, &signature);
+ if (signature_len <= 0) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "BIO_get_mem_data(3) failed");
+ }
+
+ (void)BIO_set_close(out, BIO_NOCLOSE);
+ BIO_free(out);
+
+ send_chunk(signature, signature_len, pipefd);
+}
+
+static int
+wait_for_child(pid_t pid)
+{
+ int status;
+
+ pid = waitpid(pid, &status, 0);
+ if (pid == -1)
+ err(1, "waitpid");
+
+ return (WEXITSTATUS(status));
+}
+
+int
+main(int argc, char **argv)
+{
+ int ch, error;
+ bool Vflag = false, vflag = false;
+ const char *certpath = NULL, *keypath = NULL, *outpath = NULL, *inpath = NULL;
+ FILE *certfp = NULL, *keyfp = NULL;
+ X509 *cert = NULL;
+ EVP_PKEY *key = NULL;
+ pid_t pid;
+ int pipefds[2];
+
+ while ((ch = getopt(argc, argv, "Vc:k:o:v")) != -1) {
+ switch (ch) {
+ case 'V':
+ Vflag = true;
+ break;
+ case 'c':
+ certpath = checked_strdup(optarg);
+ break;
+ case 'k':
+ keypath = checked_strdup(optarg);
+ break;
+ case 'o':
+ outpath = checked_strdup(optarg);
+ break;
+ case 'v':
+ vflag = true;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+ if (argc != 1)
+ usage();
+
+ if (Vflag) {
+ if (certpath != NULL)
+ errx(1, "-V and -c are mutually exclusive");
+ if (keypath != NULL)
+ errx(1, "-V and -k are mutually exclusive");
+ if (outpath != NULL)
+ errx(1, "-V and -o are mutually exclusive");
+ } else {
+ if (certpath == NULL)
+ errx(1, "-c option is mandatory");
+ if (keypath == NULL)
+ errx(1, "-k option is mandatory");
+ if (outpath == NULL)
+ errx(1, "-o option is mandatory");
+ }
+
+ inpath = argv[0];
+
+ OPENSSL_config(NULL);
+ ERR_load_crypto_strings();
+ OpenSSL_add_all_algorithms();
+
+ error = pipe(pipefds);
+ if (error != 0)
+ err(1, "pipe");
+
+ pid = fork();
+ if (pid < 0)
+ err(1, "fork");
+
+ if (pid == 0)
+ return (child(inpath, outpath, pipefds[1], Vflag, vflag));
+
+ if (!Vflag) {
+ certfp = checked_fopen(certpath, "r");
+ cert = PEM_read_X509(certfp, NULL, NULL, NULL);
+ if (cert == NULL) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "failed to load certificate from %s", certpath);
+ }
+
+ keyfp = checked_fopen(keypath, "r");
+ key = PEM_read_PrivateKey(keyfp, NULL, NULL, NULL);
+ if (key == NULL) {
+ ERR_print_errors_fp(stderr);
+ errx(1, "failed to load private key from %s", keypath);
+ }
+
+ sign(cert, key, pipefds[0]);
+ }
+
+ return (wait_for_child(pid));
+}
diff --git a/usr.sbin/uefisign/uefisign.h b/usr.sbin/uefisign/uefisign.h
new file mode 100644
index 0000000..d0920b8
--- /dev/null
+++ b/usr.sbin/uefisign/uefisign.h
@@ -0,0 +1,91 @@
+/*-
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was 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$
+ */
+
+#ifndef EFISIGN_H
+#define EFISIGN_H
+
+#include <stdbool.h>
+#include <openssl/evp.h>
+
+#define DIGEST "SHA256"
+#define MAX_SECTIONS 128
+
+struct executable {
+ const char *x_path;
+ FILE *x_fp;
+
+ char *x_buf;
+ size_t x_len;
+
+ /*
+ * Set by pe_parse(), used by digest().
+ */
+ size_t x_headers_len;
+
+ off_t x_checksum_off;
+ size_t x_checksum_len;
+
+ off_t x_certificate_entry_off;
+ size_t x_certificate_entry_len;
+
+ int x_nsections;
+ off_t x_section_off[MAX_SECTIONS];
+ size_t x_section_len[MAX_SECTIONS];
+
+ /*
+ * Computed by digest().
+ */
+ unsigned char x_digest[EVP_MAX_MD_SIZE];
+ unsigned int x_digest_len;
+
+ /*
+ * Received from the parent process, which computes it in sign().
+ */
+ void *x_signature;
+ size_t x_signature_len;
+};
+
+
+FILE *checked_fopen(const char *path, const char *mode);
+void send_chunk(const void *buf, size_t len, int pipefd);
+void receive_chunk(void **bufp, size_t *lenp, int pipefd);
+
+int child(const char *inpath, const char *outpath, int pipefd,
+ bool Vflag, bool vflag);
+
+void parse(struct executable *x);
+void update(struct executable *x);
+size_t signature_size(const struct executable *x);
+void show_certificate(const struct executable *x);
+void range_check(const struct executable *x,
+ off_t off, size_t len, const char *name);
+
+#endif /* !EFISIGN_H */
diff --git a/usr.sbin/ugidfw/Makefile b/usr.sbin/ugidfw/Makefile
new file mode 100644
index 0000000..7a5453e
--- /dev/null
+++ b/usr.sbin/ugidfw/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= ugidfw
+MAN= ugidfw.8
+
+LIBADD= ugidfw
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ugidfw/Makefile.depend b/usr.sbin/ugidfw/Makefile.depend
new file mode 100644
index 0000000..24a1de9
--- /dev/null
+++ b/usr.sbin/ugidfw/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libugidfw \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ugidfw/ugidfw.8 b/usr.sbin/ugidfw/ugidfw.8
new file mode 100644
index 0000000..a639d43
--- /dev/null
+++ b/usr.sbin/ugidfw/ugidfw.8
@@ -0,0 +1,360 @@
+.\" Copyright (c) 2002, 2004 Networks Associates Technology, Inc.
+.\" All rights reserved.
+.\"
+.\" This software was developed for the FreeBSD Project by Chris
+.\" Costello at Safeport Network Services 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 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 24, 2004
+.Dt UGIDFW 8
+.Os
+.Sh NAME
+.Nm ugidfw
+.Nd "firewall-like access controls for file system objects"
+.Sh SYNOPSIS
+.Nm
+.Cm add
+.Cm subject
+.Op Cm not
+.Oo
+.Op Cm \&!
+.Cm uid Ar uid | minuid:maxuid
+.Oc
+.Oo
+.Op Cm \&!
+.Cm gid Ar gid | mingid:maxgid
+.Oc
+.Oo
+.Op Cm \&!
+.Cm jailid Ad jailid
+.Oc
+.Cm object
+.Op Cm not
+.Oo
+.Op Cm \&!
+.Cm uid Ar uid | minuid:maxuid
+.Oc
+.Oo
+.Op Cm \&!
+.Cm gid Ar gid | mingid:maxgid
+.Oc
+.Oo
+.Op Cm \&!
+.Cm filesys Ad path
+.Oc
+.Oo
+.Op Cm \&!
+.Cm suid
+.Oc
+.Oo
+.Op Cm \&!
+.Cm sgid
+.Oc
+.Oo
+.Op Cm \&!
+.Cm uid_of_subject
+.Oc
+.Oo
+.Op Cm \&!
+.Cm gid_of_subject
+.Oc
+.Oo
+.Op Cm \&!
+.Cm type Ar ardbclsp
+.Oc
+.Cm mode
+.Ar arswxn
+.Nm
+.Cm list
+.Nm
+.Cm set
+.Ar rulenum
+.Cm subject
+.Op Cm not
+.Oo
+.Op Cm \&!
+.Cm uid Ar uid | minuid:maxuid
+.Oc
+.Oo
+.Op Cm \&!
+.Cm gid Ar gid | mingid:maxgid
+.Oc
+.Oo
+.Op Cm \&!
+.Cm jailid Ad jailid
+.Oc
+.Cm object
+.Op Cm not
+.Oo
+.Op Cm \&!
+.Cm uid Ar uid | minuid:maxuid
+.Oc
+.Oo
+.Op Cm \&!
+.Cm gid Ar gid | mingid:maxgid
+.Oc
+.Oo
+.Op Cm \&!
+.Cm filesys Ad path
+.Oc
+.Oo
+.Op Cm \&!
+.Cm suid
+.Oc
+.Oo
+.Op Cm \&!
+.Cm sgid
+.Oc
+.Oo
+.Op Cm \&!
+.Cm uid_of_subject
+.Oc
+.Oo
+.Op Cm \&!
+.Cm gid_of_subject
+.Oc
+.Oo
+.Op Cm \&!
+.Cm type Ar ardbclsp
+.Oc
+.Cm mode
+.Ar arswxn
+.Nm
+.Cm remove
+.Ar rulenum
+.Sh DESCRIPTION
+The
+.Nm
+utility provides an
+.Xr ipfw 8 Ns -like
+interface to manage access to file system objects by UID and GID,
+supported by the
+.Xr mac_bsdextended 4
+.Xr mac 9
+policy.
+.Pp
+The arguments are as follows:
+.Bl -tag -width indent -offset indent
+.It Xo
+.Cm add
+.Cm subject
+.Ar ...
+.Cm object
+.Ar ...
+.Cm mode
+.Ar arswxn
+.Xc
+Add a new rule, automatically selecting the rule number.
+See the description of
+.Cm set
+for syntax information.
+.It Cm list
+Produces a list of all the current
+.Nm
+rules in the system.
+.It Xo
+.Cm set Ar rulenum
+.Cm subject
+.Ar ...
+.Cm object
+.Ar ...
+.Cm mode
+.Ar arswxn
+.Xc
+Add a new rule or modify an existing rule.
+The arguments are as follows:
+.Bl -tag -width ".Ar rulenum"
+.It Ar rulenum
+Rule number.
+Entries with a lower rule number
+are applied first;
+placing the most frequently-matched rules at the beginning of the list
+(i.e., lower-numbered)
+will yield a slight performance increase.
+.It Xo
+.Cm subject
+.Op Cm not
+.Oo
+.Op Cm \&!
+.Cm uid Ar uid | minuid:maxuid
+.Oc
+.Oo
+.Op Cm \&!
+.Cm gid Ar gid | mingid:maxgid
+.Oc
+.Oo
+.Op Cm \&!
+.Cm jailid Ad jailid
+.Oc
+.Xc
+Subjects performing an operation must match all the conditions given.
+A leading
+.Cm not
+means that the subject should not match the remainder of the specification.
+A condition may be prefixed by
+.Cm \&!
+to indicate that particular condition must not match the subject.
+The subject can be required to have a particular
+.Ar uid
+and/or
+.Ar gid .
+A range of uids/gids can be specified, separated by a colon.
+The subject can be required to be in a particular jail with the
+.Ar jailid .
+.It Xo
+.Cm object
+.Op Cm not
+.Oo
+.Op Cm \&!
+.Cm uid Ar uid | minuid:maxuid
+.Oc
+.Oo
+.Op Cm \&!
+.Cm gid Ar gid | mingid:maxgid
+.Oc
+.Oo
+.Op Cm \&!
+.Cm filesys Ad path
+.Oc
+.Oo
+.Op Cm \&!
+.Cm suid
+.Oc
+.Oo
+.Op Cm \&!
+.Cm sgid
+.Oc
+.Oo
+.Op Cm \&!
+.Cm uid_of_subject
+.Oc
+.Oo
+.Op Cm \&!
+.Cm gid_of_subject
+.Oc
+.Oo
+.Op Cm \&!
+.Cm type Ar ardbclsp
+.Oc
+.Xc
+The rule will apply only to objects matching all the specified conditions.
+A leading
+.Cm not
+means that the object should not match all the remaining conditions.
+A condition may be prefixed by
+.Cm \&!
+to indicate that particular condition must not match the object.
+Objects can be required to be owned by the user and/or group specified by
+.Ar uid
+and/or
+.Ar gid .
+A range of uids/gids can be specified, separated by a colon.
+The object can be required to be in a particular filesystem by
+specifying the filesystem using
+.Cm filesys .
+Note,
+if the filesystem is unmounted and remounted,
+then the rule may need to be reapplied to ensure the correct filesystem
+id is used.
+The object can be required to have the
+.Cm suid
+or
+.Cm sgid
+bits set.
+The owner of the object can be required to match the
+.Cm uid_of_subject
+or the
+.Cm gid_of_subject
+attempting the operation.
+The type of the object can be restricted to a subset of
+the following types.
+.Pp
+.Bl -tag -width ".Cm w" -compact -offset indent
+.It Cm a
+any file type
+.It Cm r
+a regular file
+.It Cm d
+a directory
+.It Cm b
+a block special device
+.It Cm c
+a character special device
+.It Cm l
+a symbolic link
+.It Cm s
+a unix domain socket
+.It Cm p
+a named pipe (FIFO)
+.El
+.It Cm mode Ar arswxn
+Similar to
+.Xr chmod 1 ,
+each character represents an access mode.
+If the rule applies,
+the specified access permissions are enforced
+for the object.
+When a character is specified in the rule,
+the rule will allow for the operation.
+Conversely, not including it will cause the operation
+to be denied.
+The definitions of each character are as follows:
+.Pp
+.Bl -tag -width ".Cm w" -compact -offset indent
+.It Cm a
+administrative operations
+.It Cm r
+read access
+.It Cm s
+access to file attributes
+.It Cm w
+write access
+.It Cm x
+execute access
+.It Cm n
+none
+.El
+.El
+.It Cm remove Ar rulenum
+Disable and remove the rule with the specified rule number.
+.El
+.Sh SEE ALSO
+.Xr mac_bsdextended 4 ,
+.Xr mac 9
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 5.0 .
+.Sh AUTHORS
+This software was contributed to the
+.Fx
+Project by 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.
diff --git a/usr.sbin/ugidfw/ugidfw.c b/usr.sbin/ugidfw/ugidfw.c
new file mode 100644
index 0000000..977922a
--- /dev/null
+++ b/usr.sbin/ugidfw/ugidfw.c
@@ -0,0 +1,214 @@
+/*-
+ * Copyright (c) 2002, 2004 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.
+ *
+ * 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/errno.h>
+#include <sys/mount.h>
+#include <sys/time.h>
+#include <sys/sysctl.h>
+
+#include <security/mac_bsdextended/mac_bsdextended.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ugidfw.h>
+
+void add_rule(int argc, char *argv[]);
+void list_rules(void);
+void remove_rule(int argc, char *argv[]);
+void set_rule(int argc, char *argv[]);
+void usage(void);
+
+void
+usage(void)
+{
+
+ fprintf(stderr, "usage: ugidfw add [subject [not] [uid uid] [gid gid]]"
+ " [object [not] [uid uid] \\\n");
+ fprintf(stderr, " [gid gid]] mode arswxn\n");
+ fprintf(stderr, " ugidfw list\n");
+ fprintf(stderr, " ugidfw set rulenum [subject [not] [uid uid] [gid gid]]"
+ " [object [not] \\\n");
+ fprintf(stderr, " [uid uid] [gid gid]] mode arswxn\n");
+ fprintf(stderr, " ugidfw remove rulenum\n");
+
+ exit(1);
+}
+
+void
+add_rule(int argc, char *argv[])
+{
+ char errstr[BUFSIZ], charstr[BUFSIZ];
+ struct mac_bsdextended_rule rule;
+ int error, rulenum;
+
+ error = bsde_parse_rule(argc, argv, &rule, BUFSIZ, errstr);
+ if (error) {
+ warnx("%s", errstr);
+ return;
+ }
+
+ error = bsde_add_rule(&rulenum, &rule, BUFSIZ, errstr);
+ if (error) {
+ warnx("%s", errstr);
+ return;
+ }
+ if (bsde_rule_to_string(&rule, charstr, BUFSIZ) == -1)
+ warnx("Added rule, but unable to print string.");
+ else
+ printf("%d %s\n", rulenum, charstr);
+}
+
+void
+list_rules(void)
+{
+ char errstr[BUFSIZ], charstr[BUFSIZ];
+ struct mac_bsdextended_rule rule;
+ int error, i, rule_count, rule_slots;
+
+ rule_slots = bsde_get_rule_slots(BUFSIZ, errstr);
+ if (rule_slots == -1) {
+ warnx("unable to get rule slots; mac_bsdextended.ko "
+ "may not be loaded");
+ errx(1, "bsde_get_rule_slots: %s", errstr);
+ }
+
+ rule_count = bsde_get_rule_count(BUFSIZ, errstr);
+ if (rule_count == -1)
+ errx(1, "bsde_get_rule_count: %s", errstr);
+
+ printf("%d slots, %d rules\n", rule_slots, rule_count);
+
+ for (i = 0; i < rule_slots; i++) {
+ error = bsde_get_rule(i, &rule, BUFSIZ, errstr);
+ switch (error) {
+ case -2:
+ continue;
+ case -1:
+ warnx("rule %d: %s", i, errstr);
+ continue;
+ case 0:
+ break;
+ }
+
+ if (bsde_rule_to_string(&rule, charstr, BUFSIZ) == -1)
+ warnx("unable to translate rule %d to string", i);
+ else
+ printf("%d %s\n", i, charstr);
+ }
+}
+
+void
+set_rule(int argc, char *argv[])
+{
+ char errstr[BUFSIZ];
+ struct mac_bsdextended_rule rule;
+ long value;
+ int error, rulenum;
+ char *endp;
+
+ if (argc < 1)
+ usage();
+
+ value = strtol(argv[0], &endp, 10);
+ if (*endp != '\0')
+ usage();
+
+ if ((long) value != (int) value || value < 0)
+ usage();
+
+ rulenum = value;
+
+ error = bsde_parse_rule(argc - 1, argv + 1, &rule, BUFSIZ, errstr);
+ if (error) {
+ warnx("%s", errstr);
+ return;
+ }
+
+ error = bsde_set_rule(rulenum, &rule, BUFSIZ, errstr);
+ if (error) {
+ warnx("%s", errstr);
+ return;
+ }
+}
+
+void
+remove_rule(int argc, char *argv[])
+{
+ char errstr[BUFSIZ];
+ long value;
+ int error, rulenum;
+ char *endp;
+
+ if (argc != 1)
+ usage();
+
+ value = strtol(argv[0], &endp, 10);
+ if (*endp != '\0')
+ usage();
+
+ if ((long) value != (int) value || value < 0)
+ usage();
+
+ rulenum = value;
+
+ error = bsde_delete_rule(rulenum, BUFSIZ, errstr);
+ if (error)
+ warnx("%s", errstr);
+}
+
+int
+main(int argc, char *argv[])
+{
+
+ if (argc < 2)
+ usage();
+
+ if (strcmp("add", argv[1]) == 0) {
+ add_rule(argc-2, argv+2);
+ } else if (strcmp("list", argv[1]) == 0) {
+ if (argc != 2)
+ usage();
+ list_rules();
+ } else if (strcmp("set", argv[1]) == 0) {
+ set_rule(argc-2, argv+2);
+ } else if (strcmp("remove", argv[1]) == 0) {
+ remove_rule(argc-2, argv+2);
+ } else
+ usage();
+
+ return (0);
+}
diff --git a/usr.sbin/uhsoctl/Makefile b/usr.sbin/uhsoctl/Makefile
new file mode 100644
index 0000000..264384b
--- /dev/null
+++ b/usr.sbin/uhsoctl/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PROG= uhsoctl
+MAN= uhsoctl.1
+WARNS?= 1
+
+LIBADD= util
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/uhsoctl/Makefile.depend b/usr.sbin/uhsoctl/Makefile.depend
new file mode 100644
index 0000000..7de116d
--- /dev/null
+++ b/usr.sbin/uhsoctl/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/uhsoctl/uhsoctl.1 b/usr.sbin/uhsoctl/uhsoctl.1
new file mode 100644
index 0000000..826a1ec
--- /dev/null
+++ b/usr.sbin/uhsoctl/uhsoctl.1
@@ -0,0 +1,107 @@
+.\" Copyright (c) 2008-2009 Fredrik Lindberg
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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$
+.\"
+.Dd Aug 12, 2009
+.Dt UHSOCTL 1
+.Os
+.Sh NAME
+.Nm uhsoctl
+.Nd connection utility for Option based devices
+.Sh SYNOPSIS
+.Nm
+.Op Fl a Ar apn
+.Op Fl c Ar cid
+.Op Fl p Ar pin
+.Op Fl u Ar username
+.Op Fl k Ar password
+.Op Fl r Ar path
+.Op Fl f Ar path
+.Op Fl b | n
+.Ar interface
+.Nm
+.Fl d
+.Ar interface
+.Nm
+.Fl h
+.Sh DESCRIPTION
+.Nm
+is a small connection utility for Option N.V. devices that are based on Options
+packet interface and uses proprietary AT_* calls to establish connections.
+The utility (tries to) configure both default route and name servers
+.Po Pa /etc/resolv.conf Pc .
+.Pp
+By default
+.Nm
+detaches from the terminal upon on a successful connection, a few command-line
+options exists that allows this behavior to be changed.
+.Pp
+.Nm
+attempts to find a usable controlling serial port based on the provided network
+interface.
+If this fails you might specify a serial port manually.
+.Sh OPTIONS
+.Bl -tag -width XXXX
+.It Fl a Ar apn
+Specify APN to connect to.
+.It Fl c Ar cid
+Specify CID (Context ID) to use, by default CID 1 is used.
+If an APN has been configured once, it's enough to specify the CID used for
+further accesses.
+.It Fl p Ar pin
+Specify SIM PIN.
+.It Fl u Ar username
+Specify username.
+.It Fl k Ar password
+Specify username.
+.It Fl r Ar path
+Path to
+.Pa resolv.conf ,
+default
+.Pa /etc/resolv.conf .
+Use
+.Pa /dev/null
+to disable updating of name servers.
+.It Fl f Ar path
+Explicitly set the serial port to use as controlling terminal.
+Might be needed if the automatic detection fails.
+.It Fl b
+Fork into background directly, before a connection has been established.
+.It Fl n
+Never fork into background, run entirely in foreground.
+.El
+.Sh EXAMPLES
+Connect to
+.Dq Li apn.example.com
+on interface
+.Dq Li uhso0
+and use PIN
+.Dq 1234
+to enable the SIM card.
+.Dl "uhsoctl -a apn.example.com -p 1234 uhso0"
+.Pp
+Disconnect from a previously established connection.
+.Dl "uhsoctl -d uhso0"
+.Sh SEE ALSO
+.Xr uhso 4
diff --git a/usr.sbin/uhsoctl/uhsoctl.c b/usr.sbin/uhsoctl/uhsoctl.c
new file mode 100644
index 0000000..9dc13ab
--- /dev/null
+++ b/usr.sbin/uhsoctl/uhsoctl.c
@@ -0,0 +1,1561 @@
+/*-
+ * Copyright (c) 2008-2009 Fredrik Lindberg
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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$
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/queue.h>
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <signal.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <ifaddrs.h>
+#include <libutil.h>
+#include <time.h>
+
+/*
+ * Connection utility to ease connectivity using the raw IP packet interface
+ * available on uhso(4) devices.
+ */
+
+#define TTY_NAME "/dev/%s"
+#define SYSCTL_TEST "dev.uhso.%d.%%driver"
+#define SYSCTL_LOCATION "dev.uhso.%d.%%location"
+#define SYSCTL_PORTS "dev.uhso.%d.ports"
+#define SYSCTL_NETIF "dev.uhso.%d.netif"
+#define SYSCTL_NAME_TTY "dev.uhso.%d.port.%s.tty"
+#define SYSCTL_NAME_DESC "dev.uhso.%d.port.%s.desc"
+#define RESOLV_PATH "/etc/resolv.conf"
+#define PIDFILE "/var/run/uhsoctl.%s.pid"
+
+static const char *network_access_type[] = {
+ "GSM",
+ "Compact GSM",
+ "UMTS",
+ "GSM (EGPRS)",
+ "HSDPA",
+ "HSUPA",
+ "HSDPA/HSUPA"
+};
+
+static const char *network_reg_status[] = {
+ "Not registered",
+ "Registered",
+ "Searching for network",
+ "Network registration denied",
+ "Unknown",
+ "Registered (roaming)"
+};
+
+struct ctx {
+ int fd;
+ int flags;
+#define IPASSIGNED 0x01
+#define FLG_NODAEMON 0x02 /* Don't detach from terminal */
+#define FLG_DAEMON 0x04 /* Running as daemon */
+#define FLG_DELAYED 0x08 /* Fork into background after connect */
+#define FLG_NEWDATA 0x10
+#define FLG_WATCHDOG 0x20 /* Watchdog enabled */
+#define FLG_WDEXP 0x40 /* Watchdog expired */
+ const char *ifnam;
+ const char *pin; /* device PIN */
+
+ char pidfile[128];
+ struct pidfh *pfh;
+
+ time_t watchdog;
+
+ /* PDP context settings */
+ int pdp_ctx;
+ const char *pdp_apn;
+ const char *pdp_user;
+ const char *pdp_pwd;
+
+ /* Connection status */
+ int con_status; /* Connected? */
+ char *con_apn; /* Connected APN */
+ char *con_oper; /* Operator name */
+ int con_net_stat; /* Network connection status */
+ int con_net_type; /* Network connection type */
+
+ /* Misc. status */
+ int dbm;
+
+ /* IP and nameserver settings */
+ struct in_addr ip;
+ char **ns;
+ const char *resolv_path;
+ char *resolv; /* Old resolv.conf */
+ size_t resolv_sz;
+};
+
+static int readline_buf(const char *, const char *, char *, size_t);
+static int readline(int, char *, size_t);
+static void daemonize(struct ctx *);
+
+static int at_cmd_async(int, const char *, ...);
+
+typedef union {
+ void *ptr;
+ uint32_t int32;
+} resp_data;
+typedef struct {
+ resp_data val[2];
+} resp_arg;
+typedef void (*resp_cb)(resp_arg *, const char *, const char *);
+
+typedef void (*async_cb)(void *, const char *);
+struct async_handle {
+ const char *cmd;
+ async_cb func;
+};
+
+static void at_async_creg(void *, const char *);
+static void at_async_cgreg(void *, const char *);
+static void at_async_cops(void *, const char *);
+static void at_async_owancall(void *, const char *);
+static void at_async_owandata(void *, const char *);
+static void at_async_csq(void *, const char *);
+
+static struct async_handle async_cmd[] = {
+ { "+CREG", at_async_creg },
+ { "+CGREG", at_async_cgreg },
+ { "+COPS", at_async_cops },
+ { "+CSQ", at_async_csq },
+ { "_OWANCALL", at_async_owancall },
+ { "_OWANDATA", at_async_owandata },
+ { NULL, NULL }
+};
+
+struct timer_entry;
+struct timers {
+ TAILQ_HEAD(, timer_entry) head;
+ int res;
+};
+
+typedef void (*tmr_cb)(int, void *);
+struct timer_entry {
+ TAILQ_ENTRY(timer_entry) next;
+ int id;
+ int timeout;
+ tmr_cb func;
+ void *arg;
+};
+
+
+static struct timers timers;
+static volatile int running = 1;
+static int syslog_open = 0;
+static char syslog_title[64];
+
+/* Periodic timer, runs ready timer tasks every tick */
+static void
+tmr_run(struct timers *tmrs)
+{
+ struct timer_entry *te, *te2;
+
+ te = TAILQ_FIRST(&tmrs->head);
+ if (te == NULL)
+ return;
+
+ te->timeout -= tmrs->res;
+ while (te->timeout <= 0) {
+ te2 = TAILQ_NEXT(te, next);
+ TAILQ_REMOVE(&tmrs->head, te, next);
+ te->func(te->id, te->arg);
+ free(te);
+ te = te2;
+ if (te == NULL)
+ break;
+ }
+}
+
+/* Add a new timer */
+static void
+tmr_add(struct timers *tmrs, int id, int timeout, tmr_cb func, void *arg)
+{
+ struct timer_entry *te, *te2, *te3;
+
+ te = malloc(sizeof(struct timer_entry));
+ memset(te, 0, sizeof(struct timer_entry));
+
+ te->timeout = timeout;
+ te->func = func;
+ te->arg = arg;
+ te->id = id;
+
+ te2 = TAILQ_FIRST(&tmrs->head);
+
+ if (TAILQ_EMPTY(&tmrs->head)) {
+ TAILQ_INSERT_HEAD(&tmrs->head, te, next);
+ } else if (te->timeout < te2->timeout) {
+ te2->timeout -= te->timeout;
+ TAILQ_INSERT_HEAD(&tmrs->head, te, next);
+ } else {
+ while (te->timeout >= te2->timeout) {
+ te->timeout -= te2->timeout;
+ te3 = TAILQ_NEXT(te2, next);
+ if (te3 == NULL || te3->timeout > te->timeout)
+ break;
+ te2 = te3;
+ }
+ TAILQ_INSERT_AFTER(&tmrs->head, te2, te, next);
+ }
+}
+
+#define watchdog_enable(ctx) (ctx)->flags |= FLG_WATCHDOG
+#define watchdog_disable(ctx) (ctx)->flags &= ~FLG_WATCHDOG
+
+static void
+watchdog_reset(struct ctx *ctx, int timeout)
+{
+ struct timespec tp;
+
+ clock_gettime(CLOCK_MONOTONIC, &tp),
+ ctx->watchdog = tp.tv_sec + timeout;
+
+ watchdog_enable(ctx);
+}
+
+static void
+tmr_creg(int id, void *arg)
+{
+ struct ctx *ctx = arg;
+
+ at_cmd_async(ctx->fd, "AT+CREG?\r\n");
+ watchdog_reset(ctx, 10);
+}
+
+static void
+tmr_cgreg(int id, void *arg)
+{
+ struct ctx *ctx = arg;
+
+ at_cmd_async(ctx->fd, "AT+CGREG?\r\n");
+ watchdog_reset(ctx, 10);
+}
+
+static void
+tmr_status(int id, void *arg)
+{
+ struct ctx *ctx = arg;
+
+ at_cmd_async(ctx->fd, "AT+CSQ\r\n");
+ watchdog_reset(ctx, 10);
+}
+
+static void
+tmr_watchdog(int id, void *arg)
+{
+ struct ctx *ctx = arg;
+ pid_t self;
+ struct timespec tp;
+
+ tmr_add(&timers, 1, 5, tmr_watchdog, ctx);
+
+ if (!(ctx->flags & FLG_WATCHDOG))
+ return;
+
+ clock_gettime(CLOCK_MONOTONIC, &tp);
+
+ if (tp.tv_sec >= ctx->watchdog) {
+#ifdef DEBUG
+ fprintf(stderr, "Watchdog expired\n");
+#endif
+ ctx->flags |= FLG_WDEXP;
+ self = getpid();
+ kill(self, SIGHUP);
+ }
+}
+
+static void
+sig_handle(int sig)
+{
+
+ switch (sig) {
+ case SIGHUP:
+ case SIGINT:
+ case SIGQUIT:
+ case SIGTERM:
+ running = 0;
+ break;
+ case SIGALRM:
+ tmr_run(&timers);
+ break;
+ }
+}
+
+static void
+logger(int pri, const char *fmt, ...)
+{
+ char *buf;
+ va_list ap;
+
+ va_start(ap, fmt);
+ vasprintf(&buf, fmt, ap);
+ if (syslog_open)
+ syslog(pri, "%s", buf);
+ else {
+ switch (pri) {
+ case LOG_INFO:
+ case LOG_NOTICE:
+ printf("%s\n", buf);
+ break;
+ default:
+ fprintf(stderr, "%s: %s\n", getprogname(), buf);
+ break;
+ }
+ }
+
+ free(buf);
+ va_end(ap);
+}
+
+/* Add/remove IP address from an interface */
+static int
+ifaddr_ad(int d, const char *ifnam, struct sockaddr *sa, struct sockaddr *mask)
+{
+ struct ifaliasreq req;
+ int fd, error;
+
+ fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0)
+ return (-1);
+
+ memset(&req, 0, sizeof(struct ifaliasreq));
+ strlcpy(req.ifra_name, ifnam, sizeof(req.ifra_name));
+ memcpy(&req.ifra_addr, sa, sa->sa_len);
+ memcpy(&req.ifra_mask, mask, mask->sa_len);
+
+ error = ioctl(fd, d, (char *)&req);
+ close(fd);
+ return (error);
+}
+
+#define if_ifup(ifnam) if_setflags(ifnam, IFF_UP)
+#define if_ifdown(ifnam) if_setflags(ifnam, -IFF_UP)
+
+static int
+if_setflags(const char *ifnam, int flags)
+{
+ struct ifreq ifr;
+ int fd, error;
+ unsigned int oflags = 0;
+
+ memset(&ifr, 0, sizeof(struct ifreq));
+ strlcpy(ifr.ifr_name, ifnam, sizeof(ifr.ifr_name));
+
+ fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0)
+ return (-1);
+
+ error = ioctl(fd, SIOCGIFFLAGS, &ifr);
+ if (error == 0) {
+ oflags = (ifr.ifr_flags & 0xffff) | (ifr.ifr_flagshigh << 16);
+ }
+
+ if (flags < 0)
+ oflags &= ~(-flags);
+ else
+ oflags |= flags;
+
+ ifr.ifr_flags = oflags & 0xffff;
+ ifr.ifr_flagshigh = oflags >> 16;
+
+ error = ioctl(fd, SIOCSIFFLAGS, &ifr);
+ if (error != 0)
+ warn("ioctl SIOCSIFFLAGS");
+
+ close(fd);
+ return (error);
+}
+
+static int
+ifaddr_add(const char *ifnam, struct sockaddr *sa, struct sockaddr *mask)
+{
+ int error;
+
+ error = ifaddr_ad(SIOCAIFADDR, ifnam, sa, mask);
+ if (error != 0)
+ warn("ioctl SIOCAIFADDR");
+ return (error);
+}
+
+static int
+ifaddr_del(const char *ifnam, struct sockaddr *sa, struct sockaddr *mask)
+{
+ int error;
+
+ error = ifaddr_ad(SIOCDIFADDR, ifnam, sa, mask);
+ if (error != 0)
+ warn("ioctl SIOCDIFADDR");
+ return (error);
+}
+
+static int
+set_nameservers(struct ctx *ctx, const char *respath, int ns, ...)
+{
+ int i, n, fd;
+ FILE *fp;
+ char *p;
+ va_list ap;
+ struct stat sb;
+ char buf[512];
+
+ if (ctx->ns != NULL) {
+ for (i = 0; ctx->ns[i] != NULL; i++) {
+ free(ctx->ns[i]);
+ }
+ free(ctx->ns);
+ ctx->ns = NULL;
+ }
+
+ fd = open(respath, O_RDWR | O_CREAT | O_NOFOLLOW, 0666);
+ if (fd < 0)
+ return (-1);
+
+ if (ns == 0) {
+ /* Attempt to restore old resolv.conf */
+ if (ctx->resolv != NULL) {
+ ftruncate(fd, 0);
+ lseek(fd, 0, SEEK_SET);
+ write(fd, ctx->resolv, ctx->resolv_sz);
+ free(ctx->resolv);
+ ctx->resolv = NULL;
+ ctx->resolv_sz = 0;
+ }
+ close(fd);
+ return (0);
+ }
+
+
+ ctx->ns = malloc(sizeof(char *) * (ns + 1));
+ if (ctx->ns == NULL) {
+ close(fd);
+ return (-1);
+ }
+
+ va_start(ap, ns);
+ for (i = 0; i < ns; i++) {
+ p = va_arg(ap, char *);
+ ctx->ns[i] = strdup(p);
+ }
+ ctx->ns[i] = NULL;
+ va_end(ap);
+
+ /* Attempt to backup the old resolv.conf */
+ if (ctx->resolv == NULL) {
+ i = fstat(fd, &sb);
+ if (i == 0 && sb.st_size != 0) {
+ ctx->resolv_sz = sb.st_size;
+ ctx->resolv = malloc(sb.st_size);
+ if (ctx->resolv != NULL) {
+ n = read(fd, ctx->resolv, sb.st_size);
+ if (n != sb.st_size) {
+ free(ctx->resolv);
+ ctx->resolv = NULL;
+ }
+ }
+ }
+ }
+
+
+ ftruncate(fd, 0);
+ lseek(fd, 0, SEEK_SET);
+ fp = fdopen(fd, "w");
+
+ /*
+ * Write back everything other than nameserver entries to the
+ * new resolv.conf
+ */
+ if (ctx->resolv != NULL) {
+ p = ctx->resolv;
+ while ((i = readline_buf(p, ctx->resolv + ctx->resolv_sz, buf,
+ sizeof(buf))) > 0) {
+ p += i;
+ if (strncasecmp(buf, "nameserver", 10) == 0)
+ continue;
+ fprintf(fp, "%s", buf);
+ }
+ }
+
+ for (i = 0; ctx->ns[i] != NULL; i++) {
+ fprintf(fp, "nameserver %s\n", ctx->ns[i]);
+ }
+ fclose(fp);
+ return (0);
+}
+
+/* Read a \n-terminated line from buffer */
+static int
+readline_buf(const char *s, const char *e, char *buf, size_t bufsz)
+{
+ int pos = 0;
+ char *p = buf;
+
+ for (; s < e; s++) {
+ *p = *s;
+ pos++;
+ if (pos >= (bufsz - 1))
+ break;
+ if (*p++ == '\n')
+ break;
+ }
+ *p = '\0';
+ return (pos);
+}
+
+/* Read a \n-terminated line from file */
+static int
+readline(int fd, char *buf, size_t bufsz)
+{
+ int n = 0, pos = 0;
+ char *p = buf;
+
+ for (;;) {
+ n = read(fd, p, 1);
+ if (n <= 0)
+ break;
+ pos++;
+ if (pos >= (bufsz - 1))
+ break;
+ if (*p++ == '\n')
+ break;
+ }
+ *p = '\0';
+ return (n <= 0 ? n : pos);
+}
+
+/*
+ * Synchronous AT command
+ */
+static int
+at_cmd(struct ctx *ctx, const char *resp, resp_cb cb, resp_arg *ra, const char *cf, ...)
+{
+ char buf[512];
+ char cmd[64];
+ size_t l;
+ int n, error, retval = 0;
+ va_list ap;
+ fd_set set;
+ char *p;
+
+ va_start(ap, cf);
+ vsnprintf(cmd, sizeof(cmd), cf, ap);
+ va_end(ap);
+
+#ifdef DEBUG
+ fprintf(stderr, "SYNC_CMD: %s", cmd);
+#endif
+
+ l = strlen(cmd);
+ n = write(ctx->fd, cmd, l);
+ if (n <= 0)
+ return (-1);
+
+ if (resp != NULL) {
+ l = strlen(resp);
+#ifdef DEBUG
+ fprintf(stderr, "SYNC_EXP: %s (%zu)\n", resp, l);
+#endif
+ }
+
+ for (;;) {
+ bzero(buf, sizeof(buf));
+
+ FD_ZERO(&set);
+ watchdog_reset(ctx, 5);
+ do {
+ FD_SET(ctx->fd, &set);
+ error = select(ctx->fd + 1, &set, NULL, NULL, NULL);
+ if (ctx->flags & FLG_WDEXP) {
+ watchdog_disable(ctx);
+ return (-2);
+ }
+ } while (error <= 0 && errno == EINTR);
+ watchdog_disable(ctx);
+
+ if (error <= 0) {
+ retval = -2;
+ break;
+ }
+
+ n = readline(ctx->fd, buf, sizeof(buf));
+ if (n <= 0) {
+ retval = -2;
+ break;
+ }
+
+ if (strcmp(buf, "\r\n") == 0 || strcmp(buf, "\n") == 0)
+ continue;
+
+ if ((p = strchr(buf, '\r')) != NULL)
+ *p = '\0';
+ else if ((p = strchr(buf, '\n')) != NULL)
+ *p = '\0';
+#ifdef DEBUG
+ fprintf(stderr, "SYNC_RESP: %s\n", buf);
+#endif
+
+ /* Skip local echo */
+ if (strncasecmp(cmd, buf, strlen(buf)) == 0)
+ continue;
+
+ if (cb != NULL)
+ cb(ra, cmd, buf);
+
+ if (strncmp(buf, "OK", 2) == 0) {
+ retval = retval ? retval : 0;
+ break;
+ } else if (strstr(buf, "ERROR") != NULL) {
+ retval = -1;
+ break;
+ }
+ if (resp != NULL)
+ retval = strncmp(buf, resp, l);
+ }
+#ifdef DEBUG
+ fprintf(stderr, "SYNC_RETVAL=%d\n", retval);
+#endif
+ return (retval);
+}
+
+static int
+at_cmd_async(int fd, const char *cf, ...)
+{
+ size_t l;
+ va_list ap;
+ char cmd[64];
+
+ va_start(ap, cf);
+ vsnprintf(cmd, sizeof(cmd), cf, ap);
+ va_end(ap);
+
+#ifdef DEBUG
+ fprintf(stderr, "CMD: %s", cmd);
+#endif
+ l = strlen(cmd);
+ return (write(fd, cmd, l));
+}
+
+static void
+saveresp(resp_arg *ra, const char *cmd, const char *resp)
+{
+ char **buf;
+ int i = ra->val[1].int32;
+
+#ifdef DEBUG
+ fprintf(stderr, "Save '%s'\n", resp);
+#endif
+
+ buf = realloc(ra->val[0].ptr, sizeof(char *) * (i + 1));
+ if (buf == NULL)
+ return;
+
+ buf[i] = strdup(resp);
+
+ ra->val[0].ptr = buf;
+ ra->val[1].int32 = i + 1;
+}
+
+static void
+freeresp(resp_arg *ra)
+{
+ char **buf;
+ int i;
+
+ buf = ra->val[0].ptr;
+ for (i = 0; i < ra->val[1].int32; i++) {
+ free(buf[i]);
+ }
+ free(buf);
+}
+
+static void
+at_async_creg(void *arg, const char *resp)
+{
+ struct ctx *ctx = arg;
+ int n, reg;
+
+ n = sscanf(resp, "+CREG: %*d,%d", &reg);
+ if (n != 1) {
+ n = sscanf(resp, "+CREG: %d", &reg);
+ if (n != 1)
+ return;
+ }
+
+ if (ctx->con_net_stat != 1 && ctx->con_net_stat != 5) {
+ tmr_add(&timers, 1, 1, tmr_creg, ctx);
+ }
+ else {
+ tmr_add(&timers, 1, 30, tmr_creg, ctx);
+ }
+
+ if (ctx->con_net_stat == reg)
+ return;
+
+ ctx->con_net_stat = reg;
+ at_cmd_async(ctx->fd, "AT+COPS?\r\n");
+}
+
+static void
+at_async_cgreg(void *arg, const char *resp)
+{
+ struct ctx *ctx = arg;
+ int n, reg;
+
+ n = sscanf(resp, "+CGREG: %*d,%d", &reg);
+ if (n != 1) {
+ n = sscanf(resp, "+CGREG: %d", &reg);
+ if (n != 1)
+ return;
+ }
+
+ if (ctx->con_net_stat != 1 && ctx->con_net_stat != 5) {
+ tmr_add(&timers, 1, 1, tmr_cgreg, ctx);
+ }
+ else {
+ tmr_add(&timers, 1, 30, tmr_cgreg, ctx);
+ }
+
+ if (ctx->con_net_stat == reg)
+ return;
+
+ ctx->con_net_stat = reg;
+ at_cmd_async(ctx->fd, "AT+COPS?\r\n");
+}
+
+
+static void
+at_async_cops(void *arg, const char *resp)
+{
+ struct ctx *ctx = arg;
+ int n, at;
+ char opr[64];
+
+ n = sscanf(resp, "+COPS: %*d,%*d,\"%[^\"]\",%d",
+ opr, &at);
+ if (n != 2)
+ return;
+
+ if (ctx->con_oper != NULL) {
+ if (ctx->con_net_type == at &&
+ strcasecmp(opr, ctx->con_oper) == 0)
+ return;
+ free(ctx->con_oper);
+ }
+
+ ctx->con_oper = strdup(opr);
+ ctx->con_net_type = at;
+
+ if (ctx->con_net_stat == 1 || ctx->con_net_stat == 5) {
+ logger(LOG_NOTICE, "%s to \"%s\" (%s)",
+ network_reg_status[ctx->con_net_stat],
+ ctx->con_oper, network_access_type[ctx->con_net_type]);
+ if (ctx->con_status != 1) {
+ at_cmd_async(ctx->fd, "AT_OWANCALL=%d,1,1\r\n",
+ ctx->pdp_ctx);
+ }
+ }
+ else {
+ logger(LOG_NOTICE, "%s (%s)",
+ network_reg_status[ctx->con_net_stat],
+ network_access_type[ctx->con_net_type]);
+ }
+}
+
+/*
+ * Signal strength for pretty console output
+ *
+ * From 3GPP TS 27.007 V8.3.0, Section 8.5
+ * 0 = -113 dBm or less
+ * 1 = -111 dBm
+ * 2...30 = -109...-53 dBm
+ * 31 = -51 dBm or greater
+ *
+ * So, dbm = (rssi * 2) - 113
+*/
+static void
+at_async_csq(void *arg, const char *resp)
+{
+ struct ctx *ctx = arg;
+ int n, rssi;
+
+ n = sscanf(resp, "+CSQ: %d,%*d", &rssi);
+ if (n != 1)
+ return;
+ if (rssi == 99)
+ ctx->dbm = 0;
+ else {
+ ctx->dbm = (rssi * 2) - 113;
+ tmr_add(&timers, 1, 15, tmr_status, ctx);
+ }
+
+ ctx->flags |= FLG_NEWDATA;
+}
+
+static void
+at_async_owancall(void *arg, const char *resp)
+{
+ struct ctx *ctx = arg;
+ int n, i;
+
+ n = sscanf(resp, "_OWANCALL: %*d,%d", &i);
+ if (n != 1)
+ return;
+
+ if (i == ctx->con_status)
+ return;
+
+ at_cmd_async(ctx->fd, "AT_OWANDATA=%d\r\n", ctx->pdp_ctx);
+
+ ctx->con_status = i;
+ if (ctx->con_status == 1) {
+ logger(LOG_NOTICE, "Connected to \"%s\" (%s), %s",
+ ctx->con_oper, ctx->con_apn,
+ network_access_type[ctx->con_net_type]);
+ }
+ else {
+ logger(LOG_NOTICE, "Disconnected from \"%s\" (%s)",
+ ctx->con_oper, ctx->con_apn);
+ }
+}
+
+static void
+at_async_owandata(void *arg, const char *resp)
+{
+ struct ctx *ctx = arg;
+ char ip[40], ns1[40], ns2[40];
+ int n, error, rs;
+ struct ifaddrs *ifap, *ifa;
+ struct sockaddr_in sin, mask;
+ struct sockaddr_dl sdl;
+ struct {
+ struct rt_msghdr rtm;
+ char buf[512];
+ } r;
+ char *cp = r.buf;
+
+ n = sscanf(resp, "_OWANDATA: %*d, %[^,], %*[^,], %[^,], %[^,]",
+ ip, ns1, ns2);
+ if (n != 3)
+ return;
+
+ /* XXX: AF_INET assumption */
+
+ logger(LOG_NOTICE, "IP address: %s, Nameservers: %s, %s", ip, ns1, ns2);
+
+ sin.sin_len = mask.sin_len = sizeof(struct sockaddr_in);
+ memset(&mask.sin_addr.s_addr, 0xff, sizeof(mask.sin_addr.s_addr));
+ sin.sin_family = mask.sin_family = AF_INET;
+
+ if (ctx->flags & IPASSIGNED) {
+ memcpy(&sin.sin_addr.s_addr, &ctx->ip.s_addr,
+ sizeof(sin.sin_addr.s_addr));
+ ifaddr_del(ctx->ifnam, (struct sockaddr *)&sin,
+ (struct sockaddr *)&mask);
+ }
+ inet_pton(AF_INET, ip, &ctx->ip.s_addr);
+ memcpy(&sin.sin_addr.s_addr, &ctx->ip.s_addr,
+ sizeof(sin.sin_addr.s_addr));
+
+ error = ifaddr_add(ctx->ifnam, (struct sockaddr *)&sin,
+ (struct sockaddr *)&mask);
+ if (error != 0) {
+ logger(LOG_ERR, "failed to set ip-address");
+ return;
+ }
+
+ if_ifup(ctx->ifnam);
+
+ ctx->flags |= IPASSIGNED;
+
+ set_nameservers(ctx, ctx->resolv_path, 0);
+ error = set_nameservers(ctx, ctx->resolv_path, 2, ns1, ns2);
+ if (error != 0) {
+ logger(LOG_ERR, "failed to set nameservers");
+ }
+
+ error = getifaddrs(&ifap);
+ if (error != 0) {
+ logger(LOG_ERR, "getifaddrs: %s", strerror(errno));
+ return;
+ }
+
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr->sa_family != AF_LINK)
+ continue;
+ if (strcmp(ctx->ifnam, ifa->ifa_name) == 0) {
+ memcpy(&sdl, (struct sockaddr_dl *)ifa->ifa_addr,
+ sizeof(struct sockaddr_dl));
+ break;
+ }
+ }
+ if (ifa == NULL)
+ return;
+
+ rs = socket(PF_ROUTE, SOCK_RAW, 0);
+ if (rs < 0) {
+ logger(LOG_ERR, "socket PF_ROUTE: %s", strerror(errno));
+ return;
+ }
+
+ memset(&r, 0, sizeof(r));
+
+ r.rtm.rtm_version = RTM_VERSION;
+ r.rtm.rtm_type = RTM_ADD;
+ r.rtm.rtm_flags = RTF_UP | RTF_STATIC;
+ r.rtm.rtm_pid = getpid();
+ memset(&sin, 0, sizeof(struct sockaddr_in));
+ sin.sin_family = AF_INET;
+ sin.sin_len = sizeof(struct sockaddr_in);
+
+ memcpy(cp, &sin, sin.sin_len);
+ cp += SA_SIZE(&sin);
+ memcpy(cp, &sdl, sdl.sdl_len);
+ cp += SA_SIZE(&sdl);
+ memcpy(cp, &sin, sin.sin_len);
+ r.rtm.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK;
+ r.rtm.rtm_msglen = sizeof(r);
+
+ n = write(rs, &r, r.rtm.rtm_msglen);
+ if (n != r.rtm.rtm_msglen) {
+ r.rtm.rtm_type = RTM_DELETE;
+ n = write(rs, &r, r.rtm.rtm_msglen);
+ r.rtm.rtm_type = RTM_ADD;
+ n = write(rs, &r, r.rtm.rtm_msglen);
+ }
+
+ if (n != r.rtm.rtm_msglen) {
+ logger(LOG_ERR, "failed to set default route: %s",
+ strerror(errno));
+ }
+ close(rs);
+
+ /* Delayed daemonization */
+ if ((ctx->flags & FLG_DELAYED) && !(ctx->flags & FLG_NODAEMON))
+ daemonize(ctx);
+}
+
+static int
+at_async(struct ctx *ctx, void *arg)
+{
+ int n, i;
+ size_t l;
+ char buf[512];
+
+ watchdog_reset(ctx, 15);
+
+ bzero(buf, sizeof(buf));
+ n = readline(ctx->fd, buf, sizeof(buf));
+ if (n <= 0)
+ return (n <= 0 ? -1 : 0);
+
+#ifdef DEBUG
+ fprintf(stderr, "AT_ASYNC_RESP: %s", buf);
+#endif
+ for (i = 0; async_cmd[i].cmd != NULL; i++) {
+ l = strlen(async_cmd[i].cmd);
+ if (strncmp(buf, async_cmd[i].cmd, l) == 0) {
+ async_cmd[i].func(arg, buf);
+ }
+ }
+ return (0);
+}
+
+static const char *port_type_list[] = {
+ "control", "application", "application2", NULL
+};
+
+/*
+ * Attempts to find a list of control tty for the interface
+ * FreeBSD attaches USB devices per interface so we have to go through
+ * hoops to find which ttys that belong to our network interface.
+ */
+static char **
+get_tty(struct ctx *ctx)
+{
+ char buf[64], data[128];
+ int error, i, usbport, usbport0, list_size = 0;
+ char **list = NULL;
+ size_t len;
+ const char **p, *q;
+
+ /*
+ * Look for the network interface first
+ */
+ for (i = 0; ; i++) {
+ /* Check if we still have uhso nodes to check */
+ snprintf(buf, 64, SYSCTL_TEST, i);
+ len = 127;
+ error = sysctlbyname(buf, data, &len, NULL, 0);
+ data[len] = '\0';
+#ifdef DEBUG
+ fprintf(stderr, "sysctl %s returned(%d): %s\n",
+ buf, error, error == 0 ? data : "FAILED");
+#endif
+ if (error < 0 || strcasecmp(data, "uhso") != 0)
+ return NULL;
+
+ /* Check if this node contains the network interface we want */
+ snprintf(buf, 64, SYSCTL_NETIF, i);
+ len = 127;
+ error = sysctlbyname(buf, data, &len, NULL, 0);
+ data[len] = '\0';
+#ifdef DEBUG
+ fprintf(stderr, "sysctl %s returned(%d): %s\n",
+ buf, error, error == 0 ? data : "FAILED");
+#endif
+ if (error == 0 && strcasecmp(data, ctx->ifnam) == 0)
+ break;
+ }
+
+ /* Figure out the USB port location */
+ snprintf(buf, 64, SYSCTL_LOCATION, i);
+ len = 127;
+ error = sysctlbyname(buf, data, &len, NULL, 0);
+ data[len] = '\0';
+#ifdef DEBUG
+ fprintf(stderr, "sysctl %s returned(%d): %s\n",
+ buf, error, error == 0 ? data : "FAILED");
+#endif
+ if (error != 0)
+ return (NULL);
+
+ q = strstr(data, "port=");
+ if (q != NULL) {
+ error = sscanf(q, " port=%d", &usbport);
+ if (error != 1) {
+#ifdef DEBUG
+ fprintf(stderr, "failed to read usb port location from '%s'\n", data);
+#endif
+ return (NULL);
+ }
+ } else {
+#ifdef DEBUG
+ fprintf(stderr, "failed to parse location '%s'\n", data);
+#endif
+ return (NULL);
+ }
+#ifdef DEBUG
+ fprintf(stderr, "USB port location=%d\n", usbport);
+#endif
+
+ /*
+ * Now go through it all again but only look at those matching the
+ * usb port location we found.
+ */
+ for (i = 0; ; i++) {
+ snprintf(buf, 64, SYSCTL_LOCATION, i);
+ len = 127;
+ memset(&data, 0, sizeof(data));
+ error = sysctlbyname(buf, data, &len, NULL, 0);
+ if (error != 0)
+ break;
+ data[len] = '\0';
+ q = strstr(data, "port=");
+ if (q == NULL)
+ continue;
+ sscanf(q, " port=%d", &usbport0);
+ if (usbport != usbport0)
+ continue;
+
+ /* Try to add ports */
+ for (p = port_type_list; *p != NULL; p++) {
+ snprintf(buf, 64, SYSCTL_NAME_TTY, i, *p);
+ len = 127;
+ memset(&data, 0, sizeof(data));
+ error = sysctlbyname(buf, data, &len, NULL, 0);
+ data[len] = '\0';
+#ifdef DEBUG
+ fprintf(stderr, "sysctl %s returned(%d): %s\n",
+ buf, error, error == 0 ? data : "FAILED");
+#endif
+ if (error == 0) {
+ list = realloc(list, (list_size + 1) * sizeof(char *));
+ list[list_size] = malloc(strlen(data) + strlen(TTY_NAME));
+ sprintf(list[list_size], TTY_NAME, data);
+ list_size++;
+ }
+ }
+ }
+ list = realloc(list, (list_size + 1) * sizeof(char *));
+ list[list_size] = NULL;
+ return (list);
+}
+
+static int
+do_connect(struct ctx *ctx, const char *tty)
+{
+ int i, error, needcfg;
+ resp_arg ra;
+ struct termios t;
+ char **buf;
+
+#ifdef DEBUG
+ fprintf(stderr, "Attempting to open %s\n", tty);
+#endif
+
+ ctx->fd = open(tty, O_RDWR);
+ if (ctx->fd < 0) {
+#ifdef DEBUG
+ fprintf(stderr, "Failed to open %s\n", tty);
+#endif
+ return (-1);
+ }
+
+ tcgetattr(ctx->fd, &t);
+ t.c_oflag = 0;
+ t.c_iflag = 0;
+ t.c_cflag = CLOCAL | CREAD;
+ t.c_lflag = 0;
+ tcsetattr(ctx->fd, TCSAFLUSH, &t);
+
+ error = at_cmd(ctx, NULL, NULL, NULL, "AT\r\n");
+ if (error == -2) {
+ warnx("failed to read from device %s", tty);
+ return (-1);
+ }
+
+ /* Check for PIN */
+ error = at_cmd(ctx, "+CPIN: READY", NULL, NULL, "AT+CPIN?\r\n");
+ if (error != 0) {
+ ra.val[0].ptr = NULL;
+ ra.val[1].int32 = 0;
+ error = at_cmd(ctx, "+CME ERROR", saveresp, &ra, "AT+CPIN?\r\n");
+ if (ra.val[1].int32 > 0) {
+ char *p;
+
+ buf = ra.val[0].ptr;
+ if (strstr(buf[0], "+CME ERROR:") != NULL) {
+ buf[0] += 12;
+ errx(1, "%s", buf[0]);
+ }
+ freeresp(&ra);
+ } else
+ freeresp(&ra);
+
+ if (ctx->pin == NULL) {
+ errx(1, "device requires PIN");
+ }
+
+ error = at_cmd(ctx, NULL, NULL, NULL, "AT+CPIN=\"%s\"\r\n",
+ ctx->pin);
+ if (error != 0) {
+ errx(1, "wrong PIN");
+ }
+ }
+
+ /*
+ * Check if a PDP context has been configured and configure one
+ * if needed.
+ */
+ ra.val[0].ptr = NULL;
+ ra.val[1].int32 = 0;
+ error = at_cmd(ctx, "+CGDCONT", saveresp, &ra, "AT+CGDCONT?\r\n");
+ buf = ra.val[0].ptr;
+ needcfg = 1;
+ for (i = 0; i < ra.val[1].int32; i++) {
+ char apn[256];
+ int cid;
+ error = sscanf(buf[i], "+CGDCONT: %d,\"%*[^\"]\",\"%[^\"]\"",
+ &cid, apn);
+ if (error != 2) {
+ free(buf[i]);
+ continue;
+ }
+
+ if (cid == ctx->pdp_ctx) {
+ ctx->con_apn = strdup(apn);
+ if (ctx->pdp_apn != NULL) {
+ if (strcmp(apn, ctx->pdp_apn) == 0)
+ needcfg = 0;
+ }
+ else {
+ needcfg = 0;
+ }
+ }
+ free(buf[i]);
+ }
+ free(buf);
+
+ if (needcfg) {
+ if (ctx->pdp_apn == NULL)
+ errx(1, "device is not configured and no APN given");
+
+ error = at_cmd(ctx, NULL, NULL, NULL,
+ "AT+CGDCONT=%d,,\"%s\"\r\n", ctx->pdp_ctx, ctx->pdp_apn);
+ if (error != 0) {
+ errx(1, "failed to configure device");
+ }
+ ctx->con_apn = strdup(ctx->pdp_apn);
+ }
+
+ if (ctx->pdp_user != NULL || ctx->pdp_pwd != NULL) {
+ at_cmd(ctx, NULL, NULL, NULL,
+ "AT$QCPDPP=%d,1,\"%s\",\"%s\"\r\n", ctx->pdp_ctx,
+ (ctx->pdp_user != NULL) ? ctx->pdp_user : "",
+ (ctx->pdp_pwd != NULL) ? ctx->pdp_pwd : "");
+ }
+
+ error = at_cmd(ctx, NULL, NULL, NULL, "AT_OWANCALL=%d,0,0\r\n",
+ ctx->pdp_ctx);
+ if (error != 0)
+ return (-1);
+
+ at_cmd_async(ctx->fd, "AT+CGREG?\r\n");
+ at_cmd_async(ctx->fd, "AT+CREG?\r\n");
+
+ tmr_add(&timers, 1, 5, tmr_status, ctx);
+ return (0);
+}
+
+static void
+do_disconnect(struct ctx *ctx)
+{
+ struct sockaddr_in sin, mask;
+
+ /* Disconnect */
+ at_cmd(ctx, NULL, NULL, NULL, "AT_OWANCALL=%d,0,0\r\n",
+ ctx->pdp_ctx);
+ close(ctx->fd);
+
+ /* Remove ip-address from interface */
+ if (ctx->flags & IPASSIGNED) {
+ sin.sin_len = mask.sin_len = sizeof(struct sockaddr_in);
+ memset(&mask.sin_addr.s_addr, 0xff,
+ sizeof(mask.sin_addr.s_addr));
+ sin.sin_family = mask.sin_family = AF_INET;
+ memcpy(&sin.sin_addr.s_addr, &ctx->ip.s_addr,
+ sizeof(sin.sin_addr.s_addr));
+ ifaddr_del(ctx->ifnam, (struct sockaddr *)&sin,
+ (struct sockaddr *)&mask);
+
+ if_ifdown(ctx->ifnam);
+ ctx->flags &= ~IPASSIGNED;
+ }
+
+ /* Attempt to reset resolv.conf */
+ set_nameservers(ctx, ctx->resolv_path, 0);
+}
+
+static void
+daemonize(struct ctx *ctx)
+{
+ struct pidfh *pfh;
+ pid_t opid;
+
+ snprintf(ctx->pidfile, 127, PIDFILE, ctx->ifnam);
+
+ pfh = pidfile_open(ctx->pidfile, 0600, &opid);
+ if (pfh == NULL) {
+ warn("Cannot create pidfile %s", ctx->pidfile);
+ return;
+ }
+
+ if (daemon(0, 0) == -1) {
+ warn("Cannot daemonize");
+ pidfile_remove(pfh);
+ return;
+ }
+
+ pidfile_write(pfh);
+ ctx->pfh = pfh;
+ ctx->flags |= FLG_DAEMON;
+
+ snprintf(syslog_title, 63, "%s:%s", getprogname(), ctx->ifnam);
+ openlog(syslog_title, LOG_PID, LOG_USER);
+ syslog_open = 1;
+}
+
+static void
+send_disconnect(const char *ifnam)
+{
+ char pidfile[128];
+ FILE *fp;
+ pid_t pid;
+ int n;
+
+ snprintf(pidfile, 127, PIDFILE, ifnam);
+ fp = fopen(pidfile, "r");
+ if (fp == NULL) {
+ warn("Cannot open %s", pidfile);
+ return;
+ }
+
+ n = fscanf(fp, "%d", &pid);
+ fclose(fp);
+ if (n != 1) {
+ warnx("unable to read daemon pid");
+ return;
+ }
+#ifdef DEBUG
+ fprintf(stderr, "Sending SIGTERM to %d\n", pid);
+#endif
+ kill(pid, SIGTERM);
+}
+
+static void
+usage(const char *exec)
+{
+
+ printf("usage %s [-b] [-n] [-a apn] [-c cid] [-p pin] [-u username] "
+ "[-k password] [-r resolvpath] [-f tty] interface\n", exec);
+ printf("usage %s -d interface\n", exec);
+}
+
+enum {
+ MODE_CONN,
+ MODE_DISC
+};
+
+int
+main(int argc, char *argv[])
+{
+ int ch, error, mode;
+ const char *ifnam = NULL;
+ char *tty = NULL;
+ char **p, **tty_list;
+ fd_set set;
+ struct ctx ctx;
+ struct itimerval it;
+
+ TAILQ_INIT(&timers.head);
+ timers.res = 1;
+
+ ctx.pdp_ctx = 1;
+ ctx.pdp_apn = ctx.pdp_user = ctx.pdp_pwd = NULL;
+ ctx.pin = NULL;
+
+ ctx.con_status = 0;
+ ctx.con_apn = NULL;
+ ctx.con_oper = NULL;
+ ctx.con_net_stat = 0;
+ ctx.con_net_type = -1;
+ ctx.flags = 0;
+ ctx.resolv_path = RESOLV_PATH;
+ ctx.resolv = NULL;
+ ctx.ns = NULL;
+ ctx.dbm = 0;
+
+ mode = MODE_CONN;
+ ctx.flags |= FLG_DELAYED;
+
+ while ((ch = getopt(argc, argv, "?ha:p:c:u:k:r:f:dbn")) != -1) {
+ switch (ch) {
+ case 'a':
+ ctx.pdp_apn = argv[optind - 1];
+ break;
+ case 'c':
+ ctx.pdp_ctx = strtol(argv[optind - 1], NULL, 10);
+ if (ctx.pdp_ctx < 1) {
+ warnx("Invalid context ID, defaulting to 1");
+ ctx.pdp_ctx = 1;
+ }
+ break;
+ case 'p':
+ ctx.pin = argv[optind - 1];
+ break;
+ case 'u':
+ ctx.pdp_user = argv[optind - 1];
+ break;
+ case 'k':
+ ctx.pdp_pwd = argv[optind - 1];
+ break;
+ case 'r':
+ ctx.resolv_path = argv[optind - 1];
+ break;
+ case 'd':
+ mode = MODE_DISC;
+ break;
+ case 'b':
+ ctx.flags &= ~FLG_DELAYED;
+ break;
+ case 'n':
+ ctx.flags |= FLG_NODAEMON;
+ break;
+ case 'f':
+ tty = argv[optind - 1];
+ break;
+ case 'h':
+ case '?':
+ default:
+ usage(argv[0]);
+ exit(EXIT_SUCCESS);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ errx(1, "no interface given");
+
+ ifnam = argv[argc - 1];
+ ctx.ifnam = strdup(ifnam);
+
+ switch (mode) {
+ case MODE_DISC:
+ printf("Disconnecting %s\n", ifnam);
+ send_disconnect(ifnam);
+ exit(EXIT_SUCCESS);
+ default:
+ break;
+ }
+
+ signal(SIGHUP, sig_handle);
+ signal(SIGINT, sig_handle);
+ signal(SIGQUIT, sig_handle);
+ signal(SIGTERM, sig_handle);
+ signal(SIGALRM, sig_handle);
+
+ it.it_interval.tv_sec = 1;
+ it.it_interval.tv_usec = 0;
+ it.it_value.tv_sec = 1;
+ it.it_value.tv_usec = 0;
+ error = setitimer(ITIMER_REAL, &it, NULL);
+ if (error != 0)
+ errx(1, "setitimer");
+
+ tmr_add(&timers, 1, 5, &tmr_watchdog, &ctx);
+ watchdog_reset(&ctx, 15);
+
+ if (tty != NULL) {
+ error = do_connect(&ctx, tty);
+ if (error != 0)
+ errx(1, "Failed to open %s", tty);
+ }
+ else {
+ tty_list = get_tty(&ctx);
+ if (tty_list == NULL)
+ errx(1, "%s does not appear to be a uhso device", ifnam);
+#ifdef DEBUG
+ if (tty_list == NULL) {
+ fprintf(stderr, "get_tty returned empty list\n");
+ } else {
+ fprintf(stderr, "tty list:\n");
+ for (p = tty_list; *p != NULL; p++) {
+ fprintf(stderr, "\t %s\n", *p);
+ }
+ }
+#endif
+ for (p = tty_list; *p != NULL; p++) {
+ error = do_connect(&ctx, *p);
+ if (error == 0) {
+ tty = *p;
+ break;
+ }
+ }
+ if (*p == NULL)
+ errx(1, "Failed to obtain a control port, "
+ "try specifying one manually");
+ }
+
+ if (!(ctx.flags & FLG_DELAYED) && !(ctx.flags & FLG_NODAEMON))
+ daemonize(&ctx);
+
+
+ FD_ZERO(&set);
+ FD_SET(ctx.fd, &set);
+ for (;;) {
+
+ watchdog_disable(&ctx);
+ error = select(ctx.fd + 1, &set, NULL, NULL, NULL);
+ if (error <= 0) {
+ if (running && errno == EINTR)
+ continue;
+ if (ctx.flags & FLG_WDEXP) {
+ ctx.flags &= ~FLG_WDEXP;
+ watchdog_reset(&ctx, 5);
+ do_disconnect(&ctx);
+ watchdog_reset(&ctx, 15);
+ do_connect(&ctx, tty);
+ running = 1;
+ continue;
+ }
+
+ break;
+ }
+
+ if (FD_ISSET(ctx.fd, &set)) {
+ watchdog_reset(&ctx, 15);
+ error = at_async(&ctx, &ctx);
+ if (error != 0)
+ break;
+ }
+ FD_SET(ctx.fd, &set);
+
+ if (!(ctx.flags & FLG_DAEMON) && (ctx.flags & IPASSIGNED)) {
+ printf("Status: %s (%s)",
+ ctx.con_status ? "connected" : "disconnected",
+ network_access_type[ctx.con_net_type]);
+ if (ctx.dbm < 0)
+ printf(", signal: %d dBm", ctx.dbm);
+ printf("\t\t\t\r");
+ fflush(stdout);
+ }
+ }
+ if (!(ctx.flags & FLG_DAEMON) && (ctx.flags & IPASSIGNED))
+ printf("\n");
+
+ signal(SIGHUP, SIG_DFL);
+ signal(SIGINT, SIG_DFL);
+ signal(SIGQUIT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGALRM, SIG_IGN);
+
+ do_disconnect(&ctx);
+
+ if (ctx.flags & FLG_DAEMON) {
+ pidfile_remove(ctx.pfh);
+ if (syslog_open)
+ closelog();
+ }
+
+ return (0);
+}
diff --git a/usr.sbin/unbound/Makefile b/usr.sbin/unbound/Makefile
new file mode 100644
index 0000000..94cfdc3
--- /dev/null
+++ b/usr.sbin/unbound/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+SUBDIR= daemon anchor checkconf control
+SUBDIR+= local-setup
+SUBDIR_PARALLEL=
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/unbound/Makefile.inc b/usr.sbin/unbound/Makefile.inc
new file mode 100644
index 0000000..425d719
--- /dev/null
+++ b/usr.sbin/unbound/Makefile.inc
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+NO_WERROR= true
+
+.include "../Makefile.inc"
diff --git a/usr.sbin/unbound/anchor/Makefile b/usr.sbin/unbound/anchor/Makefile
new file mode 100644
index 0000000..64e01d2
--- /dev/null
+++ b/usr.sbin/unbound/anchor/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+# Vendor sources and generated files
+LDNSDIR= ${.CURDIR}/../../../contrib/ldns
+UNBOUNDDIR= ${.CURDIR}/../../../contrib/unbound
+EXPATDIR= ${.CURDIR}/../../../contrib/expat
+
+.PATH: ${UNBOUNDDIR} ${UNBOUNDDIR}/smallapp ${UNBOUNDDIR}/doc
+
+PROG= unbound-anchor
+SRCS= unbound-anchor.c
+CFLAGS= -I${UNBOUNDDIR} -I${LDNSDIR} -I${EXPATDIR}/lib
+LIBADD= unbound bsdxml ssl crypto pthread
+MAN= unbound-anchor.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/unbound/anchor/Makefile.depend b/usr.sbin/unbound/anchor/Makefile.depend
new file mode 100644
index 0000000..399b8c2
--- /dev/null
+++ b/usr.sbin/unbound/anchor/Makefile.depend
@@ -0,0 +1,24 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libexpat \
+ lib/libthr \
+ lib/libunbound \
+ secure/lib/libcrypto \
+ secure/lib/libssl \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/unbound/checkconf/Makefile b/usr.sbin/unbound/checkconf/Makefile
new file mode 100644
index 0000000..884465b
--- /dev/null
+++ b/usr.sbin/unbound/checkconf/Makefile
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+# Vendor sources and generated files
+LDNSDIR= ${.CURDIR}/../../../contrib/ldns
+UNBOUNDDIR= ${.CURDIR}/../../../contrib/unbound
+
+.PATH: ${UNBOUNDDIR} ${UNBOUNDDIR}/smallapp ${UNBOUNDDIR}/doc
+
+PROG= unbound-checkconf
+SRCS= unbound-checkconf.c worker_cb.c
+CFLAGS= -I${UNBOUNDDIR} -I${LDNSDIR}
+LIBADD= unbound pthread
+MAN= unbound-checkconf.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/unbound/checkconf/Makefile.depend b/usr.sbin/unbound/checkconf/Makefile.depend
new file mode 100644
index 0000000..36ffed0
--- /dev/null
+++ b/usr.sbin/unbound/checkconf/Makefile.depend
@@ -0,0 +1,23 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libthr \
+ lib/libunbound \
+ secure/lib/libcrypto \
+ secure/lib/libssl \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/unbound/control/Makefile b/usr.sbin/unbound/control/Makefile
new file mode 100644
index 0000000..1614127
--- /dev/null
+++ b/usr.sbin/unbound/control/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+# Vendor sources and generated files
+LDNSDIR= ${.CURDIR}/../../../contrib/ldns
+UNBOUNDDIR= ${.CURDIR}/../../../contrib/unbound
+
+.PATH: ${UNBOUNDDIR} ${UNBOUNDDIR}/smallapp ${UNBOUNDDIR}/doc
+
+PROG= unbound-control
+SCRIPTS= unbound-control-setup.sh
+SRCS= unbound-control.c worker_cb.c
+CFLAGS= -I${UNBOUNDDIR} -I${LDNSDIR}
+LIBADD= unbound crypto ssl pthread
+MAN= unbound-control.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/unbound/control/Makefile.depend b/usr.sbin/unbound/control/Makefile.depend
new file mode 100644
index 0000000..36ffed0
--- /dev/null
+++ b/usr.sbin/unbound/control/Makefile.depend
@@ -0,0 +1,23 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libthr \
+ lib/libunbound \
+ secure/lib/libcrypto \
+ secure/lib/libssl \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/unbound/daemon/Makefile b/usr.sbin/unbound/daemon/Makefile
new file mode 100644
index 0000000..f90e06e
--- /dev/null
+++ b/usr.sbin/unbound/daemon/Makefile
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+# Vendor sources and generated files
+LDNSDIR= ${.CURDIR}/../../../contrib/ldns
+UNBOUNDDIR= ${.CURDIR}/../../../contrib/unbound
+
+.PATH: ${UNBOUNDDIR} ${UNBOUNDDIR}/daemon ${UNBOUNDDIR}/doc
+
+PROG= unbound
+SRCS= acl_list.c cachedump.c daemon.c remote.c stats.c unbound.c worker.c
+CFLAGS= -I${UNBOUNDDIR} -I${LDNSDIR}
+LIBADD= unbound util ssl crypto pthread
+MAN= unbound.8 unbound.conf.5
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/unbound/daemon/Makefile.depend b/usr.sbin/unbound/daemon/Makefile.depend
new file mode 100644
index 0000000..06c0c16
--- /dev/null
+++ b/usr.sbin/unbound/daemon/Makefile.depend
@@ -0,0 +1,24 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libthr \
+ lib/libunbound \
+ lib/libutil \
+ secure/lib/libcrypto \
+ secure/lib/libssl \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/unbound/local-setup/Makefile b/usr.sbin/unbound/local-setup/Makefile
new file mode 100644
index 0000000..7a1bc0441
--- /dev/null
+++ b/usr.sbin/unbound/local-setup/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+SCRIPTS= local-unbound-setup.sh
+MAN= #
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/unbound/local-setup/Makefile.depend b/usr.sbin/unbound/local-setup/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/unbound/local-setup/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/unbound/local-setup/local-unbound-setup.sh b/usr.sbin/unbound/local-setup/local-unbound-setup.sh
new file mode 100755
index 0000000..5df4760
--- /dev/null
+++ b/usr.sbin/unbound/local-setup/local-unbound-setup.sh
@@ -0,0 +1,462 @@
+#!/bin/sh
+#-
+# Copyright (c) 2013 Dag-Erling Smørgrav
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+
+#
+# Configuration variables
+#
+user=""
+unbound_conf=""
+forward_conf=""
+lanzones_conf=""
+control_conf=""
+control_socket=""
+workdir=""
+confdir=""
+chrootdir=""
+anchor=""
+pidfile=""
+resolv_conf=""
+resolvconf_conf=""
+service=""
+start_unbound=""
+forwarders=""
+
+#
+# Global variables
+#
+self=$(basename $(realpath "$0"))
+bkext=$(date "+%Y%m%d.%H%M%S")
+
+#
+# Set default values for unset configuration variables.
+#
+set_defaults() {
+ : ${user:=unbound}
+ : ${workdir:=/var/unbound}
+ : ${confdir:=${workdir}/conf.d}
+ : ${unbound_conf:=${workdir}/unbound.conf}
+ : ${forward_conf:=${workdir}/forward.conf}
+ : ${lanzones_conf:=${workdir}/lan-zones.conf}
+ : ${control_conf:=${workdir}/control.conf}
+ : ${control_socket:=/var/run/local_unbound.ctl}
+ : ${anchor:=${workdir}/root.key}
+ : ${pidfile:=/var/run/local_unbound.pid}
+ : ${resolv_conf:=/etc/resolv.conf}
+ : ${resolvconf_conf:=/etc/resolvconf.conf}
+ : ${service:=local_unbound}
+ : ${start_unbound:=yes}
+}
+
+#
+# Verify that the configuration files are inside the working
+# directory, and if so, set the chroot directory accordingly.
+#
+set_chrootdir() {
+ chrootdir="${workdir}"
+ for file in "${unbound_conf}" "${forward_conf}" \
+ "${lanzones_conf}" "${control_conf}" "${anchor}" ; do
+ if [ "${file#${workdir%/}/}" = "${file}" ] ; then
+ echo "warning: ${file} is outside ${workdir}" >&2
+ chrootdir=""
+ fi
+ done
+ if [ -z "${chrootdir}" ] ; then
+ echo "warning: disabling chroot" >&2
+ fi
+}
+
+#
+# Scan through /etc/resolv.conf looking for uncommented nameserver
+# lines that don't point to localhost and return their values.
+#
+get_nameservers() {
+ while read line ; do
+ local bareline=${line%%\#*}
+ local key=${bareline%% *}
+ local value=${bareline#* }
+ case ${key} in
+ nameserver)
+ case ${value} in
+ 127.0.0.1|::1|localhost|localhost.*)
+ ;;
+ *)
+ echo "${value}"
+ ;;
+ esac
+ ;;
+ esac
+ done
+}
+
+#
+# Scan through /etc/resolv.conf looking for uncommented nameserver
+# lines. Comment out any that don't point to localhost. Finally,
+# append a nameserver line that points to localhost, if there wasn't
+# one already, and enable the edns0 option.
+#
+gen_resolv_conf() {
+ local localhost=no
+ local edns0=no
+ while read line ; do
+ local bareline=${line%%\#*}
+ local key=${bareline%% *}
+ local value=${bareline#* }
+ case ${key} in
+ nameserver)
+ case ${value} in
+ 127.0.0.1|::1|localhost|localhost.*)
+ localhost=yes
+ ;;
+ *)
+ echo -n "# "
+ ;;
+ esac
+ ;;
+ options)
+ case ${value} in
+ *edns0*)
+ edns0=yes
+ ;;
+ esac
+ ;;
+ esac
+ echo "${line}"
+ done
+ if [ "${localhost}" = "no" ] ; then
+ echo "nameserver 127.0.0.1"
+ fi
+ if [ "${edns0}" = "no" ] ; then
+ echo "options edns0"
+ fi
+}
+
+#
+# Boilerplate
+#
+do_not_edit() {
+ echo "# This file was generated by $self."
+ echo "# Modifications will be overwritten."
+}
+
+#
+# Generate resolvconf.conf so it updates forward.conf in addition to
+# resolv.conf. Note "in addition to" rather than "instead of",
+# because we still want it to update the domain name and search path
+# if they change. Setting name_servers to "127.0.0.1" ensures that
+# the libc resolver will try unbound first.
+#
+gen_resolvconf_conf() {
+ local style="$1"
+ do_not_edit
+ echo "resolv_conf=\"/dev/null\" # prevent updating ${resolv_conf}"
+ if [ "${style}" = "dynamic" ] ; then
+ echo "unbound_conf=\"${forward_conf}\""
+ echo "unbound_pid=\"${pidfile}\""
+ echo "unbound_service=\"${service}\""
+ # resolvconf(8) likes to restart rather than reload
+ echo "unbound_restart=\"service ${service} reload\""
+ else
+ echo "# Static DNS configuration"
+ fi
+}
+
+#
+# Generate forward.conf
+#
+gen_forward_conf() {
+ do_not_edit
+ echo "forward-zone:"
+ echo " name: ."
+ for forwarder ; do
+ if expr "${forwarder}" : "^[0-9A-Fa-f:.]\{1,\}$" >/dev/null ; then
+ echo " forward-addr: ${forwarder}"
+ else
+ echo " forward-host: ${forwarder}"
+ fi
+ done
+}
+
+#
+# Generate lan-zones.conf
+#
+gen_lanzones_conf() {
+ do_not_edit
+ echo "server:"
+ echo " # Unblock reverse lookups for LAN addresses"
+ echo " unblock-lan-zones: yes"
+ echo " domain-insecure: 10.in-addr.arpa."
+ echo " domain-insecure: 127.in-addr.arpa."
+ echo " domain-insecure: 16.172.in-addr.arpa."
+ echo " domain-insecure: 17.172.in-addr.arpa."
+ echo " domain-insecure: 18.172.in-addr.arpa."
+ echo " domain-insecure: 19.172.in-addr.arpa."
+ echo " domain-insecure: 20.172.in-addr.arpa."
+ echo " domain-insecure: 21.172.in-addr.arpa."
+ echo " domain-insecure: 22.172.in-addr.arpa."
+ echo " domain-insecure: 23.172.in-addr.arpa."
+ echo " domain-insecure: 24.172.in-addr.arpa."
+ echo " domain-insecure: 25.172.in-addr.arpa."
+ echo " domain-insecure: 26.172.in-addr.arpa."
+ echo " domain-insecure: 27.172.in-addr.arpa."
+ echo " domain-insecure: 28.172.in-addr.arpa."
+ echo " domain-insecure: 29.172.in-addr.arpa."
+ echo " domain-insecure: 30.172.in-addr.arpa."
+ echo " domain-insecure: 31.172.in-addr.arpa."
+ echo " domain-insecure: 168.192.in-addr.arpa."
+ echo " domain-insecure: 254.169.in-addr.arpa."
+ echo " domain-insecure: d.f.ip6.arpa."
+ echo " domain-insecure: 8.e.ip6.arpa."
+ echo " domain-insecure: 9.e.ip6.arpa."
+ echo " domain-insecure: a.e.ip6.arpa."
+ echo " domain-insecure: b.e.ip6.arpa."
+}
+
+#
+# Generate control.conf
+#
+gen_control_conf() {
+ do_not_edit
+ echo "remote-control:"
+ echo " control-enable: yes"
+ echo " control-interface: ${control_socket}"
+ echo " control-use-cert: no"
+}
+
+#
+# Generate unbound.conf
+#
+gen_unbound_conf() {
+ do_not_edit
+ echo "server:"
+ echo " username: ${user}"
+ echo " directory: ${workdir}"
+ echo " chroot: ${chrootdir}"
+ echo " pidfile: ${pidfile}"
+ echo " auto-trust-anchor-file: ${anchor}"
+ echo ""
+ if [ -f "${forward_conf}" ] ; then
+ echo "include: ${forward_conf}"
+ fi
+ if [ -f "${lanzones_conf}" ] ; then
+ echo "include: ${lanzones_conf}"
+ fi
+ if [ -f "${control_conf}" ] ; then
+ echo "include: ${control_conf}"
+ fi
+ if [ -d "${confdir}" ] ; then
+ echo "include: ${confdir}/*.conf"
+ fi
+}
+
+#
+# Replace one file with another, making a backup copy of the first,
+# but only if the new file is different from the old.
+#
+replace() {
+ local file="$1"
+ local newfile="$2"
+ if [ ! -f "${file}" ] ; then
+ echo "${file} created"
+ mv "${newfile}" "${file}"
+ elif ! cmp -s "${file}" "${newfile}" ; then
+ local oldfile="${file}.${bkext}"
+ echo "original ${file} saved as ${oldfile}"
+ mv "${file}" "${oldfile}"
+ mv "${newfile}" "${file}"
+ else
+ echo "${file} not modified"
+ rm "${newfile}"
+ fi
+}
+
+#
+# Print usage message and exit
+#
+usage() {
+ exec >&2
+ echo "usage: $self [options] [forwarder ...]"
+ echo "options:"
+ echo " -n do not start unbound"
+ echo " -a path full path to trust anchor file"
+ echo " -C path full path to additional configuration directory"
+ echo " -c path full path to unbound configuration file"
+ echo " -f path full path to forwarding configuration"
+ echo " -O path full path to remote control socket"
+ echo " -o path full path to remote control configuration"
+ echo " -p path full path to pid file"
+ echo " -R path full path to resolvconf.conf"
+ echo " -r path full path to resolv.conf"
+ echo " -s service name of unbound service"
+ echo " -u user user to run unbound as"
+ echo " -w path full path to working directory"
+ exit 1
+}
+
+#
+# Main
+#
+main() {
+ umask 022
+
+ #
+ # Parse and validate command-line options
+ #
+ while getopts "a:C:c:f:no:p:R:r:s:u:w:" option ; do
+ case $option in
+ a)
+ anchor="$OPTARG"
+ ;;
+ C)
+ confdir="$OPTARG"
+ ;;
+ c)
+ unbound_conf="$OPTARG"
+ ;;
+ f)
+ forward_conf="$OPTARG"
+ ;;
+ n)
+ start_unbound="no"
+ ;;
+ O)
+ control_socket="$OPTARG"
+ ;;
+ o)
+ control_conf="$OPTARG"
+ ;;
+ p)
+ pidfile="$OPTARG"
+ ;;
+ R)
+ resolvconf_conf="$OPTARG"
+ ;;
+ r)
+ resolv_conf="$OPTARG"
+ ;;
+ s)
+ service="$OPTARG"
+ ;;
+ u)
+ user="$OPTARG"
+ ;;
+ w)
+ workdir="$OPTARG"
+ ;;
+ *)
+ usage
+ ;;
+ esac
+ done
+ shift $((OPTIND-1))
+ set_defaults
+
+ #
+ # Get the list of forwarders, either from the command line or
+ # from resolv.conf.
+ #
+ forwarders="$@"
+ if [ -z "$forwarders" ] ; then
+ echo "Extracting forwarders from ${resolv_conf}."
+ forwarders=$(get_nameservers <"${resolv_conf}")
+ style=dynamic
+ else
+ style=static
+ fi
+
+ #
+ # Generate forward.conf.
+ #
+ if [ -z "${forwarders}" ] ; then
+ echo -n "No forwarders found in ${resolv_conf##*/}, "
+ if [ -f "${forward_conf}" ] ; then
+ echo "using existing ${forward_conf##*/}."
+ else
+ echo "unbound will recurse."
+ fi
+ else
+ local tmp_forward_conf=$(mktemp -u "${forward_conf}.XXXXX")
+ gen_forward_conf ${forwarders} | unexpand >"${tmp_forward_conf}"
+ replace "${forward_conf}" "${tmp_forward_conf}"
+ fi
+
+ #
+ # Generate lan-zones.conf.
+ #
+ local tmp_lanzones_conf=$(mktemp -u "${lanzones_conf}.XXXXX")
+ gen_lanzones_conf | unexpand >"${tmp_lanzones_conf}"
+ replace "${lanzones_conf}" "${tmp_lanzones_conf}"
+
+ #
+ # Generate control.conf.
+ #
+ local tmp_control_conf=$(mktemp -u "${control_conf}.XXXXX")
+ gen_control_conf | unexpand >"${tmp_control_conf}"
+ replace "${control_conf}" "${tmp_control_conf}"
+
+ #
+ # Generate unbound.conf.
+ #
+ local tmp_unbound_conf=$(mktemp -u "${unbound_conf}.XXXXX")
+ set_chrootdir
+ gen_unbound_conf | unexpand >"${tmp_unbound_conf}"
+ replace "${unbound_conf}" "${tmp_unbound_conf}"
+
+ #
+ # Start unbound, unless requested not to. Stop immediately if
+ # it is not enabled so we don't end up with a resolv.conf that
+ # points into nothingness. We could "onestart" it, but it
+ # wouldn't stick.
+ #
+ if [ "${start_unbound}" = "no" ] ; then
+ # skip
+ elif ! service "${service}" enabled ; then
+ echo "Please enable $service in rc.conf(5) and try again."
+ return 1
+ elif ! service "${service}" restart ; then
+ echo "Failed to start $service."
+ return 1
+ fi
+
+ #
+ # Rewrite resolvconf.conf so resolvconf updates forward.conf
+ # instead of resolv.conf.
+ #
+ local tmp_resolvconf_conf=$(mktemp -u "${resolvconf_conf}.XXXXX")
+ gen_resolvconf_conf "${style}" | unexpand >"${tmp_resolvconf_conf}"
+ replace "${resolvconf_conf}" "${tmp_resolvconf_conf}"
+
+ #
+ # Finally, rewrite resolv.conf.
+ #
+ local tmp_resolv_conf=$(mktemp -u "${resolv_conf}.XXXXX")
+ gen_resolv_conf <"${resolv_conf}" | unexpand >"${tmp_resolv_conf}"
+ replace "${resolv_conf}" "${tmp_resolv_conf}"
+}
+
+main "$@"
diff --git a/usr.sbin/usbconfig/Makefile b/usr.sbin/usbconfig/Makefile
new file mode 100644
index 0000000..bfc4b63
--- /dev/null
+++ b/usr.sbin/usbconfig/Makefile
@@ -0,0 +1,9 @@
+#
+# $FreeBSD$
+#
+PROG= usbconfig
+MAN= usbconfig.8
+SRCS= usbconfig.c dump.c
+LIBADD= usb
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/usbconfig/Makefile.depend b/usr.sbin/usbconfig/Makefile.depend
new file mode 100644
index 0000000..3ddb39c
--- /dev/null
+++ b/usr.sbin/usbconfig/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libthr \
+ lib/libusb \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/usbconfig/dump.c b/usr.sbin/usbconfig/dump.c
new file mode 100644
index 0000000..df5cde0
--- /dev/null
+++ b/usr.sbin/usbconfig/dump.c
@@ -0,0 +1,483 @@
+/* $FreeBSD$ */
+/*-
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <err.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+#include <ctype.h>
+
+#include <libusb20.h>
+#include <libusb20_desc.h>
+
+#include "dump.h"
+
+#define DUMP0(n,type,field,...) dump_field(pdev, " ", #field, n->field);
+#define DUMP1(n,type,field,...) dump_field(pdev, " ", #field, n->field);
+#define DUMP2(n,type,field,...) dump_field(pdev, " ", #field, n->field);
+#define DUMP3(n,type,field,...) dump_field(pdev, " ", #field, n->field);
+
+const char *
+dump_mode(uint8_t value)
+{
+ if (value == LIBUSB20_MODE_HOST)
+ return ("HOST");
+ return ("DEVICE");
+}
+
+const char *
+dump_speed(uint8_t value)
+{
+ ; /* style fix */
+ switch (value) {
+ case LIBUSB20_SPEED_LOW:
+ return ("LOW (1.5Mbps)");
+ case LIBUSB20_SPEED_FULL:
+ return ("FULL (12Mbps)");
+ case LIBUSB20_SPEED_HIGH:
+ return ("HIGH (480Mbps)");
+ case LIBUSB20_SPEED_VARIABLE:
+ return ("VARIABLE (52-480Mbps)");
+ case LIBUSB20_SPEED_SUPER:
+ return ("SUPER (5.0Gbps)");
+ default:
+ break;
+ }
+ return ("UNKNOWN ()");
+}
+
+const char *
+dump_power_mode(uint8_t value)
+{
+ ; /* style fix */
+ switch (value) {
+ case LIBUSB20_POWER_OFF:
+ return ("OFF");
+ case LIBUSB20_POWER_ON:
+ return ("ON");
+ case LIBUSB20_POWER_SAVE:
+ return ("SAVE");
+ case LIBUSB20_POWER_SUSPEND:
+ return ("SUSPEND");
+ case LIBUSB20_POWER_RESUME:
+ return ("RESUME");
+ default:
+ return ("UNKNOWN");
+ }
+}
+
+static void
+dump_field(struct libusb20_device *pdev, const char *plevel,
+ const char *field, uint32_t value)
+{
+ uint8_t temp_string[256];
+
+ printf("%s%s = 0x%04x ", plevel, field, value);
+
+ if (strlen(plevel) == 8) {
+ /* Endpoint Descriptor */
+
+ if (strcmp(field, "bEndpointAddress") == 0) {
+ if (value & 0x80)
+ printf(" <IN>\n");
+ else
+ printf(" <OUT>\n");
+ return;
+ }
+ if (strcmp(field, "bmAttributes") == 0) {
+ switch (value & 0x03) {
+ case 0:
+ printf(" <CONTROL>\n");
+ break;
+ case 1:
+ switch (value & 0x0C) {
+ case 0x00:
+ printf(" <ISOCHRONOUS>\n");
+ break;
+ case 0x04:
+ printf(" <ASYNC-ISOCHRONOUS>\n");
+ break;
+ case 0x08:
+ printf(" <ADAPT-ISOCHRONOUS>\n");
+ break;
+ default:
+ printf(" <SYNC-ISOCHRONOUS>\n");
+ break;
+ }
+ break;
+ case 2:
+ printf(" <BULK>\n");
+ break;
+ default:
+ printf(" <INTERRUPT>\n");
+ break;
+ }
+ return;
+ }
+ }
+ if ((field[0] == 'i') && (field[1] != 'd')) {
+ /* Indirect String Descriptor */
+ if (value == 0) {
+ printf(" <no string>\n");
+ return;
+ }
+ if (libusb20_dev_req_string_simple_sync(pdev, value,
+ temp_string, sizeof(temp_string))) {
+ printf(" <retrieving string failed>\n");
+ return;
+ }
+ printf(" <%s>\n", temp_string);
+ return;
+ }
+ if (strlen(plevel) == 2 || strlen(plevel) == 6) {
+
+ /* Device and Interface Descriptor class codes */
+
+ if (strcmp(field, "bInterfaceClass") == 0 ||
+ strcmp(field, "bDeviceClass") == 0) {
+
+ switch (value) {
+ case 0x00:
+ printf(" <Probed by interface class>\n");
+ break;
+ case 0x01:
+ printf(" <Audio device>\n");
+ break;
+ case 0x02:
+ printf(" <Communication device>\n");
+ break;
+ case 0x03:
+ printf(" <HID device>\n");
+ break;
+ case 0x05:
+ printf(" <Physical device>\n");
+ break;
+ case 0x06:
+ printf(" <Still imaging>\n");
+ break;
+ case 0x07:
+ printf(" <Printer device>\n");
+ break;
+ case 0x08:
+ printf(" <Mass storage>\n");
+ break;
+ case 0x09:
+ printf(" <HUB>\n");
+ break;
+ case 0x0A:
+ printf(" <CDC-data>\n");
+ break;
+ case 0x0B:
+ printf(" <Smart card>\n");
+ break;
+ case 0x0D:
+ printf(" <Content security>\n");
+ break;
+ case 0x0E:
+ printf(" <Video device>\n");
+ break;
+ case 0x0F:
+ printf(" <Personal healthcare>\n");
+ break;
+ case 0x10:
+ printf(" <Audio and video device>\n");
+ break;
+ case 0x11:
+ printf(" <Billboard device>\n");
+ break;
+ case 0xDC:
+ printf(" <Diagnostic device>\n");
+ break;
+ case 0xE0:
+ printf(" <Wireless controller>\n");
+ break;
+ case 0xEF:
+ printf(" <Miscellaneous device>\n");
+ break;
+ case 0xFE:
+ printf(" <Application specific>\n");
+ break;
+ case 0xFF:
+ printf(" <Vendor specific>\n");
+ break;
+ default:
+ printf(" <Unknown>\n");
+ break;
+ }
+ return;
+ }
+ }
+ /* No additional information */
+ printf("\n");
+}
+
+static void
+dump_extra(struct libusb20_me_struct *str, const char *plevel)
+{
+ const uint8_t *ptr;
+ uint8_t x;
+
+ ptr = NULL;
+
+ while ((ptr = libusb20_desc_foreach(str, ptr))) {
+ printf("\n" "%sAdditional Descriptor\n\n", plevel);
+ printf("%sbLength = 0x%02x\n", plevel, ptr[0]);
+ printf("%sbDescriptorType = 0x%02x\n", plevel, ptr[1]);
+ if (ptr[0] > 1)
+ printf("%sbDescriptorSubType = 0x%02x\n",
+ plevel, ptr[2]);
+ printf("%s RAW dump: ", plevel);
+ for (x = 0; x != ptr[0]; x++) {
+ if ((x % 8) == 0) {
+ printf("\n%s 0x%02x | ", plevel, x);
+ }
+ printf("0x%02x%s", ptr[x],
+ (x != (ptr[0] - 1)) ? ", " : (x % 8) ? "\n" : "");
+ }
+ printf("\n");
+ }
+ return;
+}
+
+static void
+dump_endpoint(struct libusb20_device *pdev,
+ struct libusb20_endpoint *ep)
+{
+ struct LIBUSB20_ENDPOINT_DESC_DECODED *edesc;
+
+ edesc = &ep->desc;
+ LIBUSB20_ENDPOINT_DESC(DUMP3, edesc);
+ dump_extra(&ep->extra, " " " " " ");
+ return;
+}
+
+static void
+dump_iface(struct libusb20_device *pdev,
+ struct libusb20_interface *iface)
+{
+ struct LIBUSB20_INTERFACE_DESC_DECODED *idesc;
+ uint8_t z;
+
+ idesc = &iface->desc;
+ LIBUSB20_INTERFACE_DESC(DUMP2, idesc);
+ dump_extra(&iface->extra, " " " " " ");
+
+ for (z = 0; z != iface->num_endpoints; z++) {
+ printf("\n Endpoint %u\n", z);
+ dump_endpoint(pdev, iface->endpoints + z);
+ }
+ return;
+}
+
+void
+dump_device_info(struct libusb20_device *pdev, uint8_t show_ifdrv)
+{
+ char buf[128];
+ uint8_t n;
+ unsigned int usage;
+
+ usage = libusb20_dev_get_power_usage(pdev);
+
+ printf("%s, cfg=%u md=%s spd=%s pwr=%s (%umA)\n",
+ libusb20_dev_get_desc(pdev),
+ libusb20_dev_get_config_index(pdev),
+ dump_mode(libusb20_dev_get_mode(pdev)),
+ dump_speed(libusb20_dev_get_speed(pdev)),
+ dump_power_mode(libusb20_dev_get_power_mode(pdev)),
+ usage);
+
+ if (!show_ifdrv)
+ return;
+
+ for (n = 0; n != 255; n++) {
+ if (libusb20_dev_get_iface_desc(pdev, n, buf, sizeof(buf)))
+ break;
+ if (buf[0] == 0)
+ continue;
+ printf("ugen%u.%u.%u: %s\n",
+ libusb20_dev_get_bus_number(pdev),
+ libusb20_dev_get_address(pdev), n, buf);
+ }
+}
+
+void
+dump_be_quirk_names(struct libusb20_backend *pbe)
+{
+ struct libusb20_quirk q;
+ uint16_t x;
+ int error;
+
+ memset(&q, 0, sizeof(q));
+
+ printf("\nDumping list of supported quirks:\n\n");
+
+ for (x = 0; x != 0xFFFF; x++) {
+
+ error = libusb20_be_get_quirk_name(pbe, x, &q);
+ if (error) {
+ if (x == 0) {
+ printf("No quirk names - maybe the USB quirk "
+ "module has not been loaded.\n");
+ }
+ break;
+ }
+ if (strcmp(q.quirkname, "UQ_NONE"))
+ printf("%s\n", q.quirkname);
+ }
+ printf("\n");
+ return;
+}
+
+void
+dump_be_dev_quirks(struct libusb20_backend *pbe)
+{
+ struct libusb20_quirk q;
+ uint16_t x;
+ int error;
+
+ memset(&q, 0, sizeof(q));
+
+ printf("\nDumping current device quirks:\n\n");
+
+ for (x = 0; x != 0xFFFF; x++) {
+
+ error = libusb20_be_get_dev_quirk(pbe, x, &q);
+ if (error) {
+ if (x == 0) {
+ printf("No device quirks - maybe the USB quirk "
+ "module has not been loaded.\n");
+ }
+ break;
+ }
+ if (strcmp(q.quirkname, "UQ_NONE")) {
+ printf("VID=0x%04x PID=0x%04x REVLO=0x%04x "
+ "REVHI=0x%04x QUIRK=%s\n",
+ q.vid, q.pid, q.bcdDeviceLow,
+ q.bcdDeviceHigh, q.quirkname);
+ }
+ }
+ printf("\n");
+ return;
+}
+
+void
+dump_device_desc(struct libusb20_device *pdev)
+{
+ struct LIBUSB20_DEVICE_DESC_DECODED *ddesc;
+
+ ddesc = libusb20_dev_get_device_desc(pdev);
+ LIBUSB20_DEVICE_DESC(DUMP0, ddesc);
+ return;
+}
+
+void
+dump_config(struct libusb20_device *pdev, uint8_t all_cfg)
+{
+ struct LIBUSB20_CONFIG_DESC_DECODED *cdesc;
+ struct LIBUSB20_DEVICE_DESC_DECODED *ddesc;
+ struct libusb20_config *pcfg = NULL;
+ uint8_t cfg_index;
+ uint8_t cfg_index_end;
+ uint8_t x;
+ uint8_t y;
+
+ ddesc = libusb20_dev_get_device_desc(pdev);
+
+ if (all_cfg) {
+ cfg_index = 0;
+ cfg_index_end = ddesc->bNumConfigurations;
+ } else {
+ cfg_index = libusb20_dev_get_config_index(pdev);
+ cfg_index_end = cfg_index + 1;
+ }
+
+ for (; cfg_index != cfg_index_end; cfg_index++) {
+
+ pcfg = libusb20_dev_alloc_config(pdev, cfg_index);
+ if (!pcfg) {
+ continue;
+ }
+ printf("\n Configuration index %u\n\n", cfg_index);
+ cdesc = &(pcfg->desc);
+ LIBUSB20_CONFIG_DESC(DUMP1, cdesc);
+ dump_extra(&(pcfg->extra), " " " ");
+
+ for (x = 0; x != pcfg->num_interface; x++) {
+ printf("\n Interface %u\n", x);
+ dump_iface(pdev, pcfg->interface + x);
+ printf("\n");
+ for (y = 0; y != (pcfg->interface + x)->num_altsetting; y++) {
+ printf("\n Interface %u Alt %u\n", x, y + 1);
+ dump_iface(pdev,
+ (pcfg->interface + x)->altsetting + y);
+ printf("\n");
+ }
+ }
+ printf("\n");
+ free(pcfg);
+ }
+ return;
+}
+
+void
+dump_string_by_index(struct libusb20_device *pdev, uint8_t str_index)
+{
+ char *pbuf;
+ uint8_t n;
+ uint8_t len;
+
+ pbuf = malloc(256);
+ if (pbuf == NULL)
+ err(1, "out of memory");
+
+ if (str_index == 0) {
+ /* language table */
+ if (libusb20_dev_req_string_sync(pdev,
+ str_index, 0, pbuf, 256)) {
+ printf("STRING_0x%02x = <read error>\n", str_index);
+ } else {
+ printf("STRING_0x%02x = ", str_index);
+ len = (uint8_t)pbuf[0];
+ for (n = 0; n != len; n++) {
+ printf("0x%02x%s", (uint8_t)pbuf[n],
+ (n != (len - 1)) ? ", " : "");
+ }
+ printf("\n");
+ }
+ } else {
+ /* ordinary string */
+ if (libusb20_dev_req_string_simple_sync(pdev,
+ str_index, pbuf, 256)) {
+ printf("STRING_0x%02x = <read error>\n", str_index);
+ } else {
+ printf("STRING_0x%02x = <%s>\n", str_index, pbuf);
+ }
+ }
+ free(pbuf);
+}
diff --git a/usr.sbin/usbconfig/dump.h b/usr.sbin/usbconfig/dump.h
new file mode 100644
index 0000000..581684a
--- /dev/null
+++ b/usr.sbin/usbconfig/dump.h
@@ -0,0 +1,40 @@
+/* $FreeBSD$ */
+/*-
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 _DUMP_H_
+#define _DUMP_H_
+
+const char *dump_mode(uint8_t value);
+const char *dump_speed(uint8_t value);
+const char *dump_power_mode(uint8_t value);
+void dump_string_by_index(struct libusb20_device *pdev, uint8_t index);
+void dump_device_info(struct libusb20_device *pdev, uint8_t show_drv);
+void dump_be_quirk_names(struct libusb20_backend *pbe);
+void dump_be_dev_quirks(struct libusb20_backend *pbe);
+void dump_device_desc(struct libusb20_device *pdev);
+void dump_config(struct libusb20_device *pdev, uint8_t all_cfg);
+
+#endif /* _DUMP_H_ */
diff --git a/usr.sbin/usbconfig/usbconfig.8 b/usr.sbin/usbconfig/usbconfig.8
new file mode 100644
index 0000000..ae69737
--- /dev/null
+++ b/usr.sbin/usbconfig/usbconfig.8
@@ -0,0 +1,100 @@
+.\" $FreeBSD$
+.\"
+.\" Copyright (c) 2008-2010 Hans Petter Selasky. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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.
+.\"
+.Dd January 6, 2010
+.Dt USBCONFIG 8
+.Os
+.Sh NAME
+.Nm usbconfig
+.Nd configure the USB subsystem
+.Sh SYNOPSIS
+.Nm
+.Op Fl u Ar unit
+.Op Fl a Ar addr
+.Op cmds...
+.Nm
+.Op Fl d Ar [ugen]<unit>.<addr>
+.Op cmds...
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to configure and dump information about the USB subsystem.
+.Pp
+The options are as follows:
+.Bl -tag -width " "
+.It Fl u Ar unit
+Limit device range to USB devices connected to the given USBUS unit.
+.It Fl a Ar addr
+Limit device range to the given USB device index.
+Should only be used in conjunction with the unit argument.
+.It Fl d Ar [ugen]<unit>.<addr>
+Limit device range to USB devices connected to the given unit and address.
+The unit and address coordinates may be prefixed by the lowercased word "ugen".
+.It Fl h
+Show help and available commands.
+.El
+.Pp
+When called without options,
+.Nm
+prints a list of all available USB devices.
+.Sh EXAMPLES
+Show information about the device on USB bus 1 at address 2:
+.Pp
+.Dl usbconfig -u 1 -a 2 dump_info
+.Pp
+Dump HID descriptor for device on USB bus 1 at address 2:
+.Pp
+.Dl usbconfig -u 1 -a 2 do_request 0x81 0x06 0x2200 0 0x100
+.Pp
+Dump string descriptor at index Z for device on USB bus 1 at address 2:
+.Pp
+.Dl usbconfig -u 1 -a 2 dump_string Z
+.Pp
+Dump current configuration descriptor for device on USB bus 1 at address 2:
+.Pp
+.Dl usbconfig -u 1 -a 2 dump_curr_config_desc
+.Pp
+Dump device descriptor for device on USB bus 1 at address 2:
+.Pp
+.Dl usbconfig -u 1 -a 2 dump_device_desc
+.Pp
+Program the device on USB bus 1 at address 2 to suspend, resume, power off, go into power save, or power on:
+.Pp
+.Dl usbconfig -u 1 -a 2 suspend
+.Dl usbconfig -u 1 -a 2 resume
+.Dl usbconfig -u 1 -a 2 power_off
+.Dl usbconfig -u 1 -a 2 power_save
+.Dl usbconfig -u 1 -a 2 power_on
+.Pp
+Display a list of available quirk names:
+.Pp
+.Dl usbconfig dump_quirk_names
+.Pp
+See
+.Xr usb_quirk 4
+for more information on quirks.
+.Sh SEE ALSO
+.Xr usb 4 ,
+.Xr usb_quirk 4
diff --git a/usr.sbin/usbconfig/usbconfig.c b/usr.sbin/usbconfig/usbconfig.c
new file mode 100644
index 0000000..c046003
--- /dev/null
+++ b/usr.sbin/usbconfig/usbconfig.c
@@ -0,0 +1,823 @@
+/* $FreeBSD$ */
+/*-
+ * Copyright (c) 2008-2009 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <err.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+#include <errno.h>
+#include <ctype.h>
+#include <sys/types.h>
+
+#include <libusb20_desc.h>
+#include <libusb20.h>
+
+#include "dump.h"
+
+struct options {
+ const char *quirkname;
+ void *buffer;
+ int template;
+ gid_t gid;
+ uid_t uid;
+ mode_t mode;
+ uint32_t got_any;
+ struct LIBUSB20_CONTROL_SETUP_DECODED setup;
+ uint16_t bus;
+ uint16_t addr;
+ uint16_t iface;
+ uint16_t vid;
+ uint16_t pid;
+ uint16_t lo_rev; /* inclusive */
+ uint16_t hi_rev; /* inclusive */
+ uint8_t string_index;
+ uint8_t config_index;
+ uint8_t alt_index;
+ uint8_t got_list:1;
+ uint8_t got_bus:1;
+ uint8_t got_addr:1;
+ uint8_t got_iface:1;
+ uint8_t got_set_config:1;
+ uint8_t got_set_alt:1;
+ uint8_t got_set_template:1;
+ uint8_t got_get_template:1;
+ uint8_t got_suspend:1;
+ uint8_t got_resume:1;
+ uint8_t got_reset:1;
+ uint8_t got_power_off:1;
+ uint8_t got_power_save:1;
+ uint8_t got_power_on:1;
+ uint8_t got_dump_device_quirks:1;
+ uint8_t got_dump_quirk_names:1;
+ uint8_t got_dump_device_desc:1;
+ uint8_t got_dump_curr_config:1;
+ uint8_t got_dump_all_config:1;
+ uint8_t got_dump_info:1;
+ uint8_t got_show_iface_driver:1;
+ uint8_t got_remove_device_quirk:1;
+ uint8_t got_add_device_quirk:1;
+ uint8_t got_remove_quirk:1;
+ uint8_t got_add_quirk:1;
+ uint8_t got_dump_string:1;
+ uint8_t got_do_request:1;
+};
+
+struct token {
+ const char *name;
+ uint8_t value;
+ uint8_t narg;
+};
+
+enum {
+ T_UNIT,
+ T_ADDR,
+ T_UGEN,
+ T_IFACE,
+ T_SET_CONFIG,
+ T_SET_ALT,
+ T_SET_TEMPLATE,
+ T_GET_TEMPLATE,
+ T_ADD_DEVICE_QUIRK,
+ T_REMOVE_DEVICE_QUIRK,
+ T_ADD_QUIRK,
+ T_REMOVE_QUIRK,
+ T_SHOW_IFACE_DRIVER,
+ T_DUMP_QUIRK_NAMES,
+ T_DUMP_DEVICE_QUIRKS,
+ T_DUMP_DEVICE_DESC,
+ T_DUMP_CURR_CONFIG_DESC,
+ T_DUMP_ALL_CONFIG_DESC,
+ T_DUMP_STRING,
+ T_DUMP_INFO,
+ T_SUSPEND,
+ T_RESUME,
+ T_POWER_OFF,
+ T_POWER_SAVE,
+ T_POWER_ON,
+ T_RESET,
+ T_LIST,
+ T_DO_REQUEST,
+};
+
+static struct options options;
+
+static const struct token token[] = {
+ {"-u", T_UNIT, 1},
+ {"-a", T_ADDR, 1},
+ {"-d", T_UGEN, 1},
+ {"-i", T_IFACE, 1},
+ {"set_config", T_SET_CONFIG, 1},
+ {"set_alt", T_SET_ALT, 1},
+ {"set_template", T_SET_TEMPLATE, 1},
+ {"get_template", T_GET_TEMPLATE, 0},
+ {"add_dev_quirk_vplh", T_ADD_DEVICE_QUIRK, 5},
+ {"remove_dev_quirk_vplh", T_REMOVE_DEVICE_QUIRK, 5},
+ {"add_quirk", T_ADD_QUIRK, 1},
+ {"remove_quirk", T_REMOVE_QUIRK, 1},
+ {"dump_quirk_names", T_DUMP_QUIRK_NAMES, 0},
+ {"dump_device_quirks", T_DUMP_DEVICE_QUIRKS, 0},
+ {"dump_device_desc", T_DUMP_DEVICE_DESC, 0},
+ {"dump_curr_config_desc", T_DUMP_CURR_CONFIG_DESC, 0},
+ {"dump_all_config_desc", T_DUMP_ALL_CONFIG_DESC, 0},
+ {"dump_string", T_DUMP_STRING, 1},
+ {"dump_info", T_DUMP_INFO, 0},
+ {"show_ifdrv", T_SHOW_IFACE_DRIVER, 0},
+ {"suspend", T_SUSPEND, 0},
+ {"resume", T_RESUME, 0},
+ {"power_off", T_POWER_OFF, 0},
+ {"power_save", T_POWER_SAVE, 0},
+ {"power_on", T_POWER_ON, 0},
+ {"reset", T_RESET, 0},
+ {"list", T_LIST, 0},
+ {"do_request", T_DO_REQUEST, 5},
+};
+
+static void
+be_dev_remove_quirk(struct libusb20_backend *pbe,
+ uint16_t vid, uint16_t pid, uint16_t lorev, uint16_t hirev,
+ const char *str)
+{
+ struct libusb20_quirk q;
+ int error;
+
+ memset(&q, 0, sizeof(q));
+
+ q.vid = vid;
+ q.pid = pid;
+ q.bcdDeviceLow = lorev;
+ q.bcdDeviceHigh = hirev;
+ strlcpy(q.quirkname, str, sizeof(q.quirkname));
+
+ error = libusb20_be_remove_dev_quirk(pbe, &q);
+ if (error) {
+ fprintf(stderr, "Removing quirk '%s' failed, continuing.\n", str);
+ }
+ return;
+}
+
+static void
+be_dev_add_quirk(struct libusb20_backend *pbe,
+ uint16_t vid, uint16_t pid, uint16_t lorev, uint16_t hirev,
+ const char *str)
+{
+ struct libusb20_quirk q;
+ int error;
+
+ memset(&q, 0, sizeof(q));
+
+ q.vid = vid;
+ q.pid = pid;
+ q.bcdDeviceLow = lorev;
+ q.bcdDeviceHigh = hirev;
+ strlcpy(q.quirkname, str, sizeof(q.quirkname));
+
+ error = libusb20_be_add_dev_quirk(pbe, &q);
+ if (error) {
+ fprintf(stderr, "Adding quirk '%s' failed, continuing.\n", str);
+ }
+ return;
+}
+
+static uint8_t
+get_token(const char *str, uint8_t narg)
+{
+ uint8_t n;
+
+ for (n = 0; n != (sizeof(token) / sizeof(token[0])); n++) {
+ if (strcasecmp(str, token[n].name) == 0) {
+ if (token[n].narg > narg) {
+ /* too few arguments */
+ break;
+ }
+ return (token[n].value);
+ }
+ }
+ return (0 - 1);
+}
+
+static uid_t
+num_id(const char *name, const char *type)
+{
+ uid_t val;
+ char *ep;
+
+ errno = 0;
+ val = strtoul(name, &ep, 0);
+ if (errno) {
+ err(1, "%s", name);
+ }
+ if (*ep != '\0') {
+ errx(1, "%s: illegal %s name", name, type);
+ }
+ return (val);
+}
+
+static int
+get_int(const char *s)
+{
+ int val;
+ char *ep;
+
+ errno = 0;
+ val = strtoul(s, &ep, 0);
+ if (errno) {
+ err(1, "%s", s);
+ }
+ if (*ep != '\0') {
+ errx(1, "illegal number: %s", s);
+ }
+ return val;
+}
+
+static void
+duplicate_option(const char *ptr)
+{
+ fprintf(stderr, "Syntax error: "
+ "Duplicate option: '%s'\n", ptr);
+ exit(1);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, ""
+ "usbconfig - configure the USB subsystem" "\n"
+ "usage: usbconfig -u <busnum> -a <devaddr> -i <ifaceindex> [cmds...]" "\n"
+ "usage: usbconfig -d [ugen]<busnum>.<devaddr> -i <ifaceindex> [cmds...]" "\n"
+ "commands:" "\n"
+ " set_config <cfg_index>" "\n"
+ " set_alt <alt_index>" "\n"
+ " set_template <template>" "\n"
+ " get_template" "\n"
+ " add_dev_quirk_vplh <vid> <pid> <lo_rev> <hi_rev> <quirk>" "\n"
+ " remove_dev_quirk_vplh <vid> <pid> <lo_rev> <hi_rev> <quirk>" "\n"
+ " add_quirk <quirk>" "\n"
+ " remove_quirk <quirk>" "\n"
+ " dump_quirk_names" "\n"
+ " dump_device_quirks" "\n"
+ " dump_device_desc" "\n"
+ " dump_curr_config_desc" "\n"
+ " dump_all_config_desc" "\n"
+ " dump_string <index>" "\n"
+ " dump_info" "\n"
+ " show_ifdrv" "\n"
+ " suspend" "\n"
+ " resume" "\n"
+ " power_off" "\n"
+ " power_save" "\n"
+ " power_on" "\n"
+ " reset" "\n"
+ " list" "\n"
+ " do_request <bmReqTyp> <bReq> <wVal> <wIdx> <wLen> <data...>" "\n"
+ );
+ exit(1);
+}
+
+static void
+reset_options(struct options *opt)
+{
+ if (opt->buffer)
+ free(opt->buffer);
+ memset(opt, 0, sizeof(*opt));
+ return;
+}
+
+static void
+flush_command(struct libusb20_backend *pbe, struct options *opt)
+{
+ struct libusb20_device *pdev = NULL;
+ uint32_t matches = 0;
+ uint8_t dump_any;
+
+ /* check for invalid option combinations */
+ if ((opt->got_suspend +
+ opt->got_resume +
+ opt->got_reset +
+ opt->got_set_config +
+ opt->got_set_alt +
+ opt->got_power_save +
+ opt->got_power_on +
+ opt->got_power_off) > 1) {
+ err(1, "can only specify one of 'set_config', "
+ "'set_alt', 'reset', 'suspend', 'resume', "
+ "'power_save', 'power_on' and 'power_off' "
+ "at the same time!");
+ }
+ if (opt->got_dump_quirk_names) {
+ opt->got_any--;
+ dump_be_quirk_names(pbe);
+ }
+ if (opt->got_dump_device_quirks) {
+ opt->got_any--;
+ dump_be_dev_quirks(pbe);
+ }
+ if (opt->got_remove_device_quirk) {
+ opt->got_any--;
+ be_dev_remove_quirk(pbe,
+ opt->vid, opt->pid, opt->lo_rev, opt->hi_rev, opt->quirkname);
+ }
+ if (opt->got_add_device_quirk) {
+ opt->got_any--;
+ be_dev_add_quirk(pbe,
+ opt->vid, opt->pid, opt->lo_rev, opt->hi_rev, opt->quirkname);
+ }
+ if (opt->got_set_template) {
+ opt->got_any--;
+ if (libusb20_be_set_template(pbe, opt->template)) {
+ fprintf(stderr, "Setting USB template %u failed, "
+ "continuing.\n", opt->template);
+ }
+ }
+ if (opt->got_get_template) {
+ opt->got_any--;
+ if (libusb20_be_get_template(pbe, &opt->template))
+ printf("USB template: <unknown>\n");
+ else
+ printf("USB template: %u\n", opt->template);
+ }
+ if (opt->got_any == 0) {
+ /*
+ * do not scan through all the devices if there are no valid
+ * options
+ */
+ goto done;
+ }
+ while ((pdev = libusb20_be_device_foreach(pbe, pdev))) {
+
+ if (opt->got_bus &&
+ (libusb20_dev_get_bus_number(pdev) != opt->bus)) {
+ continue;
+ }
+ if (opt->got_addr &&
+ (libusb20_dev_get_address(pdev) != opt->addr)) {
+ continue;
+ }
+ matches++;
+
+ if (opt->got_remove_quirk) {
+ struct LIBUSB20_DEVICE_DESC_DECODED *ddesc;
+
+ ddesc = libusb20_dev_get_device_desc(pdev);
+
+ be_dev_remove_quirk(pbe,
+ ddesc->idVendor, ddesc->idProduct,
+ ddesc->bcdDevice, ddesc->bcdDevice,
+ opt->quirkname);
+ }
+
+ if (opt->got_add_quirk) {
+ struct LIBUSB20_DEVICE_DESC_DECODED *ddesc;
+
+ ddesc = libusb20_dev_get_device_desc(pdev);
+
+ be_dev_add_quirk(pbe,
+ ddesc->idVendor, ddesc->idProduct,
+ ddesc->bcdDevice, ddesc->bcdDevice,
+ opt->quirkname);
+ }
+
+ if (libusb20_dev_open(pdev, 0)) {
+ err(1, "could not open device");
+ }
+ if (opt->got_dump_string) {
+ dump_string_by_index(pdev, opt->string_index);
+ }
+ if (opt->got_do_request) {
+ uint16_t actlen;
+ uint16_t t;
+
+ if (libusb20_dev_request_sync(pdev, &opt->setup,
+ opt->buffer, &actlen, 5000 /* 5 seconds */ , 0)) {
+ printf("REQUEST = <ERROR>\n");
+ } else if (!(opt->setup.bmRequestType &
+ LIBUSB20_ENDPOINT_IN)) {
+ printf("REQUEST = <OK>\n");
+ } else {
+ t = actlen;
+ printf("REQUEST = <");
+ for (t = 0; t != actlen; t++) {
+ printf("0x%02x%s",
+ ((uint8_t *)opt->buffer)[t],
+ (t == (actlen - 1)) ? "" : " ");
+ }
+ printf("><");
+ for (t = 0; t != actlen; t++) {
+ char c;
+
+ c = ((uint8_t *)opt->buffer)[t];
+ if ((c != '<') &&
+ (c != '>') && isprint(c)) {
+ putchar(c);
+ }
+ }
+ printf(">\n");
+ }
+ }
+ if (opt->got_set_config) {
+ if (libusb20_dev_set_config_index(pdev,
+ opt->config_index)) {
+ err(1, "could not set config index");
+ }
+ }
+ if (opt->got_set_alt) {
+ if (libusb20_dev_set_alt_index(pdev, opt->iface,
+ opt->alt_index)) {
+ err(1, "could not set alternate setting");
+ }
+ }
+ if (opt->got_reset) {
+ if (libusb20_dev_reset(pdev)) {
+ err(1, "could not reset device");
+ }
+ }
+ if (opt->got_suspend) {
+ if (libusb20_dev_set_power_mode(pdev,
+ LIBUSB20_POWER_SUSPEND)) {
+ err(1, "could not set suspend");
+ }
+ }
+ if (opt->got_resume) {
+ if (libusb20_dev_set_power_mode(pdev,
+ LIBUSB20_POWER_RESUME)) {
+ err(1, "could not set resume");
+ }
+ }
+ if (opt->got_power_off) {
+ if (libusb20_dev_set_power_mode(pdev,
+ LIBUSB20_POWER_OFF)) {
+ err(1, "could not set power OFF");
+ }
+ }
+ if (opt->got_power_save) {
+ if (libusb20_dev_set_power_mode(pdev,
+ LIBUSB20_POWER_SAVE)) {
+ err(1, "could not set power SAVE");
+ }
+ }
+ if (opt->got_power_on) {
+ if (libusb20_dev_set_power_mode(pdev,
+ LIBUSB20_POWER_ON)) {
+ err(1, "could not set power ON");
+ }
+ }
+ dump_any =
+ (opt->got_dump_device_desc ||
+ opt->got_dump_curr_config ||
+ opt->got_dump_all_config ||
+ opt->got_dump_info);
+
+ if (opt->got_list || dump_any) {
+ dump_device_info(pdev,
+ opt->got_show_iface_driver);
+ }
+ if (opt->got_dump_device_desc) {
+ printf("\n");
+ dump_device_desc(pdev);
+ }
+ if (opt->got_dump_all_config) {
+ printf("\n");
+ dump_config(pdev, 1);
+ } else if (opt->got_dump_curr_config) {
+ printf("\n");
+ dump_config(pdev, 0);
+ }
+ if (dump_any) {
+ printf("\n");
+ }
+ if (libusb20_dev_close(pdev)) {
+ err(1, "could not close device");
+ }
+ }
+
+ if (matches == 0) {
+ printf("No device match or lack of permissions.\n");
+ }
+done:
+ reset_options(opt);
+
+ return;
+}
+
+int
+main(int argc, char **argv)
+{
+ struct libusb20_backend *pbe;
+ struct options *opt = &options;
+ const char *ptr;
+ int unit;
+ int addr;
+ int n;
+ int t;
+
+ if (argc < 1) {
+ usage();
+ }
+ pbe = libusb20_be_alloc_default();
+ if (pbe == NULL)
+ err(1, "could not access USB backend\n");
+
+ for (n = 1; n != argc; n++) {
+
+ /* get number of additional options */
+ t = (argc - n - 1);
+ if (t > 255)
+ t = 255;
+ switch (get_token(argv[n], t)) {
+ case T_ADD_QUIRK:
+ if (opt->got_add_quirk) {
+ flush_command(pbe, opt);
+ }
+ opt->quirkname = argv[n + 1];
+ n++;
+
+ opt->got_add_quirk = 1;
+ opt->got_any++;
+ break;
+
+ case T_REMOVE_QUIRK:
+ if (opt->got_remove_quirk) {
+ flush_command(pbe, opt);
+ }
+ opt->quirkname = argv[n + 1];
+ n++;
+
+ opt->got_remove_quirk = 1;
+ opt->got_any++;
+ break;
+
+ case T_ADD_DEVICE_QUIRK:
+ if (opt->got_add_device_quirk) {
+ flush_command(pbe, opt);
+ }
+ opt->vid = num_id(argv[n + 1], "Vendor ID");
+ opt->pid = num_id(argv[n + 2], "Product ID");
+ opt->lo_rev = num_id(argv[n + 3], "Low Revision");
+ opt->hi_rev = num_id(argv[n + 4], "High Revision");
+ opt->quirkname = argv[n + 5];
+ n += 5;
+
+ opt->got_add_device_quirk = 1;
+ opt->got_any++;
+ break;
+
+ case T_REMOVE_DEVICE_QUIRK:
+ if (opt->got_remove_device_quirk) {
+ flush_command(pbe, opt);
+ }
+ opt->vid = num_id(argv[n + 1], "Vendor ID");
+ opt->pid = num_id(argv[n + 2], "Product ID");
+ opt->lo_rev = num_id(argv[n + 3], "Low Revision");
+ opt->hi_rev = num_id(argv[n + 4], "High Revision");
+ opt->quirkname = argv[n + 5];
+ n += 5;
+ opt->got_remove_device_quirk = 1;
+ opt->got_any++;
+ break;
+
+ case T_DUMP_QUIRK_NAMES:
+ if (opt->got_dump_quirk_names)
+ duplicate_option(argv[n]);
+ opt->got_dump_quirk_names = 1;
+ opt->got_any++;
+ break;
+
+ case T_DUMP_DEVICE_QUIRKS:
+ if (opt->got_dump_device_quirks)
+ duplicate_option(argv[n]);
+ opt->got_dump_device_quirks = 1;
+ opt->got_any++;
+ break;
+
+ case T_SHOW_IFACE_DRIVER:
+ opt->got_show_iface_driver = 1;
+ break;
+
+ case T_UGEN:
+ if (opt->got_any) {
+ /* allow multiple commands on the same line */
+ flush_command(pbe, opt);
+ }
+ ptr = argv[n + 1];
+
+ if ((ptr[0] == 'u') &&
+ (ptr[1] == 'g') &&
+ (ptr[2] == 'e') &&
+ (ptr[3] == 'n'))
+ ptr += 4;
+
+ if ((sscanf(ptr, "%d.%d",
+ &unit, &addr) != 2) ||
+ (unit < 0) || (unit > 65535) ||
+ (addr < 0) || (addr > 65535)) {
+ errx(1, "cannot "
+ "parse '%s'", argv[n + 1]);
+ }
+ opt->bus = unit;
+ opt->addr = addr;
+ opt->got_bus = 1;
+ opt->got_addr = 1;
+ n++;
+ break;
+
+ case T_UNIT:
+ if (opt->got_any) {
+ /* allow multiple commands on the same line */
+ flush_command(pbe, opt);
+ }
+ opt->bus = num_id(argv[n + 1], "busnum");
+ opt->got_bus = 1;
+ n++;
+ break;
+ case T_ADDR:
+ opt->addr = num_id(argv[n + 1], "addr");
+ opt->got_addr = 1;
+ n++;
+ break;
+ case T_IFACE:
+ opt->iface = num_id(argv[n + 1], "iface");
+ opt->got_iface = 1;
+ n++;
+ break;
+ case T_SET_CONFIG:
+ if (opt->got_set_config)
+ duplicate_option(argv[n]);
+ opt->config_index = num_id(argv[n + 1], "cfg_index");
+ opt->got_set_config = 1;
+ opt->got_any++;
+ n++;
+ break;
+ case T_SET_ALT:
+ if (opt->got_set_alt)
+ duplicate_option(argv[n]);
+ opt->alt_index = num_id(argv[n + 1], "cfg_index");
+ opt->got_set_alt = 1;
+ opt->got_any++;
+ n++;
+ break;
+ case T_SET_TEMPLATE:
+ if (opt->got_set_template)
+ duplicate_option(argv[n]);
+ opt->template = get_int(argv[n + 1]);
+ opt->got_set_template = 1;
+ opt->got_any++;
+ n++;
+ break;
+ case T_GET_TEMPLATE:
+ if (opt->got_get_template)
+ duplicate_option(argv[n]);
+ opt->got_get_template = 1;
+ opt->got_any++;
+ break;
+ case T_DUMP_DEVICE_DESC:
+ if (opt->got_dump_device_desc)
+ duplicate_option(argv[n]);
+ opt->got_dump_device_desc = 1;
+ opt->got_any++;
+ break;
+ case T_DUMP_CURR_CONFIG_DESC:
+ if (opt->got_dump_curr_config)
+ duplicate_option(argv[n]);
+ opt->got_dump_curr_config = 1;
+ opt->got_any++;
+ break;
+ case T_DUMP_ALL_CONFIG_DESC:
+ if (opt->got_dump_all_config)
+ duplicate_option(argv[n]);
+ opt->got_dump_all_config = 1;
+ opt->got_any++;
+ break;
+ case T_DUMP_INFO:
+ if (opt->got_dump_info)
+ duplicate_option(argv[n]);
+ opt->got_dump_info = 1;
+ opt->got_any++;
+ break;
+ case T_DUMP_STRING:
+ if (opt->got_dump_string)
+ duplicate_option(argv[n]);
+ opt->string_index = num_id(argv[n + 1], "str_index");
+ opt->got_dump_string = 1;
+ opt->got_any++;
+ n++;
+ break;
+ case T_SUSPEND:
+ if (opt->got_suspend)
+ duplicate_option(argv[n]);
+ opt->got_suspend = 1;
+ opt->got_any++;
+ break;
+ case T_RESUME:
+ if (opt->got_resume)
+ duplicate_option(argv[n]);
+ opt->got_resume = 1;
+ opt->got_any++;
+ break;
+ case T_POWER_OFF:
+ if (opt->got_power_off)
+ duplicate_option(argv[n]);
+ opt->got_power_off = 1;
+ opt->got_any++;
+ break;
+ case T_POWER_SAVE:
+ if (opt->got_power_save)
+ duplicate_option(argv[n]);
+ opt->got_power_save = 1;
+ opt->got_any++;
+ break;
+ case T_POWER_ON:
+ if (opt->got_power_on)
+ duplicate_option(argv[n]);
+ opt->got_power_on = 1;
+ opt->got_any++;
+ break;
+ case T_RESET:
+ if (opt->got_reset)
+ duplicate_option(argv[n]);
+ opt->got_reset = 1;
+ opt->got_any++;
+ break;
+ case T_LIST:
+ if (opt->got_list)
+ duplicate_option(argv[n]);
+ opt->got_list = 1;
+ opt->got_any++;
+ break;
+ case T_DO_REQUEST:
+ if (opt->got_do_request)
+ duplicate_option(argv[n]);
+ LIBUSB20_INIT(LIBUSB20_CONTROL_SETUP, &opt->setup);
+ opt->setup.bmRequestType = num_id(argv[n + 1], "bmReqTyp");
+ opt->setup.bRequest = num_id(argv[n + 2], "bReq");
+ opt->setup.wValue = num_id(argv[n + 3], "wVal");
+ opt->setup.wIndex = num_id(argv[n + 4], "wIndex");
+ opt->setup.wLength = num_id(argv[n + 5], "wLen");
+ if (opt->setup.wLength != 0) {
+ opt->buffer = malloc(opt->setup.wLength);
+ } else {
+ opt->buffer = NULL;
+ }
+
+ n += 5;
+
+ if (!(opt->setup.bmRequestType &
+ LIBUSB20_ENDPOINT_IN)) {
+ /* copy in data */
+ t = (argc - n - 1);
+ if (t < opt->setup.wLength) {
+ err(1, "request data missing");
+ }
+ t = opt->setup.wLength;
+ while (t--) {
+ ((uint8_t *)opt->buffer)[t] =
+ num_id(argv[n + t + 1], "req_data");
+ }
+ n += opt->setup.wLength;
+ }
+ opt->got_do_request = 1;
+ opt->got_any++;
+ break;
+ default:
+ usage();
+ break;
+ }
+ }
+ if (opt->got_any) {
+ /* flush out last command */
+ flush_command(pbe, opt);
+ } else {
+ /* list all the devices */
+ opt->got_list = 1;
+ opt->got_any++;
+ flush_command(pbe, opt);
+ }
+ /* release data */
+ libusb20_be_free(pbe);
+
+ return (0);
+}
diff --git a/usr.sbin/usbdump/Makefile b/usr.sbin/usbdump/Makefile
new file mode 100644
index 0000000..d42c9b6
--- /dev/null
+++ b/usr.sbin/usbdump/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= usbdump
+SRCS= usbdump.c
+MAN= usbdump.8
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/usbdump/Makefile.depend b/usr.sbin/usbdump/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/usbdump/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/usbdump/usbdump.8 b/usr.sbin/usbdump/usbdump.8
new file mode 100644
index 0000000..475e832
--- /dev/null
+++ b/usr.sbin/usbdump/usbdump.8
@@ -0,0 +1,157 @@
+.\"
+.\" Copyright (c) 2010 Weongyo Jeong.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 24, 2012
+.Dt USBDUMP 8
+.Os
+.Sh NAME
+.Nm usbdump
+.Nd "dump traffic on USB host controller"
+.Sh SYNOPSIS
+.Nm
+.Op Fl i Ar ifname
+.Op Fl r Ar file
+.Op Fl s Ar snaplen
+.Op Fl v
+.Op Fl w Ar file
+.Op Fl f Ar filter
+.Op Fl b Ar file
+.Op Fl h
+.Sh DESCRIPTION
+The
+.Nm
+utility provides a way to dump USB packets on host controllers.
+.Pp
+The following options are accepted:
+.Bl -tag -width ".Fl f Ar file"
+.It Fl b Ar file
+Store data part of the USB trace in binary format to the given
+.Ar file .
+This option also works with the -r and -f options.
+.It Fl i Ar ifname
+Listen on USB bus interface
+.Ar ifname .
+.It Fl r Ar file
+Read the raw packets from
+.Ar file .
+This option also works with the -f option.
+.It Fl s Ar snaplen
+Snapshot
+.Ar snaplen
+bytes from each packet.
+.It Fl v
+Enable debugging messages.
+When defined multiple times the verbosity level increases.
+.It Fl w Ar file
+Write the raw packets to
+.Ar file .
+This option also works with the -s and -v options.
+.It Fl f Ar filter
+The filter argument consists of either one or two numbers separated by a dot.
+The first indicates the device unit number which should be traced.
+The second number which is optional indicates the endpoint which should be traced.
+To get all traffic for the control endpoint, two filters should be
+created, one for endpoint 0 and one for endpoint 128.
+If 128 is added to the endpoint number that means IN direction, else OUT direction is implied.
+A device unit or endpoint value of -1 means ignore this field.
+If no filters are specified, all packets are passed through using the default -1,-1 filter.
+This option can be specified multiple times.
+.It Fl h
+This option displays a summary of the command line options.
+.El
+.Sh EXAMPLES
+Capture the USB raw packets on usbus2:
+.Pp
+.Dl "usbdump -i usbus2 -s 256 -v"
+.Pp
+Dump the USB raw packets of usbus2 into the file without packet
+size limit:
+.Pp
+.Dl "usbdump -i usbus2 -s 0 -w /tmp/dump_pkts"
+.Pp
+Dump the USB raw packets of usbus2, but only the control endpoint traffic
+of device unit number 3:
+.Pp
+.Dl "usbdump -i usbus2 -s 0 -f 3.0 -f 3.128 -w /tmp/dump_pkts"
+.Pp
+Read and display the USB raw packets from previous file:
+.Pp
+.Dl "usbdump -r /tmp/dump_pkts -v"
+.Sh OUTPUT FORMAT
+The output format of
+.Nm
+is as follows:
+.Pp
+.Dl "<time> <bus>.<addr> <ep> <xfertype> <S/D> (<frames>/<length>) <...>"
+.Pp
+The meaning of the output format elements is as follows:
+.Bl -tag -width "<xfertype>"
+.It <time>
+A timestamp preceding all output lines.
+The timestamp has the format "hh:mm:ss.frac" and is as accurate as
+the kernel's clock.
+.It <bus>
+The USB host controller's bus unit number.
+.It <addr>
+The unique number of the USB device as allocated by the host controller driver.
+.It <ep>
+The USB endpoint address that indicates whether the address is
+.Dv OUT
+or
+.Dv IN .
+.It <xfertype>
+The USB transfer type.
+Can be
+.Dv CTRL ,
+.Dv ISOC ,
+.Dv BULK
+or
+.Dv INTR .
+.It <S/D>
+`S' indicates a USB submit.
+`D' indicates a USB transfer done.
+.It <frames>
+Numbers of frames in this packets.
+If this is a USB submit, its value is
+.Li xfer->nframes
+which means how many frames are acceptable or registered to transfer.
+If this is a USB done,
+.Li xfer->aframes
+is the actual number of frames.
+.It <length>
+Total packet size.
+If this is a USB submit, its value is
+.Li xfer->sumlen .
+If this is a USB done, its value is
+.Li xfer->actlen .
+.It <...>
+Optional field used for printing an error string if the packet is from USB done.
+.El
+.Sh SEE ALSO
+.Xr usbconfig 8
+.Sh AUTHORS
+.An Weongyo Jeong Aq Mt weongyo@FreeBSD.org
diff --git a/usr.sbin/usbdump/usbdump.c b/usr.sbin/usbdump/usbdump.c
new file mode 100644
index 0000000..99307a4
--- /dev/null
+++ b/usr.sbin/usbdump/usbdump.c
@@ -0,0 +1,986 @@
+/*-
+ * Copyright (c) 2010 Weongyo Jeong <weongyo@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,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * 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 NONINFRINGEMENT, 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/endian.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/utsname.h>
+#include <sys/queue.h>
+#include <net/if.h>
+#include <net/bpf.h>
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_pf.h>
+#include <dev/usb/usbdi.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sysexits.h>
+#include <err.h>
+
+#define BPF_STORE_JUMP(x,_c,_k,_jt,_jf) do { \
+ (x).code = (_c); \
+ (x).k = (_k); \
+ (x).jt = (_jt); \
+ (x).jf = (_jf); \
+} while (0)
+
+#define BPF_STORE_STMT(x,_c,_k) do { \
+ (x).code = (_c); \
+ (x).k = (_k); \
+ (x).jt = 0; \
+ (x).jf = 0; \
+} while (0)
+
+struct usb_filt {
+ STAILQ_ENTRY(usb_filt) entry;
+ int unit;
+ int endpoint;
+};
+
+struct usbcap {
+ int fd; /* fd for /dev/usbpf */
+ uint32_t bufsize;
+ uint8_t *buffer;
+
+ /* for -w option */
+ int wfd;
+ /* for -r option */
+ int rfd;
+ /* for -b option */
+ int bfd;
+};
+
+struct usbcap_filehdr {
+ uint32_t magic;
+#define USBCAP_FILEHDR_MAGIC 0x9a90000e
+ uint8_t major;
+ uint8_t minor;
+ uint8_t reserved[26];
+} __packed;
+
+#define HEADER_ALIGN(x,a) (((x) + (a) - 1) & ~((a) - 1))
+
+struct header_32 {
+ /* capture timestamp */
+ uint32_t ts_sec;
+ uint32_t ts_usec;
+ /* data length and alignment information */
+ uint32_t caplen;
+ uint32_t datalen;
+ uint8_t hdrlen;
+ uint8_t align;
+} __packed;
+
+static int doexit = 0;
+static int pkt_captured = 0;
+static int verbose = 0;
+static int uf_minor;
+static const char *i_arg = "usbus0";
+static const char *r_arg = NULL;
+static const char *w_arg = NULL;
+static const char *b_arg = NULL;
+static struct usbcap uc;
+static const char *errstr_table[USB_ERR_MAX] = {
+ [USB_ERR_NORMAL_COMPLETION] = "0",
+ [USB_ERR_PENDING_REQUESTS] = "PENDING_REQUESTS",
+ [USB_ERR_NOT_STARTED] = "NOT_STARTED",
+ [USB_ERR_INVAL] = "INVAL",
+ [USB_ERR_NOMEM] = "NOMEM",
+ [USB_ERR_CANCELLED] = "CANCELLED",
+ [USB_ERR_BAD_ADDRESS] = "BAD_ADDRESS",
+ [USB_ERR_BAD_BUFSIZE] = "BAD_BUFSIZE",
+ [USB_ERR_BAD_FLAG] = "BAD_FLAG",
+ [USB_ERR_NO_CALLBACK] = "NO_CALLBACK",
+ [USB_ERR_IN_USE] = "IN_USE",
+ [USB_ERR_NO_ADDR] = "NO_ADDR",
+ [USB_ERR_NO_PIPE] = "NO_PIPE",
+ [USB_ERR_ZERO_NFRAMES] = "ZERO_NFRAMES",
+ [USB_ERR_ZERO_MAXP] = "ZERO_MAXP",
+ [USB_ERR_SET_ADDR_FAILED] = "SET_ADDR_FAILED",
+ [USB_ERR_NO_POWER] = "NO_POWER",
+ [USB_ERR_TOO_DEEP] = "TOO_DEEP",
+ [USB_ERR_IOERROR] = "IOERROR",
+ [USB_ERR_NOT_CONFIGURED] = "NOT_CONFIGURED",
+ [USB_ERR_TIMEOUT] = "TIMEOUT",
+ [USB_ERR_SHORT_XFER] = "SHORT_XFER",
+ [USB_ERR_STALLED] = "STALLED",
+ [USB_ERR_INTERRUPTED] = "INTERRUPTED",
+ [USB_ERR_DMA_LOAD_FAILED] = "DMA_LOAD_FAILED",
+ [USB_ERR_BAD_CONTEXT] = "BAD_CONTEXT",
+ [USB_ERR_NO_ROOT_HUB] = "NO_ROOT_HUB",
+ [USB_ERR_NO_INTR_THREAD] = "NO_INTR_THREAD",
+ [USB_ERR_NOT_LOCKED] = "NOT_LOCKED",
+};
+
+static const char *xfertype_table[4] = {
+ [UE_CONTROL] = "CTRL",
+ [UE_ISOCHRONOUS] = "ISOC",
+ [UE_BULK] = "BULK",
+ [UE_INTERRUPT] = "INTR"
+};
+
+static const char *speed_table[USB_SPEED_MAX] = {
+ [USB_SPEED_FULL] = "FULL",
+ [USB_SPEED_HIGH] = "HIGH",
+ [USB_SPEED_LOW] = "LOW",
+ [USB_SPEED_VARIABLE] = "VARI",
+ [USB_SPEED_SUPER] = "SUPER",
+};
+
+static STAILQ_HEAD(,usb_filt) usb_filt_head =
+ STAILQ_HEAD_INITIALIZER(usb_filt_head);
+
+static void
+add_filter(int usb_filt_unit, int usb_filt_ep)
+{
+ struct usb_filt *puf;
+
+ puf = malloc(sizeof(struct usb_filt));
+ if (puf == NULL)
+ errx(EX_SOFTWARE, "Out of memory.");
+
+ puf->unit = usb_filt_unit;
+ puf->endpoint = usb_filt_ep;
+
+ STAILQ_INSERT_TAIL(&usb_filt_head, puf, entry);
+}
+
+static void
+make_filter(struct bpf_program *pprog, int snapshot)
+{
+ struct usb_filt *puf;
+ struct bpf_insn *dynamic_insn;
+ int len;
+
+ len = 0;
+
+ STAILQ_FOREACH(puf, &usb_filt_head, entry)
+ len++;
+
+ dynamic_insn = malloc(((len * 5) + 1) * sizeof(struct bpf_insn));
+
+ if (dynamic_insn == NULL)
+ errx(EX_SOFTWARE, "Out of memory.");
+
+ len++;
+
+ if (len == 1) {
+ /* accept all packets */
+
+ BPF_STORE_STMT(dynamic_insn[0], BPF_RET | BPF_K, snapshot);
+
+ goto done;
+ }
+
+ len = 0;
+
+ STAILQ_FOREACH(puf, &usb_filt_head, entry) {
+ const int addr_off = (uintptr_t)&((struct usbpf_pkthdr *)0)->up_address;
+ const int addr_ep = (uintptr_t)&((struct usbpf_pkthdr *)0)->up_endpoint;
+
+ if (puf->unit != -1) {
+ if (puf->endpoint != -1) {
+ BPF_STORE_STMT(dynamic_insn[len],
+ BPF_LD | BPF_B | BPF_ABS, addr_off);
+ len++;
+ BPF_STORE_JUMP(dynamic_insn[len],
+ BPF_JMP | BPF_JEQ | BPF_K, (uint8_t)puf->unit, 0, 3);
+ len++;
+ BPF_STORE_STMT(dynamic_insn[len],
+ BPF_LD | BPF_W | BPF_ABS, addr_ep);
+ len++;
+ BPF_STORE_JUMP(dynamic_insn[len],
+ BPF_JMP | BPF_JEQ | BPF_K, htobe32(puf->endpoint), 0, 1);
+ len++;
+ } else {
+ BPF_STORE_STMT(dynamic_insn[len],
+ BPF_LD | BPF_B | BPF_ABS, addr_off);
+ len++;
+ BPF_STORE_JUMP(dynamic_insn[len],
+ BPF_JMP | BPF_JEQ | BPF_K, (uint8_t)puf->unit, 0, 1);
+ len++;
+ }
+ } else {
+ if (puf->endpoint != -1) {
+ BPF_STORE_STMT(dynamic_insn[len],
+ BPF_LD | BPF_W | BPF_ABS, addr_ep);
+ len++;
+ BPF_STORE_JUMP(dynamic_insn[len],
+ BPF_JMP | BPF_JEQ | BPF_K, htobe32(puf->endpoint), 0, 1);
+ len++;
+ }
+ }
+ BPF_STORE_STMT(dynamic_insn[len],
+ BPF_RET | BPF_K, snapshot);
+ len++;
+ }
+
+ BPF_STORE_STMT(dynamic_insn[len], BPF_RET | BPF_K, 0);
+ len++;
+
+done:
+ pprog->bf_len = len;
+ pprog->bf_insns = dynamic_insn;
+}
+
+static int
+match_filter(int unit, int endpoint)
+{
+ struct usb_filt *puf;
+
+ if (STAILQ_FIRST(&usb_filt_head) == NULL)
+ return (1);
+
+ STAILQ_FOREACH(puf, &usb_filt_head, entry) {
+ if ((puf->unit == -1 || puf->unit == unit) &&
+ (puf->endpoint == -1 || puf->endpoint == endpoint))
+ return (1);
+ }
+ return (0);
+}
+
+static void
+free_filter(struct bpf_program *pprog)
+{
+ struct usb_filt *puf;
+
+ while ((puf = STAILQ_FIRST(&usb_filt_head)) != NULL) {
+ STAILQ_REMOVE_HEAD(&usb_filt_head, entry);
+ free(puf);
+ }
+ free(pprog->bf_insns);
+}
+
+static void
+handle_sigint(int sig)
+{
+
+ (void)sig;
+ doexit = 1;
+}
+
+#define FLAGS(x, name) \
+ (((x) & USBPF_FLAG_##name) ? #name "|" : "")
+
+#define STATUS(x, name) \
+ (((x) & USBPF_STATUS_##name) ? #name "|" : "")
+
+static const char *
+usb_errstr(uint32_t error)
+{
+ if (error >= USB_ERR_MAX || errstr_table[error] == NULL)
+ return ("UNKNOWN");
+ else
+ return (errstr_table[error]);
+}
+
+static const char *
+usb_speedstr(uint8_t speed)
+{
+ if (speed >= USB_SPEED_MAX || speed_table[speed] == NULL)
+ return ("UNKNOWN");
+ else
+ return (speed_table[speed]);
+}
+
+static void
+print_flags(uint32_t flags)
+{
+ printf(" flags %#x <%s%s%s%s%s%s%s%s%s0>\n",
+ flags,
+ FLAGS(flags, FORCE_SHORT_XFER),
+ FLAGS(flags, SHORT_XFER_OK),
+ FLAGS(flags, SHORT_FRAMES_OK),
+ FLAGS(flags, PIPE_BOF),
+ FLAGS(flags, PROXY_BUFFER),
+ FLAGS(flags, EXT_BUFFER),
+ FLAGS(flags, MANUAL_STATUS),
+ FLAGS(flags, NO_PIPE_OK),
+ FLAGS(flags, STALL_PIPE));
+}
+
+static void
+print_status(uint32_t status)
+{
+ printf(" status %#x <%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s0>\n",
+ status,
+ STATUS(status, OPEN),
+ STATUS(status, TRANSFERRING),
+ STATUS(status, DID_DMA_DELAY),
+ STATUS(status, DID_CLOSE),
+ STATUS(status, DRAINING),
+ STATUS(status, STARTED),
+ STATUS(status, BW_RECLAIMED),
+ STATUS(status, CONTROL_XFR),
+ STATUS(status, CONTROL_HDR),
+ STATUS(status, CONTROL_ACT),
+ STATUS(status, CONTROL_STALL),
+ STATUS(status, SHORT_FRAMES_OK),
+ STATUS(status, SHORT_XFER_OK),
+ STATUS(status, BDMA_ENABLE),
+ STATUS(status, BDMA_NO_POST_SYNC),
+ STATUS(status, BDMA_SETUP),
+ STATUS(status, ISOCHRONOUS_XFR),
+ STATUS(status, CURR_DMA_SET),
+ STATUS(status, CAN_CANCEL_IMMED),
+ STATUS(status, DOING_CALLBACK));
+}
+
+/*
+ * Dump a byte into hex format.
+ */
+static void
+hexbyte(char *buf, uint8_t temp)
+{
+ uint8_t lo;
+ uint8_t hi;
+
+ lo = temp & 0xF;
+ hi = temp >> 4;
+
+ if (hi < 10)
+ buf[0] = '0' + hi;
+ else
+ buf[0] = 'A' + hi - 10;
+
+ if (lo < 10)
+ buf[1] = '0' + lo;
+ else
+ buf[1] = 'A' + lo - 10;
+}
+
+/*
+ * Display a region in traditional hexdump format.
+ */
+static void
+hexdump(const uint8_t *region, uint32_t len)
+{
+ const uint8_t *line;
+ char linebuf[128];
+ int i;
+ int x;
+ int c;
+
+ for (line = region; line < (region + len); line += 16) {
+
+ i = 0;
+
+ linebuf[i] = ' ';
+ hexbyte(linebuf + i + 1, ((line - region) >> 8) & 0xFF);
+ hexbyte(linebuf + i + 3, (line - region) & 0xFF);
+ linebuf[i + 5] = ' ';
+ linebuf[i + 6] = ' ';
+ i += 7;
+
+ for (x = 0; x < 16; x++) {
+ if ((line + x) < (region + len)) {
+ hexbyte(linebuf + i,
+ *(const u_int8_t *)(line + x));
+ } else {
+ linebuf[i] = '-';
+ linebuf[i + 1] = '-';
+ }
+ linebuf[i + 2] = ' ';
+ if (x == 7) {
+ linebuf[i + 3] = ' ';
+ i += 4;
+ } else {
+ i += 3;
+ }
+ }
+ linebuf[i] = ' ';
+ linebuf[i + 1] = '|';
+ i += 2;
+ for (x = 0; x < 16; x++) {
+ if ((line + x) < (region + len)) {
+ c = *(const u_int8_t *)(line + x);
+ /* !isprint(c) */
+ if ((c < ' ') || (c > '~'))
+ c = '.';
+ linebuf[i] = c;
+ } else {
+ linebuf[i] = ' ';
+ }
+ i++;
+ }
+ linebuf[i] = '|';
+ linebuf[i + 1] = 0;
+ i += 2;
+ puts(linebuf);
+ }
+}
+
+static void
+print_apacket(const struct header_32 *hdr, const uint8_t *ptr, int ptr_len)
+{
+ struct tm *tm;
+ struct usbpf_pkthdr up_temp;
+ struct usbpf_pkthdr *up;
+ struct timeval tv;
+ size_t len;
+ uint32_t x;
+ char buf[64];
+
+ ptr += USBPF_HDR_LEN;
+ ptr_len -= USBPF_HDR_LEN;
+ if (ptr_len < 0)
+ return;
+
+ /* make sure we don't change the source buffer */
+ memcpy(&up_temp, ptr - USBPF_HDR_LEN, sizeof(up_temp));
+ up = &up_temp;
+
+ /*
+ * A packet from the kernel is based on little endian byte
+ * order.
+ */
+ up->up_totlen = le32toh(up->up_totlen);
+ up->up_busunit = le32toh(up->up_busunit);
+ up->up_flags = le32toh(up->up_flags);
+ up->up_status = le32toh(up->up_status);
+ up->up_error = le32toh(up->up_error);
+ up->up_interval = le32toh(up->up_interval);
+ up->up_frames = le32toh(up->up_frames);
+ up->up_packet_size = le32toh(up->up_packet_size);
+ up->up_packet_count = le32toh(up->up_packet_count);
+ up->up_endpoint = le32toh(up->up_endpoint);
+
+ if (!match_filter(up->up_address, up->up_endpoint))
+ return;
+
+ tv.tv_sec = hdr->ts_sec;
+ tv.tv_usec = hdr->ts_usec;
+ tm = localtime(&tv.tv_sec);
+
+ len = strftime(buf, sizeof(buf), "%H:%M:%S", tm);
+
+ if (verbose >= 0) {
+ printf("%.*s.%06ld usbus%d.%d %s-%s-EP=%08x,SPD=%s,NFR=%d,SLEN=%d,IVAL=%d%s%s\n",
+ (int)len, buf, tv.tv_usec,
+ (int)up->up_busunit, (int)up->up_address,
+ (up->up_type == USBPF_XFERTAP_SUBMIT) ? "SUBM" : "DONE",
+ xfertype_table[up->up_xfertype],
+ (unsigned int)up->up_endpoint,
+ usb_speedstr(up->up_speed),
+ (int)up->up_frames,
+ (int)(up->up_totlen - USBPF_HDR_LEN -
+ (USBPF_FRAME_HDR_LEN * up->up_frames)),
+ (int)up->up_interval,
+ (up->up_type == USBPF_XFERTAP_DONE) ? ",ERR=" : "",
+ (up->up_type == USBPF_XFERTAP_DONE) ?
+ usb_errstr(up->up_error) : "");
+ }
+
+ if (verbose >= 1 || b_arg != NULL) {
+ for (x = 0; x != up->up_frames; x++) {
+ const struct usbpf_framehdr *uf;
+ uint32_t framelen;
+ uint32_t flags;
+
+ uf = (const struct usbpf_framehdr *)ptr;
+ ptr += USBPF_FRAME_HDR_LEN;
+ ptr_len -= USBPF_FRAME_HDR_LEN;
+ if (ptr_len < 0)
+ return;
+
+ framelen = le32toh(uf->length);
+ flags = le32toh(uf->flags);
+
+ if (verbose >= 1) {
+ printf(" frame[%u] %s %d bytes\n",
+ (unsigned int)x,
+ (flags & USBPF_FRAMEFLAG_READ) ? "READ" : "WRITE",
+ (int)framelen);
+ }
+
+ if (flags & USBPF_FRAMEFLAG_DATA_FOLLOWS) {
+
+ int tot_frame_len;
+
+ tot_frame_len = USBPF_FRAME_ALIGN(framelen);
+
+ ptr_len -= tot_frame_len;
+
+ if (tot_frame_len < 0 ||
+ (int)framelen < 0 || (int)ptr_len < 0)
+ break;
+
+ if (b_arg != NULL) {
+ struct usbcap *p = &uc;
+ int ret;
+ ret = write(p->bfd, ptr, framelen);
+ if (ret != (int)framelen)
+ err(EXIT_FAILURE, "Could not write binary data");
+ }
+ if (verbose >= 1)
+ hexdump(ptr, framelen);
+
+ ptr += tot_frame_len;
+ }
+ }
+ }
+ if (verbose >= 2)
+ print_flags(up->up_flags);
+ if (verbose >= 3)
+ print_status(up->up_status);
+}
+
+static void
+fix_packets(uint8_t *data, const int datalen)
+{
+ struct header_32 temp;
+ uint8_t *ptr;
+ uint8_t *next;
+ uint32_t hdrlen;
+ uint32_t caplen;
+
+ for (ptr = data; ptr < (data + datalen); ptr = next) {
+
+ const struct bpf_hdr *hdr;
+
+ hdr = (const struct bpf_hdr *)ptr;
+
+ temp.ts_sec = htole32(hdr->bh_tstamp.tv_sec);
+ temp.ts_usec = htole32(hdr->bh_tstamp.tv_usec);
+ temp.caplen = htole32(hdr->bh_caplen);
+ temp.datalen = htole32(hdr->bh_datalen);
+ temp.hdrlen = hdr->bh_hdrlen;
+ temp.align = BPF_WORDALIGN(1);
+
+ hdrlen = hdr->bh_hdrlen;
+ caplen = hdr->bh_caplen;
+
+ if ((hdrlen >= sizeof(temp)) && (hdrlen <= 255) &&
+ ((ptr + hdrlen) <= (data + datalen))) {
+ memcpy(ptr, &temp, sizeof(temp));
+ memset(ptr + sizeof(temp), 0, hdrlen - sizeof(temp));
+ } else {
+ err(EXIT_FAILURE, "Invalid header length %d", hdrlen);
+ }
+
+ next = ptr + BPF_WORDALIGN(hdrlen + caplen);
+
+ if (next <= ptr)
+ err(EXIT_FAILURE, "Invalid length");
+ }
+}
+
+static void
+print_packets(uint8_t *data, const int datalen)
+{
+ struct header_32 temp;
+ uint8_t *ptr;
+ uint8_t *next;
+
+ for (ptr = data; ptr < (data + datalen); ptr = next) {
+
+ const struct header_32 *hdr32;
+
+ hdr32 = (const struct header_32 *)ptr;
+
+ temp.ts_sec = le32toh(hdr32->ts_sec);
+ temp.ts_usec = le32toh(hdr32->ts_usec);
+ temp.caplen = le32toh(hdr32->caplen);
+ temp.datalen = le32toh(hdr32->datalen);
+ temp.hdrlen = hdr32->hdrlen;
+ temp.align = hdr32->align;
+
+ next = ptr + HEADER_ALIGN(temp.hdrlen + temp.caplen, temp.align);
+
+ if (next <= ptr)
+ err(EXIT_FAILURE, "Invalid length");
+
+ if (verbose >= 0 || r_arg != NULL || b_arg != NULL) {
+ print_apacket(&temp, ptr +
+ temp.hdrlen, temp.caplen);
+ }
+ pkt_captured++;
+ }
+}
+
+static void
+write_packets(struct usbcap *p, const uint8_t *data, const int datalen)
+{
+ int len = htole32(datalen);
+ int ret;
+
+ ret = write(p->wfd, &len, sizeof(int));
+ if (ret != sizeof(int)) {
+ err(EXIT_FAILURE, "Could not write length "
+ "field of USB data payload");
+ }
+ ret = write(p->wfd, data, datalen);
+ if (ret != datalen) {
+ err(EXIT_FAILURE, "Could not write "
+ "complete USB data payload");
+ }
+}
+
+static void
+read_file(struct usbcap *p)
+{
+ int datalen;
+ int ret;
+ uint8_t *data;
+
+ while ((ret = read(p->rfd, &datalen, sizeof(int))) == sizeof(int)) {
+ datalen = le32toh(datalen);
+ data = malloc(datalen);
+ if (data == NULL)
+ errx(EX_SOFTWARE, "Out of memory.");
+ ret = read(p->rfd, data, datalen);
+ if (ret != datalen) {
+ err(EXIT_FAILURE, "Could not read complete "
+ "USB data payload");
+ }
+ if (uf_minor == 2)
+ fix_packets(data, datalen);
+
+ print_packets(data, datalen);
+ free(data);
+ }
+}
+
+static void
+do_loop(struct usbcap *p)
+{
+ int cc;
+
+ while (doexit == 0) {
+ cc = read(p->fd, (uint8_t *)p->buffer, p->bufsize);
+ if (cc < 0) {
+ switch (errno) {
+ case EINTR:
+ break;
+ default:
+ fprintf(stderr, "read: %s\n", strerror(errno));
+ return;
+ }
+ continue;
+ }
+ if (cc == 0)
+ continue;
+
+ fix_packets(p->buffer, cc);
+
+ if (w_arg != NULL)
+ write_packets(p, p->buffer, cc);
+ print_packets(p->buffer, cc);
+ }
+}
+
+static void
+init_rfile(struct usbcap *p)
+{
+ struct usbcap_filehdr uf;
+ int ret;
+
+ p->rfd = open(r_arg, O_RDONLY);
+ if (p->rfd < 0) {
+ err(EXIT_FAILURE, "Could not open "
+ "'%s' for read", r_arg);
+ }
+ ret = read(p->rfd, &uf, sizeof(uf));
+ if (ret != sizeof(uf)) {
+ err(EXIT_FAILURE, "Could not read USB capture "
+ "file header");
+ }
+ if (le32toh(uf.magic) != USBCAP_FILEHDR_MAGIC) {
+ errx(EX_SOFTWARE, "Invalid magic field(0x%08x) "
+ "in USB capture file header.",
+ (unsigned int)le32toh(uf.magic));
+ }
+ if (uf.major != 0) {
+ errx(EX_SOFTWARE, "Invalid major version(%d) "
+ "field in USB capture file header.", (int)uf.major);
+ }
+
+ uf_minor = uf.minor;
+
+ if (uf.minor != 3 && uf.minor != 2) {
+ errx(EX_SOFTWARE, "Invalid minor version(%d) "
+ "field in USB capture file header.", (int)uf.minor);
+ }
+}
+
+static void
+init_wfile(struct usbcap *p)
+{
+ struct usbcap_filehdr uf;
+ int ret;
+
+ p->wfd = open(w_arg, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR);
+ if (p->wfd < 0) {
+ err(EXIT_FAILURE, "Could not open "
+ "'%s' for write", w_arg);
+ }
+ memset(&uf, 0, sizeof(uf));
+ uf.magic = htole32(USBCAP_FILEHDR_MAGIC);
+ uf.major = 0;
+ uf.minor = 3;
+ ret = write(p->wfd, (const void *)&uf, sizeof(uf));
+ if (ret != sizeof(uf)) {
+ err(EXIT_FAILURE, "Could not write "
+ "USB capture header");
+ }
+}
+
+static void
+usage(void)
+{
+
+#define FMT " %-14s %s\n"
+ fprintf(stderr, "usage: usbdump [options]\n");
+ fprintf(stderr, FMT, "-i <usbusX>", "Listen on USB bus interface");
+ fprintf(stderr, FMT, "-f <unit[.endpoint]>", "Specify a device and endpoint filter");
+ fprintf(stderr, FMT, "-r <file>", "Read the raw packets from file");
+ fprintf(stderr, FMT, "-s <snaplen>", "Snapshot bytes from each packet");
+ fprintf(stderr, FMT, "-v", "Increase the verbose level");
+ fprintf(stderr, FMT, "-b <file>", "Save raw version of all recorded data to file");
+ fprintf(stderr, FMT, "-w <file>", "Write the raw packets to file");
+ fprintf(stderr, FMT, "-h", "Display summary of command line options");
+#undef FMT
+ exit(EX_USAGE);
+}
+
+static void
+check_usb_pf_sysctl(void)
+{
+ int error;
+ int no_pf_val = 0;
+ size_t no_pf_len = sizeof(int);
+
+ /* check "hw.usb.no_pf" sysctl for 8- and 9- stable */
+
+ error = sysctlbyname("hw.usb.no_pf", &no_pf_val,
+ &no_pf_len, NULL, 0);
+ if (error == 0 && no_pf_val != 0) {
+ warnx("The USB packet filter might be disabled.");
+ warnx("See the \"hw.usb.no_pf\" sysctl for more information.");
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct timeval tv;
+ struct bpf_program total_prog;
+ struct bpf_stat us;
+ struct bpf_version bv;
+ struct usbcap *p = &uc;
+ struct ifreq ifr;
+ long snapshot = 192;
+ uint32_t v;
+ int fd;
+ int o;
+ int filt_unit;
+ int filt_ep;
+ int s;
+ int ifindex;
+ const char *optstring;
+ char *pp;
+
+ optstring = "b:hi:r:s:vw:f:";
+ while ((o = getopt(argc, argv, optstring)) != -1) {
+ switch (o) {
+ case 'i':
+ i_arg = optarg;
+ break;
+ case 'r':
+ r_arg = optarg;
+ init_rfile(p);
+ break;
+ case 's':
+ snapshot = strtol(optarg, &pp, 10);
+ errno = 0;
+ if (pp != NULL && *pp != 0)
+ usage();
+ if (snapshot == 0 && errno == EINVAL)
+ usage();
+ /* snapeshot == 0 is special */
+ if (snapshot == 0)
+ snapshot = -1;
+ break;
+ case 'b':
+ b_arg = optarg;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'w':
+ w_arg = optarg;
+ init_wfile(p);
+ break;
+ case 'f':
+ filt_unit = strtol(optarg, &pp, 10);
+ filt_ep = -1;
+ if (pp != NULL) {
+ if (*pp == '.') {
+ filt_ep = strtol(pp + 1, &pp, 10);
+ if (pp != NULL && *pp != 0)
+ usage();
+ } else if (*pp != 0) {
+ usage();
+ }
+ }
+ add_filter(filt_unit, filt_ep);
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+
+ if (b_arg != NULL) {
+ p->bfd = open(b_arg, O_CREAT | O_TRUNC |
+ O_WRONLY, S_IRUSR | S_IWUSR);
+ if (p->bfd < 0) {
+ err(EXIT_FAILURE, "Could not open "
+ "'%s' for write", b_arg);
+ }
+ }
+
+ /*
+ * Require more verbosity to print anything when -w or -b is
+ * specified on the command line:
+ */
+ if (w_arg != NULL || b_arg != NULL)
+ verbose--;
+
+ if (r_arg != NULL) {
+ read_file(p);
+ exit(EXIT_SUCCESS);
+ }
+
+ check_usb_pf_sysctl();
+
+ p->fd = fd = open("/dev/bpf", O_RDONLY);
+ if (p->fd < 0)
+ err(EXIT_FAILURE, "Could not open BPF device");
+
+ if (ioctl(fd, BIOCVERSION, (caddr_t)&bv) < 0)
+ err(EXIT_FAILURE, "BIOCVERSION ioctl failed");
+
+ if (bv.bv_major != BPF_MAJOR_VERSION ||
+ bv.bv_minor < BPF_MINOR_VERSION)
+ errx(EXIT_FAILURE, "Kernel BPF filter out of date");
+
+ /* USB transfers can be greater than 64KByte */
+ v = 1U << 16;
+
+ /* clear ifr structure */
+ memset(&ifr, 0, sizeof(ifr));
+
+ /* Try to create usbusN interface if it is not available. */
+ s = socket(AF_LOCAL, SOCK_DGRAM, 0);
+ if (s < 0)
+ errx(EXIT_FAILURE, "Could not open a socket");
+ ifindex = if_nametoindex(i_arg);
+ if (ifindex == 0) {
+ (void)strlcpy(ifr.ifr_name, i_arg, sizeof(ifr.ifr_name));
+ if (ioctl(s, SIOCIFCREATE2, &ifr) < 0)
+ errx(EXIT_FAILURE, "Invalid bus interface: %s", i_arg);
+ }
+
+ for ( ; v >= USBPF_HDR_LEN; v >>= 1) {
+ (void)ioctl(fd, BIOCSBLEN, (caddr_t)&v);
+ (void)strlcpy(ifr.ifr_name, i_arg, sizeof(ifr.ifr_name));
+ if (ioctl(fd, BIOCSETIF, (caddr_t)&ifr) >= 0)
+ break;
+ }
+ if (v == 0)
+ errx(EXIT_FAILURE, "No buffer size worked.");
+
+ if (ioctl(fd, BIOCGBLEN, (caddr_t)&v) < 0)
+ err(EXIT_FAILURE, "BIOCGBLEN ioctl failed");
+
+ p->bufsize = v;
+ p->buffer = (uint8_t *)malloc(p->bufsize);
+ if (p->buffer == NULL)
+ errx(EX_SOFTWARE, "Out of memory.");
+
+ make_filter(&total_prog, snapshot);
+
+ if (ioctl(p->fd, BIOCSETF, (caddr_t)&total_prog) < 0)
+ err(EXIT_FAILURE, "BIOCSETF ioctl failed");
+
+ free_filter(&total_prog);
+
+ /* 1 second read timeout */
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ if (ioctl(p->fd, BIOCSRTIMEOUT, (caddr_t)&tv) < 0)
+ err(EXIT_FAILURE, "BIOCSRTIMEOUT ioctl failed");
+
+ (void)signal(SIGINT, handle_sigint);
+
+ do_loop(p);
+
+ if (ioctl(fd, BIOCGSTATS, (caddr_t)&us) < 0)
+ err(EXIT_FAILURE, "BIOCGSTATS ioctl failed");
+
+ /* XXX what's difference between pkt_captured and us.us_recv? */
+ printf("\n");
+ printf("%d packets captured\n", pkt_captured);
+ printf("%d packets received by filter\n", us.bs_recv);
+ printf("%d packets dropped by kernel\n", us.bs_drop);
+
+ /*
+ * Destroy the usbusN interface only if it was created by
+ * usbdump(8). Ignore when it was already destroyed.
+ */
+ if (ifindex == 0 && if_nametoindex(i_arg) > 0) {
+ (void)strlcpy(ifr.ifr_name, i_arg, sizeof(ifr.ifr_name));
+ if (ioctl(s, SIOCIFDESTROY, &ifr) < 0)
+ warn("SIOCIFDESTROY ioctl failed");
+ }
+ close(s);
+
+ if (p->fd > 0)
+ close(p->fd);
+ if (p->rfd > 0)
+ close(p->rfd);
+ if (p->wfd > 0)
+ close(p->wfd);
+ if (p->bfd > 0)
+ close(p->bfd);
+
+ return (EXIT_SUCCESS);
+}
diff --git a/usr.sbin/utx/Makefile b/usr.sbin/utx/Makefile
new file mode 100644
index 0000000..78c4f90
--- /dev/null
+++ b/usr.sbin/utx/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= utx
+MAN= utx.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/utx/Makefile.depend b/usr.sbin/utx/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/utx/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/utx/utx.8 b/usr.sbin/utx/utx.8
new file mode 100644
index 0000000..2179496
--- /dev/null
+++ b/usr.sbin/utx/utx.8
@@ -0,0 +1,97 @@
+.\" Copyright (c) 2011-2012 Ed Schouten <ed@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 November 3, 2013
+.Dt UTX 8
+.Os
+.Sh NAME
+.Nm utx
+.Nd manage the user accounting database
+.Sh SYNOPSIS
+.Nm
+.Cm boot
+.Nm
+.Cm shutdown
+.Nm
+.Cm rm
+.Ar identifier
+.Ar ...
+.Sh DESCRIPTION
+The
+.Nm
+utility can be used to perform operations on the user accounting
+database, as done by
+.Xr pututxline 3 .
+.Pp
+The first argument to
+.Nm
+indicates an action to be performed:
+.Bl -tag -width ".Cm shutdown"
+.It Cm boot
+Write a boot time record to the user accounting database.
+This option should typically only be used by
+.Xr rc 8 .
+.It Cm shutdown
+Write a shutdown time record to the user accounting database.
+This option should typically only be used by
+.Xr rc 8 .
+.It Cm rm
+Remove stale sessions from the user accounting
+database, by referring to their
+.Ar identifier .
+Stale sessions can occur if a login service exits prematurely or fails
+to remove the session from the accounting database.
+.Pp
+Utilities such as
+.Xr w 1
+will not display the identifier corresponding with a login session,
+since its value is typically only of use by the process managing the
+record.
+The following command can be used to obtain all records from the user
+accounting database's active session table, including its identifiers:
+.Pp
+.Dl getent utmpx active
+.Pp
+Identifiers can either be supplied in hexadecimal form as displayed by
+.Xr getent 1 ,
+or as a string if the identifier allows such a representation.
+.El
+.Pp
+Because this utility requires write-access to the user accounting
+database, its use is limited to the super-user.
+.Sh SEE ALSO
+.Xr getent 1 ,
+.Xr w 1 ,
+.Xr pututxline 3
+.Sh HISTORY
+The
+.Nm
+utility replaced
+.Nm utxrm
+in
+.Fx 10.0 .
+.Sh AUTHORS
+.An Ed Schouten Aq Mt ed@FreeBSD.org
diff --git a/usr.sbin/utx/utx.c b/usr.sbin/utx/utx.c
new file mode 100644
index 0000000..59edf3b
--- /dev/null
+++ b/usr.sbin/utx/utx.c
@@ -0,0 +1,110 @@
+/*-
+ * Copyright (c) 2011-2012 Ed Schouten <ed@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/time.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <utmpx.h>
+
+static int
+b16_pton(const char *in, char *out, size_t len)
+{
+ size_t i;
+
+ for (i = 0; i < len * 2; i++)
+ if (!isxdigit((unsigned char)in[i]))
+ return (1);
+ for (i = 0; i < len; i++)
+ sscanf(&in[i * 2], "%02hhx", &out[i]);
+ return (0);
+}
+
+static int
+rm(char *id[])
+{
+ struct utmpx utx = { .ut_type = DEAD_PROCESS };
+ size_t len;
+ int ret = 0;
+
+ (void)gettimeofday(&utx.ut_tv, NULL);
+ for (; *id != NULL; id++) {
+ len = strlen(*id);
+ if (len <= sizeof(utx.ut_id)) {
+ /* Identifier as string. */
+ strncpy(utx.ut_id, *id, sizeof(utx.ut_id));
+ } else if (len != sizeof(utx.ut_id) * 2 ||
+ b16_pton(*id, utx.ut_id, sizeof(utx.ut_id)) != 0) {
+ /* Also not hexadecimal. */
+ fprintf(stderr, "%s: Invalid identifier format\n", *id);
+ ret = 1;
+ continue;
+ }
+
+ /* Zap the entry. */
+ if (pututxline(&utx) == NULL) {
+ perror(*id);
+ ret = 1;
+ }
+ }
+ return (ret);
+}
+
+static int
+boot(short type)
+{
+ struct utmpx utx = { .ut_type = type };
+
+ (void)gettimeofday(&utx.ut_tv, NULL);
+ if (pututxline(&utx) == NULL) {
+ perror("pututxline");
+ return (1);
+ }
+ return (0);
+}
+
+int
+main(int argc, char *argv[])
+{
+
+ if (argc == 2 && strcmp(argv[1], "boot") == 0)
+ return (boot(BOOT_TIME));
+ else if (argc == 2 && strcmp(argv[1], "shutdown") == 0)
+ return (boot(SHUTDOWN_TIME));
+ else if (argc >= 3 && strcmp(argv[1], "rm") == 0)
+ return (rm(&argv[2]));
+
+ fprintf(stderr,
+ "usage: utx boot\n"
+ " utx shutdown\n"
+ " utx rm identifier ...\n");
+ exit(1);
+}
diff --git a/usr.sbin/vidcontrol/Makefile b/usr.sbin/vidcontrol/Makefile
new file mode 100644
index 0000000..8c8f6bb
--- /dev/null
+++ b/usr.sbin/vidcontrol/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= vidcontrol
+SRCS= vidcontrol.c decode.c
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/vidcontrol/Makefile.depend b/usr.sbin/vidcontrol/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/vidcontrol/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/vidcontrol/decode.c b/usr.sbin/vidcontrol/decode.c
new file mode 100644
index 0000000..c7f416e
--- /dev/null
+++ b/usr.sbin/vidcontrol/decode.c
@@ -0,0 +1,99 @@
+/*-
+ * Copyright (c) 1994 Søren Schmidt
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer,
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must 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 <stdio.h>
+#include <string.h>
+#include "decode.h"
+
+int decode(FILE *fd, char *buffer, int len)
+{
+ int n, pos = 0, tpos;
+ char *bp, *p;
+ char tbuffer[3];
+ char temp[128];
+
+#define DEC(c) (((c) - ' ') & 0x3f)
+
+ do {
+ if (!fgets(temp, sizeof(temp), fd))
+ return(0);
+ } while (strncmp(temp, "begin ", 6));
+ sscanf(temp, "begin %o %s", (unsigned *)&n, temp);
+ bp = buffer;
+ for (;;) {
+ if (!fgets(p = temp, sizeof(temp), fd))
+ return(0);
+ if ((n = DEC(*p)) <= 0)
+ break;
+ for (++p; n > 0; p += 4, n -= 3) {
+ tpos = 0;
+ if (n >= 3) {
+ tbuffer[tpos++] = DEC(p[0])<<2 | DEC(p[1])>>4;
+ tbuffer[tpos++] = DEC(p[1])<<4 | DEC(p[2])>>2;
+ tbuffer[tpos++] = DEC(p[2])<<6 | DEC(p[3]);
+ }
+ else {
+ if (n >= 1) {
+ tbuffer[tpos++] =
+ DEC(p[0])<<2 | DEC(p[1])>>4;
+ }
+ if (n >= 2) {
+ tbuffer[tpos++] =
+ DEC(p[1])<<4 | DEC(p[2])>>2;
+ }
+ if (n >= 3) {
+ tbuffer[tpos++] =
+ DEC(p[2])<<6 | DEC(p[3]);
+ }
+ }
+ if (tpos == 0)
+ continue;
+ if (tpos + pos > len) {
+ tpos = len - pos;
+ /*
+ * Arrange return value > len to indicate
+ * overflow.
+ */
+ pos++;
+ }
+ bcopy(tbuffer, bp, tpos);
+ pos += tpos;
+ bp += tpos;
+ if (pos > len)
+ return(pos);
+ }
+ }
+ if (!fgets(temp, sizeof(temp), fd) || strcmp(temp, "end\n"))
+ return(0);
+ return(pos);
+}
diff --git a/usr.sbin/vidcontrol/decode.h b/usr.sbin/vidcontrol/decode.h
new file mode 100644
index 0000000..1f1cba4
--- /dev/null
+++ b/usr.sbin/vidcontrol/decode.h
@@ -0,0 +1,3 @@
+/* $FreeBSD$ */
+
+int decode(FILE *fd, char *buffer, int len);
diff --git a/usr.sbin/vidcontrol/path.h b/usr.sbin/vidcontrol/path.h
new file mode 100644
index 0000000..e1fa341
--- /dev/null
+++ b/usr.sbin/vidcontrol/path.h
@@ -0,0 +1,8 @@
+/* $FreeBSD$ */
+
+#define KEYMAP_PATH "/usr/share/syscons/keymaps/"
+#define FONT_PATH "/usr/share/syscons/fonts/"
+#define SCRNMAP_PATH "/usr/share/syscons/scrnmaps/"
+
+#define VT_KEYMAP_PATH "/usr/share/vt/keymaps/"
+#define VT_FONT_PATH "/usr/share/vt/fonts/"
diff --git a/usr.sbin/vidcontrol/vidcontrol.1 b/usr.sbin/vidcontrol/vidcontrol.1
new file mode 100644
index 0000000..d722628
--- /dev/null
+++ b/usr.sbin/vidcontrol/vidcontrol.1
@@ -0,0 +1,580 @@
+.\"
+.\" vidcontrol - a utility for manipulating the syscons or vt video driver
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" @(#)vidcontrol.1
+.\" $FreeBSD$
+.\"
+.Dd December 23, 2006
+.Dt VIDCONTROL 1
+.Os
+.Sh NAME
+.Nm vidcontrol
+.Nd system console control and configuration utility
+.Sh SYNOPSIS
+.Nm
+.Op Fl CdLHPpx
+.Op Fl b Ar color
+.Op Fl c Ar appearance
+.Oo
+.Fl f
+.Oo
+.Op Ar size
+.Ar file
+.Oc
+.Oc
+.Op Fl g Ar geometry
+.Op Fl h Ar size
+.Op Fl i Cm adapter | mode
+.Op Fl l Ar screen_map
+.Op Fl M Ar char
+.Op Fl m Cm on | off
+.Op Fl r Ar foreground Ar background
+.Op Fl S Cm on | off
+.Op Fl s Ar number
+.Op Fl T Cm xterm | cons25
+.Op Fl t Ar N | Cm off
+.Op Ar mode
+.Op Ar foreground Op Ar background
+.Op Cm show
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to set various options for the
+.Xr syscons 4
+or
+.Xr vt 4
+console driver,
+such as video mode, colors, cursor shape, screen output map, font and screen
+saver timeout.
+Only a small subset of options is supported by
+.Xr vt 4 .
+Unsupported options lead to error messages, typically including
+the text "Inappropriate ioctl for device".
+.Pp
+The following command line options are supported:
+.Bl -tag -width indent
+.It Ar mode
+Select a new video mode.
+The modes currently recognized are:
+.Ar 80x25 ,
+.Ar 80x30 ,
+.Ar 80x43 ,
+.Ar 80x50 ,
+.Ar 80x60 ,
+.Ar 132x25 ,
+.Ar 132x30 ,
+.Ar 132x43 ,
+.Ar 132x50 ,
+.Ar 132x60 ,
+.Ar VGA_40x25 ,
+.Ar VGA_80x25 ,
+.Ar VGA_80x30 ,
+.Ar VGA_80x50 ,
+.Ar VGA_80x60 ,
+.Ar VGA_90x25 ,
+.Ar VGA_90x30 ,
+.Ar VGA_90x43 ,
+.Ar VGA_90x50 ,
+.Ar VGA_90x60 ,
+.Ar EGA_80x25 ,
+.Ar EGA_80x43 ,
+.Ar VESA_132x25 ,
+.Ar VESA_132x43 ,
+.Ar VESA_132x50 ,
+.Ar VESA_132x60 .
+.\"The graphic mode
+.\".Ar VGA_320x200
+.\"and
+The raster text mode
+.Ar VESA_800x600
+can also be chosen.
+Alternatively, a mode can be specified with its number by using a mode name of
+the form
+.Li MODE_ Ns Aq Ar NUMBER .
+A list of valid mode numbers can be obtained with the
+.Fl i Cm mode
+option.
+See
+.Sx Video Mode Support
+below.
+.It Ar foreground Op Ar background
+Change colors when displaying text.
+Specify the foreground color
+(e.g.\&
+.Dq vidcontrol white ) ,
+or both a foreground and background colors
+(e.g.\&
+.Dq vidcontrol yellow blue ) .
+Use the
+.Cm show
+command below to see available colors.
+.It Cm show
+See the supported colors on a given platform.
+.It Fl b Ar color
+Set border color to
+.Ar color .
+This option may not be always supported by the video driver.
+.It Fl C
+Clear the history buffer.
+.It Fl c Cm normal | blink | destructive
+Change the cursor appearance.
+The cursor is either an inverting block
+.Pq Cm normal
+that can optionally
+.Cm blink ,
+or it can be like the old hardware cursor
+.Pq Cm destructive .
+The latter is actually a simulation.
+.It Fl d
+Print out current output screen map.
+.It Xo
+.Fl f
+.Oo
+.Op Ar size
+.Ar file
+.Oc
+.Xc
+Load font
+.Ar file
+for
+.Ar size
+(currently, only
+.Cm 8x8 ,
+.Cm 8x14
+or
+.Cm 8x16 ) .
+The font file can be either uuencoded or in raw binary format.
+You can also use the menu-driven
+.Xr vidfont 1
+command to load the font of your choice.
+.Pp
+.Ar Size
+may be omitted, in this case
+.Nm
+will try to guess it from the size of font file.
+.Pp
+When using
+.Xr vt 4
+both
+.Ar size
+and
+.Ar font
+can be omitted, and the default font will be loaded.
+.Pp
+Note that older video cards, such as MDA and CGA, do not support
+software font.
+See also
+.Sx Video Mode Support
+and
+.Sx EXAMPLES
+below and the man page for either
+.Xr syscons 4
+or
+.Xr vt 4
+(depending on which driver you use).
+.It Fl g Ar geometry
+Set the
+.Ar geometry
+of the text mode for the modes with selectable
+geometry.
+Currently only raster modes, such as
+.Ar VESA_800x600 ,
+support this option.
+See also
+.Sx Video Mode Support
+and
+.Sx EXAMPLES
+below.
+.It Fl h Ar size
+Set the size of the history (scrollback) buffer to
+.Ar size
+lines.
+.It Fl i Cm adapter
+Shows info about the current video adapter.
+.It Fl i Cm mode
+Shows the possible video modes with the current video hardware.
+.It Fl l Ar screen_map
+Install screen output map file from
+.Ar screen_map .
+See also
+.Xr syscons 4
+or
+.Xr vt 4
+(depending on which driver you use).
+.It Fl L
+Install default screen output map.
+.It Fl M Ar char
+Sets the base character used to render the mouse pointer to
+.Ar char .
+.It Fl m Cm on | off
+Switch the mouse pointer
+.Cm on
+or
+.Cm off .
+Used together with the
+.Xr moused 8
+daemon for text mode cut & paste functionality.
+.It Fl p
+Capture the current contents of the video buffer corresponding
+to the terminal device referred to by standard input.
+The
+.Nm
+utility writes contents of the video buffer to the standard
+output in a raw binary format.
+For details about that
+format see
+.Sx Format of Video Buffer Dump
+below.
+.It Fl P
+Same as
+.Fl p ,
+but dump contents of the video buffer in a plain text format
+ignoring nonprintable characters and information about text
+attributes.
+.It Fl H
+When used with
+.Fl p
+or
+.Fl P ,
+it instructs
+.Nm
+to dump full history buffer instead of visible portion of
+the video buffer only.
+.It Fl r Ar foreground background
+Change reverse mode colors to
+.Ar foreground
+and
+.Ar background .
+.It Fl S Cm on | off
+Turn vty switching on or off.
+When vty switching is off,
+attempts to switch to a different virtual terminal will fail.
+(The default is to permit vty switching.)
+This protection can be easily bypassed when the kernel is compiled with
+the
+.Dv DDB
+option.
+However, you probably should not compile the kernel debugger on a box which
+is supposed to be physically secure.
+.It Fl s Ar number
+Set the current vty to
+.Ar number .
+.It Fl T Cm xterm | cons25
+Switch between xterm and cons25 style terminal emulation.
+.It Fl t Ar N | Cm off
+Set the screensaver timeout to
+.Ar N
+seconds, or turns it
+.Cm off .
+.It Fl x
+Use hexadecimal digits for output.
+.El
+.Ss Video Mode Support
+Note that not all modes listed above may be supported by the video
+hardware.
+You can verify which mode is supported by the video hardware, using the
+.Fl i Cm mode
+option.
+.Pp
+The VESA BIOS support must be linked to the kernel
+or loaded as a KLD module if you wish to use VESA video modes
+or 132 column modes
+(see
+.Xr vga 4 ) .
+.Pp
+You need to compile your kernel with the
+.Ar VGA_WIDTH90
+option if you wish to use VGA 90 column modes
+(see
+.Xr vga 4 ) .
+.Pp
+Video modes other than 25 and 30 line modes may require specific size of font.
+Use
+.Fl f
+option above to load a font file to the kernel.
+If the required size of font has not been loaded to the kernel,
+.Nm
+will fail if the user attempts to set a new video mode.
+.Pp
+.Bl -column "25 line modes" "8x16 (VGA), 8x14 (EGA)" -compact
+.Sy Modes Ta Sy Font size
+.No 25 line modes Ta 8x16 (VGA), 8x14 (EGA)
+.No 30 line modes Ta 8x16
+.No 43 line modes Ta 8x8
+.No 50 line modes Ta 8x8
+.No 60 line modes Ta 8x8
+.El
+.Pp
+It is better to always load all three sizes (8x8, 8x14 and 8x16)
+of the same font.
+.Pp
+You may set variables in
+.Pa /etc/rc.conf
+or
+.Pa /etc/rc.conf.local
+so that desired font files will be automatically loaded
+when the system starts up.
+See below.
+.Pp
+If you want to use any of the raster text modes you need to recompile your
+kernel with the
+.Dv SC_PIXEL_MODE
+option.
+See
+.Xr syscons 4
+or
+.Xr vt 4
+(depending on which driver you use)
+for more details on this kernel option.
+.Ss Format of Video Buffer Dump
+The
+.Nm
+utility uses the
+.Xr syscons 4
+.\" is it supported on vt(4)???
+or
+.Xr vt 4
+.Dv CONS_SCRSHOT
+.Xr ioctl 2
+to capture the current contents of the video buffer.
+The
+.Nm
+utility writes version and additional information to the standard
+output, followed by the contents of the video buffer.
+.Pp
+VGA video memory is typically arranged in two byte tuples,
+one per character position.
+In each tuple, the first byte will be the character code,
+and the second byte is the character's color attribute.
+.Pp
+The VGA color attribute byte looks like this:
+.Bl -column "X:X" "<00000000>" "width" "bright foreground color"
+.Sy "bits# width meaning"
+.Li "7 <X0000000> 1 character blinking"
+.Li "6:4 <0XXX0000> 3 background color"
+.Li "3 <0000X000> 1 bright foreground color"
+.Li "2:0 <00000XXX> 3 foreground color"
+.El
+.Pp
+Here is a list of the three bit wide base colors:
+.Pp
+.Bl -hang -offset indent -compact
+.It 0
+Black
+.It 1
+Blue
+.It 2
+Green
+.It 3
+Cyan
+.It 4
+Red
+.It 5
+Magenta
+.It 6
+Brown
+.It 7
+Light Grey
+.El
+.Pp
+Base colors with bit 3 (the bright foreground flag) set:
+.Pp
+.Bl -hang -offset indent -compact
+.It 0
+Dark Grey
+.It 1
+Light Blue
+.It 2
+Light Green
+.It 3
+Light Cyan
+.It 4
+Light Red
+.It 5
+Light Magenta
+.It 6
+Yellow
+.It 7
+White
+.El
+.Pp
+For example, the two bytes
+.Pp
+.Dl "65 158"
+.Pp
+specify an uppercase A (character code 65), blinking
+(bit 7 set) in yellow (bits 3:0) on a blue background
+(bits 6:4).
+.Pp
+The
+.Nm
+output contains a small header which includes additional
+information which may be useful to utilities processing
+the output.
+.Pp
+The first 10 bytes are always arranged as follows:
+.Bl -column "Byte range" "Contents" -offset indent
+.It Sy "Byte Range Contents"
+.It "1 thru 8 Literal text" Dq Li SCRSHOT_
+.It "9 File format version number"
+.It "10 Remaining number of bytes in the header"
+.El
+.Pp
+Subsequent bytes depend on the version number.
+.Bl -column "Version" "13 and up" -offset indent
+.It Sy "Version Byte Meaning"
+.It "1 11 Terminal width, in characters"
+.It " 12 Terminal depth, in characters"
+.It " 13 and up The snapshot data"
+.El
+.Pp
+So a dump of an 80x25 screen would start (in hex)
+.Bd -literal -offset indent
+53 43 52 53 48 4f 54 5f 01 02 50 19
+----------------------- -- -- -- --
+ | | | | ` 25 decimal
+ | | | `--- 80 decimal
+ | | `------ 2 remaining bytes of header data
+ | `--------- File format version 1
+ `------------------------ Literal "SCRSHOT_"
+.Ed
+.Sh VIDEO OUTPUT CONFIGURATION
+.Ss Boot Time Configuration
+You may set the following variables in
+.Pa /etc/rc.conf
+or
+.Pa /etc/rc.conf.local
+in order to configure the video output at boot time.
+.Pp
+.Bl -tag -width foo_bar_var -compact
+.It Ar blanktime
+Sets the timeout value for the
+.Fl t
+option.
+.It Ar font8x16 , font8x14 , font8x8
+Specifies font files for the
+.Fl f
+option.
+.It Ar scrnmap
+Specifies a screen output map file for the
+.Fl l
+option.
+.El
+.Pp
+See
+.Xr rc.conf 5
+for more details.
+.Ss Driver Configuration
+The video card driver may let you change default configuration
+options, such as the default font, so that you do not need to set up
+the options at boot time.
+See video card driver manuals, (e.g.\&
+.Xr vga 4 )
+for details.
+.Sh FILES
+.Bl -tag -width /usr/share/syscons/scrnmaps/foo-bar -compact
+.It Pa /usr/share/syscons/fonts/*
+.It Pa /usr/share/vt/fonts/*
+font files.
+.It Pa /usr/share/syscons/scrnmaps/*
+screen output map files (relevant for
+.Xr syscons 4
+only).
+.El
+.Sh EXAMPLES
+If you want to load
+.Pa /usr/share/syscons/fonts/iso-8x16.fnt
+to the kernel, run
+.Nm
+as:
+.Pp
+.Dl vidcontrol -f 8x16 /usr/share/syscons/fonts/iso-8x16.fnt
+.Pp
+So long as the font file is in
+.Pa /usr/share/syscons/fonts
+(if using syscons) or
+.Pa /usr/share/vt/fonts
+(if using vt),
+you may abbreviate the file name as
+.Pa iso-8x16 :
+.Pp
+.Dl vidcontrol -f 8x16 iso-8x16
+.Pp
+Furthermore, you can also omit font size
+.Dq Li 8x16 :
+.Pp
+.Dl vidcontrol -f iso-8x16
+.Pp
+Moreover, the suffix specifying the font size can be also omitted; in
+this case,
+.Nm
+will use the size of the currently displayed font to construct the
+suffix:
+.Pp
+.Dl vidcontrol -f iso
+.Pp
+Likewise, you can also abbreviate the screen output map file name for
+the
+.Fl l
+option if the file is found in
+.Pa /usr/share/syscons/scrnmaps .
+.Pp
+.Dl vidcontrol -l iso-8859-1_to_cp437
+.Pp
+The above command will load
+.Pa /usr/share/syscons/scrnmaps/iso-8859-1_to_cp437.scm .
+.Pp
+The following command will set-up a 100x37 raster text mode (useful for
+some LCD models):
+.Pp
+.Dl vidcontrol -g 100x37 VESA_800x600
+.Pp
+The following command will capture the contents of the first virtual
+terminal video buffer, and redirect the output to the
+.Pa shot.scr
+file:
+.Pp
+.Dl vidcontrol -p < /dev/ttyv0 > shot.scr
+.Pp
+The following command will dump contents of the fourth virtual terminal
+video buffer
+to the standard output in the human readable format:
+.Pp
+.Dl vidcontrol -P < /dev/ttyv3
+.Sh SEE ALSO
+.Xr kbdcontrol 1 ,
+.Xr vidfont 1 ,
+.Xr keyboard 4 ,
+.Xr screen 4 ,
+.Xr syscons 4 ,
+.Xr vga 4 ,
+.Xr vt 4 ,
+.Xr rc.conf 5 ,
+.Xr kldload 8 ,
+.Xr moused 8 ,
+.Xr watch 8
+.Pp
+The various
+.Pa scr2*
+utilities in the
+.Pa graphics
+and
+.Pa textproc
+categories of the
+.Em "Ports Collection" .
+.Sh AUTHORS
+.An S\(/oren Schmidt Aq Mt sos@FreeBSD.org
+.An Sascha Wildner Aq Mt saw@online.de
+.Sh CONTRIBUTORS
+.An -split
+.An Maxim Sobolev Aq Mt sobomax@FreeBSD.org
+.An Nik Clayton Aq Mt nik@FreeBSD.org
diff --git a/usr.sbin/vidcontrol/vidcontrol.c b/usr.sbin/vidcontrol/vidcontrol.c
new file mode 100644
index 0000000..cbf8f47
--- /dev/null
+++ b/usr.sbin/vidcontrol/vidcontrol.c
@@ -0,0 +1,1484 @@
+/*-
+ * Copyright (c) 1994-1996 Søren Schmidt
+ * All rights reserved.
+ *
+ * Portions of this software are based in part on the work of
+ * Sascha Wildner <saw@online.de> contributed to The DragonFly 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,
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must 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.
+ *
+ * $DragonFly: src/usr.sbin/vidcontrol/vidcontrol.c,v 1.10 2005/03/02 06:08:29 joerg Exp $
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <ctype.h>
+#include <err.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/fbio.h>
+#include <sys/consio.h>
+#include <sys/endian.h>
+#include <sys/errno.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include "path.h"
+#include "decode.h"
+
+
+#define DATASIZE(x) ((x).w * (x).h * 256 / 8)
+
+/* Screen dump modes */
+#define DUMP_FMT_RAW 1
+#define DUMP_FMT_TXT 2
+/* Screen dump options */
+#define DUMP_FBF 0
+#define DUMP_ALL 1
+/* Screen dump file format revision */
+#define DUMP_FMT_REV 1
+
+static const char *legal_colors[16] = {
+ "black", "blue", "green", "cyan",
+ "red", "magenta", "brown", "white",
+ "grey", "lightblue", "lightgreen", "lightcyan",
+ "lightred", "lightmagenta", "yellow", "lightwhite"
+};
+
+static struct {
+ int active_vty;
+ vid_info_t console_info;
+ unsigned char screen_map[256];
+ int video_mode_number;
+ struct video_info video_mode_info;
+} cur_info;
+
+struct vt4font_header {
+ uint8_t magic[8];
+ uint8_t width;
+ uint8_t height;
+ uint16_t pad;
+ uint32_t glyph_count;
+ uint32_t map_count[4];
+} __packed;
+
+static int hex = 0;
+static int vesa_cols;
+static int vesa_rows;
+static int font_height;
+static int colors_changed;
+static int video_mode_changed;
+static int normal_fore_color, normal_back_color;
+static int revers_fore_color, revers_back_color;
+static int vt4_mode = 0;
+static struct vid_info info;
+static struct video_info new_mode_info;
+
+
+/*
+ * Initialize revert data.
+ *
+ * NOTE: the following parameters are not yet saved/restored:
+ *
+ * screen saver timeout
+ * cursor type
+ * mouse character and mouse show/hide state
+ * vty switching on/off state
+ * history buffer size
+ * history contents
+ * font maps
+ */
+
+static void
+init(void)
+{
+ if (ioctl(0, VT_GETACTIVE, &cur_info.active_vty) == -1)
+ errc(1, errno, "getting active vty");
+
+ cur_info.console_info.size = sizeof(cur_info.console_info);
+
+ if (ioctl(0, CONS_GETINFO, &cur_info.console_info) == -1)
+ errc(1, errno, "getting console information");
+
+ /* vt(4) use unicode, so no screen mapping required. */
+ if (vt4_mode == 0 &&
+ ioctl(0, GIO_SCRNMAP, &cur_info.screen_map) == -1)
+ errc(1, errno, "getting screen map");
+
+ if (ioctl(0, CONS_GET, &cur_info.video_mode_number) == -1)
+ errc(1, errno, "getting video mode number");
+
+ cur_info.video_mode_info.vi_mode = cur_info.video_mode_number;
+
+ if (ioctl(0, CONS_MODEINFO, &cur_info.video_mode_info) == -1)
+ errc(1, errno, "getting video mode parameters");
+
+ normal_fore_color = cur_info.console_info.mv_norm.fore;
+ normal_back_color = cur_info.console_info.mv_norm.back;
+ revers_fore_color = cur_info.console_info.mv_rev.fore;
+ revers_back_color = cur_info.console_info.mv_rev.back;
+}
+
+
+/*
+ * If something goes wrong along the way we call revert() to go back to the
+ * console state we came from (which is assumed to be working).
+ *
+ * NOTE: please also read the comments of init().
+ */
+
+static void
+revert(void)
+{
+ int size[3];
+
+ ioctl(0, VT_ACTIVATE, cur_info.active_vty);
+
+ fprintf(stderr, "\033[=%dA", cur_info.console_info.mv_ovscan);
+ fprintf(stderr, "\033[=%dF", cur_info.console_info.mv_norm.fore);
+ fprintf(stderr, "\033[=%dG", cur_info.console_info.mv_norm.back);
+ fprintf(stderr, "\033[=%dH", cur_info.console_info.mv_rev.fore);
+ fprintf(stderr, "\033[=%dI", cur_info.console_info.mv_rev.back);
+
+ if (vt4_mode == 0)
+ ioctl(0, PIO_SCRNMAP, &cur_info.screen_map);
+
+ if (cur_info.video_mode_number >= M_VESA_BASE)
+ ioctl(0, _IO('V', cur_info.video_mode_number - M_VESA_BASE),
+ NULL);
+ else
+ ioctl(0, _IO('S', cur_info.video_mode_number), NULL);
+
+ if (cur_info.video_mode_info.vi_flags & V_INFO_GRAPHICS) {
+ size[0] = cur_info.video_mode_info.vi_width / 8;
+ size[1] = cur_info.video_mode_info.vi_height /
+ cur_info.console_info.font_size;
+ size[2] = cur_info.console_info.font_size;
+
+ ioctl(0, KDRASTER, size);
+ }
+}
+
+
+/*
+ * Print a short usage string describing all options, then exit.
+ */
+
+static void
+usage(void)
+{
+ if (vt4_mode)
+ fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n",
+"usage: vidcontrol [-CHPpx] [-b color] [-c appearance] [-f [[size] file]]",
+" [-g geometry] [-h size] [-i adapter | mode]",
+" [-M char] [-m on | off] [-r foreground background]",
+" [-S on | off] [-s number] [-T xterm | cons25] [-t N | off]",
+" [mode] [foreground [background]] [show]");
+ else
+ fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n",
+"usage: vidcontrol [-CdHLPpx] [-b color] [-c appearance] [-f [size] file]",
+" [-g geometry] [-h size] [-i adapter | mode] [-l screen_map]",
+" [-M char] [-m on | off] [-r foreground background]",
+" [-S on | off] [-s number] [-T xterm | cons25] [-t N | off]",
+" [mode] [foreground [background]] [show]");
+ exit(1);
+}
+
+/* Detect presence of vt(4). */
+static int
+is_vt4(void)
+{
+ char vty_name[4] = "";
+ size_t len = sizeof(vty_name);
+
+ if (sysctlbyname("kern.vty", vty_name, &len, NULL, 0) != 0)
+ return (0);
+ return (strcmp(vty_name, "vt") == 0);
+}
+
+/*
+ * Retrieve the next argument from the command line (for options that require
+ * more than one argument).
+ */
+
+static char *
+nextarg(int ac, char **av, int *indp, int oc, int strict)
+{
+ if (*indp < ac)
+ return(av[(*indp)++]);
+
+ if (strict != 0) {
+ revert();
+ errx(1, "option requires two arguments -- %c", oc);
+ }
+
+ return(NULL);
+}
+
+
+/*
+ * Guess which file to open. Try to open each combination of a specified set
+ * of file name components.
+ */
+
+static FILE *
+openguess(const char *a[], const char *b[], const char *c[], const char *d[], char **name)
+{
+ FILE *f;
+ int i, j, k, l;
+
+ for (i = 0; a[i] != NULL; i++) {
+ for (j = 0; b[j] != NULL; j++) {
+ for (k = 0; c[k] != NULL; k++) {
+ for (l = 0; d[l] != NULL; l++) {
+ asprintf(name, "%s%s%s%s",
+ a[i], b[j], c[k], d[l]);
+
+ f = fopen(*name, "r");
+
+ if (f != NULL)
+ return (f);
+
+ free(*name);
+ }
+ }
+ }
+ }
+ return (NULL);
+}
+
+
+/*
+ * Load a screenmap from a file and set it.
+ */
+
+static void
+load_scrnmap(const char *filename)
+{
+ FILE *fd;
+ int size;
+ char *name;
+ scrmap_t scrnmap;
+ const char *a[] = {"", SCRNMAP_PATH, NULL};
+ const char *b[] = {filename, NULL};
+ const char *c[] = {"", ".scm", NULL};
+ const char *d[] = {"", NULL};
+
+ fd = openguess(a, b, c, d, &name);
+
+ if (fd == NULL) {
+ revert();
+ errx(1, "screenmap file not found");
+ }
+
+ size = sizeof(scrnmap);
+
+ if (decode(fd, (char *)&scrnmap, size) != size) {
+ rewind(fd);
+
+ if (fread(&scrnmap, 1, size, fd) != (size_t)size) {
+ warnx("bad screenmap file");
+ fclose(fd);
+ revert();
+ errx(1, "bad screenmap file");
+ }
+ }
+
+ if (ioctl(0, PIO_SCRNMAP, &scrnmap) == -1) {
+ revert();
+ errc(1, errno, "loading screenmap");
+ }
+
+ fclose(fd);
+}
+
+
+/*
+ * Set the default screenmap.
+ */
+
+static void
+load_default_scrnmap(void)
+{
+ scrmap_t scrnmap;
+ int i;
+
+ for (i=0; i<256; i++)
+ *((char*)&scrnmap + i) = i;
+
+ if (ioctl(0, PIO_SCRNMAP, &scrnmap) == -1) {
+ revert();
+ errc(1, errno, "loading default screenmap");
+ }
+}
+
+
+/*
+ * Print the current screenmap to stdout.
+ */
+
+static void
+print_scrnmap(void)
+{
+ unsigned char map[256];
+ size_t i;
+
+ if (ioctl(0, GIO_SCRNMAP, &map) == -1) {
+ revert();
+ errc(1, errno, "getting screenmap");
+ }
+ for (i=0; i<sizeof(map); i++) {
+ if (i != 0 && i % 16 == 0)
+ fprintf(stdout, "\n");
+
+ if (hex != 0)
+ fprintf(stdout, " %02x", map[i]);
+ else
+ fprintf(stdout, " %03d", map[i]);
+ }
+ fprintf(stdout, "\n");
+
+}
+
+
+/*
+ * Determine a file's size.
+ */
+
+static int
+fsize(FILE *file)
+{
+ struct stat sb;
+
+ if (fstat(fileno(file), &sb) == 0)
+ return sb.st_size;
+ else
+ return -1;
+}
+
+static vfnt_map_t *
+load_vt4mappingtable(unsigned int nmappings, FILE *f)
+{
+ vfnt_map_t *t;
+ unsigned int i;
+
+ if (nmappings == 0)
+ return (NULL);
+
+ t = malloc(sizeof *t * nmappings);
+
+ if (fread(t, sizeof *t * nmappings, 1, f) != 1) {
+ perror("mappings");
+ exit(1);
+ }
+
+ for (i = 0; i < nmappings; i++) {
+ t[i].src = be32toh(t[i].src);
+ t[i].dst = be16toh(t[i].dst);
+ t[i].len = be16toh(t[i].len);
+ }
+
+ return (t);
+}
+
+/*
+ * Set the default vt font.
+ */
+
+static void
+load_default_vt4font(void)
+{
+ if (ioctl(0, PIO_VFONT_DEFAULT) == -1) {
+ revert();
+ errc(1, errno, "loading default vt font");
+ }
+}
+
+static int
+load_vt4font(FILE *f)
+{
+ struct vt4font_header fh;
+ static vfnt_t vfnt;
+ size_t glyphsize;
+ unsigned int i;
+
+ if (fread(&fh, sizeof fh, 1, f) != 1) {
+ perror("file_header");
+ return (1);
+ }
+
+ if (memcmp(fh.magic, "VFNT0002", 8) != 0) {
+ fprintf(stderr, "Bad magic\n");
+ return (1);
+ }
+
+ for (i = 0; i < VFNT_MAPS; i++)
+ vfnt.map_count[i] = be32toh(fh.map_count[i]);
+ vfnt.glyph_count = be32toh(fh.glyph_count);
+ vfnt.width = fh.width;
+ vfnt.height = fh.height;
+
+ glyphsize = howmany(vfnt.width, 8) * vfnt.height * vfnt.glyph_count;
+ vfnt.glyphs = malloc(glyphsize);
+
+ if (fread(vfnt.glyphs, glyphsize, 1, f) != 1) {
+ perror("glyphs");
+ return (1);
+ }
+
+ for (i = 0; i < VFNT_MAPS; i++)
+ vfnt.map[i] = load_vt4mappingtable(vfnt.map_count[i], f);
+
+ if (ioctl(STDIN_FILENO, PIO_VFONT, &vfnt) == -1) {
+ perror("PIO_VFONT");
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * Load a font from file and set it.
+ */
+
+static void
+load_font(const char *type, const char *filename)
+{
+ FILE *fd;
+ int h, i, size, w;
+ unsigned long io = 0; /* silence stupid gcc(1) in the Wall mode */
+ char *name, *fontmap, size_sufx[6];
+ const char *a[] = {"", FONT_PATH, NULL};
+ const char *vt4a[] = {"", VT_FONT_PATH, NULL};
+ const char *b[] = {filename, NULL};
+ const char *c[] = {"", size_sufx, NULL};
+ const char *d[] = {"", ".fnt", NULL};
+ vid_info_t _info;
+
+ struct sizeinfo {
+ int w;
+ int h;
+ unsigned long io;
+ } sizes[] = {{8, 16, PIO_FONT8x16},
+ {8, 14, PIO_FONT8x14},
+ {8, 8, PIO_FONT8x8},
+ {0, 0, 0}};
+
+ if (vt4_mode) {
+ size_sufx[0] = '\0';
+ } else {
+ _info.size = sizeof(_info);
+ if (ioctl(0, CONS_GETINFO, &_info) == -1) {
+ revert();
+ warn("failed to obtain current video mode parameters");
+ return;
+ }
+
+ snprintf(size_sufx, sizeof(size_sufx), "-8x%d", _info.font_size);
+ }
+ fd = openguess((vt4_mode == 0) ? a : vt4a, b, c, d, &name);
+
+ if (fd == NULL) {
+ revert();
+ errx(1, "%s: can't load font file", filename);
+ }
+
+ if (vt4_mode) {
+ if(load_vt4font(fd))
+ warn("failed to load font \"%s\"", filename);
+ fclose(fd);
+ return;
+ }
+
+ if (type != NULL) {
+ size = 0;
+ if (sscanf(type, "%dx%d", &w, &h) == 2) {
+ for (i = 0; sizes[i].w != 0; i++) {
+ if (sizes[i].w == w && sizes[i].h == h) {
+ size = DATASIZE(sizes[i]);
+ io = sizes[i].io;
+ font_height = sizes[i].h;
+ }
+ }
+ }
+ if (size == 0) {
+ fclose(fd);
+ revert();
+ errx(1, "%s: bad font size specification", type);
+ }
+ } else {
+ /* Apply heuristics */
+
+ int j;
+ int dsize[2];
+
+ size = DATASIZE(sizes[0]);
+ fontmap = (char*) malloc(size);
+ dsize[0] = decode(fd, fontmap, size);
+ dsize[1] = fsize(fd);
+ free(fontmap);
+
+ size = 0;
+ for (j = 0; j < 2; j++) {
+ for (i = 0; sizes[i].w != 0; i++) {
+ if (DATASIZE(sizes[i]) == dsize[j]) {
+ size = dsize[j];
+ io = sizes[i].io;
+ font_height = sizes[i].h;
+ j = 2; /* XXX */
+ break;
+ }
+ }
+ }
+
+ if (size == 0) {
+ fclose(fd);
+ revert();
+ errx(1, "%s: can't guess font size", filename);
+ }
+
+ rewind(fd);
+ }
+
+ fontmap = (char*) malloc(size);
+
+ if (decode(fd, fontmap, size) != size) {
+ rewind(fd);
+ if (fsize(fd) != size ||
+ fread(fontmap, 1, size, fd) != (size_t)size) {
+ warnx("%s: bad font file", filename);
+ fclose(fd);
+ free(fontmap);
+ revert();
+ errx(1, "%s: bad font file", filename);
+ }
+ }
+
+ if (ioctl(0, io, fontmap) == -1) {
+ revert();
+ errc(1, errno, "loading font");
+ }
+
+ fclose(fd);
+ free(fontmap);
+}
+
+
+/*
+ * Set the timeout for the screensaver.
+ */
+
+static void
+set_screensaver_timeout(char *arg)
+{
+ int nsec;
+
+ if (!strcmp(arg, "off")) {
+ nsec = 0;
+ } else {
+ nsec = atoi(arg);
+
+ if ((*arg == '\0') || (nsec < 1)) {
+ revert();
+ errx(1, "argument must be a positive number");
+ }
+ }
+
+ if (ioctl(0, CONS_BLANKTIME, &nsec) == -1) {
+ revert();
+ errc(1, errno, "setting screensaver period");
+ }
+}
+
+
+/*
+ * Set the cursor's shape/type.
+ */
+
+static void
+set_cursor_type(char *appearance)
+{
+ int type;
+
+ if (!strcmp(appearance, "normal"))
+ type = 0;
+ else if (!strcmp(appearance, "blink"))
+ type = 1;
+ else if (!strcmp(appearance, "destructive"))
+ type = 3;
+ else {
+ revert();
+ errx(1, "argument to -c must be normal, blink or destructive");
+ }
+
+ if (ioctl(0, CONS_CURSORTYPE, &type) == -1) {
+ revert();
+ errc(1, errno, "setting cursor type");
+ }
+}
+
+
+/*
+ * Set the video mode.
+ */
+
+static int
+video_mode(int argc, char **argv, int *mode_index)
+{
+ static struct {
+ const char *name;
+ unsigned long mode;
+ unsigned long mode_num;
+ } modes[] = {
+ { "80x25", SW_TEXT_80x25, M_TEXT_80x25 },
+ { "80x30", SW_TEXT_80x30, M_TEXT_80x30 },
+ { "80x43", SW_TEXT_80x43, M_TEXT_80x43 },
+ { "80x50", SW_TEXT_80x50, M_TEXT_80x50 },
+ { "80x60", SW_TEXT_80x60, M_TEXT_80x60 },
+ { "132x25", SW_TEXT_132x25, M_TEXT_132x25 },
+ { "132x30", SW_TEXT_132x30, M_TEXT_132x30 },
+ { "132x43", SW_TEXT_132x43, M_TEXT_132x43 },
+ { "132x50", SW_TEXT_132x50, M_TEXT_132x50 },
+ { "132x60", SW_TEXT_132x60, M_TEXT_132x60 },
+ { "VGA_40x25", SW_VGA_C40x25, M_VGA_C40x25 },
+ { "VGA_80x25", SW_VGA_C80x25, M_VGA_C80x25 },
+ { "VGA_80x30", SW_VGA_C80x30, M_VGA_C80x30 },
+ { "VGA_80x50", SW_VGA_C80x50, M_VGA_C80x50 },
+ { "VGA_80x60", SW_VGA_C80x60, M_VGA_C80x60 },
+#ifdef SW_VGA_C90x25
+ { "VGA_90x25", SW_VGA_C90x25, M_VGA_C90x25 },
+ { "VGA_90x30", SW_VGA_C90x30, M_VGA_C90x30 },
+ { "VGA_90x43", SW_VGA_C90x43, M_VGA_C90x43 },
+ { "VGA_90x50", SW_VGA_C90x50, M_VGA_C90x50 },
+ { "VGA_90x60", SW_VGA_C90x60, M_VGA_C90x60 },
+#endif
+ { "VGA_320x200", SW_VGA_CG320, M_CG320 },
+ { "EGA_80x25", SW_ENH_C80x25, M_ENH_C80x25 },
+ { "EGA_80x43", SW_ENH_C80x43, M_ENH_C80x43 },
+ { "VESA_132x25", SW_VESA_C132x25,M_VESA_C132x25 },
+ { "VESA_132x43", SW_VESA_C132x43,M_VESA_C132x43 },
+ { "VESA_132x50", SW_VESA_C132x50,M_VESA_C132x50 },
+ { "VESA_132x60", SW_VESA_C132x60,M_VESA_C132x60 },
+ { "VESA_800x600", SW_VESA_800x600,M_VESA_800x600 },
+ { NULL, 0, 0 },
+ };
+
+ int new_mode_num = 0;
+ unsigned long mode = 0;
+ int cur_mode;
+ int ioerr;
+ int size[3];
+ int i;
+
+ if (ioctl(0, CONS_GET, &cur_mode) < 0)
+ err(1, "cannot get the current video mode");
+
+ /*
+ * Parse the video mode argument...
+ */
+
+ if (*mode_index < argc) {
+ if (!strncmp(argv[*mode_index], "MODE_", 5)) {
+ if (!isdigit(argv[*mode_index][5]))
+ errx(1, "invalid video mode number");
+
+ new_mode_num = atoi(&argv[*mode_index][5]);
+ } else {
+ for (i = 0; modes[i].name != NULL; ++i) {
+ if (!strcmp(argv[*mode_index], modes[i].name)) {
+ mode = modes[i].mode;
+ new_mode_num = modes[i].mode_num;
+ break;
+ }
+ }
+
+ if (modes[i].name == NULL)
+ return EXIT_FAILURE;
+ if (ioctl(0, mode, NULL) < 0) {
+ warn("cannot set videomode");
+ return EXIT_FAILURE;
+ }
+ }
+
+ /*
+ * Collect enough information about the new video mode...
+ */
+
+ new_mode_info.vi_mode = new_mode_num;
+
+ if (ioctl(0, CONS_MODEINFO, &new_mode_info) == -1) {
+ revert();
+ errc(1, errno, "obtaining new video mode parameters");
+ }
+
+ if (mode == 0) {
+ if (new_mode_num >= M_VESA_BASE)
+ mode = _IO('V', new_mode_num - M_VESA_BASE);
+ else
+ mode = _IO('S', new_mode_num);
+ }
+
+ /*
+ * Try setting the new mode.
+ */
+
+ if (ioctl(0, mode, NULL) == -1) {
+ revert();
+ errc(1, errno, "setting video mode");
+ }
+
+ /*
+ * For raster modes it's not enough to just set the mode.
+ * We also need to explicitly set the raster mode.
+ */
+
+ if (new_mode_info.vi_flags & V_INFO_GRAPHICS) {
+ /* font size */
+
+ if (font_height == 0)
+ font_height = cur_info.console_info.font_size;
+
+ size[2] = font_height;
+
+ /* adjust columns */
+
+ if ((vesa_cols * 8 > new_mode_info.vi_width) ||
+ (vesa_cols <= 0)) {
+ size[0] = new_mode_info.vi_width / 8;
+ } else {
+ size[0] = vesa_cols;
+ }
+
+ /* adjust rows */
+
+ if ((vesa_rows * font_height > new_mode_info.vi_height) ||
+ (vesa_rows <= 0)) {
+ size[1] = new_mode_info.vi_height /
+ font_height;
+ } else {
+ size[1] = vesa_rows;
+ }
+
+ /* set raster mode */
+
+ if (ioctl(0, KDRASTER, size)) {
+ ioerr = errno;
+ if (cur_mode >= M_VESA_BASE)
+ ioctl(0,
+ _IO('V', cur_mode - M_VESA_BASE),
+ NULL);
+ else
+ ioctl(0, _IO('S', cur_mode), NULL);
+ revert();
+ warnc(ioerr, "cannot activate raster display");
+ return EXIT_FAILURE;
+ }
+ }
+
+ video_mode_changed = 1;
+
+ (*mode_index)++;
+ }
+ return EXIT_SUCCESS;
+}
+
+
+/*
+ * Return the number for a specified color name.
+ */
+
+static int
+get_color_number(char *color)
+{
+ int i;
+
+ for (i=0; i<16; i++) {
+ if (!strcmp(color, legal_colors[i]))
+ return i;
+ }
+ return -1;
+}
+
+
+/*
+ * Get normal text and background colors.
+ */
+
+static void
+get_normal_colors(int argc, char **argv, int *_index)
+{
+ int color;
+
+ if (*_index < argc && (color = get_color_number(argv[*_index])) != -1) {
+ (*_index)++;
+ fprintf(stderr, "\033[=%dF", color);
+ normal_fore_color=color;
+ colors_changed = 1;
+ if (*_index < argc
+ && (color = get_color_number(argv[*_index])) != -1
+ && color < 8) {
+ (*_index)++;
+ fprintf(stderr, "\033[=%dG", color);
+ normal_back_color=color;
+ }
+ }
+}
+
+
+/*
+ * Get reverse text and background colors.
+ */
+
+static void
+get_reverse_colors(int argc, char **argv, int *_index)
+{
+ int color;
+
+ if ((color = get_color_number(argv[*(_index)-1])) != -1) {
+ fprintf(stderr, "\033[=%dH", color);
+ revers_fore_color=color;
+ colors_changed = 1;
+ if (*_index < argc
+ && (color = get_color_number(argv[*_index])) != -1
+ && color < 8) {
+ (*_index)++;
+ fprintf(stderr, "\033[=%dI", color);
+ revers_back_color=color;
+ }
+ }
+}
+
+
+/*
+ * Set normal and reverse foreground and background colors.
+ */
+
+static void
+set_colors(void)
+{
+ fprintf(stderr, "\033[=%dF", normal_fore_color);
+ fprintf(stderr, "\033[=%dG", normal_back_color);
+ fprintf(stderr, "\033[=%dH", revers_fore_color);
+ fprintf(stderr, "\033[=%dI", revers_back_color);
+}
+
+
+/*
+ * Switch to virtual terminal #arg.
+ */
+
+static void
+set_console(char *arg)
+{
+ int n;
+
+ if(!arg || strspn(arg,"0123456789") != strlen(arg)) {
+ revert();
+ errx(1, "bad console number");
+ }
+
+ n = atoi(arg);
+
+ if (n < 1 || n > 16) {
+ revert();
+ errx(1, "console number out of range");
+ } else if (ioctl(0, VT_ACTIVATE, n) == -1) {
+ revert();
+ errc(1, errno, "switching vty");
+ }
+}
+
+
+/*
+ * Sets the border color.
+ */
+
+static void
+set_border_color(char *arg)
+{
+ int color;
+
+ if ((color = get_color_number(arg)) != -1) {
+ fprintf(stderr, "\033[=%dA", color);
+ }
+ else
+ usage();
+}
+
+static void
+set_mouse_char(char *arg)
+{
+ struct mouse_info mouse;
+ long l;
+
+ l = strtol(arg, NULL, 0);
+
+ if ((l < 0) || (l > UCHAR_MAX - 3)) {
+ revert();
+ warnx("argument to -M must be 0 through %d", UCHAR_MAX - 3);
+ return;
+ }
+
+ mouse.operation = MOUSE_MOUSECHAR;
+ mouse.u.mouse_char = (int)l;
+
+ if (ioctl(0, CONS_MOUSECTL, &mouse) == -1) {
+ revert();
+ errc(1, errno, "setting mouse character");
+ }
+}
+
+
+/*
+ * Show/hide the mouse.
+ */
+
+static void
+set_mouse(char *arg)
+{
+ struct mouse_info mouse;
+
+ if (!strcmp(arg, "on")) {
+ mouse.operation = MOUSE_SHOW;
+ } else if (!strcmp(arg, "off")) {
+ mouse.operation = MOUSE_HIDE;
+ } else {
+ revert();
+ errx(1, "argument to -m must be either on or off");
+ }
+
+ if (ioctl(0, CONS_MOUSECTL, &mouse) == -1) {
+ revert();
+ errc(1, errno, "%sing the mouse",
+ mouse.operation == MOUSE_SHOW ? "show" : "hid");
+ }
+}
+
+
+static void
+set_lockswitch(char *arg)
+{
+ int data;
+
+ if (!strcmp(arg, "off")) {
+ data = 0x01;
+ } else if (!strcmp(arg, "on")) {
+ data = 0x02;
+ } else {
+ revert();
+ errx(1, "argument to -S must be either on or off");
+ }
+
+ if (ioctl(0, VT_LOCKSWITCH, &data) == -1) {
+ revert();
+ errc(1, errno, "turning %s vty switching",
+ data == 0x01 ? "off" : "on");
+ }
+}
+
+
+/*
+ * Return the adapter name for a specified type.
+ */
+
+static const char
+*adapter_name(int type)
+{
+ static struct {
+ int type;
+ const char *name;
+ } names[] = {
+ { KD_MONO, "MDA" },
+ { KD_HERCULES, "Hercules" },
+ { KD_CGA, "CGA" },
+ { KD_EGA, "EGA" },
+ { KD_VGA, "VGA" },
+ { KD_PC98, "PC-98xx" },
+ { KD_TGA, "TGA" },
+ { -1, "Unknown" },
+ };
+
+ int i;
+
+ for (i = 0; names[i].type != -1; ++i)
+ if (names[i].type == type)
+ break;
+ return names[i].name;
+}
+
+
+/*
+ * Show graphics adapter information.
+ */
+
+static void
+show_adapter_info(void)
+{
+ struct video_adapter_info ad;
+
+ ad.va_index = 0;
+
+ if (ioctl(0, CONS_ADPINFO, &ad) == -1) {
+ revert();
+ errc(1, errno, "obtaining adapter information");
+ }
+
+ printf("fb%d:\n", ad.va_index);
+ printf(" %.*s%d, type:%s%s (%d), flags:0x%x\n",
+ (int)sizeof(ad.va_name), ad.va_name, ad.va_unit,
+ (ad.va_flags & V_ADP_VESA) ? "VESA " : "",
+ adapter_name(ad.va_type), ad.va_type, ad.va_flags);
+ printf(" initial mode:%d, current mode:%d, BIOS mode:%d\n",
+ ad.va_initial_mode, ad.va_mode, ad.va_initial_bios_mode);
+ printf(" frame buffer window:0x%zx, buffer size:0x%zx\n",
+ ad.va_window, ad.va_buffer_size);
+ printf(" window size:0x%zx, origin:0x%x\n",
+ ad.va_window_size, ad.va_window_orig);
+ printf(" display start address (%d, %d), scan line width:%d\n",
+ ad.va_disp_start.x, ad.va_disp_start.y, ad.va_line_width);
+ printf(" reserved:0x%zx\n", ad.va_unused0);
+}
+
+
+/*
+ * Show video mode information.
+ */
+
+static void
+show_mode_info(void)
+{
+ char buf[80];
+ struct video_info _info;
+ int c;
+ int mm;
+ int mode;
+
+ printf(" mode# flags type size "
+ "font window linear buffer\n");
+ printf("---------------------------------------"
+ "---------------------------------------\n");
+
+ memset(&_info, 0, sizeof(_info));
+ for (mode = 0; mode <= M_VESA_MODE_MAX; ++mode) {
+ _info.vi_mode = mode;
+ if (ioctl(0, CONS_MODEINFO, &_info))
+ continue;
+ if (_info.vi_mode != mode)
+ continue;
+ if (_info.vi_width == 0 && _info.vi_height == 0 &&
+ _info.vi_cwidth == 0 && _info.vi_cheight == 0)
+ continue;
+
+ printf("%3d (0x%03x)", mode, mode);
+ printf(" 0x%08x", _info.vi_flags);
+ if (_info.vi_flags & V_INFO_GRAPHICS) {
+ c = 'G';
+
+ if (_info.vi_mem_model == V_INFO_MM_PLANAR)
+ snprintf(buf, sizeof(buf), "%dx%dx%d %d",
+ _info.vi_width, _info.vi_height,
+ _info.vi_depth, _info.vi_planes);
+ else {
+ switch (_info.vi_mem_model) {
+ case V_INFO_MM_PACKED:
+ mm = 'P';
+ break;
+ case V_INFO_MM_DIRECT:
+ mm = 'D';
+ break;
+ case V_INFO_MM_CGA:
+ mm = 'C';
+ break;
+ case V_INFO_MM_HGC:
+ mm = 'H';
+ break;
+ case V_INFO_MM_VGAX:
+ mm = 'V';
+ break;
+ default:
+ mm = ' ';
+ break;
+ }
+ snprintf(buf, sizeof(buf), "%dx%dx%d %c",
+ _info.vi_width, _info.vi_height,
+ _info.vi_depth, mm);
+ }
+ } else {
+ c = 'T';
+
+ snprintf(buf, sizeof(buf), "%dx%d",
+ _info.vi_width, _info.vi_height);
+ }
+
+ printf(" %c %-15s", c, buf);
+ snprintf(buf, sizeof(buf), "%dx%d",
+ _info.vi_cwidth, _info.vi_cheight);
+ printf(" %-5s", buf);
+ printf(" 0x%05zx %2dk %2dk",
+ _info.vi_window, (int)_info.vi_window_size/1024,
+ (int)_info.vi_window_gran/1024);
+ printf(" 0x%08zx %dk\n",
+ _info.vi_buffer, (int)_info.vi_buffer_size/1024);
+ }
+}
+
+
+static void
+show_info(char *arg)
+{
+ if (!strcmp(arg, "adapter")) {
+ show_adapter_info();
+ } else if (!strcmp(arg, "mode")) {
+ show_mode_info();
+ } else {
+ revert();
+ errx(1, "argument to -i must be either adapter or mode");
+ }
+}
+
+
+static void
+test_frame(void)
+{
+ int i, cur_mode, fore;
+
+ fore = 15;
+
+ if (ioctl(0, CONS_GET, &cur_mode) < 0)
+ err(1, "must be on a virtual console");
+ switch (cur_mode) {
+ case M_PC98_80x25:
+ case M_PC98_80x30:
+ fore = 7;
+ break;
+ }
+
+ fprintf(stdout, "\033[=0G\n\n");
+ for (i=0; i<8; i++) {
+ fprintf(stdout, "\033[=%dF\033[=0G %2d \033[=%dF%-16s"
+ "\033[=%dF\033[=0G %2d \033[=%dF%-16s "
+ "\033[=%dF %2d \033[=%dGBACKGROUND\033[=0G\n",
+ fore, i, i, legal_colors[i],
+ fore, i+8, i+8, legal_colors[i+8],
+ fore, i, i);
+ }
+ fprintf(stdout, "\033[=%dF\033[=%dG\033[=%dH\033[=%dI\n",
+ info.mv_norm.fore, info.mv_norm.back,
+ info.mv_rev.fore, info.mv_rev.back);
+}
+
+
+/*
+ * Snapshot the video memory of that terminal, using the CONS_SCRSHOT
+ * ioctl, and writes the results to stdout either in the special
+ * binary format (see manual page for details), or in the plain
+ * text format.
+ */
+
+static void
+dump_screen(int mode, int opt)
+{
+ scrshot_t shot;
+ vid_info_t _info;
+
+ _info.size = sizeof(_info);
+
+ if (ioctl(0, CONS_GETINFO, &_info) == -1) {
+ revert();
+ errc(1, errno, "obtaining current video mode parameters");
+ return;
+ }
+
+ shot.x = shot.y = 0;
+ shot.xsize = _info.mv_csz;
+ shot.ysize = _info.mv_rsz;
+ if (opt == DUMP_ALL)
+ shot.ysize += _info.mv_hsz;
+
+ shot.buf = alloca(shot.xsize * shot.ysize * sizeof(u_int16_t));
+ if (shot.buf == NULL) {
+ revert();
+ errx(1, "failed to allocate memory for dump");
+ }
+
+ if (ioctl(0, CONS_SCRSHOT, &shot) == -1) {
+ revert();
+ errc(1, errno, "dumping screen");
+ }
+
+ if (mode == DUMP_FMT_RAW) {
+ printf("SCRSHOT_%c%c%c%c", DUMP_FMT_REV, 2,
+ shot.xsize, shot.ysize);
+
+ fflush(stdout);
+
+ write(STDOUT_FILENO, shot.buf,
+ shot.xsize * shot.ysize * sizeof(u_int16_t));
+ } else {
+ char *line;
+ int x, y;
+ u_int16_t ch;
+
+ line = alloca(shot.xsize + 1);
+
+ if (line == NULL) {
+ revert();
+ errx(1, "failed to allocate memory for line buffer");
+ }
+
+ for (y = 0; y < shot.ysize; y++) {
+ for (x = 0; x < shot.xsize; x++) {
+ ch = shot.buf[x + (y * shot.xsize)];
+ ch &= 0xff;
+
+ if (isprint(ch) == 0)
+ ch = ' ';
+
+ line[x] = (char)ch;
+ }
+
+ /* Trim trailing spaces */
+
+ do {
+ line[x--] = '\0';
+ } while (line[x] == ' ' && x != 0);
+
+ puts(line);
+ }
+
+ fflush(stdout);
+ }
+}
+
+
+/*
+ * Set the console history buffer size.
+ */
+
+static void
+set_history(char *opt)
+{
+ int size;
+
+ size = atoi(opt);
+
+ if ((*opt == '\0') || size < 0) {
+ revert();
+ errx(1, "argument must be a positive number");
+ }
+
+ if (ioctl(0, CONS_HISTORY, &size) == -1) {
+ revert();
+ errc(1, errno, "setting history buffer size");
+ }
+}
+
+
+/*
+ * Clear the console history buffer.
+ */
+
+static void
+clear_history(void)
+{
+ if (ioctl(0, CONS_CLRHIST) == -1) {
+ revert();
+ errc(1, errno, "clearing history buffer");
+ }
+}
+
+static void
+set_terminal_mode(char *arg)
+{
+
+ if (strcmp(arg, "xterm") == 0)
+ fprintf(stderr, "\033[=T");
+ else if (strcmp(arg, "cons25") == 0)
+ fprintf(stderr, "\033[=1T");
+}
+
+
+int
+main(int argc, char **argv)
+{
+ char *font, *type, *termmode;
+ const char *opts;
+ int dumpmod, dumpopt, opt;
+ int reterr;
+
+ vt4_mode = is_vt4();
+
+ init();
+
+ info.size = sizeof(info);
+
+ if (ioctl(0, CONS_GETINFO, &info) == -1)
+ err(1, "must be on a virtual console");
+ dumpmod = 0;
+ dumpopt = DUMP_FBF;
+ termmode = NULL;
+ if (vt4_mode)
+ opts = "b:Cc:fg:h:Hi:M:m:pPr:S:s:T:t:x";
+ else
+ opts = "b:Cc:dfg:h:Hi:l:LM:m:pPr:S:s:T:t:x";
+
+ while ((opt = getopt(argc, argv, opts)) != -1)
+ switch(opt) {
+ case 'b':
+ set_border_color(optarg);
+ break;
+ case 'C':
+ clear_history();
+ break;
+ case 'c':
+ set_cursor_type(optarg);
+ break;
+ case 'd':
+ if (vt4_mode)
+ break;
+ print_scrnmap();
+ break;
+ case 'f':
+ optarg = nextarg(argc, argv, &optind, 'f', 0);
+ if (optarg != NULL) {
+ font = nextarg(argc, argv, &optind, 'f', 0);
+
+ if (font == NULL) {
+ type = NULL;
+ font = optarg;
+ } else
+ type = optarg;
+
+ load_font(type, font);
+ } else {
+ if (!vt4_mode)
+ usage(); /* Switch syscons to ROM? */
+
+ load_default_vt4font();
+ }
+ break;
+ case 'g':
+ if (sscanf(optarg, "%dx%d",
+ &vesa_cols, &vesa_rows) != 2) {
+ revert();
+ warnx("incorrect geometry: %s", optarg);
+ usage();
+ }
+ break;
+ case 'h':
+ set_history(optarg);
+ break;
+ case 'H':
+ dumpopt = DUMP_ALL;
+ break;
+ case 'i':
+ show_info(optarg);
+ break;
+ case 'l':
+ if (vt4_mode)
+ break;
+ load_scrnmap(optarg);
+ break;
+ case 'L':
+ if (vt4_mode)
+ break;
+ load_default_scrnmap();
+ break;
+ case 'M':
+ set_mouse_char(optarg);
+ break;
+ case 'm':
+ set_mouse(optarg);
+ break;
+ case 'p':
+ dumpmod = DUMP_FMT_RAW;
+ break;
+ case 'P':
+ dumpmod = DUMP_FMT_TXT;
+ break;
+ case 'r':
+ get_reverse_colors(argc, argv, &optind);
+ break;
+ case 'S':
+ set_lockswitch(optarg);
+ break;
+ case 's':
+ set_console(optarg);
+ break;
+ case 'T':
+ if (strcmp(optarg, "xterm") != 0 &&
+ strcmp(optarg, "cons25") != 0)
+ usage();
+ termmode = optarg;
+ break;
+ case 't':
+ set_screensaver_timeout(optarg);
+ break;
+ case 'x':
+ hex = 1;
+ break;
+ default:
+ usage();
+ }
+
+ if (dumpmod != 0)
+ dump_screen(dumpmod, dumpopt);
+ reterr = video_mode(argc, argv, &optind);
+ get_normal_colors(argc, argv, &optind);
+
+ if (optind < argc && !strcmp(argv[optind], "show")) {
+ test_frame();
+ optind++;
+ }
+
+ video_mode(argc, argv, &optind);
+ if (termmode != NULL)
+ set_terminal_mode(termmode);
+
+ get_normal_colors(argc, argv, &optind);
+
+ if (colors_changed || video_mode_changed) {
+ if (!(new_mode_info.vi_flags & V_INFO_GRAPHICS)) {
+ if ((normal_back_color < 8) && (revers_back_color < 8)) {
+ set_colors();
+ } else {
+ revert();
+ errx(1, "bg color for text modes must be < 8");
+ }
+ } else {
+ set_colors();
+ }
+ }
+
+ if ((optind != argc) || (argc == 1))
+ usage();
+ return reterr;
+}
+
diff --git a/usr.sbin/vigr/Makefile b/usr.sbin/vigr/Makefile
new file mode 100644
index 0000000..52b548e
--- /dev/null
+++ b/usr.sbin/vigr/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+SCRIPTS= vigr
+MAN= vigr.8
+CLEANFILES= vigr
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/vigr/Makefile.depend b/usr.sbin/vigr/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/vigr/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/vigr/vigr.8 b/usr.sbin/vigr/vigr.8
new file mode 100644
index 0000000..3fd582b
--- /dev/null
+++ b/usr.sbin/vigr/vigr.8
@@ -0,0 +1,71 @@
+.\"-
+.\" Copyright (c) 2014 Dag-Erling Smørgrav
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 14, 2014
+.Dt VIGR 8
+.Os
+.Sh NAME
+.Nm vigr
+.Nd edit the group file
+.Sh SYNOPSIS
+.Nm
+.Op Fl d Ar directory
+.Sh DESCRIPTION
+The
+.Nm
+utility makes a temporary copy of the group file, allows the user to
+edit it, then verifies that the edited file is valid before installing
+it.
+.Pp
+The following options are available:
+.Bl -tag -width Fl
+.It Fl d Ar directory
+Edit the group file in the specified directory instead of the default
+.Pa /etc/group .
+.El
+.Sh ENVIRONMENT
+.Bl -tag -width EDITOR
+.It Ev EDITOR
+The editor to use instead of
+.Xr vi 1 .
+.It Ev TMPDIR
+The directory in which to store the temporary copy of the group file.
+.El
+.Sh SEE ALSO
+.Xr group 5 ,
+.Xr chkgrp 8 ,
+.Xr vipw 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 11.0 .
+.Sh AUTHORS
+The
+.Nm
+utility and this manual page were written by
+.An Dag-Erling Sm\(/orgrav Aq Mt des@FreeBSD.org .
diff --git a/usr.sbin/vigr/vigr.sh b/usr.sbin/vigr/vigr.sh
new file mode 100644
index 0000000..c5dc65d
--- /dev/null
+++ b/usr.sbin/vigr/vigr.sh
@@ -0,0 +1,95 @@
+#!/bin/sh
+#-
+# Copyright (c) 2014 Dag-Erling Smørgrav
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must 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$
+#
+
+error() {
+ echo "$@" >&2
+ exit 1
+}
+
+usage() {
+ error "usage: vigr [-d dir]"
+}
+
+# Check arguments
+while getopts d: opt ; do
+ case $opt in
+ d)
+ etcdir="${OPTARG}"
+ ;;
+ *)
+ usage
+ ;;
+ esac
+done
+
+# Look for the current group file
+grpfile="${etcdir:-/etc}/group"
+if [ ! -f "${grpfile}" ] ; then
+ error "Missing group file"
+fi
+
+# Create a secure temporary working directory
+tmpdir=$(mktemp -d -t vigr)
+if [ -z "${tmpdir}" -o ! -d "${tmpdir}" ] ; then
+ error "Unable to create the temporary directory"
+fi
+tmpfile="${tmpdir}/group"
+
+# Clean up on exit
+trap "exit 1" INT
+trap "rm -rf '${tmpdir}'" EXIT
+set -e
+
+# Make a copy of the group file for the user to edit
+cp "${grpfile}" "${tmpfile}"
+
+while :; do
+ # Let the user edit the file
+ ${EDITOR:-/usr/bin/vi} "${tmpfile}"
+
+ # If the result is valid, install it and exit
+ if chkgrp -q "${tmpfile}" ; then
+ install -b -m 0644 -C -S "${tmpfile}" "${grpfile}"
+ exit 0
+ fi
+
+ # If it is not, offer to re-edit
+ while :; do
+ echo -n "Re-edit the group file? "
+ read ans
+ case $ans in
+ [Yy]|[Yy][Ee][Ss])
+ break
+ ;;
+ [Nn]|[Nn][Oo])
+ exit 1
+ ;;
+ esac
+ done
+done
diff --git a/usr.sbin/vipw/Makefile b/usr.sbin/vipw/Makefile
new file mode 100644
index 0000000..f36825d
--- /dev/null
+++ b/usr.sbin/vipw/Makefile
@@ -0,0 +1,9 @@
+# @(#)Makefile 8.1 (Berkeley) 6/6/93
+# $FreeBSD$
+
+PROG= vipw
+MAN= vipw.8
+
+LIBADD= util
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/vipw/Makefile.depend b/usr.sbin/vipw/Makefile.depend
new file mode 100644
index 0000000..58f9a33
--- /dev/null
+++ b/usr.sbin/vipw/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/vipw/vipw.8 b/usr.sbin/vipw/vipw.8
new file mode 100644
index 0000000..69f8845
--- /dev/null
+++ b/usr.sbin/vipw/vipw.8
@@ -0,0 +1,122 @@
+.\" 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.
+.\"
+.\" @(#)vipw.8 8.1 (Berkeley) 6/6/93
+.\" $FreeBSD$
+.\"
+.Dd February 14, 2012
+.Dt VIPW 8
+.Os
+.Sh NAME
+.Nm vipw
+.Nd edit the password file
+.Sh SYNOPSIS
+.Nm
+.Op Fl d Ar directory
+.Sh DESCRIPTION
+The
+.Nm
+utility edits the password file after setting the appropriate locks,
+and does any necessary processing after the password file is unlocked.
+If the password file is already locked for editing by another user,
+.Nm
+will ask you
+to try again later.
+The default editor for
+.Nm
+is
+.Xr vi 1 .
+.Pp
+When run without options,
+.Nm
+will work with the password files in
+.Pa /etc .
+The
+.Fl d
+option may be used to specify an alternative
+.Ar directory
+to work with.
+.Pp
+The
+.Nm
+utility performs a number of consistency checks on the password entries,
+and will not allow a password file with a
+.Dq mangled
+entry to be
+installed.
+If
+.Nm
+rejects the new password file, the user is prompted to re-enter
+the edit session.
+.Pp
+Once the information has been verified,
+.Nm
+uses
+.Xr pwd_mkdb 8
+to update the user database.
+This is run in the background, and,
+at very large sites could take several minutes.
+Until this update
+is completed, the password file is unavailable for other updates
+and the new information is not available to programs.
+.Sh ENVIRONMENT
+If the following environment variable exists it will be utilized by
+.Nm :
+.Bl -tag -width PW_SCAN_BIG_IDS
+.It Ev EDITOR
+The editor specified by the string
+.Ev EDITOR
+will be invoked instead of the default editor
+.Xr vi 1 .
+This can be used to allow a script to non-interactively modify the
+password file.
+.It Ev PW_SCAN_BIG_IDS
+See
+.Xr pwd_mkdb 8 .
+.El
+.Sh SEE ALSO
+.Xr chpass 1 ,
+.Xr passwd 1 ,
+.Xr passwd 5 ,
+.Xr adduser 8 ,
+.Xr pw 8 ,
+.Xr pwd_mkdb 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Bx 4.0 .
+.Sh BUGS
+The mechanism for checking for password file modifications requires that
+the modification time of the password file changes.
+This means that in a default configuration where file system timestamps
+are not calculated with sub-second precision,
+.Ev EDITOR
+has to run for at least one second.
+Non-interactive editor scripts should invoke
+.Xr sleep 1
+or equivalent to ensure this happens.
diff --git a/usr.sbin/vipw/vipw.c b/usr.sbin/vipw/vipw.c
new file mode 100644
index 0000000..4a0a700
--- /dev/null
+++ b/usr.sbin/vipw/vipw.c
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 1987, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 2002 Networks Associates Technology, Inc.
+ * All rights reserved.
+ *
+ * Portions of this software were developed for the FreeBSD Project by
+ * ThinkSec AS 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.
+ * 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) 1987, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)vipw.c 8.3 (Berkeley) 4/2/94";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libutil.h> /* must be after pwd.h */
+
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ const char *passwd_dir = NULL;
+ int ch, pfd, tfd;
+ char *line;
+ size_t len;
+
+ while ((ch = getopt(argc, argv, "d:")) != -1)
+ switch (ch) {
+ case 'd':
+ passwd_dir = optarg;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 0)
+ usage();
+
+ if (pw_init(passwd_dir, NULL) == -1)
+ err(1, "pw_init()");
+ if ((pfd = pw_lock()) == -1) {
+ pw_fini();
+ err(1, "pw_lock()");
+ }
+ if ((tfd = pw_tmp(pfd)) == -1) {
+ pw_fini();
+ err(1, "pw_tmp()");
+ }
+ (void)close(tfd);
+ /* Force umask for partial writes made in the edit phase */
+ (void)umask(077);
+
+ for (;;) {
+ switch (pw_edit(0)) {
+ case -1:
+ pw_fini();
+ err(1, "pw_edit()");
+ case 0:
+ pw_fini();
+ errx(0, "no changes made");
+ default:
+ break;
+ }
+ if (pw_mkdb(NULL) == 0) {
+ pw_fini();
+ errx(0, "password list updated");
+ }
+ printf("re-edit the password file? ");
+ fflush(stdout);
+ if ((line = fgetln(stdin, &len)) == NULL) {
+ pw_fini();
+ err(1, "fgetln()");
+ }
+ if (len > 0 && (*line == 'N' || *line == 'n'))
+ break;
+ }
+ pw_fini();
+ exit(0);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "usage: vipw [-d directory]\n");
+ exit(1);
+}
diff --git a/usr.sbin/wake/Makefile b/usr.sbin/wake/Makefile
new file mode 100644
index 0000000..f75d469
--- /dev/null
+++ b/usr.sbin/wake/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= wake
+MAN= wake.8
+
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/wake/Makefile.depend b/usr.sbin/wake/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/wake/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/wake/wake.8 b/usr.sbin/wake/wake.8
new file mode 100644
index 0000000..e08acbc
--- /dev/null
+++ b/usr.sbin/wake/wake.8
@@ -0,0 +1,68 @@
+.\"
+.\" $FreeBSD$
+.\"
+.\" Copyright (c) 2009, 2010 Marc Balmer <marc@msys.ch>
+.\"
+.\" 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.
+.\"
+.Dd December 27, 2009
+.Dt WAKE 8
+.Os
+.Sh NAME
+.Nm wake
+.Nd send Wake on LAN frames to hosts on a local Ethernet network
+.Sh SYNOPSIS
+.Nm
+.Op Ar interface
+.Ar lladdr
+.Op Ar lladdr ...
+.Sh DESCRIPTION
+The
+.Nm
+program is used to send Wake on LAN (WoL) frames over a local
+Ethernet network to one or more hosts using their link layer (hardware)
+addresses.
+WoL functionality is generally enabled in a machine's BIOS
+and can be used to power on machines from a remote system without
+having physical access to them.
+.Pp
+.Ar interface
+is an Ethernet interface of the local machine and is used to send the
+Wake on LAN frames over it.
+If there is only one Ethernet device available that is up and running, then the
+.Ar interface
+argument can be omitted.
+.Ar lladdr
+is the link layer address of the remote machine.
+This can be specified as the actual hardware address
+(six hexadecimal numbers separated by colons)
+or as a hostname entry in
+.Pa /etc/ethers .
+.Nm
+accepts multiple
+.Ar lladdr
+addresses.
+Link layer addresses can be determined and set using
+.Xr ifconfig 8 .
+.Sh FILES
+.Bl -tag -width "/etc/ethers" -compact
+.It Pa /etc/ethers
+Ethernet host name data base.
+.El
+.Sh SEE ALSO
+.Xr ethers 5 ,
+.Xr ifconfig 8
+.Sh AUTHORS
+.Nm
+was written by
+.An Marc Balmer Aq Mt marc@msys.ch .
diff --git a/usr.sbin/wake/wake.c b/usr.sbin/wake/wake.c
new file mode 100644
index 0000000..b7737da
--- /dev/null
+++ b/usr.sbin/wake/wake.c
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2006, 2007, 2008, 2009, 2010 Marc Balmer <marc@msys.ch>
+ * Copyright (C) 2000 Eugene M. Kim. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Author's name may not be used 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/ioctl.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <net/bpf.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <ifaddrs.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define _PATH_BPF "/dev/bpf"
+
+#ifndef SYNC_LEN
+#define SYNC_LEN 6
+#endif
+
+#ifndef DESTADDR_COUNT
+#define DESTADDR_COUNT 16
+#endif
+
+static int bind_if_to_bpf(char const *ifname, int bpf);
+static int find_ether(char *dst, size_t len);
+static int get_ether(char const *text, struct ether_addr *addr);
+static int send_wakeup(int bpf, struct ether_addr const *addr);
+static void usage(void);
+static int wake(int bpf, const char *host);
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr, "usage: wake [interface] lladdr [lladdr ...]\n");
+ exit(1);
+}
+
+static int
+wake(int bpf, const char *host)
+{
+ struct ether_addr macaddr;
+
+ if (get_ether(host, &macaddr) == -1)
+ return (-1);
+
+ return (send_wakeup(bpf, &macaddr));
+}
+
+static int
+bind_if_to_bpf(char const *ifname, int bpf)
+{
+ struct ifreq ifr;
+ u_int dlt;
+
+ if (strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)) >=
+ sizeof(ifr.ifr_name))
+ return (-1);
+
+ if (ioctl(bpf, BIOCSETIF, &ifr) == -1)
+ return (-1);
+
+ if (ioctl(bpf, BIOCGDLT, &dlt) == -1)
+ return (-1);
+
+ if (dlt != DLT_EN10MB)
+ return (-1);
+
+ return (0);
+}
+
+static int
+find_ether(char *dst, size_t len)
+{
+ struct ifaddrs *ifap, *ifa;
+ struct sockaddr_dl *sdl = NULL;
+ int nifs;
+
+ if (dst == NULL || len == 0)
+ return (0);
+
+ if (getifaddrs(&ifap) != 0)
+ return (-1);
+
+ /* XXX also check the link state */
+ for (nifs = 0, ifa = ifap; ifa; ifa = ifa->ifa_next)
+ if (ifa->ifa_addr->sa_family == AF_LINK &&
+ ifa->ifa_flags & IFF_UP && ifa->ifa_flags & IFF_RUNNING) {
+ sdl = (struct sockaddr_dl *)ifa->ifa_addr;
+ if (sdl->sdl_type == IFT_ETHER) {
+ strlcpy(dst, ifa->ifa_name, len);
+ nifs++;
+ }
+ }
+
+ freeifaddrs(ifap);
+ return (nifs == 1 ? 0 : -1);
+}
+
+static int
+get_ether(char const *text, struct ether_addr *addr)
+{
+ struct ether_addr *paddr;
+
+ paddr = ether_aton(text);
+ if (paddr != NULL) {
+ *addr = *paddr;
+ return (0);
+ }
+ if (ether_hostton(text, addr)) {
+ warnx("no match for host %s found", text);
+ return (-1);
+ }
+ return (0);
+}
+
+static int
+send_wakeup(int bpf, struct ether_addr const *addr)
+{
+ struct {
+ struct ether_header hdr;
+ u_char data[SYNC_LEN + ETHER_ADDR_LEN * DESTADDR_COUNT];
+ } __packed pkt;
+ u_char *p;
+ ssize_t bw;
+ ssize_t len;
+ int i;
+
+ (void)memset(pkt.hdr.ether_dhost, 0xff, sizeof(pkt.hdr.ether_dhost));
+ pkt.hdr.ether_type = htons(0);
+ (void)memset(pkt.data, 0xff, SYNC_LEN);
+ for (p = pkt.data + SYNC_LEN, i = 0; i < DESTADDR_COUNT;
+ p += ETHER_ADDR_LEN, i++)
+ bcopy(addr->octet, p, ETHER_ADDR_LEN);
+ p = (u_char *)&pkt;
+ len = sizeof(pkt);
+ bw = 0;
+ while (len) {
+ if ((bw = write(bpf, p, len)) == -1) {
+ warn("write()");
+ return (-1);
+ }
+ len -= bw;
+ p += bw;
+ }
+ return (0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int bpf, n, rval;
+ char ifname[IF_NAMESIZE];
+
+ if (argc < 2)
+ usage();
+
+ if ((bpf = open(_PATH_BPF, O_RDWR)) == -1)
+ err(1, "Cannot open bpf interface");
+
+ n = 2;
+ if (bind_if_to_bpf(argv[1], bpf) == -1) {
+ if (find_ether(ifname, sizeof(ifname)))
+ err(1, "Failed to determine ethernet interface");
+ if (bind_if_to_bpf(ifname, bpf) == -1)
+ err(1, "Cannot bind to interface `%s'", ifname);
+ --n;
+ } else
+ strlcpy(ifname, argv[1], sizeof(ifname));
+
+ if (n >= argc)
+ usage();
+ rval = 0;
+ for (; n < argc; n++) {
+ if (wake(bpf, argv[n]) != 0) {
+ rval = 1;
+ warn("Cannot send Wake on LAN frame over `%s' to `%s'",
+ ifname, argv[n]);
+ }
+ }
+ exit(rval);
+}
diff --git a/usr.sbin/watch/Makefile b/usr.sbin/watch/Makefile
new file mode 100644
index 0000000..981aec0
--- /dev/null
+++ b/usr.sbin/watch/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+PROG= watch
+MAN= watch.8
+
+WARNS?= 2
+
+LIBADD= ncursesw
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/watch/Makefile.depend b/usr.sbin/watch/Makefile.depend
new file mode 100644
index 0000000..59bc828
--- /dev/null
+++ b/usr.sbin/watch/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/ncurses/ncursesw \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/watch/watch.8 b/usr.sbin/watch/watch.8
new file mode 100644
index 0000000..052c95d
--- /dev/null
+++ b/usr.sbin/watch/watch.8
@@ -0,0 +1,120 @@
+.\"
+.\" $FreeBSD$
+.\"
+.Dd November 24, 2001
+.Dt WATCH 8
+.Os
+.Sh NAME
+.Nm watch
+.Nd snoop on another tty line
+.Sh SYNOPSIS
+.Nm
+.Op Fl cinotW
+.Op Fl f Ar snpdev
+.Op Ar tty
+.Sh DESCRIPTION
+The
+.Nm
+utility allows the user to examine all data coming through a specified tty
+using the
+.Xr snp 4
+device.
+If the
+.Xr snp 4
+device is not available,
+.Nm
+will attempt to load the module
+.Pq Nm snp .
+The
+.Nm
+utility writes to standard output.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl c
+Reconnect on close.
+If the tty observed by
+.Nm
+is closed, automatically reattach to the same tty.
+If this option is not specified,
+.Nm
+will request a new tty if running in interactive mode or exit if running
+without a controlling tty.
+.It Fl f Ar snpdev
+If this option is specified,
+.Nm
+will use
+.Ar snpdev
+as the
+.Xr snp 4
+device.
+Without this option,
+.Nm
+will attempt to find the next available
+.Xr snp 4
+device.
+.It Fl i
+Force interactive mode.
+Interactive mode is a default if
+.Nm
+is started from a tty.
+If output is redirected to a file, interactive mode can still be requested
+by specifying this option.
+.It Fl n
+Disable the ability to switch the watched tty interactively.
+This disables
+both change requests made with <control-X> as well as automatic prompting
+when the current tty is closed or overflows.
+In all cases where a prompt
+would be displayed,
+.Nm
+will exit.
+The reconnect flags are unaffected by
+this option.
+When this flag is used, <control-X> is passed through to the terminal.
+.It Fl o
+Reconnect on overflow.
+The behavior of
+.Nm
+if the observed tty overflows is similar to the behavior if the observed tty
+is closed.
+For more info see
+.Xr snp 4 .
+.It Fl t
+Print the date and time when observation of a given tty is started.
+.It Fl W
+Allow write access to observed tty.
+.It Ar tty
+Tty may be specified as a tty-style device, such as a pseudo tty device,
+a virtual console, or a serial line, etc.
+Names may be preceded by
+.Pa /dev/ .
+.El
+.Sh OPERATION
+While running in interactive mode, all user input is discarded except for:
+.Pp
+.Bl -tag -width "XXXX" -compact
+.It Sy "<control-G>"
+Exit
+.Nm .
+.It Sy "<control-W>"
+Clear screen.
+.It Sy "<control-X>"
+Change attached tty, unless this feature is disabled, in which case
+control-X is passed to the terminal as with other control characters.
+.El
+.Sh SEE ALSO
+.Xr pty 4 ,
+.Xr sio 4 ,
+.Xr snp 4 ,
+.Xr kldload 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 2.1 .
+.Sh AUTHORS
+.An Ugen J.S. Antsilevich Aq Mt ugen@NetVision.net.il
+.Sh BUGS
+No terminal emulation is performed.
+All user output is reproduced as-is.
diff --git a/usr.sbin/watch/watch.c b/usr.sbin/watch/watch.c
new file mode 100644
index 0000000..766d45a
--- /dev/null
+++ b/usr.sbin/watch/watch.c
@@ -0,0 +1,441 @@
+/*
+ * Copyright (c) 1995 Ugen J.S.Antsilevich
+ *
+ * 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.
+ *
+ * Snoop stuff.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/fcntl.h>
+#include <sys/filio.h>
+#include <sys/snoop.h>
+#include <sys/stat.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+
+#include <err.h>
+#include <errno.h>
+#include <locale.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <termcap.h>
+#include <termios.h>
+#include <time.h>
+#include <unistd.h>
+
+#define MSG_INIT "Snoop started."
+#define MSG_OFLOW "Snoop stopped due to overflow. Reconnecting."
+#define MSG_CLOSED "Snoop stopped due to tty close. Reconnecting."
+#define MSG_CHANGE "Snoop device change by user request."
+#define MSG_NOWRITE "Snoop device change due to write failure."
+
+#define DEV_NAME_LEN 1024 /* for /dev/ttyXX++ */
+#define MIN_SIZE 256
+
+#define CHR_SWITCH 24 /* Ctrl+X */
+#define CHR_CLEAR 23 /* Ctrl+V */
+
+static void clear(void);
+static void timestamp(const char *);
+static void set_tty(void);
+static void unset_tty(void);
+static void fatal(int, const char *);
+static int open_snp(void);
+static void cleanup(int);
+static void usage(void) __dead2;
+static void setup_scr(void);
+static void attach_snp(void);
+static void detach_snp(void);
+static void set_dev(const char *);
+static void ask_dev(char *, const char *);
+
+int opt_reconn_close = 0;
+int opt_reconn_oflow = 0;
+int opt_interactive = 1;
+int opt_timestamp = 0;
+int opt_write = 0;
+int opt_no_switch = 0;
+const char *opt_snpdev;
+
+char dev_name[DEV_NAME_LEN];
+int snp_io;
+int std_in = 0, std_out = 1;
+
+int clear_ok = 0;
+struct termios otty;
+char tbuf[1024], gbuf[1024];
+
+static void
+clear(void)
+{
+
+ if (clear_ok)
+ tputs(gbuf, 1, putchar);
+ fflush(stdout);
+}
+
+static void
+timestamp(const char *buf)
+{
+ time_t t;
+ char btmp[1024];
+
+ clear();
+ printf("\n---------------------------------------------\n");
+ t = time(NULL);
+ strftime(btmp, 1024, "Time: %d %b %H:%M", localtime(&t));
+ printf("%s\n", btmp);
+ printf("%s\n", buf);
+ printf("---------------------------------------------\n");
+ fflush(stdout);
+}
+
+static void
+set_tty(void)
+{
+ struct termios ntty;
+
+ ntty = otty;
+ ntty.c_lflag &= ~ICANON; /* disable canonical operation */
+ ntty.c_lflag &= ~ECHO;
+#ifdef FLUSHO
+ ntty.c_lflag &= ~FLUSHO;
+#endif
+#ifdef PENDIN
+ ntty.c_lflag &= ~PENDIN;
+#endif
+#ifdef IEXTEN
+ ntty.c_lflag &= ~IEXTEN;
+#endif
+ ntty.c_cc[VMIN] = 1; /* minimum of one character */
+ ntty.c_cc[VTIME] = 0; /* timeout value */
+
+ ntty.c_cc[VINTR] = 07; /* ^G */
+ ntty.c_cc[VQUIT] = 07; /* ^G */
+ tcsetattr(std_in, TCSANOW, &ntty);
+}
+
+static void
+unset_tty(void)
+{
+
+ tcsetattr(std_in, TCSANOW, &otty);
+}
+
+static void
+fatal(int error, const char *buf)
+{
+
+ unset_tty();
+ if (buf)
+ errx(error, "fatal: %s", buf);
+ else
+ exit(error);
+}
+
+static int
+open_snp(void)
+{
+ int f, mode;
+
+ if (opt_write)
+ mode = O_RDWR;
+ else
+ mode = O_RDONLY;
+
+ if (opt_snpdev == NULL)
+ f = open(_PATH_DEV "snp", mode);
+ else
+ f = open(opt_snpdev, mode);
+ if (f == -1)
+ fatal(EX_OSFILE, "cannot open snoop device");
+
+ return (f);
+}
+
+static void
+cleanup(int signo __unused)
+{
+
+ if (opt_timestamp)
+ timestamp("Logging Exited.");
+ close(snp_io);
+ unset_tty();
+ exit(EX_OK);
+}
+
+static void
+usage(void)
+{
+
+ fprintf(stderr, "usage: watch [-ciotnW] [tty name]\n");
+ exit(EX_USAGE);
+}
+
+static void
+setup_scr(void)
+{
+ char *cbuf = gbuf, *term;
+
+ if (!opt_interactive)
+ return;
+ if ((term = getenv("TERM")))
+ if (tgetent(tbuf, term) == 1)
+ if (tgetstr("cl", &cbuf))
+ clear_ok = 1;
+ set_tty();
+ clear();
+}
+
+static void
+detach_snp(void)
+{
+ int fd;
+
+ fd = -1;
+ ioctl(snp_io, SNPSTTY, &fd);
+}
+
+static void
+attach_snp(void)
+{
+ int snp_tty;
+
+ snp_tty = open(dev_name, O_RDONLY | O_NONBLOCK);
+ if (snp_tty < 0)
+ fatal(EX_DATAERR, "can't open device");
+ if (ioctl(snp_io, SNPSTTY, &snp_tty) != 0)
+ fatal(EX_UNAVAILABLE, "cannot attach to tty");
+ close(snp_tty);
+ if (opt_timestamp)
+ timestamp("Logging Started.");
+}
+
+static void
+set_dev(const char *name)
+{
+ char buf[DEV_NAME_LEN];
+ struct stat sb;
+
+ if (strlen(name) > 5 && !strncmp(name, _PATH_DEV, sizeof _PATH_DEV - 1)) {
+ snprintf(buf, sizeof buf, "%s", name);
+ } else {
+ if (strlen(name) == 2)
+ sprintf(buf, "%s%s", _PATH_TTY, name);
+ else
+ sprintf(buf, "%s%s", _PATH_DEV, name);
+ }
+
+ if (*name == '\0' || stat(buf, &sb) < 0)
+ fatal(EX_DATAERR, "bad device name");
+
+ if ((sb.st_mode & S_IFMT) != S_IFCHR)
+ fatal(EX_DATAERR, "must be a character device");
+
+ strlcpy(dev_name, buf, sizeof(dev_name));
+
+ attach_snp();
+}
+
+void
+ask_dev(char *dbuf, const char *msg)
+{
+ char buf[DEV_NAME_LEN];
+ int len;
+
+ clear();
+ unset_tty();
+
+ if (msg)
+ printf("%s\n", msg);
+ if (dbuf)
+ printf("Enter device name [%s]:", dbuf);
+ else
+ printf("Enter device name:");
+
+ if (fgets(buf, DEV_NAME_LEN - 1, stdin)) {
+ len = strlen(buf);
+ if (buf[len - 1] == '\n')
+ buf[len - 1] = '\0';
+ if (buf[0] != '\0' && buf[0] != ' ')
+ strcpy(dbuf, buf);
+ }
+ set_tty();
+}
+
+#define READB_LEN 5
+
+int
+main(int ac, char *av[])
+{
+ int ch, res, rv, nread;
+ size_t b_size = MIN_SIZE;
+ char *buf, chb[READB_LEN];
+ fd_set fd_s;
+
+ (void) setlocale(LC_TIME, "");
+
+ if (isatty(std_out))
+ opt_interactive = 1;
+ else
+ opt_interactive = 0;
+
+ while ((ch = getopt(ac, av, "Wciotnf:")) != -1)
+ switch (ch) {
+ case 'W':
+ opt_write = 1;
+ break;
+ case 'c':
+ opt_reconn_close = 1;
+ break;
+ case 'i':
+ opt_interactive = 1;
+ break;
+ case 'o':
+ opt_reconn_oflow = 1;
+ break;
+ case 't':
+ opt_timestamp = 1;
+ break;
+ case 'n':
+ opt_no_switch = 1;
+ break;
+ case 'f':
+ opt_snpdev = optarg;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+
+ tcgetattr(std_in, &otty);
+
+ if (modfind("snp") == -1)
+ if (kldload("snp") == -1 || modfind("snp") == -1)
+ warn("snp module not available");
+
+ signal(SIGINT, cleanup);
+
+ snp_io = open_snp();
+ setup_scr();
+
+ if (*(av += optind) == NULL) {
+ if (opt_interactive && !opt_no_switch)
+ ask_dev(dev_name, MSG_INIT);
+ else
+ fatal(EX_DATAERR, "no device name given");
+ } else
+ strlcpy(dev_name, *av, sizeof(dev_name));
+
+ set_dev(dev_name);
+
+ if (!(buf = (char *) malloc(b_size)))
+ fatal(EX_UNAVAILABLE, "malloc failed");
+
+ FD_ZERO(&fd_s);
+
+ for (;;) {
+ if (opt_interactive)
+ FD_SET(std_in, &fd_s);
+ FD_SET(snp_io, &fd_s);
+ res = select(snp_io + 1, &fd_s, NULL, NULL, NULL);
+ if (opt_interactive && FD_ISSET(std_in, &fd_s)) {
+
+ if ((res = ioctl(std_in, FIONREAD, &nread)) != 0)
+ fatal(EX_OSERR, "ioctl(FIONREAD)");
+ if (nread > READB_LEN)
+ nread = READB_LEN;
+ rv = read(std_in, chb, nread);
+ if (rv == -1 || rv != nread)
+ fatal(EX_IOERR, "read (stdin) failed");
+
+ switch (chb[0]) {
+ case CHR_CLEAR:
+ clear();
+ break;
+ case CHR_SWITCH:
+ if (!opt_no_switch) {
+ detach_snp();
+ ask_dev(dev_name, MSG_CHANGE);
+ set_dev(dev_name);
+ break;
+ }
+ default:
+ if (opt_write) {
+ rv = write(snp_io, chb, nread);
+ if (rv == -1 || rv != nread) {
+ detach_snp();
+ if (opt_no_switch)
+ fatal(EX_IOERR,
+ "write failed");
+ ask_dev(dev_name, MSG_NOWRITE);
+ set_dev(dev_name);
+ }
+ }
+
+ }
+ }
+ if (!FD_ISSET(snp_io, &fd_s))
+ continue;
+
+ if ((res = ioctl(snp_io, FIONREAD, &nread)) != 0)
+ fatal(EX_OSERR, "ioctl(FIONREAD)");
+
+ switch (nread) {
+ case SNP_OFLOW:
+ if (opt_reconn_oflow)
+ attach_snp();
+ else if (opt_interactive && !opt_no_switch) {
+ ask_dev(dev_name, MSG_OFLOW);
+ set_dev(dev_name);
+ } else
+ cleanup(-1);
+ break;
+ case SNP_DETACH:
+ case SNP_TTYCLOSE:
+ if (opt_reconn_close)
+ attach_snp();
+ else if (opt_interactive && !opt_no_switch) {
+ ask_dev(dev_name, MSG_CLOSED);
+ set_dev(dev_name);
+ } else
+ cleanup(-1);
+ break;
+ default:
+ if (nread < (b_size / 2) && (b_size / 2) > MIN_SIZE) {
+ free(buf);
+ if (!(buf = (char *) malloc(b_size / 2)))
+ fatal(EX_UNAVAILABLE, "malloc failed");
+ b_size = b_size / 2;
+ }
+ if (nread > b_size) {
+ b_size = (nread % 2) ? (nread + 1) : (nread);
+ free(buf);
+ if (!(buf = (char *) malloc(b_size)))
+ fatal(EX_UNAVAILABLE, "malloc failed");
+ }
+ rv = read(snp_io, buf, nread);
+ if (rv == -1 || rv != nread)
+ fatal(EX_IOERR, "read failed");
+ rv = write(std_out, buf, nread);
+ if (rv == -1 || rv != nread)
+ fatal(EX_IOERR, "write failed");
+ }
+ } /* While */
+ return(0);
+}
diff --git a/usr.sbin/watchdogd/Makefile b/usr.sbin/watchdogd/Makefile
new file mode 100644
index 0000000..bce983f
--- /dev/null
+++ b/usr.sbin/watchdogd/Makefile
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+PROG= watchdogd
+LINKS= ${BINDIR}/watchdogd ${BINDIR}/watchdog
+MAN= watchdogd.8 watchdog.8
+
+LIBADD= util
+
+.include <bsd.prog.mk>
+
+test: ${PROG}
+ ./${PROG} -t 1.0
diff --git a/usr.sbin/watchdogd/Makefile.depend b/usr.sbin/watchdogd/Makefile.depend
new file mode 100644
index 0000000..a79b1eb
--- /dev/null
+++ b/usr.sbin/watchdogd/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+ lib/msun \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/watchdogd/watchdog.8 b/usr.sbin/watchdogd/watchdog.8
new file mode 100644
index 0000000..9d041f5
--- /dev/null
+++ b/usr.sbin/watchdogd/watchdog.8
@@ -0,0 +1,73 @@
+.\" Copyright (c) 2004 Poul-Henning Kamp <phk@FreeBSD.org>
+.\" Copyright (c) 2003 Sean M. Kelly <smkelly@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 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 18, 2014
+.Dt WATCHDOG 8
+.Os
+.Sh NAME
+.Nm watchdog
+.Nd watchdog control program
+.Sh SYNOPSIS
+.Nm
+.Op Fl d
+.Op Fl t Ar timeout
+.Sh DESCRIPTION
+The
+.Nm
+utility can be used to control the kernel's watchdog facility.
+.Pp
+The
+.Fl d
+option
+enables debugging.
+.Pp
+The
+.Fl t Ar timeout
+option
+specifies the desired timeout period in seconds, a value of
+zero will disable the watchdog.
+The default timeout is 128 seconds.
+.Sh SEE ALSO
+.Xr watchdog 4 ,
+.Xr watchdogd 8 ,
+.Xr watchdog 9
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 5.1 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility and manual page were written by
+.An Sean Kelly Aq Mt smkelly@FreeBSD.org
+and
+.An Poul-Henning Kamp Aq Mt phk@FreeBSD.org .
+.Pp
+Some contributions made by
+.An Jeff Roberson Aq Mt jeff@FreeBSD.org .
diff --git a/usr.sbin/watchdogd/watchdogd.8 b/usr.sbin/watchdogd/watchdogd.8
new file mode 100644
index 0000000..a83e775
--- /dev/null
+++ b/usr.sbin/watchdogd/watchdogd.8
@@ -0,0 +1,325 @@
+.\" Copyright (c) 2013 iXsystems.com,
+.\" author: Alfred Perlstein <alfred@freebsd.org>
+.\" Copyright (c) 2004 Poul-Henning Kamp <phk@FreeBSD.org>
+.\" Copyright (c) 2003 Sean M. Kelly <smkelly@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 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 May 11, 2015
+.Dt WATCHDOGD 8
+.Os
+.Sh NAME
+.Nm watchdogd
+.Nd watchdog daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl dnSw
+.Op Fl -debug
+.Op Fl -softtimeout
+.Op Fl -softtimeout-action Ar action
+.Op Fl -pretimeout Ar timeout
+.Op Fl -pretimeout-action Ar action
+.Op Fl e Ar cmd
+.Op Fl I Ar file
+.Op Fl s Ar sleep
+.Op Fl t Ar timeout
+.Op Fl T Ar script_timeout
+.Op Fl x Ar exit_timeout
+.Sh DESCRIPTION
+The
+.Nm
+utility interfaces with the kernel's watchdog facility to ensure
+that the system is in a working state.
+If
+.Nm
+is unable to interface with the kernel over a specific timeout,
+the kernel will take actions to assist in debugging or restarting the computer.
+.Pp
+If
+.Fl e Ar cmd
+is specified,
+.Nm
+will attempt to execute this command with
+.Xr system 3 ,
+and only if the command returns with a zero exit code will the
+watchdog be reset.
+If
+.Fl e Ar cmd
+is not specified, the daemon will perform a trivial file system
+check instead.
+.Pp
+The
+.Fl n
+argument 'dry-run' will cause watchdog not to arm the system watchdog and
+instead only run the watchdog function and report on failures.
+This is useful for developing new watchdogd scripts as the system will not
+reboot if there are problems with the script.
+.Pp
+The
+.Fl s Ar sleep
+argument can be used to control the sleep period between each execution
+of the check and defaults to 10 seconds.
+.Pp
+The
+.Fl t Ar timeout
+specifies the desired timeout period in seconds.
+The default timeout is 128 seconds.
+.Pp
+One possible circumstance which will cause a watchdog timeout is an interrupt
+storm.
+If this occurs,
+.Nm
+will no longer execute and thus the kernel's watchdog routines will take
+action after a configurable timeout.
+.Pp
+The
+.Fl T Ar script_timeout
+specifies the threshold (in seconds) at which the watchdogd will complain
+that its script has run for too long.
+If unset
+.Ar script_timeout
+defaults to the value specified by the
+.Fl s Ar sleep
+option.
+.Pp
+The
+.Fl x Ar exit_timeout
+argument is the timeout period (in seconds) to leave in effect when the
+program exits.
+Using
+.Fl x
+with a non-zero value protects against lockup during a reboot by
+triggering a hardware reset if the software reboot doesn't complete
+before the given timeout expires.
+.Pp
+Upon receiving the
+.Dv SIGTERM
+or
+.Dv SIGINT
+signals,
+.Nm
+will terminate, after first instructing the kernel to either disable the
+timeout or reset it to the value given by
+.Fl x Ar exit_timeout .
+.Pp
+The
+.Nm
+utility recognizes the following runtime options:
+.Bl -tag -width 30m
+.It Fl I Ar file
+Write the process ID of the
+.Nm
+utility in the specified file.
+.It Fl d Fl -debug
+Do not fork.
+When this option is specified,
+.Nm
+will not fork into the background at startup.
+.It Fl S
+Do not send a message to the system logger when the watchdog command takes
+longer than expected to execute.
+The default behaviour is to log a warning via the system logger with the
+LOG_DAEMON facility, and to output a warning to standard error.
+.It Fl w
+Complain when the watchdog script takes too long.
+This flag will cause watchdogd to complain when the amount of time to
+execute the watchdog script exceeds the threshold of 'sleep' option.
+.It Fl -pretimeout Ar timeout
+Set a "pretimeout" watchdog.
+At "timeout" seconds before the watchdog will fire attempt an action.
+The action is set by the --pretimeout-action flag.
+The default is just to log a message (WD_SOFT_LOG) via
+.Xr log 9 .
+.It Fl -pretimeout-action Ar action
+Set the timeout action for the pretimeout.
+See the section
+.Sx Timeout Actions .
+.It Fl -softtimeout
+Instead of arming the various hardware watchdogs, only use a basic software
+watchdog.
+The default action is just to
+.Xr log 9
+a message (WD_SOFT_LOG).
+.It Fl -softtimeout-action Ar action
+Set the timeout action for the softtimeout.
+See the section
+.Sx Timeout Actions .
+.El
+.Sh Timeout Actions
+The following timeout actions are available via the
+.Fl -pretimeout-action
+and
+.Fl -softtimeout-action
+flags:
+.Bl -tag -width ".Ar printf "
+.It Ar panic
+Call
+.Xr panic 9
+when the timeout is reached.
+.It Ar ddb
+Enter the kernel debugger via
+.Xr kdb_enter 9
+when the timeout is reached.
+.It Ar log
+Log a message using
+.Xr log 9
+when the timeout is reached.
+.It Ar printf
+call the kernel
+.Xr printf 9
+to display a message to the console and
+.Xr dmesg 8
+buffer.
+.El
+.Pp
+Actions can be combined in a comma separated list as so:
+.Ar log,printf
+which would both
+.Xr printf 9
+and
+.Xr log 9
+which will send messages both to
+.Xr dmesg 8
+and the kernel
+.Xr log 4
+device for
+.Xr syslog 8 .
+.Sh FILES
+.Bl -tag -width ".Pa /var/run/watchdogd.pid" -compact
+.It Pa /var/run/watchdogd.pid
+.El
+.Sh EXAMPLES
+.Ss Debugging watchdogd and/or your watchdog script.
+This is a useful recipe for debugging
+.Nm
+and your watchdog script.
+.Pp
+(Note that ^C works oddly because
+.Nm
+calls
+.Xr system 3
+so the
+first ^C will terminate the "sleep" command.)
+.Pp
+Explanation of options used:
+.Bl -enum -offset indent -compact
+.It
+Set Debug on (--debug)
+.It
+Set the watchdog to trip at 30 seconds. (-t 30)
+.It
+Use of a softtimeout:
+.Bl -enum -offset indent -compact -nested
+.It
+Use a softtimeout (do not arm the hardware watchdog).
+(--softtimeout)
+.It
+Set the softtimeout action to do both kernel
+.Xr printf 9
+and
+.Xr log 9
+when it trips.
+(--softtimeout-action log,printf)
+.El
+.It
+Use of a pre-timeout:
+.Bl -enum -offset indent -compact -nested
+.It
+Set a pre-timeout of 15 seconds (this will later trigger a panic/dump).
+(--pretimeout 15)
+.It
+Set the action to also kernel
+.Xr printf 9
+and
+.Xr log 9
+when it trips.
+(--pretimeout-action log,printf)
+.El
+.It
+Use of a script:
+.Bl -enum -offset indent -compact -nested
+.It
+Run "sleep 60" as a shell command that acts as the watchdog (-e 'sleep 60')
+.It
+Warn us when the script takes longer than 1 second to run (-w)
+.El
+.El
+.Bd -literal
+watchdogd --debug -t 30 \\
+ --softtimeout --softtimeout-action log,printf \\
+ --pretimeout 15 --pretimeout-action log,printf \\
+ -e 'sleep 60' -w
+.Ed
+.Ss Production use of example
+.Bl -enum -offset indent -compact
+.It
+Set hard timeout to 120 seconds (-t 120)
+.It
+Set a panic to happen at 60 seconds (to trigger a
+.Xr crash 8
+for dump analysis):
+.Bl -enum -offset indent -compact -nested
+.It
+Use of pre-timeout (--pretimeout 60)
+.It
+Specify pre-timeout action (--pretimeout-action log,printf,panic )
+.El
+.It
+Use of a script:
+.Bl -enum -offset indent -compact -nested
+.It
+Run your script (-e '/path/to/your/script 60')
+.It
+Log if your script takes a longer than 15 seconds to run time. (-w -T 15)
+.El
+.El
+.Bd -literal
+watchdogd -t 120 \\
+ --pretimeout 60 --pretimeout-action log,printf,panic \\
+ -e '/path/to/your/script 60' -w -T 15
+.Ed
+.Sh SEE ALSO
+.Xr watchdog 4 ,
+.Xr watchdog 8 ,
+.Xr watchdog 9
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 5.1 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility and manual page were written by
+.An Sean Kelly Aq Mt smkelly@FreeBSD.org
+and
+.An Poul-Henning Kamp Aq Mt phk@FreeBSD.org .
+.Pp
+Some contributions made by
+.An Jeff Roberson Aq Mt jeff@FreeBSD.org .
+.Pp
+The pretimeout and softtimeout action system was added by
+.An Alfred Perlstein Aq Mt alfred@freebsd.org .
diff --git a/usr.sbin/watchdogd/watchdogd.c b/usr.sbin/watchdogd/watchdogd.c
new file mode 100644
index 0000000..55210d6
--- /dev/null
+++ b/usr.sbin/watchdogd/watchdogd.c
@@ -0,0 +1,792 @@
+/*-
+ * Copyright (c) 2003-2004 Sean M. Kelly <smkelly@FreeBSD.org>
+ * Copyright (c) 2013 iXsystems.com,
+ * author: Alfred Perlstein <alfred@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.
+ */
+
+/*
+ * Software watchdog daemon.
+ */
+
+#include <sys/types.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/rtprio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/sysctl.h>
+#include <sys/watchdog.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libutil.h>
+#include <math.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sysexits.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <getopt.h>
+
+static long fetchtimeout(int opt,
+ const char *longopt, const char *myoptarg, int zero_ok);
+static void parseargs(int, char *[]);
+static int seconds_to_pow2ns(int);
+static void sighandler(int);
+static void watchdog_loop(void);
+static int watchdog_init(void);
+static int watchdog_onoff(int onoff);
+static int watchdog_patpat(u_int timeout);
+static void usage(void);
+static int tstotv(struct timeval *tv, struct timespec *ts);
+static int tvtohz(struct timeval *tv);
+
+static int debugging = 0;
+static int end_program = 0;
+static const char *pidfile = _PATH_VARRUN "watchdogd.pid";
+static u_int timeout = WD_TO_128SEC;
+static u_int exit_timeout = WD_TO_NEVER;
+static u_int pretimeout = 0;
+static u_int timeout_sec;
+static u_int passive = 0;
+static int is_daemon = 0;
+static int is_dry_run = 0; /* do not arm the watchdog, only
+ report on timing of the watch
+ program */
+static int do_timedog = 0;
+static int do_syslog = 1;
+static int fd = -1;
+static int nap = 10;
+static int carp_thresh_seconds = -1;
+static char *test_cmd = NULL;
+
+static const char *getopt_shortopts;
+
+static int pretimeout_set;
+static int pretimeout_act;
+static int pretimeout_act_set;
+
+static int softtimeout_set;
+static int softtimeout_act;
+static int softtimeout_act_set;
+
+static struct option longopts[] = {
+ { "debug", no_argument, &debugging, 1 },
+ { "pretimeout", required_argument, &pretimeout_set, 1 },
+ { "pretimeout-action", required_argument, &pretimeout_act_set, 1 },
+ { "softtimeout", no_argument, &softtimeout_set, 1 },
+ { "softtimeout-action", required_argument, &softtimeout_act_set, 1 },
+ { NULL, 0, NULL, 0}
+};
+
+/*
+ * Ask malloc() to map minimum-sized chunks of virtual address space at a time,
+ * so that mlockall() won't needlessly wire megabytes of unused memory into the
+ * process. This must be done using the malloc_conf string so that it gets set
+ * up before the first allocation, which happens before entry to main().
+ */
+const char * malloc_conf = "lg_chunk:0";
+
+/*
+ * Periodically pat the watchdog, preventing it from firing.
+ */
+int
+main(int argc, char *argv[])
+{
+ struct rtprio rtp;
+ struct pidfh *pfh;
+ pid_t otherpid;
+
+ if (getuid() != 0)
+ errx(EX_SOFTWARE, "not super user");
+
+ parseargs(argc, argv);
+
+ if (do_syslog)
+ openlog("watchdogd", LOG_CONS|LOG_NDELAY|LOG_PERROR,
+ LOG_DAEMON);
+
+ rtp.type = RTP_PRIO_REALTIME;
+ rtp.prio = 0;
+ if (rtprio(RTP_SET, 0, &rtp) == -1)
+ err(EX_OSERR, "rtprio");
+
+ if (!is_dry_run && watchdog_init() == -1)
+ errx(EX_SOFTWARE, "unable to initialize watchdog");
+
+ if (is_daemon) {
+ if (watchdog_onoff(1) == -1)
+ err(EX_OSERR, "patting the dog");
+
+ pfh = pidfile_open(pidfile, 0600, &otherpid);
+ if (pfh == NULL) {
+ if (errno == EEXIST) {
+ watchdog_onoff(0);
+ errx(EX_SOFTWARE, "%s already running, pid: %d",
+ getprogname(), otherpid);
+ }
+ warn("Cannot open or create pidfile");
+ }
+
+ if (debugging == 0 && daemon(0, 0) == -1) {
+ watchdog_onoff(0);
+ pidfile_remove(pfh);
+ err(EX_OSERR, "daemon");
+ }
+
+ signal(SIGHUP, SIG_IGN);
+ signal(SIGINT, sighandler);
+ signal(SIGTERM, sighandler);
+
+ pidfile_write(pfh);
+ if (madvise(0, 0, MADV_PROTECT) != 0)
+ warn("madvise failed");
+ if (mlockall(MCL_CURRENT | MCL_FUTURE) != 0)
+ warn("mlockall failed");
+
+ watchdog_loop();
+
+ /* exiting */
+ pidfile_remove(pfh);
+ return (EX_OK);
+ } else {
+ if (passive)
+ timeout |= WD_PASSIVE;
+ else
+ timeout |= WD_ACTIVE;
+ if (watchdog_patpat(timeout) < 0)
+ err(EX_OSERR, "patting the dog");
+ return (EX_OK);
+ }
+}
+
+static void
+pow2ns_to_ts(int pow2ns, struct timespec *ts)
+{
+ uint64_t ns;
+
+ ns = 1ULL << pow2ns;
+ ts->tv_sec = ns / 1000000000ULL;
+ ts->tv_nsec = ns % 1000000000ULL;
+}
+
+/*
+ * Convert a timeout in seconds to N where 2^N nanoseconds is close to
+ * "seconds".
+ *
+ * The kernel expects the timeouts for watchdogs in "2^N nanosecond format".
+ */
+static u_int
+parse_timeout_to_pow2ns(char opt, const char *longopt, const char *myoptarg)
+{
+ double a;
+ u_int rv;
+ struct timespec ts;
+ struct timeval tv;
+ int ticks;
+ char shortopt[] = "- ";
+
+ if (!longopt)
+ shortopt[1] = opt;
+
+ a = fetchtimeout(opt, longopt, myoptarg, 1);
+
+ if (a == 0)
+ rv = WD_TO_NEVER;
+ else
+ rv = seconds_to_pow2ns(a);
+ pow2ns_to_ts(rv, &ts);
+ tstotv(&tv, &ts);
+ ticks = tvtohz(&tv);
+ if (debugging) {
+ printf("Timeout for %s%s "
+ "is 2^%d nanoseconds "
+ "(in: %s sec -> out: %jd sec %ld ns -> %d ticks)\n",
+ longopt ? "-" : "", longopt ? longopt : shortopt,
+ rv,
+ myoptarg, (intmax_t)ts.tv_sec, ts.tv_nsec, ticks);
+ }
+ if (ticks <= 0) {
+ errx(1, "Timeout for %s%s is too small, please choose a higher timeout.", longopt ? "-" : "", longopt ? longopt : shortopt);
+ }
+
+ return (rv);
+}
+
+/*
+ * Catch signals and begin shutdown process.
+ */
+static void
+sighandler(int signum)
+{
+
+ if (signum == SIGINT || signum == SIGTERM)
+ end_program = 1;
+}
+
+/*
+ * Open the watchdog device.
+ */
+static int
+watchdog_init(void)
+{
+
+ if (is_dry_run)
+ return 0;
+
+ fd = open("/dev/" _PATH_WATCHDOG, O_RDWR);
+ if (fd >= 0)
+ return (0);
+ warn("Could not open watchdog device");
+ return (-1);
+}
+
+/*
+ * If we are doing timing, then get the time.
+ */
+static int
+watchdog_getuptime(struct timespec *tp)
+{
+ int error;
+
+ if (!do_timedog)
+ return 0;
+
+ error = clock_gettime(CLOCK_UPTIME_FAST, tp);
+ if (error)
+ warn("clock_gettime");
+ return (error);
+}
+
+static long
+watchdog_check_dogfunction_time(struct timespec *tp_start,
+ struct timespec *tp_end)
+{
+ struct timeval tv_start, tv_end, tv_now, tv;
+ const char *cmd_prefix, *cmd;
+ struct timespec tp_now;
+ int sec;
+
+ if (!do_timedog)
+ return (0);
+
+ TIMESPEC_TO_TIMEVAL(&tv_start, tp_start);
+ TIMESPEC_TO_TIMEVAL(&tv_end, tp_end);
+ timersub(&tv_end, &tv_start, &tv);
+ sec = tv.tv_sec;
+ if (sec < carp_thresh_seconds)
+ return (sec);
+
+ if (test_cmd) {
+ cmd_prefix = "Watchdog program";
+ cmd = test_cmd;
+ } else {
+ cmd_prefix = "Watchdog operation";
+ cmd = "stat(\"/etc\", &sb)";
+ }
+ if (do_syslog)
+ syslog(LOG_CRIT, "%s: '%s' took too long: "
+ "%d.%06ld seconds >= %d seconds threshold",
+ cmd_prefix, cmd, sec, (long)tv.tv_usec,
+ carp_thresh_seconds);
+ else
+ warnx("%s: '%s' took too long: "
+ "%d.%06ld seconds >= %d seconds threshold",
+ cmd_prefix, cmd, sec, (long)tv.tv_usec,
+ carp_thresh_seconds);
+
+ /*
+ * Adjust the sleep interval again in case syslog(3) took a non-trivial
+ * amount of time to run.
+ */
+ if (watchdog_getuptime(&tp_now))
+ return (sec);
+ TIMESPEC_TO_TIMEVAL(&tv_now, &tp_now);
+ timersub(&tv_now, &tv_start, &tv);
+ sec = tv.tv_sec;
+
+ return (sec);
+}
+
+/*
+ * Main program loop which is iterated every second.
+ */
+static void
+watchdog_loop(void)
+{
+ struct timespec ts_start, ts_end;
+ struct stat sb;
+ long waited;
+ int error, failed;
+
+ while (end_program != 2) {
+ failed = 0;
+
+ error = watchdog_getuptime(&ts_start);
+ if (error) {
+ end_program = 1;
+ goto try_end;
+ }
+
+ if (test_cmd != NULL)
+ failed = system(test_cmd);
+ else
+ failed = stat("/etc", &sb);
+
+ error = watchdog_getuptime(&ts_end);
+ if (error) {
+ end_program = 1;
+ goto try_end;
+ }
+
+ if (failed == 0)
+ watchdog_patpat(timeout|WD_ACTIVE);
+
+ waited = watchdog_check_dogfunction_time(&ts_start, &ts_end);
+ if (nap - waited > 0)
+ sleep(nap - waited);
+
+try_end:
+ if (end_program != 0) {
+ if (watchdog_onoff(0) == 0) {
+ end_program = 2;
+ } else {
+ warnx("Could not stop the watchdog, not exiting");
+ end_program = 0;
+ }
+ }
+ }
+}
+
+/*
+ * Reset the watchdog timer. This function must be called periodically
+ * to keep the watchdog from firing.
+ */
+static int
+watchdog_patpat(u_int t)
+{
+
+ if (is_dry_run)
+ return 0;
+
+ return ioctl(fd, WDIOCPATPAT, &t);
+}
+
+/*
+ * Toggle the kernel's watchdog. This routine is used to enable and
+ * disable the watchdog.
+ */
+static int
+watchdog_onoff(int onoff)
+{
+ int error;
+
+ /* fake successful watchdog op if a dry run */
+ if (is_dry_run)
+ return 0;
+
+ if (onoff) {
+ /*
+ * Call the WDIOC_SETSOFT regardless of softtimeout_set
+ * because we'll need to turn it off if someone had turned
+ * it on.
+ */
+ error = ioctl(fd, WDIOC_SETSOFT, &softtimeout_set);
+ if (error) {
+ warn("setting WDIOC_SETSOFT %d", softtimeout_set);
+ return (error);
+ }
+ error = watchdog_patpat((timeout|WD_ACTIVE));
+ if (error) {
+ warn("watchdog_patpat failed");
+ goto failsafe;
+ }
+ if (softtimeout_act_set) {
+ error = ioctl(fd, WDIOC_SETSOFTTIMEOUTACT,
+ &softtimeout_act);
+ if (error) {
+ warn("setting WDIOC_SETSOFTTIMEOUTACT %d",
+ softtimeout_act);
+ goto failsafe;
+ }
+ }
+ if (pretimeout_set) {
+ error = ioctl(fd, WDIOC_SETPRETIMEOUT, &pretimeout);
+ if (error) {
+ warn("setting WDIOC_SETPRETIMEOUT %d",
+ pretimeout);
+ goto failsafe;
+ }
+ }
+ if (pretimeout_act_set) {
+ error = ioctl(fd, WDIOC_SETPRETIMEOUTACT,
+ &pretimeout_act);
+ if (error) {
+ warn("setting WDIOC_SETPRETIMEOUTACT %d",
+ pretimeout_act);
+ goto failsafe;
+ }
+ }
+ /* pat one more time for good measure */
+ return watchdog_patpat((timeout|WD_ACTIVE));
+ } else {
+ return watchdog_patpat(exit_timeout);
+ }
+failsafe:
+ watchdog_patpat(exit_timeout);
+ return (error);
+}
+
+/*
+ * Tell user how to use the program.
+ */
+static void
+usage(void)
+{
+ if (is_daemon)
+ fprintf(stderr, "usage:\n"
+" watchdogd [-dnSw] [-e cmd] [-I pidfile] [-s sleep] [-t timeout]\n"
+" [-T script_timeout] [-x exit_timeout]\n"
+" [--debug]\n"
+" [--pretimeout seconds] [-pretimeout-action action]\n"
+" [--softtimeout] [-softtimeout-action action]\n"
+);
+ else
+ fprintf(stderr, "usage: watchdog [-d] [-t timeout]\n");
+ exit(EX_USAGE);
+}
+
+static long
+fetchtimeout(int opt, const char *longopt, const char *myoptarg, int zero_ok)
+{
+ const char *errstr;
+ char *p;
+ long rv;
+
+ errstr = NULL;
+ p = NULL;
+ errno = 0;
+ rv = strtol(myoptarg, &p, 0);
+ if ((p != NULL && *p != '\0') || errno != 0)
+ errstr = "is not a number";
+ if (rv < 0 || (!zero_ok && rv == 0))
+ errstr = "must be greater than zero";
+ if (errstr) {
+ if (longopt)
+ errx(EX_USAGE, "--%s argument %s", longopt, errstr);
+ else
+ errx(EX_USAGE, "-%c argument %s", opt, errstr);
+ }
+ return (rv);
+}
+
+struct act_tbl {
+ const char *at_act;
+ int at_value;
+};
+
+static const struct act_tbl act_tbl[] = {
+ { "panic", WD_SOFT_PANIC },
+ { "ddb", WD_SOFT_DDB },
+ { "log", WD_SOFT_LOG },
+ { "printf", WD_SOFT_PRINTF },
+ { NULL, 0 }
+};
+
+static void
+timeout_act_error(const char *lopt, const char *badact)
+{
+ char *opts, *oldopts;
+ int i;
+
+ opts = NULL;
+ for (i = 0; act_tbl[i].at_act != NULL; i++) {
+ oldopts = opts;
+ if (asprintf(&opts, "%s%s%s",
+ oldopts == NULL ? "" : oldopts,
+ oldopts == NULL ? "" : ", ",
+ act_tbl[i].at_act) == -1)
+ err(EX_OSERR, "malloc");
+ free(oldopts);
+ }
+ warnx("bad --%s argument '%s' must be one of (%s).",
+ lopt, badact, opts);
+ usage();
+}
+
+/*
+ * Take a comma separated list of actions and or the flags
+ * together for the ioctl.
+ */
+static int
+timeout_act_str2int(const char *lopt, const char *acts)
+{
+ int i;
+ char *dupacts, *tofree;
+ char *o;
+ int rv = 0;
+
+ tofree = dupacts = strdup(acts);
+ if (!tofree)
+ err(EX_OSERR, "malloc");
+ while ((o = strsep(&dupacts, ",")) != NULL) {
+ for (i = 0; act_tbl[i].at_act != NULL; i++) {
+ if (!strcmp(o, act_tbl[i].at_act)) {
+ rv |= act_tbl[i].at_value;
+ break;
+ }
+ }
+ if (act_tbl[i].at_act == NULL)
+ timeout_act_error(lopt, o);
+ }
+ free(tofree);
+ return rv;
+}
+
+int
+tstotv(struct timeval *tv, struct timespec *ts)
+{
+
+ tv->tv_sec = ts->tv_sec;
+ tv->tv_usec = ts->tv_nsec / 1000;
+ return 0;
+}
+
+/*
+ * Convert a timeval to a number of ticks.
+ * Mostly copied from the kernel.
+ */
+int
+tvtohz(struct timeval *tv)
+{
+ register unsigned long ticks;
+ register long sec, usec;
+ int hz;
+ size_t hzsize;
+ int error;
+ int tick;
+
+ hzsize = sizeof(hz);
+
+ error = sysctlbyname("kern.hz", &hz, &hzsize, NULL, 0);
+ if (error)
+ err(1, "sysctlbyname kern.hz");
+
+ tick = 1000000 / hz;
+
+ /*
+ * If the number of usecs in the whole seconds part of the time
+ * difference fits in a long, then the total number of usecs will
+ * fit in an unsigned long. Compute the total and convert it to
+ * ticks, rounding up and adding 1 to allow for the current tick
+ * to expire. Rounding also depends on unsigned long arithmetic
+ * to avoid overflow.
+ *
+ * Otherwise, if the number of ticks in the whole seconds part of
+ * the time difference fits in a long, then convert the parts to
+ * ticks separately and add, using similar rounding methods and
+ * overflow avoidance. This method would work in the previous
+ * case but it is slightly slower and assumes that hz is integral.
+ *
+ * Otherwise, round the time difference down to the maximum
+ * representable value.
+ *
+ * If ints have 32 bits, then the maximum value for any timeout in
+ * 10ms ticks is 248 days.
+ */
+ sec = tv->tv_sec;
+ usec = tv->tv_usec;
+ if (usec < 0) {
+ sec--;
+ usec += 1000000;
+ }
+ if (sec < 0) {
+#ifdef DIAGNOSTIC
+ if (usec > 0) {
+ sec++;
+ usec -= 1000000;
+ }
+ printf("tvotohz: negative time difference %ld sec %ld usec\n",
+ sec, usec);
+#endif
+ ticks = 1;
+ } else if (sec <= LONG_MAX / 1000000)
+ ticks = (sec * 1000000 + (unsigned long)usec + (tick - 1))
+ / tick + 1;
+ else if (sec <= LONG_MAX / hz)
+ ticks = sec * hz
+ + ((unsigned long)usec + (tick - 1)) / tick + 1;
+ else
+ ticks = LONG_MAX;
+ if (ticks > INT_MAX)
+ ticks = INT_MAX;
+ return ((int)ticks);
+}
+
+static int
+seconds_to_pow2ns(int seconds)
+{
+ uint64_t power;
+ uint64_t ns;
+ uint64_t shifted;
+
+ if (seconds <= 0)
+ errx(1, "seconds %d < 0", seconds);
+ ns = ((uint64_t)seconds) * 1000000000ULL;
+ power = flsll(ns);
+ shifted = 1ULL << power;
+ if (shifted <= ns) {
+ power++;
+ }
+ if (debugging) {
+ printf("shifted %lld\n", (long long)shifted);
+ printf("seconds_to_pow2ns: seconds: %d, ns %lld, power %d\n",
+ seconds, (long long)ns, (int)power);
+ }
+ return (power);
+}
+
+
+/*
+ * Handle the few command line arguments supported.
+ */
+static void
+parseargs(int argc, char *argv[])
+{
+ int longindex;
+ int c;
+ const char *lopt;
+
+ /*
+ * if we end with a 'd' aka 'watchdogd' then we are the daemon program,
+ * otherwise run as a command line utility.
+ */
+ c = strlen(argv[0]);
+ if (argv[0][c - 1] == 'd')
+ is_daemon = 1;
+
+ if (is_daemon)
+ getopt_shortopts = "I:de:ns:t:ST:wx:?";
+ else
+ getopt_shortopts = "dt:?";
+
+ while ((c = getopt_long(argc, argv, getopt_shortopts, longopts,
+ &longindex)) != -1) {
+ switch (c) {
+ case 'I':
+ pidfile = optarg;
+ break;
+ case 'd':
+ debugging = 1;
+ break;
+ case 'e':
+ test_cmd = strdup(optarg);
+ break;
+ case 'n':
+ is_dry_run = 1;
+ break;
+#ifdef notyet
+ case 'p':
+ passive = 1;
+ break;
+#endif
+ case 's':
+ nap = fetchtimeout(c, NULL, optarg, 0);
+ break;
+ case 'S':
+ do_syslog = 0;
+ break;
+ case 't':
+ timeout_sec = atoi(optarg);
+ timeout = parse_timeout_to_pow2ns(c, NULL, optarg);
+ if (debugging)
+ printf("Timeout is 2^%d nanoseconds\n",
+ timeout);
+ break;
+ case 'T':
+ carp_thresh_seconds =
+ fetchtimeout(c, "NULL", optarg, 0);
+ break;
+ case 'w':
+ do_timedog = 1;
+ break;
+ case 'x':
+ exit_timeout = parse_timeout_to_pow2ns(c, NULL, optarg);
+ if (exit_timeout != 0)
+ exit_timeout |= WD_ACTIVE;
+ break;
+ case 0:
+ lopt = longopts[longindex].name;
+ if (!strcmp(lopt, "pretimeout")) {
+ pretimeout = fetchtimeout(0, lopt, optarg, 0);
+ } else if (!strcmp(lopt, "pretimeout-action")) {
+ pretimeout_act = timeout_act_str2int(lopt,
+ optarg);
+ } else if (!strcmp(lopt, "softtimeout-action")) {
+ softtimeout_act = timeout_act_str2int(lopt,
+ optarg);
+ } else {
+ /* warnx("bad option at index %d: %s", optind,
+ argv[optind]);
+ usage();
+ */
+ }
+ break;
+ case '?':
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+
+ if (carp_thresh_seconds == -1)
+ carp_thresh_seconds = nap;
+
+ if (argc != optind)
+ errx(EX_USAGE, "extra arguments.");
+ if (is_daemon && timeout < WD_TO_1SEC)
+ errx(EX_USAGE, "-t argument is less than one second.");
+ if (pretimeout_set) {
+ struct timespec ts;
+
+ pow2ns_to_ts(timeout, &ts);
+ if (pretimeout >= (uintmax_t)ts.tv_sec) {
+ errx(EX_USAGE,
+ "pretimeout (%d) >= timeout (%d -> %ld)\n"
+ "see manual section TIMEOUT RESOLUTION",
+ pretimeout, timeout_sec, (long)ts.tv_sec);
+ }
+ }
+}
diff --git a/usr.sbin/wlandebug/Makefile b/usr.sbin/wlandebug/Makefile
new file mode 100644
index 0000000..e916c23
--- /dev/null
+++ b/usr.sbin/wlandebug/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= wlandebug
+MAN= wlandebug.8
+
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/wlandebug/Makefile.depend b/usr.sbin/wlandebug/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/wlandebug/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/wlandebug/wlandebug.8 b/usr.sbin/wlandebug/wlandebug.8
new file mode 100644
index 0000000..0640eff
--- /dev/null
+++ b/usr.sbin/wlandebug/wlandebug.8
@@ -0,0 +1,177 @@
+.\" Copyright (c) 2007 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 AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED 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 17, 2015
+.Dt WLANDEBUG 8
+.Os
+.Sh NAME
+.Nm wlandebug
+.Nd "set/query 802.11 wireless debugging messages"
+.Sh SYNOPSIS
+.Nm
+.Op Fl d | Fl i Ar ifnet
+.Op Fl flag|+flag Ar ...
+.Sh DESCRIPTION
+The
+.Nm
+command is a tool for enabling and disabling
+debugging messages in the
+.Xr wlan 4
+module.
+Running
+.Nm
+without any options will display the current messages
+enabled for the specified network interface
+(by default, ``wlan0').
+The default debugging level for new interfaces can be set
+by specifying the
+.Fl d
+option.
+When run as the super-user
+.Nm
+can be used to enable and/or disable debugging messages.
+.Pp
+To enable debugging messages of a certain
+.Ar type
+use
+.Ar +type ;
+to disable such messages use
+.Ar -type .
+Multiple messages can be enabled and disabled with a single command.
+.Pp
+Messages are organized in the following groups:
+.Bl -tag -width ".Ar dumppkts"
+.It Ar debug
+general debugging facilities; equivalent to setting the debug
+parameter with
+.Xr ifconfig 8 .
+.It Ar dumppkts
+dump packet contents on transmit and receive.
+.It Ar crypto
+crypto-related work.
+.It Ar input
+errors encountered during input handling.
+.It Ar xrate
+extended rate set handling (for 802.11g).
+.It Ar elemid
+information element processing in 802.11 management frames.
+.It Ar node
+management of per-station state.
+.It Ar assoc
+802.11 station association processing; particularly useful to
+see when stations join and leave a BSS.
+.It Ar auth
+802.11 station authentication processing.
+.It Ar scan
+scanning operation; especially useful for debugging problems
+with not locating an access point.
+.It Ar output
+errors encountered during output handling.
+.It Ar state
+.Xr wlan 4
+state machine operation.
+.It Ar power
+802.11 power save operation; in hostap mode this enables
+copious information about buffered frames for stations operating
+in power save mode.
+.It Ar hwmp
+trace operation of Hybrid Wireless Mesh Protocol processing.
+.It Ar dot1xsm
+802.1x state machine operation; not presently meaningful as 802.1x protocol
+support is implemented in user mode by the
+.Xr hostapd 8
+program.
+.It Ar radius
+radius backend operation as it relates to 802.1x operation;
+not presently meaningful as 802.1x protocol
+support is implemented in user mode by the
+.Xr hostapd 8
+program.
+.It Ar raddump
+dump packets exchanged with the radius backend for 802.1x operation;
+not presently meaningful as 802.1x protocol
+support is implemented in user mode by the
+.Xr hostapd 8
+program.
+.It Ar mesh
+trace operation of 802.11s mesh protocol processing.
+.It Ar wpa
+trace operation of the WPA protocol;
+only partly meaningful as WPA protocol
+support is mostly implemented in user mode by the
+.Xr hostapd 8
+and
+.Xr wpa_supplicant 8
+programs.
+.It Ar acl
+trace operation of the Access Control List (ACL) support; see
+.Xr wlan_acl 4
+for more details.
+.It Ar wme
+trace operation of WME/WMM protocol processing.
+.It Ar superg
+trace operation of Atheros SuperG protocol processing.
+.It Ar doth
+trace operation of IEEE 802.11h protocol processing.
+.It Ar inact
+trace station inactivity processing; in particular,
+show when stations associated to an access point are dropped due to
+inactivity.
+.It Ar roam
+trace station mode roaming between access points.
+.It Ar rate
+trace transmit rate control operation.
+.El
+.Sh EXAMPLES
+The following might be used to debug basic station mode operation:
+.Pp
+.Dl "wlandebug -i wlan1 scan+auth+assoc"
+.Pp
+it enables debug messages while scanning, authenticating to
+an access point, and associating to an access point.
+.Sh SEE ALSO
+.Xr athdebug 8 ,
+.Xr athstats 8 ,
+.Xr ifconfig 8 ,
+.Xr wlanstats 8
+.Sh NOTES
+Different wireless drivers support different debugging messages.
+Drivers such as
+.Xr ath 4
+and
+.Xr ral 4
+that depend on the
+.Xr wlan 4
+module for 802.11 protocol processing typically support
+most of the debugging messages while devices that
+implement parts of the 802.11 protocol in firmware do not.
+.Pp
+Some debugging messages are no longer meaningful
+because protocol processing has moved from the operating
+system to user mode programs such as
+.Xr hostapd 8
+and
+.Xr wpa_supplicant 8 .
diff --git a/usr.sbin/wlandebug/wlandebug.c b/usr.sbin/wlandebug/wlandebug.c
new file mode 100644
index 0000000..9bec123
--- /dev/null
+++ b/usr.sbin/wlandebug/wlandebug.c
@@ -0,0 +1,243 @@
+/*-
+ * Copyright (c) 2002-2009 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,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * 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 NONINFRINGEMENT, 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.
+ *
+ * $FreeBSD$
+ */
+
+/*
+ * wlandebug [-i interface] flags
+ * (default interface is wlan.0).
+ */
+#include <sys/types.h>
+#include <sys/sysctl.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <string.h>
+#include <err.h>
+
+#define N(a) (sizeof(a)/sizeof(a[0]))
+
+const char *progname;
+
+#define IEEE80211_MSG_11N 0x80000000 /* 11n mode debug */
+#define IEEE80211_MSG_DEBUG 0x40000000 /* IFF_DEBUG equivalent */
+#define IEEE80211_MSG_DUMPPKTS 0x20000000 /* IFF_LINK2 equivalant */
+#define IEEE80211_MSG_CRYPTO 0x10000000 /* crypto work */
+#define IEEE80211_MSG_INPUT 0x08000000 /* input handling */
+#define IEEE80211_MSG_XRATE 0x04000000 /* rate set handling */
+#define IEEE80211_MSG_ELEMID 0x02000000 /* element id parsing */
+#define IEEE80211_MSG_NODE 0x01000000 /* node handling */
+#define IEEE80211_MSG_ASSOC 0x00800000 /* association handling */
+#define IEEE80211_MSG_AUTH 0x00400000 /* authentication handling */
+#define IEEE80211_MSG_SCAN 0x00200000 /* scanning */
+#define IEEE80211_MSG_OUTPUT 0x00100000 /* output handling */
+#define IEEE80211_MSG_STATE 0x00080000 /* state machine */
+#define IEEE80211_MSG_POWER 0x00040000 /* power save handling */
+#define IEEE80211_MSG_HWMP 0x00020000 /* hybrid mesh protocol */
+#define IEEE80211_MSG_DOT1XSM 0x00010000 /* 802.1x state machine */
+#define IEEE80211_MSG_RADIUS 0x00008000 /* 802.1x radius client */
+#define IEEE80211_MSG_RADDUMP 0x00004000 /* dump 802.1x radius packets */
+#define IEEE80211_MSG_MESH 0x00002000 /* mesh networking */
+#define IEEE80211_MSG_WPA 0x00001000 /* WPA/RSN protocol */
+#define IEEE80211_MSG_ACL 0x00000800 /* ACL handling */
+#define IEEE80211_MSG_WME 0x00000400 /* WME protocol */
+#define IEEE80211_MSG_SUPERG 0x00000200 /* Atheros SuperG protocol */
+#define IEEE80211_MSG_DOTH 0x00000100 /* 802.11h support */
+#define IEEE80211_MSG_INACT 0x00000080 /* inactivity handling */
+#define IEEE80211_MSG_ROAM 0x00000040 /* sta-mode roaming */
+#define IEEE80211_MSG_RATECTL 0x00000020 /* tx rate control */
+#define IEEE80211_MSG_ACTION 0x00000010 /* action frame handling */
+#define IEEE80211_MSG_WDS 0x00000008 /* WDS handling */
+#define IEEE80211_MSG_IOCTL 0x00000004 /* ioctl handling */
+#define IEEE80211_MSG_TDMA 0x00000002 /* TDMA handling */
+
+static struct {
+ const char *name;
+ u_int bit;
+} flags[] = {
+ { "11n", IEEE80211_MSG_11N },
+ { "debug", IEEE80211_MSG_DEBUG },
+ { "dumppkts", IEEE80211_MSG_DUMPPKTS },
+ { "crypto", IEEE80211_MSG_CRYPTO },
+ { "input", IEEE80211_MSG_INPUT },
+ { "xrate", IEEE80211_MSG_XRATE },
+ { "elemid", IEEE80211_MSG_ELEMID },
+ { "node", IEEE80211_MSG_NODE },
+ { "assoc", IEEE80211_MSG_ASSOC },
+ { "auth", IEEE80211_MSG_AUTH },
+ { "scan", IEEE80211_MSG_SCAN },
+ { "output", IEEE80211_MSG_OUTPUT },
+ { "state", IEEE80211_MSG_STATE },
+ { "power", IEEE80211_MSG_POWER },
+ { "hwmp", IEEE80211_MSG_HWMP },
+ { "dot1xsm", IEEE80211_MSG_DOT1XSM },
+ { "radius", IEEE80211_MSG_RADIUS },
+ { "raddump", IEEE80211_MSG_RADDUMP },
+ { "mesh", IEEE80211_MSG_MESH },
+ { "wpa", IEEE80211_MSG_WPA },
+ { "acl", IEEE80211_MSG_ACL },
+ { "wme", IEEE80211_MSG_WME },
+ { "superg", IEEE80211_MSG_SUPERG },
+ { "doth", IEEE80211_MSG_DOTH },
+ { "inact", IEEE80211_MSG_INACT },
+ { "roam", IEEE80211_MSG_ROAM },
+ { "rate", IEEE80211_MSG_RATECTL },
+ { "action", IEEE80211_MSG_ACTION },
+ { "wds", IEEE80211_MSG_WDS },
+ { "ioctl", IEEE80211_MSG_IOCTL },
+ { "tdma", IEEE80211_MSG_TDMA },
+};
+
+static u_int
+getflag(const char *name, int len)
+{
+ int i;
+
+ for (i = 0; i < N(flags); i++)
+ if (strncasecmp(flags[i].name, name, len) == 0)
+ return flags[i].bit;
+ return 0;
+}
+
+static void
+usage(void)
+{
+ int i;
+
+ fprintf(stderr, "usage: %s [-d | -i device] [flags]\n", progname);
+ fprintf(stderr, "where flags are:\n");
+ for (i = 0; i < N(flags); i++)
+ printf("%s\n", flags[i].name);
+ exit(-1);
+}
+
+static void
+setoid(char oid[], size_t oidlen, const char *wlan)
+{
+#ifdef __linux__
+ if (wlan)
+ snprintf(oid, oidlen, "net.%s.debug", wlan);
+#elif __FreeBSD__
+ if (wlan)
+ snprintf(oid, oidlen, "net.wlan.%s.debug", wlan+4);
+ else
+ snprintf(oid, oidlen, "net.wlan.debug");
+#elif __NetBSD__
+ if (wlan)
+ snprintf(oid, oidlen, "net.link.ieee80211.%s.debug", wlan);
+ else
+ snprintf(oid, oidlen, "net.link.ieee80211.debug");
+#else
+#error "No support for this system"
+#endif
+}
+
+int
+main(int argc, char *argv[])
+{
+ const char *cp, *tp;
+ const char *sep;
+ int op, i;
+ u_int32_t debug, ndebug;
+ size_t debuglen;
+ char oid[256];
+
+ progname = argv[0];
+ setoid(oid, sizeof(oid), "wlan0");
+ if (argc > 1) {
+ if (strcmp(argv[1], "-d") == 0) {
+ setoid(oid, sizeof(oid), NULL);
+ argc -= 1, argv += 1;
+ } else if (strcmp(argv[1], "-i") == 0) {
+ if (argc <= 2)
+ errx(1, "missing interface name for -i option");
+ if (strncmp(argv[2], "wlan", 4) != 0)
+ errx(1, "expecting a wlan interface name");
+ setoid(oid, sizeof(oid), argv[2]);
+ argc -= 2, argv += 2;
+ } else if (strcmp(argv[1], "-?") == 0)
+ usage();
+ }
+
+ debuglen = sizeof(debug);
+ if (sysctlbyname(oid, &debug, &debuglen, NULL, 0) < 0)
+ err(1, "sysctl-get(%s)", oid);
+ ndebug = debug;
+ for (; argc > 1; argc--, argv++) {
+ cp = argv[1];
+ do {
+ u_int bit;
+
+ if (*cp == '-') {
+ cp++;
+ op = -1;
+ } else if (*cp == '+') {
+ cp++;
+ op = 1;
+ } else
+ op = 0;
+ for (tp = cp; *tp != '\0' && *tp != '+' && *tp != '-';)
+ tp++;
+ bit = getflag(cp, tp-cp);
+ if (op < 0)
+ ndebug &= ~bit;
+ else if (op > 0)
+ ndebug |= bit;
+ else {
+ if (bit == 0) {
+ int c = *cp;
+ if (isdigit(c))
+ bit = strtoul(cp, NULL, 0);
+ else
+ errx(1, "unknown flag %.*s",
+ (int)(tp-cp), cp);
+ }
+ ndebug = bit;
+ }
+ } while (*(cp = tp) != '\0');
+ }
+ if (debug != ndebug) {
+ printf("%s: 0x%x => ", oid, debug);
+ if (sysctlbyname(oid, NULL, NULL, &ndebug, sizeof(ndebug)) < 0)
+ err(1, "sysctl-set(%s)", oid);
+ printf("0x%x", ndebug);
+ debug = ndebug;
+ } else
+ printf("%s: 0x%x", oid, debug);
+ sep = "<";
+ for (i = 0; i < N(flags); i++)
+ if (debug & flags[i].bit) {
+ printf("%s%s", sep, flags[i].name);
+ sep = ",";
+ }
+ printf("%s\n", *sep != '<' ? ">" : "");
+ return 0;
+}
diff --git a/usr.sbin/wlconfig/Makefile b/usr.sbin/wlconfig/Makefile
new file mode 100644
index 0000000..8bc6262
--- /dev/null
+++ b/usr.sbin/wlconfig/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PROG= wlconfig
+MAN= wlconfig.8
+MANSUBDIR= /i386
+
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/wlconfig/Makefile.depend b/usr.sbin/wlconfig/Makefile.depend
new file mode 100644
index 0000000..79eb58b
--- /dev/null
+++ b/usr.sbin/wlconfig/Makefile.depend
@@ -0,0 +1,16 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/wlconfig/wlconfig.8 b/usr.sbin/wlconfig/wlconfig.8
new file mode 100644
index 0000000..b448339
--- /dev/null
+++ b/usr.sbin/wlconfig/wlconfig.8
@@ -0,0 +1,143 @@
+.\" $FreeBSD$
+.\"
+.Dd December 26, 1996
+.Dt WLCONFIG 8 i386
+.Os
+.Sh NAME
+.Nm wlconfig
+.Nd read/write wavelan config parameters
+.Sh SYNOPSIS
+.Nm
+.Ar ifname
+.Op Ar param value ...
+.Sh DESCRIPTION
+The
+.Nm
+utility can be used to read and set parameters for the NCR/AT&T Wavelan
+radio LAN card.
+Various parameters stored in the non-volatile Parameter
+Storage Area (PSA) on the card can be modified with this program, replacing
+the DOS-based
+.Nm instconf.exe
+program.
+It can also be used to interrogate the optional signal
+strength cache which may have been compiled into the driver.
+.Pp
+The
+.Ar ifname
+parameter specifies the wavelan interface name (eg.
+.Pa wl0 ) .
+If no other arguments are supplied, the current contents of the PSA
+are interpreted and displayed.
+.Pp
+The
+.Ar param
+and
+.Ar value
+arguments can be used to change the value of several parameters.
+Any number of
+.Ar param value
+pairs may be supplied.
+.Bl -tag -width 15n -offset indent
+.It Va param
+.Va value
+.It irq
+IRQ value (used at next reset), may be one of 3,4,5,6,10,11,12,15.
+.It mac
+Local MAC value (ethernet address).
+.It macsel
+.Sq soft
+(as set by the
+.Sq mac
+parameter) or
+.Sq default
+(as set at the factory).
+.It nwid
+The NWID is a 2-byte parameter passed to the card's radio modem.
+NWIDs allow multiple logically discrete networks to operate
+independently whilst occupying the same airspace.
+Packets with a different NWID are simply ignored by the modem.
+In the hardware, NWIDs are stored long-term in non-volatile memory
+(called the PSA or programmable storage area), and are loaded by
+software into the radio modem when the driver is
+initialized.
+This sets the default NWID loaded at startup.
+.It currnwid
+This sets the current operating NWID (but does not save it to the
+PSA).
+.It cache
+The driver may maintain a per interface fixed size cache of signal strength,
+silence, and quality levels, which are indexed by sender MAC addresses.
+Input packets are stored in the cache, and when received, the values
+stored in the radio modem are interrogated and stored.
+There are also two sysctl values (iponly and multicast only) which
+can be used for filtering out some input packets.
+By default, the
+cache mechanism stores only non-unicast IP packets, but this can
+be changed with
+.Xr sysctl 8 .
+Each non-filtered
+input packet causes a cache update, hence one can monitor
+the antennae signal strength to a remote system.
+There are three commands that can be given as values:
+.Sq raw ,
+which prints out the raw signal strength data as found in the radio
+modem hardware value,
+.Sq scale ,
+which scales the raw hardware values to 0..100%, and
+.Sq zero
+which clears out the cache in case you want to store new samples.
+.El
+.Pp
+Note that if the IRQ on the Wavelan card is incorrect, the interface
+will be configured, but will not function.
+The
+.Nm
+utility should then be used to reconfigure the card to a sensible
+value.
+.Sh EXAMPLES
+Set the NWID to 0x1234:
+.Bd -literal -offset indent
+# wlconfig wl0 nwid 0x1234
+.Ed
+.Pp
+Show the current settings:
+.Bd -literal -offset indent
+# wlconfig wl0
+Board type : ISA
+Base address options : 0x300, 0x390, 0x3c0, 0x3e0
+Waitstates : 0
+Bus mode : ISA
+IRQ : 10
+Default MAC address : 08:00:0e:20:3d:4b
+Soft MAC address : 00:00:00:00:00:00
+Current MAC address : Default
+Adapter compatibility : PC-AT 2.4GHz
+Threshold preset : 1
+Call code required : NO
+Subband : 2425MHz
+Quality threshold : 3
+Hardware version : 0 (Rel1/Rel2)
+Network ID enable : YES
+NWID : 0xdead
+Datalink security : NO
+Databus width : 16 (variable)
+Configuration state : unconfigured
+CRC-16 : 0x3c26
+CRC status : OK
+.Ed
+.Pp
+Print a scaled version of the signal strength cache:
+.Bd -literal -offset indent
+# wlconfig wl0 cache scale
+.Ed
+.Sh SEE ALSO
+.Xr wl 4 ,
+.Xr sysctl 8
+.Sh HISTORY
+This implementation of the
+.Nm
+utility is completely new, written for Hilink Internet by
+.An Michael Smith ,
+and updated by
+.An Jim Binkley &c .
diff --git a/usr.sbin/wlconfig/wlconfig.c b/usr.sbin/wlconfig/wlconfig.c
new file mode 100644
index 0000000..f361b2c
--- /dev/null
+++ b/usr.sbin/wlconfig/wlconfig.c
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) 1996
+ * Michael Smith. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must 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 Michael Smith AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL Michael Smith OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 */
+
+/*
+ * wlconfig.c
+ *
+ * utility to read out and change various WAVELAN parameters.
+ * Currently supports NWID and IRQ values.
+ *
+ * The NWID is used by 2 or more wavelan controllers to determine
+ * if packets should be received or not. It is a filter that
+ * is roughly analogous to the "channel" setting with a garage
+ * door controller. Two companies side by side with wavelan devices
+ * that could actually hear each other can use different NWIDs
+ * and ignore packets. In truth however, the air space is shared,
+ * and the NWID is a virtual filter.
+ *
+ * In the current set of wavelan drivers, ioctls changed only
+ * the runtime radio modem registers which act in a manner analogous
+ * to an ethernet transceiver. The ioctls do not change the
+ * stored nvram PSA (or parameter storage area). At boot, the PSA
+ * values are stored in the radio modem. Thus when the
+ * system reboots it will restore the wavelan NWID to the value
+ * stored in the PSA. The NCR/ATT dos utilities must be used to
+ * change the initial NWID values in the PSA. The wlconfig utility
+ * may be used to set a different NWID at runtime; this is only
+ * permitted while the interface is up and running.
+ *
+ * By contrast, the IRQ value can only be changed while the
+ * Wavelan card is down and unconfigured, and it will remain
+ * disabled after an IRQ change until reboot.
+ *
+ */
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <machine/if_wl_wavelan.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+
+/* translate IRQ bit to number */
+/* array for maping irq numbers to values for the irq parameter register */
+static int irqvals[16] = {
+ 0, 0, 0, 0x01, 0x02, 0x04, 0, 0x08, 0, 0, 0x10, 0x20, 0x40, 0, 0, 0x80
+};
+
+/* cache */
+static int w_sigitems; /* count of valid items */
+static struct w_sigcache wsc[MAXCACHEITEMS];
+
+int
+wlirq(int irqval)
+{
+ int irq;
+
+ for(irq = 0; irq < 16; irq++)
+ if(irqvals[irq] == irqval)
+ return(irq);
+ return 0;
+}
+
+char *compat_type[] = {
+ "PC-AT 915MHz",
+ "PC-MC 915MHz",
+ "PC-AT 2.4GHz",
+ "PC-MC 2.4GHz",
+ "PCCARD or 1/2 size AT, 915MHz or 2.4GHz"
+};
+
+char *subband[] = {
+ "915MHz/see WaveModem",
+ "2425MHz",
+ "2460MHz",
+ "2484MHz",
+ "2430.5MHz"
+};
+
+
+/*
+** print_psa
+**
+** Given a pointer to a PSA structure, print it out
+*/
+void
+print_psa(u_char *psa, int currnwid)
+{
+ int nwid;
+
+ /*
+ ** Work out what sort of board we have
+ */
+ if (psa[0] == 0x14) {
+ printf("Board type : Microchannel\n");
+ } else {
+ if (psa[1] == 0) {
+ printf("Board type : PCCARD\n");
+ } else {
+ printf("Board type : ISA");
+ if ((psa[4] == 0) &&
+ (psa[5] == 0) &&
+ (psa[6] == 0))
+ printf(" (DEC OEM)");
+ printf("\n");
+ printf("Base address options : 0x300, 0x%02x0, 0x%02x0, 0x%02x0\n",
+ (int)psa[1], (int)psa[2], (int)psa[3]);
+ printf("Waitstates : %d\n",psa[7] & 0xf);
+ printf("Bus mode : %s\n",(psa[7] & 0x10) ? "EISA" : "ISA");
+ printf("IRQ : %d\n",wlirq(psa[8]));
+ }
+ }
+ printf("Default MAC address : %02x:%02x:%02x:%02x:%02x:%02x\n",
+ psa[0x10],psa[0x11],psa[0x12],psa[0x13],psa[0x14],psa[0x15]);
+ printf("Soft MAC address : %02x:%02x:%02x:%02x:%02x:%02x\n",
+ psa[0x16],psa[0x17],psa[0x18],psa[0x19],psa[0x1a],psa[0x1b]);
+ printf("Current MAC address : %s\n",(psa[0x1c] & 0x1) ? "Soft" : "Default");
+ printf("Adapter compatibility : ");
+ if (psa[0x1d] < 5) {
+ printf("%s\n",compat_type[psa[0x1d]]);
+ } else {
+ printf("unknown\n");
+ }
+ printf("Threshold preset : %d\n",psa[0x1e]);
+ printf("Call code required : %s\n",(psa[0x1f] & 0x1) ? "YES" : "NO");
+ if (psa[0x1f] & 0x1)
+ printf("Call code : 0x%02x%02x%02x%02x%02x%02x%02x%02x\n",
+ psa[0x30],psa[0x31],psa[0x32],psa[0x33],psa[0x34],psa[0x35],psa[0x36],psa[0x37]);
+ printf("Subband : %s\n",subband[psa[0x20] & 0xf]);
+ printf("Quality threshold : %d\n",psa[0x21]);
+ printf("Hardware version : %d (%s)\n",psa[0x22],psa[0x22] ? "Rel3" : "Rel1/Rel2");
+ printf("Network ID enable : %s\n",(psa[0x25] & 0x1) ? "YES" : "NO");
+ if (psa[0x25] & 0x1) {
+ nwid = (psa[0x23] << 8) + psa[0x24];
+ printf("NWID : 0x%04x\n",nwid);
+ if (nwid != currnwid) {
+ printf("Current NWID : 0x%04x\n",currnwid);
+ }
+ }
+ printf("Datalink security : %s\n",(psa[0x26] & 0x1) ? "YES" : "NO");
+ if (psa[0x26] & 0x1) {
+ printf("Encryption key : ");
+ if (psa[0x27] == 0) {
+ printf("DENIED\n");
+ } else {
+ printf("0x%02x%02x%02x%02x%02x%02x%02x%02x\n",
+ psa[0x27],psa[0x28],psa[0x29],psa[0x2a],psa[0x2b],psa[0x2c],psa[0x2d],psa[0x2e]);
+ }
+ }
+ printf("Databus width : %d (%s)\n",
+ (psa[0x2f] & 0x1) ? 16 : 8, (psa[0x2f] & 0x80) ? "fixed" : "variable");
+ printf("Configuration state : %sconfigured\n",(psa[0x38] & 0x1) ? "" : "un");
+ printf("CRC-16 : 0x%02x%02x\n",psa[0x3e],psa[0x3d]);
+ printf("CRC status : ");
+ switch(psa[0x3f]) {
+ case 0xaa:
+ printf("OK\n");
+ break;
+ case 0x55:
+ printf("BAD\n");
+ break;
+ default:
+ printf("Error\n");
+ break;
+ }
+}
+
+
+static void
+usage()
+{
+ fprintf(stderr,"usage: wlconfig ifname [param value ...]\n");
+ exit(1);
+}
+
+
+void
+get_cache(int sd, struct ifreq *ifr)
+{
+ /* get the cache count */
+ if (ioctl(sd, SIOCGWLCITEM, (caddr_t)ifr))
+ err(1, "SIOCGWLCITEM - get cache count");
+ w_sigitems = (int) ifr->ifr_data;
+
+ ifr->ifr_data = (caddr_t) &wsc;
+ /* get the cache */
+ if (ioctl(sd, SIOCGWLCACHE, (caddr_t)ifr))
+ err(1, "SIOCGWLCACHE - get cache count");
+}
+
+static int
+scale_value(int value, int max)
+{
+ double dmax = (double) max;
+ if (value > max)
+ return(100);
+ return((value/dmax) * 100);
+}
+
+static void
+dump_cache(int rawFlag)
+{
+ int i;
+ int signal, silence, quality;
+
+ if (rawFlag)
+ printf("signal range 0..63: silence 0..63: quality 0..15\n");
+ else
+ printf("signal range 0..100: silence 0..100: quality 0..100\n");
+
+ /* after you read it, loop through structure,i.e. wsc
+ * print each item:
+ */
+ for(i = 0; i < w_sigitems; i++) {
+ printf("[%d:%d]>\n", i+1, w_sigitems);
+ printf("\tip: %d.%d.%d.%d,",((wsc[i].ipsrc >> 0) & 0xff),
+ ((wsc[i].ipsrc >> 8) & 0xff),
+ ((wsc[i].ipsrc >> 16) & 0xff),
+ ((wsc[i].ipsrc >> 24) & 0xff));
+ printf(" mac: %02x:%02x:%02x:%02x:%02x:%02x\n",
+ wsc[i].macsrc[0]&0xff,
+ wsc[i].macsrc[1]&0xff,
+ wsc[i].macsrc[2]&0xff,
+ wsc[i].macsrc[3]&0xff,
+ wsc[i].macsrc[4]&0xff,
+ wsc[i].macsrc[5]&0xff);
+ if (rawFlag) {
+ signal = wsc[i].signal;
+ silence = wsc[i].silence;
+ quality = wsc[i].quality;
+ }
+ else {
+ signal = scale_value(wsc[i].signal, 63);
+ silence = scale_value(wsc[i].silence, 63);
+ quality = scale_value(wsc[i].quality, 15);
+ }
+ printf("\tsignal: %d, silence: %d, quality: %d, ",
+ signal,
+ silence,
+ quality);
+ printf("snr: %d\n", signal - silence);
+ }
+}
+
+#define raw_cache() dump_cache(1)
+#define scale_cache() dump_cache(0)
+
+int
+main(int argc, char *argv[])
+{
+ int sd;
+ struct ifreq ifr;
+ u_char psabuf[0x40];
+ int val, argind, i;
+ char *cp, *param, *value;
+ struct ether_addr *ea;
+ int work = 0;
+ int currnwid;
+
+ if ((argc < 2) || (argc % 2))
+ usage();
+
+ /* get a socket */
+ sd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sd < 0)
+ err(1,"socket");
+ strncpy(ifr.ifr_name, argv[1], sizeof(ifr.ifr_name));
+ ifr.ifr_addr.sa_family = AF_INET;
+
+ /* get the PSA */
+ ifr.ifr_data = (caddr_t)psabuf;
+ if (ioctl(sd, SIOCGWLPSA, (caddr_t)&ifr))
+ err(1,"get PSA");
+
+ /* get the current NWID */
+ if (ioctl(sd, SIOCGWLCNWID, (caddr_t)&ifr))
+ err(1,"get NWID");
+ currnwid = (int)ifr.ifr_data;
+
+ /* just dump and exit? */
+ if (argc == 2) {
+ print_psa(psabuf, currnwid);
+ exit(0);
+ }
+
+ /* loop reading arg pairs */
+ for (argind = 2; argind < argc; argind += 2) {
+
+ param = argv[argind];
+ value = argv[argind+1];
+
+ /* What to do? */
+
+ if (!strcasecmp(param,"currnwid")) { /* set current NWID */
+ val = strtol(value,&cp,0);
+ if ((val < 0) || (val > 0xffff) || (cp == value))
+ errx(1,"bad NWID '%s'",value);
+
+ ifr.ifr_data = (caddr_t)val;
+ if (ioctl(sd, SIOCSWLCNWID, (caddr_t)&ifr))
+ err(1,"set NWID (interface not up?)");
+ continue ;
+ }
+
+ if (!strcasecmp(param,"irq")) {
+ val = strtol(value,&cp,0);
+ val = irqvals[val];
+ if ((val == 0) || (cp == value))
+ errx(1,"bad IRQ '%s'",value);
+ psabuf[WLPSA_IRQNO] = (u_char)val;
+ work = 1;
+ continue;
+ }
+
+ if (!strcasecmp(param,"mac")) {
+ if ((ea = ether_aton(value)) == NULL)
+ errx(1,"bad ethernet address '%s'",value);
+ for (i = 0; i < 6; i++)
+ psabuf[WLPSA_LOCALMAC + i] = ea->octet[i];
+ work = 1;
+ continue;
+ }
+
+ if (!strcasecmp(param,"macsel")) {
+ if (!strcasecmp(value,"local")) {
+ psabuf[WLPSA_MACSEL] |= 0x1;
+ work = 1;
+ continue;
+ }
+ if (!strcasecmp(value,"universal")) {
+ psabuf[WLPSA_MACSEL] &= ~0x1;
+ work = 1;
+ continue;
+ }
+ errx(1,"bad macsel value '%s'",value);
+ }
+
+ if (!strcasecmp(param,"nwid")) {
+ val = strtol(value,&cp,0);
+ if ((val < 0) || (val > 0xffff) || (cp == value))
+ errx(1,"bad NWID '%s'",value);
+ psabuf[WLPSA_NWID] = (val >> 8) & 0xff;
+ psabuf[WLPSA_NWID+1] = val & 0xff;
+ work = 1;
+ continue;
+ }
+ if (!strcasecmp(param,"cache")) {
+
+ /* raw cache dump
+ */
+ if (!strcasecmp(value,"raw")) {
+ get_cache(sd, &ifr);
+ raw_cache();
+ continue;
+ }
+ /* scaled cache dump
+ */
+ else if (!strcasecmp(value,"scale")) {
+ get_cache(sd, &ifr);
+ scale_cache();
+ continue;
+ }
+ /* zero out cache
+ */
+ else if (!strcasecmp(value,"zero")) {
+ if (ioctl(sd, SIOCDWLCACHE, (caddr_t)&ifr))
+ err(1,"zero cache");
+ continue;
+ }
+ errx(1,"unknown value '%s'", value);
+ }
+ errx(1,"unknown parameter '%s'",param);
+ }
+ if (work) {
+ ifr.ifr_data = (caddr_t)psabuf;
+ if (ioctl(sd, SIOCSWLPSA, (caddr_t)&ifr))
+ err(1,"set PSA");
+ }
+ return(0);
+}
diff --git a/usr.sbin/wpa/Makefile b/usr.sbin/wpa/Makefile
new file mode 100644
index 0000000..ae07ec0
--- /dev/null
+++ b/usr.sbin/wpa/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+SUBDIR= wpa_supplicant wpa_cli wpa_passphrase
+SUBDIR+= hostapd hostapd_cli
+SUBDIR+= ndis_events
+SUBDIR_PARALLEL=
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/wpa/Makefile.crypto b/usr.sbin/wpa/Makefile.crypto
new file mode 100644
index 0000000..5c03f7d
--- /dev/null
+++ b/usr.sbin/wpa/Makefile.crypto
@@ -0,0 +1,134 @@
+# $FreeBSD$
+
+.if ${MK_OPENSSL} != "no" && !defined(RELEASE_CRUNCH)
+SRCS+= crypto_openssl.c random.c sha1-prf.c sha256-prf.c sha256-tlsprf.c
+LIBADD+= ssl crypto
+CFLAGS+= -DCONFIG_SHA256
+.else
+CFLAGS+=-DCONFIG_CRYPTO_INTERNAL
+SRCS+= crypto_internal.c random.c
+CONFIG_INTERNAL_AES=y
+CONFIG_INTERNAL_DES=y
+CONFIG_INTERNAL_MD4=y
+CONFIG_INTERNAL_MD5=y
+CONFIG_INTERNAL_RC4=y
+CONFIG_INTERNAL_SHA1=y
+NEED_SHA256=y
+CONFIG_INTERNAL_SHA256=y
+CONFIG_INTERNAL_TLS=y
+CONFIG_INTERNAL_DH5=y
+CONFIG_INTERNAL_DH=y
+NEED_AES_ENC=true
+NEED_AES_CBC=true
+.endif
+
+.if defined(TLS_FUNCS)
+NEED_TLS_PRF=y
+.if defined(CONFIG_INTERNAL_TLS)
+CFLAGS+=-DCONFIG_INTERNAL_LIBTOMMATH \
+ -DCONFIG_TLS_INTERNAL_CLIENT
+SRCS+= asn1.c \
+ bignum.c \
+ crypto_internal-cipher.c \
+ crypto_internal-modexp.c \
+ crypto_internal-rsa.c \
+ pkcs1.c \
+ pkcs5.c \
+ pkcs8.c \
+ rsa.c \
+ tls_internal.c \
+ tlsv1_common.c \
+ tlsv1_record.c \
+ tlsv1_cred.c \
+ tlsv1_client.c \
+ tlsv1_client_write.c \
+ tlsv1_client_read.c \
+ x509v3.c
+NEED_DES=y
+NEED_MD4=y
+NEED_RC4=y
+.else
+CFLAGS+=-DEAP_TLS_OPENSSL
+SRCS+= tls_openssl.c
+.endif
+.endif
+
+.if defined(CONFIG_INTERNAL_AES)
+SRCS+= aes-unwrap.c aes-wrap.c \
+ aes-internal.c \
+ aes-internal-dec.c \
+ aes-internal-enc.c
+.endif
+
+.if defined(NEED_AES_CBC)
+SRCS+= aes-cbc.c
+.endif
+
+.if defined(NEED_AES_EAX)
+SRCS+= aes-eax.c
+NEED_AES_CTR=y
+.endif
+
+.if defined(NEED_AES_CTR)
+SRCS+= aes-ctr.c
+.endif
+
+.if defined(NEED_AES_ENCBLOCK)
+SRCS+= aes-encblock.c
+.endif
+
+.if defined(NEED_AES_OMAC1)
+SRCS+= aes-omac1.c
+.endif
+
+.if defined(NEED_DES)
+.if defined(CONFIG_INTERNAL_DES)
+SRCS+= des-internal.c
+.endif
+.endif
+
+.if defined(NEED_MD4)
+.if defined(CONFIG_INTERNAL_MD4)
+SRCS+= md4-internal.c
+.endif
+.endif
+
+.if defined(CONFIG_INTERNAL_MD5)
+SRCS+= md5.c md5-internal.c
+.endif
+
+.if defined(NEED_FIPS186_2_PRF)
+.if defined(CONFIG_INTERNAL_SHA1)
+SRCS+= fips_prf_internal.c
+.else
+SRCS+= fips_prf_openssl.c
+.endif
+.endif
+
+.if defined(CONFIG_INTERNAL_RC4)
+SRCS+= rc4.c
+.endif
+
+.if defined(CONFIG_INTERNAL_SHA1)
+SRCS+= sha1-internal.c sha1-pbkdf2.c sha1.c sha1-prf.c
+.endif
+
+.if defined(NEED_SHA256)
+CFLAGS+=-DCONFIG_SHA256
+SRCS+= sha256.c
+.if defined(CONFIG_INTERNAL_SHA256)
+SRCS+= sha256-internal.c sha256-prf.c
+.endif
+.endif
+
+.if defined(NEED_TLS_PRF)
+SRCS+= sha1-tlsprf.c
+.endif
+
+.if defined(CONFIG_INTERNAL_DH5)
+SRCS+= dh_group5.c
+.endif
+
+.if defined(CONFIG_INTERNAL_DH)
+SRCS+= dh_groups.c
+.endif
diff --git a/usr.sbin/wpa/Makefile.inc b/usr.sbin/wpa/Makefile.inc
new file mode 100644
index 0000000..0b13b97
--- /dev/null
+++ b/usr.sbin/wpa/Makefile.inc
@@ -0,0 +1,38 @@
+# $FreeBSD$
+
+BINDIR?= /usr/sbin
+
+WPA_DISTDIR?= ${.CURDIR}/../../../contrib/wpa/
+WPA_SUPPLICANT_DISTDIR?=${WPA_DISTDIR}/wpa_supplicant
+HOSTAPD_DISTDIR?= ${WPA_DISTDIR}/hostapd
+
+.PATH.c:${.CURDIR}/.. \
+ ${WPA_DISTDIR}/src/ap \
+ ${WPA_DISTDIR}/src/common \
+ ${WPA_DISTDIR}/src/crypto \
+ ${WPA_DISTDIR}/src/eapol_auth \
+ ${WPA_DISTDIR}/src/eap_common \
+ ${WPA_DISTDIR}/src/eap_peer \
+ ${WPA_DISTDIR}/src/eap_server \
+ ${WPA_DISTDIR}/src/eapol_supp \
+ ${WPA_DISTDIR}/src/l2_packet \
+ ${WPA_DISTDIR}/src/radius \
+ ${WPA_DISTDIR}/src/rsn_supp \
+ ${WPA_DISTDIR}/src/tls \
+ ${WPA_DISTDIR}/src/utils \
+ ${WPA_DISTDIR}/src/wps
+
+CFLAGS+=-I${.CURDIR}
+CFLAGS+=-I${HOSTAPD_DISTDIR}
+CFLAGS+=-I${WPA_DISTDIR}/src
+CFLAGS+=-I${WPA_DISTDIR}/src/common
+CFLAGS+=-I${WPA_DISTDIR}/src/crypto
+CFLAGS+=-I${WPA_DISTDIR}/src/drivers
+CFLAGS+=-I${WPA_DISTDIR}/src/l2_packet
+CFLAGS+=-I${WPA_DISTDIR}/src/utils
+CFLAGS+=-I${WPA_DISTDIR}/src/wps
+
+CFLAGS+= -DCONFIG_CTRL_IFACE
+CFLAGS+= -DCONFIG_CTRL_IFACE_UNIX
+
+.include <bsd.own.mk>
diff --git a/usr.sbin/wpa/hostapd/Makefile b/usr.sbin/wpa/hostapd/Makefile
new file mode 100644
index 0000000..4437839
--- /dev/null
+++ b/usr.sbin/wpa/hostapd/Makefile
@@ -0,0 +1,122 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+.include "${.CURDIR}/../Makefile.inc"
+
+.PATH.c:${HOSTAPD_DISTDIR} \
+ ${WPA_DISTDIR}/src/drivers
+
+PROG= hostapd
+SRCS= accounting.c aes-omac1.c ap_config.c ap_drv_ops.c ap_mlme.c authsrv.c \
+ base64.c beacon.c bss_load.c chap.c common.c config_file.c \
+ ctrl_iface.c \
+ ctrl_iface_ap.c driver_common.c l2_packet_freebsd.c driver_bsd.c \
+ drivers.c drv_callbacks.c eap_common.c eap_peap_common.c \
+ eap_register.c eap_server.c eap_server_methods.c eap_user_db.c \
+ eapol_auth_dump.c eapol_auth_sm.c eloop.c gas.c gas_serv.c hostapd.c \
+ hs20.c http_client.c http_server.c httpread.c \
+ hw_features_common.c ieee802_11_auth.c \
+ ieee802_11_common.c ieee802_11_shared.c ieee802_1x.c ip_addr.c \
+ main.c ms_funcs.c os_unix.c peerkey_auth.c pmksa_cache_auth.c \
+ preauth_auth.c radius.c radius_client.c radius_das.c sta_info.c \
+ tkip_countermeasures.c upnp_xml.c utils.c uuid.c vlan_init.c \
+ wpa_auth.c wpa_auth_glue.c wpa_auth_ie.c wpa_common.c wpa_debug.c \
+ wpabuf.c wps.c wps_attr_build.c wps_attr_parse.c wps_attr_process.c \
+ wps_common.c wps_dev_attr.c wps_enrollee.c wps_hostapd.c \
+ wps_registrar.c wps_upnp.c wps_upnp_ap.c wps_upnp_event.c \
+ wps_upnp_ssdp.c wps_upnp_web.c
+
+MAN= hostapd.8 hostapd.conf.5
+
+.if ${MK_EXAMPLES} != "no"
+FILESDIR= ${SHAREDIR}/examples/hostapd
+.PATH: ${HOSTAPD_DISTDIR}
+FILES= hostapd.conf hostapd.eap_user hostapd.wpa_psk
+.endif
+
+CFLAGS+=-DCONFIG_DRIVER_BSD \
+ -DCONFIG_DRIVER_RADIUS_ACL \
+ -DCONFIG_HS20 \
+ -DCONFIG_INTERWORKING \
+ -DCONFIG_PEERKEY \
+ -DCONFIG_RSN_PREAUTH \
+ -DCONFIG_WPS \
+ -DCONFIG_WPS2 \
+ -DCONFIG_WPS_UPNP \
+ -DHOSTAPD
+.if ${MK_INET6} != "no"
+CFLAGS+= -DCONFIG_IPV6
+.endif
+#CFLAGS+= -g
+LIBADD+= pcap util
+
+# User customizations for wpa_supplicant/hostapd build environment
+CFLAGS+=${HOSTAPD_CFLAGS}
+#DPADD+=${HOSTAPD_DPADD}
+LDADD+=${HOSTAPD_LDADD}
+#LDFLAGS+=${HOSTAPD_LDFLAGS}
+
+CFLAGS+=-DDPKCS12_FUNCS \
+ -DEAP_SERVER \
+ -DEAP_SERVER_GTC \
+ -DEAP_SERVER_IDENTITY \
+ -DEAP_SERVER_MD5 \
+ -DEAP_SERVER_MSCHAPV2 \
+ -DEAP_SERVER_PEAP \
+ -DEAP_SERVER_TLS \
+ -DEAP_SERVER_TTLS \
+ -DEAP_SERVER_WSC \
+ -DEAP_TLS_FUNCS
+
+SRCS+= eap_server_gtc.c \
+ eap_server_identity.c \
+ eap_server_md5.c \
+ eap_server_mschapv2.c \
+ eap_server_peap.c \
+ eap_server_tls.c \
+ eap_server_tls_common.c \
+ eap_server_ttls.c \
+ eap_server_wsc.c \
+ eap_wsc_common.c
+TLS_FUNCS=y
+
+.if !empty(CFLAGS:M*-DCONFIG_WPS)
+NEED_SIM_COMMON=y
+.endif
+
+.if !empty(CFLAGS:M*-DEAP_SERVER_AKA)
+SRCS+= eap_server_aka.c
+NEED_SIM_COMMON=y
+.endif
+
+.if !empty(CFLAGS:M*-DEAP_SERVER_SIM)
+SRCS+= eap_server_sim.c
+NEED_SIM_COMMON=y
+.endif
+
+.if defined(NEED_SIM_COMMON)
+SRCS+= eap_sim_common.c \
+ eap_sim_db.c
+NEED_FIPS186_2_PRF=y
+.endif
+
+.if !empty(CFLAGS:M*-DEAP_SERVER_GPSK)
+CFLAGS+=-DEAP_GPSK_SHA256
+SRCS+= eap_server_gpsk.c \
+ eap_gpsk_common.c
+NEED_AES_OMAC1=y
+.endif
+
+.if !empty(CFLAGS:M*-DEAP_SERVER_PAX)
+SRCS+= eap_server_pax.c \
+ eap_pax_common.c
+.endif
+
+.if !empty(CFLAGS:M*-DEAP_SERVER_SAKE)
+SRCS+= eap_server_sake.c \
+ eap_sake_common.c
+.endif
+
+.include "${.CURDIR}/../Makefile.crypto"
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/wpa/hostapd/Makefile.depend b/usr.sbin/wpa/hostapd/Makefile.depend
new file mode 100644
index 0000000..8d486ac
--- /dev/null
+++ b/usr.sbin/wpa/hostapd/Makefile.depend
@@ -0,0 +1,23 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libpcap \
+ lib/libutil \
+ secure/lib/libcrypto \
+ secure/lib/libssl \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/wpa/hostapd/hostapd.8 b/usr.sbin/wpa/hostapd/hostapd.8
new file mode 100644
index 0000000..9edbd65
--- /dev/null
+++ b/usr.sbin/wpa/hostapd/hostapd.8
@@ -0,0 +1,137 @@
+.\" Copyright (c) 2005 Sam Leffler <sam@errno.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 July 18, 2012
+.Dt HOSTAPD 8
+.Os
+.Sh NAME
+.Nm hostapd
+.Nd "authenticator for IEEE 802.11 networks"
+.Sh SYNOPSIS
+.Nm
+.Op Fl BdhKtv
+.Op Fl P Ar pidfile
+.Ar config-file ...
+.Sh DESCRIPTION
+The
+.Nm
+utility
+is an authenticator for IEEE 802.11 networks.
+It provides full support for WPA/IEEE 802.11i and
+can also act as an IEEE 802.1X Authenticator with a suitable
+backend Authentication Server (typically
+.Tn FreeRADIUS ) .
+The
+.Nm
+utility
+implements the authentication protocols that piggyback on top
+of the normal IEEE 802.11 protocol mechanisms.
+To use
+.Nm
+as an authenticator, the underlying device must support some
+basic functionality such as the ability to set security information
+in the 802.11 management frames.
+Beware that not all devices have this support.
+.Pp
+The
+.Nm
+utility
+is designed to be a
+.Dq daemon
+program that runs in the
+background and acts as the backend component controlling
+the wireless connection.
+It supports separate frontend programs such as the
+text-based frontend,
+.Xr hostapd_cli 8 .
+.Pp
+The following arguments must be specified on the command line:
+.Bl -tag -width indent
+.It Ar config-file
+Use the settings in the specified configuration file; the name of
+the specified wireless interface is contained in this file.
+See
+.Xr hostapd.conf 5
+for a description of the configuration file syntax.
+.Pp
+Changes to the configuration file can be reloaded by sending a
+.Dv SIGHUP
+to the
+.Nm
+processor or with the
+.Xr hostapd_cli 8
+utility, using
+.Dq Li "hostapd_cli reconfigure" .
+.El
+.Sh OPTIONS
+The options are as follows:
+.Bl -tag -width indent
+.It Fl d
+Enable debugging messages.
+If this option is supplied twice, more verbose messages are displayed.
+.It Fl h
+Show help text.
+.It Fl t
+Include timestamps in debugging output.
+.It Fl v
+Display version information on the terminal and exit.
+.It Fl B
+Detach from the controlling terminal and run as a daemon process
+in the background.
+.It Fl K
+Include key information in debugging output.
+.It Fl P Ar pidfile
+Store PID in
+.Ar pidfile .
+.El
+.Sh SEE ALSO
+.Xr ath 4 ,
+.Xr ipw 4 ,
+.Xr iwi 4 ,
+.Xr mwl 4 ,
+.Xr ral 4 ,
+.Xr rum 4 ,
+.Xr run 4 ,
+.Xr ural 4 ,
+.Xr wi 4 ,
+.Xr hostapd.conf 5 ,
+.Xr hostapd_cli 8 ,
+.Xr ifconfig 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 6.0 .
+.Sh AUTHORS
+The
+.Nm
+utility was written by
+.An Jouni Malinen Aq Mt j@w1.fi .
+This manual page is derived from the
+.Pa README
+file included in the
+.Nm
+distribution.
diff --git a/usr.sbin/wpa/hostapd/hostapd.conf.5 b/usr.sbin/wpa/hostapd/hostapd.conf.5
new file mode 100644
index 0000000..e166f36
--- /dev/null
+++ b/usr.sbin/wpa/hostapd/hostapd.conf.5
@@ -0,0 +1,211 @@
+.\" Copyright (c) 2005 Sam Leffler <sam@errno.com>
+.\" Copyright (c) 2006 Rui Paulo
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 2, 2006
+.Dt HOSTAPD.CONF 5
+.Os
+.Sh NAME
+.Nm hostapd.conf
+.Nd configuration file for
+.Xr hostapd 8
+utility
+.Sh DESCRIPTION
+The
+.Xr hostapd 8
+utility
+is an authenticator for IEEE 802.11 networks.
+It provides full support for WPA/IEEE 802.11i and
+can also act as an IEEE 802.1X Authenticator with a suitable
+backend Authentication Server (typically
+.Tn FreeRADIUS ) .
+.Pp
+The configuration file consists of global parameters and domain
+specific configuration:
+.Bl -bullet -offset indent -compact
+.It
+IEEE 802.1X-2004
+.\" XXX not yet
+.\" .It
+.\" Integrated EAP server
+.\" .It
+.\" IEEE 802.11f - Inter-Access Point Protocol (IAPP)
+.It
+RADIUS client
+.It
+RADIUS authentication server
+.It
+WPA/IEEE 802.11i
+.El
+.Sh GLOBAL PARAMETERS
+The following parameters are recognized:
+.Bl -tag -width indent
+.It Va interface
+Interface name.
+Should be set in
+.Dq hostap
+mode. Make certain that there are no spaces after the interface name,
+or hostapd will complain that the interface does not exist.
+.It Va debug
+Debugging mode: 0 = no, 1 = minimal, 2 = verbose, 3 = msg dumps, 4 =
+excessive.
+.It Va dump_file
+Dump file for state information (on
+.Dv SIGUSR1 ) .
+.It Va ctrl_interface
+The pathname of the directory in which
+.Xr hostapd 8
+creates
+.Ux
+domain socket files for communication
+with frontend programs such as
+.Xr hostapd_cli 8 .
+.It Va ctrl_interface_group
+A group name or group ID to use in setting protection on the
+control interface file.
+This can be set to allow non-root users to access the
+control interface files.
+If no group is specified, the group ID of the control interface
+is not modified and will, typically, be the
+group ID of the directory in which the socket is created.
+.El
+.Sh IEEE 802.1X-2004 PARAMETERS
+The following parameters are recognized:
+.Bl -tag -width indent
+.It Va ieee8021x
+Require IEEE 802.1X authorization.
+.It Va eap_message
+Optional displayable message sent with EAP Request-Identity.
+.It Va wep_key_len_broadcast
+Key lengths for broadcast keys.
+.It Va wep_key_len_unicast
+Key lengths for unicast keys.
+.It Va wep_rekey_period
+Rekeying period in seconds.
+.It Va eapol_key_index_workaround
+EAPOL-Key index workaround (set bit7) for WinXP Supplicant.
+.It Va eap_reauth_period
+EAP reauthentication period in seconds.
+To disable reauthentication,
+use
+.Dq 0 .
+.\" XXX not yet
+.\" .It Va use_pae_group_addr
+.El
+.\" XXX not yet
+.\" .Sh IEEE 802.11f - IAPP PARAMETERS
+.\" The following parameters are recognized:
+.\" .Bl -tag -width indent
+.\" .It Va iapp_interface
+.\" Interface to be used for IAPP broadcast packets
+.\" .El
+.Sh RADIUS CLIENT PARAMETERS
+The following parameters are recognized:
+.Bl -tag -width indent
+.It Va own_ip_addr
+The own IP address of the access point (used as NAS-IP-Address).
+.It Va nas_identifier
+Optional NAS-Identifier string for RADIUS messages.
+.It Va auth_server_addr , auth_server_port , auth_server_shared_secret
+RADIUS authentication server parameters.
+Can be defined twice for secondary servers to be used if primary one
+does not reply to RADIUS packets.
+.It Va acct_server_addr , acct_server_port , acct_server_shared_secret
+RADIUS accounting server parameters.
+Can be defined twice for secondary servers to be used if primary one
+does not reply to RADIUS packets.
+.It Va radius_retry_primary_interval
+Retry interval for trying to return to the primary RADIUS server (in
+seconds).
+.It Va radius_acct_interim_interval
+Interim accounting update interval.
+If this is set (larger than 0) and acct_server is configured,
+.Xr hostapd 8
+will send interim accounting updates every N seconds.
+.El
+.Sh RADIUS AUTHENTICATION SERVER PARAMETERS
+The following parameters are recognized:
+.Bl -tag -width indent
+.It Va radius_server_clients
+File name of the RADIUS clients configuration for the RADIUS server.
+If this is commented out, RADIUS server is disabled.
+.It Va radius_server_auth_port
+The UDP port number for the RADIUS authentication server.
+.It Va radius_server_ipv6
+Use IPv6 with RADIUS server.
+.El
+.Sh WPA/IEEE 802.11i PARAMETERS
+The following parameters are recognized:
+.Bl -tag -width indent
+.It Va wpa
+Enable WPA.
+Setting this variable configures the AP to require WPA (either
+WPA-PSK or WPA-RADIUS/EAP based on other configuration).
+.It Va wpa_psk , wpa_passphrase
+WPA pre-shared keys for WPA-PSK.
+This can be either entered as a 256-bit secret in hex format (64 hex
+digits), wpa_psk, or as an ASCII passphrase (8..63 characters) that
+will be converted to PSK.
+This conversion uses SSID so the PSK changes when ASCII passphrase is
+used and the SSID is changed.
+.It Va wpa_psk_file
+Optionally, WPA PSKs can be read from a separate text file containing a
+list of PSK and MAC address pairs.
+.It Va wpa_key_mgmt
+Set of accepted key management algorithms (WPA-PSK, WPA-EAP, or both).
+.It Va wpa_pairwise
+Set of accepted cipher suites (encryption algorithms) for pairwise keys
+(unicast packets).
+See the example file for more information.
+.It Va wpa_group_rekey
+Time interval for rekeying GTK (broadcast/multicast encryption keys) in
+seconds.
+.It Va wpa_strict_rekey
+Rekey GTK when any STA that possesses the current GTK is leaving the
+BSS.
+.It Va wpa_gmk_rekey
+Time interval for rekeying GMK (master key used internally to generate GTKs),
+in seconds.
+.El
+.Sh SEE ALSO
+.Xr hostapd 8 ,
+.Xr hostapd_cli 8
+.Sh HISTORY
+The
+.Nm
+manual page and
+.Xr hostapd 8
+functionality first appeared in
+.Fx 6.0 .
+.Sh AUTHORS
+This manual page is derived from the
+.Pa README
+and
+.Pa hostapd.conf
+files in the
+.Nm hostapd
+distribution provided by
+.An Jouni Malinen Aq Mt j@w1.fi .
diff --git a/usr.sbin/wpa/hostapd_cli/Makefile b/usr.sbin/wpa/hostapd_cli/Makefile
new file mode 100644
index 0000000..48af140
--- /dev/null
+++ b/usr.sbin/wpa/hostapd_cli/Makefile
@@ -0,0 +1,17 @@
+# $FreeBSD$
+
+.include "${.CURDIR}/../Makefile.inc"
+
+.PATH.c:${HOSTAPD_DISTDIR}
+
+PROG= hostapd_cli
+SRCS= common.c edit.c eloop.c hostapd_cli.c os_unix.c wpa_ctrl.c wpa_debug.c
+
+CFLAGS+= -DCONFIG_CTRL_IFACE
+CFLAGS+= -DCONFIG_CTRL_IFACE_UNIX
+
+LIBADD+= util
+
+MAN= hostapd_cli.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/wpa/hostapd_cli/Makefile.depend b/usr.sbin/wpa/hostapd_cli/Makefile.depend
new file mode 100644
index 0000000..7de116d
--- /dev/null
+++ b/usr.sbin/wpa/hostapd_cli/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/wpa/hostapd_cli/hostapd_cli.8 b/usr.sbin/wpa/hostapd_cli/hostapd_cli.8
new file mode 100644
index 0000000..605a7f5
--- /dev/null
+++ b/usr.sbin/wpa/hostapd_cli/hostapd_cli.8
@@ -0,0 +1,112 @@
+.\" Copyright (c) 2005 Sam Leffler <sam@errno.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 June 16, 2005
+.Dt HOSTAPD_CLI 8
+.Os
+.Sh NAME
+.Nm hostapd_cli
+.Nd text-based frontend program for interacting with
+.Xr hostapd 8
+.Sh SYNOPSIS
+.Nm
+.Op Ar commands
+.Sh DESCRIPTION
+The
+.Nm
+utility
+is a text-based frontend program for interacting with
+.Xr hostapd 8 .
+It is used to query the current status.
+.Pp
+The
+.Nm
+utility
+can show the
+current authentication status,
+dot11 and dot1x MIBs, etc.
+.Pp
+The
+.Nm
+utility
+supports two modes: interactive and command line.
+Both modes share the same command set.
+.Pp
+Interactive mode is started when
+.Nm
+is executed without any parameters on the command line.
+Commands are then entered from the controlling terminal in
+response to the
+.Nm
+prompt.
+In command line mode, the same commands are
+entered as command line arguments.
+.Sh COMMANDS
+The following commands may be supplied on the command line
+or at a prompt when operating interactively.
+.Bl -tag -width indent
+.It Ic mib
+Report MIB variables (dot1x, dot11) for the current interface.
+.It Ic sta Ar addr
+Report the MIB variables for the associated station with MAC address
+.Ar addr .
+.It Ic all_sta
+Report the MIB variables for all associated stations.
+.It Ic help
+Show usage help.
+.It Ic interface Op Ar ifname
+Show available interfaces and/or set the current interface
+when multiple are available.
+.It Ic level Ar debug_level
+Change the debugging level in
+.Xr hostapd 8 .
+Larger numbers generate more messages.
+.It Ic license
+Display the full
+license for
+.Nm .
+.It Ic quit
+Exit
+.Nm .
+.El
+.Sh SEE ALSO
+.Xr hostapd.conf 5 ,
+.Xr hostapd 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 6.0 .
+.Sh AUTHORS
+The
+.Nm
+utility was written by
+.An Jouni Malinen Aq Mt j@w1.fi .
+This manual page is derived from the
+.Pa README
+file included in the
+.Nm hostapd
+distribution.
diff --git a/usr.sbin/wpa/ndis_events/Makefile b/usr.sbin/wpa/ndis_events/Makefile
new file mode 100644
index 0000000..07caf5a
--- /dev/null
+++ b/usr.sbin/wpa/ndis_events/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+PROG= ndis_events
+SRCS+= ndis_events.c
+
+MAN= ndis_events.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/wpa/ndis_events/Makefile.depend b/usr.sbin/wpa/ndis_events/Makefile.depend
new file mode 100644
index 0000000..54c1f6f
--- /dev/null
+++ b/usr.sbin/wpa/ndis_events/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/wpa/ndis_events/ndis_events.8 b/usr.sbin/wpa/ndis_events/ndis_events.8
new file mode 100644
index 0000000..bf11c19
--- /dev/null
+++ b/usr.sbin/wpa/ndis_events/ndis_events.8
@@ -0,0 +1,135 @@
+.\" Copyright (c) 2005
+.\" Bill Paul <wpaul@windriver.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 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd August 30, 2007
+.Dt NDIS_EVENTS 8
+.Os
+.Sh NAME
+.Nm ndis_events
+.Nd relay events from
+.Xr ndis 4
+drivers to
+.Xr wpa_supplicant 8
+.Sh SYNOPSIS
+.Nm
+.Op Fl a
+.Op Fl d
+.Op Fl v
+.Sh DESCRIPTION
+The
+.Nm
+utility listens for events generated by an
+.Xr ndis 4
+wireless network driver and relays them to
+.Xr wpa_supplicant 8
+for possible processing.
+The three event types that can occur
+are media connect and disconnect events, such as when a wireless
+interface joins or leaves a network, and media-specific events.
+In particular,
+.Xr ndis 4
+drivers that support WPA2 will generate media-specific events
+containing PMKID candidate information which
+.Xr wpa_supplicant 8
+needs in order to properly associate with WPA2-capable access points.
+.Pp
+The
+.Nm
+daemon works by listening for interface information events via
+a routing socket.
+When it detects an event that was generated by an
+.Xr ndis 4
+interface, it transmits it via UDP packet on the loopback interface,
+where
+.Xr wpa_supplicant 8
+is presumably listening.
+The standard
+.Xr wpa_supplicant 8
+distribution includes its own version of this utility for use with
+.Tn Windows\[rg] .
+The
+.Fx
+version performs the same functions as the
+.Tn Windows\[rg]
+one, except that it uses an
+.Xr ioctl 2
+and routing socket interface instead of WMI.
+.Pp
+Note that a single instance of
+.Nm
+is sufficient to scan for events for any number of
+.Xr ndis 4
+interfaces in a system.
+.Sh OPTIONS
+The
+.Nm
+daemon supports the following options:
+.Bl -tag -width indent
+.It Fl a
+Process all events.
+By default,
+.Nm
+will only process and forward media-specific events, which contain
+PMKID candidate information, and not bother forwarding connect and
+disconnect events, since
+.Xr wpa_supplicant 8
+normally can determine the current link state on its own.
+In some
+cases, the additional connect and disconnect events only confuse it
+and make the association and authentication process take longer.
+.It Fl d
+Run in debug mode.
+This causes
+.Nm
+to run in the foreground and generate any output to the standard
+error instead of using the
+.Xr syslog 3
+facility.
+.It Fl v
+Run in verbose mode.
+This causes
+.Nm
+to emit notifications when it receives events.
+.El
+.Sh SEE ALSO
+.Xr ndis 4 ,
+.Xr wpa_supplicant 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 6.0 .
+.Sh AUTHORS
+The
+.Nm
+utility was written by
+.An Bill Paul Aq Mt wpaul@windriver.com .
diff --git a/usr.sbin/wpa/ndis_events/ndis_events.c b/usr.sbin/wpa/ndis_events/ndis_events.c
new file mode 100644
index 0000000..b61fd0a
--- /dev/null
+++ b/usr.sbin/wpa/ndis_events/ndis_events.c
@@ -0,0 +1,351 @@
+/*-
+ * Copyright (c) 2005
+ * Bill Paul <wpaul@windriver.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 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/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * This program simulates the behavior of the ndis_events utility
+ * supplied with wpa_supplicant for Windows. The original utility
+ * is designed to translate Windows WMI events. We don't have WMI,
+ * but we need to supply certain event info to wpa_supplicant in
+ * order to make WPA2 work correctly, so we fake up the interface.
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/errno.h>
+#include <sys/sysctl.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <net/route.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <err.h>
+#include <syslog.h>
+#include <stdarg.h>
+
+static int verbose = 0;
+static int debug = 0;
+static int all_events = 0;
+
+#define PROGNAME "ndis_events"
+
+#define WPA_SUPPLICANT_PORT 9876
+#define NDIS_INDICATION_LEN 2048
+
+#define EVENT_CONNECT 0
+#define EVENT_DISCONNECT 1
+#define EVENT_MEDIA_SPECIFIC 2
+
+#define NDIS_STATUS_MEDIA_CONNECT 0x4001000B
+#define NDIS_STATUS_MEDIA_DISCONNECT 0x4001000C
+#define NDIS_STATUS_MEDIA_SPECIFIC_INDICATION 0x40010012
+
+struct ndis_evt {
+ uint32_t ne_sts;
+ uint32_t ne_len;
+#ifdef notdef
+ char ne_buf[1];
+#endif
+};
+
+static int find_ifname(int, char *);
+static int announce_event(char *, int, struct sockaddr_in *);
+static void usage(void);
+
+static void
+dbgmsg(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (debug)
+ vwarnx(fmt, ap);
+ else
+ vsyslog(LOG_ERR, fmt, ap);
+ va_end(ap);
+
+ return;
+}
+
+static int
+find_ifname(idx, name)
+ int idx;
+ char *name;
+{
+ int mib[6];
+ size_t needed;
+ struct if_msghdr *ifm;
+ struct sockaddr_dl *sdl;
+ char *buf, *lim, *next;
+
+ needed = 0;
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0; /* protocol */
+ mib[3] = 0; /* wildcard address family */
+ mib[4] = NET_RT_IFLIST;
+ mib[5] = 0; /* no flags */
+
+ if (sysctl (mib, 6, NULL, &needed, NULL, 0) < 0)
+ return(EIO);
+
+ buf = malloc (needed);
+ if (buf == NULL)
+ return(ENOMEM);
+
+ if (sysctl (mib, 6, buf, &needed, NULL, 0) < 0) {
+ free(buf);
+ return(EIO);
+ }
+
+ lim = buf + needed;
+
+ next = buf;
+ while (next < lim) {
+ ifm = (struct if_msghdr *)next;
+ if (ifm->ifm_type == RTM_IFINFO) {
+ sdl = (struct sockaddr_dl *)(ifm + 1);
+ if (ifm->ifm_index == idx) {
+ strncpy(name, sdl->sdl_data, sdl->sdl_nlen);
+ name[sdl->sdl_nlen] = '\0';
+ free (buf);
+ return (0);
+ }
+ }
+ next += ifm->ifm_msglen;
+ }
+
+ free (buf);
+
+ return(ENOENT);
+}
+
+static int
+announce_event(ifname, sock, dst)
+ char *ifname;
+ int sock;
+ struct sockaddr_in *dst;
+{
+ int s;
+ char indication[NDIS_INDICATION_LEN];
+ struct ifreq ifr;
+ struct ndis_evt *e;
+ char buf[512], *pos, *end;
+ int len, type, _type;
+
+ s = socket(PF_INET, SOCK_DGRAM, 0);
+
+ if (s < 0) {
+ dbgmsg("socket creation failed");
+ return(EINVAL);
+ }
+
+ bzero((char *)&ifr, sizeof(ifr));
+ e = (struct ndis_evt *)indication;
+ e->ne_len = NDIS_INDICATION_LEN - sizeof(struct ndis_evt);
+
+ strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ ifr.ifr_data = indication;
+
+ if (ioctl(s, SIOCGPRIVATE_0, &ifr) < 0) {
+ close(s);
+ if (verbose) {
+ if (errno == ENOENT)
+ dbgmsg("drained all events from %s",
+ ifname, errno);
+ else
+ dbgmsg("failed to read event info from %s: %d",
+ ifname, errno);
+ }
+ return(ENOENT);
+ }
+
+ if (e->ne_sts == NDIS_STATUS_MEDIA_CONNECT) {
+ type = EVENT_CONNECT;
+ if (verbose)
+ dbgmsg("Received a connect event for %s", ifname);
+ if (!all_events) {
+ close(s);
+ return(0);
+ }
+ }
+ if (e->ne_sts == NDIS_STATUS_MEDIA_DISCONNECT) {
+ type = EVENT_DISCONNECT;
+ if (verbose)
+ dbgmsg("Received a disconnect event for %s", ifname);
+ if (!all_events) {
+ close(s);
+ return(0);
+ }
+ }
+ if (e->ne_sts == NDIS_STATUS_MEDIA_SPECIFIC_INDICATION) {
+ type = EVENT_MEDIA_SPECIFIC;
+ if (verbose)
+ dbgmsg("Received a media-specific event for %s",
+ ifname);
+ }
+
+ end = buf + sizeof(buf);
+ _type = (int) type;
+ memcpy(buf, &_type, sizeof(_type));
+ pos = buf + sizeof(_type);
+
+ len = snprintf(pos + 1, end - pos - 1, "%s", ifname);
+ if (len < 0) {
+ close(s);
+ return(ENOSPC);
+ }
+ if (len > 255)
+ len = 255;
+ *pos = (unsigned char) len;
+ pos += 1 + len;
+ if (e->ne_len) {
+ if (e->ne_len > 255 || 1 + e->ne_len > end - pos) {
+ dbgmsg("Not enough room for send_event data (%d)\n",
+ e->ne_len);
+ close(s);
+ return(ENOSPC);
+ }
+ *pos++ = (unsigned char) e->ne_len;
+ memcpy(pos, (indication) + sizeof(struct ndis_evt), e->ne_len);
+ pos += e->ne_len;
+ }
+
+ len = sendto(sock, buf, pos - buf, 0, (struct sockaddr *) dst,
+ sizeof(struct sockaddr_in));
+
+ close(s);
+ return(0);
+}
+
+static void
+usage()
+{
+ fprintf(stderr, "Usage: ndis_events [-a] [-d] [-v]\n");
+ exit(1);
+}
+
+int
+main(argc, argv)
+ int argc;
+ char *argv[];
+{
+ int s, r, n;
+ struct sockaddr_in sin;
+ char msg[NDIS_INDICATION_LEN];
+ struct rt_msghdr *rtm;
+ struct if_msghdr *ifm;
+ char ifname[IFNAMSIZ];
+ int ch;
+
+ while ((ch = getopt(argc, argv, "dva")) != -1) {
+ switch(ch) {
+ case 'd':
+ debug++;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'a':
+ all_events++;
+ break;
+ default:
+ usage();
+ break;
+ }
+ }
+
+ if (!debug && daemon(0, 0))
+ err(1, "failed to daemonize ourselves");
+
+ if (!debug)
+ openlog(PROGNAME, LOG_PID | LOG_CONS, LOG_DAEMON);
+
+ bzero((char *)&sin, sizeof(sin));
+
+ /* Create a datagram socket. */
+
+ s = socket(PF_INET, SOCK_DGRAM, 0);
+ if (s < 0) {
+ dbgmsg("socket creation failed");
+ exit(1);
+ }
+
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = inet_addr("127.0.0.1");
+ sin.sin_port = htons(WPA_SUPPLICANT_PORT);
+
+ /* Create a routing socket. */
+
+ r = socket (PF_ROUTE, SOCK_RAW, 0);
+ if (r < 0) {
+ dbgmsg("routing socket creation failed");
+ exit(1);
+ }
+
+ /* Now sit and spin, waiting for events. */
+
+ if (verbose)
+ dbgmsg("Listening for events");
+
+ while (1) {
+ n = read(r, msg, NDIS_INDICATION_LEN);
+ rtm = (struct rt_msghdr *)msg;
+ if (rtm->rtm_type != RTM_IFINFO)
+ continue;
+ ifm = (struct if_msghdr *)msg;
+ if (find_ifname(ifm->ifm_index, ifname))
+ continue;
+ if (strstr(ifname, "ndis")) {
+ while(announce_event(ifname, s, &sin) == 0)
+ ;
+ } else {
+ if (verbose)
+ dbgmsg("Skipping ifinfo message from %s",
+ ifname);
+ }
+ }
+
+ /* NOTREACHED */
+ exit(0);
+}
diff --git a/usr.sbin/wpa/wpa_cli/Makefile b/usr.sbin/wpa/wpa_cli/Makefile
new file mode 100644
index 0000000..3203829
--- /dev/null
+++ b/usr.sbin/wpa/wpa_cli/Makefile
@@ -0,0 +1,20 @@
+# $FreeBSD$
+
+.include "${.CURDIR}/../Makefile.inc"
+
+.PATH.c:${WPA_SUPPLICANT_DISTDIR}
+
+PROG= wpa_cli
+SRCS= common.c edit.c eloop.c os_unix.c wpa_cli.c wpa_ctrl.c wpa_debug.c
+
+MAN= wpa_cli.8
+
+CFLAGS+= -DCONFIG_CTRL_IFACE
+CFLAGS+= -DCONFIG_CTRL_IFACE_UNIX
+# enable use of d_type to identify unix domain sockets
+CFLAGS+= -D_DIRENT_HAVE_D_TYPE
+
+CFLAGS+= -DCONFIG_WPA_CLI_EDIT=y
+LIBADD+= util
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/wpa/wpa_cli/Makefile.depend b/usr.sbin/wpa/wpa_cli/Makefile.depend
new file mode 100644
index 0000000..7de116d
--- /dev/null
+++ b/usr.sbin/wpa/wpa_cli/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/wpa/wpa_cli/wpa_cli.8 b/usr.sbin/wpa/wpa_cli/wpa_cli.8
new file mode 100644
index 0000000..a2bf189
--- /dev/null
+++ b/usr.sbin/wpa/wpa_cli/wpa_cli.8
@@ -0,0 +1,222 @@
+.\" Copyright (c) 2005 Sam Leffler <sam@errno.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 June 16, 2005
+.Dt WPA_CLI 8
+.Os
+.Sh NAME
+.Nm wpa_cli
+.Nd "text-based frontend program for interacting with wpa_supplicant"
+.Sh SYNOPSIS
+.Nm
+.Op Ar commands
+.Sh DESCRIPTION
+The
+.Nm
+utility
+is a text-based frontend program for interacting with
+.Xr wpa_supplicant 8 .
+It is used to query current status,
+change configuration,
+trigger events,
+and
+request interactive user input.
+.Pp
+The
+.Nm
+utility
+can show the
+current authentication status,
+selected security
+mode, dot11 and dot1x MIBs, etc.
+In addition,
+.Nm
+can configure EAPOL state machine
+parameters and trigger events such as reassociation
+and IEEE 802.1X logoff/logon.
+.Pp
+The
+.Nm
+utility
+provides an interface to supply authentication information
+such as username and password when it is not provided in the
+.Xr wpa_supplicant.conf 5
+configuration file.
+This can be used, for example, to implement
+one-time passwords or generic token card
+authentication where the authentication is based on a
+challenge-response that uses an external device for generating the
+response.
+.Pp
+The
+.Nm
+utility
+supports two modes: interactive and command line.
+Both modes share the same command set and the main difference
+is in interactive mode providing access to unsolicited messages
+(event messages, username/password requests).
+.Pp
+Interactive mode is started when
+.Nm
+is executed without any parameters on the command line.
+Commands are then entered from the controlling terminal in
+response to the
+.Nm
+prompt.
+In command line mode, the same commands are
+entered as command line arguments.
+.Pp
+The control interface of
+.Xr wpa_supplicant 8
+can be configured to allow
+non-root user access by using the
+.Va ctrl_interface_group
+parameter
+in the
+.Xr wpa_supplicant.conf 5
+configuration file.
+This makes it possible to run
+.Nm
+with a normal user account.
+.Sh AUTHENTICATION PARAMETERS
+When
+.Xr wpa_supplicant 8
+needs authentication parameters, such as username and password,
+that are not present in the configuration file, it sends a
+request message to all attached frontend programs, e.g.,
+.Nm
+in interactive mode.
+The
+.Nm
+utility
+shows these requests with a
+.Dq Li CTRL-REQ- Ns Ao Ar type Ac Ns Li - Ns Ao Ar id Ac Ns : Ns Aq Ar text
+prefix, where
+.Aq Ar type
+is
+.Li IDENTITY , PASSWORD ,
+or
+.Li OTP
+(One-Time Password),
+.Aq Ar id
+is a unique identifier for the current network,
+.Aq Ar text
+is a description of the request.
+In the case of an
+.Li OTP
+(One-Time Password) request,
+it includes the challenge from the authentication server.
+.Pp
+A user must supply
+.Xr wpa_supplicant 8
+the needed parameters in response to these requests.
+.Pp
+For example,
+.Bd -literal -offset indent
+CTRL-REQ-PASSWORD-1:Password needed for SSID foobar
+> password 1 mysecretpassword
+
+Example request for generic token card challenge-response:
+
+CTRL-REQ-OTP-2:Challenge 1235663 needed for SSID foobar
+> otp 2 9876
+.Ed
+.Sh COMMANDS
+The following commands may be supplied on the command line
+or at a prompt when operating interactively.
+.Bl -tag -width indent
+.It Ic status
+Report the current WPA/EAPOL/EAP status for the current interface.
+.It Ic mib
+Report MIB variables (dot1x, dot11) for the current interface.
+.It Ic help
+Show usage help.
+.It Ic interface Op Ar ifname
+Show available interfaces and/or set the current interface
+when multiple are available.
+.It Ic level Ar debug_level
+Change the debugging level in
+.Xr wpa_supplicant 8 .
+Larger numbers generate more messages.
+.It Ic license
+Display the full
+license for
+.Nm .
+.It Ic logoff
+Send the IEEE 802.1X EAPOL state machine into the
+.Dq logoff
+state.
+.It Ic logon
+Send the IEEE 802.1X EAPOL state machine into the
+.Dq logon
+state.
+.It Ic set Op Ar settings
+Set variables.
+When no arguments are supplied, the known variables and their settings
+are displayed.
+.It Ic pmksa
+Show the contents of the PMKSA cache.
+.It Ic reassociate
+Force a reassociation to the current access point.
+.It Ic reconfigure
+Force
+.Xr wpa_supplicant 8
+to re-read its configuration file.
+.It Ic preauthenticate Ar BSSID
+Force preauthentication of the specified
+.Ar BSSID .
+.It Ic identity Ar network_id identity
+Configure an identity for an SSID.
+.It Ic password Ar network_id password
+Configure a password for an SSID.
+.It Ic otp Ar network_id password
+Configure a one-time password for an SSID.
+.It Ic terminate
+Force
+.Xr wpa_supplicant 8
+to terminate.
+.It Ic quit
+Exit
+.Nm .
+.El
+.Sh SEE ALSO
+.Xr wpa_supplicant.conf 5 ,
+.Xr wpa_supplicant 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 6.0 .
+.Sh AUTHORS
+The
+.Nm
+utility was written by
+.An Jouni Malinen Aq Mt j@w1.fi .
+This manual page is derived from the
+.Pa README
+file included in the
+.Nm wpa_supplicant
+distribution.
diff --git a/usr.sbin/wpa/wpa_passphrase/Makefile b/usr.sbin/wpa/wpa_passphrase/Makefile
new file mode 100644
index 0000000..aaf5c19
--- /dev/null
+++ b/usr.sbin/wpa/wpa_passphrase/Makefile
@@ -0,0 +1,17 @@
+# $FreeBSD$
+
+.include "${.CURDIR}/../Makefile.inc"
+
+.PATH.c:${WPA_SUPPLICANT_DISTDIR}
+
+PROG= wpa_passphrase
+SRCS= common.c md5-internal.c md5.c os_unix.c sha1-internal.c sha1-pbkdf2.c \
+ sha1.c wpa_passphrase.c
+
+CFLAGS+= -DCONFIG_CRYPTO_INTERNAL -DINTERNAL_SHA1 -DINTERNAL_MD5
+
+LIBADD+= util
+
+MAN= wpa_passphrase.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/wpa/wpa_passphrase/Makefile.depend b/usr.sbin/wpa/wpa_passphrase/Makefile.depend
new file mode 100644
index 0000000..7de116d
--- /dev/null
+++ b/usr.sbin/wpa/wpa_passphrase/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/wpa/wpa_passphrase/wpa_passphrase.8 b/usr.sbin/wpa/wpa_passphrase/wpa_passphrase.8
new file mode 100644
index 0000000..b661a79
--- /dev/null
+++ b/usr.sbin/wpa/wpa_passphrase/wpa_passphrase.8
@@ -0,0 +1,65 @@
+.\" Copyright (c) 2006 Henrik Brix Andersen <henrik@brixandersen.dk>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 17, 2007
+.Dt WPA_PASSPHRASE 8
+.Os
+.Sh NAME
+.Nm wpa_passphrase
+.Nd "utility for generating a 256-bit pre-shared WPA key from an ASCII passphrase"
+.Sh SYNOPSIS
+.Nm
+.Aq Ar ssid
+.Op Ar passphrase
+.Sh DESCRIPTION
+The
+.Nm
+utility is a small program for generating a 256-bit pre-shared WPA key
+from an ASCII passphrase and a given SSID. The output is formatted for
+inclusion in
+.Xr wpa_supplicant.conf 5 .
+.Pp
+If
+.Nm
+is called with only an SSID as argument it will prompt for a
+passphrase on standard input.
+.Sh SEE ALSO
+.Xr wpa_supplicant.conf 5 ,
+.Xr wpa_supplicant 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 6.3 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility was written by
+.An Jouni Malinen Aq Mt j@w1.fi .
+.Pp
+This manual page was written by
+.An Henrik Brix Andersen Aq Mt henrik@brixandersen.dk .
diff --git a/usr.sbin/wpa/wpa_priv/Makefile b/usr.sbin/wpa/wpa_priv/Makefile
new file mode 100644
index 0000000..cf77678
--- /dev/null
+++ b/usr.sbin/wpa/wpa_priv/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+.include "${.CURDIR}/../Makefile.inc"
+
+.PATH.c:${WPA_SUPPLICANT_DISTDIR} \
+ ${WPA_DISTDIR}/src/drivers
+
+PROG= wpa_priv
+SRCS= drivers.c os_unix.c eloop.c common.c wpa_debug.c wpabuf.c wpa_priv.c \
+ driver_common.c l2_packet_freebsd.c
+
+LIBADD= pcap
+
+.include "${.CURDIR}/../Makefile.crypto"
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/wpa/wpa_supplicant/Makefile b/usr.sbin/wpa/wpa_supplicant/Makefile
new file mode 100644
index 0000000..65b0c77
--- /dev/null
+++ b/usr.sbin/wpa/wpa_supplicant/Makefile
@@ -0,0 +1,147 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+.include "${.CURDIR}/../Makefile.inc"
+
+.PATH.c:${WPA_SUPPLICANT_DISTDIR} \
+ ${WPA_DISTDIR}/src/drivers
+
+PROG= wpa_supplicant
+SRCS= ap_drv_ops.c base64.c blacklist.c bss.c common.c config.c \
+ config_file.c ctrl_iface.c ctrl_iface_unix.c driver_bsd.c \
+ driver_common.c driver_ndis.c driver_wired.c drivers.c \
+ eap_register.c eloop.c events.c gas.c gas_query.c hs20.c \
+ hs20_supplicant.c http_client.c http_server.c httpread.c \
+ hw_features_common.c \
+ ieee802_11_common.c ieee802_11_shared.c \
+ interworking.c l2_packet_freebsd.c main.c \
+ notify.c offchannel.c os_unix.c peerkey.c pmksa_cache.c \
+ preauth.c scan.c upnp_xml.c uuid.c wmm_ac.c \
+ wpa.c wpa_common.c wpa_debug.c \
+ wpa_ft.c wpa_ie.c wpa_supplicant.c wpabuf.c wpas_glue.c wps.c \
+ wps_attr_build.c wps_attr_parse.c wps_attr_process.c \
+ wps_common.c wps_dev_attr.c wps_enrollee.c wps_registrar.c \
+ wps_supplicant.c wps_upnp.c wps_upnp_ap.c wps_upnp_event.c \
+ wps_upnp_ssdp.c wps_upnp_web.c Packet32.c
+
+MAN= wpa_supplicant.8 wpa_supplicant.conf.5
+
+.if ${MK_EXAMPLES} != "no"
+FILESDIR= ${SHAREDIR}/examples/etc
+.PATH: ${WPA_SUPPLICANT_DISTDIR}
+FILES= wpa_supplicant.conf
+.endif
+
+CFLAGS+=-DCONFIG_BACKEND_FILE \
+ -DCONFIG_DEBUG_SYSLOG \
+ -DCONFIG_DRIVER_BSD \
+ -DCONFIG_DRIVER_NDIS \
+ -DCONFIG_DRIVER_WIRED \
+ -DCONFIG_GAS \
+ -DCONFIG_HS20 \
+ -DCONFIG_IEEE80211R \
+ -DCONFIG_INTERWORKING \
+ -DCONFIG_PEERKEY \
+ -DCONFIG_PRIVSEP \
+ -DCONFIG_SMARTCARD \
+ -DCONFIG_TERMINATE_ONLASTIF \
+ -DCONFIG_TLS=openssl \
+ -DCONFIG_WPS \
+ -DCONFIG_WPS2 \
+ -DCONFIG_WPS_UPNP \
+ -DPKCS12_FUNCS
+#CFLAGS+= -g
+LIBADD= pcap util
+
+# User customizations to the wpa_supplicant build environment
+CFLAGS+=${WPA_SUPPLICANT_CFLAGS}
+#DPADD+=${WPA_SUPPLICANT_DPADD}
+LDADD+=${WPA_SUPPLICANT_LDADD}
+#LDFLAGS+=${WPA_SUPPLICANT_LDFLAGS}
+
+.if ${MK_WPA_SUPPLICANT_EAPOL} != "no"
+CFLAGS+=-DEAP_GTC \
+ -DEAP_LEAP \
+ -DEAP_MD5 \
+ -DEAP_MSCHAPv2 \
+ -DEAP_OTP \
+ -DEAP_PEAP \
+ -DEAP_PSK \
+ -DEAP_TLS \
+ -DEAP_TTLS \
+ -DIEEE8021X_EAPOL
+SRCS+= chap.c \
+ eap.c \
+ eap_common.c \
+ eap_gtc.c \
+ eap_leap.c \
+ eap_md5.c \
+ eap_methods.c \
+ eap_mschapv2.c \
+ eap_otp.c \
+ eap_peap.c \
+ eap_peap_common.c \
+ eap_psk.c \
+ eap_psk_common.c \
+ eap_tls.c \
+ eap_tls_common.c \
+ eap_ttls.c \
+ eapol_supp_sm.c \
+ ms_funcs.c \
+ mschapv2.c
+TLS_FUNCS=y
+NEED_AES_EAX=y
+NEED_AES_ENCBLOCK=y
+NEED_AES_OMAC1=y
+.endif
+
+.if !empty(CFLAGS:M*-DEAP_AKA)
+SRCS+= eap_aka.c
+NEED_SIM_COMMON=y
+NEED_AES_CBC=y
+.endif
+
+.if !empty(CFLAGS:M*-DEAP_SIM)
+SRCS+= eap_sim.c
+NEED_SIM_COMMON=y
+NEED_AES_CBC=y
+.endif
+
+.if defined(NEED_SIM_COMMON)
+SRCS+= eap_sim_common.c
+NEED_FIPS186_2_PRF=y
+.endif
+
+# PC/SC interface for smartcards (USIM, GSM SIM)
+# GSM/UMTS authentication algorithm (for EAP-SIM/EAP-AKA)
+# NB: requires devel/pcsc-lite
+#
+# WPA_SUPPLICANT_CFLAGS=-DEAP_AKA -DPCSC_FUNCS -I/usr/local/include/PCSC
+# WPA_SUPPLICANT_LDADD=-L/usr/local/lib
+#
+.if !empty(CFLAGS:M*-DPCSC_FUNCS)
+SRCS+= pcsc_funcs.c
+LIBADD+= pcslite pthread
+.endif
+
+.if !empty(CFLAGS:M*-DEAP_GPSK)
+CFLAGS+=-DEAP_GPSK_SHA256
+SRCS+= eap_gpsk.c \
+ eap_gpsk_common.c
+NEED_AES_OMAC1=y
+.endif
+
+.if !empty(CFLAGS:M*-DEAP_PAX)
+SRCS+= eap_pax.c \
+ eap_pax_common.c
+.endif
+
+.if !empty(CFLAGS:M*-DEAP_SAKE)
+SRCS+= eap_sake.c \
+ eap_sake_common.c
+.endif
+
+.include "${.CURDIR}/../Makefile.crypto"
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/wpa/wpa_supplicant/Makefile.depend b/usr.sbin/wpa/wpa_supplicant/Makefile.depend
new file mode 100644
index 0000000..8d486ac
--- /dev/null
+++ b/usr.sbin/wpa/wpa_supplicant/Makefile.depend
@@ -0,0 +1,23 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libpcap \
+ lib/libutil \
+ secure/lib/libcrypto \
+ secure/lib/libssl \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/wpa/wpa_supplicant/Packet32.c b/usr.sbin/wpa/wpa_supplicant/Packet32.c
new file mode 100644
index 0000000..8e7da03
--- /dev/null
+++ b/usr.sbin/wpa/wpa_supplicant/Packet32.c
@@ -0,0 +1,413 @@
+/*-
+ * Copyright (c) 2005
+ * Bill Paul <wpaul@windriver.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 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/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * This file implements a small portion of the Winpcap API for the
+ * Windows NDIS interface in wpa_supplicant. It provides just enough
+ * routines to fool wpa_supplicant into thinking it's really running
+ * in a Windows environment.
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/errno.h>
+#include <sys/sysctl.h>
+#include <sys/fcntl.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <net/route.h>
+
+#include <net80211/ieee80211_ioctl.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <pcap.h>
+
+#include "Packet32.h"
+
+#define OID_802_11_ADD_KEY 0x0d01011D
+
+typedef ULONGLONG NDIS_802_11_KEY_RSC;
+typedef UCHAR NDIS_802_11_MAC_ADDRESS[6];
+
+typedef struct NDIS_802_11_KEY {
+ ULONG Length;
+ ULONG KeyIndex;
+ ULONG KeyLength;
+ NDIS_802_11_MAC_ADDRESS BSSID;
+ NDIS_802_11_KEY_RSC KeyRSC;
+ UCHAR KeyMaterial[1];
+} NDIS_802_11_KEY;
+
+typedef struct NDIS_802_11_KEY_COMPAT {
+ ULONG Length;
+ ULONG KeyIndex;
+ ULONG KeyLength;
+ NDIS_802_11_MAC_ADDRESS BSSID;
+ UCHAR Pad[6]; /* Make struct layout match Windows. */
+ NDIS_802_11_KEY_RSC KeyRSC;
+#ifdef notdef
+ UCHAR KeyMaterial[1];
+#endif
+} NDIS_802_11_KEY_COMPAT;
+
+#define TRUE 1
+#define FALSE 0
+
+struct adapter {
+ int socket;
+ char name[IFNAMSIZ];
+ int prev_roaming;
+};
+
+PCHAR
+PacketGetVersion(void)
+{
+ return("FreeBSD WinPcap compatibility shim v1.0");
+}
+
+void *
+PacketOpenAdapter(CHAR *iface)
+{
+ struct adapter *a;
+ int s;
+ int ifflags;
+ struct ifreq ifr;
+ struct ieee80211req ireq;
+
+ s = socket(PF_INET, SOCK_DGRAM, 0);
+
+ if (s == -1)
+ return(NULL);
+
+ a = malloc(sizeof(struct adapter));
+ if (a == NULL)
+ return(NULL);
+
+ a->socket = s;
+ if (strncmp(iface, "\\Device\\NPF_", 12) == 0)
+ iface += 12;
+ else if (strncmp(iface, "\\DEVICE\\", 8) == 0)
+ iface += 8;
+ snprintf(a->name, IFNAMSIZ, "%s", iface);
+
+ /* Turn off net80211 roaming */
+ bzero((char *)&ireq, sizeof(ireq));
+ strncpy(ireq.i_name, iface, sizeof (ifr.ifr_name));
+ ireq.i_type = IEEE80211_IOC_ROAMING;
+ if (ioctl(a->socket, SIOCG80211, &ireq) == 0) {
+ a->prev_roaming = ireq.i_val;
+ ireq.i_val = IEEE80211_ROAMING_MANUAL;
+ if (ioctl(a->socket, SIOCS80211, &ireq) < 0)
+ fprintf(stderr,
+ "Could not set IEEE80211_ROAMING_MANUAL\n");
+ }
+
+ bzero((char *)&ifr, sizeof(ifr));
+ strncpy(ifr.ifr_name, iface, sizeof (ifr.ifr_name));
+ if (ioctl(a->socket, SIOCGIFFLAGS, (caddr_t)&ifr) < 0) {
+ free(a);
+ close(s);
+ return(NULL);
+ }
+ ifr.ifr_flags |= IFF_UP;
+ if (ioctl(a->socket, SIOCSIFFLAGS, (caddr_t)&ifr) < 0) {
+ free(a);
+ close(s);
+ return(NULL);
+ }
+
+ return(a);
+}
+
+int
+PacketRequest(void *iface, BOOLEAN set, PACKET_OID_DATA *oid)
+{
+ struct adapter *a;
+ uint32_t retval;
+ struct ifreq ifr;
+ NDIS_802_11_KEY *old;
+ NDIS_802_11_KEY_COMPAT *new;
+ PACKET_OID_DATA *o = NULL;
+
+ if (iface == NULL)
+ return(-1);
+
+ a = iface;
+ bzero((char *)&ifr, sizeof(ifr));
+
+ /*
+ * This hack is necessary to work around a difference
+ * betwee the GNU C and Microsoft C compilers. The NDIS_802_11_KEY
+ * structure has a uint64_t in it, right after an array of
+ * chars. The Microsoft compiler inserts padding right before
+ * the 64-bit value to align it on a 64-bit boundary, but
+ * GCC only aligns it on a 32-bit boundary. Trying to pass
+ * the GCC-formatted structure to an NDIS binary driver
+ * fails because some of the fields appear to be at the
+ * wrong offsets.
+ *
+ * To get around this, if we detect someone is trying to do
+ * a set operation on OID_802_11_ADD_KEY, we shuffle the data
+ * into a properly padded structure and pass that into the
+ * driver instead. This allows the driver_ndis.c code supplied
+ * with wpa_supplicant to work unmodified.
+ */
+
+ if (set == TRUE && oid->Oid == OID_802_11_ADD_KEY) {
+ old = (NDIS_802_11_KEY *)&oid->Data;
+ o = malloc(sizeof(PACKET_OID_DATA) +
+ sizeof(NDIS_802_11_KEY_COMPAT) + old->KeyLength);
+ if (o == NULL)
+ return(0);
+ bzero((char *)o, sizeof(PACKET_OID_DATA) +
+ sizeof(NDIS_802_11_KEY_COMPAT) + old->KeyLength);
+ o->Oid = oid->Oid;
+ o->Length = sizeof(NDIS_802_11_KEY_COMPAT) + old->KeyLength;
+ new = (NDIS_802_11_KEY_COMPAT *)&o->Data;
+ new->KeyRSC = old->KeyRSC;
+ new->Length = o->Length;
+ new->KeyIndex = old->KeyIndex;
+ new->KeyLength = old->KeyLength;
+ bcopy(old->BSSID, new->BSSID, sizeof(NDIS_802_11_MAC_ADDRESS));
+ bcopy(old->KeyMaterial, (char *)new +
+ sizeof(NDIS_802_11_KEY_COMPAT), new->KeyLength);
+ ifr.ifr_data = (caddr_t)o;
+ } else
+ ifr.ifr_data = (caddr_t)oid;
+
+ strlcpy(ifr.ifr_name, a->name, sizeof(ifr.ifr_name));
+
+ if (set == TRUE)
+ retval = ioctl(a->socket, SIOCSDRVSPEC, &ifr);
+ else
+ retval = ioctl(a->socket, SIOCGDRVSPEC, &ifr);
+
+ if (o != NULL)
+ free(o);
+
+ if (retval)
+ return(0);
+
+ return(1);
+}
+
+int
+PacketGetAdapterNames(CHAR *namelist, ULONG *len)
+{
+ int mib[6];
+ size_t needed;
+ struct if_msghdr *ifm;
+ struct sockaddr_dl *sdl;
+ char *buf, *lim, *next;
+ char *plist;
+ int spc;
+ int i, ifcnt = 0;
+
+ plist = namelist;
+ spc = 0;
+
+ bzero(plist, *len);
+
+ needed = 0;
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0; /* protocol */
+ mib[3] = 0; /* wildcard address family */
+ mib[4] = NET_RT_IFLIST;
+ mib[5] = 0; /* no flags */
+
+ if (sysctl (mib, 6, NULL, &needed, NULL, 0) < 0)
+ return(FALSE);
+
+ buf = malloc (needed);
+ if (buf == NULL)
+ return(FALSE);
+
+ if (sysctl (mib, 6, buf, &needed, NULL, 0) < 0) {
+ free(buf);
+ return(FALSE);
+ }
+
+ lim = buf + needed;
+
+ /* Generate interface name list. */
+
+ next = buf;
+ while (next < lim) {
+ ifm = (struct if_msghdr *)next;
+ if (ifm->ifm_type == RTM_IFINFO) {
+ sdl = (struct sockaddr_dl *)(ifm + 1);
+ if (strnstr(sdl->sdl_data, "wlan", sdl->sdl_nlen)) {
+ if ((spc + sdl->sdl_nlen) > *len) {
+ free(buf);
+ return(FALSE);
+ }
+ strncpy(plist, sdl->sdl_data, sdl->sdl_nlen);
+ plist += (sdl->sdl_nlen + 1);
+ spc += (sdl->sdl_nlen + 1);
+ ifcnt++;
+ }
+ }
+ next += ifm->ifm_msglen;
+ }
+
+
+ /* Insert an extra "" as a spacer */
+
+ plist++;
+ spc++;
+
+ /*
+ * Now generate the interface description list. There
+ * must be a unique description for each interface, and
+ * they have to match what the ndis_events program will
+ * feed in later. To keep this simple, we just repeat
+ * the interface list over again.
+ */
+
+ next = buf;
+ while (next < lim) {
+ ifm = (struct if_msghdr *)next;
+ if (ifm->ifm_type == RTM_IFINFO) {
+ sdl = (struct sockaddr_dl *)(ifm + 1);
+ if (strnstr(sdl->sdl_data, "wlan", sdl->sdl_nlen)) {
+ if ((spc + sdl->sdl_nlen) > *len) {
+ free(buf);
+ return(FALSE);
+ }
+ strncpy(plist, sdl->sdl_data, sdl->sdl_nlen);
+ plist += (sdl->sdl_nlen + 1);
+ spc += (sdl->sdl_nlen + 1);
+ ifcnt++;
+ }
+ }
+ next += ifm->ifm_msglen;
+ }
+
+ free (buf);
+
+ *len = spc + 1;
+
+ return(TRUE);
+}
+
+void
+PacketCloseAdapter(void *iface)
+{
+ struct adapter *a;
+ struct ifreq ifr;
+ struct ieee80211req ireq;
+
+ if (iface == NULL)
+ return;
+
+ a = iface;
+
+ /* Reset net80211 roaming */
+ bzero((char *)&ireq, sizeof(ireq));
+ strncpy(ireq.i_name, a->name, sizeof (ifr.ifr_name));
+ ireq.i_type = IEEE80211_IOC_ROAMING;
+ ireq.i_val = a->prev_roaming;
+ ioctl(a->socket, SIOCS80211, &ireq);
+
+ bzero((char *)&ifr, sizeof(ifr));
+ strncpy(ifr.ifr_name, a->name, sizeof (ifr.ifr_name));
+ ioctl(a->socket, SIOCGIFFLAGS, (caddr_t)&ifr);
+ ifr.ifr_flags &= ~IFF_UP;
+ ioctl(a->socket, SIOCSIFFLAGS, (caddr_t)&ifr);
+ close(a->socket);
+ free(a);
+
+ return;
+}
+
+#if __FreeBSD_version < 600000
+
+/*
+ * The version of libpcap in FreeBSD 5.2.1 doesn't have these routines.
+ * Call me insane if you will, but I still run 5.2.1 on my laptop, and
+ * I'd like to use WPA there.
+ */
+
+int
+pcap_get_selectable_fd(pcap_t *p)
+{
+ return(pcap_fileno(p));
+}
+
+/*
+ * The old version of libpcap opens its BPF descriptor in read-only
+ * mode. We need to temporarily create a new one we can write to.
+ */
+
+int
+pcap_inject(pcap_t *p, const void *buf, size_t len)
+{
+ int fd;
+ int res, n = 0;
+ char device[sizeof "/dev/bpf0000000000"];
+ struct ifreq ifr;
+
+ /*
+ * Go through all the minors and find one that isn't in use.
+ */
+ do {
+ (void)snprintf(device, sizeof(device), "/dev/bpf%d", n++);
+ fd = open(device, O_RDWR);
+ } while (fd < 0 && errno == EBUSY);
+
+ if (fd == -1)
+ return(-1);
+
+ bzero((char *)&ifr, sizeof(ifr));
+ ioctl(pcap_fileno(p), BIOCGETIF, (caddr_t)&ifr);
+ ioctl(fd, BIOCSETIF, (caddr_t)&ifr);
+
+ res = write(fd, buf, len);
+
+ close(fd);
+
+ return(res);
+}
+#endif
diff --git a/usr.sbin/wpa/wpa_supplicant/Packet32.h b/usr.sbin/wpa/wpa_supplicant/Packet32.h
new file mode 100644
index 0000000..e0598e7
--- /dev/null
+++ b/usr.sbin/wpa/wpa_supplicant/Packet32.h
@@ -0,0 +1,67 @@
+/*-
+ * Copyright (c) 2005
+ * Bill Paul <wpaul@windriver.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 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _PACKET32_H_
+#define _PACKET32_H_
+
+#include <sys/types.h>
+#include <ntddndis.h>
+
+struct PACKET_OID_DATA {
+ uint32_t Oid;
+ uint32_t Length;
+ uint8_t Data[1];
+};
+
+
+typedef struct PACKET_OID_DATA PACKET_OID_DATA;
+
+extern PCHAR PacketGetVersion(void);
+extern void *PacketOpenAdapter(CHAR *);
+extern int PacketRequest(void *, BOOLEAN, PACKET_OID_DATA *);
+extern int PacketGetAdapterNames(CHAR *, ULONG *);
+extern void PacketCloseAdapter(void *);
+
+/*
+ * This is for backwards compatibility on FreeBSD 5.
+ */
+
+#ifndef SIOCGDRVSPEC
+#define SIOCSDRVSPEC _IOW('i', 123, struct ifreq) /* set driver-specific
+ parameters */
+#define SIOCGDRVSPEC _IOWR('i', 123, struct ifreq) /* get driver-specific
+ parameters */
+#endif
+
+#endif /* _PACKET32_H_ */
diff --git a/usr.sbin/wpa/wpa_supplicant/ntddndis.h b/usr.sbin/wpa/wpa_supplicant/ntddndis.h
new file mode 100644
index 0000000..42e403d
--- /dev/null
+++ b/usr.sbin/wpa/wpa_supplicant/ntddndis.h
@@ -0,0 +1,31 @@
+#ifndef _NTDDNDIS_H_
+#define _NTDDNDIS_H_
+
+/*
+ * $FreeBSD$
+ */
+
+/*
+ * Fake up some of the Windows type definitions so that the NDIS
+ * interface module in wpa_supplicant will build.
+ */
+
+#define ULONG uint32_t
+#define USHORT uint16_t
+#define UCHAR uint8_t
+#define LONG int32_t
+#define SHORT int16_t
+#define CHAR int8_t
+#define ULONGLONG uint64_t
+#define LONGLONG int64_t
+#define BOOLEAN uint8_t
+typedef void * LPADAPTER;
+typedef char * PTSTR;
+typedef char * PCHAR;
+
+#define TRUE 1
+#define FALSE 0
+
+#define OID_802_3_CURRENT_ADDRESS 0x01010102
+
+#endif /* _NTDDNDIS_H_ */
diff --git a/usr.sbin/wpa/wpa_supplicant/wpa_supplicant.8 b/usr.sbin/wpa/wpa_supplicant/wpa_supplicant.8
new file mode 100644
index 0000000..fe9fdce
--- /dev/null
+++ b/usr.sbin/wpa/wpa_supplicant/wpa_supplicant.8
@@ -0,0 +1,184 @@
+.\" Copyright (c) 2005 Sam Leffler <sam@errno.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 November 7, 2012
+.Dt WPA_SUPPLICANT 8
+.Os
+.Sh NAME
+.Nm wpa_supplicant
+.Nd "WPA/802.11i Supplicant for wireless network devices"
+.Sh SYNOPSIS
+.Nm
+.Op Fl BdhKLqstuvW
+.Op Fl b Ar br_ifname
+.Fl c Ar config-file
+.Op Fl C Ar ctrl
+.Op Fl D Ar driver
+.Op Fl f Ar debug file
+.Op Fl g Ar global ctrl
+.Fl i Ar ifname
+.Op Fl o Ar override driver
+.Op Fl O Ar override ctrl
+.Op Fl P Ar pid file
+.Oo Fl N
+.Fl i Ar ifname
+.Fl c Ar config-file
+.Op Fl C Ar ctrl
+.Op Fl D driver
+.Op Fl p Ar driver_param
+.Op Fl b Ar br_ifname
+.No ...
+.Oc
+.Sh DESCRIPTION
+The
+.Nm
+utility
+is an implementation of the WPA Supplicant component,
+i.e., the part that runs in the client stations.
+It implements WPA key negotiation with a WPA Authenticator
+and EAP authentication with an Authentication Server.
+In addition,
+.Nm
+controls the roaming and IEEE 802.11
+authentication/association support of the
+.Xr wlan 4
+module and can be used to configure static WEP keys
+based on identified networks.
+.Pp
+The
+.Nm
+utility
+is designed to be a
+.Dq daemon
+program that runs in the
+background and acts as the backend component controlling
+the wireless connection.
+It supports separate frontend programs such as the
+text-based
+.Xr wpa_cli 8
+program.
+.Pp
+The following arguments must be specified on the command line:
+.Bl -tag -width indent
+.It Fl i Ar ifname
+Use the specified wireless interface.
+.It Fl c Ar config-file
+Use the settings in the specified configuration file when managing
+the wireless interface.
+See
+.Xr wpa_supplicant.conf 5
+for a description of the configuration file syntax and contents.
+.Pp
+Changes to the configuration file can be reloaded by sending a
+.Dv SIGHUP
+to the
+.Nm
+process or with the
+.Xr wpa_cli 8
+utility, using
+.Dq Li "wpa_cli reconfigure" .
+.El
+.Sh OPTIONS
+The following options are available:
+.Bl -tag -width indent
+.It Fl b
+Optional bridge interface name.
+.It Fl B
+Detach from the controlling terminal and run as a daemon process
+in the background.
+.It Fl d
+Enable debugging messages.
+If this option is supplied twice, more verbose messages are displayed.
+.It Fl D
+Driver name (can be multiple drivers: nl80211,wext).
+.It Fl f
+Log output to debug file instead of stdout.
+.It Fl g
+Global ctrl_interface.
+.It Fl h
+Show help text.
+.It Fl K
+Include key information in debugging output.
+.It Fl L
+Display the license for this program on the terminal and exit.
+.It Fl N
+Start describing a new interface.
+.It Fl o
+Overrides driver parameter for new interfaces.
+.It Fl O
+Override ctrl_interface parameter for new interfaces.
+.It Fl p
+Specify driver parameters.
+.It Fl P
+File in which to save the process PID.
+.It Fl q
+Decrease debugging verbosity (i.e., counteract the use of the
+.Fl d
+flag).
+.It Fl s
+Send log messages through
+.Xr syslog 3
+instead of to the terminal.
+.It Fl t
+Include timestamp in debug messages.
+.It Fl u
+Enable DBus control interface.
+.It Fl v
+Display version information on the terminal and exit.
+.It Fl W
+Wait for a control interface monitor before starting.
+.El
+.Sh SEE ALSO
+.Xr an 4 ,
+.Xr ath 4 ,
+.Xr ipw 4 ,
+.Xr iwi 4 ,
+.Xr ral 4 ,
+.Xr rum 4 ,
+.Xr ural 4 ,
+.Xr wi 4 ,
+.Xr wlan 4 ,
+.Xr wpi 4 ,
+.Xr zyd 4 ,
+.Xr wpa_supplicant.conf 5 ,
+.Xr devd 8 ,
+.Xr ifconfig 8 ,
+.Xr wpa_cli 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 6.0 .
+.Sh AUTHORS
+The
+.Nm
+utility was written by
+.An Jouni Malinen Aq Mt j@w1.fi .
+This manual page is derived from the
+.Pa README
+file included in the
+.Nm
+distribution.
diff --git a/usr.sbin/wpa/wpa_supplicant/wpa_supplicant.conf.5 b/usr.sbin/wpa/wpa_supplicant/wpa_supplicant.conf.5
new file mode 100644
index 0000000..94e3100
--- /dev/null
+++ b/usr.sbin/wpa/wpa_supplicant/wpa_supplicant.conf.5
@@ -0,0 +1,578 @@
+.\" Copyright (c) 2005 Sam Leffler <sam@errno.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 April 10, 2010
+.Dt WPA_SUPPLICANT.CONF 5
+.Os
+.Sh NAME
+.Nm wpa_supplicant.conf
+.Nd configuration file for
+.Xr wpa_supplicant 8
+.Sh DESCRIPTION
+The
+.Xr wpa_supplicant 8
+utility is an implementation of the WPA Supplicant component,
+i.e., the part that runs in the client stations.
+It implements WPA key negotiation with a WPA Authenticator
+and EAP authentication with Authentication Server using
+configuration information stored in a text file.
+.Pp
+The configuration file consists of optional global parameter
+settings and one or more network blocks, e.g.\&
+one for each used SSID.
+The
+.Xr wpa_supplicant 8
+utility
+will automatically select the best network based on the order of
+the network blocks in the configuration file, network security level
+(WPA/WPA2 is preferred), and signal strength.
+Comments are indicated with the
+.Ql #
+character; all text to the
+end of the line will be ignored.
+.Sh GLOBAL PARAMETERS
+Default parameters used by
+.Xr wpa_supplicant 8
+may be overridden by specifying
+.Pp
+.Dl parameter=value
+.Pp
+in the configuration file (note no spaces are allowed).
+Values with embedded spaces must be enclosed in quote marks.
+.Pp
+The following parameters are recognized:
+.Bl -tag -width indent
+.It Va ctrl_interface
+The pathname of the directory in which
+.Xr wpa_supplicant 8
+creates
+.Ux
+domain socket files for communication
+with frontend programs such as
+.Xr wpa_cli 8 .
+.It Va ctrl_interface_group
+A group name or group ID to use in setting protection on the
+control interface file.
+This can be set to allow non-root users to access the
+control interface files.
+If no group is specified, the group ID of the control interface
+is not modified and will, typically, be the
+group ID of the directory in which the socket is created.
+.It Va eapol_version
+The IEEE 802.1x/EAPOL protocol version to use; either 1 (default) or 2.
+The
+.Xr wpa_supplicant 8
+utility
+is implemented according to IEEE 802-1X-REV-d8 which defines
+EAPOL version to be 2.
+However, some access points do not work when presented with
+this version so by default
+.Xr wpa_supplicant 8
+will announce that it is using EAPOL version 1.
+If version 2 must be announced for correct operation with an
+access point, this value may be set to 2.
+.It Va ap_scan
+Access point scanning and selection control; one of 0, 1 (default), or 2.
+Only setting 1 should be used with the
+.Xr wlan 4
+module; the other settings are for use on other operating systems.
+.It Va fast_reauth
+EAP fast re-authentication; either 1 (default) or 0.
+Control fast re-authentication support in EAP methods that support it.
+.El
+.Sh NETWORK BLOCKS
+Each potential network/access point should have a
+.Dq "network block"
+that describes how to identify it and how to set up security.
+When multiple network blocks are listed in a configuration file,
+the highest priority one is selected for use or, if multiple networks
+with the same priority are identified, the first one listed in the
+configuration file is used.
+.Pp
+A network block description is of the form:
+.Bd -literal -offset indent
+network={
+ parameter=value
+ ...
+}
+.Ed
+.Pp
+(note the leading
+.Qq Li "network={"
+may have no spaces).
+The block specification contains one or more parameters
+from the following list:
+.Bl -tag -width indent
+.It Va ssid No (required)
+Network name (as announced by the access point).
+An
+.Tn ASCII
+or hex string enclosed in quotation marks.
+.It Va scan_ssid
+SSID scan technique; 0 (default) or 1.
+Technique 0 scans for the SSID using a broadcast Probe Request
+frame while 1 uses a directed Probe Request frame.
+Access points that cloak themselves by not broadcasting their SSID
+require technique 1, but beware that this scheme can cause scanning
+to take longer to complete.
+.It Va bssid
+Network BSSID (typically the MAC address of the access point).
+.It Va priority
+The priority of a network when selecting among multiple networks;
+a higher value means a network is more desirable.
+By default networks have priority 0.
+When multiple networks with the same priority are considered
+for selection, other information such as security policy and
+signal strength are used to select one.
+.It Va mode
+IEEE 802.11 operation mode; either 0 (infrastructure, default) or 1 (IBSS).
+Note that IBSS (adhoc) mode can only be used with
+.Va key_mgmt
+set to
+.Li NONE
+(plaintext and static WEP), or
+.Va key_mgmt
+set to
+.Li WPA-NONE
+(fixed group key TKIP/CCMP).
+In addition,
+.Va ap_scan
+has to be set to 2 for IBSS.
+.Li WPA-NONE
+requires
+.Va proto
+set to WPA,
+.Va key_mgmt
+set to WPA-NONE,
+.Va pairwise
+set to NONE,
+.Va group
+set to either
+CCMP or TKIP (but not both), and
+.Va psk
+must also be set.
+.It Va proto
+List of acceptable protocols; one or more of:
+.Li WPA
+(IEEE 802.11i/D3.0)
+and
+.Li RSN
+(IEEE 802.11i).
+.Li WPA2
+is another name for
+.Li RSN .
+If not set this defaults to
+.Qq Li "WPA RSN" .
+.It Va key_mgmt
+List of acceptable key management protocols; one or more of:
+.Li WPA-PSK
+(WPA pre-shared key),
+.Li WPA-EAP
+(WPA using EAP authentication),
+.Li IEEE8021X
+(IEEE 802.1x using EAP authentication and,
+optionally, dynamically generated WEP keys),
+.Li NONE
+(plaintext or static WEP keys).
+If not set this defaults to
+.Qq Li "WPA-PSK WPA-EAP" .
+.It Va auth_alg
+List of allowed IEEE 802.11 authentication algorithms; one or more of:
+.Li OPEN
+(Open System authentication, required for WPA/WPA2),
+.Li SHARED
+(Shared Key authentication),
+.Li LEAP
+(LEAP/Network EAP).
+If not set automatic selection is used (Open System with LEAP
+enabled if LEAP is allowed as one of the EAP methods).
+.It Va pairwise
+List of acceptable pairwise (unicast) ciphers for WPA; one or more of:
+.Li CCMP
+(AES in Counter mode with CBC-MAC, RFC 3610, IEEE 802.11i/D7.0),
+.Li TKIP
+(Temporal Key Integrity Protocol, IEEE 802.11i/D7.0),
+.Li NONE
+(deprecated).
+If not set this defaults to
+.Qq Li "CCMP TKIP" .
+.It Va group
+List of acceptable group (multicast) ciphers for WPA; one or more of:
+.Li CCMP
+(AES in Counter mode with CBC-MAC, RFC 3610, IEEE 802.11i/D7.0),
+.Li TKIP
+(Temporal Key Integrity Protocol, IEEE 802.11i/D7.0),
+.Li WEP104
+(WEP with 104-bit key),
+.Li WEP40
+(WEP with 40-bit key).
+If not set this defaults to
+.Qq Li "CCMP TKIP WEP104 WEP40" .
+.It Va psk
+WPA preshared key used in WPA-PSK mode.
+The key is specified as 64 hex digits or as
+an 8-63 character
+.Tn ASCII
+passphrase.
+.Tn ASCII
+passphrases are dynamically converted to a 256-bit key at runtime
+using the network SSID, or they can be statically converted at
+configuration time using
+the
+.Xr wpa_passphrase 8
+utility.
+.It Va eapol_flags
+Dynamic WEP key usage for non-WPA mode, specified as a bit field.
+Bit 0 (1) forces dynamically generated unicast WEP keys to be used.
+Bit 1 (2) forces dynamically generated broadcast WEP keys to be used.
+By default this is set to 3 (use both).
+.It Va eap
+List of acceptable EAP methods; one or more of:
+.Li MD5
+(EAP-MD5, cannot be used with WPA,
+used only as a Phase 2 method with EAP-PEAP or EAP-TTLS),
+.Li MSCHAPV2
+(EAP-MSCHAPV2, cannot be used with WPA;
+used only as a Phase 2 method with EAP-PEAP or EAP-TTLS),
+.Li OTP
+(EAP-OTP, cannot be used with WPA;
+used only as a Phase 2 metod with EAP-PEAP or EAP-TTLS),
+.Li GTC
+(EAP-GTC, cannot be used with WPA;
+used only as a Phase 2 metod with EAP-PEAP or EAP-TTLS),
+.Li TLS
+(EAP-TLS, client and server certificate),
+.Li PEAP
+(EAP-PEAP, with tunneled EAP authentication),
+.Li TTLS
+(EAP-TTLS, with tunneled EAP or PAP/CHAP/MSCHAP/MSCHAPV2 authentication).
+If not set this defaults to all available methods compiled in to
+.Xr wpa_supplicant 8 .
+Note that by default
+.Xr wpa_supplicant 8
+is compiled with EAP support; see
+.Xr make.conf 5
+for the
+.Va NO_WPA_SUPPLICANT_EAPOL
+configuration variable that can be used to disable EAP support.
+.It Va identity
+Identity string for EAP.
+.It Va anonymous_identity
+Anonymous identity string for EAP (to be used as the unencrypted identity
+with EAP types that support different tunneled identities; e.g.\& EAP-TTLS).
+.It Va mixed_cell
+Configure whether networks that allow both plaintext and encryption
+are allowed when selecting a BSS from the scan results.
+By default this is set to 0 (disabled).
+.It Va password
+Password string for EAP.
+.It Va ca_cert
+Pathname to CA certificate file.
+This file can have one or more trusted CA certificates.
+If
+.Va ca_cert
+is not included, server certificates will not be verified (not recommended).
+.It Va client_cert
+Pathname to client certificate file (PEM/DER).
+.It Va private_key
+Pathname to a client private key file (PEM/DER/PFX).
+When a PKCS#12/PFX file is used, then
+.Va client_cert
+should not be specified as both the private key and certificate will be
+read from PKCS#12 file.
+.It Va private_key_passwd
+Password for any private key file.
+.It Va dh_file
+Pathname to a file holding DH/DSA parameters (in PEM format).
+This file holds parameters for an ephemeral DH key exchange.
+In most cases, the default RSA authentication does not use this configuration.
+However, it is possible to set up RSA to use an ephemeral DH key exchange.
+In addition, ciphers with
+DSA keys always use ephemeral DH keys.
+This can be used to achieve forward secrecy.
+If the
+.Va dh_file
+is in DSA parameters format, it will be automatically converted
+into DH parameters.
+.It Va subject_match
+Substring to be matched against the subject of the
+authentication server certificate.
+If this string is set, the server
+certificate is only accepted if it contains this string in the subject.
+The subject string is in following format:
+.Pp
+.Dl "/C=US/ST=CA/L=San Francisco/CN=Test AS/emailAddress=as@example.com"
+.It Va phase1
+Phase1 (outer authentication, i.e., TLS tunnel) parameters
+(string with field-value pairs, e.g.,
+.Qq Li peapver=0
+or
+.Qq Li "peapver=1 peaplabel=1" ) .
+.Bl -inset
+.It Li peapver
+can be used to force which PEAP version (0 or 1) is used.
+.It Li peaplabel=1
+can be used to force new label,
+.Dq "client PEAP encryption" ,
+to be used during key derivation when PEAPv1 or newer.
+Most existing PEAPv1 implementations seem to be using the old label,
+.Dq Li "client EAP encryption" ,
+and
+.Xr wpa_supplicant 8
+is now using that as the
+default value.
+Some servers, e.g.,
+.Tn Radiator ,
+may require
+.Li peaplabel=1
+configuration to interoperate with PEAPv1; see
+.Pa eap_testing.txt
+for more details.
+.It Li peap_outer_success=0
+can be used to terminate PEAP authentication on
+tunneled EAP-Success.
+This is required with some RADIUS servers that
+implement
+.Pa draft-josefsson-pppext-eap-tls-eap-05.txt
+(e.g.,
+.Tn Lucent NavisRadius v4.4.0
+with PEAP in
+.Dq "IETF Draft 5"
+mode).
+.It Li include_tls_length=1
+can be used to force
+.Xr wpa_supplicant 8
+to include
+TLS Message Length field in all TLS messages even if they are not
+fragmented.
+.It Li sim_min_num_chal=3
+can be used to configure EAP-SIM to require three
+challenges (by default, it accepts 2 or 3).
+.It Li fast_provisioning=1
+option enables in-line provisioning of EAP-FAST
+credentials (PAC).
+.El
+.It Va phase2
+phase2: Phase2 (inner authentication with TLS tunnel) parameters
+(string with field-value pairs, e.g.,
+.Qq Li "auth=MSCHAPV2"
+for EAP-PEAP or
+.Qq Li "autheap=MSCHAPV2 autheap=MD5"
+for EAP-TTLS).
+.It Va ca_cert2
+Like
+.Va ca_cert
+but for EAP inner Phase 2.
+.It Va client_cert2
+Like
+.Va client_cert
+but for EAP inner Phase 2.
+.It Va private_key2
+Like
+.Va private_key
+but for EAP inner Phase 2.
+.It Va private_key2_passwd
+Like
+.Va private_key_passwd
+but for EAP inner Phase 2.
+.It Va dh_file2
+Like
+.Va dh_file
+but for EAP inner Phase 2.
+.It Va subject_match2
+Like
+.Va subject_match
+but for EAP inner Phase 2.
+.It Va eappsk
+16-byte pre-shared key in hex format for use with EAP-PSK.
+.It Va nai
+User NAI for use with EAP-PSK.
+.It Va server_nai
+Authentication Server NAI for use with EAP-PSK.
+.It Va pac_file
+Pathname to the file to use for PAC entries with EAP-FAST.
+The
+.Xr wpa_supplicant 8
+utility
+must be able to create this file and write updates to it when
+PAC is being provisioned or refreshed.
+.It Va eap_workaround
+Enable/disable EAP workarounds for various interoperability issues
+with misbehaving authentication servers.
+By default these workarounds are enabled.
+Strict EAP conformance can be configured by setting this to 0.
+.It Va wep_tx_keyidx
+which key to use for transmission of packets.
+.It Va wep_keyN key
+An
+.Tn ASCII
+string enclosed in quotation marks to encode the WEP key.
+Without quotes this is a hex string of the actual key.
+WEP is considered insecure and should be avoided.
+The exact translation from an ASCII key to a hex key varies.
+Use hex keys where possible.
+.El
+.Sh CERTIFICATES
+Some EAP authentication methods require use of certificates.
+EAP-TLS uses both server- and client-side certificates,
+whereas EAP-PEAP and EAP-TTLS only require a server-side certificate.
+When a client certificate is used, a matching private key file must
+also be included in configuration.
+If the private key uses a passphrase, this
+has to be configured in the
+.Nm
+file as
+.Va private_key_passwd .
+.Pp
+The
+.Xr wpa_supplicant 8
+utility
+supports X.509 certificates in PEM and DER formats.
+User certificate and private key can be included in the same file.
+.Pp
+If the user certificate and private key is received in PKCS#12/PFX
+format, they need to be converted to a suitable PEM/DER format for
+use by
+.Xr wpa_supplicant 8 .
+This can be done using the
+.Xr openssl 1
+program, e.g.\& with the following commands:
+.Bd -literal
+# convert client certificate and private key to PEM format
+openssl pkcs12 -in example.pfx -out user.pem -clcerts
+# convert CA certificate (if included in PFX file) to PEM format
+openssl pkcs12 -in example.pfx -out ca.pem -cacerts -nokeys
+.Ed
+.Sh FILES
+.Bl -tag -width ".Pa /usr/share/examples/etc/wpa_supplicant.conf" -compact
+.It Pa /etc/wpa_supplicant.conf
+.It Pa /usr/share/examples/etc/wpa_supplicant.conf
+.El
+.Sh EXAMPLES
+WPA-Personal (PSK) as a home network and WPA-Enterprise with EAP-TLS
+as a work network:
+.Bd -literal
+# allow frontend (e.g., wpa_cli) to be used by all users in 'wheel' group
+ctrl_interface=/var/run/wpa_supplicant
+ctrl_interface_group=wheel
+#
+# home network; allow all valid ciphers
+network={
+ ssid="home"
+ scan_ssid=1
+ key_mgmt=WPA-PSK
+ psk="very secret passphrase"
+}
+#
+# work network; use EAP-TLS with WPA; allow only CCMP and TKIP ciphers
+network={
+ ssid="work"
+ scan_ssid=1
+ key_mgmt=WPA-EAP
+ pairwise=CCMP TKIP
+ group=CCMP TKIP
+ eap=TLS
+ identity="user@example.com"
+ ca_cert="/etc/cert/ca.pem"
+ client_cert="/etc/cert/user.pem"
+ private_key="/etc/cert/user.prv"
+ private_key_passwd="password"
+}
+.Ed
+.Pp
+WPA-RADIUS/EAP-PEAP/MSCHAPv2 with RADIUS servers that use old peaplabel
+(e.g., Funk Odyssey and SBR, Meetinghouse Aegis, Interlink RAD-Series):
+.Bd -literal
+ctrl_interface=/var/run/wpa_supplicant
+ctrl_interface_group=wheel
+network={
+ ssid="example"
+ scan_ssid=1
+ key_mgmt=WPA-EAP
+ eap=PEAP
+ identity="user@example.com"
+ password="foobar"
+ ca_cert="/etc/cert/ca.pem"
+ phase1="peaplabel=0"
+ phase2="auth=MSCHAPV2"
+}
+.Ed
+.Pp
+EAP-TTLS/EAP-MD5-Challenge configuration with anonymous identity for the
+unencrypted use.
+Real identity is sent only within an encrypted TLS tunnel.
+.Bd -literal
+ctrl_interface=/var/run/wpa_supplicant
+ctrl_interface_group=wheel
+network={
+ ssid="example"
+ scan_ssid=1
+ key_mgmt=WPA-EAP
+ eap=TTLS
+ identity="user@example.com"
+ anonymous_identity="anonymous@example.com"
+ password="foobar"
+ ca_cert="/etc/cert/ca.pem"
+ phase2="auth=MD5"
+}
+.Ed
+.Pp
+Traditional WEP configuration with 104 bit key specified in hexadecimal.
+Note the WEP key is not quoted.
+.Bd -literal
+ctrl_interface=/var/run/wpa_supplicant
+ctrl_interface_group=wheel
+network={
+ ssid="example"
+ scan_ssid=1
+ key_mgmt=NONE
+ wep_tx_keyidx=0
+ # hex keys denoted without quotes
+ wep_key0=42FEEDDEAFBABEDEAFBEEFAA55
+ # ASCII keys denoted with quotes.
+ wep_key1="FreeBSDr0cks!"
+}
+.Ed
+.Sh SEE ALSO
+.Xr wpa_cli 8 ,
+.Xr wpa_passphrase 8 ,
+.Xr wpa_supplicant 8
+.Sh HISTORY
+The
+.Nm
+manual page and
+.Xr wpa_supplicant 8
+functionality first appeared in
+.Fx 6.0 .
+.Sh AUTHORS
+This manual page is derived from the
+.Pa README
+and
+.Pa wpa_supplicant.conf
+files in the
+.Nm wpa_supplicant
+distribution provided by
+.An Jouni Malinen Aq Mt j@w1.fi .
diff --git a/usr.sbin/yp_mkdb/Makefile b/usr.sbin/yp_mkdb/Makefile
new file mode 100644
index 0000000..1c41105
--- /dev/null
+++ b/usr.sbin/yp_mkdb/Makefile
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../libexec/ypxfr ${.CURDIR}/../ypserv
+
+PROG= yp_mkdb
+MAN= yp_mkdb.8
+SRCS= yp_mkdb.c yp_dblookup.c yp_dbwrite.c
+
+CFLAGS+= -Dyp_error=warnx
+CFLAGS+= -I${.CURDIR}/../../libexec/ypxfr -I${.CURDIR}/../../usr.sbin/ypserv
+
+WARNS?= 1
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/yp_mkdb/Makefile.depend b/usr.sbin/yp_mkdb/Makefile.depend
new file mode 100644
index 0000000..ddd5dbd
--- /dev/null
+++ b/usr.sbin/yp_mkdb/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/yp_mkdb/yp_mkdb.8 b/usr.sbin/yp_mkdb/yp_mkdb.8
new file mode 100644
index 0000000..2689f29
--- /dev/null
+++ b/usr.sbin/yp_mkdb/yp_mkdb.8
@@ -0,0 +1,209 @@
+.\" Copyright (c) 1995, 1996
+.\" 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 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 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 CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (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, 1996
+.Dt YP_MKDB 8
+.Os
+.Sh NAME
+.Nm yp_mkdb
+.Nd "generate the NIS databases"
+.Sh SYNOPSIS
+.Nm
+.Fl c
+.Nm
+.Fl u Ar dbname
+.Nm
+.Op Fl c
+.Op Fl b
+.Op Fl s
+.Op Fl f
+.Op Fl i Ar inputfile
+.Op Fl o Ar outputfile
+.Op Fl d Ar domainname
+.Op Fl m Ar mastername
+.Ar inputfile
+.Ar dbname
+.Sh DESCRIPTION
+The
+.Nm
+utility creates
+.Xr db 3
+style databases for use with
+.Fx Ns 's
+NIS server.
+The
+.Nm
+utility reads data from
+.Ar inputfile ,
+and writes it to
+.Ar dbname
+in
+.Xr db 3
+format (using the hash table method).
+The input should be in 'key data' format, which is to say
+two fields of
+.Tn ASCII
+data separated by white space.
+The first field
+is assumed to be the key, and everything else is assumed to be
+the data.
+These databases are typically stored in
+.Pa /var/yp/[domainname]
+where
+.Ar domainname
+is the name of the NIS domain being served.
+The
+.Nm
+utility is usually invoked by
+.Pa /var/yp/Makefile .
+The
+.Nm
+utility can also be used to dump an NIS database file so that its
+contents can be examined.
+For security reasons, all databases that
+.Nm
+creates are readable and writable by owner only (and usually the
+owner is root).
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl c
+Cause
+.Nm
+to send a YPPROC_CLEAR request to
+.Xr ypserv 8
+on the local host.
+This signal tells the server to close any open
+database descriptors and flush out its database cache.
+If used alone,
+this flag signals the server and does nothing else.
+If used as part
+of a database creation command,
+.Nm
+will send the signal only after the new database has been successfully
+created.
+.It Fl b
+Cause
+.Nm
+to add a special entry to the database with a key of
+.Em YP_INTERDOMAIN
+and an empty data field.
+If this key is present in a map, it alters the
+behavior of the 'match' procedure in
+.Xr ypserv 8
+slightly.
+If a match query fails (because the server could not find
+a record that matched the supplied key), and the
+.Em YP_INTERDOMAIN
+key exists within the queried map,
+.Xr ypserv 8
+will try to match the entry again using a DNS lookup.
+Note that this
+special behavior only applies to the
+.Em hosts
+maps.
+Using the
+.Fl b
+flag for other maps has no effect.
+.It Fl s
+This flag is used to add a special entry to the database with a key of
+.Em YP_SECURE
+and an empty data field.
+If this key is present in a map,
+.Xr ypserv 8
+will deny access to the map to any client that is not using a
+reserved port for its query.
+This is used mainly for the
+.Em master.passwd
+maps, which should be restricted to privileged access only.
+.It Fl f
+This flag is used to turn on filtering of lines in the source file
+input that start with ``+'' or ``-'' characters.
+These characters
+have special meaning for the
+.Pa group ,
+.Pa passwd
+and
+.Pa master.passwd
+maps and hence should not be allowed to appear in them as the first
+character of a key or datum.
+If the
+.Fl f
+flag is used,
+.Nm
+will reject any source line that starts with a ``+'' or ``-''
+character and issue a warning message displaying the line that
+was dropped.
+.It Fl u Ar dbname
+Dump (or 'unwind') an NIS database.
+This option can be used to
+inspect the contents of an existing NIS database.
+.It Fl i Ar inputfile
+When generating an NIS map, encode
+.Ar inputfile
+as a special entry in the database with a key of
+.Em YP_INPUT_FILE .
+.It Fl o Ar outputfile
+When generating an NIS map, encode
+.Ar outputfile
+as a special entry in the database with a key of
+.Em YP_OUTPUT_FILE .
+.It Fl d Ar domainname
+When generating an NIS map, encode
+.Ar domainname
+as a special entry in the database with a key of
+.Em YP_DOMAIN_NAME .
+.It Fl m Ar mastername
+When generating an NIS map, encode
+.Ar mastername
+as a special entry in the database with a key of
+.Em YP_MASTER_NAME .
+This entry in the database is frequently used by various NIS utilities
+to determine the name of an NIS master server for a domain.
+By default,
+.Nm
+assumes that the local host is the NIS master; the
+.Fl m
+option is used to override this default.
+.El
+.Sh FILES
+.Bl -tag -width /var/yp/Makefile -compact
+.It Pa /var/yp/Makefile
+the Makefile that calls
+.Nm
+to build the NIS databases
+.El
+.Sh SEE ALSO
+.Xr db 3 ,
+.Xr ypserv 8
+.Sh AUTHORS
+.An Bill Paul Aq Mt wpaul@ctr.columbia.edu
diff --git a/usr.sbin/yp_mkdb/yp_mkdb.c b/usr.sbin/yp_mkdb/yp_mkdb.c
new file mode 100644
index 0000000..290e405
--- /dev/null
+++ b/usr.sbin/yp_mkdb/yp_mkdb.c
@@ -0,0 +1,340 @@
+/*
+ * Copyright (c) 1995, 1996
+ * 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <fcntl.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <rpc/rpc.h>
+#include <rpcsvc/yp.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "yp_extern.h"
+#include "ypxfr_extern.h"
+
+char *yp_dir = ""; /* No particular default needed. */
+int debug = 1;
+
+static void
+usage(void)
+{
+ fprintf(stderr, "%s\n%s\n%s\n%s\n",
+ "usage: yp_mkdb -c",
+ " yp_mkdb -u dbname",
+ " yp_mkdb [-c] [-b] [-s] [-f] [-i inputfile] [-o outputfile]",
+ " [-d domainname ] [-m mastername] inputfile dbname");
+ exit(1);
+}
+
+#define PERM_SECURE (S_IRUSR|S_IWUSR)
+static DB *
+open_db(char *path, int flags)
+{
+ extern HASHINFO openinfo;
+
+ return(dbopen(path, flags, PERM_SECURE, DB_HASH, &openinfo));
+}
+
+static void
+unwind(char *map)
+{
+ DB *dbp;
+ DBT key, data;
+
+ dbp = open_db(map, O_RDONLY);
+
+ if (dbp == NULL)
+ err(1, "open_db(%s) failed", map);
+
+ key.data = NULL;
+ while (yp_next_record(dbp, &key, &data, 1, 1) == YP_TRUE)
+ printf("%.*s %.*s\n", (int)key.size, key.data, (int)data.size,
+ data.data);
+
+ (void)(dbp->close)(dbp);
+ return;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ int un = 0;
+ int clear = 0;
+ int filter_plusminus = 0;
+ char *infile = NULL;
+ char *map = NULL;
+ char *domain = NULL;
+ char *infilename = NULL;
+ char *outfilename = NULL;
+ char *mastername = NULL;
+ int interdom = 0;
+ int secure = 0;
+ DB *dbp;
+ DBT key, data;
+ char buf[10240];
+ char *keybuf, *datbuf;
+ FILE *ifp;
+ char hname[MAXHOSTNAMELEN + 2];
+
+ while ((ch = getopt(argc, argv, "uhcbsfd:i:o:m:")) != -1) {
+ switch (ch) {
+ case 'f':
+ filter_plusminus++;
+ break;
+ case 'u':
+ un++;
+ break;
+ case 'c':
+ clear++;
+ break;
+ case 'b':
+ interdom++;
+ break;
+ case 's':
+ secure++;
+ break;
+ case 'd':
+ domain = optarg;
+ break;
+ case 'i':
+ infilename = optarg;
+ break;
+ case 'o':
+ outfilename = optarg;
+ break;
+ case 'm':
+ mastername = optarg;
+ break;
+ case 'h':
+ default:
+ usage();
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (un) {
+ map = argv[0];
+ if (map == NULL)
+ usage();
+ unwind(map);
+ exit(0);
+
+ }
+
+ infile = argv[0];
+ map = argv[1];
+
+ if (infile == NULL || map == NULL) {
+ if (clear)
+ goto doclear;
+ usage();
+ }
+
+ if (mastername == NULL) {
+ if (gethostname((char *)&hname, sizeof(hname)) == -1)
+ err(1, "gethostname() failed");
+ mastername = (char *)&hname;
+ }
+
+ /*
+ * Note that while we can read from stdin, we can't
+ * write to stdout; the db library doesn't let you
+ * write to a file stream like that.
+ */
+ if (!strcmp(infile, "-")) {
+ ifp = stdin;
+ } else {
+ if ((ifp = fopen(infile, "r")) == NULL)
+ err(1, "failed to open %s", infile);
+ }
+
+ if ((dbp = open_db(map, O_RDWR|O_EXLOCK|O_EXCL|O_CREAT)) == NULL)
+ err(1, "open_db(%s) failed", map);
+
+ if (interdom) {
+ key.data = "YP_INTERDOMAIN";
+ key.size = sizeof("YP_INTERDOMAIN") - 1;
+ data.data = "";
+ data.size = 0;
+ yp_put_record(dbp, &key, &data, 0);
+ }
+
+ if (secure) {
+ key.data = "YP_SECURE";
+ key.size = sizeof("YP_SECURE") - 1;
+ data.data = "";
+ data.size = 0;
+ yp_put_record(dbp, &key, &data, 0);
+ }
+
+ key.data = "YP_MASTER_NAME";
+ key.size = sizeof("YP_MASTER_NAME") - 1;
+ data.data = mastername;
+ data.size = strlen(mastername);
+ yp_put_record(dbp, &key, &data, 0);
+
+ key.data = "YP_LAST_MODIFIED";
+ key.size = sizeof("YP_LAST_MODIFIED") - 1;
+ snprintf(buf, sizeof(buf), "%jd", (intmax_t)time(NULL));
+ data.data = (char *)&buf;
+ data.size = strlen(buf);
+ yp_put_record(dbp, &key, &data, 0);
+
+ if (infilename) {
+ key.data = "YP_INPUT_FILE";
+ key.size = sizeof("YP_INPUT_FILE") - 1;
+ data.data = infilename;
+ data.size = strlen(infilename);
+ yp_put_record(dbp, &key, &data, 0);
+ }
+
+ if (outfilename) {
+ key.data = "YP_OUTPUT_FILE";
+ key.size = sizeof("YP_OUTPUT_FILE") - 1;
+ data.data = outfilename;
+ data.size = strlen(outfilename);
+ yp_put_record(dbp, &key, &data, 0);
+ }
+
+ if (domain) {
+ key.data = "YP_DOMAIN_NAME";
+ key.size = sizeof("YP_DOMAIN_NAME") - 1;
+ data.data = domain;
+ data.size = strlen(domain);
+ yp_put_record(dbp, &key, &data, 0);
+ }
+
+ while (fgets((char *)&buf, sizeof(buf), ifp)) {
+ char *sep = NULL;
+ int rval;
+
+ /* NUL terminate */
+ if ((sep = strchr(buf, '\n')))
+ *sep = '\0';
+
+ /* handle backslash line continuations */
+ while (buf[strlen(buf) - 1] == '\\') {
+ fgets((char *)&buf[strlen(buf) - 1],
+ sizeof(buf) - strlen(buf), ifp);
+ if ((sep = strchr(buf, '\n')))
+ *sep = '\0';
+ }
+
+ /* find the separation between the key and data */
+ if ((sep = strpbrk(buf, " \t")) == NULL) {
+ warnx("bad input -- no white space: %s", buf);
+ continue;
+ }
+
+ /* separate the strings */
+ keybuf = (char *)&buf;
+ datbuf = sep + 1;
+ *sep = '\0';
+
+ /* set datbuf to start at first non-whitespace character */
+ while (*datbuf == ' ' || *datbuf == '\t')
+ datbuf++;
+
+ /* Check for silliness. */
+ if (filter_plusminus) {
+ if (*keybuf == '+' || *keybuf == '-' ||
+ *datbuf == '+' || *datbuf == '-') {
+ warnx("bad character at "
+ "start of line: %s", buf);
+ continue;
+ }
+ }
+
+ if (strlen(keybuf) > YPMAXRECORD) {
+ warnx("key too long: %s", keybuf);
+ continue;
+ }
+
+ if (!strlen(keybuf)) {
+ warnx("no key -- check source file for blank lines");
+ continue;
+ }
+
+ if (strlen(datbuf) > YPMAXRECORD) {
+ warnx("data too long: %s", datbuf);
+ continue;
+ }
+
+ key.data = keybuf;
+ key.size = strlen(keybuf);
+ data.data = datbuf;
+ data.size = strlen(datbuf);
+
+ if ((rval = yp_put_record(dbp, &key, &data, 0)) != YP_TRUE) {
+ switch (rval) {
+ case YP_FALSE:
+ warnx("duplicate key '%s' - skipping", keybuf);
+ break;
+ case YP_BADDB:
+ default:
+ err(1,"failed to write new record - exiting");
+ break;
+ }
+ }
+
+ }
+
+ (void)(dbp->close)(dbp);
+
+doclear:
+ if (clear) {
+ char in = 0;
+ char *out = NULL;
+ int stat;
+ if ((stat = callrpc("localhost", YPPROG,YPVERS, YPPROC_CLEAR,
+ (xdrproc_t)xdr_void, &in,
+ (xdrproc_t)xdr_void, out)) != RPC_SUCCESS) {
+ warnx("failed to send 'clear' to local ypserv: %s",
+ clnt_sperrno((enum clnt_stat) stat));
+ }
+ }
+
+ exit(0);
+}
diff --git a/usr.sbin/ypbind/Makefile b/usr.sbin/ypbind/Makefile
new file mode 100644
index 0000000..8e9a231
--- /dev/null
+++ b/usr.sbin/ypbind/Makefile
@@ -0,0 +1,12 @@
+# from: @(#)Makefile 5.8 (Berkeley) 7/28/90
+# $FreeBSD$
+
+PROG= ypbind
+MAN= ypbind.8
+SRCS= ypbind.c yp_ping.c
+
+CFLAGS+= -DDAEMON
+
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ypbind/Makefile.depend b/usr.sbin/ypbind/Makefile.depend
new file mode 100644
index 0000000..c0b7a14
--- /dev/null
+++ b/usr.sbin/ypbind/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ypbind/yp_ping.c b/usr.sbin/ypbind/yp_ping.c
new file mode 100644
index 0000000..4e445ea
--- /dev/null
+++ b/usr.sbin/ypbind/yp_ping.c
@@ -0,0 +1,308 @@
+/*
+ * Copyright (c) 1996, 1997
+ * 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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) 2009, Sun Microsystems, 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:
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of Sun Microsystems, Inc. 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 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 HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (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 = "@(#)from: clnt_udp.c 1.39 87/08/11 Copyr 1984 Sun Micro";
+static char *sccsid = "@(#)from: clnt_udp.c 2.2 88/08/01 4.0 RPCSRC";
+#endif
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * clnt_udp.c, Implements a UDP/IP based, client side RPC.
+ *
+ * Copyright (C) 1984, Sun Microsystems, Inc.
+ */
+
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <rpc/rpc.h>
+#include <rpc/pmap_clnt.h>
+#include <rpc/pmap_prot.h>
+#include <rpcsvc/yp.h>
+#include <sys/types.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include <sys/signal.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+
+#include "yp_ping.h"
+
+/*
+ * pmap_getport.c
+ * Client interface to pmap rpc service.
+ *
+ * Copyright (C) 1984, Sun Microsystems, Inc.
+ */
+
+
+static struct timeval timeout = { 1, 0 };
+static struct timeval tottimeout = { 1, 0 };
+
+/*
+ * Find the mapped port for program,version.
+ * Calls the pmap service remotely to do the lookup.
+ * Returns 0 if no map exists.
+ */
+static u_short
+__pmap_getport(struct sockaddr_in *address, u_long program, u_long version,
+ u_int protocol)
+{
+ u_short port = 0;
+ int sock = -1;
+ register CLIENT *client;
+ struct pmap parms;
+
+ address->sin_port = htons(PMAPPORT);
+
+ client = clntudp_bufcreate(address, PMAPPROG,
+ PMAPVERS, timeout, &sock, RPCSMALLMSGSIZE, RPCSMALLMSGSIZE);
+ if (client != (CLIENT *)NULL) {
+ parms.pm_prog = program;
+ parms.pm_vers = version;
+ parms.pm_prot = protocol;
+ parms.pm_port = 0; /* not needed or used */
+ if (CLNT_CALL(client, PMAPPROC_GETPORT,
+ (xdrproc_t)xdr_pmap, &parms,
+ (xdrproc_t)xdr_u_short, &port,
+ tottimeout) != RPC_SUCCESS){
+ rpc_createerr.cf_stat = RPC_PMAPFAILURE;
+ clnt_geterr(client, &rpc_createerr.cf_error);
+ } else if (port == 0) {
+ rpc_createerr.cf_stat = RPC_PROGNOTREGISTERED;
+ }
+ CLNT_DESTROY(client);
+ }
+ if (sock != -1)
+ (void)close(sock);
+ address->sin_port = 0;
+ return (port);
+}
+
+/*
+ * Transmit to YPPROC_DOMAIN_NONACK, return immediately.
+ */
+static bool_t *
+ypproc_domain_nonack_2_send(domainname *argp, CLIENT *clnt)
+{
+ static bool_t clnt_res;
+ struct timeval TIMEOUT = { 0, 0 };
+
+ memset((char *)&clnt_res, 0, sizeof (clnt_res));
+ if (clnt_call(clnt, YPPROC_DOMAIN_NONACK,
+ (xdrproc_t) xdr_domainname, (caddr_t) argp,
+ (xdrproc_t) xdr_bool, (caddr_t) &clnt_res,
+ TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return (&clnt_res);
+}
+
+/*
+ * Receive response from YPPROC_DOMAIN_NONACK asynchronously.
+ */
+static bool_t *
+ypproc_domain_nonack_2_recv(domainname *argp, CLIENT *clnt)
+{
+ static bool_t clnt_res;
+ struct timeval TIMEOUT = { 0, 0 };
+
+ memset((char *)&clnt_res, 0, sizeof (clnt_res));
+ if (clnt_call(clnt, YPPROC_DOMAIN_NONACK,
+ (xdrproc_t) NULL, (caddr_t) argp,
+ (xdrproc_t) xdr_bool, (caddr_t) &clnt_res,
+ TIMEOUT) != RPC_SUCCESS) {
+ return (NULL);
+ }
+ return (&clnt_res);
+}
+
+/*
+ * "We have the machine that goes 'ping!'" -- Monty Python
+ *
+ * This function blasts packets at the YPPROC_DOMAIN_NONACK procedures
+ * of the NIS servers listed in restricted_addrs structure.
+ * Whoever replies the fastest becomes our chosen server.
+ *
+ * Note: THIS IS NOT A BROADCAST OPERATION! We could use clnt_broadcast()
+ * for this, but that has the following problems:
+ * - We only get the address of the machine that replied in the
+ * 'eachresult' callback, and on multi-homed machines this can
+ * lead to confusion.
+ * - clnt_broadcast() only transmits to local networks, whereas with
+ * NIS+ you can have a perfectly good server located anywhere on or
+ * off the local network.
+ * - clnt_broadcast() blocks for an arbitrary amount of time which the
+ * caller can't control -- we want to avoid that.
+ *
+ * Also note that this has nothing to do with the NIS_PING procedure used
+ * for replica updates.
+ */
+
+struct ping_req {
+ struct sockaddr_in sin;
+ u_int32_t xid;
+};
+
+int
+__yp_ping(struct in_addr *restricted_addrs, int cnt, char *dom, short *port)
+{
+ struct timeval tv = { 5, 0 };
+ struct ping_req **reqs;
+ unsigned long i;
+ int async;
+ struct sockaddr_in sin, *any = NULL;
+ struct netbuf addr;
+ int winner = -1;
+ u_int32_t xid_seed, xid_lookup;
+ int sock, dontblock = 1;
+ CLIENT *clnt;
+ char *foo = dom;
+ int validsrvs = 0;
+
+ /* Set up handles. */
+ reqs = calloc(1, sizeof(struct ping_req *) * cnt);
+ xid_seed = time(NULL) ^ getpid();
+
+ for (i = 0; i < cnt; i++) {
+ bzero((char *)&sin, sizeof(sin));
+ sin.sin_family = AF_INET;
+ bcopy((char *)&restricted_addrs[i],
+ (char *)&sin.sin_addr, sizeof(struct in_addr));
+ sin.sin_port = htons(__pmap_getport(&sin, YPPROG,
+ YPVERS, IPPROTO_UDP));
+ if (sin.sin_port == 0)
+ continue;
+ reqs[i] = calloc(1, sizeof(struct ping_req));
+ bcopy((char *)&sin, (char *)&reqs[i]->sin, sizeof(sin));
+ any = &reqs[i]->sin;
+ reqs[i]->xid = xid_seed;
+ xid_seed++;
+ validsrvs++;
+ }
+
+ /* Make sure at least one server was assigned */
+ if (!validsrvs) {
+ free(reqs);
+ return(-1);
+ }
+
+ /* Create RPC handle */
+ sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ clnt = clntudp_create(any, YPPROG, YPVERS, tv, &sock);
+ if (clnt == NULL) {
+ close(sock);
+ for (i = 0; i < cnt; i++)
+ if (reqs[i] != NULL)
+ free(reqs[i]);
+ free(reqs);
+ return(-1);
+ }
+ clnt->cl_auth = authunix_create_default();
+ tv.tv_sec = 0;
+
+ clnt_control(clnt, CLSET_TIMEOUT, (char *)&tv);
+ async = TRUE;
+ clnt_control(clnt, CLSET_ASYNC, (char *)&async);
+ ioctl(sock, FIONBIO, &dontblock);
+
+ /* Transmit */
+ for (i = 0; i < cnt; i++) {
+ if (reqs[i] != NULL) {
+ clnt_control(clnt, CLSET_XID, (char *)&reqs[i]->xid);
+ addr.len = sizeof(reqs[i]->sin);
+ addr.buf = (char *) &reqs[i]->sin;
+ clnt_control(clnt, CLSET_SVC_ADDR, &addr);
+ ypproc_domain_nonack_2_send(&foo, clnt);
+ }
+ }
+
+ /* Receive reply */
+ ypproc_domain_nonack_2_recv(&foo, clnt);
+
+ /* Got a winner -- look him up. */
+ clnt_control(clnt, CLGET_XID, (char *)&xid_lookup);
+ for (i = 0; i < cnt; i++) {
+ if (reqs[i] != NULL && reqs[i]->xid == xid_lookup) {
+ winner = i;
+ *port = reqs[i]->sin.sin_port;
+ }
+ }
+
+ /* Shut everything down */
+ auth_destroy(clnt->cl_auth);
+ clnt_destroy(clnt);
+ close(sock);
+
+ for (i = 0; i < cnt; i++)
+ if (reqs[i] != NULL)
+ free(reqs[i]);
+ free(reqs);
+
+ return(winner);
+}
diff --git a/usr.sbin/ypbind/yp_ping.h b/usr.sbin/ypbind/yp_ping.h
new file mode 100644
index 0000000..80e6a78
--- /dev/null
+++ b/usr.sbin/ypbind/yp_ping.h
@@ -0,0 +1,5 @@
+/*
+ * $FreeBSD$
+ */
+
+extern int __yp_ping(struct in_addr *, int, char *, short *);
diff --git a/usr.sbin/ypbind/ypbind.8 b/usr.sbin/ypbind/ypbind.8
new file mode 100644
index 0000000..acd20eb
--- /dev/null
+++ b/usr.sbin/ypbind/ypbind.8
@@ -0,0 +1,202 @@
+.\" Copyright (c) 1991, 1993, 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$
+.\"
+.Dd April 9, 1995
+.Dt YPBIND 8
+.Os
+.Sh NAME
+.Nm ypbind
+.Nd "NIS domain binding daemon"
+.Sh SYNOPSIS
+.Nm
+.Op Fl ypset
+.Op Fl ypsetme
+.Op Fl s
+.Op Fl m
+.Oo
+.Fl S
+.Sm off
+.Ar domainname , server1 , server2 , ...
+.Sm on
+.Oc
+.Sh DESCRIPTION
+The
+.Nm
+utility is the process that maintains NIS binding information.
+At startup,
+it searches for an NIS server responsible for serving the system's
+default domain (as set by the
+.Xr domainname 1
+command) using network broadcasts.
+Once it receives a reply,
+it will store the address of the server and other
+information in a special file located in
+.Pa /var/yp/binding .
+The NIS routines in the standard C library can then use this file
+when processing NIS requests.
+There may be several such files
+since it is possible for an NIS client to be bound to more than
+one domain.
+.Pp
+After a binding has been established,
+.Nm
+will send DOMAIN_NONACK requests to the NIS server at one minute
+intervals.
+If it fails to receive a reply to one of these requests,
+.Nm
+assumes that the server is no longer running and resumes its network
+broadcasts until another binding is established.
+The
+.Nm
+utility will also log warning messages using the
+.Xr syslog 3
+facility each time it detects that a server has stopped responding,
+as well as when it has bound to a new server.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl ypset
+It is possible to force
+.Nm
+to bind to a particular NIS server host for a given domain by using the
+.Xr ypset 8
+command.
+However,
+.Nm
+refuses YPBINDPROC_SETDOM requests by default since it has no way of
+knowing exactly who is sending them.
+Using the
+.Fl ypset
+flag causes
+.Nm
+to accept YPBINDPROC_SETDOM requests from any host.
+This option should only
+be used for diagnostic purposes and only for limited periods since allowing
+arbitrary users to reset the binding of an NIS client poses a severe
+security risk.
+.It Fl ypsetme
+This is similar to the
+.Fl ypset
+flag, except that it only permits YPBINDPROC_SETDOM requests to be processed
+if they originated from the local host.
+.It Fl s
+Cause
+.Nm
+to run in secure mode: it will refuse to bind to any NIS server
+that is not running as root (i.e., that is not using privileged
+TCP ports).
+.It Fl S Xo
+.Sm off
+.Ar domainname , server1 , server2 , server3 , ...
+.Sm on
+.Xc
+Allow the system administrator to lock
+.Nm
+to a particular
+domain and group of NIS servers.
+Up to ten servers can be specified.
+There must not be any spaces between the commas in the domain/server
+specification.
+This option is used to ensure that the system binds
+only to one domain and only to one of the specified servers, which
+is useful for systems that are both NIS servers and NIS
+clients: it provides a way to restrict what machines the system can
+bind to without the need for specifying the
+.Fl ypset
+or
+.Fl ypsetme
+options, which are often considered to be security holes.
+The specified
+servers must have valid entries in the local
+.Pa /etc/hosts
+file.
+IP addresses may be specified in place of hostnames.
+If
+.Nm
+cannot make sense out of the arguments, it will ignore
+the
+.Fl S
+flag and continue running normally.
+.Pp
+Note that
+.Nm
+will consider the domainname specified with the
+.Fl S
+flag to be the system default domain.
+.It Fl m
+Cause
+.Nm
+to use a 'many-cast' rather than a broadcast for choosing a server
+from the restricted mode server list.
+In many-cast mode,
+.Nm
+will transmit directly to the YPPROC_DOMAIN_NONACK procedure of the
+servers specified in the restricted list and bind to the server that
+responds the fastest.
+This mode of operation is useful for NIS clients on remote subnets
+where no local NIS servers are available.
+The
+.Fl m
+flag can only be used in conjunction with the
+.Fl S
+flag above (if used without the
+.Fl S
+flag, it has no effect).
+.El
+.Sh NOTES
+The
+.Nm
+utility will not make continuous attempts to keep secondary domains bound.
+If a server for a secondary domain fails to respond to a ping,
+.Nm
+will broadcast for a new server only once before giving up.
+If a
+client program attempts to reference the unbound domain,
+.Nm
+will try broadcasting again.
+By contrast,
+.Nm
+will automatically maintain a binding for the default domain whether
+client programs reference it ot not.
+.Sh FILES
+.Bl -tag -width /etc/rc.conf -compact
+.It Pa /var/yp/binding/[domainname].[version]
+the files used to hold binding information for each NIS domain
+.It Pa /etc/rc.conf
+system configuration file where the system default domain and
+ypbind startup options are specified
+.El
+.Sh SEE ALSO
+.Xr domainname 1 ,
+.Xr syslog 3 ,
+.Xr yp 8 ,
+.Xr ypserv 8 ,
+.Xr ypset 8
+.Sh AUTHORS
+.An Theo de Raadt Aq Mt deraadt@fsa.ca
diff --git a/usr.sbin/ypbind/ypbind.c b/usr.sbin/ypbind/ypbind.c
new file mode 100644
index 0000000..39bdd74
--- /dev/null
+++ b/usr.sbin/ypbind/ypbind.c
@@ -0,0 +1,1010 @@
+/*
+ * Copyright (c) 1992/3 Theo de Raadt <deraadt@fsa.ca>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/signal.h>
+#include <sys/socket.h>
+#include <sys/file.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <ctype.h>
+#include <dirent.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 <rpc/rpc.h>
+#include <rpc/xdr.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <rpc/pmap_clnt.h>
+#include <rpc/pmap_prot.h>
+#include <rpc/pmap_rmt.h>
+#include <rpc/rpc_com.h>
+#include <rpcsvc/yp.h>
+#include <rpcsvc/ypclnt.h>
+#include "yp_ping.h"
+
+#ifndef BINDINGDIR
+#define BINDINGDIR "/var/yp/binding"
+#endif
+
+#ifndef YPBINDLOCK
+#define YPBINDLOCK "/var/run/ypbind.lock"
+#endif
+
+struct _dom_binding {
+ struct _dom_binding *dom_pnext;
+ char dom_domain[YPMAXDOMAIN + 1];
+ struct sockaddr_in dom_server_addr;
+ long int dom_vers;
+ int dom_lockfd;
+ int dom_alive;
+ int dom_broadcast_pid;
+ int dom_pipe_fds[2];
+ int dom_default;
+};
+
+#define READFD ypdb->dom_pipe_fds[0]
+#define WRITEFD ypdb->dom_pipe_fds[1]
+#define BROADFD broad_domain->dom_pipe_fds[1]
+
+extern bool_t xdr_domainname(), xdr_ypbind_resp();
+extern bool_t xdr_ypreq_key(), xdr_ypresp_val();
+extern bool_t xdr_ypbind_setdom();
+
+void checkwork(void);
+void *ypbindproc_null_2_yp(SVCXPRT *, void *, CLIENT *);
+void *ypbindproc_setdom_2_yp(SVCXPRT *, struct ypbind_setdom *, CLIENT *);
+void rpc_received(char *, struct sockaddr_in *, int);
+void broadcast(struct _dom_binding *);
+int ping(struct _dom_binding *);
+int tell_parent(char *, struct sockaddr_in *);
+void handle_children(struct _dom_binding *);
+void reaper(int);
+void terminate(int);
+void yp_restricted_mode(char *);
+int verify(struct in_addr);
+
+static char *domain_name;
+static struct _dom_binding *ypbindlist;
+static struct _dom_binding *broad_domain;
+
+#define YPSET_NO 0
+#define YPSET_LOCAL 1
+#define YPSET_ALL 2
+static int ypsetmode = YPSET_NO;
+static int ypsecuremode = 0;
+static int ppid;
+
+#define NOT_RESPONDING_HYSTERESIS 10
+static int not_responding_count = 0;
+
+/*
+ * Special restricted mode variables: when in restricted mode, only the
+ * specified restricted_domain will be bound, and only the servers listed
+ * in restricted_addrs will be used for binding.
+ */
+#define RESTRICTED_SERVERS 10
+static int yp_restricted = 0;
+static int yp_manycast = 0;
+static struct in_addr restricted_addrs[RESTRICTED_SERVERS];
+
+/* No more than MAX_CHILDREN child broadcasters at a time. */
+#ifndef MAX_CHILDREN
+#define MAX_CHILDREN 5
+#endif
+/* No more than MAX_DOMAINS simultaneous domains */
+#ifndef MAX_DOMAINS
+#define MAX_DOMAINS 200
+#endif
+/* RPC timeout value */
+#ifndef FAIL_THRESHOLD
+#define FAIL_THRESHOLD 20
+#endif
+
+/* Number of times to fish for a response froma particular set of hosts */
+#ifndef MAX_RETRIES
+#define MAX_RETRIES 30
+#endif
+
+static int retries = 0;
+static int children = 0;
+static int domains = 0;
+static int yplockfd;
+static fd_set fdsr;
+
+static SVCXPRT *udptransp, *tcptransp;
+
+void *
+ypbindproc_null_2_yp(SVCXPRT *transp, void *argp, CLIENT *clnt)
+{
+ static char res;
+
+ bzero(&res, sizeof(res));
+ return &res;
+}
+
+static struct ypbind_resp *
+ypbindproc_domain_2_yp(SVCXPRT *transp, domainname *argp, CLIENT *clnt)
+{
+ static struct ypbind_resp res;
+ struct _dom_binding *ypdb;
+ char path[MAXPATHLEN];
+
+ bzero(&res, sizeof res);
+ res.ypbind_status = YPBIND_FAIL_VAL;
+ res.ypbind_resp_u.ypbind_error = YPBIND_ERR_NOSERV;
+
+ if (strchr(*argp, '/')) {
+ syslog(LOG_WARNING, "Domain name '%s' has embedded slash -- \
+rejecting.", *argp);
+ return(&res);
+ }
+
+ for (ypdb = ypbindlist; ypdb; ypdb = ypdb->dom_pnext) {
+ if (strcmp(ypdb->dom_domain, *argp) == 0)
+ break;
+ }
+
+ if (ypdb == NULL) {
+ if (yp_restricted) {
+ syslog(LOG_NOTICE, "Running in restricted mode -- request to bind domain \"%s\" rejected.\n", *argp);
+ return (&res);
+ }
+
+ if (domains >= MAX_DOMAINS) {
+ syslog(LOG_WARNING, "domain limit (%d) exceeded",
+ MAX_DOMAINS);
+ res.ypbind_resp_u.ypbind_error = YPBIND_ERR_RESC;
+ return (&res);
+ }
+ ypdb = malloc(sizeof *ypdb);
+ if (ypdb == NULL) {
+ syslog(LOG_WARNING, "malloc: %m");
+ res.ypbind_resp_u.ypbind_error = YPBIND_ERR_RESC;
+ return (&res);
+ }
+ bzero(ypdb, sizeof *ypdb);
+ strncpy(ypdb->dom_domain, *argp, sizeof ypdb->dom_domain);
+ ypdb->dom_vers = YPVERS;
+ ypdb->dom_alive = 0;
+ ypdb->dom_default = 0;
+ ypdb->dom_lockfd = -1;
+ sprintf(path, "%s/%s.%ld", BINDINGDIR,
+ ypdb->dom_domain, ypdb->dom_vers);
+ unlink(path);
+ ypdb->dom_pnext = ypbindlist;
+ ypbindlist = ypdb;
+ domains++;
+ }
+
+ if (ping(ypdb)) {
+ return (&res);
+ }
+
+ res.ypbind_status = YPBIND_SUCC_VAL;
+ res.ypbind_resp_u.ypbind_error = 0; /* Success */
+ memcpy(&res.ypbind_resp_u.ypbind_bindinfo.ypbind_binding_addr,
+ &ypdb->dom_server_addr.sin_addr.s_addr, sizeof(u_int32_t));
+ memcpy(&res.ypbind_resp_u.ypbind_bindinfo.ypbind_binding_port,
+ &ypdb->dom_server_addr.sin_port, sizeof(u_short));
+ /*printf("domain %s at %s/%d\n", ypdb->dom_domain,
+ inet_ntoa(ypdb->dom_server_addr.sin_addr),
+ ntohs(ypdb->dom_server_addr.sin_port));*/
+ return (&res);
+}
+
+void *
+ypbindproc_setdom_2_yp(SVCXPRT *transp, ypbind_setdom *argp, CLIENT *clnt)
+{
+ struct sockaddr_in *fromsin, bindsin;
+ static char *result = NULL;
+
+ if (strchr(argp->ypsetdom_domain, '/')) {
+ syslog(LOG_WARNING, "Domain name '%s' has embedded slash -- \
+rejecting.", argp->ypsetdom_domain);
+ return(NULL);
+ }
+ fromsin = svc_getcaller(transp);
+
+ switch (ypsetmode) {
+ case YPSET_LOCAL:
+ if (fromsin->sin_addr.s_addr != htonl(INADDR_LOOPBACK)) {
+ svcerr_noprog(transp);
+ return(NULL);
+ }
+ break;
+ case YPSET_ALL:
+ break;
+ case YPSET_NO:
+ default:
+ svcerr_noprog(transp);
+ return(NULL);
+ }
+
+ if (ntohs(fromsin->sin_port) >= IPPORT_RESERVED) {
+ svcerr_noprog(transp);
+ return(NULL);
+ }
+
+ if (argp->ypsetdom_vers != YPVERS) {
+ svcerr_noprog(transp);
+ return(NULL);
+ }
+
+ bzero(&bindsin, sizeof bindsin);
+ bindsin.sin_family = AF_INET;
+ memcpy(&bindsin.sin_addr.s_addr,
+ &argp->ypsetdom_binding.ypbind_binding_addr,
+ sizeof(u_int32_t));
+ memcpy(&bindsin.sin_port,
+ &argp->ypsetdom_binding.ypbind_binding_port,
+ sizeof(u_short));
+ rpc_received(argp->ypsetdom_domain, &bindsin, 1);
+
+ return((void *) &result);
+}
+
+void
+ypbindprog_2(struct svc_req *rqstp, register SVCXPRT *transp)
+{
+ union {
+ domainname ypbindproc_domain_2_arg;
+ struct ypbind_setdom ypbindproc_setdom_2_arg;
+ } argument;
+ struct authunix_parms *creds;
+ char *result;
+ bool_t (*xdr_argument)(), (*xdr_result)();
+ char *(*local)();
+
+ switch (rqstp->rq_proc) {
+ case YPBINDPROC_NULL:
+ xdr_argument = xdr_void;
+ xdr_result = xdr_void;
+ local = (char *(*)()) ypbindproc_null_2_yp;
+ break;
+
+ case YPBINDPROC_DOMAIN:
+ xdr_argument = xdr_domainname;
+ xdr_result = xdr_ypbind_resp;
+ local = (char *(*)()) ypbindproc_domain_2_yp;
+ break;
+
+ case YPBINDPROC_SETDOM:
+ switch (rqstp->rq_cred.oa_flavor) {
+ case AUTH_UNIX:
+ creds = (struct authunix_parms *)rqstp->rq_clntcred;
+ if (creds->aup_uid != 0) {
+ svcerr_auth(transp, AUTH_BADCRED);
+ return;
+ }
+ break;
+ default:
+ svcerr_auth(transp, AUTH_TOOWEAK);
+ return;
+ }
+
+ xdr_argument = xdr_ypbind_setdom;
+ xdr_result = xdr_void;
+ local = (char *(*)()) ypbindproc_setdom_2_yp;
+ break;
+
+ default:
+ svcerr_noproc(transp);
+ return;
+ }
+ bzero(&argument, sizeof(argument));
+ if (!svc_getargs(transp, (xdrproc_t)xdr_argument, &argument)) {
+ svcerr_decode(transp);
+ return;
+ }
+ result = (*local)(transp, &argument, rqstp);
+ if (result != NULL &&
+ !svc_sendreply(transp, (xdrproc_t)xdr_result, result)) {
+ svcerr_systemerr(transp);
+ }
+ return;
+}
+
+/* Jack the reaper */
+void
+reaper(int sig)
+{
+ int st;
+
+ while (wait3(&st, WNOHANG, NULL) > 0)
+ children--;
+}
+
+void
+terminate(int sig)
+{
+ struct _dom_binding *ypdb;
+ char path[MAXPATHLEN];
+
+ if (ppid != getpid())
+ exit(0);
+
+ for (ypdb = ypbindlist; ypdb; ypdb = ypdb->dom_pnext) {
+ close(ypdb->dom_lockfd);
+ if (ypdb->dom_broadcast_pid)
+ kill(ypdb->dom_broadcast_pid, SIGINT);
+ sprintf(path, "%s/%s.%ld", BINDINGDIR,
+ ypdb->dom_domain, ypdb->dom_vers);
+ unlink(path);
+ }
+ close(yplockfd);
+ unlink(YPBINDLOCK);
+ pmap_unset(YPBINDPROG, YPBINDVERS);
+ exit(0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct timeval tv;
+ int i;
+ DIR *dird;
+ struct dirent *dirp;
+ struct _dom_binding *ypdb, *next;
+
+ /* Check that another ypbind isn't already running. */
+ if ((yplockfd = (open(YPBINDLOCK, O_RDONLY|O_CREAT, 0444))) == -1)
+ err(1, "%s", YPBINDLOCK);
+
+ if (flock(yplockfd, LOCK_EX|LOCK_NB) == -1 && errno == EWOULDBLOCK)
+ errx(1, "another ypbind is already running. Aborting");
+
+ /* XXX domainname will be overridden if we use restricted mode */
+ yp_get_default_domain(&domain_name);
+ if (domain_name[0] == '\0')
+ errx(1, "domainname not set. Aborting");
+
+ for (i = 1; i<argc; i++) {
+ if (strcmp("-ypset", argv[i]) == 0)
+ ypsetmode = YPSET_ALL;
+ else if (strcmp("-ypsetme", argv[i]) == 0)
+ ypsetmode = YPSET_LOCAL;
+ else if (strcmp("-s", argv[i]) == 0)
+ ypsecuremode++;
+ else if (strcmp("-S", argv[i]) == 0 && argc > i)
+ yp_restricted_mode(argv[++i]);
+ else if (strcmp("-m", argv[i]) == 0)
+ yp_manycast++;
+ else
+ errx(1, "unknown option: %s", argv[i]);
+ }
+
+ /* blow away everything in BINDINGDIR (if it exists) */
+
+ if ((dird = opendir(BINDINGDIR)) != NULL) {
+ char path[MAXPATHLEN];
+ while ((dirp = readdir(dird)) != NULL)
+ if (strcmp(dirp->d_name, ".") &&
+ strcmp(dirp->d_name, "..")) {
+ sprintf(path,"%s/%s",BINDINGDIR,dirp->d_name);
+ unlink(path);
+ }
+ closedir(dird);
+ }
+
+#ifdef DAEMON
+ if (daemon(0,0))
+ err(1, "fork");
+#endif
+
+ pmap_unset(YPBINDPROG, YPBINDVERS);
+
+ udptransp = svcudp_create(RPC_ANYSOCK);
+ if (udptransp == NULL)
+ errx(1, "cannot create udp service");
+ if (!svc_register(udptransp, YPBINDPROG, YPBINDVERS, ypbindprog_2,
+ IPPROTO_UDP))
+ errx(1, "unable to register (YPBINDPROG, YPBINDVERS, udp)");
+
+ tcptransp = svctcp_create(RPC_ANYSOCK, 0, 0);
+ if (tcptransp == NULL)
+ errx(1, "cannot create tcp service");
+
+ if (!svc_register(tcptransp, YPBINDPROG, YPBINDVERS, ypbindprog_2,
+ IPPROTO_TCP))
+ errx(1, "unable to register (YPBINDPROG, YPBINDVERS, tcp)");
+
+ /* build initial domain binding, make it "unsuccessful" */
+ ypbindlist = malloc(sizeof *ypbindlist);
+ if (ypbindlist == NULL)
+ errx(1, "malloc");
+ bzero(ypbindlist, sizeof *ypbindlist);
+ strncpy(ypbindlist->dom_domain, domain_name, sizeof ypbindlist->dom_domain);
+ ypbindlist->dom_vers = YPVERS;
+ ypbindlist->dom_alive = 0;
+ ypbindlist->dom_lockfd = -1;
+ ypbindlist->dom_default = 1;
+ domains++;
+
+ signal(SIGCHLD, reaper);
+ signal(SIGTERM, terminate);
+
+ ppid = getpid(); /* Remember who we are. */
+
+ openlog(argv[0], LOG_PID, LOG_DAEMON);
+
+ if (madvise(NULL, 0, MADV_PROTECT) != 0)
+ syslog(LOG_WARNING, "madvise(): %m");
+
+ /* Kick off the default domain */
+ broadcast(ypbindlist);
+
+ while (1) {
+ fdsr = svc_fdset;
+
+ tv.tv_sec = 60;
+ tv.tv_usec = 0;
+
+ switch (select(_rpc_dtablesize(), &fdsr, NULL, NULL, &tv)) {
+ case 0:
+ checkwork();
+ break;
+ case -1:
+ if (errno != EINTR)
+ syslog(LOG_WARNING, "select: %m");
+ break;
+ default:
+ for (ypdb = ypbindlist; ypdb; ypdb = next) {
+ next = ypdb->dom_pnext;
+ if (READFD > 0 && FD_ISSET(READFD, &fdsr)) {
+ handle_children(ypdb);
+ if (children == (MAX_CHILDREN - 1))
+ checkwork();
+ }
+ }
+ svc_getreqset(&fdsr);
+ break;
+ }
+ }
+
+ /* NOTREACHED */
+ exit(1);
+}
+
+void
+checkwork(void)
+{
+ struct _dom_binding *ypdb;
+
+ for (ypdb = ypbindlist; ypdb; ypdb = ypdb->dom_pnext)
+ ping(ypdb);
+}
+
+/* The clnt_broadcast() callback mechanism sucks. */
+
+/*
+ * Receive results from broadcaster. Don't worry about passing
+ * bogus info to rpc_received() -- it can handle it. Note that we
+ * must be sure to invalidate the dom_pipe_fds descriptors here:
+ * since descriptors can be re-used, we have to make sure we
+ * don't mistake one of the RPC descriptors for one of the pipes.
+ * What's weird is that forgetting to invalidate the pipe descriptors
+ * doesn't always result in an error (otherwise I would have caught
+ * the mistake much sooner), even though logically it should.
+ */
+void
+handle_children(struct _dom_binding *ypdb)
+{
+ char buf[YPMAXDOMAIN + 1];
+ struct sockaddr_in addr;
+ int d = 0, a = 0;
+ struct _dom_binding *y, *prev = NULL;
+ char path[MAXPATHLEN];
+
+ if ((d = read(READFD, &buf, sizeof(buf))) <= 0)
+ syslog(LOG_WARNING, "could not read from child: %m");
+
+ if ((a = read(READFD, &addr, sizeof(struct sockaddr_in))) < 0)
+ syslog(LOG_WARNING, "could not read from child: %m");
+
+ close(READFD);
+ FD_CLR(READFD, &fdsr);
+ FD_CLR(READFD, &svc_fdset);
+ READFD = WRITEFD = -1;
+ if (d > 0 && a > 0)
+ rpc_received(buf, &addr, 0);
+ else {
+ for (y = ypbindlist; y; y = y->dom_pnext) {
+ if (y == ypdb)
+ break;
+ prev = y;
+ }
+ switch (ypdb->dom_default) {
+ case 0:
+ if (prev == NULL)
+ ypbindlist = y->dom_pnext;
+ else
+ prev->dom_pnext = y->dom_pnext;
+ sprintf(path, "%s/%s.%ld", BINDINGDIR,
+ ypdb->dom_domain, YPVERS);
+ close(ypdb->dom_lockfd);
+ unlink(path);
+ free(ypdb);
+ domains--;
+ return;
+ case 1:
+ ypdb->dom_broadcast_pid = 0;
+ ypdb->dom_alive = 0;
+ broadcast(ypdb);
+ return;
+ default:
+ break;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Send our dying words back to our parent before we perish.
+ */
+int
+tell_parent(char *dom, struct sockaddr_in *addr)
+{
+ char buf[YPMAXDOMAIN + 1];
+ struct timeval timeout;
+ fd_set fds;
+
+ timeout.tv_sec = 5;
+ timeout.tv_usec = 0;
+
+ sprintf(buf, "%s", broad_domain->dom_domain);
+ if (write(BROADFD, &buf, sizeof(buf)) < 0)
+ return(1);
+
+ /*
+ * Stay in sync with parent: wait for it to read our first
+ * message before sending the second.
+ */
+
+ FD_ZERO(&fds);
+ FD_SET(BROADFD, &fds);
+ if (select(FD_SETSIZE, NULL, &fds, NULL, &timeout) == -1)
+ return(1);
+ if (FD_ISSET(BROADFD, &fds)) {
+ if (write(BROADFD, addr, sizeof(struct sockaddr_in)) < 0)
+ return(1);
+ } else {
+ return(1);
+ }
+
+ close(BROADFD);
+ return (0);
+}
+
+static bool_t
+broadcast_result(bool_t *out, struct sockaddr_in *addr)
+{
+ if (retries >= MAX_RETRIES) {
+ bzero(addr, sizeof(struct sockaddr_in));
+ if (tell_parent(broad_domain->dom_domain, addr))
+ syslog(LOG_WARNING, "lost connection to parent");
+ return (TRUE);
+ }
+
+ if (yp_restricted && verify(addr->sin_addr)) {
+ retries++;
+ syslog(LOG_NOTICE, "NIS server at %s not in restricted mode access list -- rejecting.\n",inet_ntoa(addr->sin_addr));
+ return (FALSE);
+ } else {
+ if (tell_parent(broad_domain->dom_domain, addr))
+ syslog(LOG_WARNING, "lost connection to parent");
+ return (TRUE);
+ }
+}
+
+/*
+ * The right way to send RPC broadcasts.
+ * Use the clnt_broadcast() RPC service. Unfortunately, clnt_broadcast()
+ * blocks while waiting for replies, so we have to fork off separate
+ * broadcaster processes that do the waiting and then transmit their
+ * results back to the parent for processing. We also have to remember
+ * to save the name of the domain we're trying to bind in a global
+ * variable since clnt_broadcast() provides no way to pass things to
+ * the 'eachresult' callback function.
+ */
+void
+broadcast(struct _dom_binding *ypdb)
+{
+ bool_t out = FALSE;
+ enum clnt_stat stat;
+
+ if (children >= MAX_CHILDREN || ypdb->dom_broadcast_pid)
+ return;
+
+ if (pipe(ypdb->dom_pipe_fds) < 0) {
+ syslog(LOG_WARNING, "pipe: %m");
+ return;
+ }
+
+ if (ypdb->dom_vers == -1 && (long)ypdb->dom_server_addr.sin_addr.s_addr) {
+ if (not_responding_count++ >= NOT_RESPONDING_HYSTERESIS) {
+ not_responding_count = NOT_RESPONDING_HYSTERESIS;
+ syslog(LOG_WARNING, "NIS server [%s] for domain \"%s\" not responding",
+ inet_ntoa(ypdb->dom_server_addr.sin_addr), ypdb->dom_domain);
+ }
+ }
+
+ broad_domain = ypdb;
+ flock(ypdb->dom_lockfd, LOCK_UN);
+
+ switch ((ypdb->dom_broadcast_pid = fork())) {
+ case 0:
+ close(READFD);
+ signal(SIGCHLD, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ break;
+ case -1:
+ syslog(LOG_WARNING, "fork: %m");
+ close(READFD);
+ close(WRITEFD);
+ return;
+ default:
+ close(WRITEFD);
+ FD_SET(READFD, &svc_fdset);
+ children++;
+ return;
+ }
+
+ /* Release all locks before doing anything else. */
+ while (ypbindlist) {
+ close(ypbindlist->dom_lockfd);
+ ypbindlist = ypbindlist->dom_pnext;
+ }
+ close(yplockfd);
+
+ /*
+ * Special 'many-cast' behavior. If we're in restricted mode,
+ * we have a list of possible server addresses to try. What
+ * we can do is transmit to each ypserv's YPPROC_DOMAIN_NONACK
+ * procedure and time the replies. Whoever replies fastest
+ * gets to be our server. Note that this is not a broadcast
+ * operation: we transmit uni-cast datagrams only.
+ */
+ if (yp_restricted && yp_manycast) {
+ short port;
+ int i;
+ struct sockaddr_in sin;
+
+ i = __yp_ping(restricted_addrs, yp_restricted,
+ ypdb->dom_domain, &port);
+ if (i == -1) {
+ bzero(&ypdb->dom_server_addr,
+ sizeof(struct sockaddr_in));
+ if (tell_parent(ypdb->dom_domain,
+ &ypdb->dom_server_addr))
+ syslog(LOG_WARNING, "lost connection to parent");
+ } else {
+ bzero(&sin, sizeof(struct sockaddr_in));
+ bcopy(&restricted_addrs[i],
+ &sin.sin_addr, sizeof(struct in_addr));
+ sin.sin_family = AF_INET;
+ sin.sin_port = port;
+ if (tell_parent(broad_domain->dom_domain, &sin))
+ syslog(LOG_WARNING,
+ "lost connection to parent");
+ }
+ _exit(0);
+ }
+
+ retries = 0;
+
+ {
+ char *ptr;
+
+ ptr = ypdb->dom_domain;
+ stat = clnt_broadcast(YPPROG, YPVERS, YPPROC_DOMAIN_NONACK,
+ (xdrproc_t)xdr_domainname, &ptr,
+ (xdrproc_t)xdr_bool, &out,
+ (resultproc_t)broadcast_result);
+ }
+
+ if (stat != RPC_SUCCESS) {
+ bzero(&ypdb->dom_server_addr,
+ sizeof(struct sockaddr_in));
+ if (tell_parent(ypdb->dom_domain, &ypdb->dom_server_addr))
+ syslog(LOG_WARNING, "lost connection to parent");
+ }
+
+ _exit(0);
+}
+
+/*
+ * The right way to check if a server is alive.
+ * Attempt to get a client handle pointing to the server and send a
+ * YPPROC_DOMAIN. If we can't get a handle or we get a reply of FALSE,
+ * we invalidate this binding entry and send out a broadcast to try to
+ * establish a new binding. Note that we treat non-default domains
+ * specially: once bound, we keep tabs on our server, but if it
+ * goes away and fails to respond after one round of broadcasting, we
+ * abandon it until a client specifically references it again. We make
+ * every effort to keep our default domain bound, however, since we
+ * need it to keep the system on its feet.
+ */
+int
+ping(struct _dom_binding *ypdb)
+{
+ bool_t out;
+ struct timeval interval, timeout;
+ enum clnt_stat stat;
+ int rpcsock = RPC_ANYSOCK;
+ CLIENT *client_handle;
+
+ interval.tv_sec = FAIL_THRESHOLD;
+ interval.tv_usec = 0;
+ timeout.tv_sec = FAIL_THRESHOLD;
+ timeout.tv_usec = 0;
+
+ if (ypdb->dom_broadcast_pid)
+ return(1);
+
+ if ((client_handle = clntudp_bufcreate(&ypdb->dom_server_addr,
+ YPPROG, YPVERS, interval, &rpcsock, RPCSMALLMSGSIZE,
+ RPCSMALLMSGSIZE)) == (CLIENT *)NULL) {
+ /* Can't get a handle: we're dead. */
+ ypdb->dom_alive = 0;
+ ypdb->dom_vers = -1;
+ broadcast(ypdb);
+ return(1);
+ }
+
+ {
+ char *ptr;
+
+ ptr = ypdb->dom_domain;
+
+ stat = clnt_call(client_handle, YPPROC_DOMAIN,
+ (xdrproc_t)xdr_domainname, &ptr,
+ (xdrproc_t)xdr_bool, &out, timeout);
+ if (stat != RPC_SUCCESS || out == FALSE) {
+ ypdb->dom_alive = 0;
+ ypdb->dom_vers = -1;
+ clnt_destroy(client_handle);
+ broadcast(ypdb);
+ return(1);
+ }
+ }
+
+ clnt_destroy(client_handle);
+ return(0);
+}
+
+void
+rpc_received(char *dom, struct sockaddr_in *raddrp, int force)
+{
+ struct _dom_binding *ypdb, *prev = NULL;
+ struct iovec iov[2];
+ struct ypbind_resp ybr;
+ char path[MAXPATHLEN];
+ int fd;
+
+ /*printf("returned from %s/%d about %s\n", inet_ntoa(raddrp->sin_addr),
+ ntohs(raddrp->sin_port), dom);*/
+
+ if (dom == NULL)
+ return;
+
+ for (ypdb = ypbindlist; ypdb; ypdb = ypdb->dom_pnext) {
+ if (strcmp(ypdb->dom_domain, dom) == 0)
+ break;
+ prev = ypdb;
+ }
+
+ if (ypdb && force) {
+ if (ypdb->dom_broadcast_pid) {
+ kill(ypdb->dom_broadcast_pid, SIGINT);
+ close(READFD);
+ FD_CLR(READFD, &fdsr);
+ FD_CLR(READFD, &svc_fdset);
+ READFD = WRITEFD = -1;
+ }
+ }
+
+ /* if in secure mode, check originating port number */
+ if ((ypsecuremode && (ntohs(raddrp->sin_port) >= IPPORT_RESERVED))) {
+ syslog(LOG_WARNING, "Rejected NIS server on [%s/%d] for domain %s.",
+ inet_ntoa(raddrp->sin_addr), ntohs(raddrp->sin_port),
+ dom);
+ if (ypdb != NULL) {
+ ypdb->dom_broadcast_pid = 0;
+ ypdb->dom_alive = 0;
+ }
+ return;
+ }
+
+ if (raddrp->sin_addr.s_addr == (long)0) {
+ switch (ypdb->dom_default) {
+ case 0:
+ if (prev == NULL)
+ ypbindlist = ypdb->dom_pnext;
+ else
+ prev->dom_pnext = ypdb->dom_pnext;
+ sprintf(path, "%s/%s.%ld", BINDINGDIR,
+ ypdb->dom_domain, YPVERS);
+ close(ypdb->dom_lockfd);
+ unlink(path);
+ free(ypdb);
+ domains--;
+ return;
+ case 1:
+ ypdb->dom_broadcast_pid = 0;
+ ypdb->dom_alive = 0;
+ broadcast(ypdb);
+ return;
+ default:
+ break;
+ }
+ }
+
+ if (ypdb == NULL) {
+ if (force == 0)
+ return;
+ ypdb = malloc(sizeof *ypdb);
+ if (ypdb == NULL) {
+ syslog(LOG_WARNING, "malloc: %m");
+ return;
+ }
+ bzero(ypdb, sizeof *ypdb);
+ strncpy(ypdb->dom_domain, dom, sizeof ypdb->dom_domain);
+ ypdb->dom_lockfd = -1;
+ ypdb->dom_default = 0;
+ ypdb->dom_pnext = ypbindlist;
+ ypbindlist = ypdb;
+ }
+
+ /* We've recovered from a crash: inform the world. */
+ if (ypdb->dom_vers == -1 && ypdb->dom_server_addr.sin_addr.s_addr) {
+ if (not_responding_count >= NOT_RESPONDING_HYSTERESIS) {
+ not_responding_count = 0;
+ syslog(LOG_WARNING, "NIS server [%s] for domain \"%s\" OK",
+ inet_ntoa(raddrp->sin_addr), ypdb->dom_domain);
+ }
+ }
+
+ bcopy(raddrp, &ypdb->dom_server_addr,
+ sizeof ypdb->dom_server_addr);
+
+ ypdb->dom_vers = YPVERS;
+ ypdb->dom_alive = 1;
+ ypdb->dom_broadcast_pid = 0;
+
+ if (ypdb->dom_lockfd != -1)
+ close(ypdb->dom_lockfd);
+
+ sprintf(path, "%s/%s.%ld", BINDINGDIR,
+ ypdb->dom_domain, ypdb->dom_vers);
+#ifdef O_SHLOCK
+ if ((fd = open(path, O_CREAT|O_SHLOCK|O_RDWR|O_TRUNC, 0644)) == -1) {
+ (void)mkdir(BINDINGDIR, 0755);
+ if ((fd = open(path, O_CREAT|O_SHLOCK|O_RDWR|O_TRUNC, 0644)) == -1)
+ return;
+ }
+#else
+ if ((fd = open(path, O_CREAT|O_RDWR|O_TRUNC, 0644)) == -1) {
+ (void)mkdir(BINDINGDIR, 0755);
+ if ((fd = open(path, O_CREAT|O_RDWR|O_TRUNC, 0644)) == -1)
+ return;
+ }
+ flock(fd, LOCK_SH);
+#endif
+
+ /*
+ * ok, if BINDINGDIR exists, and we can create the binding file,
+ * then write to it..
+ */
+ ypdb->dom_lockfd = fd;
+
+ iov[0].iov_base = (char *)&(udptransp->xp_port);
+ iov[0].iov_len = sizeof udptransp->xp_port;
+ iov[1].iov_base = (char *)&ybr;
+ iov[1].iov_len = sizeof ybr;
+
+ bzero(&ybr, sizeof ybr);
+ ybr.ypbind_status = YPBIND_SUCC_VAL;
+ memcpy(&ybr.ypbind_resp_u.ypbind_bindinfo.ypbind_binding_addr,
+ &raddrp->sin_addr.s_addr, sizeof(u_int32_t));
+ memcpy(&ybr.ypbind_resp_u.ypbind_bindinfo.ypbind_binding_port,
+ &raddrp->sin_port, sizeof(u_short));
+
+ if (writev(ypdb->dom_lockfd, iov, 2) != iov[0].iov_len + iov[1].iov_len) {
+ syslog(LOG_WARNING, "write: %m");
+ close(ypdb->dom_lockfd);
+ ypdb->dom_lockfd = -1;
+ return;
+ }
+}
+
+/*
+ * Check address against list of allowed servers. Return 0 if okay,
+ * 1 if not matched.
+ */
+int
+verify(struct in_addr addr)
+{
+ int i;
+
+ for (i = 0; i < RESTRICTED_SERVERS; i++)
+ if (!bcmp(&addr, &restricted_addrs[i], sizeof(struct in_addr)))
+ return(0);
+
+ return(1);
+}
+
+/*
+ * Try to set restricted mode. We default to normal mode if we can't
+ * resolve the specified hostnames.
+ */
+void
+yp_restricted_mode(char *args)
+{
+ struct hostent *h;
+ int i = 0;
+ char *s;
+
+ /* Find the restricted domain. */
+ if ((s = strsep(&args, ",")) == NULL)
+ return;
+ domain_name = s;
+
+ /* Get the addresses of the servers. */
+ while ((s = strsep(&args, ",")) != NULL && i < RESTRICTED_SERVERS) {
+ if ((h = gethostbyname(s)) == NULL)
+ return;
+ bcopy (h->h_addr_list[0], &restricted_addrs[i],
+ sizeof(struct in_addr));
+ i++;
+ }
+
+ /* ypset and ypsetme not allowed with restricted mode */
+ ypsetmode = YPSET_NO;
+
+ yp_restricted = i;
+ return;
+}
diff --git a/usr.sbin/ypldap/Makefile b/usr.sbin/ypldap/Makefile
new file mode 100644
index 0000000..1d3cabc
--- /dev/null
+++ b/usr.sbin/ypldap/Makefile
@@ -0,0 +1,20 @@
+# $OpenBSD: Makefile,v 1.8 2015/09/09 15:33:18 deraadt Exp $
+# $FreeBSD$
+
+PROG= ypldap
+SRCS= parse.y ypldap.c log.c \
+ ldapclient.c entries.c yp.c \
+ aldap.c ber.c \
+ ypldap_dns.c
+
+MAN= ypldap.8 ypldap.conf.5
+
+LIBADD= openbsd event util rpcsvc
+
+CFLAGS+=-I${.CURDIR}
+CFLAGS+=-I${.CURDIR}/../../contrib/pf/libevent
+CFLAGS+=-I${.CURDIR}/../../lib/libopenbsd
+
+WARNS= 2
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ypldap/aldap.c b/usr.sbin/ypldap/aldap.c
new file mode 100644
index 0000000..7062507
--- /dev/null
+++ b/usr.sbin/ypldap/aldap.c
@@ -0,0 +1,1272 @@
+/* $Id: aldap.c,v 1.30 2012/04/30 21:40:03 jmatthew Exp $ */
+/* $OpenBSD: aldap.c,v 1.30 2012/04/30 21:40:03 jmatthew Exp $ */
+/* $FreeBSD$ */
+
+/*
+ * Copyright (c) 2008 Alexander Schrijver <aschrijver@openbsd.org>
+ * Copyright (c) 2006, 2007 Marc Balmer <mbalmer@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 <errno.h>
+#include <inttypes.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "aldap.h"
+
+#if 0
+#define DEBUG
+#endif
+#define VERSION 3
+
+static struct ber_element *ldap_parse_search_filter(struct ber_element *,
+ char *);
+static struct ber_element *ldap_do_parse_search_filter(
+ struct ber_element *, char **);
+char **aldap_get_stringset(struct ber_element *);
+char *utoa(char *);
+char *parseval(char *, size_t);
+int aldap_create_page_control(struct ber_element *,
+ int, struct aldap_page_control *);
+
+#ifdef DEBUG
+void ldap_debug_elements(struct ber_element *);
+#endif
+
+#ifdef DEBUG
+#define DPRINTF(x...) printf(x)
+#define LDAP_DEBUG(x, y) do { fprintf(stderr, "*** " x "\n"); ldap_debug_elements(y); } while (0)
+#else
+#define DPRINTF(x...) do { } while (0)
+#define LDAP_DEBUG(x, y) do { } while (0)
+#endif
+
+int
+aldap_close(struct aldap *al)
+{
+ if (close(al->ber.fd) == -1)
+ return (-1);
+
+ ber_free(&al->ber);
+ free(al);
+
+ return (0);
+}
+
+struct aldap *
+aldap_init(int fd)
+{
+ struct aldap *a;
+
+ if ((a = calloc(1, sizeof(*a))) == NULL)
+ return NULL;
+ a->ber.fd = fd;
+
+ return a;
+}
+
+int
+aldap_bind(struct aldap *ldap, char *binddn, char *bindcred)
+{
+ struct ber_element *root = NULL, *elm;
+ int error;
+
+ if (binddn == NULL)
+ binddn = "";
+ if (bindcred == NULL)
+ bindcred = "";
+
+ if ((root = ber_add_sequence(NULL)) == NULL)
+ goto fail;
+
+ elm = ber_printf_elements(root, "d{tdsst", ++ldap->msgid, BER_CLASS_APP,
+ (unsigned long)LDAP_REQ_BIND, VERSION, binddn, bindcred,
+ BER_CLASS_CONTEXT, (unsigned long)LDAP_AUTH_SIMPLE);
+ if (elm == NULL)
+ goto fail;
+
+ LDAP_DEBUG("aldap_bind", root);
+
+ error = ber_write_elements(&ldap->ber, root);
+ ber_free_elements(root);
+ root = NULL;
+ if (error == -1)
+ goto fail;
+
+ return (ldap->msgid);
+fail:
+ if (root != NULL)
+ ber_free_elements(root);
+
+ ldap->err = ALDAP_ERR_OPERATION_FAILED;
+ return (-1);
+}
+
+int
+aldap_unbind(struct aldap *ldap)
+{
+ struct ber_element *root = NULL, *elm;
+ int error;
+
+ if ((root = ber_add_sequence(NULL)) == NULL)
+ goto fail;
+ elm = ber_printf_elements(root, "d{t", ++ldap->msgid, BER_CLASS_APP,
+ LDAP_REQ_UNBIND_30);
+ if (elm == NULL)
+ goto fail;
+
+ LDAP_DEBUG("aldap_unbind", root);
+
+ error = ber_write_elements(&ldap->ber, root);
+ ber_free_elements(root);
+ root = NULL;
+ if (error == -1)
+ goto fail;
+
+ return (ldap->msgid);
+fail:
+ if (root != NULL)
+ ber_free_elements(root);
+
+ ldap->err = ALDAP_ERR_OPERATION_FAILED;
+
+ return (-1);
+}
+
+int
+aldap_search(struct aldap *ldap, char *basedn, enum scope scope, char *filter,
+ char **attrs, int typesonly, int sizelimit, int timelimit,
+ struct aldap_page_control *page)
+{
+ struct ber_element *root = NULL, *ber, *c;
+ int i, error;
+
+ if ((root = ber_add_sequence(NULL)) == NULL)
+ goto fail;
+
+ ber = ber_printf_elements(root, "d{t", ++ldap->msgid, BER_CLASS_APP,
+ (unsigned long) LDAP_REQ_SEARCH);
+ if (ber == NULL) {
+ ldap->err = ALDAP_ERR_OPERATION_FAILED;
+ goto fail;
+ }
+
+ c = ber;
+ ber = ber_printf_elements(ber, "sEEddb", basedn, (long long)scope,
+ (long long)LDAP_DEREF_NEVER, sizelimit,
+ timelimit, typesonly);
+ if (ber == NULL) {
+ ldap->err = ALDAP_ERR_OPERATION_FAILED;
+ goto fail;
+ }
+
+ if ((ber = ldap_parse_search_filter(ber, filter)) == NULL) {
+ ldap->err = ALDAP_ERR_PARSER_ERROR;
+ goto fail;
+ }
+
+ if ((ber = ber_add_sequence(ber)) == NULL)
+ goto fail;
+ if (attrs != NULL)
+ for (i = 0; attrs[i] != NULL; i++) {
+ if ((ber = ber_add_string(ber, attrs[i])) == NULL)
+ goto fail;
+ }
+
+ aldap_create_page_control(c, 100, page);
+
+ LDAP_DEBUG("aldap_search", root);
+
+ error = ber_write_elements(&ldap->ber, root);
+ ber_free_elements(root);
+ root = NULL;
+ if (error == -1) {
+ ldap->err = ALDAP_ERR_OPERATION_FAILED;
+ goto fail;
+ }
+
+ return (ldap->msgid);
+
+fail:
+ if (root != NULL)
+ ber_free_elements(root);
+
+ return (-1);
+}
+
+int
+aldap_create_page_control(struct ber_element *elm, int size,
+ struct aldap_page_control *page)
+{
+ int len;
+ struct ber c;
+ struct ber_element *ber = NULL;
+
+ c.br_wbuf = NULL;
+ c.fd = -1;
+
+ ber = ber_add_sequence(NULL);
+
+ if (page == NULL) {
+ if (ber_printf_elements(ber, "ds", 50, "") == NULL)
+ goto fail;
+ } else {
+ if (ber_printf_elements(ber, "dx", 50, page->cookie,
+ page->cookie_len) == NULL)
+ goto fail;
+ }
+
+ if ((len = ber_write_elements(&c, ber)) < 1)
+ goto fail;
+ if (ber_printf_elements(elm, "{t{sx", 2, 0, LDAP_PAGED_OID,
+ c.br_wbuf, (size_t)len) == NULL)
+ goto fail;
+
+ ber_free_elements(ber);
+ ber_free(&c);
+ return len;
+fail:
+ if (ber != NULL)
+ ber_free_elements(ber);
+ ber_free(&c);
+
+ return (-1);
+}
+
+struct aldap_message *
+aldap_parse(struct aldap *ldap)
+{
+ int class;
+ unsigned long type;
+ long long msgid = 0;
+ struct aldap_message *m;
+ struct ber_element *a = NULL, *ep;
+
+ if ((m = calloc(1, sizeof(struct aldap_message))) == NULL)
+ return NULL;
+
+ if ((m->msg = ber_read_elements(&ldap->ber, NULL)) == NULL)
+ goto parsefail;
+
+ LDAP_DEBUG("message", m->msg);
+
+ if (ber_scanf_elements(m->msg, "{ite", &msgid, &class, &type, &a) != 0)
+ goto parsefail;
+ m->msgid = msgid;
+ m->message_type = type;
+ m->protocol_op = a;
+
+ switch (m->message_type) {
+ case LDAP_RES_BIND:
+ case LDAP_RES_MODIFY:
+ case LDAP_RES_ADD:
+ case LDAP_RES_DELETE:
+ case LDAP_RES_MODRDN:
+ case LDAP_RES_COMPARE:
+ case LDAP_RES_SEARCH_RESULT:
+ if (ber_scanf_elements(m->protocol_op, "{EeSeSe",
+ &m->body.res.rescode, &m->dn, &m->body.res.diagmsg, &a) != 0)
+ goto parsefail;
+ if (m->body.res.rescode == LDAP_REFERRAL)
+ if (ber_scanf_elements(a, "{e", &m->references) != 0)
+ goto parsefail;
+ if (m->msg->be_sub) {
+ for (ep = m->msg->be_sub; ep != NULL; ep = ep->be_next) {
+ ber_scanf_elements(ep, "t", &class, &type);
+ if (class == 2 && type == 0)
+ m->page = aldap_parse_page_control(ep->be_sub->be_sub,
+ ep->be_sub->be_sub->be_len);
+ }
+ } else
+ m->page = NULL;
+ break;
+ case LDAP_RES_SEARCH_ENTRY:
+ if (ber_scanf_elements(m->protocol_op, "{eS{e", &m->dn,
+ &m->body.search.attrs) != 0)
+ goto parsefail;
+ break;
+ case LDAP_RES_SEARCH_REFERENCE:
+ if (ber_scanf_elements(m->protocol_op, "{e", &m->references) != 0)
+ goto parsefail;
+ break;
+ }
+
+ return m;
+parsefail:
+ ldap->err = ALDAP_ERR_PARSER_ERROR;
+ aldap_freemsg(m);
+ return NULL;
+}
+
+struct aldap_page_control *
+aldap_parse_page_control(struct ber_element *control, size_t len)
+{
+ char *oid, *s;
+ char *encoded;
+ struct ber b;
+ struct ber_element *elm;
+ struct aldap_page_control *page;
+
+ b.br_wbuf = NULL;
+ b.fd = -1;
+ ber_scanf_elements(control, "ss", &oid, &encoded);
+ ber_set_readbuf(&b, encoded, control->be_next->be_len);
+ elm = ber_read_elements(&b, NULL);
+
+ if ((page = malloc(sizeof(struct aldap_page_control))) == NULL) {
+ if (elm != NULL)
+ ber_free_elements(elm);
+ ber_free(&b);
+ return NULL;
+ }
+
+ ber_scanf_elements(elm->be_sub, "is", &page->size, &s);
+ page->cookie_len = elm->be_sub->be_next->be_len;
+
+ if ((page->cookie = malloc(page->cookie_len)) == NULL) {
+ if (elm != NULL)
+ ber_free_elements(elm);
+ ber_free(&b);
+ free(page);
+ return NULL;
+ }
+ memcpy(page->cookie, s, page->cookie_len);
+
+ ber_free_elements(elm);
+ ber_free(&b);
+ return page;
+}
+
+void
+aldap_freepage(struct aldap_page_control *page)
+{
+ free(page->cookie);
+ free(page);
+}
+
+void
+aldap_freemsg(struct aldap_message *msg)
+{
+ if (msg->msg)
+ ber_free_elements(msg->msg);
+ free(msg);
+}
+
+int
+aldap_get_resultcode(struct aldap_message *msg)
+{
+ return msg->body.res.rescode;
+}
+
+char *
+aldap_get_dn(struct aldap_message *msg)
+{
+ char *dn;
+
+ if (msg->dn == NULL)
+ return NULL;
+
+ if (ber_get_string(msg->dn, &dn) == -1)
+ return NULL;
+
+ return utoa(dn);
+}
+
+char **
+aldap_get_references(struct aldap_message *msg)
+{
+ if (msg->references == NULL)
+ return NULL;
+ return aldap_get_stringset(msg->references);
+}
+
+void
+aldap_free_references(char **values)
+{
+ int i;
+
+ if (values == NULL)
+ return;
+
+ for (i = 0; values[i] != NULL; i++)
+ free(values[i]);
+
+ free(values);
+}
+
+char *
+aldap_get_diagmsg(struct aldap_message *msg)
+{
+ char *s;
+
+ if (msg->body.res.diagmsg == NULL)
+ return NULL;
+
+ if (ber_get_string(msg->body.res.diagmsg, &s) == -1)
+ return NULL;
+
+ return utoa(s);
+}
+
+int
+aldap_count_attrs(struct aldap_message *msg)
+{
+ int i;
+ struct ber_element *a;
+
+ if (msg->body.search.attrs == NULL)
+ return (-1);
+
+ for (i = 0, a = msg->body.search.attrs;
+ a != NULL && ber_get_eoc(a) != 0;
+ i++, a = a->be_next)
+ ;
+
+ return i;
+}
+
+int
+aldap_first_attr(struct aldap_message *msg, char **outkey, char ***outvalues)
+{
+ struct ber_element *b, *c;
+ char *key;
+ char **ret;
+
+ if (msg->body.search.attrs == NULL)
+ goto fail;
+
+ if (ber_scanf_elements(msg->body.search.attrs, "{s(e)}e",
+ &key, &b, &c) != 0)
+ goto fail;
+
+ msg->body.search.iter = msg->body.search.attrs->be_next;
+
+ if ((ret = aldap_get_stringset(b)) == NULL)
+ goto fail;
+
+ (*outvalues) = ret;
+ (*outkey) = utoa(key);
+
+ return (1);
+fail:
+ (*outkey) = NULL;
+ (*outvalues) = NULL;
+ return (-1);
+}
+
+int
+aldap_next_attr(struct aldap_message *msg, char **outkey, char ***outvalues)
+{
+ struct ber_element *a, *b;
+ char *key;
+ char **ret;
+
+ if (msg->body.search.iter == NULL)
+ goto notfound;
+
+ LDAP_DEBUG("attr", msg->body.search.iter);
+
+ if (ber_get_eoc(msg->body.search.iter) == 0)
+ goto notfound;
+
+ if (ber_scanf_elements(msg->body.search.iter, "{s(e)}e", &key, &a, &b)
+ != 0)
+ goto fail;
+
+ msg->body.search.iter = msg->body.search.iter->be_next;
+
+ if ((ret = aldap_get_stringset(a)) == NULL)
+ goto fail;
+
+ (*outvalues) = ret;
+ (*outkey) = utoa(key);
+
+ return (1);
+fail:
+notfound:
+ (*outkey) = NULL;
+ (*outvalues) = NULL;
+ return (-1);
+}
+
+int
+aldap_match_attr(struct aldap_message *msg, char *inkey, char ***outvalues)
+{
+ struct ber_element *a, *b;
+ char *descr = NULL;
+ char **ret;
+
+ if (msg->body.search.attrs == NULL)
+ goto fail;
+
+ LDAP_DEBUG("attr", msg->body.search.attrs);
+
+ for (a = msg->body.search.attrs;;) {
+ if (a == NULL)
+ goto notfound;
+ if (ber_get_eoc(a) == 0)
+ goto notfound;
+ if (ber_scanf_elements(a, "{s(e", &descr, &b) != 0)
+ goto fail;
+ if (strcasecmp(descr, inkey) == 0)
+ goto attrfound;
+ a = a->be_next;
+ }
+
+attrfound:
+ if ((ret = aldap_get_stringset(b)) == NULL)
+ goto fail;
+
+ (*outvalues) = ret;
+
+ return (1);
+fail:
+notfound:
+ (*outvalues) = NULL;
+ return (-1);
+}
+
+int
+aldap_free_attr(char **values)
+{
+ int i;
+
+ if (values == NULL)
+ return -1;
+
+ for (i = 0; values[i] != NULL; i++)
+ free(values[i]);
+
+ free(values);
+
+ return (1);
+}
+
+#if 0
+void
+aldap_free_url(struct aldap_url *lu)
+{
+ free(lu->buffer);
+ free(lu->filter);
+}
+
+int
+aldap_parse_url(char *url, struct aldap_url *lu)
+{
+ char *p, *forward, *forward2;
+ const char *errstr = NULL;
+ int i;
+
+ if ((lu->buffer = p = strdup(url)) == NULL)
+ return (-1);
+
+ /* protocol */
+ if (strncasecmp(LDAP_URL, p, strlen(LDAP_URL)) != 0)
+ goto fail;
+ lu->protocol = LDAP;
+ p += strlen(LDAP_URL);
+
+ /* host and optional port */
+ if ((forward = strchr(p, '/')) != NULL)
+ *forward = '\0';
+ /* find the optional port */
+ if ((forward2 = strchr(p, ':')) != NULL) {
+ *forward2 = '\0';
+ /* if a port is given */
+ if (*(forward2+1) != '\0') {
+#define PORT_MAX UINT16_MAX
+ lu->port = strtonum(++forward2, 0, PORT_MAX, &errstr);
+ if (errstr)
+ goto fail;
+ }
+ }
+ /* fail if no host is given */
+ if (strlen(p) == 0)
+ goto fail;
+ lu->host = p;
+ if (forward == NULL)
+ goto done;
+ /* p is assigned either a pointer to a character or to '\0' */
+ p = ++forward;
+ if (strlen(p) == 0)
+ goto done;
+
+ /* dn */
+ if ((forward = strchr(p, '?')) != NULL)
+ *forward = '\0';
+ lu->dn = p;
+ if (forward == NULL)
+ goto done;
+ /* p is assigned either a pointer to a character or to '\0' */
+ p = ++forward;
+ if (strlen(p) == 0)
+ goto done;
+
+ /* attributes */
+ if ((forward = strchr(p, '?')) != NULL)
+ *forward = '\0';
+ for (i = 0; i < MAXATTR; i++) {
+ if ((forward2 = strchr(p, ',')) == NULL) {
+ if (strlen(p) == 0)
+ break;
+ lu->attributes[i] = p;
+ break;
+ }
+ *forward2 = '\0';
+ lu->attributes[i] = p;
+ p = ++forward2;
+ }
+ if (forward == NULL)
+ goto done;
+ /* p is assigned either a pointer to a character or to '\0' */
+ p = ++forward;
+ if (strlen(p) == 0)
+ goto done;
+
+ /* scope */
+ if ((forward = strchr(p, '?')) != NULL)
+ *forward = '\0';
+ if (strcmp(p, "base") == 0)
+ lu->scope = LDAP_SCOPE_BASE;
+ else if (strcmp(p, "one") == 0)
+ lu->scope = LDAP_SCOPE_ONELEVEL;
+ else if (strcmp(p, "sub") == 0)
+ lu->scope = LDAP_SCOPE_SUBTREE;
+ else
+ goto fail;
+ if (forward == NULL)
+ goto done;
+ p = ++forward;
+ if (strlen(p) == 0)
+ goto done;
+
+ /* filter */
+ if (p)
+ lu->filter = p;
+done:
+ free(url);
+ return (1);
+fail:
+ free(lu->buffer);
+ lu->buffer = NULL;
+ return (-1);
+}
+
+int
+aldap_search_url(struct aldap *ldap, char *url, int typesonly, int sizelimit,
+ int timelimit)
+{
+ struct aldap_url *lu;
+
+ if ((lu = calloc(1, sizeof(*lu))) == NULL)
+ return (-1);
+
+ if (aldap_parse_url(url, lu))
+ goto fail;
+
+ if (aldap_search(ldap, lu->dn, lu->scope, lu->filter, lu->attributes,
+ typesonly, sizelimit, timelimit) == -1)
+ goto fail;
+
+ aldap_free_url(lu);
+ return (ldap->msgid);
+fail:
+ aldap_free_url(lu);
+ return (-1);
+}
+#endif /* 0 */
+
+/*
+ * internal functions
+ */
+
+char **
+aldap_get_stringset(struct ber_element *elm)
+{
+ struct ber_element *a;
+ int i;
+ char **ret;
+ char *s;
+
+ if (elm->be_type != BER_TYPE_OCTETSTRING)
+ return NULL;
+
+ for (a = elm, i = 1; i > 0 && a != NULL && a->be_type ==
+ BER_TYPE_OCTETSTRING; a = a->be_next, i++)
+ ;
+ if (i == 1)
+ return NULL;
+
+ if ((ret = calloc(i + 1, sizeof(char *))) == NULL)
+ return NULL;
+
+ for (a = elm, i = 0; a != NULL && a->be_type == BER_TYPE_OCTETSTRING;
+ a = a->be_next, i++) {
+
+ ber_get_string(a, &s);
+ ret[i] = utoa(s);
+ }
+ ret[i + 1] = NULL;
+
+ return ret;
+}
+
+/*
+ * Base case for ldap_do_parse_search_filter
+ *
+ * returns:
+ * struct ber_element *, ber_element tree
+ * NULL, parse failed
+ */
+static struct ber_element *
+ldap_parse_search_filter(struct ber_element *ber, char *filter)
+{
+ struct ber_element *elm;
+ char *cp;
+
+ cp = filter;
+
+ if (cp == NULL || *cp == '\0') {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ if ((elm = ldap_do_parse_search_filter(ber, &cp)) == NULL)
+ return (NULL);
+
+ if (*cp != '\0') {
+ ber_free_elements(elm);
+ ber_link_elements(ber, NULL);
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ return (elm);
+}
+
+/*
+ * Translate RFC4515 search filter string into ber_element tree
+ *
+ * returns:
+ * struct ber_element *, ber_element tree
+ * NULL, parse failed
+ *
+ * notes:
+ * when cp is passed to a recursive invocation, it is updated
+ * to point one character beyond the filter that was passed
+ * i.e., cp jumps to "(filter)" upon return
+ * ^
+ * goto's used to discriminate error-handling based on error type
+ * doesn't handle extended filters (yet)
+ *
+ */
+static struct ber_element *
+ldap_do_parse_search_filter(struct ber_element *prev, char **cpp)
+{
+ struct ber_element *elm, *root = NULL;
+ char *attr_desc, *attr_val, *parsed_val, *cp;
+ size_t len;
+ unsigned long type;
+
+ root = NULL;
+
+ /* cpp should pass in pointer to opening parenthesis of "(filter)" */
+ cp = *cpp;
+ if (*cp != '(')
+ goto syntaxfail;
+
+ switch (*++cp) {
+ case '&': /* AND */
+ case '|': /* OR */
+ if (*cp == '&')
+ type = LDAP_FILT_AND;
+ else
+ type = LDAP_FILT_OR;
+
+ if ((elm = ber_add_set(prev)) == NULL)
+ goto callfail;
+ root = elm;
+ ber_set_header(elm, BER_CLASS_CONTEXT, type);
+
+ if (*++cp != '(') /* opening `(` of filter */
+ goto syntaxfail;
+
+ while (*cp == '(') {
+ if ((elm =
+ ldap_do_parse_search_filter(elm, &cp)) == NULL)
+ goto bad;
+ }
+
+ if (*cp != ')') /* trailing `)` of filter */
+ goto syntaxfail;
+ break;
+
+ case '!': /* NOT */
+ if ((root = ber_add_sequence(prev)) == NULL)
+ goto callfail;
+ ber_set_header(root, BER_CLASS_CONTEXT, LDAP_FILT_NOT);
+
+ cp++; /* now points to sub-filter */
+ if ((elm = ldap_do_parse_search_filter(root, &cp)) == NULL)
+ goto bad;
+
+ if (*cp != ')') /* trailing `)` of filter */
+ goto syntaxfail;
+ break;
+
+ default: /* SIMPLE || PRESENCE */
+ attr_desc = cp;
+
+ len = strcspn(cp, "()<>~=");
+ cp += len;
+ switch (*cp) {
+ case '~':
+ type = LDAP_FILT_APPR;
+ cp++;
+ break;
+ case '<':
+ type = LDAP_FILT_LE;
+ cp++;
+ break;
+ case '>':
+ type = LDAP_FILT_GE;
+ cp++;
+ break;
+ case '=':
+ type = LDAP_FILT_EQ; /* assume EQ until disproven */
+ break;
+ case '(':
+ case ')':
+ default:
+ goto syntaxfail;
+ }
+ attr_val = ++cp;
+
+ /* presence filter */
+ if (strncmp(attr_val, "*)", 2) == 0) {
+ cp++; /* point to trailing `)` */
+ if ((root =
+ ber_add_nstring(prev, attr_desc, len)) == NULL)
+ goto bad;
+
+ ber_set_header(root, BER_CLASS_CONTEXT, LDAP_FILT_PRES);
+ break;
+ }
+
+ if ((root = ber_add_sequence(prev)) == NULL)
+ goto callfail;
+ ber_set_header(root, BER_CLASS_CONTEXT, type);
+
+ if ((elm = ber_add_nstring(root, attr_desc, len)) == NULL)
+ goto callfail;
+
+ len = strcspn(attr_val, "*)");
+ if (len == 0 && *cp != '*')
+ goto syntaxfail;
+ cp += len;
+ if (*cp == '\0')
+ goto syntaxfail;
+
+ if (*cp == '*') { /* substring filter */
+ int initial;
+
+ cp = attr_val;
+
+ ber_set_header(root, BER_CLASS_CONTEXT, LDAP_FILT_SUBS);
+
+ if ((elm = ber_add_sequence(elm)) == NULL)
+ goto callfail;
+
+ for (initial = 1;; cp++, initial = 0) {
+ attr_val = cp;
+
+ len = strcspn(attr_val, "*)");
+ if (len == 0) {
+ if (*cp == ')')
+ break;
+ else
+ continue;
+ }
+ cp += len;
+ if (*cp == '\0')
+ goto syntaxfail;
+
+ if (initial)
+ type = LDAP_FILT_SUBS_INIT;
+ else if (*cp == ')')
+ type = LDAP_FILT_SUBS_FIN;
+ else
+ type = LDAP_FILT_SUBS_ANY;
+
+ if ((parsed_val = parseval(attr_val, len)) ==
+ NULL)
+ goto callfail;
+ elm = ber_add_nstring(elm, parsed_val,
+ strlen(parsed_val));
+ free(parsed_val);
+ if (elm == NULL)
+ goto callfail;
+ ber_set_header(elm, BER_CLASS_CONTEXT, type);
+ if (type == LDAP_FILT_SUBS_FIN)
+ break;
+ }
+ break;
+ }
+
+ if ((parsed_val = parseval(attr_val, len)) == NULL)
+ goto callfail;
+ elm = ber_add_nstring(elm, parsed_val, strlen(parsed_val));
+ free(parsed_val);
+ if (elm == NULL)
+ goto callfail;
+ break;
+ }
+
+ cp++; /* now points one char beyond the trailing `)` */
+
+ *cpp = cp;
+ return (root);
+
+syntaxfail: /* XXX -- error reporting */
+callfail:
+bad:
+ if (root != NULL)
+ ber_free_elements(root);
+ ber_link_elements(prev, NULL);
+ return (NULL);
+}
+
+#ifdef DEBUG
+/*
+ * Display a list of ber elements.
+ *
+ */
+void
+ldap_debug_elements(struct ber_element *root)
+{
+ static int indent = 0;
+ long long v;
+ int d;
+ char *buf;
+ size_t len;
+ u_int i;
+ int constructed;
+ struct ber_oid o;
+
+ /* calculate lengths */
+ ber_calc_len(root);
+
+ switch (root->be_encoding) {
+ case BER_TYPE_SEQUENCE:
+ case BER_TYPE_SET:
+ constructed = root->be_encoding;
+ break;
+ default:
+ constructed = 0;
+ break;
+ }
+
+ fprintf(stderr, "%*slen %lu ", indent, "", root->be_len);
+ switch (root->be_class) {
+ case BER_CLASS_UNIVERSAL:
+ fprintf(stderr, "class: universal(%u) type: ", root->be_class);
+ switch (root->be_type) {
+ case BER_TYPE_EOC:
+ fprintf(stderr, "end-of-content");
+ break;
+ case BER_TYPE_BOOLEAN:
+ fprintf(stderr, "boolean");
+ break;
+ case BER_TYPE_INTEGER:
+ fprintf(stderr, "integer");
+ break;
+ case BER_TYPE_BITSTRING:
+ fprintf(stderr, "bit-string");
+ break;
+ case BER_TYPE_OCTETSTRING:
+ fprintf(stderr, "octet-string");
+ break;
+ case BER_TYPE_NULL:
+ fprintf(stderr, "null");
+ break;
+ case BER_TYPE_OBJECT:
+ fprintf(stderr, "object");
+ break;
+ case BER_TYPE_ENUMERATED:
+ fprintf(stderr, "enumerated");
+ break;
+ case BER_TYPE_SEQUENCE:
+ fprintf(stderr, "sequence");
+ break;
+ case BER_TYPE_SET:
+ fprintf(stderr, "set");
+ break;
+ }
+ break;
+ case BER_CLASS_APPLICATION:
+ fprintf(stderr, "class: application(%u) type: ",
+ root->be_class);
+ switch (root->be_type) {
+ case LDAP_REQ_BIND:
+ fprintf(stderr, "bind");
+ break;
+ case LDAP_RES_BIND:
+ fprintf(stderr, "bind");
+ break;
+ case LDAP_REQ_UNBIND_30:
+ break;
+ case LDAP_REQ_SEARCH:
+ fprintf(stderr, "search");
+ break;
+ case LDAP_RES_SEARCH_ENTRY:
+ fprintf(stderr, "search_entry");
+ break;
+ case LDAP_RES_SEARCH_RESULT:
+ fprintf(stderr, "search_result");
+ break;
+ case LDAP_REQ_MODIFY:
+ fprintf(stderr, "modify");
+ break;
+ case LDAP_RES_MODIFY:
+ fprintf(stderr, "modify");
+ break;
+ case LDAP_REQ_ADD:
+ fprintf(stderr, "add");
+ break;
+ case LDAP_RES_ADD:
+ fprintf(stderr, "add");
+ break;
+ case LDAP_REQ_DELETE_30:
+ fprintf(stderr, "delete");
+ break;
+ case LDAP_RES_DELETE:
+ fprintf(stderr, "delete");
+ break;
+ case LDAP_REQ_MODRDN:
+ fprintf(stderr, "modrdn");
+ break;
+ case LDAP_RES_MODRDN:
+ fprintf(stderr, "modrdn");
+ break;
+ case LDAP_REQ_COMPARE:
+ fprintf(stderr, "compare");
+ break;
+ case LDAP_RES_COMPARE:
+ fprintf(stderr, "compare");
+ break;
+ case LDAP_REQ_ABANDON_30:
+ fprintf(stderr, "abandon");
+ break;
+ }
+ break;
+ case BER_CLASS_PRIVATE:
+ fprintf(stderr, "class: private(%u) type: ", root->be_class);
+ fprintf(stderr, "encoding (%lu) type: ", root->be_encoding);
+ break;
+ case BER_CLASS_CONTEXT:
+ /* XXX: this is not correct */
+ fprintf(stderr, "class: context(%u) type: ", root->be_class);
+ switch(root->be_type) {
+ case LDAP_AUTH_SIMPLE:
+ fprintf(stderr, "auth simple");
+ break;
+ }
+ break;
+ default:
+ fprintf(stderr, "class: <INVALID>(%u) type: ", root->be_class);
+ break;
+ }
+ fprintf(stderr, "(%lu) encoding %lu ",
+ root->be_type, root->be_encoding);
+
+ if (constructed)
+ root->be_encoding = constructed;
+
+ switch (root->be_encoding) {
+ case BER_TYPE_BOOLEAN:
+ if (ber_get_boolean(root, &d) == -1) {
+ fprintf(stderr, "<INVALID>\n");
+ break;
+ }
+ fprintf(stderr, "%s(%d)\n", d ? "true" : "false", d);
+ break;
+ case BER_TYPE_INTEGER:
+ if (ber_get_integer(root, &v) == -1) {
+ fprintf(stderr, "<INVALID>\n");
+ break;
+ }
+ fprintf(stderr, "value %lld\n", v);
+ break;
+ case BER_TYPE_ENUMERATED:
+ if (ber_get_enumerated(root, &v) == -1) {
+ fprintf(stderr, "<INVALID>\n");
+ break;
+ }
+ fprintf(stderr, "value %lld\n", v);
+ break;
+ case BER_TYPE_BITSTRING:
+ if (ber_get_bitstring(root, (void *)&buf, &len) == -1) {
+ fprintf(stderr, "<INVALID>\n");
+ break;
+ }
+ fprintf(stderr, "hexdump ");
+ for (i = 0; i < len; i++)
+ fprintf(stderr, "%02x", buf[i]);
+ fprintf(stderr, "\n");
+ break;
+ case BER_TYPE_OBJECT:
+ if (ber_get_oid(root, &o) == -1) {
+ fprintf(stderr, "<INVALID>\n");
+ break;
+ }
+ fprintf(stderr, "\n");
+ break;
+ case BER_TYPE_OCTETSTRING:
+ if (ber_get_nstring(root, (void *)&buf, &len) == -1) {
+ fprintf(stderr, "<INVALID>\n");
+ break;
+ }
+ fprintf(stderr, "string \"%.*s\"\n", len, buf);
+ break;
+ case BER_TYPE_NULL: /* no payload */
+ case BER_TYPE_EOC:
+ case BER_TYPE_SEQUENCE:
+ case BER_TYPE_SET:
+ default:
+ fprintf(stderr, "\n");
+ break;
+ }
+
+ if (constructed && root->be_sub) {
+ indent += 2;
+ ldap_debug_elements(root->be_sub);
+ indent -= 2;
+ }
+ if (root->be_next)
+ ldap_debug_elements(root->be_next);
+}
+#endif
+
+/*
+ * Convert UTF-8 to ASCII.
+ * notes:
+ * non-ASCII characters are displayed as '?'
+ * the argument u should be a NULL terminated sequence of UTF-8 bytes.
+ */
+char *
+utoa(char *u)
+{
+ int len, i, j;
+ char *str;
+
+ /* calculate the length to allocate */
+ for (len = 0, i = 0; u[i] != '\0'; ) {
+ if ((u[i] & 0xF0) == 0xF0)
+ i += 4;
+ else if ((u[i] & 0xE0) == 0xE0)
+ i += 3;
+ else if ((u[i] & 0xC0) == 0xC0)
+ i += 2;
+ else
+ i += 1;
+ len++;
+ }
+
+ if ((str = calloc(len + 1, sizeof(char))) == NULL)
+ return NULL;
+
+ /* copy the ASCII characters to the newly allocated string */
+ for (i = 0, j = 0; u[i] != '\0'; j++) {
+ if ((u[i] & 0xF0) == 0xF0) {
+ str[j] = '?';
+ i += 4;
+ } else if ((u[i] & 0xE0) == 0xE0) {
+ str[j] = '?';
+ i += 3;
+ } else if ((u[i] & 0xC0) == 0xC0) {
+ str[j] = '?';
+ i += 2;
+ } else {
+ str[j] = u[i];
+ i += 1;
+ }
+ }
+
+ return str;
+}
+
+/*
+ * Parse a LDAP value
+ * notes:
+ * the argument u should be a NULL terminated sequence of ASCII bytes.
+ */
+char *
+parseval(char *p, size_t len)
+{
+ char hex[3];
+ char *cp = p, *buffer, *newbuffer;
+ size_t size, newsize, i, j;
+
+ size = 50;
+ if ((buffer = calloc(1, size)) == NULL)
+ return NULL;
+
+ for (i = j = 0; j < len; i++) {
+ if (i >= size) {
+ newsize = size + 1024;
+ if ((newbuffer = realloc(buffer, newsize)) == NULL) {
+ free(buffer);
+ return (NULL);
+ }
+ buffer = newbuffer;
+ size = newsize;
+ }
+
+ if (cp[j] == '\\') {
+ strlcpy(hex, cp + j + 1, sizeof(hex));
+ buffer[i] = (char)strtoumax(hex, NULL, 16);
+ j += 3;
+ } else {
+ buffer[i] = cp[j];
+ j++;
+ }
+ }
+
+ return buffer;
+}
+
+int
+aldap_get_errno(struct aldap *a, const char **estr)
+{
+ switch (a->err) {
+ case ALDAP_ERR_SUCCESS:
+ *estr = "success";
+ break;
+ case ALDAP_ERR_PARSER_ERROR:
+ *estr = "parser failed";
+ break;
+ case ALDAP_ERR_INVALID_FILTER:
+ *estr = "invalid filter";
+ break;
+ case ALDAP_ERR_OPERATION_FAILED:
+ *estr = "operation failed";
+ break;
+ default:
+ *estr = "unknown";
+ break;
+ }
+ return (a->err);
+}
diff --git a/usr.sbin/ypldap/aldap.h b/usr.sbin/ypldap/aldap.h
new file mode 100644
index 0000000..cdf5316
--- /dev/null
+++ b/usr.sbin/ypldap/aldap.h
@@ -0,0 +1,221 @@
+/* $Id: aldap.h,v 1.9 2012/04/30 21:40:03 jmatthew Exp $ */
+/* $OpenBSD: aldap.h,v 1.9 2012/04/30 21:40:03 jmatthew Exp $ */
+/* $FreeBSD$ */
+
+/*
+ * Copyright (c) 2008 Alexander Schrijver <aschrijver@openbsd.org>
+ * Copyright (c) 2006, 2007 Marc Balmer <mbalmer@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 <stdio.h>
+#include "ber.h"
+
+#define LDAP_URL "ldap://"
+#define LDAP_PORT 389
+#define LDAP_PAGED_OID "1.2.840.113556.1.4.319"
+
+struct aldap {
+#define ALDAP_ERR_SUCCESS 0
+#define ALDAP_ERR_PARSER_ERROR 1
+#define ALDAP_ERR_INVALID_FILTER 2
+#define ALDAP_ERR_OPERATION_FAILED 3
+ u_int8_t err;
+ int msgid;
+ struct ber ber;
+};
+
+struct aldap_page_control {
+ int size;
+ char *cookie;
+ unsigned int cookie_len;
+};
+
+struct aldap_message {
+ int msgid;
+ int message_type;
+
+ struct ber_element *msg;
+
+ struct ber_element *header;
+ struct ber_element *protocol_op;
+
+ struct ber_element *dn;
+
+ union {
+ struct {
+ long long rescode;
+ struct ber_element *diagmsg;
+ } res;
+ struct {
+ struct ber_element *iter;
+ struct ber_element *attrs;
+ } search;
+ } body;
+ struct ber_element *references;
+ struct aldap_page_control *page;
+};
+
+enum aldap_protocol {
+ LDAP,
+ LDAPS
+};
+
+struct aldap_url {
+ int protocol;
+ char *host;
+ in_port_t port;
+ char *dn;
+#define MAXATTR 1024
+ char *attributes[MAXATTR];
+ int scope;
+ char *filter;
+ char *buffer;
+};
+
+enum protocol_op {
+ LDAP_REQ_BIND = 0,
+ LDAP_RES_BIND = 1,
+ LDAP_REQ_UNBIND_30 = 2,
+ LDAP_REQ_SEARCH = 3,
+ LDAP_RES_SEARCH_ENTRY = 4,
+ LDAP_RES_SEARCH_RESULT = 5,
+ LDAP_REQ_MODIFY = 6,
+ LDAP_RES_MODIFY = 7,
+ LDAP_REQ_ADD = 8,
+ LDAP_RES_ADD = 9,
+ LDAP_REQ_DELETE_30 = 10,
+ LDAP_RES_DELETE = 11,
+ LDAP_REQ_MODRDN = 12,
+ LDAP_RES_MODRDN = 13,
+ LDAP_REQ_COMPARE = 14,
+ LDAP_RES_COMPARE = 15,
+ LDAP_REQ_ABANDON_30 = 16,
+
+ LDAP_RES_SEARCH_REFERENCE = 19,
+};
+
+enum deref_aliases {
+ LDAP_DEREF_NEVER = 0,
+ LDAP_DEREF_SEARCHING = 1,
+ LDAP_DEREF_FINDING = 2,
+ LDAP_DEREF_ALWAYS = 3,
+};
+
+enum authentication_choice {
+ LDAP_AUTH_SIMPLE = 0,
+};
+
+enum scope {
+ LDAP_SCOPE_BASE = 0,
+ LDAP_SCOPE_ONELEVEL = 1,
+ LDAP_SCOPE_SUBTREE = 2,
+};
+
+enum result_code {
+ LDAP_SUCCESS = 0,
+ LDAP_OPERATIONS_ERROR = 1,
+ LDAP_PROTOCOL_ERROR = 2,
+ LDAP_TIMELIMIT_EXCEEDED = 3,
+ LDAP_SIZELIMIT_EXCEEDED = 4,
+ LDAP_COMPARE_FALSE = 5,
+ LDAP_COMPARE_TRUE = 6,
+ LDAP_STRONG_AUTH_NOT_SUPPORTED = 7,
+ LDAP_STRONG_AUTH_REQUIRED = 8,
+
+ LDAP_REFERRAL = 10,
+ LDAP_ADMINLIMIT_EXCEEDED = 11,
+ LDAP_UNAVAILABLE_CRITICAL_EXTENSION = 12,
+ LDAP_CONFIDENTIALITY_REQUIRED = 13,
+ LDAP_SASL_BIND_IN_PROGRESS = 14,
+ LDAP_NO_SUCH_ATTRIBUTE = 16,
+ LDAP_UNDEFINED_TYPE = 17,
+ LDAP_INAPPROPRIATE_MATCHING = 18,
+ LDAP_CONSTRAINT_VIOLATION = 19,
+ LDAP_TYPE_OR_VALUE_EXISTS = 20,
+ LDAP_INVALID_SYNTAX = 21,
+
+ LDAP_NO_SUCH_OBJECT = 32,
+ LDAP_ALIAS_PROBLEM = 33,
+ LDAP_INVALID_DN_SYNTAX = 34,
+
+ LDAP_ALIAS_DEREF_PROBLEM = 36,
+
+ LDAP_INAPPROPRIATE_AUTH = 48,
+ LDAP_INVALID_CREDENTIALS = 49,
+ LDAP_INSUFFICIENT_ACCESS = 50,
+ LDAP_BUSY = 51,
+ LDAP_UNAVAILABLE = 52,
+ LDAP_UNWILLING_TO_PERFORM = 53,
+ LDAP_LOOP_DETECT = 54,
+
+ LDAP_NAMING_VIOLATION = 64,
+ LDAP_OBJECT_CLASS_VIOLATION = 65,
+ LDAP_NOT_ALLOWED_ON_NONLEAF = 66,
+ LDAP_NOT_ALLOWED_ON_RDN = 67,
+ LDAP_ALREADY_EXISTS = 68,
+ LDAP_NO_OBJECT_CLASS_MODS = 69,
+
+ LDAP_AFFECTS_MULTIPLE_DSAS = 71,
+
+ LDAP_OTHER = 80,
+};
+
+enum filter {
+ LDAP_FILT_AND = 0,
+ LDAP_FILT_OR = 1,
+ LDAP_FILT_NOT = 2,
+ LDAP_FILT_EQ = 3,
+ LDAP_FILT_SUBS = 4,
+ LDAP_FILT_GE = 5,
+ LDAP_FILT_LE = 6,
+ LDAP_FILT_PRES = 7,
+ LDAP_FILT_APPR = 8,
+};
+
+enum subfilter {
+ LDAP_FILT_SUBS_INIT = 0,
+ LDAP_FILT_SUBS_ANY = 1,
+ LDAP_FILT_SUBS_FIN = 2,
+};
+
+struct aldap *aldap_init(int fd);
+int aldap_close(struct aldap *);
+struct aldap_message *aldap_parse(struct aldap *);
+void aldap_freemsg(struct aldap_message *);
+
+int aldap_bind(struct aldap *, char *, char *);
+int aldap_unbind(struct aldap *);
+int aldap_search(struct aldap *, char *, enum scope, char *, char **, int, int, int, struct aldap_page_control *);
+int aldap_get_errno(struct aldap *, const char **);
+
+int aldap_get_resultcode(struct aldap_message *);
+char *aldap_get_dn(struct aldap_message *);
+char *aldap_get_diagmsg(struct aldap_message *);
+char **aldap_get_references(struct aldap_message *);
+void aldap_free_references(char **values);
+#if 0
+int aldap_parse_url(char *, struct aldap_url *);
+void aldap_free_url(struct aldap_url *);
+int aldap_search_url(struct aldap *, char *, int, int, int);
+#endif
+
+int aldap_count_attrs(struct aldap_message *);
+int aldap_match_attr(struct aldap_message *, char *, char ***);
+int aldap_first_attr(struct aldap_message *, char **, char ***);
+int aldap_next_attr(struct aldap_message *, char **, char ***);
+int aldap_free_attr(char **);
+
+struct aldap_page_control *aldap_parse_page_control(struct ber_element *, size_t len);
+void aldap_freepage(struct aldap_page_control *);
diff --git a/usr.sbin/ypldap/ber.c b/usr.sbin/ypldap/ber.c
new file mode 100644
index 0000000..540df69
--- /dev/null
+++ b/usr.sbin/ypldap/ber.c
@@ -0,0 +1,1268 @@
+/* $OpenBSD: ber.c,v 1.9 2015/02/12 00:30:38 pelikan Exp $ */
+/* $FreeBSD$ */
+
+/*
+ * Copyright (c) 2007 Reyk Floeter <reyk@vantronix.net>
+ * Copyright (c) 2006, 2007 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2006, 2007 Marc Balmer <mbalmer@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 <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <err.h> /* XXX for debug output */
+#include <stdio.h> /* XXX for debug output */
+#include <string.h>
+#include <unistd.h>
+#include <stdarg.h>
+
+#include "ber.h"
+
+#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
+
+#define BER_TYPE_CONSTRUCTED 0x20 /* otherwise primitive */
+#define BER_TYPE_SINGLE_MAX 30
+#define BER_TAG_MASK 0x1f
+#define BER_TAG_MORE 0x80 /* more subsequent octets */
+#define BER_TAG_TYPE_MASK 0x7f
+#define BER_CLASS_SHIFT 6
+
+static int ber_dump_element(struct ber *ber, struct ber_element *root);
+static void ber_dump_header(struct ber *ber, struct ber_element *root);
+static void ber_putc(struct ber *ber, u_char c);
+static void ber_write(struct ber *ber, void *buf, size_t len);
+static ssize_t get_id(struct ber *b, unsigned long *tag, int *class,
+ int *cstruct);
+static ssize_t get_len(struct ber *b, ssize_t *len);
+static ssize_t ber_read_element(struct ber *ber, struct ber_element *elm);
+static ssize_t ber_readbuf(struct ber *b, void *buf, size_t nbytes);
+static ssize_t ber_getc(struct ber *b, u_char *c);
+static ssize_t ber_read(struct ber *ber, void *buf, size_t len);
+
+#ifdef DEBUG
+#define DPRINTF(...) printf(__VA_ARGS__)
+#else
+#define DPRINTF(...) do { } while (0)
+#endif
+
+struct ber_element *
+ber_get_element(unsigned long encoding)
+{
+ struct ber_element *elm;
+
+ if ((elm = calloc(1, sizeof(*elm))) == NULL)
+ return NULL;
+
+ elm->be_encoding = encoding;
+ ber_set_header(elm, BER_CLASS_UNIVERSAL, BER_TYPE_DEFAULT);
+
+ return elm;
+}
+
+void
+ber_set_header(struct ber_element *elm, int class, unsigned long type)
+{
+ elm->be_class = class & BER_CLASS_MASK;
+ if (type == BER_TYPE_DEFAULT)
+ type = elm->be_encoding;
+ elm->be_type = type;
+}
+
+void
+ber_link_elements(struct ber_element *prev, struct ber_element *elm)
+{
+ if (prev != NULL) {
+ if ((prev->be_encoding == BER_TYPE_SEQUENCE ||
+ prev->be_encoding == BER_TYPE_SET) &&
+ prev->be_sub == NULL)
+ prev->be_sub = elm;
+ else
+ prev->be_next = elm;
+ }
+}
+
+struct ber_element *
+ber_unlink_elements(struct ber_element *prev)
+{
+ struct ber_element *elm;
+
+ if ((prev->be_encoding == BER_TYPE_SEQUENCE ||
+ prev->be_encoding == BER_TYPE_SET) &&
+ prev->be_sub != NULL) {
+ elm = prev->be_sub;
+ prev->be_sub = NULL;
+ } else {
+ elm = prev->be_next;
+ prev->be_next = NULL;
+ }
+
+ return (elm);
+}
+
+void
+ber_replace_elements(struct ber_element *prev, struct ber_element *new)
+{
+ struct ber_element *ber, *next;
+
+ ber = ber_unlink_elements(prev);
+ next = ber_unlink_elements(ber);
+ ber_link_elements(new, next);
+ ber_link_elements(prev, new);
+
+ /* cleanup old element */
+ ber_free_elements(ber);
+}
+
+struct ber_element *
+ber_add_sequence(struct ber_element *prev)
+{
+ struct ber_element *elm;
+
+ if ((elm = ber_get_element(BER_TYPE_SEQUENCE)) == NULL)
+ return NULL;
+
+ ber_link_elements(prev, elm);
+
+ return elm;
+}
+
+struct ber_element *
+ber_add_set(struct ber_element *prev)
+{
+ struct ber_element *elm;
+
+ if ((elm = ber_get_element(BER_TYPE_SET)) == NULL)
+ return NULL;
+
+ ber_link_elements(prev, elm);
+
+ return elm;
+}
+
+struct ber_element *
+ber_add_enumerated(struct ber_element *prev, long long val)
+{
+ struct ber_element *elm;
+ u_int i, len = 0;
+ u_char cur, last = 0;
+
+ if ((elm = ber_get_element(BER_TYPE_ENUMERATED)) == NULL)
+ return NULL;
+
+ elm->be_numeric = val;
+
+ for (i = 0; i < sizeof(long long); i++) {
+ cur = val & 0xff;
+ if (cur != 0 && cur != 0xff)
+ len = i;
+ if ((cur == 0 && last & 0x80) ||
+ (cur == 0xff && (last & 0x80) == 0))
+ len = i;
+ val >>= 8;
+ last = cur;
+ }
+ elm->be_len = len + 1;
+
+ ber_link_elements(prev, elm);
+
+ return elm;
+}
+
+struct ber_element *
+ber_add_integer(struct ber_element *prev, long long val)
+{
+ struct ber_element *elm;
+ u_int i, len = 0;
+ u_char cur, last = 0;
+
+ if ((elm = ber_get_element(BER_TYPE_INTEGER)) == NULL)
+ return NULL;
+
+ elm->be_numeric = val;
+
+ for (i = 0; i < sizeof(long long); i++) {
+ cur = val & 0xff;
+ if (cur != 0 && cur != 0xff)
+ len = i;
+ if ((cur == 0 && last & 0x80) ||
+ (cur == 0xff && (last & 0x80) == 0))
+ len = i;
+ val >>= 8;
+ last = cur;
+ }
+ elm->be_len = len + 1;
+
+ ber_link_elements(prev, elm);
+
+ return elm;
+}
+
+int
+ber_get_integer(struct ber_element *elm, long long *n)
+{
+ if (elm->be_encoding != BER_TYPE_INTEGER)
+ return -1;
+
+ *n = elm->be_numeric;
+ return 0;
+}
+
+int
+ber_get_enumerated(struct ber_element *elm, long long *n)
+{
+ if (elm->be_encoding != BER_TYPE_ENUMERATED)
+ return -1;
+
+ *n = elm->be_numeric;
+ return 0;
+}
+
+
+struct ber_element *
+ber_add_boolean(struct ber_element *prev, int bool)
+{
+ struct ber_element *elm;
+
+ if ((elm = ber_get_element(BER_TYPE_BOOLEAN)) == NULL)
+ return NULL;
+
+ elm->be_numeric = bool ? 0xff : 0;
+ elm->be_len = 1;
+
+ ber_link_elements(prev, elm);
+
+ return elm;
+}
+
+int
+ber_get_boolean(struct ber_element *elm, int *b)
+{
+ if (elm->be_encoding != BER_TYPE_BOOLEAN)
+ return -1;
+
+ *b = !(elm->be_numeric == 0);
+ return 0;
+}
+
+struct ber_element *
+ber_add_string(struct ber_element *prev, const char *string)
+{
+ return ber_add_nstring(prev, string, strlen(string));
+}
+
+struct ber_element *
+ber_add_nstring(struct ber_element *prev, const char *string0, size_t len)
+{
+ struct ber_element *elm;
+ char *string;
+
+ if ((string = calloc(1, len)) == NULL)
+ return NULL;
+ if ((elm = ber_get_element(BER_TYPE_OCTETSTRING)) == NULL) {
+ free(string);
+ return NULL;
+ }
+
+ bcopy(string0, string, len);
+ elm->be_val = string;
+ elm->be_len = len;
+ elm->be_free = 1; /* free string on cleanup */
+
+ ber_link_elements(prev, elm);
+
+ return elm;
+}
+
+int
+ber_get_string(struct ber_element *elm, char **s)
+{
+ if (elm->be_encoding != BER_TYPE_OCTETSTRING)
+ return -1;
+
+ *s = elm->be_val;
+ return 0;
+}
+
+int
+ber_get_nstring(struct ber_element *elm, void **p, size_t *len)
+{
+ if (elm->be_encoding != BER_TYPE_OCTETSTRING)
+ return -1;
+
+ *p = elm->be_val;
+ *len = elm->be_len;
+ return 0;
+}
+
+struct ber_element *
+ber_add_bitstring(struct ber_element *prev, const void *v0, size_t len)
+{
+ struct ber_element *elm;
+ void *v;
+
+ if ((v = calloc(1, len)) == NULL)
+ return NULL;
+ if ((elm = ber_get_element(BER_TYPE_BITSTRING)) == NULL) {
+ free(v);
+ return NULL;
+ }
+
+ bcopy(v0, v, len);
+ elm->be_val = v;
+ elm->be_len = len;
+ elm->be_free = 1; /* free string on cleanup */
+
+ ber_link_elements(prev, elm);
+
+ return elm;
+}
+
+int
+ber_get_bitstring(struct ber_element *elm, void **v, size_t *len)
+{
+ if (elm->be_encoding != BER_TYPE_BITSTRING)
+ return -1;
+
+ *v = elm->be_val;
+ *len = elm->be_len;
+ return 0;
+}
+
+struct ber_element *
+ber_add_null(struct ber_element *prev)
+{
+ struct ber_element *elm;
+
+ if ((elm = ber_get_element(BER_TYPE_NULL)) == NULL)
+ return NULL;
+
+ ber_link_elements(prev, elm);
+
+ return elm;
+}
+
+int
+ber_get_null(struct ber_element *elm)
+{
+ if (elm->be_encoding != BER_TYPE_NULL)
+ return -1;
+
+ return 0;
+}
+
+struct ber_element *
+ber_add_eoc(struct ber_element *prev)
+{
+ struct ber_element *elm;
+
+ if ((elm = ber_get_element(BER_TYPE_EOC)) == NULL)
+ return NULL;
+
+ ber_link_elements(prev, elm);
+
+ return elm;
+}
+
+int
+ber_get_eoc(struct ber_element *elm)
+{
+ if (elm->be_encoding != BER_TYPE_EOC)
+ return -1;
+
+ return 0;
+}
+
+size_t
+ber_oid2ber(struct ber_oid *o, u_int8_t *buf, size_t len)
+{
+ u_int32_t v;
+ u_int i, j = 0, k;
+
+ if (o->bo_n < BER_MIN_OID_LEN || o->bo_n > BER_MAX_OID_LEN ||
+ o->bo_id[0] > 2 || o->bo_id[1] > 40)
+ return (0);
+
+ v = (o->bo_id[0] * 40) + o->bo_id[1];
+ for (i = 2, j = 0; i <= o->bo_n; v = o->bo_id[i], i++) {
+ for (k = 28; k >= 7; k -= 7) {
+ if (v >= (u_int)(1 << k)) {
+ if (len)
+ buf[j] = v >> k | BER_TAG_MORE;
+ j++;
+ }
+ }
+ if (len)
+ buf[j] = v & BER_TAG_TYPE_MASK;
+ j++;
+ }
+
+ return (j);
+}
+
+int
+ber_string2oid(const char *oidstr, struct ber_oid *o)
+{
+ char *sp, *p, str[BUFSIZ];
+ const char *errstr;
+
+ if (strlcpy(str, oidstr, sizeof(str)) >= sizeof(str))
+ return (-1);
+ bzero(o, sizeof(*o));
+
+ /* Parse OID strings in the common forms n.n.n, n_n_n_n, or n-n-n */
+ for (p = sp = str; p != NULL; sp = p) {
+ if ((p = strpbrk(p, "._-")) != NULL)
+ *p++ = '\0';
+ o->bo_id[o->bo_n++] = strtonum(sp, 0, UINT_MAX, &errstr);
+ if (errstr || o->bo_n > BER_MAX_OID_LEN)
+ return (-1);
+ }
+
+ return (0);
+}
+
+struct ber_element *
+ber_add_oid(struct ber_element *prev, struct ber_oid *o)
+{
+ struct ber_element *elm;
+ u_int8_t *buf;
+ size_t len;
+
+ if ((elm = ber_get_element(BER_TYPE_OBJECT)) == NULL)
+ return (NULL);
+
+ if ((len = ber_oid2ber(o, NULL, 0)) == 0)
+ goto fail;
+
+ if ((buf = calloc(1, len)) == NULL)
+ goto fail;
+
+ elm->be_val = buf;
+ elm->be_len = len;
+ elm->be_free = 1;
+
+ if (ber_oid2ber(o, buf, len) != len)
+ goto fail;
+
+ ber_link_elements(prev, elm);
+
+ return (elm);
+
+ fail:
+ ber_free_elements(elm);
+ return (NULL);
+}
+
+struct ber_element *
+ber_add_noid(struct ber_element *prev, struct ber_oid *o, int n)
+{
+ struct ber_oid no;
+
+ if (n > BER_MAX_OID_LEN)
+ return (NULL);
+ no.bo_n = n;
+ bcopy(&o->bo_id, &no.bo_id, sizeof(no.bo_id));
+
+ return (ber_add_oid(prev, &no));
+}
+
+struct ber_element *
+ber_add_oidstring(struct ber_element *prev, const char *oidstr)
+{
+ struct ber_oid o;
+
+ if (ber_string2oid(oidstr, &o) == -1)
+ return (NULL);
+
+ return (ber_add_oid(prev, &o));
+}
+
+int
+ber_get_oid(struct ber_element *elm, struct ber_oid *o)
+{
+ u_int8_t *buf;
+ size_t len, i = 0, j = 0;
+
+ if (elm->be_encoding != BER_TYPE_OBJECT)
+ return (-1);
+
+ buf = elm->be_val;
+ len = elm->be_len;
+
+ if (!buf[i])
+ return (-1);
+
+ bzero(o, sizeof(*o));
+ o->bo_id[j++] = buf[i] / 40;
+ o->bo_id[j++] = buf[i++] % 40;
+ for (; i < len && j < BER_MAX_OID_LEN; i++) {
+ o->bo_id[j] = (o->bo_id[j] << 7) + (buf[i] & ~0x80);
+ if (buf[i] & 0x80)
+ continue;
+ j++;
+ }
+ o->bo_n = j;
+
+ return (0);
+}
+
+struct ber_element *
+ber_printf_elements(struct ber_element *ber, char *fmt, ...)
+{
+ va_list ap;
+ int d, class;
+ size_t len;
+ unsigned long type;
+ long long i;
+ char *s;
+ void *p;
+ struct ber_oid *o;
+ struct ber_element *sub = ber, *e;
+
+ va_start(ap, fmt);
+ while (*fmt) {
+ switch (*fmt++) {
+ case 'B':
+ p = va_arg(ap, void *);
+ len = va_arg(ap, size_t);
+ if ((ber = ber_add_bitstring(ber, p, len)) == NULL)
+ goto fail;
+ break;
+ case 'b':
+ d = va_arg(ap, int);
+ if ((ber = ber_add_boolean(ber, d)) == NULL)
+ goto fail;
+ break;
+ case 'd':
+ d = va_arg(ap, int);
+ if ((ber = ber_add_integer(ber, d)) == NULL)
+ goto fail;
+ break;
+ case 'e':
+ e = va_arg(ap, struct ber_element *);
+ ber_link_elements(ber, e);
+ break;
+ case 'E':
+ i = va_arg(ap, long long);
+ if ((ber = ber_add_enumerated(ber, i)) == NULL)
+ goto fail;
+ break;
+ case 'i':
+ i = va_arg(ap, long long);
+ if ((ber = ber_add_integer(ber, i)) == NULL)
+ goto fail;
+ break;
+ case 'O':
+ o = va_arg(ap, struct ber_oid *);
+ if ((ber = ber_add_oid(ber, o)) == NULL)
+ goto fail;
+ break;
+ case 'o':
+ s = va_arg(ap, char *);
+ if ((ber = ber_add_oidstring(ber, s)) == NULL)
+ goto fail;
+ break;
+ case 's':
+ s = va_arg(ap, char *);
+ if ((ber = ber_add_string(ber, s)) == NULL)
+ goto fail;
+ break;
+ case 't':
+ class = va_arg(ap, int);
+ type = va_arg(ap, unsigned long);
+ ber_set_header(ber, class, type);
+ break;
+ case 'x':
+ s = va_arg(ap, char *);
+ len = va_arg(ap, size_t);
+ if ((ber = ber_add_nstring(ber, s, len)) == NULL)
+ goto fail;
+ break;
+ case '0':
+ if ((ber = ber_add_null(ber)) == NULL)
+ goto fail;
+ break;
+ case '{':
+ if ((ber = sub = ber_add_sequence(ber)) == NULL)
+ goto fail;
+ break;
+ case '(':
+ if ((ber = sub = ber_add_set(ber)) == NULL)
+ goto fail;
+ break;
+ case '}':
+ case ')':
+ ber = sub;
+ break;
+ case '.':
+ if ((e = ber_add_eoc(ber)) == NULL)
+ goto fail;
+ ber = e;
+ break;
+ default:
+ break;
+ }
+ }
+ va_end(ap);
+
+ return (ber);
+ fail:
+ ber_free_elements(ber);
+ return (NULL);
+}
+
+int
+ber_scanf_elements(struct ber_element *ber, char *fmt, ...)
+{
+#define _MAX_SEQ 128
+ va_list ap;
+ int *d, level = -1;
+ unsigned long *t;
+ long long *i;
+ void **ptr;
+ size_t *len, ret = 0, n = strlen(fmt);
+ char **s;
+ struct ber_oid *o;
+ struct ber_element *parent[_MAX_SEQ], **e;
+
+ bzero(parent, sizeof(struct ber_element *) * _MAX_SEQ);
+
+ va_start(ap, fmt);
+ while (*fmt) {
+ switch (*fmt++) {
+ case 'B':
+ ptr = va_arg(ap, void **);
+ len = va_arg(ap, size_t *);
+ if (ber_get_bitstring(ber, ptr, len) == -1)
+ goto fail;
+ ret++;
+ break;
+ case 'b':
+ d = va_arg(ap, int *);
+ if (ber_get_boolean(ber, d) == -1)
+ goto fail;
+ ret++;
+ break;
+ case 'e':
+ e = va_arg(ap, struct ber_element **);
+ *e = ber;
+ ret++;
+ continue;
+ case 'E':
+ i = va_arg(ap, long long *);
+ if (ber_get_enumerated(ber, i) == -1)
+ goto fail;
+ ret++;
+ break;
+ case 'i':
+ i = va_arg(ap, long long *);
+ if (ber_get_integer(ber, i) == -1)
+ goto fail;
+ ret++;
+ break;
+ case 'o':
+ o = va_arg(ap, struct ber_oid *);
+ if (ber_get_oid(ber, o) == -1)
+ goto fail;
+ ret++;
+ break;
+ case 'S':
+ ret++;
+ break;
+ case 's':
+ s = va_arg(ap, char **);
+ if (ber_get_string(ber, s) == -1)
+ goto fail;
+ ret++;
+ break;
+ case 't':
+ d = va_arg(ap, int *);
+ t = va_arg(ap, unsigned long *);
+ *d = ber->be_class;
+ *t = ber->be_type;
+ ret++;
+ continue;
+ case 'x':
+ ptr = va_arg(ap, void **);
+ len = va_arg(ap, size_t *);
+ if (ber_get_nstring(ber, ptr, len) == -1)
+ goto fail;
+ ret++;
+ break;
+ case '0':
+ if (ber->be_encoding != BER_TYPE_NULL)
+ goto fail;
+ ret++;
+ break;
+ case '.':
+ if (ber->be_encoding != BER_TYPE_EOC)
+ goto fail;
+ ret++;
+ break;
+ case '{':
+ case '(':
+ if (ber->be_encoding != BER_TYPE_SEQUENCE &&
+ ber->be_encoding != BER_TYPE_SET)
+ goto fail;
+ if (ber->be_sub == NULL || level >= _MAX_SEQ-1)
+ goto fail;
+ parent[++level] = ber;
+ ber = ber->be_sub;
+ ret++;
+ continue;
+ case '}':
+ case ')':
+ if (parent[level] == NULL)
+ goto fail;
+ ber = parent[level--];
+ ret++;
+ continue;
+ default:
+ goto fail;
+ }
+
+ if (ber->be_next == NULL)
+ continue;
+ ber = ber->be_next;
+ }
+ va_end(ap);
+ return (ret == n ? 0 : -1);
+
+ fail:
+ va_end(ap);
+ return (-1);
+
+}
+
+/*
+ * write ber elements to the socket
+ *
+ * params:
+ * ber holds the socket
+ * root fully populated element tree
+ *
+ * returns:
+ * >=0 number of bytes written
+ * -1 on failure and sets errno
+ */
+int
+ber_write_elements(struct ber *ber, struct ber_element *root)
+{
+ size_t len;
+
+ /* calculate length because only the definite form is required */
+ len = ber_calc_len(root);
+ DPRINTF("write ber element of %zd bytes length\n", len);
+
+ if (ber->br_wbuf != NULL && ber->br_wbuf + len > ber->br_wend) {
+ free(ber->br_wbuf);
+ ber->br_wbuf = NULL;
+ }
+ if (ber->br_wbuf == NULL) {
+ if ((ber->br_wbuf = malloc(len)) == NULL)
+ return -1;
+ ber->br_wend = ber->br_wbuf + len;
+ }
+
+ /* reset write pointer */
+ ber->br_wptr = ber->br_wbuf;
+
+ if (ber_dump_element(ber, root) == -1)
+ return -1;
+
+ /* XXX this should be moved to a different function */
+ if (ber->fd != -1)
+ return write(ber->fd, ber->br_wbuf, len);
+
+ return (len);
+}
+
+/*
+ * read ber elements from the socket
+ *
+ * params:
+ * ber holds the socket and lot more
+ * root if NULL, build up an element tree from what we receive on
+ * the wire. If not null, use the specified encoding for the
+ * elements received.
+ *
+ * returns:
+ * !=NULL, elements read and store in the ber_element tree
+ * NULL, type mismatch or read error
+ */
+struct ber_element *
+ber_read_elements(struct ber *ber, struct ber_element *elm)
+{
+ struct ber_element *root = elm;
+
+ if (root == NULL) {
+ if ((root = ber_get_element(0)) == NULL)
+ return NULL;
+ }
+
+ DPRINTF("read ber elements, root %p\n", root);
+
+ if (ber_read_element(ber, root) == -1) {
+ /* Cleanup if root was allocated by us */
+ if (elm == NULL)
+ ber_free_elements(root);
+ return NULL;
+ }
+
+ return root;
+}
+
+void
+ber_free_elements(struct ber_element *root)
+{
+ if (root->be_sub && (root->be_encoding == BER_TYPE_SEQUENCE ||
+ root->be_encoding == BER_TYPE_SET))
+ ber_free_elements(root->be_sub);
+ if (root->be_next)
+ ber_free_elements(root->be_next);
+ if (root->be_free && (root->be_encoding == BER_TYPE_OCTETSTRING ||
+ root->be_encoding == BER_TYPE_BITSTRING ||
+ root->be_encoding == BER_TYPE_OBJECT))
+ free(root->be_val);
+ free(root);
+}
+
+size_t
+ber_calc_len(struct ber_element *root)
+{
+ unsigned long t;
+ size_t s;
+ size_t size = 2; /* minimum 1 byte head and 1 byte size */
+
+ /* calculate the real length of a sequence or set */
+ if (root->be_sub && (root->be_encoding == BER_TYPE_SEQUENCE ||
+ root->be_encoding == BER_TYPE_SET))
+ root->be_len = ber_calc_len(root->be_sub);
+
+ /* fix header length for extended types */
+ if (root->be_type > BER_TYPE_SINGLE_MAX)
+ for (t = root->be_type; t > 0; t >>= 7)
+ size++;
+ if (root->be_len >= BER_TAG_MORE)
+ for (s = root->be_len; s > 0; s >>= 8)
+ size++;
+
+ /* calculate the length of the following elements */
+ if (root->be_next)
+ size += ber_calc_len(root->be_next);
+
+ /* This is an empty element, do not use a minimal size */
+ if (root->be_type == BER_TYPE_EOC && root->be_len == 0)
+ return (0);
+
+ return (root->be_len + size);
+}
+
+/*
+ * internal functions
+ */
+
+static int
+ber_dump_element(struct ber *ber, struct ber_element *root)
+{
+ unsigned long long l;
+ int i;
+ uint8_t u;
+
+ ber_dump_header(ber, root);
+
+ switch (root->be_encoding) {
+ case BER_TYPE_BOOLEAN:
+ case BER_TYPE_INTEGER:
+ case BER_TYPE_ENUMERATED:
+ l = (unsigned long long)root->be_numeric;
+ for (i = root->be_len; i > 0; i--) {
+ u = (l >> ((i - 1) * 8)) & 0xff;
+ ber_putc(ber, u);
+ }
+ break;
+ case BER_TYPE_BITSTRING:
+ return -1;
+ case BER_TYPE_OCTETSTRING:
+ case BER_TYPE_OBJECT:
+ ber_write(ber, root->be_val, root->be_len);
+ break;
+ case BER_TYPE_NULL: /* no payload */
+ case BER_TYPE_EOC:
+ break;
+ case BER_TYPE_SEQUENCE:
+ case BER_TYPE_SET:
+ if (root->be_sub && ber_dump_element(ber, root->be_sub) == -1)
+ return -1;
+ break;
+ }
+
+ if (root->be_next == NULL)
+ return 0;
+ return ber_dump_element(ber, root->be_next);
+}
+
+static void
+ber_dump_header(struct ber *ber, struct ber_element *root)
+{
+ u_char id = 0, t, buf[8];
+ unsigned long type;
+ size_t size;
+
+ /* class universal, type encoding depending on type value */
+ /* length encoding */
+ if (root->be_type <= BER_TYPE_SINGLE_MAX) {
+ id = root->be_type | (root->be_class << BER_CLASS_SHIFT);
+ if (root->be_encoding == BER_TYPE_SEQUENCE ||
+ root->be_encoding == BER_TYPE_SET)
+ id |= BER_TYPE_CONSTRUCTED;
+
+ ber_putc(ber, id);
+ } else {
+ id = BER_TAG_MASK | (root->be_class << BER_CLASS_SHIFT);
+ if (root->be_encoding == BER_TYPE_SEQUENCE ||
+ root->be_encoding == BER_TYPE_SET)
+ id |= BER_TYPE_CONSTRUCTED;
+
+ ber_putc(ber, id);
+
+ for (t = 0, type = root->be_type; type > 0; type >>= 7)
+ buf[t++] = type & ~BER_TAG_MORE;
+
+ while (t-- > 0) {
+ if (t > 0)
+ buf[t] |= BER_TAG_MORE;
+ ber_putc(ber, buf[t]);
+ }
+ }
+
+ if (root->be_len < BER_TAG_MORE) {
+ /* short form */
+ ber_putc(ber, root->be_len);
+ } else {
+ for (t = 0, size = root->be_len; size > 0; size >>= 8)
+ buf[t++] = size & 0xff;
+
+ ber_putc(ber, t | BER_TAG_MORE);
+
+ while (t > 0)
+ ber_putc(ber, buf[--t]);
+ }
+}
+
+static void
+ber_putc(struct ber *ber, u_char c)
+{
+ if (ber->br_wptr + 1 <= ber->br_wend)
+ *ber->br_wptr = c;
+ ber->br_wptr++;
+}
+
+static void
+ber_write(struct ber *ber, void *buf, size_t len)
+{
+ if (ber->br_wptr + len <= ber->br_wend)
+ bcopy(buf, ber->br_wptr, len);
+ ber->br_wptr += len;
+}
+
+/*
+ * extract a BER encoded tag. There are two types, a short and long form.
+ */
+static ssize_t
+get_id(struct ber *b, unsigned long *tag, int *class, int *cstruct)
+{
+ u_char u;
+ size_t i = 0;
+ unsigned long t = 0;
+
+ if (ber_getc(b, &u) == -1)
+ return -1;
+
+ *class = (u >> BER_CLASS_SHIFT) & BER_CLASS_MASK;
+ *cstruct = (u & BER_TYPE_CONSTRUCTED) == BER_TYPE_CONSTRUCTED;
+
+ if ((u & BER_TAG_MASK) != BER_TAG_MASK) {
+ *tag = u & BER_TAG_MASK;
+ return 1;
+ }
+
+ do {
+ if (ber_getc(b, &u) == -1)
+ return -1;
+ t = (t << 7) | (u & ~BER_TAG_MORE);
+ i++;
+ } while (u & BER_TAG_MORE);
+
+ if (i > sizeof(unsigned long)) {
+ errno = ERANGE;
+ return -1;
+ }
+
+ *tag = t;
+ return i + 1;
+}
+
+/*
+ * extract length of a ber object -- if length is unknown an error is returned.
+ */
+static ssize_t
+get_len(struct ber *b, ssize_t *len)
+{
+ u_char u, n;
+ ssize_t s, r;
+
+ if (ber_getc(b, &u) == -1)
+ return -1;
+ if ((u & BER_TAG_MORE) == 0) {
+ /* short form */
+ *len = u;
+ return 1;
+ }
+
+ n = u & ~BER_TAG_MORE;
+ if (sizeof(ssize_t) < n) {
+ errno = ERANGE;
+ return -1;
+ }
+ r = n + 1;
+
+ for (s = 0; n > 0; n--) {
+ if (ber_getc(b, &u) == -1)
+ return -1;
+ s = (s << 8) | u;
+ }
+
+ if (s < 0) {
+ /* overflow */
+ errno = ERANGE;
+ return -1;
+ }
+
+ if (s == 0) {
+ /* invalid encoding */
+ errno = EINVAL;
+ return -1;
+ }
+
+ *len = s;
+ return r;
+}
+
+static ssize_t
+ber_read_element(struct ber *ber, struct ber_element *elm)
+{
+ long long val = 0;
+ struct ber_element *next;
+ unsigned long type;
+ int i, class, cstruct;
+ ssize_t len, r, totlen = 0;
+ u_char c;
+
+ if ((r = get_id(ber, &type, &class, &cstruct)) == -1)
+ return -1;
+ DPRINTF("ber read got class %d type %lu, %s\n",
+ class, type, cstruct ? "constructive" : "primitive");
+ totlen += r;
+ if ((r = get_len(ber, &len)) == -1)
+ return -1;
+ DPRINTF("ber read element size %zd\n", len);
+ totlen += r + len;
+
+ elm->be_type = type;
+ elm->be_len = len;
+ elm->be_class = class;
+
+ if (elm->be_encoding == 0) {
+ /* try to figure out the encoding via class, type and cstruct */
+ if (cstruct)
+ elm->be_encoding = BER_TYPE_SEQUENCE;
+ else if (class == BER_CLASS_UNIVERSAL)
+ elm->be_encoding = type;
+ else if (ber->br_application != NULL) {
+ /*
+ * Ask the application to map the encoding to a
+ * universal type. For example, a SMI IpAddress
+ * type is defined as 4 byte OCTET STRING.
+ */
+ elm->be_encoding = (*ber->br_application)(elm);
+ } else
+ /* last resort option */
+ elm->be_encoding = BER_TYPE_NULL;
+ }
+
+ switch (elm->be_encoding) {
+ case BER_TYPE_EOC: /* End-Of-Content */
+ break;
+ case BER_TYPE_BOOLEAN:
+ case BER_TYPE_INTEGER:
+ case BER_TYPE_ENUMERATED:
+ if (len > (ssize_t)sizeof(long long))
+ return -1;
+ for (i = 0; i < len; i++) {
+ if (ber_getc(ber, &c) != 1)
+ return -1;
+ val <<= 8;
+ val |= c;
+ }
+
+ /* sign extend if MSB is set */
+ if (val >> ((i - 1) * 8) & 0x80)
+ val |= ULLONG_MAX << (i * 8);
+ elm->be_numeric = val;
+ break;
+ case BER_TYPE_BITSTRING:
+ elm->be_val = malloc(len);
+ if (elm->be_val == NULL)
+ return -1;
+ elm->be_free = 1;
+ elm->be_len = len;
+ ber_read(ber, elm->be_val, len);
+ break;
+ case BER_TYPE_OCTETSTRING:
+ case BER_TYPE_OBJECT:
+ elm->be_val = malloc(len + 1);
+ if (elm->be_val == NULL)
+ return -1;
+ elm->be_free = 1;
+ elm->be_len = len;
+ ber_read(ber, elm->be_val, len);
+ ((u_char *)elm->be_val)[len] = '\0';
+ break;
+ case BER_TYPE_NULL: /* no payload */
+ if (len != 0)
+ return -1;
+ break;
+ case BER_TYPE_SEQUENCE:
+ case BER_TYPE_SET:
+ if (elm->be_sub == NULL) {
+ if ((elm->be_sub = ber_get_element(0)) == NULL)
+ return -1;
+ }
+ next = elm->be_sub;
+ while (len > 0) {
+ r = ber_read_element(ber, next);
+ if (r == -1)
+ return -1;
+ len -= r;
+ if (len > 0 && next->be_next == NULL) {
+ if ((next->be_next = ber_get_element(0)) ==
+ NULL)
+ return -1;
+ }
+ next = next->be_next;
+ }
+ break;
+ }
+ return totlen;
+}
+
+static ssize_t
+ber_readbuf(struct ber *b, void *buf, size_t nbytes)
+{
+ size_t sz;
+ size_t len;
+
+ if (b->br_rbuf == NULL)
+ return -1;
+
+ sz = b->br_rend - b->br_rptr;
+ len = MINIMUM(nbytes, sz);
+ if (len == 0) {
+ errno = ECANCELED;
+ return (-1); /* end of buffer and parser wants more data */
+ }
+
+ bcopy(b->br_rptr, buf, len);
+ b->br_rptr += len;
+
+ return (len);
+}
+
+void
+ber_set_readbuf(struct ber *b, void *buf, size_t len)
+{
+ b->br_rbuf = b->br_rptr = buf;
+ b->br_rend = (u_int8_t *)buf + len;
+}
+
+ssize_t
+ber_get_writebuf(struct ber *b, void **buf)
+{
+ if (b->br_wbuf == NULL)
+ return -1;
+ *buf = b->br_wbuf;
+ return (b->br_wend - b->br_wbuf);
+}
+
+void
+ber_set_application(struct ber *b, unsigned long (*cb)(struct ber_element *))
+{
+ b->br_application = cb;
+}
+
+void
+ber_free(struct ber *b)
+{
+ free(b->br_wbuf);
+}
+
+static ssize_t
+ber_getc(struct ber *b, u_char *c)
+{
+ ssize_t r;
+ /*
+ * XXX calling read here is wrong in many ways. The most obvious one
+ * being that we will block till data arrives.
+ * But for now it is _good enough_ *gulp*
+ */
+ if (b->fd == -1)
+ r = ber_readbuf(b, c, 1);
+ else
+ r = read(b->fd, c, 1);
+ return r;
+}
+
+static ssize_t
+ber_read(struct ber *ber, void *buf, size_t len)
+{
+ u_char *b = buf;
+ ssize_t r, remain = len;
+
+ /*
+ * XXX calling read here is wrong in many ways. The most obvious one
+ * being that we will block till data arrives.
+ * But for now it is _good enough_ *gulp*
+ */
+
+ while (remain > 0) {
+ if (ber->fd == -1)
+ r = ber_readbuf(ber, b, remain);
+ else
+ r = read(ber->fd, b, remain);
+ if (r == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ return -1;
+ }
+ if (r == 0)
+ return (b - (u_char *)buf);
+ b += r;
+ remain -= r;
+ }
+ return (b - (u_char *)buf);
+}
diff --git a/usr.sbin/ypldap/ber.h b/usr.sbin/ypldap/ber.h
new file mode 100644
index 0000000..eec02d4
--- /dev/null
+++ b/usr.sbin/ypldap/ber.h
@@ -0,0 +1,129 @@
+/* $OpenBSD: ber.h,v 1.2 2008/12/29 15:48:13 aschrijver Exp $ */
+/* $FreeBSD$ */
+
+/*
+ * Copyright (c) 2007 Reyk Floeter <reyk@vantronix.net>
+ * Copyright (c) 2006, 2007 Claudio Jeker <claudio@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.
+ */
+
+struct ber_element {
+ struct ber_element *be_next;
+ unsigned long be_type;
+ unsigned long be_encoding;
+ size_t be_len;
+ int be_free;
+ u_int8_t be_class;
+ union {
+ struct ber_element *bv_sub;
+ void *bv_val;
+ long long bv_numeric;
+ } be_union;
+#define be_sub be_union.bv_sub
+#define be_val be_union.bv_val
+#define be_numeric be_union.bv_numeric
+};
+
+struct ber {
+ int fd;
+ u_char *br_wbuf;
+ u_char *br_wptr;
+ u_char *br_wend;
+ u_char *br_rbuf;
+ u_char *br_rptr;
+ u_char *br_rend;
+
+ unsigned long (*br_application)(struct ber_element *);
+};
+
+/* well-known ber_element types */
+#define BER_TYPE_DEFAULT ((unsigned long)-1)
+#define BER_TYPE_EOC 0
+#define BER_TYPE_BOOLEAN 1
+#define BER_TYPE_INTEGER 2
+#define BER_TYPE_BITSTRING 3
+#define BER_TYPE_OCTETSTRING 4
+#define BER_TYPE_NULL 5
+#define BER_TYPE_OBJECT 6
+#define BER_TYPE_ENUMERATED 10
+#define BER_TYPE_SEQUENCE 16
+#define BER_TYPE_SET 17
+
+/* ber classes */
+#define BER_CLASS_UNIVERSAL 0x0
+#define BER_CLASS_UNIV BER_CLASS_UNIVERSAL
+#define BER_CLASS_APPLICATION 0x1
+#define BER_CLASS_APP BER_CLASS_APPLICATION
+#define BER_CLASS_CONTEXT 0x2
+#define BER_CLASS_PRIVATE 0x3
+#define BER_CLASS_MASK 0x3
+
+/* common definitions */
+#define BER_MIN_OID_LEN 2 /* OBJECT */
+#define BER_MAX_OID_LEN 32 /* OBJECT */
+
+struct ber_oid {
+ u_int32_t bo_id[BER_MAX_OID_LEN + 1];
+ size_t bo_n;
+};
+
+__BEGIN_DECLS
+struct ber_element *ber_get_element(unsigned long);
+void ber_set_header(struct ber_element *, int,
+ unsigned long);
+void ber_link_elements(struct ber_element *,
+ struct ber_element *);
+struct ber_element *ber_unlink_elements(struct ber_element *);
+void ber_replace_elements(struct ber_element *,
+ struct ber_element *);
+struct ber_element *ber_add_sequence(struct ber_element *);
+struct ber_element *ber_add_set(struct ber_element *);
+struct ber_element *ber_add_integer(struct ber_element *, long long);
+int ber_get_integer(struct ber_element *, long long *);
+struct ber_element *ber_add_enumerated(struct ber_element *, long long);
+int ber_get_enumerated(struct ber_element *, long long *);
+struct ber_element *ber_add_boolean(struct ber_element *, int);
+int ber_get_boolean(struct ber_element *, int *);
+struct ber_element *ber_add_string(struct ber_element *, const char *);
+struct ber_element *ber_add_nstring(struct ber_element *, const char *,
+ size_t);
+int ber_get_string(struct ber_element *, char **);
+int ber_get_nstring(struct ber_element *, void **,
+ size_t *);
+struct ber_element *ber_add_bitstring(struct ber_element *, const void *,
+ size_t);
+int ber_get_bitstring(struct ber_element *, void **,
+ size_t *);
+struct ber_element *ber_add_null(struct ber_element *);
+int ber_get_null(struct ber_element *);
+struct ber_element *ber_add_eoc(struct ber_element *);
+int ber_get_eoc(struct ber_element *);
+struct ber_element *ber_add_oid(struct ber_element *, struct ber_oid *);
+struct ber_element *ber_add_noid(struct ber_element *, struct ber_oid *, int);
+struct ber_element *ber_add_oidstring(struct ber_element *, const char *);
+int ber_get_oid(struct ber_element *, struct ber_oid *);
+size_t ber_oid2ber(struct ber_oid *, u_int8_t *, size_t);
+int ber_string2oid(const char *, struct ber_oid *);
+struct ber_element *ber_printf_elements(struct ber_element *, char *, ...);
+int ber_scanf_elements(struct ber_element *, char *, ...);
+ssize_t ber_get_writebuf(struct ber *, void **);
+int ber_write_elements(struct ber *, struct ber_element *);
+void ber_set_readbuf(struct ber *, void *, size_t);
+struct ber_element *ber_read_elements(struct ber *, struct ber_element *);
+void ber_free_elements(struct ber_element *);
+size_t ber_calc_len(struct ber_element *);
+void ber_set_application(struct ber *,
+ unsigned long (*)(struct ber_element *));
+void ber_free(struct ber *);
+__END_DECLS
diff --git a/usr.sbin/ypldap/entries.c b/usr.sbin/ypldap/entries.c
new file mode 100644
index 0000000..1adce5a
--- /dev/null
+++ b/usr.sbin/ypldap/entries.c
@@ -0,0 +1,149 @@
+/* $OpenBSD: entries.c,v 1.3 2015/01/16 06:40:22 deraadt Exp $ */
+/* $FreeBSD$ */
+/*
+ * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@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/param.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/tree.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include "ypldap.h"
+
+void
+flatten_entries(struct env *env)
+{
+ size_t wrlen;
+ size_t len;
+ char *linep;
+ char *endp;
+ char *tmp;
+ struct userent *ue;
+ struct groupent *ge;
+
+ log_debug("flattening trees");
+ /*
+ * This takes all the line pointers in RB elements and
+ * concatenates them in a single string, to be able to
+ * implement next element lookup without tree traversal.
+ *
+ * An extra octet is alloced to make space for an additional NUL.
+ */
+ wrlen = env->sc_user_line_len;
+ if ((linep = calloc(1, env->sc_user_line_len + 1)) == NULL) {
+ /*
+ * XXX: try allocating a smaller chunk of memory
+ */
+ fatal("out of memory");
+ }
+ endp = linep;
+
+ RB_FOREACH(ue, user_name_tree, env->sc_user_names) {
+ /*
+ * we convert the first nul back to a column,
+ * copy the string and then convert it back to a nul.
+ */
+ ue->ue_line[strlen(ue->ue_line)] = ':';
+ log_debug("pushing line: %s", ue->ue_line);
+ len = strlen(ue->ue_line) + 1;
+ memcpy(endp, ue->ue_line, len);
+ endp[strcspn(endp, ":")] = '\0';
+ free(ue->ue_line);
+ ue->ue_line = endp;
+ endp += len;
+ wrlen -= len;
+
+ /*
+ * To save memory strdup(3) the netid_line which originally used
+ * LINE_WIDTH bytes
+ */
+ tmp = ue->ue_netid_line;
+ ue->ue_netid_line = strdup(tmp);
+ if (ue->ue_netid_line == NULL) {
+ fatal("out of memory");
+ }
+ free(tmp);
+ }
+ env->sc_user_lines = linep;
+
+ wrlen = env->sc_group_line_len;
+ if ((linep = calloc(1, env->sc_group_line_len + 1)) == NULL) {
+ /*
+ * XXX: try allocating a smaller chunk of memory
+ */
+ fatal("out of memory");
+ }
+ endp = linep;
+ RB_FOREACH(ge, group_name_tree, env->sc_group_names) {
+ /*
+ * we convert the first nul back to a column,
+ * copy the string and then convert it back to a nul.
+ */
+ ge->ge_line[strlen(ge->ge_line)] = ':';
+ log_debug("pushing line: %s", ge->ge_line);
+ len = strlen(ge->ge_line) + 1;
+ memcpy(endp, ge->ge_line, len);
+ endp[strcspn(endp, ":")] = '\0';
+ free(ge->ge_line);
+ ge->ge_line = endp;
+ endp += len;
+ wrlen -= len;
+ }
+ env->sc_group_lines = linep;
+}
+
+int
+userent_name_cmp(struct userent *ue1, struct userent *ue2)
+{
+ return (strcmp(ue1->ue_line, ue2->ue_line));
+}
+
+int
+userent_uid_cmp(struct userent *ue1, struct userent *ue2)
+{
+ return (ue1->ue_uid - ue2->ue_uid);
+}
+
+int
+groupent_name_cmp(struct groupent *ge1, struct groupent *ge2)
+{
+ return (strcmp(ge1->ge_line, ge2->ge_line));
+}
+
+int
+groupent_gid_cmp(struct groupent *ge1, struct groupent *ge2)
+{
+ return (ge1->ge_gid - ge2->ge_gid);
+}
+
+RB_GENERATE(user_name_tree, userent, ue_name_node, userent_name_cmp);
+RB_GENERATE(user_uid_tree, userent, ue_uid_node, userent_uid_cmp);
+RB_GENERATE(group_name_tree, groupent, ge_name_node, groupent_name_cmp);
+RB_GENERATE(group_gid_tree, groupent, ge_gid_node, groupent_gid_cmp);
diff --git a/usr.sbin/ypldap/ldapclient.c b/usr.sbin/ypldap/ldapclient.c
new file mode 100644
index 0000000..d522fb4
--- /dev/null
+++ b/usr.sbin/ypldap/ldapclient.c
@@ -0,0 +1,705 @@
+/* $OpenBSD: ldapclient.c,v 1.31 2014/11/16 23:24:44 tedu Exp $ */
+/* $FreeBSD$ */
+
+/*
+ * Copyright (c) 2008 Alexander Schrijver <aschrijver@openbsd.org>
+ * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@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/param.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/tree.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <netdb.h>
+#include <errno.h>
+#include <err.h>
+#include <event.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "aldap.h"
+#include "ypldap.h"
+
+void client_sig_handler(int, short, void *);
+void client_dispatch_dns(int, short, void *);
+void client_dispatch_parent(int, short, void *);
+void client_shutdown(void);
+void client_connect(int, short, void *);
+void client_configure(struct env *);
+void client_periodic_update(int, short, void *);
+int client_build_req(struct idm *, struct idm_req *, struct aldap_message *,
+ int, int);
+int client_search_idm(struct env *, struct idm *, struct aldap *,
+ char **, char *, int, int, enum imsg_type);
+int client_try_idm(struct env *, struct idm *);
+int client_addr_init(struct idm *);
+int client_addr_free(struct idm *);
+
+struct aldap *client_aldap_open(struct ypldap_addr *);
+
+/*
+ * dummy wrapper to provide aldap_init with its fd's.
+ */
+struct aldap *
+client_aldap_open(struct ypldap_addr *addr)
+{
+ int fd = -1;
+ struct ypldap_addr *p;
+
+ for (p = addr; p != NULL; p = p->next) {
+ char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
+ struct sockaddr *sa = (struct sockaddr *)&p->ss;
+
+ if (getnameinfo(sa, sa->sa_len, hbuf, sizeof(hbuf), sbuf,
+ sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV))
+ errx(1, "could not get numeric hostname");
+
+ if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0)
+ return NULL;
+
+ if (connect(fd, sa, sa->sa_len) == 0)
+ break;
+
+ warn("connect to %s port %s (%s) failed", hbuf, sbuf, "tcp");
+ close(fd);
+ }
+
+ if (fd == -1)
+ return NULL;
+
+ return aldap_init(fd);
+}
+
+int
+client_addr_init(struct idm *idm)
+{
+ struct sockaddr_in *sa_in;
+ struct sockaddr_in6 *sa_in6;
+ struct ypldap_addr *h;
+
+ for (h = idm->idm_addr; h != NULL; h = h->next) {
+ switch (h->ss.ss_family) {
+ case AF_INET:
+ sa_in = (struct sockaddr_in *)&h->ss;
+ if (ntohs(sa_in->sin_port) == 0)
+ sa_in->sin_port = htons(LDAP_PORT);
+ idm->idm_state = STATE_DNS_DONE;
+ break;
+ case AF_INET6:
+ sa_in6 = (struct sockaddr_in6 *)&h->ss;
+ if (ntohs(sa_in6->sin6_port) == 0)
+ sa_in6->sin6_port = htons(LDAP_PORT);
+ idm->idm_state = STATE_DNS_DONE;
+ break;
+ default:
+ fatalx("king bula sez: wrong AF in client_addr_init");
+ /* not reached */
+ }
+ }
+
+ return (0);
+}
+
+int
+client_addr_free(struct idm *idm)
+{
+ struct ypldap_addr *h, *p;
+
+ if (idm->idm_addr == NULL)
+ return (-1);
+
+ for (h = idm->idm_addr; h != NULL; h = p) {
+ p = h->next;
+ free(h);
+ }
+
+ idm->idm_addr = NULL;
+
+ return (0);
+}
+
+void
+client_sig_handler(int sig, short event, void *p)
+{
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ client_shutdown();
+ break;
+ default:
+ fatalx("unexpected signal");
+ }
+}
+
+void
+client_dispatch_dns(int fd, short events, void *p)
+{
+ struct imsg imsg;
+ u_int16_t dlen;
+ u_char *data;
+ struct ypldap_addr *h;
+ int n, wait_cnt = 0;
+ struct idm *idm;
+ int shut = 0;
+
+ struct env *env = p;
+ struct imsgev *iev = env->sc_iev_dns;
+ struct imsgbuf *ibuf = &iev->ibuf;
+
+ if ((events & (EV_READ | EV_WRITE)) == 0)
+ fatalx("unknown event");
+
+ if (events & EV_READ) {
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ fatal("imsg_read error");
+ if (n == 0)
+ shut = 1;
+ }
+ if (events & EV_WRITE) {
+ if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+ fatal("msgbuf_write");
+ if (n == 0)
+ shut = 1;
+ goto done;
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("client_dispatch_dns: imsg_get error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_HOST_DNS:
+ TAILQ_FOREACH(idm, &env->sc_idms, idm_entry)
+ if (idm->idm_id == imsg.hdr.peerid)
+ break;
+ if (idm == NULL) {
+ log_warnx("IMSG_HOST_DNS with invalid peerID");
+ break;
+ }
+ if (idm->idm_addr != NULL) {
+ log_warnx("IMSG_HOST_DNS but addr != NULL!");
+ break;
+ }
+
+ dlen = imsg.hdr.len - IMSG_HEADER_SIZE;
+ if (dlen == 0) { /* no data -> temp error */
+ idm->idm_state = STATE_DNS_TEMPFAIL;
+ break;
+ }
+
+ data = (u_char *)imsg.data;
+ while (dlen >= sizeof(struct sockaddr_storage)) {
+ if ((h = calloc(1, sizeof(struct ypldap_addr))) ==
+ NULL)
+ fatal(NULL);
+ memcpy(&h->ss, data, sizeof(h->ss));
+
+ if (idm->idm_addr == NULL)
+ h->next = NULL;
+ else
+ h->next = idm->idm_addr;
+
+ idm->idm_addr = h;
+
+ data += sizeof(h->ss);
+ dlen -= sizeof(h->ss);
+ }
+ if (dlen != 0)
+ fatalx("IMSG_HOST_DNS: dlen != 0");
+
+ client_addr_init(idm);
+
+ break;
+ default:
+ break;
+ }
+ imsg_free(&imsg);
+ }
+
+ TAILQ_FOREACH(idm, &env->sc_idms, idm_entry) {
+ if (client_try_idm(env, idm) == -1)
+ idm->idm_state = STATE_LDAP_FAIL;
+
+ if (idm->idm_state < STATE_LDAP_DONE)
+ wait_cnt++;
+ }
+ if (wait_cnt == 0)
+ imsg_compose_event(env->sc_iev, IMSG_END_UPDATE, 0, 0, -1,
+ NULL, 0);
+
+done:
+ if (!shut)
+ imsg_event_add(iev);
+ else {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&iev->ev);
+ event_loopexit(NULL);
+ }
+}
+
+void
+client_dispatch_parent(int fd, short events, void *p)
+{
+ int n;
+ int shut = 0;
+ struct imsg imsg;
+ struct env *env = p;
+ struct imsgev *iev = env->sc_iev;
+ struct imsgbuf *ibuf = &iev->ibuf;
+
+ if ((events & (EV_READ | EV_WRITE)) == 0)
+ fatalx("unknown event");
+
+ if (events & EV_READ) {
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ fatal("imsg_read error");
+ if (n == 0)
+ shut = 1;
+ }
+ if (events & EV_WRITE) {
+ if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+ fatal("msgbuf_write");
+ if (n == 0)
+ shut = 1;
+ goto done;
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("client_dispatch_parent: imsg_get error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_CONF_START: {
+ struct env params;
+
+ if (env->sc_flags & F_CONFIGURING) {
+ log_warnx("configuration already in progress");
+ break;
+ }
+ memcpy(&params, imsg.data, sizeof(params));
+ log_debug("configuration starting");
+ env->sc_flags |= F_CONFIGURING;
+ purge_config(env);
+ memcpy(&env->sc_conf_tv, &params.sc_conf_tv,
+ sizeof(env->sc_conf_tv));
+ env->sc_flags |= params.sc_flags;
+ break;
+ }
+ case IMSG_CONF_IDM: {
+ struct idm *idm;
+
+ if (!(env->sc_flags & F_CONFIGURING))
+ break;
+ if ((idm = calloc(1, sizeof(*idm))) == NULL)
+ fatal(NULL);
+ memcpy(idm, imsg.data, sizeof(*idm));
+ idm->idm_env = env;
+ TAILQ_INSERT_TAIL(&env->sc_idms, idm, idm_entry);
+ break;
+ }
+ case IMSG_CONF_END:
+ env->sc_flags &= ~F_CONFIGURING;
+ log_debug("applying configuration");
+ client_configure(env);
+ break;
+ default:
+ log_debug("client_dispatch_parent: unexpect imsg %d",
+ imsg.hdr.type);
+
+ break;
+ }
+ imsg_free(&imsg);
+ }
+
+done:
+ if (!shut)
+ imsg_event_add(iev);
+ else {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&iev->ev);
+ event_loopexit(NULL);
+ }
+}
+
+void
+client_shutdown(void)
+{
+ log_info("ldap client exiting");
+ _exit(0);
+}
+
+pid_t
+ldapclient(int pipe_main2client[2])
+{
+ pid_t pid, dns_pid;
+ int pipe_dns[2];
+ struct passwd *pw;
+ struct event ev_sigint;
+ struct event ev_sigterm;
+ struct env env;
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("cannot fork");
+ break;
+ case 0:
+ break;
+ default:
+ return (pid);
+ }
+
+ bzero(&env, sizeof(env));
+ TAILQ_INIT(&env.sc_idms);
+
+ if ((pw = getpwnam(YPLDAP_USER)) == NULL)
+ fatal("getpwnam");
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_dns) == -1)
+ fatal("socketpair");
+ dns_pid = ypldap_dns(pipe_dns, pw);
+ close(pipe_dns[1]);
+
+#ifndef DEBUG
+ if (chroot(pw->pw_dir) == -1)
+ fatal("chroot");
+ if (chdir("/") == -1)
+ fatal("chdir");
+#else
+#warning disabling chrooting in DEBUG mode
+#endif
+ setproctitle("ldap client");
+ ypldap_process = PROC_CLIENT;
+
+#ifndef DEBUG
+ if (setgroups(1, &pw->pw_gid) ||
+ setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
+ setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+ fatal("cannot drop privileges");
+#else
+#warning disabling privilege revocation in DEBUG mode
+#endif
+
+ event_init();
+ signal(SIGPIPE, SIG_IGN);
+ signal_set(&ev_sigint, SIGINT, client_sig_handler, NULL);
+ signal_set(&ev_sigterm, SIGTERM, client_sig_handler, NULL);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+
+ close(pipe_main2client[0]);
+ if ((env.sc_iev = calloc(1, sizeof(*env.sc_iev))) == NULL)
+ fatal(NULL);
+ if ((env.sc_iev_dns = calloc(1, sizeof(*env.sc_iev_dns))) == NULL)
+ fatal(NULL);
+
+ env.sc_iev->events = EV_READ;
+ env.sc_iev->data = &env;
+ imsg_init(&env.sc_iev->ibuf, pipe_main2client[1]);
+ env.sc_iev->handler = client_dispatch_parent;
+ event_set(&env.sc_iev->ev, env.sc_iev->ibuf.fd, env.sc_iev->events,
+ env.sc_iev->handler, &env);
+ event_add(&env.sc_iev->ev, NULL);
+
+ env.sc_iev_dns->events = EV_READ;
+ env.sc_iev_dns->data = &env;
+ imsg_init(&env.sc_iev_dns->ibuf, pipe_dns[0]);
+ env.sc_iev_dns->handler = client_dispatch_dns;
+ event_set(&env.sc_iev_dns->ev, env.sc_iev_dns->ibuf.fd,
+ env.sc_iev_dns->events, env.sc_iev_dns->handler, &env);
+ event_add(&env.sc_iev_dns->ev, NULL);
+
+ event_dispatch();
+ client_shutdown();
+
+ return (0);
+
+}
+
+int
+client_build_req(struct idm *idm, struct idm_req *ir, struct aldap_message *m,
+ int min_attr, int max_attr)
+{
+ char **ldap_attrs;
+ int i, k;
+
+ bzero(ir, sizeof(*ir));
+ for (i = min_attr; i < max_attr; i++) {
+ if (idm->idm_flags & F_FIXED_ATTR(i)) {
+ if (strlcat(ir->ir_line, idm->idm_attrs[i],
+ sizeof(ir->ir_line)) >= sizeof(ir->ir_line))
+ /*
+ * entry yields a line > 1024, trash it.
+ */
+ return (-1);
+
+ if (i == ATTR_UID) {
+ ir->ir_key.ik_uid = strtonum(
+ idm->idm_attrs[i], 0,
+ UID_MAX, NULL);
+ } else if (i == ATTR_GR_GID) {
+ ir->ir_key.ik_gid = strtonum(
+ idm->idm_attrs[i], 0,
+ GID_MAX, NULL);
+ }
+ } else if (idm->idm_list & F_LIST(i)) {
+ aldap_match_attr(m, idm->idm_attrs[i], &ldap_attrs);
+ for (k = 0; k >= 0 && ldap_attrs && ldap_attrs[k] != NULL; k++) {
+ /* XXX: Fail when attributes have illegal characters e.g. ',' */
+ if (strlcat(ir->ir_line, ldap_attrs[k],
+ sizeof(ir->ir_line)) >= sizeof(ir->ir_line))
+ continue;
+ if (ldap_attrs[k+1] != NULL)
+ if (strlcat(ir->ir_line, ",",
+ sizeof(ir->ir_line))
+ >= sizeof(ir->ir_line)) {
+ aldap_free_attr(ldap_attrs);
+ return (-1);
+ }
+ }
+ aldap_free_attr(ldap_attrs);
+ } else {
+ if (aldap_match_attr(m, idm->idm_attrs[i], &ldap_attrs) == -1)
+ return (-1);
+ if (ldap_attrs[0] == NULL)
+ return (-1);
+ if (strlcat(ir->ir_line, ldap_attrs[0],
+ sizeof(ir->ir_line)) >= sizeof(ir->ir_line)) {
+ aldap_free_attr(ldap_attrs);
+ return (-1);
+ }
+ if (i == ATTR_UID) {
+ ir->ir_key.ik_uid = strtonum(
+ ldap_attrs[0], 0, UID_MAX, NULL);
+ } else if (i == ATTR_GR_GID) {
+ ir->ir_key.ik_uid = strtonum(
+ ldap_attrs[0], 0, GID_MAX, NULL);
+ }
+ aldap_free_attr(ldap_attrs);
+ }
+
+ if (i + 1 != max_attr)
+ if (strlcat(ir->ir_line, ":",
+ sizeof(ir->ir_line)) >= sizeof(ir->ir_line))
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+client_search_idm(struct env *env, struct idm *idm, struct aldap *al,
+ char **attrs, char *filter, int min_attr, int max_attr,
+ enum imsg_type type)
+{
+ struct idm_req ir;
+ struct aldap_message *m;
+ struct aldap_page_control *pg = NULL;
+ const char *errstr;
+ char *dn;
+
+ dn = idm->idm_basedn;
+ if (type == IMSG_GRP_ENTRY && idm->idm_groupdn[0] != '\0')
+ dn = idm->idm_groupdn;
+
+ do {
+ if (aldap_search(al, dn, LDAP_SCOPE_SUBTREE,
+ filter, attrs, 0, 0, 0, pg) == -1) {
+ aldap_get_errno(al, &errstr);
+ log_debug("%s", errstr);
+ return (-1);
+ }
+
+ if (pg != NULL) {
+ aldap_freepage(pg);
+ pg = NULL;
+ }
+
+ while ((m = aldap_parse(al)) != NULL) {
+ if (al->msgid != m->msgid) {
+ goto fail;
+ }
+
+ if (m->message_type == LDAP_RES_SEARCH_RESULT) {
+ if (m->page != NULL && m->page->cookie_len != 0)
+ pg = m->page;
+ else
+ pg = NULL;
+
+ aldap_freemsg(m);
+ break;
+ }
+
+ if (m->message_type != LDAP_RES_SEARCH_ENTRY) {
+ goto fail;
+ }
+
+ if (client_build_req(idm, &ir, m, min_attr, max_attr) == 0)
+ imsg_compose_event(env->sc_iev, type, 0, 0, -1,
+ &ir, sizeof(ir));
+
+ aldap_freemsg(m);
+ }
+ } while (pg != NULL);
+
+ return (0);
+
+fail:
+ aldap_freemsg(m);
+ if (pg != NULL) {
+ aldap_freepage(pg);
+ }
+
+ return (-1);
+}
+
+int
+client_try_idm(struct env *env, struct idm *idm)
+{
+ const char *where;
+ char *attrs[ATTR_MAX+1];
+ int i, j;
+ struct aldap_message *m;
+ struct aldap *al;
+
+ where = "connect";
+ if ((al = client_aldap_open(idm->idm_addr)) == NULL)
+ return (-1);
+
+ if (idm->idm_flags & F_NEEDAUTH) {
+ where = "binding";
+ if (aldap_bind(al, idm->idm_binddn, idm->idm_bindcred) == -1)
+ goto bad;
+
+ where = "parsing";
+ if ((m = aldap_parse(al)) == NULL)
+ goto bad;
+ where = "verifying msgid";
+ if (al->msgid != m->msgid) {
+ aldap_freemsg(m);
+ goto bad;
+ }
+ aldap_freemsg(m);
+ }
+
+ bzero(attrs, sizeof(attrs));
+ for (i = 0, j = 0; i < ATTR_MAX; i++) {
+ if (idm->idm_flags & F_FIXED_ATTR(i))
+ continue;
+ attrs[j++] = idm->idm_attrs[i];
+ }
+ attrs[j] = NULL;
+
+ /*
+ * build password line.
+ */
+ where = "search";
+ log_debug("searching password entries");
+ if (client_search_idm(env, idm, al, attrs,
+ idm->idm_filters[FILTER_USER], 0, ATTR_MAX, IMSG_PW_ENTRY) == -1)
+ goto bad;
+
+ bzero(attrs, sizeof(attrs));
+ for (i = ATTR_GR_MIN, j = 0; i < ATTR_GR_MAX; i++) {
+ if (idm->idm_flags & F_FIXED_ATTR(i))
+ continue;
+ attrs[j++] = idm->idm_attrs[i];
+ }
+ attrs[j] = NULL;
+
+ /*
+ * build group line.
+ */
+ where = "search";
+ log_debug("searching group entries");
+ if (client_search_idm(env, idm, al, attrs,
+ idm->idm_filters[FILTER_GROUP], ATTR_GR_MIN, ATTR_GR_MAX,
+ IMSG_GRP_ENTRY) == -1)
+ goto bad;
+
+ aldap_close(al);
+
+ idm->idm_state = STATE_LDAP_DONE;
+
+ return (0);
+bad:
+ aldap_close(al);
+ log_debug("directory %s errored out in %s", idm->idm_name, where);
+ return (-1);
+}
+
+void
+client_periodic_update(int fd, short event, void *p)
+{
+ struct env *env = p;
+
+ struct idm *idm;
+ int fail_cnt = 0;
+
+ /* If LDAP isn't finished, notify the master process to trash the
+ * update. */
+ TAILQ_FOREACH(idm, &env->sc_idms, idm_entry) {
+ if (idm->idm_state < STATE_LDAP_DONE)
+ fail_cnt++;
+
+ idm->idm_state = STATE_NONE;
+
+ client_addr_free(idm);
+ }
+ if (fail_cnt > 0) {
+ log_debug("trash the update");
+ imsg_compose_event(env->sc_iev, IMSG_TRASH_UPDATE, 0, 0, -1,
+ NULL, 0);
+ }
+
+ client_configure(env);
+}
+
+void
+client_configure(struct env *env)
+{
+ struct timeval tv;
+ struct idm *idm;
+ u_int16_t dlen;
+
+ log_debug("connecting to directories");
+
+ imsg_compose_event(env->sc_iev, IMSG_START_UPDATE, 0, 0, -1, NULL, 0);
+
+ /* Start the DNS lookups */
+ TAILQ_FOREACH(idm, &env->sc_idms, idm_entry) {
+ dlen = strlen(idm->idm_name) + 1;
+ imsg_compose_event(env->sc_iev_dns, IMSG_HOST_DNS, idm->idm_id,
+ 0, -1, idm->idm_name, dlen);
+ }
+
+ tv.tv_sec = env->sc_conf_tv.tv_sec;
+ tv.tv_usec = env->sc_conf_tv.tv_usec;
+ evtimer_set(&env->sc_conf_ev, client_periodic_update, env);
+ evtimer_add(&env->sc_conf_ev, &tv);
+}
diff --git a/usr.sbin/ypldap/log.c b/usr.sbin/ypldap/log.c
new file mode 100644
index 0000000..7fec6f7
--- /dev/null
+++ b/usr.sbin/ypldap/log.c
@@ -0,0 +1,162 @@
+/* $OpenBSD: log.c,v 1.1 2008/06/26 15:10:01 pyr Exp $ */
+/* $FreeBSD$ */
+
+/*
+ * Copyright (c) 2003, 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 OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+
+void log_init(int);
+void log_warn(const char *, ...);
+void log_warnx(const char *, ...);
+void log_info(const char *, ...);
+void log_debug(const char *, ...);
+__dead2 void fatal(const char *);
+__dead2 void fatalx(const char *);
+
+int debug;
+
+void vlog(int, const char *, va_list);
+void logit(int, const char *, ...);
+
+void
+log_init(int n_debug)
+{
+ extern char *__progname;
+
+ debug = n_debug;
+
+ if (!debug)
+ openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON);
+
+ tzset();
+}
+
+void
+logit(int pri, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(pri, fmt, ap);
+ va_end(ap);
+}
+
+void
+vlog(int pri, const char *fmt, va_list ap)
+{
+ char *nfmt;
+
+ if (debug) {
+ /* best effort in out of mem situations */
+ if (asprintf(&nfmt, "%s\n", fmt) == -1) {
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ } else {
+ vfprintf(stderr, nfmt, ap);
+ free(nfmt);
+ }
+ fflush(stderr);
+ } else
+ vsyslog(pri, fmt, ap);
+}
+
+
+void
+log_warn(const char *emsg, ...)
+{
+ char *nfmt;
+ va_list ap;
+
+ /* best effort to even work in out of memory situations */
+ if (emsg == NULL)
+ logit(LOG_CRIT, "%s", strerror(errno));
+ else {
+ va_start(ap, emsg);
+
+ if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) {
+ /* we tried it... */
+ vlog(LOG_CRIT, emsg, ap);
+ logit(LOG_CRIT, "%s", strerror(errno));
+ } else {
+ vlog(LOG_CRIT, nfmt, ap);
+ free(nfmt);
+ }
+ va_end(ap);
+ }
+}
+
+void
+log_warnx(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_CRIT, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_info(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_INFO, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_debug(const char *emsg, ...)
+{
+ va_list ap;
+
+ if (debug > 1) {
+ va_start(ap, emsg);
+ vlog(LOG_DEBUG, emsg, ap);
+ va_end(ap);
+ }
+}
+
+void
+fatal(const char *emsg)
+{
+ if (emsg == NULL)
+ logit(LOG_CRIT, "fatal: %s", strerror(errno));
+ else
+ if (errno)
+ logit(LOG_CRIT, "fatal: %s: %s",
+ emsg, strerror(errno));
+ else
+ logit(LOG_CRIT, "fatal: %s", emsg);
+
+ exit(1);
+}
+
+void
+fatalx(const char *emsg)
+{
+ errno = 0;
+ fatal(emsg);
+}
diff --git a/usr.sbin/ypldap/parse.y b/usr.sbin/ypldap/parse.y
new file mode 100644
index 0000000..15d3ff2
--- /dev/null
+++ b/usr.sbin/ypldap/parse.y
@@ -0,0 +1,838 @@
+/* $OpenBSD: parse.y,v 1.18 2015/01/16 06:40:22 deraadt Exp $ */
+/* $FreeBSD$ */
+
+/*
+ * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
+ * Copyright (c) 2007, 2008 Reyk Floeter <reyk@openbsd.org>
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
+ * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
+ * 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.
+ *
+ * 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/param.h>
+#include <sys/time.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "ypldap.h"
+
+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, *topfile;
+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 *, ...)
+ __attribute__((__format__ (printf, 1, 2)))
+ __attribute__((__nonnull__ (1)));
+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 *);
+
+struct env *conf = NULL;
+struct idm *idm = NULL;
+static int errors = 0;
+
+typedef struct {
+ union {
+ int64_t number;
+ char *string;
+ } v;
+ int lineno;
+} YYSTYPE;
+
+%}
+
+%token SERVER FILTER ATTRIBUTE BASEDN BINDDN GROUPDN BINDCRED MAPS CHANGE DOMAIN PROVIDE
+%token USER GROUP TO EXPIRE HOME SHELL GECOS UID GID INTERVAL
+%token PASSWD NAME FIXED LIST GROUPNAME GROUPPASSWD GROUPGID MAP
+%token INCLUDE DIRECTORY CLASS PORT ERROR GROUPMEMBERS
+%token <v.string> STRING
+%token <v.number> NUMBER
+%type <v.number> opcode attribute
+%type <v.string> port
+
+%%
+
+grammar : /* empty */
+ | grammar '\n'
+ | grammar include '\n'
+ | grammar varset '\n'
+ | grammar directory '\n'
+ | grammar main '\n'
+ | grammar error '\n' { file->errors++; }
+ ;
+
+nl : '\n' optnl
+ ;
+
+optnl : '\n' optnl
+ | /* empty */
+ ;
+
+
+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');
+ }
+ ;
+
+varset : STRING '=' STRING {
+ if (symset($1, $3, 0) == -1)
+ fatal("cannot store variable");
+ free($1);
+ free($3);
+ }
+ ;
+
+port : /* empty */ { $$ = NULL; }
+ | PORT STRING { $$ = $2; }
+ ;
+
+opcode : GROUP { $$ = 0; }
+ | PASSWD { $$ = 1; }
+ ;
+
+
+attribute : NAME { $$ = 0; }
+ | PASSWD { $$ = 1; }
+ | UID { $$ = 2; }
+ | GID { $$ = 3; }
+ | CLASS { $$ = 4; }
+ | CHANGE { $$ = 5; }
+ | EXPIRE { $$ = 6; }
+ | GECOS { $$ = 7; }
+ | HOME { $$ = 8; }
+ | SHELL { $$ = 9; }
+ | GROUPNAME { $$ = 10; }
+ | GROUPPASSWD { $$ = 11; }
+ | GROUPGID { $$ = 12; }
+ | GROUPMEMBERS { $$ = 13; }
+ ;
+
+diropt : BINDDN STRING {
+ idm->idm_flags |= F_NEEDAUTH;
+ if (strlcpy(idm->idm_binddn, $2,
+ sizeof(idm->idm_binddn)) >=
+ sizeof(idm->idm_binddn)) {
+ yyerror("directory binddn truncated");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ | BINDCRED STRING {
+ idm->idm_flags |= F_NEEDAUTH;
+ if (strlcpy(idm->idm_bindcred, $2,
+ sizeof(idm->idm_bindcred)) >=
+ sizeof(idm->idm_bindcred)) {
+ yyerror("directory bindcred truncated");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ | BASEDN STRING {
+ if (strlcpy(idm->idm_basedn, $2,
+ sizeof(idm->idm_basedn)) >=
+ sizeof(idm->idm_basedn)) {
+ yyerror("directory basedn truncated");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ | GROUPDN STRING {
+ if(strlcpy(idm->idm_groupdn, $2,
+ sizeof(idm->idm_groupdn)) >=
+ sizeof(idm->idm_groupdn)) {
+ yyerror("directory groupdn truncated");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ | opcode FILTER STRING {
+ if (strlcpy(idm->idm_filters[$1], $3,
+ sizeof(idm->idm_filters[$1])) >=
+ sizeof(idm->idm_filters[$1])) {
+ yyerror("filter truncated");
+ free($3);
+ YYERROR;
+ }
+ free($3);
+ }
+ | ATTRIBUTE attribute MAPS TO STRING {
+ if (strlcpy(idm->idm_attrs[$2], $5,
+ sizeof(idm->idm_attrs[$2])) >=
+ sizeof(idm->idm_attrs[$2])) {
+ yyerror("attribute truncated");
+ free($5);
+ YYERROR;
+ }
+ free($5);
+ }
+ | FIXED ATTRIBUTE attribute STRING {
+ if (strlcpy(idm->idm_attrs[$3], $4,
+ sizeof(idm->idm_attrs[$3])) >=
+ sizeof(idm->idm_attrs[$3])) {
+ yyerror("attribute truncated");
+ free($4);
+ YYERROR;
+ }
+ idm->idm_flags |= F_FIXED_ATTR($3);
+ free($4);
+ }
+ | LIST attribute MAPS TO STRING {
+ if (strlcpy(idm->idm_attrs[$2], $5,
+ sizeof(idm->idm_attrs[$2])) >=
+ sizeof(idm->idm_attrs[$2])) {
+ yyerror("attribute truncated");
+ free($5);
+ YYERROR;
+ }
+ idm->idm_list |= F_LIST($2);
+ free($5);
+ }
+ ;
+
+directory : DIRECTORY STRING port {
+ if ((idm = calloc(1, sizeof(*idm))) == NULL)
+ fatal(NULL);
+ idm->idm_id = conf->sc_maxid++;
+
+ if (strlcpy(idm->idm_name, $2,
+ sizeof(idm->idm_name)) >=
+ sizeof(idm->idm_name)) {
+ yyerror("attribute truncated");
+ free($2);
+ YYERROR;
+ }
+
+ free($2);
+ } '{' optnl diropts '}' {
+ TAILQ_INSERT_TAIL(&conf->sc_idms, idm, idm_entry);
+ idm = NULL;
+ }
+ ;
+
+main : INTERVAL NUMBER {
+ conf->sc_conf_tv.tv_sec = $2;
+ conf->sc_conf_tv.tv_usec = 0;
+ }
+ | DOMAIN STRING {
+ if (strlcpy(conf->sc_domainname, $2,
+ sizeof(conf->sc_domainname)) >=
+ sizeof(conf->sc_domainname)) {
+ yyerror("domainname truncated");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ | PROVIDE MAP STRING {
+ if (strcmp($3, "passwd.byname") == 0)
+ conf->sc_flags |= YPMAP_PASSWD_BYNAME;
+ else if (strcmp($3, "passwd.byuid") == 0)
+ conf->sc_flags |= YPMAP_PASSWD_BYUID;
+ else if (strcmp($3, "master.passwd.byname") == 0)
+ conf->sc_flags |= YPMAP_MASTER_PASSWD_BYNAME;
+ else if (strcmp($3, "master.passwd.byuid") == 0)
+ conf->sc_flags |= YPMAP_MASTER_PASSWD_BYUID;
+ else if (strcmp($3, "group.byname") == 0)
+ conf->sc_flags |= YPMAP_GROUP_BYNAME;
+ else if (strcmp($3, "group.bygid") == 0)
+ conf->sc_flags |= YPMAP_GROUP_BYGID;
+ else if (strcmp($3, "netid.byname") == 0)
+ conf->sc_flags |= YPMAP_NETID_BYNAME;
+ else {
+ yyerror("unsupported map type: %s", $3);
+ free($3);
+ YYERROR;
+ }
+ free($3);
+ }
+ ;
+
+diropts : diropts diropt nl
+ | diropt optnl
+ ;
+
+%%
+
+struct keywords {
+ const char *k_name;
+ int k_val;
+};
+
+int
+yyerror(const char *fmt, ...)
+{
+ va_list ap;
+ char *msg;
+
+ file->errors++;
+ va_start(ap, fmt);
+ if (vasprintf(&msg, fmt, ap) == -1)
+ fatalx("yyerror vasprintf");
+ va_end(ap);
+ logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
+ free(msg);
+ 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[] = {
+ { "attribute", ATTRIBUTE },
+ { "basedn", BASEDN },
+ { "bindcred", BINDCRED },
+ { "binddn", BINDDN },
+ { "change", CHANGE },
+ { "class", CLASS },
+ { "directory", DIRECTORY },
+ { "domain", DOMAIN },
+ { "expire", EXPIRE },
+ { "filter", FILTER },
+ { "fixed", FIXED },
+ { "gecos", GECOS },
+ { "gid", GID },
+ { "group", GROUP },
+ { "groupdn", GROUPDN },
+ { "groupgid", GROUPGID },
+ { "groupmembers", GROUPMEMBERS },
+ { "groupname", GROUPNAME },
+ { "grouppasswd", GROUPPASSWD },
+ { "home", HOME },
+ { "include", INCLUDE },
+ { "interval", INTERVAL },
+ { "list", LIST },
+ { "map", MAP },
+ { "maps", MAPS },
+ { "name", NAME },
+ { "passwd", PASSWD },
+ { "port", PORT },
+ { "provide", PROVIDE },
+ { "server", SERVER },
+ { "shell", SHELL },
+ { "to", TO },
+ { "uid", UID },
+ { "user", USER },
+ };
+ const struct keywords *p;
+
+ p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
+ sizeof(keywords[0]), kw_cmp);
+
+ if (p)
+ return (p->k_val);
+ else
+ return (STRING);
+}
+
+#define MAXPUSHBACK 128
+
+u_char *parsebuf;
+int parseindex;
+u_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 (file == topfile || 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 (file == topfile || 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)
+{
+ u_char buf[8096];
+ u_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++ = 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') {
+ file->lineno++;
+ continue;
+ } else
+ lungetc(next);
+ } else if (c == quotec) {
+ *p = '\0';
+ break;
+ } else if (c == '\0') {
+ yyerror("syntax error");
+ return (findeol());
+ }
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ *p++ = c;
+ }
+ yylval.v.string = strdup(buf);
+ if (yylval.v.string == NULL)
+ err(1, "yylex: strdup");
+ return (STRING);
+ }
+
+#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 != ','))
+
+ 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)) {
+ log_warn("cannot stat %s", fname);
+ return (-1);
+ }
+ if (st.st_uid != 0 && st.st_uid != getuid()) {
+ log_warnx("%s: owner not root or current user", fname);
+ return (-1);
+ }
+ if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
+ log_warnx("%s: group writable or world read/writable", fname);
+ return (-1);
+ }
+ return (0);
+}
+
+struct file *
+pushfile(const char *name, int secret)
+{
+ struct file *nfile;
+
+ if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
+ log_warn("malloc");
+ return (NULL);
+ }
+ if ((nfile->name = strdup(name)) == NULL) {
+ log_warn("malloc");
+ free(nfile);
+ return (NULL);
+ }
+ if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
+ log_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 (file ? 0 : EOF);
+}
+
+int
+parse_config(struct env *x_conf, const char *filename, int opts)
+{
+ struct sym *sym, *next;
+
+ conf = x_conf;
+ bzero(conf, sizeof(*conf));
+
+ TAILQ_INIT(&conf->sc_idms);
+ conf->sc_conf_tv.tv_sec = DEFAULT_INTERVAL;
+ conf->sc_conf_tv.tv_usec = 0;
+
+ errors = 0;
+
+ if ((file = pushfile(filename, 1)) == NULL) {
+ return (-1);
+ }
+ topfile = file;
+
+ /*
+ * parse configuration
+ */
+ setservent(1);
+ yyparse();
+ endservent();
+ errors = file->errors;
+ popfile();
+
+ /* Free macros and check which have not been used. */
+ for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) {
+ next = TAILQ_NEXT(sym, entry);
+ if ((opts & YPLDAP_OPT_VERBOSE) && !sym->used)
+ fprintf(stderr, "warning: macro '%s' not "
+ "used\n", sym->nam);
+ if (!sym->persist) {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entry);
+ free(sym);
+ }
+ }
+
+ if (errors) {
+ return (-1);
+ }
+
+ return (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
+cmdline_symset(char *s)
+{
+ char *sym, *val;
+ int ret;
+ size_t len;
+
+ if ((val = strrchr(s, '=')) == NULL)
+ return (-1);
+
+ len = strlen(s) - strlen(val) + 1;
+ if ((sym = malloc(len)) == NULL)
+ errx(1, "cmdline_symset: malloc");
+
+ (void)strlcpy(sym, s, len);
+
+ 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);
+}
diff --git a/usr.sbin/ypldap/yp.c b/usr.sbin/ypldap/yp.c
new file mode 100644
index 0000000..e461136
--- /dev/null
+++ b/usr.sbin/ypldap/yp.c
@@ -0,0 +1,652 @@
+/* $OpenBSD: yp.c,v 1.14 2015/02/11 01:26:00 pelikan Exp $ */
+/* $FreeBSD$ */
+/*
+ * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@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/param.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <sys/tree.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include <rpc/rpc.h>
+#include <rpc/xdr.h>
+#include <rpc/pmap_clnt.h>
+#include <rpc/pmap_prot.h>
+#include <rpc/pmap_rmt.h>
+#include <rpcsvc/yp.h>
+#include <rpcsvc/ypclnt.h>
+
+#include "ypldap.h"
+
+void yp_dispatch(struct svc_req *, SVCXPRT *);
+void yp_disable_events(void);
+void yp_fd_event(int, short, void *);
+int yp_check(struct svc_req *);
+int yp_valid_domain(char *, struct ypresp_val *);
+void yp_make_val(struct ypresp_val *, char *, int);
+void yp_make_keyval(struct ypresp_key_val *, char *, char *);
+
+static struct env *env;
+
+struct yp_event {
+ TAILQ_ENTRY(yp_event) ye_entry;
+ struct event ye_event;
+};
+
+struct yp_data {
+ SVCXPRT *yp_trans_udp;
+ SVCXPRT *yp_trans_tcp;
+ TAILQ_HEAD(, yp_event) yd_events;
+};
+
+void
+yp_disable_events(void)
+{
+ struct yp_event *ye;
+
+ while ((ye = TAILQ_FIRST(&env->sc_yp->yd_events)) != NULL) {
+ TAILQ_REMOVE(&env->sc_yp->yd_events, ye, ye_entry);
+ event_del(&ye->ye_event);
+ free(ye);
+ }
+}
+
+void
+yp_enable_events(void)
+{
+ int i;
+ extern fd_set svc_fdset;
+ struct yp_event *ye;
+
+ for (i = 0; i < getdtablesize(); i++) {
+ if (FD_ISSET(i, &svc_fdset)) {
+ if ((ye = calloc(1, sizeof(*ye))) == NULL)
+ fatal(NULL);
+ event_set(&ye->ye_event, i, EV_READ, yp_fd_event, NULL);
+ event_add(&ye->ye_event, NULL);
+ TAILQ_INSERT_TAIL(&env->sc_yp->yd_events, ye, ye_entry);
+ }
+ }
+}
+
+void
+yp_fd_event(int fd, short event, void *p)
+{
+ svc_getreq_common(fd);
+ yp_disable_events();
+ yp_enable_events();
+}
+
+void
+yp_init(struct env *x_env)
+{
+ struct yp_data *yp;
+
+ if ((yp = calloc(1, sizeof(*yp))) == NULL)
+ fatal(NULL);
+ TAILQ_INIT(&yp->yd_events);
+
+ env = x_env;
+ env->sc_yp = yp;
+
+ (void)pmap_unset(YPPROG, YPVERS);
+
+ if ((yp->yp_trans_udp = svcudp_create(RPC_ANYSOCK)) == NULL)
+ fatal("cannot create udp service");
+ if ((yp->yp_trans_tcp = svctcp_create(RPC_ANYSOCK, 0, 0)) == NULL)
+ fatal("cannot create tcp service");
+
+ if (!svc_register(yp->yp_trans_udp, YPPROG, YPVERS,
+ yp_dispatch, IPPROTO_UDP)) {
+ fatal("unable to register (YPPROG, YPVERS, udp)");
+ }
+ if (!svc_register(yp->yp_trans_tcp, YPPROG, YPVERS,
+ yp_dispatch, IPPROTO_TCP)) {
+ fatal("unable to register (YPPROG, YPVERS, tcp)");
+ }
+}
+
+/*
+ * lots of inspiration from ypserv by Mats O Jansson
+ */
+void
+yp_dispatch(struct svc_req *req, SVCXPRT *trans)
+{
+ xdrproc_t xdr_argument;
+ xdrproc_t xdr_result;
+ char *result;
+ char *(*cb)(char *, struct svc_req *);
+ union {
+ domainname ypproc_domain_2_arg;
+ domainname ypproc_domain_nonack_2_arg;
+ ypreq_key ypproc_match_2_arg;
+ ypreq_nokey ypproc_first_2_arg;
+ ypreq_key ypproc_next_2_arg;
+ ypreq_xfr ypproc_xfr_2_arg;
+ ypreq_nokey ypproc_all_2_arg;
+ ypreq_nokey ypproc_master_2_arg;
+ ypreq_nokey ypproc_order_2_arg;
+ domainname ypproc_maplist_2_arg;
+ } argument;
+
+ xdr_argument = (xdrproc_t) xdr_void;
+ xdr_result = (xdrproc_t) xdr_void;
+ cb = NULL;
+ switch (req->rq_proc) {
+ case YPPROC_NULL:
+ xdr_argument = (xdrproc_t) xdr_void;
+ xdr_result = (xdrproc_t) xdr_void;
+ if (yp_check(req) == -1)
+ return;
+ result = NULL;
+ if (!svc_sendreply(trans, (xdrproc_t) xdr_void,
+ (void *)&result))
+ svcerr_systemerr(trans);
+ return;
+ case YPPROC_DOMAIN:
+ xdr_argument = (xdrproc_t) xdr_domainname;
+ xdr_result = (xdrproc_t) xdr_bool;
+ if (yp_check(req) == -1)
+ return;
+ cb = (void *)ypproc_domain_2_svc;
+ break;
+ case YPPROC_DOMAIN_NONACK:
+ xdr_argument = (xdrproc_t) xdr_domainname;
+ xdr_result = (xdrproc_t) xdr_bool;
+ if (yp_check(req) == -1)
+ return;
+ cb = (void *)ypproc_domain_nonack_2_svc;
+ break;
+ case YPPROC_MATCH:
+ xdr_argument = (xdrproc_t) xdr_ypreq_key;
+ xdr_result = (xdrproc_t) xdr_ypresp_val;
+ if (yp_check(req) == -1)
+ return;
+ cb = (void *)ypproc_match_2_svc;
+ break;
+ case YPPROC_FIRST:
+ xdr_argument = (xdrproc_t) xdr_ypreq_nokey;
+ xdr_result = (xdrproc_t) xdr_ypresp_key_val;
+ if (yp_check(req) == -1)
+ return;
+ cb = (void *)ypproc_first_2_svc;
+ break;
+ case YPPROC_NEXT:
+ xdr_argument = (xdrproc_t) xdr_ypreq_key;
+ xdr_result = (xdrproc_t) xdr_ypresp_key_val;
+ if (yp_check(req) == -1)
+ return;
+ cb = (void *)ypproc_next_2_svc;
+ break;
+ case YPPROC_XFR:
+ if (yp_check(req) == -1)
+ return;
+ svcerr_noproc(trans);
+ return;
+ case YPPROC_CLEAR:
+ log_debug("ypproc_clear");
+ if (yp_check(req) == -1)
+ return;
+ svcerr_noproc(trans);
+ return;
+ case YPPROC_ALL:
+ log_debug("ypproc_all");
+ if (yp_check(req) == -1)
+ return;
+ cb = (void *)ypproc_all_2_svc;
+ break;
+ case YPPROC_MASTER:
+ log_debug("ypproc_master");
+ if (yp_check(req) == -1)
+ return;
+ cb = (void *)ypproc_master_2_svc;
+ break;
+ case YPPROC_ORDER:
+ log_debug("ypproc_order");
+ if (yp_check(req) == -1)
+ return;
+ svcerr_noproc(trans);
+ return;
+ case YPPROC_MAPLIST:
+ log_debug("ypproc_maplist");
+ if (yp_check(req) == -1)
+ return;
+ cb = (void *)ypproc_maplist_2_svc;
+ break;
+ default:
+ svcerr_noproc(trans);
+ return;
+ }
+ (void)memset(&argument, 0, sizeof(argument));
+
+ if (!svc_getargs(trans, xdr_argument, (caddr_t)&argument)) {
+ svcerr_decode(trans);
+ return;
+ }
+ result = (*cb)((char *)&argument, req);
+ if (result != NULL && !svc_sendreply(trans, xdr_result, result))
+ svcerr_systemerr(trans);
+ if (!svc_freeargs(trans, xdr_argument, (caddr_t)&argument)) {
+ /*
+ * ypserv does it too.
+ */
+ fatal("unable to free arguments");
+ }
+}
+
+int
+yp_check(struct svc_req *req)
+{
+ struct sockaddr_in *caller;
+
+ caller = svc_getcaller(req->rq_xprt);
+ /*
+ * We might want to know who we allow here.
+ */
+ return (0);
+}
+
+int
+yp_valid_domain(char *domain, struct ypresp_val *res)
+{
+ if (domain == NULL) {
+ log_debug("NULL domain !");
+ return (-1);
+ }
+ if (strcmp(domain, env->sc_domainname) != 0) {
+ res->stat = YP_NODOM;
+ return (-1);
+ }
+ return (0);
+}
+
+bool_t *
+ypproc_domain_2_svc(domainname *arg, struct svc_req *req)
+{
+ static bool_t res;
+
+ res = (bool_t)1;
+ if (strcmp(*arg, env->sc_domainname) != 0)
+ res = (bool_t)0;
+ return (&res);
+}
+
+bool_t *
+ypproc_domain_nonack_2_svc(domainname *arg, struct svc_req *req)
+{
+ static bool_t res;
+
+ if (strcmp(*arg, env->sc_domainname) != 0)
+ return NULL;
+ res = (bool_t)1;
+ return (&res);
+}
+
+ypresp_val *
+ypproc_match_2_svc(ypreq_key *arg, struct svc_req *req)
+{
+ struct userent ukey;
+ struct userent *ue;
+ struct groupent gkey;
+ struct groupent *ge;
+ static struct ypresp_val res;
+ const char *estr;
+ char *bp, *cp;
+ char key[YPMAXRECORD+1];
+
+ log_debug("matching '%.*s' in map %s", arg->key.keydat_len,
+ arg->key.keydat_val, arg->map);
+
+ if (yp_valid_domain(arg->domain, (struct ypresp_val *)&res) == -1)
+ return (&res);
+
+ if (env->sc_user_names == NULL) {
+ /*
+ * tree not ready.
+ */
+ return (NULL);
+ }
+
+ if (arg->key.keydat_len > YPMAXRECORD) {
+ log_debug("argument too long");
+ return (NULL);
+ }
+ bzero(key, sizeof(key));
+ (void)strncpy(key, arg->key.keydat_val, arg->key.keydat_len);
+
+ if (strcmp(arg->map, "passwd.byname") == 0 ||
+ strcmp(arg->map, "master.passwd.byname") == 0) {
+ ukey.ue_line = key;
+ if ((ue = RB_FIND(user_name_tree, env->sc_user_names,
+ &ukey)) == NULL) {
+ res.stat = YP_NOKEY;
+ return (&res);
+ }
+
+ yp_make_val(&res, ue->ue_line, 1);
+ return (&res);
+ } else if (strcmp(arg->map, "passwd.byuid") == 0 ||
+ strcmp(arg->map, "master.passwd.byuid") == 0) {
+ ukey.ue_uid = strtonum(key, 0, UID_MAX, &estr);
+ if (estr) {
+ res.stat = YP_BADARGS;
+ return (&res);
+ }
+
+ if ((ue = RB_FIND(user_uid_tree, &env->sc_user_uids,
+ &ukey)) == NULL) {
+ res.stat = YP_NOKEY;
+ return (&res);
+ }
+
+ yp_make_val(&res, ue->ue_line, 1);
+ return (&res);
+ } else if (strcmp(arg->map, "group.bygid") == 0) {
+ gkey.ge_gid = strtonum(key, 0, GID_MAX, &estr);
+ if (estr) {
+ res.stat = YP_BADARGS;
+ return (&res);
+ }
+ if ((ge = RB_FIND(group_gid_tree, &env->sc_group_gids,
+ &gkey)) == NULL) {
+ res.stat = YP_NOKEY;
+ return (&res);
+ }
+
+ yp_make_val(&res, ge->ge_line, 1);
+ return (&res);
+ } else if (strcmp(arg->map, "group.byname") == 0) {
+ gkey.ge_line = key;
+ if ((ge = RB_FIND(group_name_tree, env->sc_group_names,
+ &gkey)) == NULL) {
+ res.stat = YP_NOKEY;
+ return (&res);
+ }
+
+ yp_make_val(&res, ge->ge_line, 1);
+ return (&res);
+ } else if (strcmp(arg->map, "netid.byname") == 0) {
+ bp = cp = key;
+
+ if (strncmp(bp, "unix.", strlen("unix.")) != 0) {
+ res.stat = YP_BADARGS;
+ return (&res);
+ }
+
+ bp += strlen("unix.");
+
+ if (*bp == '\0') {
+ res.stat = YP_BADARGS;
+ return (&res);
+ }
+
+ if (!(cp = strsep(&bp, "@"))) {
+ res.stat = YP_BADARGS;
+ return (&res);
+ }
+
+ if (strcmp(bp, arg->domain) != 0) {
+ res.stat = YP_BADARGS;
+ return (&res);
+ }
+
+ ukey.ue_uid = strtonum(cp, 0, UID_MAX, &estr);
+ if (estr) {
+ res.stat = YP_BADARGS;
+ return (&res);
+ }
+
+ if ((ue = RB_FIND(user_uid_tree, &env->sc_user_uids,
+ &ukey)) == NULL) {
+ res.stat = YP_NOKEY;
+ return (&res);
+ }
+
+ yp_make_val(&res, ue->ue_netid_line, 0);
+ return (&res);
+
+ } else {
+ log_debug("unknown map %s", arg->map);
+ res.stat = YP_NOMAP;
+ return (&res);
+ }
+}
+
+ypresp_key_val *
+ypproc_first_2_svc(ypreq_nokey *arg, struct svc_req *req)
+{
+ static struct ypresp_key_val res;
+
+ if (yp_valid_domain(arg->domain, (struct ypresp_val *)&res) == -1)
+ return (&res);
+
+ if (strcmp(arg->map, "passwd.byname") == 0 ||
+ strcmp(arg->map, "master.passwd.byname") == 0) {
+ if (env->sc_user_lines == NULL)
+ return (NULL);
+
+ yp_make_keyval(&res, env->sc_user_lines, env->sc_user_lines);
+ } else if (strcmp(arg->map, "group.byname") == 0) {
+ if (env->sc_group_lines == NULL)
+ return (NULL);
+
+ yp_make_keyval(&res, env->sc_group_lines, env->sc_group_lines);
+ } else {
+ log_debug("unknown map %s", arg->map);
+ res.stat = YP_NOMAP;
+ }
+
+ return (&res);
+}
+
+ypresp_key_val *
+ypproc_next_2_svc(ypreq_key *arg, struct svc_req *req)
+{
+ struct userent ukey;
+ struct userent *ue;
+ struct groupent gkey;
+ struct groupent *ge;
+ char *line;
+ static struct ypresp_key_val res;
+ char key[YPMAXRECORD+1];
+
+ if (yp_valid_domain(arg->domain, (struct ypresp_val *)&res) == -1)
+ return (&res);
+
+ if (strcmp(arg->map, "passwd.byname") == 0 ||
+ strcmp(arg->map, "master.passwd.byname") == 0) {
+ bzero(key, sizeof(key));
+ (void)strncpy(key, arg->key.keydat_val,
+ arg->key.keydat_len);
+ ukey.ue_line = key;
+ if ((ue = RB_FIND(user_name_tree, env->sc_user_names,
+ &ukey)) == NULL) {
+ /*
+ * canacar's trick:
+ * the user might have been deleted in between calls
+ * to next since the tree may be modified by a reload.
+ * next should still return the next user in
+ * lexicographical order, hence insert the search key
+ * and look up the next field, then remove it again.
+ */
+ RB_INSERT(user_name_tree, env->sc_user_names, &ukey);
+ if ((ue = RB_NEXT(user_name_tree, &env->sc_user_names,
+ &ukey)) == NULL) {
+ RB_REMOVE(user_name_tree, env->sc_user_names,
+ &ukey);
+ res.stat = YP_NOKEY;
+ return (&res);
+ }
+ RB_REMOVE(user_name_tree, env->sc_user_names, &ukey);
+ }
+ line = ue->ue_line + (strlen(ue->ue_line) + 1);
+ line = line + (strlen(line) + 1);
+ yp_make_keyval(&res, line, line);
+ return (&res);
+
+
+ } else if (strcmp(arg->map, "group.byname") == 0) {
+ bzero(key, sizeof(key));
+ (void)strncpy(key, arg->key.keydat_val,
+ arg->key.keydat_len);
+
+ gkey.ge_line = key;
+ if ((ge = RB_FIND(group_name_tree, env->sc_group_names,
+ &gkey)) == NULL) {
+ /*
+ * canacar's trick reloaded.
+ */
+ RB_INSERT(group_name_tree, env->sc_group_names, &gkey);
+ if ((ge = RB_NEXT(group_name_tree, &env->sc_group_names,
+ &gkey)) == NULL) {
+ RB_REMOVE(group_name_tree, env->sc_group_names,
+ &gkey);
+ res.stat = YP_NOKEY;
+ return (&res);
+ }
+ RB_REMOVE(group_name_tree, env->sc_group_names, &gkey);
+ }
+
+ line = ge->ge_line + (strlen(ge->ge_line) + 1);
+ line = line + (strlen(line) + 1);
+ yp_make_keyval(&res, line, line);
+ return (&res);
+ } else {
+ log_debug("unknown map %s", arg->map);
+ res.stat = YP_NOMAP;
+ return (&res);
+ }
+}
+
+ypresp_all *
+ypproc_all_2_svc(ypreq_nokey *arg, struct svc_req *req)
+{
+ static struct ypresp_all res;
+
+ if (yp_valid_domain(arg->domain, (struct ypresp_val *)&res) == -1)
+ return (&res);
+
+ svcerr_auth(req->rq_xprt, AUTH_FAILED);
+ return (NULL);
+}
+
+ypresp_master *
+ypproc_master_2_svc(ypreq_nokey *arg, struct svc_req *req)
+{
+ static struct ypresp_master res;
+
+ if (yp_valid_domain(arg->domain, (struct ypresp_val *)&res) == -1)
+ return (&res);
+
+ res.stat = YP_YPERR;
+ return (&res);
+}
+
+ypresp_maplist *
+ypproc_maplist_2_svc(domainname *arg, struct svc_req *req)
+{
+ size_t i;
+ static struct {
+ char *name;
+ int cond;
+ } mapnames[] = {
+ { "passwd.byname", YPMAP_PASSWD_BYNAME },
+ { "passwd.byuid", YPMAP_PASSWD_BYUID },
+ { "master.passwd.byname", YPMAP_MASTER_PASSWD_BYNAME },
+ { "master.passwd.byuid", YPMAP_MASTER_PASSWD_BYUID },
+ { "group.byname", YPMAP_GROUP_BYNAME },
+ { "group.bygid", YPMAP_GROUP_BYGID },
+ { "netid.byname", YPMAP_NETID_BYNAME },
+ };
+ static ypresp_maplist res;
+ static struct ypmaplist maps[sizeof(mapnames) / sizeof(mapnames[0])];
+
+ if (yp_valid_domain(*arg, (struct ypresp_val *)&res) == -1)
+ return (&res);
+
+ res.stat = YP_TRUE;
+ res.maps = NULL;
+ for (i = 0; i < sizeof(mapnames) / sizeof(mapnames[0]); i++) {
+ if (!(env->sc_flags & mapnames[i].cond))
+ continue;
+ maps[i].map = mapnames[i].name;
+ maps[i].next = res.maps;
+ res.maps = &maps[i];
+ }
+
+ return (&res);
+}
+
+void
+yp_make_val(struct ypresp_val *res, char *line, int replacecolon)
+{
+ static char buf[LINE_WIDTH];
+
+ bzero(buf, sizeof(buf));
+
+ if (replacecolon)
+ line[strlen(line)] = ':';
+ (void)strlcpy(buf, line, sizeof(buf));
+ if (replacecolon)
+ line[strcspn(line, ":")] = '\0';
+ log_debug("sending out %s", buf);
+
+ res->stat = YP_TRUE;
+ res->val.valdat_len = strlen(buf);
+ res->val.valdat_val = buf;
+}
+
+void
+yp_make_keyval(struct ypresp_key_val *res, char *key, char *line)
+{
+ static char keybuf[YPMAXRECORD+1];
+ static char buf[LINE_WIDTH];
+
+ bzero(keybuf, sizeof(keybuf));
+ bzero(buf, sizeof(buf));
+
+ (void)strlcpy(keybuf, key, sizeof(keybuf));
+ res->key.keydat_len = strlen(keybuf);
+ res->key.keydat_val = keybuf;
+
+ if (*line == '\0') {
+ res->stat = YP_NOMORE;
+ return;
+ }
+ res->stat = YP_TRUE;
+ line[strlen(line)] = ':';
+ (void)strlcpy(buf, line, sizeof(buf));
+ line[strcspn(line, ":")] = '\0';
+ log_debug("sending out %s => %s", keybuf, buf);
+
+ res->val.valdat_len = strlen(buf);
+ res->val.valdat_val = buf;
+}
diff --git a/usr.sbin/ypldap/ypldap.8 b/usr.sbin/ypldap/ypldap.8
new file mode 100644
index 0000000..2974e24
--- /dev/null
+++ b/usr.sbin/ypldap/ypldap.8
@@ -0,0 +1,82 @@
+.\" $OpenBSD: ypldap.8,v 1.10 2015/07/27 17:28:40 sobrado Exp $
+.\" $FreeBSD$
+.\"
+.\" Copyright (c) 2008 Pierre-Yves Ritschard <pyr@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.
+.\"
+.Dd $Mdocdate: July 27 2015 $
+.Dt YPLDAP 8
+.Os
+.Sh NAME
+.Nm ypldap
+.Nd YP map server using LDAP backend
+.Sh SYNOPSIS
+.Nm
+.Op Fl dnv
+.Op Fl D Ar macro Ns = Ns Ar value
+.Op Fl f Ar file
+.Sh DESCRIPTION
+.Nm
+is a daemon providing YP maps using LDAP as a backend.
+RFC 2307 or similar LDAP schemas can be tied to the different YP maps.
+.Nm
+has the same role as
+.Xr ypserv 8
+and the two daemons are exclusive.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.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 configuration file.
+.It Fl d
+Do not daemonize.
+If this option is specified,
+.Nm
+will run in the foreground and log to
+.Em stderr .
+.It Fl f Ar file
+Specify an alternative configuration file.
+.It Fl n
+Configtest mode.
+Only check the configuration file for validity.
+.It Fl v
+Produce more verbose output.
+.El
+.Sh FILES
+.Bl -tag -width "/etc/ypldap.confXX" -compact
+.It Pa /etc/ypldap.conf
+Default
+.Nm
+configuration file.
+.El
+.Sh SEE ALSO
+.Xr ypldap.conf 5 ,
+.Xr ypbind 8
+.Sh HISTORY
+The
+.Nm
+program first appeared in
+.Ox 4.4 .
+.Sh AUTHORS
+The
+.Nm
+program was written by
+.An Pierre-Yves Ritschard .
diff --git a/usr.sbin/ypldap/ypldap.c b/usr.sbin/ypldap/ypldap.c
new file mode 100644
index 0000000..7762270
--- /dev/null
+++ b/usr.sbin/ypldap/ypldap.c
@@ -0,0 +1,651 @@
+/* $OpenBSD: ypldap.c,v 1.16 2015/11/02 10:06:06 jmatthew Exp $ */
+/* $FreeBSD */
+
+/*
+ * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@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/param.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/signal.h>
+#include <sys/tree.h>
+#include <sys/wait.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include "ypldap.h"
+
+__dead2 void usage(void);
+int check_child(pid_t, const char *);
+void main_sig_handler(int, short, void *);
+void main_shutdown(void);
+void main_dispatch_client(int, short, void *);
+void main_configure_client(struct env *);
+void main_init_timer(int, short, void *);
+void main_start_update(struct env *);
+void main_trash_update(struct env *);
+void main_end_update(struct env *);
+int main_create_user_groups(struct env *);
+void purge_config(struct env *);
+void reconfigure(struct env *);
+
+int pipe_main2client[2];
+
+pid_t client_pid = 0;
+char *conffile = YPLDAP_CONF_FILE;
+int opts = 0;
+
+void
+usage(void)
+{
+ extern const char *__progname;
+
+ fprintf(stderr, "usage: %s [-dnv] [-D macro=value] [-f file]\n",
+ __progname);
+ exit(1);
+}
+
+int
+check_child(pid_t pid, const char *pname)
+{
+ int status;
+
+ if (waitpid(pid, &status, WNOHANG) > 0) {
+ if (WIFEXITED(status)) {
+ log_warnx("check_child: lost child %s exited", pname);
+ return (1);
+ }
+ if (WIFSIGNALED(status)) {
+ log_warnx("check_child: lost child %s terminated; "
+ "signal %d", pname, WTERMSIG(status));
+ return (1);
+ }
+ }
+ return (0);
+}
+
+/* ARGUSED */
+void
+main_sig_handler(int sig, short event, void *p)
+{
+ int die = 0;
+
+ switch (sig) {
+ case SIGTERM:
+ case SIGINT:
+ die = 1;
+ /* FALLTHROUGH */
+ case SIGCHLD:
+ if (check_child(client_pid, "ldap client")) {
+ client_pid = 0;
+ die = 1;
+ }
+ if (die)
+ main_shutdown();
+ break;
+ case SIGHUP:
+ /* reconfigure */
+ break;
+ default:
+ fatalx("unexpected signal");
+ }
+}
+
+void
+main_shutdown(void)
+{
+ _exit(0);
+}
+
+void
+main_start_update(struct env *env)
+{
+ env->update_trashed = 0;
+
+ log_debug("starting directory update");
+ env->sc_user_line_len = 0;
+ env->sc_group_line_len = 0;
+ if ((env->sc_user_names_t = calloc(1,
+ sizeof(*env->sc_user_names_t))) == NULL ||
+ (env->sc_group_names_t = calloc(1,
+ sizeof(*env->sc_group_names_t))) == NULL)
+ fatal(NULL);
+ RB_INIT(env->sc_user_names_t);
+ RB_INIT(env->sc_group_names_t);
+}
+
+/*
+ * XXX: Currently this function should only be called when updating is
+ * finished. A notification should be send to ldapclient that it should stop
+ * sending new pwd/grp entries before it can be called from different places.
+ */
+void
+main_trash_update(struct env *env)
+{
+ struct userent *ue;
+ struct groupent *ge;
+
+ env->update_trashed = 1;
+
+ while ((ue = RB_ROOT(env->sc_user_names_t)) != NULL) {
+ RB_REMOVE(user_name_tree,
+ env->sc_user_names_t, ue);
+ free(ue->ue_line);
+ free(ue->ue_netid_line);
+ free(ue);
+ }
+ free(env->sc_user_names_t);
+ env->sc_user_names_t = NULL;
+ while ((ge = RB_ROOT(env->sc_group_names_t))
+ != NULL) {
+ RB_REMOVE(group_name_tree,
+ env->sc_group_names_t, ge);
+ free(ge->ge_line);
+ free(ge);
+ }
+ free(env->sc_group_names_t);
+ env->sc_group_names_t = NULL;
+}
+
+int
+main_create_user_groups(struct env *env)
+{
+ struct userent *ue;
+ struct userent ukey;
+ struct groupent *ge;
+ gid_t pw_gid;
+ char *bp, *cp;
+ char *p;
+ const char *errstr = NULL;
+ size_t len;
+
+ RB_FOREACH(ue, user_name_tree, env->sc_user_names_t) {
+ bp = cp = ue->ue_line;
+
+ /* name */
+ bp += strlen(bp) + 1;
+
+ /* password */
+ bp += strcspn(bp, ":") + 1;
+
+ /* uid */
+ bp += strcspn(bp, ":") + 1;
+
+ /* gid */
+ bp[strcspn(bp, ":")] = '\0';
+
+ pw_gid = (gid_t)strtonum(bp, 0, GID_MAX, &errstr);
+ if (errstr) {
+ log_warnx("main: failed to parse gid for uid: %d\n", ue->ue_uid);
+ return (-1);
+ }
+
+ /* bring gid column back to its proper state */
+ bp[strlen(bp)] = ':';
+
+ if ((ue->ue_netid_line = calloc(1, LINE_WIDTH)) == NULL) {
+ return (-1);
+ }
+
+ if (snprintf(ue->ue_netid_line, LINE_WIDTH-1, "%d:%d", ue->ue_uid, pw_gid) >= LINE_WIDTH) {
+
+ return (-1);
+ }
+
+ ue->ue_gid = pw_gid;
+ }
+
+ RB_FOREACH(ge, group_name_tree, env->sc_group_names_t) {
+ bp = cp = ge->ge_line;
+
+ /* name */
+ bp += strlen(bp) + 1;
+
+ /* password */
+ bp += strcspn(bp, ":") + 1;
+
+ /* gid */
+ bp += strcspn(bp, ":") + 1;
+
+ cp = bp;
+ if (*bp == '\0')
+ continue;
+ bp = cp;
+ for (;;) {
+ if (!(cp = strsep(&bp, ",")))
+ break;
+ ukey.ue_line = cp;
+ if ((ue = RB_FIND(user_name_tree, env->sc_user_names_t,
+ &ukey)) == NULL) {
+ /* User not found */
+ log_warnx("main: user: %s is referenced as a "
+ "group member, but can't be found in the "
+ "users map.\n", ukey.ue_line);
+ if (bp != NULL)
+ *(bp-1) = ',';
+ continue;
+ }
+ if (bp != NULL)
+ *(bp-1) = ',';
+
+ /* Make sure the new group doesn't equal to the main gid */
+ if (ge->ge_gid == ue->ue_gid)
+ continue;
+
+ len = strlen(ue->ue_netid_line);
+ p = ue->ue_netid_line + len;
+
+ if ((snprintf(p, LINE_WIDTH-len-1, ",%d",
+ ge->ge_gid)) >= (int)(LINE_WIDTH-len)) {
+ return (-1);
+ }
+ }
+ }
+
+ return (0);
+}
+
+void
+main_end_update(struct env *env)
+{
+ struct userent *ue;
+ struct groupent *ge;
+
+ if (env->update_trashed)
+ return;
+
+ log_debug("updates are over, cleaning up trees now");
+
+ if (main_create_user_groups(env) == -1) {
+ main_trash_update(env);
+ return;
+ }
+
+ if (env->sc_user_names == NULL) {
+ env->sc_user_names = env->sc_user_names_t;
+ env->sc_user_lines = NULL;
+ env->sc_user_names_t = NULL;
+
+ env->sc_group_names = env->sc_group_names_t;
+ env->sc_group_lines = NULL;
+ env->sc_group_names_t = NULL;
+
+ flatten_entries(env);
+ goto make_uids;
+ }
+
+ /*
+ * clean previous tree.
+ */
+ while ((ue = RB_ROOT(env->sc_user_names)) != NULL) {
+ RB_REMOVE(user_name_tree, env->sc_user_names,
+ ue);
+ free(ue->ue_netid_line);
+ free(ue);
+ }
+ free(env->sc_user_names);
+ free(env->sc_user_lines);
+
+ env->sc_user_names = env->sc_user_names_t;
+ env->sc_user_lines = NULL;
+ env->sc_user_names_t = NULL;
+
+ while ((ge = RB_ROOT(env->sc_group_names)) != NULL) {
+ RB_REMOVE(group_name_tree,
+ env->sc_group_names, ge);
+ free(ge);
+ }
+ free(env->sc_group_names);
+ free(env->sc_group_lines);
+
+ env->sc_group_names = env->sc_group_names_t;
+ env->sc_group_lines = NULL;
+ env->sc_group_names_t = NULL;
+
+
+ flatten_entries(env);
+
+ /*
+ * trees are flat now. build up uid, gid and netid trees.
+ */
+
+make_uids:
+ RB_INIT(&env->sc_user_uids);
+ RB_INIT(&env->sc_group_gids);
+ RB_FOREACH(ue, user_name_tree, env->sc_user_names)
+ RB_INSERT(user_uid_tree,
+ &env->sc_user_uids, ue);
+ RB_FOREACH(ge, group_name_tree, env->sc_group_names)
+ RB_INSERT(group_gid_tree,
+ &env->sc_group_gids, ge);
+
+}
+
+void
+main_dispatch_client(int fd, short events, void *p)
+{
+ int n;
+ int shut = 0;
+ struct env *env = p;
+ struct imsgev *iev = env->sc_iev;
+ struct imsgbuf *ibuf = &iev->ibuf;
+ struct idm_req ir;
+ struct imsg imsg;
+
+ if ((events & (EV_READ | EV_WRITE)) == 0)
+ fatalx("unknown event");
+
+ if (events & EV_READ) {
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ fatal("imsg_read error");
+ if (n == 0)
+ shut = 1;
+ }
+ if (events & EV_WRITE) {
+ if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+ fatal("msgbuf_write");
+ if (n == 0)
+ shut = 1;
+ goto done;
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("main_dispatch_client: imsg_get error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_START_UPDATE:
+ main_start_update(env);
+ break;
+ case IMSG_PW_ENTRY: {
+ struct userent *ue;
+ size_t len;
+
+ if (env->update_trashed)
+ break;
+
+ (void)memcpy(&ir, imsg.data, sizeof(ir));
+ if ((ue = calloc(1, sizeof(*ue))) == NULL ||
+ (ue->ue_line = strdup(ir.ir_line)) == NULL) {
+ /*
+ * should cancel tree update instead.
+ */
+ fatal("out of memory");
+ }
+ ue->ue_uid = ir.ir_key.ik_uid;
+ len = strlen(ue->ue_line) + 1;
+ ue->ue_line[strcspn(ue->ue_line, ":")] = '\0';
+ if (RB_INSERT(user_name_tree, env->sc_user_names_t,
+ ue) != NULL) { /* dup */
+ free(ue->ue_line);
+ free(ue);
+ } else
+ env->sc_user_line_len += len;
+ break;
+ }
+ case IMSG_GRP_ENTRY: {
+ struct groupent *ge;
+ size_t len;
+
+ if (env->update_trashed)
+ break;
+
+ (void)memcpy(&ir, imsg.data, sizeof(ir));
+ if ((ge = calloc(1, sizeof(*ge))) == NULL ||
+ (ge->ge_line = strdup(ir.ir_line)) == NULL) {
+ /*
+ * should cancel tree update instead.
+ */
+ fatal("out of memory");
+ }
+ ge->ge_gid = ir.ir_key.ik_gid;
+ len = strlen(ge->ge_line) + 1;
+ ge->ge_line[strcspn(ge->ge_line, ":")] = '\0';
+ if (RB_INSERT(group_name_tree, env->sc_group_names_t,
+ ge) != NULL) { /* dup */
+ free(ge->ge_line);
+ free(ge);
+ } else
+ env->sc_group_line_len += len;
+ break;
+ }
+ case IMSG_TRASH_UPDATE:
+ main_trash_update(env);
+ break;
+ case IMSG_END_UPDATE: {
+ main_end_update(env);
+ break;
+ }
+ default:
+ log_debug("main_dispatch_client: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+
+done:
+ if (!shut)
+ imsg_event_add(iev);
+ else {
+ log_debug("king bula sez: ran into dead pipe");
+ event_del(&iev->ev);
+ event_loopexit(NULL);
+ }
+}
+
+void
+main_configure_client(struct env *env)
+{
+ struct idm *idm;
+ struct imsgev *iev = env->sc_iev;
+
+ imsg_compose_event(iev, IMSG_CONF_START, 0, 0, -1, env, sizeof(*env));
+ TAILQ_FOREACH(idm, &env->sc_idms, idm_entry) {
+ imsg_compose_event(iev, IMSG_CONF_IDM, 0, 0, -1,
+ idm, sizeof(*idm));
+ }
+ imsg_compose_event(iev, IMSG_CONF_END, 0, 0, -1, NULL, 0);
+}
+
+void
+main_init_timer(int fd, short event, void *p)
+{
+ struct env *env = p;
+
+ main_configure_client(env);
+}
+
+void
+purge_config(struct env *env)
+{
+ struct idm *idm;
+
+ while ((idm = TAILQ_FIRST(&env->sc_idms)) != NULL) {
+ TAILQ_REMOVE(&env->sc_idms, idm, idm_entry);
+ free(idm);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ int c;
+ int debug;
+ struct passwd *pw;
+ struct env env;
+ struct event ev_sigint;
+ struct event ev_sigterm;
+ struct event ev_sigchld;
+ struct event ev_sighup;
+ struct event ev_timer;
+ struct timeval tv;
+
+ debug = 0;
+ ypldap_process = PROC_MAIN;
+
+ log_init(1);
+
+ while ((c = getopt(argc, argv, "dD:nf:v")) != -1) {
+ switch (c) {
+ case 'd':
+ debug = 2;
+ break;
+ case 'D':
+ if (cmdline_symset(optarg) < 0)
+ log_warnx("could not parse macro definition %s",
+ optarg);
+ break;
+ case 'n':
+ debug = 2;
+ opts |= YPLDAP_OPT_NOACTION;
+ break;
+ case 'f':
+ conffile = optarg;
+ break;
+ case 'v':
+ opts |= YPLDAP_OPT_VERBOSE;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc)
+ usage();
+
+ RB_INIT(&env.sc_user_uids);
+ RB_INIT(&env.sc_group_gids);
+
+ if (parse_config(&env, conffile, opts))
+ exit(1);
+ if (opts & YPLDAP_OPT_NOACTION) {
+ fprintf(stderr, "configuration OK\n");
+ exit(0);
+ }
+
+ if (geteuid())
+ errx(1, "need root privileges");
+
+ log_init(debug);
+
+ if (!debug) {
+ if (daemon(1, 0) == -1)
+ err(1, "failed to daemonize");
+ }
+
+ log_info("startup%s", (debug > 1)?" [debug mode]":"");
+
+ if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, PF_UNSPEC,
+ pipe_main2client) == -1)
+ fatal("socketpair");
+
+ client_pid = ldapclient(pipe_main2client);
+
+ setproctitle("parent");
+ event_init();
+
+ signal_set(&ev_sigint, SIGINT, main_sig_handler, &env);
+ signal_set(&ev_sigterm, SIGTERM, main_sig_handler, &env);
+ signal_set(&ev_sighup, SIGHUP, main_sig_handler, &env);
+ signal_set(&ev_sigchld, SIGCHLD, main_sig_handler, &env);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+ signal_add(&ev_sighup, NULL);
+ signal_add(&ev_sigchld, NULL);
+
+ close(pipe_main2client[1]);
+ if ((env.sc_iev = calloc(1, sizeof(*env.sc_iev))) == NULL)
+ fatal(NULL);
+ imsg_init(&env.sc_iev->ibuf, pipe_main2client[0]);
+ env.sc_iev->handler = main_dispatch_client;
+
+ env.sc_iev->events = EV_READ;
+ env.sc_iev->data = &env;
+ event_set(&env.sc_iev->ev, env.sc_iev->ibuf.fd, env.sc_iev->events,
+ env.sc_iev->handler, &env);
+ event_add(&env.sc_iev->ev, NULL);
+
+ yp_init(&env);
+
+ if ((pw = getpwnam(YPLDAP_USER)) == NULL)
+ fatal("getpwnam");
+
+#ifndef DEBUG
+ if (setgroups(1, &pw->pw_gid) ||
+ setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
+ setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+ fatal("cannot drop privileges");
+#else
+#warning disabling privilege revocation in debug mode
+#endif
+
+ bzero(&tv, sizeof(tv));
+ evtimer_set(&ev_timer, main_init_timer, &env);
+ evtimer_add(&ev_timer, &tv);
+
+ yp_enable_events();
+ event_dispatch();
+ main_shutdown();
+
+ return (0);
+}
+
+void
+imsg_event_add(struct imsgev *iev)
+{
+ if (iev->handler == NULL) {
+ imsg_flush(&iev->ibuf);
+ return;
+ }
+
+ iev->events = EV_READ;
+ if (iev->ibuf.w.queued)
+ iev->events |= EV_WRITE;
+
+ event_del(&iev->ev);
+ event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev->data);
+ event_add(&iev->ev, NULL);
+}
+
+int
+imsg_compose_event(struct imsgev *iev, u_int16_t type, u_int32_t peerid,
+ pid_t pid, int fd, void *data, u_int16_t datalen)
+{
+ int ret;
+
+ if ((ret = imsg_compose(&iev->ibuf, type, peerid,
+ pid, fd, data, datalen)) != -1)
+ imsg_event_add(iev);
+ return (ret);
+}
diff --git a/usr.sbin/ypldap/ypldap.conf.5 b/usr.sbin/ypldap/ypldap.conf.5
new file mode 100644
index 0000000..5c22b3b
--- /dev/null
+++ b/usr.sbin/ypldap/ypldap.conf.5
@@ -0,0 +1,167 @@
+.\" $OpenBSD: ypldap.conf.5,v 1.19 2012/04/30 11:28:25 jmatthew Exp $
+.\" $FreeBSD$
+.\"
+.\" Copyright (c) 2008 Pierre-Yves Ritschard <pyr@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.
+.\"
+.Dd $Mdocdate: April 30 2012 $
+.Dt YPLDAP.CONF 5
+.Os
+.Sh NAME
+.Nm ypldap.conf
+.Nd LDAP YP map daemon configuration file
+.Sh DESCRIPTION
+The
+.Xr ypldap 8
+daemon provides YP maps using LDAP as a backend.
+.Sh SECTIONS
+The
+.Nm
+config file is divided into three main sections.
+.Bl -tag -width xxxx
+.It Sy Macros
+User-defined variables may be defined and used later, simplifying the
+configuration file.
+.It Sy Global Configuration
+Global settings for
+.Xr ypldap 8 .
+.It Sy Directories
+LDAP Directory specific parameters.
+.El
+.Sh MACROS
+Much like
+.Xr cpp 1
+or
+.Xr m4 1 ,
+macros can be defined that will later be expanded in context.
+Macro names must start with a letter, digit, or underscore,
+and may contain any of those characters.
+Macro names may not be reserved words (for example,
+.Ic domain ) .
+Macros are not expanded inside quotes.
+.Pp
+For example:
+.Bd -literal -offset indent
+
+fixed_gecos="Pulled from LDAP"
+
+fixed attribute gecos $fixed_gecos
+.Ed
+.Sh GLOBAL CONFIGURATION
+Global settings concern the main behaviour of the daemon.
+.Pp
+.Bl -tag -width Ds -compact
+.It domain Ar string
+Specify the name of the NIS domain
+.Nm
+will provide.
+.It interval Ar seconds
+Specify the interval in seconds at which the whole directory will be pulled
+from LDAP.
+.It provide map Ar string
+Specify a map that should be provided by
+.Nm
+The currently implemented maps are: passwd.byname, passwd.byuid,
+group.byname, group.bygid.
+.El
+.Sh DIRECTORIES
+Directories are used to describe the LDAP schema and help
+.Nm
+convert LDAP entries to
+.Xr passwd 5 ,
+.Xr master.passwd 5 ,
+and
+.Xr group 5
+lines.
+A directory declaration is of the following form:
+.Bd -literal -offset indent
+directory "some.host" {
+ # directives
+}
+.Ed
+.Pp
+Valid directives for directories are:
+.Bl -tag -width Ds
+.It Xo
+.Ic attribute Ar name Ic maps to Ar string
+.Xc
+Map the
+.Xr passwd 5 ,
+.Xr master.passwd 5 ,
+or
+.Xr group 5
+attribute to the LDAP attribute name supplied.
+.It Ic basedn Ar string
+Use the supplied search base as starting point for the directory search.
+.It Ic groupdn Ar string
+Use the supplied search base as starting point for the directory search for
+groups.
+If not supplied, the basedn value will be used.
+.It Ic bindcred Ar string
+Use the supplied credentials for simple authentication against the directory.
+.It Ic binddn Ar string
+Use the supplied Distinguished Name to bind to the directory.
+.It Ic fixed attribute Ar attribute string
+Do not retrieve the specified attribute from LDAP but
+instead set it unconditionally to the supplied value for
+every entry.
+.It Ic group filter Ar string
+Use the supplied LDAP filter to retrieve group entries.
+.It Xo
+.Ic list Ar name Ic maps to Ar string
+.Xc
+Map the
+.Xr passwd 5 ,
+.Xr master.passwd 5 ,
+or
+.Xr group 5
+attribute to the LDAP attribute name supplied.
+A list creates a comma separated list of all the LDAP attributes found.
+.Pp
+Valid attributes are:
+.Pp
+.Bl -tag -width groupmembers -offset indent -compact
+.It Ic name
+.It Ic passwd
+.It Ic uid
+.It Ic gid
+.It Ic gecos
+.It Ic home
+.It Ic shell
+.It Ic change
+.It Ic expire
+.It Ic class
+.It Ic groupname
+.It Ic grouppasswd
+.It Ic groupgid
+.It Ic groupmembers
+.El
+.It Ic passwd filter Ar string
+Use the supplied LDAP filter to retrieve password entries.
+.El
+.Sh FILES
+.Bl -tag -width "/etc/ypldap.conf" -compact
+.It Pa /etc/ypldap.conf
+.Xr ypldap 8
+configuration file.
+.El
+.Sh SEE ALSO
+.Xr ypbind 8 ,
+.Xr ypldap 8 ,
+.Xr ypserv 8
+.Sh HISTORY
+The
+.Nm
+file format first appeared in
+.Ox 4.4 .
diff --git a/usr.sbin/ypldap/ypldap.h b/usr.sbin/ypldap/ypldap.h
new file mode 100644
index 0000000..b5e5fc0
--- /dev/null
+++ b/usr.sbin/ypldap/ypldap.h
@@ -0,0 +1,222 @@
+/* $OpenBSD: ypldap.h,v 1.16 2015/01/16 06:40:22 deraadt Exp $ */
+/* $FreeBSD$ */
+
+/*
+ * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@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 <imsg.h>
+
+#define YPLDAP_USER "_ypldap"
+#define YPLDAP_CONF_FILE "/etc/ypldap.conf"
+#define DEFAULT_INTERVAL 600
+#define LINE_WIDTH 1024
+#define FILTER_WIDTH 128
+#define ATTR_WIDTH 32
+
+#define MAX_SERVERS_DNS 8
+
+enum imsg_type {
+ IMSG_NONE,
+ IMSG_CONF_START,
+ IMSG_CONF_IDM,
+ IMSG_CONF_END,
+ IMSG_START_UPDATE,
+ IMSG_END_UPDATE,
+ IMSG_TRASH_UPDATE,
+ IMSG_PW_ENTRY,
+ IMSG_GRP_ENTRY,
+ IMSG_HOST_DNS
+};
+
+struct ypldap_addr {
+ struct ypldap_addr *next;
+ struct sockaddr_storage ss;
+};
+
+enum {
+ PROC_MAIN,
+ PROC_CLIENT
+} ypldap_process;
+
+struct userent {
+ RB_ENTRY(userent) ue_name_node;
+ RB_ENTRY(userent) ue_uid_node;
+ uid_t ue_uid;
+ char *ue_line;
+ char *ue_netid_line;
+ gid_t ue_gid;
+};
+
+struct groupent {
+ RB_ENTRY(groupent) ge_name_node;
+ RB_ENTRY(groupent) ge_gid_node;
+ gid_t ge_gid;
+ char *ge_line;
+};
+
+enum client_state {
+ STATE_NONE,
+ STATE_DNS_INPROGRESS,
+ STATE_DNS_TEMPFAIL,
+ STATE_DNS_DONE,
+ STATE_LDAP_FAIL,
+ STATE_LDAP_DONE
+};
+
+/*
+ * beck, djm, dlg: pay attention to the struct name
+ */
+struct idm {
+ TAILQ_ENTRY(idm) idm_entry;
+ u_int32_t idm_id;
+ char idm_name[MAXHOSTNAMELEN];
+#define F_SSL 0x00100000
+#define F_CONFIGURING 0x00200000
+#define F_NEEDAUTH 0x00400000
+#define F_FIXED_ATTR(n) (1<<n)
+#define F_LIST(n) (1<<n)
+ enum client_state idm_state;
+ u_int32_t idm_flags; /* lower 20 reserved */
+ u_int32_t idm_list;
+ struct ypldap_addr *idm_addr;
+ in_port_t idm_port;
+ char idm_binddn[LINE_WIDTH];
+ char idm_bindcred[LINE_WIDTH];
+ char idm_basedn[LINE_WIDTH];
+ char idm_groupdn[LINE_WIDTH];
+#define FILTER_USER 1
+#define FILTER_GROUP 0
+ char idm_filters[2][FILTER_WIDTH];
+#define ATTR_NAME 0
+#define ATTR_PASSWD 1
+#define ATTR_UID 2
+#define ATTR_GID 3
+#define ATTR_CLASS 4
+#define ATTR_CHANGE 5
+#define ATTR_EXPIRE 6
+#define ATTR_GECOS 7
+#define ATTR_DIR 8
+#define ATTR_SHELL 9
+#define ATTR_GR_NAME 10
+#define ATTR_GR_PASSWD 11
+#define ATTR_GR_GID 12
+#define ATTR_GR_MEMBERS 13
+#define ATTR_MAX 10
+#define ATTR_GR_MIN 10
+#define ATTR_GR_MAX 14
+ char idm_attrs[14][ATTR_WIDTH];
+ struct env *idm_env;
+ struct event idm_ev;
+#ifdef SSL
+ struct ssl *idm_ssl;
+#endif
+};
+
+struct idm_req {
+ union {
+ uid_t ik_uid;
+ uid_t ik_gid;
+ } ir_key;
+ char ir_line[LINE_WIDTH];
+};
+
+struct imsgev {
+ struct imsgbuf ibuf;
+ void (*handler)(int, short, void *);
+ struct event ev;
+ void *data;
+ short events;
+};
+
+struct env {
+#define YPLDAP_OPT_VERBOSE 0x01
+#define YPLDAP_OPT_NOACTION 0x02
+ u_int8_t sc_opts;
+#define YPMAP_PASSWD_BYNAME 0x00000001
+#define YPMAP_PASSWD_BYUID 0x00000002
+#define YPMAP_MASTER_PASSWD_BYNAME 0x00000004
+#define YPMAP_MASTER_PASSWD_BYUID 0x00000008
+#define YPMAP_GROUP_BYNAME 0x00000010
+#define YPMAP_GROUP_BYGID 0x00000020
+#define YPMAP_NETID_BYNAME 0x00000040
+ u_int32_t sc_flags;
+
+ u_int32_t sc_maxid;
+
+ char sc_domainname[MAXHOSTNAMELEN];
+ struct timeval sc_conf_tv;
+ struct event sc_conf_ev;
+ TAILQ_HEAD(idm_list, idm) sc_idms;
+ struct imsgev *sc_iev;
+ struct imsgev *sc_iev_dns;
+
+ RB_HEAD(user_name_tree,userent) *sc_user_names;
+ RB_HEAD(user_uid_tree,userent) sc_user_uids;
+ RB_HEAD(group_name_tree,groupent)*sc_group_names;
+ RB_HEAD(group_gid_tree,groupent) sc_group_gids;
+ struct user_name_tree *sc_user_names_t;
+ struct group_name_tree *sc_group_names_t;
+ size_t sc_user_line_len;
+ size_t sc_group_line_len;
+ char *sc_user_lines;
+ char *sc_group_lines;
+
+ struct yp_data *sc_yp;
+
+ int update_trashed;
+};
+
+/* log.c */
+void log_init(int);
+void log_warn(const char *, ...);
+void log_warnx(const char *, ...);
+void log_info(const char *, ...);
+void log_debug(const char *, ...);
+void logit(int, const char *, ...);
+void vlog(int, const char *, va_list);
+__dead2 void fatal(const char *);
+__dead2 void fatalx(const char *);
+
+/* parse.y */
+int parse_config(struct env *, const char *, int);
+int cmdline_symset(char *);
+
+/* ldapclient.c */
+pid_t ldapclient(int []);
+
+/* ypldap.c */
+void purge_config(struct env *);
+void imsg_event_add(struct imsgev *);
+int imsg_compose_event(struct imsgev *, u_int16_t, u_int32_t,
+ pid_t, int, void *, u_int16_t);
+
+/* entries.c */
+void flatten_entries(struct env *);
+int userent_name_cmp(struct userent *, struct userent *);
+int userent_uid_cmp(struct userent *, struct userent *);
+int groupent_name_cmp(struct groupent *, struct groupent *);
+int groupent_gid_cmp(struct groupent *, struct groupent *);
+RB_PROTOTYPE( user_name_tree, userent, ue_name_node, userent_name_cmp);
+RB_PROTOTYPE( user_uid_tree, userent, ue_uid_node, userent_uid_cmp);
+RB_PROTOTYPE( group_name_tree, groupent, ge_name_node, groupent_name_cmp);
+RB_PROTOTYPE( group_gid_tree, groupent, ge_gid_node, groupent_gid_cmp);
+
+/* yp.c */
+void yp_init(struct env *);
+void yp_enable_events(void);
+
+/* ypldap_dns.c */
+pid_t ypldap_dns(int[2], struct passwd *);
diff --git a/usr.sbin/ypldap/ypldap_dns.c b/usr.sbin/ypldap/ypldap_dns.c
new file mode 100644
index 0000000..507253c
--- /dev/null
+++ b/usr.sbin/ypldap/ypldap_dns.c
@@ -0,0 +1,254 @@
+/* $OpenBSD: ypldap_dns.c,v 1.8 2015/01/16 06:40:22 deraadt Exp $ */
+/* $FreeBSD$ */
+
+/*
+ * Copyright (c) 2003-2008 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 OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/tree.h>
+#include <sys/queue.h>
+
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+
+#include <netdb.h>
+#include <pwd.h>
+#include <errno.h>
+#include <event.h>
+#include <resolv.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include "ypldap.h"
+
+volatile sig_atomic_t quit_dns = 0;
+struct imsgev *iev_dns;
+
+void dns_dispatch_imsg(int, short, void *);
+void dns_sig_handler(int, short, void *);
+void dns_shutdown(void);
+int host_dns(const char *s, struct ypldap_addr **hn);
+
+void
+dns_sig_handler(int sig, short event, void *p)
+{
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ dns_shutdown();
+ break;
+ default:
+ fatalx("unexpected signal");
+ }
+}
+
+void
+dns_shutdown(void)
+{
+ log_info("dns engine exiting");
+ _exit(0);
+}
+
+pid_t
+ypldap_dns(int pipe_ntp[2], struct passwd *pw)
+{
+ pid_t pid;
+ struct event ev_sigint;
+ struct event ev_sigterm;
+ struct event ev_sighup;
+ struct env env;
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("cannot fork");
+ break;
+ case 0:
+ break;
+ default:
+ return (pid);
+ }
+
+ setproctitle("dns engine");
+ close(pipe_ntp[0]);
+
+ if (setgroups(1, &pw->pw_gid) ||
+ setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
+ setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+ fatal("can't drop privileges");
+ endservent();
+
+ event_init();
+ signal_set(&ev_sigint, SIGINT, dns_sig_handler, NULL);
+ signal_set(&ev_sigterm, SIGTERM, dns_sig_handler, NULL);
+ signal_set(&ev_sighup, SIGHUP, dns_sig_handler, NULL);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+ signal_add(&ev_sighup, NULL);
+
+ if ((env.sc_iev = calloc(1, sizeof(*env.sc_iev))) == NULL)
+ fatal(NULL);
+
+ env.sc_iev->events = EV_READ;
+ env.sc_iev->data = &env;
+ imsg_init(&env.sc_iev->ibuf, pipe_ntp[1]);
+ env.sc_iev->handler = dns_dispatch_imsg;
+ event_set(&env.sc_iev->ev, env.sc_iev->ibuf.fd, env.sc_iev->events,
+ env.sc_iev->handler, &env);
+ event_add(&env.sc_iev->ev, NULL);
+
+ event_dispatch();
+ dns_shutdown();
+
+ return (0);
+}
+
+void
+dns_dispatch_imsg(int fd, short events, void *p)
+{
+ struct imsg imsg;
+ int n, cnt;
+ char *name;
+ struct ypldap_addr *h, *hn;
+ struct ibuf *buf;
+ struct env *env = p;
+ struct imsgev *iev = env->sc_iev;
+ struct imsgbuf *ibuf = &iev->ibuf;
+ int shut = 0;
+
+ if ((events & (EV_READ | EV_WRITE)) == 0)
+ fatalx("unknown event");
+
+ if (events & EV_READ) {
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ fatal("imsg_read error");
+ if (n == 0)
+ shut = 1;
+ }
+ if (events & EV_WRITE) {
+ if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+ fatal("msgbuf_write");
+ if (n == 0)
+ shut = 1;
+ goto done;
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("client_dispatch_imsg: imsg_get error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_HOST_DNS:
+ name = imsg.data;
+ if (imsg.hdr.len < 1 + IMSG_HEADER_SIZE)
+ fatalx("invalid IMSG_HOST_DNS received");
+ imsg.hdr.len -= 1 + IMSG_HEADER_SIZE;
+ if (name[imsg.hdr.len] != '\0' ||
+ strlen(name) != imsg.hdr.len)
+ fatalx("invalid IMSG_HOST_DNS received");
+ if ((cnt = host_dns(name, &hn)) == -1)
+ break;
+ buf = imsg_create(ibuf, IMSG_HOST_DNS,
+ imsg.hdr.peerid, 0,
+ cnt * sizeof(struct sockaddr_storage));
+ if (buf == NULL)
+ break;
+ if (cnt > 0) {
+ h = hn;
+ while (h != NULL) {
+ imsg_add(buf, &h->ss, sizeof(h->ss));
+ hn = h->next;
+ free(h);
+ h = hn;
+ }
+ }
+
+ imsg_close(ibuf, buf);
+ break;
+ default:
+ break;
+ }
+ imsg_free(&imsg);
+ }
+
+done:
+ if (!shut)
+ imsg_event_add(iev);
+ else {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&iev->ev);
+ event_loopexit(NULL);
+ }
+}
+
+int
+host_dns(const char *s, struct ypldap_addr **hn)
+{
+ struct addrinfo hints, *res0, *res;
+ int error, cnt = 0;
+ struct sockaddr_in *sa_in;
+ struct sockaddr_in6 *sa_in6;
+ struct ypldap_addr *h, *hh = NULL;
+
+ bzero(&hints, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM; /* DUMMY */
+ error = getaddrinfo(s, NULL, &hints, &res0);
+ if (error == EAI_AGAIN || error == EAI_NONAME)
+ return (0);
+ if (error) {
+ log_warnx("could not parse \"%s\": %s", s,
+ gai_strerror(error));
+ return (-1);
+ }
+
+ for (res = res0; res && cnt < MAX_SERVERS_DNS; res = res->ai_next) {
+ if (res->ai_family != AF_INET &&
+ res->ai_family != AF_INET6)
+ continue;
+ if ((h = calloc(1, sizeof(struct ypldap_addr))) == NULL)
+ fatal(NULL);
+ h->ss.ss_family = res->ai_family;
+ if (res->ai_family == AF_INET) {
+ sa_in = (struct sockaddr_in *)&h->ss;
+ sa_in->sin_len = sizeof(struct sockaddr_in);
+ sa_in->sin_addr.s_addr = ((struct sockaddr_in *)
+ res->ai_addr)->sin_addr.s_addr;
+ } else {
+ sa_in6 = (struct sockaddr_in6 *)&h->ss;
+ sa_in6->sin6_len = sizeof(struct sockaddr_in6);
+ memcpy(&sa_in6->sin6_addr, &((struct sockaddr_in6 *)
+ res->ai_addr)->sin6_addr, sizeof(struct in6_addr));
+ }
+
+ h->next = hh;
+ hh = h;
+ cnt++;
+ }
+ freeaddrinfo(res0);
+
+ *hn = hh;
+ return (cnt);
+}
diff --git a/usr.sbin/yppoll/Makefile b/usr.sbin/yppoll/Makefile
new file mode 100644
index 0000000..701633b
--- /dev/null
+++ b/usr.sbin/yppoll/Makefile
@@ -0,0 +1,9 @@
+# from: @(#)Makefile 5.8 (Berkeley) 7/28/90
+# $FreeBSD$
+
+PROG= yppoll
+MAN= yppoll.8
+
+WARNS?= 3
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/yppoll/Makefile.depend b/usr.sbin/yppoll/Makefile.depend
new file mode 100644
index 0000000..c0b7a14
--- /dev/null
+++ b/usr.sbin/yppoll/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/yppoll/yppoll.8 b/usr.sbin/yppoll/yppoll.8
new file mode 100644
index 0000000..cc49393
--- /dev/null
+++ b/usr.sbin/yppoll/yppoll.8
@@ -0,0 +1,78 @@
+.\" $OpenBSD: yppoll.8,v 1.10 2014/09/08 01:27:56 schwarze Exp $
+.\" $NetBSD: yppoll.8,v 1.3 1996/02/28 01:23:12 thorpej Exp $
+.\"
+.\" Copyright (c) 1996 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Jason R. Thorpe.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 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 September 3, 2015
+.Dt YPPOLL 8
+.Os
+.Sh NAME
+.Nm yppoll
+.Nd ask version of NIS map from NIS server
+.Sh SYNOPSIS
+.Nm yppoll
+.Op Fl d Ar domain
+.Op Fl h Ar host
+.Ar mapname
+.Sh DESCRIPTION
+.Nm
+asks a NIS server process for the order number and which host is the master
+server for
+.Ar mapname .
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl d Ar domain
+Use the NIS domain
+.Ar domain
+instead of the default domain as returned by
+.Xr domainname 1 .
+.It Fl h Ar host
+Ask the NIS server process running on
+.Ar host
+for information about
+.Ar mapname .
+If
+.Ar host
+is not specified, the server polled is the default server returned by
+.Xr ypwhich 1 .
+.El
+.Sh SEE ALSO
+.Xr domainname 1 ,
+.Xr ypcat 1 ,
+.Xr ypmatch 1 ,
+.Xr ypwhich 1 ,
+.Xr yp 8 ,
+.Xr ypbind 8 ,
+.Xr ypset 8
+.Sh AUTHORS
+.An -nosplit
+.An Theo de Raadt
+and
+.An John Brezak
diff --git a/usr.sbin/yppoll/yppoll.c b/usr.sbin/yppoll/yppoll.c
new file mode 100644
index 0000000..4560c21
--- /dev/null
+++ b/usr.sbin/yppoll/yppoll.c
@@ -0,0 +1,173 @@
+/* $OpenBSD: yppoll.c,v 1.15 2015/01/16 06:40:22 deraadt Exp $ */
+/* $NetBSD: yppoll.c,v 1.5 1996/05/13 02:46:36 thorpej Exp $ */
+
+/*
+ * Copyright (c) 1992, 1993 Theo de Raadt <deraadt@openbsd.org>
+ * Copyright (c) 1992, 1993 John Brezak
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <rpc/rpc.h>
+#include <rpc/xdr.h>
+#include <rpcsvc/yp_prot.h>
+#include <rpcsvc/ypclnt.h>
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: yppoll [-d domain] [-h host] mapname\n");
+ exit(1);
+}
+
+static int
+get_remote_info(char *indomain, char *inmap, char *server, int *outorder,
+ char **outname)
+{
+ struct ypresp_order ypro;
+ struct ypresp_master yprm;
+ struct ypreq_nokey yprnk;
+ struct timeval tv;
+ struct sockaddr_in rsrv_sin;
+ int rsrv_sock;
+ CLIENT *client;
+ struct hostent *h;
+ int r;
+
+ bzero((char *)&rsrv_sin, sizeof rsrv_sin);
+ rsrv_sin.sin_len = sizeof rsrv_sin;
+ rsrv_sin.sin_family = AF_INET;
+ rsrv_sock = RPC_ANYSOCK;
+
+ h = gethostbyname(server);
+ if (h == NULL) {
+ if (inet_aton(server, &rsrv_sin.sin_addr) == 0)
+ errx(1, "unknown host %s.", server);
+ } else
+ rsrv_sin.sin_addr.s_addr = *(u_int32_t *)h->h_addr;
+
+ tv.tv_sec = 10;
+ tv.tv_usec = 0;
+
+ client = clntudp_create(&rsrv_sin, YPPROG, YPVERS, tv, &rsrv_sock);
+ if (client == NULL)
+ errx(1, "clntudp_create: no contact with host %s.", server);
+
+ yprnk.domain = indomain;
+ yprnk.map = inmap;
+
+ bzero((char *)(char *)&ypro, sizeof ypro);
+
+ r = clnt_call(client, YPPROC_ORDER, (xdrproc_t)xdr_ypreq_nokey, &yprnk,
+ (xdrproc_t)xdr_ypresp_order, &ypro, tv);
+ if (r != RPC_SUCCESS)
+ clnt_perror(client, "yp_order: clnt_call");
+
+ *outorder = ypro.ordernum;
+ xdr_free((xdrproc_t)xdr_ypresp_order, (char *)&ypro);
+
+ r = ypprot_err(ypro.status);
+ if (r == RPC_SUCCESS) {
+ bzero((char *)&yprm, sizeof yprm);
+
+ r = clnt_call(client, YPPROC_MASTER, (xdrproc_t)xdr_ypreq_nokey,
+ &yprnk, (xdrproc_t)xdr_ypresp_master, &yprm, tv);
+ if (r != RPC_SUCCESS)
+ clnt_perror(client, "yp_master: clnt_call");
+ r = ypprot_err(yprm.status);
+ if (r == 0)
+ *outname = (char *)strdup(yprm.master);
+ xdr_free((xdrproc_t)xdr_ypresp_master, (char *)&yprm);
+ }
+ clnt_destroy(client);
+ return (r);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *domainname, *hostname = NULL, *inmap, *master;
+ int order, c, r;
+ time_t torder;
+
+ yp_get_default_domain(&domainname);
+
+ while ((c = getopt(argc, argv, "h:d:")) != -1)
+ switch (c) {
+ case 'd':
+ domainname = optarg;
+ break;
+ case 'h':
+ hostname = optarg;
+ break;
+ default:
+ usage();
+ /*NOTREACHED*/
+ }
+
+ if (optind + 1 != argc)
+ usage();
+ inmap = argv[optind];
+
+ if (hostname != NULL) {
+ r = get_remote_info(domainname, inmap, hostname,
+ &order, &master);
+ } else {
+ r = yp_order(domainname, inmap, &order);
+ if (r == 0)
+ r = yp_master(domainname, inmap, &master);
+ }
+
+ if (r != 0)
+ errx(1, "no such map %s: reason: %s",
+ inmap, yperr_string(r));
+
+ torder = order;
+ printf("Map %s has order number %lld. %s", inmap,
+ (long long)order, ctime(&torder));
+ printf("The master server is %s.\n", master);
+
+ exit(0);
+}
diff --git a/usr.sbin/yppush/Makefile b/usr.sbin/yppush/Makefile
new file mode 100644
index 0000000..510dc10
--- /dev/null
+++ b/usr.sbin/yppush/Makefile
@@ -0,0 +1,30 @@
+# $FreeBSD$
+
+RPCDIR= ${.CURDIR}/../../include/rpcsvc
+.PATH: ${RPCDIR} ${.CURDIR}/../../usr.sbin/ypserv \
+ ${.CURDIR}/../../libexec/ypxfr
+
+PROG= yppush
+MAN= yppush.8
+SRCS= ypxfr_getmap.c yp_dblookup.c yp_error.c ypxfr_misc.c yppush_main.c \
+ ${GENSRCS}
+GENSRCS=yp.h yp_clnt.c yppush_svc.c
+
+CFLAGS+= -I. -I${.CURDIR}/../../libexec/ypxfr
+
+WARNS?= 2
+
+RPCGEN= RPCGEN_CPP=${CPP:Q} rpcgen -C
+
+CLEANFILES= ${GENSRCS}
+
+yppush_svc.c: yp.x
+ ${RPCGEN} -DYPPUSH_ONLY -m -o ${.TARGET} ${RPCDIR}/yp.x
+
+yp_clnt.c: yp.x
+ ${RPCGEN} -DYPSERV_ONLY -l -o ${.TARGET} ${RPCDIR}/yp.x
+
+yp.h: yp.x
+ ${RPCGEN} -h -o ${.TARGET} ${RPCDIR}/yp.x
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/yppush/Makefile.depend b/usr.sbin/yppush/Makefile.depend
new file mode 100644
index 0000000..3d736ab
--- /dev/null
+++ b/usr.sbin/yppush/Makefile.depend
@@ -0,0 +1,28 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+yp_clnt.o: yp.h
+yp_clnt.o: yp_clnt.c
+yp_clnt.po: yp.h
+yp_clnt.po: yp_clnt.c
+yppush_svc.o: yp.h
+yppush_svc.o: yppush_svc.c
+yppush_svc.po: yp.h
+yppush_svc.po: yppush_svc.c
+.endif
diff --git a/usr.sbin/yppush/yppush.8 b/usr.sbin/yppush/yppush.8
new file mode 100644
index 0000000..b82c8f7
--- /dev/null
+++ b/usr.sbin/yppush/yppush.8
@@ -0,0 +1,180 @@
+.\" Copyright (c) 1991, 1993, 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$
+.\"
+.Dd February 5, 1995
+.Dt YPPUSH 8
+.Os
+.Sh NAME
+.Nm yppush
+.Nd "force propagation of updated NIS databases"
+.Sh SYNOPSIS
+.Nm
+.Op Fl d Ar domain
+.Op Fl t Ar timeout
+.Op Fl j Ar #parallel jobs
+.Op Fl h Ar host
+.Op Fl p Ar path
+.Op Fl v
+.Ar mapname
+.Sh DESCRIPTION
+The
+.Nm
+utility distributes updated NIS databases (or
+.Pa maps )
+from an NIS master server to NIS slave servers within an NIS
+domain.
+It is normally only run on the NIS master by
+.Pa /var/yp/Makefile
+whenever any of the NIS maps are updated.
+Note that
+.Pa /var/yp/Makefile
+does not invoke
+.Nm
+by default: the
+.Dq Li NOPUSH=True
+entry in the Makefile must first be commented out
+(the default
+.Fx
+configuration assumes a small network with only
+a single NIS server; in such a configuration,
+.Nm
+is not needed).
+.Pp
+By default,
+.Nm
+determines the names of the slave servers for a domain by searching the
+.Pa ypservers
+map.
+A destination host (or a list of hosts) can also be manually
+specified on the command line.
+Once it has a complete list of slave servers, it sends a 'map transfer'
+request to each slave, which in turn reads a copy of the map from
+the master NIS server using
+.Xr ypxfr 8 .
+Included within each request is the name of the map to be copied
+and some special information required by
+.Xr ypxfr 8
+to successfully 'callback' to
+.Nm
+and carry out the transfer.
+Any error messages
+.Nm
+receives from
+.Xr ypxfr 8
+via callback will be printed to stderr.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl d Ar domain
+Specify a particular domain.
+The NIS domain of
+the local host system is used by default.
+If the local host's domain
+name is not set, the domain name must be specified with this flag.
+.It Fl t Ar timeout
+Specify a timeout value in seconds.
+This timeout
+controls how long
+.Nm
+will wait for a response from a slave server before sending a
+map transfer request to the next slave server in its list.
+.It Fl j Ar #parallel jobs
+The
+.Nm
+utility normally performs transfers serially, meaning that it will
+send a map transfer request to one slave server and then wait for
+it to respond before moving on to the next slave server.
+In environments
+with many slaves, it is more efficient to initiate several map transfers
+at once so that the transfers can take place in parallel.
+The
+.Fl j
+flag is used to specify the desired number of parallel jobs:
+.Nm
+will initiate the specified number of transfers immediately and
+listen for responses.
+If the number of specified parallel jobs is
+less than the number of slave servers,
+.Nm
+will initiate only the number of specified jobs and then wait
+for some of them to finish before starting any more.
+.Pp
+Note that
+.Nm
+handles callbacks asynchronously, which means that it will collect
+and display the callback information received from
+.Xr ypxfr 8
+as soon as it arrives, even it arrives before all of the map
+transfer requests have been sent.
+.It Fl h Ar host
+Can be used to transfer a map to a user-specified machine or
+group of machines instead of the list of servers contained in
+the
+.Pa ypservers
+map.
+A list of hosts can be specified by using multiple
+instances of the
+.Fl h
+flag.
+.It Fl p Ar path
+By default,
+.Nm
+expects all the local NIS maps to be stored under
+.Pa /var/yp .
+The
+.Fl p
+flag can be used to specify an alternate path in the event that
+the system administrator decides to store the NIS maps somewhere else.
+.It Fl v
+Verbose mode: it causes
+.Nm
+to print debugging messages as it runs.
+Specifying this flag twice
+makes
+.Nm
+even more verbose.
+.El
+.Sh FILES
+.Bl -tag -width Pa -compact
+.It Pa /var/yp/[domainname]/ypservers
+the NIS ypservers map containing the names of all servers in
+a particular NIS domain
+.El
+.Sh SEE ALSO
+.Xr yp 8 ,
+.Xr ypserv 8 ,
+.Xr ypxfr 8
+.Sh AUTHORS
+.An Bill Paul Aq Mt wpaul@ctr.columbia.edu
+.Sh BUGS
+The mechanism for transferring NIS maps in NIS v1 is different
+than that in NIS version 2.
+This version of
+.Nm
+has support for transferring maps to NIS v2 systems only.
diff --git a/usr.sbin/yppush/yppush_extern.h b/usr.sbin/yppush/yppush_extern.h
new file mode 100644
index 0000000..2a4fbcc
--- /dev/null
+++ b/usr.sbin/yppush/yppush_extern.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 1995
+ * 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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$
+ */
+
+/* Privately defined error codes. */
+#define YPPUSH_TIMEDOUT 255 /* Timed out trying to talk to ypserv */
+#define YPPUSH_NOHOST 254 /* No such host */
+#define YPPUSH_YPSERV 252 /* Failed to contact ypserv. */
+#define YPPUSH_PMAP 251 /* Portmapper failure. */
+#ifndef YPPUSH_RESPONSE_TIMEOUT
+#define YPPUSH_RESPONSE_TIMEOUT 5*60
+#endif
+extern int _rpc_dtablesize(void);
+extern void yppush_xfrrespprog_1(struct svc_req *, SVCXPRT *);
diff --git a/usr.sbin/yppush/yppush_main.c b/usr.sbin/yppush/yppush_main.c
new file mode 100644
index 0000000..5a712e5
--- /dev/null
+++ b/usr.sbin/yppush/yppush_main.c
@@ -0,0 +1,620 @@
+/*
+ * Copyright (c) 1995
+ * 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/fcntl.h>
+#include <sys/wait.h>
+#include <sys/param.h>
+#include <rpc/rpc.h>
+#include <rpc/clnt.h>
+#include <rpc/pmap_clnt.h>
+#include <rpcsvc/yp.h>
+#include <rpcsvc/ypclnt.h>
+#include "ypxfr_extern.h"
+#include "yppush_extern.h"
+
+char *progname = "yppush";
+int debug = 1;
+int _rpcpmstart = 0;
+char *yp_dir = _PATH_YP;
+
+static char *yppush_mapname = NULL; /* Map to transfer. */
+static char *yppush_domain = NULL; /* Domain in which map resides. */
+static char *yppush_master = NULL; /* Master NIS server for said domain. */
+static int skip_master = 0; /* Do not attempt to push map to master. */
+static int verbose = 0; /* Toggle verbose mode. */
+static unsigned long yppush_transid = 0;
+static int yppush_timeout = 80; /* Default timeout. */
+static int yppush_jobs = 1; /* Number of allowed concurrent jobs. */
+static int yppush_running_jobs = 0; /* Number of currently running jobs. */
+
+/* Structure for holding information about a running job. */
+struct jobs {
+ unsigned long tid;
+ int port;
+ ypxfrstat stat;
+ unsigned long prognum;
+ char *server;
+ char *map;
+ int polled;
+ struct jobs *next;
+};
+
+static struct jobs *yppush_joblist; /* Linked list of running jobs. */
+static int yppush_svc_run(int);
+
+/*
+ * Local error messages.
+ */
+static const char *
+yppusherr_string(int err)
+{
+ switch (err) {
+ case YPPUSH_TIMEDOUT:
+ return("transfer or callback timed out");
+ case YPPUSH_YPSERV:
+ return("failed to contact ypserv");
+ case YPPUSH_NOHOST:
+ return("no such host");
+ case YPPUSH_PMAP:
+ return("portmapper failure");
+ default:
+ return("unknown error code");
+ }
+}
+
+/*
+ * Report state of a job.
+ */
+static int
+yppush_show_status(ypxfrstat status, unsigned long tid)
+{
+ struct jobs *job;
+
+ job = yppush_joblist;
+
+ while (job != NULL) {
+ if (job->tid == tid)
+ break;
+ job = job->next;
+ }
+
+ if (job == NULL) {
+ yp_error("warning: received callback with invalid transaction ID: %lu",
+ tid);
+ return (0);
+ }
+
+ if (job->polled) {
+ yp_error("warning: received callback with duplicate transaction ID: %lu",
+ tid);
+ return (0);
+ }
+
+ if (verbose > 1) {
+ yp_error("checking return status: transaction ID: %lu",
+ job->tid);
+ }
+
+ if (status != YPXFR_SUCC || verbose) {
+ yp_error("transfer of map %s to server %s %s",
+ job->map, job->server, status == YPXFR_SUCC ?
+ "succeeded" : "failed");
+ yp_error("status returned by ypxfr: %s", status > YPXFR_AGE ?
+ yppusherr_string(status) :
+ ypxfrerr_string(status));
+ }
+
+ job->polled = 1;
+
+ svc_unregister(job->prognum, 1);
+
+ yppush_running_jobs--;
+ return(0);
+}
+
+/* Exit routine. */
+static void
+yppush_exit(int now)
+{
+ struct jobs *jptr;
+ int still_pending = 1;
+
+ /* Let all the information trickle in. */
+ while (!now && still_pending) {
+ jptr = yppush_joblist;
+ still_pending = 0;
+ while (jptr) {
+ if (jptr->polled == 0) {
+ still_pending++;
+ if (verbose > 1)
+ yp_error("%s has not responded",
+ jptr->server);
+ } else {
+ if (verbose > 1)
+ yp_error("%s has responded",
+ jptr->server);
+ }
+ jptr = jptr->next;
+ }
+ if (still_pending) {
+ if (verbose > 1)
+ yp_error("%d transfer%sstill pending",
+ still_pending,
+ still_pending > 1 ? "s " : " ");
+ if (yppush_svc_run (YPPUSH_RESPONSE_TIMEOUT) == 0) {
+ yp_error("timed out");
+ now = 1;
+ }
+ } else {
+ if (verbose)
+ yp_error("all transfers complete");
+ break;
+ }
+ }
+
+
+ /* All stats collected and reported -- kill all the stragglers. */
+ jptr = yppush_joblist;
+ while (jptr) {
+ if (!jptr->polled)
+ yp_error("warning: exiting with transfer \
+to %s (transid = %lu) still pending", jptr->server, jptr->tid);
+ svc_unregister(jptr->prognum, 1);
+ jptr = jptr->next;
+ }
+
+ exit(0);
+}
+
+/*
+ * Handler for 'normal' signals.
+ */
+
+static void
+handler(int sig)
+{
+ yppush_exit (1);
+ return;
+}
+
+/*
+ * Dispatch loop for callback RPC services.
+ * Return value:
+ * -1 error
+ * 0 timeout
+ * >0 request serviced
+ */
+static int
+yppush_svc_run(int timeout_secs)
+{
+ int rc;
+ fd_set readfds;
+ struct timeval timeout;
+
+ timeout.tv_usec = 0;
+ timeout.tv_sec = timeout_secs;
+
+retry:
+ readfds = svc_fdset;
+ rc = select(svc_maxfd + 1, &readfds, NULL, NULL, &timeout);
+ switch (rc) {
+ case -1:
+ if (errno == EINTR)
+ goto retry;
+ yp_error("select failed: %s", strerror(errno));
+ break;
+ case 0:
+ yp_error("select() timed out");
+ break;
+ default:
+ svc_getreqset(&readfds);
+ break;
+ }
+ return rc;
+}
+
+/*
+ * RPC service routines for callbacks.
+ */
+void *
+yppushproc_null_1_svc(void *argp, struct svc_req *rqstp)
+{
+ static char * result;
+ /* Do nothing -- RPC conventions call for all a null proc. */
+ return((void *) &result);
+}
+
+void *
+yppushproc_xfrresp_1_svc(yppushresp_xfr *argp, struct svc_req *rqstp)
+{
+ static char * result;
+ yppush_show_status(argp->status, argp->transid);
+ return((void *) &result);
+}
+
+/*
+ * Transmit a YPPROC_XFR request to ypserv.
+ */
+static int
+yppush_send_xfr(struct jobs *job)
+{
+ ypreq_xfr req;
+/* ypresp_xfr *resp; */
+ DBT key, data;
+ CLIENT *clnt;
+ struct rpc_err err;
+ struct timeval timeout;
+
+ timeout.tv_usec = 0;
+ timeout.tv_sec = 0;
+
+ /*
+ * The ypreq_xfr structure has a member of type map_parms,
+ * which seems to require the order number of the map.
+ * It isn't actually used at the other end (at least the
+ * FreeBSD ypserv doesn't use it) but we fill it in here
+ * for the sake of completeness.
+ */
+ key.data = "YP_LAST_MODIFIED";
+ key.size = sizeof ("YP_LAST_MODIFIED") - 1;
+
+ if (yp_get_record(yppush_domain, yppush_mapname, &key, &data,
+ 1) != YP_TRUE) {
+ yp_error("failed to read order number from %s: %s: %s",
+ yppush_mapname, yperr_string(yp_errno),
+ strerror(errno));
+ return(1);
+ }
+
+ /* Fill in the request arguments */
+ req.map_parms.ordernum = atoi(data.data);
+ req.map_parms.domain = yppush_domain;
+ req.map_parms.peer = yppush_master;
+ req.map_parms.map = job->map;
+ req.transid = job->tid;
+ req.prog = job->prognum;
+ req.port = job->port;
+
+ /* Get a handle to the remote ypserv. */
+ if ((clnt = clnt_create(job->server, YPPROG, YPVERS, "udp")) == NULL) {
+ yp_error("%s: %s",job->server,clnt_spcreateerror("couldn't \
+create udp handle to NIS server"));
+ switch (rpc_createerr.cf_stat) {
+ case RPC_UNKNOWNHOST:
+ job->stat = YPPUSH_NOHOST;
+ break;
+ case RPC_PMAPFAILURE:
+ job->stat = YPPUSH_PMAP;
+ break;
+ default:
+ job->stat = YPPUSH_RPC;
+ break;
+ }
+ return(1);
+ }
+
+ /*
+ * Reduce timeout to nothing since we may not
+ * get a response from ypserv and we don't want to block.
+ */
+ if (clnt_control(clnt, CLSET_TIMEOUT, (char *)&timeout) == FALSE)
+ yp_error("failed to set timeout on ypproc_xfr call");
+
+ /* Invoke the ypproc_xfr service. */
+ if (ypproc_xfr_2(&req, clnt) == NULL) {
+ clnt_geterr(clnt, &err);
+ if (err.re_status != RPC_SUCCESS &&
+ err.re_status != RPC_TIMEDOUT) {
+ yp_error("%s: %s", job->server, clnt_sperror(clnt,
+ "yp_xfr failed"));
+ job->stat = YPPUSH_YPSERV;
+ clnt_destroy(clnt);
+ return(1);
+ }
+ }
+
+ clnt_destroy(clnt);
+
+ return(0);
+}
+
+/*
+ * Main driver function. Register the callback service, add the transfer
+ * request to the internal list, send the YPPROC_XFR request to ypserv
+ * do other magic things.
+ */
+static int
+yp_push(char *server, char *map, unsigned long tid)
+{
+ unsigned long prognum;
+ int sock = RPC_ANYSOCK;
+ SVCXPRT *xprt;
+ struct jobs *job;
+
+ /* Register the job in our linked list of jobs. */
+
+ /* First allocate job structure */
+ if ((job = (struct jobs *)malloc(sizeof (struct jobs))) == NULL) {
+ yp_error("malloc failed");
+ yppush_exit (1);
+ }
+
+ /*
+ * Register the callback service on the first free transient
+ * program number.
+ */
+ xprt = svcudp_create(sock);
+ for (prognum = 0x40000000; prognum < 0x5FFFFFFF; prognum++) {
+ if (svc_register(xprt, prognum, 1,
+ yppush_xfrrespprog_1, IPPROTO_UDP) == TRUE)
+ break;
+ }
+ if (prognum == 0x5FFFFFFF) {
+ yp_error ("can't register yppush_xfrrespprog_1");
+ yppush_exit (1);
+ }
+
+ /* Initialize the info for this job. */
+ job->stat = 0;
+ job->tid = tid;
+ job->port = xprt->xp_port;
+ job->server = strdup(server);
+ job->map = strdup(map);
+ job->prognum = prognum;
+ job->polled = 0;
+ job->next = yppush_joblist;
+ yppush_joblist = job;
+
+ if (verbose) {
+ yp_error("initiating transfer: %s -> %s (transid = %lu)",
+ yppush_mapname, server, tid);
+ }
+
+ /*
+ * Send the XFR request to ypserv. We don't have to wait for
+ * a response here since we handle them asynchronously.
+ */
+
+ if (yppush_send_xfr(job)){
+ /* Transfer request blew up. */
+ yppush_show_status(job->stat ? job->stat :
+ YPPUSH_YPSERV,job->tid);
+ } else {
+ if (verbose > 1)
+ yp_error("%s has been called", server);
+ }
+
+ return(0);
+}
+
+/*
+ * Called for each entry in the ypservers map from yp_get_map(), which
+ * is our private yp_all() routine.
+ */
+static int
+yppush_foreach(int status, char *key, int keylen, char *val, int vallen,
+ char *data)
+{
+ char server[YPMAXRECORD + 2];
+
+ if (status != YP_TRUE)
+ return (status);
+
+ snprintf(server, sizeof(server), "%.*s", vallen, val);
+ if (skip_master && strcasecmp(server, yppush_master) == 0)
+ return (0);
+
+ /*
+ * Restrict the number of concurrent jobs: if yppush_jobs number
+ * of jobs have already been dispatched and are still pending,
+ * wait for one of them to finish so we can reuse its slot.
+ */
+ while (yppush_running_jobs >= yppush_jobs && (yppush_svc_run (yppush_timeout) > 0))
+ ;
+
+ /* Cleared for takeoff: set everything in motion. */
+ if (yp_push(server, yppush_mapname, yppush_transid))
+ return(yp_errno);
+
+ /* Bump the job counter and transaction ID. */
+ yppush_running_jobs++;
+ yppush_transid++;
+ return (0);
+}
+
+static void
+usage()
+{
+ fprintf (stderr, "%s\n%s\n",
+ "usage: yppush [-d domain] [-t timeout] [-j #parallel jobs] [-h host]",
+ " [-p path] mapname");
+ exit(1);
+}
+
+/*
+ * Entry point. (About time!)
+ */
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ DBT key, data;
+ char myname[MAXHOSTNAMELEN];
+ struct hostlist {
+ char *name;
+ struct hostlist *next;
+ };
+ struct hostlist *yppush_hostlist = NULL;
+ struct hostlist *tmp;
+
+ while ((ch = getopt(argc, argv, "d:j:p:h:t:v")) != -1) {
+ switch (ch) {
+ case 'd':
+ yppush_domain = optarg;
+ break;
+ case 'j':
+ yppush_jobs = atoi(optarg);
+ if (yppush_jobs <= 0)
+ yppush_jobs = 1;
+ break;
+ case 'p':
+ yp_dir = optarg;
+ break;
+ case 'h': /* we can handle multiple hosts */
+ if ((tmp = (struct hostlist *)malloc(sizeof(struct hostlist))) == NULL) {
+ yp_error("malloc failed");
+ yppush_exit(1);
+ }
+ tmp->name = strdup(optarg);
+ tmp->next = yppush_hostlist;
+ yppush_hostlist = tmp;
+ break;
+ case 't':
+ yppush_timeout = atoi(optarg);
+ break;
+ case 'v':
+ verbose++;
+ break;
+ default:
+ usage();
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ yppush_mapname = argv[0];
+
+ if (yppush_mapname == NULL) {
+ /* "No guts, no glory." */
+ usage();
+ }
+
+ /*
+ * If no domain was specified, try to find the default
+ * domain. If we can't find that, we're doomed and must bail.
+ */
+ if (yppush_domain == NULL) {
+ char *yppush_check_domain;
+ if (!yp_get_default_domain(&yppush_check_domain) &&
+ !_yp_check(&yppush_check_domain)) {
+ yp_error("no domain specified and NIS not running");
+ usage();
+ } else
+ yp_get_default_domain(&yppush_domain);
+ }
+
+ /* Check to see that we are the master for this map. */
+
+ if (gethostname ((char *)&myname, sizeof(myname))) {
+ yp_error("failed to get name of local host: %s",
+ strerror(errno));
+ yppush_exit(1);
+ }
+
+ key.data = "YP_MASTER_NAME";
+ key.size = sizeof("YP_MASTER_NAME") - 1;
+
+ if (yp_get_record(yppush_domain, yppush_mapname,
+ &key, &data, 1) != YP_TRUE) {
+ yp_error("couldn't open %s map: %s", yppush_mapname,
+ strerror(errno));
+ yppush_exit(1);
+ }
+
+ if (strncasecmp(myname, data.data, data.size) == 0) {
+ /* I am master server, and no explicit host list was
+ specified: do not push map to myself -- this will
+ fail with YPPUSH_AGE anyway. */
+ if (yppush_hostlist == NULL)
+ skip_master = 1;
+ } else {
+ yp_error("warning: this host is not the master for %s",
+ yppush_mapname);
+#ifdef NITPICKY
+ yppush_exit(1);
+#endif
+ }
+
+ yppush_master = malloc(data.size + 1);
+ strncpy(yppush_master, data.data, data.size);
+ yppush_master[data.size] = '\0';
+
+ /* Install some handy handlers. */
+ signal(SIGTERM, handler);
+ signal(SIGINT, handler);
+
+ /* set initial transaction ID */
+ yppush_transid = time((time_t *)NULL);
+
+ if (yppush_hostlist) {
+ /*
+ * Host list was specified on the command line:
+ * kick off the transfers by hand.
+ */
+ tmp = yppush_hostlist;
+ while (tmp) {
+ yppush_foreach(YP_TRUE, NULL, 0, tmp->name,
+ strlen(tmp->name), NULL);
+ tmp = tmp->next;
+ }
+ } else {
+ /*
+ * Do a yp_all() on the ypservers map and initiate a ypxfr
+ * for each one.
+ */
+ ypxfr_get_map("ypservers", yppush_domain,
+ "localhost", yppush_foreach);
+ }
+
+ if (verbose > 1)
+ yp_error("all jobs dispatched");
+
+ /* All done -- normal exit. */
+ yppush_exit(0);
+
+ /* Just in case. */
+ exit(0);
+}
diff --git a/usr.sbin/ypserv/Makefile b/usr.sbin/ypserv/Makefile
new file mode 100644
index 0000000..9d045b8
--- /dev/null
+++ b/usr.sbin/ypserv/Makefile
@@ -0,0 +1,45 @@
+# $FreeBSD$
+
+RPCDIR= ${.CURDIR}/../../include/rpcsvc
+.PATH: ${RPCDIR} \
+ ${.CURDIR}/common
+
+PROG= ypserv
+MAN= ypserv.8 ypinit.8
+SRCS= yp_svc.c yp_server.c yp_dblookup.c yp_dnslookup.c \
+ ypxfr_clnt.c yp.h yp_main.c yp_error.c yp_access.c yp_svc_udp.c \
+ yplib_host.c
+
+CFLAGS+= -DDB_CACHE -DTCP_WRAPPER -I.
+
+WARNS?= 0
+
+LIBADD= wrap
+
+CLEANFILES= yp_svc.c ypxfr_clnt.c yp.h
+
+RPCGEN= RPCGEN_CPP=${CPP:Q} rpcgen -I -C
+
+# We need to remove the 'static' keyword from _rpcsvcstate so that
+# yp_main.c can see it.
+yp_svc.c: yp.x
+ rm -f ${.TARGET}
+ ${RPCGEN} -DYPSERV_ONLY -m ${RPCDIR}/yp.x | \
+ sed s/"static int _rpcsvcstate"/"int _rpcsvcstate"/g > ${.TARGET}
+
+ypxfr_clnt.c: yp.x
+ ${RPCGEN} -DYPPUSH_ONLY -l -o ${.TARGET} ${RPCDIR}/yp.x
+
+yp.h: yp.x
+ ${RPCGEN} -h -o ${.TARGET} ${RPCDIR}/yp.x
+
+FILES= Makefile.yp
+FILESNAME= Makefile.dist
+FILESDIR= /var/yp
+SCRIPTS= ypinit.sh
+
+.if !exists(${DESTDIR}${FILESDIR}/Makefile)
+SYMLINKS= ${FILESNAME} ${FILESDIR}/Makefile
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ypserv/Makefile.depend b/usr.sbin/ypserv/Makefile.depend
new file mode 100644
index 0000000..5283d7e
--- /dev/null
+++ b/usr.sbin/ypserv/Makefile.depend
@@ -0,0 +1,34 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libwrap \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+yp_main.o: yp.h
+yp_main.po: yp.h
+yp_server.o: yp.h
+yp_server.po: yp.h
+yp_svc.o: yp.h
+yp_svc.o: yp_svc.c
+yp_svc.po: yp.h
+yp_svc.po: yp_svc.c
+ypxfr_clnt.o: yp.h
+ypxfr_clnt.o: ypxfr_clnt.c
+ypxfr_clnt.po: yp.h
+ypxfr_clnt.po: ypxfr_clnt.c
+.endif
diff --git a/usr.sbin/ypserv/Makefile.yp b/usr.sbin/ypserv/Makefile.yp
new file mode 100644
index 0000000..17eabe5
--- /dev/null
+++ b/usr.sbin/ypserv/Makefile.yp
@@ -0,0 +1,695 @@
+#
+# Makefile for the NIS databases
+#
+# $FreeBSD$
+#
+# This Makefile should only be run on the NIS master server of a domain.
+# All updated maps will be pushed to all NIS slave servers listed in the
+# /var/yp/ypservers file. Please make sure that the hostnames of all
+# NIS servers in your domain are listed in /var/yp/ypservers.
+#
+# This Makefile can be modified to support more NIS maps if desired.
+#
+
+# If this machine is an NIS master, reset this variable (NOPUSH=)
+# in Makefile.local so that changes to the NIS maps can be propagated to
+# the slave servers. (By default we assume that we are only serving a
+# small domain with only one server.)
+#
+NOPUSH = "True"
+
+# If this machine does not wish to generate a linux-style shadow map
+# from the master.passwd file, reset this variable (SHADOW=) in
+# Makefile.local.
+SHADOW = "True"
+
+# If you want to use a FreeBSD NIS server to serve non-FreeBSD clients
+# (i.e. clients who expect the password field in the passwd maps to be
+# valid) then set this variable (UNSECURE="True") in Makefile.local.
+# This will cause $YPDIR/passwd to be generated with valid password
+# fields. This is insecure: FreeBSD normally only serves the
+# master.passwd and shadow maps (which have real encrypted passwords
+# in them) to the superuser on other FreeBSD machines, but non-FreeBSD
+# clients (e.g. SunOS, Solaris (without NIS+), IRIX, HP-UX, etc...)
+# will only work properly in 'unsecure' mode.
+#
+#UNSECURE = "True"
+
+# The following line encodes the YP_INTERDOMAIN key into the hosts.byname
+# and hosts.byaddr maps so that ypserv(8) will do DNS lookups to resolve
+# hosts not in the current domain. Resetting this variable in
+# Makefile.local (B=) will disable the DNS lookups.
+B=-b
+
+# Normally, the master.passwd.* and shadow.* maps are guarded against access
+# from non-privileged users. By resetting S in Makefile.local (S=), the
+# YP_SECURE key will be removed from these maps, allowing anyone to access
+# them.
+S=-s
+
+# These are commands which this Makefile needs to properly rebuild the
+# NIS databases. Don't change these unless you have a good reason. Also
+# be sure not to place an @ in front of /usr/bin/awk: it isn't necessary
+# and it'll break everything in sight.
+#
+AWK = /usr/bin/awk
+RM = @/bin/rm -f
+MV = @/bin/mv -f
+RMV = /bin/mv -f
+
+MKDB = /usr/sbin/yp_mkdb
+DBLOAD = $(MKDB) -m `hostname`
+MKNETID = /usr/libexec/mknetid
+NEWALIASES = /usr/bin/newaliases
+YPPUSH = /usr/sbin/yppush
+.if !defined(UPDATE_DOMAIN)
+DOMAIN = `/bin/domainname`
+.else
+DOMAIN = $(UPDATE_DOMAIN)
+.endif
+REVNETGROUP = /usr/libexec/revnetgroup
+TMP = `echo $@.$$$$`
+
+# It is advisable to create a separate directory to contain the
+# source files used to generate your NIS maps. If you intend to
+# support multiple domains, something like /src/dir/$DOMAIN
+# would work well.
+YPSRCDIR = /etc
+.if !defined(YP_DIR)
+YPDIR = /var/yp
+.else
+YPDIR = $(YP_DIR)
+.endif
+YPMAPDIR = $(YPDIR)/$(DOMAIN)
+
+# These are the files from which the NIS databases are built. You may edit
+# these to taste in the event that you wish to keep your NIS source files
+# separate from your NIS server's actual configuration files. Note that the
+# NIS passwd and master.passwd files are stored in /var/yp: the server's
+# real password database is not used by default. However, you may use
+# the real /etc/passwd and /etc/master.passwd files by:
+#
+#
+# - invoking yppasswdd with `-t /etc/master.passwd' (yppasswdd will do a
+# 'pwd_mkdb' as needed if /etc/master.passwd is thus specified).
+# - Specifying the location of the master.passwd file using the
+# MASTER_PASSWD variable, i.e.:
+#
+# # make MASTER_PASSWD=/path/to/some/other/master.passwd
+#
+# - (optionally): editing this Makefile to change the default location.
+#
+# To add a user, edit $(YPDIR)/master.passwd and type 'make'. The raw
+# passwd file will be generated from the master.passwd file automagically.
+#
+ETHERS = $(YPSRCDIR)/ethers # ethernet addresses (for rarpd)
+EUI64 = $(YPSRCDIR)/eui64 # eui64 addresses (for firewire)
+BOOTPARAMS= $(YPSRCDIR)/bootparams # for booting Sun boxes (bootparamd)
+HOSTS = $(YPSRCDIR)/hosts
+IPNODES = $(YPDIR)/ipnodes
+NETWORKS = $(YPSRCDIR)/networks
+PROTOCOLS = $(YPSRCDIR)/protocols
+RPC = $(YPSRCDIR)/rpc
+SERVICES = $(YPSRCDIR)/services
+SHELLS = $(YPSRCDIR)/shells
+GROUP = $(YPSRCDIR)/group
+ALIASES = $(YPSRCDIR)/mail/aliases
+NETGROUP = $(YPDIR)/netgroup
+PASSWD = $(YPDIR)/passwd
+.if !defined(MASTER_PASSWD)
+MASTER = $(YPDIR)/master.passwd
+.else
+MASTER = $(MASTER_PASSWD)
+.endif
+YPSERVERS = $(YPDIR)/ypservers # List of all NIS servers for a domain
+PUBLICKEY = $(YPSRCDIR)/publickey
+NETID = $(YPSRCDIR)/netid
+AMDHOST = $(YPSRCDIR)/amd.map
+
+target:
+ @if [ ! -d $(DOMAIN) ]; then mkdir $(DOMAIN); fi; \
+ cd $(DOMAIN) ; echo "NIS Map update started on `date` for domain $(DOMAIN)" ; \
+ make -f ../Makefile all; echo "NIS Map update completed."
+
+# Read overrides. Note, the current directory will be /var/yp/<domain>
+# when 'all' is built.
+.if exists(${YPDIR}/Makefile.local)
+.include "${YPDIR}/Makefile.local"
+.endif
+
+# List of maps that are always built.
+# If you want to omit some of them, feel free to comment
+# them out from this list.
+TARGETS= servers hosts networks protocols rpc services shells group
+TARGETS+= ipnodes
+#TARGETS+= aliases
+
+# Sanity checks: filter out targets we can't build
+# Note that we don't build the ethers, eui64, or boorparams maps by default
+# since /etc/ethers, /etc/eui64 and /etc/bootparams are not likely to be present
+# on all systems.
+.if exists($(ETHERS))
+TARGETS+= ethers
+.else
+ETHERS= /dev/null
+.endif
+
+.if exists($(EUI64))
+TARGETS+= eui64
+.else
+EUI64= /dev/null
+.endif
+
+.if exists($(BOOTPARAMS))
+TARGETS+= bootparams
+.else
+BOOTPARAMS= /dev/null
+.endif
+
+.if exists($(NETGROUP))
+TARGETS+= netgrp
+.else
+NETGROUP= /dev/null
+.endif
+
+.if exists($(MASTER))
+TARGETS+= passwd master.passwd netid
+.if ${SHADOW} == "\"True\""
+TARGETS+= shadow
+.endif
+.else
+MASTER= /dev/null
+TARGETS+= nopass
+.endif
+
+.if exists($(PUBLICKEY))
+TARGETS+= publickey
+.else
+PUBLICKEY= /dev/null
+.endif
+
+.if exists($(AMDHOST))
+TARGETS+= amd.map
+.else
+AMDHOST= /dev/null
+.endif
+
+.if !exists($(IPNODES))
+IPNODES= $(HOSTS)
+.endif
+
+all: $(TARGETS)
+
+ethers: ethers.byname ethers.byaddr
+eui64: eui64.byname eui64.byid
+bootparam: bootparams
+hosts: hosts.byname hosts.byaddr
+ipnodes: ipnodes.byname ipnodes.byaddr
+networks: networks.byaddr networks.byname
+protocols: protocols.bynumber protocols.byname
+rpc: rpc.byname rpc.bynumber
+services: services.byname
+passwd: passwd.byname passwd.byuid
+shadow: shadow.byname shadow.byuid
+group: group.byname group.bygid
+netgrp: netgroup
+netid: netid.byname
+servers: ypservers
+publickey: publickey.byname
+aliases: mail.aliases
+
+master.passwd: master.passwd.byname master.passwd.byuid
+
+#
+# This is a special target used only when doing in-place updates with
+# rpc.yppasswdd. In this case, the maps will be updated by the rpc.yppasswdd
+# server and won't need to be remade. They will have to be pushed to the
+# slaves however. Calling this target implicitly insures that this will
+# happen.
+#
+pushpw:
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) master.passwd.byname ; fi
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) master.passwd.byuid ; fi
+.if ${SHADOW} == "\"True\""
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) shadow.byname ; fi
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) shadow.byuid ; fi
+.endif
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) passwd.byname ; fi
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) passwd.byuid ; fi
+
+pushmap:
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $(PUSHMAP) ; fi
+
+nopass:
+ @echo ""
+ @echo " ********WARNING********"
+ @echo " Couldn't find the master.passwd source file. This file"
+ @echo " is needed to generate the master.passwd and passwd maps."
+ @echo " The default location is /var/yp/master.passwd. You should"
+ @echo " edit /var/yp/Makefile and set the MASTER variable to point"
+ @echo " to the source file you wish to use for building the passwd"
+ @echo " maps, or else invoke make(1) in the following manner:"
+ @echo ""
+ @echo " make MASTER_PASSWD=/path/to/master.passwd"
+ @echo ""
+
+mail.aliases: $(ALIASES)
+ @echo "Updating $@..."
+ @$(NEWALIASES) -oA$(ALIASES)
+ @$(MKDB) -u $(ALIASES).db \
+ | $(DBLOAD) -i $(ALIASES) -o $(YPMAPDIR)/$@ - $(TMP); \
+ $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+
+
+ypservers: $(YPSERVERS)
+ @echo "Updating $@..."
+ @$(AWK) '{ if ($$1 != "" && $$1 !~ "^#.*") print $$0"\t"$$0 }' \
+ $(YPSERVERS) \
+ | $(DBLOAD) -i $(YPSERVERS) -o $(YPMAPDIR)/$@ - $(TMP); \
+ $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+
+ethers.byname: $(ETHERS)
+ @echo "Updating $@..."
+.if ${ETHERS} == "/dev/null"
+ @echo "Ethers source file not found -- skipping"
+.else
+ @$(AWK) '{ if ($$1 != "" && $$1 !~ "^#.*" && $$1 != "+") \
+ print $$2"\t"$$0 }' $(ETHERS) | $(DBLOAD) -i $(ETHERS) \
+ -o $(YPMAPDIR)/$@ - $(TMP); $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+.endif
+
+ethers.byaddr: $(ETHERS)
+ @echo "Updating $@..."
+.if ${ETHERS} == "/dev/null"
+ @echo "Ethers source file not found -- skipping"
+.else
+ @$(AWK) '{ if ($$1 != "" && $$1 !~ "^#.*" && $$1 != "+") \
+ print $$1"\t"$$0 }' $(ETHERS) | $(DBLOAD) -i $(ETHERS) \
+ -o $(YPMAPDIR)/$@ - $(TMP); $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+.endif
+
+eui64.byname: $(EUI64)
+ @echo "Updating $@..."
+.if ${EUI64} == "/dev/null"
+ @echo "EUI64 source file not found -- skipping"
+.else
+ @$(AWK) '{ if ($$1 != "" && $$1 !~ "^#.*" && $$1 != "+") \
+ print $$2"\t"$$0 }' $(EUI64) | $(DBLOAD) -i $(EUI64) \
+ -o $(YPMAPDIR)/$@ - $(TMP); $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+.endif
+
+eui64.byid: $(EUI64)
+ @echo "Updating $@..."
+.if ${EUI64} == "/dev/null"
+ @echo "EUI64 source file not found -- skipping"
+.else
+ @$(AWK) '{ if ($$1 != "" && $$1 !~ "^#.*" && $$1 != "+") \
+ print $$1"\t"$$0 }' $(EUI64) | $(DBLOAD) -i $(EUI64) \
+ -o $(YPMAPDIR)/$@ - $(TMP); $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+.endif
+
+
+bootparams: $(BOOTPARAMS)
+ @echo "Updating $@..."
+.if ${BOOTPARAMS} == "/dev/null"
+ @echo "Bootparams source file not found -- skipping"
+.else
+ @$(AWK) '{ if ($$1 != "" && $$1 !~ "^#.*" && $$1 != "+") \
+ print $$0 }' $(BOOTPARAMS) | $(DBLOAD) -i $(BOOTPARAMS) \
+ -o $(YPMAPDIR)/$@ - $(TMP); $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+.endif
+
+
+netgroup: $(NETGROUP) netgroup.byhost netgroup.byuser
+ @echo "Updating $@..."
+.if ${NETGROUP} == "/dev/null"
+ @echo "Netgroup source file not found -- skipping"
+.else
+ @$(AWK) '{ if ($$1 != "" && $$1 !~ "^#.*" && $$1 != "+") \
+ print $$0 }' $(NETGROUP) | $(DBLOAD) -i $(NETGROUP) \
+ -o $(YPMAPDIR)/$@ - $(TMP); $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+.endif
+
+
+netgroup.byhost: $(NETGROUP)
+ @echo "Updating $@..."
+.if ${NETGROUP} == "/dev/null"
+ @echo "Netgroup source file not found -- skipping"
+.else
+ @$(REVNETGROUP) -h -f $(NETGROUP) | \
+ $(AWK) '{ if ($$1 != "" && $$1 !~ "^#.*" && $$1 != "+") \
+ print $$0 }' | $(DBLOAD) -i $(NETGROUP) \
+ -o $(YPMAPDIR)/$@ - $(TMP); $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+.endif
+
+
+netgroup.byuser: $(NETGROUP)
+ @echo "Updating $@..."
+.if ${NETGROUP} == "/dev/null"
+ @echo "Netgroup source file not found -- skipping"
+.else
+ @$(REVNETGROUP) -u -f $(NETGROUP) | \
+ $(AWK) '{ if ($$1 != "" && $$1 !~ "^#.*" && $$1 != "+") \
+ print $$0 }' | $(DBLOAD) -i $(NETGROUP) \
+ -o $(YPMAPDIR)/$@ - $(TMP); $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+.endif
+
+
+# Solaris 8 does the following:
+# - /etc/hosts and hosts.{byname,byaddr} are IPv4 only.
+# - /etc/inet/ipnodes and ipnodes.{byname,byaddr} are used for protocol
+# independent name-to-address mapping.
+#
+# For local name resolution, we made /etc/hosts protocol independent.
+# For NIS name resolution, we obey Solaris 8 practice.
+# - We keep hosts.{byname,byaddr} IPv4 only, to be friendly with Solaris 8
+# clients.
+# - ipnodes.{byname,byaddr} is used for protocol independent mapping.
+# We generate all the mappings from /etc/hosts unless /var/yp/ipnodes
+# exists, for compatibility with FreeBSD local name resolution.
+#
+hosts.byname: $(HOSTS)
+ @echo "Updating $@..."
+ @$(AWK) '/^[0-9.]+[\t ]/ { for (n=2; n<=NF && $$n !~ "^#.*"; n++) \
+ print $$n"\t"$$0 }' $(HOSTS) | $(DBLOAD) ${B} -i $(HOSTS) \
+ -o $(YPMAPDIR)/$@ - $(TMP); $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+
+
+hosts.byaddr: $(HOSTS)
+ @echo "Updating $@..."
+ @$(AWK) '/^[0-9.]+[\t ]/ { print $$1"\t"$$0 }' $(HOSTS) \
+ | $(DBLOAD) ${B} -i $(HOSTS) -o $(YPMAPDIR)/$@ - $(TMP); \
+ $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+
+
+ipnodes.byname: $(IPNODES)
+ @echo "Updating $@..."
+ @$(AWK) '/^[0-9a-fA-F:]/ { for (n=2; n<=NF && $$n !~ "^#.*"; n++) \
+ print $$n"\t"$$0 }' $(IPNODES) | $(DBLOAD) ${B} -i $(IPNODES) \
+ -o $(YPMAPDIR)/$@ - $(TMP); $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+
+
+ipnodes.byaddr: $(IPNODES)
+ @echo "Updating $@..."
+ @$(AWK) '$$1 !~ "^#.*" { print $$1"\t"$$0 }' $(IPNODES) \
+ | $(DBLOAD) ${B} -i $(IPNODES) -o $(YPMAPDIR)/$@ - $(TMP); \
+ $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+
+
+networks.byname: $(NETWORKS)
+ @echo "Updating $@..."
+ @$(AWK) \
+ '$$1 !~ "^#.*" { print $$1"\t"$$0; \
+ for (n=3; n<=NF && $$n !~ "^#.*"; n++) \
+ print $$n"\t"$$0 \
+ }' $(NETWORKS) \
+ | $(DBLOAD) -i $(NETWORKS) -o $(YPMAPDIR)/$@ - $(TMP); \
+ $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+
+
+networks.byaddr: $(NETWORKS)
+ @echo "Updating $@..."
+ @$(AWK) '$$1 !~ "^#.*" { print $$2"\t"$$0 }' $(NETWORKS) \
+ | $(DBLOAD) -i $(NETWORKS) -o $(YPMAPDIR)/$@ - $(TMP); \
+ $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+
+
+protocols.byname: $(PROTOCOLS)
+ @echo "Updating $@..."
+ @$(AWK) \
+ '$$1 !~ "^#.*" { print $$1"\t"$$0; \
+ for (n=3; n<=NF && $$n !~ "^#.*"; n++) \
+ print $$n"\t"$$0 \
+ }' $(PROTOCOLS) | $(DBLOAD) -i $(PROTOCOLS) \
+ -o $(YPMAPDIR)/$@ - $(TMP); $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+
+
+protocols.bynumber: $(PROTOCOLS)
+ @echo "Updating $@..."
+ @$(AWK) '$$1 !~ "^#.*" { print $$2"\t"$$0 }' $(PROTOCOLS) \
+ | $(DBLOAD) -i $(PROTOCOLS) -o $(YPMAPDIR)/$@ - $(TMP); \
+ $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+
+
+rpc.byname: $(RPC)
+ @echo "Updating $@..."
+ @$(AWK) \
+ '$$1 !~ "^#.*" { print $$1"\t"$$0; \
+ for (n=3; n<=NF && $$n !~ "^#.*"; n++) \
+ print $$n"\t"$$0 \
+ }' $(RPC) | $(DBLOAD) -i $(RPC) -o $(YPMAPDIR)/$@ - $(TMP); \
+ $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+
+
+rpc.bynumber: $(RPC)
+ @echo "Updating $@..."
+ @$(AWK) '$$1 !~ "^#.*" { print $$2"\t"$$0 }' $(RPC) \
+ | $(DBLOAD) -i $(RPC) -o $(YPMAPDIR)/$@ - $(TMP); \
+ $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+
+
+services.byname: $(SERVICES)
+ @echo "Updating $@..."
+ @$(AWK) \
+ '$$1 !~ "^#.*" { for (n=1; n<=NF && $$n !~ "^#.*"; n++) { \
+ if (split($$2, t, "/")) { \
+ printf("%s/%s", $$n, t[2]) }; \
+ print "\t"$$0; \
+ if (n == 1) n = 2; \
+ } ; print $$2"\t"$$0 ; \
+ }' $(SERVICES) \
+ | $(DBLOAD) -i $(SERVICES) -o $(YPMAPDIR)/$@ - $(TMP); \
+ $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+
+shells: $(SHELLS)
+ @echo "Updating $@..."
+ @$(AWK) '{ if ($$1 != "" && $$1 !~ "^#.*") print $$0"\t"$$0 }' \
+ $(SHELLS) \
+ | $(DBLOAD) -i $(SHELLS) -o $(YPMAPDIR)/$@ - $(TMP); \
+ $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+
+publickey.byname: $(PUBLICKEY)
+ @echo "Updating $@..."
+.if ${PUBLICKEY} == "/dev/null"
+ @echo "Publickey source file not found -- skipping"
+.else
+ @$(AWK) '$$1 !~ "^#.*" { print $$1"\t"$$2 }' $(PUBLICKEY) \
+ | $(DBLOAD) -i $(PUBLICKEY) -o $(YPMAPDIR)/$@ - $(TMP); \
+ $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+.endif
+
+
+$(PASSWD): $(MASTER)
+ @echo "Creating new $@ file from $(MASTER)..."
+ @if [ ! $(UNSECURE) ]; then \
+ $(AWK) -F: '{if ($$1 != "" && $$1 !~ "^#.*" && $$1 != "+") \
+ print $$1":*:"$$3":"$$4":"$$8":"$$9":"$$10}' $(MASTER) \
+ > $(PASSWD) ; \
+ else \
+ $(AWK) -F: '{if ($$1 != "" && $$1 !~ "^#.*" && $$1 != "+") \
+ print $$1":"$$2":"$$3":"$$4":"$$8":"$$9":"$$10}' $(MASTER) \
+ > $(PASSWD) ; fi
+
+
+passwd.byname: $(PASSWD)
+ @echo "Updating $@..."
+ @$(AWK) -F: '{ if ($$1 != "" && $$1 !~ "^#.*" && $$1 != "+") \
+ print $$1"\t"$$0 }' $(PASSWD) \
+ | $(DBLOAD) -f -i $(PASSWD) -o $(YPMAPDIR)/$@ - $(TMP); \
+ $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+
+
+passwd.byuid: $(PASSWD)
+ @echo "Updating $@..."
+ @$(AWK) -F: '{ if ($$1 != "" && $$1 !~ "^#.*" && $$1 != "+") \
+ print $$3"\t"$$0 }' $(PASSWD) \
+ | $(DBLOAD) -f -i $(PASSWD) -o $(YPMAPDIR)/$@ - $(TMP); \
+ $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+
+
+group.byname: $(GROUP)
+ @echo "Updating $@..."
+ @$(AWK) -F: '{ if ($$1 != "" && $$1 !~ "^#.*" && $$1 != "+") \
+ print $$1"\t"$$0 }' $(GROUP) \
+ | $(DBLOAD) -f -i $(GROUP) -o $(YPMAPDIR)/$@ - $(TMP); \
+ $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+
+
+group.bygid: $(GROUP)
+ @echo "Updating $@..."
+ @$(AWK) -F: '{ if ($$1 != "" && $$1 !~ "^#.*" && $$1 != "+") \
+ print $$3"\t"$$0 }' $(GROUP) \
+ | $(DBLOAD) -f -i $(GROUP) -o $(YPMAPDIR)/$@ - $(TMP); \
+ $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+
+
+netid.byname: $(GROUP) $(PASSWD) $(HOSTS)
+ @echo "Updating $@..."
+ @$(MKNETID) -q -p $(PASSWD) -g $(GROUP) -h $(HOSTS) -n $(NETID) \
+ -d $(DOMAIN) | $(DBLOAD) -o $(YPMAPDIR)/$@ - $(TMP); \
+ $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+
+
+master.passwd.byname: $(MASTER)
+ @echo "Updating $@..."
+.if ${MASTER} == "/dev/null"
+ @echo "Master.passwd source file not found -- skipping"
+.else
+ @$(AWK) -F: '{ if ($$1 != "" && $$1 !~ "^#.*" && $$1 != "+") \
+ print $$1"\t"$$0 }' $(MASTER) \
+ | $(DBLOAD) ${S} -f -i $(MASTER) -o $(YPMAPDIR)/$@ - $(TMP); \
+ $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+.endif
+
+
+master.passwd.byuid: $(MASTER)
+ @echo "Updating $@..."
+.if ${MASTER} == "/dev/null"
+ @echo "Master.passwd source file not found -- skipping"
+.else
+ @$(AWK) -F: '{ if ($$1 != "" && $$1 !~ "^#.*" && $$1 != "+") \
+ print $$3"\t"$$0 }' $(MASTER) \
+ | $(DBLOAD) ${S} -f -i $(MASTER) -o $(YPMAPDIR)/$@ - $(TMP); \
+ $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+.endif
+
+
+shadow.byname: $(MASTER)
+ @echo "Updating $@..."
+.if ${MASTER} == "/dev/null"
+ @echo "Master.passwd source file not found -- skipping"
+.else
+ @$(AWK) -F: '{ if ($$1 != "" && $$1 !~ "^#.*" && $$1 != "+") \
+ print $$1"\t"$$1":"$$2":12000:0:99999:7:::" }' $(MASTER) \
+ | sed 's/\( [^:]*:\)\*:/\1!:/' \
+ | $(DBLOAD) ${S} -f -i $(PASSWD) -o $(YPMAPDIR)/$@ - $(TMP); \
+ $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+.endif
+
+shadow.byuid: $(MASTER)
+ @echo "Updating $@..."
+.if ${MASTER} == "/dev/null"
+ @echo "Master.passwd source file not found -- skipping"
+.else
+ @$(AWK) -F: '{ if ($$1 != "" && $$1 !~ "^#.*" && $$1 != "+") \
+ print $$3"\t"$$1":"$$2":12000:0:99999:7:::" }' $(MASTER) \
+ | sed 's/\( [^:]*:\)\*:/\1!:/' \
+ | $(DBLOAD) ${S} -f -i $(PASSWD) -o $(YPMAPDIR)/$@ - $(TMP); \
+ $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
+.endif
+
+amd.map: $(AMDHOST)
+ @echo "Updating $@..."
+ @$(AWK) '$$1 !~ "^#.*" { \
+ for (i = 1; i <= NF; i++) \
+ if (i == NF) { \
+ if (substr($$i, length($$i), 1) == "\\") \
+ printf("%s", substr($$i, 1, length($$i) - 1)); \
+ else \
+ printf("%s\n", $$i); \
+ } \
+ else \
+ printf("%s ", $$i); \
+ }' $(AMDHOST) | \
+ $(DBLOAD) -i $(AMDHOST) -o $(YPMAPDIR)/$@ - $(TMP); \
+ $(RMV) $(TMP) $@
+ @$(DBLOAD) -c
+ @if [ ! $(NOPUSH) ]; then $(YPPUSH) -d $(DOMAIN) $@; fi
+ @if [ ! $(NOPUSH) ]; then echo "Pushed $@ map." ; fi
diff --git a/usr.sbin/ypserv/common/yplib_host.c b/usr.sbin/ypserv/common/yplib_host.c
new file mode 100644
index 0000000..05b6e96
--- /dev/null
+++ b/usr.sbin/ypserv/common/yplib_host.c
@@ -0,0 +1,356 @@
+/* $OpenBSD: yplib_host.c,v 1.18 2015/01/16 06:40:22 deraadt Exp $ */
+
+/*
+ * Copyright (c) 1992, 1993 Theo de Raadt <deraadt@theos.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 ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED 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/socket.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/file.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <rpc/rpc.h>
+#include <rpc/xdr.h>
+#include <rpcsvc/yp.h>
+#include <rpcsvc/ypclnt.h>
+
+#include "yplib_host.h"
+
+extern int (*ypresp_allfn)(u_long, char *, int, char *, int, void *);
+extern void *ypresp_data;
+extern bool_t xdr_ypreq_key(), xdr_ypresp_val();
+extern bool_t xdr_ypresp_all_seq();
+
+static int _yplib_host_timeout = 10;
+
+CLIENT *
+yp_bind_host(char *server, u_long program, u_long version, u_short port,
+ int usetcp)
+{
+ struct sockaddr_in rsrv_sin;
+ static CLIENT *client;
+ struct hostent *h;
+ struct timeval tv;
+ int rsrv_sock;
+
+ memset(&rsrv_sin, 0, sizeof rsrv_sin);
+ rsrv_sin.sin_len = sizeof rsrv_sin;
+ rsrv_sin.sin_family = AF_INET;
+ rsrv_sock = RPC_ANYSOCK;
+ if (port != 0)
+ rsrv_sin.sin_port = htons(port);
+
+ if (*server >= '0' && *server <= '9') {
+ if (inet_aton(server, &rsrv_sin.sin_addr) == 0) {
+ errx(1, "inet_aton: invalid address %s.",
+ server);
+ }
+ } else {
+ h = gethostbyname(server);
+ if (h == NULL) {
+ errx(1, "gethostbyname: unknown host %s.",
+ server);
+ }
+ rsrv_sin.sin_addr.s_addr = *(u_int32_t *)h->h_addr;
+ }
+
+ tv.tv_sec = 10;
+ tv.tv_usec = 0;
+
+ if (usetcp)
+ client = clnttcp_create(&rsrv_sin, program, version,
+ &rsrv_sock, 0, 0);
+ else
+ client = clntudp_create(&rsrv_sin, program, version, tv,
+ &rsrv_sock);
+
+ if (client == NULL) {
+ errx(1, "clntudp_create: no contact with host %s.",
+ server);
+ }
+
+ return (client);
+}
+
+CLIENT *
+yp_bind_local(u_long program, u_long version)
+{
+ struct sockaddr_in rsrv_sin;
+ static CLIENT *client;
+ struct timeval tv;
+ int rsrv_sock;
+
+ memset(&rsrv_sin, 0, sizeof rsrv_sin);
+ rsrv_sin.sin_len = sizeof rsrv_sin;
+ rsrv_sin.sin_family = AF_INET;
+ rsrv_sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ rsrv_sock = RPC_ANYSOCK;
+
+ tv.tv_sec = 10;
+ tv.tv_usec = 0;
+
+ client = clntudp_create(&rsrv_sin, program, version, tv, &rsrv_sock);
+ if (client == NULL) {
+ errx(1, "clntudp_create: no contact with localhost.");
+ }
+
+ return (client);
+}
+
+int
+yp_match_host(CLIENT *client, char *indomain, char *inmap, const char *inkey,
+ int inkeylen, char **outval, int *outvallen)
+{
+ struct ypresp_val yprv;
+ struct ypreq_key yprk;
+ struct timeval tv;
+ int r;
+
+ *outval = NULL;
+ *outvallen = 0;
+
+ tv.tv_sec = _yplib_host_timeout;
+ tv.tv_usec = 0;
+
+ yprk.domain = indomain;
+ yprk.map = inmap;
+ yprk.key.keydat_val = (char *)inkey;
+ yprk.key.keydat_len = inkeylen;
+
+ memset(&yprv, 0, sizeof yprv);
+
+ r = clnt_call(client, YPPROC_MATCH,
+ (xdrproc_t)xdr_ypreq_key, &yprk,
+ (xdrproc_t)xdr_ypresp_val, &yprv, tv);
+ if (r != RPC_SUCCESS)
+ clnt_perror(client, "yp_match_host: clnt_call");
+ if ( !(r = ypprot_err(yprv.stat)) ) {
+ *outvallen = yprv.val.valdat_len;
+ *outval = malloc(*outvallen + 1);
+ memcpy(*outval, yprv.val.valdat_val, *outvallen);
+ (*outval)[*outvallen] = '\0';
+ }
+ xdr_free((xdrproc_t)xdr_ypresp_val, (char *)&yprv);
+
+ return (r);
+}
+
+int
+yp_first_host(CLIENT *client, char *indomain, char *inmap, char **outkey,
+ int *outkeylen, char **outval, int *outvallen)
+{
+ struct ypresp_key_val yprkv;
+ struct ypreq_nokey yprnk;
+ struct timeval tv;
+ int r;
+
+ *outkey = *outval = NULL;
+ *outkeylen = *outvallen = 0;
+
+ tv.tv_sec = _yplib_host_timeout;
+ tv.tv_usec = 0;
+
+ yprnk.domain = indomain;
+ yprnk.map = inmap;
+ memset(&yprkv, 0, sizeof yprkv);
+
+ r = clnt_call(client, YPPROC_FIRST,
+ (xdrproc_t)xdr_ypreq_nokey, &yprnk,
+ (xdrproc_t)xdr_ypresp_key_val, &yprkv, tv);
+ if (r != RPC_SUCCESS)
+ clnt_perror(client, "yp_first_host: clnt_call");
+ if ( !(r = ypprot_err(yprkv.stat)) ) {
+ *outkeylen = yprkv.key.keydat_len;
+ *outkey = malloc(*outkeylen+1);
+ memcpy(*outkey, yprkv.key.keydat_val, *outkeylen);
+ (*outkey)[*outkeylen] = '\0';
+ *outvallen = yprkv.val.valdat_len;
+ *outval = malloc(*outvallen+1);
+ memcpy(*outval, yprkv.val.valdat_val, *outvallen);
+ (*outval)[*outvallen] = '\0';
+ }
+ xdr_free((xdrproc_t)xdr_ypresp_key_val, (char *)&yprkv);
+
+ return (r);
+}
+
+int
+yp_next_host(CLIENT *client, char *indomain, char *inmap, char *inkey,
+ int inkeylen, char **outkey, int *outkeylen, char **outval, int *outvallen)
+{
+ struct ypresp_key_val yprkv;
+ struct ypreq_key yprk;
+ struct timeval tv;
+ int r;
+
+ *outkey = *outval = NULL;
+ *outkeylen = *outvallen = 0;
+
+ tv.tv_sec = _yplib_host_timeout;
+ tv.tv_usec = 0;
+
+ yprk.domain = indomain;
+ yprk.map = inmap;
+ yprk.key.keydat_val = inkey;
+ yprk.key.keydat_len = inkeylen;
+ memset(&yprkv, 0, sizeof yprkv);
+
+ r = clnt_call(client, YPPROC_NEXT,
+ (xdrproc_t)xdr_ypreq_key, &yprk,
+ (xdrproc_t)xdr_ypresp_key_val, &yprkv, tv);
+ if (r != RPC_SUCCESS)
+ clnt_perror(client, "yp_next_host: clnt_call");
+ if ( !(r = ypprot_err(yprkv.stat)) ) {
+ *outkeylen = yprkv.key.keydat_len;
+ *outkey = malloc(*outkeylen+1);
+ memcpy(*outkey, yprkv.key.keydat_val, *outkeylen);
+ (*outkey)[*outkeylen] = '\0';
+ *outvallen = yprkv.val.valdat_len;
+ *outval = malloc(*outvallen+1);
+ memcpy(*outval, yprkv.val.valdat_val, *outvallen);
+ (*outval)[*outvallen] = '\0';
+ }
+ xdr_free((xdrproc_t)xdr_ypresp_key_val, (char *)&yprkv);
+
+ return (r);
+}
+
+int
+yp_all_host(CLIENT *client, char *indomain, char *inmap,
+ struct ypall_callback *incallback)
+{
+ struct ypreq_nokey yprnk;
+ struct timeval tv;
+ u_long status;
+
+ tv.tv_sec = _yplib_host_timeout;
+ tv.tv_usec = 0;
+
+ yprnk.domain = indomain;
+ yprnk.map = inmap;
+ ypresp_allfn = incallback->foreach;
+ ypresp_data = (void *)incallback->data;
+
+ (void) clnt_call(client, YPPROC_ALL,
+ (xdrproc_t)xdr_ypreq_nokey, &yprnk,
+ (xdrproc_t)xdr_ypresp_all_seq, &status, tv);
+ if (status != YP_FALSE)
+ return ypprot_err(status);
+
+ return (0);
+}
+
+int
+yp_order_host(CLIENT *client, char *indomain, char *inmap, u_int32_t *outorder)
+{
+ struct ypresp_order ypro;
+ struct ypreq_nokey yprnk;
+ struct timeval tv;
+ int r;
+
+ tv.tv_sec = _yplib_host_timeout;
+ tv.tv_usec = 0;
+
+ yprnk.domain = indomain;
+ yprnk.map = inmap;
+
+ memset(&ypro, 0, sizeof ypro);
+
+ r = clnt_call(client, YPPROC_ORDER,
+ (xdrproc_t)xdr_ypreq_nokey, &yprnk,
+ (xdrproc_t)xdr_ypresp_order, &ypro, tv);
+ if (r != RPC_SUCCESS)
+ clnt_perror(client, "yp_order_host: clnt_call");
+ *outorder = ypro.ordernum;
+ xdr_free((xdrproc_t)xdr_ypresp_order, (char *)&ypro);
+
+ return ypprot_err(ypro.stat);
+}
+
+int
+yp_master_host(CLIENT *client, char *indomain, char *inmap, char **outname)
+{
+ struct ypresp_master yprm;
+ struct ypreq_nokey yprnk;
+ struct timeval tv;
+ int r;
+
+ tv.tv_sec = _yplib_host_timeout;
+ tv.tv_usec = 0;
+ yprnk.domain = indomain;
+ yprnk.map = inmap;
+
+ memset(&yprm, 0, sizeof yprm);
+
+ r = clnt_call(client, YPPROC_MASTER,
+ (xdrproc_t)xdr_ypreq_nokey, &yprnk,
+ (xdrproc_t)xdr_ypresp_master, &yprm, tv);
+ if (r != RPC_SUCCESS)
+ clnt_perror(client, "yp_master: clnt_call");
+ if (!(r = ypprot_err(yprm.stat)))
+ *outname = strdup(yprm.peer);
+ xdr_free((xdrproc_t)xdr_ypresp_master, (char *)&yprm);
+
+ return (r);
+}
+
+int
+yp_maplist_host(CLIENT *client, char *indomain, struct ypmaplist **outmaplist)
+{
+ struct ypresp_maplist ypml;
+ struct timeval tv;
+ int r;
+
+ tv.tv_sec = _yplib_host_timeout;
+ tv.tv_usec = 0;
+
+ memset(&ypml, 0, sizeof ypml);
+
+ r = clnt_call(client, YPPROC_MAPLIST,
+ (xdrproc_t)xdr_domainname, &indomain,
+ (xdrproc_t)xdr_ypresp_maplist, &ypml, tv);
+ if (r != RPC_SUCCESS)
+ clnt_perror(client, "yp_maplist: clnt_call");
+ *outmaplist = ypml.maps;
+ /* NO: xdr_free(xdr_ypresp_maplist, &ypml);*/
+
+ return ypprot_err(ypml.stat);
+}
diff --git a/usr.sbin/ypserv/common/yplib_host.h b/usr.sbin/ypserv/common/yplib_host.h
new file mode 100644
index 0000000..08b9e30
--- /dev/null
+++ b/usr.sbin/ypserv/common/yplib_host.h
@@ -0,0 +1,53 @@
+/* $OpenBSD: yplib_host.h,v 1.8 2003/06/02 04:12:38 deraadt Exp $ */
+
+/*
+ * Copyright (c) 1992, 1993 Theo de Raadt <deraadt@theos.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 ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED 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 _YPLIB_HOST_H_
+#define _YPLIB_HOST_H_
+
+int yp_match_host(CLIENT *client, char *indomain, char *inmap,
+ const char *inkey, int inkeylen, char **outval, int *outvallen);
+int yp_first_host(CLIENT *client, char *indomain, char *inmap,
+ char **outkey, int *outkeylen, char **outval, int *outvallen);
+int yp_next_host(CLIENT *client, char *indomain, char *inmap, char *inkey,
+ int inkeylen, char **outkey, int *outkeylen, char **outval,
+ int *outvallen);
+int yp_master_host(CLIENT *client, char *indomain, char *inmap,
+ char **outname);
+int yp_order_host(CLIENT *client, char *indomain, char *inmap,
+ u_int32_t *outorder);
+int yp_all_host(CLIENT *client, char *indomain, char *inmap,
+ struct ypall_callback *incallback);
+int yp_maplist_host(CLIENT *client, char *indomain,
+ struct ypmaplist **outmaplist);
+CLIENT *yp_bind_local(u_long program, u_long version);
+CLIENT *yp_bind_host(char *server, u_long program, u_long version,
+ u_short port, int usetcp);
+
+#endif /* _YPLIB_HOST_H_ */
diff --git a/usr.sbin/ypserv/yp_access.c b/usr.sbin/ypserv/yp_access.c
new file mode 100644
index 0000000..dddde74
--- /dev/null
+++ b/usr.sbin/ypserv/yp_access.c
@@ -0,0 +1,334 @@
+/*
+ * Copyright (c) 1995
+ * 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <stdlib.h>
+#include <rpc/rpc.h>
+#include <rpcsvc/yp.h>
+#include <rpcsvc/yppasswd.h>
+#include <rpcsvc/ypxfrd.h>
+#include <sys/types.h>
+#include <limits.h>
+#include <db.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/stat.h>
+#include <sys/fcntl.h>
+#include <paths.h>
+#include <errno.h>
+#include <sys/param.h>
+#include "yp_extern.h"
+#ifdef TCP_WRAPPER
+#include "tcpd.h"
+#endif
+
+extern int debug;
+
+static const char *yp_procs[] = {
+ /* NIS v1 */
+ "ypoldproc_null",
+ "ypoldproc_domain",
+ "ypoldproc_domain_nonack",
+ "ypoldproc_match",
+ "ypoldproc_first",
+ "ypoldproc_next",
+ "ypoldproc_poll",
+ "ypoldproc_push",
+ "ypoldproc_get",
+ "badproc1", /* placeholder */
+ "badproc2", /* placeholder */
+ "badproc3", /* placeholder */
+
+ /* NIS v2 */
+ "ypproc_null",
+ "ypproc_domain",
+ "ypproc_domain_nonack",
+ "ypproc_match",
+ "ypproc_first",
+ "ypproc_next",
+ "ypproc_xfr",
+ "ypproc_clear",
+ "ypproc_all",
+ "ypproc_master",
+ "ypproc_order",
+ "ypproc_maplist"
+};
+
+struct securenet {
+ struct in_addr net;
+ struct in_addr mask;
+ struct securenet *next;
+};
+
+static struct securenet *securenets;
+
+#define LINEBUFSZ 1024
+
+#ifdef TCP_WRAPPER
+int hosts_ctl(char *, char *, char *, char *);
+#endif
+
+/*
+ * Read /var/yp/securenets file and initialize the securenets
+ * list. If the file doesn't exist, we set up a dummy entry that
+ * allows all hosts to connect.
+ */
+void
+load_securenets(void)
+{
+ FILE *fp;
+ char path[MAXPATHLEN + 2];
+ char linebuf[1024 + 2];
+ struct securenet *tmp;
+
+ /*
+ * If securenets is not NULL, we are being called to reload
+ * the list; free the existing list before re-reading the
+ * securenets file.
+ */
+ while (securenets) {
+ tmp = securenets->next;
+ free(securenets);
+ securenets = tmp;
+ }
+
+ snprintf(path, MAXPATHLEN, "%s/securenets", yp_dir);
+
+ if ((fp = fopen(path, "r")) == NULL) {
+ if (errno == ENOENT) {
+ securenets = malloc(sizeof(struct securenet));
+ securenets->net.s_addr = INADDR_ANY;
+ securenets->mask.s_addr = INADDR_ANY;
+ securenets->next = NULL;
+ return;
+ } else {
+ yp_error("fopen(%s) failed: %s", path, strerror(errno));
+ exit(1);
+ }
+ }
+
+ securenets = NULL;
+
+ while (fgets(linebuf, LINEBUFSZ, fp)) {
+ char addr1[20], addr2[20];
+
+ if ((linebuf[0] == '#')
+ || (strspn(linebuf, " \t\r\n") == strlen(linebuf)))
+ continue;
+ if (sscanf(linebuf, "%s %s", addr1, addr2) < 2) {
+ yp_error("badly formatted securenets entry: %s",
+ linebuf);
+ continue;
+ }
+
+ tmp = malloc(sizeof(struct securenet));
+
+ if (!inet_aton((char *)&addr1, (struct in_addr *)&tmp->net)) {
+ yp_error("badly formatted securenets entry: %s", addr1);
+ free(tmp);
+ continue;
+ }
+
+ if (!inet_aton((char *)&addr2, (struct in_addr *)&tmp->mask)) {
+ yp_error("badly formatted securenets entry: %s", addr2);
+ free(tmp);
+ continue;
+ }
+
+ tmp->next = securenets;
+ securenets = tmp;
+ }
+
+ fclose(fp);
+
+}
+
+/*
+ * Access control functions.
+ *
+ * yp_access() checks the mapname and client host address and watches for
+ * the following things:
+ *
+ * - If the client is referencing one of the master.passwd.* or shadow.* maps,
+ * it must be using a privileged port to make its RPC to us. If it is, then
+ * we can assume that the caller is root and allow the RPC to succeed. If it
+ * isn't access is denied.
+ *
+ * - The client's IP address is checked against the securenets rules.
+ * There are two kinds of securenets support: the built-in support,
+ * which is very simple and depends on the presence of a
+ * /var/yp/securenets file, and tcp-wrapper support, which requires
+ * Wietse Venema's libwrap.a and tcpd.h. (Since the tcp-wrapper
+ * package does not ship with FreeBSD, we use the built-in support
+ * by default. Users can recompile the server with the tcp-wrapper library
+ * if they already have it installed and want to use hosts.allow and
+ * hosts.deny to control access instead of having a separate securenets
+ * file.)
+ *
+ * If no /var/yp/securenets file is present, the host access checks
+ * are bypassed and all hosts are allowed to connect.
+ *
+ * The yp_validdomain() function checks the domain specified by the caller
+ * to make sure it's actually served by this server. This is more a sanity
+ * check than an a security check, but this seems to be the best place for
+ * it.
+ */
+
+#ifdef DB_CACHE
+int
+yp_access(const char *map, const char *domain, const struct svc_req *rqstp)
+#else
+int
+yp_access(const char *map, const struct svc_req *rqstp)
+#endif
+{
+ struct sockaddr_in *rqhost;
+ int status_securenets = 0;
+#ifdef TCP_WRAPPER
+ int status_tcpwrap;
+#endif
+ static unsigned long oldaddr = 0;
+ struct securenet *tmp;
+ const char *yp_procedure = NULL;
+ char procbuf[50];
+
+ if (rqstp->rq_prog != YPPASSWDPROG && rqstp->rq_prog != YPPROG) {
+ snprintf(procbuf, sizeof(procbuf), "#%lu/#%lu",
+ (unsigned long)rqstp->rq_prog,
+ (unsigned long)rqstp->rq_proc);
+ yp_procedure = (char *)&procbuf;
+ } else {
+ yp_procedure = rqstp->rq_prog == YPPASSWDPROG ?
+ "yppasswdprog_update" :
+ yp_procs[rqstp->rq_proc + (12 * (rqstp->rq_vers - 1))];
+ }
+
+ rqhost = svc_getcaller(rqstp->rq_xprt);
+
+ if (debug) {
+ yp_error("procedure %s called from %s:%d", yp_procedure,
+ inet_ntoa(rqhost->sin_addr),
+ ntohs(rqhost->sin_port));
+ if (map != NULL)
+ yp_error("client is referencing map \"%s\".", map);
+ }
+
+ /* Check the map name if one was supplied. */
+ if (map != NULL) {
+ if (strchr(map, '/')) {
+ yp_error("embedded slash in map name \"%s\" -- \
+possible spoof attempt from %s:%d",
+ map, inet_ntoa(rqhost->sin_addr),
+ ntohs(rqhost->sin_port));
+ return(1);
+ }
+#ifdef DB_CACHE
+ if ((yp_testflag((char *)map, (char *)domain, YP_SECURE) ||
+#else
+ if ((strstr(map, "master.passwd.") || strstr(map, "shadow.") ||
+#endif
+ (rqstp->rq_prog == YPPROG &&
+ rqstp->rq_proc == YPPROC_XFR) ||
+ (rqstp->rq_prog == YPXFRD_FREEBSD_PROG &&
+ rqstp->rq_proc == YPXFRD_GETMAP)) &&
+ ntohs(rqhost->sin_port) >= IPPORT_RESERVED) {
+ yp_error("access to %s denied -- client %s:%d \
+not privileged", map, inet_ntoa(rqhost->sin_addr), ntohs(rqhost->sin_port));
+ return(1);
+ }
+ }
+
+#ifdef TCP_WRAPPER
+ status_tcpwrap = hosts_ctl("ypserv", STRING_UNKNOWN,
+ inet_ntoa(rqhost->sin_addr), "");
+#endif
+ tmp = securenets;
+ while (tmp) {
+ if (((rqhost->sin_addr.s_addr & ~tmp->mask.s_addr)
+ | tmp->net.s_addr) == rqhost->sin_addr.s_addr) {
+ status_securenets = 1;
+ break;
+ }
+ tmp = tmp->next;
+ }
+
+#ifdef TCP_WRAPPER
+ if (status_securenets == 0 || status_tcpwrap == 0) {
+#else
+ if (status_securenets == 0) {
+#endif
+ /*
+ * One of the following two events occurred:
+ *
+ * (1) The /var/yp/securenets exists and the remote host does not
+ * match any of the networks specified in it.
+ * (2) The hosts.allow file has denied access and TCP_WRAPPER is
+ * defined.
+ *
+ * In either case deny access.
+ */
+ if (rqhost->sin_addr.s_addr != oldaddr) {
+ yp_error("connect from %s:%d to procedure %s refused",
+ inet_ntoa(rqhost->sin_addr),
+ ntohs(rqhost->sin_port),
+ yp_procedure);
+ oldaddr = rqhost->sin_addr.s_addr;
+ }
+ return(1);
+ }
+ return(0);
+
+}
+
+int
+yp_validdomain(const char *domain)
+{
+ struct stat statbuf;
+ char dompath[MAXPATHLEN + 2];
+
+ if (domain == NULL || strstr(domain, "binding") ||
+ !strcmp(domain, ".") || !strcmp(domain, "..") ||
+ strchr(domain, '/') || strlen(domain) > YPMAXDOMAIN)
+ return(1);
+
+ snprintf(dompath, sizeof(dompath), "%s/%s", yp_dir, domain);
+
+ if (stat(dompath, &statbuf) < 0 || !S_ISDIR(statbuf.st_mode))
+ return(1);
+
+
+ return(0);
+}
diff --git a/usr.sbin/ypserv/yp_dblookup.c b/usr.sbin/ypserv/yp_dblookup.c
new file mode 100644
index 0000000..ecd9052
--- /dev/null
+++ b/usr.sbin/ypserv/yp_dblookup.c
@@ -0,0 +1,733 @@
+/*
+ * Copyright (c) 1995
+ * 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <db.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <rpcsvc/yp.h>
+#include "yp_extern.h"
+
+int ypdb_debug = 0;
+enum ypstat yp_errno = YP_TRUE;
+
+#define PERM_SECURE (S_IRUSR|S_IWUSR)
+HASHINFO openinfo = {
+ 4096, /* bsize */
+ 32, /* ffactor */
+ 256, /* nelem */
+ 2048 * 512, /* cachesize */
+ NULL, /* hash */
+ 0, /* lorder */
+};
+
+#ifdef DB_CACHE
+#include <sys/queue.h>
+
+#ifndef MAXDBS
+#define MAXDBS 20
+#endif
+
+static int numdbs = 0;
+
+struct dbent {
+ DB *dbp;
+ char *name;
+ char *key;
+ int size;
+ int flags;
+};
+
+static TAILQ_HEAD(circlehead, circleq_entry) qhead;
+
+struct circleq_entry {
+ struct dbent *dbptr;
+ TAILQ_ENTRY(circleq_entry) links;
+};
+
+/*
+ * Initialize the circular queue.
+ */
+void
+yp_init_dbs(void)
+{
+ TAILQ_INIT(&qhead);
+ return;
+}
+
+/*
+ * Dynamically allocate an entry for the circular queue.
+ * Return a NULL pointer on failure.
+ */
+static struct circleq_entry *
+yp_malloc_qent(void)
+{
+ register struct circleq_entry *q;
+
+ q = malloc(sizeof(struct circleq_entry));
+ if (q == NULL) {
+ yp_error("failed to malloc() circleq entry");
+ return(NULL);
+ }
+ bzero((char *)q, sizeof(struct circleq_entry));
+ q->dbptr = malloc(sizeof(struct dbent));
+ if (q->dbptr == NULL) {
+ yp_error("failed to malloc() circleq entry");
+ free(q);
+ return(NULL);
+ }
+ bzero((char *)q->dbptr, sizeof(struct dbent));
+
+ return(q);
+}
+
+/*
+ * Free a previously allocated circular queue
+ * entry.
+ */
+static void
+yp_free_qent(struct circleq_entry *q)
+{
+ /*
+ * First, close the database. In theory, this is also
+ * supposed to free the resources allocated by the DB
+ * package, including the memory pointed to by q->dbptr->key.
+ * This means we don't have to free q->dbptr->key here.
+ */
+ if (q->dbptr->dbp) {
+ (void)(q->dbptr->dbp->close)(q->dbptr->dbp);
+ q->dbptr->dbp = NULL;
+ }
+ /*
+ * Then free the database name, which was strdup()'ed.
+ */
+ free(q->dbptr->name);
+
+ /*
+ * Free the rest of the dbent struct.
+ */
+ free(q->dbptr);
+ q->dbptr = NULL;
+
+ /*
+ * Free the circleq struct.
+ */
+ free(q);
+ q = NULL;
+
+ return;
+}
+
+/*
+ * Zorch a single entry in the dbent queue and release
+ * all its resources. (This always removes the last entry
+ * in the queue.)
+ */
+static void
+yp_flush(void)
+{
+ register struct circleq_entry *qptr;
+
+ qptr = TAILQ_LAST(&qhead, circlehead);
+ TAILQ_REMOVE(&qhead, qptr, links);
+ yp_free_qent(qptr);
+ numdbs--;
+
+ return;
+}
+
+/*
+ * Close all databases, erase all database names and empty the queue.
+ */
+void
+yp_flush_all(void)
+{
+ register struct circleq_entry *qptr;
+
+ while (!TAILQ_EMPTY(&qhead)) {
+ qptr = TAILQ_FIRST(&qhead); /* save this */
+ TAILQ_REMOVE(&qhead, qptr, links);
+ yp_free_qent(qptr);
+ }
+ numdbs = 0;
+
+ return;
+}
+
+static char *inter_string = "YP_INTERDOMAIN";
+static char *secure_string = "YP_SECURE";
+static int inter_sz = sizeof("YP_INTERDOMAIN") - 1;
+static int secure_sz = sizeof("YP_SECURE") - 1;
+
+static int
+yp_setflags(DB *dbp)
+{
+ DBT key = { NULL, 0 }, data = { NULL, 0 };
+ int flags = 0;
+
+ key.data = inter_string;
+ key.size = inter_sz;
+
+ if (!(dbp->get)(dbp, &key, &data, 0))
+ flags |= YP_INTERDOMAIN;
+
+ key.data = secure_string;
+ key.size = secure_sz;
+
+ if (!(dbp->get)(dbp, &key, &data, 0))
+ flags |= YP_SECURE;
+
+ return(flags);
+}
+
+int
+yp_testflag(char *map, char *domain, int flag)
+{
+ char buf[MAXPATHLEN + 2];
+ register struct circleq_entry *qptr;
+
+ if (map == NULL || domain == NULL)
+ return(0);
+
+ strcpy(buf, domain);
+ strcat(buf, "/");
+ strcat(buf, map);
+
+ TAILQ_FOREACH(qptr, &qhead, links) {
+ if (!strcmp(qptr->dbptr->name, buf)) {
+ if (qptr->dbptr->flags & flag)
+ return(1);
+ else
+ return(0);
+ }
+ }
+
+ if (yp_open_db_cache(domain, map, NULL, 0) == NULL)
+ return(0);
+
+ if (TAILQ_FIRST(&qhead)->dbptr->flags & flag)
+ return(1);
+
+ return(0);
+}
+
+/*
+ * Add a DB handle and database name to the cache. We only maintain
+ * fixed number of entries in the cache, so if we're asked to store
+ * a new entry when all our slots are already filled, we have to kick
+ * out the entry in the last slot to make room.
+ */
+static int
+yp_cache_db(DB *dbp, char *name, int size)
+{
+ register struct circleq_entry *qptr;
+
+ if (numdbs == MAXDBS) {
+ if (ypdb_debug)
+ yp_error("queue overflow -- releasing last slot");
+ yp_flush();
+ }
+
+ /*
+ * Allocate a new queue entry.
+ */
+
+ if ((qptr = yp_malloc_qent()) == NULL) {
+ yp_error("failed to allocate a new cache entry");
+ return(1);
+ }
+
+ qptr->dbptr->dbp = dbp;
+ qptr->dbptr->name = strdup(name);
+ qptr->dbptr->size = size;
+ qptr->dbptr->key = NULL;
+
+ qptr->dbptr->flags = yp_setflags(dbp);
+
+ TAILQ_INSERT_HEAD(&qhead, qptr, links);
+ numdbs++;
+
+ return(0);
+}
+
+/*
+ * Search the list for a database matching 'name.' If we find it,
+ * move it to the head of the list and return its DB handle. If
+ * not, just fail: yp_open_db_cache() will subsequently try to open
+ * the database itself and call yp_cache_db() to add it to the
+ * list.
+ *
+ * The search works like this:
+ *
+ * - The caller specifies the name of a database to locate. We try to
+ * find an entry in our queue with a matching name.
+ *
+ * - If the caller doesn't specify a key or size, we assume that the
+ * first entry that we encounter with a matching name is returned.
+ * This will result in matches regardless of the key/size values
+ * stored in the queue entry.
+ *
+ * - If the caller also specifies a key and length, we check to see
+ * if the key and length saved in the queue entry also matches.
+ * This lets us return a DB handle that's already positioned at the
+ * correct location within a database.
+ *
+ * - Once we have a match, it gets migrated to the top of the queue
+ * so that it will be easier to find if another request for
+ * the same database comes in later.
+ */
+static DB *
+yp_find_db(const char *name, const char *key, int size)
+{
+ register struct circleq_entry *qptr;
+
+ TAILQ_FOREACH(qptr, &qhead, links) {
+ if (!strcmp(qptr->dbptr->name, name)) {
+ if (size) {
+ if (size != qptr->dbptr->size ||
+ strncmp(qptr->dbptr->key, key, size))
+ continue;
+ } else {
+ if (qptr->dbptr->size)
+ continue;
+ }
+ if (qptr != TAILQ_FIRST(&qhead)) {
+ TAILQ_REMOVE(&qhead, qptr, links);
+ TAILQ_INSERT_HEAD(&qhead, qptr, links);
+ }
+ return(qptr->dbptr->dbp);
+ }
+ }
+
+ return(NULL);
+}
+
+/*
+ * Open a DB database and cache the handle for later use. We first
+ * check the cache to see if the required database is already open.
+ * If so, we fetch the handle from the cache. If not, we try to open
+ * the database and save the handle in the cache for later use.
+ */
+DB *
+yp_open_db_cache(const char *domain, const char *map, const char *key,
+ const int size)
+{
+ DB *dbp = NULL;
+ char buf[MAXPATHLEN + 2];
+/*
+ snprintf(buf, sizeof(buf), "%s/%s", domain, map);
+*/
+ yp_errno = YP_TRUE;
+
+ strcpy(buf, domain);
+ strcat(buf, "/");
+ strcat(buf, map);
+
+ if ((dbp = yp_find_db(buf, key, size)) != NULL) {
+ return(dbp);
+ } else {
+ if ((dbp = yp_open_db(domain, map)) != NULL) {
+ if (yp_cache_db(dbp, buf, size)) {
+ (void)(dbp->close)(dbp);
+ yp_errno = YP_YPERR;
+ return(NULL);
+ }
+ }
+ }
+
+ return (dbp);
+}
+#endif
+
+/*
+ * Open a DB database.
+ */
+DB *
+yp_open_db(const char *domain, const char *map)
+{
+ DB *dbp = NULL;
+ char buf[MAXPATHLEN + 2];
+
+ yp_errno = YP_TRUE;
+
+ if (map[0] == '.' || strchr(map, '/')) {
+ yp_errno = YP_BADARGS;
+ return (NULL);
+ }
+
+#ifdef DB_CACHE
+ if (yp_validdomain(domain)) {
+ yp_errno = YP_NODOM;
+ return(NULL);
+ }
+#endif
+ snprintf(buf, sizeof(buf), "%s/%s/%s", yp_dir, domain, map);
+
+#ifdef DB_CACHE
+again:
+#endif
+ dbp = dbopen(buf, O_RDONLY, PERM_SECURE, DB_HASH, NULL);
+
+ if (dbp == NULL) {
+ switch (errno) {
+#ifdef DB_CACHE
+ case ENFILE:
+ /*
+ * We ran out of file descriptors. Nuke an
+ * open one and try again.
+ */
+ yp_error("ran out of file descriptors");
+ yp_flush();
+ goto again;
+ break;
+#endif
+ case ENOENT:
+ yp_errno = YP_NOMAP;
+ break;
+ case EFTYPE:
+ yp_errno = YP_BADDB;
+ break;
+ default:
+ yp_errno = YP_YPERR;
+ break;
+ }
+ }
+
+ return (dbp);
+}
+
+/*
+ * Database access routines.
+ *
+ * - yp_get_record(): retrieve an arbitrary key/data pair given one key
+ * to match against.
+ *
+ * - yp_first_record(): retrieve first key/data base in a database.
+ *
+ * - yp_next_record(): retrieve key/data pair that sequentially follows
+ * the supplied key value in the database.
+ */
+
+#ifdef DB_CACHE
+int
+yp_get_record(DB *dbp, const DBT *key, DBT *data, int allow)
+#else
+int
+yp_get_record(const char *domain, const char *map,
+ const DBT *key, DBT *data, int allow)
+#endif
+{
+#ifndef DB_CACHE
+ DB *dbp;
+#endif
+ int rval = 0;
+#ifndef DB_CACHE
+ static unsigned char buf[YPMAXRECORD];
+#endif
+
+ if (ypdb_debug)
+ yp_error("looking up key [%.*s]",
+ (int)key->size, (char *)key->data);
+
+ /*
+ * Avoid passing back magic "YP_*" entries unless
+ * the caller specifically requested them by setting
+ * the 'allow' flag.
+ */
+ if (!allow && !strncmp(key->data, "YP_", 3))
+ return(YP_NOKEY);
+
+#ifndef DB_CACHE
+ if ((dbp = yp_open_db(domain, map)) == NULL) {
+ return(yp_errno);
+ }
+#endif
+
+ if ((rval = (dbp->get)(dbp, key, data, 0)) != 0) {
+#ifdef DB_CACHE
+ TAILQ_FIRST(&qhead)->dbptr->size = 0;
+#else
+ (void)(dbp->close)(dbp);
+#endif
+ if (rval == 1)
+ return(YP_NOKEY);
+ else
+ return(YP_BADDB);
+ }
+
+ if (ypdb_debug)
+ yp_error("result of lookup: key: [%.*s] data: [%.*s]",
+ (int)key->size, (char *)key->data,
+ (int)data->size, (char *)data->data);
+
+#ifdef DB_CACHE
+ if (TAILQ_FIRST(&qhead)->dbptr->size) {
+ TAILQ_FIRST(&qhead)->dbptr->key = "";
+ TAILQ_FIRST(&qhead)->dbptr->size = 0;
+ }
+#else
+ bcopy(data->data, &buf, data->size);
+ data->data = &buf;
+ (void)(dbp->close)(dbp);
+#endif
+
+ return(YP_TRUE);
+}
+
+int
+yp_first_record(const DB *dbp, DBT *key, DBT *data, int allow)
+{
+ int rval;
+#ifndef DB_CACHE
+ static unsigned char buf[YPMAXRECORD];
+#endif
+
+ if (ypdb_debug)
+ yp_error("retrieving first key in map");
+
+ if ((rval = (dbp->seq)(dbp,key,data,R_FIRST)) != 0) {
+#ifdef DB_CACHE
+ TAILQ_FIRST(&qhead)->dbptr->size = 0;
+#endif
+ if (rval == 1)
+ return(YP_NOKEY);
+ else
+ return(YP_BADDB);
+ }
+
+ /* Avoid passing back magic "YP_*" records. */
+ while (!strncmp(key->data, "YP_", 3) && !allow) {
+ if ((rval = (dbp->seq)(dbp,key,data,R_NEXT)) != 0) {
+#ifdef DB_CACHE
+ TAILQ_FIRST(&qhead)->dbptr->size = 0;
+#endif
+ if (rval == 1)
+ return(YP_NOKEY);
+ else
+ return(YP_BADDB);
+ }
+ }
+
+ if (ypdb_debug)
+ yp_error("result of lookup: key: [%.*s] data: [%.*s]",
+ (int)key->size, (char *)key->data,
+ (int)data->size, (char *)data->data);
+
+#ifdef DB_CACHE
+ if (TAILQ_FIRST(&qhead)->dbptr->size) {
+ TAILQ_FIRST(&qhead)->dbptr->key = key->data;
+ TAILQ_FIRST(&qhead)->dbptr->size = key->size;
+ }
+#else
+ bcopy(data->data, &buf, data->size);
+ data->data = &buf;
+#endif
+
+ return(YP_TRUE);
+}
+
+int
+yp_next_record(const DB *dbp, DBT *key, DBT *data, int all, int allow)
+{
+ static DBT lkey = { NULL, 0 };
+ static DBT ldata = { NULL, 0 };
+ int rval;
+#ifndef DB_CACHE
+ static unsigned char keybuf[YPMAXRECORD];
+ static unsigned char datbuf[YPMAXRECORD];
+#endif
+
+ if (key == NULL || !key->size || key->data == NULL) {
+ rval = yp_first_record(dbp,key,data,allow);
+ if (rval == YP_NOKEY)
+ return(YP_NOMORE);
+ else {
+#ifdef DB_CACHE
+ TAILQ_FIRST(&qhead)->dbptr->key = key->data;
+ TAILQ_FIRST(&qhead)->dbptr->size = key->size;
+#endif
+ return(rval);
+ }
+ }
+
+ if (ypdb_debug)
+ yp_error("retrieving next key, previous was: [%.*s]",
+ (int)key->size, (char *)key->data);
+
+ if (!all) {
+#ifdef DB_CACHE
+ if (TAILQ_FIRST(&qhead)->dbptr->key == NULL) {
+#endif
+ (dbp->seq)(dbp,&lkey,&ldata,R_FIRST);
+ while (key->size != lkey.size ||
+ strncmp(key->data, lkey.data,
+ (int)key->size))
+ if ((dbp->seq)(dbp,&lkey,&ldata,R_NEXT)) {
+#ifdef DB_CACHE
+ TAILQ_FIRST(&qhead)->dbptr->size = 0;
+#endif
+ return(YP_NOKEY);
+ }
+
+#ifdef DB_CACHE
+ }
+#endif
+ }
+
+ if ((dbp->seq)(dbp,key,data,R_NEXT)) {
+#ifdef DB_CACHE
+ TAILQ_FIRST(&qhead)->dbptr->size = 0;
+#endif
+ return(YP_NOMORE);
+ }
+
+ /* Avoid passing back magic "YP_*" records. */
+ while (!strncmp(key->data, "YP_", 3) && !allow)
+ if ((dbp->seq)(dbp,key,data,R_NEXT)) {
+#ifdef DB_CACHE
+ TAILQ_FIRST(&qhead)->dbptr->size = 0;
+#endif
+ return(YP_NOMORE);
+ }
+
+ if (ypdb_debug)
+ yp_error("result of lookup: key: [%.*s] data: [%.*s]",
+ (int)key->size, (char *)key->data,
+ (int)data->size, (char *)data->data);
+
+#ifdef DB_CACHE
+ if (TAILQ_FIRST(&qhead)->dbptr->size) {
+ TAILQ_FIRST(&qhead)->dbptr->key = key->data;
+ TAILQ_FIRST(&qhead)->dbptr->size = key->size;
+ }
+#else
+ bcopy(key->data, &keybuf, key->size);
+ lkey.data = &keybuf;
+ lkey.size = key->size;
+ bcopy(data->data, &datbuf, data->size);
+ data->data = &datbuf;
+#endif
+
+ return(YP_TRUE);
+}
+
+#ifdef DB_CACHE
+/*
+ * Database glue functions.
+ */
+
+static DB *yp_currmap_db = NULL;
+static int yp_allow_db = 0;
+
+ypstat
+yp_select_map(char *map, char *domain, keydat *key, int allow)
+{
+ if (key == NULL)
+ yp_currmap_db = yp_open_db_cache(domain, map, NULL, 0);
+ else
+ yp_currmap_db = yp_open_db_cache(domain, map,
+ key->keydat_val,
+ key->keydat_len);
+
+ yp_allow_db = allow;
+ return(yp_errno);
+}
+
+ypstat
+yp_getbykey(keydat *key, valdat *val)
+{
+ DBT db_key = { NULL, 0 }, db_val = { NULL, 0 };
+ ypstat rval;
+
+ db_key.data = key->keydat_val;
+ db_key.size = key->keydat_len;
+
+ rval = yp_get_record(yp_currmap_db,
+ &db_key, &db_val, yp_allow_db);
+
+ if (rval == YP_TRUE) {
+ val->valdat_val = db_val.data;
+ val->valdat_len = db_val.size;
+ }
+
+ return(rval);
+}
+
+ypstat
+yp_firstbykey(keydat *key, valdat *val)
+{
+ DBT db_key = { NULL, 0 }, db_val = { NULL, 0 };
+ ypstat rval;
+
+ rval = yp_first_record(yp_currmap_db, &db_key, &db_val, yp_allow_db);
+
+ if (rval == YP_TRUE) {
+ key->keydat_val = db_key.data;
+ key->keydat_len = db_key.size;
+ val->valdat_val = db_val.data;
+ val->valdat_len = db_val.size;
+ }
+
+ return(rval);
+}
+
+ypstat
+yp_nextbykey(keydat *key, valdat *val)
+{
+ DBT db_key = { NULL, 0 }, db_val = { NULL, 0 };
+ ypstat rval;
+
+ db_key.data = key->keydat_val;
+ db_key.size = key->keydat_len;
+
+ rval = yp_next_record(yp_currmap_db, &db_key, &db_val, 0, yp_allow_db);
+
+ if (rval == YP_TRUE) {
+ key->keydat_val = db_key.data;
+ key->keydat_len = db_key.size;
+ val->valdat_val = db_val.data;
+ val->valdat_len = db_val.size;
+ }
+
+ return(rval);
+}
+#endif
diff --git a/usr.sbin/ypserv/yp_dnslookup.c b/usr.sbin/ypserv/yp_dnslookup.c
new file mode 100644
index 0000000..1d83b64
--- /dev/null
+++ b/usr.sbin/ypserv/yp_dnslookup.c
@@ -0,0 +1,551 @@
+/*
+ * Copyright (c) 1995, 1996
+ * 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 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 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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$");
+
+/*
+ * Do standard and reverse DNS lookups using the resolver library.
+ * Take care of all the dirty work here so the main program only has to
+ * pass us a pointer to an array of characters.
+ *
+ * We have to use direct resolver calls here otherwise the YP server
+ * could end up looping by calling itself over and over again until
+ * it disappeared up its own belly button.
+ */
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/fcntl.h>
+#include <sys/queue.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <resolv.h>
+#include <unistd.h>
+
+#include <rpcsvc/yp.h>
+#include "yp_extern.h"
+
+static char *
+parse(struct hostent *hp)
+{
+ static char result[MAXHOSTNAMELEN * 2];
+ int i;
+ size_t len;
+ char addr[46];
+
+ if (hp == NULL)
+ return(NULL);
+
+ if (inet_ntop(hp->h_addrtype, hp->h_addr, addr, sizeof(addr)) == NULL)
+ return(NULL);
+
+ len = strlen(addr) + 1 + strlen(hp->h_name);
+ for (i = 0; hp->h_aliases[i]; i++)
+ len += strlen(hp->h_aliases[i]) + 1;
+ len++;
+
+ if (len > sizeof(result))
+ return(NULL);
+
+ bzero(result, sizeof(result));
+ snprintf(result, sizeof(result), "%s %s", addr, hp->h_name);
+ for (i = 0; hp->h_aliases[i]; i++) {
+ strcat(result, " ");
+ strcat(result, hp->h_aliases[i]);
+ }
+
+ return ((char *)&result);
+}
+
+#define MAXPACKET (64*1024)
+#define DEF_TTL 50
+
+#define BY_DNS_ID 1
+#define BY_RPC_XID 2
+
+extern struct hostent *__dns_getanswer(char *, int, char *, int);
+
+static TAILQ_HEAD(dns_qhead, circleq_dnsentry) qhead;
+
+struct circleq_dnsentry {
+ SVCXPRT *xprt;
+ unsigned long xid;
+ struct sockaddr_in client_addr;
+ unsigned long ypvers;
+ unsigned long id;
+ unsigned long ttl;
+ unsigned long type;
+ unsigned short prot_type;
+ char **domain;
+ char *name;
+ int addrtype;
+ int addrlen;
+ uint32_t addr[4]; /* IPv4 or IPv6 */
+ TAILQ_ENTRY(circleq_dnsentry) links;
+};
+
+static int pending = 0;
+
+int
+yp_init_resolver(void)
+{
+ TAILQ_INIT(&qhead);
+ if (!(_res.options & RES_INIT) && res_init() == -1) {
+ yp_error("res_init failed");
+ return(1);
+ }
+ if ((resfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
+ yp_error("couldn't create socket");
+ return(1);
+ }
+ if (fcntl(resfd, F_SETFL, O_NONBLOCK) == -1) {
+ yp_error("couldn't make resolver socket non-blocking");
+ return(1);
+ }
+ return(0);
+}
+
+static struct
+circleq_dnsentry *yp_malloc_dnsent(void)
+{
+ register struct circleq_dnsentry *q;
+
+ q = malloc(sizeof(struct circleq_dnsentry));
+
+ if (q == NULL) {
+ yp_error("failed to malloc() circleq dns entry");
+ return(NULL);
+ }
+
+ return(q);
+}
+
+/*
+ * Transmit a query.
+ */
+static unsigned long
+yp_send_dns_query(char *name, int type)
+{
+ char buf[MAXPACKET];
+ int n;
+ HEADER *hptr;
+ int ns;
+ int rval;
+ unsigned long id;
+
+ bzero(buf, sizeof(buf));
+
+ n = res_mkquery(QUERY,name,C_IN,type,NULL,0,NULL,buf,sizeof(buf));
+
+ if (n <= 0) {
+ yp_error("res_mkquery failed for %s type %d", name, type);
+ return(0);
+ }
+
+ hptr = (HEADER *)&buf;
+ id = ntohs(hptr->id);
+
+ for (ns = 0; ns < _res.nscount; ns++) {
+ rval = sendto(resfd, buf, n, 0,
+ (struct sockaddr *)&_res.nsaddr_list[ns],
+ sizeof(struct sockaddr));
+ if (rval == -1) {
+ yp_error("sendto failed");
+ return(0);
+ }
+ }
+
+ return(id);
+}
+
+static struct circleq_dnsentry *
+yp_find_dnsqent(unsigned long id, int type)
+{
+ register struct circleq_dnsentry *q;
+
+ TAILQ_FOREACH(q, &qhead, links) {
+ switch (type) {
+ case BY_RPC_XID:
+ if (id == q->xid)
+ return(q);
+ break;
+ case BY_DNS_ID:
+ default:
+ if (id == q->id)
+ return(q);
+ break;
+ }
+ }
+ return (NULL);
+}
+
+static void
+yp_send_dns_reply(struct circleq_dnsentry *q, char *buf)
+{
+ ypresponse result_v1;
+ ypresp_val result_v2;
+ unsigned long xid;
+ struct sockaddr_in client_addr;
+ xdrproc_t xdrfunc;
+ char *result;
+
+ /*
+ * Set up correct reply struct and
+ * XDR filter depending on ypvers.
+ */
+ switch (q->ypvers) {
+ case YPVERS:
+ bzero((char *)&result_v2, sizeof(result_v2));
+
+ if (buf == NULL)
+ result_v2.stat = YP_NOKEY;
+ else {
+ result_v2.val.valdat_len = strlen(buf);
+ result_v2.val.valdat_val = buf;
+ result_v2.stat = YP_TRUE;
+ }
+ result = (char *)&result_v2;
+ xdrfunc = (xdrproc_t)xdr_ypresp_val;
+ break;
+ case YPOLDVERS:
+ /*
+ * The odds are we will _never_ execute this
+ * particular code, but we include it anyway
+ * for the sake of completeness.
+ */
+ bzero((char *)&result_v1, sizeof(result_v1));
+ result_v1.yp_resptype = YPRESP_VAL;
+
+#define YPVAL ypresponse_u.yp_resp_valtype
+ if (buf == NULL)
+ result_v1.YPVAL.stat = YP_NOKEY;
+ else {
+ result_v1.YPVAL.val.valdat_len = strlen(buf);
+ result_v1.YPVAL.val.valdat_val = buf;
+ result_v1.YPVAL.stat = YP_TRUE;
+ }
+ result = (char *)&result_v1;
+ xdrfunc = (xdrproc_t)xdr_ypresponse;
+ break;
+ default:
+ yp_error("bad YP program version (%lu)!", q->ypvers);
+ return;
+ break;
+ }
+
+ if (debug)
+ yp_error("sending dns reply to %s (%lu)",
+ inet_ntoa(q->client_addr.sin_addr), q->id);
+ /*
+ * XXX This is disgusting. There's basically one transport
+ * handle for UDP, but we're holding off on replying to a
+ * client until we're ready, by which time we may have received
+ * several other queries from other clients with different
+ * transaction IDs. So to make the delayed response thing work,
+ * we have to save the transaction ID and client address of
+ * each request, then jam them into the transport handle when
+ * we're ready to send a reply. Then after we've send the reply,
+ * we put the old transaction ID and remote address back the
+ * way we found 'em. This is _INCREDIBLY_ non-portable; it's
+ * not even supported by the RPC library.
+ */
+ /*
+ * XXX Don't frob the transaction ID for TCP handles.
+ */
+ if (q->prot_type == SOCK_DGRAM)
+ xid = svcudp_set_xid(q->xprt, q->xid);
+ client_addr = q->xprt->xp_raddr;
+ q->xprt->xp_raddr = q->client_addr;
+
+ if (!svc_sendreply(q->xprt, xdrfunc, result))
+ yp_error("svc_sendreply failed");
+
+ /*
+ * Now that we sent the reply,
+ * put the handle back the way it was.
+ */
+ if (q->prot_type == SOCK_DGRAM)
+ svcudp_set_xid(q->xprt, xid);
+ q->xprt->xp_raddr = client_addr;
+
+ return;
+}
+
+/*
+ * Decrement TTL on all queue entries, possibly nuking
+ * any that have been around too long without being serviced.
+ */
+void
+yp_prune_dnsq(void)
+{
+ register struct circleq_dnsentry *q, *n;
+
+ q = TAILQ_FIRST(&qhead);
+ while (q != NULL) {
+ q->ttl--;
+ n = TAILQ_NEXT(q, links);
+ if (!q->ttl) {
+ TAILQ_REMOVE(&qhead, q, links);
+ free(q->name);
+ free(q);
+ pending--;
+ }
+ q = n;
+ }
+
+ if (pending < 0)
+ pending = 0;
+
+ return;
+}
+
+/*
+ * Data is pending on the DNS socket; check for valid replies
+ * to our queries and dispatch them to waiting clients.
+ */
+void
+yp_run_dnsq(void)
+{
+ register struct circleq_dnsentry *q;
+ char buf[sizeof(HEADER) + MAXPACKET];
+ struct sockaddr_in sin;
+ socklen_t len;
+ int rval;
+ HEADER *hptr;
+ struct hostent *hent;
+
+ if (debug)
+ yp_error("running dns queue");
+
+ bzero(buf, sizeof(buf));
+
+ len = sizeof(struct sockaddr_in);
+ rval = recvfrom(resfd, buf, sizeof(buf), 0,
+ (struct sockaddr *)&sin, &len);
+
+ if (rval == -1) {
+ yp_error("recvfrom failed: %s", strerror(errno));
+ return;
+ }
+
+ /*
+ * We may have data left in the socket that represents
+ * replies to earlier queries that we don't care about
+ * anymore. If there are no lookups pending or the packet
+ * ID doesn't match any of the queue IDs, just drop it
+ * on the floor.
+ */
+ hptr = (HEADER *)&buf;
+ if (!pending ||
+ (q = yp_find_dnsqent(ntohs(hptr->id), BY_DNS_ID)) == NULL) {
+ /* ignore */
+ return;
+ }
+
+ if (debug)
+ yp_error("got dns reply from %s", inet_ntoa(sin.sin_addr));
+
+ hent = __dns_getanswer(buf, rval, q->name, q->type);
+
+ if (hent != NULL) {
+ if (q->type == T_PTR) {
+ hent->h_addr = (char *)q->addr;
+ hent->h_addrtype = q->addrtype;
+ hent->h_length = q->addrlen;
+ }
+ }
+
+ /* Got an answer ready for a client -- send it off. */
+ yp_send_dns_reply(q, parse(hent));
+ pending--;
+ TAILQ_REMOVE(&qhead, q, links);
+ free(q->name);
+ free(q);
+
+ /* Decrement TTLs on other entries while we're here. */
+ yp_prune_dnsq();
+
+ return;
+}
+
+/*
+ * Queue and transmit an asynchronous DNS hostname lookup.
+ */
+ypstat
+yp_async_lookup_name(struct svc_req *rqstp, char *name, int af)
+{
+ register struct circleq_dnsentry *q;
+ socklen_t len;
+ int type;
+
+ /* Check for SOCK_DGRAM or SOCK_STREAM -- we need to know later */
+ type = -1;
+ len = sizeof(type);
+ if (getsockopt(rqstp->rq_xprt->xp_fd, SOL_SOCKET,
+ SO_TYPE, &type, &len) == -1) {
+ yp_error("getsockopt failed: %s", strerror(errno));
+ return(YP_YPERR);
+ }
+
+ /* Avoid transmitting dupe requests. */
+ if (type == SOCK_DGRAM &&
+ yp_find_dnsqent(svcudp_get_xid(rqstp->rq_xprt),BY_RPC_XID) != NULL)
+ return(YP_TRUE);
+
+ if ((q = yp_malloc_dnsent()) == NULL)
+ return(YP_YPERR);
+
+ q->type = (af == AF_INET) ? T_A : T_AAAA;
+ q->ttl = DEF_TTL;
+ q->xprt = rqstp->rq_xprt;
+ q->ypvers = rqstp->rq_vers;
+ q->prot_type = type;
+ if (q->prot_type == SOCK_DGRAM)
+ q->xid = svcudp_get_xid(q->xprt);
+ q->client_addr = q->xprt->xp_raddr;
+ q->domain = _res.dnsrch;
+ q->id = yp_send_dns_query(name, q->type);
+
+ if (q->id == 0) {
+ yp_error("DNS query failed");
+ free(q);
+ return(YP_YPERR);
+ }
+
+ q->name = strdup(name);
+ TAILQ_INSERT_HEAD(&qhead, q, links);
+ pending++;
+
+ if (debug)
+ yp_error("queueing async DNS name lookup (%lu)", q->id);
+
+ yp_prune_dnsq();
+ return(YP_TRUE);
+}
+
+/*
+ * Queue and transmit an asynchronous DNS IP address lookup.
+ */
+ypstat
+yp_async_lookup_addr(struct svc_req *rqstp, char *addr, int af)
+{
+ register struct circleq_dnsentry *q;
+ char buf[MAXHOSTNAMELEN], *qp;
+ uint32_t abuf[4]; /* IPv4 or IPv6 */
+ u_char *uaddr = (u_char *)abuf;
+ socklen_t len;
+ int type, n;
+
+ /* Check for SOCK_DGRAM or SOCK_STREAM -- we need to know later */
+ type = -1;
+ len = sizeof(type);
+ if (getsockopt(rqstp->rq_xprt->xp_fd, SOL_SOCKET,
+ SO_TYPE, &type, &len) == -1) {
+ yp_error("getsockopt failed: %s", strerror(errno));
+ return(YP_YPERR);
+ }
+
+ /* Avoid transmitting dupe requests. */
+ if (type == SOCK_DGRAM &&
+ yp_find_dnsqent(svcudp_get_xid(rqstp->rq_xprt),BY_RPC_XID) != NULL)
+ return(YP_TRUE);
+
+ if ((q = yp_malloc_dnsent()) == NULL)
+ return(YP_YPERR);
+
+ switch (af) {
+ case AF_INET:
+ if (inet_aton(addr, (struct in_addr *)uaddr) != 1)
+ return(YP_NOKEY);
+ snprintf(buf, sizeof(buf), "%u.%u.%u.%u.in-addr.arpa",
+ (uaddr[3] & 0xff), (uaddr[2] & 0xff),
+ (uaddr[1] & 0xff), (uaddr[0] & 0xff));
+ len = INADDRSZ;
+ break;
+ case AF_INET6:
+ if (inet_pton(af, addr, uaddr) != 1)
+ return(YP_NOKEY);
+ qp = buf;
+ for (n = IN6ADDRSZ - 1; n >= 0; n--) {
+ qp += (size_t)sprintf(qp, "%x.%x.", uaddr[n] & 0xf,
+ (uaddr[n] >> 4) & 0xf);
+ }
+ strlcat(buf, "ip6.arpa", sizeof(buf));
+ len = IN6ADDRSZ;
+ break;
+ default:
+ return(YP_YPERR);
+ }
+
+ if (debug)
+ yp_error("DNS address is: %s", buf);
+
+ q->type = T_PTR;
+ q->ttl = DEF_TTL;
+ q->xprt = rqstp->rq_xprt;
+ q->ypvers = rqstp->rq_vers;
+ q->domain = NULL;
+ q->prot_type = type;
+ if (q->prot_type == SOCK_DGRAM)
+ q->xid = svcudp_get_xid(q->xprt);
+ q->client_addr = q->xprt->xp_raddr;
+ q->id = yp_send_dns_query(buf, q->type);
+
+ if (q->id == 0) {
+ yp_error("DNS query failed");
+ free(q);
+ return(YP_YPERR);
+ }
+
+ memcpy(q->addr, uaddr, len);
+ q->addrlen = len;
+ q->addrtype = af;
+ q->name = strdup(buf);
+ TAILQ_INSERT_HEAD(&qhead, q, links);
+ pending++;
+
+ if (debug)
+ yp_error("queueing async DNS address lookup (%lu)", q->id);
+
+ yp_prune_dnsq();
+ return(YP_TRUE);
+}
diff --git a/usr.sbin/ypserv/yp_error.c b/usr.sbin/ypserv/yp_error.c
new file mode 100644
index 0000000..a5b1290
--- /dev/null
+++ b/usr.sbin/ypserv/yp_error.c
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 1995
+ * 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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$");
+
+/*
+ * error logging/reporting facilities
+ * stolen from /usr/libexec/mail.local via ypserv
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <syslog.h>
+#include "yp_extern.h"
+
+int debug;
+
+extern int _rpcpmstart;
+extern char *progname;
+static void __verr(const char *fmt, va_list ap) __printflike(1, 0);
+
+static void
+__verr(const char *fmt, va_list ap)
+{
+ if (debug && !_rpcpmstart) {
+ fprintf(stderr,"%s: ",progname);
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ } else {
+ vsyslog(LOG_NOTICE, fmt, ap);
+ }
+}
+
+void
+yp_error(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ __verr(fmt,ap);
+ va_end(ap);
+}
diff --git a/usr.sbin/ypserv/yp_extern.h b/usr.sbin/ypserv/yp_extern.h
new file mode 100644
index 0000000..2e574b6
--- /dev/null
+++ b/usr.sbin/ypserv/yp_extern.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 1995
+ * 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <db.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+#include <rpc/rpc.h>
+#include <rpcsvc/yp.h>
+
+#ifndef _PATH_YP
+#define _PATH_YP "/var/yp/"
+#endif
+
+#ifndef _PATH_LIBEXEC
+#define _PATH_LIBEXEC "/usr/libexec/"
+#endif
+
+#ifndef MAX_CHILDREN
+#define MAX_CHILDREN 20
+#endif
+
+#define YP_SECURE 0x1
+#define YP_INTERDOMAIN 0x2
+
+/*
+ * External functions and variables.
+ */
+
+extern int debug;
+extern int ypdb_debug;
+extern int do_dns;
+extern int children;
+extern int resfd;
+extern char *progname;
+extern char *yp_dir;
+extern pid_t yp_pid;
+
+extern enum ypstat yp_errno;
+extern void yp_error(const char *, ...) __printflike(1, 2);
+#ifdef DB_CACHE
+extern int yp_get_record(DB *, const DBT *, DBT *, int);
+#else
+extern int yp_get_record(const char *, const char *, const DBT *, DBT *, int);
+#endif
+extern int yp_first_record(const DB *, DBT *, DBT *, int);
+extern int yp_next_record(const DB *, DBT *, DBT *, int, int);
+extern char *yp_dnsname(char *);
+extern char *yp_dnsaddr(const char *);
+#ifdef DB_CACHE
+extern int yp_access(const char *, const char *, const struct svc_req *);
+#else
+extern int yp_access(const char *, const struct svc_req *);
+#endif
+extern int yp_validdomain(const char *);
+extern DB *yp_open_db(const char *, const char *);
+extern DB *yp_open_db_cache(const char *, const char *, const char *, int);
+extern void yp_flush_all(void);
+extern void yp_init_dbs(void);
+extern int yp_testflag(char *, char *, int);
+extern void load_securenets(void);
+
+#ifdef DB_CACHE
+extern ypstat yp_select_map(char *, char *, keydat *, int);
+extern ypstat yp_getbykey(keydat *, valdat *);
+extern ypstat yp_firstbykey(keydat *, valdat *);
+extern ypstat yp_nextbykey(keydat *, valdat *);
+#endif
+
+extern unsigned long svcudp_set_xid(SVCXPRT *, unsigned long);
+extern unsigned long svcudp_get_xid(SVCXPRT *);
+
+#ifndef RESOLVER_TIMEOUT
+#define RESOLVER_TIMEOUT 3600
+#endif
+
+extern int yp_init_resolver(void);
+extern void yp_run_dnsq(void);
+extern void yp_prune_dnsq(void);
+extern ypstat yp_async_lookup_name(struct svc_req *, char *, int);
+extern ypstat yp_async_lookup_addr(struct svc_req *, char *, int);
diff --git a/usr.sbin/ypserv/yp_main.c b/usr.sbin/ypserv/yp_main.c
new file mode 100644
index 0000000..8c538ca
--- /dev/null
+++ b/usr.sbin/ypserv/yp_main.c
@@ -0,0 +1,579 @@
+/*
+ * Copyright (c) 1995
+ * 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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$");
+
+/*
+ * ypserv startup function.
+ * We need out own main() since we have to do some additional work
+ * that rpcgen won't do for us. Most of this file was generated using
+ * rpcgen.new, and later modified.
+ */
+
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include "yp.h"
+#include <err.h>
+#include <errno.h>
+#include <memory.h>
+#include <stdio.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdlib.h> /* getenv, exit */
+#include <string.h> /* strcmp */
+#include <syslog.h>
+#include <unistd.h>
+#ifdef __cplusplus
+#include <sysent.h> /* getdtablesize, open */
+#endif /* __cplusplus */
+#include <netinet/in.h>
+#include <netdb.h>
+#include "yp_extern.h"
+#include <netconfig.h>
+#include <rpc/rpc.h>
+#include <rpc/rpc_com.h>
+
+#ifndef SIG_PF
+#define SIG_PF void(*)(int)
+#endif
+
+#define _RPCSVC_CLOSEDOWN 120
+int _rpcpmstart; /* Started by a port monitor ? */
+static int _rpcfdtype; /* Whether Stream or Datagram? */
+static int _rpcaf;
+static int _rpcfd;
+
+/* States a server can be in wrt request */
+#define _IDLE 0
+#define _SERVED 1
+#define _SERVING 2
+
+extern void ypprog_1(struct svc_req *, SVCXPRT *);
+extern void ypprog_2(struct svc_req *, SVCXPRT *);
+extern int _rpc_dtablesize(void);
+extern int _rpcsvcstate; /* Set when a request is serviced */
+char *progname = "ypserv";
+char *yp_dir = _PATH_YP;
+/*int debug = 0;*/
+int do_dns = 0;
+int resfd;
+
+struct socklistent {
+ int sle_sock;
+ struct sockaddr_storage sle_ss;
+ SLIST_ENTRY(socklistent) sle_next;
+};
+static SLIST_HEAD(, socklistent) sle_head =
+ SLIST_HEAD_INITIALIZER(sle_head);
+
+struct bindaddrlistent {
+ const char *ble_hostname;
+ SLIST_ENTRY(bindaddrlistent) ble_next;
+};
+static SLIST_HEAD(, bindaddrlistent) ble_head =
+ SLIST_HEAD_INITIALIZER(ble_head);
+
+static char *servname = "0";
+
+static
+void _msgout(char* msg, ...)
+{
+ va_list ap;
+
+ va_start(ap, msg);
+ if (debug) {
+ if (_rpcpmstart)
+ vsyslog(LOG_ERR, msg, ap);
+ else
+ vwarnx(msg, ap);
+ } else
+ vsyslog(LOG_ERR, msg, ap);
+ va_end(ap);
+}
+
+pid_t yp_pid;
+
+static void
+yp_svc_run(void)
+{
+#ifdef FD_SETSIZE
+ fd_set readfds;
+#else
+ int readfds;
+#endif /* def FD_SETSIZE */
+ int fd_setsize = _rpc_dtablesize();
+ struct timeval timeout;
+
+ /* Establish the identity of the parent ypserv process. */
+ yp_pid = getpid();
+
+ for (;;) {
+#ifdef FD_SETSIZE
+ readfds = svc_fdset;
+#else
+ readfds = svc_fds;
+#endif /* def FD_SETSIZE */
+
+ FD_SET(resfd, &readfds);
+
+ timeout.tv_sec = RESOLVER_TIMEOUT;
+ timeout.tv_usec = 0;
+ switch (select(fd_setsize, &readfds, NULL, NULL,
+ &timeout)) {
+ case -1:
+ if (errno == EINTR) {
+ continue;
+ }
+ warn("svc_run: - select failed");
+ return;
+ case 0:
+ if (getpid() == yp_pid)
+ yp_prune_dnsq();
+ break;
+ default:
+ if (getpid() == yp_pid) {
+ if (FD_ISSET(resfd, &readfds)) {
+ yp_run_dnsq();
+ FD_CLR(resfd, &readfds);
+ }
+ svc_getreqset(&readfds);
+ }
+ }
+ if (yp_pid != getpid())
+ _exit(0);
+ }
+}
+
+static void
+unregister(void)
+{
+ (void)svc_unreg(YPPROG, YPVERS);
+ (void)svc_unreg(YPPROG, YPOLDVERS);
+}
+
+static void
+reaper(int sig)
+{
+ int status;
+ int saved_errno;
+
+ saved_errno = errno;
+
+ if (sig == SIGHUP) {
+ load_securenets();
+#ifdef DB_CACHE
+ yp_flush_all();
+#endif
+ errno = saved_errno;
+ return;
+ }
+
+ if (sig == SIGCHLD) {
+ while (wait3(&status, WNOHANG, NULL) > 0)
+ children--;
+ } else {
+ unregister();
+ exit(0);
+ }
+ errno = saved_errno;
+ return;
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: ypserv [-h addr] [-d] [-n] [-p path] [-P port]\n");
+ exit(1);
+}
+
+static void
+closedown(int sig)
+{
+ if (_rpcsvcstate == _IDLE) {
+ extern fd_set svc_fdset;
+ static int size;
+ int i, openfd;
+
+ if (_rpcfdtype == SOCK_DGRAM) {
+ unregister();
+ exit(0);
+ }
+ if (size == 0) {
+ size = getdtablesize();
+ }
+ for (i = 0, openfd = 0; i < size && openfd < 2; i++)
+ if (FD_ISSET(i, &svc_fdset))
+ openfd++;
+ if (openfd <= 1) {
+ unregister();
+ exit(0);
+ }
+ }
+ if (_rpcsvcstate == _SERVED)
+ _rpcsvcstate = _IDLE;
+
+ (void) signal(SIGALRM, (SIG_PF) closedown);
+ (void) alarm(_RPCSVC_CLOSEDOWN/2);
+}
+
+static int
+create_service(const int sock, const struct netconfig *nconf,
+ const struct __rpc_sockinfo *si)
+{
+ int error;
+
+ SVCXPRT *transp;
+ struct addrinfo hints, *res, *res0;
+ struct socklistent *slep;
+ struct bindaddrlistent *blep;
+ struct netbuf svcaddr;
+
+ SLIST_INIT(&sle_head);
+ memset(&hints, 0, sizeof(hints));
+ memset(&svcaddr, 0, sizeof(svcaddr));
+
+ hints.ai_family = si->si_af;
+ hints.ai_socktype = si->si_socktype;
+ hints.ai_protocol = si->si_proto;
+
+ /*
+ * Build socketlist from bindaddrlist.
+ */
+ if (sock == RPC_ANYFD) {
+ SLIST_FOREACH(blep, &ble_head, ble_next) {
+ if (blep->ble_hostname == NULL)
+ hints.ai_flags = AI_PASSIVE;
+ else
+ hints.ai_flags = 0;
+ error = getaddrinfo(blep->ble_hostname, servname,
+ &hints, &res0);
+ if (error) {
+ _msgout("getaddrinfo(): %s",
+ gai_strerror(error));
+ return -1;
+ }
+ for (res = res0; res; res = res->ai_next) {
+ int s;
+
+ s = __rpc_nconf2fd(nconf);
+ if (s < 0) {
+ if (errno == EAFNOSUPPORT)
+ _msgout("unsupported"
+ " transport: %s",
+ nconf->nc_netid);
+ else
+ _msgout("cannot create"
+ " %s socket: %s",
+ nconf->nc_netid,
+ strerror(errno));
+ freeaddrinfo(res0);
+ return -1;
+ }
+ if (bindresvport_sa(s, res->ai_addr) == -1) {
+ if ((errno != EPERM) ||
+ (bind(s, res->ai_addr,
+ res->ai_addrlen) == -1)) {
+ _msgout("cannot bind "
+ "%s socket: %s",
+ nconf->nc_netid,
+ strerror(errno));
+ freeaddrinfo(res0);
+ close(sock);
+ return -1;
+ }
+ }
+ if (nconf->nc_semantics != NC_TPI_CLTS)
+ listen(s, SOMAXCONN);
+
+ slep = malloc(sizeof(*slep));
+ if (slep == NULL) {
+ _msgout("malloc failed: %s",
+ strerror(errno));
+ freeaddrinfo(res0);
+ close(s);
+ return -1;
+ }
+ memset(slep, 0, sizeof(*slep));
+ memcpy(&slep->sle_ss, res->ai_addr,
+ res->ai_addrlen);
+ slep->sle_sock = s;
+ SLIST_INSERT_HEAD(&sle_head, slep, sle_next);
+
+ /*
+ * If servname == "0", redefine it by using
+ * the bound socket.
+ */
+ if (strncmp("0", servname, 1) == 0) {
+ struct sockaddr *sap;
+ socklen_t slen;
+ char *sname;
+
+ sname = malloc(NI_MAXSERV);
+ if (sname == NULL) {
+ _msgout("malloc(): %s",
+ strerror(errno));
+ freeaddrinfo(res0);
+ close(s);
+ return -1;
+ }
+ memset(sname, 0, NI_MAXSERV);
+
+ sap = (struct sockaddr *)&slep->sle_ss;
+ slen = sizeof(*sap);
+ error = getsockname(s, sap, &slen);
+ if (error) {
+ _msgout("getsockname(): %s",
+ strerror(errno));
+ freeaddrinfo(res0);
+ close(s);
+ free(sname);
+ return -1;
+ }
+ error = getnameinfo(sap, slen,
+ NULL, 0,
+ sname, NI_MAXSERV,
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ if (error) {
+ _msgout("getnameinfo(): %s",
+ strerror(errno));
+ freeaddrinfo(res0);
+ close(s);
+ free(sname);
+ return -1;
+ }
+ servname = sname;
+ }
+ }
+ freeaddrinfo(res0);
+ }
+ } else {
+ slep = malloc(sizeof(*slep));
+ if (slep == NULL) {
+ _msgout("malloc failed: %s", strerror(errno));
+ return -1;
+ }
+ memset(slep, 0, sizeof(*slep));
+ slep->sle_sock = sock;
+ SLIST_INSERT_HEAD(&sle_head, slep, sle_next);
+ }
+
+ /*
+ * Traverse socketlist and create rpc service handles for each socket.
+ */
+ SLIST_FOREACH(slep, &sle_head, sle_next) {
+ if (nconf->nc_semantics == NC_TPI_CLTS)
+ transp = svc_dg_create(slep->sle_sock, 0, 0);
+ else
+ transp = svc_vc_create(slep->sle_sock, RPC_MAXDATASIZE,
+ RPC_MAXDATASIZE);
+ if (transp == NULL) {
+ _msgout("unable to create service: %s",
+ nconf->nc_netid);
+ continue;
+ }
+ if (!svc_reg(transp, YPPROG, YPOLDVERS, ypprog_1, NULL)) {
+ svc_destroy(transp);
+ close(slep->sle_sock);
+ _msgout("unable to register (YPPROG, YPOLDVERS, %s):"
+ " %s", nconf->nc_netid, strerror(errno));
+ continue;
+ }
+ if (!svc_reg(transp, YPPROG, YPVERS, ypprog_2, NULL)) {
+ svc_destroy(transp);
+ close(slep->sle_sock);
+ _msgout("unable to register (YPPROG, YPVERS, %s): %s",
+ nconf->nc_netid, strerror(errno));
+ continue;
+ }
+ }
+ while(!(SLIST_EMPTY(&sle_head)))
+ SLIST_REMOVE_HEAD(&sle_head, sle_next);
+
+ /*
+ * Register RPC service to rpcbind by using AI_PASSIVE address.
+ */
+ hints.ai_flags = AI_PASSIVE;
+ error = getaddrinfo(NULL, servname, &hints, &res0);
+ if (error) {
+ _msgout("getaddrinfo(): %s", gai_strerror(error));
+ return -1;
+ }
+ svcaddr.buf = res0->ai_addr;
+ svcaddr.len = res0->ai_addrlen;
+
+ if (si->si_af == AF_INET) {
+ /* XXX: ignore error intentionally */
+ rpcb_set(YPPROG, YPOLDVERS, nconf, &svcaddr);
+ }
+ /* XXX: ignore error intentionally */
+ rpcb_set(YPPROG, YPVERS, nconf, &svcaddr);
+ freeaddrinfo(res0);
+ return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ int error;
+ int ntrans;
+
+ void *nc_handle;
+ struct netconfig *nconf;
+ struct __rpc_sockinfo si;
+ struct bindaddrlistent *blep;
+
+ memset(&si, 0, sizeof(si));
+ SLIST_INIT(&ble_head);
+
+ while ((ch = getopt(argc, argv, "dh:np:P:")) != -1) {
+ switch (ch) {
+ case 'd':
+ debug = ypdb_debug = 1;
+ break;
+ case 'h':
+ blep = malloc(sizeof(*blep));
+ if (blep == NULL)
+ err(1, "malloc() failed: -h %s", optarg);
+ blep->ble_hostname = optarg;
+ SLIST_INSERT_HEAD(&ble_head, blep, ble_next);
+ break;
+ case 'n':
+ do_dns = 1;
+ break;
+ case 'p':
+ yp_dir = optarg;
+ break;
+ case 'P':
+ servname = optarg;
+ break;
+ default:
+ usage();
+ }
+ }
+ /*
+ * Add "anyaddr" entry if no -h is specified.
+ */
+ if (SLIST_EMPTY(&ble_head)) {
+ blep = malloc(sizeof(*blep));
+ if (blep == NULL)
+ err(1, "malloc() failed");
+ memset(blep, 0, sizeof(*blep));
+ SLIST_INSERT_HEAD(&ble_head, blep, ble_next);
+ }
+
+ load_securenets();
+ yp_init_resolver();
+#ifdef DB_CACHE
+ yp_init_dbs();
+#endif
+ nc_handle = setnetconfig();
+ if (nc_handle == NULL)
+ err(1, "cannot read %s", NETCONFIG);
+ if (__rpc_fd2sockinfo(0, &si) != 0) {
+ /* invoked from inetd */
+ _rpcpmstart = 1;
+ _rpcfdtype = si.si_socktype;
+ _rpcaf = si.si_af;
+ _rpcfd = 0;
+ openlog("ypserv", LOG_PID, LOG_DAEMON);
+ } else {
+ /* standalone mode */
+ if (!debug) {
+ if (daemon(0,0)) {
+ err(1,"cannot fork");
+ }
+ openlog("ypserv", LOG_PID, LOG_DAEMON);
+ }
+ _rpcpmstart = 0;
+ _rpcaf = AF_INET;
+ _rpcfd = RPC_ANYFD;
+ unregister();
+ }
+
+ if (madvise(NULL, 0, MADV_PROTECT) != 0)
+ _msgout("madvise(): %s", strerror(errno));
+
+ /*
+ * Create RPC service for each transport.
+ */
+ ntrans = 0;
+ while((nconf = getnetconfig(nc_handle))) {
+ if ((nconf->nc_flag & NC_VISIBLE)) {
+ if (__rpc_nconf2sockinfo(nconf, &si) == 0) {
+ _msgout("cannot get information for %s. "
+ "Ignored.", nconf->nc_netid);
+ continue;
+ }
+ if (_rpcpmstart) {
+ if (si.si_socktype != _rpcfdtype ||
+ si.si_af != _rpcaf)
+ continue;
+ } else if (si.si_af != _rpcaf)
+ continue;
+ error = create_service(_rpcfd, nconf, &si);
+ if (error) {
+ endnetconfig(nc_handle);
+ exit(1);
+ }
+ ntrans++;
+ }
+ }
+ endnetconfig(nc_handle);
+ while(!(SLIST_EMPTY(&ble_head)))
+ SLIST_REMOVE_HEAD(&ble_head, ble_next);
+ if (ntrans == 0) {
+ _msgout("no transport is available. Aborted.");
+ exit(1);
+ }
+ if (_rpcpmstart) {
+ (void) signal(SIGALRM, (SIG_PF) closedown);
+ (void) alarm(_RPCSVC_CLOSEDOWN/2);
+ }
+/*
+ * Make sure SIGPIPE doesn't blow us away while servicing TCP
+ * connections.
+ */
+ (void) signal(SIGPIPE, SIG_IGN);
+ (void) signal(SIGCHLD, (SIG_PF) reaper);
+ (void) signal(SIGTERM, (SIG_PF) reaper);
+ (void) signal(SIGINT, (SIG_PF) reaper);
+ (void) signal(SIGHUP, (SIG_PF) reaper);
+ yp_svc_run();
+ _msgout("svc_run returned");
+ exit(1);
+ /* NOTREACHED */
+}
diff --git a/usr.sbin/ypserv/yp_server.c b/usr.sbin/ypserv/yp_server.c
new file mode 100644
index 0000000..ba20c3cd
--- /dev/null
+++ b/usr.sbin/ypserv/yp_server.c
@@ -0,0 +1,985 @@
+/*
+ * Copyright (c) 1995
+ * 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 "yp.h"
+#include "yp_extern.h"
+#include <dirent.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <rpc/rpc.h>
+
+int children = 0;
+
+#define MASTER_STRING "YP_MASTER_NAME"
+#define MASTER_SZ sizeof(MASTER_STRING) - 1
+#define ORDER_STRING "YP_LAST_MODIFIED"
+#define ORDER_SZ sizeof(ORDER_STRING) - 1
+
+static pid_t
+yp_fork(void)
+{
+ if (yp_pid != getpid()) {
+ yp_error("child %d trying to fork!", getpid());
+ errno = EEXIST;
+ return(-1);
+ }
+
+ return(fork());
+}
+
+/*
+ * NIS v2 support. This is where most of the action happens.
+ */
+
+void *
+ypproc_null_2_svc(void *argp, struct svc_req *rqstp)
+{
+ static char * result;
+ static char rval = 0;
+
+#ifdef DB_CACHE
+ if (yp_access(NULL, NULL, (struct svc_req *)rqstp))
+#else
+ if (yp_access(NULL, (struct svc_req *)rqstp))
+#endif
+ return(NULL);
+
+ result = &rval;
+
+ return((void *) &result);
+}
+
+bool_t *
+ypproc_domain_2_svc(domainname *argp, struct svc_req *rqstp)
+{
+ static bool_t result;
+
+#ifdef DB_CACHE
+ if (yp_access(NULL, NULL, (struct svc_req *)rqstp)) {
+#else
+ if (yp_access(NULL, (struct svc_req *)rqstp)) {
+#endif
+ result = FALSE;
+ return (&result);
+ }
+
+ if (argp == NULL || yp_validdomain(*argp))
+ result = FALSE;
+ else
+ result = TRUE;
+
+ return (&result);
+}
+
+bool_t *
+ypproc_domain_nonack_2_svc(domainname *argp, struct svc_req *rqstp)
+{
+ static bool_t result;
+
+#ifdef DB_CACHE
+ if (yp_access(NULL, NULL, (struct svc_req *)rqstp))
+#else
+ if (yp_access(NULL, (struct svc_req *)rqstp))
+#endif
+ return (NULL);
+
+ if (argp == NULL || yp_validdomain(*argp))
+ return (NULL);
+ else
+ result = TRUE;
+
+ return (&result);
+}
+
+ypresp_val *
+ypproc_match_2_svc(ypreq_key *argp, struct svc_req *rqstp)
+{
+ static ypresp_val result;
+
+ result.val.valdat_val = "";
+ result.val.valdat_len = 0;
+
+#ifdef DB_CACHE
+ if (yp_access(argp->map, argp->domain, (struct svc_req *)rqstp)) {
+#else
+ if (yp_access(argp->map, (struct svc_req *)rqstp)) {
+#endif
+ result.stat = YP_YPERR;
+ return (&result);
+ }
+
+ if (argp->domain == NULL || argp->map == NULL) {
+ result.stat = YP_BADARGS;
+ return (&result);
+ }
+
+ if (yp_select_map(argp->map, argp->domain, NULL, 1) != YP_TRUE) {
+ result.stat = yp_errno;
+ return(&result);
+ }
+
+ result.stat = yp_getbykey(&argp->key, &result.val);
+
+ /*
+ * Do DNS lookups for hosts maps if database lookup failed.
+ */
+
+#ifdef DB_CACHE
+ if (do_dns && result.stat != YP_TRUE &&
+ (yp_testflag(argp->map, argp->domain, YP_INTERDOMAIN) ||
+ (strstr(argp->map, "hosts") || strstr(argp->map, "ipnodes")))) {
+#else
+ if (do_dns && result.stat != YP_TRUE &&
+ (strstr(argp->map, "hosts") || strstr(argp->map, "ipnodes"))) {
+#endif
+ char nbuf[YPMAXRECORD];
+
+ /* NUL terminate! NUL terminate!! NUL TERMINATE!!! */
+ bcopy(argp->key.keydat_val, nbuf, argp->key.keydat_len);
+ nbuf[argp->key.keydat_len] = '\0';
+
+ if (debug)
+ yp_error("doing DNS lookup of %s", nbuf);
+
+ if (!strcmp(argp->map, "hosts.byname"))
+ result.stat = yp_async_lookup_name(rqstp, nbuf,
+ AF_INET);
+ else if (!strcmp(argp->map, "hosts.byaddr"))
+ result.stat = yp_async_lookup_addr(rqstp, nbuf,
+ AF_INET);
+ else if (!strcmp(argp->map, "ipnodes.byname"))
+ result.stat = yp_async_lookup_name(rqstp, nbuf,
+ AF_INET6);
+ else if (!strcmp(argp->map, "ipnodes.byaddr"))
+ result.stat = yp_async_lookup_addr(rqstp, nbuf,
+ AF_INET6);
+
+ if (result.stat == YP_TRUE)
+ return(NULL);
+ }
+
+ return (&result);
+}
+
+ypresp_key_val *
+ypproc_first_2_svc(ypreq_nokey *argp, struct svc_req *rqstp)
+{
+ static ypresp_key_val result;
+
+ result.val.valdat_val = result.key.keydat_val = "";
+ result.val.valdat_len = result.key.keydat_len = 0;
+
+#ifdef DB_CACHE
+ if (yp_access(argp->map, argp->domain, (struct svc_req *)rqstp)) {
+#else
+ if (yp_access(argp->map, (struct svc_req *)rqstp)) {
+#endif
+ result.stat = YP_YPERR;
+ return (&result);
+ }
+
+ if (argp->domain == NULL) {
+ result.stat = YP_BADARGS;
+ return (&result);
+ }
+
+ if (yp_select_map(argp->map, argp->domain, NULL, 0) != YP_TRUE) {
+ result.stat = yp_errno;
+ return(&result);
+ }
+
+ result.stat = yp_firstbykey(&result.key, &result.val);
+
+ return (&result);
+}
+
+ypresp_key_val *
+ypproc_next_2_svc(ypreq_key *argp, struct svc_req *rqstp)
+{
+ static ypresp_key_val result;
+
+ result.val.valdat_val = result.key.keydat_val = "";
+ result.val.valdat_len = result.key.keydat_len = 0;
+
+#ifdef DB_CACHE
+ if (yp_access(argp->map, argp->domain, (struct svc_req *)rqstp)) {
+#else
+ if (yp_access(argp->map, (struct svc_req *)rqstp)) {
+#endif
+ result.stat = YP_YPERR;
+ return (&result);
+ }
+
+ if (argp->domain == NULL || argp->map == NULL) {
+ result.stat = YP_BADARGS;
+ return (&result);
+ }
+
+ if (yp_select_map(argp->map, argp->domain, &argp->key, 0) != YP_TRUE) {
+ result.stat = yp_errno;
+ return(&result);
+ }
+
+ result.key.keydat_len = argp->key.keydat_len;
+ result.key.keydat_val = argp->key.keydat_val;
+
+ result.stat = yp_nextbykey(&result.key, &result.val);
+
+ return (&result);
+}
+
+static void
+ypxfr_callback(ypxfrstat rval, struct sockaddr_in *addr, unsigned int transid,
+ unsigned int prognum, unsigned long port)
+{
+ CLIENT *clnt;
+ int sock = RPC_ANYSOCK;
+ struct timeval timeout;
+ yppushresp_xfr ypxfr_resp;
+ struct rpc_err err;
+
+ timeout.tv_sec = 5;
+ timeout.tv_usec = 0;
+ addr->sin_port = htons(port);
+
+ if ((clnt = clntudp_create(addr,prognum,1,timeout,&sock)) == NULL) {
+ yp_error("%s: %s", inet_ntoa(addr->sin_addr),
+ clnt_spcreateerror("failed to establish callback handle"));
+ return;
+ }
+
+ ypxfr_resp.status = rval;
+ ypxfr_resp.transid = transid;
+
+ /* Turn the timeout off -- we don't want to block. */
+ timeout.tv_sec = 0;
+ if (clnt_control(clnt, CLSET_TIMEOUT, &timeout) == FALSE)
+ yp_error("failed to set timeout on ypproc_xfr callback");
+
+ if (yppushproc_xfrresp_1(&ypxfr_resp, clnt) == NULL) {
+ clnt_geterr(clnt, &err);
+ if (err.re_status != RPC_SUCCESS &&
+ err.re_status != RPC_TIMEDOUT)
+ yp_error("%s", clnt_sperror(clnt,
+ "ypxfr callback failed"));
+ }
+
+ clnt_destroy(clnt);
+ return;
+}
+
+#define YPXFR_RETURN(CODE) \
+ /* Order is important: send regular RPC reply, then callback */ \
+ result.xfrstat = CODE; \
+ svc_sendreply(rqstp->rq_xprt, (xdrproc_t)xdr_ypresp_xfr, &result); \
+ ypxfr_callback(CODE,rqhost,argp->transid, \
+ argp->prog,argp->port); \
+ return(NULL);
+
+ypresp_xfr *
+ypproc_xfr_2_svc(ypreq_xfr *argp, struct svc_req *rqstp)
+{
+ static ypresp_xfr result;
+ struct sockaddr_in *rqhost;
+ ypresp_master *mres;
+ ypreq_nokey mreq;
+
+ result.transid = argp->transid;
+ rqhost = svc_getcaller(rqstp->rq_xprt);
+
+#ifdef DB_CACHE
+ if (yp_access(argp->map_parms.map,
+ argp->map_parms.domain, (struct svc_req *)rqstp)) {
+#else
+ if (yp_access(argp->map_parms.map, (struct svc_req *)rqstp)) {
+#endif
+ YPXFR_RETURN(YPXFR_REFUSED)
+ }
+
+
+ if (argp->map_parms.domain == NULL) {
+ YPXFR_RETURN(YPXFR_BADARGS)
+ }
+
+ if (yp_validdomain(argp->map_parms.domain)) {
+ YPXFR_RETURN(YPXFR_NODOM)
+ }
+
+ /*
+ * Determine the master host ourselves. The caller may
+ * be up to no good. This has the side effect of verifying
+ * that the requested map and domain actually exist.
+ */
+
+ mreq.domain = argp->map_parms.domain;
+ mreq.map = argp->map_parms.map;
+
+ mres = ypproc_master_2_svc(&mreq, rqstp);
+
+ if (mres->stat != YP_TRUE) {
+ yp_error("couldn't find master for map %s@%s",
+ argp->map_parms.map,
+ argp->map_parms.domain);
+ yp_error("host at %s (%s) may be pulling my leg",
+ argp->map_parms.peer,
+ inet_ntoa(rqhost->sin_addr));
+ YPXFR_RETURN(YPXFR_REFUSED)
+ }
+
+ switch (yp_fork()) {
+ case 0:
+ {
+ char g[11], t[11], p[11];
+ char ypxfr_command[MAXPATHLEN + 2];
+
+ snprintf (ypxfr_command, sizeof(ypxfr_command), "%sypxfr", _PATH_LIBEXEC);
+ snprintf (t, sizeof(t), "%u", argp->transid);
+ snprintf (g, sizeof(g), "%u", argp->prog);
+ snprintf (p, sizeof(p), "%u", argp->port);
+ if (debug) {
+ close(0); close(1); close(2);
+ }
+ if (strcmp(yp_dir, _PATH_YP)) {
+ execl(ypxfr_command, "ypxfr",
+ "-d", argp->map_parms.domain,
+ "-h", mres->peer,
+ "-p", yp_dir, "-C", t,
+ g, inet_ntoa(rqhost->sin_addr),
+ p, argp->map_parms.map,
+ NULL);
+ } else {
+ execl(ypxfr_command, "ypxfr",
+ "-d", argp->map_parms.domain,
+ "-h", mres->peer,
+ "-C", t,
+ g, inet_ntoa(rqhost->sin_addr),
+ p, argp->map_parms.map,
+ NULL);
+ }
+ yp_error("ypxfr execl(%s): %s", ypxfr_command, strerror(errno));
+ YPXFR_RETURN(YPXFR_XFRERR)
+ /*
+ * Just to safe, prevent PR #10970 from biting us in
+ * the unlikely case that execing ypxfr fails. We don't
+ * want to have any child processes spawned from this
+ * child process.
+ */
+ _exit(0);
+ break;
+ }
+ case -1:
+ yp_error("ypxfr fork(): %s", strerror(errno));
+ YPXFR_RETURN(YPXFR_XFRERR)
+ break;
+ default:
+ result.xfrstat = YPXFR_SUCC;
+ children++;
+ break;
+ }
+
+ return (&result);
+}
+#undef YPXFR_RETURN
+
+void *
+ypproc_clear_2_svc(void *argp, struct svc_req *rqstp)
+{
+ static char * result;
+ static char rval = 0;
+
+#ifdef DB_CACHE
+ if (yp_access(NULL, NULL, (struct svc_req *)rqstp))
+#else
+ if (yp_access(NULL, (struct svc_req *)rqstp))
+#endif
+ return (NULL);
+#ifdef DB_CACHE
+ /* clear out the database cache */
+ yp_flush_all();
+#endif
+ /* Re-read the securenets database for the hell of it. */
+ load_securenets();
+
+ result = &rval;
+ return((void *) &result);
+}
+
+/*
+ * For ypproc_all, we have to send a stream of ypresp_all structures
+ * via TCP, but the XDR filter generated from the yp.x protocol
+ * definition file only serializes one such structure. This means that
+ * to send the whole stream, you need a wrapper which feeds all the
+ * records into the underlying XDR routine until it hits an 'EOF.'
+ * But to use the wrapper, you have to violate the boundaries between
+ * RPC layers by calling svc_sendreply() directly from the ypproc_all
+ * service routine instead of letting the RPC dispatcher do it.
+ *
+ * Bleah.
+ */
+
+/*
+ * Custom XDR routine for serialzing results of ypproc_all: keep
+ * reading from the database and spew until we run out of records
+ * or encounter an error.
+ */
+static bool_t
+xdr_my_ypresp_all(register XDR *xdrs, ypresp_all *objp)
+{
+ while (1) {
+ /* Get a record. */
+ if ((objp->ypresp_all_u.val.stat =
+ yp_nextbykey(&objp->ypresp_all_u.val.key,
+ &objp->ypresp_all_u.val.val)) == YP_TRUE) {
+ objp->more = TRUE;
+ } else {
+ objp->more = FALSE;
+ }
+
+ /* Serialize. */
+ if (!xdr_ypresp_all(xdrs, objp))
+ return(FALSE);
+ if (objp->more == FALSE)
+ return(TRUE);
+ }
+}
+
+ypresp_all *
+ypproc_all_2_svc(ypreq_nokey *argp, struct svc_req *rqstp)
+{
+ static ypresp_all result;
+
+ /*
+ * Set this here so that the client will be forced to make
+ * at least one attempt to read from us even if all we're
+ * doing is returning an error.
+ */
+ result.more = TRUE;
+ result.ypresp_all_u.val.key.keydat_len = 0;
+ result.ypresp_all_u.val.key.keydat_val = "";
+
+#ifdef DB_CACHE
+ if (yp_access(argp->map, argp->domain, (struct svc_req *)rqstp)) {
+#else
+ if (yp_access(argp->map, (struct svc_req *)rqstp)) {
+#endif
+ result.ypresp_all_u.val.stat = YP_YPERR;
+ return (&result);
+ }
+
+ if (argp->domain == NULL || argp->map == NULL) {
+ result.ypresp_all_u.val.stat = YP_BADARGS;
+ return (&result);
+ }
+
+ /*
+ * XXX If we hit the child limit, fail the request.
+ * If we don't, and the map is large, we could block for
+ * a long time in the parent.
+ */
+ if (children >= MAX_CHILDREN) {
+ result.ypresp_all_u.val.stat = YP_YPERR;
+ return(&result);
+ }
+
+ /*
+ * The ypproc_all procedure can take a while to complete.
+ * Best to handle it in a subprocess so the parent doesn't
+ * block. (Is there a better way to do this? Maybe with
+ * async socket I/O?)
+ */
+ if (!debug) {
+ switch (yp_fork()) {
+ case 0:
+ break;
+ case -1:
+ yp_error("ypall fork(): %s", strerror(errno));
+ result.ypresp_all_u.val.stat = YP_YPERR;
+ return(&result);
+ break;
+ default:
+ children++;
+ return (NULL);
+ break;
+ }
+ }
+
+ /*
+ * Fix for PR #10971: don't let the child ypserv share
+ * DB handles with the parent process.
+ */
+#ifdef DB_CACHE
+ yp_flush_all();
+#endif
+
+ if (yp_select_map(argp->map, argp->domain,
+ &result.ypresp_all_u.val.key, 0) != YP_TRUE) {
+ result.ypresp_all_u.val.stat = yp_errno;
+ return(&result);
+ }
+
+ /* Kick off the actual data transfer. */
+ svc_sendreply(rqstp->rq_xprt, (xdrproc_t)xdr_my_ypresp_all, &result);
+
+ /*
+ * Proper fix for PR #10970: exit here so that we don't risk
+ * having a child spawned from this sub-process.
+ */
+ if (!debug)
+ _exit(0);
+
+ return &result;
+}
+
+ypresp_master *
+ypproc_master_2_svc(ypreq_nokey *argp, struct svc_req *rqstp)
+{
+ static ypresp_master result;
+ static char ypvalbuf[YPMAXRECORD];
+ keydat key = { MASTER_SZ, MASTER_STRING };
+ valdat val;
+
+ result.peer = "";
+
+#ifdef DB_CACHE
+ if (yp_access(argp->map, argp->domain, (struct svc_req *)rqstp)) {
+#else
+ if (yp_access(argp->map, (struct svc_req *)rqstp)) {
+#endif
+ result.stat = YP_YPERR;
+ return(&result);
+ }
+
+ if (argp->domain == NULL) {
+ result.stat = YP_BADARGS;
+ return (&result);
+ }
+
+ if (yp_select_map(argp->map, argp->domain, &key, 1) != YP_TRUE) {
+ result.stat = yp_errno;
+ return(&result);
+ }
+
+ /*
+ * Note that we copy the data retrieved from the database to
+ * a private buffer and NUL terminate the buffer rather than
+ * terminating the data in place. We do this because by stuffing
+ * a '\0' into data.data, we will actually be corrupting memory
+ * allocated by the DB package. This is a bad thing now that we
+ * cache DB handles rather than closing the database immediately.
+ */
+ result.stat = yp_getbykey(&key, &val);
+ if (result.stat == YP_TRUE) {
+ bcopy(val.valdat_val, &ypvalbuf, val.valdat_len);
+ ypvalbuf[val.valdat_len] = '\0';
+ result.peer = ypvalbuf;
+ } else
+ result.peer = "";
+
+ return (&result);
+}
+
+ypresp_order *
+ypproc_order_2_svc(ypreq_nokey *argp, struct svc_req *rqstp)
+{
+ static ypresp_order result;
+ keydat key = { ORDER_SZ, ORDER_STRING };
+ valdat val;
+
+ result.ordernum = 0;
+
+#ifdef DB_CACHE
+ if (yp_access(argp->map, argp->domain, (struct svc_req *)rqstp)) {
+#else
+ if (yp_access(argp->map, (struct svc_req *)rqstp)) {
+#endif
+ result.stat = YP_YPERR;
+ return(&result);
+ }
+
+ if (argp->domain == NULL) {
+ result.stat = YP_BADARGS;
+ return (&result);
+ }
+
+ /*
+ * We could just check the timestamp on the map file,
+ * but that's a hack: we'll only know the last time the file
+ * was touched, not the last time the database contents were
+ * updated.
+ */
+
+ if (yp_select_map(argp->map, argp->domain, &key, 1) != YP_TRUE) {
+ result.stat = yp_errno;
+ return(&result);
+ }
+
+ result.stat = yp_getbykey(&key, &val);
+
+ if (result.stat == YP_TRUE)
+ result.ordernum = atoi(val.valdat_val);
+ else
+ result.ordernum = 0;
+
+ return (&result);
+}
+
+static void yp_maplist_free(struct ypmaplist *yp_maplist)
+{
+ register struct ypmaplist *next;
+
+ while (yp_maplist) {
+ next = yp_maplist->next;
+ free(yp_maplist->map);
+ free(yp_maplist);
+ yp_maplist = next;
+ }
+ return;
+}
+
+static struct ypmaplist *
+yp_maplist_create(const char *domain)
+{
+ char yp_mapdir[MAXPATHLEN + 2];
+ char yp_mapname[MAXPATHLEN + 2];
+ struct ypmaplist *cur = NULL;
+ struct ypmaplist *yp_maplist = NULL;
+ DIR *dird;
+ struct dirent *dirp;
+ struct stat statbuf;
+
+ snprintf(yp_mapdir, sizeof(yp_mapdir), "%s/%s", yp_dir, domain);
+
+ if ((dird = opendir(yp_mapdir)) == NULL) {
+ yp_error("opendir(%s) failed: %s", yp_mapdir, strerror(errno));
+ return(NULL);
+ }
+
+ while ((dirp = readdir(dird)) != NULL) {
+ if (strcmp(dirp->d_name, ".") && strcmp(dirp->d_name, "..")) {
+ snprintf(yp_mapname, sizeof(yp_mapname), "%s/%s",
+ yp_mapdir,dirp->d_name);
+ if (stat(yp_mapname, &statbuf) < 0 ||
+ !S_ISREG(statbuf.st_mode))
+ continue;
+ if ((cur = (struct ypmaplist *)
+ malloc(sizeof(struct ypmaplist))) == NULL) {
+ yp_error("malloc() failed");
+ closedir(dird);
+ yp_maplist_free(yp_maplist);
+ return(NULL);
+ }
+ if ((cur->map = strdup(dirp->d_name)) == NULL) {
+ yp_error("strdup() failed: %s",strerror(errno));
+ closedir(dird);
+ yp_maplist_free(yp_maplist);
+ return(NULL);
+ }
+ cur->next = yp_maplist;
+ yp_maplist = cur;
+ if (debug)
+ yp_error("map: %s", yp_maplist->map);
+ }
+
+ }
+ closedir(dird);
+ return(yp_maplist);
+}
+
+ypresp_maplist *
+ypproc_maplist_2_svc(domainname *argp, struct svc_req *rqstp)
+{
+ static ypresp_maplist result = { 0, NULL };
+
+#ifdef DB_CACHE
+ if (yp_access(NULL, NULL, (struct svc_req *)rqstp)) {
+#else
+ if (yp_access(NULL, (struct svc_req *)rqstp)) {
+#endif
+ result.stat = YP_YPERR;
+ return(&result);
+ }
+
+ if (argp == NULL) {
+ result.stat = YP_BADARGS;
+ return (&result);
+ }
+
+ if (yp_validdomain(*argp)) {
+ result.stat = YP_NODOM;
+ return (&result);
+ }
+
+ /*
+ * We have to construct a linked list for the ypproc_maplist
+ * procedure using dynamically allocated memory. Since the XDR
+ * layer won't free this list for us, we have to deal with it
+ * ourselves. We call yp_maplist_free() first to free any
+ * previously allocated data we may have accumulated to insure
+ * that we have only one linked list in memory at any given
+ * time.
+ */
+
+ yp_maplist_free(result.maps);
+
+ if ((result.maps = yp_maplist_create(*argp)) == NULL) {
+ yp_error("yp_maplist_create failed");
+ result.stat = YP_YPERR;
+ return(&result);
+ } else
+ result.stat = YP_TRUE;
+
+ return (&result);
+}
+
+/*
+ * NIS v1 support. The nullproc, domain and domain_nonack
+ * functions from v1 are identical to those in v2, so all
+ * we have to do is hand off to them.
+ *
+ * The other functions are mostly just wrappers around their v2
+ * counterparts. For example, for the v1 'match' procedure, we
+ * crack open the argument structure, make a request to the v2
+ * 'match' function, repackage the data into a v1 response and
+ * then send it on its way.
+ *
+ * Note that we don't support the pull, push and get procedures.
+ * There's little documentation available to show what they
+ * do, and I suspect they're meant largely for map transfers
+ * between master and slave servers.
+ */
+
+void *
+ypoldproc_null_1_svc(void *argp, struct svc_req *rqstp)
+{
+ return(ypproc_null_2_svc(argp, rqstp));
+}
+
+bool_t *
+ypoldproc_domain_1_svc(domainname *argp, struct svc_req *rqstp)
+{
+ return(ypproc_domain_2_svc(argp, rqstp));
+}
+
+bool_t *
+ypoldproc_domain_nonack_1_svc(domainname *argp, struct svc_req *rqstp)
+{
+ return (ypproc_domain_nonack_2_svc(argp, rqstp));
+}
+
+/*
+ * the 'match' procedure sends a response of type YPRESP_VAL
+ */
+ypresponse *
+ypoldproc_match_1_svc(yprequest *argp, struct svc_req *rqstp)
+{
+ static ypresponse result;
+ ypresp_val *v2_result;
+
+ result.yp_resptype = YPRESP_VAL;
+ result.ypresponse_u.yp_resp_valtype.val.valdat_val = "";
+ result.ypresponse_u.yp_resp_valtype.val.valdat_len = 0;
+
+ if (argp->yp_reqtype != YPREQ_KEY) {
+ result.ypresponse_u.yp_resp_valtype.stat = YP_BADARGS;
+ return(&result);
+ }
+
+ v2_result = ypproc_match_2_svc(&argp->yprequest_u.yp_req_keytype,rqstp);
+ if (v2_result == NULL)
+ return(NULL);
+
+ bcopy(v2_result, &result.ypresponse_u.yp_resp_valtype,
+ sizeof(ypresp_val));
+
+ return (&result);
+}
+
+/*
+ * the 'first' procedure sends a response of type YPRESP_KEY_VAL
+ */
+ypresponse *
+ypoldproc_first_1_svc(yprequest *argp, struct svc_req *rqstp)
+{
+ static ypresponse result;
+ ypresp_key_val *v2_result;
+
+ result.yp_resptype = YPRESP_KEY_VAL;
+ result.ypresponse_u.yp_resp_key_valtype.val.valdat_val =
+ result.ypresponse_u.yp_resp_key_valtype.key.keydat_val = "";
+ result.ypresponse_u.yp_resp_key_valtype.val.valdat_len =
+ result.ypresponse_u.yp_resp_key_valtype.key.keydat_len = 0;
+
+ if (argp->yp_reqtype != YPREQ_NOKEY) {
+ result.ypresponse_u.yp_resp_key_valtype.stat = YP_BADARGS;
+ return(&result);
+ }
+
+ v2_result = ypproc_first_2_svc(&argp->yprequest_u.yp_req_nokeytype,
+ rqstp);
+ if (v2_result == NULL)
+ return(NULL);
+
+ bcopy(v2_result, &result.ypresponse_u.yp_resp_key_valtype,
+ sizeof(ypresp_key_val));
+
+ return (&result);
+}
+
+/*
+ * the 'next' procedure sends a response of type YPRESP_KEY_VAL
+ */
+ypresponse *
+ypoldproc_next_1_svc(yprequest *argp, struct svc_req *rqstp)
+{
+ static ypresponse result;
+ ypresp_key_val *v2_result;
+
+ result.yp_resptype = YPRESP_KEY_VAL;
+ result.ypresponse_u.yp_resp_key_valtype.val.valdat_val =
+ result.ypresponse_u.yp_resp_key_valtype.key.keydat_val = "";
+ result.ypresponse_u.yp_resp_key_valtype.val.valdat_len =
+ result.ypresponse_u.yp_resp_key_valtype.key.keydat_len = 0;
+
+ if (argp->yp_reqtype != YPREQ_KEY) {
+ result.ypresponse_u.yp_resp_key_valtype.stat = YP_BADARGS;
+ return(&result);
+ }
+
+ v2_result = ypproc_next_2_svc(&argp->yprequest_u.yp_req_keytype,rqstp);
+ if (v2_result == NULL)
+ return(NULL);
+
+ bcopy(v2_result, &result.ypresponse_u.yp_resp_key_valtype,
+ sizeof(ypresp_key_val));
+
+ return (&result);
+}
+
+/*
+ * the 'poll' procedure sends a response of type YPRESP_MAP_PARMS
+ */
+ypresponse *
+ypoldproc_poll_1_svc(yprequest *argp, struct svc_req *rqstp)
+{
+ static ypresponse result;
+ ypresp_master *v2_result1;
+ ypresp_order *v2_result2;
+
+ result.yp_resptype = YPRESP_MAP_PARMS;
+ result.ypresponse_u.yp_resp_map_parmstype.domain =
+ argp->yprequest_u.yp_req_nokeytype.domain;
+ result.ypresponse_u.yp_resp_map_parmstype.map =
+ argp->yprequest_u.yp_req_nokeytype.map;
+ /*
+ * Hmm... there is no 'status' value in the
+ * yp_resp_map_parmstype structure, so I have to
+ * guess at what to do to indicate a failure.
+ * I hope this is right.
+ */
+ result.ypresponse_u.yp_resp_map_parmstype.ordernum = 0;
+ result.ypresponse_u.yp_resp_map_parmstype.peer = "";
+
+ if (argp->yp_reqtype != YPREQ_MAP_PARMS) {
+ return(&result);
+ }
+
+ v2_result1 = ypproc_master_2_svc(&argp->yprequest_u.yp_req_nokeytype,
+ rqstp);
+ if (v2_result1 == NULL)
+ return(NULL);
+
+ if (v2_result1->stat != YP_TRUE) {
+ return(&result);
+ }
+
+ v2_result2 = ypproc_order_2_svc(&argp->yprequest_u.yp_req_nokeytype,
+ rqstp);
+ if (v2_result2 == NULL)
+ return(NULL);
+
+ if (v2_result2->stat != YP_TRUE) {
+ return(&result);
+ }
+
+ result.ypresponse_u.yp_resp_map_parmstype.peer =
+ v2_result1->peer;
+ result.ypresponse_u.yp_resp_map_parmstype.ordernum =
+ v2_result2->ordernum;
+
+ return (&result);
+}
+
+ypresponse *
+ypoldproc_push_1_svc(yprequest *argp, struct svc_req *rqstp)
+{
+ static ypresponse result;
+
+ /*
+ * Not implemented.
+ */
+
+ return (&result);
+}
+
+ypresponse *
+ypoldproc_pull_1_svc(yprequest *argp, struct svc_req *rqstp)
+{
+ static ypresponse result;
+
+ /*
+ * Not implemented.
+ */
+
+ return (&result);
+}
+
+ypresponse *
+ypoldproc_get_1_svc(yprequest *argp, struct svc_req *rqstp)
+{
+ static ypresponse result;
+
+ /*
+ * Not implemented.
+ */
+
+ return (&result);
+}
diff --git a/usr.sbin/ypserv/yp_svc_udp.c b/usr.sbin/ypserv/yp_svc_udp.c
new file mode 100644
index 0000000..33d058e
--- /dev/null
+++ b/usr.sbin/ypserv/yp_svc_udp.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 1996
+ * 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 CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (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 <rpc/rpc.h>
+#include <rpc/svc_dg.h>
+#include "yp_extern.h"
+
+#define su_data(xprt) ((struct svc_dg_data *)(xprt->xp_p2))
+
+/*
+ * We need to be able to manually set the transaction ID in the
+ * UDP transport handle, but the standard library offers us no way
+ * to do that. Hence we need this garbage.
+ */
+
+unsigned long
+svcudp_get_xid(SVCXPRT *xprt)
+{
+ struct svc_dg_data *su;
+
+ if (xprt == NULL)
+ return(0);
+ su = su_data(xprt);
+ return(su->su_xid);
+}
+
+unsigned long
+svcudp_set_xid(SVCXPRT *xprt, unsigned long xid)
+{
+ struct svc_dg_data *su;
+ unsigned long old_xid;
+
+ if (xprt == NULL)
+ return(0);
+ su = su_data(xprt);
+ old_xid = su->su_xid;
+ su->su_xid = xid;
+ return(old_xid);
+}
diff --git a/usr.sbin/ypserv/ypinit.8 b/usr.sbin/ypserv/ypinit.8
new file mode 100644
index 0000000..c843a66
--- /dev/null
+++ b/usr.sbin/ypserv/ypinit.8
@@ -0,0 +1,200 @@
+.\" Copyright (c) 1997
+.\" 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd August 18, 2015
+.Dt YPINIT 8
+.Os
+.Sh NAME
+.Nm ypinit
+.Nd build and install NIS databases
+.Sh SYNOPSIS
+.Nm
+.Fl m
+.Op Ar domainname
+.Nm
+.Fl s
+.Ar master_server
+.Op Ar domainname
+.Nm
+.Fl u
+.Op Ar domainname
+.Sh DESCRIPTION
+The
+.Nm
+utility is a script which sets up databases on a Network Information Service
+(NIS)
+master or slave server.
+.Pp
+On a master server,
+.Nm
+creates the
+.Pa /var/yp/$DOMAINNAME
+directory, the
+.Pa /var/yp/ypservers
+file, and calls
+.Pa /var/yp/Makefile
+to create and populate an initial set of NIS maps.
+The maps are
+created from local source files using the
+.Xr yp_mkdb 8
+utility.
+The utility will prompt the user for a list of servers
+that support the specified domain; this list is used to populate
+the ypservers map.
+.Pp
+On a slave server,
+.Nm
+creates the
+.Pa /var/yp/$DOMAINNAME ,
+populates it with copies of the NIS maps from the master.
+The maps
+are obtained from the master using the
+.Xr ypxfr 8
+utility.
+The
+.Nm
+utility obtains the list of maps to transfer in one of two ways: if
+the system is configured as an NIS client and is bound to the master
+server,
+.Nm
+is able to use the
+.Xr ypwhich 1
+utility to obtain a list of maps exported by the master server.
+If the system is not configured as a client of the NIS master,
+.Nm
+uses a hardcoded list of maps, some of which may or may not actually
+exist on the master.
+The system administrator can edit the
+.Nm
+script and
+modify the map list if necessary.
+Otherwise, individual maps can
+be transferred manually from the master using
+.Xr ypxfr 8 .
+.Sh OPTIONS
+The
+.Nm
+utility supports the following options:
+.Bl -tag -width indent
+.It Fl m Op Ar domainname
+Set up a master server.
+By default,
+.Nm
+sets up a server for
+the system default domain.
+The user can override this default by specifying
+.Ar domainname
+explicitly.
+Maps are constructed from scratch using local files as templates using
+the
+.Xr yp_mkdb 8
+utility.
+.It Fl s Ar master_server Op Ar domainname
+Set up a slave server using
+.Ar master_name
+as the master.
+Maps are copied from
+.Ar master_server
+to the slave using
+.Xr ypxfr 8 .
+By default,
+.Nm
+sets up a server for
+the system default domain.
+The user can override this default by specifying
+.Ar domainname
+explicitly.
+.It Fl u Op Ar domainname
+Update the ypservers map on the master server.
+When a new slave
+server is added to a domain, its hostname must be added to the
+ypservers map so that
+.Xr yppush 8
+can propagate updates on the master to all of the slaves.
+.El
+.Sh FILES
+.Bl -tag -width /var/yp/master.passwd -compact
+.It Pa /etc/bootparams
+Bootparams source file
+.It Pa /etc/ethers
+Ethers data source file
+.It Pa /etc/eui64
+EUI64 data source file
+.It Pa /etc/group
+Group source file
+.It Pa /etc/hosts
+Hostname/IP address source file
+.It Pa /etc/netid
+RPC netid source file
+.It Pa /etc/networks
+Networks source file
+.It Pa /etc/protocols
+Protocols source file
+.It Pa /etc/publickey
+RPC public key/secret key source file
+.It Pa /etc/services
+Services data source file
+.It Pa /etc/shells
+Shells source file
+.It Pa /var/yp/master.passwd
+Passwd database source file
+.It Pa /var/yp/netgroup
+Netgroup data source file
+.It Pa /var/yp/ypservers
+Ypservers source file (generated by
+.Nm )
+.El
+.Sh SEE ALSO
+.Xr mknetid 8 ,
+.Xr revnetgroup 8 ,
+.Xr yp 8 ,
+.Xr yp_mkdb 8 ,
+.Xr yppush 8 ,
+.Xr ypserv 8 ,
+.Xr ypxfr 8
+.Sh HISTORY
+This version of
+.Nm
+is based on the
+.Nm
+script in
+.Ox .
+It first appeared in
+.Fx 3.0 .
+.Sh AUTHORS
+.An -nosplit
+The original script was written by
+.An Mats O Jansson Aq Mt moj@stacken.kth.se .
+It was modified for
+.Fx
+by
+.An Bill Paul Aq Mt wpaul@ctr.columbia.edu .
diff --git a/usr.sbin/ypserv/ypinit.sh b/usr.sbin/ypserv/ypinit.sh
new file mode 100644
index 0000000..5008bcf
--- /dev/null
+++ b/usr.sbin/ypserv/ypinit.sh
@@ -0,0 +1,387 @@
+#!/bin/sh
+# $FreeBSD$
+#
+# ypinit.sh - setup a master or slave server.
+# (Taken from OpenBSD and modified for FreeBSD.)
+#
+DOMAINNAME=/bin/domainname
+HOSTNAME=/bin/hostname
+YPWHICH=/usr/bin/ypwhich
+YPXFR=/usr/libexec/ypxfr
+YP_DIR=/var/yp
+MAKEDBM=/usr/sbin/yp_mkdb
+MAPLIST="master.passwd.byname master.passwd.byuid passwd.byname passwd.byuid \
+ group.byname group.bygid hosts.byname hosts.byaddr services.byname \
+ rpc.byname rpc.bynumber networks.byname networks.byaddr netgroup \
+ netgroup.byuser netgroup.byhost netid.byname publickey.byname \
+ bootparams ethers.byname ethers.byaddr eui64.byname eui64.byid \
+ amd.host mail.aliases ypservers protocols.byname protocols.bynumber \
+ netmasks.byaddr"
+
+ERROR_EXISTS="NO"
+umask 077
+
+#set -xv
+
+ERROR=USAGE # assume usage error
+
+if [ $# -eq 1 ]
+then
+ if [ $1 = "-m" ] # ypinit -m
+ then
+ DOMAIN=`${DOMAINNAME}`
+ SERVERTYPE=MASTER
+ ERROR=
+ fi
+
+ if [ $1 = "-u" ] # ypinit -u
+ then
+ DOMAIN=`${DOMAINNAME}`
+ SERVERTYPE=UPDATE
+ ERROR=
+ fi
+fi
+
+if [ $# -eq 2 ]
+then
+ if [ $1 = "-m" ] # ypinit -m domainname
+ then
+ DOMAIN=${2}
+ SERVERTYPE=MASTER
+ ERROR=
+ fi
+
+ if [ $1 = "-s" ] # ypinit -s master_server
+ then
+ DOMAIN=`${DOMAINNAME}`
+ SERVERTYPE=SLAVE
+ MASTER=${2}
+ ERROR=
+ fi
+
+ if [ $1 = "-u" ] # ypinit -u domainname
+ then
+ DOMAIN=${2}
+ SERVERTYPE=UPDATE
+ ERROR=
+ fi
+fi
+
+if [ $# -eq 3 ]
+then
+ if [ $1 = "-s" ] # ypinit -s master_server domainname
+ then
+ DOMAIN=${3}
+ SERVERTYPE=SLAVE
+ MASTER=${2}
+ ERROR=
+ fi
+fi
+
+if [ "${ERROR}" = "USAGE" ]; then
+ cat << \__usage 1>&2
+usage: ypinit -m [domainname]
+ ypinit -s master_server [domainname]
+ ypinit -u [domainname]
+
+The `-m' flag builds a master YP server, and the `-s' flag builds
+a slave YP server. When building a slave YP server, `master_server'
+must be an existing, reachable YP server.
+The `-u' is for updating the ypservers map on a master server.
+__usage
+
+ exit 1
+fi
+
+# Check if domainname is set, don't accept an empty domainname
+if [ -z "${DOMAIN}" ]; then
+ cat << \__no_domain 1>&2
+The local host's YP domain name has not been set. Please set it with
+the domainname(1) command or pass the domain as an argument to ypinit(8).
+__no_domain
+
+ exit 1
+fi
+
+# Check if hostname is set, don't accept an empty hostname
+HOST=`${HOSTNAME}`
+if [ -z "${HOST}" ]; then
+ cat << \__no_hostname 1>&2
+The local host's hostname has not been set. Please set it with the
+hostname(1) command.
+__no_hostname
+
+ exit 1
+fi
+
+# Check if we have contact with master.
+# If we can't list the maps on the master, then we fake it with a
+# hard-coded list of maps. The FreeBSD ypxfr command will work even
+# if ypbind isn't running or if we are bound to ourselves instead of
+# the master (the slave should be bound to itself, but since it has
+# no maps yet, we can't get a maplist from it).
+if [ "${SERVERTYPE}" = "SLAVE" ];
+then
+ COUNT=`${YPWHICH} -d ${DOMAIN} -m 2>/dev/null | grep -i ${MASTER} | wc -l | tr -d " "`
+ if [ "$COUNT" = "0" ]
+ then
+ echo "Can't enumerate maps from ${MASTER}. Please check that it is running." 1>&2
+ echo "Note: using hardcoded maplist for map transfers." 1>&2
+ YPMAPLIST=${MAPLIST}
+ else
+ YPMAPLIST=`${YPWHICH} -d ${DOMAIN} -m | cut -d\ -f1`
+ fi
+ echo "" 1>&2
+fi
+
+# Check if user is root
+ID=`id -u`
+if [ "${ID}" != "0" ]; then
+ echo "You have to be the superuser to run this. Please login as root." 1>&2
+ exit 1
+fi
+
+# Check if the YP directory exists.
+
+if [ ! -d ${YP_DIR} -o -f ${YP_DIR} ]
+then
+ echo "The directory ${YP_DIR} doesn't exist. Restore it from the distribution." 1>&2
+ exit 1
+
+fi
+
+echo -n "Server Type: ${SERVERTYPE} Domain: ${DOMAIN}"
+if [ "${SERVERTYPE}" = "SLAVE" ]; then
+ echo -n " Master: ${MASTER}"
+fi
+echo ""
+
+if [ "${SERVERTYPE}" != "UPDATE" ];
+then
+ cat << \__notice1
+
+Creating an YP server will require that you answer a few questions.
+Questions will all be asked at the beginning of the procedure.
+
+__notice1
+
+ echo -n "Do you want this procedure to quit on non-fatal errors? [y/n: n] "
+ read DOEXIT
+
+ case ${DOEXIT} in
+ y*|Y*)
+ ERROR_EXIT="YES"
+ ;;
+
+ *) ERROR_EXIT="NO"
+ echo ""
+ echo "Ok, please remember to go back and redo manually whatever fails."
+ echo "If you don't, something might not work. "
+ ;;
+ esac
+
+ if [ -d "${YP_DIR}/${DOMAIN}" ]; then
+ echo ""
+ echo -n "Can we destroy the existing ${YP_DIR}/${DOMAIN} and its contents? [y/n: n] "
+ read KILL
+
+ ERROR=
+ case ${KILL} in
+ y*|Y*)
+ ERROR="DELETE"
+ ;;
+
+ *) ERROR=
+ ;;
+ esac
+
+ if [ "${ERROR}" = "DELETE" ]; then
+ if ! rm -rf ${YP_DIR}/${DOMAIN}; then
+ echo "Can't clean up old directory ${YP_DIR}/${DOMAIN}." 1>&2
+ exit 1
+ fi
+ else
+ echo "OK, please clean it up by hand and start again. Bye"
+ exit 0
+ fi
+ fi
+
+ if ! mkdir "${YP_DIR}/${DOMAIN}"; then
+ echo "Can't make new directory ${YP_DIR}/${DOMAIN}." 1>&2
+ exit 1
+ fi
+fi
+
+if [ "${SERVERTYPE}" = "MASTER" ];
+then
+
+ if [ ! -f ${YP_DIR}/Makefile ]
+ then
+ if [ ! -f ${YP_DIR}/Makefile.dist ]
+ then
+ echo "Can't find ${YP_DIR}/Makefile.dist. " 1>&2
+ exit 1
+ fi
+ cp ${YP_DIR}/Makefile.dist ${YP_DIR}/Makefile
+ fi
+
+fi
+
+if [ "${SERVERTYPE}" = "SLAVE" ];
+then
+
+ echo "There will be no further questions. The remainder of the procedure"
+ echo "should take a few minutes, to copy the databases from ${MASTER}."
+
+ for MAP in ${YPMAPLIST}
+ do
+ echo "Transferring ${MAP}..."
+ if ! ${YPXFR} -p ${YP_DIR} -h ${MASTER} -c -d ${DOMAIN} ${MAP}; then
+ echo "Can't transfer map ${MAP}." 1>&2
+ ERROR_EXISTS="YES"
+ if [ "${ERROR_EXIT}" = "YES" ]; then
+ exit 1
+ fi
+ fi
+ done
+
+ echo ""
+ if [ "${ERROR_EXISTS}" = "YES" ]; then
+ echo "${HOST} has been setup as an YP slave server with errors. " 1>&2
+ echo "Please remember fix any problem that occurred." 1>&2
+ else
+ echo "${HOST} has been setup as an YP slave server without any errors. "
+ fi
+
+ echo "Don't forget to update map ypservers on ${MASTER}."
+ exit 0
+fi
+
+LIST_OK="NO"
+
+while [ "${LIST_OK}" = "NO" ];
+do
+ if [ "${SERVERTYPE}" = "MASTER" ];
+ then
+ HOST_LIST="${HOST}"
+ echo ""
+ echo "At this point, we have to construct a list of this domains YP servers."
+ echo "${HOST} is already known as master server."
+ echo "Please continue to add any slave servers, one per line. When you are"
+ echo "done with the list, type a <control D>."
+ echo " master server : ${HOST}"
+ fi
+
+ if [ "${SERVERTYPE}" = "UPDATE" ];
+ then
+ HOST_LIST="${HOST}"
+ NEW_LIST=""
+ MASTER_NAME=""
+ SHORT_HOST=`echo ${HOST} | cut -d. -f1`
+ if [ -f ${YP_DIR}/${DOMAIN}/ypservers ];
+ then
+ for srv in `${MAKEDBM} -u ${YP_DIR}/${DOMAIN}/ypservers | grep -v "^YP" | tr "\t" " " | cut -d\ -f1`;
+ do
+ short_srv=`echo ${srv} | cut -d. -f1`
+ if [ "${SHORT_HOST}" != "${short_srv}" ]
+ then
+ if [ "${NEW_LIST}" = "" ];
+ then
+ NEW_LIST="${srv}"
+ else
+ NEW_LIST="${NEW_LIST} ${srv}"
+ fi
+ fi
+ done;
+ MASTER_NAME=`${MAKEDBM} -u ${YP_DIR}/${DOMAIN}/ypservers | grep "^YP_MASTER_NAME" | tr "\t" " " | cut -d\ -f2`
+ fi
+ echo ""
+ echo "Update the list of hosts running YP servers in domain ${DOMAIN}."
+ echo "Master for this domain is ${MASTER_NAME}."
+ echo ""
+ echo "First verify old servers, type \\\\ to remove a server."
+ echo "Then add new servers, one per line. When done type a <control D>."
+ echo ""
+ echo " master server : ${HOST}"
+ if [ "${NEW_LIST}" != "" ]; then
+ for node in $NEW_LIST; do
+ echo -n " verify host : [${node}] "
+ read verify
+ if [ "${verify}" != "\\" ]; then
+ HOST_LIST="${HOST_LIST} ${node}"
+ fi
+ done;
+ fi
+ fi
+
+ echo -n " next host to add: "
+
+ while read h
+ do
+ echo -n " next host to add: "
+ HOST_LIST="${HOST_LIST} ${h}"
+ done
+
+ echo ""
+ echo "The current list of NIS servers looks like this:"
+ echo ""
+
+ for h in `echo ${HOST_LIST}`;
+ do
+ echo ${h}
+ done
+
+ echo ""
+ echo -n "Is this correct? [y/n: y] "
+ read hlist_ok
+
+ case $hlist_ok in
+ n*) echo "Let's try the whole thing again...";;
+ N*) echo "Let's try the whole thing again...";;
+ *) LIST_OK="YES";;
+ esac
+
+done
+
+echo "Building ${YP_DIR}/${DOMAIN}/ypservers..."
+rm -f ${YP_DIR}/ypservers
+touch -f ${YP_DIR}/ypservers
+rm -f ${YP_DIR}/${DOMAIN}/ypservers
+for host in ${HOST_LIST};
+do
+ echo "${host} ${host}" >> ${YP_DIR}/ypservers
+ echo "${host} ${host}"
+done | ${MAKEDBM} - ${YP_DIR}/${DOMAIN}/ypservers
+
+if [ $? -ne 0 ]; then
+ echo "" 1>&2
+ echo "Couldn't build yp data base ${YP_DIR}/${DOMAIN}/ypservers." 1>&2
+ ERROR_EXISTS="YES"
+ if [ "${ERROR_EXIT}" = "YES" ]; then
+ exit 1
+ fi
+fi
+
+if [ "${SERVERTYPE}" = "MASTER" ]; then
+ CUR_PWD=`pwd`
+ cd ${YP_DIR}
+ echo "Running ${YP_DIR}/Makefile..."
+ if ! make NOPUSH=True UPDATE_DOMAIN=${DOMAIN} YP_DIR=${YP_DIR}; then
+ echo "" 1>&2
+ echo "Error running Makefile." 1>&2
+ ERROR_EXISTS="YES"
+ if [ "${ERROR_EXIT}" = "YES" ]; then
+ exit 1
+ fi
+ fi
+
+ cd ${CUR_PWD}
+
+ echo ""
+ if [ "${ERROR_EXISTS}" = "YES" ]; then
+ echo "${HOST} has been setup as an YP master server with errors. " 1>&2
+ echo "Please remember fix any problem that occurred." 1>&2
+ else
+ echo "${HOST} has been setup as an YP master server without any errors. "
+ fi
+fi
diff --git a/usr.sbin/ypserv/ypserv.8 b/usr.sbin/ypserv/ypserv.8
new file mode 100644
index 0000000..f255118
--- /dev/null
+++ b/usr.sbin/ypserv/ypserv.8
@@ -0,0 +1,463 @@
+.\" Copyright (c) 1995
+.\" 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 CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (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 13, 2009
+.Dt YPSERV 8
+.Os
+.Sh NAME
+.Nm ypserv
+.Nd NIS database server
+.Sh SYNOPSIS
+.Nm
+.Op Fl n
+.Op Fl d
+.Op Fl P Ar port
+.Op Fl p Ar path
+.Sh DESCRIPTION
+.Tn NIS
+is an RPC-based service designed to allow a number of UNIX-based
+machines to share a common set of configuration files.
+Rather than
+requiring a system administrator to update several copies of files
+such as
+.Pa /etc/hosts ,
+.Pa /etc/passwd
+and
+.Pa /etc/group ,
+which tend to require frequent changes in most environments,
+.Tn NIS
+allows groups of computers to share one set of data which can be
+updated from a single location.
+.Pp
+The
+.Nm
+utility is the server that distributes
+.Tn NIS
+databases to client systems within an
+.Tn NIS
+.Em domain .
+Each client in an
+.Tn NIS
+domain must have its domainname set to
+one of the domains served by
+.Nm
+using the
+.Xr domainname 1
+command.
+The clients must also run
+.Xr ypbind 8
+in order to attach to a particular server, since it is possible to
+have several servers within a single
+.Tn NIS
+domain.
+.Pp
+The databases distributed by
+.Nm
+are stored in
+.Pa /var/yp/[domainname]
+where
+.Pa domainname
+is the name of the domain being served.
+There can be several
+such directories with different domainnames, and you need only one
+.Nm
+daemon to handle them all.
+.Pp
+The databases, or
+.Pa maps
+as they are often called,
+are created by
+.Pa /var/yp/Makefile
+using several system files as source.
+The database files are in
+.Xr db 3
+format to help speed retrieval when there are many records involved.
+In
+.Fx ,
+the maps are always readable and writable only by root for security
+reasons.
+Technically this is only necessary for the password
+maps, but since the data in the other maps can be found in
+other world-readable files anyway, it does not hurt and it is considered
+good general practice.
+.Pp
+The
+.Nm
+utility is started by
+.Pa /etc/rc.d/ypserv
+if it has been enabled in
+.Pa /etc/rc.conf .
+.Sh SPECIAL FEATURES
+There are some problems associated with distributing a
+.Fx
+password
+database via
+.Tn NIS :
+.Fx
+normally only stores encrypted passwords
+in
+.Pa /etc/master.passwd ,
+which is readable and writable only by root.
+By turning this file
+into an
+.Tn NIS
+map, this security feature would be completely defeated.
+.Pp
+To make up for this, the
+.Fx
+version of
+.Nm
+handles the
+.Pa master.passwd.byname
+and
+.Pa master.passwd.byuid
+maps in a special way.
+When the server receives a request to access
+either of these two maps (or in fact either of the
+.Pa shadow.byname
+or
+.Pa shadow.byuid
+maps), it will check the TCP port from which the
+request originated and return an error if the port number is greater
+than 1023.
+Since only the superuser is allowed to bind to TCP ports
+with values less than 1024, the server can use this test to determine
+whether or not the access request came from a privileged user.
+Any requests made by non-privileged users are therefore rejected.
+.Pp
+Furthermore, the
+.Xr getpwent 3
+routines in the
+.Fx
+standard C library will only attempt to retrieve
+data from the
+.Pa master.passwd.byname
+and
+.Pa master.passwd.byuid
+maps for the superuser: if a normal user calls any of these functions,
+the standard
+.Pa passwd.byname
+and
+.Pa passwd.byuid
+maps will be accessed instead.
+The latter two maps are constructed by
+.Pa /var/yp/Makefile
+by parsing the
+.Pa master.passwd
+file and stripping out the password fields, and are therefore
+safe to pass on to unprivileged users.
+In this way, the shadow password
+aspect of the protected
+.Pa master.passwd
+database is maintained through
+.Tn NIS .
+.Sh NOTES
+.Ss Setting Up Master and Slave Servers
+.Xr ypinit 8
+is a convenient script that will help setup master and slave
+.Tn NIS
+servers.
+.Ss Limitations
+There are two problems inherent with password shadowing in
+.Tn NIS
+that users should
+be aware of:
+.Bl -enum -offset indent
+.It
+The
+.Sq TCP port less than 1024
+test is trivial to defeat for users with
+unrestricted access to machines on your network (even those machines
+which do not run UNIX-based operating systems).
+.It
+If you plan to use a
+.Fx
+system to serve
+.No non- Ns Fx
+clients that
+have no support for password shadowing (which is most of them), you
+will have to disable the password shadowing entirely by uncommenting the
+.Em UNSECURE=True
+entry in
+.Pa /var/yp/Makefile .
+This will cause the standard
+.Pa passwd.byname
+and
+.Pa passwd.byuid
+maps to be generated with valid encrypted password fields, which is
+necessary in order for
+.No non- Ns Fx
+clients to perform user
+authentication through
+.Tn NIS .
+.El
+.Ss Security
+In general, any remote user can issue an RPC to
+.Nm
+and retrieve the contents of your
+.Tn NIS
+maps, provided the remote user
+knows your domain name.
+To prevent such unauthorized transactions,
+.Nm
+supports a feature called
+.Pa securenets
+which can be used to restrict access to a given set of hosts.
+At startup,
+.Nm
+will attempt to load the securenets information from a file
+called
+.Pa /var/yp/securenets .
+(Note that this path varies depending on the path specified with
+the
+.Fl p
+option, which is explained below.)
+This file contains entries
+that consist of a network specification and a network mask separated
+by white space.
+Lines starting with
+.Dq \&#
+are considered to be comments.
+A
+sample securenets file might look like this:
+.Bd -unfilled -offset indent
+# allow connections from local host -- mandatory
+127.0.0.1 255.255.255.255
+# allow connections from any host
+# on the 192.168.128.0 network
+192.168.128.0 255.255.255.0
+# allow connections from any host
+# between 10.0.0.0 to 10.0.15.255
+10.0.0.0 255.255.240.0
+.Ed
+.Pp
+If
+.Nm
+receives a request from an address that matches one of these rules,
+it will process the request normally.
+If the address fails to match
+a rule, the request will be ignored and a warning message will be
+logged.
+If the
+.Pa /var/yp/securenets
+file does not exist,
+.Nm
+will allow connections from any host.
+.Pp
+The
+.Nm
+utility also has support for Wietse Venema's
+.Em tcpwrapper
+package.
+This allows the administrator to use the tcpwrapper
+configuration files
+.Pa ( /etc/hosts.allow
+and
+.Pa /etc/hosts.deny )
+for access control instead of
+.Pa /var/yp/securenets .
+.Pp
+Note: while both of these access control mechanisms provide some
+security, they, like the privileged port test, are both vulnerable
+to
+.Dq IP spoofing
+attacks.
+.Ss NIS v1 compatibility
+This version of
+.Nm
+has some support for serving
+.Tn NIS
+v1 clients.
+The
+.Fx
+.Tn NIS
+implementation only uses the
+.Tn NIS
+v2 protocol, however other implementations
+include support for the v1 protocol for backwards compatibility
+with older systems.
+The
+.Xr ypbind 8
+daemons supplied with these systems will try to establish a binding
+to an
+.Tn NIS
+v1 server even though they may never actually need it (and they may
+persist in broadcasting in search of one even after they receive a
+response from a v2 server).
+Note that while
+support for normal client calls is provided, this version of
+.Nm
+does not handle v1 map transfer requests; consequently, it cannot
+be used as a master or slave in conjunction with older
+.Tn NIS
+servers that
+only support the v1 protocol.
+Fortunately, there probably are not any
+such servers still in use today.
+.Ss NIS servers that are also NIS clients
+Care must be taken when running
+.Nm
+in a multi-server domain where the server machines are also
+.Tn NIS
+clients.
+It is generally a good idea to force the servers to
+bind to themselves rather than allowing them to broadcast bind
+requests and possibly become bound to each other: strange failure
+modes can result if one server goes down and
+others are dependent upon on it.
+(Eventually all the clients will
+time out and attempt to bind to other servers, but the delay
+involved can be considerable and the failure mode is still present
+since the servers might bind to each other all over again).
+.Pp
+Refer to the
+.Xr ypbind 8
+man page for details on how to force it to bind to a particular
+server.
+.Sh OPTIONS
+The following options are supported by
+.Nm :
+.Bl -tag -width flag
+.It Fl n
+This option affects the way
+.Nm
+handles yp_match requests for the
+.Pa hosts.byname
+and
+.Pa hosts.byaddress
+maps.
+By default, if
+.Nm
+cannot find an entry for a given host in its hosts maps, it will
+return an error and perform no further processing.
+With the
+.Fl n
+flag,
+.Nm
+will go one step further: rather than giving up immediately, it
+will try to resolve the hostname or address using a DNS nameserver
+query.
+If the query is successful,
+.Nm
+will construct a fake database record and return it to the client,
+thereby making it seem as though the client's yp_match request
+succeeded.
+.Pp
+This feature is provided for compatibility with SunOS 4.1.x,
+which has brain-damaged resolver functions in its standard C
+library that depend on
+.Tn NIS
+for hostname and address resolution.
+The
+.Fx
+resolver can be configured to do DNS
+queries directly, therefore it is not necessary to enable this
+option when serving only
+.Fx
+.Tn NIS
+clients.
+.It Fl d
+Cause the server to run in debugging mode.
+Normally,
+.Nm
+reports only unusual errors (access violations, file access failures)
+using the
+.Xr syslog 3
+facility.
+In debug mode, the server does not background
+itself and prints extra status messages to stderr for each
+request that it receives.
+Also, while running in debug mode,
+.Nm
+will not spawn any additional subprocesses as it normally does
+when handling yp_all requests or doing DNS lookups.
+(These actions
+often take a fair amount of time to complete and are therefore handled
+in subprocesses, allowing the parent server process to go on handling
+other requests.)
+This makes it easier to trace the server with
+a debugging tool.
+.It Fl h Ar addr
+Specify a specific address to bind to for requests. This option may be
+specified multiple times. If no
+.Fl h
+option is specified,
+.Nm
+will bind to default passive address
+.Pq e.g. INADDR_ANY for IPv4
+for each transport.
+.It Fl P Ar port
+Force ypserv to bind to a specific TCP/UDP port, rather than selecting
+its own.
+.It Fl p Ar path
+Normally,
+.Nm
+assumes that all
+.Tn NIS
+maps are stored under
+.Pa /var/yp .
+The
+.Fl p
+flag may be used to specify an alternate
+.Tn NIS
+root path, allowing
+the system administrator to move the map files to a different place
+within the file system.
+.El
+.Sh FILES
+.Bl -tag -width Pa -compact
+.It Pa /var/yp/[domainname]/[maps]
+the
+.Tn NIS
+maps
+.It Pa /etc/nsswitch.conf
+name switch configuration file
+.It Pa /var/yp/securenets
+host access control file
+.El
+.Sh SEE ALSO
+.Xr ypcat 1 ,
+.Xr db 3 ,
+.Xr hosts_access 5 ,
+.Xr rpc.yppasswdd 8 ,
+.Xr yp 8 ,
+.Xr ypbind 8 ,
+.Xr ypinit 8 ,
+.Xr yppush 8 ,
+.Xr ypxfr 8
+.Sh HISTORY
+This version of
+.Nm
+first appeared in
+.Fx 2.2 .
+.Sh AUTHORS
+.An Bill Paul Aq Mt wpaul@ctr.columbia.edu
diff --git a/usr.sbin/ypset/Makefile b/usr.sbin/ypset/Makefile
new file mode 100644
index 0000000..708ff87
--- /dev/null
+++ b/usr.sbin/ypset/Makefile
@@ -0,0 +1,9 @@
+# from: @(#)Makefile 5.8 (Berkeley) 7/28/90
+# $FreeBSD$
+
+PROG= ypset
+MAN= ypset.8
+
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ypset/Makefile.depend b/usr.sbin/ypset/Makefile.depend
new file mode 100644
index 0000000..c0b7a14
--- /dev/null
+++ b/usr.sbin/ypset/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/rpc \
+ include/rpcsvc \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ypset/ypset.8 b/usr.sbin/ypset/ypset.8
new file mode 100644
index 0000000..2590095
--- /dev/null
+++ b/usr.sbin/ypset/ypset.8
@@ -0,0 +1,88 @@
+.\"
+.\" Copyright (c) 1994 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 by Jason Thorpe.
+.\" 4. Neither the name of the author 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 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 September 3, 2015
+.Dt YPSET 8
+.Os
+.Sh NAME
+.Nm ypset
+.Nd tell
+.Xr ypbind 8
+which NIS server process to use
+.Sh SYNOPSIS
+.Nm
+.Op Fl h Ar host
+.Op Fl d Ar domain
+.Ar server
+.Sh DESCRIPTION
+The
+.Nm
+utility tells the
+.Xr ypbind 8
+process on the current machine which NIS server process to communicate with.
+If
+.Ar server
+is down or is not running a NIS server process, it is not discovered until
+a NIS client process attempts to access a NIS map, at which time
+.Xr ypbind 8
+tests the binding and takes appropriate action.
+.Pp
+The
+.Nm
+utility
+is most useful for binding a NIS client that is not on the same broadcast
+network as the closest NIS server, but can also be used for debugging
+a local network's NIS configuration, testing specific NIS client
+programs, or binding to a specific server when there are many servers on
+the local network supplying NIS maps.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl d Ar domain
+Use the NIS domain
+.Ar domain
+instead of the default domain as returned by
+.Xr domainname 1 .
+.It Fl h Ar host
+Set the NIS binding on
+.Ar host
+instead of the local machine.
+.El
+.Sh SEE ALSO
+.Xr domainname 1 ,
+.Xr ypcat 1 ,
+.Xr ypmatch 1 ,
+.Xr yp 8 ,
+.Xr ypbind 8
+.Sh AUTHORS
+.An Theo De Raadt
diff --git a/usr.sbin/ypset/ypset.c b/usr.sbin/ypset/ypset.c
new file mode 100644
index 0000000..c156661
--- /dev/null
+++ b/usr.sbin/ypset/ypset.c
@@ -0,0 +1,149 @@
+/* $OpenBSD: ypset.c,v 1.20 2015/01/16 06:40:23 deraadt Exp $ */
+/* $NetBSD: ypset.c,v 1.8 1996/05/13 02:46:33 thorpej Exp $ */
+
+/*
+ * Copyright (c) 1992, 1993 Theo de Raadt <deraadt@theos.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 ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED 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/types.h>
+#include <sys/socket.h>
+
+#include <err.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <rpc/rpc.h>
+#include <rpc/xdr.h>
+#include <rpcsvc/yp.h>
+#include <rpcsvc/ypclnt.h>
+#include <arpa/inet.h>
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: ypset [-d domain] [-h host] server\n");
+ exit(1);
+}
+
+static int
+bind_tohost(struct sockaddr_in *sin, char *dom, char *server)
+{
+ struct ypbind_setdom ypsd;
+ struct in_addr iaddr;
+ struct hostent *hp;
+ struct timeval tv;
+ CLIENT *client;
+ int sock, port, r;
+
+ port = getrpcport(server, YPPROG, YPPROC_NULL, IPPROTO_UDP);
+ if (port == 0)
+ errx(1, "%s not running ypserv", server);
+ port = htons(port);
+
+ memset(&ypsd, 0, sizeof ypsd);
+
+ if (inet_aton(server, &iaddr) == 0) {
+ hp = gethostbyname(server);
+ if (hp == NULL)
+ errx(1, "can't find address for %s", server);
+ memmove(&iaddr.s_addr, hp->h_addr, sizeof(iaddr.s_addr));
+ }
+ ypsd.ypsetdom_domain = dom;
+ bcopy(&iaddr.s_addr, &ypsd.ypsetdom_binding.ypbind_binding_addr,
+ sizeof(ypsd.ypsetdom_binding.ypbind_binding_addr));
+ bcopy(&port, &ypsd.ypsetdom_binding.ypbind_binding_port,
+ sizeof(ypsd.ypsetdom_binding.ypbind_binding_port));
+ ypsd.ypsetdom_vers = YPVERS;
+
+ tv.tv_sec = 15;
+ tv.tv_usec = 0;
+ sock = RPC_ANYSOCK;
+ client = clntudp_create(sin, YPBINDPROG, YPBINDVERS, tv, &sock);
+ if (client == NULL) {
+ warnx("can't yp_bind, reason: %s", yperr_string(YPERR_YPBIND));
+ return (YPERR_YPBIND);
+ }
+ client->cl_auth = authunix_create_default();
+
+ r = clnt_call(client, YPBINDPROC_SETDOM,
+ (xdrproc_t)xdr_ypbind_setdom, &ypsd,
+ (xdrproc_t)xdr_void, NULL, tv);
+ if (r) {
+ warnx("cannot ypset for domain %s on host %s: %s"
+ " - make sure ypbind was started with -ypset or -ypsetme", dom,
+ server, clnt_sperrno(r));
+ clnt_destroy(client);
+ return (YPERR_YPBIND);
+ }
+ clnt_destroy(client);
+ return (0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sockaddr_in sin;
+ struct hostent *hent;
+ char *domainname;
+ int c;
+
+ yp_get_default_domain(&domainname);
+
+ bzero(&sin, sizeof sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ while ((c = getopt(argc, argv, "h:d:")) != -1)
+ switch (c) {
+ case 'd':
+ domainname = optarg;
+ break;
+ case 'h':
+ if (inet_aton(optarg, &sin.sin_addr) == 0) {
+ hent = gethostbyname(optarg);
+ if (hent == NULL)
+ errx(1, "host %s unknown\n", optarg);
+ bcopy(hent->h_addr, &sin.sin_addr,
+ sizeof(sin.sin_addr));
+ }
+ break;
+ default:
+ usage();
+ }
+
+ if (optind + 1 != argc)
+ usage();
+
+ if (bind_tohost(&sin, domainname, argv[optind]))
+ exit(1);
+ exit(0);
+}
diff --git a/usr.sbin/zic/Makefile b/usr.sbin/zic/Makefile
new file mode 100644
index 0000000..9d699a8
--- /dev/null
+++ b/usr.sbin/zic/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+# Vendor contact: tz@elsie.nci.nih.gov
+
+SUBDIR= zic zdump
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/zic/Makefile.inc b/usr.sbin/zic/Makefile.inc
new file mode 100644
index 0000000..b915adf
--- /dev/null
+++ b/usr.sbin/zic/Makefile.inc
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+.include "${.CURDIR}/../../Makefile.inc"
diff --git a/usr.sbin/zic/README b/usr.sbin/zic/README
new file mode 100644
index 0000000..5cb701e
--- /dev/null
+++ b/usr.sbin/zic/README
@@ -0,0 +1,88 @@
+@(#)README 8.3
+This file is in the public domain, so clarified as of
+2009-05-17 by Arthur David Olson.
+
+$FreeBSD$
+
+"What time is it?" -- Richard Deacon as The King
+"Any time you want it to be." -- Frank Baxter as The Scientist
+ (from the Bell System film "About Time")
+
+The 1989 update of the time zone package featured
+
+* POSIXization (including interpretation of POSIX-style TZ environment
+ variables, provided by Guy Harris),
+* ANSIfication (including versions of "mktime" and "difftime"),
+* SVIDulation (an "altzone" variable)
+* MACHination (the "gtime" function)
+* corrections to some time zone data (including corrections to the rules
+ for Great Britain and New Zealand)
+* reference data from the United States Naval Observatory for folks who
+ want to do additional time zones
+* and the 1989 data for Saudi Arabia.
+
+(Since this code will be treated as "part of the implementation" in some places
+and as "part of the application" in others, there's no good way to name
+functions, such as timegm, that are not part of the proposed ANSI C standard;
+such functions have kept their old, underscore-free names in this update.)
+
+And the "dysize" function has disappeared; it was present to allow compilation
+of the "date" command on old BSD systems, and a version of "date" is now
+provided in the package. The "date" command is not created when you "make all"
+since it may lack options provided by the version distributed with your
+operating system, or may not interact with the system in the same way the
+native version does.
+
+Since POSIX frowns on correct leap second handling, the default behavior of
+the "zic" command (in the absence of a "-L" option) has been changed to omit
+leap second information from its output files.
+
+Here is a recipe for acquiring, building, installing, and testing the
+tz distribution on a GNU/Linux or similar host.
+
+ mkdir tz
+ cd tz
+ wget 'ftp://elsie.nci.nih.gov/pub/tz*.tar.gz'
+ gzip -dc tzcode*.tar.gz | tar -xf -
+ gzip -dc tzdata*.tar.gz | tar -xf -
+
+Be sure to read the comments in "Makefile" and make any changes needed
+to make things right for your system, especially if you are using some
+platform other than GNU/Linux. Then run the following commands,
+substituting your desired installation directory for "$HOME/tzdir":
+
+ make TOPDIR=$HOME/tzdir install
+ $HOME/tzdir/etc/zdump -v America/Los_Angeles
+
+To use the new functions, use a "-ltz" option when compiling or linking.
+
+Historical local time information has been included here to:
+
+* provide a compendium of data about the history of civil time
+ that is useful even if the data are not 100% accurate;
+
+* give an idea of the variety of local time rules that have
+ existed in the past and thus an idea of the variety that may be
+ expected in the future;
+
+* provide a test of the generality of the local time rule description
+ system.
+
+The information in the time zone data files is by no means authoritative;
+the files currently do not even attempt to cover all time stamps before
+1970, and there are undoubtedly errors even for time stamps since 1970.
+If you know that the rules are different from those in a file, by all means
+feel free to change file (and please send the changed version to
+tz@elsie.nci.nih.gov for use in the future). Europeans take note!
+
+Thanks to these Timezone Caballeros who've made major contributions to the
+time conversion package: Keith Bostic; Bob Devine; Paul Eggert; Robert Elz;
+Guy Harris; Mark Horton; John Mackin; and Bradley White. Thanks also to
+Michael Bloom, Art Neilson, Stephen Prince, John Sovereign, and Frank Wales
+for testing work, and to Gwillim Law for checking local mean time data.
+None of them are responsible for remaining errors.
+
+Look in the ~ftp/pub directory of elsie.nci.nih.gov
+for updated versions of these files.
+
+Please send comments or information to tz@elsie.nci.nih.gov.
diff --git a/usr.sbin/zic/zdump/Makefile b/usr.sbin/zic/zdump/Makefile
new file mode 100644
index 0000000..2ff7db9
--- /dev/null
+++ b/usr.sbin/zic/zdump/Makefile
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../../contrib/tzcode/zic
+
+PROG= zdump
+MAN= zdump.8
+SRCS= zdump.c ialloc.c scheck.c
+
+CFLAGS+= -DTM_GMTOFF=tm_gmtoff -DTM_ZONE=tm_zone -DSTD_INSPIRED -DPCTS
+CFLAGS+= -DHAVE_LONG_DOUBLE -DTZDIR=\"${SHAREDIR}/zoneinfo\" -Demkdir=mkdir
+CFLAGS+= -I${.CURDIR}/.. -I${.CURDIR}/../../../contrib/tzcode/stdtime
+
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/zic/zdump/Makefile.depend b/usr.sbin/zic/zdump/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/zic/zdump/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/zic/zic/Makefile b/usr.sbin/zic/zic/Makefile
new file mode 100644
index 0000000..c38e3b8
--- /dev/null
+++ b/usr.sbin/zic/zic/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../../contrib/tzcode/zic
+
+PROG= zic
+MAN= zic.8
+SRCS= zic.c ialloc.c scheck.c
+
+CFLAGS+= -DTM_GMTOFF=tm_gmtoff -DTM_ZONE=tm_zone -DSTD_INSPIRED -DPCTS
+CFLAGS+= -DHAVE_LONG_DOUBLE -DTZDIR=\"${SHAREDIR}/zoneinfo\" -Demkdir=mkdir
+CFLAGS+= -DHAVE_STRERROR -DHAVE_UNISTD_H
+CFLAGS+= -I${.CURDIR}/.. -I${.CURDIR}/../../../contrib/tzcode/stdtime
+
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/zic/zic/Makefile.depend b/usr.sbin/zic/zic/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/zic/zic/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/zzz/Makefile b/usr.sbin/zzz/Makefile
new file mode 100644
index 0000000..8c4a9be
--- /dev/null
+++ b/usr.sbin/zzz/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+SCRIPTS=zzz.sh
+MAN= zzz.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/zzz/Makefile.depend b/usr.sbin/zzz/Makefile.depend
new file mode 100644
index 0000000..f80275d
--- /dev/null
+++ b/usr.sbin/zzz/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/zzz/zzz.8 b/usr.sbin/zzz/zzz.8
new file mode 100644
index 0000000..365950f
--- /dev/null
+++ b/usr.sbin/zzz/zzz.8
@@ -0,0 +1,65 @@
+.\" Copyright (c) 2003 Nate Lawson
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must 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 13, 2003
+.Dt ZZZ 8
+.Os
+.Sh NAME
+.Nm zzz
+.Nd suspend an ACPI or APM system
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+The
+.Nm
+utility
+checks for
+.Tn ACPI
+or
+.Tn APM
+support and then suspends the system appropriately.
+For
+.Tn APM ,
+.Pp
+.Dl apm -z
+.Pp
+will be issued.
+For
+.Tn ACPI,
+the configured suspend state will be looked up, checked to see
+if it is supported and,
+.Pp
+.Dl acpiconf -s <state>
+.Pp
+will be issued.
+.Sh SEE ALSO
+.Xr acpi 4 ,
+.Xr apm 4 ,
+.Xr acpiconf 8 ,
+.Xr apm 8
+.Sh AUTHORS
+This manual page was written by
+.An Nate Lawson Aq Mt njl@FreeBSD.org .
diff --git a/usr.sbin/zzz/zzz.sh b/usr.sbin/zzz/zzz.sh
new file mode 100644
index 0000000..ef9527b
--- /dev/null
+++ b/usr.sbin/zzz/zzz.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+#
+# Suspend the system using either ACPI or APM.
+# For APM, "apm -z" will be issued.
+# For ACPI, the configured suspend state will be looked up, checked to see
+# if it is supported, and "acpiconf -s <state>" will be issued.
+#
+# Mark Santcroos <marks@ripe.net>
+#
+# $FreeBSD$
+
+PATH=/sbin:/usr/sbin:/usr/bin:/bin
+
+ACPI_SUSPEND_STATE=hw.acpi.suspend_state
+ACPI_SUPPORTED_STATES=hw.acpi.supported_sleep_state
+APM_SUSPEND_DELAY=machdep.apm_suspend_delay
+
+# Check for ACPI support
+if sysctl $ACPI_SUSPEND_STATE >/dev/null 2>&1; then
+ # Get configured suspend state
+ SUSPEND_STATE=`sysctl -n $ACPI_SUSPEND_STATE `
+
+ # Get list of supported suspend states
+ SUPPORTED_STATES=`sysctl -n $ACPI_SUPPORTED_STATES `
+
+ # Check if the configured suspend state is supported by the system
+ if echo $SUPPORTED_STATES | grep $SUSPEND_STATE >/dev/null; then
+ # execute ACPI style suspend command
+ exec acpiconf -s $SUSPEND_STATE
+ else
+ echo -n "Requested suspend state $SUSPEND_STATE "
+ echo -n "is not supported. "
+ echo "Supported states: $SUPPORTED_STATES"
+ fi
+# Check for APM support
+elif sysctl $APM_SUSPEND_DELAY >/dev/null 2>&1; then
+ # Execute APM style suspend command
+ exec apm -z
+else
+ echo "Error: no ACPI or APM suspend support found."
+fi
+
+exit 1
OpenPOWER on IntegriCloud